From 101274d00b8a77af7595ccc283c89db35f26f4fc Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 20 Aug 2021 15:07:53 +0200 Subject: [PATCH 0001/2916] docs: Fix a few anchors --- docs/images.md | 4 ++-- docs/kluctl_project.md | 4 ++-- docs/sealed-secrets.md | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/images.md b/docs/images.md index c9a3f74c8..3f39678f6 100644 --- a/docs/images.md +++ b/docs/images.md @@ -34,7 +34,7 @@ This is solved via a Jinja2 function that is available in all templates/resource * container * The name of the container inside the affected resource. * image - * The image location, excluding the tag. Please see [supported image registries](#supported-image-registries) to + * The image location, excluding the tag. Please see [supported image registries](#supported-image-registries-and-authentication) to understand which registries are supported.` * latest_version * configures how tags/versions are sorted and thus how the latest image is determined. Can be: @@ -79,7 +79,7 @@ The described `images.get_image` logic however leads to a loosely defined state might be fine in a CI/CD environment, but might be undesired when deploying to production. In that case, it might be desirable to explicitly define which versions need to be deployed. -To achieve this, you can use the `-F FIXED_IMAGE` [argument](./commands.md#-f---fixed-image-text). +To achieve this, you can use the `-F FIXED_IMAGE` [argument](./commands.md#image-arguments). `FIXED_IMAGE` must be in the form of `-F image<:namespace:deployment:container>=result`. For example, to pin the image `registry.gitlab.com/my-group/my-project` to the tag `1.1.2` you'd have to specify `-F registry.gitlab.com/my-group/my-project=registry.gitlab.com/my-group/my-project:1.1.2`. diff --git a/docs/kluctl_project.md b/docs/kluctl_project.md index 09cb606b0..927089ef7 100644 --- a/docs/kluctl_project.md +++ b/docs/kluctl_project.md @@ -129,7 +129,7 @@ targets: ``` * `name` specifies the name of the target. The name must be unique. It is referred in all commands via the - [-t](./commands.md#-t---target-text) option. + [-t](./commands.md#project-arguments) option. * `cluster` specifies the name of the target cluster. The cluster must exist in the [cluster configuration](./cluster-config.md) specified via [clusters](#clusters). * `args` specifies a map of arguments to be passed to the deployment project when it is rendered. Allowed argument names @@ -174,7 +174,7 @@ secretsConfig: #### path A simple local file based source. The path must be relative and multiple places are tried to find the file: 1. Relative to the deployment project root -2. The path provided via [--secrets-dir](./commands.md#--secrets-dir-directory) +2. The path provided via [--secrets-dir](./commands.md#seal) Example: ```yaml diff --git a/docs/sealed-secrets.md b/docs/sealed-secrets.md index 57dec83ac..7c527c983 100644 --- a/docs/sealed-secrets.md +++ b/docs/sealed-secrets.md @@ -84,7 +84,7 @@ with: file. If not specified, the base directory defaults to the subdirectory `.sealed-secrets` in the kluctl project root diretory. * `deployment_name`: The deployment name, which defaults to the kluctl project directories base name. It can also be -overridden with [--deployment-name](./commands.md#--deployment-name-text). +overridden with [--deployment-name](./commands.md#project-arguments). * `rendered_output_pattern`: The rendered outputPattern as described above. * `relative_sealme_file_dir`: The relative path from the deployment root directory. * `file_name`: The filename of the sealed secret, excluding the `.sealme` extension. @@ -92,7 +92,7 @@ overridden with [--deployment-name](./commands.md#--deployment-name-text). # Content Hashes and re-sealing Sealed secrets are stored together with hashes of all individual secret entries. These hashes are then used to avoid unnecessary re-sealing in future [seal](./commands.md#seal) invocations. If you want to force re-sealing, use the -[--force-reseal](./commands.md#--force-reseal) option. +[--force-reseal](./commands.md#seal) option. # Clusters and namespaces Sealed secrets are usually only decryptable by one cluster, simply because each cluster has its own set of randomly From 73dd92037733378c45fdc7d17cb8afdb2dc1e903 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 20 Aug 2021 15:21:52 +0200 Subject: [PATCH 0002/2916] fix: Minor cleanups --- kluctl/kluctl_project/kluctl_project.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/kluctl/kluctl_project/kluctl_project.py b/kluctl/kluctl_project/kluctl_project.py index f4af98458..55c106b87 100644 --- a/kluctl/kluctl_project/kluctl_project.py +++ b/kluctl/kluctl_project/kluctl_project.py @@ -6,7 +6,6 @@ import re import shutil import tarfile -from collections import Iterator from contextlib import contextmanager from io import BytesIO from tempfile import TemporaryDirectory @@ -18,7 +17,7 @@ from kluctl.utils.exceptions import InvalidKluctlProjectConfig from kluctl.utils.git_utils import parse_git_url, clone_project, get_git_commit, update_git_cache, git_ls_remote, \ get_git_ref -from kluctl.utils.jinja2_utils import render_str, render_dict_strs +from kluctl.utils.jinja2_utils import render_dict_strs from kluctl.utils.k8s_cluster_base import load_cluster_config from kluctl.utils.utils import get_tmp_base_dir, MyThreadPoolExecutor, copy_dict from kluctl.utils.yaml_utils import yaml_load_file From 96053056426fa15e9cd207c005aee472d29fbe1e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 20 Aug 2021 15:29:55 +0200 Subject: [PATCH 0003/2916] feat: Implement list-targets command --- docs/commands.md | 16 ++++++++++++++++ kluctl/cli/command_stubs.py | 9 +++++++++ kluctl/cli/commands.py | 9 +++++++++ 3 files changed, 34 insertions(+) diff --git a/docs/commands.md b/docs/commands.md index 562f366e5..a37e8754d 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -434,3 +434,19 @@ The following arguments are available: --image TEXT Name of the image. Can be specified multiple times [required] ``` + +## list-targets + +Usage: kluctl list-targets [OPTIONS] + + Outputs a yaml list with all target, including dynamic targets + + + +The following arguments are available: + +``` + Misc arguments: + -o, --output TEXT Specify output target file. Can be specified multiple times +``` + diff --git a/kluctl/cli/command_stubs.py b/kluctl/cli/command_stubs.py index a72e54bcc..092919345 100644 --- a/kluctl/cli/command_stubs.py +++ b/kluctl/cli/command_stubs.py @@ -131,3 +131,12 @@ def render_command_stub(obj, **kwargs): def list_images_command_stub(obj, **kwargs): from kluctl.cli.commands import list_images_command list_images_command(obj, kwargs) + +@cli_group.command("list-targets", + help="Outputs a yaml list with all target, including dynamic targets") +@kluctl_project_args() +@misc_arguments(output=True) +@click.pass_obj +def list_targets_stub(obj, **kwargs): + from kluctl.cli.commands import list_targets_command + list_targets_command(obj, kwargs) diff --git a/kluctl/cli/commands.py b/kluctl/cli/commands.py index 6904058d2..621043c78 100644 --- a/kluctl/cli/commands.py +++ b/kluctl/cli/commands.py @@ -10,10 +10,12 @@ from kluctl import get_kluctl_package_dir from kluctl.cli.utils import output_diff_result, build_seen_images, output_yaml_result, \ output_validate_result, project_command_context +from kluctl.kluctl_project.kluctl_project import load_kluctl_project_from_args from kluctl.utils.exceptions import CommandError from kluctl.utils.inclusion import Inclusion from kluctl.utils.k8s_object_utils import get_long_object_name_from_ref, ObjectRef from kluctl.utils.utils import get_tmp_base_dir, duration +from kluctl.utils.yaml_utils import yaml_dump logger = logging.getLogger(__name__) @@ -144,3 +146,10 @@ def list_images_command(obj, kwargs): } output_yaml_result(kwargs["output"], result) + +def list_targets_command(obj, kwargs): + with load_kluctl_project_from_args(kwargs) as kluctl_project: + kluctl_project.load(True) + kluctl_project.load_targets() + + output_yaml_result(kwargs["output"], kluctl_project.targets) From 883cdc7639425b95843a1c00d966531f3cec739b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 20 Aug 2021 15:39:36 +0200 Subject: [PATCH 0004/2916] feat: Also output involved repos in list-targets --- kluctl/cli/commands.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/kluctl/cli/commands.py b/kluctl/cli/commands.py index 621043c78..4f2a42315 100644 --- a/kluctl/cli/commands.py +++ b/kluctl/cli/commands.py @@ -152,4 +152,9 @@ def list_targets_command(obj, kwargs): kluctl_project.load(True) kluctl_project.load_targets() - output_yaml_result(kwargs["output"], kluctl_project.targets) + result = { + "involved_repos": kluctl_project.involved_repos, + "targets": kluctl_project.targets, + } + + output_yaml_result(kwargs["output"], result) From 68226581c324d4358a44a77b8ac50f1362198c3c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 20 Aug 2021 16:49:48 +0200 Subject: [PATCH 0005/2916] feat: Allow archiving projects and loading it from the archives --- docs/commands.md | 20 +++++ kluctl/cli/command_stubs.py | 16 ++++ kluctl/cli/commands.py | 8 +- kluctl/cli/main_cli_group.py | 3 + kluctl/cli/utils.py | 3 - kluctl/kluctl_project/kluctl_project.py | 109 +++++++++++++----------- kluctl/seal/seal_command.py | 3 - 7 files changed, 104 insertions(+), 58 deletions(-) diff --git a/docs/commands.md b/docs/commands.md index a37e8754d..51ff6681a 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -23,6 +23,7 @@ The following sets of common arguments are available: --local-deployment DIRECTORY Local deployment directory. Overrides the project from .kluctl.yml --local-sealed-secrets DIRECTORY Local sealed-secrets directory. Overrides the project from .kluctl.yml + --from-archive FILE Load project (.kluctl.yml, cluster, ...) from archive. --deployment-name TEXT Name of the kluctl deployment. Used when resolving sealed-secrets. Defaults to the base name of --local-deployment/--project-url --cluster TEXT Specify/Override cluster @@ -450,3 +451,22 @@ The following arguments are available: -o, --output TEXT Specify output target file. Can be specified multiple times ``` + +## archive + +Usage: kluctl archive [OPTIONS] + + Write project and all related components into single tgz. + + This archive can then be used with `--from-archive`. + + + +The following arguments are available: + +``` + Misc arguments: + --output PATH Path to .tgz to write project to. + --reproducible Make .tgz reproducible. +``` + diff --git a/kluctl/cli/command_stubs.py b/kluctl/cli/command_stubs.py index 092919345..c5e966fa7 100644 --- a/kluctl/cli/command_stubs.py +++ b/kluctl/cli/command_stubs.py @@ -140,3 +140,19 @@ def list_images_command_stub(obj, **kwargs): def list_targets_stub(obj, **kwargs): from kluctl.cli.commands import list_targets_command list_targets_command(obj, kwargs) + +@cli_group.command("archive", + help="Write project and all related components into single tgz.\n\n" + "This archive can then be used with `--from-archive`.") +@kluctl_project_args() +@optgroup.group("Misc arguments") +@optgroup.option("--output", + help="Path to .tgz to write project to.", + type=click.Path(file_okay=True)) +@optgroup.option("--reproducible", + help="Make .tgz reproducible.", + is_flag=True) +@click.pass_obj +def archive_command_stub(obj, **kwargs): + from kluctl.cli.commands import archive_command + archive_command(obj, kwargs) diff --git a/kluctl/cli/commands.py b/kluctl/cli/commands.py index 4f2a42315..95064cc41 100644 --- a/kluctl/cli/commands.py +++ b/kluctl/cli/commands.py @@ -15,7 +15,6 @@ from kluctl.utils.inclusion import Inclusion from kluctl.utils.k8s_object_utils import get_long_object_name_from_ref, ObjectRef from kluctl.utils.utils import get_tmp_base_dir, duration -from kluctl.utils.yaml_utils import yaml_dump logger = logging.getLogger(__name__) @@ -149,12 +148,13 @@ def list_images_command(obj, kwargs): def list_targets_command(obj, kwargs): with load_kluctl_project_from_args(kwargs) as kluctl_project: - kluctl_project.load(True) - kluctl_project.load_targets() - result = { "involved_repos": kluctl_project.involved_repos, "targets": kluctl_project.targets, } output_yaml_result(kwargs["output"], result) + +def archive_command(obj, kwargs): + with load_kluctl_project_from_args(kwargs) as kluctl_project: + kluctl_project.create_tgz(kwargs["output"], kwargs["reproducible"]) diff --git a/kluctl/cli/main_cli_group.py b/kluctl/cli/main_cli_group.py index 37a2eec5a..a536508a3 100644 --- a/kluctl/cli/main_cli_group.py +++ b/kluctl/cli/main_cli_group.py @@ -198,6 +198,9 @@ def kluctl_project_args(with_d=True, with_a=True, with_t=True): options.append(optgroup.option("--local-sealed-secrets", help="Local sealed-secrets directory. Overrides the project from .kluctl.yml", type=click.Path(file_okay=False))) + options.append(optgroup.option("--from-archive", + help="Load project (.kluctl.yml, cluster, ...) from archive.", + type=click.Path(dir_okay=False))) options.append(optgroup.option("--deployment-name", help="Name of the kluctl deployment. Used when resolving sealed-secrets. " "Defaults to the base name of --local-deployment/--project-url")) diff --git a/kluctl/cli/utils.py b/kluctl/cli/utils.py index def53236b..a56383cc6 100644 --- a/kluctl/cli/utils.py +++ b/kluctl/cli/utils.py @@ -102,9 +102,6 @@ def project_command_context(kwargs, force_offline_images=False, force_offline_kubernetes=False) -> ContextManager[CommandContext]: with load_kluctl_project_from_args(kwargs) as kluctl_project: - kluctl_project.load(True) - kluctl_project.load_targets() - target = None if kwargs["target"]: target = kluctl_project.find_target(kwargs["target"]) diff --git a/kluctl/kluctl_project/kluctl_project.py b/kluctl/kluctl_project/kluctl_project.py index 55c106b87..c405ec9ed 100644 --- a/kluctl/kluctl_project/kluctl_project.py +++ b/kluctl/kluctl_project/kluctl_project.py @@ -7,20 +7,19 @@ import shutil import tarfile from contextlib import contextmanager -from io import BytesIO -from tempfile import TemporaryDirectory +from tempfile import TemporaryDirectory, NamedTemporaryFile from typing import ContextManager import jsonschema from kluctl.schemas.schema import validate_kluctl_project_config, parse_git_project, target_config_schema -from kluctl.utils.exceptions import InvalidKluctlProjectConfig +from kluctl.utils.exceptions import InvalidKluctlProjectConfig, CommandError from kluctl.utils.git_utils import parse_git_url, clone_project, get_git_commit, update_git_cache, git_ls_remote, \ get_git_ref from kluctl.utils.jinja2_utils import render_dict_strs from kluctl.utils.k8s_cluster_base import load_cluster_config from kluctl.utils.utils import get_tmp_base_dir, MyThreadPoolExecutor, copy_dict -from kluctl.utils.yaml_utils import yaml_load_file +from kluctl.utils.yaml_utils import yaml_load_file, yaml_save_file logger = logging.getLogger(__name__) @@ -69,39 +68,51 @@ def __init__(self, deployment_name, project_url, project_ref, config_file, local self.git_cache_up_to_date = {} self.refs_for_urls = {} - def create_tgz(self): - buf = BytesIO() - with gzip.GzipFile(mode="wb", compresslevel=9, fileobj=buf, mtime=0) as gz: - with tarfile.TarFile.taropen("", mode="w", fileobj=gz) as tar: - def mf_filter(ti: tarfile.TarInfo): - if ".git" in os.path.split(ti.name): - return None - # make the tar reproducible (always same hash) - ti.uid = 0 - ti.gid = 0 - ti.mtime = 0 - return ti - - tar.add(self.config_file, ".kluctl.yml", filter=mf_filter) - tar.add(self.deployment_dir, "deployment/%s" % os.path.basename(self.deployment_dir), True, filter=mf_filter) - tar.add(self.clusters_dir, "clusters", True, filter=mf_filter) - tar.add(self.sealed_secrets_dir, "sealed-secrets", True, filter=mf_filter) - - return buf.getvalue() + def create_tgz(self, path, reproducible): + with open(path, mode="wb") as f: + with gzip.GzipFile(mode="wb", compresslevel=9, fileobj=f, mtime=0) as gz: + with tarfile.TarFile.taropen("", mode="w", fileobj=gz) as tar: + def mf_filter(ti: tarfile.TarInfo): + if ".git" in os.path.split(ti.name): + return None + if reproducible: + # make the tar reproducible (always same hash) + ti.uid = 0 + ti.gid = 0 + ti.mtime = 0 + return ti + + metadata_yaml = { + "involved_repos": self.involved_repos, + "targets": self.targets, + } + with NamedTemporaryFile(dir=get_tmp_base_dir()) as tmp: + yaml_save_file(metadata_yaml, tmp.name) + tar.add(tmp.name, "metadata.yml", filter=mf_filter) + tar.add(self.config_file, ".kluctl.yml", filter=mf_filter) + tar.add(self.deployment_dir, "deployment/%s" % os.path.basename(self.deployment_dir), True, filter=mf_filter) + tar.add(self.clusters_dir, "clusters", True, filter=mf_filter) + tar.add(self.sealed_secrets_dir, "sealed-secrets", True, filter=mf_filter) @staticmethod - def from_tgz(tgz_buf, tmp_dir): - with tarfile.open(mode="r:gz", fileobj=BytesIO(tgz_buf)) as tgz: - tgz.extractall(tmp_dir) + def from_tgz(path, tmp_dir): + with open(path, mode="rb") as f: + with tarfile.open(mode="r:gz", fileobj=f) as tgz: + tgz.extractall(tmp_dir) deployment_dir = os.path.join(tmp_dir, "deployment") deployment_name = os.listdir(deployment_dir)[0] deployment_dir = os.path.join(deployment_dir, deployment_name) - return KluctlProject(deployment_name, None, None, - os.path.join(tmp_dir, ".kluctl.yml"), - os.path.join(tmp_dir, "clusters"), - deployment_dir, - os.path.join(tmp_dir, "sealed-secrets"), - tmp_dir) + project = KluctlProject(deployment_name, None, None, + os.path.join(tmp_dir, ".kluctl.yml"), + os.path.join(tmp_dir, "clusters"), + deployment_dir, + os.path.join(tmp_dir, "sealed-secrets"), + tmp_dir) + + metadata = yaml_load_file(os.path.join(tmp_dir, "metadata.yml")) + project.involved_repos = metadata["involved_repos"] + project.targets = metadata["targets"] + return project def build_clone_dir(self, url): url = parse_git_url(url) @@ -404,21 +415,23 @@ def load_kluctl_project(project_url, project_ref, config_file, @contextmanager def load_kluctl_project_from_args(kwargs) -> ContextManager[KluctlProject]: - deployment_name = kwargs["deployment_name"] - if deployment_name is None: - if kwargs["project_url"]: - url = parse_git_url(kwargs["project_url"]) - deployment_name = url.path.split("/")[-1] - elif kwargs["local_deployment"]: - deployment_name = os.path.basename(kwargs["local_deployment"]) - else: - deployment_name = os.path.basename(os.getcwd()) with TemporaryDirectory(dir=get_tmp_base_dir()) as tmp_dir: - project = KluctlProject(deployment_name, kwargs["project_url"], kwargs["project_ref"], kwargs["project_config"], kwargs["local_clusters"], kwargs["local_deployment"], kwargs["local_sealed_secrets"], tmp_dir) - yield project - -@contextmanager -def load_kluctl_project_from_tgz(tgz_buf) -> ContextManager[KluctlProject]: - with TemporaryDirectory(dir=get_tmp_base_dir()) as tmp_dir: - project = KluctlProject.from_tgz(tgz_buf, tmp_dir) + if kwargs["from_archive"]: + if any(kwargs[x] for x in ["project_url", "project_ref", "project_config", "local_clusters", "local_deployment", "local_sealed_secrets"]): + raise CommandError("--from-archive can not be combined with any other project related option") + project = KluctlProject.from_tgz(kwargs["from_archive"], tmp_dir) + project.load(False) + else: + deployment_name = kwargs["deployment_name"] + if deployment_name is None: + if kwargs["project_url"]: + url = parse_git_url(kwargs["project_url"]) + deployment_name = url.path.split("/")[-1] + elif kwargs["local_deployment"]: + deployment_name = os.path.basename(kwargs["local_deployment"]) + else: + deployment_name = os.path.basename(os.getcwd()) + project = KluctlProject(deployment_name, kwargs["project_url"], kwargs["project_ref"], kwargs["project_config"], kwargs["local_clusters"], kwargs["local_deployment"], kwargs["local_sealed_secrets"], tmp_dir) + project.load(True) + project.load_targets() yield project diff --git a/kluctl/seal/seal_command.py b/kluctl/seal/seal_command.py index 8cd3b38b3..85bee5160 100644 --- a/kluctl/seal/seal_command.py +++ b/kluctl/seal/seal_command.py @@ -48,9 +48,6 @@ def seal_command_for_target(kwargs, kluctl_project, target, sealing_config, secr def seal_command(obj, kwargs): with load_kluctl_project_from_args(kwargs) as kluctl_project: - kluctl_project.load(True) - kluctl_project.load_targets() - secrets_loader = SecretsLoader(kluctl_project, kwargs["secrets_dir"]) base_targets = [] From 7484acd562e084cadb96e38610d120c435f4628c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 20 Aug 2021 18:10:48 +0200 Subject: [PATCH 0006/2916] fix: Don't try to resolved sealed secrets when sealing --- kluctl/deployment/deployment_collection.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index 60fc59eeb..d8e7ee24f 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -81,8 +81,9 @@ def render_deployments(self): for job in jobs: job.result() - for d in self.deployments: - d.resolve_sealed_secrets() + if not self.for_seal: + for d in self.deployments: + d.resolve_sealed_secrets() self.is_rendered = True def build_kustomize_objects(self): From eba10f20ee84753dc97666f132a389e4cec5bf5b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 20 Aug 2021 18:11:08 +0200 Subject: [PATCH 0007/2916] fix: Don't render baseTarget in target --- kluctl/kluctl_project/kluctl_project.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/kluctl/kluctl_project/kluctl_project.py b/kluctl/kluctl_project/kluctl_project.py index c405ec9ed..8b0807c35 100644 --- a/kluctl/kluctl_project/kluctl_project.py +++ b/kluctl/kluctl_project/kluctl_project.py @@ -258,18 +258,20 @@ def load_targets(self): for base_target in self.config.get("targets", []): if "targetConfig" not in base_target: - dynamic_targets = [copy_dict(base_target)] + target = self.render_target(base_target) + dynamic_targets = [target] else: dynamic_targets = self.build_dynamic_targets(base_target) + for i in range(len(dynamic_targets)): + dynamic_targets[i] = self.render_target(dynamic_targets[i]) + dynamic_targets[i]["baseTarget"] = base_target for dt in dynamic_targets: - dt2 = self.render_target(dt) - - if dt2["name"] in target_names: - logger.warning("Duplicate target %s" % dt2["name"]) + if dt["name"] in target_names: + logger.warning("Duplicate target %s" % dt["name"]) else: - target_names.add(dt2["name"]) - self.targets.append(dt2) + target_names.add(dt["name"]) + self.targets.append(dt) def render_target(self, target): errors = [] @@ -348,7 +350,6 @@ def build_dynamic_targets(self, base_target): try: target_config_file = self.load_target_config(config_path) target = copy_dict(base_target) - target["baseTarget"] = base_target target["targetConfig"]["ref"] = ref target["targetConfig"]["defaultBranch"] = default_branch From 9081d8a259b454d3c91d149242dcf7ccdd645c3d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 20 Aug 2021 18:11:22 +0200 Subject: [PATCH 0008/2916] fix: Use correct sealedSecrets field when sealing --- kluctl/seal/deployment_sealer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kluctl/seal/deployment_sealer.py b/kluctl/seal/deployment_sealer.py index 767adf6fb..b62a520bc 100644 --- a/kluctl/seal/deployment_sealer.py +++ b/kluctl/seal/deployment_sealer.py @@ -26,9 +26,9 @@ def guess_sealed_secrets_controller(self): self.sealed_secrets_controller_name = "sealed-secrets" def seal_deployment(self): - output_suffix = self.deployment_project.conf.get("secrets", {}).get("outputPattern") + output_suffix = self.deployment_project.conf.get("sealedSecrets", {}).get("outputPattern") if output_suffix is None: - raise InvalidKluctlProjectConfig("secrets.outputPattern is not defined") + raise InvalidKluctlProjectConfig("sealedSecrets.outputPattern is not defined") for dirpath, dirnames, filenames in os.walk(self.deployment_collection.tmpdir): rel_dir = os.path.relpath(dirpath, self.deployment_collection.tmpdir) From dc82184652db5869785cc0c3533210f7a44b8808 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sat, 21 Aug 2021 00:03:00 +0200 Subject: [PATCH 0009/2916] fix: Fix reproducibility of archives and add deployment name to metadata --- kluctl/kluctl_project/kluctl_project.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/kluctl/kluctl_project/kluctl_project.py b/kluctl/kluctl_project/kluctl_project.py index 8b0807c35..1b35f3b91 100644 --- a/kluctl/kluctl_project/kluctl_project.py +++ b/kluctl/kluctl_project/kluctl_project.py @@ -70,7 +70,7 @@ def __init__(self, deployment_name, project_url, project_ref, config_file, local def create_tgz(self, path, reproducible): with open(path, mode="wb") as f: - with gzip.GzipFile(mode="wb", compresslevel=9, fileobj=f, mtime=0) as gz: + with gzip.GzipFile(filename="reproducible" if reproducible else None, mode="wb", compresslevel=9, fileobj=f, mtime=0 if reproducible else None) as gz: with tarfile.TarFile.taropen("", mode="w", fileobj=gz) as tar: def mf_filter(ti: tarfile.TarInfo): if ".git" in os.path.split(ti.name): @@ -85,12 +85,13 @@ def mf_filter(ti: tarfile.TarInfo): metadata_yaml = { "involved_repos": self.involved_repos, "targets": self.targets, + "deployment_name": self.deployment_name, } with NamedTemporaryFile(dir=get_tmp_base_dir()) as tmp: yaml_save_file(metadata_yaml, tmp.name) tar.add(tmp.name, "metadata.yml", filter=mf_filter) tar.add(self.config_file, ".kluctl.yml", filter=mf_filter) - tar.add(self.deployment_dir, "deployment/%s" % os.path.basename(self.deployment_dir), True, filter=mf_filter) + tar.add(self.deployment_dir, "deployment/%s" % self.deployment_name, True, filter=mf_filter) tar.add(self.clusters_dir, "clusters", True, filter=mf_filter) tar.add(self.sealed_secrets_dir, "sealed-secrets", True, filter=mf_filter) @@ -99,8 +100,9 @@ def from_tgz(path, tmp_dir): with open(path, mode="rb") as f: with tarfile.open(mode="r:gz", fileobj=f) as tgz: tgz.extractall(tmp_dir) + metadata = yaml_load_file(os.path.join(tmp_dir, "metadata.yml")) deployment_dir = os.path.join(tmp_dir, "deployment") - deployment_name = os.listdir(deployment_dir)[0] + deployment_name = metadata["deployment_name"] deployment_dir = os.path.join(deployment_dir, deployment_name) project = KluctlProject(deployment_name, None, None, os.path.join(tmp_dir, ".kluctl.yml"), @@ -108,8 +110,6 @@ def from_tgz(path, tmp_dir): deployment_dir, os.path.join(tmp_dir, "sealed-secrets"), tmp_dir) - - metadata = yaml_load_file(os.path.join(tmp_dir, "metadata.yml")) project.involved_repos = metadata["involved_repos"] project.targets = metadata["targets"] return project From 8bb07cfd04d9acec66668232271f942310205513 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sat, 21 Aug 2021 00:29:52 +0200 Subject: [PATCH 0010/2916] feat: Implement --ignore-annotations for diff command --- docs/commands.md | 1 + kluctl/cli/commands.py | 4 +++- kluctl/cli/main_cli_group.py | 3 +++ kluctl/deployment/deployment_collection.py | 10 +++++----- kluctl/deployment/deployment_project.py | 6 +++++- 5 files changed, 17 insertions(+), 7 deletions(-) diff --git a/docs/commands.md b/docs/commands.md index 51ff6681a..a49ffcac1 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -200,6 +200,7 @@ In addition, the following arguments are available: for more details. --ignore-tags Ignores changes in tags when diffing --ignore-labels Ignores changes in labels when diffing + --ignore-annotations Ignores changes in annotations when diffing --ignore-order Ignores changes in order when diffing -o, --output TEXT Specify output format and target file, in the format 'format=path'. Format can either be 'diff' or 'yaml'. Can be specified multiple times. The actual format for diff --git a/kluctl/cli/commands.py b/kluctl/cli/commands.py index 95064cc41..fd2383742 100644 --- a/kluctl/cli/commands.py +++ b/kluctl/cli/commands.py @@ -60,7 +60,9 @@ def deploy_command2(obj, kwargs, cmd_ctx): def diff_command(obj, kwargs): with project_command_context(kwargs) as cmd_ctx: - result = cmd_ctx.deployment_collection.diff(cmd_ctx.k8s_cluster, kwargs["force_apply"], kwargs["replace_on_error"], kwargs["ignore_tags"], kwargs["ignore_labels"], kwargs["ignore_order"]) + result = cmd_ctx.deployment_collection.diff( + cmd_ctx.k8s_cluster, kwargs["force_apply"], kwargs["replace_on_error"], + kwargs["ignore_tags"], kwargs["ignore_labels"], kwargs["ignore_annotations"], kwargs["ignore_order"]) deleted_objects = cmd_ctx.deployment_collection.find_purge_objects(cmd_ctx.k8s_cluster) output_diff_result(kwargs["output"], cmd_ctx.deployment_collection, result, deleted_objects) sys.exit(1 if result.errors else 0) diff --git a/kluctl/cli/main_cli_group.py b/kluctl/cli/main_cli_group.py index a536508a3..7a7cffb08 100644 --- a/kluctl/cli/main_cli_group.py +++ b/kluctl/cli/main_cli_group.py @@ -154,6 +154,9 @@ def misc_arguments(yes=False, dry_run=False, parallel=False, force_apply=False, options.append(optgroup.option("--ignore-labels", help="Ignores changes in labels when diffing", default=False, is_flag=True)) + options.append(optgroup.option("--ignore-annotations", + help="Ignores changes in annotations when diffing", + default=False, is_flag=True)) if ignore_order: options.append(optgroup.option("--ignore-order", help="Ignores changes in order when diffing", diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index d8e7ee24f..347ec223b 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -131,17 +131,17 @@ def deploy(self, k8s_cluster, parallel, force_apply, replace_on_error, abort_on_ self.build_kustomize_objects() applied_objects = self.do_deploy(k8s_cluster, force_apply, replace_on_error, False, not parallel, abort_on_error) - new_objects, changed_objects = self.do_diff(k8s_cluster, applied_objects, False, False, False) + new_objects, changed_objects = self.do_diff(k8s_cluster, applied_objects, False, False, False, False) return DeployDiffResult(new_objects=new_objects, changed_objects=changed_objects, errors=list(self.api_errors), warnings=list(self.api_warnings)) - def diff(self, k8s_cluster, force_apply, replace_on_error, ignore_tags, ignore_labels, ignore_order): + def diff(self, k8s_cluster, force_apply, replace_on_error, ignore_tags, ignore_labels, ignore_annotations, ignore_order): self.clear_errors_and_warnings() self.render_deployments() self.build_kustomize_objects() applied_objects = self.do_deploy(k8s_cluster, force_apply, replace_on_error, True, False, False) - new_objects, changed_objects = self.do_diff(k8s_cluster, applied_objects, ignore_tags, ignore_labels, ignore_order) + new_objects, changed_objects = self.do_diff(k8s_cluster, applied_objects, ignore_tags, ignore_labels, ignore_annotations, ignore_order) return DeployDiffResult(new_objects=new_objects, changed_objects=changed_objects, errors=list(self.api_errors), warnings=list(self.api_warnings)) @@ -283,7 +283,7 @@ def do_deploy(self, k8s_cluster, force_apply, replace_on_error, dry_run, ordered return self.do_patch(k8s_cluster, force_apply, replace_on_error, dry_run, ordered, abort_on_error) - def do_diff(self, k8s_cluster, applied_objects, ignore_tags, ignore_labels, ignore_order): + def do_diff(self, k8s_cluster, applied_objects, ignore_tags, ignore_labels, ignore_annotations, ignore_order): diff_objects = {} normalized_diff_objects = {} normalized_remote_objects = {} @@ -291,7 +291,7 @@ def do_diff(self, k8s_cluster, applied_objects, ignore_tags, ignore_labels, igno if not d.check_inclusion_for_deploy(): continue - ignore_for_diffs = d.deployment_project.get_ignore_for_diffs(ignore_tags, ignore_labels) + ignore_for_diffs = d.deployment_project.get_ignore_for_diffs(ignore_tags, ignore_labels, ignore_annotations) for x in d.objects: ref = get_object_ref(x) if ref not in applied_objects: diff --git a/kluctl/deployment/deployment_project.py b/kluctl/deployment/deployment_project.py index d6ab255d6..d7153ba57 100644 --- a/kluctl/deployment/deployment_project.py +++ b/kluctl/deployment/deployment_project.py @@ -197,7 +197,7 @@ def get_tags(self): tags.add(t) return tags - def get_ignore_for_diffs(self, ignore_tags, ignore_labels): + def get_ignore_for_diffs(self, ignore_tags, ignore_labels, ignore_annotations): ret = [] for d, inc in self.get_parents(): ret += d.conf.get("ignoreForDiff", []) @@ -209,5 +209,9 @@ def get_ignore_for_diffs(self, ignore_tags, ignore_labels): ret.append({ 'fieldPath': 'metadata.labels.*', }) + if ignore_annotations: + ret.append({ + 'fieldPath': 'metadata.annotations.*', + }) return ret From 1534de86ef106cc07a33eb309f99276b3dafcbbc Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sat, 21 Aug 2021 01:21:07 +0200 Subject: [PATCH 0011/2916] fix: Fix missing files/packages --- kluctl/schemas/__init__.py | 0 setup.py | 13 +++++++++++++ 2 files changed, 13 insertions(+) create mode 100644 kluctl/schemas/__init__.py diff --git a/kluctl/schemas/__init__.py b/kluctl/schemas/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/setup.py b/setup.py index 869431f4e..8c3602dae 100644 --- a/setup.py +++ b/setup.py @@ -17,6 +17,15 @@ with open("README.md", "r", encoding="utf-8") as fh: long_description = fh.read() +datas = [] +for dirpath, dirnames, filenames in os.walk("./kluctl"): + for f in filenames: + if f.endswith(".py") or f.endswith(".pyc"): + continue + p = os.path.join("..", dirpath, f) + datas.append(p) +print("datas=%s" % datas) + # This call to setup() does all the work setup( name=name, @@ -41,6 +50,10 @@ ], packages=find_packages(), include_package_data=True, + package_data={ + "": datas, + "kluctl": ["bootstrap/**"], + }, install_requires=[ "click>=8.0.1", "click-option-group>=0.5.3", From d9329381533c5514a407c41e5f5e3a9680aac17d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sat, 21 Aug 2021 01:46:51 +0200 Subject: [PATCH 0012/2916] feat: Allow to pass git credentials via environment --- kluctl/image_registries/__init__.py | 25 ++++++++----------------- kluctl/utils/env_config_sets.py | 22 ++++++++++++++++++++++ kluctl/utils/git_utils.py | 10 +++++++++- 3 files changed, 39 insertions(+), 18 deletions(-) create mode 100644 kluctl/utils/env_config_sets.py diff --git a/kluctl/image_registries/__init__.py b/kluctl/image_registries/__init__.py index 6936c2b44..043a56cab 100644 --- a/kluctl/image_registries/__init__.py +++ b/kluctl/image_registries/__init__.py @@ -1,7 +1,7 @@ import os -import re from kluctl.image_registries.generic_registry import GenericRegistry +from kluctl.utils.env_config_sets import parse_env_config_sets def init_image_registries(): @@ -12,26 +12,17 @@ def init_image_registries(): generic = GenericRegistry(default_tlsverify) ret.append(generic) - r = re.compile(r"KLUCTL_REGISTRY_(\d+)_.*") - - registry_indexes = set() - for env_name in os.environ.keys(): - m = r.match(env_name) - if m: - registry_indexes.add(m.group(1)) - - def add_registry(base_name): - host = os.environ.get(f"{base_name}_HOST") - username = os.environ.get(f"{base_name}_USERNAME") - password = os.environ.get(f"{base_name}_PASSWORD") - tlsverify = os.environ.get(f"{base_name}_TLSVERIFY") + def add_registry(s): + host = s.get("HOST") + username = s.get("USERNAME") + password = s.get("PASSWORD") + tlsverify = s.get("TLSVERIFY") if tlsverify is not None: tlsverify = tlsverify in ["True", "true", "yes", "1"] if username and password: generic.add_creds(host, username, password, tlsverify) - add_registry(f"KLUCTL_REGISTRY") - for idx in registry_indexes: - add_registry(f"KLUCTL_REGISTRY_{idx}") + for idx, s in parse_env_config_sets("KLUCTL_REGISTRY").items(): + add_registry(s) return ret diff --git a/kluctl/utils/env_config_sets.py b/kluctl/utils/env_config_sets.py new file mode 100644 index 000000000..8dd4d09e4 --- /dev/null +++ b/kluctl/utils/env_config_sets.py @@ -0,0 +1,22 @@ +import os +import re + + +def parse_env_config_sets(prefix): + r = re.compile(r"%s_(\d+)_(.*)" % prefix) + r2 = re.compile(r"%s_(.*)" % prefix) + + ret = {} + for env_name, env_value in os.environ.items(): + m = r.match(env_name) + if m: + idx = m.group(1) + key = m.group(2) + ret.setdefault(idx, {})[key] = env_value + else: + m = r2.match(env_name) + if m: + key = m.group(1) + ret.setdefault(None, {})[key] = env_value + + return ret diff --git a/kluctl/utils/git_utils.py b/kluctl/utils/git_utils.py index 406001a6f..e7685a671 100644 --- a/kluctl/utils/git_utils.py +++ b/kluctl/utils/git_utils.py @@ -11,6 +11,7 @@ import filelock from git import Git +from kluctl.utils.env_config_sets import parse_env_config_sets from kluctl.utils.gitlab.fast_ls_remote import gitlab_fast_ls_remote from kluctl.utils.utils import get_tmp_base_dir @@ -24,7 +25,14 @@ class GitCredentialsStore: def get_credentials(self, host): return None, None -credentials_store = GitCredentialsStore() +class GitCredentialStoreEnv(GitCredentialsStore): + def get_credentials(self, host): + for idx, s in parse_env_config_sets("KLUCTL_GIT").items(): + if s.get("HOST") == host: + return s.get("USERNAME"), s.get("PASSWORD") + return None, None + +credentials_store = GitCredentialStoreEnv() def set_git_credentials_store(store): global credentials_store From b1350c2d4f10c847f1a40d58591349f3fe72015b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sat, 21 Aug 2021 02:05:36 +0200 Subject: [PATCH 0013/2916] fix: Fix missing argument in diff call --- kluctl/cli/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kluctl/cli/commands.py b/kluctl/cli/commands.py index fd2383742..2dbf725b5 100644 --- a/kluctl/cli/commands.py +++ b/kluctl/cli/commands.py @@ -53,7 +53,7 @@ def deploy_command2(obj, kwargs, cmd_ctx): logger.info("Running full diff after deploy") cmd_ctx.deployment_collection.update_remote_objects_from_diff(diff_result) cmd_ctx.deployment_collection.inclusion = Inclusion() - diff_result = cmd_ctx.deployment_collection.diff(cmd_ctx.k8s_cluster, kwargs["force_apply"], False, False, False, False) + diff_result = cmd_ctx.deployment_collection.diff(cmd_ctx.k8s_cluster, kwargs["force_apply"], False, False, False, False, False) output_diff_result([kwargs["full_diff_after_deploy"]], cmd_ctx.deployment_collection, diff_result, deleted_objects) if diff_result.errors: sys.exit(1) From cd10584dec0b891ada9ed8fcb34ebfba3f3f3dc2 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sat, 21 Aug 2021 03:02:33 +0200 Subject: [PATCH 0014/2916] feat: Speed up kustomize build in case of simple variants --- kluctl/deployment/helm_chart.py | 2 +- kluctl/deployment/kustomize_deployment.py | 79 +++++++++++++++++------ kluctl/utils/kustomize.py | 2 +- kluctl/utils/yaml_utils.py | 9 ++- 4 files changed, 66 insertions(+), 26 deletions(-) diff --git a/kluctl/deployment/helm_chart.py b/kluctl/deployment/helm_chart.py index ce8449ed1..a41800d0e 100644 --- a/kluctl/deployment/helm_chart.py +++ b/kluctl/deployment/helm_chart.py @@ -90,7 +90,7 @@ def render(self): r, rendered, stderr = self.do_helm(args) rendered = rendered.decode('utf-8') - parsed = list(yaml_load_all(rendered)) + parsed = yaml_load_all(rendered) for o in parsed: if o is None: continue diff --git a/kluctl/deployment/kustomize_deployment.py b/kluctl/deployment/kustomize_deployment.py index 8ab17d16f..590a3ff33 100644 --- a/kluctl/deployment/kustomize_deployment.py +++ b/kluctl/deployment/kustomize_deployment.py @@ -180,28 +180,24 @@ def check_inclusion_for_delete(self): values = self.build_inclusion_values() return inclusion.check_included(values, skip_delete_if_tags) - def build_objects(self): - if self.config.get("onlyRender", False): - self.objects = [] - return - if "path" not in self.config: - self.objects = [] - return - + def is_simple_kustomize_project(self, kustomize_yaml): rendered_dir = self.get_rendered_dir() - kustomize_yaml_path = os.path.join(rendered_dir, 'kustomization.yml') - kustomize_yaml = yaml_load_file(kustomize_yaml_path) - + allowed = {"apiVersion", "kind", "resources", "namespace"} + for k in kustomize_yaml.keys(): + if k not in allowed: + return False + for r in kustomize_yaml.get("resources", []): + p = r.split("/") + if "." in p or ".." in p: + return False + if not os.path.isfile(os.path.join(rendered_dir, r)): + return False + return True + + def build_objects_kustomize(self): tmp_files = [] - override_namespace = self.deployment_project.get_override_namespace() - if override_namespace is not None: - kustomize_yaml.setdefault("namespace", override_namespace) - try: - # Save modified kustomize.yml - yaml_save_file(kustomize_yaml, kustomize_yaml_path) - need_build = True if allow_cache: hash = self.calc_hash() @@ -213,7 +209,7 @@ def build_objects(self): yamls = f.read() if need_build: # Run 'kustomize build' - yamls = kustomize_build(rendered_dir) + yamls = kustomize_build(self.get_rendered_dir()) if allow_cache: with open(hash_path, "w") as f: f.write(yamls) @@ -221,10 +217,51 @@ def build_objects(self): for x in tmp_files: x.close() - # Add commonLabels to all resources. We can't use kustomize's "commonLabels" feature as it erroneously - # sets matchLabels as well. yamls = yaml_load_all(yamls) + return yamls + + def build_objects_simple_kustomize(self, kustomize_yaml): + rendered_dir = self.get_rendered_dir() + namespace = kustomize_yaml.get("namespace") + + yamls = [] + for r in kustomize_yaml.get("resources", []): + y = yaml_load_file(os.path.join(rendered_dir, r), all=True) + for x in y: + if x is None: + continue + if namespace is not None: + x.setdefault("metadata", {})["namespace"] = namespace + yamls.append(x) + return yamls + + def build_objects(self): + if self.config.get("onlyRender", False): + self.objects = [] + return + if "path" not in self.config: + self.objects = [] + return + + rendered_dir = self.get_rendered_dir() + kustomize_yaml_path = os.path.join(rendered_dir, 'kustomization.yml') + kustomize_yaml = yaml_load_file(kustomize_yaml_path) + + override_namespace = self.deployment_project.get_override_namespace() + if override_namespace is not None: + kustomize_yaml.setdefault("namespace", override_namespace) + + # Save modified kustomize.yml + yaml_save_file(kustomize_yaml, kustomize_yaml_path) + + if self.is_simple_kustomize_project(kustomize_yaml): + yamls = self.build_objects_simple_kustomize(kustomize_yaml) + else: + yamls = self.build_objects_kustomize() + self.objects = [] + # Add commonLabels to all resources. We can't use kustomize's "commonLabels" feature as it erroneously + # sets matchLabels as well. for y in yamls: labels = y.setdefault("metadata", {}).setdefault("labels") or {} annotations = y["metadata"].setdefault("annotations") or {} diff --git a/kluctl/utils/kustomize.py b/kluctl/utils/kustomize.py index dc05b8f8a..522f61802 100644 --- a/kluctl/utils/kustomize.py +++ b/kluctl/utils/kustomize.py @@ -14,5 +14,5 @@ def kustomize(args, pathToYaml, ignoreErrors=False): return r, str(out.decode("utf-8")), err def kustomize_build(pathToYaml): - r, out, err = kustomize(["build"], pathToYaml) + r, out, err = kustomize(["build", "--reorder", "none"], pathToYaml) return out diff --git a/kluctl/utils/yaml_utils.py b/kluctl/utils/yaml_utils.py index 31b65c171..93547de5d 100644 --- a/kluctl/utils/yaml_utils.py +++ b/kluctl/utils/yaml_utils.py @@ -22,11 +22,14 @@ def yaml_load(s): return yaml.load(s, Loader=SafeLoader) def yaml_load_all(s): - return yaml.load_all(s, Loader=SafeLoader) + return list(yaml.load_all(s, Loader=SafeLoader)) -def yaml_load_file(path): +def yaml_load_file(path, all=False): with open(path) as f: - y = yaml_load(f) + if all: + y = yaml_load_all(f) + else: + y = yaml_load(f) return y def yaml_dump(y, stream=None): From 509cf2ab9c1d0e8aea09ca4f04009348913a2cf0 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sun, 22 Aug 2021 08:56:24 +0200 Subject: [PATCH 0015/2916] feat: Allow --from-archive to use an extracted archive directory --- kluctl/cli/main_cli_group.py | 5 +++-- kluctl/kluctl_project/kluctl_project.py | 26 ++++++++++++++----------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/kluctl/cli/main_cli_group.py b/kluctl/cli/main_cli_group.py index 7a7cffb08..56e28eb84 100644 --- a/kluctl/cli/main_cli_group.py +++ b/kluctl/cli/main_cli_group.py @@ -202,8 +202,9 @@ def kluctl_project_args(with_d=True, with_a=True, with_t=True): help="Local sealed-secrets directory. Overrides the project from .kluctl.yml", type=click.Path(file_okay=False))) options.append(optgroup.option("--from-archive", - help="Load project (.kluctl.yml, cluster, ...) from archive.", - type=click.Path(dir_okay=False))) + help="Load project (.kluctl.yml, cluster, ...) from archive. Given path can " + "either be an archive file or a directory with the extracted contents.", + type=click.Path(dir_okay=True, file_okay=True))) options.append(optgroup.option("--deployment-name", help="Name of the kluctl deployment. Used when resolving sealed-secrets. " "Defaults to the base name of --local-deployment/--project-url")) diff --git a/kluctl/kluctl_project/kluctl_project.py b/kluctl/kluctl_project/kluctl_project.py index 1b35f3b91..70a5fad05 100644 --- a/kluctl/kluctl_project/kluctl_project.py +++ b/kluctl/kluctl_project/kluctl_project.py @@ -96,20 +96,24 @@ def mf_filter(ti: tarfile.TarInfo): tar.add(self.sealed_secrets_dir, "sealed-secrets", True, filter=mf_filter) @staticmethod - def from_tgz(path, tmp_dir): - with open(path, mode="rb") as f: - with tarfile.open(mode="r:gz", fileobj=f) as tgz: - tgz.extractall(tmp_dir) - metadata = yaml_load_file(os.path.join(tmp_dir, "metadata.yml")) - deployment_dir = os.path.join(tmp_dir, "deployment") + def from_archive(path, tmp_dir): + if os.path.isfile(path): + with open(path, mode="rb") as f: + with tarfile.open(mode="r:gz", fileobj=f) as tgz: + tgz.extractall(tmp_dir) + dir = tmp_dir + else: + dir = path + metadata = yaml_load_file(os.path.join(dir, "metadata.yml")) + deployment_dir = os.path.join(dir, "deployment") deployment_name = metadata["deployment_name"] deployment_dir = os.path.join(deployment_dir, deployment_name) project = KluctlProject(deployment_name, None, None, - os.path.join(tmp_dir, ".kluctl.yml"), - os.path.join(tmp_dir, "clusters"), + os.path.join(dir, ".kluctl.yml"), + os.path.join(dir, "clusters"), deployment_dir, - os.path.join(tmp_dir, "sealed-secrets"), - tmp_dir) + os.path.join(dir, "sealed-secrets"), + dir) project.involved_repos = metadata["involved_repos"] project.targets = metadata["targets"] return project @@ -420,7 +424,7 @@ def load_kluctl_project_from_args(kwargs) -> ContextManager[KluctlProject]: if kwargs["from_archive"]: if any(kwargs[x] for x in ["project_url", "project_ref", "project_config", "local_clusters", "local_deployment", "local_sealed_secrets"]): raise CommandError("--from-archive can not be combined with any other project related option") - project = KluctlProject.from_tgz(kwargs["from_archive"], tmp_dir) + project = KluctlProject.from_archive(kwargs["from_archive"], tmp_dir) project.load(False) else: deployment_name = kwargs["deployment_name"] From 9fceda4a1d25a0d3c45f20ba100a4d622524c174 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sun, 22 Aug 2021 08:56:35 +0200 Subject: [PATCH 0016/2916] feat: Add kluctl project to archive --- kluctl/kluctl_project/kluctl_project.py | 1 + 1 file changed, 1 insertion(+) diff --git a/kluctl/kluctl_project/kluctl_project.py b/kluctl/kluctl_project/kluctl_project.py index 70a5fad05..81c46c175 100644 --- a/kluctl/kluctl_project/kluctl_project.py +++ b/kluctl/kluctl_project/kluctl_project.py @@ -91,6 +91,7 @@ def mf_filter(ti: tarfile.TarInfo): yaml_save_file(metadata_yaml, tmp.name) tar.add(tmp.name, "metadata.yml", filter=mf_filter) tar.add(self.config_file, ".kluctl.yml", filter=mf_filter) + tar.add(self.kluctl_project_dir, "kluctl-project", True, filter=mf_filter) tar.add(self.deployment_dir, "deployment/%s" % self.deployment_name, True, filter=mf_filter) tar.add(self.clusters_dir, "clusters", True, filter=mf_filter) tar.add(self.sealed_secrets_dir, "sealed-secrets", True, filter=mf_filter) From 7a4dde1a3bc0452765f219334144a662e523b29d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 23 Aug 2021 09:01:24 +0200 Subject: [PATCH 0017/2916] docs: Fix docs for secrets sources --- docs/kluctl_project.md | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/docs/kluctl_project.md b/docs/kluctl_project.md index 927089ef7..3db888bf7 100644 --- a/docs/kluctl_project.md +++ b/docs/kluctl_project.md @@ -178,14 +178,11 @@ A simple local file based source. The path must be relative and multiple places Example: ```yaml -secretsMatrix: -- rules: - - "{{ cluster.name == cluster_name }}" - secrets: - - path: .secrets-non-prod.yml - argsMatrix: - - cluster_name: test.example.com - environment: test +secretsConfig: + secretSets: + - name: prod + sources: + - path: .secrets-non-prod.yml ``` #### passwordstate @@ -195,18 +192,15 @@ prompt for login information while sealing. The password entry must have a field Example: ```yaml -secretsMatrix: -- rules: - - "{{ cluster.name == cluster_name }}" - secrets: - - passwordstate: - host: passwordstate.example.com - passwordList: /My/Path/To/The/List/name-of-the-list - passwordTitle: title-of-the-password - passwordField: field-name # This is optional and defaults to GenericField1 - argsMatrix: - - cluster_name: test.example.com - environment: test +secretsConfig: + secretSets: + - name: prod + sources: + - passwordstate: + host: passwordstate.example.com + passwordList: /My/Path/To/The/List/name-of-the-list + passwordTitle: title-of-the-password + passwordField: field-name # This is optional and defaults to GenericField1 ``` From 8b5198756c227b49599e112b954b4ce389b32929 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 23 Aug 2021 12:38:47 +0200 Subject: [PATCH 0018/2916] fix: Don't add --debug to helm invocations --- kluctl/deployment/helm_chart.py | 1 - 1 file changed, 1 deletion(-) diff --git a/kluctl/deployment/helm_chart.py b/kluctl/deployment/helm_chart.py index a41800d0e..850232d96 100644 --- a/kluctl/deployment/helm_chart.py +++ b/kluctl/deployment/helm_chart.py @@ -85,7 +85,6 @@ def render(self): args += ['--include-crds'] args.append("--skip-tests") - args.append("--debug") r, rendered, stderr = self.do_helm(args) rendered = rendered.decode('utf-8') From 8b7bff58b7b6e4f666d1a6d2300717c2cbb2db2f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 23 Aug 2021 12:39:11 +0200 Subject: [PATCH 0019/2916] refactor: More generic/reusable run_helper --- kluctl/deployment/helm_chart.py | 8 +- kluctl/image_registries/generic_registry.py | 5 +- kluctl/utils/kubeseal.py | 7 +- kluctl/utils/kustomize.py | 9 +- kluctl/utils/run_helper.py | 115 ++++++++++++++++++++ kluctl/utils/utils.py | 62 ----------- 6 files changed, 129 insertions(+), 77 deletions(-) create mode 100644 kluctl/utils/run_helper.py diff --git a/kluctl/deployment/helm_chart.py b/kluctl/deployment/helm_chart.py index 850232d96..69ae1900d 100644 --- a/kluctl/deployment/helm_chart.py +++ b/kluctl/deployment/helm_chart.py @@ -1,10 +1,9 @@ import contextlib import hashlib -import logging import os import shutil -from kluctl.utils.utils import runHelper +from kluctl.utils.run_helper import run_helper from kluctl.utils.versions import LooseVersionComparator from kluctl.utils.yaml_utils import yaml_load_file, yaml_load_all, yaml_dump_all, yaml_load, yaml_save_file @@ -103,7 +102,8 @@ def render(self): def do_helm(self, args, input=None, ignoreErrors=False, ignoreStderr=False): args = ['helm'] + args - r, stdout, stderr = runHelper(args=args, input=input, stderr_log_level=None if ignoreStderr else logging.WARN) + + r, stdout, stderr = run_helper(args=args, input=input, print_stdout=False, print_stderr=not ignoreStderr, return_std=True) if r != 0 and not ignoreErrors: raise Exception("helm failed: r=%d\nout=%s\nerr=%s" % (r, stdout.decode("utf-8"), stderr.decode("utf-8"))) - return r, stdout, stderr \ No newline at end of file + return r, stdout, stderr diff --git a/kluctl/image_registries/generic_registry.py b/kluctl/image_registries/generic_registry.py index 6e48c7a76..e80179b5a 100644 --- a/kluctl/image_registries/generic_registry.py +++ b/kluctl/image_registries/generic_registry.py @@ -16,8 +16,9 @@ from jwt import InvalidTokenError from kluctl.image_registries.images_registry import ImagesRegistry +from kluctl.utils.run_helper import run_helper from kluctl.utils.thread_safe_cache import ThreadSafeCache, ThreadSafeMultiCache -from kluctl.utils.utils import runHelper, get_tmp_base_dir +from kluctl.utils.utils import get_tmp_base_dir logger = logging.getLogger(__name__) @@ -81,7 +82,7 @@ def do_get_creds(self, host): if cred_store is not None: cred_exe = f"docker-credential-{cred_store}" logger.debug(f"trying credStore {cred_exe}") - rc, stdout, stderr = runHelper([cred_exe, "get"], input=auth_entry) + rc, stdout, stderr = run_helper([cred_exe, "get"], input=auth_entry, return_std=True) if rc != 0: logger.debug(f"{cred_exe} exited with status {rc}") return None diff --git a/kluctl/utils/kubeseal.py b/kluctl/utils/kubeseal.py index 174adf1b2..657f6d133 100644 --- a/kluctl/utils/kubeseal.py +++ b/kluctl/utils/kubeseal.py @@ -2,7 +2,8 @@ import os import tempfile -from kluctl.utils.utils import runHelper, get_tmp_base_dir +from kluctl.utils.run_helper import run_helper +from kluctl.utils.utils import get_tmp_base_dir logger = logging.getLogger('kubeseal') @@ -24,7 +25,7 @@ def kubeseal_raw_path(cert_file, secret_path, name, namespace, scope): args += ['--namespace', namespace] if scope is not None: args += ["--scope", scope] - r, out, err = runHelper(args, logger=logger, stdout_log_level=None, stderr_log_level=logging.ERROR) + r, out, err = run_helper(args, print_stderr=True, return_std=True) if r != 0: raise Exception("kubeseal returned %d, " % (r)) return out.decode("utf-8") @@ -32,7 +33,7 @@ def kubeseal_raw_path(cert_file, secret_path, name, namespace, scope): def kubeseal_fetch_cert(context, controller_ns, controller_name): args = ['kubeseal', '--fetch-cert'] args += ['--context', context, '--controller-namespace', controller_ns, '--controller-name', controller_name] - r, out, err = runHelper(args, logger=logger, stdout_log_level=None, stderr_log_level=logging.ERROR) + r, out, err = run_helper(args, print_stderr=True, return_std=True) if r != 0: raise Exception("kubeseal returned %d, " % (r)) return out diff --git a/kluctl/utils/kustomize.py b/kluctl/utils/kustomize.py index 522f61802..c5fd56a83 100644 --- a/kluctl/utils/kustomize.py +++ b/kluctl/utils/kustomize.py @@ -1,16 +1,13 @@ -import logging - from kluctl.utils.external_tools import get_external_tool_path -from kluctl.utils.utils import runHelper +from kluctl.utils.run_helper import run_helper -logger = logging.getLogger('kustomize') def kustomize(args, pathToYaml, ignoreErrors=False): args = [get_external_tool_path("kustomize")] + args - r, out, err = runHelper(args, cwd=pathToYaml, logger=logger, stdout_log_level=logging.DEBUG - 1) + r, out, err = run_helper(args, cwd=pathToYaml, print_stderr=True, return_std=True) if r != 0 and not ignoreErrors: - raise Exception("kustomize failed: r=%d\nout=%s\nerr=%s" % (r, out, err)) + raise Exception("kustomize failed: r=%d" % r) return r, str(out.decode("utf-8")), err def kustomize_build(pathToYaml): diff --git a/kluctl/utils/run_helper.py b/kluctl/utils/run_helper.py new file mode 100644 index 000000000..2dcb66fff --- /dev/null +++ b/kluctl/utils/run_helper.py @@ -0,0 +1,115 @@ +import fcntl +import io +import os +import subprocess +import sys +import threading +import time +import traceback + + +def stdin_write_thread(s, input): + pos = 0 + try: + while pos < len(input): + n = s.write(input[pos:]) + pos += n + s.close() + except: + pass + +class StdReader(threading.Thread): + def __init__(self, stream, do_print, line_mode, cb, capture): + super().__init__() + self.stream = stream + self.do_print = do_print + self.line_mode = line_mode + self.cb = cb + self.capture = capture + self.line_buf = io.BytesIO() + self.capture_buf = io.BytesIO() + + def split_std_lines(self, buf): + self.line_buf.write(buf) + if len(buf) == 0 or buf.find(b"\n") != -1: + line_buf_value = self.line_buf.getvalue() + self.line_buf.seek(0) + self.line_buf.truncate() + + if line_buf_value: + lines = line_buf_value.split(b"\n") + if lines and not lines[-1].endswith(b"\n") and len(buf) != 0: + self.line_buf.write(lines[-1]) + lines = lines[:-1] + + lines = [l.decode("utf-8") for l in lines] + return lines + return [] + + def do_write(self, buf): + if self.do_print is not None: + self.do_print.write(buf.decode("utf-8")) + self.do_print.flush() + if self.capture: + self.capture_buf.write(buf) + if self.cb is not None: + if self.line_mode: + lines = self.split_std_lines(buf) + if len(lines) > 0: + self.cb(lines) + else: + if len(buf) > 0: + self.cb(buf) + + def run(self) -> None: + try: + while True: + buf = self.stream.read() + if buf is None: + time.sleep(0.1) + continue + self.do_write(buf) + if len(buf) == 0: + break + except Exception: + traceback.print_exc() + +def set_non_blocking(f): + if sys.platform == "linux": + fd = f.fileno() + fl = fcntl.fcntl(fd, fcntl.F_GETFL) + fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK) + +def run_helper(args, cwd=None, env=None, input=None, + stdout_func=None, stderr_func=None, + print_stdout=False, print_stderr=False, + line_mode=False, return_std=False): + stdin = None + if input is not None: + stdin = subprocess.PIPE + if isinstance(input, str): + input = input.encode('utf-8') + process = subprocess.Popen(args=args, stdin=stdin, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd, env=env) + + stdin_thread = None + if input is not None: + stdin_thread = threading.Thread(target=stdin_write_thread, args=(process.stdin, input)) + stdin_thread.start() + + set_non_blocking(process.stdout) + set_non_blocking(process.stderr) + + stdout_reader = StdReader(process.stdout, sys.stdout if print_stdout else None, line_mode, stdout_func, return_std) + stderr_reader = StdReader(process.stderr, sys.stderr if print_stderr else None, line_mode, stderr_func, return_std) + stdout_reader.start() + stderr_reader.start() + + process.wait() + stdout_reader.join() + stderr_reader.join() + if stdin_thread is not None: + stdin_thread.join() + + if return_std: + return process.returncode, stdout_reader.capture_buf.getvalue(), stderr_reader.capture_buf.getvalue() + return process.returncode diff --git a/kluctl/utils/utils.py b/kluctl/utils/utils.py index fdbb0be7d..9dfecab82 100644 --- a/kluctl/utils/utils.py +++ b/kluctl/utils/utils.py @@ -17,68 +17,6 @@ def get_tmp_base_dir(): os.makedirs(dir, exist_ok=True) return dir -def stdin_write_thread(s, input): - pos = 0 - try: - while pos < len(input): - n = s.write(input[pos:]) - pos += n - s.close() - except: - pass - -def std_read_thread(s, b, logger, log_level): - try: - while True: - line = s.readline() - if line is None or len(line) == 0: - break - - b.write(line) - if logger is not None and log_level is not None: - if line.endswith(b'\n'): - line = line[:-1] - logger.log(log_level, line.decode('utf-8')) - except: - pass - -def runHelper(args, cwd=None, input=None, logger=None, stdout_log_level=logging.DEBUG, stderr_log_level=logging.WARN): - if logger is not None: - logger.debug("runHelper: '%s'. inputLen=%d" % (" ".join(args), len(input) if input is not None else 0)) - - stdin = None - if input is not None: - stdin = subprocess.PIPE - if isinstance(input, str): - input = input.encode('utf-8') - process = subprocess.Popen(args=args, stdin=stdin, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd) - - stdin_thread = None - if input is not None: - stdin_thread = threading.Thread(target=stdin_write_thread, args=(process.stdin, input)) - stdin_thread.start() - - stdout = BytesIO() - stderr = BytesIO() - - stdout_thread = threading.Thread(target=std_read_thread, args=(process.stdout, stdout, logger, stdout_log_level)) - stderr_thread = threading.Thread(target=std_read_thread, args=(process.stderr, stderr, logger, stderr_log_level)) - stdout_thread.start() - stderr_thread.start() - - process.wait() - stdout_thread.join() - stderr_thread.join() - if stdin_thread is not None: - stdin_thread.join() - - stdout.seek(0) - stderr.seek(0) - stdout = stdout.read() - stderr = stderr.read() - - return process.returncode, stdout, stderr - def copytree(src, dst, symlinks=False, ignore=None): for item in os.listdir(src): s = os.path.join(src, item) From 237dad60ea8c1789428d91c66aabc80ed0de3953 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 23 Aug 2021 15:39:44 +0200 Subject: [PATCH 0020/2916] fix: Honor fixed images from target --- kluctl/cli/utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kluctl/cli/utils.py b/kluctl/cli/utils.py index a56383cc6..d66312ee1 100644 --- a/kluctl/cli/utils.py +++ b/kluctl/cli/utils.py @@ -146,6 +146,8 @@ def project_target_command_context(kwargs, kluctl_project, target, c = DeploymentCollection(d, images=images, inclusion=inclusion, tmpdir=output_dir, for_seal=for_seal) fixed_images = load_fixed_images(kwargs) + for fi in target.get("images", []): + c.seen_images.add_fixed_image(fi) for fi in fixed_images: c.seen_images.add_fixed_image(fi) From 7e039cd42c8c31fdd4dcfdaa8be429f73a8cb6e7 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 23 Aug 2021 16:53:40 +0200 Subject: [PATCH 0021/2916] refactor: Move dict related utils into own module --- kluctl/cli/utils.py | 3 +- kluctl/deployment/deployment_collection.py | 3 +- kluctl/deployment/deployment_project.py | 2 +- kluctl/deployment/kustomize_deployment.py | 3 +- kluctl/diff/k8s_diff.py | 2 +- kluctl/diff/managed_fields.py | 6 +-- kluctl/diff/normalize.py | 2 +- kluctl/kluctl_project/kluctl_project.py | 3 +- kluctl/seal/seal_command.py | 2 +- kluctl/seal/sealer.py | 3 +- kluctl/utils/dict_utils.py | 43 +++++++++++++++++++++ kluctl/utils/external_args.py | 2 +- kluctl/utils/jinja2_utils.py | 2 +- kluctl/utils/k8s_cluster_real.py | 2 +- kluctl/utils/utils.py | 44 ---------------------- 15 files changed, 63 insertions(+), 59 deletions(-) create mode 100644 kluctl/utils/dict_utils.py diff --git a/kluctl/cli/utils.py b/kluctl/cli/utils.py index d66312ee1..c9afa82b0 100644 --- a/kluctl/cli/utils.py +++ b/kluctl/cli/utils.py @@ -7,6 +7,7 @@ import click from kluctl.kluctl_project.kluctl_project import load_kluctl_project_from_args, KluctlProject +from kluctl.utils.dict_utils import merge_dict from kluctl.utils.exceptions import CommandError from kluctl.deployment.deployment_collection import DeploymentCollection from kluctl.deployment.deployment_project import DeploymentProject @@ -18,7 +19,7 @@ from kluctl.utils.inclusion import Inclusion from kluctl.utils.k8s_cluster_base import load_cluster_config, k8s_cluster_base from kluctl.utils.k8s_object_utils import get_long_object_name_from_ref -from kluctl.utils.utils import get_tmp_base_dir, copy_dict, merge_dict +from kluctl.utils.utils import get_tmp_base_dir from kluctl.utils.yaml_utils import yaml_load_file, yaml_dump diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index 347ec223b..8da9fca93 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -10,12 +10,13 @@ from kluctl.diff.managed_fields import remove_non_managed_fields2 from kluctl.diff.normalize import normalize_object from kluctl.deployment.images import SeenImages +from kluctl.utils.dict_utils import copy_dict from kluctl.utils.k8s_delete_utils import find_objects_for_delete from kluctl.utils.k8s_object_utils import get_object_ref, get_long_object_name, get_long_object_name_from_ref, \ ObjectRef from kluctl.utils.status_validation import validate_object, ValidateResult, ValidateResultItem from kluctl.utils.templated_dir import TemplatedDir -from kluctl.utils.utils import MyThreadPoolExecutor, copy_dict +from kluctl.utils.utils import MyThreadPoolExecutor logger = logging.getLogger(__name__) diff --git a/kluctl/deployment/deployment_project.py b/kluctl/deployment/deployment_project.py index d7153ba57..071fb076f 100644 --- a/kluctl/deployment/deployment_project.py +++ b/kluctl/deployment/deployment_project.py @@ -7,7 +7,7 @@ from kluctl.utils.exceptions import CommandError from kluctl.utils.external_args import check_required_args from kluctl.utils.jinja2_utils import add_jinja2_filters, RelEnvironment, render_str -from kluctl.utils.utils import merge_dict, copy_dict, \ +from kluctl.utils.dict_utils import merge_dict, copy_dict, \ set_default_value from kluctl.utils.yaml_utils import yaml_load, yaml_load_file diff --git a/kluctl/deployment/kustomize_deployment.py b/kluctl/deployment/kustomize_deployment.py index 590a3ff33..ba2d8d444 100644 --- a/kluctl/deployment/kustomize_deployment.py +++ b/kluctl/deployment/kustomize_deployment.py @@ -3,13 +3,14 @@ import os import shutil +from kluctl.utils.dict_utils import merge_dict from kluctl.utils.exceptions import CommandError from kluctl.deployment.helm_chart import HelmChart from kluctl.seal.deployment_sealer import SEALME_EXT from kluctl.utils.external_tools import get_external_tool_hash from kluctl.utils.kustomize import kustomize_build from kluctl.utils.templated_dir import TemplatedDir -from kluctl.utils.utils import merge_dict, get_tmp_base_dir +from kluctl.utils.utils import get_tmp_base_dir from kluctl.utils.versions import LooseSemVerLatestVersion, PrefixLatestVersion, NumberLatestVersion, \ RegexLatestVersion from kluctl.utils.yaml_utils import yaml_load_file, yaml_load_all, yaml_save_file diff --git a/kluctl/diff/k8s_diff.py b/kluctl/diff/k8s_diff.py index 918046db3..642d9cf42 100644 --- a/kluctl/diff/k8s_diff.py +++ b/kluctl/diff/k8s_diff.py @@ -6,7 +6,7 @@ from deepdiff.helper import NotPresent from kluctl.utils.k8s_object_utils import get_object_stub, get_object_ref -from kluctl.utils.utils import copy_dict +from kluctl.utils.dict_utils import copy_dict from kluctl.utils.yaml_utils import yaml_dump diff --git a/kluctl/diff/managed_fields.py b/kluctl/diff/managed_fields.py index aa751b31b..e380fde39 100644 --- a/kluctl/diff/managed_fields.py +++ b/kluctl/diff/managed_fields.py @@ -1,7 +1,7 @@ import json -from kluctl.utils.k8s_object_utils import get_object_ref -from kluctl.utils.utils import copy_dict +from kluctl.utils.k8s_object_utils import get_object_ref, get_long_object_name +from kluctl.utils.dict_utils import copy_dict def _fields_iterator(mf, path): @@ -47,7 +47,7 @@ def find_array_value(a, k): return o, j, True o = o[j] else: - raise ValueError('Unsupported managed field %s in object %s' % ('.'.join(p), get_long_object_name2(o_in))) + raise ValueError('Unsupported managed field %s in object %s' % ('.'.join(p), get_long_object_name(o_in))) ignored_fields = { ("f:metadata",) diff --git a/kluctl/diff/normalize.py b/kluctl/diff/normalize.py index 695f65d20..63d5ebd77 100644 --- a/kluctl/diff/normalize.py +++ b/kluctl/diff/normalize.py @@ -1,7 +1,7 @@ import fnmatch from kluctl.utils.k8s_object_utils import split_api_version -from kluctl.utils.utils import is_iterable, copy_dict +from kluctl.utils.dict_utils import is_iterable, copy_dict def nav_dict(d, k): diff --git a/kluctl/kluctl_project/kluctl_project.py b/kluctl/kluctl_project/kluctl_project.py index 81c46c175..2f19fee17 100644 --- a/kluctl/kluctl_project/kluctl_project.py +++ b/kluctl/kluctl_project/kluctl_project.py @@ -13,12 +13,13 @@ import jsonschema from kluctl.schemas.schema import validate_kluctl_project_config, parse_git_project, target_config_schema +from kluctl.utils.dict_utils import copy_dict from kluctl.utils.exceptions import InvalidKluctlProjectConfig, CommandError from kluctl.utils.git_utils import parse_git_url, clone_project, get_git_commit, update_git_cache, git_ls_remote, \ get_git_ref from kluctl.utils.jinja2_utils import render_dict_strs from kluctl.utils.k8s_cluster_base import load_cluster_config -from kluctl.utils.utils import get_tmp_base_dir, MyThreadPoolExecutor, copy_dict +from kluctl.utils.utils import get_tmp_base_dir, MyThreadPoolExecutor from kluctl.utils.yaml_utils import yaml_load_file, yaml_save_file logger = logging.getLogger(__name__) diff --git a/kluctl/seal/seal_command.py b/kluctl/seal/seal_command.py index 85bee5160..b7bb370c5 100644 --- a/kluctl/seal/seal_command.py +++ b/kluctl/seal/seal_command.py @@ -9,7 +9,7 @@ from kluctl.seal.deployment_sealer import DeploymentSealer from kluctl.utils.exceptions import InvalidKluctlProjectConfig, CommandError from kluctl.utils.jinja2_utils import render_dict_strs, print_template_error -from kluctl.utils.utils import merge_dict +from kluctl.utils.dict_utils import merge_dict logger = logging.getLogger(__name__) diff --git a/kluctl/seal/sealer.py b/kluctl/seal/sealer.py index dab586fd1..01ecbff9b 100644 --- a/kluctl/seal/sealer.py +++ b/kluctl/seal/sealer.py @@ -4,8 +4,9 @@ import os import tempfile +from kluctl.utils.dict_utils import copy_dict from kluctl.utils.kubeseal import kubeseal_fetch_cert, kubeseal_raw -from kluctl.utils.utils import copy_dict, get_tmp_base_dir +from kluctl.utils.utils import get_tmp_base_dir from kluctl.utils.yaml_utils import yaml_load_file, yaml_dump, yaml_load logger = logging.getLogger(__name__) diff --git a/kluctl/utils/dict_utils.py b/kluctl/utils/dict_utils.py new file mode 100644 index 000000000..95444cc44 --- /dev/null +++ b/kluctl/utils/dict_utils.py @@ -0,0 +1,43 @@ +def is_iterable(obj): + try: + iter(obj) + except Exception: + return False + else: + return True + +def copy_primitive_value(v): + if isinstance(v, dict): + return copy_dict(v) + if isinstance(v, str) or isinstance(v, bytes): + return v + if is_iterable(v): + return [copy_primitive_value(x) for x in v] + return v + +def copy_dict(a): + ret = {} + for k, v in a.items(): + ret[k] = copy_primitive_value(v) + return ret + +def merge_dict(a, b, clone=True): + if clone: + a = copy_dict(a) + if a is None: + a = {} + if b is None: + b = {} + for key in b: + if key in a: + if isinstance(a[key], dict) and isinstance(b[key], dict): + merge_dict(a[key], b[key], clone=False) + else: + a[key] = b[key] + else: + a[key] = b[key] + return a + +def set_default_value(d, n, default): + if n not in d or d[n] is None: + d[n] = default diff --git a/kluctl/utils/external_args.py b/kluctl/utils/external_args.py index e5bfeaa5a..31de28891 100644 --- a/kluctl/utils/external_args.py +++ b/kluctl/utils/external_args.py @@ -1,7 +1,7 @@ import re from kluctl.utils.exceptions import CommandError -from kluctl.utils.utils import merge_dict +from kluctl.utils.dict_utils import merge_dict def parse_args(args_list): diff --git a/kluctl/utils/jinja2_utils.py b/kluctl/utils/jinja2_utils.py index c041b948c..81f0f2aca 100644 --- a/kluctl/utils/jinja2_utils.py +++ b/kluctl/utils/jinja2_utils.py @@ -9,7 +9,7 @@ import jinja2 from jinja2 import Environment, StrictUndefined, FileSystemLoader, TemplateNotFound, TemplateError -from kluctl.utils.utils import merge_dict, is_iterable +from kluctl.utils.dict_utils import merge_dict from kluctl.utils.yaml_utils import yaml_dump, yaml_load logger = logging.getLogger(__name__) diff --git a/kluctl/utils/k8s_cluster_real.py b/kluctl/utils/k8s_cluster_real.py index 17d1c19d5..3ead7d63e 100644 --- a/kluctl/utils/k8s_cluster_real.py +++ b/kluctl/utils/k8s_cluster_real.py @@ -12,7 +12,7 @@ from kluctl.utils.exceptions import CommandError from kluctl.utils.k8s_cluster_base import k8s_cluster_base -from kluctl.utils.utils import copy_dict +from kluctl.utils.dict_utils import copy_dict from kluctl.utils.versions import LooseVersionComparator logger = logging.getLogger(__name__) diff --git a/kluctl/utils/utils.py b/kluctl/utils/utils.py index 9dfecab82..955d6b459 100644 --- a/kluctl/utils/utils.py +++ b/kluctl/utils/utils.py @@ -26,46 +26,6 @@ def copytree(src, dst, symlinks=False, ignore=None): else: shutil.copy2(s, d) -def is_iterable(obj): - try: - iter(obj) - except Exception: - return False - else: - return True - -def copy_primitive_value(v): - if isinstance(v, dict): - return copy_dict(v) - if isinstance(v, str) or isinstance(v, bytes): - return v - if is_iterable(v): - return [copy_primitive_value(x) for x in v] - return v - -def copy_dict(a): - ret = {} - for k, v in a.items(): - ret[k] = copy_primitive_value(v) - return ret - -def merge_dict(a, b, clone=True): - if clone: - a = copy_dict(a) - if a is None: - a = {} - if b is None: - b = {} - for key in b: - if key in a: - if isinstance(a[key], dict) and isinstance(b[key], dict): - merge_dict(a[key], b[key], clone=False) - else: - a[key] = b[key] - else: - a[key] = b[key] - return a - def duration(duration_string): # example: '5d3h2m1s' duration_string = duration_string.lower() total_seconds = Decimal('0') @@ -87,10 +47,6 @@ def duration(duration_string): # example: '5d3h2m1s' prev_num.append(character) return timedelta(seconds=float(total_seconds)) -def set_default_value(d, n, default): - if n not in d or d[n] is None: - d[n] = default - class DummyExecutor: def __init__(self, *args, **kwargs): pass From 6bb6816c30e8991483af441d96f2dee0207174dd Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 23 Aug 2021 18:35:17 +0200 Subject: [PATCH 0022/2916] fix: Fix issue with objects being seen as new objects Caused by objects having a namespace set while it shouldn't be. --- kluctl/deployment/deployment_collection.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index 8da9fca93..fd9f24703 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -13,7 +13,7 @@ from kluctl.utils.dict_utils import copy_dict from kluctl.utils.k8s_delete_utils import find_objects_for_delete from kluctl.utils.k8s_object_utils import get_object_ref, get_long_object_name, get_long_object_name_from_ref, \ - ObjectRef + ObjectRef, remove_namespace_from_ref_if_needed from kluctl.utils.status_validation import validate_object, ValidateResult, ValidateResultItem from kluctl.utils.templated_dir import TemplatedDir from kluctl.utils.utils import MyThreadPoolExecutor @@ -194,20 +194,25 @@ def do_patch(self, k8s_cluster, force_apply, replace_on_error, dry_run, ordered, def finish_futures(): for o, f in futures: - ref = get_object_ref(o) try: r, patch_warnings = f.result() + # We must use the ref from the applied object as k8s might remove the namespace field + ref = get_object_ref(r) applied_objects[ref] = r self.add_api_warnings(ref, patch_warnings) except ResourceNotFoundError as e: + ref = get_object_ref(o) self.add_api_error(ref, k8s_cluster.get_status_message(e)) except ApiException as e: + ref = get_object_ref(o) if replace_on_error: logger.info("Patching %s failed, retrying by deleting and re-applying" % get_long_object_name_from_ref(ref)) try: k8s_cluster.delete_single_object(ref, force_dry_run=dry_run, ignore_not_found=True) if not dry_run and not k8s_cluster.dry_run: r, patch_warnings = k8s_cluster.patch_object(o, force_apply=True) + # We must use the ref from the applied object as k8s might remove the namespace field + ref = get_object_ref(r) applied_objects[ref] = r self.add_api_warnings(ref, patch_warnings) else: @@ -295,6 +300,7 @@ def do_diff(self, k8s_cluster, applied_objects, ignore_tags, ignore_labels, igno ignore_for_diffs = d.deployment_project.get_ignore_for_diffs(ignore_tags, ignore_labels, ignore_annotations) for x in d.objects: ref = get_object_ref(x) + ref = remove_namespace_from_ref_if_needed(k8s_cluster, ref) if ref not in applied_objects: continue diff_objects[ref] = applied_objects.get(ref) From b7dcc14794ee46cac02cad26be9c7f1328fb8602 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 23 Aug 2021 18:36:36 +0200 Subject: [PATCH 0023/2916] fix: Fix get_objects_metadata to even work when API returns non-table results --- kluctl/diff/k8s_diff.py | 2 +- kluctl/utils/k8s_cluster_base.py | 16 +--------------- kluctl/utils/k8s_cluster_mocked.py | 13 +++---------- kluctl/utils/k8s_cluster_real.py | 21 ++++++++++++++------- kluctl/utils/k8s_object_utils.py | 12 ------------ 5 files changed, 19 insertions(+), 45 deletions(-) diff --git a/kluctl/diff/k8s_diff.py b/kluctl/diff/k8s_diff.py index 642d9cf42..97f2e6ab2 100644 --- a/kluctl/diff/k8s_diff.py +++ b/kluctl/diff/k8s_diff.py @@ -5,7 +5,7 @@ from deepdiff import DeepDiff from deepdiff.helper import NotPresent -from kluctl.utils.k8s_object_utils import get_object_stub, get_object_ref +from kluctl.utils.k8s_object_utils import get_object_ref from kluctl.utils.dict_utils import copy_dict from kluctl.utils.yaml_utils import yaml_dump diff --git a/kluctl/utils/k8s_cluster_base.py b/kluctl/utils/k8s_cluster_base.py index e75488d23..2dfd63966 100644 --- a/kluctl/utils/k8s_cluster_base.py +++ b/kluctl/utils/k8s_cluster_base.py @@ -43,21 +43,7 @@ def get_objects_by_object_refs(self, object_refs): def get_objects_metadata(self, group=None, version=None, kind=None, name=None, namespace=None, labels=None): if group is None or kind is None: raise ApiException("group/kind must be supplied") - tables = self.get_objects(group=group, version=version, kind=kind, name=name, namespace=namespace, labels=labels, as_table=True) - ret = [] - for table, warnings in tables: - if not table.get("rows", []): - continue - for r in table["rows"]: - o = r["object"] - o["kind"] = kind - o["apiVersion"] = group or "" - if version: - if group: - o["apiVersion"] += "/" - o["apiVersion"] += version - ret.append((o, warnings)) - return ret + return self.get_objects(group=group, version=version, kind=kind, name=name, namespace=namespace, labels=labels, as_table=True) def load_cluster_config(cluster_dir, cluster_name, offline=False, dry_run=True): if cluster_name is None: diff --git a/kluctl/utils/k8s_cluster_mocked.py b/kluctl/utils/k8s_cluster_mocked.py index 9a828a895..b5b5c2cef 100644 --- a/kluctl/utils/k8s_cluster_mocked.py +++ b/kluctl/utils/k8s_cluster_mocked.py @@ -1,4 +1,6 @@ from kluctl.utils.k8s_cluster_base import k8s_cluster_base +from kluctl.utils.k8s_object_utils import split_api_version + class k8s_cluster_mocked(k8s_cluster_base): def __init__(self): @@ -7,15 +9,6 @@ def __init__(self): def add_object(self, o): self.objects.append(o) - def _split_api_version(self, o): - if 'apiVersion' not in o: - return None, 'v1' - version = o['apiVersion'] - idx = version.find('/') - if idx == -1: - return None, version - return version[:idx], version[idx + 1:] - def _get_objects(self, group, version, kind, name, namespace, labels, as_table): if labels is None: labels = {} @@ -23,7 +16,7 @@ def _get_objects(self, group, version, kind, name, namespace, labels, as_table): ret = [] for o in self.objects: o_namespace = o['metadata']['namespace'] if 'namespace' in o['metadata'] else None - o_group, o_version = self._split_api_version(o) + o_group, o_version = split_api_version(o.get("apiVersion")) o_kind = o['kind'] o_name = o['metadata']['name'] o_labels = o['metadata']['labels'] if 'labels' in o['metadata'] else {} diff --git a/kluctl/utils/k8s_cluster_real.py b/kluctl/utils/k8s_cluster_real.py index 3ead7d63e..c9a55e52a 100644 --- a/kluctl/utils/k8s_cluster_real.py +++ b/kluctl/utils/k8s_cluster_real.py @@ -13,6 +13,7 @@ from kluctl.utils.exceptions import CommandError from kluctl.utils.k8s_cluster_base import k8s_cluster_base from kluctl.utils.dict_utils import copy_dict +from kluctl.utils.k8s_object_utils import split_api_version from kluctl.utils.versions import LooseVersionComparator logger = logging.getLogger(__name__) @@ -164,7 +165,16 @@ def fix_kind(resource, r, warnings): return [] if name is not None: return [(r, warnings)] - r = r['items'] + + ret_group, ret_version = split_api_version(r.get("apiVersion")) + if as_table and ret_group == "meta.k8s.io" and r["kind"] == "Table": + r = r["rows"] or [] + r = [x["object"] for x in r] + else: + r = r["items"] + if not r: + return [] + for x in r: x['kind'] = resource.kind x['apiVersion'] = resource.group_version @@ -177,16 +187,13 @@ def fix_kind(resource, r, warnings): if r is None: continue objects, warnings = r - if not as_table: - ret += fix_kind(resource, objects, warnings) - else: - ret.append((objects, warnings)) + ret += fix_kind(resource, objects, warnings) return ret def patch_object(self, body, namespace=None, force_dry_run=False, force_apply=False): namespace = namespace or body.get('metadata', {}).get('namespace') - group, version = self._fix_api_version(None, body.get('apiVersion')) + group, version = split_api_version(body.get("apiVersion")) kind = body['kind'] name = body['metadata']['name'] query_params = self._get_dry_run_params(force_dry_run) @@ -215,7 +222,7 @@ def patch_object(self, body, namespace=None, force_dry_run=False, force_apply=Fa def replace_object(self, body, namespace=None, force_dry_run=False, resource_version=None): namespace = namespace or body.get('metadata', {}).get('namespace') resource_version = resource_version or body.get("metadata", {}).get("resourceVersion") - group, version = self._fix_api_version(None, body.get('apiVersion')) + group, version = split_api_version(body.get("apiVersion")) kind = body['kind'] name = body['metadata']['name'] query_params = self._get_dry_run_params(force_dry_run) diff --git a/kluctl/utils/k8s_object_utils.py b/kluctl/utils/k8s_object_utils.py index efc473cad..03b363b4c 100644 --- a/kluctl/utils/k8s_object_utils.py +++ b/kluctl/utils/k8s_object_utils.py @@ -72,18 +72,6 @@ def remove_namespace_from_ref_if_needed(k8s_cluster, ref): # resource might be unknown by now as we might be in the middle of deploying CRDs return ref -def get_object_stub(ref): - o = { - "apiVersion": ref.api_version, - "kind": ref.kind, - "metadata": { - "name": ref.name, - } - } - if ref.namespace is not None: - o["metadata"]["namespace"] = ref.namespace - return o - def get_filtered_api_resources(k8s_cluster, filter): api_resources = [] for r in k8s_cluster.get_preferred_resources(None, None, None): From 4ae001bf7a6b2b0ba44717f92c969dbf3030335a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 23 Aug 2021 18:46:07 +0200 Subject: [PATCH 0024/2916] docs: Run replace-commands-help.py --- docs/commands.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/commands.md b/docs/commands.md index a49ffcac1..e43262755 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -23,7 +23,8 @@ The following sets of common arguments are available: --local-deployment DIRECTORY Local deployment directory. Overrides the project from .kluctl.yml --local-sealed-secrets DIRECTORY Local sealed-secrets directory. Overrides the project from .kluctl.yml - --from-archive FILE Load project (.kluctl.yml, cluster, ...) from archive. + --from-archive PATH Load project (.kluctl.yml, cluster, ...) from archive. Given path can either be an + archive file or a directory with the extracted contents. --deployment-name TEXT Name of the kluctl deployment. Used when resolving sealed-secrets. Defaults to the base name of --local-deployment/--project-url --cluster TEXT Specify/Override cluster From 9f3fbe8d5b0a2443039745accd4df15ea89fb715 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 24 Aug 2021 09:04:42 +0200 Subject: [PATCH 0025/2916] refactor: Introduce get_dict_value --- kluctl/cli/commands.py | 3 ++- kluctl/cli/utils.py | 4 ++-- kluctl/deployment/deployment_collection.py | 7 +++---- kluctl/deployment/deployment_project.py | 4 ++-- kluctl/kluctl_project/kluctl_project.py | 4 ++-- kluctl/seal/deployment_sealer.py | 2 +- kluctl/seal/seal_command.py | 6 +++--- kluctl/seal/sealer.py | 18 +++++++++--------- kluctl/utils/dict_utils.py | 21 +++++++++++++++++++++ kluctl/utils/k8s_cluster_real.py | 4 ++-- kluctl/utils/k8s_delete_utils.py | 7 ++++--- kluctl/utils/k8s_object_utils.py | 3 ++- 12 files changed, 53 insertions(+), 30 deletions(-) diff --git a/kluctl/cli/commands.py b/kluctl/cli/commands.py index 2dbf725b5..c70f1a5e1 100644 --- a/kluctl/cli/commands.py +++ b/kluctl/cli/commands.py @@ -11,6 +11,7 @@ from kluctl.cli.utils import output_diff_result, build_seen_images, output_yaml_result, \ output_validate_result, project_command_context from kluctl.kluctl_project.kluctl_project import load_kluctl_project_from_args +from kluctl.utils.dict_utils import get_dict_value from kluctl.utils.exceptions import CommandError from kluctl.utils.inclusion import Inclusion from kluctl.utils.k8s_object_utils import get_long_object_name_from_ref, ObjectRef @@ -27,7 +28,7 @@ def bootstrap_command(obj, kwargs): with project_command_context(kwargs) as cmd_ctx: existing, warnings = cmd_ctx.k8s_cluster.get_single_object(ObjectRef("apiextensions.k8s.io/v1", "CustomResourceDefinition", "sealedsecrets.bitnami.com")) if existing: - if not kwargs["yes"] and existing["metadata"].get("labels", {}).get("kluctl.io/component") != "bootstrap": + if not kwargs["yes"] and get_dict_value(existing, "metadata.labels.kluctl\\.io/component") != "bootstrap": click.confirm("It looks like you're trying to bootstrap a cluster that already has the sealed-secrets " "deployed but not managed by kluctl. Do you really want to continue bootrapping?", err=True, abort=True) diff --git a/kluctl/cli/utils.py b/kluctl/cli/utils.py index c9afa82b0..0678d014b 100644 --- a/kluctl/cli/utils.py +++ b/kluctl/cli/utils.py @@ -7,7 +7,7 @@ import click from kluctl.kluctl_project.kluctl_project import load_kluctl_project_from_args, KluctlProject -from kluctl.utils.dict_utils import merge_dict +from kluctl.utils.dict_utils import merge_dict, get_dict_value from kluctl.utils.exceptions import CommandError from kluctl.deployment.deployment_collection import DeploymentCollection from kluctl.deployment.deployment_project import DeploymentProject @@ -135,7 +135,7 @@ def project_target_command_context(kwargs, kluctl_project, target, option_args = parse_args(kwargs.get("arg", [])) target_args = target.get("args", {}) if target else {} - seal_args = target.get("sealingConfig", {}).get("args", {}) if target else {} + seal_args = get_dict_value(target, "sealingConfig.args", {}) if target else {} deploy_args = merge_dict(target_args, option_args) if for_seal: merge_dict(deploy_args, seal_args, False) diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index fd9f24703..0ec5d6570 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -10,7 +10,7 @@ from kluctl.diff.managed_fields import remove_non_managed_fields2 from kluctl.diff.normalize import normalize_object from kluctl.deployment.images import SeenImages -from kluctl.utils.dict_utils import copy_dict +from kluctl.utils.dict_utils import copy_dict, get_dict_value from kluctl.utils.k8s_delete_utils import find_objects_for_delete from kluctl.utils.k8s_object_utils import get_object_ref, get_long_object_name, get_long_object_name_from_ref, \ ObjectRef, remove_namespace_from_ref_if_needed @@ -335,8 +335,7 @@ def do_diff(self, k8s_cluster, applied_objects, ignore_tags, ignore_labels, igno return new_objects, changed_objects def is_replace_needed(self, o): - annotations = o.get("metadata", {}).get("annotations", {}) - hook = annotations.get("helm.sh/hook") + hook = get_dict_value(o, "metadata.annotations.helm\\.sh/hook") if not hook: return False if all(h in ["pre-install", "post-install", "pre-delete", "post-delete", "pre-upgrade", "post-upgrade", "pre-rollback", "post-rollback", "test"] for h in hook.split(",")): @@ -390,7 +389,7 @@ def do_replace(o): while True: o2 = copy_dict(o) need_replace = False - for mf in o2["metadata"].get("managedFields", []): + for mf in get_dict_value(o2, "metadata.managedFields", []): if mf["manager"] == "deployctl": mf["manager"] = "kluctl" need_replace = True diff --git a/kluctl/deployment/deployment_project.py b/kluctl/deployment/deployment_project.py index 071fb076f..9385cdce3 100644 --- a/kluctl/deployment/deployment_project.py +++ b/kluctl/deployment/deployment_project.py @@ -8,7 +8,7 @@ from kluctl.utils.external_args import check_required_args from kluctl.utils.jinja2_utils import add_jinja2_filters, RelEnvironment, render_str from kluctl.utils.dict_utils import merge_dict, copy_dict, \ - set_default_value + set_default_value, get_dict_value from kluctl.utils.yaml_utils import yaml_load, yaml_load_file logger = logging.getLogger(__name__) @@ -131,7 +131,7 @@ def load_includes(self): def get_sealed_secrets_dir(self, subdir): root = self.get_root_deployment() - output_pattern = root.conf.get("sealedSecrets", {}).get("outputPattern") + output_pattern = get_dict_value(root.conf, "sealedSecrets.outputPattern") return output_pattern def get_root_deployment(self): diff --git a/kluctl/kluctl_project/kluctl_project.py b/kluctl/kluctl_project/kluctl_project.py index 2f19fee17..bea4f0b66 100644 --- a/kluctl/kluctl_project/kluctl_project.py +++ b/kluctl/kluctl_project/kluctl_project.py @@ -13,7 +13,7 @@ import jsonschema from kluctl.schemas.schema import validate_kluctl_project_config, parse_git_project, target_config_schema -from kluctl.utils.dict_utils import copy_dict +from kluctl.utils.dict_utils import copy_dict, get_dict_value from kluctl.utils.exceptions import InvalidKluctlProjectConfig, CommandError from kluctl.utils.git_utils import parse_git_url, clone_project, get_git_commit, update_git_cache, git_ls_remote, \ get_git_ref @@ -36,7 +36,7 @@ def load_kluctl_project_config(path): config["clusters"] = [config["clusters"]] secret_sets = set() - for s in config.get("secrets", {}).get("secretSets", []): + for s in get_dict_value(config, "secrets.secretSets", []): secret_sets.add(s["name"]) for target in config.get("targets", []): for s in target.get("secretSets", []): diff --git a/kluctl/seal/deployment_sealer.py b/kluctl/seal/deployment_sealer.py index b62a520bc..376775abd 100644 --- a/kluctl/seal/deployment_sealer.py +++ b/kluctl/seal/deployment_sealer.py @@ -26,7 +26,7 @@ def guess_sealed_secrets_controller(self): self.sealed_secrets_controller_name = "sealed-secrets" def seal_deployment(self): - output_suffix = self.deployment_project.conf.get("sealedSecrets", {}).get("outputPattern") + output_suffix = get_dict_value(self.deployment_project.conf, "sealedSecrets.outputPattern") if output_suffix is None: raise InvalidKluctlProjectConfig("sealedSecrets.outputPattern is not defined") diff --git a/kluctl/seal/seal_command.py b/kluctl/seal/seal_command.py index b7bb370c5..5cfef3e5e 100644 --- a/kluctl/seal/seal_command.py +++ b/kluctl/seal/seal_command.py @@ -9,12 +9,12 @@ from kluctl.seal.deployment_sealer import DeploymentSealer from kluctl.utils.exceptions import InvalidKluctlProjectConfig, CommandError from kluctl.utils.jinja2_utils import render_dict_strs, print_template_error -from kluctl.utils.dict_utils import merge_dict +from kluctl.utils.dict_utils import merge_dict, get_dict_value logger = logging.getLogger(__name__) def find_secrets_entry(kluctl_project, name): - for e2 in kluctl_project.config.get("secretsConfig", {}).get("secretSets", []): + for e2 in get_dict_value(kluctl_project.config, "secretsConfig.secretSets", []): if name == e2["name"]: return e2 raise InvalidKluctlProjectConfig("Secret Set with name %s was not found" % name) @@ -25,7 +25,7 @@ def merge_deployment_vars(deployment, jinja_var): def load_secrets(kluctl_project, target, secrets_loader, deployment): secrets = {} - for e in target["sealingConfig"].get("secretSets", []): + for e in get_dict_value(target, "sealingConfig.secretSets", []): secrets_entry = find_secrets_entry(kluctl_project, e) for source in secrets_entry.get("sources", []): rendered_source = render_dict_strs(source, deployment.jinja_vars) diff --git a/kluctl/seal/sealer.py b/kluctl/seal/sealer.py index 01ecbff9b..00da4e7ab 100644 --- a/kluctl/seal/sealer.py +++ b/kluctl/seal/sealer.py @@ -4,7 +4,7 @@ import os import tempfile -from kluctl.utils.dict_utils import copy_dict +from kluctl.utils.dict_utils import copy_dict, get_dict_value from kluctl.utils.kubeseal import kubeseal_fetch_cert, kubeseal_raw from kluctl.utils.utils import get_tmp_base_dir from kluctl.utils.yaml_utils import yaml_load_file, yaml_dump, yaml_load @@ -42,10 +42,10 @@ def seal_file(self, path, target_file): os.makedirs(os.path.dirname(target_file), exist_ok=True) content = yaml_load_file(path) - annotations = content.get("metadata", {}).get("annotations", {}) - secret_name = content.get("metadata", {}).get("name") - secret_namespace = content.get("metadata", {}).get("namespace", "default") - secret_type = content.get("type", "Opaque") + annotations = get_dict_value(content, "metadata.annotations", {}) + secret_name = get_dict_value(content, "metadata.name") + secret_namespace = get_dict_value(content, "metadata.namespace", "default") + secret_type = get_dict_value(content, "type", "Opaque") scope = "strict" if annotations.get("sealedsecrets.bitnami.com/namespace-wide") == "true": scope = "namespace-wide" @@ -58,14 +58,14 @@ def seal_file(self, path, target_file): existing_hashes = {} if os.path.exists(target_file): existing_content = yaml_load_file(target_file) - existing_annotations = existing_content.get("metadata", {}).get("annotations", {}) + existing_annotations = get_dict_value(existing_content, "metadata.annotations", {}) if HASH_ANNOTATION in existing_annotations: existing_hashes = yaml_load(existing_annotations[HASH_ANNOTATION]) secrets = {} - for k, v in content.get("data", {}).items(): + for k, v in get_dict_value(content, "data", {}).items(): secrets[k] = base64.b64decode(v) - for k, v in content.get("stringData", {}).items(): + for k, v in get_dict_value(content, "stringData", {}).items(): secrets[k] = v.encode("utf-8") result_secret_hashes = {} @@ -100,7 +100,7 @@ def seal_file(self, path, target_file): existing_hash = existing_hashes.get(k, "") if hash == existing_hash and not self.force_reseal: logger.debug(f"Secret {secret_name} and key {k} is unchanged, skipping encryption") - result_encrypted_secrets[k] = existing_content.get("spec", {}).get("encryptedData", {}).get(k) + result_encrypted_secrets[k] = get_dict_value(existing_content, "spec.encryptedData", {}).get(k) else: logger.debug(f"Secret {secret_name} and key {k} has changed, encrypting it") result_encrypted_secrets[k] = self.encrypt_secret(v, secret_name, secret_namespace, scope) diff --git a/kluctl/utils/dict_utils.py b/kluctl/utils/dict_utils.py index 95444cc44..302840e27 100644 --- a/kluctl/utils/dict_utils.py +++ b/kluctl/utils/dict_utils.py @@ -1,3 +1,6 @@ +from uuid import uuid4 + + def is_iterable(obj): try: iter(obj) @@ -41,3 +44,21 @@ def merge_dict(a, b, clone=True): def set_default_value(d, n, default): if n not in d or d[n] is None: d[n] = default + +_dummy = str(uuid4()) + +def get_dict_value(y, path, default=None): + if "\\." in path: + path = path.replace("\\.", _dummy) + s = path.split(".") + s = [x.replace(_dummy, ".") for x in s] + else: + s = path.split(".") + + for x in s: + if y is None: + return default + if x not in y: + return default + y = y[x] + return y diff --git a/kluctl/utils/k8s_cluster_real.py b/kluctl/utils/k8s_cluster_real.py index c9a55e52a..a8ccfdb11 100644 --- a/kluctl/utils/k8s_cluster_real.py +++ b/kluctl/utils/k8s_cluster_real.py @@ -12,7 +12,7 @@ from kluctl.utils.exceptions import CommandError from kluctl.utils.k8s_cluster_base import k8s_cluster_base -from kluctl.utils.dict_utils import copy_dict +from kluctl.utils.dict_utils import copy_dict, get_dict_value from kluctl.utils.k8s_object_utils import split_api_version from kluctl.utils.versions import LooseVersionComparator @@ -221,7 +221,7 @@ def patch_object(self, body, namespace=None, force_dry_run=False, force_apply=Fa def replace_object(self, body, namespace=None, force_dry_run=False, resource_version=None): namespace = namespace or body.get('metadata', {}).get('namespace') - resource_version = resource_version or body.get("metadata", {}).get("resourceVersion") + resource_version = resource_version or get_dict_value(body, "metadata.resourceVersion") group, version = split_api_version(body.get("apiVersion")) kind = body['kind'] name = body['metadata']['name'] diff --git a/kluctl/utils/k8s_delete_utils.py b/kluctl/utils/k8s_delete_utils.py index 0dfee5214..22c75c8ae 100644 --- a/kluctl/utils/k8s_delete_utils.py +++ b/kluctl/utils/k8s_delete_utils.py @@ -1,5 +1,6 @@ import logging +from kluctl.utils.dict_utils import get_dict_value from kluctl.utils.k8s_object_utils import get_included_objects, get_label_from_object, get_object_ref, \ get_long_object_name_from_ref, split_api_version, get_filtered_api_resources, \ remove_api_version_from_ref, remove_namespace_from_ref_if_needed @@ -34,14 +35,14 @@ def exclude(x): return True # exclude when explicitely requested - if x["metadata"].get("annotations", {}).get("kluctl.io/skip-delete", "false").lower() in ["true", "1", "yes"]: + if get_dict_value(x, "metadata.annotations.kluctl\\.io/skip-delete", "false").lower() in ["true", "1", "yes"]: return True # exclude objects which are owned by some other object if 'ownerReferences' in x['metadata'] and len(x['metadata']['ownerReferences']) != 0: return True - if len(x["metadata"].get("managedFields", [])) == 0: + if len(get_dict_value(x, "metadata.managedFields", [])) == 0: # We don't know who manages it...be safe and exclude it return True @@ -59,7 +60,7 @@ def exclude(x): # TODO remove label based check if get_label_from_object(x, 'kluctl.io/skip_delete_if_tags', 'false') == 'true': return True - if x["metadata"].get("annotations", {}).get("kluctl.io/skip-delete-if-tags", "false").lower() in ["true", "1", "yes"]: + if get_dict_value(x, "metadata.annotations.kluctl\\.io/skip-delete-if-tags", "false").lower() in ["true", "1", "yes"]: return True return False diff --git a/kluctl/utils/k8s_object_utils.py b/kluctl/utils/k8s_object_utils.py index 03b363b4c..494100eb4 100644 --- a/kluctl/utils/k8s_object_utils.py +++ b/kluctl/utils/k8s_object_utils.py @@ -4,6 +4,7 @@ from kubernetes.dynamic.exceptions import ResourceNotFoundError +from kluctl.utils.dict_utils import get_dict_value from kluctl.utils.utils import MyThreadPoolExecutor logger = logging.getLogger(__name__) @@ -105,7 +106,7 @@ def get_included_objects(k8s_cluster, verbs, labels, inclusion, exclude_if_not_i inclusion_values = get_tags_from_object(r) inclusion_values = [("tag", tag) for tag in inclusion_values] - kustomize_dir = r.get("metadata", {}).get("annotations", {}).get("kluctl.io/kustomize_dir", None) + kustomize_dir = get_dict_value(r, "metadata.annotations.kluctl\\.io/kustomize_dir", None) if kustomize_dir is not None: inclusion_values.append(("kustomize_dir", kustomize_dir)) From 8f1e5e7dd9c87409653214eb3fc785fa66805fc2 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 24 Aug 2021 09:46:46 +0200 Subject: [PATCH 0026/2916] fix: Fix typo --- kluctl/utils/status_validation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kluctl/utils/status_validation.py b/kluctl/utils/status_validation.py index c7e1e7ef2..3499922fd 100644 --- a/kluctl/utils/status_validation.py +++ b/kluctl/utils/status_validation.py @@ -52,7 +52,7 @@ def validate_object(o): elif c["type"] == "Warning": result.warnings.append(ValidateResultItem(ref, reason=c.get("reason", "not-ready"), message=c.get("message", "N/A"))) if not ready_found: - result.errors.append(ValidateResultItem(ref, reason="not-readdy", message="Ready condition not found")) + result.errors.append(ValidateResultItem(ref, reason="not-ready", message="Ready condition not found")) elif o["kind"] == "Elasticsearch": if "phase" not in status: result.errors.append(ValidateResultItem(ref, reason="field-not-found", message="phase not in status yet")) From 9c11d28607a7c5af714a2072e0f970855cf19b07 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 24 Aug 2021 10:14:37 +0200 Subject: [PATCH 0027/2916] fix: Ensure stdout/stderr pipes are closed --- kluctl/utils/run_helper.py | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/kluctl/utils/run_helper.py b/kluctl/utils/run_helper.py index 2dcb66fff..39d96a9ed 100644 --- a/kluctl/utils/run_helper.py +++ b/kluctl/utils/run_helper.py @@ -90,26 +90,26 @@ def run_helper(args, cwd=None, env=None, input=None, if isinstance(input, str): input = input.encode('utf-8') process = subprocess.Popen(args=args, stdin=stdin, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd, env=env) + with process: + stdin_thread = None + if input is not None: + stdin_thread = threading.Thread(target=stdin_write_thread, args=(process.stdin, input)) + stdin_thread.start() - stdin_thread = None - if input is not None: - stdin_thread = threading.Thread(target=stdin_write_thread, args=(process.stdin, input)) - stdin_thread.start() - - set_non_blocking(process.stdout) - set_non_blocking(process.stderr) + set_non_blocking(process.stdout) + set_non_blocking(process.stderr) - stdout_reader = StdReader(process.stdout, sys.stdout if print_stdout else None, line_mode, stdout_func, return_std) - stderr_reader = StdReader(process.stderr, sys.stderr if print_stderr else None, line_mode, stderr_func, return_std) - stdout_reader.start() - stderr_reader.start() + stdout_reader = StdReader(process.stdout, sys.stdout if print_stdout else None, line_mode, stdout_func, return_std) + stderr_reader = StdReader(process.stderr, sys.stderr if print_stderr else None, line_mode, stderr_func, return_std) + stdout_reader.start() + stderr_reader.start() - process.wait() - stdout_reader.join() - stderr_reader.join() - if stdin_thread is not None: - stdin_thread.join() + process.wait() + stdout_reader.join() + stderr_reader.join() + if stdin_thread is not None: + stdin_thread.join() - if return_std: - return process.returncode, stdout_reader.capture_buf.getvalue(), stderr_reader.capture_buf.getvalue() - return process.returncode + if return_std: + return process.returncode, stdout_reader.capture_buf.getvalue(), stderr_reader.capture_buf.getvalue() + return process.returncode From b7aa73eca088d1bf399578e9a0650f29da542ed6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 24 Aug 2021 12:25:36 +0200 Subject: [PATCH 0028/2916] fix: Don't allow password promts for git --- kluctl/cli/__main__.py | 4 ++++ kluctl/utils/git_utils.py | 23 +++++++++++++++++------ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/kluctl/cli/__main__.py b/kluctl/cli/__main__.py index 45a52db30..5b429b562 100755 --- a/kluctl/cli/__main__.py +++ b/kluctl/cli/__main__.py @@ -1,6 +1,7 @@ import logging import sys +from git import GitCommandError from jinja2 import TemplateError from yaml import YAMLError @@ -24,6 +25,9 @@ def main(): except TemplateError as e: print_template_error(e) sys.exit(1) + except GitCommandError as e: + print(e.stderr) + sys.exit(1) except Exception as e: from kubernetes.dynamic.exceptions import UnauthorizedError if isinstance(e, UnauthorizedError): diff --git a/kluctl/utils/git_utils.py b/kluctl/utils/git_utils.py index e7685a671..1e44953c3 100644 --- a/kluctl/utils/git_utils.py +++ b/kluctl/utils/git_utils.py @@ -17,6 +17,16 @@ logger = logging.getLogger(__name__) +NO_CREDENTIALS_PROMPT = """#!/usr/bin/env sh +echo >&2 +echo 'Interactive password prompts for git are disabled when running kluctl.' >&2 +echo 'Please ensure credentials for %s are somehow setup.' >&2 +echo 'This can for example be achieved by running a manual git clone operation' >&2 +echo 'with a configured credential helper beforehand.' >&2 +echo >&2 +exit 1 +""" + def get_cache_base_dir(): dir = os.path.join(get_tmp_base_dir(), "git-cache") return dir @@ -74,20 +84,21 @@ def build_git_object(url, working_dir): @contextmanager def create_password_files(): - if username is None: - yield None - return # Must handle closing/deletion manually as otherwise git will complain about busy files password_script = NamedTemporaryFile("w+t", dir=get_tmp_base_dir(), delete=False) password_file = NamedTemporaryFile("w+t", dir=get_tmp_base_dir(), delete=False) - password_file.write(password) - password_script.write(f"#!/usr/bin/env sh\ncat {password_file.name}") + if username is not None: + password_file.write(password) + password_script.write(f"#!/usr/bin/env sh\ncat {password_file.name}") + else: + password_script.write(NO_CREDENTIALS_PROMPT % url) + password_file.close() password_script.close() os.chmod(password_script.name, 0o700) - g.update_environment(GIT_ASKPASS=password_script.name) + g.update_environment(GIT_ASKPASS=password_script.name, GIT_TERMINAL_PROMPT="0") try: yield None finally: From b128e836407f85639a3c309c74ff1ce5a7112aa9 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 24 Aug 2021 12:52:32 +0200 Subject: [PATCH 0029/2916] feat: Allow to write metadata outside of project archive --- docs/commands.md | 8 ++++++-- kluctl/cli/command_stubs.py | 7 +++++-- kluctl/cli/commands.py | 2 +- kluctl/cli/main_cli_group.py | 4 ++++ kluctl/kluctl_project/kluctl_project.py | 22 +++++++++++++++------- 5 files changed, 31 insertions(+), 12 deletions(-) diff --git a/docs/commands.md b/docs/commands.md index e43262755..faef3b99c 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -25,6 +25,8 @@ The following sets of common arguments are available: Local sealed-secrets directory. Overrides the project from .kluctl.yml --from-archive PATH Load project (.kluctl.yml, cluster, ...) from archive. Given path can either be an archive file or a directory with the extracted contents. + --from-archive-metadata PATH Specify where to load metadata (targets, ...) from. If not specified, metadata is + assumed to be part of the archive. --deployment-name TEXT Name of the kluctl deployment. Used when resolving sealed-secrets. Defaults to the base name of --local-deployment/--project-url --cluster TEXT Specify/Override cluster @@ -468,7 +470,9 @@ The following arguments are available: ``` Misc arguments: - --output PATH Path to .tgz to write project to. - --reproducible Make .tgz reproducible. + --output-archive PATH Path to .tgz to write project to. + --output-metadata PATH Path to .yml to write metadata to. If not specified, metadata is written into the + archive. + --reproducible Make archive reproducible. ``` diff --git a/kluctl/cli/command_stubs.py b/kluctl/cli/command_stubs.py index c5e966fa7..90f24936a 100644 --- a/kluctl/cli/command_stubs.py +++ b/kluctl/cli/command_stubs.py @@ -146,11 +146,14 @@ def list_targets_stub(obj, **kwargs): "This archive can then be used with `--from-archive`.") @kluctl_project_args() @optgroup.group("Misc arguments") -@optgroup.option("--output", +@optgroup.option("--output-archive", help="Path to .tgz to write project to.", type=click.Path(file_okay=True)) +@optgroup.option("--output-metadata", + help="Path to .yml to write metadata to. If not specified, metadata is written into the archive.", + type=click.Path(file_okay=True)) @optgroup.option("--reproducible", - help="Make .tgz reproducible.", + help="Make archive reproducible.", is_flag=True) @click.pass_obj def archive_command_stub(obj, **kwargs): diff --git a/kluctl/cli/commands.py b/kluctl/cli/commands.py index c70f1a5e1..9dc807a9e 100644 --- a/kluctl/cli/commands.py +++ b/kluctl/cli/commands.py @@ -160,4 +160,4 @@ def list_targets_command(obj, kwargs): def archive_command(obj, kwargs): with load_kluctl_project_from_args(kwargs) as kluctl_project: - kluctl_project.create_tgz(kwargs["output"], kwargs["reproducible"]) + kluctl_project.create_tgz(kwargs["output_archive"], kwargs["output_metadata"], kwargs["reproducible"]) diff --git a/kluctl/cli/main_cli_group.py b/kluctl/cli/main_cli_group.py index 56e28eb84..6bace20b5 100644 --- a/kluctl/cli/main_cli_group.py +++ b/kluctl/cli/main_cli_group.py @@ -205,6 +205,10 @@ def kluctl_project_args(with_d=True, with_a=True, with_t=True): help="Load project (.kluctl.yml, cluster, ...) from archive. Given path can " "either be an archive file or a directory with the extracted contents.", type=click.Path(dir_okay=True, file_okay=True))) + options.append(optgroup.option("--from-archive-metadata", + help="Specify where to load metadata (targets, ...) from. If not specified, " + "metadata is assumed to be part of the archive.", + type=click.Path(dir_okay=True, file_okay=True))) options.append(optgroup.option("--deployment-name", help="Name of the kluctl deployment. Used when resolving sealed-secrets. " "Defaults to the base name of --local-deployment/--project-url")) diff --git a/kluctl/kluctl_project/kluctl_project.py b/kluctl/kluctl_project/kluctl_project.py index bea4f0b66..082506a9e 100644 --- a/kluctl/kluctl_project/kluctl_project.py +++ b/kluctl/kluctl_project/kluctl_project.py @@ -69,7 +69,7 @@ def __init__(self, deployment_name, project_url, project_ref, config_file, local self.git_cache_up_to_date = {} self.refs_for_urls = {} - def create_tgz(self, path, reproducible): + def create_tgz(self, path, metadata_path, reproducible): with open(path, mode="wb") as f: with gzip.GzipFile(filename="reproducible" if reproducible else None, mode="wb", compresslevel=9, fileobj=f, mtime=0 if reproducible else None) as gz: with tarfile.TarFile.taropen("", mode="w", fileobj=gz) as tar: @@ -88,9 +88,14 @@ def mf_filter(ti: tarfile.TarInfo): "targets": self.targets, "deployment_name": self.deployment_name, } - with NamedTemporaryFile(dir=get_tmp_base_dir()) as tmp: - yaml_save_file(metadata_yaml, tmp.name) - tar.add(tmp.name, "metadata.yml", filter=mf_filter) + if metadata_path is not None: + # metadata.yml outside of archive + yaml_save_file(metadata_yaml, metadata_path) + else: + # metadata.yml as part of the archive + with NamedTemporaryFile(dir=get_tmp_base_dir()) as tmp: + yaml_save_file(metadata_yaml, tmp.name) + tar.add(tmp.name, "metadata.yml", filter=mf_filter) tar.add(self.config_file, ".kluctl.yml", filter=mf_filter) tar.add(self.kluctl_project_dir, "kluctl-project", True, filter=mf_filter) tar.add(self.deployment_dir, "deployment/%s" % self.deployment_name, True, filter=mf_filter) @@ -98,7 +103,7 @@ def mf_filter(ti: tarfile.TarInfo): tar.add(self.sealed_secrets_dir, "sealed-secrets", True, filter=mf_filter) @staticmethod - def from_archive(path, tmp_dir): + def from_archive(path, metadata_path, tmp_dir): if os.path.isfile(path): with open(path, mode="rb") as f: with tarfile.open(mode="r:gz", fileobj=f) as tgz: @@ -106,7 +111,10 @@ def from_archive(path, tmp_dir): dir = tmp_dir else: dir = path - metadata = yaml_load_file(os.path.join(dir, "metadata.yml")) + if metadata_path is not None: + metadata = yaml_load_file(metadata_path) + else: + metadata = yaml_load_file(os.path.join(dir, "metadata.yml")) deployment_dir = os.path.join(dir, "deployment") deployment_name = metadata["deployment_name"] deployment_dir = os.path.join(deployment_dir, deployment_name) @@ -426,7 +434,7 @@ def load_kluctl_project_from_args(kwargs) -> ContextManager[KluctlProject]: if kwargs["from_archive"]: if any(kwargs[x] for x in ["project_url", "project_ref", "project_config", "local_clusters", "local_deployment", "local_sealed_secrets"]): raise CommandError("--from-archive can not be combined with any other project related option") - project = KluctlProject.from_archive(kwargs["from_archive"], tmp_dir) + project = KluctlProject.from_archive(kwargs["from_archive"], kwargs["from_archive_metadata"], tmp_dir) project.load(False) else: deployment_name = kwargs["deployment_name"] From fd137b6eab62797f4fbd86e3255f382e60f22b46 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 24 Aug 2021 13:19:23 +0200 Subject: [PATCH 0030/2916] fix: Also set pipes to non-blocking on darwin --- kluctl/utils/run_helper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kluctl/utils/run_helper.py b/kluctl/utils/run_helper.py index 39d96a9ed..6a5384e91 100644 --- a/kluctl/utils/run_helper.py +++ b/kluctl/utils/run_helper.py @@ -75,7 +75,7 @@ def run(self) -> None: traceback.print_exc() def set_non_blocking(f): - if sys.platform == "linux": + if sys.platform == "linux" or sys.platform == "darwin": fd = f.fileno() fl = fcntl.fcntl(fd, fcntl.F_GETFL) fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK) From 833cbfd544bd0596a3680082be557d71d90a2ed2 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 24 Aug 2021 14:16:25 +0200 Subject: [PATCH 0031/2916] feat: Remove --full-diff-after-deploy feature --- docs/commands.md | 3 --- kluctl/cli/command_stubs.py | 3 --- kluctl/cli/commands.py | 10 ---------- kluctl/deployment/deployment_collection.py | 6 ------ 4 files changed, 22 deletions(-) diff --git a/docs/commands.md b/docs/commands.md index faef3b99c..27f1430cd 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -140,9 +140,6 @@ In addition, the following arguments are available: -o, --output TEXT Specify output format and target file, in the format 'format=path'. Format can either be 'diff' or 'yaml'. Can be specified multiple times. The actual format for yaml is currently not documented and subject to change. - --full-diff-after-deploy TEXT - Perform a full diff (no inclusion/exclusion) directly after the deployent has - finished. The argument has the same meaning as in `-o` ``` diff --git a/kluctl/cli/command_stubs.py b/kluctl/cli/command_stubs.py index 90f24936a..b5a5ca4ba 100644 --- a/kluctl/cli/command_stubs.py +++ b/kluctl/cli/command_stubs.py @@ -24,9 +24,6 @@ def bootstrap_command_stub(obj, **kwargs): @image_args() @include_exclude_args() @misc_arguments(yes=True, dry_run=True, parallel=True, force_apply=True, replace_on_error=True, abort_on_error=True, output_format=True) -@optgroup.option("--full-diff-after-deploy", - help="Perform a full diff (no inclusion/exclusion) directly after the deployent has finished. " - "The argument has the same meaning as in `-o`") @click.pass_obj def deploy_command_stub(obj, **kwargs): from kluctl.cli.commands import deploy_command diff --git a/kluctl/cli/commands.py b/kluctl/cli/commands.py index 9dc807a9e..8eaec3fc4 100644 --- a/kluctl/cli/commands.py +++ b/kluctl/cli/commands.py @@ -13,7 +13,6 @@ from kluctl.kluctl_project.kluctl_project import load_kluctl_project_from_args from kluctl.utils.dict_utils import get_dict_value from kluctl.utils.exceptions import CommandError -from kluctl.utils.inclusion import Inclusion from kluctl.utils.k8s_object_utils import get_long_object_name_from_ref, ObjectRef from kluctl.utils.utils import get_tmp_base_dir, duration @@ -24,7 +23,6 @@ def bootstrap_command(obj, kwargs): bootstrap_path = os.path.join(get_kluctl_package_dir(), "bootstrap") kwargs["local_deployment"] = bootstrap_path - kwargs["full_diff_after_deploy"] = False with project_command_context(kwargs) as cmd_ctx: existing, warnings = cmd_ctx.k8s_cluster.get_single_object(ObjectRef("apiextensions.k8s.io/v1", "CustomResourceDefinition", "sealedsecrets.bitnami.com")) if existing: @@ -50,14 +48,6 @@ def deploy_command2(obj, kwargs, cmd_ctx): output_diff_result(kwargs["output"], cmd_ctx.deployment_collection, diff_result, deleted_objects) if diff_result.errors: sys.exit(1) - if kwargs["full_diff_after_deploy"]: - logger.info("Running full diff after deploy") - cmd_ctx.deployment_collection.update_remote_objects_from_diff(diff_result) - cmd_ctx.deployment_collection.inclusion = Inclusion() - diff_result = cmd_ctx.deployment_collection.diff(cmd_ctx.k8s_cluster, kwargs["force_apply"], False, False, False, False, False) - output_diff_result([kwargs["full_diff_after_deploy"]], cmd_ctx.deployment_collection, diff_result, deleted_objects) - if diff_result.errors: - sys.exit(1) def diff_command(obj, kwargs): with project_command_context(kwargs) as cmd_ctx: diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index 0ec5d6570..4e9cb7a3e 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -111,12 +111,6 @@ def update_remote_objects(self, k8s_cluster): self.remote_objects[get_object_ref(o)] = o self.add_api_warnings(get_object_ref(o), w) - def update_remote_objects_from_diff(self, diff_result: DeployDiffResult): - for o in diff_result.new_objects: - self.remote_objects[get_object_ref(o)] = o - for co in diff_result.changed_objects: - self.remote_objects[get_object_ref(co["new_object"])] = co["new_object"] - def forget_remote_objects(self, refs): for ref in refs: self.remote_objects.pop(ref, None) From 7e2cc38df302655fa4082cef49530c7fe14cf4eb Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 24 Aug 2021 15:29:54 +0200 Subject: [PATCH 0032/2916] feat: Always run deployments in parallel --- docs/commands.md | 4 - kluctl/cli/command_stubs.py | 4 +- kluctl/cli/commands.py | 3 +- kluctl/cli/main_cli_group.py | 7 +- kluctl/deployment/deployment_collection.py | 111 +++++++++++++-------- 5 files changed, 73 insertions(+), 56 deletions(-) diff --git a/docs/commands.md b/docs/commands.md index 27f1430cd..3755706fb 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -96,8 +96,6 @@ In addition, the following arguments are available: Misc arguments: -y, --yes Supresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. --dry-run Performs all kubernetes API calls in dry-run mode. - --parallel Run deployment in parallel instead of sequentially. See documentation for more - details. --force-apply Force conflict resolution when applying. See documentation for details --replace-on-error When patching an object fails, try to delete it and then retry. See documentation for more details. @@ -131,8 +129,6 @@ In addition, the following arguments are available: Misc arguments: -y, --yes Supresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. --dry-run Performs all kubernetes API calls in dry-run mode. - --parallel Run deployment in parallel instead of sequentially. See documentation for more - details. --force-apply Force conflict resolution when applying. See documentation for details --replace-on-error When patching an object fails, try to delete it and then retry. See documentation for more details. diff --git a/kluctl/cli/command_stubs.py b/kluctl/cli/command_stubs.py index b5a5ca4ba..d673e00f9 100644 --- a/kluctl/cli/command_stubs.py +++ b/kluctl/cli/command_stubs.py @@ -9,7 +9,7 @@ "installed.\n\n" "Either --target or --cluster must be specified.") @kluctl_project_args(with_a=False) -@misc_arguments(yes=True, dry_run=True, parallel=True, force_apply=True, replace_on_error=True, abort_on_error=True, output_format=True) +@misc_arguments(yes=True, dry_run=True, force_apply=True, replace_on_error=True, abort_on_error=True, output_format=True) @click.pass_obj def bootstrap_command_stub(obj, **kwargs): from kluctl.cli.commands import bootstrap_command @@ -23,7 +23,7 @@ def bootstrap_command_stub(obj, **kwargs): @kluctl_project_args() @image_args() @include_exclude_args() -@misc_arguments(yes=True, dry_run=True, parallel=True, force_apply=True, replace_on_error=True, abort_on_error=True, output_format=True) +@misc_arguments(yes=True, dry_run=True, force_apply=True, replace_on_error=True, abort_on_error=True, output_format=True) @click.pass_obj def deploy_command_stub(obj, **kwargs): from kluctl.cli.commands import deploy_command diff --git a/kluctl/cli/commands.py b/kluctl/cli/commands.py index 8eaec3fc4..e96cb90f1 100644 --- a/kluctl/cli/commands.py +++ b/kluctl/cli/commands.py @@ -42,8 +42,7 @@ def deploy_command2(obj, kwargs, cmd_ctx): if not kwargs["yes"] and not kwargs["dry_run"]: click.confirm("Do you really want to deploy to the context/cluster %s?" % cmd_ctx.k8s_cluster.context, err=True, abort=True) - parallel = kwargs["parallel"] - diff_result = cmd_ctx.deployment_collection.deploy(cmd_ctx.k8s_cluster, parallel, kwargs["force_apply"], kwargs["replace_on_error"], kwargs["abort_on_error"]) + diff_result = cmd_ctx.deployment_collection.deploy(cmd_ctx.k8s_cluster, kwargs["force_apply"], kwargs["replace_on_error"], kwargs["abort_on_error"]) deleted_objects = cmd_ctx.deployment_collection.find_purge_objects(cmd_ctx.k8s_cluster) output_diff_result(kwargs["output"], cmd_ctx.deployment_collection, diff_result, deleted_objects) if diff_result.errors: diff --git a/kluctl/cli/main_cli_group.py b/kluctl/cli/main_cli_group.py index 6bace20b5..496ebfc50 100644 --- a/kluctl/cli/main_cli_group.py +++ b/kluctl/cli/main_cli_group.py @@ -119,7 +119,7 @@ def wrapper(func): return func return wrapper -def misc_arguments(yes=False, dry_run=False, parallel=False, force_apply=False, replace_on_error=False, +def misc_arguments(yes=False, dry_run=False, force_apply=False, replace_on_error=False, ignore_labels=False, ignore_order=False, abort_on_error=False, output_format=False, output=False): options = [] @@ -133,11 +133,6 @@ def misc_arguments(yes=False, dry_run=False, parallel=False, force_apply=False, options.append(optgroup.option("--dry-run", help="Performs all kubernetes API calls in dry-run mode.", default=False, is_flag=True)) - if parallel: - options.append(optgroup.option("--parallel", - help="Run deployment in parallel instead of sequentially. See documentation " - "for more details.", - default=False, is_flag=True)) if force_apply: options.append(optgroup.option("--force-apply", help="Force conflict resolution when applying. See documentation for details", diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index 4e9cb7a3e..ac1b31fc2 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -1,6 +1,8 @@ import dataclasses import itertools import logging +from concurrent.futures import Future +from typing import Optional from kubernetes.client import ApiException from kubernetes.dynamic.exceptions import ResourceNotFoundError, ConflictError @@ -120,12 +122,12 @@ def local_objects_by_ref(self): by_ref = dict((get_object_ref(x), x) for x in flat) return by_ref - def deploy(self, k8s_cluster, parallel, force_apply, replace_on_error, abort_on_error): + def deploy(self, k8s_cluster, force_apply, replace_on_error, abort_on_error): self.clear_errors_and_warnings() self.render_deployments() self.build_kustomize_objects() applied_objects = self.do_deploy(k8s_cluster, force_apply, replace_on_error, - False, not parallel, abort_on_error) + False, abort_on_error) new_objects, changed_objects = self.do_diff(k8s_cluster, applied_objects, False, False, False, False) return DeployDiffResult(new_objects=new_objects, changed_objects=changed_objects, errors=list(self.api_errors), warnings=list(self.api_warnings)) @@ -135,7 +137,7 @@ def diff(self, k8s_cluster, force_apply, replace_on_error, ignore_tags, ignore_l self.render_deployments() self.build_kustomize_objects() applied_objects = self.do_deploy(k8s_cluster, force_apply, replace_on_error, - True, False, False) + True, False) new_objects, changed_objects = self.do_diff(k8s_cluster, applied_objects, ignore_tags, ignore_labels, ignore_annotations, ignore_order) return DeployDiffResult(new_objects=new_objects, changed_objects=changed_objects, errors=list(self.api_errors), warnings=list(self.api_warnings)) @@ -181,41 +183,33 @@ def find_purge_objects(self, k8s_cluster): excluded_objects = list(self.local_objects_by_ref().keys()) return find_objects_for_delete(k8s_cluster, labels, self.inclusion, excluded_objects) - def do_patch(self, k8s_cluster, force_apply, replace_on_error, dry_run, ordered, abort_on_error): + def do_patch(self, k8s_cluster, force_apply, replace_on_error, dry_run, abort_on_error): logger.info("Running server-side apply for all objects%s", k8s_cluster.get_dry_run_suffix(dry_run)) - applied_objects = {} - futures = [] - def finish_futures(): - for o, f in futures: - try: - r, patch_warnings = f.result() - # We must use the ref from the applied object as k8s might remove the namespace field - ref = get_object_ref(r) - applied_objects[ref] = r - self.add_api_warnings(ref, patch_warnings) - except ResourceNotFoundError as e: - ref = get_object_ref(o) - self.add_api_error(ref, k8s_cluster.get_status_message(e)) - except ApiException as e: - ref = get_object_ref(o) - if replace_on_error: - logger.info("Patching %s failed, retrying by deleting and re-applying" % get_long_object_name_from_ref(ref)) - try: - k8s_cluster.delete_single_object(ref, force_dry_run=dry_run, ignore_not_found=True) - if not dry_run and not k8s_cluster.dry_run: - r, patch_warnings = k8s_cluster.patch_object(o, force_apply=True) - # We must use the ref from the applied object as k8s might remove the namespace field - ref = get_object_ref(r) - applied_objects[ref] = r - self.add_api_warnings(ref, patch_warnings) - else: - applied_objects[ref] = o - except ApiException as e2: - self.add_api_error(ref, k8s_cluster.get_status_message(e2)) - else: - self.add_api_error(ref, k8s_cluster.get_status_message(e)) - futures.clear() + jobs = [] + + @dataclasses.dataclass(init=False) + class Job: + object: dict + future: Future + result_object: Optional[dict] + result_exception: Optional[Exception] + done: bool = False + + def add_job(o, f): + job = Job() + job.object = o + job.future = f + jobs.append(job) + + def finish_jobs(): + for job in jobs: + if job.done: + continue + job.result_exception = job.future.exception() + if job.result_exception is None: + job.result = job.future.result() + job.done = True with MyThreadPoolExecutor(max_workers=16) as executor: for d in self.deployments: @@ -250,14 +244,47 @@ def dummy(x3): f = executor.submit(dummy, x2) else: f = executor.submit(k8s_cluster.patch_object, x2, force_dry_run=dry_run, force_apply=True) - futures.append((x2, f)) - if ordered: - finish_futures() + add_job(x2, f) + finish_jobs() + + applied_objects = {} + for job in jobs: + if job.result_exception is not None: + try: + raise job.result_exception + except ResourceNotFoundError as e: + ref = get_object_ref(job.object) + self.add_api_error(ref, k8s_cluster.get_status_message(e)) + except ApiException as e: + ref = get_object_ref(job.object) + if not replace_on_error: + self.add_api_error(ref, k8s_cluster.get_status_message(e)) + continue + logger.info("Patching %s failed, retrying by deleting and re-applying" % + get_long_object_name_from_ref(ref)) + try: + k8s_cluster.delete_single_object(ref, force_dry_run=dry_run, ignore_not_found=True) + if not dry_run and not k8s_cluster.dry_run: + r, patch_warnings = k8s_cluster.patch_object(job.object, force_apply=True) + # We must use the ref from the applied object as k8s might remove the namespace field + ref = get_object_ref(r) + applied_objects[ref] = r + self.add_api_warnings(ref, patch_warnings) + else: + applied_objects[ref] = job.object + except ApiException as e2: + self.add_api_error(ref, k8s_cluster.get_status_message(e2)) + continue + + r, patch_warnings = job.result + # We must use the ref from the applied object as k8s might remove the namespace field + ref = get_object_ref(r) + applied_objects[ref] = r + self.add_api_warnings(ref, patch_warnings) - finish_futures() return applied_objects - def do_deploy(self, k8s_cluster, force_apply, replace_on_error, dry_run, ordered, abort_on_error): + def do_deploy(self, k8s_cluster, force_apply, replace_on_error, dry_run, abort_on_error): self.update_remote_objects(k8s_cluster) replaced_objects = self.find_replaced_objects() @@ -281,7 +308,7 @@ def do_deploy(self, k8s_cluster, force_apply, replace_on_error, dry_run, ordered # TODO remove this self.migrate_to_new_manager(k8s_cluster) - return self.do_patch(k8s_cluster, force_apply, replace_on_error, dry_run, ordered, abort_on_error) + return self.do_patch(k8s_cluster, force_apply, replace_on_error, dry_run, abort_on_error) def do_diff(self, k8s_cluster, applied_objects, ignore_tags, ignore_labels, ignore_annotations, ignore_order): diff_objects = {} From 4cb300f5f2be2fadba4647dd0ed86c468a33d8a9 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 24 Aug 2021 15:53:46 +0200 Subject: [PATCH 0033/2916] feat: Implement barriers --- docs/deployments.md | 13 ++++++++++--- kluctl/deployment/deployment_collection.py | 11 +++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/docs/deployments.md b/docs/deployments.md index 51831421f..6178811b3 100644 --- a/docs/deployments.md +++ b/docs/deployments.md @@ -116,6 +116,10 @@ The relative path of the kustomize deployment. It must contain a valid `kustomiz #### tags A list of tags the kustomize deployment should have. See [tags](./tags.md) for more details. +#### barrier +Causes kluctl to wait until the current and all previous kustomize deployments have been applied. This is useful when +upcoming deployments need the current or previous deployments to be finished beforehand. + #### alwaysDeploy Forces a kustomize Deployment to be included everytime, ignoring inclusion/exclusion sets from the command line. See [Deploying with tag inclusion/exclusion](./tags.md#deploying-with-tag-inclusionexclusion) for details. @@ -154,6 +158,9 @@ The relative path of the sub-deployment project. It must contain a valid `deploy A list of tags the include and all of its sub-includes and kustomize deployments should have. See [tags](./tags.md) for more details. +#### barrier +Same as `barrier` in `kustomizeDirs`. + ### commonLabels A dictionary of [labels](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/) and values to be added to all resources deployed by any of the kustomize deployments in this deployment project. @@ -249,6 +256,6 @@ that includes Go templates, which will in most cases make Jinja2 templating fail ### ignoreForDiff # Order of deployment -When deploying a project, the order of kustomize deployments is well-defined. Deployment will always start with -kustomize deployments found in the current deployment.yml and only after that continue with kustomize deployments -from included sub-deployment projects. +Deployments are done in parallel, meaning that there are usually no order guarantees. The only way to somehow control +order, is by placing [barriers](#barrier) between kustomize deployments. You should however not overuse barriers, as +they negatively impact the speed of kluctl. diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index ac1b31fc2..a052056bc 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -60,6 +60,10 @@ def _collect_deployments(self, project): ret.append(deployment) for inc in project.conf['includes']: + if get_dict_value(inc, "barrier", False): + deployment = KustomizeDeployment(project, self, {"barrier": True}) + ret.append(deployment) + d = inc.get('_included_deployment_collection') if d is not None: ret += self._collect_deployments(d) @@ -212,9 +216,16 @@ def finish_jobs(): job.done = True with MyThreadPoolExecutor(max_workers=16) as executor: + previous_was_barrier = False for d in self.deployments: if self.api_errors and abort_on_error: break + + if previous_was_barrier: + logger.info("Waiting on barrier...") + finish_jobs() + previous_was_barrier = get_dict_value(d.config, "barrier", False) + include = d.check_inclusion_for_deploy() if "path" not in d.config: continue From 32075555e947a8c3d8065365aca0da6a386aa6ef Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 24 Aug 2021 15:54:10 +0200 Subject: [PATCH 0034/2916] fix: Fix helm-pull/helm-update commands --- kluctl/cli/util_command_stubs.py | 12 ++++++++++-- kluctl/cli/util_commands.py | 4 ++-- kluctl/deployment/helm_chart.py | 3 ++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/kluctl/cli/util_command_stubs.py b/kluctl/cli/util_command_stubs.py index 8fb42759e..0a770298a 100644 --- a/kluctl/cli/util_command_stubs.py +++ b/kluctl/cli/util_command_stubs.py @@ -17,7 +17,11 @@ def list_image_tags_command_stub(image, output): "The Helm charts are stored under the sub-directory `charts/` next to the " "`helm-chart.yml`. These Helm charts are meant to be added to version control so that " "pulling is only needed when really required (e.g. when the chart version changes).") -@kluctl_project_args(with_a=False) +@optgroup.group("Project arguments") +@optgroup.option("--local-deployment", + help="Local deployment directory. Defaults to current directory", + default=".", + type=click.Path(file_okay=False)) def helm_pull_command_stub(**kwargs): from kluctl.cli.util_commands import helm_pull_command helm_pull_command(kwargs) @@ -25,7 +29,11 @@ def helm_pull_command_stub(**kwargs): @cli_group.command("helm-update", help="Recursively searches for `helm-chart.yml` files and checks for new available versions.\n\n" "Optionally performs the actual upgrade and/or add a commit to version control.") -@kluctl_project_args(with_a=False) +@optgroup.group("Project arguments") +@optgroup.option("--local-deployment", + help="Local deployment directory. Defaults to current directory", + default=".", + type=click.Path(file_okay=False)) @optgroup.group("Misc arguments") @optgroup.option("--upgrade", help="Write new versions into helm-chart.yml and perform helm-pull afterwards", is_flag=True) @optgroup.option("--commit", help="Create a git commit for every updated chart", is_flag=True) diff --git a/kluctl/cli/util_commands.py b/kluctl/cli/util_commands.py index 141a75bf4..4b371cafd 100644 --- a/kluctl/cli/util_commands.py +++ b/kluctl/cli/util_commands.py @@ -64,7 +64,7 @@ def list_image_tags_command(image, output): output_yaml_result(output, result) def helm_pull_command(kwargs): - for dirpath, dirnames, filenames in os.walk(kwargs["deployment"]): + for dirpath, dirnames, filenames in os.walk(kwargs["local_deployment"]): for fname in filenames: if fname == 'helm-chart.yml': path = os.path.join(dirpath, fname) @@ -73,7 +73,7 @@ def helm_pull_command(kwargs): chart.pull() def helm_update_command(upgrade, commit, kwargs): - for dirpath, dirnames, filenames in os.walk(kwargs["deployment"]): + for dirpath, dirnames, filenames in os.walk(kwargs["local_deployment"]): for fname in filenames: if fname == 'helm-chart.yml': path = os.path.join(dirpath, fname) diff --git a/kluctl/deployment/helm_chart.py b/kluctl/deployment/helm_chart.py index 69ae1900d..b3e9333a2 100644 --- a/kluctl/deployment/helm_chart.py +++ b/kluctl/deployment/helm_chart.py @@ -3,6 +3,7 @@ import os import shutil +from kluctl.utils.exceptions import CommandError from kluctl.utils.run_helper import run_helper from kluctl.utils.versions import LooseVersionComparator from kluctl.utils.yaml_utils import yaml_load_file, yaml_load_all, yaml_dump_all, yaml_load, yaml_save_file @@ -105,5 +106,5 @@ def do_helm(self, args, input=None, ignoreErrors=False, ignoreStderr=False): r, stdout, stderr = run_helper(args=args, input=input, print_stdout=False, print_stderr=not ignoreStderr, return_std=True) if r != 0 and not ignoreErrors: - raise Exception("helm failed: r=%d\nout=%s\nerr=%s" % (r, stdout.decode("utf-8"), stderr.decode("utf-8"))) + raise CommandError("helm failed") return r, stdout, stderr From 3d0a51b774271a7a314f4380d1de485dd6ffff26 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 24 Aug 2021 16:41:30 +0200 Subject: [PATCH 0035/2916] fix: Add missing import --- kluctl/seal/deployment_sealer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/kluctl/seal/deployment_sealer.py b/kluctl/seal/deployment_sealer.py index 376775abd..a5171a920 100644 --- a/kluctl/seal/deployment_sealer.py +++ b/kluctl/seal/deployment_sealer.py @@ -2,6 +2,7 @@ import os from kluctl.seal.sealer import Sealer +from kluctl.utils.dict_utils import get_dict_value from kluctl.utils.exceptions import InvalidKluctlProjectConfig logger = logging.getLogger(__name__) From d2641069a7aec8fe5ce8501aa930eb3dd7bca2e7 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 25 Aug 2021 15:15:57 +0200 Subject: [PATCH 0036/2916] feat: Remove deployment-name from sealed secrets output directory --- docs/commands.md | 2 -- docs/sealed-secrets.md | 4 +--- kluctl/cli/main_cli_group.py | 3 --- kluctl/cli/utils.py | 2 +- kluctl/deployment/deployment_project.py | 7 ++----- kluctl/deployment/kustomize_deployment.py | 3 +-- kluctl/kluctl_project/kluctl_project.py | 21 ++++----------------- kluctl/seal/deployment_sealer.py | 2 +- kluctl/tests/test_base.py | 2 +- 9 files changed, 11 insertions(+), 35 deletions(-) diff --git a/docs/commands.md b/docs/commands.md index 3755706fb..48b3359d3 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -27,8 +27,6 @@ The following sets of common arguments are available: archive file or a directory with the extracted contents. --from-archive-metadata PATH Specify where to load metadata (targets, ...) from. If not specified, metadata is assumed to be part of the archive. - --deployment-name TEXT Name of the kluctl deployment. Used when resolving sealed-secrets. Defaults to the - base name of --local-deployment/--project-url --cluster TEXT Specify/Override cluster -a, --arg TEXT Template argument in the form name=value -t, --target TEXT Target name to run command for. Target must exist in .kluctl.yml. diff --git a/docs/sealed-secrets.md b/docs/sealed-secrets.md index 7c527c983..c7a91e786 100644 --- a/docs/sealed-secrets.md +++ b/docs/sealed-secrets.md @@ -77,14 +77,12 @@ As an example, `{{ cluster.name }}/{{ args.environment }}` works well, assuming The final storage location for the sealed secret is: -`////` +`///` with: * `base_dir`: The base directory for sealed secrets is configured in the [.kluctl.yml](./kluctl_project.md#sealedsecrets) config file. If not specified, the base directory defaults to the subdirectory `.sealed-secrets` in the kluctl project root diretory. -* `deployment_name`: The deployment name, which defaults to the kluctl project directories base name. It can also be -overridden with [--deployment-name](./commands.md#project-arguments). * `rendered_output_pattern`: The rendered outputPattern as described above. * `relative_sealme_file_dir`: The relative path from the deployment root directory. * `file_name`: The filename of the sealed secret, excluding the `.sealme` extension. diff --git a/kluctl/cli/main_cli_group.py b/kluctl/cli/main_cli_group.py index 496ebfc50..935f8bd7d 100644 --- a/kluctl/cli/main_cli_group.py +++ b/kluctl/cli/main_cli_group.py @@ -204,9 +204,6 @@ def kluctl_project_args(with_d=True, with_a=True, with_t=True): help="Specify where to load metadata (targets, ...) from. If not specified, " "metadata is assumed to be part of the archive.", type=click.Path(dir_okay=True, file_okay=True))) - options.append(optgroup.option("--deployment-name", - help="Name of the kluctl deployment. Used when resolving sealed-secrets. " - "Defaults to the base name of --local-deployment/--project-url")) options.append(optgroup.option("--cluster", help="Specify/Override cluster")) if with_a: diff --git a/kluctl/cli/utils.py b/kluctl/cli/utils.py index 0678d014b..ea49f291e 100644 --- a/kluctl/cli/utils.py +++ b/kluctl/cli/utils.py @@ -143,7 +143,7 @@ def project_target_command_context(kwargs, kluctl_project, target, with tempfile.TemporaryDirectory(dir=get_tmp_base_dir()) as tmpdir: if output_dir is None: output_dir = tmpdir - d = DeploymentProject(kluctl_project.deployment_dir, kluctl_project.deployment_name, jinja_vars, deploy_args, kluctl_project.sealed_secrets_dir) + d = DeploymentProject(kluctl_project.deployment_dir, jinja_vars, deploy_args, kluctl_project.sealed_secrets_dir) c = DeploymentCollection(d, images=images, inclusion=inclusion, tmpdir=output_dir, for_seal=for_seal) fixed_images = load_fixed_images(kwargs) diff --git a/kluctl/deployment/deployment_project.py b/kluctl/deployment/deployment_project.py index 9385cdce3..1863169d5 100644 --- a/kluctl/deployment/deployment_project.py +++ b/kluctl/deployment/deployment_project.py @@ -15,15 +15,12 @@ class DeploymentProject(object): - def __init__(self, dir, deployment_name, jinja_vars, deploy_args, sealed_secrets_dir, + def __init__(self, dir, jinja_vars, deploy_args, sealed_secrets_dir, parent_collection=None): if not os.path.exists(dir): raise CommandError("%s does not exist" % dir) self.dir = dir - self.deployment_name = deployment_name - if self.deployment_name is None: - self.deployment_name = os.path.basename(self.dir) self.jinja_vars = copy_dict(jinja_vars) self.deploy_args = deploy_args self.sealed_secrets_dir = sealed_secrets_dir @@ -122,7 +119,7 @@ def load_includes(self): if 'path' not in inc: continue inc_dir = os.path.join(self.dir, inc["path"]) - c = DeploymentProject(inc_dir, None, self.jinja_vars, self.deploy_args, self.sealed_secrets_dir, parent_collection=self) + c = DeploymentProject(inc_dir, self.jinja_vars, self.deploy_args, self.sealed_secrets_dir, parent_collection=self) c.parent_collection_include = inc inc['_included_deployment_collection'] = c diff --git a/kluctl/deployment/kustomize_deployment.py b/kluctl/deployment/kustomize_deployment.py index ba2d8d444..a14c6d70d 100644 --- a/kluctl/deployment/kustomize_deployment.py +++ b/kluctl/deployment/kustomize_deployment.py @@ -125,10 +125,9 @@ def resolve_sealed_secrets(self): return sealed_secrets_dir = self.deployment_project.get_sealed_secrets_dir(self.config["path"]) - root_deployment_name = os.path.basename(os.path.abspath(self.deployment_project.get_root_deployment().deployment_name)) rel_dir_to_root = self.deployment_project.get_rel_dir_to_root() rendered_dir = self.get_rendered_dir() - base_source_path = os.path.join(self.deployment_project.sealed_secrets_dir, root_deployment_name, rel_dir_to_root) + base_source_path = os.path.join(self.deployment_project.sealed_secrets_dir, rel_dir_to_root) y = yaml_load_file(os.path.join(rendered_dir, "kustomization.yml")) or {} for resource in y.get("resources", []): diff --git a/kluctl/kluctl_project/kluctl_project.py b/kluctl/kluctl_project/kluctl_project.py index 082506a9e..c2ad5ab8e 100644 --- a/kluctl/kluctl_project/kluctl_project.py +++ b/kluctl/kluctl_project/kluctl_project.py @@ -53,8 +53,7 @@ class GitProjectInfo: dir: str class KluctlProject: - def __init__(self, deployment_name, project_url, project_ref, config_file, local_clusters, local_deployment, local_sealed_secrets, tmp_dir): - self.deployment_name = deployment_name + def __init__(self, project_url, project_ref, config_file, local_clusters, local_deployment, local_sealed_secrets, tmp_dir): self.project_url = project_url self.project_ref = project_ref self.config_file = config_file @@ -86,7 +85,6 @@ def mf_filter(ti: tarfile.TarInfo): metadata_yaml = { "involved_repos": self.involved_repos, "targets": self.targets, - "deployment_name": self.deployment_name, } if metadata_path is not None: # metadata.yml outside of archive @@ -98,7 +96,7 @@ def mf_filter(ti: tarfile.TarInfo): tar.add(tmp.name, "metadata.yml", filter=mf_filter) tar.add(self.config_file, ".kluctl.yml", filter=mf_filter) tar.add(self.kluctl_project_dir, "kluctl-project", True, filter=mf_filter) - tar.add(self.deployment_dir, "deployment/%s" % self.deployment_name, True, filter=mf_filter) + tar.add(self.deployment_dir, "deployment", True, filter=mf_filter) tar.add(self.clusters_dir, "clusters", True, filter=mf_filter) tar.add(self.sealed_secrets_dir, "sealed-secrets", True, filter=mf_filter) @@ -116,9 +114,7 @@ def from_archive(path, metadata_path, tmp_dir): else: metadata = yaml_load_file(os.path.join(dir, "metadata.yml")) deployment_dir = os.path.join(dir, "deployment") - deployment_name = metadata["deployment_name"] - deployment_dir = os.path.join(deployment_dir, deployment_name) - project = KluctlProject(deployment_name, None, None, + project = KluctlProject(None, None, os.path.join(dir, ".kluctl.yml"), os.path.join(dir, "clusters"), deployment_dir, @@ -437,16 +433,7 @@ def load_kluctl_project_from_args(kwargs) -> ContextManager[KluctlProject]: project = KluctlProject.from_archive(kwargs["from_archive"], kwargs["from_archive_metadata"], tmp_dir) project.load(False) else: - deployment_name = kwargs["deployment_name"] - if deployment_name is None: - if kwargs["project_url"]: - url = parse_git_url(kwargs["project_url"]) - deployment_name = url.path.split("/")[-1] - elif kwargs["local_deployment"]: - deployment_name = os.path.basename(kwargs["local_deployment"]) - else: - deployment_name = os.path.basename(os.getcwd()) - project = KluctlProject(deployment_name, kwargs["project_url"], kwargs["project_ref"], kwargs["project_config"], kwargs["local_clusters"], kwargs["local_deployment"], kwargs["local_sealed_secrets"], tmp_dir) + project = KluctlProject(kwargs["project_url"], kwargs["project_ref"], kwargs["project_config"], kwargs["local_clusters"], kwargs["local_deployment"], kwargs["local_sealed_secrets"], tmp_dir) project.load(True) project.load_targets() yield project diff --git a/kluctl/seal/deployment_sealer.py b/kluctl/seal/deployment_sealer.py index a5171a920..6df2da0a5 100644 --- a/kluctl/seal/deployment_sealer.py +++ b/kluctl/seal/deployment_sealer.py @@ -33,7 +33,7 @@ def seal_deployment(self): for dirpath, dirnames, filenames in os.walk(self.deployment_collection.tmpdir): rel_dir = os.path.relpath(dirpath, self.deployment_collection.tmpdir) - target_dir = os.path.join(self.sealed_secrets_dir, self.deployment_project.deployment_name, rel_dir) + target_dir = os.path.join(self.sealed_secrets_dir, rel_dir) for f in filenames: if f.endswith(SEALME_EXT): source_file = os.path.join(dirpath, f) diff --git a/kluctl/tests/test_base.py b/kluctl/tests/test_base.py index 7e6b84216..3157be1b8 100644 --- a/kluctl/tests/test_base.py +++ b/kluctl/tests/test_base.py @@ -39,7 +39,7 @@ def tearDown(self): @contextlib.contextmanager def build_deployment(self, subdir, jinja_vars={}, deploy_args={}): with tempfile.TemporaryDirectory(dir=get_tmp_base_dir()) as tmpdir: - d = DeploymentProject(os.path.join(cur_dir, subdir), None, jinja_vars, deploy_args, + d = DeploymentProject(os.path.join(cur_dir, subdir), jinja_vars, deploy_args, os.path.join(cur_dir, subdir, '.sealed-secrets')) c = DeploymentCollection(d, None, None, tmpdir, False) yield d, c From a67010990bb0e8d0b8554dc5cfa266f85ecbc9ac Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 25 Aug 2021 19:10:12 +0200 Subject: [PATCH 0037/2916] fix: Remove namespace from non-namespaces objects after building with kustomize --- kluctl/cli/util_commands.py | 2 +- kluctl/deployment/deployment_collection.py | 23 +++++++++++----------- kluctl/deployment/kustomize_deployment.py | 8 +++++++- kluctl/utils/k8s_object_utils.py | 16 +++++++++++---- 4 files changed, 31 insertions(+), 18 deletions(-) diff --git a/kluctl/cli/util_commands.py b/kluctl/cli/util_commands.py index 4b371cafd..b19a12277 100644 --- a/kluctl/cli/util_commands.py +++ b/kluctl/cli/util_commands.py @@ -107,7 +107,7 @@ def check_image_updates(obj, kwargs): cmd_ctx.images.no_kubernetes = True cmd_ctx.images.raise_on_error = False cmd_ctx.deployment_collection.render_deployments() - cmd_ctx.deployment_collection.build_kustomize_objects() + cmd_ctx.deployment_collection.build_kustomize_objects(cmd_ctx.k8s_cluster) rendered_images = cmd_ctx.deployment_collection.find_rendered_images() prefix_pattern = re.compile(r"^([a-zA-Z]+[a-zA-Z-_.]*)") diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index a052056bc..f96bf1145 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -7,15 +7,15 @@ from kubernetes.client import ApiException from kubernetes.dynamic.exceptions import ResourceNotFoundError, ConflictError +from kluctl.deployment.images import SeenImages from kluctl.deployment.kustomize_deployment import KustomizeDeployment from kluctl.diff.k8s_diff import deep_diff_object from kluctl.diff.managed_fields import remove_non_managed_fields2 from kluctl.diff.normalize import normalize_object -from kluctl.deployment.images import SeenImages from kluctl.utils.dict_utils import copy_dict, get_dict_value from kluctl.utils.k8s_delete_utils import find_objects_for_delete from kluctl.utils.k8s_object_utils import get_object_ref, get_long_object_name, get_long_object_name_from_ref, \ - ObjectRef, remove_namespace_from_ref_if_needed + ObjectRef from kluctl.utils.status_validation import validate_object, ValidateResult, ValidateResultItem from kluctl.utils.templated_dir import TemplatedDir from kluctl.utils.utils import MyThreadPoolExecutor @@ -93,7 +93,7 @@ def render_deployments(self): d.resolve_sealed_secrets() self.is_rendered = True - def build_kustomize_objects(self): + def build_kustomize_objects(self, k8s_cluster): if self.is_built: return logger.info("Building kustomize objects") @@ -101,7 +101,7 @@ def build_kustomize_objects(self): with MyThreadPoolExecutor() as executor: jobs = [] for d in self.deployments: - jobs.append(executor.submit(d.build_objects)) + jobs.append(executor.submit(d.build_objects, k8s_cluster)) for job in jobs: job.result() self.is_built = True @@ -129,7 +129,7 @@ def local_objects_by_ref(self): def deploy(self, k8s_cluster, force_apply, replace_on_error, abort_on_error): self.clear_errors_and_warnings() self.render_deployments() - self.build_kustomize_objects() + self.build_kustomize_objects(k8s_cluster) applied_objects = self.do_deploy(k8s_cluster, force_apply, replace_on_error, False, abort_on_error) new_objects, changed_objects = self.do_diff(k8s_cluster, applied_objects, False, False, False, False) @@ -139,7 +139,7 @@ def deploy(self, k8s_cluster, force_apply, replace_on_error, abort_on_error): def diff(self, k8s_cluster, force_apply, replace_on_error, ignore_tags, ignore_labels, ignore_annotations, ignore_order): self.clear_errors_and_warnings() self.render_deployments() - self.build_kustomize_objects() + self.build_kustomize_objects(k8s_cluster) applied_objects = self.do_deploy(k8s_cluster, force_apply, replace_on_error, True, False) new_objects, changed_objects = self.do_diff(k8s_cluster, applied_objects, ignore_tags, ignore_labels, ignore_annotations, ignore_order) @@ -149,7 +149,7 @@ def diff(self, k8s_cluster, force_apply, replace_on_error, ignore_tags, ignore_l def validate(self, k8s_cluster): self.clear_errors_and_warnings() self.render_deployments() - self.build_kustomize_objects() + self.build_kustomize_objects(k8s_cluster) self.update_remote_objects(k8s_cluster) result = ValidateResult() @@ -181,7 +181,7 @@ def find_delete_objects(self, k8s_cluster): def find_purge_objects(self, k8s_cluster): self.clear_errors_and_warnings() self.render_deployments() - self.build_kustomize_objects() + self.build_kustomize_objects(k8s_cluster) logger.info("Searching objects not found in local objects") labels = self.project.get_delete_by_labels() excluded_objects = list(self.local_objects_by_ref().keys()) @@ -298,7 +298,7 @@ def dummy(x3): def do_deploy(self, k8s_cluster, force_apply, replace_on_error, dry_run, abort_on_error): self.update_remote_objects(k8s_cluster) - replaced_objects = self.find_replaced_objects() + replaced_objects = self.find_replaced_objects(k8s_cluster) if replaced_objects: futures = [] with MyThreadPoolExecutor(max_workers=16) as executor: @@ -332,7 +332,6 @@ def do_diff(self, k8s_cluster, applied_objects, ignore_tags, ignore_labels, igno ignore_for_diffs = d.deployment_project.get_ignore_for_diffs(ignore_tags, ignore_labels, ignore_annotations) for x in d.objects: ref = get_object_ref(x) - ref = remove_namespace_from_ref_if_needed(k8s_cluster, ref) if ref not in applied_objects: continue diff_objects[ref] = applied_objects.get(ref) @@ -374,9 +373,9 @@ def is_replace_needed(self, o): return True return False - def find_replaced_objects(self): + def find_replaced_objects(self, k8s_cluster): self.render_deployments() - self.build_kustomize_objects() + self.build_kustomize_objects(k8s_cluster) ret = [] for d in self.deployments: for o in d.objects: diff --git a/kluctl/deployment/kustomize_deployment.py b/kluctl/deployment/kustomize_deployment.py index a14c6d70d..51857ac60 100644 --- a/kluctl/deployment/kustomize_deployment.py +++ b/kluctl/deployment/kustomize_deployment.py @@ -8,6 +8,7 @@ from kluctl.deployment.helm_chart import HelmChart from kluctl.seal.deployment_sealer import SEALME_EXT from kluctl.utils.external_tools import get_external_tool_hash +from kluctl.utils.k8s_object_utils import should_remove_namespace, get_object_ref from kluctl.utils.kustomize import kustomize_build from kluctl.utils.templated_dir import TemplatedDir from kluctl.utils.utils import get_tmp_base_dir @@ -181,6 +182,8 @@ def check_inclusion_for_delete(self): return inclusion.check_included(values, skip_delete_if_tags) def is_simple_kustomize_project(self, kustomize_yaml): + return False + rendered_dir = self.get_rendered_dir() allowed = {"apiVersion", "kind", "resources", "namespace"} for k in kustomize_yaml.keys(): @@ -235,7 +238,7 @@ def build_objects_simple_kustomize(self, kustomize_yaml): yamls.append(x) return yamls - def build_objects(self): + def build_objects(self, k8s_cluster): if self.config.get("onlyRender", False): self.objects = [] return @@ -270,6 +273,9 @@ def build_objects(self): y["metadata"]["labels"] = labels y["metadata"]["annotations"] = annotations + if should_remove_namespace(k8s_cluster, get_object_ref(y)): + del y["metadata"]["namespace"] + self.objects.append(y) def calc_hash(self): diff --git a/kluctl/utils/k8s_object_utils.py b/kluctl/utils/k8s_object_utils.py index 494100eb4..a30b7bf54 100644 --- a/kluctl/utils/k8s_object_utils.py +++ b/kluctl/utils/k8s_object_utils.py @@ -61,17 +61,25 @@ def remove_api_version_from_ref(ref): g, v = split_api_version(ref.api_version) return ObjectRef(api_version=g, kind=ref.kind, name=ref.name, namespace=ref.namespace) -def remove_namespace_from_ref_if_needed(k8s_cluster, ref): +def should_remove_namespace(k8s_cluster, ref): + if ref.namespace is None: + return False + g, v = split_api_version(ref.api_version) try: resource = k8s_cluster.get_preferred_resource(g, ref.kind) if not resource.namespaced: - return ObjectRef(api_version=ref.api_version, kind=ref.kind, name=ref.name) - return ref + return True + return False except ResourceNotFoundError: # resource might be unknown by now as we might be in the middle of deploying CRDs - return ref + return False + +def remove_namespace_from_ref_if_needed(k8s_cluster, ref): + if should_remove_namespace(k8s_cluster, ref): + return ObjectRef(api_version=ref.api_version, kind=ref.kind, name=ref.name) + return ref def get_filtered_api_resources(k8s_cluster, filter): api_resources = [] From 867e4d076508856a8ce86a732ae8d4ad8e7cbf97 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 26 Aug 2021 07:59:23 +0200 Subject: [PATCH 0038/2916] fix: Remove build_objects_simple_kustomize as it was too buggy --- kluctl/deployment/kustomize_deployment.py | 36 +---------------------- 1 file changed, 1 insertion(+), 35 deletions(-) diff --git a/kluctl/deployment/kustomize_deployment.py b/kluctl/deployment/kustomize_deployment.py index 51857ac60..800f7b95e 100644 --- a/kluctl/deployment/kustomize_deployment.py +++ b/kluctl/deployment/kustomize_deployment.py @@ -181,22 +181,6 @@ def check_inclusion_for_delete(self): values = self.build_inclusion_values() return inclusion.check_included(values, skip_delete_if_tags) - def is_simple_kustomize_project(self, kustomize_yaml): - return False - - rendered_dir = self.get_rendered_dir() - allowed = {"apiVersion", "kind", "resources", "namespace"} - for k in kustomize_yaml.keys(): - if k not in allowed: - return False - for r in kustomize_yaml.get("resources", []): - p = r.split("/") - if "." in p or ".." in p: - return False - if not os.path.isfile(os.path.join(rendered_dir, r)): - return False - return True - def build_objects_kustomize(self): tmp_files = [] @@ -223,21 +207,6 @@ def build_objects_kustomize(self): yamls = yaml_load_all(yamls) return yamls - def build_objects_simple_kustomize(self, kustomize_yaml): - rendered_dir = self.get_rendered_dir() - namespace = kustomize_yaml.get("namespace") - - yamls = [] - for r in kustomize_yaml.get("resources", []): - y = yaml_load_file(os.path.join(rendered_dir, r), all=True) - for x in y: - if x is None: - continue - if namespace is not None: - x.setdefault("metadata", {})["namespace"] = namespace - yamls.append(x) - return yamls - def build_objects(self, k8s_cluster): if self.config.get("onlyRender", False): self.objects = [] @@ -257,10 +226,7 @@ def build_objects(self, k8s_cluster): # Save modified kustomize.yml yaml_save_file(kustomize_yaml, kustomize_yaml_path) - if self.is_simple_kustomize_project(kustomize_yaml): - yamls = self.build_objects_simple_kustomize(kustomize_yaml) - else: - yamls = self.build_objects_kustomize() + yamls = self.build_objects_kustomize() self.objects = [] # Add commonLabels to all resources. We can't use kustomize's "commonLabels" feature as it erroneously From ea9944bc31a116e60ccf4906483de3cf758280e9 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 26 Aug 2021 09:17:00 +0200 Subject: [PATCH 0039/2916] fix: Ensure writes to kustomize cache are thread safe --- kluctl/deployment/kustomize_deployment.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kluctl/deployment/kustomize_deployment.py b/kluctl/deployment/kustomize_deployment.py index 800f7b95e..cedc645e4 100644 --- a/kluctl/deployment/kustomize_deployment.py +++ b/kluctl/deployment/kustomize_deployment.py @@ -198,8 +198,9 @@ def build_objects_kustomize(self): # Run 'kustomize build' yamls = kustomize_build(self.get_rendered_dir()) if allow_cache: - with open(hash_path, "w") as f: + with open(hash_path + "~", "w") as f: f.write(yamls) + os.rename(hash_path + "~", hash_path) finally: for x in tmp_files: x.close() From fd6e5617f90728bd18614d0771ad9af11e661a83 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 26 Aug 2021 11:20:55 +0200 Subject: [PATCH 0040/2916] feat: Implement poke-images command --- docs/commands.md | 28 +++++++ kluctl/cli/command_stubs.py | 14 ++++ kluctl/cli/commands.py | 11 +++ kluctl/deployment/deployment_collection.py | 90 +++++++++++++++++++++- kluctl/utils/k8s_cluster_real.py | 7 +- 5 files changed, 147 insertions(+), 3 deletions(-) diff --git a/docs/commands.md b/docs/commands.md index 48b3359d3..011fadc99 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -292,6 +292,34 @@ In addition, the following arguments are available: ``` +## list-images + +Usage: kluctl poke-images [OPTIONS] + + Replace all images in target. + + This command will fully render the target and then only replace images instead of fully deploying the target. Only + images used in combination with `images.get_image(...)` are replaced. + + + +The following sets of arguments are available: +1. [project arguments](#project-arguments) +1. [image arguments](#image-arguments) +1. [inclusion/exclusion arguments](#inclusionexclusion-arguments) + +In addition, the following arguments are available: + +``` + Misc arguments: + -y, --yes Supresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. + --dry-run Performs all kubernetes API calls in dry-run mode. + -o, --output TEXT Specify output format and target file, in the format 'format=path'. Format can + either be 'diff' or 'yaml'. Can be specified multiple times. The actual format for + yaml is currently not documented and subject to change. +``` + + ## render Usage: kluctl render [OPTIONS] diff --git a/kluctl/cli/command_stubs.py b/kluctl/cli/command_stubs.py index d673e00f9..ad7f93e5b 100644 --- a/kluctl/cli/command_stubs.py +++ b/kluctl/cli/command_stubs.py @@ -75,6 +75,20 @@ def purge_command_stub(obj, **kwargs): from kluctl.cli.commands import purge_command purge_command(obj, kwargs) +@cli_group.command("poke-images", + help="Replace all images in target.\n\n" + "This command will fully render the target and then only replace images instead of fully " + "deploying the target. Only images used in combination with `images.get_image(...)` are " + "replaced.") +@kluctl_project_args() +@image_args() +@include_exclude_args() +@misc_arguments(yes=True, dry_run=True, output_format=True) +@click.pass_obj +def poke_images_command_stub(obj, **kwargs): + from kluctl.cli.commands import poke_images_command + poke_images_command(obj, kwargs) + @cli_group.command("validate", help="Validates the already deployed deployment.\n\n" "This means that all objects are retrieved from the cluster and checked for readiness.\n\n" diff --git a/kluctl/cli/commands.py b/kluctl/cli/commands.py index e96cb90f1..485c08b66 100644 --- a/kluctl/cli/commands.py +++ b/kluctl/cli/commands.py @@ -83,6 +83,17 @@ def purge_command2(obj, kwargs, cmd_ctx): objects = cmd_ctx.deployment_collection.find_purge_objects(cmd_ctx.k8s_cluster) confirmed_delete_objects(cmd_ctx.k8s_cluster, objects, kwargs) +def poke_images_command(obj, kwargs): + with project_command_context(kwargs) as cmd_ctx: + if not kwargs["yes"] and not kwargs["dry_run"]: + click.confirm("Do you really want to poke images to the context/cluster %s?" % cmd_ctx.k8s_cluster.context, + err=True, abort=True) + diff_result = cmd_ctx.deployment_collection.poke_images(cmd_ctx.k8s_cluster) + deleted_objects = cmd_ctx.deployment_collection.find_purge_objects(cmd_ctx.k8s_cluster) + output_diff_result(kwargs["output"], cmd_ctx.deployment_collection, diff_result, deleted_objects) + if diff_result.errors: + sys.exit(1) + def validate_command(obj, kwargs): wait_duration = duration(kwargs["wait"]) if kwargs["wait"] else None sleep_duration = duration(kwargs["sleep"]) if kwargs["sleep"] else None diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index f96bf1145..15add2d7e 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -146,6 +146,91 @@ def diff(self, k8s_cluster, force_apply, replace_on_error, ignore_tags, ignore_l return DeployDiffResult(new_objects=new_objects, changed_objects=changed_objects, errors=list(self.api_errors), warnings=list(self.api_warnings)) + def poke_images(self, k8s_cluster): + self.clear_errors_and_warnings() + self.render_deployments() + self.build_kustomize_objects(k8s_cluster) + self.update_remote_objects(k8s_cluster) + + def do_poke_image(ref, containers_and_images): + o = self.remote_objects.get(ref) + if o is None: + return None + + while True: + o, warnings = k8s_cluster.get_single_object(ref) + if o is None: + return None + + o2 = copy_dict(o) + + containers = get_dict_value(o2, "spec.template.spec.containers", []) + for container_name, image in containers_and_images: + for c in containers: + if c["name"] == container_name: + c["image"] = image + + if o == o2: + return o + + try: + result, warnings = k8s_cluster.replace_object(o2) + return result + except ConflictError: + logger.info("Conflict while poking images in %s. Retrying..." % get_long_object_name(o2)) + continue + + all_objects = {} + for d in self.deployments: + if not d.check_inclusion_for_deploy(): + continue + for o in d.objects: + all_objects[get_object_ref(o)] = o + + containers_and_images = {} + for fi in self.seen_images.seen_images: + deployment_name = fi["deployment"] + if "/" not in deployment_name: + deployment_name = "Deployment/%s" % deployment_name + + kind, name = tuple(deployment_name.split("/", 1)) + try: + r = k8s_cluster.get_preferred_resource(None, kind) + except ResourceNotFoundError as e: + self.add_api_error(None, k8s_cluster.get_status_message(e)) + continue + + ref = ObjectRef(r.group_version, kind=r.kind, name=name, namespace=fi.get("namespace")) + local_object = all_objects.get(ref) + if local_object is None: + continue + + containers_and_images.setdefault(ref, []).append((fi["container"], fi["resultImage"])) + + with MyThreadPoolExecutor(max_workers=8) as executor: + futures = [] + + for ref, c in containers_and_images.items(): + f = executor.submit(do_poke_image, ref, c) + futures.append((ref, f)) + + applied_objects = {} + for ref, f in futures: + e = f.exception() + if e is not None: + self.add_api_error(ref, str(e)) + continue + + o = f.result() + if o is None: + continue + ref = get_object_ref(o) + applied_objects[ref] = o + + new_objects, changed_objects = self.do_diff(k8s_cluster, applied_objects, False, False, False, False) + return DeployDiffResult(new_objects=new_objects, changed_objects=changed_objects, + errors=list(self.api_errors), warnings=list(self.api_warnings)) + def validate(self, k8s_cluster): self.clear_errors_and_warnings() self.render_deployments() @@ -406,7 +491,10 @@ def add_api_warnings(self, ref, warnings): self.api_warnings.add(DeployErrorItem(ref=ref, reason="api", message=w)) def add_api_error(self, ref, error): - logger.error("%s: Error while performing api call. message=%s" % (get_long_object_name_from_ref(ref), error)) + ref_str = "" + if ref is not None: + ref_str = "%s: " % get_long_object_name_from_ref(ref) + logger.error("%sError while performing api call. message=%s" % (ref_str, error)) self.api_errors.add(DeployErrorItem(ref=ref, reason="api", message=error)) def clear_errors_and_warnings(self): diff --git a/kluctl/utils/k8s_cluster_real.py b/kluctl/utils/k8s_cluster_real.py index a8ccfdb11..1367cd93a 100644 --- a/kluctl/utils/k8s_cluster_real.py +++ b/kluctl/utils/k8s_cluster_real.py @@ -135,8 +135,11 @@ def cmp_resource(a, b): def get_preferred_resource(self, group, kind): resources = self.get_preferred_resources(group, None, kind) for r in resources: - if (r.group or None) == group and r.kind == kind: - return r + if group is not None and group != (r.group or None): + continue + if kind is not None and kind != (r.kind or None): + continue + return r raise ResourceNotFoundError(f"{group}/{kind} not found") def _get_objects_for_resource(self, resource, name, namespace, labels, as_table): From e2bb6d6c69b24b20b5c8f546b31a75c7c97766c5 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 26 Aug 2021 13:31:39 +0200 Subject: [PATCH 0041/2916] refactor: Move directory hashing into helper method --- kluctl/deployment/kustomize_deployment.py | 17 ++--------------- kluctl/utils/utils.py | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/kluctl/deployment/kustomize_deployment.py b/kluctl/deployment/kustomize_deployment.py index cedc645e4..bb5e887cc 100644 --- a/kluctl/deployment/kustomize_deployment.py +++ b/kluctl/deployment/kustomize_deployment.py @@ -11,7 +11,7 @@ from kluctl.utils.k8s_object_utils import should_remove_namespace, get_object_ref from kluctl.utils.kustomize import kustomize_build from kluctl.utils.templated_dir import TemplatedDir -from kluctl.utils.utils import get_tmp_base_dir +from kluctl.utils.utils import get_tmp_base_dir, calc_dir_hash from kluctl.utils.versions import LooseSemVerLatestVersion, PrefixLatestVersion, NumberLatestVersion, \ RegexLatestVersion from kluctl.utils.yaml_utils import yaml_load_file, yaml_load_all, yaml_save_file @@ -247,19 +247,6 @@ def build_objects(self, k8s_cluster): def calc_hash(self): h = hashlib.sha256() - rendered_dir = self.get_rendered_dir() - h.update(get_external_tool_hash("kustomize").encode("utf-8")) - - for dirpath, dirnames, filenames in os.walk(rendered_dir, topdown=True): - dirnames.sort() # ensure iteration order is stable (thanks to topdown, we can do this here at this time) - filenames.sort() - rel_dirpath = os.path.relpath(dirpath, rendered_dir) - h.update(rel_dirpath.encode("utf-8")) - for f in filenames: - h.update(os.path.join(rel_dirpath, f).encode("utf-8")) - with open(os.path.join(dirpath, f), mode="rb") as file: - buf = file.read() - h.update(buf) - + h.update(calc_dir_hash(self.get_rendered_dir()).encode("utf-8")) return h.hexdigest() diff --git a/kluctl/utils/utils.py b/kluctl/utils/utils.py index 955d6b459..9f313cbee 100644 --- a/kluctl/utils/utils.py +++ b/kluctl/utils/utils.py @@ -1,3 +1,4 @@ +import hashlib import logging import os import shutil @@ -47,6 +48,22 @@ def duration(duration_string): # example: '5d3h2m1s' prev_num.append(character) return timedelta(seconds=float(total_seconds)) +def calc_dir_hash(dir): + h = hashlib.sha256() + + for dirpath, dirnames, filenames in os.walk(dir, topdown=True): + dirnames.sort() # ensure iteration order is stable (thanks to topdown, we can do this here at this time) + filenames.sort() + rel_dirpath = os.path.relpath(dirpath, dir) + h.update(rel_dirpath.encode("utf-8")) + for f in filenames: + h.update(os.path.join(rel_dirpath, f).encode("utf-8")) + with open(os.path.join(dirpath, f), mode="rb") as file: + buf = file.read() + h.update(buf) + + return h.hexdigest() + class DummyExecutor: def __init__(self, *args, **kwargs): pass From 0ff9a67f6517ed4824f5c66222a148354d44404c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 26 Aug 2021 14:20:48 +0200 Subject: [PATCH 0042/2916] feat: Allow to write rendered deployments into existing directory --- docs/commands.md | 8 ++++++-- kluctl/cli/command_stubs.py | 8 ++------ kluctl/cli/commands.py | 9 ++++----- kluctl/cli/main_cli_group.py | 9 ++++++++- kluctl/cli/utils.py | 11 +++++------ 5 files changed, 25 insertions(+), 20 deletions(-) diff --git a/docs/commands.md b/docs/commands.md index 011fadc99..6edf018a9 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -134,6 +134,9 @@ In addition, the following arguments are available: -o, --output TEXT Specify output format and target file, in the format 'format=path'. Format can either be 'diff' or 'yaml'. Can be specified multiple times. The actual format for yaml is currently not documented and subject to change. + --render-output-dir DIRECTORY + Specifies the target directory to render the project into. If omitted, atemporary + directory is used. ``` @@ -337,8 +340,9 @@ In addition, the following arguments are available: ``` Misc arguments: - --output-dir DIRECTORY Specified the target directory to render the project into. If omitted, a random - temporary directory is created and printed to stdout. + --render-output-dir DIRECTORY + Specifies the target directory to render the project into. If omitted, atemporary + directory is used. --output-images FILE Also output images list to given FILE. This output the same result as from the list- images command. --offline Go offline, meaning that kubernetes and registries are not asked for image versions diff --git a/kluctl/cli/command_stubs.py b/kluctl/cli/command_stubs.py index ad7f93e5b..8118dab5e 100644 --- a/kluctl/cli/command_stubs.py +++ b/kluctl/cli/command_stubs.py @@ -23,7 +23,7 @@ def bootstrap_command_stub(obj, **kwargs): @kluctl_project_args() @image_args() @include_exclude_args() -@misc_arguments(yes=True, dry_run=True, force_apply=True, replace_on_error=True, abort_on_error=True, output_format=True) +@misc_arguments(yes=True, dry_run=True, force_apply=True, replace_on_error=True, abort_on_error=True, output_format=True, render_output_dir=True) @click.pass_obj def deploy_command_stub(obj, **kwargs): from kluctl.cli.commands import deploy_command @@ -109,11 +109,7 @@ def validate_command_stub(obj, **kwargs): "a temporary directory or a specified directory.") @kluctl_project_args() @image_args() -@optgroup.group("Misc arguments") -@optgroup.option("--output-dir", - help="Specified the target directory to render the project into. If omitted, a random temporary " - "directory is created and printed to stdout.", - type=click.Path(file_okay=False)) +@misc_arguments(render_output_dir=True) @optgroup.option("--output-images", help="Also output images list to given FILE. This output the same result as from the " "list-images command.", diff --git a/kluctl/cli/commands.py b/kluctl/cli/commands.py index 485c08b66..3dc8a4f7e 100644 --- a/kluctl/cli/commands.py +++ b/kluctl/cli/commands.py @@ -123,13 +123,12 @@ def validate_command(obj, kwargs): def render_command(obj, kwargs): logger = logging.getLogger('build') - output_dir = kwargs["output_dir"] - if output_dir is None: - output_dir = tempfile.mkdtemp(dir=get_tmp_base_dir(), prefix="render-") + if kwargs["render_output_dir"] is None: + kwargs["render_output_dir"] = tempfile.mkdtemp(dir=get_tmp_base_dir(), prefix="render-") - with project_command_context(kwargs, output_dir=output_dir) as cmd_ctx: + with project_command_context(kwargs) as cmd_ctx: cmd_ctx.deployment_collection.render_deployments() - logger.info('Rendered into: %s' % output_dir) + logger.info('Rendered into: %s' % cmd_ctx.deployment_collection.tmpdir) if kwargs["output_images"]: result = { diff --git a/kluctl/cli/main_cli_group.py b/kluctl/cli/main_cli_group.py index 935f8bd7d..fd24796f5 100644 --- a/kluctl/cli/main_cli_group.py +++ b/kluctl/cli/main_cli_group.py @@ -120,7 +120,8 @@ def wrapper(func): return wrapper def misc_arguments(yes=False, dry_run=False, force_apply=False, replace_on_error=False, - ignore_labels=False, ignore_order=False, abort_on_error=False, output_format=False, output=False): + ignore_labels=False, ignore_order=False, abort_on_error=False, output_format=False, output=False, + render_output_dir=False): options = [] options.append(optgroup.group("Misc arguments")) @@ -172,6 +173,12 @@ def misc_arguments(yes=False, dry_run=False, force_apply=False, replace_on_error options.append(optgroup.option("-o", "--output", help="Specify output target file. Can be specified multiple times", multiple=True)) + if render_output_dir: + options.append(optgroup.option("--render-output-dir", + help="Specifies the target directory to render the project into. If omitted, a" + "temporary directory is used.", + type=click.Path(file_okay=False))) + return wrapper_helper(options) def kluctl_project_args(with_d=True, with_a=True, with_t=True): diff --git a/kluctl/cli/utils.py b/kluctl/cli/utils.py index ea49f291e..265222ff6 100644 --- a/kluctl/cli/utils.py +++ b/kluctl/cli/utils.py @@ -99,7 +99,6 @@ class CommandContext: @contextlib.contextmanager def project_command_context(kwargs, - output_dir=None, force_offline_images=False, force_offline_kubernetes=False) -> ContextManager[CommandContext]: with load_kluctl_project_from_args(kwargs) as kluctl_project: @@ -107,14 +106,13 @@ def project_command_context(kwargs, if kwargs["target"]: target = kluctl_project.find_target(kwargs["target"]) - with project_target_command_context(kwargs, kluctl_project, target, output_dir=output_dir, + with project_target_command_context(kwargs, kluctl_project, target, force_offline_images=force_offline_images, force_offline_kubernetes=force_offline_kubernetes) as cmd_ctx: yield cmd_ctx @contextlib.contextmanager def project_target_command_context(kwargs, kluctl_project, target, - output_dir=None, force_offline_images=False, force_offline_kubernetes=False, for_seal=False) -> ContextManager[CommandContext]: @@ -141,10 +139,11 @@ def project_target_command_context(kwargs, kluctl_project, target, merge_dict(deploy_args, seal_args, False) with tempfile.TemporaryDirectory(dir=get_tmp_base_dir()) as tmpdir: - if output_dir is None: - output_dir = tmpdir + render_output_dir = kwargs.get("render_output_dir") + if render_output_dir is None: + render_output_dir = tmpdir d = DeploymentProject(kluctl_project.deployment_dir, jinja_vars, deploy_args, kluctl_project.sealed_secrets_dir) - c = DeploymentCollection(d, images=images, inclusion=inclusion, tmpdir=output_dir, for_seal=for_seal) + c = DeploymentCollection(d, images=images, inclusion=inclusion, tmpdir=render_output_dir, for_seal=for_seal) fixed_images = load_fixed_images(kwargs) for fi in target.get("images", []): From 48f9a08dc2b504cf0ebe0430d26665634ad8a9fa Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 26 Aug 2021 14:46:10 +0200 Subject: [PATCH 0043/2916] fix: Fix dry-run deploy with replaced objects --- kluctl/deployment/deployment_collection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index 15add2d7e..f4b21a3bf 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -332,7 +332,7 @@ def finish_jobs(): else: x2 = x - if dry_run and is_replaced: + if (dry_run or k8s_cluster.dry_run) and is_replaced: # The necessary delete was not really performed in case we are in dry_run mode def dummy(x3): # let's pretend that we applied it From fbec2a29741f7c88b54d58c440fde5632c48dbcc Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 27 Aug 2021 09:50:30 +0200 Subject: [PATCH 0044/2916] fix: Also add --render-output to validate/diff --- docs/commands.md | 6 ++++++ kluctl/cli/command_stubs.py | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/commands.md b/docs/commands.md index 6edf018a9..9f94555af 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -202,6 +202,9 @@ In addition, the following arguments are available: -o, --output TEXT Specify output format and target file, in the format 'format=path'. Format can either be 'diff' or 'yaml'. Can be specified multiple times. The actual format for yaml is currently not documented and subject to change. + --render-output-dir DIRECTORY + Specifies the target directory to render the project into. If omitted, atemporary + directory is used. ``` @@ -370,6 +373,9 @@ In addition, the following arguments are available: ``` Misc arguments: -o, --output TEXT Specify output target file. Can be specified multiple times + --render-output-dir DIRECTORY + Specifies the target directory to render the project into. If omitted, atemporary + directory is used. --wait TEXT Wait for the given amount of time until the deployment validates --sleep TEXT Sleep duration between validation attempts --warnings-as-errors Consider warnings as failures diff --git a/kluctl/cli/command_stubs.py b/kluctl/cli/command_stubs.py index 8118dab5e..058a497b6 100644 --- a/kluctl/cli/command_stubs.py +++ b/kluctl/cli/command_stubs.py @@ -38,7 +38,7 @@ def deploy_command_stub(obj, **kwargs): @kluctl_project_args() @image_args() @include_exclude_args() -@misc_arguments(force_apply=True, replace_on_error=True, ignore_labels=True, ignore_order=True, output_format=True) +@misc_arguments(force_apply=True, replace_on_error=True, ignore_labels=True, ignore_order=True, output_format=True, render_output_dir=True) @click.pass_obj def diff_command_stub(obj, **kwargs): from kluctl.cli.commands import diff_command @@ -95,7 +95,7 @@ def poke_images_command_stub(obj, **kwargs): "TODO: This needs to be better documented!") @kluctl_project_args() @include_exclude_args() -@misc_arguments(output=True) +@misc_arguments(output=True, render_output_dir=True) @optgroup.option("--wait", help="Wait for the given amount of time until the deployment validates") @optgroup.option("--sleep", help="Sleep duration between validation attempts", default="5s") @optgroup.option("--warnings-as-errors", help="Consider warnings as failures", is_flag=True) From ba1d64578cb4b5d1c8acf91b4d3bce246d769888 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 27 Aug 2021 09:50:47 +0200 Subject: [PATCH 0045/2916] fix: Fix crash in get_status_message in case the body is not json --- kluctl/utils/k8s_cluster_real.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/kluctl/utils/k8s_cluster_real.py b/kluctl/utils/k8s_cluster_real.py index 1367cd93a..bafbb3094 100644 --- a/kluctl/utils/k8s_cluster_real.py +++ b/kluctl/utils/k8s_cluster_real.py @@ -85,7 +85,10 @@ def _build_label_selector(self, labels): def get_status_message(self, status): if isinstance(status, ApiException): - status = json.loads(status.body) + if status.headers.get("Content-Type") == "application/json": + status = json.loads(status.body) + else: + return status.body.decode("utf-8") if isinstance(status, Exception): return 'Exception: %s' % str(status) From 127e1c95ac6d6113ddc6be83986bfe18278cb1b8 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 27 Aug 2021 09:51:03 +0200 Subject: [PATCH 0046/2916] fix: Let validate also resolve images --- kluctl/cli/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kluctl/cli/commands.py b/kluctl/cli/commands.py index 3dc8a4f7e..de72fba99 100644 --- a/kluctl/cli/commands.py +++ b/kluctl/cli/commands.py @@ -101,7 +101,7 @@ def validate_command(obj, kwargs): start_time = time.time() - with project_command_context(kwargs, force_offline_images=True) as cmd_ctx: + with project_command_context(kwargs) as cmd_ctx: while True: result = cmd_ctx.deployment_collection.validate(cmd_ctx.k8s_cluster) failed = len(result.errors) != 0 or (warnings_as_errors and len(result.warnings) != 0) From a67d50a33be01d9e38965ca7185657f6e9c04ed2 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 27 Aug 2021 09:51:24 +0200 Subject: [PATCH 0047/2916] fix: Fix crash when no target is specified/available --- kluctl/cli/utils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/kluctl/cli/utils.py b/kluctl/cli/utils.py index 265222ff6..295b10b5b 100644 --- a/kluctl/cli/utils.py +++ b/kluctl/cli/utils.py @@ -146,8 +146,9 @@ def project_target_command_context(kwargs, kluctl_project, target, c = DeploymentCollection(d, images=images, inclusion=inclusion, tmpdir=render_output_dir, for_seal=for_seal) fixed_images = load_fixed_images(kwargs) - for fi in target.get("images", []): - c.seen_images.add_fixed_image(fi) + if target is not None: + for fi in target.get("images", []): + c.seen_images.add_fixed_image(fi) for fi in fixed_images: c.seen_images.add_fixed_image(fi) From 4acd7fccd459c2da8c501a117164edaf4b17c36c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 27 Aug 2021 09:51:53 +0200 Subject: [PATCH 0048/2916] fix: Modify kustomization.yml as part of rendering --- kluctl/deployment/deployment_collection.py | 1 + kluctl/deployment/kustomize_deployment.py | 16 +++++++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index f4b21a3bf..f5b3e4e3c 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -90,6 +90,7 @@ def render_deployments(self): if not self.for_seal: for d in self.deployments: + d.prepare_kustomization_yaml() d.resolve_sealed_secrets() self.is_rendered = True diff --git a/kluctl/deployment/kustomize_deployment.py b/kluctl/deployment/kustomize_deployment.py index bb5e887cc..147b41117 100644 --- a/kluctl/deployment/kustomize_deployment.py +++ b/kluctl/deployment/kustomize_deployment.py @@ -208,16 +208,14 @@ def build_objects_kustomize(self): yamls = yaml_load_all(yamls) return yamls - def build_objects(self, k8s_cluster): - if self.config.get("onlyRender", False): - self.objects = [] - return + def prepare_kustomization_yaml(self): if "path" not in self.config: - self.objects = [] return rendered_dir = self.get_rendered_dir() kustomize_yaml_path = os.path.join(rendered_dir, 'kustomization.yml') + if not os.path.exists(kustomize_yaml_path): + return kustomize_yaml = yaml_load_file(kustomize_yaml_path) override_namespace = self.deployment_project.get_override_namespace() @@ -227,6 +225,14 @@ def build_objects(self, k8s_cluster): # Save modified kustomize.yml yaml_save_file(kustomize_yaml, kustomize_yaml_path) + def build_objects(self, k8s_cluster): + if self.config.get("onlyRender", False): + self.objects = [] + return + if "path" not in self.config: + self.objects = [] + return + yamls = self.build_objects_kustomize() self.objects = [] From a99e8b0ca4146e944c6395a3f63f3f5afbae4ed9 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 27 Aug 2021 10:18:25 +0200 Subject: [PATCH 0049/2916] fix: Only import fcntl on linux/mac --- kluctl/utils/run_helper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kluctl/utils/run_helper.py b/kluctl/utils/run_helper.py index 6a5384e91..fb32cc46f 100644 --- a/kluctl/utils/run_helper.py +++ b/kluctl/utils/run_helper.py @@ -1,4 +1,3 @@ -import fcntl import io import os import subprocess @@ -76,6 +75,7 @@ def run(self) -> None: def set_non_blocking(f): if sys.platform == "linux" or sys.platform == "darwin": + import fcntl fd = f.fileno() fl = fcntl.fcntl(fd, fcntl.F_GETFL) fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK) From 296e616e137f2af8636978f02061d8037ee92705 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 27 Aug 2021 12:13:59 +0200 Subject: [PATCH 0050/2916] refactor: Move remove_empty/is_empty into dict_utils --- kluctl/diff/k8s_diff.py | 23 +---------------------- kluctl/utils/dict_utils.py | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/kluctl/diff/k8s_diff.py b/kluctl/diff/k8s_diff.py index 97f2e6ab2..752e92420 100644 --- a/kluctl/diff/k8s_diff.py +++ b/kluctl/diff/k8s_diff.py @@ -6,31 +6,10 @@ from deepdiff.helper import NotPresent from kluctl.utils.k8s_object_utils import get_object_ref -from kluctl.utils.dict_utils import copy_dict +from kluctl.utils.dict_utils import copy_dict, remove_empty from kluctl.utils.yaml_utils import yaml_dump -def is_empty(o): - if isinstance(o, dict) or isinstance(o, list): - return len(o) == 0 - return False - -def remove_empty(o): - if isinstance(o, dict): - for k in list(o.keys()): - remove_empty(o[k]) - if is_empty(o[k]): - del(o[k]) - elif isinstance(o, list): - i = 0 - while i < len(o): - v = o[i] - remove_empty(v) - if is_empty(v): - o.pop(i) - else: - i += 1 - def object_to_diffable_lines(o): if o is None: return ['null'] diff --git a/kluctl/utils/dict_utils.py b/kluctl/utils/dict_utils.py index 302840e27..1e0eb1d00 100644 --- a/kluctl/utils/dict_utils.py +++ b/kluctl/utils/dict_utils.py @@ -62,3 +62,24 @@ def get_dict_value(y, path, default=None): return default y = y[x] return y + +def is_empty(o): + if isinstance(o, dict) or isinstance(o, list): + return len(o) == 0 + return False + +def remove_empty(o): + if isinstance(o, dict): + for k in list(o.keys()): + remove_empty(o[k]) + if is_empty(o[k]): + del(o[k]) + elif isinstance(o, list): + i = 0 + while i < len(o): + v = o[i] + remove_empty(v) + if is_empty(v): + o.pop(i) + else: + i += 1 From 8182a6720fce980dc16ddef91157228f9a4a2592 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 27 Aug 2021 13:45:18 +0200 Subject: [PATCH 0051/2916] fix: Don't manually verify cluster context existence Rely on load_kube_config to raise an exception --- kluctl/utils/k8s_cluster_base.py | 6 ------ kluctl/utils/k8s_cluster_real.py | 6 +++++- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/kluctl/utils/k8s_cluster_base.py b/kluctl/utils/k8s_cluster_base.py index 2dfd63966..9f4d20c15 100644 --- a/kluctl/utils/k8s_cluster_base.py +++ b/kluctl/utils/k8s_cluster_base.py @@ -2,7 +2,6 @@ import logging import os -from kubernetes import config from kubernetes.client import ApiException from kluctl.utils.exceptions import CommandError @@ -59,11 +58,6 @@ def load_cluster_config(cluster_dir, cluster_name, offline=False, dry_run=True): if offline: return cluster, None - contexts, _ = config.list_kube_config_contexts() - - if not any(x['name'] == cluster['context'] for x in contexts): - raise CommandError('Context %s for cluster %s not found in kubeconfig' % (cluster['context'], cluster_name)) - if cluster['name'] != cluster_name: raise CommandError('Cluster name in %s does not match requested cluster name %s' % (cluster['name'], cluster_name)) diff --git a/kluctl/utils/k8s_cluster_real.py b/kluctl/utils/k8s_cluster_real.py index bafbb3094..5feec32d8 100644 --- a/kluctl/utils/k8s_cluster_real.py +++ b/kluctl/utils/k8s_cluster_real.py @@ -7,6 +7,7 @@ from kubernetes import config from kubernetes.client import ApiClient, Configuration, ApiException +from kubernetes.config import ConfigException from kubernetes.dynamic import EagerDiscoverer, DynamicClient, Resource from kubernetes.dynamic.exceptions import NotFoundError, ResourceNotFoundError @@ -50,7 +51,10 @@ def __init__(self, context, dry_run): self.dry_run = dry_run self.config = Configuration() - config.load_kube_config(context=context, client_configuration=self.config, persist_config=True) + try: + config.load_kube_config(context=context, client_configuration=self.config, persist_config=True) + except ConfigException as e: + raise CommandError(str(e)) if self.context != "docker-desktop" and not self.config.api_key and not self.config.key_file: raise CommandError("No authentication available. You might need to invoke kubectl first to perform a login") From f9abdd881cd6ce936d4c18c2cff5de774e5dc442 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 27 Aug 2021 16:20:17 +0200 Subject: [PATCH 0052/2916] fix: Remove gitlab_fast_ls_remote optimization Turned out that gitlab api lags behind the actual repo, meaning that the speed improvement is worthless. --- kluctl/utils/git_utils.py | 11 ----- kluctl/utils/gitlab/fast_ls_remote.py | 67 --------------------------- 2 files changed, 78 deletions(-) delete mode 100644 kluctl/utils/gitlab/fast_ls_remote.py diff --git a/kluctl/utils/git_utils.py b/kluctl/utils/git_utils.py index 1e44953c3..9e90c5484 100644 --- a/kluctl/utils/git_utils.py +++ b/kluctl/utils/git_utils.py @@ -12,7 +12,6 @@ from git import Git from kluctl.utils.env_config_sets import parse_env_config_sets -from kluctl.utils.gitlab.fast_ls_remote import gitlab_fast_ls_remote from kluctl.utils.utils import get_tmp_base_dir logger = logging.getLogger(__name__) @@ -145,12 +144,6 @@ def with_git_cache(url, do_lock=True): def update_git_cache(url, do_lock=True): with with_git_cache(url, do_lock=do_lock) as cache_dir: with build_git_object(url, cache_dir) as (g, url): - remote_refs = gitlab_fast_ls_remote(url) - if remote_refs is not None: - local_refs = git_ls_remote(cache_dir) - if remote_refs == local_refs: - return - logger.info(f"Fetching into cache: url='{url}'") g.fetch("origin", "-f") @@ -179,10 +172,6 @@ def get_git_ref(path): return tag def git_ls_remote(url, tags=False): - ret = gitlab_fast_ls_remote(url, tags=tags) - if ret is not None: - return ret - args = [] if tags: args.append("--tags") diff --git a/kluctl/utils/gitlab/fast_ls_remote.py b/kluctl/utils/gitlab/fast_ls_remote.py deleted file mode 100644 index bf7007159..000000000 --- a/kluctl/utils/gitlab/fast_ls_remote.py +++ /dev/null @@ -1,67 +0,0 @@ -import hashlib -import logging -import os -from datetime import datetime, timedelta - -from kluctl.utils.gitlab.gitlab_util import extract_gitlab_group_and_project, build_gitlab_project_id, get_gitlab_api, \ - is_gitlab_project -from kluctl.utils.utils import get_tmp_base_dir - -logger = logging.getLogger(__name__) - -def _is_backlisted(url): - hash = hashlib.sha256(url.encode("utf-8")).hexdigest() - path = os.path.join(get_tmp_base_dir(), "gitlab-blacklist", hash) - try: - with open(path) as f: - s = f.read() - t = datetime.fromisoformat(s) - return t >= datetime.utcnow() - timedelta(minutes=5) - except: - return False - -def _set_blacklisted(url): - hash = hashlib.sha256(url.encode("utf-8")).hexdigest() - path = os.path.join(get_tmp_base_dir(), "gitlab-blacklist") - os.makedirs(path, exist_ok=True) - path = os.path.join(path, hash) - try: - with open(path, mode="wt") as f: - f.write(str(datetime.utcnow())) - except: - pass - -def gitlab_fast_ls_remote(url, tags=False): - if not is_gitlab_project(url): - return None - - if _is_backlisted(url): - return None - - try: - return _gitlab_fast_ls_remote(url, tags) - except Exception as e: - logger.warning("Exception while trying fast_get_git_refs_gitlab", exc_info=e) - _set_blacklisted(url) - return None - -def _gitlab_fast_ls_remote(url, tags): - gl = get_gitlab_api(require_auth=True) - if gl is None: - return None - - result = {} - group, project = extract_gitlab_group_and_project(url) - base_path = "/projects/%s/repository" % build_gitlab_project_id(group, project) - path = "%s/branches" % base_path - r = gl.http_get(path) - for branch in r: - if branch.get("default"): - result["HEAD"] = branch["commit"]["id"] - result["refs/heads/%s" % branch["name"]] = branch["commit"]["id"] - if tags: - path = "%s/tags" % base_path - r = gl.http_get(path) - for tag in r: - result["refs/tags/%s" % tag["name"]] = tag["commit"]["id"] - return result From bd499387af07d805b46687b5d6a9a2edeba778e7 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 27 Aug 2021 17:37:24 +0200 Subject: [PATCH 0053/2916] fix: Don't swallow git exceptions when updating caches in parallel --- kluctl/kluctl_project/kluctl_project.py | 9 +++++++-- kluctl/utils/git_utils.py | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/kluctl/kluctl_project/kluctl_project.py b/kluctl/kluctl_project/kluctl_project.py index c2ad5ab8e..0ae05702f 100644 --- a/kluctl/kluctl_project/kluctl_project.py +++ b/kluctl/kluctl_project/kluctl_project.py @@ -229,6 +229,7 @@ def do_clone(key, default_git_subdir, local_dir): def update_git_caches(self): with MyThreadPoolExecutor() as executor: + futures = [] def do_update(key): if key not in self.config: return @@ -241,7 +242,8 @@ def do_update(key): if url in self.git_cache_up_to_date: return self.git_cache_up_to_date[url] = True - executor.submit(update_git_cache, url) + f = executor.submit(update_git_cache, url) + futures.append(f) do_update("deployment") do_update("clusters") @@ -255,10 +257,13 @@ def do_update(key): url = parse_git_project(target_config["project"], None).url if url not in self.git_cache_up_to_date: self.git_cache_up_to_date[url] = True - executor.submit(update_git_cache, url) + f = executor.submit(update_git_cache, url) + futures.append(f) if url not in self.refs_for_urls: self.refs_for_urls[url] = executor.submit(git_ls_remote, url) + for f in futures: + f.result() for url in list(self.refs_for_urls.keys()): self.refs_for_urls[url] = self.refs_for_urls[url].result() diff --git a/kluctl/utils/git_utils.py b/kluctl/utils/git_utils.py index 9e90c5484..209688717 100644 --- a/kluctl/utils/git_utils.py +++ b/kluctl/utils/git_utils.py @@ -28,6 +28,7 @@ def get_cache_base_dir(): dir = os.path.join(get_tmp_base_dir(), "git-cache") + logger.debug("cache base dir: %s" % dir) return dir class GitCredentialsStore: From 8e979d9b7c51d7bfe7c02f3d3f98a2854b77fe5a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 30 Aug 2021 17:17:27 +0200 Subject: [PATCH 0054/2916] fix: Remove unused code --- kluctl/utils/git_utils.py | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/kluctl/utils/git_utils.py b/kluctl/utils/git_utils.py index 209688717..0b8d4bfec 100644 --- a/kluctl/utils/git_utils.py +++ b/kluctl/utils/git_utils.py @@ -239,26 +239,3 @@ def check_git_url_match(a, b, allow_gitlab_fallback): a = parse_git_url(a, allow_gitlab_fallback) b = parse_git_url(b, allow_gitlab_fallback) return fnmatch.fnmatch(a.host, b.host) and fnmatch.fnmatch(a.path, b.path) - -def guess_git_web_url(git_url): - a = parse_git_url(git_url) - if a.host.find("gitlab") != -1: - return f"https://{a.host}/{a.path}" - if a.host.find("bitbucket") != -1: - if a.schema in ("http", "https"): - s = a.path.split("/") - if len(s) < 3: - return git_url - - group = s[1] - project = s[2] - return f"{a.schema}://{a.host}/scm/{group}/{project}.git" - elif a.schema == "ssh": - s = a.path.split("/") - if len(s) < 2: - return git_url - group = s[0] - project = s[1] - host = a.host.split(":", 1)[0] - return f"https://{host}/scm/{group}/{project}" - return git_url From 4dd553121521258dd8e114de01706df9db1f4855 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 30 Aug 2021 17:19:33 +0200 Subject: [PATCH 0055/2916] fix: Remove gitlab fallback and properly support port numbers in parse_git_url --- kluctl/utils/git_utils.py | 72 +++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 33 deletions(-) diff --git a/kluctl/utils/git_utils.py b/kluctl/utils/git_utils.py index 0b8d4bfec..7893faae3 100644 --- a/kluctl/utils/git_utils.py +++ b/kluctl/utils/git_utils.py @@ -197,45 +197,51 @@ def filter_remote_refs(refs, pattern, trim): matching_refs[r2] = commit return matching_refs -@dataclasses.dataclass +@dataclasses.dataclass(eq=True) class GitUrl: schema: str host: str + port: int path: str + username: str -def parse_git_url(p, allow_gitlab_fallback=False): - dummy = "wildcard" * 10 - replaced = p.replace("*", dummy) - def replace_back(s): - return s.replace(dummy, "*") +def parse_git_url(p): def trim_git_suffix(s): if s.endswith(".git"): return s[:-len(".git")] return s - def trim_username(host): - at_idx = host.find('@') - if at_idx != -1: - host = host[at_idx + 1:] - return host - - if replaced.startswith("http://") or replaced.startswith("https://") or replaced.startswith("ssh://"): - schema = replaced.split(":", 1)[0] - url = urlparse(replaced) - host = url.hostname - path = url.path[1:] # trim / - elif ":" in replaced: - schema = "ssh" - s = replaced.split(":", 1) - host = trim_username(s[0]) - path = s[1] - elif allow_gitlab_fallback: - return parse_git_url("https://gitlab.com/%s" % p, False) - else: - raise Exception("Invalid git url: %s" % p) - path = trim_git_suffix(path) - return GitUrl(schema, replace_back(host), replace_back(path)) - -def check_git_url_match(a, b, allow_gitlab_fallback): - a = parse_git_url(a, allow_gitlab_fallback) - b = parse_git_url(b, allow_gitlab_fallback) - return fnmatch.fnmatch(a.host, b.host) and fnmatch.fnmatch(a.path, b.path) + def normalize_port(schema, port): + if port is not None: + return port + if schema == "http": + return 80 + if schema == "https": + return 443 + if schema == "ssh": + return 22 + raise Exception("Unknown schema %s" % schema) + + schema_pattern = re.compile("^([a-z]*)://.*") + m = schema_pattern.match(p) + if m: + url = urlparse(p) + path = trim_git_suffix(url.path) + port = normalize_port(url.scheme, url.port) + return GitUrl(url.scheme, url.hostname, port, path, url.username) + + pattern = re.compile("(.+@)?([\w\d\.]+):(,*)") + m = pattern.match(p) + if not m: + raise Exception("Invalid git url %s" % p) + + username = m.group(1) + if username is not None: + username = username[:-1] + host = m.group(2) + path = m.group(3) + return GitUrl("ssh", host, 22, path, username) + +def check_git_url_match(a, b): + a = parse_git_url(a) + b = parse_git_url(b) + return a == b From 0c8a58d3210f0ceaa9a20db83fdd5c3ca3092b1f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 30 Aug 2021 17:20:18 +0200 Subject: [PATCH 0056/2916] feat: Support ssh keys via GitCredentialsStore and environment variables --- kluctl/utils/git_utils.py | 134 +++++++++++++++++++++++++------------- 1 file changed, 87 insertions(+), 47 deletions(-) diff --git a/kluctl/utils/git_utils.py b/kluctl/utils/git_utils.py index 7893faae3..9cf5d9166 100644 --- a/kluctl/utils/git_utils.py +++ b/kluctl/utils/git_utils.py @@ -31,16 +31,48 @@ def get_cache_base_dir(): logger.debug("cache base dir: %s" % dir) return dir +@dataclasses.dataclass() +class GitCredentials: + host: str + username: str + password: str + ssh_key: str + path_prefix: str + class GitCredentialsStore: - def get_credentials(self, host): - return None, None + def get_credentials(self, host, path): + return None, None, None + + def check_credentials(self, c, test_host, test_path): + if c.host != test_host: + return False + if not test_path.startswith(c.path_prefix or ""): + return False + return True + + def find_matching_credentials(self, credentials, test_host, test_path): + c = [x for x in credentials if self.check_credentials(x, test_host, test_path)] + if not c: + return None + c.sort(key=lambda x: len(x.path_prefix)) + # return credentials with longest matching path + return c[-1] class GitCredentialStoreEnv(GitCredentialsStore): - def get_credentials(self, host): + def get_credentials(self, host, path): + credentials = [] for idx, s in parse_env_config_sets("KLUCTL_GIT").items(): - if s.get("HOST") == host: - return s.get("USERNAME"), s.get("PASSWORD") - return None, None + credentials.append(GitCredentials(host=s.get("HOST"), + username=s.get("USERNAME"), + password=s.get("PASSWORD"), + ssh_key=s.get("SSH_KEY"), + path_prefix=s.get("PATH_PREFIX", ""))) + c = self.find_matching_credentials(credentials, host, path) + if c is not None and c.ssh_key is not None: + path = os.path.expanduser(c.ssh_key) + with open(path, "rt") as f: + c.ssh_key = f.read() + return c credentials_store = GitCredentialStoreEnv() @@ -62,57 +94,65 @@ def add_username_to_url(url, username): if username is None: return url u = parse_git_url(url) - return f"{u.schema}://{username}@{u.host}/{u.path}" + return f"{u.schema}://{username}@{u.host}:{u.port}{u.path}" @contextmanager -def build_git_object(url, working_dir): - username = None - password = None +def create_password_files(g, ssh_command, url, credentials): + # Must handle closing/deletion manually as otherwise git will complain about busy files + password_script = NamedTemporaryFile("w+t", dir=get_tmp_base_dir(), delete=False) + password_file = NamedTemporaryFile("w+t", dir=get_tmp_base_dir(), delete=False) + ssh_key_file = NamedTemporaryFile("w+t", dir=get_tmp_base_dir(), delete=False) + + if credentials is not None and credentials.password is not None: + password_file.write(credentials.password) + password_script.write(f"#!/usr/bin/env sh\ncat {password_file.name}") + else: + password_script.write(NO_CREDENTIALS_PROMPT % url) + + if credentials is not None and credentials.ssh_key is not None: + ssh_key_file.write(credentials.ssh_key) + ssh_command += " -i '%s'" % ssh_key_file.name + + for x in [password_script, password_file, ssh_key_file]: + x.close() + os.chmod(password_script.name, 0o700) + + g.update_environment(GIT_ASKPASS=password_script.name, GIT_TERMINAL_PROMPT="0") try: - u = parse_git_url(url) - username, password = get_git_credentials_store().get_credentials(u.host) - except: - pass - - g = Git(working_dir) - g.update_environment( - GIT_SSH_COMMAND="ssh -o 'ControlMaster=auto' -o 'ControlPath=/tmp/agent_ralf_control_master-%r@%h-%p' -o 'ControlPersist=5m'" - ) + yield ssh_command + finally: + for x in [password_script, password_file, ssh_key_file]: + try: + os.unlink(x.name) + except Exception: + pass - if username is not None: - url = add_username_to_url(url, username) +@contextmanager +def build_git_object(url, working_dir): + u = parse_git_url(url) + credentials = get_git_credentials_store().get_credentials(u.host, u.path) + if u.username is not None and u.username != credentials.username: + raise Exception("username from url does not match username from credentials store") - @contextmanager - def create_password_files(): - # Must handle closing/deletion manually as otherwise git will complain about busy files - password_script = NamedTemporaryFile("w+t", dir=get_tmp_base_dir(), delete=False) - password_file = NamedTemporaryFile("w+t", dir=get_tmp_base_dir(), delete=False) + g = Git(working_dir) - if username is not None: - password_file.write(password) - password_script.write(f"#!/usr/bin/env sh\ncat {password_file.name}") - else: - password_script.write(NO_CREDENTIALS_PROMPT % url) + ssh_command = "ssh" + ssh_command += " -o 'ControlMaster=auto'" + ssh_command += " -o 'ControlPath=/tmp/kluctl_control_master-%r@%h-%p'" + ssh_command += " -o 'ControlPersist=5m'" + ssh_command += " -o 'StrictHostKeyChecking=no'" - password_file.close() - password_script.close() - os.chmod(password_script.name, 0o700) + if credentials is not None and credentials.username is not None: + url = add_username_to_url(url, credentials.username) - g.update_environment(GIT_ASKPASS=password_script.name, GIT_TERMINAL_PROMPT="0") + with create_password_files(g, ssh_command, url, credentials) as ssh_command: + g.update_environment( + GIT_SSH_COMMAND=ssh_command + ) try: - yield None + yield g, url finally: - try: - os.unlink(password_file.name) - except: - pass - try: - os.unlink(password_script.name) - except: - pass - - with create_password_files(): - yield g, url + pass @contextmanager def with_git_cache(url, do_lock=True): From 4bd4049cf4be319e28cff5714f26b59022322eab Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 31 Aug 2021 10:42:30 +0200 Subject: [PATCH 0057/2916] fix: Remove unnecessary import --- kluctl/cli/commands.py | 1 - 1 file changed, 1 deletion(-) diff --git a/kluctl/cli/commands.py b/kluctl/cli/commands.py index de72fba99..38517b48d 100644 --- a/kluctl/cli/commands.py +++ b/kluctl/cli/commands.py @@ -1,5 +1,4 @@ import logging -import logging import os import sys import tempfile From 6010026919c55e933b3fbdafda26f1b7ee2c15fd Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 31 Aug 2021 10:42:44 +0200 Subject: [PATCH 0058/2916] fix: Actually check for dynamic args --- kluctl/cli/utils.py | 4 ++++ kluctl/kluctl_project/kluctl_project.py | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/kluctl/cli/utils.py b/kluctl/cli/utils.py index 295b10b5b..0dec58f0d 100644 --- a/kluctl/cli/utils.py +++ b/kluctl/cli/utils.py @@ -132,6 +132,10 @@ def project_target_command_context(kwargs, kluctl_project, target, inclusion = parse_inclusion(kwargs) option_args = parse_args(kwargs.get("arg", [])) + if target is not None: + for arg_name, arg_value in option_args.items(): + kluctl_project.check_dynamic_arg(target, arg_name, arg_value) + target_args = target.get("args", {}) if target else {} seal_args = get_dict_value(target, "sealingConfig.args", {}) if target else {} deploy_args = merge_dict(target_args, option_args) diff --git a/kluctl/kluctl_project/kluctl_project.py b/kluctl/kluctl_project/kluctl_project.py index 0ae05702f..a4b6d38e7 100644 --- a/kluctl/kluctl_project/kluctl_project.py +++ b/kluctl/kluctl_project/kluctl_project.py @@ -415,11 +415,11 @@ def check_dynamic_arg(self, target, arg_name, arg_value): dyn_arg = x break if not dyn_arg: - raise Exception(f"Dynamic argument {arg_name} is not allowed for target {target['name']}") + raise InvalidKluctlProjectConfig(f"Dynamic argument {arg_name} is not allowed for target {target['name']}") arg_pattern = dyn_arg.get("pattern", ".*") if not re.match(arg_pattern, arg_value): - raise Exception(f"Dynamic argument {arg_name} does not match required pattern '{arg_pattern}'") + raise InvalidKluctlProjectConfig(f"Dynamic argument {arg_name} does not match required pattern '{arg_pattern}'") @contextmanager From 901acdb5710d1a99495ae553719cd15ff26b1c8f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 31 Aug 2021 11:59:23 +0200 Subject: [PATCH 0059/2916] fix: Fail when static targets fail to load --- kluctl/kluctl_project/kluctl_project.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/kluctl/kluctl_project/kluctl_project.py b/kluctl/kluctl_project/kluctl_project.py index a4b6d38e7..84aba762d 100644 --- a/kluctl/kluctl_project/kluctl_project.py +++ b/kluctl/kluctl_project/kluctl_project.py @@ -380,7 +380,10 @@ def build_dynamic_targets(self, base_target): dynamic_targets.append(target) except Exception as e: - logger.warning("Failed to load target config for project %s. Error=%s" % (self.project_url, str(e))) + # Only fail if non-dynamic targets fail to load + if target_config_ref is not None: + raise e + logger.warning("Failed to load dynamic target config for project. Error=%s" % (str(e))) self.add_involved_repo(git_project.url, ref_pattern, involved_repo_refs) return dynamic_targets @@ -415,7 +418,7 @@ def check_dynamic_arg(self, target, arg_name, arg_value): dyn_arg = x break if not dyn_arg: - raise InvalidKluctlProjectConfig(f"Dynamic argument {arg_name} is not allowed for target {target['name']}") + raise InvalidKluctlProjectConfig(f"Dynamic argument {arg_name} is not allowed for target") arg_pattern = dyn_arg.get("pattern", ".*") if not re.match(arg_pattern, arg_value): From 52edc86d2a8571ceb49bab2a6e67796f8939c476 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 31 Aug 2021 12:17:30 +0200 Subject: [PATCH 0060/2916] fix: Add --render-output-dir to poke-images --- kluctl/cli/command_stubs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kluctl/cli/command_stubs.py b/kluctl/cli/command_stubs.py index 058a497b6..f0beadcb4 100644 --- a/kluctl/cli/command_stubs.py +++ b/kluctl/cli/command_stubs.py @@ -83,7 +83,7 @@ def purge_command_stub(obj, **kwargs): @kluctl_project_args() @image_args() @include_exclude_args() -@misc_arguments(yes=True, dry_run=True, output_format=True) +@misc_arguments(yes=True, dry_run=True, output_format=True, render_output_dir=True) @click.pass_obj def poke_images_command_stub(obj, **kwargs): from kluctl.cli.commands import poke_images_command From 97675a88fe7477a3fc565bb7be56771dbe401439 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 31 Aug 2021 15:51:25 +0200 Subject: [PATCH 0061/2916] fix: Trim leading / from git path --- kluctl/utils/git_utils.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/kluctl/utils/git_utils.py b/kluctl/utils/git_utils.py index 9cf5d9166..9b6c23d0e 100644 --- a/kluctl/utils/git_utils.py +++ b/kluctl/utils/git_utils.py @@ -246,9 +246,11 @@ class GitUrl: username: str def parse_git_url(p): - def trim_git_suffix(s): + def trim_path(s): + if s.startswith("/"): + s = s[1:] if s.endswith(".git"): - return s[:-len(".git")] + s = s[:-len(".git")] return s def normalize_port(schema, port): if port is not None: @@ -265,7 +267,7 @@ def normalize_port(schema, port): m = schema_pattern.match(p) if m: url = urlparse(p) - path = trim_git_suffix(url.path) + path = trim_path(url.path) port = normalize_port(url.scheme, url.port) return GitUrl(url.scheme, url.hostname, port, path, url.username) @@ -278,7 +280,7 @@ def normalize_port(schema, port): if username is not None: username = username[:-1] host = m.group(2) - path = m.group(3) + path = trim_path(m.group(3)) return GitUrl("ssh", host, 22, path, username) def check_git_url_match(a, b): From 49434782a50c5ab7bf0ec2a3202679e6a9fd96df Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 31 Aug 2021 15:54:56 +0200 Subject: [PATCH 0062/2916] fix: Fix missing / in add_username_to_url --- kluctl/utils/git_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kluctl/utils/git_utils.py b/kluctl/utils/git_utils.py index 9b6c23d0e..3e1178347 100644 --- a/kluctl/utils/git_utils.py +++ b/kluctl/utils/git_utils.py @@ -94,7 +94,7 @@ def add_username_to_url(url, username): if username is None: return url u = parse_git_url(url) - return f"{u.schema}://{username}@{u.host}:{u.port}{u.path}" + return f"{u.schema}://{username}@{u.host}:{u.port}/{u.path}" @contextmanager def create_password_files(g, ssh_command, url, credentials): From 46c184638ed120419803c4f729af3e643ddf3493 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 31 Aug 2021 16:07:30 +0200 Subject: [PATCH 0063/2916] fix: Fix regex for git urls --- kluctl/utils/git_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kluctl/utils/git_utils.py b/kluctl/utils/git_utils.py index 3e1178347..285d7318d 100644 --- a/kluctl/utils/git_utils.py +++ b/kluctl/utils/git_utils.py @@ -271,7 +271,7 @@ def normalize_port(schema, port): port = normalize_port(url.scheme, url.port) return GitUrl(url.scheme, url.hostname, port, path, url.username) - pattern = re.compile("(.+@)?([\w\d\.]+):(,*)") + pattern = re.compile("(.+@)?([\w\d\.]+):(.*)") m = pattern.match(p) if not m: raise Exception("Invalid git url %s" % p) From c477df58b5062b9eaa51a4f768aa259057e3be51 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 31 Aug 2021 16:08:43 +0200 Subject: [PATCH 0064/2916] fix: Run replace-commands-help.py --- docs/commands.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/commands.md b/docs/commands.md index 9f94555af..596407cb4 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -323,6 +323,9 @@ In addition, the following arguments are available: -o, --output TEXT Specify output format and target file, in the format 'format=path'. Format can either be 'diff' or 'yaml'. Can be specified multiple times. The actual format for yaml is currently not documented and subject to change. + --render-output-dir DIRECTORY + Specifies the target directory to render the project into. If omitted, atemporary + directory is used. ``` From d0f2843bb66de9c84878427678054b61340f0df2 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 31 Aug 2021 16:26:41 +0200 Subject: [PATCH 0065/2916] fix: Also allow git@example.com:my/path form in .kluctl.yml projects --- kluctl/schemas/kluctl-project.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/kluctl/schemas/kluctl-project.yml b/kluctl/schemas/kluctl-project.yml index 506498c49..b89825c55 100644 --- a/kluctl/schemas/kluctl-project.yml +++ b/kluctl/schemas/kluctl-project.yml @@ -2,10 +2,14 @@ title: .kluctl.yml schema type: object definitions: git_url: - type: string # See https://stackoverflow.com/questions/2514859/regular-expression-for-git-repository - pattern: - (\w+:\/\/)(.+@)*([\w\d\.]+)(:[\d]+){0,1}\/*(.*) + oneOf: + - type: string + pattern: + (\w+:\/\/)(.+@)*([\w\d\.]+)(:[\d]+){0,1}\/*(.*) + - type: string + pattern: + (.+@)?([\w\d\.]+):(.*) git_ref: oneOf: - type: string From fe6b8e174cb690968ebed41d7302fd05c99a5662 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 31 Aug 2021 16:37:39 +0200 Subject: [PATCH 0066/2916] fix: Use ? instead of * in git url patterns --- kluctl/schemas/kluctl-project.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/kluctl/schemas/kluctl-project.yml b/kluctl/schemas/kluctl-project.yml index b89825c55..53d010589 100644 --- a/kluctl/schemas/kluctl-project.yml +++ b/kluctl/schemas/kluctl-project.yml @@ -2,14 +2,13 @@ title: .kluctl.yml schema type: object definitions: git_url: - # See https://stackoverflow.com/questions/2514859/regular-expression-for-git-repository oneOf: - type: string pattern: - (\w+:\/\/)(.+@)*([\w\d\.]+)(:[\d]+){0,1}\/*(.*) + ^(\w+:\/\/)(.+@)?([\w\d\.]+)(:[\d]+)?\/*(.*)$ - type: string pattern: - (.+@)?([\w\d\.]+):(.*) + ^(.+@)?([\w\d\.]+):(.*)$ git_ref: oneOf: - type: string From 63663dd6a1abda2c50a423da12fd2ef712969133 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 31 Aug 2021 16:38:02 +0200 Subject: [PATCH 0067/2916] fix: Use anyOf instead of oneOf (multiple regexes might match) --- kluctl/schemas/kluctl-project.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kluctl/schemas/kluctl-project.yml b/kluctl/schemas/kluctl-project.yml index 53d010589..b41068aed 100644 --- a/kluctl/schemas/kluctl-project.yml +++ b/kluctl/schemas/kluctl-project.yml @@ -2,7 +2,7 @@ title: .kluctl.yml schema type: object definitions: git_url: - oneOf: + anyOf: - type: string pattern: ^(\w+:\/\/)(.+@)?([\w\d\.]+)(:[\d]+)?\/*(.*)$ @@ -10,7 +10,7 @@ definitions: pattern: ^(.+@)?([\w\d\.]+):(.*)$ git_ref: - oneOf: + anyOf: - type: string # See https://stackoverflow.com/questions/12093748/how-do-i-check-for-valid-git-branch-names pattern: From fc94b52fa94b92ba2ecce3bd1259d938010f4714 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 1 Sep 2021 09:59:39 +0200 Subject: [PATCH 0068/2916] fix: Fix crash when get_single_object did not find the object --- kluctl/utils/k8s_cluster_base.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/kluctl/utils/k8s_cluster_base.py b/kluctl/utils/k8s_cluster_base.py index 9f4d20c15..720c69294 100644 --- a/kluctl/utils/k8s_cluster_base.py +++ b/kluctl/utils/k8s_cluster_base.py @@ -21,7 +21,7 @@ def get_single_object(self, ref): l = self.get_objects(version=ref.api_version, kind=ref.kind, name=ref.name, namespace=ref.namespace) if not l: - return None + return None, [] if len(l) != 1: raise Exception("expected single object, got %d" % len(l)) return l[0] @@ -34,9 +34,9 @@ def get_objects_by_object_refs(self, object_refs): futures.append(f) ret = [] for f in futures: - r = f.result() - if r: - ret.append(r) + o, w = f.result() + if o: + ret.append((o, w)) return ret def get_objects_metadata(self, group=None, version=None, kind=None, name=None, namespace=None, labels=None): From 353732e2620e9da82ed534e0bad20b1cd9f49f82 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 1 Sep 2021 10:01:32 +0200 Subject: [PATCH 0069/2916] fix: Fix crash in build_git_object --- kluctl/utils/git_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kluctl/utils/git_utils.py b/kluctl/utils/git_utils.py index 285d7318d..715a64052 100644 --- a/kluctl/utils/git_utils.py +++ b/kluctl/utils/git_utils.py @@ -131,7 +131,7 @@ def create_password_files(g, ssh_command, url, credentials): def build_git_object(url, working_dir): u = parse_git_url(url) credentials = get_git_credentials_store().get_credentials(u.host, u.path) - if u.username is not None and u.username != credentials.username: + if credentials is not None and u.username is not None and u.username != credentials.username: raise Exception("username from url does not match username from credentials store") g = Git(working_dir) From 2dcf874ab57566ddfe939ddb5b42a67a2e295edc Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 2 Sep 2021 11:34:44 +0200 Subject: [PATCH 0070/2916] fix: Introduce KLUCTL_GIT_TIMEOUT environment variable --- kluctl/utils/git_utils.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/kluctl/utils/git_utils.py b/kluctl/utils/git_utils.py index 715a64052..38a687811 100644 --- a/kluctl/utils/git_utils.py +++ b/kluctl/utils/git_utils.py @@ -1,5 +1,4 @@ import dataclasses -import fnmatch import hashlib import logging import os @@ -134,7 +133,14 @@ def build_git_object(url, working_dir): if credentials is not None and u.username is not None and u.username != credentials.username: raise Exception("username from url does not match username from credentials store") - g = Git(working_dir) + class MyGit(Git): + def execute(self, command, **kwargs): + kwargs2 = kwargs.copy() + if "KLUCTL_GIT_TIMEOUT" in os.environ: + kwargs2["kill_after_timeout"] = int(os.environ["KLUCTL_GIT_TIMEOUT"]) + return super().execute(command, **kwargs2) + + g = MyGit(working_dir) ssh_command = "ssh" ssh_command += " -o 'ControlMaster=auto'" From ac52f471b41d8253488fac8a073b5187dc5c8a7c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 2 Sep 2021 11:34:55 +0200 Subject: [PATCH 0071/2916] docs: Update documentation about environment variables --- docs/commands.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/commands.md b/docs/commands.md index 596407cb4..d907b508b 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -70,6 +70,20 @@ These arguments control image versions requested by `images.get_image(...)` [cal These arguments control inclusion/exclusion based on tags and kustomize depoyment pathes. +# Environment variables +All options/arguments accepted by kluctl can also be specified via environment variables. The name of the environment +variables always start with `KLUCTL_` and end witht the option/argument in uppercase and dashes replaced with +underscores. As an example, `--project=my-project` can also be specified with the environment variable +`KLUCTL_PROJECT=my-project`. + +## Additional environment variables +A few additional environment variables are supported which do not belong to an option/argument. These are: + +1. `KLUCTL_REGISTRY__HOST`, `KLUCTL_REGISTRY__USERNAME`, and so on. See [registries](./images.md#supported-image-registries-and-authentication) for details. +2. `KLUCTL_GIT_TIMEOUT`. Specifies how long to wait on git subprocesses to finish until they are killed. +3. `KLUCTL_NO_THREADS`. Do not use multithreading while performing work. This is only useful for debugging purposes. +4. `KLUCTL_IGNORE_DEBUGGER`. Pretend that there is no debugger attached when automatically deciding if multi-threading should be enabled or not. + # Commands The following commands are available: From 57d5de1b7a2ea9bd6492f070737465d5288b152a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 2 Sep 2021 12:33:50 +0200 Subject: [PATCH 0072/2916] fix: Delete git cache in case of timeouts --- kluctl/utils/git_utils.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/kluctl/utils/git_utils.py b/kluctl/utils/git_utils.py index 38a687811..443bd7b81 100644 --- a/kluctl/utils/git_utils.py +++ b/kluctl/utils/git_utils.py @@ -3,12 +3,13 @@ import logging import os import re +import shutil from contextlib import contextmanager from tempfile import NamedTemporaryFile from urllib.parse import urlparse import filelock -from git import Git +from git import Git, GitCommandError from kluctl.utils.env_config_sets import parse_env_config_sets from kluctl.utils.utils import get_tmp_base_dir @@ -192,7 +193,17 @@ def update_git_cache(url, do_lock=True): with with_git_cache(url, do_lock=do_lock) as cache_dir: with build_git_object(url, cache_dir) as (g, url): logger.info(f"Fetching into cache: url='{url}'") - g.fetch("origin", "-f") + try: + g.fetch("origin", "-f") + except GitCommandError as e: + if "did not complete in " in e.stderr: + logger.info("Git command timed out, deleting cache (%s) to ensure that we don't get into an " + "inconsistent state" % cache_dir) + try: + shutil.rmtree(cache_dir) + except: + pass + raise def clone_project(url, ref, target_dir, git_cache_up_to_date=None): logger.info(f"Cloning git project: url='{url}', ref='{ref}'") From cc50895b546ede8a4480e0796a505014b3df146c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aljoscha=20P=C3=B6rtner?= Date: Sat, 4 Sep 2021 15:58:21 +0200 Subject: [PATCH 0073/2916] feat: add kluctl logo --- README.md | 2 ++ logo/LICENSE | 1 + logo/kluctl.png | Bin 0 -> 12230 bytes 3 files changed, 3 insertions(+) create mode 100644 logo/LICENSE create mode 100644 logo/kluctl.png diff --git a/README.md b/README.md index 9fad450a2..3343fec82 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # kluctl +![kluctl](logo/kluctl.png) + kluctl is the missing glue that puts together your (and any third-party) deployments into one large declarative Kubernetes deployment, while making it fully manageable (deploy, diff, purge, delete, ...) via one unified command line interface. diff --git a/logo/LICENSE b/logo/LICENSE new file mode 100644 index 000000000..562964b7f --- /dev/null +++ b/logo/LICENSE @@ -0,0 +1 @@ +# The kluctl logo files are licensed under a choice of either Apache-2.0 or CC-BY-4.0 (Creative Commons Attribution 4.0 International). \ No newline at end of file diff --git a/logo/kluctl.png b/logo/kluctl.png new file mode 100644 index 0000000000000000000000000000000000000000..8e3ad6e30a2d15febde0b1663c11a341f312a9e2 GIT binary patch literal 12230 zcmcgygMuyw^t`ww<$`bMEJk>yAiu)rVxnOvDffgzWJns3ru0#SLC(QsRU4Rz80l_=D^E z;ITF#fFGen1b9#6^hnS%*{*Oh(#N7x!WORc)b<=XRbn|@f@&e-N>B(>7VCQQ7 z-020sql;DYrZf}yJ;-C|eQmFljVW)pk)?6jZM#FZ9*d-})E2IfpTg+Hpo963qH_7> zjrVmXuQ}Pkz7o*!=u{XKyd2FBr+;Z@H(Gbo*Ct@@ zplJ-eqHR|tQdM(?i5d=m8ag%}=x(na_%s?9i%n3+LC9CWZtzP^c4^R9O{rF1oZPoS|N)7u@{@|iOyJ>V1t zmc=nCs~;&6uFIV8u7w>h=^bW14VROH;=qeFldIG2^!l%8@WQxw(1<$Xj5G@k=I%N6 zlT9DU;j}Ctw-;qOQRHwOD>i8kxH$ES7IoQb*!v;1)gZJz#^osHFj?cg(CHPOz$sBP z}qk#TM(>bh4c6*o5^%?&^GRK#t|3jPK$4czLkcwAaF0vFV7Jnok%&68n~1YVn(j zzM#SGvA_!Fc-cT8Pa7$Ji;@ucfeA-IV8o?YMA!o>G&o9}=DxiqxrY4=gH}0{D*N%W z7kxp%LzLUL`fpj?*^@34bchbPr^R2+Evze>QYscGd6msX=ZG%hX`2}bw$Q9Wu z`R#odn*qyQv=v61-r_hk>~vmtY@q7?fd={2CDe@;xnZwR`V)x;RbBPeKYQwQFyhfG zGOBfXvFcp-wA8EvPB$mFS?9j?v(WI`sWZ$}>ToCUe5Z>AEG^N+!zee&R78aC^{z{4 zg_ppzu8Z0;cuk@$Z;VID9E0;tF5;)$y$TdE`-2J;TyR3g%qwF8U!IUx2)3suAu_E=wH#73m%+Xy7SXGWQ zP5x(xzuqeQfZYRIC}R5U6%h^l6T*PzifS|{F7`&p(7-vG@|^KMnj(0TB>18?o~t#f zB=az_`Fu$r@T6~0Zd3ie&~w9Thp9SuyM~7f!VUM-HMAR!=c5Fgj#*254*rZ5=!G{m zm7qac^Vc8%n384|>MlOCMUu#3r5E$iyI17$5^L z+j_FwNCX5pE!z#@aWOTrO7Rv=8rSkI~W}O&Pb+ev(H7mTDyVr zC!GoIy8ju;)5_`e6^6E57t0=wIi3xrkOj-^^0InP_Yejl$!;8~DdNua?H|cJ2fwA3 zG8E+$h1n`DJGq3}@62~ZT@#Q!WJ8XZTMeYiT(3Fb?GtD|cge^_gOEYUvj5{f^N?a+ zI}DtN*8WP`Gx+6175K+@zT%6@PBivGWzVvEdRv>7s7mj&)2K<`tEte{K?4utRmHY1 zEQS}<2Y5`P37EZRg9(@k1C3l1ZP$OHJvrG`095!cJ~zBu&&2;=RL=xUIe}Nd%;NDX z8+&N0>0O4PzovB59ITHqd>)U5>|b?jMfIC?Yy7joX`(%6Tl36ht?SBu3tMYEY!+Qsa<4i=mrXkRfPEmR{c{&ZJ(6|I;m z$@HhEz?E$LuH@Nwuf?F{4I@Sv$Iev9!MRwLn>5~j{zwEYik^>n^DScpCIYkoKaO_h z_;TA}Q5NZ$OtvnayA*-PZ7i4D;e%s+ThS}&ftM#M=@bb#gkq0gyL2*Ir8svowkGj7 z{}JB5Se&V!Tx07KhF));0p1Uvn~7F~9`dxrv^4~eBq$Ao;NnEbW3Wf)jUqJyf(O#K zS?^SyW6WohtK~A;{7wXmPbN$Yi}T$=(9v2r8KPs@!CG)QOTh75I19Oda{>n2Y6aBD zZs4b%zh~ie>B%ZYgKz&L&F4>EPMQyK4caDwVXj6C8Hk%xGE3a4{A-f0n~dq9jq`Ax zqMCVlF2xpMwCw3>RM&zGmaJXW(?^4`euqk9 z-aG@573+&wWX^LE>*=LuD3iZKM)98i1Y8k)S^d={uJK(Me=3_26*;aGl z?3y43A+H}rM*N4*eab8`T;hfr%2rOqP8M^VZt!+pJ?mMRXui6@_-u|J1WW33q@xtR zm{@VPF*x?jWBf(pGY>{}BQ$PYIFlvt$~g_Ih)uZ-tF+1(mq~|yO=s&1fRbP~Em8h8D)W`at0nKt-SadLkX2tQDFQNv5yHCycmcN7hHtr9*R_cYv z70`?cs&`U&OSqCd!UX@a6TCIj?9XUG15;?9b=8F{9eyKO8qrddIT+D8xp)BWi3+xT z#m$8VRuMWB7B@+{6)@ysz_tGdS~mBl6?s$IYkRgeG_#n86Ai#GV)6d!W9&w73e>OFHG)zN!KSKo__|G4f^KjlE-|I8QzG*h4zAk}u$b{XiC|ARru3+mWs zWdjU1)?(9jLC9j`c9AumaJ`i+oGP7;T{J z_CJpu1lS(a+g7<|OvK*lG`-&$+}$W##$kxSi+c3>dhY2N8d-8_PVN}Mwsw~C)Z)-& z0}!};G>EP3(RecrBx;4`I`}}MKi~WOi)D90N9HUt+cdHi-!h5V&aU-h5StU89-o8; z<7wS82CLB=v?swv8fZ{m9)z;-L=FII^z*BJX7Lr;EP-vd%Bg)lzyRwyhP~d|UP?3x zI9W>EVWy*E69kw3^N%$t4Ddce??)xJ<3B#K-Xj_VcsZJ!lm74{^Zd)h({G&CsW+~h z!F~|Bd!V6sb+Q=i%=yJHr1!nkJ(Q!bf{fQFht0`d*sVQt+9Sj z9hs5hIqkiY>d9Q*rjeU(0z!3dEj&=Yv?w}D=@c`S-gcpBl5B)!ur@W17XhUd(=a5 z3!Mif6Ep?#b|9<>1K zNmu&bJJHg+-FKpK#TFha8(^@ywz%xg3YP=njoPJr6P@${<)ZLQO~Hy0!Y(Dgp#V!r z`pd9E1UPY@LX*vDAG>`^)62tZj4_k&SHkLJtZIzC=3ZYj_PTvO{J!im$22$5fglFOfDUIpZ6y{pFxE+ z5;410flf?#bYC^4^ODx1+4tH{JV!N&rFbn>3AUOMX`lU7kc$$8zMD*DYo0bQoD^6q z>uDC#ZxPmAyd8nSV0LCnFoLtj!2HC~2nb7%Qp#MCMo(43ANamgO4174Tbw}p# zmwB|z;nUT#NQm@)@xWZ#Um~Ha(f&r~+@!e85w@iS^Zu6zuQoGhzB;>*(XKWly0>2g z9gi4{=D>E^H{n1)TiE2_`TvAQ>d)P~fsTKsqZrBwpE_n`6El&+aHLb~^>h>dY#9va z-0Oc|Y?8bDGuBHgVp^O{0*7nO;&r_q=kG<7Ol^)5e_rRr%jzi)uwY?wxh@u#ixpQ) z$UPlUKnJ?mH|G$t$}xpavPjW;d$jg*R1O2xPOrJgZoz!F`p}6@vGGlN_M018rY?^k z@h6d2#3#H*gX(nW8=0{J3_oEPgV|WE8s9=zwyS_eV{r-c+Rq<3EhrwJ?9n)=^|Zz& zZ!T%ta?V{&cKq2A(g1m&)h)>S?@FuaDRTR1Bi*0FIKW!$^wcOyUK+w+jsofibyjL7 zEb7L_&!%Q5wDrcuCN|_16tv=oi7Ki+-MpmdX>$fR5gu37d24jPr-?V?bDyPS=0h<8We)S#C+oA!nKJ`-9Y|^IC8&Pgh|e| zHTY|0LrC~*ZtZ3>?A&KEdO(M#r4X%vI}&WdveTN&IUggxZ<46qp2h;B5)y*5T9&W| zUbNpP0`I)wtlURkX9YJK3Ncxtjs|q;(mYURF?J0Q8Z@inQUVo0PtT*-E}_4ag5q2n z5wtegOdi>}vUh@wg()Su*3-S1s46R)HiiLPE^c;sDRVgCIWtw3U8te~^0H*d-xG98 z!)ncEMdex=vR4fi6$Q!~z^rS_>kTzT9j9xDEw$qL3_Ui=#%W)i9m3&h%&_rN^MSM` zSy*0fZXRk&FaGy$|Fd-u*m!eu?Kp~d>yAS;0<93BZUuNW3t{`&=#3`rT^9MrF#gOQ z__&Ku`PZkiuFL&NLROuQewcu`6CDWoUqVHYi2IKw%ej>=xAlB)i{{;fp zcl`7DYt6?U0s&aOpa65SyM#RRdGKDyaaVivaATs{afbQa_kEyA+2!Ez&O%o-3(KTE zZ=c31sRcK2ZzUDonnWSeGXAGEjmKsx3CWbcYe5)lReI+szdUa6?3*~F2k|+iE9jg1 zk+N-N97a;hc=3<-vdsQ1n^1%vbnMJ=- zfmt;2fYkb{j<&XTjgcAqq&4p8nSaw|Kt^6(v7-6-SY(frXM<6>m)XzNvx zZj@lTLE$k+q3(ITDn}8`6~*d-WU2uf!`u0v_M%Lqwk@WEYaI)4LqOf&9X8`n&CzvN zNqi9i{zSq{#Vk&>OwN`sDuz{l7a}q5-zO(05k2Kaq3B6W1+%|=dSXVdR*3H>yMtOg zIK2p#-xeLW7G{m;$@ntbdy5@Anbg9z(o0608%Bf+`Ov%KS|7X+Ex>t~pxWpt6qxpGpU0hvV3w3WvNr{O` zUGMJh?(gr0yittnvBo9EzgNC>6lJ9npTSN~jg=!rNs0HaaoUOark${Dr7FS4CmS37 z{*f%XAYB2)=CIApO^0+fT<#CVUI(L~w&Z{d$^n4Lcwsr!HyYQ=1T4g1wF5DJw*B7x zpfAL|EoJbI(Og*a#{6LSuTrFUQDzIe*1eCjh-w7BV7D)U-gK!K_`5=EZl0q}KCZ~4 zRfJl>s5L2mi}lEwmL?<%X?iy~cw{7=Hj0g5keGOPZ*MR7+|QX*cwAEs04J%c(cnr08k4T<(56^03k^1@lVAZji0rXXg5Lp1#*S1gmn#{e&Jb|w- z*1K^UVZr_-Cnu*1g-(&zb?kne?+5Z5)^=+fc-UB_E6Yy&iCr{=xK^6vV`Em; z^;2e+JzEr2_Cu)lH&2{*YMi~ji<2&Jd~kAsz>Dcx*z;}-YRwq^;}(P_?ZWe-v4S+{!X7dr8+uYBr`-fA)XfuS zx|j9hZiL`9?hoR$ZmSoIPu?S~@1CoVU+=N~-dpyxeePN))*vdt>qe~KG_W(ba(!NR zgTfXfjwc^ul6fQ{|LxA<<8x!*QpKw^LL5X++#FvJPesU^{uh0xYYr5*nFrnnZaPJX zdF&htZ(pEG;wvPVo-nGbxFd=VP`;rs0G==KXWQ7Zwoi z4~pic5YgnWJF%o4P<99`Vry$q#8UEj?tKAPQ9feO31xWX9U&zvuB)(bS>_;@d?MqGg6qqO&Y zduzO7=6gOHY#ncJ9Ub#K>Y78pe*NOSG&Ii=|CFzZm6vB$D9Q%29~!njINf(^*&0Lf z(#zY6;HIcHUkx&nv9c=hyRiT1l9N3cnKuKaMNo;#k;Ch^eA~H2z^=m0|IRW8M@jWD zO|CR^>3=Tb628L|Yk;!PR~0Cq>+6kW+O^qMD&j0I!$r(ef0%Z6Lug?I-@75}9?t!h z)p}q3U6bM?ti5{rlpo(q4$et$I-0gC!qU_tAt51#wkkEVfM>~lltmQ4Pf4aYgqz>G z*%4!L*F_Qrv&Cwtc18L%lqKWXSQ@2Lu0Umdg5GN8;zawpN#0UN_qg1LAAP$Wtxo) z)=@iN4aUwB<3x~)i_}zB`^be{L{aB^GH&;OO1Uvgv;%HLsp|_=e2=Bd~l!b=$gux z*&zg}k!p^?OJTTH6vAHKEN}`6MG8I-8U6Gg3zXyYb;e96aItM{=%WZV!;LwT&zJ)n zjJI|mFc?Q#_br$LT8$d4a>L+#Y)#}qB($&h7WtfJ<+sJ|MZsttgpxak5Q=v~61%cS zzhq;HCJ$UAj1Z)n8&L*YKsLu9OQu^zUqRGIlqb3eLAggP3j&c=GUa97cV(S(lKgP25LT~uk zmR9RE7|X~QJO1~YIWhvV@j1@L<*V*&UJj_rR*`Od^)HWTQ+*Ct4HzFcczFxbpVBkz zOY0A$|A;UllJb6D_1IeFczgP|{X-s_%(3fl5r*DAl8{xmq@DeBR z4wQ7F@;V!t*dxZb^Vd2b#{uFV^;+4FmIqzza;PSJ#YD9wmJT6qn?y!NFd-fC284B$rc^Rt~o6%4~oP zN!qlsGN1?F9;8iY{W8yBy1mGC?X4nhoe)F2&4coB(-qqCOG`@&h=hc$orh>l zTH5>Pr4Vm#35k;vc?Sn$V+a12IDljlG($n5f)wuvohT)@8d?E8nHy7|ct60VSiT)Wvht3?(N9T9AMh5JmseJnu=MqH?Hu?`g};1ec#F`l&?U*$o)=R9!Gt^_ z2=7K?q-)JPKPlRSs@+{d!LqU-5`h$9)M8vPi2$wgJMIq?8!lL?Ip2RykgShu>5mUW z?5FC?AG(XEg9{HKu_~$#k0v&P#&|w)(;&Cs$09)JZ22ToJ?S$3qU9BlRt`NKmWQuJ zv;5MMO{FZ$U2}Z3EsgVg>SVDgC?sm}NwjWANSQALLLr8p1t>s56th4V4JR)Tp{MOp zMJTFkr<*X!l!EH%2l#;xa;rU8SF8e^eq7K90$o`g$y5+}Z!meaFFVVx+c0tOq!Uml ztI~DYt3Jkhqv$No`scImPcUo{LCf&#A^dcmEBvX$&IAgo24MaZ4L&P^K z=FObTlED64#ftnp2ZaM%PfN9z0 zdzl#3AHI`H;Kx&p=I2kli{&I4zP!RGqO(7pEa7^7EDsr6gB1uMpc$H%Y7GR`dFZmm zro(LD)zMn9p#da)z@p3m_;a)O2Z>E`3vDwtiugNIg_@B`{NY*khP-^bIvW!NetRl5 zHtr%NR%+%8t)cP{!-r8$5LuQx*EBeHW%dzKmOoO2i9jYph%kG;VFlVTZOPq0Po9_r3=Bg7S6_MBz@%V~d-GK;MxeQCNHBnv9#WyQI*sqzAw?U=J zTwEYDYgDh18nm)0Ahu}aU=Gz?I3Vka^K*AsQ8CYYYVb85+hZfYz|H~x_XG$xGXv?e zN=Dw^=zc?sW58Yu&e$`8&z~dOgpsl~$>{>BTi=s}-1Egh^pi2gA2Z~B8v%{ol^w{8 zzn%ti7{|?{&WP1I>AQ^$;v5o6|C&9K9_C-Pyc`x5N)SAxsgZ7XcKEE`);6e9v2?5t zwSQJy@u(Dmj94vmLp6ApTVm-MIWK)6p^s2nDvc1<*Pq{?o~(^yk8FcU-pryUyGE)_ zq7r`kJ^`NioRWu(9H}L=FZe)u5Sq+VeRY8tp@7p4HNoHNHnp{w-B6 z=SR$RbX;8xldT=TXT#tlxf&>xjZI6PJB9T2UOEnbsQAT=g01{`T4$fFujTAdw$EUF z3{8$hPHpmZY=9D<$ay3rm(g{mw-g8zih?54t1<~-Wnp|8uXe(pw{BjuVv%i_b2h6#{#eRji29E z+d^)4$kUkv54CoLBjvd{K0EMTlzX%jep=&f6Sm^Xgw=!_2IjEq%R;$es5^EIY9iVM3fsx<bnV z8%R6#L7wL16X)jJ+S=OD(5=d(c||AT_nA`qq6zGL)PMv57*vVkmprp?S%@6)ZA!Ud z5>iZ=P8Nv*Ud;QhI-59SMQ>k0zP)HhQ&Z*W7D0n?qcQJIIPp9bd0P}&ZzLnr*eLIR zG2N)5(sH-~Z0;xaOn{+=h8{shWn|J_em?fF`T2&1b^F;!4rV{6aY2pMr{`w$GtNuP zw!*S3PCq1aQohM(#@jRGhSh%hbb8t(n^ER+6%b9}R4>95N2E@hg|wg^+m0uNMJciU zaXs-P@5;=f@9X8}KHS6uW#rPVGHPq<)jwt9H5Ckeidi2c>yD-iij8Y1KT5d%NYPSz z+h8@V-qTH)bF}<@0VShkOsf4jzsO-m7HO7Uid>^pc?@e2OzL0uihif~j>XvE{LoM% zxG6tB4>fz=*%rAlm)GH>lC1mmV>-oG{X42F-qMjG<}XE~hJ8U(^czutoN4LjY-dY& zEFI1$VR>MnYxxwpw)V(ay^*|0IwC^Ml(FLJgXsC&4cQ%s9?rJTpXs3@$uP!*(Soa$ zKzaduI2<@bX~yjn>b23BJ+*SHXr`qD?5>-MiCx3K@#~m&+sbo&{b#AeM87szt+|Lp z>DjPLwO9AXH$0>UNB&Eo_uS+Ku8{^$!_E$NUprZa7J6{DAhX=vlU@rZZRTFxGYpjJUg~d27htNMVdbXNXJym5vr2Z64LcAi1 z6r*vkS%?;6XfU3w7-m;nenb0K7*Mt+AaVCG;s^6SWkYk2I*`|=s%l4A4I3*5xTk8PN{Ot9>BG#kY7ggwQWV5kg(yLYu9kZW*YoXvJ-(wytB{z)(AZW;^SlCit6nJ+EhLao;kG83sRWiSQB5Z!-13lL|LbGTgEEcb_CA5PynmBr^)dQ?`s|09*vy0T(_9}+ZR zbvJ^MFZBM%=sY$K8B0=*qYmcly?^ooxk&1Q zk-1Q(SuhdlA@w)Zm(Wp=sUq;WD-Ce-(6j*TIHw>uOJR1lwMw-9Sp~ndBy3QS0Rp4? zP=bXtc%@hcCDLvk_GOO~ZxnN84W-qv00VZ2-1-Si8X1a${B@6v`iZ5}fB^*!iDbnv zaI&{ICPqp(J`~mlr_Q%nk{n8}ZxPhhF;pmqcoF39yMdtAu=hn(C$HU;@d%rmj0`*v z?$E>pI&MpO!k33OHps&jagHw#bQHOTTlTsTM-yRfS>mC^BnL8rV za6?ZMnhRQgh}YHzx7OSPLVLrsKJQaA{eSsY5gaSK!8Uj+F!M{4`9FEla)Olz8bDOA~)o-9?Um`80KoOQg>Rf2y+Y8J$K z_w?G*1?QK$It9aXjv}a8z^!yV1MQLOKDLkq>y8DFwS%59_{!77b1$HlJ3uBH-~OOb z8+5>dZ*6YiNlL*66kzv~hle0AM&tWDQifSOXTk5to8?lR_x72Q+n`SmZF{%wU1Q#r zCWBbX9L^iX4h}+cT2@e(uRQmtdb+!|dOXJ_SFR6;fLJuRi7*l{bgIM@IlXsRJ428G zq~XABm>j@XbE0le?`^kWQB`2L9GyCCp(uST8^=T3{KLa70`8N6I1Z~9z$+uS(bLm+ z624NJ=&~E^tWR+GGUFTWkiIDC`95mxC!10DeM`>m@$M@cO-*TLP=P|Q;&UP9B7vw@ z?*c7M)YJWKFYoVfm^>p8lJF4;O5L^Ltv!8O1^%)bl_Hdbw&-fJ&(?Z5FjngtbR&Uy z80o_d;>vKIW@K787?a{HlKE}ZaPD_cVaX#(E?q9Lm$vc~c)VCFAY&wDUcp55)}XF5 zf1<9PXcQw~91(+*ZlOng@Z&f*+)a|I87*%K^w2F#|K-MXql}EaVpxZnF!neAB2So# u4J{`0Wcb^b!_Qz+9I6oY|2rFgAt&0kpJc Date: Sat, 4 Sep 2021 17:28:37 +0200 Subject: [PATCH 0074/2916] feat: add very simple example project --- examples/simple/.kluctl.yml | 5 ++++ examples/simple/clusters/kind.yml | 3 +++ examples/simple/deployment.yml | 14 ++++++++++ examples/simple/deployment/deployment.yml | 4 +++ examples/simple/deployment/nginx/deploy.yml | 26 +++++++++++++++++++ .../simple/deployment/nginx/kustomization.yml | 5 ++++ 6 files changed, 57 insertions(+) create mode 100644 examples/simple/.kluctl.yml create mode 100644 examples/simple/clusters/kind.yml create mode 100644 examples/simple/deployment.yml create mode 100644 examples/simple/deployment/deployment.yml create mode 100644 examples/simple/deployment/nginx/deploy.yml create mode 100644 examples/simple/deployment/nginx/kustomization.yml diff --git a/examples/simple/.kluctl.yml b/examples/simple/.kluctl.yml new file mode 100644 index 000000000..b41b5e672 --- /dev/null +++ b/examples/simple/.kluctl.yml @@ -0,0 +1,5 @@ +targets: + - name: dev + cluster: kind + args: + environment: dev \ No newline at end of file diff --git a/examples/simple/clusters/kind.yml b/examples/simple/clusters/kind.yml new file mode 100644 index 000000000..a16a76def --- /dev/null +++ b/examples/simple/clusters/kind.yml @@ -0,0 +1,3 @@ +cluster: + name: kind + context: kind-kind \ No newline at end of file diff --git a/examples/simple/deployment.yml b/examples/simple/deployment.yml new file mode 100644 index 000000000..403d4d293 --- /dev/null +++ b/examples/simple/deployment.yml @@ -0,0 +1,14 @@ +includes: +- path: deployment + +commonLabels: + my.prefix/environment: "{{ args.environment }}" + my.prefix/deployment-project: k8s-deployment-nginx + +deleteByLabels: + my.prefix/environment: "{{ args.environment }}" + my.prefix/deployment-project: k8s-deployment-nginx + +args: +- name: environment + diff --git a/examples/simple/deployment/deployment.yml b/examples/simple/deployment/deployment.yml new file mode 100644 index 000000000..b5a54253e --- /dev/null +++ b/examples/simple/deployment/deployment.yml @@ -0,0 +1,4 @@ +kustomizeDirs: +- path: nginx + +overrideNamespace: "{{ args.environment }}-nginx" \ No newline at end of file diff --git a/examples/simple/deployment/nginx/deploy.yml b/examples/simple/deployment/nginx/deploy.yml new file mode 100644 index 000000000..4a47e1c31 --- /dev/null +++ b/examples/simple/deployment/nginx/deploy.yml @@ -0,0 +1,26 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: {{ args.environment }}-nginx +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + labels: + app: nginx +spec: + replicas: 3 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.14.2 + ports: + - containerPort: 80 \ No newline at end of file diff --git a/examples/simple/deployment/nginx/kustomization.yml b/examples/simple/deployment/nginx/kustomization.yml new file mode 100644 index 000000000..8bac1b456 --- /dev/null +++ b/examples/simple/deployment/nginx/kustomization.yml @@ -0,0 +1,5 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - deploy.yml \ No newline at end of file From bac2292773a67a3e8b4c4c4756c61d7a586e05cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aljoscha=20P=C3=B6rtner?= Date: Sat, 4 Sep 2021 17:29:02 +0200 Subject: [PATCH 0075/2916] docs: add example projects section to getting-started --- docs/getting-started.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/getting-started.md b/docs/getting-started.md index 653689a72..fa96fd6bd 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -51,3 +51,13 @@ $ kluctl bootstrap --cluster test.example.com The bootstrap deployment might need to be updated from time to time. Simply re-do the above command whenever a new kluctl release is available. + +## Example projects + +Now you can play around with the projects within the `examples` folder. In order to have fun with a very simple example, just install [kind](https://kind.sigs.k8s.io/), create a [cluster](https://kind.sigs.k8s.io/docs/user/quick-start/#creating-a-cluster) and run the following commands: +``` +cd examples/simple +kluctl bootstrap --cluster kind +kluctl diff --target dev +kluctl deploy --target dev +``` \ No newline at end of file From fb375add4a95b1897c3da10597e8c72f4ca78532 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 2 Sep 2021 15:29:58 +0200 Subject: [PATCH 0076/2916] refactor: Move loading of vars lists into own function --- kluctl/deployment/deployment_project.py | 27 +++++++++++++++---------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/kluctl/deployment/deployment_project.py b/kluctl/deployment/deployment_project.py index 1863169d5..246902007 100644 --- a/kluctl/deployment/deployment_project.py +++ b/kluctl/deployment/deployment_project.py @@ -50,11 +50,22 @@ def build_jinja2_env(self, jinja_vars): add_jinja2_filters(environment) return environment - def load_jinja_vars(self, rel_file): + def load_jinja_vars_list(self, vars_list, cur_jinja_vars): + jinja_vars = copy_dict(cur_jinja_vars) + for v in vars_list: + if "values" in v: + merge_dict(jinja_vars, v["values"], False) + elif "file" in v: + jinja_vars = self.load_jinja_vars_file(v["file"], jinja_vars) + else: + raise CommandError("Invalid vars entry") + return jinja_vars + + def load_jinja_vars_file(self, rel_file, cur_jinja_vars): if not os.path.exists(os.path.join(self.dir, rel_file)): - return self.jinja_vars - new_vars = self.load_rendered_yaml(rel_file, self.jinja_vars) - jinja_vars = merge_dict(self.jinja_vars, new_vars) + raise CommandError("%s not found" % rel_file) + new_vars = self.load_rendered_yaml(rel_file, cur_jinja_vars) + jinja_vars = merge_dict(cur_jinja_vars, new_vars) return jinja_vars def load_rendered_yaml(self, rel_path, jinja_vars): @@ -80,13 +91,7 @@ def load_base_conf(self): set_default_value(self.conf, 'tags', []) set_default_value(self.conf, 'templateExcludes', []) - for v in self.conf['vars']: - if 'values' in v: - merge_dict(self.jinja_vars, v['values'], False) - elif 'file' in v: - self.jinja_vars = self.load_jinja_vars(v['file']) - else: - raise CommandError("Invalid vars entry in deployment.yml") + self.jinja_vars = self.load_jinja_vars_list(self.conf['vars'], self.jinja_vars) for c in self.conf['kustomizeDirs'] + self.conf['includes']: if 'tags' not in c and 'path' in c: From 06782998eb6e52becc003cf9411bc5ddd667b3cc Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 3 Sep 2021 14:31:36 +0200 Subject: [PATCH 0077/2916] feat: Allow the same kustomizeDir to be included multiple times --- kluctl/deployment/deployment_collection.py | 10 ++++++++-- kluctl/deployment/kustomize_deployment.py | 17 ++++++++++++----- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index f5b3e4e3c..fd9461961 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -55,13 +55,19 @@ def __init__(self, project, images, inclusion, tmpdir, for_seal): def _collect_deployments(self, project): ret = [] + indexes = {} for c in project.conf['kustomizeDirs']: - deployment = KustomizeDeployment(project, self, c) + index = 0 + if "path" in c: + p = os.path.normpath(c["path"]) + index = indexes.setdefault(p, 0) + indexes[p] += 1 + deployment = KustomizeDeployment(project, self, c, index) ret.append(deployment) for inc in project.conf['includes']: if get_dict_value(inc, "barrier", False): - deployment = KustomizeDeployment(project, self, {"barrier": True}) + deployment = KustomizeDeployment(project, self, {"barrier": True}, 0) ret.append(deployment) d = inc.get('_included_deployment_collection') diff --git a/kluctl/deployment/kustomize_deployment.py b/kluctl/deployment/kustomize_deployment.py index 147b41117..58eb75366 100644 --- a/kluctl/deployment/kustomize_deployment.py +++ b/kluctl/deployment/kustomize_deployment.py @@ -26,17 +26,24 @@ def get_kustomize_cache_dir(): allow_cache = True class KustomizeDeployment(object): - def __init__(self, deployment_project, deployment_collection, config): + def __init__(self, deployment_project, deployment_collection, config, index): self.deployment_project = deployment_project self.deployment_collection = deployment_collection self.config = config + self.index = index self.objects = None def get_rel_kustomize_dir(self): return self.deployment_project.get_rel_dir_to_root(self.config["path"]) - def get_rendered_dir(self): + def get_rel_rendered_dir(self): path = self.get_rel_kustomize_dir() + if self.index != 0: + path = "%s-%d" % (path, self.index) + return path + + def get_rendered_dir(self): + path = self.get_rel_rendered_dir() rendered_dir = os.path.join(self.deployment_collection.tmpdir, path) return rendered_dir @@ -126,9 +133,9 @@ def resolve_sealed_secrets(self): return sealed_secrets_dir = self.deployment_project.get_sealed_secrets_dir(self.config["path"]) - rel_dir_to_root = self.deployment_project.get_rel_dir_to_root() + rel_rendered_dir = self.get_rel_rendered_dir() rendered_dir = self.get_rendered_dir() - base_source_path = os.path.join(self.deployment_project.sealed_secrets_dir, rel_dir_to_root) + base_source_path = self.deployment_project.sealed_secrets_dir y = yaml_load_file(os.path.join(rendered_dir, "kustomization.yml")) or {} for resource in y.get("resources", []): @@ -141,7 +148,7 @@ def resolve_sealed_secrets(self): base_error = 'Failed to resolve SealedSecret %s' % os.path.normpath(os.path.join(self.deployment_project.dir, resource)) if sealed_secrets_dir is None: raise CommandError('%s\nSealed secrets dir could not be determined.' % base_error) - source_path = os.path.normpath(os.path.join(base_source_path, self.config["path"], rel_dir, sealed_secrets_dir, fname)) + source_path = os.path.normpath(os.path.join(base_source_path, rel_rendered_dir, rel_dir, sealed_secrets_dir, fname)) target_path = os.path.join(rendered_dir, rel_dir, fname) if not os.path.exists(source_path): raise CommandError('%s\n%s not found.\nYou might need to seal it first.' % (base_error, source_path)) From bf17de233685361aabf900e6d88c0414e273c869 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 3 Sep 2021 14:32:01 +0200 Subject: [PATCH 0078/2916] feat: Allow to parametrize kustomizeDirs --- kluctl/deployment/kustomize_deployment.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kluctl/deployment/kustomize_deployment.py b/kluctl/deployment/kustomize_deployment.py index 58eb75366..ca756c9a5 100644 --- a/kluctl/deployment/kustomize_deployment.py +++ b/kluctl/deployment/kustomize_deployment.py @@ -100,6 +100,9 @@ def render(self, executor): os.makedirs(rendered_dir, exist_ok=True) jinja_vars = self.deployment_project.jinja_vars + if "vars" in self.config: + jinja_vars = self.deployment_project.load_jinja_vars_list(self.config["vars"], jinja_vars) + jinja_env = self.build_jinja2_env(jinja_vars) excluded_patterns = self.deployment_project.conf['templateExcludes'].copy() From f718b2a4797bf2ef1a5c4cc8d2cdb1f2d54405b0 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 3 Sep 2021 20:03:49 +0200 Subject: [PATCH 0079/2916] feat: Implement proper hook support (including Helm) --- kluctl/deployment/apply_util.py | 233 +++++++++++++++++++++ kluctl/deployment/deployment_collection.py | 160 +------------- 2 files changed, 240 insertions(+), 153 deletions(-) create mode 100644 kluctl/deployment/apply_util.py diff --git a/kluctl/deployment/apply_util.py b/kluctl/deployment/apply_util.py new file mode 100644 index 000000000..fdafe4f88 --- /dev/null +++ b/kluctl/deployment/apply_util.py @@ -0,0 +1,233 @@ +import logging +import threading +import time + +from kubernetes.client import ApiException +from kubernetes.dynamic.exceptions import ResourceNotFoundError + +from kluctl.diff.managed_fields import remove_non_managed_fields2 +from kluctl.utils.dict_utils import get_dict_value +from kluctl.utils.k8s_object_utils import get_object_ref, get_long_object_name, get_long_object_name_from_ref +from kluctl.utils.status_validation import validate_object +from kluctl.utils.utils import MyThreadPoolExecutor + +logger = logging.getLogger(__name__) + +class ApplyUtil: + def __init__(self, deployment_collection, k8s_cluster, force_apply, replace_on_error, dry_run, abort_on_error): + self.deployment_collection = deployment_collection + self.k8s_cluster = k8s_cluster + self.force_apply = force_apply + self.replace_on_error = replace_on_error + self.dry_run = dry_run + self.abort_on_error = abort_on_error + + self.applied_objects = {} + self.abort_signal = False + self.error_refs = {} + self.mutex = threading.Lock() + + def handle_result(self, applied_object, patch_warnings): + with self.mutex: + ref = get_object_ref(applied_object) + self.applied_objects[ref] = applied_object + self.deployment_collection.add_api_warnings(ref, patch_warnings) + + def handle_error(self, ref, error): + with self.mutex: + self.error_refs[ref] = error + if self.abort_on_error: + self.abort_signal = True + self.deployment_collection.add_api_error(ref, error) + + def had_error(self, ref): + with self.mutex: + return ref in self.error_refs + + def delete_object(self, ref): + self.k8s_cluster.delete_single_object(ref, force_dry_run=self.dry_run, ignore_not_found=True) + + def apply_object(self, x): + logger.debug(f" {get_long_object_name(x)}") + + if not self.force_apply: + x2 = remove_non_managed_fields2(x, self.deployment_collection.remote_objects) + else: + x2 = x + + try: + r, patch_warnings = self.k8s_cluster.patch_object(x2, force_dry_run=self.dry_run, force_apply=True) + self.handle_result(r, patch_warnings) + except ResourceNotFoundError as e: + ref = get_object_ref(x) + self.handle_error(ref, self.k8s_cluster.get_status_message(e)) + except ApiException as e: + ref = get_object_ref(x) + if not self.replace_on_error: + self.handle_error(ref, self.k8s_cluster.get_status_message(e)) + return + logger.info("Patching %s failed, retrying by deleting and re-applying" % + get_long_object_name_from_ref(ref)) + try: + self.k8s_cluster.delete_single_object(ref, force_dry_run=self.dry_run, ignore_not_found=True) + if not self.dry_run and not self.k8s_cluster.dry_run: + r, patch_warnings = self.k8s_cluster.patch_object(x, force_apply=True) + self.handle_result(r, patch_warnings) + else: + self.handle_result(x, []) + except ApiException as e2: + self.handle_error(ref, self.k8s_cluster.get_status_message(e2)) + + def wait_object(self, ref): + start_time = time.time() + did_log = False + logger.debug("Starting wait for hook %s" % get_long_object_name_from_ref(ref)) + while True: + o, _ = self.k8s_cluster.get_single_object(ref) + if o is None: + self.handle_error(ref, "Object disappeared while waiting for it to become ready") + return False + v = validate_object(o) + if not v.errors: + return True + time.sleep(1) + if time.time() - start_time > 5 and not did_log: + logger.info("Waiting for for hook %s to get ready..." % get_long_object_name_from_ref(ref)) + did_log = True + + def run_hooks(self, d, hook): + l = self.get_sorted_hooks_list(d.objects) + l = [x for x in l if hook in x[1]] + + for x, hooks, weight, delete_policy in l: + if self.abort_signal: + return + if "before-hook-creation" in delete_policy: + self.delete_object(get_object_ref(x)) + + for x, hooks, weight, delete_policy in l: + if self.abort_signal: + return + self.apply_object(x) + + wait_results = {} + for x, hooks, weight, delete_policy in l: + if self.abort_signal: + return + ref = get_object_ref(x) + if self.had_error(ref): + continue + wait_results[ref] = self.wait_object(ref) + + for x, hooks, weight, delete_policy in reversed(l): + ref = get_object_ref(x) + if ref not in wait_results: + continue + if wait_results[ref]: + if "hook-succeeded" in delete_policy: + self.delete_object(ref) + else: + if "hook-failed" in delete_policy: + self.delete_object(ref) + + def apply_kustomize_deployment(self, d): + inital_deploy = True + for o in d.objects: + if get_object_ref(o) in self.deployment_collection.remote_objects: + inital_deploy = False + break + + if inital_deploy: + self.run_hooks(d, "pre-deploy-initial") + else: + self.run_hooks(d, "pre-deploy") + + for o in d.objects: + if self.get_hooks(o)[0]: + continue + self.apply_object(o) + + if inital_deploy: + self.run_hooks(d, "post-deploy-initial") + else: + self.run_hooks(d, "post-deploy") + + def apply_deployments(self): + logger.info("Running server-side apply for all objects%s", self.k8s_cluster.get_dry_run_suffix(self.dry_run)) + + futures = [] + + def finish_futures(): + for f in futures: + f.result() + + with MyThreadPoolExecutor(max_workers=16) as executor: + previous_was_barrier = False + for d in self.deployment_collection.deployments: + if self.abort_signal: + break + + if previous_was_barrier: + logger.info("Waiting on barrier...") + finish_futures() + previous_was_barrier = get_dict_value(d.config, "barrier", False) + + include = d.check_inclusion_for_deploy() + if "path" not in d.config: + continue + if not include: + logger.info("Skipping kustomizeDir %s" % d.get_rel_kustomize_dir()) + continue + logger.info("Applying kustomizeDir '%s' with %d objects" % (d.get_rel_kustomize_dir(), len(d.objects))) + + f = executor.submit(self.apply_kustomize_deployment, d) + futures.append(f) + + finish_futures() + + def get_hooks(self, o): + def get_list(path): + s = get_dict_value(o, path, "") + s = s.split(",") + s = [x for x in s if x != ""] + return s + + hooks = get_list("metadata.annotations.kluctl\\.io/hook") + + + helm_hooks = get_dict_value(o, "metadata.annotations.helm\\.sh/hook", "").split(",") + if "pre-install" in helm_hooks: + hooks.append("pre-deploy-initial") + if "pre-upgrade" in helm_hooks: + hooks.append("pre-deploy") + if "post-install" in helm_hooks: + hooks.append("post-deploy-initial") + if "post-upgrade" in helm_hooks: + hooks.append("post-deploy") + if "pre-delete" in helm_hooks: + hooks.append("pre-delete") + if "post-delete" in helm_hooks: + hooks.append("post-delete") + hooks = set(hooks) + + weight = get_dict_value(o, "metadata.annotations.kluctl\\.io/hook-weight") + if weight is None: + weight = get_dict_value(o, "metadata.annotations.helm\\.sh/hook-weight") + if weight is None: + weight = 0 + + delete_policy = get_list("metadata.annotations.kluctl\\.io/hook-delete-policy") + delete_policy += get_list("metadata.annotations.helm\\.sh/hook-delete-policy") + if not delete_policy: + delete_policy = ["before-hook-creation"] + delete_policy = set(delete_policy) + + return hooks, weight, delete_policy + + def get_sorted_hooks_list(self, l): + ret = [] + for x in l: + hooks, weight, delete_policy = self.get_hooks(x) + ret.append((x, hooks, weight, delete_policy)) + ret.sort(key=lambda x: x[2]) + return ret diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index fd9461961..04cfa14c6 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -1,16 +1,14 @@ import dataclasses import itertools import logging -from concurrent.futures import Future -from typing import Optional +import os -from kubernetes.client import ApiException from kubernetes.dynamic.exceptions import ResourceNotFoundError, ConflictError +from kluctl.deployment.apply_util import ApplyUtil from kluctl.deployment.images import SeenImages from kluctl.deployment.kustomize_deployment import KustomizeDeployment from kluctl.diff.k8s_diff import deep_diff_object -from kluctl.diff.managed_fields import remove_non_managed_fields2 from kluctl.diff.normalize import normalize_object from kluctl.utils.dict_utils import copy_dict, get_dict_value from kluctl.utils.k8s_delete_utils import find_objects_for_delete @@ -279,139 +277,18 @@ def find_purge_objects(self, k8s_cluster): excluded_objects = list(self.local_objects_by_ref().keys()) return find_objects_for_delete(k8s_cluster, labels, self.inclusion, excluded_objects) - def do_patch(self, k8s_cluster, force_apply, replace_on_error, dry_run, abort_on_error): - logger.info("Running server-side apply for all objects%s", k8s_cluster.get_dry_run_suffix(dry_run)) - - jobs = [] - - @dataclasses.dataclass(init=False) - class Job: - object: dict - future: Future - result_object: Optional[dict] - result_exception: Optional[Exception] - done: bool = False - - def add_job(o, f): - job = Job() - job.object = o - job.future = f - jobs.append(job) - - def finish_jobs(): - for job in jobs: - if job.done: - continue - job.result_exception = job.future.exception() - if job.result_exception is None: - job.result = job.future.result() - job.done = True - - with MyThreadPoolExecutor(max_workers=16) as executor: - previous_was_barrier = False - for d in self.deployments: - if self.api_errors and abort_on_error: - break - - if previous_was_barrier: - logger.info("Waiting on barrier...") - finish_jobs() - previous_was_barrier = get_dict_value(d.config, "barrier", False) - - include = d.check_inclusion_for_deploy() - if "path" not in d.config: - continue - if not include: - logger.info("Skipping kustomizeDir %s" % d.get_rel_kustomize_dir()) - continue - logger.info("Applying kustomizeDir '%s' with %d objects" % (d.get_rel_kustomize_dir(), len(d.objects))) - - for x in d.objects: - if self.api_errors and abort_on_error: - break - logger.debug(f" {get_long_object_name(x)}") - - # replaced means it is deleted and then re-created - is_replaced = self.is_replace_needed(x) - - if not force_apply and not is_replaced: - x2 = remove_non_managed_fields2(x, self.remote_objects) - else: - x2 = x - - if (dry_run or k8s_cluster.dry_run) and is_replaced: - # The necessary delete was not really performed in case we are in dry_run mode - def dummy(x3): - # let's pretend that we applied it - return x3, [] - f = executor.submit(dummy, x2) - else: - f = executor.submit(k8s_cluster.patch_object, x2, force_dry_run=dry_run, force_apply=True) - add_job(x2, f) - finish_jobs() - - applied_objects = {} - for job in jobs: - if job.result_exception is not None: - try: - raise job.result_exception - except ResourceNotFoundError as e: - ref = get_object_ref(job.object) - self.add_api_error(ref, k8s_cluster.get_status_message(e)) - except ApiException as e: - ref = get_object_ref(job.object) - if not replace_on_error: - self.add_api_error(ref, k8s_cluster.get_status_message(e)) - continue - logger.info("Patching %s failed, retrying by deleting and re-applying" % - get_long_object_name_from_ref(ref)) - try: - k8s_cluster.delete_single_object(ref, force_dry_run=dry_run, ignore_not_found=True) - if not dry_run and not k8s_cluster.dry_run: - r, patch_warnings = k8s_cluster.patch_object(job.object, force_apply=True) - # We must use the ref from the applied object as k8s might remove the namespace field - ref = get_object_ref(r) - applied_objects[ref] = r - self.add_api_warnings(ref, patch_warnings) - else: - applied_objects[ref] = job.object - except ApiException as e2: - self.add_api_error(ref, k8s_cluster.get_status_message(e2)) - continue - - r, patch_warnings = job.result - # We must use the ref from the applied object as k8s might remove the namespace field - ref = get_object_ref(r) - applied_objects[ref] = r - self.add_api_warnings(ref, patch_warnings) - - return applied_objects + def do_apply(self, k8s_cluster, force_apply, replace_on_error, dry_run, abort_on_error): + apply_util = ApplyUtil(self, k8s_cluster, force_apply, replace_on_error, dry_run, abort_on_error) + apply_util.apply_deployments() + return apply_util.applied_objects def do_deploy(self, k8s_cluster, force_apply, replace_on_error, dry_run, abort_on_error): self.update_remote_objects(k8s_cluster) - replaced_objects = self.find_replaced_objects(k8s_cluster) - if replaced_objects: - futures = [] - with MyThreadPoolExecutor(max_workers=16) as executor: - logger.info("Deleting %d objects which need replacement (delete+create)" % len(replaced_objects)) - for o in replaced_objects: - ref = get_object_ref(o) - f = executor.submit(k8s_cluster.delete_single_object, ref, force_dry_run=dry_run, ignore_not_found=True) - futures.append((ref, f)) - for ref, f in futures: - try: - f.result() - except Exception as e: - self.add_api_error(ref, str(e)) - logger.error("Failed to delete %s. %s" % (get_long_object_name_from_ref(ref), e)) - if abort_on_error and self.api_errors: - return {} - # TODO remove this self.migrate_to_new_manager(k8s_cluster) - return self.do_patch(k8s_cluster, force_apply, replace_on_error, dry_run, abort_on_error) + return self.do_apply(k8s_cluster, force_apply, replace_on_error, dry_run, abort_on_error) def do_diff(self, k8s_cluster, applied_objects, ignore_tags, ignore_labels, ignore_annotations, ignore_order): diff_objects = {} @@ -457,29 +334,6 @@ def do_diff(self, k8s_cluster, applied_objects, ignore_tags, ignore_labels, igno return new_objects, changed_objects - def is_replace_needed(self, o): - hook = get_dict_value(o, "metadata.annotations.helm\\.sh/hook") - if not hook: - return False - if all(h in ["pre-install", "post-install", "pre-delete", "post-delete", "pre-upgrade", "post-upgrade", "pre-rollback", "post-rollback", "test"] for h in hook.split(",")): - return True - return False - - def find_replaced_objects(self, k8s_cluster): - self.render_deployments() - self.build_kustomize_objects(k8s_cluster) - ret = [] - for d in self.deployments: - for o in d.objects: - ref = get_object_ref(o) - if ref not in self.remote_objects: - continue - if not d.check_inclusion_for_deploy(): - continue - if self.is_replace_needed(o): - ret.append(o) - return ret - def find_rendered_images(self): ret = {} for d in self.deployments: From 43eb64d5107c27cce9731d1b7a9dc54367ed0279 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 3 Sep 2021 20:04:11 +0200 Subject: [PATCH 0080/2916] feat: Reimplement validate_object based on the same rules as found in Helm --- kluctl/utils/status_validation.py | 199 ++++++++++++++++++++++-------- 1 file changed, 146 insertions(+), 53 deletions(-) diff --git a/kluctl/utils/status_validation.py b/kluctl/utils/status_validation.py index 3499922fd..8d583156b 100644 --- a/kluctl/utils/status_validation.py +++ b/kluctl/utils/status_validation.py @@ -1,5 +1,6 @@ import dataclasses +from kluctl.utils.dict_utils import get_dict_value from kluctl.utils.k8s_object_utils import ObjectRef, get_object_ref RESULT_ANNOTATION = "validate-result.kluctl.io/" @@ -22,60 +23,152 @@ def validate_object(o): return result ref = get_object_ref(o) status = o["status"] - if o["kind"] in ["Deployment", "StatefulSet", "ZookeeperCluster"]: - if "readyReplicas" not in status: - result.errors.append(ValidateResultItem(ref, reason="field-not-found", message="readyReplicas not in status yet")) - elif "replicas" not in status: - result.errors.append(ValidateResultItem(ref, reason="field-not-found", message="replicas not in status yet")) - elif status["readyReplicas"] < status["replicas"]: - result.errors.append(ValidateResultItem(ref, reason="not-ready", message="readyReplicas (%d) is less then replicas (%d)" % (status["readyReplicas"], status["replicas"]))) - elif o["kind"] == "DaemonSet": - if "numberReady" not in status: - result.errors.append(ValidateResultItem(ref, reason="field-not-found", message="numberReady not in status yet")) - elif "desiredNumberScheduled" not in status: - result.errors.append(ValidateResultItem(ref, reason="field-not-found", message="desiredNumberScheduled not in status yet")) - elif status["numberReady"] < status["desiredNumberScheduled"]: - result.errors.append(ValidateResultItem(ref, reason="not-ready", message="numberReady (%d) is less then desiredNumberScheduled (%d)" % (status["numberReady"], status["desiredNumberScheduled"]))) - elif o["kind"] == "Job": - for c in status.get("conditions", []): - if c["type"] == "Failed" and c["status"] == "True": - result.errors.append(ValidateResultItem(ref, reason=c.get("reason", "failed"), message=c.get("message", "N/A"))) - if status.get("active", 0) != 0: - result.errors.append(ValidateResultItem(ref, reason=status.get("reason", "not-completed"), message="Job not finished yet. active=%d" % status["active"])) - elif o["kind"] == "Kafka": - ready_found = False + + class ValidateFailed(Exception): + pass + + def add_error(reason, message): + result.errors.append(ValidateResultItem(ref, reason=reason, message=message)) + + def add_warning(reason, message): + result.warnings.append(ValidateResultItem(ref, reason=reason, message=message)) + + def find_conditions(type, do_error, do_raise): + ret = [] for c in status.get("conditions", []): - if c["type"] == "Ready": - ready_found = True - if c["status"] != "True": - result.errors.append(ValidateResultItem(ref, reason=c.get("reason", "not-ready"), message=c.get("message", "N/A"))) - elif c["type"] == "Warning": - result.warnings.append(ValidateResultItem(ref, reason=c.get("reason", "not-ready"), message=c.get("message", "N/A"))) - if not ready_found: - result.errors.append(ValidateResultItem(ref, reason="not-ready", message="Ready condition not found")) - elif o["kind"] == "Elasticsearch": - if "phase" not in status: - result.errors.append(ValidateResultItem(ref, reason="field-not-found", message="phase not in status yet")) - elif status["phase"] != "Ready": - result.errors.append(ValidateResultItem(ref, reason="not-ready", message="phase is %s" % status["phase"])) - elif "health" not in status: - result.errors.append(ValidateResultItem(ref, reason="field-not-found", message="health not in status yet")) - elif status["health"] == "yellow": - result.warnings.append(ValidateResultItem(ref, reason="health-yellow", message="health is yellow")) - elif status["health"] != "green": - result.errors.append(ValidateResultItem(ref, reason="not-ready", message="health is %s" % status["health"])) - elif o["kind"] == "Kibana": - if "health" not in status: - result.errors.append(ValidateResultItem(ref, reason="field-not-found", message="health not in status yet")) - elif status["health"] == "yellow": - result.warnings.append(ValidateResultItem(ref, reason="health-yellow", message="health is yellow")) - elif status["health"] != "green": - result.errors.append(ValidateResultItem(ref, reason="not-ready", message="health is %s" % status["health"])) - elif o["kind"] == "postgresql": - if "PostgresClusterStatus" not in status: - result.errors.append(ValidateResultItem(ref, reason="field-not-found", message="PostgresClusterStatus not in status yet")) - elif status["PostgresClusterStatus"] != "Running": - result.errors.append(ValidateResultItem(ref, reason="not-ready", message="PostgresClusterStatus is %s" % status["PostgresClusterStatus"])) + if c["type"] == type: + ret.append((c.get("status"), c.get("reason"), c.get("message"))) + if not ret and do_error: + add_error("condition-not-found", "%s condition not in status" % type) + if do_raise: + raise ValidateFailed() + return ret + + def get_condition(type, do_error, do_raise): + c = find_conditions(type, do_error, do_raise) + if not c: + return None, None, None + if len(c) != 1: + add_error("condition-not-one", "%s condition found more then once" % type) + if do_raise: + raise ValidateFailed() + return c[0] + + def get_field(field, do_error, do_raise, default=None): + v = get_dict_value(status, field) + if v is None and do_error: + add_error("field-not-found", "%s field not in status or empty" % field) + if do_raise: + raise ValidateFailed() + if v is None: + return default + return v + + def parse_int_or_percent(v): + if isinstance(v, int): + return v, False + return int(v.replace("%", "")), True + + def value_from_int_or_percent(v, total): + v, is_percent = parse_int_or_percent(v) + if is_percent: + v = v * total / 100 + return int(v) + + try: + if o["kind"] == "Pod": + s, r, m = get_condition("Ready", True, True) + if s != "True": + add_error(r or "not-ready", m or "Not ready") + elif o["kind"] == "Job": + s, r, m = get_condition("Failed", False, False) + if s == "True": + add_error(r or "failed", m or "N/A") + else: + s, r, m = get_condition("Complete", True, True) + if s != "True": + add_error(r or "not-completed", m or "Not completed") + elif o["kind"] in ["Deployment", "ZookeeperCluster"]: + ready_replicas = get_field("readyReplicas", True, True) + replicas = get_field("replicas", True, True) + if ready_replicas < replicas: + add_error("not-ready", "readyReplicas (%d) is less then replicas (%d)" % (ready_replicas, replicas)) + elif o["kind"] == "PersistentVolumeClaim": + phase = get_field("phase", True, True) + if phase != "Bound": + add_error("not-bound", "Volume is not bound") + elif o["kind"] == "Service": + svc_type = get_dict_value(o, "spec.type") + if svc_type != "ExternalName": + if get_dict_value(o, "spec.clusterIP", "") == "": + add_error("no-cluster-ip", "Service does not have a cluster IP") + elif svc_type == "LoadBalancer": + if len(get_dict_value(o, "spec.externalIPs", [])) == 0: + get_field("loadBalancer.ingress", True, True) + elif o["kind"] == "DaemonSet": + if get_dict_value(o, "spec.updateStrategy.type") == "RollingUpdate": + updated_number_scheduled = get_field("updatedNumberScheduled", True, True) + desired_number_scheduled = get_field("desiredNumberScheduled", True, True) + if updated_number_scheduled != desired_number_scheduled: + add_error("not-ready", "DaemonSet is not ready. %d out of %d expected pods have been scheduled" % (updated_number_scheduled, desired_number_scheduled)) + else: + max_unavailable = get_dict_value(o, "spec.updateStrategy.maxUnavailable", 1) + try: + max_unavailable = value_from_int_or_percent(max_unavailable, desired_number_scheduled) + except: + max_unavailable = desired_number_scheduled + expected_ready = desired_number_scheduled - max_unavailable + number_ready = get_field("numberReady", True, True) + if number_ready < expected_ready: + add_error("not-ready", "DaemonSet is not ready. %d out of %d expected pods are ready" % (number_ready, expected_ready)) + elif o["kind"] == "CustomResourceDefinition": + # This is based on how Helm check for ready CRDs. + # See https://github.com/helm/helm/blob/249d1b5fb98541f5fb89ab11019b6060d6b169f1/pkg/kube/ready.go#L342 + s, r, m = get_condition("Established", False, False) + if s != "True": + s, r, m = get_condition("NamesAccepted", True, True) + if s != "False": + add_error("not-ready", "CRD is not ready") + elif o["kind"] == "StatefulSet": + if get_dict_value(o, "spec.updateStrategy.type") == "RollingUpdate": + partition = get_dict_value(o, "spec.updateStrategy.rollingUpdate.partition", 0) + replicas = get_dict_value(o, "spec.replicas", 1) + updated_replicas = get_field("updatedReplicas", True, True) + expected_replicas = replicas - partition + if updated_replicas != expected_replicas: + add_error("not-ready", "StatefulSet is not ready. %d out of %d expected pods have been scheduled" % (updated_replicas, expected_replicas)) + else: + ready_replicas = get_field("readyReplicas", True, True) + if ready_replicas != replicas: + add_error("not-ready", "StatefulSet is not ready. %d out of %d expected pods are ready" % (ready_replicas, replicas)) + elif o["kind"] == "Kafka": + for s, r, m in find_conditions("Warning", False, False): + add_warning(r or "warning", m or "N/A") + s, r, m = get_condition("Ready", True, True) + if s != "True": + add_error(r or "not-ready", m or "N/A") + elif o["kind"] == "Elasticsearch": + phase = get_field("phase", True, True) + if phase != "Ready": + add_error("not-ready", "phase is %s" % phase) + else: + health = get_field("health", True, True) + if health == "yellow": + add_warning("health-yellow", "health is yellow") + elif health != "green": + add_error("not-ready", "health is %s" % health) + elif o["kind"] == "Kibana": + health = get_field("health", True, True) + if health == "yellow": + add_warning("health-yellow", "health is yellow") + elif health != "green": + add_error("not-ready", "health is %s" % health) + elif o["kind"] == "postgresql": + pstatus = get_field("PostgresClusterStatus", True, True) + if pstatus != "Running": + add_error("not-ready", "PostgresClusterStatus is %s" % pstatus) + except ValidateFailed: + pass for k, v in o["metadata"].get("annotations", {}).items(): if not k.startswith(RESULT_ANNOTATION): From fddd69f4c3fa9c694ce9dcbbb3204435d5c0cac5 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 6 Sep 2021 14:52:24 +0200 Subject: [PATCH 0081/2916] fix: Fix hook weight parsing --- kluctl/deployment/apply_util.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kluctl/deployment/apply_util.py b/kluctl/deployment/apply_util.py index fdafe4f88..6ab376b0e 100644 --- a/kluctl/deployment/apply_util.py +++ b/kluctl/deployment/apply_util.py @@ -214,7 +214,8 @@ def get_list(path): if weight is None: weight = get_dict_value(o, "metadata.annotations.helm\\.sh/hook-weight") if weight is None: - weight = 0 + weight = "0" + weight = int(weight) delete_policy = get_list("metadata.annotations.kluctl\\.io/hook-delete-policy") delete_policy += get_list("metadata.annotations.helm\\.sh/hook-delete-policy") From 892245ae63df9e44e70ef47d8980a9d75b694000 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aljoscha=20P=C3=B6rtner?= Date: Mon, 6 Sep 2021 20:54:14 +0200 Subject: [PATCH 0082/2916] feat(examples): add example with external repos --- examples/simple-with-external-repos/.kluctl.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 examples/simple-with-external-repos/.kluctl.yml diff --git a/examples/simple-with-external-repos/.kluctl.yml b/examples/simple-with-external-repos/.kluctl.yml new file mode 100644 index 000000000..5409b25c4 --- /dev/null +++ b/examples/simple-with-external-repos/.kluctl.yml @@ -0,0 +1,16 @@ +clusters: + project: + url: https://github.com/AljoschaP/kluctl-external-clusters + ref: main + subdir: clusters + +deployment: + project: + url: https://github.com/AljoschaP/kluctl-external-deployments + ref: main + +targets: + - name: dev-external + cluster: kind + args: + environment: dev-external \ No newline at end of file From 5c866f2e2f1b5287245beea28fcb9b58d4e8d71a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aljoscha=20P=C3=B6rtner?= Date: Mon, 6 Sep 2021 21:01:34 +0200 Subject: [PATCH 0083/2916] docs: add note how to change temp folder --- docs/commands.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/commands.md b/docs/commands.md index d907b508b..abe64b918 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -83,6 +83,7 @@ A few additional environment variables are supported which do not belong to an o 2. `KLUCTL_GIT_TIMEOUT`. Specifies how long to wait on git subprocesses to finish until they are killed. 3. `KLUCTL_NO_THREADS`. Do not use multithreading while performing work. This is only useful for debugging purposes. 4. `KLUCTL_IGNORE_DEBUGGER`. Pretend that there is no debugger attached when automatically deciding if multi-threading should be enabled or not. +5. `TMPDIR, TEMP or TMP`. Because kluctl uses [tempfile](https://docs.python.org/3/library/tempfile.html), you can set the output directory for temporary files (e.g. git-cache, repositories, rendered files) with the aforementioned envs. # Commands The following commands are available: From 619607f5b5a12645b9c366921662933194d27068 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 6 Sep 2021 15:58:04 +0200 Subject: [PATCH 0084/2916] fix: Don't wait for hooks when performing dry-run --- kluctl/deployment/apply_util.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kluctl/deployment/apply_util.py b/kluctl/deployment/apply_util.py index 6ab376b0e..07faf9304 100644 --- a/kluctl/deployment/apply_util.py +++ b/kluctl/deployment/apply_util.py @@ -79,6 +79,9 @@ def apply_object(self, x): self.handle_error(ref, self.k8s_cluster.get_status_message(e2)) def wait_object(self, ref): + if self.dry_run or self.k8s_cluster.dry_run: + return + start_time = time.time() did_log = False logger.debug("Starting wait for hook %s" % get_long_object_name_from_ref(ref)) From c5ee88ef7cfc33dcfac8f2c48dad81b4e5381945 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 7 Sep 2021 12:15:02 +0200 Subject: [PATCH 0085/2916] fix: Only try ControlMaster on non-windows This will most likely not work... --- kluctl/utils/git_utils.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/kluctl/utils/git_utils.py b/kluctl/utils/git_utils.py index 443bd7b81..e0d6674e5 100644 --- a/kluctl/utils/git_utils.py +++ b/kluctl/utils/git_utils.py @@ -4,6 +4,7 @@ import os import re import shutil +import sys from contextlib import contextmanager from tempfile import NamedTemporaryFile from urllib.parse import urlparse @@ -144,11 +145,13 @@ def execute(self, command, **kwargs): g = MyGit(working_dir) ssh_command = "ssh" - ssh_command += " -o 'ControlMaster=auto'" - ssh_command += " -o 'ControlPath=/tmp/kluctl_control_master-%r@%h-%p'" - ssh_command += " -o 'ControlPersist=5m'" ssh_command += " -o 'StrictHostKeyChecking=no'" + if sys.platform != "win32": + ssh_command += " -o 'ControlMaster=auto'" + ssh_command += " -o 'ControlPath=/tmp/kluctl_control_master-%r@%h-%p'" + ssh_command += " -o 'ControlPersist=5m'" + if credentials is not None and credentials.username is not None: url = add_username_to_url(url, credentials.username) From cae63af8ce05210810a4601a7969d04b522c12d4 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 7 Sep 2021 12:16:22 +0200 Subject: [PATCH 0086/2916] fix: Honor the GIT_SSH environment variable to properly support plink/peagent --- kluctl/utils/git_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kluctl/utils/git_utils.py b/kluctl/utils/git_utils.py index e0d6674e5..5e6a3160e 100644 --- a/kluctl/utils/git_utils.py +++ b/kluctl/utils/git_utils.py @@ -144,7 +144,7 @@ def execute(self, command, **kwargs): g = MyGit(working_dir) - ssh_command = "ssh" + ssh_command = os.environ.get("GIT_SSH", "ssh") ssh_command += " -o 'StrictHostKeyChecking=no'" if sys.platform != "win32": From eb870d46d570b44d505fdaeadf69c20148bcc541 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 7 Sep 2021 12:47:28 +0200 Subject: [PATCH 0087/2916] docs: Update documentation about hooks --- docs/helm-integration.md | 23 ++++++++++++++++++----- docs/hooks.md | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 5 deletions(-) create mode 100644 docs/hooks.md diff --git a/docs/helm-integration.md b/docs/helm-integration.md index 0a63d5038..b704d24a6 100644 --- a/docs/helm-integration.md +++ b/docs/helm-integration.md @@ -15,11 +15,24 @@ The resulting rendered yaml is then referred by your `kustomization.yml`, from w [kustomize integration](kustomize-integration.md) takes over. This means, that you can perform all desired customization (patches, namespace override, ...) as if you provided your own resources via yaml files. -### Limitations - -The way the Helm integration is implemented might result in incompatibilities in behaviour between Helm and kluctl, -especially when it comes to [Helm Hooks](https://helm.sh/docs/topics/charts_hooks/). These are currently implemented in -a very naive way, meaning that hooks are simply recreated on each deployment. +### Helm hooks + +[Helm Hooks](https://helm.sh/docs/topics/charts_hooks/) are implemented by mapping them to [kluctl hooks](./hooks.md), +based on the following mapping table: + +| Helm hook | kluctl hook | +|---------------|---------------------| +| pre-install | pre-deploy-initial | +| post-install | post-deploy-initial | +| pre-delete | Not supported | +| post-delete | Not supported | +| pre-upgrade | pre-deploy | +| post-upgrade | post-deploy | +| pre-rollback | Not supported | +| post-rollback | Not supported | +| test | Not supported | + +Please note that this is a best effort approach and not 100% compatible to how Helm would run hooks. ## helm-chart.yml diff --git a/docs/hooks.md b/docs/hooks.md new file mode 100644 index 000000000..74c806595 --- /dev/null +++ b/docs/hooks.md @@ -0,0 +1,38 @@ +# Hooks + +kluctl supports hooks in a similar fashion as known from Helm Charts. Hooks are executed/deployed before and/or after the +actual deployment of a kustomize deployment. + +To mark a resource as a hook, add the `kluctl.io/hook` annotation to a resource. The value of the annotation must be +a comma separated list of hook names. Possible value are described in the next chapter. + +## Hook types + +| Hook Type | Description | +|---|---| +| pre-deploy-initial | Executed right before the initial deployment is performed.
See below for a description on what "initial" means | +| post-deploy-initial | Executed right after the initial deployment is performed.
See below for a description on what "initial" means | +| pre-deploy | Executed right before a deployment is performed.
This also includes the initial deployment | +| post-deploy | Executed right after a deployment is performed.
This also includes the initial deployment | + +A deployment is considered to be an "initial" deployment if none of the resources related to the current kustomize +deployment are found on the cluster at the time of deployment. + +pre-deploy and post-deploy are ALWAYS executed, not considering the "initial" state of the deployment. + +## Hook deletion + +Hook resources are by default deleted right before creation (if they already existed before). This behavior can be +changed by setting the `kluctl.io/hook-delete-policy` to a comma separated list of the following values: + +| Policy | Description | +|---|---| +| before-hook-creation | The default behavior, which means that the hook resource is deleted right before (re-)creation. | +| hook-succeeded | Delete the hook resource directly after it got "ready" | +| hook-failed | Delete the hook resource when it failed to get "ready" | + +## Hook readiness + +After each deployment/execution of the hooks that belong to a deployment stage (before/after deployment), kluctl +waits for the hook resources to become "ready". Readiness depends on the resource kind, e.g. for a Job, kluctl would +wait until it finishes successfully. From cfe86cc97079489051e54c068aae7b22b63a7ea9 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 7 Sep 2021 12:48:01 +0200 Subject: [PATCH 0088/2916] fix: Better differentiation between non-readiness and actual errors --- kluctl/deployment/apply_util.py | 7 ++-- kluctl/deployment/deployment_collection.py | 2 +- kluctl/utils/status_validation.py | 38 +++++++++++++--------- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/kluctl/deployment/apply_util.py b/kluctl/deployment/apply_util.py index 07faf9304..0ec74ba63 100644 --- a/kluctl/deployment/apply_util.py +++ b/kluctl/deployment/apply_util.py @@ -90,9 +90,12 @@ def wait_object(self, ref): if o is None: self.handle_error(ref, "Object disappeared while waiting for it to become ready") return False - v = validate_object(o) - if not v.errors: + v = validate_object(o, False) + if v.ready: return True + if v.errors: + return False + time.sleep(1) if time.time() - start_time > 5 and not did_log: logger.info("Waiting for for hook %s to get ready..." % get_long_object_name_from_ref(ref)) diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index 04cfa14c6..9a9bfcad9 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -257,7 +257,7 @@ def validate(self, k8s_cluster): if not remote_object: result.errors.append(ValidateResultItem(ref=ref, reason="not-found", message="Object not found")) continue - r = validate_object(remote_object) + r = validate_object(remote_object, True) result.errors += r.errors result.warnings += r.warnings result.results += r.results diff --git a/kluctl/utils/status_validation.py b/kluctl/utils/status_validation.py index 8d583156b..7b21cc284 100644 --- a/kluctl/utils/status_validation.py +++ b/kluctl/utils/status_validation.py @@ -13,11 +13,12 @@ class ValidateResultItem: @dataclasses.dataclass class ValidateResult: + ready: bool = True warnings: list = dataclasses.field(default_factory=list) errors: list = dataclasses.field(default_factory=list) results: list = dataclasses.field(default_factory=list) -def validate_object(o): +def validate_object(o, not_ready_is_error): result = ValidateResult() if "status" not in o: return result @@ -33,6 +34,13 @@ def add_error(reason, message): def add_warning(reason, message): result.warnings.append(ValidateResultItem(ref, reason=reason, message=message)) + def add_not_ready(reason, message): + if not_ready_is_error: + add_error(reason, message) + else: + add_warning(reason, message) + result.ready = False + def find_conditions(type, do_error, do_raise): ret = [] for c in status.get("conditions", []): @@ -79,7 +87,7 @@ def value_from_int_or_percent(v, total): if o["kind"] == "Pod": s, r, m = get_condition("Ready", True, True) if s != "True": - add_error(r or "not-ready", m or "Not ready") + add_not_ready(r or "not-ready", m or "Not ready") elif o["kind"] == "Job": s, r, m = get_condition("Failed", False, False) if s == "True": @@ -87,16 +95,16 @@ def value_from_int_or_percent(v, total): else: s, r, m = get_condition("Complete", True, True) if s != "True": - add_error(r or "not-completed", m or "Not completed") + add_not_ready(r or "not-completed", m or "Not completed") elif o["kind"] in ["Deployment", "ZookeeperCluster"]: ready_replicas = get_field("readyReplicas", True, True) replicas = get_field("replicas", True, True) if ready_replicas < replicas: - add_error("not-ready", "readyReplicas (%d) is less then replicas (%d)" % (ready_replicas, replicas)) + add_not_ready("not-ready", "readyReplicas (%d) is less then replicas (%d)" % (ready_replicas, replicas)) elif o["kind"] == "PersistentVolumeClaim": phase = get_field("phase", True, True) if phase != "Bound": - add_error("not-bound", "Volume is not bound") + add_not_ready("not-bound", "Volume is not bound") elif o["kind"] == "Service": svc_type = get_dict_value(o, "spec.type") if svc_type != "ExternalName": @@ -104,13 +112,13 @@ def value_from_int_or_percent(v, total): add_error("no-cluster-ip", "Service does not have a cluster IP") elif svc_type == "LoadBalancer": if len(get_dict_value(o, "spec.externalIPs", [])) == 0: - get_field("loadBalancer.ingress", True, True) + add_not_ready("loadBalancer.ingress", True, True) elif o["kind"] == "DaemonSet": if get_dict_value(o, "spec.updateStrategy.type") == "RollingUpdate": updated_number_scheduled = get_field("updatedNumberScheduled", True, True) desired_number_scheduled = get_field("desiredNumberScheduled", True, True) if updated_number_scheduled != desired_number_scheduled: - add_error("not-ready", "DaemonSet is not ready. %d out of %d expected pods have been scheduled" % (updated_number_scheduled, desired_number_scheduled)) + add_not_ready("not-ready", "DaemonSet is not ready. %d out of %d expected pods have been scheduled" % (updated_number_scheduled, desired_number_scheduled)) else: max_unavailable = get_dict_value(o, "spec.updateStrategy.maxUnavailable", 1) try: @@ -120,7 +128,7 @@ def value_from_int_or_percent(v, total): expected_ready = desired_number_scheduled - max_unavailable number_ready = get_field("numberReady", True, True) if number_ready < expected_ready: - add_error("not-ready", "DaemonSet is not ready. %d out of %d expected pods are ready" % (number_ready, expected_ready)) + add_not_ready("not-ready", "DaemonSet is not ready. %d out of %d expected pods are ready" % (number_ready, expected_ready)) elif o["kind"] == "CustomResourceDefinition": # This is based on how Helm check for ready CRDs. # See https://github.com/helm/helm/blob/249d1b5fb98541f5fb89ab11019b6060d6b169f1/pkg/kube/ready.go#L342 @@ -136,37 +144,37 @@ def value_from_int_or_percent(v, total): updated_replicas = get_field("updatedReplicas", True, True) expected_replicas = replicas - partition if updated_replicas != expected_replicas: - add_error("not-ready", "StatefulSet is not ready. %d out of %d expected pods have been scheduled" % (updated_replicas, expected_replicas)) + add_not_ready("not-ready", "StatefulSet is not ready. %d out of %d expected pods have been scheduled" % (updated_replicas, expected_replicas)) else: ready_replicas = get_field("readyReplicas", True, True) if ready_replicas != replicas: - add_error("not-ready", "StatefulSet is not ready. %d out of %d expected pods are ready" % (ready_replicas, replicas)) + add_not_ready("not-ready", "StatefulSet is not ready. %d out of %d expected pods are ready" % (ready_replicas, replicas)) elif o["kind"] == "Kafka": for s, r, m in find_conditions("Warning", False, False): add_warning(r or "warning", m or "N/A") s, r, m = get_condition("Ready", True, True) if s != "True": - add_error(r or "not-ready", m or "N/A") + add_not_ready(r or "not-ready", m or "N/A") elif o["kind"] == "Elasticsearch": phase = get_field("phase", True, True) if phase != "Ready": - add_error("not-ready", "phase is %s" % phase) + add_not_ready("not-ready", "phase is %s" % phase) else: health = get_field("health", True, True) if health == "yellow": add_warning("health-yellow", "health is yellow") elif health != "green": - add_error("not-ready", "health is %s" % health) + add_not_ready("not-ready", "health is %s" % health) elif o["kind"] == "Kibana": health = get_field("health", True, True) if health == "yellow": add_warning("health-yellow", "health is yellow") elif health != "green": - add_error("not-ready", "health is %s" % health) + add_not_ready("not-ready", "health is %s" % health) elif o["kind"] == "postgresql": pstatus = get_field("PostgresClusterStatus", True, True) if pstatus != "Running": - add_error("not-ready", "PostgresClusterStatus is %s" % pstatus) + add_not_ready("not-ready", "PostgresClusterStatus is %s" % pstatus) except ValidateFailed: pass From fb98f1c7f58c4ccdb797cbb4c1269bed253ecc42 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 7 Sep 2021 12:58:12 +0200 Subject: [PATCH 0089/2916] fix: wait_object must return True for dry-run --- kluctl/deployment/apply_util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kluctl/deployment/apply_util.py b/kluctl/deployment/apply_util.py index 0ec74ba63..914159c56 100644 --- a/kluctl/deployment/apply_util.py +++ b/kluctl/deployment/apply_util.py @@ -80,7 +80,7 @@ def apply_object(self, x): def wait_object(self, ref): if self.dry_run or self.k8s_cluster.dry_run: - return + return True start_time = time.time() did_log = False From a93aa46c9580a1bdab8f97ec72766a1575e0228a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 7 Sep 2021 13:04:30 +0200 Subject: [PATCH 0090/2916] docs: Fix documentation about initial deployment hooks --- docs/hooks.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/hooks.md b/docs/hooks.md index 74c806595..17134a05d 100644 --- a/docs/hooks.md +++ b/docs/hooks.md @@ -10,15 +10,16 @@ a comma separated list of hook names. Possible value are described in the next c | Hook Type | Description | |---|---| -| pre-deploy-initial | Executed right before the initial deployment is performed.
See below for a description on what "initial" means | -| post-deploy-initial | Executed right after the initial deployment is performed.
See below for a description on what "initial" means | -| pre-deploy | Executed right before a deployment is performed.
This also includes the initial deployment | -| post-deploy | Executed right after a deployment is performed.
This also includes the initial deployment | +| pre-deploy-initial | Executed right before the initial deployment is performed. | +| post-deploy-initial | Executed right after the initial deployment is performed. | +| pre-deploy | Executed right before a non-initial deployment is performed.| +| post-deploy | Executed right after a non-initial deployment is performed. | A deployment is considered to be an "initial" deployment if none of the resources related to the current kustomize deployment are found on the cluster at the time of deployment. -pre-deploy and post-deploy are ALWAYS executed, not considering the "initial" state of the deployment. +If you need to execute hooks for every deployment, independent of its "initial" state, use +`pre-deploy-initial,pre-deploy` to indicate that it should be executed all the time. ## Hook deletion From 918c02fc69b7506b2ddae50ad7e2cad5d9ea791f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 7 Sep 2021 13:41:03 +0200 Subject: [PATCH 0091/2916] refactor: Rename status_validation.py to k8s_status_validation.py --- kluctl/deployment/apply_util.py | 2 +- kluctl/deployment/deployment_collection.py | 2 +- kluctl/utils/{status_validation.py => k8s_status_validation.py} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename kluctl/utils/{status_validation.py => k8s_status_validation.py} (100%) diff --git a/kluctl/deployment/apply_util.py b/kluctl/deployment/apply_util.py index 914159c56..ed70f2494 100644 --- a/kluctl/deployment/apply_util.py +++ b/kluctl/deployment/apply_util.py @@ -8,7 +8,7 @@ from kluctl.diff.managed_fields import remove_non_managed_fields2 from kluctl.utils.dict_utils import get_dict_value from kluctl.utils.k8s_object_utils import get_object_ref, get_long_object_name, get_long_object_name_from_ref -from kluctl.utils.status_validation import validate_object +from kluctl.utils.k8s_status_validation import validate_object from kluctl.utils.utils import MyThreadPoolExecutor logger = logging.getLogger(__name__) diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index 9a9bfcad9..51a6d6981 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -14,7 +14,7 @@ from kluctl.utils.k8s_delete_utils import find_objects_for_delete from kluctl.utils.k8s_object_utils import get_object_ref, get_long_object_name, get_long_object_name_from_ref, \ ObjectRef -from kluctl.utils.status_validation import validate_object, ValidateResult, ValidateResultItem +from kluctl.utils.k8s_status_validation import validate_object, ValidateResult, ValidateResultItem from kluctl.utils.templated_dir import TemplatedDir from kluctl.utils.utils import MyThreadPoolExecutor diff --git a/kluctl/utils/status_validation.py b/kluctl/utils/k8s_status_validation.py similarity index 100% rename from kluctl/utils/status_validation.py rename to kluctl/utils/k8s_status_validation.py From af02b326d09a92a8dafc488301d6b118de860ea5 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 7 Sep 2021 14:11:17 +0200 Subject: [PATCH 0092/2916] refactor: Add do_patch_object/do_finish_futures helpers --- kluctl/deployment/deployment_collection.py | 84 +++++++++++----------- 1 file changed, 43 insertions(+), 41 deletions(-) diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index 51a6d6981..2e5e849ab 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -1,4 +1,5 @@ import dataclasses +import functools import itertools import logging import os @@ -155,35 +156,15 @@ def poke_images(self, k8s_cluster): self.clear_errors_and_warnings() self.render_deployments() self.build_kustomize_objects(k8s_cluster) - self.update_remote_objects(k8s_cluster) - - def do_poke_image(ref, containers_and_images): - o = self.remote_objects.get(ref) - if o is None: - return None - - while True: - o, warnings = k8s_cluster.get_single_object(ref) - if o is None: - return None - o2 = copy_dict(o) - - containers = get_dict_value(o2, "spec.template.spec.containers", []) - for container_name, image in containers_and_images: - for c in containers: - if c["name"] == container_name: - c["image"] = image - - if o == o2: - return o - - try: - result, warnings = k8s_cluster.replace_object(o2) - return result - except ConflictError: - logger.info("Conflict while poking images in %s. Retrying..." % get_long_object_name(o2)) - continue + def do_poke_image(containers_and_images, o): + o = copy_dict(o) + containers = get_dict_value(o, "spec.template.spec.containers", []) + for container_name, image in containers_and_images: + for c in containers: + if c["name"] == container_name: + c["image"] = image + return o all_objects = {} for d in self.deployments: @@ -216,21 +197,10 @@ def do_poke_image(ref, containers_and_images): futures = [] for ref, c in containers_and_images.items(): - f = executor.submit(do_poke_image, ref, c) + f = executor.submit(self.do_patch_object, k8s_cluster, ref, functools.partial(do_poke_image, c)) futures.append((ref, f)) - applied_objects = {} - for ref, f in futures: - e = f.exception() - if e is not None: - self.add_api_error(ref, str(e)) - continue - - o = f.result() - if o is None: - continue - ref = get_object_ref(o) - applied_objects[ref] = o + applied_objects = self.do_finish_futures(futures) new_objects, changed_objects = self.do_diff(k8s_cluster, applied_objects, False, False, False, False) return DeployDiffResult(new_objects=new_objects, changed_objects=changed_objects, @@ -334,6 +304,38 @@ def do_diff(self, k8s_cluster, applied_objects, ignore_tags, ignore_labels, igno return new_objects, changed_objects + def do_patch_object(self, k8s_cluster, ref, callback): + while True: + o, warnings = k8s_cluster.get_single_object(ref) + if o is None: + return None + + o2 = callback(o) + if o == o2: + return o + + try: + result, warnings = k8s_cluster.replace_object(o2) + return result + except ConflictError: + logger.info("Conflict while patching %s. Retrying..." % get_long_object_name(o2)) + continue + + def do_finish_futures(self, futures): + ret = {} + for ref, f in futures: + e = f.exception() + if e is not None: + self.add_api_error(ref, str(e)) + continue + + o = f.result() + if o is None: + continue + ref = get_object_ref(o) + ret[ref] = o + return ret + def find_rendered_images(self): ret = {} for d in self.deployments: From 0c9aa85879de292f420e7094f6fa5ff12a7b938d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 7 Sep 2021 15:05:48 +0200 Subject: [PATCH 0093/2916] fix: Keep calling update_remote_objects to fix diffs --- kluctl/deployment/deployment_collection.py | 1 + 1 file changed, 1 insertion(+) diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index 2e5e849ab..dc743caab 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -156,6 +156,7 @@ def poke_images(self, k8s_cluster): self.clear_errors_and_warnings() self.render_deployments() self.build_kustomize_objects(k8s_cluster) + self.update_remote_objects(k8s_cluster) def do_poke_image(containers_and_images, o): o = copy_dict(o) From dd59e7ce66d609a327fdd0fae5246eb73ddba0c1 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 7 Sep 2021 15:06:03 +0200 Subject: [PATCH 0094/2916] fix: Speed up do_patch_object by reusing remote_objects --- kluctl/deployment/deployment_collection.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index dc743caab..ac2c5a181 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -306,10 +306,15 @@ def do_diff(self, k8s_cluster, applied_objects, ignore_tags, ignore_labels, igno return new_objects, changed_objects def do_patch_object(self, k8s_cluster, ref, callback): + first_call = True while True: - o, warnings = k8s_cluster.get_single_object(ref) + if first_call: + o = self.remote_objects.get(ref) + else: + o, warnings = k8s_cluster.get_single_object(ref) if o is None: return None + first_call = False o2 = callback(o) if o == o2: From a7f6a10885d09c651ed15ae0c6eea49534e1662f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 7 Sep 2021 15:06:32 +0200 Subject: [PATCH 0095/2916] feat: Implement downscale command --- docs/commands.md | 32 +++++++++++++++++++++- kluctl/cli/command_stubs.py | 13 +++++++++ kluctl/cli/commands.py | 11 ++++++++ kluctl/deployment/deployment_collection.py | 23 ++++++++++++++++ kluctl/utils/dict_utils.py | 19 +++++++++++-- kluctl/utils/k8s_downscale_utils.py | 14 ++++++++++ 6 files changed, 108 insertions(+), 4 deletions(-) create mode 100644 kluctl/utils/k8s_downscale_utils.py diff --git a/docs/commands.md b/docs/commands.md index abe64b918..20ec39095 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -313,7 +313,7 @@ In addition, the following arguments are available: ``` -## list-images +## poke-images Usage: kluctl poke-images [OPTIONS] @@ -344,6 +344,36 @@ In addition, the following arguments are available: ``` +## downscale + +Usage: kluctl downscale [OPTIONS] + + Downscale all deployments. + + This command will downscale all Deployments, StatefulSets and CronJobs. + + + +The following sets of arguments are available: +1. [project arguments](#project-arguments) +1. [image arguments](#image-arguments) +1. [inclusion/exclusion arguments](#inclusionexclusion-arguments) + +In addition, the following arguments are available: + +``` + Misc arguments: + -y, --yes Supresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. + --dry-run Performs all kubernetes API calls in dry-run mode. + -o, --output TEXT Specify output format and target file, in the format 'format=path'. Format can + either be 'diff' or 'yaml'. Can be specified multiple times. The actual format for + yaml is currently not documented and subject to change. + --render-output-dir DIRECTORY + Specifies the target directory to render the project into. If omitted, atemporary + directory is used. +``` + + ## render Usage: kluctl render [OPTIONS] diff --git a/kluctl/cli/command_stubs.py b/kluctl/cli/command_stubs.py index f0beadcb4..d4e4f9764 100644 --- a/kluctl/cli/command_stubs.py +++ b/kluctl/cli/command_stubs.py @@ -89,6 +89,19 @@ def poke_images_command_stub(obj, **kwargs): from kluctl.cli.commands import poke_images_command poke_images_command(obj, kwargs) +@cli_group.command("downscale", + help="Downscale all deployments.\n\n" + "This command will downscale all Deployments, StatefulSets " + "and CronJobs.") +@kluctl_project_args() +@image_args() +@include_exclude_args() +@misc_arguments(yes=True, dry_run=True, output_format=True, render_output_dir=True) +@click.pass_obj +def downscale_command_stub(obj, **kwargs): + from kluctl.cli.commands import downscale_command + downscale_command(obj, kwargs) + @cli_group.command("validate", help="Validates the already deployed deployment.\n\n" "This means that all objects are retrieved from the cluster and checked for readiness.\n\n" diff --git a/kluctl/cli/commands.py b/kluctl/cli/commands.py index 38517b48d..4e48be470 100644 --- a/kluctl/cli/commands.py +++ b/kluctl/cli/commands.py @@ -93,6 +93,17 @@ def poke_images_command(obj, kwargs): if diff_result.errors: sys.exit(1) +def downscale_command(obj, kwargs): + with project_command_context(kwargs) as cmd_ctx: + if not kwargs["yes"] and not kwargs["dry_run"]: + click.confirm("Do you really want to downscale on context/cluster %s?" % cmd_ctx.k8s_cluster.context, + err=True, abort=True) + diff_result = cmd_ctx.deployment_collection.downscale(cmd_ctx.k8s_cluster) + deleted_objects = cmd_ctx.deployment_collection.find_purge_objects(cmd_ctx.k8s_cluster) + output_diff_result(kwargs["output"], cmd_ctx.deployment_collection, diff_result, deleted_objects) + if diff_result.errors: + sys.exit(1) + def validate_command(obj, kwargs): wait_duration = duration(kwargs["wait"]) if kwargs["wait"] else None sleep_duration = duration(kwargs["sleep"]) if kwargs["sleep"] else None diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index ac2c5a181..5fdb8eda5 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -13,6 +13,7 @@ from kluctl.diff.normalize import normalize_object from kluctl.utils.dict_utils import copy_dict, get_dict_value from kluctl.utils.k8s_delete_utils import find_objects_for_delete +from kluctl.utils.k8s_downscale_utils import downscale_object from kluctl.utils.k8s_object_utils import get_object_ref, get_long_object_name, get_long_object_name_from_ref, \ ObjectRef from kluctl.utils.k8s_status_validation import validate_object, ValidateResult, ValidateResultItem @@ -207,6 +208,28 @@ def do_poke_image(containers_and_images, o): return DeployDiffResult(new_objects=new_objects, changed_objects=changed_objects, errors=list(self.api_errors), warnings=list(self.api_warnings)) + def downscale(self, k8s_cluster): + self.clear_errors_and_warnings() + self.render_deployments() + self.build_kustomize_objects(k8s_cluster) + self.update_remote_objects(k8s_cluster) + + with MyThreadPoolExecutor(max_workers=8) as executor: + futures = [] + for d in self.deployments: + if not d.check_inclusion_for_deploy(): + continue + for o in d.objects: + ref = get_object_ref(o) + f = executor.submit(self.do_patch_object, k8s_cluster, ref, downscale_object) + futures.append((ref, f)) + + applied_objects = self.do_finish_futures(futures) + + new_objects, changed_objects = self.do_diff(k8s_cluster, applied_objects, False, False, False, False) + return DeployDiffResult(new_objects=new_objects, changed_objects=changed_objects, + errors=list(self.api_errors), warnings=list(self.api_warnings)) + def validate(self, k8s_cluster): self.clear_errors_and_warnings() self.render_deployments() diff --git a/kluctl/utils/dict_utils.py b/kluctl/utils/dict_utils.py index 1e0eb1d00..e01e091e9 100644 --- a/kluctl/utils/dict_utils.py +++ b/kluctl/utils/dict_utils.py @@ -47,13 +47,16 @@ def set_default_value(d, n, default): _dummy = str(uuid4()) -def get_dict_value(y, path, default=None): +def _split_path(path): if "\\." in path: path = path.replace("\\.", _dummy) s = path.split(".") - s = [x.replace(_dummy, ".") for x in s] + return [x.replace(_dummy, ".") for x in s] else: - s = path.split(".") + return path.split(".") + +def get_dict_value(y, path, default=None): + s = _split_path(path) for x in s: if y is None: @@ -63,6 +66,16 @@ def get_dict_value(y, path, default=None): y = y[x] return y +def set_dict_value(y, path, value, do_clone=False): + s = _split_path(path) + d = {} + d2 = d + for x in s[:-1]: + d2[x] = {} + d2 = d2[x] + d2[s[-1]] = value + return merge_dict(y, d, do_clone) + def is_empty(o): if isinstance(o, dict) or isinstance(o, list): return len(o) == 0 diff --git a/kluctl/utils/k8s_downscale_utils.py b/kluctl/utils/k8s_downscale_utils.py new file mode 100644 index 000000000..a969c2f99 --- /dev/null +++ b/kluctl/utils/k8s_downscale_utils.py @@ -0,0 +1,14 @@ +from kluctl.utils.dict_utils import set_dict_value +from kluctl.utils.k8s_object_utils import get_object_ref, remove_api_version_from_ref + + +def downscale_object(o): + ref = get_object_ref(o) + ref = remove_api_version_from_ref(ref) + if ref.api_version == "apps" and ref.kind in ["Deployment", "StatefulSet"]: + return set_dict_value(o, "spec.replicas", 0, True) + elif ref.api_version == "batch" and ref.kind == "CronJob": + return set_dict_value(o, "spec.suspend", True) + #elif ref.api_version == "autoscaling" and ref.kind == "HorizontalPodAutoscaler": + # return set_dict_value(o, "spec.minReplicas", 0, True) + return o From b30ebaa42721cc8ea99d79c9cd56b909c67dfa0d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 7 Sep 2021 15:07:09 +0200 Subject: [PATCH 0096/2916] fix: Fix typo in help --- docs/commands.md | 12 ++++++------ kluctl/cli/main_cli_group.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/commands.md b/docs/commands.md index 20ec39095..01e79cff2 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -107,7 +107,7 @@ In addition, the following arguments are available: ``` Misc arguments: - -y, --yes Supresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. + -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. --dry-run Performs all kubernetes API calls in dry-run mode. --force-apply Force conflict resolution when applying. See documentation for details --replace-on-error When patching an object fails, try to delete it and then retry. See documentation @@ -140,7 +140,7 @@ In addition, the following arguments are available: ``` Misc arguments: - -y, --yes Supresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. + -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. --dry-run Performs all kubernetes API calls in dry-run mode. --force-apply Force conflict resolution when applying. See documentation for details --replace-on-error When patching an object fails, try to delete it and then retry. See documentation @@ -247,7 +247,7 @@ In addition, the following arguments are available: ``` Misc arguments: - -y, --yes Supresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. + -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. --dry-run Performs all kubernetes API calls in dry-run mode. ``` @@ -277,7 +277,7 @@ In addition, the following arguments are available: ``` Misc arguments: - -y, --yes Supresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. + -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. --dry-run Performs all kubernetes API calls in dry-run mode. ``` @@ -333,7 +333,7 @@ In addition, the following arguments are available: ``` Misc arguments: - -y, --yes Supresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. + -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. --dry-run Performs all kubernetes API calls in dry-run mode. -o, --output TEXT Specify output format and target file, in the format 'format=path'. Format can either be 'diff' or 'yaml'. Can be specified multiple times. The actual format for @@ -363,7 +363,7 @@ In addition, the following arguments are available: ``` Misc arguments: - -y, --yes Supresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. + -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. --dry-run Performs all kubernetes API calls in dry-run mode. -o, --output TEXT Specify output format and target file, in the format 'format=path'. Format can either be 'diff' or 'yaml'. Can be specified multiple times. The actual format for diff --git a/kluctl/cli/main_cli_group.py b/kluctl/cli/main_cli_group.py index fd24796f5..5038b13e5 100644 --- a/kluctl/cli/main_cli_group.py +++ b/kluctl/cli/main_cli_group.py @@ -127,7 +127,7 @@ def misc_arguments(yes=False, dry_run=False, force_apply=False, replace_on_error options.append(optgroup.group("Misc arguments")) if yes: options.append(optgroup.option("-y", "--yes", - help="Supresses 'Are you sure?' questions and proceeds as if you would " + help="Suppresses 'Are you sure?' questions and proceeds as if you would " "answer 'yes'.", default=False, is_flag=True)) if dry_run: From 9be6e34efe0731ba2a761512cca71a3f74f35470 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 8 Sep 2021 09:17:04 +0200 Subject: [PATCH 0097/2916] refactor: Rename wrongly named do_patch_object to do_replace_object --- kluctl/deployment/deployment_collection.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index 5fdb8eda5..34dbdff1c 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -199,7 +199,7 @@ def do_poke_image(containers_and_images, o): futures = [] for ref, c in containers_and_images.items(): - f = executor.submit(self.do_patch_object, k8s_cluster, ref, functools.partial(do_poke_image, c)) + f = executor.submit(self.do_replace_object, k8s_cluster, ref, functools.partial(do_poke_image, c)) futures.append((ref, f)) applied_objects = self.do_finish_futures(futures) @@ -221,7 +221,7 @@ def downscale(self, k8s_cluster): continue for o in d.objects: ref = get_object_ref(o) - f = executor.submit(self.do_patch_object, k8s_cluster, ref, downscale_object) + f = executor.submit(self.do_replace_object, k8s_cluster, ref, downscale_object) futures.append((ref, f)) applied_objects = self.do_finish_futures(futures) @@ -328,7 +328,7 @@ def do_diff(self, k8s_cluster, applied_objects, ignore_tags, ignore_labels, igno return new_objects, changed_objects - def do_patch_object(self, k8s_cluster, ref, callback): + def do_replace_object(self, k8s_cluster, ref, callback): first_call = True while True: if first_call: From 1b092b921e7301a30c99792b026634e099fbf15d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 8 Sep 2021 09:43:30 +0200 Subject: [PATCH 0098/2916] fix: Add missing fields manager for replace_object --- kluctl/utils/k8s_cluster_real.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kluctl/utils/k8s_cluster_real.py b/kluctl/utils/k8s_cluster_real.py index 5feec32d8..03afaab10 100644 --- a/kluctl/utils/k8s_cluster_real.py +++ b/kluctl/utils/k8s_cluster_real.py @@ -237,6 +237,8 @@ def replace_object(self, body, namespace=None, force_dry_run=False, resource_ver name = body['metadata']['name'] query_params = self._get_dry_run_params(force_dry_run) + query_params.append(('fieldManager', 'kluctl')) + resource = self.dynamic_client.resources.get(group=group, api_version=version, kind=kind) if resource.namespaced: self.dynamic_client.ensure_namespace(resource, namespace, body) From 33040aa28754bf7e45a1f6925cb4e30c900d09f0 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 8 Sep 2021 13:24:02 +0200 Subject: [PATCH 0099/2916] fix: Add api error in case of image association failure --- kluctl/deployment/deployment_collection.py | 1 + 1 file changed, 1 insertion(+) diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index 34dbdff1c..6c2a0a05d 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -191,6 +191,7 @@ def do_poke_image(containers_and_images, o): ref = ObjectRef(r.group_version, kind=r.kind, name=name, namespace=fi.get("namespace")) local_object = all_objects.get(ref) if local_object is None: + self.add_api_error(ref, "object not found while trying to associate image with deployed object") continue containers_and_images.setdefault(ref, []).append((fi["container"], fi["resultImage"])) From 1c36c8266590ce3514e740ba6e530fc7b17b9b8c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 8 Sep 2021 13:50:54 +0200 Subject: [PATCH 0100/2916] refactor: Move dict nav utils into own module --- kluctl/diff/normalize.py | 67 +------------------------------ kluctl/utils/dict_nav_utils.py | 73 ++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 65 deletions(-) create mode 100644 kluctl/utils/dict_nav_utils.py diff --git a/kluctl/diff/normalize.py b/kluctl/diff/normalize.py index 63d5ebd77..78bdc7caf 100644 --- a/kluctl/diff/normalize.py +++ b/kluctl/diff/normalize.py @@ -1,72 +1,9 @@ import fnmatch +from kluctl.utils.dict_nav_utils import del_if_exists, set_if_not_exists, del_if_falsy, del_matching_path +from kluctl.utils.dict_utils import copy_dict from kluctl.utils.k8s_object_utils import split_api_version -from kluctl.utils.dict_utils import is_iterable, copy_dict - - -def nav_dict(d, k): - dummy = 'dummy-placeholder-for-dot' - k = k.replace('\\.', dummy) - k = k.split('.') - k = [x.replace(dummy, '.') for x in k] - - for i in range(len(k)): - if d is None: - return None, k[i], False - if isinstance(d, dict): - if k[i] not in d: - return d, k[i], False - if i == len(k) - 1: - return d, k[i], True - else: - d = d[k[i]] - elif is_iterable(d): - j = int(k[i]) - if j < 0 or j >= len(d): - return d, j, False - if i == len(k) - 1: - return d, j, True - else: - d = d[j] - else: - return d, None, False - - -def del_if_exists(d, k): - d, k, e = nav_dict(d, k) - if not e: - return - del d[k] - -def set_if_not_exists(d, k, v): - d, k, e = nav_dict(d, k) - if e: - return - d[k] = v -def del_if_falsy(d, k): - d, k, e = nav_dict(d, k) - if not e: - return - if not d[k]: - del d[k] - -def _object_path_iterator(o, path): - yield path - if isinstance(o, dict): - for k, v in o.items(): - for p in _object_path_iterator(v, path + [k]): - yield p - elif not isinstance(o, str) and is_iterable(o): - for i, v in enumerate(o): - for p in _object_path_iterator(v, path + [str(i)]): - yield p - -def del_matching_path(o, path): - for p in list(_object_path_iterator(o, [])): - if fnmatch.fnmatch(".".join(p), path): - p2 = [x.replace(".", "\\.") for x in p] - del_if_exists(o, ".".join(p2)) def normalize_env(container): env = container.get("env") diff --git a/kluctl/utils/dict_nav_utils.py b/kluctl/utils/dict_nav_utils.py new file mode 100644 index 000000000..47ef3aefc --- /dev/null +++ b/kluctl/utils/dict_nav_utils.py @@ -0,0 +1,73 @@ +import fnmatch + +from kluctl.utils.dict_utils import is_iterable + + +def nav_dict(d, k): + dummy = 'dummy-placeholder-for-dot' + k = k.replace('\\.', dummy) + k = k.split('.') + k = [x.replace(dummy, '.') for x in k] + + for i in range(len(k)): + if d is None: + return None, k[i], False + if isinstance(d, dict): + if k[i] not in d: + return d, k[i], False + if i == len(k) - 1: + return d, k[i], True + else: + d = d[k[i]] + elif is_iterable(d): + j = int(k[i]) + if j < 0 or j >= len(d): + return d, j, False + if i == len(k) - 1: + return d, j, True + else: + d = d[j] + else: + return d, None, False + + +def del_if_exists(d, k): + d, k, e = nav_dict(d, k) + if not e: + return + del d[k] + + +def set_if_not_exists(d, k, v): + d, k, e = nav_dict(d, k) + if e: + return + d[k] = v + + +def del_if_falsy(d, k): + d, k, e = nav_dict(d, k) + if not e: + return + if not d[k]: + del d[k] + +def _object_path_iterator(o, path): + yield path + if isinstance(o, dict): + for k, v in o.items(): + for p in _object_path_iterator(v, path + [k]): + yield p + elif not isinstance(o, str) and is_iterable(o): + for i, v in enumerate(o): + for p in _object_path_iterator(v, path + [str(i)]): + yield p + +def object_path_iterator(o): + return _object_path_iterator(o, []) + +def del_matching_path(o, path): + for p in list(object_path_iterator(o)): + if fnmatch.fnmatch(".".join(p), path): + p2 = [x.replace(".", "\\.") for x in p] + del_if_exists(o, ".".join(p2)) From e0a7832617699c79b1a32c1f2f1c18063ef533a9 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 8 Sep 2021 13:53:03 +0200 Subject: [PATCH 0101/2916] refactor: Make object_path_iterator also return values --- kluctl/utils/dict_nav_utils.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/kluctl/utils/dict_nav_utils.py b/kluctl/utils/dict_nav_utils.py index 47ef3aefc..3274e28eb 100644 --- a/kluctl/utils/dict_nav_utils.py +++ b/kluctl/utils/dict_nav_utils.py @@ -52,22 +52,22 @@ def del_if_falsy(d, k): if not d[k]: del d[k] -def _object_path_iterator(o, path): - yield path +def _object_iterator(o, path): + yield o, path if isinstance(o, dict): for k, v in o.items(): - for p in _object_path_iterator(v, path + [k]): - yield p + for o2, p in _object_iterator(v, path + [k]): + yield o2, p elif not isinstance(o, str) and is_iterable(o): for i, v in enumerate(o): - for p in _object_path_iterator(v, path + [str(i)]): - yield p + for o2, p in _object_iterator(v, path + [str(i)]): + yield o2, p -def object_path_iterator(o): - return _object_path_iterator(o, []) +def object_iterator(o): + return _object_iterator(o, []) def del_matching_path(o, path): - for p in list(object_path_iterator(o)): + for _, p in list(object_iterator(o)): if fnmatch.fnmatch(".".join(p), path): p2 = [x.replace(".", "\\.") for x in p] del_if_exists(o, ".".join(p2)) From a0f9316de68b6692ca7c59a30072f786ca6df4c3 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 8 Sep 2021 15:48:51 +0200 Subject: [PATCH 0102/2916] refactor: Use nav_dict utils for get_dict_value/set_dict_value --- kluctl/utils/dict_nav_utils.py | 25 +++++++++++++----- kluctl/utils/dict_utils.py | 46 ++++++++-------------------------- 2 files changed, 29 insertions(+), 42 deletions(-) diff --git a/kluctl/utils/dict_nav_utils.py b/kluctl/utils/dict_nav_utils.py index 3274e28eb..3647a23d4 100644 --- a/kluctl/utils/dict_nav_utils.py +++ b/kluctl/utils/dict_nav_utils.py @@ -1,13 +1,18 @@ import fnmatch +from uuid import uuid4 -from kluctl.utils.dict_utils import is_iterable - +_dummy = str(uuid4()) def nav_dict(d, k): - dummy = 'dummy-placeholder-for-dot' - k = k.replace('\\.', dummy) - k = k.split('.') - k = [x.replace(dummy, '.') for x in k] + if isinstance(k, str): + if "\\." in k: + k = k.replace("\\.", _dummy) + k = k.split(".") + k = [x.replace(_dummy, ".") for x in k] + else: + k = k.split(".") + elif not isinstance(k, list): + raise ValueError("k must be a list and not %s" % type(k).__name__) for i in range(len(k)): if d is None: @@ -52,6 +57,14 @@ def del_if_falsy(d, k): if not d[k]: del d[k] +def is_iterable(obj): + try: + iter(obj) + except Exception: + return False + else: + return True + def _object_iterator(o, path): yield o, path if isinstance(o, dict): diff --git a/kluctl/utils/dict_utils.py b/kluctl/utils/dict_utils.py index e01e091e9..d79a8c7dd 100644 --- a/kluctl/utils/dict_utils.py +++ b/kluctl/utils/dict_utils.py @@ -1,14 +1,6 @@ -from uuid import uuid4 +from kluctl.utils.dict_nav_utils import nav_dict, is_iterable -def is_iterable(obj): - try: - iter(obj) - except Exception: - return False - else: - return True - def copy_primitive_value(v): if isinstance(v, dict): return copy_dict(v) @@ -45,36 +37,18 @@ def set_default_value(d, n, default): if n not in d or d[n] is None: d[n] = default -_dummy = str(uuid4()) - -def _split_path(path): - if "\\." in path: - path = path.replace("\\.", _dummy) - s = path.split(".") - return [x.replace(_dummy, ".") for x in s] - else: - return path.split(".") - def get_dict_value(y, path, default=None): - s = _split_path(path) - - for x in s: - if y is None: - return default - if x not in y: - return default - y = y[x] - return y + d, k, found = nav_dict(y, path) + if not found: + return default + return d[k] def set_dict_value(y, path, value, do_clone=False): - s = _split_path(path) - d = {} - d2 = d - for x in s[:-1]: - d2[x] = {} - d2 = d2[x] - d2[s[-1]] = value - return merge_dict(y, d, do_clone) + if do_clone: + y = copy_dict(y) + d, k, found = nav_dict(y, path) + d[k] = value + return y def is_empty(o): if isinstance(o, dict) or isinstance(o, list): From f62d2dd20834666f657cd9f650b1d0e872bf5fd5 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 8 Sep 2021 17:51:34 +0200 Subject: [PATCH 0103/2916] refactor: Remove preparation into common method --- kluctl/cli/commands.py | 4 ++-- kluctl/cli/util_commands.py | 4 +--- kluctl/deployment/deployment_collection.py | 28 +++++++--------------- 3 files changed, 11 insertions(+), 25 deletions(-) diff --git a/kluctl/cli/commands.py b/kluctl/cli/commands.py index 4e48be470..d16e0045c 100644 --- a/kluctl/cli/commands.py +++ b/kluctl/cli/commands.py @@ -137,7 +137,7 @@ def render_command(obj, kwargs): kwargs["render_output_dir"] = tempfile.mkdtemp(dir=get_tmp_base_dir(), prefix="render-") with project_command_context(kwargs) as cmd_ctx: - cmd_ctx.deployment_collection.render_deployments() + cmd_ctx.deployment_collection.prepare(cmd_ctx.k8s_cluster, False) logger.info('Rendered into: %s' % cmd_ctx.deployment_collection.tmpdir) if kwargs["output_images"]: @@ -150,7 +150,7 @@ def render_command(obj, kwargs): def list_images_command(obj, kwargs): with project_command_context(kwargs, force_offline_kubernetes=kwargs["no_kubernetes"]) as cmd_ctx: cmd_ctx.images.raise_on_error = False - cmd_ctx.deployment_collection.render_deployments() + cmd_ctx.deployment_collection.prepare(cmd_ctx.k8s_cluster, False) result = { "images": build_seen_images(cmd_ctx.deployment_collection, not kwargs["simple"]) diff --git a/kluctl/cli/util_commands.py b/kluctl/cli/util_commands.py index b19a12277..3b35f6cd8 100644 --- a/kluctl/cli/util_commands.py +++ b/kluctl/cli/util_commands.py @@ -104,10 +104,8 @@ def helm_update_command(upgrade, commit, kwargs): def check_image_updates(obj, kwargs): with project_command_context(kwargs) as cmd_ctx: - cmd_ctx.images.no_kubernetes = True cmd_ctx.images.raise_on_error = False - cmd_ctx.deployment_collection.render_deployments() - cmd_ctx.deployment_collection.build_kustomize_objects(cmd_ctx.k8s_cluster) + cmd_ctx.deployment_collection.prepare(cmd_ctx.k8s_cluster, False) rendered_images = cmd_ctx.deployment_collection.find_rendered_images() prefix_pattern = re.compile(r"^([a-zA-Z]+[a-zA-Z-_.]*)") diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index 6c2a0a05d..26affd04c 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -133,10 +133,15 @@ def local_objects_by_ref(self): by_ref = dict((get_object_ref(x), x) for x in flat) return by_ref - def deploy(self, k8s_cluster, force_apply, replace_on_error, abort_on_error): - self.clear_errors_and_warnings() + def prepare(self, k8s_cluster, only_render): self.render_deployments() + if only_render: + return self.build_kustomize_objects(k8s_cluster) + self.update_remote_objects(k8s_cluster) + + def deploy(self, k8s_cluster, force_apply, replace_on_error, abort_on_error): + self.clear_errors_and_warnings() applied_objects = self.do_deploy(k8s_cluster, force_apply, replace_on_error, False, abort_on_error) new_objects, changed_objects = self.do_diff(k8s_cluster, applied_objects, False, False, False, False) @@ -145,8 +150,6 @@ def deploy(self, k8s_cluster, force_apply, replace_on_error, abort_on_error): def diff(self, k8s_cluster, force_apply, replace_on_error, ignore_tags, ignore_labels, ignore_annotations, ignore_order): self.clear_errors_and_warnings() - self.render_deployments() - self.build_kustomize_objects(k8s_cluster) applied_objects = self.do_deploy(k8s_cluster, force_apply, replace_on_error, True, False) new_objects, changed_objects = self.do_diff(k8s_cluster, applied_objects, ignore_tags, ignore_labels, ignore_annotations, ignore_order) @@ -155,10 +158,6 @@ def diff(self, k8s_cluster, force_apply, replace_on_error, ignore_tags, ignore_l def poke_images(self, k8s_cluster): self.clear_errors_and_warnings() - self.render_deployments() - self.build_kustomize_objects(k8s_cluster) - self.update_remote_objects(k8s_cluster) - def do_poke_image(containers_and_images, o): o = copy_dict(o) containers = get_dict_value(o, "spec.template.spec.containers", []) @@ -211,10 +210,6 @@ def do_poke_image(containers_and_images, o): def downscale(self, k8s_cluster): self.clear_errors_and_warnings() - self.render_deployments() - self.build_kustomize_objects(k8s_cluster) - self.update_remote_objects(k8s_cluster) - with MyThreadPoolExecutor(max_workers=8) as executor: futures = [] for d in self.deployments: @@ -231,11 +226,8 @@ def downscale(self, k8s_cluster): return DeployDiffResult(new_objects=new_objects, changed_objects=changed_objects, errors=list(self.api_errors), warnings=list(self.api_warnings)) - def validate(self, k8s_cluster): + def validate(self): self.clear_errors_and_warnings() - self.render_deployments() - self.build_kustomize_objects(k8s_cluster) - self.update_remote_objects(k8s_cluster) result = ValidateResult() for w in self.api_warnings: @@ -265,8 +257,6 @@ def find_delete_objects(self, k8s_cluster): def find_purge_objects(self, k8s_cluster): self.clear_errors_and_warnings() - self.render_deployments() - self.build_kustomize_objects(k8s_cluster) logger.info("Searching objects not found in local objects") labels = self.project.get_delete_by_labels() excluded_objects = list(self.local_objects_by_ref().keys()) @@ -278,8 +268,6 @@ def do_apply(self, k8s_cluster, force_apply, replace_on_error, dry_run, abort_on return apply_util.applied_objects def do_deploy(self, k8s_cluster, force_apply, replace_on_error, dry_run, abort_on_error): - self.update_remote_objects(k8s_cluster) - # TODO remove this self.migrate_to_new_manager(k8s_cluster) From 3d13d959bbd7704bbe48b924b16344a830be453e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 8 Sep 2021 18:13:21 +0200 Subject: [PATCH 0104/2916] feat: Remove the need for namespace/deployment/container parameters in get_image --- docs/images.md | 17 +-- kluctl/cli/utils.py | 15 +- kluctl/deployment/deployment_collection.py | 35 ++++- kluctl/deployment/images.py | 153 ++++++++++----------- kluctl/deployment/kustomize_deployment.py | 22 +-- kluctl/deployment/test_images.py | 9 +- kluctl/seal/seal_command.py | 1 - 7 files changed, 127 insertions(+), 125 deletions(-) diff --git a/docs/images.md b/docs/images.md index 3f39678f6..102b2927c 100644 --- a/docs/images.md +++ b/docs/images.md @@ -2,7 +2,7 @@ There are usually 2 different scenarios where Container Images need to be specified: 1. When deploying third party applications like nginx, redis, ... (e.g. via the [Helm integration](./helm-integration.md)).
- * In this case, image versions/tags change rarely, and if they do, this is an explicit change to the deployment. + * In this case, image versions/tags rarely change, and if they do, this is an explicit change to the deployment. 1. When deploying your own applications.
* In this case, image versions/tags might change very rapidly, sometimes multiple times per hour. It would be too much effort and overhead when this would be managed explicitly via your deployment. Even with Jinja2 templating, this @@ -24,27 +24,20 @@ otherwise unchanged resources. This is solved via a Jinja2 function that is available in all templates/resources. The function is part of the global `images` object and expects the following arguments: -`images.get_image(namespace, deployment_name, container, image, latest_version)` +`images.get_image(image, latest_version)` -* namespace - * The namespace where the affected resource is deployed to. -* deployment_name - * The name of the affected resource. This will by default refer to a Kubernetes `Deployment`, but can also refer - to other resource types by prefixing the kind, for example `StatefulSet/name`. -* container - * The name of the container inside the affected resource. * image * The image location, excluding the tag. Please see [supported image registries](#supported-image-registries-and-authentication) to understand which registries are supported.` * latest_version - * configures how tags/versions are sorted and thus how the latest image is determined. Can be: + * Configures how tags/versions are sorted and thus how the latest image is determined. Can be: * `version.semver()`
Filters and sorts by loose semantic versioning. Versions must start with a number. It allows unlimited `.` inside the version. It treats versions with a suffix as less then versions without a suffix (e.g. 1.0-rc1 < 1.0). Two versions which only differ by suffix are sorted semantically. * `version.prefix(prefix)`
Only allows tags with the given prefix and then applies the same logic as images.semver() to whatever - follows right after the prefix. You can override the handling of right part by providing `suffix=xxx`, + follows right after the prefix. You can override the handling of the right part by providing `suffix=xxx`, while `xxx` is another version filter, e.g. `version.prefix("master-", suffix=version.number()) * `version.number()`
Only allows plain numbers as version numbers sorts them accordingly. @@ -64,7 +57,7 @@ spec: spec: containers: - name: c1 - image: "{{ images.get_image('my-namespace', 'my-deployment', 'c1', 'registry.gitlab.com/my-group/my-project') }}" + image: "{{ images.get_image('registry.gitlab.com/my-group/my-project') }}" ``` ## Always using the latest images diff --git a/kluctl/cli/utils.py b/kluctl/cli/utils.py index 0dec58f0d..20d9a7427 100644 --- a/kluctl/cli/utils.py +++ b/kluctl/cli/utils.py @@ -30,15 +30,14 @@ def build_jinja_vars(cluster_vars): return jinja_vars -def build_deploy_images(k8s_cluster, force_offline, kwargs): +def build_deploy_images(force_offline, kwargs): image_registries = None if not kwargs.get("no_registries", False): image_registries = init_image_registries() - images = Images(k8s_cluster, image_registries) + images = Images(image_registries) offline = force_offline or kwargs.get("offline", False) images.update_images = kwargs.get("update_images", False) and not offline images.no_registries = kwargs.get("no_registries", False) or offline - images.no_kubernetes = kwargs.get("no_kubernetes", False) or offline return images def build_fixed_image_entry_from_arg(arg): @@ -128,7 +127,7 @@ def project_target_command_context(kwargs, kluctl_project, target, offline=force_offline_kubernetes) jinja_vars = build_jinja_vars(cluster_vars) - images = build_deploy_images(k8s_cluster, force_offline_images, kwargs) + images = build_deploy_images(force_offline_images, kwargs) inclusion = parse_inclusion(kwargs) option_args = parse_args(kwargs.get("arg", [])) @@ -152,9 +151,11 @@ def project_target_command_context(kwargs, kluctl_project, target, fixed_images = load_fixed_images(kwargs) if target is not None: for fi in target.get("images", []): - c.seen_images.add_fixed_image(fi) + c.images.add_fixed_image(fi) for fi in fixed_images: - c.seen_images.add_fixed_image(fi) + c.images.add_fixed_image(fi) + + c.prepare(k8s_cluster, for_seal) ctx = CommandContext(kluctl_project=kluctl_project, target=target, cluster_vars=cluster_vars, k8s_cluster=k8s_cluster, @@ -243,7 +244,7 @@ def output_result(output_file, result): def build_seen_images(c, detailed): ret = [] - for e in c.seen_images.seen_images: + for e in c.images.seen_images: if detailed: a = e else: diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index 26affd04c..b49653213 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -7,7 +7,6 @@ from kubernetes.dynamic.exceptions import ResourceNotFoundError, ConflictError from kluctl.deployment.apply_util import ApplyUtil -from kluctl.deployment.images import SeenImages from kluctl.deployment.kustomize_deployment import KustomizeDeployment from kluctl.diff.k8s_diff import deep_diff_object from kluctl.diff.normalize import normalize_object @@ -15,7 +14,7 @@ from kluctl.utils.k8s_delete_utils import find_objects_for_delete from kluctl.utils.k8s_downscale_utils import downscale_object from kluctl.utils.k8s_object_utils import get_object_ref, get_long_object_name, get_long_object_name_from_ref, \ - ObjectRef + ObjectRef, should_remove_namespace from kluctl.utils.k8s_status_validation import validate_object, ValidateResult, ValidateResultItem from kluctl.utils.templated_dir import TemplatedDir from kluctl.utils.utils import MyThreadPoolExecutor @@ -39,7 +38,6 @@ class DeploymentCollection: def __init__(self, project, images, inclusion, tmpdir, for_seal): self.project = project self.images = images - self.seen_images = SeenImages(images) self.inclusion = inclusion self.tmpdir = tmpdir self.for_seal = for_seal @@ -108,9 +106,16 @@ def build_kustomize_objects(self, k8s_cluster): with MyThreadPoolExecutor() as executor: jobs = [] for d in self.deployments: - jobs.append(executor.submit(d.build_objects, k8s_cluster)) + jobs.append(executor.submit(d.build_objects)) for job in jobs: job.result() + + if k8s_cluster: + for d in self.deployments: + for o in d.objects: + if should_remove_namespace(k8s_cluster, get_object_ref(o)): + del o["metadata"]["namespace"] + self.is_built = True def update_remote_objects(self, k8s_cluster): @@ -124,6 +129,25 @@ def update_remote_objects(self, k8s_cluster): self.remote_objects[get_object_ref(o)] = o self.add_api_warnings(get_object_ref(o), w) + def resolve_image_placeholders(self, k8s_cluster): + logger.info("Resolving image versions") + + def do_resolve(o): + ref = get_object_ref(o) + remote_object = self.remote_objects.get(ref) + if remote_object is None and k8s_cluster is not None: + remote_object, warnings = k8s_cluster.get_single_object(ref) + self.images.resolve_placeholders(o, remote_object) + + with MyThreadPoolExecutor(max_workers=8) as executor: + futures = [] + for d in self.deployments: + for o in d.objects: + f = executor.submit(do_resolve, o) + futures.append(f) + for f in futures: + f.result() + def forget_remote_objects(self, refs): for ref in refs: self.remote_objects.pop(ref, None) @@ -139,6 +163,7 @@ def prepare(self, k8s_cluster, only_render): return self.build_kustomize_objects(k8s_cluster) self.update_remote_objects(k8s_cluster) + self.resolve_image_placeholders(k8s_cluster) def deploy(self, k8s_cluster, force_apply, replace_on_error, abort_on_error): self.clear_errors_and_warnings() @@ -175,7 +200,7 @@ def do_poke_image(containers_and_images, o): all_objects[get_object_ref(o)] = o containers_and_images = {} - for fi in self.seen_images.seen_images: + for fi in self.images.seen_images: deployment_name = fi["deployment"] if "/" not in deployment_name: deployment_name = "Deployment/%s" % deployment_name diff --git a/kluctl/deployment/images.py b/kluctl/deployment/images.py index 4ba6f1757..5c1ce2c93 100644 --- a/kluctl/deployment/images.py +++ b/kluctl/deployment/images.py @@ -1,23 +1,28 @@ +import hashlib import logging import threading +from kluctl.utils.dict_nav_utils import object_iterator +from kluctl.utils.dict_utils import copy_dict, set_dict_value, get_dict_value from kluctl.utils.exceptions import CommandError from kluctl.utils.thread_safe_cache import ThreadSafeMultiCache -from kluctl.utils.versions import LooseSemVerLatestVersion +from kluctl.utils.yaml_utils import yaml_dump logger = logging.getLogger(__name__) class Images(object): - def __init__(self, k8s_cluster, image_registries): - self.k8s_cluster = k8s_cluster + def __init__(self, image_registries): self.image_registries = image_registries self.update_images = False self.no_registries = image_registries is None - self.no_kubernetes = k8s_cluster is None self.raise_on_error = False self.image_tags_cache = ThreadSafeMultiCache() - self.k8s_object_cache = ThreadSafeMultiCache() + + self.placeholders = {} + self.seen_images = [] + self.seen_images_lock = threading.Lock() + self.fixed_images = [] def get_registry_for_image(self, image): for r in self.image_registries: @@ -48,63 +53,14 @@ def get_latest_image_from_registry(self, image, latest_version): return f'{image}:{latest_tag}' - def get_image_from_k8s_object(self, namespace, name_with_kind, container): - if self.no_kubernetes: - return None - - kind = 'Deployment' - name = name_with_kind - a = name_with_kind.split('/') - if len(a) != 1: - kind = a[0] - name = a[1] - - def get_object(): - l = self.k8s_cluster.get_objects(kind=kind, name=name, namespace=namespace) - if not l: - return None - if len(l) != 1: - raise Exception("expected single object, got %d" % len(l)) - return l[0] - - ret = None - - r = self.k8s_object_cache.get((kind, name, namespace), "object", get_object) - if r is not None: - deployment = r[0] - for c in deployment['spec']['template']['spec']['containers']: - if c['name'] == container: - ret = c['image'] - break - - logger.debug(f"get_image_from_k8s_object({namespace}, {name_with_kind}, {container}): returned {ret}") - return ret - - def get_image(self, image, namespace=None, deployment_name=None, container=None, latest_version=LooseSemVerLatestVersion()): - registry_image = self.get_latest_image_from_registry(image, latest_version) - deployed_image = None - if namespace is not None and deployment_name is not None and container is not None: - deployed_image = self.get_image_from_k8s_object(namespace, deployment_name, container) - - result_image = deployed_image - if result_image is None or self.update_images: - result_image = registry_image - - if result_image is None and self.raise_on_error: - raise Exception("Failed to determine image for %s" % image) - return result_image, deployed_image, registry_image - -class SeenImages: - def __init__(self, images): - self.images = images - self.seen_images = [] - self.seen_images_lock = threading.Lock() - self.fixed_images = [] - - def build_seen_entry(self, image, result_image, deployed_image, registry_image, kwargs): + def build_seen_entry(self, image, latest_version, + result_image, deployed_image, registry_image, + namespace, deployment, container): new_entry = { "image": image, } + if latest_version is not None: + new_entry["versionFilter"] = latest_version if result_image is not None: new_entry["resultImage"] = result_image if deployed_image is not None: @@ -112,19 +68,21 @@ def build_seen_entry(self, image, result_image, deployed_image, registry_image, if registry_image is not None: new_entry["registryImage"] = registry_image - def set_value(kw_name, entry_name): - v = kwargs.get(kw_name) - if v is not None: - new_entry[entry_name] = str(v) + if namespace is not None: + new_entry["namespace"] = namespace + if deployment is not None: + new_entry["deployment"] = deployment + if container is not None: + new_entry["container"] = container - set_value("namespace", "namespace") - set_value("deployment_name", "deployment") - set_value("container", "container") - set_value("latest_version", "versionFilter") return new_entry - def add_seen_image(self, image, result_image, deployed_image, registry_image, kustomize_dir, tags, kwargs): - new_entry = self.build_seen_entry(image, result_image, deployed_image, registry_image, kwargs) + def add_seen_image(self, image, latest_version, + result_image, deployed_image, registry_image, + kustomize_dir, tags, + namespace, deployment, container): + new_entry = self.build_seen_entry(image, latest_version, result_image, deployed_image, registry_image, + namespace, deployment, container) new_entry["deployTags"] = tags new_entry["kustomizeDir"] = kustomize_dir @@ -152,8 +110,8 @@ def add_fixed_image(self, fixed_image): # put it to the front so that newest entries are preferred self.fixed_images.insert(0, e) - def get_fixed_image(self, image, kwargs): - e = self.build_seen_entry(image, None, None, None, kwargs) + def get_fixed_image(self, image, namespace, deployment, container): + e = self.build_seen_entry(image, None, None, None, None, namespace, deployment, container) for fi in self.fixed_images: if fi["image"] != image: continue @@ -168,15 +126,44 @@ def get_fixed_image(self, image, kwargs): return fi["resultImage"] return None - def get_image_wrapper(self, kustomize_dir, tags): - def wrapper(image, **kwargs): - return self.do_get_image(kustomize_dir, tags, image, kwargs) - return wrapper - - def do_get_image(self, kustomize_dir, tags, image, kwargs): - fixed = self.get_fixed_image(image, kwargs) - result, deployed, registry = self.images.get_image(image, **kwargs) - if not self.images.update_images and fixed is not None: - result = fixed - self.add_seen_image(image, result, deployed, registry, kustomize_dir, tags, kwargs) - return result + def gen_image_placeholder(self, image, latest_version, kustomize_dir, tags): + placeholder = { + "image": image, + "latest_version": str(latest_version), + "kustomize_dir": kustomize_dir, + "tags": tags, + } + h = hashlib.sha256(yaml_dump(placeholder).encode("utf-8")).hexdigest() + with self.seen_images_lock: + self.placeholders[h] = placeholder + return h + + def resolve_placeholders(self, local_object, remote_object): + for v, p in object_iterator(copy_dict(local_object)): + if not isinstance(v, str): + continue + placeholder = self.placeholders.get(v) + if placeholder is None: + continue + + namespace = get_dict_value(local_object, "metadata.namespace") + deployment = "%s/%s" % (local_object["kind"], get_dict_value(local_object, "metadata.name")) + container = get_dict_value(local_object, p[:-1] + ["name"]) + + fixed = self.get_fixed_image(placeholder["image"], namespace, deployment, container) + deployed = None + if remote_object is not None: + deployed = get_dict_value(remote_object, p) + registry = self.get_latest_image_from_registry(placeholder["image"], placeholder["latest_version"]) + + result = deployed + if result is None or self.update_images: + result = registry + if not self.update_images and fixed is not None: + result = fixed + + self.add_seen_image(placeholder["image"], placeholder["latest_version"], result, deployed, registry, + placeholder["kustomize_dir"], placeholder["tags"], + namespace, deployment, container) + + set_dict_value(local_object, p, result) diff --git a/kluctl/deployment/kustomize_deployment.py b/kluctl/deployment/kustomize_deployment.py index ca756c9a5..c6e7b7bcd 100644 --- a/kluctl/deployment/kustomize_deployment.py +++ b/kluctl/deployment/kustomize_deployment.py @@ -3,12 +3,11 @@ import os import shutil -from kluctl.utils.dict_utils import merge_dict -from kluctl.utils.exceptions import CommandError from kluctl.deployment.helm_chart import HelmChart from kluctl.seal.deployment_sealer import SEALME_EXT +from kluctl.utils.dict_utils import merge_dict +from kluctl.utils.exceptions import CommandError from kluctl.utils.external_tools import get_external_tool_hash -from kluctl.utils.k8s_object_utils import should_remove_namespace, get_object_ref from kluctl.utils.kustomize import kustomize_build from kluctl.utils.templated_dir import TemplatedDir from kluctl.utils.utils import get_tmp_base_dir, calc_dir_hash @@ -62,15 +61,21 @@ def get_common_annotations(self): a["kluctl.io/skip-delete-if-tags"] = "true" return a + def get_image_wrapper(self, image, namespace=None, deployment_name=None, container=None, latest_version=LooseSemVerLatestVersion()): + # TODO remove this + if namespace is not None or deployment_name is not None or container is not None: + logger.warning("images.get_image called with obsolete parameters (namespace/deployment_name/container). These are not needed anymore.") + + tags = list(sorted(self.get_tags())) + return self.deployment_collection.images.gen_image_placeholder(image, latest_version, self.get_rel_kustomize_dir(), tags) + def build_images_jinja_vars(self): if self.deployment_collection.images is None: return {} - tags = list(sorted(self.get_tags())) - return { 'images': { - 'get_image': self.deployment_collection.seen_images.get_image_wrapper(self.get_rel_kustomize_dir(), tags), + 'get_image': self.get_image_wrapper, }, 'version': { 'semver': LooseSemVerLatestVersion, @@ -235,7 +240,7 @@ def prepare_kustomization_yaml(self): # Save modified kustomize.yml yaml_save_file(kustomize_yaml, kustomize_yaml_path) - def build_objects(self, k8s_cluster): + def build_objects(self): if self.config.get("onlyRender", False): self.objects = [] return @@ -256,9 +261,6 @@ def build_objects(self, k8s_cluster): y["metadata"]["labels"] = labels y["metadata"]["annotations"] = annotations - if should_remove_namespace(k8s_cluster, get_object_ref(y)): - del y["metadata"]["namespace"] - self.objects.append(y) def calc_hash(self): diff --git a/kluctl/deployment/test_images.py b/kluctl/deployment/test_images.py index e8316f184..dc3ac9258 100644 --- a/kluctl/deployment/test_images.py +++ b/kluctl/deployment/test_images.py @@ -1,19 +1,14 @@ import unittest -from kluctl.image_registries.images_registry import ImagesRegistry from kluctl.deployment.images import Images -from kluctl.utils.k8s_cluster_mocked import k8s_cluster_mocked +from kluctl.image_registries.images_registry import ImagesRegistry from kluctl.utils.versions import PrefixLatestVersion, NumberLatestVersion, LooseSemVerLatestVersion class TestImages(unittest.TestCase): - def mocked_k8s_cluster(self): - k = k8s_cluster_mocked() - return k - def build_images_object(self): self.registry = MockedImagesRegistry() - images = Images(self.mocked_k8s_cluster(), [self.registry]) + images = Images([self.registry]) self.add_images(self.registry) return images diff --git a/kluctl/seal/seal_command.py b/kluctl/seal/seal_command.py index 5cfef3e5e..9b2c482ff 100644 --- a/kluctl/seal/seal_command.py +++ b/kluctl/seal/seal_command.py @@ -40,7 +40,6 @@ def seal_command_for_target(kwargs, kluctl_project, target, sealing_config, secr # pass for_seal=True so that .sealme files are rendered as well with project_target_command_context(kwargs, kluctl_project, target, for_seal=True) as cmd_ctx: load_secrets(kluctl_project, target, secrets_loader, cmd_ctx.deployment) - cmd_ctx.deployment_collection.render_deployments() s = DeploymentSealer(cmd_ctx.deployment, cmd_ctx.deployment_collection, cmd_ctx.cluster_vars, cmd_ctx.kluctl_project.sealed_secrets_dir, kwargs["force_reseal"]) From 62a709a027859b3bffd3b048c5e985e53016e6fd Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 9 Sep 2021 10:25:44 +0200 Subject: [PATCH 0105/2916] feat: Allow specifying version filters as strings --- docs/images.md | 13 ++++++++- kluctl/deployment/images.py | 4 +++ kluctl/utils/versions.py | 54 ++++++++++++++++++++++++++++++++++--- 3 files changed, 67 insertions(+), 4 deletions(-) diff --git a/docs/images.md b/docs/images.md index 102b2927c..a6981def8 100644 --- a/docs/images.md +++ b/docs/images.md @@ -44,7 +44,18 @@ This is solved via a Jinja2 function that is available in all templates/resource * `version.regex(regex)`
Only allows versions/tags that match the given regex. Sorting is done the same way as in version.semver(), except that versions do not necessarily need to start with a number. - + +The mentioned version filters can be specified either via native Python objects (Jinja is mostly using python for +expressions) or via strings. For example, + +`images.get_version("my-image", version.prefix("master-", suffix=version.number()))` + +is the same as + +`images.get_version("my-image", "prefix('master-', suffix=number())")`. + +If no version_filter is specified, then it defaults to `version.semver()`. + Example deployment: ```yaml diff --git a/kluctl/deployment/images.py b/kluctl/deployment/images.py index 5c1ce2c93..08540fd3a 100644 --- a/kluctl/deployment/images.py +++ b/kluctl/deployment/images.py @@ -6,6 +6,7 @@ from kluctl.utils.dict_utils import copy_dict, set_dict_value, get_dict_value from kluctl.utils.exceptions import CommandError from kluctl.utils.thread_safe_cache import ThreadSafeMultiCache +from kluctl.utils.versions import build_latest_version_from_str from kluctl.utils.yaml_utils import yaml_dump logger = logging.getLogger(__name__) @@ -41,6 +42,9 @@ def get_latest_image_from_registry(self, image, latest_version): if self.no_registries: return None + if isinstance(latest_version, str): + latest_version = build_latest_version_from_str(latest_version) + tags = self.get_image_tags_from_registry(image) filtered_tags = latest_version.filter(tags) if len(filtered_tags) == 0: diff --git a/kluctl/utils/versions.py b/kluctl/utils/versions.py index fedf9faa5..e4135669a 100644 --- a/kluctl/utils/versions.py +++ b/kluctl/utils/versions.py @@ -1,3 +1,4 @@ +import ast import re LOOSE_SEMVER_REGEX=r"^(([0-9]+)(\.[0-9]+)*)?(.*)$" @@ -100,9 +101,9 @@ def latest(self, versions): raise NotImplementedError() class RegexLatestVersion(LatestVersion): - def __init__(self, p): - self.pattern_str = p - self.pattern = re.compile(p) + def __init__(self, pattern): + self.pattern_str = pattern + self.pattern = re.compile(pattern) def match(self, version): return self.pattern.match(version) @@ -172,3 +173,50 @@ def latest(self, versions): def __str__(self): return f"number()" + +def build_latest_version_from_str(s): + def do_raise(): + raise ValueError("invalid latest_version filter: %s" % s) + + def parse_ast(a): + if not isinstance(a, ast.Call): + do_raise() + + args = [] + kwargs = {} + + def parse_arg(arg): + if isinstance(arg, ast.Constant): + return arg.value + elif isinstance(arg, ast.Call): + return parse_ast(arg) + else: + do_raise() + + for arg in a.args: + args.append(parse_arg(arg)) + for arg in a.keywords: + kwargs[arg.arg] = parse_arg(arg.value) + + name = a.func.id + if name == "regex": + cls = RegexLatestVersion + elif name == "semver": + cls = LooseSemVerLatestVersion + elif name == "prefix": + cls = PrefixLatestVersion + elif name == "number": + cls = NumberLatestVersion + else: + do_raise() + return cls(*args, **kwargs) + + try: + a = ast.parse(s, mode="eval") + if not isinstance(a, ast.Expression): + do_raise() + return parse_ast(a.body) + except ValueError: + raise + except Exception as e: + raise do_raise() From f1bb14e21a596a20561206d56fab017765b2beb3 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 9 Sep 2021 10:26:43 +0200 Subject: [PATCH 0106/2916] fix: Use correct dict keys in load_kluctl_project_config --- kluctl/kluctl_project/kluctl_project.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kluctl/kluctl_project/kluctl_project.py b/kluctl/kluctl_project/kluctl_project.py index 84aba762d..7b888a5d4 100644 --- a/kluctl/kluctl_project/kluctl_project.py +++ b/kluctl/kluctl_project/kluctl_project.py @@ -36,10 +36,10 @@ def load_kluctl_project_config(path): config["clusters"] = [config["clusters"]] secret_sets = set() - for s in get_dict_value(config, "secrets.secretSets", []): + for s in get_dict_value(config, "secretsConfig.secretSets", []): secret_sets.add(s["name"]) for target in config.get("targets", []): - for s in target.get("secretSets", []): + for s in get_dict_value(target, "sealingConfig.secretSets", []): if s not in secret_sets: raise InvalidKluctlProjectConfig("secretSet %s from target %s does not exist" % (s, target["name"])) From 9e74532d5e00647651d9dfe441fa7fcf7e8fd6e1 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 9 Sep 2021 10:27:02 +0200 Subject: [PATCH 0107/2916] fix: Speed up is_iterable --- kluctl/utils/dict_nav_utils.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/kluctl/utils/dict_nav_utils.py b/kluctl/utils/dict_nav_utils.py index 3647a23d4..30b21bac9 100644 --- a/kluctl/utils/dict_nav_utils.py +++ b/kluctl/utils/dict_nav_utils.py @@ -58,6 +58,20 @@ def del_if_falsy(d, k): del d[k] def is_iterable(obj): + if isinstance(obj, list): + return True + if isinstance(obj, tuple): + return True + if isinstance(obj, dict): + return True + if isinstance(obj, str): + return True + if isinstance(obj, bytes): + return True + if isinstance(obj, int) or isinstance(obj, bool): + return False + if isinstance(obj, type): + return False try: iter(obj) except Exception: From 5376b4c9f1e342802818780a41cd3a6cbda06130 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 9 Sep 2021 11:27:31 +0200 Subject: [PATCH 0108/2916] fix: Make object_iterator non-recursive to speed it up --- kluctl/utils/dict_nav_utils.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/kluctl/utils/dict_nav_utils.py b/kluctl/utils/dict_nav_utils.py index 30b21bac9..109545c08 100644 --- a/kluctl/utils/dict_nav_utils.py +++ b/kluctl/utils/dict_nav_utils.py @@ -79,19 +79,19 @@ def is_iterable(obj): else: return True -def _object_iterator(o, path): - yield o, path - if isinstance(o, dict): - for k, v in o.items(): - for o2, p in _object_iterator(v, path + [k]): - yield o2, p - elif not isinstance(o, str) and is_iterable(o): - for i, v in enumerate(o): - for o2, p in _object_iterator(v, path + [str(i)]): - yield o2, p - def object_iterator(o): - return _object_iterator(o, []) + stack = [(o, [])] + + while len(stack) != 0: + o2, p = stack.pop() + yield o2, p + + if isinstance(o2, dict): + for k, v in o2.items(): + stack.append((v, p + [k])) + elif not isinstance(o2, str) and is_iterable(o2): + for i, v in enumerate(o2): + stack.append((v, p + [str(i)])) def del_matching_path(o, path): for _, p in list(object_iterator(o)): From e93af4c048605be65bb1efb00073b1fa457a2473 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 9 Sep 2021 13:27:12 +0200 Subject: [PATCH 0109/2916] fix: Allow to override bootstrap project via --local-deployment --- kluctl/cli/commands.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kluctl/cli/commands.py b/kluctl/cli/commands.py index d16e0045c..2e29f006b 100644 --- a/kluctl/cli/commands.py +++ b/kluctl/cli/commands.py @@ -21,7 +21,8 @@ def bootstrap_command(obj, kwargs): bootstrap_path = os.path.join(get_kluctl_package_dir(), "bootstrap") - kwargs["local_deployment"] = bootstrap_path + if not kwargs.get("local_deployment"): + kwargs["local_deployment"] = bootstrap_path with project_command_context(kwargs) as cmd_ctx: existing, warnings = cmd_ctx.k8s_cluster.get_single_object(ObjectRef("apiextensions.k8s.io/v1", "CustomResourceDefinition", "sealedsecrets.bitnami.com")) if existing: From 31838d6559f33e71e2cb3908bc656865495c2597 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 9 Sep 2021 13:27:21 +0200 Subject: [PATCH 0110/2916] fix: Fix test_images.py --- kluctl/deployment/test_images.py | 65 +++++++++++++++++++------------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/kluctl/deployment/test_images.py b/kluctl/deployment/test_images.py index dc3ac9258..307615d8a 100644 --- a/kluctl/deployment/test_images.py +++ b/kluctl/deployment/test_images.py @@ -2,6 +2,7 @@ from kluctl.deployment.images import Images from kluctl.image_registries.images_registry import ImagesRegistry +from kluctl.utils.dict_utils import get_dict_value from kluctl.utils.versions import PrefixLatestVersion, NumberLatestVersion, LooseSemVerLatestVersion @@ -28,22 +29,21 @@ def add_images(self, registry): registry.add_tags('g', 'p3', '', ['1.1-abc']) def test_gitlab_latest_prefix(self): - self.assertEqual(self.build_images_object().get_image('registry.gitlab.com/g/p', namespace='ns', deployment_name='d', container='c', latest_version=PrefixLatestVersion('z'))[0], 'registry.gitlab.com/g/p:z-10') + self.assertEqual(self.build_images_object().get_latest_image_from_registry('registry.gitlab.com/g/p', latest_version=PrefixLatestVersion('z')), 'registry.gitlab.com/g/p:z-10') def test_gitlab_latest_number(self): - self.assertEqual(self.build_images_object().get_image('registry.gitlab.com/g/p', namespace='ns', deployment_name='d', container='c', latest_version=NumberLatestVersion())[0], 'registry.gitlab.com/g/p:2') + self.assertEqual(self.build_images_object().get_latest_image_from_registry('registry.gitlab.com/g/p', latest_version=NumberLatestVersion()), 'registry.gitlab.com/g/p:2') def test_gitlab_latest_semver_simple(self): - self.assertEqual(self.build_images_object().get_image('registry.gitlab.com/g/p', namespace='ns', deployment_name='d', container='c', latest_version=LooseSemVerLatestVersion())[0], 'registry.gitlab.com/g/p:3.3') + self.assertEqual(self.build_images_object().get_latest_image_from_registry('registry.gitlab.com/g/p', latest_version=LooseSemVerLatestVersion()), 'registry.gitlab.com/g/p:3.3') def test_gitlab_latest_semver_suffix(self): - self.assertEqual(self.build_images_object().get_image('registry.gitlab.com/g/p2', namespace='ns', deployment_name='d', container='c', latest_version=LooseSemVerLatestVersion())[0], 'registry.gitlab.com/g/p2:1.1.1') - self.assertEqual(self.build_images_object().get_image('registry.gitlab.com/g/p3', namespace='ns', deployment_name='d', container='c', latest_version=LooseSemVerLatestVersion())[0], 'registry.gitlab.com/g/p3:1.1') + self.assertEqual(self.build_images_object().get_latest_image_from_registry('registry.gitlab.com/g/p2', latest_version=LooseSemVerLatestVersion()), 'registry.gitlab.com/g/p2:1.1.1') + self.assertEqual(self.build_images_object().get_latest_image_from_registry('registry.gitlab.com/g/p3', latest_version=LooseSemVerLatestVersion()), 'registry.gitlab.com/g/p3:1.1') - def build_images_object_with_deploymet(self, kind): - images = self.build_images_object() - images.k8s_cluster.add_object({ - 'kind': kind, + def build_object(self, image1, image2): + o = { + 'kind': 'Deployment', 'metadata': { 'namespace': 'ns', 'name': 'dep', @@ -51,27 +51,40 @@ def build_images_object_with_deploymet(self, kind): 'spec': { 'template': { 'spec': { - 'containers': [{ - 'name': 'c', - 'image': 'image-from-deployment:123' - }] + 'containers': [] }, }, }, - }) - return images + } + if image1 is not None: + o["spec"]["template"]["spec"]["containers"].append({ + "name": "c1", + "image": image1 + }) + if image2 is not None: + o["spec"]["template"]["spec"]["containers"].append({ + "name": "c2", + "image": image2 + }) + return o + + def do_test_placeholder_resolve(self, image, deployed_object, result_image1, result_image2): + images = self.build_images_object() + latest_version = LooseSemVerLatestVersion() + placeholder = images.gen_image_placeholder(image, latest_version, None, []) + rendered_object = self.build_object(placeholder, placeholder) + images.resolve_placeholders(rendered_object, deployed_object) + self.assertEqual(get_dict_value(rendered_object, "spec.template.spec.containers.0.image"), result_image1) + self.assertEqual(get_dict_value(rendered_object, "spec.template.spec.containers.1.image"), result_image2) + + def test_deployed(self): + self.do_test_placeholder_resolve('registry.gitlab.com/g/p', self.build_object("di1", "di2"), "di1", "di2") + + def test_not_deployed(self): + self.do_test_placeholder_resolve('registry.gitlab.com/g/p', None, "registry.gitlab.com/g/p:3.3", "registry.gitlab.com/g/p:3.3") - def test_deployment(self): - kind = 'Deployment' - self.assertEqual(self.build_images_object_with_deploymet(kind).get_image('registry.gitlab.com/g/p', namespace='ns', deployment_name='dep', container='c')[0], 'image-from-deployment:123') - self.assertEqual(self.build_images_object_with_deploymet(kind).get_image('registry.gitlab.com/g/p', namespace='ns', deployment_name='dep', container='c2')[0], 'registry.gitlab.com/g/p:3.3') - self.assertEqual(self.build_images_object_with_deploymet(kind).get_image('registry.gitlab.com/g/p', namespace='ns', deployment_name='dep2', container='c')[0], 'registry.gitlab.com/g/p:3.3') - - def test_statefulset(self): - kind = "StatefulSet" - self.assertEqual(self.build_images_object_with_deploymet(kind).get_image('registry.gitlab.com/g/p', namespace='ns', deployment_name='StatefulSet/dep', container='c')[0], 'image-from-deployment:123') - self.assertEqual(self.build_images_object_with_deploymet(kind).get_image('registry.gitlab.com/g/p', namespace='ns', deployment_name='StatefulSet/dep', container='c2')[0], 'registry.gitlab.com/g/p:3.3') - self.assertEqual(self.build_images_object_with_deploymet(kind).get_image('registry.gitlab.com/g/p', namespace='ns', deployment_name='StatefulSet/dep2', container='c')[0], 'registry.gitlab.com/g/p:3.3') + def test_container_missing(self): + self.do_test_placeholder_resolve('registry.gitlab.com/g/p', self.build_object("di1", None), "di1", "registry.gitlab.com/g/p:3.3") class MockedImagesRegistry(ImagesRegistry): From 29e3a56a835f8d776df79e38266edd45550b0aae Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 9 Sep 2021 13:28:32 +0200 Subject: [PATCH 0111/2916] tests: Introduce e2e tests (only bootstrap command for now) --- .gitignore | 3 + kluctl/e2e/__init__.py | 0 kluctl/e2e/conftest.py | 66 ++++++++++++++++++++++ kluctl/e2e/kluctl_test_project.py | 83 ++++++++++++++++++++++++++++ kluctl/e2e/test_command_bootstrap.py | 48 ++++++++++++++++ pytest.ini | 5 ++ requirements-dev.txt | 3 + 7 files changed, 208 insertions(+) create mode 100644 kluctl/e2e/__init__.py create mode 100644 kluctl/e2e/conftest.py create mode 100644 kluctl/e2e/kluctl_test_project.py create mode 100644 kluctl/e2e/test_command_bootstrap.py create mode 100644 pytest.ini create mode 100644 requirements-dev.txt diff --git a/.gitignore b/.gitignore index b9920a227..d88970ff2 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,9 @@ .secrets.yml .sealed-secrets +.kube +.pytest-kind + dist build kluctl.egg-info diff --git a/kluctl/e2e/__init__.py b/kluctl/e2e/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/kluctl/e2e/conftest.py b/kluctl/e2e/conftest.py new file mode 100644 index 000000000..3541133a2 --- /dev/null +++ b/kluctl/e2e/conftest.py @@ -0,0 +1,66 @@ +import contextlib +import logging +import subprocess +import time + +import pytest +from pytest_kind import KindCluster + +from kluctl.utils.k8s_status_validation import validate_object +from kluctl.utils.yaml_utils import yaml_load + +logger = logging.getLogger(__name__) + +@contextlib.contextmanager +def kind_cluster_fixture(name): + cluster = KindCluster(name) + try: + cluster.delete() + except: + pass + cluster.create() + try: + yield cluster + finally: + cluster.delete() + +@pytest.fixture(scope="function") +def function_kind_cluster(request): + with kind_cluster_fixture("function") as c: + yield c + +@pytest.fixture(scope="module") +def module_kind_cluster(request): + with kind_cluster_fixture("module") as c: + yield c + + +def wait_for_readiness(kind_cluster: KindCluster, namespace, resource, timeout): + logger.info("Waiting for readiness: %s/%s" % (namespace, resource)) + + t = time.time() + while time.time() - t < timeout: + y = kind_cluster.kubectl("-n", namespace, "get", resource, "-oyaml") + y = yaml_load(y) + + r = validate_object(y, True) + if r.ready: + return True + return False + +def assert_readiness(kind_cluster: KindCluster, namespace, resource, timeout): + if not wait_for_readiness(kind_cluster, namespace, resource, timeout): + raise AssertionError("%s/%s did not get ready in time" % (namespace, resource)) + +def assert_resource_exists(kind_cluster: KindCluster, namespace, resource): + try: + kind_cluster.kubectl("-n", namespace, "get", resource, stderr=subprocess.PIPE) + except subprocess.CalledProcessError as e: + assert False, e.stderr + +def assert_resource_not_exists(kind_cluster: KindCluster, namespace, resource): + try: + kind_cluster.kubectl("-n", namespace, "get", resource, stderr=subprocess.PIPE) + assert False, "'kubectl get' should not have succeeded" + except subprocess.CalledProcessError as e: + assert "(NotFound)" in e.stderr, e.stderr diff --git a/kluctl/e2e/kluctl_test_project.py b/kluctl/e2e/kluctl_test_project.py new file mode 100644 index 000000000..77e358f40 --- /dev/null +++ b/kluctl/e2e/kluctl_test_project.py @@ -0,0 +1,83 @@ +import contextlib +import logging +import os +import subprocess +from tempfile import TemporaryDirectory +from typing import ContextManager + +from pytest_kind import KindCluster + +from kluctl.utils.yaml_utils import yaml_save_file + +logger = logging.getLogger(__name__) + + +class KluctlTestProject: + def __init__(self, deployment_path, clusters_path, sealed_secrets_path, kubeconfig_path): + self.deployment_path = deployment_path + self.clusters_path = clusters_path + self.sealed_secrets_path = sealed_secrets_path + self.kubeconfig_path = kubeconfig_path + self.cluster_name = None + + def add_cluster(self, name, context, vars): + y = { + "cluster": { + "name": name, + "context": context, + **(vars if vars is not None else {}) + }, + } + path = os.path.join(self.clusters_path, "%s.yml" % name) + yaml_save_file(y, path) + self.cluster_name = name + + def kluctl(self, *args: str, **kwargs): + kluctl_path = os.path.join(os.path.dirname(__file__), "..", "..", "cli.py") + args2 = [kluctl_path] + args2 += args + + if self.deployment_path is not None: + args2 += ["--local-deployment", self.deployment_path] + if self.clusters_path is not None: + args2 += ["--local-clusters", self.clusters_path] + if self.sealed_secrets_path is not None: + args2 += ["--local-sealed-secrets", self.sealed_secrets_path] + + env = os.environ.copy() + env.update({ + "KUBECONFIG": str(self.kubeconfig_path), + }) + + logger.info("Running kluctl: %s" % " ".join(args2[1:])) + + return subprocess.check_output( + args2, + env=env, + encoding="utf-8", + **kwargs, + ) + + +@contextlib.contextmanager +def kluctl_project_context(kind_cluster: KindCluster, deployment_path, cluster_name=None, cluster_vars=None) -> ContextManager[KluctlTestProject]: + with TemporaryDirectory() as tmp: + clusters_path = os.path.join(tmp, "clusters") + sealed_secrets_path = os.path.join(tmp, ".sealed-secrets") + + os.makedirs(clusters_path, exist_ok=True) + os.makedirs(sealed_secrets_path, exist_ok=True) + + context = kind_cluster.kubectl("config", "current-context").strip() + if cluster_name is None: + cluster_name = context + + p = KluctlTestProject(deployment_path, + clusters_path, + sealed_secrets_path, + kind_cluster.kubeconfig_path) + p.add_cluster(cluster_name, context, cluster_vars) + try: + yield p + finally: + pass diff --git a/kluctl/e2e/test_command_bootstrap.py b/kluctl/e2e/test_command_bootstrap.py new file mode 100644 index 000000000..8c7eeb914 --- /dev/null +++ b/kluctl/e2e/test_command_bootstrap.py @@ -0,0 +1,48 @@ +import os +import shutil +from tempfile import TemporaryDirectory + +import pytest + +from kluctl import get_kluctl_package_dir +from kluctl.e2e.conftest import assert_readiness, assert_resource_exists, assert_resource_not_exists +from kluctl.e2e.kluctl_test_project import kluctl_project_context +from kluctl.utils.yaml_utils import yaml_save_file, yaml_load_file + +dummy_configmap = """ +apiVersion: v1 +kind: ConfigMap +metadata: + name: dummy-configmap + namespace: kube-system +data: + dummy: value +""" + +@pytest.mark.dependency() +def test_command_bootstrap(module_kind_cluster): + with kluctl_project_context(module_kind_cluster, None) as p: + p.kluctl("bootstrap", "--yes", "--cluster", p.cluster_name) + assert_readiness(module_kind_cluster, "kube-system", "Deployment/sealed-secrets", 60 * 5) + +@pytest.mark.dependency(depends=["test_command_bootstrap"]) +def test_command_bootstrap_upgrade(module_kind_cluster): + bootstrap_path = os.path.join(get_kluctl_package_dir(), "bootstrap") + with TemporaryDirectory() as tmpdir: + shutil.copytree(bootstrap_path, tmpdir, dirs_exist_ok=True) + + with open(os.path.join(tmpdir, "sealed-secrets/dummy.yml"), mode="wt") as f: + f.write(dummy_configmap) + k = yaml_load_file(os.path.join(tmpdir, "sealed-secrets/kustomization.yml")) + k["resources"].append("dummy.yml") + yaml_save_file(k, os.path.join(tmpdir, "sealed-secrets/kustomization.yml")) + + with kluctl_project_context(module_kind_cluster, tmpdir) as p: + p.kluctl("bootstrap", "--yes", "--cluster", p.cluster_name) + assert_resource_exists(module_kind_cluster, "kube-system", "ConfigMap/dummy-configmap") + +@pytest.mark.dependency(depends=["test_command_bootstrap_upgrade"]) +def test_command_bootstrap_purge(module_kind_cluster): + with kluctl_project_context(module_kind_cluster, None) as p: + p.kluctl("bootstrap", "--yes", "--cluster", p.cluster_name) + assert_resource_not_exists(module_kind_cluster, "kube-system", "ConfigMap/dummy-configmap") diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 000000000..5d3400584 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,5 @@ +[pytest] +log_cli = true +log_cli_level = 20 +log_cli_format = "%(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)" +log_cli_date_format = "%Y-%m-%d %H:%M:%S" diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 000000000..0058a51b4 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,3 @@ +pytest>=6.2.5 +pytest-kind>=21.1.3 +pytest-dependency>=0.5.1 From cf6af810eff3bacb7a9cfcc01eaa48ada4ebaa19 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 10 Sep 2021 10:16:21 +0200 Subject: [PATCH 0112/2916] build: Use pytest in actions --- .github/workflows/tests.yml | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index fe8e20465..03b3c0108 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -9,19 +9,30 @@ on: - 'main' jobs: - unittests: + tests: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v2 + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 - name: Set up Python uses: actions/setup-python@v2 with: python-version: 3.8.10 - - name: unittests + - name: Cache dot-kube + id: cache-dot-kube + uses: actions/cache@v2 + with: + path: .kube + key: ${{ runner.os }}-dot-kube + - name: tests run: | pip install . - python -munittest + pip install -r requirements-dev.txt + pytest check-code: runs-on: ubuntu-latest steps: From f15d7fc18e9f0e8893d90be4f419cfea4edcb415 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 10 Sep 2021 10:23:08 +0200 Subject: [PATCH 0113/2916] build: Download/install required tools in tests --- .github/workflows/tests.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 03b3c0108..81f84f4b4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -22,6 +22,22 @@ jobs: uses: actions/setup-python@v2 with: python-version: 3.8.10 + - name: Download required tools + run: | + KUSTOMIZE_VERSION=v4.2.0 + HELM_VERSION=v3.6.3 + KUBESEAL_VERSION=v0.16.0 + wget -O kustomize.tar.gz https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2F${KUSTOMIZE_VERSION}/kustomize_${KUSTOMIZE_VERSION}_linux_amd64.tar.gz && \ + tar xzf kustomize.tar.gz && \ + mv kustomize /usr/bin && \ + rm kustomize.tar.gz + wget -O helm.tar.gz https://get.helm.sh/helm-$HELM_VERSION-linux-amd64.tar.gz && \ + tar xzf helm.tar.gz && \ + mv linux-amd64/helm /usr/bin && \ + rm helm.tar.gz + wget -O kubeseal https://github.com/bitnami-labs/sealed-secrets/releases/download/$KUBESEAL_VERSION/kubeseal-linux-amd64 && \ + chmod +x kubeseal && \ + mv kubeseal /usr/bin - name: Cache dot-kube id: cache-dot-kube uses: actions/cache@v2 From ae075b4608b0f4a6b0b5d06e61a9f2fca8552551 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 10 Sep 2021 10:25:21 +0200 Subject: [PATCH 0114/2916] build: Use sudo to move binaries into /usr/bin --- .github/workflows/tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 81f84f4b4..e2bf7a7f3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -29,15 +29,15 @@ jobs: KUBESEAL_VERSION=v0.16.0 wget -O kustomize.tar.gz https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2F${KUSTOMIZE_VERSION}/kustomize_${KUSTOMIZE_VERSION}_linux_amd64.tar.gz && \ tar xzf kustomize.tar.gz && \ - mv kustomize /usr/bin && \ + sudo mv kustomize /usr/bin && \ rm kustomize.tar.gz wget -O helm.tar.gz https://get.helm.sh/helm-$HELM_VERSION-linux-amd64.tar.gz && \ tar xzf helm.tar.gz && \ - mv linux-amd64/helm /usr/bin && \ + sudo mv linux-amd64/helm /usr/bin && \ rm helm.tar.gz wget -O kubeseal https://github.com/bitnami-labs/sealed-secrets/releases/download/$KUBESEAL_VERSION/kubeseal-linux-amd64 && \ chmod +x kubeseal && \ - mv kubeseal /usr/bin + sudo mv kubeseal /usr/bin - name: Cache dot-kube id: cache-dot-kube uses: actions/cache@v2 From c291d42bf9a62ee9943d14e4ec06b503ba0f7c96 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 10 Sep 2021 13:00:37 +0200 Subject: [PATCH 0115/2916] fix: Use --mirror instead of --bare repos for git caches This fixes issues with default branches not being master --- kluctl/utils/git_utils.py | 55 ++++++++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/kluctl/utils/git_utils.py b/kluctl/utils/git_utils.py index 5e6a3160e..c7c21e27e 100644 --- a/kluctl/utils/git_utils.py +++ b/kluctl/utils/git_utils.py @@ -164,6 +164,13 @@ def execute(self, command, **kwargs): finally: pass +def _is_mirror_remote(path): + with open(os.path.join(path, "config"), "rt") as f: + config = f.read() + if "mirror = true" in config: + return True + return False + @contextmanager def with_git_cache(url, do_lock=True): remote_name = build_remote_name(url) @@ -178,26 +185,44 @@ def with_git_cache(url, do_lock=True): lock.acquire() try: - if not os.path.exists(init_marker): - logger.info(f"Creating bare repo at {cache_dir}") - with build_git_object(url, cache_dir) as (g, url): - g.init("--bare") - g.remote("add", "origin", url) - g.config("remote.origin.fetch", "refs/heads/*:refs/heads/*") - with open(init_marker, "w"): - # only touch it - pass - yield cache_dir + if os.path.exists(init_marker): + # TODO remove this check after some time + if _is_mirror_remote(cache_dir): + yield cache_dir, True + return + + # remove old cache repo (it was a --bare repo in the past, but we want to use --mirror repos now) + logger.info("Cleaning up obsolete --bare cache repo at %s" % cache_dir) + for n in os.listdir(cache_dir): + if n == ".cache.lock": + continue + p = os.path.join(cache_dir, n) + if os.path.isdir(p): + shutil.rmtree(os.path.join(cache_dir, n)) + else: + os.unlink(p) + + logger.info(f"Cloning mirror repo at {cache_dir}") + with build_git_object(url, cache_dir) as (g, url): + g.clone("--mirror", url, "mirror") + for n in os.listdir(os.path.join(cache_dir, "mirror")): + shutil.move(os.path.join(cache_dir, "mirror", n), cache_dir) + shutil.rmtree(os.path.join(cache_dir, "mirror")) + with open(init_marker, "w"): + # only touch it + pass + yield cache_dir, False finally: if do_lock: lock.release() def update_git_cache(url, do_lock=True): - with with_git_cache(url, do_lock=do_lock) as cache_dir: + with with_git_cache(url, do_lock=do_lock) as (cache_dir, need_update): with build_git_object(url, cache_dir) as (g, url): - logger.info(f"Fetching into cache: url='{url}'") + logger.info(f"Updating cache repo: url='{url}'") try: - g.fetch("origin", "-f") + if need_update: + g.remote("update") except GitCommandError as e: if "did not complete in " in e.stderr: logger.info("Git command timed out, deleting cache (%s) to ensure that we don't get into an " @@ -211,8 +236,8 @@ def update_git_cache(url, do_lock=True): def clone_project(url, ref, target_dir, git_cache_up_to_date=None): logger.info(f"Cloning git project: url='{url}', ref='{ref}'") - with with_git_cache(url) as cache_dir: - if git_cache_up_to_date is None or url not in git_cache_up_to_date: + with with_git_cache(url) as (cache_dir, need_update): + if need_update and (git_cache_up_to_date is None or url not in git_cache_up_to_date): update_git_cache(url, do_lock=False) args = ["file://%s" % cache_dir, "--single-branch", target_dir] if ref is not None: From ccf6ab42e709043068f048d0e037bc8abe88a0bd Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 10 Sep 2021 13:01:02 +0200 Subject: [PATCH 0116/2916] fix: Remove unneded ref in simple-with-external-repos example --- examples/simple-with-external-repos/.kluctl.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/simple-with-external-repos/.kluctl.yml b/examples/simple-with-external-repos/.kluctl.yml index 5409b25c4..4edb18728 100644 --- a/examples/simple-with-external-repos/.kluctl.yml +++ b/examples/simple-with-external-repos/.kluctl.yml @@ -1,13 +1,11 @@ clusters: project: url: https://github.com/AljoschaP/kluctl-external-clusters - ref: main subdir: clusters deployment: project: url: https://github.com/AljoschaP/kluctl-external-deployments - ref: main targets: - name: dev-external From 43274301e2978c2c11eaf8e35ef5876c5e10d51a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 10 Sep 2021 13:25:46 +0200 Subject: [PATCH 0117/2916] refactor: Refactor git cache handling to make it clearer --- kluctl/kluctl_project/kluctl_project.py | 8 +- kluctl/utils/git_utils.py | 104 ++++++++++++------------ 2 files changed, 56 insertions(+), 56 deletions(-) diff --git a/kluctl/kluctl_project/kluctl_project.py b/kluctl/kluctl_project/kluctl_project.py index 7b888a5d4..afd69f5f6 100644 --- a/kluctl/kluctl_project/kluctl_project.py +++ b/kluctl/kluctl_project/kluctl_project.py @@ -15,8 +15,8 @@ from kluctl.schemas.schema import validate_kluctl_project_config, parse_git_project, target_config_schema from kluctl.utils.dict_utils import copy_dict, get_dict_value from kluctl.utils.exceptions import InvalidKluctlProjectConfig, CommandError -from kluctl.utils.git_utils import parse_git_url, clone_project, get_git_commit, update_git_cache, git_ls_remote, \ - get_git_ref +from kluctl.utils.git_utils import parse_git_url, clone_project, get_git_commit, git_ls_remote, \ + get_git_ref, ensure_git_cache_updated from kluctl.utils.jinja2_utils import render_dict_strs from kluctl.utils.k8s_cluster_base import load_cluster_config from kluctl.utils.utils import get_tmp_base_dir, MyThreadPoolExecutor @@ -242,7 +242,7 @@ def do_update(key): if url in self.git_cache_up_to_date: return self.git_cache_up_to_date[url] = True - f = executor.submit(update_git_cache, url) + f = executor.submit(ensure_git_cache_updated, url) futures.append(f) do_update("deployment") @@ -257,7 +257,7 @@ def do_update(key): url = parse_git_project(target_config["project"], None).url if url not in self.git_cache_up_to_date: self.git_cache_up_to_date[url] = True - f = executor.submit(update_git_cache, url) + f = executor.submit(ensure_git_cache_updated, url) futures.append(f) if url not in self.refs_for_urls: self.refs_for_urls[url] = executor.submit(git_ls_remote, url) diff --git a/kluctl/utils/git_utils.py b/kluctl/utils/git_utils.py index c7c21e27e..c6079ac0b 100644 --- a/kluctl/utils/git_utils.py +++ b/kluctl/utils/git_utils.py @@ -171,74 +171,74 @@ def _is_mirror_remote(path): return True return False +def _clone_or_update_cache(url, cache_dir, do_update): + init_marker = os.path.join(cache_dir, ".cache.init") + + if os.path.exists(init_marker): + # TODO remove this check after some time + if _is_mirror_remote(cache_dir): + if do_update: + logger.info(f"Updating cache repo: url='{url}'") + g = Git(cache_dir) + g.remote("update") + return + + # remove old cache repo (it was a --bare repo in the past, but we want to use --mirror repos now) + logger.info("Cleaning up obsolete --bare cache repo at %s" % cache_dir) + for n in os.listdir(cache_dir): + if n == ".cache.lock": + continue + p = os.path.join(cache_dir, n) + if os.path.isdir(p): + shutil.rmtree(os.path.join(cache_dir, n)) + else: + os.unlink(p) + + logger.info(f"Cloning mirror repo at {cache_dir}") + with build_git_object(url, cache_dir) as (g, url): + g.clone("--mirror", url, "mirror") + for n in os.listdir(os.path.join(cache_dir, "mirror")): + shutil.move(os.path.join(cache_dir, "mirror", n), cache_dir) + shutil.rmtree(os.path.join(cache_dir, "mirror")) + with open(init_marker, "w"): + # only touch it + pass + @contextmanager -def with_git_cache(url, do_lock=True): +def with_git_cache(url, do_update=True): remote_name = build_remote_name(url) cache_dir = os.path.join(get_cache_base_dir(), remote_name) if not os.path.exists(cache_dir): os.makedirs(cache_dir, exist_ok=True) lock_file = os.path.join(cache_dir, ".cache.lock") - init_marker = os.path.join(cache_dir, ".cache.init") lock = filelock.FileLock(lock_file) - if do_lock: - lock.acquire() + lock.acquire() try: - if os.path.exists(init_marker): - # TODO remove this check after some time - if _is_mirror_remote(cache_dir): - yield cache_dir, True - return - - # remove old cache repo (it was a --bare repo in the past, but we want to use --mirror repos now) - logger.info("Cleaning up obsolete --bare cache repo at %s" % cache_dir) - for n in os.listdir(cache_dir): - if n == ".cache.lock": - continue - p = os.path.join(cache_dir, n) - if os.path.isdir(p): - shutil.rmtree(os.path.join(cache_dir, n)) - else: - os.unlink(p) - - logger.info(f"Cloning mirror repo at {cache_dir}") - with build_git_object(url, cache_dir) as (g, url): - g.clone("--mirror", url, "mirror") - for n in os.listdir(os.path.join(cache_dir, "mirror")): - shutil.move(os.path.join(cache_dir, "mirror", n), cache_dir) - shutil.rmtree(os.path.join(cache_dir, "mirror")) - with open(init_marker, "w"): - # only touch it - pass - yield cache_dir, False + _clone_or_update_cache(url, cache_dir, do_update) + yield cache_dir + except GitCommandError as e: + if "did not complete in " in e.stderr: + logger.info("Git command timed out, deleting cache (%s) to ensure that we don't get into an " + "inconsistent state" % cache_dir) + try: + shutil.rmtree(cache_dir) + except: + pass + raise finally: - if do_lock: - lock.release() + lock.release() -def update_git_cache(url, do_lock=True): - with with_git_cache(url, do_lock=do_lock) as (cache_dir, need_update): - with build_git_object(url, cache_dir) as (g, url): - logger.info(f"Updating cache repo: url='{url}'") - try: - if need_update: - g.remote("update") - except GitCommandError as e: - if "did not complete in " in e.stderr: - logger.info("Git command timed out, deleting cache (%s) to ensure that we don't get into an " - "inconsistent state" % cache_dir) - try: - shutil.rmtree(cache_dir) - except: - pass - raise +def ensure_git_cache_updated(url): + with with_git_cache(url, do_update=True): + pass def clone_project(url, ref, target_dir, git_cache_up_to_date=None): logger.info(f"Cloning git project: url='{url}', ref='{ref}'") - with with_git_cache(url) as (cache_dir, need_update): - if need_update and (git_cache_up_to_date is None or url not in git_cache_up_to_date): - update_git_cache(url, do_lock=False) + need_update = (git_cache_up_to_date is None or url not in git_cache_up_to_date) + with with_git_cache(url, do_update=need_update) as cache_dir: args = ["file://%s" % cache_dir, "--single-branch", target_dir] if ref is not None: args += ["--branch", ref] From 7549bbde68c79224bc2ffb2d45955f338f1fb567 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 10 Sep 2021 15:46:58 +0200 Subject: [PATCH 0118/2916] refactor: Refactor e2e testing to allow better creation of test projects --- kluctl/e2e/kluctl_test_project.py | 157 +++++++++++++++++++-------- kluctl/e2e/test_command_bootstrap.py | 20 ++-- 2 files changed, 125 insertions(+), 52 deletions(-) diff --git a/kluctl/e2e/kluctl_test_project.py b/kluctl/e2e/kluctl_test_project.py index 77e358f40..819ac9310 100644 --- a/kluctl/e2e/kluctl_test_project.py +++ b/kluctl/e2e/kluctl_test_project.py @@ -1,10 +1,11 @@ -import contextlib import logging import os +import shutil import subprocess +import sys from tempfile import TemporaryDirectory -from typing import ContextManager +from git import Git from pytest_kind import KindCluster from kluctl.utils.yaml_utils import yaml_save_file @@ -13,71 +14,139 @@ class KluctlTestProject: - def __init__(self, deployment_path, clusters_path, sealed_secrets_path, kubeconfig_path): - self.deployment_path = deployment_path - self.clusters_path = clusters_path - self.sealed_secrets_path = sealed_secrets_path - self.kubeconfig_path = kubeconfig_path - self.cluster_name = None + def __init__(self, kluctl_project_external=False, + clusters_external=False, deployment_external=False, sealed_secrets_external=False, + local_clusters=None, local_deployment=None, local_sealed_secrets=None): + self.kluctl_project_external = kluctl_project_external + self.clusters_external = clusters_external + self.deployment_external = deployment_external + self.sealed_secrets_external = sealed_secrets_external + self.local_clusters = local_clusters + self.local_deployment = local_deployment + self.local_sealed_secrets = local_sealed_secrets + self.kubeconfigs = [] + + def __enter__(self): + self.base_dir = TemporaryDirectory() + + os.makedirs(self.get_kluctl_project_dir(), exist_ok=True) + os.makedirs(self.get_clusters_dir(), exist_ok=True) + os.makedirs(self.get_sealed_secrets_dir(), exist_ok=True) + os.makedirs(self.get_deployment_dir(), exist_ok=True) + + self._git_init(self.get_kluctl_project_dir()) + if self.clusters_external: + self._git_init(self.get_clusters_dir()) + if self.deployment_external: + self._git_init(self.get_deployment_dir()) + if self.sealed_secrets_external: + self._git_init(self.get_sealed_secrets_dir()) + + #self._commit_dot_kluctl({"targets": []}) + + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + return self.base_dir.__exit__(exc_type, exc_val, exc_tb) + + def _git_init(self, dir): + os.makedirs(dir, exist_ok=True) + g = Git(dir) + g.init() + + def _commit(self, dir, add=None, all=None, message="commit"): + g = Git(dir) + + if add is not None: + for f in add: + g.add(f) + + if all or (add is None and all is None): + g.add("-a") + + g.commit("-m", message) + + def _commit_yaml(self, y, path, message="save yaml"): + yaml_save_file(y, path) + self._commit(os.path.dirname(path), add=[os.path.basename(path)], message=message) + + def _commit_dot_kluctl(self, y, message="update .kluctl.yml"): + self._commit_yaml(y, os.path.join(self.get_kluctl_project_dir(), ".kluctl.yml"), message=message) + + def copy_deployment(self, source): + shutil.copytree(source, self.get_deployment_dir()) + self._commit(self.get_deployment_dir(), all=True, message="copy deployment from %s" % source) def add_cluster(self, name, context, vars): y = { "cluster": { "name": name, "context": context, - **(vars if vars is not None else {}) - }, + **vars, + } } - path = os.path.join(self.clusters_path, "%s.yml" % name) - yaml_save_file(y, path) - self.cluster_name = name + self._commit_yaml(y, os.path.join(self.get_clusters_dir(), "%s.yml" % name), message="add cluster") + + def add_kind_cluster(self, kind_cluster: KindCluster, vars={}): + if kind_cluster.kubeconfig_path not in self.kubeconfigs: + self.kubeconfigs.append(kind_cluster.kubeconfig_path) + context = kind_cluster.kubectl("config", "current-context").strip() + self.add_cluster(context, context, vars) + + def get_kluctl_project_dir(self): + return os.path.join(self.base_dir.name, "kluctl-project") + + def get_clusters_dir(self): + if self.clusters_external: + return os.path.join(self.base_dir.name, "external-clusters") + else: + return os.path.join(self.get_kluctl_project_dir(), "clusters") + + def get_deployment_dir(self): + if self.deployment_external: + return os.path.join(self.base_dir.name, "external-deployment") + else: + return self.get_kluctl_project_dir() + + def get_sealed_secrets_dir(self): + if self.sealed_secrets_external: + return os.path.join(self.base_dir.name, "external-sealed-secrets") + else: + return os.path.join(self.get_kluctl_project_dir(), ".sealed-secrets") def kluctl(self, *args: str, **kwargs): kluctl_path = os.path.join(os.path.dirname(__file__), "..", "..", "cli.py") args2 = [kluctl_path] args2 += args - if self.deployment_path is not None: - args2 += ["--local-deployment", self.deployment_path] - if self.clusters_path is not None: - args2 += ["--local-clusters", self.clusters_path] - if self.sealed_secrets_path is not None: - args2 += ["--local-sealed-secrets", self.sealed_secrets_path] + cwd = None + if self.kluctl_project_external: + args2 += ["--project-url", "file://%s" % self.get_kluctl_project_dir()] + else: + cwd = self.get_kluctl_project_dir() + + if self.local_clusters: + args2 += ["--local-clusters", self.local_clusters] + if self.local_deployment: + args2 += ["--local-deployment", self.local_deployment] + if self.local_sealed_secrets: + args2 += ["--local-sealed-secrets", self.local_sealed_secrets] + if not self.kluctl_project_external: + cwd = self.get_kluctl_project_dir() + + sep = ";" if sys.platform == "win32" else ":" env = os.environ.copy() env.update({ - "KUBECONFIG": str(self.kubeconfig_path), + "KUBECONFIG": sep.join(os.path.abspath(str(x)) for x in self.kubeconfigs), }) logger.info("Running kluctl: %s" % " ".join(args2[1:])) return subprocess.check_output( args2, + cwd=cwd, env=env, encoding="utf-8", **kwargs, ) - - -@contextlib.contextmanager -def kluctl_project_context(kind_cluster: KindCluster, deployment_path, cluster_name=None, cluster_vars=None) -> ContextManager[KluctlTestProject]: - with TemporaryDirectory() as tmp: - clusters_path = os.path.join(tmp, "clusters") - sealed_secrets_path = os.path.join(tmp, ".sealed-secrets") - - os.makedirs(clusters_path, exist_ok=True) - os.makedirs(sealed_secrets_path, exist_ok=True) - - context = kind_cluster.kubectl("config", "current-context").strip() - if cluster_name is None: - cluster_name = context - - p = KluctlTestProject(deployment_path, - clusters_path, - sealed_secrets_path, - kind_cluster.kubeconfig_path) - p.add_cluster(cluster_name, context, cluster_vars) - try: - yield p - finally: - pass diff --git a/kluctl/e2e/test_command_bootstrap.py b/kluctl/e2e/test_command_bootstrap.py index 8c7eeb914..9c48fac1d 100644 --- a/kluctl/e2e/test_command_bootstrap.py +++ b/kluctl/e2e/test_command_bootstrap.py @@ -3,10 +3,11 @@ from tempfile import TemporaryDirectory import pytest +from pytest_kind import KindCluster from kluctl import get_kluctl_package_dir from kluctl.e2e.conftest import assert_readiness, assert_resource_exists, assert_resource_not_exists -from kluctl.e2e.kluctl_test_project import kluctl_project_context +from kluctl.e2e.kluctl_test_project import KluctlTestProject from kluctl.utils.yaml_utils import yaml_save_file, yaml_load_file dummy_configmap = """ @@ -20,9 +21,10 @@ """ @pytest.mark.dependency() -def test_command_bootstrap(module_kind_cluster): - with kluctl_project_context(module_kind_cluster, None) as p: - p.kluctl("bootstrap", "--yes", "--cluster", p.cluster_name) +def test_command_bootstrap(module_kind_cluster: KindCluster): + with KluctlTestProject() as p: + p.add_kind_cluster(module_kind_cluster) + p.kluctl("bootstrap", "--yes", "--cluster", "kind-module") assert_readiness(module_kind_cluster, "kube-system", "Deployment/sealed-secrets", 60 * 5) @pytest.mark.dependency(depends=["test_command_bootstrap"]) @@ -37,12 +39,14 @@ def test_command_bootstrap_upgrade(module_kind_cluster): k["resources"].append("dummy.yml") yaml_save_file(k, os.path.join(tmpdir, "sealed-secrets/kustomization.yml")) - with kluctl_project_context(module_kind_cluster, tmpdir) as p: - p.kluctl("bootstrap", "--yes", "--cluster", p.cluster_name) + with KluctlTestProject(local_deployment=tmpdir) as p: + p.add_kind_cluster(module_kind_cluster) + p.kluctl("bootstrap", "--yes", "--cluster", "kind-module") assert_resource_exists(module_kind_cluster, "kube-system", "ConfigMap/dummy-configmap") @pytest.mark.dependency(depends=["test_command_bootstrap_upgrade"]) def test_command_bootstrap_purge(module_kind_cluster): - with kluctl_project_context(module_kind_cluster, None) as p: - p.kluctl("bootstrap", "--yes", "--cluster", p.cluster_name) + with KluctlTestProject() as p: + p.add_kind_cluster(module_kind_cluster) + p.kluctl("bootstrap", "--yes", "--cluster", "kind-module") assert_resource_not_exists(module_kind_cluster, "kube-system", "ConfigMap/dummy-configmap") From f301a6c29dcb696d32e30fb0ebf277398d1e132e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sun, 12 Sep 2021 16:44:29 +0200 Subject: [PATCH 0119/2916] test: Allow to add kustomize dirs to KluctlTestProject --- kluctl/e2e/kluctl_test_project.py | 126 ++++++++++++++++++++++++--- kluctl/e2e/test_command_bootstrap.py | 9 +- 2 files changed, 118 insertions(+), 17 deletions(-) diff --git a/kluctl/e2e/kluctl_test_project.py b/kluctl/e2e/kluctl_test_project.py index 819ac9310..c792eb056 100644 --- a/kluctl/e2e/kluctl_test_project.py +++ b/kluctl/e2e/kluctl_test_project.py @@ -8,7 +8,7 @@ from git import Git from pytest_kind import KindCluster -from kluctl.utils.yaml_utils import yaml_save_file +from kluctl.utils.yaml_utils import yaml_save_file, yaml_load_file logger = logging.getLogger(__name__) @@ -30,8 +30,8 @@ def __enter__(self): self.base_dir = TemporaryDirectory() os.makedirs(self.get_kluctl_project_dir(), exist_ok=True) - os.makedirs(self.get_clusters_dir(), exist_ok=True) - os.makedirs(self.get_sealed_secrets_dir(), exist_ok=True) + os.makedirs(os.path.join(self.get_clusters_dir(), "clusters"), exist_ok=True) + os.makedirs(os.path.join(self.get_sealed_secrets_dir(), ".sealed-secrets"), exist_ok=True) os.makedirs(self.get_deployment_dir(), exist_ok=True) self._git_init(self.get_kluctl_project_dir()) @@ -42,7 +42,23 @@ def __enter__(self): if self.sealed_secrets_external: self._git_init(self.get_sealed_secrets_dir()) - #self._commit_dot_kluctl({"targets": []}) + def do_update_kluctl(y): + if self.clusters_external: + y["clusters"] = { + "project": "file://%s" % self.get_clusters_dir(), + } + if self.deployment_external: + y["deployment"] = { + "project": "file://%s" % self.get_deployment_dir(), + } + if self.sealed_secrets_external: + y["sealedSecrets"] = { + "project": "file://%s" % self.get_sealed_secrets_dir(), + } + return y + + self.update_kluctl_yaml(do_update_kluctl) + self.update_deployment_yaml(".", lambda y: y) return self @@ -53,8 +69,14 @@ def _git_init(self, dir): os.makedirs(dir, exist_ok=True) g = Git(dir) g.init() + with open(os.path.join(dir, ".dummy"), "wt") as f: + f.write("dummy") + g.add(".dummy") + g.commit("-m", "initial") def _commit(self, dir, add=None, all=None, message="commit"): + assert self.base_dir.name in dir + g = Git(dir) if add is not None: @@ -62,7 +84,7 @@ def _commit(self, dir, add=None, all=None, message="commit"): g.add(f) if all or (add is None and all is None): - g.add("-a") + g.add(".") g.commit("-m", message) @@ -70,13 +92,34 @@ def _commit_yaml(self, y, path, message="save yaml"): yaml_save_file(y, path) self._commit(os.path.dirname(path), add=[os.path.basename(path)], message=message) - def _commit_dot_kluctl(self, y, message="update .kluctl.yml"): - self._commit_yaml(y, os.path.join(self.get_kluctl_project_dir(), ".kluctl.yml"), message=message) + def update_yaml(self, path, update, message=None): + assert self.base_dir.name in path + try: + y = yaml_load_file(path) + except: + y = {} + y = update(y) + yaml_save_file(y, path) + + if message is None: + message = "update %s" % os.path.relpath(path, self.base_dir.name) + self._commit_yaml(y, path, message=message) + + def update_kluctl_yaml(self, update): + self.update_yaml(os.path.join(self.get_kluctl_project_dir(), ".kluctl.yml"), update) + + def update_deployment_yaml(self, dir, update): + self.update_yaml(os.path.join(self.get_deployment_dir(), dir, "deployment.yml"), update) def copy_deployment(self, source): shutil.copytree(source, self.get_deployment_dir()) self._commit(self.get_deployment_dir(), all=True, message="copy deployment from %s" % source) + def copy_kustomize_dir(self, source, target, add_to_deployment): + p = os.path.join(self.get_deployment_dir(), target) + shutil.copytree(source, p) + self._commit(p, all=True, message="copy kustomize dir from %s to %s" % (source, target)) + def add_cluster(self, name, context, vars): y = { "cluster": { @@ -85,13 +128,71 @@ def add_cluster(self, name, context, vars): **vars, } } - self._commit_yaml(y, os.path.join(self.get_clusters_dir(), "%s.yml" % name), message="add cluster") + self._commit_yaml(y, os.path.join(self.get_clusters_dir(), "clusters", "%s.yml" % name), message="add cluster") def add_kind_cluster(self, kind_cluster: KindCluster, vars={}): if kind_cluster.kubeconfig_path not in self.kubeconfigs: self.kubeconfigs.append(kind_cluster.kubeconfig_path) context = kind_cluster.kubectl("config", "current-context").strip() - self.add_cluster(context, context, vars) + self.add_cluster(kind_cluster.name, context, vars) + + def add_target(self, name, cluster, args={}): + def do_update(y): + targets = y.setdefault("targets", []) + targets.append({ + "name": name, + "cluster": cluster, + "args": args, + }) + return y + self.update_kluctl_yaml(do_update) + + def add_deployment_include(self, dir, include): + def do_update(y): + includes = y.setdefault("includes", []) + if any(x["path" == include] for x in includes): + return + includes.append({ + "path": include, + }) + return y + self.update_deployment_yaml(dir, do_update) + + def add_deployment_includes(self, dir): + p = ["."] + for x in os.path.split(dir): + self.add_deployment_include(os.path.join(*p), x) + p.append(x) + + def add_kustomize_deployment(self, dir, resources): + deployment_dir = os.path.dirname(dir) + if deployment_dir != "": + self.add_deployment_includes(deployment_dir) + + abs_deployment_dir = os.path.join(self.get_deployment_dir(), deployment_dir) + abs_kustomize_dir = os.path.join(self.get_deployment_dir(), dir) + + os.makedirs(abs_kustomize_dir, exist_ok=True) + + for name, content in resources.items(): + with open(os.path.join(abs_kustomize_dir, name), "wt") as f: + f.write(content) + + y = { + "apiVersion": "kustomize.config.k8s.io/v1beta1", + "kind": "Kustomization", + "resources": list(resources.keys()), + } + yaml_save_file(y, os.path.join(abs_kustomize_dir, "kustomization.yml")) + self._commit(abs_deployment_dir, all=True, message="add kustomize deployment %s" % dir) + + def do_update(y): + kustomize_dirs = y.setdefault("kustomizeDirs", []) + kustomize_dirs.append({ + "path": os.path.basename(dir) + }) + return y + self.update_deployment_yaml(deployment_dir, do_update) def get_kluctl_project_dir(self): return os.path.join(self.base_dir.name, "kluctl-project") @@ -100,7 +201,7 @@ def get_clusters_dir(self): if self.clusters_external: return os.path.join(self.base_dir.name, "external-clusters") else: - return os.path.join(self.get_kluctl_project_dir(), "clusters") + return self.get_kluctl_project_dir() def get_deployment_dir(self): if self.deployment_external: @@ -112,7 +213,7 @@ def get_sealed_secrets_dir(self): if self.sealed_secrets_external: return os.path.join(self.base_dir.name, "external-sealed-secrets") else: - return os.path.join(self.get_kluctl_project_dir(), ".sealed-secrets") + return self.get_kluctl_project_dir() def kluctl(self, *args: str, **kwargs): kluctl_path = os.path.join(os.path.dirname(__file__), "..", "..", "cli.py") @@ -132,9 +233,6 @@ def kluctl(self, *args: str, **kwargs): if self.local_sealed_secrets: args2 += ["--local-sealed-secrets", self.local_sealed_secrets] - if not self.kluctl_project_external: - cwd = self.get_kluctl_project_dir() - sep = ";" if sys.platform == "win32" else ":" env = os.environ.copy() env.update({ diff --git a/kluctl/e2e/test_command_bootstrap.py b/kluctl/e2e/test_command_bootstrap.py index 9c48fac1d..a432d09dd 100644 --- a/kluctl/e2e/test_command_bootstrap.py +++ b/kluctl/e2e/test_command_bootstrap.py @@ -24,7 +24,8 @@ def test_command_bootstrap(module_kind_cluster: KindCluster): with KluctlTestProject() as p: p.add_kind_cluster(module_kind_cluster) - p.kluctl("bootstrap", "--yes", "--cluster", "kind-module") + p.add_target("test", "module") + p.kluctl("bootstrap", "--yes", "--cluster", "module") assert_readiness(module_kind_cluster, "kube-system", "Deployment/sealed-secrets", 60 * 5) @pytest.mark.dependency(depends=["test_command_bootstrap"]) @@ -41,12 +42,14 @@ def test_command_bootstrap_upgrade(module_kind_cluster): with KluctlTestProject(local_deployment=tmpdir) as p: p.add_kind_cluster(module_kind_cluster) - p.kluctl("bootstrap", "--yes", "--cluster", "kind-module") + p.add_target("test", "module") + p.kluctl("bootstrap", "--yes", "--cluster", "module") assert_resource_exists(module_kind_cluster, "kube-system", "ConfigMap/dummy-configmap") @pytest.mark.dependency(depends=["test_command_bootstrap_upgrade"]) def test_command_bootstrap_purge(module_kind_cluster): with KluctlTestProject() as p: p.add_kind_cluster(module_kind_cluster) - p.kluctl("bootstrap", "--yes", "--cluster", "kind-module") + p.add_target("test", "module") + p.kluctl("bootstrap", "--yes", "--cluster", "module") assert_resource_not_exists(module_kind_cluster, "kube-system", "ConfigMap/dummy-configmap") From 80a814ed736f6b43cc9f7225a07af73727114170 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sun, 12 Sep 2021 16:44:53 +0200 Subject: [PATCH 0120/2916] fix: Always delete cache dir content before initializing it --- kluctl/utils/git_utils.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/kluctl/utils/git_utils.py b/kluctl/utils/git_utils.py index c6079ac0b..c1c21911b 100644 --- a/kluctl/utils/git_utils.py +++ b/kluctl/utils/git_utils.py @@ -185,14 +185,15 @@ def _clone_or_update_cache(url, cache_dir, do_update): # remove old cache repo (it was a --bare repo in the past, but we want to use --mirror repos now) logger.info("Cleaning up obsolete --bare cache repo at %s" % cache_dir) - for n in os.listdir(cache_dir): - if n == ".cache.lock": - continue - p = os.path.join(cache_dir, n) - if os.path.isdir(p): - shutil.rmtree(os.path.join(cache_dir, n)) - else: - os.unlink(p) + + for n in os.listdir(cache_dir): + if n == ".cache.lock": + continue + p = os.path.join(cache_dir, n) + if os.path.isdir(p): + shutil.rmtree(os.path.join(cache_dir, n)) + else: + os.unlink(p) logger.info(f"Cloning mirror repo at {cache_dir}") with build_git_object(url, cache_dir) as (g, url): @@ -306,6 +307,8 @@ def normalize_port(schema, port): return 443 if schema == "ssh": return 22 + if schema == "file": + return None raise Exception("Unknown schema %s" % schema) schema_pattern = re.compile("^([a-z]*)://.*") From af4ad5c73d6520d3703f0ef614248a2d2dc28eaf Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sun, 12 Sep 2021 16:45:27 +0200 Subject: [PATCH 0121/2916] perf: Paralellize deletion of objects --- kluctl/utils/k8s_delete_utils.py | 35 ++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/kluctl/utils/k8s_delete_utils.py b/kluctl/utils/k8s_delete_utils.py index 22c75c8ae..2e20d55b5 100644 --- a/kluctl/utils/k8s_delete_utils.py +++ b/kluctl/utils/k8s_delete_utils.py @@ -4,6 +4,7 @@ from kluctl.utils.k8s_object_utils import get_included_objects, get_label_from_object, get_object_ref, \ get_long_object_name_from_ref, split_api_version, get_filtered_api_resources, \ remove_api_version_from_ref, remove_namespace_from_ref_if_needed +from kluctl.utils.utils import MyThreadPoolExecutor logger = logging.getLogger(__name__) @@ -82,9 +83,31 @@ def find_objects_for_delete(k8s_cluster, labels, inclusion, excluded_objects): return ret def delete_objects(k8s_cluster, object_refs, do_wait): - for ref in object_refs: - try: - k8s_cluster.delete_single_object(ref, do_wait) - logger.info('Deleted object %s' % get_long_object_name_from_ref(ref)) - except Exception as e: - logger.error('Failed to delete object %s. Error=%s' % (get_long_object_name_from_ref(ref), k8s_cluster.get_status_message(e))) + namespaces = set(x for x in object_refs if x.kind == "Namespace") + namespace_names = set(x.name for x in namespaces) + + def do_delete_object(ref): + logger.info('Deleting %s' % get_long_object_name_from_ref(ref)) + k8s_cluster.delete_single_object(ref, do_wait) + + + with MyThreadPoolExecutor(max_workers=8) as executor: + futures = [] + for ref in namespaces: + f = executor.submit(do_delete_object, ref) + futures.append(f) + + for ref in object_refs: + if ref in namespaces: + continue + if ref.namespace in namespace_names: + continue + + f = executor.submit(do_delete_object, ref) + futures.append(f) + + for f in futures: + try: + f.result() + except Exception as e: + logger.error('Failed to delete %s. Error=%s' % (get_long_object_name_from_ref(ref), k8s_cluster.get_status_message(e))) From d19729ccd9da48d42874b1eec3fc766528e6bb44 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sun, 12 Sep 2021 16:45:56 +0200 Subject: [PATCH 0122/2916] fix: Fix a few checks in validate_object --- kluctl/utils/k8s_status_validation.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/kluctl/utils/k8s_status_validation.py b/kluctl/utils/k8s_status_validation.py index 7b21cc284..156696c40 100644 --- a/kluctl/utils/k8s_status_validation.py +++ b/kluctl/utils/k8s_status_validation.py @@ -85,7 +85,7 @@ def value_from_int_or_percent(v, total): try: if o["kind"] == "Pod": - s, r, m = get_condition("Ready", True, True) + s, r, m = get_condition("Ready", False, False) if s != "True": add_not_ready(r or "not-ready", m or "Not ready") elif o["kind"] == "Job": @@ -93,7 +93,7 @@ def value_from_int_or_percent(v, total): if s == "True": add_error(r or "failed", m or "N/A") else: - s, r, m = get_condition("Complete", True, True) + s, r, m = get_condition("Complete", False, False) if s != "True": add_not_ready(r or "not-completed", m or "Not completed") elif o["kind"] in ["Deployment", "ZookeeperCluster"]: @@ -112,7 +112,9 @@ def value_from_int_or_percent(v, total): add_error("no-cluster-ip", "Service does not have a cluster IP") elif svc_type == "LoadBalancer": if len(get_dict_value(o, "spec.externalIPs", [])) == 0: - add_not_ready("loadBalancer.ingress", True, True) + ingress = get_field("loadBalancer.ingress", False, False) + if ingress is None: + add_not_ready("not-ready", "Not ready") elif o["kind"] == "DaemonSet": if get_dict_value(o, "spec.updateStrategy.type") == "RollingUpdate": updated_number_scheduled = get_field("updatedNumberScheduled", True, True) @@ -152,7 +154,7 @@ def value_from_int_or_percent(v, total): elif o["kind"] == "Kafka": for s, r, m in find_conditions("Warning", False, False): add_warning(r or "warning", m or "N/A") - s, r, m = get_condition("Ready", True, True) + s, r, m = get_condition("Ready", False, False) if s != "True": add_not_ready(r or "not-ready", m or "N/A") elif o["kind"] == "Elasticsearch": From 5cb89f6ce88e326411e2ef6d1e41403721597553 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sun, 12 Sep 2021 16:46:13 +0200 Subject: [PATCH 0123/2916] test: e2e tests for external projects --- kluctl/e2e/kluctl_test_project_helpers.py | 34 ++++++++++++++++++ kluctl/e2e/test_external_projects.py | 43 +++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 kluctl/e2e/kluctl_test_project_helpers.py create mode 100644 kluctl/e2e/test_external_projects.py diff --git a/kluctl/e2e/kluctl_test_project_helpers.py b/kluctl/e2e/kluctl_test_project_helpers.py new file mode 100644 index 000000000..df083e94e --- /dev/null +++ b/kluctl/e2e/kluctl_test_project_helpers.py @@ -0,0 +1,34 @@ +from kluctl.e2e.kluctl_test_project import KluctlTestProject + +busybox_pod = """ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {name} + labels: + app: {name} + namespace: {namespace} +spec: + replicas: 1 + selector: + matchLabels: + app: {name} + template: + metadata: + labels: + app: {name} + spec: + containers: + - name: busybox + image: busybox + command: + - sleep + - "1000" +""" + +def add_busybox_deployment(p: KluctlTestProject, dir, name, namespace="default"): + resources = { + "busybox.yml": busybox_pod.format(namespace=namespace, name=name) + } + + p.add_kustomize_deployment(dir, resources) \ No newline at end of file diff --git a/kluctl/e2e/test_external_projects.py b/kluctl/e2e/test_external_projects.py new file mode 100644 index 000000000..390b318be --- /dev/null +++ b/kluctl/e2e/test_external_projects.py @@ -0,0 +1,43 @@ +from pytest_kind import KindCluster + +from kluctl.e2e.conftest import assert_readiness +from kluctl.e2e.kluctl_test_project import KluctlTestProject +from kluctl.e2e.kluctl_test_project_helpers import add_busybox_deployment +from pytest_kind import KindCluster + +from kluctl.e2e.conftest import assert_readiness +from kluctl.e2e.kluctl_test_project import KluctlTestProject +from kluctl.e2e.kluctl_test_project_helpers import add_busybox_deployment + + +def do_test_project(kind_cluster, namespace, **kwargs): + try: + kind_cluster.kubectl("create", "ns", namespace) + except: + pass + + with KluctlTestProject(**kwargs) as p: + p.add_kind_cluster(kind_cluster) + p.add_target("test", kind_cluster.name, {}) + add_busybox_deployment(p, "busybox", "busybox", namespace=namespace) + p.kluctl("deploy", "--yes", "-t", "test") + assert_readiness(kind_cluster, namespace, "Deployment/busybox", 5 * 60) + +def test_external_kluctl_project(module_kind_cluster: KindCluster): + do_test_project(module_kind_cluster, "a", kluctl_project_external=True) + +def test_external_clusters_project(module_kind_cluster: KindCluster): + do_test_project(module_kind_cluster, "b", clusters_external=True) + +def test_external_deployment_project(module_kind_cluster: KindCluster): + do_test_project(module_kind_cluster, "c", deployment_external=True) + +def test_external_sealed_secrets_project(module_kind_cluster: KindCluster): + do_test_project(module_kind_cluster, "d", sealed_secrets_external=True) + +def test_all_prok(module_kind_cluster: KindCluster): + do_test_project(module_kind_cluster, "e", + kluctl_project_external=True, + clusters_external=True, + deployment_external=True, + sealed_secrets_external=True) From cf597f69e1c937df2e98ddcbc6e14c171baaf430 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sun, 12 Sep 2021 16:50:37 +0200 Subject: [PATCH 0124/2916] fix: Install git before running tests --- .github/workflows/tests.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e2bf7a7f3..a233a2418 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,6 +12,10 @@ jobs: tests: runs-on: ubuntu-latest steps: + - name: Install packages + run: | + sudo apt update + sudo apt install -y git - name: Checkout uses: actions/checkout@v2 - name: Set up QEMU From ac39b3e65bdd9fd4ced7c18a586b540fefa7b501 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sun, 12 Sep 2021 16:52:48 +0200 Subject: [PATCH 0125/2916] ci: Split tests and dependency installation --- .github/workflows/tests.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a233a2418..29ca2d3cf 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -48,10 +48,12 @@ jobs: with: path: .kube key: ${{ runner.os }}-dot-kube - - name: tests + - name: Install dependencies run: | pip install . pip install -r requirements-dev.txt + - name: Run tests + run: | pytest check-code: runs-on: ubuntu-latest From 0bb672f0a0261a84c8aaddb18a7bae736adbff40 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sun, 12 Sep 2021 16:56:00 +0200 Subject: [PATCH 0126/2916] ci: Setup git email/user before running tests --- .github/workflows/tests.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 29ca2d3cf..521d60957 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -52,6 +52,10 @@ jobs: run: | pip install . pip install -r requirements-dev.txt + - name: Setup git + run: | + git config --global user.email "no@mail.com" + git config --global user.name "Test User" - name: Run tests run: | pytest From d4e296f1d63a27130294795b7532b32bbbc246d7 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sun, 12 Sep 2021 17:00:08 +0200 Subject: [PATCH 0127/2916] fix: Fix deprecation warning for \- --- kluctl/utils/external_args.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kluctl/utils/external_args.py b/kluctl/utils/external_args.py index 31de28891..dafb0881d 100644 --- a/kluctl/utils/external_args.py +++ b/kluctl/utils/external_args.py @@ -5,7 +5,7 @@ def parse_args(args_list): - r = re.compile('^[a-zA-Z0-9\-_./]*=.*$') + r = re.compile('^[a-zA-Z0-9_./-]*=.*$') args = {} for arg in args_list: if not r.match(arg): From c6e2d2db6935142ff2e688c8fbcc9ecc92045c05 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sun, 12 Sep 2021 17:03:08 +0200 Subject: [PATCH 0128/2916] test: Allow updating/overwriting clusters/targets --- kluctl/e2e/kluctl_test_project.py | 14 ++++++++------ kluctl/e2e/test_command_bootstrap.py | 12 ++++++------ kluctl/e2e/test_external_projects.py | 7 ++++--- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/kluctl/e2e/kluctl_test_project.py b/kluctl/e2e/kluctl_test_project.py index c792eb056..150ff9710 100644 --- a/kluctl/e2e/kluctl_test_project.py +++ b/kluctl/e2e/kluctl_test_project.py @@ -120,7 +120,7 @@ def copy_kustomize_dir(self, source, target, add_to_deployment): shutil.copytree(source, p) self._commit(p, all=True, message="copy kustomize dir from %s to %s" % (source, target)) - def add_cluster(self, name, context, vars): + def update_cluster(self, name, context, vars): y = { "cluster": { "name": name, @@ -128,17 +128,19 @@ def add_cluster(self, name, context, vars): **vars, } } - self._commit_yaml(y, os.path.join(self.get_clusters_dir(), "clusters", "%s.yml" % name), message="add cluster") + self._commit_yaml(y, os.path.join(self.get_clusters_dir(), "clusters", "%s.yml" % name), message="add/update cluster") - def add_kind_cluster(self, kind_cluster: KindCluster, vars={}): + def update_kind_cluster(self, kind_cluster: KindCluster, vars={}): if kind_cluster.kubeconfig_path not in self.kubeconfigs: self.kubeconfigs.append(kind_cluster.kubeconfig_path) context = kind_cluster.kubectl("config", "current-context").strip() - self.add_cluster(kind_cluster.name, context, vars) + self.update_cluster(kind_cluster.name, context, vars) - def add_target(self, name, cluster, args={}): + def update_target(self, name, cluster, args={}): def do_update(y): - targets = y.setdefault("targets", []) + targets = y.get("targets", []) + targets = [x for x in targets if x["name"] != name] + y["targets"] = targets targets.append({ "name": name, "cluster": cluster, diff --git a/kluctl/e2e/test_command_bootstrap.py b/kluctl/e2e/test_command_bootstrap.py index a432d09dd..3c9c83a3b 100644 --- a/kluctl/e2e/test_command_bootstrap.py +++ b/kluctl/e2e/test_command_bootstrap.py @@ -23,8 +23,8 @@ @pytest.mark.dependency() def test_command_bootstrap(module_kind_cluster: KindCluster): with KluctlTestProject() as p: - p.add_kind_cluster(module_kind_cluster) - p.add_target("test", "module") + p.update_kind_cluster(module_kind_cluster) + p.update_target("test", "module") p.kluctl("bootstrap", "--yes", "--cluster", "module") assert_readiness(module_kind_cluster, "kube-system", "Deployment/sealed-secrets", 60 * 5) @@ -41,15 +41,15 @@ def test_command_bootstrap_upgrade(module_kind_cluster): yaml_save_file(k, os.path.join(tmpdir, "sealed-secrets/kustomization.yml")) with KluctlTestProject(local_deployment=tmpdir) as p: - p.add_kind_cluster(module_kind_cluster) - p.add_target("test", "module") + p.update_kind_cluster(module_kind_cluster) + p.update_target("test", "module") p.kluctl("bootstrap", "--yes", "--cluster", "module") assert_resource_exists(module_kind_cluster, "kube-system", "ConfigMap/dummy-configmap") @pytest.mark.dependency(depends=["test_command_bootstrap_upgrade"]) def test_command_bootstrap_purge(module_kind_cluster): with KluctlTestProject() as p: - p.add_kind_cluster(module_kind_cluster) - p.add_target("test", "module") + p.update_kind_cluster(module_kind_cluster) + p.update_target("test", "module") p.kluctl("bootstrap", "--yes", "--cluster", "module") assert_resource_not_exists(module_kind_cluster, "kube-system", "ConfigMap/dummy-configmap") diff --git a/kluctl/e2e/test_external_projects.py b/kluctl/e2e/test_external_projects.py index 390b318be..1a5a43d9d 100644 --- a/kluctl/e2e/test_external_projects.py +++ b/kluctl/e2e/test_external_projects.py @@ -17,9 +17,10 @@ def do_test_project(kind_cluster, namespace, **kwargs): pass with KluctlTestProject(**kwargs) as p: - p.add_kind_cluster(kind_cluster) - p.add_target("test", kind_cluster.name, {}) + p.update_kind_cluster(kind_cluster) + p.update_target("test", kind_cluster.name, {}) add_busybox_deployment(p, "busybox", "busybox", namespace=namespace) + p.kluctl("deploy", "--yes", "-t", "test") assert_readiness(kind_cluster, namespace, "Deployment/busybox", 5 * 60) @@ -35,7 +36,7 @@ def test_external_deployment_project(module_kind_cluster: KindCluster): def test_external_sealed_secrets_project(module_kind_cluster: KindCluster): do_test_project(module_kind_cluster, "d", sealed_secrets_external=True) -def test_all_prok(module_kind_cluster: KindCluster): +def test_all_projects_external(module_kind_cluster: KindCluster): do_test_project(module_kind_cluster, "e", kluctl_project_external=True, clusters_external=True, From f45c2c72894d967751c73a746b90609939dbbfa9 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 13 Sep 2021 09:22:16 +0200 Subject: [PATCH 0129/2916] test: Make it easier to reuse kind clusters while debugging --- kluctl/e2e/conftest.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/kluctl/e2e/conftest.py b/kluctl/e2e/conftest.py index 3541133a2..e0d62deb0 100644 --- a/kluctl/e2e/conftest.py +++ b/kluctl/e2e/conftest.py @@ -11,18 +11,22 @@ logger = logging.getLogger(__name__) +delete_clusters = True + @contextlib.contextmanager def kind_cluster_fixture(name): cluster = KindCluster(name) try: - cluster.delete() + if delete_clusters: + cluster.delete() except: pass cluster.create() try: yield cluster finally: - cluster.delete() + if delete_clusters: + cluster.delete() @pytest.fixture(scope="function") def function_kind_cluster(request): From e221f40c407d07a0b875682950a5426422f03511 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 13 Sep 2021 09:22:34 +0200 Subject: [PATCH 0130/2916] test: Return yaml when calling assert_resource_exists --- kluctl/e2e/conftest.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kluctl/e2e/conftest.py b/kluctl/e2e/conftest.py index e0d62deb0..3052ef1ec 100644 --- a/kluctl/e2e/conftest.py +++ b/kluctl/e2e/conftest.py @@ -58,7 +58,8 @@ def assert_readiness(kind_cluster: KindCluster, namespace, resource, timeout): def assert_resource_exists(kind_cluster: KindCluster, namespace, resource): try: - kind_cluster.kubectl("-n", namespace, "get", resource, stderr=subprocess.PIPE) + y = kind_cluster.kubectl("-n", namespace, "get", resource, "-o", "yaml", stderr=subprocess.PIPE) + return yaml_load(y) except subprocess.CalledProcessError as e: assert False, e.stderr From 780424df4966389e7f3bf3591f1f4cbb285020ad Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 13 Sep 2021 09:22:58 +0200 Subject: [PATCH 0131/2916] test: Don't try to commit when unchanged --- kluctl/e2e/kluctl_test_project.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/kluctl/e2e/kluctl_test_project.py b/kluctl/e2e/kluctl_test_project.py index 150ff9710..9396dc7fb 100644 --- a/kluctl/e2e/kluctl_test_project.py +++ b/kluctl/e2e/kluctl_test_project.py @@ -8,6 +8,7 @@ from git import Git from pytest_kind import KindCluster +from kluctl.utils.dict_utils import copy_dict from kluctl.utils.yaml_utils import yaml_save_file, yaml_load_file logger = logging.getLogger(__name__) @@ -96,9 +97,14 @@ def update_yaml(self, path, update, message=None): assert self.base_dir.name in path try: y = yaml_load_file(path) + orig_y = copy_dict(y) except: y = {} + orig_y = None y = update(y) + if y == orig_y: + return + yaml_save_file(y, path) if message is None: From 6965d406c6b788012377ed2225b73e2ac897ea10 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 13 Sep 2021 09:25:54 +0200 Subject: [PATCH 0132/2916] test: Recreate namespaces in do_test_project --- kluctl/e2e/conftest.py | 9 +++++++++ kluctl/e2e/kluctl_test_project_helpers.py | 1 + kluctl/e2e/test_external_projects.py | 7 ++----- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/kluctl/e2e/conftest.py b/kluctl/e2e/conftest.py index 3052ef1ec..cf0dd1149 100644 --- a/kluctl/e2e/conftest.py +++ b/kluctl/e2e/conftest.py @@ -38,6 +38,15 @@ def module_kind_cluster(request): with kind_cluster_fixture("module") as c: yield c +def recreate_namespace(kind_cluster: KindCluster, namespace): + try: + kind_cluster.kubectl("delete", "ns", namespace) + except: + pass + try: + kind_cluster.kubectl("create", "ns", namespace) + except: + pass def wait_for_readiness(kind_cluster: KindCluster, namespace, resource, timeout): logger.info("Waiting for readiness: %s/%s" % (namespace, resource)) diff --git a/kluctl/e2e/kluctl_test_project_helpers.py b/kluctl/e2e/kluctl_test_project_helpers.py index df083e94e..5468b6b2a 100644 --- a/kluctl/e2e/kluctl_test_project_helpers.py +++ b/kluctl/e2e/kluctl_test_project_helpers.py @@ -18,6 +18,7 @@ labels: app: {name} spec: + terminationGracePeriodSeconds: 0 containers: - name: busybox image: busybox diff --git a/kluctl/e2e/test_external_projects.py b/kluctl/e2e/test_external_projects.py index 1a5a43d9d..31dd500f5 100644 --- a/kluctl/e2e/test_external_projects.py +++ b/kluctl/e2e/test_external_projects.py @@ -5,16 +5,13 @@ from kluctl.e2e.kluctl_test_project_helpers import add_busybox_deployment from pytest_kind import KindCluster -from kluctl.e2e.conftest import assert_readiness +from kluctl.e2e.conftest import assert_readiness, recreate_namespace from kluctl.e2e.kluctl_test_project import KluctlTestProject from kluctl.e2e.kluctl_test_project_helpers import add_busybox_deployment def do_test_project(kind_cluster, namespace, **kwargs): - try: - kind_cluster.kubectl("create", "ns", namespace) - except: - pass + recreate_namespace(kind_cluster, namespace) with KluctlTestProject(**kwargs) as p: p.update_kind_cluster(kind_cluster) From 6404da41633367e35a36b654965e11f94237a33c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 13 Sep 2021 09:26:18 +0200 Subject: [PATCH 0133/2916] test: Properly test updates to external projects --- kluctl/e2e/kluctl_test_project.py | 17 +++++++----- kluctl/e2e/test_external_projects.py | 40 +++++++++++++++++++++++----- 2 files changed, 43 insertions(+), 14 deletions(-) diff --git a/kluctl/e2e/kluctl_test_project.py b/kluctl/e2e/kluctl_test_project.py index 9396dc7fb..75e5e1b1b 100644 --- a/kluctl/e2e/kluctl_test_project.py +++ b/kluctl/e2e/kluctl_test_project.py @@ -127,14 +127,17 @@ def copy_kustomize_dir(self, source, target, add_to_deployment): self._commit(p, all=True, message="copy kustomize dir from %s to %s" % (source, target)) def update_cluster(self, name, context, vars): - y = { - "cluster": { - "name": name, - "context": context, - **vars, + path = os.path.join(self.get_clusters_dir(), "clusters", "%s.yml" % name) + def do_update(y): + y = { + "cluster": { + "name": name, + "context": context, + **vars, + } } - } - self._commit_yaml(y, os.path.join(self.get_clusters_dir(), "clusters", "%s.yml" % name), message="add/update cluster") + return y + self.update_yaml(path, do_update, message="add/update cluster %s" % name) def update_kind_cluster(self, kind_cluster: KindCluster, vars={}): if kind_cluster.kubeconfig_path not in self.kubeconfigs: diff --git a/kluctl/e2e/test_external_projects.py b/kluctl/e2e/test_external_projects.py index 31dd500f5..4301dad29 100644 --- a/kluctl/e2e/test_external_projects.py +++ b/kluctl/e2e/test_external_projects.py @@ -1,26 +1,52 @@ from pytest_kind import KindCluster -from kluctl.e2e.conftest import assert_readiness -from kluctl.e2e.kluctl_test_project import KluctlTestProject -from kluctl.e2e.kluctl_test_project_helpers import add_busybox_deployment -from pytest_kind import KindCluster - from kluctl.e2e.conftest import assert_readiness, recreate_namespace +from kluctl.e2e.conftest import assert_resource_not_exists, assert_resource_exists from kluctl.e2e.kluctl_test_project import KluctlTestProject from kluctl.e2e.kluctl_test_project_helpers import add_busybox_deployment +from kluctl.utils.dict_utils import get_dict_value +config_map = """ +apiVersion: v1 +kind: ConfigMap +metadata: + name: "cm" + namespace: {namespace} +data: + cluster_var: {{{{ cluster.cluster_var }}}} + target_var: {{{{ args.target_var }}}} +""" def do_test_project(kind_cluster, namespace, **kwargs): recreate_namespace(kind_cluster, namespace) with KluctlTestProject(**kwargs) as p: - p.update_kind_cluster(kind_cluster) - p.update_target("test", kind_cluster.name, {}) + p.update_kind_cluster(kind_cluster, {"cluster_var": "cluster_value1"}) + p.update_target("test", kind_cluster.name, {"target_var": "target_value1"}) add_busybox_deployment(p, "busybox", "busybox", namespace=namespace) p.kluctl("deploy", "--yes", "-t", "test") assert_readiness(kind_cluster, namespace, "Deployment/busybox", 5 * 60) + assert_resource_not_exists(kind_cluster, namespace, "ConfigMap/cm") + p.add_kustomize_deployment("cm", resources={"cm.yml": config_map.format(namespace=namespace)}) + p.kluctl("deploy", "--yes", "-t", "test") + y = assert_resource_exists(kind_cluster, namespace, "ConfigMap/cm") + assert get_dict_value(y, "data.cluster_var") == "cluster_value1" + assert get_dict_value(y, "data.target_var") == "target_value1" + + p.update_kind_cluster(kind_cluster, {"cluster_var": "cluster_value2"}) + p.kluctl("deploy", "--yes", "-t", "test") + y = assert_resource_exists(kind_cluster, namespace, "ConfigMap/cm") + assert get_dict_value(y, "data.cluster_var") == "cluster_value2" + assert get_dict_value(y, "data.target_var") == "target_value1" + + p.update_target("test", kind_cluster.name, {"target_var": "target_value2"}) + p.kluctl("deploy", "--yes", "-t", "test") + y = assert_resource_exists(kind_cluster, namespace, "ConfigMap/cm") + assert get_dict_value(y, "data.cluster_var") == "cluster_value2" + assert get_dict_value(y, "data.target_var") == "target_value2" + def test_external_kluctl_project(module_kind_cluster: KindCluster): do_test_project(module_kind_cluster, "a", kluctl_project_external=True) From 1f76160adbae38bd536c6aa465250718da263ce3 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 13 Sep 2021 14:14:17 +0200 Subject: [PATCH 0134/2916] feat: Make --replace-on-error be less disruptive by using PUT instead of delete+create Also introduce --force-replace-on-error that does the same as --replace-on-error did before --- docs/commands.md | 15 +++++---- kluctl/cli/commands.py | 5 +-- kluctl/cli/main_cli_group.py | 6 +++- kluctl/deployment/apply_util.py | 39 ++++++++++++++++------ kluctl/deployment/deployment_collection.py | 16 ++++----- 5 files changed, 54 insertions(+), 27 deletions(-) diff --git a/docs/commands.md b/docs/commands.md index 01e79cff2..1c3da35d3 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -110,8 +110,9 @@ In addition, the following arguments are available: -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. --dry-run Performs all kubernetes API calls in dry-run mode. --force-apply Force conflict resolution when applying. See documentation for details - --replace-on-error When patching an object fails, try to delete it and then retry. See documentation - for more details. + --replace-on-error When patching an object fails, try to replace. See documentation for more details. + --force-replace-on-error Same as --replace-on-error, but also try to delete and re-create objects. See + documentation for more details. --abort-on-error Abort deploying when an error occurs instead of trying the remaining deployments -o, --output TEXT Specify output format and target file, in the format 'format=path'. Format can either be 'diff' or 'yaml'. Can be specified multiple times. The actual format for @@ -143,8 +144,9 @@ In addition, the following arguments are available: -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. --dry-run Performs all kubernetes API calls in dry-run mode. --force-apply Force conflict resolution when applying. See documentation for details - --replace-on-error When patching an object fails, try to delete it and then retry. See documentation - for more details. + --replace-on-error When patching an object fails, try to replace. See documentation for more details. + --force-replace-on-error Same as --replace-on-error, but also try to delete and re-create objects. See + documentation for more details. --abort-on-error Abort deploying when an error occurs instead of trying the remaining deployments -o, --output TEXT Specify output format and target file, in the format 'format=path'. Format can either be 'diff' or 'yaml'. Can be specified multiple times. The actual format for @@ -208,8 +210,9 @@ In addition, the following arguments are available: ``` Misc arguments: --force-apply Force conflict resolution when applying. See documentation for details - --replace-on-error When patching an object fails, try to delete it and then retry. See documentation - for more details. + --replace-on-error When patching an object fails, try to replace. See documentation for more details. + --force-replace-on-error Same as --replace-on-error, but also try to delete and re-create objects. See + documentation for more details. --ignore-tags Ignores changes in tags when diffing --ignore-labels Ignores changes in labels when diffing --ignore-annotations Ignores changes in annotations when diffing diff --git a/kluctl/cli/commands.py b/kluctl/cli/commands.py index 2e29f006b..de1e7ce40 100644 --- a/kluctl/cli/commands.py +++ b/kluctl/cli/commands.py @@ -42,7 +42,8 @@ def deploy_command2(obj, kwargs, cmd_ctx): if not kwargs["yes"] and not kwargs["dry_run"]: click.confirm("Do you really want to deploy to the context/cluster %s?" % cmd_ctx.k8s_cluster.context, err=True, abort=True) - diff_result = cmd_ctx.deployment_collection.deploy(cmd_ctx.k8s_cluster, kwargs["force_apply"], kwargs["replace_on_error"], kwargs["abort_on_error"]) + diff_result = cmd_ctx.deployment_collection.deploy(cmd_ctx.k8s_cluster, kwargs["force_apply"], + kwargs["replace_on_error"], kwargs["force_replace_on_error"], kwargs["abort_on_error"]) deleted_objects = cmd_ctx.deployment_collection.find_purge_objects(cmd_ctx.k8s_cluster) output_diff_result(kwargs["output"], cmd_ctx.deployment_collection, diff_result, deleted_objects) if diff_result.errors: @@ -51,7 +52,7 @@ def deploy_command2(obj, kwargs, cmd_ctx): def diff_command(obj, kwargs): with project_command_context(kwargs) as cmd_ctx: result = cmd_ctx.deployment_collection.diff( - cmd_ctx.k8s_cluster, kwargs["force_apply"], kwargs["replace_on_error"], + cmd_ctx.k8s_cluster, kwargs["force_apply"], kwargs["replace_on_error"], kwargs["force_replace_on_error"], kwargs["ignore_tags"], kwargs["ignore_labels"], kwargs["ignore_annotations"], kwargs["ignore_order"]) deleted_objects = cmd_ctx.deployment_collection.find_purge_objects(cmd_ctx.k8s_cluster) output_diff_result(kwargs["output"], cmd_ctx.deployment_collection, result, deleted_objects) diff --git a/kluctl/cli/main_cli_group.py b/kluctl/cli/main_cli_group.py index 5038b13e5..2d2c24b2e 100644 --- a/kluctl/cli/main_cli_group.py +++ b/kluctl/cli/main_cli_group.py @@ -140,7 +140,11 @@ def misc_arguments(yes=False, dry_run=False, force_apply=False, replace_on_error default=False, is_flag=True)) if replace_on_error: options.append(optgroup.option("--replace-on-error", - help="When patching an object fails, try to delete it and then retry. " + help="When patching an object fails, try to replace. " + "See documentation for more details.", + default=False, is_flag=True)) + options.append(optgroup.option("--force-replace-on-error", + help="Same as --replace-on-error, but also try to delete and re-create objects. " "See documentation for more details.", default=False, is_flag=True)) if ignore_labels: diff --git a/kluctl/deployment/apply_util.py b/kluctl/deployment/apply_util.py index ed70f2494..d35333a8c 100644 --- a/kluctl/deployment/apply_util.py +++ b/kluctl/deployment/apply_util.py @@ -6,7 +6,7 @@ from kubernetes.dynamic.exceptions import ResourceNotFoundError from kluctl.diff.managed_fields import remove_non_managed_fields2 -from kluctl.utils.dict_utils import get_dict_value +from kluctl.utils.dict_utils import get_dict_value, copy_dict, set_dict_value from kluctl.utils.k8s_object_utils import get_object_ref, get_long_object_name, get_long_object_name_from_ref from kluctl.utils.k8s_status_validation import validate_object from kluctl.utils.utils import MyThreadPoolExecutor @@ -14,11 +14,12 @@ logger = logging.getLogger(__name__) class ApplyUtil: - def __init__(self, deployment_collection, k8s_cluster, force_apply, replace_on_error, dry_run, abort_on_error): + def __init__(self, deployment_collection, k8s_cluster, force_apply, replace_on_error, force_replace_on_error, dry_run, abort_on_error): self.deployment_collection = deployment_collection self.k8s_cluster = k8s_cluster self.force_apply = force_apply - self.replace_on_error = replace_on_error + self.replace_on_error = replace_on_error or force_replace_on_error + self.force_replace_on_error = force_replace_on_error self.dry_run = dry_run self.abort_on_error = abort_on_error @@ -66,18 +67,36 @@ def apply_object(self, x): if not self.replace_on_error: self.handle_error(ref, self.k8s_cluster.get_status_message(e)) return - logger.info("Patching %s failed, retrying by deleting and re-applying" % + logger.info("Patching %s failed, retrying with replace instead of patch" % get_long_object_name_from_ref(ref)) try: - self.k8s_cluster.delete_single_object(ref, force_dry_run=self.dry_run, ignore_not_found=True) - if not self.dry_run and not self.k8s_cluster.dry_run: - r, patch_warnings = self.k8s_cluster.patch_object(x, force_apply=True) - self.handle_result(r, patch_warnings) - else: - self.handle_result(x, []) + remote_object = self.deployment_collection.remote_objects.get(ref) + if remote_object is None: + self.handle_error(ref, self.k8s_cluster.get_status_message(e)) + return + resource_version = get_dict_value(remote_object, "metadata.resourceVersion") + x2 = set_dict_value(x, "metadata.resourceVersion", get_dict_value(remote_object, "metadata.resourceVersion"), do_clone=True) + r, patch_warnings = self.k8s_cluster.replace_object(x2, force_dry_run=self.dry_run, resource_version=resource_version) + self.handle_result(r, patch_warnings) except ApiException as e2: self.handle_error(ref, self.k8s_cluster.get_status_message(e2)) + if not self.force_replace_on_error: + self.handle_error(ref, self.k8s_cluster.get_status_message(e)) + return + + logger.info("Patching %s failed, retrying by deleting and re-applying" % + get_long_object_name_from_ref(ref)) + try: + self.k8s_cluster.delete_single_object(ref, force_dry_run=self.dry_run, ignore_not_found=True) + if not self.dry_run and not self.k8s_cluster.dry_run: + r, patch_warnings = self.k8s_cluster.patch_object(x, force_apply=True) + self.handle_result(r, patch_warnings) + else: + self.handle_result(x, []) + except ApiException as e2: + self.handle_error(ref, self.k8s_cluster.get_status_message(e2)) + def wait_object(self, ref): if self.dry_run or self.k8s_cluster.dry_run: return True diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index b49653213..8c1181164 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -165,17 +165,17 @@ def prepare(self, k8s_cluster, only_render): self.update_remote_objects(k8s_cluster) self.resolve_image_placeholders(k8s_cluster) - def deploy(self, k8s_cluster, force_apply, replace_on_error, abort_on_error): + def deploy(self, k8s_cluster, force_apply, replace_on_error, force_replace_on_error, abort_on_error): self.clear_errors_and_warnings() - applied_objects = self.do_deploy(k8s_cluster, force_apply, replace_on_error, + applied_objects = self.do_deploy(k8s_cluster, force_apply, replace_on_error, force_replace_on_error, False, abort_on_error) new_objects, changed_objects = self.do_diff(k8s_cluster, applied_objects, False, False, False, False) return DeployDiffResult(new_objects=new_objects, changed_objects=changed_objects, errors=list(self.api_errors), warnings=list(self.api_warnings)) - def diff(self, k8s_cluster, force_apply, replace_on_error, ignore_tags, ignore_labels, ignore_annotations, ignore_order): + def diff(self, k8s_cluster, force_apply, replace_on_error, force_replace_on_error, ignore_tags, ignore_labels, ignore_annotations, ignore_order): self.clear_errors_and_warnings() - applied_objects = self.do_deploy(k8s_cluster, force_apply, replace_on_error, + applied_objects = self.do_deploy(k8s_cluster, force_apply, replace_on_error, force_replace_on_error, True, False) new_objects, changed_objects = self.do_diff(k8s_cluster, applied_objects, ignore_tags, ignore_labels, ignore_annotations, ignore_order) return DeployDiffResult(new_objects=new_objects, changed_objects=changed_objects, @@ -287,16 +287,16 @@ def find_purge_objects(self, k8s_cluster): excluded_objects = list(self.local_objects_by_ref().keys()) return find_objects_for_delete(k8s_cluster, labels, self.inclusion, excluded_objects) - def do_apply(self, k8s_cluster, force_apply, replace_on_error, dry_run, abort_on_error): - apply_util = ApplyUtil(self, k8s_cluster, force_apply, replace_on_error, dry_run, abort_on_error) + def do_apply(self, k8s_cluster, force_apply, replace_on_error, force_replace_on_error, dry_run, abort_on_error): + apply_util = ApplyUtil(self, k8s_cluster, force_apply, replace_on_error, force_replace_on_error, dry_run, abort_on_error) apply_util.apply_deployments() return apply_util.applied_objects - def do_deploy(self, k8s_cluster, force_apply, replace_on_error, dry_run, abort_on_error): + def do_deploy(self, k8s_cluster, force_apply, replace_on_error, force_replace_on_error, dry_run, abort_on_error): # TODO remove this self.migrate_to_new_manager(k8s_cluster) - return self.do_apply(k8s_cluster, force_apply, replace_on_error, dry_run, abort_on_error) + return self.do_apply(k8s_cluster, force_apply, replace_on_error, force_replace_on_error, dry_run, abort_on_error) def do_diff(self, k8s_cluster, applied_objects, ignore_tags, ignore_labels, ignore_annotations, ignore_order): diff_objects = {} From 6024450af241e09d29301c2d148556f947116792 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 13 Sep 2021 15:51:26 +0200 Subject: [PATCH 0135/2916] fix: Fix validate command --- kluctl/cli/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kluctl/cli/commands.py b/kluctl/cli/commands.py index de1e7ce40..eee2897f6 100644 --- a/kluctl/cli/commands.py +++ b/kluctl/cli/commands.py @@ -115,7 +115,7 @@ def validate_command(obj, kwargs): with project_command_context(kwargs) as cmd_ctx: while True: - result = cmd_ctx.deployment_collection.validate(cmd_ctx.k8s_cluster) + result = cmd_ctx.deployment_collection.validate() failed = len(result.errors) != 0 or (warnings_as_errors and len(result.warnings) != 0) output_validate_result(kwargs["output"], result) From 26928d50af45a2aea11070ff2de0683fd76c50b2 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 13 Sep 2021 15:51:36 +0200 Subject: [PATCH 0136/2916] fix: Fix seal command --- kluctl/cli/utils.py | 3 ++- kluctl/seal/seal_command.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/kluctl/cli/utils.py b/kluctl/cli/utils.py index 20d9a7427..d9bd56500 100644 --- a/kluctl/cli/utils.py +++ b/kluctl/cli/utils.py @@ -155,7 +155,8 @@ def project_target_command_context(kwargs, kluctl_project, target, for fi in fixed_images: c.images.add_fixed_image(fi) - c.prepare(k8s_cluster, for_seal) + if not for_seal: + c.prepare(k8s_cluster, for_seal) ctx = CommandContext(kluctl_project=kluctl_project, target=target, cluster_vars=cluster_vars, k8s_cluster=k8s_cluster, diff --git a/kluctl/seal/seal_command.py b/kluctl/seal/seal_command.py index 9b2c482ff..1dd4c2421 100644 --- a/kluctl/seal/seal_command.py +++ b/kluctl/seal/seal_command.py @@ -40,6 +40,7 @@ def seal_command_for_target(kwargs, kluctl_project, target, sealing_config, secr # pass for_seal=True so that .sealme files are rendered as well with project_target_command_context(kwargs, kluctl_project, target, for_seal=True) as cmd_ctx: load_secrets(kluctl_project, target, secrets_loader, cmd_ctx.deployment) + cmd_ctx.deployment_collection.prepare(cmd_ctx.k8s_cluster, True) s = DeploymentSealer(cmd_ctx.deployment, cmd_ctx.deployment_collection, cmd_ctx.cluster_vars, cmd_ctx.kluctl_project.sealed_secrets_dir, kwargs["force_reseal"]) From 9b476e9eff7b094bc1e10ead874162ffbcb63901 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 14 Sep 2021 10:01:19 +0200 Subject: [PATCH 0137/2916] fix: Fix _clone_or_update_cache to also add credentials when updating the mirror --- kluctl/utils/git_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kluctl/utils/git_utils.py b/kluctl/utils/git_utils.py index c1c21911b..102701146 100644 --- a/kluctl/utils/git_utils.py +++ b/kluctl/utils/git_utils.py @@ -179,8 +179,8 @@ def _clone_or_update_cache(url, cache_dir, do_update): if _is_mirror_remote(cache_dir): if do_update: logger.info(f"Updating cache repo: url='{url}'") - g = Git(cache_dir) - g.remote("update") + with build_git_object(url, cache_dir) as (g, url): + g.remote("update") return # remove old cache repo (it was a --bare repo in the past, but we want to use --mirror repos now) From e111560092b85c377c14c73e9b3aef175c148f88 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 14 Sep 2021 12:56:54 +0200 Subject: [PATCH 0138/2916] fix: Fix invalid use of local_deployment arg in helm-update command --- kluctl/cli/util_commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kluctl/cli/util_commands.py b/kluctl/cli/util_commands.py index 3b35f6cd8..568dcadc9 100644 --- a/kluctl/cli/util_commands.py +++ b/kluctl/cli/util_commands.py @@ -97,7 +97,7 @@ def helm_update_command(upgrade, commit, kwargs): if commit: msg = "Updated helm chart %s from %s to %s" % (dirpath, old_version, new_version) logger.info("Committing: %s" % msg) - g = Git(working_dir=kwargs["deployment"]) + g = Git(working_dir=kwargs["local_deployment"]) g.add(os.path.join(dirpath, "charts")) g.add(path) g.commit("-m", msg, os.path.join(dirpath, "charts"), path) From 6517da80acb26f90c6eb4f77754153df43013056 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 14 Sep 2021 18:18:20 +0200 Subject: [PATCH 0139/2916] refactor: Remove unnecessary state flags --- kluctl/deployment/deployment_collection.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index 8c1181164..bec3fc2e0 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -43,8 +43,6 @@ def __init__(self, project, images, inclusion, tmpdir, for_seal): self.for_seal = for_seal self.deployments = self._collect_deployments(self.project) - self.is_rendered = False - self.is_built = False self.remote_objects = {} self.api_errors = set() @@ -75,8 +73,6 @@ def _collect_deployments(self, project): return ret def render_deployments(self): - if self.is_rendered: - return logger.info("Rendering templates and Helm charts") with MyThreadPoolExecutor(max_workers=16) as executor: jobs = [] @@ -96,11 +92,8 @@ def render_deployments(self): for d in self.deployments: d.prepare_kustomization_yaml() d.resolve_sealed_secrets() - self.is_rendered = True def build_kustomize_objects(self, k8s_cluster): - if self.is_built: - return logger.info("Building kustomize objects") with MyThreadPoolExecutor() as executor: @@ -116,8 +109,6 @@ def build_kustomize_objects(self, k8s_cluster): if should_remove_namespace(k8s_cluster, get_object_ref(o)): del o["metadata"]["namespace"] - self.is_built = True - def update_remote_objects(self, k8s_cluster): logger.info("Updating remote objects") refs = set() From a1e735cc267d995925fe3837d0e2f9fa5ebd67ee Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 14 Sep 2021 18:18:33 +0200 Subject: [PATCH 0140/2916] fix: Overwrite kubectl-client-side-apply managed fields --- kluctl/diff/managed_fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kluctl/diff/managed_fields.py b/kluctl/diff/managed_fields.py index e380fde39..050f3f73c 100644 --- a/kluctl/diff/managed_fields.py +++ b/kluctl/diff/managed_fields.py @@ -74,7 +74,7 @@ def remove_non_managed_fields(o, managed_fields): if mf['manager'] in ['kluctl', 'deployctl']: continue # force-overwrite these - if mf['manager'] in ['kubectl-edit']: + if mf['manager'] in ['kubectl-edit', 'kubectl-client-side-apply']: continue for p in _fields_iterator(mf['fieldsV1'], []): if not p or [-1] == '.': From 59edc032a45a740c4af7c1e4d417767aaef4a4c1 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 14 Sep 2021 19:13:32 +0200 Subject: [PATCH 0141/2916] fix: Store resolved/rendered yaml on disk --- kluctl/cli/utils.py | 2 +- kluctl/deployment/deployment_collection.py | 47 ++++--------- kluctl/deployment/kustomize_deployment.py | 82 ++++++++++++---------- kluctl/seal/seal_command.py | 2 +- 4 files changed, 59 insertions(+), 74 deletions(-) diff --git a/kluctl/cli/utils.py b/kluctl/cli/utils.py index d9bd56500..bf07e762b 100644 --- a/kluctl/cli/utils.py +++ b/kluctl/cli/utils.py @@ -156,7 +156,7 @@ def project_target_command_context(kwargs, kluctl_project, target, c.images.add_fixed_image(fi) if not for_seal: - c.prepare(k8s_cluster, for_seal) + c.prepare(k8s_cluster) ctx = CommandContext(kluctl_project=kluctl_project, target=target, cluster_vars=cluster_vars, k8s_cluster=k8s_cluster, diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index bec3fc2e0..50a49e461 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -14,7 +14,7 @@ from kluctl.utils.k8s_delete_utils import find_objects_for_delete from kluctl.utils.k8s_downscale_utils import downscale_object from kluctl.utils.k8s_object_utils import get_object_ref, get_long_object_name, get_long_object_name_from_ref, \ - ObjectRef, should_remove_namespace + ObjectRef from kluctl.utils.k8s_status_validation import validate_object, ValidateResult, ValidateResultItem from kluctl.utils.templated_dir import TemplatedDir from kluctl.utils.utils import MyThreadPoolExecutor @@ -88,10 +88,12 @@ def render_deployments(self): for job in jobs: job.result() - if not self.for_seal: - for d in self.deployments: - d.prepare_kustomization_yaml() - d.resolve_sealed_secrets() + def resolve_sealed_secrets(self): + if self.for_seal: + return + + for d in self.deployments: + d.resolve_sealed_secrets() def build_kustomize_objects(self, k8s_cluster): logger.info("Building kustomize objects") @@ -99,15 +101,15 @@ def build_kustomize_objects(self, k8s_cluster): with MyThreadPoolExecutor() as executor: jobs = [] for d in self.deployments: - jobs.append(executor.submit(d.build_objects)) + jobs.append(executor.submit(d.build_kustomize)) for job in jobs: job.result() + jobs.clear() - if k8s_cluster: for d in self.deployments: - for o in d.objects: - if should_remove_namespace(k8s_cluster, get_object_ref(o)): - del o["metadata"]["namespace"] + jobs.append(executor.submit(d.postprocess_and_load_objects, k8s_cluster)) + for job in jobs: + job.result() def update_remote_objects(self, k8s_cluster): logger.info("Updating remote objects") @@ -120,25 +122,6 @@ def update_remote_objects(self, k8s_cluster): self.remote_objects[get_object_ref(o)] = o self.add_api_warnings(get_object_ref(o), w) - def resolve_image_placeholders(self, k8s_cluster): - logger.info("Resolving image versions") - - def do_resolve(o): - ref = get_object_ref(o) - remote_object = self.remote_objects.get(ref) - if remote_object is None and k8s_cluster is not None: - remote_object, warnings = k8s_cluster.get_single_object(ref) - self.images.resolve_placeholders(o, remote_object) - - with MyThreadPoolExecutor(max_workers=8) as executor: - futures = [] - for d in self.deployments: - for o in d.objects: - f = executor.submit(do_resolve, o) - futures.append(f) - for f in futures: - f.result() - def forget_remote_objects(self, refs): for ref in refs: self.remote_objects.pop(ref, None) @@ -148,13 +131,11 @@ def local_objects_by_ref(self): by_ref = dict((get_object_ref(x), x) for x in flat) return by_ref - def prepare(self, k8s_cluster, only_render): + def prepare(self, k8s_cluster): self.render_deployments() - if only_render: - return + self.resolve_sealed_secrets() self.build_kustomize_objects(k8s_cluster) self.update_remote_objects(k8s_cluster) - self.resolve_image_placeholders(k8s_cluster) def deploy(self, k8s_cluster, force_apply, replace_on_error, force_replace_on_error, abort_on_error): self.clear_errors_and_warnings() diff --git a/kluctl/deployment/kustomize_deployment.py b/kluctl/deployment/kustomize_deployment.py index c6e7b7bcd..4235a0813 100644 --- a/kluctl/deployment/kustomize_deployment.py +++ b/kluctl/deployment/kustomize_deployment.py @@ -8,12 +8,13 @@ from kluctl.utils.dict_utils import merge_dict from kluctl.utils.exceptions import CommandError from kluctl.utils.external_tools import get_external_tool_hash +from kluctl.utils.k8s_object_utils import get_object_ref, should_remove_namespace from kluctl.utils.kustomize import kustomize_build from kluctl.utils.templated_dir import TemplatedDir from kluctl.utils.utils import get_tmp_base_dir, calc_dir_hash from kluctl.utils.versions import LooseSemVerLatestVersion, PrefixLatestVersion, NumberLatestVersion, \ RegexLatestVersion -from kluctl.utils.yaml_utils import yaml_load_file, yaml_load_all, yaml_save_file +from kluctl.utils.yaml_utils import yaml_load_file, yaml_save_file logger = logging.getLogger(__name__) @@ -30,7 +31,7 @@ def __init__(self, deployment_project, deployment_collection, config, index): self.deployment_collection = deployment_collection self.config = config self.index = index - self.objects = None + self.objects = [] def get_rel_kustomize_dir(self): return self.deployment_project.get_rel_dir_to_root(self.config["path"]) @@ -46,6 +47,9 @@ def get_rendered_dir(self): rendered_dir = os.path.join(self.deployment_collection.tmpdir, path) return rendered_dir + def get_rendered_yamls_path(self): + return os.path.join(self.get_rendered_dir(), ".rendered.yml") + def get_common_labels(self): l = self.deployment_project.get_common_labels() for i, tag in enumerate(self.get_tags()): @@ -196,33 +200,6 @@ def check_inclusion_for_delete(self): values = self.build_inclusion_values() return inclusion.check_included(values, skip_delete_if_tags) - def build_objects_kustomize(self): - tmp_files = [] - - try: - need_build = True - if allow_cache: - hash = self.calc_hash() - hash_path = os.path.join(get_kustomize_cache_dir(), hash[:16]) - if os.path.exists(hash_path): - need_build = False - # Load from cache - with open(hash_path) as f: - yamls = f.read() - if need_build: - # Run 'kustomize build' - yamls = kustomize_build(self.get_rendered_dir()) - if allow_cache: - with open(hash_path + "~", "w") as f: - f.write(yamls) - os.rename(hash_path + "~", hash_path) - finally: - for x in tmp_files: - x.close() - - yamls = yaml_load_all(yamls) - return yamls - def prepare_kustomization_yaml(self): if "path" not in self.config: return @@ -240,20 +217,40 @@ def prepare_kustomization_yaml(self): # Save modified kustomize.yml yaml_save_file(kustomize_yaml, kustomize_yaml_path) - def build_objects(self): - if self.config.get("onlyRender", False): - self.objects = [] + def build_kustomize(self): + if "path" not in self.config: return + + self.prepare_kustomization_yaml() + + need_build = True + if allow_cache: + hash = self.calc_hash() + hash_path = os.path.join(get_kustomize_cache_dir(), hash[:16]) + if os.path.exists(hash_path): + need_build = False + # Copy from cache + shutil.copy(hash_path, self.get_rendered_yamls_path()) + if need_build: + # Run 'kustomize build' + yamls = kustomize_build(self.get_rendered_dir()) + with open(self.get_rendered_yamls_path(), "wt") as f: + f.write(yamls) + if allow_cache: + shutil.copy(self.get_rendered_yamls_path(), hash_path) + + def postprocess_and_load_objects(self, k8s_cluster): if "path" not in self.config: - self.objects = [] return - yamls = self.build_objects_kustomize() + self.objects = yaml_load_file(self.get_rendered_yamls_path(), all=True) + for y in self.objects: + ref = get_object_ref(y) + if should_remove_namespace(k8s_cluster, ref): + ref = get_object_ref(y) + del y["metadata"]["namespace"] - self.objects = [] - # Add commonLabels to all resources. We can't use kustomize's "commonLabels" feature as it erroneously - # sets matchLabels as well. - for y in yamls: + # Set common labels/annotations labels = y.setdefault("metadata", {}).setdefault("labels") or {} annotations = y["metadata"].setdefault("annotations") or {} labels.update(self.get_common_labels()) @@ -261,7 +258,14 @@ def build_objects(self): y["metadata"]["labels"] = labels y["metadata"]["annotations"] = annotations - self.objects.append(y) + # Resolve image placeholders + s = str(y) + if any(p in s for p in self.deployment_collection.images.placeholders.keys()): + remote_object, warnings = k8s_cluster.get_single_object(ref) + self.deployment_collection.images.resolve_placeholders(y, remote_object) + + # Need to write it back to disk in case it is needed externally + yaml_save_file(self.objects, self.get_rendered_yamls_path()) def calc_hash(self): h = hashlib.sha256() diff --git a/kluctl/seal/seal_command.py b/kluctl/seal/seal_command.py index 1dd4c2421..5cfef3e5e 100644 --- a/kluctl/seal/seal_command.py +++ b/kluctl/seal/seal_command.py @@ -40,7 +40,7 @@ def seal_command_for_target(kwargs, kluctl_project, target, sealing_config, secr # pass for_seal=True so that .sealme files are rendered as well with project_target_command_context(kwargs, kluctl_project, target, for_seal=True) as cmd_ctx: load_secrets(kluctl_project, target, secrets_loader, cmd_ctx.deployment) - cmd_ctx.deployment_collection.prepare(cmd_ctx.k8s_cluster, True) + cmd_ctx.deployment_collection.render_deployments() s = DeploymentSealer(cmd_ctx.deployment, cmd_ctx.deployment_collection, cmd_ctx.cluster_vars, cmd_ctx.kluctl_project.sealed_secrets_dir, kwargs["force_reseal"]) From 529207647f933ad6b231873e331f148e483691ef Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 15 Sep 2021 17:05:22 +0200 Subject: [PATCH 0142/2916] fix: Check for supported hook annotations and add errors/warnings if needed --- kluctl/deployment/apply_util.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/kluctl/deployment/apply_util.py b/kluctl/deployment/apply_util.py index d35333a8c..0e5107ab9 100644 --- a/kluctl/deployment/apply_util.py +++ b/kluctl/deployment/apply_util.py @@ -217,10 +217,28 @@ def get_list(path): s = [x for x in s if x != ""] return s + supported_kluctl_hooks = {"pre-deploy", "post-deploy", + "pre-deploy-initial", "post-deploy-initial"} + supported_kluctl_delete_policies = {"before-hook-creation", + "hook-succeeded", + "hook-failed"} + + # delete/rollback hooks are actually not supported, but we won't show warnings about that to not spam the user + supported_helm_hooks = {"pre-install", "post-install", + "pre-upgrade", "post-upgrade", + "pre-delete", "post-delete", + "pre-rollback", "post-rollback"} + hooks = get_list("metadata.annotations.kluctl\\.io/hook") + for h in hooks: + if h not in supported_kluctl_hooks: + self.handle_error(get_object_ref(o), "Unsupported kluctl.io/hook '%s'" % h) + helm_hooks = get_list("metadata.annotations.helm\\.sh/hook") + for h in helm_hooks: + if h not in supported_helm_hooks: + self.deployment_collection.add_api_warnings("Unsupported helm.sh/hook '%s'" % h) - helm_hooks = get_dict_value(o, "metadata.annotations.helm\\.sh/hook", "").split(",") if "pre-install" in helm_hooks: hooks.append("pre-deploy-initial") if "pre-upgrade" in helm_hooks: @@ -248,6 +266,10 @@ def get_list(path): delete_policy = ["before-hook-creation"] delete_policy = set(delete_policy) + for p in delete_policy: + if p not in supported_kluctl_delete_policies: + self.handle_error(get_object_ref(o), "Unsupported kluctl.io/hook-delete-policy '%s'" % h) + return hooks, weight, delete_policy def get_sorted_hooks_list(self, l): From d1edb2b0097876c60d86ddd7a5276ce63ea3d280 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 15 Sep 2021 17:19:24 +0200 Subject: [PATCH 0143/2916] refactor: Move hooks handling into own util class --- kluctl/deployment/apply_util.py | 120 +++----------------------------- kluctl/deployment/hooks_util.py | 111 +++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+), 111 deletions(-) create mode 100644 kluctl/deployment/hooks_util.py diff --git a/kluctl/deployment/apply_util.py b/kluctl/deployment/apply_util.py index 0e5107ab9..8554de318 100644 --- a/kluctl/deployment/apply_util.py +++ b/kluctl/deployment/apply_util.py @@ -5,8 +5,9 @@ from kubernetes.client import ApiException from kubernetes.dynamic.exceptions import ResourceNotFoundError +from kluctl.deployment.hooks_util import HooksUtil from kluctl.diff.managed_fields import remove_non_managed_fields2 -from kluctl.utils.dict_utils import get_dict_value, copy_dict, set_dict_value +from kluctl.utils.dict_utils import get_dict_value, set_dict_value from kluctl.utils.k8s_object_utils import get_object_ref, get_long_object_name, get_long_object_name_from_ref from kluctl.utils.k8s_status_validation import validate_object from kluctl.utils.utils import MyThreadPoolExecutor @@ -120,41 +121,6 @@ def wait_object(self, ref): logger.info("Waiting for for hook %s to get ready..." % get_long_object_name_from_ref(ref)) did_log = True - def run_hooks(self, d, hook): - l = self.get_sorted_hooks_list(d.objects) - l = [x for x in l if hook in x[1]] - - for x, hooks, weight, delete_policy in l: - if self.abort_signal: - return - if "before-hook-creation" in delete_policy: - self.delete_object(get_object_ref(x)) - - for x, hooks, weight, delete_policy in l: - if self.abort_signal: - return - self.apply_object(x) - - wait_results = {} - for x, hooks, weight, delete_policy in l: - if self.abort_signal: - return - ref = get_object_ref(x) - if self.had_error(ref): - continue - wait_results[ref] = self.wait_object(ref) - - for x, hooks, weight, delete_policy in reversed(l): - ref = get_object_ref(x) - if ref not in wait_results: - continue - if wait_results[ref]: - if "hook-succeeded" in delete_policy: - self.delete_object(ref) - else: - if "hook-failed" in delete_policy: - self.delete_object(ref) - def apply_kustomize_deployment(self, d): inital_deploy = True for o in d.objects: @@ -162,20 +128,22 @@ def apply_kustomize_deployment(self, d): inital_deploy = False break + hook_util = HooksUtil(self) + if inital_deploy: - self.run_hooks(d, "pre-deploy-initial") + hook_util.run_hooks(d, "pre-deploy-initial") else: - self.run_hooks(d, "pre-deploy") + hook_util.run_hooks(d, "pre-deploy") for o in d.objects: - if self.get_hooks(o)[0]: + if hook_util.get_hooks(o)[0]: continue self.apply_object(o) if inital_deploy: - self.run_hooks(d, "post-deploy-initial") + hook_util.run_hooks(d, "post-deploy-initial") else: - self.run_hooks(d, "post-deploy") + hook_util.run_hooks(d, "post-deploy") def apply_deployments(self): logger.info("Running server-side apply for all objects%s", self.k8s_cluster.get_dry_run_suffix(self.dry_run)) @@ -209,73 +177,3 @@ def finish_futures(): futures.append(f) finish_futures() - - def get_hooks(self, o): - def get_list(path): - s = get_dict_value(o, path, "") - s = s.split(",") - s = [x for x in s if x != ""] - return s - - supported_kluctl_hooks = {"pre-deploy", "post-deploy", - "pre-deploy-initial", "post-deploy-initial"} - supported_kluctl_delete_policies = {"before-hook-creation", - "hook-succeeded", - "hook-failed"} - - # delete/rollback hooks are actually not supported, but we won't show warnings about that to not spam the user - supported_helm_hooks = {"pre-install", "post-install", - "pre-upgrade", "post-upgrade", - "pre-delete", "post-delete", - "pre-rollback", "post-rollback"} - - hooks = get_list("metadata.annotations.kluctl\\.io/hook") - for h in hooks: - if h not in supported_kluctl_hooks: - self.handle_error(get_object_ref(o), "Unsupported kluctl.io/hook '%s'" % h) - - helm_hooks = get_list("metadata.annotations.helm\\.sh/hook") - for h in helm_hooks: - if h not in supported_helm_hooks: - self.deployment_collection.add_api_warnings("Unsupported helm.sh/hook '%s'" % h) - - if "pre-install" in helm_hooks: - hooks.append("pre-deploy-initial") - if "pre-upgrade" in helm_hooks: - hooks.append("pre-deploy") - if "post-install" in helm_hooks: - hooks.append("post-deploy-initial") - if "post-upgrade" in helm_hooks: - hooks.append("post-deploy") - if "pre-delete" in helm_hooks: - hooks.append("pre-delete") - if "post-delete" in helm_hooks: - hooks.append("post-delete") - hooks = set(hooks) - - weight = get_dict_value(o, "metadata.annotations.kluctl\\.io/hook-weight") - if weight is None: - weight = get_dict_value(o, "metadata.annotations.helm\\.sh/hook-weight") - if weight is None: - weight = "0" - weight = int(weight) - - delete_policy = get_list("metadata.annotations.kluctl\\.io/hook-delete-policy") - delete_policy += get_list("metadata.annotations.helm\\.sh/hook-delete-policy") - if not delete_policy: - delete_policy = ["before-hook-creation"] - delete_policy = set(delete_policy) - - for p in delete_policy: - if p not in supported_kluctl_delete_policies: - self.handle_error(get_object_ref(o), "Unsupported kluctl.io/hook-delete-policy '%s'" % h) - - return hooks, weight, delete_policy - - def get_sorted_hooks_list(self, l): - ret = [] - for x in l: - hooks, weight, delete_policy = self.get_hooks(x) - ret.append((x, hooks, weight, delete_policy)) - ret.sort(key=lambda x: x[2]) - return ret diff --git a/kluctl/deployment/hooks_util.py b/kluctl/deployment/hooks_util.py new file mode 100644 index 000000000..56359a839 --- /dev/null +++ b/kluctl/deployment/hooks_util.py @@ -0,0 +1,111 @@ +from kluctl.utils.dict_utils import get_dict_value +from kluctl.utils.k8s_object_utils import get_object_ref + +class HooksUtil: + def __init__(self, apply_util): + self.apply_util = apply_util + + def run_hooks(self, d, hook): + l = self.get_sorted_hooks_list(d.objects) + l = [x for x in l if hook in x[1]] + + for x, hooks, weight, delete_policy in l: + if self.apply_util.abort_signal: + return + if "before-hook-creation" in delete_policy: + self.apply_util.delete_object(get_object_ref(x)) + + for x, hooks, weight, delete_policy in l: + if self.apply_util.abort_signal: + return + self.apply_util.apply_object(x) + + wait_results = {} + for x, hooks, weight, delete_policy in l: + if self.apply_util.abort_signal: + return + ref = get_object_ref(x) + if self.apply_util.had_error(ref): + continue + wait_results[ref] = self.apply_util.wait_object(ref) + + for x, hooks, weight, delete_policy in reversed(l): + ref = get_object_ref(x) + if ref not in wait_results: + continue + if wait_results[ref]: + if "hook-succeeded" in delete_policy: + self.apply_util.delete_object(ref) + else: + if "hook-failed" in delete_policy: + self.apply_util.delete_object(ref) + + def get_hooks(self, o): + def get_list(path): + s = get_dict_value(o, path, "") + s = s.split(",") + s = [x for x in s if x != ""] + return s + + supported_kluctl_hooks = {"pre-deploy", "post-deploy", + "pre-deploy-initial", "post-deploy-initial"} + supported_kluctl_delete_policies = {"before-hook-creation", + "hook-succeeded", + "hook-failed"} + + # delete/rollback hooks are actually not supported, but we won't show warnings about that to not spam the user + supported_helm_hooks = {"pre-install", "post-install", + "pre-upgrade", "post-upgrade", + "pre-delete", "post-delete", + "pre-rollback", "post-rollback"} + + hooks = get_list("metadata.annotations.kluctl\\.io/hook") + for h in hooks: + if h not in supported_kluctl_hooks: + self.apply_util.handle_error(get_object_ref(o), "Unsupported kluctl.io/hook '%s'" % h) + + helm_hooks = get_list("metadata.annotations.helm\\.sh/hook") + for h in helm_hooks: + if h not in supported_helm_hooks: + self.apply_util.deployment_collection.add_api_warnings("Unsupported helm.sh/hook '%s'" % h) + + if "pre-install" in helm_hooks: + hooks.append("pre-deploy-initial") + if "pre-upgrade" in helm_hooks: + hooks.append("pre-deploy") + if "post-install" in helm_hooks: + hooks.append("post-deploy-initial") + if "post-upgrade" in helm_hooks: + hooks.append("post-deploy") + if "pre-delete" in helm_hooks: + hooks.append("pre-delete") + if "post-delete" in helm_hooks: + hooks.append("post-delete") + hooks = set(hooks) + + weight = get_dict_value(o, "metadata.annotations.kluctl\\.io/hook-weight") + if weight is None: + weight = get_dict_value(o, "metadata.annotations.helm\\.sh/hook-weight") + if weight is None: + weight = "0" + weight = int(weight) + + delete_policy = get_list("metadata.annotations.kluctl\\.io/hook-delete-policy") + delete_policy += get_list("metadata.annotations.helm\\.sh/hook-delete-policy") + if not delete_policy: + delete_policy = ["before-hook-creation"] + delete_policy = set(delete_policy) + + for p in delete_policy: + if p not in supported_kluctl_delete_policies: + self.apply_util.handle_error(get_object_ref(o), "Unsupported kluctl.io/hook-delete-policy '%s'" % h) + + return hooks, weight, delete_policy + + def get_sorted_hooks_list(self, l): + ret = [] + for x in l: + hooks, weight, delete_policy = self.get_hooks(x) + ret.append((x, hooks, weight, delete_policy)) + ret.sort(key=lambda x: x[2]) + return ret From 2273b8e9352356253ee684e008665b85c88c1996 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 15 Sep 2021 18:06:54 +0200 Subject: [PATCH 0144/2916] refactor: Put hook info into own class --- kluctl/deployment/apply_util.py | 2 +- kluctl/deployment/hooks_util.py | 52 +++++++++++++++++++++------------ 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/kluctl/deployment/apply_util.py b/kluctl/deployment/apply_util.py index 8554de318..5765dbe81 100644 --- a/kluctl/deployment/apply_util.py +++ b/kluctl/deployment/apply_util.py @@ -136,7 +136,7 @@ def apply_kustomize_deployment(self, d): hook_util.run_hooks(d, "pre-deploy") for o in d.objects: - if hook_util.get_hooks(o)[0]: + if hook_util.get_hook(o) is not None: continue self.apply_object(o) diff --git a/kluctl/deployment/hooks_util.py b/kluctl/deployment/hooks_util.py index 56359a839..b6a5176e8 100644 --- a/kluctl/deployment/hooks_util.py +++ b/kluctl/deployment/hooks_util.py @@ -1,46 +1,57 @@ +import dataclasses + +from typing import List, Set, Optional + from kluctl.utils.dict_utils import get_dict_value from kluctl.utils.k8s_object_utils import get_object_ref +@dataclasses.dataclass +class Hook: + object: dict + hooks: Set[str] + weight: int + delete_policies: Set[str] + class HooksUtil: def __init__(self, apply_util): self.apply_util = apply_util def run_hooks(self, d, hook): l = self.get_sorted_hooks_list(d.objects) - l = [x for x in l if hook in x[1]] + l = [x for x in l if hook in x.hooks] - for x, hooks, weight, delete_policy in l: + for h in l: if self.apply_util.abort_signal: return - if "before-hook-creation" in delete_policy: - self.apply_util.delete_object(get_object_ref(x)) + if "before-hook-creation" in h.delete_policies: + self.apply_util.delete_object(get_object_ref(h.object)) - for x, hooks, weight, delete_policy in l: + for h in l: if self.apply_util.abort_signal: return - self.apply_util.apply_object(x) + self.apply_util.apply_object(h.object) wait_results = {} - for x, hooks, weight, delete_policy in l: + for h in l: if self.apply_util.abort_signal: return - ref = get_object_ref(x) + ref = get_object_ref(h.object) if self.apply_util.had_error(ref): continue wait_results[ref] = self.apply_util.wait_object(ref) - for x, hooks, weight, delete_policy in reversed(l): - ref = get_object_ref(x) + for h in reversed(l): + ref = get_object_ref(h.object) if ref not in wait_results: continue if wait_results[ref]: - if "hook-succeeded" in delete_policy: + if "hook-succeeded" in h.delete_policies: self.apply_util.delete_object(ref) else: - if "hook-failed" in delete_policy: + if "hook-failed" in h.delete_policies: self.apply_util.delete_object(ref) - def get_hooks(self, o): + def get_hook(self, o) -> Optional[Hook]: def get_list(path): s = get_dict_value(o, path, "") s = s.split(",") @@ -100,12 +111,17 @@ def get_list(path): if p not in supported_kluctl_delete_policies: self.apply_util.handle_error(get_object_ref(o), "Unsupported kluctl.io/hook-delete-policy '%s'" % h) - return hooks, weight, delete_policy + if not hooks: + return None - def get_sorted_hooks_list(self, l): + return Hook(object=o, hooks=hooks, weight=weight, delete_policies=delete_policy) + + def get_sorted_hooks_list(self, l) -> List[Hook]: ret = [] for x in l: - hooks, weight, delete_policy = self.get_hooks(x) - ret.append((x, hooks, weight, delete_policy)) - ret.sort(key=lambda x: x[2]) + h = self.get_hook(x) + if h is None: + continue + ret.append(h) + ret.sort(key=lambda x: x.weight) return ret From 04448e4cde18fb691eed9856c76e0762bdfa9f73 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 15 Sep 2021 18:07:04 +0200 Subject: [PATCH 0145/2916] fix: Don't print the same error/warning multiple times --- kluctl/deployment/deployment_collection.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index 50a49e461..75b86fc64 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -365,15 +365,19 @@ def find_rendered_images(self): def add_api_warnings(self, ref, warnings): for w in warnings: - logger.warning("%s: Warning while performing api call. message=%s" % (get_long_object_name_from_ref(ref), w)) - self.api_warnings.add(DeployErrorItem(ref=ref, reason="api", message=w)) + item = DeployErrorItem(ref=ref, reason="api", message=w) + if item not in self.api_warnings: + logger.warning("%s: Warning while performing api call. message=%s" % (get_long_object_name_from_ref(ref), w)) + self.api_warnings.add(item) def add_api_error(self, ref, error): ref_str = "" if ref is not None: ref_str = "%s: " % get_long_object_name_from_ref(ref) - logger.error("%sError while performing api call. message=%s" % (ref_str, error)) - self.api_errors.add(DeployErrorItem(ref=ref, reason="api", message=error)) + item = DeployErrorItem(ref=ref, reason="api", message=error) + if item not in self.api_errors: + logger.error("%sError while performing api call. message=%s" % (ref_str, error)) + self.api_errors.add(item) def clear_errors_and_warnings(self): self.api_errors = set() From 7f37b10cc2a2bfba1b9865953b44cd985f6664fc Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 15 Sep 2021 18:14:22 +0200 Subject: [PATCH 0146/2916] refactor: Introduce and use parse_bool --- kluctl/image_registries/__init__.py | 5 +++-- kluctl/utils/k8s_delete_utils.py | 8 ++++---- kluctl/utils/utils.py | 10 +++++++--- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/kluctl/image_registries/__init__.py b/kluctl/image_registries/__init__.py index 043a56cab..e3a4f7797 100644 --- a/kluctl/image_registries/__init__.py +++ b/kluctl/image_registries/__init__.py @@ -2,12 +2,13 @@ from kluctl.image_registries.generic_registry import GenericRegistry from kluctl.utils.env_config_sets import parse_env_config_sets +from kluctl.utils.utils import parse_bool def init_image_registries(): ret = [] - default_tlsverify = os.environ.get("KLUCTL_REGISTRY_DEFAULT_TLSVERIFY", "true") in ["True", "true", "yes", "1"] + default_tlsverify = parse_bool(os.environ.get("KLUCTL_REGISTRY_DEFAULT_TLSVERIFY", "true")) generic = GenericRegistry(default_tlsverify) ret.append(generic) @@ -18,7 +19,7 @@ def add_registry(s): password = s.get("PASSWORD") tlsverify = s.get("TLSVERIFY") if tlsverify is not None: - tlsverify = tlsverify in ["True", "true", "yes", "1"] + tlsverify = parse_bool(tlsverify) if username and password: generic.add_creds(host, username, password, tlsverify) diff --git a/kluctl/utils/k8s_delete_utils.py b/kluctl/utils/k8s_delete_utils.py index 2e20d55b5..e81697e34 100644 --- a/kluctl/utils/k8s_delete_utils.py +++ b/kluctl/utils/k8s_delete_utils.py @@ -4,7 +4,7 @@ from kluctl.utils.k8s_object_utils import get_included_objects, get_label_from_object, get_object_ref, \ get_long_object_name_from_ref, split_api_version, get_filtered_api_resources, \ remove_api_version_from_ref, remove_namespace_from_ref_if_needed -from kluctl.utils.utils import MyThreadPoolExecutor +from kluctl.utils.utils import MyThreadPoolExecutor, parse_bool logger = logging.getLogger(__name__) @@ -35,8 +35,8 @@ def exclude(x): if (group or "", x["kind"]) not in filtered_resources: return True - # exclude when explicitely requested - if get_dict_value(x, "metadata.annotations.kluctl\\.io/skip-delete", "false").lower() in ["true", "1", "yes"]: + # exclude when explicitly requested + if parse_bool(get_dict_value(x, "metadata.annotations.kluctl\\.io/skip-delete", "false")): return True # exclude objects which are owned by some other object @@ -61,7 +61,7 @@ def exclude(x): # TODO remove label based check if get_label_from_object(x, 'kluctl.io/skip_delete_if_tags', 'false') == 'true': return True - if get_dict_value(x, "metadata.annotations.kluctl\\.io/skip-delete-if-tags", "false").lower() in ["true", "1", "yes"]: + if parse_bool(get_dict_value(x, "metadata.annotations.kluctl\\.io/skip-delete-if-tags", "false")): return True return False diff --git a/kluctl/utils/utils.py b/kluctl/utils/utils.py index 9f313cbee..5f378c977 100644 --- a/kluctl/utils/utils.py +++ b/kluctl/utils/utils.py @@ -2,14 +2,11 @@ import logging import os import shutil -import subprocess import sys import tempfile -import threading from concurrent.futures.thread import ThreadPoolExecutor from datetime import timedelta from decimal import Decimal -from io import BytesIO logger = logging.getLogger(__name__) @@ -48,6 +45,13 @@ def duration(duration_string): # example: '5d3h2m1s' prev_num.append(character) return timedelta(seconds=float(total_seconds)) +def parse_bool(s, do_raise=False): + if s.lower() in ["true", "1", "yes"]: + return True + if do_raise and s.lower() not in ["false", "0", "no"]: + raise ValueError("Invalid boolean value %s" % s) + return False + def calc_dir_hash(dir): h = hashlib.sha256() From 1c0cbe42a6f3f480af07a1b3c21d25302cd69a03 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 15 Sep 2021 18:17:13 +0200 Subject: [PATCH 0147/2916] fix: Fix crash in supported_kluctl_delete_policies check --- kluctl/deployment/hooks_util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kluctl/deployment/hooks_util.py b/kluctl/deployment/hooks_util.py index b6a5176e8..5c219e0f0 100644 --- a/kluctl/deployment/hooks_util.py +++ b/kluctl/deployment/hooks_util.py @@ -109,7 +109,7 @@ def get_list(path): for p in delete_policy: if p not in supported_kluctl_delete_policies: - self.apply_util.handle_error(get_object_ref(o), "Unsupported kluctl.io/hook-delete-policy '%s'" % h) + self.apply_util.handle_error(get_object_ref(o), "Unsupported kluctl.io/hook-delete-policy '%s'" % p) if not hooks: return None From 9bac266eeb689c1d0b927655935727619b718ebf Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 15 Sep 2021 18:28:46 +0200 Subject: [PATCH 0148/2916] feat: Allow to disable waiting on hooks --- docs/hooks.md | 2 ++ kluctl/deployment/hooks_util.py | 13 ++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/hooks.md b/docs/hooks.md index 17134a05d..dab17736c 100644 --- a/docs/hooks.md +++ b/docs/hooks.md @@ -37,3 +37,5 @@ changed by setting the `kluctl.io/hook-delete-policy` to a comma separated list After each deployment/execution of the hooks that belong to a deployment stage (before/after deployment), kluctl waits for the hook resources to become "ready". Readiness depends on the resource kind, e.g. for a Job, kluctl would wait until it finishes successfully. + +It is possible to disable waiting for hook readiness by setting the annotation `kluctl.io/hook-wait` to "false". diff --git a/kluctl/deployment/hooks_util.py b/kluctl/deployment/hooks_util.py index 5c219e0f0..122566ae8 100644 --- a/kluctl/deployment/hooks_util.py +++ b/kluctl/deployment/hooks_util.py @@ -4,6 +4,8 @@ from kluctl.utils.dict_utils import get_dict_value from kluctl.utils.k8s_object_utils import get_object_ref +from kluctl.utils.utils import parse_bool + @dataclasses.dataclass class Hook: @@ -11,6 +13,7 @@ class Hook: hooks: Set[str] weight: int delete_policies: Set[str] + wait: bool class HooksUtil: def __init__(self, apply_util): @@ -38,6 +41,8 @@ def run_hooks(self, d, hook): ref = get_object_ref(h.object) if self.apply_util.had_error(ref): continue + if not h.wait: + continue wait_results[ref] = self.apply_util.wait_object(ref) for h in reversed(l): @@ -111,10 +116,16 @@ def get_list(path): if p not in supported_kluctl_delete_policies: self.apply_util.handle_error(get_object_ref(o), "Unsupported kluctl.io/hook-delete-policy '%s'" % p) + try: + wait = parse_bool(get_dict_value(o, "metadata.annotations.kluctl\\.io/hook-wait", "true"), do_raise=True) + except ValueError as e: + self.apply_util.handle_error(get_object_ref(o), str(e)) + wait = True + if not hooks: return None - return Hook(object=o, hooks=hooks, weight=weight, delete_policies=delete_policy) + return Hook(object=o, hooks=hooks, weight=weight, delete_policies=delete_policy, wait=wait) def get_sorted_hooks_list(self, l) -> List[Hook]: ret = [] From 73a269afb9ffdd8992db834e1f70afd2b33baabe Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 16 Sep 2021 17:14:27 +0200 Subject: [PATCH 0149/2916] fix: Fix render and list-images commands --- kluctl/cli/commands.py | 4 ++-- kluctl/cli/util_commands.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/kluctl/cli/commands.py b/kluctl/cli/commands.py index eee2897f6..1661a6983 100644 --- a/kluctl/cli/commands.py +++ b/kluctl/cli/commands.py @@ -139,7 +139,7 @@ def render_command(obj, kwargs): kwargs["render_output_dir"] = tempfile.mkdtemp(dir=get_tmp_base_dir(), prefix="render-") with project_command_context(kwargs) as cmd_ctx: - cmd_ctx.deployment_collection.prepare(cmd_ctx.k8s_cluster, False) + cmd_ctx.deployment_collection.prepare(cmd_ctx.k8s_cluster) logger.info('Rendered into: %s' % cmd_ctx.deployment_collection.tmpdir) if kwargs["output_images"]: @@ -152,7 +152,7 @@ def render_command(obj, kwargs): def list_images_command(obj, kwargs): with project_command_context(kwargs, force_offline_kubernetes=kwargs["no_kubernetes"]) as cmd_ctx: cmd_ctx.images.raise_on_error = False - cmd_ctx.deployment_collection.prepare(cmd_ctx.k8s_cluster, False) + cmd_ctx.deployment_collection.prepare(cmd_ctx.k8s_cluster) result = { "images": build_seen_images(cmd_ctx.deployment_collection, not kwargs["simple"]) diff --git a/kluctl/cli/util_commands.py b/kluctl/cli/util_commands.py index 568dcadc9..d1dbd9f3f 100644 --- a/kluctl/cli/util_commands.py +++ b/kluctl/cli/util_commands.py @@ -105,7 +105,7 @@ def helm_update_command(upgrade, commit, kwargs): def check_image_updates(obj, kwargs): with project_command_context(kwargs) as cmd_ctx: cmd_ctx.images.raise_on_error = False - cmd_ctx.deployment_collection.prepare(cmd_ctx.k8s_cluster, False) + cmd_ctx.deployment_collection.prepare(cmd_ctx.k8s_cluster) rendered_images = cmd_ctx.deployment_collection.find_rendered_images() prefix_pattern = re.compile(r"^([a-zA-Z]+[a-zA-Z-_.]*)") From ec343cd2dd9db6b0d234f0e1aa7288ae77918c55 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 16 Sep 2021 17:14:49 +0200 Subject: [PATCH 0150/2916] fix: Fix diff with hooks that need to be deleted before --- kluctl/deployment/hooks_util.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kluctl/deployment/hooks_util.py b/kluctl/deployment/hooks_util.py index 122566ae8..e6b94146f 100644 --- a/kluctl/deployment/hooks_util.py +++ b/kluctl/deployment/hooks_util.py @@ -32,6 +32,9 @@ def run_hooks(self, d, hook): for h in l: if self.apply_util.abort_signal: return + if (self.apply_util.dry_run or self.apply_util.k8s_cluster.dry_run) and "before-hook-creation" in h.delete_policies: + self.apply_util.handle_result(h.object, []) + continue self.apply_util.apply_object(h.object) wait_results = {} From 4454c3cf2e7682df00ec18f0145b2d6aff05721d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 16 Sep 2021 17:29:18 +0200 Subject: [PATCH 0151/2916] fix: Don't even call prepare() manually --- kluctl/cli/commands.py | 2 -- kluctl/cli/util_commands.py | 1 - 2 files changed, 3 deletions(-) diff --git a/kluctl/cli/commands.py b/kluctl/cli/commands.py index 1661a6983..cd5ff458c 100644 --- a/kluctl/cli/commands.py +++ b/kluctl/cli/commands.py @@ -139,7 +139,6 @@ def render_command(obj, kwargs): kwargs["render_output_dir"] = tempfile.mkdtemp(dir=get_tmp_base_dir(), prefix="render-") with project_command_context(kwargs) as cmd_ctx: - cmd_ctx.deployment_collection.prepare(cmd_ctx.k8s_cluster) logger.info('Rendered into: %s' % cmd_ctx.deployment_collection.tmpdir) if kwargs["output_images"]: @@ -152,7 +151,6 @@ def render_command(obj, kwargs): def list_images_command(obj, kwargs): with project_command_context(kwargs, force_offline_kubernetes=kwargs["no_kubernetes"]) as cmd_ctx: cmd_ctx.images.raise_on_error = False - cmd_ctx.deployment_collection.prepare(cmd_ctx.k8s_cluster) result = { "images": build_seen_images(cmd_ctx.deployment_collection, not kwargs["simple"]) diff --git a/kluctl/cli/util_commands.py b/kluctl/cli/util_commands.py index d1dbd9f3f..27cc7425d 100644 --- a/kluctl/cli/util_commands.py +++ b/kluctl/cli/util_commands.py @@ -105,7 +105,6 @@ def helm_update_command(upgrade, commit, kwargs): def check_image_updates(obj, kwargs): with project_command_context(kwargs) as cmd_ctx: cmd_ctx.images.raise_on_error = False - cmd_ctx.deployment_collection.prepare(cmd_ctx.k8s_cluster) rendered_images = cmd_ctx.deployment_collection.find_rendered_images() prefix_pattern = re.compile(r"^([a-zA-Z]+[a-zA-Z-_.]*)") From 77ce4e815ed0dd0f50d1fc40feaf9ff2cce14d1b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 16 Sep 2021 17:29:38 +0200 Subject: [PATCH 0152/2916] fix: Call get_object_ref() after removing namespace from the object --- kluctl/deployment/kustomize_deployment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kluctl/deployment/kustomize_deployment.py b/kluctl/deployment/kustomize_deployment.py index 4235a0813..8bfc92e88 100644 --- a/kluctl/deployment/kustomize_deployment.py +++ b/kluctl/deployment/kustomize_deployment.py @@ -247,8 +247,8 @@ def postprocess_and_load_objects(self, k8s_cluster): for y in self.objects: ref = get_object_ref(y) if should_remove_namespace(k8s_cluster, ref): - ref = get_object_ref(y) del y["metadata"]["namespace"] + ref = get_object_ref(y) # Set common labels/annotations labels = y.setdefault("metadata", {}).setdefault("labels") or {} From c15c182a92fe742b304cc765473b1762e9ae9a84 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 16 Sep 2021 18:10:11 +0200 Subject: [PATCH 0153/2916] fix: Fix --offline for render and friends --- kluctl/deployment/deployment_collection.py | 3 +++ kluctl/deployment/kustomize_deployment.py | 7 +++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index 75b86fc64..ae9bb021b 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -112,6 +112,9 @@ def build_kustomize_objects(self, k8s_cluster): job.result() def update_remote_objects(self, k8s_cluster): + if k8s_cluster is None: + return + logger.info("Updating remote objects") refs = set() for ref in self.local_objects_by_ref().keys(): diff --git a/kluctl/deployment/kustomize_deployment.py b/kluctl/deployment/kustomize_deployment.py index 8bfc92e88..9b03f00a5 100644 --- a/kluctl/deployment/kustomize_deployment.py +++ b/kluctl/deployment/kustomize_deployment.py @@ -246,7 +246,7 @@ def postprocess_and_load_objects(self, k8s_cluster): self.objects = yaml_load_file(self.get_rendered_yamls_path(), all=True) for y in self.objects: ref = get_object_ref(y) - if should_remove_namespace(k8s_cluster, ref): + if k8s_cluster is not None and should_remove_namespace(k8s_cluster, ref): del y["metadata"]["namespace"] ref = get_object_ref(y) @@ -261,7 +261,10 @@ def postprocess_and_load_objects(self, k8s_cluster): # Resolve image placeholders s = str(y) if any(p in s for p in self.deployment_collection.images.placeholders.keys()): - remote_object, warnings = k8s_cluster.get_single_object(ref) + if k8s_cluster is not None: + remote_object, warnings = k8s_cluster.get_single_object(ref) + else: + remote_object = None self.deployment_collection.images.resolve_placeholders(y, remote_object) # Need to write it back to disk in case it is needed externally From 5c0b4338c0f2c3568075de8181bce0b648bc7b1a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 16 Sep 2021 18:14:40 +0200 Subject: [PATCH 0154/2916] feat: Allow dynamic target config to be part of the same project/repo --- docs/kluctl_project.md | 9 ++- kluctl/kluctl_project/kluctl_project.py | 75 ++++++++++++++++--------- kluctl/schemas/kluctl-project.yml | 2 - 3 files changed, 56 insertions(+), 30 deletions(-) diff --git a/docs/kluctl_project.md b/docs/kluctl_project.md index 3db888bf7..7631a1158 100644 --- a/docs/kluctl_project.md +++ b/docs/kluctl_project.md @@ -214,7 +214,7 @@ Please note that a single entry in `target` might end up with multiple dynamic t made unique between these dynamic targets. This can be achieved by using templating in the `name` field. As an example, `{{ target.targetConfig.ref }}` can be used to set the target name to the branch name of the dynamic target. -Dynamic target have the following form: +Dynamic targets have the following form: ```yaml targets: ... @@ -229,6 +229,7 @@ targets: url: ref: refPattern: + file: sealingConfig: dynamicSealing: secretSets: @@ -254,10 +255,16 @@ targets: * `refPattern` specifies a regex pattern to use when looking for candidate branches and tags. If this is specified, using `ref` is forbidden. This will result in multiple dynamic targets. Each dynamic target will have `ref` set to the actual branch name it belong to. This allows using of `{{ target.targetConfig.ref }}` in all other target fields. + * `file` specifies the config file name to read externalized target config from. * `dynamicSealing` in `sealingConfig` allows to disable sealing per dynamic target and instead reverts to sealing only once. See [A note on sealing](#a-note-on-sealing) for details. * all other fields are the same as for normal targets +### Simple dynamic targets + +A simplified form of dynamic targets is to store target config inside the same directory/project as the `.kluctl.yml`. +This can be done by omitting `project`, `ref` and `refPattern` from `targetConfig` and only specify `file`. + ### A note on sealing When sealing dynamic targets, it is very likely that it is not known yet which dynamic targets will actually exist in diff --git a/kluctl/kluctl_project/kluctl_project.py b/kluctl/kluctl_project/kluctl_project.py index afd69f5f6..58f058de3 100644 --- a/kluctl/kluctl_project/kluctl_project.py +++ b/kluctl/kluctl_project/kluctl_project.py @@ -254,13 +254,14 @@ def do_update(key): if target_config is None: continue - url = parse_git_project(target_config["project"], None).url - if url not in self.git_cache_up_to_date: - self.git_cache_up_to_date[url] = True - f = executor.submit(ensure_git_cache_updated, url) - futures.append(f) - if url not in self.refs_for_urls: - self.refs_for_urls[url] = executor.submit(git_ls_remote, url) + if "project" in target_config: + url = parse_git_project(target_config["project"], None).url + if url not in self.git_cache_up_to_date: + self.git_cache_up_to_date[url] = True + f = executor.submit(ensure_git_cache_updated, url) + futures.append(f) + if url not in self.refs_for_urls: + self.refs_for_urls[url] = executor.submit(git_ls_remote, url) for f in futures: f.result() @@ -312,13 +313,27 @@ def render_target(self, target): return target def build_dynamic_targets(self, base_target): + target_config = base_target["targetConfig"] + if "project" in target_config: + return self.build_dynamic_targets_external(base_target) + else: + return self.build_dynamic_targets_simple(base_target) + + def build_dynamic_targets_simple(self, base_target): + target_config = base_target["targetConfig"] + if "ref" in target_config or "refPattern" in target_config: + raise InvalidKluctlProjectConfig("'ref' and/or 'refPattern' are not allowed for non-external dynamic targets") + + dynamic_targets = [self.build_dynamic_target(base_target, self.kluctl_project_dir)] + return dynamic_targets + + def build_dynamic_targets_external(self, base_target): target_config = base_target["targetConfig"] git_project = parse_git_project(target_config["project"], None) refs = self.refs_for_urls[git_project.url] target_config_ref = target_config.get("ref") ref_pattern = target_config.get("refPattern") - config_file = target_config.get("file", "target-config.yml") if target_config_ref is not None and ref_pattern is not None: raise InvalidKluctlProjectConfig("'refPattern' and 'ref' can't be specified together") @@ -357,36 +372,42 @@ def build_dynamic_targets(self, base_target): }, None, False) involved_repo_refs[ref] = info.commit - config_path = os.path.join(info.dir, config_file) - if not os.path.exists(config_path): - logger.info("Ref %s has no target config file with name %s" % (ref, config_file)) - continue - try: - target_config_file = self.load_target_config(config_path) - target = copy_dict(base_target) + target = self.build_dynamic_target(base_target, info.dir) target["targetConfig"]["ref"] = ref target["targetConfig"]["defaultBranch"] = default_branch - - target.setdefault("args", {}) - # merge args - for arg_name, value in target_config_file.get("args", {}).items(): - self.check_dynamic_arg(target, arg_name, value) - target["args"][arg_name] = value - - target.setdefault("images", []) - # We prepend the dynamic images to ensure they get higher priority later - target["images"] = target_config_file.get("images", []) + target["images"] - dynamic_targets.append(target) except Exception as e: # Only fail if non-dynamic targets fail to load if target_config_ref is not None: raise e logger.warning("Failed to load dynamic target config for project. Error=%s" % (str(e))) - self.add_involved_repo(git_project.url, ref_pattern, involved_repo_refs) + if git_project is not None: + self.add_involved_repo(git_project.url, ref_pattern, involved_repo_refs) return dynamic_targets + def build_dynamic_target(self, base_target, dir): + target_config = base_target["targetConfig"] + config_file = target_config.get("file", "target-config.yml") + config_path = os.path.join(dir, config_file) + if not os.path.exists(config_path): + raise InvalidKluctlProjectConfig("No target config file with name %s found in target" % config_file) + + target_config_file = self.load_target_config(config_path) + target = copy_dict(base_target) + + target.setdefault("args", {}) + # merge args + for arg_name, value in target_config_file.get("args", {}).items(): + self.check_dynamic_arg(target, arg_name, value) + target["args"][arg_name] = value + + target.setdefault("images", []) + # We prepend the dynamic images to ensure they get higher priority later + target["images"] = target_config_file.get("images", []) + target["images"] + + return target + def load_target_config(self, path): target_config = yaml_load_file(path) if target_config is None: diff --git a/kluctl/schemas/kluctl-project.yml b/kluctl/schemas/kluctl-project.yml index b41068aed..37d31b555 100644 --- a/kluctl/schemas/kluctl-project.yml +++ b/kluctl/schemas/kluctl-project.yml @@ -74,8 +74,6 @@ definitions: targetConfig: type: object additionalProperties: false - required: - - project properties: project: $ref: "#/definitions/git_project" From 207c9b529c78ec5eb222797bc5445d3b6d6348ec Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 16 Sep 2021 18:15:00 +0200 Subject: [PATCH 0155/2916] feat: Add --output-single-yaml option to render command --- kluctl/cli/command_stubs.py | 3 +++ kluctl/cli/commands.py | 8 +++++++- kluctl/cli/utils.py | 9 ++++++--- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/kluctl/cli/command_stubs.py b/kluctl/cli/command_stubs.py index d4e4f9764..b40444164 100644 --- a/kluctl/cli/command_stubs.py +++ b/kluctl/cli/command_stubs.py @@ -127,6 +127,9 @@ def validate_command_stub(obj, **kwargs): help="Also output images list to given FILE. This output the same result as from the " "list-images command.", type=click.Path(dir_okay=False)) +@optgroup.option("--output-single-yaml", + help="Also write all resources into a single yaml file.", + type=click.Path(dir_okay=False)) @optgroup.option("--offline", help="Go offline, meaning that kubernetes and registries are not asked for image versions", is_flag=True) diff --git a/kluctl/cli/commands.py b/kluctl/cli/commands.py index cd5ff458c..b7b3978b4 100644 --- a/kluctl/cli/commands.py +++ b/kluctl/cli/commands.py @@ -138,7 +138,7 @@ def render_command(obj, kwargs): if kwargs["render_output_dir"] is None: kwargs["render_output_dir"] = tempfile.mkdtemp(dir=get_tmp_base_dir(), prefix="render-") - with project_command_context(kwargs) as cmd_ctx: + with project_command_context(kwargs, force_offline_images=kwargs["offline"], force_offline_kubernetes=kwargs["offline"]) as cmd_ctx: logger.info('Rendered into: %s' % cmd_ctx.deployment_collection.tmpdir) if kwargs["output_images"]: @@ -148,6 +148,12 @@ def render_command(obj, kwargs): output_yaml_result([kwargs["output_images"]], result) + if kwargs["output_single_yaml"]: + all_yamls = [] + for d in cmd_ctx.deployment_collection.deployments: + all_yamls += d.objects + output_yaml_result([kwargs["output_single_yaml"]], all_yamls, all=True) + def list_images_command(obj, kwargs): with project_command_context(kwargs, force_offline_kubernetes=kwargs["no_kubernetes"]) as cmd_ctx: cmd_ctx.images.raise_on_error = False diff --git a/kluctl/cli/utils.py b/kluctl/cli/utils.py index bf07e762b..300048988 100644 --- a/kluctl/cli/utils.py +++ b/kluctl/cli/utils.py @@ -20,7 +20,7 @@ from kluctl.utils.k8s_cluster_base import load_cluster_config, k8s_cluster_base from kluctl.utils.k8s_object_utils import get_long_object_name_from_ref from kluctl.utils.utils import get_tmp_base_dir -from kluctl.utils.yaml_utils import yaml_load_file, yaml_dump +from kluctl.utils.yaml_utils import yaml_load_file, yaml_dump, yaml_dump_all def build_jinja_vars(cluster_vars): @@ -227,9 +227,12 @@ def output_validate_result(output, result): s = build_validate_result(result, format) output_result(path, s) -def output_yaml_result(output, result): +def output_yaml_result(output, result, all=False): output = output or [None] - s = yaml_dump(result) + if all: + s = yaml_dump_all(result) + else: + s = yaml_dump(result) for o in output: output_result(o, s) From ea3a3bfde829419ffa214d71d687a9bd8bd29780 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 16 Sep 2021 18:18:47 +0200 Subject: [PATCH 0156/2916] docs: Run replace-commands-help.py --- docs/commands.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/commands.md b/docs/commands.md index 1c3da35d3..4077dec1e 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -399,6 +399,7 @@ In addition, the following arguments are available: directory is used. --output-images FILE Also output images list to given FILE. This output the same result as from the list- images command. + --output-single-yaml FILE Also write all resources into a single yaml file. --offline Go offline, meaning that kubernetes and registries are not asked for image versions ``` From 3b6b74671e3595cce68177e81f679e278e014250 Mon Sep 17 00:00:00 2001 From: Aljoscha Poertner Date: Fri, 17 Sep 2021 10:08:02 +0200 Subject: [PATCH 0157/2916] fix: add missing object ref for api warning --- kluctl/deployment/hooks_util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kluctl/deployment/hooks_util.py b/kluctl/deployment/hooks_util.py index e6b94146f..f1facf05d 100644 --- a/kluctl/deployment/hooks_util.py +++ b/kluctl/deployment/hooks_util.py @@ -86,7 +86,7 @@ def get_list(path): helm_hooks = get_list("metadata.annotations.helm\\.sh/hook") for h in helm_hooks: if h not in supported_helm_hooks: - self.apply_util.deployment_collection.add_api_warnings("Unsupported helm.sh/hook '%s'" % h) + self.apply_util.deployment_collection.add_api_warnings(get_object_ref(o), "Unsupported helm.sh/hook '%s'" % h) if "pre-install" in helm_hooks: hooks.append("pre-deploy-initial") From 9b48f98c14207f45afd4c06a026680cb74b371c0 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 17 Sep 2021 14:57:40 +0200 Subject: [PATCH 0158/2916] test: Add helper for CM deployments --- kluctl/e2e/kluctl_test_project_helpers.py | 18 +++++++++++++++++- kluctl/e2e/test_external_projects.py | 20 +++++++------------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/kluctl/e2e/kluctl_test_project_helpers.py b/kluctl/e2e/kluctl_test_project_helpers.py index 5468b6b2a..1afa63ffd 100644 --- a/kluctl/e2e/kluctl_test_project_helpers.py +++ b/kluctl/e2e/kluctl_test_project_helpers.py @@ -1,4 +1,5 @@ from kluctl.e2e.kluctl_test_project import KluctlTestProject +from kluctl.utils.yaml_utils import yaml_dump busybox_pod = """ apiVersion: apps/v1 @@ -32,4 +33,19 @@ def add_busybox_deployment(p: KluctlTestProject, dir, name, namespace="default") "busybox.yml": busybox_pod.format(namespace=namespace, name=name) } - p.add_kustomize_deployment(dir, resources) \ No newline at end of file + p.add_kustomize_deployment(dir, resources) + +def add_configmap_deployment(p: KluctlTestProject, dir, name, namespace="default", data={}): + y = { + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "name": name, + "namespace": namespace, + }, + "data": data, + } + resources = { + "configmap.yml": yaml_dump(y), + } + p.add_kustomize_deployment(dir, resources) diff --git a/kluctl/e2e/test_external_projects.py b/kluctl/e2e/test_external_projects.py index 4301dad29..4cb563978 100644 --- a/kluctl/e2e/test_external_projects.py +++ b/kluctl/e2e/test_external_projects.py @@ -3,20 +3,9 @@ from kluctl.e2e.conftest import assert_readiness, recreate_namespace from kluctl.e2e.conftest import assert_resource_not_exists, assert_resource_exists from kluctl.e2e.kluctl_test_project import KluctlTestProject -from kluctl.e2e.kluctl_test_project_helpers import add_busybox_deployment +from kluctl.e2e.kluctl_test_project_helpers import add_busybox_deployment, add_configmap_deployment from kluctl.utils.dict_utils import get_dict_value -config_map = """ -apiVersion: v1 -kind: ConfigMap -metadata: - name: "cm" - namespace: {namespace} -data: - cluster_var: {{{{ cluster.cluster_var }}}} - target_var: {{{{ args.target_var }}}} -""" - def do_test_project(kind_cluster, namespace, **kwargs): recreate_namespace(kind_cluster, namespace) @@ -28,8 +17,13 @@ def do_test_project(kind_cluster, namespace, **kwargs): p.kluctl("deploy", "--yes", "-t", "test") assert_readiness(kind_cluster, namespace, "Deployment/busybox", 5 * 60) + cm_data = { + "cluster_var": "{{ cluster.cluster_var }}", + "target_var": "{{ args.target_var }}" + } + assert_resource_not_exists(kind_cluster, namespace, "ConfigMap/cm") - p.add_kustomize_deployment("cm", resources={"cm.yml": config_map.format(namespace=namespace)}) + add_configmap_deployment(p, "cm", "cm", namespace=namespace, data=cm_data) p.kluctl("deploy", "--yes", "-t", "test") y = assert_resource_exists(kind_cluster, namespace, "ConfigMap/cm") assert get_dict_value(y, "data.cluster_var") == "cluster_value1" From afc489289a004f5bb8172a97b9195d315a2b6e63 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 17 Sep 2021 17:20:28 +0200 Subject: [PATCH 0159/2916] fix: Use jsonpath-ng instead of custom/limited dict path handling --- kluctl/cli/commands.py | 2 +- kluctl/deployment/apply_util.py | 4 +- kluctl/deployment/deployment_project.py | 24 ++++----- kluctl/deployment/hooks_util.py | 14 ++--- kluctl/deployment/test_images.py | 4 +- kluctl/diff/normalize.py | 6 +-- kluctl/utils/dict_nav_utils.py | 72 ++++++++----------------- kluctl/utils/dict_utils.py | 26 +++++---- kluctl/utils/k8s_delete_utils.py | 4 +- kluctl/utils/k8s_object_utils.py | 2 +- setup.py | 5 +- 11 files changed, 71 insertions(+), 92 deletions(-) diff --git a/kluctl/cli/commands.py b/kluctl/cli/commands.py index b7b3978b4..0af6e925a 100644 --- a/kluctl/cli/commands.py +++ b/kluctl/cli/commands.py @@ -26,7 +26,7 @@ def bootstrap_command(obj, kwargs): with project_command_context(kwargs) as cmd_ctx: existing, warnings = cmd_ctx.k8s_cluster.get_single_object(ObjectRef("apiextensions.k8s.io/v1", "CustomResourceDefinition", "sealedsecrets.bitnami.com")) if existing: - if not kwargs["yes"] and get_dict_value(existing, "metadata.labels.kluctl\\.io/component") != "bootstrap": + if not kwargs["yes"] and get_dict_value(existing, 'metadata.labels."kluctl.io/component"') != "bootstrap": click.confirm("It looks like you're trying to bootstrap a cluster that already has the sealed-secrets " "deployed but not managed by kluctl. Do you really want to continue bootrapping?", err=True, abort=True) diff --git a/kluctl/deployment/apply_util.py b/kluctl/deployment/apply_util.py index 5765dbe81..21395688b 100644 --- a/kluctl/deployment/apply_util.py +++ b/kluctl/deployment/apply_util.py @@ -76,8 +76,8 @@ def apply_object(self, x): self.handle_error(ref, self.k8s_cluster.get_status_message(e)) return resource_version = get_dict_value(remote_object, "metadata.resourceVersion") - x2 = set_dict_value(x, "metadata.resourceVersion", get_dict_value(remote_object, "metadata.resourceVersion"), do_clone=True) - r, patch_warnings = self.k8s_cluster.replace_object(x2, force_dry_run=self.dry_run, resource_version=resource_version) + x2 = set_dict_value(x, "metadata.resourceVersion", resource_version, do_clone=True) + r, patch_warnings = self.k8s_cluster.replace_object(x2, force_dry_run=self.dry_run) self.handle_result(r, patch_warnings) except ApiException as e2: self.handle_error(ref, self.k8s_cluster.get_status_message(e2)) diff --git a/kluctl/deployment/deployment_project.py b/kluctl/deployment/deployment_project.py index 246902007..2c501cb8e 100644 --- a/kluctl/deployment/deployment_project.py +++ b/kluctl/deployment/deployment_project.py @@ -8,7 +8,7 @@ from kluctl.utils.external_args import check_required_args from kluctl.utils.jinja2_utils import add_jinja2_filters, RelEnvironment, render_str from kluctl.utils.dict_utils import merge_dict, copy_dict, \ - set_default_value, get_dict_value + set_dict_default_value, get_dict_value from kluctl.utils.yaml_utils import yaml_load, yaml_load_file logger = logging.getLogger(__name__) @@ -82,14 +82,14 @@ def load_base_conf(self): if self.conf is None: self.conf = {} - set_default_value(self.conf, 'vars', []) - set_default_value(self.conf, 'kustomizeDirs', []) - set_default_value(self.conf, 'includes', []) - set_default_value(self.conf, 'commonLabels', {}) - set_default_value(self.conf, 'deleteByLabels', {}) - set_default_value(self.conf, 'overrideNamespace', None) - set_default_value(self.conf, 'tags', []) - set_default_value(self.conf, 'templateExcludes', []) + set_dict_default_value(self.conf, 'vars', []) + set_dict_default_value(self.conf, 'kustomizeDirs', []) + set_dict_default_value(self.conf, 'includes', []) + set_dict_default_value(self.conf, 'commonLabels', {}) + set_dict_default_value(self.conf, 'deleteByLabels', {}) + set_dict_default_value(self.conf, 'overrideNamespace', None) + set_dict_default_value(self.conf, 'tags', []) + set_dict_default_value(self.conf, 'templateExcludes', []) self.jinja_vars = self.load_jinja_vars_list(self.conf['vars'], self.jinja_vars) @@ -205,15 +205,15 @@ def get_ignore_for_diffs(self, ignore_tags, ignore_labels, ignore_annotations): ret += d.conf.get("ignoreForDiff", []) if ignore_tags: ret.append({ - 'fieldPath': 'metadata.labels.kluctl.io/tag-*', + 'fieldPath': 'metadata.labels["kluctl.io/tag-*"]', }) if ignore_labels: ret.append({ - 'fieldPath': 'metadata.labels.*', + 'fieldPath': 'metadata.labels[*]', }) if ignore_annotations: ret.append({ - 'fieldPath': 'metadata.annotations.*', + 'fieldPath': 'metadata.annotations[*]', }) return ret diff --git a/kluctl/deployment/hooks_util.py b/kluctl/deployment/hooks_util.py index f1facf05d..29bbb6628 100644 --- a/kluctl/deployment/hooks_util.py +++ b/kluctl/deployment/hooks_util.py @@ -78,12 +78,12 @@ def get_list(path): "pre-delete", "post-delete", "pre-rollback", "post-rollback"} - hooks = get_list("metadata.annotations.kluctl\\.io/hook") + hooks = get_list('metadata.annotations."kluctl.io/hook"') for h in hooks: if h not in supported_kluctl_hooks: self.apply_util.handle_error(get_object_ref(o), "Unsupported kluctl.io/hook '%s'" % h) - helm_hooks = get_list("metadata.annotations.helm\\.sh/hook") + helm_hooks = get_list('metadata.annotations."helm.sh/hook"') for h in helm_hooks: if h not in supported_helm_hooks: self.apply_util.deployment_collection.add_api_warnings(get_object_ref(o), "Unsupported helm.sh/hook '%s'" % h) @@ -102,15 +102,15 @@ def get_list(path): hooks.append("post-delete") hooks = set(hooks) - weight = get_dict_value(o, "metadata.annotations.kluctl\\.io/hook-weight") + weight = get_dict_value(o, 'metadata.annotations."kluctl.io/hook-weight"') if weight is None: - weight = get_dict_value(o, "metadata.annotations.helm\\.sh/hook-weight") + weight = get_dict_value(o, 'metadata.annotations."helm.sh/hook-weight"') if weight is None: weight = "0" weight = int(weight) - delete_policy = get_list("metadata.annotations.kluctl\\.io/hook-delete-policy") - delete_policy += get_list("metadata.annotations.helm\\.sh/hook-delete-policy") + delete_policy = get_list('metadata.annotations."kluctl.io/hook-delete-policy"') + delete_policy += get_list('metadata.annotations."helm.sh/hook-delete-policy"') if not delete_policy: delete_policy = ["before-hook-creation"] delete_policy = set(delete_policy) @@ -120,7 +120,7 @@ def get_list(path): self.apply_util.handle_error(get_object_ref(o), "Unsupported kluctl.io/hook-delete-policy '%s'" % p) try: - wait = parse_bool(get_dict_value(o, "metadata.annotations.kluctl\\.io/hook-wait", "true"), do_raise=True) + wait = parse_bool(get_dict_value(o, 'metadata.annotations."kluctl.io/hook-wait"', "true"), do_raise=True) except ValueError as e: self.apply_util.handle_error(get_object_ref(o), str(e)) wait = True diff --git a/kluctl/deployment/test_images.py b/kluctl/deployment/test_images.py index 307615d8a..fad115ad4 100644 --- a/kluctl/deployment/test_images.py +++ b/kluctl/deployment/test_images.py @@ -74,8 +74,8 @@ def do_test_placeholder_resolve(self, image, deployed_object, result_image1, res placeholder = images.gen_image_placeholder(image, latest_version, None, []) rendered_object = self.build_object(placeholder, placeholder) images.resolve_placeholders(rendered_object, deployed_object) - self.assertEqual(get_dict_value(rendered_object, "spec.template.spec.containers.0.image"), result_image1) - self.assertEqual(get_dict_value(rendered_object, "spec.template.spec.containers.1.image"), result_image2) + self.assertEqual(get_dict_value(rendered_object, "spec.template.spec.containers[0]image"), result_image1) + self.assertEqual(get_dict_value(rendered_object, "spec.template.spec.containers[1]image"), result_image2) def test_deployed(self): self.do_test_placeholder_resolve('registry.gitlab.com/g/p', self.build_object("di1", "di2"), "di1", "di2") diff --git a/kluctl/diff/normalize.py b/kluctl/diff/normalize.py index 78bdc7caf..f67c04ae7 100644 --- a/kluctl/diff/normalize.py +++ b/kluctl/diff/normalize.py @@ -1,7 +1,5 @@ -import fnmatch - from kluctl.utils.dict_nav_utils import del_if_exists, set_if_not_exists, del_if_falsy, del_matching_path -from kluctl.utils.dict_utils import copy_dict +from kluctl.utils.dict_utils import copy_dict, get_dict_value from kluctl.utils.k8s_object_utils import split_api_version @@ -58,7 +56,7 @@ def normalize_metadata(k8s_cluster, o): # We don't care about managedFields when diffing (they just produce noise) del_if_exists(m, 'managedFields') - del_if_exists(m, 'annotations.kubectl\\.kubernetes\\.io/last-applied-configuration') + del_if_exists(m, 'annotations."kubectl.kubernetes.io/last-applied-configuration"') # We don't want to see this in diffs del_if_exists(m, 'creationTimestamp') diff --git a/kluctl/utils/dict_nav_utils.py b/kluctl/utils/dict_nav_utils.py index 109545c08..d09a9fd2b 100644 --- a/kluctl/utils/dict_nav_utils.py +++ b/kluctl/utils/dict_nav_utils.py @@ -1,61 +1,35 @@ import fnmatch +import threading from uuid import uuid4 -_dummy = str(uuid4()) +from jsonpath_ng import parse, JSONPath -def nav_dict(d, k): - if isinstance(k, str): - if "\\." in k: - k = k.replace("\\.", _dummy) - k = k.split(".") - k = [x.replace(_dummy, ".") for x in k] - else: - k = k.split(".") - elif not isinstance(k, list): - raise ValueError("k must be a list and not %s" % type(k).__name__) +_dummy = str(uuid4()) - for i in range(len(k)): - if d is None: - return None, k[i], False - if isinstance(d, dict): - if k[i] not in d: - return d, k[i], False - if i == len(k) - 1: - return d, k[i], True - else: - d = d[k[i]] - elif is_iterable(d): - j = int(k[i]) - if j < 0 or j >= len(d): - return d, j, False - if i == len(k) - 1: - return d, j, True - else: - d = d[j] - else: - return d, None, False +json_path_cache = {} +json_path_cache_mutex = threading.Lock() +def parse_json_path(p) -> JSONPath: + with json_path_cache_mutex: + if p in json_path_cache: + return json_path_cache[p] + pp = parse(p) + json_path_cache[p] = pp + return pp def del_if_exists(d, k): - d, k, e = nav_dict(d, k) - if not e: - return - del d[k] - + p = parse_json_path(k) + p.filter(lambda x: True, d) def set_if_not_exists(d, k, v): - d, k, e = nav_dict(d, k) - if e: - return - d[k] = v - + p = parse_json_path(k) + f = p.find(d) + if not f: + p.update_or_create(d, v) def del_if_falsy(d, k): - d, k, e = nav_dict(d, k) - if not e: - return - if not d[k]: - del d[k] + p = parse_json_path(k) + p.filter(lambda x: not x, d) def is_iterable(obj): if isinstance(obj, list): @@ -94,7 +68,5 @@ def object_iterator(o): stack.append((v, p + [str(i)])) def del_matching_path(o, path): - for _, p in list(object_iterator(o)): - if fnmatch.fnmatch(".".join(p), path): - p2 = [x.replace(".", "\\.") for x in p] - del_if_exists(o, ".".join(p2)) + p = parse_json_path(path) + p.filter(lambda x: True, o) diff --git a/kluctl/utils/dict_utils.py b/kluctl/utils/dict_utils.py index d79a8c7dd..1b69e69d4 100644 --- a/kluctl/utils/dict_utils.py +++ b/kluctl/utils/dict_utils.py @@ -1,4 +1,4 @@ -from kluctl.utils.dict_nav_utils import nav_dict, is_iterable +from kluctl.utils.dict_nav_utils import is_iterable, parse_json_path def copy_primitive_value(v): @@ -33,21 +33,29 @@ def merge_dict(a, b, clone=True): a[key] = b[key] return a -def set_default_value(d, n, default): - if n not in d or d[n] is None: - d[n] = default +def set_dict_default_value(d, path, default): + p = parse_json_path(path) + f = p.find(d) + + if len(f) > 1: + raise Exception("Only simple jsonpath supported in set_dict_default_value") + if len(f) == 0 or f[0].value is None: + p.update_or_create(d, default) def get_dict_value(y, path, default=None): - d, k, found = nav_dict(y, path) - if not found: + p = parse_json_path(path) + f = p.find(y) + if len(f) > 1: + raise Exception("Only simple jsonpath supported in get_dict_value") + if len(f) == 0: return default - return d[k] + return f[0].value def set_dict_value(y, path, value, do_clone=False): if do_clone: y = copy_dict(y) - d, k, found = nav_dict(y, path) - d[k] = value + p = parse_json_path(path) + p.update_or_create(y, value) return y def is_empty(o): diff --git a/kluctl/utils/k8s_delete_utils.py b/kluctl/utils/k8s_delete_utils.py index e81697e34..9523f3fd3 100644 --- a/kluctl/utils/k8s_delete_utils.py +++ b/kluctl/utils/k8s_delete_utils.py @@ -36,7 +36,7 @@ def exclude(x): return True # exclude when explicitly requested - if parse_bool(get_dict_value(x, "metadata.annotations.kluctl\\.io/skip-delete", "false")): + if parse_bool(get_dict_value(x, 'metadata.annotations."kluctl.io/skip-delete"', "false")): return True # exclude objects which are owned by some other object @@ -61,7 +61,7 @@ def exclude(x): # TODO remove label based check if get_label_from_object(x, 'kluctl.io/skip_delete_if_tags', 'false') == 'true': return True - if parse_bool(get_dict_value(x, "metadata.annotations.kluctl\\.io/skip-delete-if-tags", "false")): + if parse_bool(get_dict_value(x, 'metadata.annotations."kluctl.io/skip-delete-if-tags"', "false")): return True return False diff --git a/kluctl/utils/k8s_object_utils.py b/kluctl/utils/k8s_object_utils.py index a30b7bf54..dd67cfe19 100644 --- a/kluctl/utils/k8s_object_utils.py +++ b/kluctl/utils/k8s_object_utils.py @@ -114,7 +114,7 @@ def get_included_objects(k8s_cluster, verbs, labels, inclusion, exclude_if_not_i inclusion_values = get_tags_from_object(r) inclusion_values = [("tag", tag) for tag in inclusion_values] - kustomize_dir = get_dict_value(r, "metadata.annotations.kluctl\\.io/kustomize_dir", None) + kustomize_dir = get_dict_value(r, 'metadata.annotations."kluctl.io/kustomize_dir"', None) if kustomize_dir is not None: inclusion_values.append(("kustomize_dir", kustomize_dir)) diff --git a/setup.py b/setup.py index 8c3602dae..62c5691a5 100644 --- a/setup.py +++ b/setup.py @@ -69,8 +69,9 @@ "python-dxf>=7.7.1", "gitpython>=3.1.18", "jsonschema>=3.2.0", - "filelock==3.0.12", - "python-gitlab==2.10.0", + "filelock>=3.0.12", + "python-gitlab>=2.10.0", + "jsonpath-ng>=1.5.3", ], entry_points={ "console_scripts": [ From ad6def9f85d04b78ccda98e8b53bd51c74b68dc4 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 17 Sep 2021 17:21:10 +0200 Subject: [PATCH 0160/2916] feat: Allow to filter for group in ignoreForDiff Also add documentation for it and remove the use of fnmatch. --- docs/deployments.md | 18 ++++++++++++++++++ kluctl/diff/normalize.py | 22 ++++++++++++---------- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/docs/deployments.md b/docs/deployments.md index 6178811b3..aafd42543 100644 --- a/docs/deployments.md +++ b/docs/deployments.md @@ -255,6 +255,24 @@ that includes Go templates, which will in most cases make Jinja2 templating fail ### ignoreForDiff +A list of objects and fields to ignore while performing diffs. Consider the following example: + +```yaml +kustomizeDirs: + - ... + +ignoreForDiff: + - group: apps + kind: Deployment + namespace: my-namespace + name: my-deployment + fieldPath: spec.replicas +``` + +This will remove the `spec.replicas` field from every resource that matches the object. +`group`, `kind`, `namespace` and `name` can be omitted, which results in all objects matching. `fieldPath` must be a +valid [JSON Path](https://goessner.net/articles/JsonPath/). `fieldPath` may also be a list of JSON paths. + # Order of deployment Deployments are done in parallel, meaning that there are usually no order guarantees. The only way to somehow control order, is by placing [barriers](#barrier) between kustomize deployments. You should however not overuse barriers, as diff --git a/kluctl/diff/normalize.py b/kluctl/diff/normalize.py index f67c04ae7..6b567cd83 100644 --- a/kluctl/diff/normalize.py +++ b/kluctl/diff/normalize.py @@ -78,9 +78,10 @@ def normalize_misc(o): # Performs some deterministic sorting and other normalizations to avoid ugly diffs due to order changes def normalize_object(k8s_cluster, o, ignore_for_diffs): - ns = o['metadata'].get('namespace') + group, version = split_api_version(o["apiVersion"]) kind = o['kind'] - name = o['metadata']['name'] + ns = get_dict_value(o, "metadata.namespace") + name = get_dict_value(o, "metadata.name") o = copy_dict(o) normalize_metadata(k8s_cluster, o) @@ -93,21 +94,22 @@ def normalize_object(k8s_cluster, o, ignore_for_diffs): normalize_service_account(o) def check_match(v, m): - if v is None: + if v is None or m is None: return True - if isinstance(m, list): - return any(fnmatch.fnmatch(v, x) for x in m) - return fnmatch.fnmatch(v, m) + return v == m for ifd in ignore_for_diffs: - ns2 = ifd.get('namespace', '*') - kind2 = ifd.get('kind', '*') - name2 = ifd.get('name', '*') + group2 = ifd.get('group') + kind2 = ifd.get('kind') + ns2 = ifd.get('namespace') + name2 = ifd.get('name') field_path = ifd.get('fieldPath') - if not check_match(ns, ns2): + if not check_match(group, group2): continue if not check_match(kind, kind2): continue + if not check_match(ns, ns2): + continue if not check_match(name, name2): continue if not isinstance(field_path, list): From f3a106bb27b9ab0f3033a6b4eac6dd82a0d03fb2 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 17 Sep 2021 17:22:23 +0200 Subject: [PATCH 0161/2916] fix: Don't allow deployments without commonLabels/deleteByLabels --- kluctl/deployment/deployment_project.py | 3 +++ kluctl/e2e/kluctl_test_project.py | 12 +++++++++--- kluctl/e2e/test_command_bootstrap.py | 4 ++-- kluctl/e2e/test_external_projects.py | 2 +- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/kluctl/deployment/deployment_project.py b/kluctl/deployment/deployment_project.py index 2c501cb8e..98688dcbb 100644 --- a/kluctl/deployment/deployment_project.py +++ b/kluctl/deployment/deployment_project.py @@ -99,6 +99,9 @@ def load_base_conf(self): # enable/disable single deployments via included/excluded tags c['tags'] = [os.path.basename(c['path'])] + if self.get_common_labels() == {} or self.get_common_labels() == {}: + raise CommandError("No commonLabels/deleteByLabels in root deployment. This is not allowed") + def check_required_args(self): # First try to load the config without templating to avoid getting errors while rendering because required # args were not set. Otherwise we won't be able to iterator through the 'args' array in the deployment.yml diff --git a/kluctl/e2e/kluctl_test_project.py b/kluctl/e2e/kluctl_test_project.py index 75e5e1b1b..8957eac20 100644 --- a/kluctl/e2e/kluctl_test_project.py +++ b/kluctl/e2e/kluctl_test_project.py @@ -8,16 +8,17 @@ from git import Git from pytest_kind import KindCluster -from kluctl.utils.dict_utils import copy_dict +from kluctl.utils.dict_utils import copy_dict, set_dict_value from kluctl.utils.yaml_utils import yaml_save_file, yaml_load_file logger = logging.getLogger(__name__) class KluctlTestProject: - def __init__(self, kluctl_project_external=False, + def __init__(self, project_name, kluctl_project_external=False, clusters_external=False, deployment_external=False, sealed_secrets_external=False, local_clusters=None, local_deployment=None, local_sealed_secrets=None): + self.project_name = project_name self.kluctl_project_external = kluctl_project_external self.clusters_external = clusters_external self.deployment_external = deployment_external @@ -115,7 +116,12 @@ def update_kluctl_yaml(self, update): self.update_yaml(os.path.join(self.get_kluctl_project_dir(), ".kluctl.yml"), update) def update_deployment_yaml(self, dir, update): - self.update_yaml(os.path.join(self.get_deployment_dir(), dir, "deployment.yml"), update) + def do_update(y): + if dir == ".": + set_dict_value(y, "commonLabels.project_name", self.project_name) + set_dict_value(y, "deleteByLabels.project_name", self.project_name) + return y + self.update_yaml(os.path.join(self.get_deployment_dir(), dir, "deployment.yml"), do_update) def copy_deployment(self, source): shutil.copytree(source, self.get_deployment_dir()) diff --git a/kluctl/e2e/test_command_bootstrap.py b/kluctl/e2e/test_command_bootstrap.py index 3c9c83a3b..41f2e4ed4 100644 --- a/kluctl/e2e/test_command_bootstrap.py +++ b/kluctl/e2e/test_command_bootstrap.py @@ -40,7 +40,7 @@ def test_command_bootstrap_upgrade(module_kind_cluster): k["resources"].append("dummy.yml") yaml_save_file(k, os.path.join(tmpdir, "sealed-secrets/kustomization.yml")) - with KluctlTestProject(local_deployment=tmpdir) as p: + with KluctlTestProject("bootstrap", local_deployment=tmpdir) as p: p.update_kind_cluster(module_kind_cluster) p.update_target("test", "module") p.kluctl("bootstrap", "--yes", "--cluster", "module") @@ -48,7 +48,7 @@ def test_command_bootstrap_upgrade(module_kind_cluster): @pytest.mark.dependency(depends=["test_command_bootstrap_upgrade"]) def test_command_bootstrap_purge(module_kind_cluster): - with KluctlTestProject() as p: + with KluctlTestProject("bootstrap") as p: p.update_kind_cluster(module_kind_cluster) p.update_target("test", "module") p.kluctl("bootstrap", "--yes", "--cluster", "module") diff --git a/kluctl/e2e/test_external_projects.py b/kluctl/e2e/test_external_projects.py index 4cb563978..f4252b54f 100644 --- a/kluctl/e2e/test_external_projects.py +++ b/kluctl/e2e/test_external_projects.py @@ -9,7 +9,7 @@ def do_test_project(kind_cluster, namespace, **kwargs): recreate_namespace(kind_cluster, namespace) - with KluctlTestProject(**kwargs) as p: + with KluctlTestProject("project-%s" % namespace, **kwargs) as p: p.update_kind_cluster(kind_cluster, {"cluster_var": "cluster_value1"}) p.update_target("test", kind_cluster.name, {"target_var": "target_value1"}) add_busybox_deployment(p, "busybox", "busybox", namespace=namespace) From a7e903cdf9990cdc77521242262eebfb43f4eff4 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 17 Sep 2021 17:22:50 +0200 Subject: [PATCH 0162/2916] refactor: Use run_helper in KluctlTestProject --- kluctl/e2e/kluctl_test_project.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/kluctl/e2e/kluctl_test_project.py b/kluctl/e2e/kluctl_test_project.py index 8957eac20..643d39ee8 100644 --- a/kluctl/e2e/kluctl_test_project.py +++ b/kluctl/e2e/kluctl_test_project.py @@ -9,6 +9,7 @@ from pytest_kind import KindCluster from kluctl.utils.dict_utils import copy_dict, set_dict_value +from kluctl.utils.run_helper import run_helper from kluctl.utils.yaml_utils import yaml_save_file, yaml_load_file logger = logging.getLogger(__name__) @@ -232,7 +233,7 @@ def get_sealed_secrets_dir(self): else: return self.get_kluctl_project_dir() - def kluctl(self, *args: str, **kwargs): + def kluctl(self, *args: str, check_rc=True, **kwargs): kluctl_path = os.path.join(os.path.dirname(__file__), "..", "..", "cli.py") args2 = [kluctl_path] args2 += args @@ -258,10 +259,10 @@ def kluctl(self, *args: str, **kwargs): logger.info("Running kluctl: %s" % " ".join(args2[1:])) - return subprocess.check_output( - args2, - cwd=cwd, - env=env, - encoding="utf-8", - **kwargs, - ) + def do_log(lines): + for l in lines: + logger.info(l) + + rc, stdout, stderr = run_helper(args2, cwd=cwd, env=env, stdout_func=do_log, stderr_func=do_log, line_mode=True, return_std=True) + assert not check_rc or rc == 0, "rc=%d" % rc + return rc, stdout, stderr From f2b46e7f7a6f65a109b46583793075fe69070a59 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 17 Sep 2021 17:23:12 +0200 Subject: [PATCH 0163/2916] test: Make namespace optional in assert_resource_exists/assert_resource_not_exists --- kluctl/e2e/conftest.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/kluctl/e2e/conftest.py b/kluctl/e2e/conftest.py index cf0dd1149..c1b625b22 100644 --- a/kluctl/e2e/conftest.py +++ b/kluctl/e2e/conftest.py @@ -67,14 +67,22 @@ def assert_readiness(kind_cluster: KindCluster, namespace, resource, timeout): def assert_resource_exists(kind_cluster: KindCluster, namespace, resource): try: - y = kind_cluster.kubectl("-n", namespace, "get", resource, "-o", "yaml", stderr=subprocess.PIPE) + args = [] + if namespace is not None: + args += ["-n", namespace] + args += ["get", resource, "-o", "yaml"] + y = kind_cluster.kubectl(*args, stderr=subprocess.PIPE) return yaml_load(y) except subprocess.CalledProcessError as e: assert False, e.stderr def assert_resource_not_exists(kind_cluster: KindCluster, namespace, resource): try: - kind_cluster.kubectl("-n", namespace, "get", resource, stderr=subprocess.PIPE) + args = [] + if namespace is not None: + args += ["-n", namespace] + args += ["get", resource] + kind_cluster.kubectl(*args, stderr=subprocess.PIPE) assert False, "'kubectl get' should not have succeeded" except subprocess.CalledProcessError as e: assert "(NotFound)" in e.stderr, e.stderr From ad9c16263098d4b0bc746a9667165f996149089a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 17 Sep 2021 17:23:29 +0200 Subject: [PATCH 0164/2916] test: Add add_namespace_deployment helper --- kluctl/e2e/kluctl_test_project_helpers.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/kluctl/e2e/kluctl_test_project_helpers.py b/kluctl/e2e/kluctl_test_project_helpers.py index 1afa63ffd..20cceb714 100644 --- a/kluctl/e2e/kluctl_test_project_helpers.py +++ b/kluctl/e2e/kluctl_test_project_helpers.py @@ -29,11 +29,21 @@ """ def add_busybox_deployment(p: KluctlTestProject, dir, name, namespace="default"): - resources = { + p.add_kustomize_deployment(dir, { "busybox.yml": busybox_pod.format(namespace=namespace, name=name) - } + }) - p.add_kustomize_deployment(dir, resources) +def add_namespace_deployment(p: KluctlTestProject, dir, name): + y = { + "apiVersion": "v1", + "kind": "Namespace", + "metadata": { + "name": name, + }, + } + p.add_kustomize_deployment(dir, { + "namespace.yml": yaml_dump(y), + }) def add_configmap_deployment(p: KluctlTestProject, dir, name, namespace="default", data={}): y = { @@ -45,7 +55,6 @@ def add_configmap_deployment(p: KluctlTestProject, dir, name, namespace="default }, "data": data, } - resources = { + p.add_kustomize_deployment(dir, { "configmap.yml": yaml_dump(y), - } - p.add_kustomize_deployment(dir, resources) + }) From d5671a0ccaeca3a7d4b4d729616484e853fc5610 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 17 Sep 2021 17:23:48 +0200 Subject: [PATCH 0165/2916] test: Less garbage in live logging --- pytest.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytest.ini b/pytest.ini index 5d3400584..2cec6b43e 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,5 +1,5 @@ [pytest] log_cli = true log_cli_level = 20 -log_cli_format = "%(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)" +log_cli_format = "%(asctime)s [%(levelname)8s] %(message)s" log_cli_date_format = "%Y-%m-%d %H:%M:%S" From 6474a19e1e9252b68008cb3d207cb4ae13f43952 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sat, 18 Sep 2021 09:17:25 +0200 Subject: [PATCH 0166/2916] feat: Extend jsonpath to support generic wildcards --- kluctl/deployment/deployment_project.py | 6 +++--- kluctl/utils/dict_nav_utils.py | 19 ++++++++++++++++++- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/kluctl/deployment/deployment_project.py b/kluctl/deployment/deployment_project.py index 98688dcbb..722518b80 100644 --- a/kluctl/deployment/deployment_project.py +++ b/kluctl/deployment/deployment_project.py @@ -208,15 +208,15 @@ def get_ignore_for_diffs(self, ignore_tags, ignore_labels, ignore_annotations): ret += d.conf.get("ignoreForDiff", []) if ignore_tags: ret.append({ - 'fieldPath': 'metadata.labels["kluctl.io/tag-*"]', + 'fieldPath': 'metadata.labels."kluctl.io/tag-*"', }) if ignore_labels: ret.append({ - 'fieldPath': 'metadata.labels[*]', + 'fieldPath': 'metadata.labels.*', }) if ignore_annotations: ret.append({ - 'fieldPath': 'metadata.annotations[*]', + 'fieldPath': 'metadata.annotations.*', }) return ret diff --git a/kluctl/utils/dict_nav_utils.py b/kluctl/utils/dict_nav_utils.py index d09a9fd2b..79fc23a62 100644 --- a/kluctl/utils/dict_nav_utils.py +++ b/kluctl/utils/dict_nav_utils.py @@ -2,13 +2,30 @@ import threading from uuid import uuid4 -from jsonpath_ng import parse, JSONPath +from jsonpath_ng import JSONPath, jsonpath, auto_id_field +from jsonpath_ng.ext import parse _dummy = str(uuid4()) json_path_cache = {} json_path_cache_mutex = threading.Lock() + +def ext_reified_fields(self, datum): + result = [] + for field in self.fields: + if "*" not in field: + result.append(field) + continue + try: + fields = [f for f in datum.value.keys() if fnmatch.fnmatch(f, field)] + if auto_id_field is not None: + fields.append(auto_id_field) + result += fields + except AttributeError: + pass + return tuple(result) +jsonpath.Fields.reified_fields = ext_reified_fields def parse_json_path(p) -> JSONPath: with json_path_cache_mutex: if p in json_path_cache: From 62d3498443a1f5c5a6c3b5474dd786cdd40a9dab Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sat, 18 Sep 2021 09:17:50 +0200 Subject: [PATCH 0167/2916] fix: Properly support the result of object_iterator in parse_json_path --- kluctl/utils/dict_nav_utils.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/kluctl/utils/dict_nav_utils.py b/kluctl/utils/dict_nav_utils.py index 79fc23a62..9517d7152 100644 --- a/kluctl/utils/dict_nav_utils.py +++ b/kluctl/utils/dict_nav_utils.py @@ -26,7 +26,20 @@ def ext_reified_fields(self, datum): pass return tuple(result) jsonpath.Fields.reified_fields = ext_reified_fields + def parse_json_path(p) -> JSONPath: + if isinstance(p, list): + p2 = "$" + for x in p: + if isinstance(x, str): + if '"' in x: + p2 = "%s['%s']" % (p2, x) + else: + p2 = '%s["%s"]' % (p2, x) + else: + p2 = "%s[%d]" % (p2, x) + p = p2 + with json_path_cache_mutex: if p in json_path_cache: return json_path_cache[p] @@ -82,7 +95,7 @@ def object_iterator(o): stack.append((v, p + [k])) elif not isinstance(o2, str) and is_iterable(o2): for i, v in enumerate(o2): - stack.append((v, p + [str(i)])) + stack.append((v, p + [i])) def del_matching_path(o, path): p = parse_json_path(path) From bac61cfe28af2a62f56c07c2e275574ae118f7c4 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sat, 18 Sep 2021 09:19:32 +0200 Subject: [PATCH 0168/2916] doc: Add note about jsonpath wildcards --- docs/deployments.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/deployments.md b/docs/deployments.md index aafd42543..4510f3710 100644 --- a/docs/deployments.md +++ b/docs/deployments.md @@ -273,6 +273,9 @@ This will remove the `spec.replicas` field from every resource that matches the `group`, `kind`, `namespace` and `name` can be omitted, which results in all objects matching. `fieldPath` must be a valid [JSON Path](https://goessner.net/articles/JsonPath/). `fieldPath` may also be a list of JSON paths. +The JSON Path implementation used in kluctl has extended support for wildcards in field +names, allowing you to also specify paths like `metadata.labels.my-prefix-*`. + # Order of deployment Deployments are done in parallel, meaning that there are usually no order guarantees. The only way to somehow control order, is by placing [barriers](#barrier) between kustomize deployments. You should however not overuse barriers, as From 1a0de6065a14163ddadfbb5141dda9ff59638c3b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sat, 18 Sep 2021 09:32:19 +0200 Subject: [PATCH 0169/2916] test: Fix update_deployment_yaml --- kluctl/e2e/kluctl_test_project.py | 1 + 1 file changed, 1 insertion(+) diff --git a/kluctl/e2e/kluctl_test_project.py b/kluctl/e2e/kluctl_test_project.py index 643d39ee8..de3727d9e 100644 --- a/kluctl/e2e/kluctl_test_project.py +++ b/kluctl/e2e/kluctl_test_project.py @@ -121,6 +121,7 @@ def do_update(y): if dir == ".": set_dict_value(y, "commonLabels.project_name", self.project_name) set_dict_value(y, "deleteByLabels.project_name", self.project_name) + y = update(y) return y self.update_yaml(os.path.join(self.get_deployment_dir(), dir, "deployment.yml"), do_update) From 54c2f3064afc8e63ca66290119fc69886076a52b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sat, 18 Sep 2021 09:43:26 +0200 Subject: [PATCH 0170/2916] test: Fix all tests --- kluctl/deployment/test_images.py | 4 ++-- kluctl/e2e/test_command_bootstrap.py | 2 +- kluctl/tests/misc/test_deployment/deployment.yml | 7 ++++++- kluctl/tests/templating/test_deployment/deployment.yml | 5 +++++ kluctl/tests/templating/test_import/deployment.yml | 7 ++++++- kluctl/tests/templating/test_utils/deployment.yml | 7 ++++++- kluctl/tests/templating/test_vars/deployment.yml | 5 +++++ 7 files changed, 31 insertions(+), 6 deletions(-) diff --git a/kluctl/deployment/test_images.py b/kluctl/deployment/test_images.py index fad115ad4..7b00d6a9b 100644 --- a/kluctl/deployment/test_images.py +++ b/kluctl/deployment/test_images.py @@ -74,8 +74,8 @@ def do_test_placeholder_resolve(self, image, deployed_object, result_image1, res placeholder = images.gen_image_placeholder(image, latest_version, None, []) rendered_object = self.build_object(placeholder, placeholder) images.resolve_placeholders(rendered_object, deployed_object) - self.assertEqual(get_dict_value(rendered_object, "spec.template.spec.containers[0]image"), result_image1) - self.assertEqual(get_dict_value(rendered_object, "spec.template.spec.containers[1]image"), result_image2) + self.assertEqual(get_dict_value(rendered_object, "spec.template.spec.containers[0].image"), result_image1) + self.assertEqual(get_dict_value(rendered_object, "spec.template.spec.containers[1].image"), result_image2) def test_deployed(self): self.do_test_placeholder_resolve('registry.gitlab.com/g/p', self.build_object("di1", "di2"), "di1", "di2") diff --git a/kluctl/e2e/test_command_bootstrap.py b/kluctl/e2e/test_command_bootstrap.py index 41f2e4ed4..d2e53dca1 100644 --- a/kluctl/e2e/test_command_bootstrap.py +++ b/kluctl/e2e/test_command_bootstrap.py @@ -22,7 +22,7 @@ @pytest.mark.dependency() def test_command_bootstrap(module_kind_cluster: KindCluster): - with KluctlTestProject() as p: + with KluctlTestProject("bootstrap") as p: p.update_kind_cluster(module_kind_cluster) p.update_target("test", "module") p.kluctl("bootstrap", "--yes", "--cluster", "module") diff --git a/kluctl/tests/misc/test_deployment/deployment.yml b/kluctl/tests/misc/test_deployment/deployment.yml index f614063f8..74f59763b 100644 --- a/kluctl/tests/misc/test_deployment/deployment.yml +++ b/kluctl/tests/misc/test_deployment/deployment.yml @@ -3,4 +3,9 @@ tags: - c - b - g -- "1" \ No newline at end of file +- "1" + +commonLabels: + common_a: a +deleteByLabels: + common_a: a \ No newline at end of file diff --git a/kluctl/tests/templating/test_deployment/deployment.yml b/kluctl/tests/templating/test_deployment/deployment.yml index d0aa6fecf..a813d1064 100644 --- a/kluctl/tests/templating/test_deployment/deployment.yml +++ b/kluctl/tests/templating/test_deployment/deployment.yml @@ -5,3 +5,8 @@ tags: includes: - path: "{{ include_var }}" - path: d2 + +commonLabels: + common_a: a +deleteByLabels: + common_a: a diff --git a/kluctl/tests/templating/test_import/deployment.yml b/kluctl/tests/templating/test_import/deployment.yml index b564faf02..da0fa9627 100644 --- a/kluctl/tests/templating/test_import/deployment.yml +++ b/kluctl/tests/templating/test_import/deployment.yml @@ -1,2 +1,7 @@ kustomizeDirs: -- path: k1 \ No newline at end of file +- path: k1 + +commonLabels: + common_a: a +deleteByLabels: + common_a: a diff --git a/kluctl/tests/templating/test_utils/deployment.yml b/kluctl/tests/templating/test_utils/deployment.yml index bc9617e99..7ab834ba4 100644 --- a/kluctl/tests/templating/test_utils/deployment.yml +++ b/kluctl/tests/templating/test_utils/deployment.yml @@ -2,4 +2,9 @@ vars: - file: ./vars.yml kustomizeDirs: -- path: k1 \ No newline at end of file +- path: k1 + +commonLabels: + common_a: a +deleteByLabels: + common_a: a diff --git a/kluctl/tests/templating/test_vars/deployment.yml b/kluctl/tests/templating/test_vars/deployment.yml index 78476048e..11bbe1fb2 100644 --- a/kluctl/tests/templating/test_vars/deployment.yml +++ b/kluctl/tests/templating/test_vars/deployment.yml @@ -6,3 +6,8 @@ vars: v1: v1 - file: vars-file.yml - file: vars-file2.yml + +commonLabels: + common_a: a +deleteByLabels: + common_a: a From 02abe6d43b92bc2544145f4c5357fba288a65bae Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 20 Sep 2021 09:12:19 +0200 Subject: [PATCH 0171/2916] refactor: Rename a few dict util methods --- kluctl/diff/normalize.py | 29 +++++++++++++++-------------- kluctl/utils/dict_nav_utils.py | 8 ++------ 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/kluctl/diff/normalize.py b/kluctl/diff/normalize.py index 6b567cd83..88072e9ec 100644 --- a/kluctl/diff/normalize.py +++ b/kluctl/diff/normalize.py @@ -1,4 +1,4 @@ -from kluctl.utils.dict_nav_utils import del_if_exists, set_if_not_exists, del_if_falsy, del_matching_path +from kluctl.utils.dict_nav_utils import del_dict_value, set_default_dict_value, del_matching_path from kluctl.utils.dict_utils import copy_dict, get_dict_value from kluctl.utils.k8s_object_utils import split_api_version @@ -30,7 +30,8 @@ def normalize_containers(containers): normalize_env(c) def normalize_secret_and_configmap(o): - del_if_falsy(o, 'data') + if not get_dict_value(o, "data"): + del_dict_value(o, "data") def normalize_service_account(o): new_secrets = [] @@ -55,26 +56,26 @@ def normalize_metadata(k8s_cluster, o): del(m['namespace']) # We don't care about managedFields when diffing (they just produce noise) - del_if_exists(m, 'managedFields') - del_if_exists(m, 'annotations."kubectl.kubernetes.io/last-applied-configuration"') + del_dict_value(m, 'managedFields') + del_dict_value(m, 'annotations."kubectl.kubernetes.io/last-applied-configuration"') # We don't want to see this in diffs - del_if_exists(m, 'creationTimestamp') - del_if_exists(m, 'generation') - del_if_exists(m, 'resourceVersion') - del_if_exists(m, 'selfLink') - del_if_exists(m, 'uid') + del_dict_value(m, 'creationTimestamp') + del_dict_value(m, 'generation') + del_dict_value(m, 'resourceVersion') + del_dict_value(m, 'selfLink') + del_dict_value(m, 'uid') # Ensure empty labels/metadata exist - set_if_not_exists(m, 'labels', {}) - set_if_not_exists(m, 'annotations', {}) + set_default_dict_value(m, 'labels', {}) + set_default_dict_value(m, 'annotations', {}) def normalize_misc(o): # These are random values found in Jobs - del_if_exists(o, 'spec.template.metadata.labels.controller-uid') - del_if_exists(o, 'spec.selector.matchLabels.controller-uid') + del_dict_value(o, 'spec.template.metadata.labels.controller-uid') + del_dict_value(o, 'spec.selector.matchLabels.controller-uid') - del_if_exists(o, 'status') + del_dict_value(o, 'status') # Performs some deterministic sorting and other normalizations to avoid ugly diffs due to order changes def normalize_object(k8s_cluster, o, ignore_for_diffs): diff --git a/kluctl/utils/dict_nav_utils.py b/kluctl/utils/dict_nav_utils.py index 9517d7152..29c7481a2 100644 --- a/kluctl/utils/dict_nav_utils.py +++ b/kluctl/utils/dict_nav_utils.py @@ -47,20 +47,16 @@ def parse_json_path(p) -> JSONPath: json_path_cache[p] = pp return pp -def del_if_exists(d, k): +def del_dict_value(d, k): p = parse_json_path(k) p.filter(lambda x: True, d) -def set_if_not_exists(d, k, v): +def set_default_dict_value(d, k, v): p = parse_json_path(k) f = p.find(d) if not f: p.update_or_create(d, v) -def del_if_falsy(d, k): - p = parse_json_path(k) - p.filter(lambda x: not x, d) - def is_iterable(obj): if isinstance(obj, list): return True From 89d85689236877d8de8c9d8ca5eb917bfcd9a040 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 20 Sep 2021 09:13:36 +0200 Subject: [PATCH 0172/2916] refactor: Remove del_matching_path which did the same as del_dict_value --- kluctl/diff/normalize.py | 4 ++-- kluctl/utils/dict_nav_utils.py | 4 ---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/kluctl/diff/normalize.py b/kluctl/diff/normalize.py index 88072e9ec..228d564e9 100644 --- a/kluctl/diff/normalize.py +++ b/kluctl/diff/normalize.py @@ -1,4 +1,4 @@ -from kluctl.utils.dict_nav_utils import del_dict_value, set_default_dict_value, del_matching_path +from kluctl.utils.dict_nav_utils import del_dict_value, set_default_dict_value from kluctl.utils.dict_utils import copy_dict, get_dict_value from kluctl.utils.k8s_object_utils import split_api_version @@ -116,7 +116,7 @@ def check_match(v, m): if not isinstance(field_path, list): field_path = [field_path] for p in field_path: - del_matching_path(o, p) + del_dict_value(o, p) return o diff --git a/kluctl/utils/dict_nav_utils.py b/kluctl/utils/dict_nav_utils.py index 29c7481a2..948dcb239 100644 --- a/kluctl/utils/dict_nav_utils.py +++ b/kluctl/utils/dict_nav_utils.py @@ -92,7 +92,3 @@ def object_iterator(o): elif not isinstance(o2, str) and is_iterable(o2): for i, v in enumerate(o2): stack.append((v, p + [i])) - -def del_matching_path(o, path): - p = parse_json_path(path) - p.filter(lambda x: True, o) From 550902cb03d95f294d97ccf8045bb152ec20f48c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 20 Sep 2021 09:16:43 +0200 Subject: [PATCH 0173/2916] refactor: Move del_dict_value into dict_utils.py and remove duplicated set_default_dict_value --- kluctl/diff/normalize.py | 8 ++++---- kluctl/utils/dict_nav_utils.py | 9 --------- kluctl/utils/dict_utils.py | 4 ++++ 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/kluctl/diff/normalize.py b/kluctl/diff/normalize.py index 228d564e9..33d09b86f 100644 --- a/kluctl/diff/normalize.py +++ b/kluctl/diff/normalize.py @@ -1,5 +1,5 @@ -from kluctl.utils.dict_nav_utils import del_dict_value, set_default_dict_value -from kluctl.utils.dict_utils import copy_dict, get_dict_value +from kluctl.utils.dict_utils import copy_dict, get_dict_value, del_dict_value, \ + set_dict_default_value from kluctl.utils.k8s_object_utils import split_api_version @@ -67,8 +67,8 @@ def normalize_metadata(k8s_cluster, o): del_dict_value(m, 'uid') # Ensure empty labels/metadata exist - set_default_dict_value(m, 'labels', {}) - set_default_dict_value(m, 'annotations', {}) + set_dict_default_value(m, 'labels', {}) + set_dict_default_value(m, 'annotations', {}) def normalize_misc(o): # These are random values found in Jobs diff --git a/kluctl/utils/dict_nav_utils.py b/kluctl/utils/dict_nav_utils.py index 948dcb239..814cd004f 100644 --- a/kluctl/utils/dict_nav_utils.py +++ b/kluctl/utils/dict_nav_utils.py @@ -47,15 +47,6 @@ def parse_json_path(p) -> JSONPath: json_path_cache[p] = pp return pp -def del_dict_value(d, k): - p = parse_json_path(k) - p.filter(lambda x: True, d) - -def set_default_dict_value(d, k, v): - p = parse_json_path(k) - f = p.find(d) - if not f: - p.update_or_create(d, v) def is_iterable(obj): if isinstance(obj, list): diff --git a/kluctl/utils/dict_utils.py b/kluctl/utils/dict_utils.py index 1b69e69d4..ba335a7c8 100644 --- a/kluctl/utils/dict_utils.py +++ b/kluctl/utils/dict_utils.py @@ -58,6 +58,10 @@ def set_dict_value(y, path, value, do_clone=False): p.update_or_create(y, value) return y +def del_dict_value(d, k): + p = parse_json_path(k) + p.filter(lambda x: True, d) + def is_empty(o): if isinstance(o, dict) or isinstance(o, list): return len(o) == 0 From d184629a0d1cd5ab7a1b930ce4916a1226cd812e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 20 Sep 2021 09:22:02 +0200 Subject: [PATCH 0174/2916] refactor: Move remaining stuff from dict_nav_utils.py into dict_utils.py --- kluctl/deployment/images.py | 3 +- kluctl/utils/dict_nav_utils.py | 85 ---------------------------------- kluctl/utils/dict_utils.py | 81 +++++++++++++++++++++++++++++++- 3 files changed, 81 insertions(+), 88 deletions(-) delete mode 100644 kluctl/utils/dict_nav_utils.py diff --git a/kluctl/deployment/images.py b/kluctl/deployment/images.py index 08540fd3a..a48ddba80 100644 --- a/kluctl/deployment/images.py +++ b/kluctl/deployment/images.py @@ -2,8 +2,7 @@ import logging import threading -from kluctl.utils.dict_nav_utils import object_iterator -from kluctl.utils.dict_utils import copy_dict, set_dict_value, get_dict_value +from kluctl.utils.dict_utils import copy_dict, set_dict_value, get_dict_value, object_iterator from kluctl.utils.exceptions import CommandError from kluctl.utils.thread_safe_cache import ThreadSafeMultiCache from kluctl.utils.versions import build_latest_version_from_str diff --git a/kluctl/utils/dict_nav_utils.py b/kluctl/utils/dict_nav_utils.py deleted file mode 100644 index 814cd004f..000000000 --- a/kluctl/utils/dict_nav_utils.py +++ /dev/null @@ -1,85 +0,0 @@ -import fnmatch -import threading -from uuid import uuid4 - -from jsonpath_ng import JSONPath, jsonpath, auto_id_field -from jsonpath_ng.ext import parse - -_dummy = str(uuid4()) - -json_path_cache = {} -json_path_cache_mutex = threading.Lock() - - -def ext_reified_fields(self, datum): - result = [] - for field in self.fields: - if "*" not in field: - result.append(field) - continue - try: - fields = [f for f in datum.value.keys() if fnmatch.fnmatch(f, field)] - if auto_id_field is not None: - fields.append(auto_id_field) - result += fields - except AttributeError: - pass - return tuple(result) -jsonpath.Fields.reified_fields = ext_reified_fields - -def parse_json_path(p) -> JSONPath: - if isinstance(p, list): - p2 = "$" - for x in p: - if isinstance(x, str): - if '"' in x: - p2 = "%s['%s']" % (p2, x) - else: - p2 = '%s["%s"]' % (p2, x) - else: - p2 = "%s[%d]" % (p2, x) - p = p2 - - with json_path_cache_mutex: - if p in json_path_cache: - return json_path_cache[p] - pp = parse(p) - json_path_cache[p] = pp - return pp - - -def is_iterable(obj): - if isinstance(obj, list): - return True - if isinstance(obj, tuple): - return True - if isinstance(obj, dict): - return True - if isinstance(obj, str): - return True - if isinstance(obj, bytes): - return True - if isinstance(obj, int) or isinstance(obj, bool): - return False - if isinstance(obj, type): - return False - try: - iter(obj) - except Exception: - return False - else: - return True - -def object_iterator(o): - stack = [(o, [])] - - while len(stack) != 0: - o2, p = stack.pop() - yield o2, p - - if isinstance(o2, dict): - for k, v in o2.items(): - stack.append((v, p + [k])) - elif not isinstance(o2, str) and is_iterable(o2): - for i, v in enumerate(o2): - stack.append((v, p + [i])) diff --git a/kluctl/utils/dict_utils.py b/kluctl/utils/dict_utils.py index ba335a7c8..e93bd4a0e 100644 --- a/kluctl/utils/dict_utils.py +++ b/kluctl/utils/dict_utils.py @@ -1,5 +1,48 @@ -from kluctl.utils.dict_nav_utils import is_iterable, parse_json_path +import fnmatch +import threading +from jsonpath_ng import auto_id_field, jsonpath, JSONPath +from jsonpath_ng.ext import parse + +json_path_cache = {} +json_path_cache_mutex = threading.Lock() + +# Add better wildcard support to jsonpath lib +def ext_reified_fields(self, datum): + result = [] + for field in self.fields: + if "*" not in field: + result.append(field) + continue + try: + fields = [f for f in datum.value.keys() if fnmatch.fnmatch(f, field)] + if auto_id_field is not None: + fields.append(auto_id_field) + result += fields + except AttributeError: + pass + return tuple(result) +jsonpath.Fields.reified_fields = ext_reified_fields + +def parse_json_path(p) -> JSONPath: + if isinstance(p, list): + p2 = "$" + for x in p: + if isinstance(x, str): + if '"' in x: + p2 = "%s['%s']" % (p2, x) + else: + p2 = '%s["%s"]' % (p2, x) + else: + p2 = "%s[%d]" % (p2, x) + p = p2 + + with json_path_cache_mutex: + if p in json_path_cache: + return json_path_cache[p] + pp = parse(p) + json_path_cache[p] = pp + return pp def copy_primitive_value(v): if isinstance(v, dict): @@ -82,3 +125,39 @@ def remove_empty(o): o.pop(i) else: i += 1 + +def is_iterable(obj): + if isinstance(obj, list): + return True + if isinstance(obj, tuple): + return True + if isinstance(obj, dict): + return True + if isinstance(obj, str): + return True + if isinstance(obj, bytes): + return True + if isinstance(obj, int) or isinstance(obj, bool): + return False + if isinstance(obj, type): + return False + try: + iter(obj) + except Exception: + return False + else: + return True + +def object_iterator(o): + stack = [(o, [])] + + while len(stack) != 0: + o2, p = stack.pop() + yield o2, p + + if isinstance(o2, dict): + for k, v in o2.items(): + stack.append((v, p + [k])) + elif not isinstance(o2, str) and is_iterable(o2): + for i, v in enumerate(o2): + stack.append((v, p + [i])) From 33657350389b265c68589d02247fd5a99b38b968 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 20 Sep 2021 09:44:34 +0200 Subject: [PATCH 0175/2916] test: Add tests for dict_util.py --- kluctl/utils/dict_utils_test.py | 74 +++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 kluctl/utils/dict_utils_test.py diff --git a/kluctl/utils/dict_utils_test.py b/kluctl/utils/dict_utils_test.py new file mode 100644 index 000000000..34a74c33b --- /dev/null +++ b/kluctl/utils/dict_utils_test.py @@ -0,0 +1,74 @@ +from kluctl.utils.dict_utils import get_dict_value, copy_dict, set_dict_value, del_dict_value + +o = { + "a": "v1", + "b": "v2", + "c": { + "d": "v3", + "e": { + "f": "v4", + "xa": "v5", + "g": "v6", + "xb": "v7", + } + }, + "array": [ + {"a": "v1"}, + {"b": "v2"}, + ] +} + + +def test_get_dict_value(): + assert get_dict_value(o, "a") == "v1" + assert get_dict_value(o, "b") == "v2" + assert get_dict_value(o, "c.d") == "v3" + assert get_dict_value(o, "c.e.f") == "v4" + assert get_dict_value(o, "c.e.g") == "v6" + +def test_get_dict_value_arrays(): + assert isinstance(get_dict_value(o, "array"), list) + assert isinstance(get_dict_value(o, "array[0]"), dict) + assert get_dict_value(o, "array[0].a") == "v1" + assert get_dict_value(o, "array[1].b") == "v2" + +def test_set_dict_value(): + o2 = copy_dict(o) + set_dict_value(o2, "a", "v1a") + assert get_dict_value(o2, "a") == "v1a" + set_dict_value(o2, "c.e.f", "xyz") + assert get_dict_value(o2, "c.e.f") == "xyz" + set_dict_value(o2, "array[0].a", "a1") + assert get_dict_value(o2, "array[0].a") == "a1" + +def test_set_dict_value_add(): + o2 = copy_dict(o) + set_dict_value(o2, "x", "x") + assert get_dict_value(o2, "x") == "x" + set_dict_value(o2, "y.x", "yx") + assert get_dict_value(o2, "y.x") == "yx" + set_dict_value(o2, "array[2].a", "a1") + assert get_dict_value(o2, "array[2].a") == "a1" + +def test_del_dict_value(): + o2 = copy_dict(o) + del_dict_value(o2, "a") + assert "a" not in o2 + assert "b" in o2 + del_dict_value(o2, "c.d") + assert "d" not in o2["c"] + del_dict_value(o2, "array[1]") + assert len(o2["array"]) == 1 + +def test_del_dict_value_wildcard(): + o2 = copy_dict(o) + del_dict_value(o2, "*") + assert o2 == {} + o2 = copy_dict(o) + del_dict_value(o2, "c.*") + assert o2["c"] == {} + +def test_del_dict_value_wildcard_extended(): + o2 = copy_dict(o) + del_dict_value(o2, 'c.e."x*"') + assert o2["c"]["e"] == {"f": "v4", "g": "v6"} From 1a015b4500f8222fbae516f8914dbe8f51afdfa4 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 20 Sep 2021 09:49:38 +0200 Subject: [PATCH 0176/2916] test: Rename test modules --- kluctl/deployment/{test_images.py => images_test.py} | 0 kluctl/utils/{test_versions.py => versions_test.py} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename kluctl/deployment/{test_images.py => images_test.py} (100%) rename kluctl/utils/{test_versions.py => versions_test.py} (100%) diff --git a/kluctl/deployment/test_images.py b/kluctl/deployment/images_test.py similarity index 100% rename from kluctl/deployment/test_images.py rename to kluctl/deployment/images_test.py diff --git a/kluctl/utils/test_versions.py b/kluctl/utils/versions_test.py similarity index 100% rename from kluctl/utils/test_versions.py rename to kluctl/utils/versions_test.py From 53a185c349cf08b688a24eba22a40a2348048118 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 20 Sep 2021 09:54:21 +0200 Subject: [PATCH 0177/2916] test: Allow to add tags to test projects --- kluctl/e2e/kluctl_test_project.py | 18 ++++++++++++------ kluctl/e2e/kluctl_test_project_helpers.py | 4 ++-- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/kluctl/e2e/kluctl_test_project.py b/kluctl/e2e/kluctl_test_project.py index de3727d9e..b1feadce9 100644 --- a/kluctl/e2e/kluctl_test_project.py +++ b/kluctl/e2e/kluctl_test_project.py @@ -166,14 +166,17 @@ def do_update(y): return y self.update_kluctl_yaml(do_update) - def add_deployment_include(self, dir, include): + def add_deployment_include(self, dir, include, tags=None): def do_update(y): includes = y.setdefault("includes", []) if any(x["path" == include] for x in includes): return - includes.append({ + o = { "path": include, - }) + } + if tags is not None: + o["tags"] = tags + includes.append(o) return y self.update_deployment_yaml(dir, do_update) @@ -183,7 +186,7 @@ def add_deployment_includes(self, dir): self.add_deployment_include(os.path.join(*p), x) p.append(x) - def add_kustomize_deployment(self, dir, resources): + def add_kustomize_deployment(self, dir, resources, tags=None): deployment_dir = os.path.dirname(dir) if deployment_dir != "": self.add_deployment_includes(deployment_dir) @@ -207,9 +210,12 @@ def add_kustomize_deployment(self, dir, resources): def do_update(y): kustomize_dirs = y.setdefault("kustomizeDirs", []) - kustomize_dirs.append({ + o = { "path": os.path.basename(dir) - }) + } + if tags is not None: + o["tags"] = tags + kustomize_dirs.append(o) return y self.update_deployment_yaml(deployment_dir, do_update) diff --git a/kluctl/e2e/kluctl_test_project_helpers.py b/kluctl/e2e/kluctl_test_project_helpers.py index 20cceb714..2c26c6663 100644 --- a/kluctl/e2e/kluctl_test_project_helpers.py +++ b/kluctl/e2e/kluctl_test_project_helpers.py @@ -45,7 +45,7 @@ def add_namespace_deployment(p: KluctlTestProject, dir, name): "namespace.yml": yaml_dump(y), }) -def add_configmap_deployment(p: KluctlTestProject, dir, name, namespace="default", data={}): +def add_configmap_deployment(p: KluctlTestProject, dir, name, namespace="default", data={}, tags=None): y = { "apiVersion": "v1", "kind": "ConfigMap", @@ -57,4 +57,4 @@ def add_configmap_deployment(p: KluctlTestProject, dir, name, namespace="default } p.add_kustomize_deployment(dir, { "configmap.yml": yaml_dump(y), - }) + }, tags=tags) From 3790b3b4efe2ae18294f4add524ad105adc89c55 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 20 Sep 2021 10:37:05 +0200 Subject: [PATCH 0178/2916] test: Add deploy and inclusion tests --- kluctl/e2e/kluctl_test_project.py | 4 ++ kluctl/e2e/test_command_deploy.py | 18 +++++++ kluctl/e2e/test_inclusion.py | 82 +++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+) create mode 100644 kluctl/e2e/test_command_deploy.py create mode 100644 kluctl/e2e/test_inclusion.py diff --git a/kluctl/e2e/kluctl_test_project.py b/kluctl/e2e/kluctl_test_project.py index b1feadce9..5f3c7cacf 100644 --- a/kluctl/e2e/kluctl_test_project.py +++ b/kluctl/e2e/kluctl_test_project.py @@ -125,6 +125,10 @@ def do_update(y): return y self.update_yaml(os.path.join(self.get_deployment_dir(), dir, "deployment.yml"), do_update) + def get_deployment_yaml(self, dir): + path = os.path.join(self.get_deployment_dir(), dir, "deployment.yml") + return yaml_load_file(path) + def copy_deployment(self, source): shutil.copytree(source, self.get_deployment_dir()) self._commit(self.get_deployment_dir(), all=True, message="copy deployment from %s" % source) diff --git a/kluctl/e2e/test_command_deploy.py b/kluctl/e2e/test_command_deploy.py new file mode 100644 index 000000000..e56301be9 --- /dev/null +++ b/kluctl/e2e/test_command_deploy.py @@ -0,0 +1,18 @@ +from pytest_kind import KindCluster + +from kluctl.e2e.conftest import assert_resource_exists, recreate_namespace +from kluctl.e2e.kluctl_test_project import KluctlTestProject +from kluctl.e2e.kluctl_test_project_helpers import add_configmap_deployment + + +def test_command_deploy_simple(module_kind_cluster: KindCluster): + with KluctlTestProject("simple") as p: + recreate_namespace(module_kind_cluster, "simple") + + p.update_kind_cluster(module_kind_cluster) + p.update_target("test", "module") + + add_configmap_deployment(p, "cm", "cm", namespace="simple") + p.kluctl("deploy", "--yes", "-t", "test") + + assert_resource_exists(module_kind_cluster, "simple", "ConfigMap/cm") diff --git a/kluctl/e2e/test_inclusion.py b/kluctl/e2e/test_inclusion.py new file mode 100644 index 000000000..da6249b23 --- /dev/null +++ b/kluctl/e2e/test_inclusion.py @@ -0,0 +1,82 @@ +from pytest_kind import KindCluster + +from kluctl.e2e.conftest import assert_resource_exists, recreate_namespace, assert_resource_not_exists +from kluctl.e2e.kluctl_test_project import KluctlTestProject +from kluctl.e2e.kluctl_test_project_helpers import add_configmap_deployment + +def prepare_project(module_kind_cluster: KindCluster, p: KluctlTestProject, namespace): + recreate_namespace(module_kind_cluster, namespace) + + p.update_kind_cluster(module_kind_cluster) + p.update_target("test", "module") + add_configmap_deployment(p, "cm1", "cm1", namespace=namespace) + add_configmap_deployment(p, "cm2", "cm2", namespace=namespace) + add_configmap_deployment(p, "cm3", "cm3", namespace=namespace, tags=["tag1", "tag2"]) + add_configmap_deployment(p, "cm4", "cm4", namespace=namespace, tags=["tag1", "tag3"]) + add_configmap_deployment(p, "cm5", "cm5", namespace=namespace, tags=["tag1", "tag4"]) + add_configmap_deployment(p, "cm6", "cm6", namespace=namespace, tags=["tag1", "tag5"]) + add_configmap_deployment(p, "cm7", "cm7", namespace=namespace, tags=["tag1", "tag6"]) + +def assert_exists_helper(kind_cluster, p, namespace, should_exists, add=None): + if add is not None: + for x in add: + should_exists.add(x) + for x in p.get_deployment_yaml(".")["kustomizeDirs"]: + if x["path"] in should_exists: + assert_resource_exists(kind_cluster, namespace, "ConfigMap/%s" % x["path"]) + else: + assert_resource_not_exists(kind_cluster, namespace, "ConfigMap/%s" % x["path"]) + +def test_inclusion_exclusion(module_kind_cluster: KindCluster): + with KluctlTestProject("inclusion") as p: + prepare_project(module_kind_cluster, p, "inclusion") + + should_exists = set() + def do_assert_exists(add=None): + assert_exists_helper(module_kind_cluster, p, "inclusion", should_exists, add) + + do_assert_exists() + + # test default tags + p.kluctl("deploy", "--yes", "-t", "test", "-I", "cm1") + do_assert_exists({"cm1"}) + p.kluctl("deploy", "--yes", "-t", "test", "-I", "cm2") + do_assert_exists({"cm2"}) + + # cm3/cm4 don't have default tags, so they should not deploy + p.kluctl("deploy", "--yes", "-t", "test", "-I", "cm3") + do_assert_exists() + + # but with tag2, at least cm3 should deploy + p.kluctl("deploy", "--yes", "-t", "test", "-I", "tag2") + do_assert_exists({"cm3"}) + + # let's try 2 tags at once + p.kluctl("deploy", "--yes", "-t", "test", "-I", "tag3", "-I", "tag4") + do_assert_exists({"cm4", "cm5"}) + + # And now let's try a tag that matches all non-default ones + p.kluctl("deploy", "--yes", "-t", "test", "-I", "tag1") + do_assert_exists({"cm6", "cm7"}) + +def test_exclusion_exclusion(module_kind_cluster: KindCluster): + with KluctlTestProject("exclusion") as p: + prepare_project(module_kind_cluster, p, "exclusion") + + should_exists = set() + def do_assert_exists(add=None): + assert_exists_helper(module_kind_cluster, p, "exclusion", should_exists, add) + + do_assert_exists() + + # Exclude everything except cm1 + p.kluctl("deploy", "--yes", "-t", "test", "-E", "cm2", "-E", "tag1") + do_assert_exists({"cm1"}) + + # Test that exclusion has precedence over inclusion + p.kluctl("deploy", "--yes", "-t", "test", "-E", "cm2", "-E", "tag1", "-I", "cm2") + do_assert_exists() + + # Test that exclusion has precedence over inclusion + p.kluctl("deploy", "--yes", "-t", "test", "-I", "tag1", "-E", "tag6") + do_assert_exists({"cm3", "cm4", "cm5", "cm6"}) From 3b474a2d961b01136ba05f7fe3737789b69727b3 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 20 Sep 2021 11:42:52 +0200 Subject: [PATCH 0179/2916] test: Also test --dry-run in deploy command --- kluctl/e2e/test_command_deploy.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/kluctl/e2e/test_command_deploy.py b/kluctl/e2e/test_command_deploy.py index e56301be9..a0db271ee 100644 --- a/kluctl/e2e/test_command_deploy.py +++ b/kluctl/e2e/test_command_deploy.py @@ -1,6 +1,6 @@ from pytest_kind import KindCluster -from kluctl.e2e.conftest import assert_resource_exists, recreate_namespace +from kluctl.e2e.conftest import assert_resource_exists, recreate_namespace, assert_resource_not_exists from kluctl.e2e.kluctl_test_project import KluctlTestProject from kluctl.e2e.kluctl_test_project_helpers import add_configmap_deployment @@ -14,5 +14,10 @@ def test_command_deploy_simple(module_kind_cluster: KindCluster): add_configmap_deployment(p, "cm", "cm", namespace="simple") p.kluctl("deploy", "--yes", "-t", "test") - assert_resource_exists(module_kind_cluster, "simple", "ConfigMap/cm") + + add_configmap_deployment(p, "cm2", "cm2", namespace="simple") + p.kluctl("deploy", "--yes", "-t", "test", "--dry-run") + assert_resource_not_exists(module_kind_cluster, "simple", "ConfigMap/cm2") + p.kluctl("deploy", "--yes", "-t", "test") + assert_resource_exists(module_kind_cluster, "simple", "ConfigMap/cm2") From 421c6be3e1da9a965933ad795f4275d77b30a634 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 20 Sep 2021 12:10:08 +0200 Subject: [PATCH 0180/2916] feat: Use jsonpath for get_var template function --- docs/jinja2-templating.md | 5 +++-- kluctl/utils/jinja2_utils.py | 20 +++++--------------- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/docs/jinja2-templating.md b/docs/jinja2-templating.md index 1a9a4e1c0..0da86c98b 100644 --- a/docs/jinja2-templating.md +++ b/docs/jinja2-templating.md @@ -138,8 +138,9 @@ Loads the given file into memory, renders it with the current Jinja2 context and The filename given to `load_template` is treated as relative to the template that is currently rendered. ### get_var(field_path, default) -Convenience method to navigate through the current context variables via a field path. Let's assume you currently have -these variables defines (e.g. via [vars](./deployments.md#vars)): +Convenience method to navigate through the current context variables via a +[JSON Path](https://goessner.net/articles/JsonPath/). Let's assume you currently have these variables defines +(e.g. via [vars](./deployments.md#vars)): ```yaml my: deep: diff --git a/kluctl/utils/jinja2_utils.py b/kluctl/utils/jinja2_utils.py index 81f0f2aca..4ecb8f0cf 100644 --- a/kluctl/utils/jinja2_utils.py +++ b/kluctl/utils/jinja2_utils.py @@ -9,7 +9,7 @@ import jinja2 from jinja2 import Environment, StrictUndefined, FileSystemLoader, TemplateNotFound, TemplateError -from kluctl.utils.dict_utils import merge_dict +from kluctl.utils.dict_utils import merge_dict, get_dict_value from kluctl.utils.yaml_utils import yaml_dump, yaml_load logger = logging.getLogger(__name__) @@ -66,25 +66,15 @@ def load_template(ctx, path, **kwargs): class VarNotFoundException(Exception): pass -def get_var2(ctx, path): - path = path.split('.') - v = ctx.parent - for p in path: - if p not in v: - raise VarNotFoundException() - v = v.get(p) - return v - @jinja2.pass_context def get_var(ctx, path, default): if not isinstance(path, list): path = [path] for p in path: - try: - r = get_var2(ctx, p) - return r - except VarNotFoundException: - pass + r = get_dict_value(ctx.parent, p, VarNotFoundException()) + if isinstance(r, VarNotFoundException): + continue + return r return default def update_dict(a, b): From 768faffd9c31be3675ec1cb4e834ac875ea20fb2 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 20 Sep 2021 12:10:26 +0200 Subject: [PATCH 0181/2916] fix: Fix uses of os.path.join() --- kluctl/e2e/kluctl_test_project.py | 3 ++- kluctl/kluctl_project/kluctl_project.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/kluctl/e2e/kluctl_test_project.py b/kluctl/e2e/kluctl_test_project.py index 5f3c7cacf..b623d2fda 100644 --- a/kluctl/e2e/kluctl_test_project.py +++ b/kluctl/e2e/kluctl_test_project.py @@ -3,6 +3,7 @@ import shutil import subprocess import sys +from pathlib import Path from tempfile import TemporaryDirectory from git import Git @@ -186,7 +187,7 @@ def do_update(y): def add_deployment_includes(self, dir): p = ["."] - for x in os.path.split(dir): + for x in Path(dir).parts: self.add_deployment_include(os.path.join(*p), x) p.append(x) diff --git a/kluctl/kluctl_project/kluctl_project.py b/kluctl/kluctl_project/kluctl_project.py index 58f058de3..b5a0491ce 100644 --- a/kluctl/kluctl_project/kluctl_project.py +++ b/kluctl/kluctl_project/kluctl_project.py @@ -7,6 +7,7 @@ import shutil import tarfile from contextlib import contextmanager +from pathlib import Path from tempfile import TemporaryDirectory, NamedTemporaryFile from typing import ContextManager @@ -73,7 +74,7 @@ def create_tgz(self, path, metadata_path, reproducible): with gzip.GzipFile(filename="reproducible" if reproducible else None, mode="wb", compresslevel=9, fileobj=f, mtime=0 if reproducible else None) as gz: with tarfile.TarFile.taropen("", mode="w", fileobj=gz) as tar: def mf_filter(ti: tarfile.TarInfo): - if ".git" in os.path.split(ti.name): + if ".git" in Path(ti.name).parts: return None if reproducible: # make the tar reproducible (always same hash) From 1b2db2d7c3ad81422a1fb40f370eaf401a3c9c85 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 20 Sep 2021 12:36:13 +0200 Subject: [PATCH 0182/2916] test: Add more inclusion tests --- kluctl/e2e/kluctl_test_project.py | 13 ++++++- kluctl/e2e/test_inclusion.py | 65 ++++++++++++++++++++++++++++--- 2 files changed, 70 insertions(+), 8 deletions(-) diff --git a/kluctl/e2e/kluctl_test_project.py b/kluctl/e2e/kluctl_test_project.py index b623d2fda..4c07e06a1 100644 --- a/kluctl/e2e/kluctl_test_project.py +++ b/kluctl/e2e/kluctl_test_project.py @@ -130,6 +130,15 @@ def get_deployment_yaml(self, dir): path = os.path.join(self.get_deployment_dir(), dir, "deployment.yml") return yaml_load_file(path) + def list_kustomize_deployments(self, dir="."): + ret = [] + y = self.get_deployment_yaml(dir) + for inc in y.get("includes", []): + ret += self.list_kustomize_deployments(os.path.join(dir, inc["path"])) + + ret += self.get_deployment_yaml(dir).get("kustomizeDirs", []) + return ret + def copy_deployment(self, source): shutil.copytree(source, self.get_deployment_dir()) self._commit(self.get_deployment_dir(), all=True, message="copy deployment from %s" % source) @@ -174,8 +183,8 @@ def do_update(y): def add_deployment_include(self, dir, include, tags=None): def do_update(y): includes = y.setdefault("includes", []) - if any(x["path" == include] for x in includes): - return + if any(x["path"] == include for x in includes): + return y o = { "path": include, } diff --git a/kluctl/e2e/test_inclusion.py b/kluctl/e2e/test_inclusion.py index da6249b23..8409d81ea 100644 --- a/kluctl/e2e/test_inclusion.py +++ b/kluctl/e2e/test_inclusion.py @@ -4,7 +4,7 @@ from kluctl.e2e.kluctl_test_project import KluctlTestProject from kluctl.e2e.kluctl_test_project_helpers import add_configmap_deployment -def prepare_project(module_kind_cluster: KindCluster, p: KluctlTestProject, namespace): +def prepare_project(module_kind_cluster: KindCluster, p: KluctlTestProject, namespace, with_includes): recreate_namespace(module_kind_cluster, namespace) p.update_kind_cluster(module_kind_cluster) @@ -17,19 +17,34 @@ def prepare_project(module_kind_cluster: KindCluster, p: KluctlTestProject, name add_configmap_deployment(p, "cm6", "cm6", namespace=namespace, tags=["tag1", "tag5"]) add_configmap_deployment(p, "cm7", "cm7", namespace=namespace, tags=["tag1", "tag6"]) + if with_includes: + p.add_deployment_include(".", "include1") + add_configmap_deployment(p, "include1/icm1", "icm1", namespace=namespace, tags=["itag1", "itag2"]) + + p.add_deployment_include(".", "include2") + add_configmap_deployment(p, "include2/icm2", "icm2", namespace=namespace) + add_configmap_deployment(p, "include2/icm3", "icm3", namespace=namespace, tags=["itag3", "itag4"]) + + p.add_deployment_include(".", "include3", tags=["itag5"]) + add_configmap_deployment(p, "include3/icm4", "icm4", namespace=namespace) + add_configmap_deployment(p, "include3/icm5", "icm5", namespace=namespace, tags=["itag5", "itag6"]) + def assert_exists_helper(kind_cluster, p, namespace, should_exists, add=None): if add is not None: for x in add: should_exists.add(x) - for x in p.get_deployment_yaml(".")["kustomizeDirs"]: + found = set() + for x in p.list_kustomize_deployments(): if x["path"] in should_exists: assert_resource_exists(kind_cluster, namespace, "ConfigMap/%s" % x["path"]) + found.add(x["path"]) else: assert_resource_not_exists(kind_cluster, namespace, "ConfigMap/%s" % x["path"]) + assert found == should_exists -def test_inclusion_exclusion(module_kind_cluster: KindCluster): +def test_inclusion_tags(module_kind_cluster: KindCluster): with KluctlTestProject("inclusion") as p: - prepare_project(module_kind_cluster, p, "inclusion") + prepare_project(module_kind_cluster, p, "inclusion", False) should_exists = set() def do_assert_exists(add=None): @@ -59,9 +74,9 @@ def do_assert_exists(add=None): p.kluctl("deploy", "--yes", "-t", "test", "-I", "tag1") do_assert_exists({"cm6", "cm7"}) -def test_exclusion_exclusion(module_kind_cluster: KindCluster): +def test_exclusion_tags(module_kind_cluster: KindCluster): with KluctlTestProject("exclusion") as p: - prepare_project(module_kind_cluster, p, "exclusion") + prepare_project(module_kind_cluster, p, "exclusion", False) should_exists = set() def do_assert_exists(add=None): @@ -80,3 +95,41 @@ def do_assert_exists(add=None): # Test that exclusion has precedence over inclusion p.kluctl("deploy", "--yes", "-t", "test", "-I", "tag1", "-E", "tag6") do_assert_exists({"cm3", "cm4", "cm5", "cm6"}) + +def test_inclusion_include_dirs(module_kind_cluster: KindCluster): + with KluctlTestProject("include-dirs") as p: + prepare_project(module_kind_cluster, p, "include-dirs", True) + + should_exists = set() + def do_assert_exists(add=None): + assert_exists_helper(module_kind_cluster, p, "include-dirs", should_exists, add) + + do_assert_exists() + + p.kluctl("deploy", "--yes", "-t", "test", "-I", "itag1") + do_assert_exists({"icm1"}) + + p.kluctl("deploy", "--yes", "-t", "test", "-I", "include2") + do_assert_exists({"icm2", "icm3"}) + + p.kluctl("deploy", "--yes", "-t", "test", "-I", "itag5") + do_assert_exists({"icm4", "icm5"}) + +def test_inclusion_kustomize_dirs(module_kind_cluster: KindCluster): + with KluctlTestProject("include-dirs") as p: + prepare_project(module_kind_cluster, p, "include-dirs", True) + + should_exists = set() + def do_assert_exists(add=None): + assert_exists_helper(module_kind_cluster, p, "include-dirs", should_exists, add) + + do_assert_exists() + + p.kluctl("deploy", "--yes", "-t", "test", "--include-kustomize-dir", "include1/icm1") + do_assert_exists({"icm1"}) + + p.kluctl("deploy", "--yes", "-t", "test", "--include-kustomize-dir", "include2/icm3") + do_assert_exists({"icm3"}) + + p.kluctl("deploy", "--yes", "-t", "test", "--exclude-kustomize-dir", "include3/icm5") + do_assert_exists(set(x["path"] for x in p.list_kustomize_deployments()) - {"icm5"}) From 85e39aaf80bf3351c85c97c0838dbd231a088ff9 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 20 Sep 2021 12:57:53 +0200 Subject: [PATCH 0183/2916] feat: Rename purge command to prune --- README.md | 4 ++-- docs/commands.md | 14 +++++++------- docs/deployments.md | 2 +- kluctl/cli/command_stubs.py | 14 +++++++------- kluctl/cli/commands.py | 18 +++++++++--------- kluctl/deployment/deployment_collection.py | 2 +- kluctl/e2e/test_command_bootstrap.py | 2 +- 7 files changed, 28 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 3343fec82..77172056b 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ ![kluctl](logo/kluctl.png) kluctl is the missing glue that puts together your (and any third-party) deployments into one large declarative -Kubernetes deployment, while making it fully manageable (deploy, diff, purge, delete, ...) via one unified command +Kubernetes deployment, while making it fully manageable (deploy, diff, prune, delete, ...) via one unified command line interface. Use kluctl to: @@ -11,7 +11,7 @@ Use kluctl to: * Do the same for small and simple deployments, as the overhead is small * Always know what the state of your deployments is by being able to run diffs on the whole deployment * Always know what you actually changed after performing a deployment -* Keep your clusters clean by issuing regular purge calls +* Keep your clusters clean by issuing regular prune calls * Deploy the same deployment to multiple environments (dev, test, prod, ...), with flexible differences in configuration * Manage multiple target clusters (in multiple clouds or bare-metal if you want) * Manage encrypted secrets for multiple target environments and clusters (based on [Sealed Secrets](https://github.com/bitnami-labs/sealed-secrets)) diff --git a/docs/commands.md b/docs/commands.md index 4077dec1e..9f061579f 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -127,7 +127,7 @@ Usage: kluctl deploy [OPTIONS] Deploys a target to the corresponding cluster. This command will also output a diff between the initial state and the state after deployment. The format of this - diff is the same as for the `diff` command. It will also output a list of purgable objects (without actually + diff is the same as for the `diff` command. It will also output a list of prunable objects (without actually deleting them). @@ -196,7 +196,7 @@ Usage: kluctl diff [OPTIONS] The output is by default in human readable form (a table combined with unified diffs). The output can also be changed to output yaml file. Please note however that the format is currently not documented and prone to changes. - After the diff is performed, the command will also search for purgable objects and list them. + After the diff is performed, the command will also search for prunable objects and list them. @@ -257,11 +257,11 @@ In addition, the following arguments are available: They have the same meaning as described in [deploy](#deploy). -## purge - -Usage: kluctl purge [OPTIONS] +## prune + +Usage: kluctl prune [OPTIONS] - Searches the target cluster for purgable objects and deletes them. + Searches the target cluster for prunable objects and deletes them. Searching works by: @@ -277,7 +277,7 @@ The following sets of arguments are available: 1. [inclusion/exclusion arguments](#inclusionexclusion-arguments) In addition, the following arguments are available: - + ``` Misc arguments: -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. diff --git a/docs/deployments.md b/docs/deployments.md index 4510f3710..576c03689 100644 --- a/docs/deployments.md +++ b/docs/deployments.md @@ -204,7 +204,7 @@ kustomize which causes `commonLabels` to also be applied to label selectors, whi read-only when it comes to `commonLabels`. ### deleteByLabels -A dictionary of labels used to filter resources when performing `kluctl delete` or `kluctl purge` operations. +A dictionary of labels used to filter resources when performing `kluctl delete` or `kluctl prune` operations. It should usually match `commonLabels`, but can also omit parts of `commonLabels` (DANGEROUS!!!). It should however never add labels that are not present in `commonLabels`. diff --git a/kluctl/cli/command_stubs.py b/kluctl/cli/command_stubs.py index b40444164..8d91521e9 100644 --- a/kluctl/cli/command_stubs.py +++ b/kluctl/cli/command_stubs.py @@ -19,7 +19,7 @@ def bootstrap_command_stub(obj, **kwargs): help="Deploys a target to the corresponding cluster.\n\n" "This command will also output a diff between the initial state and the state after " "deployment. The format of this diff is the same as for the `diff` command. " - "It will also output a list of purgable objects (without actually deleting them).") + "It will also output a list of prunable objects (without actually deleting them).") @kluctl_project_args() @image_args() @include_exclude_args() @@ -34,7 +34,7 @@ def deploy_command_stub(obj, **kwargs): "The output is by default in human readable form (a table combined with unified diffs). " "The output can also be changed to output yaml file. Please note however that the format " "is currently not documented and prone to changes.\n\n" - "After the diff is performed, the command will also search for purgable objects and list them.") + "After the diff is performed, the command will also search for prunable objects and list them.") @kluctl_project_args() @image_args() @include_exclude_args() @@ -59,8 +59,8 @@ def delete_command_stub(obj, **kwargs): from kluctl.cli.commands import delete_command delete_command(obj, kwargs) -@cli_group.command("purge", - help="Searches the target cluster for purgable objects and deletes them.\n\n" +@cli_group.command("prune", + help="Searches the target cluster for prunable objects and deletes them.\n\n" "Searching works by:\n\n" "\b\n" " 1. Search the cluster for all objects match `deleteByLabels`, as configured in `deployment.yml`\n" @@ -71,9 +71,9 @@ def delete_command_stub(obj, **kwargs): @include_exclude_args() @misc_arguments(yes=True, dry_run=True) @click.pass_obj -def purge_command_stub(obj, **kwargs): - from kluctl.cli.commands import purge_command - purge_command(obj, kwargs) +def prune_command_stub(obj, **kwargs): + from kluctl.cli.commands import prune_command + prune_command(obj, kwargs) @cli_group.command("poke-images", help="Replace all images in target.\n\n" diff --git a/kluctl/cli/commands.py b/kluctl/cli/commands.py index 0af6e925a..3fbc6b4a1 100644 --- a/kluctl/cli/commands.py +++ b/kluctl/cli/commands.py @@ -32,7 +32,7 @@ def bootstrap_command(obj, kwargs): err=True, abort=True) deploy_command2(obj, kwargs, cmd_ctx) - purge_command2(obj, kwargs, cmd_ctx) + prune_command2(obj, kwargs, cmd_ctx) def deploy_command(obj, kwargs): with project_command_context(kwargs) as cmd_ctx: @@ -44,7 +44,7 @@ def deploy_command2(obj, kwargs, cmd_ctx): diff_result = cmd_ctx.deployment_collection.deploy(cmd_ctx.k8s_cluster, kwargs["force_apply"], kwargs["replace_on_error"], kwargs["force_replace_on_error"], kwargs["abort_on_error"]) - deleted_objects = cmd_ctx.deployment_collection.find_purge_objects(cmd_ctx.k8s_cluster) + deleted_objects = cmd_ctx.deployment_collection.find_prune_objects(cmd_ctx.k8s_cluster) output_diff_result(kwargs["output"], cmd_ctx.deployment_collection, diff_result, deleted_objects) if diff_result.errors: sys.exit(1) @@ -54,7 +54,7 @@ def diff_command(obj, kwargs): result = cmd_ctx.deployment_collection.diff( cmd_ctx.k8s_cluster, kwargs["force_apply"], kwargs["replace_on_error"], kwargs["force_replace_on_error"], kwargs["ignore_tags"], kwargs["ignore_labels"], kwargs["ignore_annotations"], kwargs["ignore_order"]) - deleted_objects = cmd_ctx.deployment_collection.find_purge_objects(cmd_ctx.k8s_cluster) + deleted_objects = cmd_ctx.deployment_collection.find_prune_objects(cmd_ctx.k8s_cluster) output_diff_result(kwargs["output"], cmd_ctx.deployment_collection, result, deleted_objects) sys.exit(1 if result.errors else 0) @@ -76,12 +76,12 @@ def delete_command(obj, kwargs): objects = cmd_ctx.deployment_collection.find_delete_objects(cmd_ctx.k8s_cluster) confirmed_delete_objects(cmd_ctx.k8s_cluster, objects, kwargs) -def purge_command(obj, kwargs): +def prune_command(obj, kwargs): with project_command_context(kwargs) as cmd_ctx: - purge_command2(obj, kwargs, cmd_ctx) + prune_command2(obj, kwargs, cmd_ctx) -def purge_command2(obj, kwargs, cmd_ctx): - objects = cmd_ctx.deployment_collection.find_purge_objects(cmd_ctx.k8s_cluster) +def prune_command2(obj, kwargs, cmd_ctx): + objects = cmd_ctx.deployment_collection.find_prune_objects(cmd_ctx.k8s_cluster) confirmed_delete_objects(cmd_ctx.k8s_cluster, objects, kwargs) def poke_images_command(obj, kwargs): @@ -90,7 +90,7 @@ def poke_images_command(obj, kwargs): click.confirm("Do you really want to poke images to the context/cluster %s?" % cmd_ctx.k8s_cluster.context, err=True, abort=True) diff_result = cmd_ctx.deployment_collection.poke_images(cmd_ctx.k8s_cluster) - deleted_objects = cmd_ctx.deployment_collection.find_purge_objects(cmd_ctx.k8s_cluster) + deleted_objects = cmd_ctx.deployment_collection.find_prune_objects(cmd_ctx.k8s_cluster) output_diff_result(kwargs["output"], cmd_ctx.deployment_collection, diff_result, deleted_objects) if diff_result.errors: sys.exit(1) @@ -101,7 +101,7 @@ def downscale_command(obj, kwargs): click.confirm("Do you really want to downscale on context/cluster %s?" % cmd_ctx.k8s_cluster.context, err=True, abort=True) diff_result = cmd_ctx.deployment_collection.downscale(cmd_ctx.k8s_cluster) - deleted_objects = cmd_ctx.deployment_collection.find_purge_objects(cmd_ctx.k8s_cluster) + deleted_objects = cmd_ctx.deployment_collection.find_prune_objects(cmd_ctx.k8s_cluster) output_diff_result(kwargs["output"], cmd_ctx.deployment_collection, diff_result, deleted_objects) if diff_result.errors: sys.exit(1) diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index ae9bb021b..3e133007e 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -255,7 +255,7 @@ def find_delete_objects(self, k8s_cluster): labels = self.project.get_delete_by_labels() return find_objects_for_delete(k8s_cluster, labels, self.inclusion, []) - def find_purge_objects(self, k8s_cluster): + def find_prune_objects(self, k8s_cluster): self.clear_errors_and_warnings() logger.info("Searching objects not found in local objects") labels = self.project.get_delete_by_labels() diff --git a/kluctl/e2e/test_command_bootstrap.py b/kluctl/e2e/test_command_bootstrap.py index d2e53dca1..93c292659 100644 --- a/kluctl/e2e/test_command_bootstrap.py +++ b/kluctl/e2e/test_command_bootstrap.py @@ -47,7 +47,7 @@ def test_command_bootstrap_upgrade(module_kind_cluster): assert_resource_exists(module_kind_cluster, "kube-system", "ConfigMap/dummy-configmap") @pytest.mark.dependency(depends=["test_command_bootstrap_upgrade"]) -def test_command_bootstrap_purge(module_kind_cluster): +def test_command_bootstrap_prune(module_kind_cluster): with KluctlTestProject("bootstrap") as p: p.update_kind_cluster(module_kind_cluster) p.update_target("test", "module") From 64dc797a2eb25c6f275a65c7eda9417d62bd3c3b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 21 Sep 2021 15:24:50 +0200 Subject: [PATCH 0184/2916] test: Add inclusion tests for pruning --- kluctl/e2e/kluctl_test_project.py | 16 +++++++++- kluctl/e2e/test_inclusion.py | 49 +++++++++++++++++++++++++------ 2 files changed, 55 insertions(+), 10 deletions(-) diff --git a/kluctl/e2e/kluctl_test_project.py b/kluctl/e2e/kluctl_test_project.py index 4c07e06a1..d80877ccc 100644 --- a/kluctl/e2e/kluctl_test_project.py +++ b/kluctl/e2e/kluctl_test_project.py @@ -9,7 +9,7 @@ from git import Git from pytest_kind import KindCluster -from kluctl.utils.dict_utils import copy_dict, set_dict_value +from kluctl.utils.dict_utils import copy_dict, set_dict_value, del_dict_value from kluctl.utils.run_helper import run_helper from kluctl.utils.yaml_utils import yaml_save_file, yaml_load_file @@ -139,6 +139,9 @@ def list_kustomize_deployments(self, dir="."): ret += self.get_deployment_yaml(dir).get("kustomizeDirs", []) return ret + def list_kustomize_deployment_pathes(self, dir="."): + return [x["path"] for x in self.list_kustomize_deployments(dir)] + def copy_deployment(self, source): shutil.copytree(source, self.get_deployment_dir()) self._commit(self.get_deployment_dir(), all=True, message="copy deployment from %s" % source) @@ -233,6 +236,17 @@ def do_update(y): return y self.update_deployment_yaml(deployment_dir, do_update) + def delete_kustomize_deployment(self, dir): + deployment_dir = os.path.dirname(dir) + + def do_update(y): + for i in range(len(y.get("kustomizeDirs", []))): + if y["kustomizeDirs"][i]["path"] == os.path.basename(dir): + del y["kustomizeDirs"][i] + break + return y + self.update_deployment_yaml(deployment_dir, do_update) + def get_kluctl_project_dir(self): return os.path.join(self.base_dir.name, "kluctl-project") diff --git a/kluctl/e2e/test_inclusion.py b/kluctl/e2e/test_inclusion.py index 8409d81ea..7e306bdc7 100644 --- a/kluctl/e2e/test_inclusion.py +++ b/kluctl/e2e/test_inclusion.py @@ -3,6 +3,9 @@ from kluctl.e2e.conftest import assert_resource_exists, recreate_namespace, assert_resource_not_exists from kluctl.e2e.kluctl_test_project import KluctlTestProject from kluctl.e2e.kluctl_test_project_helpers import add_configmap_deployment +from kluctl.utils.dict_utils import get_dict_value +from kluctl.utils.yaml_utils import yaml_load + def prepare_project(module_kind_cluster: KindCluster, p: KluctlTestProject, namespace, with_includes): recreate_namespace(module_kind_cluster, namespace) @@ -29,17 +32,16 @@ def prepare_project(module_kind_cluster: KindCluster, p: KluctlTestProject, name add_configmap_deployment(p, "include3/icm4", "icm4", namespace=namespace) add_configmap_deployment(p, "include3/icm5", "icm5", namespace=namespace, tags=["itag5", "itag6"]) -def assert_exists_helper(kind_cluster, p, namespace, should_exists, add=None): +def assert_exists_helper(kind_cluster, p, namespace, should_exists, add=None, remove=None): if add is not None: for x in add: should_exists.add(x) - found = set() - for x in p.list_kustomize_deployments(): - if x["path"] in should_exists: - assert_resource_exists(kind_cluster, namespace, "ConfigMap/%s" % x["path"]) - found.add(x["path"]) - else: - assert_resource_not_exists(kind_cluster, namespace, "ConfigMap/%s" % x["path"]) + if remove is not None: + for x in remove: + should_exists.remove(x) + exists = kind_cluster.kubectl("-n", namespace, "get", "configmaps", "-l", "project_name=%s" % p.project_name, "-o", "yaml") + exists = yaml_load(exists)["items"] + found = set(get_dict_value(x, "metadata.name") for x in exists) assert found == should_exists def test_inclusion_tags(module_kind_cluster: KindCluster): @@ -132,4 +134,33 @@ def do_assert_exists(add=None): do_assert_exists({"icm3"}) p.kluctl("deploy", "--yes", "-t", "test", "--exclude-kustomize-dir", "include3/icm5") - do_assert_exists(set(x["path"] for x in p.list_kustomize_deployments()) - {"icm5"}) + do_assert_exists(set(p.list_kustomize_deployment_pathes()) - {"icm5"}) + +def test_inclusion_prune(module_kind_cluster: KindCluster): + with KluctlTestProject("inclusion-prune") as p: + prepare_project(module_kind_cluster, p, "inclusion-prune", False) + + should_exists = set() + def do_assert_exists(add=None, remove=None): + assert_exists_helper(module_kind_cluster, p, "inclusion-prune", should_exists, add, remove) + + p.kluctl("deploy", "--yes", "-t", "test") + do_assert_exists(p.list_kustomize_deployment_pathes()) + + p.delete_kustomize_deployment("cm1") + p.kluctl("prune", "--yes", "-t", "test", "-I", "non-existent-tag") + do_assert_exists() + + p.kluctl("prune", "--yes", "-t", "test", "-I", "cm1") + do_assert_exists(remove={"cm1"}) + + p.delete_kustomize_deployment("cm2") + p.kluctl("prune", "--yes", "-t", "test", "-E", "cm2") + do_assert_exists() + + p.delete_kustomize_deployment("cm3") + p.kluctl("prune", "--yes", "-t", "test", "--exclude-kustomize-dir", "cm3") + do_assert_exists(remove={"cm2"}) + + p.kluctl("prune", "--yes", "-t", "test") + do_assert_exists(remove={"cm3"}) From 4bdcdd3b5024d5923b513a71af20d2bd1eab83af Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 21 Sep 2021 15:25:50 +0200 Subject: [PATCH 0185/2916] fix: Also include non-excluded resources in get_included_objects --- kluctl/utils/k8s_delete_utils.py | 2 +- kluctl/utils/k8s_object_utils.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/kluctl/utils/k8s_delete_utils.py b/kluctl/utils/k8s_delete_utils.py index 9523f3fd3..fd8260da2 100644 --- a/kluctl/utils/k8s_delete_utils.py +++ b/kluctl/utils/k8s_delete_utils.py @@ -72,7 +72,7 @@ def exclude(x): def find_objects_for_delete(k8s_cluster, labels, inclusion, excluded_objects): logger.info("Getting all cluster objects matching deleteByLabels") - all_cluster_objects = get_included_objects(k8s_cluster, ["delete"], labels, inclusion, True) + all_cluster_objects = get_included_objects(k8s_cluster, ["delete"], labels, inclusion) all_cluster_objects = [x for x, warnings in all_cluster_objects] ret = [] diff --git a/kluctl/utils/k8s_object_utils.py b/kluctl/utils/k8s_object_utils.py index dd67cfe19..29ae8503b 100644 --- a/kluctl/utils/k8s_object_utils.py +++ b/kluctl/utils/k8s_object_utils.py @@ -106,7 +106,7 @@ def get_objects_metadata(k8s_cluster, verbs, labels): return ret -def get_included_objects(k8s_cluster, verbs, labels, inclusion, exclude_if_not_included=False): +def get_included_objects(k8s_cluster, verbs, labels, inclusion): resources = get_objects_metadata(k8s_cluster, verbs, labels) ret = [] @@ -118,7 +118,7 @@ def get_included_objects(k8s_cluster, verbs, labels, inclusion, exclude_if_not_i if kustomize_dir is not None: inclusion_values.append(("kustomize_dir", kustomize_dir)) - if inclusion.check_included(inclusion_values, exclude_if_not_included): + if inclusion.check_included(inclusion_values): ret.append((r, warnings)) return ret From 414e2fec7281eb9c90b7db66c9c1ffd3f4fefbc0 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 21 Sep 2021 16:50:18 +0200 Subject: [PATCH 0186/2916] test: Add inclusion tests for delete --- kluctl/e2e/kluctl_test_project.py | 2 +- kluctl/e2e/test_inclusion.py | 25 +++++++++++++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/kluctl/e2e/kluctl_test_project.py b/kluctl/e2e/kluctl_test_project.py index d80877ccc..861029384 100644 --- a/kluctl/e2e/kluctl_test_project.py +++ b/kluctl/e2e/kluctl_test_project.py @@ -31,7 +31,7 @@ def __init__(self, project_name, kluctl_project_external=False, self.kubeconfigs = [] def __enter__(self): - self.base_dir = TemporaryDirectory() + self.base_dir = TemporaryDirectory(prefix="kluctl-e2e-") os.makedirs(self.get_kluctl_project_dir(), exist_ok=True) os.makedirs(os.path.join(self.get_clusters_dir(), "clusters"), exist_ok=True) diff --git a/kluctl/e2e/test_inclusion.py b/kluctl/e2e/test_inclusion.py index 7e306bdc7..d6687ebb6 100644 --- a/kluctl/e2e/test_inclusion.py +++ b/kluctl/e2e/test_inclusion.py @@ -1,6 +1,6 @@ from pytest_kind import KindCluster -from kluctl.e2e.conftest import assert_resource_exists, recreate_namespace, assert_resource_not_exists +from kluctl.e2e.conftest import recreate_namespace from kluctl.e2e.kluctl_test_project import KluctlTestProject from kluctl.e2e.kluctl_test_project_helpers import add_configmap_deployment from kluctl.utils.dict_utils import get_dict_value @@ -38,7 +38,8 @@ def assert_exists_helper(kind_cluster, p, namespace, should_exists, add=None, re should_exists.add(x) if remove is not None: for x in remove: - should_exists.remove(x) + if x in should_exists: + should_exists.remove(x) exists = kind_cluster.kubectl("-n", namespace, "get", "configmaps", "-l", "project_name=%s" % p.project_name, "-o", "yaml") exists = yaml_load(exists)["items"] found = set(get_dict_value(x, "metadata.name") for x in exists) @@ -164,3 +165,23 @@ def do_assert_exists(add=None, remove=None): p.kluctl("prune", "--yes", "-t", "test") do_assert_exists(remove={"cm3"}) + +def test_inclusion_delete(module_kind_cluster: KindCluster): + with KluctlTestProject("inclusion-delete") as p: + prepare_project(module_kind_cluster, p, "inclusion-delete", False) + + should_exists = set() + def do_assert_exists(add=None, remove=None): + assert_exists_helper(module_kind_cluster, p, "inclusion-delete", should_exists, add, remove) + + p.kluctl("deploy", "--yes", "-t", "test") + do_assert_exists(p.list_kustomize_deployment_pathes()) + + p.kluctl("delete", "--yes", "-t", "test", "-I", "non-existent-tag") + do_assert_exists() + + p.kluctl("delete", "--yes", "-t", "test", "-I", "cm1") + do_assert_exists(remove={"cm1"}) + + p.kluctl("delete", "--yes", "-t", "test", "-E", "cm2") + do_assert_exists(remove=set(p.list_kustomize_deployment_pathes()) - {"cm1", "cm2"}) From 125c06ac7ef4f454d874586c194a7755636330c3 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 22 Sep 2021 15:45:46 +0200 Subject: [PATCH 0187/2916] fix: Fix warning for unsupported helm hooks --- kluctl/deployment/hooks_util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kluctl/deployment/hooks_util.py b/kluctl/deployment/hooks_util.py index 29bbb6628..46f970bd3 100644 --- a/kluctl/deployment/hooks_util.py +++ b/kluctl/deployment/hooks_util.py @@ -86,7 +86,7 @@ def get_list(path): helm_hooks = get_list('metadata.annotations."helm.sh/hook"') for h in helm_hooks: if h not in supported_helm_hooks: - self.apply_util.deployment_collection.add_api_warnings(get_object_ref(o), "Unsupported helm.sh/hook '%s'" % h) + self.apply_util.deployment_collection.add_api_warnings(get_object_ref(o), ["Unsupported helm.sh/hook '%s'" % h]) if "pre-install" in helm_hooks: hooks.append("pre-deploy-initial") From 3e8154e4eadd0187e6975679365c9b39bab24e97 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 22 Sep 2021 15:47:07 +0200 Subject: [PATCH 0188/2916] feat: Add some logging for helm hooks --- kluctl/deployment/apply_util.py | 4 +--- kluctl/deployment/hooks_util.py | 20 +++++++++++++------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/kluctl/deployment/apply_util.py b/kluctl/deployment/apply_util.py index 21395688b..bb1fa307a 100644 --- a/kluctl/deployment/apply_util.py +++ b/kluctl/deployment/apply_util.py @@ -102,7 +102,6 @@ def wait_object(self, ref): if self.dry_run or self.k8s_cluster.dry_run: return True - start_time = time.time() did_log = False logger.debug("Starting wait for hook %s" % get_long_object_name_from_ref(ref)) while True: @@ -116,8 +115,7 @@ def wait_object(self, ref): if v.errors: return False - time.sleep(1) - if time.time() - start_time > 5 and not did_log: + if not did_log: logger.info("Waiting for for hook %s to get ready..." % get_long_object_name_from_ref(ref)) did_log = True diff --git a/kluctl/deployment/hooks_util.py b/kluctl/deployment/hooks_util.py index 46f970bd3..bba8e4d1d 100644 --- a/kluctl/deployment/hooks_util.py +++ b/kluctl/deployment/hooks_util.py @@ -1,11 +1,13 @@ import dataclasses +import logging from typing import List, Set, Optional from kluctl.utils.dict_utils import get_dict_value -from kluctl.utils.k8s_object_utils import get_object_ref +from kluctl.utils.k8s_object_utils import get_object_ref, get_long_object_name from kluctl.utils.utils import parse_bool +logger = logging.getLogger(__name__) @dataclasses.dataclass class Hook: @@ -27,11 +29,13 @@ def run_hooks(self, d, hook): if self.apply_util.abort_signal: return if "before-hook-creation" in h.delete_policies: + logger.info("Deleting hook %s due to hook-delete-policy %s" % (get_long_object_name(h.object), ",".join(h.delete_policies))) self.apply_util.delete_object(get_object_ref(h.object)) for h in l: if self.apply_util.abort_signal: return + logger.info("Deploying hook %s" % get_long_object_name(h.object)) if (self.apply_util.dry_run or self.apply_util.k8s_cluster.dry_run) and "before-hook-creation" in h.delete_policies: self.apply_util.handle_result(h.object, []) continue @@ -52,12 +56,14 @@ def run_hooks(self, d, hook): ref = get_object_ref(h.object) if ref not in wait_results: continue - if wait_results[ref]: - if "hook-succeeded" in h.delete_policies: - self.apply_util.delete_object(ref) - else: - if "hook-failed" in h.delete_policies: - self.apply_util.delete_object(ref) + do_delete = False + if wait_results[ref] and "hook-succeeded" in h.delete_policies: + do_delete = True + elif not wait_results[ref] and "hook-failed" in h.delete_policies: + do_delete = True + if do_delete: + logger.info("Deleting hook %s due to hook-delete-policy %s" % (get_long_object_name(h.object), ",".join(h.delete_policies))) + self.apply_util.delete_object(ref) def get_hook(self, o) -> Optional[Hook]: def get_list(path): From 276d9a2030fe9acc2d9dc713d0c972289cea9015 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 22 Sep 2021 17:01:35 +0200 Subject: [PATCH 0189/2916] feat: Introduce pre-deploy-upgrade/post-deploy-upgrade Also change pre-deploy/post-deploy to be always executed. --- docs/helm-integration.md | 4 ++-- docs/hooks.md | 6 ++++-- kluctl/deployment/apply_util.py | 8 ++++---- kluctl/deployment/hooks_util.py | 11 ++++++----- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/docs/helm-integration.md b/docs/helm-integration.md index b704d24a6..cd7578770 100644 --- a/docs/helm-integration.md +++ b/docs/helm-integration.md @@ -26,8 +26,8 @@ based on the following mapping table: | post-install | post-deploy-initial | | pre-delete | Not supported | | post-delete | Not supported | -| pre-upgrade | pre-deploy | -| post-upgrade | post-deploy | +| pre-upgrade | pre-deploy-upgrade | +| post-upgrade | post-deploy-upgrade | | pre-rollback | Not supported | | post-rollback | Not supported | | test | Not supported | diff --git a/docs/hooks.md b/docs/hooks.md index dab17736c..0cea73244 100644 --- a/docs/hooks.md +++ b/docs/hooks.md @@ -12,8 +12,10 @@ a comma separated list of hook names. Possible value are described in the next c |---|---| | pre-deploy-initial | Executed right before the initial deployment is performed. | | post-deploy-initial | Executed right after the initial deployment is performed. | -| pre-deploy | Executed right before a non-initial deployment is performed.| -| post-deploy | Executed right after a non-initial deployment is performed. | +| pre-deploy-upgrade | Executed right before a non-initial deployment is performed. | +| post-deploy-upgrade | Executed right after a non-initial deployment is performed. | +| pre-deploy | Executed right before any (initial and non-initial) deployment is performed.| +| post-deploy | Executed right after any (initial and non-initial) deployment is performed. | A deployment is considered to be an "initial" deployment if none of the resources related to the current kustomize deployment are found on the cluster at the time of deployment. diff --git a/kluctl/deployment/apply_util.py b/kluctl/deployment/apply_util.py index bb1fa307a..75026dc5b 100644 --- a/kluctl/deployment/apply_util.py +++ b/kluctl/deployment/apply_util.py @@ -129,9 +129,9 @@ def apply_kustomize_deployment(self, d): hook_util = HooksUtil(self) if inital_deploy: - hook_util.run_hooks(d, "pre-deploy-initial") + hook_util.run_hooks(d, ["pre-deploy-initial", "pre-deploy"]) else: - hook_util.run_hooks(d, "pre-deploy") + hook_util.run_hooks(d, ["pre-deploy-upgrade", "pre-deploy"]) for o in d.objects: if hook_util.get_hook(o) is not None: @@ -139,9 +139,9 @@ def apply_kustomize_deployment(self, d): self.apply_object(o) if inital_deploy: - hook_util.run_hooks(d, "post-deploy-initial") + hook_util.run_hooks(d, ["post-deploy-initial", "post-deploy"]) else: - hook_util.run_hooks(d, "post-deploy") + hook_util.run_hooks(d, ["post-deploy-upgrade", "post-deploy"]) def apply_deployments(self): logger.info("Running server-side apply for all objects%s", self.k8s_cluster.get_dry_run_suffix(self.dry_run)) diff --git a/kluctl/deployment/hooks_util.py b/kluctl/deployment/hooks_util.py index bba8e4d1d..b8c527802 100644 --- a/kluctl/deployment/hooks_util.py +++ b/kluctl/deployment/hooks_util.py @@ -21,9 +21,9 @@ class HooksUtil: def __init__(self, apply_util): self.apply_util = apply_util - def run_hooks(self, d, hook): + def run_hooks(self, d, hooks): l = self.get_sorted_hooks_list(d.objects) - l = [x for x in l if hook in x.hooks] + l = [x for x in l if any(h in hooks for h in x.hooks)] for h in l: if self.apply_util.abort_signal: @@ -73,7 +73,8 @@ def get_list(path): return s supported_kluctl_hooks = {"pre-deploy", "post-deploy", - "pre-deploy-initial", "post-deploy-initial"} + "pre-deploy-initial", "post-deploy-initial", + "pre-deploy-upgrade", "post-deploy-upgrade"} supported_kluctl_delete_policies = {"before-hook-creation", "hook-succeeded", "hook-failed"} @@ -97,11 +98,11 @@ def get_list(path): if "pre-install" in helm_hooks: hooks.append("pre-deploy-initial") if "pre-upgrade" in helm_hooks: - hooks.append("pre-deploy") + hooks.append("pre-deploy-upgrade") if "post-install" in helm_hooks: hooks.append("post-deploy-initial") if "post-upgrade" in helm_hooks: - hooks.append("post-deploy") + hooks.append("post-deploy-upgrade") if "pre-delete" in helm_hooks: hooks.append("pre-delete") if "post-delete" in helm_hooks: From 7db5900e893b98a179c37ee0420bf57dd5145619 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 22 Sep 2021 17:01:55 +0200 Subject: [PATCH 0190/2916] test: Add tests for hooks --- kluctl/e2e/kluctl_test_project.py | 22 ++- kluctl/e2e/kluctl_test_project_helpers.py | 128 ++++++++++++--- kluctl/e2e/test_hooks.py | 182 ++++++++++++++++++++++ 3 files changed, 304 insertions(+), 28 deletions(-) create mode 100644 kluctl/e2e/test_hooks.py diff --git a/kluctl/e2e/kluctl_test_project.py b/kluctl/e2e/kluctl_test_project.py index 861029384..048c41833 100644 --- a/kluctl/e2e/kluctl_test_project.py +++ b/kluctl/e2e/kluctl_test_project.py @@ -142,6 +142,11 @@ def list_kustomize_deployments(self, dir="."): def list_kustomize_deployment_pathes(self, dir="."): return [x["path"] for x in self.list_kustomize_deployments(dir)] + + def update_kustomize_deployment(self, dir, update): + path = os.path.join(self.get_deployment_dir(), dir, "kustomization.yml") + self.update_yaml(path, update, message="Update kustomization.yml for %s" % dir) + def copy_deployment(self, source): shutil.copytree(source, self.get_deployment_dir()) self._commit(self.get_deployment_dir(), all=True, message="copy deployment from %s" % source) @@ -213,18 +218,15 @@ def add_kustomize_deployment(self, dir, resources, tags=None): os.makedirs(abs_kustomize_dir, exist_ok=True) - for name, content in resources.items(): - with open(os.path.join(abs_kustomize_dir, name), "wt") as f: - f.write(content) - y = { "apiVersion": "kustomize.config.k8s.io/v1beta1", "kind": "Kustomization", - "resources": list(resources.keys()), } yaml_save_file(y, os.path.join(abs_kustomize_dir, "kustomization.yml")) self._commit(abs_deployment_dir, all=True, message="add kustomize deployment %s" % dir) + self.add_kustomize_resources(dir, resources) + def do_update(y): kustomize_dirs = y.setdefault("kustomizeDirs", []) o = { @@ -236,6 +238,16 @@ def do_update(y): return y self.update_deployment_yaml(deployment_dir, do_update) + def add_kustomize_resources(self, dir, resources): + def do_update(y): + y.setdefault("resources", []) + y["resources"] += list(resources.keys()) + for name, content in resources.items(): + with open(os.path.join(self.get_deployment_dir(), dir, name), "wt") as f: + f.write(content) + return y + self.update_kustomize_deployment(dir, do_update) + def delete_kustomize_deployment(self, dir): deployment_dir = os.path.dirname(dir) diff --git a/kluctl/e2e/kluctl_test_project_helpers.py b/kluctl/e2e/kluctl_test_project_helpers.py index 2c26c6663..86e63883c 100644 --- a/kluctl/e2e/kluctl_test_project_helpers.py +++ b/kluctl/e2e/kluctl_test_project_helpers.py @@ -1,37 +1,118 @@ from kluctl.e2e.kluctl_test_project import KluctlTestProject -from kluctl.utils.yaml_utils import yaml_dump +from kluctl.utils.dict_utils import merge_dict, set_dict_value +from kluctl.utils.yaml_utils import yaml_dump, yaml_load, yaml_load_all, yaml_dump_all -busybox_pod = """ -apiVersion: apps/v1 -kind: Deployment + +def merge_metadata(y, labels, annotations): + if labels is not None: + merge_dict(y, { + "metadata": { + "labels": labels, + } + }, clone=False) + if annotations is not None: + merge_dict(y, { + "metadata": { + "annotations": annotations, + } + }, clone=False) + +pod_rbac=""" +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {name} + namespace: {namespace} +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: "{name}" + namespace: "{namespace}" +subjects: +- kind: ServiceAccount + name: "{name}" + namespace: "{namespace}" +roleRef: + kind: ClusterRole + name: "cluster-admin" + apiGroup: rbac.authorization.k8s.io +""" + +pod=""" +apiVersion: v1 +kind: Pod +metadata: + name: {name} + namespace: {namespace} +spec: + terminationGracePeriodSeconds: 0 + restartPolicy: OnFailure + serviceAccountName: {name} + containers: + - image: {image} + imagePullPolicy: IfNotPresent + name: container +""" + +job=""" +apiVersion: batch/v1 +kind: Job metadata: name: {name} - labels: - app: {name} namespace: {namespace} spec: - replicas: 1 - selector: - matchLabels: - app: {name} template: - metadata: - labels: - app: {name} spec: terminationGracePeriodSeconds: 0 + restartPolicy: OnFailure + serviceAccountName: {name} containers: - - name: busybox - image: busybox - command: - - sleep - - "1000" + - image: {image} + imagePullPolicy: IfNotPresent + name: container """ -def add_busybox_deployment(p: KluctlTestProject, dir, name, namespace="default"): - p.add_kustomize_deployment(dir, { - "busybox.yml": busybox_pod.format(namespace=namespace, name=name) - }) + +def add_deployment_helper(p: KluctlTestProject, dir, name, + r, with_rbac, + namespace="default", tags=None, + labels=None, annotations=None): + resources = {} + + if with_rbac: + y = pod_rbac.format(name=name, namespace=namespace) + y = yaml_load_all(y) + for x in y: + merge_metadata(x, labels, annotations) + resources["rbac.yml"] = yaml_dump_all(y) + + merge_metadata(r, labels, annotations) + resources["deploy.yml"] = yaml_dump(r) + + p.add_kustomize_deployment(dir, resources, tags=tags) + +def add_pod_deployment(p: KluctlTestProject, dir, name, + image, command, args, with_rbac, + namespace="default", **kwargs): + y = pod.format(name=name, namespace=namespace, image=image) + y = yaml_load(y) + set_dict_value(y, "spec.containers[0].command", command) + set_dict_value(y, "spec.containers[0].args", args) + add_deployment_helper(p, dir, name, y, with_rbac, namespace=namespace, **kwargs) + +def add_job_deployment(p: KluctlTestProject, dir, name, + image, command, args, with_rbac, + namespace="default", **kwargs): + y = job.format(name=name, namespace=namespace, image=image) + y = yaml_load(y) + set_dict_value(y, "spec.template.spec.containers[0].command", command) + set_dict_value(y, "spec.template.spec.containers[0].args", args) + add_deployment_helper(p, dir, name, y, with_rbac, namespace=namespace, **kwargs) + +def add_busybox_deployment(p: KluctlTestProject, dir, name, + namespace="default"): + add_pod_deployment(p, dir, name, "busybox", "sleep", ["1000"], False, namespace=namespace) def add_namespace_deployment(p: KluctlTestProject, dir, name): y = { @@ -45,7 +126,7 @@ def add_namespace_deployment(p: KluctlTestProject, dir, name): "namespace.yml": yaml_dump(y), }) -def add_configmap_deployment(p: KluctlTestProject, dir, name, namespace="default", data={}, tags=None): +def add_configmap_deployment(p: KluctlTestProject, dir, name, namespace="default", data={}, tags=None, labels=None, annotations=None): y = { "apiVersion": "v1", "kind": "ConfigMap", @@ -55,6 +136,7 @@ def add_configmap_deployment(p: KluctlTestProject, dir, name, namespace="default }, "data": data, } + merge_metadata(y, labels, annotations) p.add_kustomize_deployment(dir, { "configmap.yml": yaml_dump(y), }, tags=tags) diff --git a/kluctl/e2e/test_hooks.py b/kluctl/e2e/test_hooks.py new file mode 100644 index 000000000..2bde44d77 --- /dev/null +++ b/kluctl/e2e/test_hooks.py @@ -0,0 +1,182 @@ +import base64 +import contextlib + +from pytest_kind import KindCluster + +from kluctl.e2e.conftest import recreate_namespace, assert_resource_not_exists, assert_resource_exists +from kluctl.e2e.kluctl_test_project import KluctlTestProject +from kluctl.e2e.kluctl_test_project_helpers import add_job_deployment +from kluctl.utils.dict_utils import get_dict_value +from kluctl.utils.yaml_utils import yaml_load, yaml_dump + +hook_script=""" +kubectl get configmap -oyaml > /tmp/result.yml +cat /tmp/result.yml +if ! kubectl get secret {name}-result; then + name="{name}-result" +else + name="{name}-result2" +fi +kubectl create secret generic $name --from-file=result=/tmp/result.yml +kubectl delete configmap cm2 || true +""" + +def add_hook_deployment(p: KluctlTestProject, name, namespace, is_helm, hook, hook_deletion_policy=None): + a = {} + if is_helm: + a["helm.sh/hook"] = hook + if hook_deletion_policy is not None: + a["helm.sh/hook-deletion-policy"] = hook_deletion_policy + else: + a["kluctl.io/hook"] = hook + if hook_deletion_policy is not None: + a["kluctl.io/hook-deletion-policy"] = hook_deletion_policy + + script = hook_script.format(name=name, namespace=namespace) + + add_job_deployment(p, name, name, "bitnami/kubectl:1.21", + command=["sh"], args=["-c", script], + with_rbac=True, namespace=namespace, annotations=a) + +def add_configmap(p: KluctlTestProject, dir, name, namespace): + y = { + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "name": name, + "namespace": namespace, + }, + "data": {}, + } + + p.add_kustomize_resources(dir, { + "%s.yml" % name: yaml_dump(y) + }) + +def get_hook_result(module_kind_cluster: KindCluster, p: KluctlTestProject, secret_name): + y = module_kind_cluster.kubectl("-n", p.project_name, "get", "secret", secret_name, "-o", "yaml") + y = yaml_load(y) + y = get_dict_value(y, "data.result") + y = base64.b64decode(y).decode("utf-8") + y = yaml_load(y) + return y + +def get_hook_result_cm_names(module_kind_cluster: KindCluster, p: KluctlTestProject, second=False): + y = get_hook_result(module_kind_cluster, p, "hook-result" if not second else "hook-result2") + return [get_dict_value(x, "metadata.name") for x in y["items"]] + +@contextlib.contextmanager +def prepare_project(kind_cluster: KindCluster, name, hook, hook_deletion_policy): + namespace = "hook-%s" % name + with KluctlTestProject(namespace) as p: + recreate_namespace(kind_cluster, namespace) + + p.update_kind_cluster(kind_cluster) + p.update_target("test", "module") + + add_hook_deployment(p, "hook", namespace, is_helm=False, hook=hook, hook_deletion_policy=hook_deletion_policy) + add_configmap(p, "hook", "cm1", namespace) + + yield p + +def ensure_hook_executed(kind_cluster, p: KluctlTestProject): + try: + kind_cluster.kubectl("delete", "-n", p.project_name, "secret", "hook-result") + kind_cluster.kubectl("delete", "-n", p.project_name, "secret", "hook-result2") + except: + pass + p.kluctl("deploy", "--yes", "-t", "test") + assert_resource_exists(kind_cluster, p.project_name, "ConfigMap/cm1") + +def ensure_hook_not_executed(kind_cluster, p: KluctlTestProject): + try: + kind_cluster.kubectl("delete", "-n", p.project_name, "secret", "hook-result") + kind_cluster.kubectl("delete", "-n", p.project_name, "secret", "hook-result2") + except: + pass + p.kluctl("deploy", "--yes", "-t", "test") + assert_resource_not_exists(kind_cluster, p.project_name, "Secret/hook-result") + +def test_hooks_pre_deploy_initial(module_kind_cluster: KindCluster): + with prepare_project(module_kind_cluster, "pre-deploy-initial", "pre-deploy-initial", None) as p: + ensure_hook_executed(module_kind_cluster, p) + assert "cm1" not in get_hook_result_cm_names(module_kind_cluster, p) + ensure_hook_not_executed(module_kind_cluster, p) + +def test_hooks_post_deploy_initial(module_kind_cluster: KindCluster): + with prepare_project(module_kind_cluster, "post-deploy-initial", "post-deploy-initial", None) as p: + ensure_hook_executed(module_kind_cluster, p) + assert "cm1" in get_hook_result_cm_names(module_kind_cluster, p) + ensure_hook_not_executed(module_kind_cluster, p) + +def test_hooks_pre_deploy_upgrade(module_kind_cluster: KindCluster): + with prepare_project(module_kind_cluster, "pre-deploy-upgrade", "pre-deploy-upgrade", None) as p: + add_configmap(p, "hook", "cm2", p.project_name) + ensure_hook_not_executed(module_kind_cluster, p) + module_kind_cluster.kubectl("delete", "-n", p.project_name, "configmap", "cm1") + ensure_hook_executed(module_kind_cluster, p) + assert "cm1" not in get_hook_result_cm_names(module_kind_cluster, p) + ensure_hook_executed(module_kind_cluster, p) + assert "cm1" in get_hook_result_cm_names(module_kind_cluster, p) + + +def test_hooks_post_deploy_upgrade(module_kind_cluster: KindCluster): + with prepare_project(module_kind_cluster, "post-deploy-upgrade", "post-deploy-upgrade", None) as p: + add_configmap(p, "hook", "cm2", p.project_name) + ensure_hook_not_executed(module_kind_cluster, p) + module_kind_cluster.kubectl("delete", "-n", p.project_name, "configmap", "cm1") + ensure_hook_executed(module_kind_cluster, p) + assert "cm1" in get_hook_result_cm_names(module_kind_cluster, p) + +def do_test_hooks_pre_deploy(module_kind_cluster: KindCluster, name, hooks): + with prepare_project(module_kind_cluster, name, hooks, None) as p: + add_configmap(p, "hook", "cm2", p.project_name) + ensure_hook_executed(module_kind_cluster, p) + module_kind_cluster.kubectl("delete", "-n", p.project_name, "configmap", "cm1") + ensure_hook_executed(module_kind_cluster, p) + assert "cm1" not in get_hook_result_cm_names(module_kind_cluster, p) + ensure_hook_executed(module_kind_cluster, p) + assert "cm1" in get_hook_result_cm_names(module_kind_cluster, p) + +def do_test_hooks_post_deploy(module_kind_cluster: KindCluster, name, hooks): + with prepare_project(module_kind_cluster, name, hooks, None) as p: + add_configmap(p, "hook", "cm2", p.project_name) + ensure_hook_executed(module_kind_cluster, p) + module_kind_cluster.kubectl("delete", "-n", p.project_name, "configmap", "cm1") + ensure_hook_executed(module_kind_cluster, p) + assert "cm1" in get_hook_result_cm_names(module_kind_cluster, p) + +def do_test_hooks_pre_post_deploy(module_kind_cluster: KindCluster, name, hooks): + with prepare_project(module_kind_cluster, name, hooks, None) as p: + add_configmap(p, "hook", "cm2", p.project_name) + ensure_hook_executed(module_kind_cluster, p) + assert "cm1" not in get_hook_result_cm_names(module_kind_cluster, p) + assert "cm2" not in get_hook_result_cm_names(module_kind_cluster, p) + assert "cm1" in get_hook_result_cm_names(module_kind_cluster, p, True) + assert "cm2" in get_hook_result_cm_names(module_kind_cluster, p, True) + + ensure_hook_executed(module_kind_cluster, p) + assert "cm1" in get_hook_result_cm_names(module_kind_cluster, p) + assert "cm2" not in get_hook_result_cm_names(module_kind_cluster, p) + assert "cm1" in get_hook_result_cm_names(module_kind_cluster, p, True) + assert "cm2" in get_hook_result_cm_names(module_kind_cluster, p, True) + +def test_hooks_pre_deploy(module_kind_cluster: KindCluster): + do_test_hooks_pre_deploy(module_kind_cluster, "pre-deploy", "pre-deploy") + +def test_hooks_pre_deploy2(module_kind_cluster: KindCluster): + # same as pre-deploy + do_test_hooks_pre_deploy(module_kind_cluster, "pre-deploy2", "pre-deploy-initial,pre-deploy-upgrade") + +def test_hooks_post_deploy(module_kind_cluster: KindCluster): + do_test_hooks_post_deploy(module_kind_cluster, "post-deploy", "post-deploy") + +def test_hooks_post_deploy2(module_kind_cluster: KindCluster): + # same as post-deploy + do_test_hooks_post_deploy(module_kind_cluster, "post-deploy2", "post-deploy-initial,post-deploy-upgrade") + +def test_hooks_pre_post_deploy(module_kind_cluster: KindCluster): + do_test_hooks_pre_post_deploy(module_kind_cluster, "pre-post-deploy", "pre-deploy,post-deploy") + +def test_hooks_pre_post_deploy2(module_kind_cluster: KindCluster): + do_test_hooks_pre_post_deploy(module_kind_cluster, "pre-post-deploy2", "pre-deploy-initial,pre-deploy-upgrade,post-deploy-initial,post-deploy-upgrade") From 5dcc11de17c07ce41d3e8cd15b83ffce94277501 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 22 Sep 2021 17:32:09 +0200 Subject: [PATCH 0191/2916] fix: Better error messages on registry auth issues --- kluctl/image_registries/generic_registry.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/kluctl/image_registries/generic_registry.py b/kluctl/image_registries/generic_registry.py index e80179b5a..510da4dfa 100644 --- a/kluctl/image_registries/generic_registry.py +++ b/kluctl/image_registries/generic_registry.py @@ -1,6 +1,7 @@ import base64 import contextlib import hashlib +import http import json import logging import os @@ -13,9 +14,12 @@ import jwt import requests from dxf import DXF +from dxf.exceptions import DXFUnauthorizedError from jwt import InvalidTokenError +from requests import HTTPError from kluctl.image_registries.images_registry import ImagesRegistry +from kluctl.utils.exceptions import CommandError from kluctl.utils.run_helper import run_helper from kluctl.utils.thread_safe_cache import ThreadSafeCache, ThreadSafeMultiCache from kluctl.utils.utils import get_tmp_base_dir @@ -225,7 +229,18 @@ def do_authenticate(dxf, response): return token with first_auth_context(): logger.debug(f"calling dxf.authenticate with username={username}") - token = dxf.authenticate(username=username, password=password, response=response, actions=["pull"]) + base_auth_error = f"Got 401 Unauthorized from registry. Please ensure you provided correct registry " \ + f"credentials for '{host}', either by logging in with 'docker login {host}' or by " \ + f"providing environment variables as described in the documentation." + try: + token = dxf.authenticate(username=username, password=password, response=response, actions=["pull"]) + except DXFUnauthorizedError: + raise CommandError(f"Got 401 Unauthorized from registry. {base_auth_error}") + except HTTPError as e: + if e.response.status_code == http.HTTPStatus.FORBIDDEN: + raise CommandError(f"Got 403 Forbidden from registry. {base_auth_error}") + else: + raise if token is not None: self.set_cached_token(image, username, password, token) @@ -243,6 +258,7 @@ def is_image_from_registry(self, image): return True def list_tags_for_image(self, image): + # TODO error message dxf = self.get_client(image) tags = dxf.list_aliases(batch_size=100) return tags From fab27abb33f104e5d12bf622932eb6ebeebe6ac7 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 22 Sep 2021 18:10:37 +0200 Subject: [PATCH 0192/2916] test: Fix committing of kustomize resources --- kluctl/e2e/kluctl_test_project.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kluctl/e2e/kluctl_test_project.py b/kluctl/e2e/kluctl_test_project.py index 048c41833..7b7198a23 100644 --- a/kluctl/e2e/kluctl_test_project.py +++ b/kluctl/e2e/kluctl_test_project.py @@ -242,9 +242,11 @@ def add_kustomize_resources(self, dir, resources): def do_update(y): y.setdefault("resources", []) y["resources"] += list(resources.keys()) + g = Git(os.path.join(self.get_deployment_dir(), dir)) for name, content in resources.items(): with open(os.path.join(self.get_deployment_dir(), dir, name), "wt") as f: f.write(content) + g.add(name) return y self.update_kustomize_deployment(dir, do_update) From d99fce57a29e53b6fc369dcba429f0c253863170 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 22 Sep 2021 18:11:07 +0200 Subject: [PATCH 0193/2916] test: Use Deployment for busyboy again and always deploy rbac --- kluctl/e2e/kluctl_test_project_helpers.py | 62 +++++++++++++---------- kluctl/e2e/test_hooks.py | 2 +- 2 files changed, 36 insertions(+), 28 deletions(-) diff --git a/kluctl/e2e/kluctl_test_project_helpers.py b/kluctl/e2e/kluctl_test_project_helpers.py index 86e63883c..cb064ad65 100644 --- a/kluctl/e2e/kluctl_test_project_helpers.py +++ b/kluctl/e2e/kluctl_test_project_helpers.py @@ -39,20 +39,30 @@ def merge_metadata(y, labels, annotations): apiGroup: rbac.authorization.k8s.io """ -pod=""" -apiVersion: v1 -kind: Pod +deployment=""" +apiVersion: apps/v1 +kind: Deployment metadata: name: {name} namespace: {namespace} + labels: + app.kubernetes.io/name: {name} spec: - terminationGracePeriodSeconds: 0 - restartPolicy: OnFailure - serviceAccountName: {name} - containers: - - image: {image} - imagePullPolicy: IfNotPresent - name: container + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: {name} + template: + metadata: + labels: + app.kubernetes.io/name: {name} + spec: + terminationGracePeriodSeconds: 0 + serviceAccountName: {name} + containers: + - image: {image} + imagePullPolicy: IfNotPresent + name: container """ job=""" @@ -74,45 +84,43 @@ def merge_metadata(y, labels, annotations): """ -def add_deployment_helper(p: KluctlTestProject, dir, name, - r, with_rbac, +def add_deployment_helper(p: KluctlTestProject, dir, name, r, namespace="default", tags=None, labels=None, annotations=None): resources = {} - if with_rbac: - y = pod_rbac.format(name=name, namespace=namespace) - y = yaml_load_all(y) - for x in y: - merge_metadata(x, labels, annotations) - resources["rbac.yml"] = yaml_dump_all(y) + y = pod_rbac.format(name=name, namespace=namespace) + y = yaml_load_all(y) + for x in y: + merge_metadata(x, labels, annotations) + resources["rbac.yml"] = yaml_dump_all(y) merge_metadata(r, labels, annotations) resources["deploy.yml"] = yaml_dump(r) p.add_kustomize_deployment(dir, resources, tags=tags) -def add_pod_deployment(p: KluctlTestProject, dir, name, - image, command, args, with_rbac, +def add_deployment_deployment(p: KluctlTestProject, dir, name, + image, command, args, namespace="default", **kwargs): - y = pod.format(name=name, namespace=namespace, image=image) + y = deployment.format(name=name, namespace=namespace, image=image) y = yaml_load(y) - set_dict_value(y, "spec.containers[0].command", command) - set_dict_value(y, "spec.containers[0].args", args) - add_deployment_helper(p, dir, name, y, with_rbac, namespace=namespace, **kwargs) + set_dict_value(y, "spec.template.spec.containers[0].command", command) + set_dict_value(y, "spec.template.spec.containers[0].args", args) + add_deployment_helper(p, dir, name, y, namespace=namespace, **kwargs) def add_job_deployment(p: KluctlTestProject, dir, name, - image, command, args, with_rbac, + image, command, args, namespace="default", **kwargs): y = job.format(name=name, namespace=namespace, image=image) y = yaml_load(y) set_dict_value(y, "spec.template.spec.containers[0].command", command) set_dict_value(y, "spec.template.spec.containers[0].args", args) - add_deployment_helper(p, dir, name, y, with_rbac, namespace=namespace, **kwargs) + add_deployment_helper(p, dir, name, y, namespace=namespace, **kwargs) def add_busybox_deployment(p: KluctlTestProject, dir, name, namespace="default"): - add_pod_deployment(p, dir, name, "busybox", "sleep", ["1000"], False, namespace=namespace) + add_deployment_deployment(p, dir, name, "busybox", ["sleep"], ["1000"], namespace=namespace) def add_namespace_deployment(p: KluctlTestProject, dir, name): y = { diff --git a/kluctl/e2e/test_hooks.py b/kluctl/e2e/test_hooks.py index 2bde44d77..19aa1a0e4 100644 --- a/kluctl/e2e/test_hooks.py +++ b/kluctl/e2e/test_hooks.py @@ -36,7 +36,7 @@ def add_hook_deployment(p: KluctlTestProject, name, namespace, is_helm, hook, ho add_job_deployment(p, name, name, "bitnami/kubectl:1.21", command=["sh"], args=["-c", script], - with_rbac=True, namespace=namespace, annotations=a) + namespace=namespace, annotations=a) def add_configmap(p: KluctlTestProject, dir, name, namespace): y = { From f3b17a4fdab8aa26527f919fb69e053adf7f3069 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 22 Sep 2021 18:11:24 +0200 Subject: [PATCH 0194/2916] test: Run bootstrap tests in one single test --- kluctl/e2e/test_command_bootstrap.py | 11 ++++------- requirements-dev.txt | 1 - 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/kluctl/e2e/test_command_bootstrap.py b/kluctl/e2e/test_command_bootstrap.py index 93c292659..9b9bd9c63 100644 --- a/kluctl/e2e/test_command_bootstrap.py +++ b/kluctl/e2e/test_command_bootstrap.py @@ -2,7 +2,6 @@ import shutil from tempfile import TemporaryDirectory -import pytest from pytest_kind import KindCluster from kluctl import get_kluctl_package_dir @@ -20,17 +19,16 @@ dummy: value """ -@pytest.mark.dependency() def test_command_bootstrap(module_kind_cluster: KindCluster): + bootstrap_path = os.path.join(get_kluctl_package_dir(), "bootstrap") + with KluctlTestProject("bootstrap") as p: p.update_kind_cluster(module_kind_cluster) p.update_target("test", "module") p.kluctl("bootstrap", "--yes", "--cluster", "module") assert_readiness(module_kind_cluster, "kube-system", "Deployment/sealed-secrets", 60 * 5) -@pytest.mark.dependency(depends=["test_command_bootstrap"]) -def test_command_bootstrap_upgrade(module_kind_cluster): - bootstrap_path = os.path.join(get_kluctl_package_dir(), "bootstrap") + # test upgrades with TemporaryDirectory() as tmpdir: shutil.copytree(bootstrap_path, tmpdir, dirs_exist_ok=True) @@ -46,8 +44,7 @@ def test_command_bootstrap_upgrade(module_kind_cluster): p.kluctl("bootstrap", "--yes", "--cluster", "module") assert_resource_exists(module_kind_cluster, "kube-system", "ConfigMap/dummy-configmap") -@pytest.mark.dependency(depends=["test_command_bootstrap_upgrade"]) -def test_command_bootstrap_prune(module_kind_cluster): + # test pruning with KluctlTestProject("bootstrap") as p: p.update_kind_cluster(module_kind_cluster) p.update_target("test", "module") diff --git a/requirements-dev.txt b/requirements-dev.txt index 0058a51b4..4811bf4df 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,3 +1,2 @@ pytest>=6.2.5 pytest-kind>=21.1.3 -pytest-dependency>=0.5.1 From 938e9c9fb0df062f269f5611acd66aa97b74d6cb Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 22 Sep 2021 18:11:42 +0200 Subject: [PATCH 0195/2916] test: Fix namespaces --- kluctl/e2e/test_external_projects.py | 10 +++++----- kluctl/e2e/test_inclusion.py | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/kluctl/e2e/test_external_projects.py b/kluctl/e2e/test_external_projects.py index f4252b54f..12d38a3c1 100644 --- a/kluctl/e2e/test_external_projects.py +++ b/kluctl/e2e/test_external_projects.py @@ -42,19 +42,19 @@ def do_test_project(kind_cluster, namespace, **kwargs): assert get_dict_value(y, "data.target_var") == "target_value2" def test_external_kluctl_project(module_kind_cluster: KindCluster): - do_test_project(module_kind_cluster, "a", kluctl_project_external=True) + do_test_project(module_kind_cluster, "external-kluctl-project", kluctl_project_external=True) def test_external_clusters_project(module_kind_cluster: KindCluster): - do_test_project(module_kind_cluster, "b", clusters_external=True) + do_test_project(module_kind_cluster, "external-clusters-project", clusters_external=True) def test_external_deployment_project(module_kind_cluster: KindCluster): - do_test_project(module_kind_cluster, "c", deployment_external=True) + do_test_project(module_kind_cluster, "external-deployment-project", deployment_external=True) def test_external_sealed_secrets_project(module_kind_cluster: KindCluster): - do_test_project(module_kind_cluster, "d", sealed_secrets_external=True) + do_test_project(module_kind_cluster, "external-sealed-secrets-project", sealed_secrets_external=True) def test_all_projects_external(module_kind_cluster: KindCluster): - do_test_project(module_kind_cluster, "e", + do_test_project(module_kind_cluster, "external-all-projects", kluctl_project_external=True, clusters_external=True, deployment_external=True, diff --git a/kluctl/e2e/test_inclusion.py b/kluctl/e2e/test_inclusion.py index d6687ebb6..33dff9c3e 100644 --- a/kluctl/e2e/test_inclusion.py +++ b/kluctl/e2e/test_inclusion.py @@ -46,7 +46,7 @@ def assert_exists_helper(kind_cluster, p, namespace, should_exists, add=None, re assert found == should_exists def test_inclusion_tags(module_kind_cluster: KindCluster): - with KluctlTestProject("inclusion") as p: + with KluctlTestProject("inclusion-tags") as p: prepare_project(module_kind_cluster, p, "inclusion", False) should_exists = set() @@ -78,7 +78,7 @@ def do_assert_exists(add=None): do_assert_exists({"cm6", "cm7"}) def test_exclusion_tags(module_kind_cluster: KindCluster): - with KluctlTestProject("exclusion") as p: + with KluctlTestProject("inclusion-exclusion") as p: prepare_project(module_kind_cluster, p, "exclusion", False) should_exists = set() @@ -100,7 +100,7 @@ def do_assert_exists(add=None): do_assert_exists({"cm3", "cm4", "cm5", "cm6"}) def test_inclusion_include_dirs(module_kind_cluster: KindCluster): - with KluctlTestProject("include-dirs") as p: + with KluctlTestProject("inclusion-dirs") as p: prepare_project(module_kind_cluster, p, "include-dirs", True) should_exists = set() @@ -119,7 +119,7 @@ def do_assert_exists(add=None): do_assert_exists({"icm4", "icm5"}) def test_inclusion_kustomize_dirs(module_kind_cluster: KindCluster): - with KluctlTestProject("include-dirs") as p: + with KluctlTestProject("inclusion-kustomize-dirs") as p: prepare_project(module_kind_cluster, p, "include-dirs", True) should_exists = set() From aaea015018ee1502d12a3ea929dc45cc3cc6aebd Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 22 Sep 2021 18:12:34 +0200 Subject: [PATCH 0196/2916] test: Run tests in parallel --- .github/workflows/tests.yml | 2 +- requirements-dev.txt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 521d60957..cb4525305 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -58,7 +58,7 @@ jobs: git config --global user.name "Test User" - name: Run tests run: | - pytest + pytest -n4 check-code: runs-on: ubuntu-latest steps: diff --git a/requirements-dev.txt b/requirements-dev.txt index 4811bf4df..7239cfa99 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,2 +1,3 @@ pytest>=6.2.5 +pytest-xdist>=2.4.0 pytest-kind>=21.1.3 From 91c9ebb3799e3ddb8e3780ec3a036bcedf583365 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 22 Sep 2021 18:16:48 +0200 Subject: [PATCH 0197/2916] test: Use project name for namespaces --- kluctl/e2e/test_inclusion.py | 56 ++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/kluctl/e2e/test_inclusion.py b/kluctl/e2e/test_inclusion.py index 33dff9c3e..5d712bd75 100644 --- a/kluctl/e2e/test_inclusion.py +++ b/kluctl/e2e/test_inclusion.py @@ -7,32 +7,32 @@ from kluctl.utils.yaml_utils import yaml_load -def prepare_project(module_kind_cluster: KindCluster, p: KluctlTestProject, namespace, with_includes): - recreate_namespace(module_kind_cluster, namespace) +def prepare_project(module_kind_cluster: KindCluster, p: KluctlTestProject, with_includes): + recreate_namespace(module_kind_cluster, p.project_name) p.update_kind_cluster(module_kind_cluster) p.update_target("test", "module") - add_configmap_deployment(p, "cm1", "cm1", namespace=namespace) - add_configmap_deployment(p, "cm2", "cm2", namespace=namespace) - add_configmap_deployment(p, "cm3", "cm3", namespace=namespace, tags=["tag1", "tag2"]) - add_configmap_deployment(p, "cm4", "cm4", namespace=namespace, tags=["tag1", "tag3"]) - add_configmap_deployment(p, "cm5", "cm5", namespace=namespace, tags=["tag1", "tag4"]) - add_configmap_deployment(p, "cm6", "cm6", namespace=namespace, tags=["tag1", "tag5"]) - add_configmap_deployment(p, "cm7", "cm7", namespace=namespace, tags=["tag1", "tag6"]) + add_configmap_deployment(p, "cm1", "cm1", namespace=p.project_name) + add_configmap_deployment(p, "cm2", "cm2", namespace=p.project_name) + add_configmap_deployment(p, "cm3", "cm3", namespace=p.project_name, tags=["tag1", "tag2"]) + add_configmap_deployment(p, "cm4", "cm4", namespace=p.project_name, tags=["tag1", "tag3"]) + add_configmap_deployment(p, "cm5", "cm5", namespace=p.project_name, tags=["tag1", "tag4"]) + add_configmap_deployment(p, "cm6", "cm6", namespace=p.project_name, tags=["tag1", "tag5"]) + add_configmap_deployment(p, "cm7", "cm7", namespace=p.project_name, tags=["tag1", "tag6"]) if with_includes: p.add_deployment_include(".", "include1") - add_configmap_deployment(p, "include1/icm1", "icm1", namespace=namespace, tags=["itag1", "itag2"]) + add_configmap_deployment(p, "include1/icm1", "icm1", namespace=p.project_name, tags=["itag1", "itag2"]) p.add_deployment_include(".", "include2") - add_configmap_deployment(p, "include2/icm2", "icm2", namespace=namespace) - add_configmap_deployment(p, "include2/icm3", "icm3", namespace=namespace, tags=["itag3", "itag4"]) + add_configmap_deployment(p, "include2/icm2", "icm2", namespace=p.project_name) + add_configmap_deployment(p, "include2/icm3", "icm3", namespace=p.project_name, tags=["itag3", "itag4"]) p.add_deployment_include(".", "include3", tags=["itag5"]) - add_configmap_deployment(p, "include3/icm4", "icm4", namespace=namespace) - add_configmap_deployment(p, "include3/icm5", "icm5", namespace=namespace, tags=["itag5", "itag6"]) + add_configmap_deployment(p, "include3/icm4", "icm4", namespace=p.project_name) + add_configmap_deployment(p, "include3/icm5", "icm5", namespace=p.project_name, tags=["itag5", "itag6"]) -def assert_exists_helper(kind_cluster, p, namespace, should_exists, add=None, remove=None): +def assert_exists_helper(kind_cluster, p, should_exists, add=None, remove=None): if add is not None: for x in add: should_exists.add(x) @@ -40,18 +40,18 @@ def assert_exists_helper(kind_cluster, p, namespace, should_exists, add=None, re for x in remove: if x in should_exists: should_exists.remove(x) - exists = kind_cluster.kubectl("-n", namespace, "get", "configmaps", "-l", "project_name=%s" % p.project_name, "-o", "yaml") + exists = kind_cluster.kubectl("-n", p.project_name, "get", "configmaps", "-l", "project_name=%s" % p.project_name, "-o", "yaml") exists = yaml_load(exists)["items"] found = set(get_dict_value(x, "metadata.name") for x in exists) assert found == should_exists def test_inclusion_tags(module_kind_cluster: KindCluster): with KluctlTestProject("inclusion-tags") as p: - prepare_project(module_kind_cluster, p, "inclusion", False) + prepare_project(module_kind_cluster, p, False) should_exists = set() def do_assert_exists(add=None): - assert_exists_helper(module_kind_cluster, p, "inclusion", should_exists, add) + assert_exists_helper(module_kind_cluster, p, should_exists, add) do_assert_exists() @@ -79,11 +79,11 @@ def do_assert_exists(add=None): def test_exclusion_tags(module_kind_cluster: KindCluster): with KluctlTestProject("inclusion-exclusion") as p: - prepare_project(module_kind_cluster, p, "exclusion", False) + prepare_project(module_kind_cluster, p, False) should_exists = set() def do_assert_exists(add=None): - assert_exists_helper(module_kind_cluster, p, "exclusion", should_exists, add) + assert_exists_helper(module_kind_cluster, p, should_exists, add) do_assert_exists() @@ -101,11 +101,11 @@ def do_assert_exists(add=None): def test_inclusion_include_dirs(module_kind_cluster: KindCluster): with KluctlTestProject("inclusion-dirs") as p: - prepare_project(module_kind_cluster, p, "include-dirs", True) + prepare_project(module_kind_cluster, p, True) should_exists = set() def do_assert_exists(add=None): - assert_exists_helper(module_kind_cluster, p, "include-dirs", should_exists, add) + assert_exists_helper(module_kind_cluster, p, should_exists, add) do_assert_exists() @@ -120,11 +120,11 @@ def do_assert_exists(add=None): def test_inclusion_kustomize_dirs(module_kind_cluster: KindCluster): with KluctlTestProject("inclusion-kustomize-dirs") as p: - prepare_project(module_kind_cluster, p, "include-dirs", True) + prepare_project(module_kind_cluster, p, True) should_exists = set() def do_assert_exists(add=None): - assert_exists_helper(module_kind_cluster, p, "include-dirs", should_exists, add) + assert_exists_helper(module_kind_cluster, p, should_exists, add) do_assert_exists() @@ -139,11 +139,11 @@ def do_assert_exists(add=None): def test_inclusion_prune(module_kind_cluster: KindCluster): with KluctlTestProject("inclusion-prune") as p: - prepare_project(module_kind_cluster, p, "inclusion-prune", False) + prepare_project(module_kind_cluster, p, False) should_exists = set() def do_assert_exists(add=None, remove=None): - assert_exists_helper(module_kind_cluster, p, "inclusion-prune", should_exists, add, remove) + assert_exists_helper(module_kind_cluster, p, should_exists, add, remove) p.kluctl("deploy", "--yes", "-t", "test") do_assert_exists(p.list_kustomize_deployment_pathes()) @@ -168,11 +168,11 @@ def do_assert_exists(add=None, remove=None): def test_inclusion_delete(module_kind_cluster: KindCluster): with KluctlTestProject("inclusion-delete") as p: - prepare_project(module_kind_cluster, p, "inclusion-delete", False) + prepare_project(module_kind_cluster, p, False) should_exists = set() def do_assert_exists(add=None, remove=None): - assert_exists_helper(module_kind_cluster, p, "inclusion-delete", should_exists, add, remove) + assert_exists_helper(module_kind_cluster, p, should_exists, add, remove) p.kluctl("deploy", "--yes", "-t", "test") do_assert_exists(p.list_kustomize_deployment_pathes()) From 2c0e759c21e48efadc5c27a315b9573bcf276455 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 23 Sep 2021 09:48:08 +0200 Subject: [PATCH 0198/2916] test: Switch to default session based kind cluster --- .test_durations | 1 + kluctl/e2e/conftest.py | 29 ------ kluctl/e2e/test_command_bootstrap.py | 26 ++--- kluctl/e2e/test_command_deploy.py | 14 +-- kluctl/e2e/test_external_projects.py | 20 ++-- kluctl/e2e/test_hooks.py | 142 +++++++++++++-------------- kluctl/e2e/test_inclusion.py | 44 ++++----- 7 files changed, 124 insertions(+), 152 deletions(-) create mode 100644 .test_durations diff --git a/.test_durations b/.test_durations new file mode 100644 index 000000000..1405fb094 --- /dev/null +++ b/.test_durations @@ -0,0 +1 @@ +{"kluctl/deployment/images_test.py::TestImages::test_container_missing": 0.17141590600000012, "kluctl/deployment/images_test.py::TestImages::test_deployed": 0.0013724499999998585, "kluctl/deployment/images_test.py::TestImages::test_gitlab_latest_number": 0.0006029400000000518, "kluctl/deployment/images_test.py::TestImages::test_gitlab_latest_prefix": 0.00071098600000008, "kluctl/deployment/images_test.py::TestImages::test_gitlab_latest_semver_simple": 0.00045844800000027774, "kluctl/deployment/images_test.py::TestImages::test_gitlab_latest_semver_suffix": 0.0005591350000000439, "kluctl/deployment/images_test.py::TestImages::test_not_deployed": 0.0014066830000001307, "kluctl/e2e/test_command_bootstrap.py::test_command_bootstrap": 7.980425351000001, "kluctl/e2e/test_command_deploy.py::test_command_deploy_simple": 7.499922836, "kluctl/e2e/test_external_projects.py::test_external_kluctl_project": 8.514446664999998, "kluctl/e2e/test_external_projects.py::test_external_clusters_project": 9.044969405, "kluctl/e2e/test_external_projects.py::test_external_deployment_project": 9.043852996000005, "kluctl/e2e/test_external_projects.py::test_external_sealed_secrets_project": 8.825914165999997, "kluctl/e2e/test_external_projects.py::test_all_projects_external": 9.835674475000005, "kluctl/e2e/test_hooks.py::test_hooks_pre_deploy_initial": 33.86717190399999, "kluctl/e2e/test_hooks.py::test_hooks_post_deploy_initial": 7.135027168999997, "kluctl/e2e/test_hooks.py::test_hooks_pre_deploy_upgrade": 11.931310410000009, "kluctl/e2e/test_hooks.py::test_hooks_post_deploy_upgrade": 6.980959783999978, "kluctl/e2e/test_hooks.py::test_hooks_pre_deploy": 13.948702370999996, "kluctl/e2e/test_hooks.py::test_hooks_pre_deploy2": 14.076728710000026, "kluctl/e2e/test_hooks.py::test_hooks_post_deploy": 10.02011994999998, "kluctl/e2e/test_hooks.py::test_hooks_post_deploy2": 10.028683436999984, "kluctl/e2e/test_hooks.py::test_hooks_pre_post_deploy": 15.202726206999984, "kluctl/e2e/test_hooks.py::test_hooks_pre_post_deploy2": 15.088598263000023, "kluctl/e2e/test_inclusion.py::test_inclusion_tags": 13.18928894100003, "kluctl/e2e/test_inclusion.py::test_exclusion_tags": 7.076015705000003, "kluctl/e2e/test_inclusion.py::test_inclusion_include_dirs": 8.950236328000017, "kluctl/e2e/test_inclusion.py::test_inclusion_kustomize_dirs": 8.771479913999997, "kluctl/e2e/test_inclusion.py::test_inclusion_prune": 11.930896682999986, "kluctl/e2e/test_inclusion.py::test_inclusion_delete": 8.656658246999996, "kluctl/tests/includes/test_includes.py::TestIncludes::test_collection_tags": 0.2307598899999448, "kluctl/tests/includes/test_includes.py::TestIncludes::test_collection_tags_sub_include": 0.004422947999955795, "kluctl/tests/includes/test_includes.py::TestIncludes::test_common_label_merge": 0.004345824000040466, "kluctl/tests/includes/test_includes.py::TestIncludes::test_common_label_merge_sub_include": 0.0045816909999985, "kluctl/tests/includes/test_includes.py::TestIncludes::test_delete_by_label_merge": 0.0043263449999244585, "kluctl/tests/includes/test_includes.py::TestIncludes::test_delete_by_label_merge_sub_include": 0.004260996000027717, "kluctl/tests/includes/test_includes.py::TestIncludes::test_deployment_pathes": 0.004694427000003998, "kluctl/tests/includes/test_includes.py::TestIncludes::test_get_kustomize_dir_tags": 0.004454030999966108, "kluctl/tests/includes/test_includes.py::TestIncludes::test_override_namespace": 0.004301176000012674, "kluctl/tests/includes/test_includes.py::TestIncludes::test_override_namespace_sub_include": 0.004606921999993574, "kluctl/tests/includes/test_includes.py::TestIncludes::test_simple": 0.004748638999956256, "kluctl/tests/misc/test_misc.py::TestTemplating::test_stable_tags_ordering": 0.0021009400000480127, "kluctl/tests/templating/test_templating.py::TestTemplating::test_deployment_yml": 0.003359053000053791, "kluctl/tests/templating/test_templating.py::TestTemplating::test_get_var": 0.07002809499999785, "kluctl/tests/templating/test_templating.py::TestTemplating::test_import_no_context": 0.0050945950000595985, "kluctl/tests/templating/test_templating.py::TestTemplating::test_include_var": 0.0029756959999645005, "kluctl/tests/templating/test_templating.py::TestTemplating::test_load_template": 0.008140107000031094, "kluctl/tests/templating/test_templating.py::TestTemplating::test_not_rendered_kustomize_resource": 0.007553739000002224, "kluctl/tests/templating/test_templating.py::TestTemplating::test_rendered_kustomization_yml": 0.007568721999973604, "kluctl/tests/templating/test_templating.py::TestTemplating::test_rendered_kustomize_resource": 0.008014451999940775, "kluctl/tests/templating/test_templating.py::TestTemplating::test_vars": 0.005280256999924404, "kluctl/utils/dict_utils_test.py::test_get_dict_value": 0.11056591200002686, "kluctl/utils/dict_utils_test.py::test_get_dict_value_arrays": 0.0869486170000755, "kluctl/utils/dict_utils_test.py::test_set_dict_value": 0.0005526429999918037, "kluctl/utils/dict_utils_test.py::test_set_dict_value_add": 0.06687758900000063, "kluctl/utils/dict_utils_test.py::test_del_dict_value": 0.022795438999935413, "kluctl/utils/dict_utils_test.py::test_del_dict_value_wildcard": 0.044004519999987224, "kluctl/utils/dict_utils_test.py::test_del_dict_value_wildcard_extended": 0.022446880000018155, "kluctl/utils/versions_test.py::TestLooseVersionComparator::test_equality": 0.0009527990000037789, "kluctl/utils/versions_test.py::TestLooseVersionComparator::test_less": 0.000906323999970482, "kluctl/utils/versions_test.py::TestLooseVersionComparator::test_loose_version_no_nums": 0.0007862360000103763, "kluctl/utils/versions_test.py::TestLooseVersionComparator::test_maven_versions": 0.0006167200000390949, "kluctl/utils/versions_test.py::TestLooseVersionComparator::test_suffixes": 0.0018046489999505866, "kluctl/utils/versions_test.py::TestVersionFilters::test_loose_semver": 0.0006110550000357762, "kluctl/utils/versions_test.py::TestVersionFilters::test_number": 0.0005053250000059961, "kluctl/utils/versions_test.py::TestVersionFilters::test_prefix": 1.5378612869999984} \ No newline at end of file diff --git a/kluctl/e2e/conftest.py b/kluctl/e2e/conftest.py index c1b625b22..88ab260fd 100644 --- a/kluctl/e2e/conftest.py +++ b/kluctl/e2e/conftest.py @@ -1,9 +1,7 @@ -import contextlib import logging import subprocess import time -import pytest from pytest_kind import KindCluster from kluctl.utils.k8s_status_validation import validate_object @@ -11,33 +9,6 @@ logger = logging.getLogger(__name__) -delete_clusters = True - -@contextlib.contextmanager -def kind_cluster_fixture(name): - cluster = KindCluster(name) - try: - if delete_clusters: - cluster.delete() - except: - pass - cluster.create() - try: - yield cluster - finally: - if delete_clusters: - cluster.delete() - -@pytest.fixture(scope="function") -def function_kind_cluster(request): - with kind_cluster_fixture("function") as c: - yield c - -@pytest.fixture(scope="module") -def module_kind_cluster(request): - with kind_cluster_fixture("module") as c: - yield c - def recreate_namespace(kind_cluster: KindCluster, namespace): try: kind_cluster.kubectl("delete", "ns", namespace) diff --git a/kluctl/e2e/test_command_bootstrap.py b/kluctl/e2e/test_command_bootstrap.py index 9b9bd9c63..70259d1fd 100644 --- a/kluctl/e2e/test_command_bootstrap.py +++ b/kluctl/e2e/test_command_bootstrap.py @@ -19,14 +19,14 @@ dummy: value """ -def test_command_bootstrap(module_kind_cluster: KindCluster): +def test_command_bootstrap(kind_cluster: KindCluster): bootstrap_path = os.path.join(get_kluctl_package_dir(), "bootstrap") with KluctlTestProject("bootstrap") as p: - p.update_kind_cluster(module_kind_cluster) - p.update_target("test", "module") - p.kluctl("bootstrap", "--yes", "--cluster", "module") - assert_readiness(module_kind_cluster, "kube-system", "Deployment/sealed-secrets", 60 * 5) + p.update_kind_cluster(kind_cluster) + p.update_target("test", kind_cluster.name) + p.kluctl("bootstrap", "--yes", "--cluster", "pytest-kind") + assert_readiness(kind_cluster, "kube-system", "Deployment/sealed-secrets", 60 * 5) # test upgrades with TemporaryDirectory() as tmpdir: @@ -39,14 +39,14 @@ def test_command_bootstrap(module_kind_cluster: KindCluster): yaml_save_file(k, os.path.join(tmpdir, "sealed-secrets/kustomization.yml")) with KluctlTestProject("bootstrap", local_deployment=tmpdir) as p: - p.update_kind_cluster(module_kind_cluster) - p.update_target("test", "module") - p.kluctl("bootstrap", "--yes", "--cluster", "module") - assert_resource_exists(module_kind_cluster, "kube-system", "ConfigMap/dummy-configmap") + p.update_kind_cluster(kind_cluster) + p.update_target("test", kind_cluster.name) + p.kluctl("bootstrap", "--yes", "--cluster", "pytest-kind") + assert_resource_exists(kind_cluster, "kube-system", "ConfigMap/dummy-configmap") # test pruning with KluctlTestProject("bootstrap") as p: - p.update_kind_cluster(module_kind_cluster) - p.update_target("test", "module") - p.kluctl("bootstrap", "--yes", "--cluster", "module") - assert_resource_not_exists(module_kind_cluster, "kube-system", "ConfigMap/dummy-configmap") + p.update_kind_cluster(kind_cluster) + p.update_target("test", kind_cluster.name) + p.kluctl("bootstrap", "--yes", "--cluster", "pytest-kind") + assert_resource_not_exists(kind_cluster, "kube-system", "ConfigMap/dummy-configmap") diff --git a/kluctl/e2e/test_command_deploy.py b/kluctl/e2e/test_command_deploy.py index a0db271ee..dd19738a6 100644 --- a/kluctl/e2e/test_command_deploy.py +++ b/kluctl/e2e/test_command_deploy.py @@ -5,19 +5,19 @@ from kluctl.e2e.kluctl_test_project_helpers import add_configmap_deployment -def test_command_deploy_simple(module_kind_cluster: KindCluster): +def test_command_deploy_simple(kind_cluster: KindCluster): with KluctlTestProject("simple") as p: - recreate_namespace(module_kind_cluster, "simple") + recreate_namespace(kind_cluster, "simple") - p.update_kind_cluster(module_kind_cluster) - p.update_target("test", "module") + p.update_kind_cluster(kind_cluster) + p.update_target("test", kind_cluster.name) add_configmap_deployment(p, "cm", "cm", namespace="simple") p.kluctl("deploy", "--yes", "-t", "test") - assert_resource_exists(module_kind_cluster, "simple", "ConfigMap/cm") + assert_resource_exists(kind_cluster, "simple", "ConfigMap/cm") add_configmap_deployment(p, "cm2", "cm2", namespace="simple") p.kluctl("deploy", "--yes", "-t", "test", "--dry-run") - assert_resource_not_exists(module_kind_cluster, "simple", "ConfigMap/cm2") + assert_resource_not_exists(kind_cluster, "simple", "ConfigMap/cm2") p.kluctl("deploy", "--yes", "-t", "test") - assert_resource_exists(module_kind_cluster, "simple", "ConfigMap/cm2") + assert_resource_exists(kind_cluster, "simple", "ConfigMap/cm2") diff --git a/kluctl/e2e/test_external_projects.py b/kluctl/e2e/test_external_projects.py index 12d38a3c1..423a73883 100644 --- a/kluctl/e2e/test_external_projects.py +++ b/kluctl/e2e/test_external_projects.py @@ -41,20 +41,20 @@ def do_test_project(kind_cluster, namespace, **kwargs): assert get_dict_value(y, "data.cluster_var") == "cluster_value2" assert get_dict_value(y, "data.target_var") == "target_value2" -def test_external_kluctl_project(module_kind_cluster: KindCluster): - do_test_project(module_kind_cluster, "external-kluctl-project", kluctl_project_external=True) +def test_external_kluctl_project(kind_cluster: KindCluster): + do_test_project(kind_cluster, "external-kluctl-project", kluctl_project_external=True) -def test_external_clusters_project(module_kind_cluster: KindCluster): - do_test_project(module_kind_cluster, "external-clusters-project", clusters_external=True) +def test_external_clusters_project(kind_cluster: KindCluster): + do_test_project(kind_cluster, "external-clusters-project", clusters_external=True) -def test_external_deployment_project(module_kind_cluster: KindCluster): - do_test_project(module_kind_cluster, "external-deployment-project", deployment_external=True) +def test_external_deployment_project(kind_cluster: KindCluster): + do_test_project(kind_cluster, "external-deployment-project", deployment_external=True) -def test_external_sealed_secrets_project(module_kind_cluster: KindCluster): - do_test_project(module_kind_cluster, "external-sealed-secrets-project", sealed_secrets_external=True) +def test_external_sealed_secrets_project(kind_cluster: KindCluster): + do_test_project(kind_cluster, "external-sealed-secrets-project", sealed_secrets_external=True) -def test_all_projects_external(module_kind_cluster: KindCluster): - do_test_project(module_kind_cluster, "external-all-projects", +def test_all_projects_external(kind_cluster: KindCluster): + do_test_project(kind_cluster, "external-all-projects", kluctl_project_external=True, clusters_external=True, deployment_external=True, diff --git a/kluctl/e2e/test_hooks.py b/kluctl/e2e/test_hooks.py index 19aa1a0e4..ee60089b8 100644 --- a/kluctl/e2e/test_hooks.py +++ b/kluctl/e2e/test_hooks.py @@ -53,16 +53,16 @@ def add_configmap(p: KluctlTestProject, dir, name, namespace): "%s.yml" % name: yaml_dump(y) }) -def get_hook_result(module_kind_cluster: KindCluster, p: KluctlTestProject, secret_name): - y = module_kind_cluster.kubectl("-n", p.project_name, "get", "secret", secret_name, "-o", "yaml") +def get_hook_result(kind_cluster: KindCluster, p: KluctlTestProject, secret_name): + y = kind_cluster.kubectl("-n", p.project_name, "get", "secret", secret_name, "-o", "yaml") y = yaml_load(y) y = get_dict_value(y, "data.result") y = base64.b64decode(y).decode("utf-8") y = yaml_load(y) return y -def get_hook_result_cm_names(module_kind_cluster: KindCluster, p: KluctlTestProject, second=False): - y = get_hook_result(module_kind_cluster, p, "hook-result" if not second else "hook-result2") +def get_hook_result_cm_names(kind_cluster: KindCluster, p: KluctlTestProject, second=False): + y = get_hook_result(kind_cluster, p, "hook-result" if not second else "hook-result2") return [get_dict_value(x, "metadata.name") for x in y["items"]] @contextlib.contextmanager @@ -72,7 +72,7 @@ def prepare_project(kind_cluster: KindCluster, name, hook, hook_deletion_policy) recreate_namespace(kind_cluster, namespace) p.update_kind_cluster(kind_cluster) - p.update_target("test", "module") + p.update_target("test", kind_cluster.name) add_hook_deployment(p, "hook", namespace, is_helm=False, hook=hook, hook_deletion_policy=hook_deletion_policy) add_configmap(p, "hook", "cm1", namespace) @@ -97,86 +97,86 @@ def ensure_hook_not_executed(kind_cluster, p: KluctlTestProject): p.kluctl("deploy", "--yes", "-t", "test") assert_resource_not_exists(kind_cluster, p.project_name, "Secret/hook-result") -def test_hooks_pre_deploy_initial(module_kind_cluster: KindCluster): - with prepare_project(module_kind_cluster, "pre-deploy-initial", "pre-deploy-initial", None) as p: - ensure_hook_executed(module_kind_cluster, p) - assert "cm1" not in get_hook_result_cm_names(module_kind_cluster, p) - ensure_hook_not_executed(module_kind_cluster, p) +def test_hooks_pre_deploy_initial(kind_cluster: KindCluster): + with prepare_project(kind_cluster, "pre-deploy-initial", "pre-deploy-initial", None) as p: + ensure_hook_executed(kind_cluster, p) + assert "cm1" not in get_hook_result_cm_names(kind_cluster, p) + ensure_hook_not_executed(kind_cluster, p) -def test_hooks_post_deploy_initial(module_kind_cluster: KindCluster): - with prepare_project(module_kind_cluster, "post-deploy-initial", "post-deploy-initial", None) as p: - ensure_hook_executed(module_kind_cluster, p) - assert "cm1" in get_hook_result_cm_names(module_kind_cluster, p) - ensure_hook_not_executed(module_kind_cluster, p) +def test_hooks_post_deploy_initial(kind_cluster: KindCluster): + with prepare_project(kind_cluster, "post-deploy-initial", "post-deploy-initial", None) as p: + ensure_hook_executed(kind_cluster, p) + assert "cm1" in get_hook_result_cm_names(kind_cluster, p) + ensure_hook_not_executed(kind_cluster, p) -def test_hooks_pre_deploy_upgrade(module_kind_cluster: KindCluster): - with prepare_project(module_kind_cluster, "pre-deploy-upgrade", "pre-deploy-upgrade", None) as p: +def test_hooks_pre_deploy_upgrade(kind_cluster: KindCluster): + with prepare_project(kind_cluster, "pre-deploy-upgrade", "pre-deploy-upgrade", None) as p: add_configmap(p, "hook", "cm2", p.project_name) - ensure_hook_not_executed(module_kind_cluster, p) - module_kind_cluster.kubectl("delete", "-n", p.project_name, "configmap", "cm1") - ensure_hook_executed(module_kind_cluster, p) - assert "cm1" not in get_hook_result_cm_names(module_kind_cluster, p) - ensure_hook_executed(module_kind_cluster, p) - assert "cm1" in get_hook_result_cm_names(module_kind_cluster, p) + ensure_hook_not_executed(kind_cluster, p) + kind_cluster.kubectl("delete", "-n", p.project_name, "configmap", "cm1") + ensure_hook_executed(kind_cluster, p) + assert "cm1" not in get_hook_result_cm_names(kind_cluster, p) + ensure_hook_executed(kind_cluster, p) + assert "cm1" in get_hook_result_cm_names(kind_cluster, p) -def test_hooks_post_deploy_upgrade(module_kind_cluster: KindCluster): - with prepare_project(module_kind_cluster, "post-deploy-upgrade", "post-deploy-upgrade", None) as p: +def test_hooks_post_deploy_upgrade(kind_cluster: KindCluster): + with prepare_project(kind_cluster, "post-deploy-upgrade", "post-deploy-upgrade", None) as p: add_configmap(p, "hook", "cm2", p.project_name) - ensure_hook_not_executed(module_kind_cluster, p) - module_kind_cluster.kubectl("delete", "-n", p.project_name, "configmap", "cm1") - ensure_hook_executed(module_kind_cluster, p) - assert "cm1" in get_hook_result_cm_names(module_kind_cluster, p) + ensure_hook_not_executed(kind_cluster, p) + kind_cluster.kubectl("delete", "-n", p.project_name, "configmap", "cm1") + ensure_hook_executed(kind_cluster, p) + assert "cm1" in get_hook_result_cm_names(kind_cluster, p) -def do_test_hooks_pre_deploy(module_kind_cluster: KindCluster, name, hooks): - with prepare_project(module_kind_cluster, name, hooks, None) as p: +def do_test_hooks_pre_deploy(kind_cluster: KindCluster, name, hooks): + with prepare_project(kind_cluster, name, hooks, None) as p: add_configmap(p, "hook", "cm2", p.project_name) - ensure_hook_executed(module_kind_cluster, p) - module_kind_cluster.kubectl("delete", "-n", p.project_name, "configmap", "cm1") - ensure_hook_executed(module_kind_cluster, p) - assert "cm1" not in get_hook_result_cm_names(module_kind_cluster, p) - ensure_hook_executed(module_kind_cluster, p) - assert "cm1" in get_hook_result_cm_names(module_kind_cluster, p) - -def do_test_hooks_post_deploy(module_kind_cluster: KindCluster, name, hooks): - with prepare_project(module_kind_cluster, name, hooks, None) as p: + ensure_hook_executed(kind_cluster, p) + kind_cluster.kubectl("delete", "-n", p.project_name, "configmap", "cm1") + ensure_hook_executed(kind_cluster, p) + assert "cm1" not in get_hook_result_cm_names(kind_cluster, p) + ensure_hook_executed(kind_cluster, p) + assert "cm1" in get_hook_result_cm_names(kind_cluster, p) + +def do_test_hooks_post_deploy(kind_cluster: KindCluster, name, hooks): + with prepare_project(kind_cluster, name, hooks, None) as p: add_configmap(p, "hook", "cm2", p.project_name) - ensure_hook_executed(module_kind_cluster, p) - module_kind_cluster.kubectl("delete", "-n", p.project_name, "configmap", "cm1") - ensure_hook_executed(module_kind_cluster, p) - assert "cm1" in get_hook_result_cm_names(module_kind_cluster, p) + ensure_hook_executed(kind_cluster, p) + kind_cluster.kubectl("delete", "-n", p.project_name, "configmap", "cm1") + ensure_hook_executed(kind_cluster, p) + assert "cm1" in get_hook_result_cm_names(kind_cluster, p) -def do_test_hooks_pre_post_deploy(module_kind_cluster: KindCluster, name, hooks): - with prepare_project(module_kind_cluster, name, hooks, None) as p: +def do_test_hooks_pre_post_deploy(kind_cluster: KindCluster, name, hooks): + with prepare_project(kind_cluster, name, hooks, None) as p: add_configmap(p, "hook", "cm2", p.project_name) - ensure_hook_executed(module_kind_cluster, p) - assert "cm1" not in get_hook_result_cm_names(module_kind_cluster, p) - assert "cm2" not in get_hook_result_cm_names(module_kind_cluster, p) - assert "cm1" in get_hook_result_cm_names(module_kind_cluster, p, True) - assert "cm2" in get_hook_result_cm_names(module_kind_cluster, p, True) - - ensure_hook_executed(module_kind_cluster, p) - assert "cm1" in get_hook_result_cm_names(module_kind_cluster, p) - assert "cm2" not in get_hook_result_cm_names(module_kind_cluster, p) - assert "cm1" in get_hook_result_cm_names(module_kind_cluster, p, True) - assert "cm2" in get_hook_result_cm_names(module_kind_cluster, p, True) - -def test_hooks_pre_deploy(module_kind_cluster: KindCluster): - do_test_hooks_pre_deploy(module_kind_cluster, "pre-deploy", "pre-deploy") - -def test_hooks_pre_deploy2(module_kind_cluster: KindCluster): + ensure_hook_executed(kind_cluster, p) + assert "cm1" not in get_hook_result_cm_names(kind_cluster, p) + assert "cm2" not in get_hook_result_cm_names(kind_cluster, p) + assert "cm1" in get_hook_result_cm_names(kind_cluster, p, True) + assert "cm2" in get_hook_result_cm_names(kind_cluster, p, True) + + ensure_hook_executed(kind_cluster, p) + assert "cm1" in get_hook_result_cm_names(kind_cluster, p) + assert "cm2" not in get_hook_result_cm_names(kind_cluster, p) + assert "cm1" in get_hook_result_cm_names(kind_cluster, p, True) + assert "cm2" in get_hook_result_cm_names(kind_cluster, p, True) + +def test_hooks_pre_deploy(kind_cluster: KindCluster): + do_test_hooks_pre_deploy(kind_cluster, "pre-deploy", "pre-deploy") + +def test_hooks_pre_deploy2(kind_cluster: KindCluster): # same as pre-deploy - do_test_hooks_pre_deploy(module_kind_cluster, "pre-deploy2", "pre-deploy-initial,pre-deploy-upgrade") + do_test_hooks_pre_deploy(kind_cluster, "pre-deploy2", "pre-deploy-initial,pre-deploy-upgrade") -def test_hooks_post_deploy(module_kind_cluster: KindCluster): - do_test_hooks_post_deploy(module_kind_cluster, "post-deploy", "post-deploy") +def test_hooks_post_deploy(kind_cluster: KindCluster): + do_test_hooks_post_deploy(kind_cluster, "post-deploy", "post-deploy") -def test_hooks_post_deploy2(module_kind_cluster: KindCluster): +def test_hooks_post_deploy2(kind_cluster: KindCluster): # same as post-deploy - do_test_hooks_post_deploy(module_kind_cluster, "post-deploy2", "post-deploy-initial,post-deploy-upgrade") + do_test_hooks_post_deploy(kind_cluster, "post-deploy2", "post-deploy-initial,post-deploy-upgrade") -def test_hooks_pre_post_deploy(module_kind_cluster: KindCluster): - do_test_hooks_pre_post_deploy(module_kind_cluster, "pre-post-deploy", "pre-deploy,post-deploy") +def test_hooks_pre_post_deploy(kind_cluster: KindCluster): + do_test_hooks_pre_post_deploy(kind_cluster, "pre-post-deploy", "pre-deploy,post-deploy") -def test_hooks_pre_post_deploy2(module_kind_cluster: KindCluster): - do_test_hooks_pre_post_deploy(module_kind_cluster, "pre-post-deploy2", "pre-deploy-initial,pre-deploy-upgrade,post-deploy-initial,post-deploy-upgrade") +def test_hooks_pre_post_deploy2(kind_cluster: KindCluster): + do_test_hooks_pre_post_deploy(kind_cluster, "pre-post-deploy2", "pre-deploy-initial,pre-deploy-upgrade,post-deploy-initial,post-deploy-upgrade") diff --git a/kluctl/e2e/test_inclusion.py b/kluctl/e2e/test_inclusion.py index 5d712bd75..a8b405bcf 100644 --- a/kluctl/e2e/test_inclusion.py +++ b/kluctl/e2e/test_inclusion.py @@ -7,11 +7,11 @@ from kluctl.utils.yaml_utils import yaml_load -def prepare_project(module_kind_cluster: KindCluster, p: KluctlTestProject, with_includes): - recreate_namespace(module_kind_cluster, p.project_name) +def prepare_project(kind_cluster: KindCluster, p: KluctlTestProject, with_includes): + recreate_namespace(kind_cluster, p.project_name) - p.update_kind_cluster(module_kind_cluster) - p.update_target("test", "module") + p.update_kind_cluster(kind_cluster) + p.update_target("test", kind_cluster.name) add_configmap_deployment(p, "cm1", "cm1", namespace=p.project_name) add_configmap_deployment(p, "cm2", "cm2", namespace=p.project_name) add_configmap_deployment(p, "cm3", "cm3", namespace=p.project_name, tags=["tag1", "tag2"]) @@ -45,13 +45,13 @@ def assert_exists_helper(kind_cluster, p, should_exists, add=None, remove=None): found = set(get_dict_value(x, "metadata.name") for x in exists) assert found == should_exists -def test_inclusion_tags(module_kind_cluster: KindCluster): +def test_inclusion_tags(kind_cluster: KindCluster): with KluctlTestProject("inclusion-tags") as p: - prepare_project(module_kind_cluster, p, False) + prepare_project(kind_cluster, p, False) should_exists = set() def do_assert_exists(add=None): - assert_exists_helper(module_kind_cluster, p, should_exists, add) + assert_exists_helper(kind_cluster, p, should_exists, add) do_assert_exists() @@ -77,13 +77,13 @@ def do_assert_exists(add=None): p.kluctl("deploy", "--yes", "-t", "test", "-I", "tag1") do_assert_exists({"cm6", "cm7"}) -def test_exclusion_tags(module_kind_cluster: KindCluster): +def test_exclusion_tags(kind_cluster: KindCluster): with KluctlTestProject("inclusion-exclusion") as p: - prepare_project(module_kind_cluster, p, False) + prepare_project(kind_cluster, p, False) should_exists = set() def do_assert_exists(add=None): - assert_exists_helper(module_kind_cluster, p, should_exists, add) + assert_exists_helper(kind_cluster, p, should_exists, add) do_assert_exists() @@ -99,13 +99,13 @@ def do_assert_exists(add=None): p.kluctl("deploy", "--yes", "-t", "test", "-I", "tag1", "-E", "tag6") do_assert_exists({"cm3", "cm4", "cm5", "cm6"}) -def test_inclusion_include_dirs(module_kind_cluster: KindCluster): +def test_inclusion_include_dirs(kind_cluster: KindCluster): with KluctlTestProject("inclusion-dirs") as p: - prepare_project(module_kind_cluster, p, True) + prepare_project(kind_cluster, p, True) should_exists = set() def do_assert_exists(add=None): - assert_exists_helper(module_kind_cluster, p, should_exists, add) + assert_exists_helper(kind_cluster, p, should_exists, add) do_assert_exists() @@ -118,13 +118,13 @@ def do_assert_exists(add=None): p.kluctl("deploy", "--yes", "-t", "test", "-I", "itag5") do_assert_exists({"icm4", "icm5"}) -def test_inclusion_kustomize_dirs(module_kind_cluster: KindCluster): +def test_inclusion_kustomize_dirs(kind_cluster: KindCluster): with KluctlTestProject("inclusion-kustomize-dirs") as p: - prepare_project(module_kind_cluster, p, True) + prepare_project(kind_cluster, p, True) should_exists = set() def do_assert_exists(add=None): - assert_exists_helper(module_kind_cluster, p, should_exists, add) + assert_exists_helper(kind_cluster, p, should_exists, add) do_assert_exists() @@ -137,13 +137,13 @@ def do_assert_exists(add=None): p.kluctl("deploy", "--yes", "-t", "test", "--exclude-kustomize-dir", "include3/icm5") do_assert_exists(set(p.list_kustomize_deployment_pathes()) - {"icm5"}) -def test_inclusion_prune(module_kind_cluster: KindCluster): +def test_inclusion_prune(kind_cluster: KindCluster): with KluctlTestProject("inclusion-prune") as p: - prepare_project(module_kind_cluster, p, False) + prepare_project(kind_cluster, p, False) should_exists = set() def do_assert_exists(add=None, remove=None): - assert_exists_helper(module_kind_cluster, p, should_exists, add, remove) + assert_exists_helper(kind_cluster, p, should_exists, add, remove) p.kluctl("deploy", "--yes", "-t", "test") do_assert_exists(p.list_kustomize_deployment_pathes()) @@ -166,13 +166,13 @@ def do_assert_exists(add=None, remove=None): p.kluctl("prune", "--yes", "-t", "test") do_assert_exists(remove={"cm3"}) -def test_inclusion_delete(module_kind_cluster: KindCluster): +def test_inclusion_delete(kind_cluster: KindCluster): with KluctlTestProject("inclusion-delete") as p: - prepare_project(module_kind_cluster, p, False) + prepare_project(kind_cluster, p, False) should_exists = set() def do_assert_exists(add=None, remove=None): - assert_exists_helper(module_kind_cluster, p, should_exists, add, remove) + assert_exists_helper(kind_cluster, p, should_exists, add, remove) p.kluctl("deploy", "--yes", "-t", "test") do_assert_exists(p.list_kustomize_deployment_pathes()) From ec16f790f1d93941ea4dd88ccfe999fb25bdcf2c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 23 Sep 2021 10:00:35 +0200 Subject: [PATCH 0199/2916] ci: Run tests in parallel by using pytest-split --- .github/workflows/tests.yml | 8 +++++++- requirements-dev.txt | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index cb4525305..0fd4d5f25 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -11,6 +11,9 @@ on: jobs: tests: runs-on: ubuntu-latest + strategy: + matrix: + pytest-group: [ 1, 2, 3, 4 ] steps: - name: Install packages run: | @@ -57,8 +60,11 @@ jobs: git config --global user.email "no@mail.com" git config --global user.name "Test User" - name: Run tests + env: + PYTEST_SPLITS: 3 + PYTEST_GROUP: ${{ matrix.pytest-group }} run: | - pytest -n4 + pytest --splits $PYTEST_SPLITS --group $PYTEST_GROUP check-code: runs-on: ubuntu-latest steps: diff --git a/requirements-dev.txt b/requirements-dev.txt index 7239cfa99..96dd675ff 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,3 +1,3 @@ pytest>=6.2.5 -pytest-xdist>=2.4.0 +pytest-split>=0.3.3 pytest-kind>=21.1.3 From 007f1feecaf2cb1376a012e60d32a93b64f99dff Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 23 Sep 2021 10:02:47 +0200 Subject: [PATCH 0200/2916] ci: Fix split group count --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0fd4d5f25..d5fac5c8e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -61,7 +61,7 @@ jobs: git config --global user.name "Test User" - name: Run tests env: - PYTEST_SPLITS: 3 + PYTEST_SPLITS: 4 PYTEST_GROUP: ${{ matrix.pytest-group }} run: | pytest --splits $PYTEST_SPLITS --group $PYTEST_GROUP From df7e2413158dd6889f61bcf8190ecbd8ad8716a3 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 23 Sep 2021 15:40:37 +0200 Subject: [PATCH 0201/2916] ci: Allow debugging of jobs --- .github/workflows/tests.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d5fac5c8e..5b1749087 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,6 +7,12 @@ on: pull_request: branches: - 'main' + workflow_dispatch: + inputs: + debug_enabled: + description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)' + required: false + default: false jobs: tests: @@ -45,6 +51,10 @@ jobs: wget -O kubeseal https://github.com/bitnami-labs/sealed-secrets/releases/download/$KUBESEAL_VERSION/kubeseal-linux-amd64 && \ chmod +x kubeseal && \ sudo mv kubeseal /usr/bin + # Enable tmate debugging of manually-triggered workflows if the input option was provided + - name: Setup tmate session + uses: mxschmitt/action-tmate@v3 + if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }} - name: Cache dot-kube id: cache-dot-kube uses: actions/cache@v2 From 0e4fcdf8622dd1fa7f2b4faa6a0d14efdc32a0a9 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 23 Sep 2021 15:42:02 +0200 Subject: [PATCH 0202/2916] ci: Only allow actors to connect to tmate --- .github/workflows/tests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5b1749087..d4c5601f8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -55,6 +55,8 @@ jobs: - name: Setup tmate session uses: mxschmitt/action-tmate@v3 if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }} + with: + limit-access-to-actor: true - name: Cache dot-kube id: cache-dot-kube uses: actions/cache@v2 From cf32b924c375bf26260ffea06817040b097dd230 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 23 Sep 2021 16:09:45 +0200 Subject: [PATCH 0203/2916] test: Use newer kind/kubectl versions --- kluctl/e2e/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/kluctl/e2e/__init__.py b/kluctl/e2e/__init__.py index e69de29bb..626b30f75 100644 --- a/kluctl/e2e/__init__.py +++ b/kluctl/e2e/__init__.py @@ -0,0 +1,4 @@ +import pytest_kind + +pytest_kind.cluster.KIND_VERSION = "v0.11.1" +pytest_kind.cluster.KUBECTL_VERSION = "v1.21.5" From 97deb6d3dfec3539734661c7a17740647f509353 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 23 Sep 2021 16:54:36 +0200 Subject: [PATCH 0204/2916] feat: Remove some legacy/migration code --- kluctl/deployment/deployment_collection.py | 37 --------------------- kluctl/diff/managed_fields.py | 6 ++-- kluctl/image_registries/generic_registry.py | 1 - kluctl/utils/git_utils.py | 22 +++--------- kluctl/utils/k8s_delete_utils.py | 3 -- 5 files changed, 7 insertions(+), 62 deletions(-) diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index 3e133007e..d5da02c54 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -268,9 +268,6 @@ def do_apply(self, k8s_cluster, force_apply, replace_on_error, force_replace_on_ return apply_util.applied_objects def do_deploy(self, k8s_cluster, force_apply, replace_on_error, force_replace_on_error, dry_run, abort_on_error): - # TODO remove this - self.migrate_to_new_manager(k8s_cluster) - return self.do_apply(k8s_cluster, force_apply, replace_on_error, force_replace_on_error, dry_run, abort_on_error) def do_diff(self, k8s_cluster, applied_objects, ignore_tags, ignore_labels, ignore_annotations, ignore_order): @@ -386,40 +383,6 @@ def clear_errors_and_warnings(self): self.api_errors = set() self.api_warnings = set() - # TODO remove this when legacy deployments are all migrated - def migrate_to_new_manager(self, k8s_cluster): - - def do_replace(o): - while True: - o2 = copy_dict(o) - need_replace = False - for mf in get_dict_value(o2, "metadata.managedFields", []): - if mf["manager"] == "deployctl": - mf["manager"] = "kluctl" - need_replace = True - break - if not need_replace: - break - - try: - k8s_cluster.replace_object(o2) - logger.info("Migrated %s to new kluctl field manager" % get_long_object_name(o2)) - break - except ConflictError: - o, _ = k8s_cluster.get_single_object(get_object_ref(o)) - logger.info("Conflict while migrating %s to new kluctl field manager" % get_long_object_name(o2)) - continue - - with MyThreadPoolExecutor(max_workers=8) as executor: - for d in self.deployments: - if not d.check_inclusion_for_deploy(): - continue - for o in d.objects: - o2 = self.remote_objects.get(get_object_ref(o)) - if o2 is None: - continue - executor.submit(do_replace, o2) - def do_diff_object(old_object, new_object, ignore_order): if old_object == new_object: return [] diff --git a/kluctl/diff/managed_fields.py b/kluctl/diff/managed_fields.py index 050f3f73c..0b9061c08 100644 --- a/kluctl/diff/managed_fields.py +++ b/kluctl/diff/managed_fields.py @@ -58,8 +58,7 @@ def remove_non_managed_fields(o, managed_fields): kluctl_fields = None for mf in v1_fields: - # TODO remove legacy deployctl manager - if mf['manager'] in ['kluctl', 'deployctl'] and mf['operation'] == 'Apply': + if mf['manager'] in ['kluctl'] and mf['operation'] == 'Apply': kluctl_fields = mf break if kluctl_fields is None: @@ -70,8 +69,7 @@ def remove_non_managed_fields(o, managed_fields): did_copy = False for mf in v1_fields: - # TODO remove legacy deployctl manager - if mf['manager'] in ['kluctl', 'deployctl']: + if mf['manager'] in ['kluctl']: continue # force-overwrite these if mf['manager'] in ['kubectl-edit', 'kubectl-client-side-apply']: diff --git a/kluctl/image_registries/generic_registry.py b/kluctl/image_registries/generic_registry.py index 510da4dfa..76904fc57 100644 --- a/kluctl/image_registries/generic_registry.py +++ b/kluctl/image_registries/generic_registry.py @@ -258,7 +258,6 @@ def is_image_from_registry(self, image): return True def list_tags_for_image(self, image): - # TODO error message dxf = self.get_client(image) tags = dxf.list_aliases(batch_size=100) return tags diff --git a/kluctl/utils/git_utils.py b/kluctl/utils/git_utils.py index 102701146..269f1ff00 100644 --- a/kluctl/utils/git_utils.py +++ b/kluctl/utils/git_utils.py @@ -164,27 +164,15 @@ def execute(self, command, **kwargs): finally: pass -def _is_mirror_remote(path): - with open(os.path.join(path, "config"), "rt") as f: - config = f.read() - if "mirror = true" in config: - return True - return False - def _clone_or_update_cache(url, cache_dir, do_update): init_marker = os.path.join(cache_dir, ".cache.init") if os.path.exists(init_marker): - # TODO remove this check after some time - if _is_mirror_remote(cache_dir): - if do_update: - logger.info(f"Updating cache repo: url='{url}'") - with build_git_object(url, cache_dir) as (g, url): - g.remote("update") - return - - # remove old cache repo (it was a --bare repo in the past, but we want to use --mirror repos now) - logger.info("Cleaning up obsolete --bare cache repo at %s" % cache_dir) + if do_update: + logger.info(f"Updating cache repo: url='{url}'") + with build_git_object(url, cache_dir) as (g, url): + g.remote("update") + return for n in os.listdir(cache_dir): if n == ".cache.lock": diff --git a/kluctl/utils/k8s_delete_utils.py b/kluctl/utils/k8s_delete_utils.py index fd8260da2..7e4fdd746 100644 --- a/kluctl/utils/k8s_delete_utils.py +++ b/kluctl/utils/k8s_delete_utils.py @@ -58,9 +58,6 @@ def exclude(x): # exclude resources which have the 'kluctl.io/skip-delete-if-tags' annotation set if inclusion_has_tags: - # TODO remove label based check - if get_label_from_object(x, 'kluctl.io/skip_delete_if_tags', 'false') == 'true': - return True if parse_bool(get_dict_value(x, 'metadata.annotations."kluctl.io/skip-delete-if-tags"', "false")): return True From 84d635dc5e17824be78ef9d6d72bead676931050 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 23 Sep 2021 16:57:20 +0200 Subject: [PATCH 0205/2916] feat: Overwrite rancher edited fields --- kluctl/diff/managed_fields.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/kluctl/diff/managed_fields.py b/kluctl/diff/managed_fields.py index 0b9061c08..315c69cf2 100644 --- a/kluctl/diff/managed_fields.py +++ b/kluctl/diff/managed_fields.py @@ -53,6 +53,15 @@ def find_array_value(a, k): ("f:metadata",) } +# We automatically force overwrite these fields as we assume these are human-edited +overwrite_allowed_managers = { + "kluctl", + "kubectl", + "kubectl-edit", + "kubectl-client-side-apply", + "rancher", +} + def remove_non_managed_fields(o, managed_fields): v1_fields = [mf for mf in managed_fields if mf['fieldsType'] == 'FieldsV1'] @@ -69,10 +78,7 @@ def remove_non_managed_fields(o, managed_fields): did_copy = False for mf in v1_fields: - if mf['manager'] in ['kluctl']: - continue - # force-overwrite these - if mf['manager'] in ['kubectl-edit', 'kubectl-client-side-apply']: + if mf['manager'] in overwrite_allowed_managers: continue for p in _fields_iterator(mf['fieldsV1'], []): if not p or [-1] == '.': From 5afce2bc9c48e903b5166c075fe1bb6fac0307f5 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 23 Sep 2021 17:17:43 +0200 Subject: [PATCH 0206/2916] fix: Handle kluctl field manager the same way, no matter if Apply or Update --- kluctl/diff/managed_fields.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/kluctl/diff/managed_fields.py b/kluctl/diff/managed_fields.py index 315c69cf2..7de1ca85c 100644 --- a/kluctl/diff/managed_fields.py +++ b/kluctl/diff/managed_fields.py @@ -65,23 +65,23 @@ def find_array_value(a, k): def remove_non_managed_fields(o, managed_fields): v1_fields = [mf for mf in managed_fields if mf['fieldsType'] == 'FieldsV1'] - kluctl_fields = None + kluctl_fields = set() + kluctl_manager_found = False for mf in v1_fields: - if mf['manager'] in ['kluctl'] and mf['operation'] == 'Apply': - kluctl_fields = mf - break - if kluctl_fields is None: + if mf["manager"] == "kluctl": + kluctl_manager_found = True + for p in _fields_iterator(mf["fieldsV1"], []): + kluctl_fields.add(tuple(p)) + if not kluctl_manager_found: # Can't do anything with this object...let's hope it will not conflict return o - kluctl_fields = set(tuple(p) for p in _fields_iterator(kluctl_fields['fieldsV1'], [])) - did_copy = False for mf in v1_fields: if mf['manager'] in overwrite_allowed_managers: continue for p in _fields_iterator(mf['fieldsV1'], []): - if not p or [-1] == '.': + if not p: continue if tuple(p) in ignored_fields: continue From 7994350068a5942c450db9e993636a25ea1ca48d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 23 Sep 2021 17:44:27 +0200 Subject: [PATCH 0207/2916] refactor: Set dry_run=True if k8s_cluster.dry_run is true and then rely on it --- kluctl/deployment/apply_util.py | 4 ++-- kluctl/deployment/deployment_collection.py | 13 ++++++------- kluctl/deployment/hooks_util.py | 2 +- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/kluctl/deployment/apply_util.py b/kluctl/deployment/apply_util.py index 75026dc5b..b933171ca 100644 --- a/kluctl/deployment/apply_util.py +++ b/kluctl/deployment/apply_util.py @@ -90,7 +90,7 @@ def apply_object(self, x): get_long_object_name_from_ref(ref)) try: self.k8s_cluster.delete_single_object(ref, force_dry_run=self.dry_run, ignore_not_found=True) - if not self.dry_run and not self.k8s_cluster.dry_run: + if not self.dry_run: r, patch_warnings = self.k8s_cluster.patch_object(x, force_apply=True) self.handle_result(r, patch_warnings) else: @@ -99,7 +99,7 @@ def apply_object(self, x): self.handle_error(ref, self.k8s_cluster.get_status_message(e2)) def wait_object(self, ref): - if self.dry_run or self.k8s_cluster.dry_run: + if self.dry_run: return True did_log = False diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index d5da02c54..51644d53b 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -142,16 +142,16 @@ def prepare(self, k8s_cluster): def deploy(self, k8s_cluster, force_apply, replace_on_error, force_replace_on_error, abort_on_error): self.clear_errors_and_warnings() - applied_objects = self.do_deploy(k8s_cluster, force_apply, replace_on_error, force_replace_on_error, - False, abort_on_error) + applied_objects = self.do_apply(k8s_cluster, force_apply, replace_on_error, force_replace_on_error, + False, abort_on_error) new_objects, changed_objects = self.do_diff(k8s_cluster, applied_objects, False, False, False, False) return DeployDiffResult(new_objects=new_objects, changed_objects=changed_objects, errors=list(self.api_errors), warnings=list(self.api_warnings)) def diff(self, k8s_cluster, force_apply, replace_on_error, force_replace_on_error, ignore_tags, ignore_labels, ignore_annotations, ignore_order): self.clear_errors_and_warnings() - applied_objects = self.do_deploy(k8s_cluster, force_apply, replace_on_error, force_replace_on_error, - True, False) + applied_objects = self.do_apply(k8s_cluster, force_apply, replace_on_error, force_replace_on_error, + True, False) new_objects, changed_objects = self.do_diff(k8s_cluster, applied_objects, ignore_tags, ignore_labels, ignore_annotations, ignore_order) return DeployDiffResult(new_objects=new_objects, changed_objects=changed_objects, errors=list(self.api_errors), warnings=list(self.api_warnings)) @@ -263,13 +263,12 @@ def find_prune_objects(self, k8s_cluster): return find_objects_for_delete(k8s_cluster, labels, self.inclusion, excluded_objects) def do_apply(self, k8s_cluster, force_apply, replace_on_error, force_replace_on_error, dry_run, abort_on_error): + if k8s_cluster.dry_run: + dry_run = dry_run apply_util = ApplyUtil(self, k8s_cluster, force_apply, replace_on_error, force_replace_on_error, dry_run, abort_on_error) apply_util.apply_deployments() return apply_util.applied_objects - def do_deploy(self, k8s_cluster, force_apply, replace_on_error, force_replace_on_error, dry_run, abort_on_error): - return self.do_apply(k8s_cluster, force_apply, replace_on_error, force_replace_on_error, dry_run, abort_on_error) - def do_diff(self, k8s_cluster, applied_objects, ignore_tags, ignore_labels, ignore_annotations, ignore_order): diff_objects = {} normalized_diff_objects = {} diff --git a/kluctl/deployment/hooks_util.py b/kluctl/deployment/hooks_util.py index b8c527802..89193278a 100644 --- a/kluctl/deployment/hooks_util.py +++ b/kluctl/deployment/hooks_util.py @@ -36,7 +36,7 @@ def run_hooks(self, d, hooks): if self.apply_util.abort_signal: return logger.info("Deploying hook %s" % get_long_object_name(h.object)) - if (self.apply_util.dry_run or self.apply_util.k8s_cluster.dry_run) and "before-hook-creation" in h.delete_policies: + if self.apply_util.dry_run and "before-hook-creation" in h.delete_policies: self.apply_util.handle_result(h.object, []) continue self.apply_util.apply_object(h.object) From 973e815660af2b2439ec1a2762df246829922f69 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 24 Sep 2021 10:03:50 +0200 Subject: [PATCH 0208/2916] refactor: Move logging per kustomize deployment into apply_kustomize_deployment --- kluctl/deployment/apply_util.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/kluctl/deployment/apply_util.py b/kluctl/deployment/apply_util.py index b933171ca..96de6ed67 100644 --- a/kluctl/deployment/apply_util.py +++ b/kluctl/deployment/apply_util.py @@ -120,6 +120,14 @@ def wait_object(self, ref): did_log = True def apply_kustomize_deployment(self, d): + if "path" not in d.config: + return + include = d.check_inclusion_for_deploy() + if not include: + logger.info("Skipping kustomizeDir %s" % d.get_rel_kustomize_dir()) + return + logger.info("%s: Applying %d objects" % (d.get_rel_kustomize_dir(), len(d.objects))) + inital_deploy = True for o in d.objects: if get_object_ref(o) in self.deployment_collection.remote_objects: @@ -163,14 +171,6 @@ def finish_futures(): finish_futures() previous_was_barrier = get_dict_value(d.config, "barrier", False) - include = d.check_inclusion_for_deploy() - if "path" not in d.config: - continue - if not include: - logger.info("Skipping kustomizeDir %s" % d.get_rel_kustomize_dir()) - continue - logger.info("Applying kustomizeDir '%s' with %d objects" % (d.get_rel_kustomize_dir(), len(d.objects))) - f = executor.submit(self.apply_kustomize_deployment, d) futures.append(f) From a4082b709a7e5e51349c3570a8da78c38d9ec2fd Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 24 Sep 2021 10:13:30 +0200 Subject: [PATCH 0209/2916] fix: Improve logging of kustomize deployments and hooks --- kluctl/cli/main_cli_group.py | 5 +++-- kluctl/deployment/apply_util.py | 11 +++++++--- kluctl/deployment/hooks_util.py | 36 ++++++++++++++++++++++++--------- 3 files changed, 38 insertions(+), 14 deletions(-) diff --git a/kluctl/cli/main_cli_group.py b/kluctl/cli/main_cli_group.py index 2d2c24b2e..355a8cb16 100644 --- a/kluctl/cli/main_cli_group.py +++ b/kluctl/cli/main_cli_group.py @@ -28,8 +28,9 @@ def setup_logging(verbose): level = logging.INFO if verbose: level = logging.DEBUG - - format = '%(asctime)s %(name)-30s %(levelname)-8s %(message)s' + format = '%(asctime)s %(name)-30s %(levelname)-6s %(message)s' + else: + format = '%(asctime)s %(levelname)-6s %(message)s' logging.basicConfig(level=level, format=format) diff --git a/kluctl/deployment/apply_util.py b/kluctl/deployment/apply_util.py index 96de6ed67..4a0964a68 100644 --- a/kluctl/deployment/apply_util.py +++ b/kluctl/deployment/apply_util.py @@ -1,6 +1,5 @@ import logging import threading -import time from kubernetes.client import ApiException from kubernetes.dynamic.exceptions import ResourceNotFoundError @@ -124,9 +123,8 @@ def apply_kustomize_deployment(self, d): return include = d.check_inclusion_for_deploy() if not include: - logger.info("Skipping kustomizeDir %s" % d.get_rel_kustomize_dir()) + self.do_log(d, logging.INFO, "Skipping") return - logger.info("%s: Applying %d objects" % (d.get_rel_kustomize_dir(), len(d.objects))) inital_deploy = True for o in d.objects: @@ -141,9 +139,13 @@ def apply_kustomize_deployment(self, d): else: hook_util.run_hooks(d, ["pre-deploy-upgrade", "pre-deploy"]) + apply_objects = [] for o in d.objects: if hook_util.get_hook(o) is not None: continue + apply_objects.append(o) + self.do_log(d, logging.INFO, "Applying %d objects" % len(d.objects)) + for o in apply_objects: self.apply_object(o) if inital_deploy: @@ -175,3 +177,6 @@ def finish_futures(): futures.append(f) finish_futures() + + def do_log(self, d, level, str): + logger.log(level, "%s: %s" % (d.get_rel_kustomize_dir(), str)) diff --git a/kluctl/deployment/hooks_util.py b/kluctl/deployment/hooks_util.py index 89193278a..c0c506f04 100644 --- a/kluctl/deployment/hooks_util.py +++ b/kluctl/deployment/hooks_util.py @@ -22,23 +22,35 @@ def __init__(self, apply_util): self.apply_util = apply_util def run_hooks(self, d, hooks): + def do_log(level, str): + self.apply_util.do_log(d, level, str) + l = self.get_sorted_hooks_list(d.objects) l = [x for x in l if any(h in hooks for h in x.hooks)] - for h in l: - if self.apply_util.abort_signal: - return - if "before-hook-creation" in h.delete_policies: - logger.info("Deleting hook %s due to hook-delete-policy %s" % (get_long_object_name(h.object), ",".join(h.delete_policies))) - self.apply_util.delete_object(get_object_ref(h.object)) + delete_before_objects = [] + apply_objects = [] for h in l: if self.apply_util.abort_signal: return - logger.info("Deploying hook %s" % get_long_object_name(h.object)) + if "before-hook-creation" in h.delete_policies: + delete_before_objects.append(h) + apply_objects.append(h) + + if len(delete_before_objects) != 0: + do_log(logging.INFO, "Deleting %d hooks before hook execution" % len(delete_before_objects)) + for h in delete_before_objects: + do_log(logging.DEBUG, "Deleting hook %s due to hook-delete-policy %s" % (get_long_object_name(h.object), ",".join(h.delete_policies))) + self.apply_util.delete_object(get_object_ref(h.object)) + + if len(apply_objects) != 0: + do_log(logging.INFO, "Applying %d hooks" % len(delete_before_objects)) + for h in apply_objects: if self.apply_util.dry_run and "before-hook-creation" in h.delete_policies: self.apply_util.handle_result(h.object, []) continue + do_log(logging.DEBUG, "Applying hook %s" % get_long_object_name(h.object)) self.apply_util.apply_object(h.object) wait_results = {} @@ -52,6 +64,7 @@ def run_hooks(self, d, hooks): continue wait_results[ref] = self.apply_util.wait_object(ref) + delete_after_objects = [] for h in reversed(l): ref = get_object_ref(h.object) if ref not in wait_results: @@ -62,8 +75,13 @@ def run_hooks(self, d, hooks): elif not wait_results[ref] and "hook-failed" in h.delete_policies: do_delete = True if do_delete: - logger.info("Deleting hook %s due to hook-delete-policy %s" % (get_long_object_name(h.object), ",".join(h.delete_policies))) - self.apply_util.delete_object(ref) + delete_after_objects.append(h) + + if len(delete_after_objects) != 0: + do_log(logging.INFO, "Deleting %d hooks after hook execution" % len(delete_after_objects)) + for h in delete_after_objects: + do_log(logging.DEBUG, "Deleting hook %s due to hook-delete-policy %s" % (get_long_object_name(h.object), ",".join(h.delete_policies))) + self.apply_util.delete_object(get_object_ref(h.object)) def get_hook(self, o) -> Optional[Hook]: def get_list(path): From dde0a1ffa63d72b93fdcaf3eef2dfddcdd6325fb Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 24 Sep 2021 10:13:56 +0200 Subject: [PATCH 0210/2916] refactor: Use object_iterator in manaved_fields.py --- kluctl/diff/managed_fields.py | 33 ++++++++++----------------------- kluctl/utils/dict_utils.py | 3 ++- 2 files changed, 12 insertions(+), 24 deletions(-) diff --git a/kluctl/diff/managed_fields.py b/kluctl/diff/managed_fields.py index 7de1ca85c..75015106d 100644 --- a/kluctl/diff/managed_fields.py +++ b/kluctl/diff/managed_fields.py @@ -1,15 +1,9 @@ import json from kluctl.utils.k8s_object_utils import get_object_ref, get_long_object_name -from kluctl.utils.dict_utils import copy_dict +from kluctl.utils.dict_utils import copy_dict, object_iterator -def _fields_iterator(mf, path): - yield path - for k, v in mf.items(): - for p in _fields_iterator(v, path + [k]): - yield p - def nav_managed_field(o, p): def find_array_value(a, k): k = json.loads(k) @@ -65,27 +59,21 @@ def find_array_value(a, k): def remove_non_managed_fields(o, managed_fields): v1_fields = [mf for mf in managed_fields if mf['fieldsType'] == 'FieldsV1'] - kluctl_fields = set() - kluctl_manager_found = False + owned_fields = set() for mf in v1_fields: - if mf["manager"] == "kluctl": - kluctl_manager_found = True - for p in _fields_iterator(mf["fieldsV1"], []): - kluctl_fields.add(tuple(p)) - if not kluctl_manager_found: + if mf["manager"] in overwrite_allowed_managers: + for v, p in object_iterator(mf["fieldsV1"]): + owned_fields.add(tuple(p)) + if not owned_fields: # Can't do anything with this object...let's hope it will not conflict return o + owned_fields.update(ignored_fields) + did_copy = False for mf in v1_fields: - if mf['manager'] in overwrite_allowed_managers: - continue - for p in _fields_iterator(mf['fieldsV1'], []): - if not p: - continue - if tuple(p) in ignored_fields: - continue - if tuple(p) in kluctl_fields: + for v, p in object_iterator(mf['fieldsV1']): + if tuple(p) in owned_fields: continue d, k, found = nav_managed_field(o, p) @@ -99,7 +87,6 @@ def remove_non_managed_fields(o, managed_fields): return o - def remove_non_managed_fields2(o, remote_objects): r = remote_objects.get(get_object_ref(o)) if r is not None: diff --git a/kluctl/utils/dict_utils.py b/kluctl/utils/dict_utils.py index e93bd4a0e..a3d28c1ec 100644 --- a/kluctl/utils/dict_utils.py +++ b/kluctl/utils/dict_utils.py @@ -153,7 +153,8 @@ def object_iterator(o): while len(stack) != 0: o2, p = stack.pop() - yield o2, p + if len(p) != 0: + yield o2, p if isinstance(o2, dict): for k, v in o2.items(): From d5f7251b50cda62dceefa5c0b69c5193d6d04dd0 Mon Sep 17 00:00:00 2001 From: Aljoscha Poertner Date: Mon, 27 Sep 2021 16:20:45 +0200 Subject: [PATCH 0211/2916] feat: add more informative error message when deployment.yml is missing --- kluctl/deployment/deployment_project.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/kluctl/deployment/deployment_project.py b/kluctl/deployment/deployment_project.py index 722518b80..c9a6ab571 100644 --- a/kluctl/deployment/deployment_project.py +++ b/kluctl/deployment/deployment_project.py @@ -75,8 +75,13 @@ def load_rendered_yaml(self, rel_path, jinja_vars): return yaml_load(rendered) def load_base_conf(self): - if not os.path.exists(os.path.join(self.dir, 'deployment.yml')): - raise CommandError("deployment.yml not found") + base_conf_path = os.path.join(self.dir, 'deployment.yml') + if not os.path.exists(base_conf_path): + if os.path.exists(os.path.join(self.dir, 'kustomization.yml')): + error_text = "deployment.yml not found but folder %s contains kustomization.yml. Is it a kustomizeDir?" % self.dir + else: + error_text = "%s not found" % base_conf_path + raise CommandError(error_text) self.conf = self.load_rendered_yaml('deployment.yml', self.jinja_vars) if self.conf is None: From 79fe9b93413d6ec32e40cc4e074e61c0c34ccef4 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 28 Sep 2021 10:14:22 +0200 Subject: [PATCH 0212/2916] fix: Upgrade helm and kustomize in docker image --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index a3049e401..2f0fd2485 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,8 +3,8 @@ FROM python:3.8.10-slim-buster RUN apt update && apt install wget libyaml-dev git -y && rm -rf /var/lib/apt/lists/* # Install tools -ENV KUSTOMIZE_VERSION=v4.2.0 -ENV HELM_VERSION=v3.6.3 +ENV KUSTOMIZE_VERSION=v4.4.0 +ENV HELM_VERSION=v3.7.0 ENV KUBESEAL_VERSION=v0.16.0 RUN wget -O kustomize.tar.gz https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2F${KUSTOMIZE_VERSION}/kustomize_${KUSTOMIZE_VERSION}_linux_amd64.tar.gz && \ tar xzf kustomize.tar.gz && \ From d8836731e9d77b3ec73e0abaa14eabcd8153ba7b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 28 Sep 2021 10:17:34 +0200 Subject: [PATCH 0213/2916] fix: Use apply_object even for replaced hooks This ensures managed fields are handled properly --- kluctl/deployment/apply_util.py | 10 ++++++++-- kluctl/deployment/hooks_util.py | 6 ++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/kluctl/deployment/apply_util.py b/kluctl/deployment/apply_util.py index 4a0964a68..e6bc99d12 100644 --- a/kluctl/deployment/apply_util.py +++ b/kluctl/deployment/apply_util.py @@ -48,7 +48,7 @@ def had_error(self, ref): def delete_object(self, ref): self.k8s_cluster.delete_single_object(ref, force_dry_run=self.dry_run, ignore_not_found=True) - def apply_object(self, x): + def apply_object(self, x, replaced): logger.debug(f" {get_long_object_name(x)}") if not self.force_apply: @@ -56,6 +56,12 @@ def apply_object(self, x): else: x2 = x + if self.dry_run and replaced and get_object_ref(x) in self.deployment_collection.remote_objects: + # Let's simulate that this object was deleted in dry-run mode. If we'd actually try a dry-run apply with + # this object, it might fail as it is expected to not exist. + self.handle_result(x2, []) + return + try: r, patch_warnings = self.k8s_cluster.patch_object(x2, force_dry_run=self.dry_run, force_apply=True) self.handle_result(r, patch_warnings) @@ -146,7 +152,7 @@ def apply_kustomize_deployment(self, d): apply_objects.append(o) self.do_log(d, logging.INFO, "Applying %d objects" % len(d.objects)) for o in apply_objects: - self.apply_object(o) + self.apply_object(o, False) if inital_deploy: hook_util.run_hooks(d, ["post-deploy-initial", "post-deploy"]) diff --git a/kluctl/deployment/hooks_util.py b/kluctl/deployment/hooks_util.py index c0c506f04..f27e491d4 100644 --- a/kluctl/deployment/hooks_util.py +++ b/kluctl/deployment/hooks_util.py @@ -47,11 +47,9 @@ def do_log(level, str): if len(apply_objects) != 0: do_log(logging.INFO, "Applying %d hooks" % len(delete_before_objects)) for h in apply_objects: - if self.apply_util.dry_run and "before-hook-creation" in h.delete_policies: - self.apply_util.handle_result(h.object, []) - continue + replaced = "before-hook-creation" in h.delete_policies do_log(logging.DEBUG, "Applying hook %s" % get_long_object_name(h.object)) - self.apply_util.apply_object(h.object) + self.apply_util.apply_object(h.object, replaced) wait_results = {} for h in l: From 0db9f0ce68bfc784d12ff5be234efc42f226bd9f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 28 Sep 2021 10:18:53 +0200 Subject: [PATCH 0214/2916] refactor: Use dot notation whenever possible when converting lists to json path --- kluctl/utils/dict_utils.py | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/kluctl/utils/dict_utils.py b/kluctl/utils/dict_utils.py index a3d28c1ec..25b2ab90b 100644 --- a/kluctl/utils/dict_utils.py +++ b/kluctl/utils/dict_utils.py @@ -24,18 +24,30 @@ def ext_reified_fields(self, datum): return tuple(result) jsonpath.Fields.reified_fields = ext_reified_fields -def parse_json_path(p) -> JSONPath: - if isinstance(p, list): - p2 = "$" - for x in p: - if isinstance(x, str): +def convert_list_to_json_path(p): + p2 = "" + for x in p: + if isinstance(x, str): + if x.isalnum(): + if p2 != "": + p2 += "." + p2 += "%s" % x + else: + if p2 == "": + p2 = "$" if '"' in x: p2 = "%s['%s']" % (p2, x) else: p2 = '%s["%s"]' % (p2, x) - else: - p2 = "%s[%d]" % (p2, x) - p = p2 + else: + if p2 == "": + p2 = "$" + p2 = "%s[%d]" % (p2, x) + return p2 + +def parse_json_path(p) -> JSONPath: + if isinstance(p, list): + p = convert_list_to_json_path(p) with json_path_cache_mutex: if p in json_path_cache: From b6f5a2b33626765c7ce783c4163cce70bd764331 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 28 Sep 2021 10:19:40 +0200 Subject: [PATCH 0215/2916] refactor: Simplify is_iterable calls --- kluctl/utils/dict_utils.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/kluctl/utils/dict_utils.py b/kluctl/utils/dict_utils.py index 25b2ab90b..0f1f9a187 100644 --- a/kluctl/utils/dict_utils.py +++ b/kluctl/utils/dict_utils.py @@ -59,9 +59,7 @@ def parse_json_path(p) -> JSONPath: def copy_primitive_value(v): if isinstance(v, dict): return copy_dict(v) - if isinstance(v, str) or isinstance(v, bytes): - return v - if is_iterable(v): + if is_iterable(v, False): return [copy_primitive_value(x) for x in v] return v @@ -138,7 +136,7 @@ def remove_empty(o): else: i += 1 -def is_iterable(obj): +def is_iterable(obj, str_and_bytes=True): if isinstance(obj, list): return True if isinstance(obj, tuple): @@ -146,9 +144,9 @@ def is_iterable(obj): if isinstance(obj, dict): return True if isinstance(obj, str): - return True + return str_and_bytes if isinstance(obj, bytes): - return True + return str_and_bytes if isinstance(obj, int) or isinstance(obj, bool): return False if isinstance(obj, type): @@ -171,6 +169,6 @@ def object_iterator(o): if isinstance(o2, dict): for k, v in o2.items(): stack.append((v, p + [k])) - elif not isinstance(o2, str) and is_iterable(o2): + elif is_iterable(o2, False): for i, v in enumerate(o2): stack.append((v, p + [i])) From c51bb44234a05779eb4adaaf0f1a56f8923ffd45 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 28 Sep 2021 10:36:24 +0200 Subject: [PATCH 0216/2916] fix: Call fix_object_for_patch from ApplyUtil.apply_object --- kluctl/deployment/apply_util.py | 1 + kluctl/utils/k8s_cluster_real.py | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/kluctl/deployment/apply_util.py b/kluctl/deployment/apply_util.py index e6bc99d12..efa887d11 100644 --- a/kluctl/deployment/apply_util.py +++ b/kluctl/deployment/apply_util.py @@ -51,6 +51,7 @@ def delete_object(self, ref): def apply_object(self, x, replaced): logger.debug(f" {get_long_object_name(x)}") + x2 = self.k8s_cluster.fix_object_for_patch(x) if not self.force_apply: x2 = remove_non_managed_fields2(x, self.deployment_collection.remote_objects) else: diff --git a/kluctl/utils/k8s_cluster_real.py b/kluctl/utils/k8s_cluster_real.py index 03afaab10..7ea2545fe 100644 --- a/kluctl/utils/k8s_cluster_real.py +++ b/kluctl/utils/k8s_cluster_real.py @@ -208,8 +208,6 @@ def patch_object(self, body, namespace=None, force_dry_run=False, force_apply=Fa name = body['metadata']['name'] query_params = self._get_dry_run_params(force_dry_run) - body = self._fix_object_for_patch(body) - query_params.append(('fieldManager', 'kluctl')) if force_apply: query_params.append(('force', 'true')) @@ -255,7 +253,7 @@ def replace_object(self, body, namespace=None, force_dry_run=False, resource_ver self.dynamic_client.resources.force_invalidate_cache() return r, warnings - def _fix_object_for_patch(self, o): + def fix_object_for_patch(self, o): # A bug in versions < 1.20 cause errors when applying resources that have some fields omitted which have # default values. We need to fix these resources. # UPDATE even though https://github.com/kubernetes-sigs/structured-merge-diff/issues/130 says it's fixed, the From e3e21452e0f055d2e441eb14be8a89cd61e6d935 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 28 Sep 2021 10:37:14 +0200 Subject: [PATCH 0217/2916] fix: Only perform defaults fix for k8s < 1.21 The issue seems to be finally fixed in 1.21. --- kluctl/utils/k8s_cluster_real.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kluctl/utils/k8s_cluster_real.py b/kluctl/utils/k8s_cluster_real.py index 7ea2545fe..06424cc84 100644 --- a/kluctl/utils/k8s_cluster_real.py +++ b/kluctl/utils/k8s_cluster_real.py @@ -258,7 +258,7 @@ def fix_object_for_patch(self, o): # default values. We need to fix these resources. # UPDATE even though https://github.com/kubernetes-sigs/structured-merge-diff/issues/130 says it's fixed, the # issue is still present. - needs_defaults_fix = StrictVersion(self.server_version) < StrictVersion('1.100') + needs_defaults_fix = StrictVersion(self.server_version) < StrictVersion('1.21') # TODO check when this is actually fixed (see https://github.com/kubernetes/kubernetes/issues/94275) needs_type_conversion_fix = StrictVersion(self.server_version) < StrictVersion('1.100') if not needs_defaults_fix and not needs_type_conversion_fix: From 9fc24c45b84105bd989e55e798028095457f642e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 28 Sep 2021 12:03:12 +0200 Subject: [PATCH 0218/2916] refactor: Move json path code into own module --- kluctl/utils/dict_utils.py | 57 +----------------- kluctl/utils/jsonpath_utils.py | 105 +++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 56 deletions(-) create mode 100644 kluctl/utils/jsonpath_utils.py diff --git a/kluctl/utils/dict_utils.py b/kluctl/utils/dict_utils.py index 0f1f9a187..85aae0810 100644 --- a/kluctl/utils/dict_utils.py +++ b/kluctl/utils/dict_utils.py @@ -1,60 +1,5 @@ -import fnmatch -import threading +from kluctl.utils.jsonpath_utils import parse_json_path -from jsonpath_ng import auto_id_field, jsonpath, JSONPath -from jsonpath_ng.ext import parse - -json_path_cache = {} -json_path_cache_mutex = threading.Lock() - -# Add better wildcard support to jsonpath lib -def ext_reified_fields(self, datum): - result = [] - for field in self.fields: - if "*" not in field: - result.append(field) - continue - try: - fields = [f for f in datum.value.keys() if fnmatch.fnmatch(f, field)] - if auto_id_field is not None: - fields.append(auto_id_field) - result += fields - except AttributeError: - pass - return tuple(result) -jsonpath.Fields.reified_fields = ext_reified_fields - -def convert_list_to_json_path(p): - p2 = "" - for x in p: - if isinstance(x, str): - if x.isalnum(): - if p2 != "": - p2 += "." - p2 += "%s" % x - else: - if p2 == "": - p2 = "$" - if '"' in x: - p2 = "%s['%s']" % (p2, x) - else: - p2 = '%s["%s"]' % (p2, x) - else: - if p2 == "": - p2 = "$" - p2 = "%s[%d]" % (p2, x) - return p2 - -def parse_json_path(p) -> JSONPath: - if isinstance(p, list): - p = convert_list_to_json_path(p) - - with json_path_cache_mutex: - if p in json_path_cache: - return json_path_cache[p] - pp = parse(p) - json_path_cache[p] = pp - return pp def copy_primitive_value(v): if isinstance(v, dict): diff --git a/kluctl/utils/jsonpath_utils.py b/kluctl/utils/jsonpath_utils.py new file mode 100644 index 000000000..b526d8013 --- /dev/null +++ b/kluctl/utils/jsonpath_utils.py @@ -0,0 +1,105 @@ +# Add better wildcard support to jsonpath lib +import atexit +import fnmatch +import logging +import os +import pickle +import threading + +from jsonpath_ng import auto_id_field, jsonpath, JSONPath +from jsonpath_ng.ext import parse + +from kluctl.utils.utils import get_tmp_base_dir + +logger = logging.getLogger(__name__) + +def ext_reified_fields(self, datum): + result = [] + for field in self.fields: + if "*" not in field: + result.append(field) + continue + try: + fields = [f for f in datum.value.keys() if fnmatch.fnmatch(f, field)] + if auto_id_field is not None: + fields.append(auto_id_field) + result += fields + except AttributeError: + pass + return tuple(result) +jsonpath.Fields.reified_fields = ext_reified_fields + +def convert_list_to_json_path(p): + p2 = "" + for x in p: + if isinstance(x, str): + if x.isalnum(): + if p2 != "": + p2 += "." + p2 += "%s" % x + else: + if p2 == "": + p2 = "$" + if '"' in x: + p2 = "%s['%s']" % (p2, x) + else: + p2 = '%s["%s"]' % (p2, x) + else: + if p2 == "": + p2 = "$" + p2 = "%s[%d]" % (p2, x) + return p2 + +json_path_cache = None +json_path_cache_modified = False +json_path_cache_lock = threading.Lock() + +def load_json_path_cache(): + global json_path_cache + if json_path_cache is not None: + return + with json_path_cache_lock: + path = os.path.join(get_tmp_base_dir(), "kluctl-jsonpath-cache.bin") + if not os.path.exists(path): + json_path_cache = {} + return + try: + with open(path, mode="rb") as f: + json_path_cache = pickle.load(f) + except Exception as e: + logger.exception("Excpetion while loading jsonpath cache", exc_info=e) + json_path_cache = {} + +def save_json_path_cache(): + with json_path_cache_lock: + if not json_path_cache: + return + if not json_path_cache_modified: + return + path = os.path.join(get_tmp_base_dir(), "kluctl-jsonpath-cache.bin") + try: + logger.debug("Storing %d jsonpath cache entries" % len(json_path_cache)) + with open(path + ".tmp", mode="wb") as f: + pickle.dump(json_path_cache, f) + os.rename(path + ".tmp", path) + except Exception as e: + logger.exception("Exception while storing jsonpath cache", exc_info=e) + +atexit.register(save_json_path_cache) + +def parse_json_path(p) -> JSONPath: + if isinstance(p, list) or isinstance(p, tuple): + p = convert_list_to_json_path(p) + + load_json_path_cache() + + if p in json_path_cache: + return json_path_cache[p] + + pp = parse(p) + pp = json_path_cache.setdefault(p, pp) + + global json_path_cache_modified + json_path_cache_modified = True + + return pp From 2ed58198761a96cbe7e5135dcc5835b8ae07d8c7 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 29 Sep 2021 16:32:50 +0200 Subject: [PATCH 0219/2916] feat: Reimplement managed fields conflict resolution --- kluctl/deployment/apply_util.py | 13 +- kluctl/diff/managed_fields.py | 202 +++++++++++++++++++------------- kluctl/utils/dict_utils.py | 10 +- 3 files changed, 136 insertions(+), 89 deletions(-) diff --git a/kluctl/deployment/apply_util.py b/kluctl/deployment/apply_util.py index efa887d11..7738d9d57 100644 --- a/kluctl/deployment/apply_util.py +++ b/kluctl/deployment/apply_util.py @@ -5,7 +5,7 @@ from kubernetes.dynamic.exceptions import ResourceNotFoundError from kluctl.deployment.hooks_util import HooksUtil -from kluctl.diff.managed_fields import remove_non_managed_fields2 +from kluctl.diff.managed_fields import resolve_field_manager_conflicts from kluctl.utils.dict_utils import get_dict_value, set_dict_value from kluctl.utils.k8s_object_utils import get_object_ref, get_long_object_name, get_long_object_name_from_ref from kluctl.utils.k8s_status_validation import validate_object @@ -53,9 +53,14 @@ def apply_object(self, x, replaced): x2 = self.k8s_cluster.fix_object_for_patch(x) if not self.force_apply: - x2 = remove_non_managed_fields2(x, self.deployment_collection.remote_objects) - else: - x2 = x + ref = get_object_ref(x) + remote_object = self.deployment_collection.remote_objects.get(ref) + x2, overwritten = resolve_field_manager_conflicts(x2, remote_object) + warnings = [] + for ow in overwritten: + warnings.append("Field '%s' is now owned by field manager '%s'. " + "It is NOT updated to the desired value '%s'!" % (ow.path, ow.field_manager, ow.local_value)) + self.deployment_collection.add_api_warnings(ref, warnings) if self.dry_run and replaced and get_object_ref(x) in self.deployment_collection.remote_objects: # Let's simulate that this object was deleted in dry-run mode. If we'd actually try a dry-run apply with diff --git a/kluctl/diff/managed_fields.py b/kluctl/diff/managed_fields.py index 75015106d..bb5df4125 100644 --- a/kluctl/diff/managed_fields.py +++ b/kluctl/diff/managed_fields.py @@ -1,51 +1,10 @@ +import dataclasses import json +from typing import Any -from kluctl.utils.k8s_object_utils import get_object_ref, get_long_object_name -from kluctl.utils.dict_utils import copy_dict, object_iterator - - -def nav_managed_field(o, p): - def find_array_value(a, k): - k = json.loads(k) - for i in range(len(a)): - o = a[i] - match = True - for n, v in k.items(): - if n not in o or o[n] != v: - match = False - break - if match: - return i - return -1 - - o_in = o - - for i in range(len(p)): - if p[i] == '.': - return o, None, False - - k = p[i] - t = k[0] - k = k[2:] - if t == 'f': - if k not in o: - return o, k, False - if i == len(p) - 1: - return o, k, True - o = o[k] - elif t == 'k': - j = find_array_value(o, k) - if j == -1: - return o, -1, False - if i == len(p) - 1: - return o, j, True - o = o[j] - else: - raise ValueError('Unsupported managed field %s in object %s' % ('.'.join(p), get_long_object_name(o_in))) - -ignored_fields = { - ("f:metadata",) -} +from kluctl.utils.dict_utils import copy_dict, object_iterator, del_dict_value, get_dict_value, is_iterable, \ + set_dict_value +from kluctl.utils.jsonpath_utils import convert_list_to_json_path # We automatically force overwrite these fields as we assume these are human-edited overwrite_allowed_managers = { @@ -56,44 +15,123 @@ def find_array_value(a, k): "rancher", } -def remove_non_managed_fields(o, managed_fields): +def check_item_match(o, kv, index): + if kv[0:2] == "k:": + k = kv[2:] + j = json.loads(k) + for k2 in j.keys(): + if k2 not in o: + return False + if j[k2] != o[k2]: + return False + return True + elif kv[0:2] == "v:": + k = kv[2:] + j = json.loads(k) + return j == o + elif kv[0:2] == "i:": + index2 = int(kv[2:]) + return index == index2 + else: + raise ValueError("Invalid managedFields entry '%s'" % kv) + +def convert_to_json_path(o, mf_path): + ret = [] + for i in range(len(mf_path)): + k = mf_path[i] + if k == "{}": + raise Exception() + if k == ".": + if i != len(mf_path) - 1: + raise ValueError("Unexpected . element at %s" % convert_list_to_json_path(ret)) + return ret, True + if k[:2] == 'f:': + k = k[2:] + if not isinstance(o, dict): + raise ValueError("%s is not a dict" % convert_list_to_json_path(ret)) + if k not in o: + return ret, False + ret.append(k) + o = o[k] + else: + if not is_iterable(o, False): + raise ValueError("%s is not a list" % convert_list_to_json_path(ret)) + found = False + for j, v in enumerate(o): + if check_item_match(v, k, j): + found = True + ret.append(j) + o = o[j] + break + if not found: + return ret, False + return ret, True + +not_found = object() + +@dataclasses.dataclass +class OverwrittenField: + path: str + local_value: Any + remote_value: Any + value: Any + field_manager: str + +def resolve_field_manager_conflicts(local_object, remote_object): + overwritten = [] + + managed_fields = get_dict_value(remote_object, "metadata.managedFields") + if managed_fields is None: + return local_object, overwritten v1_fields = [mf for mf in managed_fields if mf['fieldsType'] == 'FieldsV1'] - owned_fields = set() + local_field_owners = {} for mf in v1_fields: - if mf["manager"] in overwrite_allowed_managers: - for v, p in object_iterator(mf["fieldsV1"]): - owned_fields.add(tuple(p)) - if not owned_fields: - # Can't do anything with this object...let's hope it will not conflict - return o + for v, p in object_iterator(mf["fieldsV1"], only_leafs=True): + local_json_path, local_found = convert_to_json_path(local_object, p) - owned_fields.update(ignored_fields) + if local_found: + local_field_owners[tuple(local_json_path)] = mf - did_copy = False - for mf in v1_fields: - for v, p in object_iterator(mf['fieldsV1']): - if tuple(p) in owned_fields: - continue - - d, k, found = nav_managed_field(o, p) - if found: - if not did_copy: - did_copy = True - o = copy_dict(o) - d, k, found = nav_managed_field(o, p) - assert found - del d[k] - - return o - -def remove_non_managed_fields2(o, remote_objects): - r = remote_objects.get(get_object_ref(o)) - if r is not None: - mf = r['metadata'].get('managedFields') - if mf is None: - return o - else: - return remove_non_managed_fields(o, mf) - else: - return o + def find_owner(owners, p): + tp = tuple(p) + fm = None + while len(tp) > 0: + fm = owners.get(tp) + if fm is not None: + break + tp = tp[:-1] + return fm, tp + + ret = copy_dict(local_object) + to_delete = set() + for v, p in object_iterator(local_object, only_leafs=True): + remote_value = get_dict_value(remote_object, p, not_found) + + fm, tp = find_owner(local_field_owners, p) + + if fm is None: + # No manager found that claimed this field. If it's not existing in the remote object, it means it's a + # new field so we can safely claim it. If it's present in the remote object AND has changed, it's a system + # field that we have no control over! + if remote_value is not not_found: + if v != remote_value: + set_dict_value(ret, p, remote_value) + overwritten.append(OverwrittenField(path=convert_list_to_json_path(p), + local_value=v if v is not not_found else None, + remote_value=remote_value, + value=remote_value, field_manager="")) + elif fm["manager"] not in overwrite_allowed_managers: + to_delete.add(tp) + if v != remote_value: + overwritten.append(OverwrittenField(path=convert_list_to_json_path(p), + local_value=v if v is not not_found else None, + remote_value=remote_value if remote_value is not not_found else None, + value=None, field_manager=fm["manager"])) + + for p in to_delete: + # We do not own this field, so we should also not set it (not even to the same value to ensure we don't + # claim shared ownership) + del_dict_value(ret, p) + + return ret, overwritten diff --git a/kluctl/utils/dict_utils.py b/kluctl/utils/dict_utils.py index 85aae0810..fc175f067 100644 --- a/kluctl/utils/dict_utils.py +++ b/kluctl/utils/dict_utils.py @@ -103,17 +103,21 @@ def is_iterable(obj, str_and_bytes=True): else: return True -def object_iterator(o): +def object_iterator(o, only_leafs=False): stack = [(o, [])] while len(stack) != 0: o2, p = stack.pop() - if len(p) != 0: - yield o2, p + is_leaf = True if isinstance(o2, dict): for k, v in o2.items(): + is_leaf = False stack.append((v, p + [k])) elif is_iterable(o2, False): for i, v in enumerate(o2): + is_leaf = False stack.append((v, p + [i])) + + if len(p) != 0 and (is_leaf or not only_leafs): + yield o2, p From d8b892749d120f819d672fe84bdb2740e691bb00 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 29 Sep 2021 16:38:45 +0200 Subject: [PATCH 0220/2916] fix: More logging while getting docker creds --- kluctl/image_registries/generic_registry.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/kluctl/image_registries/generic_registry.py b/kluctl/image_registries/generic_registry.py index 76904fc57..5a6317985 100644 --- a/kluctl/image_registries/generic_registry.py +++ b/kluctl/image_registries/generic_registry.py @@ -68,8 +68,14 @@ def do_get_creds(self, host): try: with open(os.path.expanduser("~/.docker/config.json")) as f: config = json.load(f) + except Exception as e: + logger.warning("Failed to load docker config. Error=%s" % str(e)) + return None + + try: auth = config.get("auths", {}).get(auth_entry) if auth is None: + logger.debug("No 'auths.%s' entry in docker config" % auth_entry) return None if "username" in auth and "password" in auth: logger.debug(f"found docker config entry with username {auth['username']}") @@ -83,7 +89,9 @@ def do_get_creds(self, host): logger.debug(f"found docker config entry with username {a[0]}") return DockerCreds(username=a[0], password=a[1], tlsverify=None) cred_store = config.get("credsStore") - if cred_store is not None: + if cred_store is None: + logger.warning("No 'credsStore' found in docker config") + else: cred_exe = f"docker-credential-{cred_store}" logger.debug(f"trying credStore {cred_exe}") rc, stdout, stderr = run_helper([cred_exe, "get"], input=auth_entry, return_std=True) @@ -93,8 +101,8 @@ def do_get_creds(self, host): j = json.loads(stdout) logger.debug(f"{cred_exe} returned auth entry with username {j['Username']}") return DockerCreds(username=j["Username"], password=j["Secret"], tlsverify=None) - except: - pass + except Exception as e: + logger.warning("Exception while loading docker config/creds. Error=%s" % str(e)) return None def get_creds(self, host): From fedcabba96e86c33a7dac7cdd5068c3273c3556c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 29 Sep 2021 21:01:11 +0200 Subject: [PATCH 0221/2916] fix: Better error handling for jsonpath parsing errors --- kluctl/utils/jsonpath_utils.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/kluctl/utils/jsonpath_utils.py b/kluctl/utils/jsonpath_utils.py index b526d8013..76d9181b7 100644 --- a/kluctl/utils/jsonpath_utils.py +++ b/kluctl/utils/jsonpath_utils.py @@ -7,8 +7,10 @@ import threading from jsonpath_ng import auto_id_field, jsonpath, JSONPath +from jsonpath_ng.exceptions import JsonPathParserError from jsonpath_ng.ext import parse +from kluctl.utils.exceptions import CommandError from kluctl.utils.utils import get_tmp_base_dir logger = logging.getLogger(__name__) @@ -96,7 +98,10 @@ def parse_json_path(p) -> JSONPath: if p in json_path_cache: return json_path_cache[p] - pp = parse(p) + try: + pp = parse(p) + except JsonPathParserError as e: + raise CommandError("Invalid json path '%s'. Error=%s" % (p, str(e))) pp = json_path_cache.setdefault(p, pp) global json_path_cache_modified From 82303ef98769d932fa404655a9cd8fa6cbbf2bb9 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 29 Sep 2021 21:26:07 +0200 Subject: [PATCH 0222/2916] fix: Remove null/None objects from helm rendered output These cause yaml to be dumped later that is incompatible with kustomize --- kluctl/deployment/helm_chart.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/kluctl/deployment/helm_chart.py b/kluctl/deployment/helm_chart.py index b3e9333a2..0edfbc4ef 100644 --- a/kluctl/deployment/helm_chart.py +++ b/kluctl/deployment/helm_chart.py @@ -90,9 +90,8 @@ def render(self): rendered = rendered.decode('utf-8') parsed = yaml_load_all(rendered) + parsed = [o for o in parsed if o is not None] for o in parsed: - if o is None: - continue # "helm install" will deploy resources to the given namespace automatically, but "helm template" does not # add the necessary namespace in the rendered resources o.setdefault("metadata", {}).setdefault("namespace", namespace) From 7ddb12458aa7fadf1d60a4094515ab5a9b857be1 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 29 Sep 2021 22:08:14 +0200 Subject: [PATCH 0223/2916] fix: Remove jsonpath caching --- kluctl/utils/jsonpath_utils.py | 45 ++-------------------------------- 1 file changed, 2 insertions(+), 43 deletions(-) diff --git a/kluctl/utils/jsonpath_utils.py b/kluctl/utils/jsonpath_utils.py index 76d9181b7..bcec9a521 100644 --- a/kluctl/utils/jsonpath_utils.py +++ b/kluctl/utils/jsonpath_utils.py @@ -1,9 +1,7 @@ # Add better wildcard support to jsonpath lib -import atexit import fnmatch import logging import os -import pickle import threading from jsonpath_ng import auto_id_field, jsonpath, JSONPath @@ -52,49 +50,13 @@ def convert_list_to_json_path(p): p2 = "%s[%d]" % (p2, x) return p2 -json_path_cache = None -json_path_cache_modified = False -json_path_cache_lock = threading.Lock() - -def load_json_path_cache(): - global json_path_cache - if json_path_cache is not None: - return - with json_path_cache_lock: - path = os.path.join(get_tmp_base_dir(), "kluctl-jsonpath-cache.bin") - if not os.path.exists(path): - json_path_cache = {} - return - try: - with open(path, mode="rb") as f: - json_path_cache = pickle.load(f) - except Exception as e: - logger.exception("Excpetion while loading jsonpath cache", exc_info=e) - json_path_cache = {} - -def save_json_path_cache(): - with json_path_cache_lock: - if not json_path_cache: - return - if not json_path_cache_modified: - return - path = os.path.join(get_tmp_base_dir(), "kluctl-jsonpath-cache.bin") - try: - logger.debug("Storing %d jsonpath cache entries" % len(json_path_cache)) - with open(path + ".tmp", mode="wb") as f: - pickle.dump(json_path_cache, f) - os.rename(path + ".tmp", path) - except Exception as e: - logger.exception("Exception while storing jsonpath cache", exc_info=e) - -atexit.register(save_json_path_cache) +json_path_cache = {} +json_path_local = threading.local() def parse_json_path(p) -> JSONPath: if isinstance(p, list) or isinstance(p, tuple): p = convert_list_to_json_path(p) - load_json_path_cache() - if p in json_path_cache: return json_path_cache[p] @@ -104,7 +66,4 @@ def parse_json_path(p) -> JSONPath: raise CommandError("Invalid json path '%s'. Error=%s" % (p, str(e))) pp = json_path_cache.setdefault(p, pp) - global json_path_cache_modified - json_path_cache_modified = True - return pp From 621c2ebdc56d487d537cbb7b368cb1dd8f9f0590 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 29 Sep 2021 22:08:36 +0200 Subject: [PATCH 0224/2916] fix: Fix jsonpath parse performance by pre-creating yacc parser --- kluctl/utils/jsonpath_utils.py | 41 +++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/kluctl/utils/jsonpath_utils.py b/kluctl/utils/jsonpath_utils.py index bcec9a521..30e1bb9e6 100644 --- a/kluctl/utils/jsonpath_utils.py +++ b/kluctl/utils/jsonpath_utils.py @@ -4,12 +4,13 @@ import os import threading +import ply from jsonpath_ng import auto_id_field, jsonpath, JSONPath from jsonpath_ng.exceptions import JsonPathParserError -from jsonpath_ng.ext import parse +from jsonpath_ng.ext.parser import ExtentedJsonPathParser +from jsonpath_ng.parser import IteratorToTokenStream from kluctl.utils.exceptions import CommandError -from kluctl.utils.utils import get_tmp_base_dir logger = logging.getLogger(__name__) @@ -29,6 +30,37 @@ def ext_reified_fields(self, datum): return tuple(result) jsonpath.Fields.reified_fields = ext_reified_fields +# This class pre-creates the yacc parser to speed up things +class MyJsonPathParser(ExtentedJsonPathParser): + ''' + Dummy doc-string to avoid exceptions + ''' + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + start_symbol = 'jsonpath' + + output_directory = os.path.dirname(__file__) + try: + module_name = os.path.splitext(os.path.split(__file__)[1])[0] + except: + module_name = __name__ + + parsing_table_module = '_'.join([module_name, start_symbol, 'parsetab']) + self.parser = ply.yacc.yacc(module=self, + debug=self.debug, + tabmodule=parsing_table_module, + outputdir=output_directory, + write_tables=0, + start=start_symbol, + errorlog=logger) + + def parse_token_stream(self, token_iterator, start_symbol='jsonpath'): + assert start_symbol == "jsonpath" + return self.parser.parse(lexer = IteratorToTokenStream(token_iterator)) + + def convert_list_to_json_path(p): p2 = "" for x in p: @@ -61,7 +93,10 @@ def parse_json_path(p) -> JSONPath: return json_path_cache[p] try: - pp = parse(p) + if not hasattr(json_path_local, "parser"): + json_path_local.parser = MyJsonPathParser() + + pp = json_path_local.parser.parse(p) except JsonPathParserError as e: raise CommandError("Invalid json path '%s'. Error=%s" % (p, str(e))) pp = json_path_cache.setdefault(p, pp) From d6ebb70336f2c8d79a8037fa74705f9bda55e08a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 29 Sep 2021 22:08:50 +0200 Subject: [PATCH 0225/2916] fix: Raise CommandError on kustomize error --- kluctl/utils/kustomize.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kluctl/utils/kustomize.py b/kluctl/utils/kustomize.py index c5fd56a83..593f91f11 100644 --- a/kluctl/utils/kustomize.py +++ b/kluctl/utils/kustomize.py @@ -1,3 +1,4 @@ +from kluctl.utils.exceptions import CommandError from kluctl.utils.external_tools import get_external_tool_path from kluctl.utils.run_helper import run_helper @@ -7,7 +8,7 @@ def kustomize(args, pathToYaml, ignoreErrors=False): r, out, err = run_helper(args, cwd=pathToYaml, print_stderr=True, return_std=True) if r != 0 and not ignoreErrors: - raise Exception("kustomize failed: r=%d" % r) + raise CommandError("kustomize failed: r=%d" % r) return r, str(out.decode("utf-8")), err def kustomize_build(pathToYaml): From 10230a7fc793306aaae2e0c1e6c36d9b7f7a10c5 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 29 Sep 2021 22:28:08 +0200 Subject: [PATCH 0226/2916] fix: Ignore kube-apiserver managed fields and metadata.creationTimestamp --- kluctl/diff/managed_fields.py | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/kluctl/diff/managed_fields.py b/kluctl/diff/managed_fields.py index bb5df4125..6c6aca1eb 100644 --- a/kluctl/diff/managed_fields.py +++ b/kluctl/diff/managed_fields.py @@ -15,6 +15,15 @@ "rancher", } +ignore_managers = { + # If kube-apiserver claimed a field, then this was for a good reason, so lets not complain about it + "kube-apiserver", +} + +ignore_fields = { + ("metadata", "creationTimestamp"), +} + def check_item_match(o, kv, index): if kv[0:2] == "k:": k = kv[2:] @@ -74,7 +83,6 @@ class OverwrittenField: path: str local_value: Any remote_value: Any - value: Any field_manager: str def resolve_field_manager_conflicts(local_object, remote_object): @@ -110,6 +118,7 @@ def find_owner(owners, p): fm, tp = find_owner(local_field_owners, p) + did_overwrite = False if fm is None: # No manager found that claimed this field. If it's not existing in the remote object, it means it's a # new field so we can safely claim it. If it's present in the remote object AND has changed, it's a system @@ -117,17 +126,17 @@ def find_owner(owners, p): if remote_value is not not_found: if v != remote_value: set_dict_value(ret, p, remote_value) - overwritten.append(OverwrittenField(path=convert_list_to_json_path(p), - local_value=v if v is not not_found else None, - remote_value=remote_value, - value=remote_value, field_manager="")) + did_overwrite = True elif fm["manager"] not in overwrite_allowed_managers: to_delete.add(tp) - if v != remote_value: - overwritten.append(OverwrittenField(path=convert_list_to_json_path(p), - local_value=v if v is not not_found else None, - remote_value=remote_value if remote_value is not not_found else None, - value=None, field_manager=fm["manager"])) + did_overwrite = True + + ignore = (fm and fm["manager"] in ignore_managers) or tuple(p) in ignore_fields + if did_overwrite and v != remote_value and not ignore: + overwritten.append(OverwrittenField(path=convert_list_to_json_path(p), + local_value=v if v is not not_found else None, + remote_value=remote_value if remote_value is not not_found else None, + field_manager=fm["manager"] if fm else "")) for p in to_delete: # We do not own this field, so we should also not set it (not even to the same value to ensure we don't From db01482641905c9672b56d035d054ccd31c3996f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 30 Sep 2021 13:23:03 +0200 Subject: [PATCH 0227/2916] fix: Introduce LimittedFileSystemBytecodeCache to avoid too many files in cache --- kluctl/deployment/deployment_project.py | 13 +++--- kluctl/utils/jinja2_utils.py | 62 ++++++++++++++++++++++++- 2 files changed, 68 insertions(+), 7 deletions(-) diff --git a/kluctl/deployment/deployment_project.py b/kluctl/deployment/deployment_project.py index c9a6ab571..3141a42ca 100644 --- a/kluctl/deployment/deployment_project.py +++ b/kluctl/deployment/deployment_project.py @@ -1,14 +1,14 @@ import logging import os -from jinja2 import FileSystemLoader, StrictUndefined, FileSystemBytecodeCache +from jinja2 import FileSystemLoader, StrictUndefined from ordered_set import OrderedSet -from kluctl.utils.exceptions import CommandError -from kluctl.utils.external_args import check_required_args -from kluctl.utils.jinja2_utils import add_jinja2_filters, RelEnvironment, render_str from kluctl.utils.dict_utils import merge_dict, copy_dict, \ set_dict_default_value, get_dict_value +from kluctl.utils.exceptions import CommandError +from kluctl.utils.external_args import check_required_args +from kluctl.utils.jinja2_utils import add_jinja2_filters, RelEnvironment, LimittedFileSystemBytecodeCache from kluctl.utils.yaml_utils import yaml_load, yaml_load_file logger = logging.getLogger(__name__) @@ -20,6 +20,8 @@ def __init__(self, dir, jinja_vars, deploy_args, sealed_secrets_dir, if not os.path.exists(dir): raise CommandError("%s does not exist" % dir) + self.jinja2_cache = LimittedFileSystemBytecodeCache(max_cache_files=10000) + self.dir = dir self.jinja_vars = copy_dict(jinja_vars) self.deploy_args = deploy_args @@ -42,10 +44,9 @@ def build_jinja2_env(self, jinja_vars): dirs = [] for d, _ in self.get_parents(): dirs.append(d.dir) - cache = FileSystemBytecodeCache() environment = RelEnvironment(loader=FileSystemLoader(dirs), undefined=StrictUndefined, cache_size=10000, - bytecode_cache=cache, auto_reload=False) + bytecode_cache=self.jinja2_cache, auto_reload=False) merge_dict(environment.globals, jinja_vars, clone=False) add_jinja2_filters(environment) return environment diff --git a/kluctl/utils/jinja2_utils.py b/kluctl/utils/jinja2_utils.py index 4ecb8f0cf..993008b47 100644 --- a/kluctl/utils/jinja2_utils.py +++ b/kluctl/utils/jinja2_utils.py @@ -1,13 +1,17 @@ import base64 +import fnmatch import hashlib import json import logging import os import sys import traceback +from datetime import datetime import jinja2 -from jinja2 import Environment, StrictUndefined, FileSystemLoader, TemplateNotFound, TemplateError +from jinja2 import Environment, StrictUndefined, FileSystemLoader, TemplateNotFound, TemplateError, \ + FileSystemBytecodeCache +from jinja2.bccache import Bucket from kluctl.utils.dict_utils import merge_dict, get_dict_value from kluctl.utils.yaml_utils import yaml_dump, yaml_load @@ -27,6 +31,62 @@ def join_path(self, template, parent): p = os.path.normpath(p) return p.replace('\\', '/') +class LimittedFileSystemBytecodeCache(FileSystemBytecodeCache): + def __init__(self, *args, max_cache_files, **kwargs): + super().__init__(*args, **kwargs) + self.max_cache_files = max_cache_files + self.clear_old_entries() + self.did_touch = set() + + def touch_loaded_marker(self, bucket): + filename = super()._get_cache_filename(bucket) + ".loaded" + if filename in self.did_touch: + return + with open(filename + ".tmp", mode="wt") as f: + f.write(str(datetime.utcnow())) + try: + os.rename(filename + ".tmp", filename) + except: + pass + self.did_touch.add(filename) + + def load_bytecode(self, bucket: Bucket) -> None: + ret = super().load_bytecode(bucket) + if ret is not None: + self.touch_loaded_marker(bucket) + return ret + + def dump_bytecode(self, bucket: Bucket) -> None: + # thread/multi-process safe version of super().dump_bytecode() + + filename = self._get_cache_filename(bucket) + with open(filename + ".tmp", "wb") as f: + bucket.write_bytecode(f) + try: + os.rename(filename + ".tmp", filename) + except: + pass + self.touch_loaded_marker(bucket) + + def clear_old_entries(self): + files = fnmatch.filter(os.listdir(self.directory), self.pattern % ("*",)) + if len(files) <= self.max_cache_files: + return + times = {} + for f in files: + try: + with open(os.path.join(self.directory, f + ".loaded"), mode="rt") as f2: + times[f] = datetime.fromisoformat(f2.read()) + except: + times[f] = datetime.min + files.sort(key=lambda f: times[f], reverse=True) + for f in files[self.max_cache_files:]: + try: + os.remove(os.path.join(self.directory, f)) + os.remove(os.path.join(self.directory, f + ".loaded")) + except: + pass + def b64encode(string): return base64.b64encode(string.encode()).decode() From 52874f59db92aab40217de3497d5c9ed46d61891 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 30 Sep 2021 13:23:03 +0200 Subject: [PATCH 0228/2916] fix: Introduce KluctlBytecodeCache to fix excess cache usage --- kluctl/deployment/deployment_project.py | 7 +- kluctl/utils/jinja2_cache.py | 162 ++++++++++++++++++++++++ kluctl/utils/jinja2_utils.py | 64 +--------- 3 files changed, 168 insertions(+), 65 deletions(-) create mode 100644 kluctl/utils/jinja2_cache.py diff --git a/kluctl/deployment/deployment_project.py b/kluctl/deployment/deployment_project.py index 3141a42ca..6852f084a 100644 --- a/kluctl/deployment/deployment_project.py +++ b/kluctl/deployment/deployment_project.py @@ -1,14 +1,15 @@ import logging import os -from jinja2 import FileSystemLoader, StrictUndefined +from jinja2 import StrictUndefined, FileSystemLoader from ordered_set import OrderedSet from kluctl.utils.dict_utils import merge_dict, copy_dict, \ set_dict_default_value, get_dict_value from kluctl.utils.exceptions import CommandError from kluctl.utils.external_args import check_required_args -from kluctl.utils.jinja2_utils import add_jinja2_filters, RelEnvironment, LimittedFileSystemBytecodeCache +from kluctl.utils.jinja2_cache import KluctlBytecodeCache +from kluctl.utils.jinja2_utils import add_jinja2_filters, RelEnvironment from kluctl.utils.yaml_utils import yaml_load, yaml_load_file logger = logging.getLogger(__name__) @@ -20,7 +21,7 @@ def __init__(self, dir, jinja_vars, deploy_args, sealed_secrets_dir, if not os.path.exists(dir): raise CommandError("%s does not exist" % dir) - self.jinja2_cache = LimittedFileSystemBytecodeCache(max_cache_files=10000) + self.jinja2_cache = KluctlBytecodeCache(max_cache_files=10000) self.dir = dir self.jinja_vars = copy_dict(jinja_vars) diff --git a/kluctl/utils/jinja2_cache.py b/kluctl/utils/jinja2_cache.py new file mode 100644 index 000000000..a571b28e1 --- /dev/null +++ b/kluctl/utils/jinja2_cache.py @@ -0,0 +1,162 @@ +import errno +import fnmatch +import os +import stat +import typing +from datetime import datetime +from types import CodeType + +from jinja2 import BytecodeCache +from jinja2.bccache import Bucket + +from kluctl.utils.utils import get_tmp_base_dir + + +""" +A bytecode cache that is able to share bytecode between different directories. It does this by removing filenames +from dumped code and then later re-adding them after loading. + +This cache is also thread and multi-process safe. +""" +class KluctlBytecodeCache(BytecodeCache): + def __init__(self, max_cache_files): + self.cache_dir = self._get_cache_dir() + self.max_cache_files = max_cache_files + self.did_touch = set() + self.clear_old_entries() + + def _get_cache_dir(self) -> str: + def _unsafe_dir() -> "te.NoReturn": + raise RuntimeError( + "Cannot determine safe temp directory. You " + "need to explicitly provide one." + ) + + tmpdir = os.path.join(get_tmp_base_dir(), "jinja2-cache") + + # On windows the temporary directory is used specific unless + # explicitly forced otherwise. We can just use that. + if os.name == "nt": + os.makedirs(tmpdir, exist_ok=True) + return tmpdir + if not hasattr(os, "getuid"): + _unsafe_dir() + + tmpdir = os.path.join(tmpdir, str(os.getuid())) + + try: + os.makedirs(tmpdir, exist_ok=True, mode=stat.S_IRWXU) + except OSError as e: + if e.errno != errno.EEXIST: + raise + try: + os.chmod(tmpdir, stat.S_IRWXU) + actual_dir_stat = os.lstat(tmpdir) + if ( + actual_dir_stat.st_uid != os.getuid() + or not stat.S_ISDIR(actual_dir_stat.st_mode) + or stat.S_IMODE(actual_dir_stat.st_mode) != stat.S_IRWXU + ): + _unsafe_dir() + except OSError as e: + if e.errno != errno.EEXIST: + raise + + actual_dir_stat = os.lstat(tmpdir) + if ( + actual_dir_stat.st_uid != os.getuid() + or not stat.S_ISDIR(actual_dir_stat.st_mode) + or stat.S_IMODE(actual_dir_stat.st_mode) != stat.S_IRWXU + ): + _unsafe_dir() + + return tmpdir + + def get_cache_key(self, name: str, filename: typing.Optional[typing.Union[str]] = None): + return (name, filename) + + def _get_cache_filename(self, bucket: Bucket) -> str: + return os.path.join(self.cache_dir, bucket.checksum[:2], bucket.checksum) + ".cache" + + def touch_loaded_marker(self, bucket): + filename = self._get_cache_filename(bucket) + ".loaded" + if filename in self.did_touch: + return + with open(filename + ".tmp", mode="wt") as f: + f.write(str(datetime.utcnow())) + try: + os.rename(filename + ".tmp", filename) + except: + pass + self.did_touch.add(filename) + + def replace_code_filenames(self, code, old, new): + co_filename = code.co_filename + co_consts = [] + if co_filename == old: + co_filename = new + else: + asd = 123 + + for c in code.co_consts: + if isinstance(c, CodeType): + co_consts.append(self.replace_code_filenames(c, old, new)) + else: + co_consts.append(c) + + return code.replace(co_filename=co_filename, co_consts=tuple(co_consts)) + + def load_bytecode(self, bucket: Bucket) -> None: + filename = self._get_cache_filename(bucket) + + if os.path.exists(filename): + with open(filename, "rb") as f: + bucket.load_bytecode(f) + if bucket.code is not None: + bucket.code = self.replace_code_filenames(bucket.code, "", bucket.key[1]) + self.touch_loaded_marker(bucket) + + def dump_bytecode(self, bucket: Bucket) -> None: + # thread/multi-process safe version of super().dump_bytecode() + + filename = self._get_cache_filename(bucket) + os.makedirs(os.path.dirname(filename), exist_ok=True) + + with open(filename + ".tmp", "wb") as f: + orig_code = bucket.code + bucket.code = self.replace_code_filenames(bucket.code, bucket.key[1], "") + bucket.write_bytecode(f) + bucket.code = orig_code + try: + os.rename(filename + ".tmp", filename) + except: + pass + self.touch_loaded_marker(bucket) + + def clear_old_entries(self): + files = [] + for d in os.listdir(self.cache_dir): + path = os.path.join(self.cache_dir, d) + if not os.path.isdir(path): + continue + for n in os.listdir(path): + if not fnmatch.fnmatch(n, "*.cache"): + continue + files.append(os.path.join(path, n)) + + if len(files) <= self.max_cache_files: + return + times = {} + for f in files: + try: + with open(f + ".loaded", mode="rt") as f2: + times[f] = datetime.fromisoformat(f2.read()) + except: + times[f] = datetime.min + files.sort(key=lambda f: times[f], reverse=True) + for f in files[self.max_cache_files:]: + try: + os.remove(f) + os.remove(f + ".loaded") + except: + pass diff --git a/kluctl/utils/jinja2_utils.py b/kluctl/utils/jinja2_utils.py index 993008b47..0ebb56cb2 100644 --- a/kluctl/utils/jinja2_utils.py +++ b/kluctl/utils/jinja2_utils.py @@ -1,24 +1,20 @@ import base64 -import fnmatch import hashlib import json import logging import os import sys import traceback -from datetime import datetime import jinja2 -from jinja2 import Environment, StrictUndefined, FileSystemLoader, TemplateNotFound, TemplateError, \ - FileSystemBytecodeCache -from jinja2.bccache import Bucket +from jinja2 import Environment, StrictUndefined, FileSystemLoader, TemplateNotFound, TemplateError from kluctl.utils.dict_utils import merge_dict, get_dict_value from kluctl.utils.yaml_utils import yaml_dump, yaml_load logger = logging.getLogger(__name__) -# This Jinja2 environment allows to load templaces relative to the parent template. This means that for example +# This Jinja2 environment allows to load templates relative to the parent template. This means that for example # '{% include "file.yml" %}' will try to include the template from a ./file.yml class RelEnvironment(Environment): def __init__(self, *args, **kwargs): @@ -31,62 +27,6 @@ def join_path(self, template, parent): p = os.path.normpath(p) return p.replace('\\', '/') -class LimittedFileSystemBytecodeCache(FileSystemBytecodeCache): - def __init__(self, *args, max_cache_files, **kwargs): - super().__init__(*args, **kwargs) - self.max_cache_files = max_cache_files - self.clear_old_entries() - self.did_touch = set() - - def touch_loaded_marker(self, bucket): - filename = super()._get_cache_filename(bucket) + ".loaded" - if filename in self.did_touch: - return - with open(filename + ".tmp", mode="wt") as f: - f.write(str(datetime.utcnow())) - try: - os.rename(filename + ".tmp", filename) - except: - pass - self.did_touch.add(filename) - - def load_bytecode(self, bucket: Bucket) -> None: - ret = super().load_bytecode(bucket) - if ret is not None: - self.touch_loaded_marker(bucket) - return ret - - def dump_bytecode(self, bucket: Bucket) -> None: - # thread/multi-process safe version of super().dump_bytecode() - - filename = self._get_cache_filename(bucket) - with open(filename + ".tmp", "wb") as f: - bucket.write_bytecode(f) - try: - os.rename(filename + ".tmp", filename) - except: - pass - self.touch_loaded_marker(bucket) - - def clear_old_entries(self): - files = fnmatch.filter(os.listdir(self.directory), self.pattern % ("*",)) - if len(files) <= self.max_cache_files: - return - times = {} - for f in files: - try: - with open(os.path.join(self.directory, f + ".loaded"), mode="rt") as f2: - times[f] = datetime.fromisoformat(f2.read()) - except: - times[f] = datetime.min - files.sort(key=lambda f: times[f], reverse=True) - for f in files[self.max_cache_files:]: - try: - os.remove(os.path.join(self.directory, f)) - os.remove(os.path.join(self.directory, f + ".loaded")) - except: - pass - def b64encode(string): return base64.b64encode(string.encode()).decode() From 80c62c4cb34b9f628ef7a2e6c52a1b5ced83f511 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 1 Oct 2021 13:59:53 +0200 Subject: [PATCH 0229/2916] refactor: Rename add_api_error/add_api_warnings to add_error/add_warnings --- kluctl/deployment/apply_util.py | 6 ++-- kluctl/deployment/deployment_collection.py | 40 +++++++++++----------- kluctl/deployment/hooks_util.py | 2 +- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/kluctl/deployment/apply_util.py b/kluctl/deployment/apply_util.py index 7738d9d57..229dde705 100644 --- a/kluctl/deployment/apply_util.py +++ b/kluctl/deployment/apply_util.py @@ -32,14 +32,14 @@ def handle_result(self, applied_object, patch_warnings): with self.mutex: ref = get_object_ref(applied_object) self.applied_objects[ref] = applied_object - self.deployment_collection.add_api_warnings(ref, patch_warnings) + self.deployment_collection.add_warnings(ref, patch_warnings) def handle_error(self, ref, error): with self.mutex: self.error_refs[ref] = error if self.abort_on_error: self.abort_signal = True - self.deployment_collection.add_api_error(ref, error) + self.deployment_collection.add_error(ref, error) def had_error(self, ref): with self.mutex: @@ -60,7 +60,7 @@ def apply_object(self, x, replaced): for ow in overwritten: warnings.append("Field '%s' is now owned by field manager '%s'. " "It is NOT updated to the desired value '%s'!" % (ow.path, ow.field_manager, ow.local_value)) - self.deployment_collection.add_api_warnings(ref, warnings) + self.deployment_collection.add_warnings(ref, warnings) if self.dry_run and replaced and get_object_ref(x) in self.deployment_collection.remote_objects: # Let's simulate that this object was deleted in dry-run mode. If we'd actually try a dry-run apply with diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index 51644d53b..f29c4abe5 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -45,8 +45,8 @@ def __init__(self, project, images, inclusion, tmpdir, for_seal): self.remote_objects = {} - self.api_errors = set() - self.api_warnings = set() + self.errors = set() + self.warnings = set() def _collect_deployments(self, project): ret = [] @@ -123,7 +123,7 @@ def update_remote_objects(self, k8s_cluster): r = k8s_cluster.get_objects_by_object_refs(refs) for o, w in r: self.remote_objects[get_object_ref(o)] = o - self.add_api_warnings(get_object_ref(o), w) + self.add_warnings(get_object_ref(o), w) def forget_remote_objects(self, refs): for ref in refs: @@ -146,7 +146,7 @@ def deploy(self, k8s_cluster, force_apply, replace_on_error, force_replace_on_er False, abort_on_error) new_objects, changed_objects = self.do_diff(k8s_cluster, applied_objects, False, False, False, False) return DeployDiffResult(new_objects=new_objects, changed_objects=changed_objects, - errors=list(self.api_errors), warnings=list(self.api_warnings)) + errors=list(self.errors), warnings=list(self.warnings)) def diff(self, k8s_cluster, force_apply, replace_on_error, force_replace_on_error, ignore_tags, ignore_labels, ignore_annotations, ignore_order): self.clear_errors_and_warnings() @@ -154,7 +154,7 @@ def diff(self, k8s_cluster, force_apply, replace_on_error, force_replace_on_erro True, False) new_objects, changed_objects = self.do_diff(k8s_cluster, applied_objects, ignore_tags, ignore_labels, ignore_annotations, ignore_order) return DeployDiffResult(new_objects=new_objects, changed_objects=changed_objects, - errors=list(self.api_errors), warnings=list(self.api_warnings)) + errors=list(self.errors), warnings=list(self.warnings)) def poke_images(self, k8s_cluster): self.clear_errors_and_warnings() @@ -184,13 +184,13 @@ def do_poke_image(containers_and_images, o): try: r = k8s_cluster.get_preferred_resource(None, kind) except ResourceNotFoundError as e: - self.add_api_error(None, k8s_cluster.get_status_message(e)) + self.add_error(None, k8s_cluster.get_status_message(e)) continue ref = ObjectRef(r.group_version, kind=r.kind, name=name, namespace=fi.get("namespace")) local_object = all_objects.get(ref) if local_object is None: - self.add_api_error(ref, "object not found while trying to associate image with deployed object") + self.add_error(ref, "object not found while trying to associate image with deployed object") continue containers_and_images.setdefault(ref, []).append((fi["container"], fi["resultImage"])) @@ -206,7 +206,7 @@ def do_poke_image(containers_and_images, o): new_objects, changed_objects = self.do_diff(k8s_cluster, applied_objects, False, False, False, False) return DeployDiffResult(new_objects=new_objects, changed_objects=changed_objects, - errors=list(self.api_errors), warnings=list(self.api_warnings)) + errors=list(self.errors), warnings=list(self.warnings)) def downscale(self, k8s_cluster): self.clear_errors_and_warnings() @@ -224,15 +224,15 @@ def downscale(self, k8s_cluster): new_objects, changed_objects = self.do_diff(k8s_cluster, applied_objects, False, False, False, False) return DeployDiffResult(new_objects=new_objects, changed_objects=changed_objects, - errors=list(self.api_errors), warnings=list(self.api_warnings)) + errors=list(self.errors), warnings=list(self.warnings)) def validate(self): self.clear_errors_and_warnings() result = ValidateResult() - for w in self.api_warnings: + for w in self.warnings: result.warnings.append(ValidateResultItem(ref=w.ref, reason=w.reason, message=w.message)) - for e in self.api_errors: + for e in self.errors: result.errors.append(ValidateResultItem(ref=e.ref, reason=e.reason, message=e.message)) for d in self.deployments: @@ -340,7 +340,7 @@ def do_finish_futures(self, futures): for ref, f in futures: e = f.exception() if e is not None: - self.add_api_error(ref, str(e)) + self.add_error(ref, str(e)) continue o = f.result() @@ -362,25 +362,25 @@ def find_rendered_images(self): ret.setdefault(get_object_ref(o), []).append(image) return ret - def add_api_warnings(self, ref, warnings): + def add_warnings(self, ref, warnings): for w in warnings: item = DeployErrorItem(ref=ref, reason="api", message=w) - if item not in self.api_warnings: + if item not in self.warnings: logger.warning("%s: Warning while performing api call. message=%s" % (get_long_object_name_from_ref(ref), w)) - self.api_warnings.add(item) + self.warnings.add(item) - def add_api_error(self, ref, error): + def add_error(self, ref, error): ref_str = "" if ref is not None: ref_str = "%s: " % get_long_object_name_from_ref(ref) item = DeployErrorItem(ref=ref, reason="api", message=error) - if item not in self.api_errors: + if item not in self.errors: logger.error("%sError while performing api call. message=%s" % (ref_str, error)) - self.api_errors.add(item) + self.errors.add(item) def clear_errors_and_warnings(self): - self.api_errors = set() - self.api_warnings = set() + self.errors = set() + self.warnings = set() def do_diff_object(old_object, new_object, ignore_order): if old_object == new_object: diff --git a/kluctl/deployment/hooks_util.py b/kluctl/deployment/hooks_util.py index f27e491d4..1c6ec5653 100644 --- a/kluctl/deployment/hooks_util.py +++ b/kluctl/deployment/hooks_util.py @@ -109,7 +109,7 @@ def get_list(path): helm_hooks = get_list('metadata.annotations."helm.sh/hook"') for h in helm_hooks: if h not in supported_helm_hooks: - self.apply_util.deployment_collection.add_api_warnings(get_object_ref(o), ["Unsupported helm.sh/hook '%s'" % h]) + self.apply_util.deployment_collection.add_warnings(get_object_ref(o), ["Unsupported helm.sh/hook '%s'" % h]) if "pre-install" in helm_hooks: hooks.append("pre-deploy-initial") From 765dba2485bb8632ac7a48c85b5384ca14d6f268 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 1 Oct 2021 14:01:37 +0200 Subject: [PATCH 0230/2916] refactor: Remove reason field from DeployErrorItem --- kluctl/deployment/deployment_collection.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index f29c4abe5..c1840a37f 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -24,7 +24,6 @@ @dataclasses.dataclass(frozen=True, eq=True) class DeployErrorItem: ref: ObjectRef - reason: str message: str @dataclasses.dataclass @@ -231,9 +230,9 @@ def validate(self): result = ValidateResult() for w in self.warnings: - result.warnings.append(ValidateResultItem(ref=w.ref, reason=w.reason, message=w.message)) + result.warnings.append(ValidateResultItem(ref=w.ref, reason="api", message=w.message)) for e in self.errors: - result.errors.append(ValidateResultItem(ref=e.ref, reason=e.reason, message=e.message)) + result.errors.append(ValidateResultItem(ref=e.ref, reason="api", message=e.message)) for d in self.deployments: if not d.check_inclusion_for_deploy(): @@ -364,7 +363,7 @@ def find_rendered_images(self): def add_warnings(self, ref, warnings): for w in warnings: - item = DeployErrorItem(ref=ref, reason="api", message=w) + item = DeployErrorItem(ref=ref, message=w) if item not in self.warnings: logger.warning("%s: Warning while performing api call. message=%s" % (get_long_object_name_from_ref(ref), w)) self.warnings.add(item) @@ -373,7 +372,7 @@ def add_error(self, ref, error): ref_str = "" if ref is not None: ref_str = "%s: " % get_long_object_name_from_ref(ref) - item = DeployErrorItem(ref=ref, reason="api", message=error) + item = DeployErrorItem(ref=ref, message=error) if item not in self.errors: logger.error("%sError while performing api call. message=%s" % (ref_str, error)) self.errors.add(item) From 70e8d14762f5a4ced6218bd7152f9e960c719152 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 1 Oct 2021 14:25:50 +0200 Subject: [PATCH 0231/2916] refactor: Rename DeployDiffResult to CommandResult --- kluctl/cli/commands.py | 22 ++++++++++----------- kluctl/cli/utils.py | 10 +++++----- kluctl/deployment/deployment_collection.py | 23 +++++++++------------- 3 files changed, 25 insertions(+), 30 deletions(-) diff --git a/kluctl/cli/commands.py b/kluctl/cli/commands.py index 3fbc6b4a1..b66179253 100644 --- a/kluctl/cli/commands.py +++ b/kluctl/cli/commands.py @@ -7,7 +7,7 @@ import click from kluctl import get_kluctl_package_dir -from kluctl.cli.utils import output_diff_result, build_seen_images, output_yaml_result, \ +from kluctl.cli.utils import output_command_result, build_seen_images, output_yaml_result, \ output_validate_result, project_command_context from kluctl.kluctl_project.kluctl_project import load_kluctl_project_from_args from kluctl.utils.dict_utils import get_dict_value @@ -42,11 +42,11 @@ def deploy_command2(obj, kwargs, cmd_ctx): if not kwargs["yes"] and not kwargs["dry_run"]: click.confirm("Do you really want to deploy to the context/cluster %s?" % cmd_ctx.k8s_cluster.context, err=True, abort=True) - diff_result = cmd_ctx.deployment_collection.deploy(cmd_ctx.k8s_cluster, kwargs["force_apply"], + result = cmd_ctx.deployment_collection.deploy(cmd_ctx.k8s_cluster, kwargs["force_apply"], kwargs["replace_on_error"], kwargs["force_replace_on_error"], kwargs["abort_on_error"]) deleted_objects = cmd_ctx.deployment_collection.find_prune_objects(cmd_ctx.k8s_cluster) - output_diff_result(kwargs["output"], cmd_ctx.deployment_collection, diff_result, deleted_objects) - if diff_result.errors: + output_command_result(kwargs["output"], cmd_ctx.deployment_collection, result, deleted_objects) + if result.errors: sys.exit(1) def diff_command(obj, kwargs): @@ -55,7 +55,7 @@ def diff_command(obj, kwargs): cmd_ctx.k8s_cluster, kwargs["force_apply"], kwargs["replace_on_error"], kwargs["force_replace_on_error"], kwargs["ignore_tags"], kwargs["ignore_labels"], kwargs["ignore_annotations"], kwargs["ignore_order"]) deleted_objects = cmd_ctx.deployment_collection.find_prune_objects(cmd_ctx.k8s_cluster) - output_diff_result(kwargs["output"], cmd_ctx.deployment_collection, result, deleted_objects) + output_command_result(kwargs["output"], cmd_ctx.deployment_collection, result, deleted_objects) sys.exit(1 if result.errors else 0) def confirmed_delete_objects(k8s_cluster, objects, kwargs): @@ -89,10 +89,10 @@ def poke_images_command(obj, kwargs): if not kwargs["yes"] and not kwargs["dry_run"]: click.confirm("Do you really want to poke images to the context/cluster %s?" % cmd_ctx.k8s_cluster.context, err=True, abort=True) - diff_result = cmd_ctx.deployment_collection.poke_images(cmd_ctx.k8s_cluster) + result = cmd_ctx.deployment_collection.poke_images(cmd_ctx.k8s_cluster) deleted_objects = cmd_ctx.deployment_collection.find_prune_objects(cmd_ctx.k8s_cluster) - output_diff_result(kwargs["output"], cmd_ctx.deployment_collection, diff_result, deleted_objects) - if diff_result.errors: + output_command_result(kwargs["output"], cmd_ctx.deployment_collection, result, deleted_objects) + if result.errors: sys.exit(1) def downscale_command(obj, kwargs): @@ -100,10 +100,10 @@ def downscale_command(obj, kwargs): if not kwargs["yes"] and not kwargs["dry_run"]: click.confirm("Do you really want to downscale on context/cluster %s?" % cmd_ctx.k8s_cluster.context, err=True, abort=True) - diff_result = cmd_ctx.deployment_collection.downscale(cmd_ctx.k8s_cluster) + result = cmd_ctx.deployment_collection.downscale(cmd_ctx.k8s_cluster) deleted_objects = cmd_ctx.deployment_collection.find_prune_objects(cmd_ctx.k8s_cluster) - output_diff_result(kwargs["output"], cmd_ctx.deployment_collection, diff_result, deleted_objects) - if diff_result.errors: + output_command_result(kwargs["output"], cmd_ctx.deployment_collection, result, deleted_objects) + if result.errors: sys.exit(1) def validate_command(obj, kwargs): diff --git a/kluctl/cli/utils.py b/kluctl/cli/utils.py index 300048988..8c570af77 100644 --- a/kluctl/cli/utils.py +++ b/kluctl/cli/utils.py @@ -163,14 +163,14 @@ def project_target_command_context(kwargs, kluctl_project, target, deployment=d, deployment_collection=c, images=images) yield ctx -def build_diff_result(c, deploy_diff_result, deleted_objects, format): +def build_command_result(c, command_result, deleted_objects, format): if format == "diff": - return format_diff(deploy_diff_result.new_objects, deploy_diff_result.changed_objects, deleted_objects) + return format_diff(command_result.new_objects, command_result.changed_objects, deleted_objects) elif format != "yaml": raise CommandError(f"Invalid format: {format}") result = { - "diff": changes_to_yaml(deploy_diff_result.new_objects, deploy_diff_result.changed_objects, deploy_diff_result.errors, deploy_diff_result.warnings), + "diff": changes_to_yaml(command_result.new_objects, command_result.changed_objects, command_result.errors, command_result.warnings), "images": build_seen_images(c, True), } if deleted_objects is not None: @@ -203,7 +203,7 @@ def build_validate_result(result, format): else: raise CommandError(f"Invalid format: {format}") -def output_diff_result(output, c, deploy_diff_result, deleted_objects): +def output_command_result(output, c, command_result, deleted_objects): if not output: output = ["diff"] for o in output: @@ -212,7 +212,7 @@ def output_diff_result(output, c, deploy_diff_result, deleted_objects): path = None if len(s) > 1: path = s[1] - s = build_diff_result(c, deploy_diff_result, deleted_objects, format) + s = build_command_result(c, command_result, deleted_objects, format) output_result(path, s) def output_validate_result(output, result): diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index c1840a37f..c6f2ddcc8 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -27,7 +27,7 @@ class DeployErrorItem: message: str @dataclasses.dataclass -class DeployDiffResult: +class CommandResult: new_objects: list changed_objects: list errors: list @@ -144,16 +144,16 @@ def deploy(self, k8s_cluster, force_apply, replace_on_error, force_replace_on_er applied_objects = self.do_apply(k8s_cluster, force_apply, replace_on_error, force_replace_on_error, False, abort_on_error) new_objects, changed_objects = self.do_diff(k8s_cluster, applied_objects, False, False, False, False) - return DeployDiffResult(new_objects=new_objects, changed_objects=changed_objects, - errors=list(self.errors), warnings=list(self.warnings)) + return CommandResult(new_objects=new_objects, changed_objects=changed_objects, + errors=list(self.errors), warnings=list(self.warnings)) def diff(self, k8s_cluster, force_apply, replace_on_error, force_replace_on_error, ignore_tags, ignore_labels, ignore_annotations, ignore_order): self.clear_errors_and_warnings() applied_objects = self.do_apply(k8s_cluster, force_apply, replace_on_error, force_replace_on_error, True, False) new_objects, changed_objects = self.do_diff(k8s_cluster, applied_objects, ignore_tags, ignore_labels, ignore_annotations, ignore_order) - return DeployDiffResult(new_objects=new_objects, changed_objects=changed_objects, - errors=list(self.errors), warnings=list(self.warnings)) + return CommandResult(new_objects=new_objects, changed_objects=changed_objects, + errors=list(self.errors), warnings=list(self.warnings)) def poke_images(self, k8s_cluster): self.clear_errors_and_warnings() @@ -204,8 +204,8 @@ def do_poke_image(containers_and_images, o): applied_objects = self.do_finish_futures(futures) new_objects, changed_objects = self.do_diff(k8s_cluster, applied_objects, False, False, False, False) - return DeployDiffResult(new_objects=new_objects, changed_objects=changed_objects, - errors=list(self.errors), warnings=list(self.warnings)) + return CommandResult(new_objects=new_objects, changed_objects=changed_objects, + errors=list(self.errors), warnings=list(self.warnings)) def downscale(self, k8s_cluster): self.clear_errors_and_warnings() @@ -222,8 +222,8 @@ def downscale(self, k8s_cluster): applied_objects = self.do_finish_futures(futures) new_objects, changed_objects = self.do_diff(k8s_cluster, applied_objects, False, False, False, False) - return DeployDiffResult(new_objects=new_objects, changed_objects=changed_objects, - errors=list(self.errors), warnings=list(self.warnings)) + return CommandResult(new_objects=new_objects, changed_objects=changed_objects, + errors=list(self.errors), warnings=list(self.warnings)) def validate(self): self.clear_errors_and_warnings() @@ -365,16 +365,11 @@ def add_warnings(self, ref, warnings): for w in warnings: item = DeployErrorItem(ref=ref, message=w) if item not in self.warnings: - logger.warning("%s: Warning while performing api call. message=%s" % (get_long_object_name_from_ref(ref), w)) self.warnings.add(item) def add_error(self, ref, error): - ref_str = "" - if ref is not None: - ref_str = "%s: " % get_long_object_name_from_ref(ref) item = DeployErrorItem(ref=ref, message=error) if item not in self.errors: - logger.error("%sError while performing api call. message=%s" % (ref_str, error)) self.errors.add(item) def clear_errors_and_warnings(self): From 5f34591bd0b7d7b913e9a6869c52e802d8c67558 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 1 Oct 2021 14:30:49 +0200 Subject: [PATCH 0232/2916] refactor: Rename find_prune_objects to find_orphan_objects --- kluctl/cli/commands.py | 10 +++++----- kluctl/deployment/deployment_collection.py | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/kluctl/cli/commands.py b/kluctl/cli/commands.py index b66179253..809c57748 100644 --- a/kluctl/cli/commands.py +++ b/kluctl/cli/commands.py @@ -44,7 +44,7 @@ def deploy_command2(obj, kwargs, cmd_ctx): result = cmd_ctx.deployment_collection.deploy(cmd_ctx.k8s_cluster, kwargs["force_apply"], kwargs["replace_on_error"], kwargs["force_replace_on_error"], kwargs["abort_on_error"]) - deleted_objects = cmd_ctx.deployment_collection.find_prune_objects(cmd_ctx.k8s_cluster) + deleted_objects = cmd_ctx.deployment_collection.find_orphan_objects(cmd_ctx.k8s_cluster) output_command_result(kwargs["output"], cmd_ctx.deployment_collection, result, deleted_objects) if result.errors: sys.exit(1) @@ -54,7 +54,7 @@ def diff_command(obj, kwargs): result = cmd_ctx.deployment_collection.diff( cmd_ctx.k8s_cluster, kwargs["force_apply"], kwargs["replace_on_error"], kwargs["force_replace_on_error"], kwargs["ignore_tags"], kwargs["ignore_labels"], kwargs["ignore_annotations"], kwargs["ignore_order"]) - deleted_objects = cmd_ctx.deployment_collection.find_prune_objects(cmd_ctx.k8s_cluster) + deleted_objects = cmd_ctx.deployment_collection.find_orphan_objects(cmd_ctx.k8s_cluster) output_command_result(kwargs["output"], cmd_ctx.deployment_collection, result, deleted_objects) sys.exit(1 if result.errors else 0) @@ -81,7 +81,7 @@ def prune_command(obj, kwargs): prune_command2(obj, kwargs, cmd_ctx) def prune_command2(obj, kwargs, cmd_ctx): - objects = cmd_ctx.deployment_collection.find_prune_objects(cmd_ctx.k8s_cluster) + objects = cmd_ctx.deployment_collection.find_orphan_objects(cmd_ctx.k8s_cluster) confirmed_delete_objects(cmd_ctx.k8s_cluster, objects, kwargs) def poke_images_command(obj, kwargs): @@ -90,7 +90,7 @@ def poke_images_command(obj, kwargs): click.confirm("Do you really want to poke images to the context/cluster %s?" % cmd_ctx.k8s_cluster.context, err=True, abort=True) result = cmd_ctx.deployment_collection.poke_images(cmd_ctx.k8s_cluster) - deleted_objects = cmd_ctx.deployment_collection.find_prune_objects(cmd_ctx.k8s_cluster) + deleted_objects = cmd_ctx.deployment_collection.find_orphan_objects(cmd_ctx.k8s_cluster) output_command_result(kwargs["output"], cmd_ctx.deployment_collection, result, deleted_objects) if result.errors: sys.exit(1) @@ -101,7 +101,7 @@ def downscale_command(obj, kwargs): click.confirm("Do you really want to downscale on context/cluster %s?" % cmd_ctx.k8s_cluster.context, err=True, abort=True) result = cmd_ctx.deployment_collection.downscale(cmd_ctx.k8s_cluster) - deleted_objects = cmd_ctx.deployment_collection.find_prune_objects(cmd_ctx.k8s_cluster) + deleted_objects = cmd_ctx.deployment_collection.find_orphan_objects(cmd_ctx.k8s_cluster) output_command_result(kwargs["output"], cmd_ctx.deployment_collection, result, deleted_objects) if result.errors: sys.exit(1) diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index c6f2ddcc8..51bd48186 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -254,9 +254,9 @@ def find_delete_objects(self, k8s_cluster): labels = self.project.get_delete_by_labels() return find_objects_for_delete(k8s_cluster, labels, self.inclusion, []) - def find_prune_objects(self, k8s_cluster): + def find_orphan_objects(self, k8s_cluster): self.clear_errors_and_warnings() - logger.info("Searching objects not found in local objects") + logger.info("Searching for orphan objects") labels = self.project.get_delete_by_labels() excluded_objects = list(self.local_objects_by_ref().keys()) return find_objects_for_delete(k8s_cluster, labels, self.inclusion, excluded_objects) From df86fa9164a6684badf8567249030bea5fdf6515 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 4 Oct 2021 13:14:16 +0200 Subject: [PATCH 0233/2916] feat: Make orphaned objects part of CommandResult --- kluctl/cli/commands.py | 9 +++------ kluctl/cli/utils.py | 15 ++++++++------- kluctl/deployment/deployment_collection.py | 11 +++++++++-- kluctl/diff/k8s_diff.py | 6 +----- kluctl/diff/k8s_pretty_diff.py | 8 ++++---- 5 files changed, 25 insertions(+), 24 deletions(-) diff --git a/kluctl/cli/commands.py b/kluctl/cli/commands.py index 809c57748..a188d94a1 100644 --- a/kluctl/cli/commands.py +++ b/kluctl/cli/commands.py @@ -54,8 +54,7 @@ def diff_command(obj, kwargs): result = cmd_ctx.deployment_collection.diff( cmd_ctx.k8s_cluster, kwargs["force_apply"], kwargs["replace_on_error"], kwargs["force_replace_on_error"], kwargs["ignore_tags"], kwargs["ignore_labels"], kwargs["ignore_annotations"], kwargs["ignore_order"]) - deleted_objects = cmd_ctx.deployment_collection.find_orphan_objects(cmd_ctx.k8s_cluster) - output_command_result(kwargs["output"], cmd_ctx.deployment_collection, result, deleted_objects) + output_command_result(kwargs["output"], cmd_ctx.deployment_collection, result) sys.exit(1 if result.errors else 0) def confirmed_delete_objects(k8s_cluster, objects, kwargs): @@ -90,8 +89,7 @@ def poke_images_command(obj, kwargs): click.confirm("Do you really want to poke images to the context/cluster %s?" % cmd_ctx.k8s_cluster.context, err=True, abort=True) result = cmd_ctx.deployment_collection.poke_images(cmd_ctx.k8s_cluster) - deleted_objects = cmd_ctx.deployment_collection.find_orphan_objects(cmd_ctx.k8s_cluster) - output_command_result(kwargs["output"], cmd_ctx.deployment_collection, result, deleted_objects) + output_command_result(kwargs["output"], cmd_ctx.deployment_collection, result) if result.errors: sys.exit(1) @@ -101,8 +99,7 @@ def downscale_command(obj, kwargs): click.confirm("Do you really want to downscale on context/cluster %s?" % cmd_ctx.k8s_cluster.context, err=True, abort=True) result = cmd_ctx.deployment_collection.downscale(cmd_ctx.k8s_cluster) - deleted_objects = cmd_ctx.deployment_collection.find_orphan_objects(cmd_ctx.k8s_cluster) - output_command_result(kwargs["output"], cmd_ctx.deployment_collection, result, deleted_objects) + output_command_result(kwargs["output"], cmd_ctx.deployment_collection, result) if result.errors: sys.exit(1) diff --git a/kluctl/cli/utils.py b/kluctl/cli/utils.py index 8c570af77..c2e777e1f 100644 --- a/kluctl/cli/utils.py +++ b/kluctl/cli/utils.py @@ -163,18 +163,19 @@ def project_target_command_context(kwargs, kluctl_project, target, deployment=d, deployment_collection=c, images=images) yield ctx -def build_command_result(c, command_result, deleted_objects, format): +def build_command_result(c, command_result, format): if format == "diff": - return format_diff(command_result.new_objects, command_result.changed_objects, deleted_objects) + return format_diff(command_result.new_objects, command_result.changed_objects, command_result.orphan_objects) elif format != "yaml": raise CommandError(f"Invalid format: {format}") result = { - "diff": changes_to_yaml(command_result.new_objects, command_result.changed_objects, command_result.errors, command_result.warnings), + "diff": changes_to_yaml(command_result.new_objects, command_result.changed_objects), + "orphan_objects": [{"ref": dataclasses.asdict(ref)} for ref in command_result.orphan_objects], + "errors": [dataclasses.asdict(x) for x in command_result.errors], + "warnings": [dataclasses.asdict(x) for x in command_result.warnings], "images": build_seen_images(c, True), } - if deleted_objects is not None: - result["deleted"] = [{"ref": dataclasses.asdict(ref)} for ref in deleted_objects] return yaml_dump(result) def build_validate_result(result, format): @@ -203,7 +204,7 @@ def build_validate_result(result, format): else: raise CommandError(f"Invalid format: {format}") -def output_command_result(output, c, command_result, deleted_objects): +def output_command_result(output, c, command_result): if not output: output = ["diff"] for o in output: @@ -212,7 +213,7 @@ def output_command_result(output, c, command_result, deleted_objects): path = None if len(s) > 1: path = s[1] - s = build_command_result(c, command_result, deleted_objects, format) + s = build_command_result(c, command_result, format) output_result(path, s) def output_validate_result(output, result): diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index 51bd48186..aee115520 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -30,6 +30,7 @@ class DeployErrorItem: class CommandResult: new_objects: list changed_objects: list + orphan_objects: list errors: list warnings: list @@ -144,7 +145,9 @@ def deploy(self, k8s_cluster, force_apply, replace_on_error, force_replace_on_er applied_objects = self.do_apply(k8s_cluster, force_apply, replace_on_error, force_replace_on_error, False, abort_on_error) new_objects, changed_objects = self.do_diff(k8s_cluster, applied_objects, False, False, False, False) + orphan_objects = self.find_orphan_objects(k8s_cluster) return CommandResult(new_objects=new_objects, changed_objects=changed_objects, + orphan_objects=orphan_objects, errors=list(self.errors), warnings=list(self.warnings)) def diff(self, k8s_cluster, force_apply, replace_on_error, force_replace_on_error, ignore_tags, ignore_labels, ignore_annotations, ignore_order): @@ -152,7 +155,9 @@ def diff(self, k8s_cluster, force_apply, replace_on_error, force_replace_on_erro applied_objects = self.do_apply(k8s_cluster, force_apply, replace_on_error, force_replace_on_error, True, False) new_objects, changed_objects = self.do_diff(k8s_cluster, applied_objects, ignore_tags, ignore_labels, ignore_annotations, ignore_order) + orphan_objects = self.find_orphan_objects(k8s_cluster) return CommandResult(new_objects=new_objects, changed_objects=changed_objects, + orphan_objects=orphan_objects, errors=list(self.errors), warnings=list(self.warnings)) def poke_images(self, k8s_cluster): @@ -204,7 +209,9 @@ def do_poke_image(containers_and_images, o): applied_objects = self.do_finish_futures(futures) new_objects, changed_objects = self.do_diff(k8s_cluster, applied_objects, False, False, False, False) + orphan_objects = self.find_orphan_objects(k8s_cluster) return CommandResult(new_objects=new_objects, changed_objects=changed_objects, + orphan_objects=orphan_objects, errors=list(self.errors), warnings=list(self.warnings)) def downscale(self, k8s_cluster): @@ -222,7 +229,9 @@ def downscale(self, k8s_cluster): applied_objects = self.do_finish_futures(futures) new_objects, changed_objects = self.do_diff(k8s_cluster, applied_objects, False, False, False, False) + orphan_objects = self.find_orphan_objects(k8s_cluster) return CommandResult(new_objects=new_objects, changed_objects=changed_objects, + orphan_objects=orphan_objects, errors=list(self.errors), warnings=list(self.warnings)) def validate(self): @@ -250,12 +259,10 @@ def validate(self): return result def find_delete_objects(self, k8s_cluster): - self.clear_errors_and_warnings() labels = self.project.get_delete_by_labels() return find_objects_for_delete(k8s_cluster, labels, self.inclusion, []) def find_orphan_objects(self, k8s_cluster): - self.clear_errors_and_warnings() logger.info("Searching for orphan objects") labels = self.project.get_delete_by_labels() excluded_objects = list(self.local_objects_by_ref().keys()) diff --git a/kluctl/diff/k8s_diff.py b/kluctl/diff/k8s_diff.py index 752e92420..d88b36c81 100644 --- a/kluctl/diff/k8s_diff.py +++ b/kluctl/diff/k8s_diff.py @@ -101,7 +101,7 @@ def deep_diff_to_changes(diff, old_object, new_object): return changes -def changes_to_yaml(new_objects, changed_objects, errors, warnings): +def changes_to_yaml(new_objects, changed_objects): new_objects = [{ "ref": dataclasses.asdict(get_object_ref(x)), "object": x @@ -110,12 +110,8 @@ def changes_to_yaml(new_objects, changed_objects, errors, warnings): "ref": dataclasses.asdict(get_object_ref(c["old_object"])), "changes": c["changes"], } for c in changed_objects] - errors = [dataclasses.asdict(x) for x in errors] - warnings = [dataclasses.asdict(x) for x in warnings] return { "new": new_objects, "changed": changed_objects, - "errors": errors, - "warnings": warnings, } diff --git a/kluctl/diff/k8s_pretty_diff.py b/kluctl/diff/k8s_pretty_diff.py index 8940fec19..bf3d55f07 100644 --- a/kluctl/diff/k8s_pretty_diff.py +++ b/kluctl/diff/k8s_pretty_diff.py @@ -5,7 +5,7 @@ from kluctl.utils.pretty_table import pretty_table -def format_diff(new_objects, changed_objects, deleted_objects): +def format_diff(new_objects, changed_objects, orphan_objects): result = '' if new_objects: @@ -24,9 +24,9 @@ def format_diff(new_objects, changed_objects, deleted_objects): changes = x["changes"] result += "%s\n" % pretty_changes(get_object_ref(object), changes) - if deleted_objects: - result += "Locally deleted/old objects:\n" - for ref in deleted_objects: + if orphan_objects: + result += "Orphan objects:\n" + for ref in orphan_objects: result += " %s\n" % get_long_object_name_from_ref(ref) return result From 7f843831f964d76b7ed7d4d953488122ca358712 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 4 Oct 2021 13:18:09 +0200 Subject: [PATCH 0234/2916] refactor: Move/rename k8s_pretty_diff.py to cli/command_result.py --- kluctl/{diff/k8s_pretty_diff.py => cli/command_result.py} | 2 +- kluctl/cli/utils.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) rename kluctl/{diff/k8s_pretty_diff.py => cli/command_result.py} (94%) diff --git a/kluctl/diff/k8s_pretty_diff.py b/kluctl/cli/command_result.py similarity index 94% rename from kluctl/diff/k8s_pretty_diff.py rename to kluctl/cli/command_result.py index bf3d55f07..f74f85b46 100644 --- a/kluctl/diff/k8s_pretty_diff.py +++ b/kluctl/cli/command_result.py @@ -5,7 +5,7 @@ from kluctl.utils.pretty_table import pretty_table -def format_diff(new_objects, changed_objects, orphan_objects): +def format_command_result_tables(new_objects, changed_objects, orphan_objects): result = '' if new_objects: diff --git a/kluctl/cli/utils.py b/kluctl/cli/utils.py index c2e777e1f..e70c65e84 100644 --- a/kluctl/cli/utils.py +++ b/kluctl/cli/utils.py @@ -12,7 +12,7 @@ from kluctl.deployment.deployment_collection import DeploymentCollection from kluctl.deployment.deployment_project import DeploymentProject from kluctl.diff.k8s_diff import changes_to_yaml -from kluctl.diff.k8s_pretty_diff import format_diff +from kluctl.diff.k8s_pretty_diff import format_command_result_tables from kluctl.image_registries import init_image_registries from kluctl.deployment.images import Images from kluctl.utils.external_args import parse_args @@ -163,9 +163,9 @@ def project_target_command_context(kwargs, kluctl_project, target, deployment=d, deployment_collection=c, images=images) yield ctx -def build_command_result(c, command_result, format): +def format_command_result(c, command_result, format): if format == "diff": - return format_diff(command_result.new_objects, command_result.changed_objects, command_result.orphan_objects) + return format_command_result_tables(command_result.new_objects, command_result.changed_objects, command_result.orphan_objects) elif format != "yaml": raise CommandError(f"Invalid format: {format}") @@ -213,7 +213,7 @@ def output_command_result(output, c, command_result): path = None if len(s) > 1: path = s[1] - s = build_command_result(c, command_result, format) + s = format_command_result(c, command_result, format) output_result(path, s) def output_validate_result(output, result): From 1b350691da537fd83771b974b10cc54efa6c4589 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 4 Oct 2021 13:19:47 +0200 Subject: [PATCH 0235/2916] refactor: Move result related methods to command_result.py --- kluctl/cli/command_result.py | 99 +++++++++++++++++++++++++++++++++++- kluctl/cli/commands.py | 4 +- kluctl/cli/util_commands.py | 2 +- kluctl/cli/utils.py | 91 +-------------------------------- 4 files changed, 102 insertions(+), 94 deletions(-) diff --git a/kluctl/cli/command_result.py b/kluctl/cli/command_result.py index f74f85b46..17268f01a 100644 --- a/kluctl/cli/command_result.py +++ b/kluctl/cli/command_result.py @@ -1,8 +1,15 @@ +import dataclasses +import os + +import click from deepdiff.helper import NotPresent -from kluctl.diff.k8s_diff import unified_diff_object +from kluctl.cli.utils import build_seen_images +from kluctl.diff.k8s_diff import unified_diff_object, changes_to_yaml +from kluctl.utils.exceptions import CommandError from kluctl.utils.k8s_object_utils import get_long_object_name, get_long_object_name_from_ref, get_object_ref from kluctl.utils.pretty_table import pretty_table +from kluctl.utils.yaml_utils import yaml_dump, yaml_dump_all def format_command_result_tables(new_objects, changed_objects, orphan_objects): @@ -45,3 +52,93 @@ def pretty_changes(ref, changes): ret += pretty_table(table, [60]) return ret + + +def format_command_result(c, command_result, format): + if format == "diff": + return format_command_result_tables(command_result.new_objects, command_result.changed_objects, command_result.orphan_objects) + elif format != "yaml": + raise CommandError(f"Invalid format: {format}") + + result = { + "diff": changes_to_yaml(command_result.new_objects, command_result.changed_objects), + "orphan_objects": [{"ref": dataclasses.asdict(ref)} for ref in command_result.orphan_objects], + "errors": [dataclasses.asdict(x) for x in command_result.errors], + "warnings": [dataclasses.asdict(x) for x in command_result.warnings], + "images": build_seen_images(c, True), + } + return yaml_dump(result) + + +def build_validate_result(result, format): + if format == "text": + str = "" + if result.warnings: + str += "Validation Warnings:\n" + for item in result.warnings: + str += " %s: reason=%s, message=%s\n" % (get_long_object_name_from_ref(item.ref), item.reason, item.message) + if result.errors: + if str: + str += "\n" + str += "Validation Errors:\n" + for item in result.errors: + str += " %s: reason=%s, message=%s\n" % (get_long_object_name_from_ref(item.ref), item.reason, item.message) + if result.results: + if str: + str += "\n" + str += "Results:\n" + for item in result.results: + str += " %s: reason=%s, message=%s\n" % (get_long_object_name_from_ref(item.ref), item.reason, item.message) + return str + if format == "yaml": + y = yaml_dump(dataclasses.asdict(result)) + return y + else: + raise CommandError(f"Invalid format: {format}") + + +def output_command_result(output, c, command_result): + if not output: + output = ["diff"] + for o in output: + s = o.split("=", 1) + format = s[0] + path = None + if len(s) > 1: + path = s[1] + s = format_command_result(c, command_result, format) + output_result(path, s) + + +def output_validate_result(output, result): + if not output: + output = ["text"] + for o in output: + s = o.split("=", 1) + format = s[0] + path = None + if len(s) > 1: + path = s[1] + s = build_validate_result(result, format) + output_result(path, s) + + +def output_yaml_result(output, result, all=False): + output = output or [None] + if all: + s = yaml_dump_all(result) + else: + s = yaml_dump(result) + for o in output: + output_result(o, s) + + +def output_result(output_file, result): + path = None + if output_file and output_file != "-": + path = os.path.expanduser(output_file) + if path is None: + click.echo(result) + else: + with open(path, "wt") as f: + f.write(result) \ No newline at end of file diff --git a/kluctl/cli/commands.py b/kluctl/cli/commands.py index a188d94a1..eb709594a 100644 --- a/kluctl/cli/commands.py +++ b/kluctl/cli/commands.py @@ -7,8 +7,8 @@ import click from kluctl import get_kluctl_package_dir -from kluctl.cli.utils import output_command_result, build_seen_images, output_yaml_result, \ - output_validate_result, project_command_context +from kluctl.cli.utils import build_seen_images, project_command_context +from kluctl.cli.command_result import output_command_result, output_validate_result, output_yaml_result from kluctl.kluctl_project.kluctl_project import load_kluctl_project_from_args from kluctl.utils.dict_utils import get_dict_value from kluctl.utils.exceptions import CommandError diff --git a/kluctl/cli/util_commands.py b/kluctl/cli/util_commands.py index 27cc7425d..6a58fa0c0 100644 --- a/kluctl/cli/util_commands.py +++ b/kluctl/cli/util_commands.py @@ -7,7 +7,7 @@ import click from git import Git -from kluctl.cli.commands import output_yaml_result +from kluctl.cli.command_result import output_yaml_result from kluctl.cli.utils import project_command_context from kluctl.deployment.helm_chart import HelmChart from kluctl.image_registries import init_image_registries diff --git a/kluctl/cli/utils.py b/kluctl/cli/utils.py index e70c65e84..44dd06e28 100644 --- a/kluctl/cli/utils.py +++ b/kluctl/cli/utils.py @@ -1,26 +1,20 @@ import contextlib import dataclasses -import os import tempfile from typing import ContextManager -import click - from kluctl.kluctl_project.kluctl_project import load_kluctl_project_from_args, KluctlProject from kluctl.utils.dict_utils import merge_dict, get_dict_value from kluctl.utils.exceptions import CommandError from kluctl.deployment.deployment_collection import DeploymentCollection from kluctl.deployment.deployment_project import DeploymentProject -from kluctl.diff.k8s_diff import changes_to_yaml -from kluctl.diff.k8s_pretty_diff import format_command_result_tables from kluctl.image_registries import init_image_registries from kluctl.deployment.images import Images from kluctl.utils.external_args import parse_args from kluctl.utils.inclusion import Inclusion from kluctl.utils.k8s_cluster_base import load_cluster_config, k8s_cluster_base -from kluctl.utils.k8s_object_utils import get_long_object_name_from_ref from kluctl.utils.utils import get_tmp_base_dir -from kluctl.utils.yaml_utils import yaml_load_file, yaml_dump, yaml_dump_all +from kluctl.utils.yaml_utils import yaml_load_file def build_jinja_vars(cluster_vars): @@ -163,89 +157,6 @@ def project_target_command_context(kwargs, kluctl_project, target, deployment=d, deployment_collection=c, images=images) yield ctx -def format_command_result(c, command_result, format): - if format == "diff": - return format_command_result_tables(command_result.new_objects, command_result.changed_objects, command_result.orphan_objects) - elif format != "yaml": - raise CommandError(f"Invalid format: {format}") - - result = { - "diff": changes_to_yaml(command_result.new_objects, command_result.changed_objects), - "orphan_objects": [{"ref": dataclasses.asdict(ref)} for ref in command_result.orphan_objects], - "errors": [dataclasses.asdict(x) for x in command_result.errors], - "warnings": [dataclasses.asdict(x) for x in command_result.warnings], - "images": build_seen_images(c, True), - } - return yaml_dump(result) - -def build_validate_result(result, format): - if format == "text": - str = "" - if result.warnings: - str += "Validation Warnings:\n" - for item in result.warnings: - str += " %s: reason=%s, message=%s\n" % (get_long_object_name_from_ref(item.ref), item.reason, item.message) - if result.errors: - if str: - str += "\n" - str += "Validation Errors:\n" - for item in result.errors: - str += " %s: reason=%s, message=%s\n" % (get_long_object_name_from_ref(item.ref), item.reason, item.message) - if result.results: - if str: - str += "\n" - str += "Results:\n" - for item in result.results: - str += " %s: reason=%s, message=%s\n" % (get_long_object_name_from_ref(item.ref), item.reason, item.message) - return str - if format == "yaml": - y = yaml_dump(dataclasses.asdict(result)) - return y - else: - raise CommandError(f"Invalid format: {format}") - -def output_command_result(output, c, command_result): - if not output: - output = ["diff"] - for o in output: - s = o.split("=", 1) - format = s[0] - path = None - if len(s) > 1: - path = s[1] - s = format_command_result(c, command_result, format) - output_result(path, s) - -def output_validate_result(output, result): - if not output: - output = ["text"] - for o in output: - s = o.split("=", 1) - format = s[0] - path = None - if len(s) > 1: - path = s[1] - s = build_validate_result(result, format) - output_result(path, s) - -def output_yaml_result(output, result, all=False): - output = output or [None] - if all: - s = yaml_dump_all(result) - else: - s = yaml_dump(result) - for o in output: - output_result(o, s) - -def output_result(output_file, result): - path = None - if output_file and output_file != "-": - path = os.path.expanduser(output_file) - if path is None: - click.echo(result) - else: - with open(path, "wt") as f: - f.write(result) def build_seen_images(c, detailed): ret = [] From 5450d0068d44816a9baed8ca78a659ff147b22c8 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 4 Oct 2021 13:20:49 +0200 Subject: [PATCH 0236/2916] refactor: Rename build_validate_result to format_validate_result --- kluctl/cli/command_result.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kluctl/cli/command_result.py b/kluctl/cli/command_result.py index 17268f01a..c8379ce20 100644 --- a/kluctl/cli/command_result.py +++ b/kluctl/cli/command_result.py @@ -70,7 +70,7 @@ def format_command_result(c, command_result, format): return yaml_dump(result) -def build_validate_result(result, format): +def format_validate_result(result, format): if format == "text": str = "" if result.warnings: @@ -119,7 +119,7 @@ def output_validate_result(output, result): path = None if len(s) > 1: path = s[1] - s = build_validate_result(result, format) + s = format_validate_result(result, format) output_result(path, s) @@ -141,4 +141,4 @@ def output_result(output_file, result): click.echo(result) else: with open(path, "wt") as f: - f.write(result) \ No newline at end of file + f.write(result) From c384ae81a4b9cc410bd8b142a612037753064bb6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 4 Oct 2021 13:22:49 +0200 Subject: [PATCH 0237/2916] refactor: Move yaml output into own method --- kluctl/cli/command_result.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/kluctl/cli/command_result.py b/kluctl/cli/command_result.py index c8379ce20..68e701654 100644 --- a/kluctl/cli/command_result.py +++ b/kluctl/cli/command_result.py @@ -53,13 +53,7 @@ def pretty_changes(ref, changes): return ret - -def format_command_result(c, command_result, format): - if format == "diff": - return format_command_result_tables(command_result.new_objects, command_result.changed_objects, command_result.orphan_objects) - elif format != "yaml": - raise CommandError(f"Invalid format: {format}") - +def format_command_result_yaml(c, command_result): result = { "diff": changes_to_yaml(command_result.new_objects, command_result.changed_objects), "orphan_objects": [{"ref": dataclasses.asdict(ref)} for ref in command_result.orphan_objects], @@ -69,6 +63,13 @@ def format_command_result(c, command_result, format): } return yaml_dump(result) +def format_command_result(c, command_result, format): + if format == "diff": + return format_command_result_tables(command_result.new_objects, command_result.changed_objects, command_result.orphan_objects) + elif format == "yaml": + return format_command_result_yaml(c, command_result) + else: + raise CommandError(f"Invalid format: {format}") def format_validate_result(result, format): if format == "text": From 0a0d39988dbac1110fee7b221a5af65d8a2949f9 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 4 Oct 2021 13:25:18 +0200 Subject: [PATCH 0238/2916] feat: Rename diff format to text format --- kluctl/cli/command_result.py | 8 ++++---- kluctl/cli/main_cli_group.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/kluctl/cli/command_result.py b/kluctl/cli/command_result.py index 68e701654..46f9a7320 100644 --- a/kluctl/cli/command_result.py +++ b/kluctl/cli/command_result.py @@ -12,7 +12,7 @@ from kluctl.utils.yaml_utils import yaml_dump, yaml_dump_all -def format_command_result_tables(new_objects, changed_objects, orphan_objects): +def format_command_result_text(new_objects, changed_objects, orphan_objects): result = '' if new_objects: @@ -64,8 +64,8 @@ def format_command_result_yaml(c, command_result): return yaml_dump(result) def format_command_result(c, command_result, format): - if format == "diff": - return format_command_result_tables(command_result.new_objects, command_result.changed_objects, command_result.orphan_objects) + if format == "text": + return format_command_result_text(command_result.new_objects, command_result.changed_objects, command_result.orphan_objects) elif format == "yaml": return format_command_result_yaml(c, command_result) else: @@ -100,7 +100,7 @@ def format_validate_result(result, format): def output_command_result(output, c, command_result): if not output: - output = ["diff"] + output = ["text"] for o in output: s = o.split("=", 1) format = s[0] diff --git a/kluctl/cli/main_cli_group.py b/kluctl/cli/main_cli_group.py index 355a8cb16..98ef423a0 100644 --- a/kluctl/cli/main_cli_group.py +++ b/kluctl/cli/main_cli_group.py @@ -170,7 +170,7 @@ def misc_arguments(yes=False, dry_run=False, force_apply=False, replace_on_error if output_format: options.append(optgroup.option("-o", "--output", help="Specify output format and target file, in the format 'format=path'. " - "Format can either be 'diff' or 'yaml'. Can be specified multiple times. " + "Format can either be 'text' or 'yaml'. Can be specified multiple times. " "The actual format for yaml is currently not documented and subject to " "change.", multiple=True)) From 1f97eaa626619af5c1048f891941dff3086aa35d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 5 Oct 2021 10:15:43 +0200 Subject: [PATCH 0239/2916] feat: Print errors/warnings as tables --- kluctl/cli/command_result.py | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/kluctl/cli/command_result.py b/kluctl/cli/command_result.py index 46f9a7320..893db11f6 100644 --- a/kluctl/cli/command_result.py +++ b/kluctl/cli/command_result.py @@ -5,6 +5,7 @@ from deepdiff.helper import NotPresent from kluctl.cli.utils import build_seen_images +from kluctl.deployment.deployment_collection import CommandResult from kluctl.diff.k8s_diff import unified_diff_object, changes_to_yaml from kluctl.utils.exceptions import CommandError from kluctl.utils.k8s_object_utils import get_long_object_name, get_long_object_name_from_ref, get_object_ref @@ -12,32 +13,46 @@ from kluctl.utils.yaml_utils import yaml_dump, yaml_dump_all -def format_command_result_text(new_objects, changed_objects, orphan_objects): +def format_command_result_text(command_result: CommandResult): result = '' - if new_objects: + if command_result.warnings: + result += "Warnings:\n" + result += pretty_errors(command_result.warnings) + + if command_result.new_objects: result += "New objects:\n" - for x in new_objects: + for x in command_result.new_objects: result += " %s\n" % get_long_object_name(x, include_api_version=False) - if changed_objects: + if command_result.changed_objects: result += "Changed objects:\n" - for x in changed_objects: + for x in command_result.changed_objects: result += " %s\n" % get_long_object_name(x["new_object"], include_api_version=False) result += "\n" - for x in changed_objects: + for x in command_result.changed_objects: object = x["new_object"] changes = x["changes"] result += "%s\n" % pretty_changes(get_object_ref(object), changes) - if orphan_objects: + if command_result.orphan_objects: result += "Orphan objects:\n" - for ref in orphan_objects: + for ref in command_result.orphan_objects: result += " %s\n" % get_long_object_name_from_ref(ref) + if command_result.errors: + result += "Errors:\n" + result += pretty_errors(command_result.errors) + return result +def pretty_errors(errors): + table = [("Object", "Message")] + for e in errors: + table.append((get_long_object_name_from_ref(e.ref), e.message)) + return pretty_table(table, [60]) + def pretty_changes(ref, changes): ret = 'Diff for object %s\n' % get_long_object_name_from_ref(ref) @@ -65,7 +80,7 @@ def format_command_result_yaml(c, command_result): def format_command_result(c, command_result, format): if format == "text": - return format_command_result_text(command_result.new_objects, command_result.changed_objects, command_result.orphan_objects) + return format_command_result_text(command_result) elif format == "yaml": return format_command_result_yaml(c, command_result) else: From 3508b042546135064f8588d4d8f9678821b9135b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 5 Oct 2021 10:16:01 +0200 Subject: [PATCH 0240/2916] fix: Add k9s to field managers that can be overwritten --- kluctl/diff/managed_fields.py | 1 + 1 file changed, 1 insertion(+) diff --git a/kluctl/diff/managed_fields.py b/kluctl/diff/managed_fields.py index 6c6aca1eb..2b776c9fc 100644 --- a/kluctl/diff/managed_fields.py +++ b/kluctl/diff/managed_fields.py @@ -13,6 +13,7 @@ "kubectl-edit", "kubectl-client-side-apply", "rancher", + "k9s", } ignore_managers = { From 51919d1ba84fdd96707867768cad590fbf457e80 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 5 Oct 2021 10:25:57 +0200 Subject: [PATCH 0241/2916] fix: Use regular expressions for overwrite_allowed_managers And include all managers with kubectl- as prefix. --- kluctl/diff/managed_fields.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/kluctl/diff/managed_fields.py b/kluctl/diff/managed_fields.py index 2b776c9fc..28fb0167f 100644 --- a/kluctl/diff/managed_fields.py +++ b/kluctl/diff/managed_fields.py @@ -1,5 +1,6 @@ import dataclasses import json +import re from typing import Any from kluctl.utils.dict_utils import copy_dict, object_iterator, del_dict_value, get_dict_value, is_iterable, \ @@ -7,14 +8,13 @@ from kluctl.utils.jsonpath_utils import convert_list_to_json_path # We automatically force overwrite these fields as we assume these are human-edited -overwrite_allowed_managers = { +overwrite_allowed_managers = [ "kluctl", "kubectl", - "kubectl-edit", - "kubectl-client-side-apply", + "kubectl-.*", "rancher", "k9s", -} +] ignore_managers = { # If kube-apiserver claimed a field, then this was for a good reason, so lets not complain about it @@ -128,7 +128,7 @@ def find_owner(owners, p): if v != remote_value: set_dict_value(ret, p, remote_value) did_overwrite = True - elif fm["manager"] not in overwrite_allowed_managers: + elif not any(re.fullmatch(x, fm["manager"]) for x in overwrite_allowed_managers): to_delete.add(tp) did_overwrite = True From 5ce2e99fdd66ec84d024a9ad8309947a927aac07 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 5 Oct 2021 10:53:48 +0200 Subject: [PATCH 0242/2916] fix: Run replace-commands-help.py --- docs/commands.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/commands.md b/docs/commands.md index 9f061579f..a8b1eef5f 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -115,7 +115,7 @@ In addition, the following arguments are available: documentation for more details. --abort-on-error Abort deploying when an error occurs instead of trying the remaining deployments -o, --output TEXT Specify output format and target file, in the format 'format=path'. Format can - either be 'diff' or 'yaml'. Can be specified multiple times. The actual format for + either be 'text' or 'yaml'. Can be specified multiple times. The actual format for yaml is currently not documented and subject to change. ``` @@ -149,7 +149,7 @@ In addition, the following arguments are available: documentation for more details. --abort-on-error Abort deploying when an error occurs instead of trying the remaining deployments -o, --output TEXT Specify output format and target file, in the format 'format=path'. Format can - either be 'diff' or 'yaml'. Can be specified multiple times. The actual format for + either be 'text' or 'yaml'. Can be specified multiple times. The actual format for yaml is currently not documented and subject to change. --render-output-dir DIRECTORY Specifies the target directory to render the project into. If omitted, atemporary @@ -218,7 +218,7 @@ In addition, the following arguments are available: --ignore-annotations Ignores changes in annotations when diffing --ignore-order Ignores changes in order when diffing -o, --output TEXT Specify output format and target file, in the format 'format=path'. Format can - either be 'diff' or 'yaml'. Can be specified multiple times. The actual format for + either be 'text' or 'yaml'. Can be specified multiple times. The actual format for yaml is currently not documented and subject to change. --render-output-dir DIRECTORY Specifies the target directory to render the project into. If omitted, atemporary @@ -339,7 +339,7 @@ In addition, the following arguments are available: -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. --dry-run Performs all kubernetes API calls in dry-run mode. -o, --output TEXT Specify output format and target file, in the format 'format=path'. Format can - either be 'diff' or 'yaml'. Can be specified multiple times. The actual format for + either be 'text' or 'yaml'. Can be specified multiple times. The actual format for yaml is currently not documented and subject to change. --render-output-dir DIRECTORY Specifies the target directory to render the project into. If omitted, atemporary @@ -369,7 +369,7 @@ In addition, the following arguments are available: -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. --dry-run Performs all kubernetes API calls in dry-run mode. -o, --output TEXT Specify output format and target file, in the format 'format=path'. Format can - either be 'diff' or 'yaml'. Can be specified multiple times. The actual format for + either be 'text' or 'yaml'. Can be specified multiple times. The actual format for yaml is currently not documented and subject to change. --render-output-dir DIRECTORY Specifies the target directory to render the project into. If omitted, atemporary From 49a04617b931e44ba1dd81996a4a4fa9a1b67cb1 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 5 Oct 2021 11:32:10 +0200 Subject: [PATCH 0243/2916] feat: Don't make hooks part of diffs --- kluctl/cli/command_result.py | 5 +++++ kluctl/deployment/apply_util.py | 22 +++++++++++++--------- kluctl/deployment/deployment_collection.py | 19 ++++++++++++------- kluctl/deployment/hooks_util.py | 2 +- 4 files changed, 31 insertions(+), 17 deletions(-) diff --git a/kluctl/cli/command_result.py b/kluctl/cli/command_result.py index 893db11f6..bb5fb4a47 100644 --- a/kluctl/cli/command_result.py +++ b/kluctl/cli/command_result.py @@ -36,6 +36,11 @@ def format_command_result_text(command_result: CommandResult): changes = x["changes"] result += "%s\n" % pretty_changes(get_object_ref(object), changes) + if command_result.hook_objects: + result += "Applied hooks:\n" + for x in command_result.hook_objects: + result += " %s\n" % get_long_object_name(x) + if command_result.orphan_objects: result += "Orphan objects:\n" for ref in command_result.orphan_objects: diff --git a/kluctl/deployment/apply_util.py b/kluctl/deployment/apply_util.py index 229dde705..aa8b4123e 100644 --- a/kluctl/deployment/apply_util.py +++ b/kluctl/deployment/apply_util.py @@ -24,14 +24,18 @@ def __init__(self, deployment_collection, k8s_cluster, force_apply, replace_on_e self.abort_on_error = abort_on_error self.applied_objects = {} + self.applied_hook_objects = {} self.abort_signal = False self.error_refs = {} self.mutex = threading.Lock() - def handle_result(self, applied_object, patch_warnings): + def handle_result(self, applied_object, patch_warnings, hook): with self.mutex: ref = get_object_ref(applied_object) - self.applied_objects[ref] = applied_object + if hook: + self.applied_hook_objects[ref] = applied_object + else: + self.applied_objects[ref] = applied_object self.deployment_collection.add_warnings(ref, patch_warnings) def handle_error(self, ref, error): @@ -48,7 +52,7 @@ def had_error(self, ref): def delete_object(self, ref): self.k8s_cluster.delete_single_object(ref, force_dry_run=self.dry_run, ignore_not_found=True) - def apply_object(self, x, replaced): + def apply_object(self, x, replaced, hook): logger.debug(f" {get_long_object_name(x)}") x2 = self.k8s_cluster.fix_object_for_patch(x) @@ -65,12 +69,12 @@ def apply_object(self, x, replaced): if self.dry_run and replaced and get_object_ref(x) in self.deployment_collection.remote_objects: # Let's simulate that this object was deleted in dry-run mode. If we'd actually try a dry-run apply with # this object, it might fail as it is expected to not exist. - self.handle_result(x2, []) + self.handle_result(x2, [], hook) return try: r, patch_warnings = self.k8s_cluster.patch_object(x2, force_dry_run=self.dry_run, force_apply=True) - self.handle_result(r, patch_warnings) + self.handle_result(r, patch_warnings, hook) except ResourceNotFoundError as e: ref = get_object_ref(x) self.handle_error(ref, self.k8s_cluster.get_status_message(e)) @@ -89,7 +93,7 @@ def apply_object(self, x, replaced): resource_version = get_dict_value(remote_object, "metadata.resourceVersion") x2 = set_dict_value(x, "metadata.resourceVersion", resource_version, do_clone=True) r, patch_warnings = self.k8s_cluster.replace_object(x2, force_dry_run=self.dry_run) - self.handle_result(r, patch_warnings) + self.handle_result(r, patch_warnings, hook) except ApiException as e2: self.handle_error(ref, self.k8s_cluster.get_status_message(e2)) @@ -103,9 +107,9 @@ def apply_object(self, x, replaced): self.k8s_cluster.delete_single_object(ref, force_dry_run=self.dry_run, ignore_not_found=True) if not self.dry_run: r, patch_warnings = self.k8s_cluster.patch_object(x, force_apply=True) - self.handle_result(r, patch_warnings) + self.handle_result(r, patch_warnings, hook) else: - self.handle_result(x, []) + self.handle_result(x, [], hook) except ApiException as e2: self.handle_error(ref, self.k8s_cluster.get_status_message(e2)) @@ -158,7 +162,7 @@ def apply_kustomize_deployment(self, d): apply_objects.append(o) self.do_log(d, logging.INFO, "Applying %d objects" % len(d.objects)) for o in apply_objects: - self.apply_object(o, False) + self.apply_object(o, False, False) if inital_deploy: hook_util.run_hooks(d, ["post-deploy-initial", "post-deploy"]) diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index aee115520..f3fcda44c 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -30,6 +30,7 @@ class DeployErrorItem: class CommandResult: new_objects: list changed_objects: list + hook_objects: list orphan_objects: list errors: list warnings: list @@ -142,21 +143,25 @@ def prepare(self, k8s_cluster): def deploy(self, k8s_cluster, force_apply, replace_on_error, force_replace_on_error, abort_on_error): self.clear_errors_and_warnings() - applied_objects = self.do_apply(k8s_cluster, force_apply, replace_on_error, force_replace_on_error, - False, abort_on_error) + applied_objects, applied_hook_objects = self.do_apply(k8s_cluster, + force_apply, replace_on_error, force_replace_on_error, + False, abort_on_error) new_objects, changed_objects = self.do_diff(k8s_cluster, applied_objects, False, False, False, False) orphan_objects = self.find_orphan_objects(k8s_cluster) - return CommandResult(new_objects=new_objects, changed_objects=changed_objects, + applied_hook_objects = list(applied_hook_objects.values()) + return CommandResult(new_objects=new_objects, changed_objects=changed_objects, hook_objects=applied_hook_objects, orphan_objects=orphan_objects, errors=list(self.errors), warnings=list(self.warnings)) def diff(self, k8s_cluster, force_apply, replace_on_error, force_replace_on_error, ignore_tags, ignore_labels, ignore_annotations, ignore_order): self.clear_errors_and_warnings() - applied_objects = self.do_apply(k8s_cluster, force_apply, replace_on_error, force_replace_on_error, - True, False) + applied_objects, applied_hook_objects = self.do_apply(k8s_cluster, + force_apply, replace_on_error, force_replace_on_error, + True, False) new_objects, changed_objects = self.do_diff(k8s_cluster, applied_objects, ignore_tags, ignore_labels, ignore_annotations, ignore_order) orphan_objects = self.find_orphan_objects(k8s_cluster) - return CommandResult(new_objects=new_objects, changed_objects=changed_objects, + applied_hook_objects = list(applied_hook_objects.values()) + return CommandResult(new_objects=new_objects, changed_objects=changed_objects, hook_objects=applied_hook_objects, orphan_objects=orphan_objects, errors=list(self.errors), warnings=list(self.warnings)) @@ -273,7 +278,7 @@ def do_apply(self, k8s_cluster, force_apply, replace_on_error, force_replace_on_ dry_run = dry_run apply_util = ApplyUtil(self, k8s_cluster, force_apply, replace_on_error, force_replace_on_error, dry_run, abort_on_error) apply_util.apply_deployments() - return apply_util.applied_objects + return apply_util.applied_objects, apply_util.applied_hook_objects def do_diff(self, k8s_cluster, applied_objects, ignore_tags, ignore_labels, ignore_annotations, ignore_order): diff_objects = {} diff --git a/kluctl/deployment/hooks_util.py b/kluctl/deployment/hooks_util.py index 1c6ec5653..c68d98c66 100644 --- a/kluctl/deployment/hooks_util.py +++ b/kluctl/deployment/hooks_util.py @@ -49,7 +49,7 @@ def do_log(level, str): for h in apply_objects: replaced = "before-hook-creation" in h.delete_policies do_log(logging.DEBUG, "Applying hook %s" % get_long_object_name(h.object)) - self.apply_util.apply_object(h.object, replaced) + self.apply_util.apply_object(h.object, replaced, True) wait_results = {} for h in l: From 511dbd679bc666344fd1c3e88c407d8622c0c792 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 5 Oct 2021 11:32:20 +0200 Subject: [PATCH 0244/2916] fix: Fix output_command_result call --- kluctl/cli/commands.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/kluctl/cli/commands.py b/kluctl/cli/commands.py index eb709594a..7ebe234be 100644 --- a/kluctl/cli/commands.py +++ b/kluctl/cli/commands.py @@ -44,8 +44,7 @@ def deploy_command2(obj, kwargs, cmd_ctx): result = cmd_ctx.deployment_collection.deploy(cmd_ctx.k8s_cluster, kwargs["force_apply"], kwargs["replace_on_error"], kwargs["force_replace_on_error"], kwargs["abort_on_error"]) - deleted_objects = cmd_ctx.deployment_collection.find_orphan_objects(cmd_ctx.k8s_cluster) - output_command_result(kwargs["output"], cmd_ctx.deployment_collection, result, deleted_objects) + output_command_result(kwargs["output"], cmd_ctx.deployment_collection, result) if result.errors: sys.exit(1) From 32aaf5f16206f4cc3ec9192cffd08a3488b817ea Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 5 Oct 2021 11:35:53 +0200 Subject: [PATCH 0245/2916] fix: Fix newlines in command result --- kluctl/cli/command_result.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/kluctl/cli/command_result.py b/kluctl/cli/command_result.py index bb5fb4a47..d955a03d4 100644 --- a/kluctl/cli/command_result.py +++ b/kluctl/cli/command_result.py @@ -17,16 +17,16 @@ def format_command_result_text(command_result: CommandResult): result = '' if command_result.warnings: - result += "Warnings:\n" + result += "\nWarnings:\n" result += pretty_errors(command_result.warnings) if command_result.new_objects: - result += "New objects:\n" + result += "\nNew objects:\n" for x in command_result.new_objects: result += " %s\n" % get_long_object_name(x, include_api_version=False) if command_result.changed_objects: - result += "Changed objects:\n" + result += "\nChanged objects:\n" for x in command_result.changed_objects: result += " %s\n" % get_long_object_name(x["new_object"], include_api_version=False) @@ -34,20 +34,20 @@ def format_command_result_text(command_result: CommandResult): for x in command_result.changed_objects: object = x["new_object"] changes = x["changes"] - result += "%s\n" % pretty_changes(get_object_ref(object), changes) + result += "%s" % pretty_changes(get_object_ref(object), changes) if command_result.hook_objects: - result += "Applied hooks:\n" + result += "\nApplied hooks:\n" for x in command_result.hook_objects: result += " %s\n" % get_long_object_name(x) if command_result.orphan_objects: - result += "Orphan objects:\n" + result += "\nOrphan objects:\n" for ref in command_result.orphan_objects: result += " %s\n" % get_long_object_name_from_ref(ref) if command_result.errors: - result += "Errors:\n" + result += "\nErrors:\n" result += pretty_errors(command_result.errors) return result From 20d8f07787b1583d1f524ed9cf62cc7091130f10 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 5 Oct 2021 11:39:29 +0200 Subject: [PATCH 0246/2916] feat: Add applied_hooks to yaml output --- kluctl/cli/command_result.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kluctl/cli/command_result.py b/kluctl/cli/command_result.py index d955a03d4..e015e36e8 100644 --- a/kluctl/cli/command_result.py +++ b/kluctl/cli/command_result.py @@ -73,9 +73,10 @@ def pretty_changes(ref, changes): return ret -def format_command_result_yaml(c, command_result): +def format_command_result_yaml(c, command_result: CommandResult): result = { "diff": changes_to_yaml(command_result.new_objects, command_result.changed_objects), + "applied_hooks": command_result.hook_objects, "orphan_objects": [{"ref": dataclasses.asdict(ref)} for ref in command_result.orphan_objects], "errors": [dataclasses.asdict(x) for x in command_result.errors], "warnings": [dataclasses.asdict(x) for x in command_result.warnings], From 7e941d820fafeda5cb1a1722fde27d8fe4c62d52 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 8 Oct 2021 14:36:13 +0200 Subject: [PATCH 0247/2916] build: Upgrade some dependencies --- setup.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/setup.py b/setup.py index 62c5691a5..934e11182 100644 --- a/setup.py +++ b/setup.py @@ -58,19 +58,19 @@ "click>=8.0.1", "click-option-group>=0.5.3", "termcolor>=1.1.0", - "jinja2>=3.0.1", + "jinja2>=3.0.2", "requests>=2.26.0", "requests_ntlm>=1.1.0", - "pyyaml>=5.4.1", + "pyyaml>=6.0b1", "deepdiff>=5.5.0", - "kubernetes>=18.20.0b1", + "kubernetes>=19.15.0a1", "adal>=1.2.7", - "PyJWT>=2.1.0", + "PyJWT>=2.2.0", "python-dxf>=7.7.1", - "gitpython>=3.1.18", - "jsonschema>=3.2.0", - "filelock>=3.0.12", - "python-gitlab>=2.10.0", + "gitpython>=3.1.24", + "jsonschema>=4.0.1", + "filelock>=3.3.0", + "python-gitlab>=2.10.1", "jsonpath-ng>=1.5.3", ], entry_points={ From 89b5f788be35fa0a5b5ce32c3eb7feb1f036432f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 8 Oct 2021 15:15:56 +0200 Subject: [PATCH 0248/2916] fix: Less verbose logging for filelock library --- kluctl/cli/main_cli_group.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/kluctl/cli/main_cli_group.py b/kluctl/cli/main_cli_group.py index 98ef423a0..b82cd9431 100644 --- a/kluctl/cli/main_cli_group.py +++ b/kluctl/cli/main_cli_group.py @@ -38,6 +38,12 @@ def setup_logging(verbose): logging.getLogger('urllib3.connectionpool').setLevel(logging.ERROR) logging.getLogger('filelock').setLevel(logging.WARN) + # Import filelock to force the bad setLevel call performed internally so that we can then later override the level + # with what we actually want. TODO remove this when https://github.com/tox-dev/py-filelock/issues/104 is resolved. + import filelock + logging.getLogger("filelock").setLevel(logging.WARN) + + def check_new_version(): if _version.__version__ == "0.0.0": # local dev version From b767fc0d297d8b113e0757883546be8860c89631 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 8 Oct 2021 16:24:02 +0200 Subject: [PATCH 0249/2916] fix: Fix tests on Windows --- kluctl/tests/includes/test_includes.py | 2 +- kluctl/tests/templating/test_templating.py | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/kluctl/tests/includes/test_includes.py b/kluctl/tests/includes/test_includes.py index 0a6b30b9a..3462a3ec4 100644 --- a/kluctl/tests/includes/test_includes.py +++ b/kluctl/tests/includes/test_includes.py @@ -7,7 +7,7 @@ class TestIncludes(DeploymentTestBase): def build_fixed_deployment(self): - return self.build_deployment("includes/test_deployment") + return self.build_deployment(os.path.join("includes", "test_deployment")) def test_simple(self): self.assertEqual(len(self.d.includes), 2) diff --git a/kluctl/tests/templating/test_templating.py b/kluctl/tests/templating/test_templating.py index b8b167b9c..c43cde7b2 100644 --- a/kluctl/tests/templating/test_templating.py +++ b/kluctl/tests/templating/test_templating.py @@ -14,49 +14,49 @@ def get_jinja2_vars(self): } def test_deployment_yml(self): - with self.build_deployment('templating/test_deployment', self.get_jinja2_vars(), {'a': 'a2'}) as (d, c): + with self.build_deployment(os.path.join('templating', 'test_deployment'), self.get_jinja2_vars(), {'a': 'a2'}) as (d, c): self.assertEqual(len(d.includes), 2) self.assertListEqual(d.conf['tags'], ['a1', 'a2']) def test_include_var(self): - with self.build_deployment('templating/test_deployment', self.get_jinja2_vars(), {'a': 'a2'}) as (d, c): + with self.build_deployment(os.path.join('templating', 'test_deployment'), self.get_jinja2_vars(), {'a': 'a2'}) as (d, c): self.assertEqual(d.includes[0].dir, os.path.join(cur_dir, 'test_deployment', 'd1')) def test_not_rendered_kustomize_resource(self): - with self.render_deployment('templating/test_deployment', self.get_jinja2_vars(), {'a': 'a2'}) as c: + with self.render_deployment(os.path.join('templating', 'test_deployment'), self.get_jinja2_vars(), {'a': 'a2'}) as c: y = yaml_load_file(os.path.join(c.tmpdir, 'd1/k1/not-rendered.yml')) self.assertEqual(y['a'], '{{ a }}') def test_rendered_kustomize_resource(self): - with self.render_deployment('templating/test_deployment', self.get_jinja2_vars(), {'a': 'a2'}) as c: + with self.render_deployment(os.path.join('templating', 'test_deployment'), self.get_jinja2_vars(), {'a': 'a2'}) as c: y = yaml_load_file(os.path.join(c.tmpdir, 'd1/k1/rendered.yml')) self.assertEqual(y['a'], 'a1') def test_load_template(self): - with self.render_deployment('templating/test_deployment', self.get_jinja2_vars(), {'a': 'a2'}) as c: + with self.render_deployment(os.path.join('templating', 'test_deployment'), self.get_jinja2_vars(), {'a': 'a2'}) as c: y = yaml_load_file(os.path.join(c.tmpdir, 'd1/k1/rendered.yml')) self.assertEqual(y['b'], 'test a1') self.assertEqual(y['c'], 'test a1') def test_rendered_kustomization_yml(self): - with self.render_deployment('templating/test_deployment', self.get_jinja2_vars(), {'a': 'a2'}) as c: + with self.render_deployment(os.path.join('templating', 'test_deployment'), self.get_jinja2_vars(), {'a': 'a2'}) as c: y = yaml_load_file(os.path.join(c.tmpdir, 'd1/k1/kustomization.yml')) self.assertListEqual(y['resources'], ['b1']) def test_import_no_context(self): - with self.render_deployment('templating/test_import', self.get_jinja2_vars(), {}) as c: + with self.render_deployment(os.path.join('templating', 'test_import'), self.get_jinja2_vars(), {}) as c: y = yaml_load_file(os.path.join(c.tmpdir, 'k1/rendered.yml')) self.assertEqual(y['a'], 'a1') def test_get_var(self): - with self.render_deployment('templating/test_utils', self.get_jinja2_vars(), {}) as c: + with self.render_deployment(os.path.join('templating', 'test_utils'), self.get_jinja2_vars(), {}) as c: y = yaml_load_file(os.path.join(c.tmpdir, 'k1/get_var.yml')) self.assertEqual(y['test1'], 'default') self.assertEqual(y['test2'], 'default') self.assertEqual(y['test3'], 'a') def test_vars(self): - with self.render_deployment('templating/test_vars', self.get_jinja2_vars(), {}) as c: + with self.render_deployment(os.path.join('templating', 'test_vars'), self.get_jinja2_vars(), {}) as c: y = yaml_load_file(os.path.join(c.tmpdir, 'k1/test.yml')) self.assertEqual(y['test1'], 'v1') self.assertEqual(y['test2'], 'f1') From 71964ad94b7f539563e8f1416ea261ded7495af5 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 8 Oct 2021 17:25:41 +0200 Subject: [PATCH 0250/2916] test: Add user config per repo --- .github/workflows/tests.yml | 4 ---- kluctl/e2e/kluctl_test_project.py | 2 ++ 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d4c5601f8..a648958d6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -67,10 +67,6 @@ jobs: run: | pip install . pip install -r requirements-dev.txt - - name: Setup git - run: | - git config --global user.email "no@mail.com" - git config --global user.name "Test User" - name: Run tests env: PYTEST_SPLITS: 4 diff --git a/kluctl/e2e/kluctl_test_project.py b/kluctl/e2e/kluctl_test_project.py index 7b7198a23..cefcf3cbe 100644 --- a/kluctl/e2e/kluctl_test_project.py +++ b/kluctl/e2e/kluctl_test_project.py @@ -73,6 +73,8 @@ def _git_init(self, dir): os.makedirs(dir, exist_ok=True) g = Git(dir) g.init() + g.config("user.email", "no@mail.com") + g.config("user.name", "Your Name") with open(os.path.join(dir, ".dummy"), "wt") as f: f.write("dummy") g.add(".dummy") From 7844814d0cec1dc12a693c48a17c4916f83eb79d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 8 Oct 2021 17:32:26 +0200 Subject: [PATCH 0251/2916] test: Fix kluctl invocation on Windows --- kluctl/e2e/kluctl_test_project.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/kluctl/e2e/kluctl_test_project.py b/kluctl/e2e/kluctl_test_project.py index cefcf3cbe..8ccf4a09b 100644 --- a/kluctl/e2e/kluctl_test_project.py +++ b/kluctl/e2e/kluctl_test_project.py @@ -285,8 +285,9 @@ def get_sealed_secrets_dir(self): return self.get_kluctl_project_dir() def kluctl(self, *args: str, check_rc=True, **kwargs): + python_exe = shutil.which("python") kluctl_path = os.path.join(os.path.dirname(__file__), "..", "..", "cli.py") - args2 = [kluctl_path] + args2 = [python_exe, kluctl_path] args2 += args cwd = None @@ -308,7 +309,7 @@ def kluctl(self, *args: str, check_rc=True, **kwargs): "KUBECONFIG": sep.join(os.path.abspath(str(x)) for x in self.kubeconfigs), }) - logger.info("Running kluctl: %s" % " ".join(args2[1:])) + logger.info("Running kluctl: %s" % " ".join(args2[2:])) def do_log(lines): for l in lines: From 0668ea25f1ba602b52e074fb6ec722aacc71f9f1 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 8 Oct 2021 19:51:11 +0200 Subject: [PATCH 0252/2916] test: Don't swallow ns creation errors --- kluctl/e2e/conftest.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/kluctl/e2e/conftest.py b/kluctl/e2e/conftest.py index 88ab260fd..f9ee600d0 100644 --- a/kluctl/e2e/conftest.py +++ b/kluctl/e2e/conftest.py @@ -14,10 +14,7 @@ def recreate_namespace(kind_cluster: KindCluster, namespace): kind_cluster.kubectl("delete", "ns", namespace) except: pass - try: - kind_cluster.kubectl("create", "ns", namespace) - except: - pass + kind_cluster.kubectl("create", "ns", namespace) def wait_for_readiness(kind_cluster: KindCluster, namespace, resource, timeout): logger.info("Waiting for readiness: %s/%s" % (namespace, resource)) From 1d28963b5499b5854eaa6aaf3992ef8000bb3a8a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 11 Oct 2021 09:01:20 +0200 Subject: [PATCH 0253/2916] fix: Fix passing of current env vars to kubectl --- kluctl/e2e/__init__.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/kluctl/e2e/__init__.py b/kluctl/e2e/__init__.py index 626b30f75..5ad950fc8 100644 --- a/kluctl/e2e/__init__.py +++ b/kluctl/e2e/__init__.py @@ -1,4 +1,23 @@ +import os +import subprocess + import pytest_kind pytest_kind.cluster.KIND_VERSION = "v0.11.1" pytest_kind.cluster.KUBECTL_VERSION = "v1.21.5" + +# Same as pytest_kind.cluster.KindCluster.kubectl, but with os.environment properly passed to the subprocess +def my_kubectl(self, *args, **kwargs): + self.ensure_kubectl() + return subprocess.check_output( + [str(self.kubectl_path), *args], + env={ + **os.environ, + "KUBECONFIG": str(self.kubeconfig_path), + }, + encoding="utf-8", + **kwargs, + ) + + +pytest_kind.cluster.KindCluster.kubectl = my_kubectl \ No newline at end of file From 59e012ee9513429f688486840c4e8083bfb4d9f0 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 8 Oct 2021 18:11:17 +0200 Subject: [PATCH 0254/2916] test: Fix test project urls --- kluctl/e2e/kluctl_test_project.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/kluctl/e2e/kluctl_test_project.py b/kluctl/e2e/kluctl_test_project.py index 8ccf4a09b..f9d70a949 100644 --- a/kluctl/e2e/kluctl_test_project.py +++ b/kluctl/e2e/kluctl_test_project.py @@ -1,7 +1,6 @@ import logging import os import shutil -import subprocess import sys from pathlib import Path from tempfile import TemporaryDirectory @@ -9,7 +8,7 @@ from git import Git from pytest_kind import KindCluster -from kluctl.utils.dict_utils import copy_dict, set_dict_value, del_dict_value +from kluctl.utils.dict_utils import copy_dict, set_dict_value from kluctl.utils.run_helper import run_helper from kluctl.utils.yaml_utils import yaml_save_file, yaml_load_file @@ -49,15 +48,15 @@ def __enter__(self): def do_update_kluctl(y): if self.clusters_external: y["clusters"] = { - "project": "file://%s" % self.get_clusters_dir(), + "project": self.build_file_url(self.get_clusters_dir()), } if self.deployment_external: y["deployment"] = { - "project": "file://%s" % self.get_deployment_dir(), + "project": self.build_file_url(self.get_deployment_dir()), } if self.sealed_secrets_external: y["sealedSecrets"] = { - "project": "file://%s" % self.get_sealed_secrets_dir(), + "project": self.build_file_url(self.get_sealed_secrets_dir()), } return y @@ -292,7 +291,7 @@ def kluctl(self, *args: str, check_rc=True, **kwargs): cwd = None if self.kluctl_project_external: - args2 += ["--project-url", "file://%s" % self.get_kluctl_project_dir()] + args2 += ["--project-url", self.build_file_url(self.get_kluctl_project_dir())] else: cwd = self.get_kluctl_project_dir() @@ -318,3 +317,9 @@ def do_log(lines): rc, stdout, stderr = run_helper(args2, cwd=cwd, env=env, stdout_func=do_log, stderr_func=do_log, line_mode=True, return_std=True) assert not check_rc or rc == 0, "rc=%d" % rc return rc, stdout, stderr + + def build_file_url(self, path): + if sys.platform == "win32": + return "file:///%s" % path + else: + return "file://%s" % path From a2a9b2f7e7000d5c9c7aa71ea8de35fb22da01b4 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 11 Oct 2021 10:10:05 +0200 Subject: [PATCH 0255/2916] test: Remove trailing whitespaces from kluctl log lines --- kluctl/e2e/kluctl_test_project.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kluctl/e2e/kluctl_test_project.py b/kluctl/e2e/kluctl_test_project.py index f9d70a949..b038bb5b4 100644 --- a/kluctl/e2e/kluctl_test_project.py +++ b/kluctl/e2e/kluctl_test_project.py @@ -312,7 +312,7 @@ def kluctl(self, *args: str, check_rc=True, **kwargs): def do_log(lines): for l in lines: - logger.info(l) + logger.info(l.rstrip()) rc, stdout, stderr = run_helper(args2, cwd=cwd, env=env, stdout_func=do_log, stderr_func=do_log, line_mode=True, return_std=True) assert not check_rc or rc == 0, "rc=%d" % rc From 504a8c5058475eb46d78593bef6e4710a828b45a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 11 Oct 2021 10:10:21 +0200 Subject: [PATCH 0256/2916] fix: Ignore errors when touching jinja2 cache files --- kluctl/utils/jinja2_cache.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/kluctl/utils/jinja2_cache.py b/kluctl/utils/jinja2_cache.py index a571b28e1..b1cfd1fca 100644 --- a/kluctl/utils/jinja2_cache.py +++ b/kluctl/utils/jinja2_cache.py @@ -82,11 +82,12 @@ def touch_loaded_marker(self, bucket): filename = self._get_cache_filename(bucket) + ".loaded" if filename in self.did_touch: return - with open(filename + ".tmp", mode="wt") as f: - f.write(str(datetime.utcnow())) try: + with open(filename + ".tmp", mode="wt") as f: + f.write(str(datetime.utcnow())) os.rename(filename + ".tmp", filename) except: + # Failure here it "ok" and is mostly happening on Windows here (permission denied for opened files...ugh..) pass self.did_touch.add(filename) From 82a98a6aaffd212bcbd9426abe95498e310e019f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 11 Oct 2021 10:12:57 +0200 Subject: [PATCH 0257/2916] fix: Ignore errors when writing jinja2 cache --- kluctl/utils/jinja2_cache.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/kluctl/utils/jinja2_cache.py b/kluctl/utils/jinja2_cache.py index b1cfd1fca..8824fe7cb 100644 --- a/kluctl/utils/jinja2_cache.py +++ b/kluctl/utils/jinja2_cache.py @@ -96,8 +96,6 @@ def replace_code_filenames(self, code, old, new): co_consts = [] if co_filename == old: co_filename = new - else: - asd = 123 for c in code.co_consts: if isinstance(c, CodeType): @@ -120,19 +118,20 @@ def load_bytecode(self, bucket: Bucket) -> None: def dump_bytecode(self, bucket: Bucket) -> None: # thread/multi-process safe version of super().dump_bytecode() - filename = self._get_cache_filename(bucket) - os.makedirs(os.path.dirname(filename), exist_ok=True) - - with open(filename + ".tmp", "wb") as f: - orig_code = bucket.code - bucket.code = self.replace_code_filenames(bucket.code, bucket.key[1], "") - bucket.write_bytecode(f) - bucket.code = orig_code try: + filename = self._get_cache_filename(bucket) + os.makedirs(os.path.dirname(filename), exist_ok=True) + + with open(filename + ".tmp", "wb") as f: + orig_code = bucket.code + bucket.code = self.replace_code_filenames(bucket.code, bucket.key[1], "") + bucket.write_bytecode(f) + bucket.code = orig_code os.rename(filename + ".tmp", filename) + self.touch_loaded_marker(bucket) except: + # Failure here it "ok" and is mostly happening on Windows here (permission denied for opened files...ugh..) pass - self.touch_loaded_marker(bucket) def clear_old_entries(self): files = [] From dcfa57fa4aa407a450dbea869e8493f78534415e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 11 Oct 2021 12:52:33 +0200 Subject: [PATCH 0258/2916] fix: Make --include-kustomize-dir and --exclude-kustomize-dir work on Windows --- kluctl/cli/utils.py | 9 +++++++-- kluctl/deployment/kustomize_deployment.py | 4 ++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/kluctl/cli/utils.py b/kluctl/cli/utils.py index 44dd06e28..7b4889a8f 100644 --- a/kluctl/cli/utils.py +++ b/kluctl/cli/utils.py @@ -1,5 +1,6 @@ import contextlib import dataclasses +import os import tempfile from typing import ContextManager @@ -75,9 +76,13 @@ def parse_inclusion(kwargs): for tag in kwargs.get("exclude_tag", []): inclusion.add_exclude("tag", tag) for dir in kwargs.get("include_kustomize_dir", []): - inclusion.add_include("kustomize_dir", dir) + if os.path.isabs(dir): + raise CommandError("--include-kustomize-dir path must be relative") + inclusion.add_include("kustomize_dir", dir.replace(os.path.sep, "/")) for dir in kwargs.get("exclude_kustomize_dir", []): - inclusion.add_exclude("kustomize_dir", dir) + if os.path.isabs(dir): + raise CommandError("--exclude-kustomize-dir path must be relative") + inclusion.add_exclude("kustomize_dir", dir.replace(os.path.sep, "/")) return inclusion @dataclasses.dataclass diff --git a/kluctl/deployment/kustomize_deployment.py b/kluctl/deployment/kustomize_deployment.py index 9b03f00a5..063c61858 100644 --- a/kluctl/deployment/kustomize_deployment.py +++ b/kluctl/deployment/kustomize_deployment.py @@ -59,7 +59,7 @@ def get_common_labels(self): def get_common_annotations(self): a = { # Must use annotations instead of labels due to limitations on value (max 63 chars, no slash) - "kluctl.io/kustomize_dir": self.get_rel_kustomize_dir().replace("\\", "/") + "kluctl.io/kustomize_dir": self.get_rel_kustomize_dir().replace(os.path.sep, "/") } if self.config.get("skipDeleteIfTags", False): a["kluctl.io/skip-delete-if-tags"] = "true" @@ -177,7 +177,7 @@ def get_tags(self): def build_inclusion_values(self): values = [("tag", tag) for tag in self.get_tags()] if "path" in self.config: - kustomize_dir = self.get_rel_kustomize_dir() + kustomize_dir = self.get_rel_kustomize_dir().replace(os.path.sep, "/") values.append(("kustomize_dir", kustomize_dir)) return values From 75a438e4c5ff044af1af2a779d8df8d1e9f0aa7e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 24 Sep 2021 14:04:19 +0200 Subject: [PATCH 0259/2916] ci: Don't fail fast --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a648958d6..03852b8b9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -20,6 +20,7 @@ jobs: strategy: matrix: pytest-group: [ 1, 2, 3, 4 ] + fail-fast: false steps: - name: Install packages run: | From 08bd1a0bed5e8f8a260b59b2b724d526a8b7a1be Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 24 Sep 2021 14:30:07 +0200 Subject: [PATCH 0260/2916] ci: Use older kustomize version in tests (Windows binary not available for 4.2.0) --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 03852b8b9..b81f3270e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -38,7 +38,7 @@ jobs: python-version: 3.8.10 - name: Download required tools run: | - KUSTOMIZE_VERSION=v4.2.0 + KUSTOMIZE_VERSION=v4.1.3 HELM_VERSION=v3.6.3 KUBESEAL_VERSION=v0.16.0 wget -O kustomize.tar.gz https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2F${KUSTOMIZE_VERSION}/kustomize_${KUSTOMIZE_VERSION}_linux_amd64.tar.gz && \ From d1b7b25403d51f54fa6e20ac0cf2f7a2ae1f8280 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 24 Sep 2021 11:32:02 +0200 Subject: [PATCH 0261/2916] ci: Rewrite Github workflow to also run tests on macOS and Windows This relies on docker/kind running on a remote machine. --- .github/workflows/tests.yml | 197 +++++++++++++++++++++------ kluctl/e2e/test_command_bootstrap.py | 6 +- requirements-dev.txt | 2 +- 3 files changed, 162 insertions(+), 43 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b81f3270e..cc9a5a9f8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,64 +16,183 @@ on: jobs: tests: - runs-on: ubuntu-latest strategy: matrix: - pytest-group: [ 1, 2, 3, 4 ] + os: [ubuntu-18.04, macos-10.15, windows-2016] fail-fast: false + concurrency: docker.ci.kluctl.io-${{ matrix.os }} + runs-on: ${{ matrix.os }} steps: - - name: Install packages - run: | - sudo apt update - sudo apt install -y git - name: Checkout uses: actions/checkout@v2 - - name: Set up QEMU - uses: docker/setup-qemu-action@v1 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: 3.8.10 - - name: Download required tools + - name: Setup TOOLS envs + shell: bash run: | - KUSTOMIZE_VERSION=v4.1.3 - HELM_VERSION=v3.6.3 - KUBESEAL_VERSION=v0.16.0 - wget -O kustomize.tar.gz https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2F${KUSTOMIZE_VERSION}/kustomize_${KUSTOMIZE_VERSION}_linux_amd64.tar.gz && \ - tar xzf kustomize.tar.gz && \ - sudo mv kustomize /usr/bin && \ - rm kustomize.tar.gz - wget -O helm.tar.gz https://get.helm.sh/helm-$HELM_VERSION-linux-amd64.tar.gz && \ - tar xzf helm.tar.gz && \ - sudo mv linux-amd64/helm /usr/bin && \ - rm helm.tar.gz - wget -O kubeseal https://github.com/bitnami-labs/sealed-secrets/releases/download/$KUBESEAL_VERSION/kubeseal-linux-amd64 && \ - chmod +x kubeseal && \ - sudo mv kubeseal /usr/bin + if [ "${{ runner.os }}" != "Windows" ]; then + echo "SUDO=sudo" >> $GITHUB_ENV + fi + + TOOLS_EXE= + TOOLS_TARGET_DIR=$GITHUB_WORKSPACE/bin + mkdir $TOOLS_TARGET_DIR + + if [ "${{ runner.os }}" == "macOS" ]; then + TOOLS_OS=darwin + elif [ "${{ runner.os }}" == "Windows" ]; then + TOOLS_OS=windows + TOOLS_EXE=.exe + else + TOOLS_OS=linux + fi + echo "TOOLS_OS=$TOOLS_OS" >> $GITHUB_ENV + echo "TOOLS_EXE=$TOOLS_EXE" >> $GITHUB_ENV + echo "TOOLS_TARGET_DIR=$TOOLS_TARGET_DIR" >> $GITHUB_ENV + echo "$TOOLS_TARGET_DIR" >> $GITHUB_PATH + - name: "[Windows] Install openssh" + if: runner.os == 'Windows' + shell: bash + run: | + choco install openssh + - name: Port-forward docker + shell: bash + run: | + echo "${{ secrets.CI_SSH_KEY }}" > kluctl-ci.pem + chmod og-rwx kluctl-ci.pem + echo "Forwarding ports" + + # pre-create this to avoid races in the background ssh calls + mkdir -p $HOME/.ssh + + # docker + nohup /usr/bin/ssh -i kluctl-ci.pem -o StrictHostKeyChecking=no -L2375:/run/docker.sock -N kluctl-ci@docker.ci.kluctl.io &> ssh-log-2375 & + + # keep ports alive + nohup bash -c "while true; do curl http://localhost:2375 &> /dev/null ; sleep 5; done" & + + echo "DOCKER_HOST=localhost:2375" >> $GITHUB_ENV # Enable tmate debugging of manually-triggered workflows if the input option was provided - name: Setup tmate session uses: mxschmitt/action-tmate@v3 if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }} with: limit-access-to-actor: true - - name: Cache dot-kube - id: cache-dot-kube - uses: actions/cache@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3.8.10 + - uses: actions/cache@v2 with: - path: .kube - key: ${{ runner.os }}-dot-kube + path: .venv + key: ${{ matrix.os }}-${{ hashFiles('setup.py') }}-${{ hashFiles('requirements-dev.txt') }} + - name: Setup virtualenv + shell: bash + run: | + if [ ! -d .venv ]; then + pip install virtualenv + virtualenv .venv + fi + if [ "${{ runner.os }}" == "Windows" ]; then + echo "$GITHUB_WORKSPACE\\.venv\\Scripts" >> $GITHUB_PATH + else + echo "$GITHUB_WORKSPACE/.venv/bin" >> $GITHUB_PATH + fi - name: Install dependencies + shell: bash run: | - pip install . + python ./setup.py develop pip install -r requirements-dev.txt + - name: Provide required tools versions + shell: bash + run: | + echo "KUBECTL_VERSION=1.21.5" >> $GITHUB_ENV + echo "KIND_VERSION=0.11.1" >> $GITHUB_ENV + echo "KUSTOMIZE_VERSION=4.1.3" >> $GITHUB_ENV + echo "HELM_VERSION=3.6.3" >> $GITHUB_ENV + echo "KUBESEAL_VERSION=0.16.0" >> $GITHUB_ENV + echo "DOCKER_VERSION=20.10.9" >> $GITHUB_ENV + - name: Download required tools + shell: bash + run: | + curl -L -o kubectl$TOOLS_EXE https://storage.googleapis.com/kubernetes-release/release/v$KUBECTL_VERSION/bin/${TOOLS_OS}/amd64/kubectl$TOOLS_EXE && \ + $SUDO mv kubectl$TOOLS_EXE "$TOOLS_TARGET_DIR/" + curl -L -o kind$TOOLS_EXE https://github.com/kubernetes-sigs/kind/releases/download/v${KIND_VERSION}/kind-${TOOLS_OS}-amd64 && \ + $SUDO mv kind$TOOLS_EXE "$TOOLS_TARGET_DIR/" + curl -L -o kustomize.tar.gz https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2Fv${KUSTOMIZE_VERSION}/kustomize_v${KUSTOMIZE_VERSION}_${TOOLS_OS}_amd64.tar.gz && \ + tar xzf kustomize.tar.gz && \ + $SUDO mv kustomize$TOOLS_EXE "$TOOLS_TARGET_DIR/" + curl -L -o helm.tar.gz https://get.helm.sh/helm-v$HELM_VERSION-${TOOLS_OS}-amd64.tar.gz && \ + tar xzf helm.tar.gz && \ + $SUDO mv ${TOOLS_OS}-amd64/helm$TOOLS_EXE "$TOOLS_TARGET_DIR/" + KUBESEAL_EXE=kubeseal-${TOOLS_OS}-amd64 + if [ "${{ runner.os }}" == "Windows" ]; then + KUBESEAL_EXE=kubeseal.exe + fi + curl -L -o kubeseal$TOOLS_EXE https://github.com/bitnami-labs/sealed-secrets/releases/download/v$KUBESEAL_VERSION/$KUBESEAL_EXE && \ + $SUDO mv kubeseal$TOOLS_EXE "$TOOLS_TARGET_DIR/" + if [ "${{ runner.os }}" == "macOS" ]; then + curl -L -o docker.tar.gz https://download.docker.com/mac/static/stable/x86_64/docker-$DOCKER_VERSION.tgz + tar xzf docker.tar.gz + $SUDO mv docker/docker "$TOOLS_TARGET_DIR/" + rm -rf docker + elif [ "${{ runner.os }}" == "Windows" ]; then + curl -L -o docker.zip https://download.docker.com/win/static/stable/x86_64/docker-$DOCKER_VERSION.zip + unzip docker.zip + mv docker/docker.exe "$TOOLS_TARGET_DIR/" + rm -rf docker + fi + $SUDO chmod -R +x "$TOOLS_TARGET_DIR/" + - name: Test required tools + shell: bash + run: | + kubectl version || true + kind version || true + kustomize version || true + helm version || true + kubeseal --version || true + - name: Start kind cluster + shell: bash + run: | + if [ "${{ runner.os }}" == "Linux" ]; then + PORT=10000 + elif [ "${{ runner.os }}" == "Windows" ]; then + PORT=10001 + else + PORT=10002 + fi + cat << EOF > kind-cluster.yml + kind: Cluster + apiVersion: kind.x-k8s.io/v1alpha4 + networking: + apiServerAddress: "0.0.0.0" + apiServerPort: $PORT + EOF + KIND_CLUSTER_NAME=$(echo "pytest-${{ runner.os }}" | awk '{{ print tolower($1)}}') + echo "KIND_CLUSTER_NAME=$KIND_CLUSTER_NAME" >> $GITHUB_ENV + + export KUBECONFIG=$GITHUB_WORKSPACE/kind-kubeconfig + kind delete cluster --name $KIND_CLUSTER_NAME || true + kind create cluster --config kind-cluster.yml --name $KIND_CLUSTER_NAME + + # Rewrite cluster info to point to docker.ci.kluctl.io + # This also fully disables TLS verification + IP=$(nslookup docker.ci.kluctl.io | grep Address | tail -n1 | sed 's/Address://g' | awk '{print $1}') + echo IP=$IP + kubectl config view -ojson --raw \ + | jq ".clusters[0].cluster.\"insecure-skip-tls-verify\"=true" \ + | jq "del(.clusters[0].cluster.\"certificate-authority-data\")" \ + | jq ".clusters[0].cluster.server=\"https://$IP:$PORT\"" \ + > kind-kubeconfig2 + mv kind-kubeconfig2 kind-kubeconfig - name: Run tests - env: - PYTEST_SPLITS: 4 - PYTEST_GROUP: ${{ matrix.pytest-group }} + shell: bash + run: | + TOOLS_TARGET_DIR=$(echo $TOOLS_TARGET_DIR | sed 's|\\|/|g') + export PYTEST_ADDOPTS="--cluster-name=$KIND_CLUSTER_NAME --kubeconfig=kind-kubeconfig --kind-bin=$TOOLS_TARGET_DIR/kind$TOOLS_EXE --kind-kubectl-bin=$TOOLS_TARGET_DIR/kubectl$TOOLS_EXE --keep-cluster" + pytest -n4 + - name: Delete kind cluster + shell: bash run: | - pytest --splits $PYTEST_SPLITS --group $PYTEST_GROUP + kind delete cluster --name $KIND_CLUSTER_NAME check-code: runs-on: ubuntu-latest steps: diff --git a/kluctl/e2e/test_command_bootstrap.py b/kluctl/e2e/test_command_bootstrap.py index 70259d1fd..47c2ed5fd 100644 --- a/kluctl/e2e/test_command_bootstrap.py +++ b/kluctl/e2e/test_command_bootstrap.py @@ -25,7 +25,7 @@ def test_command_bootstrap(kind_cluster: KindCluster): with KluctlTestProject("bootstrap") as p: p.update_kind_cluster(kind_cluster) p.update_target("test", kind_cluster.name) - p.kluctl("bootstrap", "--yes", "--cluster", "pytest-kind") + p.kluctl("bootstrap", "--yes", "--cluster", kind_cluster.name) assert_readiness(kind_cluster, "kube-system", "Deployment/sealed-secrets", 60 * 5) # test upgrades @@ -41,12 +41,12 @@ def test_command_bootstrap(kind_cluster: KindCluster): with KluctlTestProject("bootstrap", local_deployment=tmpdir) as p: p.update_kind_cluster(kind_cluster) p.update_target("test", kind_cluster.name) - p.kluctl("bootstrap", "--yes", "--cluster", "pytest-kind") + p.kluctl("bootstrap", "--yes", "--cluster", kind_cluster.name) assert_resource_exists(kind_cluster, "kube-system", "ConfigMap/dummy-configmap") # test pruning with KluctlTestProject("bootstrap") as p: p.update_kind_cluster(kind_cluster) p.update_target("test", kind_cluster.name) - p.kluctl("bootstrap", "--yes", "--cluster", "pytest-kind") + p.kluctl("bootstrap", "--yes", "--cluster", kind_cluster.name) assert_resource_not_exists(kind_cluster, "kube-system", "ConfigMap/dummy-configmap") diff --git a/requirements-dev.txt b/requirements-dev.txt index 96dd675ff..7239cfa99 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,3 +1,3 @@ pytest>=6.2.5 -pytest-split>=0.3.3 +pytest-xdist>=2.4.0 pytest-kind>=21.1.3 From 4153a729124b253df5844b4e7e5a45dae56093ed Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 12 Oct 2021 12:52:17 +0200 Subject: [PATCH 0262/2916] fix: Properly handle resolve_field_manager_conflicts errors --- kluctl/deployment/apply_util.py | 12 ++++++++---- kluctl/diff/managed_fields.py | 4 ++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/kluctl/deployment/apply_util.py b/kluctl/deployment/apply_util.py index aa8b4123e..f60abe5b8 100644 --- a/kluctl/deployment/apply_util.py +++ b/kluctl/deployment/apply_util.py @@ -59,7 +59,11 @@ def apply_object(self, x, replaced, hook): if not self.force_apply: ref = get_object_ref(x) remote_object = self.deployment_collection.remote_objects.get(ref) - x2, overwritten = resolve_field_manager_conflicts(x2, remote_object) + try: + x2, overwritten = resolve_field_manager_conflicts(x2, remote_object) + except Exception as e: + self.handle_error(ref, str(e)) + return warnings = [] for ow in overwritten: warnings.append("Field '%s' is now owned by field manager '%s'. " @@ -83,8 +87,8 @@ def apply_object(self, x, replaced, hook): if not self.replace_on_error: self.handle_error(ref, self.k8s_cluster.get_status_message(e)) return - logger.info("Patching %s failed, retrying with replace instead of patch" % - get_long_object_name_from_ref(ref)) + logger.warning("Patching %s failed, retrying with replace instead of patch" % + get_long_object_name_from_ref(ref)) try: remote_object = self.deployment_collection.remote_objects.get(ref) if remote_object is None: @@ -101,7 +105,7 @@ def apply_object(self, x, replaced, hook): self.handle_error(ref, self.k8s_cluster.get_status_message(e)) return - logger.info("Patching %s failed, retrying by deleting and re-applying" % + logger.warning("Patching %s failed, retrying by deleting and re-applying" % get_long_object_name_from_ref(ref)) try: self.k8s_cluster.delete_single_object(ref, force_dry_run=self.dry_run, ignore_not_found=True) diff --git a/kluctl/diff/managed_fields.py b/kluctl/diff/managed_fields.py index 28fb0167f..28500f75c 100644 --- a/kluctl/diff/managed_fields.py +++ b/kluctl/diff/managed_fields.py @@ -57,6 +57,8 @@ def convert_to_json_path(o, mf_path): return ret, True if k[:2] == 'f:': k = k[2:] + if o is None: + return ret, False if not isinstance(o, dict): raise ValueError("%s is not a dict" % convert_list_to_json_path(ret)) if k not in o: @@ -64,6 +66,8 @@ def convert_to_json_path(o, mf_path): ret.append(k) o = o[k] else: + if o is None: + return ret, False if not is_iterable(o, False): raise ValueError("%s is not a list" % convert_list_to_json_path(ret)) found = False From cc6a62057f52d982123f36165e7a73d81c1d6793 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 12 Oct 2021 12:58:46 +0200 Subject: [PATCH 0263/2916] feat: Add load_sha256 jinja2 method --- docs/jinja2-templating.md | 17 +++++++++++++++++ kluctl/utils/jinja2_utils.py | 13 +++++++++++++ 2 files changed, 30 insertions(+) diff --git a/docs/jinja2-templating.md b/docs/jinja2-templating.md index 0da86c98b..147e6b3e3 100644 --- a/docs/jinja2-templating.md +++ b/docs/jinja2-templating.md @@ -137,6 +137,23 @@ Loads the given file into memory, renders it with the current Jinja2 context and The filename given to `load_template` is treated as relative to the template that is currently rendered. +### load_sha256(file, digest_len) +Loads the given file into memory, renders it and calculates the sha256 hash of the result. + +The filename given to `load_sha256` is treated the same as in `load_template`. Recursive loading/calculating of hashes +is allowed and is solved by replacing `load_sha256` invocations with currently loaded templates with dummy strings. +This also allows to calculate the hash of the currently rendered template, for example: + +``` +apiVersion: v1 +kind: ConfigMap +metadata: + name: my-config-{{ load_sha256("configmap.yml") }} +data: +``` + +`digest_len` is an optional parameter that allows to limit the length of the returned hex digest. + ### get_var(field_path, default) Convenience method to navigate through the current context variables via a [JSON Path](https://goessner.net/articles/JsonPath/). Let's assume you currently have these variables defines diff --git a/kluctl/utils/jinja2_utils.py b/kluctl/utils/jinja2_utils.py index 0ebb56cb2..78d86fb76 100644 --- a/kluctl/utils/jinja2_utils.py +++ b/kluctl/utils/jinja2_utils.py @@ -8,6 +8,7 @@ import jinja2 from jinja2 import Environment, StrictUndefined, FileSystemLoader, TemplateNotFound, TemplateError +from jinja2.runtime import Context from kluctl.utils.dict_utils import merge_dict, get_dict_value from kluctl.utils.yaml_utils import yaml_dump, yaml_load @@ -88,6 +89,17 @@ def debug_print(msg): logger.info("debug_print: %s" % str(msg)) return "" +@jinja2.pass_context +def load_sha256(ctx: Context, path, digest_len=None): + if "__calc_sha256__" in ctx: + return "__self_sha256__" + rendered = load_template(ctx, path, __calc_sha256__=True) + hash = hashlib.sha256(rendered.encode("utf-8")).hexdigest() + if digest_len is not None: + hash = hash[:digest_len] + return hash + + def add_jinja2_filters(jinja2_env): jinja2_env.filters['b64encode'] = b64encode jinja2_env.filters['b64decode'] = b64decode @@ -102,6 +114,7 @@ def add_jinja2_filters(jinja2_env): jinja2_env.globals['update_dict'] = update_dict jinja2_env.globals['raise'] = raise_helper jinja2_env.globals['debug_print'] = debug_print + jinja2_env.globals['load_sha256'] = load_sha256 def render_str(s, jinja_vars): if "{" not in s: From 5b16e2b1ddaf438e456b3aaa88f4997b0c00201b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 19 Oct 2021 18:25:38 +0200 Subject: [PATCH 0264/2916] fix: Better error messages when templates are not found --- kluctl/cli/__main__.py | 5 ++++- kluctl/deployment/deployment_project.py | 2 -- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/kluctl/cli/__main__.py b/kluctl/cli/__main__.py index 5b429b562..736acbf6f 100755 --- a/kluctl/cli/__main__.py +++ b/kluctl/cli/__main__.py @@ -2,7 +2,7 @@ import sys from git import GitCommandError -from jinja2 import TemplateError +from jinja2 import TemplateError, TemplateNotFound from yaml import YAMLError from kluctl.cli.main_cli_group import cli_group @@ -22,6 +22,9 @@ def main(): except InvalidKluctlProjectConfig as e: print(e.message, file=sys.stderr) sys.exit(1) + except TemplateNotFound as e: + print("Template not found: %s" % e.name, file=sys.stderr) + sys.exit(1) except TemplateError as e: print_template_error(e) sys.exit(1) diff --git a/kluctl/deployment/deployment_project.py b/kluctl/deployment/deployment_project.py index 6852f084a..dab9357d5 100644 --- a/kluctl/deployment/deployment_project.py +++ b/kluctl/deployment/deployment_project.py @@ -64,8 +64,6 @@ def load_jinja_vars_list(self, vars_list, cur_jinja_vars): return jinja_vars def load_jinja_vars_file(self, rel_file, cur_jinja_vars): - if not os.path.exists(os.path.join(self.dir, rel_file)): - raise CommandError("%s not found" % rel_file) new_vars = self.load_rendered_yaml(rel_file, cur_jinja_vars) jinja_vars = merge_dict(cur_jinja_vars, new_vars) return jinja_vars From 6dd31d2bccf8c592c6e5b3157679e3c5d3d5d6cc Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 19 Oct 2021 18:26:08 +0200 Subject: [PATCH 0265/2916] feat: Add jinja2.ext.loopcontrols extension --- kluctl/deployment/deployment_project.py | 8 ++++---- kluctl/utils/jinja2_utils.py | 7 ++++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/kluctl/deployment/deployment_project.py b/kluctl/deployment/deployment_project.py index dab9357d5..d28ddf906 100644 --- a/kluctl/deployment/deployment_project.py +++ b/kluctl/deployment/deployment_project.py @@ -9,7 +9,7 @@ from kluctl.utils.exceptions import CommandError from kluctl.utils.external_args import check_required_args from kluctl.utils.jinja2_cache import KluctlBytecodeCache -from kluctl.utils.jinja2_utils import add_jinja2_filters, RelEnvironment +from kluctl.utils.jinja2_utils import add_jinja2_filters, KluctlJinja2Environment from kluctl.utils.yaml_utils import yaml_load, yaml_load_file logger = logging.getLogger(__name__) @@ -45,9 +45,9 @@ def build_jinja2_env(self, jinja_vars): dirs = [] for d, _ in self.get_parents(): dirs.append(d.dir) - environment = RelEnvironment(loader=FileSystemLoader(dirs), undefined=StrictUndefined, - cache_size=10000, - bytecode_cache=self.jinja2_cache, auto_reload=False) + environment = KluctlJinja2Environment(loader=FileSystemLoader(dirs), undefined=StrictUndefined, + cache_size=10000, + bytecode_cache=self.jinja2_cache, auto_reload=False) merge_dict(environment.globals, jinja_vars, clone=False) add_jinja2_filters(environment) return environment diff --git a/kluctl/utils/jinja2_utils.py b/kluctl/utils/jinja2_utils.py index 78d86fb76..00e41e73b 100644 --- a/kluctl/utils/jinja2_utils.py +++ b/kluctl/utils/jinja2_utils.py @@ -17,9 +17,10 @@ # This Jinja2 environment allows to load templates relative to the parent template. This means that for example # '{% include "file.yml" %}' will try to include the template from a ./file.yml -class RelEnvironment(Environment): +class KluctlJinja2Environment(Environment): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + self.add_extension("jinja2.ext.loopcontrols") """Override join_path() to enable relative template paths.""" """See https://stackoverflow.com/a/3655911/7132642""" @@ -119,7 +120,7 @@ def add_jinja2_filters(jinja2_env): def render_str(s, jinja_vars): if "{" not in s: return s - e = RelEnvironment(undefined=StrictUndefined) + e = KluctlJinja2Environment(undefined=StrictUndefined) add_jinja2_filters(e) merge_dict(e.globals, jinja_vars, False) t = e.from_string(s) @@ -155,7 +156,7 @@ def render_dict_strs(d, jinja_vars, do_raise=True): return ret, errors def render_file(root_dir, path, jinja_vars): - e = RelEnvironment(loader=FileSystemLoader(root_dir), undefined=StrictUndefined) + e = KluctlJinja2Environment(loader=FileSystemLoader(root_dir), undefined=StrictUndefined) path = os.path.normpath(path) add_jinja2_filters(e) merge_dict(e.globals, jinja_vars, False) From 4c09ce00c1654fb8f5333057cf565e96cb2e354f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 19 Oct 2021 18:26:27 +0200 Subject: [PATCH 0266/2916] fix: Fix version comparion for EKS clusters --- kluctl/utils/k8s_cluster_real.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/kluctl/utils/k8s_cluster_real.py b/kluctl/utils/k8s_cluster_real.py index 06424cc84..7c53b81d1 100644 --- a/kluctl/utils/k8s_cluster_real.py +++ b/kluctl/utils/k8s_cluster_real.py @@ -3,7 +3,7 @@ import logging import threading import time -from distutils.version import StrictVersion +from distutils.version import LooseVersion from kubernetes import config from kubernetes.client import ApiClient, Configuration, ApiException @@ -11,9 +11,9 @@ from kubernetes.dynamic import EagerDiscoverer, DynamicClient, Resource from kubernetes.dynamic.exceptions import NotFoundError, ResourceNotFoundError +from kluctl.utils.dict_utils import copy_dict, get_dict_value from kluctl.utils.exceptions import CommandError from kluctl.utils.k8s_cluster_base import k8s_cluster_base -from kluctl.utils.dict_utils import copy_dict, get_dict_value from kluctl.utils.k8s_object_utils import split_api_version from kluctl.utils.versions import LooseVersionComparator @@ -258,9 +258,9 @@ def fix_object_for_patch(self, o): # default values. We need to fix these resources. # UPDATE even though https://github.com/kubernetes-sigs/structured-merge-diff/issues/130 says it's fixed, the # issue is still present. - needs_defaults_fix = StrictVersion(self.server_version) < StrictVersion('1.21') + needs_defaults_fix = LooseVersion(self.server_version) < LooseVersion('1.21') # TODO check when this is actually fixed (see https://github.com/kubernetes/kubernetes/issues/94275) - needs_type_conversion_fix = StrictVersion(self.server_version) < StrictVersion('1.100') + needs_type_conversion_fix = LooseVersion(self.server_version) < LooseVersion('1.100') if not needs_defaults_fix and not needs_type_conversion_fix: return o From 565ded65d133ed4735f13d0d0fc30903e3f66fcb Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 19 Oct 2021 18:27:28 +0200 Subject: [PATCH 0267/2916] fix: Properly search for docker credentials when ports are explicitely provided --- kluctl/image_registries/generic_registry.py | 77 +++++++++++++++++---- 1 file changed, 65 insertions(+), 12 deletions(-) diff --git a/kluctl/image_registries/generic_registry.py b/kluctl/image_registries/generic_registry.py index 5a6317985..bf445ef42 100644 --- a/kluctl/image_registries/generic_registry.py +++ b/kluctl/image_registries/generic_registry.py @@ -10,6 +10,7 @@ from calendar import timegm from collections import namedtuple from datetime import datetime +from urllib.parse import urlparse import jwt import requests @@ -52,7 +53,46 @@ def add_creds(self, host, username, password, tlsverify): host = DOCKER_HUB_REGISTRY self.creds[host] = DockerCreds(username=username, password=password, tlsverify=tlsverify) + def split_host_and_port(self, h): + if h.startswith("http://") or h.startswith("https://"): + url = urlparse(h) + host = url.hostname + port = url.port + if port is None: + if url.scheme == "http": + port = 80 + else: + port = 443 + else: + if ":" in h: + s = h.split(":") + port = s[-1] + host = h[:-len(port) - 1] + port = int(port) + else: + host = h + port = 443 + return host, port + + def parse_docker_auths(self, config): + ret = [] + auths = config.get("auths", {}) + for k, v in auths.items(): + try: + host, port = self.split_host_and_port(k) + ret.append({ + "key": k, + "auth": v, + "host": host, + "port": port, + }) + except Exception as e: + logger.debug("Failed to parse auth entry: %s. Errors=%s" % (k, str(e))) + return ret + def do_get_creds(self, host): + host, port = self.split_host_and_port(host) + logger.debug(f"looking up {host} in creds") if host in self.creds: logger.debug(f"found entry with username {self.creds[host].username} in creds") @@ -60,10 +100,6 @@ def do_get_creds(self, host): logger.debug("entry not found in creds, looking into docker config") - auth_entry = host - if host == DOCKER_HUB_REGISTRY: - auth_entry = DOCKER_HUB_AUTH_ENTRY - # try from docker creds try: with open(os.path.expanduser("~/.docker/config.json")) as f: @@ -73,9 +109,20 @@ def do_get_creds(self, host): return None try: - auth = config.get("auths", {}).get(auth_entry) + auths = self.parse_docker_auths(config) + + auth_entry = None + if host == DOCKER_HUB_REGISTRY: + auth_entry = DOCKER_HUB_AUTH_ENTRY + else: + for a in auths: + if a["host"] == host and a["port"] == port: + auth_entry = a["key"] + break + auth = config.get("auths", {}).get(auth_entry) if auth_entry else None + if auth is None: - logger.debug("No 'auths.%s' entry in docker config" % auth_entry) + logger.debug("No auth entry in docker config") return None if "username" in auth and "password" in auth: logger.debug(f"found docker config entry with username {auth['username']}") @@ -193,6 +240,12 @@ def set_cached_token(self, image, username, password, token): os.chmod(path, 0o600) f.write(token) + def build_auth_error(self, host, title): + error = f"{title}. Please ensure you provided correct registry " \ + f"credentials for '{host}', either by logging in with 'docker login {host}' or by " \ + f"providing environment variables as described in the documentation." + return error + def get_client(self, image): host, repo = self.parse_image(image) if host == DOCKER_HUB_REGISTRY: @@ -237,16 +290,13 @@ def do_authenticate(dxf, response): return token with first_auth_context(): logger.debug(f"calling dxf.authenticate with username={username}") - base_auth_error = f"Got 401 Unauthorized from registry. Please ensure you provided correct registry " \ - f"credentials for '{host}', either by logging in with 'docker login {host}' or by " \ - f"providing environment variables as described in the documentation." try: token = dxf.authenticate(username=username, password=password, response=response, actions=["pull"]) except DXFUnauthorizedError: - raise CommandError(f"Got 401 Unauthorized from registry. {base_auth_error}") + raise CommandError(self.build_auth_error(host, f"Got 401 Unauthorized from registry")) except HTTPError as e: if e.response.status_code == http.HTTPStatus.FORBIDDEN: - raise CommandError(f"Got 403 Forbidden from registry. {base_auth_error}") + raise CommandError(self.build_auth_error(host, f"Got 403 Forbidden from registry")) else: raise if token is not None: @@ -267,5 +317,8 @@ def is_image_from_registry(self, image): def list_tags_for_image(self, image): dxf = self.get_client(image) - tags = dxf.list_aliases(batch_size=100) + try: + tags = dxf.list_aliases(batch_size=100) + except DXFUnauthorizedError as e: + raise CommandError(self.build_auth_error(dxf._host, f"Got 401 Unauthorized from registry")) return tags From 20b469295a2f680a9daf9021d8de87e5f47bc0e0 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 21 Oct 2021 15:10:47 +0200 Subject: [PATCH 0268/2916] fix: Fix crashes whith null specs --- kluctl/utils/k8s_cluster_real.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/kluctl/utils/k8s_cluster_real.py b/kluctl/utils/k8s_cluster_real.py index 7c53b81d1..471612d63 100644 --- a/kluctl/utils/k8s_cluster_real.py +++ b/kluctl/utils/k8s_cluster_real.py @@ -284,17 +284,17 @@ def fix_string_type(d, k): d[k] = str(d[k]) def fix_container(c): - fix_ports(c.get('ports', [])) - fix_string_type((c.get('resources') or {}).get('limits', None), 'cpu') - fix_string_type((c.get('resources') or {}).get('requests', None), 'cpu') + fix_ports(get_dict_value(c, "ports", [])) + fix_string_type(get_dict_value(c, "resources.limits"), "cpu") + fix_string_type(get_dict_value(c, "resources.requests"), "cpu") def fix_containers(containers): for c in containers: fix_container(c) - fix_containers(o.get('spec', {}).get('template', {}).get('spec', {}).get('containers', [])) - fix_ports(o.get('spec', {}).get('ports', [])) - for x in o.get('spec', {}).get('limits', []): + fix_containers(get_dict_value(o, "spec.template.spec.containers", [])) + fix_ports(get_dict_value(o, "spec.ports", [])) + for x in get_dict_value(o, "spec.limits", []): fix_string_type(x.get('default'), 'cpu') fix_string_type(x.get('defaultRequest'), 'cpu') return o From 61d48b8b67153daa971ac71b71eded42d7678d3f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 3 Nov 2021 15:21:40 +0100 Subject: [PATCH 0269/2916] feat: Allow to include sub-deployments from top-levels --- kluctl/cli/utils.py | 3 +- kluctl/deployment/deployment_collection.py | 22 +++++++------ kluctl/deployment/deployment_project.py | 10 +++++- kluctl/deployment/kustomize_deployment.py | 37 ++++++++++++---------- 4 files changed, 44 insertions(+), 28 deletions(-) diff --git a/kluctl/cli/utils.py b/kluctl/cli/utils.py index 7b4889a8f..6266407f1 100644 --- a/kluctl/cli/utils.py +++ b/kluctl/cli/utils.py @@ -144,7 +144,8 @@ def project_target_command_context(kwargs, kluctl_project, target, render_output_dir = kwargs.get("render_output_dir") if render_output_dir is None: render_output_dir = tmpdir - d = DeploymentProject(kluctl_project.deployment_dir, jinja_vars, deploy_args, kluctl_project.sealed_secrets_dir) + project_dir = os.path.realpath(kluctl_project.deployment_dir) + d = DeploymentProject(project_dir, jinja_vars, deploy_args, kluctl_project.sealed_secrets_dir) c = DeploymentCollection(d, images=images, inclusion=inclusion, tmpdir=render_output_dir, for_seal=for_seal) fixed_images = load_fixed_images(kwargs) diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index f3fcda44c..edfb03203 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -11,6 +11,7 @@ from kluctl.diff.k8s_diff import deep_diff_object from kluctl.diff.normalize import normalize_object from kluctl.utils.dict_utils import copy_dict, get_dict_value +from kluctl.utils.exceptions import CommandError from kluctl.utils.k8s_delete_utils import find_objects_for_delete from kluctl.utils.k8s_downscale_utils import downscale_object from kluctl.utils.k8s_object_utils import get_object_ref, get_long_object_name, get_long_object_name_from_ref, \ @@ -42,34 +43,37 @@ def __init__(self, project, images, inclusion, tmpdir, for_seal): self.inclusion = inclusion self.tmpdir = tmpdir self.for_seal = for_seal - self.deployments = self._collect_deployments(self.project) + self.deployments = self._collect_deployments(self.project, {}) self.remote_objects = {} self.errors = set() self.warnings = set() - def _collect_deployments(self, project): + def _collect_deployments(self, project, indexes): ret = [] - indexes = {} for c in project.conf['kustomizeDirs']: + dir = None index = 0 if "path" in c: - p = os.path.normpath(c["path"]) - index = indexes.setdefault(p, 0) - indexes[p] += 1 - deployment = KustomizeDeployment(project, self, c, index) + dir = os.path.join(project.dir, c["path"]) + dir = os.path.realpath(dir) + if not dir.startswith(project.get_root_deployment().dir): + raise CommandError("kustomizeDir path is not part of root deployment project: %s" % c["path"]) + index = indexes.setdefault(dir, 0) + indexes[dir] += 1 + deployment = KustomizeDeployment(project, self, c, dir, index) ret.append(deployment) for inc in project.conf['includes']: if get_dict_value(inc, "barrier", False): - deployment = KustomizeDeployment(project, self, {"barrier": True}, 0) + deployment = KustomizeDeployment(project, self, {"barrier": True}, None, 0) ret.append(deployment) d = inc.get('_included_deployment_collection') if d is not None: - ret += self._collect_deployments(d) + ret += self._collect_deployments(d, indexes) return ret diff --git a/kluctl/deployment/deployment_project.py b/kluctl/deployment/deployment_project.py index d28ddf906..2cc0114c1 100644 --- a/kluctl/deployment/deployment_project.py +++ b/kluctl/deployment/deployment_project.py @@ -131,7 +131,13 @@ def load_includes(self): for inc in self.conf['includes']: if 'path' not in inc: continue + root_project = self.get_root_deployment() + inc_dir = os.path.join(self.dir, inc["path"]) + inc_dir = os.path.realpath(inc_dir) + if not inc_dir.startswith(root_project.dir): + raise CommandError("Include path is not part of root deployment project: %s" % inc["path"]) + c = DeploymentProject(inc_dir, self.jinja_vars, self.deploy_args, self.sealed_secrets_dir, parent_collection=self) c.parent_collection_include = inc @@ -139,7 +145,7 @@ def load_includes(self): self.includes.append(c) - def get_sealed_secrets_dir(self, subdir): + def get_sealed_secrets_dir(self): root = self.get_root_deployment() output_pattern = get_dict_value(root.conf, "sealedSecrets.outputPattern") return output_pattern @@ -154,6 +160,8 @@ def get_rel_dir_to_root(self, subdir=None): root_dir = os.path.abspath(root.dir) dir = self.dir if subdir is not None: + if os.path.isabs(subdir): + raise CommandError("Path can't be absolute: %s" % subdir) dir = os.path.join(dir, subdir) dir = os.path.abspath(dir) rel_dir = os.path.relpath(dir, root_dir) diff --git a/kluctl/deployment/kustomize_deployment.py b/kluctl/deployment/kustomize_deployment.py index 063c61858..58ea76f2d 100644 --- a/kluctl/deployment/kustomize_deployment.py +++ b/kluctl/deployment/kustomize_deployment.py @@ -26,15 +26,17 @@ def get_kustomize_cache_dir(): allow_cache = True class KustomizeDeployment(object): - def __init__(self, deployment_project, deployment_collection, config, index): + def __init__(self, deployment_project, deployment_collection, config, dir, index): self.deployment_project = deployment_project self.deployment_collection = deployment_collection self.config = config + self.dir = dir self.index = index self.objects = [] def get_rel_kustomize_dir(self): - return self.deployment_project.get_rel_dir_to_root(self.config["path"]) + root_project = self.deployment_project.get_root_deployment() + return os.path.relpath(self.dir, root_project.dir) def get_rel_rendered_dir(self): path = self.get_rel_kustomize_dir() @@ -100,13 +102,14 @@ def build_jinja2_env(self, jinja_vars): return self.deployment_project.build_jinja2_env(jinja_vars) def render(self, executor): - if "path" not in self.config: + if self.dir is None: return [] - path = self.config["path"] - rendered_dir = self.get_rendered_dir() + rel_kustomize_dir = self.get_rel_kustomize_dir() + abs_kustomize_dir = os.path.join(self.deployment_project.get_root_deployment().dir, rel_kustomize_dir) + abs_rendered_dir = self.get_rendered_dir() - os.makedirs(rendered_dir, exist_ok=True) + os.makedirs(abs_rendered_dir, exist_ok=True) jinja_vars = self.deployment_project.jinja_vars if "vars" in self.config: @@ -115,18 +118,18 @@ def render(self, executor): jinja_env = self.build_jinja2_env(jinja_vars) excluded_patterns = self.deployment_project.conf['templateExcludes'].copy() - if os.path.exists(os.path.join(self.deployment_project.dir, path, 'helm-chart.yml')): + if os.path.exists(os.path.join(abs_kustomize_dir, 'helm-chart.yml')): # never try to render helm charts - excluded_patterns.append(os.path.join(path, 'charts/*')) + excluded_patterns.append(os.path.join(rel_kustomize_dir, 'charts/*')) if not self.deployment_collection.for_seal: # .sealme files are rendered while sealing and not while deploying excluded_patterns.append('*.sealme') - d = TemplatedDir(self.deployment_project.dir, jinja_env, executor, excluded_patterns) - return d.async_render_subdir(path, rendered_dir) + d = TemplatedDir(self.deployment_project.get_root_deployment().dir, jinja_env, executor, excluded_patterns) + return d.async_render_subdir(rel_kustomize_dir, abs_rendered_dir) def render_helm_charts(self, executor): - if "path" not in self.config: + if self.dir is None: return [] jobs = [] @@ -141,10 +144,10 @@ def render_helm_charts(self, executor): return jobs def resolve_sealed_secrets(self): - if "path" not in self.config: + if self.dir is None: return - sealed_secrets_dir = self.deployment_project.get_sealed_secrets_dir(self.config["path"]) + sealed_secrets_dir = self.deployment_project.get_sealed_secrets_dir() rel_rendered_dir = self.get_rel_rendered_dir() rendered_dir = self.get_rendered_dir() base_source_path = self.deployment_project.sealed_secrets_dir @@ -176,7 +179,7 @@ def get_tags(self): def build_inclusion_values(self): values = [("tag", tag) for tag in self.get_tags()] - if "path" in self.config: + if self.dir is not None: kustomize_dir = self.get_rel_kustomize_dir().replace(os.path.sep, "/") values.append(("kustomize_dir", kustomize_dir)) return values @@ -201,7 +204,7 @@ def check_inclusion_for_delete(self): return inclusion.check_included(values, skip_delete_if_tags) def prepare_kustomization_yaml(self): - if "path" not in self.config: + if self.dir is None: return rendered_dir = self.get_rendered_dir() @@ -218,7 +221,7 @@ def prepare_kustomization_yaml(self): yaml_save_file(kustomize_yaml, kustomize_yaml_path) def build_kustomize(self): - if "path" not in self.config: + if self.dir is None: return self.prepare_kustomization_yaml() @@ -240,7 +243,7 @@ def build_kustomize(self): shutil.copy(self.get_rendered_yamls_path(), hash_path) def postprocess_and_load_objects(self, k8s_cluster): - if "path" not in self.config: + if self.dir is None: return self.objects = yaml_load_file(self.get_rendered_yamls_path(), all=True) From 76af0dc64bfaff49723a4ba7435c0c6dee705041 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 3 Nov 2021 15:57:48 +0100 Subject: [PATCH 0270/2916] feat: Allow loading of vars from cluster configmaps and secrets --- kluctl/cli/utils.py | 2 +- kluctl/deployment/deployment_collection.py | 6 ++-- kluctl/deployment/deployment_project.py | 36 +++++++++++++++++----- kluctl/deployment/kustomize_deployment.py | 4 +-- kluctl/seal/seal_command.py | 2 +- kluctl/tests/test_base.py | 4 +-- 6 files changed, 37 insertions(+), 17 deletions(-) diff --git a/kluctl/cli/utils.py b/kluctl/cli/utils.py index 6266407f1..3d5d264f0 100644 --- a/kluctl/cli/utils.py +++ b/kluctl/cli/utils.py @@ -145,7 +145,7 @@ def project_target_command_context(kwargs, kluctl_project, target, if render_output_dir is None: render_output_dir = tmpdir project_dir = os.path.realpath(kluctl_project.deployment_dir) - d = DeploymentProject(project_dir, jinja_vars, deploy_args, kluctl_project.sealed_secrets_dir) + d = DeploymentProject(k8s_cluster, project_dir, jinja_vars, deploy_args, kluctl_project.sealed_secrets_dir) c = DeploymentCollection(d, images=images, inclusion=inclusion, tmpdir=render_output_dir, for_seal=for_seal) fixed_images = load_fixed_images(kwargs) diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index edfb03203..14df20fa6 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -77,13 +77,13 @@ def _collect_deployments(self, project, indexes): return ret - def render_deployments(self): + def render_deployments(self, k8s_cluster): logger.info("Rendering templates and Helm charts") with MyThreadPoolExecutor(max_workers=16) as executor: jobs = [] for d in self.deployments: - jobs += d.render(executor) + jobs += d.render(k8s_cluster, executor) TemplatedDir.finish_jobs(jobs) @@ -140,7 +140,7 @@ def local_objects_by_ref(self): return by_ref def prepare(self, k8s_cluster): - self.render_deployments() + self.render_deployments(k8s_cluster) self.resolve_sealed_secrets() self.build_kustomize_objects(k8s_cluster) self.update_remote_objects(k8s_cluster) diff --git a/kluctl/deployment/deployment_project.py b/kluctl/deployment/deployment_project.py index 2cc0114c1..c36ab24a3 100644 --- a/kluctl/deployment/deployment_project.py +++ b/kluctl/deployment/deployment_project.py @@ -10,13 +10,14 @@ from kluctl.utils.external_args import check_required_args from kluctl.utils.jinja2_cache import KluctlBytecodeCache from kluctl.utils.jinja2_utils import add_jinja2_filters, KluctlJinja2Environment +from kluctl.utils.k8s_object_utils import ObjectRef, get_long_object_name_from_ref from kluctl.utils.yaml_utils import yaml_load, yaml_load_file logger = logging.getLogger(__name__) class DeploymentProject(object): - def __init__(self, dir, jinja_vars, deploy_args, sealed_secrets_dir, + def __init__(self, k8s_cluster, dir, jinja_vars, deploy_args, sealed_secrets_dir, parent_collection=None): if not os.path.exists(dir): raise CommandError("%s does not exist" % dir) @@ -33,8 +34,8 @@ def __init__(self, dir, jinja_vars, deploy_args, sealed_secrets_dir, self.parent_collection_include = None self.check_required_args() self.jinja_vars['args'] = self.deploy_args # need to update 'args' after applying default values in check_required_args - self.load_base_conf() - self.load_includes() + self.load_base_conf(k8s_cluster) + self.load_includes(k8s_cluster) def fix_relative_paths(self, y, key, abs_path): for x in y: @@ -52,13 +53,19 @@ def build_jinja2_env(self, jinja_vars): add_jinja2_filters(environment) return environment - def load_jinja_vars_list(self, vars_list, cur_jinja_vars): + def load_jinja_vars_list(self, k8s_cluster, vars_list, cur_jinja_vars): jinja_vars = copy_dict(cur_jinja_vars) for v in vars_list: if "values" in v: merge_dict(jinja_vars, v["values"], False) elif "file" in v: jinja_vars = self.load_jinja_vars_file(v["file"], jinja_vars) + elif "clusterConfigMap" in v: + ref = ObjectRef(api_version="v1", kind="ConfigMap", name=v["clusterConfigMap"]["name"], namespace=v["clusterConfigMap"]["namespace"]) + jinja_vars = self.load_jinja_vars_k8s_object(k8s_cluster, ref, v["clusterConfigMap"]["key"], jinja_vars) + elif "clusterSecret" in v: + ref = ObjectRef(api_version="v1", kind="Secret", name=v["clusterSecret"]["name"], namespace=v["clusterSecret"]["namespace"]) + jinja_vars = self.load_jinja_vars_k8s_object(k8s_cluster, ref, v["clusterSecret"]["key"], jinja_vars) else: raise CommandError("Invalid vars entry") return jinja_vars @@ -68,13 +75,26 @@ def load_jinja_vars_file(self, rel_file, cur_jinja_vars): jinja_vars = merge_dict(cur_jinja_vars, new_vars) return jinja_vars + def load_jinja_vars_k8s_object(self, k8s_cluster, ref, key, cur_jinja_vars): + o, _ = k8s_cluster.get_single_object(ref) + if o is None: + raise CommandError("%s not found on cluster %s" % (get_long_object_name_from_ref(ref), k8s_cluster.context)) + value = get_dict_value(o, "data.%s" % key) + if value is None: + raise CommandError("Key %s not found in %s on cluster %s" % (key, get_long_object_name_from_ref(ref), k8s_cluster.context)) + jinja_env = self.build_jinja2_env(cur_jinja_vars) + template = jinja_env.from_string(value) + new_vars = yaml_load(template.render()) + jinja_vars = merge_dict(cur_jinja_vars, new_vars) + return jinja_vars + def load_rendered_yaml(self, rel_path, jinja_vars): jinja_env = self.build_jinja2_env(jinja_vars) template = jinja_env.get_template(rel_path.replace('\\', '/')) rendered = template.render() return yaml_load(rendered) - def load_base_conf(self): + def load_base_conf(self, k8s_cluster): base_conf_path = os.path.join(self.dir, 'deployment.yml') if not os.path.exists(base_conf_path): if os.path.exists(os.path.join(self.dir, 'kustomization.yml')): @@ -96,7 +116,7 @@ def load_base_conf(self): set_dict_default_value(self.conf, 'tags', []) set_dict_default_value(self.conf, 'templateExcludes', []) - self.jinja_vars = self.load_jinja_vars_list(self.conf['vars'], self.jinja_vars) + self.jinja_vars = self.load_jinja_vars_list(k8s_cluster, self.conf['vars'], self.jinja_vars) for c in self.conf['kustomizeDirs'] + self.conf['includes']: if 'tags' not in c and 'path' in c: @@ -127,7 +147,7 @@ def check_required_args(self): self.deploy_args = check_required_args(conf["args"], self.deploy_args) - def load_includes(self): + def load_includes(self, k8s_cluster): for inc in self.conf['includes']: if 'path' not in inc: continue @@ -138,7 +158,7 @@ def load_includes(self): if not inc_dir.startswith(root_project.dir): raise CommandError("Include path is not part of root deployment project: %s" % inc["path"]) - c = DeploymentProject(inc_dir, self.jinja_vars, self.deploy_args, self.sealed_secrets_dir, parent_collection=self) + c = DeploymentProject(k8s_cluster, inc_dir, self.jinja_vars, self.deploy_args, self.sealed_secrets_dir, parent_collection=self) c.parent_collection_include = inc inc['_included_deployment_collection'] = c diff --git a/kluctl/deployment/kustomize_deployment.py b/kluctl/deployment/kustomize_deployment.py index 58ea76f2d..79fc35b90 100644 --- a/kluctl/deployment/kustomize_deployment.py +++ b/kluctl/deployment/kustomize_deployment.py @@ -101,7 +101,7 @@ def build_jinja2_env(self, jinja_vars): }, clone=False) return self.deployment_project.build_jinja2_env(jinja_vars) - def render(self, executor): + def render(self, k8s_cluster, executor): if self.dir is None: return [] @@ -113,7 +113,7 @@ def render(self, executor): jinja_vars = self.deployment_project.jinja_vars if "vars" in self.config: - jinja_vars = self.deployment_project.load_jinja_vars_list(self.config["vars"], jinja_vars) + jinja_vars = self.deployment_project.load_jinja_vars_list(k8s_cluster, self.config["vars"], jinja_vars) jinja_env = self.build_jinja2_env(jinja_vars) diff --git a/kluctl/seal/seal_command.py b/kluctl/seal/seal_command.py index 5cfef3e5e..01310debf 100644 --- a/kluctl/seal/seal_command.py +++ b/kluctl/seal/seal_command.py @@ -40,7 +40,7 @@ def seal_command_for_target(kwargs, kluctl_project, target, sealing_config, secr # pass for_seal=True so that .sealme files are rendered as well with project_target_command_context(kwargs, kluctl_project, target, for_seal=True) as cmd_ctx: load_secrets(kluctl_project, target, secrets_loader, cmd_ctx.deployment) - cmd_ctx.deployment_collection.render_deployments() + cmd_ctx.deployment_collection.render_deployments(cmd_ctx.k8s_cluster) s = DeploymentSealer(cmd_ctx.deployment, cmd_ctx.deployment_collection, cmd_ctx.cluster_vars, cmd_ctx.kluctl_project.sealed_secrets_dir, kwargs["force_reseal"]) diff --git a/kluctl/tests/test_base.py b/kluctl/tests/test_base.py index 3157be1b8..ce6278c18 100644 --- a/kluctl/tests/test_base.py +++ b/kluctl/tests/test_base.py @@ -39,7 +39,7 @@ def tearDown(self): @contextlib.contextmanager def build_deployment(self, subdir, jinja_vars={}, deploy_args={}): with tempfile.TemporaryDirectory(dir=get_tmp_base_dir()) as tmpdir: - d = DeploymentProject(os.path.join(cur_dir, subdir), jinja_vars, deploy_args, + d = DeploymentProject(None, os.path.join(cur_dir, subdir), jinja_vars, deploy_args, os.path.join(cur_dir, subdir, '.sealed-secrets')) c = DeploymentCollection(d, None, None, tmpdir, False) yield d, c @@ -47,5 +47,5 @@ def build_deployment(self, subdir, jinja_vars={}, deploy_args={}): @contextlib.contextmanager def render_deployment(self, subdir, jinja_vars={}, deploy_args={}): with self.build_deployment(subdir, jinja_vars, deploy_args) as (d, c): - c.render_deployments() + c.render_deployments(None) yield c From 2fab15ca9bde907f392b33bee9fe6037e3132532 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 3 Nov 2021 16:39:33 +0100 Subject: [PATCH 0271/2916] fix: Fix duplicate words --- kluctl/deployment/apply_util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kluctl/deployment/apply_util.py b/kluctl/deployment/apply_util.py index f60abe5b8..5336a34b2 100644 --- a/kluctl/deployment/apply_util.py +++ b/kluctl/deployment/apply_util.py @@ -135,7 +135,7 @@ def wait_object(self, ref): return False if not did_log: - logger.info("Waiting for for hook %s to get ready..." % get_long_object_name_from_ref(ref)) + logger.info("Waiting for hook %s to get ready..." % get_long_object_name_from_ref(ref)) did_log = True def apply_kustomize_deployment(self, d): From edea6727b30f2ec1714c4d5f904daf95aa82bba4 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 11 Nov 2021 23:51:10 +0100 Subject: [PATCH 0272/2916] fix: Reimplemnt TemplatedDir and make it properly perform exclusion checks --- kluctl/deployment/deployment_project.py | 12 ----- kluctl/deployment/kustomize_deployment.py | 9 ++-- kluctl/utils/templated_dir.py | 55 +++++++++++------------ 3 files changed, 32 insertions(+), 44 deletions(-) diff --git a/kluctl/deployment/deployment_project.py b/kluctl/deployment/deployment_project.py index c36ab24a3..213456e7a 100644 --- a/kluctl/deployment/deployment_project.py +++ b/kluctl/deployment/deployment_project.py @@ -175,18 +175,6 @@ def get_root_deployment(self): return self return self.parent_collection.get_root_deployment() - def get_rel_dir_to_root(self, subdir=None): - root = self.get_root_deployment() - root_dir = os.path.abspath(root.dir) - dir = self.dir - if subdir is not None: - if os.path.isabs(subdir): - raise CommandError("Path can't be absolute: %s" % subdir) - dir = os.path.join(dir, subdir) - dir = os.path.abspath(dir) - rel_dir = os.path.relpath(dir, root_dir) - return rel_dir - def get_parents(self): parents = [] d = self diff --git a/kluctl/deployment/kustomize_deployment.py b/kluctl/deployment/kustomize_deployment.py index 79fc35b90..befecf8f6 100644 --- a/kluctl/deployment/kustomize_deployment.py +++ b/kluctl/deployment/kustomize_deployment.py @@ -105,8 +105,9 @@ def render(self, k8s_cluster, executor): if self.dir is None: return [] - rel_kustomize_dir = self.get_rel_kustomize_dir() - abs_kustomize_dir = os.path.join(self.deployment_project.get_root_deployment().dir, rel_kustomize_dir) + root_dir = self.deployment_project.get_root_deployment().dir + rel_deployment_dir = os.path.relpath(self.deployment_project.dir, root_dir) + rel_kustomize_dir = os.path.relpath(self.get_rel_kustomize_dir(), rel_deployment_dir) abs_rendered_dir = self.get_rendered_dir() os.makedirs(abs_rendered_dir, exist_ok=True) @@ -118,14 +119,14 @@ def render(self, k8s_cluster, executor): jinja_env = self.build_jinja2_env(jinja_vars) excluded_patterns = self.deployment_project.conf['templateExcludes'].copy() - if os.path.exists(os.path.join(abs_kustomize_dir, 'helm-chart.yml')): + if os.path.exists(os.path.join(self.dir, 'helm-chart.yml')): # never try to render helm charts excluded_patterns.append(os.path.join(rel_kustomize_dir, 'charts/*')) if not self.deployment_collection.for_seal: # .sealme files are rendered while sealing and not while deploying excluded_patterns.append('*.sealme') - d = TemplatedDir(self.deployment_project.get_root_deployment().dir, jinja_env, executor, excluded_patterns) + d = TemplatedDir(root_dir, rel_deployment_dir, jinja_env, executor, excluded_patterns) return d.async_render_subdir(rel_kustomize_dir, abs_rendered_dir) def render_helm_charts(self, executor): diff --git a/kluctl/utils/templated_dir.py b/kluctl/utils/templated_dir.py index 91ec519f1..efae0d15a 100644 --- a/kluctl/utils/templated_dir.py +++ b/kluctl/utils/templated_dir.py @@ -7,8 +7,9 @@ class TemplatedDir(object): - def __init__(self, dir, jinja_env, executor, excluded_patterns): - self.dir = dir + def __init__(self, root_dir, rel_source_dir, jinja_env, executor, excluded_patterns): + self.root_dir = root_dir + self.rel_source_dir = rel_source_dir self.jinja_env = jinja_env self.excluded_patterns = excluded_patterns self.executor = executor @@ -19,10 +20,32 @@ def needs_render(self, path): return False return True - def async_render_subdir(self, rel_dir, target_dir): - jobs = self.do_render_dir(rel_dir, target_dir) + def async_render_subdir(self, subdir, target_dir): + jobs = [] + + walk_dir = os.path.join(self.root_dir, self.rel_source_dir, subdir) + for dirpath, dirnames, filenames in os.walk(walk_dir): + rel_dirpath = os.path.relpath(dirpath, walk_dir) + for n in dirnames: + target_path = os.path.join(target_dir, rel_dirpath, n) + os.mkdir(target_path) + for n in filenames: + source_path = os.path.join(subdir, rel_dirpath, n) + target_path = os.path.join(target_dir, rel_dirpath, n) + if self.needs_render(source_path): + f = self.executor.submit(self.do_render_file, os.path.join(self.rel_source_dir, source_path), target_path) + else: + f = self.executor.submit(shutil.copy, os.path.join(self.root_dir, self.rel_source_dir, source_path), target_path) + jobs.append(f) + return jobs + def do_render_file(self, source_path, target_path): + template = self.jinja_env.get_template(source_path.replace("\\", "/")) + rendered = template.render() + with open(target_path, "wt") as f: + f.write(rendered) + @staticmethod def finish_jobs(jobs): error = None @@ -34,27 +57,3 @@ def finish_jobs(jobs): error = e if error is not None: raise error - - def do_render_dir(self, rel_source_dir, target_dir): - jobs = [] - abs_source_dir = os.path.join(self.dir, rel_source_dir) - for name in os.listdir(abs_source_dir): - rel_source_path = os.path.join(rel_source_dir, name) - abs_source_path = os.path.join(abs_source_dir, name) - abs_target_path = os.path.join(target_dir, name) - if os.path.isdir(abs_source_path): - os.mkdir(abs_target_path) - jobs += self.do_render_dir(rel_source_path, abs_target_path) - elif self.needs_render(rel_source_path): - f = self.executor.submit(self.do_render_file, rel_source_path, abs_target_path) - jobs.append(f) - else: - shutil.copy(abs_source_path, abs_target_path) - - return jobs - - def do_render_file(self, rel_source_path, abs_target_path): - template = self.jinja_env.get_template(rel_source_path.replace('\\', '/')) - rendered = template.render() - with open(abs_target_path, "wt") as f: - f.write(rendered) From 0ec8ebfc51f6bccfc29f71c6ad8705a8295b427d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 11 Nov 2021 23:52:53 +0100 Subject: [PATCH 0273/2916] fix: Reimplement server-side apply conflict handling and rely on API responses --- kluctl/deployment/apply_util.py | 108 +++++++++++++++---------- kluctl/diff/managed_fields.py | 137 ++++++++++++++++---------------- kluctl/utils/dict_utils.py | 9 +++ 3 files changed, 141 insertions(+), 113 deletions(-) diff --git a/kluctl/deployment/apply_util.py b/kluctl/deployment/apply_util.py index 5336a34b2..d259a7f22 100644 --- a/kluctl/deployment/apply_util.py +++ b/kluctl/deployment/apply_util.py @@ -1,8 +1,9 @@ +import json import logging import threading from kubernetes.client import ApiException -from kubernetes.dynamic.exceptions import ResourceNotFoundError +from kubernetes.dynamic.exceptions import ResourceNotFoundError, ConflictError from kluctl.deployment.hooks_util import HooksUtil from kluctl.diff.managed_fields import resolve_field_manager_conflicts @@ -55,67 +56,86 @@ def delete_object(self, ref): def apply_object(self, x, replaced, hook): logger.debug(f" {get_long_object_name(x)}") - x2 = self.k8s_cluster.fix_object_for_patch(x) - if not self.force_apply: - ref = get_object_ref(x) - remote_object = self.deployment_collection.remote_objects.get(ref) - try: - x2, overwritten = resolve_field_manager_conflicts(x2, remote_object) - except Exception as e: - self.handle_error(ref, str(e)) - return - warnings = [] - for ow in overwritten: - warnings.append("Field '%s' is now owned by field manager '%s'. " - "It is NOT updated to the desired value '%s'!" % (ow.path, ow.field_manager, ow.local_value)) - self.deployment_collection.add_warnings(ref, warnings) + x = self.k8s_cluster.fix_object_for_patch(x) + + ref = get_object_ref(x) + remote_object = self.deployment_collection.remote_objects.get(ref) if self.dry_run and replaced and get_object_ref(x) in self.deployment_collection.remote_objects: # Let's simulate that this object was deleted in dry-run mode. If we'd actually try a dry-run apply with # this object, it might fail as it is expected to not exist. - self.handle_result(x2, [], hook) + self.handle_result(x, [], hook) return - try: - r, patch_warnings = self.k8s_cluster.patch_object(x2, force_dry_run=self.dry_run, force_apply=True) - self.handle_result(r, patch_warnings, hook) - except ResourceNotFoundError as e: - ref = get_object_ref(x) - self.handle_error(ref, self.k8s_cluster.get_status_message(e)) - except ApiException as e: - ref = get_object_ref(x) - if not self.replace_on_error: + def retry_with_force_replace(e): + if not self.force_replace_on_error: + self.handle_error(ref, self.k8s_cluster.get_status_message(e)) + return + + logger.warning("Patching %s failed, retrying by deleting and re-applying" % + get_long_object_name_from_ref(ref)) + try: + self.k8s_cluster.delete_single_object(ref, force_dry_run=self.dry_run, ignore_not_found=True) + if not self.dry_run: + r, patch_warnings = self.k8s_cluster.patch_object(x, force_apply=True) + self.handle_result(r, patch_warnings, hook) + else: + self.handle_result(x, [], hook) + except ApiException as e2: + self.handle_error(ref, self.k8s_cluster.get_status_message(e2)) + + def retry_with_replace(e): + if not self.replace_on_error or remote_object is None: self.handle_error(ref, self.k8s_cluster.get_status_message(e)) return + logger.warning("Patching %s failed, retrying with replace instead of patch" % get_long_object_name_from_ref(ref)) + + resource_version = get_dict_value(remote_object, "metadata.resourceVersion") + x2 = set_dict_value(x, "metadata.resourceVersion", resource_version, do_clone=True) + try: - remote_object = self.deployment_collection.remote_objects.get(ref) - if remote_object is None: - self.handle_error(ref, self.k8s_cluster.get_status_message(e)) - return - resource_version = get_dict_value(remote_object, "metadata.resourceVersion") - x2 = set_dict_value(x, "metadata.resourceVersion", resource_version, do_clone=True) r, patch_warnings = self.k8s_cluster.replace_object(x2, force_dry_run=self.dry_run) self.handle_result(r, patch_warnings, hook) except ApiException as e2: - self.handle_error(ref, self.k8s_cluster.get_status_message(e2)) + retry_with_force_replace(e2) - if not self.force_replace_on_error: - self.handle_error(ref, self.k8s_cluster.get_status_message(e)) - return + def retry_with_conflicts(e): + if remote_object is None: + self.handle_error(ref, self.k8s_cluster.get_status_message(e)) + return + + resolve_warnings = [] + if not self.force_apply: + status = json.loads(e.body) - logger.warning("Patching %s failed, retrying by deleting and re-applying" % - get_long_object_name_from_ref(ref)) try: - self.k8s_cluster.delete_single_object(ref, force_dry_run=self.dry_run, ignore_not_found=True) - if not self.dry_run: - r, patch_warnings = self.k8s_cluster.patch_object(x, force_apply=True) - self.handle_result(r, patch_warnings, hook) - else: - self.handle_result(x, [], hook) - except ApiException as e2: + x2, lost_ownership = resolve_field_manager_conflicts(x, remote_object, status) + for cause in lost_ownership: + resolve_warnings.append("%s. Not updating field '%s' as we lost field ownership." % (cause["message"], cause["field"])) + except Exception as e2: self.handle_error(ref, self.k8s_cluster.get_status_message(e2)) + return + else: + x2 = x + + try: + r, patch_warnings = self.k8s_cluster.patch_object(x2, force_dry_run=self.dry_run, force_apply=True) + self.handle_result(r, patch_warnings + resolve_warnings, hook) + except ApiException as e: + # We didn't manage to solve it, better to abort (and not retry with replace!) + self.handle_error(ref, e) + + try: + r, patch_warnings = self.k8s_cluster.patch_object(x, force_dry_run=self.dry_run) + self.handle_result(r, patch_warnings, hook) + except ResourceNotFoundError as e: + self.handle_error(ref, self.k8s_cluster.get_status_message(e)) + except ConflictError as e: + retry_with_conflicts(e) + except ApiException as e: + retry_with_replace(e) def wait_object(self, ref): if self.dry_run: diff --git a/kluctl/diff/managed_fields.py b/kluctl/diff/managed_fields.py index 28500f75c..df84430e3 100644 --- a/kluctl/diff/managed_fields.py +++ b/kluctl/diff/managed_fields.py @@ -1,10 +1,8 @@ -import dataclasses import json import re -from typing import Any from kluctl.utils.dict_utils import copy_dict, object_iterator, del_dict_value, get_dict_value, is_iterable, \ - set_dict_value + has_dict_value from kluctl.utils.jsonpath_utils import convert_list_to_json_path # We automatically force overwrite these fields as we assume these are human-edited @@ -16,15 +14,6 @@ "k9s", ] -ignore_managers = { - # If kube-apiserver claimed a field, then this was for a good reason, so lets not complain about it - "kube-apiserver", -} - -ignore_fields = { - ("metadata", "creationTimestamp"), -} - def check_item_match(o, kv, index): if kv[0:2] == "k:": k = kv[2:] @@ -81,71 +70,81 @@ def convert_to_json_path(o, mf_path): return ret, False return ret, True -not_found = object() - -@dataclasses.dataclass -class OverwrittenField: - path: str - local_value: Any - remote_value: Any - field_manager: str - -def resolve_field_manager_conflicts(local_object, remote_object): - overwritten = [] +def convert_to_string(mf_path): + ret = [] + for i in range(len(mf_path)): + k = mf_path[i] + if k == "{}": + raise Exception() + if k == ".": + if i != len(mf_path) - 1: + raise ValueError("Unexpected . element at %s" % convert_list_to_json_path(ret)) + break + if k[:2] == "f:": + k = k[2:] + ret.append(".%s" % k) + elif k[:2] == "k:": + strs = [] + k = json.loads(k[2:]) + for k2 in sorted(k.keys()): + strs.append("%s=%s" % (k2, json.dumps(k[k2]))) + ret.append("[%s]" % ",".join(strs)) + elif k[:2] == "v:": + ret.append("[=%s]" % k[2:]) + elif k[:2] == "i:": + ret.append("[%s]" % k[2:]) + else: + raise ValueError("Invalid path element %s" % k) + return "".join(ret) +def resolve_field_manager_conflicts(local_object, remote_object, conflict_status): managed_fields = get_dict_value(remote_object, "metadata.managedFields") if managed_fields is None: - return local_object, overwritten + raise Exception("managedFields not found") v1_fields = [mf for mf in managed_fields if mf['fieldsType'] == 'FieldsV1'] - local_field_owners = {} + # "stupid" because the string representation in "details.causes.field" might be ambiguous as k8s does not escape dots + fields_as_stupid_strings = {} + managers_by_field = {} for mf in v1_fields: for v, p in object_iterator(mf["fieldsV1"], only_leafs=True): - local_json_path, local_found = convert_to_json_path(local_object, p) + s = convert_to_string(p) + fields_as_stupid_strings.setdefault(s, set()).add(tuple(p)) + managers_by_field.setdefault(tuple(p), []).append(mf) - if local_found: - local_field_owners[tuple(local_json_path)] = mf + ret = copy_dict(local_object) - def find_owner(owners, p): - tp = tuple(p) - fm = None - while len(tp) > 0: - fm = owners.get(tp) - if fm is not None: + lost_ownership = [] + for cause in get_dict_value(conflict_status, "details.causes", []): + if cause.get("reason") != "FieldManagerConflict" or "field" not in cause: + raise Exception("Unknown reason '%s'" % cause.get("reason")) + + mf_path = fields_as_stupid_strings.get(cause["field"]) + if not mf_path: + raise Exception("Could not find matching field for path '%s'" % cause["field"]) + if len(mf_path) != 1: + raise Exception("field path '%s' is ambiguous" % cause["field"]) + mf_path = list(mf_path)[0] + + p, found = convert_to_json_path(remote_object, mf_path) + if not found: + raise Exception("Field '%s' not found in remote object" % cause["field"]) + if not has_dict_value(local_object, p): + raise Exception("Field '%s' not found in local object" % cause["field"]) + + overwrite = True + for mf in managers_by_field[mf_path]: + if not any(re.fullmatch(x, mf["manager"]) for x in overwrite_allowed_managers): + overwrite = False break - tp = tp[:-1] - return fm, tp - ret = copy_dict(local_object) - to_delete = set() - for v, p in object_iterator(local_object, only_leafs=True): - remote_value = get_dict_value(remote_object, p, not_found) - - fm, tp = find_owner(local_field_owners, p) - - did_overwrite = False - if fm is None: - # No manager found that claimed this field. If it's not existing in the remote object, it means it's a - # new field so we can safely claim it. If it's present in the remote object AND has changed, it's a system - # field that we have no control over! - if remote_value is not not_found: - if v != remote_value: - set_dict_value(ret, p, remote_value) - did_overwrite = True - elif not any(re.fullmatch(x, fm["manager"]) for x in overwrite_allowed_managers): - to_delete.add(tp) - did_overwrite = True - - ignore = (fm and fm["manager"] in ignore_managers) or tuple(p) in ignore_fields - if did_overwrite and v != remote_value and not ignore: - overwritten.append(OverwrittenField(path=convert_list_to_json_path(p), - local_value=v if v is not not_found else None, - remote_value=remote_value if remote_value is not not_found else None, - field_manager=fm["manager"] if fm else "")) - - for p in to_delete: - # We do not own this field, so we should also not set it (not even to the same value to ensure we don't - # claim shared ownership) - del_dict_value(ret, p) - - return ret, overwritten + if not overwrite: + local_value = get_dict_value(local_object, p) + remote_value = get_dict_value(remote_object, p) + + del_dict_value(ret, p) + + if local_value != remote_value: + lost_ownership.append(cause) + + return ret, lost_ownership diff --git a/kluctl/utils/dict_utils.py b/kluctl/utils/dict_utils.py index fc175f067..378043eec 100644 --- a/kluctl/utils/dict_utils.py +++ b/kluctl/utils/dict_utils.py @@ -49,6 +49,15 @@ def get_dict_value(y, path, default=None): return default return f[0].value +def has_dict_value(y, path): + p = parse_json_path(path) + f = p.find(y) + if len(f) > 1: + raise Exception("Only simple jsonpath supported in get_dict_value") + if len(f) == 0: + return False + return True + def set_dict_value(y, path, value, do_clone=False): if do_clone: y = copy_dict(y) From 9f8e2db98c83fdcfd04f9dc9cbcfa99db875fedf Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 11 Nov 2021 23:54:35 +0100 Subject: [PATCH 0274/2916] feat: Support oci:// helm charts --- kluctl/deployment/helm_chart.py | 38 ++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/kluctl/deployment/helm_chart.py b/kluctl/deployment/helm_chart.py index 0edfbc4ef..e2585ada8 100644 --- a/kluctl/deployment/helm_chart.py +++ b/kluctl/deployment/helm_chart.py @@ -1,6 +1,7 @@ import contextlib import hashlib import os +import re import shutil from kluctl.utils.exceptions import CommandError @@ -38,28 +39,45 @@ def repo_context(self): if need_repo: self.do_helm(['repo', 'remove', repo_name], ignoreErrors=True, ignoreStderr=True) + def get_chart_name(self): + if self.conf.get("repo", "").startswith("oci://"): + chart_name = self.conf["repo"].split("/")[-1] + if not re.fullmatch(r"[a-zA-Z_-]+", chart_name): + raise CommandError("Invalid oci chart url: %s" % self.conf["repo"]) + return chart_name + else: + return self.conf["chartName"] + def pull(self): target_dir = os.path.join(self.dir, 'charts') - with self.repo_context() as repo_name: - rm_dir = os.path.join(target_dir, self.conf['chartName']) - shutil.rmtree(rm_dir, ignore_errors=True) + rm_dir = os.path.join(target_dir, self.get_chart_name()) + shutil.rmtree(rm_dir, ignore_errors=True) - args = ['pull', '%s/%s' % (repo_name, self.conf['chartName']), '--destination', target_dir, '--untar'] + if self.conf.get("repo", "").startswith("oci://"): + args = ['pull', self.conf["repo"], '--destination', target_dir, '--untar'] if 'chartVersion' in self.conf: args += ['--version', self.conf['chartVersion']] self.do_helm(args) + else: + with self.repo_context() as repo_name: + args = ['pull', '%s/%s' % (repo_name, self.get_chart_name()), '--destination', target_dir, '--untar'] + if 'chartVersion' in self.conf: + args += ['--version', self.conf['chartVersion']] + self.do_helm(args) def check_update(self): + if self.conf.get("repo", "").startswith("oci://"): + return None with self.repo_context() as repo_name: - chart_name = '%s/%s' % (repo_name, self.conf['chartName']) + chart_name = '%s/%s' % (repo_name, self.get_chart_name()) args = ['search', 'repo', chart_name, '-oyaml', '-l'] r, stdout, stderr = self.do_helm(args) l = yaml_load(stdout) # ensure we didn't get partial matches l = [x for x in l if x["name"] == chart_name] if len(l) == 0: - raise Exception("Helm chart %s not found in repository" % self.conf['chartName']) + raise Exception("Helm chart %s not found in repository" % self.get_chart_name()) l.sort(key=lambda x: LooseVersionComparator(x["version"])) latest_chart = l[-1] latest_version = latest_chart["version"] @@ -68,7 +86,7 @@ def check_update(self): return latest_version def render(self): - chart_dir = os.path.join(self.dir, 'charts', self.conf['chartName']) + chart_dir = os.path.join(self.dir, 'charts', self.get_chart_name()) values_path = os.path.join(self.dir, 'helm-values.yml') output_path = os.path.join(self.dir, self.conf['output']) @@ -102,8 +120,12 @@ def render(self): def do_helm(self, args, input=None, ignoreErrors=False, ignoreStderr=False): args = ['helm'] + args + env = { + "HELM_EXPERIMENTAL_OCI": "true", + **os.environ, + } - r, stdout, stderr = run_helper(args=args, input=input, print_stdout=False, print_stderr=not ignoreStderr, return_std=True) + r, stdout, stderr = run_helper(args=args, env=env, input=input, print_stdout=False, print_stderr=not ignoreStderr, return_std=True) if r != 0 and not ignoreErrors: raise CommandError("helm failed") return r, stdout, stderr From 1e2da74a33a20c2bfdd250c717fde5afde988f7e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 12 Nov 2021 00:07:44 +0100 Subject: [PATCH 0275/2916] fix: Normalize path before checking for exlusion --- kluctl/utils/templated_dir.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kluctl/utils/templated_dir.py b/kluctl/utils/templated_dir.py index efae0d15a..2ff8dc943 100644 --- a/kluctl/utils/templated_dir.py +++ b/kluctl/utils/templated_dir.py @@ -30,7 +30,7 @@ def async_render_subdir(self, subdir, target_dir): target_path = os.path.join(target_dir, rel_dirpath, n) os.mkdir(target_path) for n in filenames: - source_path = os.path.join(subdir, rel_dirpath, n) + source_path = os.path.normpath(os.path.join(subdir, rel_dirpath, n)) target_path = os.path.join(target_dir, rel_dirpath, n) if self.needs_render(source_path): f = self.executor.submit(self.do_render_file, os.path.join(self.rel_source_dir, source_path), target_path) From 301cf9964d93adb79d52922e5e035f5a2b56c720 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 12 Nov 2021 01:17:19 +0100 Subject: [PATCH 0276/2916] fix: Upgrade kustomize version in docker image and in tests --- .github/workflows/tests.yml | 2 +- Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index cc9a5a9f8..077e17ebc 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -106,7 +106,7 @@ jobs: run: | echo "KUBECTL_VERSION=1.21.5" >> $GITHUB_ENV echo "KIND_VERSION=0.11.1" >> $GITHUB_ENV - echo "KUSTOMIZE_VERSION=4.1.3" >> $GITHUB_ENV + echo "KUSTOMIZE_VERSION=4.4.1" >> $GITHUB_ENV echo "HELM_VERSION=3.6.3" >> $GITHUB_ENV echo "KUBESEAL_VERSION=0.16.0" >> $GITHUB_ENV echo "DOCKER_VERSION=20.10.9" >> $GITHUB_ENV diff --git a/Dockerfile b/Dockerfile index 2f0fd2485..d9a5f3161 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ FROM python:3.8.10-slim-buster RUN apt update && apt install wget libyaml-dev git -y && rm -rf /var/lib/apt/lists/* # Install tools -ENV KUSTOMIZE_VERSION=v4.4.0 +ENV KUSTOMIZE_VERSION=v4.4.1 ENV HELM_VERSION=v3.7.0 ENV KUBESEAL_VERSION=v0.16.0 RUN wget -O kustomize.tar.gz https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2F${KUSTOMIZE_VERSION}/kustomize_${KUSTOMIZE_VERSION}_linux_amd64.tar.gz && \ From 57f62a782bb815f4317c65b3bfacee629f143389 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 12 Nov 2021 09:30:44 +0100 Subject: [PATCH 0277/2916] fix: Use consitent wording in build_git_object --- kluctl/utils/git_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kluctl/utils/git_utils.py b/kluctl/utils/git_utils.py index 269f1ff00..53b0a9e64 100644 --- a/kluctl/utils/git_utils.py +++ b/kluctl/utils/git_utils.py @@ -169,7 +169,7 @@ def _clone_or_update_cache(url, cache_dir, do_update): if os.path.exists(init_marker): if do_update: - logger.info(f"Updating cache repo: url='{url}'") + logger.info(f"Updating mirror repo: url='{url}'") with build_git_object(url, cache_dir) as (g, url): g.remote("update") return From cef48a769e66577f19f26860ec9fe1aeb3087d56 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 12 Nov 2021 09:30:57 +0100 Subject: [PATCH 0278/2916] fix: Normalize path before loading jinja2 template --- kluctl/utils/templated_dir.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/kluctl/utils/templated_dir.py b/kluctl/utils/templated_dir.py index 2ff8dc943..eed560736 100644 --- a/kluctl/utils/templated_dir.py +++ b/kluctl/utils/templated_dir.py @@ -41,7 +41,10 @@ def async_render_subdir(self, subdir, target_dir): return jobs def do_render_file(self, source_path, target_path): - template = self.jinja_env.get_template(source_path.replace("\\", "/")) + source_path = os.path.normpath(source_path) + # jinja2 templates are using / even on windows + source_path = source_path.replace("\\", "/") + template = self.jinja_env.get_template(source_path) rendered = template.render() with open(target_path, "wt") as f: f.write(rendered) From 665178918d13b2281b2869d6c4aefa664c8789e2 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 12 Nov 2021 13:42:46 +0100 Subject: [PATCH 0279/2916] doc: Update documentation with new features --- docs/helm-integration.md | 11 ++++++ docs/jinja2-templating.md | 72 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 82 insertions(+), 1 deletion(-) diff --git a/docs/helm-integration.md b/docs/helm-integration.md index cd7578770..9b7075605 100644 --- a/docs/helm-integration.md +++ b/docs/helm-integration.md @@ -67,6 +67,17 @@ resource. The url to the Helm repository where the Helm Chart is located. You can use hub.helm.sh to search for repositories and charts and then use the repos found there. +oci based repositories are also supported, for example: +```yaml +helmChart: + repo: oci://r.myreg.io/mycharts/pepper + chartName: pepper + chartVersion: 1.2.3 + releaseName: pepper + namespace: pepper + output: deploy.yml +``` + ### chartName The name of the chart that can be found in the repository. diff --git a/docs/jinja2-templating.md b/docs/jinja2-templating.md index 147e6b3e3..343d260ca 100644 --- a/docs/jinja2-templating.md +++ b/docs/jinja2-templating.md @@ -16,7 +16,7 @@ for every kustomize deployment of the current deployment project and also inside These are however not available while the `deployment.yml` is rendered that defines these vars. This file is rendered and interpreted before the `vars` are processed and added to the Jinja2 context. -However, each file in `vars` can use all variables defined before that specific file is processed. Consider the +However, each entry in `vars` can use all variables defined before that specific file is processed. Consider the following example. ```yaml @@ -28,6 +28,76 @@ vars: `vars2.yml` can now use variables that are defined in `vars1.yml`. At all times, variables defined by parents of the current sub-deployment project can be used in the current vars file. +Different types of vars entries are possible: + +### file +This loads variables from a yaml file. Assume the following yaml file with the name `vars1.yml`: +```yaml +my_vars: + a: 1 + b: "b" + c: + - l1 + - l2 +``` + +This file can be loaded via: + +```yaml +vars: + - file: vars1.yml +``` + +After which all included kustomizeDirs and sub-deployments can use the jinja2 variables from `vars1.yml`. + +### values +An inline definition of variables. Example: + +```yaml +vars: + - values: + a: 1 + b: c +``` + +These variables can then be used in all kustomizeDirs and sub-deployments. + +### clusterConfigMap +Loads a configmap from the target's cluster and loads the specified key's value as a yaml file into the jinja2 variables +context. + +Assume the following configmap to be deployed to the target cluster: +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: my-vars + namespace: my-namespace +data: + vars: | + a: 1 + b: "b" + c: + - l1 + - l2 +``` + +This configmap can be loaded via: + +```yaml +vars: + - clusterConfigMap: + name: my-vars + namespace: my-namespace + key: vars +``` + +It assumes that the configmap is already deployed before the kluctl deployment happens. This might for example be +useful to store meta information about the cluster itself and then make it available to kluctl deployments. + +### clusterSecret +Same as clusterConfigMap, but for secrets. + ## Global variables There are multiple variables available which are pre-defined by kluctl. These are: From 56270dacae5c4ba05a1443025fd41c840e66c529 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 12 Nov 2021 13:42:56 +0100 Subject: [PATCH 0280/2916] fix: Fix missing parameter for CommandResult --- kluctl/deployment/deployment_collection.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index 14df20fa6..89deaab78 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -219,7 +219,7 @@ def do_poke_image(containers_and_images, o): new_objects, changed_objects = self.do_diff(k8s_cluster, applied_objects, False, False, False, False) orphan_objects = self.find_orphan_objects(k8s_cluster) - return CommandResult(new_objects=new_objects, changed_objects=changed_objects, + return CommandResult(new_objects=new_objects, changed_objects=changed_objects, hook_objects=[], orphan_objects=orphan_objects, errors=list(self.errors), warnings=list(self.warnings)) @@ -239,7 +239,7 @@ def downscale(self, k8s_cluster): new_objects, changed_objects = self.do_diff(k8s_cluster, applied_objects, False, False, False, False) orphan_objects = self.find_orphan_objects(k8s_cluster) - return CommandResult(new_objects=new_objects, changed_objects=changed_objects, + return CommandResult(new_objects=new_objects, changed_objects=changed_objects, hook_objects=[], orphan_objects=orphan_objects, errors=list(self.errors), warnings=list(self.warnings)) From 5e565ccc4b59585a60f7cef7254032bb1c528563 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 12 Nov 2021 16:42:10 +0100 Subject: [PATCH 0281/2916] fix: Collect validation results before checking for non-existent status --- kluctl/utils/k8s_status_validation.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/kluctl/utils/k8s_status_validation.py b/kluctl/utils/k8s_status_validation.py index 156696c40..0f006f643 100644 --- a/kluctl/utils/k8s_status_validation.py +++ b/kluctl/utils/k8s_status_validation.py @@ -19,10 +19,16 @@ class ValidateResult: results: list = dataclasses.field(default_factory=list) def validate_object(o, not_ready_is_error): + ref = get_object_ref(o) result = ValidateResult() + + for k, v in o["metadata"].get("annotations", {}).items(): + if not k.startswith(RESULT_ANNOTATION): + continue + result.results.append(ValidateResultItem(ref, reason=k, message=v)) + if "status" not in o: return result - ref = get_object_ref(o) status = o["status"] class ValidateFailed(Exception): @@ -180,9 +186,4 @@ def value_from_int_or_percent(v, total): except ValidateFailed: pass - for k, v in o["metadata"].get("annotations", {}).items(): - if not k.startswith(RESULT_ANNOTATION): - continue - result.results.append(ValidateResultItem(ref, reason=k, message=v)) - return result From c174b1e372524b982236be527fedb41faada1aa3 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sat, 13 Nov 2021 11:55:48 +0100 Subject: [PATCH 0282/2916] feat: Support patch annotations to influence downscale --- docs/commands.md | 3 ++- kluctl/cli/command_stubs.py | 5 ++-- kluctl/cli/commands.py | 2 +- kluctl/deployment/deployment_collection.py | 2 +- kluctl/utils/k8s_downscale_utils.py | 28 +++++++++++++++++----- setup.py | 1 + 6 files changed, 30 insertions(+), 11 deletions(-) diff --git a/docs/commands.md b/docs/commands.md index a8b1eef5f..e42271224 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -353,7 +353,8 @@ Usage: kluctl downscale [OPTIONS] Downscale all deployments. - This command will downscale all Deployments, StatefulSets and CronJobs. + This command will downscale all Deployments, StatefulSets and CronJobs. It is also possible to influence the + behaviour with the help of annotations, as described in the documentation. diff --git a/kluctl/cli/command_stubs.py b/kluctl/cli/command_stubs.py index 8d91521e9..c48922657 100644 --- a/kluctl/cli/command_stubs.py +++ b/kluctl/cli/command_stubs.py @@ -91,8 +91,9 @@ def poke_images_command_stub(obj, **kwargs): @cli_group.command("downscale", help="Downscale all deployments.\n\n" - "This command will downscale all Deployments, StatefulSets " - "and CronJobs.") + "This command will downscale all Deployments, StatefulSets and CronJobs. " + "It is also possible to influence the behaviour with the help of annotations, as described in " + "the documentation.") @kluctl_project_args() @image_args() @include_exclude_args() diff --git a/kluctl/cli/commands.py b/kluctl/cli/commands.py index 7ebe234be..8b110ee84 100644 --- a/kluctl/cli/commands.py +++ b/kluctl/cli/commands.py @@ -28,7 +28,7 @@ def bootstrap_command(obj, kwargs): if existing: if not kwargs["yes"] and get_dict_value(existing, 'metadata.labels."kluctl.io/component"') != "bootstrap": click.confirm("It looks like you're trying to bootstrap a cluster that already has the sealed-secrets " - "deployed but not managed by kluctl. Do you really want to continue bootrapping?", + "deployed but not managed by kluctl. Do you really want to continue bootstrapping?", err=True, abort=True) deploy_command2(obj, kwargs, cmd_ctx) diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index 89deaab78..722519be3 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -232,7 +232,7 @@ def downscale(self, k8s_cluster): continue for o in d.objects: ref = get_object_ref(o) - f = executor.submit(self.do_replace_object, k8s_cluster, ref, downscale_object) + f = executor.submit(self.do_replace_object, k8s_cluster, ref, lambda x, o=o: downscale_object(x, o)) futures.append((ref, f)) applied_objects = self.do_finish_futures(futures) diff --git a/kluctl/utils/k8s_downscale_utils.py b/kluctl/utils/k8s_downscale_utils.py index a969c2f99..e88ad3ff3 100644 --- a/kluctl/utils/k8s_downscale_utils.py +++ b/kluctl/utils/k8s_downscale_utils.py @@ -1,14 +1,30 @@ -from kluctl.utils.dict_utils import set_dict_value +import re + +import jsonpatch + +from kluctl.utils.dict_utils import set_dict_value, get_dict_value, copy_dict from kluctl.utils.k8s_object_utils import get_object_ref, remove_api_version_from_ref +from kluctl.utils.utils import parse_bool +from kluctl.utils.yaml_utils import yaml_load + +DOWNSCALE_ANNOTATION_PATCH_REGEX = re.compile(r"^kluctl.io/downscale-patch(-\d*)?$") +DOWNSCALE_ANNOTATION_IGNORE = "kluctl.io/downscale-ignore" +def downscale_object(remote_object, local_object): + if parse_bool(get_dict_value(local_object, "metadata.annotations[\"%s\"]" % DOWNSCALE_ANNOTATION_IGNORE, "false")): + return remote_object + for k, v in get_dict_value(local_object, "metadata.annotations", {}).items(): + if DOWNSCALE_ANNOTATION_PATCH_REGEX.fullmatch(k): + patch = yaml_load(v) + patch = jsonpatch.JsonPatch(patch) + remote_object = patch.apply(remote_object) -def downscale_object(o): - ref = get_object_ref(o) + ref = get_object_ref(remote_object) ref = remove_api_version_from_ref(ref) if ref.api_version == "apps" and ref.kind in ["Deployment", "StatefulSet"]: - return set_dict_value(o, "spec.replicas", 0, True) + return set_dict_value(remote_object, "spec.replicas", 0, True) elif ref.api_version == "batch" and ref.kind == "CronJob": - return set_dict_value(o, "spec.suspend", True) + return set_dict_value(remote_object, "spec.suspend", True) #elif ref.api_version == "autoscaling" and ref.kind == "HorizontalPodAutoscaler": # return set_dict_value(o, "spec.minReplicas", 0, True) - return o + return remote_object diff --git a/setup.py b/setup.py index 934e11182..6acf2b30a 100644 --- a/setup.py +++ b/setup.py @@ -72,6 +72,7 @@ "filelock>=3.3.0", "python-gitlab>=2.10.1", "jsonpath-ng>=1.5.3", + "jsonpatch>=1.32", ], entry_points={ "console_scripts": [ From d677a8330247775839222e9ef70b3a133b33e3c3 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sat, 13 Nov 2021 11:56:03 +0100 Subject: [PATCH 0283/2916] docs: Add documentation about supported annotations --- docs/annotations.md | 70 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 docs/annotations.md diff --git a/docs/annotations.md b/docs/annotations.md new file mode 100644 index 000000000..430cbbcab --- /dev/null +++ b/docs/annotations.md @@ -0,0 +1,70 @@ +# Annotations + +kluctl supports multiple annotations that influence individual commands. These are: + +## Deployment related +These annotations control details about how deployments should be handled. + +### kluctl.io/skip-delete +If set to "true", the annotated resource will not be deleted when [delete](./commands.md#delete) or +[prune](./commands.md#prune) is called. + +### kluctl.io/skip-delete-if-tags +If set to "true", the annotated resource will not be deleted when [delete](./commands.md#delete) or +[prune](./commands.md#prune) is called and inclusion/exclusion tags are used at the same time. + +This tag is especially useful and required on resources that would otherwise cause cascaded deletions of resources that +do not match the specified inclusion/exclusion tags. Namespaces are the most prominent example of such resources, as +they most likely don't match exclusion tags, but cascaded deletion would still cause deletion of the excluded resources. + +### kluctl.io/downscale-patch +Describes how a [downscale](./commands.md#downscale) on a resource can be done via a json patch. This is useful on +CRD based resources where no automatic downscale can be performed by kluctl. + +Example: +```yaml +apiVersion: kibana.k8s.elastic.co/v1 +kind: Kibana +metadata: + name: kibana + annotations: + kluctl.io/downscale-patch: | + - op: replace + path: /spec/count + value: 0 +spec: + version: 7.14.1 + count: 1 +``` + +If more than one patch needs to be specified, add `-xxx` to the annoation key, where `xxx` is an arbitrary number. + +### kluctl.io/downscale-ignore +If set to "true", the resource will be ignored while [downscale](./commands.md#downscale) is executed. + +## Hooks related +See [hooks](./hooks.md) for more details. + +### kluctl.io/hook +Declares a resource to be a hook, which is deployed/executed as described in [hooks](./hooks.md). The value of the +annotation determines when the hook is deployed/executed. + +### kluctl.io/hook-weight +Specifies a weight for the hook, used to determine deployment/execution order. + +### kluctl.io/hook-delete-policy +Defines when to delete the hook resource. + +### kluctl.io/hook-wait +Defines whether kluctl should wait for hook-completion. + +## Validation related +The following annotations influence the [validate](./commands.md#validate) command. + +### validate-result.kluctl.io/xxx +If this annotation is found on a resource that is checked while validation, the key and the value of the annotation +are added to the validation result, which is then returned by the validate command. + +The annotation key is dynamic, meaning that all annotations that begin with `validate-result.kluctl.io/` are taken +into account. + From 734ef680c6aec1d7bdbaa4eabf63316f0f6aee42 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sat, 13 Nov 2021 12:08:39 +0100 Subject: [PATCH 0284/2916] fix: Fix exception() in Future of DummyExecutor --- kluctl/utils/utils.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/kluctl/utils/utils.py b/kluctl/utils/utils.py index 5f378c977..d6200c507 100644 --- a/kluctl/utils/utils.py +++ b/kluctl/utils/utils.py @@ -83,9 +83,20 @@ def __exit__(self, exc_type, exc_val, exc_tb): def submit(self, f, *args, **kwargs): class Future: + def __init__(self, result, exc): + self._result = result + self._exc = exc + def exception(self): + return self._exc def result(self): - return f(*args, **kwargs) - return Future() + if self._exc is not None: + raise self._exc + return self._result + try: + result = f(*args, **kwargs) + return Future(result, None) + except Exception as e: + return Future(None, e) if os.environ.get("KLUCTL_NO_THREADS", "false").lower() in ["1", "true"] or (sys.gettrace() is not None and os.environ.get("KLUCTL_IGNORE_DEBUGGER", "false").lower() not in ["1", "true"]): print("Detected a debugger, using DummyExecutor for ThreadPool", file=sys.stderr) From 997802c760236a79238a45db4293f8cf0dac698c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 15 Nov 2021 09:40:27 +0100 Subject: [PATCH 0285/2916] feat: Add annotation to allow deleting of resources while downscaling --- docs/annotations.md | 3 +++ kluctl/deployment/deployment_collection.py | 23 ++++++++++++++-------- kluctl/utils/k8s_delete_utils.py | 20 ++++++++++++++----- kluctl/utils/k8s_downscale_utils.py | 8 +++++++- 4 files changed, 40 insertions(+), 14 deletions(-) diff --git a/docs/annotations.md b/docs/annotations.md index 430cbbcab..9230e547c 100644 --- a/docs/annotations.md +++ b/docs/annotations.md @@ -42,6 +42,9 @@ If more than one patch needs to be specified, add `-xxx` to the annoation key, w ### kluctl.io/downscale-ignore If set to "true", the resource will be ignored while [downscale](./commands.md#downscale) is executed. +### kluctl.io/downscale-delete +If set to "true", the resource will be deleted while [downscale](./commands.md#downscale) is executed. + ## Hooks related See [hooks](./hooks.md) for more details. diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index 722519be3..6fb9de355 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -13,8 +13,8 @@ from kluctl.utils.dict_utils import copy_dict, get_dict_value from kluctl.utils.exceptions import CommandError from kluctl.utils.k8s_delete_utils import find_objects_for_delete -from kluctl.utils.k8s_downscale_utils import downscale_object -from kluctl.utils.k8s_object_utils import get_object_ref, get_long_object_name, get_long_object_name_from_ref, \ +from kluctl.utils.k8s_downscale_utils import downscale_object, downsclae_is_delete +from kluctl.utils.k8s_object_utils import get_object_ref, get_long_object_name, \ ObjectRef from kluctl.utils.k8s_status_validation import validate_object, ValidateResult, ValidateResultItem from kluctl.utils.templated_dir import TemplatedDir @@ -31,6 +31,7 @@ class DeployErrorItem: class CommandResult: new_objects: list changed_objects: list + deleted_objects: list hook_objects: list orphan_objects: list errors: list @@ -226,21 +227,27 @@ def do_poke_image(containers_and_images, o): def downscale(self, k8s_cluster): self.clear_errors_and_warnings() with MyThreadPoolExecutor(max_workers=8) as executor: - futures = [] + replace_futures = [] + delete_futures = [] for d in self.deployments: if not d.check_inclusion_for_deploy(): continue for o in d.objects: ref = get_object_ref(o) - f = executor.submit(self.do_replace_object, k8s_cluster, ref, lambda x, o=o: downscale_object(x, o)) - futures.append((ref, f)) + if downsclae_is_delete(o): + f = executor.submit(k8s_cluster.delete_single_object, ref, ignore_not_found=True) + delete_futures.append((ref, f)) + else: + f = executor.submit(self.do_replace_object, k8s_cluster, ref, lambda x, o=o: downscale_object(x, o)) + replace_futures.append((ref, f)) - applied_objects = self.do_finish_futures(futures) + applied_objects = self.do_finish_futures(replace_futures) + deleted_objects = self.do_finish_futures(delete_futures) new_objects, changed_objects = self.do_diff(k8s_cluster, applied_objects, False, False, False, False) orphan_objects = self.find_orphan_objects(k8s_cluster) - return CommandResult(new_objects=new_objects, changed_objects=changed_objects, hook_objects=[], - orphan_objects=orphan_objects, + return CommandResult(new_objects=new_objects, changed_objects=changed_objects, deleted_objects=list(deleted_objects.keys()), + hook_objects=[], orphan_objects=orphan_objects, errors=list(self.errors), warnings=list(self.warnings)) def validate(self): diff --git a/kluctl/utils/k8s_delete_utils.py b/kluctl/utils/k8s_delete_utils.py index 7e4fdd746..ad02307f7 100644 --- a/kluctl/utils/k8s_delete_utils.py +++ b/kluctl/utils/k8s_delete_utils.py @@ -1,7 +1,7 @@ import logging from kluctl.utils.dict_utils import get_dict_value -from kluctl.utils.k8s_object_utils import get_included_objects, get_label_from_object, get_object_ref, \ +from kluctl.utils.k8s_object_utils import get_included_objects, get_object_ref, \ get_long_object_name_from_ref, split_api_version, get_filtered_api_resources, \ remove_api_version_from_ref, remove_namespace_from_ref_if_needed from kluctl.utils.utils import MyThreadPoolExecutor, parse_bool @@ -80,13 +80,18 @@ def find_objects_for_delete(k8s_cluster, labels, inclusion, excluded_objects): return ret def delete_objects(k8s_cluster, object_refs, do_wait): + from kluctl.deployment.deployment_collection import DeployErrorItem + from kluctl.deployment.deployment_collection import CommandResult + namespaces = set(x for x in object_refs if x.kind == "Namespace") namespace_names = set(x.name for x in namespaces) def do_delete_object(ref): logger.info('Deleting %s' % get_long_object_name_from_ref(ref)) - k8s_cluster.delete_single_object(ref, do_wait) + return k8s_cluster.delete_single_object(ref, do_wait) + deleted_objects = [] + errors = [] with MyThreadPoolExecutor(max_workers=8) as executor: futures = [] @@ -101,10 +106,15 @@ def do_delete_object(ref): continue f = executor.submit(do_delete_object, ref) - futures.append(f) + futures.append((ref, f)) - for f in futures: + for ref, f in futures: try: f.result() + deleted_objects.append(ref) except Exception as e: - logger.error('Failed to delete %s. Error=%s' % (get_long_object_name_from_ref(ref), k8s_cluster.get_status_message(e))) + errors.append(DeployErrorItem(ref=ref, message="Failed to delete object. %s" % k8s_cluster.get_status_message(e))) + + result = CommandResult(new_objects=[], changed_objects=[], deleted_objects=deleted_objects, + hook_objects=[], orphan_objects=[], errors=errors, warnings=[]) + return result diff --git a/kluctl/utils/k8s_downscale_utils.py b/kluctl/utils/k8s_downscale_utils.py index e88ad3ff3..d72adcb7c 100644 --- a/kluctl/utils/k8s_downscale_utils.py +++ b/kluctl/utils/k8s_downscale_utils.py @@ -2,14 +2,20 @@ import jsonpatch -from kluctl.utils.dict_utils import set_dict_value, get_dict_value, copy_dict +from kluctl.utils.dict_utils import set_dict_value, get_dict_value from kluctl.utils.k8s_object_utils import get_object_ref, remove_api_version_from_ref from kluctl.utils.utils import parse_bool from kluctl.utils.yaml_utils import yaml_load DOWNSCALE_ANNOTATION_PATCH_REGEX = re.compile(r"^kluctl.io/downscale-patch(-\d*)?$") +DOWNSCALE_ANNOTATION_DELETE = "kluctl.io/downscale-delete" DOWNSCALE_ANNOTATION_IGNORE = "kluctl.io/downscale-ignore" +def downsclae_is_delete(local_object): + if parse_bool(get_dict_value(local_object, "metadata.annotations[\"%s\"]" % DOWNSCALE_ANNOTATION_DELETE, "false")): + return True + return False + def downscale_object(remote_object, local_object): if parse_bool(get_dict_value(local_object, "metadata.annotations[\"%s\"]" % DOWNSCALE_ANNOTATION_IGNORE, "false")): return remote_object From 9fce9a77867c4d5d1fa613b9c17ea701cf74db59 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 15 Nov 2021 09:41:10 +0100 Subject: [PATCH 0286/2916] feat: Show objects that got deleted by commands --- docs/commands.md | 6 ++++++ kluctl/cli/command_result.py | 6 ++++++ kluctl/cli/command_stubs.py | 4 ++-- kluctl/cli/commands.py | 11 +++++++---- kluctl/deployment/deployment_collection.py | 12 ++++++------ 5 files changed, 27 insertions(+), 12 deletions(-) diff --git a/docs/commands.md b/docs/commands.md index e42271224..cb8737d3e 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -252,6 +252,9 @@ In addition, the following arguments are available: Misc arguments: -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. --dry-run Performs all kubernetes API calls in dry-run mode. + -o, --output TEXT Specify output format and target file, in the format 'format=path'. Format can + either be 'text' or 'yaml'. Can be specified multiple times. The actual format for + yaml is currently not documented and subject to change. ``` @@ -282,6 +285,9 @@ In addition, the following arguments are available: Misc arguments: -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. --dry-run Performs all kubernetes API calls in dry-run mode. + -o, --output TEXT Specify output format and target file, in the format 'format=path'. Format can + either be 'text' or 'yaml'. Can be specified multiple times. The actual format for + yaml is currently not documented and subject to change. ``` diff --git a/kluctl/cli/command_result.py b/kluctl/cli/command_result.py index e015e36e8..2a00c7be2 100644 --- a/kluctl/cli/command_result.py +++ b/kluctl/cli/command_result.py @@ -36,6 +36,11 @@ def format_command_result_text(command_result: CommandResult): changes = x["changes"] result += "%s" % pretty_changes(get_object_ref(object), changes) + if command_result.deleted_objects: + result += "\nDeleted objects:\n" + for x in command_result.deleted_objects: + result += " %s\n" % get_long_object_name_from_ref(x) + if command_result.hook_objects: result += "\nApplied hooks:\n" for x in command_result.hook_objects: @@ -76,6 +81,7 @@ def pretty_changes(ref, changes): def format_command_result_yaml(c, command_result: CommandResult): result = { "diff": changes_to_yaml(command_result.new_objects, command_result.changed_objects), + "deleted_objects": command_result.deleted_objects, "applied_hooks": command_result.hook_objects, "orphan_objects": [{"ref": dataclasses.asdict(ref)} for ref in command_result.orphan_objects], "errors": [dataclasses.asdict(x) for x in command_result.errors], diff --git a/kluctl/cli/command_stubs.py b/kluctl/cli/command_stubs.py index c48922657..8b968338d 100644 --- a/kluctl/cli/command_stubs.py +++ b/kluctl/cli/command_stubs.py @@ -53,7 +53,7 @@ def diff_command_stub(obj, **kwargs): @kluctl_project_args() @image_args() @include_exclude_args() -@misc_arguments(yes=True, dry_run=True) +@misc_arguments(yes=True, dry_run=True, output_format=True) @click.pass_obj def delete_command_stub(obj, **kwargs): from kluctl.cli.commands import delete_command @@ -69,7 +69,7 @@ def delete_command_stub(obj, **kwargs): @kluctl_project_args() @image_args() @include_exclude_args() -@misc_arguments(yes=True, dry_run=True) +@misc_arguments(yes=True, dry_run=True, output_format=True) @click.pass_obj def prune_command_stub(obj, **kwargs): from kluctl.cli.commands import prune_command diff --git a/kluctl/cli/commands.py b/kluctl/cli/commands.py index 8b110ee84..e3168edf8 100644 --- a/kluctl/cli/commands.py +++ b/kluctl/cli/commands.py @@ -7,8 +7,8 @@ import click from kluctl import get_kluctl_package_dir -from kluctl.cli.utils import build_seen_images, project_command_context from kluctl.cli.command_result import output_command_result, output_validate_result, output_yaml_result +from kluctl.cli.utils import build_seen_images, project_command_context from kluctl.kluctl_project.kluctl_project import load_kluctl_project_from_args from kluctl.utils.dict_utils import get_dict_value from kluctl.utils.exceptions import CommandError @@ -67,12 +67,14 @@ def confirmed_delete_objects(k8s_cluster, objects, kwargs): if not kwargs["yes"] and not kwargs["dry_run"]: click.confirm("Do you really want to delete %d objects?" % len(objects), abort=True, err=True) - delete_objects(k8s_cluster, objects, False) + result = delete_objects(k8s_cluster, objects, False) + return result def delete_command(obj, kwargs): with project_command_context(kwargs) as cmd_ctx: objects = cmd_ctx.deployment_collection.find_delete_objects(cmd_ctx.k8s_cluster) - confirmed_delete_objects(cmd_ctx.k8s_cluster, objects, kwargs) + result = confirmed_delete_objects(cmd_ctx.k8s_cluster, objects, kwargs) + output_command_result(kwargs["output"], cmd_ctx.deployment_collection, result) def prune_command(obj, kwargs): with project_command_context(kwargs) as cmd_ctx: @@ -80,7 +82,8 @@ def prune_command(obj, kwargs): def prune_command2(obj, kwargs, cmd_ctx): objects = cmd_ctx.deployment_collection.find_orphan_objects(cmd_ctx.k8s_cluster) - confirmed_delete_objects(cmd_ctx.k8s_cluster, objects, kwargs) + result = confirmed_delete_objects(cmd_ctx.k8s_cluster, objects, kwargs) + output_command_result(kwargs["output"], cmd_ctx.deployment_collection, result) def poke_images_command(obj, kwargs): with project_command_context(kwargs) as cmd_ctx: diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index 6fb9de355..b6831b356 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -154,8 +154,8 @@ def deploy(self, k8s_cluster, force_apply, replace_on_error, force_replace_on_er new_objects, changed_objects = self.do_diff(k8s_cluster, applied_objects, False, False, False, False) orphan_objects = self.find_orphan_objects(k8s_cluster) applied_hook_objects = list(applied_hook_objects.values()) - return CommandResult(new_objects=new_objects, changed_objects=changed_objects, hook_objects=applied_hook_objects, - orphan_objects=orphan_objects, + return CommandResult(new_objects=new_objects, changed_objects=changed_objects, deleted_objects=[], + hook_objects=applied_hook_objects, orphan_objects=orphan_objects, errors=list(self.errors), warnings=list(self.warnings)) def diff(self, k8s_cluster, force_apply, replace_on_error, force_replace_on_error, ignore_tags, ignore_labels, ignore_annotations, ignore_order): @@ -166,8 +166,8 @@ def diff(self, k8s_cluster, force_apply, replace_on_error, force_replace_on_erro new_objects, changed_objects = self.do_diff(k8s_cluster, applied_objects, ignore_tags, ignore_labels, ignore_annotations, ignore_order) orphan_objects = self.find_orphan_objects(k8s_cluster) applied_hook_objects = list(applied_hook_objects.values()) - return CommandResult(new_objects=new_objects, changed_objects=changed_objects, hook_objects=applied_hook_objects, - orphan_objects=orphan_objects, + return CommandResult(new_objects=new_objects, changed_objects=changed_objects, deleted_objects=[], + hook_objects=applied_hook_objects, orphan_objects=orphan_objects, errors=list(self.errors), warnings=list(self.warnings)) def poke_images(self, k8s_cluster): @@ -220,8 +220,8 @@ def do_poke_image(containers_and_images, o): new_objects, changed_objects = self.do_diff(k8s_cluster, applied_objects, False, False, False, False) orphan_objects = self.find_orphan_objects(k8s_cluster) - return CommandResult(new_objects=new_objects, changed_objects=changed_objects, hook_objects=[], - orphan_objects=orphan_objects, + return CommandResult(new_objects=new_objects, changed_objects=changed_objects, deleted_objects=[], + hook_objects=[], orphan_objects=orphan_objects, errors=list(self.errors), warnings=list(self.warnings)) def downscale(self, k8s_cluster): From a2f4de6952d8cb302d83fc183a715e39efbbb27d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 15 Nov 2021 10:02:51 +0100 Subject: [PATCH 0287/2916] fix: Fix crash in prune/delete --- kluctl/cli/commands.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/kluctl/cli/commands.py b/kluctl/cli/commands.py index e3168edf8..bac584696 100644 --- a/kluctl/cli/commands.py +++ b/kluctl/cli/commands.py @@ -58,14 +58,12 @@ def diff_command(obj, kwargs): def confirmed_delete_objects(k8s_cluster, objects, kwargs): from kluctl.utils.k8s_delete_utils import delete_objects - if len(objects) == 0: - return - - click.echo("The following objects will be deleted:", err=True) - for ref in objects: - click.echo(" %s" % get_long_object_name_from_ref(ref), err=True) - if not kwargs["yes"] and not kwargs["dry_run"]: - click.confirm("Do you really want to delete %d objects?" % len(objects), abort=True, err=True) + if len(objects) != 0: + click.echo("The following objects will be deleted:", err=True) + for ref in objects: + click.echo(" %s" % get_long_object_name_from_ref(ref), err=True) + if not kwargs["yes"] and not kwargs["dry_run"]: + click.confirm("Do you really want to delete %d objects?" % len(objects), abort=True, err=True) result = delete_objects(k8s_cluster, objects, False) return result From 1d5ae8eff3457043a5ffe6af916001581078aa5e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 15 Nov 2021 12:46:58 +0100 Subject: [PATCH 0288/2916] feat: Better reporting of hook waiting status --- kluctl/deployment/apply_util.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/kluctl/deployment/apply_util.py b/kluctl/deployment/apply_util.py index d259a7f22..269612541 100644 --- a/kluctl/deployment/apply_util.py +++ b/kluctl/deployment/apply_util.py @@ -142,16 +142,24 @@ def wait_object(self, ref): return True did_log = False - logger.debug("Starting wait for hook %s" % get_long_object_name_from_ref(ref)) + logger.debug("Waiting for hook %s to get ready" % get_long_object_name_from_ref(ref)) while True: o, _ = self.k8s_cluster.get_single_object(ref) if o is None: + if did_log: + logger.warning("Cancelled waiting for hook %s as it disappeared while waiting for it" % get_long_object_name_from_ref(ref)) self.handle_error(ref, "Object disappeared while waiting for it to become ready") return False v = validate_object(o, False) if v.ready: + if did_log: + logger.info("Finished waiting for hook %s" % get_long_object_name_from_ref(ref)) return True if v.errors: + if did_log: + logger.warning("Cancelled waiting for hook %s due to errors" % get_long_object_name_from_ref(ref)) + for e in v.errors: + self.handle_error(ref, e.message) return False if not did_log: From 941c72b0accb74b3a863096637dca3e9958c2268 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 15 Nov 2021 14:26:35 +0100 Subject: [PATCH 0289/2916] feat: Implement --hook-timeout for deploy command --- docs/commands.md | 12 +++++++++--- kluctl/cli/command_stubs.py | 2 +- kluctl/cli/commands.py | 3 ++- kluctl/cli/main_cli_group.py | 12 +++++++++--- kluctl/deployment/apply_util.py | 13 +++++++++++-- kluctl/deployment/deployment_collection.py | 10 +++++----- kluctl/deployment/hooks_util.py | 2 +- kluctl/utils/utils.py | 2 ++ 8 files changed, 40 insertions(+), 16 deletions(-) diff --git a/docs/commands.md b/docs/commands.md index cb8737d3e..570037423 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -110,7 +110,8 @@ In addition, the following arguments are available: -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. --dry-run Performs all kubernetes API calls in dry-run mode. --force-apply Force conflict resolution when applying. See documentation for details - --replace-on-error When patching an object fails, try to replace. See documentation for more details. + --replace-on-error When patching an object fails, try to replace it. See documentation for more + details. --force-replace-on-error Same as --replace-on-error, but also try to delete and re-create objects. See documentation for more details. --abort-on-error Abort deploying when an error occurs instead of trying the remaining deployments @@ -144,9 +145,13 @@ In addition, the following arguments are available: -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. --dry-run Performs all kubernetes API calls in dry-run mode. --force-apply Force conflict resolution when applying. See documentation for details - --replace-on-error When patching an object fails, try to replace. See documentation for more details. + --replace-on-error When patching an object fails, try to replace it. See documentation for more + details. --force-replace-on-error Same as --replace-on-error, but also try to delete and re-create objects. See documentation for more details. + --hook-timeout DURATION Maximum time to wait for hook readiness. The timeout is meant per-hook. Timeouts are + in the duration format (1s, 1m, 1h, ...). If not specified, a default timeout of 5m + is used. --abort-on-error Abort deploying when an error occurs instead of trying the remaining deployments -o, --output TEXT Specify output format and target file, in the format 'format=path'. Format can either be 'text' or 'yaml'. Can be specified multiple times. The actual format for @@ -210,7 +215,8 @@ In addition, the following arguments are available: ``` Misc arguments: --force-apply Force conflict resolution when applying. See documentation for details - --replace-on-error When patching an object fails, try to replace. See documentation for more details. + --replace-on-error When patching an object fails, try to replace it. See documentation for more + details. --force-replace-on-error Same as --replace-on-error, but also try to delete and re-create objects. See documentation for more details. --ignore-tags Ignores changes in tags when diffing diff --git a/kluctl/cli/command_stubs.py b/kluctl/cli/command_stubs.py index 8b968338d..47ca30cbd 100644 --- a/kluctl/cli/command_stubs.py +++ b/kluctl/cli/command_stubs.py @@ -23,7 +23,7 @@ def bootstrap_command_stub(obj, **kwargs): @kluctl_project_args() @image_args() @include_exclude_args() -@misc_arguments(yes=True, dry_run=True, force_apply=True, replace_on_error=True, abort_on_error=True, output_format=True, render_output_dir=True) +@misc_arguments(yes=True, dry_run=True, force_apply=True, replace_on_error=True, abort_on_error=True, hook_timeout=True, output_format=True, render_output_dir=True) @click.pass_obj def deploy_command_stub(obj, **kwargs): from kluctl.cli.commands import deploy_command diff --git a/kluctl/cli/commands.py b/kluctl/cli/commands.py index bac584696..46215ac39 100644 --- a/kluctl/cli/commands.py +++ b/kluctl/cli/commands.py @@ -43,7 +43,8 @@ def deploy_command2(obj, kwargs, cmd_ctx): click.confirm("Do you really want to deploy to the context/cluster %s?" % cmd_ctx.k8s_cluster.context, err=True, abort=True) result = cmd_ctx.deployment_collection.deploy(cmd_ctx.k8s_cluster, kwargs["force_apply"], - kwargs["replace_on_error"], kwargs["force_replace_on_error"], kwargs["abort_on_error"]) + kwargs["replace_on_error"], kwargs["force_replace_on_error"], kwargs["abort_on_error"], + kwargs["hook_timeout"]) output_command_result(kwargs["output"], cmd_ctx.deployment_collection, result) if result.errors: sys.exit(1) diff --git a/kluctl/cli/main_cli_group.py b/kluctl/cli/main_cli_group.py index b82cd9431..9fb18e8da 100644 --- a/kluctl/cli/main_cli_group.py +++ b/kluctl/cli/main_cli_group.py @@ -10,7 +10,7 @@ from kluctl import _version from kluctl.utils.external_tools import get_external_tool_path from kluctl.utils.gitlab.gitlab_util import init_gitlab_util_from_glab -from kluctl.utils.utils import get_tmp_base_dir +from kluctl.utils.utils import get_tmp_base_dir, duration from kluctl.utils.yaml_utils import yaml_load_file, yaml_save_file logger = logging.getLogger(__name__) @@ -126,7 +126,7 @@ def wrapper(func): return func return wrapper -def misc_arguments(yes=False, dry_run=False, force_apply=False, replace_on_error=False, +def misc_arguments(yes=False, dry_run=False, force_apply=False, replace_on_error=False, hook_timeout=False, ignore_labels=False, ignore_order=False, abort_on_error=False, output_format=False, output=False, render_output_dir=False): options = [] @@ -147,13 +147,19 @@ def misc_arguments(yes=False, dry_run=False, force_apply=False, replace_on_error default=False, is_flag=True)) if replace_on_error: options.append(optgroup.option("--replace-on-error", - help="When patching an object fails, try to replace. " + help="When patching an object fails, try to replace it. " "See documentation for more details.", default=False, is_flag=True)) options.append(optgroup.option("--force-replace-on-error", help="Same as --replace-on-error, but also try to delete and re-create objects. " "See documentation for more details.", default=False, is_flag=True)) + if hook_timeout: + options.append(optgroup.option("--hook-timeout", + help="Maximum time to wait for hook readiness. The timeout is meant per-hook. " + "Timeouts are in the duration format (1s, 1m, 1h, ...). If not specified, " + "a default timeout of 5m is used.", + default="5s", type=duration)) if ignore_labels: options.append(optgroup.option("--ignore-tags", help="Ignores changes in tags when diffing", diff --git a/kluctl/deployment/apply_util.py b/kluctl/deployment/apply_util.py index 269612541..fc7c10281 100644 --- a/kluctl/deployment/apply_util.py +++ b/kluctl/deployment/apply_util.py @@ -1,6 +1,7 @@ import json import logging import threading +from datetime import datetime from kubernetes.client import ApiException from kubernetes.dynamic.exceptions import ResourceNotFoundError, ConflictError @@ -15,7 +16,7 @@ logger = logging.getLogger(__name__) class ApplyUtil: - def __init__(self, deployment_collection, k8s_cluster, force_apply, replace_on_error, force_replace_on_error, dry_run, abort_on_error): + def __init__(self, deployment_collection, k8s_cluster, force_apply, replace_on_error, force_replace_on_error, dry_run, abort_on_error, hook_timeout): self.deployment_collection = deployment_collection self.k8s_cluster = k8s_cluster self.force_apply = force_apply @@ -23,6 +24,7 @@ def __init__(self, deployment_collection, k8s_cluster, force_apply, replace_on_e self.force_replace_on_error = force_replace_on_error self.dry_run = dry_run self.abort_on_error = abort_on_error + self.hook_timeout = hook_timeout self.applied_objects = {} self.applied_hook_objects = {} @@ -137,12 +139,13 @@ def retry_with_conflicts(e): except ApiException as e: retry_with_replace(e) - def wait_object(self, ref): + def wait_hook(self, ref): if self.dry_run: return True did_log = False logger.debug("Waiting for hook %s to get ready" % get_long_object_name_from_ref(ref)) + start_time = datetime.now() while True: o, _ = self.k8s_cluster.get_single_object(ref) if o is None: @@ -162,6 +165,12 @@ def wait_object(self, ref): self.handle_error(ref, e.message) return False + if self.hook_timeout is not None and datetime.now() - start_time >= self.hook_timeout: + err = "Timed out while waiting for hook %s" % get_long_object_name_from_ref(ref) + logger.warning(err) + self.handle_error(ref, err) + return False + if not did_log: logger.info("Waiting for hook %s to get ready..." % get_long_object_name_from_ref(ref)) did_log = True diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index b6831b356..f9fda265a 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -146,11 +146,11 @@ def prepare(self, k8s_cluster): self.build_kustomize_objects(k8s_cluster) self.update_remote_objects(k8s_cluster) - def deploy(self, k8s_cluster, force_apply, replace_on_error, force_replace_on_error, abort_on_error): + def deploy(self, k8s_cluster, force_apply, replace_on_error, force_replace_on_error, abort_on_error, hook_timeout): self.clear_errors_and_warnings() applied_objects, applied_hook_objects = self.do_apply(k8s_cluster, force_apply, replace_on_error, force_replace_on_error, - False, abort_on_error) + False, abort_on_error, hook_timeout) new_objects, changed_objects = self.do_diff(k8s_cluster, applied_objects, False, False, False, False) orphan_objects = self.find_orphan_objects(k8s_cluster) applied_hook_objects = list(applied_hook_objects.values()) @@ -162,7 +162,7 @@ def diff(self, k8s_cluster, force_apply, replace_on_error, force_replace_on_erro self.clear_errors_and_warnings() applied_objects, applied_hook_objects = self.do_apply(k8s_cluster, force_apply, replace_on_error, force_replace_on_error, - True, False) + True, False, None) new_objects, changed_objects = self.do_diff(k8s_cluster, applied_objects, ignore_tags, ignore_labels, ignore_annotations, ignore_order) orphan_objects = self.find_orphan_objects(k8s_cluster) applied_hook_objects = list(applied_hook_objects.values()) @@ -284,10 +284,10 @@ def find_orphan_objects(self, k8s_cluster): excluded_objects = list(self.local_objects_by_ref().keys()) return find_objects_for_delete(k8s_cluster, labels, self.inclusion, excluded_objects) - def do_apply(self, k8s_cluster, force_apply, replace_on_error, force_replace_on_error, dry_run, abort_on_error): + def do_apply(self, k8s_cluster, force_apply, replace_on_error, force_replace_on_error, dry_run, abort_on_error, hook_timeout): if k8s_cluster.dry_run: dry_run = dry_run - apply_util = ApplyUtil(self, k8s_cluster, force_apply, replace_on_error, force_replace_on_error, dry_run, abort_on_error) + apply_util = ApplyUtil(self, k8s_cluster, force_apply, replace_on_error, force_replace_on_error, dry_run, abort_on_error, hook_timeout) apply_util.apply_deployments() return apply_util.applied_objects, apply_util.applied_hook_objects diff --git a/kluctl/deployment/hooks_util.py b/kluctl/deployment/hooks_util.py index c68d98c66..637670d3b 100644 --- a/kluctl/deployment/hooks_util.py +++ b/kluctl/deployment/hooks_util.py @@ -60,7 +60,7 @@ def do_log(level, str): continue if not h.wait: continue - wait_results[ref] = self.apply_util.wait_object(ref) + wait_results[ref] = self.apply_util.wait_hook(ref) delete_after_objects = [] for h in reversed(l): diff --git a/kluctl/utils/utils.py b/kluctl/utils/utils.py index d6200c507..a4dca9bc1 100644 --- a/kluctl/utils/utils.py +++ b/kluctl/utils/utils.py @@ -25,6 +25,8 @@ def copytree(src, dst, symlinks=False, ignore=None): shutil.copy2(s, d) def duration(duration_string): # example: '5d3h2m1s' + if isinstance(duration_string, timedelta): + return duration_string duration_string = duration_string.lower() total_seconds = Decimal('0') prev_num = [] From e0bc130c5907f9e0227d7cd454fb876798be4b3f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 15 Nov 2021 15:03:47 +0100 Subject: [PATCH 0290/2916] fix: dry_run=True for ApplyUtil when --dry-run was specified --- kluctl/deployment/deployment_collection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index f9fda265a..e32e59089 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -286,7 +286,7 @@ def find_orphan_objects(self, k8s_cluster): def do_apply(self, k8s_cluster, force_apply, replace_on_error, force_replace_on_error, dry_run, abort_on_error, hook_timeout): if k8s_cluster.dry_run: - dry_run = dry_run + dry_run = True apply_util = ApplyUtil(self, k8s_cluster, force_apply, replace_on_error, force_replace_on_error, dry_run, abort_on_error, hook_timeout) apply_util.apply_deployments() return apply_util.applied_objects, apply_util.applied_hook_objects From 2364736a0400ea422d77ee3cee12b959de0f79b2 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 15 Nov 2021 15:07:05 +0100 Subject: [PATCH 0291/2916] fix: Use 5m instead of 5s for --hook-timeout default --- kluctl/cli/main_cli_group.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kluctl/cli/main_cli_group.py b/kluctl/cli/main_cli_group.py index 9fb18e8da..932116bcf 100644 --- a/kluctl/cli/main_cli_group.py +++ b/kluctl/cli/main_cli_group.py @@ -159,7 +159,7 @@ def misc_arguments(yes=False, dry_run=False, force_apply=False, replace_on_error help="Maximum time to wait for hook readiness. The timeout is meant per-hook. " "Timeouts are in the duration format (1s, 1m, 1h, ...). If not specified, " "a default timeout of 5m is used.", - default="5s", type=duration)) + default="5m", type=duration)) if ignore_labels: options.append(optgroup.option("--ignore-tags", help="Ignores changes in tags when diffing", From 4a1abb563f75bed249ff67d1d453b6717b08bfd2 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 15 Nov 2021 15:21:54 +0100 Subject: [PATCH 0292/2916] fix: Remove cleanup/migration code in _clone_or_update_cache --- kluctl/utils/git_utils.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/kluctl/utils/git_utils.py b/kluctl/utils/git_utils.py index 53b0a9e64..744382c61 100644 --- a/kluctl/utils/git_utils.py +++ b/kluctl/utils/git_utils.py @@ -174,15 +174,6 @@ def _clone_or_update_cache(url, cache_dir, do_update): g.remote("update") return - for n in os.listdir(cache_dir): - if n == ".cache.lock": - continue - p = os.path.join(cache_dir, n) - if os.path.isdir(p): - shutil.rmtree(os.path.join(cache_dir, n)) - else: - os.unlink(p) - logger.info(f"Cloning mirror repo at {cache_dir}") with build_git_object(url, cache_dir) as (g, url): g.clone("--mirror", url, "mirror") From 8fd282262ef3142ae05b94899e66ebcb5188884b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 15 Nov 2021 16:22:13 +0100 Subject: [PATCH 0293/2916] fix: Use deterministic clone path in build_clone_dir --- kluctl/kluctl_project/kluctl_project.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/kluctl/kluctl_project/kluctl_project.py b/kluctl/kluctl_project/kluctl_project.py index b5a0491ce..8b1febaf0 100644 --- a/kluctl/kluctl_project/kluctl_project.py +++ b/kluctl/kluctl_project/kluctl_project.py @@ -125,23 +125,20 @@ def from_archive(path, metadata_path, tmp_dir): project.targets = metadata["targets"] return project - def build_clone_dir(self, url): + def build_clone_dir(self, url, ref): + if ref is None: + ref = "HEAD" + ref = ref.replace("/", "-").replace("\\", "-") url = parse_git_url(url) base_name = os.path.basename(url.path) url_hash = hashlib.sha256(("%s:%s" % (url.host, url.path)).encode("utf-8")).hexdigest() - for i in range(len(url_hash)): - dir = os.path.join(self.tmp_dir, "git", base_name) - if i != 0: - dir += "-%s" % url_hash[0:i] - if not os.path.exists(dir): - return dir - raise Exception("Unexpected!") + return os.path.join(self.tmp_dir, "%s-%s" % (base_name, url_hash), ref) def clone_git_project(self, git_project_config, default_git_subdir, do_add_involved_repo): os.makedirs(os.path.join(self.tmp_dir, "git"), exist_ok=True) git_project = parse_git_project(git_project_config["project"], default_git_subdir) - target_dir = self.build_clone_dir(git_project.url) + target_dir = self.build_clone_dir(git_project.url, git_project.ref) clone_project(git_project.url, git_project.ref, target_dir, git_cache_up_to_date=self.git_cache_up_to_date) git_ref = get_git_ref(target_dir) From f404600262b2fb71632bae745dc4ff2e9b4565ec Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 15 Nov 2021 17:53:00 +0100 Subject: [PATCH 0294/2916] pref: Paralellize cloning of target repos --- kluctl/kluctl_project/kluctl_project.py | 227 +++++++++++++++--------- kluctl/utils/git_utils.py | 124 +++++++------ 2 files changed, 219 insertions(+), 132 deletions(-) diff --git a/kluctl/kluctl_project/kluctl_project.py b/kluctl/kluctl_project/kluctl_project.py index 8b1febaf0..a82c84dbb 100644 --- a/kluctl/kluctl_project/kluctl_project.py +++ b/kluctl/kluctl_project/kluctl_project.py @@ -1,3 +1,4 @@ +import contextlib import dataclasses import gzip import hashlib @@ -14,10 +15,9 @@ import jsonschema from kluctl.schemas.schema import validate_kluctl_project_config, parse_git_project, target_config_schema -from kluctl.utils.dict_utils import copy_dict, get_dict_value +from kluctl.utils.dict_utils import copy_dict, get_dict_value, merge_dict from kluctl.utils.exceptions import InvalidKluctlProjectConfig, CommandError -from kluctl.utils.git_utils import parse_git_url, clone_project, get_git_commit, git_ls_remote, \ - get_git_ref, ensure_git_cache_updated +from kluctl.utils.git_utils import parse_git_url, get_git_commit, get_git_ref, MirroredGitRepo, git_ls_remote from kluctl.utils.jinja2_utils import render_dict_strs from kluctl.utils.k8s_cluster_base import load_cluster_config from kluctl.utils.utils import get_tmp_base_dir, MyThreadPoolExecutor @@ -53,6 +53,15 @@ class GitProjectInfo: commit: str dir: str +@dataclasses.dataclass +class DynamicTargetInfo: + base_target: dict + dir: str + extra_target_config: dict = None + git_project: object = None + ref: str = None + ref_pattern: str = None + class KluctlProject: def __init__(self, project_url, project_ref, config_file, local_clusters, local_deployment, local_sealed_secrets, tmp_dir): self.project_url = project_url @@ -66,7 +75,7 @@ def __init__(self, project_url, project_ref, config_file, local_clusters, local_ self.config = None self.targets = None self.involved_repos = {} - self.git_cache_up_to_date = {} + self.mirror_repos = {} self.refs_for_urls = {} def create_tgz(self, path, metadata_path, reproducible): @@ -134,16 +143,19 @@ def build_clone_dir(self, url, ref): url_hash = hashlib.sha256(("%s:%s" % (url.host, url.path)).encode("utf-8")).hexdigest() return os.path.join(self.tmp_dir, "%s-%s" % (base_name, url_hash), ref) - def clone_git_project(self, git_project_config, default_git_subdir, do_add_involved_repo): + def clone_git_project(self, git_project_config, default_git_subdir, do_add_involved_repo, do_lock): os.makedirs(os.path.join(self.tmp_dir, "git"), exist_ok=True) git_project = parse_git_project(git_project_config["project"], default_git_subdir) target_dir = self.build_clone_dir(git_project.url, git_project.ref) - clone_project(git_project.url, git_project.ref, target_dir, git_cache_up_to_date=self.git_cache_up_to_date) - git_ref = get_git_ref(target_dir) + mirror_repo = self.mirror_repos.setdefault(git_project.url, MirroredGitRepo(git_project.url)) + with (mirror_repo.locked() if do_lock else contextlib.suppress()): + if not mirror_repo.has_updated: + mirror_repo.update() + mirror_repo.clone_project(git_project.ref, target_dir) - self.git_cache_up_to_date[git_project.url] = True + git_ref = get_git_ref(target_dir) dir = target_dir if git_project.subdir is not None: @@ -166,7 +178,7 @@ def clone_kluctl_project(self): "url": self.project_url, "ref": self.project_ref } - }, None, True) + }, None, True, True) def add_involved_repo(self, url, ref_pattern, refs): s = self.involved_repos.setdefault(url, []) @@ -209,7 +221,7 @@ def do_clone(key, default_git_subdir, local_dir): projects = [self.config[key]] ret = [] for project in projects: - info = self.clone_git_project(project, default_git_subdir, True) + info = self.clone_git_project(project, default_git_subdir, True, True) ret.append(info) return ret @@ -228,7 +240,12 @@ def do_clone(key, default_git_subdir, local_dir): def update_git_caches(self): with MyThreadPoolExecutor() as executor: futures = [] - def do_update(key): + def do_update_repo(repo): + with repo.locked(): + if not repo.has_updated: + repo.update() + + def do_update_projects(key): if key not in self.config: return if isinstance(self.config[key], list): @@ -237,15 +254,16 @@ def do_update(key): projects = [self.config[key]] for project in projects: url = parse_git_project(project["project"], None).url - if url in self.git_cache_up_to_date: + if url in self.mirror_repos: return - self.git_cache_up_to_date[url] = True - f = executor.submit(ensure_git_cache_updated, url) + mirror_repo = MirroredGitRepo(url) + self.mirror_repos[url] = mirror_repo + f = executor.submit(do_update_repo, mirror_repo) futures.append(f) - do_update("deployment") - do_update("clusters") - do_update("sealedSecrets") + do_update_projects("deployment") + do_update_projects("clusters") + do_update_projects("sealedSecrets") for target in self.config.get("targets", []): target_config = target.get("targetConfig") @@ -254,9 +272,10 @@ def do_update(key): if "project" in target_config: url = parse_git_project(target_config["project"], None).url - if url not in self.git_cache_up_to_date: - self.git_cache_up_to_date[url] = True - f = executor.submit(ensure_git_cache_updated, url) + if url not in self.mirror_repos: + mirror_repo = MirroredGitRepo(url) + self.mirror_repos[url] = mirror_repo + f = executor.submit(do_update_repo, mirror_repo) futures.append(f) if url not in self.refs_for_urls: self.refs_for_urls[url] = executor.submit(git_ls_remote, url) @@ -270,22 +289,32 @@ def load_targets(self): target_names = set() self.targets = [] + target_infos = [] for base_target in self.config.get("targets", []): - if "targetConfig" not in base_target: - target = self.render_target(base_target) - dynamic_targets = [target] + target_infos += self.prepare_dynamic_targets(base_target) + + self.clone_dynamic_targets(target_infos) + + for target_info in target_infos: + try: + target = self.build_dynamic_target(target_info.base_target, target_info.dir) + if target_info.extra_target_config is not None: + merge_dict(target, {"targetConfig": target_info.extra_target_config}, clone=False) + except Exception as e: + # Only fail if non-dynamic targets fail to load + if target_info.ref_pattern is None: + raise e + logger.warning("Failed to load dynamic target config for project. Error=%s" % (str(e))) + continue + + target = self.render_target(target) + target["baseTarget"] = target_info.base_target + + if target["name"] in target_names: + logger.warning("Duplicate target %s" % target["name"]) else: - dynamic_targets = self.build_dynamic_targets(base_target) - for i in range(len(dynamic_targets)): - dynamic_targets[i] = self.render_target(dynamic_targets[i]) - dynamic_targets[i]["baseTarget"] = base_target - - for dt in dynamic_targets: - if dt["name"] in target_names: - logger.warning("Duplicate target %s" % dt["name"]) - else: - target_names.add(dt["name"]) - self.targets.append(dt) + target_names.add(target["name"]) + self.targets.append(target) def render_target(self, target): errors = [] @@ -310,22 +339,26 @@ def render_target(self, target): raise errors[0] return target - def build_dynamic_targets(self, base_target): - target_config = base_target["targetConfig"] - if "project" in target_config: - return self.build_dynamic_targets_external(base_target) + def prepare_dynamic_targets(self, base_target): + target_config = base_target.get("targetConfig") + if target_config and "project" in target_config: + return self.prepare_dynamic_targets_external(base_target) else: - return self.build_dynamic_targets_simple(base_target) - - def build_dynamic_targets_simple(self, base_target): - target_config = base_target["targetConfig"] - if "ref" in target_config or "refPattern" in target_config: - raise InvalidKluctlProjectConfig("'ref' and/or 'refPattern' are not allowed for non-external dynamic targets") - - dynamic_targets = [self.build_dynamic_target(base_target, self.kluctl_project_dir)] + return self.prepare_dynamic_targets_simple(base_target) + + def prepare_dynamic_targets_simple(self, base_target): + if "targetConfig" in base_target: + target_config = base_target["targetConfig"] + if "ref" in target_config or "refPattern" in target_config: + raise InvalidKluctlProjectConfig("'ref' and/or 'refPattern' are not allowed for non-external dynamic targets") + + dynamic_targets = [DynamicTargetInfo( + base_target=base_target, + dir=self.kluctl_project_dir, + )] return dynamic_targets - def build_dynamic_targets_external(self, base_target): + def prepare_dynamic_targets_external(self, base_target): target_config = base_target["targetConfig"] git_project = parse_git_project(target_config["project"], None) refs = self.refs_for_urls[git_project.url] @@ -360,49 +393,83 @@ def build_dynamic_targets_external(self, base_target): matched_refs.append(ref[len("refs/heads/"):]) dynamic_targets = [] - involved_repo_refs = {} + for ref in matched_refs: - info = self.clone_git_project({ - "project": { - "url": git_project.url, + dynamic_targets.append(DynamicTargetInfo( + base_target=base_target, + dir=self.build_clone_dir(git_project.url, ref), + git_project=git_project, + ref=ref, + ref_pattern=ref_pattern, + extra_target_config={ "ref": ref, + "defaultBranch": default_branch, } - }, None, False) - involved_repo_refs[ref] = info.commit + )) - try: - target = self.build_dynamic_target(base_target, info.dir) - target["targetConfig"]["ref"] = ref - target["targetConfig"]["defaultBranch"] = default_branch - dynamic_targets.append(target) - except Exception as e: - # Only fail if non-dynamic targets fail to load - if target_config_ref is not None: - raise e - logger.warning("Failed to load dynamic target config for project. Error=%s" % (str(e))) - if git_project is not None: - self.add_involved_repo(git_project.url, ref_pattern, involved_repo_refs) return dynamic_targets - def build_dynamic_target(self, base_target, dir): - target_config = base_target["targetConfig"] - config_file = target_config.get("file", "target-config.yml") - config_path = os.path.join(dir, config_file) - if not os.path.exists(config_path): - raise InvalidKluctlProjectConfig("No target config file with name %s found in target" % config_file) + def clone_dynamic_targets(self, dynamic_targets): + @contextmanager + def lock_all_repos(): + for r in self.mirror_repos.values(): + r.lock() + try: + yield + finally: + for r in self.mirror_repos.values(): + r.unlock() + + with lock_all_repos(), MyThreadPoolExecutor(max_workers=8) as executor: + unique_clones = {} + for target_info in dynamic_targets: + if target_info.git_project is None: + continue - target_config_file = self.load_target_config(config_path) - target = copy_dict(base_target) + if target_info.dir in unique_clones: + continue - target.setdefault("args", {}) - # merge args - for arg_name, value in target_config_file.get("args", {}).items(): - self.check_dynamic_arg(target, arg_name, value) - target["args"][arg_name] = value + f = executor.submit(self.clone_git_project, { + "project": { + "url": target_info.git_project.url, + "ref": target_info.ref, + } + }, None, False, False) + unique_clones[target_info.dir] = f + refs_by_url_and_pattern = {} + + for target_info in dynamic_targets: + if target_info.git_project is None: + continue + info = unique_clones[target_info.dir].result() + refs_by_url_and_pattern.setdefault(info.url, {}).setdefault(target_info.ref_pattern, {})[info.ref] = info.commit + + for url, ref_patterns in refs_by_url_and_pattern.items(): + for ref_pattern, refs in ref_patterns.items(): + self.add_involved_repo(url, ref_pattern, refs) + + def build_dynamic_target(self, base_target, dir): + target = copy_dict(base_target) + target.setdefault("args", {}) target.setdefault("images", []) - # We prepend the dynamic images to ensure they get higher priority later - target["images"] = target_config_file.get("images", []) + target["images"] + + if "targetConfig" in base_target: + target_config = base_target["targetConfig"] + config_file = target_config.get("file", "target-config.yml") + config_path = os.path.join(dir, config_file) + if not os.path.exists(config_path): + raise InvalidKluctlProjectConfig("No target config file with name %s found in target" % config_file) + + target_config_file = self.load_target_config(config_path) + + # merge args + for arg_name, value in target_config_file.get("args", {}).items(): + self.check_dynamic_arg(target, arg_name, value) + target["args"][arg_name] = value + + # We prepend the dynamic images to ensure they get higher priority later + target["images"] = target_config_file.get("images", []) + target["images"] return target diff --git a/kluctl/utils/git_utils.py b/kluctl/utils/git_utils.py index 744382c61..5884780fa 100644 --- a/kluctl/utils/git_utils.py +++ b/kluctl/utils/git_utils.py @@ -164,65 +164,85 @@ def execute(self, command, **kwargs): finally: pass -def _clone_or_update_cache(url, cache_dir, do_update): - init_marker = os.path.join(cache_dir, ".cache.init") +class MirroredGitRepo: + def __init__(self, url): + self.url = url - if os.path.exists(init_marker): - if do_update: - logger.info(f"Updating mirror repo: url='{url}'") - with build_git_object(url, cache_dir) as (g, url): - g.remote("update") - return - - logger.info(f"Cloning mirror repo at {cache_dir}") - with build_git_object(url, cache_dir) as (g, url): - g.clone("--mirror", url, "mirror") - for n in os.listdir(os.path.join(cache_dir, "mirror")): - shutil.move(os.path.join(cache_dir, "mirror", n), cache_dir) - shutil.rmtree(os.path.join(cache_dir, "mirror")) - with open(init_marker, "w"): - # only touch it - pass + remote_name = build_remote_name(url) + self.mirror_dir = os.path.join(get_cache_base_dir(), remote_name) -@contextmanager -def with_git_cache(url, do_update=True): - remote_name = build_remote_name(url) - cache_dir = os.path.join(get_cache_base_dir(), remote_name) - if not os.path.exists(cache_dir): - os.makedirs(cache_dir, exist_ok=True) - lock_file = os.path.join(cache_dir, ".cache.lock") + self.has_lock = False + self.has_updated = False - lock = filelock.FileLock(lock_file) - lock.acquire() + if not os.path.exists(self.mirror_dir): + os.makedirs(self.mirror_dir, exist_ok=True) - try: - _clone_or_update_cache(url, cache_dir, do_update) - yield cache_dir - except GitCommandError as e: - if "did not complete in " in e.stderr: - logger.info("Git command timed out, deleting cache (%s) to ensure that we don't get into an " - "inconsistent state" % cache_dir) - try: - shutil.rmtree(cache_dir) - except: - pass - raise - finally: - lock.release() + self.file_lock = filelock.FileLock(os.path.join(self.mirror_dir, ".cache.lock")) + + def _build_git_object(self): + return build_git_object(self.url, self.mirror_dir) + + def _clone_or_update2(self): + init_marker = os.path.join(self.mirror_dir, ".cache.init") + + if os.path.exists(init_marker): + logger.info(f"Updating mirror repo: url='{self.url}'") + with self._build_git_object() as (g, url): + g.remote("update") + return + + logger.info(f"Cloning mirror repo at {self.mirror_dir}") + with self._build_git_object() as (g, url): + g.clone("--mirror", url, "mirror") + for n in os.listdir(os.path.join(self.mirror_dir, "mirror")): + shutil.move(os.path.join(self.mirror_dir, "mirror", n), self.mirror_dir) + shutil.rmtree(os.path.join(self.mirror_dir, "mirror")) + with open(init_marker, "w"): + # only touch it + pass + + def update(self): + try: + self._clone_or_update2() + self.has_updated = True + except GitCommandError as e: + if "did not complete in " in e.stderr: + logger.info("Git command timed out, deleting cache (%s) to ensure that we don't get into an " + "inconsistent state" % self.mirror_dir) + try: + shutil.rmtree(self.mirror_dir) + except: + pass + raise + + def lock(self): + self.file_lock.acquire() + self.has_lock = True + + def unlock(self): + self.file_lock.release() + self.has_lock = False + + @contextmanager + def locked(self): + self.lock() + + try: + yield self + finally: + self.unlock() -def ensure_git_cache_updated(url): - with with_git_cache(url, do_update=True): - pass + def clone_project(self, ref, target_dir): + assert self.has_lock + assert self.has_updated -def clone_project(url, ref, target_dir, git_cache_up_to_date=None): - logger.info(f"Cloning git project: url='{url}', ref='{ref}'") + logger.info(f"Cloning git project: url='{self.url}', ref='{ref}'") - need_update = (git_cache_up_to_date is None or url not in git_cache_up_to_date) - with with_git_cache(url, do_update=need_update) as cache_dir: - args = ["file://%s" % cache_dir, "--single-branch", target_dir] - if ref is not None: - args += ["--branch", ref] - Git().clone(*args) + with self._build_git_object(): + args = ["file://%s" % self.mirror_dir, "--single-branch", target_dir] + if ref is not None: + args += ["--branch", ref] + Git().clone(*args) def get_git_commit(path): g = Git(path) From 76a0cac223a662412e03775ace799275c0b7e64c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 15 Nov 2021 17:59:34 +0100 Subject: [PATCH 0295/2916] fix: Fix bootstrap command --- kluctl/cli/command_stubs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kluctl/cli/command_stubs.py b/kluctl/cli/command_stubs.py index 47ca30cbd..872af6830 100644 --- a/kluctl/cli/command_stubs.py +++ b/kluctl/cli/command_stubs.py @@ -9,7 +9,7 @@ "installed.\n\n" "Either --target or --cluster must be specified.") @kluctl_project_args(with_a=False) -@misc_arguments(yes=True, dry_run=True, force_apply=True, replace_on_error=True, abort_on_error=True, output_format=True) +@misc_arguments(yes=True, dry_run=True, force_apply=True, replace_on_error=True, hook_timeout=True, abort_on_error=True, output_format=True) @click.pass_obj def bootstrap_command_stub(obj, **kwargs): from kluctl.cli.commands import bootstrap_command From e9537e62c1379b1950ad5d9d71a01ab39320b1fb Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 15 Nov 2021 18:02:10 +0100 Subject: [PATCH 0296/2916] docs: Run replace-commands-help.py --- docs/commands.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/commands.md b/docs/commands.md index 570037423..00f3e545f 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -114,6 +114,9 @@ In addition, the following arguments are available: details. --force-replace-on-error Same as --replace-on-error, but also try to delete and re-create objects. See documentation for more details. + --hook-timeout DURATION Maximum time to wait for hook readiness. The timeout is meant per-hook. Timeouts are + in the duration format (1s, 1m, 1h, ...). If not specified, a default timeout of 5m + is used. --abort-on-error Abort deploying when an error occurs instead of trying the remaining deployments -o, --output TEXT Specify output format and target file, in the format 'format=path'. Format can either be 'text' or 'yaml'. Can be specified multiple times. The actual format for From c85589afeacbac8a1c6cee346792c543d112b2de Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 16 Nov 2021 15:48:49 +0100 Subject: [PATCH 0297/2916] fix: Don't pass -o arguments to plink.exe --- kluctl/utils/git_utils.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/kluctl/utils/git_utils.py b/kluctl/utils/git_utils.py index 5884780fa..f36912f1d 100644 --- a/kluctl/utils/git_utils.py +++ b/kluctl/utils/git_utils.py @@ -145,9 +145,12 @@ def execute(self, command, **kwargs): g = MyGit(working_dir) ssh_command = os.environ.get("GIT_SSH", "ssh") - ssh_command += " -o 'StrictHostKeyChecking=no'" - if sys.platform != "win32": + if sys.platform == "win32": + if "plink.exe" not in ssh_command.lower(): + ssh_command += " -o 'StrictHostKeyChecking=no'" + else: + ssh_command += " -o 'StrictHostKeyChecking=no'" ssh_command += " -o 'ControlMaster=auto'" ssh_command += " -o 'ControlPath=/tmp/kluctl_control_master-%r@%h-%p'" ssh_command += " -o 'ControlPersist=5m'" From 07b9d927677330222cbdc088ecf71bd1b2405f66 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 17 Nov 2021 09:11:19 +0100 Subject: [PATCH 0298/2916] fix: Upgrade to python 3.10.0 to fix pyinstaller issues --- .github/workflows/build-and-release.yml | 4 ++-- .github/workflows/tests.yml | 4 ++-- Dockerfile | 2 +- setup.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index b19128afb..037c36727 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -103,7 +103,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 with: - python-version: 3.8.10 + python-version: 3.10.0 - name: Write Version to _version.py run: | echo "__version__ = '${{ needs.version.outputs.version }}'" > kluctl/_version.py @@ -143,7 +143,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 with: - python-version: 3.8.10 + python-version: 3.10.0 - name: Write Version to _version.py run: | echo "__version__ = '${{ needs.version.outputs.version }}'" > kluctl/_version.py diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 077e17ebc..e83e1928b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -79,7 +79,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 with: - python-version: 3.8.10 + python-version: 3.10.0 - uses: actions/cache@v2 with: path: .venv @@ -205,7 +205,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 with: - python-version: 3.8.10 + python-version: 3.10.0 - name: pip install run: | pip install . diff --git a/Dockerfile b/Dockerfile index d9a5f3161..2d027f05f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.8.10-slim-buster +FROM python:3.10.0-slim-buster RUN apt update && apt install wget libyaml-dev git -y && rm -rf /var/lib/apt/lists/* diff --git a/setup.py b/setup.py index 6acf2b30a..6462af950 100644 --- a/setup.py +++ b/setup.py @@ -68,7 +68,7 @@ "PyJWT>=2.2.0", "python-dxf>=7.7.1", "gitpython>=3.1.24", - "jsonschema>=4.0.1", + "jsonschema>=4.2.1", "filelock>=3.3.0", "python-gitlab>=2.10.1", "jsonpath-ng>=1.5.3", From ee19377829406e9e287079ca39b77e122762bd36 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 17 Nov 2021 09:16:20 +0100 Subject: [PATCH 0299/2916] fix: Upgrade pyinstaller --- .github/workflows/build-and-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index 037c36727..557ca9f3e 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -124,7 +124,7 @@ jobs: pip install . - name: Run Pyinstaller run: | - pip install pyinstaller==4.2 + pip install pyinstaller==4.7 pyinstaller kluctl.spec - name: Upload artifacts uses: actions/upload-artifact@v2 From cbc70c041e9364098e204df02a6d761887243fcb Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 17 Nov 2021 09:35:28 +0100 Subject: [PATCH 0300/2916] tests: Run pyinstaller based executables in e2e tests --- .github/workflows/build-and-release.yml | 2 +- .github/workflows/tests.yml | 7 ++++++- kluctl/e2e/kluctl_test_project.py | 7 +++---- requirements-dev.txt | 1 + 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index 557ca9f3e..875406881 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -122,9 +122,9 @@ jobs: - name: Install kluctl run: | pip install . + pip install -r requirements-dev.txt - name: Run Pyinstaller run: | - pip install pyinstaller==4.7 pyinstaller kluctl.spec - name: Upload artifacts uses: actions/upload-artifact@v2 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e83e1928b..caa99f8ed 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -96,11 +96,16 @@ jobs: else echo "$GITHUB_WORKSPACE/.venv/bin" >> $GITHUB_PATH fi - - name: Install dependencies + - name: Install kluctl and dependencies shell: bash run: | python ./setup.py develop pip install -r requirements-dev.txt + - name: Create pyinstaller executable + shell: bash + run: | + pyinstaller kluctl.spec + cp dist/* $TOOLS_TARGET_DIR - name: Provide required tools versions shell: bash run: | diff --git a/kluctl/e2e/kluctl_test_project.py b/kluctl/e2e/kluctl_test_project.py index b038bb5b4..9f974b5d0 100644 --- a/kluctl/e2e/kluctl_test_project.py +++ b/kluctl/e2e/kluctl_test_project.py @@ -284,9 +284,8 @@ def get_sealed_secrets_dir(self): return self.get_kluctl_project_dir() def kluctl(self, *args: str, check_rc=True, **kwargs): - python_exe = shutil.which("python") - kluctl_path = os.path.join(os.path.dirname(__file__), "..", "..", "cli.py") - args2 = [python_exe, kluctl_path] + kluctl_exe = shutil.which("kluctl") + args2 = [kluctl_exe] args2 += args cwd = None @@ -308,7 +307,7 @@ def kluctl(self, *args: str, check_rc=True, **kwargs): "KUBECONFIG": sep.join(os.path.abspath(str(x)) for x in self.kubeconfigs), }) - logger.info("Running kluctl: %s" % " ".join(args2[2:])) + logger.info("Running kluctl: %s" % " ".join(args2)) def do_log(lines): for l in lines: diff --git a/requirements-dev.txt b/requirements-dev.txt index 7239cfa99..69a07bae0 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,3 +1,4 @@ pytest>=6.2.5 pytest-xdist>=2.4.0 pytest-kind>=21.1.3 +pyinstaller>=4.7.0 From f89b98cb9098f6c7ab06885114989599368135cd Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 17 Nov 2021 10:38:21 +0100 Subject: [PATCH 0301/2916] tests: Ensure pyinstaller based kluctl is used --- .github/workflows/tests.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index caa99f8ed..f31160ad6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -105,7 +105,12 @@ jobs: shell: bash run: | pyinstaller kluctl.spec + OLD_KLUCTL=$(which kluctl) + echo OLD_KLUCTL=$OLD_KLUCTL cp dist/* $TOOLS_TARGET_DIR + which kluctl + rm $OLD_KLUCTL + which kluctl - name: Provide required tools versions shell: bash run: | From 3f1b02c4751af28679b2a38288347229b23761c5 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 18 Nov 2021 16:45:31 +0100 Subject: [PATCH 0302/2916] fix: Use re.fullmatch() where appropriate --- kluctl/kluctl_project/kluctl_project.py | 2 +- kluctl/utils/env_config_sets.py | 4 ++-- kluctl/utils/git_utils.py | 4 ++-- kluctl/utils/versions.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/kluctl/kluctl_project/kluctl_project.py b/kluctl/kluctl_project/kluctl_project.py index a82c84dbb..425e27629 100644 --- a/kluctl/kluctl_project/kluctl_project.py +++ b/kluctl/kluctl_project/kluctl_project.py @@ -507,7 +507,7 @@ def check_dynamic_arg(self, target, arg_name, arg_value): raise InvalidKluctlProjectConfig(f"Dynamic argument {arg_name} is not allowed for target") arg_pattern = dyn_arg.get("pattern", ".*") - if not re.match(arg_pattern, arg_value): + if not re.fullmatch(arg_pattern, arg_value): raise InvalidKluctlProjectConfig(f"Dynamic argument {arg_name} does not match required pattern '{arg_pattern}'") diff --git a/kluctl/utils/env_config_sets.py b/kluctl/utils/env_config_sets.py index 8dd4d09e4..5a13d1f19 100644 --- a/kluctl/utils/env_config_sets.py +++ b/kluctl/utils/env_config_sets.py @@ -8,13 +8,13 @@ def parse_env_config_sets(prefix): ret = {} for env_name, env_value in os.environ.items(): - m = r.match(env_name) + m = r.fullmatch(env_name) if m: idx = m.group(1) key = m.group(2) ret.setdefault(idx, {})[key] = env_value else: - m = r2.match(env_name) + m = r2.fullmatch(env_name) if m: key = m.group(1) ret.setdefault(None, {})[key] = env_value diff --git a/kluctl/utils/git_utils.py b/kluctl/utils/git_utils.py index f36912f1d..609eabd7f 100644 --- a/kluctl/utils/git_utils.py +++ b/kluctl/utils/git_utils.py @@ -278,7 +278,7 @@ def filter_remote_refs(refs, pattern, trim): pattern = re.compile(r"refs/heads/%s" % pattern) matching_refs = {} for r, commit in refs.items(): - if pattern.match(r): + if pattern.fullmatch(r): r2 = r if trim: r2 = r[len("refs/heads/"):] @@ -322,7 +322,7 @@ def normalize_port(schema, port): return GitUrl(url.scheme, url.hostname, port, path, url.username) pattern = re.compile("(.+@)?([\w\d\.]+):(.*)") - m = pattern.match(p) + m = pattern.fullmatch(p) if not m: raise Exception("Invalid git url %s" % p) diff --git a/kluctl/utils/versions.py b/kluctl/utils/versions.py index e4135669a..10b393b4b 100644 --- a/kluctl/utils/versions.py +++ b/kluctl/utils/versions.py @@ -106,7 +106,7 @@ def __init__(self, pattern): self.pattern = re.compile(pattern) def match(self, version): - return self.pattern.match(version) + return self.pattern.fullmatch(version) def latest(self, versions): versions = sorted(versions, key=LooseVersionComparator) From 40007d2767b42c13ef9ed66de9322f374f35151e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 18 Nov 2021 16:45:45 +0100 Subject: [PATCH 0303/2916] fix: Better error output for {{ raise() }} --- kluctl/utils/jinja2_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kluctl/utils/jinja2_utils.py b/kluctl/utils/jinja2_utils.py index 00e41e73b..d0c62d275 100644 --- a/kluctl/utils/jinja2_utils.py +++ b/kluctl/utils/jinja2_utils.py @@ -84,7 +84,7 @@ def update_dict(a, b): return "" def raise_helper(msg): - raise Exception(msg) + raise TemplateError(msg) def debug_print(msg): logger.info("debug_print: %s" % str(msg)) From 97c04f720fe628406f311624e76dc3dac1deb282 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 24 Nov 2021 14:50:26 +0100 Subject: [PATCH 0304/2916] fix: Fix crash when printing api errors --- kluctl/deployment/apply_util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kluctl/deployment/apply_util.py b/kluctl/deployment/apply_util.py index fc7c10281..30ed535d9 100644 --- a/kluctl/deployment/apply_util.py +++ b/kluctl/deployment/apply_util.py @@ -127,7 +127,7 @@ def retry_with_conflicts(e): self.handle_result(r, patch_warnings + resolve_warnings, hook) except ApiException as e: # We didn't manage to solve it, better to abort (and not retry with replace!) - self.handle_error(ref, e) + self.handle_error(ref, self.k8s_cluster.get_status_message(e)) try: r, patch_warnings = self.k8s_cluster.patch_object(x, force_dry_run=self.dry_run) From 0ddd0f6ba07290c0e90286f278fe31fde849a0e6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 24 Nov 2021 14:51:20 +0100 Subject: [PATCH 0305/2916] feat: Pass --api-versions for current cluster to Helm --- kluctl/deployment/deployment_collection.py | 2 +- kluctl/deployment/helm_chart.py | 6 +++++- kluctl/deployment/kustomize_deployment.py | 4 ++-- kluctl/utils/k8s_cluster_real.py | 9 +++++++++ 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index e32e59089..f830de32a 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -90,7 +90,7 @@ def render_deployments(self, k8s_cluster): jobs = [] for d in self.deployments: - jobs += d.render_helm_charts(executor) + jobs += d.render_helm_charts(k8s_cluster, executor) for job in jobs: job.result() diff --git a/kluctl/deployment/helm_chart.py b/kluctl/deployment/helm_chart.py index e2585ada8..2fb3cfcd9 100644 --- a/kluctl/deployment/helm_chart.py +++ b/kluctl/deployment/helm_chart.py @@ -85,7 +85,7 @@ def check_update(self): return None return latest_version - def render(self): + def render(self, k8s_cluster): chart_dir = os.path.join(self.dir, 'charts', self.get_chart_name()) values_path = os.path.join(self.dir, 'helm-values.yml') output_path = os.path.join(self.dir, self.conf['output']) @@ -104,6 +104,10 @@ def render(self): args.append("--skip-tests") + for api_version in k8s_cluster.get_all_api_versions(): + args.append("--api-versions=%s" % api_version) + args.append("--kube-version=%s" % k8s_cluster.server_version) + r, rendered, stderr = self.do_helm(args) rendered = rendered.decode('utf-8') diff --git a/kluctl/deployment/kustomize_deployment.py b/kluctl/deployment/kustomize_deployment.py index befecf8f6..b29cbcf46 100644 --- a/kluctl/deployment/kustomize_deployment.py +++ b/kluctl/deployment/kustomize_deployment.py @@ -129,7 +129,7 @@ def render(self, k8s_cluster, executor): d = TemplatedDir(root_dir, rel_deployment_dir, jinja_env, executor, excluded_patterns) return d.async_render_subdir(rel_kustomize_dir, abs_rendered_dir) - def render_helm_charts(self, executor): + def render_helm_charts(self, k8s_cluster, executor): if self.dir is None: return [] @@ -140,7 +140,7 @@ def render_helm_charts(self, executor): if not os.path.exists(path): continue chart = HelmChart(path) - job = executor.submit(chart.render) + job = executor.submit(chart.render, k8s_cluster) jobs.append(job) return jobs diff --git a/kluctl/utils/k8s_cluster_real.py b/kluctl/utils/k8s_cluster_real.py index 471612d63..7597c9eab 100644 --- a/kluctl/utils/k8s_cluster_real.py +++ b/kluctl/utils/k8s_cluster_real.py @@ -149,6 +149,15 @@ def get_preferred_resource(self, group, kind): return r raise ResourceNotFoundError(f"{group}/{kind} not found") + def get_all_resources(self): + return list(self.dynamic_client.resources.search()) + + def get_all_api_versions(self): + ret = set() + for r in self.get_all_resources(): + ret.add(r.group_version) + return ret + def _get_objects_for_resource(self, resource, name, namespace, labels, as_table): label_selector = self._build_label_selector(labels) From cbe6b4a4b860983b5669212d219c28c62cc588c7 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 24 Nov 2021 14:51:53 +0100 Subject: [PATCH 0306/2916] fix: Support crash in fix_object_for_patch for EKS version numbers --- kluctl/utils/k8s_cluster_real.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/kluctl/utils/k8s_cluster_real.py b/kluctl/utils/k8s_cluster_real.py index 7597c9eab..3b0e5e32e 100644 --- a/kluctl/utils/k8s_cluster_real.py +++ b/kluctl/utils/k8s_cluster_real.py @@ -3,7 +3,6 @@ import logging import threading import time -from distutils.version import LooseVersion from kubernetes import config from kubernetes.client import ApiClient, Configuration, ApiException @@ -65,7 +64,7 @@ def __init__(self, context, dry_run): self.table_dynamic_client = DynamicClient(self.table_api_client, discoverer=MyDiscoverer) v = self.dynamic_client.version - self.server_version = "%s.%s" % (v['kubernetes']['major'], v['kubernetes']['minor']) + self.server_version = v["kubernetes"]["gitVersion"] def _fix_api_version(self, group, version): if group is None and version is not None: @@ -267,9 +266,9 @@ def fix_object_for_patch(self, o): # default values. We need to fix these resources. # UPDATE even though https://github.com/kubernetes-sigs/structured-merge-diff/issues/130 says it's fixed, the # issue is still present. - needs_defaults_fix = LooseVersion(self.server_version) < LooseVersion('1.21') + needs_defaults_fix = LooseVersionComparator(self.server_version) < LooseVersionComparator('1.21') # TODO check when this is actually fixed (see https://github.com/kubernetes/kubernetes/issues/94275) - needs_type_conversion_fix = LooseVersion(self.server_version) < LooseVersion('1.100') + needs_type_conversion_fix = LooseVersionComparator(self.server_version) < LooseVersionComparator('1.100') if not needs_defaults_fix and not needs_type_conversion_fix: return o From 271e7d19b42ce8baf6e0ba4bc352bfad85b5bd0c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 24 Nov 2021 14:52:08 +0100 Subject: [PATCH 0307/2916] fix: Cleanup old git cache dirs before cloning --- kluctl/utils/git_utils.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/kluctl/utils/git_utils.py b/kluctl/utils/git_utils.py index 609eabd7f..09189b9cf 100644 --- a/kluctl/utils/git_utils.py +++ b/kluctl/utils/git_utils.py @@ -194,6 +194,14 @@ def _clone_or_update2(self): g.remote("update") return + if os.path.exists(self.mirror_dir): + for n in os.listdir(self.mirror_dir): + p = os.path.join(self.mirror_dir, n) + if os.path.isdir(p): + shutil.rmtree(p) + else: + os.unlink(p) + logger.info(f"Cloning mirror repo at {self.mirror_dir}") with self._build_git_object() as (g, url): g.clone("--mirror", url, "mirror") From 8f2371c8137aa9b51bc5751a492bb248acbdb6b3 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 24 Nov 2021 15:21:02 +0100 Subject: [PATCH 0308/2916] feat: Remove all offline capabilities --- docs/commands.md | 3 --- kluctl/cli/command_stubs.py | 5 ----- kluctl/cli/commands.py | 4 ++-- kluctl/cli/utils.py | 28 +++++++------------------ kluctl/deployment/images.py | 4 ---- kluctl/kluctl_project/kluctl_project.py | 2 +- kluctl/utils/k8s_cluster_base.py | 5 +---- 7 files changed, 12 insertions(+), 39 deletions(-) diff --git a/docs/commands.md b/docs/commands.md index 00f3e545f..b63cbc28e 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -325,8 +325,6 @@ In addition, the following arguments are available: ``` Misc arguments: -o, --output TEXT Specify output target file. Can be specified multiple times - --no-kubernetes Don't check kubernetes for current image versions - --no-registries Don't check registries for new image versions --simple Output a simplified version of the images list ``` @@ -416,7 +414,6 @@ In addition, the following arguments are available: --output-images FILE Also output images list to given FILE. This output the same result as from the list- images command. --output-single-yaml FILE Also write all resources into a single yaml file. - --offline Go offline, meaning that kubernetes and registries are not asked for image versions ``` diff --git a/kluctl/cli/command_stubs.py b/kluctl/cli/command_stubs.py index 872af6830..a273c6f7c 100644 --- a/kluctl/cli/command_stubs.py +++ b/kluctl/cli/command_stubs.py @@ -131,9 +131,6 @@ def validate_command_stub(obj, **kwargs): @optgroup.option("--output-single-yaml", help="Also write all resources into a single yaml file.", type=click.Path(dir_okay=False)) -@optgroup.option("--offline", - help="Go offline, meaning that kubernetes and registries are not asked for image versions", - is_flag=True) @click.pass_obj def render_command_stub(obj, **kwargs): from kluctl.cli.commands import render_command @@ -148,8 +145,6 @@ def render_command_stub(obj, **kwargs): @image_args() @include_exclude_args() @misc_arguments(output=True) -@optgroup.option("--no-kubernetes", help="Don't check kubernetes for current image versions", default=False, is_flag=True) -@optgroup.option("--no-registries", help="Don't check registries for new image versions", default=False, is_flag=True) @optgroup.option("--simple", help="Output a simplified version of the images list", is_flag=True) @click.pass_obj def list_images_command_stub(obj, **kwargs): diff --git a/kluctl/cli/commands.py b/kluctl/cli/commands.py index 46215ac39..f82ff9dc0 100644 --- a/kluctl/cli/commands.py +++ b/kluctl/cli/commands.py @@ -136,7 +136,7 @@ def render_command(obj, kwargs): if kwargs["render_output_dir"] is None: kwargs["render_output_dir"] = tempfile.mkdtemp(dir=get_tmp_base_dir(), prefix="render-") - with project_command_context(kwargs, force_offline_images=kwargs["offline"], force_offline_kubernetes=kwargs["offline"]) as cmd_ctx: + with project_command_context(kwargs) as cmd_ctx: logger.info('Rendered into: %s' % cmd_ctx.deployment_collection.tmpdir) if kwargs["output_images"]: @@ -153,7 +153,7 @@ def render_command(obj, kwargs): output_yaml_result([kwargs["output_single_yaml"]], all_yamls, all=True) def list_images_command(obj, kwargs): - with project_command_context(kwargs, force_offline_kubernetes=kwargs["no_kubernetes"]) as cmd_ctx: + with project_command_context(kwargs) as cmd_ctx: cmd_ctx.images.raise_on_error = False result = { diff --git a/kluctl/cli/utils.py b/kluctl/cli/utils.py index 3d5d264f0..13b04a5a1 100644 --- a/kluctl/cli/utils.py +++ b/kluctl/cli/utils.py @@ -25,14 +25,10 @@ def build_jinja_vars(cluster_vars): return jinja_vars -def build_deploy_images(force_offline, kwargs): - image_registries = None - if not kwargs.get("no_registries", False): - image_registries = init_image_registries() +def build_deploy_images(kwargs): + image_registries = init_image_registries() images = Images(image_registries) - offline = force_offline or kwargs.get("offline", False) - images.update_images = kwargs.get("update_images", False) and not offline - images.no_registries = kwargs.get("no_registries", False) or offline + images.update_images = kwargs.get("update_images", False) return images def build_fixed_image_entry_from_arg(arg): @@ -96,24 +92,17 @@ class CommandContext: images: Images @contextlib.contextmanager -def project_command_context(kwargs, - force_offline_images=False, - force_offline_kubernetes=False) -> ContextManager[CommandContext]: +def project_command_context(kwargs) -> ContextManager[CommandContext]: with load_kluctl_project_from_args(kwargs) as kluctl_project: target = None if kwargs["target"]: target = kluctl_project.find_target(kwargs["target"]) - with project_target_command_context(kwargs, kluctl_project, target, - force_offline_images=force_offline_images, - force_offline_kubernetes=force_offline_kubernetes) as cmd_ctx: + with project_target_command_context(kwargs, kluctl_project, target) as cmd_ctx: yield cmd_ctx @contextlib.contextmanager -def project_target_command_context(kwargs, kluctl_project, target, - force_offline_images=False, - force_offline_kubernetes=False, - for_seal=False) -> ContextManager[CommandContext]: +def project_target_command_context(kwargs, kluctl_project, target, for_seal=False) -> ContextManager[CommandContext]: cluster_name = kwargs["cluster"] if not cluster_name: @@ -122,11 +111,10 @@ def project_target_command_context(kwargs, kluctl_project, target, cluster_name = target["cluster"] cluster_vars, k8s_cluster = load_cluster_config(kluctl_project.clusters_dir, cluster_name, - dry_run=kwargs.get("dry_run", True), - offline=force_offline_kubernetes) + dry_run=kwargs.get("dry_run", True)) jinja_vars = build_jinja_vars(cluster_vars) - images = build_deploy_images(force_offline_images, kwargs) + images = build_deploy_images(kwargs) inclusion = parse_inclusion(kwargs) option_args = parse_args(kwargs.get("arg", [])) diff --git a/kluctl/deployment/images.py b/kluctl/deployment/images.py index a48ddba80..3881ac51b 100644 --- a/kluctl/deployment/images.py +++ b/kluctl/deployment/images.py @@ -15,7 +15,6 @@ class Images(object): def __init__(self, image_registries): self.image_registries = image_registries self.update_images = False - self.no_registries = image_registries is None self.raise_on_error = False self.image_tags_cache = ThreadSafeMultiCache() @@ -38,9 +37,6 @@ def do_get(): return self.image_tags_cache.get(image, "tags", do_get) def get_latest_image_from_registry(self, image, latest_version): - if self.no_registries: - return None - if isinstance(latest_version, str): latest_version = build_latest_version_from_str(latest_version) diff --git a/kluctl/kluctl_project/kluctl_project.py b/kluctl/kluctl_project/kluctl_project.py index 425e27629..1394d15f4 100644 --- a/kluctl/kluctl_project/kluctl_project.py +++ b/kluctl/kluctl_project/kluctl_project.py @@ -327,7 +327,7 @@ def render_target(self, target): try: # Try to load cluster vars. This might fail in case jinja templating is used in the cluster name # of the target. We assume that this will then succeed in a later iteration - cluster_vars, _ = load_cluster_config(self.clusters_dir, target["cluster"], offline=True) + cluster_vars, _ = load_cluster_config(self.clusters_dir, target["cluster"]) jinja2_vars["cluster"] = cluster_vars except: pass diff --git a/kluctl/utils/k8s_cluster_base.py b/kluctl/utils/k8s_cluster_base.py index 720c69294..d772ef774 100644 --- a/kluctl/utils/k8s_cluster_base.py +++ b/kluctl/utils/k8s_cluster_base.py @@ -44,7 +44,7 @@ def get_objects_metadata(self, group=None, version=None, kind=None, name=None, n raise ApiException("group/kind must be supplied") return self.get_objects(group=group, version=version, kind=kind, name=name, namespace=namespace, labels=labels, as_table=True) -def load_cluster_config(cluster_dir, cluster_name, offline=False, dry_run=True): +def load_cluster_config(cluster_dir, cluster_name, dry_run=True): if cluster_name is None: raise CommandError("Cluster name must be specified!") @@ -55,9 +55,6 @@ def load_cluster_config(cluster_dir, cluster_name, offline=False, dry_run=True): cluster = y['cluster'] - if offline: - return cluster, None - if cluster['name'] != cluster_name: raise CommandError('Cluster name in %s does not match requested cluster name %s' % (cluster['name'], cluster_name)) From 6f7a015d437ec6bfff0a37bd8fb8f824fb48c82c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 24 Nov 2021 15:50:45 +0100 Subject: [PATCH 0309/2916] fix: Don't delete .cache.lock when cleaning git cache dir --- kluctl/utils/git_utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kluctl/utils/git_utils.py b/kluctl/utils/git_utils.py index 09189b9cf..820a6c695 100644 --- a/kluctl/utils/git_utils.py +++ b/kluctl/utils/git_utils.py @@ -196,6 +196,8 @@ def _clone_or_update2(self): if os.path.exists(self.mirror_dir): for n in os.listdir(self.mirror_dir): + if n == ".cache.lock": + continue p = os.path.join(self.mirror_dir, n) if os.path.isdir(p): shutil.rmtree(p) From 3bc5f83c01e3de876a9270f667becd1ab4b73f01 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 24 Nov 2021 16:03:19 +0100 Subject: [PATCH 0310/2916] fix: Fix spurious permission errors on Windows --- kluctl/utils/jinja2_cache.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/kluctl/utils/jinja2_cache.py b/kluctl/utils/jinja2_cache.py index 8824fe7cb..9cb6afac5 100644 --- a/kluctl/utils/jinja2_cache.py +++ b/kluctl/utils/jinja2_cache.py @@ -109,10 +109,15 @@ def load_bytecode(self, bucket: Bucket) -> None: filename = self._get_cache_filename(bucket) if os.path.exists(filename): - with open(filename, "rb") as f: - bucket.load_bytecode(f) - if bucket.code is not None: - bucket.code = self.replace_code_filenames(bucket.code, "", bucket.key[1]) + for i in range(4): + try: + with open(filename, "rb") as f: + bucket.load_bytecode(f) + if bucket.code is not None: + bucket.code = self.replace_code_filenames(bucket.code, "", bucket.key[1]) + except PermissionError: + # Retry. Windows is still failing from time to time... + continue self.touch_loaded_marker(bucket) def dump_bytecode(self, bucket: Bucket) -> None: From 4e6d4a80a52138fdd6ea761d202eba2f04e47d2b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 16 Dec 2021 17:34:26 +0100 Subject: [PATCH 0311/2916] feat: Add kluctl.io/force-apply annotations --- docs/annotations.md | 12 +++++++++++- kluctl/diff/managed_fields.py | 34 +++++++++++++++++++++++++--------- kluctl/utils/dict_utils.py | 8 ++++++++ 3 files changed, 44 insertions(+), 10 deletions(-) diff --git a/docs/annotations.md b/docs/annotations.md index 9230e547c..857ddd959 100644 --- a/docs/annotations.md +++ b/docs/annotations.md @@ -37,7 +37,7 @@ spec: count: 1 ``` -If more than one patch needs to be specified, add `-xxx` to the annoation key, where `xxx` is an arbitrary number. +If more than one patch needs to be specified, add `-xxx` to the annotation key, where `xxx` is an arbitrary number. ### kluctl.io/downscale-ignore If set to "true", the resource will be ignored while [downscale](./commands.md#downscale) is executed. @@ -45,6 +45,16 @@ If set to "true", the resource will be ignored while [downscale](./commands.md#d ### kluctl.io/downscale-delete If set to "true", the resource will be deleted while [downscale](./commands.md#downscale) is executed. +### kluctl.io/force-apply +If set to "true", the whole resource will be force-applied, meaning that all fields will be overwritten in case of +field manager conflicts. + +### kluctl.io/force-apply-field +Specifies a [JSON Path](https://goessner.net/articles/JsonPath/) for fields that should be force-applied. Matching +fields will be overwritten in case of field manager conflicts. + +If more than one field needs to be specified, add `-xxx` to the annotation key, where `xxx` is an arbitrary number. + ## Hooks related See [hooks](./hooks.md) for more details. diff --git a/kluctl/diff/managed_fields.py b/kluctl/diff/managed_fields.py index df84430e3..7fbc16501 100644 --- a/kluctl/diff/managed_fields.py +++ b/kluctl/diff/managed_fields.py @@ -2,10 +2,14 @@ import re from kluctl.utils.dict_utils import copy_dict, object_iterator, del_dict_value, get_dict_value, is_iterable, \ - has_dict_value -from kluctl.utils.jsonpath_utils import convert_list_to_json_path + has_dict_value, list_matching_dict_pathes +from kluctl.utils.jsonpath_utils import convert_list_to_json_path, parse_json_path # We automatically force overwrite these fields as we assume these are human-edited +from kluctl.utils.utils import parse_bool + +FORCE_APPLY_FIELD_ANNOTATION_REGEX = re.compile(r"^kluctl.io/force-apply-field(-\d*)?$") + overwrite_allowed_managers = [ "kluctl", "kubectl", @@ -114,6 +118,15 @@ def resolve_field_manager_conflicts(local_object, remote_object, conflict_status ret = copy_dict(local_object) + force_apply_all = get_dict_value(local_object, 'metadata.annotations["kluctl.io/force-apply"]', "false") + force_apply_all = parse_bool(force_apply_all) + + force_apply_fields = set() + for k, v in get_dict_value(local_object, "metadata.annotations", {}).items(): + if FORCE_APPLY_FIELD_ANNOTATION_REGEX.fullmatch(k): + for x in list_matching_dict_pathes(ret, v): + force_apply_fields.add(x) + lost_ownership = [] for cause in get_dict_value(conflict_status, "details.causes", []): if cause.get("reason") != "FieldManagerConflict" or "field" not in cause: @@ -132,16 +145,19 @@ def resolve_field_manager_conflicts(local_object, remote_object, conflict_status if not has_dict_value(local_object, p): raise Exception("Field '%s' not found in local object" % cause["field"]) + local_value = get_dict_value(local_object, p) + remote_value = get_dict_value(remote_object, p) + overwrite = True - for mf in managers_by_field[mf_path]: - if not any(re.fullmatch(x, mf["manager"]) for x in overwrite_allowed_managers): - overwrite = False - break + if not force_apply_all: + for mf in managers_by_field[mf_path]: + if not any(re.fullmatch(x, mf["manager"]) for x in overwrite_allowed_managers): + overwrite = False + break + if str(parse_json_path(p)) in force_apply_fields: + overwrite = True if not overwrite: - local_value = get_dict_value(local_object, p) - remote_value = get_dict_value(remote_object, p) - del_dict_value(ret, p) if local_value != remote_value: diff --git a/kluctl/utils/dict_utils.py b/kluctl/utils/dict_utils.py index 378043eec..5703769fa 100644 --- a/kluctl/utils/dict_utils.py +++ b/kluctl/utils/dict_utils.py @@ -69,6 +69,14 @@ def del_dict_value(d, k): p = parse_json_path(k) p.filter(lambda x: True, d) +def list_matching_dict_pathes(d, k): + p = parse_json_path(k) + f = p.find(d) + ret = [] + for x in f: + ret.append(str(x.full_path)) + return ret + def is_empty(o): if isinstance(o, dict) or isinstance(o, list): return len(o) == 0 From 06920cb42316899c7933d084d07805771d9cf9f1 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 16 Dec 2021 17:35:22 +0100 Subject: [PATCH 0312/2916] feat: Implement AWS secrets manager support --- docs/kluctl_project.md | 30 +++++++++++++++ kluctl/kluctl_project/aws_secrets_manager.py | 40 ++++++++++++++++++++ kluctl/kluctl_project/secrets.py | 15 ++++++++ kluctl/schemas/kluctl-project.yml | 23 +++++++++++ kluctl/seal/seal_command.py | 2 + kluctl/utils/aws_utils.py | 19 ++++++++++ setup.py | 1 + 7 files changed, 130 insertions(+) create mode 100644 kluctl/kluctl_project/aws_secrets_manager.py create mode 100644 kluctl/utils/aws_utils.py diff --git a/docs/kluctl_project.md b/docs/kluctl_project.md index 7631a1158..4c2c962c8 100644 --- a/docs/kluctl_project.md +++ b/docs/kluctl_project.md @@ -203,6 +203,36 @@ secretsConfig: passwordField: field-name # This is optional and defaults to GenericField1 ``` +#### awsSecretsManager +[AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) integration. Loads a secrets YAML from an AWS Secrets +Manager secret. The secret can either be specified via an ARN or via a secretName and region combination. An AWS +config profile can also be specified (which must exist while sealing). + +Example using an ARN: +```yaml +secretsConfig: + secretSets: + - name: prod + sources: + - awsSecretsManager: + secretName: arn:aws:secretsmanager:eu-central-1:12345678:secret:secret-name-XYZ + profile: my-prod-profile +``` + +Example using a secret name and region: +```yaml +secretsConfig: + secretSets: + - name: prod + sources: + - awsSecretsManager: + secretName: secret-name + region: eu-central-1 + profile: my-prod-profile +``` + +The advantage of the latter is that the auto-generated suffix in the ARN (which might not be known at the time of +writing the configuration) doesn't have to be specified. ## Dynamic Targets diff --git a/kluctl/kluctl_project/aws_secrets_manager.py b/kluctl/kluctl_project/aws_secrets_manager.py new file mode 100644 index 000000000..7d5e56aa8 --- /dev/null +++ b/kluctl/kluctl_project/aws_secrets_manager.py @@ -0,0 +1,40 @@ +import base64 +import os + +import boto3 +from botocore.exceptions import ClientError, BotoCoreError + +from kluctl.utils.aws_utils import parse_arn +from kluctl.utils.exceptions import InvalidKluctlProjectConfig + + +def get_aws_secrets_manager_secret(profile, region_name, secret_name): + if "AWS_PROFILE" in os.environ: + # Environment variable always takes precedence + profile = None + session = boto3.session.Session(profile_name=profile) + + if region_name is None: + try: + arn = parse_arn(secret_name) + region_name = arn["region"] + except: + raise InvalidKluctlProjectConfig("When omitting the AWS region, the secret name must be a valid ARN") + + sm_client = session.client( + service_name='secretsmanager', + region_name=region_name + ) + try: + secret_value = sm_client.get_secret_value(SecretId=secret_name) + except BotoCoreError as e: + raise InvalidKluctlProjectConfig("Getting secret %s from AWS secrets manager failed: %s" % (secret_name, str(e))) + except ClientError as e: + raise InvalidKluctlProjectConfig("Getting secret %s from AWS secrets manager failed: %s" % (secret_name, str(e))) + + if 'SecretString' in secret_value: + secret = secret_value['SecretString'] + else: + secret = base64.b64decode(secret_value['SecretBinary']).decode("utf-8") + + return secret diff --git a/kluctl/kluctl_project/secrets.py b/kluctl/kluctl_project/secrets.py index 8eb2b6739..77ddbf632 100644 --- a/kluctl/kluctl_project/secrets.py +++ b/kluctl/kluctl_project/secrets.py @@ -2,6 +2,7 @@ import os from kluctl.kluctl_project.passwordstate import Passwordstate +from kluctl.utils.dict_utils import get_dict_value from kluctl.utils.exceptions import InvalidKluctlProjectConfig from kluctl.utils.yaml_utils import yaml_load_file, yaml_load @@ -20,6 +21,8 @@ def load_secrets(self, secrets_source): return self.load_secrets_file(secrets_source) if "passwordstate" in secrets_source: return self.load_secrets_passwordstate(secrets_source) + if "awsSecretsManager" in secrets_source: + return self.load_secrets_aws_secrets_manager(secrets_source) raise InvalidKluctlProjectConfig("Invalid secrets entry") def load_secrets_file(self, secrets_source): @@ -50,3 +53,15 @@ def load_secrets_passwordstate(self, secrets_source): doc = self.passwordstate.get_password(host, l["PasswordListID"], title, field) y = yaml_load(doc) return y.get("secrets", {}) + + def load_secrets_aws_secrets_manager(self, secrets_source): + profile = get_dict_value(secrets_source, "awsSecretsManager.profile") + secret_name = get_dict_value(secrets_source, "awsSecretsManager.secretName") + region_name = get_dict_value(secrets_source, "awsSecretsManager.region") + if not secret_name: + raise InvalidKluctlProjectConfig("secretName is missing in secrets entry") + + from kluctl.kluctl_project.aws_secrets_manager import get_aws_secrets_manager_secret + secret = get_aws_secrets_manager_secret(profile, region_name, secret_name) + y = yaml_load(secret) + return y.get("secrets", {}) diff --git a/kluctl/schemas/kluctl-project.yml b/kluctl/schemas/kluctl-project.yml index 37d31b555..33ba16344 100644 --- a/kluctl/schemas/kluctl-project.yml +++ b/kluctl/schemas/kluctl-project.yml @@ -120,6 +120,7 @@ definitions: oneOf: - $ref: "#/definitions/secret_source_path" - $ref: "#/definitions/secret_source_passwordstate" + - $ref: "#/definitions/secret_source_aws_secrets_manager" secret_source_path: type: object @@ -159,6 +160,28 @@ definitions: description: Field name where to load secret file from default: GenericField1 + secret_source_aws_secrets_manager: + type: object + additionalProperties: false + required: + - awsSecretsManager + properties: + awsSecretsManager: + type: object + additionalProperties: false + required: + - secretName + properties: + secretName: + type: string + description: Name or ARN of the secret. In case a name is given, the region must be specified as well + region: + type: string + description: The aws region + profile: + type: string + description: AWS credentials profile to use. The AWS_PROFILE environemnt variables will take predence in case it is also set + additionalProperties: false required: - targets diff --git a/kluctl/seal/seal_command.py b/kluctl/seal/seal_command.py index 01310debf..5a4b0a106 100644 --- a/kluctl/seal/seal_command.py +++ b/kluctl/seal/seal_command.py @@ -73,5 +73,7 @@ def seal_command(obj, kwargs): print_template_error(e) except CommandError as e: print(e, file=sys.stderr) + except InvalidKluctlProjectConfig as e: + print(e.message, file=sys.stderr) except Exception as e: logger.exception("Sealing for target %s failed. Error=%s" % (target["name"], str(e))) diff --git a/kluctl/utils/aws_utils.py b/kluctl/utils/aws_utils.py new file mode 100644 index 000000000..0e3fd40b3 --- /dev/null +++ b/kluctl/utils/aws_utils.py @@ -0,0 +1,19 @@ +def parse_arn(arn): + # http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html + elements = arn.split(':', 5) + if len(elements) < 6: + raise ValueError("%s is not a valid arn" % arn) + result = { + 'arn': elements[0], + 'partition': elements[1], + 'service': elements[2], + 'region': elements[3], + 'account': elements[4], + 'resource': elements[5], + 'resource_type': None + } + if '/' in result['resource']: + result['resource_type'], result['resource'] = result['resource'].split('/',1) + elif ':' in result['resource']: + result['resource_type'], result['resource'] = result['resource'].split(':',1) + return result diff --git a/setup.py b/setup.py index 6462af950..a53976bd6 100644 --- a/setup.py +++ b/setup.py @@ -73,6 +73,7 @@ "python-gitlab>=2.10.1", "jsonpath-ng>=1.5.3", "jsonpatch>=1.32", + "boto3>=1.20.24", ], entry_points={ "console_scripts": [ From 3f869241cd96ef697763857f77631ad3c2e258ae Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 16 Dec 2021 17:35:45 +0100 Subject: [PATCH 0313/2916] fix: Silence git clone logging --- kluctl/utils/git_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kluctl/utils/git_utils.py b/kluctl/utils/git_utils.py index 820a6c695..8d75df300 100644 --- a/kluctl/utils/git_utils.py +++ b/kluctl/utils/git_utils.py @@ -249,7 +249,7 @@ def clone_project(self, ref, target_dir): assert self.has_lock assert self.has_updated - logger.info(f"Cloning git project: url='{self.url}', ref='{ref}'") + logger.debug(f"Cloning git project: url='{self.url}', ref='{ref}'") with self._build_git_object(): args = ["file://%s" % self.mirror_dir, "--single-branch", target_dir] From a64115edc884b16042bb7603b390b8f586459d2a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 16 Dec 2021 17:37:19 +0100 Subject: [PATCH 0314/2916] fix: Introduce cache for cluster_vars in KluctlProject.load_targets This avoids unnecessary and slow instantiation of kubernetes clients. --- kluctl/kluctl_project/kluctl_project.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/kluctl/kluctl_project/kluctl_project.py b/kluctl/kluctl_project/kluctl_project.py index 1394d15f4..c3f49308d 100644 --- a/kluctl/kluctl_project/kluctl_project.py +++ b/kluctl/kluctl_project/kluctl_project.py @@ -295,6 +295,7 @@ def load_targets(self): self.clone_dynamic_targets(target_infos) + cluster_vars_cache = {} for target_info in target_infos: try: target = self.build_dynamic_target(target_info.base_target, target_info.dir) @@ -307,7 +308,7 @@ def load_targets(self): logger.warning("Failed to load dynamic target config for project. Error=%s" % (str(e))) continue - target = self.render_target(target) + target = self.render_target(target, cluster_vars_cache) target["baseTarget"] = target_info.base_target if target["name"] in target_names: @@ -316,7 +317,7 @@ def load_targets(self): target_names.add(target["name"]) self.targets.append(target) - def render_target(self, target): + def render_target(self, target, cluster_vars_cache): errors = [] # Try rendering the target multiple times, until all values can be rendered successfully. This allows the target # to reference itself in complex ways. We'll also try loading the cluster vars in each iteration. @@ -327,9 +328,15 @@ def render_target(self, target): try: # Try to load cluster vars. This might fail in case jinja templating is used in the cluster name # of the target. We assume that this will then succeed in a later iteration - cluster_vars, _ = load_cluster_config(self.clusters_dir, target["cluster"]) + cluster_vars = cluster_vars_cache.get(target["cluster"]) + if isinstance(cluster_vars, Exception): + raise cluster_vars + if cluster_vars is None: + cluster_vars, _ = load_cluster_config(self.clusters_dir, target["cluster"]) + cluster_vars_cache[target["cluster"]] = cluster_vars jinja2_vars["cluster"] = cluster_vars - except: + except Exception as e: + cluster_vars_cache[target["cluster"]] = e pass target2, errors = render_dict_strs(target, jinja2_vars, do_raise=False) if not errors and target == target2: From b8306c5ba906b72c155de4b3c7ca0acf83bea5be Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 17 Dec 2021 11:40:20 +0100 Subject: [PATCH 0315/2916] ci: Upgrade python to 3.10.1 --- .github/workflows/build-and-release.yml | 4 ++-- .github/workflows/tests.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index 875406881..e10a0c663 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -103,7 +103,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 with: - python-version: 3.10.0 + python-version: 3.10.1 - name: Write Version to _version.py run: | echo "__version__ = '${{ needs.version.outputs.version }}'" > kluctl/_version.py @@ -143,7 +143,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 with: - python-version: 3.10.0 + python-version: 3.10.1 - name: Write Version to _version.py run: | echo "__version__ = '${{ needs.version.outputs.version }}'" > kluctl/_version.py diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f31160ad6..264e7eeec 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -79,7 +79,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 with: - python-version: 3.10.0 + python-version: 3.10.1 - uses: actions/cache@v2 with: path: .venv @@ -215,7 +215,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 with: - python-version: 3.10.0 + python-version: 3.10.1 - name: pip install run: | pip install . From ba1db5449ac7b2a74d0f1c109df368f1691d01ae Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 17 Dec 2021 11:42:49 +0100 Subject: [PATCH 0316/2916] Revert "ci: Upgrade python to 3.10.1" This reverts commit b8306c5ba906b72c155de4b3c7ca0acf83bea5be. --- .github/workflows/build-and-release.yml | 4 ++-- .github/workflows/tests.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index e10a0c663..875406881 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -103,7 +103,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 with: - python-version: 3.10.1 + python-version: 3.10.0 - name: Write Version to _version.py run: | echo "__version__ = '${{ needs.version.outputs.version }}'" > kluctl/_version.py @@ -143,7 +143,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 with: - python-version: 3.10.1 + python-version: 3.10.0 - name: Write Version to _version.py run: | echo "__version__ = '${{ needs.version.outputs.version }}'" > kluctl/_version.py diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 264e7eeec..f31160ad6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -79,7 +79,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 with: - python-version: 3.10.1 + python-version: 3.10.0 - uses: actions/cache@v2 with: path: .venv @@ -215,7 +215,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 with: - python-version: 3.10.1 + python-version: 3.10.0 - name: pip install run: | pip install . From e88e355c0585b4aba8c50f13859507b92124c330 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 17 Dec 2021 12:53:01 +0100 Subject: [PATCH 0317/2916] build: Remove printing of data entries --- kluctl.spec | 1 - setup.py | 1 - 2 files changed, 2 deletions(-) diff --git a/kluctl.spec b/kluctl.spec index 6ac97d9ee..ca358af60 100644 --- a/kluctl.spec +++ b/kluctl.spec @@ -11,7 +11,6 @@ for dirpath, dirnames, filenames in os.walk("./kluctl"): continue p = os.path.join(dirpath, f) datas.append((p, dirpath)) -print("datas=%s" % datas) a = Analysis(['cli.py'], binaries=[], diff --git a/setup.py b/setup.py index a53976bd6..5e27e6cdc 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,6 @@ continue p = os.path.join("..", dirpath, f) datas.append(p) -print("datas=%s" % datas) # This call to setup() does all the work setup( From 42da7ca98a90b7ef573203a9e5c94d2e73c6b5bd Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 17 Dec 2021 12:53:15 +0100 Subject: [PATCH 0318/2916] fix: Pin python-rsa to 4.6 --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 5e27e6cdc..ae978a1c3 100644 --- a/setup.py +++ b/setup.py @@ -73,6 +73,7 @@ "jsonpath-ng>=1.5.3", "jsonpatch>=1.32", "boto3>=1.20.24", + "rsa==4.6", # >=4.7 is broken when used with pyinstaller ], entry_points={ "console_scripts": [ From efe654f6de40fc4186acd51a849732834ffbe45e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 21 Dec 2021 15:59:03 +0100 Subject: [PATCH 0319/2916] feat: Implement kluctl.io/ignore-diff annotations --- docs/annotations.md | 9 +++++++++ kluctl/deployment/deployment_collection.py | 4 ++-- kluctl/diff/normalize.py | 21 +++++++++++++++++++-- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/docs/annotations.md b/docs/annotations.md index 857ddd959..e48b96be5 100644 --- a/docs/annotations.md +++ b/docs/annotations.md @@ -55,6 +55,15 @@ fields will be overwritten in case of field manager conflicts. If more than one field needs to be specified, add `-xxx` to the annotation key, where `xxx` is an arbitrary number. +### kluctl.io/ignore-diff +If set to "true", the whole resource will be ignored while calculating diffs. + +### kluctl.io/ignore-diff-field +Specifies a [JSON Path](https://goessner.net/articles/JsonPath/) for fields that should be ignored while calculating +diffs. + +If more than one field needs to be specified, add `-xxx` to the annotation key, where `xxx` is an arbitrary number. + ## Hooks related See [hooks](./hooks.md) for more details. diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index f830de32a..1d4adbab0 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -305,9 +305,9 @@ def do_diff(self, k8s_cluster, applied_objects, ignore_tags, ignore_labels, igno if ref not in applied_objects: continue diff_objects[ref] = applied_objects.get(ref) - normalized_diff_objects[ref] = normalize_object(k8s_cluster, diff_objects[ref], ignore_for_diffs) + normalized_diff_objects[ref] = normalize_object(k8s_cluster, diff_objects[ref], ignore_for_diffs, diff_objects[ref]) if ref in self.remote_objects: - normalized_remote_objects[ref] = normalize_object(k8s_cluster, self.remote_objects[ref], ignore_for_diffs) + normalized_remote_objects[ref] = normalize_object(k8s_cluster, self.remote_objects[ref], ignore_for_diffs, diff_objects[ref]) logger.info("Diffing remote/old objects against applied/new objects") new_objects = [] diff --git a/kluctl/diff/normalize.py b/kluctl/diff/normalize.py index 33d09b86f..cff189b2b 100644 --- a/kluctl/diff/normalize.py +++ b/kluctl/diff/normalize.py @@ -1,7 +1,11 @@ +import re + from kluctl.utils.dict_utils import copy_dict, get_dict_value, del_dict_value, \ - set_dict_default_value + set_dict_default_value, list_matching_dict_pathes from kluctl.utils.k8s_object_utils import split_api_version +from kluctl.utils.utils import parse_bool +IGNORE_DIFF_FIELD_ANNOTATION_REGEX = re.compile(r"^kluctl.io/ignore-diff-field(-\d*)?$") def normalize_env(container): env = container.get("env") @@ -78,7 +82,7 @@ def normalize_misc(o): del_dict_value(o, 'status') # Performs some deterministic sorting and other normalizations to avoid ugly diffs due to order changes -def normalize_object(k8s_cluster, o, ignore_for_diffs): +def normalize_object(k8s_cluster, o, ignore_for_diffs, local_object): group, version = split_api_version(o["apiVersion"]) kind = o['kind'] ns = get_dict_value(o, "metadata.namespace") @@ -118,5 +122,18 @@ def check_match(v, m): for p in field_path: del_dict_value(o, p) + ignore_all = parse_bool(get_dict_value(local_object, 'metadata.annotations["kluctl.io/ignore-diff"]', "false")) + if ignore_all: + # Return empty object so that diffs will always be empty + return {} + + del_fields = set() + for k, v in get_dict_value(local_object, "metadata.annotations", {}).items(): + if IGNORE_DIFF_FIELD_ANNOTATION_REGEX.fullmatch(k): + for x in list_matching_dict_pathes(o, v): + del_fields.add(x) + for p in del_fields: + del_dict_value(o, p) + return o From c0d1ea186d6971beff50e50a85b0f2fe9e5d3653 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 21 Dec 2021 15:59:22 +0100 Subject: [PATCH 0320/2916] fix: Better error handling when secrets.yml is invalid --- kluctl/kluctl_project/secrets.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/kluctl/kluctl_project/secrets.py b/kluctl/kluctl_project/secrets.py index 77ddbf632..cd42c62b8 100644 --- a/kluctl/kluctl_project/secrets.py +++ b/kluctl/kluctl_project/secrets.py @@ -1,6 +1,9 @@ import logging import os +from yaml import YAMLError +from yaml.parser import ParserError + from kluctl.kluctl_project.passwordstate import Passwordstate from kluctl.utils.dict_utils import get_dict_value from kluctl.utils.exceptions import InvalidKluctlProjectConfig @@ -51,7 +54,11 @@ def load_secrets_passwordstate(self, secrets_source): field = ps.get("passwordField", "GenericField1") l = self.passwordstate.get_password_list(host, path) doc = self.passwordstate.get_password(host, l["PasswordListID"], title, field) - y = yaml_load(doc) + try: + y = yaml_load(doc) + except YAMLError as e: + raise InvalidKluctlProjectConfig("Failed to parse yaml from passwordstate: %s" % str(e)) + return y.get("secrets", {}) def load_secrets_aws_secrets_manager(self, secrets_source): @@ -63,5 +70,8 @@ def load_secrets_aws_secrets_manager(self, secrets_source): from kluctl.kluctl_project.aws_secrets_manager import get_aws_secrets_manager_secret secret = get_aws_secrets_manager_secret(profile, region_name, secret_name) - y = yaml_load(secret) + try: + y = yaml_load(secret) + except YAMLError as e: + raise InvalidKluctlProjectConfig("Failed to parse yaml from AWS Secrets Manager (secretName=%s): %s" % (secret_name, str(e))) return y.get("secrets", {}) From ca830230cf9089922dd37ef2dcc375f4b92a8cb9 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 5 Jan 2022 14:21:47 +0100 Subject: [PATCH 0321/2916] fix: Wait for hook in same order as they are executed --- kluctl/deployment/hooks_util.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/kluctl/deployment/hooks_util.py b/kluctl/deployment/hooks_util.py index 637670d3b..5f337e55b 100644 --- a/kluctl/deployment/hooks_util.py +++ b/kluctl/deployment/hooks_util.py @@ -44,17 +44,14 @@ def do_log(level, str): do_log(logging.DEBUG, "Deleting hook %s due to hook-delete-policy %s" % (get_long_object_name(h.object), ",".join(h.delete_policies))) self.apply_util.delete_object(get_object_ref(h.object)) + wait_results = {} if len(apply_objects) != 0: - do_log(logging.INFO, "Applying %d hooks" % len(delete_before_objects)) + do_log(logging.INFO, "Applying %d hooks" % len(apply_objects)) for h in apply_objects: replaced = "before-hook-creation" in h.delete_policies do_log(logging.DEBUG, "Applying hook %s" % get_long_object_name(h.object)) self.apply_util.apply_object(h.object, replaced, True) - wait_results = {} - for h in l: - if self.apply_util.abort_signal: - return ref = get_object_ref(h.object) if self.apply_util.had_error(ref): continue @@ -63,7 +60,7 @@ def do_log(level, str): wait_results[ref] = self.apply_util.wait_hook(ref) delete_after_objects = [] - for h in reversed(l): + for h in reversed(apply_objects): ref = get_object_ref(h.object) if ref not in wait_results: continue From de920080cfd5a637873931825aab456910120393 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 5 Jan 2022 14:22:15 +0100 Subject: [PATCH 0322/2916] fix: Improve a few error messages --- kluctl/deployment/kustomize_deployment.py | 5 ++++- kluctl/utils/k8s_cluster_real.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/kluctl/deployment/kustomize_deployment.py b/kluctl/deployment/kustomize_deployment.py index b29cbcf46..e3e7e3283 100644 --- a/kluctl/deployment/kustomize_deployment.py +++ b/kluctl/deployment/kustomize_deployment.py @@ -6,7 +6,7 @@ from kluctl.deployment.helm_chart import HelmChart from kluctl.seal.deployment_sealer import SEALME_EXT from kluctl.utils.dict_utils import merge_dict -from kluctl.utils.exceptions import CommandError +from kluctl.utils.exceptions import CommandError, InvalidKluctlProjectConfig from kluctl.utils.external_tools import get_external_tool_hash from kluctl.utils.k8s_object_utils import get_object_ref, should_remove_namespace from kluctl.utils.kustomize import kustomize_build @@ -34,6 +34,9 @@ def __init__(self, deployment_project, deployment_collection, config, dir, index self.index = index self.objects = [] + if dir and not os.path.isdir(dir): + raise InvalidKluctlProjectConfig("kustomizeDir does not exist: %s" % dir) + def get_rel_kustomize_dir(self): root_project = self.deployment_project.get_root_deployment() return os.path.relpath(self.dir, root_project.dir) diff --git a/kluctl/utils/k8s_cluster_real.py b/kluctl/utils/k8s_cluster_real.py index 3b0e5e32e..eeecb60e1 100644 --- a/kluctl/utils/k8s_cluster_real.py +++ b/kluctl/utils/k8s_cluster_real.py @@ -56,7 +56,7 @@ def __init__(self, context, dry_run): raise CommandError(str(e)) if self.context != "docker-desktop" and not self.config.api_key and not self.config.key_file: - raise CommandError("No authentication available. You might need to invoke kubectl first to perform a login") + raise CommandError("No authentication available for kubernetes context %s. You might need to invoke kubectl first to perform a login" % self.context) self.api_client = ApiClient(configuration=self.config) self.dynamic_client = DynamicClient(self.api_client, discoverer=MyDiscoverer) From ead8e2dce601df008e4274457557fe6b93b2befb Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 5 Jan 2022 14:22:35 +0100 Subject: [PATCH 0323/2916] fix: Fix loading of yaml files with = as list items --- kluctl/utils/yaml_utils.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/kluctl/utils/yaml_utils.py b/kluctl/utils/yaml_utils.py index 93547de5d..f7091608c 100644 --- a/kluctl/utils/yaml_utils.py +++ b/kluctl/utils/yaml_utils.py @@ -8,6 +8,21 @@ print("Failed to load fast LibYAML bindings. You should install them to speed up kluctl.", file=sys.stderr) from yaml import SafeLoader as SafeLoader, SafeDumper as SafeDumper + +def construct_value(load, node): + if not isinstance(node, yaml.ScalarNode): + raise yaml.constructor.ConstructorError( + "while constructing a value", + node.start_mark, + "expected a scalar, but found %s" % node.id, node.start_mark + ) + yield str(node.value) + + +# See https://github.com/yaml/pyyaml/issues/89 +SafeLoader.add_constructor(u'tag:yaml.org,2002:value', construct_value) + + def multiline_str_representer(dumper, data): if len(data.splitlines()) > 1: # check for multiline string return dumper.represent_scalar('tag:yaml.org,2002:str', data, style='|') From 4388968997ccc00c0e5440df64b15d2572d6e418 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 5 Jan 2022 14:22:52 +0100 Subject: [PATCH 0324/2916] fix: Fix crash in delete_objects --- kluctl/utils/k8s_delete_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kluctl/utils/k8s_delete_utils.py b/kluctl/utils/k8s_delete_utils.py index ad02307f7..b2cead603 100644 --- a/kluctl/utils/k8s_delete_utils.py +++ b/kluctl/utils/k8s_delete_utils.py @@ -97,7 +97,7 @@ def do_delete_object(ref): futures = [] for ref in namespaces: f = executor.submit(do_delete_object, ref) - futures.append(f) + futures.append((ref, f)) for ref in object_refs: if ref in namespaces: From fa8d4131d7eae04728e726a2116bdfb431506412 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 12 Jan 2022 11:54:03 +0100 Subject: [PATCH 0325/2916] fix: Avoid contacting unrelated clusters --- kluctl/cli/utils.py | 6 +++--- kluctl/kluctl_project/kluctl_project.py | 2 +- kluctl/utils/k8s_cluster_base.py | 9 ++++++--- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/kluctl/cli/utils.py b/kluctl/cli/utils.py index 13b04a5a1..08f33abce 100644 --- a/kluctl/cli/utils.py +++ b/kluctl/cli/utils.py @@ -13,7 +13,7 @@ from kluctl.deployment.images import Images from kluctl.utils.external_args import parse_args from kluctl.utils.inclusion import Inclusion -from kluctl.utils.k8s_cluster_base import load_cluster_config, k8s_cluster_base +from kluctl.utils.k8s_cluster_base import load_cluster_config, k8s_cluster_base, load_cluster from kluctl.utils.utils import get_tmp_base_dir from kluctl.utils.yaml_utils import yaml_load_file @@ -110,8 +110,8 @@ def project_target_command_context(kwargs, kluctl_project, target, for_seal=Fals raise CommandError("You must specify an existing --cluster when not providing a --target") cluster_name = target["cluster"] - cluster_vars, k8s_cluster = load_cluster_config(kluctl_project.clusters_dir, cluster_name, - dry_run=kwargs.get("dry_run", True)) + cluster_vars = load_cluster_config(kluctl_project.clusters_dir, cluster_name) + k8s_cluster = load_cluster(cluster_vars, dry_run=kwargs.get("dry_run", True)) jinja_vars = build_jinja_vars(cluster_vars) images = build_deploy_images(kwargs) diff --git a/kluctl/kluctl_project/kluctl_project.py b/kluctl/kluctl_project/kluctl_project.py index c3f49308d..287733aca 100644 --- a/kluctl/kluctl_project/kluctl_project.py +++ b/kluctl/kluctl_project/kluctl_project.py @@ -332,7 +332,7 @@ def render_target(self, target, cluster_vars_cache): if isinstance(cluster_vars, Exception): raise cluster_vars if cluster_vars is None: - cluster_vars, _ = load_cluster_config(self.clusters_dir, target["cluster"]) + cluster_vars = load_cluster_config(self.clusters_dir, target["cluster"]) cluster_vars_cache[target["cluster"]] = cluster_vars jinja2_vars["cluster"] = cluster_vars except Exception as e: diff --git a/kluctl/utils/k8s_cluster_base.py b/kluctl/utils/k8s_cluster_base.py index d772ef774..a6f4e2904 100644 --- a/kluctl/utils/k8s_cluster_base.py +++ b/kluctl/utils/k8s_cluster_base.py @@ -44,7 +44,7 @@ def get_objects_metadata(self, group=None, version=None, kind=None, name=None, n raise ApiException("group/kind must be supplied") return self.get_objects(group=group, version=version, kind=kind, name=name, namespace=namespace, labels=labels, as_table=True) -def load_cluster_config(cluster_dir, cluster_name, dry_run=True): +def load_cluster_config(cluster_dir, cluster_name): if cluster_name is None: raise CommandError("Cluster name must be specified!") @@ -58,6 +58,9 @@ def load_cluster_config(cluster_dir, cluster_name, dry_run=True): if cluster['name'] != cluster_name: raise CommandError('Cluster name in %s does not match requested cluster name %s' % (cluster['name'], cluster_name)) + return cluster + +def load_cluster(cluster_config, dry_run=True): from kluctl.utils.k8s_cluster_real import k8s_cluster_real - k8s_cluster = k8s_cluster_real(cluster['context'], dry_run) - return cluster, k8s_cluster + k8s_cluster = k8s_cluster_real(cluster_config['context'], dry_run) + return k8s_cluster From 63869d4bc85c093d54c94e23d53ed32b559726fe Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 12 Jan 2022 11:54:45 +0100 Subject: [PATCH 0326/2916] fix: Make --output-archive a required parameter in archive command --- kluctl/cli/command_stubs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/kluctl/cli/command_stubs.py b/kluctl/cli/command_stubs.py index a273c6f7c..a862b193f 100644 --- a/kluctl/cli/command_stubs.py +++ b/kluctl/cli/command_stubs.py @@ -167,6 +167,7 @@ def list_targets_stub(obj, **kwargs): @optgroup.group("Misc arguments") @optgroup.option("--output-archive", help="Path to .tgz to write project to.", + required=True, type=click.Path(file_okay=True)) @optgroup.option("--output-metadata", help="Path to .yml to write metadata to. If not specified, metadata is written into the archive.", From 09098b0ede122d72702432409601803a1b8bed56 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 12 Jan 2022 11:55:02 +0100 Subject: [PATCH 0327/2916] fix: Don't fail deleting objects for non-existing CRDs --- kluctl/deployment/apply_util.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/kluctl/deployment/apply_util.py b/kluctl/deployment/apply_util.py index 30ed535d9..fbe46c9e8 100644 --- a/kluctl/deployment/apply_util.py +++ b/kluctl/deployment/apply_util.py @@ -53,7 +53,10 @@ def had_error(self, ref): return ref in self.error_refs def delete_object(self, ref): - self.k8s_cluster.delete_single_object(ref, force_dry_run=self.dry_run, ignore_not_found=True) + try: + self.k8s_cluster.delete_single_object(ref, force_dry_run=self.dry_run, ignore_not_found=True) + except ResourceNotFoundError: + pass def apply_object(self, x, replaced, hook): logger.debug(f" {get_long_object_name(x)}") From a7069bf22bfe78c5529fe403c3b3c877f026d3c3 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 12 Jan 2022 12:16:14 +0100 Subject: [PATCH 0328/2916] fix: Run replace-commands-help.py --- docs/commands.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/commands.md b/docs/commands.md index b63cbc28e..41256be07 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -564,7 +564,7 @@ The following arguments are available: ``` Misc arguments: - --output-archive PATH Path to .tgz to write project to. + --output-archive PATH Path to .tgz to write project to. [required] --output-metadata PATH Path to .yml to write metadata to. If not specified, metadata is written into the archive. --reproducible Make archive reproducible. From 1178c4f851046449785343ef5e73fe36edcb50a7 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 17 Jan 2022 10:49:46 +0100 Subject: [PATCH 0329/2916] fix: Better error output in build_kustomize_objects --- kluctl/deployment/deployment_collection.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py index 1d4adbab0..de753abd4 100644 --- a/kluctl/deployment/deployment_collection.py +++ b/kluctl/deployment/deployment_collection.py @@ -107,15 +107,23 @@ def build_kustomize_objects(self, k8s_cluster): with MyThreadPoolExecutor() as executor: jobs = [] for d in self.deployments: - jobs.append(executor.submit(d.build_kustomize)) - for job in jobs: - job.result() + jobs.append((d, executor.submit(d.build_kustomize))) + for d, job in jobs: + try: + job.result() + except Exception as e: + logger.error("Building kustomize objects for %s failed. %s" % (d.dir, str(e))) + raise jobs.clear() for d in self.deployments: - jobs.append(executor.submit(d.postprocess_and_load_objects, k8s_cluster)) - for job in jobs: - job.result() + jobs.append((d, executor.submit(d.postprocess_and_load_objects, k8s_cluster))) + for d, job in jobs: + try: + job.result() + except Exception as e: + logger.error("Postprocessing kustomize objects for %s failed. %s" % (d.dir, str(e))) + raise def update_remote_objects(self, k8s_cluster): if k8s_cluster is None: From 49e804061312f81241d636c1f33f155650481c03 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 31 Jan 2022 09:19:53 +0100 Subject: [PATCH 0330/2916] fix: Sleep a little bit between waiting iterations --- kluctl/deployment/apply_util.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kluctl/deployment/apply_util.py b/kluctl/deployment/apply_util.py index fbe46c9e8..4dc54ea6d 100644 --- a/kluctl/deployment/apply_util.py +++ b/kluctl/deployment/apply_util.py @@ -1,6 +1,7 @@ import json import logging import threading +import time from datetime import datetime from kubernetes.client import ApiException @@ -178,6 +179,8 @@ def wait_hook(self, ref): logger.info("Waiting for hook %s to get ready..." % get_long_object_name_from_ref(ref)) did_log = True + time.sleep(0.5) + def apply_kustomize_deployment(self, d): if "path" not in d.config: return From de3b6d7341af342120aac05d051d205fe641d21d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 2 Feb 2022 16:08:13 +0100 Subject: [PATCH 0331/2916] feat!: Reimplement diff, render and everything that is needed for it --- .dockerignore | 6 - .gitignore | 10 +- .test_durations | 1 - Dockerfile | 28 - cli.py | 5 - cmd/kluctl/args/images.go | 73 ++ cmd/kluctl/args/inclusion.go | 47 + cmd/kluctl/args/misc.go | 72 ++ cmd/kluctl/args/project.go | 51 + .../kluctl}/bootstrap/deployment.yml | 0 .../charts/sealed-secrets/.helmignore | 0 .../charts/sealed-secrets/Chart.yaml | 0 .../charts/sealed-secrets/README.md | 0 .../charts/sealed-secrets/ci/ci-values.yaml | 0 .../sealed-secrets/crds/sealedsecret-crd.yaml | 0 .../dashboards/sealed-secrets-controller.json | 0 .../charts/sealed-secrets/templates/NOTES.txt | 0 .../sealed-secrets/templates/_helpers.tpl | 0 .../templates/cluster-role-binding.yaml | 0 .../templates/cluster-role.yaml | 0 .../templates/configmap-dashboards.yaml | 0 .../sealed-secrets/templates/deployment.yaml | 0 .../sealed-secrets/templates/ingress.yaml | 0 .../templates/networkpolicy.yaml | 0 .../templates/psp-clusterrole.yaml | 0 .../templates/psp-clusterrolebinding.yaml | 0 .../charts/sealed-secrets/templates/psp.yaml | 0 .../templates/role-binding.yaml | 0 .../charts/sealed-secrets/templates/role.yaml | 0 .../templates/service-account.yaml | 0 .../sealed-secrets/templates/service.yaml | 0 .../templates/servicemonitor.yaml | 0 .../charts/sealed-secrets/values.yaml | 0 .../bootstrap/sealed-secrets/helm-chart.yml | 0 .../bootstrap/sealed-secrets/helm-values.yml | 0 .../sealed-secrets/kustomization.yml | 0 cmd/kluctl/cmd_diff.go | 55 + cmd/kluctl/cmd_render.go | 48 + cmd/kluctl/command_result.go | 246 ++++ cmd/kluctl/main.go | 20 + cmd/kluctl/root.go | 46 + cmd/kluctl/utils.go | 187 +++ go.mod | 117 ++ go.sum | 1094 +++++++++++++++++ jinja2-server/.gitignore | 8 + jinja2-server/dict_utils.py | 64 + jinja2-server/gen-go.sh | 6 + jinja2-server/gen-python.sh | 9 + .../utils => jinja2-server}/jinja2_cache.py | 7 +- jinja2-server/jinja2_server.proto | 31 + jinja2-server/jinja2_server.py | 109 ++ .../utils => jinja2-server}/jinja2_utils.py | 63 +- .../utils => jinja2-server}/jsonpath_utils.py | 3 +- jinja2-server/main.py | 40 + jinja2-server/requirements-dev.txt | 2 + jinja2-server/requirements.txt | 6 + {kluctl/utils => jinja2-server}/yaml_utils.py | 9 +- kluctl.spec | 41 - kluctl.toml | 6 - kluctl/__init__.py | 5 - kluctl/_version.py | 1 - kluctl/cli/__init__.py | 10 - kluctl/cli/__main__.py | 43 - kluctl/cli/command_result.py | 172 --- kluctl/cli/command_stubs.py | 181 --- kluctl/cli/commands.py | 176 --- kluctl/cli/main_cli_group.py | 283 ----- kluctl/cli/recursive_click_context.py | 11 - kluctl/cli/seal_command_stubs.py | 28 - kluctl/cli/util_command_stubs.py | 51 - kluctl/cli/util_commands.py | 158 --- kluctl/cli/utils.py | 167 --- kluctl/deployment/__init__.py | 0 kluctl/deployment/apply_util.py | 245 ---- kluctl/deployment/deployment_collection.py | 413 ------- kluctl/deployment/deployment_project.py | 243 ---- kluctl/deployment/helm_chart.py | 135 -- kluctl/deployment/hooks_util.py | 161 --- kluctl/deployment/images.py | 168 --- kluctl/deployment/images_test.py | 106 -- kluctl/deployment/kustomize_deployment.py | 284 ----- kluctl/diff/__init__.py | 0 kluctl/diff/k8s_diff.py | 117 -- kluctl/diff/managed_fields.py | 166 --- kluctl/diff/normalize.py | 139 --- kluctl/e2e/__init__.py | 23 - kluctl/e2e/conftest.py | 56 - kluctl/e2e/kluctl_test_project.py | 324 ----- kluctl/e2e/kluctl_test_project_helpers.py | 150 --- kluctl/e2e/test_command_bootstrap.py | 52 - kluctl/e2e/test_command_deploy.py | 23 - kluctl/e2e/test_external_projects.py | 61 - kluctl/e2e/test_hooks.py | 182 --- kluctl/e2e/test_inclusion.py | 187 --- kluctl/image_registries/__init__.py | 29 - kluctl/image_registries/generic_registry.py | 324 ----- kluctl/image_registries/images_registry.py | 6 - kluctl/kluctl_project/__init__.py | 0 kluctl/kluctl_project/aws_secrets_manager.py | 40 - kluctl/kluctl_project/kluctl_project.py | 540 -------- kluctl/kluctl_project/passwordstate.py | 75 -- kluctl/kluctl_project/secrets.py | 77 -- kluctl/schemas/__init__.py | 0 kluctl/schemas/fixed-images.yml | 33 - kluctl/schemas/kluctl-project.yml | 210 ---- kluctl/schemas/schema.py | 45 - kluctl/schemas/target-config.yml | 37 - kluctl/seal/__init__.py | 0 kluctl/seal/deployment_sealer.py | 51 - kluctl/seal/seal_command.py | 79 -- kluctl/seal/sealer.py | 123 -- kluctl/tests/__init__.py | 0 kluctl/tests/includes/__init__.py | 0 .../test_deployment/d1/d1_sub/deployment.yml | 0 .../test_deployment/d1/deployment.yml | 15 - .../test_deployment/d1/k1/kustomization.yml | 0 .../test_deployment/d2/d2_sub/deployment.yml | 0 .../test_deployment/d2/deployment.yml | 5 - .../test_deployment/d2/k1/kustomization.yml | 0 .../includes/test_deployment/deployment.yml | 21 - kluctl/tests/includes/test_includes.py | 55 - kluctl/tests/misc/__init__.py | 0 .../misc/test_deployment/d1/deployment.yml | 0 .../tests/misc/test_deployment/deployment.yml | 11 - kluctl/tests/misc/test_misc.py | 6 - kluctl/tests/templating/__init__.py | 0 .../test_deployment/d1/deployment.yml | 5 - .../test_deployment/d1/k1/kustomization.yml | 5 - .../test_deployment/d1/k1/loaded.txt | 1 - .../test_deployment/d1/k1/not-rendered.yml | 1 - .../test_deployment/d1/k1/rendered.yml | 6 - .../test_deployment/d2/deployment.yml | 2 - .../test_deployment/d2/k1/kustomization.yml | 0 .../templating/test_deployment/deployment.yml | 12 - .../templating/test_import/deployment.yml | 7 - .../templating/test_import/k1/imported.yml | 1 - .../test_import/k1/kustomization.yml | 2 - .../templating/test_import/k1/rendered.yml | 4 - kluctl/tests/templating/test_templating.py | 64 - .../templating/test_utils/deployment.yml | 10 - .../templating/test_utils/k1/get_var.yml | 4 - .../test_utils/k1/kustomization.yml | 2 - kluctl/tests/templating/test_utils/vars.yml | 3 - .../tests/templating/test_vars/deployment.yml | 13 - .../templating/test_vars/k1/kustomization.yml | 2 - kluctl/tests/templating/test_vars/k1/test.yml | 5 - .../tests/templating/test_vars/vars-file.yml | 3 - .../tests/templating/test_vars/vars-file2.yml | 3 - kluctl/tests/test_base.py | 51 - kluctl/utils/__init__.py | 0 kluctl/utils/aws_utils.py | 19 - kluctl/utils/dict_utils.py | 140 --- kluctl/utils/dict_utils_test.py | 74 -- kluctl/utils/env_config_sets.py | 22 - kluctl/utils/exceptions.py | 8 - kluctl/utils/external_args.py | 44 - kluctl/utils/external_tools.py | 25 - kluctl/utils/git_utils.py | 349 ------ kluctl/utils/gitlab/__init__.py | 0 kluctl/utils/gitlab/gitlab_util.py | 72 -- kluctl/utils/inclusion.py | 43 - kluctl/utils/k8s_cluster_base.py | 66 - kluctl/utils/k8s_cluster_mocked.py | 43 - kluctl/utils/k8s_cluster_real.py | 345 ------ kluctl/utils/k8s_delete_utils.py | 120 -- kluctl/utils/k8s_downscale_utils.py | 36 - kluctl/utils/k8s_object_utils.py | 142 --- kluctl/utils/k8s_status_validation.py | 189 --- kluctl/utils/kubeseal.py | 39 - kluctl/utils/kustomize.py | 16 - kluctl/utils/pretty_table.py | 51 - kluctl/utils/run_helper.py | 115 -- kluctl/utils/session_pool.py | 35 - kluctl/utils/templated_dir.py | 62 - kluctl/utils/thread_safe_cache.py | 38 - kluctl/utils/utils.py | 111 -- kluctl/utils/versions.py | 222 ---- kluctl/utils/versions_test.py | 94 -- pkg/deployment/apply_utils.go | 356 ++++++ pkg/deployment/deployment_collection.go | 449 +++++++ pkg/deployment/deployment_item.go | 370 ++++++ pkg/deployment/deployment_project.go | 258 ++++ pkg/deployment/external_args.go | 86 ++ pkg/deployment/helm_chart.go | 291 +++++ pkg/deployment/hooks_util.go | 251 ++++ pkg/deployment/images.go | 207 ++++ pkg/deployment/vars.go | 167 +++ pkg/diff/diff.go | 154 +++ pkg/diff/managed_fields.go | 223 ++++ pkg/diff/normalize.go | 205 +++ pkg/git/auth/auth_provider.go | 30 + pkg/git/auth/git_credentials_file.go | 77 ++ pkg/git/git-url/url.go | 98 ++ pkg/git/mirrored_repo.go | 269 ++++ pkg/git/utils.go | 24 + pkg/jinja2_server/.gitignore | 2 + pkg/jinja2_server/server.go | 375 ++++++ pkg/k8s/all_objects.go | 58 + pkg/k8s/delete_utils.go | 218 ++++ pkg/k8s/k8s_cluster.go | 622 ++++++++++ pkg/kluctl_project/git.go | 223 ++++ pkg/kluctl_project/load.go | 186 +++ pkg/kluctl_project/load_targets.go | 319 +++++ pkg/kluctl_project/project.go | 94 ++ pkg/types/cluster_config.go | 47 + pkg/types/command_result.go | 47 + pkg/types/deployment.go | 132 ++ pkg/types/git_project.go | 53 + pkg/types/helm_chart.go | 33 + pkg/types/kluctl_project.go | 71 ++ pkg/types/metadata.go | 20 + pkg/types/ref.go | 50 + pkg/types/secrets_source.go | 33 + pkg/types/target_config.go | 52 + pkg/types/validator.go | 7 + pkg/utils/debugger.go | 24 + pkg/utils/errorlist.go | 20 + pkg/utils/fs.go | 66 + pkg/utils/inclusion.go | 68 + pkg/utils/jsonpath.go | 124 ++ pkg/utils/object_iterator.go | 106 ++ pkg/utils/ordered_map.go | 53 + pkg/utils/prettytable.go | 114 ++ pkg/utils/tar.go | 57 + pkg/utils/unstructured.go | 165 +++ pkg/utils/utils.go | 34 + pkg/utils/workerpool.go | 84 ++ pkg/utils/yaml.go | 124 ++ pkg/validation/validation.go | 25 + pytest.ini | 5 - requirements-dev.txt | 4 - setup.py | 83 -- 232 files changed, 9571 insertions(+), 9281 deletions(-) delete mode 100644 .dockerignore delete mode 100644 .test_durations delete mode 100644 Dockerfile delete mode 100755 cli.py create mode 100644 cmd/kluctl/args/images.go create mode 100644 cmd/kluctl/args/inclusion.go create mode 100644 cmd/kluctl/args/misc.go create mode 100644 cmd/kluctl/args/project.go rename {kluctl => cmd/kluctl}/bootstrap/deployment.yml (100%) rename {kluctl => cmd/kluctl}/bootstrap/sealed-secrets/charts/sealed-secrets/.helmignore (100%) rename {kluctl => cmd/kluctl}/bootstrap/sealed-secrets/charts/sealed-secrets/Chart.yaml (100%) rename {kluctl => cmd/kluctl}/bootstrap/sealed-secrets/charts/sealed-secrets/README.md (100%) rename {kluctl => cmd/kluctl}/bootstrap/sealed-secrets/charts/sealed-secrets/ci/ci-values.yaml (100%) rename {kluctl => cmd/kluctl}/bootstrap/sealed-secrets/charts/sealed-secrets/crds/sealedsecret-crd.yaml (100%) rename {kluctl => cmd/kluctl}/bootstrap/sealed-secrets/charts/sealed-secrets/dashboards/sealed-secrets-controller.json (100%) rename {kluctl => cmd/kluctl}/bootstrap/sealed-secrets/charts/sealed-secrets/templates/NOTES.txt (100%) rename {kluctl => cmd/kluctl}/bootstrap/sealed-secrets/charts/sealed-secrets/templates/_helpers.tpl (100%) rename {kluctl => cmd/kluctl}/bootstrap/sealed-secrets/charts/sealed-secrets/templates/cluster-role-binding.yaml (100%) rename {kluctl => cmd/kluctl}/bootstrap/sealed-secrets/charts/sealed-secrets/templates/cluster-role.yaml (100%) rename {kluctl => cmd/kluctl}/bootstrap/sealed-secrets/charts/sealed-secrets/templates/configmap-dashboards.yaml (100%) rename {kluctl => cmd/kluctl}/bootstrap/sealed-secrets/charts/sealed-secrets/templates/deployment.yaml (100%) rename {kluctl => cmd/kluctl}/bootstrap/sealed-secrets/charts/sealed-secrets/templates/ingress.yaml (100%) rename {kluctl => cmd/kluctl}/bootstrap/sealed-secrets/charts/sealed-secrets/templates/networkpolicy.yaml (100%) rename {kluctl => cmd/kluctl}/bootstrap/sealed-secrets/charts/sealed-secrets/templates/psp-clusterrole.yaml (100%) rename {kluctl => cmd/kluctl}/bootstrap/sealed-secrets/charts/sealed-secrets/templates/psp-clusterrolebinding.yaml (100%) rename {kluctl => cmd/kluctl}/bootstrap/sealed-secrets/charts/sealed-secrets/templates/psp.yaml (100%) rename {kluctl => cmd/kluctl}/bootstrap/sealed-secrets/charts/sealed-secrets/templates/role-binding.yaml (100%) rename {kluctl => cmd/kluctl}/bootstrap/sealed-secrets/charts/sealed-secrets/templates/role.yaml (100%) rename {kluctl => cmd/kluctl}/bootstrap/sealed-secrets/charts/sealed-secrets/templates/service-account.yaml (100%) rename {kluctl => cmd/kluctl}/bootstrap/sealed-secrets/charts/sealed-secrets/templates/service.yaml (100%) rename {kluctl => cmd/kluctl}/bootstrap/sealed-secrets/charts/sealed-secrets/templates/servicemonitor.yaml (100%) rename {kluctl => cmd/kluctl}/bootstrap/sealed-secrets/charts/sealed-secrets/values.yaml (100%) rename {kluctl => cmd/kluctl}/bootstrap/sealed-secrets/helm-chart.yml (100%) rename {kluctl => cmd/kluctl}/bootstrap/sealed-secrets/helm-values.yml (100%) rename {kluctl => cmd/kluctl}/bootstrap/sealed-secrets/kustomization.yml (100%) create mode 100644 cmd/kluctl/cmd_diff.go create mode 100644 cmd/kluctl/cmd_render.go create mode 100644 cmd/kluctl/command_result.go create mode 100644 cmd/kluctl/main.go create mode 100644 cmd/kluctl/root.go create mode 100644 cmd/kluctl/utils.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 jinja2-server/.gitignore create mode 100644 jinja2-server/dict_utils.py create mode 100755 jinja2-server/gen-go.sh create mode 100755 jinja2-server/gen-python.sh rename {kluctl/utils => jinja2-server}/jinja2_cache.py (97%) create mode 100644 jinja2-server/jinja2_server.proto create mode 100644 jinja2-server/jinja2_server.py rename {kluctl/utils => jinja2-server}/jinja2_utils.py (67%) rename {kluctl/utils => jinja2-server}/jsonpath_utils.py (96%) create mode 100755 jinja2-server/main.py create mode 100644 jinja2-server/requirements-dev.txt create mode 100644 jinja2-server/requirements.txt rename {kluctl/utils => jinja2-server}/yaml_utils.py (83%) delete mode 100644 kluctl.spec delete mode 100644 kluctl.toml delete mode 100644 kluctl/__init__.py delete mode 100644 kluctl/_version.py delete mode 100644 kluctl/cli/__init__.py delete mode 100755 kluctl/cli/__main__.py delete mode 100644 kluctl/cli/command_result.py delete mode 100644 kluctl/cli/command_stubs.py delete mode 100644 kluctl/cli/commands.py delete mode 100644 kluctl/cli/main_cli_group.py delete mode 100644 kluctl/cli/recursive_click_context.py delete mode 100644 kluctl/cli/seal_command_stubs.py delete mode 100644 kluctl/cli/util_command_stubs.py delete mode 100644 kluctl/cli/util_commands.py delete mode 100644 kluctl/cli/utils.py delete mode 100644 kluctl/deployment/__init__.py delete mode 100644 kluctl/deployment/apply_util.py delete mode 100644 kluctl/deployment/deployment_collection.py delete mode 100644 kluctl/deployment/deployment_project.py delete mode 100644 kluctl/deployment/helm_chart.py delete mode 100644 kluctl/deployment/hooks_util.py delete mode 100644 kluctl/deployment/images.py delete mode 100644 kluctl/deployment/images_test.py delete mode 100644 kluctl/deployment/kustomize_deployment.py delete mode 100644 kluctl/diff/__init__.py delete mode 100644 kluctl/diff/k8s_diff.py delete mode 100644 kluctl/diff/managed_fields.py delete mode 100644 kluctl/diff/normalize.py delete mode 100644 kluctl/e2e/__init__.py delete mode 100644 kluctl/e2e/conftest.py delete mode 100644 kluctl/e2e/kluctl_test_project.py delete mode 100644 kluctl/e2e/kluctl_test_project_helpers.py delete mode 100644 kluctl/e2e/test_command_bootstrap.py delete mode 100644 kluctl/e2e/test_command_deploy.py delete mode 100644 kluctl/e2e/test_external_projects.py delete mode 100644 kluctl/e2e/test_hooks.py delete mode 100644 kluctl/e2e/test_inclusion.py delete mode 100644 kluctl/image_registries/__init__.py delete mode 100644 kluctl/image_registries/generic_registry.py delete mode 100644 kluctl/image_registries/images_registry.py delete mode 100644 kluctl/kluctl_project/__init__.py delete mode 100644 kluctl/kluctl_project/aws_secrets_manager.py delete mode 100644 kluctl/kluctl_project/kluctl_project.py delete mode 100644 kluctl/kluctl_project/passwordstate.py delete mode 100644 kluctl/kluctl_project/secrets.py delete mode 100644 kluctl/schemas/__init__.py delete mode 100644 kluctl/schemas/fixed-images.yml delete mode 100644 kluctl/schemas/kluctl-project.yml delete mode 100644 kluctl/schemas/schema.py delete mode 100644 kluctl/schemas/target-config.yml delete mode 100644 kluctl/seal/__init__.py delete mode 100644 kluctl/seal/deployment_sealer.py delete mode 100644 kluctl/seal/seal_command.py delete mode 100644 kluctl/seal/sealer.py delete mode 100644 kluctl/tests/__init__.py delete mode 100644 kluctl/tests/includes/__init__.py delete mode 100644 kluctl/tests/includes/test_deployment/d1/d1_sub/deployment.yml delete mode 100644 kluctl/tests/includes/test_deployment/d1/deployment.yml delete mode 100644 kluctl/tests/includes/test_deployment/d1/k1/kustomization.yml delete mode 100644 kluctl/tests/includes/test_deployment/d2/d2_sub/deployment.yml delete mode 100644 kluctl/tests/includes/test_deployment/d2/deployment.yml delete mode 100644 kluctl/tests/includes/test_deployment/d2/k1/kustomization.yml delete mode 100644 kluctl/tests/includes/test_deployment/deployment.yml delete mode 100644 kluctl/tests/includes/test_includes.py delete mode 100644 kluctl/tests/misc/__init__.py delete mode 100644 kluctl/tests/misc/test_deployment/d1/deployment.yml delete mode 100644 kluctl/tests/misc/test_deployment/deployment.yml delete mode 100644 kluctl/tests/misc/test_misc.py delete mode 100644 kluctl/tests/templating/__init__.py delete mode 100644 kluctl/tests/templating/test_deployment/d1/deployment.yml delete mode 100644 kluctl/tests/templating/test_deployment/d1/k1/kustomization.yml delete mode 100644 kluctl/tests/templating/test_deployment/d1/k1/loaded.txt delete mode 100644 kluctl/tests/templating/test_deployment/d1/k1/not-rendered.yml delete mode 100644 kluctl/tests/templating/test_deployment/d1/k1/rendered.yml delete mode 100644 kluctl/tests/templating/test_deployment/d2/deployment.yml delete mode 100644 kluctl/tests/templating/test_deployment/d2/k1/kustomization.yml delete mode 100644 kluctl/tests/templating/test_deployment/deployment.yml delete mode 100644 kluctl/tests/templating/test_import/deployment.yml delete mode 100644 kluctl/tests/templating/test_import/k1/imported.yml delete mode 100644 kluctl/tests/templating/test_import/k1/kustomization.yml delete mode 100644 kluctl/tests/templating/test_import/k1/rendered.yml delete mode 100644 kluctl/tests/templating/test_templating.py delete mode 100644 kluctl/tests/templating/test_utils/deployment.yml delete mode 100644 kluctl/tests/templating/test_utils/k1/get_var.yml delete mode 100644 kluctl/tests/templating/test_utils/k1/kustomization.yml delete mode 100644 kluctl/tests/templating/test_utils/vars.yml delete mode 100644 kluctl/tests/templating/test_vars/deployment.yml delete mode 100644 kluctl/tests/templating/test_vars/k1/kustomization.yml delete mode 100644 kluctl/tests/templating/test_vars/k1/test.yml delete mode 100644 kluctl/tests/templating/test_vars/vars-file.yml delete mode 100644 kluctl/tests/templating/test_vars/vars-file2.yml delete mode 100644 kluctl/tests/test_base.py delete mode 100644 kluctl/utils/__init__.py delete mode 100644 kluctl/utils/aws_utils.py delete mode 100644 kluctl/utils/dict_utils.py delete mode 100644 kluctl/utils/dict_utils_test.py delete mode 100644 kluctl/utils/env_config_sets.py delete mode 100644 kluctl/utils/exceptions.py delete mode 100644 kluctl/utils/external_args.py delete mode 100644 kluctl/utils/external_tools.py delete mode 100644 kluctl/utils/git_utils.py delete mode 100644 kluctl/utils/gitlab/__init__.py delete mode 100644 kluctl/utils/gitlab/gitlab_util.py delete mode 100644 kluctl/utils/inclusion.py delete mode 100644 kluctl/utils/k8s_cluster_base.py delete mode 100644 kluctl/utils/k8s_cluster_mocked.py delete mode 100644 kluctl/utils/k8s_cluster_real.py delete mode 100644 kluctl/utils/k8s_delete_utils.py delete mode 100644 kluctl/utils/k8s_downscale_utils.py delete mode 100644 kluctl/utils/k8s_object_utils.py delete mode 100644 kluctl/utils/k8s_status_validation.py delete mode 100644 kluctl/utils/kubeseal.py delete mode 100644 kluctl/utils/kustomize.py delete mode 100644 kluctl/utils/pretty_table.py delete mode 100644 kluctl/utils/run_helper.py delete mode 100644 kluctl/utils/session_pool.py delete mode 100644 kluctl/utils/templated_dir.py delete mode 100644 kluctl/utils/thread_safe_cache.py delete mode 100644 kluctl/utils/utils.py delete mode 100644 kluctl/utils/versions.py delete mode 100644 kluctl/utils/versions_test.py create mode 100644 pkg/deployment/apply_utils.go create mode 100644 pkg/deployment/deployment_collection.go create mode 100644 pkg/deployment/deployment_item.go create mode 100644 pkg/deployment/deployment_project.go create mode 100644 pkg/deployment/external_args.go create mode 100644 pkg/deployment/helm_chart.go create mode 100644 pkg/deployment/hooks_util.go create mode 100644 pkg/deployment/images.go create mode 100644 pkg/deployment/vars.go create mode 100644 pkg/diff/diff.go create mode 100644 pkg/diff/managed_fields.go create mode 100644 pkg/diff/normalize.go create mode 100644 pkg/git/auth/auth_provider.go create mode 100644 pkg/git/auth/git_credentials_file.go create mode 100644 pkg/git/git-url/url.go create mode 100644 pkg/git/mirrored_repo.go create mode 100644 pkg/git/utils.go create mode 100644 pkg/jinja2_server/.gitignore create mode 100644 pkg/jinja2_server/server.go create mode 100644 pkg/k8s/all_objects.go create mode 100644 pkg/k8s/delete_utils.go create mode 100644 pkg/k8s/k8s_cluster.go create mode 100644 pkg/kluctl_project/git.go create mode 100644 pkg/kluctl_project/load.go create mode 100644 pkg/kluctl_project/load_targets.go create mode 100644 pkg/kluctl_project/project.go create mode 100644 pkg/types/cluster_config.go create mode 100644 pkg/types/command_result.go create mode 100644 pkg/types/deployment.go create mode 100644 pkg/types/git_project.go create mode 100644 pkg/types/helm_chart.go create mode 100644 pkg/types/kluctl_project.go create mode 100644 pkg/types/metadata.go create mode 100644 pkg/types/ref.go create mode 100644 pkg/types/secrets_source.go create mode 100644 pkg/types/target_config.go create mode 100644 pkg/types/validator.go create mode 100644 pkg/utils/debugger.go create mode 100644 pkg/utils/errorlist.go create mode 100644 pkg/utils/fs.go create mode 100644 pkg/utils/inclusion.go create mode 100644 pkg/utils/jsonpath.go create mode 100644 pkg/utils/object_iterator.go create mode 100644 pkg/utils/ordered_map.go create mode 100644 pkg/utils/prettytable.go create mode 100644 pkg/utils/tar.go create mode 100644 pkg/utils/unstructured.go create mode 100644 pkg/utils/utils.go create mode 100644 pkg/utils/workerpool.go create mode 100644 pkg/utils/yaml.go create mode 100644 pkg/validation/validation.go delete mode 100644 pytest.ini delete mode 100644 requirements-dev.txt delete mode 100644 setup.py diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 85ec1d52f..000000000 --- a/.dockerignore +++ /dev/null @@ -1,6 +0,0 @@ -.idea -.venv -*.pyc - -dist -build diff --git a/.gitignore b/.gitignore index d88970ff2..6e8729afc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,7 @@ .idea -.venv -*.pyc *.kubeconfig .secrets.yml .sealed-secrets -.kube -.pytest-kind - -dist -build -kluctl.egg-info +/vendor +/kluctl \ No newline at end of file diff --git a/.test_durations b/.test_durations deleted file mode 100644 index 1405fb094..000000000 --- a/.test_durations +++ /dev/null @@ -1 +0,0 @@ -{"kluctl/deployment/images_test.py::TestImages::test_container_missing": 0.17141590600000012, "kluctl/deployment/images_test.py::TestImages::test_deployed": 0.0013724499999998585, "kluctl/deployment/images_test.py::TestImages::test_gitlab_latest_number": 0.0006029400000000518, "kluctl/deployment/images_test.py::TestImages::test_gitlab_latest_prefix": 0.00071098600000008, "kluctl/deployment/images_test.py::TestImages::test_gitlab_latest_semver_simple": 0.00045844800000027774, "kluctl/deployment/images_test.py::TestImages::test_gitlab_latest_semver_suffix": 0.0005591350000000439, "kluctl/deployment/images_test.py::TestImages::test_not_deployed": 0.0014066830000001307, "kluctl/e2e/test_command_bootstrap.py::test_command_bootstrap": 7.980425351000001, "kluctl/e2e/test_command_deploy.py::test_command_deploy_simple": 7.499922836, "kluctl/e2e/test_external_projects.py::test_external_kluctl_project": 8.514446664999998, "kluctl/e2e/test_external_projects.py::test_external_clusters_project": 9.044969405, "kluctl/e2e/test_external_projects.py::test_external_deployment_project": 9.043852996000005, "kluctl/e2e/test_external_projects.py::test_external_sealed_secrets_project": 8.825914165999997, "kluctl/e2e/test_external_projects.py::test_all_projects_external": 9.835674475000005, "kluctl/e2e/test_hooks.py::test_hooks_pre_deploy_initial": 33.86717190399999, "kluctl/e2e/test_hooks.py::test_hooks_post_deploy_initial": 7.135027168999997, "kluctl/e2e/test_hooks.py::test_hooks_pre_deploy_upgrade": 11.931310410000009, "kluctl/e2e/test_hooks.py::test_hooks_post_deploy_upgrade": 6.980959783999978, "kluctl/e2e/test_hooks.py::test_hooks_pre_deploy": 13.948702370999996, "kluctl/e2e/test_hooks.py::test_hooks_pre_deploy2": 14.076728710000026, "kluctl/e2e/test_hooks.py::test_hooks_post_deploy": 10.02011994999998, "kluctl/e2e/test_hooks.py::test_hooks_post_deploy2": 10.028683436999984, "kluctl/e2e/test_hooks.py::test_hooks_pre_post_deploy": 15.202726206999984, "kluctl/e2e/test_hooks.py::test_hooks_pre_post_deploy2": 15.088598263000023, "kluctl/e2e/test_inclusion.py::test_inclusion_tags": 13.18928894100003, "kluctl/e2e/test_inclusion.py::test_exclusion_tags": 7.076015705000003, "kluctl/e2e/test_inclusion.py::test_inclusion_include_dirs": 8.950236328000017, "kluctl/e2e/test_inclusion.py::test_inclusion_kustomize_dirs": 8.771479913999997, "kluctl/e2e/test_inclusion.py::test_inclusion_prune": 11.930896682999986, "kluctl/e2e/test_inclusion.py::test_inclusion_delete": 8.656658246999996, "kluctl/tests/includes/test_includes.py::TestIncludes::test_collection_tags": 0.2307598899999448, "kluctl/tests/includes/test_includes.py::TestIncludes::test_collection_tags_sub_include": 0.004422947999955795, "kluctl/tests/includes/test_includes.py::TestIncludes::test_common_label_merge": 0.004345824000040466, "kluctl/tests/includes/test_includes.py::TestIncludes::test_common_label_merge_sub_include": 0.0045816909999985, "kluctl/tests/includes/test_includes.py::TestIncludes::test_delete_by_label_merge": 0.0043263449999244585, "kluctl/tests/includes/test_includes.py::TestIncludes::test_delete_by_label_merge_sub_include": 0.004260996000027717, "kluctl/tests/includes/test_includes.py::TestIncludes::test_deployment_pathes": 0.004694427000003998, "kluctl/tests/includes/test_includes.py::TestIncludes::test_get_kustomize_dir_tags": 0.004454030999966108, "kluctl/tests/includes/test_includes.py::TestIncludes::test_override_namespace": 0.004301176000012674, "kluctl/tests/includes/test_includes.py::TestIncludes::test_override_namespace_sub_include": 0.004606921999993574, "kluctl/tests/includes/test_includes.py::TestIncludes::test_simple": 0.004748638999956256, "kluctl/tests/misc/test_misc.py::TestTemplating::test_stable_tags_ordering": 0.0021009400000480127, "kluctl/tests/templating/test_templating.py::TestTemplating::test_deployment_yml": 0.003359053000053791, "kluctl/tests/templating/test_templating.py::TestTemplating::test_get_var": 0.07002809499999785, "kluctl/tests/templating/test_templating.py::TestTemplating::test_import_no_context": 0.0050945950000595985, "kluctl/tests/templating/test_templating.py::TestTemplating::test_include_var": 0.0029756959999645005, "kluctl/tests/templating/test_templating.py::TestTemplating::test_load_template": 0.008140107000031094, "kluctl/tests/templating/test_templating.py::TestTemplating::test_not_rendered_kustomize_resource": 0.007553739000002224, "kluctl/tests/templating/test_templating.py::TestTemplating::test_rendered_kustomization_yml": 0.007568721999973604, "kluctl/tests/templating/test_templating.py::TestTemplating::test_rendered_kustomize_resource": 0.008014451999940775, "kluctl/tests/templating/test_templating.py::TestTemplating::test_vars": 0.005280256999924404, "kluctl/utils/dict_utils_test.py::test_get_dict_value": 0.11056591200002686, "kluctl/utils/dict_utils_test.py::test_get_dict_value_arrays": 0.0869486170000755, "kluctl/utils/dict_utils_test.py::test_set_dict_value": 0.0005526429999918037, "kluctl/utils/dict_utils_test.py::test_set_dict_value_add": 0.06687758900000063, "kluctl/utils/dict_utils_test.py::test_del_dict_value": 0.022795438999935413, "kluctl/utils/dict_utils_test.py::test_del_dict_value_wildcard": 0.044004519999987224, "kluctl/utils/dict_utils_test.py::test_del_dict_value_wildcard_extended": 0.022446880000018155, "kluctl/utils/versions_test.py::TestLooseVersionComparator::test_equality": 0.0009527990000037789, "kluctl/utils/versions_test.py::TestLooseVersionComparator::test_less": 0.000906323999970482, "kluctl/utils/versions_test.py::TestLooseVersionComparator::test_loose_version_no_nums": 0.0007862360000103763, "kluctl/utils/versions_test.py::TestLooseVersionComparator::test_maven_versions": 0.0006167200000390949, "kluctl/utils/versions_test.py::TestLooseVersionComparator::test_suffixes": 0.0018046489999505866, "kluctl/utils/versions_test.py::TestVersionFilters::test_loose_semver": 0.0006110550000357762, "kluctl/utils/versions_test.py::TestVersionFilters::test_number": 0.0005053250000059961, "kluctl/utils/versions_test.py::TestVersionFilters::test_prefix": 1.5378612869999984} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 2d027f05f..000000000 --- a/Dockerfile +++ /dev/null @@ -1,28 +0,0 @@ -FROM python:3.10.0-slim-buster - -RUN apt update && apt install wget libyaml-dev git -y && rm -rf /var/lib/apt/lists/* - -# Install tools -ENV KUSTOMIZE_VERSION=v4.4.1 -ENV HELM_VERSION=v3.7.0 -ENV KUBESEAL_VERSION=v0.16.0 -RUN wget -O kustomize.tar.gz https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2F${KUSTOMIZE_VERSION}/kustomize_${KUSTOMIZE_VERSION}_linux_amd64.tar.gz && \ - tar xzf kustomize.tar.gz && \ - mv kustomize /usr/bin && \ - rm kustomize.tar.gz -RUN wget -O helm.tar.gz https://get.helm.sh/helm-$HELM_VERSION-linux-amd64.tar.gz && \ - tar xzf helm.tar.gz && \ - mv linux-amd64/helm /usr/bin && \ - rm helm.tar.gz -RUN wget -O kubeseal https://github.com/bitnami-labs/sealed-secrets/releases/download/$KUBESEAL_VERSION/kubeseal-linux-amd64 && \ - chmod +x kubeseal && \ - mv kubeseal /usr/bin - -RUN apt update && apt install gcc -y && \ - pip install "pyyaml==5.4.1" --global-option=--with-libyaml && \ - apt remove gcc -y && apt autoremove -y && rm -rf /var/lib/apt/lists/* - -ADD . /kluctl -RUN pip install --no-cache-dir /kluctl - -ENTRYPOINT /usr/local/bin/kluctl diff --git a/cli.py b/cli.py deleted file mode 100755 index 3001c32c9..000000000 --- a/cli.py +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env python3 -from kluctl.cli.__main__ import main - -if __name__ == '__main__': - main() diff --git a/cmd/kluctl/args/images.go b/cmd/kluctl/args/images.go new file mode 100644 index 000000000..c82c1bc52 --- /dev/null +++ b/cmd/kluctl/args/images.go @@ -0,0 +1,73 @@ +package args + +import ( + "fmt" + "github.com/codablock/kluctl/pkg/types" + "github.com/spf13/cobra" + "strings" +) + +var ( + FixedImages []string + FixedImagesFile string + UpdateImages bool +) + +func AddImageArgs(cmd *cobra.Command) { + cmd.Flags().StringArrayVarP(&FixedImages, "fixed-image", "F", nil, "Pin an image to a given version. Expects '--fixed-image=image<:namespace:deployment:container>=result'") + cmd.Flags().StringVar(&FixedImagesFile, "fixed-images-file", "", "Use .yml file to pin image versions. See output of list-images sub-command or read the documentation for details about the output format") + cmd.Flags().BoolVarP(&UpdateImages, "update-images", "u", false, "This causes kluctl to prefer the latest image found in registries, based on the `latest_image` filters provided to 'images.get_image(...)' calls. Use this flag if you want to update to the latest versions/tags of all images. '-u' takes precedence over `--fixed-image/--fixed-images-file`, meaning that the latest images are used even if an older image is given via fixed images.") + + _ = cmd.MarkFlagFilename("fixed-images-file", "yml", "yaml") +} + +func LoadFixedImagesFromArgs() (*types.FixedImagesConfig, error) { + var ret types.FixedImagesConfig + + if FixedImagesFile != "" { + err := types.LoadFixedImagesConfig(FixedImagesFile, &ret) + if err != nil { + return nil, err + } + } + + for _, fi := range FixedImages { + e, err := buildFixedImageEntryFromArg(fi) + if err != nil { + return nil, err + } + ret.Images = append(ret.Images, *e) + } + + return &ret, nil +} + +func buildFixedImageEntryFromArg(arg string) (*types.FixedImage, error) { + s := strings.Split(arg, "=") + if len(s) != 2 { + return nil, fmt.Errorf("--fixed-image expects 'image<:namespace:deployment:container>=result'") + } + image := s[0] + result := s[1] + + s = strings.Split(image, ":") + e := types.FixedImage{ + Image: s[0], + ResultImage: result, + } + + if len(s) >= 2 { + e.Namespace = &s[2] + } + if len(s) >= 3 { + e.Deployment = &s[3] + } + if len(s) >= 4 { + e.Container = &s[4] + } + if len(s) >= 5 { + return nil, fmt.Errorf("--fixed-image expects 'image<:namespace:deployment:container>=result'") + } + + return &e, nil +} diff --git a/cmd/kluctl/args/inclusion.go b/cmd/kluctl/args/inclusion.go new file mode 100644 index 000000000..0e2f38950 --- /dev/null +++ b/cmd/kluctl/args/inclusion.go @@ -0,0 +1,47 @@ +package args + +import ( + "fmt" + "github.com/codablock/kluctl/pkg/utils" + "github.com/spf13/cobra" + "os" + "path" + "strings" +) + +var ( + IncludeTags []string + ExcludeTags []string + IncludeDeploymentDirs []string + ExcludeDeploymentDirs []string +) + +func AddInclusionArgs(cmd *cobra.Command) { + cmd.Flags().StringArrayVarP(&IncludeTags, "include-tags", "I", nil, "Include deployments with given tag.") + cmd.Flags().StringArrayVarP(&ExcludeTags, "exclude-tags", "E", nil, "Exclude deployments with given tag. Exclusion has precedence over inclusion, meaning that explicitly excluded deployments will always be excluded even if an inclusion rule would match the same deployment.") + cmd.Flags().StringArrayVar(&IncludeDeploymentDirs, "include-deployment-dir", nil, "Include deployment dir. The path must be relative to the root deployment project.") + cmd.Flags().StringArrayVar(&ExcludeDeploymentDirs, "exclude-deployment-dir", nil, "Exclude deployment dir. The path must be relative to the root deployment project. Exclusion has precedence over inclusion, same as in --exclude-tag") +} + +func ParseInclusionFromArgs() (*utils.Inclusion, error) { + inclusion := utils.NewInclusion() + for _, tag := range IncludeTags { + inclusion.AddInclude("tag", tag) + } + for _, tag := range ExcludeTags { + inclusion.AddExclude("tag", tag) + } + for _, dir := range IncludeDeploymentDirs { + if path.IsAbs(dir) { + return nil, fmt.Errorf("--include-deployment-dir path must be relative") + } + inclusion.AddInclude("deploymentItemDir", strings.ReplaceAll(dir, string(os.PathSeparator), "/")) + } + for _, dir := range ExcludeDeploymentDirs { + if path.IsAbs(dir) { + return nil, fmt.Errorf("--exclude-deployment-dir path must be relative") + } + inclusion.AddExclude("deploymentItemDir", strings.ReplaceAll(dir, string(os.PathSeparator), "/")) + } + return inclusion, nil +} diff --git a/cmd/kluctl/args/misc.go b/cmd/kluctl/args/misc.go new file mode 100644 index 000000000..75d4312f3 --- /dev/null +++ b/cmd/kluctl/args/misc.go @@ -0,0 +1,72 @@ +package args + +import ( + "github.com/spf13/cobra" + "time" +) + +var ( + ForceYes bool + DryRun bool + ForceApply bool + ReplaceOnError bool + ForceReplaceOnError bool + HookTimeout time.Duration + IgnoreTags bool + IgnoreLabels bool + IgnoreAnnotations bool + AbortOnError bool + OutputFormat []string + Output []string + RenderOutputDir string +) + +type EnabledMiscArguments struct { + Yes bool + DryRun bool + ForceApply bool + ReplaceOnError bool + HookTimeout bool + IgnoreLabels bool + AbortOnError bool + OutputFormat bool + Output bool + RenderOutputDir bool +} + +func AddMiscArguments(cmd *cobra.Command, enabledArgs EnabledMiscArguments) { + if enabledArgs.Yes { + cmd.Flags().BoolVarP(&ForceYes, "yes", "y", false, "Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'.") + } + if enabledArgs.DryRun { + cmd.Flags().BoolVar(&DryRun, "dry-run", false, "Performs all kubernetes API calls in dry-run mode.") + } + if enabledArgs.ForceApply { + cmd.Flags().BoolVar(&ForceApply, "force-apply", false, "Force conflict resolution when applying. See documentation for details") + } + if enabledArgs.ReplaceOnError { + cmd.Flags().BoolVar(&ReplaceOnError, "replace-on-error", false, "When patching an object fails, try to replace it. See documentation for more details.") + cmd.Flags().BoolVar(&ForceReplaceOnError, "force-replace-on-error", false, "Same as --replace-on-error, but also try to delete and re-create objects. See documentation for more details.") + } + if enabledArgs.HookTimeout { + cmd.Flags().DurationVar(&HookTimeout, "hook-timeout", 5*time.Minute, "Maximum time to wait for hook readiness. The timeout is meant per-hook. Timeouts are in the duration format (1s, 1m, 1h, ...). If not specified, a default timeout of 5m is used.") + } + if enabledArgs.IgnoreLabels { + cmd.Flags().BoolVar(&IgnoreTags, "ignore-tags", false, "Ignores changes in tags when diffing") + cmd.Flags().BoolVar(&IgnoreLabels, "ignore-labels", false, "Ignores changes in labels when diffing") + cmd.Flags().BoolVar(&IgnoreAnnotations, "ignore-annotations", false, "Ignores changes in annotations when diffing") + } + if enabledArgs.AbortOnError { + cmd.Flags().BoolVar(&AbortOnError, "abort-on-error", false, "Abort deploying when an error occurs instead of trying the remaining deployments") + } + if enabledArgs.OutputFormat { + cmd.Flags().StringArrayVarP(&OutputFormat, "output", "o", nil, "Specify output format and target file, in the format 'format=path'. Format can either be 'text' or 'yaml'. Can be specified multiple times. The actual format for yaml is currently not documented and subject to change.") + } + if enabledArgs.Output { + cmd.Flags().StringArrayVarP(&Output, "output", "o", nil, "Specify output target file. Can be specified multiple times") + } + if enabledArgs.RenderOutputDir { + cmd.Flags().StringVar(&RenderOutputDir, "render-output-dir", "", "Specifies the target directory to render the project into. If omitted, a temporary directory is used.") + _ = cmd.MarkFlagDirname("render-output-dir") + } +} diff --git a/cmd/kluctl/args/project.go b/cmd/kluctl/args/project.go new file mode 100644 index 000000000..f7eaa631a --- /dev/null +++ b/cmd/kluctl/args/project.go @@ -0,0 +1,51 @@ +package args + +import ( + "github.com/spf13/cobra" +) + +var ( + ProjectUrl string + ProjectRef string + ProjectConfig string + LocalClusters string + LocalDeployment string + LocalSealedSecrets string + FromArchive string + FromArchiveMetadata string + Cluster string + + Args []string + + Target string +) + +func AddProjectArgs(cmd *cobra.Command, withDeploymentArgs bool, withArgs bool, withTarget bool) { + if withDeploymentArgs { + cmd.Flags().StringVarP(&ProjectUrl, "project-url", "p", "", "Git url of the kluctl project. If not specified, the current directory will be used instead of a remote Git project") + cmd.Flags().StringVarP(&ProjectRef, "project-ref", "b", "", "Git ref of the kluctl project. Only used when --project-url was given.") + + cmd.Flags().StringVarP(&ProjectConfig, "project-config", "c", "", "Location of the .kluctl.yml config file. Defaults to $PROJECT/.kluctl.yml") + cmd.Flags().StringVar(&LocalClusters, "local-clusters", "", "Local clusters directory. Overrides the project from .kluctl.yml") + cmd.Flags().StringVar(&LocalDeployment, "local-deployment", "", "Local deployment directory. Overrides the project from .kluctl.yml") + cmd.Flags().StringVar(&LocalSealedSecrets, "local-sealed-secrets", "", "Local sealed-secrets directory. Overrides the project from .kluctl.yml") + cmd.Flags().StringVar(&FromArchive, "from-archive", "", "Load project (.kluctl.yml, cluster, ...) from archive. Given path can either be an archive file or a directory with the extracted contents.") + cmd.Flags().StringVar(&FromArchiveMetadata, "from-archive-metadata", "", "Specify where to load metadata (targets, ...) from. If not specified, metadata is assumed to be part of the archive.") + cmd.Flags().StringVar(&Cluster, "cluster", "", "Specify/Override cluster") + + _ = cmd.MarkFlagFilename("project-config") + _ = cmd.MarkFlagDirname("local-clusters") + _ = cmd.MarkFlagDirname("local-deployment") + _ = cmd.MarkFlagDirname("local-sealed-secrets") + _ = cmd.MarkFlagFilename("from-archive") + _ = cmd.MarkFlagFilename("from-archive-metadata") + } + + if withArgs { + cmd.Flags().StringArrayVarP(&Args, "arg", "a", nil, "Template argument in the form name=value") + } + + if withTarget { + cmd.Flags().StringVarP(&Target, "target", "t", "", "Target name to run command for. Target must exist in .kluctl.yml.") + } +} diff --git a/kluctl/bootstrap/deployment.yml b/cmd/kluctl/bootstrap/deployment.yml similarity index 100% rename from kluctl/bootstrap/deployment.yml rename to cmd/kluctl/bootstrap/deployment.yml diff --git a/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/.helmignore b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/.helmignore similarity index 100% rename from kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/.helmignore rename to cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/.helmignore diff --git a/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/Chart.yaml b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/Chart.yaml similarity index 100% rename from kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/Chart.yaml rename to cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/Chart.yaml diff --git a/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/README.md b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/README.md similarity index 100% rename from kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/README.md rename to cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/README.md diff --git a/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/ci/ci-values.yaml b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/ci/ci-values.yaml similarity index 100% rename from kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/ci/ci-values.yaml rename to cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/ci/ci-values.yaml diff --git a/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/crds/sealedsecret-crd.yaml b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/crds/sealedsecret-crd.yaml similarity index 100% rename from kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/crds/sealedsecret-crd.yaml rename to cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/crds/sealedsecret-crd.yaml diff --git a/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/dashboards/sealed-secrets-controller.json b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/dashboards/sealed-secrets-controller.json similarity index 100% rename from kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/dashboards/sealed-secrets-controller.json rename to cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/dashboards/sealed-secrets-controller.json diff --git a/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/NOTES.txt b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/NOTES.txt similarity index 100% rename from kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/NOTES.txt rename to cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/NOTES.txt diff --git a/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/_helpers.tpl b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/_helpers.tpl similarity index 100% rename from kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/_helpers.tpl rename to cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/_helpers.tpl diff --git a/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/cluster-role-binding.yaml b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/cluster-role-binding.yaml similarity index 100% rename from kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/cluster-role-binding.yaml rename to cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/cluster-role-binding.yaml diff --git a/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/cluster-role.yaml b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/cluster-role.yaml similarity index 100% rename from kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/cluster-role.yaml rename to cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/cluster-role.yaml diff --git a/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/configmap-dashboards.yaml b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/configmap-dashboards.yaml similarity index 100% rename from kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/configmap-dashboards.yaml rename to cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/configmap-dashboards.yaml diff --git a/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/deployment.yaml b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/deployment.yaml similarity index 100% rename from kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/deployment.yaml rename to cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/deployment.yaml diff --git a/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/ingress.yaml b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/ingress.yaml similarity index 100% rename from kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/ingress.yaml rename to cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/ingress.yaml diff --git a/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/networkpolicy.yaml b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/networkpolicy.yaml similarity index 100% rename from kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/networkpolicy.yaml rename to cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/networkpolicy.yaml diff --git a/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/psp-clusterrole.yaml b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/psp-clusterrole.yaml similarity index 100% rename from kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/psp-clusterrole.yaml rename to cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/psp-clusterrole.yaml diff --git a/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/psp-clusterrolebinding.yaml b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/psp-clusterrolebinding.yaml similarity index 100% rename from kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/psp-clusterrolebinding.yaml rename to cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/psp-clusterrolebinding.yaml diff --git a/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/psp.yaml b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/psp.yaml similarity index 100% rename from kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/psp.yaml rename to cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/psp.yaml diff --git a/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/role-binding.yaml b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/role-binding.yaml similarity index 100% rename from kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/role-binding.yaml rename to cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/role-binding.yaml diff --git a/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/role.yaml b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/role.yaml similarity index 100% rename from kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/role.yaml rename to cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/role.yaml diff --git a/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/service-account.yaml b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/service-account.yaml similarity index 100% rename from kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/service-account.yaml rename to cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/service-account.yaml diff --git a/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/service.yaml b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/service.yaml similarity index 100% rename from kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/service.yaml rename to cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/service.yaml diff --git a/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/servicemonitor.yaml b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/servicemonitor.yaml similarity index 100% rename from kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/servicemonitor.yaml rename to cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/servicemonitor.yaml diff --git a/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/values.yaml b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/values.yaml similarity index 100% rename from kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/values.yaml rename to cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/values.yaml diff --git a/kluctl/bootstrap/sealed-secrets/helm-chart.yml b/cmd/kluctl/bootstrap/sealed-secrets/helm-chart.yml similarity index 100% rename from kluctl/bootstrap/sealed-secrets/helm-chart.yml rename to cmd/kluctl/bootstrap/sealed-secrets/helm-chart.yml diff --git a/kluctl/bootstrap/sealed-secrets/helm-values.yml b/cmd/kluctl/bootstrap/sealed-secrets/helm-values.yml similarity index 100% rename from kluctl/bootstrap/sealed-secrets/helm-values.yml rename to cmd/kluctl/bootstrap/sealed-secrets/helm-values.yml diff --git a/kluctl/bootstrap/sealed-secrets/kustomization.yml b/cmd/kluctl/bootstrap/sealed-secrets/kustomization.yml similarity index 100% rename from kluctl/bootstrap/sealed-secrets/kustomization.yml rename to cmd/kluctl/bootstrap/sealed-secrets/kustomization.yml diff --git a/cmd/kluctl/cmd_diff.go b/cmd/kluctl/cmd_diff.go new file mode 100644 index 000000000..82d0b3ec9 --- /dev/null +++ b/cmd/kluctl/cmd_diff.go @@ -0,0 +1,55 @@ +package main + +import ( + "fmt" + "github.com/codablock/kluctl/cmd/kluctl/args" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +func runCmdDiff(cmd *cobra.Command, args_ []string) error { + return withProjectCommandContext(func(ctx *commandCtx) error { + result, err := ctx.deploymentCollection.Diff(ctx.k, args.ForceApply, args.ReplaceOnError, args.ForceReplaceOnError, args.IgnoreTags, args.IgnoreLabels, args.IgnoreAnnotations) + if err != nil { + return err + } + err = outputCommandResult(args.Output, result) + if err != nil { + return err + } + if len(result.Errors) != 0 { + return fmt.Errorf("command failed") + } + return nil + }) +} + +func init() { + var cmd = &cobra.Command{ + Use: "diff", + Short: "Perform a diff between the locally rendered target and the already deployed target", + Long: `Perform a diff between the locally rendered target and the already deployed target + +The output is by default in human readable form (a table combined with unified diffs). +The output can also be changed to output yaml file. Please note however that the format +is currently not documented and prone to changes. +After the diff is performed, the command will also search for prunable objects and list them.`, + RunE: runCmdDiff, + } + args.AddProjectArgs(cmd, true, true, true) + args.AddImageArgs(cmd) + args.AddInclusionArgs(cmd) + args.AddMiscArguments(cmd, args.EnabledMiscArguments{ + ForceApply: true, + ReplaceOnError: true, + IgnoreLabels: true, + OutputFormat: true, + RenderOutputDir: true, + }) + err := viper.BindPFlags(cmd.Flags()) + if err != nil { + panic(err) + } + + rootCmd.AddCommand(cmd) +} diff --git a/cmd/kluctl/cmd_render.go b/cmd/kluctl/cmd_render.go new file mode 100644 index 000000000..dd01aa7cc --- /dev/null +++ b/cmd/kluctl/cmd_render.go @@ -0,0 +1,48 @@ +package main + +import ( + "github.com/codablock/kluctl/cmd/kluctl/args" + "github.com/codablock/kluctl/pkg/utils" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "io/ioutil" +) + +func runCmdRender(cmd *cobra.Command, args_ []string) error { + if args.RenderOutputDir == "" { + p, err := ioutil.TempDir(utils.GetTmpBaseDir(), "rendered-") + if err != nil { + return err + } + args.RenderOutputDir = p + } + + return withProjectCommandContext(func(ctx *commandCtx) error { + log.Infof("Rendered into %s", ctx.deploymentCollection.RenderDir) + return nil + }) +} + +func init() { + var cmd = &cobra.Command{ + Use: "render", + Short: "Renders all resources and configuration files", + Long: "Renders all resources and configuration files and stores the result in either " + + "a temporary directory or a specified directory.", + RunE: runCmdRender, + } + args.AddProjectArgs(cmd, true, true, true) + args.AddImageArgs(cmd) + args.AddMiscArguments(cmd, args.EnabledMiscArguments{ + RenderOutputDir: true, + }) + + err := viper.BindPFlags(cmd.Flags()) + if err != nil { + panic(err) + } + + rootCmd.AddCommand(cmd) +} + diff --git a/cmd/kluctl/command_result.go b/cmd/kluctl/command_result.go new file mode 100644 index 000000000..1ab2de6dc --- /dev/null +++ b/cmd/kluctl/command_result.go @@ -0,0 +1,246 @@ +package main + +import ( + "bytes" + "fmt" + "github.com/codablock/kluctl/pkg/types" + "github.com/codablock/kluctl/pkg/utils" + "gopkg.in/yaml.v2" + "io" + "os" + "strings" +) + +func formatCommandResultText(cr *types.CommandResult) string { + buf := bytes.NewBuffer(nil) + + if len(cr.Warnings) != 0 { + buf.WriteString("\nWarnings:\n") + prettyErrors(buf, cr.Warnings) + } + + if len(cr.NewObjects) != 0 { + buf.WriteString("\nNew objects:\n") + var refs []types.ObjectRef + for _, o := range cr.NewObjects { + refs = append(refs, types.RefFromObject(o)) + } + prettyObjectRefs(buf, refs) + } + if len(cr.ChangedObjects) != 0 { + buf.WriteString("\nChanged objects:\n") + var refs []types.ObjectRef + for _, co := range cr.ChangedObjects { + refs = append(refs, types.RefFromObject(co.NewObject)) + } + prettyObjectRefs(buf, refs) + + buf.WriteString("\n") + for _, co := range cr.ChangedObjects { + prettyChanges(buf, types.RefFromObject(co.NewObject), co.Changes) + } + } + + if len(cr.DeletedObjects) != 0 { + buf.WriteString("\nDeleted objects:\n") + prettyObjectRefs(buf, cr.DeletedObjects) + } + + if len(cr.HookObjects) != 0 { + buf.WriteString("\nApplied hooks:\n") + var refs []types.ObjectRef + for _, o := range cr.HookObjects { + refs = append(refs, types.RefFromObject(o)) + } + prettyObjectRefs(buf, refs) + } + if len(cr.OrphanObjects) != 0 { + buf.WriteString("\nOrphan objects:\n") + prettyObjectRefs(buf, cr.OrphanObjects) + } + + if len(cr.Errors) != 0 { + buf.WriteString("\nErrors:\n") + prettyErrors(buf, cr.Warnings) + } + + return buf.String() +} + +func prettyObjectRefs(buf io.StringWriter, refs []types.ObjectRef) { + for _, ref := range refs { + _, _ = buf.WriteString(fmt.Sprintf(" %s\n", ref.String())) + } +} + +func prettyErrors(buf io.StringWriter, errors []types.DeploymentError) { + for _, e := range errors { + _, _ = buf.WriteString(fmt.Sprintf(" %s: %s\n", e.Ref.String(), e.Error)) + } +} + +func prettyChanges(buf io.StringWriter, ref types.ObjectRef, changes []types.Change) { + _, _ = buf.WriteString(fmt.Sprintf("Diff for object %s\n", ref.String())) + + var t utils.PrettyTable + t.AddRow("Path", "Diff") + + for _, c := range changes { + t.AddRow(c.JsonPath, c.UnifiedDiff) + } + s := t.Render([]int{60}) + _, _ = buf.WriteString(s) +} + +func formatCommandResultYaml(cr *types.CommandResult) (string, error) { + b, err := yaml.Marshal(cr) + if err != nil { + return "", err + } + return string(b), nil +} + +func formatCommandResult(cr *types.CommandResult, format string) (string, error) { + switch format { + case "text": + return formatCommandResultText(cr), nil + case "yal": + return formatCommandResultYaml(cr) + default: + return "", fmt.Errorf("invalid format: %s", format) + } +} + +func prettyValidationResults(buf io.StringWriter, results []types.ValidateResultEntry) { + var t utils.PrettyTable + t.AddRow("Object", "Message") + + for _, e := range results { + t.AddRow(e.Ref.String(), e.Message) + } + s := t.Render([]int{60}) + _, _ = buf.WriteString(s) +} + +func formatValidateResultText(vr *types.ValidateResult) string { + buf := bytes.NewBuffer(nil) + + if len(vr.Warnings) != 0 { + buf.WriteString("\nValidation Warnings:\n") + prettyErrors(buf, vr.Warnings) + } + + if len(vr.Errors) != 0 { + if buf.Len() != 0 { + buf.WriteString("\n") + } + buf.WriteString("Validation Errors:\n") + prettyErrors(buf, vr.Errors) + } + + if len(vr.Results) != 0 { + if buf.Len() != 0 { + buf.WriteString("\n") + } + buf.WriteString("Results:\n") + prettyValidationResults(buf, vr.Results) + } + return buf.String() +} + +func formatValidateResultYaml(vr *types.ValidateResult) (string, error) { + b, err := yaml.Marshal(vr) + if err != nil { + return "", err + } + return string(b), nil +} + +func formatValidateResult(vr *types.ValidateResult, format string) (string, error) { + switch format { + case "text": + return formatValidateResultText(vr), nil + case "yaml": + return formatValidateResultYaml(vr) + default: + return "", fmt.Errorf("invalid validation result format: %s", format) + } +} + +func outputHelper(output []string, cb func(format string) (string, error)) error { + if len(output) == 0 { + output = []string{"text"} + } + for _, o := range output { + s := strings.SplitN(o, "=", 1) + format := s[0] + var path *string + if len(s) > 1 { + path = &s[1] + } + r, err := cb(format) + + err = outputResult(path, r) + if err != nil { + return err + } + } + return nil +} + +func outputCommandResult(output []string, cr *types.CommandResult) error { + return outputHelper(output, func(format string) (string, error) { + return formatCommandResult(cr, format) + }) +} + +func outputValidateResult(output []string, vr *types.ValidateResult) error { + return outputHelper(output, func(format string) (string, error) { + return formatValidateResult(vr, format) + }) +} + +func outputYamlResult(output []*string, result interface{}, multiDoc bool) error { + if len(output) == 0 { + output = []*string{nil} + } + var s string + if multiDoc { + l, ok := result.([]interface{}) + if !ok { + return fmt.Errorf("object is not a list") + } + x, err := utils.WriteYamlAllString(l) + if err != nil { + return err + } + s = x + } else { + x, err := utils.WriteYamlString(result) + if err != nil { + return err + } + s = x + } + for _, path := range output { + err := outputResult(path, s) + if err != nil { + return err + } + } + return nil +} + +func outputResult(f *string, result string) error { + w := os.Stdout + if f != nil && *f != "-" { + f, err := os.Open(*f) + if err != nil { + return err + } + defer f.Close() + w = f + } + _, err := w.Write([]byte(result)) + return err +} diff --git a/cmd/kluctl/main.go b/cmd/kluctl/main.go new file mode 100644 index 000000000..3c24062ba --- /dev/null +++ b/cmd/kluctl/main.go @@ -0,0 +1,20 @@ +/* +Copyright © 2022 Alexander Block + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package main + +func main() { + Execute() +} diff --git a/cmd/kluctl/root.go b/cmd/kluctl/root.go new file mode 100644 index 000000000..c66032ae4 --- /dev/null +++ b/cmd/kluctl/root.go @@ -0,0 +1,46 @@ +/* +Copyright © 2022 Alexander Block + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package main + +import ( + "os" + + "github.com/spf13/cobra" +) + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "kluctl", + Short: "Deploy and manage complex deployments on Kubernetes", + Long: `The missing glue to put together large Kubernetes deployments, +composed of multiple smaller parts (Helm/Kustomize/...) in a manageable and unified way.`, + // Uncomment the following line if your bare application + // has an action associated with it: + // Run: func(cmd *cobra.Command, args []string) { }, +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } +} + +func init() { + rootCmd.SilenceUsage = true +} diff --git a/cmd/kluctl/utils.go b/cmd/kluctl/utils.go new file mode 100644 index 000000000..e3e806b47 --- /dev/null +++ b/cmd/kluctl/utils.go @@ -0,0 +1,187 @@ +package main + +import ( + "fmt" + args "github.com/codablock/kluctl/cmd/kluctl/args" + "github.com/codablock/kluctl/pkg/deployment" + git_url "github.com/codablock/kluctl/pkg/git/git-url" + "github.com/codablock/kluctl/pkg/jinja2_server" + "github.com/codablock/kluctl/pkg/k8s" + "github.com/codablock/kluctl/pkg/kluctl_project" + "github.com/codablock/kluctl/pkg/types" + "github.com/codablock/kluctl/pkg/utils" + "io/ioutil" + "os" + "path/filepath" +) + +func withKluctlProjectFromArgs(cb func(p *kluctl_project.KluctlProjectContext) error) error { + var url *git_url.GitUrl + if args.ProjectUrl != "" { + var err error + url, err = git_url.Parse(args.ProjectUrl) + if err != nil { + return err + } + } + js, err := jinja2_server.NewJinja2Server() + if err != nil { + return err + } + defer js.Stop() + loadArgs := kluctl_project.LoadKluctlProjectArgs{ + ProjectUrl: url, + ProjectRef: args.ProjectRef, + ProjectConfig: args.ProjectConfig, + LocalClusters: args.LocalClusters, + LocalDeployment: args.LocalDeployment, + LocalSealedSecrets: args.LocalSealedSecrets, + FromArchive: args.FromArchive, + FromArchiveMetadata: args.FromArchiveMetadata, + JS: js, + } + return kluctl_project.LoadKluctlProject(loadArgs, cb) +} + +type commandCtx struct { + kluctlProject *kluctl_project.KluctlProjectContext + target *types.Target + clusterConfig *types.ClusterConfig + k *k8s.K8sCluster + deploymentProject *deployment.DeploymentProject + deploymentCollection *deployment.DeploymentCollection +} + +func withProjectCommandContext(cb func(ctx *commandCtx) error) error { + return withKluctlProjectFromArgs(func(p *kluctl_project.KluctlProjectContext) error { + var target *types.Target + if args.Target != "" { + t, err := p.FindTarget(args.Target) + if err != nil { + return err + } + target = t + } + return withProjectTargetCommandContext(p, target, false, cb) + }) +} + +func withProjectTargetCommandContext(p *kluctl_project.KluctlProjectContext, target *types.Target, forSeal bool, cb func(ctx *commandCtx) error) error { + projectDir, err := filepath.Abs(p.ProjectDir) + if err != nil { + return err + } + + clusterName := args.Cluster + if clusterName == "" { + if target == nil { + return fmt.Errorf("you must specify an existing --cluster when not providing a --target") + } + clusterName = target.Cluster + } + + clusterConfig, err := p.LoadClusterConfig(clusterName) + if err != nil { + return err + } + + k, err := k8s.NewK8sCluster(clusterConfig.Cluster.Context, args.DryRun) + if err != nil { + return err + } + + varsCtx := deployment.NewVarsCtx(p.JS) + err = varsCtx.UpdateChildFromStruct("cluster", clusterConfig.Cluster) + if err != nil { + return err + } + + images, err := deployment.NewImages(args.UpdateImages) + if err != nil { + return err + } + + inclusion, err := args.ParseInclusionFromArgs() + if err != nil { + return err + } + + allArgs := make(map[string]interface{}) + + optionArgs, err := deployment.ParseArgs(args.Args) + if err != nil { + return err + } + if target != nil { + for argName, argValue := range optionArgs { + err = p.CheckDynamicArg(target, argName, argValue) + if err != nil { + return err + } + } + } + utils.MergeObject(allArgs, deployment.ConvertArgsToVars(optionArgs)) + if target != nil { + utils.MergeObject(allArgs, target.Args) + if forSeal { + utils.MergeObject(allArgs, target.SealingConfig.Args) + } + } + + err = deployment.CheckRequiredDeployArgs(projectDir, varsCtx, allArgs) + if err != nil { + return err + } + + varsCtx.UpdateChild("args", allArgs) + + renderOutputDir := args.RenderOutputDir + if renderOutputDir == "" { + tmpDir, err := ioutil.TempDir(p.TmpDir, "rendered") + if err != nil { + return err + } + defer os.RemoveAll(tmpDir) + renderOutputDir = tmpDir + } + + d, err := deployment.NewDeploymentProject(k, varsCtx, projectDir, p.SealedSecretsDir, nil) + if err != nil { + return err + } + c, err := deployment.NewDeploymentCollection(d, images, inclusion, renderOutputDir, forSeal) + if err != nil { + return err + } + + fixedImages, err := args.LoadFixedImagesFromArgs() + if err != nil { + return err + } + if target != nil { + for _, fi := range target.Images { + images.AddFixedImage(fi) + } + } + for _, fi := range fixedImages.Images { + images.AddFixedImage(fi) + } + + if !forSeal { + err = c.Prepare(k) + if err != nil { + return err + } + } + + ctx := &commandCtx{ + kluctlProject: p, + target: target, + clusterConfig: clusterConfig, + k: k, + deploymentProject: d, + deploymentCollection: c, + } + + return cb(ctx) +} diff --git a/go.mod b/go.mod new file mode 100644 index 000000000..7fc2a6d7c --- /dev/null +++ b/go.mod @@ -0,0 +1,117 @@ +module github.com/codablock/kluctl + +go 1.17 + +require ( + github.com/gammazero/workerpool v1.1.2 + github.com/go-git/go-git/v5 v5.4.2 + github.com/go-playground/validator/v10 v10.10.0 + github.com/gobwas/glob v0.2.3 + github.com/gofrs/flock v0.8.1 + github.com/hashicorp/go-version v1.4.0 + github.com/hexops/gotextdiff v1.0.3 + github.com/jinzhu/copier v0.3.5 + github.com/mitchellh/go-ps v1.0.0 + github.com/ohler55/ojg v1.12.12 + github.com/r3labs/diff/v2 v2.15.0 + github.com/sirupsen/logrus v1.8.1 + github.com/spf13/cobra v1.3.0 + github.com/spf13/viper v1.10.1 + github.com/whilp/git-urls v1.0.0 + golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c + google.golang.org/grpc v1.43.0 + gopkg.in/yaml.v2 v2.4.0 + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b + k8s.io/apimachinery v0.23.3 + k8s.io/client-go v0.23.3 + sigs.k8s.io/kustomize/api v0.11.1 + sigs.k8s.io/structured-merge-diff/v4 v4.2.1 +) + +require ( + cloud.google.com/go v0.99.0 // indirect + github.com/Azure/go-autorest v14.2.0+incompatible // indirect + github.com/Azure/go-autorest/autorest v0.11.20 // indirect + github.com/Azure/go-autorest/autorest/adal v0.9.15 // indirect + github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect + github.com/Azure/go-autorest/logger v0.2.1 // indirect + github.com/Azure/go-autorest/tracing v0.6.0 // indirect + github.com/Microsoft/go-winio v0.5.1 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 // indirect + github.com/PuerkitoBio/purell v1.1.1 // indirect + github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect + github.com/acomagu/bufpipe v1.0.3 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/emirpasic/gods v1.12.0 // indirect + github.com/evanphx/json-patch v4.12.0+incompatible // indirect + github.com/fsnotify/fsnotify v1.5.1 // indirect + github.com/gammazero/deque v0.1.0 // indirect + github.com/go-errors/errors v1.0.1 // indirect + github.com/go-git/gcfg v1.5.0 // indirect + github.com/go-git/go-billy/v5 v5.3.1 // indirect + github.com/go-logr/logr v1.2.0 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.19.5 // indirect + github.com/go-openapi/swag v0.19.14 // indirect + github.com/go-playground/locales v0.14.0 // indirect + github.com/go-playground/universal-translator v0.18.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt/v4 v4.0.0 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/google/btree v1.0.1 // indirect + github.com/google/go-cmp v0.5.6 // indirect + github.com/google/gofuzz v1.1.0 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect + github.com/googleapis/gnostic v0.5.5 // indirect + github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/imdario/mergo v0.3.12 // indirect + github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect + github.com/leodido/go-urn v1.2.1 // indirect + github.com/magiconair/properties v1.8.5 // indirect + github.com/mailru/easyjson v0.7.6 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.4.3 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect + github.com/pelletier/go-toml v1.9.4 // indirect + github.com/peterbourgon/diskv v2.0.1+incompatible // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/sergi/go-diff v1.1.0 // indirect + github.com/spf13/afero v1.6.0 // indirect + github.com/spf13/cast v1.4.1 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/testify v1.7.0 // indirect + github.com/subosito/gotenv v1.2.0 // indirect + github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect + github.com/xanzy/ssh-agent v0.3.0 // indirect + github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect + go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect + golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d // indirect + golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect + golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect + golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368 // indirect + google.golang.org/protobuf v1.27.1 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/ini.v1 v1.66.2 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect + k8s.io/api v0.23.3 // indirect + k8s.io/klog/v2 v2.30.0 // indirect + k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect + k8s.io/utils v0.0.0-20211116205334-6203023598ed // indirect + sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect + sigs.k8s.io/kustomize/kyaml v0.13.3 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 000000000..051634036 --- /dev/null +++ b/go.sum @@ -0,0 +1,1094 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM= +cloud.google.com/go v0.99.0 h1:y/cM2iqGgGi5D5DQZl6D9STN/3dR/Vx5Mp8s752oJTY= +cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= +github.com/Azure/go-autorest/autorest v0.11.20 h1:s8H1PbCZSqg/DH7JMlOz6YMig6htWLNPsjDdlLqCx3M= +github.com/Azure/go-autorest/autorest v0.11.20/go.mod h1:o3tqFY+QR40VOlk+pV4d77mORO64jOXSgEnPQgLK6JY= +github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= +github.com/Azure/go-autorest/autorest/adal v0.9.15 h1:X+p2GF0GWyOiSmqohIaEeuNFNDY4I4EOlVuUQvFdWMk= +github.com/Azure/go-autorest/autorest/adal v0.9.15/go.mod h1:tGMin8I49Yij6AQ+rvV+Xa/zwxYQB5hmsd6DkfAx2+A= +github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= +github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY= +github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C67SkzkDfmQuVln04ygHj3vjZfd9FL+GmQQ= +github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= +github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= +github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= +github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= +github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= +github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= +github.com/gammazero/deque v0.1.0 h1:f9LnNmq66VDeuAlSAapemq/U7hJ2jpIWa4c09q8Dlik= +github.com/gammazero/deque v0.1.0/go.mod h1:KQw7vFau1hHuM8xmI9RbgKFbAsQFWmBpqQ2KenFLk6M= +github.com/gammazero/workerpool v1.1.2 h1:vuioDQbgrz4HoaCi2q1HLlOXdpbap5AET7xu5/qj87g= +github.com/gammazero/workerpool v1.1.2/go.mod h1:UelbXcO0zCIGFcufcirHhq2/xtLXJdQ29qZNlXG9OjQ= +github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= +github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= +github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= +github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34= +github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-git-fixtures/v4 v4.2.1 h1:n9gGL1Ct/yIw+nfsfr8s4+sbhT+Ncu2SubfXjIWgci8= +github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0= +github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4= +github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v1.2.0 h1:QK40JKJyMdUDz+h+xvCsru/bJhvG0UxvePV0ufL/AcE= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM= +github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= +github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0= +github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.0.0 h1:RAqyYixv1p7uEnocuy8P1nru5wprCh/MH2BIlW5z5/o= +github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= +github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw= +github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= +github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.4.0 h1:aAQzgqIrRKRa7w75CKpbBxYsmUoPjzVm1W59ca1L0J4= +github.com/hashicorp/go-version v1.4.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= +github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= +github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= +github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg= +github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= +github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= +github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= +github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= +github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= +github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/ohler55/ojg v1.12.12 h1:hepbQFn7GHAecTPmwS3j5dCiOLsOpzPLvhiqnlAVAoE= +github.com/ohler55/ojg v1.12.12/go.mod h1:LBbIVRAgoFbYBXQhRhuEpaJIqq+goSO63/FQ+nyJU88= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= +github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/r3labs/diff/v2 v2.15.0 h1:3TEoJ6dBqESl1YgL+7curys5PvuEnwrtjkFNskgUvfg= +github.com/r3labs/diff/v2 v2.15.0/go.mod h1:I8noH9Fc2fjSaMxqF3G2lhDdC0b+JXCfyx85tWFM9kc= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= +github.com/sagikazarmark/crypt v0.4.0/go.mod h1:ALv2SRj7GxYV4HO9elxH9nS6M9gW+xDNxqmyJ6RfDFM= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= +github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= +github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= +github.com/spf13/cobra v1.3.0 h1:R7cSvGu+Vv+qX0gW5R/85dx2kmmJT5z5NM8ifdYjdn0= +github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= +github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= +github.com/spf13/viper v1.10.1 h1:nuJZuYpG7gTj/XqiUwg8bA0cp1+M2mC3J4g5luUYBKk= +github.com/spf13/viper v1.10.1/go.mod h1:IGlFPqhNAPKRxohIzWpI5QEy4kuI7tcl5WvR+8qy1rU= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= +github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= +github.com/whilp/git-urls v1.0.0 h1:95f6UMWN5FKW71ECsXRUd3FVYiXdrE7aX4NZKcPmIjU= +github.com/whilp/git-urls v1.0.0/go.mod h1:J16SAmobsqc3Qcy98brfl5f5+e0clUvg1krgwk/qCfE= +github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= +github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= +github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca h1:1CFlNzQhALwjS9mBAUkycX616GzgsuYUOCHA5+HSlXI= +github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= +go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee331t6JAXeK2bcyhLOOc= +go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 h1:/pEO3GD/ABYAjuakUS6xSEmmlyVS4kxBNkeA9tLJiTI= +golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d h1:62NvYBuaanGXR2ZOfwDFkhhl6X1DUgf8qg3GuQvxZsE= +golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU= +google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= +google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw= +google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368 h1:Et6SkiuvnBn+SgrSYXs/BrUpGB4mbdwt4R3vaPIlicA= +google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.43.0 h1:Eeu7bZtDZ2DpRCsLhUlcrLnvYaMK1Gz86a+hMVvELmM= +google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI= +gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/api v0.23.3 h1:KNrME8KHGr12Ozjf8ytOewKzZh6hl/hHUZeHddT3a38= +k8s.io/api v0.23.3/go.mod h1:w258XdGyvCmnBj/vGzQMj6kzdufJZVUwEM1U2fRJwSQ= +k8s.io/apimachinery v0.23.3 h1:7IW6jxNzrXTsP0c8yXz2E5Yx/WTzVPTsHIx/2Vm0cIk= +k8s.io/apimachinery v0.23.3/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM= +k8s.io/client-go v0.23.3 h1:23QYUmCQ/W6hW78xIwm3XqZrrKZM+LWDqW2zfo+szJs= +k8s.io/client-go v0.23.3/go.mod h1:47oMd+YvAOqZM7pcQ6neJtBiFH7alOyfunYN48VsmwE= +k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.30.0 h1:bUO6drIvCIsvZ/XFgfxoGFQU/a4Qkh0iAlvUR7vlHJw= +k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= +k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 h1:E3J9oCLlaobFUqsjG9DfKbP2BmgwBL2p7pn0A3dG9W4= +k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= +k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20211116205334-6203023598ed h1:ck1fRPWPJWsMd8ZRFsWc6mh/zHp5fZ/shhbrgPUxDAE= +k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 h1:fD1pz4yfdADVNfFmcP2aBEtudwUQ1AlLnRBALr33v3s= +sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= +sigs.k8s.io/kustomize/api v0.11.1 h1:/Vutu+gAqVo8skw1xCZrsZD39SN4Adg+z7FrSTw9pds= +sigs.k8s.io/kustomize/api v0.11.1/go.mod h1:GZuhith5YcqxIDe0GnRJNx5xxPTjlwaLTt/e+ChUtJA= +sigs.k8s.io/kustomize/kyaml v0.13.3 h1:tNNQIC+8cc+aXFTVg+RtQAOsjwUdYBZRAgYOVI3RBc4= +sigs.k8s.io/kustomize/kyaml v0.13.3/go.mod h1:/ya3Gk4diiQzlE4mBh7wykyLRFZNvqlbh+JnwQ9Vhrc= +sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y= +sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/jinja2-server/.gitignore b/jinja2-server/.gitignore new file mode 100644 index 000000000..c06f787ba --- /dev/null +++ b/jinja2-server/.gitignore @@ -0,0 +1,8 @@ +__pycache__ + +/venv +/main.bin +/main.build + +jinja2_server_pb2.py +jinja2_server_pb2_grpc.py diff --git a/jinja2-server/dict_utils.py b/jinja2-server/dict_utils.py new file mode 100644 index 000000000..f076faeea --- /dev/null +++ b/jinja2-server/dict_utils.py @@ -0,0 +1,64 @@ +from jsonpath_utils import parse_json_path + + +def copy_primitive_value(v): + if isinstance(v, dict): + return copy_dict(v) + if is_iterable(v, False): + return [copy_primitive_value(x) for x in v] + return v + +def copy_dict(a): + ret = {} + for k, v in a.items(): + ret[k] = copy_primitive_value(v) + return ret + +def merge_dict(a, b, clone=True): + if clone: + a = copy_dict(a) + if a is None: + a = {} + if b is None: + b = {} + for key in b: + if key in a: + if isinstance(a[key], dict) and isinstance(b[key], dict): + merge_dict(a[key], b[key], clone=False) + else: + a[key] = b[key] + else: + a[key] = b[key] + return a + + +def get_dict_value(y, path, default=None): + p = parse_json_path(path) + f = p.find(y) + if len(f) > 1: + raise Exception("Only simple jsonpath supported in get_dict_value") + if len(f) == 0: + return default + return f[0].value + +def is_iterable(obj, str_and_bytes=True): + if isinstance(obj, list): + return True + if isinstance(obj, tuple): + return True + if isinstance(obj, dict): + return True + if isinstance(obj, str): + return str_and_bytes + if isinstance(obj, bytes): + return str_and_bytes + if isinstance(obj, int) or isinstance(obj, bool): + return False + if isinstance(obj, type): + return False + try: + iter(obj) + except Exception: + return False + else: + return True diff --git a/jinja2-server/gen-go.sh b/jinja2-server/gen-go.sh new file mode 100755 index 000000000..432d4123d --- /dev/null +++ b/jinja2-server/gen-go.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +DIR=$(cd $(dirname $0) && pwd) +cd $DIR/.. + +protoc -I=jinja2-server --go_out=pkg --go-grpc_out=pkg/jinja2_server --go-grpc_opt=paths=source_relative ./jinja2-server/jinja2_server.proto diff --git a/jinja2-server/gen-python.sh b/jinja2-server/gen-python.sh new file mode 100755 index 000000000..3a5964910 --- /dev/null +++ b/jinja2-server/gen-python.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +DIR=$(cd $(dirname $0) && pwd) +cd $DIR + +export PATH=$DIR/venv/bin:$PATH + +protoc -I=. --python_out=. ./jinja2_server.proto +python -m grpc_tools.protoc -I=. --python_out=. --grpc_python_out=. jinja2_server.proto diff --git a/kluctl/utils/jinja2_cache.py b/jinja2-server/jinja2_cache.py similarity index 97% rename from kluctl/utils/jinja2_cache.py rename to jinja2-server/jinja2_cache.py index 9cb6afac5..d869d30ed 100644 --- a/kluctl/utils/jinja2_cache.py +++ b/jinja2-server/jinja2_cache.py @@ -2,6 +2,7 @@ import fnmatch import os import stat +import tempfile import typing from datetime import datetime from types import CodeType @@ -9,8 +10,10 @@ from jinja2 import BytecodeCache from jinja2.bccache import Bucket -from kluctl.utils.utils import get_tmp_base_dir - +def get_tmp_base_dir(): + dir = os.path.join(tempfile.gettempdir(), "kluctl-j2-server") + os.makedirs(dir, exist_ok=True) + return dir """ A bytecode cache that is able to share bytecode between different directories. It does this by removing filenames diff --git a/jinja2-server/jinja2_server.proto b/jinja2-server/jinja2_server.proto new file mode 100644 index 000000000..9e0a52368 --- /dev/null +++ b/jinja2-server/jinja2_server.proto @@ -0,0 +1,31 @@ +syntax = "proto3"; + +package jinja_server; +option go_package = "/jinja2_server"; + +message StringsJob { + string vars = 1; + repeated string search_dirs = 2; + repeated string templates = 3; +} + +message FilesJob { + string vars = 1; + repeated string search_dirs = 2; + repeated string templates = 3; +} + +message SingleResult { + optional string result = 1; + optional string error = 2; +} + +message JobResult { + optional string error = 1; + repeated SingleResult results = 2; +} + +service Jinja2Server { + rpc RenderStrings(StringsJob) returns(JobResult); + rpc RenderFiles(FilesJob) returns(JobResult); +} diff --git a/jinja2-server/jinja2_server.py b/jinja2-server/jinja2_server.py new file mode 100644 index 000000000..0ec2e07c8 --- /dev/null +++ b/jinja2-server/jinja2_server.py @@ -0,0 +1,109 @@ +import base64 +import json +from concurrent.futures import ThreadPoolExecutor + +from jinja2 import StrictUndefined, FileSystemLoader + +import jinja2_server_pb2 +import jinja2_server_pb2_grpc +from dict_utils import merge_dict +from jinja2_cache import KluctlBytecodeCache +from jinja2_utils import KluctlJinja2Environment, add_jinja2_filters, extract_template_error +from yaml_utils import yaml_load + +jinja2_cache = KluctlBytecodeCache(max_cache_files=10000) + +begin_placeholder = "XXXXXbegin_get_image_" +end_placeholder = "_end_get_imageXXXXX" + +class Jinja2Servicer(jinja2_server_pb2_grpc.Jinja2ServerServicer): + def __init__(self): + self.executor = ThreadPoolExecutor() + + def get_image_wrapper(self, image, latest_version=None): + if latest_version is None: + latest_version = "semver()" + placeholder = { + "image": image, + "latestVersion": str(latest_version), + } + j = json.dumps(placeholder) + j = base64.b64encode(j.encode("utf8")).decode("utf8") + j = begin_placeholder + j + end_placeholder + return j + + def build_images_vars(self): + def semver(allow_no_nums=False): + return "semver(allow_no_nums=%s)" % allow_no_nums + def prefix(s, suffix=None): + if suffix is None: + return "prefix(\"%s\")" % s + else: + return "prefix(\"%s\", suffix=\"%s\")" % (s, suffix) + def number(): + return "number()" + def regex(r): + return "regex(\"%s\")" % r + + vars = { + 'images': { + 'get_image': self.get_image_wrapper, + }, + 'version': { + 'semver': semver, + 'prefix': prefix, + 'number': number, + 'regex': regex, + }, + } + return vars + + def build_env(self, vars_str, search_dirs): + vars = json.loads(vars_str) + image_vars = self.build_images_vars() + merge_dict(vars, image_vars, clone=False) + + environment = KluctlJinja2Environment(loader=FileSystemLoader(search_dirs), undefined=StrictUndefined, + cache_size=10000, + bytecode_cache=jinja2_cache, auto_reload=False) + merge_dict(environment.globals, vars, clone=False) + + add_jinja2_filters(environment) + return environment + + def prepare_result(self, cnt): + result = jinja2_server_pb2.JobResult() + for i in range(cnt): + r = jinja2_server_pb2.SingleResult() + result.results.append(r) + return result + + + def render_helper(self, request, is_string): + result = self.prepare_result(len(request.templates)) + env = self.build_env(request.vars, request.search_dirs) + + def do_render(i, t): + try: + if is_string: + t = env.from_string(t) + else: + t = env.get_template(t) + result.results[i].result = t.render() + except Exception as e: + result.results[i].error = extract_template_error(e) + + futures = [] + for i, t in enumerate(request.templates): + f = self.executor.submit(do_render, i, t) + futures.append(f) + + for f in futures: + f.result() + return result + + def RenderStrings(self, request, context): + return self.render_helper(request, True) + + def RenderFiles(self, request, context): + return self.render_helper(request, False) diff --git a/kluctl/utils/jinja2_utils.py b/jinja2-server/jinja2_utils.py similarity index 67% rename from kluctl/utils/jinja2_utils.py rename to jinja2-server/jinja2_utils.py index d0c62d275..2ac936b99 100644 --- a/kluctl/utils/jinja2_utils.py +++ b/jinja2-server/jinja2_utils.py @@ -1,5 +1,6 @@ import base64 import hashlib +import io import json import logging import os @@ -7,11 +8,11 @@ import traceback import jinja2 -from jinja2 import Environment, StrictUndefined, FileSystemLoader, TemplateNotFound, TemplateError +from jinja2 import Environment, TemplateNotFound, TemplateError from jinja2.runtime import Context -from kluctl.utils.dict_utils import merge_dict, get_dict_value -from kluctl.utils.yaml_utils import yaml_dump, yaml_load +from dict_utils import merge_dict, get_dict_value +from yaml_utils import yaml_dump, yaml_load logger = logging.getLogger(__name__) @@ -117,53 +118,7 @@ def add_jinja2_filters(jinja2_env): jinja2_env.globals['debug_print'] = debug_print jinja2_env.globals['load_sha256'] = load_sha256 -def render_str(s, jinja_vars): - if "{" not in s: - return s - e = KluctlJinja2Environment(undefined=StrictUndefined) - add_jinja2_filters(e) - merge_dict(e.globals, jinja_vars, False) - t = e.from_string(s) - return t.render() - -def render_dict_strs2(d, jinja_vars, errors): - if isinstance(d, dict): - ret = {} - for n, v in d.items(): - ret[n] = render_dict_strs2(v, jinja_vars, errors) - return ret - elif isinstance(d, list): - ret = [] - for v in d: - ret.append(render_dict_strs2(v, jinja_vars, errors)) - return ret - elif isinstance(d, str): - try: - return render_str(d, jinja_vars) - except TemplateError as e: - errors.append(e) - return d - else: - return d - -def render_dict_strs(d, jinja_vars, do_raise=True): - errors = [] - ret = render_dict_strs2(d, jinja_vars, errors) - if do_raise: - if errors: - raise errors[0] - return ret - return ret, errors - -def render_file(root_dir, path, jinja_vars): - e = KluctlJinja2Environment(loader=FileSystemLoader(root_dir), undefined=StrictUndefined) - path = os.path.normpath(path) - add_jinja2_filters(e) - merge_dict(e.globals, jinja_vars, False) - t = e.get_template(path.replace('\\', '/')) - return t.render() - -def print_template_error(e): +def extract_template_error(e): try: raise e except: @@ -174,8 +129,10 @@ def print_template_error(e): if not s.filename.endswith(".py"): found_template = i break + f = io.StringIO() if found_template is not None: - traceback.print_list([extracted_tb[found_template]]) - print("%s: %s" % (type(e).__name__, str(e)), file=sys.stderr) + traceback.print_list([extracted_tb[found_template]], file=f) + print("%s: %s" % (type(e).__name__, str(e)), file=f) else: - traceback.print_exception(etype, value, tb) + traceback.print_exception(etype, value, tb, file=f) + return f.getvalue() diff --git a/kluctl/utils/jsonpath_utils.py b/jinja2-server/jsonpath_utils.py similarity index 96% rename from kluctl/utils/jsonpath_utils.py rename to jinja2-server/jsonpath_utils.py index 30e1bb9e6..2135238ff 100644 --- a/kluctl/utils/jsonpath_utils.py +++ b/jinja2-server/jsonpath_utils.py @@ -10,7 +10,6 @@ from jsonpath_ng.ext.parser import ExtentedJsonPathParser from jsonpath_ng.parser import IteratorToTokenStream -from kluctl.utils.exceptions import CommandError logger = logging.getLogger(__name__) @@ -98,7 +97,7 @@ def parse_json_path(p) -> JSONPath: pp = json_path_local.parser.parse(p) except JsonPathParserError as e: - raise CommandError("Invalid json path '%s'. Error=%s" % (p, str(e))) + raise Exception("Invalid json path '%s'. Error=%s" % (p, str(e))) pp = json_path_cache.setdefault(p, pp) return pp diff --git a/jinja2-server/main.py b/jinja2-server/main.py new file mode 100755 index 000000000..2caedd9ac --- /dev/null +++ b/jinja2-server/main.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python +import logging +import sys +from concurrent import futures + +import click +import grpc + +import jinja2_server_pb2_grpc +from jinja2_server import Jinja2Servicer + +logger = logging.getLogger(__name__) + + +@click.group() +@click.option("--debug", is_flag=True) +def cli(debug): + level = logging.INFO + if debug: + level = logging.DEBUG + logging.basicConfig(level=level) + pass + +@cli.command() +def serve(): + executor = futures.ThreadPoolExecutor(max_workers=10) + server = grpc.server(executor) + jinja2_server_pb2_grpc.add_Jinja2ServerServicer_to_server(Jinja2Servicer(), server) + + port = server.add_insecure_port('[::]:0') + try: + server.start() + click.echo("%d" % port) + server.wait_for_termination() + except Exception as e: + logger.warning("Failed to start server: %s", e) + sys.exit(1) + +if __name__ == "__main__": + cli() diff --git a/jinja2-server/requirements-dev.txt b/jinja2-server/requirements-dev.txt new file mode 100644 index 000000000..08d716cc4 --- /dev/null +++ b/jinja2-server/requirements-dev.txt @@ -0,0 +1,2 @@ +nuitka==0.6.19.6 +grpcio-tools==1.43.0 diff --git a/jinja2-server/requirements.txt b/jinja2-server/requirements.txt new file mode 100644 index 000000000..292d41012 --- /dev/null +++ b/jinja2-server/requirements.txt @@ -0,0 +1,6 @@ +jinja2===3.0.3 +click==8.0.3 +jsonpath-ng==1.5.3 +pyyaml==6.0 +protobuf==3.19.4 +grpcio==1.43.0 diff --git a/kluctl/utils/yaml_utils.py b/jinja2-server/yaml_utils.py similarity index 83% rename from kluctl/utils/yaml_utils.py rename to jinja2-server/yaml_utils.py index f7091608c..ce9d8ec8d 100644 --- a/kluctl/utils/yaml_utils.py +++ b/jinja2-server/yaml_utils.py @@ -1,13 +1,6 @@ -import sys - import yaml -try: - from yaml import CSafeLoader as SafeLoader, CSafeDumper as SafeDumper -except ImportError: - print("Failed to load fast LibYAML bindings. You should install them to speed up kluctl.", file=sys.stderr) - from yaml import SafeLoader as SafeLoader, SafeDumper as SafeDumper - +from yaml import SafeLoader as SafeLoader, SafeDumper as SafeDumper def construct_value(load, node): if not isinstance(node, yaml.ScalarNode): diff --git a/kluctl.spec b/kluctl.spec deleted file mode 100644 index ca358af60..000000000 --- a/kluctl.spec +++ /dev/null @@ -1,41 +0,0 @@ -# -*- mode: python ; coding: utf-8 -*- - -import os - -block_cipher = None - -datas = [] -for dirpath, dirnames, filenames in os.walk("./kluctl"): - for f in filenames: - if f.endswith(".py") or f.endswith(".pyc"): - continue - p = os.path.join(dirpath, f) - datas.append((p, dirpath)) - -a = Analysis(['cli.py'], - binaries=[], - datas=datas, - hiddenimports=[], - hookspath=[], - runtime_hooks=[], - excludes=[], - win_no_prefer_redirects=False, - win_private_assemblies=False, - cipher=block_cipher, - noarchive=False) -pyz = PYZ(a.pure, a.zipped_data, - cipher=block_cipher) -exe = EXE(pyz, - a.scripts, - a.binaries, - a.zipfiles, - a.datas, - [], - name='kluctl', - debug=False, - bootloader_ignore_signals=False, - strip=False, - upx=True, - upx_exclude=[], - runtime_tmpdir=None, - console=True ) diff --git a/kluctl.toml b/kluctl.toml deleted file mode 100644 index 9bf5c7e9e..000000000 --- a/kluctl.toml +++ /dev/null @@ -1,6 +0,0 @@ -[build-system] -requires = [ - "setuptools>=56", - "wheel" -] -build-backend = "setuptools.build_meta" diff --git a/kluctl/__init__.py b/kluctl/__init__.py deleted file mode 100644 index 03ad84b60..000000000 --- a/kluctl/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -import os - - -def get_kluctl_package_dir(): - return os.path.dirname(__file__) \ No newline at end of file diff --git a/kluctl/_version.py b/kluctl/_version.py deleted file mode 100644 index 6c8e6b979..000000000 --- a/kluctl/_version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.0.0" diff --git a/kluctl/cli/__init__.py b/kluctl/cli/__init__.py deleted file mode 100644 index 63637b635..000000000 --- a/kluctl/cli/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -import click - -from kluctl.cli.recursive_click_context import RecursiveClickContext - -click.BaseCommand.context_class = RecursiveClickContext - -from kluctl.cli import main_cli_group -from kluctl.cli import util_command_stubs -from kluctl.cli import command_stubs -from kluctl.cli import seal_command_stubs diff --git a/kluctl/cli/__main__.py b/kluctl/cli/__main__.py deleted file mode 100755 index 736acbf6f..000000000 --- a/kluctl/cli/__main__.py +++ /dev/null @@ -1,43 +0,0 @@ -import logging -import sys - -from git import GitCommandError -from jinja2 import TemplateError, TemplateNotFound -from yaml import YAMLError - -from kluctl.cli.main_cli_group import cli_group -from kluctl.utils.exceptions import CommandError, InvalidKluctlProjectConfig -from kluctl.utils.jinja2_utils import print_template_error - -logger = logging.getLogger(__name__) - -config = {} - -def main(): - try: - cli_group(prog_name="kluctl") - except (CommandError, YAMLError) as e: - print(e, file=sys.stderr) - sys.exit(1) - except InvalidKluctlProjectConfig as e: - print(e.message, file=sys.stderr) - sys.exit(1) - except TemplateNotFound as e: - print("Template not found: %s" % e.name, file=sys.stderr) - sys.exit(1) - except TemplateError as e: - print_template_error(e) - sys.exit(1) - except GitCommandError as e: - print(e.stderr) - sys.exit(1) - except Exception as e: - from kubernetes.dynamic.exceptions import UnauthorizedError - if isinstance(e, UnauthorizedError): - logger.error("Failed to authenticate/authorize for kubernetes cluster") - sys.exit(1) - else: - raise - -if __name__ == "__main__": - main() diff --git a/kluctl/cli/command_result.py b/kluctl/cli/command_result.py deleted file mode 100644 index 2a00c7be2..000000000 --- a/kluctl/cli/command_result.py +++ /dev/null @@ -1,172 +0,0 @@ -import dataclasses -import os - -import click -from deepdiff.helper import NotPresent - -from kluctl.cli.utils import build_seen_images -from kluctl.deployment.deployment_collection import CommandResult -from kluctl.diff.k8s_diff import unified_diff_object, changes_to_yaml -from kluctl.utils.exceptions import CommandError -from kluctl.utils.k8s_object_utils import get_long_object_name, get_long_object_name_from_ref, get_object_ref -from kluctl.utils.pretty_table import pretty_table -from kluctl.utils.yaml_utils import yaml_dump, yaml_dump_all - - -def format_command_result_text(command_result: CommandResult): - result = '' - - if command_result.warnings: - result += "\nWarnings:\n" - result += pretty_errors(command_result.warnings) - - if command_result.new_objects: - result += "\nNew objects:\n" - for x in command_result.new_objects: - result += " %s\n" % get_long_object_name(x, include_api_version=False) - - if command_result.changed_objects: - result += "\nChanged objects:\n" - for x in command_result.changed_objects: - result += " %s\n" % get_long_object_name(x["new_object"], include_api_version=False) - - result += "\n" - for x in command_result.changed_objects: - object = x["new_object"] - changes = x["changes"] - result += "%s" % pretty_changes(get_object_ref(object), changes) - - if command_result.deleted_objects: - result += "\nDeleted objects:\n" - for x in command_result.deleted_objects: - result += " %s\n" % get_long_object_name_from_ref(x) - - if command_result.hook_objects: - result += "\nApplied hooks:\n" - for x in command_result.hook_objects: - result += " %s\n" % get_long_object_name(x) - - if command_result.orphan_objects: - result += "\nOrphan objects:\n" - for ref in command_result.orphan_objects: - result += " %s\n" % get_long_object_name_from_ref(ref) - - if command_result.errors: - result += "\nErrors:\n" - result += pretty_errors(command_result.errors) - - return result - -def pretty_errors(errors): - table = [("Object", "Message")] - for e in errors: - table.append((get_long_object_name_from_ref(e.ref), e.message)) - return pretty_table(table, [60]) - -def pretty_changes(ref, changes): - ret = 'Diff for object %s\n' % get_long_object_name_from_ref(ref) - - table = [('Path', 'Diff')] - for c in changes: - if "unified_diff" in c: - diff = c["unified_diff"] - else: - diff = unified_diff_object(c.get("old_value", NotPresent()), c.get("new_value", NotPresent())) - table.append((c["path"], diff)) - - ret += pretty_table(table, [60]) - - return ret - -def format_command_result_yaml(c, command_result: CommandResult): - result = { - "diff": changes_to_yaml(command_result.new_objects, command_result.changed_objects), - "deleted_objects": command_result.deleted_objects, - "applied_hooks": command_result.hook_objects, - "orphan_objects": [{"ref": dataclasses.asdict(ref)} for ref in command_result.orphan_objects], - "errors": [dataclasses.asdict(x) for x in command_result.errors], - "warnings": [dataclasses.asdict(x) for x in command_result.warnings], - "images": build_seen_images(c, True), - } - return yaml_dump(result) - -def format_command_result(c, command_result, format): - if format == "text": - return format_command_result_text(command_result) - elif format == "yaml": - return format_command_result_yaml(c, command_result) - else: - raise CommandError(f"Invalid format: {format}") - -def format_validate_result(result, format): - if format == "text": - str = "" - if result.warnings: - str += "Validation Warnings:\n" - for item in result.warnings: - str += " %s: reason=%s, message=%s\n" % (get_long_object_name_from_ref(item.ref), item.reason, item.message) - if result.errors: - if str: - str += "\n" - str += "Validation Errors:\n" - for item in result.errors: - str += " %s: reason=%s, message=%s\n" % (get_long_object_name_from_ref(item.ref), item.reason, item.message) - if result.results: - if str: - str += "\n" - str += "Results:\n" - for item in result.results: - str += " %s: reason=%s, message=%s\n" % (get_long_object_name_from_ref(item.ref), item.reason, item.message) - return str - if format == "yaml": - y = yaml_dump(dataclasses.asdict(result)) - return y - else: - raise CommandError(f"Invalid format: {format}") - - -def output_command_result(output, c, command_result): - if not output: - output = ["text"] - for o in output: - s = o.split("=", 1) - format = s[0] - path = None - if len(s) > 1: - path = s[1] - s = format_command_result(c, command_result, format) - output_result(path, s) - - -def output_validate_result(output, result): - if not output: - output = ["text"] - for o in output: - s = o.split("=", 1) - format = s[0] - path = None - if len(s) > 1: - path = s[1] - s = format_validate_result(result, format) - output_result(path, s) - - -def output_yaml_result(output, result, all=False): - output = output or [None] - if all: - s = yaml_dump_all(result) - else: - s = yaml_dump(result) - for o in output: - output_result(o, s) - - -def output_result(output_file, result): - path = None - if output_file and output_file != "-": - path = os.path.expanduser(output_file) - if path is None: - click.echo(result) - else: - with open(path, "wt") as f: - f.write(result) diff --git a/kluctl/cli/command_stubs.py b/kluctl/cli/command_stubs.py deleted file mode 100644 index a862b193f..000000000 --- a/kluctl/cli/command_stubs.py +++ /dev/null @@ -1,181 +0,0 @@ -import click -from click_option_group import optgroup - -from kluctl.cli.main_cli_group import cli_group, kluctl_project_args, misc_arguments, image_args, include_exclude_args - -@cli_group.command("bootstrap", - help="Bootstrap a target cluster.\n\n" - "This will install the sealed-secrets operator into the specified cluster if not already " - "installed.\n\n" - "Either --target or --cluster must be specified.") -@kluctl_project_args(with_a=False) -@misc_arguments(yes=True, dry_run=True, force_apply=True, replace_on_error=True, hook_timeout=True, abort_on_error=True, output_format=True) -@click.pass_obj -def bootstrap_command_stub(obj, **kwargs): - from kluctl.cli.commands import bootstrap_command - bootstrap_command(obj, kwargs) - -@cli_group.command("deploy", - help="Deploys a target to the corresponding cluster.\n\n" - "This command will also output a diff between the initial state and the state after " - "deployment. The format of this diff is the same as for the `diff` command. " - "It will also output a list of prunable objects (without actually deleting them).") -@kluctl_project_args() -@image_args() -@include_exclude_args() -@misc_arguments(yes=True, dry_run=True, force_apply=True, replace_on_error=True, abort_on_error=True, hook_timeout=True, output_format=True, render_output_dir=True) -@click.pass_obj -def deploy_command_stub(obj, **kwargs): - from kluctl.cli.commands import deploy_command - deploy_command(obj, kwargs) - -@cli_group.command("diff", - help="Perform a diff between the locally rendered target and the already deployed target.\n\n" - "The output is by default in human readable form (a table combined with unified diffs). " - "The output can also be changed to output yaml file. Please note however that the format " - "is currently not documented and prone to changes.\n\n" - "After the diff is performed, the command will also search for prunable objects and list them.") -@kluctl_project_args() -@image_args() -@include_exclude_args() -@misc_arguments(force_apply=True, replace_on_error=True, ignore_labels=True, ignore_order=True, output_format=True, render_output_dir=True) -@click.pass_obj -def diff_command_stub(obj, **kwargs): - from kluctl.cli.commands import diff_command - diff_command(obj, kwargs) - -@cli_group.command("delete", - help="Delete the a target (or parts of it) from the corresponding cluster.\n\n" - "Objects are located based on `deleteByLabels`, configured in `deployment.yml`\n\n" - "WARNING: This command will also delete objects which are not part of your deployment " - "project (anymore). It really only decides based on the `deleteByLabel` labels and does NOT " - "take the local target/state into account!") -@kluctl_project_args() -@image_args() -@include_exclude_args() -@misc_arguments(yes=True, dry_run=True, output_format=True) -@click.pass_obj -def delete_command_stub(obj, **kwargs): - from kluctl.cli.commands import delete_command - delete_command(obj, kwargs) - -@cli_group.command("prune", - help="Searches the target cluster for prunable objects and deletes them.\n\n" - "Searching works by:\n\n" - "\b\n" - " 1. Search the cluster for all objects match `deleteByLabels`, as configured in `deployment.yml`\n" - " 2. Render the local target and list all objects.\n" - " 3. Remove all objects from the list of 1. that are part of the list in 2.\n") -@kluctl_project_args() -@image_args() -@include_exclude_args() -@misc_arguments(yes=True, dry_run=True, output_format=True) -@click.pass_obj -def prune_command_stub(obj, **kwargs): - from kluctl.cli.commands import prune_command - prune_command(obj, kwargs) - -@cli_group.command("poke-images", - help="Replace all images in target.\n\n" - "This command will fully render the target and then only replace images instead of fully " - "deploying the target. Only images used in combination with `images.get_image(...)` are " - "replaced.") -@kluctl_project_args() -@image_args() -@include_exclude_args() -@misc_arguments(yes=True, dry_run=True, output_format=True, render_output_dir=True) -@click.pass_obj -def poke_images_command_stub(obj, **kwargs): - from kluctl.cli.commands import poke_images_command - poke_images_command(obj, kwargs) - -@cli_group.command("downscale", - help="Downscale all deployments.\n\n" - "This command will downscale all Deployments, StatefulSets and CronJobs. " - "It is also possible to influence the behaviour with the help of annotations, as described in " - "the documentation.") -@kluctl_project_args() -@image_args() -@include_exclude_args() -@misc_arguments(yes=True, dry_run=True, output_format=True, render_output_dir=True) -@click.pass_obj -def downscale_command_stub(obj, **kwargs): - from kluctl.cli.commands import downscale_command - downscale_command(obj, kwargs) - -@cli_group.command("validate", - help="Validates the already deployed deployment.\n\n" - "This means that all objects are retrieved from the cluster and checked for readiness.\n\n" - "TODO: This needs to be better documented!") -@kluctl_project_args() -@include_exclude_args() -@misc_arguments(output=True, render_output_dir=True) -@optgroup.option("--wait", help="Wait for the given amount of time until the deployment validates") -@optgroup.option("--sleep", help="Sleep duration between validation attempts", default="5s") -@optgroup.option("--warnings-as-errors", help="Consider warnings as failures", is_flag=True) -@click.pass_obj -def validate_command_stub(obj, **kwargs): - from kluctl.cli.commands import validate_command - validate_command(obj, kwargs) - -@cli_group.command("render", - help="Renders all resources and configuration files and stores the result in either " - "a temporary directory or a specified directory.") -@kluctl_project_args() -@image_args() -@misc_arguments(render_output_dir=True) -@optgroup.option("--output-images", - help="Also output images list to given FILE. This output the same result as from the " - "list-images command.", - type=click.Path(dir_okay=False)) -@optgroup.option("--output-single-yaml", - help="Also write all resources into a single yaml file.", - type=click.Path(dir_okay=False)) -@click.pass_obj -def render_command_stub(obj, **kwargs): - from kluctl.cli.commands import render_command - render_command(obj, kwargs) - -@cli_group.command("list-images", - help="Renders the target and outputs all images used via `images.get_image(...)`\n\n" - "The result is a compatible with yaml files expected by --fixed-images-file.\n\n" - "If fixed images (`-f/--fixed-image`) are provided, these are also taken into account, " - "as described in for the deploy command.") -@kluctl_project_args() -@image_args() -@include_exclude_args() -@misc_arguments(output=True) -@optgroup.option("--simple", help="Output a simplified version of the images list", is_flag=True) -@click.pass_obj -def list_images_command_stub(obj, **kwargs): - from kluctl.cli.commands import list_images_command - list_images_command(obj, kwargs) - -@cli_group.command("list-targets", - help="Outputs a yaml list with all target, including dynamic targets") -@kluctl_project_args() -@misc_arguments(output=True) -@click.pass_obj -def list_targets_stub(obj, **kwargs): - from kluctl.cli.commands import list_targets_command - list_targets_command(obj, kwargs) - -@cli_group.command("archive", - help="Write project and all related components into single tgz.\n\n" - "This archive can then be used with `--from-archive`.") -@kluctl_project_args() -@optgroup.group("Misc arguments") -@optgroup.option("--output-archive", - help="Path to .tgz to write project to.", - required=True, - type=click.Path(file_okay=True)) -@optgroup.option("--output-metadata", - help="Path to .yml to write metadata to. If not specified, metadata is written into the archive.", - type=click.Path(file_okay=True)) -@optgroup.option("--reproducible", - help="Make archive reproducible.", - is_flag=True) -@click.pass_obj -def archive_command_stub(obj, **kwargs): - from kluctl.cli.commands import archive_command - archive_command(obj, kwargs) diff --git a/kluctl/cli/commands.py b/kluctl/cli/commands.py deleted file mode 100644 index f82ff9dc0..000000000 --- a/kluctl/cli/commands.py +++ /dev/null @@ -1,176 +0,0 @@ -import logging -import os -import sys -import tempfile -import time - -import click - -from kluctl import get_kluctl_package_dir -from kluctl.cli.command_result import output_command_result, output_validate_result, output_yaml_result -from kluctl.cli.utils import build_seen_images, project_command_context -from kluctl.kluctl_project.kluctl_project import load_kluctl_project_from_args -from kluctl.utils.dict_utils import get_dict_value -from kluctl.utils.exceptions import CommandError -from kluctl.utils.k8s_object_utils import get_long_object_name_from_ref, ObjectRef -from kluctl.utils.utils import get_tmp_base_dir, duration - -logger = logging.getLogger(__name__) - - -def bootstrap_command(obj, kwargs): - bootstrap_path = os.path.join(get_kluctl_package_dir(), "bootstrap") - - if not kwargs.get("local_deployment"): - kwargs["local_deployment"] = bootstrap_path - with project_command_context(kwargs) as cmd_ctx: - existing, warnings = cmd_ctx.k8s_cluster.get_single_object(ObjectRef("apiextensions.k8s.io/v1", "CustomResourceDefinition", "sealedsecrets.bitnami.com")) - if existing: - if not kwargs["yes"] and get_dict_value(existing, 'metadata.labels."kluctl.io/component"') != "bootstrap": - click.confirm("It looks like you're trying to bootstrap a cluster that already has the sealed-secrets " - "deployed but not managed by kluctl. Do you really want to continue bootstrapping?", - err=True, abort=True) - - deploy_command2(obj, kwargs, cmd_ctx) - prune_command2(obj, kwargs, cmd_ctx) - -def deploy_command(obj, kwargs): - with project_command_context(kwargs) as cmd_ctx: - deploy_command2(obj, kwargs, cmd_ctx) - -def deploy_command2(obj, kwargs, cmd_ctx): - if not kwargs["yes"] and not kwargs["dry_run"]: - click.confirm("Do you really want to deploy to the context/cluster %s?" % cmd_ctx.k8s_cluster.context, err=True, abort=True) - - result = cmd_ctx.deployment_collection.deploy(cmd_ctx.k8s_cluster, kwargs["force_apply"], - kwargs["replace_on_error"], kwargs["force_replace_on_error"], kwargs["abort_on_error"], - kwargs["hook_timeout"]) - output_command_result(kwargs["output"], cmd_ctx.deployment_collection, result) - if result.errors: - sys.exit(1) - -def diff_command(obj, kwargs): - with project_command_context(kwargs) as cmd_ctx: - result = cmd_ctx.deployment_collection.diff( - cmd_ctx.k8s_cluster, kwargs["force_apply"], kwargs["replace_on_error"], kwargs["force_replace_on_error"], - kwargs["ignore_tags"], kwargs["ignore_labels"], kwargs["ignore_annotations"], kwargs["ignore_order"]) - output_command_result(kwargs["output"], cmd_ctx.deployment_collection, result) - sys.exit(1 if result.errors else 0) - -def confirmed_delete_objects(k8s_cluster, objects, kwargs): - from kluctl.utils.k8s_delete_utils import delete_objects - if len(objects) != 0: - click.echo("The following objects will be deleted:", err=True) - for ref in objects: - click.echo(" %s" % get_long_object_name_from_ref(ref), err=True) - if not kwargs["yes"] and not kwargs["dry_run"]: - click.confirm("Do you really want to delete %d objects?" % len(objects), abort=True, err=True) - - result = delete_objects(k8s_cluster, objects, False) - return result - -def delete_command(obj, kwargs): - with project_command_context(kwargs) as cmd_ctx: - objects = cmd_ctx.deployment_collection.find_delete_objects(cmd_ctx.k8s_cluster) - result = confirmed_delete_objects(cmd_ctx.k8s_cluster, objects, kwargs) - output_command_result(kwargs["output"], cmd_ctx.deployment_collection, result) - -def prune_command(obj, kwargs): - with project_command_context(kwargs) as cmd_ctx: - prune_command2(obj, kwargs, cmd_ctx) - -def prune_command2(obj, kwargs, cmd_ctx): - objects = cmd_ctx.deployment_collection.find_orphan_objects(cmd_ctx.k8s_cluster) - result = confirmed_delete_objects(cmd_ctx.k8s_cluster, objects, kwargs) - output_command_result(kwargs["output"], cmd_ctx.deployment_collection, result) - -def poke_images_command(obj, kwargs): - with project_command_context(kwargs) as cmd_ctx: - if not kwargs["yes"] and not kwargs["dry_run"]: - click.confirm("Do you really want to poke images to the context/cluster %s?" % cmd_ctx.k8s_cluster.context, - err=True, abort=True) - result = cmd_ctx.deployment_collection.poke_images(cmd_ctx.k8s_cluster) - output_command_result(kwargs["output"], cmd_ctx.deployment_collection, result) - if result.errors: - sys.exit(1) - -def downscale_command(obj, kwargs): - with project_command_context(kwargs) as cmd_ctx: - if not kwargs["yes"] and not kwargs["dry_run"]: - click.confirm("Do you really want to downscale on context/cluster %s?" % cmd_ctx.k8s_cluster.context, - err=True, abort=True) - result = cmd_ctx.deployment_collection.downscale(cmd_ctx.k8s_cluster) - output_command_result(kwargs["output"], cmd_ctx.deployment_collection, result) - if result.errors: - sys.exit(1) - -def validate_command(obj, kwargs): - wait_duration = duration(kwargs["wait"]) if kwargs["wait"] else None - sleep_duration = duration(kwargs["sleep"]) if kwargs["sleep"] else None - warnings_as_errors = kwargs["warnings_as_errors"] - - start_time = time.time() - - with project_command_context(kwargs) as cmd_ctx: - while True: - result = cmd_ctx.deployment_collection.validate() - failed = len(result.errors) != 0 or (warnings_as_errors and len(result.warnings) != 0) - - output_validate_result(kwargs["output"], result) - - if not failed: - click.echo("Validation succeeded", file=sys.stderr) - return - - if wait_duration is None or time.time() - start_time > wait_duration.total_seconds(): - raise CommandError("Validation failed") - - time.sleep(sleep_duration.total_seconds()) - - # Need to force re-requesting these objects - cmd_ctx.deployment_collection.forget_remote_objects([x.ref for x in result.errors + result.warnings + result.extra_results]) - -def render_command(obj, kwargs): - logger = logging.getLogger('build') - - if kwargs["render_output_dir"] is None: - kwargs["render_output_dir"] = tempfile.mkdtemp(dir=get_tmp_base_dir(), prefix="render-") - - with project_command_context(kwargs) as cmd_ctx: - logger.info('Rendered into: %s' % cmd_ctx.deployment_collection.tmpdir) - - if kwargs["output_images"]: - result = { - "images": build_seen_images(cmd_ctx.deployment_collection, True) - } - - output_yaml_result([kwargs["output_images"]], result) - - if kwargs["output_single_yaml"]: - all_yamls = [] - for d in cmd_ctx.deployment_collection.deployments: - all_yamls += d.objects - output_yaml_result([kwargs["output_single_yaml"]], all_yamls, all=True) - -def list_images_command(obj, kwargs): - with project_command_context(kwargs) as cmd_ctx: - cmd_ctx.images.raise_on_error = False - - result = { - "images": build_seen_images(cmd_ctx.deployment_collection, not kwargs["simple"]) - } - - output_yaml_result(kwargs["output"], result) - -def list_targets_command(obj, kwargs): - with load_kluctl_project_from_args(kwargs) as kluctl_project: - result = { - "involved_repos": kluctl_project.involved_repos, - "targets": kluctl_project.targets, - } - - output_yaml_result(kwargs["output"], result) - -def archive_command(obj, kwargs): - with load_kluctl_project_from_args(kwargs) as kluctl_project: - kluctl_project.create_tgz(kwargs["output_archive"], kwargs["output_metadata"], kwargs["reproducible"]) diff --git a/kluctl/cli/main_cli_group.py b/kluctl/cli/main_cli_group.py deleted file mode 100644 index 932116bcf..000000000 --- a/kluctl/cli/main_cli_group.py +++ /dev/null @@ -1,283 +0,0 @@ -import datetime -import logging -import os.path -import sys -from distutils.version import LooseVersion - -import click -from click_option_group import optgroup - -from kluctl import _version -from kluctl.utils.external_tools import get_external_tool_path -from kluctl.utils.gitlab.gitlab_util import init_gitlab_util_from_glab -from kluctl.utils.utils import get_tmp_base_dir, duration -from kluctl.utils.yaml_utils import yaml_load_file, yaml_save_file - -logger = logging.getLogger(__name__) - -LATEST_RELEASE_URL = "https://api.github.com/repos/codablock/kluctl/releases/latest" -HELP_BASE_URL = "https://github.com/codablock/kluctl/blob/%s/docs" % (("v%s" % _version.__version__) if _version.__version__ != "0.0.0" else "main") - -def build_help_link(md, section=None): - s = "%s/%s" % (HELP_BASE_URL, md) - if section: - s = "%s#%s" % (s, section) - return s - -def setup_logging(verbose): - level = logging.INFO - if verbose: - level = logging.DEBUG - format = '%(asctime)s %(name)-30s %(levelname)-6s %(message)s' - else: - format = '%(asctime)s %(levelname)-6s %(message)s' - - logging.basicConfig(level=level, format=format) - - logging.getLogger('urllib3').setLevel(logging.WARN) - logging.getLogger('urllib3.connectionpool').setLevel(logging.ERROR) - logging.getLogger('filelock').setLevel(logging.WARN) - - # Import filelock to force the bad setLevel call performed internally so that we can then later override the level - # with what we actually want. TODO remove this when https://github.com/tox-dev/py-filelock/issues/104 is resolved. - import filelock - logging.getLogger("filelock").setLevel(logging.WARN) - - -def check_new_version(): - if _version.__version__ == "0.0.0": - # local dev version - return - - version_check_path = os.path.join(get_tmp_base_dir(), "version_check.yml") - try: - y = yaml_load_file(version_check_path) - now = datetime.datetime.utcnow() - check_time = datetime.datetime.fromisoformat(y["last_version_check"]) + datetime.timedelta(hours=1) - if now < check_time: - return - except: - pass - - y = { - "last_version_check": str(datetime.datetime.utcnow()), - } - yaml_save_file(y, version_check_path) - - logger.debug("Checking for new kluctl version") - try: - import requests - r = requests.get(LATEST_RELEASE_URL, timeout=5) - r.raise_for_status() - release = r.json() - latest_version = release["tag_name"] - if latest_version.startswith("v"): - latest_version = latest_version[1:] - except Exception as e: - logger.warning("Failed to query latest kluctl version. %s" % str(e)) - return - local_version = LooseVersion(_version.__version__) - if local_version < latest_version: - logger.warning("You are using an outdated version (%s) of kluctl. You should update soon to version %s", - str(local_version), str(latest_version)) - -def check_external_tools_installed(): - get_external_tool_path("kustomize") - get_external_tool_path("helm") - get_external_tool_path("kubeseal") - -def configure(ctx, param, filename): - if not os.path.exists(filename): - return - config = yaml_load_file(filename) - config = config["config"] - ctx.default_map = config - -@click.group(context_settings={"auto_envvar_prefix": "kluctl", "max_content_width": 200}) -@click.version_option(version=_version.__version__, package_name="kluctl") -@click.option("--verbose", "-v", help="Enable verbose logging", default=False, is_flag=True) -@click.option("--no-update-check", help="Disable update check on startup", default=False, is_flag=True) -@click.option( - '--config', - type=click.Path(dir_okay=False), - default=os.path.join(click.get_app_dir("kluctl", force_posix=True), "config.yml"), - callback=configure, - is_eager=True, - expose_value=False, - help='Read option defaults from the specified config file', - show_default=True -) -@click.pass_context -def cli_group(ctx: click.Context, verbose, no_update_check): - ctx.ensure_object(dict) - obj = ctx.obj - obj["verbose"] = verbose - setup_logging(verbose) - if not no_update_check: - check_new_version() - if "--help" not in sys.argv: - check_external_tools_installed() - init_gitlab_util_from_glab() - -def wrapper_helper(options): - def wrapper(func): - for o in reversed(options): - func = o(func) - return func - return wrapper - -def misc_arguments(yes=False, dry_run=False, force_apply=False, replace_on_error=False, hook_timeout=False, - ignore_labels=False, ignore_order=False, abort_on_error=False, output_format=False, output=False, - render_output_dir=False): - options = [] - - options.append(optgroup.group("Misc arguments")) - if yes: - options.append(optgroup.option("-y", "--yes", - help="Suppresses 'Are you sure?' questions and proceeds as if you would " - "answer 'yes'.", - default=False, is_flag=True)) - if dry_run: - options.append(optgroup.option("--dry-run", - help="Performs all kubernetes API calls in dry-run mode.", - default=False, is_flag=True)) - if force_apply: - options.append(optgroup.option("--force-apply", - help="Force conflict resolution when applying. See documentation for details", - default=False, is_flag=True)) - if replace_on_error: - options.append(optgroup.option("--replace-on-error", - help="When patching an object fails, try to replace it. " - "See documentation for more details.", - default=False, is_flag=True)) - options.append(optgroup.option("--force-replace-on-error", - help="Same as --replace-on-error, but also try to delete and re-create objects. " - "See documentation for more details.", - default=False, is_flag=True)) - if hook_timeout: - options.append(optgroup.option("--hook-timeout", - help="Maximum time to wait for hook readiness. The timeout is meant per-hook. " - "Timeouts are in the duration format (1s, 1m, 1h, ...). If not specified, " - "a default timeout of 5m is used.", - default="5m", type=duration)) - if ignore_labels: - options.append(optgroup.option("--ignore-tags", - help="Ignores changes in tags when diffing", - default=False, is_flag=True)) - options.append(optgroup.option("--ignore-labels", - help="Ignores changes in labels when diffing", - default=False, is_flag=True)) - options.append(optgroup.option("--ignore-annotations", - help="Ignores changes in annotations when diffing", - default=False, is_flag=True)) - if ignore_order: - options.append(optgroup.option("--ignore-order", - help="Ignores changes in order when diffing", - default=False, is_flag=True)) - if abort_on_error: - options.append(optgroup.option("--abort-on-error", - help="Abort deploying when an error occurs instead of trying the remaining " - "deployments", - default=False, is_flag=True)) - if output_format: - options.append(optgroup.option("-o", "--output", - help="Specify output format and target file, in the format 'format=path'. " - "Format can either be 'text' or 'yaml'. Can be specified multiple times. " - "The actual format for yaml is currently not documented and subject to " - "change.", - multiple=True)) - if output: - options.append(optgroup.option("-o", "--output", - help="Specify output target file. Can be specified multiple times", - multiple=True)) - if render_output_dir: - options.append(optgroup.option("--render-output-dir", - help="Specifies the target directory to render the project into. If omitted, a" - "temporary directory is used.", - type=click.Path(file_okay=False))) - - return wrapper_helper(options) - -def kluctl_project_args(with_d=True, with_a=True, with_t=True): - options = [] - options.append(optgroup.group("Project arguments", - help="Define where and how to load the kluctl project and its components from.")) - if with_d: - options.append(optgroup.option("--project-url", "-p", - help="Git url of the kluctl project. If not specified, the current directory " - "will be used instead of a remote Git project")) - options.append(optgroup.option("--project-ref", "-b", - help="Git ref of the kluctl project. Only used when --project-url was given.")) - options.append(optgroup.option("--project-config", "-c", - help="Location of the .kluctl.yml config file. Defaults to $PROJECT/.kluctl.yml", - type=click.Path(dir_okay=False))) - options.append(optgroup.option("--local-clusters", - help="Local clusters directory. Overrides the project from .kluctl.yml", - type=click.Path(file_okay=False))) - options.append(optgroup.option("--local-deployment", - help="Local deployment directory. Overrides the project from .kluctl.yml", - type=click.Path(file_okay=False))) - options.append(optgroup.option("--local-sealed-secrets", - help="Local sealed-secrets directory. Overrides the project from .kluctl.yml", - type=click.Path(file_okay=False))) - options.append(optgroup.option("--from-archive", - help="Load project (.kluctl.yml, cluster, ...) from archive. Given path can " - "either be an archive file or a directory with the extracted contents.", - type=click.Path(dir_okay=True, file_okay=True))) - options.append(optgroup.option("--from-archive-metadata", - help="Specify where to load metadata (targets, ...) from. If not specified, " - "metadata is assumed to be part of the archive.", - type=click.Path(dir_okay=True, file_okay=True))) - options.append(optgroup.option("--cluster", - help="Specify/Override cluster")) - if with_a: - options.append(optgroup.option("-a", "--arg", - help="Template argument in the form name=value", - multiple=True)) - if with_t: - options.append(optgroup.option("-t", "--target", - help="Target name to run command for. Target must exist in .kluctl.yml.")) - return wrapper_helper(options) - -def include_exclude_args(): - options = [] - options.append(optgroup.group("Inclusion/Exclusion arguments", help="Control inclusion/exclusion.")) - options.append(optgroup.option("-I", "--include-tag", - help="Include deployments with given tag.", - multiple=True)) - options.append(optgroup.option("-E", "--exclude-tag", - help="Exclude deployments with given tag. Exclusion has precedence over inclusion, " - "meaning that explicitly excluded deployments will always be excluded even if " - "an inclusion rule would match the same deployment.", - multiple=True)) - options.append(optgroup.option("--include-kustomize-dir", - help="Include kustomize dir. The path must be relative to the root deployment " - "project.", - multiple=True)) - options.append(optgroup.option("--exclude-kustomize-dir", - help="Exclude kustomize dir. The path must be relative to the root deployment " - "project. Exclusion has precedence over inclusion, same as in " - "--exclude-tag", - multiple=True)) - return wrapper_helper(options) - -def image_args(): - options = [] - options.append(optgroup.group("Image arguments")) - options.append(optgroup.option("-F", "--fixed-image", - help="Pin an image to a given version. " - "Expects '--fixed-image=image<:namespace:deployment:container>=result'", - multiple=True)) - options.append(optgroup.option("--fixed-images-file", - help="Use .yml file to pin image versions. " - "See output of list-images sub-command or read the " - "documentation for details about the output format", - required=False, type=click.Path(dir_okay=False))) - options.append(optgroup.option("-u", "--update-images", - help="This causes kluctl to prefer the latest image found in registries, " - "based on the `latest_image` filters provided to 'images.get_image(...)' calls. " - "Use this flag if you want to update to the latest versions/tags of all images. " - "'-u' takes precedence over `--fixed-image/--fixed-images-file`, meaning that " - "the latest images are used even if an older image is given via fixed images.", - default=False, is_flag=True)) - return wrapper_helper(options) diff --git a/kluctl/cli/recursive_click_context.py b/kluctl/cli/recursive_click_context.py deleted file mode 100644 index 4f5ba02ca..000000000 --- a/kluctl/cli/recursive_click_context.py +++ /dev/null @@ -1,11 +0,0 @@ -import click - - -class RecursiveClickContext(click.Context): - def __init__(self, command, parent=None, default_map=None, auto_envvar_prefix=None, *args, **kwargs): - default_map = default_map or (parent.default_map if parent else None) - auto_envvar_prefix = auto_envvar_prefix or (parent.auto_envvar_prefix if parent else None) - super().__init__(command, parent=parent, default_map=default_map, auto_envvar_prefix=auto_envvar_prefix, *args, **kwargs) - -class RecursiveContextGroup(click.Group): - context_class = RecursiveClickContext diff --git a/kluctl/cli/seal_command_stubs.py b/kluctl/cli/seal_command_stubs.py deleted file mode 100644 index 1c4a8b719..000000000 --- a/kluctl/cli/seal_command_stubs.py +++ /dev/null @@ -1,28 +0,0 @@ -import click -from click_option_group import optgroup - -from kluctl.cli.main_cli_group import kluctl_project_args, cli_group - - -@cli_group.command("seal", - help="Seal secrets based on target's sealingConfig.\n\n" - "Loads all secrets from the specified secrets sets from the target's sealingConfig and " - "then renders the target, including all files with the `.sealme` extension. Then runs " - "kubeseal on each `.sealme` file and stores secrets in the directory specified by " - "`--local-sealed-secrets`, using the outputPattern from your deployment project.\n\n" - "If no `--target` is specified, sealing is performed for all targets.") -@kluctl_project_args() -@optgroup.group("Misc arguments") -@optgroup.option("--secrets-dir", - help="Specifies where to find unencrypted secret files. The given directory is NOT meant to be part " - "of your source repository! The given path only matters for secrets of type 'path'. Defaults " - "to the current working directory.", - default='.', type=click.Path(exists=True, file_okay=False)) -@optgroup.option("--force-reseal", - help="Lets kluctl ignore secret hashes found in already sealed secrets and thus forces " - "resealing of those.", - is_flag=True) -@click.pass_obj -def seal_command_stub(obj, **kwargs): - from kluctl.seal.seal_command import seal_command - seal_command(obj, kwargs) diff --git a/kluctl/cli/util_command_stubs.py b/kluctl/cli/util_command_stubs.py deleted file mode 100644 index 0a770298a..000000000 --- a/kluctl/cli/util_command_stubs.py +++ /dev/null @@ -1,51 +0,0 @@ -import click -from click_option_group import optgroup - -from kluctl.cli.main_cli_group import cli_group, misc_arguments, kluctl_project_args - - -@cli_group.command("list-image-tags", - help="Queries the tags for the given images.") -@misc_arguments(output=True) -@optgroup.option("--image", required=True, help="Name of the image. Can be specified multiple times", multiple=True) -def list_image_tags_command_stub(image, output): - from kluctl.cli.util_commands import list_image_tags_command - list_image_tags_command(image, output) - -@cli_group.command("helm-pull", - help="Recursively searches for `helm-chart.yml` files and pulls the specified Helm charts.\n\n" - "The Helm charts are stored under the sub-directory `charts/` next to the " - "`helm-chart.yml`. These Helm charts are meant to be added to version control so that " - "pulling is only needed when really required (e.g. when the chart version changes).") -@optgroup.group("Project arguments") -@optgroup.option("--local-deployment", - help="Local deployment directory. Defaults to current directory", - default=".", - type=click.Path(file_okay=False)) -def helm_pull_command_stub(**kwargs): - from kluctl.cli.util_commands import helm_pull_command - helm_pull_command(kwargs) - -@cli_group.command("helm-update", - help="Recursively searches for `helm-chart.yml` files and checks for new available versions.\n\n" - "Optionally performs the actual upgrade and/or add a commit to version control.") -@optgroup.group("Project arguments") -@optgroup.option("--local-deployment", - help="Local deployment directory. Defaults to current directory", - default=".", - type=click.Path(file_okay=False)) -@optgroup.group("Misc arguments") -@optgroup.option("--upgrade", help="Write new versions into helm-chart.yml and perform helm-pull afterwards", is_flag=True) -@optgroup.option("--commit", help="Create a git commit for every updated chart", is_flag=True) -def helm_update_command_stub(upgrade, commit, **kwargs): - from kluctl.cli.util_commands import helm_update_command - helm_update_command(upgrade, commit, kwargs) - -@cli_group.command("check-image-updates", - help="Render deployment and check if any images have new tags available.\n\n" - "This is based on a best effort approach and might give many false-positives.") -@kluctl_project_args() -@click.pass_obj -def check_image_updates_stub(obj, **kwargs): - from kluctl.cli.util_commands import check_image_updates - check_image_updates(obj, kwargs) \ No newline at end of file diff --git a/kluctl/cli/util_commands.py b/kluctl/cli/util_commands.py deleted file mode 100644 index 6a58fa0c0..000000000 --- a/kluctl/cli/util_commands.py +++ /dev/null @@ -1,158 +0,0 @@ -import itertools -import logging -import os -import re -import sys - -import click -from git import Git - -from kluctl.cli.command_result import output_yaml_result -from kluctl.cli.utils import project_command_context -from kluctl.deployment.helm_chart import HelmChart -from kluctl.image_registries import init_image_registries -from kluctl.utils.k8s_object_utils import get_long_object_name_from_ref -from kluctl.utils.pretty_table import pretty_table -from kluctl.utils.utils import MyThreadPoolExecutor -from kluctl.utils.versions import LooseVersionComparator - -logger = logging.getLogger(__name__) - -def list_images_helper(images): - registries = init_image_registries() - if len(registries) == 0: - logger.error("No registry configured, aborting") - sys.exit(1) - - def do_list(image): - for r in registries: - if r.is_image_from_registry(image): - try: - tags = r.list_tags_for_image(image) - return tags - except Exception as e: - return e - return Exception(f"No registry found for image {image}, aborting") - - with MyThreadPoolExecutor(max_workers=8) as executor: - futures = {} - for image in images: - futures[image] = executor.submit(do_list, image) - result = {} - for image in images: - result[image] = futures[image].result() - return result - -def list_image_tags_command(image, output): - images = set(image) - tags = list_images_helper(images) - - result = { - "images": {} - } - for image in images: - t = tags[image] - if isinstance(t, Exception): - result["images"][image] = { - "error": str(t), - } - else: - result["images"][image] = { - "tags": t, - } - - output_yaml_result(output, result) - -def helm_pull_command(kwargs): - for dirpath, dirnames, filenames in os.walk(kwargs["local_deployment"]): - for fname in filenames: - if fname == 'helm-chart.yml': - path = os.path.join(dirpath, fname) - logger.info("Pulling for %s" % path) - chart = HelmChart(path) - chart.pull() - -def helm_update_command(upgrade, commit, kwargs): - for dirpath, dirnames, filenames in os.walk(kwargs["local_deployment"]): - for fname in filenames: - if fname == 'helm-chart.yml': - path = os.path.join(dirpath, fname) - chart = HelmChart(path) - new_version = chart.check_update() - if new_version is None: - continue - logger.info("Chart %s has new version %s available. Old version is %s." % (path, new_version, chart.conf["chartVersion"])) - - if upgrade: - if chart.conf.get("skipUpdate", False): - logger.info("NOT upgrading chart %s as skipUpdate was set to true" % path) - continue - - old_version = chart.conf["chartVersion"] - chart.conf["chartVersion"] = new_version - chart.save(path) - logger.info("Pulling for %s" % path) - chart.pull() - - if commit: - msg = "Updated helm chart %s from %s to %s" % (dirpath, old_version, new_version) - logger.info("Committing: %s" % msg) - g = Git(working_dir=kwargs["local_deployment"]) - g.add(os.path.join(dirpath, "charts")) - g.add(path) - g.commit("-m", msg, os.path.join(dirpath, "charts"), path) - -def check_image_updates(obj, kwargs): - with project_command_context(kwargs) as cmd_ctx: - cmd_ctx.images.raise_on_error = False - rendered_images = cmd_ctx.deployment_collection.find_rendered_images() - - prefix_pattern = re.compile(r"^([a-zA-Z]+[a-zA-Z-_.]*)") - suffix_pattern = re.compile(r"([-][a-zA-Z]+[a-zA-Z-_.]*)$") - - all_repos = set(itertools.chain.from_iterable(rendered_images.values())) - all_repos = set(x.split(":", 1)[0] for x in all_repos if ":" in x) - tags = list_images_helper(all_repos) - - table = [["OBJECT", "IMAGE", "OLD", "NEW"]] - - for ref, images in rendered_images.items(): - for image in images: - obj_name = get_long_object_name_from_ref(ref) - if ":" not in image: - logger.warning("%s: Ignoring image %s as it doesn't specify a tag'" % (obj_name, image)) - continue - repo, cur_tag = tuple(image.split(":", 1)) - repo_tags = tags[repo] - if isinstance(repo_tags, Exception): - logger.warning("%s: Failed to list tags for %s. %s" % (obj_name, repo, str(repo_tags))) - continue - - prefix = prefix_pattern.match(cur_tag) - suffix = suffix_pattern.search(cur_tag) - prefix = prefix.group(1) if prefix else "" - suffix = suffix.group(1) if suffix else "" - has_dot = "." in cur_tag - def do_filter(x): - if has_dot != ("." in x): - return False - if prefix and not x.startswith(prefix): - return False - if suffix and not x.endswith(suffix): - return False - return True - def do_key(x): - if prefix: - x = x[len(prefix):] - if suffix: - x = x[:-len(suffix)] - return LooseVersionComparator(x) - filtered_tags = [x for x in repo_tags if do_filter(x)] - filtered_tags.sort(key=lambda x: do_key(x)) - latest_tag = filtered_tags[-1] - - if latest_tag != cur_tag: - table.append([obj_name, repo, cur_tag, latest_tag]) - - table_txt = pretty_table(table, [60]) - click.echo(table_txt) diff --git a/kluctl/cli/utils.py b/kluctl/cli/utils.py deleted file mode 100644 index 08f33abce..000000000 --- a/kluctl/cli/utils.py +++ /dev/null @@ -1,167 +0,0 @@ -import contextlib -import dataclasses -import os -import tempfile -from typing import ContextManager - -from kluctl.kluctl_project.kluctl_project import load_kluctl_project_from_args, KluctlProject -from kluctl.utils.dict_utils import merge_dict, get_dict_value -from kluctl.utils.exceptions import CommandError -from kluctl.deployment.deployment_collection import DeploymentCollection -from kluctl.deployment.deployment_project import DeploymentProject -from kluctl.image_registries import init_image_registries -from kluctl.deployment.images import Images -from kluctl.utils.external_args import parse_args -from kluctl.utils.inclusion import Inclusion -from kluctl.utils.k8s_cluster_base import load_cluster_config, k8s_cluster_base, load_cluster -from kluctl.utils.utils import get_tmp_base_dir -from kluctl.utils.yaml_utils import yaml_load_file - - -def build_jinja_vars(cluster_vars): - jinja_vars = { - 'cluster': cluster_vars, - } - - return jinja_vars - -def build_deploy_images(kwargs): - image_registries = init_image_registries() - images = Images(image_registries) - images.update_images = kwargs.get("update_images", False) - return images - -def build_fixed_image_entry_from_arg(arg): - s = arg.split('=') - if len(s) != 2: - raise CommandError("--fixed-image expects 'image<:namespace:deployment:container>=result'") - image = s[0] - result = s[1] - - s = image.split(":") - e = { - "image": s[0], - "resultImage": result, - } - if len(s) >= 2: - e["namespace"] = s[1] - if len(s) >= 3: - e["deployment"] = s[2] - if len(s) >= 4: - e["container"] = s[3] - if len(s) >= 5: - raise CommandError("--fixed-image expects 'image<:namespace:deployment:container>=result'") - return e - -def load_fixed_images(kwargs): - ret = [] - if kwargs.get("fixed_images_file"): - y = yaml_load_file(kwargs["fixed_images_file"]) - ret += y.get("images", []) - - for fi in kwargs.get("fixed_image", []): - e = build_fixed_image_entry_from_arg(fi) - ret.append(e) - return ret - - -def parse_inclusion(kwargs): - inclusion = Inclusion() - for tag in kwargs.get("include_tag", []): - inclusion.add_include("tag", tag) - for tag in kwargs.get("exclude_tag", []): - inclusion.add_exclude("tag", tag) - for dir in kwargs.get("include_kustomize_dir", []): - if os.path.isabs(dir): - raise CommandError("--include-kustomize-dir path must be relative") - inclusion.add_include("kustomize_dir", dir.replace(os.path.sep, "/")) - for dir in kwargs.get("exclude_kustomize_dir", []): - if os.path.isabs(dir): - raise CommandError("--exclude-kustomize-dir path must be relative") - inclusion.add_exclude("kustomize_dir", dir.replace(os.path.sep, "/")) - return inclusion - -@dataclasses.dataclass -class CommandContext: - kluctl_project: KluctlProject - target: dict - cluster_vars: dict - k8s_cluster: k8s_cluster_base - deployment: DeploymentProject - deployment_collection: DeploymentCollection - images: Images - -@contextlib.contextmanager -def project_command_context(kwargs) -> ContextManager[CommandContext]: - with load_kluctl_project_from_args(kwargs) as kluctl_project: - target = None - if kwargs["target"]: - target = kluctl_project.find_target(kwargs["target"]) - - with project_target_command_context(kwargs, kluctl_project, target) as cmd_ctx: - yield cmd_ctx - -@contextlib.contextmanager -def project_target_command_context(kwargs, kluctl_project, target, for_seal=False) -> ContextManager[CommandContext]: - - cluster_name = kwargs["cluster"] - if not cluster_name: - if not target: - raise CommandError("You must specify an existing --cluster when not providing a --target") - cluster_name = target["cluster"] - - cluster_vars = load_cluster_config(kluctl_project.clusters_dir, cluster_name) - k8s_cluster = load_cluster(cluster_vars, dry_run=kwargs.get("dry_run", True)) - - jinja_vars = build_jinja_vars(cluster_vars) - images = build_deploy_images(kwargs) - inclusion = parse_inclusion(kwargs) - - option_args = parse_args(kwargs.get("arg", [])) - if target is not None: - for arg_name, arg_value in option_args.items(): - kluctl_project.check_dynamic_arg(target, arg_name, arg_value) - - target_args = target.get("args", {}) if target else {} - seal_args = get_dict_value(target, "sealingConfig.args", {}) if target else {} - deploy_args = merge_dict(target_args, option_args) - if for_seal: - merge_dict(deploy_args, seal_args, False) - - with tempfile.TemporaryDirectory(dir=get_tmp_base_dir()) as tmpdir: - render_output_dir = kwargs.get("render_output_dir") - if render_output_dir is None: - render_output_dir = tmpdir - project_dir = os.path.realpath(kluctl_project.deployment_dir) - d = DeploymentProject(k8s_cluster, project_dir, jinja_vars, deploy_args, kluctl_project.sealed_secrets_dir) - c = DeploymentCollection(d, images=images, inclusion=inclusion, tmpdir=render_output_dir, for_seal=for_seal) - - fixed_images = load_fixed_images(kwargs) - if target is not None: - for fi in target.get("images", []): - c.images.add_fixed_image(fi) - for fi in fixed_images: - c.images.add_fixed_image(fi) - - if not for_seal: - c.prepare(k8s_cluster) - - ctx = CommandContext(kluctl_project=kluctl_project, target=target, - cluster_vars=cluster_vars, k8s_cluster=k8s_cluster, - deployment=d, deployment_collection=c, images=images) - yield ctx - - -def build_seen_images(c, detailed): - ret = [] - for e in c.images.seen_images: - if detailed: - a = e - else: - a = { - "image": e["image"], - "resultImage": e["resultImage"] - } - ret.append(a) - ret.sort(key=lambda x: x["image"]) - return ret diff --git a/kluctl/deployment/__init__.py b/kluctl/deployment/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/kluctl/deployment/apply_util.py b/kluctl/deployment/apply_util.py deleted file mode 100644 index 4dc54ea6d..000000000 --- a/kluctl/deployment/apply_util.py +++ /dev/null @@ -1,245 +0,0 @@ -import json -import logging -import threading -import time -from datetime import datetime - -from kubernetes.client import ApiException -from kubernetes.dynamic.exceptions import ResourceNotFoundError, ConflictError - -from kluctl.deployment.hooks_util import HooksUtil -from kluctl.diff.managed_fields import resolve_field_manager_conflicts -from kluctl.utils.dict_utils import get_dict_value, set_dict_value -from kluctl.utils.k8s_object_utils import get_object_ref, get_long_object_name, get_long_object_name_from_ref -from kluctl.utils.k8s_status_validation import validate_object -from kluctl.utils.utils import MyThreadPoolExecutor - -logger = logging.getLogger(__name__) - -class ApplyUtil: - def __init__(self, deployment_collection, k8s_cluster, force_apply, replace_on_error, force_replace_on_error, dry_run, abort_on_error, hook_timeout): - self.deployment_collection = deployment_collection - self.k8s_cluster = k8s_cluster - self.force_apply = force_apply - self.replace_on_error = replace_on_error or force_replace_on_error - self.force_replace_on_error = force_replace_on_error - self.dry_run = dry_run - self.abort_on_error = abort_on_error - self.hook_timeout = hook_timeout - - self.applied_objects = {} - self.applied_hook_objects = {} - self.abort_signal = False - self.error_refs = {} - self.mutex = threading.Lock() - - def handle_result(self, applied_object, patch_warnings, hook): - with self.mutex: - ref = get_object_ref(applied_object) - if hook: - self.applied_hook_objects[ref] = applied_object - else: - self.applied_objects[ref] = applied_object - self.deployment_collection.add_warnings(ref, patch_warnings) - - def handle_error(self, ref, error): - with self.mutex: - self.error_refs[ref] = error - if self.abort_on_error: - self.abort_signal = True - self.deployment_collection.add_error(ref, error) - - def had_error(self, ref): - with self.mutex: - return ref in self.error_refs - - def delete_object(self, ref): - try: - self.k8s_cluster.delete_single_object(ref, force_dry_run=self.dry_run, ignore_not_found=True) - except ResourceNotFoundError: - pass - - def apply_object(self, x, replaced, hook): - logger.debug(f" {get_long_object_name(x)}") - - x = self.k8s_cluster.fix_object_for_patch(x) - - ref = get_object_ref(x) - remote_object = self.deployment_collection.remote_objects.get(ref) - - if self.dry_run and replaced and get_object_ref(x) in self.deployment_collection.remote_objects: - # Let's simulate that this object was deleted in dry-run mode. If we'd actually try a dry-run apply with - # this object, it might fail as it is expected to not exist. - self.handle_result(x, [], hook) - return - - def retry_with_force_replace(e): - if not self.force_replace_on_error: - self.handle_error(ref, self.k8s_cluster.get_status_message(e)) - return - - logger.warning("Patching %s failed, retrying by deleting and re-applying" % - get_long_object_name_from_ref(ref)) - try: - self.k8s_cluster.delete_single_object(ref, force_dry_run=self.dry_run, ignore_not_found=True) - if not self.dry_run: - r, patch_warnings = self.k8s_cluster.patch_object(x, force_apply=True) - self.handle_result(r, patch_warnings, hook) - else: - self.handle_result(x, [], hook) - except ApiException as e2: - self.handle_error(ref, self.k8s_cluster.get_status_message(e2)) - - def retry_with_replace(e): - if not self.replace_on_error or remote_object is None: - self.handle_error(ref, self.k8s_cluster.get_status_message(e)) - return - - logger.warning("Patching %s failed, retrying with replace instead of patch" % - get_long_object_name_from_ref(ref)) - - resource_version = get_dict_value(remote_object, "metadata.resourceVersion") - x2 = set_dict_value(x, "metadata.resourceVersion", resource_version, do_clone=True) - - try: - r, patch_warnings = self.k8s_cluster.replace_object(x2, force_dry_run=self.dry_run) - self.handle_result(r, patch_warnings, hook) - except ApiException as e2: - retry_with_force_replace(e2) - - def retry_with_conflicts(e): - if remote_object is None: - self.handle_error(ref, self.k8s_cluster.get_status_message(e)) - return - - resolve_warnings = [] - if not self.force_apply: - status = json.loads(e.body) - - try: - x2, lost_ownership = resolve_field_manager_conflicts(x, remote_object, status) - for cause in lost_ownership: - resolve_warnings.append("%s. Not updating field '%s' as we lost field ownership." % (cause["message"], cause["field"])) - except Exception as e2: - self.handle_error(ref, self.k8s_cluster.get_status_message(e2)) - return - else: - x2 = x - - try: - r, patch_warnings = self.k8s_cluster.patch_object(x2, force_dry_run=self.dry_run, force_apply=True) - self.handle_result(r, patch_warnings + resolve_warnings, hook) - except ApiException as e: - # We didn't manage to solve it, better to abort (and not retry with replace!) - self.handle_error(ref, self.k8s_cluster.get_status_message(e)) - - try: - r, patch_warnings = self.k8s_cluster.patch_object(x, force_dry_run=self.dry_run) - self.handle_result(r, patch_warnings, hook) - except ResourceNotFoundError as e: - self.handle_error(ref, self.k8s_cluster.get_status_message(e)) - except ConflictError as e: - retry_with_conflicts(e) - except ApiException as e: - retry_with_replace(e) - - def wait_hook(self, ref): - if self.dry_run: - return True - - did_log = False - logger.debug("Waiting for hook %s to get ready" % get_long_object_name_from_ref(ref)) - start_time = datetime.now() - while True: - o, _ = self.k8s_cluster.get_single_object(ref) - if o is None: - if did_log: - logger.warning("Cancelled waiting for hook %s as it disappeared while waiting for it" % get_long_object_name_from_ref(ref)) - self.handle_error(ref, "Object disappeared while waiting for it to become ready") - return False - v = validate_object(o, False) - if v.ready: - if did_log: - logger.info("Finished waiting for hook %s" % get_long_object_name_from_ref(ref)) - return True - if v.errors: - if did_log: - logger.warning("Cancelled waiting for hook %s due to errors" % get_long_object_name_from_ref(ref)) - for e in v.errors: - self.handle_error(ref, e.message) - return False - - if self.hook_timeout is not None and datetime.now() - start_time >= self.hook_timeout: - err = "Timed out while waiting for hook %s" % get_long_object_name_from_ref(ref) - logger.warning(err) - self.handle_error(ref, err) - return False - - if not did_log: - logger.info("Waiting for hook %s to get ready..." % get_long_object_name_from_ref(ref)) - did_log = True - - time.sleep(0.5) - - def apply_kustomize_deployment(self, d): - if "path" not in d.config: - return - include = d.check_inclusion_for_deploy() - if not include: - self.do_log(d, logging.INFO, "Skipping") - return - - inital_deploy = True - for o in d.objects: - if get_object_ref(o) in self.deployment_collection.remote_objects: - inital_deploy = False - break - - hook_util = HooksUtil(self) - - if inital_deploy: - hook_util.run_hooks(d, ["pre-deploy-initial", "pre-deploy"]) - else: - hook_util.run_hooks(d, ["pre-deploy-upgrade", "pre-deploy"]) - - apply_objects = [] - for o in d.objects: - if hook_util.get_hook(o) is not None: - continue - apply_objects.append(o) - self.do_log(d, logging.INFO, "Applying %d objects" % len(d.objects)) - for o in apply_objects: - self.apply_object(o, False, False) - - if inital_deploy: - hook_util.run_hooks(d, ["post-deploy-initial", "post-deploy"]) - else: - hook_util.run_hooks(d, ["post-deploy-upgrade", "post-deploy"]) - - def apply_deployments(self): - logger.info("Running server-side apply for all objects%s", self.k8s_cluster.get_dry_run_suffix(self.dry_run)) - - futures = [] - - def finish_futures(): - for f in futures: - f.result() - - with MyThreadPoolExecutor(max_workers=16) as executor: - previous_was_barrier = False - for d in self.deployment_collection.deployments: - if self.abort_signal: - break - - if previous_was_barrier: - logger.info("Waiting on barrier...") - finish_futures() - previous_was_barrier = get_dict_value(d.config, "barrier", False) - - f = executor.submit(self.apply_kustomize_deployment, d) - futures.append(f) - - finish_futures() - - def do_log(self, d, level, str): - logger.log(level, "%s: %s" % (d.get_rel_kustomize_dir(), str)) diff --git a/kluctl/deployment/deployment_collection.py b/kluctl/deployment/deployment_collection.py deleted file mode 100644 index de753abd4..000000000 --- a/kluctl/deployment/deployment_collection.py +++ /dev/null @@ -1,413 +0,0 @@ -import dataclasses -import functools -import itertools -import logging -import os - -from kubernetes.dynamic.exceptions import ResourceNotFoundError, ConflictError - -from kluctl.deployment.apply_util import ApplyUtil -from kluctl.deployment.kustomize_deployment import KustomizeDeployment -from kluctl.diff.k8s_diff import deep_diff_object -from kluctl.diff.normalize import normalize_object -from kluctl.utils.dict_utils import copy_dict, get_dict_value -from kluctl.utils.exceptions import CommandError -from kluctl.utils.k8s_delete_utils import find_objects_for_delete -from kluctl.utils.k8s_downscale_utils import downscale_object, downsclae_is_delete -from kluctl.utils.k8s_object_utils import get_object_ref, get_long_object_name, \ - ObjectRef -from kluctl.utils.k8s_status_validation import validate_object, ValidateResult, ValidateResultItem -from kluctl.utils.templated_dir import TemplatedDir -from kluctl.utils.utils import MyThreadPoolExecutor - -logger = logging.getLogger(__name__) - -@dataclasses.dataclass(frozen=True, eq=True) -class DeployErrorItem: - ref: ObjectRef - message: str - -@dataclasses.dataclass -class CommandResult: - new_objects: list - changed_objects: list - deleted_objects: list - hook_objects: list - orphan_objects: list - errors: list - warnings: list - -class DeploymentCollection: - def __init__(self, project, images, inclusion, tmpdir, for_seal): - self.project = project - self.images = images - self.inclusion = inclusion - self.tmpdir = tmpdir - self.for_seal = for_seal - self.deployments = self._collect_deployments(self.project, {}) - - self.remote_objects = {} - - self.errors = set() - self.warnings = set() - - def _collect_deployments(self, project, indexes): - ret = [] - - for c in project.conf['kustomizeDirs']: - dir = None - index = 0 - if "path" in c: - dir = os.path.join(project.dir, c["path"]) - dir = os.path.realpath(dir) - if not dir.startswith(project.get_root_deployment().dir): - raise CommandError("kustomizeDir path is not part of root deployment project: %s" % c["path"]) - index = indexes.setdefault(dir, 0) - indexes[dir] += 1 - deployment = KustomizeDeployment(project, self, c, dir, index) - ret.append(deployment) - - for inc in project.conf['includes']: - if get_dict_value(inc, "barrier", False): - deployment = KustomizeDeployment(project, self, {"barrier": True}, None, 0) - ret.append(deployment) - - d = inc.get('_included_deployment_collection') - if d is not None: - ret += self._collect_deployments(d, indexes) - - return ret - - def render_deployments(self, k8s_cluster): - logger.info("Rendering templates and Helm charts") - with MyThreadPoolExecutor(max_workers=16) as executor: - jobs = [] - - for d in self.deployments: - jobs += d.render(k8s_cluster, executor) - - TemplatedDir.finish_jobs(jobs) - - jobs = [] - for d in self.deployments: - jobs += d.render_helm_charts(k8s_cluster, executor) - for job in jobs: - job.result() - - def resolve_sealed_secrets(self): - if self.for_seal: - return - - for d in self.deployments: - d.resolve_sealed_secrets() - - def build_kustomize_objects(self, k8s_cluster): - logger.info("Building kustomize objects") - - with MyThreadPoolExecutor() as executor: - jobs = [] - for d in self.deployments: - jobs.append((d, executor.submit(d.build_kustomize))) - for d, job in jobs: - try: - job.result() - except Exception as e: - logger.error("Building kustomize objects for %s failed. %s" % (d.dir, str(e))) - raise - jobs.clear() - - for d in self.deployments: - jobs.append((d, executor.submit(d.postprocess_and_load_objects, k8s_cluster))) - for d, job in jobs: - try: - job.result() - except Exception as e: - logger.error("Postprocessing kustomize objects for %s failed. %s" % (d.dir, str(e))) - raise - - def update_remote_objects(self, k8s_cluster): - if k8s_cluster is None: - return - - logger.info("Updating remote objects") - refs = set() - for ref in self.local_objects_by_ref().keys(): - if ref not in self.remote_objects: - refs.add(ref) - r = k8s_cluster.get_objects_by_object_refs(refs) - for o, w in r: - self.remote_objects[get_object_ref(o)] = o - self.add_warnings(get_object_ref(o), w) - - def forget_remote_objects(self, refs): - for ref in refs: - self.remote_objects.pop(ref, None) - - def local_objects_by_ref(self): - flat = list(itertools.chain(*[d.objects for d in self.deployments])) - by_ref = dict((get_object_ref(x), x) for x in flat) - return by_ref - - def prepare(self, k8s_cluster): - self.render_deployments(k8s_cluster) - self.resolve_sealed_secrets() - self.build_kustomize_objects(k8s_cluster) - self.update_remote_objects(k8s_cluster) - - def deploy(self, k8s_cluster, force_apply, replace_on_error, force_replace_on_error, abort_on_error, hook_timeout): - self.clear_errors_and_warnings() - applied_objects, applied_hook_objects = self.do_apply(k8s_cluster, - force_apply, replace_on_error, force_replace_on_error, - False, abort_on_error, hook_timeout) - new_objects, changed_objects = self.do_diff(k8s_cluster, applied_objects, False, False, False, False) - orphan_objects = self.find_orphan_objects(k8s_cluster) - applied_hook_objects = list(applied_hook_objects.values()) - return CommandResult(new_objects=new_objects, changed_objects=changed_objects, deleted_objects=[], - hook_objects=applied_hook_objects, orphan_objects=orphan_objects, - errors=list(self.errors), warnings=list(self.warnings)) - - def diff(self, k8s_cluster, force_apply, replace_on_error, force_replace_on_error, ignore_tags, ignore_labels, ignore_annotations, ignore_order): - self.clear_errors_and_warnings() - applied_objects, applied_hook_objects = self.do_apply(k8s_cluster, - force_apply, replace_on_error, force_replace_on_error, - True, False, None) - new_objects, changed_objects = self.do_diff(k8s_cluster, applied_objects, ignore_tags, ignore_labels, ignore_annotations, ignore_order) - orphan_objects = self.find_orphan_objects(k8s_cluster) - applied_hook_objects = list(applied_hook_objects.values()) - return CommandResult(new_objects=new_objects, changed_objects=changed_objects, deleted_objects=[], - hook_objects=applied_hook_objects, orphan_objects=orphan_objects, - errors=list(self.errors), warnings=list(self.warnings)) - - def poke_images(self, k8s_cluster): - self.clear_errors_and_warnings() - def do_poke_image(containers_and_images, o): - o = copy_dict(o) - containers = get_dict_value(o, "spec.template.spec.containers", []) - for container_name, image in containers_and_images: - for c in containers: - if c["name"] == container_name: - c["image"] = image - return o - - all_objects = {} - for d in self.deployments: - if not d.check_inclusion_for_deploy(): - continue - for o in d.objects: - all_objects[get_object_ref(o)] = o - - containers_and_images = {} - for fi in self.images.seen_images: - deployment_name = fi["deployment"] - if "/" not in deployment_name: - deployment_name = "Deployment/%s" % deployment_name - - kind, name = tuple(deployment_name.split("/", 1)) - try: - r = k8s_cluster.get_preferred_resource(None, kind) - except ResourceNotFoundError as e: - self.add_error(None, k8s_cluster.get_status_message(e)) - continue - - ref = ObjectRef(r.group_version, kind=r.kind, name=name, namespace=fi.get("namespace")) - local_object = all_objects.get(ref) - if local_object is None: - self.add_error(ref, "object not found while trying to associate image with deployed object") - continue - - containers_and_images.setdefault(ref, []).append((fi["container"], fi["resultImage"])) - - with MyThreadPoolExecutor(max_workers=8) as executor: - futures = [] - - for ref, c in containers_and_images.items(): - f = executor.submit(self.do_replace_object, k8s_cluster, ref, functools.partial(do_poke_image, c)) - futures.append((ref, f)) - - applied_objects = self.do_finish_futures(futures) - - new_objects, changed_objects = self.do_diff(k8s_cluster, applied_objects, False, False, False, False) - orphan_objects = self.find_orphan_objects(k8s_cluster) - return CommandResult(new_objects=new_objects, changed_objects=changed_objects, deleted_objects=[], - hook_objects=[], orphan_objects=orphan_objects, - errors=list(self.errors), warnings=list(self.warnings)) - - def downscale(self, k8s_cluster): - self.clear_errors_and_warnings() - with MyThreadPoolExecutor(max_workers=8) as executor: - replace_futures = [] - delete_futures = [] - for d in self.deployments: - if not d.check_inclusion_for_deploy(): - continue - for o in d.objects: - ref = get_object_ref(o) - if downsclae_is_delete(o): - f = executor.submit(k8s_cluster.delete_single_object, ref, ignore_not_found=True) - delete_futures.append((ref, f)) - else: - f = executor.submit(self.do_replace_object, k8s_cluster, ref, lambda x, o=o: downscale_object(x, o)) - replace_futures.append((ref, f)) - - applied_objects = self.do_finish_futures(replace_futures) - deleted_objects = self.do_finish_futures(delete_futures) - - new_objects, changed_objects = self.do_diff(k8s_cluster, applied_objects, False, False, False, False) - orphan_objects = self.find_orphan_objects(k8s_cluster) - return CommandResult(new_objects=new_objects, changed_objects=changed_objects, deleted_objects=list(deleted_objects.keys()), - hook_objects=[], orphan_objects=orphan_objects, - errors=list(self.errors), warnings=list(self.warnings)) - - def validate(self): - self.clear_errors_and_warnings() - result = ValidateResult() - - for w in self.warnings: - result.warnings.append(ValidateResultItem(ref=w.ref, reason="api", message=w.message)) - for e in self.errors: - result.errors.append(ValidateResultItem(ref=e.ref, reason="api", message=e.message)) - - for d in self.deployments: - if not d.check_inclusion_for_deploy(): - continue - for o in d.objects: - ref = get_object_ref(o) - remote_object = self.remote_objects.get(ref) - if not remote_object: - result.errors.append(ValidateResultItem(ref=ref, reason="not-found", message="Object not found")) - continue - r = validate_object(remote_object, True) - result.errors += r.errors - result.warnings += r.warnings - result.results += r.results - return result - - def find_delete_objects(self, k8s_cluster): - labels = self.project.get_delete_by_labels() - return find_objects_for_delete(k8s_cluster, labels, self.inclusion, []) - - def find_orphan_objects(self, k8s_cluster): - logger.info("Searching for orphan objects") - labels = self.project.get_delete_by_labels() - excluded_objects = list(self.local_objects_by_ref().keys()) - return find_objects_for_delete(k8s_cluster, labels, self.inclusion, excluded_objects) - - def do_apply(self, k8s_cluster, force_apply, replace_on_error, force_replace_on_error, dry_run, abort_on_error, hook_timeout): - if k8s_cluster.dry_run: - dry_run = True - apply_util = ApplyUtil(self, k8s_cluster, force_apply, replace_on_error, force_replace_on_error, dry_run, abort_on_error, hook_timeout) - apply_util.apply_deployments() - return apply_util.applied_objects, apply_util.applied_hook_objects - - def do_diff(self, k8s_cluster, applied_objects, ignore_tags, ignore_labels, ignore_annotations, ignore_order): - diff_objects = {} - normalized_diff_objects = {} - normalized_remote_objects = {} - for d in self.deployments: - if not d.check_inclusion_for_deploy(): - continue - - ignore_for_diffs = d.deployment_project.get_ignore_for_diffs(ignore_tags, ignore_labels, ignore_annotations) - for x in d.objects: - ref = get_object_ref(x) - if ref not in applied_objects: - continue - diff_objects[ref] = applied_objects.get(ref) - normalized_diff_objects[ref] = normalize_object(k8s_cluster, diff_objects[ref], ignore_for_diffs, diff_objects[ref]) - if ref in self.remote_objects: - normalized_remote_objects[ref] = normalize_object(k8s_cluster, self.remote_objects[ref], ignore_for_diffs, diff_objects[ref]) - - logger.info("Diffing remote/old objects against applied/new objects") - new_objects = [] - changed_objects = [] - with MyThreadPoolExecutor() as executor: - futures = {} - for ref, o in diff_objects.items(): - normalized_remote_object = normalized_remote_objects.get(ref) - if normalized_remote_object is None: - new_objects.append(o) - continue - normalized_diff_object = normalized_diff_objects[ref] - futures[ref] = o, executor.submit(do_diff_object, - normalized_remote_object, normalized_diff_object, ignore_order) - - for ref, (o, f) in futures.items(): - changes = f.result() - if not changes: - continue - changed_objects.append({ - "new_object": o, - "old_object": self.remote_objects[ref], - "changes": changes - }) - - return new_objects, changed_objects - - def do_replace_object(self, k8s_cluster, ref, callback): - first_call = True - while True: - if first_call: - o = self.remote_objects.get(ref) - else: - o, warnings = k8s_cluster.get_single_object(ref) - if o is None: - return None - first_call = False - - o2 = callback(o) - if o == o2: - return o - - try: - result, warnings = k8s_cluster.replace_object(o2) - return result - except ConflictError: - logger.info("Conflict while patching %s. Retrying..." % get_long_object_name(o2)) - continue - - def do_finish_futures(self, futures): - ret = {} - for ref, f in futures: - e = f.exception() - if e is not None: - self.add_error(ref, str(e)) - continue - - o = f.result() - if o is None: - continue - ref = get_object_ref(o) - ret[ref] = o - return ret - - def find_rendered_images(self): - ret = {} - for d in self.deployments: - for o in d.objects: - containers = o.get('spec', {}).get('template', {}).get('spec', {}).get('containers', []) - for c in containers: - image = c.get("image") - if not image: - continue - ret.setdefault(get_object_ref(o), []).append(image) - return ret - - def add_warnings(self, ref, warnings): - for w in warnings: - item = DeployErrorItem(ref=ref, message=w) - if item not in self.warnings: - self.warnings.add(item) - - def add_error(self, ref, error): - item = DeployErrorItem(ref=ref, message=error) - if item not in self.errors: - self.errors.add(item) - - def clear_errors_and_warnings(self): - self.errors = set() - self.warnings = set() - -def do_diff_object(old_object, new_object, ignore_order): - if old_object == new_object: - return [] - return deep_diff_object(old_object, new_object, ignore_order) diff --git a/kluctl/deployment/deployment_project.py b/kluctl/deployment/deployment_project.py deleted file mode 100644 index 213456e7a..000000000 --- a/kluctl/deployment/deployment_project.py +++ /dev/null @@ -1,243 +0,0 @@ -import logging -import os - -from jinja2 import StrictUndefined, FileSystemLoader -from ordered_set import OrderedSet - -from kluctl.utils.dict_utils import merge_dict, copy_dict, \ - set_dict_default_value, get_dict_value -from kluctl.utils.exceptions import CommandError -from kluctl.utils.external_args import check_required_args -from kluctl.utils.jinja2_cache import KluctlBytecodeCache -from kluctl.utils.jinja2_utils import add_jinja2_filters, KluctlJinja2Environment -from kluctl.utils.k8s_object_utils import ObjectRef, get_long_object_name_from_ref -from kluctl.utils.yaml_utils import yaml_load, yaml_load_file - -logger = logging.getLogger(__name__) - - -class DeploymentProject(object): - def __init__(self, k8s_cluster, dir, jinja_vars, deploy_args, sealed_secrets_dir, - parent_collection=None): - if not os.path.exists(dir): - raise CommandError("%s does not exist" % dir) - - self.jinja2_cache = KluctlBytecodeCache(max_cache_files=10000) - - self.dir = dir - self.jinja_vars = copy_dict(jinja_vars) - self.deploy_args = deploy_args - self.sealed_secrets_dir = sealed_secrets_dir - self.jinja_vars['args'] = self.deploy_args - self.includes = [] - self.parent_collection = parent_collection - self.parent_collection_include = None - self.check_required_args() - self.jinja_vars['args'] = self.deploy_args # need to update 'args' after applying default values in check_required_args - self.load_base_conf(k8s_cluster) - self.load_includes(k8s_cluster) - - def fix_relative_paths(self, y, key, abs_path): - for x in y: - if key in x: - x[key] = os.path.join(abs_path, x[key]) - - def build_jinja2_env(self, jinja_vars): - dirs = [] - for d, _ in self.get_parents(): - dirs.append(d.dir) - environment = KluctlJinja2Environment(loader=FileSystemLoader(dirs), undefined=StrictUndefined, - cache_size=10000, - bytecode_cache=self.jinja2_cache, auto_reload=False) - merge_dict(environment.globals, jinja_vars, clone=False) - add_jinja2_filters(environment) - return environment - - def load_jinja_vars_list(self, k8s_cluster, vars_list, cur_jinja_vars): - jinja_vars = copy_dict(cur_jinja_vars) - for v in vars_list: - if "values" in v: - merge_dict(jinja_vars, v["values"], False) - elif "file" in v: - jinja_vars = self.load_jinja_vars_file(v["file"], jinja_vars) - elif "clusterConfigMap" in v: - ref = ObjectRef(api_version="v1", kind="ConfigMap", name=v["clusterConfigMap"]["name"], namespace=v["clusterConfigMap"]["namespace"]) - jinja_vars = self.load_jinja_vars_k8s_object(k8s_cluster, ref, v["clusterConfigMap"]["key"], jinja_vars) - elif "clusterSecret" in v: - ref = ObjectRef(api_version="v1", kind="Secret", name=v["clusterSecret"]["name"], namespace=v["clusterSecret"]["namespace"]) - jinja_vars = self.load_jinja_vars_k8s_object(k8s_cluster, ref, v["clusterSecret"]["key"], jinja_vars) - else: - raise CommandError("Invalid vars entry") - return jinja_vars - - def load_jinja_vars_file(self, rel_file, cur_jinja_vars): - new_vars = self.load_rendered_yaml(rel_file, cur_jinja_vars) - jinja_vars = merge_dict(cur_jinja_vars, new_vars) - return jinja_vars - - def load_jinja_vars_k8s_object(self, k8s_cluster, ref, key, cur_jinja_vars): - o, _ = k8s_cluster.get_single_object(ref) - if o is None: - raise CommandError("%s not found on cluster %s" % (get_long_object_name_from_ref(ref), k8s_cluster.context)) - value = get_dict_value(o, "data.%s" % key) - if value is None: - raise CommandError("Key %s not found in %s on cluster %s" % (key, get_long_object_name_from_ref(ref), k8s_cluster.context)) - jinja_env = self.build_jinja2_env(cur_jinja_vars) - template = jinja_env.from_string(value) - new_vars = yaml_load(template.render()) - jinja_vars = merge_dict(cur_jinja_vars, new_vars) - return jinja_vars - - def load_rendered_yaml(self, rel_path, jinja_vars): - jinja_env = self.build_jinja2_env(jinja_vars) - template = jinja_env.get_template(rel_path.replace('\\', '/')) - rendered = template.render() - return yaml_load(rendered) - - def load_base_conf(self, k8s_cluster): - base_conf_path = os.path.join(self.dir, 'deployment.yml') - if not os.path.exists(base_conf_path): - if os.path.exists(os.path.join(self.dir, 'kustomization.yml')): - error_text = "deployment.yml not found but folder %s contains kustomization.yml. Is it a kustomizeDir?" % self.dir - else: - error_text = "%s not found" % base_conf_path - raise CommandError(error_text) - - self.conf = self.load_rendered_yaml('deployment.yml', self.jinja_vars) - if self.conf is None: - self.conf = {} - - set_dict_default_value(self.conf, 'vars', []) - set_dict_default_value(self.conf, 'kustomizeDirs', []) - set_dict_default_value(self.conf, 'includes', []) - set_dict_default_value(self.conf, 'commonLabels', {}) - set_dict_default_value(self.conf, 'deleteByLabels', {}) - set_dict_default_value(self.conf, 'overrideNamespace', None) - set_dict_default_value(self.conf, 'tags', []) - set_dict_default_value(self.conf, 'templateExcludes', []) - - self.jinja_vars = self.load_jinja_vars_list(k8s_cluster, self.conf['vars'], self.jinja_vars) - - for c in self.conf['kustomizeDirs'] + self.conf['includes']: - if 'tags' not in c and 'path' in c: - # If there are no explicit tags set, interpret the path as a tag, which allows to - # enable/disable single deployments via included/excluded tags - c['tags'] = [os.path.basename(c['path'])] - - if self.get_common_labels() == {} or self.get_common_labels() == {}: - raise CommandError("No commonLabels/deleteByLabels in root deployment. This is not allowed") - - def check_required_args(self): - # First try to load the config without templating to avoid getting errors while rendering because required - # args were not set. Otherwise we won't be able to iterator through the 'args' array in the deployment.yml - # when the rendering error is actually args related. - try: - conf = yaml_load_file(os.path.join(self.dir, 'deployment.yml')) - except Exception as e: - # If that failed, it might be that conditional jinja blocks are present in the config, so lets try loading - # the config in rendered form. If it fails due to missing args now, we can't help much with better error - # messages anymore. - conf = self.load_rendered_yaml('deployment.yml', self.jinja_vars) - - if not conf or 'args' not in conf: - return - - if self.parent_collection is not None: - raise CommandError("Only the root deployment.yml can contain args") - - self.deploy_args = check_required_args(conf["args"], self.deploy_args) - - def load_includes(self, k8s_cluster): - for inc in self.conf['includes']: - if 'path' not in inc: - continue - root_project = self.get_root_deployment() - - inc_dir = os.path.join(self.dir, inc["path"]) - inc_dir = os.path.realpath(inc_dir) - if not inc_dir.startswith(root_project.dir): - raise CommandError("Include path is not part of root deployment project: %s" % inc["path"]) - - c = DeploymentProject(k8s_cluster, inc_dir, self.jinja_vars, self.deploy_args, self.sealed_secrets_dir, parent_collection=self) - c.parent_collection_include = inc - - inc['_included_deployment_collection'] = c - - self.includes.append(c) - - def get_sealed_secrets_dir(self): - root = self.get_root_deployment() - output_pattern = get_dict_value(root.conf, "sealedSecrets.outputPattern") - return output_pattern - - def get_root_deployment(self): - if self.parent_collection is None: - return self - return self.parent_collection.get_root_deployment() - - def get_parents(self): - parents = [] - d = self - inc = None - while d is not None: - parents.append((d, inc)) - inc = d.parent_collection_include - d = d.parent_collection - return parents - - def get_children(self, recursive, include_self): - children = [] - if include_self: - children.append(self) - for d in self.includes: - children.append(d) - if recursive: - children += d.get_children(True, False) - return children - - def get_common_labels(self): - ret = {} - for d, inc in reversed(self.get_parents()): - merge_dict(ret, d.conf['commonLabels'], False) - return ret - - def get_delete_by_labels(self): - ret = {} - for d, inc in reversed(self.get_parents()): - merge_dict(ret, d.conf['deleteByLabels'], False) - return ret - - def get_override_namespace(self): - for d, inc in self.get_parents(): - if 'overrideNamespace' in d.conf and d.conf['overrideNamespace'] is not None: - return d.conf['overrideNamespace'] - return None - - def get_tags(self): - tags = OrderedSet() - for d, inc in self.get_parents(): - if inc is not None: - for t in inc['tags']: - tags.add(t) - for t in d.conf['tags']: - tags.add(t) - return tags - - def get_ignore_for_diffs(self, ignore_tags, ignore_labels, ignore_annotations): - ret = [] - for d, inc in self.get_parents(): - ret += d.conf.get("ignoreForDiff", []) - if ignore_tags: - ret.append({ - 'fieldPath': 'metadata.labels."kluctl.io/tag-*"', - }) - if ignore_labels: - ret.append({ - 'fieldPath': 'metadata.labels.*', - }) - if ignore_annotations: - ret.append({ - 'fieldPath': 'metadata.annotations.*', - }) - return ret - diff --git a/kluctl/deployment/helm_chart.py b/kluctl/deployment/helm_chart.py deleted file mode 100644 index 2fb3cfcd9..000000000 --- a/kluctl/deployment/helm_chart.py +++ /dev/null @@ -1,135 +0,0 @@ -import contextlib -import hashlib -import os -import re -import shutil - -from kluctl.utils.exceptions import CommandError -from kluctl.utils.run_helper import run_helper -from kluctl.utils.versions import LooseVersionComparator -from kluctl.utils.yaml_utils import yaml_load_file, yaml_load_all, yaml_dump_all, yaml_load, yaml_save_file - - -class HelmChart(object): - def __init__(self, config_file): - self.dir = os.path.dirname(config_file) - self.config_file = config_file - self.conf = yaml_load_file(config_file)['helmChart'] - - def save(self, config_file): - yaml_save_file({ - "helmChart": self.conf - }, config_file) - - @contextlib.contextmanager - def repo_context(self): - need_repo = False - repo_name = 'stable' - if 'repo' in self.conf and self.conf['repo'] != 'stable': - need_repo = True - repo_name = "kluctl-%s" % hashlib.sha256(self.conf['repo'].encode("utf-8")).hexdigest()[:16] - try: - if need_repo: - self.do_helm(['repo', 'remove', repo_name], ignoreErrors=True, ignoreStderr=True) - self.do_helm(['repo', 'add', repo_name, self.conf['repo']]) - else: - self.do_helm(['repo', 'update']) - yield repo_name - finally: - if need_repo: - self.do_helm(['repo', 'remove', repo_name], ignoreErrors=True, ignoreStderr=True) - - def get_chart_name(self): - if self.conf.get("repo", "").startswith("oci://"): - chart_name = self.conf["repo"].split("/")[-1] - if not re.fullmatch(r"[a-zA-Z_-]+", chart_name): - raise CommandError("Invalid oci chart url: %s" % self.conf["repo"]) - return chart_name - else: - return self.conf["chartName"] - - def pull(self): - target_dir = os.path.join(self.dir, 'charts') - - rm_dir = os.path.join(target_dir, self.get_chart_name()) - shutil.rmtree(rm_dir, ignore_errors=True) - - if self.conf.get("repo", "").startswith("oci://"): - args = ['pull', self.conf["repo"], '--destination', target_dir, '--untar'] - if 'chartVersion' in self.conf: - args += ['--version', self.conf['chartVersion']] - self.do_helm(args) - else: - with self.repo_context() as repo_name: - args = ['pull', '%s/%s' % (repo_name, self.get_chart_name()), '--destination', target_dir, '--untar'] - if 'chartVersion' in self.conf: - args += ['--version', self.conf['chartVersion']] - self.do_helm(args) - - def check_update(self): - if self.conf.get("repo", "").startswith("oci://"): - return None - with self.repo_context() as repo_name: - chart_name = '%s/%s' % (repo_name, self.get_chart_name()) - args = ['search', 'repo', chart_name, '-oyaml', '-l'] - r, stdout, stderr = self.do_helm(args) - l = yaml_load(stdout) - # ensure we didn't get partial matches - l = [x for x in l if x["name"] == chart_name] - if len(l) == 0: - raise Exception("Helm chart %s not found in repository" % self.get_chart_name()) - l.sort(key=lambda x: LooseVersionComparator(x["version"])) - latest_chart = l[-1] - latest_version = latest_chart["version"] - if latest_version == self.conf["chartVersion"]: - return None - return latest_version - - def render(self, k8s_cluster): - chart_dir = os.path.join(self.dir, 'charts', self.get_chart_name()) - values_path = os.path.join(self.dir, 'helm-values.yml') - output_path = os.path.join(self.dir, self.conf['output']) - - args = ['template', self.conf['releaseName'], chart_dir] - - namespace = self.conf.get("namespace", "default") - - if os.path.exists(values_path): - args += ['-f', values_path] - args += ['-n', namespace] - if self.conf.get('skipCRDs', False): - args += ['--skip-crds'] - else: - args += ['--include-crds'] - - args.append("--skip-tests") - - for api_version in k8s_cluster.get_all_api_versions(): - args.append("--api-versions=%s" % api_version) - args.append("--kube-version=%s" % k8s_cluster.server_version) - - r, rendered, stderr = self.do_helm(args) - rendered = rendered.decode('utf-8') - - parsed = yaml_load_all(rendered) - parsed = [o for o in parsed if o is not None] - for o in parsed: - # "helm install" will deploy resources to the given namespace automatically, but "helm template" does not - # add the necessary namespace in the rendered resources - o.setdefault("metadata", {}).setdefault("namespace", namespace) - rendered = yaml_dump_all(parsed) - - with open(output_path, 'w') as f: - f.write(rendered) - - def do_helm(self, args, input=None, ignoreErrors=False, ignoreStderr=False): - args = ['helm'] + args - env = { - "HELM_EXPERIMENTAL_OCI": "true", - **os.environ, - } - - r, stdout, stderr = run_helper(args=args, env=env, input=input, print_stdout=False, print_stderr=not ignoreStderr, return_std=True) - if r != 0 and not ignoreErrors: - raise CommandError("helm failed") - return r, stdout, stderr diff --git a/kluctl/deployment/hooks_util.py b/kluctl/deployment/hooks_util.py deleted file mode 100644 index 5f337e55b..000000000 --- a/kluctl/deployment/hooks_util.py +++ /dev/null @@ -1,161 +0,0 @@ -import dataclasses -import logging - -from typing import List, Set, Optional - -from kluctl.utils.dict_utils import get_dict_value -from kluctl.utils.k8s_object_utils import get_object_ref, get_long_object_name -from kluctl.utils.utils import parse_bool - -logger = logging.getLogger(__name__) - -@dataclasses.dataclass -class Hook: - object: dict - hooks: Set[str] - weight: int - delete_policies: Set[str] - wait: bool - -class HooksUtil: - def __init__(self, apply_util): - self.apply_util = apply_util - - def run_hooks(self, d, hooks): - def do_log(level, str): - self.apply_util.do_log(d, level, str) - - l = self.get_sorted_hooks_list(d.objects) - l = [x for x in l if any(h in hooks for h in x.hooks)] - - delete_before_objects = [] - apply_objects = [] - - for h in l: - if self.apply_util.abort_signal: - return - if "before-hook-creation" in h.delete_policies: - delete_before_objects.append(h) - apply_objects.append(h) - - if len(delete_before_objects) != 0: - do_log(logging.INFO, "Deleting %d hooks before hook execution" % len(delete_before_objects)) - for h in delete_before_objects: - do_log(logging.DEBUG, "Deleting hook %s due to hook-delete-policy %s" % (get_long_object_name(h.object), ",".join(h.delete_policies))) - self.apply_util.delete_object(get_object_ref(h.object)) - - wait_results = {} - if len(apply_objects) != 0: - do_log(logging.INFO, "Applying %d hooks" % len(apply_objects)) - for h in apply_objects: - replaced = "before-hook-creation" in h.delete_policies - do_log(logging.DEBUG, "Applying hook %s" % get_long_object_name(h.object)) - self.apply_util.apply_object(h.object, replaced, True) - - ref = get_object_ref(h.object) - if self.apply_util.had_error(ref): - continue - if not h.wait: - continue - wait_results[ref] = self.apply_util.wait_hook(ref) - - delete_after_objects = [] - for h in reversed(apply_objects): - ref = get_object_ref(h.object) - if ref not in wait_results: - continue - do_delete = False - if wait_results[ref] and "hook-succeeded" in h.delete_policies: - do_delete = True - elif not wait_results[ref] and "hook-failed" in h.delete_policies: - do_delete = True - if do_delete: - delete_after_objects.append(h) - - if len(delete_after_objects) != 0: - do_log(logging.INFO, "Deleting %d hooks after hook execution" % len(delete_after_objects)) - for h in delete_after_objects: - do_log(logging.DEBUG, "Deleting hook %s due to hook-delete-policy %s" % (get_long_object_name(h.object), ",".join(h.delete_policies))) - self.apply_util.delete_object(get_object_ref(h.object)) - - def get_hook(self, o) -> Optional[Hook]: - def get_list(path): - s = get_dict_value(o, path, "") - s = s.split(",") - s = [x for x in s if x != ""] - return s - - supported_kluctl_hooks = {"pre-deploy", "post-deploy", - "pre-deploy-initial", "post-deploy-initial", - "pre-deploy-upgrade", "post-deploy-upgrade"} - supported_kluctl_delete_policies = {"before-hook-creation", - "hook-succeeded", - "hook-failed"} - - # delete/rollback hooks are actually not supported, but we won't show warnings about that to not spam the user - supported_helm_hooks = {"pre-install", "post-install", - "pre-upgrade", "post-upgrade", - "pre-delete", "post-delete", - "pre-rollback", "post-rollback"} - - hooks = get_list('metadata.annotations."kluctl.io/hook"') - for h in hooks: - if h not in supported_kluctl_hooks: - self.apply_util.handle_error(get_object_ref(o), "Unsupported kluctl.io/hook '%s'" % h) - - helm_hooks = get_list('metadata.annotations."helm.sh/hook"') - for h in helm_hooks: - if h not in supported_helm_hooks: - self.apply_util.deployment_collection.add_warnings(get_object_ref(o), ["Unsupported helm.sh/hook '%s'" % h]) - - if "pre-install" in helm_hooks: - hooks.append("pre-deploy-initial") - if "pre-upgrade" in helm_hooks: - hooks.append("pre-deploy-upgrade") - if "post-install" in helm_hooks: - hooks.append("post-deploy-initial") - if "post-upgrade" in helm_hooks: - hooks.append("post-deploy-upgrade") - if "pre-delete" in helm_hooks: - hooks.append("pre-delete") - if "post-delete" in helm_hooks: - hooks.append("post-delete") - hooks = set(hooks) - - weight = get_dict_value(o, 'metadata.annotations."kluctl.io/hook-weight"') - if weight is None: - weight = get_dict_value(o, 'metadata.annotations."helm.sh/hook-weight"') - if weight is None: - weight = "0" - weight = int(weight) - - delete_policy = get_list('metadata.annotations."kluctl.io/hook-delete-policy"') - delete_policy += get_list('metadata.annotations."helm.sh/hook-delete-policy"') - if not delete_policy: - delete_policy = ["before-hook-creation"] - delete_policy = set(delete_policy) - - for p in delete_policy: - if p not in supported_kluctl_delete_policies: - self.apply_util.handle_error(get_object_ref(o), "Unsupported kluctl.io/hook-delete-policy '%s'" % p) - - try: - wait = parse_bool(get_dict_value(o, 'metadata.annotations."kluctl.io/hook-wait"', "true"), do_raise=True) - except ValueError as e: - self.apply_util.handle_error(get_object_ref(o), str(e)) - wait = True - - if not hooks: - return None - - return Hook(object=o, hooks=hooks, weight=weight, delete_policies=delete_policy, wait=wait) - - def get_sorted_hooks_list(self, l) -> List[Hook]: - ret = [] - for x in l: - h = self.get_hook(x) - if h is None: - continue - ret.append(h) - ret.sort(key=lambda x: x.weight) - return ret diff --git a/kluctl/deployment/images.py b/kluctl/deployment/images.py deleted file mode 100644 index 3881ac51b..000000000 --- a/kluctl/deployment/images.py +++ /dev/null @@ -1,168 +0,0 @@ -import hashlib -import logging -import threading - -from kluctl.utils.dict_utils import copy_dict, set_dict_value, get_dict_value, object_iterator -from kluctl.utils.exceptions import CommandError -from kluctl.utils.thread_safe_cache import ThreadSafeMultiCache -from kluctl.utils.versions import build_latest_version_from_str -from kluctl.utils.yaml_utils import yaml_dump - -logger = logging.getLogger(__name__) - - -class Images(object): - def __init__(self, image_registries): - self.image_registries = image_registries - self.update_images = False - self.raise_on_error = False - self.image_tags_cache = ThreadSafeMultiCache() - - self.placeholders = {} - self.seen_images = [] - self.seen_images_lock = threading.Lock() - self.fixed_images = [] - - def get_registry_for_image(self, image): - for r in self.image_registries: - if r.is_image_from_registry(image): - return r - raise Exception('Registry for image %s is not supported or not initialized (missing credentials?)' % image) - - def get_image_tags_from_registry(self, image): - def do_get(): - r = self.get_registry_for_image(image) - tags = r.list_tags_for_image(image) - return tags - return self.image_tags_cache.get(image, "tags", do_get) - - def get_latest_image_from_registry(self, image, latest_version): - if isinstance(latest_version, str): - latest_version = build_latest_version_from_str(latest_version) - - tags = self.get_image_tags_from_registry(image) - filtered_tags = latest_version.filter(tags) - if len(filtered_tags) == 0: - logger.error(f"Failed to find latest image for {image}. Available tags are: {','.join(tags)}") - return None - - latest_tag = latest_version.latest(filtered_tags) - - logger.debug(f"get_latest_image_from_registry({image}): returned {latest_tag}") - - return f'{image}:{latest_tag}' - - def build_seen_entry(self, image, latest_version, - result_image, deployed_image, registry_image, - namespace, deployment, container): - new_entry = { - "image": image, - } - if latest_version is not None: - new_entry["versionFilter"] = latest_version - if result_image is not None: - new_entry["resultImage"] = result_image - if deployed_image is not None: - new_entry["deployedImage"] = deployed_image - if registry_image is not None: - new_entry["registryImage"] = registry_image - - if namespace is not None: - new_entry["namespace"] = namespace - if deployment is not None: - new_entry["deployment"] = deployment - if container is not None: - new_entry["container"] = container - - return new_entry - - def add_seen_image(self, image, latest_version, - result_image, deployed_image, registry_image, - kustomize_dir, tags, - namespace, deployment, container): - new_entry = self.build_seen_entry(image, latest_version, result_image, deployed_image, registry_image, - namespace, deployment, container) - new_entry["deployTags"] = tags - new_entry["kustomizeDir"] = kustomize_dir - - with self.seen_images_lock: - if new_entry not in self.seen_images: - self.seen_images.append(new_entry) - - def add_fixed_image(self, fixed_image): - if "image" not in fixed_image: - raise CommandError("'image' is missing in fixed image entry") - if "resultImage" not in fixed_image: - raise CommandError("'resultImage' is missing in fixed image entry") - - e = { - "image": fixed_image["image"], - "resultImage": fixed_image["resultImage"], - } - if "namespace" in fixed_image: - e["namespace"] = fixed_image["namespace"] - if "deployment" in fixed_image: - e["deployment"] = fixed_image["deployment"] - if "container" in fixed_image: - e["container"] = fixed_image["container"] - - # put it to the front so that newest entries are preferred - self.fixed_images.insert(0, e) - - def get_fixed_image(self, image, namespace, deployment, container): - e = self.build_seen_entry(image, None, None, None, None, namespace, deployment, container) - for fi in self.fixed_images: - if fi["image"] != image: - continue - match = True - for k, v in fi.items(): - if k == "resultImage": - continue - if v != e.get(k): - match = False - break - if match: - return fi["resultImage"] - return None - - def gen_image_placeholder(self, image, latest_version, kustomize_dir, tags): - placeholder = { - "image": image, - "latest_version": str(latest_version), - "kustomize_dir": kustomize_dir, - "tags": tags, - } - h = hashlib.sha256(yaml_dump(placeholder).encode("utf-8")).hexdigest() - with self.seen_images_lock: - self.placeholders[h] = placeholder - return h - - def resolve_placeholders(self, local_object, remote_object): - for v, p in object_iterator(copy_dict(local_object)): - if not isinstance(v, str): - continue - placeholder = self.placeholders.get(v) - if placeholder is None: - continue - - namespace = get_dict_value(local_object, "metadata.namespace") - deployment = "%s/%s" % (local_object["kind"], get_dict_value(local_object, "metadata.name")) - container = get_dict_value(local_object, p[:-1] + ["name"]) - - fixed = self.get_fixed_image(placeholder["image"], namespace, deployment, container) - deployed = None - if remote_object is not None: - deployed = get_dict_value(remote_object, p) - registry = self.get_latest_image_from_registry(placeholder["image"], placeholder["latest_version"]) - - result = deployed - if result is None or self.update_images: - result = registry - if not self.update_images and fixed is not None: - result = fixed - - self.add_seen_image(placeholder["image"], placeholder["latest_version"], result, deployed, registry, - placeholder["kustomize_dir"], placeholder["tags"], - namespace, deployment, container) - - set_dict_value(local_object, p, result) diff --git a/kluctl/deployment/images_test.py b/kluctl/deployment/images_test.py deleted file mode 100644 index 7b00d6a9b..000000000 --- a/kluctl/deployment/images_test.py +++ /dev/null @@ -1,106 +0,0 @@ -import unittest - -from kluctl.deployment.images import Images -from kluctl.image_registries.images_registry import ImagesRegistry -from kluctl.utils.dict_utils import get_dict_value -from kluctl.utils.versions import PrefixLatestVersion, NumberLatestVersion, LooseSemVerLatestVersion - - -class TestImages(unittest.TestCase): - def build_images_object(self): - self.registry = MockedImagesRegistry() - images = Images([self.registry]) - self.add_images(self.registry) - return images - - def add_images(self, registry): - registry.add_tags('g', 'p', '', ['1']) - registry.add_tags('g', 'p', '', ['2']) - registry.add_tags('g', 'p', '', ['3.3']) - registry.add_tags('g', 'p', '', ['a']) - registry.add_tags('g', 'p', '', ['b']) - registry.add_tags('g', 'p', '', ['abc']) - registry.add_tags('g', 'p', '', ['abc-1']) - registry.add_tags('g', 'p', '', ['z-10']) - registry.add_tags('g', 'p', '', ['z-1']) - registry.add_tags('g', 'p2', '', ['1.1']) - registry.add_tags('g', 'p2', '', ['1.1.1']) - registry.add_tags('g', 'p3', '', ['1.1']) - registry.add_tags('g', 'p3', '', ['1.1-abc']) - - def test_gitlab_latest_prefix(self): - self.assertEqual(self.build_images_object().get_latest_image_from_registry('registry.gitlab.com/g/p', latest_version=PrefixLatestVersion('z')), 'registry.gitlab.com/g/p:z-10') - - def test_gitlab_latest_number(self): - self.assertEqual(self.build_images_object().get_latest_image_from_registry('registry.gitlab.com/g/p', latest_version=NumberLatestVersion()), 'registry.gitlab.com/g/p:2') - - def test_gitlab_latest_semver_simple(self): - self.assertEqual(self.build_images_object().get_latest_image_from_registry('registry.gitlab.com/g/p', latest_version=LooseSemVerLatestVersion()), 'registry.gitlab.com/g/p:3.3') - - def test_gitlab_latest_semver_suffix(self): - self.assertEqual(self.build_images_object().get_latest_image_from_registry('registry.gitlab.com/g/p2', latest_version=LooseSemVerLatestVersion()), 'registry.gitlab.com/g/p2:1.1.1') - self.assertEqual(self.build_images_object().get_latest_image_from_registry('registry.gitlab.com/g/p3', latest_version=LooseSemVerLatestVersion()), 'registry.gitlab.com/g/p3:1.1') - - def build_object(self, image1, image2): - o = { - 'kind': 'Deployment', - 'metadata': { - 'namespace': 'ns', - 'name': 'dep', - }, - 'spec': { - 'template': { - 'spec': { - 'containers': [] - }, - }, - }, - } - if image1 is not None: - o["spec"]["template"]["spec"]["containers"].append({ - "name": "c1", - "image": image1 - }) - if image2 is not None: - o["spec"]["template"]["spec"]["containers"].append({ - "name": "c2", - "image": image2 - }) - return o - - def do_test_placeholder_resolve(self, image, deployed_object, result_image1, result_image2): - images = self.build_images_object() - latest_version = LooseSemVerLatestVersion() - placeholder = images.gen_image_placeholder(image, latest_version, None, []) - rendered_object = self.build_object(placeholder, placeholder) - images.resolve_placeholders(rendered_object, deployed_object) - self.assertEqual(get_dict_value(rendered_object, "spec.template.spec.containers[0].image"), result_image1) - self.assertEqual(get_dict_value(rendered_object, "spec.template.spec.containers[1].image"), result_image2) - - def test_deployed(self): - self.do_test_placeholder_resolve('registry.gitlab.com/g/p', self.build_object("di1", "di2"), "di1", "di2") - - def test_not_deployed(self): - self.do_test_placeholder_resolve('registry.gitlab.com/g/p', None, "registry.gitlab.com/g/p:3.3", "registry.gitlab.com/g/p:3.3") - - def test_container_missing(self): - self.do_test_placeholder_resolve('registry.gitlab.com/g/p', self.build_object("di1", None), "di1", "registry.gitlab.com/g/p:3.3") - - -class MockedImagesRegistry(ImagesRegistry): - def __init__(self): - self.images = {} - - def add_tags(self, g, p, r, tags): - i = f"{g}/{p}" - if r: - i += f"/{r}" - t = self.images.setdefault(i, []) - t += tags - - def is_image_from_registry(self, image): - return True - - def list_tags_for_image(self, image): - image = image.replace("registry.gitlab.com/", "") - return self.images.get(image) diff --git a/kluctl/deployment/kustomize_deployment.py b/kluctl/deployment/kustomize_deployment.py deleted file mode 100644 index e3e7e3283..000000000 --- a/kluctl/deployment/kustomize_deployment.py +++ /dev/null @@ -1,284 +0,0 @@ -import hashlib -import logging -import os -import shutil - -from kluctl.deployment.helm_chart import HelmChart -from kluctl.seal.deployment_sealer import SEALME_EXT -from kluctl.utils.dict_utils import merge_dict -from kluctl.utils.exceptions import CommandError, InvalidKluctlProjectConfig -from kluctl.utils.external_tools import get_external_tool_hash -from kluctl.utils.k8s_object_utils import get_object_ref, should_remove_namespace -from kluctl.utils.kustomize import kustomize_build -from kluctl.utils.templated_dir import TemplatedDir -from kluctl.utils.utils import get_tmp_base_dir, calc_dir_hash -from kluctl.utils.versions import LooseSemVerLatestVersion, PrefixLatestVersion, NumberLatestVersion, \ - RegexLatestVersion -from kluctl.utils.yaml_utils import yaml_load_file, yaml_save_file - -logger = logging.getLogger(__name__) - -def get_kustomize_cache_dir(): - dir = os.path.join(get_tmp_base_dir(), "kluctl-kustomize-cache") - os.makedirs(dir, exist_ok=True) - return dir - -allow_cache = True - -class KustomizeDeployment(object): - def __init__(self, deployment_project, deployment_collection, config, dir, index): - self.deployment_project = deployment_project - self.deployment_collection = deployment_collection - self.config = config - self.dir = dir - self.index = index - self.objects = [] - - if dir and not os.path.isdir(dir): - raise InvalidKluctlProjectConfig("kustomizeDir does not exist: %s" % dir) - - def get_rel_kustomize_dir(self): - root_project = self.deployment_project.get_root_deployment() - return os.path.relpath(self.dir, root_project.dir) - - def get_rel_rendered_dir(self): - path = self.get_rel_kustomize_dir() - if self.index != 0: - path = "%s-%d" % (path, self.index) - return path - - def get_rendered_dir(self): - path = self.get_rel_rendered_dir() - rendered_dir = os.path.join(self.deployment_collection.tmpdir, path) - return rendered_dir - - def get_rendered_yamls_path(self): - return os.path.join(self.get_rendered_dir(), ".rendered.yml") - - def get_common_labels(self): - l = self.deployment_project.get_common_labels() - for i, tag in enumerate(self.get_tags()): - l['kluctl.io/tag-%d' % i] = tag - return l - - def get_common_annotations(self): - a = { - # Must use annotations instead of labels due to limitations on value (max 63 chars, no slash) - "kluctl.io/kustomize_dir": self.get_rel_kustomize_dir().replace(os.path.sep, "/") - } - if self.config.get("skipDeleteIfTags", False): - a["kluctl.io/skip-delete-if-tags"] = "true" - return a - - def get_image_wrapper(self, image, namespace=None, deployment_name=None, container=None, latest_version=LooseSemVerLatestVersion()): - # TODO remove this - if namespace is not None or deployment_name is not None or container is not None: - logger.warning("images.get_image called with obsolete parameters (namespace/deployment_name/container). These are not needed anymore.") - - tags = list(sorted(self.get_tags())) - return self.deployment_collection.images.gen_image_placeholder(image, latest_version, self.get_rel_kustomize_dir(), tags) - - def build_images_jinja_vars(self): - if self.deployment_collection.images is None: - return {} - - return { - 'images': { - 'get_image': self.get_image_wrapper, - }, - 'version': { - 'semver': LooseSemVerLatestVersion, - 'prefix': PrefixLatestVersion, - 'number': NumberLatestVersion, - 'regex': RegexLatestVersion, - }, - } - - def build_jinja2_env(self, jinja_vars): - images_vars = self.build_images_jinja_vars() - jinja_vars = merge_dict(jinja_vars, images_vars) - merge_dict(jinja_vars, { - "deployment": { - "tags": list(self.get_tags()) - } - }, clone=False) - return self.deployment_project.build_jinja2_env(jinja_vars) - - def render(self, k8s_cluster, executor): - if self.dir is None: - return [] - - root_dir = self.deployment_project.get_root_deployment().dir - rel_deployment_dir = os.path.relpath(self.deployment_project.dir, root_dir) - rel_kustomize_dir = os.path.relpath(self.get_rel_kustomize_dir(), rel_deployment_dir) - abs_rendered_dir = self.get_rendered_dir() - - os.makedirs(abs_rendered_dir, exist_ok=True) - - jinja_vars = self.deployment_project.jinja_vars - if "vars" in self.config: - jinja_vars = self.deployment_project.load_jinja_vars_list(k8s_cluster, self.config["vars"], jinja_vars) - - jinja_env = self.build_jinja2_env(jinja_vars) - - excluded_patterns = self.deployment_project.conf['templateExcludes'].copy() - if os.path.exists(os.path.join(self.dir, 'helm-chart.yml')): - # never try to render helm charts - excluded_patterns.append(os.path.join(rel_kustomize_dir, 'charts/*')) - if not self.deployment_collection.for_seal: - # .sealme files are rendered while sealing and not while deploying - excluded_patterns.append('*.sealme') - - d = TemplatedDir(root_dir, rel_deployment_dir, jinja_env, executor, excluded_patterns) - return d.async_render_subdir(rel_kustomize_dir, abs_rendered_dir) - - def render_helm_charts(self, k8s_cluster, executor): - if self.dir is None: - return [] - - jobs = [] - rendered_dir = self.get_rendered_dir() - for dirpath, dirnames, filenames in os.walk(rendered_dir): - path = os.path.join(dirpath, 'helm-chart.yml') - if not os.path.exists(path): - continue - chart = HelmChart(path) - job = executor.submit(chart.render, k8s_cluster) - jobs.append(job) - return jobs - - def resolve_sealed_secrets(self): - if self.dir is None: - return - - sealed_secrets_dir = self.deployment_project.get_sealed_secrets_dir() - rel_rendered_dir = self.get_rel_rendered_dir() - rendered_dir = self.get_rendered_dir() - base_source_path = self.deployment_project.sealed_secrets_dir - - y = yaml_load_file(os.path.join(rendered_dir, "kustomization.yml")) or {} - for resource in y.get("resources", []): - p = os.path.join(rendered_dir, resource) - if os.path.exists(p) or not os.path.exists(p + SEALME_EXT): - continue - rel_dir = os.path.relpath(os.path.dirname(p), rendered_dir) - fname = os.path.basename(p) - - base_error = 'Failed to resolve SealedSecret %s' % os.path.normpath(os.path.join(self.deployment_project.dir, resource)) - if sealed_secrets_dir is None: - raise CommandError('%s\nSealed secrets dir could not be determined.' % base_error) - source_path = os.path.normpath(os.path.join(base_source_path, rel_rendered_dir, rel_dir, sealed_secrets_dir, fname)) - target_path = os.path.join(rendered_dir, rel_dir, fname) - if not os.path.exists(source_path): - raise CommandError('%s\n%s not found.\nYou might need to seal it first.' % (base_error, source_path)) - shutil.copy(source_path, target_path) - - def get_tags(self): - tags = self.deployment_project.get_tags() - - for t in self.config.get("tags", []): - tags.add(t) - - return tags - - def build_inclusion_values(self): - values = [("tag", tag) for tag in self.get_tags()] - if self.dir is not None: - kustomize_dir = self.get_rel_kustomize_dir().replace(os.path.sep, "/") - values.append(("kustomize_dir", kustomize_dir)) - return values - - def check_inclusion_for_deploy(self): - inclusion = self.deployment_collection.inclusion - if inclusion is None: - return True - if self.config.get("onlyRender", False): - return True - if self.config.get("alwaysDeploy", False): - return True - values = self.build_inclusion_values() - return inclusion.check_included(values) - - def check_inclusion_for_delete(self): - inclusion = self.deployment_collection.inclusion - if inclusion is None: - return True - skip_delete_if_tags = self.config.get("skipDeleteIfTags", False) - values = self.build_inclusion_values() - return inclusion.check_included(values, skip_delete_if_tags) - - def prepare_kustomization_yaml(self): - if self.dir is None: - return - - rendered_dir = self.get_rendered_dir() - kustomize_yaml_path = os.path.join(rendered_dir, 'kustomization.yml') - if not os.path.exists(kustomize_yaml_path): - return - kustomize_yaml = yaml_load_file(kustomize_yaml_path) - - override_namespace = self.deployment_project.get_override_namespace() - if override_namespace is not None: - kustomize_yaml.setdefault("namespace", override_namespace) - - # Save modified kustomize.yml - yaml_save_file(kustomize_yaml, kustomize_yaml_path) - - def build_kustomize(self): - if self.dir is None: - return - - self.prepare_kustomization_yaml() - - need_build = True - if allow_cache: - hash = self.calc_hash() - hash_path = os.path.join(get_kustomize_cache_dir(), hash[:16]) - if os.path.exists(hash_path): - need_build = False - # Copy from cache - shutil.copy(hash_path, self.get_rendered_yamls_path()) - if need_build: - # Run 'kustomize build' - yamls = kustomize_build(self.get_rendered_dir()) - with open(self.get_rendered_yamls_path(), "wt") as f: - f.write(yamls) - if allow_cache: - shutil.copy(self.get_rendered_yamls_path(), hash_path) - - def postprocess_and_load_objects(self, k8s_cluster): - if self.dir is None: - return - - self.objects = yaml_load_file(self.get_rendered_yamls_path(), all=True) - for y in self.objects: - ref = get_object_ref(y) - if k8s_cluster is not None and should_remove_namespace(k8s_cluster, ref): - del y["metadata"]["namespace"] - ref = get_object_ref(y) - - # Set common labels/annotations - labels = y.setdefault("metadata", {}).setdefault("labels") or {} - annotations = y["metadata"].setdefault("annotations") or {} - labels.update(self.get_common_labels()) - annotations.update(self.get_common_annotations()) - y["metadata"]["labels"] = labels - y["metadata"]["annotations"] = annotations - - # Resolve image placeholders - s = str(y) - if any(p in s for p in self.deployment_collection.images.placeholders.keys()): - if k8s_cluster is not None: - remote_object, warnings = k8s_cluster.get_single_object(ref) - else: - remote_object = None - self.deployment_collection.images.resolve_placeholders(y, remote_object) - - # Need to write it back to disk in case it is needed externally - yaml_save_file(self.objects, self.get_rendered_yamls_path()) - - def calc_hash(self): - h = hashlib.sha256() - h.update(get_external_tool_hash("kustomize").encode("utf-8")) - h.update(calc_dir_hash(self.get_rendered_dir()).encode("utf-8")) - return h.hexdigest() diff --git a/kluctl/diff/__init__.py b/kluctl/diff/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/kluctl/diff/k8s_diff.py b/kluctl/diff/k8s_diff.py deleted file mode 100644 index d88b36c81..000000000 --- a/kluctl/diff/k8s_diff.py +++ /dev/null @@ -1,117 +0,0 @@ -import dataclasses -import difflib -import re - -from deepdiff import DeepDiff -from deepdiff.helper import NotPresent - -from kluctl.utils.k8s_object_utils import get_object_ref -from kluctl.utils.dict_utils import copy_dict, remove_empty -from kluctl.utils.yaml_utils import yaml_dump - - -def object_to_diffable_lines(o): - if o is None: - return ['null'] - if isinstance(o, NotPresent): - return [] - if isinstance(o, str): - return o.splitlines() - if isinstance(o, bool): - return ['true' if o else 'false'] - if isinstance(o, int): - return [str(o)] - y = yaml_dump(o) - return y.splitlines() - -def unified_diff_object(old_object, new_object): - old_lines = object_to_diffable_lines(old_object) - new_lines = object_to_diffable_lines(new_object) - - if len(old_lines) == 0: - d = ['+' + x for x in new_lines] - elif len(new_lines) == 0: - d = ['-' + x for x in old_lines] - elif len(old_lines) == 1 and len(new_lines) == 1: - d = ['-%s' % old_lines[0], '+%s' % new_lines[0]] - else: - d = list(difflib.unified_diff(old_lines, new_lines)) - # Skip diff header - d = d[2:] - d = '\n'.join(d) - return d - -diff_path_regex = re.compile(r'\[([^\]]*)\]') - -def to_dotted_path(diff_path): - diff_path = diff_path[len('root'):] - s = diff_path_regex.split(diff_path) - s = [l for l in s if l] - - def remove_quotes(s): - return s[1:-1] if s.startswith('\'') else s - def escape(s): - return s.replace('.', '\\.') - - for i in range(len(s)): - s[i] = remove_quotes(s[i]) - s[i] = escape(s[i]) - - return '.'.join(s) - -def eval_diff_path(diff_path, o): - try: - return eval(diff_path, {'root': o}) - except KeyError: - return NotPresent() - except: - return '...' - -def deep_diff_object(old_object, new_object, ignore_order): - old_object = copy_dict(old_object) - new_object = copy_dict(new_object) - remove_empty(old_object) - remove_empty(new_object) - diff = DeepDiff(old_object, new_object, ignore_order=ignore_order) - return deep_diff_to_changes(diff, old_object, new_object) - -def deep_diff_to_changes(diff, old_object, new_object): - changes = [] - for report_type in diff.keys(): - r = diff[report_type] - for path in list(r): - change = { - "path": to_dotted_path(path) - } - if report_type in ['values_changed', 'type_changes']: - e = r[path] - change["old_value"] = e['old_value'] - change["new_value"] = e['new_value'] - change["unified_diff"] = unified_diff_object(e['old_value'], e['new_value']) - elif report_type in ['dictionary_item_added', 'iterable_item_added', 'attribute_added']: - change["new_value"] = eval_diff_path(path, new_object) - elif report_type in ['dictionary_item_removed', 'iterable_item_removed', 'attribute_removed']: - change["old_value"] = eval_diff_path(path, old_object) - else: - raise Exception('Unknown report_type=\'%s\'. items=%s' % (report_type, str(r))) - - changes.append(change) - - changes.sort(key=lambda x: x["path"]) - - return changes - -def changes_to_yaml(new_objects, changed_objects): - new_objects = [{ - "ref": dataclasses.asdict(get_object_ref(x)), - "object": x - } for x in new_objects] - changed_objects = [{ - "ref": dataclasses.asdict(get_object_ref(c["old_object"])), - "changes": c["changes"], - } for c in changed_objects] - - return { - "new": new_objects, - "changed": changed_objects, - } diff --git a/kluctl/diff/managed_fields.py b/kluctl/diff/managed_fields.py deleted file mode 100644 index 7fbc16501..000000000 --- a/kluctl/diff/managed_fields.py +++ /dev/null @@ -1,166 +0,0 @@ -import json -import re - -from kluctl.utils.dict_utils import copy_dict, object_iterator, del_dict_value, get_dict_value, is_iterable, \ - has_dict_value, list_matching_dict_pathes -from kluctl.utils.jsonpath_utils import convert_list_to_json_path, parse_json_path - -# We automatically force overwrite these fields as we assume these are human-edited -from kluctl.utils.utils import parse_bool - -FORCE_APPLY_FIELD_ANNOTATION_REGEX = re.compile(r"^kluctl.io/force-apply-field(-\d*)?$") - -overwrite_allowed_managers = [ - "kluctl", - "kubectl", - "kubectl-.*", - "rancher", - "k9s", -] - -def check_item_match(o, kv, index): - if kv[0:2] == "k:": - k = kv[2:] - j = json.loads(k) - for k2 in j.keys(): - if k2 not in o: - return False - if j[k2] != o[k2]: - return False - return True - elif kv[0:2] == "v:": - k = kv[2:] - j = json.loads(k) - return j == o - elif kv[0:2] == "i:": - index2 = int(kv[2:]) - return index == index2 - else: - raise ValueError("Invalid managedFields entry '%s'" % kv) - -def convert_to_json_path(o, mf_path): - ret = [] - for i in range(len(mf_path)): - k = mf_path[i] - if k == "{}": - raise Exception() - if k == ".": - if i != len(mf_path) - 1: - raise ValueError("Unexpected . element at %s" % convert_list_to_json_path(ret)) - return ret, True - if k[:2] == 'f:': - k = k[2:] - if o is None: - return ret, False - if not isinstance(o, dict): - raise ValueError("%s is not a dict" % convert_list_to_json_path(ret)) - if k not in o: - return ret, False - ret.append(k) - o = o[k] - else: - if o is None: - return ret, False - if not is_iterable(o, False): - raise ValueError("%s is not a list" % convert_list_to_json_path(ret)) - found = False - for j, v in enumerate(o): - if check_item_match(v, k, j): - found = True - ret.append(j) - o = o[j] - break - if not found: - return ret, False - return ret, True - -def convert_to_string(mf_path): - ret = [] - for i in range(len(mf_path)): - k = mf_path[i] - if k == "{}": - raise Exception() - if k == ".": - if i != len(mf_path) - 1: - raise ValueError("Unexpected . element at %s" % convert_list_to_json_path(ret)) - break - if k[:2] == "f:": - k = k[2:] - ret.append(".%s" % k) - elif k[:2] == "k:": - strs = [] - k = json.loads(k[2:]) - for k2 in sorted(k.keys()): - strs.append("%s=%s" % (k2, json.dumps(k[k2]))) - ret.append("[%s]" % ",".join(strs)) - elif k[:2] == "v:": - ret.append("[=%s]" % k[2:]) - elif k[:2] == "i:": - ret.append("[%s]" % k[2:]) - else: - raise ValueError("Invalid path element %s" % k) - return "".join(ret) - -def resolve_field_manager_conflicts(local_object, remote_object, conflict_status): - managed_fields = get_dict_value(remote_object, "metadata.managedFields") - if managed_fields is None: - raise Exception("managedFields not found") - v1_fields = [mf for mf in managed_fields if mf['fieldsType'] == 'FieldsV1'] - - # "stupid" because the string representation in "details.causes.field" might be ambiguous as k8s does not escape dots - fields_as_stupid_strings = {} - managers_by_field = {} - for mf in v1_fields: - for v, p in object_iterator(mf["fieldsV1"], only_leafs=True): - s = convert_to_string(p) - fields_as_stupid_strings.setdefault(s, set()).add(tuple(p)) - managers_by_field.setdefault(tuple(p), []).append(mf) - - ret = copy_dict(local_object) - - force_apply_all = get_dict_value(local_object, 'metadata.annotations["kluctl.io/force-apply"]', "false") - force_apply_all = parse_bool(force_apply_all) - - force_apply_fields = set() - for k, v in get_dict_value(local_object, "metadata.annotations", {}).items(): - if FORCE_APPLY_FIELD_ANNOTATION_REGEX.fullmatch(k): - for x in list_matching_dict_pathes(ret, v): - force_apply_fields.add(x) - - lost_ownership = [] - for cause in get_dict_value(conflict_status, "details.causes", []): - if cause.get("reason") != "FieldManagerConflict" or "field" not in cause: - raise Exception("Unknown reason '%s'" % cause.get("reason")) - - mf_path = fields_as_stupid_strings.get(cause["field"]) - if not mf_path: - raise Exception("Could not find matching field for path '%s'" % cause["field"]) - if len(mf_path) != 1: - raise Exception("field path '%s' is ambiguous" % cause["field"]) - mf_path = list(mf_path)[0] - - p, found = convert_to_json_path(remote_object, mf_path) - if not found: - raise Exception("Field '%s' not found in remote object" % cause["field"]) - if not has_dict_value(local_object, p): - raise Exception("Field '%s' not found in local object" % cause["field"]) - - local_value = get_dict_value(local_object, p) - remote_value = get_dict_value(remote_object, p) - - overwrite = True - if not force_apply_all: - for mf in managers_by_field[mf_path]: - if not any(re.fullmatch(x, mf["manager"]) for x in overwrite_allowed_managers): - overwrite = False - break - if str(parse_json_path(p)) in force_apply_fields: - overwrite = True - - if not overwrite: - del_dict_value(ret, p) - - if local_value != remote_value: - lost_ownership.append(cause) - - return ret, lost_ownership diff --git a/kluctl/diff/normalize.py b/kluctl/diff/normalize.py deleted file mode 100644 index cff189b2b..000000000 --- a/kluctl/diff/normalize.py +++ /dev/null @@ -1,139 +0,0 @@ -import re - -from kluctl.utils.dict_utils import copy_dict, get_dict_value, del_dict_value, \ - set_dict_default_value, list_matching_dict_pathes -from kluctl.utils.k8s_object_utils import split_api_version -from kluctl.utils.utils import parse_bool - -IGNORE_DIFF_FIELD_ANNOTATION_REGEX = re.compile(r"^kluctl.io/ignore-diff-field(-\d*)?$") - -def normalize_env(container): - env = container.get("env") - envFrom = container.get("envFrom") - if env: - container['env'] = dict((e['name'], e) for e in env) - if envFrom: - types = ['configMapRef', 'secretRef'] - m = {} - for e in container['envFrom']: - k = None - for t in types: - if t in e: - k = '%s/%s' % (t, e[t]['name']) - break - if k is None: - if 'unknown' not in m: - m['unknown'] = [] - m['unknown'].append(e) - else: - m[k] = e - container['envFrom'] = m - -def normalize_containers(containers): - for c in containers: - normalize_env(c) - -def normalize_secret_and_configmap(o): - if not get_dict_value(o, "data"): - del_dict_value(o, "data") - -def normalize_service_account(o): - new_secrets = [] - for s in o.get("secrets", []): - if s["name"].startswith("%s-" % o["metadata"]["name"]): - continue - new_secrets.append(s) - o["secrets"] = new_secrets - -def normalize_metadata(k8s_cluster, o): - group, version = split_api_version(o["apiVersion"]) - api_resource = k8s_cluster.get_resource(group, version, o["kind"]) - - if 'metadata' not in o: - return - - m = o['metadata'] - - # remove namespace in case the resource is not namespaced - if 'namespace' in m: - if api_resource and not api_resource.namespaced: - del(m['namespace']) - - # We don't care about managedFields when diffing (they just produce noise) - del_dict_value(m, 'managedFields') - del_dict_value(m, 'annotations."kubectl.kubernetes.io/last-applied-configuration"') - - # We don't want to see this in diffs - del_dict_value(m, 'creationTimestamp') - del_dict_value(m, 'generation') - del_dict_value(m, 'resourceVersion') - del_dict_value(m, 'selfLink') - del_dict_value(m, 'uid') - - # Ensure empty labels/metadata exist - set_dict_default_value(m, 'labels', {}) - set_dict_default_value(m, 'annotations', {}) - -def normalize_misc(o): - # These are random values found in Jobs - del_dict_value(o, 'spec.template.metadata.labels.controller-uid') - del_dict_value(o, 'spec.selector.matchLabels.controller-uid') - - del_dict_value(o, 'status') - -# Performs some deterministic sorting and other normalizations to avoid ugly diffs due to order changes -def normalize_object(k8s_cluster, o, ignore_for_diffs, local_object): - group, version = split_api_version(o["apiVersion"]) - kind = o['kind'] - ns = get_dict_value(o, "metadata.namespace") - name = get_dict_value(o, "metadata.name") - - o = copy_dict(o) - normalize_metadata(k8s_cluster, o) - normalize_misc(o) - if o['kind'] in ['Deployment', 'StatefulSet', 'DaemonSet', 'Job']: - normalize_containers(o['spec']['template']['spec']['containers']) - elif o['kind'] in ['Secret', 'ConfigMap']: - normalize_secret_and_configmap(o) - elif o['kind'] in ['ServiceAccount']: - normalize_service_account(o) - - def check_match(v, m): - if v is None or m is None: - return True - return v == m - - for ifd in ignore_for_diffs: - group2 = ifd.get('group') - kind2 = ifd.get('kind') - ns2 = ifd.get('namespace') - name2 = ifd.get('name') - field_path = ifd.get('fieldPath') - if not check_match(group, group2): - continue - if not check_match(kind, kind2): - continue - if not check_match(ns, ns2): - continue - if not check_match(name, name2): - continue - if not isinstance(field_path, list): - field_path = [field_path] - for p in field_path: - del_dict_value(o, p) - - ignore_all = parse_bool(get_dict_value(local_object, 'metadata.annotations["kluctl.io/ignore-diff"]', "false")) - if ignore_all: - # Return empty object so that diffs will always be empty - return {} - - del_fields = set() - for k, v in get_dict_value(local_object, "metadata.annotations", {}).items(): - if IGNORE_DIFF_FIELD_ANNOTATION_REGEX.fullmatch(k): - for x in list_matching_dict_pathes(o, v): - del_fields.add(x) - for p in del_fields: - del_dict_value(o, p) - - return o - diff --git a/kluctl/e2e/__init__.py b/kluctl/e2e/__init__.py deleted file mode 100644 index 5ad950fc8..000000000 --- a/kluctl/e2e/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -import os -import subprocess - -import pytest_kind - -pytest_kind.cluster.KIND_VERSION = "v0.11.1" -pytest_kind.cluster.KUBECTL_VERSION = "v1.21.5" - -# Same as pytest_kind.cluster.KindCluster.kubectl, but with os.environment properly passed to the subprocess -def my_kubectl(self, *args, **kwargs): - self.ensure_kubectl() - return subprocess.check_output( - [str(self.kubectl_path), *args], - env={ - **os.environ, - "KUBECONFIG": str(self.kubeconfig_path), - }, - encoding="utf-8", - **kwargs, - ) - - -pytest_kind.cluster.KindCluster.kubectl = my_kubectl \ No newline at end of file diff --git a/kluctl/e2e/conftest.py b/kluctl/e2e/conftest.py deleted file mode 100644 index f9ee600d0..000000000 --- a/kluctl/e2e/conftest.py +++ /dev/null @@ -1,56 +0,0 @@ -import logging -import subprocess -import time - -from pytest_kind import KindCluster - -from kluctl.utils.k8s_status_validation import validate_object -from kluctl.utils.yaml_utils import yaml_load - -logger = logging.getLogger(__name__) - -def recreate_namespace(kind_cluster: KindCluster, namespace): - try: - kind_cluster.kubectl("delete", "ns", namespace) - except: - pass - kind_cluster.kubectl("create", "ns", namespace) - -def wait_for_readiness(kind_cluster: KindCluster, namespace, resource, timeout): - logger.info("Waiting for readiness: %s/%s" % (namespace, resource)) - - t = time.time() - while time.time() - t < timeout: - y = kind_cluster.kubectl("-n", namespace, "get", resource, "-oyaml") - y = yaml_load(y) - - r = validate_object(y, True) - if r.ready: - return True - return False - -def assert_readiness(kind_cluster: KindCluster, namespace, resource, timeout): - if not wait_for_readiness(kind_cluster, namespace, resource, timeout): - raise AssertionError("%s/%s did not get ready in time" % (namespace, resource)) - -def assert_resource_exists(kind_cluster: KindCluster, namespace, resource): - try: - args = [] - if namespace is not None: - args += ["-n", namespace] - args += ["get", resource, "-o", "yaml"] - y = kind_cluster.kubectl(*args, stderr=subprocess.PIPE) - return yaml_load(y) - except subprocess.CalledProcessError as e: - assert False, e.stderr - -def assert_resource_not_exists(kind_cluster: KindCluster, namespace, resource): - try: - args = [] - if namespace is not None: - args += ["-n", namespace] - args += ["get", resource] - kind_cluster.kubectl(*args, stderr=subprocess.PIPE) - assert False, "'kubectl get' should not have succeeded" - except subprocess.CalledProcessError as e: - assert "(NotFound)" in e.stderr, e.stderr diff --git a/kluctl/e2e/kluctl_test_project.py b/kluctl/e2e/kluctl_test_project.py deleted file mode 100644 index 9f974b5d0..000000000 --- a/kluctl/e2e/kluctl_test_project.py +++ /dev/null @@ -1,324 +0,0 @@ -import logging -import os -import shutil -import sys -from pathlib import Path -from tempfile import TemporaryDirectory - -from git import Git -from pytest_kind import KindCluster - -from kluctl.utils.dict_utils import copy_dict, set_dict_value -from kluctl.utils.run_helper import run_helper -from kluctl.utils.yaml_utils import yaml_save_file, yaml_load_file - -logger = logging.getLogger(__name__) - - -class KluctlTestProject: - def __init__(self, project_name, kluctl_project_external=False, - clusters_external=False, deployment_external=False, sealed_secrets_external=False, - local_clusters=None, local_deployment=None, local_sealed_secrets=None): - self.project_name = project_name - self.kluctl_project_external = kluctl_project_external - self.clusters_external = clusters_external - self.deployment_external = deployment_external - self.sealed_secrets_external = sealed_secrets_external - self.local_clusters = local_clusters - self.local_deployment = local_deployment - self.local_sealed_secrets = local_sealed_secrets - self.kubeconfigs = [] - - def __enter__(self): - self.base_dir = TemporaryDirectory(prefix="kluctl-e2e-") - - os.makedirs(self.get_kluctl_project_dir(), exist_ok=True) - os.makedirs(os.path.join(self.get_clusters_dir(), "clusters"), exist_ok=True) - os.makedirs(os.path.join(self.get_sealed_secrets_dir(), ".sealed-secrets"), exist_ok=True) - os.makedirs(self.get_deployment_dir(), exist_ok=True) - - self._git_init(self.get_kluctl_project_dir()) - if self.clusters_external: - self._git_init(self.get_clusters_dir()) - if self.deployment_external: - self._git_init(self.get_deployment_dir()) - if self.sealed_secrets_external: - self._git_init(self.get_sealed_secrets_dir()) - - def do_update_kluctl(y): - if self.clusters_external: - y["clusters"] = { - "project": self.build_file_url(self.get_clusters_dir()), - } - if self.deployment_external: - y["deployment"] = { - "project": self.build_file_url(self.get_deployment_dir()), - } - if self.sealed_secrets_external: - y["sealedSecrets"] = { - "project": self.build_file_url(self.get_sealed_secrets_dir()), - } - return y - - self.update_kluctl_yaml(do_update_kluctl) - self.update_deployment_yaml(".", lambda y: y) - - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - return self.base_dir.__exit__(exc_type, exc_val, exc_tb) - - def _git_init(self, dir): - os.makedirs(dir, exist_ok=True) - g = Git(dir) - g.init() - g.config("user.email", "no@mail.com") - g.config("user.name", "Your Name") - with open(os.path.join(dir, ".dummy"), "wt") as f: - f.write("dummy") - g.add(".dummy") - g.commit("-m", "initial") - - def _commit(self, dir, add=None, all=None, message="commit"): - assert self.base_dir.name in dir - - g = Git(dir) - - if add is not None: - for f in add: - g.add(f) - - if all or (add is None and all is None): - g.add(".") - - g.commit("-m", message) - - def _commit_yaml(self, y, path, message="save yaml"): - yaml_save_file(y, path) - self._commit(os.path.dirname(path), add=[os.path.basename(path)], message=message) - - def update_yaml(self, path, update, message=None): - assert self.base_dir.name in path - try: - y = yaml_load_file(path) - orig_y = copy_dict(y) - except: - y = {} - orig_y = None - y = update(y) - if y == orig_y: - return - - yaml_save_file(y, path) - - if message is None: - message = "update %s" % os.path.relpath(path, self.base_dir.name) - self._commit_yaml(y, path, message=message) - - def update_kluctl_yaml(self, update): - self.update_yaml(os.path.join(self.get_kluctl_project_dir(), ".kluctl.yml"), update) - - def update_deployment_yaml(self, dir, update): - def do_update(y): - if dir == ".": - set_dict_value(y, "commonLabels.project_name", self.project_name) - set_dict_value(y, "deleteByLabels.project_name", self.project_name) - y = update(y) - return y - self.update_yaml(os.path.join(self.get_deployment_dir(), dir, "deployment.yml"), do_update) - - def get_deployment_yaml(self, dir): - path = os.path.join(self.get_deployment_dir(), dir, "deployment.yml") - return yaml_load_file(path) - - def list_kustomize_deployments(self, dir="."): - ret = [] - y = self.get_deployment_yaml(dir) - for inc in y.get("includes", []): - ret += self.list_kustomize_deployments(os.path.join(dir, inc["path"])) - - ret += self.get_deployment_yaml(dir).get("kustomizeDirs", []) - return ret - - def list_kustomize_deployment_pathes(self, dir="."): - return [x["path"] for x in self.list_kustomize_deployments(dir)] - - - def update_kustomize_deployment(self, dir, update): - path = os.path.join(self.get_deployment_dir(), dir, "kustomization.yml") - self.update_yaml(path, update, message="Update kustomization.yml for %s" % dir) - - def copy_deployment(self, source): - shutil.copytree(source, self.get_deployment_dir()) - self._commit(self.get_deployment_dir(), all=True, message="copy deployment from %s" % source) - - def copy_kustomize_dir(self, source, target, add_to_deployment): - p = os.path.join(self.get_deployment_dir(), target) - shutil.copytree(source, p) - self._commit(p, all=True, message="copy kustomize dir from %s to %s" % (source, target)) - - def update_cluster(self, name, context, vars): - path = os.path.join(self.get_clusters_dir(), "clusters", "%s.yml" % name) - def do_update(y): - y = { - "cluster": { - "name": name, - "context": context, - **vars, - } - } - return y - self.update_yaml(path, do_update, message="add/update cluster %s" % name) - - def update_kind_cluster(self, kind_cluster: KindCluster, vars={}): - if kind_cluster.kubeconfig_path not in self.kubeconfigs: - self.kubeconfigs.append(kind_cluster.kubeconfig_path) - context = kind_cluster.kubectl("config", "current-context").strip() - self.update_cluster(kind_cluster.name, context, vars) - - def update_target(self, name, cluster, args={}): - def do_update(y): - targets = y.get("targets", []) - targets = [x for x in targets if x["name"] != name] - y["targets"] = targets - targets.append({ - "name": name, - "cluster": cluster, - "args": args, - }) - return y - self.update_kluctl_yaml(do_update) - - def add_deployment_include(self, dir, include, tags=None): - def do_update(y): - includes = y.setdefault("includes", []) - if any(x["path"] == include for x in includes): - return y - o = { - "path": include, - } - if tags is not None: - o["tags"] = tags - includes.append(o) - return y - self.update_deployment_yaml(dir, do_update) - - def add_deployment_includes(self, dir): - p = ["."] - for x in Path(dir).parts: - self.add_deployment_include(os.path.join(*p), x) - p.append(x) - - def add_kustomize_deployment(self, dir, resources, tags=None): - deployment_dir = os.path.dirname(dir) - if deployment_dir != "": - self.add_deployment_includes(deployment_dir) - - abs_deployment_dir = os.path.join(self.get_deployment_dir(), deployment_dir) - abs_kustomize_dir = os.path.join(self.get_deployment_dir(), dir) - - os.makedirs(abs_kustomize_dir, exist_ok=True) - - y = { - "apiVersion": "kustomize.config.k8s.io/v1beta1", - "kind": "Kustomization", - } - yaml_save_file(y, os.path.join(abs_kustomize_dir, "kustomization.yml")) - self._commit(abs_deployment_dir, all=True, message="add kustomize deployment %s" % dir) - - self.add_kustomize_resources(dir, resources) - - def do_update(y): - kustomize_dirs = y.setdefault("kustomizeDirs", []) - o = { - "path": os.path.basename(dir) - } - if tags is not None: - o["tags"] = tags - kustomize_dirs.append(o) - return y - self.update_deployment_yaml(deployment_dir, do_update) - - def add_kustomize_resources(self, dir, resources): - def do_update(y): - y.setdefault("resources", []) - y["resources"] += list(resources.keys()) - g = Git(os.path.join(self.get_deployment_dir(), dir)) - for name, content in resources.items(): - with open(os.path.join(self.get_deployment_dir(), dir, name), "wt") as f: - f.write(content) - g.add(name) - return y - self.update_kustomize_deployment(dir, do_update) - - def delete_kustomize_deployment(self, dir): - deployment_dir = os.path.dirname(dir) - - def do_update(y): - for i in range(len(y.get("kustomizeDirs", []))): - if y["kustomizeDirs"][i]["path"] == os.path.basename(dir): - del y["kustomizeDirs"][i] - break - return y - self.update_deployment_yaml(deployment_dir, do_update) - - def get_kluctl_project_dir(self): - return os.path.join(self.base_dir.name, "kluctl-project") - - def get_clusters_dir(self): - if self.clusters_external: - return os.path.join(self.base_dir.name, "external-clusters") - else: - return self.get_kluctl_project_dir() - - def get_deployment_dir(self): - if self.deployment_external: - return os.path.join(self.base_dir.name, "external-deployment") - else: - return self.get_kluctl_project_dir() - - def get_sealed_secrets_dir(self): - if self.sealed_secrets_external: - return os.path.join(self.base_dir.name, "external-sealed-secrets") - else: - return self.get_kluctl_project_dir() - - def kluctl(self, *args: str, check_rc=True, **kwargs): - kluctl_exe = shutil.which("kluctl") - args2 = [kluctl_exe] - args2 += args - - cwd = None - if self.kluctl_project_external: - args2 += ["--project-url", self.build_file_url(self.get_kluctl_project_dir())] - else: - cwd = self.get_kluctl_project_dir() - - if self.local_clusters: - args2 += ["--local-clusters", self.local_clusters] - if self.local_deployment: - args2 += ["--local-deployment", self.local_deployment] - if self.local_sealed_secrets: - args2 += ["--local-sealed-secrets", self.local_sealed_secrets] - - sep = ";" if sys.platform == "win32" else ":" - env = os.environ.copy() - env.update({ - "KUBECONFIG": sep.join(os.path.abspath(str(x)) for x in self.kubeconfigs), - }) - - logger.info("Running kluctl: %s" % " ".join(args2)) - - def do_log(lines): - for l in lines: - logger.info(l.rstrip()) - - rc, stdout, stderr = run_helper(args2, cwd=cwd, env=env, stdout_func=do_log, stderr_func=do_log, line_mode=True, return_std=True) - assert not check_rc or rc == 0, "rc=%d" % rc - return rc, stdout, stderr - - def build_file_url(self, path): - if sys.platform == "win32": - return "file:///%s" % path - else: - return "file://%s" % path diff --git a/kluctl/e2e/kluctl_test_project_helpers.py b/kluctl/e2e/kluctl_test_project_helpers.py deleted file mode 100644 index cb064ad65..000000000 --- a/kluctl/e2e/kluctl_test_project_helpers.py +++ /dev/null @@ -1,150 +0,0 @@ -from kluctl.e2e.kluctl_test_project import KluctlTestProject -from kluctl.utils.dict_utils import merge_dict, set_dict_value -from kluctl.utils.yaml_utils import yaml_dump, yaml_load, yaml_load_all, yaml_dump_all - - -def merge_metadata(y, labels, annotations): - if labels is not None: - merge_dict(y, { - "metadata": { - "labels": labels, - } - }, clone=False) - if annotations is not None: - merge_dict(y, { - "metadata": { - "annotations": annotations, - } - }, clone=False) - -pod_rbac=""" -apiVersion: v1 -kind: ServiceAccount -metadata: - name: {name} - namespace: {namespace} ---- -kind: RoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: "{name}" - namespace: "{namespace}" -subjects: -- kind: ServiceAccount - name: "{name}" - namespace: "{namespace}" -roleRef: - kind: ClusterRole - name: "cluster-admin" - apiGroup: rbac.authorization.k8s.io -""" - -deployment=""" -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {name} - namespace: {namespace} - labels: - app.kubernetes.io/name: {name} -spec: - replicas: 1 - selector: - matchLabels: - app.kubernetes.io/name: {name} - template: - metadata: - labels: - app.kubernetes.io/name: {name} - spec: - terminationGracePeriodSeconds: 0 - serviceAccountName: {name} - containers: - - image: {image} - imagePullPolicy: IfNotPresent - name: container -""" - -job=""" -apiVersion: batch/v1 -kind: Job -metadata: - name: {name} - namespace: {namespace} -spec: - template: - spec: - terminationGracePeriodSeconds: 0 - restartPolicy: OnFailure - serviceAccountName: {name} - containers: - - image: {image} - imagePullPolicy: IfNotPresent - name: container -""" - - -def add_deployment_helper(p: KluctlTestProject, dir, name, r, - namespace="default", tags=None, - labels=None, annotations=None): - resources = {} - - y = pod_rbac.format(name=name, namespace=namespace) - y = yaml_load_all(y) - for x in y: - merge_metadata(x, labels, annotations) - resources["rbac.yml"] = yaml_dump_all(y) - - merge_metadata(r, labels, annotations) - resources["deploy.yml"] = yaml_dump(r) - - p.add_kustomize_deployment(dir, resources, tags=tags) - -def add_deployment_deployment(p: KluctlTestProject, dir, name, - image, command, args, - namespace="default", **kwargs): - y = deployment.format(name=name, namespace=namespace, image=image) - y = yaml_load(y) - set_dict_value(y, "spec.template.spec.containers[0].command", command) - set_dict_value(y, "spec.template.spec.containers[0].args", args) - add_deployment_helper(p, dir, name, y, namespace=namespace, **kwargs) - -def add_job_deployment(p: KluctlTestProject, dir, name, - image, command, args, - namespace="default", **kwargs): - y = job.format(name=name, namespace=namespace, image=image) - y = yaml_load(y) - set_dict_value(y, "spec.template.spec.containers[0].command", command) - set_dict_value(y, "spec.template.spec.containers[0].args", args) - add_deployment_helper(p, dir, name, y, namespace=namespace, **kwargs) - -def add_busybox_deployment(p: KluctlTestProject, dir, name, - namespace="default"): - add_deployment_deployment(p, dir, name, "busybox", ["sleep"], ["1000"], namespace=namespace) - -def add_namespace_deployment(p: KluctlTestProject, dir, name): - y = { - "apiVersion": "v1", - "kind": "Namespace", - "metadata": { - "name": name, - }, - } - p.add_kustomize_deployment(dir, { - "namespace.yml": yaml_dump(y), - }) - -def add_configmap_deployment(p: KluctlTestProject, dir, name, namespace="default", data={}, tags=None, labels=None, annotations=None): - y = { - "apiVersion": "v1", - "kind": "ConfigMap", - "metadata": { - "name": name, - "namespace": namespace, - }, - "data": data, - } - merge_metadata(y, labels, annotations) - p.add_kustomize_deployment(dir, { - "configmap.yml": yaml_dump(y), - }, tags=tags) diff --git a/kluctl/e2e/test_command_bootstrap.py b/kluctl/e2e/test_command_bootstrap.py deleted file mode 100644 index 47c2ed5fd..000000000 --- a/kluctl/e2e/test_command_bootstrap.py +++ /dev/null @@ -1,52 +0,0 @@ -import os -import shutil -from tempfile import TemporaryDirectory - -from pytest_kind import KindCluster - -from kluctl import get_kluctl_package_dir -from kluctl.e2e.conftest import assert_readiness, assert_resource_exists, assert_resource_not_exists -from kluctl.e2e.kluctl_test_project import KluctlTestProject -from kluctl.utils.yaml_utils import yaml_save_file, yaml_load_file - -dummy_configmap = """ -apiVersion: v1 -kind: ConfigMap -metadata: - name: dummy-configmap - namespace: kube-system -data: - dummy: value -""" - -def test_command_bootstrap(kind_cluster: KindCluster): - bootstrap_path = os.path.join(get_kluctl_package_dir(), "bootstrap") - - with KluctlTestProject("bootstrap") as p: - p.update_kind_cluster(kind_cluster) - p.update_target("test", kind_cluster.name) - p.kluctl("bootstrap", "--yes", "--cluster", kind_cluster.name) - assert_readiness(kind_cluster, "kube-system", "Deployment/sealed-secrets", 60 * 5) - - # test upgrades - with TemporaryDirectory() as tmpdir: - shutil.copytree(bootstrap_path, tmpdir, dirs_exist_ok=True) - - with open(os.path.join(tmpdir, "sealed-secrets/dummy.yml"), mode="wt") as f: - f.write(dummy_configmap) - k = yaml_load_file(os.path.join(tmpdir, "sealed-secrets/kustomization.yml")) - k["resources"].append("dummy.yml") - yaml_save_file(k, os.path.join(tmpdir, "sealed-secrets/kustomization.yml")) - - with KluctlTestProject("bootstrap", local_deployment=tmpdir) as p: - p.update_kind_cluster(kind_cluster) - p.update_target("test", kind_cluster.name) - p.kluctl("bootstrap", "--yes", "--cluster", kind_cluster.name) - assert_resource_exists(kind_cluster, "kube-system", "ConfigMap/dummy-configmap") - - # test pruning - with KluctlTestProject("bootstrap") as p: - p.update_kind_cluster(kind_cluster) - p.update_target("test", kind_cluster.name) - p.kluctl("bootstrap", "--yes", "--cluster", kind_cluster.name) - assert_resource_not_exists(kind_cluster, "kube-system", "ConfigMap/dummy-configmap") diff --git a/kluctl/e2e/test_command_deploy.py b/kluctl/e2e/test_command_deploy.py deleted file mode 100644 index dd19738a6..000000000 --- a/kluctl/e2e/test_command_deploy.py +++ /dev/null @@ -1,23 +0,0 @@ -from pytest_kind import KindCluster - -from kluctl.e2e.conftest import assert_resource_exists, recreate_namespace, assert_resource_not_exists -from kluctl.e2e.kluctl_test_project import KluctlTestProject -from kluctl.e2e.kluctl_test_project_helpers import add_configmap_deployment - - -def test_command_deploy_simple(kind_cluster: KindCluster): - with KluctlTestProject("simple") as p: - recreate_namespace(kind_cluster, "simple") - - p.update_kind_cluster(kind_cluster) - p.update_target("test", kind_cluster.name) - - add_configmap_deployment(p, "cm", "cm", namespace="simple") - p.kluctl("deploy", "--yes", "-t", "test") - assert_resource_exists(kind_cluster, "simple", "ConfigMap/cm") - - add_configmap_deployment(p, "cm2", "cm2", namespace="simple") - p.kluctl("deploy", "--yes", "-t", "test", "--dry-run") - assert_resource_not_exists(kind_cluster, "simple", "ConfigMap/cm2") - p.kluctl("deploy", "--yes", "-t", "test") - assert_resource_exists(kind_cluster, "simple", "ConfigMap/cm2") diff --git a/kluctl/e2e/test_external_projects.py b/kluctl/e2e/test_external_projects.py deleted file mode 100644 index 423a73883..000000000 --- a/kluctl/e2e/test_external_projects.py +++ /dev/null @@ -1,61 +0,0 @@ -from pytest_kind import KindCluster - -from kluctl.e2e.conftest import assert_readiness, recreate_namespace -from kluctl.e2e.conftest import assert_resource_not_exists, assert_resource_exists -from kluctl.e2e.kluctl_test_project import KluctlTestProject -from kluctl.e2e.kluctl_test_project_helpers import add_busybox_deployment, add_configmap_deployment -from kluctl.utils.dict_utils import get_dict_value - -def do_test_project(kind_cluster, namespace, **kwargs): - recreate_namespace(kind_cluster, namespace) - - with KluctlTestProject("project-%s" % namespace, **kwargs) as p: - p.update_kind_cluster(kind_cluster, {"cluster_var": "cluster_value1"}) - p.update_target("test", kind_cluster.name, {"target_var": "target_value1"}) - add_busybox_deployment(p, "busybox", "busybox", namespace=namespace) - - p.kluctl("deploy", "--yes", "-t", "test") - assert_readiness(kind_cluster, namespace, "Deployment/busybox", 5 * 60) - - cm_data = { - "cluster_var": "{{ cluster.cluster_var }}", - "target_var": "{{ args.target_var }}" - } - - assert_resource_not_exists(kind_cluster, namespace, "ConfigMap/cm") - add_configmap_deployment(p, "cm", "cm", namespace=namespace, data=cm_data) - p.kluctl("deploy", "--yes", "-t", "test") - y = assert_resource_exists(kind_cluster, namespace, "ConfigMap/cm") - assert get_dict_value(y, "data.cluster_var") == "cluster_value1" - assert get_dict_value(y, "data.target_var") == "target_value1" - - p.update_kind_cluster(kind_cluster, {"cluster_var": "cluster_value2"}) - p.kluctl("deploy", "--yes", "-t", "test") - y = assert_resource_exists(kind_cluster, namespace, "ConfigMap/cm") - assert get_dict_value(y, "data.cluster_var") == "cluster_value2" - assert get_dict_value(y, "data.target_var") == "target_value1" - - p.update_target("test", kind_cluster.name, {"target_var": "target_value2"}) - p.kluctl("deploy", "--yes", "-t", "test") - y = assert_resource_exists(kind_cluster, namespace, "ConfigMap/cm") - assert get_dict_value(y, "data.cluster_var") == "cluster_value2" - assert get_dict_value(y, "data.target_var") == "target_value2" - -def test_external_kluctl_project(kind_cluster: KindCluster): - do_test_project(kind_cluster, "external-kluctl-project", kluctl_project_external=True) - -def test_external_clusters_project(kind_cluster: KindCluster): - do_test_project(kind_cluster, "external-clusters-project", clusters_external=True) - -def test_external_deployment_project(kind_cluster: KindCluster): - do_test_project(kind_cluster, "external-deployment-project", deployment_external=True) - -def test_external_sealed_secrets_project(kind_cluster: KindCluster): - do_test_project(kind_cluster, "external-sealed-secrets-project", sealed_secrets_external=True) - -def test_all_projects_external(kind_cluster: KindCluster): - do_test_project(kind_cluster, "external-all-projects", - kluctl_project_external=True, - clusters_external=True, - deployment_external=True, - sealed_secrets_external=True) diff --git a/kluctl/e2e/test_hooks.py b/kluctl/e2e/test_hooks.py deleted file mode 100644 index ee60089b8..000000000 --- a/kluctl/e2e/test_hooks.py +++ /dev/null @@ -1,182 +0,0 @@ -import base64 -import contextlib - -from pytest_kind import KindCluster - -from kluctl.e2e.conftest import recreate_namespace, assert_resource_not_exists, assert_resource_exists -from kluctl.e2e.kluctl_test_project import KluctlTestProject -from kluctl.e2e.kluctl_test_project_helpers import add_job_deployment -from kluctl.utils.dict_utils import get_dict_value -from kluctl.utils.yaml_utils import yaml_load, yaml_dump - -hook_script=""" -kubectl get configmap -oyaml > /tmp/result.yml -cat /tmp/result.yml -if ! kubectl get secret {name}-result; then - name="{name}-result" -else - name="{name}-result2" -fi -kubectl create secret generic $name --from-file=result=/tmp/result.yml -kubectl delete configmap cm2 || true -""" - -def add_hook_deployment(p: KluctlTestProject, name, namespace, is_helm, hook, hook_deletion_policy=None): - a = {} - if is_helm: - a["helm.sh/hook"] = hook - if hook_deletion_policy is not None: - a["helm.sh/hook-deletion-policy"] = hook_deletion_policy - else: - a["kluctl.io/hook"] = hook - if hook_deletion_policy is not None: - a["kluctl.io/hook-deletion-policy"] = hook_deletion_policy - - script = hook_script.format(name=name, namespace=namespace) - - add_job_deployment(p, name, name, "bitnami/kubectl:1.21", - command=["sh"], args=["-c", script], - namespace=namespace, annotations=a) - -def add_configmap(p: KluctlTestProject, dir, name, namespace): - y = { - "apiVersion": "v1", - "kind": "ConfigMap", - "metadata": { - "name": name, - "namespace": namespace, - }, - "data": {}, - } - - p.add_kustomize_resources(dir, { - "%s.yml" % name: yaml_dump(y) - }) - -def get_hook_result(kind_cluster: KindCluster, p: KluctlTestProject, secret_name): - y = kind_cluster.kubectl("-n", p.project_name, "get", "secret", secret_name, "-o", "yaml") - y = yaml_load(y) - y = get_dict_value(y, "data.result") - y = base64.b64decode(y).decode("utf-8") - y = yaml_load(y) - return y - -def get_hook_result_cm_names(kind_cluster: KindCluster, p: KluctlTestProject, second=False): - y = get_hook_result(kind_cluster, p, "hook-result" if not second else "hook-result2") - return [get_dict_value(x, "metadata.name") for x in y["items"]] - -@contextlib.contextmanager -def prepare_project(kind_cluster: KindCluster, name, hook, hook_deletion_policy): - namespace = "hook-%s" % name - with KluctlTestProject(namespace) as p: - recreate_namespace(kind_cluster, namespace) - - p.update_kind_cluster(kind_cluster) - p.update_target("test", kind_cluster.name) - - add_hook_deployment(p, "hook", namespace, is_helm=False, hook=hook, hook_deletion_policy=hook_deletion_policy) - add_configmap(p, "hook", "cm1", namespace) - - yield p - -def ensure_hook_executed(kind_cluster, p: KluctlTestProject): - try: - kind_cluster.kubectl("delete", "-n", p.project_name, "secret", "hook-result") - kind_cluster.kubectl("delete", "-n", p.project_name, "secret", "hook-result2") - except: - pass - p.kluctl("deploy", "--yes", "-t", "test") - assert_resource_exists(kind_cluster, p.project_name, "ConfigMap/cm1") - -def ensure_hook_not_executed(kind_cluster, p: KluctlTestProject): - try: - kind_cluster.kubectl("delete", "-n", p.project_name, "secret", "hook-result") - kind_cluster.kubectl("delete", "-n", p.project_name, "secret", "hook-result2") - except: - pass - p.kluctl("deploy", "--yes", "-t", "test") - assert_resource_not_exists(kind_cluster, p.project_name, "Secret/hook-result") - -def test_hooks_pre_deploy_initial(kind_cluster: KindCluster): - with prepare_project(kind_cluster, "pre-deploy-initial", "pre-deploy-initial", None) as p: - ensure_hook_executed(kind_cluster, p) - assert "cm1" not in get_hook_result_cm_names(kind_cluster, p) - ensure_hook_not_executed(kind_cluster, p) - -def test_hooks_post_deploy_initial(kind_cluster: KindCluster): - with prepare_project(kind_cluster, "post-deploy-initial", "post-deploy-initial", None) as p: - ensure_hook_executed(kind_cluster, p) - assert "cm1" in get_hook_result_cm_names(kind_cluster, p) - ensure_hook_not_executed(kind_cluster, p) - -def test_hooks_pre_deploy_upgrade(kind_cluster: KindCluster): - with prepare_project(kind_cluster, "pre-deploy-upgrade", "pre-deploy-upgrade", None) as p: - add_configmap(p, "hook", "cm2", p.project_name) - ensure_hook_not_executed(kind_cluster, p) - kind_cluster.kubectl("delete", "-n", p.project_name, "configmap", "cm1") - ensure_hook_executed(kind_cluster, p) - assert "cm1" not in get_hook_result_cm_names(kind_cluster, p) - ensure_hook_executed(kind_cluster, p) - assert "cm1" in get_hook_result_cm_names(kind_cluster, p) - - -def test_hooks_post_deploy_upgrade(kind_cluster: KindCluster): - with prepare_project(kind_cluster, "post-deploy-upgrade", "post-deploy-upgrade", None) as p: - add_configmap(p, "hook", "cm2", p.project_name) - ensure_hook_not_executed(kind_cluster, p) - kind_cluster.kubectl("delete", "-n", p.project_name, "configmap", "cm1") - ensure_hook_executed(kind_cluster, p) - assert "cm1" in get_hook_result_cm_names(kind_cluster, p) - -def do_test_hooks_pre_deploy(kind_cluster: KindCluster, name, hooks): - with prepare_project(kind_cluster, name, hooks, None) as p: - add_configmap(p, "hook", "cm2", p.project_name) - ensure_hook_executed(kind_cluster, p) - kind_cluster.kubectl("delete", "-n", p.project_name, "configmap", "cm1") - ensure_hook_executed(kind_cluster, p) - assert "cm1" not in get_hook_result_cm_names(kind_cluster, p) - ensure_hook_executed(kind_cluster, p) - assert "cm1" in get_hook_result_cm_names(kind_cluster, p) - -def do_test_hooks_post_deploy(kind_cluster: KindCluster, name, hooks): - with prepare_project(kind_cluster, name, hooks, None) as p: - add_configmap(p, "hook", "cm2", p.project_name) - ensure_hook_executed(kind_cluster, p) - kind_cluster.kubectl("delete", "-n", p.project_name, "configmap", "cm1") - ensure_hook_executed(kind_cluster, p) - assert "cm1" in get_hook_result_cm_names(kind_cluster, p) - -def do_test_hooks_pre_post_deploy(kind_cluster: KindCluster, name, hooks): - with prepare_project(kind_cluster, name, hooks, None) as p: - add_configmap(p, "hook", "cm2", p.project_name) - ensure_hook_executed(kind_cluster, p) - assert "cm1" not in get_hook_result_cm_names(kind_cluster, p) - assert "cm2" not in get_hook_result_cm_names(kind_cluster, p) - assert "cm1" in get_hook_result_cm_names(kind_cluster, p, True) - assert "cm2" in get_hook_result_cm_names(kind_cluster, p, True) - - ensure_hook_executed(kind_cluster, p) - assert "cm1" in get_hook_result_cm_names(kind_cluster, p) - assert "cm2" not in get_hook_result_cm_names(kind_cluster, p) - assert "cm1" in get_hook_result_cm_names(kind_cluster, p, True) - assert "cm2" in get_hook_result_cm_names(kind_cluster, p, True) - -def test_hooks_pre_deploy(kind_cluster: KindCluster): - do_test_hooks_pre_deploy(kind_cluster, "pre-deploy", "pre-deploy") - -def test_hooks_pre_deploy2(kind_cluster: KindCluster): - # same as pre-deploy - do_test_hooks_pre_deploy(kind_cluster, "pre-deploy2", "pre-deploy-initial,pre-deploy-upgrade") - -def test_hooks_post_deploy(kind_cluster: KindCluster): - do_test_hooks_post_deploy(kind_cluster, "post-deploy", "post-deploy") - -def test_hooks_post_deploy2(kind_cluster: KindCluster): - # same as post-deploy - do_test_hooks_post_deploy(kind_cluster, "post-deploy2", "post-deploy-initial,post-deploy-upgrade") - -def test_hooks_pre_post_deploy(kind_cluster: KindCluster): - do_test_hooks_pre_post_deploy(kind_cluster, "pre-post-deploy", "pre-deploy,post-deploy") - -def test_hooks_pre_post_deploy2(kind_cluster: KindCluster): - do_test_hooks_pre_post_deploy(kind_cluster, "pre-post-deploy2", "pre-deploy-initial,pre-deploy-upgrade,post-deploy-initial,post-deploy-upgrade") diff --git a/kluctl/e2e/test_inclusion.py b/kluctl/e2e/test_inclusion.py deleted file mode 100644 index a8b405bcf..000000000 --- a/kluctl/e2e/test_inclusion.py +++ /dev/null @@ -1,187 +0,0 @@ -from pytest_kind import KindCluster - -from kluctl.e2e.conftest import recreate_namespace -from kluctl.e2e.kluctl_test_project import KluctlTestProject -from kluctl.e2e.kluctl_test_project_helpers import add_configmap_deployment -from kluctl.utils.dict_utils import get_dict_value -from kluctl.utils.yaml_utils import yaml_load - - -def prepare_project(kind_cluster: KindCluster, p: KluctlTestProject, with_includes): - recreate_namespace(kind_cluster, p.project_name) - - p.update_kind_cluster(kind_cluster) - p.update_target("test", kind_cluster.name) - add_configmap_deployment(p, "cm1", "cm1", namespace=p.project_name) - add_configmap_deployment(p, "cm2", "cm2", namespace=p.project_name) - add_configmap_deployment(p, "cm3", "cm3", namespace=p.project_name, tags=["tag1", "tag2"]) - add_configmap_deployment(p, "cm4", "cm4", namespace=p.project_name, tags=["tag1", "tag3"]) - add_configmap_deployment(p, "cm5", "cm5", namespace=p.project_name, tags=["tag1", "tag4"]) - add_configmap_deployment(p, "cm6", "cm6", namespace=p.project_name, tags=["tag1", "tag5"]) - add_configmap_deployment(p, "cm7", "cm7", namespace=p.project_name, tags=["tag1", "tag6"]) - - if with_includes: - p.add_deployment_include(".", "include1") - add_configmap_deployment(p, "include1/icm1", "icm1", namespace=p.project_name, tags=["itag1", "itag2"]) - - p.add_deployment_include(".", "include2") - add_configmap_deployment(p, "include2/icm2", "icm2", namespace=p.project_name) - add_configmap_deployment(p, "include2/icm3", "icm3", namespace=p.project_name, tags=["itag3", "itag4"]) - - p.add_deployment_include(".", "include3", tags=["itag5"]) - add_configmap_deployment(p, "include3/icm4", "icm4", namespace=p.project_name) - add_configmap_deployment(p, "include3/icm5", "icm5", namespace=p.project_name, tags=["itag5", "itag6"]) - -def assert_exists_helper(kind_cluster, p, should_exists, add=None, remove=None): - if add is not None: - for x in add: - should_exists.add(x) - if remove is not None: - for x in remove: - if x in should_exists: - should_exists.remove(x) - exists = kind_cluster.kubectl("-n", p.project_name, "get", "configmaps", "-l", "project_name=%s" % p.project_name, "-o", "yaml") - exists = yaml_load(exists)["items"] - found = set(get_dict_value(x, "metadata.name") for x in exists) - assert found == should_exists - -def test_inclusion_tags(kind_cluster: KindCluster): - with KluctlTestProject("inclusion-tags") as p: - prepare_project(kind_cluster, p, False) - - should_exists = set() - def do_assert_exists(add=None): - assert_exists_helper(kind_cluster, p, should_exists, add) - - do_assert_exists() - - # test default tags - p.kluctl("deploy", "--yes", "-t", "test", "-I", "cm1") - do_assert_exists({"cm1"}) - p.kluctl("deploy", "--yes", "-t", "test", "-I", "cm2") - do_assert_exists({"cm2"}) - - # cm3/cm4 don't have default tags, so they should not deploy - p.kluctl("deploy", "--yes", "-t", "test", "-I", "cm3") - do_assert_exists() - - # but with tag2, at least cm3 should deploy - p.kluctl("deploy", "--yes", "-t", "test", "-I", "tag2") - do_assert_exists({"cm3"}) - - # let's try 2 tags at once - p.kluctl("deploy", "--yes", "-t", "test", "-I", "tag3", "-I", "tag4") - do_assert_exists({"cm4", "cm5"}) - - # And now let's try a tag that matches all non-default ones - p.kluctl("deploy", "--yes", "-t", "test", "-I", "tag1") - do_assert_exists({"cm6", "cm7"}) - -def test_exclusion_tags(kind_cluster: KindCluster): - with KluctlTestProject("inclusion-exclusion") as p: - prepare_project(kind_cluster, p, False) - - should_exists = set() - def do_assert_exists(add=None): - assert_exists_helper(kind_cluster, p, should_exists, add) - - do_assert_exists() - - # Exclude everything except cm1 - p.kluctl("deploy", "--yes", "-t", "test", "-E", "cm2", "-E", "tag1") - do_assert_exists({"cm1"}) - - # Test that exclusion has precedence over inclusion - p.kluctl("deploy", "--yes", "-t", "test", "-E", "cm2", "-E", "tag1", "-I", "cm2") - do_assert_exists() - - # Test that exclusion has precedence over inclusion - p.kluctl("deploy", "--yes", "-t", "test", "-I", "tag1", "-E", "tag6") - do_assert_exists({"cm3", "cm4", "cm5", "cm6"}) - -def test_inclusion_include_dirs(kind_cluster: KindCluster): - with KluctlTestProject("inclusion-dirs") as p: - prepare_project(kind_cluster, p, True) - - should_exists = set() - def do_assert_exists(add=None): - assert_exists_helper(kind_cluster, p, should_exists, add) - - do_assert_exists() - - p.kluctl("deploy", "--yes", "-t", "test", "-I", "itag1") - do_assert_exists({"icm1"}) - - p.kluctl("deploy", "--yes", "-t", "test", "-I", "include2") - do_assert_exists({"icm2", "icm3"}) - - p.kluctl("deploy", "--yes", "-t", "test", "-I", "itag5") - do_assert_exists({"icm4", "icm5"}) - -def test_inclusion_kustomize_dirs(kind_cluster: KindCluster): - with KluctlTestProject("inclusion-kustomize-dirs") as p: - prepare_project(kind_cluster, p, True) - - should_exists = set() - def do_assert_exists(add=None): - assert_exists_helper(kind_cluster, p, should_exists, add) - - do_assert_exists() - - p.kluctl("deploy", "--yes", "-t", "test", "--include-kustomize-dir", "include1/icm1") - do_assert_exists({"icm1"}) - - p.kluctl("deploy", "--yes", "-t", "test", "--include-kustomize-dir", "include2/icm3") - do_assert_exists({"icm3"}) - - p.kluctl("deploy", "--yes", "-t", "test", "--exclude-kustomize-dir", "include3/icm5") - do_assert_exists(set(p.list_kustomize_deployment_pathes()) - {"icm5"}) - -def test_inclusion_prune(kind_cluster: KindCluster): - with KluctlTestProject("inclusion-prune") as p: - prepare_project(kind_cluster, p, False) - - should_exists = set() - def do_assert_exists(add=None, remove=None): - assert_exists_helper(kind_cluster, p, should_exists, add, remove) - - p.kluctl("deploy", "--yes", "-t", "test") - do_assert_exists(p.list_kustomize_deployment_pathes()) - - p.delete_kustomize_deployment("cm1") - p.kluctl("prune", "--yes", "-t", "test", "-I", "non-existent-tag") - do_assert_exists() - - p.kluctl("prune", "--yes", "-t", "test", "-I", "cm1") - do_assert_exists(remove={"cm1"}) - - p.delete_kustomize_deployment("cm2") - p.kluctl("prune", "--yes", "-t", "test", "-E", "cm2") - do_assert_exists() - - p.delete_kustomize_deployment("cm3") - p.kluctl("prune", "--yes", "-t", "test", "--exclude-kustomize-dir", "cm3") - do_assert_exists(remove={"cm2"}) - - p.kluctl("prune", "--yes", "-t", "test") - do_assert_exists(remove={"cm3"}) - -def test_inclusion_delete(kind_cluster: KindCluster): - with KluctlTestProject("inclusion-delete") as p: - prepare_project(kind_cluster, p, False) - - should_exists = set() - def do_assert_exists(add=None, remove=None): - assert_exists_helper(kind_cluster, p, should_exists, add, remove) - - p.kluctl("deploy", "--yes", "-t", "test") - do_assert_exists(p.list_kustomize_deployment_pathes()) - - p.kluctl("delete", "--yes", "-t", "test", "-I", "non-existent-tag") - do_assert_exists() - - p.kluctl("delete", "--yes", "-t", "test", "-I", "cm1") - do_assert_exists(remove={"cm1"}) - - p.kluctl("delete", "--yes", "-t", "test", "-E", "cm2") - do_assert_exists(remove=set(p.list_kustomize_deployment_pathes()) - {"cm1", "cm2"}) diff --git a/kluctl/image_registries/__init__.py b/kluctl/image_registries/__init__.py deleted file mode 100644 index e3a4f7797..000000000 --- a/kluctl/image_registries/__init__.py +++ /dev/null @@ -1,29 +0,0 @@ -import os - -from kluctl.image_registries.generic_registry import GenericRegistry -from kluctl.utils.env_config_sets import parse_env_config_sets -from kluctl.utils.utils import parse_bool - - -def init_image_registries(): - ret = [] - - default_tlsverify = parse_bool(os.environ.get("KLUCTL_REGISTRY_DEFAULT_TLSVERIFY", "true")) - - generic = GenericRegistry(default_tlsverify) - ret.append(generic) - - def add_registry(s): - host = s.get("HOST") - username = s.get("USERNAME") - password = s.get("PASSWORD") - tlsverify = s.get("TLSVERIFY") - if tlsverify is not None: - tlsverify = parse_bool(tlsverify) - if username and password: - generic.add_creds(host, username, password, tlsverify) - - for idx, s in parse_env_config_sets("KLUCTL_REGISTRY").items(): - add_registry(s) - - return ret diff --git a/kluctl/image_registries/generic_registry.py b/kluctl/image_registries/generic_registry.py deleted file mode 100644 index bf445ef42..000000000 --- a/kluctl/image_registries/generic_registry.py +++ /dev/null @@ -1,324 +0,0 @@ -import base64 -import contextlib -import hashlib -import http -import json -import logging -import os -import socket -import threading -from calendar import timegm -from collections import namedtuple -from datetime import datetime -from urllib.parse import urlparse - -import jwt -import requests -from dxf import DXF -from dxf.exceptions import DXFUnauthorizedError -from jwt import InvalidTokenError -from requests import HTTPError - -from kluctl.image_registries.images_registry import ImagesRegistry -from kluctl.utils.exceptions import CommandError -from kluctl.utils.run_helper import run_helper -from kluctl.utils.thread_safe_cache import ThreadSafeCache, ThreadSafeMultiCache -from kluctl.utils.utils import get_tmp_base_dir - -logger = logging.getLogger(__name__) - -DOCKER_HUB_REGISTRY = "registry-1.docker.io" -DOCKER_HUB_REGISTRY2 = "docker.io" -DOCKER_HUB_AUTH_ENTRY = "https://index.docker.io/v1/" -USER_AGENT = "Docker-Client/19.03.2 (linux)'" - -DockerCreds = namedtuple("DockerCreds", "username password tlsverify") - -class GenericRegistry(ImagesRegistry): - def __init__(self, default_tlsverify): - self.default_tlsverify = default_tlsverify - self.creds = {} - self.creds_cache = ThreadSafeMultiCache() - self.dns_cache = ThreadSafeCache() - self.info_cache = ThreadSafeMultiCache() - self.client_cache = ThreadSafeMultiCache() - self.first_auth_cache = ThreadSafeCache() - - self.shared_session = requests.Session() - adapter = requests.adapters.HTTPAdapter(pool_connections=4, pool_maxsize=16) - self.shared_session.mount('https://', adapter) - - def add_creds(self, host, username, password, tlsverify): - if host is None: - host = DOCKER_HUB_REGISTRY - self.creds[host] = DockerCreds(username=username, password=password, tlsverify=tlsverify) - - def split_host_and_port(self, h): - if h.startswith("http://") or h.startswith("https://"): - url = urlparse(h) - host = url.hostname - port = url.port - if port is None: - if url.scheme == "http": - port = 80 - else: - port = 443 - else: - if ":" in h: - s = h.split(":") - port = s[-1] - host = h[:-len(port) - 1] - port = int(port) - else: - host = h - port = 443 - return host, port - - def parse_docker_auths(self, config): - ret = [] - auths = config.get("auths", {}) - for k, v in auths.items(): - try: - host, port = self.split_host_and_port(k) - ret.append({ - "key": k, - "auth": v, - "host": host, - "port": port, - }) - except Exception as e: - logger.debug("Failed to parse auth entry: %s. Errors=%s" % (k, str(e))) - return ret - - def do_get_creds(self, host): - host, port = self.split_host_and_port(host) - - logger.debug(f"looking up {host} in creds") - if host in self.creds: - logger.debug(f"found entry with username {self.creds[host].username} in creds") - return self.creds[host] - - logger.debug("entry not found in creds, looking into docker config") - - # try from docker creds - try: - with open(os.path.expanduser("~/.docker/config.json")) as f: - config = json.load(f) - except Exception as e: - logger.warning("Failed to load docker config. Error=%s" % str(e)) - return None - - try: - auths = self.parse_docker_auths(config) - - auth_entry = None - if host == DOCKER_HUB_REGISTRY: - auth_entry = DOCKER_HUB_AUTH_ENTRY - else: - for a in auths: - if a["host"] == host and a["port"] == port: - auth_entry = a["key"] - break - auth = config.get("auths", {}).get(auth_entry) if auth_entry else None - - if auth is None: - logger.debug("No auth entry in docker config") - return None - if "username" in auth and "password" in auth: - logger.debug(f"found docker config entry with username {auth['username']}") - return DockerCreds(username=auth["username"], password=auth["password"], tlsverify=None) - if "auth" in auth: - a = base64.b64decode(auth["auth"]).decode("utf-8") - a = a.split(":", 1) - if len(a) != 2: - logger.debug(f"don't know how to handle auth entry in docker config with len={len(a)}") - return None - logger.debug(f"found docker config entry with username {a[0]}") - return DockerCreds(username=a[0], password=a[1], tlsverify=None) - cred_store = config.get("credsStore") - if cred_store is None: - logger.warning("No 'credsStore' found in docker config") - else: - cred_exe = f"docker-credential-{cred_store}" - logger.debug(f"trying credStore {cred_exe}") - rc, stdout, stderr = run_helper([cred_exe, "get"], input=auth_entry, return_std=True) - if rc != 0: - logger.debug(f"{cred_exe} exited with status {rc}") - return None - j = json.loads(stdout) - logger.debug(f"{cred_exe} returned auth entry with username {j['Username']}") - return DockerCreds(username=j["Username"], password=j["Secret"], tlsverify=None) - except Exception as e: - logger.warning("Exception while loading docker config/creds. Error=%s" % str(e)) - return None - - def get_creds(self, host): - return self.creds_cache.get(host, "creds", lambda: self.do_get_creds(host)) - - def get_dns_info(self, n): - try: - return self.dns_cache.get(n, lambda: socket.gethostbyname(n)) - except: - return None - - def get_info_response2(self, host, path): - headers = { - "User-Agent": USER_AGENT, - } - - def do_get_info(): - tlsverify = None - if host in self.creds: - tlsverify = self.creds[host][2] - if tlsverify is None: - tlsverify = self.default_tlsverify - url = f"https://{host}{path}" - r = self.shared_session.get(url, headers=headers, verify=tlsverify) - _ = r.content - r.close() - logger.debug(f"GET for {url} returned status {r.status_code} and headers {r.headers}") - return r - - return self.info_cache.get((host, path), "info", do_get_info) - - def get_info_response(self, host, repo): - r = self.get_info_response2(host, "/v2") - if r.status_code == 404: - r = self.get_info_response2(host, f"/v2/{repo}/tags/list") - return r - - def parse_image(self, image): - s = image.split("/") - if s[0] == DOCKER_HUB_REGISTRY2: - s[0] = DOCKER_HUB_REGISTRY - if self.get_dns_info(s[0]) is not None: - return s[0], "/".join(s[1:]) - else: - return DOCKER_HUB_REGISTRY, image - - def get_cached_token_path(self, image, username, password): - hash = hashlib.sha256(f"{image}_{username}_{password}".encode("utf-8")).hexdigest() - path = os.path.join(get_tmp_base_dir(), "registry-tokens", hash) - return path - - def parse_token(self, token): - try: - return jwt.decode(token, - None, - algorithms=["RS256"], - options={"verify_signature": False} - ) - except InvalidTokenError as e: - return None - - def get_cached_token(self, image, username, password): - path = self.get_cached_token_path(image, username, password) - if not os.path.exists(path): - return None - with open(path, "r+t") as f: - token = f.read() - data = self.parse_token(token) - try: - exp = int(data["exp"]) - now = timegm(datetime.utcnow().utctimetuple()) - leeway = 10 - if exp - leeway < now: - raise Exception("Signature has expired") - return token - except Exception: - os.unlink(path) - return None - - def set_cached_token(self, image, username, password, token): - # ensure we can actually parse it (jwt compliant) - if self.parse_token(token) is None: - return - - path = self.get_cached_token_path(image, username, password) - os.makedirs(os.path.dirname(path), exist_ok=True) - with open(path, "wt") as f: - os.chmod(path, 0o600) - f.write(token) - - def build_auth_error(self, host, title): - error = f"{title}. Please ensure you provided correct registry " \ - f"credentials for '{host}', either by logging in with 'docker login {host}' or by " \ - f"providing environment variables as described in the documentation." - return error - - def get_client(self, image): - host, repo = self.parse_image(image) - if host == DOCKER_HUB_REGISTRY: - if len(repo.split("/")) == 1: - repo = "library/" + repo - - creds = self.get_creds(host) - tlsverify = None - if creds is not None: - tlsverify = creds.tlsverify - if tlsverify is None: - tlsverify = self.default_tlsverify - - username = creds.username if creds else None - password = creds.password if creds else None - - @contextlib.contextmanager - def first_auth_context(): - first = self.first_auth_cache.get((host, username, password), lambda: {"lock": threading.Lock(), "first": True, "exception": None}) - first["lock"].acquire() - has_lock = True - try: - if not first["first"]: - has_lock = False - first["lock"].release() - if first["exception"] is not None: - raise first["exception"] - first["first"] = False - yield None - except Exception as e: - if has_lock: - first["exception"] = e - raise e - finally: - if has_lock: - first["lock"].release() - - def do_authenticate(dxf, response): - token = self.get_cached_token(image, username, password) - if token is not None: - dxf.token = token - return token - with first_auth_context(): - logger.debug(f"calling dxf.authenticate with username={username}") - try: - token = dxf.authenticate(username=username, password=password, response=response, actions=["pull"]) - except DXFUnauthorizedError: - raise CommandError(self.build_auth_error(host, f"Got 401 Unauthorized from registry")) - except HTTPError as e: - if e.response.status_code == http.HTTPStatus.FORBIDDEN: - raise CommandError(self.build_auth_error(host, f"Got 403 Forbidden from registry")) - else: - raise - if token is not None: - self.set_cached_token(image, username, password, token) - - def do_get_client(): - info_response = self.get_info_response(host, repo) - dxf = DXF(host, repo, tlsverify=tlsverify, auth=do_authenticate) - # This is not how it was intended to be used, but it's the most effective way... - dxf._sessions[0] = self.shared_session - do_authenticate(dxf, info_response) - return dxf - - return self.client_cache.get(image, "client", do_get_client) - - def is_image_from_registry(self, image): - return True - - def list_tags_for_image(self, image): - dxf = self.get_client(image) - try: - tags = dxf.list_aliases(batch_size=100) - except DXFUnauthorizedError as e: - raise CommandError(self.build_auth_error(dxf._host, f"Got 401 Unauthorized from registry")) - return tags diff --git a/kluctl/image_registries/images_registry.py b/kluctl/image_registries/images_registry.py deleted file mode 100644 index 0fa8fd36a..000000000 --- a/kluctl/image_registries/images_registry.py +++ /dev/null @@ -1,6 +0,0 @@ -class ImagesRegistry(object): - def is_image_from_registry(self, image): - return False - - def list_tags_for_image(self, image): - pass diff --git a/kluctl/kluctl_project/__init__.py b/kluctl/kluctl_project/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/kluctl/kluctl_project/aws_secrets_manager.py b/kluctl/kluctl_project/aws_secrets_manager.py deleted file mode 100644 index 7d5e56aa8..000000000 --- a/kluctl/kluctl_project/aws_secrets_manager.py +++ /dev/null @@ -1,40 +0,0 @@ -import base64 -import os - -import boto3 -from botocore.exceptions import ClientError, BotoCoreError - -from kluctl.utils.aws_utils import parse_arn -from kluctl.utils.exceptions import InvalidKluctlProjectConfig - - -def get_aws_secrets_manager_secret(profile, region_name, secret_name): - if "AWS_PROFILE" in os.environ: - # Environment variable always takes precedence - profile = None - session = boto3.session.Session(profile_name=profile) - - if region_name is None: - try: - arn = parse_arn(secret_name) - region_name = arn["region"] - except: - raise InvalidKluctlProjectConfig("When omitting the AWS region, the secret name must be a valid ARN") - - sm_client = session.client( - service_name='secretsmanager', - region_name=region_name - ) - try: - secret_value = sm_client.get_secret_value(SecretId=secret_name) - except BotoCoreError as e: - raise InvalidKluctlProjectConfig("Getting secret %s from AWS secrets manager failed: %s" % (secret_name, str(e))) - except ClientError as e: - raise InvalidKluctlProjectConfig("Getting secret %s from AWS secrets manager failed: %s" % (secret_name, str(e))) - - if 'SecretString' in secret_value: - secret = secret_value['SecretString'] - else: - secret = base64.b64decode(secret_value['SecretBinary']).decode("utf-8") - - return secret diff --git a/kluctl/kluctl_project/kluctl_project.py b/kluctl/kluctl_project/kluctl_project.py deleted file mode 100644 index 287733aca..000000000 --- a/kluctl/kluctl_project/kluctl_project.py +++ /dev/null @@ -1,540 +0,0 @@ -import contextlib -import dataclasses -import gzip -import hashlib -import logging -import os -import re -import shutil -import tarfile -from contextlib import contextmanager -from pathlib import Path -from tempfile import TemporaryDirectory, NamedTemporaryFile -from typing import ContextManager - -import jsonschema - -from kluctl.schemas.schema import validate_kluctl_project_config, parse_git_project, target_config_schema -from kluctl.utils.dict_utils import copy_dict, get_dict_value, merge_dict -from kluctl.utils.exceptions import InvalidKluctlProjectConfig, CommandError -from kluctl.utils.git_utils import parse_git_url, get_git_commit, get_git_ref, MirroredGitRepo, git_ls_remote -from kluctl.utils.jinja2_utils import render_dict_strs -from kluctl.utils.k8s_cluster_base import load_cluster_config -from kluctl.utils.utils import get_tmp_base_dir, MyThreadPoolExecutor -from kluctl.utils.yaml_utils import yaml_load_file, yaml_save_file - -logger = logging.getLogger(__name__) - - -def load_kluctl_project_config(path): - try: - config = yaml_load_file(path) - except Exception as e: - raise InvalidKluctlProjectConfig(str(e), None) - validate_kluctl_project_config(config) - - if "clusters" in config and isinstance(config["clusters"], dict): - config["clusters"] = [config["clusters"]] - - secret_sets = set() - for s in get_dict_value(config, "secretsConfig.secretSets", []): - secret_sets.add(s["name"]) - for target in config.get("targets", []): - for s in get_dict_value(target, "sealingConfig.secretSets", []): - if s not in secret_sets: - raise InvalidKluctlProjectConfig("secretSet %s from target %s does not exist" % (s, target["name"])) - - return config - -@dataclasses.dataclass -class GitProjectInfo: - url: str - ref: str - commit: str - dir: str - -@dataclasses.dataclass -class DynamicTargetInfo: - base_target: dict - dir: str - extra_target_config: dict = None - git_project: object = None - ref: str = None - ref_pattern: str = None - -class KluctlProject: - def __init__(self, project_url, project_ref, config_file, local_clusters, local_deployment, local_sealed_secrets, tmp_dir): - self.project_url = project_url - self.project_ref = project_ref - self.config_file = config_file - self.local_clusters = local_clusters - self.local_deployment = local_deployment - self.local_sealed_secrets = local_sealed_secrets - - self.tmp_dir = tmp_dir - self.config = None - self.targets = None - self.involved_repos = {} - self.mirror_repos = {} - self.refs_for_urls = {} - - def create_tgz(self, path, metadata_path, reproducible): - with open(path, mode="wb") as f: - with gzip.GzipFile(filename="reproducible" if reproducible else None, mode="wb", compresslevel=9, fileobj=f, mtime=0 if reproducible else None) as gz: - with tarfile.TarFile.taropen("", mode="w", fileobj=gz) as tar: - def mf_filter(ti: tarfile.TarInfo): - if ".git" in Path(ti.name).parts: - return None - if reproducible: - # make the tar reproducible (always same hash) - ti.uid = 0 - ti.gid = 0 - ti.mtime = 0 - return ti - - metadata_yaml = { - "involved_repos": self.involved_repos, - "targets": self.targets, - } - if metadata_path is not None: - # metadata.yml outside of archive - yaml_save_file(metadata_yaml, metadata_path) - else: - # metadata.yml as part of the archive - with NamedTemporaryFile(dir=get_tmp_base_dir()) as tmp: - yaml_save_file(metadata_yaml, tmp.name) - tar.add(tmp.name, "metadata.yml", filter=mf_filter) - tar.add(self.config_file, ".kluctl.yml", filter=mf_filter) - tar.add(self.kluctl_project_dir, "kluctl-project", True, filter=mf_filter) - tar.add(self.deployment_dir, "deployment", True, filter=mf_filter) - tar.add(self.clusters_dir, "clusters", True, filter=mf_filter) - tar.add(self.sealed_secrets_dir, "sealed-secrets", True, filter=mf_filter) - - @staticmethod - def from_archive(path, metadata_path, tmp_dir): - if os.path.isfile(path): - with open(path, mode="rb") as f: - with tarfile.open(mode="r:gz", fileobj=f) as tgz: - tgz.extractall(tmp_dir) - dir = tmp_dir - else: - dir = path - if metadata_path is not None: - metadata = yaml_load_file(metadata_path) - else: - metadata = yaml_load_file(os.path.join(dir, "metadata.yml")) - deployment_dir = os.path.join(dir, "deployment") - project = KluctlProject(None, None, - os.path.join(dir, ".kluctl.yml"), - os.path.join(dir, "clusters"), - deployment_dir, - os.path.join(dir, "sealed-secrets"), - dir) - project.involved_repos = metadata["involved_repos"] - project.targets = metadata["targets"] - return project - - def build_clone_dir(self, url, ref): - if ref is None: - ref = "HEAD" - ref = ref.replace("/", "-").replace("\\", "-") - url = parse_git_url(url) - base_name = os.path.basename(url.path) - url_hash = hashlib.sha256(("%s:%s" % (url.host, url.path)).encode("utf-8")).hexdigest() - return os.path.join(self.tmp_dir, "%s-%s" % (base_name, url_hash), ref) - - def clone_git_project(self, git_project_config, default_git_subdir, do_add_involved_repo, do_lock): - os.makedirs(os.path.join(self.tmp_dir, "git"), exist_ok=True) - - git_project = parse_git_project(git_project_config["project"], default_git_subdir) - target_dir = self.build_clone_dir(git_project.url, git_project.ref) - - mirror_repo = self.mirror_repos.setdefault(git_project.url, MirroredGitRepo(git_project.url)) - with (mirror_repo.locked() if do_lock else contextlib.suppress()): - if not mirror_repo.has_updated: - mirror_repo.update() - mirror_repo.clone_project(git_project.ref, target_dir) - - git_ref = get_git_ref(target_dir) - - dir = target_dir - if git_project.subdir is not None: - dir = os.path.join(dir, git_project.subdir) - commit = get_git_commit(target_dir) - info = GitProjectInfo(url=git_project.url, ref=git_ref, commit=commit, dir=dir) - if do_add_involved_repo: - self.add_involved_repo(info.url, info.ref, {info.ref: info.commit}) - return info - - def local_project(self, dir): - return GitProjectInfo(url=None, ref=None, dir=dir, commit=None) - - def clone_kluctl_project(self): - if self.project_url is None: - return self.local_project(os.getcwd()) - - return self.clone_git_project({ - "project": { - "url": self.project_url, - "ref": self.project_ref - } - }, None, True, True) - - def add_involved_repo(self, url, ref_pattern, refs): - s = self.involved_repos.setdefault(url, []) - e = { - "ref_pattern": ref_pattern, - "refs": refs, - } - if e not in s: - s.append(e) - - def load(self, allow_git): - kluctl_project_info = self.clone_kluctl_project() - if self.config_file is None: - c = os.path.join(kluctl_project_info.dir, ".kluctl.yml") - if os.path.exists(c): - self.config_file = c - if self.config_file is not None: - self.config = load_kluctl_project_config(self.config_file) - else: - self.config = {} - - if allow_git: - self.update_git_caches() - - def do_clone(key, default_git_subdir, local_dir): - if local_dir is not None: - return [self.local_project(local_dir)] - if key not in self.config: - path = kluctl_project_info.dir - if default_git_subdir is not None: - path = os.path.join(path, default_git_subdir) - return [self.local_project(path)] - - if not allow_git: - raise InvalidKluctlProjectConfig("Tried to load something from git while it was not allowed") - - if isinstance(self.config[key], list): - projects = self.config[key] - else: - projects = [self.config[key]] - ret = [] - for project in projects: - info = self.clone_git_project(project, default_git_subdir, True, True) - ret.append(info) - return ret - - deployment_info = do_clone("deployment", None, self.local_deployment)[0] - clusters_infos = do_clone("clusters", "clusters", self.local_clusters) - sealed_secrets_info = do_clone("sealedSecrets", ".sealed-secrets", self.local_sealed_secrets)[0] - - merged_clusters_dir = os.path.join(self.tmp_dir, "merged-clusters") - self.merge_clusters_dirs(merged_clusters_dir, clusters_infos) - - self.kluctl_project_dir = kluctl_project_info.dir - self.deployment_dir = deployment_info.dir - self.clusters_dir = merged_clusters_dir - self.sealed_secrets_dir = sealed_secrets_info.dir - - def update_git_caches(self): - with MyThreadPoolExecutor() as executor: - futures = [] - def do_update_repo(repo): - with repo.locked(): - if not repo.has_updated: - repo.update() - - def do_update_projects(key): - if key not in self.config: - return - if isinstance(self.config[key], list): - projects = self.config[key] - else: - projects = [self.config[key]] - for project in projects: - url = parse_git_project(project["project"], None).url - if url in self.mirror_repos: - return - mirror_repo = MirroredGitRepo(url) - self.mirror_repos[url] = mirror_repo - f = executor.submit(do_update_repo, mirror_repo) - futures.append(f) - - do_update_projects("deployment") - do_update_projects("clusters") - do_update_projects("sealedSecrets") - - for target in self.config.get("targets", []): - target_config = target.get("targetConfig") - if target_config is None: - continue - - if "project" in target_config: - url = parse_git_project(target_config["project"], None).url - if url not in self.mirror_repos: - mirror_repo = MirroredGitRepo(url) - self.mirror_repos[url] = mirror_repo - f = executor.submit(do_update_repo, mirror_repo) - futures.append(f) - if url not in self.refs_for_urls: - self.refs_for_urls[url] = executor.submit(git_ls_remote, url) - - for f in futures: - f.result() - for url in list(self.refs_for_urls.keys()): - self.refs_for_urls[url] = self.refs_for_urls[url].result() - - def load_targets(self): - target_names = set() - self.targets = [] - - target_infos = [] - for base_target in self.config.get("targets", []): - target_infos += self.prepare_dynamic_targets(base_target) - - self.clone_dynamic_targets(target_infos) - - cluster_vars_cache = {} - for target_info in target_infos: - try: - target = self.build_dynamic_target(target_info.base_target, target_info.dir) - if target_info.extra_target_config is not None: - merge_dict(target, {"targetConfig": target_info.extra_target_config}, clone=False) - except Exception as e: - # Only fail if non-dynamic targets fail to load - if target_info.ref_pattern is None: - raise e - logger.warning("Failed to load dynamic target config for project. Error=%s" % (str(e))) - continue - - target = self.render_target(target, cluster_vars_cache) - target["baseTarget"] = target_info.base_target - - if target["name"] in target_names: - logger.warning("Duplicate target %s" % target["name"]) - else: - target_names.add(target["name"]) - self.targets.append(target) - - def render_target(self, target, cluster_vars_cache): - errors = [] - # Try rendering the target multiple times, until all values can be rendered successfully. This allows the target - # to reference itself in complex ways. We'll also try loading the cluster vars in each iteration. - for i in range(10): - jinja2_vars = { - "target": target - } - try: - # Try to load cluster vars. This might fail in case jinja templating is used in the cluster name - # of the target. We assume that this will then succeed in a later iteration - cluster_vars = cluster_vars_cache.get(target["cluster"]) - if isinstance(cluster_vars, Exception): - raise cluster_vars - if cluster_vars is None: - cluster_vars = load_cluster_config(self.clusters_dir, target["cluster"]) - cluster_vars_cache[target["cluster"]] = cluster_vars - jinja2_vars["cluster"] = cluster_vars - except Exception as e: - cluster_vars_cache[target["cluster"]] = e - pass - target2, errors = render_dict_strs(target, jinja2_vars, do_raise=False) - if not errors and target == target2: - break - target = target2 - if errors: - raise errors[0] - return target - - def prepare_dynamic_targets(self, base_target): - target_config = base_target.get("targetConfig") - if target_config and "project" in target_config: - return self.prepare_dynamic_targets_external(base_target) - else: - return self.prepare_dynamic_targets_simple(base_target) - - def prepare_dynamic_targets_simple(self, base_target): - if "targetConfig" in base_target: - target_config = base_target["targetConfig"] - if "ref" in target_config or "refPattern" in target_config: - raise InvalidKluctlProjectConfig("'ref' and/or 'refPattern' are not allowed for non-external dynamic targets") - - dynamic_targets = [DynamicTargetInfo( - base_target=base_target, - dir=self.kluctl_project_dir, - )] - return dynamic_targets - - def prepare_dynamic_targets_external(self, base_target): - target_config = base_target["targetConfig"] - git_project = parse_git_project(target_config["project"], None) - refs = self.refs_for_urls[git_project.url] - - target_config_ref = target_config.get("ref") - ref_pattern = target_config.get("refPattern") - - if target_config_ref is not None and ref_pattern is not None: - raise InvalidKluctlProjectConfig("'refPattern' and 'ref' can't be specified together") - - default_branch = None - for ref, commit in refs.items(): - if ref != "HEAD" and commit == refs["HEAD"]: - default_branch = ref[len("refs/heads/"):] - - if target_config_ref is None and ref_pattern is None: - # use default branch of repo - target_config_ref = default_branch - if target_config_ref is None: - raise InvalidKluctlProjectConfig("Git project %s seems to have no default branch" % git_project.url) - - matched_refs = [] - if target_config_ref is not None: - if "refs/heads/%s" % target_config_ref not in refs: - raise InvalidKluctlProjectConfig("Git project %s has no ref %s" % (git_project.url, target_config_ref)) - ref_pattern = target_config_ref - - ref_pattern_re = re.compile(r'^refs/heads/%s$' % ref_pattern) - for ref, commit in refs.items(): - if not ref_pattern_re.match(ref): - continue - matched_refs.append(ref[len("refs/heads/"):]) - - dynamic_targets = [] - - for ref in matched_refs: - dynamic_targets.append(DynamicTargetInfo( - base_target=base_target, - dir=self.build_clone_dir(git_project.url, ref), - git_project=git_project, - ref=ref, - ref_pattern=ref_pattern, - extra_target_config={ - "ref": ref, - "defaultBranch": default_branch, - } - )) - - return dynamic_targets - - def clone_dynamic_targets(self, dynamic_targets): - @contextmanager - def lock_all_repos(): - for r in self.mirror_repos.values(): - r.lock() - try: - yield - finally: - for r in self.mirror_repos.values(): - r.unlock() - - with lock_all_repos(), MyThreadPoolExecutor(max_workers=8) as executor: - unique_clones = {} - for target_info in dynamic_targets: - if target_info.git_project is None: - continue - - if target_info.dir in unique_clones: - continue - - f = executor.submit(self.clone_git_project, { - "project": { - "url": target_info.git_project.url, - "ref": target_info.ref, - } - }, None, False, False) - unique_clones[target_info.dir] = f - - refs_by_url_and_pattern = {} - - for target_info in dynamic_targets: - if target_info.git_project is None: - continue - info = unique_clones[target_info.dir].result() - refs_by_url_and_pattern.setdefault(info.url, {}).setdefault(target_info.ref_pattern, {})[info.ref] = info.commit - - for url, ref_patterns in refs_by_url_and_pattern.items(): - for ref_pattern, refs in ref_patterns.items(): - self.add_involved_repo(url, ref_pattern, refs) - - def build_dynamic_target(self, base_target, dir): - target = copy_dict(base_target) - target.setdefault("args", {}) - target.setdefault("images", []) - - if "targetConfig" in base_target: - target_config = base_target["targetConfig"] - config_file = target_config.get("file", "target-config.yml") - config_path = os.path.join(dir, config_file) - if not os.path.exists(config_path): - raise InvalidKluctlProjectConfig("No target config file with name %s found in target" % config_file) - - target_config_file = self.load_target_config(config_path) - - # merge args - for arg_name, value in target_config_file.get("args", {}).items(): - self.check_dynamic_arg(target, arg_name, value) - target["args"][arg_name] = value - - # We prepend the dynamic images to ensure they get higher priority later - target["images"] = target_config_file.get("images", []) + target["images"] - - return target - - def load_target_config(self, path): - target_config = yaml_load_file(path) - if target_config is None: - return {} - jsonschema.validate(target_config, target_config_schema) - return target_config - - def merge_clusters_dirs(self, merged_clusters_dir, clusters_infos): - os.makedirs(merged_clusters_dir) - for c in clusters_infos: - if not os.path.exists(c.dir): - logger.warning("Cluster dir '%s' does not exist" % c.dir) - continue - for f in os.listdir(c.dir): - af = os.path.join(c.dir, f) - if os.path.isfile(af): - shutil.copy(af, os.path.join(merged_clusters_dir, f)) - - def find_target(self, name): - for target in self.targets: - if target["name"] == name: - return target - raise InvalidKluctlProjectConfig("Target '%s' not existent in kluctl project config" % name) - - def check_dynamic_arg(self, target, arg_name, arg_value): - dyn_arg = None - for x in target.get("dynamicArgs", []): - if x["name"] == arg_name: - dyn_arg = x - break - if not dyn_arg: - raise InvalidKluctlProjectConfig(f"Dynamic argument {arg_name} is not allowed for target") - - arg_pattern = dyn_arg.get("pattern", ".*") - if not re.fullmatch(arg_pattern, arg_value): - raise InvalidKluctlProjectConfig(f"Dynamic argument {arg_name} does not match required pattern '{arg_pattern}'") - - -@contextmanager -def load_kluctl_project(project_url, project_ref, config_file, - local_clusters=None, local_deployment=None, local_sealed_secrets=None) -> ContextManager[KluctlProject]: - with TemporaryDirectory(dir=get_tmp_base_dir()) as tmp_dir: - project = KluctlProject(None, project_url, project_ref, config_file, local_clusters, local_deployment, local_sealed_secrets, tmp_dir) - yield project - -@contextmanager -def load_kluctl_project_from_args(kwargs) -> ContextManager[KluctlProject]: - with TemporaryDirectory(dir=get_tmp_base_dir()) as tmp_dir: - if kwargs["from_archive"]: - if any(kwargs[x] for x in ["project_url", "project_ref", "project_config", "local_clusters", "local_deployment", "local_sealed_secrets"]): - raise CommandError("--from-archive can not be combined with any other project related option") - project = KluctlProject.from_archive(kwargs["from_archive"], kwargs["from_archive_metadata"], tmp_dir) - project.load(False) - else: - project = KluctlProject(kwargs["project_url"], kwargs["project_ref"], kwargs["project_config"], kwargs["local_clusters"], kwargs["local_deployment"], kwargs["local_sealed_secrets"], tmp_dir) - project.load(True) - project.load_targets() - yield project diff --git a/kluctl/kluctl_project/passwordstate.py b/kluctl/kluctl_project/passwordstate.py deleted file mode 100644 index dd6aa3a8c..000000000 --- a/kluctl/kluctl_project/passwordstate.py +++ /dev/null @@ -1,75 +0,0 @@ -import sys -from getpass import getpass -from urllib.parse import urlencode - -import requests -from requests_ntlm import HttpNtlmAuth - -from kluctl.utils.thread_safe_cache import ThreadSafeCache - - -class Passwordstate: - session_cache = ThreadSafeCache() - def get_session(self, host): - def do_get_session(): - print(f"Please enter credentials for passwordstate host '{host}'", file=sys.stderr) - sys.stderr.write("Username: ") - username = input() - password = getpass("Password: ") - session = requests.Session() - session.auth = HttpNtlmAuth(username=username, password=password) - return session - - return self.session_cache.get(f"session-{host}", do_get_session) - - password_list_cache = ThreadSafeCache() - def get_password_list(self, host, path): - path = path.replace("/", "\\") - name = path.split("\\")[-1] - - def do_get(): - session = self.get_session(host) - url = f"https://{host}/winapi/searchpasswordlists/?" - url += urlencode({ - "PasswordList": f"{name}" - }) - r = session.get(url) - r.raise_for_status() - j = r.json() - j = [x for x in j if x["PasswordList"] == name and x["TreePath"] == path] - if len(j) == 0: - raise Exception(f"No passwordlist found for {path}") - if len(j) != 1: - raise Exception(f"More then one passwordlist found for {path}") - return j[0] - return self.password_list_cache.get(f"{host}-{path}", do_get) - - password_cache = ThreadSafeCache() - def get_password(self, host, password_list_id, title, field): - def do_get(): - session = self.get_session(host) - url = f"https://{host}/winapi/searchpasswords/{password_list_id}?" - url += urlencode({ - "title": f"\"{title}\"" - }) - r = session.get(url) - r.raise_for_status() - j = r.json() - if len(j) == 0: - raise Exception(f"No password with title {title} found in passwordlist {password_list_id}") - if len(j) != 1: - raise Exception(f"More then one password with title {title} found in passwordlist {password_list_id}") - if field not in j[0]: - raise Exception(f"Password has no field with name '{field}'") - return j[0][field] - return self.password_cache.get(f"{host}-{password_list_id}-{title}-{field}", do_get) - - document_cache = ThreadSafeCache() - def get_document(self, host, id): - def do_get(): - session = self.get_session(host) - url = f"https://{host}/winapi/document/passwordlist/{id}" - r = session.get(url) - r.raise_for_status() - return r.text - return self.document_cache.get(f"{host}-{id}", do_get) diff --git a/kluctl/kluctl_project/secrets.py b/kluctl/kluctl_project/secrets.py deleted file mode 100644 index cd42c62b8..000000000 --- a/kluctl/kluctl_project/secrets.py +++ /dev/null @@ -1,77 +0,0 @@ -import logging -import os - -from yaml import YAMLError -from yaml.parser import ParserError - -from kluctl.kluctl_project.passwordstate import Passwordstate -from kluctl.utils.dict_utils import get_dict_value -from kluctl.utils.exceptions import InvalidKluctlProjectConfig -from kluctl.utils.yaml_utils import yaml_load_file, yaml_load - -logger = logging.getLogger(__name__) - - -class SecretsLoader: - def __init__(self, kluctl_project, secrets_dir): - from kluctl.kluctl_project.kluctl_project import KluctlProject - self.kluctl_project: KluctlProject = kluctl_project - self.secrets_dir = secrets_dir - self.passwordstate = Passwordstate() - - def load_secrets(self, secrets_source): - if "path" in secrets_source: - return self.load_secrets_file(secrets_source) - if "passwordstate" in secrets_source: - return self.load_secrets_passwordstate(secrets_source) - if "awsSecretsManager" in secrets_source: - return self.load_secrets_aws_secrets_manager(secrets_source) - raise InvalidKluctlProjectConfig("Invalid secrets entry") - - def load_secrets_file(self, secrets_source): - secrets_path = secrets_source["path"] - path = None - if os.path.exists(os.path.join(self.kluctl_project.deployment_dir, secrets_path)): - path = os.path.join(self.kluctl_project.deployment_dir, secrets_path) - elif os.path.exists(os.path.join(self.secrets_dir, secrets_path)): - path = os.path.join(self.secrets_dir, secrets_path) - if not path or not os.path.exists(path): - logger.error(f"Secrets file {secrets_path} does not exist") - raise InvalidKluctlProjectConfig(f"Secrets file {secrets_path} does not exist") - - secrets = yaml_load_file(path) - return secrets.get('secrets', {}) - - def load_secrets_passwordstate(self, secrets_source): - ps = secrets_source["passwordstate"] - host = ps["host"] - if "documentId" in ps: - document_id = ps["documentId"] - doc = self.passwordstate.get_document(host, document_id) - else: - path = ps["passwordList"] - title = ps["passwordTitle"] - field = ps.get("passwordField", "GenericField1") - l = self.passwordstate.get_password_list(host, path) - doc = self.passwordstate.get_password(host, l["PasswordListID"], title, field) - try: - y = yaml_load(doc) - except YAMLError as e: - raise InvalidKluctlProjectConfig("Failed to parse yaml from passwordstate: %s" % str(e)) - - return y.get("secrets", {}) - - def load_secrets_aws_secrets_manager(self, secrets_source): - profile = get_dict_value(secrets_source, "awsSecretsManager.profile") - secret_name = get_dict_value(secrets_source, "awsSecretsManager.secretName") - region_name = get_dict_value(secrets_source, "awsSecretsManager.region") - if not secret_name: - raise InvalidKluctlProjectConfig("secretName is missing in secrets entry") - - from kluctl.kluctl_project.aws_secrets_manager import get_aws_secrets_manager_secret - secret = get_aws_secrets_manager_secret(profile, region_name, secret_name) - try: - y = yaml_load(secret) - except YAMLError as e: - raise InvalidKluctlProjectConfig("Failed to parse yaml from AWS Secrets Manager (secretName=%s): %s" % (secret_name, str(e))) - return y.get("secrets", {}) diff --git a/kluctl/schemas/__init__.py b/kluctl/schemas/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/kluctl/schemas/fixed-images.yml b/kluctl/schemas/fixed-images.yml deleted file mode 100644 index 5b30a1fb3..000000000 --- a/kluctl/schemas/fixed-images.yml +++ /dev/null @@ -1,33 +0,0 @@ -type: object -properties: - images: - type: array - items: - type: object - additionalProperties: false - required: - - image - - resultImage - properties: - image: - type: string - resultImage: - type: string - deployedImage: - type: string - registryImage: - type: string - namespace: - type: string - deployment: - type: string - container: - type: string - versionFilter: - type: string - deployTags: - type: array - items: - type: string - kustomizeDir: - type: string diff --git a/kluctl/schemas/kluctl-project.yml b/kluctl/schemas/kluctl-project.yml deleted file mode 100644 index 33ba16344..000000000 --- a/kluctl/schemas/kluctl-project.yml +++ /dev/null @@ -1,210 +0,0 @@ -title: .kluctl.yml schema -type: object -definitions: - git_url: - anyOf: - - type: string - pattern: - ^(\w+:\/\/)(.+@)?([\w\d\.]+)(:[\d]+)?\/*(.*)$ - - type: string - pattern: - ^(.+@)?([\w\d\.]+):(.*)$ - git_ref: - anyOf: - - type: string - # See https://stackoverflow.com/questions/12093748/how-do-i-check-for-valid-git-branch-names - pattern: - ^(?!.*/\.)(?!.*\.\.)(?!/)(?!.*//)(?!.*@\{)(?!.*\\)[^\000-\037\177 ~^:?*[]+/[^\000-\037\177 ~^:?*[]+(? 1: - raise Exception("Only simple jsonpath supported in set_dict_default_value") - if len(f) == 0 or f[0].value is None: - p.update_or_create(d, default) - -def get_dict_value(y, path, default=None): - p = parse_json_path(path) - f = p.find(y) - if len(f) > 1: - raise Exception("Only simple jsonpath supported in get_dict_value") - if len(f) == 0: - return default - return f[0].value - -def has_dict_value(y, path): - p = parse_json_path(path) - f = p.find(y) - if len(f) > 1: - raise Exception("Only simple jsonpath supported in get_dict_value") - if len(f) == 0: - return False - return True - -def set_dict_value(y, path, value, do_clone=False): - if do_clone: - y = copy_dict(y) - p = parse_json_path(path) - p.update_or_create(y, value) - return y - -def del_dict_value(d, k): - p = parse_json_path(k) - p.filter(lambda x: True, d) - -def list_matching_dict_pathes(d, k): - p = parse_json_path(k) - f = p.find(d) - ret = [] - for x in f: - ret.append(str(x.full_path)) - return ret - -def is_empty(o): - if isinstance(o, dict) or isinstance(o, list): - return len(o) == 0 - return False - -def remove_empty(o): - if isinstance(o, dict): - for k in list(o.keys()): - remove_empty(o[k]) - if is_empty(o[k]): - del(o[k]) - elif isinstance(o, list): - i = 0 - while i < len(o): - v = o[i] - remove_empty(v) - if is_empty(v): - o.pop(i) - else: - i += 1 - -def is_iterable(obj, str_and_bytes=True): - if isinstance(obj, list): - return True - if isinstance(obj, tuple): - return True - if isinstance(obj, dict): - return True - if isinstance(obj, str): - return str_and_bytes - if isinstance(obj, bytes): - return str_and_bytes - if isinstance(obj, int) or isinstance(obj, bool): - return False - if isinstance(obj, type): - return False - try: - iter(obj) - except Exception: - return False - else: - return True - -def object_iterator(o, only_leafs=False): - stack = [(o, [])] - - while len(stack) != 0: - o2, p = stack.pop() - - is_leaf = True - if isinstance(o2, dict): - for k, v in o2.items(): - is_leaf = False - stack.append((v, p + [k])) - elif is_iterable(o2, False): - for i, v in enumerate(o2): - is_leaf = False - stack.append((v, p + [i])) - - if len(p) != 0 and (is_leaf or not only_leafs): - yield o2, p diff --git a/kluctl/utils/dict_utils_test.py b/kluctl/utils/dict_utils_test.py deleted file mode 100644 index 34a74c33b..000000000 --- a/kluctl/utils/dict_utils_test.py +++ /dev/null @@ -1,74 +0,0 @@ -from kluctl.utils.dict_utils import get_dict_value, copy_dict, set_dict_value, del_dict_value - -o = { - "a": "v1", - "b": "v2", - "c": { - "d": "v3", - "e": { - "f": "v4", - "xa": "v5", - "g": "v6", - "xb": "v7", - } - }, - "array": [ - {"a": "v1"}, - {"b": "v2"}, - ] -} - - -def test_get_dict_value(): - assert get_dict_value(o, "a") == "v1" - assert get_dict_value(o, "b") == "v2" - assert get_dict_value(o, "c.d") == "v3" - assert get_dict_value(o, "c.e.f") == "v4" - assert get_dict_value(o, "c.e.g") == "v6" - -def test_get_dict_value_arrays(): - assert isinstance(get_dict_value(o, "array"), list) - assert isinstance(get_dict_value(o, "array[0]"), dict) - assert get_dict_value(o, "array[0].a") == "v1" - assert get_dict_value(o, "array[1].b") == "v2" - -def test_set_dict_value(): - o2 = copy_dict(o) - set_dict_value(o2, "a", "v1a") - assert get_dict_value(o2, "a") == "v1a" - set_dict_value(o2, "c.e.f", "xyz") - assert get_dict_value(o2, "c.e.f") == "xyz" - set_dict_value(o2, "array[0].a", "a1") - assert get_dict_value(o2, "array[0].a") == "a1" - -def test_set_dict_value_add(): - o2 = copy_dict(o) - set_dict_value(o2, "x", "x") - assert get_dict_value(o2, "x") == "x" - set_dict_value(o2, "y.x", "yx") - assert get_dict_value(o2, "y.x") == "yx" - set_dict_value(o2, "array[2].a", "a1") - assert get_dict_value(o2, "array[2].a") == "a1" - -def test_del_dict_value(): - o2 = copy_dict(o) - del_dict_value(o2, "a") - assert "a" not in o2 - assert "b" in o2 - del_dict_value(o2, "c.d") - assert "d" not in o2["c"] - del_dict_value(o2, "array[1]") - assert len(o2["array"]) == 1 - -def test_del_dict_value_wildcard(): - o2 = copy_dict(o) - del_dict_value(o2, "*") - assert o2 == {} - o2 = copy_dict(o) - del_dict_value(o2, "c.*") - assert o2["c"] == {} - -def test_del_dict_value_wildcard_extended(): - o2 = copy_dict(o) - del_dict_value(o2, 'c.e."x*"') - assert o2["c"]["e"] == {"f": "v4", "g": "v6"} diff --git a/kluctl/utils/env_config_sets.py b/kluctl/utils/env_config_sets.py deleted file mode 100644 index 5a13d1f19..000000000 --- a/kluctl/utils/env_config_sets.py +++ /dev/null @@ -1,22 +0,0 @@ -import os -import re - - -def parse_env_config_sets(prefix): - r = re.compile(r"%s_(\d+)_(.*)" % prefix) - r2 = re.compile(r"%s_(.*)" % prefix) - - ret = {} - for env_name, env_value in os.environ.items(): - m = r.fullmatch(env_name) - if m: - idx = m.group(1) - key = m.group(2) - ret.setdefault(idx, {})[key] = env_value - else: - m = r2.fullmatch(env_name) - if m: - key = m.group(1) - ret.setdefault(None, {})[key] = env_value - - return ret diff --git a/kluctl/utils/exceptions.py b/kluctl/utils/exceptions.py deleted file mode 100644 index a124f3faa..000000000 --- a/kluctl/utils/exceptions.py +++ /dev/null @@ -1,8 +0,0 @@ -class CommandError(Exception): - def __init__(self, message): - self.message = message - -class InvalidKluctlProjectConfig(Exception): - def __init__(self, message, config=None): - self.message = message - self.config = config diff --git a/kluctl/utils/external_args.py b/kluctl/utils/external_args.py deleted file mode 100644 index dafb0881d..000000000 --- a/kluctl/utils/external_args.py +++ /dev/null @@ -1,44 +0,0 @@ -import re - -from kluctl.utils.exceptions import CommandError -from kluctl.utils.dict_utils import merge_dict - - -def parse_args(args_list): - r = re.compile('^[a-zA-Z0-9_./-]*=.*$') - args = {} - for arg in args_list: - if not r.match(arg): - raise CommandError("Invalid --arg argument. Must be --arg=some_var_name=value") - s = arg.split("=", 1) - name = s[0] - value = s[1] - args[name] = value - return args - -def check_required_args(args_def, args): - defaults = {} - for a in args_def: - if 'default' not in a: - continue - name = a['name'].split('.') - m = defaults - for i, n in enumerate(name): - if i == len(name) - 1: - m[n] = a['default'] - else: - if n not in m: - m[n] = {} - m = m[n] - - for a in args_def: - name = a['name'].split('.') - m = args - for i, n in enumerate(name): - if n not in m: - if 'default' not in a: - raise CommandError('Required argument %s not set' % a['name']) - break - m = m[n] - args = merge_dict(defaults, args) - return args diff --git a/kluctl/utils/external_tools.py b/kluctl/utils/external_tools.py deleted file mode 100644 index 9b4bea97c..000000000 --- a/kluctl/utils/external_tools.py +++ /dev/null @@ -1,25 +0,0 @@ -import hashlib -import shutil -import threading - -from kluctl.utils.exceptions import CommandError - -def get_external_tool_path(name): - path = shutil.which(name) - if path is None: - raise CommandError("%s was not found. Is it installed and in your PATH?" % name) - return path - -external_tool_hashes = {} -external_tool_hashes_lock = threading.Lock() -def get_external_tool_hash(name): - global external_tool_hashes - global external_tool_hashes_lock - with external_tool_hashes_lock: - if name not in external_tool_hashes: - external_tool_hashes[name] = calc_external_tool_hash(name) - return external_tool_hashes[name] - -def calc_external_tool_hash(name): - with open(get_external_tool_path(name), "rb") as f: - return hashlib.sha256(f.read()).hexdigest() diff --git a/kluctl/utils/git_utils.py b/kluctl/utils/git_utils.py deleted file mode 100644 index 8d75df300..000000000 --- a/kluctl/utils/git_utils.py +++ /dev/null @@ -1,349 +0,0 @@ -import dataclasses -import hashlib -import logging -import os -import re -import shutil -import sys -from contextlib import contextmanager -from tempfile import NamedTemporaryFile -from urllib.parse import urlparse - -import filelock -from git import Git, GitCommandError - -from kluctl.utils.env_config_sets import parse_env_config_sets -from kluctl.utils.utils import get_tmp_base_dir - -logger = logging.getLogger(__name__) - -NO_CREDENTIALS_PROMPT = """#!/usr/bin/env sh -echo >&2 -echo 'Interactive password prompts for git are disabled when running kluctl.' >&2 -echo 'Please ensure credentials for %s are somehow setup.' >&2 -echo 'This can for example be achieved by running a manual git clone operation' >&2 -echo 'with a configured credential helper beforehand.' >&2 -echo >&2 -exit 1 -""" - -def get_cache_base_dir(): - dir = os.path.join(get_tmp_base_dir(), "git-cache") - logger.debug("cache base dir: %s" % dir) - return dir - -@dataclasses.dataclass() -class GitCredentials: - host: str - username: str - password: str - ssh_key: str - path_prefix: str - -class GitCredentialsStore: - def get_credentials(self, host, path): - return None, None, None - - def check_credentials(self, c, test_host, test_path): - if c.host != test_host: - return False - if not test_path.startswith(c.path_prefix or ""): - return False - return True - - def find_matching_credentials(self, credentials, test_host, test_path): - c = [x for x in credentials if self.check_credentials(x, test_host, test_path)] - if not c: - return None - c.sort(key=lambda x: len(x.path_prefix)) - # return credentials with longest matching path - return c[-1] - -class GitCredentialStoreEnv(GitCredentialsStore): - def get_credentials(self, host, path): - credentials = [] - for idx, s in parse_env_config_sets("KLUCTL_GIT").items(): - credentials.append(GitCredentials(host=s.get("HOST"), - username=s.get("USERNAME"), - password=s.get("PASSWORD"), - ssh_key=s.get("SSH_KEY"), - path_prefix=s.get("PATH_PREFIX", ""))) - c = self.find_matching_credentials(credentials, host, path) - if c is not None and c.ssh_key is not None: - path = os.path.expanduser(c.ssh_key) - with open(path, "rt") as f: - c.ssh_key = f.read() - return c - -credentials_store = GitCredentialStoreEnv() - -def set_git_credentials_store(store): - global credentials_store - credentials_store = store - -def get_git_credentials_store(): - return credentials_store - -def build_remote_name(url): - remote_name = os.path.basename(url) - if remote_name.endswith(".git"): - remote_name = remote_name[:-len(".git")] - remote_name += "-" + hashlib.sha256(url.encode()).hexdigest()[:6] - return remote_name - -def add_username_to_url(url, username): - if username is None: - return url - u = parse_git_url(url) - return f"{u.schema}://{username}@{u.host}:{u.port}/{u.path}" - -@contextmanager -def create_password_files(g, ssh_command, url, credentials): - # Must handle closing/deletion manually as otherwise git will complain about busy files - password_script = NamedTemporaryFile("w+t", dir=get_tmp_base_dir(), delete=False) - password_file = NamedTemporaryFile("w+t", dir=get_tmp_base_dir(), delete=False) - ssh_key_file = NamedTemporaryFile("w+t", dir=get_tmp_base_dir(), delete=False) - - if credentials is not None and credentials.password is not None: - password_file.write(credentials.password) - password_script.write(f"#!/usr/bin/env sh\ncat {password_file.name}") - else: - password_script.write(NO_CREDENTIALS_PROMPT % url) - - if credentials is not None and credentials.ssh_key is not None: - ssh_key_file.write(credentials.ssh_key) - ssh_command += " -i '%s'" % ssh_key_file.name - - for x in [password_script, password_file, ssh_key_file]: - x.close() - os.chmod(password_script.name, 0o700) - - g.update_environment(GIT_ASKPASS=password_script.name, GIT_TERMINAL_PROMPT="0") - try: - yield ssh_command - finally: - for x in [password_script, password_file, ssh_key_file]: - try: - os.unlink(x.name) - except Exception: - pass - -@contextmanager -def build_git_object(url, working_dir): - u = parse_git_url(url) - credentials = get_git_credentials_store().get_credentials(u.host, u.path) - if credentials is not None and u.username is not None and u.username != credentials.username: - raise Exception("username from url does not match username from credentials store") - - class MyGit(Git): - def execute(self, command, **kwargs): - kwargs2 = kwargs.copy() - if "KLUCTL_GIT_TIMEOUT" in os.environ: - kwargs2["kill_after_timeout"] = int(os.environ["KLUCTL_GIT_TIMEOUT"]) - return super().execute(command, **kwargs2) - - g = MyGit(working_dir) - - ssh_command = os.environ.get("GIT_SSH", "ssh") - - if sys.platform == "win32": - if "plink.exe" not in ssh_command.lower(): - ssh_command += " -o 'StrictHostKeyChecking=no'" - else: - ssh_command += " -o 'StrictHostKeyChecking=no'" - ssh_command += " -o 'ControlMaster=auto'" - ssh_command += " -o 'ControlPath=/tmp/kluctl_control_master-%r@%h-%p'" - ssh_command += " -o 'ControlPersist=5m'" - - if credentials is not None and credentials.username is not None: - url = add_username_to_url(url, credentials.username) - - with create_password_files(g, ssh_command, url, credentials) as ssh_command: - g.update_environment( - GIT_SSH_COMMAND=ssh_command - ) - try: - yield g, url - finally: - pass - -class MirroredGitRepo: - def __init__(self, url): - self.url = url - - remote_name = build_remote_name(url) - self.mirror_dir = os.path.join(get_cache_base_dir(), remote_name) - - self.has_lock = False - self.has_updated = False - - if not os.path.exists(self.mirror_dir): - os.makedirs(self.mirror_dir, exist_ok=True) - - self.file_lock = filelock.FileLock(os.path.join(self.mirror_dir, ".cache.lock")) - - def _build_git_object(self): - return build_git_object(self.url, self.mirror_dir) - - def _clone_or_update2(self): - init_marker = os.path.join(self.mirror_dir, ".cache.init") - - if os.path.exists(init_marker): - logger.info(f"Updating mirror repo: url='{self.url}'") - with self._build_git_object() as (g, url): - g.remote("update") - return - - if os.path.exists(self.mirror_dir): - for n in os.listdir(self.mirror_dir): - if n == ".cache.lock": - continue - p = os.path.join(self.mirror_dir, n) - if os.path.isdir(p): - shutil.rmtree(p) - else: - os.unlink(p) - - logger.info(f"Cloning mirror repo at {self.mirror_dir}") - with self._build_git_object() as (g, url): - g.clone("--mirror", url, "mirror") - for n in os.listdir(os.path.join(self.mirror_dir, "mirror")): - shutil.move(os.path.join(self.mirror_dir, "mirror", n), self.mirror_dir) - shutil.rmtree(os.path.join(self.mirror_dir, "mirror")) - with open(init_marker, "w"): - # only touch it - pass - - def update(self): - try: - self._clone_or_update2() - self.has_updated = True - except GitCommandError as e: - if "did not complete in " in e.stderr: - logger.info("Git command timed out, deleting cache (%s) to ensure that we don't get into an " - "inconsistent state" % self.mirror_dir) - try: - shutil.rmtree(self.mirror_dir) - except: - pass - raise - - def lock(self): - self.file_lock.acquire() - self.has_lock = True - - def unlock(self): - self.file_lock.release() - self.has_lock = False - - @contextmanager - def locked(self): - self.lock() - - try: - yield self - finally: - self.unlock() - - def clone_project(self, ref, target_dir): - assert self.has_lock - assert self.has_updated - - logger.debug(f"Cloning git project: url='{self.url}', ref='{ref}'") - - with self._build_git_object(): - args = ["file://%s" % self.mirror_dir, "--single-branch", target_dir] - if ref is not None: - args += ["--branch", ref] - Git().clone(*args) - -def get_git_commit(path): - g = Git(path) - commit = g.rev_parse("HEAD", stdout_as_string=True) - return commit.strip() - -def get_git_ref(path): - g = Git(path) - branch = g.rev_parse("--abbrev-ref", "HEAD", stdout_as_string=True).strip() - if branch != "HEAD": - return branch - tag = g.describe("--tags", stdout_as_string=True).strip() - return tag - -def git_ls_remote(url, tags=False): - args = [] - if tags: - args.append("--tags") - with build_git_object(url, None) as (g, url): - args.append(url) - txt = g.ls_remote("-q", *args) - lines = txt.splitlines() - ret = {} - for l in lines: - x = l.split() - ret[x[1]] = x[0] - return ret - -def filter_remote_refs(refs, pattern, trim): - pattern = re.compile(r"refs/heads/%s" % pattern) - matching_refs = {} - for r, commit in refs.items(): - if pattern.fullmatch(r): - r2 = r - if trim: - r2 = r[len("refs/heads/"):] - matching_refs[r2] = commit - return matching_refs - -@dataclasses.dataclass(eq=True) -class GitUrl: - schema: str - host: str - port: int - path: str - username: str - -def parse_git_url(p): - def trim_path(s): - if s.startswith("/"): - s = s[1:] - if s.endswith(".git"): - s = s[:-len(".git")] - return s - def normalize_port(schema, port): - if port is not None: - return port - if schema == "http": - return 80 - if schema == "https": - return 443 - if schema == "ssh": - return 22 - if schema == "file": - return None - raise Exception("Unknown schema %s" % schema) - - schema_pattern = re.compile("^([a-z]*)://.*") - m = schema_pattern.match(p) - if m: - url = urlparse(p) - path = trim_path(url.path) - port = normalize_port(url.scheme, url.port) - return GitUrl(url.scheme, url.hostname, port, path, url.username) - - pattern = re.compile("(.+@)?([\w\d\.]+):(.*)") - m = pattern.fullmatch(p) - if not m: - raise Exception("Invalid git url %s" % p) - - username = m.group(1) - if username is not None: - username = username[:-1] - host = m.group(2) - path = trim_path(m.group(3)) - return GitUrl("ssh", host, 22, path, username) - -def check_git_url_match(a, b): - a = parse_git_url(a) - b = parse_git_url(b) - return a == b diff --git a/kluctl/utils/gitlab/__init__.py b/kluctl/utils/gitlab/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/kluctl/utils/gitlab/gitlab_util.py b/kluctl/utils/gitlab/gitlab_util.py deleted file mode 100644 index dff78ee57..000000000 --- a/kluctl/utils/gitlab/gitlab_util.py +++ /dev/null @@ -1,72 +0,0 @@ -import os.path -from urllib.parse import quote - -import gitlab -from urllib3.util import parse_url - -from kluctl.utils.session_pool import get_session_from_pool -from kluctl.utils.yaml_utils import yaml_load - -gitlab_url = "https://gitlab.com" -gitlab_token = None - -def init_gitlab_util(url, token): - global gitlab_url - global gitlab_token - if url is not None: - gitlab_url = url - if token is not None: - gitlab_token = token - -def init_gitlab_util_from_glab(): - try: - with open(os.path.expanduser("~/.config/glab-cli/config.yml")) as f: - buf = f.read() - # some strange protection applied by glab? - buf = buf.replace("token: !!null ", "token: ") - y = yaml_load(buf) - - for h in y.get("hosts", []).values(): - if h.get("api_host") == "gitlab.com": - token = h.get("token") - init_gitlab_util("https://gitlab.com", token) - return - except: - pass - -def get_gitlab_api(job_token=None, require_auth=None): - kwargs = {} - if job_token is not None: - kwargs["job_token"] = job_token - else: - if gitlab_token: - kwargs["private_token"] = gitlab_token - elif require_auth: - return None - - kwargs["session"] = get_session_from_pool(("gitlab", job_token, require_auth)) - return gitlab.Gitlab(gitlab_url, **kwargs) - -def is_gitlab_project(git_url): - from kluctl.utils.git_utils import parse_git_url - gitlab_host = parse_url(gitlab_url).host - - try: - u = parse_git_url(git_url) - return u.host == gitlab_host - except: - return False - -def extract_gitlab_group_and_project(git_url): - from kluctl.utils.git_utils import parse_git_url - assert is_gitlab_project(git_url) - - u = parse_git_url(git_url) - p = u.path.split("/") - group = "/".join(p[:-1]) - project = p[-1] - return group, project - -def build_gitlab_project_id(group, name): - id = quote("%s/%s" % (group, name), safe="") - return id diff --git a/kluctl/utils/inclusion.py b/kluctl/utils/inclusion.py deleted file mode 100644 index bdb17953a..000000000 --- a/kluctl/utils/inclusion.py +++ /dev/null @@ -1,43 +0,0 @@ - - -class Inclusion: - def __init__(self): - self.includes = set() - self.excludes = set() - - def add_include(self, type, value): - self.includes.add((type, value)) - - def add_exclude(self, type, value): - self.excludes.add((type, value)) - - def has_type(self, type): - for t, v in self.includes: - if t == type: - return True - for t, v in self.excludes: - if t == type: - return True - return False - - def _check_list(self, type_and_values, l): - for t, v in type_and_values: - if (t, v) in l: - return True - return False - - def check_included(self, type_and_values, exclude_if_not_included=False): - if not self.includes and not self.excludes: - return True - - is_included = self._check_list(type_and_values, self.includes) - is_excluded = self._check_list(type_and_values, self.excludes) - - if exclude_if_not_included: - if not is_included: - return False - - if is_excluded: - return False - - return not self.includes or is_included diff --git a/kluctl/utils/k8s_cluster_base.py b/kluctl/utils/k8s_cluster_base.py deleted file mode 100644 index a6f4e2904..000000000 --- a/kluctl/utils/k8s_cluster_base.py +++ /dev/null @@ -1,66 +0,0 @@ -# Helper methods for k8s-related tools like kubectl or kustomize -import logging -import os - -from kubernetes.client import ApiException - -from kluctl.utils.exceptions import CommandError -from kluctl.utils.utils import MyThreadPoolExecutor -from kluctl.utils.yaml_utils import yaml_load_file - -logger = logging.getLogger(__name__) - -class k8s_cluster_base(object): - def get_objects(self, group=None, version=None, kind=None, name=None, namespace=None, labels=None, as_table=False): - return self._get_objects(group, version, kind, name, namespace, labels, as_table) - - def _get_objects(self, group, version, kind, name, namespace, labels, as_table): - raise NotImplementedError() - - def get_single_object(self, ref): - l = self.get_objects(version=ref.api_version, kind=ref.kind, name=ref.name, namespace=ref.namespace) - - if not l: - return None, [] - if len(l) != 1: - raise Exception("expected single object, got %d" % len(l)) - return l[0] - - def get_objects_by_object_refs(self, object_refs): - with MyThreadPoolExecutor(max_workers=32) as executor: - futures = [] - for ref in object_refs: - f = executor.submit(self.get_single_object, ref) - futures.append(f) - ret = [] - for f in futures: - o, w = f.result() - if o: - ret.append((o, w)) - return ret - - def get_objects_metadata(self, group=None, version=None, kind=None, name=None, namespace=None, labels=None): - if group is None or kind is None: - raise ApiException("group/kind must be supplied") - return self.get_objects(group=group, version=version, kind=kind, name=name, namespace=namespace, labels=labels, as_table=True) - -def load_cluster_config(cluster_dir, cluster_name): - if cluster_name is None: - raise CommandError("Cluster name must be specified!") - - path = os.path.join(cluster_dir, "%s.yml" % cluster_name) - if not os.path.exists(path): - raise CommandError("Cluster %s not known" % cluster_name) - y = yaml_load_file(path) - - cluster = y['cluster'] - - if cluster['name'] != cluster_name: - raise CommandError('Cluster name in %s does not match requested cluster name %s' % (cluster['name'], cluster_name)) - - return cluster - -def load_cluster(cluster_config, dry_run=True): - from kluctl.utils.k8s_cluster_real import k8s_cluster_real - k8s_cluster = k8s_cluster_real(cluster_config['context'], dry_run) - return k8s_cluster diff --git a/kluctl/utils/k8s_cluster_mocked.py b/kluctl/utils/k8s_cluster_mocked.py deleted file mode 100644 index b5b5c2cef..000000000 --- a/kluctl/utils/k8s_cluster_mocked.py +++ /dev/null @@ -1,43 +0,0 @@ -from kluctl.utils.k8s_cluster_base import k8s_cluster_base -from kluctl.utils.k8s_object_utils import split_api_version - - -class k8s_cluster_mocked(k8s_cluster_base): - def __init__(self): - self.objects = [] - - def add_object(self, o): - self.objects.append(o) - - def _get_objects(self, group, version, kind, name, namespace, labels, as_table): - if labels is None: - labels = {} - - ret = [] - for o in self.objects: - o_namespace = o['metadata']['namespace'] if 'namespace' in o['metadata'] else None - o_group, o_version = split_api_version(o.get("apiVersion")) - o_kind = o['kind'] - o_name = o['metadata']['name'] - o_labels = o['metadata']['labels'] if 'labels' in o['metadata'] else {} - - if namespace != o_namespace: - continue - if group is not None and group != o_group: - continue - if version is not None and version != o_version: - continue - if kind is not None and kind != o_kind: - continue - if name is not None and name != o_name: - continue - - labels_ok = True - for n, v in labels.items(): - if n not in o_labels or v != o_labels[n]: - labels_ok = False - break - if not labels_ok: - continue - ret.append((o, [])) - return ret diff --git a/kluctl/utils/k8s_cluster_real.py b/kluctl/utils/k8s_cluster_real.py deleted file mode 100644 index eeecb60e1..000000000 --- a/kluctl/utils/k8s_cluster_real.py +++ /dev/null @@ -1,345 +0,0 @@ -import functools -import json -import logging -import threading -import time - -from kubernetes import config -from kubernetes.client import ApiClient, Configuration, ApiException -from kubernetes.config import ConfigException -from kubernetes.dynamic import EagerDiscoverer, DynamicClient, Resource -from kubernetes.dynamic.exceptions import NotFoundError, ResourceNotFoundError - -from kluctl.utils.dict_utils import copy_dict, get_dict_value -from kluctl.utils.exceptions import CommandError -from kluctl.utils.k8s_cluster_base import k8s_cluster_base -from kluctl.utils.k8s_object_utils import split_api_version -from kluctl.utils.versions import LooseVersionComparator - -logger = logging.getLogger(__name__) - -discoverer_lock = threading.Lock() - -deprecated_resources = { - ("extensions", "Ingress") -} - -class MyDiscoverer(EagerDiscoverer): - did_invalidate_cache = False - - def invalidate_cache(self): - with discoverer_lock: - if self.did_invalidate_cache: - return - super().invalidate_cache() - self.did_invalidate_cache = True - - def force_invalidate_cache(self): - with discoverer_lock: - self.did_invalidate_cache = False - -# This is a hack to force the apiserver to return server-side tables. These also include the full metadata of each -# object, which we can then use to query for only metadata -class TableApiClient(ApiClient): - def select_header_accept(self, accepts): - return "application/json;as=Table;v=v1;g=meta.k8s.io,application/json;as=Table;v=v1beta1;g=meta.k8s.io,application/json" - -class k8s_cluster_real(k8s_cluster_base): - def __init__(self, context, dry_run): - self.context = context - self.dry_run = dry_run - - self.config = Configuration() - try: - config.load_kube_config(context=context, client_configuration=self.config, persist_config=True) - except ConfigException as e: - raise CommandError(str(e)) - - if self.context != "docker-desktop" and not self.config.api_key and not self.config.key_file: - raise CommandError("No authentication available for kubernetes context %s. You might need to invoke kubectl first to perform a login" % self.context) - - self.api_client = ApiClient(configuration=self.config) - self.dynamic_client = DynamicClient(self.api_client, discoverer=MyDiscoverer) - self.table_api_client = TableApiClient(configuration=self.config) - self.table_dynamic_client = DynamicClient(self.table_api_client, discoverer=MyDiscoverer) - - v = self.dynamic_client.version - self.server_version = v["kubernetes"]["gitVersion"] - - def _fix_api_version(self, group, version): - if group is None and version is not None: - version = version.split('/', 2) - if len(version) == 1: - version = version[0] - else: - group = version[0] - version = version[1] - return group, version - - def _build_label_selector(self, labels): - if labels is None: - return None - if isinstance(labels, list): - return ','.join(labels) - if isinstance(labels, dict): - return ','.join(['%s=%s' % (k, v) for k, v in labels.items()]) - else: - raise ValueError('Unsupported labels type') - - def get_status_message(self, status): - if isinstance(status, ApiException): - if status.headers.get("Content-Type") == "application/json": - status = json.loads(status.body) - else: - return status.body.decode("utf-8") - if isinstance(status, Exception): - return 'Exception: %s' % str(status) - - message = 'Unknown' - if 'message' in status: - message = status['message'] - elif 'reason' in status: - message = status['reason'] - return message - - def get_dry_run_suffix(self, force=False): - if self.dry_run or force: - return ' (dry-run)' - return '' - - def _get_dry_run_params(self, force=False): - params = [] - if self.dry_run or force: - params.append(('dryRun', 'All')) - return params - - def get_resource(self, group, version, kind): - return self.dynamic_client.resources.get(group=group, api_version=version, kind=kind) - - def get_preferred_resources(self, group, api_version, kind): - kinds = {} - for r in self.dynamic_client.resources.search(group=group, api_version=api_version, kind=kind): - if type(r) != Resource: - continue - if (r.group, r.kind) in deprecated_resources: - continue - key = (r.group or None, r.kind) - kinds.setdefault(key, []).append(r) - def cmp_resource(a, b): - va = a.api_version - vb = b.api_version - if a.preferred: - va = "100000000" - if b.preferred: - vb = "100000000" - return LooseVersionComparator.compare(va, vb) - for key in kinds.keys(): - kinds[key].sort(key=functools.cmp_to_key(cmp_resource)) - kinds[key] = kinds[key][-1] - return list(kinds.values()) - - def get_preferred_resource(self, group, kind): - resources = self.get_preferred_resources(group, None, kind) - for r in resources: - if group is not None and group != (r.group or None): - continue - if kind is not None and kind != (r.kind or None): - continue - return r - raise ResourceNotFoundError(f"{group}/{kind} not found") - - def get_all_resources(self): - return list(self.dynamic_client.resources.search()) - - def get_all_api_versions(self): - ret = set() - for r in self.get_all_resources(): - ret.add(r.group_version) - return ret - - def _get_objects_for_resource(self, resource, name, namespace, labels, as_table): - label_selector = self._build_label_selector(labels) - - logger.debug("GET resource=%s, name=%s, namespace=%s, labels=%s" % (resource, name, namespace, labels)) - - try: - client = self.dynamic_client - if as_table: - client = self.table_dynamic_client - r = client.get(resource, name, namespace, serialize=False, label_selector=label_selector) - warnings = r.headers.getlist("Warning") - ret = json.loads(r.data) - return ret, warnings - except NotFoundError: - return None - - def _get_objects(self, group, version, kind, name, namespace, labels, as_table): - group, version = self._fix_api_version(group, version) - - resources = self.get_preferred_resources(group, version, kind) - - def fix_kind(resource, r, warnings): - if not r: - return [] - if name is not None: - return [(r, warnings)] - - ret_group, ret_version = split_api_version(r.get("apiVersion")) - if as_table and ret_group == "meta.k8s.io" and r["kind"] == "Table": - r = r["rows"] or [] - r = [x["object"] for x in r] - else: - r = r["items"] - if not r: - return [] - - for x in r: - x['kind'] = resource.kind - x['apiVersion'] = resource.group_version - r = [(x, warnings) for x in r] - return r - - ret = [] - for resource in resources: - r = self._get_objects_for_resource(resource, name, namespace, labels, as_table) - if r is None: - continue - objects, warnings = r - ret += fix_kind(resource, objects, warnings) - - return ret - - def patch_object(self, body, namespace=None, force_dry_run=False, force_apply=False): - namespace = namespace or body.get('metadata', {}).get('namespace') - group, version = split_api_version(body.get("apiVersion")) - kind = body['kind'] - name = body['metadata']['name'] - query_params = self._get_dry_run_params(force_dry_run) - - query_params.append(('fieldManager', 'kluctl')) - if force_apply: - query_params.append(('force', 'true')) - - resource = self.dynamic_client.resources.get(group=group, api_version=version, kind=kind) - if resource.namespaced: - self.dynamic_client.ensure_namespace(resource, namespace, body) - - body = json.dumps(body) - - logger.debug("PATCH resource=%s, name=%s, namespace=%s" % (resource, name, namespace)) - r = self.dynamic_client.patch(resource, body=body, name=name, namespace=namespace, serialize=False, - query_params=query_params, content_type='application/apply-patch+yaml') - warnings = r.headers.getlist("Warning") - r = json.loads(r.data) - if not force_dry_run and not self.dry_run and kind == "CustomResourceDefinition": - self.dynamic_client.resources.force_invalidate_cache() - return r, warnings - - def replace_object(self, body, namespace=None, force_dry_run=False, resource_version=None): - namespace = namespace or body.get('metadata', {}).get('namespace') - resource_version = resource_version or get_dict_value(body, "metadata.resourceVersion") - group, version = split_api_version(body.get("apiVersion")) - kind = body['kind'] - name = body['metadata']['name'] - query_params = self._get_dry_run_params(force_dry_run) - - query_params.append(('fieldManager', 'kluctl')) - - resource = self.dynamic_client.resources.get(group=group, api_version=version, kind=kind) - if resource.namespaced: - self.dynamic_client.ensure_namespace(resource, namespace, body) - - body = json.dumps(body) - - logger.debug("PUT resource=%s, name=%s, namespace=%s" % (resource, name, namespace)) - r = self.dynamic_client.replace(resource, body=body, name=name, namespace=namespace, serialize=False, - query_params=query_params, content_type='application/yaml', - resource_version=resource_version) - warnings = r.headers.getlist("Warning") - r = json.loads(r.data) - if not force_dry_run and not self.dry_run and kind == "CustomResourceDefinition": - self.dynamic_client.resources.force_invalidate_cache() - return r, warnings - - def fix_object_for_patch(self, o): - # A bug in versions < 1.20 cause errors when applying resources that have some fields omitted which have - # default values. We need to fix these resources. - # UPDATE even though https://github.com/kubernetes-sigs/structured-merge-diff/issues/130 says it's fixed, the - # issue is still present. - needs_defaults_fix = LooseVersionComparator(self.server_version) < LooseVersionComparator('1.21') - # TODO check when this is actually fixed (see https://github.com/kubernetes/kubernetes/issues/94275) - needs_type_conversion_fix = LooseVersionComparator(self.server_version) < LooseVersionComparator('1.100') - if not needs_defaults_fix and not needs_type_conversion_fix: - return o - - o = copy_dict(o) - - def fix_ports(ports): - if not needs_defaults_fix: - return - for p in ports: - if 'protocol' not in p: - p['protocol'] = 'TCP' - - def fix_string_type(d, k): - if d is None: - return - if not needs_type_conversion_fix: - return - if k not in d: - return - if not isinstance(d[k], str): - d[k] = str(d[k]) - - def fix_container(c): - fix_ports(get_dict_value(c, "ports", [])) - fix_string_type(get_dict_value(c, "resources.limits"), "cpu") - fix_string_type(get_dict_value(c, "resources.requests"), "cpu") - - def fix_containers(containers): - for c in containers: - fix_container(c) - - fix_containers(get_dict_value(o, "spec.template.spec.containers", [])) - fix_ports(get_dict_value(o, "spec.ports", [])) - for x in get_dict_value(o, "spec.limits", []): - fix_string_type(x.get('default'), 'cpu') - fix_string_type(x.get('defaultRequest'), 'cpu') - return o - - def delete_single_object(self, ref, force_dry_run=False, cascade="Foreground", ignore_not_found=False, do_wait=True): - group, version = self._fix_api_version(None, ref.api_version) - dry_run = self.dry_run or force_dry_run - - resource = self.dynamic_client.resources.get(group=group, api_version=version, kind=ref.kind) - - body = { - "kind": "DeleteOptions", - "apiVersion": "v1", - "propagationPolicy": cascade, - } - if dry_run: - body["dryRun"] = ["All"] - body = json.dumps(body) - - logger.debug("DELETE resource=%s, name=%s, namespace=%s" % (resource, ref.name, ref.namespace)) - try: - r = self.dynamic_client.delete(resource, ref.name, ref.namespace, serialize=False, body=body, content_type='application/yaml') - # We need to ensure that the object is actually deleted, as the DELETE request is returning early - if not dry_run and do_wait: - self._wait_for_deleted_object(ref) - except NotFoundError as e: - if not ignore_not_found: - raise e - return None - return json.loads(r.data) - - def _wait_for_deleted_object(self, ref): - while True: - try: - o, _ = self.get_single_object(ref) - if not o: - break - except: - break - time.sleep(0.2) diff --git a/kluctl/utils/k8s_delete_utils.py b/kluctl/utils/k8s_delete_utils.py deleted file mode 100644 index b2cead603..000000000 --- a/kluctl/utils/k8s_delete_utils.py +++ /dev/null @@ -1,120 +0,0 @@ -import logging - -from kluctl.utils.dict_utils import get_dict_value -from kluctl.utils.k8s_object_utils import get_included_objects, get_object_ref, \ - get_long_object_name_from_ref, split_api_version, get_filtered_api_resources, \ - remove_api_version_from_ref, remove_namespace_from_ref_if_needed -from kluctl.utils.utils import MyThreadPoolExecutor, parse_bool - -logger = logging.getLogger(__name__) - -# either names or apigroups -high_level_resources_for_delete = [ - "monitoring.coreos.com", - "kafka.strimzi.io", - "zookeeper.pravega.io", - "elasticsearch.k8s.elastic.co", - "cert-manager.io", - "bitnami.com", - "acid.zalan.do", -] - -def filter_objects_for_delete(k8s_cluster, objects, api_filter, inclusion, excluded_objects): - filtered_resources = get_filtered_api_resources(k8s_cluster, api_filter) - filtered_resources = set((x.group, x.kind) for x in filtered_resources) - - # we must ignore api versions when checking excluded objects as k8s might have changed the version after an object - # was applied - excluded_objects = [remove_namespace_from_ref_if_needed(k8s_cluster, x) for x in excluded_objects] - excluded_objects = [remove_api_version_from_ref(x) for x in excluded_objects] - - inclusion_has_tags = inclusion.has_type("tags") - - def exclude(x): - group, version = split_api_version(x["apiVersion"]) - if (group or "", x["kind"]) not in filtered_resources: - return True - - # exclude when explicitly requested - if parse_bool(get_dict_value(x, 'metadata.annotations."kluctl.io/skip-delete"', "false")): - return True - - # exclude objects which are owned by some other object - if 'ownerReferences' in x['metadata'] and len(x['metadata']['ownerReferences']) != 0: - return True - - if len(get_dict_value(x, "metadata.managedFields", [])) == 0: - # We don't know who manages it...be safe and exclude it - return True - - # check if kluctl is managing this object - if not any([mf['manager'] == 'kluctl' for mf in x['metadata']['managedFields']]): - # This object is not managed by kluctl, so we shouldn't delete it - return True - - # exclude objects from excluded_objects - if remove_api_version_from_ref(get_object_ref(x)) in excluded_objects: - return True - - # exclude resources which have the 'kluctl.io/skip-delete-if-tags' annotation set - if inclusion_has_tags: - if parse_bool(get_dict_value(x, 'metadata.annotations."kluctl.io/skip-delete-if-tags"', "false")): - return True - - return False - - objects = [x for x in objects if not exclude(x)] - objects = [get_object_ref(x) for x in objects] - return objects - -def find_objects_for_delete(k8s_cluster, labels, inclusion, excluded_objects): - logger.info("Getting all cluster objects matching deleteByLabels") - all_cluster_objects = get_included_objects(k8s_cluster, ["delete"], labels, inclusion) - all_cluster_objects = [x for x, warnings in all_cluster_objects] - - ret = [] - ret += filter_objects_for_delete(k8s_cluster, all_cluster_objects, ["Namespace"], inclusion, excluded_objects + ret) - ret += filter_objects_for_delete(k8s_cluster, all_cluster_objects, high_level_resources_for_delete, inclusion, excluded_objects + ret) - ret += filter_objects_for_delete(k8s_cluster, all_cluster_objects, ['Deployment', 'StatefulSet', 'DaemonSet', 'Service', 'Ingress'], inclusion, excluded_objects + ret) - ret += filter_objects_for_delete(k8s_cluster, all_cluster_objects, None, inclusion, excluded_objects + ret) - return ret - -def delete_objects(k8s_cluster, object_refs, do_wait): - from kluctl.deployment.deployment_collection import DeployErrorItem - from kluctl.deployment.deployment_collection import CommandResult - - namespaces = set(x for x in object_refs if x.kind == "Namespace") - namespace_names = set(x.name for x in namespaces) - - def do_delete_object(ref): - logger.info('Deleting %s' % get_long_object_name_from_ref(ref)) - return k8s_cluster.delete_single_object(ref, do_wait) - - deleted_objects = [] - errors = [] - - with MyThreadPoolExecutor(max_workers=8) as executor: - futures = [] - for ref in namespaces: - f = executor.submit(do_delete_object, ref) - futures.append((ref, f)) - - for ref in object_refs: - if ref in namespaces: - continue - if ref.namespace in namespace_names: - continue - - f = executor.submit(do_delete_object, ref) - futures.append((ref, f)) - - for ref, f in futures: - try: - f.result() - deleted_objects.append(ref) - except Exception as e: - errors.append(DeployErrorItem(ref=ref, message="Failed to delete object. %s" % k8s_cluster.get_status_message(e))) - - result = CommandResult(new_objects=[], changed_objects=[], deleted_objects=deleted_objects, - hook_objects=[], orphan_objects=[], errors=errors, warnings=[]) - return result diff --git a/kluctl/utils/k8s_downscale_utils.py b/kluctl/utils/k8s_downscale_utils.py deleted file mode 100644 index d72adcb7c..000000000 --- a/kluctl/utils/k8s_downscale_utils.py +++ /dev/null @@ -1,36 +0,0 @@ -import re - -import jsonpatch - -from kluctl.utils.dict_utils import set_dict_value, get_dict_value -from kluctl.utils.k8s_object_utils import get_object_ref, remove_api_version_from_ref -from kluctl.utils.utils import parse_bool -from kluctl.utils.yaml_utils import yaml_load - -DOWNSCALE_ANNOTATION_PATCH_REGEX = re.compile(r"^kluctl.io/downscale-patch(-\d*)?$") -DOWNSCALE_ANNOTATION_DELETE = "kluctl.io/downscale-delete" -DOWNSCALE_ANNOTATION_IGNORE = "kluctl.io/downscale-ignore" - -def downsclae_is_delete(local_object): - if parse_bool(get_dict_value(local_object, "metadata.annotations[\"%s\"]" % DOWNSCALE_ANNOTATION_DELETE, "false")): - return True - return False - -def downscale_object(remote_object, local_object): - if parse_bool(get_dict_value(local_object, "metadata.annotations[\"%s\"]" % DOWNSCALE_ANNOTATION_IGNORE, "false")): - return remote_object - for k, v in get_dict_value(local_object, "metadata.annotations", {}).items(): - if DOWNSCALE_ANNOTATION_PATCH_REGEX.fullmatch(k): - patch = yaml_load(v) - patch = jsonpatch.JsonPatch(patch) - remote_object = patch.apply(remote_object) - - ref = get_object_ref(remote_object) - ref = remove_api_version_from_ref(ref) - if ref.api_version == "apps" and ref.kind in ["Deployment", "StatefulSet"]: - return set_dict_value(remote_object, "spec.replicas", 0, True) - elif ref.api_version == "batch" and ref.kind == "CronJob": - return set_dict_value(remote_object, "spec.suspend", True) - #elif ref.api_version == "autoscaling" and ref.kind == "HorizontalPodAutoscaler": - # return set_dict_value(o, "spec.minReplicas", 0, True) - return remote_object diff --git a/kluctl/utils/k8s_object_utils.py b/kluctl/utils/k8s_object_utils.py deleted file mode 100644 index 29ae8503b..000000000 --- a/kluctl/utils/k8s_object_utils.py +++ /dev/null @@ -1,142 +0,0 @@ -import dataclasses -import logging -from typing import Optional - -from kubernetes.dynamic.exceptions import ResourceNotFoundError - -from kluctl.utils.dict_utils import get_dict_value -from kluctl.utils.utils import MyThreadPoolExecutor - -logger = logging.getLogger(__name__) - -@dataclasses.dataclass(frozen=True) -class ObjectRef: - api_version: Optional[str] = None - kind: str = None - name: str = None - namespace: Optional[str] = None - -def split_api_version(api_version): - if not api_version: - return None, 'v1' - a = api_version.split('/') - if len(a) == 2: - return a[0], a[1] - elif len(a) == 1: - return None, a[0] - elif len(a) == 0: - return None, 'v1' - else: - raise ValueError('%s is not a valid apiVersion' % api_version) - -def _get_long_object_name(api_version, kind, name, namespace): - n = [namespace, api_version, kind, name] - n = [x for x in n if x] - return '/'.join(n) - -def get_long_object_name(o, include_api_version=True): - api_version = o["apiVersion"] - if not include_api_version: - api_version = None - return _get_long_object_name(api_version, - o.get('kind'), - o['metadata']['name'], - o['metadata'].get('namespace')) - -def get_long_object_name_from_ref(ref): - return _get_long_object_name(ref.api_version, ref.kind, ref.name, ref.namespace) - -def get_object_ref(o) -> ObjectRef: - api_version = o['apiVersion'] - kind = o['kind'] - name = o['metadata']['name'] - namespace = o['metadata'].get('namespace') - ref = ObjectRef(api_version=api_version, kind=kind, name=name, namespace=namespace) - return ref - -def get_object_refs(objects): - return [get_object_ref(o) for o in objects] - -def remove_api_version_from_ref(ref): - g, v = split_api_version(ref.api_version) - return ObjectRef(api_version=g, kind=ref.kind, name=ref.name, namespace=ref.namespace) - -def should_remove_namespace(k8s_cluster, ref): - if ref.namespace is None: - return False - - g, v = split_api_version(ref.api_version) - - try: - resource = k8s_cluster.get_preferred_resource(g, ref.kind) - if not resource.namespaced: - return True - return False - except ResourceNotFoundError: - # resource might be unknown by now as we might be in the middle of deploying CRDs - return False - -def remove_namespace_from_ref_if_needed(k8s_cluster, ref): - if should_remove_namespace(k8s_cluster, ref): - return ObjectRef(api_version=ref.api_version, kind=ref.kind, name=ref.name) - return ref - -def get_filtered_api_resources(k8s_cluster, filter): - api_resources = [] - for r in k8s_cluster.get_preferred_resources(None, None, None): - if filter and r.name not in filter and r.group not in filter and r.kind not in filter: - continue - api_resources.append(r) - return api_resources - -def get_objects_metadata(k8s_cluster, verbs, labels): - api_resources = k8s_cluster.get_preferred_resources(None, None, None) - - with MyThreadPoolExecutor(max_workers=8) as executor: - futures = [] - for x in api_resources: - if any(v not in x.verbs for v in verbs): - continue - f = executor.submit(k8s_cluster.get_objects_metadata, x.group, x.api_version, x.kind, labels=labels) - futures.append(f) - - ret = [] - for f in futures: - ret += f.result() - - return ret - -def get_included_objects(k8s_cluster, verbs, labels, inclusion): - resources = get_objects_metadata(k8s_cluster, verbs, labels) - - ret = [] - for r, warnings in resources: - inclusion_values = get_tags_from_object(r) - inclusion_values = [("tag", tag) for tag in inclusion_values] - - kustomize_dir = get_dict_value(r, 'metadata.annotations."kluctl.io/kustomize_dir"', None) - if kustomize_dir is not None: - inclusion_values.append(("kustomize_dir", kustomize_dir)) - - if inclusion.check_included(inclusion_values): - ret.append((r, warnings)) - - return ret - -def get_label_from_object(resource, name, default=None): - if 'labels' not in resource['metadata']: - return default - if name not in resource['metadata']['labels']: - return default - return resource['metadata']['labels'][name] - -def get_tags_from_object(resource): - if 'labels' not in resource['metadata']: - return {} - - tags = {} - for n, v in resource['metadata']['labels'].items(): - if n.startswith('kluctl.io/tag-'): - tags[v] = True - - return tags \ No newline at end of file diff --git a/kluctl/utils/k8s_status_validation.py b/kluctl/utils/k8s_status_validation.py deleted file mode 100644 index 0f006f643..000000000 --- a/kluctl/utils/k8s_status_validation.py +++ /dev/null @@ -1,189 +0,0 @@ -import dataclasses - -from kluctl.utils.dict_utils import get_dict_value -from kluctl.utils.k8s_object_utils import ObjectRef, get_object_ref - -RESULT_ANNOTATION = "validate-result.kluctl.io/" - -@dataclasses.dataclass(frozen=True, eq=True) -class ValidateResultItem: - ref: ObjectRef - reason: str - message: str - -@dataclasses.dataclass -class ValidateResult: - ready: bool = True - warnings: list = dataclasses.field(default_factory=list) - errors: list = dataclasses.field(default_factory=list) - results: list = dataclasses.field(default_factory=list) - -def validate_object(o, not_ready_is_error): - ref = get_object_ref(o) - result = ValidateResult() - - for k, v in o["metadata"].get("annotations", {}).items(): - if not k.startswith(RESULT_ANNOTATION): - continue - result.results.append(ValidateResultItem(ref, reason=k, message=v)) - - if "status" not in o: - return result - status = o["status"] - - class ValidateFailed(Exception): - pass - - def add_error(reason, message): - result.errors.append(ValidateResultItem(ref, reason=reason, message=message)) - - def add_warning(reason, message): - result.warnings.append(ValidateResultItem(ref, reason=reason, message=message)) - - def add_not_ready(reason, message): - if not_ready_is_error: - add_error(reason, message) - else: - add_warning(reason, message) - result.ready = False - - def find_conditions(type, do_error, do_raise): - ret = [] - for c in status.get("conditions", []): - if c["type"] == type: - ret.append((c.get("status"), c.get("reason"), c.get("message"))) - if not ret and do_error: - add_error("condition-not-found", "%s condition not in status" % type) - if do_raise: - raise ValidateFailed() - return ret - - def get_condition(type, do_error, do_raise): - c = find_conditions(type, do_error, do_raise) - if not c: - return None, None, None - if len(c) != 1: - add_error("condition-not-one", "%s condition found more then once" % type) - if do_raise: - raise ValidateFailed() - return c[0] - - def get_field(field, do_error, do_raise, default=None): - v = get_dict_value(status, field) - if v is None and do_error: - add_error("field-not-found", "%s field not in status or empty" % field) - if do_raise: - raise ValidateFailed() - if v is None: - return default - return v - - def parse_int_or_percent(v): - if isinstance(v, int): - return v, False - return int(v.replace("%", "")), True - - def value_from_int_or_percent(v, total): - v, is_percent = parse_int_or_percent(v) - if is_percent: - v = v * total / 100 - return int(v) - - try: - if o["kind"] == "Pod": - s, r, m = get_condition("Ready", False, False) - if s != "True": - add_not_ready(r or "not-ready", m or "Not ready") - elif o["kind"] == "Job": - s, r, m = get_condition("Failed", False, False) - if s == "True": - add_error(r or "failed", m or "N/A") - else: - s, r, m = get_condition("Complete", False, False) - if s != "True": - add_not_ready(r or "not-completed", m or "Not completed") - elif o["kind"] in ["Deployment", "ZookeeperCluster"]: - ready_replicas = get_field("readyReplicas", True, True) - replicas = get_field("replicas", True, True) - if ready_replicas < replicas: - add_not_ready("not-ready", "readyReplicas (%d) is less then replicas (%d)" % (ready_replicas, replicas)) - elif o["kind"] == "PersistentVolumeClaim": - phase = get_field("phase", True, True) - if phase != "Bound": - add_not_ready("not-bound", "Volume is not bound") - elif o["kind"] == "Service": - svc_type = get_dict_value(o, "spec.type") - if svc_type != "ExternalName": - if get_dict_value(o, "spec.clusterIP", "") == "": - add_error("no-cluster-ip", "Service does not have a cluster IP") - elif svc_type == "LoadBalancer": - if len(get_dict_value(o, "spec.externalIPs", [])) == 0: - ingress = get_field("loadBalancer.ingress", False, False) - if ingress is None: - add_not_ready("not-ready", "Not ready") - elif o["kind"] == "DaemonSet": - if get_dict_value(o, "spec.updateStrategy.type") == "RollingUpdate": - updated_number_scheduled = get_field("updatedNumberScheduled", True, True) - desired_number_scheduled = get_field("desiredNumberScheduled", True, True) - if updated_number_scheduled != desired_number_scheduled: - add_not_ready("not-ready", "DaemonSet is not ready. %d out of %d expected pods have been scheduled" % (updated_number_scheduled, desired_number_scheduled)) - else: - max_unavailable = get_dict_value(o, "spec.updateStrategy.maxUnavailable", 1) - try: - max_unavailable = value_from_int_or_percent(max_unavailable, desired_number_scheduled) - except: - max_unavailable = desired_number_scheduled - expected_ready = desired_number_scheduled - max_unavailable - number_ready = get_field("numberReady", True, True) - if number_ready < expected_ready: - add_not_ready("not-ready", "DaemonSet is not ready. %d out of %d expected pods are ready" % (number_ready, expected_ready)) - elif o["kind"] == "CustomResourceDefinition": - # This is based on how Helm check for ready CRDs. - # See https://github.com/helm/helm/blob/249d1b5fb98541f5fb89ab11019b6060d6b169f1/pkg/kube/ready.go#L342 - s, r, m = get_condition("Established", False, False) - if s != "True": - s, r, m = get_condition("NamesAccepted", True, True) - if s != "False": - add_error("not-ready", "CRD is not ready") - elif o["kind"] == "StatefulSet": - if get_dict_value(o, "spec.updateStrategy.type") == "RollingUpdate": - partition = get_dict_value(o, "spec.updateStrategy.rollingUpdate.partition", 0) - replicas = get_dict_value(o, "spec.replicas", 1) - updated_replicas = get_field("updatedReplicas", True, True) - expected_replicas = replicas - partition - if updated_replicas != expected_replicas: - add_not_ready("not-ready", "StatefulSet is not ready. %d out of %d expected pods have been scheduled" % (updated_replicas, expected_replicas)) - else: - ready_replicas = get_field("readyReplicas", True, True) - if ready_replicas != replicas: - add_not_ready("not-ready", "StatefulSet is not ready. %d out of %d expected pods are ready" % (ready_replicas, replicas)) - elif o["kind"] == "Kafka": - for s, r, m in find_conditions("Warning", False, False): - add_warning(r or "warning", m or "N/A") - s, r, m = get_condition("Ready", False, False) - if s != "True": - add_not_ready(r or "not-ready", m or "N/A") - elif o["kind"] == "Elasticsearch": - phase = get_field("phase", True, True) - if phase != "Ready": - add_not_ready("not-ready", "phase is %s" % phase) - else: - health = get_field("health", True, True) - if health == "yellow": - add_warning("health-yellow", "health is yellow") - elif health != "green": - add_not_ready("not-ready", "health is %s" % health) - elif o["kind"] == "Kibana": - health = get_field("health", True, True) - if health == "yellow": - add_warning("health-yellow", "health is yellow") - elif health != "green": - add_not_ready("not-ready", "health is %s" % health) - elif o["kind"] == "postgresql": - pstatus = get_field("PostgresClusterStatus", True, True) - if pstatus != "Running": - add_not_ready("not-ready", "PostgresClusterStatus is %s" % pstatus) - except ValidateFailed: - pass - - return result diff --git a/kluctl/utils/kubeseal.py b/kluctl/utils/kubeseal.py deleted file mode 100644 index 657f6d133..000000000 --- a/kluctl/utils/kubeseal.py +++ /dev/null @@ -1,39 +0,0 @@ -import logging -import os -import tempfile - -from kluctl.utils.run_helper import run_helper -from kluctl.utils.utils import get_tmp_base_dir - -logger = logging.getLogger('kubeseal') - -def kubeseal_raw(cert_file, secret, name, namespace, scope): - with tempfile.NamedTemporaryFile(dir=get_tmp_base_dir(), delete=False) as tmp: - try: - tmp.write(secret) - tmp.close() - return kubeseal_raw_path(cert_file, tmp.name, name, namespace, scope) - finally: - os.unlink(tmp.name) - -def kubeseal_raw_path(cert_file, secret_path, name, namespace, scope): - args = ['kubeseal', '-oyaml'] - args += ["--raw", "--cert", cert_file, "--from-file", secret_path] - if name is not None: - args += ["--name", name] - if namespace is not None: - args += ['--namespace', namespace] - if scope is not None: - args += ["--scope", scope] - r, out, err = run_helper(args, print_stderr=True, return_std=True) - if r != 0: - raise Exception("kubeseal returned %d, " % (r)) - return out.decode("utf-8") - -def kubeseal_fetch_cert(context, controller_ns, controller_name): - args = ['kubeseal', '--fetch-cert'] - args += ['--context', context, '--controller-namespace', controller_ns, '--controller-name', controller_name] - r, out, err = run_helper(args, print_stderr=True, return_std=True) - if r != 0: - raise Exception("kubeseal returned %d, " % (r)) - return out diff --git a/kluctl/utils/kustomize.py b/kluctl/utils/kustomize.py deleted file mode 100644 index 593f91f11..000000000 --- a/kluctl/utils/kustomize.py +++ /dev/null @@ -1,16 +0,0 @@ -from kluctl.utils.exceptions import CommandError -from kluctl.utils.external_tools import get_external_tool_path -from kluctl.utils.run_helper import run_helper - - -def kustomize(args, pathToYaml, ignoreErrors=False): - args = [get_external_tool_path("kustomize")] + args - - r, out, err = run_helper(args, cwd=pathToYaml, print_stderr=True, return_std=True) - if r != 0 and not ignoreErrors: - raise CommandError("kustomize failed: r=%d" % r) - return r, str(out.decode("utf-8")), err - -def kustomize_build(pathToYaml): - r, out, err = kustomize(["build", "--reorder", "none"], pathToYaml) - return out diff --git a/kluctl/utils/pretty_table.py b/kluctl/utils/pretty_table.py deleted file mode 100644 index d28df85cf..000000000 --- a/kluctl/utils/pretty_table.py +++ /dev/null @@ -1,51 +0,0 @@ -import shutil - - -def pretty_table(table, limit_widths): - cols = len(table[0]) - - def max_width(col, max_w): - w = 0 - for l in table: - w = max(w, len(l[col])) - if max_w != -1: - w = min(w, max_w) - return w - - widths = [max_width(i, limit_widths[i] if i < len(limit_widths) else -1) for i in range(cols)] - - if len(limit_widths) < cols: - term_columns, _ = shutil.get_terminal_size() - # last column should use all remaining space - widths[len(limit_widths)] = term_columns - sum(widths[:-1]) - (cols - 1) * 3 - 4 - - horizontal_separator = '+-' - for i in range(cols): - horizontal_separator += '-' * widths[i] - if i != cols - 1: - horizontal_separator += '-+-' - horizontal_separator += '-+' - - ret = '' - ret += horizontal_separator + '\n' - for l in table: - pos = [0] * cols - - while any([pos[i] < len(l[i]) for i in range(cols)]): - ret += '| ' - for i in range(cols): - t = l[i][pos[i]:pos[i] + widths[i]] - new_line = t.find('\n') - if new_line != -1: - t = t[:new_line] - pos[i] += 1 - pos[i] += len(t) - ret += t - ret += ' ' * (widths[i] - len(t)) - if i != cols - 1: - ret += ' | ' - ret += ' |\n' - - ret += horizontal_separator + '\n' - - return ret diff --git a/kluctl/utils/run_helper.py b/kluctl/utils/run_helper.py deleted file mode 100644 index fb32cc46f..000000000 --- a/kluctl/utils/run_helper.py +++ /dev/null @@ -1,115 +0,0 @@ -import io -import os -import subprocess -import sys -import threading -import time -import traceback - - -def stdin_write_thread(s, input): - pos = 0 - try: - while pos < len(input): - n = s.write(input[pos:]) - pos += n - s.close() - except: - pass - -class StdReader(threading.Thread): - def __init__(self, stream, do_print, line_mode, cb, capture): - super().__init__() - self.stream = stream - self.do_print = do_print - self.line_mode = line_mode - self.cb = cb - self.capture = capture - self.line_buf = io.BytesIO() - self.capture_buf = io.BytesIO() - - def split_std_lines(self, buf): - self.line_buf.write(buf) - if len(buf) == 0 or buf.find(b"\n") != -1: - line_buf_value = self.line_buf.getvalue() - self.line_buf.seek(0) - self.line_buf.truncate() - - if line_buf_value: - lines = line_buf_value.split(b"\n") - if lines and not lines[-1].endswith(b"\n") and len(buf) != 0: - self.line_buf.write(lines[-1]) - lines = lines[:-1] - - lines = [l.decode("utf-8") for l in lines] - return lines - return [] - - def do_write(self, buf): - if self.do_print is not None: - self.do_print.write(buf.decode("utf-8")) - self.do_print.flush() - if self.capture: - self.capture_buf.write(buf) - if self.cb is not None: - if self.line_mode: - lines = self.split_std_lines(buf) - if len(lines) > 0: - self.cb(lines) - else: - if len(buf) > 0: - self.cb(buf) - - def run(self) -> None: - try: - while True: - buf = self.stream.read() - if buf is None: - time.sleep(0.1) - continue - self.do_write(buf) - if len(buf) == 0: - break - except Exception: - traceback.print_exc() - -def set_non_blocking(f): - if sys.platform == "linux" or sys.platform == "darwin": - import fcntl - fd = f.fileno() - fl = fcntl.fcntl(fd, fcntl.F_GETFL) - fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK) - -def run_helper(args, cwd=None, env=None, input=None, - stdout_func=None, stderr_func=None, - print_stdout=False, print_stderr=False, - line_mode=False, return_std=False): - stdin = None - if input is not None: - stdin = subprocess.PIPE - if isinstance(input, str): - input = input.encode('utf-8') - process = subprocess.Popen(args=args, stdin=stdin, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd, env=env) - with process: - stdin_thread = None - if input is not None: - stdin_thread = threading.Thread(target=stdin_write_thread, args=(process.stdin, input)) - stdin_thread.start() - - set_non_blocking(process.stdout) - set_non_blocking(process.stderr) - - stdout_reader = StdReader(process.stdout, sys.stdout if print_stdout else None, line_mode, stdout_func, return_std) - stderr_reader = StdReader(process.stderr, sys.stderr if print_stderr else None, line_mode, stderr_func, return_std) - stdout_reader.start() - stderr_reader.start() - - process.wait() - stdout_reader.join() - stderr_reader.join() - if stdin_thread is not None: - stdin_thread.join() - - if return_std: - return process.returncode, stdout_reader.capture_buf.getvalue(), stderr_reader.capture_buf.getvalue() - return process.returncode diff --git a/kluctl/utils/session_pool.py b/kluctl/utils/session_pool.py deleted file mode 100644 index d24208940..000000000 --- a/kluctl/utils/session_pool.py +++ /dev/null @@ -1,35 +0,0 @@ -import threading - -import requests - -mutex = threading.Lock() -local = threading.local() -session_pool = {} - -class LocalSessionHolder: - def __init__(self, key, session): - self.key = key - self.session = session - - def __del__(self): - with mutex: - # release it to the global pool - session_pool.setdefault(self.key, []).append(self.session) - -def get_session_from_pool(key): - try: - d = local.sessions - except: - local.sessions = {} - d = local.sessions - - if key not in d: - with mutex: - pool = session_pool.get(key) - if pool: - s = pool.pop() - else: - s = requests.Session() - d[key] = LocalSessionHolder(key, s) - - return d[key].session diff --git a/kluctl/utils/templated_dir.py b/kluctl/utils/templated_dir.py deleted file mode 100644 index eed560736..000000000 --- a/kluctl/utils/templated_dir.py +++ /dev/null @@ -1,62 +0,0 @@ -import fnmatch -import logging -import os -import shutil - -logger = logging.getLogger(__name__) - - -class TemplatedDir(object): - def __init__(self, root_dir, rel_source_dir, jinja_env, executor, excluded_patterns): - self.root_dir = root_dir - self.rel_source_dir = rel_source_dir - self.jinja_env = jinja_env - self.excluded_patterns = excluded_patterns - self.executor = executor - - def needs_render(self, path): - for p in self.excluded_patterns: - if fnmatch.fnmatch(path, p): - return False - return True - - def async_render_subdir(self, subdir, target_dir): - jobs = [] - - walk_dir = os.path.join(self.root_dir, self.rel_source_dir, subdir) - for dirpath, dirnames, filenames in os.walk(walk_dir): - rel_dirpath = os.path.relpath(dirpath, walk_dir) - for n in dirnames: - target_path = os.path.join(target_dir, rel_dirpath, n) - os.mkdir(target_path) - for n in filenames: - source_path = os.path.normpath(os.path.join(subdir, rel_dirpath, n)) - target_path = os.path.join(target_dir, rel_dirpath, n) - if self.needs_render(source_path): - f = self.executor.submit(self.do_render_file, os.path.join(self.rel_source_dir, source_path), target_path) - else: - f = self.executor.submit(shutil.copy, os.path.join(self.root_dir, self.rel_source_dir, source_path), target_path) - jobs.append(f) - - return jobs - - def do_render_file(self, source_path, target_path): - source_path = os.path.normpath(source_path) - # jinja2 templates are using / even on windows - source_path = source_path.replace("\\", "/") - template = self.jinja_env.get_template(source_path) - rendered = template.render() - with open(target_path, "wt") as f: - f.write(rendered) - - @staticmethod - def finish_jobs(jobs): - error = None - for f in jobs: - try: - f.result() - except Exception as e: - if error is None: - error = e - if error is not None: - raise error diff --git a/kluctl/utils/thread_safe_cache.py b/kluctl/utils/thread_safe_cache.py deleted file mode 100644 index bffdb12fa..000000000 --- a/kluctl/utils/thread_safe_cache.py +++ /dev/null @@ -1,38 +0,0 @@ -import threading - - -class ThreadSafeCache: - def __init__(self): - self.lock = threading.Lock() - self.cache = {} - - def get(self, key, func): - with self.lock: - entry = self.cache.setdefault(key, { - "done": False, - "value": None, - "exception": None - }) - - def do_ret(): - if entry["exception"] is not None: - raise entry["exception"] - return entry["value"] - - if entry["done"]: - return do_ret() - - try: - entry["value"] = func() - except Exception as e: - entry["exception"] = e - entry["done"] = True - return do_ret() - -class ThreadSafeMultiCache: - def __init__(self): - self.cache = ThreadSafeCache() - - def get(self, cache_key, key, func): - cache = self.cache.get(cache_key, lambda: ThreadSafeCache()) - return cache.get(key, func) diff --git a/kluctl/utils/utils.py b/kluctl/utils/utils.py deleted file mode 100644 index a4dca9bc1..000000000 --- a/kluctl/utils/utils.py +++ /dev/null @@ -1,111 +0,0 @@ -import hashlib -import logging -import os -import shutil -import sys -import tempfile -from concurrent.futures.thread import ThreadPoolExecutor -from datetime import timedelta -from decimal import Decimal - -logger = logging.getLogger(__name__) - -def get_tmp_base_dir(): - dir = os.path.join(tempfile.gettempdir(), "kluctl") - os.makedirs(dir, exist_ok=True) - return dir - -def copytree(src, dst, symlinks=False, ignore=None): - for item in os.listdir(src): - s = os.path.join(src, item) - d = os.path.join(dst, item) - if os.path.isdir(s): - shutil.copytree(s, d, symlinks, ignore) - else: - shutil.copy2(s, d) - -def duration(duration_string): # example: '5d3h2m1s' - if isinstance(duration_string, timedelta): - return duration_string - duration_string = duration_string.lower() - total_seconds = Decimal('0') - prev_num = [] - for character in duration_string: - if character.isalpha(): - if prev_num: - num = Decimal(''.join(prev_num)) - if character == 'd': - total_seconds += num * 60 * 60 * 24 - elif character == 'h': - total_seconds += num * 60 * 60 - elif character == 'm': - total_seconds += num * 60 - elif character == 's': - total_seconds += num - prev_num = [] - elif character.isnumeric() or character == '.': - prev_num.append(character) - return timedelta(seconds=float(total_seconds)) - -def parse_bool(s, do_raise=False): - if s.lower() in ["true", "1", "yes"]: - return True - if do_raise and s.lower() not in ["false", "0", "no"]: - raise ValueError("Invalid boolean value %s" % s) - return False - -def calc_dir_hash(dir): - h = hashlib.sha256() - - for dirpath, dirnames, filenames in os.walk(dir, topdown=True): - dirnames.sort() # ensure iteration order is stable (thanks to topdown, we can do this here at this time) - filenames.sort() - rel_dirpath = os.path.relpath(dirpath, dir) - h.update(rel_dirpath.encode("utf-8")) - for f in filenames: - h.update(os.path.join(rel_dirpath, f).encode("utf-8")) - with open(os.path.join(dirpath, f), mode="rb") as file: - buf = file.read() - h.update(buf) - - return h.hexdigest() - -class DummyExecutor: - def __init__(self, *args, **kwargs): - pass - - def shutdown(self): - pass - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - pass - - def submit(self, f, *args, **kwargs): - class Future: - def __init__(self, result, exc): - self._result = result - self._exc = exc - def exception(self): - return self._exc - def result(self): - if self._exc is not None: - raise self._exc - return self._result - try: - result = f(*args, **kwargs) - return Future(result, None) - except Exception as e: - return Future(None, e) - -if os.environ.get("KLUCTL_NO_THREADS", "false").lower() in ["1", "true"] or (sys.gettrace() is not None and os.environ.get("KLUCTL_IGNORE_DEBUGGER", "false").lower() not in ["1", "true"]): - print("Detected a debugger, using DummyExecutor for ThreadPool", file=sys.stderr) - class MyThreadPoolExecutor(DummyExecutor): - pass -else: - class MyThreadPoolExecutor(ThreadPoolExecutor): - - def __init__(self, max_workers=os.cpu_count(), *args, **kwargs) -> None: - super().__init__(max_workers=max_workers, *args, **kwargs) diff --git a/kluctl/utils/versions.py b/kluctl/utils/versions.py deleted file mode 100644 index 10b393b4b..000000000 --- a/kluctl/utils/versions.py +++ /dev/null @@ -1,222 +0,0 @@ -import ast -import re - -LOOSE_SEMVER_REGEX=r"^(([0-9]+)(\.[0-9]+)*)?(.*)$" -SUFFIX_COMPONENT_REGEX = re.compile(r'(\d+ | [a-z]+ | \.)', re.VERBOSE) - -""" -Allows to compare ints and strings. Strings are always considered less then ints. -""" -class IntOrString(object): - def __init__(self, x): - self.x = x - - @staticmethod - def less(a, b): - a_int = isinstance(a, int) - b_int = isinstance(b, int) - if a_int == b_int: - return a < b - if a_int: - return False - else: - return True - - def __lt__(self, other): - return IntOrString.less(self.x, other.x) - -class LooseVersionComparator(object): - def __init__(self, v): - self.version = v - - @staticmethod - def split_version(v): - r = re.compile(LOOSE_SEMVER_REGEX) - m = r.match(v) - nums = m.group(1) - suffix = m.group(4) - nums = [int(x) for x in nums.split('.')] if nums else [] - return nums, suffix - - @staticmethod - def split_suffix(v): - components = [x for x in SUFFIX_COMPONENT_REGEX.split(v) if x and x != '.'] - for i, obj in enumerate(components): - try: - components[i] = int(obj) - except ValueError: - pass - components = [IntOrString(x) for x in components] - return components - - @staticmethod - def less(a, b, prefer_long_suffix=True): - a_nums, a_suffix = LooseVersionComparator.split_version(a) - b_nums, b_suffix = LooseVersionComparator.split_version(b) - - if a_nums < b_nums: - return True - if a_nums > b_nums: - return False - - if len(a_suffix) == 0 and len(b_suffix) != 0: - return False - elif len(a_suffix) != 0 and len(b_suffix) == 0: - return True - - a_suffix = LooseVersionComparator.split_suffix(a_suffix) - b_suffix = LooseVersionComparator.split_suffix(b_suffix) - - for i in range(min(len(a_suffix), len(b_suffix))): - if a_suffix[i] < b_suffix[i]: - return True - if b_suffix[i] < a_suffix[i]: - return False - - if prefer_long_suffix: - if len(a_suffix) < len(b_suffix): - return True - else: - if len(b_suffix) < len(a_suffix): - return True - return False - - def __lt__(self, other): - return LooseVersionComparator.less(self.version, other.version) - - @staticmethod - def compare(a, b): - if LooseVersionComparator.less(a, b): - return -1 - if LooseVersionComparator.less(b, a): - return 1 - return 0 - -class LatestVersion(object): - def match(self, version): - raise NotImplementedError() - def filter(self, versions): - return [v for v in versions if self.match(v)] - def latest(self, versions): - raise NotImplementedError() - -class RegexLatestVersion(LatestVersion): - def __init__(self, pattern): - self.pattern_str = pattern - self.pattern = re.compile(pattern) - - def match(self, version): - return self.pattern.fullmatch(version) - - def latest(self, versions): - versions = sorted(versions, key=LooseVersionComparator) - return versions[-1] - - def __str__(self): - return f"regex(pattern=\"{self.pattern_str}\")" - -class LooseSemVerLatestVersion(LatestVersion): - def __init__(self, allow_no_nums=False): - self.allow_no_nums = allow_no_nums - self.pattern = re.compile(LOOSE_SEMVER_REGEX) - - def match(self, version): - m = self.pattern.match(version) - if not m: - return False - if not self.allow_no_nums and m.group(1) is None: - return False - return True - - def latest(self, versions): - versions = sorted(versions, key=LooseVersionComparator) - return versions[-1] - - def __str__(self): - return f"semver(allow_no_nums={self.allow_no_nums})" - -class PrefixLatestVersion(LatestVersion): - def __init__(self, prefix, suffix=RegexLatestVersion('.*')): - self.prefix = prefix - self.suffix = suffix - self.pattern = re.compile(r"^%s(.*)$" % prefix) - - def match(self, version): - m = self.pattern.match(version) - if not m: - return False - return self.suffix.match(m.group(1)) - - def latest(self, versions): - filtered_versions = [] - suffix_versions = [] - for v in versions: - m = self.pattern.match(v) - if m: - filtered_versions.append(v) - suffix_versions.append(m.group(1)) - latest = self.suffix.latest(suffix_versions) - i = suffix_versions.index(latest) - return filtered_versions[i] - - def __str__(self): - return f"prefix(prefix=\"{self.prefix}\", suffix={str(self.suffix)})" - -class NumberLatestVersion(RegexLatestVersion): - def __init__(self): - super().__init__("^[0-9]+$") - - def latest(self, versions): - versions = [int(x) for x in versions] - versions.sort() - return versions[-1] - - def __str__(self): - return f"number()" - -def build_latest_version_from_str(s): - def do_raise(): - raise ValueError("invalid latest_version filter: %s" % s) - - def parse_ast(a): - if not isinstance(a, ast.Call): - do_raise() - - args = [] - kwargs = {} - - def parse_arg(arg): - if isinstance(arg, ast.Constant): - return arg.value - elif isinstance(arg, ast.Call): - return parse_ast(arg) - else: - do_raise() - - for arg in a.args: - args.append(parse_arg(arg)) - for arg in a.keywords: - kwargs[arg.arg] = parse_arg(arg.value) - - name = a.func.id - if name == "regex": - cls = RegexLatestVersion - elif name == "semver": - cls = LooseSemVerLatestVersion - elif name == "prefix": - cls = PrefixLatestVersion - elif name == "number": - cls = NumberLatestVersion - else: - do_raise() - return cls(*args, **kwargs) - - try: - a = ast.parse(s, mode="eval") - if not isinstance(a, ast.Expression): - do_raise() - return parse_ast(a.body) - except ValueError: - raise - except Exception as e: - raise do_raise() diff --git a/kluctl/utils/versions_test.py b/kluctl/utils/versions_test.py deleted file mode 100644 index 828a250f8..000000000 --- a/kluctl/utils/versions_test.py +++ /dev/null @@ -1,94 +0,0 @@ -import unittest - -from kluctl.utils.versions import LooseVersionComparator, LooseSemVerLatestVersion, PrefixLatestVersion, \ - NumberLatestVersion - -versions_list = [ - "abc", - "1", - "1.1", - "a-1", - "1.1.1", - "1.1.1-a", - "1.1.1-1", - "b-1-c", - "b-100", - "12", -] - -class TestLooseVersionComparator(unittest.TestCase): - def check_equal(self, a, b): - self.assertFalse(LooseVersionComparator.less(a, b)) - self.assertFalse(LooseVersionComparator.less(b, a)) - self.assertIs(LooseVersionComparator.compare(a, b), 0) - - def check_less(self, a, b): - self.assertTrue(LooseVersionComparator.less(a, b)) - self.assertFalse(LooseVersionComparator.less(b, a)) - self.assertIs(LooseVersionComparator.compare(a, b), -1) - self.assertIs(LooseVersionComparator.compare(b, a), 1) - - def test_equality(self): - self.check_equal('1', '1') - self.check_equal('1.1', '1.1') - self.check_equal('1.1a', '1.1a') - self.check_equal('1.1-a', '1.1-a') - self.check_equal('1.a-1', '1.a-1') - - def test_less(self): - self.check_less('1', '2') - self.check_less('1', '1.1') - self.check_less('1.1a', '1.1') - self.check_less('1.1-a', '1.1') - self.check_less('1.1a', '1.1b') - self.check_less('1.1-a', '1.1-b') - self.check_less('1.a-1', '1.a-2') - - def test_maven_versions(self): - self.check_less('1.1-SNAPSHOT', '1.1') - self.check_less('1', '1.1') - self.check_less('1-SNAPSHOT', '1.1') - self.check_less('1.1', '1.1.1-SNAPSHOT') - self.check_less('1.1-SNAPSHOT', '1.1.1-SNAPSHOT') - - def test_suffixes(self): - self.check_less('1.1-1', '1.1-2') - self.check_less('1.1-suffix-1', '1.1-suffix-2') - self.check_less('1.1-suffix-2', '1.1-suffix-10') - self.check_less('1.1-suffix-2', '1.1-suffiy-1') - self.check_less('1.1-1-1', '1.1-2-1') - self.check_less('1.1-2-1', '1.1-2-2') - self.check_less('1.1-2-1', '1.1-100-2') - self.check_less('1.1-2-1', '1.1') - self.check_less('1.1-2', '1.1-2-1') - self.check_less('1.1-a-1', '1.1-1-1') - - def test_loose_version_no_nums(self): - self.check_less('-snapshot1', '-snapshot2') - self.check_less('-1', '-2') - self.check_less('-1.1', '-1.2') - -class TestVersionFilters(unittest.TestCase): - def test_loose_semver(self): - filtered = LooseSemVerLatestVersion().filter(versions_list) - self.assertListEqual(filtered, [ - "1", - "1.1", - "1.1.1", - "1.1.1-a", - "1.1.1-1", - "12", - ]) - - def test_prefix(self): - filtered = PrefixLatestVersion("a-").filter(versions_list) - self.assertListEqual(filtered, [ - "a-1", - ]) - - def test_number(self): - filtered = NumberLatestVersion().filter(versions_list) - self.assertListEqual(filtered, [ - "1", - "12" - ]) \ No newline at end of file diff --git a/pkg/deployment/apply_utils.go b/pkg/deployment/apply_utils.go new file mode 100644 index 000000000..f037c0347 --- /dev/null +++ b/pkg/deployment/apply_utils.go @@ -0,0 +1,356 @@ +package deployment + +import ( + "fmt" + "github.com/codablock/kluctl/pkg/diff" + "github.com/codablock/kluctl/pkg/k8s" + "github.com/codablock/kluctl/pkg/types" + "github.com/codablock/kluctl/pkg/utils" + "github.com/codablock/kluctl/pkg/validation" + log "github.com/sirupsen/logrus" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sync" + "time" +) + +type applyUtilOptions struct { + forceApply bool + replaceOnError bool + forceReplaceOnError bool + dryRun bool + abortOnError bool + hookTimeout time.Duration +} + +type applyUtil struct { + deploymentCollection *DeploymentCollection + k *k8s.K8sCluster + o applyUtilOptions + + appliedObjects map[types.ObjectRef]*unstructured.Unstructured + appliedHookObjects map[types.ObjectRef]*unstructured.Unstructured + abortSignal bool + errorRefs map[types.ObjectRef]error + mutex sync.Mutex +} + +func newApplyUtil(deploymentCollection *DeploymentCollection, k *k8s.K8sCluster, o applyUtilOptions) *applyUtil { + return &applyUtil{ + deploymentCollection: deploymentCollection, + k: k, + o: o, + appliedObjects: map[types.ObjectRef]*unstructured.Unstructured{}, + appliedHookObjects: map[types.ObjectRef]*unstructured.Unstructured{}, + errorRefs: map[types.ObjectRef]error{}, + } +} + +func (a *applyUtil) handleResult(appliedObject *unstructured.Unstructured, hook bool) { + a.mutex.Lock() + defer a.mutex.Unlock() + + ref := types.RefFromObject(appliedObject) + if hook { + a.appliedHookObjects[ref] = appliedObject + } else { + a.appliedObjects[ref] = appliedObject + } +} + +func (a *applyUtil) handleError(ref types.ObjectRef, err error) { + a.mutex.Lock() + defer a.mutex.Unlock() + + a.errorRefs[ref] = err + if a.o.abortOnError { + a.abortSignal = true + } + + a.deploymentCollection.addError(ref, err) +} + +func (a *applyUtil) hadError(ref types.ObjectRef) bool { + a.mutex.Lock() + defer a.mutex.Unlock() + + _, ok := a.errorRefs[ref] + return ok +} + +func (a *applyUtil) deleteObject(ref types.ObjectRef) bool { + o := k8s.DeleteOptions{ + ForceDryRun: a.o.dryRun, + } + err := a.k.DeleteSingleObject(ref, o) + if err != nil { + a.handleError(ref, err) + return false + } + return true +} + +func (a *applyUtil) retryApplyForceReplace(x *unstructured.Unstructured, hook bool, applyError error) { + ref := types.RefFromObject(x) + log2 := log.WithField("ref", ref) + + if !a.o.forceReplaceOnError { + a.handleError(ref, applyError) + return + } + + log2.Warningf("Patching failed, retrying by deleting and re-applying") + + if !a.deleteObject(ref) { + return + } + + if !a.o.dryRun { + o := k8s.PatchOptions{ + ForceDryRun: a.o.dryRun, + } + r, err := a.k.PatchObject(x, o) + if err != nil { + a.handleError(ref, err) + return + } + a.handleResult(r, hook) + } else { + a.handleResult(x, hook) + } +} + +func (a *applyUtil) retryApplyWithReplace(x *unstructured.Unstructured, hook bool, remoteObject *unstructured.Unstructured, applyError error) { + ref := types.RefFromObject(x) + log2 := log.WithField("ref", ref) + + if !a.o.replaceOnError || remoteObject == nil { + a.handleError(ref, applyError) + return + } + + log2.Warningf("Patching failed, retrying with replace instead of patch") + + rv := remoteObject.GetResourceVersion() + x2 := utils.CopyUnstructured(x) + x2.SetResourceVersion(rv) + + o := k8s.UpdateOptions{ + ForceDryRun: a.o.dryRun, + } + + r, err := a.k.UpdateObject(x, o) + if err != nil { + a.retryApplyForceReplace(x, hook, err) + return + } + a.handleResult(r, hook) +} + +func (a *applyUtil) retryApplyWithConflicts(x *unstructured.Unstructured, hook bool, remoteObject *unstructured.Unstructured, applyError error) { + ref := types.RefFromObject(x) + + if remoteObject == nil { + a.handleError(ref, applyError) + } + + var resolveWarnings []error + var x2 *unstructured.Unstructured + if !a.o.forceApply { + statusError, ok := applyError.(*errors.StatusError) + if !ok { + a.handleError(ref, applyError) + return + } + + x3, lostOwnership, err := diff.ResolveFieldManagerConflicts(x, remoteObject, statusError.ErrStatus) + if err != nil { + a.handleError(ref, err) + return + } + for _, lo := range lostOwnership { + resolveWarnings = append(resolveWarnings, fmt.Errorf("%s. Not updating field '%s' as we lost field ownership", lo.Message, lo.Field)) + } + a.deploymentCollection.addWarnings(ref, resolveWarnings) + x2 = x3 + } else { + x2 = x + } + + options := k8s.PatchOptions{ + ForceDryRun: a.o.dryRun, + ForceApply: true, + } + r, err := a.k.PatchObject(x2, options) + if err != nil { + // We didn't manage to solve it, better to abort (and not retry with replace!) + a.handleError(ref, err) + return + } + a.handleResult(r, hook) +} + +func (a *applyUtil) applyObject(x *unstructured.Unstructured, replaced bool, hook bool) { + ref := types.RefFromObject(x) + log2 := log.WithField("ref", ref) + log2.Debugf("applying object") + + x = a.k.FixObjectForPatch(x) + remoteObject := a.deploymentCollection.getRemoteObject(ref) + + if a.o.dryRun && replaced && remoteObject != nil { + // Let's simulate that this object was deleted in dry-run mode. If we'd actually try a dry-run apply with + // this object, it might fail as it is expected to not exist. + a.handleResult(x, hook) + return + } + + options := k8s.PatchOptions{ + ForceDryRun: a.o.dryRun, + } + r, err := a.k.PatchObject(x, options) + if err == nil { + a.handleResult(r, hook) + } else if meta.IsNoMatchError(err) { + a.handleError(ref, err) + } else if errors.IsConflict(err) { + a.retryApplyWithConflicts(x, hook, remoteObject, err) + } else { + a.retryApplyWithReplace(x, hook, remoteObject, err) + } +} + +func (a *applyUtil) waitHook(ref types.ObjectRef) bool { + if a.o.dryRun { + return true + } + + log2 := log.WithField("ref", ref) + log2.Debugf("Waiting for hook to get ready") + + didLog := false + startTime := time.Now() + for true { + o, err := a.k.GetSingleObject(ref) + if err != nil { + if errors.IsNotFound(err) { + if didLog { + log2.Warningf("Cancelled waiting for hook as it disappeared while waiting for it") + } + a.handleError(ref, fmt.Errorf("object disappeared while waiting for it to become ready")) + return false + } + a.handleError(ref, err) + return false + } + v := validation.ValidateObject(o, false) + if v.Ready { + if didLog { + log2.Infof("Finished waiting for hook") + } + return true + } + if len(v.Errors) != 0 { + if didLog { + log2.Warningf("Cancelled waiting for hook due to errors") + } + for _, e := range v.Errors { + a.handleError(ref, fmt.Errorf(e.Message)) + } + return false + } + + if a.o.hookTimeout != 0 && time.Now().Sub(startTime) >= a.o.hookTimeout { + err := fmt.Errorf("timed out while waiting for hook") + log2.Warningf(err.Error()) + a.handleError(ref, err) + return false + } + + if !didLog { + log2.Infof("Waiting for hook to get ready...") + didLog = true + } + + time.Sleep(500 * time.Millisecond) + } + return false +} + +func (a *applyUtil) applyKustomizeDeployment(d *deploymentItem) { + if d.config.Path == nil { + return + } + + if !d.checkInclusionForDeploy() { + a.doLog(d, log.InfoLevel, "Skipping") + return + } + + initialDeploy := true + for _, o := range d.objects { + if a.deploymentCollection.getRemoteObject(types.RefFromObject(o)) != nil { + initialDeploy = false + } + } + + h := hooksUtil{a: a} + + if initialDeploy { + h.runHooks(d, []string{"pre-deploy-initial", "pre-deploy"}) + } else { + h.runHooks(d, []string{"pre-deploy-upgrade", "pre-deploy"}) + } + + var applyObjects []*unstructured.Unstructured + for _, o := range d.objects { + if h.getHook(o) != nil { + continue + } + applyObjects = append(applyObjects, o) + } + + a.doLog(d, log.InfoLevel, "Applying %d objects", len(d.objects)) + for _, o := range applyObjects { + a.applyObject(o, false, false) + } + + if initialDeploy { + h.runHooks(d, []string{"post-deploy-initial", "post-deploy"}) + } else { + h.runHooks(d, []string{"post-deploy-upgrade", "post-deploy"}) + } +} + +func (a *applyUtil) applyDeployments() { + log.Infof("Running server-side apply for all objects") + + wp := utils.NewDebuggerAwareWorkerPool(16) + defer wp.StopWait(false) + + previousWasBarrier := false + for _, d_ := range a.deploymentCollection.deployments { + d := d_ + if a.abortSignal { + break + } + if previousWasBarrier { + log.Infof("Waiting on barrier...") + _ = wp.StopWait(true) + } + + previousWasBarrier = d.config.Barrier != nil && *d.config.Barrier + + wp.Submit(func() error { + a.applyKustomizeDeployment(d) + return nil + }) + } + _ = wp.StopWait(false) +} + +func (a *applyUtil) doLog(d *deploymentItem, level log.Level, s string, f ...interface{}) { + s = fmt.Sprintf("%s: %s", d.relToProjectItemDir, fmt.Sprintf(s, f...)) + log.StandardLogger().Logf(level, s) +} diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go new file mode 100644 index 000000000..a410305bf --- /dev/null +++ b/pkg/deployment/deployment_collection.go @@ -0,0 +1,449 @@ +package deployment + +import ( + "fmt" + "github.com/codablock/kluctl/pkg/diff" + "github.com/codablock/kluctl/pkg/k8s" + "github.com/codablock/kluctl/pkg/types" + "github.com/codablock/kluctl/pkg/utils" + log "github.com/sirupsen/logrus" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "path" + "path/filepath" + "strings" + "sync" + "time" +) + +type DeploymentCollection struct { + project *DeploymentProject + images *Images + inclusion *utils.Inclusion + RenderDir string + forSeal bool + + deployments []*deploymentItem + remoteObjects map[types.ObjectRef]*unstructured.Unstructured + + errors map[types.DeploymentError]bool + warnings map[types.DeploymentError]bool +} + +func NewDeploymentCollection(project *DeploymentProject, images *Images, inclusion *utils.Inclusion, renderDir string, forSeal bool) (*DeploymentCollection, error) { + dc := &DeploymentCollection{ + project: project, + images: images, + inclusion: inclusion, + RenderDir: renderDir, + forSeal: forSeal, + remoteObjects: map[types.ObjectRef]*unstructured.Unstructured{}, + errors: map[types.DeploymentError]bool{}, + warnings: map[types.DeploymentError]bool{}, + } + + indexes := make(map[string]int) + deployments, err := dc.collectDeployments(project, indexes) + if err != nil { + return nil, err + } + dc.deployments = deployments + return dc, nil +} + +func (c *DeploymentCollection) collectDeployments(project *DeploymentProject, indexes map[string]int) ([]*deploymentItem, error) { + var ret []*deploymentItem + + for _, diConfig := range project.config.DeploymentItems { + var dir2 *string + index := 0 + if diConfig.Path != nil { + dir := path.Join(project.dir, *diConfig.Path) + dir, err := filepath.Abs(dir) + if err != nil { + return nil, err + } + if !strings.HasPrefix(dir, project.getRootProject().dir) { + return nil, fmt.Errorf("kustomizeDir path is not part of root deployment project: %s", *diConfig.Path) + } + if _, ok := indexes[dir]; !ok { + indexes[dir] = 0 + } + index, _ = indexes[dir] + indexes[dir] = index + 1 + dir2 = &dir + } + di, err := NewDeploymentItem(project, c, diConfig, dir2, index) + if err != nil { + return nil, err + } + ret = append(ret, di) + } + + for i, incConf := range project.config.Includes { + if incConf.Barrier != nil && *incConf.Barrier { + tmpDiConfig := &types.DeploymentItemConfig{ + BaseItemConfig: incConf.BaseItemConfig, + } + di, err := NewDeploymentItem(project, c, tmpDiConfig, nil, 0) + if err != nil { + return nil, err + } + ret = append(ret, di) + } + + if includedProject, ok := project.includes[i]; ok { + ret2, err := c.collectDeployments(includedProject, indexes) + if err != nil { + return nil, err + } + ret = append(ret, ret2...) + } + } + + return ret, nil +} + +func (c *DeploymentCollection) renderDeployments(k *k8s.K8sCluster) error { + log.Infof("Rendering templates and Helm charts") + + wp := utils.NewDebuggerAwareWorkerPool(16) + defer wp.StopWait(false) + + for _, d := range c.deployments { + err := d.render(k, wp) + if err != nil { + return err + } + } + err := wp.StopWait(true) + if err != nil { + return err + } + + for _, d := range c.deployments { + err := d.renderHelmCharts(k, wp) + if err != nil { + return err + } + } + err = wp.StopWait(false) + if err != nil { + return err + } + + return nil +} + +func (c *DeploymentCollection) resolveSealedSecrets() error { + if c.forSeal { + return nil + } + + for _, d := range c.deployments { + err := d.resolveSealedSecrets() + if err != nil { + return err + } + } + return nil +} + +func (c *DeploymentCollection) buildKustomizeObjects(k *k8s.K8sCluster) error { + log.Infof("Building kustomize objects") + + wp := utils.NewDebuggerAwareWorkerPool(8) + defer wp.StopWait(false) + + for _, d_ := range c.deployments { + d := d_ + wp.Submit(func() error { + err := d.buildKustomize() + if err != nil { + return fmt.Errorf("building kustomize objects for %s failed. %w", *d.dir, err) + } + err = d.postprocessAndLoadObjects(k) + if err != nil { + return fmt.Errorf("postprocessing kustomize objects for %s failed. %w", *d.dir, err) + } + return nil + }) + } + err := wp.StopWait(false) + if err != nil { + return err + } + + return nil +} + +func (c *DeploymentCollection) updateRemoteObjects(k *k8s.K8sCluster) error { + if k == nil { + return nil + } + + notFoundRefsMap := make(map[types.ObjectRef]bool) + var notFoundRefsList []types.ObjectRef + for ref := range c.localObjectsByRef() { + if _, ok := c.remoteObjects[ref]; !ok { + if _, ok = notFoundRefsMap[ref]; !ok { + notFoundRefsMap[ref] = true + notFoundRefsList = append(notFoundRefsList, ref) + } + } + } + + log.Infof("Updating %d remote objects", len(notFoundRefsList)) + + r, err := k.GetObjectsByRefs(notFoundRefsList) + if err != nil { + return err + } + for _, o := range r { + c.remoteObjects[types.RefFromObject(o)] = o + } + return nil +} + +func (c *DeploymentCollection) forgetRemoteObjects(refs []types.ObjectRef) { + for _, ref := range refs { + delete(c.remoteObjects, ref) + } +} + +func (c *DeploymentCollection) localObjectsByRef() map[types.ObjectRef]bool { + ret := make(map[types.ObjectRef]bool) + for _, d := range c.deployments { + for _, o := range d.objects { + ret[types.RefFromObject(o)] = true + } + } + return ret +} + +func (c *DeploymentCollection) localObjectRefs() []types.ObjectRef { + var ret []types.ObjectRef + for ref := range c.localObjectsByRef() { + ret = append(ret, ref) + } + return ret +} + +func (c *DeploymentCollection) Prepare(k *k8s.K8sCluster) error { + err := c.renderDeployments(k) + if err != nil { + return err + } + err = c.resolveSealedSecrets() + if err != nil { + return err + } + err = c.buildKustomizeObjects(k) + if err != nil { + return err + } + err = c.updateRemoteObjects(k) + if err != nil { + return err + } + return nil +} + +func (c *DeploymentCollection) Deploy(k *k8s.K8sCluster, forceApply bool, replaceOnError bool, forceReplaceOnError, abortOnError bool, hookTimeout time.Duration) (*types.CommandResult, error) { + c.clearErrorsAndWarnings() + o := applyUtilOptions{ + forceApply: forceApply, + replaceOnError: replaceOnError, + forceReplaceOnError: forceReplaceOnError, + dryRun: k.DryRun, + abortOnError: abortOnError, + hookTimeout: hookTimeout, + } + appliedObjects, appliedHookObjects, err := c.doApply(k, o) + if err != nil { + return nil, err + } + var appliedHookObjectsList []*unstructured.Unstructured + for _, o := range appliedHookObjects { + appliedHookObjectsList = append(appliedHookObjectsList, o) + } + newObjects, changedObjects, err := c.doDiff(k, appliedObjects, false, false, false) + if err != nil { + return nil, err + } + orphanObjects, err := c.findOrphanObjects(k) + if err != nil { + return nil, err + } + return &types.CommandResult{ + NewObjects: newObjects, + ChangedObjects: changedObjects, + HookObjects: appliedHookObjectsList, + OrphanObjects: orphanObjects, + Errors: c.errorsList(), + Warnings: c.warningsList(), + }, nil +} + +func (c *DeploymentCollection) Diff(k *k8s.K8sCluster, forceApply bool, replaceOnError bool, forceReplaceOnError bool, ignoreTags bool, ignoreLabels bool, ignoreAnnotations bool) (*types.CommandResult, error) { + c.clearErrorsAndWarnings() + o := applyUtilOptions{ + forceApply: forceApply, + replaceOnError: replaceOnError, + forceReplaceOnError: forceReplaceOnError, + dryRun: true, + abortOnError: false, + hookTimeout: 0, + } + appliedObjects, appliedHookObjects, err := c.doApply(k, o) + if err != nil { + return nil, err + } + var appliedHookObjectsList []*unstructured.Unstructured + for _, o := range appliedHookObjects { + appliedHookObjectsList = append(appliedHookObjectsList, o) + } + newObjects, changedObjects, err := c.doDiff(k, appliedObjects, ignoreTags, ignoreLabels, ignoreAnnotations) + if err != nil { + return nil, err + } + orphanObjects, err := c.findOrphanObjects(k) + if err != nil { + return nil, err + } + return &types.CommandResult{ + NewObjects: newObjects, + ChangedObjects: changedObjects, + HookObjects: appliedHookObjectsList, + OrphanObjects: orphanObjects, + Errors: c.errorsList(), + Warnings: c.warningsList(), + }, nil +} + +func (c *DeploymentCollection) findDeleteObjects(k *k8s.K8sCluster) ([]types.ObjectRef, error) { + labels := c.project.getDeleteByLabels() + return k8s.FindObjectsForDelete(k, labels, c.inclusion, nil) +} + +func (c *DeploymentCollection) findOrphanObjects(k *k8s.K8sCluster) ([]types.ObjectRef, error) { + log.Infof("Searching for orphan objects") + labels := c.project.getDeleteByLabels() + return k8s.FindObjectsForDelete(k, labels, c.inclusion, c.localObjectRefs()) +} + +func (c *DeploymentCollection) doApply(k *k8s.K8sCluster, o applyUtilOptions) (map[types.ObjectRef]*unstructured.Unstructured, map[types.ObjectRef]*unstructured.Unstructured, error) { + au := newApplyUtil(c, k, o) + au.applyDeployments() + return au.appliedObjects, au.appliedHookObjects, nil +} + +func (c *DeploymentCollection) doDiff(k *k8s.K8sCluster, appliedObjects map[types.ObjectRef]*unstructured.Unstructured, ignoreTags bool, ignoreLabels bool, ignoreAnnotations bool) ([]*unstructured.Unstructured, []*types.ChangedObject, error) { + diffObjects := make(map[types.ObjectRef]*unstructured.Unstructured) + normalizedDiffObjects := make(map[types.ObjectRef]*unstructured.Unstructured) + normalizedRemoteObjects := make(map[types.ObjectRef]*unstructured.Unstructured) + + for _, d := range c.deployments { + if !d.checkInclusionForDeploy() { + continue + } + + ignoreForDiffs := d.project.getIgnoreForDiffs(ignoreTags, ignoreLabels, ignoreAnnotations) + for _, o := range d.objects { + ref := types.RefFromObject(o) + if _, ok := appliedObjects[ref]; !ok { + continue + } + diffObjects[ref] = appliedObjects[ref] + normalizedDiffObjects[ref] = diff.NormalizeObject(k, diffObjects[ref], ignoreForDiffs, diffObjects[ref]) + if r, ok := c.remoteObjects[ref]; ok { + normalizedRemoteObjects[ref] = diff.NormalizeObject(k, r, ignoreForDiffs, diffObjects[ref]) + } + } + } + + wp := utils.NewDebuggerAwareWorkerPool(8) + defer wp.StopWait(false) + + var newObjects []*unstructured.Unstructured + var changedObjects []*types.ChangedObject + var mutex sync.Mutex + + for ref, o_ := range diffObjects { + o := o_ + normalizedRemoteObject, ok := normalizedRemoteObjects[ref] + if !ok { + newObjects = append(newObjects, o) + continue + } + normalizedDiffObject, _ := normalizedDiffObjects[ref] + remoteObject, _ := c.remoteObjects[ref] + wp.Submit(func() error { + changes, err := diff.Diff(normalizedRemoteObject, normalizedDiffObject) + if err != nil { + return err + } + if len(changes) == 0 { + return nil + } + mutex.Lock() + defer mutex.Unlock() + changedObjects = append(changedObjects, &types.ChangedObject{ + NewObject: o, + OldObject: remoteObject, + Changes: changes, + }) + return nil + }) + } + + err := wp.StopWait(false) + if err != nil { + return nil, nil, err + } + + return newObjects, changedObjects, nil +} + +func (c *DeploymentCollection) addWarnings(ref types.ObjectRef, warnings []error) { + for _, w := range warnings { + de := types.DeploymentError{ + Ref: ref, + Error: w.Error(), + } + c.warnings[de] = true + } +} + +func (c *DeploymentCollection) addError(ref types.ObjectRef, err error) { + de := types.DeploymentError{ + Ref: ref, + Error: err.Error(), + } + c.errors[de] = true +} + +func (c *DeploymentCollection) errorsList() []types.DeploymentError { + var l []types.DeploymentError + for de := range c.errors { + l = append(l, de) + } + return l +} + +func (c *DeploymentCollection) warningsList() []types.DeploymentError { + var l []types.DeploymentError + for de := range c.warnings { + l = append(l, de) + } + return l +} + +func (c *DeploymentCollection) clearErrorsAndWarnings() { + c.warnings = map[types.DeploymentError]bool{} + c.errors = map[types.DeploymentError]bool{} +} + +func (c *DeploymentCollection) getRemoteObject(ref types.ObjectRef) *unstructured.Unstructured { + o, _ := c.remoteObjects[ref] + return o +} diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go new file mode 100644 index 000000000..3ad91d2b3 --- /dev/null +++ b/pkg/deployment/deployment_item.go @@ -0,0 +1,370 @@ +package deployment + +import ( + "fmt" + "github.com/codablock/kluctl/pkg/k8s" + "github.com/codablock/kluctl/pkg/types" + "github.com/codablock/kluctl/pkg/utils" + "io/fs" + "io/ioutil" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "os" + "path" + "path/filepath" + "sigs.k8s.io/kustomize/api/filesys" + "sigs.k8s.io/kustomize/api/krusty" + "strings" +) + +const sealmeExt = ".sealme" + +type deploymentItem struct { + project *DeploymentProject + collection *DeploymentCollection + config *types.DeploymentItemConfig + dir *string + index int + + objects []*unstructured.Unstructured + + relProjectDir string + relToRootItemDir string + relToProjectItemDir string + relRenderedDir string + renderedDir string + renderedYamlPath string +} + +func NewDeploymentItem(project *DeploymentProject, collection *DeploymentCollection, config *types.DeploymentItemConfig, dir *string, index int) (*deploymentItem, error) { + di := &deploymentItem{ + project: project, + collection: collection, + config: config, + dir: dir, + index: index, + } + + if dir != nil && !utils.IsDirectory(*dir) { + return nil, fmt.Errorf("kustomizeDir does not exist: %s", *dir) + } + + var err error + + rootProject := di.project.getRootProject() + + di.relProjectDir, err = filepath.Rel(rootProject.dir, di.project.dir) + if err != nil { + return nil, err + } + + if di.dir != nil { + di.relToRootItemDir, err = filepath.Rel(rootProject.dir, *di.dir) + if err != nil { + return nil, err + } + + di.relToProjectItemDir, err = filepath.Rel(di.relProjectDir, di.relToRootItemDir) + if err != nil { + return nil, err + } + + di.relRenderedDir = di.relToRootItemDir + if di.index != 0 { + di.relRenderedDir = fmt.Sprintf("%s-%d", di.relRenderedDir, di.index) + } + + di.renderedDir = path.Join(di.collection.RenderDir, di.relRenderedDir) + di.renderedYamlPath = path.Join(di.renderedDir, ".rendered.yml") + } + return di, nil +} + +func (di *deploymentItem) getCommonLabels() map[string]string { + l := di.project.getCommonLabels() + i := 0 + for _, t := range di.getTags().ListKeys() { + l[fmt.Sprintf("kluctl.io/tag-%d", i)] = t + i += 1 + } + return l +} + +func (di *deploymentItem) getCommonAnnotations() map[string]string { + // TODO change it to kluctl.io/deployment_dir + a := map[string]string{ + "kluctl.io/kustomize_dir": strings.ReplaceAll(di.relToRootItemDir, string(os.PathSeparator), "/"), + } + if di.config.SkipDeleteIfTags != nil && *di.config.SkipDeleteIfTags { + a["kluctl.io/skip-delete-if-tags"] = "true" + } + return a +} + +func (di *deploymentItem) render(k *k8s.K8sCluster, wp *utils.WorkerPoolWithErrors) error { + if di.dir == nil { + return nil + } + + rootDir := di.project.getRootProject().dir + + err := os.MkdirAll(di.renderedDir, 0o777) + if err != nil { + return err + } + + varsCtx := di.project.varsCtx.Copy() + err = varsCtx.loadVarsList(k, di.project.getRenderSearchDirs(), di.config.Vars) + if err != nil { + return err + } + + var excludePatterns []string + excludePatterns = append(excludePatterns, di.project.config.TemplateExcludes...) + if utils.IsFile(path.Join(*di.dir, "helm-chart.yml")) { + // never try to render helm charts + excludePatterns = append(excludePatterns, path.Join(di.relToProjectItemDir, "charts/**")) + } + if !di.collection.forSeal { + // .sealme files are rendered while sealing and not while deploying + excludePatterns = append(excludePatterns, "**.sealme") + } + + wp.Submit(func() error { + return varsCtx.renderDirectory(rootDir, di.project.getRenderSearchDirs(), di.relProjectDir, excludePatterns, di.relToProjectItemDir, di.renderedDir) + }) + + return nil +} + +func (di *deploymentItem) renderHelmCharts(k *k8s.K8sCluster, wp *utils.WorkerPoolWithErrors) error { + if di.dir == nil { + return nil + } + + err := filepath.Walk(di.renderedDir, func(p string, info fs.FileInfo, err error) error { + if !strings.HasSuffix(p, "helm-chart.yml") { + return nil + } + + wp.Submit(func() error { + chart, err := NewHelmChart(p) + if err != nil { + return err + } + return chart.Render(k) + }) + return nil + }) + if err != nil { + return err + } + return nil +} + +func (di *deploymentItem) resolveSealedSecrets() error { + if di.dir == nil { + return nil + } + + // TODO check for bootstrap + + sealedSecretsDir := di.project.getSealedSecretsDir() + baseSourcePath := di.project.sealedSecretsDir + + y := make(map[string]interface{}) + err := utils.ReadYamlFile(path.Join(di.renderedDir, "kustomization.yml"), y) + if err != nil { + return err + } + l, _, err := unstructured.NestedStringSlice(y, "resources") + if err != nil { + return err + } + for _, resource := range l { + p := path.Join(di.renderedDir, resource) + if utils.Exists(p) || !utils.Exists(p+sealmeExt) { + continue + } + relDir, err := filepath.Rel(di.renderedDir, path.Dir(p)) + if err != nil { + return err + } + fname := path.Base(p) + + baseError := fmt.Sprintf("failed to resolve SealedSecret %s", path.Clean(path.Join(di.project.dir, resource))) + if sealedSecretsDir == nil { + return fmt.Errorf("%s. Sealed secrets dir could not be determined", baseError) + } + sourcePath := path.Clean(path.Join(baseSourcePath, di.relRenderedDir, relDir, *sealedSecretsDir, fname)) + targetPath := path.Join(di.renderedDir, relDir, fname) + if !utils.IsFile(sourcePath) { + return fmt.Errorf("%s. %s not found. You might need to seal it first", baseError, sourcePath) + } + b, err := ioutil.ReadFile(sourcePath) + if err != nil { + return fmt.Errorf("failed to read source secret file %s: %w", sourcePath, err) + } + err = ioutil.WriteFile(targetPath, b, 0o666) + if err != nil { + return fmt.Errorf("failed to write target secret file %s: %w", targetPath, err) + } + } + return nil +} + +func (di *deploymentItem) getTags() *utils.OrderedMap { + tags := di.project.getTags() + for _, t := range di.config.Tags { + tags.Set(t, true) + } + return tags +} + +func (di *deploymentItem) buildInclusionEntries() []utils.InclusionEntry { + var values []utils.InclusionEntry + for _, t := range di.getTags().ListKeys() { + values = append(values, utils.InclusionEntry{Type: "tag", Value: t}) + } + if di.dir != nil { + dir := strings.ReplaceAll(di.relToProjectItemDir, string(os.PathSeparator), "/") + values = append(values, utils.InclusionEntry{Type: "kustomize_dir", Value: dir}) + } + return values +} + +func (di *deploymentItem) checkInclusionForDeploy() bool { + if di.collection.inclusion == nil { + return true + } + if di.config.OnlyRender != nil && *di.config.OnlyRender { + return true + } + if di.config.AlwaysDeploy != nil && *di.config.AlwaysDeploy { + return true + } + values := di.buildInclusionEntries() + return di.collection.inclusion.CheckIncluded(values, false) +} + +func (di *deploymentItem) checkInclusionForDelete() bool { + if di.collection.inclusion == nil { + return true + } + skipDeleteIfTags := di.config.SkipDeleteIfTags != nil && *di.config.SkipDeleteIfTags + values := di.buildInclusionEntries() + return di.collection.inclusion.CheckIncluded(values, skipDeleteIfTags) +} + +func (di *deploymentItem) prepareKusomizationYaml() error { + if di.dir == nil { + return nil + } + + kustomizeYamlPath := path.Join(di.renderedDir, "kustomization.yml") + if !utils.IsFile(kustomizeYamlPath) { + return nil + } + + kustomizeYaml := make(map[string]interface{}) + err := utils.ReadYamlFile(kustomizeYamlPath, kustomizeYaml) + if err != nil { + return err + } + + overrideNamespace := di.project.getOverrideNamespace() + if overrideNamespace != nil { + if _, ok := kustomizeYaml["namespace"]; !ok { + kustomizeYaml["namespace"] = *overrideNamespace + } + } + + // Save modified kustomize.yml + err = utils.WriteYamlFile(kustomizeYamlPath, kustomizeYaml) + if err != nil { + return err + } + return nil +} + +func (di *deploymentItem) buildKustomize() error { + if di.dir == nil { + return nil + } + + err := di.prepareKusomizationYaml() + if err != nil { + return err + } + + ko := krusty.MakeDefaultOptions() + k := krusty.MakeKustomizer(ko) + + fsys := filesys.MakeFsOnDisk() + rm, err := k.Run(fsys, di.renderedDir) + if err != nil { + return err + } + + var yamls []interface{} + for _, r := range rm.Resources() { + y, err := r.Map() + if err != nil { + return err + } + yamls = append(yamls, y) + } + + err = utils.WriteYamlAllFile(di.renderedYamlPath, yamls) + if err != nil { + return err + } + + return nil +} + +func (di *deploymentItem) postprocessAndLoadObjects(k *k8s.K8sCluster) error { + if di.dir == nil { + return nil + } + + objects, err := utils.ReadYamlAllFile(di.renderedYamlPath) + if err != nil { + return err + } + + di.objects = []*unstructured.Unstructured{} + for _, o := range objects { + m, ok := o.(map[string]interface{}) + if !ok { + return fmt.Errorf("object is not a map") + } + di.objects = append(di.objects, &unstructured.Unstructured{ + Object: m, + }) + } + + for _, o := range di.objects { + if k != nil { + k.RemoveNamespaceIfNeeded(o) + } + + // Set common labels/annotations + o.SetLabels(utils.CopyMergeStrMap(o.GetLabels(), di.getCommonLabels())) + commonAnnotations := di.getCommonAnnotations() + o.SetAnnotations(utils.CopyMergeStrMap(o.GetAnnotations(), commonAnnotations)) + + // Resolve image placeholders + err = di.collection.images.ResolvePlaceholders(k, o, di.relRenderedDir, di.getTags().ListKeys()) + if err != nil { + return err + } + } + + // Need to write it back to disk in case it is needed externally + err = utils.WriteYamlAllFile(di.renderedYamlPath, objects) + if err != nil { + return err + } + + return nil +} diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go new file mode 100644 index 000000000..21c8b883f --- /dev/null +++ b/pkg/deployment/deployment_project.go @@ -0,0 +1,258 @@ +package deployment + +import ( + "fmt" + "github.com/codablock/kluctl/pkg/k8s" + "github.com/codablock/kluctl/pkg/types" + "github.com/codablock/kluctl/pkg/utils" + log "github.com/sirupsen/logrus" + "path" + "path/filepath" + "strings" + "sync" +) + +var kustomizeDirsDeprecatedOnce sync.Once + +type DeploymentProject struct { + varsCtx *VarsCtx + dir string + sealedSecretsDir string + + config types.DeploymentProjectConfig + + includes map[int]*DeploymentProject + + parentProject *DeploymentProject + parentProjectInclude *types.IncludeItemConfig +} + +func NewDeploymentProject(k *k8s.K8sCluster, varsCtx *VarsCtx, dir string, sealedSecretsDir string, parentProject *DeploymentProject) (*DeploymentProject, error) { + dp := &DeploymentProject{ + varsCtx: varsCtx.Copy(), + dir: dir, + sealedSecretsDir: sealedSecretsDir, + parentProject: parentProject, + includes: map[int]*DeploymentProject{}, + } + + if !utils.IsDirectory(dir) { + return nil, fmt.Errorf("%s does not exist or is not a directory", dir) + } + + err := dp.loadBaseConfig(k) + if err != nil { + return nil, fmt.Errorf("failed to load deployment config for %s: %w", dir, err) + } + + err = dp.loadIncludes(k) + if err != nil { + return nil, fmt.Errorf("failed to load includes for %s: %w", dir, err) + } + + return dp, nil +} + +func (p *DeploymentProject) loadBaseConfig(k *k8s.K8sCluster) error { + configPath := path.Join(p.dir, "deployment.yml") + if !utils.Exists(configPath) { + if utils.Exists(path.Join(p.dir, "kustomization.yml")) { + return fmt.Errorf("deployment.yml not found but folder %s contains a kustomization.yml", p.dir) + } + return fmt.Errorf("%s not found", p.dir) + } + + err := p.varsCtx.renderYamlFile("deployment.yml", p.getRenderSearchDirs(), &p.config) + if err != nil { + return fmt.Errorf("failed to load deployment.yml: %w", err) + } + + err = p.varsCtx.loadVarsList(k, p.getRenderSearchDirs(), p.config.Vars) + if err != nil { + return fmt.Errorf("failed to load deployment.yml vars: %w", err) + } + + // TODO remove obsolete code + if len(p.config.DeploymentItems) != 0 && len(p.config.KustomizeDirs) != 0 { + return fmt.Errorf("using deploymentItems and kustomizeDirs at the same time is not allowed") + } + if len(p.config.KustomizeDirs) != 0 { + kustomizeDirsDeprecatedOnce.Do(func() { + log.Warningf("kustomizeDirs is deprecated, use deploymentItems instead") + }) + p.config.DeploymentItems = p.config.KustomizeDirs + p.config.KustomizeDirs = nil + } + + // If there are no explicit tags set, interpret the path as a tag, which allows to + // enable/disable single deployments via included/excluded tags + setDefaultTag := func(item *types.BaseItemConfig) { + if len(item.Tags) != 0 || item.Path == nil { + return + } + item.Tags = []string{path.Base(*item.Path)} + } + for _, di := range p.config.DeploymentItems { + setDefaultTag(&di.BaseItemConfig) + } + for _, di := range p.config.Includes { + setDefaultTag(&di.BaseItemConfig) + } + + if len(p.getCommonLabels()) == 0 { + return fmt.Errorf("no commonLabels/deleteByLabels in root deployment. This is not allowed") + } + if len(p.config.Args) != 0 && p.parentProject != nil { + return fmt.Errorf("only the root deployment.yml can define args") + } + return nil +} + +func (p *DeploymentProject) loadIncludes(k *k8s.K8sCluster) error { + for i, inc := range p.config.Includes { + if inc.Path == nil { + continue + } + rootProject := p.getRootProject() + + incDir := path.Join(p.dir, *inc.Path) + incDir, err := filepath.Abs(incDir) + if err != nil { + return err + } + + if !strings.HasPrefix(incDir, rootProject.dir) { + return fmt.Errorf("include path is not part of root deployment project: %s", *inc.Path) + } + + varsCtx := p.varsCtx.Copy() + err = varsCtx.loadVarsList(k, p.getRenderSearchDirs(), inc.Vars) + if err != nil { + return err + } + + newProject, err := NewDeploymentProject(k, varsCtx, incDir, p.sealedSecretsDir, p) + if err != nil { + return err + } + newProject.parentProjectInclude = inc + + p.includes[i] = newProject + } + return nil +} + +func (p *DeploymentProject) getSealedSecretsDir() *string { + root := p.getRootProject() + if root.config.SealedSecrets == nil { + return nil + } + return root.config.SealedSecrets.OutputPattern +} + +func (p *DeploymentProject) getRootProject() *DeploymentProject { + if p.parentProject == nil { + return p + } + return p.parentProject.getRootProject() +} + +type deploymentProjectAndIncludeItem struct { + p *DeploymentProject + inc *types.IncludeItemConfig +} + +func (p *DeploymentProject) getParents() []deploymentProjectAndIncludeItem { + var parents []deploymentProjectAndIncludeItem + var inc *types.IncludeItemConfig + d := p + for d != nil { + parents = append(parents, deploymentProjectAndIncludeItem{p: d, inc: inc}) + inc = d.parentProjectInclude + d = d.parentProject + } + return parents +} + +func (p *DeploymentProject) getChildren(recursive bool, includeSelf bool) []*DeploymentProject { + var children []*DeploymentProject + if includeSelf { + children = append(children, p) + } + for _, d := range p.includes { + children = append(children, d) + if recursive { + children = append(children, d.getChildren(true, false)...) + } + } + return children +} + +func (p *DeploymentProject) getRenderSearchDirs() []string { + var ret []string + for _, d := range p.getParents() { + ret = append(ret, d.p.dir) + } + return ret +} + +func (p *DeploymentProject) getCommonLabels() map[string]string { + ret := make(map[string]string) + parents := p.getParents() + for i, _ := range parents { + d := parents[len(parents)-i-1] + utils.MergeStrMap(ret, d.p.config.CommonLabels) + } + return ret +} + +func (p *DeploymentProject) getDeleteByLabels() map[string]string { + ret := make(map[string]string) + parents := p.getParents() + for i, _ := range parents { + d := parents[len(parents)-i-1] + utils.MergeStrMap(ret, d.p.config.DeleteByLabels) + } + return ret +} + +func (p *DeploymentProject) getOverrideNamespace() *string { + for _, e := range p.getParents() { + if e.p.config.OverrideNamespace != nil { + return e.p.config.OverrideNamespace + } + } + return nil +} + +func (p *DeploymentProject) getTags() *utils.OrderedMap { + var tags utils.OrderedMap + for _, e := range p.getParents() { + if e.inc != nil { + for _, t := range e.inc.Tags { + tags.Set(t, true) + } + } + for _, t := range e.p.config.Tags { + tags.Set(t, true) + } + } + return &tags +} + +func (p *DeploymentProject) getIgnoreForDiffs(ignoreTags, ignoreLabels, ignoreAnnotations bool) []*types.IgnoreForDiffItemConfig { + var ret []*types.IgnoreForDiffItemConfig + for _, e := range p.getParents() { + ret = append(ret, e.p.config.IgnoreForDiff...) + } + if ignoreTags { + ret = append(ret, &types.IgnoreForDiffItemConfig{FieldPath: []string{`metadata.labels."kluctl.io/tag-*"`}}) + } + if ignoreLabels { + ret = append(ret, &types.IgnoreForDiffItemConfig{FieldPath: []string{`metadata.labels.*`}}) + } + if ignoreAnnotations { + ret = append(ret, &types.IgnoreForDiffItemConfig{FieldPath: []string{`metadata.annotations.*`}}) + } + return ret +} diff --git a/pkg/deployment/external_args.go b/pkg/deployment/external_args.go new file mode 100644 index 000000000..d41790262 --- /dev/null +++ b/pkg/deployment/external_args.go @@ -0,0 +1,86 @@ +package deployment + +import ( + "fmt" + "github.com/codablock/kluctl/pkg/types" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "path" + "regexp" + "strings" +) + +var argPattern = regexp.MustCompile("^[a-zA-Z0-9_./-]*=.*$") + +func ParseArgs(argsList []string) (map[string]string, error) { + args := make(map[string]string) + for _, arg := range argsList { + if !argPattern.MatchString(arg) { + return nil, fmt.Errorf("invalid --arg argument. Must be --arg=some_var_name=value") + } + + s := strings.SplitN(arg, "=", 1) + name := s[0] + value := s[1] + args[name] = value + } + return args, nil +} + +func ConvertArgsToVars(args map[string]string) map[string]interface{} { + m := make(map[string]interface{}) + for n, v := range args { + p := strings.Split(n, ".") + _ = unstructured.SetNestedField(m, v, p...) + } + return m +} + +func CheckRequiredDeployArgs(dir string, varsCtx *VarsCtx, deployArgs map[string]interface{}) error { + // First try to load the config without templating to avoid getting errors while rendering because required + // args were not set. Otherwise we won't be able to iterator through the 'args' array in the deployment.yml + // when the rendering error is actually args related. + + var conf types.DeploymentProjectConfig + + err := types.LoadDeploymentProjectConfig(path.Join(dir, "deployment.yml"), &conf) + if err != nil { + // If that failed, it might be that conditional jinja blocks are present in the config, so lets try loading + // the config in rendered form. If it fails due to missing args now, we can't help much with better error + // messages anymore. + varsCtx2 := varsCtx.Copy() + varsCtx2.UpdateChild("args", deployArgs) + err = varsCtx2.renderYamlFile("deployment.yml", []string{dir}, &conf) + if err != nil { + return err + } + } + + if len(conf.Args) == 0 { + return nil + } + + err = checkRequiredArgs(conf.Args, deployArgs) + if err != nil { + return err + } + return nil +} + +func checkRequiredArgs(argsDef []*types.DeploymentArg, args map[string]interface{}) error { + for _, a := range argsDef { + p := strings.Split(a.Name, ".") + _, found, _ := unstructured.NestedFieldNoCopy(args, p...) + if !found { + if a.Default == nil { + return fmt.Errorf("required argument %s not set", a.Name) + } else { + err := unstructured.SetNestedField(args, a.Default, p...) + if err != nil { + return err + } + } + } + } + + return nil +} diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go new file mode 100644 index 000000000..b4fb95794 --- /dev/null +++ b/pkg/deployment/helm_chart.go @@ -0,0 +1,291 @@ +package deployment + +import ( + "fmt" + "github.com/codablock/kluctl/pkg/k8s" + "github.com/codablock/kluctl/pkg/types" + "github.com/codablock/kluctl/pkg/utils" + goversion "github.com/hashicorp/go-version" + "gopkg.in/yaml.v2" + "io/ioutil" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "os" + "os/exec" + "path" + "regexp" + "sort" + "strings" +) + +type helmChart struct { + configFile string + config *types.HelmChartConfig +} + +func NewHelmChart(configFile string) (*helmChart, error) { + config, err := types.LoadHelmChartConfig(configFile) + if err != nil { + return nil, err + } + + hc := &helmChart{ + configFile: configFile, + config: config, + } + return hc, nil +} + +func (c *helmChart) withRepoContext(cb func(repoName string) error) error { + needRepo := false + repoName := "stable" + + if c.config.Repo != nil && *c.config.Repo != "stable" { + needRepo = true + repoName = fmt.Sprintf("kluctl-%s", utils.Sha256String(*c.config.Repo))[:16] + } + + if needRepo { + _, err := c.doHelm([]string{"repo", "remove", repoName}, true) + if err != nil { + return err + } + _, err = c.doHelm([]string{"repo", "add", repoName, *c.config.Repo}, false) + if err != nil { + return err + } + defer func() { + _, _ = c.doHelm([]string{"repo", "remove", repoName}, true) + }() + } else { + _, err := c.doHelm([]string{"repo", "update"}, false) + if err != nil { + return err + } + } + return cb(repoName) +} + +func (c *helmChart) GetChartName() (string, error) { + if c.config.Repo != nil && strings.HasPrefix(*c.config.Repo, "oci://") { + s := strings.Split(*c.config.Repo, "/") + chartName := s[len(s)-1] + if m, _ := regexp.MatchString(`[a-zA-Z_-]+`, chartName); !m { + return "", fmt.Errorf("invalid oci chart url: %s", *c.config.Repo) + } + return chartName, nil + } + if c.config.ChartName == nil { + return "", fmt.Errorf("chartName is missing in helm-chart.yml") + } + return *c.config.ChartName, nil +} + +func (c *helmChart) Pull() error { + chartName, err := c.GetChartName() + if err != nil { + return err + } + + dir := path.Dir(c.configFile) + targetDir := path.Join(dir, "charts") + rmDir := path.Join(targetDir, chartName) + _ = os.RemoveAll(rmDir) + + var args []string + if c.config.Repo != nil && strings.HasPrefix(*c.config.Repo, "oci://") { + args = []string{"pull", *c.config.Repo, "--destination", targetDir, "--untar"} + if c.config.ChartVersion != nil { + args = append(args, "--version") + args = append(args, *c.config.ChartVersion) + } + _, err = c.doHelm(args, false) + return err + } else { + return c.withRepoContext(func(repoName string) error { + args = []string{"pull", fmt.Sprintf("%s/%s", repoName, chartName), "--destination", targetDir, "--untar"} + if c.config.ChartVersion != nil { + args = append(args, "--version", *c.config.ChartVersion) + } + _, err = c.doHelm(args, false) + return err + }) + } +} + +type VersionSlice []string + +func (x VersionSlice) Less(i, j int) bool { + v1, err1 := goversion.NewVersion(x[i]) + v2, err2 := goversion.NewVersion(x[j]) + if err1 != nil { + v1, _ = goversion.NewVersion("0") + } + if err2 != nil { + v2, _ = goversion.NewVersion("0") + } + return v1.LessThan(v2) +} +func (x VersionSlice) Len() int { return len(x) } +func (x VersionSlice) Swap(i, j int) { x[i], x[j] = x[j], x[i] } + +func (c *helmChart) CheckUpdate() (string, error) { + if c.config.Repo != nil && strings.HasPrefix(*c.config.Repo, "oci://") { + return "", nil + } + chartName, err := c.GetChartName() + if err != nil { + return "", err + } + var latestVersion string + err = c.withRepoContext(func(repoName string) error { + chartName := fmt.Sprintf("%s/%s", repoName, chartName) + args := []string{"search", "repo", chartName, "-oyaml", "-l"} + stdout, err := c.doHelm(args, false) + if err != nil { + return err + } + // ensure we didn't get partial matches + var lm []map[string]string + var ls VersionSlice + err = yaml.Unmarshal([]byte(stdout), &lm) + if err != nil { + return err + } + for _, x := range lm { + if n, ok := x["name"]; ok && n == chartName { + if v, ok := x["version"]; ok { + ls = append(ls, v) + } + } + } + if len(ls) == 0 { + return fmt.Errorf("helm chart %s not found in repository", chartName) + } + sort.Sort(ls) + latestVersion = ls[len(ls)-1] + if c.config.ChartVersion != nil && latestVersion == *c.config.ChartVersion { + return nil + } + return nil + }) + if err != nil { + return "", err + } + return latestVersion, nil +} + +func (c *helmChart) Render(k *k8s.K8sCluster) error { + chartName, err := c.GetChartName() + if err != nil { + return err + } + dir := path.Dir(c.configFile) + chartDir := path.Join(dir, "charts", chartName) + valuesPath := path.Join(dir, "helm-values.yml") + outputPath := path.Join(dir, c.config.Output) + + args := []string{"template", c.config.ReleaseName, chartDir} + + namespace := "default" + if c.config.Namespace != nil { + namespace = *c.config.Namespace + } + + if utils.Exists(valuesPath) { + args = append(args, "-f", valuesPath) + } + + if c.config.SkipCRDs != nil && *c.config.SkipCRDs { + args = append(args, "--skip-crds") + } else { + args = append(args, "--include-crds") + } + args = append(args, "--skip-tests") + + gvs, err := k.GetAllGroupVersions() + if err != nil { + return err + } + for _, gv := range gvs { + args = append(args, fmt.Sprintf("--api-versions=%s", gv)) + } + args = append(args, fmt.Sprintf("--kube-version=%s", k.ServerVersion.String())) + + rendered, err := c.doHelm(args, false) + if err != nil { + return err + } + + parsed, err := utils.ReadYamlAllBytes(rendered) + + for _, o := range parsed { + m, ok := o.(map[string]interface{}) + if !ok { + return fmt.Errorf("object is not a map") + } + + // "helm install" will deploy resources to the given namespace automatically, but "helm template" does not + // add the necessary namespace in the rendered resources + _, found, _ := unstructured.NestedString(m, "metadata", "namespace") + if !found { + _ = unstructured.SetNestedField(m, namespace, "metadata", "namespace") + } + } + rendered, err = utils.WriteYamlAllBytes(parsed) + if err != nil { + return err + } + + err = ioutil.WriteFile(outputPath, rendered, 0o666) + if err != nil { + return err + } + return nil +} + +func (c *helmChart) doHelm(args []string, ignoreStdErr bool) ([]byte, error) { + cmd := exec.Command("helm", args...) + cmd.Env = append(os.Environ(), "HELM_EXPERIMENTAL_OCI=true") + + if ignoreStdErr { + stderrPipe, err := cmd.StderrPipe() + if err != nil { + return nil, err + } + go func() { + buf := make([]byte, 1024) + for true { + n, err := stderrPipe.Read(buf) + if err != nil || n == 0 { + return + } + } + }() + } else { + cmd.Stderr = os.Stderr + } + + stdoutPipe, err := cmd.StdoutPipe() + if err != nil { + return nil, err + } + + err = cmd.Start() + if err != nil { + return nil, err + } + defer cmd.Process.Kill() + + stdout, err := ioutil.ReadAll(stdoutPipe) + if err != nil { + return nil, err + } + ps, err := cmd.Process.Wait() + if err != nil { + return nil, err + } + if ps.ExitCode() != 0 { + return nil, fmt.Errorf("helm returned non-zero exit code %d", ps.ExitCode()) + } + return stdout, nil +} diff --git a/pkg/deployment/hooks_util.go b/pkg/deployment/hooks_util.go new file mode 100644 index 000000000..25b1ff675 --- /dev/null +++ b/pkg/deployment/hooks_util.go @@ -0,0 +1,251 @@ +package deployment + +import ( + "fmt" + "github.com/codablock/kluctl/pkg/types" + "github.com/codablock/kluctl/pkg/utils" + log "github.com/sirupsen/logrus" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sort" + "strconv" + "strings" +) + +var supportedKluctlHooks = []string{ + "pre-deploy", "post-deploy", + "pre-deploy-initial", "post-deploy-initial", + "pre-deploy-upgrade", "post-deploy-upgrade", +} + +var supportedKluctlDeletePolicies = []string{ + "before-hook-creation", + "hook-succeeded", + "hook-failed", +} + +// delete/rollback hooks are actually not supported, but we won't show warnings about that to not spam the user +var supportedHelmHooks = []string{ + "pre-install", "post-install", + "pre-upgrade", "post-upgrade", + "pre-delete", "post-delete", + "pre-rollback", "post-rollback", +} + +type hooksUtil struct { + a *applyUtil +} + +type hook struct { + object *unstructured.Unstructured + hooks map[string]bool + weight int + deletePolicies map[string]bool + wait bool +} + +func (u *hooksUtil) runHooks(d *deploymentItem, hooks []string) { + doLog := func(level log.Level, s string, f ...interface{}) { + u.a.doLog(d, level, s, f...) + } + + var l []*hook + for _, h := range u.getSortedHooksList(d.objects) { + for h2 := range h.hooks { + if utils.FindStrInSlice(hooks, h2) != -1 { + l = append(l, h) + break + } + } + } + + if len(l) != 0 && log.IsLevelEnabled(log.DebugLevel) { + doLog(log.DebugLevel, "Sorted hooks:") + for _, h := range l { + doLog(log.DebugLevel, " %s", types.RefFromObject(h.object).String()) + } + } + + var deleteBeforeObjects []*hook + var applyObjects []*hook + + for _, h := range l { + if u.a.abortSignal { + return + } + if _, ok := h.deletePolicies["before-hook-creation"]; ok { + deleteBeforeObjects = append(deleteBeforeObjects, h) + } + applyObjects = append(applyObjects, h) + } + + doDeleteForPolicy := func(h *hook) bool { + ref := types.RefFromObject(h.object) + var dpStr []string + for p := range h.deletePolicies { + dpStr = append(dpStr, p) + } + doLog(log.DebugLevel, "Deleting hook %s due to hook-delete-policy %s", ref.String(), strings.Join(dpStr, ",")) + return u.a.deleteObject(ref) + } + + if len(deleteBeforeObjects) != 0 { + doLog(log.InfoLevel, "Deleting %d hooks before hook execution", len(deleteBeforeObjects)) + } + for _, h := range deleteBeforeObjects { + if !doDeleteForPolicy(h) { + return + } + } + + waitResults := make(map[types.ObjectRef]bool) + if len(applyObjects) != 0 { + doLog(log.InfoLevel, "Applying %d hooks", len(applyObjects)) + } + for _, h := range applyObjects { + ref := types.RefFromObject(h.object) + _, replaced := h.deletePolicies["before-hook-creation"] + doLog(log.DebugLevel, "Applying hook %s", ref.String()) + u.a.applyObject(h.object, replaced, true) + + if u.a.hadError(ref) { + continue + } + if !h.wait { + continue + } + waitResults[ref] = u.a.waitHook(ref) + } + + var deleteAfterObjects []*hook + for i := len(applyObjects) - 1; i >= 0; i-- { + h := applyObjects[i] + ref := types.RefFromObject(h.object) + waitResult, ok := waitResults[ref] + if !ok { + continue + } + doDelete := false + if _, ok := h.deletePolicies["hook-succeeded"]; waitResult && ok { + doDelete = true + } else if _, ok := h.deletePolicies["hook-failed"]; !waitResult && ok { + doDelete = true + } + if doDelete { + deleteAfterObjects = append(deleteAfterObjects, h) + } + } + + if len(deleteAfterObjects) != 0 { + doLog(log.InfoLevel, "Deleting %d hooks after hook execution", len(deleteAfterObjects)) + } + for _, h := range deleteAfterObjects { + doDeleteForPolicy(h) + } +} + +func (u *hooksUtil) getHook(o *unstructured.Unstructured) *hook { + ref := types.RefFromObject(o) + getSet := func(name string) map[string]bool { + ret := make(map[string]bool) + a, ok := o.GetAnnotations()[name] + if !ok { + return ret + } + for _, x := range strings.Split(a, ",") { + if x != "" { + ret[x] = true + } + } + return ret + } + + hooks := getSet("kluctl.io/hook") + for h := range hooks { + if utils.FindStrInSlice(supportedKluctlHooks, h) == -1 { + u.a.handleError(ref, fmt.Errorf("unsupported kluctl.io/hook '%s'", h)) + } + } + + helmHooks := getSet("helm.sh/hook") + for h := range helmHooks { + if utils.FindStrInSlice(supportedHelmHooks, h) == -1 { + u.a.handleError(ref, fmt.Errorf("unsupported helm.sh/hook '%s'", h)) + } + } + + helmCompatibility := func(helmName string, kluctlName string) { + if _, ok := helmHooks[helmName]; ok { + hooks[kluctlName] = true + } + } + + helmCompatibility("pre-install", "pre-deploy-initial") + helmCompatibility("pre-upgrade", "pre-deploy-upgrade") + helmCompatibility("post-install", "post-deploy-initial") + helmCompatibility("post-upgrade", "post-deploy-upgrade") + helmCompatibility("pre-delete", "pre-delete") + helmCompatibility("post-delete", "post-delete") + + weightStr, ok := o.GetAnnotations()["kluctl.io/hook-weight"] + if !ok { + weightStr, ok = o.GetAnnotations()["helm.sh/hook-weight"] + } + if !ok { + weightStr = "0" + } + weight, err := strconv.ParseInt(weightStr, 10, 32) + if err != nil { + u.a.handleError(ref, fmt.Errorf("failed to parse hook weight: %w", err)) + } + + deletePolicy := getSet("kluctl.io/hook-delete-policy") + for d := range getSet("helm.sh/hook-delete-policy") { + deletePolicy[d] = true + } + if len(deletePolicy) == 0 { + deletePolicy["before-hook-creation"] = true + } + + for p := range deletePolicy { + if utils.FindStrInSlice(supportedKluctlDeletePolicies, p) == -1 { + u.a.handleError(ref, fmt.Errorf("unsupported kluctl.io/hook-delete-policy '%s'", p)) + } + } + + waitStr, ok := o.GetAnnotations()["kluctl.io/hook-wait"] + if !ok { + waitStr = "true" + } + wait, err := strconv.ParseBool(waitStr) + if err != nil { + u.a.handleError(ref, fmt.Errorf("failed to parse %s as bool", waitStr)) + wait = true + } + + if len(hooks) == 0 { + return nil + } + + return &hook{ + object: o, + hooks: hooks, + weight: int(weight), + deletePolicies: deletePolicy, + wait: wait, + } +} + +func (u *hooksUtil) getSortedHooksList(objects []*unstructured.Unstructured) []*hook { + var ret []*hook + for _, o := range objects { + h := u.getHook(o) + if h == nil { + continue + } + ret = append(ret, h) + } + sort.SliceStable(ret, func(i, j int) bool { + return ret[i].weight < ret[j].weight + }) + return ret +} diff --git a/pkg/deployment/images.go b/pkg/deployment/images.go new file mode 100644 index 000000000..d564dc28d --- /dev/null +++ b/pkg/deployment/images.go @@ -0,0 +1,207 @@ +package deployment + +import ( + "bytes" + "encoding/base64" + "fmt" + "github.com/codablock/kluctl/pkg/k8s" + "github.com/codablock/kluctl/pkg/types" + "github.com/codablock/kluctl/pkg/utils" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "strings" + "sync" +) + +type Images struct { + updateImages bool + fixedImages []types.FixedImage + seenImages []types.FixedImage + mutex sync.Mutex +} + +func NewImages(updateImages bool) (*Images, error) { + return &Images{ + updateImages: updateImages, + }, nil +} + +func (images *Images) AddFixedImage(fi types.FixedImage) { + images.fixedImages = append(images.fixedImages, fi) +} + +func (images *Images) GetFixedImage(image string, namespace string, deployment string, container string) *string { + for i := len(images.fixedImages) - 1; i >= 0; i-- { + fi := &images.fixedImages[i] + if fi.Image != image { + continue + } + if fi.Namespace != nil && namespace != *fi.Namespace { + continue + } + if fi.Deployment != nil && deployment != *fi.Deployment { + continue + } + if fi.Container != nil && container != *fi.Container { + continue + } + return &fi.ResultImage + } + return nil +} + +func (images *Images) GetLatestImageFromRegistry(image string, latestVersion string) (*string, error) { + return nil, nil +} + +const beginPlaceholder = "XXXXXbegin_get_image_" +const endPlaceholder = "_end_get_imageXXXXX" + +type placeHolder struct { + Image string `yaml:"image"` + LatestVersion string `yaml:"latestVersion"` + + startOffset int + endOffset int +} + +func (images *Images) parsePlaceholder(s string, offset int) (*placeHolder, error) { + start := strings.Index(s[offset:], beginPlaceholder) + if start == -1 { + return nil, nil + } + end := strings.Index(s[start:], endPlaceholder) + if end == -1 { + return nil, fmt.Errorf("beginPlaceholder marker without endPlaceholder marker") + } + b64 := s[start+len(beginPlaceholder) : end] + b, err := base64.StdEncoding.DecodeString(b64) + if err != nil { + return nil, err + } + var ph placeHolder + err = utils.ReadYamlStream(bytes.NewReader(b), &ph) + if err != nil { + return nil, err + } + ph.startOffset = start + ph.endOffset = end + len(endPlaceholder) + + return &ph, nil +} + +func (images *Images) extractContainerName(parent interface{}) string { + parentM, ok := parent.(map[string]interface{}) + if ok { + if x, ok := parentM["name"]; ok { + if y, ok := x.(string); ok { + return y + } + } + } + return "" +} + +func (images *Images) ResolvePlaceholders(k *k8s.K8sCluster, o *unstructured.Unstructured, deploymentDir string, tags []string) error { + namespace := o.GetNamespace() + deployment := fmt.Sprintf("%s/%s", o.GetKind(), o.GetName()) + + var remoteObject *unstructured.Unstructured + triedRemoteObject := false + + err := utils.NewObjectIterator(o.Object).IterateLeafs(func(it *utils.ObjectIterator) error { + s, ok := it.Value().(string) + if !ok { + return nil + } + newS := "" + container := images.extractContainerName(it.Parent()) + + offset := 0 + for true { + ph, err := images.parsePlaceholder(s, offset) + if err != nil { + return err + } + if ph == nil { + newS += s[offset:] + break + } else { + newS += s[offset:ph.startOffset] + } + + if !triedRemoteObject { + triedRemoteObject = true + remoteObject, err = k.GetSingleObject(types.RefFromObject(o)) + if err != nil && !errors.IsNotFound(err) { + return err + } + } + + var deployed *string + if remoteObject != nil && ph.startOffset == 0 && ph.endOffset == len(s) { + x, found, _ := utils.GetNestedChild(remoteObject.Object, it.KeyPath()...) + if found { + if y, ok := x.(string); ok { + deployed = &y + } + } + } + + resultImage, err := images.resolveImage(ph, namespace, deployment, container, deployed, deploymentDir, tags) + if err != nil { + return err + } + if resultImage == nil { + return fmt.Errorf("failed to find image for %s and latest version %s", ph.Image, ph.LatestVersion) + } + newS += *resultImage + + offset = ph.endOffset + if offset >= len(s) { + break + } + } + return it.SetValue(newS) + }) + if err != nil { + return err + } + return nil +} + +func (images *Images) resolveImage(ph *placeHolder, namespace string, deployment string, container string, deployed *string, deploymentDir string, tags []string) (*string, error) { + fixed := images.GetFixedImage(ph.Image, namespace, deployment, container) + + registry, err := images.GetLatestImageFromRegistry(ph.Image, ph.LatestVersion) + if err != nil { + return nil, err + } + + result := deployed + if result == nil || images.updateImages { + result = registry + } + if !images.updateImages && fixed != nil { + result = fixed + } + + if result != nil { + si := types.FixedImage{ + Image: ph.Image, + ResultImage: *result, + DeployedImage: deployed, + RegistryImage: registry, + Namespace: &namespace, + Deployment: &deployment, + Container: &container, + VersionFilter: &ph.LatestVersion, + DeployTags: tags, + DeploymentDir: &deploymentDir, + } + images.mutex.Lock() + images.seenImages = append(images.seenImages, si) + images.mutex.Unlock() + } + return result, nil +} diff --git a/pkg/deployment/vars.go b/pkg/deployment/vars.go new file mode 100644 index 000000000..12d77ebdc --- /dev/null +++ b/pkg/deployment/vars.go @@ -0,0 +1,167 @@ +package deployment + +import ( + "fmt" + "github.com/codablock/kluctl/pkg/jinja2_server" + "github.com/codablock/kluctl/pkg/k8s" + "github.com/codablock/kluctl/pkg/types" + "github.com/codablock/kluctl/pkg/utils" + log "github.com/sirupsen/logrus" + "gopkg.in/yaml.v3" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +type VarsCtx struct { + JS *jinja2_server.Jinja2Server + Vars map[string]interface{} +} + +func NewVarsCtx(js *jinja2_server.Jinja2Server) *VarsCtx { + vc := &VarsCtx{ + JS: js, + Vars: map[string]interface{}{}, + } + return vc +} + +func (vc *VarsCtx) Copy() *VarsCtx { + cp := &VarsCtx{ + JS: vc.JS, + Vars: utils.CopyObject(vc.Vars), + } + return cp +} + +func (vc *VarsCtx) MergedVars(vars map[string]interface{}) map[string]interface{} { + newVars, err := utils.CopyMergeObjects(vc.Vars, vars) + if err != nil { + log.Fatal(err) + } + return newVars +} + +func (vc *VarsCtx) MergedCtx(vars map[string]interface{}) *VarsCtx { + return &VarsCtx{ + JS: vc.JS, + Vars: vc.MergedVars(vars), + } +} + +func (vc *VarsCtx) Update(vars map[string]interface{}) { + utils.MergeObject(vc.Vars, vars) +} + +func (vc *VarsCtx) UpdateChild(child string, vars map[string]interface{}) { + vc.Update(map[string]interface{}{child: vars}) +} + +func (vc *VarsCtx) UpdateChildFromStruct(child string, o interface{}) error { + m, err := utils.StructToObject(o) + if err != nil { + return err + } + vc.UpdateChild(child, m) + return nil +} + +func (vc *VarsCtx) loadVarsList(k *k8s.K8sCluster, searchDirs []string, varsList []*types.VarsListItem) error { + for _, v := range varsList { + if v.Values != nil { + vc.Update(*v.Values) + } else if v.File != nil { + err := vc.loadVarsFile(*v.File, searchDirs) + if err != nil { + return err + } + } else if v.ClusterConfigMap != nil { + ref := types.NewObjectRef("", "v1", "ConfigMap", v.ClusterConfigMap.Name, v.ClusterConfigMap.Namespace) + err := vc.loadVarsFromK8sObject(k, ref, v.ClusterConfigMap.Key) + if err != nil { + return err + } + } else if v.ClusterSecret != nil { + ref := types.NewObjectRef("", "v1", "Secret", v.ClusterSecret.Name, v.ClusterSecret.Namespace) + err := vc.loadVarsFromK8sObject(k, ref, v.ClusterSecret.Key) + if err != nil { + return err + } + } else { + return fmt.Errorf("invalid vars entry: %v", v) + } + } + + return nil +} + +func (vc *VarsCtx) loadVarsFile(p string, searchDirs []string) error { + newVars := make(map[string]interface{}) + err := vc.renderYamlFile(p, searchDirs, newVars) + if err != nil { + return fmt.Errorf("failed to load vars from %s: %w", p, err) + } + vc.Update(newVars) + return nil +} + +func (vc *VarsCtx) loadVarsFromK8sObject(k *k8s.K8sCluster, ref types.ObjectRef, key string) error { + o, err := k.GetSingleObject(ref) + if err != nil { + return err + } + + value, found, err := unstructured.NestedString(o.UnstructuredContent(), "data", key) + if err != nil { + return err + } + if !found { + return fmt.Errorf("key %s not found in %s on cluster %s", key, ref.String(), k.Context()) + } + + err = vc.loadVarsFromString(value) + if err != nil { + return fmt.Errorf("failed to load vars from kubernetes object %s and key %s: %w", ref.String(), key, err) + } + return nil +} + +func (vc *VarsCtx) loadVarsFromString(s string) error { + newVars := make(map[string]interface{}) + err := vc.renderYamlString(s, newVars) + if err != nil { + return err + } + vc.Update(newVars) + return nil +} + +func (vc *VarsCtx) renderYamlString(s string, out interface{}) error { + ret, err := vc.JS.RenderString(s, nil, vc.Vars) + if err != nil { + return err + } + + err = yaml.Unmarshal([]byte(ret), out) + if err != nil { + return err + } + + return nil +} + +func (vc *VarsCtx) renderYamlFile(p string, searchDirs []string, out interface{}) error { + ret, err := vc.JS.RenderFile(p, searchDirs, vc.Vars) + if err != nil { + return err + } + + err = yaml.Unmarshal([]byte(ret), out) + if err != nil { + return err + } + + return nil +} + +func (vc *VarsCtx) renderDirectory(rootDir string, searchDirs []string, relSourceDir string, excludePatterns []string, subdir string, targetDir string) error { + return vc.JS.RenderDirectory(rootDir, searchDirs, relSourceDir, excludePatterns, subdir, targetDir, vc.Vars) +} diff --git a/pkg/diff/diff.go b/pkg/diff/diff.go new file mode 100644 index 000000000..0cbb68a21 --- /dev/null +++ b/pkg/diff/diff.go @@ -0,0 +1,154 @@ +package diff + +import ( + "fmt" + "github.com/codablock/kluctl/pkg/types" + "github.com/codablock/kluctl/pkg/utils" + "github.com/hexops/gotextdiff" + "github.com/hexops/gotextdiff/myers" + "github.com/hexops/gotextdiff/span" + diff2 "github.com/r3labs/diff/v2" + "gopkg.in/yaml.v3" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "strconv" + "strings" +) + +var notPresent = struct{}{} + +func convertPath(path []string, o interface{}) (string, error) { + var ret []interface{} + for _, p := range path { + if i, err := strconv.ParseInt(p, 10, 32); err == nil { + x, found, _ := utils.GetChild(o, int(i)) + if found { + ret = append(ret, int(i)) + o = x + continue + } + } + x, found, err := utils.GetChild(o, p) + if !found { + return "", fmt.Errorf("path element %v is invalid: %w", p, err) + } + ret = append(ret, p) + o = x + } + return utils.KeyListToJsonPath(ret), nil +} + +func Diff(oldObject *unstructured.Unstructured, newObject *unstructured.Unstructured) ([]types.Change, error) { + cl, err := diff2.Diff(oldObject.Object, newObject.Object) + if err != nil { + return nil, err + } + + var changes []types.Change + for _, c := range cl { + switch c.Type { + case "create": + ud, err := buildUnifiedDiff(notPresent, c.To) + if err != nil { + return nil, err + } + p, err := convertPath(c.Path, newObject.Object) + if err != nil { + return nil, err + } + changes = append(changes, types.Change{ + Type: "insert", + JsonPath: p, + NewValue: c.To, + UnifiedDiff: ud, + }) + case "delete": + ud, err := buildUnifiedDiff(c.From, notPresent) + if err != nil { + return nil, err + } + p, err := convertPath(c.Path, oldObject.Object) + if err != nil { + return nil, err + } + changes = append(changes, types.Change{ + Type: "delete", + JsonPath: p, + OldValue: c.From, + UnifiedDiff: ud, + }) + case "update": + ud, err := buildUnifiedDiff(c.From, c.To) + if err != nil { + return nil, err + } + p, err := convertPath(c.Path, newObject.Object) + if err != nil { + return nil, err + } + changes = append(changes, types.Change{ + Type: "update", + JsonPath: p, + NewValue: c.To, + OldValue: c.From, + UnifiedDiff: ud, + }) + } + } + return changes, nil +} + +func buildUnifiedDiff(a interface{}, b interface{}) (string, error) { + aStr, err := objectToDiffableString(a) + if err != nil { + return "", err + } + bStr, err := objectToDiffableString(b) + if err != nil { + return "", err + } + + if len(aStr) == 0 { + return prependStrToLines(bStr, "+"), nil + } else if len(bStr) == 0 { + return prependStrToLines(aStr, "-"), nil + } else if strings.Index(aStr, "\n") == -1 && strings.Index(bStr, "\n") == -1 { + return fmt.Sprintf("-%s\n+%s", aStr, bStr), nil + } + + edits := myers.ComputeEdits(span.URIFromPath("a"), aStr, bStr) + diff := fmt.Sprint(gotextdiff.ToUnified("a", "b", aStr, edits)) + // Skip diff header + lines := strings.Split(diff, "\n") + lines = lines[2:] + return strings.Join(lines, "\n"), nil +} + +func objectToDiffableString(o interface{}) (string, error) { + if o == nil { + return "null", nil + } + if o == notPresent { + return "", nil + } + if v, ok := o.(string); ok { + return v, nil + } + + b, err := yaml.Marshal(o) + if err != nil { + return "", err + } + return string(b), nil +} + +func prependStrToLines(s string, prepend string) string { + if strings.HasSuffix(s, "\n") { + s = s[:len(s)-1] + } + + lines := strings.Split(s, "\n") + for i, _ := range lines { + lines[i] = prepend + lines[i] + } + return strings.Join(lines, "\n") +} diff --git a/pkg/diff/managed_fields.go b/pkg/diff/managed_fields.go new file mode 100644 index 000000000..2d9ce421e --- /dev/null +++ b/pkg/diff/managed_fields.go @@ -0,0 +1,223 @@ +package diff + +import ( + "bytes" + "fmt" + "github.com/codablock/kluctl/pkg/utils" + log "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "reflect" + "regexp" + "sigs.k8s.io/structured-merge-diff/v4/fieldpath" + "sigs.k8s.io/structured-merge-diff/v4/value" + "strconv" +) + +type LostOwnership struct { + Field string + Message string +} + +var forceApplyFieldAnnotationRegex = regexp.MustCompile(`^kluctl.io/force-apply-field(-\d*)?$`) +var overwriteAllowedManagers = []*regexp.Regexp{ + regexp.MustCompile("kluctl"), + regexp.MustCompile("kubectl"), + regexp.MustCompile("kubectl-.*"), + regexp.MustCompile("rancher"), + regexp.MustCompile("k9s"), +} + +func checkListItemMatch(o interface{}, pathElement fieldpath.PathElement, index int) (bool, error) { + if pathElement.Key != nil { + m, ok := o.(map[string]interface{}) + if !ok { + return false, fmt.Errorf("object is not a map") + } + for _, f := range *pathElement.Key { + c, ok := m[f.Name] + if !ok { + return false, nil + } + lhs := value.NewValueInterface(c) + if value.Compare(lhs, f.Value) != 0 { + return false, nil + } + } + return true, nil + } else if pathElement.Value != nil { + lhs := value.NewValueInterface(o) + if value.Compare(lhs, *pathElement.Value) == 0 { + return true, nil + } + return false, nil + } else if pathElement.Index != nil { + if *pathElement.Index == index { + return true, nil + } + return false, nil + } else { + return false, fmt.Errorf("unexpected path element") + } +} + +func convertToKeyList(remote *unstructured.Unstructured, path fieldpath.Path) ([]interface{}, bool, error) { + var ret []interface{} + var o interface{} = remote.Object + for _, e := range path { + if e.FieldName != nil { + ret = append(ret, *e.FieldName) + x, found, err := utils.GetChild(o, *e.FieldName) + if err != nil { + return nil, false, err + } + if !found { + return ret, false, nil + } + o = x + } else { + l, ok := o.([]interface{}) + if !ok { + return nil, false, fmt.Errorf("object is not a list") + } + found := false + for i, x := range l { + match, err := checkListItemMatch(x, e, i) + if err != nil { + return nil, false, err + } + if match { + found = true + ret = append(ret, i) + break + } + } + if !found { + return ret, false, nil + } + } + } + return ret, true, nil +} + +func ResolveFieldManagerConflicts(local *unstructured.Unstructured, remote *unstructured.Unstructured, conflictStatus metav1.Status) (*unstructured.Unstructured, []LostOwnership, error) { + managedFields := remote.GetManagedFields() + + // "stupid" because the string representation in "details.causes.field" might be ambiguous as k8s does not escape dots + fieldsAsStupidStrings := make(map[string][]fieldpath.Path) + managersByFields := make(map[string][]string) + + for _, mf := range managedFields { + fieldSet := fieldpath.NewSet() + err := fieldSet.FromJSON(bytes.NewReader(mf.FieldsV1.Raw)) + if err != nil { + return nil, nil, err + } + + fieldSet.Iterate(func(path fieldpath.Path) { + s := path.String() + fieldsAsStupidStrings[s] = append(fieldsAsStupidStrings[s], path) + managersByFields[s] = append(managersByFields[s], mf.Manager) + }) + } + + ret := utils.CopyUnstructured(local) + + forceApplyAll := false + if x, ok := local.GetAnnotations()[`metadata.annotations["kluctl.io/force-apply"]`]; ok { + forceApplyAll, _ = strconv.ParseBool(x) + } + + forceApplyFields := make(map[string]bool) + for k, v := range local.GetAnnotations() { + if !forceApplyFieldAnnotationRegex.MatchString(k) { + continue + } + j, err := utils.NewMyJsonPath(v) + if err != nil { + return nil, nil, err + } + fields, err := j.ListMatchingFields(ret.Object) + if err != nil { + return nil, nil, err + } + for _, f := range fields { + forceApplyFields[utils.KeyListToJsonPath(f)] = true + } + } + + var lostOwnership []LostOwnership + for _, cause := range conflictStatus.Details.Causes { + if cause.Type != metav1.CauseTypeFieldManagerConflict { + return nil, nil, fmt.Errorf("unknown type %s", cause.Type) + } + + mfPath, ok := fieldsAsStupidStrings[cause.Field] + if !ok { + return nil, nil, fmt.Errorf("could not find matching field for path '%s'", cause.Field) + } + if len(mfPath) != 1 { + return nil, nil, fmt.Errorf("field path '%s' is ambiguous", cause.Field) + } + + p, found, err := convertToKeyList(remote, mfPath[0]) + if err != nil { + return nil, nil, err + } + if !found { + return nil, nil, fmt.Errorf("field '%s' not found in remote object", cause.Field) + } + localValue, found, err := utils.GetNestedChild(local.Object, p...) + if err != nil { + return nil, nil, err + } + if !found { + return nil, nil, fmt.Errorf("field '%s' not found in local object", cause.Field) + } + remoteValue, found, err := utils.GetNestedChild(remote.Object, p...) + if !found { + log.Fatalf("field '%s' not found in remote object...which can't be!", cause.Field) + } + + overwrite := true + if !forceApplyAll { + mfbf, _ := managersByFields[mfPath[0].String()] + for _, mfn := range mfbf { + found := false + for _, oa := range overwriteAllowedManagers { + if oa.MatchString(mfn) { + found = true + break + } + } + if !found { + overwrite = false + break + } + } + if _, ok := forceApplyFields[utils.KeyListToJsonPath(p)]; ok { + overwrite = true + } + } + + if !overwrite { + j, err := utils.NewMyJsonPath(utils.KeyListToJsonPath(p)) + if err != nil { + return nil, nil, err + } + err = j.Del(ret.Object) + if err != nil { + return nil, nil, err + } + + if !reflect.DeepEqual(localValue, remoteValue) { + lostOwnership = append(lostOwnership, LostOwnership{ + Field: cause.Field, + Message: cause.Message, + }) + } + } + } + + return ret, lostOwnership, nil +} diff --git a/pkg/diff/normalize.go b/pkg/diff/normalize.go new file mode 100644 index 000000000..905d8bbf8 --- /dev/null +++ b/pkg/diff/normalize.go @@ -0,0 +1,205 @@ +package diff + +import ( + "fmt" + "github.com/codablock/kluctl/pkg/k8s" + "github.com/codablock/kluctl/pkg/types" + "github.com/codablock/kluctl/pkg/utils" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "regexp" + "strconv" + "strings" +) + +func listToMap(l []map[string]interface{}, key string) (map[string]interface{}, error) { + m := make(map[string]interface{}) + for _, e := range l { + kv, ok := e[key] + if !ok { + return nil, fmt.Errorf("%s not found in list element", key) + } + kvs, ok := kv.(string) + if !ok { + return nil, fmt.Errorf("%s in list element is not a string", key) + } + m[kvs] = e + } + return m, nil +} + +func normalizeEnv(container map[string]interface{}) { + env := utils.NestedMapSliceNoErr(container, "env") + envFrom := utils.NestedMapSliceNoErr(container, "envFrom") + + if len(env) != 0 { + newEnv, err := listToMap(env, "name") + if err == nil { + container["env"] = newEnv + } + } + if len(envFrom) != 0 { + envTypes := []string{"configMapRef", "secretRef"} + m := make(map[string]interface{}) + for _, e := range envFrom { + k := "" + for _, t := range envTypes { + name, found, _ := unstructured.NestedString(e, t, "name") + if !found { + continue + } + k = fmt.Sprintf("%s/%s", t, name) + } + if k == "" { + if _, ok := m["unknown"]; !ok { + m["unknown"] = []interface{}{} + } + m["unknown"] = append(m["unknown"].([]interface{}), e) + } else { + m[k] = e + } + } + container["envFrom"] = m + } +} + +func normalizeContainers(containers []map[string]interface{}) { + for _, c := range containers { + normalizeEnv(c) + } +} + +func normalizeSecretAndConfigMaps(o map[string]interface{}) { + data, found, _ := unstructured.NestedMap(o, "data") + if found && len(data) == 0 { + unstructured.RemoveNestedField(o, "data") + } +} + +func normalizeServiceAccount(o map[string]interface{}) { + serviceAccountName, found, _ := unstructured.NestedString(o, "metadata", "name") + if !found { + return + } + + secrets, found, _ := unstructured.NestedSlice(o, "secrets") + if !found { + return + } + + // remove default service account tokens + var newSecrets []interface{} + for _, s := range secrets { + s2, ok := s.(map[string]interface{}) + if !ok { + continue + } + + name, found, _ := unstructured.NestedString(s2, "name") + if !found || strings.HasPrefix(name, fmt.Sprintf("%s-", serviceAccountName)) { + continue + } + newSecrets = append(newSecrets, s) + } + o["secrets"] = newSecrets +} + +func normalizeMetadata(k *k8s.K8sCluster, o *unstructured.Unstructured) { + k.RemoveNamespaceIfNeeded(o) + + // We don't care about managedFields when diffing (they just produce noise) + unstructured.RemoveNestedField(o.Object, "metadata", "managedFields") + unstructured.RemoveNestedField(o.Object, "metadata", "annotations", "managedFields", "kubectl.kubernetes.io/last-applied-configuration") + + // We don't want to see this in diffs + unstructured.RemoveNestedField(o.Object, "metadata", "creationTimestamp") + unstructured.RemoveNestedField(o.Object, "metadata", "generation") + unstructured.RemoveNestedField(o.Object, "metadata", "resourceVersion") + unstructured.RemoveNestedField(o.Object, "metadata", "selfLink") + unstructured.RemoveNestedField(o.Object, "metadata", "uid") + + // Ensure empty labels/metadata exist + if len(o.GetLabels()) == 0 { + _ = unstructured.SetNestedStringMap(o.Object, map[string]string{}, "metadata", "labels") + } + if len(o.GetAnnotations()) == 0 { + _ = unstructured.SetNestedStringMap(o.Object, map[string]string{}, "metadata", "annotations") + } +} + +func normalizeMisc(o map[string]interface{}) { + // These are random values found in Jobs + unstructured.RemoveNestedField(o, "spec", "template", "metadata", "labels", "controller-uid") + unstructured.RemoveNestedField(o, "spec", "selector", "matchLabels", "controller-uid") + + unstructured.RemoveNestedField(o, "status") +} + +var ignoreDiffFieldAnnotationRegex = regexp.MustCompile(`^kluctl.io/ignore-diff-field(-\d*)?$`) + +// NormalizeObject Performs some deterministic sorting and other normalizations to avoid ugly diffs due to order changes +func NormalizeObject(k *k8s.K8sCluster, o *unstructured.Unstructured, ignoreForDiffs []*types.IgnoreForDiffItemConfig, localObject *unstructured.Unstructured) *unstructured.Unstructured { + gvk := o.GroupVersionKind() + name := o.GetName() + ns := o.GetNamespace() + + o = utils.CopyUnstructured(o) + normalizeMetadata(k, o) + normalizeMisc(o.Object) + + switch gvk.Kind { + case "Deployment", "StatefulSet", "DaemonSet", "job": + normalizeContainers(utils.NestedMapSliceNoErr(o.Object, "spec", "template", "spec", "containers")) + case "Secret", "ConfigMap": + normalizeSecretAndConfigMaps(o.Object) + case "ServiceAccount": + normalizeServiceAccount(o.Object) + } + + checkMatch := func(v string, m *string) bool { + if v == "" || m == nil { + return true + } + return v == *m + } + + for _, ifd := range ignoreForDiffs { + if !checkMatch(gvk.Group, ifd.Group) { + continue + } + if !checkMatch(gvk.Kind, ifd.Kind) { + continue + } + if !checkMatch(ns, ifd.Namespace) { + continue + } + if !checkMatch(name, ifd.Name) { + continue + } + + for _, fp := range ifd.FieldPath { + jp, err := utils.NewMyJsonPath(fp) + if err != nil { + continue + } + _ = jp.Del(o.Object) + } + } + + ignoreAll, _ := strconv.ParseBool(localObject.GetAnnotations()["kluctl.io/ignore-diff"]) + if ignoreAll { + // Return empty object so that diffs will always be empty + return &unstructured.Unstructured{Object: map[string]interface{}{}} + } + + for k, v := range localObject.GetAnnotations() { + if ignoreDiffFieldAnnotationRegex.MatchString(k) { + j, err := utils.NewMyJsonPath(v) + if err != nil { + continue + } + _ = j.Del(o.Object) + } + } + + return o +} diff --git a/pkg/git/auth/auth_provider.go b/pkg/git/auth/auth_provider.go new file mode 100644 index 000000000..f49133e51 --- /dev/null +++ b/pkg/git/auth/auth_provider.go @@ -0,0 +1,30 @@ +package auth + +import ( + git_url "github.com/codablock/kluctl/pkg/git/git-url" + "github.com/go-git/go-git/v5/plumbing/transport" +) + +type GitAuthProvider interface { + BuildAuth(gitUrl git_url.GitUrl) transport.AuthMethod +} + +var authProviders []GitAuthProvider + +func RegisterAuthProvider(p GitAuthProvider) { + authProviders = append(authProviders, p) +} + +func BuildAuth(gitUrl git_url.GitUrl) transport.AuthMethod { + for _, p := range authProviders { + auth := p.BuildAuth(gitUrl) + if auth != nil { + return auth + } + } + return nil +} + +func init() { + RegisterAuthProvider(&GitCredentialsFileAuthProvider{}) +} diff --git a/pkg/git/auth/git_credentials_file.go b/pkg/git/auth/git_credentials_file.go new file mode 100644 index 000000000..41aab3a9f --- /dev/null +++ b/pkg/git/auth/git_credentials_file.go @@ -0,0 +1,77 @@ +package auth + +import ( + "bufio" + git_url "github.com/codablock/kluctl/pkg/git/git-url" + "github.com/codablock/kluctl/pkg/utils" + "github.com/go-git/go-git/v5/plumbing/transport" + "github.com/go-git/go-git/v5/plumbing/transport/http" + log "github.com/sirupsen/logrus" + giturls "github.com/whilp/git-urls" + "os" + "path" +) + +type GitCredentialsFileAuthProvider struct { +} + +func (a *GitCredentialsFileAuthProvider) BuildAuth(gitUrl git_url.GitUrl) transport.AuthMethod { + home, err := os.UserHomeDir() + if err != nil { + log.Warningf("Could not determine home directory: %v", err) + return nil + } + auth := a.tryBuildAuth(gitUrl, path.Join(home, ".git-credentials")) + if auth != nil { + return auth + } + + if xdgHome, ok := os.LookupEnv("XDG_CONFIG_HOME"); ok && xdgHome != "" { + auth = a.tryBuildAuth(gitUrl, path.Join(xdgHome, ".git-credentials")) + if auth != nil { + return auth + } + } else { + auth = a.tryBuildAuth(gitUrl, path.Join(home, ".config/.git-credentials")) + if auth != nil { + return auth + } + } + return nil +} + +func (a *GitCredentialsFileAuthProvider) tryBuildAuth(gitUrl git_url.GitUrl, gitCredentialsPath string) transport.AuthMethod { + if !utils.IsFile(gitCredentialsPath) { + return nil + } + + f, err := os.Open(gitCredentialsPath) + if err != nil { + log.Warningf("Failed to open %s: %v", gitCredentialsPath, err) + return nil + } + defer f.Close() + + s := bufio.NewScanner(f) + for s.Scan() { + u, err := giturls.Parse(s.Text()) + if err != nil { + continue + } + if u.Host != gitUrl.Host { + continue + } + if u.User == nil { + continue + } + if password, ok := u.User.Password(); ok { + if u.User.Username() != "" { + return &http.BasicAuth{ + Username: u.User.Username(), + Password: password, + } + } + } + } + return nil +} diff --git a/pkg/git/git-url/url.go b/pkg/git/git-url/url.go new file mode 100644 index 000000000..328d95dfd --- /dev/null +++ b/pkg/git/git-url/url.go @@ -0,0 +1,98 @@ +package git_url + +import ( + "fmt" + giturls "github.com/whilp/git-urls" + "gopkg.in/yaml.v3" + "net/url" + "strings" +) + +type GitUrl struct { + url.URL +} + +func Parse(u string) (*GitUrl, error) { + u2, err := giturls.Parse(u) + if err != nil { + return nil, err + } + return &GitUrl{*u2}, nil +} + +func (u *GitUrl) UnmarshalYAML(value *yaml.Node) error { + var s string + err := value.Decode(&s) + if err != nil { + return err + } + u2, err := Parse(s) + if err != nil { + return err + } + *u = *u2 + return err +} + +func (u GitUrl) MarshalYAML() (interface{}, error) { + return u.String(), nil +} + +func (u *GitUrl) NormalizePort() string { + port := u.Port() + if port == "" { + return "" + } + + defaultPort := "" + switch u.Scheme { + case "http": + defaultPort = "80" + case "https": + defaultPort = "443" + case "git": + defaultPort = "22" + case "git+ssh": + defaultPort = "22" + case "ssh": + defaultPort = "22" + case "ftp": + defaultPort = "21" + case "rsync": + defaultPort = "873" + case "file": + break + default: + return port + } + + if defaultPort == "" || port == defaultPort { + return "" + } + return port +} + +func (u *GitUrl) Normalize() *GitUrl { + path := strings.ToLower(u.Path) + if strings.HasSuffix(path, ".git") { + path = path[:len(path)-len(".git")] + } + if strings.HasSuffix(path, "/") { + path = path[:len(path)-1] + } + hostname := strings.ToLower(u.Hostname()) + port := u.NormalizePort() + + u2 := *u + u2.Path = path + u2.Host = hostname + if port != "" { + u2.Host += ":" + port + } + return &u2 +} + +func (u *GitUrl) NormalizedRepoKey() string { + u2 := u.Normalize() + return fmt.Sprintf("%s:%s", u2.Host, u2.Path) +} diff --git a/pkg/git/mirrored_repo.go b/pkg/git/mirrored_repo.go new file mode 100644 index 000000000..93167c618 --- /dev/null +++ b/pkg/git/mirrored_repo.go @@ -0,0 +1,269 @@ +package git + +import ( + "fmt" + auth2 "github.com/codablock/kluctl/pkg/git/auth" + git_url "github.com/codablock/kluctl/pkg/git/git-url" + "github.com/codablock/kluctl/pkg/utils" + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/config" + "github.com/go-git/go-git/v5/plumbing" + log "github.com/sirupsen/logrus" + "io/ioutil" + "os" + "path" + "strings" +) +import "github.com/gofrs/flock" + +var cacheBaseDir = path.Join(utils.GetTmpBaseDir(), "git-cache") + +type MirroredGitRepo struct { + url git_url.GitUrl + remoteName string + mirrorDir string + + hasLock bool + hasUpdated bool + fileLock *flock.Flock + + remoteRefs []*plumbing.Reference +} + +func NewMirroredGitRepo(u git_url.GitUrl) (*MirroredGitRepo, error) { + remoteName := buildRemoteName(u) + o := &MirroredGitRepo{ + url: u, + remoteName: remoteName, + mirrorDir: path.Join(cacheBaseDir, remoteName), + } + + if !utils.IsDirectory(o.mirrorDir) { + err := os.MkdirAll(o.mirrorDir, 0o777) + if err != nil { + return nil, fmt.Errorf("failed to create mirror repo for %v: %w", u.String(), err) + } + } + o.fileLock = flock.New(path.Join(o.mirrorDir, ".cache.lock")) + return o, nil +} + +func (g *MirroredGitRepo) HasUpdated() bool { + return g.hasUpdated +} + +func (g *MirroredGitRepo) Lock() error { + err := g.fileLock.Lock() + if err != nil { + return err + } + g.hasLock = true + return nil +} + +func (g *MirroredGitRepo) Unlock() error { + err := g.fileLock.Unlock() + if err != nil { + return err + } + g.hasLock = false + return nil +} + +func (g *MirroredGitRepo) WithLock(cb func() error) error { + err := g.Lock() + if err != nil { + return err + } + defer g.Unlock() + return cb() +} + +func (g *MirroredGitRepo) MaybeWithLock(lock bool, cb func() error) error { + if lock { + return g.WithLock(cb) + } + return cb() +} + +func (g *MirroredGitRepo) RemoteRefHashesMap() map[string]string { + refs := make(map[string]string) + for _, r := range g.remoteRefs { + refs[r.Name().String()] = r.Hash().String() + } + return refs +} + +func (g *MirroredGitRepo) DefaultRef() *string { + for _, ref := range g.remoteRefs { + if ref.Name() == "HEAD" { + if ref.Type() == plumbing.SymbolicReference { + s := string(ref.Target()) + return &s + } + } + } + return nil +} + +func (g *MirroredGitRepo) buildRepositoryObject() (*git.Repository, error) { + return git.PlainOpen(g.mirrorDir) +} + +func (g *MirroredGitRepo) cleanupMirrorDir() error { + if utils.IsDirectory(g.mirrorDir) { + files, err := ioutil.ReadDir(g.mirrorDir) + if err != nil { + return err + } + for _, fi := range files { + if fi.Name() == ".cache.lock" { + continue + } + _ = os.RemoveAll(path.Join(g.mirrorDir, fi.Name())) + } + } + return nil +} + +func (g *MirroredGitRepo) update(repoDir string) error { + log.Infof("Updating mirror repo: url='%v'", g.url.String()) + r, err := git.PlainOpen(repoDir) + if err != nil { + return err + } + + auth := auth2.BuildAuth(g.url) + + remote, err := r.Remote("origin") + if err != nil { + return err + } + + g.remoteRefs, err = remote.List(&git.ListOptions{ + Auth: auth, + }) + if err != nil { + return err + } + + err = remote.Fetch(&git.FetchOptions{ + Auth: auth, + Progress: os.Stdout, + Tags: git.AllTags, + Force: true, + }) + if err != nil && err != git.NoErrAlreadyUpToDate { + return err + } + + // update default branch, referenced via HEAD + // we assume that HEAD is a symbolic ref and don't care about old git versions + for _, ref := range g.remoteRefs { + if ref.Name() == "HEAD" { + err = r.Storer.SetReference(ref) + if err != nil { + return err + } + break + } + } + + return nil +} + +func (g *MirroredGitRepo) cloneOrUpdate() error { + initMarker := path.Join(g.mirrorDir, ".cache2.init") + if utils.IsFile(initMarker) { + return g.update(g.mirrorDir) + } + err := g.cleanupMirrorDir() + if err != nil { + return err + } + + log.Infof("Cloning mirror repo at %v", g.mirrorDir) + + tmpMirrorDir, err := ioutil.TempDir(utils.GetTmpBaseDir(), "mirror-") + if err != nil { + return err + } + defer os.RemoveAll(tmpMirrorDir) + + repo, err := git.PlainInit(tmpMirrorDir, true) + if err != nil { + return err + } + + _, err = repo.CreateRemote(&config.RemoteConfig{ + Name: "origin", + URLs: []string{g.url.String()}, + Fetch: []config.RefSpec{ + "+refs/*:refs/*", // same as with "git clone --mirror" + }, + }) + if err != nil { + return err + } + + err = g.update(tmpMirrorDir) + if err != nil { + return err + } + + files, err := ioutil.ReadDir(tmpMirrorDir) + if err != nil { + return err + } + for _, fi := range files { + err = os.Rename(path.Join(tmpMirrorDir, fi.Name()), path.Join(g.mirrorDir, fi.Name())) + if err != nil { + return err + } + } + err = utils.Touch(initMarker) + if err != nil { + return err + } + return nil +} + +func (g *MirroredGitRepo) Update() error { + err := g.cloneOrUpdate() + if err != nil { + return err + } + g.hasUpdated = true + return nil +} + +func (g *MirroredGitRepo) CloneProject(ref string, targetDir string) error { + if !g.hasLock || !g.hasUpdated { + log.Fatalf("tried to clone from a project that is not locked/updated") + } + + log.Debugf("Cloning git project: url='%v', ref='%v'", g.url.String(), ref) + + fileUrl := fmt.Sprintf("file://%s", g.mirrorDir) + if ref != "" { + ref = fmt.Sprintf("refs/heads/%s", plumbing.ReferenceName(ref)) + } + _, err := git.PlainClone(targetDir, false, &git.CloneOptions{ + URL: fileUrl, + SingleBranch: true, + ReferenceName: plumbing.ReferenceName(ref), + }) + if err != nil { + return fmt.Errorf("failed to clone %s to %s: %w", fileUrl, targetDir, err) + } + return err +} + +func buildRemoteName(u git_url.GitUrl) string { + r := path.Base(u.Path) + if strings.HasSuffix(r, ".git") { + r = r[:len(r)-len(".git")] + } + r += "-" + utils.Sha256String(u.String())[:6] + return r +} diff --git a/pkg/git/utils.go b/pkg/git/utils.go new file mode 100644 index 000000000..3f9d72066 --- /dev/null +++ b/pkg/git/utils.go @@ -0,0 +1,24 @@ +package git + +import ( + "github.com/go-git/go-git/v5" +) + +type GitRepoInfo struct { + CheckedOutRef string + CheckedOutCommit string +} + +func GetGitRepoInfo(path string) (ri GitRepoInfo, err error) { + r, err := git.PlainOpen(path) + if err != nil { + return + } + head, err := r.Head() + if err != nil { + return + } + ri.CheckedOutRef = head.Name().String() + ri.CheckedOutCommit = head.Hash().String() + return +} diff --git a/pkg/jinja2_server/.gitignore b/pkg/jinja2_server/.gitignore new file mode 100644 index 000000000..46d6257b8 --- /dev/null +++ b/pkg/jinja2_server/.gitignore @@ -0,0 +1,2 @@ +jinja2_server.pb.go +jinja2_server_grpc.pb.go \ No newline at end of file diff --git a/pkg/jinja2_server/server.go b/pkg/jinja2_server/server.go new file mode 100644 index 000000000..8eae88c27 --- /dev/null +++ b/pkg/jinja2_server/server.go @@ -0,0 +1,375 @@ +package jinja2_server + +import ( + "bufio" + "bytes" + "context" + "encoding/json" + "fmt" + "github.com/codablock/kluctl/pkg/utils" + "github.com/gobwas/glob" + log "github.com/sirupsen/logrus" + "golang.org/x/sync/semaphore" + "google.golang.org/grpc" + "io/fs" + "io/ioutil" + "os" + "os/exec" + "path" + "path/filepath" + "strconv" + "strings" + "sync" +) + +type Jinja2Error struct { + error string +} + +func (m *Jinja2Error) Error() string { + return m.error +} + +type Jinja2Server struct { + serverPath string + pythonVenv string + cmd *exec.Cmd + + port int + conn *grpc.ClientConn + client Jinja2ServerClient + sem *semaphore.Weighted + + globCache map[string]interface{} + globCacheMutex sync.Mutex +} + +func NewJinja2Server() (*Jinja2Server, error) { + js := &Jinja2Server{ + sem: semaphore.NewWeighted(8), + globCache: map[string]interface{}{}, + } + + serverPath, ok := os.LookupEnv("JINJA2_SERVER") + if !ok { + executable, err := os.Executable() + if err != nil { + log.Fatal(err) + } + serverPath = path.Join(path.Dir(executable), "jinja2-server") + } + + js.serverPath = serverPath + js.pythonVenv = path.Join(js.serverPath, "venv") + + cmdName := path.Join(js.pythonVenv, "bin/python") + args := []string{"main.py"} + + args = append(args, "serve") + js.cmd = exec.Command(cmdName, args...) + js.cmd.Dir = js.serverPath + + stdout, err := js.cmd.StdoutPipe() + if err != nil { + return nil, err + } + + err = js.cmd.Start() + if err != nil { + _ = stdout.Close() + return nil, err + } + + s := bufio.NewScanner(stdout) + if !s.Scan() { + _ = js.cmd.Process.Kill() + return nil, fmt.Errorf("failed to determine jinja2-server port") + } + + port, err := strconv.ParseInt(s.Text(), 10, 32) + if err != nil { + _ = js.cmd.Process.Kill() + return nil, fmt.Errorf("failed to parse port: %w", err) + } + + js.port = int(port) + js.conn, err = grpc.Dial(fmt.Sprintf("localhost:%d", js.port), grpc.WithInsecure()) + if err != nil { + _ = js.cmd.Process.Kill() + return nil, err + } + js.client = NewJinja2ServerClient(js.conn) + + return js, nil +} + +func (js *Jinja2Server) Stop() error { + _ = js.conn.Close() + return js.cmd.Process.Kill() +} + +type RenderJob struct { + Template string + Result *string + Error error + + target string +} + +func (js *Jinja2Server) isMaybeTemplate(template string, searchDirs []string, isString bool) (bool, *string) { + if isString { + if strings.IndexRune(template, '{') == -1 { + return false, &template + } + } else { + for _, s := range searchDirs { + b, err := ioutil.ReadFile(path.Join(s, template)) + if err != nil { + continue + } + if bytes.IndexRune(b, '{') == -1 { + x := string(b) + return false, &x + } + } + } + return true, nil +} + +func (js *Jinja2Server) renderHelper(jobs []*RenderJob, searchDirs []string, vars map[string]interface{}, isString bool) error { + varsStr, err := json.Marshal(vars) + if err != nil { + return err + } + + var processedJobs []*RenderJob + var templates []string + for _, job := range jobs { + if ist, r := js.isMaybeTemplate(job.Template, searchDirs, isString); !ist { + job.Result = r + continue + } + processedJobs = append(processedJobs, job) + templates = append(templates, job.Template) + } + if len(templates) == 0 { + return nil + } + + err = js.sem.Acquire(context.Background(), 1) + if err != nil { + return err + } + defer js.sem.Release(1) + + var result *JobResult + if isString { + request := &StringsJob{ + Vars: string(varsStr), + Templates: templates, + SearchDirs: searchDirs, + } + result, err = js.client.RenderStrings(context.Background(), request) + } else { + request := &FilesJob{ + Vars: string(varsStr), + Templates: templates, + SearchDirs: searchDirs, + } + result, err = js.client.RenderFiles(context.Background(), request) + } + if err != nil { + return err + } + + for i, r := range result.Results { + if r.Error != nil { + processedJobs[i].Error = &Jinja2Error{error: *r.Error} + } else { + processedJobs[i].Result = r.Result + } + } + return nil +} + +func (js *Jinja2Server) RenderStrings(jobs []*RenderJob, searchDirs []string, vars map[string]interface{}) error { + return js.renderHelper(jobs, searchDirs, vars, true) +} + +func (js *Jinja2Server) RenderFiles(jobs []*RenderJob, searchDirs []string, vars map[string]interface{}) error { + return js.renderHelper(jobs, searchDirs, vars, false) +} + +func (js *Jinja2Server) RenderString(template string, searchDirs []string, vars map[string]interface{}) (string, error) { + jobs := []*RenderJob{{ + Template: template, + }} + err := js.RenderStrings(jobs, searchDirs, vars) + if err != nil { + return "", err + } + return *jobs[0].Result, nil +} + +func (js *Jinja2Server) RenderFile(template string, searchDirs []string, vars map[string]interface{}) (string, error) { + jobs := []*RenderJob{{ + Template: template, + }} + err := js.RenderFiles(jobs, searchDirs, vars) + if err != nil { + return "", err + } + return *jobs[0].Result, nil +} + +func (js *Jinja2Server) RenderStruct(dst interface{}, src interface{}, vars map[string]interface{}) error { + m, err := utils.StructToObject(src) + + type pk struct { + parent interface{} + key interface{} + } + + var jobs []*RenderJob + var fields []pk + err = utils.NewObjectIterator(m).IterateLeafs(func(it *utils.ObjectIterator) error { + value := it.Value() + if s, ok := value.(string); ok { + jobs = append(jobs, &RenderJob{Template: s}) + fields = append(fields, pk{ + parent: it.Parent(), + key: it.Key(), + }) + } + return nil + }) + if err != nil { + return err + } + + err = js.RenderStrings(jobs, nil, vars) + if err != nil { + return err + } + + var errors []error + for i, j := range jobs { + if j.Error != nil { + errors = append(errors, err) + } + + err = utils.SetChild(fields[i].parent, fields[i].key, *j.Result) + if err != nil { + return err + } + } + if len(errors) != 0 { + return utils.NewErrorList(errors) + } + + err = utils.ObjectToStruct(m, dst) + if err != nil { + return err + } + return nil +} + +func (js *Jinja2Server) getGlob(pattern string) (glob.Glob, error) { + js.globCacheMutex.Lock() + defer js.globCacheMutex.Unlock() + + g, ok := js.globCache[pattern] + if ok { + if g2, ok := g.(glob.Glob); ok { + return g2, nil + } else { + return nil, g2.(error) + } + } + g, err := glob.Compile(pattern) + if err != nil { + js.globCache[pattern] = err + return nil, err + } + js.globCache[pattern] = g + return g.(glob.Glob), nil +} +func (js *Jinja2Server) needsRender(path string, excludedPatterns []string) bool { + for _, p := range excludedPatterns { + g, err := js.getGlob(p) + if err != nil { + return false + } + if g.Match(path) { + return false + } + } + return true +} + +func (js *Jinja2Server) RenderDirectory(rootDir string, searchDirs []string, relSourceDir string, excludePatterns []string, subdir string, targetDir string, vars map[string]interface{}) error { + walkDir := path.Join(rootDir, relSourceDir, subdir) + + var jobs []*RenderJob + + err := filepath.WalkDir(walkDir, func(p string, d fs.DirEntry, err error) error { + relPath, err := filepath.Rel(walkDir, p) + if err != nil { + return err + } + if d.IsDir() { + err = os.MkdirAll(path.Join(targetDir, relPath), 0o777) + if err != nil { + return err + } + return nil + } + + sourcePath := path.Clean(path.Join(subdir, relPath)) + targetPath := path.Join(targetDir, relPath) + + if strings.Index(sourcePath, ".sealme") != -1 { + sourcePath += "" + } + + if !js.needsRender(sourcePath, excludePatterns) { + return utils.CopyFile(p, targetPath) + } + + // jinja2 templates are using / even on Windows + sourcePath = strings.ReplaceAll(sourcePath, "\\", "/") + + job := &RenderJob{ + Template: sourcePath, + target: targetPath, + } + jobs = append(jobs, job) + return nil + }) + if err != nil { + return err + } + + err = js.RenderFiles(jobs, searchDirs, vars) + if err != nil { + return err + } + + var errors []error + for _, job := range jobs { + if job.Error != nil { + errors = append(errors, job.Error) + continue + } + + err = ioutil.WriteFile(job.target, []byte(*job.Result), 0o666) + if err != nil { + return err + } + } + if len(errors) != 0 { + return utils.NewErrorList(errors) + } + + return nil +} diff --git a/pkg/k8s/all_objects.go b/pkg/k8s/all_objects.go new file mode 100644 index 000000000..fcac2395d --- /dev/null +++ b/pkg/k8s/all_objects.go @@ -0,0 +1,58 @@ +package k8s + +import ( + "github.com/codablock/kluctl/pkg/utils" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "strings" +) + +func GetIncludedObjectsMetadata(k *K8sCluster, verbs []string, labels map[string]string, inclusion *utils.Inclusion) ([]*v1.PartialObjectMetadata, error) { + objects, err := k.ListAllObjectsMetadata(verbs, "", labels) + if err != nil { + return nil, err + } + + var ret []*v1.PartialObjectMetadata + + for _, o := range objects { + var iv []utils.InclusionEntry + for t := range getTagsFromObject(o) { + iv = append(iv, utils.InclusionEntry{ + Type: "tag", + Value: t, + }) + } + + annotations := o.GetAnnotations() + if annotations != nil { + if itemDir, ok := annotations["kluctl.io/kustomize_dir"]; ok { + iv = append(iv, utils.InclusionEntry{ + Type: "kustomize_dir", + Value: itemDir, + }) + } + } + + if inclusion.CheckIncluded(iv, false) { + ret = append(ret, o) + } + + } + + return ret, nil +} + +func getTagsFromObject(o *v1.PartialObjectMetadata) map[string]bool { + labels := o.GetLabels() + if len(labels) == 0 { + return nil + } + + tags := make(map[string]bool) + for n, v := range labels { + if strings.HasPrefix(n, "kluctl.io/tag") { + tags[v] = true + } + } + return tags +} diff --git a/pkg/k8s/delete_utils.go b/pkg/k8s/delete_utils.go new file mode 100644 index 000000000..5dea8d01b --- /dev/null +++ b/pkg/k8s/delete_utils.go @@ -0,0 +1,218 @@ +package k8s + +import ( + "github.com/codablock/kluctl/pkg/types" + "github.com/codablock/kluctl/pkg/utils" + log "github.com/sirupsen/logrus" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "strconv" + "sync" +) + +// either names or apigroups +var deleteOrder = [][]string{ + // delete namespaces first + { + "Namespace", + }, + // high level stuff from CRDs + { + "monitoring.coreos.com", + "kafka.strimzi.io", + "zookeeper.pravega.io", + "elasticsearch.k8s.elastic.co", + "cert-manager.io", + "bitnami.com", + "acid.zalan.do", + }, + { + // generic high level stuff + "Deployment", + "StatefulSet", + "DaemonSet", + "Service", + "Ingress", + }, + // and now everything else + nil, +} + +func getFilteredApiResources(k *K8sCluster, filter []string) (map[schema.GroupVersionKind]bool, error) { + apiResourceLists, err := k.discovery.ServerPreferredResources() + if err != nil { + return nil, err + } + + ret := make(map[schema.GroupVersionKind]bool) + for _, l := range apiResourceLists { + for _, r := range l.APIResources { + for _, f := range filter { + if r.Name != f && r.Group != f && r.Kind != f { + continue + } + gvk := schema.GroupVersionKind{ + Group: r.Group, + Version: r.Version, + Kind: r.Kind, + } + ret[gvk] = true + } + } + } + return ret, nil +} + +func objectRefForExclusion(k *K8sCluster, ref types.ObjectRef) types.ObjectRef { + ref = k.RemoveNamespaceFromRefIfNeeded(ref) + ref.GVK.Version = "" + return ref +} + +func filterObjectsForDelete(k *K8sCluster, objects []*v1.PartialObjectMetadata, apiFilter []string, inclusion *utils.Inclusion, excludedObjects map[types.ObjectRef]bool) ([]*v1.PartialObjectMetadata, error) { + filteredResources, err := getFilteredApiResources(k, apiFilter) + if err != nil { + return nil, err + } + + inclusionHasTags := inclusion.HasType("tags") + var ret []*v1.PartialObjectMetadata + + for _, o := range objects { + ref := types.RefFromPartialObject(o) + ref = objectRefForExclusion(k, ref) + if _, ok := filteredResources[ref.GVK]; !ok { + continue + } + + annotations := o.GetAnnotations() + ownerRefs := o.GetOwnerReferences() + managedFields := o.GetManagedFields() + + // exclude when explicitly requested + skipDelete, err := strconv.ParseBool(annotations["kluctl.io/skip-delete"]) + if err == nil && skipDelete { + continue + } + + // exclude objects which are owned by some other object + if len(ownerRefs) != 0 { + continue + } + + if len(managedFields) == 0 { + // We don't know who manages it...be safe and exclude it + continue + } + + // check if kluctl is managing this object + found := false + for _, mf := range managedFields { + if mf.Manager == "kluctl" { + found = true + break + } + } + if !found { + // This object is not managed by kluctl, so we shouldn't delete it + continue + } + + // exclude objects from excluded_objects + if _, ok := excludedObjects[ref]; ok { + continue + } + + // exclude resources which have the 'kluctl.io/skip-delete-if-tags' annotation set + if inclusionHasTags { + skipDeleteIfTags, err := strconv.ParseBool(annotations["kluctl.io/skip-delete-if-tags"]) + if err == nil && skipDeleteIfTags { + continue + } + } + + ret = append(ret, o) + } + return ret, nil +} + +func FindObjectsForDelete(k *K8sCluster, labels map[string]string, inclusion *utils.Inclusion, excludedObjects []types.ObjectRef) ([]types.ObjectRef, error) { + log.Infof("Getting all cluster objects matching deleteByLabels") + + excludedObjectsMap := make(map[types.ObjectRef]bool) + for _, ref := range excludedObjects { + excludedObjectsMap[objectRefForExclusion(k, ref)] = true + } + + allClusterObjects, err := GetIncludedObjectsMetadata(k, []string{"delete"}, labels, inclusion) + if err != nil { + return nil, err + } + + var ret []types.ObjectRef + + for _, filter := range deleteOrder { + l, err := filterObjectsForDelete(k, allClusterObjects, filter, inclusion, excludedObjectsMap) + if err != nil { + return nil, err + } + for _, o := range l { + ref := types.RefFromPartialObject(o) + excludedObjectsMap[objectRefForExclusion(k, ref)] = true + ret = append(ret, ref) + } + } + + return ret, nil +} + +func DeleteObjects(k *K8sCluster, refs []types.ObjectRef, doWait bool) (*types.CommandResult, error) { + wp := utils.NewDebuggerAwareWorkerPool(8) + defer wp.StopWait(false) + + namespaceNames := make(map[string]bool) + var deletedObjects []types.ObjectRef + var mutex sync.Mutex + + for _, ref_ := range refs { + ref := ref_ + if ref.GVK.GroupVersion().String() == "v1" && ref.GVK.Kind == "Namespace" { + namespaceNames[ref.Name] = true + wp.Submit(func() error { + err := k.DeleteSingleObject(ref, DeleteOptions{NoWait: !doWait}) + mutex.Lock() + defer mutex.Unlock() + if err == nil { + deletedObjects = append(deletedObjects, ref) + } + return err + }) + } + } + err := wp.StopWait(true) + if err != nil { + return nil, err + } + + for _, ref_ := range refs { + ref := ref_ + if ref.GVK.GroupVersion().String() == "v1" && ref.GVK.Kind == "Namespace" { + continue + } + if _, ok := namespaceNames[ref.Namespace]; ok { + // already deleted via namespace + continue + } + wp.Submit(func() error { + err := k.DeleteSingleObject(ref, DeleteOptions{NoWait: !doWait}) + mutex.Lock() + defer mutex.Unlock() + if err == nil { + deletedObjects = append(deletedObjects, ref) + } + return err + }) + } + err = wp.StopWait(false) + return &types.CommandResult{DeletedObjects: deletedObjects}, err +} diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go new file mode 100644 index 000000000..6d8df220b --- /dev/null +++ b/pkg/k8s/k8s_cluster.go @@ -0,0 +1,622 @@ +package k8s + +import ( + "context" + "fmt" + types2 "github.com/codablock/kluctl/pkg/types" + "github.com/codablock/kluctl/pkg/utils" + goversion "github.com/hashicorp/go-version" + log "github.com/sirupsen/logrus" + "k8s.io/apimachinery/pkg/api/errors" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/discovery/cached/disk" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/metadata" + _ "k8s.io/client-go/plugin/pkg/client/auth" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/util/homedir" + "path" + "strings" + "sync" + "time" +) + +type K8sCluster struct { + context string + DryRun bool + + discovery *disk.CachedDiscoveryClient + dynamicClientPool chan dynamic.Interface + metadataClientPool chan metadata.Interface + + ServerVersion *goversion.Version + + allResources map[schema.GroupVersionKind]v1.APIResource + preferredResources map[schema.GroupKind]v1.APIResource + mutex sync.Mutex +} + +func NewK8sCluster(context string, dryRun bool) (*K8sCluster, error) { + k := &K8sCluster{ + context: context, + DryRun: dryRun, + } + + home := homedir.HomeDir() + if home == "" { + return nil, fmt.Errorf("home dir could not be determined") + } + kubeconfig := path.Join(home, ".kube", "config") + + configLoadingRules := &clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfig} + configOverrides := &clientcmd.ConfigOverrides{CurrentContext: context} + + config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(configLoadingRules, configOverrides).ClientConfig() + if err != nil { + return nil, err + } + config.QPS = 10 + config.Burst = 20 + + restConfig := dynamic.ConfigFor(config) + + discovery2, err := disk.NewCachedDiscoveryClientForConfig(restConfig, path.Join(utils.GetTmpBaseDir(), "kube-cache"), path.Join(utils.GetTmpBaseDir(), "kube-http-cache"), time.Hour*24) + if err != nil { + return nil, err + } + k.discovery = discovery2 + + parallelClients := 16 + k.dynamicClientPool = make(chan dynamic.Interface, parallelClients) + k.metadataClientPool = make(chan metadata.Interface, parallelClients) + for i := 0; i < parallelClients; i++ { + dynamic2, err := dynamic.NewForConfig(restConfig) + if err != nil { + return nil, err + } + + metadataClient, err := metadata.NewForConfig(config) + if err != nil { + return nil, err + } + + k.dynamicClientPool <- dynamic2 + k.metadataClientPool <- metadataClient + } + + // TODO config.WarningHandler + + v, err := k.discovery.ServerVersion() + if err != nil { + return nil, err + } + v2, err := goversion.NewVersion(v.String()) + if err != nil { + return nil, err + } + k.ServerVersion = v2 + + err = k.updateResources() + if err != nil { + return nil, err + } + + return k, nil +} + +func (k *K8sCluster) Context() string { + return k.context +} + +func (k *K8sCluster) updateResources() error { + k.mutex.Lock() + defer k.mutex.Unlock() + + k.allResources = map[schema.GroupVersionKind]v1.APIResource{} + k.preferredResources = map[schema.GroupKind]v1.APIResource{} + + _, arls, err := k.discovery.ServerGroupsAndResources() + if err != nil { + return err + } + for _, arl := range arls { + for _, ar := range arl.APIResources { + if strings.Index(ar.Name, "/") != -1 { + // skip subresources + continue + } + gv, err := schema.ParseGroupVersion(arl.GroupVersion) + if err != nil { + continue + } + + ar := ar + ar.Group = gv.Group + ar.Version = gv.Version + + gvk := schema.GroupVersionKind{ + Group: ar.Group, + Version: ar.Version, + Kind: ar.Kind, + } + if _, ok := k.allResources[gvk]; ok { + ok = false + } + k.allResources[gvk] = ar + } + } + + arls, err = k.discovery.ServerPreferredResources() + for _, arl := range arls { + for _, ar := range arl.APIResources { + if strings.Index(ar.Name, "/") != -1 { + // skip subresources + continue + } + gv, err := schema.ParseGroupVersion(arl.GroupVersion) + if err != nil { + continue + } + + ar := ar + ar.Group = gv.Group + ar.Version = gv.Version + + gk := schema.GroupKind{ + Group: ar.Group, + Kind: ar.Kind, + } + k.preferredResources[gk] = ar + } + } + return nil +} + +func (k *K8sCluster) IsNamespaced(gvk schema.GroupVersionKind) bool { + k.mutex.Lock() + defer k.mutex.Unlock() + r, ok := k.allResources[gvk] + if !ok { + return false + } + return r.Namespaced +} + +func (k *K8sCluster) ShouldRemoveNamespace(ref types2.ObjectRef) bool { + k.mutex.Lock() + defer k.mutex.Unlock() + r, ok := k.allResources[ref.GVK] + if !ok { + // we don't know, so don't remove the NS + return false + } + if ref.Namespace == "" { + return false + } + if r.Namespaced { + return false + } + return true +} + +func (k *K8sCluster) RemoveNamespaceIfNeeded(o *unstructured.Unstructured) { + ref := types2.RefFromObject(o) + if !k.ShouldRemoveNamespace(ref) { + return + } + o.SetNamespace("") +} + +func (k *K8sCluster) RemoveNamespaceFromRefIfNeeded(ref types2.ObjectRef) types2.ObjectRef { + if !k.ShouldRemoveNamespace(ref) { + return ref + } + ref.Namespace = "" + return ref +} + +func (k *K8sCluster) GetAllGroupVersions() ([]string, error) { + k.mutex.Lock() + defer k.mutex.Unlock() + + m := make(map[string]bool) + var l []string + + for gvk, _ := range k.allResources { + gv := gvk.GroupVersion().String() + if _, ok := m[gv]; !ok { + m[gv] = true + l = append(l, gv) + } + } + return l, nil +} + +func (k *K8sCluster) getGVRForGVK(gvk schema.GroupVersionKind) (*schema.GroupVersionResource, bool, error) { + k.mutex.Lock() + defer k.mutex.Unlock() + + ar, ok := k.allResources[gvk] + if !ok { + return nil, false, fmt.Errorf("resource not found for %s", gvk.String()) + } + + return &schema.GroupVersionResource{ + Group: ar.Group, + Version: ar.Version, + Resource: ar.Name, + }, ar.Namespaced, nil +} + +func (k *K8sCluster) withDynamicClientForGVK(gvk schema.GroupVersionKind, namespace string, cb func(r dynamic.ResourceInterface) error) error { + gvr, namespaced, err := k.getGVRForGVK(gvk) + if err != nil { + return err + } + + client := <-k.dynamicClientPool + defer func() { k.dynamicClientPool <- client }() + + if namespaced { + return cb(client.Resource(*gvr).Namespace(namespace)) + } else { + return cb(client.Resource(*gvr)) + } +} + +func (k *K8sCluster) withMetadataClientForGVK(gvk schema.GroupVersionKind, namespace string, cb func(r metadata.ResourceInterface) error) error { + gvr, namespaced, err := k.getGVRForGVK(gvk) + if err != nil { + return err + } + client := <-k.metadataClientPool + defer func() { k.metadataClientPool <- client }() + + if namespaced { + return cb(client.Resource(*gvr).Namespace(namespace)) + } else { + return cb(client.Resource(*gvr)) + } +} + +func (k *K8sCluster) buildLabelSelector(labels map[string]string) string { + ret := "" + + for k, v := range labels { + if len(ret) != 0 { + ret += "," + } + ret += fmt.Sprintf("%s=%s", k, v) + } + return ret +} + +func (k *K8sCluster) ListObjects(gvk schema.GroupVersionKind, namespace string) (*unstructured.UnstructuredList, error) { + var result *unstructured.UnstructuredList + + err := k.withDynamicClientForGVK(gvk, namespace, func(r dynamic.ResourceInterface) error { + o := v1.ListOptions{} + x, err := r.List(context.Background(), o) + if err != nil { + return err + } + result = x + return nil + }) + return result, err +} + +func (k *K8sCluster) ListObjectsMetadata(gvk schema.GroupVersionKind, namespace string, labels map[string]string) (*v1.PartialObjectMetadataList, error) { + var result *v1.PartialObjectMetadataList + + err := k.withMetadataClientForGVK(gvk, namespace, func(r metadata.ResourceInterface) error { + o := v1.ListOptions{ + LabelSelector: k.buildLabelSelector(labels), + } + x, err := r.List(context.Background(), o) + if err != nil { + return err + } + result = x + return nil + }) + return result, err +} + +func (k *K8sCluster) ListAllObjectsMetadata(verbs []string, namespace string, labels map[string]string) ([]*v1.PartialObjectMetadata, error) { + wp := utils.NewWorkerPoolWithErrors(8) + defer wp.StopWait(false) + + var ret []*v1.PartialObjectMetadata + var mutex sync.Mutex + + k.mutex.Lock() + for _, ar := range k.preferredResources { + foundVerb := false + for _, v := range verbs { + if utils.FindStrInSlice(ar.Verbs, v) != -1 { + foundVerb = true + break + } + } + if !foundVerb { + continue + } + gvk := schema.GroupVersionKind{ + Group: ar.Group, + Version: ar.Version, + Kind: ar.Kind, + } + wp.Submit(func() error { + lm, err := k.ListObjectsMetadata(gvk, namespace, labels) + if err != nil { + return err + } + mutex.Lock() + defer mutex.Unlock() + for _, o := range lm.Items { + o := o + o.SetGroupVersionKind(gvk) + ret = append(ret, &o) + } + return nil + }) + } + // release it early and let the goroutines finish (deadlocking otherwise) + k.mutex.Unlock() + + err := wp.StopWait(false) + if err != nil { + return nil, err + } + return ret, nil +} + +func (k *K8sCluster) GetSingleObject(ref types2.ObjectRef) (*unstructured.Unstructured, error) { + var result *unstructured.Unstructured + err := k.withDynamicClientForGVK(ref.GVK, ref.Namespace, func(r dynamic.ResourceInterface) error { + o := v1.GetOptions{} + x, err := r.Get(context.Background(), ref.Name, o) + if err != nil { + return err + } + result = x + return nil + }) + return result, err +} + +func (k *K8sCluster) GetObjectsByRefs(refs []types2.ObjectRef) ([]*unstructured.Unstructured, error) { + wp := utils.NewWorkerPoolWithErrors(32) + defer wp.StopWait(false) + + for _, ref_ := range refs { + ref := ref_ + wp.SubmitWithResult(func() (interface{}, error) { + o, err := k.GetSingleObject(ref) + if err != nil { + if errors.IsNotFound(err) { + return nil, nil + } + return nil, err + } + return o, nil + }) + } + err := wp.StopWait(false) + if err != nil { + return nil, err + } + + var results []*unstructured.Unstructured + for _, o := range wp.Results() { + results = append(results, o.(*unstructured.Unstructured)) + } + return results, nil +} + +type DeleteOptions struct { + ForceDryRun bool + NoWait bool + ErrorOnNotFound bool +} + +func (k *K8sCluster) DeleteSingleObject(ref types2.ObjectRef, options DeleteOptions) error { + dryRun := k.DryRun || options.ForceDryRun + + pp := v1.DeletePropagationForeground + o := v1.DeleteOptions{ + PropagationPolicy: &pp, + } + if dryRun { + o.DryRun = []string{"All"} + } + + return k.withDynamicClientForGVK(ref.GVK, ref.Namespace, func(r dynamic.ResourceInterface) error { + err := r.Delete(context.Background(), ref.Name, o) + if err != nil { + if errors.IsNotFound(err) { + return nil + } + return err + } + + if !dryRun && !options.NoWait { + err = k.waitForDeletedObject(r, ref) + if err != nil { + return err + } + } + return nil + }) +} + +func (k *K8sCluster) waitForDeletedObject(r dynamic.ResourceInterface, ref types2.ObjectRef) error { + for true { + o := v1.GetOptions{} + _, err := r.Get(context.Background(), ref.Name, o) + if err != nil { + if errors.IsNotFound(err) { + return nil + } + return err + } + } + return nil +} + +var v1_21, _ = goversion.NewVersion("1.21") +var v1_1000, _ = goversion.NewVersion("1.1000") + +func (k *K8sCluster) FixObjectForPatch(o *unstructured.Unstructured) *unstructured.Unstructured { + // A bug in versions < 1.20 cause errors when applying resources that have some fields omitted which have + // default values. We need to fix these resources. + // UPDATE even though https://github.com/kubernetes-sigs/structured-merge-diff/issues/130 says it's fixed, the + // issue is still present. + needsDefaultsFix := k.ServerVersion.LessThan(v1_21) || true + // TODO check when this is actually fixed (see https://github.com/kubernetes/kubernetes/issues/94275) + needsTypeConversionFix := k.ServerVersion.LessThan(v1_1000) + if !needsDefaultsFix && !needsTypeConversionFix { + return o + } + + o = utils.CopyUnstructured(o) + + fixPorts := func(p string) { + if !needsDefaultsFix { + return + } + + ports, found, _ := utils.NewMyJsonPathMust(p).GetFirstListOfMaps(o) + if !found { + return + } + + for _, port := range ports { + if _, ok := port["protocol"]; !ok { + port["protocol"] = "TCP" + } + } + } + + fixStringType := func(p string, k string) { + if !needsTypeConversionFix { + return + } + d, found, _ := utils.NewMyJsonPathMust(p).GetFirstMap(o) + if !found { + return + } + v, ok := d[k] + if !ok { + return + } + _, ok = v.(string) + if !ok { + d[k] = fmt.Sprintf("%v", v) + } + } + + fixContainer := func(p string) { + fixPorts(p + ".ports") + fixStringType(p+"resources.limits", "cpu") + fixStringType(p+"resources.requests", "cpu") + } + + fixContainers := func(p string) { + containers, found, _ := utils.NewMyJsonPathMust(p).GetFirstListOfMaps(o) + if !found { + return + } + for i, _ := range containers { + fixContainer(fmt.Sprintf("%s[%d]", p, i)) + } + } + + fixLimits := func(p string) { + limits, found, _ := utils.NewMyJsonPathMust(p).GetFirstListOfMaps(o) + if !found { + return + } + for i, _ := range limits { + fixStringType(fmt.Sprintf("%s[%d].default", p, i), "cpu") + fixStringType(fmt.Sprintf("%s[%d].defaultRequest", p, i), "cpu") + } + } + + fixContainers("spec.template.spec.containers") + fixPorts("spec.ports") + fixLimits("spec.limits") + + return o +} + +type PatchOptions struct { + ForceDryRun bool + ForceApply bool +} + +func (k *K8sCluster) PatchObject(o *unstructured.Unstructured, options PatchOptions) (*unstructured.Unstructured, error) { + dryRun := k.DryRun || options.ForceDryRun + ref := types2.RefFromObject(o) + log2 := log.WithField("ref", ref) + + data, err := o.MarshalJSON() + if err != nil { + return nil, err + } + + po := v1.PatchOptions{ + FieldManager: "kluctl", + } + if dryRun { + po.DryRun = []string{"All"} + } + if options.ForceApply { + po.Force = &options.ForceApply + } + + log2.Debugf("patching") + var result *unstructured.Unstructured + err = k.withDynamicClientForGVK(ref.GVK, ref.Namespace, func(r dynamic.ResourceInterface) error { + x, err := r.Patch(context.Background(), ref.Name, types.ApplyPatchType, data, po) + if err != nil { + return err + } + result = x + return nil + }) + return result, err +} + +type UpdateOptions struct { + ForceDryRun bool +} + +func (k *K8sCluster) UpdateObject(o *unstructured.Unstructured, options UpdateOptions) (*unstructured.Unstructured, error) { + dryRun := k.DryRun || options.ForceDryRun + ref := types2.RefFromObject(o) + log2 := log.WithField("ref", ref) + + uo := v1.UpdateOptions{ + FieldManager: "kluctl", + } + if dryRun { + uo.DryRun = []string{"All"} + } + + log2.Debugf("updating") + var result *unstructured.Unstructured + err := k.withDynamicClientForGVK(ref.GVK, ref.Namespace, func(r dynamic.ResourceInterface) error { + x, err := r.Update(context.Background(), o, uo) + if err != nil { + return err + } + result = x + return nil + }) + return result, err +} diff --git a/pkg/kluctl_project/git.go b/pkg/kluctl_project/git.go new file mode 100644 index 000000000..3016dd5b8 --- /dev/null +++ b/pkg/kluctl_project/git.go @@ -0,0 +1,223 @@ +package kluctl_project + +import ( + "fmt" + "github.com/codablock/kluctl/pkg/git" + git_url "github.com/codablock/kluctl/pkg/git/git-url" + types2 "github.com/codablock/kluctl/pkg/types" + "github.com/codablock/kluctl/pkg/utils" + "os" + "path" + "reflect" + "strings" + "sync" +) + +func (c *KluctlProjectContext) updateGitCaches() error { + var waitGroup sync.WaitGroup + var firstError error + var firstErrorLock sync.Mutex + + doError := func(err error) { + firstErrorLock.Lock() + defer firstErrorLock.Unlock() + if firstError == nil { + firstError = err + } + } + + doUpdateRepo := func(repo *git.MirroredGitRepo) error { + return repo.WithLock(func() error { + if !repo.HasUpdated() { + return repo.Update() + } + return nil + }) + } + doUpdateGitProject := func(u git_url.GitUrl) error { + mr, ok := c.mirroredRepos[u.NormalizedRepoKey()] + if ok { + return nil + } + mr, err := git.NewMirroredGitRepo(u) + if err != nil { + return err + } + c.mirroredRepos[u.NormalizedRepoKey()] = mr + + waitGroup.Add(1) + go func() { + defer waitGroup.Done() + err := doUpdateRepo(mr) + if err != nil { + doError(fmt.Errorf("failed to update git project %v: %v", u.String(), err)) + } + }() + + return nil + } + doUpdateExternalProject := func(p *types2.ExternalProject) error { + if p == nil { + return nil + } + return doUpdateGitProject(p.Project.Url) + } + + err := doUpdateExternalProject(c.config.Deployment) + if err != nil { + waitGroup.Wait() + return err + } + err = doUpdateExternalProject(c.config.SealedSecrets) + if err != nil { + waitGroup.Wait() + return err + } + for _, ep := range c.config.Clusters.Projects { + err = doUpdateExternalProject(&ep) + if err != nil { + waitGroup.Wait() + return err + } + } + + for _, target := range c.config.Targets { + if target.TargetConfig == nil || target.TargetConfig.Project == nil { + continue + } + + err = doUpdateGitProject(target.TargetConfig.Project.Url) + if err != nil { + waitGroup.Wait() + return err + } + } + + waitGroup.Wait() + return firstError +} + +type gitProjectInfo struct { + url git_url.GitUrl + ref string + commit string + dir string +} + +func (c *KluctlProjectContext) buildCloneDir(u git_url.GitUrl, ref string) (string, error) { + if ref == "" { + ref = "HEAD" + } + ref = strings.ReplaceAll(ref, "/", "-") + ref = strings.ReplaceAll(ref, "\\", "-") + baseName := path.Base(u.Path) + urlHash := utils.Sha256String(fmt.Sprintf("%s:%s", u.Host, u.Path)) + return path.Join(c.TmpDir, fmt.Sprintf("%s-%s", baseName, urlHash), ref), nil +} + +func (c *KluctlProjectContext) cloneGitProject(gitProject types2.ExternalProject, defaultGitSubDir string, doAddInvolvedRepo bool, doLock bool) (result gitProjectInfo, err error) { + err = os.MkdirAll(path.Join(c.TmpDir, "git"), 0o777) + if err != nil { + return + } + + targetDir, err := c.buildCloneDir(gitProject.Project.Url, gitProject.Project.Ref) + if err != nil { + return + } + + subDir := gitProject.Project.SubDir + if subDir == "" { + subDir = defaultGitSubDir + } + dir := targetDir + if subDir != "" { + dir = path.Join(dir, subDir) + } + + mr, ok := c.mirroredRepos[gitProject.Project.Url.NormalizedRepoKey()] + if !ok { + mr, err = git.NewMirroredGitRepo(gitProject.Project.Url) + if err != nil { + return + } + c.mirroredRepos[gitProject.Project.Url.NormalizedRepoKey()] = mr + err = mr.Lock() + if err != nil { + return + } + defer mr.Unlock() + } + + err = mr.MaybeWithLock(doLock, func() error { + if !mr.HasUpdated() { + err = mr.Update() + if err != nil { + return err + } + } + return mr.CloneProject(gitProject.Project.Ref, targetDir) + }) + if err != nil { + return + } + + ri, err := git.GetGitRepoInfo(targetDir) + if err != nil { + return + } + + result.url = gitProject.Project.Url + result.ref = ri.CheckedOutRef + result.commit = ri.CheckedOutCommit + result.dir = dir + + if doAddInvolvedRepo { + c.addInvolvedRepo(result.url, result.ref, map[string]string{ + result.ref: result.commit, + }) + } + + return +} + +func (c *KluctlProjectContext) localProject(dir string) gitProjectInfo { + return gitProjectInfo{ + dir: dir, + } +} + +func (c *KluctlProjectContext) cloneKluctlProject() (gitProjectInfo, error) { + if c.loadArgs.ProjectUrl == nil { + p, err := os.Getwd() + if err != nil { + return gitProjectInfo{}, err + } + return c.localProject(p), err + } + return c.cloneGitProject(types2.ExternalProject{ + Project: types2.GitProject{ + Url: *c.loadArgs.ProjectUrl, + Ref: c.loadArgs.ProjectRef, + }, + }, "", true, true) +} + +func (c *KluctlProjectContext) addInvolvedRepo(u git_url.GitUrl, refPattern string, refs map[string]string) { + repoKey := u.NormalizedRepoKey() + irs, _ := c.involvedRepos[repoKey] + e := &types2.InvolvedRepo{ + RefPattern: refPattern, + Refs: refs, + } + found := false + for _, ir := range irs { + if reflect.DeepEqual(ir, e) { + found = true + break + } + } + if !found { + c.involvedRepos[repoKey] = append(c.involvedRepos[repoKey], *e) + } +} diff --git a/pkg/kluctl_project/load.go b/pkg/kluctl_project/load.go new file mode 100644 index 000000000..3af4a34ab --- /dev/null +++ b/pkg/kluctl_project/load.go @@ -0,0 +1,186 @@ +package kluctl_project + +import ( + "fmt" + types2 "github.com/codablock/kluctl/pkg/types" + "github.com/codablock/kluctl/pkg/utils" + log "github.com/sirupsen/logrus" + "io/ioutil" + "os" + "path" +) + +func (c *KluctlProjectContext) mergeClustersDirs(mergedClustersDir string, clustersInfos []gitProjectInfo) error { + err := os.MkdirAll(mergedClustersDir, 0o777) + if err != nil { + return err + } + + for _, ci := range clustersInfos { + if !utils.IsDirectory(ci.dir) { + log.Warningf("Cluster dir '%s' does not exist", ci.dir) + continue + } + files, err := ioutil.ReadDir(ci.dir) + if err != nil { + return err + } + for _, fi := range files { + p := path.Join(ci.dir, fi.Name()) + if utils.IsFile(p) { + err = utils.CopyFile(p, path.Join(mergedClustersDir, fi.Name())) + if err != nil { + return err + } + } + } + } + return nil +} + +func (c *KluctlProjectContext) load(allowGit bool) error { + kluctlProjectInfo, err := c.cloneKluctlProject() + if err != nil { + return err + } + + configPath := c.loadArgs.ProjectConfig + if configPath == "" { + p := path.Join(kluctlProjectInfo.dir, ".kluctl.yml") + if utils.IsFile(p) { + configPath = p + } + } + + if configPath != "" { + err := types2.LoadKluctlProjectConfig(configPath, &c.config) + if err != nil { + return err + } + } + + if allowGit { + err := c.updateGitCaches() + if err != nil { + return err + } + } + + doClone := func(ep *types2.ExternalProject, defaultGitSubDir string, localDir string) (gitProjectInfo, error) { + if localDir != "" { + return c.localProject(localDir), nil + } + if ep == nil { + p := kluctlProjectInfo.dir + if defaultGitSubDir != "" { + p = path.Join(p, defaultGitSubDir) + } + return c.localProject(p), nil + } + if !allowGit { + return gitProjectInfo{}, fmt.Errorf("tried to load something from git while it was not allowed") + } + + return c.cloneGitProject(*ep, defaultGitSubDir, true, true) + } + + deploymentInfo, err := doClone(c.config.Deployment, "", c.loadArgs.LocalDeployment) + if err != nil { + return err + } + sealedSecretsInfo, err := doClone(c.config.SealedSecrets, ".sealed-secrets", c.loadArgs.LocalSealedSecrets) + if err != nil { + return err + } + var clustersInfos []gitProjectInfo + for _, ep := range c.config.Clusters.Projects { + info, err := doClone(&ep, "clusters", c.loadArgs.LocalClusters) + if err != nil { + return err + } + clustersInfos = append(clustersInfos, info) + } + + mergedClustersDir := path.Join(c.TmpDir, "merged-clusters") + err = c.mergeClustersDirs(mergedClustersDir, clustersInfos) + if err != nil { + return err + } + + c.ProjectDir = kluctlProjectInfo.dir + c.DeploymentDir = deploymentInfo.dir + c.ClustersDir = mergedClustersDir + c.SealedSecretsDir = sealedSecretsInfo.dir + + return nil +} + +func LoadKluctlProject(args LoadKluctlProjectArgs, cb func(ctx *KluctlProjectContext) error) error { + tmpDir, err := ioutil.TempDir(utils.GetTmpBaseDir(), "project-") + if err != nil { + return fmt.Errorf("creating temporary project directory failed: %w", err) + } + defer os.RemoveAll(tmpDir) + + if args.FromArchive != "" { + if args.ProjectUrl != nil || args.ProjectRef != "" || args.ProjectConfig != "" || args.LocalClusters != "" || args.LocalDeployment != "" || args.LocalSealedSecrets != "" { + return fmt.Errorf("--from-archive can not be combined with any other project related option") + } + project, err := loadKluctlProjectFromArchive(args.FromArchive, args.FromArchiveMetadata, tmpDir) + if err != nil { + return err + } + err = project.load(false) + if err != nil { + return err + } + return cb(project) + } else { + p := NewKluctlProjectContext(args, tmpDir) + err = p.load(true) + if err != nil { + return err + } + err = p.loadTargets() + if err != nil { + return err + } + return cb(p) + } +} + +func loadKluctlProjectFromArchive(fromArchive string, fromArchiveMetadata string, tmpDir string) (*KluctlProjectContext, error) { + var dir string + if utils.IsFile(fromArchiveMetadata) { + err := utils.ExtractTarGz(fromArchive, tmpDir) + if err != nil { + return nil, fmt.Errorf("failed to extract archive %v: %w", fromArchive, err) + } + dir = tmpDir + } else { + dir = fromArchive + } + + var metdataPath string + if fromArchiveMetadata != "" { + metdataPath = fromArchiveMetadata + } else { + metdataPath = path.Join(dir, "metadata.yml") + } + + metadata, err := types2.LoadArchiveMetadata(metdataPath) + if err != nil { + return nil, err + } + + p := NewKluctlProjectContext( + LoadKluctlProjectArgs{ + ProjectConfig: path.Join(dir, ".kluctl.yml"), + LocalClusters: path.Join(dir, "clusters"), + LocalDeployment: path.Join(dir, "deployment"), + LocalSealedSecrets: path.Join(dir, "sealed-secrets"), + }, dir) + p.involvedRepos = metadata.InvolvedRepos + p.targets = metadata.Targets + return p, nil +} diff --git a/pkg/kluctl_project/load_targets.go b/pkg/kluctl_project/load_targets.go new file mode 100644 index 000000000..79ad04dd1 --- /dev/null +++ b/pkg/kluctl_project/load_targets.go @@ -0,0 +1,319 @@ +package kluctl_project + +import ( + "fmt" + "github.com/codablock/kluctl/pkg/deployment" + git_url "github.com/codablock/kluctl/pkg/git/git-url" + "github.com/codablock/kluctl/pkg/types" + "github.com/codablock/kluctl/pkg/utils" + log "github.com/sirupsen/logrus" + "path" + "reflect" + "regexp" + "sync" +) + +type dynamicTargetInfo struct { + baseTarget *types.Target + dir string + gitProject *types.GitProject + ref *string + refPattern *string + defaultBranch string +} + +func (c *KluctlProjectContext) loadTargets() error { + targetNames := make(map[string]bool) + c.targets = nil + + var targetInfos []*dynamicTargetInfo + for _, baseTarget := range c.config.Targets { + l, err := c.prepareDynamicTargets(baseTarget) + if err != nil { + return err + } + targetInfos = append(targetInfos, l...) + } + + err := c.cloneDynamicTargets(targetInfos) + if err != nil { + return err + } + + for _, targetInfo := range targetInfos { + target, err := c.buildDynamicTarget(targetInfo) + if err != nil { + // Only fail if non-dynamic targets fail to load + if targetInfo.refPattern == nil { + return err + } + log.Warningf("Failed to load dynamic target config for project: %v", err) + continue + } + + target, err = c.renderTarget(target) + if err != nil { + return err + } + + if _, ok := targetNames[target.Name]; ok { + log.Warningf("Duplicate target %s", target.Name) + } else { + targetNames[target.Name] = true + c.targets = append(c.targets, target) + } + } + return nil +} + +func (c *KluctlProjectContext) renderTarget(target *types.Target) (*types.Target, error) { + // Try rendering the target multiple times, until all values can be rendered successfully. This allows the target + // to reference itself in complex ways. We'll also try loading the cluster vars in each iteration. + + var errors []error + curTarget := target + for i := 0; i < 10; i++ { + varsCtx := deployment.NewVarsCtx(c.JS) + err := varsCtx.UpdateChildFromStruct("target", curTarget) + if err != nil { + return nil, err + } + + cc, err := types.LoadClusterConfig(c.ClustersDir, target.Cluster) + if err == nil { + err = varsCtx.UpdateChildFromStruct("cluster", cc.Cluster) + if err != nil { + return nil, err + } + } + + var newTarget types.Target + err = c.JS.RenderStruct(&newTarget, curTarget, varsCtx.Vars) + if err == nil && reflect.DeepEqual(curTarget, &newTarget) { + return curTarget, nil + } + curTarget = &newTarget + } + if len(errors) != 0 { + return nil, errors[0] + } + return curTarget, nil +} + +func (c *KluctlProjectContext) prepareDynamicTargets(baseTarget types.Target) ([]*dynamicTargetInfo, error) { + if baseTarget.TargetConfig != nil && baseTarget.TargetConfig.Project != nil { + return c.prepareDynamicTargetsExternal(baseTarget) + } else { + return c.prepareDynamicTargetsSimple(baseTarget) + } +} + +func (c *KluctlProjectContext) prepareDynamicTargetsSimple(baseTarget types.Target) ([]*dynamicTargetInfo, error) { + if baseTarget.TargetConfig != nil { + if baseTarget.TargetConfig.Ref != nil || baseTarget.TargetConfig.RefPattern != nil { + return nil, fmt.Errorf("'ref' and/or 'refPattern' are not allowed for non-external dynamic targets") + } + } + dynamicTargets := []*dynamicTargetInfo{ + { + baseTarget: &baseTarget, + dir: c.ProjectDir, + }, + } + return dynamicTargets, nil +} + +func (c *KluctlProjectContext) prepareDynamicTargetsExternal(baseTarget types.Target) ([]*dynamicTargetInfo, error) { + mr, ok := c.mirroredRepos[baseTarget.TargetConfig.Project.Url.NormalizedRepoKey()] + if !ok { + return nil, fmt.Errorf("repo not found in mirroredRepos, this is unexpected and probably a bug") + } + + if baseTarget.TargetConfig.Ref != nil && baseTarget.TargetConfig.RefPattern != nil { + return nil, fmt.Errorf("'refPattern' and 'ref' can't be specified together") + } + + targetConfigRef := baseTarget.TargetConfig.Ref + refPattern := baseTarget.TargetConfig.RefPattern + + defaultBranch := mr.DefaultRef() + if defaultBranch == nil { + return nil, fmt.Errorf("git project %v seems to have no default branch", baseTarget.TargetConfig.Project.Url.String()) + } + *defaultBranch = (*defaultBranch)[len("refs/heads/"):] + + if baseTarget.TargetConfig.Ref == nil && baseTarget.TargetConfig.RefPattern == nil { + // use default branch of repo + targetConfigRef = defaultBranch + } + + refs := mr.RemoteRefHashesMap() + + if targetConfigRef != nil { + if _, ok := refs[fmt.Sprintf("refs/heads/%s", *targetConfigRef)]; !ok { + return nil, fmt.Errorf("git project %s has no ref %s", baseTarget.TargetConfig.Project.Url.String(), *targetConfigRef) + } + refPattern = targetConfigRef + } + + compiledRefPattern, err := regexp.Compile(fmt.Sprintf("^refs/heads/%s$", *refPattern)) + if err != nil { + return nil, fmt.Errorf("invalid ref pattern %s: %w", *refPattern, err) + } + + var dynamicTargets []*dynamicTargetInfo + for fullRefName := range refs { + if !compiledRefPattern.MatchString(fullRefName) { + continue + } + ref := fullRefName[len("refs/heads/"):] + + cloneDir, err := c.buildCloneDir(baseTarget.TargetConfig.Project.Url, ref) + if err != nil { + return nil, err + } + + dynamicTargets = append(dynamicTargets, &dynamicTargetInfo{ + baseTarget: &baseTarget, + dir: cloneDir, + gitProject: baseTarget.TargetConfig.Project, + ref: &ref, + refPattern: refPattern, + defaultBranch: *defaultBranch, + }) + } + return dynamicTargets, nil +} + +func (c *KluctlProjectContext) cloneDynamicTargets(dynamicTargets []*dynamicTargetInfo) error { + wp := utils.NewDebuggerAwareWorkerPool(8) + defer wp.StopWait(false) + + // lock all involved repos first + for _, mr := range c.mirroredRepos { + err := mr.Lock() + if err != nil { + return err + } + defer mr.Unlock() + } + + uniqueClones := make(map[string]interface{}) + var mutex sync.Mutex + + for _, targetInfo_ := range dynamicTargets { + targetInfo := targetInfo_ + + if targetInfo.gitProject == nil { + continue + } + if _, ok := uniqueClones[targetInfo.dir]; ok { + continue + } + uniqueClones[targetInfo.dir] = nil + + wp.Submit(func() error { + ep := types.ExternalProject{Project: *targetInfo.gitProject} + ep.Project.Ref = *targetInfo.ref + + gi, err := c.cloneGitProject(ep, "", false, false) + mutex.Lock() + defer mutex.Unlock() + if err != nil { + uniqueClones[targetInfo.dir] = err + } else { + uniqueClones[targetInfo.dir] = &gi + } + return nil + }) + } + err := wp.StopWait(false) + if err != nil { + return err + } + + refsByUrlAndPattern := make(map[string]map[string]map[string]string) + for _, targetInfo := range dynamicTargets { + if targetInfo.gitProject == nil { + continue + } + o, ok := uniqueClones[targetInfo.dir] + if !ok { + return fmt.Errorf("%s not in uniqueClones. This is probably a bug", targetInfo.dir) + } + err, ok := o.(error) + if ok { + return err + } + info := o.(*gitProjectInfo) + normalizedUrl := info.url.Normalize().String() + if _, ok := refsByUrlAndPattern[normalizedUrl]; !ok { + refsByUrlAndPattern[normalizedUrl] = make(map[string]map[string]string) + } + if _, ok := refsByUrlAndPattern[normalizedUrl][*targetInfo.refPattern]; !ok { + refsByUrlAndPattern[normalizedUrl][*targetInfo.refPattern] = make(map[string]string) + } + refsByUrlAndPattern[normalizedUrl][*targetInfo.refPattern][info.ref] = info.commit + } + + for url, refPatterns := range refsByUrlAndPattern { + for refPattern, refs := range refPatterns { + u, err := git_url.Parse(url) + if err != nil { + return err + } + c.addInvolvedRepo(*u, refPattern, refs) + } + } + + return nil +} + +func (c *KluctlProjectContext) buildDynamicTarget(targetInfo *dynamicTargetInfo) (*types.Target, error) { + var target types.Target + err := utils.DeepCopy(&target, targetInfo.baseTarget) + if err != nil { + return nil, err + } + if targetInfo.baseTarget.TargetConfig == nil { + return &target, nil + } + + configFile := "target-config.yml" + if targetInfo.baseTarget.TargetConfig.File != nil { + configFile = *targetInfo.baseTarget.TargetConfig.File + } + configPath := path.Join(targetInfo.dir, configFile) + if !utils.IsFile(configPath) { + return nil, fmt.Errorf("no target config file with name %s found in target", configFile) + } + + var targetConfig types.TargetConfig + err = types.LoadTargetConfig(configPath, &targetConfig) + if err != nil { + return nil, err + } + + // check and merge args + err = utils.NewObjectIterator(targetConfig.Args).IterateLeafs(func(it *utils.ObjectIterator) error { + strValue := fmt.Sprintf("%v", it.Value()) + err := c.CheckDynamicArg(&target, it.JsonPath(), strValue) + if err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + utils.MergeObject(target.Args, targetConfig.Args) + + // We prepend the dynamic images to ensure they get higher priority later + target.Images = append(targetConfig.Images, target.Images...) + + if targetInfo.ref != nil { + target.TargetConfig.Ref = targetInfo.ref + } + + return &target, nil +} diff --git a/pkg/kluctl_project/project.go b/pkg/kluctl_project/project.go new file mode 100644 index 000000000..56e4df9dd --- /dev/null +++ b/pkg/kluctl_project/project.go @@ -0,0 +1,94 @@ +package kluctl_project + +import ( + "fmt" + "github.com/codablock/kluctl/pkg/git" + git_url "github.com/codablock/kluctl/pkg/git/git-url" + "github.com/codablock/kluctl/pkg/jinja2_server" + "github.com/codablock/kluctl/pkg/types" + "regexp" +) + +type LoadKluctlProjectArgs struct { + ProjectUrl *git_url.GitUrl + ProjectRef string + ProjectConfig string + LocalClusters string + LocalDeployment string + LocalSealedSecrets string + FromArchive string + FromArchiveMetadata string + + JS *jinja2_server.Jinja2Server +} + +type KluctlProjectContext struct { + loadArgs LoadKluctlProjectArgs + + TmpDir string + config types.KluctlProject + + ProjectDir string + DeploymentDir string + ClustersDir string + SealedSecretsDir string + + involvedRepos map[string][]types.InvolvedRepo + targets []*types.Target + + mirroredRepos map[string]*git.MirroredGitRepo + + JS *jinja2_server.Jinja2Server +} + +func NewKluctlProjectContext(loadArgs LoadKluctlProjectArgs, tmpDir string) *KluctlProjectContext { + o := &KluctlProjectContext{ + loadArgs: loadArgs, + TmpDir: tmpDir, + involvedRepos: make(map[string][]types.InvolvedRepo), + mirroredRepos: make(map[string]*git.MirroredGitRepo), + JS: loadArgs.JS, + } + return o +} + +func (c *KluctlProjectContext) FindTarget(name string) (*types.Target, error) { + for _, target := range c.targets { + if target.Name == name { + return target, nil + } + } + return nil, fmt.Errorf("target %s not existent in kluctl project config", name) +} + +func (c *KluctlProjectContext) LoadClusterConfig(clusterName string) (*types.ClusterConfig, error) { + return types.LoadClusterConfig(c.ClustersDir, clusterName) +} + +func (c *KluctlProjectContext) CheckDynamicArg(target *types.Target, argName string, argValue string) error { + var dynArg *types.DynamicArg + for _, x := range target.DynamicArgs { + if x.Name == argName { + dynArg = &x + break + } + } + if dynArg == nil { + return fmt.Errorf("dynamic argument %s is not allowed for target", argName) + } + + argPattern := ".*" + if dynArg.Pattern != nil { + argPattern = *dynArg.Pattern + } + argPattern = fmt.Sprintf("^%s$", argPattern) + + m, err := regexp.MatchString(argPattern, argValue) + if err != nil { + return err + } + if !m { + return fmt.Errorf("dynamic argument %s does not match required pattern '%s", argName, argPattern) + } + return nil +} diff --git a/pkg/types/cluster_config.go b/pkg/types/cluster_config.go new file mode 100644 index 000000000..a11d346d9 --- /dev/null +++ b/pkg/types/cluster_config.go @@ -0,0 +1,47 @@ +package types + +import ( + "fmt" + "github.com/codablock/kluctl/pkg/utils" + "gopkg.in/yaml.v3" + "io/ioutil" + "path" +) + +type ClusterConfig2 struct { + Name string `yaml:"name" validate:"required"` + Context string `yaml:"context" validate:"required"` + Vars map[string]interface{} `yaml:",inline"` +} + +type ClusterConfig struct { + Cluster ClusterConfig2 `yaml:"cluster"` +} + +func LoadClusterConfig(clusterDir string, clusterName string) (*ClusterConfig, error) { + if clusterName == "" { + return nil, fmt.Errorf("cluster name must be specified") + } + + p := path.Join(clusterDir, fmt.Sprintf("%s.yml", clusterName)) + if !utils.IsFile(p) { + return nil, fmt.Errorf("cluster config for %s not found", clusterName) + } + + b, err := ioutil.ReadFile(p) + if err != nil { + return nil, err + } + + var config ClusterConfig + err = yaml.Unmarshal(b, &config) + if err != nil { + return nil, err + } + + if config.Cluster.Name != clusterName { + return nil, fmt.Errorf("cluster name in config (%s) does not match requested cluster name %s", config.Cluster.Name, clusterName) + } + + return &config, nil +} diff --git a/pkg/types/command_result.go b/pkg/types/command_result.go new file mode 100644 index 000000000..e8c107cba --- /dev/null +++ b/pkg/types/command_result.go @@ -0,0 +1,47 @@ +package types + +import ( + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +type Change struct { + Type string `yaml:"type" validate:"required"` + JsonPath string `yaml:"jsonPath" validate:"required"` + OldValue interface{} `yaml:"oldValue,omitempty"` + NewValue interface{} `yaml:"newValue,omitempty"` + UnifiedDiff string `yaml:"unifiedDiff,omitempty"` +} + +type ChangedObject struct { + NewObject *unstructured.Unstructured `yaml:"newObject,omitempty"` + OldObject *unstructured.Unstructured `yaml:"oldObject,omitempty"` + Changes []Change `yaml:"changes,omitempty"` +} + +type DeploymentError struct { + Ref ObjectRef `yaml:"ref"` + Error string `yaml:"error"` +} + +type CommandResult struct { + NewObjects []*unstructured.Unstructured `yaml:"newObjects,omitempty"` + ChangedObjects []*ChangedObject `yaml:"changedObjects,omitempty"` + HookObjects []*unstructured.Unstructured `yaml:"hook-objects,omitempty"` + OrphanObjects []ObjectRef `yaml:"orphanObjects,omitempty"` + DeletedObjects []ObjectRef `yaml:"deletedObjects,omitempty"` + Errors []DeploymentError `yaml:"errors,omitempty"` + Warnings []DeploymentError `yaml:"warnings,omitempty"` + SeenImages []FixedImage `yaml:"seenImages,omitempty"` +} + +type ValidateResultEntry struct { + Ref ObjectRef `yaml:"ref"` + Message string `yaml:"message"` +} + +type ValidateResult struct { + Ready bool `yaml:"ref"` + Warnings []DeploymentError `yaml:"warnings,omitempty"` + Errors []DeploymentError `yaml:"errors,omitempty"` + Results []ValidateResultEntry `yaml:"results,omitempty"` +} diff --git a/pkg/types/deployment.go b/pkg/types/deployment.go new file mode 100644 index 000000000..cf94133f2 --- /dev/null +++ b/pkg/types/deployment.go @@ -0,0 +1,132 @@ +package types + +import ( + "fmt" + "github.com/codablock/kluctl/pkg/utils" + "github.com/go-playground/validator/v10" + "gopkg.in/yaml.v3" +) + +type BaseItemConfig struct { + Path *string `yaml:"path,omitempty"` + Tags []string `yaml:"tags,omitempty"` + Barrier *bool `yaml:"barrier,omitempty"` + Vars []*VarsListItem `yaml:"vars,omitempty"` +} + +type DeploymentItemConfig struct { + BaseItemConfig `yaml:"base_item_config,inline"` + SkipDeleteIfTags *bool `yaml:"skipDeleteIfTags,omitempty"` + OnlyRender *bool `yaml:"onlyRender,omitempty"` + AlwaysDeploy *bool `yaml:"alwaysDeploy,omitempty"` +} + +type IncludeItemConfig struct { + BaseItemConfig `yaml:"base_item_config,inline"` +} + +type DeploymentArg struct { + Name string `yaml:"name" validate:"required"` + Default interface{} `yaml:"default,omitempty"` +} + +type VarsListItemClusterConfigMapOrSecret struct { + Name string `yaml:"name" validate:"required"` + Namespace string `yaml:"namespace,omitempty"` + Key string `yaml:"key" validate:"required"` +} + +type VarsListItem struct { + Values *map[string]interface{} `yaml:"values,omitempty"` + File *string `yaml:"file,omitempty"` + ClusterConfigMap *VarsListItemClusterConfigMapOrSecret `yaml:"clusterConfigMap,omitempty"` + ClusterSecret *VarsListItemClusterConfigMapOrSecret `yaml:"clusterSecret,omitempty"` +} + +func ValidateVarsListItem(sl validator.StructLevel) { + s := sl.Current().Interface().(VarsListItem) + count := 0 + if s.Values != nil { + count += 1 + } + if s.File != nil { + count += 1 + } + if s.ClusterConfigMap != nil { + count += 1 + } + if s.ClusterSecret != nil { + count += 1 + } + if count == 0 { + sl.ReportError(s, "self", "self", "invalidvars", "unknown vars type") + } else if count != 1 { + sl.ReportError(s, "self", "self", "invalidvars", "more then one vars type") + } +} + +type SealedSecretsConfig struct { + OutputPattern *string `yaml:"outputPattern,omitempty"` +} + +type SingleStringOrList []string + +func (s *SingleStringOrList) UnmarshalYAML(value *yaml.Node) error { + var single string + if err := value.Decode(&single); err == nil { + // it's a single project + *s = []string{single} + return nil + } + // try as array + var arr []string + if err := value.Decode(&arr); err != nil { + return err + } + *s = arr + return nil +} + +type IgnoreForDiffItemConfig struct { + FieldPath SingleStringOrList `yaml:"fieldPath" validate:"required"` + Group *string `yaml:"group,omitempty"` + Kind *string `yaml:"kind,omitempty"` + Name *string `yaml:"name,omitempty"` + Namespace *string `yaml:"namespace,omitempty"` +} + +type DeploymentProjectConfig struct { + Args []*DeploymentArg `yaml:"args,omitempty"` + Vars []*VarsListItem `yaml:"vars,omitempty"` + SealedSecrets *SealedSecretsConfig `yaml:"sealedSecrets,omitempty"` + + // Obsolete + DeploymentItems []*DeploymentItemConfig `yaml:"deploymentItems,omitempty"` + KustomizeDirs []*DeploymentItemConfig `yaml:"kustomizeDirs,omitempty"` + + Includes []*IncludeItemConfig `yaml:"includes,omitempty"` + + CommonLabels map[string]string `yaml:"commonLabels,omitempty"` + DeleteByLabels map[string]string `yaml:"deleteByLabels,omitempty"` + OverrideNamespace *string `yaml:"overrideNamespace,omitempty"` + Tags []string `yaml:"tags,omitempty"` + + IgnoreForDiff []*IgnoreForDiffItemConfig `yaml:"ignoreForDiff,omitempty"` + TemplateExcludes []string `yaml:"TemplateExcludes,omitempty"` +} + +func LoadDeploymentProjectConfig(p string, o *DeploymentProjectConfig) error { + err := utils.ReadYamlFile(p, o) + if err != nil { + return err + } + err = validate.Struct(o) + if err != nil { + return fmt.Errorf("validation for %v failed: %w", p, err) + } + return nil +} + +func init() { + validate.RegisterStructValidation(ValidateVarsListItem, VarsListItem{}) +} diff --git a/pkg/types/git_project.go b/pkg/types/git_project.go new file mode 100644 index 000000000..ff6068646 --- /dev/null +++ b/pkg/types/git_project.go @@ -0,0 +1,53 @@ +package types + +import ( + git_url "github.com/codablock/kluctl/pkg/git/git-url" + "github.com/go-playground/validator/v10" + "gopkg.in/yaml.v3" + "strings" +) + +type GitProject struct { + Url git_url.GitUrl `yaml:"url" validate:"required"` + Ref string `yaml:"ref"` + SubDir string `yaml:"subDir"` +} + +func (gp *GitProject) UnmarshalYAML(value *yaml.Node) error { + if err := value.Decode(&gp.Url); err == nil { + // it's a simple string + return nil + } + type raw GitProject + return value.Decode((*raw)(gp)) +} + +func ValidateGitProject(sl validator.StructLevel) { + gp := sl.Current().Interface().(GitProject) + if strings.Index(gp.SubDir, "/") != -1 { + sl.ReportError(gp.SubDir, "subDir", "SubDir", "subdirinvalid", "/ is not allowed in subdir") + } +} + +type ExternalProject struct { + Project GitProject `yaml:"project"` +} + +type ExternalProjects struct { + Projects []ExternalProject +} + +func (gp *ExternalProjects) UnmarshalYAML(value *yaml.Node) error { + singleProject := ExternalProject{} + if err := value.Decode(&singleProject); err == nil { + // it's a single project + gp.Projects = []ExternalProject{singleProject} + return nil + } + // try as array + return value.Decode(gp.Projects) +} + +func init() { + validate.RegisterStructValidation(ValidateGitProject, GitProject{}) +} diff --git a/pkg/types/helm_chart.go b/pkg/types/helm_chart.go new file mode 100644 index 000000000..10df99131 --- /dev/null +++ b/pkg/types/helm_chart.go @@ -0,0 +1,33 @@ +package types + +import ( + "fmt" + "github.com/codablock/kluctl/pkg/utils" +) + +type HelmChartConfig2 struct { + Repo *string `yaml:"repo,omitempty"` + ChartName *string `yaml:"chartName,omitempty"` + ChartVersion *string `yaml:"chartVersion,omitempty"` + ReleaseName string `yaml:"releaseName"` + Namespace *string `yaml:"namespace,omitempty"` + Output string `yaml:"output"` + SkipCRDs *bool `yaml:"skipCRDs,omitempty"` +} + +type HelmChartConfig struct { + HelmChartConfig2 `yaml:"helmChart" validate:"required"` +} + +func LoadHelmChartConfig(p string) (*HelmChartConfig, error) { + var o HelmChartConfig + err := utils.ReadYamlFile(p, &o) + if err != nil { + return nil, err + } + err = validate.Struct(o) + if err != nil { + return nil, fmt.Errorf("validation for %v failed: %w", p, err) + } + return &o, nil +} diff --git a/pkg/types/kluctl_project.go b/pkg/types/kluctl_project.go new file mode 100644 index 000000000..9ff3921e7 --- /dev/null +++ b/pkg/types/kluctl_project.go @@ -0,0 +1,71 @@ +package types + +import ( + "fmt" + "github.com/codablock/kluctl/pkg/utils" +) + +type DynamicArg struct { + Name string `yaml:"name" validate:"required"` + Pattern *string `yaml:"pattern,omitempty"` +} + +type ExternalTargetConfig struct { + Project *GitProject `yaml:"project,omitempty"` + // Ref Branch/Tag to be used. Can't be combined with 'refPattern'. If 'branch' and 'branchPattern' are not used, 'branch' defaults to the default branch of targetConfig.project + Ref *string `yaml:"ref,omitempty"` + // RefPattern If set, multiple dynamic targets are created, each with 'ref' being set to the ref that matched the given pattern. + RefPattern *string `yaml:"refPattern,omitempty"` + // File defaults to 'target-config.yml' + File *string `yaml:"file,omitempty"` +} + +type SealingConfig struct { + // DynamicSealing Set this to false if you want to disable sealing for every dynamic target + DynamicSealing bool `yaml:"dynamicSealing,omitempty"` + Args map[string]interface{} `yaml:"args,omitempty"` + SecretSets []string `yaml:"secretSets,omitempty"` +} + +type Target struct { + Name string `yaml:"name" validate:"required"` + Cluster string `yaml:"cluster" validate:"required"` + Args map[string]interface{} `yaml:"args,omitempty"` + DynamicArgs []DynamicArg `yaml:"dynamicArgs,omitempty"` + TargetConfig *ExternalTargetConfig `yaml:"targetConfig"` + SealingConfig SealingConfig `yaml:"sealingConfig"` + Images []FixedImage `yaml:"images,omitempty"` +} + +type SecretSet struct { + Name string `yaml:"name" validate:"required"` + Sources []interface{} `yaml:"sources" validate:"required,gt=0,dive,required"` +} + +type SecretsConfig struct { + SecretSets []SecretSet `yaml:"secretSets,omitempty"` +} + +type KluctlProject struct { + Deployment *ExternalProject `yaml:"name,omitempty"` + SealedSecrets *ExternalProject `yaml:"sealedSecrets,omitempty"` + Clusters ExternalProjects `yaml:"clusters,omitempty"` + Targets []Target `yaml:"targets,omitempty"` + SecretsConfig *SecretsConfig `yaml:"secretsConfig,omitempty"` +} + +func LoadKluctlProjectConfig(p string, o *KluctlProject) error { + err := utils.ReadYamlFile(p, o) + if err != nil { + return err + } + err = validate.Struct(o) + if err != nil { + return fmt.Errorf("validation for %v failed: %w", p, err) + } + return nil +} + +func init() { + validate.RegisterStructValidation(ValidateSecretSource, SecretSource{}) +} diff --git a/pkg/types/metadata.go b/pkg/types/metadata.go new file mode 100644 index 000000000..477f2cdc4 --- /dev/null +++ b/pkg/types/metadata.go @@ -0,0 +1,20 @@ +package types + +import ( + "github.com/codablock/kluctl/pkg/utils" +) + +type InvolvedRepo struct { + RefPattern string `yaml:"refPattern"` + Refs map[string]string `yaml:"refs"` +} + +type ArchiveMetadata struct { + InvolvedRepos map[string][]InvolvedRepo `yaml:"involvedRepo"` + Targets []*Target `yaml:"targets"` +} + +func LoadArchiveMetadata(p string) (*ArchiveMetadata, error) { + o := &ArchiveMetadata{} + return o, utils.ReadYamlFile(p, o) +} diff --git a/pkg/types/ref.go b/pkg/types/ref.go new file mode 100644 index 000000000..543334a25 --- /dev/null +++ b/pkg/types/ref.go @@ -0,0 +1,50 @@ +package types + +import ( + "fmt" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +type ObjectRef struct { + GVK schema.GroupVersionKind + Name string + Namespace string +} + +func (r ObjectRef) String() string { + if r.Namespace != "" { + return fmt.Sprintf("%s/%s/%s", r.Namespace, r.GVK.Kind, r.Name) + } else { + return fmt.Sprintf("%s/%s", r.GVK.Kind, r.Name) + } +} + +func NewObjectRef(group string, version string, kind string, name string, namespace string) ObjectRef { + return ObjectRef{ + GVK: schema.GroupVersionKind{ + Group: group, + Version: version, + Kind: kind, + }, + Name: name, + Namespace: namespace, + } +} + +func RefFromObject(o *unstructured.Unstructured) ObjectRef { + return ObjectRef{ + GVK: o.GroupVersionKind(), + Name: o.GetName(), + Namespace: o.GetNamespace(), + } +} + +func RefFromPartialObject(o *v1.PartialObjectMetadata) ObjectRef { + return ObjectRef{ + GVK: o.GroupVersionKind(), + Name: o.GetName(), + Namespace: o.GetNamespace(), + } +} diff --git a/pkg/types/secrets_source.go b/pkg/types/secrets_source.go new file mode 100644 index 000000000..70ea5ad00 --- /dev/null +++ b/pkg/types/secrets_source.go @@ -0,0 +1,33 @@ +package types + +import "github.com/go-playground/validator/v10" + +type SecretSourceAwsSecretsManager struct { + // Name or ARN of the secret. In case a name is given, the region must be specified as well + SecretName string `yaml:"path" validate:"required"` + // The aws region + Region string `yaml:"path,omitempty"` + // AWS credentials profile to use. The AWS_PROFILE environemnt variables will take predence in case it is also set + Profile string `yaml:"path,omitempty"` +} + +type SecretSource struct { + Path *string `yaml:"path,omitempty"` + AwsSecretsManager *SecretSourceAwsSecretsManager `yaml:"awsSecretsManager,omitempty"` +} + +func ValidateSecretSource(sl validator.StructLevel) { + s := sl.Current().Interface().(SecretSource) + count := 0 + if s.Path != nil { + count += 1 + } + if s.AwsSecretsManager != nil { + count += 1 + } + if count == 0 { + sl.ReportError(s, "self", "self", "invalidsource", "unknown secret source type") + } else if count != 1 { + sl.ReportError(s, "self", "self", "invalidsource", "more then one secret source type") + } +} diff --git a/pkg/types/target_config.go b/pkg/types/target_config.go new file mode 100644 index 000000000..605d59abc --- /dev/null +++ b/pkg/types/target_config.go @@ -0,0 +1,52 @@ +package types + +import ( + "fmt" + "github.com/codablock/kluctl/pkg/utils" +) + +type FixedImage struct { + Image string `yaml:"image" validate:"required"` + ResultImage string `yaml:"resultImage" validate:"required"` + DeployedImage *string `yaml:"deployedImage,omitempty"` + RegistryImage *string `yaml:"registryImage,omitempty"` + Namespace *string `yaml:"namespace,omitempty"` + Deployment *string `yaml:"deployment,omitempty"` + Container *string `yaml:"container,omitempty"` + VersionFilter *string `yaml:"versionFilter,omitempty"` + DeployTags []string `yaml:"deployTags,omitempty"` + DeploymentDir *string `yaml:"deploymentDir,omitempty"` +} + +type FixedImagesConfig struct { + Images []FixedImage `yaml:"images,omitempty"` +} + +type TargetConfig struct { + FixedImagesConfig `yaml:"fixed_images_config,inline"` + Args map[string]interface{} `yaml:"args,omitempty"` +} + +func LoadFixedImagesConfig(p string, o *FixedImagesConfig) error { + err := utils.ReadYamlFile(p, o) + if err != nil { + return err + } + err = validate.Struct(o) + if err != nil { + return fmt.Errorf("validation for %v failed: %w", p, err) + } + return nil +} + +func LoadTargetConfig(p string, o *TargetConfig) error { + err := utils.ReadYamlFile(p, o) + if err != nil { + return err + } + err = validate.Struct(o) + if err != nil { + return fmt.Errorf("validation for %v failed: %w", p, err) + } + return nil +} diff --git a/pkg/types/validator.go b/pkg/types/validator.go new file mode 100644 index 000000000..5063a72bf --- /dev/null +++ b/pkg/types/validator.go @@ -0,0 +1,7 @@ +package types + +import "github.com/go-playground/validator/v10" + +var ( + validate = validator.New() +) diff --git a/pkg/utils/debugger.go b/pkg/utils/debugger.go new file mode 100644 index 000000000..2800e1584 --- /dev/null +++ b/pkg/utils/debugger.go @@ -0,0 +1,24 @@ +package utils + +import ( + "os" + + "github.com/mitchellh/go-ps" +) + +func IsLaunchedByDebugger() bool { + pid := os.Getppid() + + // We loop in case there were intermediary processes like the gopls language server. + for pid != 0 { + switch p, err := ps.FindProcess(pid); { + case err != nil: + return false + case p.Executable() == "dlv": + return true + default: + pid = p.PPid() + } + } + return false +} diff --git a/pkg/utils/errorlist.go b/pkg/utils/errorlist.go new file mode 100644 index 000000000..aa35c5780 --- /dev/null +++ b/pkg/utils/errorlist.go @@ -0,0 +1,20 @@ +package utils + +type errorList struct { + errors []error +} + +func NewErrorList(errors []error) *errorList { + return &errorList{errors: errors} +} + +func (el *errorList) Error() string { + s := "" + for _, err := range el.errors { + if len(s) != 0 { + s += "; " + } + s += err.Error() + } + return s +} diff --git a/pkg/utils/fs.go b/pkg/utils/fs.go new file mode 100644 index 000000000..a40ed2c71 --- /dev/null +++ b/pkg/utils/fs.go @@ -0,0 +1,66 @@ +package utils + +import ( + "fmt" + "io" + "os" +) + +func Exists(path string) bool { + _, err := os.Stat(path) + if err != nil { + return false + } + return true +} + +func IsFile(path string) bool { + fileInfo, err := os.Stat(path) + if err != nil { + return false + } + + return fileInfo.Mode().IsRegular() +} + +func IsDirectory(path string) bool { + fileInfo, err := os.Stat(path) + if err != nil { + return false + } + + return fileInfo.IsDir() +} + +func Touch(path string) error { + f, err := os.Create(path) + if err != nil { + return fmt.Errorf("failed to touch %v: %w", path, err) + } + return f.Close() +} + +func CopyFile(src, dst string) error { + sourceFileStat, err := os.Stat(src) + if err != nil { + return err + } + + if !sourceFileStat.Mode().IsRegular() { + return fmt.Errorf("%s is not a regular file", src) + } + + source, err := os.Open(src) + if err != nil { + return err + } + defer source.Close() + + destination, err := os.Create(dst) + if err != nil { + return err + } + defer destination.Close() + _, err = io.Copy(destination, source) + return err +} diff --git a/pkg/utils/inclusion.go b/pkg/utils/inclusion.go new file mode 100644 index 000000000..63990cf7c --- /dev/null +++ b/pkg/utils/inclusion.go @@ -0,0 +1,68 @@ +package utils + +type InclusionEntry struct { + Type string + Value string +} + +type Inclusion struct { + includes map[InclusionEntry]bool + excludes map[InclusionEntry]bool +} + +func NewInclusion() *Inclusion { + return &Inclusion{ + includes: map[InclusionEntry]bool{}, + excludes: map[InclusionEntry]bool{}, + } +} + +func (inc *Inclusion) AddInclude(typ string, value string) { + inc.includes[InclusionEntry{typ, value}] = true +} + +func (inc *Inclusion) AddExclude(typ string, value string) { + inc.excludes[InclusionEntry{typ, value}] = true +} + +func (inc *Inclusion) HasType(typ string) bool { + for e, _ := range inc.includes { + if e.Type == typ { + return true + } + } + for e, _ := range inc.excludes { + if e.Type == typ { + return true + } + } + return false +} + +func (inc *Inclusion) checkList(l []InclusionEntry, m map[InclusionEntry]bool) bool { + for _, e := range l { + if _, ok := m[e]; ok { + return true + } + } + return false +} + +func (inc *Inclusion) CheckIncluded(l []InclusionEntry, excludeIfNotIncluded bool) bool { + if len(inc.includes) == 0 && len(inc.excludes) == 0 { + return true + } + + isIncluded := inc.checkList(l, inc.includes) + isExcluded := inc.checkList(l, inc.excludes) + + if excludeIfNotIncluded { + if !isIncluded { + return false + } + } + if isExcluded { + return false + } + return len(inc.includes) == 0 || isIncluded +} diff --git a/pkg/utils/jsonpath.go b/pkg/utils/jsonpath.go new file mode 100644 index 000000000..31b9530e0 --- /dev/null +++ b/pkg/utils/jsonpath.go @@ -0,0 +1,124 @@ +package utils + +import ( + "fmt" + "github.com/ohler55/ojg/jp" + log "github.com/sirupsen/logrus" + "strings" +) + +func KeyListToJsonPath(keys []interface{}) string { + p := "" + for _, k := range keys { + if i, ok := k.(int); ok { + p = fmt.Sprintf("%s[%d]", p, i) + } else if s, ok := k.(string); ok { + if isAlpha.MatchString(s) { + if p != "" { + p += "." + } + p += s + } else { + if p == "" { + p = "$" + } + if strings.Index(s, "\"") != -1 { + p = fmt.Sprintf("%s['%s']", p, s) + } else { + p = fmt.Sprintf("%s[\"%s\"]", p, s) + } + } + } else { + if p == "" { + p = "$" + } + p = fmt.Sprintf("%s[%v]", p, k) + } + } + return p +} + +type MyJsonPath struct { + exp jp.Expr +} + +func NewMyJsonPath(p string) (*MyJsonPath, error) { + exp, err := jp.ParseString(p) + if err != nil { + return nil, err + } + return &MyJsonPath{ + exp: exp, + }, nil +} + +func NewMyJsonPathMust(p string) *MyJsonPath { + j, err := NewMyJsonPath(p) + if err != nil { + log.Fatal(err) + } + return j +} + +func (j *MyJsonPath) ListMatchingFields(o map[string]interface{}) ([][]interface{}, error) { + var ret [][]interface{} + + o = CopyObject(o) + magic := struct{}{} + + err := j.exp.Set(o, magic) + if err != nil { + return nil, err + } + + _ = NewObjectIterator(o).IterateLeafs(func(it *ObjectIterator) error { + if it.Value() == magic { + var c []interface{} + c = append(c, it.KeyPath()...) + ret = append(ret, c) + } + return nil + }) + + return ret, nil +} + +func (j *MyJsonPath) Get(o interface{}) []interface{} { + return j.exp.Get(o) +} + +func (j *MyJsonPath) GetFirst(o interface{}) (interface{}, bool) { + l := j.Get(o) + if len(l) == 0 { + return nil, false + } + return l[0], true +} + +func (j *MyJsonPath) GetFirstMap(o interface{}) (map[string]interface{}, bool, error) { + o, found := j.GetFirst(o) + if !found { + return nil, false, nil + } + m, ok := o.(map[string]interface{}) + if !ok { + return nil, false, fmt.Errorf("child is not a map") + } + return m, true, nil +} + +func (j *MyJsonPath) GetFirstListOfMaps(o interface{}) ([]map[string]interface{}, bool, error) { + o, found := j.GetFirst(o) + if !found { + return nil, false, nil + } + m, ok := o.([]map[string]interface{}) + if !ok { + return nil, false, fmt.Errorf("child is not a list of maps") + } + return m, true, nil +} + +func (j *MyJsonPath) Del(o interface{}) error { + return j.exp.Del(o) +} diff --git a/pkg/utils/object_iterator.go b/pkg/utils/object_iterator.go new file mode 100644 index 000000000..0b8d82861 --- /dev/null +++ b/pkg/utils/object_iterator.go @@ -0,0 +1,106 @@ +package utils + +import ( + log "github.com/sirupsen/logrus" + "regexp" +) + +type ObjectIteratorFunc func(it *ObjectIterator) error +type ObjectIterator struct { + path []interface{} + keys []interface{} + cb ObjectIteratorFunc +} + +func NewObjectIterator(o map[string]interface{}) *ObjectIterator { + return &ObjectIterator{ + path: []interface{}{o}, + } +} + +func (it *ObjectIterator) KeyPath() []interface{} { + return it.keys +} + +func (it *ObjectIterator) Key() interface{} { + if len(it.keys) == 0 { + return nil + } + return it.keys[len(it.keys)-1] +} + +func (it *ObjectIterator) Parent() interface{} { + if len(it.path) < 2 { + return nil + } + return it.path[len(it.path)-2] +} + +func (it *ObjectIterator) Value() interface{} { + return it.path[len(it.path)-1] +} + +func (it *ObjectIterator) SetValue(v interface{}) error { + return SetChild(it.Parent(), it.Key(), v) +} + +var isAlpha = regexp.MustCompile(`^[A-Za-z]+$`) + +func (it *ObjectIterator) JsonPath() string { + return KeyListToJsonPath(it.keys) +} + +func (it *ObjectIterator) IterateLeafs(cb ObjectIteratorFunc) error { + it.cb = cb + return it.iterateInterface() +} + +func (it *ObjectIterator) iterateInterface() error { + value := it.Value() + if _, ok := value.(map[string]interface{}); ok { + return it.iterateMap() + } else if _, ok := value.([]interface{}); ok { + return it.iterateList() + } else { + return it.cb(it) + } +} + +func (it *ObjectIterator) iterateMap() error { + m, ok := it.Value().(map[string]interface{}) + if !ok { + log.Fatalf("!ok") + } + + for k, v := range m { + it.path = append(it.path, v) + it.keys = append(it.keys, k) + err := it.iterateInterface() + it.path = it.path[:len(it.path)-1] + it.keys = it.keys[:len(it.keys)-1] + + if err != nil { + return err + } + } + return nil +} + +func (it *ObjectIterator) iterateList() error { + l, ok := it.Value().([]interface{}) + if !ok { + log.Fatalf("!ok") + } + + for i, e := range l { + it.path = append(it.path, e) + it.keys = append(it.keys, i) + err := it.iterateInterface() + it.path = it.path[:len(it.path)-1] + it.keys = it.keys[:len(it.keys)-1] + if err != nil { + return err + } + } + return nil +} diff --git a/pkg/utils/ordered_map.go b/pkg/utils/ordered_map.go new file mode 100644 index 000000000..ab845989e --- /dev/null +++ b/pkg/utils/ordered_map.go @@ -0,0 +1,53 @@ +package utils + +type orderedMapEntry struct { + k string + v interface{} +} + +type OrderedMap struct { + m map[string]int + l []orderedMapEntry +} + +func (s *OrderedMap) Set(k string, v interface{}) bool { + if s.m == nil { + s.m = map[string]int{k: 0} + } else { + if _, ok := s.m[k]; ok { + return false + } + s.m[k] = len(s.l) + } + s.l = append(s.l, orderedMapEntry{k: k, v: v}) + return true +} + +func (s *OrderedMap) Has(v string) bool { + _, ok := s.m[v] + return ok +} + +func (s *OrderedMap) Get(v string) (interface{}, bool) { + i, ok := s.m[v] + if !ok { + return nil, ok + } + return s.l[i].v, true +} + +func (s *OrderedMap) ListKeys() []string { + l := make([]string, len(s.l)) + for i, e := range s.l { + l[i] = e.k + } + return l +} + +func (s *OrderedMap) ListValues() []interface{} { + l := make([]interface{}, len(s.l)) + for i, e := range s.l { + l[i] = e.v + } + return l +} diff --git a/pkg/utils/prettytable.go b/pkg/utils/prettytable.go new file mode 100644 index 000000000..ce9bcdaa1 --- /dev/null +++ b/pkg/utils/prettytable.go @@ -0,0 +1,114 @@ +package utils + +import ( + "bytes" + "golang.org/x/crypto/ssh/terminal" + "strings" +) + +type Row []string + +type PrettyTable struct { + rows []Row +} + +func (t *PrettyTable) AddRow(c ...string) { + t.rows = append(t.rows, c) +} + +func (t *PrettyTable) Render(limitWidths []int) string { + cols := len(t.rows[0]) + + maxWidth := func(col int, maxW int) int { + w := 0 + for _, l := range t.rows { + if len(l[col]) > w { + w = len(l[col]) + } + if maxW != -1 { + if maxW < w { + w = maxW + } + } + } + return w + } + subStr := func(str string, s int, e int) string { + if s > len(str) { + s = len(str) + } + if e > len(str) { + e = len(str) + } + return str[s:e] + } + + widths := make([]int, cols) + widthSum := 0 + for i := 0; i < cols; i++ { + if i < len(limitWidths) { + widths[i] = maxWidth(i, limitWidths[i]) + widthSum += widths[i] + } else { + widths[i] = -1 + } + } + + if len(limitWidths) < cols { + tw, _, err := terminal.GetSize(0) + if err != nil { + tw = 80 + } + // last column should use all remaining space + widths[len(limitWidths)] = tw - widthSum - (cols-1)*3 - 4 + } + + hsep := "+-" + for i := 0; i < cols; i++ { + hsep += strings.Repeat("-", widths[i]) + if i != cols-1 { + hsep += "-+-" + } + } + hsep += "-+\n" + + buf := bytes.NewBuffer(nil) + buf.WriteString(hsep) + pos := make([]int, cols) + for _, l := range t.rows { + for i := 0; i < cols; i++ { + pos[i] = 0 + } + + for { + anyLess := false + for i := 0; i < cols; i++ { + if pos[i] < len(l[i]) { + anyLess = true + } + } + if !anyLess { + break + } + + buf.WriteString("| ") + for i := 0; i < cols; i++ { + x := subStr(l[i], pos[i], pos[i]+widths[i]) + newLine := strings.IndexRune(x, '\n') + if newLine != -1 { + x = x[:newLine] + pos[i] += 1 + } + pos[i] += len(x) + buf.WriteString(x) + buf.WriteString(strings.Repeat(" ", widths[i]-len(x))) + if i != cols-1 { + buf.WriteString(" | ") + } + } + buf.WriteString(" |\n") + } + buf.WriteString(hsep) + } + return buf.String() +} diff --git a/pkg/utils/tar.go b/pkg/utils/tar.go new file mode 100644 index 000000000..d9670af8f --- /dev/null +++ b/pkg/utils/tar.go @@ -0,0 +1,57 @@ +package utils + +import ( + "archive/tar" + "compress/gzip" + "fmt" + "io" + "os" + "path" +) + +func ExtractTarGz(tarGzPath string, targetPath string) error { + f, err := os.Open(tarGzPath) + if err != nil { + return fmt.Errorf("archive %v could not be opened: %w", tarGzPath, err) + } + defer f.Close() + + gz, err := gzip.NewReader(f) + if err != nil { + return fmt.Errorf("archive %v could not be opened: %w", tarGzPath, err) + } + defer gz.Close() + + tarReader := tar.NewReader(gz) + for true { + header, err := tarReader.Next() + + if err == io.EOF { + break + } + + if err != nil { + return fmt.Errorf("ExtractTarGz: Next() for %v failed: %w", tarGzPath, err) + } + + switch header.Typeflag { + case tar.TypeDir: + if err := os.Mkdir(path.Join(targetPath, header.Name), 0755); err != nil { + return fmt.Errorf("ExtractTarGz: Mkdir() for %v failed: %w", tarGzPath, err) + } + case tar.TypeReg: + outFile, err := os.Create(path.Join(targetPath, header.Name)) + if err != nil { + return fmt.Errorf("ExtractTarGz: Create() for %v failed: %w", tarGzPath, err) + } + _, err = io.Copy(outFile, tarReader) + _ = outFile.Close() + if err != nil { + return fmt.Errorf("ExtractTarGz: Copy() for %v failed: %w", tarGzPath, err) + } + default: + return fmt.Errorf("ExtractTarGz: uknown type for %v: %v in %v", tarGzPath, header.Typeflag, header.Name) + } + } + return nil +} diff --git a/pkg/utils/unstructured.go b/pkg/utils/unstructured.go new file mode 100644 index 000000000..cc6e632e2 --- /dev/null +++ b/pkg/utils/unstructured.go @@ -0,0 +1,165 @@ +package utils + +import ( + "fmt" + log "github.com/sirupsen/logrus" + "gopkg.in/yaml.v3" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +func CopyObject(a map[string]interface{}) map[string]interface{} { + var c map[string]interface{} + err := DeepCopy(&c, &a) + if err != nil { + log.Fatal(err) + } + return c +} + +func CopyUnstructured(u *unstructured.Unstructured) *unstructured.Unstructured { + return &unstructured.Unstructured{Object: CopyObject(u.Object)} +} + +func StructToObject(s interface{}) (map[string]interface{}, error) { + b, err := yaml.Marshal(s) + if err != nil { + return nil, err + } + + m := make(map[string]interface{}) + err = yaml.Unmarshal(b, m) + if err != nil { + return nil, err + } + return m, nil +} + +func ObjectToStruct(m map[string]interface{}, dst interface{}) error { + b, err := yaml.Marshal(m) + if err != nil { + return err + } + return yaml.Unmarshal(b, dst) +} + +func MergeObject(a map[string]interface{}, b map[string]interface{}) { + if b == nil { + b = map[string]interface{}{} + } + + for key := range b { + if _, ok := a[key]; ok { + adict, adictOk := a[key].(map[string]interface{}) + bdict, bdictOk := b[key].(map[string]interface{}) + if adictOk && bdictOk { + MergeObject(adict, bdict) + } else { + a[key] = b[key] + } + } else { + a[key] = b[key] + } + } +} + +func CopyMergeObjects(a map[string]interface{}, b map[string]interface{}) (map[string]interface{}, error) { + c := CopyObject(a) + MergeObject(c, b) + return c, nil +} + +func MergeStrMap(a map[string]string, b map[string]string) { + for k, v := range b { + a[k] = v + } +} + +func CopyMergeStrMap(a map[string]string, b map[string]string) map[string]string { + c := make(map[string]string) + MergeStrMap(c, a) + MergeStrMap(c, b) + return c +} + +func NestedMapSlice(o map[string]interface{}, fields ...string) ([]map[string]interface{}, bool, error) { + a, found, err := unstructured.NestedSlice(o, fields...) + if err != nil { + return nil, false, err + } + if !found { + return nil, false, nil + } + + var ret []map[string]interface{} + for _, x := range a { + x2, ok := x.(map[string]interface{}) + if !ok { + return nil, false, fmt.Errorf("nested value is not a slice of maps") + } + ret = append(ret, x2) + } + return ret, true, nil +} + +func NestedMapSliceNoErr(o map[string]interface{}, fields ...string) []map[string]interface{} { + l, found, err := NestedMapSlice(o, fields...) + if !found || err != nil { + return nil + } + return l +} + +func GetChild(parent interface{}, key interface{}) (interface{}, bool, error) { + if m, ok := parent.(map[string]interface{}); ok { + keyStr, ok := key.(string) + if !ok { + return nil, false, fmt.Errorf("key is not a string") + } + v, found := m[keyStr] + return v, found, nil + } else if l, ok := parent.([]interface{}); ok { + keyInt, ok := key.(int) + if !ok { + return nil, false, fmt.Errorf("key is not an int") + } + if keyInt < 0 || keyInt >= len(l) { + return nil, false, fmt.Errorf("index out of bounds") + } + v := l[keyInt] + return v, true, nil + } + return nil, false, fmt.Errorf("unknown parent type") +} + +func GetNestedChild(o interface{}, keys ...interface{}) (interface{}, bool, error) { + for _, k := range keys { + v, found, err := GetChild(o, k) + if err != nil { + return nil, false, err + } + if !found { + return nil, false, nil + } + o = v + } + return o, true, nil +} + +func SetChild(parent interface{}, key interface{}, value interface{}) error { + if m, ok := parent.(map[string]interface{}); ok { + keyStr, ok := key.(string) + if !ok { + return fmt.Errorf("key is not a string") + } + m[keyStr] = value + return nil + } else if l, ok := parent.([]interface{}); ok { + keyInt, ok := key.(int) + if !ok { + return fmt.Errorf("key is not an int") + } + l[keyInt] = value + return nil + } + return fmt.Errorf("unknown parent type") +} diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go new file mode 100644 index 000000000..da51e2942 --- /dev/null +++ b/pkg/utils/utils.go @@ -0,0 +1,34 @@ +package utils + +import ( + "crypto/sha256" + "encoding/hex" + "github.com/jinzhu/copier" + "os" + "path" +) + +func GetTmpBaseDir() string { + dir := path.Join(os.TempDir(), "kluctl") + return dir +} + +func Sha256String(data string) string { + h := sha256.Sum256([]byte(data)) + return hex.EncodeToString(h[:]) +} + +func DeepCopy(dst interface{}, src interface{}) error { + return copier.CopyWithOption(dst, src, copier.Option{ + DeepCopy: true, + }) +} + +func FindStrInSlice(a []string, s string) int { + for i, v := range a { + if v == s { + return i + } + } + return -1 +} diff --git a/pkg/utils/workerpool.go b/pkg/utils/workerpool.go new file mode 100644 index 000000000..5621723e6 --- /dev/null +++ b/pkg/utils/workerpool.go @@ -0,0 +1,84 @@ +package utils + +import ( + "github.com/gammazero/workerpool" + "os" + "strconv" + "sync" +) + +type WorkerPoolWithErrors struct { + maxWorkers int + pool *workerpool.WorkerPool + errors []error + results []interface{} + mutex sync.Mutex +} + +func NewWorkerPoolWithErrors(maxWorkers int) *WorkerPoolWithErrors { + return &WorkerPoolWithErrors{ + maxWorkers: maxWorkers, + pool: workerpool.New(maxWorkers), + } +} + +func NewDebuggerAwareWorkerPool(maxWorkers int) *WorkerPoolWithErrors { + if IsLaunchedByDebugger() { + ignoreDebuggerStr, _ := os.LookupEnv("KLUCTL_IGNORE_DEBUGGER") + ignoreDebugger, _ := strconv.ParseBool(ignoreDebuggerStr) + if !ignoreDebugger { + maxWorkers = 1 + } + } + return NewWorkerPoolWithErrors(maxWorkers) +} + +func (wp *WorkerPoolWithErrors) Submit(cb func() error) { + wp.SubmitWithResult(func() (interface{}, error) { + err := cb() + return nil, err + }) +} + +func (wp *WorkerPoolWithErrors) SubmitWithResult(cb func() (interface{}, error)) { + wp.pool.Submit(func() { + result, err := cb() + wp.mutex.Lock() + defer wp.mutex.Unlock() + if err != nil { + wp.errors = append(wp.errors, err) + } else if result != nil { + wp.results = append(wp.results, result) + } + }) +} + +func (wp *WorkerPoolWithErrors) StopWait(restart bool) error { + if wp.pool == nil { + return nil + } + wp.pool.StopWait() + if restart { + wp.pool = workerpool.New(wp.maxWorkers) + } else { + wp.pool = nil + } + + wp.mutex.Lock() + defer wp.mutex.Unlock() + + if len(wp.errors) == 0 { + return nil + } + err := NewErrorList(wp.errors) + wp.errors = nil + return err +} + +func (wp *WorkerPoolWithErrors) Errors() []error { + return wp.errors +} + +func (wp *WorkerPoolWithErrors) Results() []interface{} { + return wp.results +} diff --git a/pkg/utils/yaml.go b/pkg/utils/yaml.go new file mode 100644 index 000000000..049c4302b --- /dev/null +++ b/pkg/utils/yaml.go @@ -0,0 +1,124 @@ +package utils + +import ( + "bytes" + "errors" + "fmt" + "gopkg.in/yaml.v3" + "io" + "os" + "strings" +) + +func ReadYamlFile(p string, o interface{}) error { + r, err := os.Open(p) + if err != nil { + return fmt.Errorf("opening %v failed: %w", p, err) + } + defer r.Close() + + err = ReadYamlStream(r, o) + if err != nil { + return fmt.Errorf("unmarshalling %v failed: %w", p, err) + } + return nil +} + +func ReadYamlString(s string, o interface{}) error { + return ReadYamlStream(strings.NewReader(s), o) +} + +func ReadYamlStream(r io.Reader, o interface{}) error { + d := yaml.NewDecoder(r) + d.KnownFields(true) + err := d.Decode(o) + return err +} + +func ReadYamlAllFile(p string) ([]interface{}, error) { + r, err := os.Open(p) + if err != nil { + return nil, fmt.Errorf("opening %v failed: %w", p, err) + } + defer r.Close() + + return ReadYamlAllStream(r) +} + +func ReadYamlAllString(s string) ([]interface{}, error) { + return ReadYamlAllStream(strings.NewReader(s)) +} + +func ReadYamlAllBytes(b []byte) ([]interface{}, error) { + return ReadYamlAllStream(bytes.NewReader(b)) +} + +func ReadYamlAllStream(r io.Reader) ([]interface{}, error) { + d := yaml.NewDecoder(r) + d.KnownFields(true) + + var l []interface{} + for true { + var o interface{} + err := d.Decode(&o) + if err != nil { + if errors.Is(err, io.EOF) { + break + } + return nil, err + } + l = append(l, o) + } + + return l, nil +} + +func WriteYamlString(o interface{}) (string, error) { + return WriteYamlAllString([]interface{}{o}) +} + +func WriteYamlFile(p string, o interface{}) error { + return WriteYamlAllFile(p, []interface{}{o}) +} + +func WriteYamlAllFile(p string, l []interface{}) error { + w, err := os.Create(p) + if err != nil { + return err + } + defer w.Close() + + return WriteYamlAllStream(w, l) +} + +func WriteYamlAllBytes(l []interface{}) ([]byte, error) { + w := bytes.NewBuffer(nil) + + err := WriteYamlAllStream(w, l) + if err != nil { + return nil, err + } + return w.Bytes(), nil +} + +func WriteYamlAllString(l []interface{}) (string, error) { + b, err := WriteYamlAllBytes(l) + if err != nil { + return "", err + } + return string(b), nil +} + +func WriteYamlAllStream(w io.Writer, l []interface{}) error { + enc := yaml.NewEncoder(w) + defer enc.Close() + enc.SetIndent(2) + + for _, o := range l { + err := enc.Encode(o) + if err != nil { + return err + } + } + return nil +} diff --git a/pkg/validation/validation.go b/pkg/validation/validation.go new file mode 100644 index 000000000..86123749c --- /dev/null +++ b/pkg/validation/validation.go @@ -0,0 +1,25 @@ +package validation + +import ( + "github.com/codablock/kluctl/pkg/types" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +type ValidateResultItem struct { + Ref types.ObjectRef `yaml:"ref"` + Reason string `yaml:"reason"` + Message string `yaml:"message"` +} + +type ValidationResult struct { + Ready bool `yaml:"ready"` + Warnings []ValidateResultItem `yaml:"warnings,omitempty"` + Errors []ValidateResultItem `yaml:"errors,omitempty"` + Results []ValidateResultItem `yaml:"results,omitempty"` +} + +func ValidateObject(o *unstructured.Unstructured, notReadyIsError bool) ValidationResult { + return ValidationResult{ + Ready: false, + } +} diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index 2cec6b43e..000000000 --- a/pytest.ini +++ /dev/null @@ -1,5 +0,0 @@ -[pytest] -log_cli = true -log_cli_level = 20 -log_cli_format = "%(asctime)s [%(levelname)8s] %(message)s" -log_cli_date_format = "%Y-%m-%d %H:%M:%S" diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index 69a07bae0..000000000 --- a/requirements-dev.txt +++ /dev/null @@ -1,4 +0,0 @@ -pytest>=6.2.5 -pytest-xdist>=2.4.0 -pytest-kind>=21.1.3 -pyinstaller>=4.7.0 diff --git a/setup.py b/setup.py deleted file mode 100644 index ae978a1c3..000000000 --- a/setup.py +++ /dev/null @@ -1,83 +0,0 @@ -"""Setup script for kluctl""" - -import os - -from setuptools import setup, find_packages - -# The directory containing this file -HERE = os.path.abspath(os.path.dirname(__file__)) - -name = 'kluctl' -filename = '{0}/_version.py'.format(name) -_locals = {} -with open(filename) as src: - exec(src.read(), None, _locals) -version = _locals['__version__'] - -with open("README.md", "r", encoding="utf-8") as fh: - long_description = fh.read() - -datas = [] -for dirpath, dirnames, filenames in os.walk("./kluctl"): - for f in filenames: - if f.endswith(".py") or f.endswith(".pyc"): - continue - p = os.path.join("..", dirpath, f) - datas.append(p) - -# This call to setup() does all the work -setup( - name=name, - version=version, - description="Deploy and manage complex deployments on Kubernetes", - long_description=long_description, - long_description_content_type="text/markdown", - url="https://github.com/codablock/kluctl", - author="Alexander Block", - author_email="ablock@codablock.de", - license="Apache", - classifiers=[ - "License :: OSI Approved :: Apache Software License", - "Intended Audience :: Developers", - "Intended Audience :: Information Technology", - "Intended Audience :: System Administrators", - "Operating System :: MacOS :: MacOS X", - "Operating System :: POSIX", - "Operating System :: Unix", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - ], - packages=find_packages(), - include_package_data=True, - package_data={ - "": datas, - "kluctl": ["bootstrap/**"], - }, - install_requires=[ - "click>=8.0.1", - "click-option-group>=0.5.3", - "termcolor>=1.1.0", - "jinja2>=3.0.2", - "requests>=2.26.0", - "requests_ntlm>=1.1.0", - "pyyaml>=6.0b1", - "deepdiff>=5.5.0", - "kubernetes>=19.15.0a1", - "adal>=1.2.7", - "PyJWT>=2.2.0", - "python-dxf>=7.7.1", - "gitpython>=3.1.24", - "jsonschema>=4.2.1", - "filelock>=3.3.0", - "python-gitlab>=2.10.1", - "jsonpath-ng>=1.5.3", - "jsonpatch>=1.32", - "boto3>=1.20.24", - "rsa==4.6", # >=4.7 is broken when used with pyinstaller - ], - entry_points={ - "console_scripts": [ - "kluctl=kluctl.cli.__main__:main" - ] - }, -) From 1b06d7e0c51759bb5016f42c24e37cb02c74d237 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sat, 12 Feb 2022 02:46:43 +0100 Subject: [PATCH 0332/2916] Implement bootstrap, delete, deploy, prune and validate --- cmd/kluctl/args/misc.go | 4 ++ cmd/kluctl/cmd_bootstrap.go | 91 +++++++++++++++++++++++++ cmd/kluctl/cmd_delete.go | 75 ++++++++++++++++++++ cmd/kluctl/cmd_deploy.go | 66 ++++++++++++++++++ cmd/kluctl/cmd_prune.go | 61 +++++++++++++++++ cmd/kluctl/cmd_render.go | 1 - cmd/kluctl/cmd_validate.go | 74 ++++++++++++++++++++ cmd/kluctl/confirmation.go | 36 ++++++++++ cmd/kluctl/utils.go | 6 +- go.mod | 2 +- pkg/deployment/apply_utils.go | 2 +- pkg/deployment/deployment_collection.go | 48 ++++++++++--- pkg/deployment/helm_chart.go | 3 +- pkg/k8s/delete_utils.go | 36 ++-------- pkg/k8s/k8s_cluster.go | 36 ++++++++++ pkg/types/kluctl_project.go | 2 +- pkg/utils/fs.go | 60 ++++++++++++++-- pkg/validation/validation.go | 17 +---- 18 files changed, 551 insertions(+), 69 deletions(-) create mode 100644 cmd/kluctl/cmd_bootstrap.go create mode 100644 cmd/kluctl/cmd_delete.go create mode 100644 cmd/kluctl/cmd_deploy.go create mode 100644 cmd/kluctl/cmd_prune.go create mode 100644 cmd/kluctl/cmd_validate.go create mode 100644 cmd/kluctl/confirmation.go diff --git a/cmd/kluctl/args/misc.go b/cmd/kluctl/args/misc.go index 75d4312f3..dca50864c 100644 --- a/cmd/kluctl/args/misc.go +++ b/cmd/kluctl/args/misc.go @@ -19,6 +19,10 @@ var ( OutputFormat []string Output []string RenderOutputDir string + + Wait time.Duration + Sleep time.Duration + WarningsAsErrors bool ) type EnabledMiscArguments struct { diff --git a/cmd/kluctl/cmd_bootstrap.go b/cmd/kluctl/cmd_bootstrap.go new file mode 100644 index 000000000..6a41bcd80 --- /dev/null +++ b/cmd/kluctl/cmd_bootstrap.go @@ -0,0 +1,91 @@ +package main + +import ( + "embed" + "fmt" + "github.com/codablock/kluctl/cmd/kluctl/args" + "github.com/codablock/kluctl/pkg/types" + "github.com/codablock/kluctl/pkg/utils" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "io/ioutil" + "k8s.io/apimachinery/pkg/runtime/schema" + "os" +) + +var ( + // files with _ as prefix are excluded by default, this is a workaround until go 1.18 is released + // see https://github.com/golang/go/issues/43854 + //go:embed bootstrap bootstrap/sealed-secrets/charts/sealed-secrets/templates/* + embedBootstrap embed.FS +) + +func runCmdBootstrap(cmd *cobra.Command, args_ []string) error { + tmpBootstrapDir, err := ioutil.TempDir(utils.GetTmpBaseDir(), "bootstrap-") + if err != nil { + return err + } + defer os.RemoveAll(tmpBootstrapDir) + + err = utils.FsCopyDir(embedBootstrap, "bootstrap", tmpBootstrapDir) + if err != nil { + return err + } + + if args.LocalDeployment == "" { + args.LocalDeployment = tmpBootstrapDir + } + + return withProjectCommandContext(func(ctx *commandCtx) error { + existing, err := ctx.k.GetSingleObject(types.ObjectRef{ + GVK: schema.GroupVersionKind{Group: "apiextensions.k8s.io", Version: "v1", Kind: "CustomResourceDefinition"}, + Name: "sealedsecrets.bitnami.com", + }) + if existing != nil { + component, _ := existing.GetLabels()["kluctl.io/component"] + if !args.ForceYes && component != "bootstrap" { + if !AskForConfirmation("It looks like you're trying to bootstrap a cluster that already has the sealed-secrets deployed but not managed by kluctl. Do you really want to continue bootstrapping?") { + return fmt.Errorf("aborted") + } + } + } + + err = runCmdDeploy2(cmd, ctx) + if err != nil { + return err + } + err = runCmdPrune2(cmd, ctx) + if err != nil { + return err + } + return nil + }) +} + +func init() { + cmd := &cobra.Command{ + Use: "bootstrap", + Short: "Bootstrap a target cluster", + Long: "This will install the sealed-secrets operator into the specified cluster if not already " + + "installed.\n\n" + + "Either --target or --cluster must be specified.", + RunE: runCmdBootstrap, + } + + args.AddProjectArgs(cmd, false, true, true) + args.AddMiscArguments(cmd, args.EnabledMiscArguments{ + Yes: true, + DryRun: true, + ForceApply: true, + ReplaceOnError: true, + HookTimeout: true, + AbortOnError: true, + OutputFormat: true, + }) + err := viper.BindPFlags(cmd.Flags()) + if err != nil { + panic(err) + } + + rootCmd.AddCommand(cmd) +} diff --git a/cmd/kluctl/cmd_delete.go b/cmd/kluctl/cmd_delete.go new file mode 100644 index 000000000..a60e3061e --- /dev/null +++ b/cmd/kluctl/cmd_delete.go @@ -0,0 +1,75 @@ +package main + +import ( + "fmt" + "github.com/codablock/kluctl/cmd/kluctl/args" + "github.com/codablock/kluctl/pkg/k8s" + "github.com/codablock/kluctl/pkg/types" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "os" +) + +func runCmdDelete(cmd *cobra.Command, args_ []string) error { + return withProjectCommandContext(func(ctx *commandCtx) error { + objects, err := ctx.deploymentCollection.FindDeleteObjects(ctx.k) + if err != nil { + return err + } + result, err := confirmedDeleteObjects(ctx.k, objects) + if err != nil { + return err + } + err = outputCommandResult(args.Output, result) + if err != nil { + return err + } + if len(result.Errors) != 0 { + return fmt.Errorf("command failed") + } + return nil + }) +} + +func confirmedDeleteObjects(k *k8s.K8sCluster, refs []types.ObjectRef) (*types.CommandResult, error) { + if len(refs) != 0 { + _, _ = os.Stderr.WriteString("The following objects will be deleted:\n") + for _, ref := range refs { + _, _ = os.Stderr.WriteString(fmt.Sprintf(" %s\n", ref.String())) + } + if !args.ForceYes && !args.DryRun { + if !AskForConfirmation(fmt.Sprintf("Do you really want to delete %d objects?", len(refs))) { + return nil, fmt.Errorf("aborted") + } + } + } + + return k8s.DeleteObjects(k, refs, false) +} + +func init() { + var cmd = &cobra.Command{ + Use: "delete", + Short: "Delete the a target (or parts of it) from the corresponding cluster", + Long: "Delete the a target (or parts of it) from the corresponding cluster.\n\n" + + "Objects are located based on `deleteByLabels`, configured in `deployment.yml`\n\n" + + "WARNING: This command will also delete objects which are not part of your deployment " + + "project (anymore). It really only decides based on the `deleteByLabel` labels and does NOT " + + "take the local target/state into account!", + RunE: runCmdDelete, + } + args.AddProjectArgs(cmd, true, true, true) + args.AddImageArgs(cmd) + args.AddInclusionArgs(cmd) + args.AddMiscArguments(cmd, args.EnabledMiscArguments{ + Yes: true, + DryRun: true, + OutputFormat: true, + }) + err := viper.BindPFlags(cmd.Flags()) + if err != nil { + panic(err) + } + + rootCmd.AddCommand(cmd) +} diff --git a/cmd/kluctl/cmd_deploy.go b/cmd/kluctl/cmd_deploy.go new file mode 100644 index 000000000..d71d4dee7 --- /dev/null +++ b/cmd/kluctl/cmd_deploy.go @@ -0,0 +1,66 @@ +package main + +import ( + "fmt" + "github.com/codablock/kluctl/cmd/kluctl/args" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +func runCmdDeploy(cmd *cobra.Command, args_ []string) error { + return withProjectCommandContext(func(ctx *commandCtx) error { + return runCmdDeploy2(cmd, ctx) + }) +} + +func runCmdDeploy2(cmd *cobra.Command, ctx *commandCtx) error { + if !args.ForceYes && !args.DryRun { + if !AskForConfirmation(fmt.Sprintf("Do you really want to deploy to the context/cluster %s", ctx.k.Context())) { + return fmt.Errorf("aborted") + } + } + + result, err := ctx.deploymentCollection.Deploy(ctx.k, args.ForceApply, args.ReplaceOnError, args.ForceReplaceOnError, args.AbortOnError, args.HookTimeout) + if err != nil { + return err + } + err = outputCommandResult(args.Output, result) + if err != nil { + return err + } + if len(result.Errors) != 0 { + return fmt.Errorf("command failed") + } + return nil +} + +func init() { + var cmd = &cobra.Command{ + Use: "deploy", + Short: "Deploys a target to the corresponding cluster", + Long: "Deploys a target to the corresponding cluster.\n\n" + + "This command will also output a diff between the initial state and the state after " + + "deployment. The format of this diff is the same as for the `diff` command. " + + "It will also output a list of prunable objects (without actually deleting them).", + RunE: runCmdDeploy, + } + args.AddProjectArgs(cmd, true, true, true) + args.AddImageArgs(cmd) + args.AddInclusionArgs(cmd) + args.AddMiscArguments(cmd, args.EnabledMiscArguments{ + Yes: true, + DryRun: true, + ForceApply: true, + ReplaceOnError: true, + AbortOnError: true, + HookTimeout: true, + OutputFormat: true, + RenderOutputDir: true, + }) + err := viper.BindPFlags(cmd.Flags()) + if err != nil { + panic(err) + } + + rootCmd.AddCommand(cmd) +} diff --git a/cmd/kluctl/cmd_prune.go b/cmd/kluctl/cmd_prune.go new file mode 100644 index 000000000..a64930d23 --- /dev/null +++ b/cmd/kluctl/cmd_prune.go @@ -0,0 +1,61 @@ +package main + +import ( + "fmt" + "github.com/codablock/kluctl/cmd/kluctl/args" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +func runCmdPrune(cmd *cobra.Command, args_ []string) error { + return withProjectCommandContext(func(ctx *commandCtx) error { + return runCmdPrune2(cmd, ctx) + }) +} + +func runCmdPrune2(cmd *cobra.Command, ctx *commandCtx) error { + objects, err := ctx.deploymentCollection.FindOrphanObjects(ctx.k) + if err != nil { + return err + } + result, err := confirmedDeleteObjects(ctx.k, objects) + if err != nil { + return err + } + err = outputCommandResult(args.Output, result) + if err != nil { + return err + } + if len(result.Errors) != 0 { + return fmt.Errorf("command failed") + } + return nil +} + +func init() { + var cmd = &cobra.Command{ + Use: "prune", + Short: "Searches the target cluster for prunable objects and deletes them", + Long: "Searches the target cluster for prunable objects and deletes them.\n\n" + + "Searching works by:\n\n" + + "\b\n" + + " 1. Search the cluster for all objects match `deleteByLabels`, as configured in `deployment.yml`\n" + + " 2. Render the local target and list all objects.\n" + + " 3. Remove all objects from the list of 1. that are part of the list in 2.\n", + RunE: runCmdPrune, + } + args.AddProjectArgs(cmd, true, true, true) + args.AddImageArgs(cmd) + args.AddInclusionArgs(cmd) + args.AddMiscArguments(cmd, args.EnabledMiscArguments{ + Yes: true, + DryRun: true, + OutputFormat: true, + }) + err := viper.BindPFlags(cmd.Flags()) + if err != nil { + panic(err) + } + + rootCmd.AddCommand(cmd) +} diff --git a/cmd/kluctl/cmd_render.go b/cmd/kluctl/cmd_render.go index dd01aa7cc..3872a6f41 100644 --- a/cmd/kluctl/cmd_render.go +++ b/cmd/kluctl/cmd_render.go @@ -45,4 +45,3 @@ func init() { rootCmd.AddCommand(cmd) } - diff --git a/cmd/kluctl/cmd_validate.go b/cmd/kluctl/cmd_validate.go new file mode 100644 index 000000000..385e49916 --- /dev/null +++ b/cmd/kluctl/cmd_validate.go @@ -0,0 +1,74 @@ +package main + +import ( + "fmt" + "github.com/codablock/kluctl/cmd/kluctl/args" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "os" + "time" +) + +func runCmdValidate(cmd *cobra.Command, args_ []string) error { + return withProjectCommandContext(func(ctx *commandCtx) error { + startTime := time.Now() + for true { + result := ctx.deploymentCollection.Validate() + failed := len(result.Errors) != 0 || (args.WarningsAsErrors && len(result.Warnings) != 0) + + err := outputValidateResult(args.Output, result) + if err != nil { + return err + } + + if !failed { + _, _ = os.Stderr.WriteString("Validation succeeded\n") + return nil + } + + if args.Wait <= 0 || time.Now().Sub(startTime) > args.Wait { + return fmt.Errorf("Validation failed") + } + + time.Sleep(args.Sleep) + + // Need to force re-requesting these objects + for _, e := range result.Results { + ctx.deploymentCollection.ForgetRemoteObject(e.Ref) + } + for _, e := range result.Warnings { + ctx.deploymentCollection.ForgetRemoteObject(e.Ref) + } + for _, e := range result.Errors { + ctx.deploymentCollection.ForgetRemoteObject(e.Ref) + } + } + return nil + }) +} + +func init() { + var cmd = &cobra.Command{ + Use: "validate", + Short: "Validates the already deployed deployment", + Long: "Validates the already deployed deployment.\n\n" + + "This means that all objects are retrieved from the cluster and checked for readiness.\n\n" + + "TODO: This needs to be better documented!", + RunE: runCmdValidate, + } + args.AddProjectArgs(cmd, true, true, true) + args.AddInclusionArgs(cmd) + args.AddMiscArguments(cmd, args.EnabledMiscArguments{ + Output: true, + RenderOutputDir: true, + }) + cmd.Flags().DurationVar(&args.Wait, "wait", 0, "Wait for the given amount of time until the deployment validates") + cmd.Flags().DurationVar(&args.Sleep, "sleep", time.Second*5, "Sleep duration between validation attempts") + cmd.Flags().BoolVar(&args.WarningsAsErrors, "warnings-as-errors", false, "Consider warnings as failures") + err := viper.BindPFlags(cmd.Flags()) + if err != nil { + panic(err) + } + + rootCmd.AddCommand(cmd) +} diff --git a/cmd/kluctl/confirmation.go b/cmd/kluctl/confirmation.go new file mode 100644 index 000000000..ebf867027 --- /dev/null +++ b/cmd/kluctl/confirmation.go @@ -0,0 +1,36 @@ +package main + +import ( + "fmt" + "github.com/codablock/kluctl/pkg/utils" + log "github.com/sirupsen/logrus" + "os" +) + +// AskForConfirmation uses Scanln to parse user input. A user must type in "yes" or "no" and +// then press enter. It has fuzzy matching, so "y", "Y", "yes", "YES", and "Yes" all count as +// confirmations. If the input is not recognized, it will ask again. The function does not return +// until it gets a valid response from the user. Typically, you should use fmt to print out a question +// before calling askForConfirmation. E.g. fmt.Println("WARNING: Are you sure? (yes/no)") +func AskForConfirmation(promt string) bool { + _, err := os.Stderr.WriteString(promt) + if err != nil { + log.Fatal(err) + } + + var response string + _, err = fmt.Scanln(&response) + if err != nil { + log.Fatal(err) + } + okayResponses := []string{"y", "Y", "yes", "Yes", "YES"} + nokayResponses := []string{"n", "N", "no", "No", "NO"} + if utils.FindStrInSlice(okayResponses, response) != -1 { + return true + } else if utils.FindStrInSlice(nokayResponses, response) != -1 { + return false + } else { + fmt.Println("Please type yes or no and then press enter:") + return AskForConfirmation(promt) + } +} diff --git a/cmd/kluctl/utils.go b/cmd/kluctl/utils.go index e3e806b47..a52621ed4 100644 --- a/cmd/kluctl/utils.go +++ b/cmd/kluctl/utils.go @@ -67,7 +67,7 @@ func withProjectCommandContext(cb func(ctx *commandCtx) error) error { } func withProjectTargetCommandContext(p *kluctl_project.KluctlProjectContext, target *types.Target, forSeal bool, cb func(ctx *commandCtx) error) error { - projectDir, err := filepath.Abs(p.ProjectDir) + deploymentDir, err := filepath.Abs(p.DeploymentDir) if err != nil { return err } @@ -128,7 +128,7 @@ func withProjectTargetCommandContext(p *kluctl_project.KluctlProjectContext, tar } } - err = deployment.CheckRequiredDeployArgs(projectDir, varsCtx, allArgs) + err = deployment.CheckRequiredDeployArgs(deploymentDir, varsCtx, allArgs) if err != nil { return err } @@ -145,7 +145,7 @@ func withProjectTargetCommandContext(p *kluctl_project.KluctlProjectContext, tar renderOutputDir = tmpDir } - d, err := deployment.NewDeploymentProject(k, varsCtx, projectDir, p.SealedSecretsDir, nil) + d, err := deployment.NewDeploymentProject(k, varsCtx, deploymentDir, p.SealedSecretsDir, nil) if err != nil { return err } diff --git a/go.mod b/go.mod index 7fc2a6d7c..cd3f26a3e 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,7 @@ require ( golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c google.golang.org/grpc v1.43.0 + google.golang.org/protobuf v1.27.1 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b k8s.io/apimachinery v0.23.3 @@ -103,7 +104,6 @@ require ( golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368 // indirect - google.golang.org/protobuf v1.27.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.66.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect diff --git a/pkg/deployment/apply_utils.go b/pkg/deployment/apply_utils.go index f037c0347..c2eab9da1 100644 --- a/pkg/deployment/apply_utils.go +++ b/pkg/deployment/apply_utils.go @@ -256,7 +256,7 @@ func (a *applyUtil) waitHook(ref types.ObjectRef) bool { log2.Warningf("Cancelled waiting for hook due to errors") } for _, e := range v.Errors { - a.handleError(ref, fmt.Errorf(e.Message)) + a.handleError(ref, fmt.Errorf(e.Error)) } return false } diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index a410305bf..eedbe533e 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -6,6 +6,7 @@ import ( "github.com/codablock/kluctl/pkg/k8s" "github.com/codablock/kluctl/pkg/types" "github.com/codablock/kluctl/pkg/utils" + "github.com/codablock/kluctl/pkg/validation" log "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "path" @@ -204,10 +205,8 @@ func (c *DeploymentCollection) updateRemoteObjects(k *k8s.K8sCluster) error { return nil } -func (c *DeploymentCollection) forgetRemoteObjects(refs []types.ObjectRef) { - for _, ref := range refs { - delete(c.remoteObjects, ref) - } +func (c *DeploymentCollection) ForgetRemoteObject(ref types.ObjectRef) { + delete(c.remoteObjects, ref) } func (c *DeploymentCollection) localObjectsByRef() map[types.ObjectRef]bool { @@ -270,7 +269,7 @@ func (c *DeploymentCollection) Deploy(k *k8s.K8sCluster, forceApply bool, replac if err != nil { return nil, err } - orphanObjects, err := c.findOrphanObjects(k) + orphanObjects, err := c.FindOrphanObjects(k) if err != nil { return nil, err } @@ -306,7 +305,7 @@ func (c *DeploymentCollection) Diff(k *k8s.K8sCluster, forceApply bool, replaceO if err != nil { return nil, err } - orphanObjects, err := c.findOrphanObjects(k) + orphanObjects, err := c.FindOrphanObjects(k) if err != nil { return nil, err } @@ -320,12 +319,45 @@ func (c *DeploymentCollection) Diff(k *k8s.K8sCluster, forceApply bool, replaceO }, nil } -func (c *DeploymentCollection) findDeleteObjects(k *k8s.K8sCluster) ([]types.ObjectRef, error) { +func (c *DeploymentCollection) Validate() *types.ValidateResult { + c.clearErrorsAndWarnings() + + var result types.ValidateResult + + for e := range c.warnings { + result.Warnings = append(result.Warnings, e) + } + for e := range c.errors { + result.Errors = append(result.Errors, e) + } + + for _, d := range c.deployments { + if !d.checkInclusionForDeploy() { + continue + } + for _, o := range d.objects { + ref := types.RefFromObject(o) + remoteObject := c.getRemoteObject(ref) + if remoteObject == nil { + result.Errors = append(result.Errors, types.DeploymentError{Ref: ref, Error: "object not found"}) + continue + } + r := validation.ValidateObject(o, true) + result.Errors = append(result.Errors, r.Errors...) + result.Warnings = append(result.Warnings, r.Warnings...) + result.Results = append(result.Results, r.Results...) + } + } + + return &result +} + +func (c *DeploymentCollection) FindDeleteObjects(k *k8s.K8sCluster) ([]types.ObjectRef, error) { labels := c.project.getDeleteByLabels() return k8s.FindObjectsForDelete(k, labels, c.inclusion, nil) } -func (c *DeploymentCollection) findOrphanObjects(k *k8s.K8sCluster) ([]types.ObjectRef, error) { +func (c *DeploymentCollection) FindOrphanObjects(k *k8s.K8sCluster) ([]types.ObjectRef, error) { log.Infof("Searching for orphan objects") labels := c.project.getDeleteByLabels() return k8s.FindObjectsForDelete(k, labels, c.inclusion, c.localObjectRefs()) diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index b4fb95794..c2d550b91 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -194,6 +194,7 @@ func (c *helmChart) Render(k *k8s.K8sCluster) error { if utils.Exists(valuesPath) { args = append(args, "-f", valuesPath) } + args = append(args, "-n", namespace) if c.config.SkipCRDs != nil && *c.config.SkipCRDs { args = append(args, "--skip-crds") @@ -227,7 +228,7 @@ func (c *helmChart) Render(k *k8s.K8sCluster) error { // "helm install" will deploy resources to the given namespace automatically, but "helm template" does not // add the necessary namespace in the rendered resources _, found, _ := unstructured.NestedString(m, "metadata", "namespace") - if !found { + if !found && k.IsNamespaced((&unstructured.Unstructured{Object: m}).GroupVersionKind()) { _ = unstructured.SetNestedField(m, namespace, "metadata", "namespace") } } diff --git a/pkg/k8s/delete_utils.go b/pkg/k8s/delete_utils.go index 5dea8d01b..114aa58f6 100644 --- a/pkg/k8s/delete_utils.go +++ b/pkg/k8s/delete_utils.go @@ -38,31 +38,6 @@ var deleteOrder = [][]string{ nil, } -func getFilteredApiResources(k *K8sCluster, filter []string) (map[schema.GroupVersionKind]bool, error) { - apiResourceLists, err := k.discovery.ServerPreferredResources() - if err != nil { - return nil, err - } - - ret := make(map[schema.GroupVersionKind]bool) - for _, l := range apiResourceLists { - for _, r := range l.APIResources { - for _, f := range filter { - if r.Name != f && r.Group != f && r.Kind != f { - continue - } - gvk := schema.GroupVersionKind{ - Group: r.Group, - Version: r.Version, - Kind: r.Kind, - } - ret[gvk] = true - } - } - } - return ret, nil -} - func objectRefForExclusion(k *K8sCluster, ref types.ObjectRef) types.ObjectRef { ref = k.RemoveNamespaceFromRefIfNeeded(ref) ref.GVK.Version = "" @@ -70,9 +45,9 @@ func objectRefForExclusion(k *K8sCluster, ref types.ObjectRef) types.ObjectRef { } func filterObjectsForDelete(k *K8sCluster, objects []*v1.PartialObjectMetadata, apiFilter []string, inclusion *utils.Inclusion, excludedObjects map[types.ObjectRef]bool) ([]*v1.PartialObjectMetadata, error) { - filteredResources, err := getFilteredApiResources(k, apiFilter) - if err != nil { - return nil, err + filteredResources := make(map[schema.GroupKind]bool) + for _, gk := range k.GetFilteredGKs(apiFilter) { + filteredResources[gk] = true } inclusionHasTags := inclusion.HasType("tags") @@ -80,8 +55,7 @@ func filterObjectsForDelete(k *K8sCluster, objects []*v1.PartialObjectMetadata, for _, o := range objects { ref := types.RefFromPartialObject(o) - ref = objectRefForExclusion(k, ref) - if _, ok := filteredResources[ref.GVK]; !ok { + if _, ok := filteredResources[ref.GVK.GroupKind()]; !ok { continue } @@ -119,7 +93,7 @@ func filterObjectsForDelete(k *K8sCluster, objects []*v1.PartialObjectMetadata, } // exclude objects from excluded_objects - if _, ok := excludedObjects[ref]; ok { + if _, ok := excludedObjects[objectRefForExclusion(k, ref)]; ok { continue } diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index 6d8df220b..82bcf5b23 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -24,6 +24,12 @@ import ( "time" ) +var ( + deprecatedResources = map[schema.GroupKind]bool{ + {Group: "extensions", Kind: "Ingress"}: true, + } +) + type K8sCluster struct { context string DryRun bool @@ -142,6 +148,9 @@ func (k *K8sCluster) updateResources() error { Version: ar.Version, Kind: ar.Kind, } + if _, ok := deprecatedResources[gvk.GroupKind()]; ok { + continue + } if _, ok := k.allResources[gvk]; ok { ok = false } @@ -169,6 +178,9 @@ func (k *K8sCluster) updateResources() error { Group: ar.Group, Kind: ar.Kind, } + if _, ok := deprecatedResources[gk]; ok { + continue + } k.preferredResources[gk] = ar } } @@ -235,6 +247,30 @@ func (k *K8sCluster) GetAllGroupVersions() ([]string, error) { return l, nil } +func (k *K8sCluster) GetFilteredGKs(filters []string) []schema.GroupKind { + k.mutex.Lock() + defer k.mutex.Unlock() + + m := make(map[schema.GroupKind]bool) + var l []schema.GroupKind + for gk, ar := range k.preferredResources { + found := len(filters) == 0 + for _, f := range filters { + if ar.Name == f || ar.Group == f || ar.Kind == f { + found = true + break + } + } + if found { + if _, ok := m[gk]; !ok { + m[gk] = true + l = append(l, gk) + } + } + } + return l +} + func (k *K8sCluster) getGVRForGVK(gvk schema.GroupVersionKind) (*schema.GroupVersionResource, bool, error) { k.mutex.Lock() defer k.mutex.Unlock() diff --git a/pkg/types/kluctl_project.go b/pkg/types/kluctl_project.go index 9ff3921e7..60680fe68 100644 --- a/pkg/types/kluctl_project.go +++ b/pkg/types/kluctl_project.go @@ -47,7 +47,7 @@ type SecretsConfig struct { } type KluctlProject struct { - Deployment *ExternalProject `yaml:"name,omitempty"` + Deployment *ExternalProject `yaml:"deployment,omitempty"` SealedSecrets *ExternalProject `yaml:"sealedSecrets,omitempty"` Clusters ExternalProjects `yaml:"clusters,omitempty"` Targets []Target `yaml:"targets,omitempty"` diff --git a/pkg/utils/fs.go b/pkg/utils/fs.go index a40ed2c71..aea3921b8 100644 --- a/pkg/utils/fs.go +++ b/pkg/utils/fs.go @@ -3,7 +3,9 @@ package utils import ( "fmt" "io" + "io/fs" "os" + "path" ) func Exists(path string) bool { @@ -40,27 +42,71 @@ func Touch(path string) error { return f.Close() } -func CopyFile(src, dst string) error { - sourceFileStat, err := os.Stat(src) +func CopyFile(src string, dst string) error { + if !IsFile(src) { + return fmt.Errorf("%s is not a regular file", src) + } + f, err := os.Open(src) if err != nil { return err } + defer f.Close() + return CopyFileStream(f, dst) +} - if !sourceFileStat.Mode().IsRegular() { - return fmt.Errorf("%s is not a regular file", src) +func FsCopyFile(srcFs fs.FS, src, dst string) error { + source, err := srcFs.Open(src) + if err != nil { + return err } + defer source.Close() - source, err := os.Open(src) + sourceFileStat, err := source.Stat() if err != nil { return err } - defer source.Close() + if !sourceFileStat.Mode().IsRegular() { + return fmt.Errorf("%s is not a regular file", src) + } + + return CopyFileStream(source, dst) +} + +func CopyFileStream(src io.Reader, dst string) error { destination, err := os.Create(dst) if err != nil { return err } defer destination.Close() - _, err = io.Copy(destination, source) + + _, err = io.Copy(destination, src) return err } + +func FsCopyDir(srcFs fs.ReadDirFS, src string, dst string) error { + var err error + var fds []fs.DirEntry + + if fds, err = srcFs.ReadDir(src); err != nil { + return err + } + if err = os.MkdirAll(dst, 0o777); err != nil { + return err + } + for _, fd := range fds { + srcfp := path.Join(src, fd.Name()) + dstfp := path.Join(dst, fd.Name()) + + if fd.IsDir() { + if err = FsCopyDir(srcFs, srcfp, dstfp); err != nil { + return err + } + } else { + if err = FsCopyFile(srcFs, srcfp, dstfp); err != nil { + return err + } + } + } + return nil +} diff --git a/pkg/validation/validation.go b/pkg/validation/validation.go index 86123749c..5b344e5aa 100644 --- a/pkg/validation/validation.go +++ b/pkg/validation/validation.go @@ -5,21 +5,8 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) -type ValidateResultItem struct { - Ref types.ObjectRef `yaml:"ref"` - Reason string `yaml:"reason"` - Message string `yaml:"message"` -} - -type ValidationResult struct { - Ready bool `yaml:"ready"` - Warnings []ValidateResultItem `yaml:"warnings,omitempty"` - Errors []ValidateResultItem `yaml:"errors,omitempty"` - Results []ValidateResultItem `yaml:"results,omitempty"` -} - -func ValidateObject(o *unstructured.Unstructured, notReadyIsError bool) ValidationResult { - return ValidationResult{ +func ValidateObject(o *unstructured.Unstructured, notReadyIsError bool) types.ValidateResult { + return types.ValidateResult{ Ready: false, } } From 6c25fd70178634041c2a0c01aafbc1287537c5a8 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sat, 12 Feb 2022 03:33:35 +0100 Subject: [PATCH 0333/2916] Multiple fixes --- pkg/diff/managed_fields.go | 1 + pkg/jinja2_server/server.go | 2 ++ pkg/kluctl_project/load.go | 16 +++++++++++++--- pkg/types/helm_chart.go | 1 + pkg/utils/yaml.go | 4 +++- 5 files changed, 20 insertions(+), 4 deletions(-) diff --git a/pkg/diff/managed_fields.go b/pkg/diff/managed_fields.go index 2d9ce421e..2a7877544 100644 --- a/pkg/diff/managed_fields.go +++ b/pkg/diff/managed_fields.go @@ -89,6 +89,7 @@ func convertToKeyList(remote *unstructured.Unstructured, path fieldpath.Path) ([ if match { found = true ret = append(ret, i) + o = x break } } diff --git a/pkg/jinja2_server/server.go b/pkg/jinja2_server/server.go index 8eae88c27..48bc5a4e8 100644 --- a/pkg/jinja2_server/server.go +++ b/pkg/jinja2_server/server.go @@ -130,6 +130,8 @@ func (js *Jinja2Server) isMaybeTemplate(template string, searchDirs []string, is if bytes.IndexRune(b, '{') == -1 { x := string(b) return false, &x + } else { + return true, nil } } } diff --git a/pkg/kluctl_project/load.go b/pkg/kluctl_project/load.go index 3af4a34ab..fbd3726ab 100644 --- a/pkg/kluctl_project/load.go +++ b/pkg/kluctl_project/load.go @@ -93,12 +93,22 @@ func (c *KluctlProjectContext) load(allowGit bool) error { return err } var clustersInfos []gitProjectInfo - for _, ep := range c.config.Clusters.Projects { - info, err := doClone(&ep, "clusters", c.loadArgs.LocalClusters) + if c.loadArgs.LocalClusters != "" { + clustersInfos = append(clustersInfos, c.localProject(c.loadArgs.LocalClusters)) + } else if len(c.config.Clusters.Projects) != 0 { + for _, ep := range c.config.Clusters.Projects { + info, err := doClone(&ep, "clusters", "") + if err != nil { + return err + } + clustersInfos = append(clustersInfos, info) + } + } else { + ci, err := doClone(nil, "clusters", "") if err != nil { return err } - clustersInfos = append(clustersInfos, info) + clustersInfos = append(clustersInfos, ci) } mergedClustersDir := path.Join(c.TmpDir, "merged-clusters") diff --git a/pkg/types/helm_chart.go b/pkg/types/helm_chart.go index 10df99131..dcc808f85 100644 --- a/pkg/types/helm_chart.go +++ b/pkg/types/helm_chart.go @@ -13,6 +13,7 @@ type HelmChartConfig2 struct { Namespace *string `yaml:"namespace,omitempty"` Output string `yaml:"output"` SkipCRDs *bool `yaml:"skipCRDs,omitempty"` + SkipUpdate *bool `yaml:"skipUpdate,omitempty"` } type HelmChartConfig struct { diff --git a/pkg/utils/yaml.go b/pkg/utils/yaml.go index 049c4302b..3c6cc22d3 100644 --- a/pkg/utils/yaml.go +++ b/pkg/utils/yaml.go @@ -67,7 +67,9 @@ func ReadYamlAllStream(r io.Reader) ([]interface{}, error) { } return nil, err } - l = append(l, o) + if o != nil { + l = append(l, o) + } } return l, nil From c01291c2a2874c9f8725a08a31e56e9e035663be Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sat, 12 Feb 2022 23:55:47 +0100 Subject: [PATCH 0334/2916] Collect api warnings and output them into the command result --- cmd/kluctl/cmd_bootstrap.go | 2 +- cmd/kluctl/command_result.go | 2 +- pkg/deployment/apply_utils.go | 39 ++++--- pkg/deployment/deployment_collection.go | 89 +++++++++++---- pkg/deployment/hooks_util.go | 2 +- pkg/deployment/images.go | 2 +- pkg/deployment/vars.go | 2 +- pkg/k8s/delete_utils.go | 44 +++++--- pkg/k8s/k8s_cluster.go | 142 +++++++++++++++--------- 9 files changed, 210 insertions(+), 114 deletions(-) diff --git a/cmd/kluctl/cmd_bootstrap.go b/cmd/kluctl/cmd_bootstrap.go index 6a41bcd80..8a0d9cbab 100644 --- a/cmd/kluctl/cmd_bootstrap.go +++ b/cmd/kluctl/cmd_bootstrap.go @@ -37,7 +37,7 @@ func runCmdBootstrap(cmd *cobra.Command, args_ []string) error { } return withProjectCommandContext(func(ctx *commandCtx) error { - existing, err := ctx.k.GetSingleObject(types.ObjectRef{ + existing, _, err := ctx.k.GetSingleObject(types.ObjectRef{ GVK: schema.GroupVersionKind{Group: "apiextensions.k8s.io", Version: "v1", Kind: "CustomResourceDefinition"}, Name: "sealedsecrets.bitnami.com", }) diff --git a/cmd/kluctl/command_result.go b/cmd/kluctl/command_result.go index 1ab2de6dc..23106579a 100644 --- a/cmd/kluctl/command_result.go +++ b/cmd/kluctl/command_result.go @@ -61,7 +61,7 @@ func formatCommandResultText(cr *types.CommandResult) string { if len(cr.Errors) != 0 { buf.WriteString("\nErrors:\n") - prettyErrors(buf, cr.Warnings) + prettyErrors(buf, cr.Errors) } return buf.String() diff --git a/pkg/deployment/apply_utils.go b/pkg/deployment/apply_utils.go index c2eab9da1..18b87e890 100644 --- a/pkg/deployment/apply_utils.go +++ b/pkg/deployment/apply_utils.go @@ -32,7 +32,6 @@ type applyUtil struct { appliedObjects map[types.ObjectRef]*unstructured.Unstructured appliedHookObjects map[types.ObjectRef]*unstructured.Unstructured abortSignal bool - errorRefs map[types.ObjectRef]error mutex sync.Mutex } @@ -43,7 +42,6 @@ func newApplyUtil(deploymentCollection *DeploymentCollection, k *k8s.K8sCluster, o: o, appliedObjects: map[types.ObjectRef]*unstructured.Unstructured{}, appliedHookObjects: map[types.ObjectRef]*unstructured.Unstructured{}, - errorRefs: map[types.ObjectRef]error{}, } } @@ -59,11 +57,18 @@ func (a *applyUtil) handleResult(appliedObject *unstructured.Unstructured, hook } } +func (a *applyUtil) handleApiWarnings(ref types.ObjectRef, warnings []k8s.ApiWarning) { + a.deploymentCollection.addApiWarnings(ref, warnings) +} + +func (a *applyUtil) handleWarning(ref types.ObjectRef, warning error) { + a.deploymentCollection.addWarning(ref, warning) +} + func (a *applyUtil) handleError(ref types.ObjectRef, err error) { a.mutex.Lock() defer a.mutex.Unlock() - a.errorRefs[ref] = err if a.o.abortOnError { a.abortSignal = true } @@ -72,18 +77,15 @@ func (a *applyUtil) handleError(ref types.ObjectRef, err error) { } func (a *applyUtil) hadError(ref types.ObjectRef) bool { - a.mutex.Lock() - defer a.mutex.Unlock() - - _, ok := a.errorRefs[ref] - return ok + return a.deploymentCollection.hadError(ref) } func (a *applyUtil) deleteObject(ref types.ObjectRef) bool { o := k8s.DeleteOptions{ ForceDryRun: a.o.dryRun, } - err := a.k.DeleteSingleObject(ref, o) + apiWarnings, err := a.k.DeleteSingleObject(ref, o) + a.handleApiWarnings(ref, apiWarnings) if err != nil { a.handleError(ref, err) return false @@ -110,7 +112,8 @@ func (a *applyUtil) retryApplyForceReplace(x *unstructured.Unstructured, hook bo o := k8s.PatchOptions{ ForceDryRun: a.o.dryRun, } - r, err := a.k.PatchObject(x, o) + r, apiWarnings, err := a.k.PatchObject(x, o) + a.handleApiWarnings(ref, apiWarnings) if err != nil { a.handleError(ref, err) return @@ -140,7 +143,8 @@ func (a *applyUtil) retryApplyWithReplace(x *unstructured.Unstructured, hook boo ForceDryRun: a.o.dryRun, } - r, err := a.k.UpdateObject(x, o) + r, apiWarnings, err := a.k.UpdateObject(x, o) + a.handleApiWarnings(ref, apiWarnings) if err != nil { a.retryApplyForceReplace(x, hook, err) return @@ -155,7 +159,6 @@ func (a *applyUtil) retryApplyWithConflicts(x *unstructured.Unstructured, hook b a.handleError(ref, applyError) } - var resolveWarnings []error var x2 *unstructured.Unstructured if !a.o.forceApply { statusError, ok := applyError.(*errors.StatusError) @@ -170,9 +173,8 @@ func (a *applyUtil) retryApplyWithConflicts(x *unstructured.Unstructured, hook b return } for _, lo := range lostOwnership { - resolveWarnings = append(resolveWarnings, fmt.Errorf("%s. Not updating field '%s' as we lost field ownership", lo.Message, lo.Field)) + a.deploymentCollection.addWarning(ref, fmt.Errorf("%s. Not updating field '%s' as we lost field ownership", lo.Message, lo.Field)) } - a.deploymentCollection.addWarnings(ref, resolveWarnings) x2 = x3 } else { x2 = x @@ -182,7 +184,8 @@ func (a *applyUtil) retryApplyWithConflicts(x *unstructured.Unstructured, hook b ForceDryRun: a.o.dryRun, ForceApply: true, } - r, err := a.k.PatchObject(x2, options) + r, apiWarnings, err := a.k.PatchObject(x2, options) + a.handleApiWarnings(ref, apiWarnings) if err != nil { // We didn't manage to solve it, better to abort (and not retry with replace!) a.handleError(ref, err) @@ -209,7 +212,8 @@ func (a *applyUtil) applyObject(x *unstructured.Unstructured, replaced bool, hoo options := k8s.PatchOptions{ ForceDryRun: a.o.dryRun, } - r, err := a.k.PatchObject(x, options) + r, apiWarnings, err := a.k.PatchObject(x, options) + a.handleApiWarnings(ref, apiWarnings) if err == nil { a.handleResult(r, hook) } else if meta.IsNoMatchError(err) { @@ -232,7 +236,8 @@ func (a *applyUtil) waitHook(ref types.ObjectRef) bool { didLog := false startTime := time.Now() for true { - o, err := a.k.GetSingleObject(ref) + o, apiWarnings, err := a.k.GetSingleObject(ref) + a.handleApiWarnings(ref, apiWarnings) if err != nil { if errors.IsNotFound(err) { if didLog { diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index eedbe533e..1f577b48c 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -26,8 +26,9 @@ type DeploymentCollection struct { deployments []*deploymentItem remoteObjects map[types.ObjectRef]*unstructured.Unstructured - errors map[types.DeploymentError]bool - warnings map[types.DeploymentError]bool + errors map[types.ObjectRef]map[types.DeploymentError]bool + warnings map[types.ObjectRef]map[types.DeploymentError]bool + mutex sync.Mutex } func NewDeploymentCollection(project *DeploymentProject, images *Images, inclusion *utils.Inclusion, renderDir string, forSeal bool) (*DeploymentCollection, error) { @@ -38,8 +39,8 @@ func NewDeploymentCollection(project *DeploymentProject, images *Images, inclusi RenderDir: renderDir, forSeal: forSeal, remoteObjects: map[types.ObjectRef]*unstructured.Unstructured{}, - errors: map[types.DeploymentError]bool{}, - warnings: map[types.DeploymentError]bool{}, + errors: map[types.ObjectRef]map[types.DeploymentError]bool{}, + warnings: map[types.ObjectRef]map[types.DeploymentError]bool{}, } indexes := make(map[string]int) @@ -195,7 +196,10 @@ func (c *DeploymentCollection) updateRemoteObjects(k *k8s.K8sCluster) error { log.Infof("Updating %d remote objects", len(notFoundRefsList)) - r, err := k.GetObjectsByRefs(notFoundRefsList) + r, apiWarnings, err := k.GetObjectsByRefs(notFoundRefsList) + for ref, aw := range apiWarnings { + c.addApiWarnings(ref, aw) + } if err != nil { return err } @@ -324,11 +328,15 @@ func (c *DeploymentCollection) Validate() *types.ValidateResult { var result types.ValidateResult - for e := range c.warnings { - result.Warnings = append(result.Warnings, e) + for _, m := range c.warnings { + for e := range m { + result.Warnings = append(result.Warnings, e) + } } - for e := range c.errors { - result.Errors = append(result.Errors, e) + for _, m := range c.warnings { + for e := range m { + result.Errors = append(result.Errors, e) + } } for _, d := range c.deployments { @@ -436,14 +444,19 @@ func (c *DeploymentCollection) doDiff(k *k8s.K8sCluster, appliedObjects map[type return newObjects, changedObjects, nil } -func (c *DeploymentCollection) addWarnings(ref types.ObjectRef, warnings []error) { - for _, w := range warnings { - de := types.DeploymentError{ - Ref: ref, - Error: w.Error(), - } - c.warnings[de] = true +func (c *DeploymentCollection) addWarning(ref types.ObjectRef, warning error) { + de := types.DeploymentError{ + Ref: ref, + Error: warning.Error(), } + c.mutex.Lock() + defer c.mutex.Unlock() + m, ok := c.warnings[ref] + if !ok { + m = make(map[types.DeploymentError]bool) + c.warnings[ref] = m + } + m[de] = true } func (c *DeploymentCollection) addError(ref types.ObjectRef, err error) { @@ -451,28 +464,58 @@ func (c *DeploymentCollection) addError(ref types.ObjectRef, err error) { Ref: ref, Error: err.Error(), } - c.errors[de] = true + c.mutex.Lock() + defer c.mutex.Unlock() + m, ok := c.errors[ref] + if !ok { + m = make(map[types.DeploymentError]bool) + c.errors[ref] = m + } + m[de] = true +} + +func (c *DeploymentCollection) addApiWarnings(ref types.ObjectRef, warnings []k8s.ApiWarning) { + for _, w := range warnings { + c.addWarning(ref, fmt.Errorf(w.Text)) + } +} + +func (c *DeploymentCollection) hadError(ref types.ObjectRef) bool { + c.mutex.Lock() + defer c.mutex.Unlock() + _, ok := c.errors[ref] + return ok } func (c *DeploymentCollection) errorsList() []types.DeploymentError { + c.mutex.Lock() + defer c.mutex.Unlock() var l []types.DeploymentError - for de := range c.errors { - l = append(l, de) + for _, m := range c.errors { + for de := range m { + l = append(l, de) + } } return l } func (c *DeploymentCollection) warningsList() []types.DeploymentError { + c.mutex.Lock() + defer c.mutex.Unlock() var l []types.DeploymentError - for de := range c.warnings { - l = append(l, de) + for _, m := range c.warnings { + for de := range m { + l = append(l, de) + } } return l } func (c *DeploymentCollection) clearErrorsAndWarnings() { - c.warnings = map[types.DeploymentError]bool{} - c.errors = map[types.DeploymentError]bool{} + c.mutex.Lock() + defer c.mutex.Unlock() + c.warnings = map[types.ObjectRef]map[types.DeploymentError]bool{} + c.errors = map[types.ObjectRef]map[types.DeploymentError]bool{} } func (c *DeploymentCollection) getRemoteObject(ref types.ObjectRef) *unstructured.Unstructured { diff --git a/pkg/deployment/hooks_util.go b/pkg/deployment/hooks_util.go index 25b1ff675..1e12db768 100644 --- a/pkg/deployment/hooks_util.go +++ b/pkg/deployment/hooks_util.go @@ -169,7 +169,7 @@ func (u *hooksUtil) getHook(o *unstructured.Unstructured) *hook { helmHooks := getSet("helm.sh/hook") for h := range helmHooks { if utils.FindStrInSlice(supportedHelmHooks, h) == -1 { - u.a.handleError(ref, fmt.Errorf("unsupported helm.sh/hook '%s'", h)) + u.a.handleWarning(ref, fmt.Errorf("unsupported helm.sh/hook '%s'", h)) } } diff --git a/pkg/deployment/images.go b/pkg/deployment/images.go index d564dc28d..c963d7d7a 100644 --- a/pkg/deployment/images.go +++ b/pkg/deployment/images.go @@ -132,7 +132,7 @@ func (images *Images) ResolvePlaceholders(k *k8s.K8sCluster, o *unstructured.Uns if !triedRemoteObject { triedRemoteObject = true - remoteObject, err = k.GetSingleObject(types.RefFromObject(o)) + remoteObject, _, err = k.GetSingleObject(types.RefFromObject(o)) if err != nil && !errors.IsNotFound(err) { return err } diff --git a/pkg/deployment/vars.go b/pkg/deployment/vars.go index 12d77ebdc..c8493411d 100644 --- a/pkg/deployment/vars.go +++ b/pkg/deployment/vars.go @@ -104,7 +104,7 @@ func (vc *VarsCtx) loadVarsFile(p string, searchDirs []string) error { } func (vc *VarsCtx) loadVarsFromK8sObject(k *k8s.K8sCluster, ref types.ObjectRef, key string) error { - o, err := k.GetSingleObject(ref) + o, _, err := k.GetSingleObject(ref) if err != nil { return err } diff --git a/pkg/k8s/delete_utils.go b/pkg/k8s/delete_utils.go index 114aa58f6..7f26982fa 100644 --- a/pkg/k8s/delete_utils.go +++ b/pkg/k8s/delete_utils.go @@ -144,22 +144,38 @@ func DeleteObjects(k *K8sCluster, refs []types.ObjectRef, doWait bool) (*types.C wp := utils.NewDebuggerAwareWorkerPool(8) defer wp.StopWait(false) + var ret types.CommandResult namespaceNames := make(map[string]bool) - var deletedObjects []types.ObjectRef var mutex sync.Mutex + handleResult := func(ref types.ObjectRef, apiWarnings []ApiWarning, err error) { + mutex.Lock() + defer mutex.Unlock() + + if err == nil { + ret.DeletedObjects = append(ret.DeletedObjects, ref) + } else { + ret.Errors = append(ret.Errors, types.DeploymentError{ + Ref: ref, + Error: err.Error(), + }) + } + for _, w := range apiWarnings { + ret.Warnings = append(ret.Warnings, types.DeploymentError{ + Ref: ref, + Error: w.Text, + }) + } + } + for _, ref_ := range refs { ref := ref_ if ref.GVK.GroupVersion().String() == "v1" && ref.GVK.Kind == "Namespace" { namespaceNames[ref.Name] = true wp.Submit(func() error { - err := k.DeleteSingleObject(ref, DeleteOptions{NoWait: !doWait}) - mutex.Lock() - defer mutex.Unlock() - if err == nil { - deletedObjects = append(deletedObjects, ref) - } - return err + apiWarnings, err := k.DeleteSingleObject(ref, DeleteOptions{NoWait: !doWait}) + handleResult(ref, apiWarnings, err) + return nil }) } } @@ -178,15 +194,11 @@ func DeleteObjects(k *K8sCluster, refs []types.ObjectRef, doWait bool) (*types.C continue } wp.Submit(func() error { - err := k.DeleteSingleObject(ref, DeleteOptions{NoWait: !doWait}) - mutex.Lock() - defer mutex.Unlock() - if err == nil { - deletedObjects = append(deletedObjects, ref) - } - return err + apiWarnings, err := k.DeleteSingleObject(ref, DeleteOptions{NoWait: !doWait}) + handleResult(ref, apiWarnings, err) + return nil }) } err = wp.StopWait(false) - return &types.CommandResult{DeletedObjects: deletedObjects}, err + return &ret, err } diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index 82bcf5b23..e3d94c2b7 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -34,9 +34,8 @@ type K8sCluster struct { context string DryRun bool - discovery *disk.CachedDiscoveryClient - dynamicClientPool chan dynamic.Interface - metadataClientPool chan metadata.Interface + discovery *disk.CachedDiscoveryClient + clientPool chan *parallelClientEntry ServerVersion *goversion.Version @@ -45,6 +44,27 @@ type K8sCluster struct { mutex sync.Mutex } +type parallelClientEntry struct { + dynamicClient dynamic.Interface + metadataClient metadata.Interface + + warnings []ApiWarning +} + +type ApiWarning struct { + Code int + Agent string + Text string +} + +func (p *parallelClientEntry) HandleWarningHeader(code int, agent string, text string) { + p.warnings = append(p.warnings, ApiWarning{ + Code: code, + Agent: agent, + Text: text, + }) +} + func NewK8sCluster(context string, dryRun bool) (*K8sCluster, error) { k := &K8sCluster{ context: context, @@ -76,25 +96,24 @@ func NewK8sCluster(context string, dryRun bool) (*K8sCluster, error) { k.discovery = discovery2 parallelClients := 16 - k.dynamicClientPool = make(chan dynamic.Interface, parallelClients) - k.metadataClientPool = make(chan metadata.Interface, parallelClients) + k.clientPool = make(chan *parallelClientEntry, parallelClients) for i := 0; i < parallelClients; i++ { - dynamic2, err := dynamic.NewForConfig(restConfig) + p := ¶llelClientEntry{} + restConfig.WarningHandler = p + + p.dynamicClient, err = dynamic.NewForConfig(restConfig) if err != nil { return nil, err } - metadataClient, err := metadata.NewForConfig(config) + p.metadataClient, err = metadata.NewForConfig(config) if err != nil { return nil, err } - k.dynamicClientPool <- dynamic2 - k.metadataClientPool <- metadataClient + k.clientPool <- p } - // TODO config.WarningHandler - v, err := k.discovery.ServerVersion() if err != nil { return nil, err @@ -287,34 +306,43 @@ func (k *K8sCluster) getGVRForGVK(gvk schema.GroupVersionKind) (*schema.GroupVer }, ar.Namespaced, nil } -func (k *K8sCluster) withDynamicClientForGVK(gvk schema.GroupVersionKind, namespace string, cb func(r dynamic.ResourceInterface) error) error { +func (k *K8sCluster) withDynamicClientForGVK(gvk schema.GroupVersionKind, namespace string, cb func(r dynamic.ResourceInterface) error) ([]ApiWarning, error) { gvr, namespaced, err := k.getGVRForGVK(gvk) if err != nil { - return err + return nil, err } - client := <-k.dynamicClientPool - defer func() { k.dynamicClientPool <- client }() + p := <-k.clientPool + defer func() { k.clientPool <- p }() + + p.warnings = nil if namespaced { - return cb(client.Resource(*gvr).Namespace(namespace)) + err = cb(p.dynamicClient.Resource(*gvr).Namespace(namespace)) + return append([]ApiWarning(nil), p.warnings...), err } else { - return cb(client.Resource(*gvr)) + err = cb(p.dynamicClient.Resource(*gvr)) + return append([]ApiWarning(nil), p.warnings...), err } } -func (k *K8sCluster) withMetadataClientForGVK(gvk schema.GroupVersionKind, namespace string, cb func(r metadata.ResourceInterface) error) error { +func (k *K8sCluster) withMetadataClientForGVK(gvk schema.GroupVersionKind, namespace string, cb func(r metadata.ResourceInterface) error) ([]ApiWarning, error) { gvr, namespaced, err := k.getGVRForGVK(gvk) if err != nil { - return err + return nil, err } - client := <-k.metadataClientPool - defer func() { k.metadataClientPool <- client }() + + p := <-k.clientPool + defer func() { k.clientPool <- p }() + + p.warnings = nil if namespaced { - return cb(client.Resource(*gvr).Namespace(namespace)) + err = cb(p.metadataClient.Resource(*gvr).Namespace(namespace)) + return append([]ApiWarning(nil), p.warnings...), err } else { - return cb(client.Resource(*gvr)) + err = cb(p.metadataClient.Resource(*gvr)) + return append([]ApiWarning(nil), p.warnings...), err } } @@ -330,10 +358,10 @@ func (k *K8sCluster) buildLabelSelector(labels map[string]string) string { return ret } -func (k *K8sCluster) ListObjects(gvk schema.GroupVersionKind, namespace string) (*unstructured.UnstructuredList, error) { +func (k *K8sCluster) ListObjects(gvk schema.GroupVersionKind, namespace string) (*unstructured.UnstructuredList, []ApiWarning, error) { var result *unstructured.UnstructuredList - err := k.withDynamicClientForGVK(gvk, namespace, func(r dynamic.ResourceInterface) error { + apiWarnings, err := k.withDynamicClientForGVK(gvk, namespace, func(r dynamic.ResourceInterface) error { o := v1.ListOptions{} x, err := r.List(context.Background(), o) if err != nil { @@ -342,13 +370,13 @@ func (k *K8sCluster) ListObjects(gvk schema.GroupVersionKind, namespace string) result = x return nil }) - return result, err + return result, apiWarnings, err } -func (k *K8sCluster) ListObjectsMetadata(gvk schema.GroupVersionKind, namespace string, labels map[string]string) (*v1.PartialObjectMetadataList, error) { +func (k *K8sCluster) ListObjectsMetadata(gvk schema.GroupVersionKind, namespace string, labels map[string]string) (*v1.PartialObjectMetadataList, []ApiWarning, error) { var result *v1.PartialObjectMetadataList - err := k.withMetadataClientForGVK(gvk, namespace, func(r metadata.ResourceInterface) error { + apiWarnings, err := k.withMetadataClientForGVK(gvk, namespace, func(r metadata.ResourceInterface) error { o := v1.ListOptions{ LabelSelector: k.buildLabelSelector(labels), } @@ -359,7 +387,7 @@ func (k *K8sCluster) ListObjectsMetadata(gvk schema.GroupVersionKind, namespace result = x return nil }) - return result, err + return result, apiWarnings, err } func (k *K8sCluster) ListAllObjectsMetadata(verbs []string, namespace string, labels map[string]string) ([]*v1.PartialObjectMetadata, error) { @@ -367,6 +395,7 @@ func (k *K8sCluster) ListAllObjectsMetadata(verbs []string, namespace string, la defer wp.StopWait(false) var ret []*v1.PartialObjectMetadata + var retApiWarnings []ApiWarning var mutex sync.Mutex k.mutex.Lock() @@ -387,7 +416,7 @@ func (k *K8sCluster) ListAllObjectsMetadata(verbs []string, namespace string, la Kind: ar.Kind, } wp.Submit(func() error { - lm, err := k.ListObjectsMetadata(gvk, namespace, labels) + lm, apiWarnings, err := k.ListObjectsMetadata(gvk, namespace, labels) if err != nil { return err } @@ -398,6 +427,7 @@ func (k *K8sCluster) ListAllObjectsMetadata(verbs []string, namespace string, la o.SetGroupVersionKind(gvk) ret = append(ret, &o) } + retApiWarnings = append(retApiWarnings, apiWarnings...) return nil }) } @@ -411,9 +441,9 @@ func (k *K8sCluster) ListAllObjectsMetadata(verbs []string, namespace string, la return ret, nil } -func (k *K8sCluster) GetSingleObject(ref types2.ObjectRef) (*unstructured.Unstructured, error) { +func (k *K8sCluster) GetSingleObject(ref types2.ObjectRef) (*unstructured.Unstructured, []ApiWarning, error) { var result *unstructured.Unstructured - err := k.withDynamicClientForGVK(ref.GVK, ref.Namespace, func(r dynamic.ResourceInterface) error { + apiWarnings, err := k.withDynamicClientForGVK(ref.GVK, ref.Namespace, func(r dynamic.ResourceInterface) error { o := v1.GetOptions{} x, err := r.Get(context.Background(), ref.Name, o) if err != nil { @@ -422,36 +452,42 @@ func (k *K8sCluster) GetSingleObject(ref types2.ObjectRef) (*unstructured.Unstru result = x return nil }) - return result, err + return result, apiWarnings, err } -func (k *K8sCluster) GetObjectsByRefs(refs []types2.ObjectRef) ([]*unstructured.Unstructured, error) { +func (k *K8sCluster) GetObjectsByRefs(refs []types2.ObjectRef) ([]*unstructured.Unstructured, map[types2.ObjectRef][]ApiWarning, error) { wp := utils.NewWorkerPoolWithErrors(32) defer wp.StopWait(false) + var ret []*unstructured.Unstructured + retApiWarnings := make(map[types2.ObjectRef][]ApiWarning) + var mutex sync.Mutex + for _, ref_ := range refs { ref := ref_ - wp.SubmitWithResult(func() (interface{}, error) { - o, err := k.GetSingleObject(ref) + wp.Submit(func() error { + o, apiWarnings, err := k.GetSingleObject(ref) + mutex.Lock() + defer mutex.Unlock() + if len(apiWarnings) != 0 { + retApiWarnings[ref] = apiWarnings + } if err != nil { if errors.IsNotFound(err) { - return nil, nil + return nil } - return nil, err + return err } - return o, nil + ret = append(ret, o) + return nil }) } err := wp.StopWait(false) if err != nil { - return nil, err + return nil, retApiWarnings, err } - var results []*unstructured.Unstructured - for _, o := range wp.Results() { - results = append(results, o.(*unstructured.Unstructured)) - } - return results, nil + return ret, retApiWarnings, nil } type DeleteOptions struct { @@ -460,7 +496,7 @@ type DeleteOptions struct { ErrorOnNotFound bool } -func (k *K8sCluster) DeleteSingleObject(ref types2.ObjectRef, options DeleteOptions) error { +func (k *K8sCluster) DeleteSingleObject(ref types2.ObjectRef, options DeleteOptions) ([]ApiWarning, error) { dryRun := k.DryRun || options.ForceDryRun pp := v1.DeletePropagationForeground @@ -595,14 +631,14 @@ type PatchOptions struct { ForceApply bool } -func (k *K8sCluster) PatchObject(o *unstructured.Unstructured, options PatchOptions) (*unstructured.Unstructured, error) { +func (k *K8sCluster) PatchObject(o *unstructured.Unstructured, options PatchOptions) (*unstructured.Unstructured, []ApiWarning, error) { dryRun := k.DryRun || options.ForceDryRun ref := types2.RefFromObject(o) log2 := log.WithField("ref", ref) data, err := o.MarshalJSON() if err != nil { - return nil, err + return nil, nil, err } po := v1.PatchOptions{ @@ -617,7 +653,7 @@ func (k *K8sCluster) PatchObject(o *unstructured.Unstructured, options PatchOpti log2.Debugf("patching") var result *unstructured.Unstructured - err = k.withDynamicClientForGVK(ref.GVK, ref.Namespace, func(r dynamic.ResourceInterface) error { + apiWarnings, err := k.withDynamicClientForGVK(ref.GVK, ref.Namespace, func(r dynamic.ResourceInterface) error { x, err := r.Patch(context.Background(), ref.Name, types.ApplyPatchType, data, po) if err != nil { return err @@ -625,14 +661,14 @@ func (k *K8sCluster) PatchObject(o *unstructured.Unstructured, options PatchOpti result = x return nil }) - return result, err + return result, apiWarnings, err } type UpdateOptions struct { ForceDryRun bool } -func (k *K8sCluster) UpdateObject(o *unstructured.Unstructured, options UpdateOptions) (*unstructured.Unstructured, error) { +func (k *K8sCluster) UpdateObject(o *unstructured.Unstructured, options UpdateOptions) (*unstructured.Unstructured, []ApiWarning, error) { dryRun := k.DryRun || options.ForceDryRun ref := types2.RefFromObject(o) log2 := log.WithField("ref", ref) @@ -646,7 +682,7 @@ func (k *K8sCluster) UpdateObject(o *unstructured.Unstructured, options UpdateOp log2.Debugf("updating") var result *unstructured.Unstructured - err := k.withDynamicClientForGVK(ref.GVK, ref.Namespace, func(r dynamic.ResourceInterface) error { + apiWarnings, err := k.withDynamicClientForGVK(ref.GVK, ref.Namespace, func(r dynamic.ResourceInterface) error { x, err := r.Update(context.Background(), o, uo) if err != nil { return err @@ -654,5 +690,5 @@ func (k *K8sCluster) UpdateObject(o *unstructured.Unstructured, options UpdateOp result = x return nil }) - return result, err + return result, apiWarnings, err } From a606e2df7e205e80d85bdc455a7d140ef1b3d272 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sun, 13 Feb 2022 00:46:15 +0100 Subject: [PATCH 0335/2916] Allow deploymentItems to also do includes and deprecate the old includes --- pkg/deployment/deployment_collection.go | 81 ++++++++++++++----------- pkg/deployment/deployment_item.go | 4 -- pkg/deployment/deployment_project.go | 79 +++++++++++++++++------- pkg/types/deployment.go | 28 +++------ 4 files changed, 113 insertions(+), 79 deletions(-) diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index 1f577b48c..eda7d9d68 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -11,7 +11,6 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "path" "path/filepath" - "strings" "sync" "time" ) @@ -52,53 +51,63 @@ func NewDeploymentCollection(project *DeploymentProject, images *Images, inclusi return dc, nil } -func (c *DeploymentCollection) collectDeployments(project *DeploymentProject, indexes map[string]int) ([]*deploymentItem, error) { - var ret []*deploymentItem +func (c *DeploymentCollection) createBarrierDummy(project *DeploymentProject) *deploymentItem { + b := true + tmpDiConfig := &types.DeploymentItemConfig{ + Barrier: &b, + } + di, err := NewDeploymentItem(project, c, tmpDiConfig, nil, 0) + if err != nil { + log.Fatal(err) + } + return di +} - for _, diConfig := range project.config.DeploymentItems { - var dir2 *string - index := 0 - if diConfig.Path != nil { - dir := path.Join(project.dir, *diConfig.Path) - dir, err := filepath.Abs(dir) - if err != nil { - return nil, err - } - if !strings.HasPrefix(dir, project.getRootProject().dir) { - return nil, fmt.Errorf("kustomizeDir path is not part of root deployment project: %s", *diConfig.Path) - } - if _, ok := indexes[dir]; !ok { - indexes[dir] = 0 - } - index, _ = indexes[dir] - indexes[dir] = index + 1 - dir2 = &dir - } - di, err := NewDeploymentItem(project, c, diConfig, dir2, index) +func findDeploymentItemIndex(project *DeploymentProject, diConfig *types.DeploymentItemConfig, indexes map[string]int) (int, *string) { + var dir2 *string + index := 0 + if diConfig.Path != nil { + dir := path.Join(project.dir, *diConfig.Path) + absDir, err := filepath.Abs(dir) if err != nil { - return nil, err + // we pre-checked directories, so this should not happen + log.Fatal(err) + } + + if _, ok := indexes[absDir]; !ok { + indexes[absDir] = 0 } - ret = append(ret, di) + index, _ = indexes[absDir] + indexes[absDir] = index + 1 + dir2 = &absDir } + return index, dir2 +} + +func (c *DeploymentCollection) collectDeployments(project *DeploymentProject, indexes map[string]int) ([]*deploymentItem, error) { + var ret []*deploymentItem - for i, incConf := range project.config.Includes { - if incConf.Barrier != nil && *incConf.Barrier { - tmpDiConfig := &types.DeploymentItemConfig{ - BaseItemConfig: incConf.BaseItemConfig, + for i, diConfig := range project.config.DeploymentItems { + if project.isIncludeDeployment(diConfig) { + if diConfig.Barrier != nil && *diConfig.Barrier { + ret = append(ret, c.createBarrierDummy(project)) } - di, err := NewDeploymentItem(project, c, tmpDiConfig, nil, 0) - if err != nil { - return nil, err + includedProject, ok := project.includes[i] + if !ok { + log.Fatalf("Did not find find index %d in project.includes", i) } - ret = append(ret, di) - } - - if includedProject, ok := project.includes[i]; ok { ret2, err := c.collectDeployments(includedProject, indexes) if err != nil { return nil, err } ret = append(ret, ret2...) + } else { + index, dir2 := findDeploymentItemIndex(project, diConfig, indexes) + di, err := NewDeploymentItem(project, c, diConfig, dir2, index) + if err != nil { + return nil, err + } + ret = append(ret, di) } } diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 3ad91d2b3..7c78662a1 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -44,10 +44,6 @@ func NewDeploymentItem(project *DeploymentProject, collection *DeploymentCollect index: index, } - if dir != nil && !utils.IsDirectory(*dir) { - return nil, fmt.Errorf("kustomizeDir does not exist: %s", *dir) - } - var err error rootProject := di.project.getRootProject() diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go index 21c8b883f..fa4ff24f0 100644 --- a/pkg/deployment/deployment_project.go +++ b/pkg/deployment/deployment_project.go @@ -13,6 +13,7 @@ import ( ) var kustomizeDirsDeprecatedOnce sync.Once +var includesDeprecatedOnce sync.Once type DeploymentProject struct { varsCtx *VarsCtx @@ -24,7 +25,7 @@ type DeploymentProject struct { includes map[int]*DeploymentProject parentProject *DeploymentProject - parentProjectInclude *types.IncludeItemConfig + parentProjectInclude *types.DeploymentItemConfig } func NewDeploymentProject(k *k8s.K8sCluster, varsCtx *VarsCtx, dir string, sealedSecretsDir string, parentProject *DeploymentProject) (*DeploymentProject, error) { @@ -40,7 +41,7 @@ func NewDeploymentProject(k *k8s.K8sCluster, varsCtx *VarsCtx, dir string, seale return nil, fmt.Errorf("%s does not exist or is not a directory", dir) } - err := dp.loadBaseConfig(k) + err := dp.loadConfig(k) if err != nil { return nil, fmt.Errorf("failed to load deployment config for %s: %w", dir, err) } @@ -53,7 +54,7 @@ func NewDeploymentProject(k *k8s.K8sCluster, varsCtx *VarsCtx, dir string, seale return dp, nil } -func (p *DeploymentProject) loadBaseConfig(k *k8s.K8sCluster) error { +func (p *DeploymentProject) loadConfig(k *k8s.K8sCluster) error { configPath := path.Join(p.dir, "deployment.yml") if !utils.Exists(configPath) { if utils.Exists(path.Join(p.dir, "kustomization.yml")) { @@ -73,30 +74,39 @@ func (p *DeploymentProject) loadBaseConfig(k *k8s.K8sCluster) error { } // TODO remove obsolete code - if len(p.config.DeploymentItems) != 0 && len(p.config.KustomizeDirs) != 0 { - return fmt.Errorf("using deploymentItems and kustomizeDirs at the same time is not allowed") + if len(p.config.DeploymentItems) != 0 && (len(p.config.KustomizeDirs) != 0 || len(p.config.Includes) != 0) { + return fmt.Errorf("using 'deploymentItems' and 'kustomizeDirs' at the same time is not allowed") } if len(p.config.KustomizeDirs) != 0 { kustomizeDirsDeprecatedOnce.Do(func() { - log.Warningf("kustomizeDirs is deprecated, use deploymentItems instead") + log.Warningf("'kustomizeDirs' is deprecated, use 'deploymentItems' instead") }) p.config.DeploymentItems = p.config.KustomizeDirs p.config.KustomizeDirs = nil } + if len(p.config.Includes) != 0 { + includesDeprecatedOnce.Do(func() { + log.Warningf("'includes' is deprecated, use 'deploymentItems' instead") + }) + p.config.DeploymentItems = append(p.config.DeploymentItems, p.config.Includes...) + p.config.Includes = nil + } // If there are no explicit tags set, interpret the path as a tag, which allows to // enable/disable single deployments via included/excluded tags - setDefaultTag := func(item *types.BaseItemConfig) { + setDefaultTag := func(item *types.DeploymentItemConfig) { if len(item.Tags) != 0 || item.Path == nil { return } item.Tags = []string{path.Base(*item.Path)} } for _, di := range p.config.DeploymentItems { - setDefaultTag(&di.BaseItemConfig) + setDefaultTag(di) } - for _, di := range p.config.Includes { - setDefaultTag(&di.BaseItemConfig) + + err = p.checkDeploymentDirs() + if err != nil { + return err } if len(p.getCommonLabels()) == 0 { @@ -108,25 +118,52 @@ func (p *DeploymentProject) loadBaseConfig(k *k8s.K8sCluster) error { return nil } -func (p *DeploymentProject) loadIncludes(k *k8s.K8sCluster) error { - for i, inc := range p.config.Includes { - if inc.Path == nil { +func (p *DeploymentProject) checkDeploymentDirs() error { + rootProject := p.getRootProject() + for _, di := range p.config.DeploymentItems { + if di.Path == nil { continue } - rootProject := p.getRootProject() - incDir := path.Join(p.dir, *inc.Path) - incDir, err := filepath.Abs(incDir) + diDir := path.Join(p.dir, *di.Path) + diDir, err := filepath.Abs(diDir) if err != nil { return err } - if !strings.HasPrefix(incDir, rootProject.dir) { - return fmt.Errorf("include path is not part of root deployment project: %s", *inc.Path) + if !strings.HasPrefix(diDir, rootProject.dir) { + return fmt.Errorf("path is not part of root deployment project: %s", *di.Path) + } + + if !utils.Exists(diDir) { + return fmt.Errorf("deployment directory does not exist: %s", *di.Path) } + if !utils.IsDirectory(diDir) { + return fmt.Errorf("deployment path is not a directory: %s", *di.Path) + } + } + return nil +} + +func (p *DeploymentProject) isIncludeDeployment(di *types.DeploymentItemConfig) bool { + if di.Path == nil { + return false + } + + incDir := path.Join(p.dir, *di.Path, "deployment.yml") + return utils.Exists(incDir) +} + +func (p *DeploymentProject) loadIncludes(k *k8s.K8sCluster) error { + for i, inc := range p.config.DeploymentItems { + if !p.isIncludeDeployment(inc) { + continue + } + + incDir := path.Join(p.dir, *inc.Path) varsCtx := p.varsCtx.Copy() - err = varsCtx.loadVarsList(k, p.getRenderSearchDirs(), inc.Vars) + err := varsCtx.loadVarsList(k, p.getRenderSearchDirs(), inc.Vars) if err != nil { return err } @@ -159,12 +196,12 @@ func (p *DeploymentProject) getRootProject() *DeploymentProject { type deploymentProjectAndIncludeItem struct { p *DeploymentProject - inc *types.IncludeItemConfig + inc *types.DeploymentItemConfig } func (p *DeploymentProject) getParents() []deploymentProjectAndIncludeItem { var parents []deploymentProjectAndIncludeItem - var inc *types.IncludeItemConfig + var inc *types.DeploymentItemConfig d := p for d != nil { parents = append(parents, deploymentProjectAndIncludeItem{p: d, inc: inc}) diff --git a/pkg/types/deployment.go b/pkg/types/deployment.go index cf94133f2..e31218aa2 100644 --- a/pkg/types/deployment.go +++ b/pkg/types/deployment.go @@ -7,22 +7,14 @@ import ( "gopkg.in/yaml.v3" ) -type BaseItemConfig struct { - Path *string `yaml:"path,omitempty"` - Tags []string `yaml:"tags,omitempty"` - Barrier *bool `yaml:"barrier,omitempty"` - Vars []*VarsListItem `yaml:"vars,omitempty"` -} - type DeploymentItemConfig struct { - BaseItemConfig `yaml:"base_item_config,inline"` - SkipDeleteIfTags *bool `yaml:"skipDeleteIfTags,omitempty"` - OnlyRender *bool `yaml:"onlyRender,omitempty"` - AlwaysDeploy *bool `yaml:"alwaysDeploy,omitempty"` -} - -type IncludeItemConfig struct { - BaseItemConfig `yaml:"base_item_config,inline"` + Path *string `yaml:"path,omitempty"` + Tags []string `yaml:"tags,omitempty"` + Barrier *bool `yaml:"barrier,omitempty"` + Vars []*VarsListItem `yaml:"vars,omitempty"` + SkipDeleteIfTags *bool `yaml:"skipDeleteIfTags,omitempty"` + OnlyRender *bool `yaml:"onlyRender,omitempty"` + AlwaysDeploy *bool `yaml:"alwaysDeploy,omitempty"` } type DeploymentArg struct { @@ -100,11 +92,11 @@ type DeploymentProjectConfig struct { Vars []*VarsListItem `yaml:"vars,omitempty"` SealedSecrets *SealedSecretsConfig `yaml:"sealedSecrets,omitempty"` - // Obsolete DeploymentItems []*DeploymentItemConfig `yaml:"deploymentItems,omitempty"` - KustomizeDirs []*DeploymentItemConfig `yaml:"kustomizeDirs,omitempty"` - Includes []*IncludeItemConfig `yaml:"includes,omitempty"` + // Obsolete + KustomizeDirs []*DeploymentItemConfig `yaml:"kustomizeDirs,omitempty"` + Includes []*DeploymentItemConfig `yaml:"includes,omitempty"` CommonLabels map[string]string `yaml:"commonLabels,omitempty"` DeleteByLabels map[string]string `yaml:"deleteByLabels,omitempty"` From 6548fbd7b754cdfa38c1f20ff219aa4d8a881b00 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sun, 13 Feb 2022 00:47:14 +0100 Subject: [PATCH 0336/2916] Rename deploymentItems to deployments --- pkg/deployment/deployment_collection.go | 2 +- pkg/deployment/deployment_project.go | 18 +++++++++--------- pkg/types/deployment.go | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index eda7d9d68..345757508 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -87,7 +87,7 @@ func findDeploymentItemIndex(project *DeploymentProject, diConfig *types.Deploym func (c *DeploymentCollection) collectDeployments(project *DeploymentProject, indexes map[string]int) ([]*deploymentItem, error) { var ret []*deploymentItem - for i, diConfig := range project.config.DeploymentItems { + for i, diConfig := range project.config.Deployments { if project.isIncludeDeployment(diConfig) { if diConfig.Barrier != nil && *diConfig.Barrier { ret = append(ret, c.createBarrierDummy(project)) diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go index fa4ff24f0..3d7413a63 100644 --- a/pkg/deployment/deployment_project.go +++ b/pkg/deployment/deployment_project.go @@ -74,21 +74,21 @@ func (p *DeploymentProject) loadConfig(k *k8s.K8sCluster) error { } // TODO remove obsolete code - if len(p.config.DeploymentItems) != 0 && (len(p.config.KustomizeDirs) != 0 || len(p.config.Includes) != 0) { - return fmt.Errorf("using 'deploymentItems' and 'kustomizeDirs' at the same time is not allowed") + if len(p.config.Deployments) != 0 && (len(p.config.KustomizeDirs) != 0 || len(p.config.Includes) != 0) { + return fmt.Errorf("using 'deployments' and 'kustomizeDirs' at the same time is not allowed") } if len(p.config.KustomizeDirs) != 0 { kustomizeDirsDeprecatedOnce.Do(func() { - log.Warningf("'kustomizeDirs' is deprecated, use 'deploymentItems' instead") + log.Warningf("'kustomizeDirs' is deprecated, use 'deployments' instead") }) - p.config.DeploymentItems = p.config.KustomizeDirs + p.config.Deployments = p.config.KustomizeDirs p.config.KustomizeDirs = nil } if len(p.config.Includes) != 0 { includesDeprecatedOnce.Do(func() { - log.Warningf("'includes' is deprecated, use 'deploymentItems' instead") + log.Warningf("'includes' is deprecated, use 'deployments' instead") }) - p.config.DeploymentItems = append(p.config.DeploymentItems, p.config.Includes...) + p.config.Deployments = append(p.config.Deployments, p.config.Includes...) p.config.Includes = nil } @@ -100,7 +100,7 @@ func (p *DeploymentProject) loadConfig(k *k8s.K8sCluster) error { } item.Tags = []string{path.Base(*item.Path)} } - for _, di := range p.config.DeploymentItems { + for _, di := range p.config.Deployments { setDefaultTag(di) } @@ -120,7 +120,7 @@ func (p *DeploymentProject) loadConfig(k *k8s.K8sCluster) error { func (p *DeploymentProject) checkDeploymentDirs() error { rootProject := p.getRootProject() - for _, di := range p.config.DeploymentItems { + for _, di := range p.config.Deployments { if di.Path == nil { continue } @@ -155,7 +155,7 @@ func (p *DeploymentProject) isIncludeDeployment(di *types.DeploymentItemConfig) } func (p *DeploymentProject) loadIncludes(k *k8s.K8sCluster) error { - for i, inc := range p.config.DeploymentItems { + for i, inc := range p.config.Deployments { if !p.isIncludeDeployment(inc) { continue } diff --git a/pkg/types/deployment.go b/pkg/types/deployment.go index e31218aa2..879289102 100644 --- a/pkg/types/deployment.go +++ b/pkg/types/deployment.go @@ -92,7 +92,7 @@ type DeploymentProjectConfig struct { Vars []*VarsListItem `yaml:"vars,omitempty"` SealedSecrets *SealedSecretsConfig `yaml:"sealedSecrets,omitempty"` - DeploymentItems []*DeploymentItemConfig `yaml:"deploymentItems,omitempty"` + Deployments []*DeploymentItemConfig `yaml:"deployments,omitempty"` // Obsolete KustomizeDirs []*DeploymentItemConfig `yaml:"kustomizeDirs,omitempty"` From dc8a377d6ef486b2c4f41e01dda301f8e462ce1d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sun, 13 Feb 2022 01:19:35 +0100 Subject: [PATCH 0337/2916] Consitently use utils package for yaml reading/writing --- cmd/kluctl/command_result.go | 7 +++---- pkg/deployment/external_args.go | 3 ++- pkg/deployment/helm_chart.go | 3 +-- pkg/deployment/vars.go | 5 ++--- pkg/diff/diff.go | 5 ++--- pkg/types/cluster_config.go | 9 +-------- pkg/types/deployment.go | 10 +++++----- pkg/utils/unstructured.go | 9 ++++----- pkg/utils/yaml.go | 8 ++++++++ 9 files changed, 28 insertions(+), 31 deletions(-) diff --git a/cmd/kluctl/command_result.go b/cmd/kluctl/command_result.go index 23106579a..feb78fc7b 100644 --- a/cmd/kluctl/command_result.go +++ b/cmd/kluctl/command_result.go @@ -5,7 +5,6 @@ import ( "fmt" "github.com/codablock/kluctl/pkg/types" "github.com/codablock/kluctl/pkg/utils" - "gopkg.in/yaml.v2" "io" "os" "strings" @@ -93,11 +92,11 @@ func prettyChanges(buf io.StringWriter, ref types.ObjectRef, changes []types.Cha } func formatCommandResultYaml(cr *types.CommandResult) (string, error) { - b, err := yaml.Marshal(cr) + b, err := utils.WriteYamlString(cr) if err != nil { return "", err } - return string(b), nil + return b, nil } func formatCommandResult(cr *types.CommandResult, format string) (string, error) { @@ -149,7 +148,7 @@ func formatValidateResultText(vr *types.ValidateResult) string { } func formatValidateResultYaml(vr *types.ValidateResult) (string, error) { - b, err := yaml.Marshal(vr) + b, err := utils.WriteYamlString(vr) if err != nil { return "", err } diff --git a/pkg/deployment/external_args.go b/pkg/deployment/external_args.go index d41790262..10f51e616 100644 --- a/pkg/deployment/external_args.go +++ b/pkg/deployment/external_args.go @@ -3,6 +3,7 @@ package deployment import ( "fmt" "github.com/codablock/kluctl/pkg/types" + "github.com/codablock/kluctl/pkg/utils" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "path" "regexp" @@ -42,7 +43,7 @@ func CheckRequiredDeployArgs(dir string, varsCtx *VarsCtx, deployArgs map[string var conf types.DeploymentProjectConfig - err := types.LoadDeploymentProjectConfig(path.Join(dir, "deployment.yml"), &conf) + err := utils.ReadYamlFile(path.Join(dir, "deployment.yml"), &conf) if err != nil { // If that failed, it might be that conditional jinja blocks are present in the config, so lets try loading // the config in rendered form. If it fails due to missing args now, we can't help much with better error diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index c2d550b91..2b22a5ab5 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -6,7 +6,6 @@ import ( "github.com/codablock/kluctl/pkg/types" "github.com/codablock/kluctl/pkg/utils" goversion "github.com/hashicorp/go-version" - "gopkg.in/yaml.v2" "io/ioutil" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "os" @@ -147,7 +146,7 @@ func (c *helmChart) CheckUpdate() (string, error) { // ensure we didn't get partial matches var lm []map[string]string var ls VersionSlice - err = yaml.Unmarshal([]byte(stdout), &lm) + err = utils.ReadYamlBytes(stdout, &lm) if err != nil { return err } diff --git a/pkg/deployment/vars.go b/pkg/deployment/vars.go index c8493411d..9a74b42c2 100644 --- a/pkg/deployment/vars.go +++ b/pkg/deployment/vars.go @@ -7,7 +7,6 @@ import ( "github.com/codablock/kluctl/pkg/types" "github.com/codablock/kluctl/pkg/utils" log "github.com/sirupsen/logrus" - "gopkg.in/yaml.v3" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) @@ -140,7 +139,7 @@ func (vc *VarsCtx) renderYamlString(s string, out interface{}) error { return err } - err = yaml.Unmarshal([]byte(ret), out) + err = utils.ReadYamlString(ret, out) if err != nil { return err } @@ -154,7 +153,7 @@ func (vc *VarsCtx) renderYamlFile(p string, searchDirs []string, out interface{} return err } - err = yaml.Unmarshal([]byte(ret), out) + err = utils.ReadYamlString(ret, out) if err != nil { return err } diff --git a/pkg/diff/diff.go b/pkg/diff/diff.go index 0cbb68a21..3dec29c54 100644 --- a/pkg/diff/diff.go +++ b/pkg/diff/diff.go @@ -8,7 +8,6 @@ import ( "github.com/hexops/gotextdiff/myers" "github.com/hexops/gotextdiff/span" diff2 "github.com/r3labs/diff/v2" - "gopkg.in/yaml.v3" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "strconv" "strings" @@ -134,11 +133,11 @@ func objectToDiffableString(o interface{}) (string, error) { return v, nil } - b, err := yaml.Marshal(o) + b, err := utils.WriteYamlString(o) if err != nil { return "", err } - return string(b), nil + return b, nil } func prependStrToLines(s string, prepend string) string { diff --git a/pkg/types/cluster_config.go b/pkg/types/cluster_config.go index a11d346d9..42f9557cc 100644 --- a/pkg/types/cluster_config.go +++ b/pkg/types/cluster_config.go @@ -3,8 +3,6 @@ package types import ( "fmt" "github.com/codablock/kluctl/pkg/utils" - "gopkg.in/yaml.v3" - "io/ioutil" "path" ) @@ -28,13 +26,8 @@ func LoadClusterConfig(clusterDir string, clusterName string) (*ClusterConfig, e return nil, fmt.Errorf("cluster config for %s not found", clusterName) } - b, err := ioutil.ReadFile(p) - if err != nil { - return nil, err - } - var config ClusterConfig - err = yaml.Unmarshal(b, &config) + err := utils.ReadYamlFile(p, &config) if err != nil { return nil, err } diff --git a/pkg/types/deployment.go b/pkg/types/deployment.go index 879289102..55d0b5938 100644 --- a/pkg/types/deployment.go +++ b/pkg/types/deployment.go @@ -2,7 +2,6 @@ package types import ( "fmt" - "github.com/codablock/kluctl/pkg/utils" "github.com/go-playground/validator/v10" "gopkg.in/yaml.v3" ) @@ -107,14 +106,15 @@ type DeploymentProjectConfig struct { TemplateExcludes []string `yaml:"TemplateExcludes,omitempty"` } -func LoadDeploymentProjectConfig(p string, o *DeploymentProjectConfig) error { - err := utils.ReadYamlFile(p, o) +func (c *DeploymentProjectConfig) UnmarshalYAML(value *yaml.Node) error { + type raw DeploymentProjectConfig + err := value.Decode((*raw)(c)) if err != nil { return err } - err = validate.Struct(o) + err = validate.Struct(c) if err != nil { - return fmt.Errorf("validation for %v failed: %w", p, err) + return fmt.Errorf("validation for DeploymentProjectConfig failed: %w", err) } return nil } diff --git a/pkg/utils/unstructured.go b/pkg/utils/unstructured.go index cc6e632e2..bb7689c3d 100644 --- a/pkg/utils/unstructured.go +++ b/pkg/utils/unstructured.go @@ -3,7 +3,6 @@ package utils import ( "fmt" log "github.com/sirupsen/logrus" - "gopkg.in/yaml.v3" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) @@ -21,13 +20,13 @@ func CopyUnstructured(u *unstructured.Unstructured) *unstructured.Unstructured { } func StructToObject(s interface{}) (map[string]interface{}, error) { - b, err := yaml.Marshal(s) + b, err := WriteYamlBytes(s) if err != nil { return nil, err } m := make(map[string]interface{}) - err = yaml.Unmarshal(b, m) + err = ReadYamlBytes(b, m) if err != nil { return nil, err } @@ -35,11 +34,11 @@ func StructToObject(s interface{}) (map[string]interface{}, error) { } func ObjectToStruct(m map[string]interface{}, dst interface{}) error { - b, err := yaml.Marshal(m) + b, err := WriteYamlBytes(m) if err != nil { return err } - return yaml.Unmarshal(b, dst) + return ReadYamlBytes(b, dst) } func MergeObject(a map[string]interface{}, b map[string]interface{}) { diff --git a/pkg/utils/yaml.go b/pkg/utils/yaml.go index 3c6cc22d3..e627ecd19 100644 --- a/pkg/utils/yaml.go +++ b/pkg/utils/yaml.go @@ -28,6 +28,10 @@ func ReadYamlString(s string, o interface{}) error { return ReadYamlStream(strings.NewReader(s), o) } +func ReadYamlBytes(b []byte, o interface{}) error { + return ReadYamlStream(bytes.NewReader(b), o) +} + func ReadYamlStream(r io.Reader, o interface{}) error { d := yaml.NewDecoder(r) d.KnownFields(true) @@ -79,6 +83,10 @@ func WriteYamlString(o interface{}) (string, error) { return WriteYamlAllString([]interface{}{o}) } +func WriteYamlBytes(o interface{}) ([]byte, error) { + return WriteYamlAllBytes([]interface{}{o}) +} + func WriteYamlFile(p string, o interface{}) error { return WriteYamlAllFile(p, []interface{}{o}) } From 2224c8911a6c63eedbe3b5bd59617f0f37a1ebde Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sun, 13 Feb 2022 02:42:56 +0100 Subject: [PATCH 0338/2916] Switch to github.com/goccy/go-yaml --- cmd/kluctl/args/images.go | 3 +- go.mod | 9 ++++-- go.sum | 11 ++++++++ pkg/deployment/helm_chart.go | 5 ++-- pkg/deployment/vars.go | 4 +-- pkg/git/git-url/url.go | 5 ++-- pkg/jinja2_server/server.go | 6 ++++ pkg/kluctl_project/load.go | 4 +-- pkg/kluctl_project/load_targets.go | 2 +- pkg/types/cluster_config.go | 45 ++++++++++++++++++++++++++++-- pkg/types/deployment.go | 24 ++++------------ pkg/types/git_project.go | 16 +++++------ pkg/types/helm_chart.go | 18 ------------ pkg/types/kluctl_project.go | 15 +--------- pkg/types/target_config.go | 29 ------------------- pkg/utils/unstructured.go | 4 +-- pkg/{types => utils}/validator.go | 4 +-- pkg/utils/yaml.go | 19 ++++++++----- 18 files changed, 109 insertions(+), 114 deletions(-) rename pkg/{types => utils}/validator.go (57%) diff --git a/cmd/kluctl/args/images.go b/cmd/kluctl/args/images.go index c82c1bc52..73872b729 100644 --- a/cmd/kluctl/args/images.go +++ b/cmd/kluctl/args/images.go @@ -3,6 +3,7 @@ package args import ( "fmt" "github.com/codablock/kluctl/pkg/types" + "github.com/codablock/kluctl/pkg/utils" "github.com/spf13/cobra" "strings" ) @@ -25,7 +26,7 @@ func LoadFixedImagesFromArgs() (*types.FixedImagesConfig, error) { var ret types.FixedImagesConfig if FixedImagesFile != "" { - err := types.LoadFixedImagesConfig(FixedImagesFile, &ret) + err := utils.ReadYamlFile(FixedImagesFile, &ret) if err != nil { return nil, err } diff --git a/go.mod b/go.mod index cd3f26a3e..db0d51385 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/go-git/go-git/v5 v5.4.2 github.com/go-playground/validator/v10 v10.10.0 github.com/gobwas/glob v0.2.3 + github.com/goccy/go-yaml v1.9.5 github.com/gofrs/flock v0.8.1 github.com/hashicorp/go-version v1.4.0 github.com/hexops/gotextdiff v1.0.3 @@ -22,8 +23,6 @@ require ( golang.org/x/sync v0.0.0-20210220032951-036812b2e83c google.golang.org/grpc v1.43.0 google.golang.org/protobuf v1.27.1 - gopkg.in/yaml.v2 v2.4.0 - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b k8s.io/apimachinery v0.23.3 k8s.io/client-go v0.23.3 sigs.k8s.io/kustomize/api v0.11.1 @@ -46,6 +45,7 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/emirpasic/gods v1.12.0 // indirect github.com/evanphx/json-patch v4.12.0+incompatible // indirect + github.com/fatih/color v1.13.0 // indirect github.com/fsnotify/fsnotify v1.5.1 // indirect github.com/gammazero/deque v0.1.0 // indirect github.com/go-errors/errors v1.0.1 // indirect @@ -76,6 +76,8 @@ require ( github.com/leodido/go-urn v1.2.1 // indirect github.com/magiconair/properties v1.8.5 // indirect github.com/mailru/easyjson v0.7.6 // indirect + github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.4.3 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect @@ -102,11 +104,14 @@ require ( golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.66.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect k8s.io/api v0.23.3 // indirect k8s.io/klog/v2 v2.30.0 // indirect k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect diff --git a/go.sum b/go.sum index 051634036..11d3b56f8 100644 --- a/go.sum +++ b/go.sum @@ -151,6 +151,8 @@ github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= @@ -200,15 +202,20 @@ github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5F github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0= github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/goccy/go-yaml v1.9.5 h1:Eh/+3uk9kLxG4koCX6lRMAPS1OaMSAi+FJcya0INdB0= +github.com/goccy/go-yaml v1.9.5/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= @@ -390,6 +397,7 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= @@ -405,13 +413,16 @@ github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlW github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index 2b22a5ab5..3d191bd27 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -22,14 +22,15 @@ type helmChart struct { } func NewHelmChart(configFile string) (*helmChart, error) { - config, err := types.LoadHelmChartConfig(configFile) + var config types.HelmChartConfig + err := utils.ReadYamlFile(configFile, &config) if err != nil { return nil, err } hc := &helmChart{ configFile: configFile, - config: config, + config: &config, } return hc, nil } diff --git a/pkg/deployment/vars.go b/pkg/deployment/vars.go index 9a74b42c2..334d3d4ac 100644 --- a/pkg/deployment/vars.go +++ b/pkg/deployment/vars.go @@ -124,8 +124,8 @@ func (vc *VarsCtx) loadVarsFromK8sObject(k *k8s.K8sCluster, ref types.ObjectRef, } func (vc *VarsCtx) loadVarsFromString(s string) error { - newVars := make(map[string]interface{}) - err := vc.renderYamlString(s, newVars) + var newVars map[string]interface{} + err := vc.renderYamlString(s, &newVars) if err != nil { return err } diff --git a/pkg/git/git-url/url.go b/pkg/git/git-url/url.go index 328d95dfd..55fdd956b 100644 --- a/pkg/git/git-url/url.go +++ b/pkg/git/git-url/url.go @@ -3,7 +3,6 @@ package git_url import ( "fmt" giturls "github.com/whilp/git-urls" - "gopkg.in/yaml.v3" "net/url" "strings" ) @@ -20,9 +19,9 @@ func Parse(u string) (*GitUrl, error) { return &GitUrl{*u2}, nil } -func (u *GitUrl) UnmarshalYAML(value *yaml.Node) error { +func (u *GitUrl) UnmarshalYAML(unmarshal func(interface{}) error) error { var s string - err := value.Decode(&s) + err := unmarshal(&s) if err != nil { return err } diff --git a/pkg/jinja2_server/server.go b/pkg/jinja2_server/server.go index 48bc5a4e8..933b069a3 100644 --- a/pkg/jinja2_server/server.go +++ b/pkg/jinja2_server/server.go @@ -210,6 +210,9 @@ func (js *Jinja2Server) RenderString(template string, searchDirs []string, vars if err != nil { return "", err } + if jobs[0].Error != nil { + return "", jobs[0].Error + } return *jobs[0].Result, nil } @@ -221,6 +224,9 @@ func (js *Jinja2Server) RenderFile(template string, searchDirs []string, vars ma if err != nil { return "", err } + if jobs[0].Error != nil { + return "", jobs[0].Error + } return *jobs[0].Result, nil } diff --git a/pkg/kluctl_project/load.go b/pkg/kluctl_project/load.go index fbd3726ab..b7c17a151 100644 --- a/pkg/kluctl_project/load.go +++ b/pkg/kluctl_project/load.go @@ -53,14 +53,14 @@ func (c *KluctlProjectContext) load(allowGit bool) error { } if configPath != "" { - err := types2.LoadKluctlProjectConfig(configPath, &c.config) + err = utils.ReadYamlFile(configPath, &c.config) if err != nil { return err } } if allowGit { - err := c.updateGitCaches() + err = c.updateGitCaches() if err != nil { return err } diff --git a/pkg/kluctl_project/load_targets.go b/pkg/kluctl_project/load_targets.go index 79ad04dd1..72b695e22 100644 --- a/pkg/kluctl_project/load_targets.go +++ b/pkg/kluctl_project/load_targets.go @@ -289,7 +289,7 @@ func (c *KluctlProjectContext) buildDynamicTarget(targetInfo *dynamicTargetInfo) } var targetConfig types.TargetConfig - err = types.LoadTargetConfig(configPath, &targetConfig) + err = utils.ReadYamlFile(configPath, &targetConfig) if err != nil { return nil, err } diff --git a/pkg/types/cluster_config.go b/pkg/types/cluster_config.go index 42f9557cc..56abb35b0 100644 --- a/pkg/types/cluster_config.go +++ b/pkg/types/cluster_config.go @@ -9,11 +9,52 @@ import ( type ClusterConfig2 struct { Name string `yaml:"name" validate:"required"` Context string `yaml:"context" validate:"required"` - Vars map[string]interface{} `yaml:",inline"` + Vars map[string]interface{} `yaml:"vars,omitempty"` } type ClusterConfig struct { - Cluster ClusterConfig2 `yaml:"cluster"` + Cluster *ClusterConfig2 `yaml:"cluster"` +} + +// TODO remove custom unmarshaller when https://github.com/goccy/go-yaml/pull/220 gets released +func (cc *ClusterConfig2) UnmarshalYAML(unmarshal func(interface{}) error) error { + var m map[string]interface{} + err := unmarshal(&m) + if err != nil { + return err + } + + nameI, ok := m["name"] + if !ok { + return fmt.Errorf("name is missing in cluster config") + } + contextI, ok := m["context"] + if !ok { + return fmt.Errorf("context is missing in cluster config") + } + + cc.Name, ok = nameI.(string) + if !ok { + return fmt.Errorf("name is not a string") + } + cc.Context, ok = contextI.(string) + if !ok { + return fmt.Errorf("context is not a string") + } + + delete(m, "name") + delete(m, "context") + cc.Vars = m + return nil +} + +func (cc *ClusterConfig2) MarshalYAML() (interface{}, error) { + m := map[string]interface{}{ + "name": cc.Name, + "context": cc.Context, + } + utils.MergeObject(m, cc.Vars) + return m, nil } func LoadClusterConfig(clusterDir string, clusterName string) (*ClusterConfig, error) { diff --git a/pkg/types/deployment.go b/pkg/types/deployment.go index 55d0b5938..cfec841c4 100644 --- a/pkg/types/deployment.go +++ b/pkg/types/deployment.go @@ -1,9 +1,8 @@ package types import ( - "fmt" + "github.com/codablock/kluctl/pkg/utils" "github.com/go-playground/validator/v10" - "gopkg.in/yaml.v3" ) type DeploymentItemConfig struct { @@ -62,16 +61,16 @@ type SealedSecretsConfig struct { type SingleStringOrList []string -func (s *SingleStringOrList) UnmarshalYAML(value *yaml.Node) error { +func (s *SingleStringOrList) UnmarshalYAML(unmarshal func(interface{}) error) error { var single string - if err := value.Decode(&single); err == nil { + if err := unmarshal(&single); err == nil { // it's a single project *s = []string{single} return nil } // try as array var arr []string - if err := value.Decode(&arr); err != nil { + if err := unmarshal(&arr); err != nil { return err } *s = arr @@ -106,19 +105,6 @@ type DeploymentProjectConfig struct { TemplateExcludes []string `yaml:"TemplateExcludes,omitempty"` } -func (c *DeploymentProjectConfig) UnmarshalYAML(value *yaml.Node) error { - type raw DeploymentProjectConfig - err := value.Decode((*raw)(c)) - if err != nil { - return err - } - err = validate.Struct(c) - if err != nil { - return fmt.Errorf("validation for DeploymentProjectConfig failed: %w", err) - } - return nil -} - func init() { - validate.RegisterStructValidation(ValidateVarsListItem, VarsListItem{}) + utils.Validator.RegisterStructValidation(ValidateVarsListItem, VarsListItem{}) } diff --git a/pkg/types/git_project.go b/pkg/types/git_project.go index ff6068646..042cb27f3 100644 --- a/pkg/types/git_project.go +++ b/pkg/types/git_project.go @@ -2,8 +2,8 @@ package types import ( git_url "github.com/codablock/kluctl/pkg/git/git-url" + "github.com/codablock/kluctl/pkg/utils" "github.com/go-playground/validator/v10" - "gopkg.in/yaml.v3" "strings" ) @@ -13,13 +13,13 @@ type GitProject struct { SubDir string `yaml:"subDir"` } -func (gp *GitProject) UnmarshalYAML(value *yaml.Node) error { - if err := value.Decode(&gp.Url); err == nil { +func (gp *GitProject) UnmarshalYAML(unmarshal func(interface{}) error) error { + if err := unmarshal(&gp.Url); err == nil { // it's a simple string return nil } type raw GitProject - return value.Decode((*raw)(gp)) + return unmarshal((*raw)(gp)) } func ValidateGitProject(sl validator.StructLevel) { @@ -37,17 +37,17 @@ type ExternalProjects struct { Projects []ExternalProject } -func (gp *ExternalProjects) UnmarshalYAML(value *yaml.Node) error { +func (gp *ExternalProjects) UnmarshalYAML(unmarshal func(interface{}) error) error { singleProject := ExternalProject{} - if err := value.Decode(&singleProject); err == nil { + if err := unmarshal(&singleProject); err == nil { // it's a single project gp.Projects = []ExternalProject{singleProject} return nil } // try as array - return value.Decode(gp.Projects) + return unmarshal(gp.Projects) } func init() { - validate.RegisterStructValidation(ValidateGitProject, GitProject{}) + utils.Validator.RegisterStructValidation(ValidateGitProject, GitProject{}) } diff --git a/pkg/types/helm_chart.go b/pkg/types/helm_chart.go index dcc808f85..353ed3aba 100644 --- a/pkg/types/helm_chart.go +++ b/pkg/types/helm_chart.go @@ -1,10 +1,5 @@ package types -import ( - "fmt" - "github.com/codablock/kluctl/pkg/utils" -) - type HelmChartConfig2 struct { Repo *string `yaml:"repo,omitempty"` ChartName *string `yaml:"chartName,omitempty"` @@ -19,16 +14,3 @@ type HelmChartConfig2 struct { type HelmChartConfig struct { HelmChartConfig2 `yaml:"helmChart" validate:"required"` } - -func LoadHelmChartConfig(p string) (*HelmChartConfig, error) { - var o HelmChartConfig - err := utils.ReadYamlFile(p, &o) - if err != nil { - return nil, err - } - err = validate.Struct(o) - if err != nil { - return nil, fmt.Errorf("validation for %v failed: %w", p, err) - } - return &o, nil -} diff --git a/pkg/types/kluctl_project.go b/pkg/types/kluctl_project.go index 60680fe68..8a770c867 100644 --- a/pkg/types/kluctl_project.go +++ b/pkg/types/kluctl_project.go @@ -1,7 +1,6 @@ package types import ( - "fmt" "github.com/codablock/kluctl/pkg/utils" ) @@ -54,18 +53,6 @@ type KluctlProject struct { SecretsConfig *SecretsConfig `yaml:"secretsConfig,omitempty"` } -func LoadKluctlProjectConfig(p string, o *KluctlProject) error { - err := utils.ReadYamlFile(p, o) - if err != nil { - return err - } - err = validate.Struct(o) - if err != nil { - return fmt.Errorf("validation for %v failed: %w", p, err) - } - return nil -} - func init() { - validate.RegisterStructValidation(ValidateSecretSource, SecretSource{}) + utils.Validator.RegisterStructValidation(ValidateSecretSource, SecretSource{}) } diff --git a/pkg/types/target_config.go b/pkg/types/target_config.go index 605d59abc..9040450c2 100644 --- a/pkg/types/target_config.go +++ b/pkg/types/target_config.go @@ -1,10 +1,5 @@ package types -import ( - "fmt" - "github.com/codablock/kluctl/pkg/utils" -) - type FixedImage struct { Image string `yaml:"image" validate:"required"` ResultImage string `yaml:"resultImage" validate:"required"` @@ -26,27 +21,3 @@ type TargetConfig struct { FixedImagesConfig `yaml:"fixed_images_config,inline"` Args map[string]interface{} `yaml:"args,omitempty"` } - -func LoadFixedImagesConfig(p string, o *FixedImagesConfig) error { - err := utils.ReadYamlFile(p, o) - if err != nil { - return err - } - err = validate.Struct(o) - if err != nil { - return fmt.Errorf("validation for %v failed: %w", p, err) - } - return nil -} - -func LoadTargetConfig(p string, o *TargetConfig) error { - err := utils.ReadYamlFile(p, o) - if err != nil { - return err - } - err = validate.Struct(o) - if err != nil { - return fmt.Errorf("validation for %v failed: %w", p, err) - } - return nil -} diff --git a/pkg/utils/unstructured.go b/pkg/utils/unstructured.go index bb7689c3d..bc4d65e28 100644 --- a/pkg/utils/unstructured.go +++ b/pkg/utils/unstructured.go @@ -25,8 +25,8 @@ func StructToObject(s interface{}) (map[string]interface{}, error) { return nil, err } - m := make(map[string]interface{}) - err = ReadYamlBytes(b, m) + var m map[string]interface{} + err = ReadYamlBytes(b, &m) if err != nil { return nil, err } diff --git a/pkg/types/validator.go b/pkg/utils/validator.go similarity index 57% rename from pkg/types/validator.go rename to pkg/utils/validator.go index 5063a72bf..726e5613b 100644 --- a/pkg/types/validator.go +++ b/pkg/utils/validator.go @@ -1,7 +1,7 @@ -package types +package utils import "github.com/go-playground/validator/v10" var ( - validate = validator.New() + Validator = validator.New() ) diff --git a/pkg/utils/yaml.go b/pkg/utils/yaml.go index e627ecd19..cfeb04c69 100644 --- a/pkg/utils/yaml.go +++ b/pkg/utils/yaml.go @@ -4,12 +4,20 @@ import ( "bytes" "errors" "fmt" - "gopkg.in/yaml.v3" + "github.com/goccy/go-yaml" "io" "os" "strings" ) +func newYamlDecoder(r io.Reader) *yaml.Decoder { + return yaml.NewDecoder(r, yaml.Strict(), yaml.Validator(Validator)) +} + +func newYamlEncoder(w io.Writer) *yaml.Encoder { + return yaml.NewEncoder(w) +} + func ReadYamlFile(p string, o interface{}) error { r, err := os.Open(p) if err != nil { @@ -33,8 +41,7 @@ func ReadYamlBytes(b []byte, o interface{}) error { } func ReadYamlStream(r io.Reader, o interface{}) error { - d := yaml.NewDecoder(r) - d.KnownFields(true) + d := newYamlDecoder(r) err := d.Decode(o) return err } @@ -58,8 +65,7 @@ func ReadYamlAllBytes(b []byte) ([]interface{}, error) { } func ReadYamlAllStream(r io.Reader) ([]interface{}, error) { - d := yaml.NewDecoder(r) - d.KnownFields(true) + d := newYamlDecoder(r) var l []interface{} for true { @@ -120,9 +126,8 @@ func WriteYamlAllString(l []interface{}) (string, error) { } func WriteYamlAllStream(w io.Writer, l []interface{}) error { - enc := yaml.NewEncoder(w) + enc := newYamlEncoder(w) defer enc.Close() - enc.SetIndent(2) for _, o := range l { err := enc.Encode(o) From 619e726d30d6854c53c175ad5f186dc7db4ead21 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sun, 13 Feb 2022 02:53:24 +0100 Subject: [PATCH 0339/2916] Fix isIncludeDeployment --- pkg/deployment/deployment_project.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go index 3d7413a63..ae899e6ae 100644 --- a/pkg/deployment/deployment_project.go +++ b/pkg/deployment/deployment_project.go @@ -150,8 +150,16 @@ func (p *DeploymentProject) isIncludeDeployment(di *types.DeploymentItemConfig) return false } - incDir := path.Join(p.dir, *di.Path, "deployment.yml") - return utils.Exists(incDir) + base := path.Join(p.dir, *di.Path) + + if utils.Exists(path.Join(base, "kustomization.yml")) || utils.Exists(path.Join(base, "kustomization.yaml")) { + return false + } + if !utils.Exists(path.Join(base, "deployment.yml")) { + return false + } + + return true } func (p *DeploymentProject) loadIncludes(k *k8s.K8sCluster) error { From 07d4788fbde7cd8fbaf311e7ff871a1adfcdd6fb Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sun, 13 Feb 2022 03:16:24 +0100 Subject: [PATCH 0340/2916] Fix yaml reading issues --- pkg/deployment/deployment_item.go | 8 ++++---- pkg/kluctl_project/load.go | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 7c78662a1..0b8832bb9 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -167,8 +167,8 @@ func (di *deploymentItem) resolveSealedSecrets() error { sealedSecretsDir := di.project.getSealedSecretsDir() baseSourcePath := di.project.sealedSecretsDir - y := make(map[string]interface{}) - err := utils.ReadYamlFile(path.Join(di.renderedDir, "kustomization.yml"), y) + var y map[string]interface{} + err := utils.ReadYamlFile(path.Join(di.renderedDir, "kustomization.yml"), &y) if err != nil { return err } @@ -261,8 +261,8 @@ func (di *deploymentItem) prepareKusomizationYaml() error { return nil } - kustomizeYaml := make(map[string]interface{}) - err := utils.ReadYamlFile(kustomizeYamlPath, kustomizeYaml) + var kustomizeYaml map[string]interface{} + err := utils.ReadYamlFile(kustomizeYamlPath, &kustomizeYaml) if err != nil { return err } diff --git a/pkg/kluctl_project/load.go b/pkg/kluctl_project/load.go index b7c17a151..8f16e57c7 100644 --- a/pkg/kluctl_project/load.go +++ b/pkg/kluctl_project/load.go @@ -178,7 +178,8 @@ func loadKluctlProjectFromArchive(fromArchive string, fromArchiveMetadata string metdataPath = path.Join(dir, "metadata.yml") } - metadata, err := types2.LoadArchiveMetadata(metdataPath) + var metadata types2.ArchiveMetadata + err := utils.ReadYamlFile(metdataPath, &metadata) if err != nil { return nil, err } From cc840ba9f43379bee0b192c6e9566dca417bd5ae Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sun, 13 Feb 2022 03:16:38 +0100 Subject: [PATCH 0341/2916] Use yaml.v3 for unstructured data --- pkg/utils/yaml.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/pkg/utils/yaml.go b/pkg/utils/yaml.go index cfeb04c69..2937e6460 100644 --- a/pkg/utils/yaml.go +++ b/pkg/utils/yaml.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "github.com/goccy/go-yaml" + yaml3 "gopkg.in/yaml.v3" "io" "os" "strings" @@ -41,6 +42,13 @@ func ReadYamlBytes(b []byte, o interface{}) error { } func ReadYamlStream(r io.Reader, o interface{}) error { + if _, ok := o.(*map[string]interface{}); ok { + // much faster + d := yaml3.NewDecoder(r) + return d.Decode(o) + } + + // we need proper working strict mode d := newYamlDecoder(r) err := d.Decode(o) return err @@ -65,7 +73,8 @@ func ReadYamlAllBytes(b []byte) ([]interface{}, error) { } func ReadYamlAllStream(r io.Reader) ([]interface{}, error) { - d := newYamlDecoder(r) + // yaml.v3 is much faster then go-yaml + d := yaml3.NewDecoder(r) var l []interface{} for true { @@ -126,7 +135,7 @@ func WriteYamlAllString(l []interface{}) (string, error) { } func WriteYamlAllStream(w io.Writer, l []interface{}) error { - enc := newYamlEncoder(w) + enc := yaml3.NewEncoder(w) defer enc.Close() for _, o := range l { From 3736d9c7ebfda2981b07e8cd395b813f008a3ef0 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sun, 13 Feb 2022 21:43:36 +0100 Subject: [PATCH 0342/2916] Fix warning handlers for metadata client --- pkg/k8s/k8s_cluster.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index e3d94c2b7..2b73cb162 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -100,6 +100,7 @@ func NewK8sCluster(context string, dryRun bool) (*K8sCluster, error) { for i := 0; i < parallelClients; i++ { p := ¶llelClientEntry{} restConfig.WarningHandler = p + config.WarningHandler = p p.dynamicClient, err = dynamic.NewForConfig(restConfig) if err != nil { From d20c649975f3bb4340ec39a8cb6516d94ffbe805 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 14 Feb 2022 00:09:34 +0100 Subject: [PATCH 0343/2916] Introduce UnstructuredObject --- cmd/kluctl/args/images.go | 4 +- cmd/kluctl/command_result.go | 9 +- cmd/kluctl/utils.go | 14 ++- pkg/deployment/apply_utils.go | 3 +- pkg/deployment/deployment_item.go | 18 +-- pkg/deployment/deployment_project.go | 5 +- pkg/deployment/external_args.go | 32 +++-- pkg/deployment/helm_chart.go | 9 +- pkg/deployment/images.go | 9 +- pkg/deployment/vars.go | 53 +++------ pkg/diff/diff.go | 11 +- pkg/diff/managed_fields.go | 20 ++-- pkg/diff/normalize.go | 111 ++++++++--------- pkg/jinja2_server/server.go | 25 ++-- pkg/k8s/k8s_cluster.go | 11 +- pkg/kluctl_project/load.go | 5 +- pkg/kluctl_project/load_targets.go | 8 +- pkg/types/cluster_config.go | 45 ++++--- pkg/types/deployment.go | 7 +- pkg/types/git_project.go | 4 +- pkg/types/kluctl_project.go | 13 +- pkg/types/metadata.go | 4 +- pkg/types/target_config.go | 4 +- pkg/utils/unstructured.go | 164 ------------------------- pkg/utils/{ => uo}/jsonpath.go | 8 +- pkg/utils/uo/nested_fields.go | 165 ++++++++++++++++++++++++++ pkg/utils/{ => uo}/object_iterator.go | 2 +- pkg/utils/uo/unstructured.go | 71 +++++++++++ pkg/utils/uo/uo.go | 94 +++++++++++++++ pkg/utils/uo/utils.go | 17 +++ pkg/{utils => yaml}/validator.go | 2 +- pkg/{utils => yaml}/yaml.go | 2 +- 32 files changed, 564 insertions(+), 385 deletions(-) delete mode 100644 pkg/utils/unstructured.go rename pkg/utils/{ => uo}/jsonpath.go (91%) create mode 100644 pkg/utils/uo/nested_fields.go rename pkg/utils/{ => uo}/object_iterator.go (99%) create mode 100644 pkg/utils/uo/unstructured.go create mode 100644 pkg/utils/uo/uo.go create mode 100644 pkg/utils/uo/utils.go rename pkg/{utils => yaml}/validator.go (86%) rename pkg/{utils => yaml}/yaml.go (99%) diff --git a/cmd/kluctl/args/images.go b/cmd/kluctl/args/images.go index 73872b729..539681cdb 100644 --- a/cmd/kluctl/args/images.go +++ b/cmd/kluctl/args/images.go @@ -3,7 +3,7 @@ package args import ( "fmt" "github.com/codablock/kluctl/pkg/types" - "github.com/codablock/kluctl/pkg/utils" + "github.com/codablock/kluctl/pkg/yaml" "github.com/spf13/cobra" "strings" ) @@ -26,7 +26,7 @@ func LoadFixedImagesFromArgs() (*types.FixedImagesConfig, error) { var ret types.FixedImagesConfig if FixedImagesFile != "" { - err := utils.ReadYamlFile(FixedImagesFile, &ret) + err := yaml.ReadYamlFile(FixedImagesFile, &ret) if err != nil { return nil, err } diff --git a/cmd/kluctl/command_result.go b/cmd/kluctl/command_result.go index feb78fc7b..581001636 100644 --- a/cmd/kluctl/command_result.go +++ b/cmd/kluctl/command_result.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/codablock/kluctl/pkg/types" "github.com/codablock/kluctl/pkg/utils" + "github.com/codablock/kluctl/pkg/yaml" "io" "os" "strings" @@ -92,7 +93,7 @@ func prettyChanges(buf io.StringWriter, ref types.ObjectRef, changes []types.Cha } func formatCommandResultYaml(cr *types.CommandResult) (string, error) { - b, err := utils.WriteYamlString(cr) + b, err := yaml.WriteYamlString(cr) if err != nil { return "", err } @@ -148,7 +149,7 @@ func formatValidateResultText(vr *types.ValidateResult) string { } func formatValidateResultYaml(vr *types.ValidateResult) (string, error) { - b, err := utils.WriteYamlString(vr) + b, err := yaml.WriteYamlString(vr) if err != nil { return "", err } @@ -209,13 +210,13 @@ func outputYamlResult(output []*string, result interface{}, multiDoc bool) error if !ok { return fmt.Errorf("object is not a list") } - x, err := utils.WriteYamlAllString(l) + x, err := yaml.WriteYamlAllString(l) if err != nil { return err } s = x } else { - x, err := utils.WriteYamlString(result) + x, err := yaml.WriteYamlString(result) if err != nil { return err } diff --git a/cmd/kluctl/utils.go b/cmd/kluctl/utils.go index a52621ed4..c31a00079 100644 --- a/cmd/kluctl/utils.go +++ b/cmd/kluctl/utils.go @@ -9,7 +9,7 @@ import ( "github.com/codablock/kluctl/pkg/k8s" "github.com/codablock/kluctl/pkg/kluctl_project" "github.com/codablock/kluctl/pkg/types" - "github.com/codablock/kluctl/pkg/utils" + "github.com/codablock/kluctl/pkg/utils/uo" "io/ioutil" "os" "path/filepath" @@ -106,7 +106,7 @@ func withProjectTargetCommandContext(p *kluctl_project.KluctlProjectContext, tar return err } - allArgs := make(map[string]interface{}) + allArgs := uo.New() optionArgs, err := deployment.ParseArgs(args.Args) if err != nil { @@ -120,11 +120,15 @@ func withProjectTargetCommandContext(p *kluctl_project.KluctlProjectContext, tar } } } - utils.MergeObject(allArgs, deployment.ConvertArgsToVars(optionArgs)) + allArgs.Merge(deployment.ConvertArgsToVars(optionArgs)) if target != nil { - utils.MergeObject(allArgs, target.Args) + if target.Args != nil { + allArgs.Merge(target.Args) + } if forSeal { - utils.MergeObject(allArgs, target.SealingConfig.Args) + if target.SealingConfig.Args != nil { + allArgs.Merge(target.SealingConfig.Args) + } } } diff --git a/pkg/deployment/apply_utils.go b/pkg/deployment/apply_utils.go index 18b87e890..4f2381229 100644 --- a/pkg/deployment/apply_utils.go +++ b/pkg/deployment/apply_utils.go @@ -6,6 +6,7 @@ import ( "github.com/codablock/kluctl/pkg/k8s" "github.com/codablock/kluctl/pkg/types" "github.com/codablock/kluctl/pkg/utils" + "github.com/codablock/kluctl/pkg/utils/uo" "github.com/codablock/kluctl/pkg/validation" log "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/api/errors" @@ -136,7 +137,7 @@ func (a *applyUtil) retryApplyWithReplace(x *unstructured.Unstructured, hook boo log2.Warningf("Patching failed, retrying with replace instead of patch") rv := remoteObject.GetResourceVersion() - x2 := utils.CopyUnstructured(x) + x2 := uo.CopyUnstructured(x) x2.SetResourceVersion(rv) o := k8s.UpdateOptions{ diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 0b8832bb9..e9a89913a 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -5,6 +5,8 @@ import ( "github.com/codablock/kluctl/pkg/k8s" "github.com/codablock/kluctl/pkg/types" "github.com/codablock/kluctl/pkg/utils" + "github.com/codablock/kluctl/pkg/utils/uo" + "github.com/codablock/kluctl/pkg/yaml" "io/fs" "io/ioutil" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -168,7 +170,7 @@ func (di *deploymentItem) resolveSealedSecrets() error { baseSourcePath := di.project.sealedSecretsDir var y map[string]interface{} - err := utils.ReadYamlFile(path.Join(di.renderedDir, "kustomization.yml"), &y) + err := yaml.ReadYamlFile(path.Join(di.renderedDir, "kustomization.yml"), &y) if err != nil { return err } @@ -262,7 +264,7 @@ func (di *deploymentItem) prepareKusomizationYaml() error { } var kustomizeYaml map[string]interface{} - err := utils.ReadYamlFile(kustomizeYamlPath, &kustomizeYaml) + err := yaml.ReadYamlFile(kustomizeYamlPath, &kustomizeYaml) if err != nil { return err } @@ -275,7 +277,7 @@ func (di *deploymentItem) prepareKusomizationYaml() error { } // Save modified kustomize.yml - err = utils.WriteYamlFile(kustomizeYamlPath, kustomizeYaml) + err = yaml.WriteYamlFile(kustomizeYamlPath, kustomizeYaml) if err != nil { return err } @@ -310,7 +312,7 @@ func (di *deploymentItem) buildKustomize() error { yamls = append(yamls, y) } - err = utils.WriteYamlAllFile(di.renderedYamlPath, yamls) + err = yaml.WriteYamlAllFile(di.renderedYamlPath, yamls) if err != nil { return err } @@ -323,7 +325,7 @@ func (di *deploymentItem) postprocessAndLoadObjects(k *k8s.K8sCluster) error { return nil } - objects, err := utils.ReadYamlAllFile(di.renderedYamlPath) + objects, err := yaml.ReadYamlAllFile(di.renderedYamlPath) if err != nil { return err } @@ -345,9 +347,9 @@ func (di *deploymentItem) postprocessAndLoadObjects(k *k8s.K8sCluster) error { } // Set common labels/annotations - o.SetLabels(utils.CopyMergeStrMap(o.GetLabels(), di.getCommonLabels())) + o.SetLabels(uo.CopyMergeStrMap(o.GetLabels(), di.getCommonLabels())) commonAnnotations := di.getCommonAnnotations() - o.SetAnnotations(utils.CopyMergeStrMap(o.GetAnnotations(), commonAnnotations)) + o.SetAnnotations(uo.CopyMergeStrMap(o.GetAnnotations(), commonAnnotations)) // Resolve image placeholders err = di.collection.images.ResolvePlaceholders(k, o, di.relRenderedDir, di.getTags().ListKeys()) @@ -357,7 +359,7 @@ func (di *deploymentItem) postprocessAndLoadObjects(k *k8s.K8sCluster) error { } // Need to write it back to disk in case it is needed externally - err = utils.WriteYamlAllFile(di.renderedYamlPath, objects) + err = yaml.WriteYamlAllFile(di.renderedYamlPath, objects) if err != nil { return err } diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go index ae899e6ae..f0702adf3 100644 --- a/pkg/deployment/deployment_project.go +++ b/pkg/deployment/deployment_project.go @@ -5,6 +5,7 @@ import ( "github.com/codablock/kluctl/pkg/k8s" "github.com/codablock/kluctl/pkg/types" "github.com/codablock/kluctl/pkg/utils" + "github.com/codablock/kluctl/pkg/utils/uo" log "github.com/sirupsen/logrus" "path" "path/filepath" @@ -246,7 +247,7 @@ func (p *DeploymentProject) getCommonLabels() map[string]string { parents := p.getParents() for i, _ := range parents { d := parents[len(parents)-i-1] - utils.MergeStrMap(ret, d.p.config.CommonLabels) + uo.MergeStrMap(ret, d.p.config.CommonLabels) } return ret } @@ -256,7 +257,7 @@ func (p *DeploymentProject) getDeleteByLabels() map[string]string { parents := p.getParents() for i, _ := range parents { d := parents[len(parents)-i-1] - utils.MergeStrMap(ret, d.p.config.DeleteByLabels) + uo.MergeStrMap(ret, d.p.config.DeleteByLabels) } return ret } diff --git a/pkg/deployment/external_args.go b/pkg/deployment/external_args.go index 10f51e616..4048105ba 100644 --- a/pkg/deployment/external_args.go +++ b/pkg/deployment/external_args.go @@ -3,8 +3,8 @@ package deployment import ( "fmt" "github.com/codablock/kluctl/pkg/types" - "github.com/codablock/kluctl/pkg/utils" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "github.com/codablock/kluctl/pkg/utils/uo" + "github.com/codablock/kluctl/pkg/yaml" "path" "regexp" "strings" @@ -27,23 +27,26 @@ func ParseArgs(argsList []string) (map[string]string, error) { return args, nil } -func ConvertArgsToVars(args map[string]string) map[string]interface{} { - m := make(map[string]interface{}) +func ConvertArgsToVars(args map[string]string) *uo.UnstructuredObject { + vars := uo.New() for n, v := range args { - p := strings.Split(n, ".") - _ = unstructured.SetNestedField(m, v, p...) + var p []interface{} + for _, x := range strings.Split(n, ".") { + p = append(p, x) + } + _ = vars.SetNestedField(v, p...) } - return m + return vars } -func CheckRequiredDeployArgs(dir string, varsCtx *VarsCtx, deployArgs map[string]interface{}) error { +func CheckRequiredDeployArgs(dir string, varsCtx *VarsCtx, deployArgs *uo.UnstructuredObject) error { // First try to load the config without templating to avoid getting errors while rendering because required // args were not set. Otherwise we won't be able to iterator through the 'args' array in the deployment.yml // when the rendering error is actually args related. var conf types.DeploymentProjectConfig - err := utils.ReadYamlFile(path.Join(dir, "deployment.yml"), &conf) + err := yaml.ReadYamlFile(path.Join(dir, "deployment.yml"), &conf) if err != nil { // If that failed, it might be that conditional jinja blocks are present in the config, so lets try loading // the config in rendered form. If it fails due to missing args now, we can't help much with better error @@ -67,15 +70,18 @@ func CheckRequiredDeployArgs(dir string, varsCtx *VarsCtx, deployArgs map[string return nil } -func checkRequiredArgs(argsDef []*types.DeploymentArg, args map[string]interface{}) error { +func checkRequiredArgs(argsDef []*types.DeploymentArg, args *uo.UnstructuredObject) error { for _, a := range argsDef { - p := strings.Split(a.Name, ".") - _, found, _ := unstructured.NestedFieldNoCopy(args, p...) + var p []interface{} + for _, x := range strings.Split(a.Name, ".") { + p = append(p, x) + } + _, found, _ := args.GetNestedField(p...) if !found { if a.Default == nil { return fmt.Errorf("required argument %s not set", a.Name) } else { - err := unstructured.SetNestedField(args, a.Default, p...) + err := args.SetNestedField(a.Default, p...) if err != nil { return err } diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index 3d191bd27..18045f40d 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -5,6 +5,7 @@ import ( "github.com/codablock/kluctl/pkg/k8s" "github.com/codablock/kluctl/pkg/types" "github.com/codablock/kluctl/pkg/utils" + "github.com/codablock/kluctl/pkg/yaml" goversion "github.com/hashicorp/go-version" "io/ioutil" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -23,7 +24,7 @@ type helmChart struct { func NewHelmChart(configFile string) (*helmChart, error) { var config types.HelmChartConfig - err := utils.ReadYamlFile(configFile, &config) + err := yaml.ReadYamlFile(configFile, &config) if err != nil { return nil, err } @@ -147,7 +148,7 @@ func (c *helmChart) CheckUpdate() (string, error) { // ensure we didn't get partial matches var lm []map[string]string var ls VersionSlice - err = utils.ReadYamlBytes(stdout, &lm) + err = yaml.ReadYamlBytes(stdout, &lm) if err != nil { return err } @@ -217,7 +218,7 @@ func (c *helmChart) Render(k *k8s.K8sCluster) error { return err } - parsed, err := utils.ReadYamlAllBytes(rendered) + parsed, err := yaml.ReadYamlAllBytes(rendered) for _, o := range parsed { m, ok := o.(map[string]interface{}) @@ -232,7 +233,7 @@ func (c *helmChart) Render(k *k8s.K8sCluster) error { _ = unstructured.SetNestedField(m, namespace, "metadata", "namespace") } } - rendered, err = utils.WriteYamlAllBytes(parsed) + rendered, err = yaml.WriteYamlAllBytes(parsed) if err != nil { return err } diff --git a/pkg/deployment/images.go b/pkg/deployment/images.go index c963d7d7a..80ca26ad5 100644 --- a/pkg/deployment/images.go +++ b/pkg/deployment/images.go @@ -6,7 +6,8 @@ import ( "fmt" "github.com/codablock/kluctl/pkg/k8s" "github.com/codablock/kluctl/pkg/types" - "github.com/codablock/kluctl/pkg/utils" + "github.com/codablock/kluctl/pkg/utils/uo" + "github.com/codablock/kluctl/pkg/yaml" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "strings" @@ -80,7 +81,7 @@ func (images *Images) parsePlaceholder(s string, offset int) (*placeHolder, erro return nil, err } var ph placeHolder - err = utils.ReadYamlStream(bytes.NewReader(b), &ph) + err = yaml.ReadYamlStream(bytes.NewReader(b), &ph) if err != nil { return nil, err } @@ -109,7 +110,7 @@ func (images *Images) ResolvePlaceholders(k *k8s.K8sCluster, o *unstructured.Uns var remoteObject *unstructured.Unstructured triedRemoteObject := false - err := utils.NewObjectIterator(o.Object).IterateLeafs(func(it *utils.ObjectIterator) error { + err := uo.NewObjectIterator(o.Object).IterateLeafs(func(it *uo.ObjectIterator) error { s, ok := it.Value().(string) if !ok { return nil @@ -140,7 +141,7 @@ func (images *Images) ResolvePlaceholders(k *k8s.K8sCluster, o *unstructured.Uns var deployed *string if remoteObject != nil && ph.startOffset == 0 && ph.endOffset == len(s) { - x, found, _ := utils.GetNestedChild(remoteObject.Object, it.KeyPath()...) + x, found, _ := uo.FromUnstructured(remoteObject).GetNestedField(it.KeyPath()...) if found { if y, ok := x.(string); ok { deployed = &y diff --git a/pkg/deployment/vars.go b/pkg/deployment/vars.go index 334d3d4ac..7ae1c05fd 100644 --- a/pkg/deployment/vars.go +++ b/pkg/deployment/vars.go @@ -5,20 +5,20 @@ import ( "github.com/codablock/kluctl/pkg/jinja2_server" "github.com/codablock/kluctl/pkg/k8s" "github.com/codablock/kluctl/pkg/types" - "github.com/codablock/kluctl/pkg/utils" - log "github.com/sirupsen/logrus" + "github.com/codablock/kluctl/pkg/utils/uo" + "github.com/codablock/kluctl/pkg/yaml" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) type VarsCtx struct { JS *jinja2_server.Jinja2Server - Vars map[string]interface{} + Vars *uo.UnstructuredObject } func NewVarsCtx(js *jinja2_server.Jinja2Server) *VarsCtx { vc := &VarsCtx{ JS: js, - Vars: map[string]interface{}{}, + Vars: uo.New(), } return vc } @@ -26,47 +26,32 @@ func NewVarsCtx(js *jinja2_server.Jinja2Server) *VarsCtx { func (vc *VarsCtx) Copy() *VarsCtx { cp := &VarsCtx{ JS: vc.JS, - Vars: utils.CopyObject(vc.Vars), + Vars: vc.Vars.Clone(), } return cp } -func (vc *VarsCtx) MergedVars(vars map[string]interface{}) map[string]interface{} { - newVars, err := utils.CopyMergeObjects(vc.Vars, vars) - if err != nil { - log.Fatal(err) - } - return newVars -} - -func (vc *VarsCtx) MergedCtx(vars map[string]interface{}) *VarsCtx { - return &VarsCtx{ - JS: vc.JS, - Vars: vc.MergedVars(vars), - } -} - -func (vc *VarsCtx) Update(vars map[string]interface{}) { - utils.MergeObject(vc.Vars, vars) +func (vc *VarsCtx) Update(vars *uo.UnstructuredObject) { + vc.Vars.Merge(vars) } -func (vc *VarsCtx) UpdateChild(child string, vars map[string]interface{}) { - vc.Update(map[string]interface{}{child: vars}) +func (vc *VarsCtx) UpdateChild(child string, vars *uo.UnstructuredObject) { + vc.Vars.MergeChild(child, vars) } func (vc *VarsCtx) UpdateChildFromStruct(child string, o interface{}) error { - m, err := utils.StructToObject(o) + other, err := uo.FromStruct(o) if err != nil { return err } - vc.UpdateChild(child, m) + vc.UpdateChild(child, other) return nil } func (vc *VarsCtx) loadVarsList(k *k8s.K8sCluster, searchDirs []string, varsList []*types.VarsListItem) error { for _, v := range varsList { if v.Values != nil { - vc.Update(*v.Values) + vc.Update(v.Values) } else if v.File != nil { err := vc.loadVarsFile(*v.File, searchDirs) if err != nil { @@ -93,12 +78,12 @@ func (vc *VarsCtx) loadVarsList(k *k8s.K8sCluster, searchDirs []string, varsList } func (vc *VarsCtx) loadVarsFile(p string, searchDirs []string) error { - newVars := make(map[string]interface{}) - err := vc.renderYamlFile(p, searchDirs, newVars) + var newVars uo.UnstructuredObject + err := vc.renderYamlFile(p, searchDirs, &newVars) if err != nil { return fmt.Errorf("failed to load vars from %s: %w", p, err) } - vc.Update(newVars) + vc.Update(&newVars) return nil } @@ -124,12 +109,12 @@ func (vc *VarsCtx) loadVarsFromK8sObject(k *k8s.K8sCluster, ref types.ObjectRef, } func (vc *VarsCtx) loadVarsFromString(s string) error { - var newVars map[string]interface{} + var newVars uo.UnstructuredObject err := vc.renderYamlString(s, &newVars) if err != nil { return err } - vc.Update(newVars) + vc.Update(&newVars) return nil } @@ -139,7 +124,7 @@ func (vc *VarsCtx) renderYamlString(s string, out interface{}) error { return err } - err = utils.ReadYamlString(ret, out) + err = yaml.ReadYamlString(ret, out) if err != nil { return err } @@ -153,7 +138,7 @@ func (vc *VarsCtx) renderYamlFile(p string, searchDirs []string, out interface{} return err } - err = utils.ReadYamlString(ret, out) + err = yaml.ReadYamlString(ret, out) if err != nil { return err } diff --git a/pkg/diff/diff.go b/pkg/diff/diff.go index 3dec29c54..f9b42878c 100644 --- a/pkg/diff/diff.go +++ b/pkg/diff/diff.go @@ -3,7 +3,8 @@ package diff import ( "fmt" "github.com/codablock/kluctl/pkg/types" - "github.com/codablock/kluctl/pkg/utils" + "github.com/codablock/kluctl/pkg/utils/uo" + "github.com/codablock/kluctl/pkg/yaml" "github.com/hexops/gotextdiff" "github.com/hexops/gotextdiff/myers" "github.com/hexops/gotextdiff/span" @@ -19,21 +20,21 @@ func convertPath(path []string, o interface{}) (string, error) { var ret []interface{} for _, p := range path { if i, err := strconv.ParseInt(p, 10, 32); err == nil { - x, found, _ := utils.GetChild(o, int(i)) + x, found, _ := uo.GetChild(o, int(i)) if found { ret = append(ret, int(i)) o = x continue } } - x, found, err := utils.GetChild(o, p) + x, found, err := uo.GetChild(o, p) if !found { return "", fmt.Errorf("path element %v is invalid: %w", p, err) } ret = append(ret, p) o = x } - return utils.KeyListToJsonPath(ret), nil + return uo.KeyListToJsonPath(ret), nil } func Diff(oldObject *unstructured.Unstructured, newObject *unstructured.Unstructured) ([]types.Change, error) { @@ -133,7 +134,7 @@ func objectToDiffableString(o interface{}) (string, error) { return v, nil } - b, err := utils.WriteYamlString(o) + b, err := yaml.WriteYamlString(o) if err != nil { return "", err } diff --git a/pkg/diff/managed_fields.go b/pkg/diff/managed_fields.go index 2a7877544..54692bd9f 100644 --- a/pkg/diff/managed_fields.go +++ b/pkg/diff/managed_fields.go @@ -3,7 +3,7 @@ package diff import ( "bytes" "fmt" - "github.com/codablock/kluctl/pkg/utils" + "github.com/codablock/kluctl/pkg/utils/uo" log "github.com/sirupsen/logrus" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -67,7 +67,7 @@ func convertToKeyList(remote *unstructured.Unstructured, path fieldpath.Path) ([ for _, e := range path { if e.FieldName != nil { ret = append(ret, *e.FieldName) - x, found, err := utils.GetChild(o, *e.FieldName) + x, found, err := uo.GetChild(o, *e.FieldName) if err != nil { return nil, false, err } @@ -122,7 +122,7 @@ func ResolveFieldManagerConflicts(local *unstructured.Unstructured, remote *unst }) } - ret := utils.CopyUnstructured(local) + ret := uo.CopyUnstructured(local) forceApplyAll := false if x, ok := local.GetAnnotations()[`metadata.annotations["kluctl.io/force-apply"]`]; ok { @@ -134,16 +134,16 @@ func ResolveFieldManagerConflicts(local *unstructured.Unstructured, remote *unst if !forceApplyFieldAnnotationRegex.MatchString(k) { continue } - j, err := utils.NewMyJsonPath(v) + j, err := uo.NewMyJsonPath(v) if err != nil { return nil, nil, err } - fields, err := j.ListMatchingFields(ret.Object) + fields, err := j.ListMatchingFields(uo.FromUnstructured(ret)) if err != nil { return nil, nil, err } for _, f := range fields { - forceApplyFields[utils.KeyListToJsonPath(f)] = true + forceApplyFields[uo.KeyListToJsonPath(f)] = true } } @@ -168,14 +168,14 @@ func ResolveFieldManagerConflicts(local *unstructured.Unstructured, remote *unst if !found { return nil, nil, fmt.Errorf("field '%s' not found in remote object", cause.Field) } - localValue, found, err := utils.GetNestedChild(local.Object, p...) + localValue, found, err := uo.FromUnstructured(local).GetNestedField(p...) if err != nil { return nil, nil, err } if !found { return nil, nil, fmt.Errorf("field '%s' not found in local object", cause.Field) } - remoteValue, found, err := utils.GetNestedChild(remote.Object, p...) + remoteValue, found, err := uo.FromUnstructured(remote).GetNestedField(p...) if !found { log.Fatalf("field '%s' not found in remote object...which can't be!", cause.Field) } @@ -196,13 +196,13 @@ func ResolveFieldManagerConflicts(local *unstructured.Unstructured, remote *unst break } } - if _, ok := forceApplyFields[utils.KeyListToJsonPath(p)]; ok { + if _, ok := forceApplyFields[uo.KeyListToJsonPath(p)]; ok { overwrite = true } } if !overwrite { - j, err := utils.NewMyJsonPath(utils.KeyListToJsonPath(p)) + j, err := uo.NewMyJsonPath(uo.KeyListToJsonPath(p)) if err != nil { return nil, nil, err } diff --git a/pkg/diff/normalize.go b/pkg/diff/normalize.go index 905d8bbf8..2ae780304 100644 --- a/pkg/diff/normalize.go +++ b/pkg/diff/normalize.go @@ -4,37 +4,33 @@ import ( "fmt" "github.com/codablock/kluctl/pkg/k8s" "github.com/codablock/kluctl/pkg/types" - "github.com/codablock/kluctl/pkg/utils" + "github.com/codablock/kluctl/pkg/utils/uo" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "regexp" "strconv" "strings" ) -func listToMap(l []map[string]interface{}, key string) (map[string]interface{}, error) { - m := make(map[string]interface{}) +func listToMap(l []*uo.UnstructuredObject, key string) (map[string]*uo.UnstructuredObject, error) { + m := make(map[string]*uo.UnstructuredObject) for _, e := range l { - kv, ok := e[key] - if !ok { + kv, found, _ := e.GetNestedString(key) + if !found { return nil, fmt.Errorf("%s not found in list element", key) } - kvs, ok := kv.(string) - if !ok { - return nil, fmt.Errorf("%s in list element is not a string", key) - } - m[kvs] = e + m[kv] = e } return m, nil } -func normalizeEnv(container map[string]interface{}) { - env := utils.NestedMapSliceNoErr(container, "env") - envFrom := utils.NestedMapSliceNoErr(container, "envFrom") +func normalizeEnv(container *uo.UnstructuredObject) { + env := container.GetNestedObjectListNoErr("env") + envFrom := container.GetNestedObjectListNoErr("envFrom") if len(env) != 0 { newEnv, err := listToMap(env, "name") if err == nil { - container["env"] = newEnv + _ = container.SetNestedField(newEnv, "env") } } if len(envFrom) != 0 { @@ -43,7 +39,7 @@ func normalizeEnv(container map[string]interface{}) { for _, e := range envFrom { k := "" for _, t := range envTypes { - name, found, _ := unstructured.NestedString(e, t, "name") + name, found, _ := e.GetNestedString(t, "name") if !found { continue } @@ -58,30 +54,30 @@ func normalizeEnv(container map[string]interface{}) { m[k] = e } } - container["envFrom"] = m + _ = container.SetNestedField(m, "envFrom") } } -func normalizeContainers(containers []map[string]interface{}) { +func normalizeContainers(containers []*uo.UnstructuredObject) { for _, c := range containers { normalizeEnv(c) } } -func normalizeSecretAndConfigMaps(o map[string]interface{}) { - data, found, _ := unstructured.NestedMap(o, "data") - if found && len(data) == 0 { - unstructured.RemoveNestedField(o, "data") +func normalizeSecretAndConfigMaps(o *uo.UnstructuredObject) { + data, found, _ := o.GetNestedObject("data") + if found && len(data.Object) == 0 { + _ = data.RemoveNestedField("data") } } -func normalizeServiceAccount(o map[string]interface{}) { - serviceAccountName, found, _ := unstructured.NestedString(o, "metadata", "name") +func normalizeServiceAccount(o *uo.UnstructuredObject) { + serviceAccountName, found, _ := o.GetNestedString("metadata", "name") if !found { return } - secrets, found, _ := unstructured.NestedSlice(o, "secrets") + secrets, found, _ := o.GetNestedObjectList("secrets") if !found { return } @@ -89,70 +85,61 @@ func normalizeServiceAccount(o map[string]interface{}) { // remove default service account tokens var newSecrets []interface{} for _, s := range secrets { - s2, ok := s.(map[string]interface{}) - if !ok { - continue - } - - name, found, _ := unstructured.NestedString(s2, "name") + name, found, _ := s.GetNestedString("name") if !found || strings.HasPrefix(name, fmt.Sprintf("%s-", serviceAccountName)) { continue } newSecrets = append(newSecrets, s) } - o["secrets"] = newSecrets + _ = o.SetNestedField(newSecrets, "secrets") } -func normalizeMetadata(k *k8s.K8sCluster, o *unstructured.Unstructured) { - k.RemoveNamespaceIfNeeded(o) +func normalizeMetadata(k *k8s.K8sCluster, o *uo.UnstructuredObject) { + k.RemoveNamespaceIfNeeded(o.ToUnstructured()) // We don't care about managedFields when diffing (they just produce noise) - unstructured.RemoveNestedField(o.Object, "metadata", "managedFields") - unstructured.RemoveNestedField(o.Object, "metadata", "annotations", "managedFields", "kubectl.kubernetes.io/last-applied-configuration") + _ = o.RemoveNestedField("metadata", "managedFields") + _ = o.RemoveNestedField("metadata", "annotations", "managedFields", "kubectl.kubernetes.io/last-applied-configuration") // We don't want to see this in diffs - unstructured.RemoveNestedField(o.Object, "metadata", "creationTimestamp") - unstructured.RemoveNestedField(o.Object, "metadata", "generation") - unstructured.RemoveNestedField(o.Object, "metadata", "resourceVersion") - unstructured.RemoveNestedField(o.Object, "metadata", "selfLink") - unstructured.RemoveNestedField(o.Object, "metadata", "uid") + _ = o.RemoveNestedField("metadata", "creationTimestamp") + _ = o.RemoveNestedField("metadata", "generation") + _ = o.RemoveNestedField("metadata", "resourceVersion") + _ = o.RemoveNestedField("metadata", "selfLink") + _ = o.RemoveNestedField("metadata", "uid") // Ensure empty labels/metadata exist - if len(o.GetLabels()) == 0 { - _ = unstructured.SetNestedStringMap(o.Object, map[string]string{}, "metadata", "labels") - } - if len(o.GetAnnotations()) == 0 { - _ = unstructured.SetNestedStringMap(o.Object, map[string]string{}, "metadata", "annotations") - } + _ = o.SetNestedFieldDefault(map[string]string{}, "metadata", "labels") + _ = o.SetNestedFieldDefault(map[string]string{}, "metadata", "annotations") } -func normalizeMisc(o map[string]interface{}) { +func normalizeMisc(o *uo.UnstructuredObject) { // These are random values found in Jobs - unstructured.RemoveNestedField(o, "spec", "template", "metadata", "labels", "controller-uid") - unstructured.RemoveNestedField(o, "spec", "selector", "matchLabels", "controller-uid") + _ = o.RemoveNestedField("spec", "template", "metadata", "labels", "controller-uid") + _ = o.RemoveNestedField("spec", "selector", "matchLabels", "controller-uid") - unstructured.RemoveNestedField(o, "status") + _ = o.RemoveNestedField("status") } var ignoreDiffFieldAnnotationRegex = regexp.MustCompile(`^kluctl.io/ignore-diff-field(-\d*)?$`) // NormalizeObject Performs some deterministic sorting and other normalizations to avoid ugly diffs due to order changes -func NormalizeObject(k *k8s.K8sCluster, o *unstructured.Unstructured, ignoreForDiffs []*types.IgnoreForDiffItemConfig, localObject *unstructured.Unstructured) *unstructured.Unstructured { - gvk := o.GroupVersionKind() - name := o.GetName() - ns := o.GetNamespace() +func NormalizeObject(k *k8s.K8sCluster, o_ *unstructured.Unstructured, ignoreForDiffs []*types.IgnoreForDiffItemConfig, localObject *unstructured.Unstructured) *unstructured.Unstructured { + gvk := o_.GroupVersionKind() + name := o_.GetName() + ns := o_.GetNamespace() - o = utils.CopyUnstructured(o) + o := uo.FromUnstructured(o_).Clone() normalizeMetadata(k, o) - normalizeMisc(o.Object) + normalizeMisc(o) switch gvk.Kind { case "Deployment", "StatefulSet", "DaemonSet", "job": - normalizeContainers(utils.NestedMapSliceNoErr(o.Object, "spec", "template", "spec", "containers")) + normalizeContainers(o.GetNestedObjectListNoErr("spec", "template", "spec", "containers")) case "Secret", "ConfigMap": - normalizeSecretAndConfigMaps(o.Object) + normalizeSecretAndConfigMaps(o) case "ServiceAccount": - normalizeServiceAccount(o.Object) + normalizeServiceAccount(o) } checkMatch := func(v string, m *string) bool { @@ -177,7 +164,7 @@ func NormalizeObject(k *k8s.K8sCluster, o *unstructured.Unstructured, ignoreForD } for _, fp := range ifd.FieldPath { - jp, err := utils.NewMyJsonPath(fp) + jp, err := uo.NewMyJsonPath(fp) if err != nil { continue } @@ -193,7 +180,7 @@ func NormalizeObject(k *k8s.K8sCluster, o *unstructured.Unstructured, ignoreForD for k, v := range localObject.GetAnnotations() { if ignoreDiffFieldAnnotationRegex.MatchString(k) { - j, err := utils.NewMyJsonPath(v) + j, err := uo.NewMyJsonPath(v) if err != nil { continue } @@ -201,5 +188,5 @@ func NormalizeObject(k *k8s.K8sCluster, o *unstructured.Unstructured, ignoreForD } } - return o + return o.ToUnstructured() } diff --git a/pkg/jinja2_server/server.go b/pkg/jinja2_server/server.go index 933b069a3..cd191748c 100644 --- a/pkg/jinja2_server/server.go +++ b/pkg/jinja2_server/server.go @@ -7,6 +7,7 @@ import ( "encoding/json" "fmt" "github.com/codablock/kluctl/pkg/utils" + "github.com/codablock/kluctl/pkg/utils/uo" "github.com/gobwas/glob" log "github.com/sirupsen/logrus" "golang.org/x/sync/semaphore" @@ -138,8 +139,8 @@ func (js *Jinja2Server) isMaybeTemplate(template string, searchDirs []string, is return true, nil } -func (js *Jinja2Server) renderHelper(jobs []*RenderJob, searchDirs []string, vars map[string]interface{}, isString bool) error { - varsStr, err := json.Marshal(vars) +func (js *Jinja2Server) renderHelper(jobs []*RenderJob, searchDirs []string, vars *uo.UnstructuredObject, isString bool) error { + varsStr, err := json.Marshal(vars.Object) if err != nil { return err } @@ -194,15 +195,15 @@ func (js *Jinja2Server) renderHelper(jobs []*RenderJob, searchDirs []string, var return nil } -func (js *Jinja2Server) RenderStrings(jobs []*RenderJob, searchDirs []string, vars map[string]interface{}) error { +func (js *Jinja2Server) RenderStrings(jobs []*RenderJob, searchDirs []string, vars *uo.UnstructuredObject) error { return js.renderHelper(jobs, searchDirs, vars, true) } -func (js *Jinja2Server) RenderFiles(jobs []*RenderJob, searchDirs []string, vars map[string]interface{}) error { +func (js *Jinja2Server) RenderFiles(jobs []*RenderJob, searchDirs []string, vars *uo.UnstructuredObject) error { return js.renderHelper(jobs, searchDirs, vars, false) } -func (js *Jinja2Server) RenderString(template string, searchDirs []string, vars map[string]interface{}) (string, error) { +func (js *Jinja2Server) RenderString(template string, searchDirs []string, vars *uo.UnstructuredObject) (string, error) { jobs := []*RenderJob{{ Template: template, }} @@ -216,7 +217,7 @@ func (js *Jinja2Server) RenderString(template string, searchDirs []string, vars return *jobs[0].Result, nil } -func (js *Jinja2Server) RenderFile(template string, searchDirs []string, vars map[string]interface{}) (string, error) { +func (js *Jinja2Server) RenderFile(template string, searchDirs []string, vars *uo.UnstructuredObject) (string, error) { jobs := []*RenderJob{{ Template: template, }} @@ -230,8 +231,8 @@ func (js *Jinja2Server) RenderFile(template string, searchDirs []string, vars ma return *jobs[0].Result, nil } -func (js *Jinja2Server) RenderStruct(dst interface{}, src interface{}, vars map[string]interface{}) error { - m, err := utils.StructToObject(src) +func (js *Jinja2Server) RenderStruct(dst interface{}, src interface{}, vars *uo.UnstructuredObject) error { + m, err := uo.FromStruct(src) type pk struct { parent interface{} @@ -240,7 +241,7 @@ func (js *Jinja2Server) RenderStruct(dst interface{}, src interface{}, vars map[ var jobs []*RenderJob var fields []pk - err = utils.NewObjectIterator(m).IterateLeafs(func(it *utils.ObjectIterator) error { + err = m.NewIterator().IterateLeafs(func(it *uo.ObjectIterator) error { value := it.Value() if s, ok := value.(string); ok { jobs = append(jobs, &RenderJob{Template: s}) @@ -266,7 +267,7 @@ func (js *Jinja2Server) RenderStruct(dst interface{}, src interface{}, vars map[ errors = append(errors, err) } - err = utils.SetChild(fields[i].parent, fields[i].key, *j.Result) + err = uo.SetChild(fields[i].parent, fields[i].key, *j.Result) if err != nil { return err } @@ -275,7 +276,7 @@ func (js *Jinja2Server) RenderStruct(dst interface{}, src interface{}, vars map[ return utils.NewErrorList(errors) } - err = utils.ObjectToStruct(m, dst) + err = m.ToStruct(dst) if err != nil { return err } @@ -315,7 +316,7 @@ func (js *Jinja2Server) needsRender(path string, excludedPatterns []string) bool return true } -func (js *Jinja2Server) RenderDirectory(rootDir string, searchDirs []string, relSourceDir string, excludePatterns []string, subdir string, targetDir string, vars map[string]interface{}) error { +func (js *Jinja2Server) RenderDirectory(rootDir string, searchDirs []string, relSourceDir string, excludePatterns []string, subdir string, targetDir string, vars *uo.UnstructuredObject) error { walkDir := path.Join(rootDir, relSourceDir, subdir) var jobs []*RenderJob diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index 2b73cb162..4acfacef4 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -5,6 +5,7 @@ import ( "fmt" types2 "github.com/codablock/kluctl/pkg/types" "github.com/codablock/kluctl/pkg/utils" + "github.com/codablock/kluctl/pkg/utils/uo" goversion "github.com/hashicorp/go-version" log "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/api/errors" @@ -556,14 +557,14 @@ func (k *K8sCluster) FixObjectForPatch(o *unstructured.Unstructured) *unstructur return o } - o = utils.CopyUnstructured(o) + o = uo.CopyUnstructured(o) fixPorts := func(p string) { if !needsDefaultsFix { return } - ports, found, _ := utils.NewMyJsonPathMust(p).GetFirstListOfMaps(o) + ports, found, _ := uo.NewMyJsonPathMust(p).GetFirstListOfMaps(o) if !found { return } @@ -579,7 +580,7 @@ func (k *K8sCluster) FixObjectForPatch(o *unstructured.Unstructured) *unstructur if !needsTypeConversionFix { return } - d, found, _ := utils.NewMyJsonPathMust(p).GetFirstMap(o) + d, found, _ := uo.NewMyJsonPathMust(p).GetFirstMap(o) if !found { return } @@ -600,7 +601,7 @@ func (k *K8sCluster) FixObjectForPatch(o *unstructured.Unstructured) *unstructur } fixContainers := func(p string) { - containers, found, _ := utils.NewMyJsonPathMust(p).GetFirstListOfMaps(o) + containers, found, _ := uo.NewMyJsonPathMust(p).GetFirstListOfMaps(o) if !found { return } @@ -610,7 +611,7 @@ func (k *K8sCluster) FixObjectForPatch(o *unstructured.Unstructured) *unstructur } fixLimits := func(p string) { - limits, found, _ := utils.NewMyJsonPathMust(p).GetFirstListOfMaps(o) + limits, found, _ := uo.NewMyJsonPathMust(p).GetFirstListOfMaps(o) if !found { return } diff --git a/pkg/kluctl_project/load.go b/pkg/kluctl_project/load.go index 8f16e57c7..5ac0909d5 100644 --- a/pkg/kluctl_project/load.go +++ b/pkg/kluctl_project/load.go @@ -4,6 +4,7 @@ import ( "fmt" types2 "github.com/codablock/kluctl/pkg/types" "github.com/codablock/kluctl/pkg/utils" + "github.com/codablock/kluctl/pkg/yaml" log "github.com/sirupsen/logrus" "io/ioutil" "os" @@ -53,7 +54,7 @@ func (c *KluctlProjectContext) load(allowGit bool) error { } if configPath != "" { - err = utils.ReadYamlFile(configPath, &c.config) + err = yaml.ReadYamlFile(configPath, &c.config) if err != nil { return err } @@ -179,7 +180,7 @@ func loadKluctlProjectFromArchive(fromArchive string, fromArchiveMetadata string } var metadata types2.ArchiveMetadata - err := utils.ReadYamlFile(metdataPath, &metadata) + err := yaml.ReadYamlFile(metdataPath, &metadata) if err != nil { return nil, err } diff --git a/pkg/kluctl_project/load_targets.go b/pkg/kluctl_project/load_targets.go index 72b695e22..603a7d9c3 100644 --- a/pkg/kluctl_project/load_targets.go +++ b/pkg/kluctl_project/load_targets.go @@ -6,6 +6,8 @@ import ( git_url "github.com/codablock/kluctl/pkg/git/git-url" "github.com/codablock/kluctl/pkg/types" "github.com/codablock/kluctl/pkg/utils" + "github.com/codablock/kluctl/pkg/utils/uo" + "github.com/codablock/kluctl/pkg/yaml" log "github.com/sirupsen/logrus" "path" "reflect" @@ -289,13 +291,13 @@ func (c *KluctlProjectContext) buildDynamicTarget(targetInfo *dynamicTargetInfo) } var targetConfig types.TargetConfig - err = utils.ReadYamlFile(configPath, &targetConfig) + err = yaml.ReadYamlFile(configPath, &targetConfig) if err != nil { return nil, err } // check and merge args - err = utils.NewObjectIterator(targetConfig.Args).IterateLeafs(func(it *utils.ObjectIterator) error { + err = targetConfig.Args.NewIterator().IterateLeafs(func(it *uo.ObjectIterator) error { strValue := fmt.Sprintf("%v", it.Value()) err := c.CheckDynamicArg(&target, it.JsonPath(), strValue) if err != nil { @@ -306,7 +308,7 @@ func (c *KluctlProjectContext) buildDynamicTarget(targetInfo *dynamicTargetInfo) if err != nil { return nil, err } - utils.MergeObject(target.Args, targetConfig.Args) + target.Args.Merge(targetConfig.Args) // We prepend the dynamic images to ensure they get higher priority later target.Images = append(targetConfig.Images, target.Images...) diff --git a/pkg/types/cluster_config.go b/pkg/types/cluster_config.go index 56abb35b0..d0e49e014 100644 --- a/pkg/types/cluster_config.go +++ b/pkg/types/cluster_config.go @@ -3,13 +3,15 @@ package types import ( "fmt" "github.com/codablock/kluctl/pkg/utils" + "github.com/codablock/kluctl/pkg/utils/uo" + "github.com/codablock/kluctl/pkg/yaml" "path" ) type ClusterConfig2 struct { Name string `yaml:"name" validate:"required"` Context string `yaml:"context" validate:"required"` - Vars map[string]interface{} `yaml:"vars,omitempty"` + Vars *uo.UnstructuredObject `yaml:"vars,omitempty"` } type ClusterConfig struct { @@ -18,43 +20,38 @@ type ClusterConfig struct { // TODO remove custom unmarshaller when https://github.com/goccy/go-yaml/pull/220 gets released func (cc *ClusterConfig2) UnmarshalYAML(unmarshal func(interface{}) error) error { - var m map[string]interface{} - err := unmarshal(&m) + var u uo.UnstructuredObject + err := unmarshal(&u) if err != nil { return err } - nameI, ok := m["name"] + name, ok, err := u.GetNestedString("name") if !ok { - return fmt.Errorf("name is missing in cluster config") + return fmt.Errorf("name is missing (or not a string) in cluster config") } - contextI, ok := m["context"] + context, ok, err := u.GetNestedString("context") if !ok { - return fmt.Errorf("context is missing in cluster config") + return fmt.Errorf("context is missing (or not a string) in cluster config") } - cc.Name, ok = nameI.(string) - if !ok { - return fmt.Errorf("name is not a string") - } - cc.Context, ok = contextI.(string) - if !ok { - return fmt.Errorf("context is not a string") - } + cc.Name = name + cc.Context = context + + _ = u.RemoveNestedField("name") + _ = u.RemoveNestedField("context") - delete(m, "name") - delete(m, "context") - cc.Vars = m + cc.Vars = &u return nil } func (cc *ClusterConfig2) MarshalYAML() (interface{}, error) { - m := map[string]interface{}{ - "name": cc.Name, + o := uo.FromMap(map[string]interface{}{ + "name": cc.Name, "context": cc.Context, - } - utils.MergeObject(m, cc.Vars) - return m, nil + }) + o.Merge(cc.Vars) + return o, nil } func LoadClusterConfig(clusterDir string, clusterName string) (*ClusterConfig, error) { @@ -68,7 +65,7 @@ func LoadClusterConfig(clusterDir string, clusterName string) (*ClusterConfig, e } var config ClusterConfig - err := utils.ReadYamlFile(p, &config) + err := yaml.ReadYamlFile(p, &config) if err != nil { return nil, err } diff --git a/pkg/types/deployment.go b/pkg/types/deployment.go index cfec841c4..fdc576508 100644 --- a/pkg/types/deployment.go +++ b/pkg/types/deployment.go @@ -1,7 +1,8 @@ package types import ( - "github.com/codablock/kluctl/pkg/utils" + "github.com/codablock/kluctl/pkg/utils/uo" + "github.com/codablock/kluctl/pkg/yaml" "github.com/go-playground/validator/v10" ) @@ -27,7 +28,7 @@ type VarsListItemClusterConfigMapOrSecret struct { } type VarsListItem struct { - Values *map[string]interface{} `yaml:"values,omitempty"` + Values *uo.UnstructuredObject `yaml:"values,omitempty"` File *string `yaml:"file,omitempty"` ClusterConfigMap *VarsListItemClusterConfigMapOrSecret `yaml:"clusterConfigMap,omitempty"` ClusterSecret *VarsListItemClusterConfigMapOrSecret `yaml:"clusterSecret,omitempty"` @@ -106,5 +107,5 @@ type DeploymentProjectConfig struct { } func init() { - utils.Validator.RegisterStructValidation(ValidateVarsListItem, VarsListItem{}) + yaml.Validator.RegisterStructValidation(ValidateVarsListItem, VarsListItem{}) } diff --git a/pkg/types/git_project.go b/pkg/types/git_project.go index 042cb27f3..983cf5ce3 100644 --- a/pkg/types/git_project.go +++ b/pkg/types/git_project.go @@ -2,7 +2,7 @@ package types import ( git_url "github.com/codablock/kluctl/pkg/git/git-url" - "github.com/codablock/kluctl/pkg/utils" + "github.com/codablock/kluctl/pkg/yaml" "github.com/go-playground/validator/v10" "strings" ) @@ -49,5 +49,5 @@ func (gp *ExternalProjects) UnmarshalYAML(unmarshal func(interface{}) error) err } func init() { - utils.Validator.RegisterStructValidation(ValidateGitProject, GitProject{}) + yaml.Validator.RegisterStructValidation(ValidateGitProject, GitProject{}) } diff --git a/pkg/types/kluctl_project.go b/pkg/types/kluctl_project.go index 8a770c867..31438c954 100644 --- a/pkg/types/kluctl_project.go +++ b/pkg/types/kluctl_project.go @@ -1,7 +1,8 @@ package types import ( - "github.com/codablock/kluctl/pkg/utils" + "github.com/codablock/kluctl/pkg/utils/uo" + "github.com/codablock/kluctl/pkg/yaml" ) type DynamicArg struct { @@ -22,17 +23,17 @@ type ExternalTargetConfig struct { type SealingConfig struct { // DynamicSealing Set this to false if you want to disable sealing for every dynamic target DynamicSealing bool `yaml:"dynamicSealing,omitempty"` - Args map[string]interface{} `yaml:"args,omitempty"` + Args *uo.UnstructuredObject `yaml:"args,omitempty"` SecretSets []string `yaml:"secretSets,omitempty"` } type Target struct { Name string `yaml:"name" validate:"required"` Cluster string `yaml:"cluster" validate:"required"` - Args map[string]interface{} `yaml:"args,omitempty"` + Args *uo.UnstructuredObject `yaml:"args,omitempty"` DynamicArgs []DynamicArg `yaml:"dynamicArgs,omitempty"` - TargetConfig *ExternalTargetConfig `yaml:"targetConfig"` - SealingConfig SealingConfig `yaml:"sealingConfig"` + TargetConfig *ExternalTargetConfig `yaml:"targetConfig,omitempty"` + SealingConfig *SealingConfig `yaml:"sealingConfig,omitempty"` Images []FixedImage `yaml:"images,omitempty"` } @@ -54,5 +55,5 @@ type KluctlProject struct { } func init() { - utils.Validator.RegisterStructValidation(ValidateSecretSource, SecretSource{}) + yaml.Validator.RegisterStructValidation(ValidateSecretSource, SecretSource{}) } diff --git a/pkg/types/metadata.go b/pkg/types/metadata.go index 477f2cdc4..6bd5b181f 100644 --- a/pkg/types/metadata.go +++ b/pkg/types/metadata.go @@ -1,7 +1,7 @@ package types import ( - "github.com/codablock/kluctl/pkg/utils" + "github.com/codablock/kluctl/pkg/yaml" ) type InvolvedRepo struct { @@ -16,5 +16,5 @@ type ArchiveMetadata struct { func LoadArchiveMetadata(p string) (*ArchiveMetadata, error) { o := &ArchiveMetadata{} - return o, utils.ReadYamlFile(p, o) + return o, yaml.ReadYamlFile(p, o) } diff --git a/pkg/types/target_config.go b/pkg/types/target_config.go index 9040450c2..94e265cfc 100644 --- a/pkg/types/target_config.go +++ b/pkg/types/target_config.go @@ -1,5 +1,7 @@ package types +import "github.com/codablock/kluctl/pkg/utils/uo" + type FixedImage struct { Image string `yaml:"image" validate:"required"` ResultImage string `yaml:"resultImage" validate:"required"` @@ -19,5 +21,5 @@ type FixedImagesConfig struct { type TargetConfig struct { FixedImagesConfig `yaml:"fixed_images_config,inline"` - Args map[string]interface{} `yaml:"args,omitempty"` + Args *uo.UnstructuredObject `yaml:"args,omitempty"` } diff --git a/pkg/utils/unstructured.go b/pkg/utils/unstructured.go deleted file mode 100644 index bc4d65e28..000000000 --- a/pkg/utils/unstructured.go +++ /dev/null @@ -1,164 +0,0 @@ -package utils - -import ( - "fmt" - log "github.com/sirupsen/logrus" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" -) - -func CopyObject(a map[string]interface{}) map[string]interface{} { - var c map[string]interface{} - err := DeepCopy(&c, &a) - if err != nil { - log.Fatal(err) - } - return c -} - -func CopyUnstructured(u *unstructured.Unstructured) *unstructured.Unstructured { - return &unstructured.Unstructured{Object: CopyObject(u.Object)} -} - -func StructToObject(s interface{}) (map[string]interface{}, error) { - b, err := WriteYamlBytes(s) - if err != nil { - return nil, err - } - - var m map[string]interface{} - err = ReadYamlBytes(b, &m) - if err != nil { - return nil, err - } - return m, nil -} - -func ObjectToStruct(m map[string]interface{}, dst interface{}) error { - b, err := WriteYamlBytes(m) - if err != nil { - return err - } - return ReadYamlBytes(b, dst) -} - -func MergeObject(a map[string]interface{}, b map[string]interface{}) { - if b == nil { - b = map[string]interface{}{} - } - - for key := range b { - if _, ok := a[key]; ok { - adict, adictOk := a[key].(map[string]interface{}) - bdict, bdictOk := b[key].(map[string]interface{}) - if adictOk && bdictOk { - MergeObject(adict, bdict) - } else { - a[key] = b[key] - } - } else { - a[key] = b[key] - } - } -} - -func CopyMergeObjects(a map[string]interface{}, b map[string]interface{}) (map[string]interface{}, error) { - c := CopyObject(a) - MergeObject(c, b) - return c, nil -} - -func MergeStrMap(a map[string]string, b map[string]string) { - for k, v := range b { - a[k] = v - } -} - -func CopyMergeStrMap(a map[string]string, b map[string]string) map[string]string { - c := make(map[string]string) - MergeStrMap(c, a) - MergeStrMap(c, b) - return c -} - -func NestedMapSlice(o map[string]interface{}, fields ...string) ([]map[string]interface{}, bool, error) { - a, found, err := unstructured.NestedSlice(o, fields...) - if err != nil { - return nil, false, err - } - if !found { - return nil, false, nil - } - - var ret []map[string]interface{} - for _, x := range a { - x2, ok := x.(map[string]interface{}) - if !ok { - return nil, false, fmt.Errorf("nested value is not a slice of maps") - } - ret = append(ret, x2) - } - return ret, true, nil -} - -func NestedMapSliceNoErr(o map[string]interface{}, fields ...string) []map[string]interface{} { - l, found, err := NestedMapSlice(o, fields...) - if !found || err != nil { - return nil - } - return l -} - -func GetChild(parent interface{}, key interface{}) (interface{}, bool, error) { - if m, ok := parent.(map[string]interface{}); ok { - keyStr, ok := key.(string) - if !ok { - return nil, false, fmt.Errorf("key is not a string") - } - v, found := m[keyStr] - return v, found, nil - } else if l, ok := parent.([]interface{}); ok { - keyInt, ok := key.(int) - if !ok { - return nil, false, fmt.Errorf("key is not an int") - } - if keyInt < 0 || keyInt >= len(l) { - return nil, false, fmt.Errorf("index out of bounds") - } - v := l[keyInt] - return v, true, nil - } - return nil, false, fmt.Errorf("unknown parent type") -} - -func GetNestedChild(o interface{}, keys ...interface{}) (interface{}, bool, error) { - for _, k := range keys { - v, found, err := GetChild(o, k) - if err != nil { - return nil, false, err - } - if !found { - return nil, false, nil - } - o = v - } - return o, true, nil -} - -func SetChild(parent interface{}, key interface{}, value interface{}) error { - if m, ok := parent.(map[string]interface{}); ok { - keyStr, ok := key.(string) - if !ok { - return fmt.Errorf("key is not a string") - } - m[keyStr] = value - return nil - } else if l, ok := parent.([]interface{}); ok { - keyInt, ok := key.(int) - if !ok { - return fmt.Errorf("key is not an int") - } - l[keyInt] = value - return nil - } - return fmt.Errorf("unknown parent type") -} diff --git a/pkg/utils/jsonpath.go b/pkg/utils/uo/jsonpath.go similarity index 91% rename from pkg/utils/jsonpath.go rename to pkg/utils/uo/jsonpath.go index 31b9530e0..d96e61afc 100644 --- a/pkg/utils/jsonpath.go +++ b/pkg/utils/uo/jsonpath.go @@ -1,4 +1,4 @@ -package utils +package uo import ( "fmt" @@ -60,10 +60,10 @@ func NewMyJsonPathMust(p string) *MyJsonPath { return j } -func (j *MyJsonPath) ListMatchingFields(o map[string]interface{}) ([][]interface{}, error) { +func (j *MyJsonPath) ListMatchingFields(o *UnstructuredObject) ([][]interface{}, error) { var ret [][]interface{} - o = CopyObject(o) + o = o.Clone() magic := struct{}{} err := j.exp.Set(o, magic) @@ -71,7 +71,7 @@ func (j *MyJsonPath) ListMatchingFields(o map[string]interface{}) ([][]interface return nil, err } - _ = NewObjectIterator(o).IterateLeafs(func(it *ObjectIterator) error { + _ = o.NewIterator().IterateLeafs(func(it *ObjectIterator) error { if it.Value() == magic { var c []interface{} c = append(c, it.KeyPath()...) diff --git a/pkg/utils/uo/nested_fields.go b/pkg/utils/uo/nested_fields.go new file mode 100644 index 000000000..130df89aa --- /dev/null +++ b/pkg/utils/uo/nested_fields.go @@ -0,0 +1,165 @@ +package uo + +import "fmt" + +func (uo *UnstructuredObject) GetNestedField(keys ...interface{}) (interface{}, bool, error) { + var o interface{} = uo.Object + for _, k := range keys { + v, found, err := GetChild(o, k) + if err != nil { + return nil, false, err + } + if !found { + return nil, false, nil + } + o = v + } + return o, true, nil +} + +func (uo *UnstructuredObject) SetNestedField(value interface{}, keys ...interface{}) error { + var o interface{} = uo.Object + + for _, field := range keys[:len(keys)-1] { + val, ok, err := GetChild(o, field) + if err != nil { + return err + } + if ok { + o = val + } else { + newVal := make(map[string]interface{}) + err = SetChild(o, field, newVal) + if err != nil { + return err + } + o = newVal + } + } + return SetChild(o, keys[len(keys)-1], value) +} + +func (uo *UnstructuredObject) RemoveNestedField(keys ...interface{}) error { + var o interface{} = uo.Object + + for _, field := range keys[:len(keys)-1] { + val, ok, err := GetChild(o, field) + if err != nil { + return err + } + if !ok { + return nil + } + o = val + } + last := keys[len(keys)-1] + if l, ok := o.([]interface{}); ok { + if i, ok := last.(int); ok { + if i < 0 || i >= len(l) { + return nil + } + l = append(l[:i], l[i+1:]...) + return SetChild(o, i, l) + } else { + return fmt.Errorf("key is not an index") + } + } else if m, ok := o.(map[string]interface{}); ok { + if s, ok := last.(string); ok { + delete(m, s) + } else { + return fmt.Errorf("key is not a string") + } + } + + return nil +} + +func (uo *UnstructuredObject) GetNestedString(keys ...interface{}) (string, bool, error) { + v, found, err := uo.GetNestedField(keys...) + if err != nil { + return "", false, err + } + if !found { + return "", false, nil + } + s, ok := v.(string) + if !ok { + return "", false, fmt.Errorf("value at %s is not a string", KeyListToJsonPath(keys)) + } + return s, true, nil +} + +func (uo *UnstructuredObject) GetNestedList(keys ...interface{}) ([]interface{}, bool, error) { + v, found, err := uo.GetNestedField(keys...) + if err != nil { + return nil, false, err + } + if !found { + return nil, false, nil + } + l, ok := v.([]interface{}) + if !ok { + return nil, false, fmt.Errorf("value at %s is not a slice", KeyListToJsonPath(keys)) + } + return l, true, nil +} + +func (uo *UnstructuredObject) GetNestedObject(keys ...interface{}) (*UnstructuredObject, bool, error) { + a, found, err := uo.GetNestedField(keys...) + if err != nil { + return nil, false, err + } + if !found { + return nil, false, nil + } + + m, ok := a.(map[string]interface{}) + if !ok { + return nil, false, fmt.Errorf("nested value is not a map") + } + return FromMap(m), true, nil +} + +func (uo *UnstructuredObject) GetNestedObjectList(keys ...interface{}) ([]*UnstructuredObject, bool, error) { + a, found, err := uo.GetNestedField(keys...) + if err != nil { + return nil, false, err + } + if !found { + return nil, false, nil + } + + l, ok := a.([]interface{}) + if !ok { + return nil, false, fmt.Errorf("nested value is not a slice") + } + + var ret []*UnstructuredObject + for _, x := range l { + x2, ok := x.(map[string]interface{}) + if !ok { + return nil, false, fmt.Errorf("nested value is not a slice of maps") + } + ret = append(ret, FromMap(x2)) + } + return ret, true, nil +} + +func (uo *UnstructuredObject) GetNestedObjectListNoErr(keys ...interface{}) []*UnstructuredObject { + l, found, err := uo.GetNestedObjectList(keys...) + if !found || err != nil { + return nil + } + return l +} + +func (uo *UnstructuredObject) SetNestedFieldDefault(defaultValue interface{}, keys ...interface{}) error { + v, found, err := uo.GetNestedField(keys...) + if err != nil { + return err + } + if found && v != nil { + return nil + } + return uo.SetNestedField(defaultValue, keys...) +} diff --git a/pkg/utils/object_iterator.go b/pkg/utils/uo/object_iterator.go similarity index 99% rename from pkg/utils/object_iterator.go rename to pkg/utils/uo/object_iterator.go index 0b8d82861..d39ce6152 100644 --- a/pkg/utils/object_iterator.go +++ b/pkg/utils/uo/object_iterator.go @@ -1,4 +1,4 @@ -package utils +package uo import ( log "github.com/sirupsen/logrus" diff --git a/pkg/utils/uo/unstructured.go b/pkg/utils/uo/unstructured.go new file mode 100644 index 000000000..885908391 --- /dev/null +++ b/pkg/utils/uo/unstructured.go @@ -0,0 +1,71 @@ +package uo + +import ( + "fmt" + "github.com/jinzhu/copier" + log "github.com/sirupsen/logrus" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +func CopyUnstructured(u *unstructured.Unstructured) *unstructured.Unstructured { + var m map[string]interface{} + err := copier.Copy(&m, &u.Object) + if err != nil { + log.Fatal(err) + } + return &unstructured.Unstructured{Object: m} +} + +func MergeStrMap(a map[string]string, b map[string]string) { + for k, v := range b { + a[k] = v + } +} + +func CopyMergeStrMap(a map[string]string, b map[string]string) map[string]string { + c := make(map[string]string) + MergeStrMap(c, a) + MergeStrMap(c, b) + return c +} + +func GetChild(parent interface{}, key interface{}) (interface{}, bool, error) { + if m, ok := parent.(map[string]interface{}); ok { + keyStr, ok := key.(string) + if !ok { + return nil, false, fmt.Errorf("key is not a string") + } + v, found := m[keyStr] + return v, found, nil + } else if l, ok := parent.([]interface{}); ok { + keyInt, ok := key.(int) + if !ok { + return nil, false, fmt.Errorf("key is not an int") + } + if keyInt < 0 || keyInt >= len(l) { + return nil, false, fmt.Errorf("index out of bounds") + } + v := l[keyInt] + return v, true, nil + } + return nil, false, fmt.Errorf("unknown parent type") +} + +func SetChild(parent interface{}, key interface{}, value interface{}) error { + if m, ok := parent.(map[string]interface{}); ok { + keyStr, ok := key.(string) + if !ok { + return fmt.Errorf("key is not a string") + } + m[keyStr] = value + return nil + } else if l, ok := parent.([]interface{}); ok { + keyInt, ok := key.(int) + if !ok { + return fmt.Errorf("key is not an int") + } + l[keyInt] = value + return nil + } + return fmt.Errorf("unknown parent type") +} diff --git a/pkg/utils/uo/uo.go b/pkg/utils/uo/uo.go new file mode 100644 index 000000000..8b1bed350 --- /dev/null +++ b/pkg/utils/uo/uo.go @@ -0,0 +1,94 @@ +package uo + +import ( + "github.com/codablock/kluctl/pkg/yaml" + "github.com/jinzhu/copier" + log "github.com/sirupsen/logrus" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +type UnstructuredObject struct { + Object map[string]interface{} `yaml:"object,omitempty,inline"` +} + +func (uo *UnstructuredObject) MarshalYAML() (interface{}, error) { + return &uo.Object, nil +} + +func (uo *UnstructuredObject) UnmarshalYAML(unmarshal func(interface{}) error) error { + return unmarshal(&uo.Object) +} + +func (uo *UnstructuredObject) IsZero() bool { + return len(uo.Object) == 0 +} + +func New() *UnstructuredObject { + return &UnstructuredObject{Object: map[string]interface{}{}} +} + +func FromMap(o map[string]interface{}) *UnstructuredObject { + return &UnstructuredObject{Object: o} +} + +func FromUnstructured(u *unstructured.Unstructured) *UnstructuredObject { + return FromMap(u.Object) +} + +func (uo *UnstructuredObject) ToUnstructured() *unstructured.Unstructured { + return &unstructured.Unstructured{Object: uo.Object} +} + +func FromStruct(o interface{}) (*UnstructuredObject, error) { + b, err := yaml.WriteYamlBytes(o) + if err != nil { + return nil, err + } + + var m map[string]interface{} + err = yaml.ReadYamlBytes(b, &m) + if err != nil { + return nil, err + } + return FromMap(m), nil +} + +func (uo *UnstructuredObject) ToStruct(out interface{}) error { + b, err := yaml.WriteYamlBytes(uo.Object) + if err != nil { + return err + } + + return yaml.ReadYamlBytes(b, out) +} + +func (uo *UnstructuredObject) Clone() *UnstructuredObject { + var c map[string]interface{} + err := copier.CopyWithOption(&c, &uo.Object, copier.Option{ + DeepCopy: true, + }) + if err != nil { + log.Fatal(err) + } + return FromMap(c) +} + +func (uo *UnstructuredObject) Merge(other *UnstructuredObject) { + MergeMap(uo.Object, other.Object) +} + +func (uo *UnstructuredObject) MergeChild(child string, other *UnstructuredObject) { + MergeMap(uo.Object, map[string]interface{}{ + child: other.Object, + }) +} + +func (uo *UnstructuredObject) MergeCopy(other *UnstructuredObject) *UnstructuredObject { + c := uo.Clone() + c.Merge(other) + return c +} + +func (uo *UnstructuredObject) NewIterator() *ObjectIterator { + return NewObjectIterator(uo.Object) +} diff --git a/pkg/utils/uo/utils.go b/pkg/utils/uo/utils.go new file mode 100644 index 000000000..3e324fc1c --- /dev/null +++ b/pkg/utils/uo/utils.go @@ -0,0 +1,17 @@ +package uo + +func MergeMap(a, b map[string]interface{}) { + for key := range b { + if _, ok := a[key]; ok { + adict, adictOk := a[key].(map[string]interface{}) + bdict, bdictOk := b[key].(map[string]interface{}) + if adictOk && bdictOk { + MergeMap(adict, bdict) + } else { + a[key] = b[key] + } + } else { + a[key] = b[key] + } + } +} diff --git a/pkg/utils/validator.go b/pkg/yaml/validator.go similarity index 86% rename from pkg/utils/validator.go rename to pkg/yaml/validator.go index 726e5613b..aa2398605 100644 --- a/pkg/utils/validator.go +++ b/pkg/yaml/validator.go @@ -1,4 +1,4 @@ -package utils +package yaml import "github.com/go-playground/validator/v10" diff --git a/pkg/utils/yaml.go b/pkg/yaml/yaml.go similarity index 99% rename from pkg/utils/yaml.go rename to pkg/yaml/yaml.go index 2937e6460..6e0d70733 100644 --- a/pkg/utils/yaml.go +++ b/pkg/yaml/yaml.go @@ -1,4 +1,4 @@ -package utils +package yaml import ( "bytes" From 69c975145fb17f90450e56ddca8b6df7c497e0d1 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 14 Feb 2022 15:03:42 +0100 Subject: [PATCH 0344/2916] Implement seal command --- cmd/kluctl/cmd_seal.go | 179 ++++++++++++++++ cmd/kluctl/utils.go | 6 +- go.mod | 7 +- go.sum | 246 ++++++++++++++++++++++ pkg/deployment/deployment_collection.go | 33 ++- pkg/deployment/deployment_item.go | 6 +- pkg/deployment/deployment_project.go | 21 +- pkg/deployment/external_args.go | 5 +- pkg/jinja2_server/server.go | 3 + pkg/{deployment => jinja2_server}/vars.go | 15 +- pkg/k8s/k8s_cluster.go | 30 ++- pkg/kluctl_project/git.go | 8 +- pkg/kluctl_project/load.go | 12 +- pkg/kluctl_project/load_targets.go | 44 ++-- pkg/kluctl_project/project.go | 19 +- pkg/seal/bootstrap.go | 95 +++++++++ pkg/seal/fetch_cert.go | 109 ++++++++++ pkg/seal/sealer.go | 244 +++++++++++++++++++++ pkg/seal/secrets_loader.go | 114 ++++++++++ pkg/types/deployment.go | 2 +- pkg/types/kluctl_project.go | 22 +- pkg/types/metadata.go | 11 +- pkg/types/secrets_source.go | 17 +- pkg/utils/aws/arn.go | 42 ++++ pkg/utils/aws/secrets_manager.go | 47 +++++ pkg/utils/uo/k8s_fields.go | 125 +++++++++++ pkg/utils/uo/nested_fields.go | 24 +++ pkg/utils/uo/uo.go | 18 ++ pkg/yaml/yaml.go | 16 +- 29 files changed, 1426 insertions(+), 94 deletions(-) create mode 100644 cmd/kluctl/cmd_seal.go rename pkg/{deployment => jinja2_server}/vars.go (89%) create mode 100644 pkg/seal/bootstrap.go create mode 100644 pkg/seal/fetch_cert.go create mode 100644 pkg/seal/sealer.go create mode 100644 pkg/seal/secrets_loader.go create mode 100644 pkg/utils/aws/arn.go create mode 100644 pkg/utils/aws/secrets_manager.go create mode 100644 pkg/utils/uo/k8s_fields.go diff --git a/cmd/kluctl/cmd_seal.go b/cmd/kluctl/cmd_seal.go new file mode 100644 index 000000000..505a1f231 --- /dev/null +++ b/cmd/kluctl/cmd_seal.go @@ -0,0 +1,179 @@ +package main + +import ( + "fmt" + "github.com/codablock/kluctl/cmd/kluctl/args" + "github.com/codablock/kluctl/pkg/kluctl_project" + "github.com/codablock/kluctl/pkg/seal" + "github.com/codablock/kluctl/pkg/types" + "github.com/codablock/kluctl/pkg/utils/uo" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var ( + SecretsDir string + ForceReseal bool +) + +func findSecretsEntry(ctx *commandCtx, name string) (*types.SecretSet, error) { + for _, e := range ctx.kluctlProject.Config.SecretsConfig.SecretSets { + if e.Name == name { + return &e, nil + } + } + return nil, fmt.Errorf("secret Set with name %s was not found", name) +} + +func loadSecrets(ctx *commandCtx, target *types.Target, secretsLoader *seal.SecretsLoader) error { + secrets := uo.New() + for _, secretSetName := range target.SealingConfig.SecretSets { + secretEntry, err := findSecretsEntry(ctx, secretSetName) + if err != nil { + return err + } + for _, source := range secretEntry.Sources { + var renderedSource types.SecretSource + err = ctx.kluctlProject.JS.RenderStruct(&renderedSource, &source, ctx.deploymentProject.VarsCtx.Vars) + if err != nil { + return err + } + s, err := secretsLoader.LoadSecrets(&renderedSource) + if err != nil { + return err + } + secrets.Merge(s) + } + } + ctx.deploymentProject.MergeSecretsIntoAllChildren(secrets) + return nil +} + +func runCmdSealForTarget(p *kluctl_project.KluctlProjectContext, target *types.Target, secretsLoader *seal.SecretsLoader) error { + log.Infof("Sealing for target %s", target.Name) + + // pass forSeal=True so that .sealme files are rendered as well + return withProjectTargetCommandContext(p, target, true, func(ctx *commandCtx) error { + err := loadSecrets(ctx, target, secretsLoader) + if err != nil { + return err + } + err = ctx.deploymentCollection.RenderDeployments(ctx.k) + if err != nil { + return err + } + + sealedSecretsNamespace := "kube-system" + sealedSecretsControllerName := "sealed-secrets-controller" + if p.Config.SecretsConfig != nil && p.Config.SecretsConfig.SealedSecrets != nil { + if p.Config.SecretsConfig.SealedSecrets.Namespace != nil { + sealedSecretsNamespace = *p.Config.SecretsConfig.SealedSecrets.Namespace + } + if p.Config.SecretsConfig.SealedSecrets.ControllerName != nil { + sealedSecretsControllerName = *p.Config.SecretsConfig.SealedSecrets.ControllerName + } + } + if p.Config.SecretsConfig == nil || p.Config.SecretsConfig.SealedSecrets == nil || p.Config.SecretsConfig.SealedSecrets.Bootstrap == nil || *p.Config.SecretsConfig.SealedSecrets.Bootstrap { + err = seal.BootstrapSealedSecrets(ctx.k, sealedSecretsNamespace) + if err != nil { + return err + } + } + + clusterConfig, err := p.LoadClusterConfig(target.Cluster) + if err != nil { + return err + } + sealer, err := seal.NewSealer(ctx.k, sealedSecretsNamespace, sealedSecretsControllerName, clusterConfig.Cluster, ForceReseal) + if err != nil { + return err + } + + err = ctx.deploymentCollection.Seal(sealer) + if err != nil { + return err + } + return err + }) +} + +func runCmdSeal(cmd *cobra.Command, args_ []string) error { + return withKluctlProjectFromArgs(func(p *kluctl_project.KluctlProjectContext) error { + hadError := false + + secretsLoader := seal.NewSecretsLoader(p, SecretsDir) + + baseTargets := make(map[string]bool) + noTargetMatch := true + for _, target := range p.DynamicTargets { + if args.Target != "" && args.Target != target.Target.Name { + continue + } + if args.Cluster != "" && args.Cluster != target.Target.Cluster { + continue + } + if target.Target.SealingConfig == nil { + log.Infof("Target %s has no sealingConfig", target.Target.Name) + continue + } + noTargetMatch = false + + sealTarget := target.Target + dynamicSealing := target.Target.SealingConfig.DynamicSealing == nil || *target.Target.SealingConfig.DynamicSealing + isDynamicTarget := target.BaseTargetName != target.Target.Name + if !dynamicSealing && isDynamicTarget { + baseTarget, err := p.FindBaseTarget(target.BaseTargetName) + if err != nil { + return err + } + if baseTargets[target.BaseTargetName] { + // Skip this target as it was already sealed + continue + } + baseTargets[target.BaseTargetName] = true + sealTarget = baseTarget + } + + err := runCmdSealForTarget(p, sealTarget, secretsLoader) + if err != nil { + log.Warningf("Sealing for target %s failed: %v", sealTarget.Name, err) + hadError = true + } + } + if hadError { + return fmt.Errorf("sealing for at least one target failed") + } + if noTargetMatch { + return fmt.Errorf("no target matched the given target and/or cluster name") + } + return nil + }) +} + +func init() { + cmd := &cobra.Command{ + Use: "seal", + Short: "Seal secrets based on target's sealingConfig", + Long: "Loads all secrets from the specified secrets sets from the target's sealingConfig and " + + "then renders the target, including all files with the `.sealme` extension. Then runs " + + "kubeseal on each `.sealme` file and stores secrets in the directory specified by " + + "`--local-sealed-secrets`, using the outputPattern from your deployment project.\n\n" + + "If no `--target` is specified, sealing is performed for all targets.", + RunE: runCmdSeal, + } + + args.AddProjectArgs(cmd, true, true, true) + + cmd.Flags().StringVar(&SecretsDir, "secrets-dir", ".", "Specifies where to find unencrypted secret files. The given directory is NOT meant to be part "+ + "of your source repository! The given path only matters for secrets of type 'path'. Defaults "+ + "to the current working directory.") + cmd.Flags().BoolVar(&ForceReseal, "force-reseal", false, "Lets kluctl ignore secret hashes found in already sealed secrets and thus forces resealing of those.") + + err := viper.BindPFlags(cmd.Flags()) + if err != nil { + panic(err) + } + + rootCmd.AddCommand(cmd) +} diff --git a/cmd/kluctl/utils.go b/cmd/kluctl/utils.go index c31a00079..f43864632 100644 --- a/cmd/kluctl/utils.go +++ b/cmd/kluctl/utils.go @@ -56,11 +56,11 @@ func withProjectCommandContext(cb func(ctx *commandCtx) error) error { return withKluctlProjectFromArgs(func(p *kluctl_project.KluctlProjectContext) error { var target *types.Target if args.Target != "" { - t, err := p.FindTarget(args.Target) + t, err := p.FindDynamicTarget(args.Target) if err != nil { return err } - target = t + target = t.Target } return withProjectTargetCommandContext(p, target, false, cb) }) @@ -90,7 +90,7 @@ func withProjectTargetCommandContext(p *kluctl_project.KluctlProjectContext, tar return err } - varsCtx := deployment.NewVarsCtx(p.JS) + varsCtx := jinja2_server.NewVarsCtx(p.JS) err = varsCtx.UpdateChildFromStruct("cluster", clusterConfig.Cluster) if err != nil { return err diff --git a/go.mod b/go.mod index db0d51385..3dcaec293 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,8 @@ module github.com/codablock/kluctl go 1.17 require ( + github.com/aws/aws-sdk-go v1.27.0 + github.com/bitnami-labs/sealed-secrets v0.17.3 github.com/gammazero/workerpool v1.1.2 github.com/go-git/go-git/v5 v5.4.2 github.com/go-playground/validator/v10 v10.10.0 @@ -23,6 +25,8 @@ require ( golang.org/x/sync v0.0.0-20210220032951-036812b2e83c google.golang.org/grpc v1.43.0 google.golang.org/protobuf v1.27.1 + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b + k8s.io/api v0.23.3 k8s.io/apimachinery v0.23.3 k8s.io/client-go v0.23.3 sigs.k8s.io/kustomize/api v0.11.1 @@ -70,6 +74,7 @@ require ( github.com/imdario/mergo v0.3.12 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect @@ -111,8 +116,6 @@ require ( gopkg.in/ini.v1 v1.66.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect - k8s.io/api v0.23.3 // indirect k8s.io/klog/v2 v2.30.0 // indirect k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect k8s.io/utils v0.0.0-20211116205334-6203023598ed // indirect diff --git a/go.sum b/go.sum index 11d3b56f8..232b067ac 100644 --- a/go.sum +++ b/go.sum @@ -67,6 +67,7 @@ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBp github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY= @@ -75,19 +76,28 @@ github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb0 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C67SkzkDfmQuVln04ygHj3vjZfd9FL+GmQQ= github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= +github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= @@ -95,12 +105,23 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.27.0 h1:0xphMHGMLBrPMfxR2AmVjZKcMEESEgWF8Kru94BNByk= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bitnami-labs/flagenv v0.0.0-20190607135054-a87af7a1d6fc/go.mod h1:OeW4NPgFPO7+t8q1Vn2Yv+rkO+4kEQzlDskwm7C7PXs= +github.com/bitnami-labs/pflagenv v0.0.0-20190702160147-b4d9f048d98f/go.mod h1:Lw3ejf6HTt4DqBIAXlkOIvFjnpj8Zq+zD/UtH29ILFA= +github.com/bitnami-labs/sealed-secrets v0.17.3 h1:ebus6Rbz9MLhWcHVkFiwDpVkrecvjzOibXdkmJkRhss= +github.com/bitnami-labs/sealed-secrets v0.17.3/go.mod h1:EMXakbe/TMdfzATuEH+pTA2K29aZhNgyBNTdQvDc/eU= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= @@ -111,6 +132,7 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -122,19 +144,35 @@ github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -157,6 +195,8 @@ github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYF github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= @@ -166,6 +206,7 @@ github.com/gammazero/deque v0.1.0/go.mod h1:KQw7vFau1hHuM8xmI9RbgKFbAsQFWmBpqQ2K github.com/gammazero/workerpool v1.1.2 h1:vuioDQbgrz4HoaCi2q1HLlOXdpbap5AET7xu5/qj87g= github.com/gammazero/workerpool v1.1.2/go.mod h1:UelbXcO0zCIGFcufcirHhq2/xtLXJdQ29qZNlXG9OjQ= github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= @@ -185,18 +226,29 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0 h1:QK40JKJyMdUDz+h+xvCsru/bJhvG0UxvePV0ufL/AcE= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM= github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= +github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= +github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= +github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= @@ -211,6 +263,7 @@ github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0= github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= @@ -219,12 +272,17 @@ github.com/goccy/go-yaml v1.9.5/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3K github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.0.0 h1:RAqyYixv1p7uEnocuy8P1nru5wprCh/MH2BIlW5z5/o= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -238,6 +296,7 @@ github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -256,7 +315,9 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= @@ -274,6 +335,7 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -298,25 +360,36 @@ github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLe github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw= github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= @@ -336,6 +409,7 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.4.0 h1:aAQzgqIrRKRa7w75CKpbBxYsmUoPjzVm1W59ca1L0J4= github.com/hashicorp/go-version v1.4.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= @@ -357,6 +431,7 @@ github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpT github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -365,15 +440,24 @@ github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg= github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= @@ -381,11 +465,15 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -394,15 +482,20 @@ github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= @@ -418,12 +511,14 @@ github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= @@ -443,10 +538,12 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mkmik/multierror v0.3.0/go.mod h1:wjBYXRpDhh+8mIp+iLBOq0kZ3Y4ICTncojwvP8LUYLQ= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= @@ -455,49 +552,97 @@ github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/ github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/ohler55/ojg v1.12.12 h1:hepbQFn7GHAecTPmwS3j5dCiOLsOpzPLvhiqnlAVAoE= github.com/ohler55/ojg v1.12.12/go.mod h1:LBbIVRAgoFbYBXQhRhuEpaJIqq+goSO63/FQ+nyJU88= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/r3labs/diff/v2 v2.15.0 h1:3TEoJ6dBqESl1YgL+7curys5PvuEnwrtjkFNskgUvfg= github.com/r3labs/diff/v2 v2.15.0/go.mod h1:I8noH9Fc2fjSaMxqF3G2lhDdC0b+JXCfyx85tWFM9kc= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= @@ -508,6 +653,7 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= github.com/sagikazarmark/crypt v0.4.0/go.mod h1:ALv2SRj7GxYV4HO9elxH9nS6M9gW+xDNxqmyJ6RfDFM= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= @@ -515,11 +661,14 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= @@ -528,12 +677,15 @@ github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/cobra v1.3.0 h1:R7cSvGu+Vv+qX0gW5R/85dx2kmmJT5z5NM8ifdYjdn0= github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v0.0.0-20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= @@ -541,10 +693,14 @@ github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy github.com/spf13/viper v1.10.1 h1:nuJZuYpG7gTj/XqiUwg8bA0cp1+M2mC3J4g5luUYBKk= github.com/spf13/viper v1.10.1/go.mod h1:IGlFPqhNAPKRxohIzWpI5QEy4kuI7tcl5WvR+8qy1rU= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -554,13 +710,18 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/throttled/throttled v2.2.2+incompatible/go.mod h1:0BjlrEGQmvxps+HuXLsyRdqpSRvJpq0PNIsOtqP9Nos= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/whilp/git-urls v1.0.0 h1:95f6UMWN5FKW71ECsXRUd3FVYiXdrE7aX4NZKcPmIjU= github.com/whilp/git-urls v1.0.0/go.mod h1:J16SAmobsqc3Qcy98brfl5f5+e0clUvg1krgwk/qCfE= github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca h1:1CFlNzQhALwjS9mBAUkycX616GzgsuYUOCHA5+HSlXI= github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -568,12 +729,16 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -584,8 +749,15 @@ go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee331t6JAXeK2bcyhLOOc= go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -593,11 +765,14 @@ golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= @@ -605,7 +780,9 @@ golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 h1:/pEO3GD/ABYAjuakUS6xSEmmlyVS4kxBNkeA9tLJiTI= golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= @@ -640,13 +817,16 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -657,8 +837,10 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -685,6 +867,7 @@ golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5o golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -721,12 +904,15 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -735,8 +921,10 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -748,7 +936,9 @@ golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -764,11 +954,14 @@ golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -800,10 +993,13 @@ golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -814,13 +1010,19 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -830,12 +1032,16 @@ golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -844,6 +1050,7 @@ golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -881,6 +1088,10 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -916,6 +1127,7 @@ google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3h google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw= google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= @@ -928,6 +1140,7 @@ google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= @@ -989,10 +1202,15 @@ google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ6 google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368 h1:Et6SkiuvnBn+SgrSYXs/BrUpGB4mbdwt4R3vaPIlicA= google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= @@ -1040,16 +1258,21 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI= gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -1063,6 +1286,7 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1070,24 +1294,41 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/api v0.22.1/go.mod h1:bh13rkTp3F1XEaLGykbyRD2QaTTzPm0e/BMd8ptFONY= k8s.io/api v0.23.3 h1:KNrME8KHGr12Ozjf8ytOewKzZh6hl/hHUZeHddT3a38= k8s.io/api v0.23.3/go.mod h1:w258XdGyvCmnBj/vGzQMj6kzdufJZVUwEM1U2fRJwSQ= +k8s.io/apimachinery v0.22.1/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= k8s.io/apimachinery v0.23.3 h1:7IW6jxNzrXTsP0c8yXz2E5Yx/WTzVPTsHIx/2Vm0cIk= k8s.io/apimachinery v0.23.3/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM= +k8s.io/client-go v0.22.1/go.mod h1:BquC5A4UOo4qVDUtoc04/+Nxp1MeHcVc1HJm1KmG8kk= k8s.io/client-go v0.23.3 h1:23QYUmCQ/W6hW78xIwm3XqZrrKZM+LWDqW2zfo+szJs= k8s.io/client-go v0.23.3/go.mod h1:47oMd+YvAOqZM7pcQ6neJtBiFH7alOyfunYN48VsmwE= +k8s.io/code-generator v0.16.8/go.mod h1:wFdrXdVi/UC+xIfLi+4l9elsTT/uEF61IfcN2wOLULQ= +k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= +k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= k8s.io/klog/v2 v2.30.0 h1:bUO6drIvCIsvZ/XFgfxoGFQU/a4Qkh0iAlvUR7vlHJw= k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 h1:E3J9oCLlaobFUqsjG9DfKbP2BmgwBL2p7pn0A3dG9W4= k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= +k8s.io/utils v0.0.0-20210707171843-4b05e18ac7d9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20211116205334-6203023598ed h1:ck1fRPWPJWsMd8ZRFsWc6mh/zHp5fZ/shhbrgPUxDAE= k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= +modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= +modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= +modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= +modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= @@ -1097,9 +1338,14 @@ sigs.k8s.io/kustomize/api v0.11.1 h1:/Vutu+gAqVo8skw1xCZrsZD39SN4Adg+z7FrSTw9pds sigs.k8s.io/kustomize/api v0.11.1/go.mod h1:GZuhith5YcqxIDe0GnRJNx5xxPTjlwaLTt/e+ChUtJA= sigs.k8s.io/kustomize/kyaml v0.13.3 h1:tNNQIC+8cc+aXFTVg+RtQAOsjwUdYBZRAgYOVI3RBc4= sigs.k8s.io/kustomize/kyaml v0.13.3/go.mod h1:/ya3Gk4diiQzlE4mBh7wykyLRFZNvqlbh+JnwQ9Vhrc= +sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e h1:4Z09Hglb792X0kfOBBJUPFEyvVfQWrYT/l8h5EKA6JQ= +sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y= sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index 345757508..93028d8f1 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -4,13 +4,16 @@ import ( "fmt" "github.com/codablock/kluctl/pkg/diff" "github.com/codablock/kluctl/pkg/k8s" + "github.com/codablock/kluctl/pkg/seal" "github.com/codablock/kluctl/pkg/types" "github.com/codablock/kluctl/pkg/utils" "github.com/codablock/kluctl/pkg/validation" log "github.com/sirupsen/logrus" + "io/fs" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "path" "path/filepath" + "strings" "sync" "time" ) @@ -114,7 +117,7 @@ func (c *DeploymentCollection) collectDeployments(project *DeploymentProject, in return ret, nil } -func (c *DeploymentCollection) renderDeployments(k *k8s.K8sCluster) error { +func (c *DeploymentCollection) RenderDeployments(k *k8s.K8sCluster) error { log.Infof("Rendering templates and Helm charts") wp := utils.NewDebuggerAwareWorkerPool(16) @@ -145,6 +148,32 @@ func (c *DeploymentCollection) renderDeployments(k *k8s.K8sCluster) error { return nil } +func (c *DeploymentCollection) Seal(sealer *seal.Sealer) error { + if c.project.config.SealedSecrets.OutputPattern == nil { + return fmt.Errorf("sealedSecrets.outputPattern is not defined") + } + + err := filepath.WalkDir(c.RenderDir, func(p string, d fs.DirEntry, err error) error { + if !strings.HasSuffix(p, sealmeExt) { + return nil + } + + relPath, err := filepath.Rel(c.RenderDir, p) + if err != nil { + return err + } + targetDir := path.Join(c.project.sealedSecretsDir, path.Dir(relPath)) + targetFile := path.Join(targetDir, *c.project.config.SealedSecrets.OutputPattern, path.Base(p)) + targetFile = targetFile[:len(targetFile)-len(sealmeExt)] + err = sealer.SealFile(p, targetFile) + if err != nil { + return fmt.Errorf("failed sealing %s: %w", path.Base(p), err) + } + return nil + }) + return err +} + func (c *DeploymentCollection) resolveSealedSecrets() error { if c.forSeal { return nil @@ -241,7 +270,7 @@ func (c *DeploymentCollection) localObjectRefs() []types.ObjectRef { } func (c *DeploymentCollection) Prepare(k *k8s.K8sCluster) error { - err := c.renderDeployments(k) + err := c.RenderDeployments(k) if err != nil { return err } diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index e9a89913a..8c4827e03 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -110,8 +110,8 @@ func (di *deploymentItem) render(k *k8s.K8sCluster, wp *utils.WorkerPoolWithErro return err } - varsCtx := di.project.varsCtx.Copy() - err = varsCtx.loadVarsList(k, di.project.getRenderSearchDirs(), di.config.Vars) + varsCtx := di.project.VarsCtx.Copy() + err = varsCtx.LoadVarsList(k, di.project.getRenderSearchDirs(), di.config.Vars) if err != nil { return err } @@ -128,7 +128,7 @@ func (di *deploymentItem) render(k *k8s.K8sCluster, wp *utils.WorkerPoolWithErro } wp.Submit(func() error { - return varsCtx.renderDirectory(rootDir, di.project.getRenderSearchDirs(), di.relProjectDir, excludePatterns, di.relToProjectItemDir, di.renderedDir) + return varsCtx.RenderDirectory(rootDir, di.project.getRenderSearchDirs(), di.relProjectDir, excludePatterns, di.relToProjectItemDir, di.renderedDir) }) return nil diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go index f0702adf3..816e76073 100644 --- a/pkg/deployment/deployment_project.go +++ b/pkg/deployment/deployment_project.go @@ -2,6 +2,7 @@ package deployment import ( "fmt" + "github.com/codablock/kluctl/pkg/jinja2_server" "github.com/codablock/kluctl/pkg/k8s" "github.com/codablock/kluctl/pkg/types" "github.com/codablock/kluctl/pkg/utils" @@ -17,7 +18,7 @@ var kustomizeDirsDeprecatedOnce sync.Once var includesDeprecatedOnce sync.Once type DeploymentProject struct { - varsCtx *VarsCtx + VarsCtx *jinja2_server.VarsCtx dir string sealedSecretsDir string @@ -29,9 +30,9 @@ type DeploymentProject struct { parentProjectInclude *types.DeploymentItemConfig } -func NewDeploymentProject(k *k8s.K8sCluster, varsCtx *VarsCtx, dir string, sealedSecretsDir string, parentProject *DeploymentProject) (*DeploymentProject, error) { +func NewDeploymentProject(k *k8s.K8sCluster, varsCtx *jinja2_server.VarsCtx, dir string, sealedSecretsDir string, parentProject *DeploymentProject) (*DeploymentProject, error) { dp := &DeploymentProject{ - varsCtx: varsCtx.Copy(), + VarsCtx: varsCtx.Copy(), dir: dir, sealedSecretsDir: sealedSecretsDir, parentProject: parentProject, @@ -55,6 +56,12 @@ func NewDeploymentProject(k *k8s.K8sCluster, varsCtx *VarsCtx, dir string, seale return dp, nil } +func (p *DeploymentProject) MergeSecretsIntoAllChildren(vars *uo.UnstructuredObject) { + for _, c := range p.getChildren(true, true) { + c.VarsCtx.UpdateChild("secrets", vars) + } +} + func (p *DeploymentProject) loadConfig(k *k8s.K8sCluster) error { configPath := path.Join(p.dir, "deployment.yml") if !utils.Exists(configPath) { @@ -64,12 +71,12 @@ func (p *DeploymentProject) loadConfig(k *k8s.K8sCluster) error { return fmt.Errorf("%s not found", p.dir) } - err := p.varsCtx.renderYamlFile("deployment.yml", p.getRenderSearchDirs(), &p.config) + err := p.VarsCtx.RenderYamlFile("deployment.yml", p.getRenderSearchDirs(), &p.config) if err != nil { return fmt.Errorf("failed to load deployment.yml: %w", err) } - err = p.varsCtx.loadVarsList(k, p.getRenderSearchDirs(), p.config.Vars) + err = p.VarsCtx.LoadVarsList(k, p.getRenderSearchDirs(), p.config.Vars) if err != nil { return fmt.Errorf("failed to load deployment.yml vars: %w", err) } @@ -171,8 +178,8 @@ func (p *DeploymentProject) loadIncludes(k *k8s.K8sCluster) error { incDir := path.Join(p.dir, *inc.Path) - varsCtx := p.varsCtx.Copy() - err := varsCtx.loadVarsList(k, p.getRenderSearchDirs(), inc.Vars) + varsCtx := p.VarsCtx.Copy() + err := varsCtx.LoadVarsList(k, p.getRenderSearchDirs(), inc.Vars) if err != nil { return err } diff --git a/pkg/deployment/external_args.go b/pkg/deployment/external_args.go index 4048105ba..20c86bbbd 100644 --- a/pkg/deployment/external_args.go +++ b/pkg/deployment/external_args.go @@ -2,6 +2,7 @@ package deployment import ( "fmt" + "github.com/codablock/kluctl/pkg/jinja2_server" "github.com/codablock/kluctl/pkg/types" "github.com/codablock/kluctl/pkg/utils/uo" "github.com/codablock/kluctl/pkg/yaml" @@ -39,7 +40,7 @@ func ConvertArgsToVars(args map[string]string) *uo.UnstructuredObject { return vars } -func CheckRequiredDeployArgs(dir string, varsCtx *VarsCtx, deployArgs *uo.UnstructuredObject) error { +func CheckRequiredDeployArgs(dir string, varsCtx *jinja2_server.VarsCtx, deployArgs *uo.UnstructuredObject) error { // First try to load the config without templating to avoid getting errors while rendering because required // args were not set. Otherwise we won't be able to iterator through the 'args' array in the deployment.yml // when the rendering error is actually args related. @@ -53,7 +54,7 @@ func CheckRequiredDeployArgs(dir string, varsCtx *VarsCtx, deployArgs *uo.Unstru // messages anymore. varsCtx2 := varsCtx.Copy() varsCtx2.UpdateChild("args", deployArgs) - err = varsCtx2.renderYamlFile("deployment.yml", []string{dir}, &conf) + err = varsCtx2.RenderYamlFile("deployment.yml", []string{dir}, &conf) if err != nil { return err } diff --git a/pkg/jinja2_server/server.go b/pkg/jinja2_server/server.go index cd191748c..8bd957394 100644 --- a/pkg/jinja2_server/server.go +++ b/pkg/jinja2_server/server.go @@ -233,6 +233,9 @@ func (js *Jinja2Server) RenderFile(template string, searchDirs []string, vars *u func (js *Jinja2Server) RenderStruct(dst interface{}, src interface{}, vars *uo.UnstructuredObject) error { m, err := uo.FromStruct(src) + if err != nil { + return err + } type pk struct { parent interface{} diff --git a/pkg/deployment/vars.go b/pkg/jinja2_server/vars.go similarity index 89% rename from pkg/deployment/vars.go rename to pkg/jinja2_server/vars.go index 7ae1c05fd..db4adc3f9 100644 --- a/pkg/deployment/vars.go +++ b/pkg/jinja2_server/vars.go @@ -1,8 +1,7 @@ -package deployment +package jinja2_server import ( "fmt" - "github.com/codablock/kluctl/pkg/jinja2_server" "github.com/codablock/kluctl/pkg/k8s" "github.com/codablock/kluctl/pkg/types" "github.com/codablock/kluctl/pkg/utils/uo" @@ -11,11 +10,11 @@ import ( ) type VarsCtx struct { - JS *jinja2_server.Jinja2Server + JS *Jinja2Server Vars *uo.UnstructuredObject } -func NewVarsCtx(js *jinja2_server.Jinja2Server) *VarsCtx { +func NewVarsCtx(js *Jinja2Server) *VarsCtx { vc := &VarsCtx{ JS: js, Vars: uo.New(), @@ -48,7 +47,7 @@ func (vc *VarsCtx) UpdateChildFromStruct(child string, o interface{}) error { return nil } -func (vc *VarsCtx) loadVarsList(k *k8s.K8sCluster, searchDirs []string, varsList []*types.VarsListItem) error { +func (vc *VarsCtx) LoadVarsList(k *k8s.K8sCluster, searchDirs []string, varsList []*types.VarsListItem) error { for _, v := range varsList { if v.Values != nil { vc.Update(v.Values) @@ -79,7 +78,7 @@ func (vc *VarsCtx) loadVarsList(k *k8s.K8sCluster, searchDirs []string, varsList func (vc *VarsCtx) loadVarsFile(p string, searchDirs []string) error { var newVars uo.UnstructuredObject - err := vc.renderYamlFile(p, searchDirs, &newVars) + err := vc.RenderYamlFile(p, searchDirs, &newVars) if err != nil { return fmt.Errorf("failed to load vars from %s: %w", p, err) } @@ -132,7 +131,7 @@ func (vc *VarsCtx) renderYamlString(s string, out interface{}) error { return nil } -func (vc *VarsCtx) renderYamlFile(p string, searchDirs []string, out interface{}) error { +func (vc *VarsCtx) RenderYamlFile(p string, searchDirs []string, out interface{}) error { ret, err := vc.JS.RenderFile(p, searchDirs, vc.Vars) if err != nil { return err @@ -146,6 +145,6 @@ func (vc *VarsCtx) renderYamlFile(p string, searchDirs []string, out interface{} return nil } -func (vc *VarsCtx) renderDirectory(rootDir string, searchDirs []string, relSourceDir string, excludePatterns []string, subdir string, targetDir string) error { +func (vc *VarsCtx) RenderDirectory(rootDir string, searchDirs []string, relSourceDir string, excludePatterns []string, subdir string, targetDir string) error { return vc.JS.RenderDirectory(rootDir, searchDirs, relSourceDir, excludePatterns, subdir, targetDir, vc.Vars) } diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index 4acfacef4..7bcae33cd 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -15,10 +15,13 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/discovery/cached/disk" "k8s.io/client-go/dynamic" + corev1 "k8s.io/client-go/kubernetes/typed/core/v1" "k8s.io/client-go/metadata" _ "k8s.io/client-go/plugin/pkg/client/auth" + "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/util/homedir" + "net/http" "path" "strings" "sync" @@ -46,6 +49,8 @@ type K8sCluster struct { } type parallelClientEntry struct { + http *http.Client + corev1 *corev1.CoreV1Client dynamicClient dynamic.Interface metadataClient metadata.Interface @@ -88,9 +93,7 @@ func NewK8sCluster(context string, dryRun bool) (*K8sCluster, error) { config.QPS = 10 config.Burst = 20 - restConfig := dynamic.ConfigFor(config) - - discovery2, err := disk.NewCachedDiscoveryClientForConfig(restConfig, path.Join(utils.GetTmpBaseDir(), "kube-cache"), path.Join(utils.GetTmpBaseDir(), "kube-http-cache"), time.Hour*24) + discovery2, err := disk.NewCachedDiscoveryClientForConfig(dynamic.ConfigFor(config), path.Join(utils.GetTmpBaseDir(), "kube-cache"), path.Join(utils.GetTmpBaseDir(), "kube-http-cache"), time.Hour*24) if err != nil { return nil, err } @@ -100,15 +103,24 @@ func NewK8sCluster(context string, dryRun bool) (*K8sCluster, error) { k.clientPool = make(chan *parallelClientEntry, parallelClients) for i := 0; i < parallelClients; i++ { p := ¶llelClientEntry{} - restConfig.WarningHandler = p config.WarningHandler = p - p.dynamicClient, err = dynamic.NewForConfig(restConfig) + p.http, err = rest.HTTPClientFor(config) if err != nil { return nil, err } - p.metadataClient, err = metadata.NewForConfig(config) + p.corev1, err = corev1.NewForConfigAndClient(config, p.http) + if err != nil { + return nil, err + } + + p.dynamicClient, err = dynamic.NewForConfigAndClient(config, p.http) + if err != nil { + return nil, err + } + + p.metadataClient, err = metadata.NewForConfigAndClient(config, p.http) if err != nil { return nil, err } @@ -308,6 +320,12 @@ func (k *K8sCluster) getGVRForGVK(gvk schema.GroupVersionKind) (*schema.GroupVer }, ar.Namespaced, nil } +func (k *K8sCluster) WithCoreV1(cb func(client *corev1.CoreV1Client) error) error { + p := <-k.clientPool + defer func() { k.clientPool <- p }() + return cb(p.corev1) +} + func (k *K8sCluster) withDynamicClientForGVK(gvk schema.GroupVersionKind, namespace string, cb func(r dynamic.ResourceInterface) error) ([]ApiWarning, error) { gvr, namespaced, err := k.getGVRForGVK(gvk) if err != nil { diff --git a/pkg/kluctl_project/git.go b/pkg/kluctl_project/git.go index 3016dd5b8..d54c782ac 100644 --- a/pkg/kluctl_project/git.go +++ b/pkg/kluctl_project/git.go @@ -63,17 +63,17 @@ func (c *KluctlProjectContext) updateGitCaches() error { return doUpdateGitProject(p.Project.Url) } - err := doUpdateExternalProject(c.config.Deployment) + err := doUpdateExternalProject(c.Config.Deployment) if err != nil { waitGroup.Wait() return err } - err = doUpdateExternalProject(c.config.SealedSecrets) + err = doUpdateExternalProject(c.Config.SealedSecrets) if err != nil { waitGroup.Wait() return err } - for _, ep := range c.config.Clusters.Projects { + for _, ep := range c.Config.Clusters.Projects { err = doUpdateExternalProject(&ep) if err != nil { waitGroup.Wait() @@ -81,7 +81,7 @@ func (c *KluctlProjectContext) updateGitCaches() error { } } - for _, target := range c.config.Targets { + for _, target := range c.Config.Targets { if target.TargetConfig == nil || target.TargetConfig.Project == nil { continue } diff --git a/pkg/kluctl_project/load.go b/pkg/kluctl_project/load.go index 5ac0909d5..404c5b469 100644 --- a/pkg/kluctl_project/load.go +++ b/pkg/kluctl_project/load.go @@ -54,7 +54,7 @@ func (c *KluctlProjectContext) load(allowGit bool) error { } if configPath != "" { - err = yaml.ReadYamlFile(configPath, &c.config) + err = yaml.ReadYamlFile(configPath, &c.Config) if err != nil { return err } @@ -85,19 +85,19 @@ func (c *KluctlProjectContext) load(allowGit bool) error { return c.cloneGitProject(*ep, defaultGitSubDir, true, true) } - deploymentInfo, err := doClone(c.config.Deployment, "", c.loadArgs.LocalDeployment) + deploymentInfo, err := doClone(c.Config.Deployment, "", c.loadArgs.LocalDeployment) if err != nil { return err } - sealedSecretsInfo, err := doClone(c.config.SealedSecrets, ".sealed-secrets", c.loadArgs.LocalSealedSecrets) + sealedSecretsInfo, err := doClone(c.Config.SealedSecrets, ".sealed-secrets", c.loadArgs.LocalSealedSecrets) if err != nil { return err } var clustersInfos []gitProjectInfo if c.loadArgs.LocalClusters != "" { clustersInfos = append(clustersInfos, c.localProject(c.loadArgs.LocalClusters)) - } else if len(c.config.Clusters.Projects) != 0 { - for _, ep := range c.config.Clusters.Projects { + } else if len(c.Config.Clusters.Projects) != 0 { + for _, ep := range c.Config.Clusters.Projects { info, err := doClone(&ep, "clusters", "") if err != nil { return err @@ -193,6 +193,6 @@ func loadKluctlProjectFromArchive(fromArchive string, fromArchiveMetadata string LocalSealedSecrets: path.Join(dir, "sealed-secrets"), }, dir) p.involvedRepos = metadata.InvolvedRepos - p.targets = metadata.Targets + p.DynamicTargets = metadata.Targets return p, nil } diff --git a/pkg/kluctl_project/load_targets.go b/pkg/kluctl_project/load_targets.go index 603a7d9c3..332796b92 100644 --- a/pkg/kluctl_project/load_targets.go +++ b/pkg/kluctl_project/load_targets.go @@ -2,8 +2,8 @@ package kluctl_project import ( "fmt" - "github.com/codablock/kluctl/pkg/deployment" git_url "github.com/codablock/kluctl/pkg/git/git-url" + "github.com/codablock/kluctl/pkg/jinja2_server" "github.com/codablock/kluctl/pkg/types" "github.com/codablock/kluctl/pkg/utils" "github.com/codablock/kluctl/pkg/utils/uo" @@ -26,10 +26,10 @@ type dynamicTargetInfo struct { func (c *KluctlProjectContext) loadTargets() error { targetNames := make(map[string]bool) - c.targets = nil + c.DynamicTargets = nil var targetInfos []*dynamicTargetInfo - for _, baseTarget := range c.config.Targets { + for _, baseTarget := range c.Config.Targets { l, err := c.prepareDynamicTargets(baseTarget) if err != nil { return err @@ -62,7 +62,10 @@ func (c *KluctlProjectContext) loadTargets() error { log.Warningf("Duplicate target %s", target.Name) } else { targetNames[target.Name] = true - c.targets = append(c.targets, target) + c.DynamicTargets = append(c.DynamicTargets, &types.DynamicTarget{ + Target: target, + BaseTargetName: targetInfo.baseTarget.Name, + }) } } return nil @@ -75,7 +78,7 @@ func (c *KluctlProjectContext) renderTarget(target *types.Target) (*types.Target var errors []error curTarget := target for i := 0; i < 10; i++ { - varsCtx := deployment.NewVarsCtx(c.JS) + varsCtx := jinja2_server.NewVarsCtx(c.JS) err := varsCtx.UpdateChildFromStruct("target", curTarget) if err != nil { return nil, err @@ -102,7 +105,7 @@ func (c *KluctlProjectContext) renderTarget(target *types.Target) (*types.Target return curTarget, nil } -func (c *KluctlProjectContext) prepareDynamicTargets(baseTarget types.Target) ([]*dynamicTargetInfo, error) { +func (c *KluctlProjectContext) prepareDynamicTargets(baseTarget *types.Target) ([]*dynamicTargetInfo, error) { if baseTarget.TargetConfig != nil && baseTarget.TargetConfig.Project != nil { return c.prepareDynamicTargetsExternal(baseTarget) } else { @@ -110,7 +113,7 @@ func (c *KluctlProjectContext) prepareDynamicTargets(baseTarget types.Target) ([ } } -func (c *KluctlProjectContext) prepareDynamicTargetsSimple(baseTarget types.Target) ([]*dynamicTargetInfo, error) { +func (c *KluctlProjectContext) prepareDynamicTargetsSimple(baseTarget *types.Target) ([]*dynamicTargetInfo, error) { if baseTarget.TargetConfig != nil { if baseTarget.TargetConfig.Ref != nil || baseTarget.TargetConfig.RefPattern != nil { return nil, fmt.Errorf("'ref' and/or 'refPattern' are not allowed for non-external dynamic targets") @@ -118,14 +121,14 @@ func (c *KluctlProjectContext) prepareDynamicTargetsSimple(baseTarget types.Targ } dynamicTargets := []*dynamicTargetInfo{ { - baseTarget: &baseTarget, + baseTarget: baseTarget, dir: c.ProjectDir, }, } return dynamicTargets, nil } -func (c *KluctlProjectContext) prepareDynamicTargetsExternal(baseTarget types.Target) ([]*dynamicTargetInfo, error) { +func (c *KluctlProjectContext) prepareDynamicTargetsExternal(baseTarget *types.Target) ([]*dynamicTargetInfo, error) { mr, ok := c.mirroredRepos[baseTarget.TargetConfig.Project.Url.NormalizedRepoKey()] if !ok { return nil, fmt.Errorf("repo not found in mirroredRepos, this is unexpected and probably a bug") @@ -176,7 +179,7 @@ func (c *KluctlProjectContext) prepareDynamicTargetsExternal(baseTarget types.Ta } dynamicTargets = append(dynamicTargets, &dynamicTargetInfo{ - baseTarget: &baseTarget, + baseTarget: baseTarget, dir: cloneDir, gitProject: baseTarget.TargetConfig.Project, ref: &ref, @@ -297,19 +300,20 @@ func (c *KluctlProjectContext) buildDynamicTarget(targetInfo *dynamicTargetInfo) } // check and merge args - err = targetConfig.Args.NewIterator().IterateLeafs(func(it *uo.ObjectIterator) error { - strValue := fmt.Sprintf("%v", it.Value()) - err := c.CheckDynamicArg(&target, it.JsonPath(), strValue) + if targetConfig.Args != nil { + err = targetConfig.Args.NewIterator().IterateLeafs(func(it *uo.ObjectIterator) error { + strValue := fmt.Sprintf("%v", it.Value()) + err := c.CheckDynamicArg(&target, it.JsonPath(), strValue) + if err != nil { + return err + } + return nil + }) if err != nil { - return err + return nil, err } - return nil - }) - if err != nil { - return nil, err + target.Args.Merge(targetConfig.Args) } - target.Args.Merge(targetConfig.Args) - // We prepend the dynamic images to ensure they get higher priority later target.Images = append(targetConfig.Images, target.Images...) diff --git a/pkg/kluctl_project/project.go b/pkg/kluctl_project/project.go index 56e4df9dd..7ac498104 100644 --- a/pkg/kluctl_project/project.go +++ b/pkg/kluctl_project/project.go @@ -26,15 +26,15 @@ type KluctlProjectContext struct { loadArgs LoadKluctlProjectArgs TmpDir string - config types.KluctlProject + Config types.KluctlProject ProjectDir string DeploymentDir string ClustersDir string SealedSecretsDir string - involvedRepos map[string][]types.InvolvedRepo - targets []*types.Target + involvedRepos map[string][]types.InvolvedRepo + DynamicTargets []*types.DynamicTarget mirroredRepos map[string]*git.MirroredGitRepo @@ -52,8 +52,8 @@ func NewKluctlProjectContext(loadArgs LoadKluctlProjectArgs, tmpDir string) *Klu return o } -func (c *KluctlProjectContext) FindTarget(name string) (*types.Target, error) { - for _, target := range c.targets { +func (c *KluctlProjectContext) FindBaseTarget(name string) (*types.Target, error) { + for _, target := range c.Config.Targets { if target.Name == name { return target, nil } @@ -61,6 +61,15 @@ func (c *KluctlProjectContext) FindTarget(name string) (*types.Target, error) { return nil, fmt.Errorf("target %s not existent in kluctl project config", name) } +func (c *KluctlProjectContext) FindDynamicTarget(name string) (*types.DynamicTarget, error) { + for _, target := range c.DynamicTargets { + if target.Target.Name == name { + return target, nil + } + } + return nil, fmt.Errorf("target %s not existent in kluctl project config", name) +} + func (c *KluctlProjectContext) LoadClusterConfig(clusterName string) (*types.ClusterConfig, error) { return types.LoadClusterConfig(c.ClustersDir, clusterName) } diff --git a/pkg/seal/bootstrap.go b/pkg/seal/bootstrap.go new file mode 100644 index 000000000..69d707630 --- /dev/null +++ b/pkg/seal/bootstrap.go @@ -0,0 +1,95 @@ +package seal + +import ( + "crypto/rsa" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "github.com/bitnami-labs/sealed-secrets/pkg/crypto" + "github.com/codablock/kluctl/pkg/k8s" + "github.com/codablock/kluctl/pkg/types" + "github.com/codablock/kluctl/pkg/utils/uo" + log "github.com/sirupsen/logrus" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + certUtil "k8s.io/client-go/util/cert" + "k8s.io/client-go/util/keyutil" + "time" +) + +const sealedSecretsKeyLabel = "sealedsecrets.bitnami.com/sealed-secrets-key" +const secretName = "sealed-secrets-key-kluctl-bootstrap" +const configMapName = "sealed-secrets-key-kluctl-bootstrap" + +func BootstrapSealedSecrets(k *k8s.K8sCluster, namespace string) error { + existing, _, err := k.GetSingleObject(types.ObjectRef{ + GVK: schema.GroupVersionKind{Group: "apiextensions.k8s.io", Version: "v1", Kind: "CustomResourceDefinition"}, + Name: "sealedsecrets.bitnami.com", + }) + if existing != nil { + // no bootstrap needed as the sealed-secrets operator seams to be installed already + return nil + } + + existing, _, err = k.GetSingleObject(types.ObjectRef{ + GVK: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "ConfigMap"}, + Name: configMapName, + Namespace: namespace, + }) + if existing != nil { + // bootstrap has already been done + return nil + } + + log.Infof("Bootstrapping sealed-secrets with a self-generated key") + + key, cert, err := crypto.GeneratePrivateKeyAndCert(2048, 10*365*24*time.Hour, "bootstrap.kluctl.io") + if err != nil { + return err + } + + certs := []*x509.Certificate{cert} + err = writeKey(k, key, certs, namespace) + if err != nil { + return err + } + return nil +} + +func writeKey(k *k8s.K8sCluster, key *rsa.PrivateKey, certs []*x509.Certificate, namespace string) error { + certbytes := []byte{} + for _, cert := range certs { + certbytes = append(certbytes, pem.EncodeToMemory(&pem.Block{Type: certUtil.CertificateBlockType, Bytes: cert.Raw})...) + } + keybytes := pem.EncodeToMemory(&pem.Block{Type: keyutil.RSAPrivateKeyBlockType, Bytes: x509.MarshalPKCS1PrivateKey(key)}) + + secret := uo.New() + secret.SetK8sGVK(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Secret"}) + secret.SetK8sName(secretName) + secret.SetK8sNamespace(namespace) + secret.SetK8sLabel(sealedSecretsKeyLabel, "active") + secret.Object["data"] = map[string]string{ + v1.TLSPrivateKeyKey: base64.StdEncoding.EncodeToString(keybytes), + v1.TLSCertKey: base64.StdEncoding.EncodeToString(certbytes), + } + secret.Object["type"] = v1.SecretTypeTLS + + configMap := uo.New() + configMap.SetK8sGVK(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "ConfigMap"}) + configMap.SetK8sName(configMapName) + configMap.SetK8sNamespace(namespace) + configMap.Object["data"] = map[string]string{ + v1.TLSCertKey: string(certbytes), + } + + _, _, err := k.PatchObject(secret.ToUnstructured(), k8s.PatchOptions{}) + if err != nil { + return err + } + _, _, err = k.PatchObject(configMap.ToUnstructured(), k8s.PatchOptions{}) + if err != nil { + return err + } + + return nil +} diff --git a/pkg/seal/fetch_cert.go b/pkg/seal/fetch_cert.go new file mode 100644 index 000000000..9cae7926a --- /dev/null +++ b/pkg/seal/fetch_cert.go @@ -0,0 +1,109 @@ +package seal + +import ( + "context" + "crypto/rsa" + "errors" + "fmt" + "github.com/codablock/kluctl/pkg/k8s" + log "github.com/sirupsen/logrus" + "io/ioutil" + v12 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "k8s.io/client-go/kubernetes/typed/core/v1" + "k8s.io/client-go/util/cert" +) + +func fetchCert(k *k8s.K8sCluster, namespace string, controllerName string) (*rsa.PublicKey, error) { + var certData []byte + + err := k.WithCoreV1(func(client *v1.CoreV1Client) error { + s, err := openCertFromController(client, namespace, controllerName) + if err != nil { + if controllerName == "sealed-secrets-controller" { + s2, err2 := openCertFromController(client, namespace, "sealed-secrets") + if err2 == nil { + log.Warningf("Looks like you have sealed-secrets controller installed with name 'sealed-secrets', which comes from a legacy kluctl version that deployed it with a non-default name. Please consider re-deploying sealed-secrets operator manually.") + err = nil + s = s2 + } + } + + if err != nil { + log.Warningf("Failed to retrieve public certificate from sealed-secrets-controller, re-trying with bootstrap secret") + s, err = openCertFromBootstrap(client, namespace) + if err != nil { + return fmt.Errorf("failed to retrieve selaed secrets public key: %w", err) + } + } + } + certData = s + return nil + }) + if err != nil { + return nil, err + } + + cert, err := parseKey(certData) + return cert, err +} + +func openCertFromBootstrap(c *v1.CoreV1Client, namespace string) ([]byte, error) { + cm, err := c.ConfigMaps(namespace).Get(context.Background(), configMapName, metav1.GetOptions{}) + if err != nil { + return nil, err + } + + v, ok := cm.Data[v12.TLSCertKey] + if !ok { + return nil, fmt.Errorf("%s key not found in ConfigMap %s", v12.TLSCertKey, configMapName) + } + + return []byte(v), nil +} + +func openCertFromController(c v1.CoreV1Interface, namespace, name string) ([]byte, error) { + portName, err := getServicePortName(c, namespace, name) + if err != nil { + return nil, err + } + r, err := c.Services(namespace).ProxyGet("http", name, portName, "/v1/cert.pem", nil).Stream(context.Background()) + if err != nil { + return nil, fmt.Errorf("cannot fetch certificate: %v", err) + } + defer r.Close() + + cert, err := ioutil.ReadAll(r) + if err != nil { + return nil, err + } + + return cert, nil +} + +func getServicePortName(client v1.CoreV1Interface, namespace, serviceName string) (string, error) { + service, err := client.Services(namespace).Get(context.Background(), serviceName, metav1.GetOptions{}) + if err != nil { + return "", fmt.Errorf("cannot get sealed secret service: %v", err) + } + return service.Spec.Ports[0].Name, nil +} + +func parseKey(data []byte) (*rsa.PublicKey, error) { + certs, err := cert.ParseCertsPEM(data) + if err != nil { + return nil, err + } + + // ParseCertsPem returns error if len(certs) == 0, but best to be sure... + if len(certs) == 0 { + return nil, errors.New("failed to read any certificates") + } + + cert, ok := certs[0].PublicKey.(*rsa.PublicKey) + if !ok { + return nil, fmt.Errorf("fxpected RSA public key but found %v", certs[0].PublicKey) + } + + return cert, nil +} diff --git a/pkg/seal/sealer.go b/pkg/seal/sealer.go new file mode 100644 index 000000000..1df83f831 --- /dev/null +++ b/pkg/seal/sealer.go @@ -0,0 +1,244 @@ +package seal + +import ( + "crypto/rand" + "crypto/rsa" + "encoding/base64" + "encoding/hex" + "fmt" + "github.com/bitnami-labs/sealed-secrets/pkg/crypto" + "github.com/codablock/kluctl/pkg/k8s" + "github.com/codablock/kluctl/pkg/types" + "github.com/codablock/kluctl/pkg/utils" + "github.com/codablock/kluctl/pkg/utils/uo" + "github.com/codablock/kluctl/pkg/yaml" + log "github.com/sirupsen/logrus" + "golang.org/x/crypto/scrypt" + "k8s.io/apimachinery/pkg/runtime/schema" + "os" + "path" + "reflect" + "strconv" + "strings" +) + +const hashAnnotation = "kluctl.io/sealedsecret-hashes" + +type Sealer struct { + clusterConfig *types.ClusterConfig2 + forceReseal bool + cert *rsa.PublicKey +} + +func NewSealer(k *k8s.K8sCluster, sealedSecretsNamespace string, sealedSecretsControllerName string, clusterConfig *types.ClusterConfig2, forceReseal bool) (*Sealer, error) { + s := &Sealer{ + clusterConfig: clusterConfig, + forceReseal: forceReseal, + } + cert, err := fetchCert(k, sealedSecretsNamespace, sealedSecretsControllerName) + if err != nil { + return nil, err + } + s.cert = cert + return s, nil +} + +func (s *Sealer) doHash(key string, secret []byte, secretName string, secretNamespace string, scope string) string { + if secretNamespace == "" { + secretNamespace = "*" + } + salt := fmt.Sprintf("%s-%s-%s-%s", s.clusterConfig.Name, secretName, secretNamespace, key) + if scope != "strict" { + salt += "-" + scope + } + + h, err := scrypt.Key(secret, []byte(salt), 1<<14, 8, 1, 64) + if err != nil { + log.Fatal(err) + } + return hex.EncodeToString(h) +} + +func encryptionLabel(namespace string, name string, scope string) []byte { + var l string + switch scope { + case "cluster-wide": + l = "" + case "namespace-wide": + l = namespace + case "strict": + fallthrough + default: + l = fmt.Sprintf("%s/%s", namespace, name) + } + return []byte(l) +} + +func (s *Sealer) encryptSecret(secret []byte, secretName string, secretNamespace string, scope string) (string, error) { + b, err := crypto.HybridEncrypt(rand.Reader, s.cert, secret, encryptionLabel(secretNamespace, secretName, scope)) + if err != nil { + return "", err + } + return base64.StdEncoding.EncodeToString(b), nil +} + +func (s *Sealer) SealFile(p string, targetFile string) error { + baseName := path.Base(targetFile) + err := os.MkdirAll(path.Dir(targetFile), 0o777) + if err != nil { + return err + } + + o, err := uo.FromFile(p) + if err != nil { + return err + } + + secretName := o.ToUnstructured().GetName() + secretNamespace := o.ToUnstructured().GetNamespace() + if secretNamespace == "" { + secretNamespace = "default" + } + secretType, ok, err := o.GetNestedString("type") + if err != nil { + return err + } + if !ok { + secretType = "Opaque" + } + + var scope *string + x, _, _ := o.GetNestedString("metadata", "annotations", "sealedsecrets.bitnami.com/namespace-wide") + if b, _ := strconv.ParseBool(x); b { + tmp := "namespace-wide" + scope = &tmp + } + x, _, _ = o.GetNestedString("metadata", "annotations", "sealedsecrets.bitnami.com/cluster-wide") + if b, _ := strconv.ParseBool(x); b { + tmp := "cluster-wide" + scope = &tmp + } + if scope == nil { + x, _, _ = o.GetNestedString("metadata", "annotations", "sealedsecrets.bitnami.com/scope") + if x == "" { + x = "strict" + } + scope = &x + } + + var existingContent *uo.UnstructuredObject + var existingHashes *uo.UnstructuredObject + + if utils.Exists(targetFile) { + existingContent, err = uo.FromFile(targetFile) + if err != nil { + return err + } + if x, ok := existingContent.ToUnstructured().GetAnnotations()[hashAnnotation]; ok { + existingHashes, _ = uo.FromString(x) + } + } + if existingHashes == nil { + existingHashes = uo.New() + } + + secrets := make(map[string][]byte) + + data, ok, err := o.GetNestedObject("data") + if err != nil { + return err + } + if ok { + for k, v := range data.Object { + s, ok := v.(string) + if !ok { + return fmt.Errorf("%s is not a string", k) + } + secrets[k], err = base64.StdEncoding.DecodeString(s) + if err != nil { + return fmt.Errorf("failed to decode base64 string for secret %s and key %s", secretName, k) + } + } + } + + stringData, ok, err := o.GetNestedObject("stringData") + if err != nil { + return err + } + for k, v := range stringData.Object { + s, ok := v.(string) + if !ok { + return fmt.Errorf("%s is not a string", k) + } + secrets[k] = []byte(s) + } + + resultSecretHashes := make(map[string]string) + + result := uo.New() + result.SetK8sGVK(schema.GroupVersionKind{Group: "bitnami.com", Version: "v1alpha1", Kind: "SealedSecret"}) + result.SetK8sName(secretName) + result.SetK8sNamespace(secretNamespace) + result.SetK8sAnnotation("sealedsecrets.bitnami.com/scope", *scope) + if *scope == "namespace-wide" { + result.SetK8sAnnotation("sealedsecrets.bitnami.com/namespace-wide", "true") + } + if *scope == "cluster-wide" { + result.SetK8sAnnotation("sealedsecrets.bitnami.com/cluster-wide", "true") + } + _ = result.SetNestedField(secretType, "spec", "template", "type") + metadata, ok, _ := o.GetNestedObject("metadata") + if ok { + result.SetNestedField(metadata.Object, "spec", "template", "metadata") + } + + var changedKeys []string + for k, v := range secrets { + hash := s.doHash(k, v, secretName, secretNamespace, *scope) + existingHash, _, _ := existingHashes.GetNestedString(k) + if hash == existingHash && !s.forceReseal { + e, ok, _ := existingContent.GetNestedString("spec", "encryptedData", k) + if ok { + log.Debugf("Secret %s and key %s is unchanged, skipping encryption", secretName, k) + result.SetNestedField(e, "spec", "encryptedData", k) + resultSecretHashes[k] = hash + continue + } + } + log.Debugf("Secret %s and key %s has changed, encrypting it", secretName, k) + e, err := s.encryptSecret(v, secretName, secretNamespace, *scope) + if err != nil { + return fmt.Errorf("failed to encrypt secret %s with key %s", secretName, k) + } + result.SetNestedField(e, "spec", "encryptedData", k) + resultSecretHashes[k] = hash + changedKeys = append(changedKeys, k) + } + + for k := range existingHashes.Object { + _, ok, _ := result.GetNestedString("spec", "encryptedData", k) + if !ok { + log.Debugf("Secret %s and key %s has been deleted", secretName, k) + changedKeys = append(changedKeys, k) + } + } + + resultSecretHashesStr, err := yaml.WriteYamlString(resultSecretHashes) + if err != nil { + return err + } + result.SetNestedField(resultSecretHashesStr, "metadata", "annotations", hashAnnotation) + + if reflect.DeepEqual(existingContent, result) { + log.Infof("Skipped %s as it did not change", baseName) + return nil + } + + log.Infof("Sealed %s. New/changed/deleted keys: %s", baseName, strings.Join(changedKeys, ", ")) + + err = yaml.WriteYamlFile(targetFile, result) + if err != nil { + return err + } + return nil +} diff --git a/pkg/seal/secrets_loader.go b/pkg/seal/secrets_loader.go new file mode 100644 index 000000000..9d31fffeb --- /dev/null +++ b/pkg/seal/secrets_loader.go @@ -0,0 +1,114 @@ +package seal + +import ( + "fmt" + "github.com/codablock/kluctl/pkg/kluctl_project" + "github.com/codablock/kluctl/pkg/types" + "github.com/codablock/kluctl/pkg/utils" + "github.com/codablock/kluctl/pkg/utils/aws" + "github.com/codablock/kluctl/pkg/utils/uo" + "os" + "path" + "path/filepath" + "strings" +) + +type SecretsLoader struct { + project *kluctl_project.KluctlProjectContext + secretsDir string +} + +func NewSecretsLoader(p *kluctl_project.KluctlProjectContext, secretsDir string) *SecretsLoader { + return &SecretsLoader{ + project: p, + secretsDir: secretsDir, + } +} + +func (s *SecretsLoader) LoadSecrets(source *types.SecretSource) (*uo.UnstructuredObject, error) { + if source.Path != nil { + return s.loadSecretsFile(source) + } else if source.SystemEnvVars != nil { + return s.loadSecretsSystemEnvs(source) + } else if source.AwsSecretsManager != nil { + return s.loadSecretsAwsSecertsManager(source) + } else { + return nil, fmt.Errorf("invalid secrets entry") + } +} + +func (s *SecretsLoader) loadSecretsFile(source *types.SecretSource) (*uo.UnstructuredObject, error) { + var p string + if utils.Exists(path.Join(s.project.DeploymentDir, *source.Path)) { + p = path.Join(s.project.DeploymentDir, *source.Path) + } else if utils.Exists(path.Join(s.secretsDir, *source.Path)) { + p = path.Join(s.secretsDir, *source.Path) + } + if p == "" || !utils.Exists(p) { + return nil, fmt.Errorf("secrets file %s does not exist", *source.Path) + } + + abs, err := filepath.Abs(p) + if err != nil { + return nil, err + } + if !strings.HasPrefix(abs, s.project.DeploymentDir) { + return nil, fmt.Errorf("secrets file %s is not part of the deployment project", *source.Path) + } + + secrets, err := uo.FromFile(p) + if err != nil { + return nil, err + } + secrets, ok, err := secrets.GetNestedObject("secrets") + if err != nil { + return nil, err + } + if !ok { + return uo.New(), nil + } + return secrets, nil +} + +func (s *SecretsLoader) loadSecretsSystemEnvs(source *types.SecretSource) (*uo.UnstructuredObject, error) { + secrets := uo.New() + err := source.SystemEnvVars.NewIterator().IterateLeafs(func(it *uo.ObjectIterator) error { + envName, ok := it.Value().(string) + if !ok { + return fmt.Errorf("value at %s is not a string", it.JsonPath()) + } + envValue, ok := os.LookupEnv(envName) + if !ok { + return fmt.Errorf("environment variable %s not found for secret %s", envName, it.JsonPath()) + } + err := secrets.SetNestedField(envValue, it.KeyPath()...) + if err != nil { + return fmt.Errorf("failed to set secret %s: %w", it.JsonPath(), err) + } + return nil + }) + if err != nil { + return nil, err + } + return secrets, nil +} + +func (s *SecretsLoader) loadSecretsAwsSecertsManager(source *types.SecretSource) (*uo.UnstructuredObject, error) { + secret, err := aws.GetAwsSecretsManagerSecret(source.AwsSecretsManager.Profile, source.AwsSecretsManager.Region, source.AwsSecretsManager.SecretName) + if err != nil { + return nil, err + } + + secrets, err := uo.FromString(secret) + if err != nil { + return nil, fmt.Errorf("failed to parse yaml from AWS Secrets Manager (secretName=%s): %w", source.AwsSecretsManager.SecretName, err) + } + secrets, ok, err := secrets.GetNestedObject("secrets") + if err != nil { + return nil, err + } + if !ok { + return uo.New(), nil + } + return secrets, nil +} diff --git a/pkg/types/deployment.go b/pkg/types/deployment.go index fdc576508..13969ac78 100644 --- a/pkg/types/deployment.go +++ b/pkg/types/deployment.go @@ -103,7 +103,7 @@ type DeploymentProjectConfig struct { Tags []string `yaml:"tags,omitempty"` IgnoreForDiff []*IgnoreForDiffItemConfig `yaml:"ignoreForDiff,omitempty"` - TemplateExcludes []string `yaml:"TemplateExcludes,omitempty"` + TemplateExcludes []string `yaml:"templateExcludes,omitempty"` } func init() { diff --git a/pkg/types/kluctl_project.go b/pkg/types/kluctl_project.go index 31438c954..32d6fb43d 100644 --- a/pkg/types/kluctl_project.go +++ b/pkg/types/kluctl_project.go @@ -22,7 +22,7 @@ type ExternalTargetConfig struct { type SealingConfig struct { // DynamicSealing Set this to false if you want to disable sealing for every dynamic target - DynamicSealing bool `yaml:"dynamicSealing,omitempty"` + DynamicSealing *bool `yaml:"dynamicSealing,omitempty"` Args *uo.UnstructuredObject `yaml:"args,omitempty"` SecretSets []string `yaml:"secretSets,omitempty"` } @@ -37,20 +37,32 @@ type Target struct { Images []FixedImage `yaml:"images,omitempty"` } +type DynamicTarget struct { + Target *Target `yaml:"target" validate:"required"` + BaseTargetName string `yaml:"baseTargetName"` +} + type SecretSet struct { - Name string `yaml:"name" validate:"required"` - Sources []interface{} `yaml:"sources" validate:"required,gt=0,dive,required"` + Name string `yaml:"name" validate:"required"` + Sources []SecretSource `yaml:"sources" validate:"required,gt=0"` +} + +type GlobalSealedSecretsConfig struct { + Bootstrap *bool `yaml:"bootstrap,omitempty"` + Namespace *string `yaml:"namespace,omitempty"` + ControllerName *string `yaml:"controllerName,omitempty"` } type SecretsConfig struct { - SecretSets []SecretSet `yaml:"secretSets,omitempty"` + SealedSecrets *GlobalSealedSecretsConfig `yaml:"sealedSecrets,omitempty"` + SecretSets []SecretSet `yaml:"secretSets,omitempty"` } type KluctlProject struct { Deployment *ExternalProject `yaml:"deployment,omitempty"` SealedSecrets *ExternalProject `yaml:"sealedSecrets,omitempty"` Clusters ExternalProjects `yaml:"clusters,omitempty"` - Targets []Target `yaml:"targets,omitempty"` + Targets []*Target `yaml:"targets,omitempty"` SecretsConfig *SecretsConfig `yaml:"secretsConfig,omitempty"` } diff --git a/pkg/types/metadata.go b/pkg/types/metadata.go index 6bd5b181f..7e588ddaf 100644 --- a/pkg/types/metadata.go +++ b/pkg/types/metadata.go @@ -1,9 +1,5 @@ package types -import ( - "github.com/codablock/kluctl/pkg/yaml" -) - type InvolvedRepo struct { RefPattern string `yaml:"refPattern"` Refs map[string]string `yaml:"refs"` @@ -11,10 +7,5 @@ type InvolvedRepo struct { type ArchiveMetadata struct { InvolvedRepos map[string][]InvolvedRepo `yaml:"involvedRepo"` - Targets []*Target `yaml:"targets"` -} - -func LoadArchiveMetadata(p string) (*ArchiveMetadata, error) { - o := &ArchiveMetadata{} - return o, yaml.ReadYamlFile(p, o) + Targets []*DynamicTarget `yaml:"targets"` } diff --git a/pkg/types/secrets_source.go b/pkg/types/secrets_source.go index 70ea5ad00..63ed5e584 100644 --- a/pkg/types/secrets_source.go +++ b/pkg/types/secrets_source.go @@ -1,18 +1,22 @@ package types -import "github.com/go-playground/validator/v10" +import ( + "github.com/codablock/kluctl/pkg/utils/uo" + "github.com/go-playground/validator/v10" +) type SecretSourceAwsSecretsManager struct { // Name or ARN of the secret. In case a name is given, the region must be specified as well - SecretName string `yaml:"path" validate:"required"` + SecretName string `yaml:"secretName" validate:"required"` // The aws region - Region string `yaml:"path,omitempty"` - // AWS credentials profile to use. The AWS_PROFILE environemnt variables will take predence in case it is also set - Profile string `yaml:"path,omitempty"` + Region *string `yaml:"region,omitempty"` + // AWS credentials profile to use. The AWS_PROFILE environemnt variables will take precedence in case it is also set + Profile *string `yaml:"profile,omitempty"` } type SecretSource struct { Path *string `yaml:"path,omitempty"` + SystemEnvVars *uo.UnstructuredObject `yaml:"systemEnvVars,omitempty"` AwsSecretsManager *SecretSourceAwsSecretsManager `yaml:"awsSecretsManager,omitempty"` } @@ -22,6 +26,9 @@ func ValidateSecretSource(sl validator.StructLevel) { if s.Path != nil { count += 1 } + if s.SystemEnvVars != nil { + count += 1 + } if s.AwsSecretsManager != nil { count += 1 } diff --git a/pkg/utils/aws/arn.go b/pkg/utils/aws/arn.go new file mode 100644 index 000000000..16b79bdf2 --- /dev/null +++ b/pkg/utils/aws/arn.go @@ -0,0 +1,42 @@ +package aws + +import ( + "fmt" + "strings" +) + +type Arn struct { + Arn string + Partition string + Service string + Region string + Account string + Resource string + ResourceType string +} + +func ParseArn(arn string) (Arn, error) { + // http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html + elements := strings.SplitN(arn, ":", 5) + if len(elements) < 6 { + return Arn{}, fmt.Errorf("%s is not a valid arn", arn) + } + var result Arn + result.Arn = elements[0] + result.Partition = elements[1] + result.Service = elements[2] + result.Region = elements[3] + result.Account = elements[4] + result.Resource = elements[5] + + if strings.Index(result.Resource, "/") != 0 { + s := strings.SplitN(result.Resource, "/", 1) + result.ResourceType = s[0] + result.Resource = s[1] + } else if strings.Index(result.Resource, ":") != 0 { + s := strings.SplitN(result.Resource, ":", 1) + result.ResourceType = s[0] + result.Resource = s[1] + } + return result, nil +} diff --git a/pkg/utils/aws/secrets_manager.go b/pkg/utils/aws/secrets_manager.go new file mode 100644 index 000000000..8e0f0358e --- /dev/null +++ b/pkg/utils/aws/secrets_manager.go @@ -0,0 +1,47 @@ +package aws + +import ( + "fmt" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/secretsmanager" + + "os" +) + +func GetAwsSecretsManagerSecret(profile *string, region *string, secretName string) (string, error) { + var opts session.Options + // Environment variable always takes precedence + if _, ok := os.LookupEnv("AWS_PROFILE"); !ok && region != nil { + opts.Profile = *profile + } + s, err := session.NewSessionWithOptions(opts) + if err != nil { + return "", err + } + + if region == nil { + arn, err := ParseArn(secretName) + if err != nil { + return "", fmt.Errorf("when omitting the AWS region, the secret name must be a valid ARN") + } + region = &arn.Region + } + + smClient := secretsmanager.New(s, &aws.Config{Region: region}) + r, err := smClient.GetSecretValue(&secretsmanager.GetSecretValueInput{ + SecretId: &secretName, + }) + if err != nil { + return "", fmt.Errorf("getting secret %s from AWS secrets manager failed: %w", secretName, err) + } + + var secret string + if r.SecretString != nil { + secret = *r.SecretString + } else { + secret = string(r.SecretBinary) + } + + return secret, nil +} diff --git a/pkg/utils/uo/k8s_fields.go b/pkg/utils/uo/k8s_fields.go new file mode 100644 index 000000000..5f2498e76 --- /dev/null +++ b/pkg/utils/uo/k8s_fields.go @@ -0,0 +1,125 @@ +package uo + +import ( + log "github.com/sirupsen/logrus" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +func (uo *UnstructuredObject) GetK8sGVK() schema.GroupVersionKind { + kind, _, err := uo.GetNestedString("kind") + if err != nil { + log.Fatal(err) + } + apiVersion, _, err := uo.GetNestedString("apiVersion") + if err != nil { + log.Fatal(err) + } + gv, err := schema.ParseGroupVersion(apiVersion) + if err != nil { + log.Fatal(err) + } + return schema.GroupVersionKind{ + Group: gv.Group, + Version: gv.Version, + Kind: kind, + } +} + +func (uo *UnstructuredObject) SetK8sGVK(gvk schema.GroupVersionKind) { + err := uo.SetNestedField(gvk.GroupVersion().String(), "apiVersion") + if err != nil { + log.Fatal(err) + } + err = uo.SetNestedField(gvk.Kind, "kind") + if err != nil { + log.Fatal(err) + } +} + +func (uo *UnstructuredObject) GetK8sName() string { + s, _, err := uo.GetNestedString("metadata", "name") + if err != nil { + log.Fatal(err) + } + return s +} + +func (uo *UnstructuredObject) SetK8sName(name string) { + err := uo.SetNestedField(name, "metadata", "name") + if err != nil { + log.Fatal(err) + } +} + +func (uo *UnstructuredObject) GetK8sNamespace() string { + s, _, err := uo.GetNestedString("metadata", "namespace") + if err != nil { + log.Fatal(err) + } + return s +} + +func (uo *UnstructuredObject) SetK8sNamespace(name string) { + err := uo.SetNestedField(name, "metadata", "namespace") + if err != nil { + log.Fatal(err) + } +} + +func (uo *UnstructuredObject) GetK8sLabels() map[string]string { + ret, ok, err := uo.GetNestedStringMapCopy("metadata", "labels") + if err != nil { + log.Fatal(err) + } + if !ok { + return map[string]string{} + } + return ret +} + +func (uo *UnstructuredObject) GetK8sLabel(name string) *string { + ret, ok, err := uo.GetNestedString("metadata", "labels", name) + if err != nil { + log.Fatal(err) + } + if !ok { + return nil + } + return &ret +} + +func (uo *UnstructuredObject) SetK8sLabel(name string, value string) { + err := uo.SetNestedField(value, "metadata", "labels", name) + if err != nil { + log.Fatal(err) + } +} + +func (uo *UnstructuredObject) GetK8sAnnotations() map[string]string { + ret, ok, err := uo.GetNestedStringMapCopy("metadata", "annotations") + if err != nil { + log.Fatal(err) + } + if !ok { + return map[string]string{} + } + return ret +} + +func (uo *UnstructuredObject) GetK8sAnnotation(name string) *string { + ret, ok, err := uo.GetNestedString("metadata", "annotations", name) + if err != nil { + log.Fatal(err) + } + if !ok { + return nil + } + return &ret +} + +func (uo *UnstructuredObject) SetK8sAnnotation(name string, value string) { + err := uo.SetNestedField(value, "metadata", "annotations", name) + if err != nil { + log.Fatal(err) + } +} diff --git a/pkg/utils/uo/nested_fields.go b/pkg/utils/uo/nested_fields.go index 130df89aa..9238355fa 100644 --- a/pkg/utils/uo/nested_fields.go +++ b/pkg/utils/uo/nested_fields.go @@ -153,6 +153,30 @@ func (uo *UnstructuredObject) GetNestedObjectListNoErr(keys ...interface{}) []*U return l } +func (uo *UnstructuredObject) GetNestedStringMapCopy(keys ...interface{}) (map[string]string, bool, error) { + v, found, err := uo.GetNestedField(keys...) + if err != nil { + return nil, false, err + } + if !found { + return nil, false, nil + } + m, ok := v.(map[string]interface{}) + if !ok { + return nil, false, fmt.Errorf("value at %s is not a map", KeyListToJsonPath(keys)) + } + ret := make(map[string]string) + for k, v := range m { + s, ok := v.(string) + if !ok { + return nil, false, fmt.Errorf("value at %s.%s is not a string", KeyListToJsonPath(keys), k) + } + ret[k] = s + } + + return ret, true, nil +} + func (uo *UnstructuredObject) SetNestedFieldDefault(defaultValue interface{}, keys ...interface{}) error { v, found, err := uo.GetNestedField(keys...) if err != nil { diff --git a/pkg/utils/uo/uo.go b/pkg/utils/uo/uo.go index 8b1bed350..c5beaceeb 100644 --- a/pkg/utils/uo/uo.go +++ b/pkg/utils/uo/uo.go @@ -62,6 +62,24 @@ func (uo *UnstructuredObject) ToStruct(out interface{}) error { return yaml.ReadYamlBytes(b, out) } +func FromString(s string) (*UnstructuredObject, error) { + o := New() + err := yaml.ReadYamlString(s, &o.Object) + if err != nil { + return nil, err + } + return o, nil +} + +func FromFile(p string) (*UnstructuredObject, error) { + o := New() + err := yaml.ReadYamlFile(p, &o.Object) + if err != nil { + return nil, err + } + return o, nil +} + func (uo *UnstructuredObject) Clone() *UnstructuredObject { var c map[string]interface{} err := copier.CopyWithOption(&c, &uo.Object, copier.Option{ diff --git a/pkg/yaml/yaml.go b/pkg/yaml/yaml.go index 6e0d70733..20ed4aac6 100644 --- a/pkg/yaml/yaml.go +++ b/pkg/yaml/yaml.go @@ -42,15 +42,19 @@ func ReadYamlBytes(b []byte, o interface{}) error { } func ReadYamlStream(r io.Reader, o interface{}) error { + var err error if _, ok := o.(*map[string]interface{}); ok { // much faster d := yaml3.NewDecoder(r) - return d.Decode(o) + err = d.Decode(o) + } else { + // we need proper working strict mode + d := newYamlDecoder(r) + err = d.Decode(o) + } + if err != nil && errors.Is(err, io.EOF) { + return nil } - - // we need proper working strict mode - d := newYamlDecoder(r) - err := d.Decode(o) return err } @@ -138,6 +142,8 @@ func WriteYamlAllStream(w io.Writer, l []interface{}) error { enc := yaml3.NewEncoder(w) defer enc.Close() + enc.SetIndent(2) + for _, o := range l { err := enc.Encode(o) if err != nil { From 2f25e517af35492e0da949dc08dee96b379facba Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 14 Feb 2022 16:37:18 +0100 Subject: [PATCH 0345/2916] Fix KeyListToJsonPath to return simpler pathes --- pkg/utils/uo/jsonpath.go | 5 ++++- pkg/utils/uo/object_iterator.go | 3 --- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/utils/uo/jsonpath.go b/pkg/utils/uo/jsonpath.go index d96e61afc..267283e23 100644 --- a/pkg/utils/uo/jsonpath.go +++ b/pkg/utils/uo/jsonpath.go @@ -4,16 +4,19 @@ import ( "fmt" "github.com/ohler55/ojg/jp" log "github.com/sirupsen/logrus" + "regexp" "strings" ) +var isSimpleIdentifier = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]+$`) + func KeyListToJsonPath(keys []interface{}) string { p := "" for _, k := range keys { if i, ok := k.(int); ok { p = fmt.Sprintf("%s[%d]", p, i) } else if s, ok := k.(string); ok { - if isAlpha.MatchString(s) { + if isSimpleIdentifier.MatchString(s) { if p != "" { p += "." } diff --git a/pkg/utils/uo/object_iterator.go b/pkg/utils/uo/object_iterator.go index d39ce6152..7c127af98 100644 --- a/pkg/utils/uo/object_iterator.go +++ b/pkg/utils/uo/object_iterator.go @@ -2,7 +2,6 @@ package uo import ( log "github.com/sirupsen/logrus" - "regexp" ) type ObjectIteratorFunc func(it *ObjectIterator) error @@ -44,8 +43,6 @@ func (it *ObjectIterator) SetValue(v interface{}) error { return SetChild(it.Parent(), it.Key(), v) } -var isAlpha = regexp.MustCompile(`^[A-Za-z]+$`) - func (it *ObjectIterator) JsonPath() string { return KeyListToJsonPath(it.keys) } From 82a173f8db8bd40cf16b61ec28ca07762aa22a50 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 14 Feb 2022 17:16:34 +0100 Subject: [PATCH 0346/2916] Implement clusterId as part of sealed secrets --- pkg/seal/sealer.go | 84 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 62 insertions(+), 22 deletions(-) diff --git a/pkg/seal/sealer.go b/pkg/seal/sealer.go index 1df83f831..7eb137d86 100644 --- a/pkg/seal/sealer.go +++ b/pkg/seal/sealer.go @@ -19,15 +19,16 @@ import ( "path" "reflect" "strconv" - "strings" ) const hashAnnotation = "kluctl.io/sealedsecret-hashes" +const clusterIdAnnotation = "kluctl.io/sealedsecret-cluster-id" type Sealer struct { clusterConfig *types.ClusterConfig2 forceReseal bool cert *rsa.PublicKey + clusterId string } func NewSealer(k *k8s.K8sCluster, sealedSecretsNamespace string, sealedSecretsControllerName string, clusterConfig *types.ClusterConfig2, forceReseal bool) (*Sealer, error) { @@ -40,9 +41,38 @@ func NewSealer(k *k8s.K8sCluster, sealedSecretsNamespace string, sealedSecretsCo return nil, err } s.cert = cert + + clusterId, err := getClusterId(k) + if err != nil { + return nil, err + } + + s.clusterId = clusterId + return s, nil } +// We treat the hashed kube-root-ca.crt as cluster id for now. We also accept that it might change when keys +// get rotated. +func getClusterId(k *k8s.K8sCluster) (string, error) { + o, _, err := k.GetSingleObject(types.ObjectRef{ + GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, + Name: "kube-root-ca.crt", + Namespace: "kube-system", + }) + if err != nil { + return "", fmt.Errorf("failed to retrieve kube-root-ca.crt: %w", err) + } + kubeRootCA, ok, err := uo.FromUnstructured(o).GetNestedString("data", "ca.crt") + if err != nil { + return "", fmt.Errorf("failed to retrieve kube-root-ca.crt: %w", err) + } + if !ok { + return "", fmt.Errorf("failed to retrieve kube-root-ca.crt: ca.crt key is missing") + } + return utils.Sha256String(kubeRootCA), nil +} + func (s *Sealer) doHash(key string, secret []byte, secretName string, secretNamespace string, scope string) string { if secretNamespace == "" { secretNamespace = "*" @@ -51,7 +81,6 @@ func (s *Sealer) doHash(key string, secret []byte, secretName string, secretName if scope != "strict" { salt += "-" + scope } - h, err := scrypt.Key(secret, []byte(salt), 1<<14, 8, 1, 64) if err != nil { log.Fatal(err) @@ -128,14 +157,17 @@ func (s *Sealer) SealFile(p string, targetFile string) error { var existingContent *uo.UnstructuredObject var existingHashes *uo.UnstructuredObject + var existingClusterId string if utils.Exists(targetFile) { existingContent, err = uo.FromFile(targetFile) - if err != nil { - return err + a := existingContent.GetK8sAnnotation(hashAnnotation) + if a != nil { + existingHashes, _ = uo.FromString(*a) } - if x, ok := existingContent.ToUnstructured().GetAnnotations()[hashAnnotation]; ok { - existingHashes, _ = uo.FromString(x) + a = existingContent.GetK8sAnnotation(clusterIdAnnotation) + if a != nil { + existingClusterId = *a } } if existingHashes == nil { @@ -192,50 +224,58 @@ func (s *Sealer) SealFile(p string, targetFile string) error { result.SetNestedField(metadata.Object, "spec", "template", "metadata") } - var changedKeys []string + resealAll := true + if s.forceReseal { + resealAll = true + log.Infof("Forcing reseal of secrets in %s", secretName) + } else if existingClusterId != s.clusterId { + resealAll = true + log.Infof("Target cluster for secret %s has changed, forcing reseal", secretName) + } + for k, v := range secrets { hash := s.doHash(k, v, secretName, secretNamespace, *scope) existingHash, _, _ := existingHashes.GetNestedString(k) - if hash == existingHash && !s.forceReseal { + + doEncrypt := resealAll + if !doEncrypt && hash != existingHash { + log.Infof("Secret %s and key %s has changed, resealing", secretName, k) + doEncrypt = true + } + + if !doEncrypt { e, ok, _ := existingContent.GetNestedString("spec", "encryptedData", k) if ok { - log.Debugf("Secret %s and key %s is unchanged, skipping encryption", secretName, k) + log.Debugf("Secret %s and key %s is unchanged", secretName, k) result.SetNestedField(e, "spec", "encryptedData", k) resultSecretHashes[k] = hash continue + } else { + log.Infof("Old encrypted secret %s and key %s not found", secretName, k) + doEncrypt = true } } - log.Debugf("Secret %s and key %s has changed, encrypting it", secretName, k) + e, err := s.encryptSecret(v, secretName, secretNamespace, *scope) if err != nil { return fmt.Errorf("failed to encrypt secret %s with key %s", secretName, k) } result.SetNestedField(e, "spec", "encryptedData", k) resultSecretHashes[k] = hash - changedKeys = append(changedKeys, k) - } - - for k := range existingHashes.Object { - _, ok, _ := result.GetNestedString("spec", "encryptedData", k) - if !ok { - log.Debugf("Secret %s and key %s has been deleted", secretName, k) - changedKeys = append(changedKeys, k) - } } resultSecretHashesStr, err := yaml.WriteYamlString(resultSecretHashes) if err != nil { return err } - result.SetNestedField(resultSecretHashesStr, "metadata", "annotations", hashAnnotation) + result.SetK8sAnnotation(hashAnnotation, resultSecretHashesStr) + result.SetK8sAnnotation(clusterIdAnnotation, s.clusterId) if reflect.DeepEqual(existingContent, result) { log.Infof("Skipped %s as it did not change", baseName) return nil } - log.Infof("Sealed %s. New/changed/deleted keys: %s", baseName, strings.Join(changedKeys, ", ")) - err = yaml.WriteYamlFile(targetFile, result) if err != nil { return err From 00c094c70f3e6216a9edb3f91fedd9a12a7019fb Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 14 Feb 2022 17:19:02 +0100 Subject: [PATCH 0347/2916] Remove bootstrap command --- cmd/kluctl/bootstrap/deployment.yml | 10 - .../charts/sealed-secrets/.helmignore | 21 -- .../charts/sealed-secrets/Chart.yaml | 12 - .../charts/sealed-secrets/README.md | 103 ------ .../charts/sealed-secrets/ci/ci-values.yaml | 4 - .../sealed-secrets/crds/sealedsecret-crd.yaml | 27 -- .../dashboards/sealed-secrets-controller.json | 302 ------------------ .../charts/sealed-secrets/templates/NOTES.txt | 47 --- .../sealed-secrets/templates/_helpers.tpl | 49 --- .../templates/cluster-role-binding.yaml | 24 -- .../templates/cluster-role.yaml | 46 --- .../templates/configmap-dashboards.yaml | 25 -- .../sealed-secrets/templates/deployment.yaml | 95 ------ .../sealed-secrets/templates/ingress.yaml | 44 --- .../templates/networkpolicy.yaml | 20 -- .../templates/psp-clusterrole.yaml | 18 -- .../templates/psp-clusterrolebinding.yaml | 20 -- .../charts/sealed-secrets/templates/psp.yaml | 34 -- .../templates/role-binding.yaml | 48 --- .../charts/sealed-secrets/templates/role.yaml | 58 ---- .../templates/service-account.yaml | 16 - .../sealed-secrets/templates/service.yaml | 23 -- .../templates/servicemonitor.yaml | 38 --- .../charts/sealed-secrets/values.yaml | 95 ------ .../bootstrap/sealed-secrets/helm-chart.yml | 7 - .../bootstrap/sealed-secrets/helm-values.yml | 3 - .../sealed-secrets/kustomization.yml | 5 - cmd/kluctl/cmd_bootstrap.go | 91 ------ 28 files changed, 1285 deletions(-) delete mode 100644 cmd/kluctl/bootstrap/deployment.yml delete mode 100644 cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/.helmignore delete mode 100644 cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/Chart.yaml delete mode 100644 cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/README.md delete mode 100644 cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/ci/ci-values.yaml delete mode 100644 cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/crds/sealedsecret-crd.yaml delete mode 100644 cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/dashboards/sealed-secrets-controller.json delete mode 100644 cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/NOTES.txt delete mode 100644 cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/_helpers.tpl delete mode 100644 cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/cluster-role-binding.yaml delete mode 100644 cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/cluster-role.yaml delete mode 100644 cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/configmap-dashboards.yaml delete mode 100644 cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/deployment.yaml delete mode 100644 cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/ingress.yaml delete mode 100644 cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/networkpolicy.yaml delete mode 100644 cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/psp-clusterrole.yaml delete mode 100644 cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/psp-clusterrolebinding.yaml delete mode 100644 cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/psp.yaml delete mode 100644 cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/role-binding.yaml delete mode 100644 cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/role.yaml delete mode 100644 cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/service-account.yaml delete mode 100644 cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/service.yaml delete mode 100644 cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/servicemonitor.yaml delete mode 100644 cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/values.yaml delete mode 100644 cmd/kluctl/bootstrap/sealed-secrets/helm-chart.yml delete mode 100644 cmd/kluctl/bootstrap/sealed-secrets/helm-values.yml delete mode 100644 cmd/kluctl/bootstrap/sealed-secrets/kustomization.yml delete mode 100644 cmd/kluctl/cmd_bootstrap.go diff --git a/cmd/kluctl/bootstrap/deployment.yml b/cmd/kluctl/bootstrap/deployment.yml deleted file mode 100644 index 24e1b5ed8..000000000 --- a/cmd/kluctl/bootstrap/deployment.yml +++ /dev/null @@ -1,10 +0,0 @@ - -kustomizeDirs: -- path: sealed-secrets - -commonLabels: - kluctl.io/component: bootstrap - kluctl.io/cluster-name: {{ cluster.name }} - -deleteByLabels: - kluctl.io/component: bootstrap diff --git a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/.helmignore b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/.helmignore deleted file mode 100644 index f0c131944..000000000 --- a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/.helmignore +++ /dev/null @@ -1,21 +0,0 @@ -# Patterns to ignore when building packages. -# This supports shell glob matching, relative path matching, and -# negation (prefixed with !). Only one pattern per line. -.DS_Store -# Common VCS dirs -.git/ -.gitignore -.bzr/ -.bzrignore -.hg/ -.hgignore -.svn/ -# Common backup files -*.swp -*.bak -*.tmp -*~ -# Various IDEs -.project -.idea/ -*.tmproj diff --git a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/Chart.yaml b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/Chart.yaml deleted file mode 100644 index d03e0f430..000000000 --- a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/Chart.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: v2 -appVersion: v0.16.0 -description: Helm chart for the sealed-secrets controller. -home: https://github.com/bitnami-labs/sealed-secrets -icon: https://avatars0.githubusercontent.com/u/34656521?s=200&v=4 -kubeVersion: '>=1.16.0-0' -maintainers: -- email: mmikulicic@gmail.com - name: mkmik -name: sealed-secrets -type: application -version: 1.16.1 diff --git a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/README.md b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/README.md deleted file mode 100644 index f70f10012..000000000 --- a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/README.md +++ /dev/null @@ -1,103 +0,0 @@ -# Sealed Secrets - -This chart contains the resources to use [sealed-secrets](https://github.com/bitnami-labs/sealed-secrets). - -## Prerequisites - -* Kubernetes >= 1.16 - -## Installing the Chart -To install the chart with the release name `my-release`: - -### Helm 3 - -```bash -$ helm3 install --namespace kube-system my-release sealed-secrets/sealed-secrets -``` - -### Helm 2 - -```bash -$ helm2 install --namespace kube-system --name my-release sealed-secrets/sealed-secrets -``` - -The command deploys a controller and [CRD](https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/) for sealed secrets on the Kubernetes cluster in the default configuration. The [configuration](#configuration) section lists the parameters that can be configured during installation. - -## Uninstalling the Chart - -To uninstall/delete all of the resources associated with the chart `my-release` - -### Helm 2 - -```bash -$ helm2 delete [--purge] my-release -``` - -### Helm 3 - -```bash -$ helm3 uninstall my-release -n kube-system -``` - -## Using kubeseal - -Install the kubeseal CLI by downloading the binary from [sealed-secrets/releases](https://github.com/bitnami-labs/sealed-secrets/releases). - -Fetch the public key by passing the release name and namespace: - -```bash -kubeseal --fetch-cert \ ---controller-name=my-release \ ---controller-namespace=my-release-namespace \ -> pub-cert.pem -``` - -Read about kubeseal usage on [sealed-secrets docs](https://github.com/bitnami-labs/sealed-secrets#usage). - -## Configuration - -| Parameter | Description | Default | -|---------------------------------:|:---------------------------------------------------------------------------|:--------------------------------------------| -| **controller.create** | `true` if Sealed Secrets controller resources should be created | `true` | -| **namespace** | The name of the Namespace to deploy the controller | `.Release.namespace` | -| **rbac.create** | `true` if rbac resources should be created | `true` | -| **rbac.pspEnabled** | `true` if psp resources should be created | `false` | -| **serviceAccount.create** | Whether to create a service account or not | `true` | -| **serviceAccount.name** | The name of the service account to create or use | `"sealed-secrets-controller"` | -| **secretName** | The name of the TLS secret containing the key used to encrypt secrets | `"sealed-secrets-key"` | -| **image.tag** | The `Sealed Secrets` image tag | `v0.13.1` | -| **image.pullPolicy** | The image pull policy for the deployment | `IfNotPresent` | -| **image.repository** | The repository to get the controller image from | `quay.io/bitnami/sealed-secrets-controller` | -| **resources** | CPU/Memory resource requests/limits | `{}` | -| **crd.create** | `true` if crd resources should be created | `true` | -| **crd.keep** | `true` if the sealed secret CRD should be kept when the chart is deleted | `true` | -| **networkPolicy** | Whether to create a network policy that allows access to the service | `false` | -| **securityContext.runAsUser** | Defines under which user the operator Pod and its containers/processes run | `1001` | -| **securityContext.fsGroup** | Defines fsGroup for the operator Pod and its containers/processes run | `65534` | -| **commandArgs** | Set optional command line arguments passed to the controller process | `[]` | -| **ingress.enabled** | Enables Ingress | `false` | -| **ingress.annotations** | Ingress annotations | `{}` | -| **ingress.path** | Ingress path | `/v1/cert.pem` | -| **ingress.hosts** | Ingress accepted hostnames | `["chart-example.local"]` | -| **ingress.tls** | Ingress TLS configuration | `[]` | -| **podAnnotations** | Annotations to annotate pods with. | `{}` | -| **podLabels** | Labels to be added to pods | `{}` | -| **priorityClassName** | Optional class to specify priority for pods | `""` | -| **serviceMonitor.create** | Create servicemonitor from prometheus operator | `false` | -| **serviceMonitor.interval** | How frequently Prometheus should scrape | `""` | -| **serviceMonitor.labels** | Labels for the servicemonitor passed to Prometheus Operator | `{}` | -| **serviceMonitor.namespace** | Namespace this servicemonitor is installed in | `""` | -| **serviceMonitor.scrapeTimeout** | Timeout after which the scrape is ended | `""` | -| **dashboards.create** | Create Grafana dashboard config map | `false` | -| **dashboards.labels** | Extra labels to apply to the dashboard configmaps | `{}` | -| **dashboards.namespace** | Namespace this dashboards are installed in | `""` | - -- In the case that **serviceAccount.create** is `false` and **rbac.create** is `true` it is expected for a service account with the name **serviceAccount.name** to exist _in the same namespace as this chart_ before installation. -- If **serviceAccount.create** is `true` there cannot be an existing service account with the name **serviceAccount.name**. -- If a secret with name **secretName** does not exist _in the same namespace as this chart_, then on install one will be created. If a secret already exists with this name the keys inside will be used. -- OpenShift: unset the runAsUser and fsGroup like this: -``` - securityContext: - runAsUser: - fsGroup: -``` diff --git a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/ci/ci-values.yaml b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/ci/ci-values.yaml deleted file mode 100644 index f9e5b0bc8..000000000 --- a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/ci/ci-values.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# CI is running on GKE, it requires the chart to clean up after itself so we cannot keep the CRD - -crd: - keep: false diff --git a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/crds/sealedsecret-crd.yaml b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/crds/sealedsecret-crd.yaml deleted file mode 100644 index 062729e59..000000000 --- a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/crds/sealedsecret-crd.yaml +++ /dev/null @@ -1,27 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: sealedsecrets.bitnami.com -spec: - group: bitnami.com - names: - kind: SealedSecret - listKind: SealedSecretList - plural: sealedsecrets - singular: sealedsecret - scope: Namespaced - versions: - - name: v1alpha1 - served: true - storage: true - subresources: - status: {} - schema: - openAPIV3Schema: - type: object - properties: - spec: - type: object - x-kubernetes-preserve-unknown-fields: true - status: - x-kubernetes-preserve-unknown-fields: true diff --git a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/dashboards/sealed-secrets-controller.json b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/dashboards/sealed-secrets-controller.json deleted file mode 100644 index a01a7e175..000000000 --- a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/dashboards/sealed-secrets-controller.json +++ /dev/null @@ -1,302 +0,0 @@ -{ - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": "-- Grafana --", - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "type": "dashboard" - } - ] - }, - "description": "Sealed Secrets Controller", - "editable": true, - "gnetId": null, - "graphTooltip": 0, - "id": 3, - "iteration": 1585599163503, - "links": [ - { - "icon": "external link", - "tags": [], - "title": "GitHub", - "tooltip": "View Project on GitHub", - "type": "link", - "url": "https://github.com/bitnami-labs/sealed-secrets" - } - ], - "panels": [ - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "description": "Rate of requests to unseal a SealedSecret.\n\nThis can include non-obvious operations such as deleting a SealedSecret.", - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 9, - "w": 12, - "x": 0, - "y": 0 - }, - "hiddenSeries": false, - "id": 2, - "legend": { - "avg": true, - "current": false, - "max": true, - "min": true, - "show": true, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "options": { - "dataLinks": [] - }, - "percentage": false, - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum(rate(sealed_secrets_controller_unseal_requests_total{}[1m]))", - "format": "time_series", - "instant": false, - "intervalFactor": 1, - "legendFormat": "rps", - "refId": "A" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Unseal Request Rate/s", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "description": "Rate of errors when unsealing a SealedSecret. \n\nReason for error included as label value, eg:\n- unseal = cryptography issue (key/namespace) or RBAC\n- unmanaged = destination Secret wasn't created by SealedSecrets\n- update = potentially RBAC\n- status = potentially RBAC\n- fetch = potentially RBAC\n", - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 9, - "w": 12, - "x": 12, - "y": 0 - }, - "hiddenSeries": false, - "id": 3, - "legend": { - "avg": false, - "current": false, - "hideEmpty": false, - "hideZero": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null as zero", - "options": { - "dataLinks": [] - }, - "percentage": false, - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum(rate(sealed_secrets_controller_unseal_errors_total{pod=~\"$pod\"}[1m])) by (reason)", - "format": "time_series", - "intervalFactor": 1, - "legendFormat": "{{ reason }}", - "refId": "A" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Unseal Error Rate/s", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - } - ], - "refresh": false, - "schemaVersion": 22, - "style": "dark", - "tags": [], - "templating": { - "list": [ - { - "current": { - "text": "prometheus", - "value": "prometheus" - }, - "hide": 0, - "includeAll": false, - "label": null, - "multi": false, - "name": "datasource", - "options": [], - "query": "prometheus", - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "type": "datasource" - }, - { - "allValue": null, - "current": { - "selected": false, - "text": "All", - "value": "$__all" - }, - "datasource": "$datasource", - "definition": "label_values(kube_pod_info, pod)", - "hide": 0, - "includeAll": true, - "label": null, - "multi": false, - "name": "pod", - "options": [], - "query": "label_values(kube_pod_info, pod)", - "refresh": 1, - "regex": "/^sealed-secrets-controller.*$/", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tags": [], - "tagsQuery": "", - "type": "query", - "useTags": false - } - ] - }, - "time": { - "from": "now-1h", - "to": "now" - }, - "timepicker": { - "refresh_intervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "time_options": [ - "5m", - "15m", - "1h", - "6h", - "12h", - "24h", - "2d", - "7d", - "30d" - ] - }, - "timezone": "", - "title": "Sealed Secrets Controller", - "uid": "UuEtZCVWz", - "version": 2 -} diff --git a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/NOTES.txt b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/NOTES.txt deleted file mode 100644 index beca3a496..000000000 --- a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/NOTES.txt +++ /dev/null @@ -1,47 +0,0 @@ -{{ if .Values.controller.create -}} -You should now be able to create sealed secrets. - -1. Install client-side tool into /usr/local/bin/ - -GOOS=$(go env GOOS) -GOARCH=$(go env GOARCH) -wget https://github.com/bitnami-labs/sealed-secrets/releases/download/{{ .Values.image.tag }}/kubeseal-$GOOS-$GOARCH -sudo install -m 755 kubeseal-$GOOS-$GOARCH /usr/local/bin/kubeseal - -2. Create a sealed secret file - -# note the use of `--dry-run` - this does not create a secret in your cluster -kubectl create secret generic secret-name --dry-run --from-literal=foo=bar -o [json|yaml] | \ - kubeseal \ - --controller-name={{ template "sealed-secrets.fullname" . }} \ - --controller-namespace={{ .Release.Namespace }} \ - --format [json|yaml] > mysealedsecret.[json|yaml] - -The file mysealedsecret.[json|yaml] is a commitable file. - -If you would rather not need access to the cluster to generate the sealed secret you can run - -kubeseal \ - --controller-name={{ template "sealed-secrets.fullname" . }} \ - --controller-namespace={{ .Release.Namespace }} \ - --fetch-cert > mycert.pem - -to retrieve the public cert used for encryption and store it locally. You can then run 'kubeseal --cert mycert.pem' instead to use the local cert e.g. - -kubectl create secret generic secret-name --dry-run --from-literal=foo=bar -o [json|yaml] | \ -kubeseal \ - --controller-name={{ template "sealed-secrets.fullname" . }} \ - --controller-namespace={{ .Release.Namespace }} \ - --format [json|yaml] --cert mycert.pem > mysealedsecret.[json|yaml] - -3. Apply the sealed secret - -kubectl create -f mysealedsecret.[json|yaml] - -Running 'kubectl get secret secret-name -o [json|yaml]' will show the decrypted secret that was generated from the sealed secret. - -Both the SealedSecret and generated Secret must have the same name and namespace. -{{- else }} -Sealed Secrets controller not installed, You need to install controller before -sealed secrets can be created. -{{- end }} diff --git a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/_helpers.tpl b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/_helpers.tpl deleted file mode 100644 index 78bbc748b..000000000 --- a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/_helpers.tpl +++ /dev/null @@ -1,49 +0,0 @@ -{{/* -Expand to the namespace sealed-secrets installs into. -*/}} -{{- define "sealed-secrets.namespace" -}} -{{- default .Release.Namespace .Values.namespace -}} -{{- end -}} - -{{/* -Create chart name and version as used by the chart label. -*/}} -{{- define "sealed-secrets.chart" -}} -{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Expand the name of the chart. -*/}} -{{- define "sealed-secrets.name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -If release name contains chart name it will be used as a full name. -*/}} -{{- define "sealed-secrets.fullname" -}} -{{- if .Values.fullnameOverride -}} -{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} -{{- else -}} -{{- $name := default .Chart.Name .Values.nameOverride -}} -{{- if contains $name .Release.Name -}} -{{- .Release.Name | trunc 63 | trimSuffix "-" -}} -{{- else -}} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} -{{- end -}} -{{- end -}} -{{- end -}} - -{{/* -Create the name of the service account to use -*/}} -{{- define "sealed-secrets.serviceAccountName" -}} -{{- if .Values.serviceAccount.create -}} - {{ default (include "sealed-secrets.fullname" .) .Values.serviceAccount.name }} -{{- else -}} - {{ default "default" .Values.serviceAccount.name }} -{{- end -}} -{{- end -}} diff --git a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/cluster-role-binding.yaml b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/cluster-role-binding.yaml deleted file mode 100644 index 869ae0bc2..000000000 --- a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/cluster-role-binding.yaml +++ /dev/null @@ -1,24 +0,0 @@ -{{ if .Values.rbac.create }} -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: {{ template "sealed-secrets.fullname" . }} - labels: - app.kubernetes.io/name: {{ template "sealed-secrets.name" . }} - helm.sh/chart: {{ template "sealed-secrets.chart" . }} - app.kubernetes.io/managed-by: {{ .Release.Service }} - app.kubernetes.io/instance: {{ .Release.Name }} - app.kubernetes.io/version: {{ .Chart.AppVersion }} - {{- if .Values.rbac.labels }} -{{ toYaml .Values.rbac.labels | indent 4 }} - {{- end }} -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: secrets-unsealer -subjects: - - apiGroup: "" - kind: ServiceAccount - name: {{ template "sealed-secrets.serviceAccountName" . }} - namespace: {{ template "sealed-secrets.namespace" . }} -{{ end }} diff --git a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/cluster-role.yaml b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/cluster-role.yaml deleted file mode 100644 index 09ecd1e4e..000000000 --- a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/cluster-role.yaml +++ /dev/null @@ -1,46 +0,0 @@ -{{ if .Values.rbac.create }} -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: secrets-unsealer - labels: - app.kubernetes.io/name: {{ template "sealed-secrets.name" . }} - helm.sh/chart: {{ template "sealed-secrets.chart" . }} - app.kubernetes.io/managed-by: {{ .Release.Service }} - app.kubernetes.io/instance: {{ .Release.Name }} - app.kubernetes.io/version: {{ .Chart.AppVersion }} - {{- if .Values.rbac.labels }} -{{ toYaml .Values.rbac.labels | indent 4 }} - {{- end }} -rules: - - apiGroups: - - bitnami.com - resources: - - sealedsecrets - verbs: - - get - - list - - watch - - apiGroups: - - bitnami.com - resources: - - sealedsecrets/status - verbs: - - update - - apiGroups: - - "" - resources: - - secrets - verbs: - - get - - create - - update - - delete - - apiGroups: - - "" - resources: - - events - verbs: - - create - - patch -{{ end }} diff --git a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/configmap-dashboards.yaml b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/configmap-dashboards.yaml deleted file mode 100644 index 31a0b875b..000000000 --- a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/configmap-dashboards.yaml +++ /dev/null @@ -1,25 +0,0 @@ -{{- if .Values.dashboards.create }} -{{- $namespace := .Values.dashboards.namespace | default $.Release.Namespace }} -{{- range $path, $_ := .Files.Glob "dashboards/*.json" }} -{{- $filename := trimSuffix (ext $path) (base $path) }} -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ template "sealed-secrets.fullname" $ }}-{{ $filename }} - namespace: {{ $namespace }} - labels: - grafana_dashboard: "1" - app.kubernetes.io/name: {{ template "sealed-secrets.name" $ }} - helm.sh/chart: {{ template "sealed-secrets.chart" $ }} - app.kubernetes.io/managed-by: {{ $.Release.Service }} - app.kubernetes.io/instance: {{ $.Release.Name }} - app.kubernetes.io/version: {{ $.Chart.AppVersion }} - {{- if $.Values.dashboards.labels }} - {{- toYaml $.Values.dashboards.labels | nindent 4 }} - {{- end }} -data: - {{ base $path }}: |- -{{ $.Files.Get $path | indent 4 }} ---- -{{- end }} -{{- end }} diff --git a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/deployment.yaml b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/deployment.yaml deleted file mode 100644 index 1e9fceea1..000000000 --- a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/deployment.yaml +++ /dev/null @@ -1,95 +0,0 @@ -{{- if .Values.controller.create -}} -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ template "sealed-secrets.fullname" . }} - namespace: {{ template "sealed-secrets.namespace" . }} - labels: - app.kubernetes.io/name: {{ template "sealed-secrets.name" . }} - helm.sh/chart: {{ template "sealed-secrets.chart" . }} - app.kubernetes.io/managed-by: {{ .Release.Service }} - app.kubernetes.io/instance: {{ .Release.Name }} - app.kubernetes.io/version: {{ .Chart.AppVersion }} - {{- if .Values.controller.labels }} -{{ toYaml .Values.controller.labels | indent 4 }} - {{- end }} -spec: - selector: - matchLabels: - app.kubernetes.io/name: {{ template "sealed-secrets.name" . }} - app.kubernetes.io/instance: {{ .Release.Name }} - template: - metadata: - annotations: - {{- with .Values.podAnnotations }} - {{- toYaml . | nindent 8 }} - {{- end }} - labels: - app.kubernetes.io/name: {{ template "sealed-secrets.name" . }} - app.kubernetes.io/instance: {{ .Release.Name }} - {{- if .Values.podLabels }} -{{ toYaml .Values.podLabels | indent 8 }} - {{- end }} - spec: - {{- if .Values.image.pullSecret }} - imagePullSecrets: - - name: {{ .Values.image.pullSecret }} - {{- end }} - serviceAccountName: {{ template "sealed-secrets.serviceAccountName" . }} - {{- if .Values.priorityClassName }} - priorityClassName: "{{ .Values.priorityClassName }}" - {{- end }} - containers: - - name: {{ template "sealed-secrets.fullname" . }} - command: - - controller - args: - - "--key-prefix" - - "{{ .Values.secretName }}" - {{- range $value := .Values.commandArgs }} - - {{ $value | quote }} - {{- end }} - image: {{ .Values.image.repository }}:{{ .Values.image.tag }} - imagePullPolicy: {{ .Values.image.pullPolicy }} - ports: - - containerPort: 8080 - name: http - volumeMounts: - - mountPath: /tmp - name: tmp - livenessProbe: - httpGet: - path: /healthz - port: 8080 - readinessProbe: - httpGet: - path: /healthz - port: 8080 - securityContext: - readOnlyRootFilesystem: true - {{- if .Values.securityContext.runAsUser }} - runAsNonRoot: true - runAsUser: {{ .Values.securityContext.runAsUser }} - {{- end }} - resources: -{{ toYaml .Values.resources | indent 12 }} - {{- if .Values.securityContext.fsGroup }} - securityContext: - fsGroup: {{ .Values.securityContext.fsGroup }} - {{- end }} - volumes: - - name: tmp - emptyDir: {} - {{- with .Values.nodeSelector }} - nodeSelector: -{{ toYaml . | indent 8 }} - {{- end }} - {{- with .Values.affinity }} - affinity: -{{ toYaml . | indent 8 }} - {{- end }} - {{- with .Values.tolerations }} - tolerations: -{{ toYaml . | indent 8 }} - {{- end }} -{{- end }} diff --git a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/ingress.yaml b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/ingress.yaml deleted file mode 100644 index 6a4dedd84..000000000 --- a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/ingress.yaml +++ /dev/null @@ -1,44 +0,0 @@ -{{- if .Values.ingress.enabled -}} -{{- $fullName := include "sealed-secrets.fullname" . -}} -{{- $ingressPath := .Values.ingress.path -}} -{{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} -apiVersion: networking.k8s.io/v1beta1 -{{- else -}} -apiVersion: extensions/v1beta1 -{{- end }} -kind: Ingress -metadata: - name: {{ $fullName }} - namespace: {{ template "sealed-secrets.namespace" . }} - labels: - app.kubernetes.io/name: {{ template "sealed-secrets.name" . }} - helm.sh/chart: {{ template "sealed-secrets.chart" . }} - app.kubernetes.io/managed-by: {{ .Release.Service }} - app.kubernetes.io/instance: {{ .Release.Name }} - app.kubernetes.io/version: {{ .Chart.AppVersion }} - {{- with .Values.ingress.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -spec: -{{- if .Values.ingress.tls }} - tls: - {{- range .Values.ingress.tls }} - - hosts: - {{- range .hosts }} - - {{ . | quote }} - {{- end }} - secretName: {{ .secretName }} - {{- end }} -{{- end }} - rules: - {{- range .Values.ingress.hosts }} - - host: {{ . }} - http: - paths: - - path: {{ $ingressPath }} - backend: - serviceName: {{ $fullName }} - servicePort: 8080 - {{- end }} -{{- end }} diff --git a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/networkpolicy.yaml b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/networkpolicy.yaml deleted file mode 100644 index 3757ad0e2..000000000 --- a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/networkpolicy.yaml +++ /dev/null @@ -1,20 +0,0 @@ -{{- if .Values.networkPolicy -}} -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: {{ template "sealed-secrets.fullname" . }} - namespace: {{ template "sealed-secrets.namespace" . }} - labels: - app.kubernetes.io/name: {{ template "sealed-secrets.name" . }} - helm.sh/chart: {{ template "sealed-secrets.chart" . }} - app.kubernetes.io/managed-by: {{ .Release.Service }} - app.kubernetes.io/instance: {{ .Release.Name }} - app.kubernetes.io/version: {{ .Chart.AppVersion }} -spec: - podSelector: - matchLabels: - app.kubernetes.io/name: {{ template "sealed-secrets.name" . }} - ingress: - - ports: - - port: 8080 -{{- end -}} diff --git a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/psp-clusterrole.yaml b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/psp-clusterrole.yaml deleted file mode 100644 index 051c83042..000000000 --- a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/psp-clusterrole.yaml +++ /dev/null @@ -1,18 +0,0 @@ -{{- if .Values.rbac.pspEnabled }} -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: {{ template "sealed-secrets.fullname" . }}-psp - labels: - app.kubernetes.io/name: {{ template "sealed-secrets.name" . }} - helm.sh/chart: {{ template "sealed-secrets.chart" . }} - app.kubernetes.io/managed-by: {{ .Release.Service }} - app.kubernetes.io/instance: {{ .Release.Name }} - app.kubernetes.io/version: {{ .Chart.AppVersion }} -rules: -- apiGroups: ['extensions'] - resources: ['podsecuritypolicies'] - verbs: ['use'] - resourceNames: - - {{ template "sealed-secrets.fullname" . }} -{{- end }} diff --git a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/psp-clusterrolebinding.yaml b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/psp-clusterrolebinding.yaml deleted file mode 100644 index c90d2687b..000000000 --- a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/psp-clusterrolebinding.yaml +++ /dev/null @@ -1,20 +0,0 @@ -{{- if .Values.rbac.pspEnabled }} -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: {{ template "sealed-secrets.fullname" . }}-psp - labels: - app.kubernetes.io/name: {{ template "sealed-secrets.name" . }} - helm.sh/chart: {{ template "sealed-secrets.chart" . }} - app.kubernetes.io/managed-by: {{ .Release.Service }} - app.kubernetes.io/instance: {{ .Release.Name }} - app.kubernetes.io/version: {{ .Chart.AppVersion }} -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: {{ template "sealed-secrets.fullname" . }}-psp -subjects: - - kind: ServiceAccount - name: {{ template "sealed-secrets.serviceAccountName" . }} - namespace: {{ template "sealed-secrets.namespace" . }} -{{- end }} diff --git a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/psp.yaml b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/psp.yaml deleted file mode 100644 index c744e190e..000000000 --- a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/psp.yaml +++ /dev/null @@ -1,34 +0,0 @@ -{{- if .Values.rbac.pspEnabled }} -apiVersion: policy/v1beta1 -kind: PodSecurityPolicy -metadata: - name: {{ template "sealed-secrets.fullname" . }} - labels: - app.kubernetes.io/name: {{ template "sealed-secrets.name" . }} - helm.sh/chart: {{ template "sealed-secrets.chart" . }} - app.kubernetes.io/managed-by: {{ .Release.Service }} - app.kubernetes.io/instance: {{ .Release.Name }} - app.kubernetes.io/version: {{ .Chart.AppVersion }} -spec: - privileged: false - allowPrivilegeEscalation: false - allowedCapabilities: [] - volumes: - - 'configMap' - - 'emptyDir' - - 'projected' - - 'secret' - - 'downwardAPI' - - 'persistentVolumeClaim' - hostNetwork: false - hostIPC: false - hostPID: false - runAsUser: - rule: 'RunAsAny' - seLinux: - rule: 'RunAsAny' - supplementalGroups: - rule: 'RunAsAny' - fsGroup: - rule: 'RunAsAny' -{{- end }} diff --git a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/role-binding.yaml b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/role-binding.yaml deleted file mode 100644 index 92ac69ab1..000000000 --- a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/role-binding.yaml +++ /dev/null @@ -1,48 +0,0 @@ -{{ if .Values.rbac.create }} -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: {{ template "sealed-secrets.fullname" . }}-key-admin - namespace: {{ template "sealed-secrets.namespace" . }} - labels: - app.kubernetes.io/name: {{ template "sealed-secrets.name" . }} - helm.sh/chart: {{ template "sealed-secrets.chart" . }} - app.kubernetes.io/managed-by: {{ .Release.Service }} - app.kubernetes.io/instance: {{ .Release.Name }} - app.kubernetes.io/version: {{ .Chart.AppVersion }} - {{- if .Values.rbac.labels }} -{{ toYaml .Values.rbac.labels | indent 4 }} - {{- end }} -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: {{ template "sealed-secrets.fullname" . }}-key-admin -subjects: - - apiGroup: "" - kind: ServiceAccount - name: {{ template "sealed-secrets.serviceAccountName" . }} - namespace: {{ template "sealed-secrets.namespace" . }} ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: {{ template "sealed-secrets.fullname" . }}-service-proxier - namespace: {{ template "sealed-secrets.namespace" . }} - labels: - app.kubernetes.io/name: {{ template "sealed-secrets.name" . }} - helm.sh/chart: {{ template "sealed-secrets.chart" . }} - app.kubernetes.io/managed-by: {{ .Release.Service }} - app.kubernetes.io/instance: {{ .Release.Name }} - app.kubernetes.io/version: {{ .Chart.AppVersion }} - {{- if .Values.rbac.labels }} -{{ toYaml .Values.rbac.labels | indent 4 }} - {{- end }} -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: {{ template "sealed-secrets.fullname" . }}-service-proxier -subjects: -- apiGroup: rbac.authorization.k8s.io - kind: Group - name: system:authenticated -{{ end }} diff --git a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/role.yaml b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/role.yaml deleted file mode 100644 index c25b1dbe0..000000000 --- a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/role.yaml +++ /dev/null @@ -1,58 +0,0 @@ -{{ if .Values.rbac.create }} -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: {{ template "sealed-secrets.fullname" . }}-key-admin - namespace: {{ template "sealed-secrets.namespace" . }} - labels: - app.kubernetes.io/name: {{ template "sealed-secrets.name" . }} - helm.sh/chart: {{ template "sealed-secrets.chart" . }} - app.kubernetes.io/managed-by: {{ .Release.Service }} - app.kubernetes.io/instance: {{ .Release.Name }} - app.kubernetes.io/version: {{ .Chart.AppVersion }} - {{- if .Values.rbac.labels }} -{{ toYaml .Values.rbac.labels | indent 4 }} - {{- end }} -rules: - - apiGroups: - - "" - resourceNames: - - {{ .Values.secretName }} - resources: - - secrets - verbs: - - get - - apiGroups: - - "" - resources: - - secrets - verbs: - - create - - list ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: {{ template "sealed-secrets.fullname" . }}-service-proxier - namespace: {{ template "sealed-secrets.namespace" . }} - labels: - app.kubernetes.io/name: {{ template "sealed-secrets.name" . }} - helm.sh/chart: {{ template "sealed-secrets.chart" . }} - app.kubernetes.io/managed-by: {{ .Release.Service }} - app.kubernetes.io/instance: {{ .Release.Name }} - app.kubernetes.io/version: {{ .Chart.AppVersion }} - {{- if .Values.rbac.labels }} -{{ toYaml .Values.rbac.labels | indent 4 }} - {{- end }} -rules: -- apiGroups: - - "" - resourceNames: - - 'http:{{ template "sealed-secrets.fullname" . }}:' - - {{ template "sealed-secrets.fullname" . }} - resources: - - services/proxy - verbs: - - create - - get -{{ end }} diff --git a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/service-account.yaml b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/service-account.yaml deleted file mode 100644 index 1248496f4..000000000 --- a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/service-account.yaml +++ /dev/null @@ -1,16 +0,0 @@ -{{ if .Values.serviceAccount.create }} -apiVersion: v1 -kind: ServiceAccount -metadata: - name: {{ template "sealed-secrets.serviceAccountName" . }} - namespace: {{ template "sealed-secrets.namespace" . }} - labels: - app.kubernetes.io/name: {{ template "sealed-secrets.name" . }} - helm.sh/chart: {{ template "sealed-secrets.chart" . }} - app.kubernetes.io/managed-by: {{ .Release.Service }} - app.kubernetes.io/instance: {{ .Release.Name }} - app.kubernetes.io/version: {{ .Chart.AppVersion }} - {{- if .Values.serviceAccount.labels }} -{{ toYaml .Values.serviceAccount.labels | indent 4 }} - {{- end }} -{{ end }} diff --git a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/service.yaml b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/service.yaml deleted file mode 100644 index ebd7607e0..000000000 --- a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/service.yaml +++ /dev/null @@ -1,23 +0,0 @@ -{{- if .Values.controller.create -}} -apiVersion: v1 -kind: Service -metadata: - name: {{ template "sealed-secrets.fullname" . }} - namespace: {{ template "sealed-secrets.namespace" . }} - labels: - app.kubernetes.io/name: {{ template "sealed-secrets.name" . }} - helm.sh/chart: {{ template "sealed-secrets.chart" . }} - app.kubernetes.io/managed-by: {{ .Release.Service }} - app.kubernetes.io/instance: {{ .Release.Name }} - app.kubernetes.io/version: {{ .Chart.AppVersion }} - {{- if .Values.controller.service.labels }} -{{ toYaml .Values.controller.service.labels | indent 4 }} - {{- end }} -spec: - ports: - - port: 8080 - targetPort: 8080 - selector: - app.kubernetes.io/name: {{ template "sealed-secrets.name" . }} - type: ClusterIP -{{- end }} diff --git a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/servicemonitor.yaml b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/servicemonitor.yaml deleted file mode 100644 index d832e9993..000000000 --- a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/templates/servicemonitor.yaml +++ /dev/null @@ -1,38 +0,0 @@ -{{ if .Values.serviceMonitor.create }} -apiVersion: monitoring.coreos.com/v1 -kind: ServiceMonitor -metadata: - name: {{ template "sealed-secrets.fullname" . }} - {{- if .Values.serviceMonitor.namespace }} - namespace: {{ .Values.serviceMonitor.namespace }} - {{- end }} - labels: - app.kubernetes.io/name: {{ template "sealed-secrets.name" . }} - helm.sh/chart: {{ template "sealed-secrets.chart" . }} - app.kubernetes.io/managed-by: {{ .Release.Service }} - app.kubernetes.io/instance: {{ .Release.Name }} - app.kubernetes.io/version: {{ .Chart.AppVersion }} - {{- if .Values.serviceMonitor.labels }} - {{- toYaml .Values.serviceMonitor.labels | nindent 4 }} - {{- end }} -spec: - endpoints: - - honorLabels: true - targetPort: 8080 - {{- with .Values.serviceMonitor.interval }} - interval: {{ . }} - {{- end }} - {{- with .Values.serviceMonitor.scrapeTimeout }} - scrapeTimeout: {{ . }} - {{- end }} - namespaceSelector: - matchNames: - - {{ .Release.Namespace }} - selector: - matchLabels: - app.kubernetes.io/name: {{ template "sealed-secrets.name" . }} - helm.sh/chart: {{ template "sealed-secrets.chart" . }} - app.kubernetes.io/managed-by: {{ .Release.Service }} - app.kubernetes.io/instance: {{ .Release.Name }} - app.kubernetes.io/version: {{ .Chart.AppVersion }} -{{- end }} diff --git a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/values.yaml b/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/values.yaml deleted file mode 100644 index 891d9c861..000000000 --- a/cmd/kluctl/bootstrap/sealed-secrets/charts/sealed-secrets/values.yaml +++ /dev/null @@ -1,95 +0,0 @@ -image: - repository: quay.io/bitnami/sealed-secrets-controller - tag: v0.16.0 - pullPolicy: IfNotPresent - pullSecret: "" - -resources: {} -nodeSelector: {} -tolerations: [] -affinity: {} - -controller: - # controller.create: `true` if Sealed Secrets controller should be created - create: true - # controller.labels: Extra labels to be added to controller deployment - labels: {} - # controller.service: Configuration options for controller service - service: - # controller.service.labels: Extra labels to be added to controller service - labels: {} - -# namespace: Namespace to deploy the controller. -namespace: "" - -serviceAccount: - # serviceAccount.create: Whether to create a service account or not - create: true - # serviceAccount.labels: Extra labels to be added to service account - labels: {} - # serviceAccount.name: The name of the service account to create or use - name: "" - -rbac: - # rbac.create: `true` if rbac resources should be created - create: true - # rbac.labels: Extra labels to be added to rbac resources - labels: {} - pspEnabled: false - -# secretName: The name of the TLS secret containing the key used to encrypt secrets -secretName: "sealed-secrets-key" - -ingress: - enabled: false - annotations: {} - # kubernetes.io/ingress.class: nginx - # kubernetes.io/tls-acme: "true" - path: /v1/cert.pem - hosts: - - chart-example.local - tls: [] - # - secretName: chart-example-tls - # hosts: - # - chart-example.local - -crd: - # crd.create: `true` if the crd resources should be created - create: true - # crd.keep: `true` if the sealed secret CRD should be kept when the chart is deleted - keep: true - -networkPolicy: false - -securityContext: - # securityContext.runAsUser defines under which user the operator Pod and its containers/processes run. - runAsUser: 1001 - # securityContext.fsGroup defines the filesystem group - fsGroup: 65534 - -podAnnotations: {} - -podLabels: {} - -priorityClassName: "" - -serviceMonitor: - # Enables ServiceMonitor creation for the Prometheus Operator - create: false - # How frequently Prometheus should scrape the ServiceMonitor - interval: - # Extra labels to apply to the sealed-secrets ServiceMonitor - labels: - # The namespace where the ServiceMonitor is deployed, defaults to the installation namespace - namespace: - # The timeout after which the scrape is ended - scrapeTimeout: - -dashboards: - # If enabled, sealed-secrets will create a configmap with a dashboard in json that's going to be picked up by grafana - # See https://github.com/helm/charts/tree/master/stable/grafana#configuration - `sidecar.dashboards.enabled` - create: false - # Extra labels to apply to the dashboard configmaps - labels: - # The namespace where the dashboards are deployed, defaults to the installation namespace - namespace: diff --git a/cmd/kluctl/bootstrap/sealed-secrets/helm-chart.yml b/cmd/kluctl/bootstrap/sealed-secrets/helm-chart.yml deleted file mode 100644 index 76d6791c4..000000000 --- a/cmd/kluctl/bootstrap/sealed-secrets/helm-chart.yml +++ /dev/null @@ -1,7 +0,0 @@ -helmChart: - repo: https://bitnami-labs.github.io/sealed-secrets - chartName: sealed-secrets - chartVersion: 1.16.1 - releaseName: sealed-secrets - namespace: kube-system - output: deploy.yml diff --git a/cmd/kluctl/bootstrap/sealed-secrets/helm-values.yml b/cmd/kluctl/bootstrap/sealed-secrets/helm-values.yml deleted file mode 100644 index c0ea1cbdf..000000000 --- a/cmd/kluctl/bootstrap/sealed-secrets/helm-values.yml +++ /dev/null @@ -1,3 +0,0 @@ -tolerations: - - key: CriticalAddonsOnly - operator: Exists diff --git a/cmd/kluctl/bootstrap/sealed-secrets/kustomization.yml b/cmd/kluctl/bootstrap/sealed-secrets/kustomization.yml deleted file mode 100644 index e19e67a96..000000000 --- a/cmd/kluctl/bootstrap/sealed-secrets/kustomization.yml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization - -resources: -- deploy.yml diff --git a/cmd/kluctl/cmd_bootstrap.go b/cmd/kluctl/cmd_bootstrap.go deleted file mode 100644 index 8a0d9cbab..000000000 --- a/cmd/kluctl/cmd_bootstrap.go +++ /dev/null @@ -1,91 +0,0 @@ -package main - -import ( - "embed" - "fmt" - "github.com/codablock/kluctl/cmd/kluctl/args" - "github.com/codablock/kluctl/pkg/types" - "github.com/codablock/kluctl/pkg/utils" - "github.com/spf13/cobra" - "github.com/spf13/viper" - "io/ioutil" - "k8s.io/apimachinery/pkg/runtime/schema" - "os" -) - -var ( - // files with _ as prefix are excluded by default, this is a workaround until go 1.18 is released - // see https://github.com/golang/go/issues/43854 - //go:embed bootstrap bootstrap/sealed-secrets/charts/sealed-secrets/templates/* - embedBootstrap embed.FS -) - -func runCmdBootstrap(cmd *cobra.Command, args_ []string) error { - tmpBootstrapDir, err := ioutil.TempDir(utils.GetTmpBaseDir(), "bootstrap-") - if err != nil { - return err - } - defer os.RemoveAll(tmpBootstrapDir) - - err = utils.FsCopyDir(embedBootstrap, "bootstrap", tmpBootstrapDir) - if err != nil { - return err - } - - if args.LocalDeployment == "" { - args.LocalDeployment = tmpBootstrapDir - } - - return withProjectCommandContext(func(ctx *commandCtx) error { - existing, _, err := ctx.k.GetSingleObject(types.ObjectRef{ - GVK: schema.GroupVersionKind{Group: "apiextensions.k8s.io", Version: "v1", Kind: "CustomResourceDefinition"}, - Name: "sealedsecrets.bitnami.com", - }) - if existing != nil { - component, _ := existing.GetLabels()["kluctl.io/component"] - if !args.ForceYes && component != "bootstrap" { - if !AskForConfirmation("It looks like you're trying to bootstrap a cluster that already has the sealed-secrets deployed but not managed by kluctl. Do you really want to continue bootstrapping?") { - return fmt.Errorf("aborted") - } - } - } - - err = runCmdDeploy2(cmd, ctx) - if err != nil { - return err - } - err = runCmdPrune2(cmd, ctx) - if err != nil { - return err - } - return nil - }) -} - -func init() { - cmd := &cobra.Command{ - Use: "bootstrap", - Short: "Bootstrap a target cluster", - Long: "This will install the sealed-secrets operator into the specified cluster if not already " + - "installed.\n\n" + - "Either --target or --cluster must be specified.", - RunE: runCmdBootstrap, - } - - args.AddProjectArgs(cmd, false, true, true) - args.AddMiscArguments(cmd, args.EnabledMiscArguments{ - Yes: true, - DryRun: true, - ForceApply: true, - ReplaceOnError: true, - HookTimeout: true, - AbortOnError: true, - OutputFormat: true, - }) - err := viper.BindPFlags(cmd.Flags()) - if err != nil { - panic(err) - } - - rootCmd.AddCommand(cmd) -} From b3b5e6da285c97fd417105cd76524934a288749e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Feb 2022 08:38:26 +0100 Subject: [PATCH 0348/2916] Implement validate command and proper hook wait logic --- cmd/kluctl/cmd_validate.go | 2 +- pkg/deployment/deployment_collection.go | 16 +- pkg/deployment/hooks_util.go | 9 + pkg/k8s/k8s_cluster.go | 9 +- pkg/seal/sealer.go | 6 +- pkg/types/command_result.go | 5 +- pkg/types/secrets_source.go | 2 +- pkg/utils/errorlist.go | 2 +- pkg/utils/uo/nested_fields.go | 19 ++ pkg/validation/validation.go | 294 +++++++++++++++++++++++- 10 files changed, 345 insertions(+), 19 deletions(-) diff --git a/cmd/kluctl/cmd_validate.go b/cmd/kluctl/cmd_validate.go index 385e49916..a86389017 100644 --- a/cmd/kluctl/cmd_validate.go +++ b/cmd/kluctl/cmd_validate.go @@ -13,7 +13,7 @@ func runCmdValidate(cmd *cobra.Command, args_ []string) error { return withProjectCommandContext(func(ctx *commandCtx) error { startTime := time.Now() for true { - result := ctx.deploymentCollection.Validate() + result := ctx.deploymentCollection.Validate(ctx.k) failed := len(result.Errors) != 0 || (args.WarningsAsErrors && len(result.Warnings) != 0) err := outputValidateResult(args.Output, result) diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index 93028d8f1..21a98f7f0 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -361,9 +361,7 @@ func (c *DeploymentCollection) Diff(k *k8s.K8sCluster, forceApply bool, replaceO }, nil } -func (c *DeploymentCollection) Validate() *types.ValidateResult { - c.clearErrorsAndWarnings() - +func (c *DeploymentCollection) Validate(k *k8s.K8sCluster) *types.ValidateResult { var result types.ValidateResult for _, m := range c.warnings { @@ -371,24 +369,32 @@ func (c *DeploymentCollection) Validate() *types.ValidateResult { result.Warnings = append(result.Warnings, e) } } - for _, m := range c.warnings { + for _, m := range c.errors { for e := range m { result.Errors = append(result.Errors, e) } } + a := newApplyUtil(c, k, applyUtilOptions{}) + h := hooksUtil{a: a} for _, d := range c.deployments { if !d.checkInclusionForDeploy() { continue } for _, o := range d.objects { + hook := h.getHook(o) + if hook != nil && !hook.isPersistent() { + continue + } + ref := types.RefFromObject(o) + remoteObject := c.getRemoteObject(ref) if remoteObject == nil { result.Errors = append(result.Errors, types.DeploymentError{Ref: ref, Error: "object not found"}) continue } - r := validation.ValidateObject(o, true) + r := validation.ValidateObject(remoteObject, true) result.Errors = append(result.Errors, r.Errors...) result.Warnings = append(result.Warnings, r.Warnings...) result.Results = append(result.Results, r.Results...) diff --git a/pkg/deployment/hooks_util.go b/pkg/deployment/hooks_util.go index 1e12db768..42221bdd4 100644 --- a/pkg/deployment/hooks_util.go +++ b/pkg/deployment/hooks_util.go @@ -249,3 +249,12 @@ func (u *hooksUtil) getSortedHooksList(objects []*unstructured.Unstructured) []* }) return ret } + +func (h *hook) isPersistent() bool { + for p := range h.deletePolicies { + if p != "before-hook-creation" && p != "hook-failed" { + return false + } + } + return true +} diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index 7bcae33cd..0b8b421b3 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -93,7 +93,7 @@ func NewK8sCluster(context string, dryRun bool) (*K8sCluster, error) { config.QPS = 10 config.Burst = 20 - discovery2, err := disk.NewCachedDiscoveryClientForConfig(dynamic.ConfigFor(config), path.Join(utils.GetTmpBaseDir(), "kube-cache"), path.Join(utils.GetTmpBaseDir(), "kube-http-cache"), time.Hour*24) + discovery2, err := disk.NewCachedDiscoveryClientForConfig(dynamic.ConfigFor(config), path.Join(utils.GetTmpBaseDir(), "kube-cache"), path.Join(utils.GetTmpBaseDir(), "kube-http-cache"), time.Minute*1) if err != nil { return nil, err } @@ -310,7 +310,10 @@ func (k *K8sCluster) getGVRForGVK(gvk schema.GroupVersionKind) (*schema.GroupVer ar, ok := k.allResources[gvk] if !ok { - return nil, false, fmt.Errorf("resource not found for %s", gvk.String()) + return nil, false, errors.NewNotFound(schema.GroupResource{ + Group: gvk.Group, + Resource: gvk.Kind, + }, "gvk") } return &schema.GroupVersionResource{ @@ -676,7 +679,7 @@ func (k *K8sCluster) PatchObject(o *unstructured.Unstructured, options PatchOpti apiWarnings, err := k.withDynamicClientForGVK(ref.GVK, ref.Namespace, func(r dynamic.ResourceInterface) error { x, err := r.Patch(context.Background(), ref.Name, types.ApplyPatchType, data, po) if err != nil { - return err + return fmt.Errorf("failed to patch %s: %w", ref.String(), err) } result = x return nil diff --git a/pkg/seal/sealer.go b/pkg/seal/sealer.go index 7eb137d86..9c052c30c 100644 --- a/pkg/seal/sealer.go +++ b/pkg/seal/sealer.go @@ -28,7 +28,7 @@ type Sealer struct { clusterConfig *types.ClusterConfig2 forceReseal bool cert *rsa.PublicKey - clusterId string + clusterId string } func NewSealer(k *k8s.K8sCluster, sealedSecretsNamespace string, sealedSecretsControllerName string, clusterConfig *types.ClusterConfig2, forceReseal bool) (*Sealer, error) { @@ -56,8 +56,8 @@ func NewSealer(k *k8s.K8sCluster, sealedSecretsNamespace string, sealedSecretsCo // get rotated. func getClusterId(k *k8s.K8sCluster) (string, error) { o, _, err := k.GetSingleObject(types.ObjectRef{ - GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, - Name: "kube-root-ca.crt", + GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, + Name: "kube-root-ca.crt", Namespace: "kube-system", }) if err != nil { diff --git a/pkg/types/command_result.go b/pkg/types/command_result.go index e8c107cba..301096afd 100644 --- a/pkg/types/command_result.go +++ b/pkg/types/command_result.go @@ -35,8 +35,9 @@ type CommandResult struct { } type ValidateResultEntry struct { - Ref ObjectRef `yaml:"ref"` - Message string `yaml:"message"` + Ref ObjectRef `yaml:"ref"` + Annotation string `yaml:"annotation"` + Message string `yaml:"message"` } type ValidateResult struct { diff --git a/pkg/types/secrets_source.go b/pkg/types/secrets_source.go index 63ed5e584..176a069c3 100644 --- a/pkg/types/secrets_source.go +++ b/pkg/types/secrets_source.go @@ -16,7 +16,7 @@ type SecretSourceAwsSecretsManager struct { type SecretSource struct { Path *string `yaml:"path,omitempty"` - SystemEnvVars *uo.UnstructuredObject `yaml:"systemEnvVars,omitempty"` + SystemEnvVars *uo.UnstructuredObject `yaml:"systemEnvVars,omitempty"` AwsSecretsManager *SecretSourceAwsSecretsManager `yaml:"awsSecretsManager,omitempty"` } diff --git a/pkg/utils/errorlist.go b/pkg/utils/errorlist.go index aa35c5780..374564cc5 100644 --- a/pkg/utils/errorlist.go +++ b/pkg/utils/errorlist.go @@ -12,7 +12,7 @@ func (el *errorList) Error() string { s := "" for _, err := range el.errors { if len(s) != 0 { - s += "; " + s += "\n" } s += err.Error() } diff --git a/pkg/utils/uo/nested_fields.go b/pkg/utils/uo/nested_fields.go index 9238355fa..00a39b51e 100644 --- a/pkg/utils/uo/nested_fields.go +++ b/pkg/utils/uo/nested_fields.go @@ -89,6 +89,25 @@ func (uo *UnstructuredObject) GetNestedString(keys ...interface{}) (string, bool return s, true, nil } +func (uo *UnstructuredObject) GetNestedInt(keys ...interface{}) (int64, bool, error) { + v, found, err := uo.GetNestedField(keys...) + if err != nil { + return 0, false, err + } + if !found { + return 0, false, nil + } + i, ok := v.(int64) + if !ok { + if i2, ok := v.(int); ok { + i = int64(i2) + } else { + return 0, false, fmt.Errorf("value at %s is not an int", KeyListToJsonPath(keys)) + } + } + return i, true, nil +} + func (uo *UnstructuredObject) GetNestedList(keys ...interface{}) ([]interface{}, bool, error) { v, found, err := uo.GetNestedField(keys...) if err != nil { diff --git a/pkg/validation/validation.go b/pkg/validation/validation.go index 5b344e5aa..18b8d507c 100644 --- a/pkg/validation/validation.go +++ b/pkg/validation/validation.go @@ -1,12 +1,300 @@ package validation import ( + "fmt" "github.com/codablock/kluctl/pkg/types" + "github.com/codablock/kluctl/pkg/utils/uo" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "strconv" + "strings" ) -func ValidateObject(o *unstructured.Unstructured, notReadyIsError bool) types.ValidateResult { - return types.ValidateResult{ - Ready: false, +const resultAnnotation = "validate-result.kluctl.io/" + +type validationFailed struct { +} + +func (err *validationFailed) Error() string { + return "validation failed" +} + +type condition struct { + status string + reason string + message string +} + +func (c condition) getMessage(def string) string { + if c.message == "" { + return def + } + return c.message +} + +func ValidateObject(o *unstructured.Unstructured, notReadyIsError bool) (ret types.ValidateResult) { + ref := types.RefFromObject(o) + + defer func() { + if r := recover(); r != nil { + if _, ok := r.(*validationFailed); ok { + // all good + } else if e, ok := r.(error); ok { + err := fmt.Errorf("panic in ValidateObject: %w", e) + ret.Errors = append(ret.Errors, types.DeploymentError{Ref: ref, Error: err.Error()}) + } else { + err := fmt.Errorf("panic in ValidateObject: %v", e) + ret.Errors = append(ret.Errors, types.DeploymentError{Ref: ref, Error: err.Error()}) + } + } + }() + + for k, v := range o.GetAnnotations() { + if strings.HasPrefix(k, resultAnnotation) { + ret.Results = append(ret.Results, types.ValidateResultEntry{ + Ref: ref, + Annotation: k, + Message: v, + }) + } + } + + u := uo.FromUnstructured(o) + status, ok, _ := u.GetNestedObject("status") + if !ok { + return + } + + addError := func(message string) { + ret.Errors = append(ret.Errors, types.DeploymentError{ + Ref: ref, + Error: message, + }) + } + addWarning := func(message string) { + ret.Warnings = append(ret.Warnings, types.DeploymentError{ + Ref: ref, + Error: message, + }) + } + addNotReady := func(message string) { + if notReadyIsError { + addError(message) + } else { + addWarning(message) + } + ret.Ready = false + } + + findConditions := func(typ string, doError bool, doRaise bool) []condition { + var ret []condition + l, ok, _ := status.GetNestedObjectList("conditions") + if ok { + for _, c := range l { + t, _, _ := c.GetNestedString("type") + status, _, _ := c.GetNestedString("status") + reason, _, _ := c.GetNestedString("reason") + message, _, _ := c.GetNestedString("message") + if t == typ { + ret = append(ret, condition{ + status: status, + reason: reason, + message: message, + }) + } + } + } + if len(ret) == 0 && doError { + err := fmt.Errorf("%s condition not in status", typ) + addError(err.Error()) + if doRaise { + panic(&validationFailed{}) + } + } + return ret + } + + getCondition := func(typ string, doError bool, doRaise bool) condition { + c := findConditions(typ, doError, doRaise) + if len(c) == 0 { + return condition{} + } + if len(c) != 1 { + err := fmt.Errorf("%s condition found more then once", typ) + addError(err.Error()) + if doRaise { + panic(&validationFailed{}) + } + } + return c[0] + } + getStatusField := func(field string, doError bool, doRaise bool, def interface{}) interface{} { + v, ok, _ := status.GetNestedField(field) + if !ok && doError { + err := fmt.Errorf("%s field not in status or empty", field) + addError(err.Error()) + if doRaise { + panic(&validationFailed{}) + } + } + if !ok { + return def + } + return v + } + getStatusFieldStr := func(field string, doError bool, doRaise bool, def string) string { + v := getStatusField(field, doError, doRaise, def) + if s, ok := v.(string); ok { + return s + } else { + err := fmt.Errorf("%s field is not a string", field) + addError(err.Error()) + if doRaise { + panic(&validationFailed{}) + } + } + return def + } + getStatusFieldInt := func(field string, doError bool, doRaise bool, def int64) int64 { + v := getStatusField(field, doError, doRaise, def) + if i, ok := v.(int64); ok { + return i + } else if i, ok := v.(int); ok { + return int64(i) + } else { + err := fmt.Errorf("%s field is not an int", field) + if doError { + addError(err.Error()) + } + if doRaise { + panic(&validationFailed{}) + } + } + return def + } + parseIntOrPercent := func(v interface{}) (int64, bool, error) { + if i, ok := v.(int64); ok { + return i, false, nil + } + if i, ok := v.(int); ok { + return int64(i), false, nil + } + if s, ok := v.(string); ok { + s = strings.ReplaceAll(s, "%", "") + i, err := strconv.ParseInt(s, 10, 32) + if err != nil { + return 0, false, err + } + return i, true, nil + } + return 0, false, fmt.Errorf("don't know how to parse %v", v) + } + valueFromIntOrPercent := func(v interface{}, total int64) (int64, error) { + i, isPercent, err := parseIntOrPercent(v) + if err != nil { + return 0, err + } + if isPercent { + return int64((float32(i) * float32(total)) / 100), nil + } + return i, nil + } + + switch u.GetK8sGVK().GroupKind() { + case schema.GroupKind{Group: "", Kind: "Pod"}: + c := getCondition("Ready", false, false) + if c.status != "True" { + addNotReady(c.getMessage("Not ready")) + } + case schema.GroupKind{Group: "batch", Kind: "Job"}: + c := getCondition("Failed", false, false) + if c.status == "True" { + addError(c.getMessage("Failed")) + } else { + c = getCondition("Complete", false, false) + if c.status != "True" { + addNotReady(c.getMessage("Not completed")) + } + } + case schema.GroupKind{Group: "apps", Kind: "Deployment"}: + readyReplicas := getStatusFieldInt("readyReplicas", true, true, 0) + replicas := getStatusFieldInt("replicas", true, true, 0) + if readyReplicas < replicas { + addNotReady(fmt.Sprintf("readyReplicas (%d) is less then replicas (%d)", readyReplicas, replicas)) + } + case schema.GroupKind{Group: "", Kind: "PersistentVolumeClaim"}: + phase := getStatusFieldStr("phase", true, true, "") + if phase != "Bound" { + addNotReady("Volume is not bound") + } + case schema.GroupKind{Group: "", Kind: "Service"}: + svcType, _, _ := u.GetNestedString("spec", "type") + if svcType != "ExternalName" { + clusterIP, _, _ := u.GetNestedString("spec", "clusterIP") + if clusterIP == "" { + addError("Service does not have a cluster IP") + } else if svcType == "LoadBalancer" { + externalIPs, _, _ := u.GetNestedList("spec", "externalIPs") + if len(externalIPs) == 0 { + ingress, _, _ := status.GetNestedList("loadBalancer", "ingress") + if len(ingress) == 0 { + addNotReady("Not ready") + } + } + } + } + case schema.GroupKind{Group: "apps", Kind: "DaemonSet"}: + updateStrategyType, _, _ := u.GetNestedString("spec", "updateStrategy", "type") + if updateStrategyType == "RollingUpdate" { + updatedNumberScheduled := getStatusFieldInt("updatedNumberScheduled", true, true, 0) + desiredNumberScheduled := getStatusFieldInt("desiredNumberScheduled", true, true, 0) + if updatedNumberScheduled != desiredNumberScheduled { + addNotReady(fmt.Sprintf("DaemonSet is not ready. %d out of %d expected pods have been scheduled", updatedNumberScheduled, desiredNumberScheduled)) + } else { + maxUnavailableI, _, _ := u.GetNestedField("spec", "updateStrategy", "maxUnavailable") + if maxUnavailableI == nil { + maxUnavailableI = 1 + } + maxUnavailable, err := valueFromIntOrPercent(maxUnavailableI, desiredNumberScheduled) + if err != nil { + maxUnavailable = desiredNumberScheduled + } + expectedReady := desiredNumberScheduled - maxUnavailable + numberReady := getStatusFieldInt("numberReady", true, true, 0) + if numberReady < expectedReady { + addNotReady(fmt.Sprintf("DaemonSet is not ready. %d out of %d expected pods are ready", numberReady, expectedReady)) + } + } + } + case schema.GroupKind{Group: "apiextensions.k8s.io", Kind: "CustomResourceDefinition"}: + // This is based on how Helm check for ready CRDs. + // See https://github.com/helm/helm/blob/249d1b5fb98541f5fb89ab11019b6060d6b169f1/pkg/kube/ready.go#L342 + c := getCondition("Established", false, false) + if c.status != "True" { + c = getCondition("NamesAccepted", true, true) + if c.status != "False" { + addNotReady("CRD is not ready") + } + } + case schema.GroupKind{Group: "apps", Kind: "StatefulSet"}: + updateStrategyType, _, _ := u.GetNestedString("spec", "updateStrategy", "type") + if updateStrategyType == "RollingUpdate" { + partition, _, _ := u.GetNestedInt("spec", "updateStrategy", "rollingUpdate", "partition") + replicas, ok, _ := u.GetNestedInt("spec", "replicas") + if !ok { + replicas = 1 + } + updatedReplicas := getStatusFieldInt("updatedReplicas", true, true, 0) + expectedReplicas := replicas - partition + if updatedReplicas != expectedReplicas { + addNotReady(fmt.Sprintf("StatefulSet is not ready. %d out of %d expected pods have been scheduled", updatedReplicas, expectedReplicas)) + } else { + readyReplicas := getStatusFieldInt("readyReplicas", true, true, 0) + if readyReplicas != replicas { + addNotReady(fmt.Sprintf("StatefulSet is not ready. %d out of %d expected pods are ready", readyReplicas, replicas)) + } + } + } } + return } From 7cc808a223093b3afb95a16af7ad531c88f2ec71 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Feb 2022 12:36:25 +0100 Subject: [PATCH 0349/2916] Fix confirmation --- cmd/kluctl/cmd_deploy.go | 2 +- cmd/kluctl/confirmation.go | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cmd/kluctl/cmd_deploy.go b/cmd/kluctl/cmd_deploy.go index d71d4dee7..d053ec4c9 100644 --- a/cmd/kluctl/cmd_deploy.go +++ b/cmd/kluctl/cmd_deploy.go @@ -15,7 +15,7 @@ func runCmdDeploy(cmd *cobra.Command, args_ []string) error { func runCmdDeploy2(cmd *cobra.Command, ctx *commandCtx) error { if !args.ForceYes && !args.DryRun { - if !AskForConfirmation(fmt.Sprintf("Do you really want to deploy to the context/cluster %s", ctx.k.Context())) { + if !AskForConfirmation(fmt.Sprintf("Do you really want to deploy to the context/cluster %s?", ctx.k.Context())) { return fmt.Errorf("aborted") } } diff --git a/cmd/kluctl/confirmation.go b/cmd/kluctl/confirmation.go index ebf867027..778aa8170 100644 --- a/cmd/kluctl/confirmation.go +++ b/cmd/kluctl/confirmation.go @@ -12,8 +12,8 @@ import ( // confirmations. If the input is not recognized, it will ask again. The function does not return // until it gets a valid response from the user. Typically, you should use fmt to print out a question // before calling askForConfirmation. E.g. fmt.Println("WARNING: Are you sure? (yes/no)") -func AskForConfirmation(promt string) bool { - _, err := os.Stderr.WriteString(promt) +func AskForConfirmation(prompt string) bool { + _, err := os.Stderr.WriteString(prompt + " (y/N) ") if err != nil { log.Fatal(err) } @@ -21,16 +21,16 @@ func AskForConfirmation(promt string) bool { var response string _, err = fmt.Scanln(&response) if err != nil { - log.Fatal(err) + return false } okayResponses := []string{"y", "Y", "yes", "Yes", "YES"} nokayResponses := []string{"n", "N", "no", "No", "NO"} if utils.FindStrInSlice(okayResponses, response) != -1 { return true - } else if utils.FindStrInSlice(nokayResponses, response) != -1 { + } else if utils.FindStrInSlice(nokayResponses, response) != -1 || response == "" { return false } else { fmt.Println("Please type yes or no and then press enter:") - return AskForConfirmation(promt) + return AskForConfirmation(prompt) } } From 38d7a745ab12cf08c191a725f80904be0982f1fe Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Feb 2022 12:36:43 +0100 Subject: [PATCH 0350/2916] Fix cast to errors.StatusError --- pkg/deployment/apply_utils.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/deployment/apply_utils.go b/pkg/deployment/apply_utils.go index 4f2381229..923f2a213 100644 --- a/pkg/deployment/apply_utils.go +++ b/pkg/deployment/apply_utils.go @@ -1,6 +1,7 @@ package deployment import ( + errors2 "errors" "fmt" "github.com/codablock/kluctl/pkg/diff" "github.com/codablock/kluctl/pkg/k8s" @@ -158,12 +159,13 @@ func (a *applyUtil) retryApplyWithConflicts(x *unstructured.Unstructured, hook b if remoteObject == nil { a.handleError(ref, applyError) + return } var x2 *unstructured.Unstructured if !a.o.forceApply { - statusError, ok := applyError.(*errors.StatusError) - if !ok { + var statusError *errors.StatusError + if !errors2.As(applyError, &statusError) { a.handleError(ref, applyError) return } From 7823f2e79c535f7efbd1f5d7e06930580d8c9300 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Feb 2022 12:37:00 +0100 Subject: [PATCH 0351/2916] Consider resources as ready when there are no explicit checks --- pkg/validation/validation.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/validation/validation.go b/pkg/validation/validation.go index 18b8d507c..7dcec5c9a 100644 --- a/pkg/validation/validation.go +++ b/pkg/validation/validation.go @@ -35,6 +35,9 @@ func (c condition) getMessage(def string) string { func ValidateObject(o *unstructured.Unstructured, notReadyIsError bool) (ret types.ValidateResult) { ref := types.RefFromObject(o) + // We assume all is good in case no validation is performed + ret.Ready = true + defer func() { if r := recover(); r != nil { if _, ok := r.(*validationFailed); ok { @@ -46,6 +49,7 @@ func ValidateObject(o *unstructured.Unstructured, notReadyIsError bool) (ret typ err := fmt.Errorf("panic in ValidateObject: %v", e) ret.Errors = append(ret.Errors, types.DeploymentError{Ref: ref, Error: err.Error()}) } + ret.Ready = false } }() From b0143b97e914e305a0d975c43a437d275e56a110 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Feb 2022 23:01:09 +0100 Subject: [PATCH 0352/2916] Implement deleteObjects --- pkg/deployment/apply_utils.go | 45 +++++++++++++++++++++---- pkg/deployment/deployment_collection.go | 14 +++++--- pkg/deployment/hooks_util.go | 2 +- pkg/k8s/delete_utils.go | 4 +-- pkg/k8s/k8s_cluster.go | 8 ++--- pkg/types/deployment.go | 31 +++++++++++++---- 6 files changed, 79 insertions(+), 25 deletions(-) diff --git a/pkg/deployment/apply_utils.go b/pkg/deployment/apply_utils.go index 923f2a213..30972f18f 100644 --- a/pkg/deployment/apply_utils.go +++ b/pkg/deployment/apply_utils.go @@ -33,6 +33,8 @@ type applyUtil struct { appliedObjects map[types.ObjectRef]*unstructured.Unstructured appliedHookObjects map[types.ObjectRef]*unstructured.Unstructured + deletedObjects map[types.ObjectRef]bool + deletedHookObjects map[types.ObjectRef]bool abortSignal bool mutex sync.Mutex } @@ -44,6 +46,8 @@ func newApplyUtil(deploymentCollection *DeploymentCollection, k *k8s.K8sCluster, o: o, appliedObjects: map[types.ObjectRef]*unstructured.Unstructured{}, appliedHookObjects: map[types.ObjectRef]*unstructured.Unstructured{}, + deletedObjects: map[types.ObjectRef]bool{}, + deletedHookObjects: map[types.ObjectRef]bool{}, } } @@ -82,16 +86,23 @@ func (a *applyUtil) hadError(ref types.ObjectRef) bool { return a.deploymentCollection.hadError(ref) } -func (a *applyUtil) deleteObject(ref types.ObjectRef) bool { +func (a *applyUtil) deleteObject(ref types.ObjectRef, hook bool) bool { o := k8s.DeleteOptions{ ForceDryRun: a.o.dryRun, } apiWarnings, err := a.k.DeleteSingleObject(ref, o) a.handleApiWarnings(ref, apiWarnings) if err != nil { - a.handleError(ref, err) + if !errors.IsNotFound(err) { + a.handleError(ref, err) + } return false } + if hook { + a.deletedHookObjects[ref] = true + } else { + a.deletedObjects[ref] = true + } return true } @@ -106,7 +117,7 @@ func (a *applyUtil) retryApplyForceReplace(x *unstructured.Unstructured, hook bo log2.Warningf("Patching failed, retrying by deleting and re-applying") - if !a.deleteObject(ref) { + if !a.deleteObject(ref, hook) { return } @@ -286,9 +297,9 @@ func (a *applyUtil) waitHook(ref types.ObjectRef) bool { return false } -func (a *applyUtil) applyKustomizeDeployment(d *deploymentItem) { +func (a *applyUtil) applyDeploymentItem(d *deploymentItem) { if d.config.Path == nil { - return + _ = 1 } if !d.checkInclusionForDeploy() { @@ -296,6 +307,24 @@ func (a *applyUtil) applyKustomizeDeployment(d *deploymentItem) { return } + var toDelete []types.ObjectRef + for _, x := range d.config.DeleteObjects { + for _, gvk := range a.k.GetGVKs(x.Group, x.Version, x.Kind) { + ref := types.ObjectRef{ + GVK: gvk, + Name: x.Name, + Namespace: x.Namespace, + } + toDelete = append(toDelete, ref) + } + } + if len(toDelete) != 0 { + log.Infof("Deleting %d objects", len(toDelete)) + for _, ref := range toDelete { + a.deleteObject(ref, false) + } + } + initialDeploy := true for _, o := range d.objects { if a.deploymentCollection.getRemoteObject(types.RefFromObject(o)) != nil { @@ -319,7 +348,9 @@ func (a *applyUtil) applyKustomizeDeployment(d *deploymentItem) { applyObjects = append(applyObjects, o) } - a.doLog(d, log.InfoLevel, "Applying %d objects", len(d.objects)) + if len(applyObjects) != 0 { + a.doLog(d, log.InfoLevel, "Applying %d objects", len(applyObjects)) + } for _, o := range applyObjects { a.applyObject(o, false, false) } @@ -351,7 +382,7 @@ func (a *applyUtil) applyDeployments() { previousWasBarrier = d.config.Barrier != nil && *d.config.Barrier wp.Submit(func() error { - a.applyKustomizeDeployment(d) + a.applyDeploymentItem(d) return nil }) } diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index 21a98f7f0..51a0541f4 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -299,7 +299,7 @@ func (c *DeploymentCollection) Deploy(k *k8s.K8sCluster, forceApply bool, replac abortOnError: abortOnError, hookTimeout: hookTimeout, } - appliedObjects, appliedHookObjects, err := c.doApply(k, o) + appliedObjects, appliedHookObjects, deletedObjects, err := c.doApply(k, o) if err != nil { return nil, err } @@ -318,6 +318,7 @@ func (c *DeploymentCollection) Deploy(k *k8s.K8sCluster, forceApply bool, replac return &types.CommandResult{ NewObjects: newObjects, ChangedObjects: changedObjects, + DeletedObjects: deletedObjects, HookObjects: appliedHookObjectsList, OrphanObjects: orphanObjects, Errors: c.errorsList(), @@ -335,7 +336,7 @@ func (c *DeploymentCollection) Diff(k *k8s.K8sCluster, forceApply bool, replaceO abortOnError: false, hookTimeout: 0, } - appliedObjects, appliedHookObjects, err := c.doApply(k, o) + appliedObjects, appliedHookObjects, deletedObjects, err := c.doApply(k, o) if err != nil { return nil, err } @@ -354,6 +355,7 @@ func (c *DeploymentCollection) Diff(k *k8s.K8sCluster, forceApply bool, replaceO return &types.CommandResult{ NewObjects: newObjects, ChangedObjects: changedObjects, + DeletedObjects: deletedObjects, HookObjects: appliedHookObjectsList, OrphanObjects: orphanObjects, Errors: c.errorsList(), @@ -415,10 +417,14 @@ func (c *DeploymentCollection) FindOrphanObjects(k *k8s.K8sCluster) ([]types.Obj return k8s.FindObjectsForDelete(k, labels, c.inclusion, c.localObjectRefs()) } -func (c *DeploymentCollection) doApply(k *k8s.K8sCluster, o applyUtilOptions) (map[types.ObjectRef]*unstructured.Unstructured, map[types.ObjectRef]*unstructured.Unstructured, error) { +func (c *DeploymentCollection) doApply(k *k8s.K8sCluster, o applyUtilOptions) (map[types.ObjectRef]*unstructured.Unstructured, map[types.ObjectRef]*unstructured.Unstructured, []types.ObjectRef, error) { au := newApplyUtil(c, k, o) au.applyDeployments() - return au.appliedObjects, au.appliedHookObjects, nil + var deletedObjects []types.ObjectRef + for ref := range au.deletedObjects { + deletedObjects = append(deletedObjects, ref) + } + return au.appliedObjects, au.appliedHookObjects, deletedObjects, nil } func (c *DeploymentCollection) doDiff(k *k8s.K8sCluster, appliedObjects map[types.ObjectRef]*unstructured.Unstructured, ignoreTags bool, ignoreLabels bool, ignoreAnnotations bool) ([]*unstructured.Unstructured, []*types.ChangedObject, error) { diff --git a/pkg/deployment/hooks_util.go b/pkg/deployment/hooks_util.go index 42221bdd4..2807e1c49 100644 --- a/pkg/deployment/hooks_util.go +++ b/pkg/deployment/hooks_util.go @@ -85,7 +85,7 @@ func (u *hooksUtil) runHooks(d *deploymentItem, hooks []string) { dpStr = append(dpStr, p) } doLog(log.DebugLevel, "Deleting hook %s due to hook-delete-policy %s", ref.String(), strings.Join(dpStr, ",")) - return u.a.deleteObject(ref) + return u.a.deleteObject(ref, true) } if len(deleteBeforeObjects) != 0 { diff --git a/pkg/k8s/delete_utils.go b/pkg/k8s/delete_utils.go index 7f26982fa..7f8c4c317 100644 --- a/pkg/k8s/delete_utils.go +++ b/pkg/k8s/delete_utils.go @@ -173,7 +173,7 @@ func DeleteObjects(k *K8sCluster, refs []types.ObjectRef, doWait bool) (*types.C if ref.GVK.GroupVersion().String() == "v1" && ref.GVK.Kind == "Namespace" { namespaceNames[ref.Name] = true wp.Submit(func() error { - apiWarnings, err := k.DeleteSingleObject(ref, DeleteOptions{NoWait: !doWait}) + apiWarnings, err := k.DeleteSingleObject(ref, DeleteOptions{NoWait: !doWait, IgnoreNotFoundError: true}) handleResult(ref, apiWarnings, err) return nil }) @@ -194,7 +194,7 @@ func DeleteObjects(k *K8sCluster, refs []types.ObjectRef, doWait bool) (*types.C continue } wp.Submit(func() error { - apiWarnings, err := k.DeleteSingleObject(ref, DeleteOptions{NoWait: !doWait}) + apiWarnings, err := k.DeleteSingleObject(ref, DeleteOptions{NoWait: !doWait, IgnoreNotFoundError: true}) handleResult(ref, apiWarnings, err) return nil }) diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index 0b8b421b3..80db85f5d 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -514,9 +514,9 @@ func (k *K8sCluster) GetObjectsByRefs(refs []types2.ObjectRef) ([]*unstructured. } type DeleteOptions struct { - ForceDryRun bool - NoWait bool - ErrorOnNotFound bool + ForceDryRun bool + NoWait bool + IgnoreNotFoundError bool } func (k *K8sCluster) DeleteSingleObject(ref types2.ObjectRef, options DeleteOptions) ([]ApiWarning, error) { @@ -533,7 +533,7 @@ func (k *K8sCluster) DeleteSingleObject(ref types2.ObjectRef, options DeleteOpti return k.withDynamicClientForGVK(ref.GVK, ref.Namespace, func(r dynamic.ResourceInterface) error { err := r.Delete(context.Background(), ref.Name, o) if err != nil { - if errors.IsNotFound(err) { + if options.IgnoreNotFoundError && errors.IsNotFound(err) { return nil } return err diff --git a/pkg/types/deployment.go b/pkg/types/deployment.go index 13969ac78..6e4bab1f1 100644 --- a/pkg/types/deployment.go +++ b/pkg/types/deployment.go @@ -7,13 +7,29 @@ import ( ) type DeploymentItemConfig struct { - Path *string `yaml:"path,omitempty"` - Tags []string `yaml:"tags,omitempty"` - Barrier *bool `yaml:"barrier,omitempty"` - Vars []*VarsListItem `yaml:"vars,omitempty"` - SkipDeleteIfTags *bool `yaml:"skipDeleteIfTags,omitempty"` - OnlyRender *bool `yaml:"onlyRender,omitempty"` - AlwaysDeploy *bool `yaml:"alwaysDeploy,omitempty"` + Path *string `yaml:"path,omitempty"` + Tags []string `yaml:"tags,omitempty"` + Barrier *bool `yaml:"barrier,omitempty"` + Vars []*VarsListItem `yaml:"vars,omitempty"` + SkipDeleteIfTags *bool `yaml:"skipDeleteIfTags,omitempty"` + OnlyRender *bool `yaml:"onlyRender,omitempty"` + AlwaysDeploy *bool `yaml:"alwaysDeploy,omitempty"` + DeleteObjects []DeleteObjectItemConfig `yaml:"deleteObjects,omitempty"` +} + +type DeleteObjectItemConfig struct { + Group *string `yaml:"group,omitempty"` + Version *string `yaml:"version,omitempty"` + Kind *string `yaml:"kind,omitempty"` + Name string `yaml:"name" validate:"required"` + Namespace string `yaml:"namespace,omitempty"` +} + +func ValidateDeleteObjectItemConfig(sl validator.StructLevel) { + s := sl.Current().Interface().(DeleteObjectItemConfig) + if s.Group == nil && s.Version == nil && s.Kind == nil { + sl.ReportError(s, "self", "self", "missingfield", "at least one of group/version/kind must be set") + } } type DeploymentArg struct { @@ -108,4 +124,5 @@ type DeploymentProjectConfig struct { func init() { yaml.Validator.RegisterStructValidation(ValidateVarsListItem, VarsListItem{}) + yaml.Validator.RegisterStructValidation(ValidateDeleteObjectItemConfig, DeleteObjectItemConfig{}) } From 4cd35c665007b6137198505f4a86607f007a6dae Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Feb 2022 23:02:35 +0100 Subject: [PATCH 0353/2916] Retry patch in case a required CRD was deployed before --- pkg/deployment/apply_utils.go | 32 ++++++++++++++++++++++ pkg/k8s/k8s_cluster.go | 51 ++++++++++++++++++++++++++++------- 2 files changed, 73 insertions(+), 10 deletions(-) diff --git a/pkg/deployment/apply_utils.go b/pkg/deployment/apply_utils.go index 30972f18f..33bba5e3e 100644 --- a/pkg/deployment/apply_utils.go +++ b/pkg/deployment/apply_utils.go @@ -36,6 +36,7 @@ type applyUtil struct { deletedObjects map[types.ObjectRef]bool deletedHookObjects map[types.ObjectRef]bool abortSignal bool + deployedNewCRD bool mutex sync.Mutex } @@ -48,6 +49,7 @@ func newApplyUtil(deploymentCollection *DeploymentCollection, k *k8s.K8sCluster, appliedHookObjects: map[types.ObjectRef]*unstructured.Unstructured{}, deletedObjects: map[types.ObjectRef]bool{}, deletedHookObjects: map[types.ObjectRef]bool{}, + deployedNewCRD: true, // assume someone deployed CRDs in the meantime } } @@ -227,6 +229,10 @@ func (a *applyUtil) applyObject(x *unstructured.Unstructured, replaced bool, hoo ForceDryRun: a.o.dryRun, } r, apiWarnings, err := a.k.PatchObject(x, options) + retry, err := a.handleNewCRDs(r, err) + if retry { + r, apiWarnings, err = a.k.PatchObject(x, options) + } a.handleApiWarnings(ref, apiWarnings) if err == nil { a.handleResult(r, hook) @@ -234,11 +240,37 @@ func (a *applyUtil) applyObject(x *unstructured.Unstructured, replaced bool, hoo a.handleError(ref, err) } else if errors.IsConflict(err) { a.retryApplyWithConflicts(x, hook, remoteObject, err) + } else if errors.IsInternalError(err) { + a.handleError(ref, err) } else { a.retryApplyWithReplace(x, hook, remoteObject, err) } } +func (a *applyUtil) handleNewCRDs(x *unstructured.Unstructured, err error) (bool, error) { + if err != nil && meta.IsNoMatchError(err) { + // maybe this was a resource for which the CRD was only deployed recently, so we should do rediscovery and then + // retry the patch + if a.deployedNewCRD { + a.deployedNewCRD = false + err = a.k.RediscoverResources() + if err != nil { + return false, err + } + return true, nil + } + } else if err == nil { + ref := types.RefFromObject(x) + if ref.GVK.Group == "apiextensions.k8s.io" && ref.GVK.Kind == "CustomResourceDefinition" { + // this is a freshly deployed CRD, so we must perform rediscovery in case an api resource can't be found + a.deployedNewCRD = true + return true, nil + } + return false, nil + } + return false, err +} + func (a *applyUtil) waitHook(ref types.ObjectRef) bool { if a.o.dryRun { return true diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index 80db85f5d..2d449bfb4 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -9,6 +9,7 @@ import ( goversion "github.com/hashicorp/go-version" log "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" @@ -93,7 +94,7 @@ func NewK8sCluster(context string, dryRun bool) (*K8sCluster, error) { config.QPS = 10 config.Burst = 20 - discovery2, err := disk.NewCachedDiscoveryClientForConfig(dynamic.ConfigFor(config), path.Join(utils.GetTmpBaseDir(), "kube-cache"), path.Join(utils.GetTmpBaseDir(), "kube-http-cache"), time.Minute*1) + discovery2, err := disk.NewCachedDiscoveryClientForConfig(dynamic.ConfigFor(config), path.Join(utils.GetTmpBaseDir(), "kube-cache"), path.Join(utils.GetTmpBaseDir(), "kube-http-cache"), time.Hour*24) if err != nil { return nil, err } @@ -138,7 +139,7 @@ func NewK8sCluster(context string, dryRun bool) (*K8sCluster, error) { } k.ServerVersion = v2 - err = k.updateResources() + err = k.updateResources(true) if err != nil { return nil, err } @@ -150,9 +151,11 @@ func (k *K8sCluster) Context() string { return k.context } -func (k *K8sCluster) updateResources() error { - k.mutex.Lock() - defer k.mutex.Unlock() +func (k *K8sCluster) updateResources(doLock bool) error { + if doLock { + k.mutex.Lock() + defer k.mutex.Unlock() + } k.allResources = map[schema.GroupVersionKind]v1.APIResource{} k.preferredResources = map[schema.GroupKind]v1.APIResource{} @@ -220,6 +223,14 @@ func (k *K8sCluster) updateResources() error { return nil } +func (k *K8sCluster) RediscoverResources() error { + k.mutex.Lock() + defer k.mutex.Unlock() + + k.discovery.Invalidate() + return k.updateResources(false) +} + func (k *K8sCluster) IsNamespaced(gvk schema.GroupVersionKind) bool { k.mutex.Lock() defer k.mutex.Unlock() @@ -304,16 +315,36 @@ func (k *K8sCluster) GetFilteredGKs(filters []string) []schema.GroupKind { return l } +func (k *K8sCluster) GetGVKs(group *string, version *string, kind *string) []schema.GroupVersionKind { + k.mutex.Lock() + defer k.mutex.Unlock() + + var ret []schema.GroupVersionKind + for gvk := range k.allResources { + if group != nil && *group != gvk.Group { + continue + } + if version != nil && *version != gvk.Version { + continue + } + if kind != nil && *kind != gvk.Kind { + continue + } + ret = append(ret, gvk) + } + return ret +} + func (k *K8sCluster) getGVRForGVK(gvk schema.GroupVersionKind) (*schema.GroupVersionResource, bool, error) { k.mutex.Lock() defer k.mutex.Unlock() ar, ok := k.allResources[gvk] if !ok { - return nil, false, errors.NewNotFound(schema.GroupResource{ - Group: gvk.Group, - Resource: gvk.Kind, - }, "gvk") + return nil, false, &meta.NoKindMatchError{ + GroupKind: gvk.GroupKind(), + SearchedVersions: []string{gvk.Version}, + } } return &schema.GroupVersionResource{ @@ -496,7 +527,7 @@ func (k *K8sCluster) GetObjectsByRefs(refs []types2.ObjectRef) ([]*unstructured. retApiWarnings[ref] = apiWarnings } if err != nil { - if errors.IsNotFound(err) { + if errors.IsNotFound(err) || meta.IsNoMatchError(err) { return nil } return err From 1b26a5006a6a897b66a55a1fad25a8f76f09c73d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 16 Feb 2022 11:22:25 +0100 Subject: [PATCH 0354/2916] Implement check-image-updates command --- cmd/kluctl/cmd_check_image_updates.go | 144 +++++++++ go.mod | 10 + go.sum | 414 +++++++++++++++++++++++- pkg/deployment/deployment_collection.go | 23 ++ pkg/registries/registries.go | 13 + pkg/utils/looseversion.go | 152 +++++++++ pkg/utils/looseversion_test.go | 81 +++++ pkg/utils/math.go | 16 + pkg/utils/prettytable.go | 15 +- 9 files changed, 863 insertions(+), 5 deletions(-) create mode 100644 cmd/kluctl/cmd_check_image_updates.go create mode 100644 pkg/registries/registries.go create mode 100644 pkg/utils/looseversion.go create mode 100644 pkg/utils/looseversion_test.go create mode 100644 pkg/utils/math.go diff --git a/cmd/kluctl/cmd_check_image_updates.go b/cmd/kluctl/cmd_check_image_updates.go new file mode 100644 index 000000000..6e26ba082 --- /dev/null +++ b/cmd/kluctl/cmd_check_image_updates.go @@ -0,0 +1,144 @@ +package main + +import ( + "github.com/codablock/kluctl/cmd/kluctl/args" + "github.com/codablock/kluctl/pkg/registries" + "github.com/codablock/kluctl/pkg/types" + "github.com/codablock/kluctl/pkg/utils" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "os" + "regexp" + "sort" + "strings" + "sync" +) + +func runCmdCheckImageUpdates(cmd *cobra.Command, args_ []string) error { + var renderedImages map[types.ObjectRef][]string + err := withProjectCommandContext(func(ctx *commandCtx) error { + renderedImages = ctx.deploymentCollection.FindRenderedImages() + return nil + }) + if err != nil { + return err + } + + wg := utils.NewWorkerPoolWithErrors(8) + defer wg.StopWait(false) + + imageTags := make(map[string]interface{}) + var mutex sync.Mutex + + for _, images := range renderedImages { + for _, image := range images { + s := strings.SplitN(image, ":", 2) + if len(s) == 1 { + continue + } + repo := s[0] + if _, ok := imageTags[repo]; !ok { + wg.Submit(func() error { + tags, err := registries.ListImageTags(repo) + mutex.Lock() + defer mutex.Unlock() + if err != nil { + imageTags[repo] = err + } else { + imageTags[repo] = tags + } + return nil + }) + } + } + } + err = wg.StopWait(false) + if err != nil { + return err + } + + prefixPattern := regexp.MustCompile("^([a-zA-Z]+[a-zA-Z-_.]*)") + suffixPattern := regexp.MustCompile("([-][a-zA-Z]+[a-zA-Z-_.]*)$") + + var table utils.PrettyTable + table.AddRow("Object", "Image", "Old", "New") + + for ref, images := range renderedImages { + for _, image := range images { + s := strings.SplitN(image, ":", 2) + if len(s) == 1 { + log.Warningf("%s: Ignoring image %s as it doesn't specify a tag", ref.String(), image) + continue + } + repo := s[0] + curTag := s[1] + repoTags, _ := imageTags[repo].([]string) + err, _ := imageTags[repo].(error) + if err != nil { + log.Warningf("%s: Failed to list tags for %s. %v", ref.String(), repo, err) + continue + } + + prefix := prefixPattern.FindString(curTag) + suffix := suffixPattern.FindString(curTag) + hasDot := strings.Index(curTag, ".") != -1 + + var filteredTags []string + for _, tag := range repoTags { + hasDot2 := strings.Index(tag, ".") != -1 + if hasDot != hasDot2 { + continue + } + if prefix != "" && !strings.HasPrefix(tag, prefix) { + continue + } + if suffix != "" && !strings.HasSuffix(tag, suffix) { + continue + } + filteredTags = append(filteredTags, tag) + } + doKey := func(tag string) utils.LooseVersion { + if prefix != "" { + tag = tag[len(prefix):] + } + if suffix != "" { + tag = tag[:len(tag)-len(suffix)] + } + return utils.LooseVersion(tag) + } + sort.SliceStable(filteredTags, func(i, j int) bool { + a := doKey(filteredTags[i]) + b := doKey(filteredTags[j]) + return a.Less(b, true) + }) + latestTag := filteredTags[len(filteredTags)-1] + + if latestTag != curTag { + table.AddRow(ref.String(), repo, curTag, latestTag) + } + } + } + + table.SortRows(1) + _, _ = os.Stdout.WriteString(table.Render([]int{60})) + return nil +} + +func init() { + var cmd = &cobra.Command{ + Use: "check-image-updates", + Short: "Render deployment and check if any images have new tags available", + Long: `Render deployment and check if any images have new tags available + +This is based on a best effort approach and might give many false-positives.`, + RunE: runCmdCheckImageUpdates, + } + args.AddProjectArgs(cmd, true, true, true) + err := viper.BindPFlags(cmd.Flags()) + if err != nil { + panic(err) + } + + rootCmd.AddCommand(cmd) +} diff --git a/go.mod b/go.mod index 3dcaec293..135a426f9 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/gobwas/glob v0.2.3 github.com/goccy/go-yaml v1.9.5 github.com/gofrs/flock v0.8.1 + github.com/google/go-containerregistry v0.8.0 github.com/hashicorp/go-version v1.4.0 github.com/hexops/gotextdiff v1.0.3 github.com/jinzhu/copier v0.3.5 @@ -46,7 +47,12 @@ require ( github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/acomagu/bufpipe v1.0.3 // indirect + github.com/containerd/stargz-snapshotter/estargz v0.10.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/docker/cli v20.10.12+incompatible // indirect + github.com/docker/distribution v2.7.1+incompatible // indirect + github.com/docker/docker v20.10.12+incompatible // indirect + github.com/docker/docker-credential-helpers v0.6.4 // indirect github.com/emirpasic/gods v1.12.0 // indirect github.com/evanphx/json-patch v4.12.0+incompatible // indirect github.com/fatih/color v1.13.0 // indirect @@ -78,6 +84,7 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect + github.com/klauspost/compress v1.13.6 // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/magiconair/properties v1.8.5 // indirect github.com/mailru/easyjson v0.7.6 // indirect @@ -88,6 +95,8 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.0.2-0.20211117181255-693428a734f5 // indirect github.com/pelletier/go-toml v1.9.4 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect @@ -99,6 +108,7 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/testify v1.7.0 // indirect github.com/subosito/gotenv v1.2.0 // indirect + github.com/vbatts/tar-split v0.11.2 // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/xanzy/ssh-agent v0.3.0 // indirect github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect diff --git a/go.sum b/go.sum index 232b067ac..7f9eb4b59 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,4 @@ +bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -48,18 +49,26 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= github.com/Azure/go-autorest/autorest v0.11.20 h1:s8H1PbCZSqg/DH7JMlOz6YMig6htWLNPsjDdlLqCx3M= github.com/Azure/go-autorest/autorest v0.11.20/go.mod h1:o3tqFY+QR40VOlk+pV4d77mORO64jOXSgEnPQgLK6JY= +github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= +github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= github.com/Azure/go-autorest/autorest/adal v0.9.15 h1:X+p2GF0GWyOiSmqohIaEeuNFNDY4I4EOlVuUQvFdWMk= github.com/Azure/go-autorest/autorest/adal v0.9.15/go.mod h1:tGMin8I49Yij6AQ+rvV+Xa/zwxYQB5hmsd6DkfAx2+A= github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= @@ -68,10 +77,26 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= +github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY= github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= +github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= +github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= +github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= +github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= +github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00= +github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600= +github.com/Microsoft/hcsshim v0.8.23/go.mod h1:4zegtUJth7lAvFyc6cH2gGQ5B3OFQim01nnU2M8jKDg= +github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= +github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C67SkzkDfmQuVln04ygHj3vjZfd9FL+GmQQ= @@ -82,6 +107,7 @@ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= @@ -93,12 +119,14 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= @@ -108,28 +136,48 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkY github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/aws/aws-sdk-go v1.27.0 h1:0xphMHGMLBrPMfxR2AmVjZKcMEESEgWF8Kru94BNByk= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= github.com/bitnami-labs/flagenv v0.0.0-20190607135054-a87af7a1d6fc/go.mod h1:OeW4NPgFPO7+t8q1Vn2Yv+rkO+4kEQzlDskwm7C7PXs= github.com/bitnami-labs/pflagenv v0.0.0-20190702160147-b4d9f048d98f/go.mod h1:Lw3ejf6HTt4DqBIAXlkOIvFjnpj8Zq+zD/UtH29ILFA= github.com/bitnami-labs/sealed-secrets v0.17.3 h1:ebus6Rbz9MLhWcHVkFiwDpVkrecvjzOibXdkmJkRhss= github.com/bitnami-labs/sealed-secrets v0.17.3/go.mod h1:EMXakbe/TMdfzATuEH+pTA2K29aZhNgyBNTdQvDc/eU= +github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= +github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= +github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= +github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= +github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= +github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= +github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= +github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc= +github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= +github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= +github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= @@ -146,23 +194,148 @@ github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= +github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= +github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= +github.com/containerd/aufs v1.0.0/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= +github.com/containerd/btrfs v0.0.0-20201111183144-404b9149801e/go.mod h1:jg2QkJcsabfHugurUvvPhS3E08Oxiuh5W/g1ybB4e0E= +github.com/containerd/btrfs v0.0.0-20210316141732-918d888fb676/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= +github.com/containerd/btrfs v1.0.0/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= +github.com/containerd/cgroups v0.0.0-20190717030353-c4b9ac5c7601/go.mod h1:X9rLEHIqSf/wfK8NsPqxJmeZgW4pcfzdXITDrUSJ6uI= +github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= +github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM= +github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= +github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= +github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= +github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU= +github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= +github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= +github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= +github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= +github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= +github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.1-0.20191213020239-082f7e3aed57/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.0-beta.2.0.20200729163537-40b22ef07410/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.9/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ= +github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU= +github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= +github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s= +github.com/containerd/containerd v1.5.8/go.mod h1:YdFSv5bTFLpG2HIYmfqDpSYYTDX+mc5qtSuYx1YUb/s= +github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo= +github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y= +github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ= +github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= +github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= +github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= +github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= +github.com/containerd/fifo v0.0.0-20201026212402-0724c46b320c/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= +github.com/containerd/fifo v0.0.0-20210316144830-115abcc95a1d/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= +github.com/containerd/fifo v1.0.0/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= +github.com/containerd/go-cni v1.0.1/go.mod h1:+vUpYxKvAF72G9i1WoDOiPGRtQpqsNW/ZHtSlv++smU= +github.com/containerd/go-cni v1.0.2/go.mod h1:nrNABBHzu0ZwCug9Ije8hL2xBCYh/pjfMb1aZGrrohk= +github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= +github.com/containerd/go-runc v0.0.0-20190911050354-e029b79d8cda/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= +github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g= +github.com/containerd/go-runc v0.0.0-20201020171139-16b287bc67d0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= +github.com/containerd/go-runc v1.0.0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= +github.com/containerd/imgcrypt v1.0.1/go.mod h1:mdd8cEPW7TPgNG4FpuP3sGBiQ7Yi/zak9TYCG3juvb0= +github.com/containerd/imgcrypt v1.0.4-0.20210301171431-0ae5c75f59ba/go.mod h1:6TNsg0ctmizkrOgXRNQjAPFWpMYRWuiB6dSF4Pfa5SA= +github.com/containerd/imgcrypt v1.1.1-0.20210312161619-7ed62a527887/go.mod h1:5AZJNI6sLHJljKuI9IHnw1pWqo/F0nGDOuR9zgTs7ow= +github.com/containerd/imgcrypt v1.1.1/go.mod h1:xpLnwiQmEUJPvQoAapeb2SNCxz7Xr6PJrXQb0Dpc4ms= +github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c= +github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= +github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= +github.com/containerd/stargz-snapshotter/estargz v0.10.1 h1:hd1EoVjI2Ax8Cr64tdYqnJ4i4pZU49FkEf5kU8KxQng= +github.com/containerd/stargz-snapshotter/estargz v0.10.1/go.mod h1:aE5PCyhFMwR8sbrErO5eM2GcvkyXTTJremG883D4qF0= +github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= +github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= +github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8= +github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= +github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= +github.com/containerd/ttrpc v1.1.0/go.mod h1:XX4ZTnoOId4HklF4edwc4DcqskFZuvXB1Evzy5KFQpQ= +github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= +github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk= +github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg= +github.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcDZXTn6oPz9s= +github.com/containerd/zfs v0.0.0-20200918131355-0a33824f23a2/go.mod h1:8IgZOBdv8fAgXddBT4dBXJPtxyRsejFIpXoklgxgEjw= +github.com/containerd/zfs v0.0.0-20210301145711-11e8f1707f62/go.mod h1:A9zfAbMlQwE+/is6hi0Xw8ktpL+6glmqZYtevJgaB8Y= +github.com/containerd/zfs v0.0.0-20210315114300-dde8f0fda960/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= +github.com/containerd/zfs v0.0.0-20210324211415-d5c4544f0433/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= +github.com/containerd/zfs v1.0.0/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= +github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= +github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= +github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= +github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM= +github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8= +github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc= +github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4= +github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= +github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= +github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= +github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= +github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= +github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= +github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= +github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I= +github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg= github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= +github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= +github.com/docker/cli v20.10.12+incompatible h1:lZlz0uzG+GH+c0plStMUdF/qk3ppmgnswpR5EbqzVGA= +github.com/docker/cli v20.10.12+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= +github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v20.10.12+incompatible h1:CEeNmFM0QZIsJCZKMkZx0ZcahTiewkrgiwfYD+dfl1U= +github.com/docker/docker v20.10.12+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.6.4 h1:axCks+yV+2MR3/kZhAmy07yC56WZ2Pwu/fKWtKuZB0o= +github.com/docker/docker-credential-helpers v0.6.4/go.mod h1:ofX3UI0Gz1TteYBjtgs07O36Pyasyp66D2uKT7H8W1c= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= +github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= @@ -184,6 +357,7 @@ github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go. github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= +github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= @@ -197,14 +371,17 @@ github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoD github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= +github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= github.com/gammazero/deque v0.1.0 h1:f9LnNmq66VDeuAlSAapemq/U7hJ2jpIWa4c09q8Dlik= github.com/gammazero/deque v0.1.0/go.mod h1:KQw7vFau1hHuM8xmI9RbgKFbAsQFWmBpqQ2KenFLk6M= github.com/gammazero/workerpool v1.1.2 h1:vuioDQbgrz4HoaCi2q1HLlOXdpbap5AET7xu5/qj87g= github.com/gammazero/workerpool v1.1.2/go.mod h1:UelbXcO0zCIGFcufcirHhq2/xtLXJdQ29qZNlXG9OjQ= +github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -224,6 +401,7 @@ github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= @@ -247,6 +425,7 @@ github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8 github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= +github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= @@ -269,20 +448,29 @@ github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/goccy/go-yaml v1.9.5 h1:Eh/+3uk9kLxG4koCX6lRMAPS1OaMSAi+FJcya0INdB0= github.com/goccy/go-yaml v1.9.5/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA= +github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= +github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= +github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= +github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= +github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.0.0 h1:RAqyYixv1p7uEnocuy8P1nru5wprCh/MH2BIlW5z5/o= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -335,6 +523,8 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-containerregistry v0.8.0 h1:mtR24eN6rapCN+shds82qFEIWWmg64NPMuyCNT7/Ogc= +github.com/google/go-containerregistry v0.8.0/go.mod h1:wW5v71NHGnQyb4k+gSshjxidrC7lN33MdWEn+Mz9TsI= github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= @@ -363,25 +553,32 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3 github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw= github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= @@ -391,6 +588,7 @@ github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUo github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= +github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= @@ -400,6 +598,7 @@ github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39E github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= @@ -436,16 +635,22 @@ github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg= github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= +github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= @@ -472,7 +677,12 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -493,6 +703,7 @@ github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-b github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -501,6 +712,7 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= @@ -519,10 +731,14 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= +github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= +github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -538,8 +754,15 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= github.com/mkmik/multierror v0.3.0/go.mod h1:wjBYXRpDhh+8mIp+iLBOq0kZ3Y4ICTncojwvP8LUYLQ= +github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= +github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= +github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ= +github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= +github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -550,7 +773,10 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= @@ -561,6 +787,7 @@ github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzE github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= @@ -568,19 +795,53 @@ github.com/ohler55/ojg v1.12.12 h1:hepbQFn7GHAecTPmwS3j5dCiOLsOpzPLvhiqnlAVAoE= github.com/ohler55/ojg v1.12.12/go.mod h1:LBbIVRAgoFbYBXQhRhuEpaJIqq+goSO63/FQ+nyJU88= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA= +github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0-rc1.0.20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.0.2-0.20211117181255-693428a734f5 h1:q37d91F6BO4Jp1UqWiun0dUFYaqv6WsKTLTCaWv+8LY= +github.com/opencontainers/image-spec v1.0.2-0.20211117181255-693428a734f5/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0= +github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= +github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= +github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= +github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= +github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= @@ -593,6 +854,8 @@ github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIw github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= @@ -603,6 +866,7 @@ github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -613,31 +877,47 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= +github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU= +github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= +github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/r3labs/diff/v2 v2.15.0 h1:3TEoJ6dBqESl1YgL+7curys5PvuEnwrtjkFNskgUvfg= github.com/r3labs/diff/v2 v2.15.0/go.mod h1:I8noH9Fc2fjSaMxqF3G2lhDdC0b+JXCfyx85tWFM9kc= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= @@ -651,13 +931,18 @@ github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6po github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= github.com/sagikazarmark/crypt v0.4.0/go.mod h1:ALv2SRj7GxYV4HO9elxH9nS6M9gW+xDNxqmyJ6RfDFM= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -666,41 +951,53 @@ github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/cobra v1.3.0 h1:R7cSvGu+Vv+qX0gW5R/85dx2kmmJT5z5NM8ifdYjdn0= github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v0.0.0-20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= github.com/spf13/viper v1.10.1 h1:nuJZuYpG7gTj/XqiUwg8bA0cp1+M2mC3J4g5luUYBKk= github.com/spf13/viper v1.10.1/go.mod h1:IGlFPqhNAPKRxohIzWpI5QEy4kuI7tcl5WvR+8qy1rU= +github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -710,33 +1007,64 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= github.com/throttled/throttled v2.2.2+incompatible/go.mod h1:0BjlrEGQmvxps+HuXLsyRdqpSRvJpq0PNIsOtqP9Nos= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/vbatts/tar-split v0.11.2 h1:Via6XqJr0hceW4wff3QRzD5gAk/tatMw/4ZA7cTlIME= +github.com/vbatts/tar-split v0.11.2/go.mod h1:vV3ZuO2yWSVsz+pfFzDG/upWH1JhjOiEaWq6kXyQ3VI= +github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= +github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= +github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= +github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= +github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= +github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/whilp/git-urls v1.0.0 h1:95f6UMWN5FKW71ECsXRUd3FVYiXdrE7aX4NZKcPmIjU= github.com/whilp/git-urls v1.0.0/go.mod h1:J16SAmobsqc3Qcy98brfl5f5+e0clUvg1krgwk/qCfE= +github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= +github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca h1:1CFlNzQhALwjS9mBAUkycX616GzgsuYUOCHA5+HSlXI= github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= +github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= +github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs= +go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -750,6 +1078,7 @@ go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqe go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee331t6JAXeK2bcyhLOOc= go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= @@ -759,7 +1088,9 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9E go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -771,6 +1102,7 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= @@ -817,10 +1149,12 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -832,8 +1166,10 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -856,10 +1192,12 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= @@ -869,8 +1207,10 @@ golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d h1:62NvYBuaanGXR2ZOfwDFkhhl6X1DUgf8qg3GuQvxZsE= golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -920,10 +1260,16 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -933,18 +1279,23 @@ golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -955,12 +1306,22 @@ golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -974,6 +1335,7 @@ golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -988,6 +1350,7 @@ golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1014,6 +1377,8 @@ golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1034,6 +1399,7 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1083,6 +1449,7 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1091,6 +1458,7 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= +google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= @@ -1135,11 +1503,13 @@ google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCID google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= @@ -1149,6 +1519,7 @@ google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvx google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= @@ -1169,6 +1540,7 @@ google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= @@ -1202,6 +1574,7 @@ google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ6 google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368 h1:Et6SkiuvnBn+SgrSYXs/BrUpGB4mbdwt4R3vaPIlicA= google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= @@ -1211,6 +1584,7 @@ google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ij google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= @@ -1251,8 +1625,10 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -1262,12 +1638,17 @@ gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qS gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI= gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= @@ -1286,6 +1667,11 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= +gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1294,16 +1680,35 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= +k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= +k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= k8s.io/api v0.22.1/go.mod h1:bh13rkTp3F1XEaLGykbyRD2QaTTzPm0e/BMd8ptFONY= k8s.io/api v0.23.3 h1:KNrME8KHGr12Ozjf8ytOewKzZh6hl/hHUZeHddT3a38= k8s.io/api v0.23.3/go.mod h1:w258XdGyvCmnBj/vGzQMj6kzdufJZVUwEM1U2fRJwSQ= +k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= +k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= +k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= k8s.io/apimachinery v0.22.1/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= k8s.io/apimachinery v0.23.3 h1:7IW6jxNzrXTsP0c8yXz2E5Yx/WTzVPTsHIx/2Vm0cIk= k8s.io/apimachinery v0.23.3/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM= +k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= +k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= +k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= +k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= +k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= +k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= k8s.io/client-go v0.22.1/go.mod h1:BquC5A4UOo4qVDUtoc04/+Nxp1MeHcVc1HJm1KmG8kk= k8s.io/client-go v0.23.3 h1:23QYUmCQ/W6hW78xIwm3XqZrrKZM+LWDqW2zfo+szJs= k8s.io/client-go v0.23.3/go.mod h1:47oMd+YvAOqZM7pcQ6neJtBiFH7alOyfunYN48VsmwE= k8s.io/code-generator v0.16.8/go.mod h1:wFdrXdVi/UC+xIfLi+4l9elsTT/uEF61IfcN2wOLULQ= +k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= +k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= +k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= +k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM= +k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= +k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= +k8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= @@ -1313,13 +1718,17 @@ k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= k8s.io/klog/v2 v2.30.0 h1:bUO6drIvCIsvZ/XFgfxoGFQU/a4Qkh0iAlvUR7vlHJw= k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= +k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 h1:E3J9oCLlaobFUqsjG9DfKbP2BmgwBL2p7pn0A3dG9W4= k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= +k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= +k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210707171843-4b05e18ac7d9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20211116205334-6203023598ed h1:ck1fRPWPJWsMd8ZRFsWc6mh/zHp5fZ/shhbrgPUxDAE= @@ -1332,6 +1741,8 @@ modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 h1:fD1pz4yfdADVNfFmcP2aBEtudwUQ1AlLnRBALr33v3s= sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= sigs.k8s.io/kustomize/api v0.11.1 h1:/Vutu+gAqVo8skw1xCZrsZD39SN4Adg+z7FrSTw9pds= @@ -1341,6 +1752,7 @@ sigs.k8s.io/kustomize/kyaml v0.13.3/go.mod h1:/ya3Gk4diiQzlE4mBh7wykyLRFZNvqlbh+ sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e h1:4Z09Hglb792X0kfOBBJUPFEyvVfQWrYT/l8h5EKA6JQ= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y= sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index 51a0541f4..e9f0b3b21 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -7,6 +7,7 @@ import ( "github.com/codablock/kluctl/pkg/seal" "github.com/codablock/kluctl/pkg/types" "github.com/codablock/kluctl/pkg/utils" + "github.com/codablock/kluctl/pkg/utils/uo" "github.com/codablock/kluctl/pkg/validation" log "github.com/sirupsen/logrus" "io/fs" @@ -572,3 +573,25 @@ func (c *DeploymentCollection) getRemoteObject(ref types.ObjectRef) *unstructure o, _ := c.remoteObjects[ref] return o } + +func (c *DeploymentCollection) FindRenderedImages() map[types.ObjectRef][]string { + ret := make(map[types.ObjectRef][]string) + for _, d := range c.deployments { + for _, o := range d.objects { + ref := types.RefFromObject(o) + u := uo.FromUnstructured(o) + l, ok, _ := u.GetNestedObjectList("spec", "template", "spec", "containers") + if !ok { + continue + } + for _, c := range l { + image, ok, _ := c.GetNestedString("image") + if !ok { + continue + } + ret[ref] = append(ret[ref], image) + } + } + } + return ret +} diff --git a/pkg/registries/registries.go b/pkg/registries/registries.go new file mode 100644 index 000000000..9eb9f8022 --- /dev/null +++ b/pkg/registries/registries.go @@ -0,0 +1,13 @@ +package registries + +import ( + "github.com/google/go-containerregistry/pkg/crane" +) + +func ListImageTags(image string) ([]string, error) { + tags, err := crane.ListTags(image) + if err != nil { + return nil, err + } + return tags, nil +} diff --git a/pkg/utils/looseversion.go b/pkg/utils/looseversion.go new file mode 100644 index 000000000..ae472d317 --- /dev/null +++ b/pkg/utils/looseversion.go @@ -0,0 +1,152 @@ +package utils + +import ( + "regexp" + "strconv" + "strings" +) + +var looseSemverRegex = regexp.MustCompile(`^(([0-9]+)(\.[0-9]+)*)?(.*)$`) +var suffixComponentRegex = regexp.MustCompile(`\d+|[a-zA-Z]+|\.`) + +// Allows to compare ints and strings. Strings are always considered less than ints. +type LooseVersionSuffixElement struct { + v interface{} +} + +func (x LooseVersionSuffixElement) Less(b LooseVersionSuffixElement) bool { + ia, iaOk := x.v.(int64) + ib, ibOk := b.v.(int64) + sa, _ := x.v.(string) + sb, _ := b.v.(string) + if ibOk == iaOk { + if iaOk { + return ia < ib + } else { + return sa < sb + } + } + if iaOk { + return false + } + return true +} + +type LooseVersion string + +func (lv LooseVersion) SplitVersion() ([]int, string) { + m := looseSemverRegex.FindStringSubmatch(string(lv)) + numsStr := m[1] + suffix := m[4] + + var nums []int + for _, x := range strings.Split(numsStr, ".") { + i, _ := strconv.ParseInt(x, 10, 32) + nums = append(nums, int(i)) + } + return nums, suffix +} + +func regexSplitLikePython(s string, re *regexp.Regexp) []string { + ret := []string{s} + indexes := re.FindAllStringIndex(s, -1) + start := 0 + for _, se := range indexes { + m := s[start:se[0]] + if len(m) != 0 { + ret = append(ret, m) + } + m = s[se[0]:se[1]] + if len(m) != 0 { + ret = append(ret, m) + } + start = se[1] + } + if start < len(s) { + ret = append(ret, s[start:]) + } + return ret +} + +func splitSuffix(suffix string) []LooseVersionSuffixElement { + var components []LooseVersionSuffixElement + for i, x := range regexSplitLikePython(suffix, suffixComponentRegex) { + if i == 0 { + continue + } + if x != "." { + y, err := strconv.ParseInt(x, 10, 32) + if err == nil { + components = append(components, LooseVersionSuffixElement{v: y}) + } else { + components = append(components, LooseVersionSuffixElement{v: x}) + } + } + } + return components +} + +func (lv LooseVersion) Less(b LooseVersion, preferLongSuffix bool) bool { + aNums, aSuffixStr := lv.SplitVersion() + bNums, bSuffixStr := b.SplitVersion() + + cmp := func(a []int, b []int) bool { + l := IntMin(len(a), len(b)) + for i := 0; i < l; i++ { + if a[i] < b[i] { + return true + } + if b[i] < a[i] { + return false + } + } + if len(a) < len(b) { + return true + } + return false + } + + if cmp(aNums, bNums) { + return true + } + if cmp(bNums, aNums) { + return false + } + if len(aSuffixStr) == 0 && len(bSuffixStr) != 0 { + return false + } else if len(aSuffixStr) != 0 && len(bSuffixStr) == 0 { + return true + } + + aSuffix := splitSuffix(aSuffixStr) + bSuffix := splitSuffix(bSuffixStr) + l := IntMin(len(aSuffix), len(bSuffix)) + + for i := 0; i < l; i++ { + if aSuffix[i].Less(bSuffix[i]) { + return true + } else if bSuffix[i].Less(aSuffix[i]) { + return false + } + } + + if preferLongSuffix { + if len(aSuffix) < len(bSuffix) { + return true + } + } else { + if len(bSuffix) < len(aSuffix) { + return true + } + } + return false +} + +func (lv LooseVersion) Compare(b LooseVersion) int { + if lv.Less(b, true) { + return -1 + } else if b.Less(lv, true) { + return 1 + } + return 0 +} diff --git a/pkg/utils/looseversion_test.go b/pkg/utils/looseversion_test.go new file mode 100644 index 000000000..abeab5e4e --- /dev/null +++ b/pkg/utils/looseversion_test.go @@ -0,0 +1,81 @@ +package utils + +import ( + "testing" +) + +func checkEqual(t *testing.T, a_ string, b_ string) { + a := LooseVersion(a_) + b := LooseVersion(b_) + if a.Less(b, true) { + t.Errorf("%s < %s should be false", a, b) + } + if b.Less(a, true) { + t.Errorf("%s > %s should be false", b, a) + } + if a.Compare(b) != 0 { + t.Errorf("%s == %s should be true", a, b) + } +} + +func checkLess(t *testing.T, a_ string, b_ string) { + a := LooseVersion(a_) + b := LooseVersion(b_) + if !a.Less(b, true) { + t.Errorf("%s < %s should be true", a, b) + } + if b.Less(a, true) { + t.Errorf("%s < %s should be false", b, a) + } + if a.Compare(b) != -1 { + t.Errorf("%s.Compare(%s) should be -1", a, b) + } + if b.Compare(a) != 1 { + t.Errorf("%s.Compare(%s) should be 1", b, a) + } +} + +func TestEquality(t *testing.T) { + checkEqual(t, "1", "1") + checkEqual(t, "1.1", "1.1") + checkEqual(t, "1.1a", "1.1a") + checkEqual(t, "1.1-a", "1.1-a") + checkEqual(t, "1.a-1", "1.a-1") +} + +func TestLess(t *testing.T) { + checkLess(t, "1", "2") + checkLess(t, "1", "1.1") + checkLess(t, "1.1a", "1.1") + checkLess(t, "1.1-a", "1.1") + checkLess(t, "1.1a", "1.1b") + checkLess(t, "1.1-a", "1.1-b") + checkLess(t, "1.a-1", "1.a-2") +} + +func TestMavenVersions(t *testing.T) { + checkLess(t, "1.1-SNAPSHOT", "1.1") + checkLess(t, "1", "1.1") + checkLess(t, "1-SNAPSHOT", "1.1") + checkLess(t, "1.1", "1.1.1-SNAPSHOT") + checkLess(t, "1.1-SNAPSHOT", "1.1.1-SNAPSHOT") +} + +func TestSuffixes(t *testing.T) { + checkLess(t, "1.1-1", "1.1-2") + checkLess(t, "1.1-suffix-1", "1.1-suffix-2") + checkLess(t, "1.1-suffix-2", "1.1-suffix-10") + checkLess(t, "1.1-suffix-2", "1.1-suffiy-1") + checkLess(t, "1.1-1-1", "1.1-2-1") + checkLess(t, "1.1-2-1", "1.1-2-2") + checkLess(t, "1.1-2-1", "1.1-100-2") + checkLess(t, "1.1-2-1", "1.1") + checkLess(t, "1.1-2", "1.1-2-1") + checkLess(t, "1.1-a-1", "1.1-1-1") +} + +func TestLooseVersionNoNums(t *testing.T) { + checkLess(t, "-snapshot1", "-snapshot2") + checkLess(t, "-1", "-2") + checkLess(t, "-1.1", "-1.2") +} diff --git a/pkg/utils/math.go b/pkg/utils/math.go new file mode 100644 index 000000000..0b69fda42 --- /dev/null +++ b/pkg/utils/math.go @@ -0,0 +1,16 @@ +package utils + +func IntMin(a, b int) int { + if a < b { + return a + } + return b +} + +// IntMax returns the maximum integer provided +func IntMax(a, b int) int { + if a > b { + return a + } + return b +} diff --git a/pkg/utils/prettytable.go b/pkg/utils/prettytable.go index ce9bcdaa1..8d2d0afbe 100644 --- a/pkg/utils/prettytable.go +++ b/pkg/utils/prettytable.go @@ -3,6 +3,7 @@ package utils import ( "bytes" "golang.org/x/crypto/ssh/terminal" + "sort" "strings" ) @@ -16,6 +17,12 @@ func (t *PrettyTable) AddRow(c ...string) { t.rows = append(t.rows, c) } +func (t *PrettyTable) SortRows(col int) { + sort.SliceStable(t.rows[1:], func(i, j int) bool { + return t.rows[i+1][col] < t.rows[j+1][col] + }) +} + func (t *PrettyTable) Render(limitWidths []int) string { cols := len(t.rows[0]) @@ -46,12 +53,12 @@ func (t *PrettyTable) Render(limitWidths []int) string { widths := make([]int, cols) widthSum := 0 for i := 0; i < cols; i++ { + w := -1 if i < len(limitWidths) { - widths[i] = maxWidth(i, limitWidths[i]) - widthSum += widths[i] - } else { - widths[i] = -1 + w = limitWidths[i] } + widths[i] = maxWidth(i, w) + widthSum += widths[i] } if len(limitWidths) < cols { From c56b009023f6d58e5324d6b98c84b6e11a85cf97 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 16 Feb 2022 13:00:00 +0100 Subject: [PATCH 0355/2916] Implement downscale command --- cmd/kluctl/cmd_delete.go | 4 +- cmd/kluctl/cmd_downscale.go | 57 +++++++++++++ go.mod | 2 +- pkg/deployment/deployment_collection.go | 106 ++++++++++++++++++++++++ pkg/deployment/downscale_utils.go | 92 ++++++++++++++++++++ pkg/yaml/yaml.go | 14 ++++ 6 files changed, 272 insertions(+), 3 deletions(-) create mode 100644 cmd/kluctl/cmd_downscale.go create mode 100644 pkg/deployment/downscale_utils.go diff --git a/cmd/kluctl/cmd_delete.go b/cmd/kluctl/cmd_delete.go index a60e3061e..309a1789b 100644 --- a/cmd/kluctl/cmd_delete.go +++ b/cmd/kluctl/cmd_delete.go @@ -50,8 +50,8 @@ func confirmedDeleteObjects(k *k8s.K8sCluster, refs []types.ObjectRef) (*types.C func init() { var cmd = &cobra.Command{ Use: "delete", - Short: "Delete the a target (or parts of it) from the corresponding cluster", - Long: "Delete the a target (or parts of it) from the corresponding cluster.\n\n" + + Short: "Delete a target (or parts of it) from the corresponding cluster", + Long: "Delete a target (or parts of it) from the corresponding cluster.\n\n" + "Objects are located based on `deleteByLabels`, configured in `deployment.yml`\n\n" + "WARNING: This command will also delete objects which are not part of your deployment " + "project (anymore). It really only decides based on the `deleteByLabel` labels and does NOT " + diff --git a/cmd/kluctl/cmd_downscale.go b/cmd/kluctl/cmd_downscale.go new file mode 100644 index 000000000..805b84170 --- /dev/null +++ b/cmd/kluctl/cmd_downscale.go @@ -0,0 +1,57 @@ +package main + +import ( + "fmt" + "github.com/codablock/kluctl/cmd/kluctl/args" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +func runCmdDownscale(cmd *cobra.Command, args_ []string) error { + return withProjectCommandContext(func(ctx *commandCtx) error { + if !args.ForceYes && !args.DryRun { + if !AskForConfirmation(fmt.Sprintf("Do you really want to downscale on context/cluster %s?", ctx.k.Context())) { + return fmt.Errorf("aborted") + } + } + result, err := ctx.deploymentCollection.Downscale(ctx.k) + if err != nil { + return err + } + err = outputCommandResult(args.Output, result) + if err != nil { + return err + } + if len(result.Errors) != 0 { + return fmt.Errorf("command failed") + } + return nil + }) +} + +func init() { + var cmd = &cobra.Command{ + Use: "downscale", + Short: "Downscale all deployments", + Long: "Downscale all deployments.\n\n" + + "This command will downscale all Deployments, StatefulSets and CronJobs. " + + "It is also possible to influence the behaviour with the help of annotations, as described in " + + "the documentation.", + RunE: runCmdDownscale, + } + args.AddProjectArgs(cmd, true, true, true) + args.AddImageArgs(cmd) + args.AddInclusionArgs(cmd) + args.AddMiscArguments(cmd, args.EnabledMiscArguments{ + Yes: true, + DryRun: true, + OutputFormat: true, + RenderOutputDir: true, + }) + err := viper.BindPFlags(cmd.Flags()) + if err != nil { + panic(err) + } + + rootCmd.AddCommand(cmd) +} diff --git a/go.mod b/go.mod index 135a426f9..b9c9cd93a 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.17 require ( github.com/aws/aws-sdk-go v1.27.0 github.com/bitnami-labs/sealed-secrets v0.17.3 + github.com/evanphx/json-patch v4.12.0+incompatible github.com/gammazero/workerpool v1.1.2 github.com/go-git/go-git/v5 v5.4.2 github.com/go-playground/validator/v10 v10.10.0 @@ -54,7 +55,6 @@ require ( github.com/docker/docker v20.10.12+incompatible // indirect github.com/docker/docker-credential-helpers v0.6.4 // indirect github.com/emirpasic/gods v1.12.0 // indirect - github.com/evanphx/json-patch v4.12.0+incompatible // indirect github.com/fatih/color v1.13.0 // indirect github.com/fsnotify/fsnotify v1.5.1 // indirect github.com/gammazero/deque v0.1.0 // indirect diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index e9f0b3b21..e3841ca5c 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -11,9 +11,11 @@ import ( "github.com/codablock/kluctl/pkg/validation" log "github.com/sirupsen/logrus" "io/fs" + "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "path" "path/filepath" + "reflect" "strings" "sync" "time" @@ -364,6 +366,68 @@ func (c *DeploymentCollection) Diff(k *k8s.K8sCluster, forceApply bool, replaceO }, nil } +func (c *DeploymentCollection) Downscale(k *k8s.K8sCluster) (*types.CommandResult, error) { + wp := utils.NewWorkerPoolWithErrors(8) + defer wp.StopWait(false) + + appliedObjects := make(map[types.ObjectRef]*unstructured.Unstructured) + var deletedObjects []types.ObjectRef + var mutex sync.Mutex + + for _, d := range c.deployments { + if !d.checkInclusionForDeploy() { + continue + } + for _, o := range d.objects { + o := o + ref := types.RefFromObject(o) + if isDownscaleDelete(o) { + wp.Submit(func() error { + apiWarnings, err := k.DeleteSingleObject(ref, k8s.DeleteOptions{IgnoreNotFoundError: true}) + c.addApiWarnings(ref, apiWarnings) + if err != nil { + return err + } + mutex.Lock() + defer mutex.Unlock() + deletedObjects = append(deletedObjects, ref) + return nil + }) + } else { + wp.Submit(func() error { + o2, err := c.doReplaceObject(k, ref, func(remote *unstructured.Unstructured) (*unstructured.Unstructured, error) { + return downscaleObject(remote, o) + }) + if err != nil { + return err + } + mutex.Lock() + defer mutex.Unlock() + appliedObjects[ref] = o2 + return nil + }) + } + } + } + + err := wp.StopWait(false) + if err != nil { + return nil, err + } + + newObjects, changedObjects, err := c.doDiff(k, appliedObjects, false, false, false) + if err != nil { + return nil, err + } + return &types.CommandResult{ + NewObjects: newObjects, + ChangedObjects: changedObjects, + DeletedObjects: deletedObjects, + Errors: c.errorsList(), + Warnings: c.warningsList(), + }, nil +} + func (c *DeploymentCollection) Validate(k *k8s.K8sCluster) *types.ValidateResult { var result types.ValidateResult @@ -495,6 +559,48 @@ func (c *DeploymentCollection) doDiff(k *k8s.K8sCluster, appliedObjects map[type return newObjects, changedObjects, nil } +func (c *DeploymentCollection) doReplaceObject(k *k8s.K8sCluster, ref types.ObjectRef, callback func(o *unstructured.Unstructured) (*unstructured.Unstructured, error)) (*unstructured.Unstructured, error) { + firstCall := true + for true { + var remote *unstructured.Unstructured + if firstCall { + remote = c.getRemoteObject(ref) + } else { + o2, apiWarnings, err := k.GetSingleObject(ref) + c.addApiWarnings(ref, apiWarnings) + if err != nil && !errors.IsNotFound(err) { + return nil, err + } + remote = o2 + } + if remote == nil { + return remote, nil + } + firstCall = false + + modified, err := callback(remote) + if err != nil { + return nil, err + } + if reflect.DeepEqual(remote.Object, modified.Object) { + return remote, nil + } + + result, apiWarnings, err := k.UpdateObject(modified, k8s.UpdateOptions{}) + c.addApiWarnings(ref, apiWarnings) + if err != nil { + if errors.IsConflict(err) { + log.Warningf("Conflict while patching %s. Retrying...", ref.String()) + continue + } else { + return nil, err + } + } + return result, nil + } + return nil, fmt.Errorf("unexpected end of loop") +} + func (c *DeploymentCollection) addWarning(ref types.ObjectRef, warning error) { de := types.DeploymentError{ Ref: ref, diff --git a/pkg/deployment/downscale_utils.go b/pkg/deployment/downscale_utils.go new file mode 100644 index 000000000..7b4b34c55 --- /dev/null +++ b/pkg/deployment/downscale_utils.go @@ -0,0 +1,92 @@ +package deployment + +import ( + "fmt" + "github.com/codablock/kluctl/pkg/types" + "github.com/codablock/kluctl/pkg/utils/uo" + "github.com/codablock/kluctl/pkg/yaml" + jsonpatch "github.com/evanphx/json-patch" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "regexp" + "strconv" +) + +var ( + downscaleAnnotationPatchRegex = regexp.MustCompile(`^kluctl.io/downscale-patch(-\d*)?$`) + downscaleAnnotationDelete = "kluctl.io/downscale-delete" + downscaleAnnotationIgnore = "kluctl.io/downscale-ignore" +) + +func isDownscaleDelete(o *unstructured.Unstructured) bool { + a, _ := o.GetAnnotations()[downscaleAnnotationDelete] + b, _ := strconv.ParseBool(a) + return b +} + +func isDownscaleIgnore(o *unstructured.Unstructured) bool { + a, _ := o.GetAnnotations()[downscaleAnnotationIgnore] + b, _ := strconv.ParseBool(a) + return b +} + +func downscaleObject(remote *unstructured.Unstructured, local *unstructured.Unstructured) (*unstructured.Unstructured, error) { + ret := remote + if isDownscaleIgnore(local) { + return ret, nil + } + var patch jsonpatch.Patch + for k, v := range local.GetAnnotations() { + if downscaleAnnotationPatchRegex.MatchString(k) { + j, err := yaml.ConvertYamlToJson([]byte(v)) + if err != nil { + return nil, fmt.Errorf("invalid jsonpatch json/yaml: %w", err) + } + p, err := jsonpatch.DecodePatch(j) + if err != nil { + return nil, fmt.Errorf("invalid jsonpatch: %w", err) + } + patch = append(patch, p...) + } + } + + if len(patch) != 0 { + j, err := yaml.WriteYamlBytes(ret.Object) + if err != nil { + return nil, err + } + j, err = yaml.ConvertYamlToJson(j) + if err != nil { + return nil, err + } + j, err = patch.Apply(j) + if err != nil { + return nil, err + } + ret = &unstructured.Unstructured{} + err = yaml.ReadYamlBytes(j, &ret.Object) + if err != nil { + return nil, err + } + } + + ref := types.RefFromObject(remote) + switch ref.GVK.GroupKind() { + case schema.GroupKind{Group: "apps", Kind: "Deployment"}: + fallthrough + case schema.GroupKind{Group: "apps", Kind: "StatefulSet"}: + ret = uo.CopyUnstructured(ret) + err := uo.FromUnstructured(ret).SetNestedField(0, "spec", "replicas") + if err != nil { + return nil, err + } + case schema.GroupKind{Group: "batch", Kind: "CronJob"}: + ret = uo.CopyUnstructured(ret) + err := uo.FromUnstructured(ret).SetNestedField(true, "spec", "suspend") + if err != nil { + return nil, err + } + } + + return ret, nil +} diff --git a/pkg/yaml/yaml.go b/pkg/yaml/yaml.go index 20ed4aac6..bd532e111 100644 --- a/pkg/yaml/yaml.go +++ b/pkg/yaml/yaml.go @@ -2,6 +2,7 @@ package yaml import ( "bytes" + "encoding/json" "errors" "fmt" "github.com/goccy/go-yaml" @@ -152,3 +153,16 @@ func WriteYamlAllStream(w io.Writer, l []interface{}) error { } return nil } + +func ConvertYamlToJson(b []byte) ([]byte, error) { + var x interface{} + err := ReadYamlBytes(b, &x) + if err != nil { + return nil, err + } + b, err = json.Marshal(x) + if err != nil { + return nil, err + } + return b, nil +} From d7fa96390035538ccf70cee2fe8b0a455775d08a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 16 Feb 2022 13:00:11 +0100 Subject: [PATCH 0356/2916] Fix CopyUnstructured to actually do a deep copy --- pkg/utils/uo/unstructured.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/utils/uo/unstructured.go b/pkg/utils/uo/unstructured.go index 885908391..709d402ac 100644 --- a/pkg/utils/uo/unstructured.go +++ b/pkg/utils/uo/unstructured.go @@ -2,18 +2,18 @@ package uo import ( "fmt" - "github.com/jinzhu/copier" + "github.com/codablock/kluctl/pkg/utils" log "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) func CopyUnstructured(u *unstructured.Unstructured) *unstructured.Unstructured { - var m map[string]interface{} - err := copier.Copy(&m, &u.Object) + var ret unstructured.Unstructured + err := utils.DeepCopy(&ret.Object, &u.Object) if err != nil { log.Fatal(err) } - return &unstructured.Unstructured{Object: m} + return &ret } func MergeStrMap(a map[string]string, b map[string]string) { From f601b6640e06c43fe74ee1939d25002c9630e810 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 16 Feb 2022 13:00:29 +0100 Subject: [PATCH 0357/2916] Fix cached discovery to honor different clusters --- pkg/k8s/k8s_cluster.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index 2d449bfb4..74916bce6 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -23,6 +23,7 @@ import ( "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/util/homedir" "net/http" + "net/url" "path" "strings" "sync" @@ -94,7 +95,13 @@ func NewK8sCluster(context string, dryRun bool) (*K8sCluster, error) { config.QPS = 10 config.Burst = 20 - discovery2, err := disk.NewCachedDiscoveryClientForConfig(dynamic.ConfigFor(config), path.Join(utils.GetTmpBaseDir(), "kube-cache"), path.Join(utils.GetTmpBaseDir(), "kube-http-cache"), time.Hour*24) + apiHost, err := url.Parse(config.Host) + if err != nil { + return nil, err + } + discoveryCacheDir := path.Join(utils.GetTmpBaseDir(), "kube-cache/discovery", apiHost.Hostname()) + httpCacheDir := path.Join(utils.GetTmpBaseDir(), "kube-cache/http", apiHost.Hostname()) + discovery2, err := disk.NewCachedDiscoveryClientForConfig(dynamic.ConfigFor(config), discoveryCacheDir, httpCacheDir, time.Hour*24) if err != nil { return nil, err } From 6a1fcede1672ce942f00e1246a333d575581ef98 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 16 Feb 2022 13:57:34 +0100 Subject: [PATCH 0358/2916] Introduce LooseVersionSlice --- pkg/deployment/helm_chart.go | 22 +++------------------- pkg/utils/looseversion.go | 7 +++++++ 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index 18045f40d..265bf3e7a 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -113,22 +113,6 @@ func (c *helmChart) Pull() error { } } -type VersionSlice []string - -func (x VersionSlice) Less(i, j int) bool { - v1, err1 := goversion.NewVersion(x[i]) - v2, err2 := goversion.NewVersion(x[j]) - if err1 != nil { - v1, _ = goversion.NewVersion("0") - } - if err2 != nil { - v2, _ = goversion.NewVersion("0") - } - return v1.LessThan(v2) -} -func (x VersionSlice) Len() int { return len(x) } -func (x VersionSlice) Swap(i, j int) { x[i], x[j] = x[j], x[i] } - func (c *helmChart) CheckUpdate() (string, error) { if c.config.Repo != nil && strings.HasPrefix(*c.config.Repo, "oci://") { return "", nil @@ -147,7 +131,7 @@ func (c *helmChart) CheckUpdate() (string, error) { } // ensure we didn't get partial matches var lm []map[string]string - var ls VersionSlice + var ls utils.LooseVersionSlice err = yaml.ReadYamlBytes(stdout, &lm) if err != nil { return err @@ -155,7 +139,7 @@ func (c *helmChart) CheckUpdate() (string, error) { for _, x := range lm { if n, ok := x["name"]; ok && n == chartName { if v, ok := x["version"]; ok { - ls = append(ls, v) + ls = append(ls, utils.LooseVersion(v)) } } } @@ -163,7 +147,7 @@ func (c *helmChart) CheckUpdate() (string, error) { return fmt.Errorf("helm chart %s not found in repository", chartName) } sort.Sort(ls) - latestVersion = ls[len(ls)-1] + latestVersion = string(ls[len(ls)-1]) if c.config.ChartVersion != nil && latestVersion == *c.config.ChartVersion { return nil } diff --git a/pkg/utils/looseversion.go b/pkg/utils/looseversion.go index ae472d317..60bb7d9b9 100644 --- a/pkg/utils/looseversion.go +++ b/pkg/utils/looseversion.go @@ -150,3 +150,10 @@ func (lv LooseVersion) Compare(b LooseVersion) int { } return 0 } + +type LooseVersionSlice []LooseVersion +func (x LooseVersionSlice) Less(i, j int) bool { + return x[i].Less(x[j], true) +} +func (x LooseVersionSlice) Len() int { return len(x) } +func (x LooseVersionSlice) Swap(i, j int) { x[i], x[j] = x[j], x[i] } From 04d54e29bea2f8735e0960f4c6fe3bf2f6863054 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 16 Feb 2022 13:57:44 +0100 Subject: [PATCH 0359/2916] Implement helm-pull command --- cmd/kluctl/cmd_helm_pull.go | 54 ++++++++++++++++++++++++++++++++++++ pkg/deployment/helm_chart.go | 8 ++---- 2 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 cmd/kluctl/cmd_helm_pull.go diff --git a/cmd/kluctl/cmd_helm_pull.go b/cmd/kluctl/cmd_helm_pull.go new file mode 100644 index 000000000..7e59f452f --- /dev/null +++ b/cmd/kluctl/cmd_helm_pull.go @@ -0,0 +1,54 @@ +package main + +import ( + "github.com/codablock/kluctl/cmd/kluctl/args" + "github.com/codablock/kluctl/pkg/deployment" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "io/fs" + "path" + "path/filepath" +) + +func runCmdHelmPull(cmd *cobra.Command, args_ []string) error { + rootPath := "." + if args.LocalDeployment != "" { + rootPath = args.LocalDeployment + } + err := filepath.WalkDir(rootPath, func(p string, d fs.DirEntry, err error) error { + fname := path.Base(p) + if fname == "helm-chart.yml" { + log.Infof("Pulling for %s", p) + chart, err := deployment.NewHelmChart(p) + if err != nil { + return err + } + err = chart.Pull() + if err != nil { + return err + } + } + return nil + }) + return err +} + +func init() { + var cmd = &cobra.Command{ + Use: "helm-pull", + Short: "Recursively searches for `helm-chart.yml` files and pulls the specified Helm charts", + Long: "Recursively searches for `helm-chart.yml` files and pulls the specified Helm charts.\n\n"+ + "The Helm charts are stored under the sub-directory `charts/` next to the "+ + "`helm-chart.yml`. These Helm charts are meant to be added to version control so that "+ + "pulling is only needed when really required (e.g. when the chart version changes).", + RunE: runCmdHelmPull, + } + cmd.Flags().StringVar(&args.LocalDeployment, "local-deployment", "", "Local deployment directory. Defaults to current directory") + err := viper.BindPFlags(cmd.Flags()) + if err != nil { + panic(err) + } + + rootCmd.AddCommand(cmd) +} diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index 265bf3e7a..b87fd071d 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -6,7 +6,6 @@ import ( "github.com/codablock/kluctl/pkg/types" "github.com/codablock/kluctl/pkg/utils" "github.com/codablock/kluctl/pkg/yaml" - goversion "github.com/hashicorp/go-version" "io/ioutil" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "os" @@ -46,11 +45,8 @@ func (c *helmChart) withRepoContext(cb func(repoName string) error) error { } if needRepo { - _, err := c.doHelm([]string{"repo", "remove", repoName}, true) - if err != nil { - return err - } - _, err = c.doHelm([]string{"repo", "add", repoName, *c.config.Repo}, false) + _, _ = c.doHelm([]string{"repo", "remove", repoName}, true) + _, err := c.doHelm([]string{"repo", "add", repoName, *c.config.Repo}, false) if err != nil { return err } From 784d6326d8b6ddc2120bede999cf9995fcb7a1f4 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 16 Feb 2022 14:43:51 +0100 Subject: [PATCH 0360/2916] Implement helm-update command --- cmd/kluctl/cmd_helm_pull.go | 8 +- cmd/kluctl/cmd_helm_update.go | 134 ++++++++++++++++++++++++++++++++++ pkg/deployment/helm_chart.go | 64 ++++++++-------- pkg/types/helm_chart.go | 8 +- pkg/utils/looseversion.go | 1 + 5 files changed, 174 insertions(+), 41 deletions(-) create mode 100644 cmd/kluctl/cmd_helm_update.go diff --git a/cmd/kluctl/cmd_helm_pull.go b/cmd/kluctl/cmd_helm_pull.go index 7e59f452f..da7db7b85 100644 --- a/cmd/kluctl/cmd_helm_pull.go +++ b/cmd/kluctl/cmd_helm_pull.go @@ -38,10 +38,10 @@ func init() { var cmd = &cobra.Command{ Use: "helm-pull", Short: "Recursively searches for `helm-chart.yml` files and pulls the specified Helm charts", - Long: "Recursively searches for `helm-chart.yml` files and pulls the specified Helm charts.\n\n"+ - "The Helm charts are stored under the sub-directory `charts/` next to the "+ - "`helm-chart.yml`. These Helm charts are meant to be added to version control so that "+ - "pulling is only needed when really required (e.g. when the chart version changes).", + Long: "Recursively searches for `helm-chart.yml` files and pulls the specified Helm charts.\n\n" + + "The Helm charts are stored under the sub-directory `charts/` next to the " + + "`helm-chart.yml`. These Helm charts are meant to be added to version control so that " + + "pulling is only needed when really required (e.g. when the chart version changes).", RunE: runCmdHelmPull, } cmd.Flags().StringVar(&args.LocalDeployment, "local-deployment", "", "Local deployment directory. Defaults to current directory") diff --git a/cmd/kluctl/cmd_helm_update.go b/cmd/kluctl/cmd_helm_update.go new file mode 100644 index 000000000..40d253831 --- /dev/null +++ b/cmd/kluctl/cmd_helm_update.go @@ -0,0 +1,134 @@ +package main + +import ( + "fmt" + "github.com/codablock/kluctl/cmd/kluctl/args" + "github.com/codablock/kluctl/pkg/deployment" + "github.com/go-git/go-git/v5" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "io/fs" + "path" + "path/filepath" +) + +var ( + upgrade bool + commit bool +) + +func runCmdHelmUpdate(cmd *cobra.Command, args_ []string) error { + rootPath := "." + if args.LocalDeployment != "" { + rootPath = args.LocalDeployment + } + err := filepath.WalkDir(rootPath, func(p string, d fs.DirEntry, err error) error { + fname := path.Base(p) + if fname == "helm-chart.yml" { + chart, err := deployment.NewHelmChart(p) + if err != nil { + return err + } + newVersion, updated, err := chart.CheckUpdate() + if err != nil { + return err + } + if !updated { + return nil + } + log.Infof("Chart %s has new version %s available. Old version is %s.", p, newVersion, *chart.Config.ChartVersion) + + if upgrade { + if chart.Config.SkipUpdate != nil && !*chart.Config.SkipUpdate { + log.Infof("NOT upgrading chart %s as skipUpdate was set to true", p) + return nil + } + + oldVersion := *chart.Config.ChartVersion + chart.Config.ChartVersion = &newVersion + err = chart.Save() + if err != nil { + return err + } + + chartsDir := path.Join(path.Dir(p), "charts") + + // we need to list all files contained inside the charts dir BEFORE doing the pull, so that we later + // know what got deleted + gitFiles := make(map[string]bool) + gitFiles[p] = true + err = filepath.WalkDir(chartsDir, func(p string, d fs.DirEntry, err error) error { + if !d.IsDir() { + gitFiles[p] = true + } + return nil + }) + if err != nil { + return err + } + + log.Infof("Pulling for %s", p) + err = chart.Pull() + if err != nil { + return err + } + + // and now list all files again to catch all new files + err = filepath.WalkDir(chartsDir, func(p string, d fs.DirEntry, err error) error { + if !d.IsDir() { + gitFiles[p] = true + } + return nil + }) + if err != nil { + return err + } + + if commit { + msg := fmt.Sprintf("Updated helm chart %s from %s to %s", path.Dir(p), oldVersion, newVersion) + log.Infof("Committing: %s", msg) + r, err := git.PlainOpen(rootPath) + if err != nil { + return err + } + wt, err := r.Worktree() + if err != nil { + return err + } + for p := range gitFiles { + _, err = wt.Add(p) + if err != nil { + return err + } + } + _, err = wt.Commit(msg, &git.CommitOptions{}) + if err != nil { + return err + } + } + } + } + return nil + }) + return err +} + +func init() { + var cmd = &cobra.Command{ + Use: "helm-update", + Short: "Recursively searches for `helm-chart.yml` files and checks for new available versions", + Long: "Recursively searches for `helm-chart.yml` files and checks for new available versions.\n\n" + + "Optionally performs the actual upgrade and/or add a commit to version control.", + RunE: runCmdHelmUpdate, + } + cmd.Flags().StringVar(&args.LocalDeployment, "local-deployment", "", "Local deployment directory. Defaults to current directory") + cmd.Flags().BoolVar(&upgrade, "upgrade", false, "Write new versions into helm-chart.yml and perform helm-pull afterwards") + cmd.Flags().BoolVar(&commit, "commit", false, "Create a git commit for every updated chart") + err := viper.BindPFlags(cmd.Flags()) + if err != nil { + panic(err) + } + + rootCmd.AddCommand(cmd) +} diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index b87fd071d..aef46d5a5 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -18,7 +18,7 @@ import ( type helmChart struct { configFile string - config *types.HelmChartConfig + Config *types.HelmChartConfig } func NewHelmChart(configFile string) (*helmChart, error) { @@ -30,7 +30,7 @@ func NewHelmChart(configFile string) (*helmChart, error) { hc := &helmChart{ configFile: configFile, - config: &config, + Config: &config, } return hc, nil } @@ -39,14 +39,14 @@ func (c *helmChart) withRepoContext(cb func(repoName string) error) error { needRepo := false repoName := "stable" - if c.config.Repo != nil && *c.config.Repo != "stable" { + if c.Config.Repo != nil && *c.Config.Repo != "stable" { needRepo = true - repoName = fmt.Sprintf("kluctl-%s", utils.Sha256String(*c.config.Repo))[:16] + repoName = fmt.Sprintf("kluctl-%s", utils.Sha256String(*c.Config.Repo))[:16] } if needRepo { _, _ = c.doHelm([]string{"repo", "remove", repoName}, true) - _, err := c.doHelm([]string{"repo", "add", repoName, *c.config.Repo}, false) + _, err := c.doHelm([]string{"repo", "add", repoName, *c.Config.Repo}, false) if err != nil { return err } @@ -63,18 +63,18 @@ func (c *helmChart) withRepoContext(cb func(repoName string) error) error { } func (c *helmChart) GetChartName() (string, error) { - if c.config.Repo != nil && strings.HasPrefix(*c.config.Repo, "oci://") { - s := strings.Split(*c.config.Repo, "/") + if c.Config.Repo != nil && strings.HasPrefix(*c.Config.Repo, "oci://") { + s := strings.Split(*c.Config.Repo, "/") chartName := s[len(s)-1] if m, _ := regexp.MatchString(`[a-zA-Z_-]+`, chartName); !m { - return "", fmt.Errorf("invalid oci chart url: %s", *c.config.Repo) + return "", fmt.Errorf("invalid oci chart url: %s", *c.Config.Repo) } return chartName, nil } - if c.config.ChartName == nil { + if c.Config.ChartName == nil { return "", fmt.Errorf("chartName is missing in helm-chart.yml") } - return *c.config.ChartName, nil + return *c.Config.ChartName, nil } func (c *helmChart) Pull() error { @@ -89,33 +89,29 @@ func (c *helmChart) Pull() error { _ = os.RemoveAll(rmDir) var args []string - if c.config.Repo != nil && strings.HasPrefix(*c.config.Repo, "oci://") { - args = []string{"pull", *c.config.Repo, "--destination", targetDir, "--untar"} - if c.config.ChartVersion != nil { - args = append(args, "--version") - args = append(args, *c.config.ChartVersion) - } + if c.Config.Repo != nil && strings.HasPrefix(*c.Config.Repo, "oci://") { + args = []string{"pull", *c.Config.Repo, "--destination", targetDir, "--untar"} + args = append(args, "--version") + args = append(args, *c.Config.ChartVersion) _, err = c.doHelm(args, false) return err } else { return c.withRepoContext(func(repoName string) error { args = []string{"pull", fmt.Sprintf("%s/%s", repoName, chartName), "--destination", targetDir, "--untar"} - if c.config.ChartVersion != nil { - args = append(args, "--version", *c.config.ChartVersion) - } + args = append(args, "--version", *c.Config.ChartVersion) _, err = c.doHelm(args, false) return err }) } } -func (c *helmChart) CheckUpdate() (string, error) { - if c.config.Repo != nil && strings.HasPrefix(*c.config.Repo, "oci://") { - return "", nil +func (c *helmChart) CheckUpdate() (string, bool, error) { + if c.Config.Repo != nil && strings.HasPrefix(*c.Config.Repo, "oci://") { + return "", false, nil } chartName, err := c.GetChartName() if err != nil { - return "", err + return "", false, err } var latestVersion string err = c.withRepoContext(func(repoName string) error { @@ -144,15 +140,13 @@ func (c *helmChart) CheckUpdate() (string, error) { } sort.Sort(ls) latestVersion = string(ls[len(ls)-1]) - if c.config.ChartVersion != nil && latestVersion == *c.config.ChartVersion { - return nil - } return nil }) if err != nil { - return "", err + return "", false, err } - return latestVersion, nil + updated := latestVersion != *c.Config.ChartVersion + return latestVersion, updated, nil } func (c *helmChart) Render(k *k8s.K8sCluster) error { @@ -163,13 +157,13 @@ func (c *helmChart) Render(k *k8s.K8sCluster) error { dir := path.Dir(c.configFile) chartDir := path.Join(dir, "charts", chartName) valuesPath := path.Join(dir, "helm-values.yml") - outputPath := path.Join(dir, c.config.Output) + outputPath := path.Join(dir, c.Config.Output) - args := []string{"template", c.config.ReleaseName, chartDir} + args := []string{"template", c.Config.ReleaseName, chartDir} namespace := "default" - if c.config.Namespace != nil { - namespace = *c.config.Namespace + if c.Config.Namespace != nil { + namespace = *c.Config.Namespace } if utils.Exists(valuesPath) { @@ -177,7 +171,7 @@ func (c *helmChart) Render(k *k8s.K8sCluster) error { } args = append(args, "-n", namespace) - if c.config.SkipCRDs != nil && *c.config.SkipCRDs { + if c.Config.SkipCRDs != nil && *c.Config.SkipCRDs { args = append(args, "--skip-crds") } else { args = append(args, "--include-crds") @@ -271,3 +265,7 @@ func (c *helmChart) doHelm(args []string, ignoreStdErr bool) ([]byte, error) { } return stdout, nil } + +func (c *helmChart) Save() error { + return yaml.WriteYamlFile(c.configFile, c.Config) +} diff --git a/pkg/types/helm_chart.go b/pkg/types/helm_chart.go index 353ed3aba..c8e0e91f3 100644 --- a/pkg/types/helm_chart.go +++ b/pkg/types/helm_chart.go @@ -1,12 +1,12 @@ package types type HelmChartConfig2 struct { - Repo *string `yaml:"repo,omitempty"` + Repo *string `yaml:"repo" validate:"required"` ChartName *string `yaml:"chartName,omitempty"` - ChartVersion *string `yaml:"chartVersion,omitempty"` - ReleaseName string `yaml:"releaseName"` + ChartVersion *string `yaml:"chartVersion" validate:"required"` + ReleaseName string `yaml:"releaseName" validate:"required"` Namespace *string `yaml:"namespace,omitempty"` - Output string `yaml:"output"` + Output string `yaml:"output" validate:"required"` SkipCRDs *bool `yaml:"skipCRDs,omitempty"` SkipUpdate *bool `yaml:"skipUpdate,omitempty"` } diff --git a/pkg/utils/looseversion.go b/pkg/utils/looseversion.go index 60bb7d9b9..830010820 100644 --- a/pkg/utils/looseversion.go +++ b/pkg/utils/looseversion.go @@ -152,6 +152,7 @@ func (lv LooseVersion) Compare(b LooseVersion) int { } type LooseVersionSlice []LooseVersion + func (x LooseVersionSlice) Less(i, j int) bool { return x[i].Less(x[j], true) } From 374dc4398c696bc26bf898be24f6b73b83cdf4ae Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 16 Feb 2022 14:54:45 +0100 Subject: [PATCH 0361/2916] Implement list-images command --- cmd/kluctl/cmd_list_images.go | 41 +++++++++++++++++++++++++++++++++++ cmd/kluctl/command_result.go | 6 ++--- cmd/kluctl/utils.go | 2 ++ pkg/deployment/images.go | 15 +++++++++++++ 4 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 cmd/kluctl/cmd_list_images.go diff --git a/cmd/kluctl/cmd_list_images.go b/cmd/kluctl/cmd_list_images.go new file mode 100644 index 000000000..ea29e3514 --- /dev/null +++ b/cmd/kluctl/cmd_list_images.go @@ -0,0 +1,41 @@ +package main + +import ( + "github.com/codablock/kluctl/cmd/kluctl/args" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var simple = false + +func runCmdListImages(cmd *cobra.Command, args_ []string) error { + return withProjectCommandContext(func(ctx *commandCtx) error { + result := ctx.images.SeenImages(simple) + return outputYamlResult(args.Output, result, false) + }) +} + +func init() { + var cmd = &cobra.Command{ + Use: "list-images", + Short: "Renders the target and outputs all images used via `images.get_image(...)", + Long: "Renders the target and outputs all images used via `images.get_image(...)`\n\n"+ + "The result is a compatible with yaml files expected by --fixed-images-file.\n\n"+ + "If fixed images (`-f/--fixed-image`) are provided, these are also taken into account, "+ + "as described in for the deploy command.", + RunE: runCmdListImages, + } + args.AddProjectArgs(cmd, true, true, true) + args.AddImageArgs(cmd) + args.AddInclusionArgs(cmd) + args.AddMiscArguments(cmd, args.EnabledMiscArguments{ + Output: true, + }) + cmd.Flags().BoolVar(&simple, "simple", false, "Output a simplified version of the images list") + err := viper.BindPFlags(cmd.Flags()) + if err != nil { + panic(err) + } + + rootCmd.AddCommand(cmd) +} diff --git a/cmd/kluctl/command_result.go b/cmd/kluctl/command_result.go index 581001636..62f88f0da 100644 --- a/cmd/kluctl/command_result.go +++ b/cmd/kluctl/command_result.go @@ -200,9 +200,9 @@ func outputValidateResult(output []string, vr *types.ValidateResult) error { }) } -func outputYamlResult(output []*string, result interface{}, multiDoc bool) error { +func outputYamlResult(output []string, result interface{}, multiDoc bool) error { if len(output) == 0 { - output = []*string{nil} + output = []string{"-"} } var s string if multiDoc { @@ -223,7 +223,7 @@ func outputYamlResult(output []*string, result interface{}, multiDoc bool) error s = x } for _, path := range output { - err := outputResult(path, s) + err := outputResult(&path, s) if err != nil { return err } diff --git a/cmd/kluctl/utils.go b/cmd/kluctl/utils.go index f43864632..e9485f9c0 100644 --- a/cmd/kluctl/utils.go +++ b/cmd/kluctl/utils.go @@ -48,6 +48,7 @@ type commandCtx struct { target *types.Target clusterConfig *types.ClusterConfig k *k8s.K8sCluster + images *deployment.Images deploymentProject *deployment.DeploymentProject deploymentCollection *deployment.DeploymentCollection } @@ -183,6 +184,7 @@ func withProjectTargetCommandContext(p *kluctl_project.KluctlProjectContext, tar target: target, clusterConfig: clusterConfig, k: k, + images: images, deploymentProject: d, deploymentCollection: c, } diff --git a/pkg/deployment/images.go b/pkg/deployment/images.go index 80ca26ad5..187433ca1 100644 --- a/pkg/deployment/images.go +++ b/pkg/deployment/images.go @@ -31,6 +31,21 @@ func (images *Images) AddFixedImage(fi types.FixedImage) { images.fixedImages = append(images.fixedImages, fi) } +func (images *Images) SeenImages(simple bool) []types.FixedImage { + var ret []types.FixedImage + for _, fi := range images.seenImages { + if simple { + ret = append(ret, types.FixedImage{ + Image: fi.Image, + ResultImage: fi.ResultImage, + }) + } else { + ret = append(ret, fi) + } + } + return ret +} + func (images *Images) GetFixedImage(image string, namespace string, deployment string, container string) *string { for i := len(images.fixedImages) - 1; i >= 0; i-- { fi := &images.fixedImages[i] From eb686c682f0f11b5df38d3c5a1d92601e49a3d7b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 16 Feb 2022 15:02:49 +0100 Subject: [PATCH 0362/2916] Implement list-targets command --- cmd/kluctl/cmd_list_targets.go | 38 ++++++++++++++++++++++++++++++++++ pkg/types/git_project.go | 4 ++-- 2 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 cmd/kluctl/cmd_list_targets.go diff --git a/cmd/kluctl/cmd_list_targets.go b/cmd/kluctl/cmd_list_targets.go new file mode 100644 index 000000000..adc87f8a8 --- /dev/null +++ b/cmd/kluctl/cmd_list_targets.go @@ -0,0 +1,38 @@ +package main + +import ( + "github.com/codablock/kluctl/cmd/kluctl/args" + "github.com/codablock/kluctl/pkg/kluctl_project" + "github.com/codablock/kluctl/pkg/types" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +func runCmdListTargets(cmd *cobra.Command, args_ []string) error { + return withKluctlProjectFromArgs(func(p *kluctl_project.KluctlProjectContext) error { + var result []*types.Target + for _, t := range p.DynamicTargets { + result = append(result, t.Target) + } + return outputYamlResult(args.Output, result, false) + }) +} + +func init() { + var cmd = &cobra.Command{ + Use: "list-targets", + Short: "Outputs a yaml list with all target, including dynamic targets", + Long: "Outputs a yaml list with all target, including dynamic targets", + RunE: runCmdListTargets, + } + args.AddProjectArgs(cmd, true, true, false) + args.AddMiscArguments(cmd, args.EnabledMiscArguments{ + Output: true, + }) + err := viper.BindPFlags(cmd.Flags()) + if err != nil { + panic(err) + } + + rootCmd.AddCommand(cmd) +} diff --git a/pkg/types/git_project.go b/pkg/types/git_project.go index 983cf5ce3..d72a12552 100644 --- a/pkg/types/git_project.go +++ b/pkg/types/git_project.go @@ -9,8 +9,8 @@ import ( type GitProject struct { Url git_url.GitUrl `yaml:"url" validate:"required"` - Ref string `yaml:"ref"` - SubDir string `yaml:"subDir"` + Ref string `yaml:"ref,omitempty"` + SubDir string `yaml:"subDir,omitempty"` } func (gp *GitProject) UnmarshalYAML(unmarshal func(interface{}) error) error { From 64efb7aae011df4ef5cead747631b5492212d98b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 16 Feb 2022 15:26:29 +0100 Subject: [PATCH 0363/2916] Implement new-version check --- cmd/kluctl/root.go | 72 ++++++++++++++++++++++++++++++++++++++++++ pkg/version/version.go | 3 ++ 2 files changed, 75 insertions(+) create mode 100644 pkg/version/version.go diff --git a/cmd/kluctl/root.go b/cmd/kluctl/root.go index c66032ae4..bce620942 100644 --- a/cmd/kluctl/root.go +++ b/cmd/kluctl/root.go @@ -16,17 +16,87 @@ limitations under the License. package main import ( + "github.com/codablock/kluctl/pkg/utils" + "github.com/codablock/kluctl/pkg/utils/uo" + "github.com/codablock/kluctl/pkg/version" + "github.com/codablock/kluctl/pkg/yaml" + log "github.com/sirupsen/logrus" + "net/http" "os" + "path" + "strings" + "time" "github.com/spf13/cobra" ) +var noUpdateCheck = false + +const latestReleaseUrl = "https://api.github.com/repos/codablock/kluctl/releases/latest" + + +type VersionCheckState struct { + LastVersionCheck time.Time `yaml:"lastVersionCheck"` +} + +func checkNewVersion() { + if noUpdateCheck { + return + } + if version.Version == "0.0.0" { + return + } + + versionCheckPath := path.Join(utils.GetTmpBaseDir(), "version_check.yml") + var versionCheckState VersionCheckState + err := yaml.ReadYamlFile(versionCheckPath, &versionCheckState) + if err == nil { + if time.Now().Sub(versionCheckState.LastVersionCheck) < time.Hour { + return + } + } + + versionCheckState.LastVersionCheck = time.Now() + _ = yaml.WriteYamlFile(versionCheckPath, &versionCheckState) + + log.Debugf("Checking for new kluctl version") + + r, err := http.Get(latestReleaseUrl) + if err != nil { + return + } + defer r.Body.Close() + + var release uo.UnstructuredObject + err = yaml.ReadYamlStream(r.Body, &release) + if err != nil { + return + } + + latestVersionStr, ok, _ := release.GetNestedString("tag_name") + if !ok { + return + } + if strings.HasPrefix(latestVersionStr, "v") { + latestVersionStr = latestVersionStr[1:] + } + latestVersion := utils.LooseVersion(latestVersionStr) + localVersion := utils.LooseVersion(version.Version) + if localVersion.Less(latestVersion, true) { + log.Warningf("You are using an outdated version (%v) of kluctl. You should update soon to version %v", localVersion, latestVersion) + } +} + // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "kluctl", Short: "Deploy and manage complex deployments on Kubernetes", Long: `The missing glue to put together large Kubernetes deployments, composed of multiple smaller parts (Helm/Kustomize/...) in a manageable and unified way.`, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + checkNewVersion() + return nil + }, // Uncomment the following line if your bare application // has an action associated with it: // Run: func(cmd *cobra.Command, args []string) { }, @@ -43,4 +113,6 @@ func Execute() { func init() { rootCmd.SilenceUsage = true + + rootCmd.Flags().BoolVar(&noUpdateCheck, "no-update-check", false, "Disable update check on startup") } diff --git a/pkg/version/version.go b/pkg/version/version.go new file mode 100644 index 000000000..415100dcc --- /dev/null +++ b/pkg/version/version.go @@ -0,0 +1,3 @@ +package version + +var Version = "0.0.0" From 34c3dd6369001188b0c71a732007bfe5745effb9 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 16 Feb 2022 15:57:10 +0100 Subject: [PATCH 0364/2916] Fix listToMap --- pkg/diff/normalize.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/diff/normalize.go b/pkg/diff/normalize.go index 2ae780304..be09e4f96 100644 --- a/pkg/diff/normalize.go +++ b/pkg/diff/normalize.go @@ -11,14 +11,14 @@ import ( "strings" ) -func listToMap(l []*uo.UnstructuredObject, key string) (map[string]*uo.UnstructuredObject, error) { - m := make(map[string]*uo.UnstructuredObject) +func listToMap(l []*uo.UnstructuredObject, key string) (map[string]interface{}, error) { + m := make(map[string]interface{}) for _, e := range l { kv, found, _ := e.GetNestedString(key) if !found { return nil, fmt.Errorf("%s not found in list element", key) } - m[kv] = e + m[kv] = e.Object } return m, nil } From 9d9866dfa89311f0d1fc6633390629a551e794f2 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 16 Feb 2022 15:57:22 +0100 Subject: [PATCH 0365/2916] Fix pretty table maxWidth calculation --- pkg/utils/prettytable.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/pkg/utils/prettytable.go b/pkg/utils/prettytable.go index 8d2d0afbe..d58834882 100644 --- a/pkg/utils/prettytable.go +++ b/pkg/utils/prettytable.go @@ -29,15 +29,17 @@ func (t *PrettyTable) Render(limitWidths []int) string { maxWidth := func(col int, maxW int) int { w := 0 for _, l := range t.rows { - if len(l[col]) > w { - w = len(l[col]) - } - if maxW != -1 { - if maxW < w { - w = maxW + for _, cl := range strings.Split(l[col], "\n") { + if len(cl) > w { + w = len(cl) } } } + if maxW != -1 { + if maxW < w { + w = maxW + } + } return w } subStr := func(str string, s int, e int) string { @@ -58,7 +60,9 @@ func (t *PrettyTable) Render(limitWidths []int) string { w = limitWidths[i] } widths[i] = maxWidth(i, w) - widthSum += widths[i] + if i != cols - 1 { + widthSum += widths[i] + } } if len(limitWidths) < cols { From 9d9560d053be8c72dc57fac4094e72a8a7441df7 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 17 Feb 2022 01:38:47 +0100 Subject: [PATCH 0366/2916] Move commands into own package --- cmd/kluctl/{ => commands}/cmd_check_image_updates.go | 2 +- cmd/kluctl/{ => commands}/cmd_delete.go | 2 +- cmd/kluctl/{ => commands}/cmd_deploy.go | 2 +- cmd/kluctl/{ => commands}/cmd_diff.go | 2 +- cmd/kluctl/{ => commands}/cmd_downscale.go | 2 +- cmd/kluctl/{ => commands}/cmd_helm_pull.go | 2 +- cmd/kluctl/{ => commands}/cmd_helm_update.go | 2 +- cmd/kluctl/{ => commands}/cmd_list_images.go | 2 +- cmd/kluctl/{ => commands}/cmd_list_targets.go | 6 +++--- cmd/kluctl/{ => commands}/cmd_prune.go | 2 +- cmd/kluctl/{ => commands}/cmd_render.go | 2 +- cmd/kluctl/{ => commands}/cmd_seal.go | 2 +- cmd/kluctl/{ => commands}/cmd_validate.go | 2 +- cmd/kluctl/{ => commands}/command_result.go | 2 +- cmd/kluctl/{ => commands}/confirmation.go | 2 +- cmd/kluctl/{ => commands}/root.go | 2 +- cmd/kluctl/{ => commands}/utils.go | 2 +- cmd/kluctl/main.go | 4 +++- 18 files changed, 22 insertions(+), 20 deletions(-) rename cmd/kluctl/{ => commands}/cmd_check_image_updates.go (99%) rename cmd/kluctl/{ => commands}/cmd_delete.go (99%) rename cmd/kluctl/{ => commands}/cmd_deploy.go (99%) rename cmd/kluctl/{ => commands}/cmd_diff.go (98%) rename cmd/kluctl/{ => commands}/cmd_downscale.go (98%) rename cmd/kluctl/{ => commands}/cmd_helm_pull.go (98%) rename cmd/kluctl/{ => commands}/cmd_helm_update.go (99%) rename cmd/kluctl/{ => commands}/cmd_list_images.go (98%) rename cmd/kluctl/{ => commands}/cmd_list_targets.go (88%) rename cmd/kluctl/{ => commands}/cmd_prune.go (98%) rename cmd/kluctl/{ => commands}/cmd_render.go (98%) rename cmd/kluctl/{ => commands}/cmd_seal.go (99%) rename cmd/kluctl/{ => commands}/cmd_validate.go (99%) rename cmd/kluctl/{ => commands}/command_result.go (99%) rename cmd/kluctl/{ => commands}/confirmation.go (98%) rename cmd/kluctl/{ => commands}/root.go (99%) rename cmd/kluctl/{ => commands}/utils.go (99%) diff --git a/cmd/kluctl/cmd_check_image_updates.go b/cmd/kluctl/commands/cmd_check_image_updates.go similarity index 99% rename from cmd/kluctl/cmd_check_image_updates.go rename to cmd/kluctl/commands/cmd_check_image_updates.go index 6e26ba082..91d916fb9 100644 --- a/cmd/kluctl/cmd_check_image_updates.go +++ b/cmd/kluctl/commands/cmd_check_image_updates.go @@ -1,4 +1,4 @@ -package main +package commands import ( "github.com/codablock/kluctl/cmd/kluctl/args" diff --git a/cmd/kluctl/cmd_delete.go b/cmd/kluctl/commands/cmd_delete.go similarity index 99% rename from cmd/kluctl/cmd_delete.go rename to cmd/kluctl/commands/cmd_delete.go index 309a1789b..f998eb3d8 100644 --- a/cmd/kluctl/cmd_delete.go +++ b/cmd/kluctl/commands/cmd_delete.go @@ -1,4 +1,4 @@ -package main +package commands import ( "fmt" diff --git a/cmd/kluctl/cmd_deploy.go b/cmd/kluctl/commands/cmd_deploy.go similarity index 99% rename from cmd/kluctl/cmd_deploy.go rename to cmd/kluctl/commands/cmd_deploy.go index d053ec4c9..32e56bf21 100644 --- a/cmd/kluctl/cmd_deploy.go +++ b/cmd/kluctl/commands/cmd_deploy.go @@ -1,4 +1,4 @@ -package main +package commands import ( "fmt" diff --git a/cmd/kluctl/cmd_diff.go b/cmd/kluctl/commands/cmd_diff.go similarity index 98% rename from cmd/kluctl/cmd_diff.go rename to cmd/kluctl/commands/cmd_diff.go index 82d0b3ec9..f7349dbf3 100644 --- a/cmd/kluctl/cmd_diff.go +++ b/cmd/kluctl/commands/cmd_diff.go @@ -1,4 +1,4 @@ -package main +package commands import ( "fmt" diff --git a/cmd/kluctl/cmd_downscale.go b/cmd/kluctl/commands/cmd_downscale.go similarity index 98% rename from cmd/kluctl/cmd_downscale.go rename to cmd/kluctl/commands/cmd_downscale.go index 805b84170..496638c4c 100644 --- a/cmd/kluctl/cmd_downscale.go +++ b/cmd/kluctl/commands/cmd_downscale.go @@ -1,4 +1,4 @@ -package main +package commands import ( "fmt" diff --git a/cmd/kluctl/cmd_helm_pull.go b/cmd/kluctl/commands/cmd_helm_pull.go similarity index 98% rename from cmd/kluctl/cmd_helm_pull.go rename to cmd/kluctl/commands/cmd_helm_pull.go index da7db7b85..631f5f3fd 100644 --- a/cmd/kluctl/cmd_helm_pull.go +++ b/cmd/kluctl/commands/cmd_helm_pull.go @@ -1,4 +1,4 @@ -package main +package commands import ( "github.com/codablock/kluctl/cmd/kluctl/args" diff --git a/cmd/kluctl/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go similarity index 99% rename from cmd/kluctl/cmd_helm_update.go rename to cmd/kluctl/commands/cmd_helm_update.go index 40d253831..81f14775f 100644 --- a/cmd/kluctl/cmd_helm_update.go +++ b/cmd/kluctl/commands/cmd_helm_update.go @@ -1,4 +1,4 @@ -package main +package commands import ( "fmt" diff --git a/cmd/kluctl/cmd_list_images.go b/cmd/kluctl/commands/cmd_list_images.go similarity index 98% rename from cmd/kluctl/cmd_list_images.go rename to cmd/kluctl/commands/cmd_list_images.go index ea29e3514..49c925c08 100644 --- a/cmd/kluctl/cmd_list_images.go +++ b/cmd/kluctl/commands/cmd_list_images.go @@ -1,4 +1,4 @@ -package main +package commands import ( "github.com/codablock/kluctl/cmd/kluctl/args" diff --git a/cmd/kluctl/cmd_list_targets.go b/cmd/kluctl/commands/cmd_list_targets.go similarity index 88% rename from cmd/kluctl/cmd_list_targets.go rename to cmd/kluctl/commands/cmd_list_targets.go index adc87f8a8..d1ea881f2 100644 --- a/cmd/kluctl/cmd_list_targets.go +++ b/cmd/kluctl/commands/cmd_list_targets.go @@ -1,4 +1,4 @@ -package main +package commands import ( "github.com/codablock/kluctl/cmd/kluctl/args" @@ -22,8 +22,8 @@ func init() { var cmd = &cobra.Command{ Use: "list-targets", Short: "Outputs a yaml list with all target, including dynamic targets", - Long: "Outputs a yaml list with all target, including dynamic targets", - RunE: runCmdListTargets, + Long: "Outputs a yaml list with all target, including dynamic targets", + RunE: runCmdListTargets, } args.AddProjectArgs(cmd, true, true, false) args.AddMiscArguments(cmd, args.EnabledMiscArguments{ diff --git a/cmd/kluctl/cmd_prune.go b/cmd/kluctl/commands/cmd_prune.go similarity index 98% rename from cmd/kluctl/cmd_prune.go rename to cmd/kluctl/commands/cmd_prune.go index a64930d23..22530c610 100644 --- a/cmd/kluctl/cmd_prune.go +++ b/cmd/kluctl/commands/cmd_prune.go @@ -1,4 +1,4 @@ -package main +package commands import ( "fmt" diff --git a/cmd/kluctl/cmd_render.go b/cmd/kluctl/commands/cmd_render.go similarity index 98% rename from cmd/kluctl/cmd_render.go rename to cmd/kluctl/commands/cmd_render.go index 3872a6f41..c9caf2c80 100644 --- a/cmd/kluctl/cmd_render.go +++ b/cmd/kluctl/commands/cmd_render.go @@ -1,4 +1,4 @@ -package main +package commands import ( "github.com/codablock/kluctl/cmd/kluctl/args" diff --git a/cmd/kluctl/cmd_seal.go b/cmd/kluctl/commands/cmd_seal.go similarity index 99% rename from cmd/kluctl/cmd_seal.go rename to cmd/kluctl/commands/cmd_seal.go index 505a1f231..8840cd26a 100644 --- a/cmd/kluctl/cmd_seal.go +++ b/cmd/kluctl/commands/cmd_seal.go @@ -1,4 +1,4 @@ -package main +package commands import ( "fmt" diff --git a/cmd/kluctl/cmd_validate.go b/cmd/kluctl/commands/cmd_validate.go similarity index 99% rename from cmd/kluctl/cmd_validate.go rename to cmd/kluctl/commands/cmd_validate.go index a86389017..dc94b9f69 100644 --- a/cmd/kluctl/cmd_validate.go +++ b/cmd/kluctl/commands/cmd_validate.go @@ -1,4 +1,4 @@ -package main +package commands import ( "fmt" diff --git a/cmd/kluctl/command_result.go b/cmd/kluctl/commands/command_result.go similarity index 99% rename from cmd/kluctl/command_result.go rename to cmd/kluctl/commands/command_result.go index 62f88f0da..6e476ed42 100644 --- a/cmd/kluctl/command_result.go +++ b/cmd/kluctl/commands/command_result.go @@ -1,4 +1,4 @@ -package main +package commands import ( "bytes" diff --git a/cmd/kluctl/confirmation.go b/cmd/kluctl/commands/confirmation.go similarity index 98% rename from cmd/kluctl/confirmation.go rename to cmd/kluctl/commands/confirmation.go index 778aa8170..423d42cac 100644 --- a/cmd/kluctl/confirmation.go +++ b/cmd/kluctl/commands/confirmation.go @@ -1,4 +1,4 @@ -package main +package commands import ( "fmt" diff --git a/cmd/kluctl/root.go b/cmd/kluctl/commands/root.go similarity index 99% rename from cmd/kluctl/root.go rename to cmd/kluctl/commands/root.go index bce620942..12ad503a0 100644 --- a/cmd/kluctl/root.go +++ b/cmd/kluctl/commands/root.go @@ -13,7 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ -package main +package commands import ( "github.com/codablock/kluctl/pkg/utils" diff --git a/cmd/kluctl/utils.go b/cmd/kluctl/commands/utils.go similarity index 99% rename from cmd/kluctl/utils.go rename to cmd/kluctl/commands/utils.go index e9485f9c0..cbe2ecab9 100644 --- a/cmd/kluctl/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -1,4 +1,4 @@ -package main +package commands import ( "fmt" diff --git a/cmd/kluctl/main.go b/cmd/kluctl/main.go index 3c24062ba..c9805f137 100644 --- a/cmd/kluctl/main.go +++ b/cmd/kluctl/main.go @@ -15,6 +15,8 @@ limitations under the License. */ package main +import "github.com/codablock/kluctl/cmd/kluctl/commands" + func main() { - Execute() + commands.Execute() } From 1821442b7f38482596a64ea64859f60dfffe412d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 18 Feb 2022 13:32:49 +0100 Subject: [PATCH 0367/2916] Introduce include field in DeploymentItemConfig --- pkg/deployment/apply_utils.go | 4 -- pkg/deployment/deployment_collection.go | 36 +++++++------- pkg/deployment/deployment_project.go | 62 ++++++++++++------------- pkg/types/deployment.go | 9 ++++ 4 files changed, 57 insertions(+), 54 deletions(-) diff --git a/pkg/deployment/apply_utils.go b/pkg/deployment/apply_utils.go index 33bba5e3e..aab0cf86b 100644 --- a/pkg/deployment/apply_utils.go +++ b/pkg/deployment/apply_utils.go @@ -330,10 +330,6 @@ func (a *applyUtil) waitHook(ref types.ObjectRef) bool { } func (a *applyUtil) applyDeploymentItem(d *deploymentItem) { - if d.config.Path == nil { - _ = 1 - } - if !d.checkInclusionForDeploy() { a.doLog(d, log.InfoLevel, "Skipping") return diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index e3841ca5c..d33520fd6 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -69,24 +69,22 @@ func (c *DeploymentCollection) createBarrierDummy(project *DeploymentProject) *d return di } -func findDeploymentItemIndex(project *DeploymentProject, diConfig *types.DeploymentItemConfig, indexes map[string]int) (int, *string) { +func findDeploymentItemIndex(project *DeploymentProject, pth string, indexes map[string]int) (int, *string) { var dir2 *string index := 0 - if diConfig.Path != nil { - dir := path.Join(project.dir, *diConfig.Path) - absDir, err := filepath.Abs(dir) - if err != nil { - // we pre-checked directories, so this should not happen - log.Fatal(err) - } + dir := path.Join(project.dir, pth) + absDir, err := filepath.Abs(dir) + if err != nil { + // we pre-checked directories, so this should not happen + log.Fatal(err) + } - if _, ok := indexes[absDir]; !ok { - indexes[absDir] = 0 - } - index, _ = indexes[absDir] - indexes[absDir] = index + 1 - dir2 = &absDir + if _, ok := indexes[absDir]; !ok { + indexes[absDir] = 0 } + index, _ = indexes[absDir] + indexes[absDir] = index + 1 + dir2 = &absDir return index, dir2 } @@ -94,10 +92,7 @@ func (c *DeploymentCollection) collectDeployments(project *DeploymentProject, in var ret []*deploymentItem for i, diConfig := range project.config.Deployments { - if project.isIncludeDeployment(diConfig) { - if diConfig.Barrier != nil && *diConfig.Barrier { - ret = append(ret, c.createBarrierDummy(project)) - } + if diConfig.Include != nil { includedProject, ok := project.includes[i] if !ok { log.Fatalf("Did not find find index %d in project.includes", i) @@ -107,8 +102,11 @@ func (c *DeploymentCollection) collectDeployments(project *DeploymentProject, in return nil, err } ret = append(ret, ret2...) + if diConfig.Barrier != nil && *diConfig.Barrier { + ret = append(ret, c.createBarrierDummy(project)) + } } else { - index, dir2 := findDeploymentItemIndex(project, diConfig, indexes) + index, dir2 := findDeploymentItemIndex(project, *diConfig.Path, indexes) di, err := NewDeploymentItem(project, c, diConfig, dir2, index) if err != nil { return nil, err diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go index 816e76073..8897482f6 100644 --- a/pkg/deployment/deployment_project.go +++ b/pkg/deployment/deployment_project.go @@ -102,14 +102,15 @@ func (p *DeploymentProject) loadConfig(k *k8s.K8sCluster) error { // If there are no explicit tags set, interpret the path as a tag, which allows to // enable/disable single deployments via included/excluded tags - setDefaultTag := func(item *types.DeploymentItemConfig) { - if len(item.Tags) != 0 || item.Path == nil { - return + for _, item := range p.config.Deployments { + if len(item.Tags) != 0 { + continue + } + if item.Path != nil { + item.Tags = []string{path.Base(*item.Path)} + } else if item.Include != nil { + item.Tags = []string{path.Base(*item.Include)} } - item.Tags = []string{path.Base(*item.Path)} - } - for _, di := range p.config.Deployments { - setDefaultTag(di) } err = p.checkDeploymentDirs() @@ -129,54 +130,53 @@ func (p *DeploymentProject) loadConfig(k *k8s.K8sCluster) error { func (p *DeploymentProject) checkDeploymentDirs() error { rootProject := p.getRootProject() for _, di := range p.config.Deployments { - if di.Path == nil { + if di.Path == nil && di.Include == nil { continue } - diDir := path.Join(p.dir, *di.Path) + var pth string + if di.Path != nil { + pth = *di.Path + } else { + pth = *di.Include + } + + diDir := path.Join(p.dir, pth) diDir, err := filepath.Abs(diDir) if err != nil { return err } if !strings.HasPrefix(diDir, rootProject.dir) { - return fmt.Errorf("path is not part of root deployment project: %s", *di.Path) + return fmt.Errorf("path/include is not part of root deployment project: %s", pth) } if !utils.Exists(diDir) { - return fmt.Errorf("deployment directory does not exist: %s", *di.Path) + return fmt.Errorf("deployment directory does not exist: %s", pth) } if !utils.IsDirectory(diDir) { - return fmt.Errorf("deployment path is not a directory: %s", *di.Path) + return fmt.Errorf("deployment path is not a directory: %s", pth) } - } - return nil -} -func (p *DeploymentProject) isIncludeDeployment(di *types.DeploymentItemConfig) bool { - if di.Path == nil { - return false - } - - base := path.Join(p.dir, *di.Path) - - if utils.Exists(path.Join(base, "kustomization.yml")) || utils.Exists(path.Join(base, "kustomization.yaml")) { - return false - } - if !utils.Exists(path.Join(base, "deployment.yml")) { - return false + if di.Path != nil { + pth = path.Join(pth, "kustomization.yml") + } else { + pth = path.Join(pth, "deployment.yml") + } + if !utils.IsFile(pth) { + return fmt.Errorf("%s not found or not a file", pth) + } } - - return true + return nil } func (p *DeploymentProject) loadIncludes(k *k8s.K8sCluster) error { for i, inc := range p.config.Deployments { - if !p.isIncludeDeployment(inc) { + if inc.Include == nil { continue } - incDir := path.Join(p.dir, *inc.Path) + incDir := path.Join(p.dir, *inc.Include) varsCtx := p.VarsCtx.Copy() err := varsCtx.LoadVarsList(k, p.getRenderSearchDirs(), inc.Vars) diff --git a/pkg/types/deployment.go b/pkg/types/deployment.go index 6e4bab1f1..7a5840ef9 100644 --- a/pkg/types/deployment.go +++ b/pkg/types/deployment.go @@ -8,6 +8,7 @@ import ( type DeploymentItemConfig struct { Path *string `yaml:"path,omitempty"` + Include *string `yaml:"include,omitempty"` Tags []string `yaml:"tags,omitempty"` Barrier *bool `yaml:"barrier,omitempty"` Vars []*VarsListItem `yaml:"vars,omitempty"` @@ -17,6 +18,13 @@ type DeploymentItemConfig struct { DeleteObjects []DeleteObjectItemConfig `yaml:"deleteObjects,omitempty"` } +func ValidateDeploymentItemConfig(sl validator.StructLevel) { + s := sl.Current().Interface().(DeploymentItemConfig) + if s.Path != nil && s.Include != nil { + sl.ReportError(s, "path", "Path", "pathinclude", "path and include can not be set at the same time") + } +} + type DeleteObjectItemConfig struct { Group *string `yaml:"group,omitempty"` Version *string `yaml:"version,omitempty"` @@ -124,5 +132,6 @@ type DeploymentProjectConfig struct { func init() { yaml.Validator.RegisterStructValidation(ValidateVarsListItem, VarsListItem{}) + yaml.Validator.RegisterStructValidation(ValidateDeploymentItemConfig, DeploymentItemConfig{}) yaml.Validator.RegisterStructValidation(ValidateDeleteObjectItemConfig, DeleteObjectItemConfig{}) } From ebf5f6340167109cc18f18a289b8368cafe51fc1 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 18 Feb 2022 13:33:37 +0100 Subject: [PATCH 0368/2916] Fix uses of file modes --- pkg/deployment/deployment_item.go | 4 ++-- pkg/deployment/helm_chart.go | 2 +- pkg/git/mirrored_repo.go | 2 +- pkg/jinja2_server/server.go | 4 ++-- pkg/kluctl_project/git.go | 2 +- pkg/kluctl_project/load.go | 2 +- pkg/seal/sealer.go | 2 +- pkg/utils/fs.go | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 8c4827e03..394eee6b3 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -105,7 +105,7 @@ func (di *deploymentItem) render(k *k8s.K8sCluster, wp *utils.WorkerPoolWithErro rootDir := di.project.getRootProject().dir - err := os.MkdirAll(di.renderedDir, 0o777) + err := os.MkdirAll(di.renderedDir, 0o700) if err != nil { return err } @@ -202,7 +202,7 @@ func (di *deploymentItem) resolveSealedSecrets() error { if err != nil { return fmt.Errorf("failed to read source secret file %s: %w", sourcePath, err) } - err = ioutil.WriteFile(targetPath, b, 0o666) + err = ioutil.WriteFile(targetPath, b, 0o600) if err != nil { return fmt.Errorf("failed to write target secret file %s: %w", targetPath, err) } diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index aef46d5a5..eb72fb742 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -212,7 +212,7 @@ func (c *helmChart) Render(k *k8s.K8sCluster) error { return err } - err = ioutil.WriteFile(outputPath, rendered, 0o666) + err = ioutil.WriteFile(outputPath, rendered, 0o600) if err != nil { return err } diff --git a/pkg/git/mirrored_repo.go b/pkg/git/mirrored_repo.go index 93167c618..249327891 100644 --- a/pkg/git/mirrored_repo.go +++ b/pkg/git/mirrored_repo.go @@ -39,7 +39,7 @@ func NewMirroredGitRepo(u git_url.GitUrl) (*MirroredGitRepo, error) { } if !utils.IsDirectory(o.mirrorDir) { - err := os.MkdirAll(o.mirrorDir, 0o777) + err := os.MkdirAll(o.mirrorDir, 0o700) if err != nil { return nil, fmt.Errorf("failed to create mirror repo for %v: %w", u.String(), err) } diff --git a/pkg/jinja2_server/server.go b/pkg/jinja2_server/server.go index 8bd957394..e686ee07a 100644 --- a/pkg/jinja2_server/server.go +++ b/pkg/jinja2_server/server.go @@ -330,7 +330,7 @@ func (js *Jinja2Server) RenderDirectory(rootDir string, searchDirs []string, rel return err } if d.IsDir() { - err = os.MkdirAll(path.Join(targetDir, relPath), 0o777) + err = os.MkdirAll(path.Join(targetDir, relPath), 0o700) if err != nil { return err } @@ -374,7 +374,7 @@ func (js *Jinja2Server) RenderDirectory(rootDir string, searchDirs []string, rel continue } - err = ioutil.WriteFile(job.target, []byte(*job.Result), 0o666) + err = ioutil.WriteFile(job.target, []byte(*job.Result), 0o600) if err != nil { return err } diff --git a/pkg/kluctl_project/git.go b/pkg/kluctl_project/git.go index d54c782ac..961aeea63 100644 --- a/pkg/kluctl_project/git.go +++ b/pkg/kluctl_project/git.go @@ -116,7 +116,7 @@ func (c *KluctlProjectContext) buildCloneDir(u git_url.GitUrl, ref string) (stri } func (c *KluctlProjectContext) cloneGitProject(gitProject types2.ExternalProject, defaultGitSubDir string, doAddInvolvedRepo bool, doLock bool) (result gitProjectInfo, err error) { - err = os.MkdirAll(path.Join(c.TmpDir, "git"), 0o777) + err = os.MkdirAll(path.Join(c.TmpDir, "git"), 0o700) if err != nil { return } diff --git a/pkg/kluctl_project/load.go b/pkg/kluctl_project/load.go index 404c5b469..182ed7311 100644 --- a/pkg/kluctl_project/load.go +++ b/pkg/kluctl_project/load.go @@ -12,7 +12,7 @@ import ( ) func (c *KluctlProjectContext) mergeClustersDirs(mergedClustersDir string, clustersInfos []gitProjectInfo) error { - err := os.MkdirAll(mergedClustersDir, 0o777) + err := os.MkdirAll(mergedClustersDir, 0o700) if err != nil { return err } diff --git a/pkg/seal/sealer.go b/pkg/seal/sealer.go index 9c052c30c..71e34690d 100644 --- a/pkg/seal/sealer.go +++ b/pkg/seal/sealer.go @@ -113,7 +113,7 @@ func (s *Sealer) encryptSecret(secret []byte, secretName string, secretNamespace func (s *Sealer) SealFile(p string, targetFile string) error { baseName := path.Base(targetFile) - err := os.MkdirAll(path.Dir(targetFile), 0o777) + err := os.MkdirAll(path.Dir(targetFile), 0o700) if err != nil { return err } diff --git a/pkg/utils/fs.go b/pkg/utils/fs.go index aea3921b8..c81dcea1d 100644 --- a/pkg/utils/fs.go +++ b/pkg/utils/fs.go @@ -91,7 +91,7 @@ func FsCopyDir(srcFs fs.ReadDirFS, src string, dst string) error { if fds, err = srcFs.ReadDir(src); err != nil { return err } - if err = os.MkdirAll(dst, 0o777); err != nil { + if err = os.MkdirAll(dst, 0o700); err != nil { return err } for _, fd := range fds { From aa78ae6b3f2666813c23bb02f2a3c7653fd331fc Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 18 Feb 2022 13:33:59 +0100 Subject: [PATCH 0369/2916] Allow uint64 in status fields --- pkg/validation/validation.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/validation/validation.go b/pkg/validation/validation.go index 7dcec5c9a..469cecca5 100644 --- a/pkg/validation/validation.go +++ b/pkg/validation/validation.go @@ -163,6 +163,8 @@ func ValidateObject(o *unstructured.Unstructured, notReadyIsError bool) (ret typ v := getStatusField(field, doError, doRaise, def) if i, ok := v.(int64); ok { return i + } else if i, ok := v.(uint64); ok { + return int64(i) } else if i, ok := v.(int); ok { return int64(i) } else { @@ -180,6 +182,9 @@ func ValidateObject(o *unstructured.Unstructured, notReadyIsError bool) (ret typ if i, ok := v.(int64); ok { return i, false, nil } + if i, ok := v.(uint64); ok { + return int64(i), false, nil + } if i, ok := v.(int); ok { return int64(i), false, nil } From 41643a6e5c43c3fc6b0ddaa15a0bc3fdd28e5268 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 18 Feb 2022 13:34:31 +0100 Subject: [PATCH 0370/2916] Fix --include-/--exclude-deployment-dir --- pkg/deployment/deployment_item.go | 4 ++-- pkg/k8s/all_objects.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 394eee6b3..6b381f833 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -224,8 +224,8 @@ func (di *deploymentItem) buildInclusionEntries() []utils.InclusionEntry { values = append(values, utils.InclusionEntry{Type: "tag", Value: t}) } if di.dir != nil { - dir := strings.ReplaceAll(di.relToProjectItemDir, string(os.PathSeparator), "/") - values = append(values, utils.InclusionEntry{Type: "kustomize_dir", Value: dir}) + dir := strings.ReplaceAll(di.relToRootItemDir, string(os.PathSeparator), "/") + values = append(values, utils.InclusionEntry{Type: "deploymentItemDir", Value: dir}) } return values } diff --git a/pkg/k8s/all_objects.go b/pkg/k8s/all_objects.go index fcac2395d..514317d10 100644 --- a/pkg/k8s/all_objects.go +++ b/pkg/k8s/all_objects.go @@ -27,7 +27,7 @@ func GetIncludedObjectsMetadata(k *K8sCluster, verbs []string, labels map[string if annotations != nil { if itemDir, ok := annotations["kluctl.io/kustomize_dir"]; ok { iv = append(iv, utils.InclusionEntry{ - Type: "kustomize_dir", + Type: "deploymentItemDir", Value: itemDir, }) } From 3475f50e3dcecd5a4c4bd758365fb6ba332111b1 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 18 Feb 2022 13:34:46 +0100 Subject: [PATCH 0371/2916] Properly detect and load KUBECONFIG --- pkg/k8s/k8s_cluster.go | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index 74916bce6..cb84ae260 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -21,7 +21,6 @@ import ( _ "k8s.io/client-go/plugin/pkg/client/auth" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" - "k8s.io/client-go/util/homedir" "net/http" "net/url" "path" @@ -79,13 +78,7 @@ func NewK8sCluster(context string, dryRun bool) (*K8sCluster, error) { DryRun: dryRun, } - home := homedir.HomeDir() - if home == "" { - return nil, fmt.Errorf("home dir could not be determined") - } - kubeconfig := path.Join(home, ".kube", "config") - - configLoadingRules := &clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfig} + configLoadingRules := clientcmd.NewDefaultClientConfigLoadingRules() configOverrides := &clientcmd.ConfigOverrides{CurrentContext: context} config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(configLoadingRules, configOverrides).ClientConfig() From 78b482a43e98ca8c4c69df204e58d4bc723f1e8e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 18 Feb 2022 13:35:06 +0100 Subject: [PATCH 0372/2916] Fix hook deletion handling --- pkg/deployment/hooks_util.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/deployment/hooks_util.go b/pkg/deployment/hooks_util.go index 2807e1c49..dd468e805 100644 --- a/pkg/deployment/hooks_util.go +++ b/pkg/deployment/hooks_util.go @@ -92,9 +92,7 @@ func (u *hooksUtil) runHooks(d *deploymentItem, hooks []string) { doLog(log.InfoLevel, "Deleting %d hooks before hook execution", len(deleteBeforeObjects)) } for _, h := range deleteBeforeObjects { - if !doDeleteForPolicy(h) { - return - } + doDeleteForPolicy(h) } waitResults := make(map[types.ObjectRef]bool) From 27f3b5822c4f88df3a54432eb7d93a126b6a0b36 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 18 Feb 2022 13:35:33 +0100 Subject: [PATCH 0373/2916] Make FsCopyDir more generic --- pkg/utils/fs.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pkg/utils/fs.go b/pkg/utils/fs.go index c81dcea1d..d67728d6b 100644 --- a/pkg/utils/fs.go +++ b/pkg/utils/fs.go @@ -84,11 +84,11 @@ func CopyFileStream(src io.Reader, dst string) error { return err } -func FsCopyDir(srcFs fs.ReadDirFS, src string, dst string) error { +func FsCopyDir(srcFs fs.FS, src string, dst string) error { var err error var fds []fs.DirEntry - if fds, err = srcFs.ReadDir(src); err != nil { + if fds, err = fs.ReadDir(srcFs, src); err != nil { return err } if err = os.MkdirAll(dst, 0o700); err != nil { @@ -110,3 +110,7 @@ func FsCopyDir(srcFs fs.ReadDirFS, src string, dst string) error { } return nil } + +func CopyDir(src string, dst string) error { + return FsCopyDir(os.DirFS(src), "", dst) +} From 53814eba330ab7ab8e96505bbd42078ba150a58a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 18 Feb 2022 13:36:33 +0100 Subject: [PATCH 0374/2916] Multiple improvements to UnstructeredObject --- pkg/utils/uo/k8s_fields.go | 34 ++++++++++++++++++++++++++++++---- pkg/utils/uo/nested_fields.go | 8 ++++++++ pkg/utils/uo/uo.go | 21 +++++++++++++++++++++ 3 files changed, 59 insertions(+), 4 deletions(-) diff --git a/pkg/utils/uo/k8s_fields.go b/pkg/utils/uo/k8s_fields.go index 5f2498e76..6ee9044f9 100644 --- a/pkg/utils/uo/k8s_fields.go +++ b/pkg/utils/uo/k8s_fields.go @@ -36,6 +36,10 @@ func (uo *UnstructuredObject) SetK8sGVK(gvk schema.GroupVersionKind) { } } +func (uo *UnstructuredObject) SetK8sGVKs(g string, v string, k string) { + uo.SetK8sGVK(schema.GroupVersionKind{Group: g, Version: v, Kind: k}) +} + func (uo *UnstructuredObject) GetK8sName() string { s, _, err := uo.GetNestedString("metadata", "name") if err != nil { @@ -59,11 +63,19 @@ func (uo *UnstructuredObject) GetK8sNamespace() string { return s } -func (uo *UnstructuredObject) SetK8sNamespace(name string) { - err := uo.SetNestedField(name, "metadata", "namespace") - if err != nil { - log.Fatal(err) +func (uo *UnstructuredObject) SetK8sNamespace(namespace string) { + if namespace != "" { + err := uo.SetNestedField(namespace, "metadata", "namespace") + if err != nil { + log.Fatal(err) + } + } else { + err := uo.RemoveNestedField("metadata", "namespace") + if err != nil { + log.Fatal(err) + } } + } func (uo *UnstructuredObject) GetK8sLabels() map[string]string { @@ -77,6 +89,13 @@ func (uo *UnstructuredObject) GetK8sLabels() map[string]string { return ret } +func (uo *UnstructuredObject) SetK8sLabels(labels map[string]string) { + _ = uo.RemoveNestedField("metadata", "labels") + for k, v := range labels { + uo.SetK8sLabel(k, v) + } +} + func (uo *UnstructuredObject) GetK8sLabel(name string) *string { ret, ok, err := uo.GetNestedString("metadata", "labels", name) if err != nil { @@ -117,6 +136,13 @@ func (uo *UnstructuredObject) GetK8sAnnotation(name string) *string { return &ret } +func (uo *UnstructuredObject) SetK8sAnnotations(annotations map[string]string) { + _ = uo.RemoveNestedField("metadata", "annotations") + for k, v := range annotations { + uo.SetK8sAnnotation(k, v) + } +} + func (uo *UnstructuredObject) SetK8sAnnotation(name string, value string) { err := uo.SetNestedField(value, "metadata", "annotations", name) if err != nil { diff --git a/pkg/utils/uo/nested_fields.go b/pkg/utils/uo/nested_fields.go index 00a39b51e..77b9412e7 100644 --- a/pkg/utils/uo/nested_fields.go +++ b/pkg/utils/uo/nested_fields.go @@ -164,6 +164,14 @@ func (uo *UnstructuredObject) GetNestedObjectList(keys ...interface{}) ([]*Unstr return ret, true, nil } +func (uo *UnstructuredObject) SetNestedObjectList(items []*UnstructuredObject, keys ...interface{}) error { + var l []map[string]interface{} + for _, i := range items { + l = append(l, i.Object) + } + return uo.SetNestedField(l, keys...) +} + func (uo *UnstructuredObject) GetNestedObjectListNoErr(keys ...interface{}) []*UnstructuredObject { l, found, err := uo.GetNestedObjectList(keys...) if !found || err != nil { diff --git a/pkg/utils/uo/uo.go b/pkg/utils/uo/uo.go index c5beaceeb..28df2b5a6 100644 --- a/pkg/utils/uo/uo.go +++ b/pkg/utils/uo/uo.go @@ -1,6 +1,7 @@ package uo import ( + "fmt" "github.com/codablock/kluctl/pkg/yaml" "github.com/jinzhu/copier" log "github.com/sirupsen/logrus" @@ -80,6 +81,22 @@ func FromFile(p string) (*UnstructuredObject, error) { return o, nil } +func FromStringMulti(s string) ([]*UnstructuredObject, error) { + ifs, err := yaml.ReadYamlAllString(s) + if err != nil { + return nil, err + } + var ret []*UnstructuredObject + for _, i := range ifs { + m, ok := i.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("object is not a map") + } + ret = append(ret, FromMap(m)) + } + return ret, nil +} + func (uo *UnstructuredObject) Clone() *UnstructuredObject { var c map[string]interface{} err := copier.CopyWithOption(&c, &uo.Object, copier.Option{ @@ -110,3 +127,7 @@ func (uo *UnstructuredObject) MergeCopy(other *UnstructuredObject) *Unstructured func (uo *UnstructuredObject) NewIterator() *ObjectIterator { return NewObjectIterator(uo.Object) } + +func (uo *UnstructuredObject) Clear() { + uo.Object = make(map[string]interface{}) +} From 8e3e4401c10784be79e9baee852824268431533d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 18 Feb 2022 14:02:15 +0100 Subject: [PATCH 0375/2916] Create base tmp dir on startup --- pkg/utils/utils.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index da51e2942..f461a4c46 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -4,12 +4,22 @@ import ( "crypto/sha256" "encoding/hex" "github.com/jinzhu/copier" + "log" "os" "path" + "sync" ) +var createTmpBaseDirOnce sync.Once + func GetTmpBaseDir() string { dir := path.Join(os.TempDir(), "kluctl") + createTmpBaseDirOnce.Do(func() { + err := os.MkdirAll(dir, 0o700) + if err != nil { + log.Fatal(err) + } + }) return dir } From b79c47fdd842f545688d77b22dc0ffa2e6aa63b7 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 18 Feb 2022 16:09:01 +0100 Subject: [PATCH 0376/2916] Wait for deletion of objects --- cmd/kluctl/commands/cmd_delete.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/kluctl/commands/cmd_delete.go b/cmd/kluctl/commands/cmd_delete.go index f998eb3d8..f9a7d2bab 100644 --- a/cmd/kluctl/commands/cmd_delete.go +++ b/cmd/kluctl/commands/cmd_delete.go @@ -44,7 +44,7 @@ func confirmedDeleteObjects(k *k8s.K8sCluster, refs []types.ObjectRef) (*types.C } } - return k8s.DeleteObjects(k, refs, false) + return k8s.DeleteObjects(k, refs, true) } func init() { From f30c5682057e1c527c1598e4494a1a38ec1b11d6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 18 Feb 2022 16:09:52 +0100 Subject: [PATCH 0377/2916] Implement e2e tests --- cmd/kluctl/commands/root.go | 4 + e2e/cmd_wrapper_test.go | 61 ++++ e2e/cmd_wrapper_util.go | 69 ++++ e2e/deploy_test.go | 32 ++ e2e/external_projects_test.go | 79 +++++ e2e/hooks_test.go | 257 ++++++++++++++ e2e/inclusion_test.go | 244 +++++++++++++ e2e/kind_cluster.go | 160 +++++++++ e2e/project.go | 505 +++++++++++++++++++++++++++ e2e/utils_resources.go | 171 +++++++++ e2e/utils_test.go | 92 +++++ go.mod | 6 +- go.sum | 15 + pkg/deployment/deployment_project.go | 4 +- 14 files changed, 1696 insertions(+), 3 deletions(-) create mode 100644 e2e/cmd_wrapper_test.go create mode 100644 e2e/cmd_wrapper_util.go create mode 100644 e2e/deploy_test.go create mode 100644 e2e/external_projects_test.go create mode 100644 e2e/hooks_test.go create mode 100644 e2e/inclusion_test.go create mode 100644 e2e/kind_cluster.go create mode 100644 e2e/project.go create mode 100644 e2e/utils_resources.go create mode 100644 e2e/utils_test.go diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index 12ad503a0..6500a6ed3 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -102,6 +102,10 @@ composed of multiple smaller parts (Helm/Kustomize/...) in a manageable and unif // Run: func(cmd *cobra.Command, args []string) { }, } +func RootCmd() *cobra.Command { + return rootCmd +} + // Execute adds all child commands to the root command and sets flags appropriately. // This is called by main.main(). It only needs to happen once to the rootCmd. func Execute() { diff --git a/e2e/cmd_wrapper_test.go b/e2e/cmd_wrapper_test.go new file mode 100644 index 000000000..b4a3e10e4 --- /dev/null +++ b/e2e/cmd_wrapper_test.go @@ -0,0 +1,61 @@ +package e2e + +import ( + "flag" + "github.com/codablock/kluctl/cmd/kluctl/commands" + log "github.com/sirupsen/logrus" + "os" + "strings" + "testing" +) + +type arrayFlags []string + +func (i *arrayFlags) String() string { + return strings.Join(*i, " ") +} + +func (i *arrayFlags) Set(value string) error { + *i = append(*i, value) + return nil +} + +var cmdArgs arrayFlags + +func init() { + flag.Var(&cmdArgs, "karg", "") +} + +func doRunCmd() error { + log.Infof("Started wrapper: %s", cmdArgs.String()) + + _, err := os.Stdout.WriteString(stdoutStartMarker + "\n") + if err != nil { + return err + } + _, err = os.Stderr.WriteString(stdoutStartMarker + "\n") + if err != nil { + return err + } + defer func() { + _, _ = os.Stdout.WriteString(stdoutEndMarker + "\n") + _, _ = os.Stderr.WriteString(stdoutEndMarker + "\n") + }() + + cmd := commands.RootCmd() + cmd.SetArgs(cmdArgs) + err = cmd.Execute() + return err +} + +func TestKluctlWrapper(t *testing.T) { + if len(cmdArgs) == 0 { + return + } + + err := doRunCmd() + if err != nil { + t.Fatal(err) + } + os.Exit(0) +} diff --git a/e2e/cmd_wrapper_util.go b/e2e/cmd_wrapper_util.go new file mode 100644 index 000000000..adf0c061d --- /dev/null +++ b/e2e/cmd_wrapper_util.go @@ -0,0 +1,69 @@ +package e2e + +import ( + "bufio" + "bytes" + "io" + "os" + "os/exec" +) + +func runWrappedCmd(testName string, cwd string, env[] string, args []string) (string, string, error) { + executable, err := os.Executable() + if err != nil { + return "", "", err + } + + var args2 []string + args2 = append(args2, "-test.run", testName) + for _, a := range args { + args2 = append(args2, "-karg", a) + } + + cmd := exec.Command(executable, args2...) + cmd.Env = env + cmd.Dir = cwd + + stdoutPipe, err := cmd.StdoutPipe() + if err != nil { + return "", "", err + } + stderrPipe, err := cmd.StderrPipe() + if err != nil { + _ = stdoutPipe.Close() + return "", "", err + } + + stdReader := func(std *os.File, buf io.StringWriter, pipe io.Reader) { + scanner := bufio.NewScanner(pipe) + inMarker := false + for scanner.Scan() { + l := scanner.Text() + if !inMarker { + if l == stdoutStartMarker { + inMarker = true + continue + } + _, _ = std.WriteString(l + "\n") + } else { + if l == stdoutEndMarker { + inMarker = false + continue + } + + l += "\n" + _, _ = std.WriteString(l) + _, _ = buf.WriteString(l) + } + } + } + + stdoutBuf := bytes.NewBuffer(nil) + stderrBuf := bytes.NewBuffer(nil) + + go stdReader(os.Stdout, stdoutBuf, stdoutPipe) + go stdReader(os.Stderr, stderrBuf, stderrPipe) + + err = cmd.Run() + return stdoutBuf.String(), stderrBuf.String(), err +} diff --git a/e2e/deploy_test.go b/e2e/deploy_test.go new file mode 100644 index 000000000..9445d385e --- /dev/null +++ b/e2e/deploy_test.go @@ -0,0 +1,32 @@ +package e2e + +import "testing" + +func TestCommandDeploySimple(t *testing.T) { + t.Parallel() + p := &testProject{} + p.init(t, "simple") + defer p.cleanup() + + k := defaultKindCluster + recreateNamespace(t, k, p.projectName) + + p.updateKindCluster(k, nil) + p.updateTarget("test", k.Name, nil) + + addConfigMapDeployment(p, "cm", nil, resourceOpts{ + name: "cm", + namespace: p.projectName, + }) + p.KluctlMust("deploy", "--yes", "-t", "test") + assertResourceExists(t, k, p.projectName, "ConfigMap/cm") + + addConfigMapDeployment(p, "cm2", nil, resourceOpts{ + name: "cm2", + namespace: p.projectName, + }) + p.KluctlMust("deploy", "--yes", "-t", "test", "--dry-run") + assertResourceNotExists(t, k, p.projectName, "ConfigMap/cm2") + p.KluctlMust("deploy", "--yes", "-t", "test") + assertResourceExists(t, k, p.projectName, "ConfigMap/cm2") +} diff --git a/e2e/external_projects_test.go b/e2e/external_projects_test.go new file mode 100644 index 000000000..1f4637c0e --- /dev/null +++ b/e2e/external_projects_test.go @@ -0,0 +1,79 @@ +package e2e + +import ( + "fmt" + "github.com/codablock/kluctl/pkg/utils/uo" + "testing" + "time" +) + +func doTestProject(t *testing.T, namespace string, p *testProject) { + k := defaultKindCluster + + p.init(t, fmt.Sprintf("project-%s", namespace)) + defer p.cleanup() + + recreateNamespace(t, k, namespace) + + p.updateKindCluster(k, uo.FromMap(map[string]interface{}{ + "cluster_var": "cluster_value1", + })) + p.updateTarget("test", k.Name, uo.FromMap(map[string]interface{}{ + "target_var": "target_value1", + })) + addBusyboxDeployment(p, "busybox", resourceOpts{name: "busybox", namespace: namespace}) + + p.KluctlMust("deploy", "--yes", "-t", "test") + assertReadiness(t, k, namespace, "Deployment/busybox", time.Minute * 5) + + cmData := map[string]string { + "cluster_var": "{{ cluster.cluster_var }}", + "target_var": "{{ args.target_var }}", + } + + assertResourceNotExists(t, k, namespace, "ConfigMap/cm") + addConfigMapDeployment(p, "cm", cmData, resourceOpts{name: "cm", namespace: namespace}) + p.KluctlMust("deploy", "--yes", "-t", "test") + + o := assertResourceExists(t, k, namespace, "ConfigMap/cm") + assertNestedFieldEquals(t, o, "cluster_value1", "data", "cluster_var") + assertNestedFieldEquals(t, o, "target_value1", "data", "target_var") + + p.updateKindCluster(k, uo.FromMap(map[string]interface{}{ + "cluster_var": "cluster_value2", + })) + p.KluctlMust("deploy", "--yes", "-t", "test") + o = assertResourceExists(t, k, namespace, "ConfigMap/cm") + assertNestedFieldEquals(t, o, "cluster_value2", "data", "cluster_var") + assertNestedFieldEquals(t, o, "target_value1", "data", "target_var") + + p.updateTarget("test", k.Name, uo.FromMap(map[string]interface{}{ + "target_var": "target_value2", + })) + p.KluctlMust("deploy", "--yes", "-t", "test") + o = assertResourceExists(t, k, namespace, "ConfigMap/cm") + assertNestedFieldEquals(t, o, "cluster_value2", "data", "cluster_var") + assertNestedFieldEquals(t, o, "target_value2", "data", "target_var") +} + +func TestExternalProjects(t *testing.T) { + testCases := []struct{ + name string + p testProject + }{ + {name: "external-kluctl-project", p: testProject{kluctlProjectExternal: true}}, + {name: "external-clusters-project", p: testProject{clustersExternal: true}}, + {name: "external-deployment-project", p: testProject{deploymentExternal: true}}, + {name: "external-sealed-secrets-project", p: testProject{sealedSecretsExternal: true}}, + {name: "external-all-projects", p: testProject{kluctlProjectExternal: true, clustersExternal: true, deploymentExternal: true, sealedSecretsExternal: true}}, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + doTestProject(t, tc.name, &tc.p) + }) + } +} + diff --git a/e2e/hooks_test.go b/e2e/hooks_test.go new file mode 100644 index 000000000..16a2b0138 --- /dev/null +++ b/e2e/hooks_test.go @@ -0,0 +1,257 @@ +package e2e + +import ( + "encoding/base64" + "fmt" + "github.com/codablock/kluctl/pkg/utils/uo" + "testing" +) + +const hookScript=` +kubectl get configmap -oyaml > /tmp/result.yml +cat /tmp/result.yml +if ! kubectl get secret {{ .name }}-result; then + name="{{ .name }}-result" +else + name="{{ .name }}-result2" +fi +kubectl create secret generic $name --from-file=result=/tmp/result.yml +kubectl delete configmap cm2 || true +` + +func addHookDeployment(p *testProject, dir string, opts resourceOpts, isHelm bool, hook string, hookDeletionPolicy string) { + annotations := make(map[string]string) + if isHelm { + annotations["helm.sh/hook"] = hook + if hookDeletionPolicy != "" { + annotations["helm.sh/hook-deletion-policy"] = hookDeletionPolicy + } + } else { + annotations["kluctl.io/hook"] = hook + } + if hookDeletionPolicy != "" { + annotations["kluctl.io/hook-deletion-policy"] = hookDeletionPolicy + } + + script := renderTemplateHelper(hookScript, map[string]interface{}{ + "name": opts.name, + "namespace": opts.namespace, + }) + + opts.annotations = uo.CopyMergeStrMap(opts.annotations, annotations) + + addJobDeployment(p, dir, opts, "bitnami/kubectl:1.21", []string{"sh"}, []string{"-c", script}) +} + +func addConfigMap(p *testProject, dir string, opts resourceOpts) { + o := uo.New() + o.SetK8sGVKs("", "v1", "ConfigMap") + mergeMetadata(o, opts) + o.SetNestedField(map[string]interface{}{}, "data") + p.addKustomizeResources(dir, []kustomizeResource{ + {fmt.Sprintf("%s.yml", opts.name), o}, + }) +} + +func getHookResult(t *testing.T, p *testProject, k *KindCluster, secretName string) *uo.UnstructuredObject { + o := k.KubectlYamlMust(t, "-n", p.projectName, "get", "secret", secretName) + s, ok, err := o.GetNestedString("data", "result") + if err != nil { + t.Fatal(err) + } + if !ok { + t.Fatalf("result not found") + } + b, err := base64.StdEncoding.DecodeString(s) + if err != nil { + t.Fatal(err) + } + r, err := uo.FromString(string(b)) + if err != nil { + t.Fatal(err) + } + return r +} + +func getHookResultCMNames(t *testing.T, p *testProject, k *KindCluster, second bool) []string { + secretName := "hook-result" + if second { + secretName = "hook-result2" + } + o := getHookResult(t, p, k, secretName) + items, _, _ := o.GetNestedObjectList("items") + var names []string + for _, x := range items { + names = append(names, x.GetK8sName()) + } + return names +} + +func assertHookResultCMName(t *testing.T, p *testProject, k *KindCluster, second bool, cmName string) { + names := getHookResultCMNames(t, p, k, second) + for _, x := range names { + if x == cmName { + return + } + } + t.Fatalf("%s not found in hook result", cmName) +} + +func assertHookResultNotCMName(t *testing.T, p *testProject, k *KindCluster, second bool, cmName string) { + names := getHookResultCMNames(t, p, k, second) + for _, x := range names { + if x == cmName { + t.Fatalf("%s found in hook result", cmName) + } + } +} + +func prepareHookTestProject(t *testing.T, name string, hook string, hookDeletionPolicy string) (*testProject, *KindCluster) { + isDone := false + namespace := fmt.Sprintf("hook-%s", name) + p := &testProject{} + p.init(t, namespace) + defer func() { + if !isDone { + p.cleanup() + } + }() + + k := defaultKindCluster + + recreateNamespace(t, k, namespace) + + p.updateKindCluster(k, nil) + p.updateTarget("test", k.Name, nil) + + addHookDeployment(p, "hook", resourceOpts{name: "hook", namespace: namespace}, false, hook, hookDeletionPolicy) + addConfigMap(p, "hook", resourceOpts{name: "cm1", namespace: namespace}) + + isDone = true + return p, k +} + +func ensureHookExecuted(t *testing.T, p *testProject, k *KindCluster) { + _, _ = k.Kubectl("delete", "-n", p.projectName, "secret", "hook-result", "hook-result2") + p.KluctlMust("deploy", "--yes", "-t", "test") + assertResourceExists(t, k, p.projectName, "ConfigMap/cm1") +} + +func ensureHookNotExecuted(t *testing.T, p *testProject, k *KindCluster) { + _, _ = k.Kubectl("delete", "-n", p.projectName, "secret", "hook-result", "hook-result2") + p.KluctlMust("deploy", "--yes", "-t", "test") + assertResourceNotExists(t, k, p.projectName, "Secret/hook-result") +} + +func TestHooksPreDeployInitial(t *testing.T) { + t.Parallel() + p, k := prepareHookTestProject(t, "pre-deploy-initial", "pre-deploy-initial", "") + defer p.cleanup() + ensureHookExecuted(t, p, k) + assertHookResultNotCMName(t, p, k, false, "cm1") + ensureHookNotExecuted(t, p, k) +} + +func TestHooksPostDeployInitial(t *testing.T) { + t.Parallel() + p, k := prepareHookTestProject(t, "post-deploy-initial", "post-deploy-initial", "") + defer p.cleanup() + ensureHookExecuted(t, p, k) + assertHookResultCMName(t, p, k, false, "cm1") + ensureHookNotExecuted(t, p, k) +} + +func TestHooksPreDeployUpgrade(t *testing.T) { + t.Parallel() + p, k := prepareHookTestProject(t, "pre-deploy-upgrade", "pre-deploy-upgrade", "") + defer p.cleanup() + addConfigMap(p, "hook", resourceOpts{name: "cm2", namespace: p.projectName}) + ensureHookNotExecuted(t, p, k) + k.KubectlMust(t, "delete", "-n", p.projectName, "configmap", "cm1") + ensureHookExecuted(t, p, k) + assertHookResultNotCMName(t, p, k, false, "cm1") + ensureHookExecuted(t, p, k) + assertHookResultCMName(t, p, k, false, "cm1") +} + +func TestHooksPostDeployUpgrade(t *testing.T) { + t.Parallel() + p, k := prepareHookTestProject(t, "post-deploy-upgrade", "post-deploy-upgrade", "") + defer p.cleanup() + addConfigMap(p, "hook", resourceOpts{name: "cm2", namespace: p.projectName}) + ensureHookNotExecuted(t, p, k) + k.KubectlMust(t, "delete", "-n", p.projectName, "configmap", "cm1") + ensureHookExecuted(t, p, k) + assertHookResultCMName(t, p, k, false, "cm1") +} + +func doTestHooksPreDeploy(t *testing.T, name string, hooks string) { + p, k := prepareHookTestProject(t, name, hooks, "") + defer p.cleanup() + addConfigMap(p, "hook", resourceOpts{name: "cm2", namespace: p.projectName}) + ensureHookExecuted(t, p, k) + k.KubectlMust(t, "delete", "-n", p.projectName, "configmap", "cm1") + ensureHookExecuted(t, p, k) + assertHookResultNotCMName(t, p, k, false, "cm1") + ensureHookExecuted(t, p, k) + assertHookResultCMName(t, p, k, false, "cm1") +} + +func doTestHooksPostDeploy(t *testing.T, name string, hooks string) { + p, k := prepareHookTestProject(t, name, hooks, "") + defer p.cleanup() + addConfigMap(p, "hook", resourceOpts{name: "cm2", namespace: p.projectName}) + ensureHookExecuted(t, p, k) + k.KubectlMust(t, "delete", "-n", p.projectName, "configmap", "cm1") + ensureHookExecuted(t, p, k) + assertHookResultCMName(t, p, k, false, "cm1") +} + +func doTestHooksPrePostDeploy(t *testing.T, name string, hooks string) { + p, k := prepareHookTestProject(t, name, hooks, "") + defer p.cleanup() + addConfigMap(p, "hook", resourceOpts{name: "cm2", namespace: p.projectName}) + ensureHookExecuted(t, p, k) + assertHookResultNotCMName(t, p, k, false, "cm1") + assertHookResultNotCMName(t, p, k, false, "cm2") + assertHookResultCMName(t, p, k, true, "cm1") + assertHookResultCMName(t, p, k, true, "cm2") + + ensureHookExecuted(t, p, k) + assertHookResultCMName(t, p, k, false, "cm1") + assertHookResultNotCMName(t, p, k, false, "cm2") + assertHookResultCMName(t, p, k, true, "cm1") + assertHookResultCMName(t, p, k, true, "cm2") +} + +func TestHooksPreDeploy(t *testing.T) { + t.Parallel() + doTestHooksPreDeploy(t, "pre-deploy", "pre-deploy") +} + +func TestHooksPreDeploy2(t *testing.T) { + t.Parallel() + // same as pre-deploy + doTestHooksPreDeploy(t, "pre-deploy2", "pre-deploy-initial,pre-deploy-upgrade") +} + +func TestHooksPostDeploy(t *testing.T) { + t.Parallel() + doTestHooksPostDeploy(t, "post-deploy", "post-deploy") +} + +func TestHooksPostDeploy2(t *testing.T) { + t.Parallel() + // same as post-deploy + doTestHooksPostDeploy(t, "post-deploy2", "post-deploy-initial,post-deploy-upgrade") +} + +func TestHooksPrePostDeploy(t *testing.T) { + t.Parallel() + doTestHooksPrePostDeploy(t, "pre-post-deploy", "pre-deploy,post-deploy") +} + +func TestHooksPrePostDeploy2(t *testing.T) { + t.Parallel() + doTestHooksPrePostDeploy(t, "pre-post-deploy2", "pre-deploy-initial,pre-deploy-upgrade,post-deploy-initial,post-deploy-upgrade") +} diff --git a/e2e/inclusion_test.go b/e2e/inclusion_test.go new file mode 100644 index 000000000..eab1bdcaf --- /dev/null +++ b/e2e/inclusion_test.go @@ -0,0 +1,244 @@ +package e2e + +import ( + "fmt" + "path" + "reflect" + "testing" +) + +func prepareInclusionTestProject(t *testing.T, namespace string, withIncludes bool) (*testProject, *KindCluster) { + isDone := false + + p := &testProject{} + p.init(t, namespace) + defer func() { + if !isDone { + p.cleanup() + } + }() + + k := defaultKindCluster + + recreateNamespace(t, k, p.projectName) + + p.updateKindCluster(k, nil) + p.updateTarget("test", k.Name, nil) + + addConfigMapDeployment(p, "cm1", nil, resourceOpts{name: "cm1", namespace: p.projectName}) + addConfigMapDeployment(p, "cm2", nil, resourceOpts{name: "cm2", namespace: p.projectName}) + addConfigMapDeployment(p, "cm3", nil, resourceOpts{name: "cm3", namespace: p.projectName, tags: []string{"tag1", "tag2"}}) + addConfigMapDeployment(p, "cm4", nil, resourceOpts{name: "cm4", namespace: p.projectName, tags: []string{"tag1", "tag3"}}) + addConfigMapDeployment(p, "cm5", nil, resourceOpts{name: "cm5", namespace: p.projectName, tags: []string{"tag1", "tag4"}}) + addConfigMapDeployment(p, "cm6", nil, resourceOpts{name: "cm6", namespace: p.projectName, tags: []string{"tag1", "tag5"}}) + addConfigMapDeployment(p, "cm7", nil, resourceOpts{name: "cm7", namespace: p.projectName, tags: []string{"tag1", "tag6"}}) + + if withIncludes { + p.addDeploymentInclude(".", "include1", nil) + addConfigMapDeployment(p, "include1/icm1", nil, resourceOpts{name: "icm1", namespace: p.projectName, tags: []string{"itag1", "itag2"}}) + + p.addDeploymentInclude(".", "include2", nil) + addConfigMapDeployment(p, "include2/icm2", nil, resourceOpts{name: "icm2", namespace: p.projectName}) + addConfigMapDeployment(p, "include2/icm3", nil, resourceOpts{name: "icm3", namespace: p.projectName, tags: []string{"itag3", "itag4"}}) + + p.addDeploymentInclude(".", "include3", []string{"itag5"}) + addConfigMapDeployment(p, "include3/icm4", nil, resourceOpts{name: "icm4", namespace: p.projectName}) + addConfigMapDeployment(p, "include3/icm5", nil, resourceOpts{name: "icm5", namespace: p.projectName, tags: []string{"itag5", "itag6"}}) + } + + isDone = true + return p, k +} + +func assertExistsHelper(t *testing.T, p *testProject, k *KindCluster, shouldExists map[string]bool, add []string, remove []string) { + for _, x := range add { + shouldExists[x] = true + } + for _, x := range remove { + if _, ok := shouldExists[x]; ok { + delete(shouldExists, x) + } + } + exists := k.KubectlYamlMust(t, "-n", p.projectName, "get", "configmaps", "-l", fmt.Sprintf("project_name=%s", p.projectName)) + items, _, _ := exists.GetNestedObjectList("items") + found := make(map[string]bool) + for _, x := range items { + found[x.GetK8sName()] = true + } + if !reflect.DeepEqual(shouldExists, found) { + t.Errorf("found != shouldExists") + } +} + +func TestInclusionTags(t *testing.T) { + t.Parallel() + p, k := prepareInclusionTestProject(t, "inclusion-tags", false) + defer p.cleanup() + + shouldExists := make(map[string]bool) + doAssertExists := func(add... string) { + assertExistsHelper(t, p, k, shouldExists, add, nil) + } + + doAssertExists() + + // test default tags + p.KluctlMust("deploy", "--yes", "-t", "test", "-I", "cm1") + doAssertExists("cm1") + p.KluctlMust("deploy", "--yes", "-t", "test", "-I", "cm2") + doAssertExists("cm2") + + // cm3/cm4 don't have default tags, so they should not deploy + p.KluctlMust("deploy", "--yes", "-t", "test", "-I", "cm3") + doAssertExists() + + // but with tag2, at least cm3 should deploy + p.KluctlMust("deploy", "--yes", "-t", "test", "-I", "tag2") + doAssertExists("cm3") + + // let's try 2 tags at once + p.KluctlMust("deploy", "--yes", "-t", "test", "-I", "tag3", "-I", "tag4") + doAssertExists("cm4", "cm5") + + // And now let's try a tag that matches all non-default ones + p.KluctlMust("deploy", "--yes", "-t", "test", "-I", "tag3", "-I", "tag1") + doAssertExists("cm6", "cm7") +} + +func TestExclusionTags(t *testing.T) { + t.Parallel() + p, k := prepareInclusionTestProject(t, "inclusion-exclusion", false) + defer p.cleanup() + + shouldExists := make(map[string]bool) + doAssertExists := func(add... string) { + assertExistsHelper(t, p, k, shouldExists, add, nil) + } + + doAssertExists() + + // Exclude everything except cm1 + p.KluctlMust("deploy", "--yes", "-t", "test", "-E", "cm2", "-E", "tag1") + doAssertExists("cm1") + + // Test that exclusion has precedence over inclusion + p.KluctlMust("deploy", "--yes", "-t", "test", "-E", "cm2", "-E", "tag1", "-I", "cm2") + doAssertExists() + + // Test that exclusion has precedence over inclusion + p.KluctlMust("deploy", "--yes", "-t", "test", "-I", "tag1", "-E", "tag6") + doAssertExists("cm3", "cm4", "cm5", "cm6") +} + +func TestInclusionIncludeDirs(t *testing.T) { + t.Parallel() + p, k := prepareInclusionTestProject(t, "inclusion-dirs", true) + defer p.cleanup() + + shouldExists := make(map[string]bool) + doAssertExists := func(add... string) { + assertExistsHelper(t, p, k, shouldExists, add, nil) + } + + doAssertExists() + + p.KluctlMust("deploy", "--yes", "-t", "test", "-I", "itag1") + doAssertExists("icm1") + + p.KluctlMust("deploy", "--yes", "-t", "test", "-I", "include2") + doAssertExists("icm2", "icm3") + + p.KluctlMust("deploy", "--yes", "-t", "test", "-I", "itag5") + doAssertExists("icm4", "icm5") +} + +func TestInclusionDeploymentDirs(t *testing.T) { + t.Parallel() + p, k := prepareInclusionTestProject(t, "inclusion-kustomize-dirs", true) + defer p.cleanup() + + shouldExists := make(map[string]bool) + doAssertExists := func(add... string) { + assertExistsHelper(t, p, k, shouldExists, add, nil) + } + + doAssertExists() + + p.KluctlMust("deploy", "--yes", "-t", "test", "--include-deployment-dir", "include1/icm1") + doAssertExists("icm1") + + p.KluctlMust("deploy", "--yes", "-t", "test", "--include-deployment-dir", "include2/icm3") + doAssertExists("icm3") + + p.KluctlMust("deploy", "--yes", "-t", "test", "--exclude-deployment-dir", "include3/icm5") + var a []string + for _, x := range p.listDeploymentItemPathes(".", false) { + if x != "icm5" { + a = append(a, path.Base(x)) + } + } + doAssertExists(a...) +} + +func TestInclusionPrune(t *testing.T) { + t.Parallel() + p, k := prepareInclusionTestProject(t, "inclusion-prune", false) + defer p.cleanup() + + shouldExists := make(map[string]bool) + doAssertExists := func(add []string, remove []string) { + assertExistsHelper(t, p, k, shouldExists, add, remove) + } + + doAssertExists(nil, nil) + + p.KluctlMust("deploy", "--yes", "-t", "test") + doAssertExists(p.listDeploymentItemPathes(".", false), nil) + + p.deleteKustomizeDeployment("cm1") + p.KluctlMust("prune", "--yes", "-t", "test", "-I", "non-existent-tag") + doAssertExists(nil, nil) + + p.KluctlMust("prune", "--yes", "-t", "test", "-I", "cm1") + doAssertExists(nil, []string{"cm1"}) + + p.deleteKustomizeDeployment("cm2") + p.KluctlMust("prune", "--yes", "-t", "test", "-E", "cm2") + doAssertExists(nil, nil) + + p.deleteKustomizeDeployment("cm3") + p.KluctlMust("prune", "--yes", "-t", "test", "--exclude-deployment-dir", "cm3") + doAssertExists(nil, []string{"cm2"}) + + p.KluctlMust("prune", "--yes", "-t", "test") + doAssertExists(nil, []string{"cm3"}) +} + +func TestInclusionDelete(t *testing.T) { + t.Parallel() + p, k := prepareInclusionTestProject(t, "inclusion-delete", false) + defer p.cleanup() + + shouldExists := make(map[string]bool) + doAssertExists := func(add []string, remove []string) { + assertExistsHelper(t, p, k, shouldExists, add, remove) + } + + p.KluctlMust("deploy", "--yes", "-t", "test") + doAssertExists(p.listDeploymentItemPathes(".", false), nil) + + p.KluctlMust("delete", "--yes", "-t", "test", "-I", "non-existent-tag") + doAssertExists(nil, nil) + + p.KluctlMust("delete", "--yes", "-t", "test", "-I", "cm1") + doAssertExists(nil, []string{"cm1"}) + + p.KluctlMust("delete", "--yes", "-t", "test", "-E", "cm2") + var a []string + for _, x := range p.listDeploymentItemPathes(".", false) { + if x != "cm1" && x != "cm2"{ + a = append(a, x) + } + } + doAssertExists(nil, a) +} diff --git a/e2e/kind_cluster.go b/e2e/kind_cluster.go new file mode 100644 index 000000000..3d519a7c6 --- /dev/null +++ b/e2e/kind_cluster.go @@ -0,0 +1,160 @@ +package e2e + +import ( + "fmt" + "github.com/codablock/kluctl/pkg/utils/uo" + "github.com/codablock/kluctl/pkg/yaml" + "github.com/pkg/errors" + log "github.com/sirupsen/logrus" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + "os" + "os/exec" + "path" + "sigs.k8s.io/kind/pkg/cluster" + kindcmd "sigs.k8s.io/kind/pkg/cmd" + "testing" + "time" +) + +type KindCluster struct { + Name string + kubeconfig string + config *rest.Config +} + +func CreateKindCluster(name, kubeconfigPath string) (*KindCluster, error) { + provider := cluster.NewProvider(cluster.ProviderWithLogger(kindcmd.NewLogger())) + + c := &KindCluster{ + Name: name, + kubeconfig: kubeconfigPath, + } + + n, err := provider.ListNodes(name) + if err != nil { + return nil, err + } + if len(n) == 0 { + if err := kindCreate(name, kubeconfigPath); err != nil { + return nil, err + } + } + + return c, nil +} + +// Delete removes the cluster from kind. The cluster may not be deleted straight away - this only issues a delete command +func (c *KindCluster) Delete() error { + provider := cluster.NewProvider(cluster.ProviderWithLogger(kindcmd.NewLogger())) + return provider.Delete(c.Name, c.kubeconfig) +} + +// Kubeconfig returns the path to the cluster kubeconfig +func (c *KindCluster) Kubeconfig() string { + return c.kubeconfig +} + +// RESTConfig returns K8s client config to pass to clientset objects +func (c *KindCluster) RESTConfig() *rest.Config { + if c.config == nil { + var err error + c.config, err = clientcmd.BuildConfigFromFlags("", c.Kubeconfig()) + if err != nil { + panic(err) + } + } + return c.config +} + +func (c *KindCluster) Kubectl(args... string) (string, error) { + cmd := exec.Command("kubectl", args...) + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, fmt.Sprintf("KUBECONFIG=%s", c.kubeconfig)) + + stdout, err := cmd.Output() + return string(stdout), err +} + +func (c *KindCluster) KubectlMust(t *testing.T, args... string) string { + stdout, err := c.Kubectl(args...) + if err != nil { + if e, ok := err.(*exec.ExitError); ok { + t.Fatalf("%v, stderr=%s\n", err, string(e.Stderr)) + } else { + t.Fatal(err) + } + } + return stdout +} + +func (c *KindCluster) KubectlYaml(args... string) (*uo.UnstructuredObject, error) { + args = append(args, "-oyaml") + stdout, err := c.Kubectl(args...) + if err != nil { + return nil, err + } + ret := uo.New() + err = yaml.ReadYamlString(stdout, ret) + return ret, err +} + +func (c *KindCluster) KubectlYamlMust(t *testing.T, args... string) *uo.UnstructuredObject { + o, err := c.KubectlYaml(args...) + if err != nil { + if e, ok := err.(*exec.ExitError); ok { + t.Fatalf("%v, stderr=%s\n", err, string(e.Stderr)) + } else { + t.Fatal(err) + } + } + return o +} + +// kindCreate creates the kind cluster. It will retry up to 10 times if cluster creation fails. +func kindCreate(name, kubeconfig string) error { + + fmt.Printf("🌧️ Creating kind cluster %s...\n", name) + provider := cluster.NewProvider(cluster.ProviderWithLogger(kindcmd.NewLogger())) + attempts := 0 + maxAttempts := 10 + for { + err := provider.Create( + name, + cluster.CreateWithNodeImage(""), + cluster.CreateWithRetain(false), + cluster.CreateWithWaitForReady(time.Duration(0)), + cluster.CreateWithKubeconfigPath(kubeconfig), + cluster.CreateWithDisplayUsage(false), + ) + if err == nil { + return nil + } + + fmt.Printf("Error bringing up cluster, will retry (attempt %d): %v", attempts, err) + attempts++ + if attempts >= maxAttempts { + return errors.Wrapf(err, "Error bringing up cluster, exceeded max attempts (%d)", attempts) + } + } +} + +func crateKindCluster(name string) *KindCluster { + tmpdir := path.Join(os.TempDir(), "kluctl-e2e") + err := os.MkdirAll(tmpdir, 0o700) + if err != nil { + log.Fatal(err) + } + kubeconfig := path.Join(tmpdir, fmt.Sprintf("kubeconfig-%s.yml", name)) + k, err := CreateKindCluster(name, kubeconfig) + if err != nil { + log.Fatal(err) + } + return k +} + +func createDefaultKindCluster() *KindCluster { + return crateKindCluster("kluctl-e2e") +} + +var defaultKindCluster = createDefaultKindCluster() diff --git a/e2e/project.go b/e2e/project.go new file mode 100644 index 000000000..217b16340 --- /dev/null +++ b/e2e/project.go @@ -0,0 +1,505 @@ +package e2e + +import ( + "fmt" + "github.com/codablock/kluctl/pkg/utils" + "github.com/codablock/kluctl/pkg/utils/uo" + "github.com/codablock/kluctl/pkg/yaml" + "github.com/go-git/go-git/v5" + log "github.com/sirupsen/logrus" + "io/ioutil" + "os" + "path" + "path/filepath" + "reflect" + "runtime" + "strings" + "testing" +) + +type testProject struct { + t *testing.T + projectName string + + kluctlProjectExternal bool + clustersExternal bool + deploymentExternal bool + sealedSecretsExternal bool + + localClusters *string + localDeployment *string + localSealedSecrets *string + + kubeconfigs []string + + baseDir string +} + +func (p *testProject) init(t *testing.T, projectName string) { + p.t = t + p.projectName = projectName + baseDir, err := ioutil.TempDir(os.TempDir(), "kluctl-e2e-") + if err != nil { + p.t.Fatal(err) + } + p.baseDir = baseDir + + _ = os.MkdirAll(p.getKluctlProjectDir(), 0o700) + _ = os.MkdirAll(path.Join(p.getClustersDir(), "clusters"), 0o700) + _ = os.MkdirAll(path.Join(p.getSealedSecretsDir(), ".sealed-secrets"), 0o700) + _ = os.MkdirAll(p.getDeploymentDir(), 0o700) + + p.gitInit(p.getKluctlProjectDir()) + if p.clustersExternal { + p.gitInit(p.getClustersDir()) + } + if p.deploymentExternal { + p.gitInit(p.getDeploymentDir()) + } + if p.sealedSecretsExternal { + p.gitInit(p.getSealedSecretsDir()) + } + + p.updateKluctlYaml(func(o *uo.UnstructuredObject) error { + if p.clustersExternal { + o.SetNestedField(p.buildFileUrl(p.getClustersDir()), "clusters", "project") + } + if p.deploymentExternal { + o.SetNestedField(p.buildFileUrl(p.getDeploymentDir()), "deployment", "project") + } + if p.sealedSecretsExternal { + o.SetNestedField(p.buildFileUrl(p.getSealedSecretsDir()), "sealedSecrets", "project") + } + return nil + }) + p.updateDeploymentYaml(".", func(c *uo.UnstructuredObject) error { + return nil + }) +} + +func (p *testProject) cleanup() { + if p.baseDir == "" { + return + } + _ = os.RemoveAll(p.baseDir) + p.baseDir = "" +} + +func (p *testProject) gitInit(dir string) { + err := os.MkdirAll(dir, 0o700) + if err != nil { + p.t.Fatal(err) + } + + r, err := git.PlainInit(dir, false) + if err != nil { + p.t.Fatal(err) + } + config, err := r.Config() + if err != nil { + p.t.Fatal(err) + } + wt, err := r.Worktree() + if err != nil { + p.t.Fatal(err) + } + + config.User.Name = "Test User" + config.User.Email = "no@mail.com" + err = utils.Touch(path.Join(dir, ".dummy")) + if err != nil { + p.t.Fatal(err) + } + _, err = wt.Add(".dummy") + if err != nil { + p.t.Fatal(err) + } + _, err = wt.Commit("initial", &git.CommitOptions{}) + if err != nil { + p.t.Fatal(err) + } +} + +func (p *testProject) commitFiles(repo string, add []string, all bool, message string) { + r, err := git.PlainOpen(repo) + if err != nil { + p.t.Fatal(err) + } + wt, err := r.Worktree() + if err != nil { + p.t.Fatal(err) + } + for _, a := range add { + _, err = wt.Add(a) + if err != nil { + p.t.Fatal(err) + } + } + _, err = wt.Commit(message, &git.CommitOptions{ + All: all, + }) + if err != nil { + p.t.Fatal(err) + } +} + +func (p *testProject) commitYaml(y *uo.UnstructuredObject, repo string, pth string, message string) { + err := yaml.WriteYamlFile(path.Join(repo, pth), y) + if err != nil { + p.t.Fatal(err) + } + if message == "" { + relPath, err := filepath.Rel(p.baseDir, repo) + if err != nil { + p.t.Fatal(err) + } + message = fmt.Sprintf("update %s", path.Join(relPath, pth)) + } + p.commitFiles(repo, []string{pth}, false, message) +} + +func (p *testProject) updateYaml(repo string, pth string, update func(o *uo.UnstructuredObject) error, message string) { + if !strings.HasPrefix(repo, p.baseDir) { + p.t.Fatal() + } + o := uo.New() + if utils.Exists(path.Join(repo, pth)) { + err := yaml.ReadYamlFile(path.Join(repo, pth), o) + if err != nil { + p.t.Fatal(err) + } + } + orig := o.Clone() + err := update(o) + if err != nil { + p.t.Fatal(err) + } + if reflect.DeepEqual(o, orig) { + return + } + p.commitYaml(o, repo, pth, message) +} + +func (p *testProject) updateKluctlYaml(update func(o *uo.UnstructuredObject) error) { + p.updateYaml(p.getKluctlProjectDir(), ".kluctl.yml", update, "") +} + +func (p *testProject) updateDeploymentYaml(dir string, update func(o *uo.UnstructuredObject) error) { + p.updateYaml(p.getDeploymentDir(), path.Join(dir, "deployment.yml"), func(o *uo.UnstructuredObject) error { + if dir == "." { + o.SetNestedField(p.projectName, "commonLabels", "project_name") + o.SetNestedField(p.projectName, "deleteByLabels", "project_name") + } + return update(o) + }, "") +} + +func (p *testProject) getDeploymentYaml(dir string) *uo.UnstructuredObject { + o, err := uo.FromFile(path.Join(p.getDeploymentDir(), dir, "deployment.yml")) + if err != nil { + p.t.Fatal(err) + } + return o +} + +func (p *testProject) listDeploymentItemPathes(dir string, fullPath bool) []string { + var ret []string + o := p.getDeploymentYaml(dir) + l, _, err := o.GetNestedObjectList("deployments") + if err != nil { + p.t.Fatal(err) + } + for _, x := range l { + pth, ok, _ := x.GetNestedString("path") + if ok { + x := pth + if fullPath { + x = path.Join(dir, pth) + } + ret = append(ret, x) + } + pth, ok, _ = x.GetNestedString("include") + if ok { + ret = append(ret, p.listDeploymentItemPathes(path.Join(dir, pth), fullPath)...) + } + } + return ret +} + +func (p *testProject) updateKustomizeDeployment(dir string, update func (o *uo.UnstructuredObject, wt *git.Worktree) error) { + r, err := git.PlainOpen(p.getDeploymentDir()) + if err != nil { + p.t.Fatal(err) + } + wt, err := r.Worktree() + if err != nil { + p.t.Fatal(err) + } + + pth := path.Join(dir, "kustomization.yml") + p.updateYaml(p.getDeploymentDir(), pth, func(o *uo.UnstructuredObject) error { + return update(o, wt) + }, fmt.Sprintf("Update kustomization.yml for %s", dir)) +} + +func (p *testProject) updateCluster(name string, context string, vars *uo.UnstructuredObject) { + pth := path.Join("clusters", fmt.Sprintf("%s.yml", name)) + p.updateYaml(p.getClustersDir(), pth, func(o *uo.UnstructuredObject) error { + o.Clear() + o.SetNestedField(name, "cluster", "name") + o.SetNestedField(context, "cluster", "context") + if vars != nil { + o.MergeChild("cluster", vars) + } + return nil + }, fmt.Sprintf("add/update cluster %s", name)) +} + +func (p *testProject) updateKindCluster(k *KindCluster, vars *uo.UnstructuredObject) { + if utils.FindStrInSlice(p.kubeconfigs, k.Kubeconfig()) == -1 { + p.kubeconfigs = append(p.kubeconfigs, k.Kubeconfig()) + } + context, err := k.Kubectl("config", "current-context") + if err != nil { + p.t.Fatal(err) + } + context = strings.TrimSpace(context) + p.updateCluster(k.Name, context, vars) +} + +func (p *testProject) updateTarget(name string, cluster string, args *uo.UnstructuredObject) { + p.updateKluctlYaml(func(o *uo.UnstructuredObject) error { + targets, _, _ := o.GetNestedObjectList("targets") + var newTargets []*uo.UnstructuredObject + for _, t := range targets { + n, _, _ := t.GetNestedString("name") + if n == name { + continue + } + newTargets = append(newTargets, t) + } + n := uo.FromMap(map[string]interface{}{ + "name": name, + "cluster": cluster, + }) + if args != nil { + n.MergeChild("args", args) + } + + newTargets = append(newTargets, n) + _ = o.SetNestedObjectList(newTargets, "targets") + return nil + }) +} + +func (p *testProject) updateDeploymentItems(dir string, update func(items []*uo.UnstructuredObject) []*uo.UnstructuredObject) { + p.updateDeploymentYaml(dir, func(o *uo.UnstructuredObject) error { + items, _, _ := o.GetNestedObjectList("deployments") + items = update(items) + return o.SetNestedField(items, "deployments") + }) +} + +func (p *testProject) addDeploymentItem(dir string, item *uo.UnstructuredObject) { + p.updateDeploymentItems(dir, func(items []*uo.UnstructuredObject) []*uo.UnstructuredObject { + for _, x := range items { + if reflect.DeepEqual(x, item) { + return items + } + } + items = append(items, item) + return items + }) +} + +func (p *testProject) addDeploymentInclude(dir string, includePath string, tags []string) { + n := uo.FromMap(map[string]interface{}{ + "include": includePath, + }) + if len(tags) != 0 { + n.SetNestedField(tags, "tags") + } + p.addDeploymentItem(dir, n) +} + +func (p *testProject) addDeploymentIncludes(dir string) { + var pp []string + for _, x := range strings.Split(dir, "/") { + if x != "." { + p.addDeploymentInclude(path.Join(pp...), x, nil) + } + pp = append(pp, x) + } +} + +func (p *testProject) addKustomizeDeployment(dir string, resources []kustomizeResource, tags []string) { + deploymentDir := path.Dir(dir) + if deploymentDir != "" { + p.addDeploymentIncludes(deploymentDir) + } + + absKustomizeDir := path.Join(p.getDeploymentDir(), dir) + + err := os.MkdirAll(absKustomizeDir, 0o700) + if err != nil { + p.t.Fatal(err) + } + + p.updateKustomizeDeployment(dir, func(o *uo.UnstructuredObject, wt *git.Worktree) error { + o.SetNestedField("kustomize.config.k8s.io/v1beta1", "apiVersion") + o.SetNestedField("Kustomization", "kind") + return nil + }) + + p.addKustomizeResources(dir, resources) + p.updateDeploymentYaml(deploymentDir, func(o *uo.UnstructuredObject) error { + d, _, _ := o.GetNestedObjectList("deployments") + n := uo.FromMap(map[string]interface{}{ + "path": path.Base(dir), + }) + if len(tags) != 0 { + n.SetNestedField(tags, "tags") + } + d = append(d, n) + _ = o.SetNestedObjectList(d, "deployments") + return nil + }) +} + +func (p *testProject) convertInterfaceToList(x interface{}) []interface{} { + var ret []interface{} + if l, ok := x.([]interface{}); ok { + return l + } + if l, ok := x.([]*uo.UnstructuredObject); ok { + for _, y := range l { + ret = append(ret, y) + } + return ret + } + if l, ok := x.([]map[string]interface{}); ok { + for _, y := range l { + ret = append(ret, y) + } + return ret + } + return []interface{}{x} +} + +type kustomizeResource struct { + name string + content interface{} +} + +func (p *testProject) addKustomizeResources(dir string, resources []kustomizeResource) { + p.updateKustomizeDeployment(dir, func(o *uo.UnstructuredObject, wt *git.Worktree) error { + l, _, _ := o.GetNestedList("resources") + for _, r := range resources { + l = append(l, r.name) + x := p.convertInterfaceToList(r.content) + err := yaml.WriteYamlAllFile(path.Join(p.getDeploymentDir(), dir, r.name), x) + if err != nil { + return err + } + _, err = wt.Add(path.Join(dir, r.name)) + if err != nil { + return err + } + } + o.SetNestedField(l, "resources") + return nil + }) +} + +func (p *testProject) deleteKustomizeDeployment(dir string) { + deploymentDir := path.Dir(dir) + p.updateDeploymentItems(deploymentDir, func(items []*uo.UnstructuredObject) []*uo.UnstructuredObject { + var newItems []*uo.UnstructuredObject + for _, item := range items { + pth, _, _ := item.GetNestedString("path") + if pth == path.Base(dir) { + continue + } + newItems = append(newItems, item) + } + return newItems + }) +} + +func (p *testProject) getKluctlProjectDir() string { + return path.Join(p.baseDir, "kluctl-project") +} + +func (p *testProject) getClustersDir() string { + if p.clustersExternal { + return path.Join(p.baseDir, "external-clusters") + } + return p.getKluctlProjectDir() +} + +func (p *testProject) getDeploymentDir() string { + if p.deploymentExternal { + return path.Join(p.baseDir, "external-deployment") + } + return p.getKluctlProjectDir() +} + +func (p *testProject) getSealedSecretsDir() string { + if p.sealedSecretsExternal { + return path.Join(p.baseDir, "external-sealed-secrets") + } + return p.getKluctlProjectDir() +} + +const stdoutStartMarker = "========= stdout start =========" +const stdoutEndMarker = "========= stdout end =========" + +func (p *testProject) Kluctl(argsIn... string) (string, string, error) { + var args []string + args = append(args, argsIn...) + + cwd := "" + if p.kluctlProjectExternal { + args = append(args, "--project-url", p.buildFileUrl(p.getKluctlProjectDir())) + } else { + cwd = p.getKluctlProjectDir() + } + + if p.localClusters != nil { + args = append(args, "--local-clusters", *p.localClusters) + } + if p.localDeployment != nil { + args = append(args, "--local-deployment", *p.localDeployment) + } + if p.localSealedSecrets != nil { + args = append(args, "--local-sealed-secrets", *p.localSealedSecrets) + } + + sep := ":" + if runtime.GOOS == "windows" { + sep = ";" + } + env := os.Environ() + env = append(env, fmt.Sprintf("KUBECONFIG=%s", strings.Join(p.kubeconfigs, sep))) + + log.Infof("Runnning kluctl: %s", strings.Join(args, " ")) + + stdout, stderr, err := runWrappedCmd("TestKluctlWrapper", cwd, env, args) + return stdout, stderr, err +} + +func (p *testProject) KluctlMust(argsIn... string) (string, string) { + stdout, stderr, err := p.Kluctl(argsIn...) + if err != nil { + log.Error(stderr) + p.t.Fatal(fmt.Errorf("kluctl failed: %w", err)) + } + return stdout, stderr +} + +func (p *testProject) buildFileUrl(pth string) string { + if runtime.GOOS == "windows" { + return fmt.Sprintf("file:///%s", pth) + } + return fmt.Sprintf("file://%s", pth) +} diff --git a/e2e/utils_resources.go b/e2e/utils_resources.go new file mode 100644 index 000000000..f7b27580b --- /dev/null +++ b/e2e/utils_resources.go @@ -0,0 +1,171 @@ +package e2e + +import ( + "bytes" + "github.com/codablock/kluctl/pkg/utils/uo" + "text/template" +) + +type resourceOpts struct { + name string + namespace string + tags []string + labels map[string]string + annotations map[string]string +} + +func mergeMetadata(o *uo.UnstructuredObject, opts resourceOpts) { + if opts.name != "" { + o.SetK8sName(opts.name) + } + if opts.namespace != "" { + o.SetK8sNamespace(opts.namespace) + } + if opts.labels != nil { + o.SetK8sLabels(opts.labels) + } + if opts.annotations != nil { + o.SetK8sAnnotations(opts.annotations) + } +} + +func renderTemplateHelper(tmpl string, m map[string]interface{}) string { + t := template.Must(template.New("").Parse(tmpl)) + r := bytes.NewBuffer(nil) + err := t.Execute(r, m) + if err != nil { + panic(err) + } + return r.String() +} + +func renderTemplateObjectHelper(tmpl string, m map[string]interface{}) []*uo.UnstructuredObject { + s := renderTemplateHelper(tmpl, m) + ret, err := uo.FromStringMulti(s) + if err != nil { + panic(err) + } + return ret +} + +func addConfigMapDeployment(p *testProject, dir string, data map[string]string, opts resourceOpts) { + o := uo.New() + o.SetK8sGVKs("", "v1", "ConfigMap") + mergeMetadata(o, opts) + if data != nil { + o.SetNestedField(data, "data") + } + p.addKustomizeDeployment(dir, []kustomizeResource{ + {"configmap.yml", o}, + }, opts.tags) +} + +func addDeploymentHelper(p *testProject, dir string, o *uo.UnstructuredObject, opts resourceOpts) { + rbac := renderTemplateObjectHelper(podRbacTemplate, map[string]interface{}{ + "name": o.GetK8sName(), + "namespace": o.GetK8sNamespace(), + }) + for _, x := range rbac { + mergeMetadata(x, opts) + } + mergeMetadata(o, opts) + + resources := []kustomizeResource{ + {"rbac.yml", rbac}, + {"deploy.yml", o}, + } + + p.addKustomizeDeployment(dir, resources, opts.tags) +} + +func addDeploymentDeployment(p *testProject, dir string, opts resourceOpts, image string, command []string, args []string) { + o := renderTemplateObjectHelper(deploymentTemplate, map[string]interface{}{ + "name": opts.name, + "namespace": opts.namespace, + "image": image, + }) + o[0].SetNestedField(command, "spec", "template", "spec", "containers", 0, "command") + o[0].SetNestedField(args, "spec", "template", "spec", "containers", 0, "args") + addDeploymentHelper(p, dir, o[0], opts) +} + +func addJobDeployment(p *testProject, dir string, opts resourceOpts, image string, command []string, args []string) { + o := renderTemplateObjectHelper(jobTemplate, map[string]interface{}{ + "name": opts.name, + "namespace": opts.namespace, + "image": image, + }) + o[0].SetNestedField(command, "spec", "template", "spec", "containers", 0, "command") + o[0].SetNestedField(args, "spec", "template", "spec", "containers", 0, "args") + addDeploymentHelper(p, dir, o[0], opts) +} + +func addBusyboxDeployment(p *testProject, dir string, opts resourceOpts) { + addDeploymentDeployment(p, dir, opts, "busybox", []string{"sleep"}, []string{"1000"}) +} + +const podRbacTemplate = ` +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .name }} + namespace: {{ .namespace }} +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: "{{ .name }}" + namespace: "{{ .namespace }}" +subjects: +- kind: ServiceAccount + name: "{{ .name }}" + namespace: "{{ .namespace }}" +roleRef: + kind: ClusterRole + name: "cluster-admin" + apiGroup: rbac.authorization.k8s.io +` + +const deploymentTemplate = ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .name }} + namespace: {{ .namespace }} + labels: + app.kubernetes.io/name: {{ .name }} +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: {{ .name }} + template: + metadata: + labels: + app.kubernetes.io/name: {{ .name }} + spec: + terminationGracePeriodSeconds: 0 + serviceAccountName: {{ .name }} + containers: + - image: {{ .image }} + imagePullPolicy: IfNotPresent + name: container +` + +const jobTemplate=` +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ .name }} + namespace: {{ .namespace }} +spec: + template: + spec: + terminationGracePeriodSeconds: 0 + restartPolicy: OnFailure + serviceAccountName: {{ .name }} + containers: + - image: {{ .image }} + imagePullPolicy: IfNotPresent + name: container +` \ No newline at end of file diff --git a/e2e/utils_test.go b/e2e/utils_test.go new file mode 100644 index 000000000..932f9a7e4 --- /dev/null +++ b/e2e/utils_test.go @@ -0,0 +1,92 @@ +package e2e + +import ( + "github.com/codablock/kluctl/pkg/utils/uo" + "github.com/codablock/kluctl/pkg/validation" + log "github.com/sirupsen/logrus" + "os/exec" + "reflect" + "strings" + "testing" + "time" +) + +func recreateNamespace(t* testing.T, k *KindCluster, namespace string) { + _, _ = k.Kubectl("delete", "ns", namespace) + k.KubectlMust(t, "create", "ns", namespace) + k.KubectlMust(t,"label", "ns", namespace, "kluctl-e2e=true") +} + +func deleteTestNamespaces(k *KindCluster) { + _, _ = k.Kubectl("delete", "ns", "-l", "kubectl-e2e=true") +} + +func waitForReadiness(t *testing.T, k *KindCluster, namespace string, resource string, timeout time.Duration) bool { + log.Infof("Waiting for readiness: %s/%s", namespace, resource) + + startTime := time.Now() + for time.Now().Sub(startTime) < timeout { + y, err := k.KubectlYaml("-n", namespace, "get", resource) + if err != nil { + t.Fatal(err) + } + + v := validation.ValidateObject(y.ToUnstructured(), true) + if v.Ready { + return true + } + } + return false +} + +func assertReadiness(t *testing.T, k *KindCluster, namespace string, resource string, timeout time.Duration) { + if !waitForReadiness(t, k, namespace, resource, timeout) { + t.Errorf("%s/%s did not get ready in time", namespace, resource) + } +} + +func assertResourceExists(t *testing.T, k *KindCluster, namespace string, resource string) *uo.UnstructuredObject { + var args []string + if namespace != "" { + args = append(args, "-n", namespace) + } + args = append(args, "get", resource) + return k.KubectlYamlMust(t, args...) +} + +func assertResourceNotExists(t *testing.T, k *KindCluster, namespace string, resource string) { + var args []string + if namespace != "" { + args = append(args, "-n", namespace) + } + args = append(args, "get", resource) + _, err := k.KubectlYaml(args...) + if err == nil { + t.Fatalf("'kubectl get' for %s should not have succeeded", resource) + } else { + ee, ok := err.(*exec.ExitError) + if !ok { + t.Fatal(err) + } + if strings.Index(string(ee.Stderr), "(NotFound)") == -1 { + t.Fatal(err) + } + } +} + +func assertNestedFieldEquals(t *testing.T, o *uo.UnstructuredObject, expected interface{}, keys... interface{}) { + v, ok , err := o.GetNestedField(keys...) + if err != nil { + t.Fatal(err) + } + if !ok { + t.Fatalf("field %s not found in object", uo.KeyListToJsonPath(keys)) + } + if !reflect.DeepEqual(v, expected) { + t.Fatalf("%v != %v", v, expected) + } +} + +func init() { + deleteTestNamespaces(defaultKindCluster) +} diff --git a/go.mod b/go.mod index b9c9cd93a..fcb050235 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( github.com/jinzhu/copier v0.3.5 github.com/mitchellh/go-ps v1.0.0 github.com/ohler55/ojg v1.12.12 + github.com/pkg/errors v0.9.1 github.com/r3labs/diff/v2 v2.15.0 github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v1.3.0 @@ -31,6 +32,7 @@ require ( k8s.io/api v0.23.3 k8s.io/apimachinery v0.23.3 k8s.io/client-go v0.23.3 + sigs.k8s.io/kind v0.11.1 sigs.k8s.io/kustomize/api v0.11.1 sigs.k8s.io/structured-merge-diff/v4 v4.2.1 ) @@ -43,11 +45,13 @@ require ( github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect + github.com/BurntSushi/toml v0.3.1 // indirect github.com/Microsoft/go-winio v0.5.1 // indirect github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 // indirect github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/acomagu/bufpipe v1.0.3 // indirect + github.com/alessio/shellescape v1.4.1 // indirect github.com/containerd/stargz-snapshotter/estargz v0.10.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/docker/cli v20.10.12+incompatible // indirect @@ -55,6 +59,7 @@ require ( github.com/docker/docker v20.10.12+incompatible // indirect github.com/docker/docker-credential-helpers v0.6.4 // indirect github.com/emirpasic/gods v1.12.0 // indirect + github.com/evanphx/json-patch/v5 v5.2.0 // indirect github.com/fatih/color v1.13.0 // indirect github.com/fsnotify/fsnotify v1.5.1 // indirect github.com/gammazero/deque v0.1.0 // indirect @@ -99,7 +104,6 @@ require ( github.com/opencontainers/image-spec v1.0.2-0.20211117181255-693428a734f5 // indirect github.com/pelletier/go-toml v1.9.4 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/sergi/go-diff v1.1.0 // indirect github.com/spf13/afero v1.6.0 // indirect diff --git a/go.sum b/go.sum index 7f9eb4b59..8cb710025 100644 --- a/go.sum +++ b/go.sum @@ -73,6 +73,7 @@ github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+Z github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= @@ -119,6 +120,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= +github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= @@ -151,6 +154,7 @@ github.com/bitnami-labs/pflagenv v0.0.0-20190702160147-b4d9f048d98f/go.mod h1:Lw github.com/bitnami-labs/sealed-secrets v0.17.3 h1:ebus6Rbz9MLhWcHVkFiwDpVkrecvjzOibXdkmJkRhss= github.com/bitnami-labs/sealed-secrets v0.17.3/go.mod h1:EMXakbe/TMdfzATuEH+pTA2K29aZhNgyBNTdQvDc/eU= github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= @@ -281,6 +285,7 @@ github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgU github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= @@ -361,6 +366,8 @@ github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLi github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.2.0 h1:8ozOH5xxoMYDt5/u+yMTsVXydVCbTORFnOOoq2lumco= +github.com/evanphx/json-patch/v5 v5.2.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= @@ -646,6 +653,7 @@ github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg= github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= @@ -704,6 +712,7 @@ github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0U github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -968,6 +977,7 @@ github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/cobra v1.3.0 h1:R7cSvGu+Vv+qX0gW5R/85dx2kmmJT5z5NM8ifdYjdn0= github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4= @@ -982,6 +992,7 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= github.com/spf13/viper v1.10.1 h1:nuJZuYpG7gTj/XqiUwg8bA0cp1+M2mC3J4g5luUYBKk= @@ -1641,6 +1652,7 @@ gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI= gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= @@ -1687,6 +1699,7 @@ k8s.io/api v0.22.1/go.mod h1:bh13rkTp3F1XEaLGykbyRD2QaTTzPm0e/BMd8ptFONY= k8s.io/api v0.23.3 h1:KNrME8KHGr12Ozjf8ytOewKzZh6hl/hHUZeHddT3a38= k8s.io/api v0.23.3/go.mod h1:w258XdGyvCmnBj/vGzQMj6kzdufJZVUwEM1U2fRJwSQ= k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= +k8s.io/apimachinery v0.20.2/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= k8s.io/apimachinery v0.22.1/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= @@ -1745,6 +1758,8 @@ sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyz sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 h1:fD1pz4yfdADVNfFmcP2aBEtudwUQ1AlLnRBALr33v3s= sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= +sigs.k8s.io/kind v0.11.1 h1:pVzOkhUwMBrCB0Q/WllQDO3v14Y+o2V0tFgjTqIUjwA= +sigs.k8s.io/kind v0.11.1/go.mod h1:fRpgVhtqAWrtLB9ED7zQahUimpUXuG/iHT88xYqEGIA= sigs.k8s.io/kustomize/api v0.11.1 h1:/Vutu+gAqVo8skw1xCZrsZD39SN4Adg+z7FrSTw9pds= sigs.k8s.io/kustomize/api v0.11.1/go.mod h1:GZuhith5YcqxIDe0GnRJNx5xxPTjlwaLTt/e+ChUtJA= sigs.k8s.io/kustomize/kyaml v0.13.3 h1:tNNQIC+8cc+aXFTVg+RtQAOsjwUdYBZRAgYOVI3RBc4= diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go index 8897482f6..9a32fb163 100644 --- a/pkg/deployment/deployment_project.go +++ b/pkg/deployment/deployment_project.go @@ -159,9 +159,9 @@ func (p *DeploymentProject) checkDeploymentDirs() error { } if di.Path != nil { - pth = path.Join(pth, "kustomization.yml") + pth = path.Join(diDir, "kustomization.yml") } else { - pth = path.Join(pth, "deployment.yml") + pth = path.Join(diDir, "deployment.yml") } if !utils.IsFile(pth) { return fmt.Errorf("%s not found or not a file", pth) From fc83bb150cae74aa7989c360bd2ed3015a4c1837 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sat, 19 Feb 2022 12:39:03 +0100 Subject: [PATCH 0378/2916] Fix ListMatchingFields --- pkg/utils/uo/jsonpath.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/utils/uo/jsonpath.go b/pkg/utils/uo/jsonpath.go index 267283e23..a7bdbb760 100644 --- a/pkg/utils/uo/jsonpath.go +++ b/pkg/utils/uo/jsonpath.go @@ -69,7 +69,7 @@ func (j *MyJsonPath) ListMatchingFields(o *UnstructuredObject) ([][]interface{}, o = o.Clone() magic := struct{}{} - err := j.exp.Set(o, magic) + err := j.exp.Set(o.Object, magic) if err != nil { return nil, err } From 9ed45b55c062d1b58abbc99669d3eee252f98dc7 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sat, 19 Feb 2022 23:25:26 +0100 Subject: [PATCH 0379/2916] Switch to statically linked python interpreter instead of grpc based server --- cmd/kluctl/commands/cmd_seal.go | 2 +- cmd/kluctl/commands/utils.go | 11 +- go.mod | 4 +- go.sum | 3 - jinja2-server/.gitignore | 8 - jinja2-server/gen-go.sh | 6 - jinja2-server/gen-python.sh | 9 - jinja2-server/jinja2_server.proto | 31 -- jinja2-server/main.py | 40 -- jinja2-server/requirements-dev.txt | 2 - pkg/deployment/deployment_project.go | 6 +- pkg/deployment/external_args.go | 4 +- pkg/jinja2/jinja2.go | 425 ++++++++++++++++++ pkg/jinja2/python_src/.gitignore | 4 + pkg/jinja2/python_src/.python-version | 1 + .../jinja2/python_src}/dict_utils.py | 0 .../jinja2/python_src}/jinja2_cache.py | 0 .../jinja2/python_src/jinja2_renderer.py | 59 ++- .../jinja2/python_src}/jinja2_utils.py | 0 .../jinja2/python_src}/jsonpath_utils.py | 9 +- .../jinja2/python_src}/requirements.txt | 2 - .../jinja2/python_src}/yaml_utils.py | 0 pkg/jinja2/source.go | 79 ++++ pkg/{jinja2_server => jinja2}/vars.go | 16 +- pkg/jinja2_server/.gitignore | 2 - pkg/jinja2_server/server.go | 387 ---------------- pkg/kluctl_project/load_targets.go | 6 +- pkg/kluctl_project/project.go | 8 +- pkg/python/dict.go | 24 + pkg/python/error.go | 10 + pkg/python/interpreter.go | 106 +++++ pkg/python/list.go | 24 + pkg/python/misc.go | 49 ++ pkg/python/object.go | 57 +++ pkg/python/string.go | 19 + pkg/python/variadic.c | 107 +++++ pkg/python/variadic.h | 16 + 37 files changed, 979 insertions(+), 557 deletions(-) delete mode 100644 jinja2-server/.gitignore delete mode 100755 jinja2-server/gen-go.sh delete mode 100755 jinja2-server/gen-python.sh delete mode 100644 jinja2-server/jinja2_server.proto delete mode 100755 jinja2-server/main.py delete mode 100644 jinja2-server/requirements-dev.txt create mode 100644 pkg/jinja2/jinja2.go create mode 100644 pkg/jinja2/python_src/.gitignore create mode 100644 pkg/jinja2/python_src/.python-version rename {jinja2-server => pkg/jinja2/python_src}/dict_utils.py (100%) rename {jinja2-server => pkg/jinja2/python_src}/jinja2_cache.py (100%) rename jinja2-server/jinja2_server.py => pkg/jinja2/python_src/jinja2_renderer.py (66%) rename {jinja2-server => pkg/jinja2/python_src}/jinja2_utils.py (100%) rename {jinja2-server => pkg/jinja2/python_src}/jsonpath_utils.py (93%) rename {jinja2-server => pkg/jinja2/python_src}/requirements.txt (64%) rename {jinja2-server => pkg/jinja2/python_src}/yaml_utils.py (100%) create mode 100644 pkg/jinja2/source.go rename pkg/{jinja2_server => jinja2}/vars.go (92%) delete mode 100644 pkg/jinja2_server/.gitignore delete mode 100644 pkg/jinja2_server/server.go create mode 100644 pkg/python/dict.go create mode 100644 pkg/python/error.go create mode 100644 pkg/python/interpreter.go create mode 100644 pkg/python/list.go create mode 100644 pkg/python/misc.go create mode 100644 pkg/python/object.go create mode 100644 pkg/python/string.go create mode 100644 pkg/python/variadic.c create mode 100644 pkg/python/variadic.h diff --git a/cmd/kluctl/commands/cmd_seal.go b/cmd/kluctl/commands/cmd_seal.go index 8840cd26a..be3754a57 100644 --- a/cmd/kluctl/commands/cmd_seal.go +++ b/cmd/kluctl/commands/cmd_seal.go @@ -35,7 +35,7 @@ func loadSecrets(ctx *commandCtx, target *types.Target, secretsLoader *seal.Secr } for _, source := range secretEntry.Sources { var renderedSource types.SecretSource - err = ctx.kluctlProject.JS.RenderStruct(&renderedSource, &source, ctx.deploymentProject.VarsCtx.Vars) + err = ctx.kluctlProject.J2.RenderStruct(&renderedSource, &source, ctx.deploymentProject.VarsCtx.Vars) if err != nil { return err } diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index cbe2ecab9..92cbc4b8a 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -5,7 +5,7 @@ import ( args "github.com/codablock/kluctl/cmd/kluctl/args" "github.com/codablock/kluctl/pkg/deployment" git_url "github.com/codablock/kluctl/pkg/git/git-url" - "github.com/codablock/kluctl/pkg/jinja2_server" + "github.com/codablock/kluctl/pkg/jinja2" "github.com/codablock/kluctl/pkg/k8s" "github.com/codablock/kluctl/pkg/kluctl_project" "github.com/codablock/kluctl/pkg/types" @@ -24,11 +24,12 @@ func withKluctlProjectFromArgs(cb func(p *kluctl_project.KluctlProjectContext) e return err } } - js, err := jinja2_server.NewJinja2Server() + j2, err := jinja2.NewJinja2() if err != nil { return err } - defer js.Stop() + defer j2.Close() + loadArgs := kluctl_project.LoadKluctlProjectArgs{ ProjectUrl: url, ProjectRef: args.ProjectRef, @@ -38,7 +39,7 @@ func withKluctlProjectFromArgs(cb func(p *kluctl_project.KluctlProjectContext) e LocalSealedSecrets: args.LocalSealedSecrets, FromArchive: args.FromArchive, FromArchiveMetadata: args.FromArchiveMetadata, - JS: js, + J2: j2, } return kluctl_project.LoadKluctlProject(loadArgs, cb) } @@ -91,7 +92,7 @@ func withProjectTargetCommandContext(p *kluctl_project.KluctlProjectContext, tar return err } - varsCtx := jinja2_server.NewVarsCtx(p.JS) + varsCtx := jinja2.NewVarsCtx(p.J2) err = varsCtx.UpdateChildFromStruct("cluster", clusterConfig.Cluster) if err != nil { return err diff --git a/go.mod b/go.mod index fcb050235..84d74798f 100644 --- a/go.mod +++ b/go.mod @@ -26,8 +26,6 @@ require ( github.com/whilp/git-urls v1.0.0 golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - google.golang.org/grpc v1.43.0 - google.golang.org/protobuf v1.27.1 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b k8s.io/api v0.23.3 k8s.io/apimachinery v0.23.3 @@ -125,7 +123,7 @@ require ( golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368 // indirect + google.golang.org/protobuf v1.27.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.66.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect diff --git a/go.sum b/go.sum index 8cb710025..829453e8c 100644 --- a/go.sum +++ b/go.sum @@ -1583,8 +1583,6 @@ google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ6 google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368 h1:Et6SkiuvnBn+SgrSYXs/BrUpGB4mbdwt4R3vaPIlicA= -google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -1619,7 +1617,6 @@ google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.43.0 h1:Eeu7bZtDZ2DpRCsLhUlcrLnvYaMK1Gz86a+hMVvELmM= google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= diff --git a/jinja2-server/.gitignore b/jinja2-server/.gitignore deleted file mode 100644 index c06f787ba..000000000 --- a/jinja2-server/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -__pycache__ - -/venv -/main.bin -/main.build - -jinja2_server_pb2.py -jinja2_server_pb2_grpc.py diff --git a/jinja2-server/gen-go.sh b/jinja2-server/gen-go.sh deleted file mode 100755 index 432d4123d..000000000 --- a/jinja2-server/gen-go.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash - -DIR=$(cd $(dirname $0) && pwd) -cd $DIR/.. - -protoc -I=jinja2-server --go_out=pkg --go-grpc_out=pkg/jinja2_server --go-grpc_opt=paths=source_relative ./jinja2-server/jinja2_server.proto diff --git a/jinja2-server/gen-python.sh b/jinja2-server/gen-python.sh deleted file mode 100755 index 3a5964910..000000000 --- a/jinja2-server/gen-python.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash - -DIR=$(cd $(dirname $0) && pwd) -cd $DIR - -export PATH=$DIR/venv/bin:$PATH - -protoc -I=. --python_out=. ./jinja2_server.proto -python -m grpc_tools.protoc -I=. --python_out=. --grpc_python_out=. jinja2_server.proto diff --git a/jinja2-server/jinja2_server.proto b/jinja2-server/jinja2_server.proto deleted file mode 100644 index 9e0a52368..000000000 --- a/jinja2-server/jinja2_server.proto +++ /dev/null @@ -1,31 +0,0 @@ -syntax = "proto3"; - -package jinja_server; -option go_package = "/jinja2_server"; - -message StringsJob { - string vars = 1; - repeated string search_dirs = 2; - repeated string templates = 3; -} - -message FilesJob { - string vars = 1; - repeated string search_dirs = 2; - repeated string templates = 3; -} - -message SingleResult { - optional string result = 1; - optional string error = 2; -} - -message JobResult { - optional string error = 1; - repeated SingleResult results = 2; -} - -service Jinja2Server { - rpc RenderStrings(StringsJob) returns(JobResult); - rpc RenderFiles(FilesJob) returns(JobResult); -} diff --git a/jinja2-server/main.py b/jinja2-server/main.py deleted file mode 100755 index 2caedd9ac..000000000 --- a/jinja2-server/main.py +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env python -import logging -import sys -from concurrent import futures - -import click -import grpc - -import jinja2_server_pb2_grpc -from jinja2_server import Jinja2Servicer - -logger = logging.getLogger(__name__) - - -@click.group() -@click.option("--debug", is_flag=True) -def cli(debug): - level = logging.INFO - if debug: - level = logging.DEBUG - logging.basicConfig(level=level) - pass - -@cli.command() -def serve(): - executor = futures.ThreadPoolExecutor(max_workers=10) - server = grpc.server(executor) - jinja2_server_pb2_grpc.add_Jinja2ServerServicer_to_server(Jinja2Servicer(), server) - - port = server.add_insecure_port('[::]:0') - try: - server.start() - click.echo("%d" % port) - server.wait_for_termination() - except Exception as e: - logger.warning("Failed to start server: %s", e) - sys.exit(1) - -if __name__ == "__main__": - cli() diff --git a/jinja2-server/requirements-dev.txt b/jinja2-server/requirements-dev.txt deleted file mode 100644 index 08d716cc4..000000000 --- a/jinja2-server/requirements-dev.txt +++ /dev/null @@ -1,2 +0,0 @@ -nuitka==0.6.19.6 -grpcio-tools==1.43.0 diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go index 9a32fb163..77ce9876c 100644 --- a/pkg/deployment/deployment_project.go +++ b/pkg/deployment/deployment_project.go @@ -2,7 +2,7 @@ package deployment import ( "fmt" - "github.com/codablock/kluctl/pkg/jinja2_server" + "github.com/codablock/kluctl/pkg/jinja2" "github.com/codablock/kluctl/pkg/k8s" "github.com/codablock/kluctl/pkg/types" "github.com/codablock/kluctl/pkg/utils" @@ -18,7 +18,7 @@ var kustomizeDirsDeprecatedOnce sync.Once var includesDeprecatedOnce sync.Once type DeploymentProject struct { - VarsCtx *jinja2_server.VarsCtx + VarsCtx *jinja2.VarsCtx dir string sealedSecretsDir string @@ -30,7 +30,7 @@ type DeploymentProject struct { parentProjectInclude *types.DeploymentItemConfig } -func NewDeploymentProject(k *k8s.K8sCluster, varsCtx *jinja2_server.VarsCtx, dir string, sealedSecretsDir string, parentProject *DeploymentProject) (*DeploymentProject, error) { +func NewDeploymentProject(k *k8s.K8sCluster, varsCtx *jinja2.VarsCtx, dir string, sealedSecretsDir string, parentProject *DeploymentProject) (*DeploymentProject, error) { dp := &DeploymentProject{ VarsCtx: varsCtx.Copy(), dir: dir, diff --git a/pkg/deployment/external_args.go b/pkg/deployment/external_args.go index 20c86bbbd..c3423c4ad 100644 --- a/pkg/deployment/external_args.go +++ b/pkg/deployment/external_args.go @@ -2,7 +2,7 @@ package deployment import ( "fmt" - "github.com/codablock/kluctl/pkg/jinja2_server" + "github.com/codablock/kluctl/pkg/jinja2" "github.com/codablock/kluctl/pkg/types" "github.com/codablock/kluctl/pkg/utils/uo" "github.com/codablock/kluctl/pkg/yaml" @@ -40,7 +40,7 @@ func ConvertArgsToVars(args map[string]string) *uo.UnstructuredObject { return vars } -func CheckRequiredDeployArgs(dir string, varsCtx *jinja2_server.VarsCtx, deployArgs *uo.UnstructuredObject) error { +func CheckRequiredDeployArgs(dir string, varsCtx *jinja2.VarsCtx, deployArgs *uo.UnstructuredObject) error { // First try to load the config without templating to avoid getting errors while rendering because required // args were not set. Otherwise we won't be able to iterator through the 'args' array in the deployment.yml // when the rendering error is actually args related. diff --git a/pkg/jinja2/jinja2.go b/pkg/jinja2/jinja2.go new file mode 100644 index 000000000..42b581771 --- /dev/null +++ b/pkg/jinja2/jinja2.go @@ -0,0 +1,425 @@ +package jinja2 + +import "C" +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "github.com/codablock/kluctl/pkg/python" + "github.com/codablock/kluctl/pkg/utils" + "github.com/codablock/kluctl/pkg/utils/uo" + "github.com/gobwas/glob" + "golang.org/x/sync/semaphore" + "io/fs" + "io/ioutil" + "os" + "path" + "path/filepath" + "strings" + "sync" +) + +type Jinja2 struct { + srcDir string + p *python.PythonInterpreter + renderer *python.PyObject + + sem *semaphore.Weighted + globCache map[string]interface{} + globCacheMutex sync.Mutex +} + +type RenderJob struct { + Template string + Result *string + Error error + + target string +} + +type Jinja2Error struct { + error string +} + +func (m *Jinja2Error) Error() string { + return m.error +} + +func NewJinja2() (*Jinja2, error) { + srcDir, err := extractSource() + if err != nil { + return nil, err + } + isOk := false + defer func() { + if !isOk { + _ = os.RemoveAll(srcDir) + } + }() + + p, err := python.NewPythonInterpreter() + if err != nil { + return nil, err + } + defer func() { + if !isOk { + p.Stop() + } + }() + + err = p.AppendSysPath(srcDir) + if err != nil { + return nil, err + } + + j := &Jinja2{ + srcDir: srcDir, + p: p, + sem: semaphore.NewWeighted(1), + globCache: map[string]interface{}{}, + } + + err = p.Run(func() error { + mod := python.PyImport_ImportModule("jinja2_renderer") + if mod == nil { + python.PyErr_Print() + return fmt.Errorf("unexpected error") + } + defer mod.DecRef() + + clazz := mod.GetAttrString("Jinja2Renderer") + if clazz == nil { + python.PyErr_Print() + return fmt.Errorf("unexpected error") + } + defer clazz.DecRef() + + renderer := clazz.CallObject(nil) + if renderer == nil { + python.PyErr_Print() + return fmt.Errorf("unexpected error") + } + + j.renderer = renderer + return nil + }) + if err != nil { + return nil, err + } + + isOk = true + return j, nil +} + +func (j *Jinja2) Close() { + _ = j.p.Run(func() error { + if j.renderer != nil { + j.renderer.DecRef() + j.renderer = nil + } + return nil + }) + + j.p.Stop() + + _ = os.RemoveAll(j.srcDir) +} + +func (j *Jinja2) isMaybeTemplate(template string, searchDirs []string, isString bool) (bool, *string) { + if isString { + if strings.IndexRune(template, '{') == -1 { + return false, &template + } + } else { + for _, s := range searchDirs { + b, err := ioutil.ReadFile(path.Join(s, template)) + if err != nil { + continue + } + if bytes.IndexRune(b, '{') == -1 { + x := string(b) + return false, &x + } else { + return true, nil + } + } + } + return true, nil +} + +func (j *Jinja2) renderHelper(jobs []*RenderJob, searchDirs []string, vars *uo.UnstructuredObject, isString bool) error { + err := j.sem.Acquire(context.Background(), 1) + if err != nil { + return err + } + defer j.sem.Release(1) + + return j.p.Run(func() error { + return j.renderHelperNoLock(jobs, searchDirs, vars, isString) + }) +} + +func (j *Jinja2) renderHelperNoLock(jobs []*RenderJob, searchDirs []string, vars *uo.UnstructuredObject, isString bool) error { + varsStr, err := json.Marshal(vars.Object) + if err != nil { + return err + } + + var processedJobs []*RenderJob + + pyTemplates := python.PyList_New(0) + pySearchDirs := python.PyList_New(0) + pyVars := python.PyUnicode_FromString(string(varsStr)) + + defer func() { + for i := 0; i < pyTemplates.Length(); i++ { + pyTemplates.GetItem(i).DecRef() + } + for i := 0; i < pySearchDirs.Length(); i++ { + pySearchDirs.GetItem(i).DecRef() + } + pyTemplates.DecRef() + pySearchDirs.DecRef() + pyVars.DecRef() + }() + + for _, job := range jobs { + if ist, r := j.isMaybeTemplate(job.Template, searchDirs, isString); !ist { + job.Result = r + continue + } + processedJobs = append(processedJobs, job) + pyTemplates.Append(python.PyUnicode_FromString(job.Template)) + } + if len(processedJobs) == 0 { + return nil + } + + for _, sd := range searchDirs { + pySearchDirs.Append(python.PyUnicode_FromString(sd)) + } + + var pyFuncName *python.PyObject + var pyResult *python.PyObject + if isString { + pyFuncName = python.PyUnicode_FromString("RenderStrings") + } else { + pyFuncName = python.PyUnicode_FromString("RenderFiles") + } + defer pyFuncName.DecRef() + pyResult = python.PyList_FromObject(j.renderer.CallMethodObjArgs(pyFuncName, pyTemplates, pySearchDirs, pyVars)) + if pyResult == nil { + python.PyErr_Print() + return fmt.Errorf("unexpected exception while calling python code: %w", err) + } + + for i := 0; i < pyResult.Length(); i++ { + item := python.PyDict_FromObject(pyResult.GetItem(i)) + r := item.GetItemString("result") + if r != nil { + resultStr := python.PyUnicode_AsUTF8(r) + processedJobs[i].Result = &resultStr + } else { + e := item.GetItemString("error") + if e == nil { + return fmt.Errorf("missing result and error from item at index %d", i) + } + eStr := python.PyUnicode_AsUTF8(e) + processedJobs[i].Error = &Jinja2Error{eStr} + } + } + + return nil +} + +func (j *Jinja2) RenderStrings(jobs []*RenderJob, searchDirs []string, vars *uo.UnstructuredObject) error { + return j.renderHelper(jobs, searchDirs, vars, true) +} + +func (j *Jinja2) RenderFiles(jobs []*RenderJob, searchDirs []string, vars *uo.UnstructuredObject) error { + return j.renderHelper(jobs, searchDirs, vars, false) +} + +func (j *Jinja2) RenderString(template string, searchDirs []string, vars *uo.UnstructuredObject) (string, error) { + jobs := []*RenderJob{{ + Template: template, + }} + err := j.RenderStrings(jobs, searchDirs, vars) + if err != nil { + return "", err + } + if jobs[0].Error != nil { + return "", jobs[0].Error + } + return *jobs[0].Result, nil +} + +func (j *Jinja2) RenderFile(template string, searchDirs []string, vars *uo.UnstructuredObject) (string, error) { + jobs := []*RenderJob{{ + Template: template, + }} + err := j.RenderFiles(jobs, searchDirs, vars) + if err != nil { + return "", err + } + if jobs[0].Error != nil { + return "", jobs[0].Error + } + return *jobs[0].Result, nil +} + +func (j *Jinja2) RenderStruct(dst interface{}, src interface{}, vars *uo.UnstructuredObject) error { + m, err := uo.FromStruct(src) + if err != nil { + return err + } + + type pk struct { + parent interface{} + key interface{} + } + + var jobs []*RenderJob + var fields []pk + err = m.NewIterator().IterateLeafs(func(it *uo.ObjectIterator) error { + value := it.Value() + if s, ok := value.(string); ok { + jobs = append(jobs, &RenderJob{Template: s}) + fields = append(fields, pk{ + parent: it.Parent(), + key: it.Key(), + }) + } + return nil + }) + if err != nil { + return err + } + + err = j.RenderStrings(jobs, nil, vars) + if err != nil { + return err + } + + var errors []error + for i, j := range jobs { + if j.Error != nil { + errors = append(errors, err) + } + + err = uo.SetChild(fields[i].parent, fields[i].key, *j.Result) + if err != nil { + return err + } + } + if len(errors) != 0 { + return utils.NewErrorList(errors) + } + + err = m.ToStruct(dst) + if err != nil { + return err + } + return nil +} + +func (j *Jinja2) getGlob(pattern string) (glob.Glob, error) { + j.globCacheMutex.Lock() + defer j.globCacheMutex.Unlock() + + g, ok := j.globCache[pattern] + if ok { + if g2, ok := g.(glob.Glob); ok { + return g2, nil + } else { + return nil, g2.(error) + } + } + g, err := glob.Compile(pattern) + if err != nil { + j.globCache[pattern] = err + return nil, err + } + j.globCache[pattern] = g + return g.(glob.Glob), nil +} +func (j *Jinja2) needsRender(path string, excludedPatterns []string) bool { + for _, p := range excludedPatterns { + g, err := j.getGlob(p) + if err != nil { + return false + } + if g.Match(path) { + return false + } + } + return true +} + +func (j *Jinja2) RenderDirectory(rootDir string, searchDirs []string, relSourceDir string, excludePatterns []string, subdir string, targetDir string, vars *uo.UnstructuredObject) error { + walkDir := path.Join(rootDir, relSourceDir, subdir) + + var jobs []*RenderJob + + err := filepath.WalkDir(walkDir, func(p string, d fs.DirEntry, err error) error { + relPath, err := filepath.Rel(walkDir, p) + if err != nil { + return err + } + if d.IsDir() { + err = os.MkdirAll(path.Join(targetDir, relPath), 0o700) + if err != nil { + return err + } + return nil + } + + sourcePath := path.Clean(path.Join(subdir, relPath)) + targetPath := path.Join(targetDir, relPath) + + if strings.Index(sourcePath, ".sealme") != -1 { + sourcePath += "" + } + + if !j.needsRender(sourcePath, excludePatterns) { + return utils.CopyFile(p, targetPath) + } + + // jinja2 templates are using / even on Windows + sourcePath = strings.ReplaceAll(sourcePath, "\\", "/") + + job := &RenderJob{ + Template: sourcePath, + target: targetPath, + } + jobs = append(jobs, job) + return nil + }) + if err != nil { + return err + } + + err = j.RenderFiles(jobs, searchDirs, vars) + if err != nil { + return err + } + + var errors []error + for _, job := range jobs { + if job.Error != nil { + errors = append(errors, job.Error) + continue + } + + err = ioutil.WriteFile(job.target, []byte(*job.Result), 0o600) + if err != nil { + return err + } + } + if len(errors) != 0 { + return utils.NewErrorList(errors) + } + + return nil +} diff --git a/pkg/jinja2/python_src/.gitignore b/pkg/jinja2/python_src/.gitignore new file mode 100644 index 000000000..c4095b70b --- /dev/null +++ b/pkg/jinja2/python_src/.gitignore @@ -0,0 +1,4 @@ +__pycache__ + +/venv +/wheel diff --git a/pkg/jinja2/python_src/.python-version b/pkg/jinja2/python_src/.python-version new file mode 100644 index 000000000..30291cba2 --- /dev/null +++ b/pkg/jinja2/python_src/.python-version @@ -0,0 +1 @@ +3.10.0 diff --git a/jinja2-server/dict_utils.py b/pkg/jinja2/python_src/dict_utils.py similarity index 100% rename from jinja2-server/dict_utils.py rename to pkg/jinja2/python_src/dict_utils.py diff --git a/jinja2-server/jinja2_cache.py b/pkg/jinja2/python_src/jinja2_cache.py similarity index 100% rename from jinja2-server/jinja2_cache.py rename to pkg/jinja2/python_src/jinja2_cache.py diff --git a/jinja2-server/jinja2_server.py b/pkg/jinja2/python_src/jinja2_renderer.py similarity index 66% rename from jinja2-server/jinja2_server.py rename to pkg/jinja2/python_src/jinja2_renderer.py index 0ec2e07c8..b5ca3411e 100644 --- a/jinja2-server/jinja2_server.py +++ b/pkg/jinja2/python_src/jinja2_renderer.py @@ -1,24 +1,19 @@ import base64 import json -from concurrent.futures import ThreadPoolExecutor from jinja2 import StrictUndefined, FileSystemLoader -import jinja2_server_pb2 -import jinja2_server_pb2_grpc from dict_utils import merge_dict from jinja2_cache import KluctlBytecodeCache from jinja2_utils import KluctlJinja2Environment, add_jinja2_filters, extract_template_error -from yaml_utils import yaml_load jinja2_cache = KluctlBytecodeCache(max_cache_files=10000) begin_placeholder = "XXXXXbegin_get_image_" end_placeholder = "_end_get_imageXXXXX" -class Jinja2Servicer(jinja2_server_pb2_grpc.Jinja2ServerServicer): - def __init__(self): - self.executor = ThreadPoolExecutor() + +class Jinja2Renderer: def get_image_wrapper(self, image, latest_version=None): if latest_version is None: @@ -71,39 +66,39 @@ def build_env(self, vars_str, search_dirs): add_jinja2_filters(environment) return environment - def prepare_result(self, cnt): - result = jinja2_server_pb2.JobResult() - for i in range(cnt): - r = jinja2_server_pb2.SingleResult() - result.results.append(r) - return result - + def render_helper(self, templates, search_dirs, vars, is_string): + env = self.build_env(vars, search_dirs) - def render_helper(self, request, is_string): - result = self.prepare_result(len(request.templates)) - env = self.build_env(request.vars, request.search_dirs) + result = [] - def do_render(i, t): + for i, t in enumerate(templates): try: if is_string: t = env.from_string(t) else: t = env.get_template(t) - result.results[i].result = t.render() + result.append({ + "result": t.render() + }) except Exception as e: - result.results[i].error = extract_template_error(e) + result.append({ + "error": extract_template_error(e), + }) - futures = [] - for i, t in enumerate(request.templates): - f = self.executor.submit(do_render, i, t) - futures.append(f) - - for f in futures: - f.result() return result - def RenderStrings(self, request, context): - return self.render_helper(request, True) - - def RenderFiles(self, request, context): - return self.render_helper(request, False) + def RenderStrings(self, templates, search_dirs, vars): + try: + return self.render_helper(templates, search_dirs, vars, True) + except Exception as e: + return [{ + "error": str(e) + }] * len(templates) + + def RenderFiles(self, templates, search_dirs, vars): + try: + return self.render_helper(templates, search_dirs, vars, False) + except Exception as e: + return [{ + "error": str(e) + }] * len(templates) diff --git a/jinja2-server/jinja2_utils.py b/pkg/jinja2/python_src/jinja2_utils.py similarity index 100% rename from jinja2-server/jinja2_utils.py rename to pkg/jinja2/python_src/jinja2_utils.py diff --git a/jinja2-server/jsonpath_utils.py b/pkg/jinja2/python_src/jsonpath_utils.py similarity index 93% rename from jinja2-server/jsonpath_utils.py rename to pkg/jinja2/python_src/jsonpath_utils.py index 2135238ff..4d7f8d494 100644 --- a/jinja2-server/jsonpath_utils.py +++ b/pkg/jinja2/python_src/jsonpath_utils.py @@ -2,7 +2,6 @@ import fnmatch import logging import os -import threading import ply from jsonpath_ng import auto_id_field, jsonpath, JSONPath @@ -82,7 +81,7 @@ def convert_list_to_json_path(p): return p2 json_path_cache = {} -json_path_local = threading.local() +json_path_parser = MyJsonPathParser() def parse_json_path(p) -> JSONPath: if isinstance(p, list) or isinstance(p, tuple): @@ -92,12 +91,10 @@ def parse_json_path(p) -> JSONPath: return json_path_cache[p] try: - if not hasattr(json_path_local, "parser"): - json_path_local.parser = MyJsonPathParser() - - pp = json_path_local.parser.parse(p) + pp = json_path_parser.parse(p) except JsonPathParserError as e: raise Exception("Invalid json path '%s'. Error=%s" % (p, str(e))) + pp = json_path_cache.setdefault(p, pp) return pp diff --git a/jinja2-server/requirements.txt b/pkg/jinja2/python_src/requirements.txt similarity index 64% rename from jinja2-server/requirements.txt rename to pkg/jinja2/python_src/requirements.txt index 292d41012..7bc8d801c 100644 --- a/jinja2-server/requirements.txt +++ b/pkg/jinja2/python_src/requirements.txt @@ -2,5 +2,3 @@ jinja2===3.0.3 click==8.0.3 jsonpath-ng==1.5.3 pyyaml==6.0 -protobuf==3.19.4 -grpcio==1.43.0 diff --git a/jinja2-server/yaml_utils.py b/pkg/jinja2/python_src/yaml_utils.py similarity index 100% rename from jinja2-server/yaml_utils.py rename to pkg/jinja2/python_src/yaml_utils.py diff --git a/pkg/jinja2/source.go b/pkg/jinja2/source.go new file mode 100644 index 000000000..0c47cc7f8 --- /dev/null +++ b/pkg/jinja2/source.go @@ -0,0 +1,79 @@ +package jinja2 + +import ( + "archive/zip" + "embed" + "github.com/codablock/kluctl/pkg/utils" + "io" + "io/fs" + "io/ioutil" + "os" + "path" + "path/filepath" + "strings" +) + +//go:embed python_src +var pythonSrc embed.FS + +func extractSource() (string, error) { + tmpDir, err := ioutil.TempDir(utils.GetTmpBaseDir(), "jinja2-src-") + if err != nil { + return "", err + } + + err = utils.FsCopyDir(pythonSrc, "python_src", tmpDir) + if err != nil { + return "", err + } + + err = filepath.WalkDir(path.Join(tmpDir, "wheel"), func(p string, d fs.DirEntry, err error) error { + if !strings.HasSuffix(p,".whl") { + return nil + } + r, err := zip.OpenReader(p) + if err != nil { + return err + } + defer r.Close() + + for _, f := range r.File { + filePath := filepath.Join(tmpDir, f.Name) + if f.FileInfo().IsDir() { + err = os.MkdirAll(filePath, 0o700) + if err != nil { + return err + } + continue + } + if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil { + return err + } + + err = func () error { + dstFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) + if err != nil { + return err + } + defer dstFile.Close() + + fileInArchive, err := f.Open() + if err != nil { + return err + } + defer fileInArchive.Close() + + if _, err := io.Copy(dstFile, fileInArchive); err != nil { + return err + } + return nil + }() + } + return nil + }) + if err != nil { + return "", err + } + + return tmpDir, nil +} \ No newline at end of file diff --git a/pkg/jinja2_server/vars.go b/pkg/jinja2/vars.go similarity index 92% rename from pkg/jinja2_server/vars.go rename to pkg/jinja2/vars.go index db4adc3f9..4784e9416 100644 --- a/pkg/jinja2_server/vars.go +++ b/pkg/jinja2/vars.go @@ -1,4 +1,4 @@ -package jinja2_server +package jinja2 import ( "fmt" @@ -10,13 +10,13 @@ import ( ) type VarsCtx struct { - JS *Jinja2Server + J2 *Jinja2 Vars *uo.UnstructuredObject } -func NewVarsCtx(js *Jinja2Server) *VarsCtx { +func NewVarsCtx(j2 *Jinja2) *VarsCtx { vc := &VarsCtx{ - JS: js, + J2: j2, Vars: uo.New(), } return vc @@ -24,7 +24,7 @@ func NewVarsCtx(js *Jinja2Server) *VarsCtx { func (vc *VarsCtx) Copy() *VarsCtx { cp := &VarsCtx{ - JS: vc.JS, + J2: vc.J2, Vars: vc.Vars.Clone(), } return cp @@ -118,7 +118,7 @@ func (vc *VarsCtx) loadVarsFromString(s string) error { } func (vc *VarsCtx) renderYamlString(s string, out interface{}) error { - ret, err := vc.JS.RenderString(s, nil, vc.Vars) + ret, err := vc.J2.RenderString(s, nil, vc.Vars) if err != nil { return err } @@ -132,7 +132,7 @@ func (vc *VarsCtx) renderYamlString(s string, out interface{}) error { } func (vc *VarsCtx) RenderYamlFile(p string, searchDirs []string, out interface{}) error { - ret, err := vc.JS.RenderFile(p, searchDirs, vc.Vars) + ret, err := vc.J2.RenderFile(p, searchDirs, vc.Vars) if err != nil { return err } @@ -146,5 +146,5 @@ func (vc *VarsCtx) RenderYamlFile(p string, searchDirs []string, out interface{} } func (vc *VarsCtx) RenderDirectory(rootDir string, searchDirs []string, relSourceDir string, excludePatterns []string, subdir string, targetDir string) error { - return vc.JS.RenderDirectory(rootDir, searchDirs, relSourceDir, excludePatterns, subdir, targetDir, vc.Vars) + return vc.J2.RenderDirectory(rootDir, searchDirs, relSourceDir, excludePatterns, subdir, targetDir, vc.Vars) } diff --git a/pkg/jinja2_server/.gitignore b/pkg/jinja2_server/.gitignore deleted file mode 100644 index 46d6257b8..000000000 --- a/pkg/jinja2_server/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -jinja2_server.pb.go -jinja2_server_grpc.pb.go \ No newline at end of file diff --git a/pkg/jinja2_server/server.go b/pkg/jinja2_server/server.go deleted file mode 100644 index e686ee07a..000000000 --- a/pkg/jinja2_server/server.go +++ /dev/null @@ -1,387 +0,0 @@ -package jinja2_server - -import ( - "bufio" - "bytes" - "context" - "encoding/json" - "fmt" - "github.com/codablock/kluctl/pkg/utils" - "github.com/codablock/kluctl/pkg/utils/uo" - "github.com/gobwas/glob" - log "github.com/sirupsen/logrus" - "golang.org/x/sync/semaphore" - "google.golang.org/grpc" - "io/fs" - "io/ioutil" - "os" - "os/exec" - "path" - "path/filepath" - "strconv" - "strings" - "sync" -) - -type Jinja2Error struct { - error string -} - -func (m *Jinja2Error) Error() string { - return m.error -} - -type Jinja2Server struct { - serverPath string - pythonVenv string - cmd *exec.Cmd - - port int - conn *grpc.ClientConn - client Jinja2ServerClient - sem *semaphore.Weighted - - globCache map[string]interface{} - globCacheMutex sync.Mutex -} - -func NewJinja2Server() (*Jinja2Server, error) { - js := &Jinja2Server{ - sem: semaphore.NewWeighted(8), - globCache: map[string]interface{}{}, - } - - serverPath, ok := os.LookupEnv("JINJA2_SERVER") - if !ok { - executable, err := os.Executable() - if err != nil { - log.Fatal(err) - } - serverPath = path.Join(path.Dir(executable), "jinja2-server") - } - - js.serverPath = serverPath - js.pythonVenv = path.Join(js.serverPath, "venv") - - cmdName := path.Join(js.pythonVenv, "bin/python") - args := []string{"main.py"} - - args = append(args, "serve") - js.cmd = exec.Command(cmdName, args...) - js.cmd.Dir = js.serverPath - - stdout, err := js.cmd.StdoutPipe() - if err != nil { - return nil, err - } - - err = js.cmd.Start() - if err != nil { - _ = stdout.Close() - return nil, err - } - - s := bufio.NewScanner(stdout) - if !s.Scan() { - _ = js.cmd.Process.Kill() - return nil, fmt.Errorf("failed to determine jinja2-server port") - } - - port, err := strconv.ParseInt(s.Text(), 10, 32) - if err != nil { - _ = js.cmd.Process.Kill() - return nil, fmt.Errorf("failed to parse port: %w", err) - } - - js.port = int(port) - js.conn, err = grpc.Dial(fmt.Sprintf("localhost:%d", js.port), grpc.WithInsecure()) - if err != nil { - _ = js.cmd.Process.Kill() - return nil, err - } - js.client = NewJinja2ServerClient(js.conn) - - return js, nil -} - -func (js *Jinja2Server) Stop() error { - _ = js.conn.Close() - return js.cmd.Process.Kill() -} - -type RenderJob struct { - Template string - Result *string - Error error - - target string -} - -func (js *Jinja2Server) isMaybeTemplate(template string, searchDirs []string, isString bool) (bool, *string) { - if isString { - if strings.IndexRune(template, '{') == -1 { - return false, &template - } - } else { - for _, s := range searchDirs { - b, err := ioutil.ReadFile(path.Join(s, template)) - if err != nil { - continue - } - if bytes.IndexRune(b, '{') == -1 { - x := string(b) - return false, &x - } else { - return true, nil - } - } - } - return true, nil -} - -func (js *Jinja2Server) renderHelper(jobs []*RenderJob, searchDirs []string, vars *uo.UnstructuredObject, isString bool) error { - varsStr, err := json.Marshal(vars.Object) - if err != nil { - return err - } - - var processedJobs []*RenderJob - var templates []string - for _, job := range jobs { - if ist, r := js.isMaybeTemplate(job.Template, searchDirs, isString); !ist { - job.Result = r - continue - } - processedJobs = append(processedJobs, job) - templates = append(templates, job.Template) - } - if len(templates) == 0 { - return nil - } - - err = js.sem.Acquire(context.Background(), 1) - if err != nil { - return err - } - defer js.sem.Release(1) - - var result *JobResult - if isString { - request := &StringsJob{ - Vars: string(varsStr), - Templates: templates, - SearchDirs: searchDirs, - } - result, err = js.client.RenderStrings(context.Background(), request) - } else { - request := &FilesJob{ - Vars: string(varsStr), - Templates: templates, - SearchDirs: searchDirs, - } - result, err = js.client.RenderFiles(context.Background(), request) - } - if err != nil { - return err - } - - for i, r := range result.Results { - if r.Error != nil { - processedJobs[i].Error = &Jinja2Error{error: *r.Error} - } else { - processedJobs[i].Result = r.Result - } - } - return nil -} - -func (js *Jinja2Server) RenderStrings(jobs []*RenderJob, searchDirs []string, vars *uo.UnstructuredObject) error { - return js.renderHelper(jobs, searchDirs, vars, true) -} - -func (js *Jinja2Server) RenderFiles(jobs []*RenderJob, searchDirs []string, vars *uo.UnstructuredObject) error { - return js.renderHelper(jobs, searchDirs, vars, false) -} - -func (js *Jinja2Server) RenderString(template string, searchDirs []string, vars *uo.UnstructuredObject) (string, error) { - jobs := []*RenderJob{{ - Template: template, - }} - err := js.RenderStrings(jobs, searchDirs, vars) - if err != nil { - return "", err - } - if jobs[0].Error != nil { - return "", jobs[0].Error - } - return *jobs[0].Result, nil -} - -func (js *Jinja2Server) RenderFile(template string, searchDirs []string, vars *uo.UnstructuredObject) (string, error) { - jobs := []*RenderJob{{ - Template: template, - }} - err := js.RenderFiles(jobs, searchDirs, vars) - if err != nil { - return "", err - } - if jobs[0].Error != nil { - return "", jobs[0].Error - } - return *jobs[0].Result, nil -} - -func (js *Jinja2Server) RenderStruct(dst interface{}, src interface{}, vars *uo.UnstructuredObject) error { - m, err := uo.FromStruct(src) - if err != nil { - return err - } - - type pk struct { - parent interface{} - key interface{} - } - - var jobs []*RenderJob - var fields []pk - err = m.NewIterator().IterateLeafs(func(it *uo.ObjectIterator) error { - value := it.Value() - if s, ok := value.(string); ok { - jobs = append(jobs, &RenderJob{Template: s}) - fields = append(fields, pk{ - parent: it.Parent(), - key: it.Key(), - }) - } - return nil - }) - if err != nil { - return err - } - - err = js.RenderStrings(jobs, nil, vars) - if err != nil { - return err - } - - var errors []error - for i, j := range jobs { - if j.Error != nil { - errors = append(errors, err) - } - - err = uo.SetChild(fields[i].parent, fields[i].key, *j.Result) - if err != nil { - return err - } - } - if len(errors) != 0 { - return utils.NewErrorList(errors) - } - - err = m.ToStruct(dst) - if err != nil { - return err - } - return nil -} - -func (js *Jinja2Server) getGlob(pattern string) (glob.Glob, error) { - js.globCacheMutex.Lock() - defer js.globCacheMutex.Unlock() - - g, ok := js.globCache[pattern] - if ok { - if g2, ok := g.(glob.Glob); ok { - return g2, nil - } else { - return nil, g2.(error) - } - } - g, err := glob.Compile(pattern) - if err != nil { - js.globCache[pattern] = err - return nil, err - } - js.globCache[pattern] = g - return g.(glob.Glob), nil -} -func (js *Jinja2Server) needsRender(path string, excludedPatterns []string) bool { - for _, p := range excludedPatterns { - g, err := js.getGlob(p) - if err != nil { - return false - } - if g.Match(path) { - return false - } - } - return true -} - -func (js *Jinja2Server) RenderDirectory(rootDir string, searchDirs []string, relSourceDir string, excludePatterns []string, subdir string, targetDir string, vars *uo.UnstructuredObject) error { - walkDir := path.Join(rootDir, relSourceDir, subdir) - - var jobs []*RenderJob - - err := filepath.WalkDir(walkDir, func(p string, d fs.DirEntry, err error) error { - relPath, err := filepath.Rel(walkDir, p) - if err != nil { - return err - } - if d.IsDir() { - err = os.MkdirAll(path.Join(targetDir, relPath), 0o700) - if err != nil { - return err - } - return nil - } - - sourcePath := path.Clean(path.Join(subdir, relPath)) - targetPath := path.Join(targetDir, relPath) - - if strings.Index(sourcePath, ".sealme") != -1 { - sourcePath += "" - } - - if !js.needsRender(sourcePath, excludePatterns) { - return utils.CopyFile(p, targetPath) - } - - // jinja2 templates are using / even on Windows - sourcePath = strings.ReplaceAll(sourcePath, "\\", "/") - - job := &RenderJob{ - Template: sourcePath, - target: targetPath, - } - jobs = append(jobs, job) - return nil - }) - if err != nil { - return err - } - - err = js.RenderFiles(jobs, searchDirs, vars) - if err != nil { - return err - } - - var errors []error - for _, job := range jobs { - if job.Error != nil { - errors = append(errors, job.Error) - continue - } - - err = ioutil.WriteFile(job.target, []byte(*job.Result), 0o600) - if err != nil { - return err - } - } - if len(errors) != 0 { - return utils.NewErrorList(errors) - } - - return nil -} diff --git a/pkg/kluctl_project/load_targets.go b/pkg/kluctl_project/load_targets.go index 332796b92..2594e1fb4 100644 --- a/pkg/kluctl_project/load_targets.go +++ b/pkg/kluctl_project/load_targets.go @@ -3,7 +3,7 @@ package kluctl_project import ( "fmt" git_url "github.com/codablock/kluctl/pkg/git/git-url" - "github.com/codablock/kluctl/pkg/jinja2_server" + "github.com/codablock/kluctl/pkg/jinja2" "github.com/codablock/kluctl/pkg/types" "github.com/codablock/kluctl/pkg/utils" "github.com/codablock/kluctl/pkg/utils/uo" @@ -78,7 +78,7 @@ func (c *KluctlProjectContext) renderTarget(target *types.Target) (*types.Target var errors []error curTarget := target for i := 0; i < 10; i++ { - varsCtx := jinja2_server.NewVarsCtx(c.JS) + varsCtx := jinja2.NewVarsCtx(c.J2) err := varsCtx.UpdateChildFromStruct("target", curTarget) if err != nil { return nil, err @@ -93,7 +93,7 @@ func (c *KluctlProjectContext) renderTarget(target *types.Target) (*types.Target } var newTarget types.Target - err = c.JS.RenderStruct(&newTarget, curTarget, varsCtx.Vars) + err = c.J2.RenderStruct(&newTarget, curTarget, varsCtx.Vars) if err == nil && reflect.DeepEqual(curTarget, &newTarget) { return curTarget, nil } diff --git a/pkg/kluctl_project/project.go b/pkg/kluctl_project/project.go index 7ac498104..c716a6935 100644 --- a/pkg/kluctl_project/project.go +++ b/pkg/kluctl_project/project.go @@ -4,7 +4,7 @@ import ( "fmt" "github.com/codablock/kluctl/pkg/git" git_url "github.com/codablock/kluctl/pkg/git/git-url" - "github.com/codablock/kluctl/pkg/jinja2_server" + "github.com/codablock/kluctl/pkg/jinja2" "github.com/codablock/kluctl/pkg/types" "regexp" ) @@ -19,7 +19,7 @@ type LoadKluctlProjectArgs struct { FromArchive string FromArchiveMetadata string - JS *jinja2_server.Jinja2Server + J2 *jinja2.Jinja2 } type KluctlProjectContext struct { @@ -38,7 +38,7 @@ type KluctlProjectContext struct { mirroredRepos map[string]*git.MirroredGitRepo - JS *jinja2_server.Jinja2Server + J2 *jinja2.Jinja2 } func NewKluctlProjectContext(loadArgs LoadKluctlProjectArgs, tmpDir string) *KluctlProjectContext { @@ -47,7 +47,7 @@ func NewKluctlProjectContext(loadArgs LoadKluctlProjectArgs, tmpDir string) *Klu TmpDir: tmpDir, involvedRepos: make(map[string][]types.InvolvedRepo), mirroredRepos: make(map[string]*git.MirroredGitRepo), - JS: loadArgs.JS, + J2: loadArgs.J2, } return o } diff --git a/pkg/python/dict.go b/pkg/python/dict.go new file mode 100644 index 000000000..8f1cd525f --- /dev/null +++ b/pkg/python/dict.go @@ -0,0 +1,24 @@ +package python + +/* +#include "Python.h" +*/ +import "C" +import "unsafe" + +type PyDict = PyObject + +func PyDict_New() *PyDict { + return (*PyList)(C.PyDict_New()) +} + +func PyDict_FromObject(o *PyObject) *PyDict { + return (*PyDict)(o) +} + +func (d *PyDict) GetItemString(key string) *PyObject { + ckey := C.CString(key) + defer C.free(unsafe.Pointer(ckey)) + + return (*PyObject)(C.PyDict_GetItemString((*C.PyObject)(d), ckey)) +} \ No newline at end of file diff --git a/pkg/python/error.go b/pkg/python/error.go new file mode 100644 index 000000000..61086f9cd --- /dev/null +++ b/pkg/python/error.go @@ -0,0 +1,10 @@ +package python + +/* +#include "Python.h" +*/ +import "C" + +func PyErr_Print() { + C.PyErr_PrintEx(1) +} diff --git a/pkg/python/interpreter.go b/pkg/python/interpreter.go new file mode 100644 index 000000000..e2da88096 --- /dev/null +++ b/pkg/python/interpreter.go @@ -0,0 +1,106 @@ +package python + + +import ( + "log" + "runtime" +) + +type PythonInterpreter struct { + calls chan *pythonInterpreterCall + init chan error + stopChan chan bool + + threadState *PyThreadState +} + +type pythonInterpreterCall struct { + fun func() error + result chan error +} + +func NewPythonInterpreter() (*PythonInterpreter, error) { + p := &PythonInterpreter{ + calls: make(chan *pythonInterpreterCall), + init: make(chan error), + stopChan: make(chan bool), + } + + go p.runThread() + + err := <-p.init + if err != nil { + return nil, err + } + return p, nil +} + +func (p *PythonInterpreter) Stop() { + p.stopChan <- true +} + +func (p *PythonInterpreter) Run(fun func() error) error { + c := &pythonInterpreterCall{ + fun: fun, + result: make(chan error), + } + p.calls <- c + err := <- c.result + return err +} + +func (p *PythonInterpreter) AppendSysPath(pth string) error { + return p.Run(func() error { + sys := PyImport_ImportModule("sys") + defer sys.DecRef() + l := PyList_FromObject(sys.GetAttrString("path")) + defer l.DecRef() + + l.Append(PyUnicode_FromString(pth)) + return nil + }) +} + +func (p *PythonInterpreter) runThread() { + runtime.LockOSThread() + + PyEval_AcquireThread(mainThreadState) + p.threadState = Py_NewInterpreter() + PyEval_ReleaseThread(p.threadState) + + defer func() { + PyEval_AcquireThread(p.threadState) + Py_EndInterpreter(p.threadState) + }() + + p.init <- nil + + for true { + select { + case c := <-p.calls: + PyEval_AcquireThread(p.threadState) + p.processCall(c) + PyEval_ReleaseThread(p.threadState) + case <-p.stopChan: + return + } + } +} + +func (p *PythonInterpreter) processCall(c *pythonInterpreterCall) { + ss := PyThreadState_Get() + if ss != p.threadState { + log.Panicf("ss != p.threadState") + } + + err := c.fun() + c.result<-err +} + +var mainThreadState *PyThreadState + +func init() { + Py_Initialize() + mainThreadState = PyEval_SaveThread() +} + diff --git a/pkg/python/list.go b/pkg/python/list.go new file mode 100644 index 000000000..8ac6d8e30 --- /dev/null +++ b/pkg/python/list.go @@ -0,0 +1,24 @@ +package python + +/* +#include "Python.h" +*/ +import "C" + +type PyList = PyObject + +func PyList_New(len int) *PyList { + return (*PyList)(C.PyList_New(C.Py_ssize_t(len))) +} + +func PyList_FromObject(l *PyObject) *PyList { + return l +} + +func (l *PyList) GetItem(pos int) *PyObject { + return (*PyObject)(C.PyList_GetItem((*C.PyObject)(l), C.Py_ssize_t(pos))) +} + +func (l *PyList) Append(item *PyObject) int { + return int(C.PyList_Append((*C.PyObject)(l), (*C.PyObject)(item))) +} diff --git a/pkg/python/misc.go b/pkg/python/misc.go new file mode 100644 index 000000000..78d3bcc17 --- /dev/null +++ b/pkg/python/misc.go @@ -0,0 +1,49 @@ +package python + +/* +#cgo pkg-config: python3 +#include "Python.h" +*/ +import "C" + +import ( + "unsafe" +) + +type PyThreadState C.PyThreadState + +func Py_Initialize() { + C.Py_Initialize() +} + +func Py_NewInterpreter() *PyThreadState { + p := C.Py_NewInterpreter() + return (*PyThreadState)(unsafe.Pointer(p)) +} + +func Py_EndInterpreter(o *PyThreadState) { + C.Py_EndInterpreter((*C.PyThreadState)(unsafe.Pointer(o))) +} + +func PyEval_AcquireThread(o *PyThreadState) { + C.PyEval_AcquireThread((*C.PyThreadState)(unsafe.Pointer(o))) +} + +func PyEval_ReleaseThread(o *PyThreadState) { + C.PyEval_ReleaseThread((*C.PyThreadState)(unsafe.Pointer(o))) +} + +func PyEval_SaveThread() *PyThreadState { + return (*PyThreadState)(C.PyEval_SaveThread()) +} + +func PyThreadState_Get() *PyThreadState { + return (*PyThreadState)(C.PyThreadState_Get()) +} + +func PyImport_ImportModule(name string) *PyObject { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + return (*PyObject)(C.PyImport_ImportModule(cname)) +} diff --git a/pkg/python/object.go b/pkg/python/object.go new file mode 100644 index 000000000..6c02d69d9 --- /dev/null +++ b/pkg/python/object.go @@ -0,0 +1,57 @@ +package python + +/* +#include "Python.h" +#include "variadic.h" +*/ +import "C" +import "unsafe" + +const MaxVariadicLength = 20 + +type PyObject C.PyObject + +func (p *PyObject) IncRef() { + C.Py_IncRef((*C.PyObject)(p)) +} + +func (p *PyObject) DecRef() { + C.Py_DecRef((*C.PyObject)(p)) +} + +func (p *PyObject) Length() int { + return int(C.PyObject_Length((*C.PyObject)(p))) +} + +func (p *PyObject) GetAttrString(attr_name string) *PyObject { + cattr_name := C.CString(attr_name) + defer C.free(unsafe.Pointer(cattr_name)) + + return (*PyObject)(C.PyObject_GetAttrString((*C.PyObject)(p), cattr_name)) +} + +func (p *PyObject) SetAttrString(attr_name string, v *PyObject) int { + cattr_name := C.CString(attr_name) + defer C.free(unsafe.Pointer(cattr_name)) + + return int(C.PyObject_SetAttrString((*C.PyObject)(p), cattr_name, (*C.PyObject)(v))) +} + +func (p *PyObject) CallObject(args *PyObject) *PyObject { + return (*PyObject)(C.PyObject_CallObject((*C.PyObject)(p), (*C.PyObject)(args))) +} + +func (p *PyObject) CallMethodObjArgs(name *PyObject, args ...*PyObject) *PyObject { + if len(args) > MaxVariadicLength { + panic("CallMethodObjArgs: too many arguments") + } + if len(args) == 0 { + return (*PyObject)(C._go_PyObject_CallMethodObjArgs((*C.PyObject)(p), (*C.PyObject)(name), 0, (**C.PyObject)(nil))) + } + + cargs := make([]*C.PyObject, len(args), len(args)) + for i, arg := range args { + cargs[i] = (*C.PyObject)(arg) + } + return (*PyObject)(C._go_PyObject_CallMethodObjArgs((*C.PyObject)(p), (*C.PyObject)(name), C.int(len(args)), (**C.PyObject)(unsafe.Pointer(&cargs[0])))) +} diff --git a/pkg/python/string.go b/pkg/python/string.go new file mode 100644 index 000000000..7c52a94b8 --- /dev/null +++ b/pkg/python/string.go @@ -0,0 +1,19 @@ +package python + +/* +#include "Python.h" +*/ +import "C" +import "unsafe" + +func PyUnicode_FromString(u string) *PyObject { + cu := C.CString(u) + defer C.free(unsafe.Pointer(cu)) + + return (*PyObject)(C.PyUnicode_FromString(cu)) +} + +func PyUnicode_AsUTF8(unicode *PyObject) string { + cutf8 := C.PyUnicode_AsUTF8((*C.PyObject)(unicode)) + return C.GoString(cutf8) +} diff --git a/pkg/python/variadic.c b/pkg/python/variadic.c new file mode 100644 index 000000000..5c490723a --- /dev/null +++ b/pkg/python/variadic.c @@ -0,0 +1,107 @@ +/* +Unless explicitly stated otherwise all files in this repository are licensed +under the MIT License. +This product includes software developed at Datadog (https://www.datadoghq.com/). +Copyright 2018 Datadog, Inc. +*/ + +#include "Python.h" + +PyObject* _go_PyObject_CallFunctionObjArgs(PyObject *callable, int argc, PyObject **argv) { + + PyObject *result = NULL; + switch (argc) { + case 0: + return PyObject_CallFunctionObjArgs(callable, NULL); + case 1: + return PyObject_CallFunctionObjArgs(callable, argv[0], NULL); + case 2: + return PyObject_CallFunctionObjArgs(callable, argv[0], argv[1], NULL); + case 3: + return PyObject_CallFunctionObjArgs(callable, argv[0], argv[1], argv[2], NULL); + case 4: + return PyObject_CallFunctionObjArgs(callable, argv[0], argv[1], argv[2], argv[3], NULL); + case 5: + return PyObject_CallFunctionObjArgs(callable, argv[0], argv[1], argv[2], argv[3], argv[4], NULL); + case 6: + return PyObject_CallFunctionObjArgs(callable, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], NULL); + case 7: + return PyObject_CallFunctionObjArgs(callable, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], NULL); + case 8: + return PyObject_CallFunctionObjArgs(callable, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], NULL); + case 9: + return PyObject_CallFunctionObjArgs(callable, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], NULL); + case 10: + return PyObject_CallFunctionObjArgs(callable, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], NULL); + case 11: + return PyObject_CallFunctionObjArgs(callable, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], NULL); + case 12: + return PyObject_CallFunctionObjArgs(callable, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], NULL); + case 13: + return PyObject_CallFunctionObjArgs(callable, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], NULL); + case 14: + return PyObject_CallFunctionObjArgs(callable, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], NULL); + case 15: + return PyObject_CallFunctionObjArgs(callable, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], NULL); + case 16: + return PyObject_CallFunctionObjArgs(callable, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], NULL); + case 17: + return PyObject_CallFunctionObjArgs(callable, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], argv[16], NULL); + case 18: + return PyObject_CallFunctionObjArgs(callable, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], argv[16], argv[17], NULL); + case 19: + return PyObject_CallFunctionObjArgs(callable, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], argv[16], argv[17], argv[18], NULL); + case 20: + return PyObject_CallFunctionObjArgs(callable, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], argv[16], argv[17], argv[18], argv[19], NULL); + } + return result; +} +PyObject* _go_PyObject_CallMethodObjArgs(PyObject *obj, PyObject *name, int argc, PyObject **argv) { + + PyObject *result = NULL; + switch (argc) { + case 0: + return PyObject_CallMethodObjArgs(obj, name, NULL); + case 1: + return PyObject_CallMethodObjArgs(obj, name, argv[0], NULL); + case 2: + return PyObject_CallMethodObjArgs(obj, name, argv[0], argv[1], NULL); + case 3: + return PyObject_CallMethodObjArgs(obj, name, argv[0], argv[1], argv[2], NULL); + case 4: + return PyObject_CallMethodObjArgs(obj, name, argv[0], argv[1], argv[2], argv[3], NULL); + case 5: + return PyObject_CallMethodObjArgs(obj, name, argv[0], argv[1], argv[2], argv[3], argv[4], NULL); + case 6: + return PyObject_CallMethodObjArgs(obj, name, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], NULL); + case 7: + return PyObject_CallMethodObjArgs(obj, name, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], NULL); + case 8: + return PyObject_CallMethodObjArgs(obj, name, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], NULL); + case 9: + return PyObject_CallMethodObjArgs(obj, name, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], NULL); + case 10: + return PyObject_CallMethodObjArgs(obj, name, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], NULL); + case 11: + return PyObject_CallMethodObjArgs(obj, name, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], NULL); + case 12: + return PyObject_CallMethodObjArgs(obj, name, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], NULL); + case 13: + return PyObject_CallMethodObjArgs(obj, name, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], NULL); + case 14: + return PyObject_CallMethodObjArgs(obj, name, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], NULL); + case 15: + return PyObject_CallMethodObjArgs(obj, name, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], NULL); + case 16: + return PyObject_CallMethodObjArgs(obj, name, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], NULL); + case 17: + return PyObject_CallMethodObjArgs(obj, name, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], argv[16], NULL); + case 18: + return PyObject_CallMethodObjArgs(obj, name, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], argv[16], argv[17], NULL); + case 19: + return PyObject_CallMethodObjArgs(obj, name, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], argv[16], argv[17], argv[18], NULL); + case 20: + return PyObject_CallMethodObjArgs(obj, name, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], argv[16], argv[17], argv[18], argv[19], NULL); + } + return result; +} diff --git a/pkg/python/variadic.h b/pkg/python/variadic.h new file mode 100644 index 000000000..2aa7dbeda --- /dev/null +++ b/pkg/python/variadic.h @@ -0,0 +1,16 @@ +/* +Unless explicitly stated otherwise all files in this repository are licensed +under the MIT License. +This product includes software developed at Datadog (https://www.datadoghq.com/). +Copyright 2018 Datadog, Inc. +*/ + +#ifndef VARIADIC_H +#define VARIADIC_H + +#include "Python.h" + +PyObject* _go_PyObject_CallFunctionObjArgs(PyObject *callable, int argc, PyObject **args); +PyObject* _go_PyObject_CallMethodObjArgs(PyObject *obj, PyObject *name, int argc, PyObject **args); + +#endif \ No newline at end of file From eb142466cd2b67ed09399b0c49af795dad0c69ec Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sat, 19 Feb 2022 23:25:52 +0100 Subject: [PATCH 0380/2916] Fix bug in compatibility code for includes --- pkg/deployment/deployment_project.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go index 77ce9876c..ebd640feb 100644 --- a/pkg/deployment/deployment_project.go +++ b/pkg/deployment/deployment_project.go @@ -96,7 +96,12 @@ func (p *DeploymentProject) loadConfig(k *k8s.K8sCluster) error { includesDeprecatedOnce.Do(func() { log.Warningf("'includes' is deprecated, use 'deployments' instead") }) - p.config.Deployments = append(p.config.Deployments, p.config.Includes...) + for _, inc := range p.config.Includes { + c := *inc + c.Include = c.Path + c.Path = nil + p.config.Deployments = append(p.config.Deployments, &c) + } p.config.Includes = nil } From b5be69459b090501290089e4e28133eb4aa3d38f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sat, 19 Feb 2022 23:26:08 +0100 Subject: [PATCH 0381/2916] Fix crash in collectDeployments --- pkg/deployment/deployment_collection.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index d33520fd6..0b21fa80c 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -69,10 +69,13 @@ func (c *DeploymentCollection) createBarrierDummy(project *DeploymentProject) *d return di } -func findDeploymentItemIndex(project *DeploymentProject, pth string, indexes map[string]int) (int, *string) { +func findDeploymentItemIndex(project *DeploymentProject, pth *string, indexes map[string]int) (int, *string) { + if pth == nil { + return 0, nil + } var dir2 *string index := 0 - dir := path.Join(project.dir, pth) + dir := path.Join(project.dir, *pth) absDir, err := filepath.Abs(dir) if err != nil { // we pre-checked directories, so this should not happen @@ -106,7 +109,7 @@ func (c *DeploymentCollection) collectDeployments(project *DeploymentProject, in ret = append(ret, c.createBarrierDummy(project)) } } else { - index, dir2 := findDeploymentItemIndex(project, *diConfig.Path, indexes) + index, dir2 := findDeploymentItemIndex(project, diConfig.Path, indexes) di, err := NewDeploymentItem(project, c, diConfig, dir2, index) if err != nil { return nil, err From 5db755400d8ca0acbe4086068b886c254c6cd58e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sun, 20 Feb 2022 22:03:12 +0100 Subject: [PATCH 0382/2916] Implement lib_wrapper --- pkg/utils/lib_wrapper/call.c | 99 ++++++++++++++++ pkg/utils/lib_wrapper/call.h | 16 +++ pkg/utils/lib_wrapper/lib_wrapper.go | 114 +++++++++++++++++++ pkg/utils/lib_wrapper/lib_wrapper_unix.go | 39 +++++++ pkg/utils/lib_wrapper/lib_wrapper_windows.go | 30 +++++ pkg/utils/lib_wrapper/str.go | 20 ++++ 6 files changed, 318 insertions(+) create mode 100644 pkg/utils/lib_wrapper/call.c create mode 100644 pkg/utils/lib_wrapper/call.h create mode 100644 pkg/utils/lib_wrapper/lib_wrapper.go create mode 100644 pkg/utils/lib_wrapper/lib_wrapper_unix.go create mode 100644 pkg/utils/lib_wrapper/lib_wrapper_windows.go create mode 100644 pkg/utils/lib_wrapper/str.go diff --git a/pkg/utils/lib_wrapper/call.c b/pkg/utils/lib_wrapper/call.c new file mode 100644 index 000000000..c1299035f --- /dev/null +++ b/pkg/utils/lib_wrapper/call.c @@ -0,0 +1,99 @@ + +#include "call.h" + +void* call_vp_ptrs(void* f, int argc, void **argv) { + switch (argc) { + case 0: { + typedef void* (*F)(); + return ((F)(f))(); + } + case 1: { + typedef void* (*F)(void*); + return ((F)(f))(argv[0]); + } + case 2: { + typedef void* (*F)(void*,void*); + return ((F)(f))(argv[0],argv[1]); + } + case 3: { + typedef void* (*F)(void*,void*,void*); + return ((F)(f))(argv[0],argv[1],argv[2]); + } + case 4: { + typedef void* (*F)(void*,void*,void*,void*); + return ((F)(f))(argv[0],argv[1],argv[2],argv[3]); + } + } + return NULL; +} + +void* call_vp_ptrs_vargs(void* f, int nptrs, void **ptrs, int argc, void **argv) { + switch (nptrs) { + case 0: { + // crash!! + return &*((int*)NULL); + } + case 1: { + typedef void* (*F)(void*, ...); + switch (argc) { + case 0: return ((F)(f))(ptrs[0]); + case 1: return ((F)(f))(ptrs[0], argv[0]); + case 2: return ((F)(f))(ptrs[0], argv[0], argv[1]); + case 3: return ((F)(f))(ptrs[0], argv[0], argv[1], argv[2]); + case 4: return ((F)(f))(ptrs[0], argv[0], argv[1], argv[2], argv[3]); + } + } + case 2: { + typedef void* (*F)(void*,void*, ...); + switch (argc) { + case 0: return ((F)(f))(ptrs[0], ptrs[1]); + case 1: return ((F)(f))(ptrs[0], ptrs[1], argv[0]); + case 2: return ((F)(f))(ptrs[0], ptrs[1], argv[0], argv[1]); + case 3: return ((F)(f))(ptrs[0], ptrs[1], argv[0], argv[1], argv[2]); + case 4: return ((F)(f))(ptrs[0], ptrs[1], argv[0], argv[1], argv[2], argv[3]); + } + } + } + // crash!! + return &*((int*)NULL); +} + +int call_i_ptrs(void* f, int argc, void **argv) { + switch (argc) { + case 0: { + typedef int (*F)(); + return ((F)(f))(); + } + case 1: { + typedef int (*F)(void*); + return ((F)(f))(argv[0]); + } + case 2: { + typedef int (*F)(void*,void*); + return ((F)(f))(argv[0],argv[1]); + } + case 3: { + typedef int (*F)(void*,void*,void*); + return ((F)(f))(argv[0],argv[1],argv[2]); + } + case 4: { + typedef int (*F)(void*,void*,void*,void*); + return ((F)(f))(argv[0],argv[1],argv[2],argv[3]); + } + } + return 0; +} + +void call_v_ptrs(void* f, int argc, void **argv) { + call_vp_ptrs(f, argc, argv); +} + +void* call_vp_ss(void* f, ssize_t i) { + typedef void* (*F)(ssize_t); + return ((F)(f))(i); +} + +void* call_vp_vp_ss(void* f, void *a1, ssize_t a2) { + typedef void* (*F)(void*,ssize_t); + return ((F)(f))(a1,a2); +} diff --git a/pkg/utils/lib_wrapper/call.h b/pkg/utils/lib_wrapper/call.h new file mode 100644 index 000000000..b007d8b98 --- /dev/null +++ b/pkg/utils/lib_wrapper/call.h @@ -0,0 +1,16 @@ + +#ifndef VARIADIC_H +#define VARIADIC_H + +#include +#include + +void* call_vp_ptrs(void* f, int argc, void **argv); +void* call_vp_ptrs_vargs(void* f, int nptrs, void **ptrs, int argc, void **argv); +void call_v_ptrs(void* f, int argc, void **argv); +int call_i_ptrs(void* f, int argc, void **argv); + +void* call_vp_ss(void* f, ssize_t a1); +void* call_vp_vp_ss(void* f, void *a1, ssize_t a2); + +#endif diff --git a/pkg/utils/lib_wrapper/lib_wrapper.go b/pkg/utils/lib_wrapper/lib_wrapper.go new file mode 100644 index 000000000..2a856b058 --- /dev/null +++ b/pkg/utils/lib_wrapper/lib_wrapper.go @@ -0,0 +1,114 @@ +package lib_wrapper + +/* +#include "call.h" +*/ +import "C" +import ( + "log" + "sync" + "unsafe" +) + +const MaxVariadicLength = 5 + +type FunctionPtr unsafe.Pointer + +type LibWrapper struct { + module unsafe.Pointer + funcMap sync.Map +} + +func (lw *LibWrapper) getFunc(funcName string) FunctionPtr { + f, ok := lw.funcMap.Load(funcName) + if !ok { + f = lw.loadFunc(funcName) + f, _ = lw.funcMap.LoadOrStore(funcName, f) + } + f2, ok := f.(FunctionPtr) + if !ok { + log.Panicf("f2 != FunctionPtr") + } + return f2 +} + +func (lw *LibWrapper) Call_V_PTRS(funcName string, args ...unsafe.Pointer) { + f := lw.getFunc(funcName) + + if len(args) > MaxVariadicLength { + panic("Call_V_PTRS: too many arguments") + } + + var cargs *unsafe.Pointer + if len(args) == 0 { + cargs = nil + } else { + cargs = &args[0] + } + C.call_v_ptrs(unsafe.Pointer(f), C.int(len(args)), cargs) +} + +func (lw *LibWrapper) Call_VP_PTRS(funcName string, args ...unsafe.Pointer) unsafe.Pointer { + f := lw.getFunc(funcName) + + if len(args) > MaxVariadicLength { + panic("Call_V_PTRS: too many arguments") + } + + var cargs *unsafe.Pointer + if len(args) == 0 { + cargs = nil + } else { + cargs = &args[0] + } + return C.call_vp_ptrs(unsafe.Pointer(f), C.int(len(args)), cargs) +} + +func (lw *LibWrapper) Call_VP_PTRS_VARGS(funcName string, a1 []unsafe.Pointer, vargs ...unsafe.Pointer) unsafe.Pointer { + f := lw.getFunc(funcName) + + if len(vargs) > MaxVariadicLength { + panic("Call_V_PTRS: too many arguments") + } + + var ca1 *unsafe.Pointer + if len(a1) == 0 { + ca1 = nil + } else { + ca1 = &a1[0] + } + + var cvargs *unsafe.Pointer + if len(vargs) == 0 { + cvargs = nil + } else { + cvargs = &vargs[0] + } + return C.call_vp_ptrs_vargs(unsafe.Pointer(f), C.int(len(a1)), ca1, C.int(len(vargs)), cvargs) +} + +func (lw *LibWrapper) Call_I_PTRS(funcName string, args ...unsafe.Pointer) int { + f := lw.getFunc(funcName) + + if len(args) > MaxVariadicLength { + panic("Call_V_PTRS: too many arguments") + } + + var cargs *unsafe.Pointer + if len(args) == 0 { + cargs = nil + } else { + cargs = &args[0] + } + return int(C.call_i_ptrs(unsafe.Pointer(f), C.int(len(args)), cargs)) +} + +func (lw *LibWrapper) Call_VP_SS(funcName string, a1 int) unsafe.Pointer { + f := lw.getFunc(funcName) + return C.call_vp_ss(unsafe.Pointer(f), C.ssize_t(a1)) +} + +func (lw *LibWrapper) Call_VP_VP_SS(funcName string, a1 unsafe.Pointer, a2 int) unsafe.Pointer { + f := lw.getFunc(funcName) + return C.call_vp_vp_ss(unsafe.Pointer(f), a1, C.ssize_t(a2)) +} diff --git a/pkg/utils/lib_wrapper/lib_wrapper_unix.go b/pkg/utils/lib_wrapper/lib_wrapper_unix.go new file mode 100644 index 000000000..68e102408 --- /dev/null +++ b/pkg/utils/lib_wrapper/lib_wrapper_unix.go @@ -0,0 +1,39 @@ +//go:build darwin || linux +// +build darwin linux + +package lib_wrapper + +/* +#include +#include + +*/ +import "C" +import ( + log "github.com/sirupsen/logrus" + "unsafe" +) + +func LoadModule(pth string) *LibWrapper { + cPth := NewCString(pth) + defer cPth.Free() + + mod := C.dlopen((*C.char)(cPth.P), C.RTLD_LAZY) + if mod == nil { + log.Panicf("dlopen for %s failed", pth) + } + return &LibWrapper{ + module: unsafe.Pointer(mod), + } +} + +func (lw *LibWrapper) loadFunc(funcName string) FunctionPtr { + cFuncName := NewCString(funcName) + defer cFuncName.Free() + + f := C.dlsym(lw.module, (*C.char)(cFuncName.P)) + if f == nil { + log.Panicf("dlsym for %s failed", funcName) + } + return FunctionPtr(f) +} diff --git a/pkg/utils/lib_wrapper/lib_wrapper_windows.go b/pkg/utils/lib_wrapper/lib_wrapper_windows.go new file mode 100644 index 000000000..dcebcbf65 --- /dev/null +++ b/pkg/utils/lib_wrapper/lib_wrapper_windows.go @@ -0,0 +1,30 @@ +//go:build windows +// +build windows + +package lib_wrapper + +import "C" +import ( + log "github.com/sirupsen/logrus" + "syscall" + "unsafe" +) + +func LoadModule(pth string) *LibWrapper { + mod, err := syscall.LoadLibrary(pth) + if err != nil { + log.Panicf("LoadLibrary for %s failed: %w", pth, err) + } + return &LibWrapper{ + module: unsafe.Pointer(mod), + } +} + +func (lw *LibWrapper) loadFunc(funcName string) FunctionPtr { + mod := syscall.Handle(lw.module) + f, err := syscall.GetProcAddress(mod, funcName) + if err != nil { + log.Panicf("GetProcAddress for %s failed", funcName) + } + return FunctionPtr(f) +} diff --git a/pkg/utils/lib_wrapper/str.go b/pkg/utils/lib_wrapper/str.go new file mode 100644 index 000000000..52470967e --- /dev/null +++ b/pkg/utils/lib_wrapper/str.go @@ -0,0 +1,20 @@ +package lib_wrapper + +/* +#include +*/ +import "C" +import "unsafe" + +type UnsafeStr struct { + P unsafe.Pointer +} + +func NewCString(s string) *UnsafeStr { + us := C.CString(s) + return &UnsafeStr{P: unsafe.Pointer(us)} +} + +func (us *UnsafeStr) Free() { + C.free(us.P) +} From c8d08e5eae9a421d6280e7940d2ae6a1518258cd Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 22 Feb 2022 17:12:32 +0100 Subject: [PATCH 0383/2916] Implement symlink support ExtractTarGz --- pkg/kluctl_project/load.go | 2 +- pkg/utils/tar.go | 23 +++++++++++++++-------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/pkg/kluctl_project/load.go b/pkg/kluctl_project/load.go index 182ed7311..c70ba5700 100644 --- a/pkg/kluctl_project/load.go +++ b/pkg/kluctl_project/load.go @@ -163,7 +163,7 @@ func LoadKluctlProject(args LoadKluctlProjectArgs, cb func(ctx *KluctlProjectCon func loadKluctlProjectFromArchive(fromArchive string, fromArchiveMetadata string, tmpDir string) (*KluctlProjectContext, error) { var dir string if utils.IsFile(fromArchiveMetadata) { - err := utils.ExtractTarGz(fromArchive, tmpDir) + err := utils.ExtractTarGzFile(fromArchive, tmpDir) if err != nil { return nil, fmt.Errorf("failed to extract archive %v: %w", fromArchive, err) } diff --git a/pkg/utils/tar.go b/pkg/utils/tar.go index d9670af8f..66b26e214 100644 --- a/pkg/utils/tar.go +++ b/pkg/utils/tar.go @@ -8,8 +8,7 @@ import ( "os" "path" ) - -func ExtractTarGz(tarGzPath string, targetPath string) error { +func ExtractTarGzFile(tarGzPath string, targetPath string) error { f, err := os.Open(tarGzPath) if err != nil { return fmt.Errorf("archive %v could not be opened: %w", tarGzPath, err) @@ -22,7 +21,11 @@ func ExtractTarGz(tarGzPath string, targetPath string) error { } defer gz.Close() - tarReader := tar.NewReader(gz) + return ExtractTarGzStream(gz, targetPath) +} + +func ExtractTarGzStream(r io.Reader, targetPath string) error { + tarReader := tar.NewReader(r) for true { header, err := tarReader.Next() @@ -31,26 +34,30 @@ func ExtractTarGz(tarGzPath string, targetPath string) error { } if err != nil { - return fmt.Errorf("ExtractTarGz: Next() for %v failed: %w", tarGzPath, err) + return fmt.Errorf("ExtractTarGz: Next() failed: %w", err) } switch header.Typeflag { case tar.TypeDir: if err := os.Mkdir(path.Join(targetPath, header.Name), 0755); err != nil { - return fmt.Errorf("ExtractTarGz: Mkdir() for %v failed: %w", tarGzPath, err) + return fmt.Errorf("ExtractTarGz: Mkdir() failed: %w", err) } case tar.TypeReg: outFile, err := os.Create(path.Join(targetPath, header.Name)) if err != nil { - return fmt.Errorf("ExtractTarGz: Create() for %v failed: %w", tarGzPath, err) + return fmt.Errorf("ExtractTarGz: Create() failed: %w", err) } _, err = io.Copy(outFile, tarReader) _ = outFile.Close() if err != nil { - return fmt.Errorf("ExtractTarGz: Copy() for %v failed: %w", tarGzPath, err) + return fmt.Errorf("ExtractTarGz: Copy() failed: %w", err) + } + case tar.TypeSymlink: + if err := os.Symlink(header.Linkname, path.Join(targetPath, header.Name)); err != nil { + return fmt.Errorf("ExtractTarGz: Symlink() failed: %w", err) } default: - return fmt.Errorf("ExtractTarGz: uknown type for %v: %v in %v", tarGzPath, header.Typeflag, header.Name) + return fmt.Errorf("ExtractTarGz: uknown type %v in %v", header.Typeflag, header.Name) } } return nil From 325d921f90aabed305b3917f84d4c149be38c483 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 22 Feb 2022 17:13:34 +0100 Subject: [PATCH 0384/2916] Reimplement python package to be lib_wrapper based --- pkg/python/.gitignore | 1 + pkg/python/dict.go | 20 ++++--- pkg/python/error.go | 7 +-- pkg/python/interpreter.go | 31 +++++------ pkg/python/lib.go | 87 +++++++++++++++++++++++++++++++ pkg/python/lib_darwin.go | 6 +++ pkg/python/lib_linux.go | 6 +++ pkg/python/lib_windows.go | 6 +++ pkg/python/list.go | 12 ++--- pkg/python/misc.go | 57 +++++++++++--------- pkg/python/object.go | 56 +++++++++----------- pkg/python/string.go | 17 +++--- pkg/python/variadic.c | 107 -------------------------------------- pkg/python/variadic.h | 16 ------ 14 files changed, 200 insertions(+), 229 deletions(-) create mode 100644 pkg/python/.gitignore create mode 100644 pkg/python/lib.go create mode 100644 pkg/python/lib_darwin.go create mode 100644 pkg/python/lib_linux.go create mode 100644 pkg/python/lib_windows.go delete mode 100644 pkg/python/variadic.c delete mode 100644 pkg/python/variadic.h diff --git a/pkg/python/.gitignore b/pkg/python/.gitignore new file mode 100644 index 000000000..aeeb43173 --- /dev/null +++ b/pkg/python/.gitignore @@ -0,0 +1 @@ +python-lib-*.tar.gz \ No newline at end of file diff --git a/pkg/python/dict.go b/pkg/python/dict.go index 8f1cd525f..5e3b924c1 100644 --- a/pkg/python/dict.go +++ b/pkg/python/dict.go @@ -1,24 +1,22 @@ package python - -/* -#include "Python.h" -*/ import "C" -import "unsafe" +import ( + "github.com/codablock/kluctl/pkg/utils/lib_wrapper" +) type PyDict = PyObject func PyDict_New() *PyDict { - return (*PyList)(C.PyDict_New()) + return togo(pythonModule.Call_VP_PTRS("PyDict_New")) } func PyDict_FromObject(o *PyObject) *PyDict { - return (*PyDict)(o) + return o } func (d *PyDict) GetItemString(key string) *PyObject { - ckey := C.CString(key) - defer C.free(unsafe.Pointer(ckey)) + ckey := lib_wrapper.NewCString(key) + defer ckey.Free() - return (*PyObject)(C.PyDict_GetItemString((*C.PyObject)(d), ckey)) -} \ No newline at end of file + return togo(pythonModule.Call_VP_PTRS("PyDict_GetItemString", d.p, ckey.P)) +} diff --git a/pkg/python/error.go b/pkg/python/error.go index 61086f9cd..243a91582 100644 --- a/pkg/python/error.go +++ b/pkg/python/error.go @@ -1,10 +1,5 @@ package python -/* -#include "Python.h" -*/ -import "C" - func PyErr_Print() { - C.PyErr_PrintEx(1) + pythonModule.Call_V_PTRS("PyErr_Print") } diff --git a/pkg/python/interpreter.go b/pkg/python/interpreter.go index e2da88096..a451c1b1c 100644 --- a/pkg/python/interpreter.go +++ b/pkg/python/interpreter.go @@ -1,6 +1,5 @@ package python - import ( "log" "runtime" @@ -11,18 +10,18 @@ type PythonInterpreter struct { init chan error stopChan chan bool - threadState *PyThreadState + threadState PyThreadState } type pythonInterpreterCall struct { - fun func() error + fun func() error result chan error } func NewPythonInterpreter() (*PythonInterpreter, error) { p := &PythonInterpreter{ - calls: make(chan *pythonInterpreterCall), - init: make(chan error), + calls: make(chan *pythonInterpreterCall), + init: make(chan error), stopChan: make(chan bool), } @@ -41,11 +40,11 @@ func (p *PythonInterpreter) Stop() { func (p *PythonInterpreter) Run(fun func() error) error { c := &pythonInterpreterCall{ - fun: fun, + fun: fun, result: make(chan error), } p.calls <- c - err := <- c.result + err := <-c.result return err } @@ -78,9 +77,11 @@ func (p *PythonInterpreter) runThread() { for true { select { case c := <-p.calls: - PyEval_AcquireThread(p.threadState) - p.processCall(c) - PyEval_ReleaseThread(p.threadState) + func() { + PyEval_AcquireThread(p.threadState) + defer PyEval_ReleaseThread(p.threadState) + p.processCall(c) + }() case <-p.stopChan: return } @@ -94,13 +95,7 @@ func (p *PythonInterpreter) processCall(c *pythonInterpreterCall) { } err := c.fun() - c.result<-err -} - -var mainThreadState *PyThreadState - -func init() { - Py_Initialize() - mainThreadState = PyEval_SaveThread() + c.result <- err } +var mainThreadState PyThreadState diff --git a/pkg/python/lib.go b/pkg/python/lib.go new file mode 100644 index 000000000..9c2c779f2 --- /dev/null +++ b/pkg/python/lib.go @@ -0,0 +1,87 @@ +package python + +import ( + "bytes" + "compress/gzip" + "crypto/sha256" + "encoding/hex" + "fmt" + "github.com/codablock/kluctl/pkg/utils" + "github.com/codablock/kluctl/pkg/utils/lib_wrapper" + "io/ioutil" + "log" + "os" + "path/filepath" + "runtime" + "unsafe" +) + +var pythonModule *lib_wrapper.LibWrapper + +func init() { + libPath := decompressLib() + + var module string + if runtime.GOOS == "windows" { + module = "python310.dll" + } else if runtime.GOOS == "darwin" { + module = "lib/libpython3.10.dylib" + } else { + module = "lib/libpython3.10.so" + } + + pythonModule = lib_wrapper.LoadModule(filepath.Join(libPath, module)) + + err := Py_SetPythonHome(libPath) + if err != nil { + log.Panic(err) + } + + Py_Initialize() + mainThreadState = PyEval_SaveThread() +} + +func togo(p unsafe.Pointer) *PyObject { + if p == nil { + return nil + } + return &PyObject{p: p} +} + +func decompressLib() string { + tgz, err := pythonLib.ReadFile(fmt.Sprintf("python-lib-%s.tar.gz", runtime.GOOS)) + if err != nil { + log.Panic(err) + } + + hash := sha256.Sum256(tgz) + hashStr := hex.EncodeToString(hash[:]) + + libPath := filepath.Join(utils.GetTmpBaseDir(), fmt.Sprintf("python-libs-%s-%s", runtime.GOOS, hashStr[:16])) + if utils.Exists(libPath) { + return libPath + } + + g, err := gzip.NewReader(bytes.NewReader(tgz)) + if err != nil { + log.Panic(err) + } + defer g.Close() + + tmpLibPath, err := ioutil.TempDir(utils.GetTmpBaseDir(), "python-libs-tmp-") + if err != nil { + log.Panic(err) + } + defer os.RemoveAll(tmpLibPath) + + err = utils.ExtractTarGzStream(g, tmpLibPath) + if err != nil { + log.Panic(err) + } + + err = os.Rename(tmpLibPath, libPath) + if err != nil && !os.IsExist(err) { + log.Panic(err) + } + return libPath +} diff --git a/pkg/python/lib_darwin.go b/pkg/python/lib_darwin.go new file mode 100644 index 000000000..708de5fff --- /dev/null +++ b/pkg/python/lib_darwin.go @@ -0,0 +1,6 @@ +package python + +import "embed" + +//go:embed python-lib-darwin.tar.gz +var pythonLib embed.FS diff --git a/pkg/python/lib_linux.go b/pkg/python/lib_linux.go new file mode 100644 index 000000000..a67838c1b --- /dev/null +++ b/pkg/python/lib_linux.go @@ -0,0 +1,6 @@ +package python + +import "embed" + +//go:embed python-lib-linux.tar.gz +var pythonLib embed.FS diff --git a/pkg/python/lib_windows.go b/pkg/python/lib_windows.go new file mode 100644 index 000000000..9232a5115 --- /dev/null +++ b/pkg/python/lib_windows.go @@ -0,0 +1,6 @@ +package python + +import "embed" + +//go:embed python-lib-windows.tar.gz +var pythonLib embed.FS diff --git a/pkg/python/list.go b/pkg/python/list.go index 8ac6d8e30..b537a2da7 100644 --- a/pkg/python/list.go +++ b/pkg/python/list.go @@ -1,14 +1,10 @@ package python -/* -#include "Python.h" -*/ -import "C" - type PyList = PyObject func PyList_New(len int) *PyList { - return (*PyList)(C.PyList_New(C.Py_ssize_t(len))) + p := pythonModule.Call_VP_SS("PyList_New", len) + return togo(p) } func PyList_FromObject(l *PyObject) *PyList { @@ -16,9 +12,9 @@ func PyList_FromObject(l *PyObject) *PyList { } func (l *PyList) GetItem(pos int) *PyObject { - return (*PyObject)(C.PyList_GetItem((*C.PyObject)(l), C.Py_ssize_t(pos))) + return togo(pythonModule.Call_VP_VP_SS("PyList_GetItem", l.p, pos)) } func (l *PyList) Append(item *PyObject) int { - return int(C.PyList_Append((*C.PyObject)(l), (*C.PyObject)(item))) + return pythonModule.Call_I_PTRS("PyList_Append", l.p, item.p) } diff --git a/pkg/python/misc.go b/pkg/python/misc.go index 78d3bcc17..cb32f4f30 100644 --- a/pkg/python/misc.go +++ b/pkg/python/misc.go @@ -1,49 +1,58 @@ package python -/* -#cgo pkg-config: python3 -#include "Python.h" -*/ -import "C" - import ( + "fmt" + "github.com/codablock/kluctl/pkg/utils/lib_wrapper" "unsafe" ) -type PyThreadState C.PyThreadState +type PyThreadState unsafe.Pointer + +func Py_SetPythonHome(home string) error { + chome := lib_wrapper.NewCString(home) + defer chome.Free() + + newHome := togo(pythonModule.Call_VP_PTRS("Py_DecodeLocale", chome.P, nil)) + if newHome == nil { + return fmt.Errorf("fail to call Py_DecodeLocale on '%s'", home) + } + pythonModule.Call_V_PTRS("Py_SetPythonHome", newHome.p) + + return nil +} func Py_Initialize() { - C.Py_Initialize() + pythonModule.Call_V_PTRS("Py_Initialize") } -func Py_NewInterpreter() *PyThreadState { - p := C.Py_NewInterpreter() - return (*PyThreadState)(unsafe.Pointer(p)) +func Py_NewInterpreter() PyThreadState { + p := pythonModule.Call_VP_PTRS("Py_NewInterpreter") + return (PyThreadState)(p) } -func Py_EndInterpreter(o *PyThreadState) { - C.Py_EndInterpreter((*C.PyThreadState)(unsafe.Pointer(o))) +func Py_EndInterpreter(o PyThreadState) { + pythonModule.Call_V_PTRS("Py_EndInterpreter", unsafe.Pointer(o)) } -func PyEval_AcquireThread(o *PyThreadState) { - C.PyEval_AcquireThread((*C.PyThreadState)(unsafe.Pointer(o))) +func PyEval_AcquireThread(o PyThreadState) { + pythonModule.Call_V_PTRS("PyEval_AcquireThread", unsafe.Pointer(o)) } -func PyEval_ReleaseThread(o *PyThreadState) { - C.PyEval_ReleaseThread((*C.PyThreadState)(unsafe.Pointer(o))) +func PyEval_ReleaseThread(o PyThreadState) { + pythonModule.Call_V_PTRS("PyEval_ReleaseThread", unsafe.Pointer(o)) } -func PyEval_SaveThread() *PyThreadState { - return (*PyThreadState)(C.PyEval_SaveThread()) +func PyEval_SaveThread() PyThreadState { + return PyThreadState(pythonModule.Call_VP_PTRS("PyEval_SaveThread")) } -func PyThreadState_Get() *PyThreadState { - return (*PyThreadState)(C.PyThreadState_Get()) +func PyThreadState_Get() PyThreadState { + return PyThreadState(pythonModule.Call_VP_PTRS("PyThreadState_Get")) } func PyImport_ImportModule(name string) *PyObject { - cname := C.CString(name) - defer C.free(unsafe.Pointer(cname)) + cname := lib_wrapper.NewCString(name) + defer cname.Free() - return (*PyObject)(C.PyImport_ImportModule(cname)) + return togo(pythonModule.Call_VP_PTRS("PyImport_ImportModule", cname.P)) } diff --git a/pkg/python/object.go b/pkg/python/object.go index 6c02d69d9..2f143ae48 100644 --- a/pkg/python/object.go +++ b/pkg/python/object.go @@ -1,57 +1,53 @@ package python -/* -#include "Python.h" -#include "variadic.h" -*/ -import "C" -import "unsafe" +import ( + "github.com/codablock/kluctl/pkg/utils/lib_wrapper" + "unsafe" +) -const MaxVariadicLength = 20 - -type PyObject C.PyObject +type PyObject struct { + p unsafe.Pointer +} func (p *PyObject) IncRef() { - C.Py_IncRef((*C.PyObject)(p)) + pythonModule.Call_V_PTRS("Py_IncRef", p.p) } func (p *PyObject) DecRef() { - C.Py_DecRef((*C.PyObject)(p)) + pythonModule.Call_V_PTRS("Py_DecRef", p.p) } func (p *PyObject) Length() int { - return int(C.PyObject_Length((*C.PyObject)(p))) + return pythonModule.Call_I_PTRS("PyObject_Length", p.p) } func (p *PyObject) GetAttrString(attr_name string) *PyObject { - cattr_name := C.CString(attr_name) - defer C.free(unsafe.Pointer(cattr_name)) + cattr_name := lib_wrapper.NewCString(attr_name) + defer cattr_name.Free() - return (*PyObject)(C.PyObject_GetAttrString((*C.PyObject)(p), cattr_name)) + return togo(pythonModule.Call_VP_PTRS("PyObject_GetAttrString", p.p, cattr_name.P)) } func (p *PyObject) SetAttrString(attr_name string, v *PyObject) int { - cattr_name := C.CString(attr_name) - defer C.free(unsafe.Pointer(cattr_name)) + cattr_name := lib_wrapper.NewCString(attr_name) + defer cattr_name.Free() - return int(C.PyObject_SetAttrString((*C.PyObject)(p), cattr_name, (*C.PyObject)(v))) + return pythonModule.Call_I_PTRS("PyObject_SetAttrString", cattr_name.P, v.p) } func (p *PyObject) CallObject(args *PyObject) *PyObject { - return (*PyObject)(C.PyObject_CallObject((*C.PyObject)(p), (*C.PyObject)(args))) + var a unsafe.Pointer + if args != nil { + a = args.p + } + return togo(pythonModule.Call_VP_PTRS("PyObject_CallObject", p.p, a)) } func (p *PyObject) CallMethodObjArgs(name *PyObject, args ...*PyObject) *PyObject { - if len(args) > MaxVariadicLength { - panic("CallMethodObjArgs: too many arguments") - } - if len(args) == 0 { - return (*PyObject)(C._go_PyObject_CallMethodObjArgs((*C.PyObject)(p), (*C.PyObject)(name), 0, (**C.PyObject)(nil))) - } - - cargs := make([]*C.PyObject, len(args), len(args)) - for i, arg := range args { - cargs[i] = (*C.PyObject)(arg) + cargs := make([]unsafe.Pointer, len(args), len(args)+1) + for i, o := range args { + cargs[i] = o.p } - return (*PyObject)(C._go_PyObject_CallMethodObjArgs((*C.PyObject)(p), (*C.PyObject)(name), C.int(len(args)), (**C.PyObject)(unsafe.Pointer(&cargs[0])))) + cargs = append(cargs, nil) + return togo(pythonModule.Call_VP_PTRS_VARGS("PyObject_CallMethodObjArgs", []unsafe.Pointer{p.p, name.p}, cargs...)) } diff --git a/pkg/python/string.go b/pkg/python/string.go index 7c52a94b8..5299517ce 100644 --- a/pkg/python/string.go +++ b/pkg/python/string.go @@ -1,19 +1,18 @@ package python -/* -#include "Python.h" -*/ import "C" -import "unsafe" +import ( + "github.com/codablock/kluctl/pkg/utils/lib_wrapper" +) func PyUnicode_FromString(u string) *PyObject { - cu := C.CString(u) - defer C.free(unsafe.Pointer(cu)) + cu := lib_wrapper.NewCString(u) + defer cu.Free() - return (*PyObject)(C.PyUnicode_FromString(cu)) + return togo(pythonModule.Call_VP_PTRS("PyUnicode_FromString", cu.P)) } func PyUnicode_AsUTF8(unicode *PyObject) string { - cutf8 := C.PyUnicode_AsUTF8((*C.PyObject)(unicode)) - return C.GoString(cutf8) + cutf8 := pythonModule.Call_VP_PTRS("PyUnicode_AsUTF8", unicode.p) + return C.GoString((*C.char)(cutf8)) } diff --git a/pkg/python/variadic.c b/pkg/python/variadic.c deleted file mode 100644 index 5c490723a..000000000 --- a/pkg/python/variadic.c +++ /dev/null @@ -1,107 +0,0 @@ -/* -Unless explicitly stated otherwise all files in this repository are licensed -under the MIT License. -This product includes software developed at Datadog (https://www.datadoghq.com/). -Copyright 2018 Datadog, Inc. -*/ - -#include "Python.h" - -PyObject* _go_PyObject_CallFunctionObjArgs(PyObject *callable, int argc, PyObject **argv) { - - PyObject *result = NULL; - switch (argc) { - case 0: - return PyObject_CallFunctionObjArgs(callable, NULL); - case 1: - return PyObject_CallFunctionObjArgs(callable, argv[0], NULL); - case 2: - return PyObject_CallFunctionObjArgs(callable, argv[0], argv[1], NULL); - case 3: - return PyObject_CallFunctionObjArgs(callable, argv[0], argv[1], argv[2], NULL); - case 4: - return PyObject_CallFunctionObjArgs(callable, argv[0], argv[1], argv[2], argv[3], NULL); - case 5: - return PyObject_CallFunctionObjArgs(callable, argv[0], argv[1], argv[2], argv[3], argv[4], NULL); - case 6: - return PyObject_CallFunctionObjArgs(callable, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], NULL); - case 7: - return PyObject_CallFunctionObjArgs(callable, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], NULL); - case 8: - return PyObject_CallFunctionObjArgs(callable, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], NULL); - case 9: - return PyObject_CallFunctionObjArgs(callable, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], NULL); - case 10: - return PyObject_CallFunctionObjArgs(callable, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], NULL); - case 11: - return PyObject_CallFunctionObjArgs(callable, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], NULL); - case 12: - return PyObject_CallFunctionObjArgs(callable, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], NULL); - case 13: - return PyObject_CallFunctionObjArgs(callable, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], NULL); - case 14: - return PyObject_CallFunctionObjArgs(callable, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], NULL); - case 15: - return PyObject_CallFunctionObjArgs(callable, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], NULL); - case 16: - return PyObject_CallFunctionObjArgs(callable, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], NULL); - case 17: - return PyObject_CallFunctionObjArgs(callable, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], argv[16], NULL); - case 18: - return PyObject_CallFunctionObjArgs(callable, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], argv[16], argv[17], NULL); - case 19: - return PyObject_CallFunctionObjArgs(callable, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], argv[16], argv[17], argv[18], NULL); - case 20: - return PyObject_CallFunctionObjArgs(callable, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], argv[16], argv[17], argv[18], argv[19], NULL); - } - return result; -} -PyObject* _go_PyObject_CallMethodObjArgs(PyObject *obj, PyObject *name, int argc, PyObject **argv) { - - PyObject *result = NULL; - switch (argc) { - case 0: - return PyObject_CallMethodObjArgs(obj, name, NULL); - case 1: - return PyObject_CallMethodObjArgs(obj, name, argv[0], NULL); - case 2: - return PyObject_CallMethodObjArgs(obj, name, argv[0], argv[1], NULL); - case 3: - return PyObject_CallMethodObjArgs(obj, name, argv[0], argv[1], argv[2], NULL); - case 4: - return PyObject_CallMethodObjArgs(obj, name, argv[0], argv[1], argv[2], argv[3], NULL); - case 5: - return PyObject_CallMethodObjArgs(obj, name, argv[0], argv[1], argv[2], argv[3], argv[4], NULL); - case 6: - return PyObject_CallMethodObjArgs(obj, name, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], NULL); - case 7: - return PyObject_CallMethodObjArgs(obj, name, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], NULL); - case 8: - return PyObject_CallMethodObjArgs(obj, name, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], NULL); - case 9: - return PyObject_CallMethodObjArgs(obj, name, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], NULL); - case 10: - return PyObject_CallMethodObjArgs(obj, name, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], NULL); - case 11: - return PyObject_CallMethodObjArgs(obj, name, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], NULL); - case 12: - return PyObject_CallMethodObjArgs(obj, name, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], NULL); - case 13: - return PyObject_CallMethodObjArgs(obj, name, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], NULL); - case 14: - return PyObject_CallMethodObjArgs(obj, name, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], NULL); - case 15: - return PyObject_CallMethodObjArgs(obj, name, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], NULL); - case 16: - return PyObject_CallMethodObjArgs(obj, name, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], NULL); - case 17: - return PyObject_CallMethodObjArgs(obj, name, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], argv[16], NULL); - case 18: - return PyObject_CallMethodObjArgs(obj, name, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], argv[16], argv[17], NULL); - case 19: - return PyObject_CallMethodObjArgs(obj, name, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], argv[16], argv[17], argv[18], NULL); - case 20: - return PyObject_CallMethodObjArgs(obj, name, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], argv[16], argv[17], argv[18], argv[19], NULL); - } - return result; -} diff --git a/pkg/python/variadic.h b/pkg/python/variadic.h deleted file mode 100644 index 2aa7dbeda..000000000 --- a/pkg/python/variadic.h +++ /dev/null @@ -1,16 +0,0 @@ -/* -Unless explicitly stated otherwise all files in this repository are licensed -under the MIT License. -This product includes software developed at Datadog (https://www.datadoghq.com/). -Copyright 2018 Datadog, Inc. -*/ - -#ifndef VARIADIC_H -#define VARIADIC_H - -#include "Python.h" - -PyObject* _go_PyObject_CallFunctionObjArgs(PyObject *callable, int argc, PyObject **args); -PyObject* _go_PyObject_CallMethodObjArgs(PyObject *obj, PyObject *name, int argc, PyObject **args); - -#endif \ No newline at end of file From ce6d8375ef2037b00f0a574b4deed878117127c8 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 22 Feb 2022 14:52:38 +0100 Subject: [PATCH 0385/2916] Fix uses of path vs filepath --- cmd/kluctl/args/inclusion.go | 6 +++--- cmd/kluctl/commands/cmd_helm_pull.go | 3 +-- cmd/kluctl/commands/cmd_helm_update.go | 7 +++---- cmd/kluctl/commands/root.go | 4 ++-- pkg/deployment/deployment_collection.go | 9 ++++----- pkg/deployment/deployment_item.go | 25 ++++++++++++------------- pkg/deployment/deployment_project.go | 17 ++++++++--------- pkg/deployment/external_args.go | 4 ++-- pkg/deployment/helm_chart.go | 16 ++++++++-------- pkg/git/auth/git_credentials_file.go | 8 ++++---- pkg/git/mirrored_repo.go | 16 ++++++++-------- pkg/jinja2/jinja2.go | 11 +++++------ pkg/jinja2/source.go | 3 +-- pkg/k8s/k8s_cluster.go | 6 +++--- pkg/kluctl_project/git.go | 16 ++++++++++------ pkg/kluctl_project/load.go | 22 +++++++++++----------- pkg/kluctl_project/load_targets.go | 4 ++-- pkg/seal/sealer.go | 6 +++--- pkg/seal/secrets_loader.go | 9 ++++----- pkg/types/cluster_config.go | 4 ++-- pkg/utils/debugger.go | 2 ++ pkg/utils/tar.go | 8 ++++---- pkg/utils/utils.go | 4 ++-- 23 files changed, 104 insertions(+), 106 deletions(-) diff --git a/cmd/kluctl/args/inclusion.go b/cmd/kluctl/args/inclusion.go index 0e2f38950..c99e20413 100644 --- a/cmd/kluctl/args/inclusion.go +++ b/cmd/kluctl/args/inclusion.go @@ -5,7 +5,7 @@ import ( "github.com/codablock/kluctl/pkg/utils" "github.com/spf13/cobra" "os" - "path" + "path/filepath" "strings" ) @@ -32,13 +32,13 @@ func ParseInclusionFromArgs() (*utils.Inclusion, error) { inclusion.AddExclude("tag", tag) } for _, dir := range IncludeDeploymentDirs { - if path.IsAbs(dir) { + if filepath.IsAbs(dir) { return nil, fmt.Errorf("--include-deployment-dir path must be relative") } inclusion.AddInclude("deploymentItemDir", strings.ReplaceAll(dir, string(os.PathSeparator), "/")) } for _, dir := range ExcludeDeploymentDirs { - if path.IsAbs(dir) { + if filepath.IsAbs(dir) { return nil, fmt.Errorf("--exclude-deployment-dir path must be relative") } inclusion.AddExclude("deploymentItemDir", strings.ReplaceAll(dir, string(os.PathSeparator), "/")) diff --git a/cmd/kluctl/commands/cmd_helm_pull.go b/cmd/kluctl/commands/cmd_helm_pull.go index 631f5f3fd..dd713626d 100644 --- a/cmd/kluctl/commands/cmd_helm_pull.go +++ b/cmd/kluctl/commands/cmd_helm_pull.go @@ -7,7 +7,6 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" "io/fs" - "path" "path/filepath" ) @@ -17,7 +16,7 @@ func runCmdHelmPull(cmd *cobra.Command, args_ []string) error { rootPath = args.LocalDeployment } err := filepath.WalkDir(rootPath, func(p string, d fs.DirEntry, err error) error { - fname := path.Base(p) + fname := filepath.Base(p) if fname == "helm-chart.yml" { log.Infof("Pulling for %s", p) chart, err := deployment.NewHelmChart(p) diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go index 81f14775f..407be051a 100644 --- a/cmd/kluctl/commands/cmd_helm_update.go +++ b/cmd/kluctl/commands/cmd_helm_update.go @@ -9,7 +9,6 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" "io/fs" - "path" "path/filepath" ) @@ -24,7 +23,7 @@ func runCmdHelmUpdate(cmd *cobra.Command, args_ []string) error { rootPath = args.LocalDeployment } err := filepath.WalkDir(rootPath, func(p string, d fs.DirEntry, err error) error { - fname := path.Base(p) + fname := filepath.Base(p) if fname == "helm-chart.yml" { chart, err := deployment.NewHelmChart(p) if err != nil { @@ -52,7 +51,7 @@ func runCmdHelmUpdate(cmd *cobra.Command, args_ []string) error { return err } - chartsDir := path.Join(path.Dir(p), "charts") + chartsDir := filepath.Join(filepath.Dir(p), "charts") // we need to list all files contained inside the charts dir BEFORE doing the pull, so that we later // know what got deleted @@ -86,7 +85,7 @@ func runCmdHelmUpdate(cmd *cobra.Command, args_ []string) error { } if commit { - msg := fmt.Sprintf("Updated helm chart %s from %s to %s", path.Dir(p), oldVersion, newVersion) + msg := fmt.Sprintf("Updated helm chart %s from %s to %s", filepath.Dir(p), oldVersion, newVersion) log.Infof("Committing: %s", msg) r, err := git.PlainOpen(rootPath) if err != nil { diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index 6500a6ed3..0bb054e95 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -23,7 +23,7 @@ import ( log "github.com/sirupsen/logrus" "net/http" "os" - "path" + "path/filepath" "strings" "time" @@ -47,7 +47,7 @@ func checkNewVersion() { return } - versionCheckPath := path.Join(utils.GetTmpBaseDir(), "version_check.yml") + versionCheckPath := filepath.Join(utils.GetTmpBaseDir(), "version_check.yml") var versionCheckState VersionCheckState err := yaml.ReadYamlFile(versionCheckPath, &versionCheckState) if err == nil { diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index 0b21fa80c..2f8543183 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -13,7 +13,6 @@ import ( "io/fs" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "path" "path/filepath" "reflect" "strings" @@ -75,7 +74,7 @@ func findDeploymentItemIndex(project *DeploymentProject, pth *string, indexes ma } var dir2 *string index := 0 - dir := path.Join(project.dir, *pth) + dir := filepath.Join(project.dir, *pth) absDir, err := filepath.Abs(dir) if err != nil { // we pre-checked directories, so this should not happen @@ -166,12 +165,12 @@ func (c *DeploymentCollection) Seal(sealer *seal.Sealer) error { if err != nil { return err } - targetDir := path.Join(c.project.sealedSecretsDir, path.Dir(relPath)) - targetFile := path.Join(targetDir, *c.project.config.SealedSecrets.OutputPattern, path.Base(p)) + targetDir := filepath.Join(c.project.sealedSecretsDir, filepath.Dir(relPath)) + targetFile := filepath.Join(targetDir, *c.project.config.SealedSecrets.OutputPattern, filepath.Base(p)) targetFile = targetFile[:len(targetFile)-len(sealmeExt)] err = sealer.SealFile(p, targetFile) if err != nil { - return fmt.Errorf("failed sealing %s: %w", path.Base(p), err) + return fmt.Errorf("failed sealing %s: %w", filepath.Base(p), err) } return nil }) diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 6b381f833..a5a27daa4 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -11,7 +11,6 @@ import ( "io/ioutil" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "os" - "path" "path/filepath" "sigs.k8s.io/kustomize/api/filesys" "sigs.k8s.io/kustomize/api/krusty" @@ -71,8 +70,8 @@ func NewDeploymentItem(project *DeploymentProject, collection *DeploymentCollect di.relRenderedDir = fmt.Sprintf("%s-%d", di.relRenderedDir, di.index) } - di.renderedDir = path.Join(di.collection.RenderDir, di.relRenderedDir) - di.renderedYamlPath = path.Join(di.renderedDir, ".rendered.yml") + di.renderedDir = filepath.Join(di.collection.RenderDir, di.relRenderedDir) + di.renderedYamlPath = filepath.Join(di.renderedDir, ".rendered.yml") } return di, nil } @@ -118,9 +117,9 @@ func (di *deploymentItem) render(k *k8s.K8sCluster, wp *utils.WorkerPoolWithErro var excludePatterns []string excludePatterns = append(excludePatterns, di.project.config.TemplateExcludes...) - if utils.IsFile(path.Join(*di.dir, "helm-chart.yml")) { + if utils.IsFile(filepath.Join(*di.dir, "helm-chart.yml")) { // never try to render helm charts - excludePatterns = append(excludePatterns, path.Join(di.relToProjectItemDir, "charts/**")) + excludePatterns = append(excludePatterns, filepath.Join(di.relToProjectItemDir, "charts/**")) } if !di.collection.forSeal { // .sealme files are rendered while sealing and not while deploying @@ -170,7 +169,7 @@ func (di *deploymentItem) resolveSealedSecrets() error { baseSourcePath := di.project.sealedSecretsDir var y map[string]interface{} - err := yaml.ReadYamlFile(path.Join(di.renderedDir, "kustomization.yml"), &y) + err := yaml.ReadYamlFile(filepath.Join(di.renderedDir, "kustomization.yml"), &y) if err != nil { return err } @@ -179,22 +178,22 @@ func (di *deploymentItem) resolveSealedSecrets() error { return err } for _, resource := range l { - p := path.Join(di.renderedDir, resource) + p := filepath.Join(di.renderedDir, resource) if utils.Exists(p) || !utils.Exists(p+sealmeExt) { continue } - relDir, err := filepath.Rel(di.renderedDir, path.Dir(p)) + relDir, err := filepath.Rel(di.renderedDir, filepath.Dir(p)) if err != nil { return err } - fname := path.Base(p) + fname := filepath.Base(p) - baseError := fmt.Sprintf("failed to resolve SealedSecret %s", path.Clean(path.Join(di.project.dir, resource))) + baseError := fmt.Sprintf("failed to resolve SealedSecret %s", filepath.Clean(filepath.Join(di.project.dir, resource))) if sealedSecretsDir == nil { return fmt.Errorf("%s. Sealed secrets dir could not be determined", baseError) } - sourcePath := path.Clean(path.Join(baseSourcePath, di.relRenderedDir, relDir, *sealedSecretsDir, fname)) - targetPath := path.Join(di.renderedDir, relDir, fname) + sourcePath := filepath.Clean(filepath.Join(baseSourcePath, di.relRenderedDir, relDir, *sealedSecretsDir, fname)) + targetPath := filepath.Join(di.renderedDir, relDir, fname) if !utils.IsFile(sourcePath) { return fmt.Errorf("%s. %s not found. You might need to seal it first", baseError, sourcePath) } @@ -258,7 +257,7 @@ func (di *deploymentItem) prepareKusomizationYaml() error { return nil } - kustomizeYamlPath := path.Join(di.renderedDir, "kustomization.yml") + kustomizeYamlPath := filepath.Join(di.renderedDir, "kustomization.yml") if !utils.IsFile(kustomizeYamlPath) { return nil } diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go index ebd640feb..c2ddb64fe 100644 --- a/pkg/deployment/deployment_project.go +++ b/pkg/deployment/deployment_project.go @@ -8,7 +8,6 @@ import ( "github.com/codablock/kluctl/pkg/utils" "github.com/codablock/kluctl/pkg/utils/uo" log "github.com/sirupsen/logrus" - "path" "path/filepath" "strings" "sync" @@ -63,9 +62,9 @@ func (p *DeploymentProject) MergeSecretsIntoAllChildren(vars *uo.UnstructuredObj } func (p *DeploymentProject) loadConfig(k *k8s.K8sCluster) error { - configPath := path.Join(p.dir, "deployment.yml") + configPath := filepath.Join(p.dir, "deployment.yml") if !utils.Exists(configPath) { - if utils.Exists(path.Join(p.dir, "kustomization.yml")) { + if utils.Exists(filepath.Join(p.dir, "kustomization.yml")) { return fmt.Errorf("deployment.yml not found but folder %s contains a kustomization.yml", p.dir) } return fmt.Errorf("%s not found", p.dir) @@ -112,9 +111,9 @@ func (p *DeploymentProject) loadConfig(k *k8s.K8sCluster) error { continue } if item.Path != nil { - item.Tags = []string{path.Base(*item.Path)} + item.Tags = []string{filepath.Base(*item.Path)} } else if item.Include != nil { - item.Tags = []string{path.Base(*item.Include)} + item.Tags = []string{filepath.Base(*item.Include)} } } @@ -146,7 +145,7 @@ func (p *DeploymentProject) checkDeploymentDirs() error { pth = *di.Include } - diDir := path.Join(p.dir, pth) + diDir := filepath.Join(p.dir, pth) diDir, err := filepath.Abs(diDir) if err != nil { return err @@ -164,9 +163,9 @@ func (p *DeploymentProject) checkDeploymentDirs() error { } if di.Path != nil { - pth = path.Join(diDir, "kustomization.yml") + pth = filepath.Join(diDir, "kustomization.yml") } else { - pth = path.Join(diDir, "deployment.yml") + pth = filepath.Join(diDir, "deployment.yml") } if !utils.IsFile(pth) { return fmt.Errorf("%s not found or not a file", pth) @@ -181,7 +180,7 @@ func (p *DeploymentProject) loadIncludes(k *k8s.K8sCluster) error { continue } - incDir := path.Join(p.dir, *inc.Include) + incDir := filepath.Join(p.dir, *inc.Include) varsCtx := p.VarsCtx.Copy() err := varsCtx.LoadVarsList(k, p.getRenderSearchDirs(), inc.Vars) diff --git a/pkg/deployment/external_args.go b/pkg/deployment/external_args.go index c3423c4ad..e32029648 100644 --- a/pkg/deployment/external_args.go +++ b/pkg/deployment/external_args.go @@ -6,7 +6,7 @@ import ( "github.com/codablock/kluctl/pkg/types" "github.com/codablock/kluctl/pkg/utils/uo" "github.com/codablock/kluctl/pkg/yaml" - "path" + "path/filepath" "regexp" "strings" ) @@ -47,7 +47,7 @@ func CheckRequiredDeployArgs(dir string, varsCtx *jinja2.VarsCtx, deployArgs *uo var conf types.DeploymentProjectConfig - err := yaml.ReadYamlFile(path.Join(dir, "deployment.yml"), &conf) + err := yaml.ReadYamlFile(filepath.Join(dir, "deployment.yml"), &conf) if err != nil { // If that failed, it might be that conditional jinja blocks are present in the config, so lets try loading // the config in rendered form. If it fails due to missing args now, we can't help much with better error diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index eb72fb742..f14816395 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -10,7 +10,7 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "os" "os/exec" - "path" + "path/filepath" "regexp" "sort" "strings" @@ -83,9 +83,9 @@ func (c *helmChart) Pull() error { return err } - dir := path.Dir(c.configFile) - targetDir := path.Join(dir, "charts") - rmDir := path.Join(targetDir, chartName) + dir := filepath.Dir(c.configFile) + targetDir := filepath.Join(dir, "charts") + rmDir := filepath.Join(targetDir, chartName) _ = os.RemoveAll(rmDir) var args []string @@ -154,10 +154,10 @@ func (c *helmChart) Render(k *k8s.K8sCluster) error { if err != nil { return err } - dir := path.Dir(c.configFile) - chartDir := path.Join(dir, "charts", chartName) - valuesPath := path.Join(dir, "helm-values.yml") - outputPath := path.Join(dir, c.Config.Output) + dir := filepath.Dir(c.configFile) + chartDir := filepath.Join(dir, "charts", chartName) + valuesPath := filepath.Join(dir, "helm-values.yml") + outputPath := filepath.Join(dir, c.Config.Output) args := []string{"template", c.Config.ReleaseName, chartDir} diff --git a/pkg/git/auth/git_credentials_file.go b/pkg/git/auth/git_credentials_file.go index 41aab3a9f..82fb9851c 100644 --- a/pkg/git/auth/git_credentials_file.go +++ b/pkg/git/auth/git_credentials_file.go @@ -9,7 +9,7 @@ import ( log "github.com/sirupsen/logrus" giturls "github.com/whilp/git-urls" "os" - "path" + "path/filepath" ) type GitCredentialsFileAuthProvider struct { @@ -21,18 +21,18 @@ func (a *GitCredentialsFileAuthProvider) BuildAuth(gitUrl git_url.GitUrl) transp log.Warningf("Could not determine home directory: %v", err) return nil } - auth := a.tryBuildAuth(gitUrl, path.Join(home, ".git-credentials")) + auth := a.tryBuildAuth(gitUrl, filepath.Join(home, ".git-credentials")) if auth != nil { return auth } if xdgHome, ok := os.LookupEnv("XDG_CONFIG_HOME"); ok && xdgHome != "" { - auth = a.tryBuildAuth(gitUrl, path.Join(xdgHome, ".git-credentials")) + auth = a.tryBuildAuth(gitUrl, filepath.Join(xdgHome, ".git-credentials")) if auth != nil { return auth } } else { - auth = a.tryBuildAuth(gitUrl, path.Join(home, ".config/.git-credentials")) + auth = a.tryBuildAuth(gitUrl, filepath.Join(home, ".config/.git-credentials")) if auth != nil { return auth } diff --git a/pkg/git/mirrored_repo.go b/pkg/git/mirrored_repo.go index 249327891..06f9fa059 100644 --- a/pkg/git/mirrored_repo.go +++ b/pkg/git/mirrored_repo.go @@ -11,12 +11,12 @@ import ( log "github.com/sirupsen/logrus" "io/ioutil" "os" - "path" + "path/filepath" "strings" ) import "github.com/gofrs/flock" -var cacheBaseDir = path.Join(utils.GetTmpBaseDir(), "git-cache") +var cacheBaseDir = filepath.Join(utils.GetTmpBaseDir(), "git-cache") type MirroredGitRepo struct { url git_url.GitUrl @@ -35,7 +35,7 @@ func NewMirroredGitRepo(u git_url.GitUrl) (*MirroredGitRepo, error) { o := &MirroredGitRepo{ url: u, remoteName: remoteName, - mirrorDir: path.Join(cacheBaseDir, remoteName), + mirrorDir: filepath.Join(cacheBaseDir, remoteName), } if !utils.IsDirectory(o.mirrorDir) { @@ -44,7 +44,7 @@ func NewMirroredGitRepo(u git_url.GitUrl) (*MirroredGitRepo, error) { return nil, fmt.Errorf("failed to create mirror repo for %v: %w", u.String(), err) } } - o.fileLock = flock.New(path.Join(o.mirrorDir, ".cache.lock")) + o.fileLock = flock.New(filepath.Join(o.mirrorDir, ".cache.lock")) return o, nil } @@ -120,7 +120,7 @@ func (g *MirroredGitRepo) cleanupMirrorDir() error { if fi.Name() == ".cache.lock" { continue } - _ = os.RemoveAll(path.Join(g.mirrorDir, fi.Name())) + _ = os.RemoveAll(filepath.Join(g.mirrorDir, fi.Name())) } } return nil @@ -173,7 +173,7 @@ func (g *MirroredGitRepo) update(repoDir string) error { } func (g *MirroredGitRepo) cloneOrUpdate() error { - initMarker := path.Join(g.mirrorDir, ".cache2.init") + initMarker := filepath.Join(g.mirrorDir, ".cache2.init") if utils.IsFile(initMarker) { return g.update(g.mirrorDir) } @@ -216,7 +216,7 @@ func (g *MirroredGitRepo) cloneOrUpdate() error { return err } for _, fi := range files { - err = os.Rename(path.Join(tmpMirrorDir, fi.Name()), path.Join(g.mirrorDir, fi.Name())) + err = os.Rename(filepath.Join(tmpMirrorDir, fi.Name()), filepath.Join(g.mirrorDir, fi.Name())) if err != nil { return err } @@ -260,7 +260,7 @@ func (g *MirroredGitRepo) CloneProject(ref string, targetDir string) error { } func buildRemoteName(u git_url.GitUrl) string { - r := path.Base(u.Path) + r := filepath.Base(u.Path) if strings.HasSuffix(r, ".git") { r = r[:len(r)-len(".git")] } diff --git a/pkg/jinja2/jinja2.go b/pkg/jinja2/jinja2.go index 42b581771..97836fc98 100644 --- a/pkg/jinja2/jinja2.go +++ b/pkg/jinja2/jinja2.go @@ -14,7 +14,6 @@ import ( "io/fs" "io/ioutil" "os" - "path" "path/filepath" "strings" "sync" @@ -133,7 +132,7 @@ func (j *Jinja2) isMaybeTemplate(template string, searchDirs []string, isString } } else { for _, s := range searchDirs { - b, err := ioutil.ReadFile(path.Join(s, template)) + b, err := ioutil.ReadFile(filepath.Join(s, template)) if err != nil { continue } @@ -358,7 +357,7 @@ func (j *Jinja2) needsRender(path string, excludedPatterns []string) bool { } func (j *Jinja2) RenderDirectory(rootDir string, searchDirs []string, relSourceDir string, excludePatterns []string, subdir string, targetDir string, vars *uo.UnstructuredObject) error { - walkDir := path.Join(rootDir, relSourceDir, subdir) + walkDir := filepath.Join(rootDir, relSourceDir, subdir) var jobs []*RenderJob @@ -368,15 +367,15 @@ func (j *Jinja2) RenderDirectory(rootDir string, searchDirs []string, relSourceD return err } if d.IsDir() { - err = os.MkdirAll(path.Join(targetDir, relPath), 0o700) + err = os.MkdirAll(filepath.Join(targetDir, relPath), 0o700) if err != nil { return err } return nil } - sourcePath := path.Clean(path.Join(subdir, relPath)) - targetPath := path.Join(targetDir, relPath) + sourcePath := filepath.Clean(filepath.Join(subdir, relPath)) + targetPath := filepath.Join(targetDir, relPath) if strings.Index(sourcePath, ".sealme") != -1 { sourcePath += "" diff --git a/pkg/jinja2/source.go b/pkg/jinja2/source.go index 0c47cc7f8..389c886f5 100644 --- a/pkg/jinja2/source.go +++ b/pkg/jinja2/source.go @@ -8,7 +8,6 @@ import ( "io/fs" "io/ioutil" "os" - "path" "path/filepath" "strings" ) @@ -27,7 +26,7 @@ func extractSource() (string, error) { return "", err } - err = filepath.WalkDir(path.Join(tmpDir, "wheel"), func(p string, d fs.DirEntry, err error) error { + err = filepath.WalkDir(filepath.Join(tmpDir, "wheel"), func(p string, d fs.DirEntry, err error) error { if !strings.HasSuffix(p,".whl") { return nil } diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index cb84ae260..b9e4c80a7 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -23,7 +23,7 @@ import ( "k8s.io/client-go/tools/clientcmd" "net/http" "net/url" - "path" + "path/filepath" "strings" "sync" "time" @@ -92,8 +92,8 @@ func NewK8sCluster(context string, dryRun bool) (*K8sCluster, error) { if err != nil { return nil, err } - discoveryCacheDir := path.Join(utils.GetTmpBaseDir(), "kube-cache/discovery", apiHost.Hostname()) - httpCacheDir := path.Join(utils.GetTmpBaseDir(), "kube-cache/http", apiHost.Hostname()) + discoveryCacheDir := filepath.Join(utils.GetTmpBaseDir(), "kube-cache/discovery", apiHost.Hostname()) + httpCacheDir := filepath.Join(utils.GetTmpBaseDir(), "kube-cache/http", apiHost.Hostname()) discovery2, err := disk.NewCachedDiscoveryClientForConfig(dynamic.ConfigFor(config), discoveryCacheDir, httpCacheDir, time.Hour*24) if err != nil { return nil, err diff --git a/pkg/kluctl_project/git.go b/pkg/kluctl_project/git.go index 961aeea63..66121ff19 100644 --- a/pkg/kluctl_project/git.go +++ b/pkg/kluctl_project/git.go @@ -6,8 +6,9 @@ import ( git_url "github.com/codablock/kluctl/pkg/git/git-url" types2 "github.com/codablock/kluctl/pkg/types" "github.com/codablock/kluctl/pkg/utils" + log "github.com/sirupsen/logrus" "os" - "path" + "path/filepath" "reflect" "strings" "sync" @@ -110,13 +111,16 @@ func (c *KluctlProjectContext) buildCloneDir(u git_url.GitUrl, ref string) (stri } ref = strings.ReplaceAll(ref, "/", "-") ref = strings.ReplaceAll(ref, "\\", "-") - baseName := path.Base(u.Path) - urlHash := utils.Sha256String(fmt.Sprintf("%s:%s", u.Host, u.Path)) - return path.Join(c.TmpDir, fmt.Sprintf("%s-%s", baseName, urlHash), ref), nil + urlPath := strings.ReplaceAll(u.Path, "/", string(os.PathSeparator)) + baseName := filepath.Base(urlPath) + urlHash := utils.Sha256String(fmt.Sprintf("%s:%s", u.Host, u.Path))[:16] + cloneDir := filepath.Join(c.TmpDir, fmt.Sprintf("%s-%s", baseName, urlHash), ref) + log.Tracef("buildCloneDir: ref=%s, urlPath=%s, baseName=%s, urlHash=%s, cloneDir=%s", ref, urlPath, baseName, urlHash, cloneDir) + return cloneDir, nil } func (c *KluctlProjectContext) cloneGitProject(gitProject types2.ExternalProject, defaultGitSubDir string, doAddInvolvedRepo bool, doLock bool) (result gitProjectInfo, err error) { - err = os.MkdirAll(path.Join(c.TmpDir, "git"), 0o700) + err = os.MkdirAll(filepath.Join(c.TmpDir, "git"), 0o700) if err != nil { return } @@ -132,7 +136,7 @@ func (c *KluctlProjectContext) cloneGitProject(gitProject types2.ExternalProject } dir := targetDir if subDir != "" { - dir = path.Join(dir, subDir) + dir = filepath.Join(dir, subDir) } mr, ok := c.mirroredRepos[gitProject.Project.Url.NormalizedRepoKey()] diff --git a/pkg/kluctl_project/load.go b/pkg/kluctl_project/load.go index c70ba5700..306e146f9 100644 --- a/pkg/kluctl_project/load.go +++ b/pkg/kluctl_project/load.go @@ -8,7 +8,7 @@ import ( log "github.com/sirupsen/logrus" "io/ioutil" "os" - "path" + "path/filepath" ) func (c *KluctlProjectContext) mergeClustersDirs(mergedClustersDir string, clustersInfos []gitProjectInfo) error { @@ -27,9 +27,9 @@ func (c *KluctlProjectContext) mergeClustersDirs(mergedClustersDir string, clust return err } for _, fi := range files { - p := path.Join(ci.dir, fi.Name()) + p := filepath.Join(ci.dir, fi.Name()) if utils.IsFile(p) { - err = utils.CopyFile(p, path.Join(mergedClustersDir, fi.Name())) + err = utils.CopyFile(p, filepath.Join(mergedClustersDir, fi.Name())) if err != nil { return err } @@ -47,7 +47,7 @@ func (c *KluctlProjectContext) load(allowGit bool) error { configPath := c.loadArgs.ProjectConfig if configPath == "" { - p := path.Join(kluctlProjectInfo.dir, ".kluctl.yml") + p := filepath.Join(kluctlProjectInfo.dir, ".kluctl.yml") if utils.IsFile(p) { configPath = p } @@ -74,7 +74,7 @@ func (c *KluctlProjectContext) load(allowGit bool) error { if ep == nil { p := kluctlProjectInfo.dir if defaultGitSubDir != "" { - p = path.Join(p, defaultGitSubDir) + p = filepath.Join(p, defaultGitSubDir) } return c.localProject(p), nil } @@ -112,7 +112,7 @@ func (c *KluctlProjectContext) load(allowGit bool) error { clustersInfos = append(clustersInfos, ci) } - mergedClustersDir := path.Join(c.TmpDir, "merged-clusters") + mergedClustersDir := filepath.Join(c.TmpDir, "merged-clusters") err = c.mergeClustersDirs(mergedClustersDir, clustersInfos) if err != nil { return err @@ -176,7 +176,7 @@ func loadKluctlProjectFromArchive(fromArchive string, fromArchiveMetadata string if fromArchiveMetadata != "" { metdataPath = fromArchiveMetadata } else { - metdataPath = path.Join(dir, "metadata.yml") + metdataPath = filepath.Join(dir, "metadata.yml") } var metadata types2.ArchiveMetadata @@ -187,10 +187,10 @@ func loadKluctlProjectFromArchive(fromArchive string, fromArchiveMetadata string p := NewKluctlProjectContext( LoadKluctlProjectArgs{ - ProjectConfig: path.Join(dir, ".kluctl.yml"), - LocalClusters: path.Join(dir, "clusters"), - LocalDeployment: path.Join(dir, "deployment"), - LocalSealedSecrets: path.Join(dir, "sealed-secrets"), + ProjectConfig: filepath.Join(dir, ".kluctl.yml"), + LocalClusters: filepath.Join(dir, "clusters"), + LocalDeployment: filepath.Join(dir, "deployment"), + LocalSealedSecrets: filepath.Join(dir, "sealed-secrets"), }, dir) p.involvedRepos = metadata.InvolvedRepos p.DynamicTargets = metadata.Targets diff --git a/pkg/kluctl_project/load_targets.go b/pkg/kluctl_project/load_targets.go index 2594e1fb4..c5889e9a7 100644 --- a/pkg/kluctl_project/load_targets.go +++ b/pkg/kluctl_project/load_targets.go @@ -9,7 +9,7 @@ import ( "github.com/codablock/kluctl/pkg/utils/uo" "github.com/codablock/kluctl/pkg/yaml" log "github.com/sirupsen/logrus" - "path" + "path/filepath" "reflect" "regexp" "sync" @@ -288,7 +288,7 @@ func (c *KluctlProjectContext) buildDynamicTarget(targetInfo *dynamicTargetInfo) if targetInfo.baseTarget.TargetConfig.File != nil { configFile = *targetInfo.baseTarget.TargetConfig.File } - configPath := path.Join(targetInfo.dir, configFile) + configPath := filepath.Join(targetInfo.dir, configFile) if !utils.IsFile(configPath) { return nil, fmt.Errorf("no target config file with name %s found in target", configFile) } diff --git a/pkg/seal/sealer.go b/pkg/seal/sealer.go index 71e34690d..af04ae796 100644 --- a/pkg/seal/sealer.go +++ b/pkg/seal/sealer.go @@ -16,7 +16,7 @@ import ( "golang.org/x/crypto/scrypt" "k8s.io/apimachinery/pkg/runtime/schema" "os" - "path" + "path/filepath" "reflect" "strconv" ) @@ -112,8 +112,8 @@ func (s *Sealer) encryptSecret(secret []byte, secretName string, secretNamespace } func (s *Sealer) SealFile(p string, targetFile string) error { - baseName := path.Base(targetFile) - err := os.MkdirAll(path.Dir(targetFile), 0o700) + baseName := filepath.Base(targetFile) + err := os.MkdirAll(filepath.Dir(targetFile), 0o700) if err != nil { return err } diff --git a/pkg/seal/secrets_loader.go b/pkg/seal/secrets_loader.go index 9d31fffeb..3517fe1c7 100644 --- a/pkg/seal/secrets_loader.go +++ b/pkg/seal/secrets_loader.go @@ -8,7 +8,6 @@ import ( "github.com/codablock/kluctl/pkg/utils/aws" "github.com/codablock/kluctl/pkg/utils/uo" "os" - "path" "path/filepath" "strings" ) @@ -39,10 +38,10 @@ func (s *SecretsLoader) LoadSecrets(source *types.SecretSource) (*uo.Unstructure func (s *SecretsLoader) loadSecretsFile(source *types.SecretSource) (*uo.UnstructuredObject, error) { var p string - if utils.Exists(path.Join(s.project.DeploymentDir, *source.Path)) { - p = path.Join(s.project.DeploymentDir, *source.Path) - } else if utils.Exists(path.Join(s.secretsDir, *source.Path)) { - p = path.Join(s.secretsDir, *source.Path) + if utils.Exists(filepath.Join(s.project.DeploymentDir, *source.Path)) { + p = filepath.Join(s.project.DeploymentDir, *source.Path) + } else if utils.Exists(filepath.Join(s.secretsDir, *source.Path)) { + p = filepath.Join(s.secretsDir, *source.Path) } if p == "" || !utils.Exists(p) { return nil, fmt.Errorf("secrets file %s does not exist", *source.Path) diff --git a/pkg/types/cluster_config.go b/pkg/types/cluster_config.go index d0e49e014..dcee38010 100644 --- a/pkg/types/cluster_config.go +++ b/pkg/types/cluster_config.go @@ -5,7 +5,7 @@ import ( "github.com/codablock/kluctl/pkg/utils" "github.com/codablock/kluctl/pkg/utils/uo" "github.com/codablock/kluctl/pkg/yaml" - "path" + "path/filepath" ) type ClusterConfig2 struct { @@ -59,7 +59,7 @@ func LoadClusterConfig(clusterDir string, clusterName string) (*ClusterConfig, e return nil, fmt.Errorf("cluster name must be specified") } - p := path.Join(clusterDir, fmt.Sprintf("%s.yml", clusterName)) + p := filepath.Join(clusterDir, fmt.Sprintf("%s.yml", clusterName)) if !utils.IsFile(p) { return nil, fmt.Errorf("cluster config for %s not found", clusterName) } diff --git a/pkg/utils/debugger.go b/pkg/utils/debugger.go index 2800e1584..d16910726 100644 --- a/pkg/utils/debugger.go +++ b/pkg/utils/debugger.go @@ -12,6 +12,8 @@ func IsLaunchedByDebugger() bool { // We loop in case there were intermediary processes like the gopls language server. for pid != 0 { switch p, err := ps.FindProcess(pid); { + case p == nil: + return false case err != nil: return false case p.Executable() == "dlv": diff --git a/pkg/utils/tar.go b/pkg/utils/tar.go index 66b26e214..b50ff5756 100644 --- a/pkg/utils/tar.go +++ b/pkg/utils/tar.go @@ -6,7 +6,7 @@ import ( "fmt" "io" "os" - "path" + "path/filepath" ) func ExtractTarGzFile(tarGzPath string, targetPath string) error { f, err := os.Open(tarGzPath) @@ -39,11 +39,11 @@ func ExtractTarGzStream(r io.Reader, targetPath string) error { switch header.Typeflag { case tar.TypeDir: - if err := os.Mkdir(path.Join(targetPath, header.Name), 0755); err != nil { + if err := os.Mkdir(filepath.Join(targetPath, header.Name), 0755); err != nil { return fmt.Errorf("ExtractTarGz: Mkdir() failed: %w", err) } case tar.TypeReg: - outFile, err := os.Create(path.Join(targetPath, header.Name)) + outFile, err := os.Create(filepath.Join(targetPath, header.Name)) if err != nil { return fmt.Errorf("ExtractTarGz: Create() failed: %w", err) } @@ -53,7 +53,7 @@ func ExtractTarGzStream(r io.Reader, targetPath string) error { return fmt.Errorf("ExtractTarGz: Copy() failed: %w", err) } case tar.TypeSymlink: - if err := os.Symlink(header.Linkname, path.Join(targetPath, header.Name)); err != nil { + if err := os.Symlink(header.Linkname, filepath.Join(targetPath, header.Name)); err != nil { return fmt.Errorf("ExtractTarGz: Symlink() failed: %w", err) } default: diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index f461a4c46..5f303199b 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -6,14 +6,14 @@ import ( "github.com/jinzhu/copier" "log" "os" - "path" + "path/filepath" "sync" ) var createTmpBaseDirOnce sync.Once func GetTmpBaseDir() string { - dir := path.Join(os.TempDir(), "kluctl") + dir := filepath.Join(os.TempDir(), "kluctl") createTmpBaseDirOnce.Do(func() { err := os.MkdirAll(dir, 0o700) if err != nil { From c6a41761015d3f9b68ec0c47c3035cbd389e75e5 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 22 Feb 2022 14:52:53 +0100 Subject: [PATCH 0386/2916] Allow to configure log verbosity --- cmd/kluctl/commands/root.go | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index 0bb054e95..a01141a5f 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -30,10 +30,21 @@ import ( "github.com/spf13/cobra" ) -var noUpdateCheck = false +var ( + v string + noUpdateCheck = false +) const latestReleaseUrl = "https://api.github.com/repos/codablock/kluctl/releases/latest" +func setupLogs() error { + lvl, err := log.ParseLevel(v) + if err != nil { + return err + } + log.SetLevel(lvl) + return nil +} type VersionCheckState struct { LastVersionCheck time.Time `yaml:"lastVersionCheck"` @@ -94,6 +105,9 @@ var rootCmd = &cobra.Command{ Long: `The missing glue to put together large Kubernetes deployments, composed of multiple smaller parts (Helm/Kustomize/...) in a manageable and unified way.`, PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + if err := setupLogs(); err != nil { + return err + } checkNewVersion() return nil }, @@ -118,5 +132,6 @@ func Execute() { func init() { rootCmd.SilenceUsage = true - rootCmd.Flags().BoolVar(&noUpdateCheck, "no-update-check", false, "Disable update check on startup") + rootCmd.PersistentFlags().StringVarP(&v, "verbosity", "v", log.InfoLevel.String(), "Log level (debug, info, warn, error, fatal, panic") + rootCmd.PersistentFlags().BoolVar(&noUpdateCheck, "no-update-check", false, "Disable update check on startup") } From 868ce7c839e7f916749569a9652e1e1aaddfb312 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 22 Feb 2022 16:23:59 +0100 Subject: [PATCH 0387/2916] Implement and use PoorMansClone --- pkg/git/mirrored_repo.go | 16 +------ pkg/git/poor_mans_clone.go | 98 ++++++++++++++++++++++++++++++++++++++ pkg/utils/fs.go | 2 +- 3 files changed, 101 insertions(+), 15 deletions(-) create mode 100644 pkg/git/poor_mans_clone.go diff --git a/pkg/git/mirrored_repo.go b/pkg/git/mirrored_repo.go index 06f9fa059..1cc7d4784 100644 --- a/pkg/git/mirrored_repo.go +++ b/pkg/git/mirrored_repo.go @@ -242,21 +242,9 @@ func (g *MirroredGitRepo) CloneProject(ref string, targetDir string) error { log.Fatalf("tried to clone from a project that is not locked/updated") } - log.Debugf("Cloning git project: url='%v', ref='%v'", g.url.String(), ref) + log.Debugf("Cloning git project: url='%s', ref='%s', target='%s'", g.url.String(), ref, targetDir) - fileUrl := fmt.Sprintf("file://%s", g.mirrorDir) - if ref != "" { - ref = fmt.Sprintf("refs/heads/%s", plumbing.ReferenceName(ref)) - } - _, err := git.PlainClone(targetDir, false, &git.CloneOptions{ - URL: fileUrl, - SingleBranch: true, - ReferenceName: plumbing.ReferenceName(ref), - }) - if err != nil { - return fmt.Errorf("failed to clone %s to %s: %w", fileUrl, targetDir, err) - } - return err + return PoorMansClone(g.mirrorDir, targetDir, ref) } func buildRemoteName(u git_url.GitUrl) string { diff --git a/pkg/git/poor_mans_clone.go b/pkg/git/poor_mans_clone.go new file mode 100644 index 000000000..b91f101c5 --- /dev/null +++ b/pkg/git/poor_mans_clone.go @@ -0,0 +1,98 @@ +package git + +import ( + "github.com/codablock/kluctl/pkg/utils" + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/config" + "github.com/go-git/go-git/v5/plumbing" + "io/ioutil" + "os" + "path/filepath" + "runtime" +) + +// PoorMansClone poor mans clone from a local repo, which does not rely on go-git using git-upload-pack +func PoorMansClone(sourceDir string, targetDir string, ref string) error { + err := os.MkdirAll(targetDir, 0o700) + if err != nil { + return err + } + err = os.Mkdir(filepath.Join(targetDir, ".git"), 0o700) + if err != nil { + return err + } + des, err := os.ReadDir(sourceDir) + if err != nil { + return err + } + for _, de := range des { + s := filepath.Join(sourceDir, de.Name()) + d := filepath.Join(targetDir, ".git", de.Name()) + if de.Name() == ".cache.lock" { + continue + } + if de.Name() == "objects" { + err = os.Symlink(s, d) + if err != nil && runtime.GOOS == "windows" { + // windows 10 does not support symlinks as unprivileged users... + err = utils.CopyDir(s, d) + } + } else { + if de.IsDir() { + err = utils.CopyDir(s, d) + } else { + err = utils.CopyFile(s, d) + } + } + if err != nil { + return err + } + } + + gitConfigReader, err := os.Open(filepath.Join(targetDir, ".git", "config")) + if err != nil { + return err + } + defer gitConfigReader.Close() + gitConfig, err := config.ReadConfig(gitConfigReader) + if err != nil { + return err + } + gitConfig.Core.IsBare = false + + b, err := gitConfig.Marshal() + if err != nil { + return err + } + err = ioutil.WriteFile(filepath.Join(targetDir, ".git", "config"), b, 0o600) + if err != nil { + return err + } + + r, err := git.PlainOpen(targetDir) + if err != nil { + return err + } + + wt, err := r.Worktree() + if err != nil { + return err + } + var ref2 plumbing.ReferenceName + if ref != "" { + ref2 = plumbing.NewBranchReferenceName(ref) + } else { + x, err := r.Reference("HEAD", true) + if err != nil { + return err + } + ref2 = x.Name() + } + err = wt.Checkout(&git.CheckoutOptions{ + Branch: ref2, + }) + if err != nil { + return err + } + return nil +} diff --git a/pkg/utils/fs.go b/pkg/utils/fs.go index d67728d6b..9c43339fc 100644 --- a/pkg/utils/fs.go +++ b/pkg/utils/fs.go @@ -112,5 +112,5 @@ func FsCopyDir(srcFs fs.FS, src string, dst string) error { } func CopyDir(src string, dst string) error { - return FsCopyDir(os.DirFS(src), "", dst) + return FsCopyDir(os.DirFS(src), ".", dst) } From fcc33ddb5277bab8ebdf8eb1caf2c1a22dcac262 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 22 Feb 2022 16:28:21 +0100 Subject: [PATCH 0388/2916] Run go fmt --- cmd/kluctl/commands/cmd_list_images.go | 8 ++++---- cmd/kluctl/commands/root.go | 2 +- cmd/kluctl/commands/utils.go | 2 +- pkg/deployment/images.go | 2 +- pkg/jinja2/jinja2.go | 12 ++++++------ pkg/jinja2/source.go | 6 +++--- pkg/types/deployment.go | 2 +- pkg/utils/prettytable.go | 2 +- pkg/utils/tar.go | 1 + 9 files changed, 19 insertions(+), 18 deletions(-) diff --git a/cmd/kluctl/commands/cmd_list_images.go b/cmd/kluctl/commands/cmd_list_images.go index 49c925c08..7d8540d74 100644 --- a/cmd/kluctl/commands/cmd_list_images.go +++ b/cmd/kluctl/commands/cmd_list_images.go @@ -19,10 +19,10 @@ func init() { var cmd = &cobra.Command{ Use: "list-images", Short: "Renders the target and outputs all images used via `images.get_image(...)", - Long: "Renders the target and outputs all images used via `images.get_image(...)`\n\n"+ - "The result is a compatible with yaml files expected by --fixed-images-file.\n\n"+ - "If fixed images (`-f/--fixed-image`) are provided, these are also taken into account, "+ - "as described in for the deploy command.", + Long: "Renders the target and outputs all images used via `images.get_image(...)`\n\n" + + "The result is a compatible with yaml files expected by --fixed-images-file.\n\n" + + "If fixed images (`-f/--fixed-image`) are provided, these are also taken into account, " + + "as described in for the deploy command.", RunE: runCmdListImages, } args.AddProjectArgs(cmd, true, true, true) diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index a01141a5f..2393da08c 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -31,7 +31,7 @@ import ( ) var ( - v string + v string noUpdateCheck = false ) diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 92cbc4b8a..c80cfb7ca 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -185,7 +185,7 @@ func withProjectTargetCommandContext(p *kluctl_project.KluctlProjectContext, tar target: target, clusterConfig: clusterConfig, k: k, - images: images, + images: images, deploymentProject: d, deploymentCollection: c, } diff --git a/pkg/deployment/images.go b/pkg/deployment/images.go index 187433ca1..16c307d15 100644 --- a/pkg/deployment/images.go +++ b/pkg/deployment/images.go @@ -36,7 +36,7 @@ func (images *Images) SeenImages(simple bool) []types.FixedImage { for _, fi := range images.seenImages { if simple { ret = append(ret, types.FixedImage{ - Image: fi.Image, + Image: fi.Image, ResultImage: fi.ResultImage, }) } else { diff --git a/pkg/jinja2/jinja2.go b/pkg/jinja2/jinja2.go index 97836fc98..7956d805b 100644 --- a/pkg/jinja2/jinja2.go +++ b/pkg/jinja2/jinja2.go @@ -20,11 +20,11 @@ import ( ) type Jinja2 struct { - srcDir string - p *python.PythonInterpreter + srcDir string + p *python.PythonInterpreter renderer *python.PyObject - sem *semaphore.Weighted + sem *semaphore.Weighted globCache map[string]interface{} globCacheMutex sync.Mutex } @@ -73,8 +73,8 @@ func NewJinja2() (*Jinja2, error) { } j := &Jinja2{ - srcDir: srcDir, - p: p, + srcDir: srcDir, + p: p, sem: semaphore.NewWeighted(1), globCache: map[string]interface{}{}, } @@ -121,7 +121,7 @@ func (j *Jinja2) Close() { }) j.p.Stop() - + _ = os.RemoveAll(j.srcDir) } diff --git a/pkg/jinja2/source.go b/pkg/jinja2/source.go index 389c886f5..6ac5b465c 100644 --- a/pkg/jinja2/source.go +++ b/pkg/jinja2/source.go @@ -27,7 +27,7 @@ func extractSource() (string, error) { } err = filepath.WalkDir(filepath.Join(tmpDir, "wheel"), func(p string, d fs.DirEntry, err error) error { - if !strings.HasSuffix(p,".whl") { + if !strings.HasSuffix(p, ".whl") { return nil } r, err := zip.OpenReader(p) @@ -49,7 +49,7 @@ func extractSource() (string, error) { return err } - err = func () error { + err = func() error { dstFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) if err != nil { return err @@ -75,4 +75,4 @@ func extractSource() (string, error) { } return tmpDir, nil -} \ No newline at end of file +} diff --git a/pkg/types/deployment.go b/pkg/types/deployment.go index 7a5840ef9..d90f82685 100644 --- a/pkg/types/deployment.go +++ b/pkg/types/deployment.go @@ -8,7 +8,7 @@ import ( type DeploymentItemConfig struct { Path *string `yaml:"path,omitempty"` - Include *string `yaml:"include,omitempty"` + Include *string `yaml:"include,omitempty"` Tags []string `yaml:"tags,omitempty"` Barrier *bool `yaml:"barrier,omitempty"` Vars []*VarsListItem `yaml:"vars,omitempty"` diff --git a/pkg/utils/prettytable.go b/pkg/utils/prettytable.go index d58834882..a4b2fd934 100644 --- a/pkg/utils/prettytable.go +++ b/pkg/utils/prettytable.go @@ -60,7 +60,7 @@ func (t *PrettyTable) Render(limitWidths []int) string { w = limitWidths[i] } widths[i] = maxWidth(i, w) - if i != cols - 1 { + if i != cols-1 { widthSum += widths[i] } } diff --git a/pkg/utils/tar.go b/pkg/utils/tar.go index b50ff5756..c0425d8fa 100644 --- a/pkg/utils/tar.go +++ b/pkg/utils/tar.go @@ -8,6 +8,7 @@ import ( "os" "path/filepath" ) + func ExtractTarGzFile(tarGzPath string, targetPath string) error { f, err := os.Open(tarGzPath) if err != nil { From f8b745f514388203fe6715294fc84ef052db18da Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 22 Feb 2022 17:02:28 +0100 Subject: [PATCH 0389/2916] Properly handle changed types in diffs --- pkg/diff/diff.go | 48 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/pkg/diff/diff.go b/pkg/diff/diff.go index f9b42878c..b035dbe46 100644 --- a/pkg/diff/diff.go +++ b/pkg/diff/diff.go @@ -10,6 +10,7 @@ import ( "github.com/hexops/gotextdiff/span" diff2 "github.com/r3labs/diff/v2" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "reflect" "strconv" "strings" ) @@ -38,7 +39,11 @@ func convertPath(path []string, o interface{}) (string, error) { } func Diff(oldObject *unstructured.Unstructured, newObject *unstructured.Unstructured) ([]types.Change, error) { - cl, err := diff2.Diff(oldObject.Object, newObject.Object) + differ, err := diff2.NewDiffer(diff2.AllowTypeMismatch(true)) + if err != nil { + return nil, err + } + cl, err := differ.Diff(oldObject.Object, newObject.Object) if err != nil { return nil, err } @@ -47,7 +52,7 @@ func Diff(oldObject *unstructured.Unstructured, newObject *unstructured.Unstruct for _, c := range cl { switch c.Type { case "create": - ud, err := buildUnifiedDiff(notPresent, c.To) + ud, err := buildUnifiedDiff(notPresent, c.To, false) if err != nil { return nil, err } @@ -62,7 +67,7 @@ func Diff(oldObject *unstructured.Unstructured, newObject *unstructured.Unstruct UnifiedDiff: ud, }) case "delete": - ud, err := buildUnifiedDiff(c.From, notPresent) + ud, err := buildUnifiedDiff(c.From, notPresent, false) if err != nil { return nil, err } @@ -77,7 +82,11 @@ func Diff(oldObject *unstructured.Unstructured, newObject *unstructured.Unstruct UnifiedDiff: ud, }) case "update": - ud, err := buildUnifiedDiff(c.From, c.To) + showType := false + if reflect.TypeOf(c.From) != reflect.TypeOf(c.To) { + showType = true + } + ud, err := buildUnifiedDiff(c.From, c.To, showType) if err != nil { return nil, err } @@ -97,12 +106,12 @@ func Diff(oldObject *unstructured.Unstructured, newObject *unstructured.Unstruct return changes, nil } -func buildUnifiedDiff(a interface{}, b interface{}) (string, error) { - aStr, err := objectToDiffableString(a) +func buildUnifiedDiff(a interface{}, b interface{}, showType bool) (string, error) { + aStr, err := objectToDiffableString(a, showType) if err != nil { return "", err } - bStr, err := objectToDiffableString(b) + bStr, err := objectToDiffableString(b, showType) if err != nil { return "", err } @@ -123,7 +132,18 @@ func buildUnifiedDiff(a interface{}, b interface{}) (string, error) { return strings.Join(lines, "\n"), nil } -func objectToDiffableString(o interface{}) (string, error) { +func objectToDiffableString(o interface{}, showType bool) (string, error) { + s, err := objectToDiffableStringNoType(o) + if err != nil { + return "", err + } + if showType { + s += fmt.Sprintf(" (type: %s)", reflect.TypeOf(o).Name()) + } + return s, nil +} + +func objectToDiffableStringNoType(o interface{}) (string, error) { if o == nil { return "null", nil } @@ -134,11 +154,15 @@ func objectToDiffableString(o interface{}) (string, error) { return v, nil } - b, err := yaml.WriteYamlString(o) - if err != nil { - return "", err + if _, ok := o.(map[string]interface{}); ok { + b, err := yaml.WriteYamlString(o) + if err != nil { + return "", err + } + return b, nil + } else { + return fmt.Sprint(o), nil } - return b, nil } func prependStrToLines(s string, prepend string) string { From e4f3b181aeb13f82b6a8a8fbd2759afe541c88f7 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 22 Feb 2022 17:08:02 +0100 Subject: [PATCH 0390/2916] Fix exclusion check on windows --- pkg/deployment/deployment_item.go | 3 ++- pkg/jinja2/jinja2.go | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index a5a27daa4..853971985 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -11,6 +11,7 @@ import ( "io/ioutil" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "os" + "path" "path/filepath" "sigs.k8s.io/kustomize/api/filesys" "sigs.k8s.io/kustomize/api/krusty" @@ -119,7 +120,7 @@ func (di *deploymentItem) render(k *k8s.K8sCluster, wp *utils.WorkerPoolWithErro excludePatterns = append(excludePatterns, di.project.config.TemplateExcludes...) if utils.IsFile(filepath.Join(*di.dir, "helm-chart.yml")) { // never try to render helm charts - excludePatterns = append(excludePatterns, filepath.Join(di.relToProjectItemDir, "charts/**")) + excludePatterns = append(excludePatterns, path.Join(strings.ReplaceAll(di.relToProjectItemDir, string(os.PathSeparator), "/"), "charts/**")) } if !di.collection.forSeal { // .sealme files are rendered while sealing and not while deploying diff --git a/pkg/jinja2/jinja2.go b/pkg/jinja2/jinja2.go index 7956d805b..7f946f4bc 100644 --- a/pkg/jinja2/jinja2.go +++ b/pkg/jinja2/jinja2.go @@ -335,7 +335,7 @@ func (j *Jinja2) getGlob(pattern string) (glob.Glob, error) { return nil, g2.(error) } } - g, err := glob.Compile(pattern) + g, err := glob.Compile(pattern, '/') if err != nil { j.globCache[pattern] = err return nil, err @@ -344,6 +344,8 @@ func (j *Jinja2) getGlob(pattern string) (glob.Glob, error) { return g.(glob.Glob), nil } func (j *Jinja2) needsRender(path string, excludedPatterns []string) bool { + path = strings.ReplaceAll(path, string(os.PathSeparator), "/") + for _, p := range excludedPatterns { g, err := j.getGlob(p) if err != nil { From e8e0289e515f377b75d04aedde4319478c34557f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 24 Feb 2022 10:25:03 +0100 Subject: [PATCH 0391/2916] Reimplement lib_wrapper to use statically generated code and split up jinja2 renderer --- pkg/jinja2/jinja2.go | 213 +---------- pkg/jinja2/jinja2_renderer.go | 196 +++++++++++ pkg/jinja2/source.go | 124 ++++--- pkg/python/dict.go | 19 +- pkg/python/error.go | 5 - pkg/python/interpreter.go | 111 +++--- pkg/python/lib.go | 37 +- pkg/python/libpython_wrapper.go | 46 +++ pkg/python/libpython_wrapper_impl.go | 349 +++++++++++++++++++ pkg/python/list.go | 16 +- pkg/python/misc.go | 58 --- pkg/python/object.go | 46 +-- pkg/python/string.go | 18 - pkg/utils/lib_wrapper/call.c | 99 ------ pkg/utils/lib_wrapper/call.h | 16 - pkg/utils/lib_wrapper/gen/lib_wrapper_gen.go | 341 ++++++++++++++++++ pkg/utils/lib_wrapper/lib_wrapper.go | 89 +---- pkg/utils/lib_wrapper/lib_wrapper_unix.go | 13 +- pkg/utils/lib_wrapper/str.go | 20 -- 19 files changed, 1153 insertions(+), 663 deletions(-) create mode 100644 pkg/jinja2/jinja2_renderer.go delete mode 100644 pkg/python/error.go create mode 100644 pkg/python/libpython_wrapper.go create mode 100644 pkg/python/libpython_wrapper_impl.go delete mode 100644 pkg/python/misc.go delete mode 100644 pkg/python/string.go delete mode 100644 pkg/utils/lib_wrapper/call.c delete mode 100644 pkg/utils/lib_wrapper/call.h create mode 100644 pkg/utils/lib_wrapper/gen/lib_wrapper_gen.go delete mode 100644 pkg/utils/lib_wrapper/str.go diff --git a/pkg/jinja2/jinja2.go b/pkg/jinja2/jinja2.go index 7f946f4bc..b3a71984d 100644 --- a/pkg/jinja2/jinja2.go +++ b/pkg/jinja2/jinja2.go @@ -2,15 +2,9 @@ package jinja2 import "C" import ( - "bytes" - "context" - "encoding/json" - "fmt" - "github.com/codablock/kluctl/pkg/python" "github.com/codablock/kluctl/pkg/utils" "github.com/codablock/kluctl/pkg/utils/uo" "github.com/gobwas/glob" - "golang.org/x/sync/semaphore" "io/fs" "io/ioutil" "os" @@ -20,13 +14,9 @@ import ( ) type Jinja2 struct { - srcDir string - p *python.PythonInterpreter - renderer *python.PyObject - - sem *semaphore.Weighted + pj *pythonJinja2Renderer globCache map[string]interface{} - globCacheMutex sync.Mutex + mutex sync.Mutex } type RenderJob struct { @@ -46,198 +36,33 @@ func (m *Jinja2Error) Error() string { } func NewJinja2() (*Jinja2, error) { - srcDir, err := extractSource() - if err != nil { - return nil, err - } - isOk := false - defer func() { - if !isOk { - _ = os.RemoveAll(srcDir) - } - }() - - p, err := python.NewPythonInterpreter() - if err != nil { - return nil, err - } - defer func() { - if !isOk { - p.Stop() - } - }() - - err = p.AppendSysPath(srcDir) + pj, err := newPythonJinja2Renderer() if err != nil { return nil, err } j := &Jinja2{ - srcDir: srcDir, - p: p, - sem: semaphore.NewWeighted(1), + pj: pj, globCache: map[string]interface{}{}, } - err = p.Run(func() error { - mod := python.PyImport_ImportModule("jinja2_renderer") - if mod == nil { - python.PyErr_Print() - return fmt.Errorf("unexpected error") - } - defer mod.DecRef() - - clazz := mod.GetAttrString("Jinja2Renderer") - if clazz == nil { - python.PyErr_Print() - return fmt.Errorf("unexpected error") - } - defer clazz.DecRef() - - renderer := clazz.CallObject(nil) - if renderer == nil { - python.PyErr_Print() - return fmt.Errorf("unexpected error") - } - - j.renderer = renderer - return nil - }) - if err != nil { - return nil, err - } - - isOk = true return j, nil } func (j *Jinja2) Close() { - _ = j.p.Run(func() error { - if j.renderer != nil { - j.renderer.DecRef() - j.renderer = nil - } - return nil - }) - - j.p.Stop() - - _ = os.RemoveAll(j.srcDir) -} - -func (j *Jinja2) isMaybeTemplate(template string, searchDirs []string, isString bool) (bool, *string) { - if isString { - if strings.IndexRune(template, '{') == -1 { - return false, &template - } - } else { - for _, s := range searchDirs { - b, err := ioutil.ReadFile(filepath.Join(s, template)) - if err != nil { - continue - } - if bytes.IndexRune(b, '{') == -1 { - x := string(b) - return false, &x - } else { - return true, nil - } - } - } - return true, nil -} - -func (j *Jinja2) renderHelper(jobs []*RenderJob, searchDirs []string, vars *uo.UnstructuredObject, isString bool) error { - err := j.sem.Acquire(context.Background(), 1) - if err != nil { - return err - } - defer j.sem.Release(1) - - return j.p.Run(func() error { - return j.renderHelperNoLock(jobs, searchDirs, vars, isString) - }) -} - -func (j *Jinja2) renderHelperNoLock(jobs []*RenderJob, searchDirs []string, vars *uo.UnstructuredObject, isString bool) error { - varsStr, err := json.Marshal(vars.Object) - if err != nil { - return err - } - - var processedJobs []*RenderJob - - pyTemplates := python.PyList_New(0) - pySearchDirs := python.PyList_New(0) - pyVars := python.PyUnicode_FromString(string(varsStr)) - - defer func() { - for i := 0; i < pyTemplates.Length(); i++ { - pyTemplates.GetItem(i).DecRef() - } - for i := 0; i < pySearchDirs.Length(); i++ { - pySearchDirs.GetItem(i).DecRef() - } - pyTemplates.DecRef() - pySearchDirs.DecRef() - pyVars.DecRef() - }() - - for _, job := range jobs { - if ist, r := j.isMaybeTemplate(job.Template, searchDirs, isString); !ist { - job.Result = r - continue - } - processedJobs = append(processedJobs, job) - pyTemplates.Append(python.PyUnicode_FromString(job.Template)) - } - if len(processedJobs) == 0 { - return nil - } - - for _, sd := range searchDirs { - pySearchDirs.Append(python.PyUnicode_FromString(sd)) - } - - var pyFuncName *python.PyObject - var pyResult *python.PyObject - if isString { - pyFuncName = python.PyUnicode_FromString("RenderStrings") - } else { - pyFuncName = python.PyUnicode_FromString("RenderFiles") - } - defer pyFuncName.DecRef() - pyResult = python.PyList_FromObject(j.renderer.CallMethodObjArgs(pyFuncName, pyTemplates, pySearchDirs, pyVars)) - if pyResult == nil { - python.PyErr_Print() - return fmt.Errorf("unexpected exception while calling python code: %w", err) - } - - for i := 0; i < pyResult.Length(); i++ { - item := python.PyDict_FromObject(pyResult.GetItem(i)) - r := item.GetItemString("result") - if r != nil { - resultStr := python.PyUnicode_AsUTF8(r) - processedJobs[i].Result = &resultStr - } else { - e := item.GetItemString("error") - if e == nil { - return fmt.Errorf("missing result and error from item at index %d", i) - } - eStr := python.PyUnicode_AsUTF8(e) - processedJobs[i].Error = &Jinja2Error{eStr} - } - } - - return nil + j.pj.Close() } func (j *Jinja2) RenderStrings(jobs []*RenderJob, searchDirs []string, vars *uo.UnstructuredObject) error { - return j.renderHelper(jobs, searchDirs, vars, true) + j.mutex.Lock() + defer j.mutex.Unlock() + return j.pj.renderHelper(jobs, searchDirs, vars, true) } func (j *Jinja2) RenderFiles(jobs []*RenderJob, searchDirs []string, vars *uo.UnstructuredObject) error { - return j.renderHelper(jobs, searchDirs, vars, false) + j.mutex.Lock() + defer j.mutex.Unlock() + return j.pj.renderHelper(jobs, searchDirs, vars, false) } func (j *Jinja2) RenderString(template string, searchDirs []string, vars *uo.UnstructuredObject) (string, error) { @@ -304,12 +129,12 @@ func (j *Jinja2) RenderStruct(dst interface{}, src interface{}, vars *uo.Unstruc var errors []error for i, j := range jobs { if j.Error != nil { - errors = append(errors, err) - } - - err = uo.SetChild(fields[i].parent, fields[i].key, *j.Result) - if err != nil { - return err + errors = append(errors, j.Error) + } else { + err = uo.SetChild(fields[i].parent, fields[i].key, *j.Result) + if err != nil { + return err + } } } if len(errors) != 0 { @@ -324,8 +149,8 @@ func (j *Jinja2) RenderStruct(dst interface{}, src interface{}, vars *uo.Unstruc } func (j *Jinja2) getGlob(pattern string) (glob.Glob, error) { - j.globCacheMutex.Lock() - defer j.globCacheMutex.Unlock() + j.mutex.Lock() + defer j.mutex.Unlock() g, ok := j.globCache[pattern] if ok { diff --git a/pkg/jinja2/jinja2_renderer.go b/pkg/jinja2/jinja2_renderer.go new file mode 100644 index 000000000..d5d5a9cc2 --- /dev/null +++ b/pkg/jinja2/jinja2_renderer.go @@ -0,0 +1,196 @@ +package jinja2 + +import ( + "bytes" + "encoding/json" + "fmt" + "github.com/codablock/kluctl/pkg/python" + "github.com/codablock/kluctl/pkg/utils/uo" + "io/ioutil" + "log" + "path/filepath" + "strings" + "sync" +) + +type pythonJinja2Renderer struct { + p *python.PythonInterpreter + renderer *python.PyObject +} + +var addSrcToPathOnce sync.Once +func addSrcToPath() { + addSrcToPathOnce.Do(func() { + p, err := python.MainPythonInterpreter() + if err != nil { + log.Fatal(err) + } + err = p.AppendSysPath(pythonSrcExtracted) + if err != nil { + log.Fatal(err) + } + }) +} + +func newPythonJinja2Renderer() (*pythonJinja2Renderer, error) { + addSrcToPath() + + isOk := false + p, err := python.MainPythonInterpreter() + if err != nil { + return nil, err + } + defer func() { + if !isOk { + p.Stop() + } + }() + + j := &pythonJinja2Renderer{ + p: p, + } + + err = p.Run(func() error { + mod := python.PythonWrapper.PyImport_ImportModule("jinja2_renderer") + if mod == nil { + python.PythonWrapper.PyErr_Print() + return fmt.Errorf("unexpected error") + } + defer mod.DecRef() + + clazz := mod.GetAttrString("Jinja2Renderer") + if clazz == nil { + python.PythonWrapper.PyErr_Print() + return fmt.Errorf("unexpected error") + } + defer clazz.DecRef() + + renderer := clazz.CallObject(nil) + if renderer == nil { + python.PythonWrapper.PyErr_Print() + return fmt.Errorf("unexpected error") + } + + j.renderer = renderer + return nil + }) + if err != nil { + return nil, err + } + + isOk = true + return j, nil +} + +func (j *pythonJinja2Renderer) Close() { + _ = j.p.Run(func() error { + if j.renderer != nil { + j.renderer.DecRef() + j.renderer = nil + } + return nil + }) + + j.p.Stop() +} + +func (j *pythonJinja2Renderer) isMaybeTemplate(template string, searchDirs []string, isString bool) (bool, *string) { + if isString { + if strings.IndexRune(template, '{') == -1 { + return false, &template + } + } else { + for _, s := range searchDirs { + b, err := ioutil.ReadFile(filepath.Join(s, template)) + if err != nil { + continue + } + if bytes.IndexRune(b, '{') == -1 { + x := string(b) + return false, &x + } else { + return true, nil + } + } + } + return true, nil +} + +func (j *pythonJinja2Renderer) renderHelper(jobs []*RenderJob, searchDirs []string, vars *uo.UnstructuredObject, isString bool) error { + return j.p.Run(func() error { + return j.renderHelper2(jobs, searchDirs, vars, isString) + }) +} + +func (j *pythonJinja2Renderer) renderHelper2(jobs []*RenderJob, searchDirs []string, vars *uo.UnstructuredObject, isString bool) error { + varsStr, err := json.Marshal(vars.Object) + if err != nil { + return err + } + + var processedJobs []*RenderJob + + pyTemplates := python.PyList_New(0) + pySearchDirs := python.PyList_New(0) + pyVars := python.PythonWrapper.PyUnicode_FromString(string(varsStr)) + + defer func() { + for i := 0; i < pyTemplates.Length(); i++ { + pyTemplates.GetItem(i).DecRef() + } + for i := 0; i < pySearchDirs.Length(); i++ { + pySearchDirs.GetItem(i).DecRef() + } + pyTemplates.DecRef() + pySearchDirs.DecRef() + pyVars.DecRef() + }() + + for _, job := range jobs { + if ist, r := j.isMaybeTemplate(job.Template, searchDirs, isString); !ist { + job.Result = r + continue + } + processedJobs = append(processedJobs, job) + pyTemplates.Append(python.PythonWrapper.PyUnicode_FromString(job.Template)) + } + if len(processedJobs) == 0 { + return nil + } + + for _, sd := range searchDirs { + pySearchDirs.Append(python.PythonWrapper.PyUnicode_FromString(sd)) + } + + var pyFuncName *python.PyObject + var pyResult *python.PyList + if isString { + pyFuncName = python.PythonWrapper.PyUnicode_FromString("RenderStrings") + } else { + pyFuncName = python.PythonWrapper.PyUnicode_FromString("RenderFiles") + } + defer pyFuncName.DecRef() + pyResult = python.PyList_FromObject(j.renderer.CallMethodObjArgs(pyFuncName, pyTemplates, pySearchDirs, pyVars)) + if pyResult == nil { + python.PythonWrapper.PyErr_Print() + return fmt.Errorf("unexpected exception while calling python code: %w", err) + } + + for i := 0; i < pyResult.Length(); i++ { + item := python.PyDict_FromObject(pyResult.GetItem(i)) + r := item.GetItemString("result") + if r != nil { + resultStr := python.PythonWrapper.PyUnicode_AsUTF8(r) + processedJobs[i].Result = &resultStr + } else { + e := item.GetItemString("error") + if e == nil { + return fmt.Errorf("missing result and error from item at index %d", i) + } + eStr := python.PythonWrapper.PyUnicode_AsUTF8(e) + processedJobs[i].Error = &Jinja2Error{eStr} + } + } + + return nil +} diff --git a/pkg/jinja2/source.go b/pkg/jinja2/source.go index 6ac5b465c..8b1f8b195 100644 --- a/pkg/jinja2/source.go +++ b/pkg/jinja2/source.go @@ -2,11 +2,14 @@ package jinja2 import ( "archive/zip" + "crypto/sha256" "embed" + "encoding/hex" + "fmt" "github.com/codablock/kluctl/pkg/utils" + log "github.com/sirupsen/logrus" "io" "io/fs" - "io/ioutil" "os" "path/filepath" "strings" @@ -14,65 +17,102 @@ import ( //go:embed python_src var pythonSrc embed.FS +var pythonSrcExtracted string + +func init() { + srcDir, err := extractSource() + if err != nil { + log.Panic(err) + } + pythonSrcExtracted = srcDir +} func extractSource() (string, error) { - tmpDir, err := ioutil.TempDir(utils.GetTmpBaseDir(), "jinja2-src-") + h := sha256.New() + err := fs.WalkDir(pythonSrc, ".", func(path string, d fs.DirEntry, err error) error { + h.Write([]byte(path)) + if !d.IsDir() { + b, err := pythonSrc.ReadFile(path) + if err != nil { + log.Panic(err) + } + h.Write(b) + } + return nil + }) if err != nil { - return "", err + log.Panic(err) + } + hash := hex.EncodeToString(h.Sum(nil)) + + srcDir := filepath.Join(utils.GetTmpBaseDir(), fmt.Sprintf("jinja2-src-%s", hash[:16])) + if utils.Exists(srcDir) { + return srcDir, nil } + srcDirTmp := srcDir + ".tmp" + defer os.RemoveAll(srcDirTmp) - err = utils.FsCopyDir(pythonSrc, "python_src", tmpDir) + err = utils.FsCopyDir(pythonSrc, "python_src", srcDirTmp) if err != nil { return "", err } - err = filepath.WalkDir(filepath.Join(tmpDir, "wheel"), func(p string, d fs.DirEntry, err error) error { + err = filepath.WalkDir(filepath.Join(srcDirTmp, "wheel"), func(p string, d fs.DirEntry, err error) error { if !strings.HasSuffix(p, ".whl") { return nil } - r, err := zip.OpenReader(p) - if err != nil { + return unzipFile(p, srcDirTmp) + }) + if err != nil { + return "", err + } + + err = os.Rename(srcDirTmp, srcDir) + if err != nil && !os.IsExist(err) { + return "", err + } + + return srcDir, nil +} + +func unzipFile(src string, target string) error { + r, err := zip.OpenReader(src) + if err != nil { + return err + } + defer r.Close() + + for _, f := range r.File { + filePath := filepath.Join(target, f.Name) + if f.FileInfo().IsDir() { + err = os.MkdirAll(filePath, 0o700) + if err != nil { + return err + } + continue + } + if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil { return err } - defer r.Close() - for _, f := range r.File { - filePath := filepath.Join(tmpDir, f.Name) - if f.FileInfo().IsDir() { - err = os.MkdirAll(filePath, 0o700) - if err != nil { - return err - } - continue - } - if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil { + err = func() error { + dstFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) + if err != nil { return err } + defer dstFile.Close() - err = func() error { - dstFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) - if err != nil { - return err - } - defer dstFile.Close() - - fileInArchive, err := f.Open() - if err != nil { - return err - } - defer fileInArchive.Close() + fileInArchive, err := f.Open() + if err != nil { + return err + } + defer fileInArchive.Close() - if _, err := io.Copy(dstFile, fileInArchive); err != nil { - return err - } - return nil - }() - } - return nil - }) - if err != nil { - return "", err + if _, err := io.Copy(dstFile, fileInArchive); err != nil { + return err + } + return nil + }() } - - return tmpDir, nil + return nil } diff --git a/pkg/python/dict.go b/pkg/python/dict.go index 5e3b924c1..59930ddcf 100644 --- a/pkg/python/dict.go +++ b/pkg/python/dict.go @@ -1,13 +1,19 @@ package python + import "C" -import ( - "github.com/codablock/kluctl/pkg/utils/lib_wrapper" -) +import "unsafe" type PyDict = PyObject func PyDict_New() *PyDict { - return togo(pythonModule.Call_VP_PTRS("PyDict_New")) + return PythonWrapper.PyDict_New() +} + +func PyDict_FromPointer(p unsafe.Pointer) *PyDict { + if p == nil { + return nil + } + return &PyDict{p: p} } func PyDict_FromObject(o *PyObject) *PyDict { @@ -15,8 +21,5 @@ func PyDict_FromObject(o *PyObject) *PyDict { } func (d *PyDict) GetItemString(key string) *PyObject { - ckey := lib_wrapper.NewCString(key) - defer ckey.Free() - - return togo(pythonModule.Call_VP_PTRS("PyDict_GetItemString", d.p, ckey.P)) + return PythonWrapper.PyDict_GetItemString(d, key) } diff --git a/pkg/python/error.go b/pkg/python/error.go deleted file mode 100644 index 243a91582..000000000 --- a/pkg/python/error.go +++ /dev/null @@ -1,5 +0,0 @@ -package python - -func PyErr_Print() { - pythonModule.Call_V_PTRS("PyErr_Print") -} diff --git a/pkg/python/interpreter.go b/pkg/python/interpreter.go index a451c1b1c..be1cb0cf2 100644 --- a/pkg/python/interpreter.go +++ b/pkg/python/interpreter.go @@ -1,101 +1,76 @@ package python +import "C" + import ( - "log" + "fmt" "runtime" ) type PythonInterpreter struct { - calls chan *pythonInterpreterCall - init chan error - stopChan chan bool - - threadState PyThreadState -} - -type pythonInterpreterCall struct { - fun func() error - result chan error + threadState *PythonThreadState } func NewPythonInterpreter() (*PythonInterpreter, error) { - p := &PythonInterpreter{ - calls: make(chan *pythonInterpreterCall), - init: make(chan error), - stopChan: make(chan bool), + p := &PythonInterpreter{} + + PythonWrapper.PyEval_AcquireThread(mainThreadState) + p.threadState = PythonWrapper.Py_NewInterpreter() + if p.threadState == nil { + PythonWrapper.PyErr_Print() + return nil, fmt.Errorf("failed to initialize sub-interpreter") } + PythonWrapper.PyEval_ReleaseThread(p.threadState) - go p.runThread() + return p, nil +} - err := <-p.init - if err != nil { - return nil, err +func MainPythonInterpreter() (*PythonInterpreter, error) { + p := &PythonInterpreter{ + threadState: mainThreadState, } return p, nil } func (p *PythonInterpreter) Stop() { - p.stopChan <- true + if p.threadState != mainThreadState { + PythonWrapper.PyEval_AcquireThread(p.threadState) + PythonWrapper.Py_EndInterpreter(p.threadState) + } + p.threadState = nil } -func (p *PythonInterpreter) Run(fun func() error) error { - c := &pythonInterpreterCall{ - fun: fun, - result: make(chan error), +func (p *PythonInterpreter) Lock() { + runtime.LockOSThread() + if p.threadState != nil { + PythonWrapper.PyEval_AcquireThread(p.threadState) } - p.calls <- c - err := <-c.result - return err +} + +func (p *PythonInterpreter) Unlock() { + if p.threadState != nil { + PythonWrapper.PyEval_ReleaseThread(p.threadState) + } + runtime.UnlockOSThread() +} + +func (p *PythonInterpreter) Run(fun func() error) error { + p.Lock() + defer p.Unlock() + + return fun() } func (p *PythonInterpreter) AppendSysPath(pth string) error { return p.Run(func() error { - sys := PyImport_ImportModule("sys") + sys := PythonWrapper.PyImport_ImportModule("sys") defer sys.DecRef() l := PyList_FromObject(sys.GetAttrString("path")) defer l.DecRef() - l.Append(PyUnicode_FromString(pth)) + l.Append(PythonWrapper.PyUnicode_FromString(pth)) return nil }) } -func (p *PythonInterpreter) runThread() { - runtime.LockOSThread() - - PyEval_AcquireThread(mainThreadState) - p.threadState = Py_NewInterpreter() - PyEval_ReleaseThread(p.threadState) - - defer func() { - PyEval_AcquireThread(p.threadState) - Py_EndInterpreter(p.threadState) - }() - - p.init <- nil - - for true { - select { - case c := <-p.calls: - func() { - PyEval_AcquireThread(p.threadState) - defer PyEval_ReleaseThread(p.threadState) - p.processCall(c) - }() - case <-p.stopChan: - return - } - } -} - -func (p *PythonInterpreter) processCall(c *pythonInterpreterCall) { - ss := PyThreadState_Get() - if ss != p.threadState { - log.Panicf("ss != p.threadState") - } - - err := c.fun() - c.result <- err -} - -var mainThreadState PyThreadState +var mainThreadState *PythonThreadState diff --git a/pkg/python/lib.go b/pkg/python/lib.go index 9c2c779f2..b0a0d31d3 100644 --- a/pkg/python/lib.go +++ b/pkg/python/lib.go @@ -17,6 +17,25 @@ import ( ) var pythonModule *lib_wrapper.LibWrapper +var PythonWrapper LibPythonWrapper + +type PythonThreadState struct { + P unsafe.Pointer +} + +func PythonThreadState_FromPointer(p unsafe.Pointer) *PythonThreadState { + if p == nil { + return nil + } + return &PythonThreadState{P: p} +} + +func (p *PythonThreadState) GetPointer() unsafe.Pointer { + if p == nil { + return nil + } + return p.P +} func init() { libPath := decompressLib() @@ -31,21 +50,13 @@ func init() { } pythonModule = lib_wrapper.LoadModule(filepath.Join(libPath, module)) + PythonWrapper = New_LibPythonWrapper(pythonModule) - err := Py_SetPythonHome(libPath) - if err != nil { - log.Panic(err) - } - - Py_Initialize() - mainThreadState = PyEval_SaveThread() -} + l := PythonWrapper.Py_DecodeLocale(libPath) + PythonWrapper.Py_SetPythonHome(l) -func togo(p unsafe.Pointer) *PyObject { - if p == nil { - return nil - } - return &PyObject{p: p} + PythonWrapper.Py_InitializeEx(0) + mainThreadState = PythonWrapper.PyEval_SaveThread() } func decompressLib() string { diff --git a/pkg/python/libpython_wrapper.go b/pkg/python/libpython_wrapper.go new file mode 100644 index 000000000..7b71d5e0a --- /dev/null +++ b/pkg/python/libpython_wrapper.go @@ -0,0 +1,46 @@ +package python + +import "unsafe" + +type ssize_t = int + +//go:generate go run .../lib_wrapper/gen --input libpython_wrapper.go --output libpython_wrapper_impl.go +type LibPythonWrapper interface { + Py_Initialize() + Py_InitializeEx(initsigs int) + + Py_DecodeLocale(s string) unsafe.Pointer + Py_SetPythonHome(l unsafe.Pointer) + + Py_NewInterpreter() *PythonThreadState + Py_EndInterpreter(o *PythonThreadState) + + PyEval_AcquireThread(o *PythonThreadState) + PyEval_ReleaseThread(o *PythonThreadState) + PyEval_SaveThread() *PythonThreadState + PyThreadState_Swap(o *PythonThreadState) *PythonThreadState + PyEval_ReleaseLock() + + PyImport_ImportModule(name string) *PyObject + + Py_IncRef(o *PyObject) + Py_DecRef(o *PyObject) + PyObject_Length(p *PyObject) int + PyObject_GetAttrString(p *PyObject, name string) *PyObject + PyObject_SetAttrString(p *PyObject, name string, p2 *PyObject) int + PyObject_CallObject(p *PyObject, args *PyObject) *PyObject + PyObject_CallMethodObjArgs(p *PyObject, name *PyObject, cargs_vargs []*PyObject) *PyObject + + PyList_New(len ssize_t) *PyList + PyList_GetItem(o *PyList, i ssize_t) *PyObject + PyList_SetItem(o *PyList, i ssize_t, e *PyObject) int + PyList_Append(o *PyList, e *PyObject) int + + PyDict_New() *PyDict + PyDict_GetItemString(p *PyDict, key string) *PyObject + + PyUnicode_FromString(pth string) *PyObject + PyUnicode_AsUTF8(r *PyObject) string + + PyErr_Print() +} diff --git a/pkg/python/libpython_wrapper_impl.go b/pkg/python/libpython_wrapper_impl.go new file mode 100644 index 000000000..5855f2ec1 --- /dev/null +++ b/pkg/python/libpython_wrapper_impl.go @@ -0,0 +1,349 @@ +package python + +/* +#include +#include +#include +void _trampoline_Py_Initialize(void* f) { + typedef void (*F)(); + ((F)f)(); +} +void _trampoline_Py_InitializeEx(void* f, int initsigs) { + typedef void (*F)(int); + ((F)f)(initsigs); +} +void* _trampoline_Py_DecodeLocale(void* f, char* s) { + typedef void* (*F)(char*); + return ((F)f)(s); +} +void _trampoline_Py_SetPythonHome(void* f, void* l) { + typedef void (*F)(void*); + ((F)f)(l); +} +void* _trampoline_Py_NewInterpreter(void* f) { + typedef void* (*F)(); + return ((F)f)(); +} +void _trampoline_Py_EndInterpreter(void* f, void* o) { + typedef void (*F)(void*); + ((F)f)(o); +} +void _trampoline_PyEval_AcquireThread(void* f, void* o) { + typedef void (*F)(void*); + ((F)f)(o); +} +void _trampoline_PyEval_ReleaseThread(void* f, void* o) { + typedef void (*F)(void*); + ((F)f)(o); +} +void* _trampoline_PyEval_SaveThread(void* f) { + typedef void* (*F)(); + return ((F)f)(); +} +void* _trampoline_PyThreadState_Swap(void* f, void* o) { + typedef void* (*F)(void*); + return ((F)f)(o); +} +void _trampoline_PyEval_ReleaseLock(void* f) { + typedef void (*F)(); + ((F)f)(); +} +void* _trampoline_PyImport_ImportModule(void* f, char* name) { + typedef void* (*F)(char*); + return ((F)f)(name); +} +void _trampoline_Py_IncRef(void* f, void* o) { + typedef void (*F)(void*); + ((F)f)(o); +} +void _trampoline_Py_DecRef(void* f, void* o) { + typedef void (*F)(void*); + ((F)f)(o); +} +int _trampoline_PyObject_Length(void* f, void* p) { + typedef int (*F)(void*); + return ((F)f)(p); +} +void* _trampoline_PyObject_GetAttrString(void* f, void* p, char* name) { + typedef void* (*F)(void*, char*); + return ((F)f)(p, name); +} +int _trampoline_PyObject_SetAttrString(void* f, void* p, char* name, void* p2) { + typedef int (*F)(void*, char*, void*); + return ((F)f)(p, name, p2); +} +void* _trampoline_PyObject_CallObject(void* f, void* p, void* args) { + typedef void* (*F)(void*, void*); + return ((F)f)(p, args); +} +void* _trampoline_PyObject_CallMethodObjArgs(void* f, void* p, void* name, int cargs_vargs_len, void** cargs_vargs) { + typedef void* (*F)(void*, void*, ...); + switch(cargs_vargs_len) { + case 0: return ((F)f)(p, name); + case 1: return ((F)f)(p, name, cargs_vargs[0]); + case 2: return ((F)f)(p, name, cargs_vargs[0], cargs_vargs[1]); + case 3: return ((F)f)(p, name, cargs_vargs[0], cargs_vargs[1], cargs_vargs[2]); + case 4: return ((F)f)(p, name, cargs_vargs[0], cargs_vargs[1], cargs_vargs[2], cargs_vargs[3]); + case 5: return ((F)f)(p, name, cargs_vargs[0], cargs_vargs[1], cargs_vargs[2], cargs_vargs[3], cargs_vargs[4]); + case 6: return ((F)f)(p, name, cargs_vargs[0], cargs_vargs[1], cargs_vargs[2], cargs_vargs[3], cargs_vargs[4], cargs_vargs[5]); + case 7: return ((F)f)(p, name, cargs_vargs[0], cargs_vargs[1], cargs_vargs[2], cargs_vargs[3], cargs_vargs[4], cargs_vargs[5], cargs_vargs[6]); + case 8: return ((F)f)(p, name, cargs_vargs[0], cargs_vargs[1], cargs_vargs[2], cargs_vargs[3], cargs_vargs[4], cargs_vargs[5], cargs_vargs[6], cargs_vargs[7]); + case 9: return ((F)f)(p, name, cargs_vargs[0], cargs_vargs[1], cargs_vargs[2], cargs_vargs[3], cargs_vargs[4], cargs_vargs[5], cargs_vargs[6], cargs_vargs[7], cargs_vargs[8]); + default: assert(0); + } +} +void* _trampoline_PyList_New(void* f, ssize_t len) { + typedef void* (*F)(ssize_t); + return ((F)f)(len); +} +void* _trampoline_PyList_GetItem(void* f, void* o, ssize_t i) { + typedef void* (*F)(void*, ssize_t); + return ((F)f)(o, i); +} +int _trampoline_PyList_SetItem(void* f, void* o, ssize_t i, void* e) { + typedef int (*F)(void*, ssize_t, void*); + return ((F)f)(o, i, e); +} +int _trampoline_PyList_Append(void* f, void* o, void* e) { + typedef int (*F)(void*, void*); + return ((F)f)(o, e); +} +void* _trampoline_PyDict_New(void* f) { + typedef void* (*F)(); + return ((F)f)(); +} +void* _trampoline_PyDict_GetItemString(void* f, void* p, char* key) { + typedef void* (*F)(void*, char*); + return ((F)f)(p, key); +} +void* _trampoline_PyUnicode_FromString(void* f, char* pth) { + typedef void* (*F)(char*); + return ((F)f)(pth); +} +char* _trampoline_PyUnicode_AsUTF8(void* f, void* r) { + typedef char* (*F)(void*); + return ((F)f)(r); +} +void _trampoline_PyErr_Print(void* f) { + typedef void (*F)(); + ((F)f)(); +} +*/ +import "C" + +import "unsafe" +import "github.com/codablock/kluctl/pkg/utils/lib_wrapper" + +type LibPythonWrapperImpl struct { + module *lib_wrapper.LibWrapper + _func_Py_Initialize lib_wrapper.FunctionPtr + _func_Py_InitializeEx lib_wrapper.FunctionPtr + _func_Py_DecodeLocale lib_wrapper.FunctionPtr + _func_Py_SetPythonHome lib_wrapper.FunctionPtr + _func_Py_NewInterpreter lib_wrapper.FunctionPtr + _func_Py_EndInterpreter lib_wrapper.FunctionPtr + _func_PyEval_AcquireThread lib_wrapper.FunctionPtr + _func_PyEval_ReleaseThread lib_wrapper.FunctionPtr + _func_PyEval_SaveThread lib_wrapper.FunctionPtr + _func_PyThreadState_Swap lib_wrapper.FunctionPtr + _func_PyEval_ReleaseLock lib_wrapper.FunctionPtr + _func_PyImport_ImportModule lib_wrapper.FunctionPtr + _func_Py_IncRef lib_wrapper.FunctionPtr + _func_Py_DecRef lib_wrapper.FunctionPtr + _func_PyObject_Length lib_wrapper.FunctionPtr + _func_PyObject_GetAttrString lib_wrapper.FunctionPtr + _func_PyObject_SetAttrString lib_wrapper.FunctionPtr + _func_PyObject_CallObject lib_wrapper.FunctionPtr + _func_PyObject_CallMethodObjArgs lib_wrapper.FunctionPtr + _func_PyList_New lib_wrapper.FunctionPtr + _func_PyList_GetItem lib_wrapper.FunctionPtr + _func_PyList_SetItem lib_wrapper.FunctionPtr + _func_PyList_Append lib_wrapper.FunctionPtr + _func_PyDict_New lib_wrapper.FunctionPtr + _func_PyDict_GetItemString lib_wrapper.FunctionPtr + _func_PyUnicode_FromString lib_wrapper.FunctionPtr + _func_PyUnicode_AsUTF8 lib_wrapper.FunctionPtr + _func_PyErr_Print lib_wrapper.FunctionPtr +} + +func New_LibPythonWrapper(module *lib_wrapper.LibWrapper) LibPythonWrapper { + w := &LibPythonWrapperImpl{module: module} + w._func_Py_Initialize = w.module.GetFunc("Py_Initialize") + w._func_Py_InitializeEx = w.module.GetFunc("Py_InitializeEx") + w._func_Py_DecodeLocale = w.module.GetFunc("Py_DecodeLocale") + w._func_Py_SetPythonHome = w.module.GetFunc("Py_SetPythonHome") + w._func_Py_NewInterpreter = w.module.GetFunc("Py_NewInterpreter") + w._func_Py_EndInterpreter = w.module.GetFunc("Py_EndInterpreter") + w._func_PyEval_AcquireThread = w.module.GetFunc("PyEval_AcquireThread") + w._func_PyEval_ReleaseThread = w.module.GetFunc("PyEval_ReleaseThread") + w._func_PyEval_SaveThread = w.module.GetFunc("PyEval_SaveThread") + w._func_PyThreadState_Swap = w.module.GetFunc("PyThreadState_Swap") + w._func_PyEval_ReleaseLock = w.module.GetFunc("PyEval_ReleaseLock") + w._func_PyImport_ImportModule = w.module.GetFunc("PyImport_ImportModule") + w._func_Py_IncRef = w.module.GetFunc("Py_IncRef") + w._func_Py_DecRef = w.module.GetFunc("Py_DecRef") + w._func_PyObject_Length = w.module.GetFunc("PyObject_Length") + w._func_PyObject_GetAttrString = w.module.GetFunc("PyObject_GetAttrString") + w._func_PyObject_SetAttrString = w.module.GetFunc("PyObject_SetAttrString") + w._func_PyObject_CallObject = w.module.GetFunc("PyObject_CallObject") + w._func_PyObject_CallMethodObjArgs = w.module.GetFunc("PyObject_CallMethodObjArgs") + w._func_PyList_New = w.module.GetFunc("PyList_New") + w._func_PyList_GetItem = w.module.GetFunc("PyList_GetItem") + w._func_PyList_SetItem = w.module.GetFunc("PyList_SetItem") + w._func_PyList_Append = w.module.GetFunc("PyList_Append") + w._func_PyDict_New = w.module.GetFunc("PyDict_New") + w._func_PyDict_GetItemString = w.module.GetFunc("PyDict_GetItemString") + w._func_PyUnicode_FromString = w.module.GetFunc("PyUnicode_FromString") + w._func_PyUnicode_AsUTF8 = w.module.GetFunc("PyUnicode_AsUTF8") + w._func_PyErr_Print = w.module.GetFunc("PyErr_Print") + return w +} +func (w *LibPythonWrapperImpl) Py_Initialize() { + C._trampoline_Py_Initialize(unsafe.Pointer(w._func_Py_Initialize)) +} +func (w *LibPythonWrapperImpl) Py_InitializeEx(initsigs int) { + _c_initsigs := C.int(initsigs) + C._trampoline_Py_InitializeEx(unsafe.Pointer(w._func_Py_InitializeEx), _c_initsigs) +} +func (w *LibPythonWrapperImpl) Py_DecodeLocale(s string) unsafe.Pointer { + _c_s := C.CString(s) + defer C.free(unsafe.Pointer(_c_s)) + _g_ret := unsafe.Pointer(C._trampoline_Py_DecodeLocale(unsafe.Pointer(w._func_Py_DecodeLocale), _c_s)) + return _g_ret +} +func (w *LibPythonWrapperImpl) Py_SetPythonHome(l unsafe.Pointer) { + C._trampoline_Py_SetPythonHome(unsafe.Pointer(w._func_Py_SetPythonHome), l) +} +func (w *LibPythonWrapperImpl) Py_NewInterpreter() *PythonThreadState { + _g_ret := PythonThreadState_FromPointer(C._trampoline_Py_NewInterpreter(unsafe.Pointer(w._func_Py_NewInterpreter))) + return _g_ret +} +func (w *LibPythonWrapperImpl) Py_EndInterpreter(o *PythonThreadState) { + _c_o := (o).GetPointer() + C._trampoline_Py_EndInterpreter(unsafe.Pointer(w._func_Py_EndInterpreter), _c_o) +} +func (w *LibPythonWrapperImpl) PyEval_AcquireThread(o *PythonThreadState) { + _c_o := (o).GetPointer() + C._trampoline_PyEval_AcquireThread(unsafe.Pointer(w._func_PyEval_AcquireThread), _c_o) +} +func (w *LibPythonWrapperImpl) PyEval_ReleaseThread(o *PythonThreadState) { + _c_o := (o).GetPointer() + C._trampoline_PyEval_ReleaseThread(unsafe.Pointer(w._func_PyEval_ReleaseThread), _c_o) +} +func (w *LibPythonWrapperImpl) PyEval_SaveThread() *PythonThreadState { + _g_ret := PythonThreadState_FromPointer(C._trampoline_PyEval_SaveThread(unsafe.Pointer(w._func_PyEval_SaveThread))) + return _g_ret +} +func (w *LibPythonWrapperImpl) PyThreadState_Swap(o *PythonThreadState) *PythonThreadState { + _c_o := (o).GetPointer() + _g_ret := PythonThreadState_FromPointer(C._trampoline_PyThreadState_Swap(unsafe.Pointer(w._func_PyThreadState_Swap), _c_o)) + return _g_ret +} +func (w *LibPythonWrapperImpl) PyEval_ReleaseLock() { + C._trampoline_PyEval_ReleaseLock(unsafe.Pointer(w._func_PyEval_ReleaseLock)) +} +func (w *LibPythonWrapperImpl) PyImport_ImportModule(name string) *PyObject { + _c_name := C.CString(name) + defer C.free(unsafe.Pointer(_c_name)) + _g_ret := PyObject_FromPointer(C._trampoline_PyImport_ImportModule(unsafe.Pointer(w._func_PyImport_ImportModule), _c_name)) + return _g_ret +} +func (w *LibPythonWrapperImpl) Py_IncRef(o *PyObject) { + _c_o := (o).GetPointer() + C._trampoline_Py_IncRef(unsafe.Pointer(w._func_Py_IncRef), _c_o) +} +func (w *LibPythonWrapperImpl) Py_DecRef(o *PyObject) { + _c_o := (o).GetPointer() + C._trampoline_Py_DecRef(unsafe.Pointer(w._func_Py_DecRef), _c_o) +} +func (w *LibPythonWrapperImpl) PyObject_Length(p *PyObject) int { + _c_p := (p).GetPointer() + _g_ret := int(C._trampoline_PyObject_Length(unsafe.Pointer(w._func_PyObject_Length), _c_p)) + return _g_ret +} +func (w *LibPythonWrapperImpl) PyObject_GetAttrString(p *PyObject, name string) *PyObject { + _c_p := (p).GetPointer() + _c_name := C.CString(name) + defer C.free(unsafe.Pointer(_c_name)) + _g_ret := PyObject_FromPointer(C._trampoline_PyObject_GetAttrString(unsafe.Pointer(w._func_PyObject_GetAttrString), _c_p, _c_name)) + return _g_ret +} +func (w *LibPythonWrapperImpl) PyObject_SetAttrString(p *PyObject, name string, p2 *PyObject) int { + _c_p := (p).GetPointer() + _c_name := C.CString(name) + defer C.free(unsafe.Pointer(_c_name)) + _c_p2 := (p2).GetPointer() + _g_ret := int(C._trampoline_PyObject_SetAttrString(unsafe.Pointer(w._func_PyObject_SetAttrString), _c_p, _c_name, _c_p2)) + return _g_ret +} +func (w *LibPythonWrapperImpl) PyObject_CallObject(p *PyObject, args *PyObject) *PyObject { + _c_p := (p).GetPointer() + _c_args := (args).GetPointer() + _g_ret := PyObject_FromPointer(C._trampoline_PyObject_CallObject(unsafe.Pointer(w._func_PyObject_CallObject), _c_p, _c_args)) + return _g_ret +} +func (w *LibPythonWrapperImpl) PyObject_CallMethodObjArgs(p *PyObject, name *PyObject, cargs_vargs []*PyObject) *PyObject { + _c_p := (p).GetPointer() + _c_name := (name).GetPointer() + _c_cargs_vargs_ := cargs_vargs + var _c_cargs_vargs *unsafe.Pointer + _c_cargs_vargs__ := make([]unsafe.Pointer, len(_c_cargs_vargs_), len(_c_cargs_vargs_)) + for i, _ := range _c_cargs_vargs_ { + _c_cargs_vargs__[i] = _c_cargs_vargs_[i].GetPointer() + } + if len(_c_cargs_vargs_) != 0 { + _c_cargs_vargs = &_c_cargs_vargs__[0] + } + _c_cargs_vargs_len := len(_c_cargs_vargs_) + _g_ret := PyObject_FromPointer(C._trampoline_PyObject_CallMethodObjArgs(unsafe.Pointer(w._func_PyObject_CallMethodObjArgs), _c_p, _c_name, C.int(_c_cargs_vargs_len), _c_cargs_vargs)) + return _g_ret +} +func (w *LibPythonWrapperImpl) PyList_New(len ssize_t) *PyList { + _c_len := C.ssize_t(len) + _g_ret := PyList_FromPointer(C._trampoline_PyList_New(unsafe.Pointer(w._func_PyList_New), _c_len)) + return _g_ret +} +func (w *LibPythonWrapperImpl) PyList_GetItem(o *PyList, i ssize_t) *PyObject { + _c_o := (o).GetPointer() + _c_i := C.ssize_t(i) + _g_ret := PyObject_FromPointer(C._trampoline_PyList_GetItem(unsafe.Pointer(w._func_PyList_GetItem), _c_o, _c_i)) + return _g_ret +} +func (w *LibPythonWrapperImpl) PyList_SetItem(o *PyList, i ssize_t, e *PyObject) int { + _c_o := (o).GetPointer() + _c_i := C.ssize_t(i) + _c_e := (e).GetPointer() + _g_ret := int(C._trampoline_PyList_SetItem(unsafe.Pointer(w._func_PyList_SetItem), _c_o, _c_i, _c_e)) + return _g_ret +} +func (w *LibPythonWrapperImpl) PyList_Append(o *PyList, e *PyObject) int { + _c_o := (o).GetPointer() + _c_e := (e).GetPointer() + _g_ret := int(C._trampoline_PyList_Append(unsafe.Pointer(w._func_PyList_Append), _c_o, _c_e)) + return _g_ret +} +func (w *LibPythonWrapperImpl) PyDict_New() *PyDict { + _g_ret := PyDict_FromPointer(C._trampoline_PyDict_New(unsafe.Pointer(w._func_PyDict_New))) + return _g_ret +} +func (w *LibPythonWrapperImpl) PyDict_GetItemString(p *PyDict, key string) *PyObject { + _c_p := (p).GetPointer() + _c_key := C.CString(key) + defer C.free(unsafe.Pointer(_c_key)) + _g_ret := PyObject_FromPointer(C._trampoline_PyDict_GetItemString(unsafe.Pointer(w._func_PyDict_GetItemString), _c_p, _c_key)) + return _g_ret +} +func (w *LibPythonWrapperImpl) PyUnicode_FromString(pth string) *PyObject { + _c_pth := C.CString(pth) + defer C.free(unsafe.Pointer(_c_pth)) + _g_ret := PyObject_FromPointer(C._trampoline_PyUnicode_FromString(unsafe.Pointer(w._func_PyUnicode_FromString), _c_pth)) + return _g_ret +} +func (w *LibPythonWrapperImpl) PyUnicode_AsUTF8(r *PyObject) string { + _c_r := (r).GetPointer() + _g_ret := C.GoString(C._trampoline_PyUnicode_AsUTF8(unsafe.Pointer(w._func_PyUnicode_AsUTF8), _c_r)) + return _g_ret +} +func (w *LibPythonWrapperImpl) PyErr_Print() { + C._trampoline_PyErr_Print(unsafe.Pointer(w._func_PyErr_Print)) +} diff --git a/pkg/python/list.go b/pkg/python/list.go index b537a2da7..d4b21b3b4 100644 --- a/pkg/python/list.go +++ b/pkg/python/list.go @@ -1,10 +1,18 @@ package python +import "unsafe" + type PyList = PyObject func PyList_New(len int) *PyList { - p := pythonModule.Call_VP_SS("PyList_New", len) - return togo(p) + return PythonWrapper.PyList_New(len) +} + +func PyList_FromPointer(p unsafe.Pointer) *PyList { + if p == nil { + return nil + } + return &PyList{p: p} } func PyList_FromObject(l *PyObject) *PyList { @@ -12,9 +20,9 @@ func PyList_FromObject(l *PyObject) *PyList { } func (l *PyList) GetItem(pos int) *PyObject { - return togo(pythonModule.Call_VP_VP_SS("PyList_GetItem", l.p, pos)) + return PythonWrapper.PyList_GetItem(l, pos) } func (l *PyList) Append(item *PyObject) int { - return pythonModule.Call_I_PTRS("PyList_Append", l.p, item.p) + return PythonWrapper.PyList_Append(l, item) } diff --git a/pkg/python/misc.go b/pkg/python/misc.go deleted file mode 100644 index cb32f4f30..000000000 --- a/pkg/python/misc.go +++ /dev/null @@ -1,58 +0,0 @@ -package python - -import ( - "fmt" - "github.com/codablock/kluctl/pkg/utils/lib_wrapper" - "unsafe" -) - -type PyThreadState unsafe.Pointer - -func Py_SetPythonHome(home string) error { - chome := lib_wrapper.NewCString(home) - defer chome.Free() - - newHome := togo(pythonModule.Call_VP_PTRS("Py_DecodeLocale", chome.P, nil)) - if newHome == nil { - return fmt.Errorf("fail to call Py_DecodeLocale on '%s'", home) - } - pythonModule.Call_V_PTRS("Py_SetPythonHome", newHome.p) - - return nil -} - -func Py_Initialize() { - pythonModule.Call_V_PTRS("Py_Initialize") -} - -func Py_NewInterpreter() PyThreadState { - p := pythonModule.Call_VP_PTRS("Py_NewInterpreter") - return (PyThreadState)(p) -} - -func Py_EndInterpreter(o PyThreadState) { - pythonModule.Call_V_PTRS("Py_EndInterpreter", unsafe.Pointer(o)) -} - -func PyEval_AcquireThread(o PyThreadState) { - pythonModule.Call_V_PTRS("PyEval_AcquireThread", unsafe.Pointer(o)) -} - -func PyEval_ReleaseThread(o PyThreadState) { - pythonModule.Call_V_PTRS("PyEval_ReleaseThread", unsafe.Pointer(o)) -} - -func PyEval_SaveThread() PyThreadState { - return PyThreadState(pythonModule.Call_VP_PTRS("PyEval_SaveThread")) -} - -func PyThreadState_Get() PyThreadState { - return PyThreadState(pythonModule.Call_VP_PTRS("PyThreadState_Get")) -} - -func PyImport_ImportModule(name string) *PyObject { - cname := lib_wrapper.NewCString(name) - defer cname.Free() - - return togo(pythonModule.Call_VP_PTRS("PyImport_ImportModule", cname.P)) -} diff --git a/pkg/python/object.go b/pkg/python/object.go index 2f143ae48..8f3a8ed68 100644 --- a/pkg/python/object.go +++ b/pkg/python/object.go @@ -1,7 +1,6 @@ package python import ( - "github.com/codablock/kluctl/pkg/utils/lib_wrapper" "unsafe" ) @@ -9,45 +8,46 @@ type PyObject struct { p unsafe.Pointer } +func PyObject_FromPointer(p unsafe.Pointer) *PyObject { + if p == nil { + return nil + } + return &PyObject{p: p} +} + +func (p *PyObject) GetPointer() unsafe.Pointer { + if p == nil { + return nil + } + return p.p +} + func (p *PyObject) IncRef() { - pythonModule.Call_V_PTRS("Py_IncRef", p.p) + PythonWrapper.Py_IncRef(p) } func (p *PyObject) DecRef() { - pythonModule.Call_V_PTRS("Py_DecRef", p.p) + PythonWrapper.Py_DecRef(p) } func (p *PyObject) Length() int { - return pythonModule.Call_I_PTRS("PyObject_Length", p.p) + return PythonWrapper.PyObject_Length(p) } func (p *PyObject) GetAttrString(attr_name string) *PyObject { - cattr_name := lib_wrapper.NewCString(attr_name) - defer cattr_name.Free() - - return togo(pythonModule.Call_VP_PTRS("PyObject_GetAttrString", p.p, cattr_name.P)) + return PythonWrapper.PyObject_GetAttrString(p, attr_name) } func (p *PyObject) SetAttrString(attr_name string, v *PyObject) int { - cattr_name := lib_wrapper.NewCString(attr_name) - defer cattr_name.Free() - - return pythonModule.Call_I_PTRS("PyObject_SetAttrString", cattr_name.P, v.p) + return PythonWrapper.PyObject_SetAttrString(p, attr_name, v) } func (p *PyObject) CallObject(args *PyObject) *PyObject { - var a unsafe.Pointer - if args != nil { - a = args.p - } - return togo(pythonModule.Call_VP_PTRS("PyObject_CallObject", p.p, a)) + return PythonWrapper.PyObject_CallObject(p, args) } func (p *PyObject) CallMethodObjArgs(name *PyObject, args ...*PyObject) *PyObject { - cargs := make([]unsafe.Pointer, len(args), len(args)+1) - for i, o := range args { - cargs[i] = o.p - } - cargs = append(cargs, nil) - return togo(pythonModule.Call_VP_PTRS_VARGS("PyObject_CallMethodObjArgs", []unsafe.Pointer{p.p, name.p}, cargs...)) + args = append([]*PyObject{}, args...) + args = append(args, nil) + return PythonWrapper.PyObject_CallMethodObjArgs(p, name, args) } diff --git a/pkg/python/string.go b/pkg/python/string.go deleted file mode 100644 index 5299517ce..000000000 --- a/pkg/python/string.go +++ /dev/null @@ -1,18 +0,0 @@ -package python - -import "C" -import ( - "github.com/codablock/kluctl/pkg/utils/lib_wrapper" -) - -func PyUnicode_FromString(u string) *PyObject { - cu := lib_wrapper.NewCString(u) - defer cu.Free() - - return togo(pythonModule.Call_VP_PTRS("PyUnicode_FromString", cu.P)) -} - -func PyUnicode_AsUTF8(unicode *PyObject) string { - cutf8 := pythonModule.Call_VP_PTRS("PyUnicode_AsUTF8", unicode.p) - return C.GoString((*C.char)(cutf8)) -} diff --git a/pkg/utils/lib_wrapper/call.c b/pkg/utils/lib_wrapper/call.c deleted file mode 100644 index c1299035f..000000000 --- a/pkg/utils/lib_wrapper/call.c +++ /dev/null @@ -1,99 +0,0 @@ - -#include "call.h" - -void* call_vp_ptrs(void* f, int argc, void **argv) { - switch (argc) { - case 0: { - typedef void* (*F)(); - return ((F)(f))(); - } - case 1: { - typedef void* (*F)(void*); - return ((F)(f))(argv[0]); - } - case 2: { - typedef void* (*F)(void*,void*); - return ((F)(f))(argv[0],argv[1]); - } - case 3: { - typedef void* (*F)(void*,void*,void*); - return ((F)(f))(argv[0],argv[1],argv[2]); - } - case 4: { - typedef void* (*F)(void*,void*,void*,void*); - return ((F)(f))(argv[0],argv[1],argv[2],argv[3]); - } - } - return NULL; -} - -void* call_vp_ptrs_vargs(void* f, int nptrs, void **ptrs, int argc, void **argv) { - switch (nptrs) { - case 0: { - // crash!! - return &*((int*)NULL); - } - case 1: { - typedef void* (*F)(void*, ...); - switch (argc) { - case 0: return ((F)(f))(ptrs[0]); - case 1: return ((F)(f))(ptrs[0], argv[0]); - case 2: return ((F)(f))(ptrs[0], argv[0], argv[1]); - case 3: return ((F)(f))(ptrs[0], argv[0], argv[1], argv[2]); - case 4: return ((F)(f))(ptrs[0], argv[0], argv[1], argv[2], argv[3]); - } - } - case 2: { - typedef void* (*F)(void*,void*, ...); - switch (argc) { - case 0: return ((F)(f))(ptrs[0], ptrs[1]); - case 1: return ((F)(f))(ptrs[0], ptrs[1], argv[0]); - case 2: return ((F)(f))(ptrs[0], ptrs[1], argv[0], argv[1]); - case 3: return ((F)(f))(ptrs[0], ptrs[1], argv[0], argv[1], argv[2]); - case 4: return ((F)(f))(ptrs[0], ptrs[1], argv[0], argv[1], argv[2], argv[3]); - } - } - } - // crash!! - return &*((int*)NULL); -} - -int call_i_ptrs(void* f, int argc, void **argv) { - switch (argc) { - case 0: { - typedef int (*F)(); - return ((F)(f))(); - } - case 1: { - typedef int (*F)(void*); - return ((F)(f))(argv[0]); - } - case 2: { - typedef int (*F)(void*,void*); - return ((F)(f))(argv[0],argv[1]); - } - case 3: { - typedef int (*F)(void*,void*,void*); - return ((F)(f))(argv[0],argv[1],argv[2]); - } - case 4: { - typedef int (*F)(void*,void*,void*,void*); - return ((F)(f))(argv[0],argv[1],argv[2],argv[3]); - } - } - return 0; -} - -void call_v_ptrs(void* f, int argc, void **argv) { - call_vp_ptrs(f, argc, argv); -} - -void* call_vp_ss(void* f, ssize_t i) { - typedef void* (*F)(ssize_t); - return ((F)(f))(i); -} - -void* call_vp_vp_ss(void* f, void *a1, ssize_t a2) { - typedef void* (*F)(void*,ssize_t); - return ((F)(f))(a1,a2); -} diff --git a/pkg/utils/lib_wrapper/call.h b/pkg/utils/lib_wrapper/call.h deleted file mode 100644 index b007d8b98..000000000 --- a/pkg/utils/lib_wrapper/call.h +++ /dev/null @@ -1,16 +0,0 @@ - -#ifndef VARIADIC_H -#define VARIADIC_H - -#include -#include - -void* call_vp_ptrs(void* f, int argc, void **argv); -void* call_vp_ptrs_vargs(void* f, int nptrs, void **ptrs, int argc, void **argv); -void call_v_ptrs(void* f, int argc, void **argv); -int call_i_ptrs(void* f, int argc, void **argv); - -void* call_vp_ss(void* f, ssize_t a1); -void* call_vp_vp_ss(void* f, void *a1, ssize_t a2); - -#endif diff --git a/pkg/utils/lib_wrapper/gen/lib_wrapper_gen.go b/pkg/utils/lib_wrapper/gen/lib_wrapper_gen.go new file mode 100644 index 000000000..c34881d75 --- /dev/null +++ b/pkg/utils/lib_wrapper/gen/lib_wrapper_gen.go @@ -0,0 +1,341 @@ +package main + +import ( + "flag" + "fmt" + log "github.com/sirupsen/logrus" + "go/ast" + "go/parser" + "go/token" + "io/ioutil" + "reflect" + "strings" +) + +var inputFile = flag.String("input", "", "input .go file") +var outputFile = flag.String("output", "", "output .go file") + +func main() { + flag.Parse() + + if *inputFile == "" || *outputFile == "" { + log.Fatalf("Missing parameters") + } + + src, err := ioutil.ReadFile(*inputFile) + if err != nil { + log.Fatal(err) + } + + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, *inputFile, src, 0) + if err != nil { + log.Fatal(err) + } + + v := &collector{} + ast.Walk(v, f) + + goCode := "" + cCode := "" + for _, g := range v.gens { + gc, cc := g.generateImpl() + + goCode += gc + cCode += cc + } + + goFile := "package python\n" + goFile += "/*\n" + goFile += "#include \n" + goFile += "#include \n" + goFile += "#include \n" + goFile += cCode + goFile += "*/\n" + goFile += "import \"C\"\n" + goFile += "\n" + goFile += "import \"unsafe\"\n" + goFile += "import \"github.com/codablock/kluctl/pkg/utils/lib_wrapper\"\n" + goFile += goCode + + err = ioutil.WriteFile(*outputFile, []byte(goFile), 0o600) + if err != nil { + log.Fatal(err) + } +} + +type collector struct { + gens []*generator +} + +func (v *collector) Visit(n ast.Node) ast.Visitor { + ce, ok := n.(*ast.TypeSpec) + if !ok { + return v + } + it, ok := ce.Type.(*ast.InterfaceType) + if !ok { + return v + } + + v.gens = append(v.gens, &generator{ + typ: ce, + iface: it, + }) + + return v +} + +type generator struct { + typ *ast.TypeSpec + iface *ast.InterfaceType +} + +func (g *generator) generateImpl() (string, string) { + + structDecls := "module *lib_wrapper.LibWrapper\n" + initCalls := "" + goFuncs := "" + cFuncs := "" + + for _, m := range g.iface.Methods.List { + ft, _ := m.Type.(*ast.FuncType) + + structDecls += fmt.Sprintf("_func_%s lib_wrapper.FunctionPtr\n", m.Names[0].String()) + initCalls += fmt.Sprintf("w._func_%s = w.module.GetFunc(\"%s\")\n", m.Names[0].String(), m.Names[0].String()) + goFuncs += g.generateGoFunc(m.Names[0].String(), ft) + cFuncs += g.generateCFunc(m.Names[0].String(), ft) + } + + var goCode string + + goCode += fmt.Sprintf("type %sImpl struct {\n", g.typ.Name.String()) + goCode += indent(structDecls, 2) + goCode += "}\n" + + goCode += fmt.Sprintf("func New_%s(module *lib_wrapper.LibWrapper) %s {\n", g.typ.Name.String(), g.typ.Name.String()) + goCode += fmt.Sprintf(" w := &%sImpl{module: module}\n", g.typ.Name.String()) + goCode += indent(initCalls, 2) + goCode += " return w\n" + goCode += "}\n" + + goCode += goFuncs + + return goCode, cFuncs +} + +func exprToString(e ast.Expr) string { + if x, ok := e.(*ast.Ident); ok { + return x.String() + } else if x, ok := e.(*ast.SelectorExpr); ok { + return fmt.Sprintf("%s.%s", exprToString(x.X), exprToString(x.Sel)) + } else if x, ok := e.(*ast.ArrayType); ok { + return fmt.Sprintf("[]%s", exprToString(x.Elt)) + } else if x, ok := e.(*ast.StarExpr); ok { + return fmt.Sprintf("*%s", x.X) + } + log.Fatalf("unknown type %s", reflect.TypeOf(e).String()) + return "" +} + +func fieldListToString(fl *ast.FieldList) string { + var args []string + for _, p := range fl.List { + var names []string + for _, n := range p.Names { + names = append(names, n.String()) + } + var a string + if len(names) == 0 { + a = exprToString(p.Type) + } else { + a = fmt.Sprintf("%s %s", strings.Join(names, ", "), exprToString(p.Type)) + } + args = append(args, a) + } + return strings.Join(args, ", ") +} + +func (g *generator) generateGoFunc(name string, m *ast.FuncType) string { + implName := g.typ.Name.String() + "Impl" + + funcStr := fmt.Sprintf("func (w* %s) %s(%s)", implName, name, fieldListToString(m.Params)) + + if m.Results != nil && len(m.Results.List) != 0 { + if len(m.Results.List) != 1 || len(m.Results.List[0].Names) != 0 { + log.Fatalf("%s: only one result allowed", name) + } + funcStr += fmt.Sprintf(" (%s)", fieldListToString(m.Results)) + } + + funcStr += " {\n" + funcStr += indent(g.generateTrampolineCall(name, m), 2) + funcStr += "}\n" + + return funcStr +} + +func (g *generator) generateTrampolineCall(name string, m *ast.FuncType) string { + result := "" + + funcArgs := []string{fmt.Sprintf("unsafe.Pointer(w._func_%s)", name)} + for _, p := range m.Params.List { + for _, n := range p.Names { + t := exprToString(p.Type) + a, stmt := g.generateToCConversion(n.String(), n.String(), t) + result += stmt + funcArgs = append(funcArgs, a...) + } + } + call := fmt.Sprintf("C._trampoline_%s(%s)", name, strings.Join(funcArgs, ", ")) + + if m.Results != nil && len(m.Results.List) != 0 { + t := exprToString(m.Results.List[0].Type) + a, stmt := g.generateToGoConversion("ret", call, t) + result += stmt + result += fmt.Sprintf("return %s\n", a) + } else { + result += fmt.Sprintf("%s\n", call) + } + return result +} + +func (g *generator) generateToCConversion(name string, a string, t string) ([]string, string) { + cn := fmt.Sprintf("_c_%s", name) + switch t { + case "int", "ssize_t": + stmt := fmt.Sprintf("%s := C.%s(%s)\n", cn, t, a) + return []string{cn}, stmt + case "string": + stmt := fmt.Sprintf("%s := C.CString(%s)\n", cn, a) + stmt += fmt.Sprintf("defer C.free(unsafe.Pointer(%s))\n", cn) + return []string{cn}, stmt + case "unsafe.Pointer": + return []string{a}, "" + } + if strings.HasPrefix(t, "[]") { + t = t[2:] + stmt := fmt.Sprintf("%s_ := %s\n", cn, a) + if t == "unsafe.Pointer" { + stmt += fmt.Sprintf("var %s *unsafe.Pointer\n", cn) + stmt += fmt.Sprintf("if len(%s_) != 0 { %s = &%s_[0] }\n", cn, cn, cn) + } else { + stmt += fmt.Sprintf("var %s *unsafe.Pointer\n", cn) + stmt += fmt.Sprintf("%s__ := make([]unsafe.Pointer, len(%s_), len(%s_))\n", cn, cn, cn) + stmt += fmt.Sprintf("for i, _ := range %s_ { %s__[i] = %s_[i].GetPointer() }\n", cn, cn, cn) + stmt += fmt.Sprintf("if len(%s_) != 0 { %s = &%s__[0] }\n", cn, cn, cn) + } + if strings.HasSuffix(name, "_vargs") { + stmt += fmt.Sprintf("%s_len := len(%s_)\n", cn, cn) + return []string{fmt.Sprintf("C.int(%s_len)", cn), cn}, stmt + } else { + return []string{cn}, stmt + } + } + if strings.HasPrefix(t, "*") { + stmt := fmt.Sprintf("%s := (%s).GetPointer()\n", cn, a) + return []string{cn}, stmt + } + log.Fatalf("unknown type %s", t) + return nil, "" +} + +func (g *generator) generateToGoConversion(name string, a string, t string) (string, string) { + gn := fmt.Sprintf("_g_%s", name) + switch t { + case "int", "ssize_t": + stmt := fmt.Sprintf("%s := int(%s)\n", gn, a) + return gn, stmt + case "string": + stmt := fmt.Sprintf("%s := C.GoString(%s)\n", gn, a) + return gn, stmt + case "unsafe.Pointer": + stmt := fmt.Sprintf("%s := unsafe.Pointer(%s)\n", gn, a) + return gn, stmt + } + if strings.HasPrefix(t, "*") { + stmt := fmt.Sprintf("%s := %s_FromPointer(%s)\n", gn, t[1:], a) + return gn, stmt + } + log.Fatalf("unknown type %s", t) + return "", "" +} + +func (g *generator) generateCFunc(name string, m *ast.FuncType) string { + returnType := "void" + returnPrefix := "" + if m.Results != nil && len(m.Results.List) != 0 { + if len(m.Results.List) != 1 || len(m.Results.List[0].Names) != 0 { + log.Fatalf("%s: only one result allowed", name) + } + returnType = g.getCType(exprToString(m.Results.List[0].Type)) + returnPrefix = "return " + } + + var paramDecls []string + var typedefParams []string + var callArgs []string + vargs_param := "" + + paramDecls = append(paramDecls, "void* f") + for _, p := range m.Params.List { + n := p.Names[0].String() + t := g.getCType(exprToString(p.Type)) + if strings.HasSuffix(n, "_vargs") { + paramDecls = append(paramDecls, fmt.Sprintf("int %s_len", n), fmt.Sprintf("%s %s", t, n)) + typedefParams = append(typedefParams, "...") + vargs_param = n + } else { + paramDecls = append(paramDecls, fmt.Sprintf("%s %s", t, n)) + typedefParams = append(typedefParams, t) + callArgs = append(callArgs, n) + } + } + + funcStr := fmt.Sprintf("%s _trampoline_%s(%s) {\n", returnType, name, strings.Join(paramDecls, ", ")) + body := fmt.Sprintf("typedef %s (*F)(%s);\n", returnType, strings.Join(typedefParams, ", ")) + if vargs_param == "" { + body += fmt.Sprintf("%s((F)f)(%s);\n", returnPrefix, strings.Join(callArgs, ", ")) + } else { + body += fmt.Sprintf("switch(%s_len) {\n", vargs_param) + for i := 0; i < 10; i++ { + body += fmt.Sprintf("case %d: %s((F)f)(%s);\n", i, returnPrefix, strings.Join(callArgs, ", ")) + callArgs = append(callArgs, fmt.Sprintf("%s[%d]", vargs_param, i)) + } + body += "default: assert(0);\n" + body += "}\n" + } + funcStr += indent(body, 2) + funcStr += "}\n" + return funcStr +} + +func (g *generator) getCType(t string) string { + switch t { + case "int", "ssize_t": + return t + case "string": + return "char*" + case "unsafe.Pointer": + return "void*" + } + if strings.HasPrefix(t, "[]") { + return "void**" + } + if strings.HasPrefix(t, "*") { + return "void*" + } + log.Fatalf("unknown type %s", t) + return "" +} + +func indent(s string, i int) string { + l := strings.Split(s, "\n") + is := strings.Repeat(" ", i) + for i, _ := range l { + if l[i] != "" { + l[i] = is + l[i] + } + } + return strings.Join(l, "\n") +} diff --git a/pkg/utils/lib_wrapper/lib_wrapper.go b/pkg/utils/lib_wrapper/lib_wrapper.go index 2a856b058..78cb3ddd7 100644 --- a/pkg/utils/lib_wrapper/lib_wrapper.go +++ b/pkg/utils/lib_wrapper/lib_wrapper.go @@ -1,17 +1,11 @@ package lib_wrapper -/* -#include "call.h" -*/ -import "C" import ( "log" "sync" "unsafe" ) -const MaxVariadicLength = 5 - type FunctionPtr unsafe.Pointer type LibWrapper struct { @@ -19,7 +13,7 @@ type LibWrapper struct { funcMap sync.Map } -func (lw *LibWrapper) getFunc(funcName string) FunctionPtr { +func (lw *LibWrapper) GetFunc(funcName string) FunctionPtr { f, ok := lw.funcMap.Load(funcName) if !ok { f = lw.loadFunc(funcName) @@ -31,84 +25,3 @@ func (lw *LibWrapper) getFunc(funcName string) FunctionPtr { } return f2 } - -func (lw *LibWrapper) Call_V_PTRS(funcName string, args ...unsafe.Pointer) { - f := lw.getFunc(funcName) - - if len(args) > MaxVariadicLength { - panic("Call_V_PTRS: too many arguments") - } - - var cargs *unsafe.Pointer - if len(args) == 0 { - cargs = nil - } else { - cargs = &args[0] - } - C.call_v_ptrs(unsafe.Pointer(f), C.int(len(args)), cargs) -} - -func (lw *LibWrapper) Call_VP_PTRS(funcName string, args ...unsafe.Pointer) unsafe.Pointer { - f := lw.getFunc(funcName) - - if len(args) > MaxVariadicLength { - panic("Call_V_PTRS: too many arguments") - } - - var cargs *unsafe.Pointer - if len(args) == 0 { - cargs = nil - } else { - cargs = &args[0] - } - return C.call_vp_ptrs(unsafe.Pointer(f), C.int(len(args)), cargs) -} - -func (lw *LibWrapper) Call_VP_PTRS_VARGS(funcName string, a1 []unsafe.Pointer, vargs ...unsafe.Pointer) unsafe.Pointer { - f := lw.getFunc(funcName) - - if len(vargs) > MaxVariadicLength { - panic("Call_V_PTRS: too many arguments") - } - - var ca1 *unsafe.Pointer - if len(a1) == 0 { - ca1 = nil - } else { - ca1 = &a1[0] - } - - var cvargs *unsafe.Pointer - if len(vargs) == 0 { - cvargs = nil - } else { - cvargs = &vargs[0] - } - return C.call_vp_ptrs_vargs(unsafe.Pointer(f), C.int(len(a1)), ca1, C.int(len(vargs)), cvargs) -} - -func (lw *LibWrapper) Call_I_PTRS(funcName string, args ...unsafe.Pointer) int { - f := lw.getFunc(funcName) - - if len(args) > MaxVariadicLength { - panic("Call_V_PTRS: too many arguments") - } - - var cargs *unsafe.Pointer - if len(args) == 0 { - cargs = nil - } else { - cargs = &args[0] - } - return int(C.call_i_ptrs(unsafe.Pointer(f), C.int(len(args)), cargs)) -} - -func (lw *LibWrapper) Call_VP_SS(funcName string, a1 int) unsafe.Pointer { - f := lw.getFunc(funcName) - return C.call_vp_ss(unsafe.Pointer(f), C.ssize_t(a1)) -} - -func (lw *LibWrapper) Call_VP_VP_SS(funcName string, a1 unsafe.Pointer, a2 int) unsafe.Pointer { - f := lw.getFunc(funcName) - return C.call_vp_vp_ss(unsafe.Pointer(f), a1, C.ssize_t(a2)) -} diff --git a/pkg/utils/lib_wrapper/lib_wrapper_unix.go b/pkg/utils/lib_wrapper/lib_wrapper_unix.go index 68e102408..5ac3280c3 100644 --- a/pkg/utils/lib_wrapper/lib_wrapper_unix.go +++ b/pkg/utils/lib_wrapper/lib_wrapper_unix.go @@ -6,7 +6,6 @@ package lib_wrapper /* #include #include - */ import "C" import ( @@ -15,10 +14,10 @@ import ( ) func LoadModule(pth string) *LibWrapper { - cPth := NewCString(pth) - defer cPth.Free() + cPth := C.CString(pth) + defer C.free(unsafe.Pointer(cPth)) - mod := C.dlopen((*C.char)(cPth.P), C.RTLD_LAZY) + mod := C.dlopen(cPth, C.RTLD_LAZY) if mod == nil { log.Panicf("dlopen for %s failed", pth) } @@ -28,10 +27,10 @@ func LoadModule(pth string) *LibWrapper { } func (lw *LibWrapper) loadFunc(funcName string) FunctionPtr { - cFuncName := NewCString(funcName) - defer cFuncName.Free() + cFuncName := C.CString(funcName) + defer C.free(unsafe.Pointer(cFuncName)) - f := C.dlsym(lw.module, (*C.char)(cFuncName.P)) + f := C.dlsym(lw.module, cFuncName) if f == nil { log.Panicf("dlsym for %s failed", funcName) } diff --git a/pkg/utils/lib_wrapper/str.go b/pkg/utils/lib_wrapper/str.go deleted file mode 100644 index 52470967e..000000000 --- a/pkg/utils/lib_wrapper/str.go +++ /dev/null @@ -1,20 +0,0 @@ -package lib_wrapper - -/* -#include -*/ -import "C" -import "unsafe" - -type UnsafeStr struct { - P unsafe.Pointer -} - -func NewCString(s string) *UnsafeStr { - us := C.CString(s) - return &UnsafeStr{P: unsafe.Pointer(us)} -} - -func (us *UnsafeStr) Free() { - C.free(us.P) -} From e55c0717f0f714172f360785b26e903296b8d55a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 24 Feb 2022 11:26:46 +0100 Subject: [PATCH 0392/2916] Add script to download python wheels --- pkg/jinja2/jinja2.go | 2 ++ pkg/jinja2/python_src/pip-wheel.sh | 9 +++++++++ 2 files changed, 11 insertions(+) create mode 100755 pkg/jinja2/python_src/pip-wheel.sh diff --git a/pkg/jinja2/jinja2.go b/pkg/jinja2/jinja2.go index b3a71984d..09a343343 100644 --- a/pkg/jinja2/jinja2.go +++ b/pkg/jinja2/jinja2.go @@ -13,6 +13,8 @@ import ( "sync" ) +//go:generate bash ./python_src/pip-wheel.sh + type Jinja2 struct { pj *pythonJinja2Renderer globCache map[string]interface{} diff --git a/pkg/jinja2/python_src/pip-wheel.sh b/pkg/jinja2/python_src/pip-wheel.sh new file mode 100755 index 000000000..409950a3d --- /dev/null +++ b/pkg/jinja2/python_src/pip-wheel.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +DIR=$(cd $(dirname $0) && pwd) + +mkdir -p $DIR/wheel +cd $DIR/wheel + +rm *.whl +pip wheel -r ../requirements.txt From 90713305851e1dc78c61ea65db3f4e2cc52e40fe Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 24 Feb 2022 11:41:22 +0100 Subject: [PATCH 0393/2916] Fix formatting in generated code --- go.mod | 2 +- pkg/jinja2/jinja2.go | 6 +- pkg/jinja2/jinja2_renderer.go | 1 + pkg/python/libpython_wrapper_impl.go | 136 +++++++++---------- pkg/utils/lib_wrapper/gen/lib_wrapper_gen.go | 22 +-- 5 files changed, 87 insertions(+), 80 deletions(-) diff --git a/go.mod b/go.mod index 84d74798f..e100147a2 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,6 @@ require ( github.com/spf13/viper v1.10.1 github.com/whilp/git-urls v1.0.0 golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b k8s.io/api v0.23.3 k8s.io/apimachinery v0.23.3 @@ -117,6 +116,7 @@ require ( go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d // indirect golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.7 // indirect diff --git a/pkg/jinja2/jinja2.go b/pkg/jinja2/jinja2.go index 09a343343..b03dff2dc 100644 --- a/pkg/jinja2/jinja2.go +++ b/pkg/jinja2/jinja2.go @@ -16,9 +16,9 @@ import ( //go:generate bash ./python_src/pip-wheel.sh type Jinja2 struct { - pj *pythonJinja2Renderer - globCache map[string]interface{} - mutex sync.Mutex + pj *pythonJinja2Renderer + globCache map[string]interface{} + mutex sync.Mutex } type RenderJob struct { diff --git a/pkg/jinja2/jinja2_renderer.go b/pkg/jinja2/jinja2_renderer.go index d5d5a9cc2..2f04eb051 100644 --- a/pkg/jinja2/jinja2_renderer.go +++ b/pkg/jinja2/jinja2_renderer.go @@ -19,6 +19,7 @@ type pythonJinja2Renderer struct { } var addSrcToPathOnce sync.Once + func addSrcToPath() { addSrcToPathOnce.Do(func() { p, err := python.MainPythonInterpreter() diff --git a/pkg/python/libpython_wrapper_impl.go b/pkg/python/libpython_wrapper_impl.go index 5855f2ec1..8bcff00b8 100644 --- a/pkg/python/libpython_wrapper_impl.go +++ b/pkg/python/libpython_wrapper_impl.go @@ -5,128 +5,128 @@ package python #include #include void _trampoline_Py_Initialize(void* f) { - typedef void (*F)(); - ((F)f)(); + typedef void (*F)(); + ((F)f)(); } void _trampoline_Py_InitializeEx(void* f, int initsigs) { - typedef void (*F)(int); - ((F)f)(initsigs); + typedef void (*F)(int); + ((F)f)(initsigs); } void* _trampoline_Py_DecodeLocale(void* f, char* s) { - typedef void* (*F)(char*); - return ((F)f)(s); + typedef void* (*F)(char*); + return ((F)f)(s); } void _trampoline_Py_SetPythonHome(void* f, void* l) { - typedef void (*F)(void*); - ((F)f)(l); + typedef void (*F)(void*); + ((F)f)(l); } void* _trampoline_Py_NewInterpreter(void* f) { - typedef void* (*F)(); - return ((F)f)(); + typedef void* (*F)(); + return ((F)f)(); } void _trampoline_Py_EndInterpreter(void* f, void* o) { - typedef void (*F)(void*); - ((F)f)(o); + typedef void (*F)(void*); + ((F)f)(o); } void _trampoline_PyEval_AcquireThread(void* f, void* o) { - typedef void (*F)(void*); - ((F)f)(o); + typedef void (*F)(void*); + ((F)f)(o); } void _trampoline_PyEval_ReleaseThread(void* f, void* o) { - typedef void (*F)(void*); - ((F)f)(o); + typedef void (*F)(void*); + ((F)f)(o); } void* _trampoline_PyEval_SaveThread(void* f) { - typedef void* (*F)(); - return ((F)f)(); + typedef void* (*F)(); + return ((F)f)(); } void* _trampoline_PyThreadState_Swap(void* f, void* o) { - typedef void* (*F)(void*); - return ((F)f)(o); + typedef void* (*F)(void*); + return ((F)f)(o); } void _trampoline_PyEval_ReleaseLock(void* f) { - typedef void (*F)(); - ((F)f)(); + typedef void (*F)(); + ((F)f)(); } void* _trampoline_PyImport_ImportModule(void* f, char* name) { - typedef void* (*F)(char*); - return ((F)f)(name); + typedef void* (*F)(char*); + return ((F)f)(name); } void _trampoline_Py_IncRef(void* f, void* o) { - typedef void (*F)(void*); - ((F)f)(o); + typedef void (*F)(void*); + ((F)f)(o); } void _trampoline_Py_DecRef(void* f, void* o) { - typedef void (*F)(void*); - ((F)f)(o); + typedef void (*F)(void*); + ((F)f)(o); } int _trampoline_PyObject_Length(void* f, void* p) { - typedef int (*F)(void*); - return ((F)f)(p); + typedef int (*F)(void*); + return ((F)f)(p); } void* _trampoline_PyObject_GetAttrString(void* f, void* p, char* name) { - typedef void* (*F)(void*, char*); - return ((F)f)(p, name); + typedef void* (*F)(void*, char*); + return ((F)f)(p, name); } int _trampoline_PyObject_SetAttrString(void* f, void* p, char* name, void* p2) { - typedef int (*F)(void*, char*, void*); - return ((F)f)(p, name, p2); + typedef int (*F)(void*, char*, void*); + return ((F)f)(p, name, p2); } void* _trampoline_PyObject_CallObject(void* f, void* p, void* args) { - typedef void* (*F)(void*, void*); - return ((F)f)(p, args); + typedef void* (*F)(void*, void*); + return ((F)f)(p, args); } void* _trampoline_PyObject_CallMethodObjArgs(void* f, void* p, void* name, int cargs_vargs_len, void** cargs_vargs) { - typedef void* (*F)(void*, void*, ...); - switch(cargs_vargs_len) { - case 0: return ((F)f)(p, name); - case 1: return ((F)f)(p, name, cargs_vargs[0]); - case 2: return ((F)f)(p, name, cargs_vargs[0], cargs_vargs[1]); - case 3: return ((F)f)(p, name, cargs_vargs[0], cargs_vargs[1], cargs_vargs[2]); - case 4: return ((F)f)(p, name, cargs_vargs[0], cargs_vargs[1], cargs_vargs[2], cargs_vargs[3]); - case 5: return ((F)f)(p, name, cargs_vargs[0], cargs_vargs[1], cargs_vargs[2], cargs_vargs[3], cargs_vargs[4]); - case 6: return ((F)f)(p, name, cargs_vargs[0], cargs_vargs[1], cargs_vargs[2], cargs_vargs[3], cargs_vargs[4], cargs_vargs[5]); - case 7: return ((F)f)(p, name, cargs_vargs[0], cargs_vargs[1], cargs_vargs[2], cargs_vargs[3], cargs_vargs[4], cargs_vargs[5], cargs_vargs[6]); - case 8: return ((F)f)(p, name, cargs_vargs[0], cargs_vargs[1], cargs_vargs[2], cargs_vargs[3], cargs_vargs[4], cargs_vargs[5], cargs_vargs[6], cargs_vargs[7]); - case 9: return ((F)f)(p, name, cargs_vargs[0], cargs_vargs[1], cargs_vargs[2], cargs_vargs[3], cargs_vargs[4], cargs_vargs[5], cargs_vargs[6], cargs_vargs[7], cargs_vargs[8]); - default: assert(0); - } + typedef void* (*F)(void*, void*, ...); + switch(cargs_vargs_len) { + case 0: return ((F)f)(p, name); + case 1: return ((F)f)(p, name, cargs_vargs[0]); + case 2: return ((F)f)(p, name, cargs_vargs[0], cargs_vargs[1]); + case 3: return ((F)f)(p, name, cargs_vargs[0], cargs_vargs[1], cargs_vargs[2]); + case 4: return ((F)f)(p, name, cargs_vargs[0], cargs_vargs[1], cargs_vargs[2], cargs_vargs[3]); + case 5: return ((F)f)(p, name, cargs_vargs[0], cargs_vargs[1], cargs_vargs[2], cargs_vargs[3], cargs_vargs[4]); + case 6: return ((F)f)(p, name, cargs_vargs[0], cargs_vargs[1], cargs_vargs[2], cargs_vargs[3], cargs_vargs[4], cargs_vargs[5]); + case 7: return ((F)f)(p, name, cargs_vargs[0], cargs_vargs[1], cargs_vargs[2], cargs_vargs[3], cargs_vargs[4], cargs_vargs[5], cargs_vargs[6]); + case 8: return ((F)f)(p, name, cargs_vargs[0], cargs_vargs[1], cargs_vargs[2], cargs_vargs[3], cargs_vargs[4], cargs_vargs[5], cargs_vargs[6], cargs_vargs[7]); + case 9: return ((F)f)(p, name, cargs_vargs[0], cargs_vargs[1], cargs_vargs[2], cargs_vargs[3], cargs_vargs[4], cargs_vargs[5], cargs_vargs[6], cargs_vargs[7], cargs_vargs[8]); + default: assert(0); + } } void* _trampoline_PyList_New(void* f, ssize_t len) { - typedef void* (*F)(ssize_t); - return ((F)f)(len); + typedef void* (*F)(ssize_t); + return ((F)f)(len); } void* _trampoline_PyList_GetItem(void* f, void* o, ssize_t i) { - typedef void* (*F)(void*, ssize_t); - return ((F)f)(o, i); + typedef void* (*F)(void*, ssize_t); + return ((F)f)(o, i); } int _trampoline_PyList_SetItem(void* f, void* o, ssize_t i, void* e) { - typedef int (*F)(void*, ssize_t, void*); - return ((F)f)(o, i, e); + typedef int (*F)(void*, ssize_t, void*); + return ((F)f)(o, i, e); } int _trampoline_PyList_Append(void* f, void* o, void* e) { - typedef int (*F)(void*, void*); - return ((F)f)(o, e); + typedef int (*F)(void*, void*); + return ((F)f)(o, e); } void* _trampoline_PyDict_New(void* f) { - typedef void* (*F)(); - return ((F)f)(); + typedef void* (*F)(); + return ((F)f)(); } void* _trampoline_PyDict_GetItemString(void* f, void* p, char* key) { - typedef void* (*F)(void*, char*); - return ((F)f)(p, key); + typedef void* (*F)(void*, char*); + return ((F)f)(p, key); } void* _trampoline_PyUnicode_FromString(void* f, char* pth) { - typedef void* (*F)(char*); - return ((F)f)(pth); + typedef void* (*F)(char*); + return ((F)f)(pth); } char* _trampoline_PyUnicode_AsUTF8(void* f, void* r) { - typedef char* (*F)(void*); - return ((F)f)(r); + typedef char* (*F)(void*); + return ((F)f)(r); } void _trampoline_PyErr_Print(void* f) { - typedef void (*F)(); - ((F)f)(); + typedef void (*F)(); + ((F)f)(); } */ import "C" diff --git a/pkg/utils/lib_wrapper/gen/lib_wrapper_gen.go b/pkg/utils/lib_wrapper/gen/lib_wrapper_gen.go index c34881d75..69e1ac539 100644 --- a/pkg/utils/lib_wrapper/gen/lib_wrapper_gen.go +++ b/pkg/utils/lib_wrapper/gen/lib_wrapper_gen.go @@ -5,6 +5,7 @@ import ( "fmt" log "github.com/sirupsen/logrus" "go/ast" + "go/format" "go/parser" "go/token" "io/ioutil" @@ -58,7 +59,12 @@ func main() { goFile += "import \"github.com/codablock/kluctl/pkg/utils/lib_wrapper\"\n" goFile += goCode - err = ioutil.WriteFile(*outputFile, []byte(goFile), 0o600) + formatted, err := format.Source([]byte(goFile)) + if err != nil { + log.Fatal(err) + } + + err = ioutil.WriteFile(*outputFile, []byte(formatted), 0o600) if err != nil { log.Fatal(err) } @@ -110,13 +116,13 @@ func (g *generator) generateImpl() (string, string) { var goCode string goCode += fmt.Sprintf("type %sImpl struct {\n", g.typ.Name.String()) - goCode += indent(structDecls, 2) + goCode += indent(structDecls, 1) goCode += "}\n" goCode += fmt.Sprintf("func New_%s(module *lib_wrapper.LibWrapper) %s {\n", g.typ.Name.String(), g.typ.Name.String()) - goCode += fmt.Sprintf(" w := &%sImpl{module: module}\n", g.typ.Name.String()) - goCode += indent(initCalls, 2) - goCode += " return w\n" + goCode += fmt.Sprintf("\tw := &%sImpl{module: module}\n", g.typ.Name.String()) + goCode += indent(initCalls, 1) + goCode += "\treturn w\n" goCode += "}\n" goCode += goFuncs @@ -169,7 +175,7 @@ func (g *generator) generateGoFunc(name string, m *ast.FuncType) string { } funcStr += " {\n" - funcStr += indent(g.generateTrampolineCall(name, m), 2) + funcStr += indent(g.generateTrampolineCall(name, m), 1) funcStr += "}\n" return funcStr @@ -305,7 +311,7 @@ func (g *generator) generateCFunc(name string, m *ast.FuncType) string { body += "default: assert(0);\n" body += "}\n" } - funcStr += indent(body, 2) + funcStr += indent(body, 1) funcStr += "}\n" return funcStr } @@ -331,7 +337,7 @@ func (g *generator) getCType(t string) string { func indent(s string, i int) string { l := strings.Split(s, "\n") - is := strings.Repeat(" ", i) + is := strings.Repeat("\t", i) for i, _ := range l { if l[i] != "" { l[i] = is + l[i] From 3f9a26bb4dbde805c64fc18006d7e79e732420f5 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 24 Feb 2022 12:44:28 +0100 Subject: [PATCH 0394/2916] Run gofmt on e2e --- e2e/cmd_wrapper_util.go | 2 +- e2e/deploy_test.go | 44 ++--- e2e/external_projects_test.go | 11 +- e2e/hooks_test.go | 342 +++++++++++++++++----------------- e2e/inclusion_test.go | 10 +- e2e/kind_cluster.go | 12 +- e2e/project.go | 20 +- e2e/utils_resources.go | 22 +-- e2e/utils_test.go | 8 +- 9 files changed, 235 insertions(+), 236 deletions(-) diff --git a/e2e/cmd_wrapper_util.go b/e2e/cmd_wrapper_util.go index adf0c061d..f72f36a9d 100644 --- a/e2e/cmd_wrapper_util.go +++ b/e2e/cmd_wrapper_util.go @@ -8,7 +8,7 @@ import ( "os/exec" ) -func runWrappedCmd(testName string, cwd string, env[] string, args []string) (string, string, error) { +func runWrappedCmd(testName string, cwd string, env []string, args []string) (string, string, error) { executable, err := os.Executable() if err != nil { return "", "", err diff --git a/e2e/deploy_test.go b/e2e/deploy_test.go index 9445d385e..64b57dbd4 100644 --- a/e2e/deploy_test.go +++ b/e2e/deploy_test.go @@ -3,30 +3,30 @@ package e2e import "testing" func TestCommandDeploySimple(t *testing.T) { - t.Parallel() - p := &testProject{} - p.init(t, "simple") - defer p.cleanup() + t.Parallel() + p := &testProject{} + p.init(t, "simple") + defer p.cleanup() - k := defaultKindCluster - recreateNamespace(t, k, p.projectName) + k := defaultKindCluster + recreateNamespace(t, k, p.projectName) - p.updateKindCluster(k, nil) - p.updateTarget("test", k.Name, nil) + p.updateKindCluster(k, nil) + p.updateTarget("test", k.Name, nil) - addConfigMapDeployment(p, "cm", nil, resourceOpts{ - name: "cm", - namespace: p.projectName, - }) - p.KluctlMust("deploy", "--yes", "-t", "test") - assertResourceExists(t, k, p.projectName, "ConfigMap/cm") + addConfigMapDeployment(p, "cm", nil, resourceOpts{ + name: "cm", + namespace: p.projectName, + }) + p.KluctlMust("deploy", "--yes", "-t", "test") + assertResourceExists(t, k, p.projectName, "ConfigMap/cm") - addConfigMapDeployment(p, "cm2", nil, resourceOpts{ - name: "cm2", - namespace: p.projectName, - }) - p.KluctlMust("deploy", "--yes", "-t", "test", "--dry-run") - assertResourceNotExists(t, k, p.projectName, "ConfigMap/cm2") - p.KluctlMust("deploy", "--yes", "-t", "test") - assertResourceExists(t, k, p.projectName, "ConfigMap/cm2") + addConfigMapDeployment(p, "cm2", nil, resourceOpts{ + name: "cm2", + namespace: p.projectName, + }) + p.KluctlMust("deploy", "--yes", "-t", "test", "--dry-run") + assertResourceNotExists(t, k, p.projectName, "ConfigMap/cm2") + p.KluctlMust("deploy", "--yes", "-t", "test") + assertResourceExists(t, k, p.projectName, "ConfigMap/cm2") } diff --git a/e2e/external_projects_test.go b/e2e/external_projects_test.go index 1f4637c0e..a21f58449 100644 --- a/e2e/external_projects_test.go +++ b/e2e/external_projects_test.go @@ -24,11 +24,11 @@ func doTestProject(t *testing.T, namespace string, p *testProject) { addBusyboxDeployment(p, "busybox", resourceOpts{name: "busybox", namespace: namespace}) p.KluctlMust("deploy", "--yes", "-t", "test") - assertReadiness(t, k, namespace, "Deployment/busybox", time.Minute * 5) + assertReadiness(t, k, namespace, "Deployment/busybox", time.Minute*5) - cmData := map[string]string { + cmData := map[string]string{ "cluster_var": "{{ cluster.cluster_var }}", - "target_var": "{{ args.target_var }}", + "target_var": "{{ args.target_var }}", } assertResourceNotExists(t, k, namespace, "ConfigMap/cm") @@ -57,9 +57,9 @@ func doTestProject(t *testing.T, namespace string, p *testProject) { } func TestExternalProjects(t *testing.T) { - testCases := []struct{ + testCases := []struct { name string - p testProject + p testProject }{ {name: "external-kluctl-project", p: testProject{kluctlProjectExternal: true}}, {name: "external-clusters-project", p: testProject{clustersExternal: true}}, @@ -76,4 +76,3 @@ func TestExternalProjects(t *testing.T) { }) } } - diff --git a/e2e/hooks_test.go b/e2e/hooks_test.go index 16a2b0138..a503f0cf9 100644 --- a/e2e/hooks_test.go +++ b/e2e/hooks_test.go @@ -1,13 +1,13 @@ package e2e import ( - "encoding/base64" - "fmt" - "github.com/codablock/kluctl/pkg/utils/uo" - "testing" + "encoding/base64" + "fmt" + "github.com/codablock/kluctl/pkg/utils/uo" + "testing" ) -const hookScript=` +const hookScript = ` kubectl get configmap -oyaml > /tmp/result.yml cat /tmp/result.yml if ! kubectl get secret {{ .name }}-result; then @@ -20,238 +20,238 @@ kubectl delete configmap cm2 || true ` func addHookDeployment(p *testProject, dir string, opts resourceOpts, isHelm bool, hook string, hookDeletionPolicy string) { - annotations := make(map[string]string) - if isHelm { - annotations["helm.sh/hook"] = hook - if hookDeletionPolicy != "" { - annotations["helm.sh/hook-deletion-policy"] = hookDeletionPolicy - } - } else { - annotations["kluctl.io/hook"] = hook - } - if hookDeletionPolicy != "" { - annotations["kluctl.io/hook-deletion-policy"] = hookDeletionPolicy - } - - script := renderTemplateHelper(hookScript, map[string]interface{}{ - "name": opts.name, - "namespace": opts.namespace, - }) - - opts.annotations = uo.CopyMergeStrMap(opts.annotations, annotations) - - addJobDeployment(p, dir, opts, "bitnami/kubectl:1.21", []string{"sh"}, []string{"-c", script}) + annotations := make(map[string]string) + if isHelm { + annotations["helm.sh/hook"] = hook + if hookDeletionPolicy != "" { + annotations["helm.sh/hook-deletion-policy"] = hookDeletionPolicy + } + } else { + annotations["kluctl.io/hook"] = hook + } + if hookDeletionPolicy != "" { + annotations["kluctl.io/hook-deletion-policy"] = hookDeletionPolicy + } + + script := renderTemplateHelper(hookScript, map[string]interface{}{ + "name": opts.name, + "namespace": opts.namespace, + }) + + opts.annotations = uo.CopyMergeStrMap(opts.annotations, annotations) + + addJobDeployment(p, dir, opts, "bitnami/kubectl:1.21", []string{"sh"}, []string{"-c", script}) } func addConfigMap(p *testProject, dir string, opts resourceOpts) { - o := uo.New() - o.SetK8sGVKs("", "v1", "ConfigMap") - mergeMetadata(o, opts) - o.SetNestedField(map[string]interface{}{}, "data") - p.addKustomizeResources(dir, []kustomizeResource{ - {fmt.Sprintf("%s.yml", opts.name), o}, - }) + o := uo.New() + o.SetK8sGVKs("", "v1", "ConfigMap") + mergeMetadata(o, opts) + o.SetNestedField(map[string]interface{}{}, "data") + p.addKustomizeResources(dir, []kustomizeResource{ + {fmt.Sprintf("%s.yml", opts.name), o}, + }) } func getHookResult(t *testing.T, p *testProject, k *KindCluster, secretName string) *uo.UnstructuredObject { - o := k.KubectlYamlMust(t, "-n", p.projectName, "get", "secret", secretName) - s, ok, err := o.GetNestedString("data", "result") - if err != nil { - t.Fatal(err) - } - if !ok { - t.Fatalf("result not found") - } - b, err := base64.StdEncoding.DecodeString(s) - if err != nil { - t.Fatal(err) - } - r, err := uo.FromString(string(b)) - if err != nil { - t.Fatal(err) - } - return r + o := k.KubectlYamlMust(t, "-n", p.projectName, "get", "secret", secretName) + s, ok, err := o.GetNestedString("data", "result") + if err != nil { + t.Fatal(err) + } + if !ok { + t.Fatalf("result not found") + } + b, err := base64.StdEncoding.DecodeString(s) + if err != nil { + t.Fatal(err) + } + r, err := uo.FromString(string(b)) + if err != nil { + t.Fatal(err) + } + return r } func getHookResultCMNames(t *testing.T, p *testProject, k *KindCluster, second bool) []string { - secretName := "hook-result" - if second { - secretName = "hook-result2" - } - o := getHookResult(t, p, k, secretName) - items, _, _ := o.GetNestedObjectList("items") - var names []string - for _, x := range items { - names = append(names, x.GetK8sName()) - } - return names + secretName := "hook-result" + if second { + secretName = "hook-result2" + } + o := getHookResult(t, p, k, secretName) + items, _, _ := o.GetNestedObjectList("items") + var names []string + for _, x := range items { + names = append(names, x.GetK8sName()) + } + return names } func assertHookResultCMName(t *testing.T, p *testProject, k *KindCluster, second bool, cmName string) { - names := getHookResultCMNames(t, p, k, second) - for _, x := range names { - if x == cmName { - return - } - } - t.Fatalf("%s not found in hook result", cmName) + names := getHookResultCMNames(t, p, k, second) + for _, x := range names { + if x == cmName { + return + } + } + t.Fatalf("%s not found in hook result", cmName) } func assertHookResultNotCMName(t *testing.T, p *testProject, k *KindCluster, second bool, cmName string) { - names := getHookResultCMNames(t, p, k, second) - for _, x := range names { - if x == cmName { - t.Fatalf("%s found in hook result", cmName) - } - } + names := getHookResultCMNames(t, p, k, second) + for _, x := range names { + if x == cmName { + t.Fatalf("%s found in hook result", cmName) + } + } } func prepareHookTestProject(t *testing.T, name string, hook string, hookDeletionPolicy string) (*testProject, *KindCluster) { - isDone := false - namespace := fmt.Sprintf("hook-%s", name) - p := &testProject{} - p.init(t, namespace) - defer func() { - if !isDone { - p.cleanup() - } - }() + isDone := false + namespace := fmt.Sprintf("hook-%s", name) + p := &testProject{} + p.init(t, namespace) + defer func() { + if !isDone { + p.cleanup() + } + }() - k := defaultKindCluster + k := defaultKindCluster - recreateNamespace(t, k, namespace) + recreateNamespace(t, k, namespace) - p.updateKindCluster(k, nil) - p.updateTarget("test", k.Name, nil) + p.updateKindCluster(k, nil) + p.updateTarget("test", k.Name, nil) - addHookDeployment(p, "hook", resourceOpts{name: "hook", namespace: namespace}, false, hook, hookDeletionPolicy) - addConfigMap(p, "hook", resourceOpts{name: "cm1", namespace: namespace}) + addHookDeployment(p, "hook", resourceOpts{name: "hook", namespace: namespace}, false, hook, hookDeletionPolicy) + addConfigMap(p, "hook", resourceOpts{name: "cm1", namespace: namespace}) - isDone = true - return p, k + isDone = true + return p, k } func ensureHookExecuted(t *testing.T, p *testProject, k *KindCluster) { - _, _ = k.Kubectl("delete", "-n", p.projectName, "secret", "hook-result", "hook-result2") - p.KluctlMust("deploy", "--yes", "-t", "test") - assertResourceExists(t, k, p.projectName, "ConfigMap/cm1") + _, _ = k.Kubectl("delete", "-n", p.projectName, "secret", "hook-result", "hook-result2") + p.KluctlMust("deploy", "--yes", "-t", "test") + assertResourceExists(t, k, p.projectName, "ConfigMap/cm1") } func ensureHookNotExecuted(t *testing.T, p *testProject, k *KindCluster) { - _, _ = k.Kubectl("delete", "-n", p.projectName, "secret", "hook-result", "hook-result2") - p.KluctlMust("deploy", "--yes", "-t", "test") - assertResourceNotExists(t, k, p.projectName, "Secret/hook-result") + _, _ = k.Kubectl("delete", "-n", p.projectName, "secret", "hook-result", "hook-result2") + p.KluctlMust("deploy", "--yes", "-t", "test") + assertResourceNotExists(t, k, p.projectName, "Secret/hook-result") } func TestHooksPreDeployInitial(t *testing.T) { - t.Parallel() - p, k := prepareHookTestProject(t, "pre-deploy-initial", "pre-deploy-initial", "") - defer p.cleanup() - ensureHookExecuted(t, p, k) - assertHookResultNotCMName(t, p, k, false, "cm1") - ensureHookNotExecuted(t, p, k) + t.Parallel() + p, k := prepareHookTestProject(t, "pre-deploy-initial", "pre-deploy-initial", "") + defer p.cleanup() + ensureHookExecuted(t, p, k) + assertHookResultNotCMName(t, p, k, false, "cm1") + ensureHookNotExecuted(t, p, k) } func TestHooksPostDeployInitial(t *testing.T) { - t.Parallel() - p, k := prepareHookTestProject(t, "post-deploy-initial", "post-deploy-initial", "") - defer p.cleanup() - ensureHookExecuted(t, p, k) - assertHookResultCMName(t, p, k, false, "cm1") - ensureHookNotExecuted(t, p, k) + t.Parallel() + p, k := prepareHookTestProject(t, "post-deploy-initial", "post-deploy-initial", "") + defer p.cleanup() + ensureHookExecuted(t, p, k) + assertHookResultCMName(t, p, k, false, "cm1") + ensureHookNotExecuted(t, p, k) } func TestHooksPreDeployUpgrade(t *testing.T) { - t.Parallel() - p, k := prepareHookTestProject(t, "pre-deploy-upgrade", "pre-deploy-upgrade", "") - defer p.cleanup() - addConfigMap(p, "hook", resourceOpts{name: "cm2", namespace: p.projectName}) - ensureHookNotExecuted(t, p, k) - k.KubectlMust(t, "delete", "-n", p.projectName, "configmap", "cm1") - ensureHookExecuted(t, p, k) - assertHookResultNotCMName(t, p, k, false, "cm1") - ensureHookExecuted(t, p, k) - assertHookResultCMName(t, p, k, false, "cm1") + t.Parallel() + p, k := prepareHookTestProject(t, "pre-deploy-upgrade", "pre-deploy-upgrade", "") + defer p.cleanup() + addConfigMap(p, "hook", resourceOpts{name: "cm2", namespace: p.projectName}) + ensureHookNotExecuted(t, p, k) + k.KubectlMust(t, "delete", "-n", p.projectName, "configmap", "cm1") + ensureHookExecuted(t, p, k) + assertHookResultNotCMName(t, p, k, false, "cm1") + ensureHookExecuted(t, p, k) + assertHookResultCMName(t, p, k, false, "cm1") } func TestHooksPostDeployUpgrade(t *testing.T) { - t.Parallel() - p, k := prepareHookTestProject(t, "post-deploy-upgrade", "post-deploy-upgrade", "") - defer p.cleanup() - addConfigMap(p, "hook", resourceOpts{name: "cm2", namespace: p.projectName}) - ensureHookNotExecuted(t, p, k) - k.KubectlMust(t, "delete", "-n", p.projectName, "configmap", "cm1") - ensureHookExecuted(t, p, k) - assertHookResultCMName(t, p, k, false, "cm1") + t.Parallel() + p, k := prepareHookTestProject(t, "post-deploy-upgrade", "post-deploy-upgrade", "") + defer p.cleanup() + addConfigMap(p, "hook", resourceOpts{name: "cm2", namespace: p.projectName}) + ensureHookNotExecuted(t, p, k) + k.KubectlMust(t, "delete", "-n", p.projectName, "configmap", "cm1") + ensureHookExecuted(t, p, k) + assertHookResultCMName(t, p, k, false, "cm1") } func doTestHooksPreDeploy(t *testing.T, name string, hooks string) { - p, k := prepareHookTestProject(t, name, hooks, "") - defer p.cleanup() - addConfigMap(p, "hook", resourceOpts{name: "cm2", namespace: p.projectName}) - ensureHookExecuted(t, p, k) - k.KubectlMust(t, "delete", "-n", p.projectName, "configmap", "cm1") - ensureHookExecuted(t, p, k) - assertHookResultNotCMName(t, p, k, false, "cm1") - ensureHookExecuted(t, p, k) - assertHookResultCMName(t, p, k, false, "cm1") + p, k := prepareHookTestProject(t, name, hooks, "") + defer p.cleanup() + addConfigMap(p, "hook", resourceOpts{name: "cm2", namespace: p.projectName}) + ensureHookExecuted(t, p, k) + k.KubectlMust(t, "delete", "-n", p.projectName, "configmap", "cm1") + ensureHookExecuted(t, p, k) + assertHookResultNotCMName(t, p, k, false, "cm1") + ensureHookExecuted(t, p, k) + assertHookResultCMName(t, p, k, false, "cm1") } func doTestHooksPostDeploy(t *testing.T, name string, hooks string) { - p, k := prepareHookTestProject(t, name, hooks, "") - defer p.cleanup() - addConfigMap(p, "hook", resourceOpts{name: "cm2", namespace: p.projectName}) - ensureHookExecuted(t, p, k) - k.KubectlMust(t, "delete", "-n", p.projectName, "configmap", "cm1") - ensureHookExecuted(t, p, k) - assertHookResultCMName(t, p, k, false, "cm1") + p, k := prepareHookTestProject(t, name, hooks, "") + defer p.cleanup() + addConfigMap(p, "hook", resourceOpts{name: "cm2", namespace: p.projectName}) + ensureHookExecuted(t, p, k) + k.KubectlMust(t, "delete", "-n", p.projectName, "configmap", "cm1") + ensureHookExecuted(t, p, k) + assertHookResultCMName(t, p, k, false, "cm1") } func doTestHooksPrePostDeploy(t *testing.T, name string, hooks string) { - p, k := prepareHookTestProject(t, name, hooks, "") - defer p.cleanup() - addConfigMap(p, "hook", resourceOpts{name: "cm2", namespace: p.projectName}) - ensureHookExecuted(t, p, k) - assertHookResultNotCMName(t, p, k, false, "cm1") - assertHookResultNotCMName(t, p, k, false, "cm2") - assertHookResultCMName(t, p, k, true, "cm1") - assertHookResultCMName(t, p, k, true, "cm2") - - ensureHookExecuted(t, p, k) - assertHookResultCMName(t, p, k, false, "cm1") - assertHookResultNotCMName(t, p, k, false, "cm2") - assertHookResultCMName(t, p, k, true, "cm1") - assertHookResultCMName(t, p, k, true, "cm2") + p, k := prepareHookTestProject(t, name, hooks, "") + defer p.cleanup() + addConfigMap(p, "hook", resourceOpts{name: "cm2", namespace: p.projectName}) + ensureHookExecuted(t, p, k) + assertHookResultNotCMName(t, p, k, false, "cm1") + assertHookResultNotCMName(t, p, k, false, "cm2") + assertHookResultCMName(t, p, k, true, "cm1") + assertHookResultCMName(t, p, k, true, "cm2") + + ensureHookExecuted(t, p, k) + assertHookResultCMName(t, p, k, false, "cm1") + assertHookResultNotCMName(t, p, k, false, "cm2") + assertHookResultCMName(t, p, k, true, "cm1") + assertHookResultCMName(t, p, k, true, "cm2") } func TestHooksPreDeploy(t *testing.T) { - t.Parallel() - doTestHooksPreDeploy(t, "pre-deploy", "pre-deploy") + t.Parallel() + doTestHooksPreDeploy(t, "pre-deploy", "pre-deploy") } func TestHooksPreDeploy2(t *testing.T) { - t.Parallel() - // same as pre-deploy - doTestHooksPreDeploy(t, "pre-deploy2", "pre-deploy-initial,pre-deploy-upgrade") + t.Parallel() + // same as pre-deploy + doTestHooksPreDeploy(t, "pre-deploy2", "pre-deploy-initial,pre-deploy-upgrade") } func TestHooksPostDeploy(t *testing.T) { - t.Parallel() - doTestHooksPostDeploy(t, "post-deploy", "post-deploy") + t.Parallel() + doTestHooksPostDeploy(t, "post-deploy", "post-deploy") } func TestHooksPostDeploy2(t *testing.T) { - t.Parallel() - // same as post-deploy - doTestHooksPostDeploy(t, "post-deploy2", "post-deploy-initial,post-deploy-upgrade") + t.Parallel() + // same as post-deploy + doTestHooksPostDeploy(t, "post-deploy2", "post-deploy-initial,post-deploy-upgrade") } func TestHooksPrePostDeploy(t *testing.T) { - t.Parallel() - doTestHooksPrePostDeploy(t, "pre-post-deploy", "pre-deploy,post-deploy") + t.Parallel() + doTestHooksPrePostDeploy(t, "pre-post-deploy", "pre-deploy,post-deploy") } func TestHooksPrePostDeploy2(t *testing.T) { - t.Parallel() - doTestHooksPrePostDeploy(t, "pre-post-deploy2", "pre-deploy-initial,pre-deploy-upgrade,post-deploy-initial,post-deploy-upgrade") + t.Parallel() + doTestHooksPrePostDeploy(t, "pre-post-deploy2", "pre-deploy-initial,pre-deploy-upgrade,post-deploy-initial,post-deploy-upgrade") } diff --git a/e2e/inclusion_test.go b/e2e/inclusion_test.go index eab1bdcaf..259f14831 100644 --- a/e2e/inclusion_test.go +++ b/e2e/inclusion_test.go @@ -76,7 +76,7 @@ func TestInclusionTags(t *testing.T) { defer p.cleanup() shouldExists := make(map[string]bool) - doAssertExists := func(add... string) { + doAssertExists := func(add ...string) { assertExistsHelper(t, p, k, shouldExists, add, nil) } @@ -111,7 +111,7 @@ func TestExclusionTags(t *testing.T) { defer p.cleanup() shouldExists := make(map[string]bool) - doAssertExists := func(add... string) { + doAssertExists := func(add ...string) { assertExistsHelper(t, p, k, shouldExists, add, nil) } @@ -136,7 +136,7 @@ func TestInclusionIncludeDirs(t *testing.T) { defer p.cleanup() shouldExists := make(map[string]bool) - doAssertExists := func(add... string) { + doAssertExists := func(add ...string) { assertExistsHelper(t, p, k, shouldExists, add, nil) } @@ -158,7 +158,7 @@ func TestInclusionDeploymentDirs(t *testing.T) { defer p.cleanup() shouldExists := make(map[string]bool) - doAssertExists := func(add... string) { + doAssertExists := func(add ...string) { assertExistsHelper(t, p, k, shouldExists, add, nil) } @@ -236,7 +236,7 @@ func TestInclusionDelete(t *testing.T) { p.KluctlMust("delete", "--yes", "-t", "test", "-E", "cm2") var a []string for _, x := range p.listDeploymentItemPathes(".", false) { - if x != "cm1" && x != "cm2"{ + if x != "cm1" && x != "cm2" { a = append(a, x) } } diff --git a/e2e/kind_cluster.go b/e2e/kind_cluster.go index 3d519a7c6..831636c23 100644 --- a/e2e/kind_cluster.go +++ b/e2e/kind_cluster.go @@ -27,7 +27,7 @@ func CreateKindCluster(name, kubeconfigPath string) (*KindCluster, error) { provider := cluster.NewProvider(cluster.ProviderWithLogger(kindcmd.NewLogger())) c := &KindCluster{ - Name: name, + Name: name, kubeconfig: kubeconfigPath, } @@ -67,7 +67,7 @@ func (c *KindCluster) RESTConfig() *rest.Config { return c.config } -func (c *KindCluster) Kubectl(args... string) (string, error) { +func (c *KindCluster) Kubectl(args ...string) (string, error) { cmd := exec.Command("kubectl", args...) cmd.Env = os.Environ() cmd.Env = append(cmd.Env, fmt.Sprintf("KUBECONFIG=%s", c.kubeconfig)) @@ -76,7 +76,7 @@ func (c *KindCluster) Kubectl(args... string) (string, error) { return string(stdout), err } -func (c *KindCluster) KubectlMust(t *testing.T, args... string) string { +func (c *KindCluster) KubectlMust(t *testing.T, args ...string) string { stdout, err := c.Kubectl(args...) if err != nil { if e, ok := err.(*exec.ExitError); ok { @@ -88,7 +88,7 @@ func (c *KindCluster) KubectlMust(t *testing.T, args... string) string { return stdout } -func (c *KindCluster) KubectlYaml(args... string) (*uo.UnstructuredObject, error) { +func (c *KindCluster) KubectlYaml(args ...string) (*uo.UnstructuredObject, error) { args = append(args, "-oyaml") stdout, err := c.Kubectl(args...) if err != nil { @@ -99,7 +99,7 @@ func (c *KindCluster) KubectlYaml(args... string) (*uo.UnstructuredObject, error return ret, err } -func (c *KindCluster) KubectlYamlMust(t *testing.T, args... string) *uo.UnstructuredObject { +func (c *KindCluster) KubectlYamlMust(t *testing.T, args ...string) *uo.UnstructuredObject { o, err := c.KubectlYaml(args...) if err != nil { if e, ok := err.(*exec.ExitError); ok { @@ -145,7 +145,7 @@ func crateKindCluster(name string) *KindCluster { if err != nil { log.Fatal(err) } - kubeconfig := path.Join(tmpdir, fmt.Sprintf("kubeconfig-%s.yml", name)) + kubeconfig := path.Join(tmpdir, fmt.Sprintf("kubeconfig-%s.yml", name)) k, err := CreateKindCluster(name, kubeconfig) if err != nil { log.Fatal(err) diff --git a/e2e/project.go b/e2e/project.go index 217b16340..a33413b5c 100644 --- a/e2e/project.go +++ b/e2e/project.go @@ -18,16 +18,16 @@ import ( ) type testProject struct { - t *testing.T + t *testing.T projectName string kluctlProjectExternal bool - clustersExternal bool - deploymentExternal bool + clustersExternal bool + deploymentExternal bool sealedSecretsExternal bool - localClusters *string - localDeployment *string + localClusters *string + localDeployment *string localSealedSecrets *string kubeconfigs []string @@ -226,7 +226,7 @@ func (p *testProject) listDeploymentItemPathes(dir string, fullPath bool) []stri return ret } -func (p *testProject) updateKustomizeDeployment(dir string, update func (o *uo.UnstructuredObject, wt *git.Worktree) error) { +func (p *testProject) updateKustomizeDeployment(dir string, update func(o *uo.UnstructuredObject, wt *git.Worktree) error) { r, err := git.PlainOpen(p.getDeploymentDir()) if err != nil { p.t.Fatal(err) @@ -279,7 +279,7 @@ func (p *testProject) updateTarget(name string, cluster string, args *uo.Unstruc newTargets = append(newTargets, t) } n := uo.FromMap(map[string]interface{}{ - "name": name, + "name": name, "cluster": cluster, }) if args != nil { @@ -387,7 +387,7 @@ func (p *testProject) convertInterfaceToList(x interface{}) []interface{} { } type kustomizeResource struct { - name string + name string content interface{} } @@ -454,7 +454,7 @@ func (p *testProject) getSealedSecretsDir() string { const stdoutStartMarker = "========= stdout start =========" const stdoutEndMarker = "========= stdout end =========" -func (p *testProject) Kluctl(argsIn... string) (string, string, error) { +func (p *testProject) Kluctl(argsIn ...string) (string, string, error) { var args []string args = append(args, argsIn...) @@ -488,7 +488,7 @@ func (p *testProject) Kluctl(argsIn... string) (string, string, error) { return stdout, stderr, err } -func (p *testProject) KluctlMust(argsIn... string) (string, string) { +func (p *testProject) KluctlMust(argsIn ...string) (string, string) { stdout, stderr, err := p.Kluctl(argsIn...) if err != nil { log.Error(stderr) diff --git a/e2e/utils_resources.go b/e2e/utils_resources.go index f7b27580b..91d747392 100644 --- a/e2e/utils_resources.go +++ b/e2e/utils_resources.go @@ -7,10 +7,10 @@ import ( ) type resourceOpts struct { - name string - namespace string - tags []string - labels map[string]string + name string + namespace string + tags []string + labels map[string]string annotations map[string]string } @@ -62,7 +62,7 @@ func addConfigMapDeployment(p *testProject, dir string, data map[string]string, func addDeploymentHelper(p *testProject, dir string, o *uo.UnstructuredObject, opts resourceOpts) { rbac := renderTemplateObjectHelper(podRbacTemplate, map[string]interface{}{ - "name": o.GetK8sName(), + "name": o.GetK8sName(), "namespace": o.GetK8sNamespace(), }) for _, x := range rbac { @@ -80,9 +80,9 @@ func addDeploymentHelper(p *testProject, dir string, o *uo.UnstructuredObject, o func addDeploymentDeployment(p *testProject, dir string, opts resourceOpts, image string, command []string, args []string) { o := renderTemplateObjectHelper(deploymentTemplate, map[string]interface{}{ - "name": opts.name, + "name": opts.name, "namespace": opts.namespace, - "image": image, + "image": image, }) o[0].SetNestedField(command, "spec", "template", "spec", "containers", 0, "command") o[0].SetNestedField(args, "spec", "template", "spec", "containers", 0, "args") @@ -91,9 +91,9 @@ func addDeploymentDeployment(p *testProject, dir string, opts resourceOpts, imag func addJobDeployment(p *testProject, dir string, opts resourceOpts, image string, command []string, args []string) { o := renderTemplateObjectHelper(jobTemplate, map[string]interface{}{ - "name": opts.name, + "name": opts.name, "namespace": opts.namespace, - "image": image, + "image": image, }) o[0].SetNestedField(command, "spec", "template", "spec", "containers", 0, "command") o[0].SetNestedField(args, "spec", "template", "spec", "containers", 0, "args") @@ -152,7 +152,7 @@ spec: name: container ` -const jobTemplate=` +const jobTemplate = ` apiVersion: batch/v1 kind: Job metadata: @@ -168,4 +168,4 @@ spec: - image: {{ .image }} imagePullPolicy: IfNotPresent name: container -` \ No newline at end of file +` diff --git a/e2e/utils_test.go b/e2e/utils_test.go index 932f9a7e4..8f6ed81fc 100644 --- a/e2e/utils_test.go +++ b/e2e/utils_test.go @@ -11,10 +11,10 @@ import ( "time" ) -func recreateNamespace(t* testing.T, k *KindCluster, namespace string) { +func recreateNamespace(t *testing.T, k *KindCluster, namespace string) { _, _ = k.Kubectl("delete", "ns", namespace) k.KubectlMust(t, "create", "ns", namespace) - k.KubectlMust(t,"label", "ns", namespace, "kluctl-e2e=true") + k.KubectlMust(t, "label", "ns", namespace, "kluctl-e2e=true") } func deleteTestNamespaces(k *KindCluster) { @@ -74,8 +74,8 @@ func assertResourceNotExists(t *testing.T, k *KindCluster, namespace string, res } } -func assertNestedFieldEquals(t *testing.T, o *uo.UnstructuredObject, expected interface{}, keys... interface{}) { - v, ok , err := o.GetNestedField(keys...) +func assertNestedFieldEquals(t *testing.T, o *uo.UnstructuredObject, expected interface{}, keys ...interface{}) { + v, ok, err := o.GetNestedField(keys...) if err != nil { t.Fatal(err) } From c695864ce3c329f8744492127f8b534896bb4a46 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 24 Feb 2022 13:46:07 +0100 Subject: [PATCH 0395/2916] Allow to specify kind cluster name and kubeconfig --- e2e/kind_cluster.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/e2e/kind_cluster.go b/e2e/kind_cluster.go index 831636c23..28f6a6686 100644 --- a/e2e/kind_cluster.go +++ b/e2e/kind_cluster.go @@ -1,7 +1,9 @@ package e2e import ( + "flag" "fmt" + "github.com/codablock/kluctl/pkg/utils" "github.com/codablock/kluctl/pkg/utils/uo" "github.com/codablock/kluctl/pkg/yaml" "github.com/pkg/errors" @@ -139,13 +141,7 @@ func kindCreate(name, kubeconfig string) error { } } -func crateKindCluster(name string) *KindCluster { - tmpdir := path.Join(os.TempDir(), "kluctl-e2e") - err := os.MkdirAll(tmpdir, 0o700) - if err != nil { - log.Fatal(err) - } - kubeconfig := path.Join(tmpdir, fmt.Sprintf("kubeconfig-%s.yml", name)) +func createKindCluster(name string, kubeconfig string) *KindCluster { k, err := CreateKindCluster(name, kubeconfig) if err != nil { log.Fatal(err) @@ -153,8 +149,11 @@ func crateKindCluster(name string) *KindCluster { return k } +var kindName = flag.String("kind-cluster-name", "kluctl-e2e", "Kind cluster name to use/create") +var kindKubeconfig = flag.String("kind-kubeconfig", path.Join(utils.GetTmpBaseDir(), "kluctl-e2e-kubeconfig.yml"), "Kind kubeconfig to use/create") + func createDefaultKindCluster() *KindCluster { - return crateKindCluster("kluctl-e2e") + return createKindCluster(*kindName, *kindKubeconfig) } var defaultKindCluster = createDefaultKindCluster() From 7f3e8095a60cb4ccf18e0f2e9bf18066cc3dc741 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 24 Feb 2022 15:46:21 +0100 Subject: [PATCH 0396/2916] Package python binaries as go:generate script --- pkg/python/lib_darwin.go | 2 ++ pkg/python/lib_linux.go | 2 ++ pkg/python/lib_windows.go | 2 ++ pkg/python/tar-python.sh | 9 +++++++++ 4 files changed, 15 insertions(+) create mode 100755 pkg/python/tar-python.sh diff --git a/pkg/python/lib_darwin.go b/pkg/python/lib_darwin.go index 708de5fff..5181aebf9 100644 --- a/pkg/python/lib_darwin.go +++ b/pkg/python/lib_darwin.go @@ -2,5 +2,7 @@ package python import "embed" +//go:generate bash ./tar-python.sh python-lib-darwin.tar.gz darwin/cpython-install lib + //go:embed python-lib-darwin.tar.gz var pythonLib embed.FS diff --git a/pkg/python/lib_linux.go b/pkg/python/lib_linux.go index a67838c1b..e6f94ba58 100644 --- a/pkg/python/lib_linux.go +++ b/pkg/python/lib_linux.go @@ -2,5 +2,7 @@ package python import "embed" +//go:generate bash ./tar-python.sh python-lib-linux.tar.gz linux/cpython-install lib + //go:embed python-lib-linux.tar.gz var pythonLib embed.FS diff --git a/pkg/python/lib_windows.go b/pkg/python/lib_windows.go index 9232a5115..b7b53e6f6 100644 --- a/pkg/python/lib_windows.go +++ b/pkg/python/lib_windows.go @@ -2,5 +2,7 @@ package python import "embed" +//go:generate bash ./tar-python.sh python-lib-windows.tar.gz windows '*' + //go:embed python-lib-windows.tar.gz var pythonLib embed.FS diff --git a/pkg/python/tar-python.sh b/pkg/python/tar-python.sh new file mode 100755 index 000000000..9b94a481d --- /dev/null +++ b/pkg/python/tar-python.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -e + +DIR=$(cd $(dirname $0) && pwd) + +cd $DIR/../../build-python/$2 + +tar czf $DIR/$1 $3 From 467dd5cd48900ab21426afafec681436c8b8b848 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 24 Feb 2022 21:18:49 +0100 Subject: [PATCH 0397/2916] Honor COLUMN env var in prettytable --- pkg/utils/prettytable.go | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/pkg/utils/prettytable.go b/pkg/utils/prettytable.go index a4b2fd934..2a7b2af0d 100644 --- a/pkg/utils/prettytable.go +++ b/pkg/utils/prettytable.go @@ -3,7 +3,9 @@ package utils import ( "bytes" "golang.org/x/crypto/ssh/terminal" + "os" "sort" + "strconv" "strings" ) @@ -23,6 +25,20 @@ func (t *PrettyTable) SortRows(col int) { }) } +func getTermWidth() int { + if c, ok := os.LookupEnv("COLUMNS"); ok { + tw, err := strconv.ParseInt(c, 10, 32) + if err == nil { + return int(tw) + } + } + tw, _, err := terminal.GetSize(0) + if err != nil { + return 80 + } + return tw +} + func (t *PrettyTable) Render(limitWidths []int) string { cols := len(t.rows[0]) @@ -66,10 +82,7 @@ func (t *PrettyTable) Render(limitWidths []int) string { } if len(limitWidths) < cols { - tw, _, err := terminal.GetSize(0) - if err != nil { - tw = 80 - } + tw := getTermWidth() // last column should use all remaining space widths[len(limitWidths)] = tw - widthSum - (cols-1)*3 - 4 } From 296bcff8420bebbe51875878a0bcb4bc4bde48fe Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 25 Feb 2022 17:17:05 +0100 Subject: [PATCH 0398/2916] Switch from cobra -> kong --- cmd/kluctl/args/images.go | 25 ++-- cmd/kluctl/args/inclusion.go | 28 ++--- cmd/kluctl/args/misc.go | 102 ++++++--------- cmd/kluctl/args/project.go | 63 +++------- .../commands/cmd_check_image_updates.go | 37 +++--- cmd/kluctl/commands/cmd_delete.go | 68 +++++----- cmd/kluctl/commands/cmd_deploy.go | 79 ++++++------ cmd/kluctl/commands/cmd_diff.go | 68 +++++----- cmd/kluctl/commands/cmd_downscale.go | 64 +++++----- cmd/kluctl/commands/cmd_helm_pull.go | 38 ++---- cmd/kluctl/commands/cmd_helm_update.go | 45 +++---- cmd/kluctl/commands/cmd_list_images.go | 54 ++++---- cmd/kluctl/commands/cmd_list_targets.go | 36 ++---- cmd/kluctl/commands/cmd_prune.go | 69 +++++------ cmd/kluctl/commands/cmd_render.go | 52 ++++---- cmd/kluctl/commands/cmd_seal.go | 72 +++++------ cmd/kluctl/commands/cmd_validate.go | 66 +++++----- cmd/kluctl/commands/root.go | 116 ++++++++++++------ cmd/kluctl/commands/utils.go | 58 +++++---- e2e/cmd_wrapper_test.go | 4 +- go.mod | 13 +- go.sum | 18 +-- 22 files changed, 524 insertions(+), 651 deletions(-) diff --git a/cmd/kluctl/args/images.go b/cmd/kluctl/args/images.go index 539681cdb..fc2d39ece 100644 --- a/cmd/kluctl/args/images.go +++ b/cmd/kluctl/args/images.go @@ -4,35 +4,26 @@ import ( "fmt" "github.com/codablock/kluctl/pkg/types" "github.com/codablock/kluctl/pkg/yaml" - "github.com/spf13/cobra" "strings" ) -var ( - FixedImages []string - FixedImagesFile string - UpdateImages bool -) - -func AddImageArgs(cmd *cobra.Command) { - cmd.Flags().StringArrayVarP(&FixedImages, "fixed-image", "F", nil, "Pin an image to a given version. Expects '--fixed-image=image<:namespace:deployment:container>=result'") - cmd.Flags().StringVar(&FixedImagesFile, "fixed-images-file", "", "Use .yml file to pin image versions. See output of list-images sub-command or read the documentation for details about the output format") - cmd.Flags().BoolVarP(&UpdateImages, "update-images", "u", false, "This causes kluctl to prefer the latest image found in registries, based on the `latest_image` filters provided to 'images.get_image(...)' calls. Use this flag if you want to update to the latest versions/tags of all images. '-u' takes precedence over `--fixed-image/--fixed-images-file`, meaning that the latest images are used even if an older image is given via fixed images.") - - _ = cmd.MarkFlagFilename("fixed-images-file", "yml", "yaml") +type ImageFlags struct { + FixedImage []string `group:"images" short:"F" help:"Pin an image to a given version. Expects '--fixed-image=image<:namespace:deployment:container>=result'"` + FixedImagesFile string `group:"images" help:"Use .yml file to pin image versions. See output of list-images sub-command or read the documentation for details about the output format" type:"existingfile"` + UpdateImages bool `group:"images" short:"u" help:"This causes kluctl to prefer the latest image found in registries, based on the 'latest_image' filters provided to 'images.get_image(...)' calls. Use this flag if you want to update to the latest versions/tags of all images. '-u' takes precedence over '--fixed-image/--fixed-images-file', meaning that the latest images are used even if an older image is given via fixed images."` } -func LoadFixedImagesFromArgs() (*types.FixedImagesConfig, error) { +func (args *ImageFlags) LoadFixedImagesFromArgs() (*types.FixedImagesConfig, error) { var ret types.FixedImagesConfig - if FixedImagesFile != "" { - err := yaml.ReadYamlFile(FixedImagesFile, &ret) + if args.FixedImagesFile != "" { + err := yaml.ReadYamlFile(args.FixedImagesFile, &ret) if err != nil { return nil, err } } - for _, fi := range FixedImages { + for _, fi := range args.FixedImage { e, err := buildFixedImageEntryFromArg(fi) if err != nil { return nil, err diff --git a/cmd/kluctl/args/inclusion.go b/cmd/kluctl/args/inclusion.go index c99e20413..993f10af7 100644 --- a/cmd/kluctl/args/inclusion.go +++ b/cmd/kluctl/args/inclusion.go @@ -3,41 +3,33 @@ package args import ( "fmt" "github.com/codablock/kluctl/pkg/utils" - "github.com/spf13/cobra" "os" "path/filepath" "strings" ) -var ( - IncludeTags []string - ExcludeTags []string - IncludeDeploymentDirs []string - ExcludeDeploymentDirs []string -) - -func AddInclusionArgs(cmd *cobra.Command) { - cmd.Flags().StringArrayVarP(&IncludeTags, "include-tags", "I", nil, "Include deployments with given tag.") - cmd.Flags().StringArrayVarP(&ExcludeTags, "exclude-tags", "E", nil, "Exclude deployments with given tag. Exclusion has precedence over inclusion, meaning that explicitly excluded deployments will always be excluded even if an inclusion rule would match the same deployment.") - cmd.Flags().StringArrayVar(&IncludeDeploymentDirs, "include-deployment-dir", nil, "Include deployment dir. The path must be relative to the root deployment project.") - cmd.Flags().StringArrayVar(&ExcludeDeploymentDirs, "exclude-deployment-dir", nil, "Exclude deployment dir. The path must be relative to the root deployment project. Exclusion has precedence over inclusion, same as in --exclude-tag") +type InclusionFlags struct { + IncludeTag []string `group:"inclusion" short:"I" help:"Include deployments with given tag."` + ExcludeTag []string `group:"inclusion" short:"E" help:"Exclude deployments with given tag. Exclusion has precedence over inclusion, meaning that explicitly excluded deployments will always be excluded even if an inclusion rule would match the same deployment."` + IncludeDeploymentDir []string `group:"inclusion" help:"Include deployment dir. The path must be relative to the root deployment project."` + ExcludeDeploymentDir []string `group:"inclusion" help:"Exclude deployment dir. The path must be relative to the root deployment project. Exclusion has precedence over inclusion, same as in --exclude-tag"` } -func ParseInclusionFromArgs() (*utils.Inclusion, error) { +func (args *InclusionFlags) ParseInclusionFromArgs() (*utils.Inclusion, error) { inclusion := utils.NewInclusion() - for _, tag := range IncludeTags { + for _, tag := range args.IncludeTag { inclusion.AddInclude("tag", tag) } - for _, tag := range ExcludeTags { + for _, tag := range args.ExcludeTag { inclusion.AddExclude("tag", tag) } - for _, dir := range IncludeDeploymentDirs { + for _, dir := range args.IncludeDeploymentDir { if filepath.IsAbs(dir) { return nil, fmt.Errorf("--include-deployment-dir path must be relative") } inclusion.AddInclude("deploymentItemDir", strings.ReplaceAll(dir, string(os.PathSeparator), "/")) } - for _, dir := range ExcludeDeploymentDirs { + for _, dir := range args.ExcludeDeploymentDir { if filepath.IsAbs(dir) { return nil, fmt.Errorf("--exclude-deployment-dir path must be relative") } diff --git a/cmd/kluctl/args/misc.go b/cmd/kluctl/args/misc.go index dca50864c..a9fc026a8 100644 --- a/cmd/kluctl/args/misc.go +++ b/cmd/kluctl/args/misc.go @@ -1,76 +1,48 @@ package args import ( - "github.com/spf13/cobra" "time" ) -var ( - ForceYes bool - DryRun bool - ForceApply bool - ReplaceOnError bool - ForceReplaceOnError bool - HookTimeout time.Duration - IgnoreTags bool - IgnoreLabels bool - IgnoreAnnotations bool - AbortOnError bool - OutputFormat []string - Output []string - RenderOutputDir string +type YesFlags struct { + Yes bool `group:"misc" short:"y" help:"Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'."` +} - Wait time.Duration - Sleep time.Duration - WarningsAsErrors bool -) +type DryRunFlags struct { + DryRun bool `group:"misc" help:"Performs all kubernetes API calls in dry-run mode."` +} + +type ForceApplyFlags struct { + ForceApply bool `group:"misc" help:"Force conflict resolution when applying. See documentation for details"` +} + +type ReplaceOnErrorFlags struct { + ReplaceOnError bool `group:"misc" help:"When patching an object fails, try to replace it. See documentation for more details."` + ForceReplaceOnError bool `group:"misc" help:"Same as --replace-on-error, but also try to delete and re-create objects. See documentation for more details."` +} + +type HookFlags struct { + HookTimeout time.Duration `group:"misc" help:"Maximum time to wait for hook readiness. The timeout is meant per-hook. Timeouts are in the duration format (1s, 1m, 1h, ...). If not specified, a default timeout of 5m is used." default:"5m"` +} + +type IgnoreFlags struct { + IgnoreTags bool `group:"misc" help:"Ignores changes in tags when diffing"` + IgnoreLabels bool `group:"misc" help:"Ignores changes in labels when diffing"` + IgnoreAnnotations bool `group:"misc" help:"Ignores changes in annotations when diffing"` +} + +type AbortOnErrorFlags struct { + AbortOnError bool `group:"misc" help:"Abort deploying when an error occurs instead of trying the remaining deployments"` +} + +type OutputFormatFlags struct { + OutputFormat []string `group:"misc" short:"o" help:"Specify output format and target file, in the format 'format=path'. Format can either be 'text' or 'yaml'. Can be specified multiple times. The actual format for yaml is currently not documented and subject to change."` +} -type EnabledMiscArguments struct { - Yes bool - DryRun bool - ForceApply bool - ReplaceOnError bool - HookTimeout bool - IgnoreLabels bool - AbortOnError bool - OutputFormat bool - Output bool - RenderOutputDir bool +type OutputFlags struct { + Output []string `group:"misc" short:"o" help:"Specify output target file. Can be specified multiple times"` } -func AddMiscArguments(cmd *cobra.Command, enabledArgs EnabledMiscArguments) { - if enabledArgs.Yes { - cmd.Flags().BoolVarP(&ForceYes, "yes", "y", false, "Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'.") - } - if enabledArgs.DryRun { - cmd.Flags().BoolVar(&DryRun, "dry-run", false, "Performs all kubernetes API calls in dry-run mode.") - } - if enabledArgs.ForceApply { - cmd.Flags().BoolVar(&ForceApply, "force-apply", false, "Force conflict resolution when applying. See documentation for details") - } - if enabledArgs.ReplaceOnError { - cmd.Flags().BoolVar(&ReplaceOnError, "replace-on-error", false, "When patching an object fails, try to replace it. See documentation for more details.") - cmd.Flags().BoolVar(&ForceReplaceOnError, "force-replace-on-error", false, "Same as --replace-on-error, but also try to delete and re-create objects. See documentation for more details.") - } - if enabledArgs.HookTimeout { - cmd.Flags().DurationVar(&HookTimeout, "hook-timeout", 5*time.Minute, "Maximum time to wait for hook readiness. The timeout is meant per-hook. Timeouts are in the duration format (1s, 1m, 1h, ...). If not specified, a default timeout of 5m is used.") - } - if enabledArgs.IgnoreLabels { - cmd.Flags().BoolVar(&IgnoreTags, "ignore-tags", false, "Ignores changes in tags when diffing") - cmd.Flags().BoolVar(&IgnoreLabels, "ignore-labels", false, "Ignores changes in labels when diffing") - cmd.Flags().BoolVar(&IgnoreAnnotations, "ignore-annotations", false, "Ignores changes in annotations when diffing") - } - if enabledArgs.AbortOnError { - cmd.Flags().BoolVar(&AbortOnError, "abort-on-error", false, "Abort deploying when an error occurs instead of trying the remaining deployments") - } - if enabledArgs.OutputFormat { - cmd.Flags().StringArrayVarP(&OutputFormat, "output", "o", nil, "Specify output format and target file, in the format 'format=path'. Format can either be 'text' or 'yaml'. Can be specified multiple times. The actual format for yaml is currently not documented and subject to change.") - } - if enabledArgs.Output { - cmd.Flags().StringArrayVarP(&Output, "output", "o", nil, "Specify output target file. Can be specified multiple times") - } - if enabledArgs.RenderOutputDir { - cmd.Flags().StringVar(&RenderOutputDir, "render-output-dir", "", "Specifies the target directory to render the project into. If omitted, a temporary directory is used.") - _ = cmd.MarkFlagDirname("render-output-dir") - } +type RenderOutputDirFlags struct { + RenderOutputDir string `group:"misc" help:"Specifies the target directory to render the project into. If omitted, a temporary directory is used." type:"path"` } diff --git a/cmd/kluctl/args/project.go b/cmd/kluctl/args/project.go index f7eaa631a..2dc50b9ae 100644 --- a/cmd/kluctl/args/project.go +++ b/cmd/kluctl/args/project.go @@ -1,51 +1,22 @@ package args -import ( - "github.com/spf13/cobra" -) - -var ( - ProjectUrl string - ProjectRef string - ProjectConfig string - LocalClusters string - LocalDeployment string - LocalSealedSecrets string - FromArchive string - FromArchiveMetadata string - Cluster string - - Args []string - - Target string -) - -func AddProjectArgs(cmd *cobra.Command, withDeploymentArgs bool, withArgs bool, withTarget bool) { - if withDeploymentArgs { - cmd.Flags().StringVarP(&ProjectUrl, "project-url", "p", "", "Git url of the kluctl project. If not specified, the current directory will be used instead of a remote Git project") - cmd.Flags().StringVarP(&ProjectRef, "project-ref", "b", "", "Git ref of the kluctl project. Only used when --project-url was given.") - - cmd.Flags().StringVarP(&ProjectConfig, "project-config", "c", "", "Location of the .kluctl.yml config file. Defaults to $PROJECT/.kluctl.yml") - cmd.Flags().StringVar(&LocalClusters, "local-clusters", "", "Local clusters directory. Overrides the project from .kluctl.yml") - cmd.Flags().StringVar(&LocalDeployment, "local-deployment", "", "Local deployment directory. Overrides the project from .kluctl.yml") - cmd.Flags().StringVar(&LocalSealedSecrets, "local-sealed-secrets", "", "Local sealed-secrets directory. Overrides the project from .kluctl.yml") - cmd.Flags().StringVar(&FromArchive, "from-archive", "", "Load project (.kluctl.yml, cluster, ...) from archive. Given path can either be an archive file or a directory with the extracted contents.") - cmd.Flags().StringVar(&FromArchiveMetadata, "from-archive-metadata", "", "Specify where to load metadata (targets, ...) from. If not specified, metadata is assumed to be part of the archive.") - cmd.Flags().StringVar(&Cluster, "cluster", "", "Specify/Override cluster") - - _ = cmd.MarkFlagFilename("project-config") - _ = cmd.MarkFlagDirname("local-clusters") - _ = cmd.MarkFlagDirname("local-deployment") - _ = cmd.MarkFlagDirname("local-sealed-secrets") - _ = cmd.MarkFlagFilename("from-archive") - _ = cmd.MarkFlagFilename("from-archive-metadata") - } +type ProjectFlags struct { + ProjectUrl string `group:"project" short:"p" help:"Git url of the kluctl project. If not specified, the current directory will be used instead of a remote Git project"` + ProjectRef string `group:"project" short:"b" help:"Git ref of the kluctl project. Only used when --project-url was given."` + + ProjectConfig string `group:"project" short:"c" help:"Location of the .kluctl.yml config file. Defaults to $PROJECT/.kluctl.yml" type:"existingfile"` + LocalClusters string `group:"project" help:"Local clusters directory. Overrides the project from .kluctl.yml" type:"existingdir"` + LocalDeployment string `group:"project" help:"Local deployment directory. Overrides the project from .kluctl.yml" type:"existingdir"` + LocalSealedSecrets string `group:"project" help:"Local sealed-secrets directory. Overrides the project from .kluctl.yml" type:"existingdir"` + FromArchive string `group:"project" help:"Load project (.kluctl.yml, cluster, ...) from archive. Given path can either be an archive file or a directory with the extracted contents." type:"existingfile"` + FromArchiveMetadata string `group:"project" help:"Specify where to load metadata (targets, ...) from. If not specified, metadata is assumed to be part of the archive." type:"existingfile"` + Cluster string `group:"project" help:"Specify/Override cluster"` +} - if withArgs { - cmd.Flags().StringArrayVarP(&Args, "arg", "a", nil, "Template argument in the form name=value") - } +type ArgsFlags struct { + Arg []string `group:"project" short:"a" help:"Template argument in the form name=value"` +} - if withTarget { - cmd.Flags().StringVarP(&Target, "target", "t", "", "Target name to run command for. Target must exist in .kluctl.yml.") - } +type TargetFlags struct { + Target string `group:"project" short:"t" help:"Target name to run command for. Target must exist in .kluctl.yml."` } diff --git a/cmd/kluctl/commands/cmd_check_image_updates.go b/cmd/kluctl/commands/cmd_check_image_updates.go index 91d916fb9..f49847024 100644 --- a/cmd/kluctl/commands/cmd_check_image_updates.go +++ b/cmd/kluctl/commands/cmd_check_image_updates.go @@ -6,8 +6,6 @@ import ( "github.com/codablock/kluctl/pkg/types" "github.com/codablock/kluctl/pkg/utils" log "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - "github.com/spf13/viper" "os" "regexp" "sort" @@ -15,9 +13,22 @@ import ( "sync" ) -func runCmdCheckImageUpdates(cmd *cobra.Command, args_ []string) error { +type checkImageUpdatesCmd struct { + args.ProjectFlags + args.TargetFlags +} + +func (cmd *checkImageUpdatesCmd) Help() string { + return `This is based on a best effort approach and might give many false-positives.` +} + +func (cmd *checkImageUpdatesCmd) Run() error { var renderedImages map[types.ObjectRef][]string - err := withProjectCommandContext(func(ctx *commandCtx) error { + ptArgs := projectTargetCommandArgs{ + projectFlags: cmd.ProjectFlags, + targetFlags: cmd.TargetFlags, + } + err := withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { renderedImages = ctx.deploymentCollection.FindRenderedImages() return nil }) @@ -124,21 +135,3 @@ func runCmdCheckImageUpdates(cmd *cobra.Command, args_ []string) error { _, _ = os.Stdout.WriteString(table.Render([]int{60})) return nil } - -func init() { - var cmd = &cobra.Command{ - Use: "check-image-updates", - Short: "Render deployment and check if any images have new tags available", - Long: `Render deployment and check if any images have new tags available - -This is based on a best effort approach and might give many false-positives.`, - RunE: runCmdCheckImageUpdates, - } - args.AddProjectArgs(cmd, true, true, true) - err := viper.BindPFlags(cmd.Flags()) - if err != nil { - panic(err) - } - - rootCmd.AddCommand(cmd) -} diff --git a/cmd/kluctl/commands/cmd_delete.go b/cmd/kluctl/commands/cmd_delete.go index f9a7d2bab..db2e5565c 100644 --- a/cmd/kluctl/commands/cmd_delete.go +++ b/cmd/kluctl/commands/cmd_delete.go @@ -5,22 +5,47 @@ import ( "github.com/codablock/kluctl/cmd/kluctl/args" "github.com/codablock/kluctl/pkg/k8s" "github.com/codablock/kluctl/pkg/types" - "github.com/spf13/cobra" - "github.com/spf13/viper" "os" ) -func runCmdDelete(cmd *cobra.Command, args_ []string) error { - return withProjectCommandContext(func(ctx *commandCtx) error { +type deleteCmd struct { + args.ProjectFlags + args.TargetFlags + args.ArgsFlags + args.ImageFlags + args.InclusionFlags + args.YesFlags + args.DryRunFlags + args.OutputFormatFlags +} + +func (cmd *deleteCmd) Help() string { + return `Objects are located based on 'deleteByLabels'', configured in 'deployment.yml' + +WARNING: This command will also delete objects which are not part of your deployment +project (anymore). It really only decides based on the 'deleteByLabel' labels and does NOT +take the local target/state into account!` +} + +func (cmd *deleteCmd) Run() error { + ptArgs := projectTargetCommandArgs{ + projectFlags: cmd.ProjectFlags, + targetFlags: cmd.TargetFlags, + argsFlags: cmd.ArgsFlags, + imageFlags: cmd.ImageFlags, + inclusionFlags: cmd.InclusionFlags, + dryRunArgs: &cmd.DryRunFlags, + } + return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { objects, err := ctx.deploymentCollection.FindDeleteObjects(ctx.k) if err != nil { return err } - result, err := confirmedDeleteObjects(ctx.k, objects) + result, err := confirmedDeleteObjects(ctx.k, objects, cmd.DryRun, cmd.Yes) if err != nil { return err } - err = outputCommandResult(args.Output, result) + err = outputCommandResult(cmd.OutputFormat, result) if err != nil { return err } @@ -31,13 +56,13 @@ func runCmdDelete(cmd *cobra.Command, args_ []string) error { }) } -func confirmedDeleteObjects(k *k8s.K8sCluster, refs []types.ObjectRef) (*types.CommandResult, error) { +func confirmedDeleteObjects(k *k8s.K8sCluster, refs []types.ObjectRef, dryRun bool, forceYes bool) (*types.CommandResult, error) { if len(refs) != 0 { _, _ = os.Stderr.WriteString("The following objects will be deleted:\n") for _, ref := range refs { _, _ = os.Stderr.WriteString(fmt.Sprintf(" %s\n", ref.String())) } - if !args.ForceYes && !args.DryRun { + if !forceYes && !dryRun { if !AskForConfirmation(fmt.Sprintf("Do you really want to delete %d objects?", len(refs))) { return nil, fmt.Errorf("aborted") } @@ -46,30 +71,3 @@ func confirmedDeleteObjects(k *k8s.K8sCluster, refs []types.ObjectRef) (*types.C return k8s.DeleteObjects(k, refs, true) } - -func init() { - var cmd = &cobra.Command{ - Use: "delete", - Short: "Delete a target (or parts of it) from the corresponding cluster", - Long: "Delete a target (or parts of it) from the corresponding cluster.\n\n" + - "Objects are located based on `deleteByLabels`, configured in `deployment.yml`\n\n" + - "WARNING: This command will also delete objects which are not part of your deployment " + - "project (anymore). It really only decides based on the `deleteByLabel` labels and does NOT " + - "take the local target/state into account!", - RunE: runCmdDelete, - } - args.AddProjectArgs(cmd, true, true, true) - args.AddImageArgs(cmd) - args.AddInclusionArgs(cmd) - args.AddMiscArguments(cmd, args.EnabledMiscArguments{ - Yes: true, - DryRun: true, - OutputFormat: true, - }) - err := viper.BindPFlags(cmd.Flags()) - if err != nil { - panic(err) - } - - rootCmd.AddCommand(cmd) -} diff --git a/cmd/kluctl/commands/cmd_deploy.go b/cmd/kluctl/commands/cmd_deploy.go index 32e56bf21..fe63b5b26 100644 --- a/cmd/kluctl/commands/cmd_deploy.go +++ b/cmd/kluctl/commands/cmd_deploy.go @@ -3,28 +3,58 @@ package commands import ( "fmt" "github.com/codablock/kluctl/cmd/kluctl/args" - "github.com/spf13/cobra" - "github.com/spf13/viper" ) -func runCmdDeploy(cmd *cobra.Command, args_ []string) error { - return withProjectCommandContext(func(ctx *commandCtx) error { - return runCmdDeploy2(cmd, ctx) +type deployCmd struct { + args.ProjectFlags + args.TargetFlags + args.ArgsFlags + args.ImageFlags + args.InclusionFlags + args.YesFlags + args.DryRunFlags + args.ForceApplyFlags + args.ReplaceOnErrorFlags + args.AbortOnErrorFlags + args.HookFlags + args.OutputFlags + args.RenderOutputDirFlags +} + +func (cmd *deployCmd) Help() string { + return `This command will also output a diff between the initial state and the state after +deployment. The format of this diff is the same as for the 'diff' command. +It will also output a list of prunable objects (without actually deleting them). +` +} + +func (cmd *deployCmd) Run() error { + ptArgs := projectTargetCommandArgs{ + projectFlags: cmd.ProjectFlags, + targetFlags: cmd.TargetFlags, + argsFlags: cmd.ArgsFlags, + imageFlags: cmd.ImageFlags, + inclusionFlags: cmd.InclusionFlags, + dryRunArgs: &cmd.DryRunFlags, + renderOutputDirFlags: cmd.RenderOutputDirFlags, + } + return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { + return cmd.runCmdDeploy(ctx) }) } -func runCmdDeploy2(cmd *cobra.Command, ctx *commandCtx) error { - if !args.ForceYes && !args.DryRun { +func (cmd *deployCmd) runCmdDeploy(ctx *commandCtx) error { + if !cmd.Yes && !cmd.DryRun { if !AskForConfirmation(fmt.Sprintf("Do you really want to deploy to the context/cluster %s?", ctx.k.Context())) { return fmt.Errorf("aborted") } } - result, err := ctx.deploymentCollection.Deploy(ctx.k, args.ForceApply, args.ReplaceOnError, args.ForceReplaceOnError, args.AbortOnError, args.HookTimeout) + result, err := ctx.deploymentCollection.Deploy(ctx.k, cmd.ForceApply, cmd.ReplaceOnError, cmd.ForceReplaceOnError, cmd.AbortOnError, cmd.HookTimeout) if err != nil { return err } - err = outputCommandResult(args.Output, result) + err = outputCommandResult(cmd.Output, result) if err != nil { return err } @@ -33,34 +63,3 @@ func runCmdDeploy2(cmd *cobra.Command, ctx *commandCtx) error { } return nil } - -func init() { - var cmd = &cobra.Command{ - Use: "deploy", - Short: "Deploys a target to the corresponding cluster", - Long: "Deploys a target to the corresponding cluster.\n\n" + - "This command will also output a diff between the initial state and the state after " + - "deployment. The format of this diff is the same as for the `diff` command. " + - "It will also output a list of prunable objects (without actually deleting them).", - RunE: runCmdDeploy, - } - args.AddProjectArgs(cmd, true, true, true) - args.AddImageArgs(cmd) - args.AddInclusionArgs(cmd) - args.AddMiscArguments(cmd, args.EnabledMiscArguments{ - Yes: true, - DryRun: true, - ForceApply: true, - ReplaceOnError: true, - AbortOnError: true, - HookTimeout: true, - OutputFormat: true, - RenderOutputDir: true, - }) - err := viper.BindPFlags(cmd.Flags()) - if err != nil { - panic(err) - } - - rootCmd.AddCommand(cmd) -} diff --git a/cmd/kluctl/commands/cmd_diff.go b/cmd/kluctl/commands/cmd_diff.go index f7349dbf3..2f79be3b7 100644 --- a/cmd/kluctl/commands/cmd_diff.go +++ b/cmd/kluctl/commands/cmd_diff.go @@ -3,17 +3,43 @@ package commands import ( "fmt" "github.com/codablock/kluctl/cmd/kluctl/args" - "github.com/spf13/cobra" - "github.com/spf13/viper" ) -func runCmdDiff(cmd *cobra.Command, args_ []string) error { - return withProjectCommandContext(func(ctx *commandCtx) error { - result, err := ctx.deploymentCollection.Diff(ctx.k, args.ForceApply, args.ReplaceOnError, args.ForceReplaceOnError, args.IgnoreTags, args.IgnoreLabels, args.IgnoreAnnotations) +type diffCmd struct { + args.ProjectFlags + args.TargetFlags + args.ArgsFlags + args.InclusionFlags + args.ImageFlags + args.ForceApplyFlags + args.ReplaceOnErrorFlags + args.IgnoreFlags + args.OutputFormatFlags + args.RenderOutputDirFlags +} + +func (cmd *diffCmd) Help() string { + return `The output is by default in human readable form (a table combined with unified diffs). +The output can also be changed to output a yaml file. Please note however that the format +is currently not documented and prone to changes. +After the diff is performed, the command will also search for prunable objects and list them.` +} + +func (cmd *diffCmd) Run() error { + ptArgs := projectTargetCommandArgs{ + projectFlags: cmd.ProjectFlags, + targetFlags: cmd.TargetFlags, + argsFlags: cmd.ArgsFlags, + imageFlags: cmd.ImageFlags, + inclusionFlags: cmd.InclusionFlags, + renderOutputDirFlags: cmd.RenderOutputDirFlags, + } + return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { + result, err := ctx.deploymentCollection.Diff(ctx.k, cmd.ForceApply, cmd.ReplaceOnError, cmd.ForceReplaceOnError, cmd.IgnoreTags, cmd.IgnoreLabels, cmd.IgnoreAnnotations) if err != nil { return err } - err = outputCommandResult(args.Output, result) + err = outputCommandResult(cmd.OutputFormat, result) if err != nil { return err } @@ -23,33 +49,3 @@ func runCmdDiff(cmd *cobra.Command, args_ []string) error { return nil }) } - -func init() { - var cmd = &cobra.Command{ - Use: "diff", - Short: "Perform a diff between the locally rendered target and the already deployed target", - Long: `Perform a diff between the locally rendered target and the already deployed target - -The output is by default in human readable form (a table combined with unified diffs). -The output can also be changed to output yaml file. Please note however that the format -is currently not documented and prone to changes. -After the diff is performed, the command will also search for prunable objects and list them.`, - RunE: runCmdDiff, - } - args.AddProjectArgs(cmd, true, true, true) - args.AddImageArgs(cmd) - args.AddInclusionArgs(cmd) - args.AddMiscArguments(cmd, args.EnabledMiscArguments{ - ForceApply: true, - ReplaceOnError: true, - IgnoreLabels: true, - OutputFormat: true, - RenderOutputDir: true, - }) - err := viper.BindPFlags(cmd.Flags()) - if err != nil { - panic(err) - } - - rootCmd.AddCommand(cmd) -} diff --git a/cmd/kluctl/commands/cmd_downscale.go b/cmd/kluctl/commands/cmd_downscale.go index 496638c4c..6149a3883 100644 --- a/cmd/kluctl/commands/cmd_downscale.go +++ b/cmd/kluctl/commands/cmd_downscale.go @@ -3,13 +3,38 @@ package commands import ( "fmt" "github.com/codablock/kluctl/cmd/kluctl/args" - "github.com/spf13/cobra" - "github.com/spf13/viper" ) -func runCmdDownscale(cmd *cobra.Command, args_ []string) error { - return withProjectCommandContext(func(ctx *commandCtx) error { - if !args.ForceYes && !args.DryRun { +type downscaleCmd struct { + args.ProjectFlags + args.TargetFlags + args.ArgsFlags + args.ImageFlags + args.InclusionFlags + args.YesFlags + args.DryRunFlags + args.OutputFormatFlags + args.RenderOutputDirFlags +} + +func (cmd *downscaleCmd) Help() string { + return `This command will downscale all Deployments, StatefulSets and CronJobs. +It is also possible to influence the behaviour with the help of annotations, as described in +the documentation.` +} + +func (cmd *downscaleCmd) Run() error { + ptArgs := projectTargetCommandArgs{ + projectFlags: cmd.ProjectFlags, + targetFlags: cmd.TargetFlags, + argsFlags: cmd.ArgsFlags, + imageFlags: cmd.ImageFlags, + inclusionFlags: cmd.InclusionFlags, + dryRunArgs: &cmd.DryRunFlags, + renderOutputDirFlags: cmd.RenderOutputDirFlags, + } + return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { + if !cmd.Yes && !cmd.DryRun { if !AskForConfirmation(fmt.Sprintf("Do you really want to downscale on context/cluster %s?", ctx.k.Context())) { return fmt.Errorf("aborted") } @@ -18,7 +43,7 @@ func runCmdDownscale(cmd *cobra.Command, args_ []string) error { if err != nil { return err } - err = outputCommandResult(args.Output, result) + err = outputCommandResult(cmd.OutputFormat, result) if err != nil { return err } @@ -28,30 +53,3 @@ func runCmdDownscale(cmd *cobra.Command, args_ []string) error { return nil }) } - -func init() { - var cmd = &cobra.Command{ - Use: "downscale", - Short: "Downscale all deployments", - Long: "Downscale all deployments.\n\n" + - "This command will downscale all Deployments, StatefulSets and CronJobs. " + - "It is also possible to influence the behaviour with the help of annotations, as described in " + - "the documentation.", - RunE: runCmdDownscale, - } - args.AddProjectArgs(cmd, true, true, true) - args.AddImageArgs(cmd) - args.AddInclusionArgs(cmd) - args.AddMiscArguments(cmd, args.EnabledMiscArguments{ - Yes: true, - DryRun: true, - OutputFormat: true, - RenderOutputDir: true, - }) - err := viper.BindPFlags(cmd.Flags()) - if err != nil { - panic(err) - } - - rootCmd.AddCommand(cmd) -} diff --git a/cmd/kluctl/commands/cmd_helm_pull.go b/cmd/kluctl/commands/cmd_helm_pull.go index dd713626d..77f7b2a25 100644 --- a/cmd/kluctl/commands/cmd_helm_pull.go +++ b/cmd/kluctl/commands/cmd_helm_pull.go @@ -1,19 +1,26 @@ package commands import ( - "github.com/codablock/kluctl/cmd/kluctl/args" "github.com/codablock/kluctl/pkg/deployment" log "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - "github.com/spf13/viper" "io/fs" "path/filepath" ) -func runCmdHelmPull(cmd *cobra.Command, args_ []string) error { +type helmPullCmd struct { + LocalDeployment string `help:"Local deployment directory. Defaults to current directory"` +} + +func (cmd *helmPullCmd) Help() string { + return `The Helm charts are stored under the sub-directory 'charts/' next to the +'helm-chart.yml'. These Helm charts are meant to be added to version control so that +pulling is only needed when really required (e.g. when the chart version changes).` +} + +func (cmd *helmPullCmd) Run() error { rootPath := "." - if args.LocalDeployment != "" { - rootPath = args.LocalDeployment + if cmd.LocalDeployment != "" { + rootPath = cmd.LocalDeployment } err := filepath.WalkDir(rootPath, func(p string, d fs.DirEntry, err error) error { fname := filepath.Base(p) @@ -32,22 +39,3 @@ func runCmdHelmPull(cmd *cobra.Command, args_ []string) error { }) return err } - -func init() { - var cmd = &cobra.Command{ - Use: "helm-pull", - Short: "Recursively searches for `helm-chart.yml` files and pulls the specified Helm charts", - Long: "Recursively searches for `helm-chart.yml` files and pulls the specified Helm charts.\n\n" + - "The Helm charts are stored under the sub-directory `charts/` next to the " + - "`helm-chart.yml`. These Helm charts are meant to be added to version control so that " + - "pulling is only needed when really required (e.g. when the chart version changes).", - RunE: runCmdHelmPull, - } - cmd.Flags().StringVar(&args.LocalDeployment, "local-deployment", "", "Local deployment directory. Defaults to current directory") - err := viper.BindPFlags(cmd.Flags()) - if err != nil { - panic(err) - } - - rootCmd.AddCommand(cmd) -} diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go index 407be051a..658943838 100644 --- a/cmd/kluctl/commands/cmd_helm_update.go +++ b/cmd/kluctl/commands/cmd_helm_update.go @@ -2,25 +2,27 @@ package commands import ( "fmt" - "github.com/codablock/kluctl/cmd/kluctl/args" "github.com/codablock/kluctl/pkg/deployment" "github.com/go-git/go-git/v5" log "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - "github.com/spf13/viper" "io/fs" "path/filepath" ) -var ( - upgrade bool - commit bool -) +type helmUpdateCmd struct { + LocalDeployment string `help:"Local deployment directory. Defaults to current directory"` + Upgrade bool `help:"Write new versions into helm-chart.yml and perform helm-pull afterwards"` + Commit bool `help:"Create a git commit for every updated chart"` +} -func runCmdHelmUpdate(cmd *cobra.Command, args_ []string) error { +func (cmd *helmUpdateCmd) Help() string { + return `Optionally performs the actual upgrade and/or add a commit to version control.` +} + +func (cmd *helmUpdateCmd) Run() error { rootPath := "." - if args.LocalDeployment != "" { - rootPath = args.LocalDeployment + if cmd.LocalDeployment != "" { + rootPath = cmd.LocalDeployment } err := filepath.WalkDir(rootPath, func(p string, d fs.DirEntry, err error) error { fname := filepath.Base(p) @@ -38,7 +40,7 @@ func runCmdHelmUpdate(cmd *cobra.Command, args_ []string) error { } log.Infof("Chart %s has new version %s available. Old version is %s.", p, newVersion, *chart.Config.ChartVersion) - if upgrade { + if cmd.Upgrade { if chart.Config.SkipUpdate != nil && !*chart.Config.SkipUpdate { log.Infof("NOT upgrading chart %s as skipUpdate was set to true", p) return nil @@ -84,7 +86,7 @@ func runCmdHelmUpdate(cmd *cobra.Command, args_ []string) error { return err } - if commit { + if cmd.Commit { msg := fmt.Sprintf("Updated helm chart %s from %s to %s", filepath.Dir(p), oldVersion, newVersion) log.Infof("Committing: %s", msg) r, err := git.PlainOpen(rootPath) @@ -112,22 +114,3 @@ func runCmdHelmUpdate(cmd *cobra.Command, args_ []string) error { }) return err } - -func init() { - var cmd = &cobra.Command{ - Use: "helm-update", - Short: "Recursively searches for `helm-chart.yml` files and checks for new available versions", - Long: "Recursively searches for `helm-chart.yml` files and checks for new available versions.\n\n" + - "Optionally performs the actual upgrade and/or add a commit to version control.", - RunE: runCmdHelmUpdate, - } - cmd.Flags().StringVar(&args.LocalDeployment, "local-deployment", "", "Local deployment directory. Defaults to current directory") - cmd.Flags().BoolVar(&upgrade, "upgrade", false, "Write new versions into helm-chart.yml and perform helm-pull afterwards") - cmd.Flags().BoolVar(&commit, "commit", false, "Create a git commit for every updated chart") - err := viper.BindPFlags(cmd.Flags()) - if err != nil { - panic(err) - } - - rootCmd.AddCommand(cmd) -} diff --git a/cmd/kluctl/commands/cmd_list_images.go b/cmd/kluctl/commands/cmd_list_images.go index 7d8540d74..585527ae1 100644 --- a/cmd/kluctl/commands/cmd_list_images.go +++ b/cmd/kluctl/commands/cmd_list_images.go @@ -2,40 +2,36 @@ package commands import ( "github.com/codablock/kluctl/cmd/kluctl/args" - "github.com/spf13/cobra" - "github.com/spf13/viper" ) -var simple = false +type listImagesCmd struct { + args.ProjectFlags + args.TargetFlags + args.ArgsFlags + args.ImageFlags + args.InclusionFlags + args.OutputFlags -func runCmdListImages(cmd *cobra.Command, args_ []string) error { - return withProjectCommandContext(func(ctx *commandCtx) error { - result := ctx.images.SeenImages(simple) - return outputYamlResult(args.Output, result, false) - }) + Simple bool `help:"Output a simplified version of the images list"` +} + +func (cmd *listImagesCmd) Help() string { + return `The result is a compatible with yaml files expected by --fixed-images-file. + +If fixed images ('-f/--fixed-image') are provided, these are also taken into account, +as described in for the deploy command.` } -func init() { - var cmd = &cobra.Command{ - Use: "list-images", - Short: "Renders the target and outputs all images used via `images.get_image(...)", - Long: "Renders the target and outputs all images used via `images.get_image(...)`\n\n" + - "The result is a compatible with yaml files expected by --fixed-images-file.\n\n" + - "If fixed images (`-f/--fixed-image`) are provided, these are also taken into account, " + - "as described in for the deploy command.", - RunE: runCmdListImages, +func (cmd *listImagesCmd) Run() error { + ptArgs := projectTargetCommandArgs{ + projectFlags: cmd.ProjectFlags, + targetFlags: cmd.TargetFlags, + argsFlags: cmd.ArgsFlags, + imageFlags: cmd.ImageFlags, + inclusionFlags: cmd.InclusionFlags, } - args.AddProjectArgs(cmd, true, true, true) - args.AddImageArgs(cmd) - args.AddInclusionArgs(cmd) - args.AddMiscArguments(cmd, args.EnabledMiscArguments{ - Output: true, + return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { + result := ctx.images.SeenImages(cmd.Simple) + return outputYamlResult(cmd.Output, result, false) }) - cmd.Flags().BoolVar(&simple, "simple", false, "Output a simplified version of the images list") - err := viper.BindPFlags(cmd.Flags()) - if err != nil { - panic(err) - } - - rootCmd.AddCommand(cmd) } diff --git a/cmd/kluctl/commands/cmd_list_targets.go b/cmd/kluctl/commands/cmd_list_targets.go index d1ea881f2..3920f91c6 100644 --- a/cmd/kluctl/commands/cmd_list_targets.go +++ b/cmd/kluctl/commands/cmd_list_targets.go @@ -4,35 +4,23 @@ import ( "github.com/codablock/kluctl/cmd/kluctl/args" "github.com/codablock/kluctl/pkg/kluctl_project" "github.com/codablock/kluctl/pkg/types" - "github.com/spf13/cobra" - "github.com/spf13/viper" ) -func runCmdListTargets(cmd *cobra.Command, args_ []string) error { - return withKluctlProjectFromArgs(func(p *kluctl_project.KluctlProjectContext) error { +type listTargetsCmd struct { + args.ProjectFlags + args.OutputFlags +} + +func (cmd *listTargetsCmd) Help() string { + return `Outputs a yaml list with all target, including dynamic targets` +} + +func (cmd *listTargetsCmd) Run() error { + return withKluctlProjectFromArgs(cmd.ProjectFlags, func(p *kluctl_project.KluctlProjectContext) error { var result []*types.Target for _, t := range p.DynamicTargets { result = append(result, t.Target) } - return outputYamlResult(args.Output, result, false) + return outputYamlResult(cmd.Output, result, false) }) } - -func init() { - var cmd = &cobra.Command{ - Use: "list-targets", - Short: "Outputs a yaml list with all target, including dynamic targets", - Long: "Outputs a yaml list with all target, including dynamic targets", - RunE: runCmdListTargets, - } - args.AddProjectArgs(cmd, true, true, false) - args.AddMiscArguments(cmd, args.EnabledMiscArguments{ - Output: true, - }) - err := viper.BindPFlags(cmd.Flags()) - if err != nil { - panic(err) - } - - rootCmd.AddCommand(cmd) -} diff --git a/cmd/kluctl/commands/cmd_prune.go b/cmd/kluctl/commands/cmd_prune.go index 22530c610..913dcf995 100644 --- a/cmd/kluctl/commands/cmd_prune.go +++ b/cmd/kluctl/commands/cmd_prune.go @@ -3,26 +3,51 @@ package commands import ( "fmt" "github.com/codablock/kluctl/cmd/kluctl/args" - "github.com/spf13/cobra" - "github.com/spf13/viper" ) -func runCmdPrune(cmd *cobra.Command, args_ []string) error { - return withProjectCommandContext(func(ctx *commandCtx) error { - return runCmdPrune2(cmd, ctx) +type pruneCmd struct { + args.ProjectFlags + args.TargetFlags + args.ArgsFlags + args.ImageFlags + args.InclusionFlags + args.YesFlags + args.DryRunFlags + args.OutputFormatFlags +} + +func (cmd *pruneCmd) Help() string { + return `"Searching works by: + + 1. Search the cluster for all objects match 'deleteByLabels', as configured in 'deployment.yml'' + 2. Render the local target and list all objects. + 3. Remove all objects from the list of 1. that are part of the list in 2.` +} + +func (cmd *pruneCmd) Run() error { + ptArgs := projectTargetCommandArgs{ + projectFlags: cmd.ProjectFlags, + targetFlags: cmd.TargetFlags, + argsFlags: cmd.ArgsFlags, + imageFlags: cmd.ImageFlags, + inclusionFlags: cmd.InclusionFlags, + dryRunArgs: &cmd.DryRunFlags, + } + return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { + return cmd.runCmdPrune(ctx) }) } -func runCmdPrune2(cmd *cobra.Command, ctx *commandCtx) error { +func (cmd *pruneCmd) runCmdPrune(ctx *commandCtx) error { objects, err := ctx.deploymentCollection.FindOrphanObjects(ctx.k) if err != nil { return err } - result, err := confirmedDeleteObjects(ctx.k, objects) + result, err := confirmedDeleteObjects(ctx.k, objects, cmd.DryRun, cmd.Yes) if err != nil { return err } - err = outputCommandResult(args.Output, result) + err = outputCommandResult(cmd.OutputFormat, result) if err != nil { return err } @@ -31,31 +56,3 @@ func runCmdPrune2(cmd *cobra.Command, ctx *commandCtx) error { } return nil } - -func init() { - var cmd = &cobra.Command{ - Use: "prune", - Short: "Searches the target cluster for prunable objects and deletes them", - Long: "Searches the target cluster for prunable objects and deletes them.\n\n" + - "Searching works by:\n\n" + - "\b\n" + - " 1. Search the cluster for all objects match `deleteByLabels`, as configured in `deployment.yml`\n" + - " 2. Render the local target and list all objects.\n" + - " 3. Remove all objects from the list of 1. that are part of the list in 2.\n", - RunE: runCmdPrune, - } - args.AddProjectArgs(cmd, true, true, true) - args.AddImageArgs(cmd) - args.AddInclusionArgs(cmd) - args.AddMiscArguments(cmd, args.EnabledMiscArguments{ - Yes: true, - DryRun: true, - OutputFormat: true, - }) - err := viper.BindPFlags(cmd.Flags()) - if err != nil { - panic(err) - } - - rootCmd.AddCommand(cmd) -} diff --git a/cmd/kluctl/commands/cmd_render.go b/cmd/kluctl/commands/cmd_render.go index c9caf2c80..9fe846f1a 100644 --- a/cmd/kluctl/commands/cmd_render.go +++ b/cmd/kluctl/commands/cmd_render.go @@ -4,44 +4,40 @@ import ( "github.com/codablock/kluctl/cmd/kluctl/args" "github.com/codablock/kluctl/pkg/utils" log "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - "github.com/spf13/viper" "io/ioutil" ) -func runCmdRender(cmd *cobra.Command, args_ []string) error { - if args.RenderOutputDir == "" { +type renderCmd struct { + args.ProjectFlags + args.TargetFlags + args.ArgsFlags + args.ImageFlags + args.RenderOutputDirFlags +} + +func (cmd *renderCmd) Help() string { + return `Renders all resources and configuration files and stores the result in either +a temporary directory or a specified directory.` +} + +func (cmd *renderCmd) Run() error { + if cmd.RenderOutputDir == "" { p, err := ioutil.TempDir(utils.GetTmpBaseDir(), "rendered-") if err != nil { return err } - args.RenderOutputDir = p + cmd.RenderOutputDir = p } - return withProjectCommandContext(func(ctx *commandCtx) error { + ptArgs := projectTargetCommandArgs{ + projectFlags: cmd.ProjectFlags, + targetFlags: cmd.TargetFlags, + argsFlags: cmd.ArgsFlags, + imageFlags: cmd.ImageFlags, + renderOutputDirFlags: cmd.RenderOutputDirFlags, + } + return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { log.Infof("Rendered into %s", ctx.deploymentCollection.RenderDir) return nil }) } - -func init() { - var cmd = &cobra.Command{ - Use: "render", - Short: "Renders all resources and configuration files", - Long: "Renders all resources and configuration files and stores the result in either " + - "a temporary directory or a specified directory.", - RunE: runCmdRender, - } - args.AddProjectArgs(cmd, true, true, true) - args.AddImageArgs(cmd) - args.AddMiscArguments(cmd, args.EnabledMiscArguments{ - RenderOutputDir: true, - }) - - err := viper.BindPFlags(cmd.Flags()) - if err != nil { - panic(err) - } - - rootCmd.AddCommand(cmd) -} diff --git a/cmd/kluctl/commands/cmd_seal.go b/cmd/kluctl/commands/cmd_seal.go index be3754a57..03bb589a7 100644 --- a/cmd/kluctl/commands/cmd_seal.go +++ b/cmd/kluctl/commands/cmd_seal.go @@ -8,14 +8,24 @@ import ( "github.com/codablock/kluctl/pkg/types" "github.com/codablock/kluctl/pkg/utils/uo" log "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - "github.com/spf13/viper" ) -var ( - SecretsDir string - ForceReseal bool -) +type sealCmd struct { + args.ProjectFlags + args.TargetFlags + + SecretsDir string `help:"Specifies where to find unencrypted secret files. The given directory is NOT meant to be part of your source repository! The given path only matters for secrets of type 'path'. Defaults to the current working directory."` + ForceReseal bool `help:"Lets kluctl ignore secret hashes found in already sealed secrets and thus forces resealing of those."` +} + +func (cmd *sealCmd) Help() string { + return `Loads all secrets from the specified secrets sets from the target's sealingConfig and +then renders the target, including all files with the '.sealme' extension. Then runs +kubeseal on each '.sealme' file and stores secrets in the directory specified by +'--local-sealed-secrets', using the outputPattern from your deployment project. + +If no '--target' is specified, sealing is performed for all targets.` +} func findSecretsEntry(ctx *commandCtx, name string) (*types.SecretSet, error) { for _, e := range ctx.kluctlProject.Config.SecretsConfig.SecretSets { @@ -50,11 +60,16 @@ func loadSecrets(ctx *commandCtx, target *types.Target, secretsLoader *seal.Secr return nil } -func runCmdSealForTarget(p *kluctl_project.KluctlProjectContext, target *types.Target, secretsLoader *seal.SecretsLoader) error { +func (cmd *sealCmd) runCmdSealForTarget(p *kluctl_project.KluctlProjectContext, target *types.Target, secretsLoader *seal.SecretsLoader) error { log.Infof("Sealing for target %s", target.Name) + ptArgs := projectTargetCommandArgs{ + projectFlags: cmd.ProjectFlags, + targetFlags: cmd.TargetFlags, + } + // pass forSeal=True so that .sealme files are rendered as well - return withProjectTargetCommandContext(p, target, true, func(ctx *commandCtx) error { + return withProjectTargetCommandContext(ptArgs, p, target, true, func(ctx *commandCtx) error { err := loadSecrets(ctx, target, secretsLoader) if err != nil { return err @@ -85,7 +100,7 @@ func runCmdSealForTarget(p *kluctl_project.KluctlProjectContext, target *types.T if err != nil { return err } - sealer, err := seal.NewSealer(ctx.k, sealedSecretsNamespace, sealedSecretsControllerName, clusterConfig.Cluster, ForceReseal) + sealer, err := seal.NewSealer(ctx.k, sealedSecretsNamespace, sealedSecretsControllerName, clusterConfig.Cluster, cmd.ForceReseal) if err != nil { return err } @@ -98,19 +113,19 @@ func runCmdSealForTarget(p *kluctl_project.KluctlProjectContext, target *types.T }) } -func runCmdSeal(cmd *cobra.Command, args_ []string) error { - return withKluctlProjectFromArgs(func(p *kluctl_project.KluctlProjectContext) error { +func (cmd *sealCmd) Run() error { + return withKluctlProjectFromArgs(cmd.ProjectFlags, func(p *kluctl_project.KluctlProjectContext) error { hadError := false - secretsLoader := seal.NewSecretsLoader(p, SecretsDir) + secretsLoader := seal.NewSecretsLoader(p, cmd.SecretsDir) baseTargets := make(map[string]bool) noTargetMatch := true for _, target := range p.DynamicTargets { - if args.Target != "" && args.Target != target.Target.Name { + if cmd.Target != "" && cmd.Target != target.Target.Name { continue } - if args.Cluster != "" && args.Cluster != target.Target.Cluster { + if cmd.Cluster != "" && cmd.Cluster != target.Target.Cluster { continue } if target.Target.SealingConfig == nil { @@ -135,7 +150,7 @@ func runCmdSeal(cmd *cobra.Command, args_ []string) error { sealTarget = baseTarget } - err := runCmdSealForTarget(p, sealTarget, secretsLoader) + err := cmd.runCmdSealForTarget(p, sealTarget, secretsLoader) if err != nil { log.Warningf("Sealing for target %s failed: %v", sealTarget.Name, err) hadError = true @@ -150,30 +165,3 @@ func runCmdSeal(cmd *cobra.Command, args_ []string) error { return nil }) } - -func init() { - cmd := &cobra.Command{ - Use: "seal", - Short: "Seal secrets based on target's sealingConfig", - Long: "Loads all secrets from the specified secrets sets from the target's sealingConfig and " + - "then renders the target, including all files with the `.sealme` extension. Then runs " + - "kubeseal on each `.sealme` file and stores secrets in the directory specified by " + - "`--local-sealed-secrets`, using the outputPattern from your deployment project.\n\n" + - "If no `--target` is specified, sealing is performed for all targets.", - RunE: runCmdSeal, - } - - args.AddProjectArgs(cmd, true, true, true) - - cmd.Flags().StringVar(&SecretsDir, "secrets-dir", ".", "Specifies where to find unencrypted secret files. The given directory is NOT meant to be part "+ - "of your source repository! The given path only matters for secrets of type 'path'. Defaults "+ - "to the current working directory.") - cmd.Flags().BoolVar(&ForceReseal, "force-reseal", false, "Lets kluctl ignore secret hashes found in already sealed secrets and thus forces resealing of those.") - - err := viper.BindPFlags(cmd.Flags()) - if err != nil { - panic(err) - } - - rootCmd.AddCommand(cmd) -} diff --git a/cmd/kluctl/commands/cmd_validate.go b/cmd/kluctl/commands/cmd_validate.go index dc94b9f69..8cb64d787 100644 --- a/cmd/kluctl/commands/cmd_validate.go +++ b/cmd/kluctl/commands/cmd_validate.go @@ -3,20 +3,44 @@ package commands import ( "fmt" "github.com/codablock/kluctl/cmd/kluctl/args" - "github.com/spf13/cobra" - "github.com/spf13/viper" "os" "time" ) -func runCmdValidate(cmd *cobra.Command, args_ []string) error { - return withProjectCommandContext(func(ctx *commandCtx) error { +type validateCmd struct { + args.ProjectFlags + args.TargetFlags + args.ArgsFlags + args.InclusionFlags + args.OutputFlags + args.RenderOutputDirFlags + + Wait time.Duration `help:"Wait for the given amount of time until the deployment validates"` + Sleep time.Duration `help:"Sleep duration between validation attempts" default:"5s"` + WarningsAsErrors bool `help:"Consider warnings as failures"` +} + +func (cmd *validateCmd) Help() string { + return `This means that all objects are retrieved from the cluster and checked for readiness. + +TODO: This needs to be better documented!` +} + +func (cmd *validateCmd) Run() error { + ptArgs := projectTargetCommandArgs{ + projectFlags: cmd.ProjectFlags, + targetFlags: cmd.TargetFlags, + argsFlags: cmd.ArgsFlags, + inclusionFlags: cmd.InclusionFlags, + renderOutputDirFlags: cmd.RenderOutputDirFlags, + } + return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { startTime := time.Now() for true { result := ctx.deploymentCollection.Validate(ctx.k) - failed := len(result.Errors) != 0 || (args.WarningsAsErrors && len(result.Warnings) != 0) + failed := len(result.Errors) != 0 || (cmd.WarningsAsErrors && len(result.Warnings) != 0) - err := outputValidateResult(args.Output, result) + err := outputValidateResult(cmd.Output, result) if err != nil { return err } @@ -26,11 +50,11 @@ func runCmdValidate(cmd *cobra.Command, args_ []string) error { return nil } - if args.Wait <= 0 || time.Now().Sub(startTime) > args.Wait { + if cmd.Wait <= 0 || time.Now().Sub(startTime) > cmd.Wait { return fmt.Errorf("Validation failed") } - time.Sleep(args.Sleep) + time.Sleep(cmd.Sleep) // Need to force re-requesting these objects for _, e := range result.Results { @@ -46,29 +70,3 @@ func runCmdValidate(cmd *cobra.Command, args_ []string) error { return nil }) } - -func init() { - var cmd = &cobra.Command{ - Use: "validate", - Short: "Validates the already deployed deployment", - Long: "Validates the already deployed deployment.\n\n" + - "This means that all objects are retrieved from the cluster and checked for readiness.\n\n" + - "TODO: This needs to be better documented!", - RunE: runCmdValidate, - } - args.AddProjectArgs(cmd, true, true, true) - args.AddInclusionArgs(cmd) - args.AddMiscArguments(cmd, args.EnabledMiscArguments{ - Output: true, - RenderOutputDir: true, - }) - cmd.Flags().DurationVar(&args.Wait, "wait", 0, "Wait for the given amount of time until the deployment validates") - cmd.Flags().DurationVar(&args.Sleep, "sleep", time.Second*5, "Sleep duration between validation attempts") - cmd.Flags().BoolVar(&args.WarningsAsErrors, "warnings-as-errors", false, "Consider warnings as failures") - err := viper.BindPFlags(cmd.Flags()) - if err != nil { - panic(err) - } - - rootCmd.AddCommand(cmd) -} diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index 2393da08c..4e9e23b6c 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -16,6 +16,7 @@ limitations under the License. package commands import ( + "github.com/alecthomas/kong" "github.com/codablock/kluctl/pkg/utils" "github.com/codablock/kluctl/pkg/utils/uo" "github.com/codablock/kluctl/pkg/version" @@ -26,19 +27,40 @@ import ( "path/filepath" "strings" "time" - - "github.com/spf13/cobra" -) - -var ( - v string - noUpdateCheck = false ) const latestReleaseUrl = "https://api.github.com/repos/codablock/kluctl/releases/latest" -func setupLogs() error { - lvl, err := log.ParseLevel(v) +type cli struct { + Verbosity string `group:"global" short:"v" help:"Log level (debug, info, warn, error, fatal, panic)." default:"info"` + NoUpdateCheck bool `group:"global" help:"Disable update check on startup"` + + CheckImageUpdates checkImageUpdatesCmd `cmd:"" help:"Render deployment and check if any images have new tags available"` + Delete deleteCmd `cmd:"" help:"Delete a target (or parts of it) from the corresponding cluster"` + Deploy deployCmd `cmd:"" help:"Deploys a target to the corresponding cluster"` + Diff diffCmd `cmd:"" help:"Perform a diff between the locally rendered target and the already deployed target"` + Downscale downscaleCmd `cmd:"" help:"Downscale all deployments"` + HelmPull helmPullCmd `cmd:"" help:"Recursively searches for 'helm-chart.yml' files and pulls the specified Helm charts"` + HelmUpdate helmUpdateCmd `cmd:"" help:"Recursively searches for 'helm-chart.yml'' files and checks for new available versions"` + ListImages listImagesCmd `cmd:"" help:"Renders the target and outputs all images used via 'images.get_image(...)"` + ListTargets listTargetsCmd `cmd:"" help:"Outputs a yaml list with all target, including dynamic targets"` + Prune pruneCmd `cmd:"" help:"Searches the target cluster for prunable objects and deletes them"` + Render renderCmd `cmd:"" help:"Renders all resources and configuration files"` + Seal sealCmd `cmd:"" help:"Seal secrets based on target's sealingConfig"` + Validate validateCmd `cmd:"" help:"Validates the already deployed deployment"` +} + +var flagGroups = []kong.Group{ + {Key: "project", Title: "Project arguments:", Description: ""}, + {Key: "images", Title: "Image arguments:", Description: ""}, + {Key: "inclusion", Title: "Inclusion/Exclusion arguments:", Description: ""}, + {Key: "misc", Title: "Misc arguments:", Description: ""}, + {Key: "global", Title: "Global arguments:", Description: ""}, +} +var globalFlagGroup = &flagGroups[len(flagGroups)-1] + +func (c *cli) setupLogs() error { + lvl, err := log.ParseLevel(c.Verbosity) if err != nil { return err } @@ -50,8 +72,8 @@ type VersionCheckState struct { LastVersionCheck time.Time `yaml:"lastVersionCheck"` } -func checkNewVersion() { - if noUpdateCheck { +func (c *cli) checkNewVersion() { + if c.NoUpdateCheck { return } if version.Version == "0.0.0" { @@ -98,40 +120,58 @@ func checkNewVersion() { } } -// rootCmd represents the base command when called without any subcommands -var rootCmd = &cobra.Command{ - Use: "kluctl", - Short: "Deploy and manage complex deployments on Kubernetes", - Long: `The missing glue to put together large Kubernetes deployments, -composed of multiple smaller parts (Helm/Kustomize/...) in a manageable and unified way.`, - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - if err := setupLogs(); err != nil { - return err - } - checkNewVersion() - return nil - }, - // Uncomment the following line if your bare application - // has an action associated with it: - // Run: func(cmd *cobra.Command, args []string) { }, +func (c *cli) BeforeApply() error { + if err := c.setupLogs(); err != nil { + return err + } + c.checkNewVersion() + return nil } -func RootCmd() *cobra.Command { - return rootCmd +func (c *cli) Help() string { + return ` +Deploy and manage complex deployments on Kubernetes + +The missing glue to put together large Kubernetes deployments, +composed of multiple smaller parts (Helm/Kustomize/...) in a manageable and unified way.` } -// Execute adds all child commands to the root command and sets flags appropriately. -// This is called by main.main(). It only needs to happen once to the rootCmd. -func Execute() { - err := rootCmd.Execute() +func ParseArgs(args []string, options ...kong.Option) (*kong.Context, error) { + var cli cli + + helpOption := kong.HelpOptions{ + Compact: true, + Summary: true, + WrapUpperBound: 120, + } + + var options2 []kong.Option + options2 = append(options2, helpOption) + options2 = append(options2, kong.ExplicitGroups(flagGroups)) + options2 = append(options2, options...) + + parser, err := kong.New(&cli, options2...) if err != nil { - os.Exit(1) + panic(err) } + parser.Model.HelpFlag.Group = globalFlagGroup + ctx, err := parser.Parse(args) + return ctx, err } -func init() { - rootCmd.SilenceUsage = true +func Execute() { + confOption := kong.Configuration(kong.JSON, "/etc/kluctl.json", "~/.kluctl/config.json") + ctx, err := ExecuteWithArgs(os.Args[1:], confOption) + if err != nil && ctx == nil { + log.Fatal(err) + } + ctx.FatalIfErrorf(err) +} - rootCmd.PersistentFlags().StringVarP(&v, "verbosity", "v", log.InfoLevel.String(), "Log level (debug, info, warn, error, fatal, panic") - rootCmd.PersistentFlags().BoolVar(&noUpdateCheck, "no-update-check", false, "Disable update check on startup") +func ExecuteWithArgs(args []string, options ...kong.Option) (*kong.Context, error) { + ctx, err := ParseArgs(args, options...) + if err != nil { + return nil, err + } + return ctx, ctx.Run() } diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index c80cfb7ca..355745329 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -2,7 +2,7 @@ package commands import ( "fmt" - args "github.com/codablock/kluctl/cmd/kluctl/args" + "github.com/codablock/kluctl/cmd/kluctl/args" "github.com/codablock/kluctl/pkg/deployment" git_url "github.com/codablock/kluctl/pkg/git/git-url" "github.com/codablock/kluctl/pkg/jinja2" @@ -15,11 +15,11 @@ import ( "path/filepath" ) -func withKluctlProjectFromArgs(cb func(p *kluctl_project.KluctlProjectContext) error) error { +func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, cb func(p *kluctl_project.KluctlProjectContext) error) error { var url *git_url.GitUrl - if args.ProjectUrl != "" { + if projectFlags.ProjectUrl != "" { var err error - url, err = git_url.Parse(args.ProjectUrl) + url, err = git_url.Parse(projectFlags.ProjectUrl) if err != nil { return err } @@ -32,18 +32,28 @@ func withKluctlProjectFromArgs(cb func(p *kluctl_project.KluctlProjectContext) e loadArgs := kluctl_project.LoadKluctlProjectArgs{ ProjectUrl: url, - ProjectRef: args.ProjectRef, - ProjectConfig: args.ProjectConfig, - LocalClusters: args.LocalClusters, - LocalDeployment: args.LocalDeployment, - LocalSealedSecrets: args.LocalSealedSecrets, - FromArchive: args.FromArchive, - FromArchiveMetadata: args.FromArchiveMetadata, + ProjectRef: projectFlags.ProjectRef, + ProjectConfig: projectFlags.ProjectConfig, + LocalClusters: projectFlags.LocalClusters, + LocalDeployment: projectFlags.LocalDeployment, + LocalSealedSecrets: projectFlags.LocalSealedSecrets, + FromArchive: projectFlags.FromArchive, + FromArchiveMetadata: projectFlags.FromArchiveMetadata, J2: j2, } return kluctl_project.LoadKluctlProject(loadArgs, cb) } +type projectTargetCommandArgs struct { + projectFlags args.ProjectFlags + targetFlags args.TargetFlags + argsFlags args.ArgsFlags + imageFlags args.ImageFlags + inclusionFlags args.InclusionFlags + dryRunArgs *args.DryRunFlags + renderOutputDirFlags args.RenderOutputDirFlags +} + type commandCtx struct { kluctlProject *kluctl_project.KluctlProjectContext target *types.Target @@ -54,27 +64,27 @@ type commandCtx struct { deploymentCollection *deployment.DeploymentCollection } -func withProjectCommandContext(cb func(ctx *commandCtx) error) error { - return withKluctlProjectFromArgs(func(p *kluctl_project.KluctlProjectContext) error { +func withProjectCommandContext(args projectTargetCommandArgs, cb func(ctx *commandCtx) error) error { + return withKluctlProjectFromArgs(args.projectFlags, func(p *kluctl_project.KluctlProjectContext) error { var target *types.Target - if args.Target != "" { - t, err := p.FindDynamicTarget(args.Target) + if args.targetFlags.Target != "" { + t, err := p.FindDynamicTarget(args.targetFlags.Target) if err != nil { return err } target = t.Target } - return withProjectTargetCommandContext(p, target, false, cb) + return withProjectTargetCommandContext(args, p, target, false, cb) }) } -func withProjectTargetCommandContext(p *kluctl_project.KluctlProjectContext, target *types.Target, forSeal bool, cb func(ctx *commandCtx) error) error { +func withProjectTargetCommandContext(args projectTargetCommandArgs, p *kluctl_project.KluctlProjectContext, target *types.Target, forSeal bool, cb func(ctx *commandCtx) error) error { deploymentDir, err := filepath.Abs(p.DeploymentDir) if err != nil { return err } - clusterName := args.Cluster + clusterName := args.projectFlags.Cluster if clusterName == "" { if target == nil { return fmt.Errorf("you must specify an existing --cluster when not providing a --target") @@ -87,7 +97,7 @@ func withProjectTargetCommandContext(p *kluctl_project.KluctlProjectContext, tar return err } - k, err := k8s.NewK8sCluster(clusterConfig.Cluster.Context, args.DryRun) + k, err := k8s.NewK8sCluster(clusterConfig.Cluster.Context, args.dryRunArgs == nil || args.dryRunArgs.DryRun) if err != nil { return err } @@ -98,19 +108,19 @@ func withProjectTargetCommandContext(p *kluctl_project.KluctlProjectContext, tar return err } - images, err := deployment.NewImages(args.UpdateImages) + images, err := deployment.NewImages(args.imageFlags.UpdateImages) if err != nil { return err } - inclusion, err := args.ParseInclusionFromArgs() + inclusion, err := args.inclusionFlags.ParseInclusionFromArgs() if err != nil { return err } allArgs := uo.New() - optionArgs, err := deployment.ParseArgs(args.Args) + optionArgs, err := deployment.ParseArgs(args.argsFlags.Arg) if err != nil { return err } @@ -141,7 +151,7 @@ func withProjectTargetCommandContext(p *kluctl_project.KluctlProjectContext, tar varsCtx.UpdateChild("args", allArgs) - renderOutputDir := args.RenderOutputDir + renderOutputDir := args.renderOutputDirFlags.RenderOutputDir if renderOutputDir == "" { tmpDir, err := ioutil.TempDir(p.TmpDir, "rendered") if err != nil { @@ -160,7 +170,7 @@ func withProjectTargetCommandContext(p *kluctl_project.KluctlProjectContext, tar return err } - fixedImages, err := args.LoadFixedImagesFromArgs() + fixedImages, err := args.imageFlags.LoadFixedImagesFromArgs() if err != nil { return err } diff --git a/e2e/cmd_wrapper_test.go b/e2e/cmd_wrapper_test.go index b4a3e10e4..07be7bb3d 100644 --- a/e2e/cmd_wrapper_test.go +++ b/e2e/cmd_wrapper_test.go @@ -42,9 +42,7 @@ func doRunCmd() error { _, _ = os.Stderr.WriteString(stdoutEndMarker + "\n") }() - cmd := commands.RootCmd() - cmd.SetArgs(cmdArgs) - err = cmd.Execute() + _, err = commands.ExecuteWithArgs(cmdArgs) return err } diff --git a/go.mod b/go.mod index e100147a2..74f96b1aa 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/codablock/kluctl go 1.17 require ( + github.com/alecthomas/kong v0.4.1 github.com/aws/aws-sdk-go v1.27.0 github.com/bitnami-labs/sealed-secrets v0.17.3 github.com/evanphx/json-patch v4.12.0+incompatible @@ -21,8 +22,6 @@ require ( github.com/pkg/errors v0.9.1 github.com/r3labs/diff/v2 v2.15.0 github.com/sirupsen/logrus v1.8.1 - github.com/spf13/cobra v1.3.0 - github.com/spf13/viper v1.10.1 github.com/whilp/git-urls v1.0.0 golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b @@ -58,7 +57,6 @@ require ( github.com/emirpasic/gods v1.12.0 // indirect github.com/evanphx/json-patch/v5 v5.2.0 // indirect github.com/fatih/color v1.13.0 // indirect - github.com/fsnotify/fsnotify v1.5.1 // indirect github.com/gammazero/deque v0.1.0 // indirect github.com/go-errors/errors v1.0.1 // indirect github.com/go-git/gcfg v1.5.0 // indirect @@ -78,7 +76,6 @@ require ( github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/googleapis/gnostic v0.5.5 // indirect github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect @@ -88,12 +85,10 @@ require ( github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect github.com/klauspost/compress v1.13.6 // indirect github.com/leodido/go-urn v1.2.1 // indirect - github.com/magiconair/properties v1.8.5 // indirect github.com/mailru/easyjson v0.7.6 // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect - github.com/mitchellh/mapstructure v1.4.3 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect @@ -103,12 +98,9 @@ require ( github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/sergi/go-diff v1.1.0 // indirect - github.com/spf13/afero v1.6.0 // indirect - github.com/spf13/cast v1.4.1 // indirect - github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/cobra v1.3.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/testify v1.7.0 // indirect - github.com/subosito/gotenv v1.2.0 // indirect github.com/vbatts/tar-split v0.11.2 // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/xanzy/ssh-agent v0.3.0 // indirect @@ -125,7 +117,6 @@ require ( google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.27.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/ini.v1 v1.66.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/klog/v2 v2.30.0 // indirect diff --git a/go.sum b/go.sum index 829453e8c..abed9d001 100644 --- a/go.sum +++ b/go.sum @@ -115,6 +115,10 @@ github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/ github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/alecthomas/kong v0.4.1 h1:0sFnMts+ijOiFuSHsMB9MlDi3NGINBkx9KIw1/gcuDw= +github.com/alecthomas/kong v0.4.1/go.mod h1:uzxf/HUh0tj43x1AyJROl3JT7SgsZ5m+icOv1csRhc0= +github.com/alecthomas/repr v0.0.0-20210801044451-80ca428c5142 h1:8Uy0oSf5co/NZXje7U1z8Mpep++QJOldL2hs/sBQf48= +github.com/alecthomas/repr v0.0.0-20210801044451-80ca428c5142/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -591,7 +595,6 @@ github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFb github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= -github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= @@ -622,7 +625,6 @@ github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= @@ -713,7 +715,6 @@ github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc8 github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -761,7 +762,6 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= github.com/mkmik/multierror v0.3.0/go.mod h1:wjBYXRpDhh+8mIp+iLBOq0kZ3Y4ICTncojwvP8LUYLQ= @@ -942,7 +942,6 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= -github.com/sagikazarmark/crypt v0.4.0/go.mod h1:ALv2SRj7GxYV4HO9elxH9nS6M9gW+xDNxqmyJ6RfDFM= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= @@ -968,11 +967,9 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= -github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= @@ -982,7 +979,6 @@ github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t6 github.com/spf13/cobra v1.3.0 h1:R7cSvGu+Vv+qX0gW5R/85dx2kmmJT5z5NM8ifdYjdn0= github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v0.0.0-20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -995,8 +991,6 @@ github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/y github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= -github.com/spf13/viper v1.10.1 h1:nuJZuYpG7gTj/XqiUwg8bA0cp1+M2mC3J4g5luUYBKk= -github.com/spf13/viper v1.10.1/go.mod h1:IGlFPqhNAPKRxohIzWpI5QEy4kuI7tcl5WvR+8qy1rU= github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= @@ -1016,7 +1010,6 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= @@ -1364,7 +1357,6 @@ golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= @@ -1504,7 +1496,6 @@ google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdr google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU= google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw= -google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1651,7 +1642,6 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI= gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= From 69ef933f72208fd87d75264c11a6f80e18ef875b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 28 Feb 2022 14:25:55 +0100 Subject: [PATCH 0399/2916] Implement GetLatestImageFromRegistry --- .../commands/cmd_check_image_updates.go | 5 +- cmd/kluctl/commands/root.go | 5 +- pkg/deployment/helm_chart.go | 5 +- pkg/deployment/images.go | 32 +- pkg/types/target_config.go | 21 +- pkg/utils/utils.go | 6 + pkg/utils/versions/latest_version.go | 144 +++++++++ pkg/utils/versions/latest_version_parse.go | 292 ++++++++++++++++++ pkg/utils/{ => versions}/looseversion.go | 17 +- pkg/utils/{ => versions}/looseversion_test.go | 2 +- 10 files changed, 503 insertions(+), 26 deletions(-) create mode 100644 pkg/utils/versions/latest_version.go create mode 100644 pkg/utils/versions/latest_version_parse.go rename pkg/utils/{ => versions}/looseversion.go (90%) rename pkg/utils/{ => versions}/looseversion_test.go (99%) diff --git a/cmd/kluctl/commands/cmd_check_image_updates.go b/cmd/kluctl/commands/cmd_check_image_updates.go index f49847024..67a1e1e64 100644 --- a/cmd/kluctl/commands/cmd_check_image_updates.go +++ b/cmd/kluctl/commands/cmd_check_image_updates.go @@ -5,6 +5,7 @@ import ( "github.com/codablock/kluctl/pkg/registries" "github.com/codablock/kluctl/pkg/types" "github.com/codablock/kluctl/pkg/utils" + "github.com/codablock/kluctl/pkg/utils/versions" log "github.com/sirupsen/logrus" "os" "regexp" @@ -109,14 +110,14 @@ func (cmd *checkImageUpdatesCmd) Run() error { } filteredTags = append(filteredTags, tag) } - doKey := func(tag string) utils.LooseVersion { + doKey := func(tag string) versions.LooseVersion { if prefix != "" { tag = tag[len(prefix):] } if suffix != "" { tag = tag[:len(tag)-len(suffix)] } - return utils.LooseVersion(tag) + return versions.LooseVersion(tag) } sort.SliceStable(filteredTags, func(i, j int) bool { a := doKey(filteredTags[i]) diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index 4e9e23b6c..76bbd01c1 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -19,6 +19,7 @@ import ( "github.com/alecthomas/kong" "github.com/codablock/kluctl/pkg/utils" "github.com/codablock/kluctl/pkg/utils/uo" + "github.com/codablock/kluctl/pkg/utils/versions" "github.com/codablock/kluctl/pkg/version" "github.com/codablock/kluctl/pkg/yaml" log "github.com/sirupsen/logrus" @@ -113,8 +114,8 @@ func (c *cli) checkNewVersion() { if strings.HasPrefix(latestVersionStr, "v") { latestVersionStr = latestVersionStr[1:] } - latestVersion := utils.LooseVersion(latestVersionStr) - localVersion := utils.LooseVersion(version.Version) + latestVersion := versions.LooseVersion(latestVersionStr) + localVersion := versions.LooseVersion(version.Version) if localVersion.Less(latestVersion, true) { log.Warningf("You are using an outdated version (%v) of kluctl. You should update soon to version %v", localVersion, latestVersion) } diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index f14816395..30819ac72 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -5,6 +5,7 @@ import ( "github.com/codablock/kluctl/pkg/k8s" "github.com/codablock/kluctl/pkg/types" "github.com/codablock/kluctl/pkg/utils" + "github.com/codablock/kluctl/pkg/utils/versions" "github.com/codablock/kluctl/pkg/yaml" "io/ioutil" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -123,7 +124,7 @@ func (c *helmChart) CheckUpdate() (string, bool, error) { } // ensure we didn't get partial matches var lm []map[string]string - var ls utils.LooseVersionSlice + var ls versions.LooseVersionSlice err = yaml.ReadYamlBytes(stdout, &lm) if err != nil { return err @@ -131,7 +132,7 @@ func (c *helmChart) CheckUpdate() (string, bool, error) { for _, x := range lm { if n, ok := x["name"]; ok && n == chartName { if v, ok := x["version"]; ok { - ls = append(ls, utils.LooseVersion(v)) + ls = append(ls, versions.LooseVersion(v)) } } } diff --git a/pkg/deployment/images.go b/pkg/deployment/images.go index 16c307d15..201c4d805 100644 --- a/pkg/deployment/images.go +++ b/pkg/deployment/images.go @@ -5,8 +5,10 @@ import ( "encoding/base64" "fmt" "github.com/codablock/kluctl/pkg/k8s" + "github.com/codablock/kluctl/pkg/registries" "github.com/codablock/kluctl/pkg/types" "github.com/codablock/kluctl/pkg/utils/uo" + "github.com/codablock/kluctl/pkg/utils/versions" "github.com/codablock/kluctl/pkg/yaml" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -67,7 +69,24 @@ func (images *Images) GetFixedImage(image string, namespace string, deployment s } func (images *Images) GetLatestImageFromRegistry(image string, latestVersion string) (*string, error) { - return nil, nil + tags, err := registries.ListImageTags(image) + if err != nil { + return nil, err + } + if len(tags) == 0 { + return nil, nil + } + + lv, err := versions.ParseLatestVersion(latestVersion) + if err != nil { + return nil, err + } + + tags = versions.Filter(lv, tags) + + latest := lv.Latest(tags) + result := fmt.Sprintf("%s:%s", image, latest) + return &result, nil } const beginPlaceholder = "XXXXXbegin_get_image_" @@ -119,7 +138,7 @@ func (images *Images) extractContainerName(parent interface{}) string { } func (images *Images) ResolvePlaceholders(k *k8s.K8sCluster, o *unstructured.Unstructured, deploymentDir string, tags []string) error { - namespace := o.GetNamespace() + ref := types.RefFromObject(o) deployment := fmt.Sprintf("%s/%s", o.GetKind(), o.GetName()) var remoteObject *unstructured.Unstructured @@ -164,7 +183,7 @@ func (images *Images) ResolvePlaceholders(k *k8s.K8sCluster, o *unstructured.Uns } } - resultImage, err := images.resolveImage(ph, namespace, deployment, container, deployed, deploymentDir, tags) + resultImage, err := images.resolveImage(ph, ref, deployment, container, deployed, deploymentDir, tags) if err != nil { return err } @@ -186,8 +205,8 @@ func (images *Images) ResolvePlaceholders(k *k8s.K8sCluster, o *unstructured.Uns return nil } -func (images *Images) resolveImage(ph *placeHolder, namespace string, deployment string, container string, deployed *string, deploymentDir string, tags []string) (*string, error) { - fixed := images.GetFixedImage(ph.Image, namespace, deployment, container) +func (images *Images) resolveImage(ph *placeHolder, ref types.ObjectRef, deployment string, container string, deployed *string, deploymentDir string, tags []string) (*string, error) { + fixed := images.GetFixedImage(ph.Image, ref.Namespace, deployment, container) registry, err := images.GetLatestImageFromRegistry(ph.Image, ph.LatestVersion) if err != nil { @@ -208,7 +227,8 @@ func (images *Images) resolveImage(ph *placeHolder, namespace string, deployment ResultImage: *result, DeployedImage: deployed, RegistryImage: registry, - Namespace: &namespace, + Namespace: &ref.Namespace, + Object: &ref, Deployment: &deployment, Container: &container, VersionFilter: &ph.LatestVersion, diff --git a/pkg/types/target_config.go b/pkg/types/target_config.go index 94e265cfc..7f8dee841 100644 --- a/pkg/types/target_config.go +++ b/pkg/types/target_config.go @@ -3,16 +3,17 @@ package types import "github.com/codablock/kluctl/pkg/utils/uo" type FixedImage struct { - Image string `yaml:"image" validate:"required"` - ResultImage string `yaml:"resultImage" validate:"required"` - DeployedImage *string `yaml:"deployedImage,omitempty"` - RegistryImage *string `yaml:"registryImage,omitempty"` - Namespace *string `yaml:"namespace,omitempty"` - Deployment *string `yaml:"deployment,omitempty"` - Container *string `yaml:"container,omitempty"` - VersionFilter *string `yaml:"versionFilter,omitempty"` - DeployTags []string `yaml:"deployTags,omitempty"` - DeploymentDir *string `yaml:"deploymentDir,omitempty"` + Image string `yaml:"image" validate:"required"` + ResultImage string `yaml:"resultImage" validate:"required"` + DeployedImage *string `yaml:"deployedImage,omitempty"` + RegistryImage *string `yaml:"registryImage,omitempty"` + Namespace *string `yaml:"namespace,omitempty"` + Object *ObjectRef `yaml:"object,omitempty"` + Deployment *string `yaml:"deployment,omitempty"` + Container *string `yaml:"container,omitempty"` + VersionFilter *string `yaml:"versionFilter,omitempty"` + DeployTags []string `yaml:"deployTags,omitempty"` + DeploymentDir *string `yaml:"deploymentDir,omitempty"` } type FixedImagesConfig struct { diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 5f303199b..0ab99d994 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -34,6 +34,12 @@ func DeepCopy(dst interface{}, src interface{}) error { }) } +func CloneStringSlice(s []string) []string { + n := make([]string, len(s), len(s)) + copy(n, s) + return n +} + func FindStrInSlice(a []string, s string) int { for i, v := range a { if v == s { diff --git a/pkg/utils/versions/latest_version.go b/pkg/utils/versions/latest_version.go new file mode 100644 index 000000000..12146a1c2 --- /dev/null +++ b/pkg/utils/versions/latest_version.go @@ -0,0 +1,144 @@ +package versions + +import ( + "fmt" + "github.com/codablock/kluctl/pkg/utils" + "regexp" + "strconv" +) + +type LatestVersionFilter interface { + Match(version string) bool + Latest(versions []string) string + String() string +} + +func Filter(lv LatestVersionFilter, versions []string) []string { + var filtered []string + for _, v := range versions { + if lv.Match(v) { + filtered = append(filtered, v) + } + } + return filtered +} + +type regexVersionFilter struct { + patternStr string + pattern *regexp.Regexp +} + +func NewRegexVersionFilter(pattern string) (LatestVersionFilter, error) { + p, err := regexp.Compile(fmt.Sprintf("^%s$", pattern)) + if err != nil { + return nil, err + } + return ®exVersionFilter{ + patternStr: pattern, + pattern: p, + }, nil +} + +func (f *regexVersionFilter) Match(version string) bool{ + return f.pattern.MatchString(version) +} + +func (f *regexVersionFilter) Latest(versions []string) string { + c := SortLooseVersionStrings(versions) + return string(c[len(c)-1]) +} + +func (f *regexVersionFilter) String() string { + return fmt.Sprintf(`regex(pattern="%s")`, f.patternStr) +} + +type looseSemVerVersionFilter struct { + allowNoNums bool +} + +func NewLooseSemVerVersionFilter(allowNoNums bool) LatestVersionFilter { + return &looseSemVerVersionFilter{allowNoNums: allowNoNums} +} + +func (f *looseSemVerVersionFilter) Match(version string) bool { + groups := looseSemverRegex.FindStringSubmatch(version) + if groups == nil { + return false + } + if !f.allowNoNums && groups[1] == "" { + return false + } + return true +} + +func (f *looseSemVerVersionFilter) Latest(versions []string) string { + c := SortLooseVersionStrings(versions) + return string(c[len(c)-1]) +} + +func (f *looseSemVerVersionFilter) String() string { + return fmt.Sprintf(`semver(allow_no_nums=%s)`, strconv.FormatBool(f.allowNoNums)) +} + +type prefixVersionFilter struct { + prefix string + suffix LatestVersionFilter + pattern *regexp.Regexp +} + +func NewPrefixVersionFilter(prefix string, suffix LatestVersionFilter) (LatestVersionFilter, error) { + if suffix == nil { + suffix = NewLooseSemVerVersionFilter(false) + } + + p, err := regexp.Compile(fmt.Sprintf(`^%s(.*)$`, prefix)) + if err != nil { + return nil, err + } + return &prefixVersionFilter{ + prefix: prefix, + suffix: suffix, + pattern: p, + }, nil +} + +func (f *prefixVersionFilter) Match(version string) bool { + groups := f.pattern.FindStringSubmatch(version) + if groups == nil { + return false + } + return f.suffix.Match(groups[1]) +} + +func (f *prefixVersionFilter) Latest(versions []string) string { + var filteredVersions []string + var suffixVersions []string + for _, v := range versions { + groups := f.pattern.FindStringSubmatch(v) + if groups == nil { + continue + } + filteredVersions = append(filteredVersions, v) + suffixVersions = append(suffixVersions, groups[1]) + } + latest := f.suffix.Latest(suffixVersions) + i := utils.FindStrInSlice(suffixVersions, latest) + return filteredVersions[i] +} + +func (f *prefixVersionFilter) String() string { + return fmt.Sprintf(`prefix(prefix="%s", suffix=%s)`, f.prefix, f.suffix.String()) +} + +type numberVersionFilter struct { + LatestVersionFilter +} + +func NewNumberVersionFilter() (LatestVersionFilter, error) { + f, _ := NewRegexVersionFilter("[0-9]+") + return &numberVersionFilter{f}, nil +} + +func (f *numberVersionFilter) String() string { + return "number()" +} diff --git a/pkg/utils/versions/latest_version_parse.go b/pkg/utils/versions/latest_version_parse.go new file mode 100644 index 000000000..39d190c4b --- /dev/null +++ b/pkg/utils/versions/latest_version_parse.go @@ -0,0 +1,292 @@ +package versions + +import ( + "fmt" + log "github.com/sirupsen/logrus" + "strconv" + "strings" + "text/scanner" +) + +type tokenAndText struct { + tok rune + text string +} + +type preparsed struct { + tokens []tokenAndText + nextPos int +} + +func (p *preparsed) CurToken() rune { + if p.nextPos > len(p.tokens) { + return scanner.EOF + } + return p.tokens[p.nextPos-1].tok +} + +func (p *preparsed) CurTokenText() string { + if p.nextPos > len(p.tokens) { + log.Panicf("CurTokenText at EOF") + } + return p.tokens[p.nextPos-1].text +} + +func (p *preparsed) Next() rune { + if p.nextPos > len(p.tokens) { + return scanner.EOF + } + p.nextPos += 1 + return p.CurToken() +} + +func (p *preparsed) Peek() rune { + return p.PeekN(0) +} + +func (p *preparsed) PeekN(n int) rune { + if p.nextPos + n >= len(p.tokens) { + return scanner.EOF + } + return p.tokens[p.nextPos + n].tok +} + +func ParseLatestVersion(str string) (LatestVersionFilter, error) { + var p preparsed + var s scanner.Scanner + s.Init(strings.NewReader(str)) + s.Mode |= scanner.ScanIdents | scanner.ScanStrings + + for true { + tok := s.Scan() + if tok == scanner.EOF { + break + } + p.tokens = append(p.tokens, tokenAndText{ + tok: tok, + text: s.TokenText(), + }) + } + + return parseFilter(&p) +} + +func parseFilter(p *preparsed) (LatestVersionFilter, error) { + tok := p.Next() + + if tok != scanner.Ident { + return nil, fmt.Errorf("unexpected token %v, expected ident", p.CurToken()) + } + name := p.CurTokenText() + + tok = p.Next() + if tok != '(' {return nil, fmt.Errorf("unexpected token %v, expected (", tok)} + + var f LatestVersionFilter + + var err error + switch name { + case "regex": + f, err = parseRegexFilter(p) + case "semver": + f, err = parseSemVerFilter(p) + case "prefix": + f, err = parsePrefixFilter(p) + case "number": + f, err = parseNumberFilter(p) + } + if err != nil { + return nil, err + } + + tok = p.Next() + if tok != ')' {return nil, fmt.Errorf("unexpected token %v, expected (", tok)} + + return f, nil +} + +func parseRegexFilter(p *preparsed) (LatestVersionFilter, error) { + args := []*arg{ + {name: "pattern", tok: scanner.String, required: true}, + } + err := parseArgs(p, args) + if err != nil { + return nil, err + } + + if args[0].value == "" { + return nil, fmt.Errorf("pattern can't be empty") + } + + return NewRegexVersionFilter(args[0].value.(string)) +} + +func parseSemVerFilter(p *preparsed) (LatestVersionFilter, error) { + args := []*arg{ + {name: "allow_no_nums", tok: scanner.Ident, isBool: true}, + } + err := parseArgs(p, args) + if err != nil { + return nil, err + } + + return NewLooseSemVerVersionFilter(args[0].value.(bool)), nil +} + +func parsePrefixFilter(p *preparsed) (LatestVersionFilter, error) { + args := []*arg{ + {name: "prefix", tok: scanner.String, required: true}, + {name: "suffix", tok: scanner.Ident}, + } + err := parseArgs(p, args) + if err != nil { + return nil, err + } + + var suffix LatestVersionFilter + if args[1].found { + x, ok := args[1].value.(LatestVersionFilter) + if !ok { + return nil, fmt.Errorf("invalid suffix, must be a filter") + } + suffix = x + } + + return NewPrefixVersionFilter(args[0].value.(string), suffix) +} + +func parseNumberFilter(p *preparsed) (LatestVersionFilter, error) { + return NewNumberVersionFilter() +} + +type arg struct { + name string + tok rune + value interface{} + required bool + isBool bool + + found bool +} + +func parseArgs(p *preparsed, args []*arg) error { + var parsedArgs []arg + parsedKvArgs := make(map[string]arg) + + for true { + var name string + + if p.Peek() == ')' { + break + } + if len(parsedArgs) + len(parsedKvArgs) != 0 { + if tok := p.Next(); tok != ',' { + return fmt.Errorf("unexpected token %v, expected ')' or ','", tok) + } + } + + a, err := parseArg(p) + if err != nil { + return err + } + + if a.name == "" { + if len(parsedKvArgs) != 0 { + return fmt.Errorf("can't have unnamed args after named args") + } + parsedArgs = append(parsedArgs, *a) + } else { + if _, ok := parsedKvArgs[name]; ok { + return fmt.Errorf("duplicate named arg %s", name) + } + parsedKvArgs[a.name] = *a + } + } + + if len(parsedArgs) > len(args) { + return fmt.Errorf("too many arguments") + } + for i, a := range parsedArgs { + if a.tok != args[i].tok { + return fmt.Errorf("unexpected argument type for %s, expected %v, got %v", a.name, args[i].tok, a.tok) + } + args[i].value = a.value + args[i].found = true + } + + for k, v := range parsedKvArgs { + foundArg := false + for _, a := range args { + if k == a.name { + foundArg = true + if a.found { + return fmt.Errorf("named arg %s has already been provided via unnamed args", k) + } + a.value = v.value + a.found = true + } + } + if !foundArg { + return fmt.Errorf("unkown arg %s", k) + } + } + + for _, a := range args { + if a.required && !a.found { + return fmt.Errorf("required arg %s not found", a.name) + } + if a.isBool { + if a.tok != scanner.Ident || (a.value != "true" && a.value != "false") { + return fmt.Errorf("invalid value for arg %s, must be a bool", a.name) + } + a.value, _ = strconv.ParseBool(a.value.(string)) + } + } + + return nil +} + +func parseArg(p *preparsed) (*arg, error) { + parseValue := func() (rune, interface{}, error) { + if p.Peek() == scanner.Ident && p.PeekN(1) == '(' { + f, err := parseFilter(p) + return scanner.Ident, f, err + } + tok := p.Next() + if tok != scanner.String && tok != scanner.Ident && tok != scanner.Int { + return 'e', nil, fmt.Errorf("unexpected token %v, expected string, ident or int") + } + value := p.CurTokenText() + if tok == scanner.String { + value = value[1:len(value)-1] + } + return tok, value, nil + } + + if p.Peek() == scanner.Ident && p.PeekN(1) == '=' { + p.Next() // consume ident + name := p.CurTokenText() + p.Next() // consume '=' + + valueTok, value, err := parseValue() + if err != nil { + return nil, err + } + a := arg{ + name: name, + tok: valueTok, + value: value, + } + return &a, nil + } + + valueTok, value, err := parseValue() + if err != nil { + return nil, err + } + a := arg{ + tok: valueTok, + value: value, + } + return &a, nil +} diff --git a/pkg/utils/looseversion.go b/pkg/utils/versions/looseversion.go similarity index 90% rename from pkg/utils/looseversion.go rename to pkg/utils/versions/looseversion.go index 830010820..79b36c3ea 100644 --- a/pkg/utils/looseversion.go +++ b/pkg/utils/versions/looseversion.go @@ -1,7 +1,9 @@ -package utils +package versions import ( + "github.com/codablock/kluctl/pkg/utils" "regexp" + "sort" "strconv" "strings" ) @@ -91,7 +93,7 @@ func (lv LooseVersion) Less(b LooseVersion, preferLongSuffix bool) bool { bNums, bSuffixStr := b.SplitVersion() cmp := func(a []int, b []int) bool { - l := IntMin(len(a), len(b)) + l := utils.IntMin(len(a), len(b)) for i := 0; i < l; i++ { if a[i] < b[i] { return true @@ -120,7 +122,7 @@ func (lv LooseVersion) Less(b LooseVersion, preferLongSuffix bool) bool { aSuffix := splitSuffix(aSuffixStr) bSuffix := splitSuffix(bSuffixStr) - l := IntMin(len(aSuffix), len(bSuffix)) + l := utils.IntMin(len(aSuffix), len(bSuffix)) for i := 0; i < l; i++ { if aSuffix[i].Less(bSuffix[i]) { @@ -158,3 +160,12 @@ func (x LooseVersionSlice) Less(i, j int) bool { } func (x LooseVersionSlice) Len() int { return len(x) } func (x LooseVersionSlice) Swap(i, j int) { x[i], x[j] = x[j], x[i] } + +func SortLooseVersionStrings(versions []string) LooseVersionSlice { + var c LooseVersionSlice + for _, v := range versions { + c = append(c, LooseVersion(v)) + } + sort.Stable(c) + return c +} diff --git a/pkg/utils/looseversion_test.go b/pkg/utils/versions/looseversion_test.go similarity index 99% rename from pkg/utils/looseversion_test.go rename to pkg/utils/versions/looseversion_test.go index abeab5e4e..5d2bab86f 100644 --- a/pkg/utils/looseversion_test.go +++ b/pkg/utils/versions/looseversion_test.go @@ -1,4 +1,4 @@ -package utils +package versions import ( "testing" From fe6d027d15e3ac2e81bac918ffd91e11de31dbe1 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 28 Feb 2022 14:26:17 +0100 Subject: [PATCH 0400/2916] Implement poke-images command --- cmd/kluctl/commands/cmd_poke_images.go | 56 +++++++++++++++++ cmd/kluctl/commands/root.go | 5 +- pkg/deployment/deployment_collection.go | 80 ++++++++++++++++++++++++- 3 files changed, 138 insertions(+), 3 deletions(-) create mode 100644 cmd/kluctl/commands/cmd_poke_images.go diff --git a/cmd/kluctl/commands/cmd_poke_images.go b/cmd/kluctl/commands/cmd_poke_images.go new file mode 100644 index 000000000..1d79bdcde --- /dev/null +++ b/cmd/kluctl/commands/cmd_poke_images.go @@ -0,0 +1,56 @@ +package commands + +import ( + "fmt" + "github.com/codablock/kluctl/cmd/kluctl/args" +) + +type pokeImagesCmd struct { + args.ProjectFlags + args.TargetFlags + args.ArgsFlags + args.ImageFlags + args.InclusionFlags + args.YesFlags + args.DryRunFlags + args.OutputFlags + args.RenderOutputDirFlags +} + +func (cmd *pokeImagesCmd) Help() string { + return `This command will fully render the target and then only replace images instead of fully +deploying the target. Only images used in combination with 'images.get_image(...)' are +replaced` +} + +func (cmd *pokeImagesCmd) Run() error { + ptArgs := projectTargetCommandArgs{ + projectFlags: cmd.ProjectFlags, + targetFlags: cmd.TargetFlags, + argsFlags: cmd.ArgsFlags, + imageFlags: cmd.ImageFlags, + inclusionFlags: cmd.InclusionFlags, + dryRunArgs: &cmd.DryRunFlags, + renderOutputDirFlags: cmd.RenderOutputDirFlags, + } + return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { + if !cmd.Yes && !cmd.DryRun { + if !AskForConfirmation(fmt.Sprintf("Do you really want to poke images to the context/cluster %s?", ctx.k.Context())) { + return fmt.Errorf("aborted") + } + } + + result, err := ctx.deploymentCollection.PokeImages(ctx.k) + if err != nil { + return err + } + err = outputCommandResult(cmd.Output, result) + if err != nil { + return err + } + if len(result.Errors) != 0 { + return fmt.Errorf("command failed") + } + return nil + }) +} diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index 76bbd01c1..7823d2c4c 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -45,6 +45,7 @@ type cli struct { HelmUpdate helmUpdateCmd `cmd:"" help:"Recursively searches for 'helm-chart.yml'' files and checks for new available versions"` ListImages listImagesCmd `cmd:"" help:"Renders the target and outputs all images used via 'images.get_image(...)"` ListTargets listTargetsCmd `cmd:"" help:"Outputs a yaml list with all target, including dynamic targets"` + PokeImages pokeImagesCmd `cmd:"" help:"Replace all images in target"` Prune pruneCmd `cmd:"" help:"Searches the target cluster for prunable objects and deletes them"` Render renderCmd `cmd:"" help:"Renders all resources and configuration files"` Seal sealCmd `cmd:"" help:"Seal secrets based on target's sealingConfig"` @@ -141,8 +142,8 @@ func ParseArgs(args []string, options ...kong.Option) (*kong.Context, error) { var cli cli helpOption := kong.HelpOptions{ - Compact: true, - Summary: true, + Compact: true, + Summary: true, WrapUpperBound: 120, } diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index 2f8543183..4b343d921 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -366,6 +366,83 @@ func (c *DeploymentCollection) Diff(k *k8s.K8sCluster, forceApply bool, replaceO }, nil } +func (c *DeploymentCollection) PokeImages(k *k8s.K8sCluster) (*types.CommandResult, error) { + allObjects := make(map[types.ObjectRef]*unstructured.Unstructured) + for _, d := range c.deployments { + if !d.checkInclusionForDeploy() { + continue + } + for _, o := range d.objects { + allObjects[types.RefFromObject(o)] = o + } + } + + containersAndImages := make(map[types.ObjectRef][]types.FixedImage) + for _, fi := range c.images.seenImages { + _, ok := allObjects[*fi.Object] + if !ok { + c.addError(*fi.Object, fmt.Errorf("object not found while trying to associate image with deployed object")) + continue + } + + containersAndImages[*fi.Object] = append(containersAndImages[*fi.Object], fi) + } + + wp := utils.NewWorkerPoolWithErrors(8) + defer wp.StopWait(false) + + doPokeImage := func(images []types.FixedImage, o *unstructured.Unstructured) (*unstructured.Unstructured, error) { + u := uo.FromUnstructured(o) + containers, _, _ := u.GetNestedObjectList("spec", "template", "spec", "containers") + + for _, image := range images { + for _, c := range containers { + containerName, _, _ := c.GetNestedString("name") + if image.Container != nil && containerName == *image.Container { + c.SetNestedField(image.ResultImage, "image") + } + } + } + return u.ToUnstructured(), nil + } + + appliedObjects := make(map[types.ObjectRef]*unstructured.Unstructured) + var mutex sync.Mutex + + for ref, containers := range containersAndImages { + ref := ref + containers := containers + wp.Submit(func() error { + newObject, err := c.doReplaceObject(k, ref, func(o *unstructured.Unstructured) (*unstructured.Unstructured, error) { + return doPokeImage(containers, o) + }) + if err != nil { + c.addError(ref, err) + } else { + mutex.Lock() + defer mutex.Unlock() + appliedObjects[ref] = newObject + } + return nil + }) + } + err := wp.StopWait(false) + if err != nil { + return nil, err + } + + newObjects, changedObjects, err := c.doDiff(k, appliedObjects, false, false, false) + if err != nil { + return nil, err + } + return &types.CommandResult{ + NewObjects: newObjects, + ChangedObjects: changedObjects, + Errors: c.errorsList(), + Warnings: c.warningsList(), + }, nil +} + func (c *DeploymentCollection) Downscale(k *k8s.K8sCluster) (*types.CommandResult, error) { wp := utils.NewWorkerPoolWithErrors(8) defer wp.StopWait(false) @@ -578,7 +655,8 @@ func (c *DeploymentCollection) doReplaceObject(k *k8s.K8sCluster, ref types.Obje } firstCall = false - modified, err := callback(remote) + remoteCopy := uo.CopyUnstructured(remote) + modified, err := callback(remoteCopy) if err != nil { return nil, err } From c7c15626a16947b50a7b7c5d013845fcbc15ed58 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 28 Feb 2022 14:26:36 +0100 Subject: [PATCH 0401/2916] Refactor and fix crash in doDiff --- pkg/deployment/deployment_collection.go | 83 ++++++++++++------------- 1 file changed, 40 insertions(+), 43 deletions(-) diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index 4b343d921..a196560b1 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -570,9 +570,12 @@ func (c *DeploymentCollection) doApply(k *k8s.K8sCluster, o applyUtilOptions) (m } func (c *DeploymentCollection) doDiff(k *k8s.K8sCluster, appliedObjects map[types.ObjectRef]*unstructured.Unstructured, ignoreTags bool, ignoreLabels bool, ignoreAnnotations bool) ([]*unstructured.Unstructured, []*types.ChangedObject, error) { - diffObjects := make(map[types.ObjectRef]*unstructured.Unstructured) - normalizedDiffObjects := make(map[types.ObjectRef]*unstructured.Unstructured) - normalizedRemoteObjects := make(map[types.ObjectRef]*unstructured.Unstructured) + var newObjects []*unstructured.Unstructured + var changedObjects []*types.ChangedObject + var mutex sync.Mutex + + wp := utils.NewDebuggerAwareWorkerPool(8) + defer wp.StopWait(false) for _, d := range c.deployments { if !d.checkInclusionForDeploy() { @@ -581,51 +584,45 @@ func (c *DeploymentCollection) doDiff(k *k8s.K8sCluster, appliedObjects map[type ignoreForDiffs := d.project.getIgnoreForDiffs(ignoreTags, ignoreLabels, ignoreAnnotations) for _, o := range d.objects { + o := o ref := types.RefFromObject(o) - if _, ok := appliedObjects[ref]; !ok { + ao, ok := appliedObjects[ref] + if !ok { + // if we can't even find it in appliedObjects, it probably ran into an error continue } - diffObjects[ref] = appliedObjects[ref] - normalizedDiffObjects[ref] = diff.NormalizeObject(k, diffObjects[ref], ignoreForDiffs, diffObjects[ref]) - if r, ok := c.remoteObjects[ref]; ok { - normalizedRemoteObjects[ref] = diff.NormalizeObject(k, r, ignoreForDiffs, diffObjects[ref]) - } - } - } + ro, _ := c.remoteObjects[ref] - wp := utils.NewDebuggerAwareWorkerPool(8) - defer wp.StopWait(false) - - var newObjects []*unstructured.Unstructured - var changedObjects []*types.ChangedObject - var mutex sync.Mutex - - for ref, o_ := range diffObjects { - o := o_ - normalizedRemoteObject, ok := normalizedRemoteObjects[ref] - if !ok { - newObjects = append(newObjects, o) - continue - } - normalizedDiffObject, _ := normalizedDiffObjects[ref] - remoteObject, _ := c.remoteObjects[ref] - wp.Submit(func() error { - changes, err := diff.Diff(normalizedRemoteObject, normalizedDiffObject) - if err != nil { - return err - } - if len(changes) == 0 { - return nil + if ao != nil && ro == nil { + newObjects = append(newObjects, ao) + } else if ao == nil && ro != nil { + // deleted? + continue + } else if ao == nil && ro == nil { + // did not apply? (e.g. in downscale command) + continue + } else { + wp.Submit(func() error { + nao := diff.NormalizeObject(k, ao, ignoreForDiffs, o) + nro := diff.NormalizeObject(k, ro, ignoreForDiffs, o) + changes, err := diff.Diff(nro, nao) + if err != nil { + return err + } + if len(changes) == 0 { + return nil + } + mutex.Lock() + defer mutex.Unlock() + changedObjects = append(changedObjects, &types.ChangedObject{ + NewObject: ao, + OldObject: ro, + Changes: changes, + }) + return nil + }) } - mutex.Lock() - defer mutex.Unlock() - changedObjects = append(changedObjects, &types.ChangedObject{ - NewObject: o, - OldObject: remoteObject, - Changes: changes, - }) - return nil - }) + } } err := wp.StopWait(false) From 88099792f0bf878d4796c438c20ee6db4b59e5c8 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 28 Feb 2022 14:32:11 +0100 Subject: [PATCH 0402/2916] Add missing flag groups --- cmd/kluctl/commands/cmd_helm_pull.go | 2 +- cmd/kluctl/commands/cmd_helm_update.go | 6 +++--- cmd/kluctl/commands/cmd_list_images.go | 2 +- cmd/kluctl/commands/cmd_seal.go | 4 ++-- cmd/kluctl/commands/cmd_validate.go | 6 +++--- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/cmd/kluctl/commands/cmd_helm_pull.go b/cmd/kluctl/commands/cmd_helm_pull.go index 77f7b2a25..4f4ff5e4b 100644 --- a/cmd/kluctl/commands/cmd_helm_pull.go +++ b/cmd/kluctl/commands/cmd_helm_pull.go @@ -8,7 +8,7 @@ import ( ) type helmPullCmd struct { - LocalDeployment string `help:"Local deployment directory. Defaults to current directory"` + LocalDeployment string `group:"project" help:"Local deployment directory. Defaults to current directory"` } func (cmd *helmPullCmd) Help() string { diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go index 658943838..ddf8b91df 100644 --- a/cmd/kluctl/commands/cmd_helm_update.go +++ b/cmd/kluctl/commands/cmd_helm_update.go @@ -10,9 +10,9 @@ import ( ) type helmUpdateCmd struct { - LocalDeployment string `help:"Local deployment directory. Defaults to current directory"` - Upgrade bool `help:"Write new versions into helm-chart.yml and perform helm-pull afterwards"` - Commit bool `help:"Create a git commit for every updated chart"` + LocalDeployment string `group:"project" help:"Local deployment directory. Defaults to current directory"` + Upgrade bool `group:"misc" help:"Write new versions into helm-chart.yml and perform helm-pull afterwards"` + Commit bool `group:"misc" help:"Create a git commit for every updated chart"` } func (cmd *helmUpdateCmd) Help() string { diff --git a/cmd/kluctl/commands/cmd_list_images.go b/cmd/kluctl/commands/cmd_list_images.go index 585527ae1..b59ec9b84 100644 --- a/cmd/kluctl/commands/cmd_list_images.go +++ b/cmd/kluctl/commands/cmd_list_images.go @@ -12,7 +12,7 @@ type listImagesCmd struct { args.InclusionFlags args.OutputFlags - Simple bool `help:"Output a simplified version of the images list"` + Simple bool `group:"misc" help:"Output a simplified version of the images list"` } func (cmd *listImagesCmd) Help() string { diff --git a/cmd/kluctl/commands/cmd_seal.go b/cmd/kluctl/commands/cmd_seal.go index 03bb589a7..5046060dc 100644 --- a/cmd/kluctl/commands/cmd_seal.go +++ b/cmd/kluctl/commands/cmd_seal.go @@ -14,8 +14,8 @@ type sealCmd struct { args.ProjectFlags args.TargetFlags - SecretsDir string `help:"Specifies where to find unencrypted secret files. The given directory is NOT meant to be part of your source repository! The given path only matters for secrets of type 'path'. Defaults to the current working directory."` - ForceReseal bool `help:"Lets kluctl ignore secret hashes found in already sealed secrets and thus forces resealing of those."` + SecretsDir string `group:"misc" help:"Specifies where to find unencrypted secret files. The given directory is NOT meant to be part of your source repository! The given path only matters for secrets of type 'path'. Defaults to the current working directory."` + ForceReseal bool `group:"misc" help:"Lets kluctl ignore secret hashes found in already sealed secrets and thus forces resealing of those."` } func (cmd *sealCmd) Help() string { diff --git a/cmd/kluctl/commands/cmd_validate.go b/cmd/kluctl/commands/cmd_validate.go index 8cb64d787..29581316b 100644 --- a/cmd/kluctl/commands/cmd_validate.go +++ b/cmd/kluctl/commands/cmd_validate.go @@ -15,9 +15,9 @@ type validateCmd struct { args.OutputFlags args.RenderOutputDirFlags - Wait time.Duration `help:"Wait for the given amount of time until the deployment validates"` - Sleep time.Duration `help:"Sleep duration between validation attempts" default:"5s"` - WarningsAsErrors bool `help:"Consider warnings as failures"` + Wait time.Duration `group:"misc" help:"Wait for the given amount of time until the deployment validates"` + Sleep time.Duration `group:"misc" help:"Sleep duration between validation attempts" default:"5s"` + WarningsAsErrors bool `group:"misc" help:"Consider warnings as failures"` } func (cmd *validateCmd) Help() string { From eee76b9a536e056fb308dd80b5c4a6ed90a59d3e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 28 Feb 2022 15:56:06 +0100 Subject: [PATCH 0403/2916] Implement archive command --- cmd/kluctl/commands/cmd_archive.go | 23 ++++++ cmd/kluctl/commands/root.go | 1 + pkg/kluctl_project/load.go | 113 +++++++++++++++++++++++++---- pkg/utils/tar.go | 69 ++++++++++++++++++ 4 files changed, 191 insertions(+), 15 deletions(-) create mode 100644 cmd/kluctl/commands/cmd_archive.go diff --git a/cmd/kluctl/commands/cmd_archive.go b/cmd/kluctl/commands/cmd_archive.go new file mode 100644 index 000000000..7fa30624b --- /dev/null +++ b/cmd/kluctl/commands/cmd_archive.go @@ -0,0 +1,23 @@ +package commands + +import ( + "github.com/codablock/kluctl/cmd/kluctl/args" + "github.com/codablock/kluctl/pkg/kluctl_project" +) + +type archiveCmd struct { + args.ProjectFlags + + OutputArchive string `group:"misc" help:"Path to .tgz to write project to." type:"path"` + OutputMetadata string `group:"misc" help:"Path to .yml to write metadata to. If not specified, metadata is written into the archive."` +} + +func (cmd *archiveCmd) Help() string { + return `This archive can then be used with '--from-archive'` +} + +func (cmd *archiveCmd) Run() error { + return withKluctlProjectFromArgs(cmd.ProjectFlags, func(p *kluctl_project.KluctlProjectContext) error { + return p.CreateTGZArchive(cmd.OutputArchive, cmd.OutputMetadata) + }) +} diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index 7823d2c4c..32e4cb8b1 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -36,6 +36,7 @@ type cli struct { Verbosity string `group:"global" short:"v" help:"Log level (debug, info, warn, error, fatal, panic)." default:"info"` NoUpdateCheck bool `group:"global" help:"Disable update check on startup"` + Archive archiveCmd `cmd:"" help:"Write project and all related components into single tgz"` CheckImageUpdates checkImageUpdatesCmd `cmd:"" help:"Render deployment and check if any images have new tags available"` Delete deleteCmd `cmd:"" help:"Delete a target (or parts of it) from the corresponding cluster"` Deploy deployCmd `cmd:"" help:"Deploys a target to the corresponding cluster"` diff --git a/pkg/kluctl_project/load.go b/pkg/kluctl_project/load.go index 306e146f9..84467a545 100644 --- a/pkg/kluctl_project/load.go +++ b/pkg/kluctl_project/load.go @@ -1,6 +1,8 @@ package kluctl_project import ( + "archive/tar" + "compress/gzip" "fmt" types2 "github.com/codablock/kluctl/pkg/types" "github.com/codablock/kluctl/pkg/utils" @@ -9,6 +11,8 @@ import ( "io/ioutil" "os" "path/filepath" + "strings" + "time" ) func (c *KluctlProjectContext) mergeClustersDirs(mergedClustersDir string, clustersInfos []gitProjectInfo) error { @@ -39,19 +43,24 @@ func (c *KluctlProjectContext) mergeClustersDirs(mergedClustersDir string, clust return nil } -func (c *KluctlProjectContext) load(allowGit bool) error { - kluctlProjectInfo, err := c.cloneKluctlProject() - if err != nil { - return err - } - +func (c *KluctlProjectContext) getConfigPath(projectDir string) string { configPath := c.loadArgs.ProjectConfig if configPath == "" { - p := filepath.Join(kluctlProjectInfo.dir, ".kluctl.yml") + p := filepath.Join(projectDir, ".kluctl.yml") if utils.IsFile(p) { configPath = p } } + return configPath +} + +func (c *KluctlProjectContext) load(allowGit bool) error { + kluctlProjectInfo, err := c.cloneKluctlProject() + if err != nil { + return err + } + + configPath := c.getConfigPath(kluctlProjectInfo.dir) if configPath != "" { err = yaml.ReadYamlFile(configPath, &c.Config) @@ -137,7 +146,7 @@ func LoadKluctlProject(args LoadKluctlProjectArgs, cb func(ctx *KluctlProjectCon if args.ProjectUrl != nil || args.ProjectRef != "" || args.ProjectConfig != "" || args.LocalClusters != "" || args.LocalDeployment != "" || args.LocalSealedSecrets != "" { return fmt.Errorf("--from-archive can not be combined with any other project related option") } - project, err := loadKluctlProjectFromArchive(args.FromArchive, args.FromArchiveMetadata, tmpDir) + project, err := loadKluctlProjectFromArchive(args, tmpDir) if err != nil { return err } @@ -160,21 +169,21 @@ func LoadKluctlProject(args LoadKluctlProjectArgs, cb func(ctx *KluctlProjectCon } } -func loadKluctlProjectFromArchive(fromArchive string, fromArchiveMetadata string, tmpDir string) (*KluctlProjectContext, error) { +func loadKluctlProjectFromArchive(args LoadKluctlProjectArgs, tmpDir string) (*KluctlProjectContext, error) { var dir string - if utils.IsFile(fromArchiveMetadata) { - err := utils.ExtractTarGzFile(fromArchive, tmpDir) + if utils.IsFile(args.FromArchive) { + err := utils.ExtractTarGzFile(args.FromArchive, tmpDir) if err != nil { - return nil, fmt.Errorf("failed to extract archive %v: %w", fromArchive, err) + return nil, fmt.Errorf("failed to extract archive %v: %w", args.FromArchive, err) } dir = tmpDir } else { - dir = fromArchive + dir = args.FromArchive } var metdataPath string - if fromArchiveMetadata != "" { - metdataPath = fromArchiveMetadata + if args.FromArchiveMetadata != "" { + metdataPath = args.FromArchiveMetadata } else { metdataPath = filepath.Join(dir, "metadata.yml") } @@ -191,8 +200,82 @@ func loadKluctlProjectFromArchive(fromArchive string, fromArchiveMetadata string LocalClusters: filepath.Join(dir, "clusters"), LocalDeployment: filepath.Join(dir, "deployment"), LocalSealedSecrets: filepath.Join(dir, "sealed-secrets"), + J2: args.J2, }, dir) p.involvedRepos = metadata.InvolvedRepos p.DynamicTargets = metadata.Targets return p, nil } + +func (c *KluctlProjectContext) CreateTGZArchive(archivePath string, metadataPath string) error { + f, err := os.Create(archivePath) + if err != nil { + return err + } + defer f.Close() + gz := gzip.NewWriter(f) + defer gz.Close() + tw := tar.NewWriter(gz) + defer tw.Close() + + filter := func(h *tar.Header) (*tar.Header, error) { + if strings.HasSuffix(strings.ToLower(h.Name), ".git") { + return nil, nil + } + h.Uid = 0 + h.Gid = 0 + h.Uname = "" + h.Gname = "" + h.ModTime = time.Time{} + h.ChangeTime = time.Time{} + h.AccessTime = time.Time{} + return h, nil + } + + md := types2.ArchiveMetadata{ + InvolvedRepos: c.involvedRepos, + Targets: c.DynamicTargets, + } + mdStr, err := yaml.WriteYamlBytes(md) + if err != nil { + return err + } + + if metadataPath != "" { + err = ioutil.WriteFile(metadataPath, mdStr, 0o666) + if err != nil { + return err + } + } else { + err = tw.WriteHeader(&tar.Header{ + Name: "metadata.yml", + Size: int64(len(mdStr)), + Mode: 0o666 | tar.TypeReg, + }) + if err != nil { + return err + } + _, err = tw.Write(mdStr) + if err != nil { + return err + } + } + + if err = utils.AddToTar(tw, c.getConfigPath(c.ProjectDir), ".kluctl.yml", filter); err != nil { + return err + } + if err = utils.AddToTar(tw, c.ProjectDir, "kluctl-project", filter); err != nil { + return err + } + if err = utils.AddToTar(tw, c.DeploymentDir, "deployment", filter); err != nil { + return err + } + if err = utils.AddToTar(tw, c.ClustersDir, "clusters", filter); err != nil { + return err + } + if err = utils.AddToTar(tw, c.SealedSecretsDir, "sealed-secrets", filter); err != nil { + return err + } + + return nil +} diff --git a/pkg/utils/tar.go b/pkg/utils/tar.go index c0425d8fa..9c8511bfd 100644 --- a/pkg/utils/tar.go +++ b/pkg/utils/tar.go @@ -5,6 +5,7 @@ import ( "compress/gzip" "fmt" "io" + "io/fs" "os" "path/filepath" ) @@ -63,3 +64,71 @@ func ExtractTarGzStream(r io.Reader, targetPath string) error { } return nil } + +func AddToTar(tw *tar.Writer, pth string, name string, filter func(h *tar.Header) (*tar.Header, error)) error { + fi, err := os.Stat(pth) + if err != nil { + return err + } + + var linkName string + if fi.Mode().Type() == fs.ModeSymlink { + x, err := os.Readlink(pth) + if err != nil { + return err + } + linkName = x + } + + h, err := tar.FileInfoHeader(fi, linkName) + if err != nil { + return err + } + h.Name = name + + if filter != nil { + h, err = filter(h) + if err != nil { + return err + } + if h == nil { + return nil + } + } + + err = tw.WriteHeader(h) + if err != nil { + return err + } + + if fi.Mode().Type() == fs.ModeSymlink { + return nil + } + + if fi.Mode().IsDir() { + des, err := os.ReadDir(pth) + if err != nil { + return err + } + for _, d := range des { + err = AddToTar(tw, filepath.Join(pth, d.Name()), filepath.Join(name, d.Name()), filter) + if err != nil { + return err + } + } + return nil + } else if fi.Mode().IsRegular() { + f, err := os.Open(pth) + if err != nil { + return err + } + defer f.Close() + _, err = io.Copy(tw, f) + if err != nil { + return err + } + return nil + } else { + return fmt.Errorf("unsupported file type/mode %s", fi.Mode().String()) + } +} From d523bfcab7a4d5af5a3e831ee9aff3df5e186740 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 28 Feb 2022 15:56:15 +0100 Subject: [PATCH 0404/2916] Run gofmt --- pkg/utils/versions/latest_version.go | 6 ++-- pkg/utils/versions/latest_version_parse.go | 34 ++++++++++++---------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/pkg/utils/versions/latest_version.go b/pkg/utils/versions/latest_version.go index 12146a1c2..68c3779ec 100644 --- a/pkg/utils/versions/latest_version.go +++ b/pkg/utils/versions/latest_version.go @@ -25,7 +25,7 @@ func Filter(lv LatestVersionFilter, versions []string) []string { type regexVersionFilter struct { patternStr string - pattern *regexp.Regexp + pattern *regexp.Regexp } func NewRegexVersionFilter(pattern string) (LatestVersionFilter, error) { @@ -35,11 +35,11 @@ func NewRegexVersionFilter(pattern string) (LatestVersionFilter, error) { } return ®exVersionFilter{ patternStr: pattern, - pattern: p, + pattern: p, }, nil } -func (f *regexVersionFilter) Match(version string) bool{ +func (f *regexVersionFilter) Match(version string) bool { return f.pattern.MatchString(version) } diff --git a/pkg/utils/versions/latest_version_parse.go b/pkg/utils/versions/latest_version_parse.go index 39d190c4b..8a28c8dca 100644 --- a/pkg/utils/versions/latest_version_parse.go +++ b/pkg/utils/versions/latest_version_parse.go @@ -9,12 +9,12 @@ import ( ) type tokenAndText struct { - tok rune + tok rune text string } type preparsed struct { - tokens []tokenAndText + tokens []tokenAndText nextPos int } @@ -45,10 +45,10 @@ func (p *preparsed) Peek() rune { } func (p *preparsed) PeekN(n int) rune { - if p.nextPos + n >= len(p.tokens) { + if p.nextPos+n >= len(p.tokens) { return scanner.EOF } - return p.tokens[p.nextPos + n].tok + return p.tokens[p.nextPos+n].tok } func ParseLatestVersion(str string) (LatestVersionFilter, error) { @@ -80,7 +80,9 @@ func parseFilter(p *preparsed) (LatestVersionFilter, error) { name := p.CurTokenText() tok = p.Next() - if tok != '(' {return nil, fmt.Errorf("unexpected token %v, expected (", tok)} + if tok != '(' { + return nil, fmt.Errorf("unexpected token %v, expected (", tok) + } var f LatestVersionFilter @@ -100,7 +102,9 @@ func parseFilter(p *preparsed) (LatestVersionFilter, error) { } tok = p.Next() - if tok != ')' {return nil, fmt.Errorf("unexpected token %v, expected (", tok)} + if tok != ')' { + return nil, fmt.Errorf("unexpected token %v, expected (", tok) + } return f, nil } @@ -160,11 +164,11 @@ func parseNumberFilter(p *preparsed) (LatestVersionFilter, error) { } type arg struct { - name string - tok rune - value interface{} + name string + tok rune + value interface{} required bool - isBool bool + isBool bool found bool } @@ -179,7 +183,7 @@ func parseArgs(p *preparsed, args []*arg) error { if p.Peek() == ')' { break } - if len(parsedArgs) + len(parsedKvArgs) != 0 { + if len(parsedArgs)+len(parsedKvArgs) != 0 { if tok := p.Next(); tok != ',' { return fmt.Errorf("unexpected token %v, expected ')' or ','", tok) } @@ -258,7 +262,7 @@ func parseArg(p *preparsed) (*arg, error) { } value := p.CurTokenText() if tok == scanner.String { - value = value[1:len(value)-1] + value = value[1 : len(value)-1] } return tok, value, nil } @@ -273,8 +277,8 @@ func parseArg(p *preparsed) (*arg, error) { return nil, err } a := arg{ - name: name, - tok: valueTok, + name: name, + tok: valueTok, value: value, } return &a, nil @@ -285,7 +289,7 @@ func parseArg(p *preparsed) (*arg, error) { return nil, err } a := arg{ - tok: valueTok, + tok: valueTok, value: value, } return &a, nil From be41bdeb03404bbd6f1ae0494ea8f5cfb52890be Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 28 Feb 2022 16:33:23 +0100 Subject: [PATCH 0405/2916] Cache image requests --- pkg/deployment/images.go | 9 ++++++- pkg/utils/thread_safe_cache.go | 43 ++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 pkg/utils/thread_safe_cache.go diff --git a/pkg/deployment/images.go b/pkg/deployment/images.go index 201c4d805..ae7791fa3 100644 --- a/pkg/deployment/images.go +++ b/pkg/deployment/images.go @@ -7,6 +7,7 @@ import ( "github.com/codablock/kluctl/pkg/k8s" "github.com/codablock/kluctl/pkg/registries" "github.com/codablock/kluctl/pkg/types" + "github.com/codablock/kluctl/pkg/utils" "github.com/codablock/kluctl/pkg/utils/uo" "github.com/codablock/kluctl/pkg/utils/versions" "github.com/codablock/kluctl/pkg/yaml" @@ -21,6 +22,8 @@ type Images struct { fixedImages []types.FixedImage seenImages []types.FixedImage mutex sync.Mutex + + registryCache utils.ThreadSafeMultiCache } func NewImages(updateImages bool) (*Images, error) { @@ -69,10 +72,14 @@ func (images *Images) GetFixedImage(image string, namespace string, deployment s } func (images *Images) GetLatestImageFromRegistry(image string, latestVersion string) (*string, error) { - tags, err := registries.ListImageTags(image) + ret, err := images.registryCache.Get(image, "tag", func() (interface{}, error) { + return registries.ListImageTags(image) + }) if err != nil { return nil, err } + tags, _ := ret.([]string) + if len(tags) == 0 { return nil, nil } diff --git a/pkg/utils/thread_safe_cache.go b/pkg/utils/thread_safe_cache.go new file mode 100644 index 000000000..f73829d29 --- /dev/null +++ b/pkg/utils/thread_safe_cache.go @@ -0,0 +1,43 @@ +package utils + +import "sync" + +type ThreadSafeCache struct { + mutex sync.Mutex + cache map[string]*cacheEntry +} + +type cacheEntry struct { + value interface{} + err error +} + +func (c *ThreadSafeCache) Get(key string, f func() (interface{}, error)) (interface{}, error) { + c.mutex.Lock() + defer c.mutex.Unlock() + + if c.cache == nil { + c.cache = make(map[string]*cacheEntry) + } + + e, ok := c.cache[key] + if ok { + return e.value, e.err + } + e = &cacheEntry{} + e.value, e.err = f() + c.cache[key] = e + + return e.value, e.err +} + +type ThreadSafeMultiCache struct { + cache ThreadSafeCache +} + +func (c *ThreadSafeMultiCache) Get(cacheKey string, key string, f func() (interface{}, error)) (interface{}, error) { + cache, _ := c.cache.Get(cacheKey, func() (interface{}, error) { + return &ThreadSafeCache{}, nil + }) + return cache.(*ThreadSafeCache).Get(key, f) +} From 69a68711fef4f19410d7ddbd30848efd85ef85c9 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 28 Feb 2022 16:46:04 +0100 Subject: [PATCH 0406/2916] Reimplement script to update commands.md --- cmd/kluctl/commands/root.go | 10 +- docs/commands.md | 456 ++++++++++++----------------- hack/replace-commands-help/main.go | 148 ++++++++++ scripts/replace-commands-help.py | 114 -------- 4 files changed, 333 insertions(+), 395 deletions(-) create mode 100644 hack/replace-commands-help/main.go delete mode 100644 scripts/replace-commands-help.py diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index 32e4cb8b1..bfa139b67 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -54,11 +54,11 @@ type cli struct { } var flagGroups = []kong.Group{ - {Key: "project", Title: "Project arguments:", Description: ""}, - {Key: "images", Title: "Image arguments:", Description: ""}, - {Key: "inclusion", Title: "Inclusion/Exclusion arguments:", Description: ""}, - {Key: "misc", Title: "Misc arguments:", Description: ""}, - {Key: "global", Title: "Global arguments:", Description: ""}, + {Key: "project", Title: "Project arguments:", Description: "Define where and how to load the kluctl project and its components from."}, + {Key: "images", Title: "Image arguments:", Description: "Control fixed images and update behaviour."}, + {Key: "inclusion", Title: "Inclusion/Exclusion arguments:", Description: "Control inclusion/exclusion."}, + {Key: "misc", Title: "Misc arguments:", Description: "Command specific arguments."}, + {Key: "global", Title: "Global arguments:", Description: "Global arguments."}, } var globalFlagGroup = &flagGroups[len(flagGroups)-1] diff --git a/docs/commands.md b/docs/commands.md index 41256be07..bb5e45c4a 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -14,22 +14,24 @@ The following sets of common arguments are available: ## project arguments ``` - Project arguments: Define where and how to load the kluctl project and its components from. - -p, --project-url TEXT Git url of the kluctl project. If not specified, the current directory will be used - instead of a remote Git project - -b, --project-ref TEXT Git ref of the kluctl project. Only used when --project-url was given. - -c, --project-config FILE Location of the .kluctl.yml config file. Defaults to $PROJECT/.kluctl.yml - --local-clusters DIRECTORY Local clusters directory. Overrides the project from .kluctl.yml - --local-deployment DIRECTORY Local deployment directory. Overrides the project from .kluctl.yml - --local-sealed-secrets DIRECTORY - Local sealed-secrets directory. Overrides the project from .kluctl.yml - --from-archive PATH Load project (.kluctl.yml, cluster, ...) from archive. Given path can either be an - archive file or a directory with the extracted contents. - --from-archive-metadata PATH Specify where to load metadata (targets, ...) from. If not specified, metadata is - assumed to be part of the archive. - --cluster TEXT Specify/Override cluster - -a, --arg TEXT Template argument in the form name=value - -t, --target TEXT Target name to run command for. Target must exist in .kluctl.yml. +Project arguments: + Define where and how to load the kluctl project and its components from. + + -p, --project-url=STRING Git url of the kluctl project. If not specified, the current directory will be + used instead of a remote Git project + -b, --project-ref=STRING Git ref of the kluctl project. Only used when --project-url was given. + -c, --project-config=STRING Location of the .kluctl.yml config file. Defaults to $PROJECT/.kluctl.yml + --local-clusters=STRING Local clusters directory. Overrides the project from .kluctl.yml + --local-deployment=STRING Local deployment directory. Overrides the project from .kluctl.yml + --local-sealed-secrets=STRING Local sealed-secrets directory. Overrides the project from .kluctl.yml + --from-archive=STRING Load project (.kluctl.yml, cluster, ...) from archive. Given path can either be + an archive file or a directory with the extracted contents. + --from-archive-metadata=STRING Specify where to load metadata (targets, ...) from. If not specified, metadata + is assumed to be part of the archive. + --cluster=STRING Specify/Override cluster + -t, --target=STRING Target name to run command for. Target must exist in .kluctl.yml. + -a, --arg=ARG,... Template argument in the form name=value + ``` @@ -38,16 +40,19 @@ These arguments control where and how to load the kluctl project and deployment ## image arguments ``` - Image arguments: - -F, --fixed-image TEXT Pin an image to a given version. Expects '--fixed- - image=image<:namespace:deployment:container>=result' - --fixed-images-file FILE Use .yml file to pin image versions. See output of list-images sub-command or read - the documentation for details about the output format - -u, --update-images This causes kluctl to prefer the latest image found in registries, based on the - `latest_image` filters provided to 'images.get_image(...)' calls. Use this flag if - you want to update to the latest versions/tags of all images. '-u' takes precedence - over `--fixed-image/--fixed-images-file`, meaning that the latest images are used - even if an older image is given via fixed images. +Image arguments: + Control fixed images and update behaviour. + + -F, --fixed-image=FIXED-IMAGE,... Pin an image to a given version. Expects + '--fixed-image=image<:namespace:deployment:container>=result' + --fixed-images-file=STRING Use .yml file to pin image versions. See output of list-images sub-command or + read the documentation for details about the output format + -u, --update-images This causes kluctl to prefer the latest image found in registries, based on the + 'latest_image' filters provided to 'images.get_image(...)' calls. Use this flag + if you want to update to the latest versions/tags of all images. '-u' takes + precedence over '--fixed-image/--fixed-images-file', meaning that the latest + images are used even if an older image is given via fixed images. + ``` @@ -56,15 +61,19 @@ These arguments control image versions requested by `images.get_image(...)` [cal ## Inclusion/Exclusion arguments ``` - Inclusion/Exclusion arguments: - Control inclusion/exclusion. - -I, --include-tag TEXT Include deployments with given tag. - -E, --exclude-tag TEXT Exclude deployments with given tag. Exclusion has precedence over inclusion, meaning - that explicitly excluded deployments will always be excluded even if an inclusion - rule would match the same deployment. - --include-kustomize-dir TEXT Include kustomize dir. The path must be relative to the root deployment project. - --exclude-kustomize-dir TEXT Exclude kustomize dir. The path must be relative to the root deployment project. - Exclusion has precedence over inclusion, same as in --exclude-tag +Inclusion/Exclusion arguments: + Control inclusion/exclusion. + + -I, --include-tag=INCLUDE-TAG,... Include deployments with given tag. + -E, --exclude-tag=EXCLUDE-TAG,... Exclude deployments with given tag. Exclusion has precedence over inclusion, + meaning that explicitly excluded deployments will always be excluded even if an + inclusion rule would match the same deployment. + --include-deployment-dir=INCLUDE-DEPLOYMENT-DIR,... + Include deployment dir. The path must be relative to the root deployment project. + --exclude-deployment-dir=EXCLUDE-DEPLOYMENT-DIR,... + Exclude deployment dir. The path must be relative to the root deployment project. + Exclusion has precedence over inclusion, same as in --exclude-tag + ``` @@ -88,51 +97,9 @@ A few additional environment variables are supported which do not belong to an o # Commands The following commands are available: -## bootstrap - -Usage: kluctl bootstrap [OPTIONS] - - Bootstrap a target cluster. - - This will install the sealed-secrets operator into the specified cluster if not already installed. - - Either --target or --cluster must be specified. - - - -The following sets of arguments are available: -1. [project arguments](#project-arguments) - -In addition, the following arguments are available: - -``` - Misc arguments: - -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. - --dry-run Performs all kubernetes API calls in dry-run mode. - --force-apply Force conflict resolution when applying. See documentation for details - --replace-on-error When patching an object fails, try to replace it. See documentation for more - details. - --force-replace-on-error Same as --replace-on-error, but also try to delete and re-create objects. See - documentation for more details. - --hook-timeout DURATION Maximum time to wait for hook readiness. The timeout is meant per-hook. Timeouts are - in the duration format (1s, 1m, 1h, ...). If not specified, a default timeout of 5m - is used. - --abort-on-error Abort deploying when an error occurs instead of trying the remaining deployments - -o, --output TEXT Specify output format and target file, in the format 'format=path'. Format can - either be 'text' or 'yaml'. Can be specified multiple times. The actual format for - yaml is currently not documented and subject to change. -``` - - ## deploy -Usage: kluctl deploy [OPTIONS] - - Deploys a target to the corresponding cluster. - - This command will also output a diff between the initial state and the state after deployment. The format of this - diff is the same as for the `diff` command. It will also output a list of prunable objects (without actually - deleting them). +Usage: replace-commands-help deploy @@ -144,24 +111,24 @@ The following sets of arguments are available: In addition, the following arguments are available: ``` - Misc arguments: - -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. - --dry-run Performs all kubernetes API calls in dry-run mode. - --force-apply Force conflict resolution when applying. See documentation for details - --replace-on-error When patching an object fails, try to replace it. See documentation for more - details. - --force-replace-on-error Same as --replace-on-error, but also try to delete and re-create objects. See - documentation for more details. - --hook-timeout DURATION Maximum time to wait for hook readiness. The timeout is meant per-hook. Timeouts are - in the duration format (1s, 1m, 1h, ...). If not specified, a default timeout of 5m - is used. - --abort-on-error Abort deploying when an error occurs instead of trying the remaining deployments - -o, --output TEXT Specify output format and target file, in the format 'format=path'. Format can - either be 'text' or 'yaml'. Can be specified multiple times. The actual format for - yaml is currently not documented and subject to change. - --render-output-dir DIRECTORY - Specifies the target directory to render the project into. If omitted, atemporary - directory is used. +Misc arguments: + Command specific arguments. + + -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. + --dry-run Performs all kubernetes API calls in dry-run mode. + --force-apply Force conflict resolution when applying. See documentation for details + --replace-on-error When patching an object fails, try to replace it. See documentation for more + details. + --force-replace-on-error Same as --replace-on-error, but also try to delete and re-create objects. See + documentation for more details. + --abort-on-error Abort deploying when an error occurs instead of trying the remaining deployments + --hook-timeout=5m Maximum time to wait for hook readiness. The timeout is meant per-hook. Timeouts are + in the duration format (1s, 1m, 1h, ...). If not specified, a default timeout of 5m + is used. + -o, --output=OUTPUT,... Specify output target file. Can be specified multiple times + --render-output-dir=STRING Specifies the target directory to render the project into. If omitted, a temporary + directory is used. + ``` @@ -197,14 +164,7 @@ and outputs them instead. This option modifies the behaviour to immediately abor ## diff -Usage: kluctl diff [OPTIONS] - - Perform a diff between the locally rendered target and the already deployed target. - - The output is by default in human readable form (a table combined with unified diffs). The output can also be - changed to output yaml file. Please note however that the format is currently not documented and prone to changes. - - After the diff is performed, the command will also search for prunable objects and list them. +Usage: replace-commands-help diff @@ -216,22 +176,23 @@ The following sets of arguments are available: In addition, the following arguments are available: ``` - Misc arguments: - --force-apply Force conflict resolution when applying. See documentation for details - --replace-on-error When patching an object fails, try to replace it. See documentation for more - details. - --force-replace-on-error Same as --replace-on-error, but also try to delete and re-create objects. See - documentation for more details. - --ignore-tags Ignores changes in tags when diffing - --ignore-labels Ignores changes in labels when diffing - --ignore-annotations Ignores changes in annotations when diffing - --ignore-order Ignores changes in order when diffing - -o, --output TEXT Specify output format and target file, in the format 'format=path'. Format can - either be 'text' or 'yaml'. Can be specified multiple times. The actual format for - yaml is currently not documented and subject to change. - --render-output-dir DIRECTORY - Specifies the target directory to render the project into. If omitted, atemporary - directory is used. +Misc arguments: + Command specific arguments. + + --force-apply Force conflict resolution when applying. See documentation for details + --replace-on-error When patching an object fails, try to replace it. See documentation for more + details. + --force-replace-on-error Same as --replace-on-error, but also try to delete and re-create objects. See + documentation for more details. + --ignore-tags Ignores changes in tags when diffing + --ignore-labels Ignores changes in labels when diffing + --ignore-annotations Ignores changes in annotations when diffing + -o, --output-format=OUTPUT-FORMAT,... Specify output format and target file, in the format 'format=path'. Format + can either be 'text' or 'yaml'. Can be specified multiple times. The actual + format for yaml is currently not documented and subject to change. + --render-output-dir=STRING Specifies the target directory to render the project into. If omitted, a + temporary directory is used. + ``` @@ -239,14 +200,7 @@ In addition, the following arguments are available: ## delete -Usage: kluctl delete [OPTIONS] - - Delete the a target (or parts of it) from the corresponding cluster. - - Objects are located based on `deleteByLabels`, configured in `deployment.yml` - - WARNING: This command will also delete objects which are not part of your deployment project (anymore). It really - only decides based on the `deleteByLabel` labels and does NOT take the local target/state into account! +Usage: replace-commands-help delete @@ -258,12 +212,16 @@ The following sets of arguments are available: In addition, the following arguments are available: ``` - Misc arguments: - -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. - --dry-run Performs all kubernetes API calls in dry-run mode. - -o, --output TEXT Specify output format and target file, in the format 'format=path'. Format can - either be 'text' or 'yaml'. Can be specified multiple times. The actual format for - yaml is currently not documented and subject to change. +Misc arguments: + Command specific arguments. + + -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you would answer + 'yes'. + --dry-run Performs all kubernetes API calls in dry-run mode. + -o, --output-format=OUTPUT-FORMAT,... Specify output format and target file, in the format 'format=path'. Format + can either be 'text' or 'yaml'. Can be specified multiple times. The actual + format for yaml is currently not documented and subject to change. + ``` @@ -271,15 +229,7 @@ They have the same meaning as described in [deploy](#deploy). ## prune -Usage: kluctl prune [OPTIONS] - - Searches the target cluster for prunable objects and deletes them. - - Searching works by: - - 1. Search the cluster for all objects match `deleteByLabels`, as configured in `deployment.yml` - 2. Render the local target and list all objects. - 3. Remove all objects from the list of 1. that are part of the list in 2. +Usage: replace-commands-help prune @@ -291,12 +241,16 @@ The following sets of arguments are available: In addition, the following arguments are available: ``` - Misc arguments: - -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. - --dry-run Performs all kubernetes API calls in dry-run mode. - -o, --output TEXT Specify output format and target file, in the format 'format=path'. Format can - either be 'text' or 'yaml'. Can be specified multiple times. The actual format for - yaml is currently not documented and subject to change. +Misc arguments: + Command specific arguments. + + -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you would answer + 'yes'. + --dry-run Performs all kubernetes API calls in dry-run mode. + -o, --output-format=OUTPUT-FORMAT,... Specify output format and target file, in the format 'format=path'. Format + can either be 'text' or 'yaml'. Can be specified multiple times. The actual + format for yaml is currently not documented and subject to change. + ``` @@ -304,14 +258,7 @@ They have the same meaning as described in [deploy](#deploy). ## list-images -Usage: kluctl list-images [OPTIONS] - - Renders the target and outputs all images used via `images.get_image(...)` - - The result is a compatible with yaml files expected by --fixed-images-file. - - If fixed images (`-f/--fixed-image`) are provided, these are also taken into account, as described in for the deploy - command. +Usage: replace-commands-help list-images @@ -323,20 +270,18 @@ The following sets of arguments are available: In addition, the following arguments are available: ``` - Misc arguments: - -o, --output TEXT Specify output target file. Can be specified multiple times - --simple Output a simplified version of the images list +Misc arguments: + Command specific arguments. + + -o, --output=OUTPUT,... Specify output target file. Can be specified multiple times + --simple Output a simplified version of the images list + ``` ## poke-images -Usage: kluctl poke-images [OPTIONS] - - Replace all images in target. - - This command will fully render the target and then only replace images instead of fully deploying the target. Only - images used in combination with `images.get_image(...)` are replaced. +Usage: replace-commands-help poke-images @@ -348,26 +293,21 @@ The following sets of arguments are available: In addition, the following arguments are available: ``` - Misc arguments: - -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. - --dry-run Performs all kubernetes API calls in dry-run mode. - -o, --output TEXT Specify output format and target file, in the format 'format=path'. Format can - either be 'text' or 'yaml'. Can be specified multiple times. The actual format for - yaml is currently not documented and subject to change. - --render-output-dir DIRECTORY - Specifies the target directory to render the project into. If omitted, atemporary - directory is used. +Misc arguments: + Command specific arguments. + + -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. + --dry-run Performs all kubernetes API calls in dry-run mode. + -o, --output=OUTPUT,... Specify output target file. Can be specified multiple times + --render-output-dir=STRING Specifies the target directory to render the project into. If omitted, a temporary + directory is used. + ``` ## downscale -Usage: kluctl downscale [OPTIONS] - - Downscale all deployments. - - This command will downscale all Deployments, StatefulSets and CronJobs. It is also possible to influence the - behaviour with the help of annotations, as described in the documentation. +Usage: replace-commands-help downscale @@ -379,24 +319,24 @@ The following sets of arguments are available: In addition, the following arguments are available: ``` - Misc arguments: - -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. - --dry-run Performs all kubernetes API calls in dry-run mode. - -o, --output TEXT Specify output format and target file, in the format 'format=path'. Format can - either be 'text' or 'yaml'. Can be specified multiple times. The actual format for - yaml is currently not documented and subject to change. - --render-output-dir DIRECTORY - Specifies the target directory to render the project into. If omitted, atemporary - directory is used. +Misc arguments: + Command specific arguments. + + -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you would answer + 'yes'. + --dry-run Performs all kubernetes API calls in dry-run mode. + -o, --output-format=OUTPUT-FORMAT,... Specify output format and target file, in the format 'format=path'. Format + can either be 'text' or 'yaml'. Can be specified multiple times. The actual + format for yaml is currently not documented and subject to change. + --render-output-dir=STRING Specifies the target directory to render the project into. If omitted, a + temporary directory is used. + ``` ## render -Usage: kluctl render [OPTIONS] - - Renders all resources and configuration files and stores the result in either a temporary directory or a specified - directory. +Usage: replace-commands-help render @@ -407,25 +347,18 @@ The following sets of arguments are available: In addition, the following arguments are available: ``` - Misc arguments: - --render-output-dir DIRECTORY - Specifies the target directory to render the project into. If omitted, atemporary - directory is used. - --output-images FILE Also output images list to given FILE. This output the same result as from the list- - images command. - --output-single-yaml FILE Also write all resources into a single yaml file. +Misc arguments: + Command specific arguments. + + --render-output-dir=STRING Specifies the target directory to render the project into. If omitted, a temporary + directory is used. + ``` ## validate -Usage: kluctl validate [OPTIONS] - - Validates the already deployed deployment. - - This means that all objects are retrieved from the cluster and checked for readiness. - - TODO: This needs to be better documented! +Usage: replace-commands-help validate @@ -436,28 +369,22 @@ The following sets of arguments are available: In addition, the following arguments are available: ``` - Misc arguments: - -o, --output TEXT Specify output target file. Can be specified multiple times - --render-output-dir DIRECTORY - Specifies the target directory to render the project into. If omitted, atemporary - directory is used. - --wait TEXT Wait for the given amount of time until the deployment validates - --sleep TEXT Sleep duration between validation attempts - --warnings-as-errors Consider warnings as failures +Misc arguments: + Command specific arguments. + + -o, --output=OUTPUT,... Specify output target file. Can be specified multiple times + --render-output-dir=STRING Specifies the target directory to render the project into. If omitted, a temporary + directory is used. + --wait=DURATION Wait for the given amount of time until the deployment validates + --sleep=5s Sleep duration between validation attempts + --warnings-as-errors Consider warnings as failures + ``` ## seal -Usage: kluctl seal [OPTIONS] - - Seal secrets based on target's sealingConfig. - - Loads all secrets from the specified secrets sets from the target's sealingConfig and then renders the target, - including all files with the `.sealme` extension. Then runs kubeseal on each `.sealme` file and stores secrets in - the directory specified by `--local-sealed-secrets`, using the outputPattern from your deployment project. - - If no `--target` is specified, sealing is performed for all targets. +Usage: replace-commands-help seal @@ -469,24 +396,21 @@ The following sets of arguments are available: In addition, the following arguments are available: ``` - Misc arguments: - --secrets-dir DIRECTORY Specifies where to find unencrypted secret files. The given directory is NOT meant - to be part of your source repository! The given path only matters for secrets of - type 'path'. Defaults to the current working directory. - --force-reseal Lets kluctl ignore secret hashes found in already sealed secrets and thus forces - resealing of those. +Misc arguments: + Command specific arguments. + + --secrets-dir=STRING Specifies where to find unencrypted secret files. The given directory is NOT meant to be part + of your source repository! The given path only matters for secrets of type 'path'. Defaults to + the current working directory. + --force-reseal Lets kluctl ignore secret hashes found in already sealed secrets and thus forces resealing of + those. + ``` ## helm-pull -Usage: kluctl helm-pull [OPTIONS] - - Recursively searches for `helm-chart.yml` files and pulls the specified Helm charts. - - The Helm charts are stored under the sub-directory `charts/` next to the `helm-chart.yml`. These Helm - charts are meant to be added to version control so that pulling is only needed when really required (e.g. when the - chart version changes). +Usage: replace-commands-help helm-pull @@ -497,11 +421,7 @@ The following sets of arguments are available: ## helm-update -Usage: kluctl helm-update [OPTIONS] - - Recursively searches for `helm-chart.yml` files and checks for new available versions. - - Optionally performs the actual upgrade and/or add a commit to version control. +Usage: replace-commands-help helm-update @@ -511,62 +431,46 @@ The following sets of arguments are available: In addition, the following arguments are available: ``` - Misc arguments: - --upgrade Write new versions into helm-chart.yml and perform helm-pull afterwards - --commit Create a git commit for every updated chart -``` - - -## list-image-tags - -Usage: kluctl list-image-tags [OPTIONS] - - Queries the tags for the given images. +Misc arguments: + Command specific arguments. - + --upgrade Write new versions into helm-chart.yml and perform helm-pull afterwards + --commit Create a git commit for every updated chart -The following arguments are available: - -``` - Misc arguments: - -o, --output TEXT Specify output target file. Can be specified multiple times - --image TEXT Name of the image. Can be specified multiple times [required] ``` ## list-targets -Usage: kluctl list-targets [OPTIONS] - - Outputs a yaml list with all target, including dynamic targets +Usage: replace-commands-help list-targets The following arguments are available: ``` - Misc arguments: - -o, --output TEXT Specify output target file. Can be specified multiple times +Misc arguments: + Command specific arguments. + + -o, --output=OUTPUT,... Specify output target file. Can be specified multiple times + ``` ## archive -Usage: kluctl archive [OPTIONS] - - Write project and all related components into single tgz. - - This archive can then be used with `--from-archive`. +Usage: replace-commands-help archive The following arguments are available: ``` - Misc arguments: - --output-archive PATH Path to .tgz to write project to. [required] - --output-metadata PATH Path to .yml to write metadata to. If not specified, metadata is written into the - archive. - --reproducible Make archive reproducible. +Misc arguments: + Command specific arguments. + + --output-archive=STRING Path to .tgz to write project to. + --output-metadata=STRING Path to .yml to write metadata to. If not specified, metadata is written into the archive. + ``` diff --git a/hack/replace-commands-help/main.go b/hack/replace-commands-help/main.go new file mode 100644 index 000000000..f95b878ce --- /dev/null +++ b/hack/replace-commands-help/main.go @@ -0,0 +1,148 @@ +package main + +import ( + "bytes" + "flag" + "fmt" + "github.com/alecthomas/kong" + log "github.com/sirupsen/logrus" + "io/ioutil" + "reflect" + "regexp" + "strings" + "syscall" + + "github.com/codablock/kluctl/cmd/kluctl/commands" +) + +var commandsMDPath = flag.String("commands-md", "", "Path to commands.md") + +type section struct { + start int + end int + command string + section string + code bool +} + +func main() { + _ = syscall.Setenv("COLUMNS", "120") + + flag.Parse() + text, err := ioutil.ReadFile(*commandsMDPath) + if err != nil { + log.Fatal(err) + } + lines := strings.Split(string(text), "\n") + + var newLines []string + pos := 0 + for true { + s := findNextSection(lines, pos) + if s == nil { + newLines = append(newLines, lines[pos:]...) + break + } + + newLines = append(newLines, lines[pos:s.start+1]...) + + s2 := getHelpSection(s.command, s.section) + + if s.code { + newLines = append(newLines, "```") + } + newLines = append(newLines, s2...) + if s.code { + newLines = append(newLines, "```") + } + + newLines = append(newLines, lines[s.end]) + pos = s.end + 1 + } + + if !reflect.DeepEqual(lines, newLines) { + err = ioutil.WriteFile(*commandsMDPath, []byte(strings.Join(newLines, "\n")), 0o600) + if err != nil { + log.Fatal(err) + } + } +} + +var beginPattern = regexp.MustCompile(``) +var endPattern = regexp.MustCompile(``) +func findNextSection(lines []string, start int) *section { + + for i := start; i < len(lines); i++ { + m := beginPattern.FindSubmatch([]byte(lines[i])) + if m == nil { + continue + } + + var s section + s.start = i + s.command = string(m[1]) + s.section = string(m[2]) + s.code = string(m[3]) == "true" + + for j := i + 1; j < len(lines); j++ { + m = endPattern.FindSubmatch([]byte(lines[j])) + if m == nil { + continue + } + s.end = j + return &s + } + } + return nil +} + +func countIndent(str string) int { + for i := 0; i < len(str); i++ { + if str[i] != ' ' { + return i + } + } + return 0 +} + +func getHelpSection(command string, section string) []string { + log.Infof("Getting section '%s' from command '%s'", section, command) + + exitFunc := func(x int) { + _ = command + } + + helpBuf := bytes.NewBuffer(nil) + ctx, err := commands.ParseArgs([]string{command, "--help"}, kong.Exit(exitFunc), kong.Writers(helpBuf, helpBuf)) + if err != nil { + log.Fatal(err) + } + _ = ctx + + lines := strings.Split(helpBuf.String(), "\n") + + sectionStart := -1 + for i := 0; i < len(lines); i++ { + indent := countIndent(lines[i]) + if strings.HasPrefix(lines[i][indent:], fmt.Sprintf("%s:", section)) { + sectionStart = i + break + } + } + if sectionStart == -1 { + log.Fatalf("Section %s not found in command %s", section, command) + } + + var ret []string + ret = append(ret, lines[sectionStart]) + sectionIndent := countIndent(lines[sectionStart]) + for i := sectionStart + 1; i < len(lines); i++ { + indent := countIndent(lines[i]) + if lines[i] != "" && indent <= sectionIndent { + break + } + ret = append(ret, lines[i]) + } + return ret +} + diff --git a/scripts/replace-commands-help.py b/scripts/replace-commands-help.py deleted file mode 100644 index 79cdc6902..000000000 --- a/scripts/replace-commands-help.py +++ /dev/null @@ -1,114 +0,0 @@ -import dataclasses -import logging -import os -import re -import subprocess - -logger = logging.getLogger(__name__) -logging.basicConfig(level=logging.INFO) - -commands_md_path = os.path.join(os.path.dirname(__file__), "../docs/commands.md") - -@dataclasses.dataclass -class Section: - start: int - end: int - command: str - section: str - code: bool - -def find_next_section(lines, start): - begin_pattern = re.compile(r"") - end_pattern = re.compile(r"") - - for i in range(start, len(lines)): - m = begin_pattern.match(lines[i]) - if not m: - continue - - command = m.group(1) - section = m.group(2) - code = m.group(3) == "true" - - for j in range(i + 1, len(lines)): - m = end_pattern.match(lines[j]) - if not m: - continue - - return Section(start=i, end=j, command=command, section=section, code=code) - - return None - -def count_indent(str): - indent = 0 - for i in range(len(str)): - if str[i] != " ": - break - indent += 1 - return indent - -def get_help_section(command, section): - logger.info("Getting section '%s' from command '%s'" % (section, command)) - - args = ["kluctl"] - if command: - args += [command] - args += ["--help"] - - env = { - # This is a good value to be rendered into markdown - "COLUMNS": "120", - **os.environ, - } - - r = subprocess.run(args, env=env, capture_output=True, text=True, check=False) - if r.returncode != 0: - logger.error("kluctl call failed with exit code %d\nstdout=%s\nstderr=%s" % (r.returncode, r.stdout, r.stderr)) - raise Exception("kluctl call failed with exit code %d" % r.returncode) - lines = r.stdout.splitlines() - section_start = None - for i in range(len(lines)): - indent = count_indent(lines[i]) - if lines[i][indent:].startswith("%s:" % section): - section_start = i - break - if section_start is None: - raise Exception("Section %s not found in command %s" % (section, command)) - - ret = [lines[section_start] + "\n"] - section_indent = count_indent(lines[section_start]) - for i in range(section_start + 1, len(lines)): - indent = count_indent(lines[i]) - if lines[i] != "" and indent <= section_indent: - break - ret.append(lines[i] + "\n") - return ret - - -with open(commands_md_path) as f: - lines = f.readlines() - -new_lines = [] -pos = 0 -while True: - s = find_next_section(lines, pos) - if s is None: - new_lines += lines[pos:] - break - - new_lines += lines[pos:s.start + 1] - - s2 = get_help_section(s.command, s.section) - - if s.code: - new_lines += ["```\n"] - new_lines += s2 - if s.code: - new_lines += ["```\n"] - - new_lines += [lines[s.end]] - pos = s.end + 1 - -if lines != new_lines: - with open(commands_md_path, mode="w") as f: - f.writelines(new_lines) From 9a6537e7345197fa46ffb889d9bdb4826e598887 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 28 Feb 2022 17:06:33 +0100 Subject: [PATCH 0407/2916] Fix Errorf call --- pkg/utils/versions/latest_version_parse.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/utils/versions/latest_version_parse.go b/pkg/utils/versions/latest_version_parse.go index 8a28c8dca..cf02f81e4 100644 --- a/pkg/utils/versions/latest_version_parse.go +++ b/pkg/utils/versions/latest_version_parse.go @@ -258,7 +258,7 @@ func parseArg(p *preparsed) (*arg, error) { } tok := p.Next() if tok != scanner.String && tok != scanner.Ident && tok != scanner.Int { - return 'e', nil, fmt.Errorf("unexpected token %v, expected string, ident or int") + return 'e', nil, fmt.Errorf("unexpected token %v, expected string, ident or int", tok) } value := p.CurTokenText() if tok == scanner.String { From c649e78f70f0a531ed4fef78602822301cdf2edd Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 22 Feb 2022 17:13:55 +0100 Subject: [PATCH 0408/2916] Reimplement CI on Github Actions --- .github/workflows/build-and-release.yml | 373 +++++++++++++++------- .github/workflows/tests.yml | 228 ------------- .gitignore | 8 +- hack/build-python-unix.sh | 37 +++ hack/build-python-windows.sh | 17 + hack/setup-docker-port-forward.sh | 20 ++ hack/start-kind-cluster.sh | 30 ++ pkg/utils/lib_wrapper/lib_wrapper_unix.go | 1 + 8 files changed, 374 insertions(+), 340 deletions(-) delete mode 100644 .github/workflows/tests.yml create mode 100755 hack/build-python-unix.sh create mode 100755 hack/build-python-windows.sh create mode 100755 hack/setup-docker-port-forward.sh create mode 100755 hack/start-kind-cluster.sh diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index 875406881..7e075fea7 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -1,11 +1,17 @@ -name: build-and-release +name: build on: push: branches: - - 'main' + - '*' tags: - 'v*' + workflow_dispatch: + inputs: + debug_enabled: + description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)' + required: false + default: false jobs: version: @@ -59,148 +65,293 @@ jobs: path: | CHANGELOG.md - docker: - runs-on: ubuntu-latest + build: + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-18.04 + binary-suffix: linux-amd64 + - os: macos-10.15 + binary-suffix: darwin-amd64 + - os: windows-2019 + binary-suffix: windows-amd64 + os: [ubuntu-18.04, macos-10.15, windows-2019] + runs-on: ${{ matrix.os }} needs: - version steps: - name: Checkout uses: actions/checkout@v2 - - name: Set up QEMU - uses: docker/setup-qemu-action@v1 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 - - name: Login to DockerHub - if: github.event_name != 'pull_request' - uses: docker/login-action@v1 + - uses: actions/setup-go@v2 + with: + go-version: '1.17.6' + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3.10.2 + - uses: actions/cache@v2 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + - name: Install dependencies on Linux + if: runner.os == 'Linux' + run: | + echo "deb-src http://archive.ubuntu.com/ubuntu/ bionic main restricted" | sudo tee /etc/apt/sources.list.d/deb-src.list + sudo apt update + sudo apt-get build-dep -y python3 + - name: Install dependencies on Mac + if: runner.os == 'macOS' + run: | + brew install openssl + brew install readline + - name: Cache Python build + id: python-cache + uses: actions/cache@v2 + with: + path: build-python/*/cpython-install + key: build-python-${{ runner.os }}-${{ hashFiles('hack/build-python-*') }} + - name: Build python (non-Windows) + if: runner.os != 'Windows' && steps.python-cache.outputs.cache-hit != 'true' + shell: bash + run: | + ./hack/build-python-unix.sh + - name: Build python (Windows) + if: runner.os == 'Windows' + shell: bash + run: | + ./hack/build-python-windows.sh + - name: Write Version to version.go + run: | + sed -ibak 's/0.0.0/${{ needs.version.outputs.version }}/g' pkg/version/version.go + cat pkg/version/version.go + - name: Go Mod Vendor + shell: bash + run: | + go mod vendor + - name: Go Generate + shell: bash + run: | + go generate ./... + - name: Build kluctl + shell: bash + run: | + go build ./cmd/kluctl + EXE="" + if [ "${{ runner.os }}" = "Windows" ]; then + EXE=".exe" + fi + mv kluctl$EXE kluctl-${{ matrix.binary-suffix }}$EXE + - name: Upload dist artifact + uses: actions/upload-artifact@v2 with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - name: Write Version to _version.py - run: | - echo "__version__ = '${{ needs.version.outputs.version }}'" > kluctl/_version.py - cat kluctl/_version.py - - name: Build and push - id: docker_build - uses: docker/build-push-action@v2 + name: dist-${{ matrix.binary-suffix }} + path: | + kluctl-${{ matrix.binary-suffix }}* + - name: Upload pkg artifact + uses: actions/upload-artifact@v2 with: - context: . - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ needs.version.outputs.tags }} - cache-from: type=gha - cache-to: type=gha,mode=max + name: pkg-${{ matrix.binary-suffix }} + path: | + pkg - pyinstaller: - needs: - - version + tests: strategy: matrix: - os: [ubuntu-18.04, macos-10.15, windows-2016] + include: + - os: ubuntu-18.04 + binary-suffix: linux-amd64 + - os: macos-10.15 + binary-suffix: darwin-amd64 + - os: windows-2019 + binary-suffix: windows-amd64 + os: [ubuntu-18.04, macos-10.15, windows-2019] + fail-fast: false + concurrency: docker.ci.kluctl.io-${{ matrix.binary-suffix }} + needs: + - version + - build runs-on: ${{ matrix.os }} steps: - name: Checkout uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 + - uses: actions/setup-go@v2 with: - python-version: 3.10.0 - - name: Write Version to _version.py + go-version: '1.17.6' + - uses: actions/cache@v2 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + - name: Setup TOOLS envs + shell: bash run: | - echo "__version__ = '${{ needs.version.outputs.version }}'" > kluctl/_version.py - cat kluctl/_version.py - - name: Install libyaml-dev on Linux - if: runner.os == 'Linux' + if [ "${{ runner.os }}" != "Windows" ]; then + echo "SUDO=sudo" >> $GITHUB_ENV + fi + + TOOLS_EXE= + TOOLS_TARGET_DIR=$GITHUB_WORKSPACE/bin + mkdir $TOOLS_TARGET_DIR + + if [ "${{ runner.os }}" == "macOS" ]; then + TOOLS_OS=darwin + elif [ "${{ runner.os }}" == "Windows" ]; then + TOOLS_OS=windows + TOOLS_EXE=.exe + else + TOOLS_OS=linux + fi + echo "TOOLS_OS=$TOOLS_OS" >> $GITHUB_ENV + echo "TOOLS_TARGET_DIR=$TOOLS_TARGET_DIR" >> $GITHUB_ENV + echo "$TOOLS_TARGET_DIR" >> $GITHUB_PATH + - name: "[Windows] Install openssh" + if: runner.os == 'Windows' + shell: bash run: | - sudo apt update - sudo apt install libyaml-dev binutils gcc -y - pip install "pyyaml==5.4.1" --global-option=--with-libyaml - - name: Install libyaml on MacOS - if: runner.os == 'macOS' + choco install openssh + # Enable tmate debugging of manually-triggered workflows if the input option was provided + - name: Setup tmate session + uses: mxschmitt/action-tmate@v3 + if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }} + with: + limit-access-to-actor: true + - name: Provide required tools versions + shell: bash run: | - brew install libyaml - pip install "pyyaml==5.4.1" --global-option=--with-libyaml - - name: Install kluctl + echo "KUBECTL_VERSION=1.21.5" >> $GITHUB_ENV + echo "KIND_VERSION=0.11.1" >> $GITHUB_ENV + echo "HELM_VERSION=3.6.3" >> $GITHUB_ENV + echo "DOCKER_VERSION=20.10.9" >> $GITHUB_ENV + - name: Download required tools + shell: bash run: | - pip install . - pip install -r requirements-dev.txt - - name: Run Pyinstaller + curl -L -o kubectl$TOOLS_EXE https://storage.googleapis.com/kubernetes-release/release/v$KUBECTL_VERSION/bin/${TOOLS_OS}/amd64/kubectl$TOOLS_EXE && \ + $SUDO mv kubectl$TOOLS_EXE "$TOOLS_TARGET_DIR/" + curl -L -o kind$TOOLS_EXE https://github.com/kubernetes-sigs/kind/releases/download/v${KIND_VERSION}/kind-${TOOLS_OS}-amd64 && \ + $SUDO mv kind$TOOLS_EXE "$TOOLS_TARGET_DIR/" + curl -L -o helm.tar.gz https://get.helm.sh/helm-v$HELM_VERSION-${TOOLS_OS}-amd64.tar.gz && \ + tar xzf helm.tar.gz && \ + $SUDO mv ${TOOLS_OS}-amd64/helm$TOOLS_EXE "$TOOLS_TARGET_DIR/" + if [ "${{ runner.os }}" == "macOS" ]; then + curl -L -o docker.tar.gz https://download.docker.com/mac/static/stable/x86_64/docker-$DOCKER_VERSION.tgz + tar xzf docker.tar.gz + $SUDO mv docker/docker "$TOOLS_TARGET_DIR/" + rm -rf docker + elif [ "${{ runner.os }}" == "Windows" ]; then + curl -L -o docker.zip https://download.docker.com/win/static/stable/x86_64/docker-$DOCKER_VERSION.zip + unzip docker.zip + mv docker/docker.exe "$TOOLS_TARGET_DIR/" + rm -rf docker + fi + $SUDO chmod -R +x "$TOOLS_TARGET_DIR/" + - name: Test required tools + shell: bash run: | - pyinstaller kluctl.spec - - name: Upload artifacts - uses: actions/upload-artifact@v2 + kubectl version || true + kind version || true + helm version || true + - name: Download pkg artifacts + uses: actions/download-artifact@v2 with: - name: dist-binary-${{ matrix.os }} - path: | - dist + name: pkg-${{ matrix.binary-suffix }} + path: pkg + - name: Port-forward docker + shell: bash + run: | + echo "${{ secrets.CI_SSH_KEY }}" > kluctl-ci.pem + chmod og-rwx kluctl-ci.pem + ls -lah kluctl-ci.pem + ./hack/setup-docker-port-forward.sh + echo "DOCKER_HOST=localhost:2375" >> $GITHUB_ENV + - name: Start kind cluster + shell: bash + run: | + if [ "${{ runner.os }}" == "Linux" ]; then + PORT=10000 + elif [ "${{ runner.os }}" == "Windows" ]; then + PORT=10001 + else + PORT=10002 + fi + KIND_CLUSTER_NAME=$(echo "kluctl-${{ runner.os }}" | awk '{{ print tolower($1)}}') + echo "KIND_CLUSTER_NAME=$KIND_CLUSTER_NAME" >> $GITHUB_ENV + + ./hack/start-kind-cluster.sh "$KIND_CLUSTER_NAME" "$PORT" + - name: Go Mod Vendor + shell: bash + run: | + go mod vendor + - name: Run tests + shell: bash + run: | + go test ./cmd/... ./pkg/... -v + go test ./e2e/... -kind-cluster-name=$KIND_CLUSTER_NAME -kind-kubeconfig=$(pwd)/kind-kubeconfig -parallel 4 -v + - name: Delete kind cluster + shell: bash + run: | + kind delete cluster --name $KIND_CLUSTER_NAME + - name: Check docs + shell: bash + run: | + go run ./hack/replace-commands-help --commands-md ./docs/commands.md - publish_pypi: + if [ "$(git status --porcelain -- docs)" != "" ]; then + echo "commands.md is not up-to-date. Run "./hack/replace-commands-help --commands-md ./docs/commands.md" to fix this!" + exit 1 + fi + check-code: + runs-on: ubuntu-latest needs: - - version - runs-on: ubuntu-18.04 + - build steps: + - name: Install packages + run: | + sudo apt update + sudo apt install -y git - name: Checkout uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v2 with: - python-version: 3.10.0 - - name: Write Version to _version.py - run: | - echo "__version__ = '${{ needs.version.outputs.version }}'" > kluctl/_version.py - cat kluctl/_version.py - - name: Install pypa/build - run: >- - python -m - pip install - build - --user - - name: Build a binary wheel and a source tarball - run: >- - python -m - build - --sdist - --wheel - --outdir dist/ - . - # TODO this fails atm due to invalid version numbers (the git hash...) - #- name: Publish distribution 📦 to Test PyPI - # uses: pypa/gh-action-pypi-publish@release/v1 - # with: - # user: __token__ - # password: ${{ secrets.TEST_PYPI_API_TOKEN }} - # repository_url: https://test.pypi.org/legacy/ - - name: Publish distribution 📦 to PyPI - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') - uses: pypa/gh-action-pypi-publish@release/v1 - with: - user: __token__ - password: ${{ secrets.PYPI_API_TOKEN }} + python-version: 3.10.2 + - name: Download artifacts + uses: actions/download-artifact@v2 + - name: check-docs + run: | + #python scripts/replace-commands-help.py + #if [ "$(git status --porcelain)" != "" ]; then + # echo "commands.md is not up-to-date. Run ./scripts/replace-commands-help.py to fix this!" + # exit 1 + #fi release: runs-on: ubuntu-latest if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') needs: - version - - docker - - pyinstaller + - build + - check-code + - tests steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Download artifacts - uses: actions/download-artifact@v2 - - name: find - run: | - find . - - name: Prepare release-files - run: | - mkdir dist - mv dist-binary-ubuntu-18.04/kluctl dist/kluctl-linux-amd64 - mv dist-binary-macos-10.15/kluctl dist/kluctl-darwin-amd64 - mv dist-binary-windows-2016/kluctl.exe dist/kluctl-windows-amd64.exe - - name: Release - uses: softprops/action-gh-release@v1 - with: - draft: false - body_path: changelog/CHANGELOG.md - files: | - dist/* + - name: Checkout + uses: actions/checkout@v2 + - name: Download changelog artifacts + uses: actions/download-artifact@v2 + with: + name: changelog + - name: Download dist artifacts + uses: actions/download-artifact@v2 + - name: Release + uses: softprops/action-gh-release@v1 + with: + draft: false + prerelease: true + body_path: changelog/CHANGELOG.md + files: | + kluctl-linux-* + kluctl-darwin-* + kluctl-windows-* diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index f31160ad6..000000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,228 +0,0 @@ -name: tests - -on: - push: - branches: - - '*' - pull_request: - branches: - - 'main' - workflow_dispatch: - inputs: - debug_enabled: - description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)' - required: false - default: false - -jobs: - tests: - strategy: - matrix: - os: [ubuntu-18.04, macos-10.15, windows-2016] - fail-fast: false - concurrency: docker.ci.kluctl.io-${{ matrix.os }} - runs-on: ${{ matrix.os }} - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Setup TOOLS envs - shell: bash - run: | - if [ "${{ runner.os }}" != "Windows" ]; then - echo "SUDO=sudo" >> $GITHUB_ENV - fi - - TOOLS_EXE= - TOOLS_TARGET_DIR=$GITHUB_WORKSPACE/bin - mkdir $TOOLS_TARGET_DIR - - if [ "${{ runner.os }}" == "macOS" ]; then - TOOLS_OS=darwin - elif [ "${{ runner.os }}" == "Windows" ]; then - TOOLS_OS=windows - TOOLS_EXE=.exe - else - TOOLS_OS=linux - fi - echo "TOOLS_OS=$TOOLS_OS" >> $GITHUB_ENV - echo "TOOLS_EXE=$TOOLS_EXE" >> $GITHUB_ENV - echo "TOOLS_TARGET_DIR=$TOOLS_TARGET_DIR" >> $GITHUB_ENV - echo "$TOOLS_TARGET_DIR" >> $GITHUB_PATH - - name: "[Windows] Install openssh" - if: runner.os == 'Windows' - shell: bash - run: | - choco install openssh - - name: Port-forward docker - shell: bash - run: | - echo "${{ secrets.CI_SSH_KEY }}" > kluctl-ci.pem - chmod og-rwx kluctl-ci.pem - echo "Forwarding ports" - - # pre-create this to avoid races in the background ssh calls - mkdir -p $HOME/.ssh - - # docker - nohup /usr/bin/ssh -i kluctl-ci.pem -o StrictHostKeyChecking=no -L2375:/run/docker.sock -N kluctl-ci@docker.ci.kluctl.io &> ssh-log-2375 & - - # keep ports alive - nohup bash -c "while true; do curl http://localhost:2375 &> /dev/null ; sleep 5; done" & - - echo "DOCKER_HOST=localhost:2375" >> $GITHUB_ENV - # Enable tmate debugging of manually-triggered workflows if the input option was provided - - name: Setup tmate session - uses: mxschmitt/action-tmate@v3 - if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }} - with: - limit-access-to-actor: true - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: 3.10.0 - - uses: actions/cache@v2 - with: - path: .venv - key: ${{ matrix.os }}-${{ hashFiles('setup.py') }}-${{ hashFiles('requirements-dev.txt') }} - - name: Setup virtualenv - shell: bash - run: | - if [ ! -d .venv ]; then - pip install virtualenv - virtualenv .venv - fi - if [ "${{ runner.os }}" == "Windows" ]; then - echo "$GITHUB_WORKSPACE\\.venv\\Scripts" >> $GITHUB_PATH - else - echo "$GITHUB_WORKSPACE/.venv/bin" >> $GITHUB_PATH - fi - - name: Install kluctl and dependencies - shell: bash - run: | - python ./setup.py develop - pip install -r requirements-dev.txt - - name: Create pyinstaller executable - shell: bash - run: | - pyinstaller kluctl.spec - OLD_KLUCTL=$(which kluctl) - echo OLD_KLUCTL=$OLD_KLUCTL - cp dist/* $TOOLS_TARGET_DIR - which kluctl - rm $OLD_KLUCTL - which kluctl - - name: Provide required tools versions - shell: bash - run: | - echo "KUBECTL_VERSION=1.21.5" >> $GITHUB_ENV - echo "KIND_VERSION=0.11.1" >> $GITHUB_ENV - echo "KUSTOMIZE_VERSION=4.4.1" >> $GITHUB_ENV - echo "HELM_VERSION=3.6.3" >> $GITHUB_ENV - echo "KUBESEAL_VERSION=0.16.0" >> $GITHUB_ENV - echo "DOCKER_VERSION=20.10.9" >> $GITHUB_ENV - - name: Download required tools - shell: bash - run: | - curl -L -o kubectl$TOOLS_EXE https://storage.googleapis.com/kubernetes-release/release/v$KUBECTL_VERSION/bin/${TOOLS_OS}/amd64/kubectl$TOOLS_EXE && \ - $SUDO mv kubectl$TOOLS_EXE "$TOOLS_TARGET_DIR/" - curl -L -o kind$TOOLS_EXE https://github.com/kubernetes-sigs/kind/releases/download/v${KIND_VERSION}/kind-${TOOLS_OS}-amd64 && \ - $SUDO mv kind$TOOLS_EXE "$TOOLS_TARGET_DIR/" - curl -L -o kustomize.tar.gz https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2Fv${KUSTOMIZE_VERSION}/kustomize_v${KUSTOMIZE_VERSION}_${TOOLS_OS}_amd64.tar.gz && \ - tar xzf kustomize.tar.gz && \ - $SUDO mv kustomize$TOOLS_EXE "$TOOLS_TARGET_DIR/" - curl -L -o helm.tar.gz https://get.helm.sh/helm-v$HELM_VERSION-${TOOLS_OS}-amd64.tar.gz && \ - tar xzf helm.tar.gz && \ - $SUDO mv ${TOOLS_OS}-amd64/helm$TOOLS_EXE "$TOOLS_TARGET_DIR/" - KUBESEAL_EXE=kubeseal-${TOOLS_OS}-amd64 - if [ "${{ runner.os }}" == "Windows" ]; then - KUBESEAL_EXE=kubeseal.exe - fi - curl -L -o kubeseal$TOOLS_EXE https://github.com/bitnami-labs/sealed-secrets/releases/download/v$KUBESEAL_VERSION/$KUBESEAL_EXE && \ - $SUDO mv kubeseal$TOOLS_EXE "$TOOLS_TARGET_DIR/" - if [ "${{ runner.os }}" == "macOS" ]; then - curl -L -o docker.tar.gz https://download.docker.com/mac/static/stable/x86_64/docker-$DOCKER_VERSION.tgz - tar xzf docker.tar.gz - $SUDO mv docker/docker "$TOOLS_TARGET_DIR/" - rm -rf docker - elif [ "${{ runner.os }}" == "Windows" ]; then - curl -L -o docker.zip https://download.docker.com/win/static/stable/x86_64/docker-$DOCKER_VERSION.zip - unzip docker.zip - mv docker/docker.exe "$TOOLS_TARGET_DIR/" - rm -rf docker - fi - $SUDO chmod -R +x "$TOOLS_TARGET_DIR/" - - name: Test required tools - shell: bash - run: | - kubectl version || true - kind version || true - kustomize version || true - helm version || true - kubeseal --version || true - - name: Start kind cluster - shell: bash - run: | - if [ "${{ runner.os }}" == "Linux" ]; then - PORT=10000 - elif [ "${{ runner.os }}" == "Windows" ]; then - PORT=10001 - else - PORT=10002 - fi - cat << EOF > kind-cluster.yml - kind: Cluster - apiVersion: kind.x-k8s.io/v1alpha4 - networking: - apiServerAddress: "0.0.0.0" - apiServerPort: $PORT - EOF - KIND_CLUSTER_NAME=$(echo "pytest-${{ runner.os }}" | awk '{{ print tolower($1)}}') - echo "KIND_CLUSTER_NAME=$KIND_CLUSTER_NAME" >> $GITHUB_ENV - - export KUBECONFIG=$GITHUB_WORKSPACE/kind-kubeconfig - kind delete cluster --name $KIND_CLUSTER_NAME || true - kind create cluster --config kind-cluster.yml --name $KIND_CLUSTER_NAME - - # Rewrite cluster info to point to docker.ci.kluctl.io - # This also fully disables TLS verification - IP=$(nslookup docker.ci.kluctl.io | grep Address | tail -n1 | sed 's/Address://g' | awk '{print $1}') - echo IP=$IP - kubectl config view -ojson --raw \ - | jq ".clusters[0].cluster.\"insecure-skip-tls-verify\"=true" \ - | jq "del(.clusters[0].cluster.\"certificate-authority-data\")" \ - | jq ".clusters[0].cluster.server=\"https://$IP:$PORT\"" \ - > kind-kubeconfig2 - mv kind-kubeconfig2 kind-kubeconfig - - name: Run tests - shell: bash - run: | - TOOLS_TARGET_DIR=$(echo $TOOLS_TARGET_DIR | sed 's|\\|/|g') - export PYTEST_ADDOPTS="--cluster-name=$KIND_CLUSTER_NAME --kubeconfig=kind-kubeconfig --kind-bin=$TOOLS_TARGET_DIR/kind$TOOLS_EXE --kind-kubectl-bin=$TOOLS_TARGET_DIR/kubectl$TOOLS_EXE --keep-cluster" - pytest -n4 - - name: Delete kind cluster - shell: bash - run: | - kind delete cluster --name $KIND_CLUSTER_NAME - check-code: - runs-on: ubuntu-latest - steps: - - name: Install packages - run: | - sudo apt update - sudo apt install -y git - - name: Checkout - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: 3.10.0 - - name: pip install - run: | - pip install . - - name: check-docs - run: | - python scripts/replace-commands-help.py - if [ "$(git status --porcelain)" != "" ]; then - echo "commands.md is not up-to-date. Run ./scripts/replace-commands-help.py to fix this!" - exit 1 - fi diff --git a/.gitignore b/.gitignore index 6e8729afc..a0d685139 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,13 @@ +.DS_Store + .idea +.python-version + *.kubeconfig .secrets.yml .sealed-secrets /vendor -/kluctl \ No newline at end of file +/build-python +/kluctl +/kluctl.exe diff --git a/hack/build-python-unix.sh b/hack/build-python-unix.sh new file mode 100755 index 000000000..ca2b2354c --- /dev/null +++ b/hack/build-python-unix.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash + +set -e + +DIR=$(cd $(dirname $0) && pwd) +cd $DIR/.. + +case "$(uname -s)" in + Linux*) os=linux;; + Darwin*) os=darwin;; + MINGW*) os=windows;; + *) echo "unknown os"; exit 1; +esac + +mkdir -p build-python/$os +cd build-python/$os + +PYTHON_VERSION=3.10.2 + +if [ ! -d cpython ]; then + git clone -bv$PYTHON_VERSION --single-branch --depth 1 https://github.com/python/cpython.git cpython +fi + +cd cpython + +if [ "$os" = "darwin" ]; then + #export PKG_CONFIG_PATH="$(brew --prefix openssl)/lib/pkgconfig" + export CPPFLAGS="-I$(brew --prefix readline)/include" + export LDFLAGS="-L$(brew --prefix readline)/lib" +fi +./configure $CONFIGURE_FLAGS --enable-shared --prefix $DIR/../build-python/$os/cpython-install +make -j4 +make install + +cd .. +cd cpython-install +tar czf $DIR/../pkg/python/python-lib-$os.tar.gz lib diff --git a/hack/build-python-windows.sh b/hack/build-python-windows.sh new file mode 100755 index 000000000..9e2afd3b2 --- /dev/null +++ b/hack/build-python-windows.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +set -e + +DIR=$(cd $(dirname $0) && pwd) +cd $DIR/.. + +mkdir -p build-python/windows +cd build-python/windows + +PYTHON_VERSION=3.10.2 + +curl -L -o python.zip https://www.python.org/ftp/python/$PYTHON_VERSION/python-$PYTHON_VERSION-embed-amd64.zip +unzip -o python.zip +rm python.zip + +tar czf $DIR/../pkg/python/python-lib-windows.tar.gz * diff --git a/hack/setup-docker-port-forward.sh b/hack/setup-docker-port-forward.sh new file mode 100755 index 000000000..886ed268f --- /dev/null +++ b/hack/setup-docker-port-forward.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +set -e + +echo "Forwarding ports" + +# pre-create this to avoid races in the background ssh calls +mkdir -p $HOME/.ssh + +# docker +tail -F ssh-log-2375 & +nohup /usr/bin/ssh -i kluctl-ci.pem -o StrictHostKeyChecking=no -L2375:/run/docker.sock -N kluctl-ci@docker.ci.kluctl.io &> ssh-log-2375 & + +while ! curl http://localhost:2375 &> /dev/null; do + echo "Waiting for ports to get available..." + sleep 5 +done + +# keep ports alive +nohup bash -c "while true; do curl http://localhost:2375 &> /dev/null ; sleep 5; done" & diff --git a/hack/start-kind-cluster.sh b/hack/start-kind-cluster.sh new file mode 100755 index 000000000..cdda4795c --- /dev/null +++ b/hack/start-kind-cluster.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +set -e + +NAME=$1 +PORT=$2 + +cat << EOF > kind-cluster.yml +kind: Cluster +apiVersion: kind.x-k8s.io/v1alpha4 +networking: + apiServerAddress: "0.0.0.0" + apiServerPort: $PORT +EOF + +rm -f $(pwd)/kind-kubeconfig +export KUBECONFIG=$(pwd)/kind-kubeconfig +kind delete cluster --name $NAME || true +kind create cluster --config kind-cluster.yml --name $NAME + +# Rewrite cluster info to point to docker.ci.kluctl.io +# This also fully disables TLS verification +IP=$(nslookup docker.ci.kluctl.io | grep Address | tail -n1 | sed 's/Address://g' | awk '{print $1}') +echo IP=$IP +kubectl config view -ojson --raw \ + | jq ".clusters[0].cluster.\"insecure-skip-tls-verify\"=true" \ + | jq "del(.clusters[0].cluster.\"certificate-authority-data\")" \ + | jq ".clusters[0].cluster.server=\"https://$IP:$PORT\"" \ +> kind-kubeconfig2 +mv kind-kubeconfig2 kind-kubeconfig diff --git a/pkg/utils/lib_wrapper/lib_wrapper_unix.go b/pkg/utils/lib_wrapper/lib_wrapper_unix.go index 5ac3280c3..339965c36 100644 --- a/pkg/utils/lib_wrapper/lib_wrapper_unix.go +++ b/pkg/utils/lib_wrapper/lib_wrapper_unix.go @@ -4,6 +4,7 @@ package lib_wrapper /* +#cgo LDFLAGS: -ldl #include #include */ From 1105a57c4108402a64b5667cc7d5f58d0177758a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 1 Mar 2022 11:19:12 +0100 Subject: [PATCH 0409/2916] Set author/committer in test git repos --- e2e/project.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/e2e/project.go b/e2e/project.go index a33413b5c..b2fc6dcfd 100644 --- a/e2e/project.go +++ b/e2e/project.go @@ -106,6 +106,8 @@ func (p *testProject) gitInit(dir string) { config.User.Name = "Test User" config.User.Email = "no@mail.com" + config.Author = config.User + config.Committer = config.User err = utils.Touch(path.Join(dir, ".dummy")) if err != nil { p.t.Fatal(err) From 2134612b1dfcb8c3cfe497a033eae241df911ace Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 1 Mar 2022 11:29:46 +0100 Subject: [PATCH 0410/2916] Fix typo --- pkg/deployment/deployment_item.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 853971985..14f5c1f84 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -253,7 +253,7 @@ func (di *deploymentItem) checkInclusionForDelete() bool { return di.collection.inclusion.CheckIncluded(values, skipDeleteIfTags) } -func (di *deploymentItem) prepareKusomizationYaml() error { +func (di *deploymentItem) prepareKustomizationYaml() error { if di.dir == nil { return nil } @@ -289,7 +289,7 @@ func (di *deploymentItem) buildKustomize() error { return nil } - err := di.prepareKusomizationYaml() + err := di.prepareKustomizationYaml() if err != nil { return err } From db2f8e7fdc672d5d39980bb45740c1f965b6a5fd Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 1 Mar 2022 11:34:22 +0100 Subject: [PATCH 0411/2916] Actually store the git config --- e2e/project.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/e2e/project.go b/e2e/project.go index b2fc6dcfd..ad387ba58 100644 --- a/e2e/project.go +++ b/e2e/project.go @@ -108,6 +108,10 @@ func (p *testProject) gitInit(dir string) { config.User.Email = "no@mail.com" config.Author = config.User config.Committer = config.User + err = r.SetConfig(config) + if err != nil { + p.t.Fatal(err) + } err = utils.Touch(path.Join(dir, ".dummy")) if err != nil { p.t.Fatal(err) From e07530a19fe9de0ef675b601d316c8ef74e875b3 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 1 Mar 2022 11:49:37 +0100 Subject: [PATCH 0412/2916] Split tests and e2e tests --- .github/workflows/build-and-release.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index 7e075fea7..2c5a82d63 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -289,6 +289,9 @@ jobs: shell: bash run: | go test ./cmd/... ./pkg/... -v + - name: Run e2e tests + shell: bash + run: | go test ./e2e/... -kind-cluster-name=$KIND_CLUSTER_NAME -kind-kubeconfig=$(pwd)/kind-kubeconfig -parallel 4 -v - name: Delete kind cluster shell: bash From a44a8e34e7612396a92200715c1e16b846a166b2 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 1 Mar 2022 12:08:57 +0100 Subject: [PATCH 0413/2916] Pass kind name and kubeconfig via env --- .github/workflows/build-and-release.yml | 3 ++- e2e/kind_cluster.go | 14 +++++++++----- hack/start-kind-cluster.sh | 2 +- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index 2c5a82d63..88b6583d1 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -279,6 +279,7 @@ jobs: fi KIND_CLUSTER_NAME=$(echo "kluctl-${{ runner.os }}" | awk '{{ print tolower($1)}}') echo "KIND_CLUSTER_NAME=$KIND_CLUSTER_NAME" >> $GITHUB_ENV + echo "KIND_KUBECONFIG=$(pwd)/kind-kubeconfig" >> $GITHUB_ENV ./hack/start-kind-cluster.sh "$KIND_CLUSTER_NAME" "$PORT" - name: Go Mod Vendor @@ -292,7 +293,7 @@ jobs: - name: Run e2e tests shell: bash run: | - go test ./e2e/... -kind-cluster-name=$KIND_CLUSTER_NAME -kind-kubeconfig=$(pwd)/kind-kubeconfig -parallel 4 -v + go test ./e2e/... -parallel 4 -v - name: Delete kind cluster shell: bash run: | diff --git a/e2e/kind_cluster.go b/e2e/kind_cluster.go index 28f6a6686..f62d7c397 100644 --- a/e2e/kind_cluster.go +++ b/e2e/kind_cluster.go @@ -1,7 +1,6 @@ package e2e import ( - "flag" "fmt" "github.com/codablock/kluctl/pkg/utils" "github.com/codablock/kluctl/pkg/utils/uo" @@ -149,11 +148,16 @@ func createKindCluster(name string, kubeconfig string) *KindCluster { return k } -var kindName = flag.String("kind-cluster-name", "kluctl-e2e", "Kind cluster name to use/create") -var kindKubeconfig = flag.String("kind-kubeconfig", path.Join(utils.GetTmpBaseDir(), "kluctl-e2e-kubeconfig.yml"), "Kind kubeconfig to use/create") - func createDefaultKindCluster() *KindCluster { - return createKindCluster(*kindName, *kindKubeconfig) + kindClusterName := os.Getenv("KIND_CLUSTER_NAME") + kindKubeconfig := os.Getenv("KIND_KUBECONFIG") + if kindClusterName == "" { + kindClusterName = "kluctl-e2e" + } + if kindKubeconfig == "" { + kindKubeconfig = path.Join(utils.GetTmpBaseDir(), "kluctl-e2e-kubeconfig.yml") + } + return createKindCluster(kindClusterName, kindKubeconfig) } var defaultKindCluster = createDefaultKindCluster() diff --git a/hack/start-kind-cluster.sh b/hack/start-kind-cluster.sh index cdda4795c..c3e2aa406 100755 --- a/hack/start-kind-cluster.sh +++ b/hack/start-kind-cluster.sh @@ -14,7 +14,7 @@ networking: EOF rm -f $(pwd)/kind-kubeconfig -export KUBECONFIG=$(pwd)/kind-kubeconfig +export KUBECONFIG=$KIND_KUBECONFIG kind delete cluster --name $NAME || true kind create cluster --config kind-cluster.yml --name $NAME From 1750e24c0c53b6b7f411951e160323c95c188325 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 1 Mar 2022 12:34:09 +0100 Subject: [PATCH 0414/2916] Fix Panicf call --- pkg/utils/lib_wrapper/lib_wrapper_windows.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/utils/lib_wrapper/lib_wrapper_windows.go b/pkg/utils/lib_wrapper/lib_wrapper_windows.go index dcebcbf65..8020e53bf 100644 --- a/pkg/utils/lib_wrapper/lib_wrapper_windows.go +++ b/pkg/utils/lib_wrapper/lib_wrapper_windows.go @@ -13,7 +13,7 @@ import ( func LoadModule(pth string) *LibWrapper { mod, err := syscall.LoadLibrary(pth) if err != nil { - log.Panicf("LoadLibrary for %s failed: %w", pth, err) + log.Panicf("LoadLibrary for %s failed: %v", pth, err) } return &LibWrapper{ module: unsafe.Pointer(mod), From 1f38d3b19b9d0f657ad21e797983c2b0b1783242 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 1 Mar 2022 14:19:48 +0100 Subject: [PATCH 0415/2916] Use RTLD_GLOBAL wen calling dlopen --- hack/build-python-unix.sh | 1 - pkg/utils/lib_wrapper/lib_wrapper_unix.go | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/hack/build-python-unix.sh b/hack/build-python-unix.sh index ca2b2354c..5b8bc3ff1 100755 --- a/hack/build-python-unix.sh +++ b/hack/build-python-unix.sh @@ -24,7 +24,6 @@ fi cd cpython if [ "$os" = "darwin" ]; then - #export PKG_CONFIG_PATH="$(brew --prefix openssl)/lib/pkgconfig" export CPPFLAGS="-I$(brew --prefix readline)/include" export LDFLAGS="-L$(brew --prefix readline)/lib" fi diff --git a/pkg/utils/lib_wrapper/lib_wrapper_unix.go b/pkg/utils/lib_wrapper/lib_wrapper_unix.go index 339965c36..bb96aff1f 100644 --- a/pkg/utils/lib_wrapper/lib_wrapper_unix.go +++ b/pkg/utils/lib_wrapper/lib_wrapper_unix.go @@ -18,7 +18,7 @@ func LoadModule(pth string) *LibWrapper { cPth := C.CString(pth) defer C.free(unsafe.Pointer(cPth)) - mod := C.dlopen(cPth, C.RTLD_LAZY) + mod := C.dlopen(cPth, C.RTLD_LAZY | C.RTLD_GLOBAL) if mod == nil { log.Panicf("dlopen for %s failed", pth) } From 82db23c012af15dba74a484aa03192925a5a1e66 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 1 Mar 2022 15:23:28 +0100 Subject: [PATCH 0416/2916] Fix invalid Py_DecodeLocale wrapper --- pkg/python/lib.go | 2 +- pkg/python/libpython_wrapper.go | 8 ++++++-- pkg/python/libpython_wrapper_impl.go | 11 ++++++----- pkg/python/list.go | 4 ++-- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/pkg/python/lib.go b/pkg/python/lib.go index b0a0d31d3..4ed630fe4 100644 --- a/pkg/python/lib.go +++ b/pkg/python/lib.go @@ -52,7 +52,7 @@ func init() { pythonModule = lib_wrapper.LoadModule(filepath.Join(libPath, module)) PythonWrapper = New_LibPythonWrapper(pythonModule) - l := PythonWrapper.Py_DecodeLocale(libPath) + l := PythonWrapper.Py_DecodeLocale(libPath, nil) PythonWrapper.Py_SetPythonHome(l) PythonWrapper.Py_InitializeEx(0) diff --git a/pkg/python/libpython_wrapper.go b/pkg/python/libpython_wrapper.go index 7b71d5e0a..368aefbf7 100644 --- a/pkg/python/libpython_wrapper.go +++ b/pkg/python/libpython_wrapper.go @@ -2,14 +2,18 @@ package python import "unsafe" -type ssize_t = int +type ssize_t uint + +func (s *ssize_t) GetPointer() unsafe.Pointer { + return unsafe.Pointer(s) +} //go:generate go run .../lib_wrapper/gen --input libpython_wrapper.go --output libpython_wrapper_impl.go type LibPythonWrapper interface { Py_Initialize() Py_InitializeEx(initsigs int) - Py_DecodeLocale(s string) unsafe.Pointer + Py_DecodeLocale(s string, size *ssize_t) unsafe.Pointer Py_SetPythonHome(l unsafe.Pointer) Py_NewInterpreter() *PythonThreadState diff --git a/pkg/python/libpython_wrapper_impl.go b/pkg/python/libpython_wrapper_impl.go index 8bcff00b8..f49703ef2 100644 --- a/pkg/python/libpython_wrapper_impl.go +++ b/pkg/python/libpython_wrapper_impl.go @@ -12,9 +12,9 @@ void _trampoline_Py_InitializeEx(void* f, int initsigs) { typedef void (*F)(int); ((F)f)(initsigs); } -void* _trampoline_Py_DecodeLocale(void* f, char* s) { - typedef void* (*F)(char*); - return ((F)f)(s); +void* _trampoline_Py_DecodeLocale(void* f, char* s, void* size) { + typedef void* (*F)(char*, void*); + return ((F)f)(s, size); } void _trampoline_Py_SetPythonHome(void* f, void* l) { typedef void (*F)(void*); @@ -205,10 +205,11 @@ func (w *LibPythonWrapperImpl) Py_InitializeEx(initsigs int) { _c_initsigs := C.int(initsigs) C._trampoline_Py_InitializeEx(unsafe.Pointer(w._func_Py_InitializeEx), _c_initsigs) } -func (w *LibPythonWrapperImpl) Py_DecodeLocale(s string) unsafe.Pointer { +func (w *LibPythonWrapperImpl) Py_DecodeLocale(s string, size *ssize_t) unsafe.Pointer { _c_s := C.CString(s) defer C.free(unsafe.Pointer(_c_s)) - _g_ret := unsafe.Pointer(C._trampoline_Py_DecodeLocale(unsafe.Pointer(w._func_Py_DecodeLocale), _c_s)) + _c_size := (size).GetPointer() + _g_ret := unsafe.Pointer(C._trampoline_Py_DecodeLocale(unsafe.Pointer(w._func_Py_DecodeLocale), _c_s, _c_size)) return _g_ret } func (w *LibPythonWrapperImpl) Py_SetPythonHome(l unsafe.Pointer) { diff --git a/pkg/python/list.go b/pkg/python/list.go index d4b21b3b4..56015d4d0 100644 --- a/pkg/python/list.go +++ b/pkg/python/list.go @@ -5,7 +5,7 @@ import "unsafe" type PyList = PyObject func PyList_New(len int) *PyList { - return PythonWrapper.PyList_New(len) + return PythonWrapper.PyList_New(ssize_t(len)) } func PyList_FromPointer(p unsafe.Pointer) *PyList { @@ -20,7 +20,7 @@ func PyList_FromObject(l *PyObject) *PyList { } func (l *PyList) GetItem(pos int) *PyObject { - return PythonWrapper.PyList_GetItem(l, pos) + return PythonWrapper.PyList_GetItem(l, ssize_t(pos)) } func (l *PyList) Append(item *PyObject) int { From ef355a8700def3bd679358d55c59af77a7794e8f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 1 Mar 2022 15:56:18 +0100 Subject: [PATCH 0417/2916] Use filepath instead of path in e2e tests --- e2e/inclusion_test.go | 4 ++-- e2e/kind_cluster.go | 4 ++-- e2e/project.go | 51 +++++++++++++++++++++---------------------- 3 files changed, 29 insertions(+), 30 deletions(-) diff --git a/e2e/inclusion_test.go b/e2e/inclusion_test.go index 259f14831..c7502a361 100644 --- a/e2e/inclusion_test.go +++ b/e2e/inclusion_test.go @@ -2,7 +2,7 @@ package e2e import ( "fmt" - "path" + "path/filepath" "reflect" "testing" ) @@ -174,7 +174,7 @@ func TestInclusionDeploymentDirs(t *testing.T) { var a []string for _, x := range p.listDeploymentItemPathes(".", false) { if x != "icm5" { - a = append(a, path.Base(x)) + a = append(a, filepath.Base(x)) } } doAssertExists(a...) diff --git a/e2e/kind_cluster.go b/e2e/kind_cluster.go index f62d7c397..8db400c58 100644 --- a/e2e/kind_cluster.go +++ b/e2e/kind_cluster.go @@ -11,7 +11,7 @@ import ( "k8s.io/client-go/tools/clientcmd" "os" "os/exec" - "path" + "path/filepath" "sigs.k8s.io/kind/pkg/cluster" kindcmd "sigs.k8s.io/kind/pkg/cmd" "testing" @@ -155,7 +155,7 @@ func createDefaultKindCluster() *KindCluster { kindClusterName = "kluctl-e2e" } if kindKubeconfig == "" { - kindKubeconfig = path.Join(utils.GetTmpBaseDir(), "kluctl-e2e-kubeconfig.yml") + kindKubeconfig = filepath.Join(utils.GetTmpBaseDir(), "kluctl-e2e-kubeconfig.yml") } return createKindCluster(kindClusterName, kindKubeconfig) } diff --git a/e2e/project.go b/e2e/project.go index ad387ba58..9790c8a24 100644 --- a/e2e/project.go +++ b/e2e/project.go @@ -9,7 +9,6 @@ import ( log "github.com/sirupsen/logrus" "io/ioutil" "os" - "path" "path/filepath" "reflect" "runtime" @@ -45,8 +44,8 @@ func (p *testProject) init(t *testing.T, projectName string) { p.baseDir = baseDir _ = os.MkdirAll(p.getKluctlProjectDir(), 0o700) - _ = os.MkdirAll(path.Join(p.getClustersDir(), "clusters"), 0o700) - _ = os.MkdirAll(path.Join(p.getSealedSecretsDir(), ".sealed-secrets"), 0o700) + _ = os.MkdirAll(filepath.Join(p.getClustersDir(), "clusters"), 0o700) + _ = os.MkdirAll(filepath.Join(p.getSealedSecretsDir(), ".sealed-secrets"), 0o700) _ = os.MkdirAll(p.getDeploymentDir(), 0o700) p.gitInit(p.getKluctlProjectDir()) @@ -112,7 +111,7 @@ func (p *testProject) gitInit(dir string) { if err != nil { p.t.Fatal(err) } - err = utils.Touch(path.Join(dir, ".dummy")) + err = utils.Touch(filepath.Join(dir, ".dummy")) if err != nil { p.t.Fatal(err) } @@ -150,7 +149,7 @@ func (p *testProject) commitFiles(repo string, add []string, all bool, message s } func (p *testProject) commitYaml(y *uo.UnstructuredObject, repo string, pth string, message string) { - err := yaml.WriteYamlFile(path.Join(repo, pth), y) + err := yaml.WriteYamlFile(filepath.Join(repo, pth), y) if err != nil { p.t.Fatal(err) } @@ -159,7 +158,7 @@ func (p *testProject) commitYaml(y *uo.UnstructuredObject, repo string, pth stri if err != nil { p.t.Fatal(err) } - message = fmt.Sprintf("update %s", path.Join(relPath, pth)) + message = fmt.Sprintf("update %s", filepath.Join(relPath, pth)) } p.commitFiles(repo, []string{pth}, false, message) } @@ -169,8 +168,8 @@ func (p *testProject) updateYaml(repo string, pth string, update func(o *uo.Unst p.t.Fatal() } o := uo.New() - if utils.Exists(path.Join(repo, pth)) { - err := yaml.ReadYamlFile(path.Join(repo, pth), o) + if utils.Exists(filepath.Join(repo, pth)) { + err := yaml.ReadYamlFile(filepath.Join(repo, pth), o) if err != nil { p.t.Fatal(err) } @@ -191,7 +190,7 @@ func (p *testProject) updateKluctlYaml(update func(o *uo.UnstructuredObject) err } func (p *testProject) updateDeploymentYaml(dir string, update func(o *uo.UnstructuredObject) error) { - p.updateYaml(p.getDeploymentDir(), path.Join(dir, "deployment.yml"), func(o *uo.UnstructuredObject) error { + p.updateYaml(p.getDeploymentDir(), filepath.Join(dir, "deployment.yml"), func(o *uo.UnstructuredObject) error { if dir == "." { o.SetNestedField(p.projectName, "commonLabels", "project_name") o.SetNestedField(p.projectName, "deleteByLabels", "project_name") @@ -201,7 +200,7 @@ func (p *testProject) updateDeploymentYaml(dir string, update func(o *uo.Unstruc } func (p *testProject) getDeploymentYaml(dir string) *uo.UnstructuredObject { - o, err := uo.FromFile(path.Join(p.getDeploymentDir(), dir, "deployment.yml")) + o, err := uo.FromFile(filepath.Join(p.getDeploymentDir(), dir, "deployment.yml")) if err != nil { p.t.Fatal(err) } @@ -220,13 +219,13 @@ func (p *testProject) listDeploymentItemPathes(dir string, fullPath bool) []stri if ok { x := pth if fullPath { - x = path.Join(dir, pth) + x = filepath.Join(dir, pth) } ret = append(ret, x) } pth, ok, _ = x.GetNestedString("include") if ok { - ret = append(ret, p.listDeploymentItemPathes(path.Join(dir, pth), fullPath)...) + ret = append(ret, p.listDeploymentItemPathes(filepath.Join(dir, pth), fullPath)...) } } return ret @@ -242,14 +241,14 @@ func (p *testProject) updateKustomizeDeployment(dir string, update func(o *uo.Un p.t.Fatal(err) } - pth := path.Join(dir, "kustomization.yml") + pth := filepath.Join(dir, "kustomization.yml") p.updateYaml(p.getDeploymentDir(), pth, func(o *uo.UnstructuredObject) error { return update(o, wt) }, fmt.Sprintf("Update kustomization.yml for %s", dir)) } func (p *testProject) updateCluster(name string, context string, vars *uo.UnstructuredObject) { - pth := path.Join("clusters", fmt.Sprintf("%s.yml", name)) + pth := filepath.Join("clusters", fmt.Sprintf("%s.yml", name)) p.updateYaml(p.getClustersDir(), pth, func(o *uo.UnstructuredObject) error { o.Clear() o.SetNestedField(name, "cluster", "name") @@ -332,19 +331,19 @@ func (p *testProject) addDeploymentIncludes(dir string) { var pp []string for _, x := range strings.Split(dir, "/") { if x != "." { - p.addDeploymentInclude(path.Join(pp...), x, nil) + p.addDeploymentInclude(filepath.Join(pp...), x, nil) } pp = append(pp, x) } } func (p *testProject) addKustomizeDeployment(dir string, resources []kustomizeResource, tags []string) { - deploymentDir := path.Dir(dir) + deploymentDir := filepath.Dir(dir) if deploymentDir != "" { p.addDeploymentIncludes(deploymentDir) } - absKustomizeDir := path.Join(p.getDeploymentDir(), dir) + absKustomizeDir := filepath.Join(p.getDeploymentDir(), dir) err := os.MkdirAll(absKustomizeDir, 0o700) if err != nil { @@ -361,7 +360,7 @@ func (p *testProject) addKustomizeDeployment(dir string, resources []kustomizeRe p.updateDeploymentYaml(deploymentDir, func(o *uo.UnstructuredObject) error { d, _, _ := o.GetNestedObjectList("deployments") n := uo.FromMap(map[string]interface{}{ - "path": path.Base(dir), + "path": filepath.Base(dir), }) if len(tags) != 0 { n.SetNestedField(tags, "tags") @@ -403,11 +402,11 @@ func (p *testProject) addKustomizeResources(dir string, resources []kustomizeRes for _, r := range resources { l = append(l, r.name) x := p.convertInterfaceToList(r.content) - err := yaml.WriteYamlAllFile(path.Join(p.getDeploymentDir(), dir, r.name), x) + err := yaml.WriteYamlAllFile(filepath.Join(p.getDeploymentDir(), dir, r.name), x) if err != nil { return err } - _, err = wt.Add(path.Join(dir, r.name)) + _, err = wt.Add(filepath.Join(dir, r.name)) if err != nil { return err } @@ -418,12 +417,12 @@ func (p *testProject) addKustomizeResources(dir string, resources []kustomizeRes } func (p *testProject) deleteKustomizeDeployment(dir string) { - deploymentDir := path.Dir(dir) + deploymentDir := filepath.Dir(dir) p.updateDeploymentItems(deploymentDir, func(items []*uo.UnstructuredObject) []*uo.UnstructuredObject { var newItems []*uo.UnstructuredObject for _, item := range items { pth, _, _ := item.GetNestedString("path") - if pth == path.Base(dir) { + if pth == filepath.Base(dir) { continue } newItems = append(newItems, item) @@ -433,26 +432,26 @@ func (p *testProject) deleteKustomizeDeployment(dir string) { } func (p *testProject) getKluctlProjectDir() string { - return path.Join(p.baseDir, "kluctl-project") + return filepath.Join(p.baseDir, "kluctl-project") } func (p *testProject) getClustersDir() string { if p.clustersExternal { - return path.Join(p.baseDir, "external-clusters") + return filepath.Join(p.baseDir, "external-clusters") } return p.getKluctlProjectDir() } func (p *testProject) getDeploymentDir() string { if p.deploymentExternal { - return path.Join(p.baseDir, "external-deployment") + return filepath.Join(p.baseDir, "external-deployment") } return p.getKluctlProjectDir() } func (p *testProject) getSealedSecretsDir() string { if p.sealedSecretsExternal { - return path.Join(p.baseDir, "external-sealed-secrets") + return filepath.Join(p.baseDir, "external-sealed-secrets") } return p.getKluctlProjectDir() } From da6b0f1742c1d7ed3bfcc9da61c500475f74245e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 1 Mar 2022 16:00:35 +0100 Subject: [PATCH 0418/2916] Use os independent path separators in tar files --- pkg/utils/tar.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/utils/tar.go b/pkg/utils/tar.go index 9c8511bfd..96f3e4952 100644 --- a/pkg/utils/tar.go +++ b/pkg/utils/tar.go @@ -8,6 +8,7 @@ import ( "io/fs" "os" "path/filepath" + "strings" ) func ExtractTarGzFile(tarGzPath string, targetPath string) error { @@ -30,6 +31,7 @@ func ExtractTarGzStream(r io.Reader, targetPath string) error { tarReader := tar.NewReader(r) for true { header, err := tarReader.Next() + header.Name = strings.ReplaceAll(header.Name, "/", string(os.PathSeparator)) if err == io.EOF { break @@ -84,7 +86,7 @@ func AddToTar(tw *tar.Writer, pth string, name string, filter func(h *tar.Header if err != nil { return err } - h.Name = name + h.Name = strings.ReplaceAll(name, string(os.PathSeparator), "/") if filter != nil { h, err = filter(h) From 5ff7fb0bbadf58785d72e4fe9cd7248a8aa188dd Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 1 Mar 2022 17:07:08 +0100 Subject: [PATCH 0419/2916] Fix crash in ExtractTarGzFile --- pkg/utils/tar.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/utils/tar.go b/pkg/utils/tar.go index 96f3e4952..67e17719d 100644 --- a/pkg/utils/tar.go +++ b/pkg/utils/tar.go @@ -31,8 +31,6 @@ func ExtractTarGzStream(r io.Reader, targetPath string) error { tarReader := tar.NewReader(r) for true { header, err := tarReader.Next() - header.Name = strings.ReplaceAll(header.Name, "/", string(os.PathSeparator)) - if err == io.EOF { break } @@ -41,6 +39,8 @@ func ExtractTarGzStream(r io.Reader, targetPath string) error { return fmt.Errorf("ExtractTarGz: Next() failed: %w", err) } + header.Name = strings.ReplaceAll(header.Name, "/", string(os.PathSeparator)) + switch header.Typeflag { case tar.TypeDir: if err := os.Mkdir(filepath.Join(targetPath, header.Name), 0755); err != nil { From 0aca44d0cb180545bcb1e8c32487bd29169486ff Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 1 Mar 2022 17:44:26 +0100 Subject: [PATCH 0420/2916] Use / as path separator when using io.fs --- pkg/utils/fs.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/utils/fs.go b/pkg/utils/fs.go index 9c43339fc..b4fe0fe6d 100644 --- a/pkg/utils/fs.go +++ b/pkg/utils/fs.go @@ -6,6 +6,8 @@ import ( "io/fs" "os" "path" + "path/filepath" + "strings" ) func Exists(path string) bool { @@ -55,6 +57,7 @@ func CopyFile(src string, dst string) error { } func FsCopyFile(srcFs fs.FS, src, dst string) error { + src = strings.ReplaceAll(src, string(os.PathSeparator), "/") source, err := srcFs.Open(src) if err != nil { return err @@ -88,6 +91,8 @@ func FsCopyDir(srcFs fs.FS, src string, dst string) error { var err error var fds []fs.DirEntry + src = strings.ReplaceAll(src, string(os.PathSeparator), "/") + if fds, err = fs.ReadDir(srcFs, src); err != nil { return err } @@ -96,7 +101,7 @@ func FsCopyDir(srcFs fs.FS, src string, dst string) error { } for _, fd := range fds { srcfp := path.Join(src, fd.Name()) - dstfp := path.Join(dst, fd.Name()) + dstfp := filepath.Join(dst, fd.Name()) if fd.IsDir() { if err = FsCopyDir(srcFs, srcfp, dstfp); err != nil { From 52ba81067837570633bbc782954c1e19417eb4eb Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 1 Mar 2022 22:05:50 +0100 Subject: [PATCH 0421/2916] Use / in file:// urls --- e2e/project.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/project.go b/e2e/project.go index 9790c8a24..afdcaf3e1 100644 --- a/e2e/project.go +++ b/e2e/project.go @@ -504,7 +504,7 @@ func (p *testProject) KluctlMust(argsIn ...string) (string, string) { func (p *testProject) buildFileUrl(pth string) string { if runtime.GOOS == "windows" { - return fmt.Sprintf("file:///%s", pth) + return fmt.Sprintf("file:///%s", strings.ReplaceAll(pth, string(os.PathSeparator), "/")) } return fmt.Sprintf("file://%s", pth) } From 9fa1ca90539f0f9248d316097e7b5e1f784ea4ad Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 2 Mar 2022 12:33:20 +0100 Subject: [PATCH 0422/2916] Don't rely on file:// support in tests as it fails on windows Start a local smart http server instead. --- e2e/project.go | 53 ++++++-- go.mod | 2 +- pkg/git/http-server/http.go | 191 +++++++++++++++++++++++++++ pkg/git/http-server/utils.go | 83 ++++++++++++ pkg/git/http-server/write_flusher.go | 25 ++++ pkg/utils/process/process_posix.go | 35 +++++ pkg/utils/process/process_windows.go | 60 +++++++++ 7 files changed, 440 insertions(+), 9 deletions(-) create mode 100644 pkg/git/http-server/http.go create mode 100644 pkg/git/http-server/utils.go create mode 100644 pkg/git/http-server/write_flusher.go create mode 100644 pkg/utils/process/process_posix.go create mode 100644 pkg/utils/process/process_windows.go diff --git a/e2e/project.go b/e2e/project.go index afdcaf3e1..2a009e35d 100644 --- a/e2e/project.go +++ b/e2e/project.go @@ -1,13 +1,17 @@ package e2e import ( + "context" "fmt" + http_server "github.com/codablock/kluctl/pkg/git/http-server" "github.com/codablock/kluctl/pkg/utils" "github.com/codablock/kluctl/pkg/utils/uo" "github.com/codablock/kluctl/pkg/yaml" "github.com/go-git/go-git/v5" log "github.com/sirupsen/logrus" "io/ioutil" + "net" + "net/http" "os" "path/filepath" "reflect" @@ -32,6 +36,10 @@ type testProject struct { kubeconfigs []string baseDir string + + gitServer *http_server.Server + gitHttpServer *http.Server + gitServerPort int } func (p *testProject) init(t *testing.T, projectName string) { @@ -48,6 +56,8 @@ func (p *testProject) init(t *testing.T, projectName string) { _ = os.MkdirAll(filepath.Join(p.getSealedSecretsDir(), ".sealed-secrets"), 0o700) _ = os.MkdirAll(p.getDeploymentDir(), 0o700) + p.initGitServer() + p.gitInit(p.getKluctlProjectDir()) if p.clustersExternal { p.gitInit(p.getClustersDir()) @@ -61,13 +71,13 @@ func (p *testProject) init(t *testing.T, projectName string) { p.updateKluctlYaml(func(o *uo.UnstructuredObject) error { if p.clustersExternal { - o.SetNestedField(p.buildFileUrl(p.getClustersDir()), "clusters", "project") + o.SetNestedField(p.buildLocalGitUrl(p.getClustersDir()), "clusters", "project") } if p.deploymentExternal { - o.SetNestedField(p.buildFileUrl(p.getDeploymentDir()), "deployment", "project") + o.SetNestedField(p.buildLocalGitUrl(p.getDeploymentDir()), "deployment", "project") } if p.sealedSecretsExternal { - o.SetNestedField(p.buildFileUrl(p.getSealedSecretsDir()), "sealedSecrets", "project") + o.SetNestedField(p.buildLocalGitUrl(p.getSealedSecretsDir()), "sealedSecrets", "project") } return nil }) @@ -76,7 +86,33 @@ func (p *testProject) init(t *testing.T, projectName string) { }) } +func (p *testProject) initGitServer() { + p.gitServer = http_server.New(p.baseDir) + + p.gitHttpServer = &http.Server{ + Addr: "127.0.0.1:0", + Handler: p.gitServer, + } + + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + log.Fatal(err) + } + a := ln.Addr().(*net.TCPAddr) + p.gitServerPort = a.Port + + go func() { + _ = p.gitHttpServer.Serve(ln) + }() +} + func (p *testProject) cleanup() { + if p.gitHttpServer != nil { + _ = p.gitHttpServer.Shutdown(context.Background()) + p.gitHttpServer = nil + p.gitServer = nil + } + if p.baseDir == "" { return } @@ -465,7 +501,7 @@ func (p *testProject) Kluctl(argsIn ...string) (string, string, error) { cwd := "" if p.kluctlProjectExternal { - args = append(args, "--project-url", p.buildFileUrl(p.getKluctlProjectDir())) + args = append(args, "--project-url", p.buildLocalGitUrl(p.getKluctlProjectDir())) } else { cwd = p.getKluctlProjectDir() } @@ -502,9 +538,10 @@ func (p *testProject) KluctlMust(argsIn ...string) (string, string) { return stdout, stderr } -func (p *testProject) buildFileUrl(pth string) string { - if runtime.GOOS == "windows" { - return fmt.Sprintf("file:///%s", strings.ReplaceAll(pth, string(os.PathSeparator), "/")) +func (p *testProject) buildLocalGitUrl(repo string) string { + relPth, err := filepath.Rel(p.baseDir, repo) + if err != nil { + log.Panic(err) } - return fmt.Sprintf("file://%s", pth) + return fmt.Sprintf("http://localhost:%d/%s/.git", p.gitServerPort, relPth) } diff --git a/go.mod b/go.mod index 74f96b1aa..484364468 100644 --- a/go.mod +++ b/go.mod @@ -24,6 +24,7 @@ require ( github.com/sirupsen/logrus v1.8.1 github.com/whilp/git-urls v1.0.0 golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 + golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b k8s.io/api v0.23.3 k8s.io/apimachinery v0.23.3 @@ -109,7 +110,6 @@ require ( golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d // indirect golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect - golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect diff --git a/pkg/git/http-server/http.go b/pkg/git/http-server/http.go new file mode 100644 index 000000000..d2ec7ddf0 --- /dev/null +++ b/pkg/git/http-server/http.go @@ -0,0 +1,191 @@ +// This is copied from https://github.com/sosedoff/gitkit and simplified +package http_server + +import ( + "compress/gzip" + "fmt" + process2 "github.com/codablock/kluctl/pkg/utils/process" + log "github.com/sirupsen/logrus" + "io" + "net/http" + "os" + "os/exec" + "path" + "strings" +) + +type service struct { + method string + suffix string + handler func(string, http.ResponseWriter, *Request) + rpc string +} + +type Server struct { + baseDir string + services []service +} + +type Request struct { + *http.Request + RepoName string + RepoPath string +} + +func New(baseDir string) *Server { + s := Server{baseDir: baseDir} + s.services = []service{ + service{"GET", "/info/refs", s.getInfoRefs, ""}, + service{"POST", "/git-upload-pack", s.postRPC, "git-upload-pack"}, + service{"POST", "/git-receive-pack", s.postRPC, "git-receive-pack"}, + } + + return &s +} + +// findService returns a matching git subservice and parsed repository name +func (s *Server) findService(req *http.Request) (*service, string) { + for _, svc := range s.services { + if svc.method == req.Method && strings.HasSuffix(req.URL.Path, svc.suffix) { + path := strings.Replace(req.URL.Path, svc.suffix, "", 1) + return &svc, path + } + } + return nil, "" +} + +func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { + log.Info("request", r.Method+" "+r.Host+r.URL.String()) + + // Find the git subservice to handle the request + svc, repoUrlPath := s.findService(r) + if svc == nil { + http.Error(w, "Forbidden", http.StatusForbidden) + return + } + + // Determine namespace and repo name from request path + repoNamespace, repoName := getNamespaceAndRepo(repoUrlPath) + if repoName == "" { + log.Error("auth", fmt.Errorf("no repo name provided")) + w.WriteHeader(http.StatusBadRequest) + return + } + + req := &Request{ + Request: r, + RepoName: path.Join(repoNamespace, repoName), + RepoPath: path.Join(s.baseDir, repoNamespace, repoName), + } + + if !repoExists(req.RepoPath) { + log.Error("repo-init", fmt.Errorf("%s does not exist", req.RepoPath)) + http.NotFound(w, r) + return + } + + svc.handler(svc.rpc, w, req) +} + +func (s *Server) getInfoRefs(_ string, w http.ResponseWriter, r *Request) { + context := "get-info-refs" + rpc := r.URL.Query().Get("service") + + if !(rpc == "git-upload-pack" || rpc == "git-receive-pack") { + http.Error(w, "Not Found", 404) + return + } + + cmd, pipe := gitCommand("git", subCommand(rpc), "--stateless-rpc", "--advertise-refs", r.RepoPath) + if err := cmd.Start(); err != nil { + fail500(w, context, err) + return + } + defer cleanUpProcessGroup(cmd) + + w.Header().Add("Content-Type", fmt.Sprintf("application/x-%s-advertisement", rpc)) + w.Header().Add("Cache-Control", "no-cache") + w.WriteHeader(200) + + if err := packLine(w, fmt.Sprintf("# service=%s\n", rpc)); err != nil { + log.Error(context, err) + return + } + + if err := packFlush(w); err != nil { + log.Error(context, err) + return + } + + if _, err := io.Copy(w, pipe); err != nil { + log.Error(context, err) + return + } + + if err := cmd.Wait(); err != nil { + log.Error(context, err) + return + } +} + +func (s *Server) postRPC(rpc string, w http.ResponseWriter, r *Request) { + context := "post-rpc" + body := r.Body + + if r.Header.Get("Content-Encoding") == "gzip" { + var err error + body, err = gzip.NewReader(r.Body) + if err != nil { + fail500(w, context, err) + return + } + } + + cmd, pipe := gitCommand("git", subCommand(rpc), "--stateless-rpc", r.RepoPath) + stdin, err := cmd.StdinPipe() + if err != nil { + fail500(w, context, err) + return + } + defer stdin.Close() + + if err := cmd.Start(); err != nil { + fail500(w, context, err) + return + } + defer cleanUpProcessGroup(cmd) + + if _, err := io.Copy(stdin, body); err != nil { + fail500(w, context, err) + return + } + + w.Header().Add("Content-Type", fmt.Sprintf("application/x-%s-result", rpc)) + w.Header().Add("Cache-Control", "no-cache") + w.WriteHeader(200) + + if _, err := io.Copy(newWriteFlusher(w), pipe); err != nil { + log.Error(context, err) + return + } + if err := cmd.Wait(); err != nil { + log.Error(context, err) + return + } +} + +func repoExists(p string) bool { + _, err := os.Stat(path.Join(p, "objects")) + return err == nil +} + +func gitCommand(name string, args ...string) (*exec.Cmd, io.Reader) { + cmd := exec.Command(name, args...) + cmd.SysProcAttr = process2.ProcAttrWithProcessGroup + cmd.Env = os.Environ() + + r, _ := cmd.StdoutPipe() + cmd.Stderr = cmd.Stdout + + return cmd, r +} \ No newline at end of file diff --git a/pkg/git/http-server/utils.go b/pkg/git/http-server/utils.go new file mode 100644 index 000000000..e39401304 --- /dev/null +++ b/pkg/git/http-server/utils.go @@ -0,0 +1,83 @@ +package http_server + +import ( + "fmt" + process2 "github.com/codablock/kluctl/pkg/utils/process" + "io" + "log" + "net/http" + "os" + "os/exec" + "regexp" + "strings" +) + +var reSlashDedup = regexp.MustCompile(`\/{2,}`) + +func fail500(w http.ResponseWriter, context string, err error) { + http.Error(w, "Internal server error", 500) + logError(context, err) +} + +func logError(context string, err error) { + log.Printf("%s: %v\n", context, err) +} + +func logInfo(context string, message string) { + log.Printf("%s: %s\n", context, message) +} + +func cleanUpProcessGroup(cmd *exec.Cmd) { + if cmd == nil { + return + } + + process := cmd.Process + if process != nil && process.Pid > 0 { + _ = process2.TerminateProcess(process.Pid, os.Interrupt) + } + + go cmd.Wait() +} + +func packLine(w io.Writer, s string) error { + _, err := fmt.Fprintf(w, "%04x%s", len(s)+4, s) + return err +} + +func packFlush(w io.Writer) error { + _, err := fmt.Fprint(w, "0000") + return err +} + +func subCommand(rpc string) string { + return strings.TrimPrefix(rpc, "git-") +} + +// Parse out namespace and repository name from the path. +// Examples: +// repo -> "", "repo" +// org/repo -> "org", "repo" +// org/suborg/rpeo -> "org/suborg", "repo" +func getNamespaceAndRepo(input string) (string, string) { + if input == "" || input == "/" { + return "", "" + } + + // Remove duplicate slashes + input = reSlashDedup.ReplaceAllString(input, "/") + + // Remove leading slash + if input[0] == '/' && input != "/" { + input = input[1:] + } + + blocks := strings.Split(input, "/") + num := len(blocks) + + if num < 2 { + return "", blocks[0] + } + + return strings.Join(blocks[0:num-1], "/"), blocks[num-1] +} diff --git a/pkg/git/http-server/write_flusher.go b/pkg/git/http-server/write_flusher.go new file mode 100644 index 000000000..be8aed202 --- /dev/null +++ b/pkg/git/http-server/write_flusher.go @@ -0,0 +1,25 @@ +package http_server + +import ( + "io" + "net/http" +) + +func newWriteFlusher(w http.ResponseWriter) io.Writer { + return writeFlusher{w.(interface { + io.Writer + http.Flusher + })} +} + +type writeFlusher struct { + wf interface { + io.Writer + http.Flusher + } +} + +func (w writeFlusher) Write(p []byte) (int, error) { + defer w.wf.Flush() + return w.wf.Write(p) +} diff --git a/pkg/utils/process/process_posix.go b/pkg/utils/process/process_posix.go new file mode 100644 index 000000000..17937422e --- /dev/null +++ b/pkg/utils/process/process_posix.go @@ -0,0 +1,35 @@ +// +build !windows + +package process + +import ( + "golang.org/x/sys/unix" + "os" +) + +var ProcAttrWithProcessGroup = &unix.SysProcAttr{Setpgid: true} + +func TerminateProcess(pid int, signal os.Signal) error { + if pid == 0 || pid == -1 { + return nil + } + pgid, err := unix.Getpgid(pid) + if err != nil { + return err + } + + if pgid == pid { + pid = -1 * pid + } + + target, err := os.FindProcess(pid) + if err != nil { + return err + } + return target.Signal(signal) +} + +// kills the process with pid pid, as well as its children. +func KillProcess(process *os.Process) error { + return unix.Kill(-1*process.Pid, unix.SIGKILL) +} diff --git a/pkg/utils/process/process_windows.go b/pkg/utils/process/process_windows.go new file mode 100644 index 000000000..1694a8e7c --- /dev/null +++ b/pkg/utils/process/process_windows.go @@ -0,0 +1,60 @@ +// +build windows + +package process + +import ( + "os" + "syscall" + + "golang.org/x/sys/windows" +) + +var ProcAttrWithProcessGroup = &windows.SysProcAttr{ + CreationFlags: windows.CREATE_UNICODE_ENVIRONMENT | windows.CREATE_NEW_PROCESS_GROUP, +} + +func TerminateProcess(pid int, _ os.Signal) error { + if pid == 0 || pid == -1 { + return nil + } + dll, err := windows.LoadDLL("kernel32.dll") + if err != nil { + return err + } + defer dll.Release() + + f, err := dll.FindProc("AttachConsole") + if err != nil { + return err + } + r1, _, err := f.Call(uintptr(pid)) + if r1 == 0 && err != syscall.ERROR_ACCESS_DENIED { + return err + } + + f, err = dll.FindProc("SetConsoleCtrlHandler") + if err != nil { + return err + } + r1, _, err = f.Call(0, 1) + if r1 == 0 { + return err + } + f, err = dll.FindProc("GenerateConsoleCtrlEvent") + if err != nil { + return err + } + r1, _, err = f.Call(windows.CTRL_BREAK_EVENT, uintptr(pid)) + if r1 == 0 { + return err + } + r1, _, err = f.Call(windows.CTRL_C_EVENT, uintptr(pid)) + if r1 == 0 { + return err + } + return nil +} + +func KillProcess(process *os.Process) error { + return process.Kill() +} From 033d8ba0e0d9912e999c9a4508af184425161e67 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 2 Mar 2022 12:37:46 +0100 Subject: [PATCH 0423/2916] Remove check-docs job --- .github/workflows/build-and-release.yml | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index 88b6583d1..bf90e982c 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -307,30 +307,6 @@ jobs: echo "commands.md is not up-to-date. Run "./hack/replace-commands-help --commands-md ./docs/commands.md" to fix this!" exit 1 fi - check-code: - runs-on: ubuntu-latest - needs: - - build - steps: - - name: Install packages - run: | - sudo apt update - sudo apt install -y git - - name: Checkout - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: 3.10.2 - - name: Download artifacts - uses: actions/download-artifact@v2 - - name: check-docs - run: | - #python scripts/replace-commands-help.py - #if [ "$(git status --porcelain)" != "" ]; then - # echo "commands.md is not up-to-date. Run ./scripts/replace-commands-help.py to fix this!" - # exit 1 - #fi release: runs-on: ubuntu-latest @@ -338,7 +314,6 @@ jobs: needs: - version - build - - check-code - tests steps: - name: Checkout From 828069ab0e1255a5436cd643f16a672f61ec4721 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 2 Mar 2022 15:06:58 +0100 Subject: [PATCH 0424/2916] Disable parallel e2e testing --- .github/workflows/build-and-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index bf90e982c..7f51a3e17 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -293,7 +293,7 @@ jobs: - name: Run e2e tests shell: bash run: | - go test ./e2e/... -parallel 4 -v + go test ./e2e/... -parallel 1 -v - name: Delete kind cluster shell: bash run: | From 848eaed08c2dd224d89f6dcf13609f04cc3b129e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 2 Mar 2022 17:44:28 +0100 Subject: [PATCH 0425/2916] Disable check-docs on windows --- .github/workflows/build-and-release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index 7f51a3e17..1beeed080 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -299,6 +299,7 @@ jobs: run: | kind delete cluster --name $KIND_CLUSTER_NAME - name: Check docs + if: runner.os != 'Windows' shell: bash run: | go run ./hack/replace-commands-help --commands-md ./docs/commands.md From e07acedc53a107256c0613ad8773fb6725d26d01 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 2 Mar 2022 17:45:18 +0100 Subject: [PATCH 0426/2916] Implement credentials via env vars support and caching for ListImageTags --- pkg/registries/registries.go | 183 ++++++++++++++++++++++++++++++++++- pkg/utils/env.go | 50 ++++++++++ 2 files changed, 230 insertions(+), 3 deletions(-) create mode 100644 pkg/utils/env.go diff --git a/pkg/registries/registries.go b/pkg/registries/registries.go index 9eb9f8022..86eab8cc4 100644 --- a/pkg/registries/registries.go +++ b/pkg/registries/registries.go @@ -1,13 +1,190 @@ package registries import ( - "github.com/google/go-containerregistry/pkg/crane" + "bufio" + "bytes" + "fmt" + "github.com/codablock/kluctl/pkg/utils" + "github.com/docker/distribution/registry/client/auth/challenge" + "github.com/google/go-containerregistry/pkg/authn" + "github.com/google/go-containerregistry/pkg/name" + "github.com/google/go-containerregistry/pkg/v1/remote" + "net/http" + "net/http/httputil" + "os" + "strconv" + "sync" ) func ListImageTags(image string) ([]string, error) { - tags, err := crane.ListTags(image) + var nameOpts []name.Option + remoteOpts := []remote.Option{ + remote.WithAuthFromKeychain(&globalMyKeychain), + remote.WithTransport(&globalMyKeychain), + } + + repo, err := name.NewRepository(image, nameOpts...) + if err != nil { + return nil, err + } + + if isRegistryInsecure(repo.RegistryStr()) { + nameOpts = append(nameOpts, name.Insecure) + repo, err = name.NewRepository(image, nameOpts...) + } + + return remote.List(repo, remoteOpts...) +} + +var globalMyKeychain myKeychain + +type myKeychain struct { + cachedResponses utils.ThreadSafeMultiCache + authRealms map[string]bool + authErrors map[string]bool + init sync.Once + mutex sync.Mutex +} + +func (kc *myKeychain) Resolve(resource authn.Resource) (authn.Authenticator, error) { + registry := resource.RegistryStr() + + for _, m := range utils.ParseEnvConfigSets("KLUCTL_REGISTRY") { + host, _ := m["HOST"] + if host == registry { + username, _ := m["USERNAME"] + password, _ := m["PASSWORD"] + return authn.FromConfig(authn.AuthConfig{ + Username: username, + Password: password, + }), nil + } + } + + return authn.DefaultKeychain.Resolve(resource) +} + +func (kc *myKeychain) realmFromRequest(req *http.Request) string { + return fmt.Sprintf("%s://%s%s", req.URL.Scheme, req.URL.Host, req.URL.Path) +} + +func (kc *myKeychain) RoundTripCached(req *http.Request, extraKey string, onNew func(res *http.Response) error) (*http.Response, error) { + resI, err := kc.cachedResponses.Get(req.Host, req.URL.Path + "#" + extraKey, func() (interface{}, error) { + res, err := remote.DefaultTransport.RoundTrip(req) + if err != nil { + return nil, err + } + + if onNew != nil { + err = onNew(res) + if err != nil { + return nil, err + } + } + + return httputil.DumpResponse(res, true) + }) + if err != nil { return nil, err } - return tags, nil + resBytes, _ := resI.([]byte) + r := bufio.NewReader(bytes.NewReader(resBytes)) + + res, err := http.ReadResponse(r, req) + if err != nil { + return res, err + } + + return res, err +} + +func (kc *myKeychain) RoundTripInfoReq(req *http.Request) (*http.Response, error) { + return kc.RoundTripCached(req, "info", func(res *http.Response) error { + kc.mutex.Lock() + defer kc.mutex.Unlock() + + chgs := challenge.ResponseChallenges(res) + for _, chg := range chgs { + if realm, ok := chg.Parameters["realm"]; ok { + kc.authRealms[realm] = true + } + } + return nil + }) +} + +func (kc *myKeychain) RoundTripAuth(req *http.Request) (*http.Response, error) { + b := bytes.NewBuffer(nil) + err := req.Header.Write(b) + if err != nil { + return nil, err + } + + b.WriteString("\n" + req.URL.RawQuery) + + hash := utils.Sha256String(b.String()) + + return kc.RoundTripCached(req, hash, func(res *http.Response) error { + kc.mutex.Lock() + defer kc.mutex.Unlock() + + if res.StatusCode == http.StatusUnauthorized || res.StatusCode == http.StatusForbidden { + // if auth fails once for a registry, we must not retry any auth on that registry as we could easily run + // into an IP block + kc.authErrors[kc.realmFromRequest(req)] = true + } + + return nil + }) +} + +func (kc *myKeychain) RoundTrip(req *http.Request) (*http.Response, error) { + kc.init.Do(func() { + kc.authRealms = make(map[string]bool) + kc.authErrors = make(map[string]bool) + }) + + if req.URL.Path == "/v2/" { + return kc.RoundTripInfoReq(req) + } + + kc.mutex.Lock() + realm := kc.realmFromRequest(req) + _, isAuthRealm := kc.authRealms[realm] + _, isAuthError := kc.authErrors[realm] + kc.mutex.Unlock() + + if isAuthError { + return nil, fmt.Errorf("previous auth request for %s gave an error, we won't retry", realm) + } + + if isAuthRealm { + return kc.RoundTripAuth(req) + } + + resp, err := remote.DefaultTransport.RoundTrip(req) + return resp, err +} + +func isRegistryInsecure(registry string) bool { + for _, m := range utils.ParseEnvConfigSets("KLUCTL_REGISTRY") { + host, _ := m["HOST"] + if host == registry { + tlsverifyStr, ok := m["TLSVERIFY"] + if ok { + tlsverify, err := strconv.ParseBool(tlsverifyStr) + if err == nil { + return !tlsverify + } + } + } + } + + if x, ok := os.LookupEnv("KLUCTL_REGISTRY_DEFAULT_TLSVERIFY"); ok { + if b, err := strconv.ParseBool(x); err == nil { + return !b + } + } + return false } diff --git a/pkg/utils/env.go b/pkg/utils/env.go new file mode 100644 index 000000000..f0f609f07 --- /dev/null +++ b/pkg/utils/env.go @@ -0,0 +1,50 @@ +package utils + +import ( + "fmt" + log "github.com/sirupsen/logrus" + "os" + "regexp" + "strconv" + "strings" +) + + +func ParseEnvConfigSets(prefix string) map[int]map[string]string { + ret := make(map[int]map[string]string) + + r := regexp.MustCompile(fmt.Sprintf(`^%s_(\d+)_(.*)$`, prefix)) + r2 := regexp.MustCompile(fmt.Sprintf(`^%s_(.*)$`, prefix)) + + for _, e := range os.Environ() { + eq := strings.Index(e, "=") + if eq == -1 { + log.Panicf("unexpected env var %s", e) + } + n := e[:eq] + v := e[eq+1:] + + idx := -1 + key := "" + + m := r.FindStringSubmatch(n) + if m != nil { + x, _ := strconv.ParseInt(m[1], 10, 32) + idx = int(x) + key = m[2] + } else { + m = r2.FindStringSubmatch(n) + if m != nil { + key = m[1] + } + } + + if key != "" { + if _, ok := ret[idx]; !ok { + ret[idx] = make(map[string]string) + } + ret[idx][key] = v + } + } + return ret +} \ No newline at end of file From 51ff68e24c48a782e5b87281c45a483f8e44bbc6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 3 Mar 2022 10:03:00 +0100 Subject: [PATCH 0427/2916] Update documentation --- README.md | 19 ++--- docs/commands.md | 1 - docs/deployments.md | 163 ++++++++++++++++++++++++++------------ docs/getting-started.md | 21 +---- docs/jinja2-templating.md | 34 ++++++-- docs/kluctl_project.md | 25 +++++- docs/sealed-secrets.md | 17 ++-- docs/tags.md | 25 +++--- 8 files changed, 195 insertions(+), 110 deletions(-) diff --git a/README.md b/README.md index 77172056b..e6ebe1135 100644 --- a/README.md +++ b/README.md @@ -44,10 +44,10 @@ in a sane way. ## Installation -kluctl can be installed in 3 different ways. +kluctl can currently only be installed this way: 1. Download a standalone binary from the latest release and make it available in your PATH, either by copying it into `/usr/local/bin` or by modifying the PATH variable -2. Use pip to install kluctl: `pip install kluctl` -3. Use kluctl inside docker, which is useful in CI/CD for example. The image is currently hosted on Docker Hub: https://hub.docker.com/repository/docker/codablock/kluctl + +Future releases will include packaged releases for homebrew and other established package managers (contributions are welcome). ## Documentation @@ -58,7 +58,7 @@ You can find a basic Getting Started documentation [here](./docs/getting-started ### kluctl project config (.kluctl.yml) The [.kluctl.yml](./docs/kluctl_project.md) file is the central configuration file that defines your kluctl project. -It declares what (clusters, secrets, deployment projects, ...) is need for your deployment, where to find it and what +It declares what (clusters, secrets, deployment projects, ...) is needed for your deployment, where to find it and what targets are available to invoke commands against. ### Deployment projects @@ -94,10 +94,10 @@ base infrastructure. ### Jinja2 based templating engine Kubernetes resources and all other involved configuration is based on [Jinja2](https://palletsprojects.com/p/jinja/) -templates. Templates can easily be configured via CLI arguments (`-a var_name=value`) or via -the [deployment project](./docs/deployments.md#vars) declare a custom set of variables. +templates. Jinja2 context variables are usually passed though [kluctl targets](./docs/kluctl_project.md#targets) +but can also be overridden via CLI. -Jinja2 macros allow unifying of heavily repeated deployments (e.g. you 100 microservices) in a convenient way. +Jinja2 macros allow unifying of heavily repeated deployments (e.g. your 100 microservices) in a convenient way. ### Unified CLI (command line interface) @@ -105,16 +105,13 @@ Deploying your application and all of its dependencies is done via a unified com the same, no matter how large (single nginx or 100 microservices) or flexible (test env, uat env, prod env, local env...) your deployment actually is. -The unified CLI also allows to build other tools around kluctl, for example web based UIs around your deployments -or ChatOps bots. - ### Secure management of (sealed) secrets in Git Maintaining secrets inside Git is a complex and dangerous task, but at the same time has many advantages when done properly. Encrypting such secrets is a must, but there are multiple more or less secure ways to do so. kluctl has builtin support for [sealed-secrets](https://github.com/bitnami-labs/sealed-secrets). This means, -it can plug in sealed secrets into you deployment in a dynamic and configurable way, targeting multiple clusters, +it can plug in sealed secrets into your deployment in a dynamic and configurable way, targeting multiple clusters, environments, configurations and so on. sealed-secrets is public-key crypto based, allowing to target individual clusters or namespaces in a secure way, diff --git a/docs/commands.md b/docs/commands.md index bb5e45c4a..c40515087 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -92,7 +92,6 @@ A few additional environment variables are supported which do not belong to an o 2. `KLUCTL_GIT_TIMEOUT`. Specifies how long to wait on git subprocesses to finish until they are killed. 3. `KLUCTL_NO_THREADS`. Do not use multithreading while performing work. This is only useful for debugging purposes. 4. `KLUCTL_IGNORE_DEBUGGER`. Pretend that there is no debugger attached when automatically deciding if multi-threading should be enabled or not. -5. `TMPDIR, TEMP or TMP`. Because kluctl uses [tempfile](https://docs.python.org/3/library/tempfile.html), you can set the output directory for temporary files (e.g. git-cache, repositories, rendered files) with the aforementioned envs. # Commands The following commands are available: diff --git a/docs/deployments.md b/docs/deployments.md index 576c03689..ab4a08a8b 100644 --- a/docs/deployments.md +++ b/docs/deployments.md @@ -78,12 +78,10 @@ An example `deployment.yml` looks like this: sealedSecrets: outputPattern: "{{ cluster.name }}/{{ args.environment }}" -kustomizeDirs: +deployments: - path: nginx - path: my-app - -includes: -- path: monitoring +- include: monitoring commonLabels: my.prefix/environment: "{{ args.environment }}" @@ -103,63 +101,130 @@ The following sub-chapters describe the available properties/fields in the `depl `sealedSecrets` configures how sealed secrets are stored while sealing and located while rendering. See [Sealed Secrets](./sealed-secrets.md#outputpattern-and-location-of-stored-sealed-secrets) for details. -### kustomizeDirs +### deployments + +`deployments` is a list of deployment items. Multiple deployment types are supported, which is documented further down. +Individual deployments are performed in parallel, unless a [barrier](#barriers) is encountered which causes kluctl to +wait for all previous deployments to finish. -`kustomizeDirs` is a list of [kustomize](https://kustomize.io/) deployments to be included in the deployment. The -kustomize deployment must be located in a directory that is relative to the location of the `deployment.yml`. +#### [kustomize](https://kustomize.io/) deployments +Specifies a [kustomize](https://kustomize.io/) deployment. Please see [Kustomize integration](./kustomize-integration.md) for more details. -#### path -The relative path of the kustomize deployment. It must contain a valid `kustomization.yml`. +Example: +```yaml +deployments: +- path: path/to/deployment +``` -#### tags -A list of tags the kustomize deployment should have. See [tags](./tags.md) for more details. +The `path` must point to a directory relative to the directory containing the `deployment.yml`. Only directories +that are part of the kluctl project are allowed. The directory must contain a valid `kustomization.yml`. -#### barrier -Causes kluctl to wait until the current and all previous kustomize deployments have been applied. This is useful when -upcoming deployments need the current or previous deployments to be finished beforehand. +#### Includes -#### alwaysDeploy -Forces a kustomize Deployment to be included everytime, ignoring inclusion/exclusion sets from the command line. -See [Deploying with tag inclusion/exclusion](./tags.md#deploying-with-tag-inclusionexclusion) for details. +Specifies a sub-deployment project to be included. The included sub-deployment project will inherit many properties +of the parent project, e.g. tags, commonLabels and so on. -#### skipDeleteIfTags -Forces exclusion of a kustomize deployment whenever inclusion/exclusion tags are specified via command line. -See [Deleting with tag inclusion/exclusion](./tags.md#deleting-with-tag-inclusionexclusion) for details. +Example: +```yaml +deployments: +- include: path/to/sub-deploment +``` -### vars -A list of additional sets of variables to be added to the Jinja2 context. +The `path` must point to a directory relative to the directory containing the `deployment.yml`. Only directories +that are part of the kluctl project are allowed. The directory must contain a valid `deployment.yml`. -There are currently two types of entries possible: -1. If `values` is present, it directly contains the new variables -1. If `file` is present, it points to a relative yaml file that contains the variables. +#### Barriers +Causes kluctl to wait until all previous kustomize deployments have been applied. This is useful when +upcoming deployments need the current or previous deployments to be finished beforehand. Previous deployments also +include all sub-deployments from included deployments. Example: ```yaml -vars: -- file: vars1.yml -- values: - var1: value1 - var2: - var3: value3 +deployments: +- path: kustomizeDeployment1 +- path: kustomizeDeployment2 +- include: subDeployment1 +- barrier: true +# At this point, it's ensured that kustomizeDeployment1, kustomizeDeployment2 and all sub-deployments from +# subDeployment1 are fully deployed. +- path: kustomizeDeployment3 ``` -See [jinja2-templating](./jinja2-templating.md) for more details. +### deployments common properties +All entries in `deployments` can have the following common properties: -### includes -A list of sub-deployment projects to include. These are deployment-projects as well and inherit many of the -properties of the parent deployment project. +#### vars (deployment item) +A list of additional sets of variables to be added to the Jinja2 context the corresponding (and sub-deployments in case +of includes). -#### path -The relative path of the sub-deployment project. It must contain a valid `deployment.yml`. +See [jinja2-templating](./jinja2-templating.md#vars-from-deploymentyml) for more details. -#### tags -A list of tags the include and all of its sub-includes and kustomize deployments should have. -See [tags](./tags.md) for more details. +Example: +```yaml +deployments: +- path: kustomizeDeployment1 + vars: + - file: vars1.yml + - values: + var1: value1 +- path: kustomizeDeployment2 +# all sub-deployments of this include will have the given variables available in their Jinj2 context. +- include: subDeployment1 + vars: + - file: vars2.yml +``` + +#### tags (deployment item) +A list of tags the deployment should have. See [tags](./tags.md) for more details. For includes, this means that all +sub-deployments will get these tags applied to. If not specified, the default tags logic as described in [tags](./tags.md) +is applied. -#### barrier -Same as `barrier` in `kustomizeDirs`. +Example: + +```yaml +deployments: +- path: kustomizeDeployment1 + tags: + - tag1 + - tag2 +- path: kustomizeDeployment2 + tags: + - tag3 +# all sub-deployments of this include will get tag4 applied +- include: subDeployment1 + tags: + - tag4 +``` + +#### alwaysDeploy +Forces a deployment to be included everytime, ignoring inclusion/exclusion sets from the command line. +See [Deploying with tag inclusion/exclusion](./tags.md#deploying-with-tag-inclusionexclusion) for details. + +```yaml +deployments: +- path: kustomizeDeployment1 + alwaysDeploy: true +- path: kustomizeDeployment2 +``` + +#### skipDeleteIfTags +Forces exclusion of a deployment whenever inclusion/exclusion tags are specified via command line. +See [Deleting with tag inclusion/exclusion](./tags.md#deleting-with-tag-inclusionexclusion) for details. + +```yaml +deployments: +- path: kustomizeDeployment1 + skipDeleteIfTags: true +- path: kustomizeDeployment2 +``` + +### vars (deployment project) +A list of additional sets of variables to be added to the Jinja2 context of all deployments (and sub-deployment) of the +current deployment project. + +See [jinja2-templating](./jinja2-templating.md#vars-from-deploymentyml) for more details. ### commonLabels A dictionary of [labels](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/) and values to be @@ -170,11 +235,9 @@ through a specific deployment project. Consider the following example `deployment.yml`: ```yaml -kustomizeDirs: +deployments: - path: nginx - -includes: - - path: sub-deployment1 + - include: sub-deployment1 commonLabels: my.prefix/deployment-name: my-deployment-project-name @@ -215,7 +278,7 @@ to delete. This might then cause deletion of object that are NOT related to your A string that is used as the default namespace for all kustomize deployments which don't have a `namespace` set in their `kustomization.yml`. -### tags +### tags (deployment project) A list of common tags which are applied to all kustomize deployments and sub-deployment includes. See [tags](./tags.md) for more details. @@ -226,8 +289,8 @@ in Jinja2 templating via the global `args` object. Only the root `deployment.yml An example looks like this: ```yaml -kustomizeDirs: - - name: nginx +deployments: + - path: nginx args: - name: environment @@ -258,7 +321,7 @@ that includes Go templates, which will in most cases make Jinja2 templating fail A list of objects and fields to ignore while performing diffs. Consider the following example: ```yaml -kustomizeDirs: +deployments: - ... ignoreForDiff: diff --git a/docs/getting-started.md b/docs/getting-started.md index fa96fd6bd..5ace95a26 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -6,11 +6,6 @@ This is a short documentation on how to get started before actually deploying yo You need to install a set of command line tools to fully use kluctl. These are: -1. [kustomize](https://kubectl.docs.kubernetes.io/installation/kustomize/)
- It is currently not possible to use the kubectl integration of kustomize. This means, that you must install the - kustomize binaries. This might change in the future. -1. [kubeseal](https://github.com/bitnami-labs/sealed-secrets/releases)
- Follow the "Client side" instructions for the latest release 1. [helm](https://github.com/helm/helm/releases)
Download the binaries for your system, make them executable and make them globally available (modify your PATH or copy it into /usr/local/bin) @@ -23,7 +18,7 @@ The first step is of course: You need a kubernetes cluster. It doesn't really ma it's a managed cluster, or a self-hosted cluster, kops or kubespray based, AWS, GCE, Azure, ... and so on. kluctl is completely independent of how Kubernetes is deployed and where it is hosted. -There is however a minimum Kubenetes version that must be met: 1.20.0. This is due to the heavy use of server-side apply +There is however a minimum Kubernetes version that must be met: 1.20.0. This is due to the heavy use of server-side apply which was not stable enough in older versions of Kubernetes. ## Prepare your kubeconfig @@ -39,25 +34,11 @@ you used, you might also be able to directly export the context into your local [kops](https://github.com/kubernetes/kops/blob/master/docs/cli/kops_export.md) is able to export and merge the kubeconfig for a given cluster. -## Bootstrap the cluster - -A cluster that is managed by kluctl needs a bootstrap deployment first. This bootstrap deployment installs mandatory -things into the cluster (e.g. the sealed-secrets controller). The bootstrap deployment is located under the kluctl -Git repository in the `kluctl/bootstrap` directory. You can easily deploy it with this kluctl invocation: - -```shell -$ kluctl bootstrap --cluster test.example.com -``` - -The bootstrap deployment might need to be updated from time to time. Simply re-do the above command whenever a new -kluctl release is available. - ## Example projects Now you can play around with the projects within the `examples` folder. In order to have fun with a very simple example, just install [kind](https://kind.sigs.k8s.io/), create a [cluster](https://kind.sigs.k8s.io/docs/user/quick-start/#creating-a-cluster) and run the following commands: ``` cd examples/simple -kluctl bootstrap --cluster kind kluctl diff --target dev kluctl deploy --target dev ``` \ No newline at end of file diff --git a/docs/jinja2-templating.md b/docs/jinja2-templating.md index 343d260ca..80cdd4097 100644 --- a/docs/jinja2-templating.md +++ b/docs/jinja2-templating.md @@ -10,13 +10,16 @@ is possible and how to use it. ## vars from deployment.yml -[vars](./deployments.md#vars) can be used to add more variables to the deployment project. These are then available -for every kustomize deployment of the current deployment project and also inside all included sub-deployments. +There are multiple places in deployment projects (deployment.yml) where additional variables can be loaded into +future Jinja2 contexts. -These are however not available while the `deployment.yml` is rendered that defines these vars. This file is -rendered and interpreted before the `vars` are processed and added to the Jinja2 context. +The first place where vars can be specified is the deployment root, as documented [here](./deployments.md#vars-deployment-project). +These vars are visible for all deployments inside the deployment project, including sub-deployments from includes. -However, each entry in `vars` can use all variables defined before that specific file is processed. Consider the +The second place to specify variables is in the deployment items, as documented [here](./deployments.md#vars-deployment-item). + +The variables loaded for each entry in `vars` are not available inside the `deployment.yml` file itself. +However, each entry in `vars` can use all variables defined before that specific entry is processed. Consider the following example. ```yaml @@ -48,7 +51,7 @@ vars: - file: vars1.yml ``` -After which all included kustomizeDirs and sub-deployments can use the jinja2 variables from `vars1.yml`. +After which all included deployments and sub-deployments can use the jinja2 variables from `vars1.yml`. ### values An inline definition of variables. Example: @@ -60,7 +63,7 @@ vars: b: c ``` -These variables can then be used in all kustomizeDirs and sub-deployments. +These variables can then be used in all deployments and sub-deployments. ### clusterConfigMap Loads a configmap from the target's cluster and loads the specified key's value as a yaml file into the jinja2 variables @@ -227,7 +230,7 @@ data: ### get_var(field_path, default) Convenience method to navigate through the current context variables via a [JSON Path](https://goessner.net/articles/JsonPath/). Let's assume you currently have these variables defines -(e.g. via [vars](./deployments.md#vars)): +(e.g. via [vars](./deployments.md#vars-deployment-project)): ```yaml my: deep: @@ -263,3 +266,18 @@ The path given to include/import is treated as relative to the template that is [Jinja2 macros](https://jinja.palletsprojects.com/en/2.11.x/templates/#macros) are fully supported. When writing macros that produce yaml resources, you must use the `---` yaml separator in case you want to produce multiple resources in one go. + +# Why no Go Templating + +kluctl started as a python project and was then migrated to be a Go project. In the python world, Jinja2 is the obvious +choice when it comes to templating. In the Go world, of course Go Templates would be the first choice. + +When the migration to Go was performed, it was a conscious and opinionated decision to stick with Jinja2 templating. +The reason is that I (@codablock) believe that Go Templates are hard to read and write and at the same time quite limited +in their features (without extensive work). It never felt natural to write Go Templates. + +This "feeling" was confirmed by multiple users of kluctl when it started and users described as "relieving" to not +be forced to use Go Templates. + +The above is my personal experience and opinion. I'm still quite open for contributions in regard to Go Templating +support, as long as Jinja2 support is kept. diff --git a/docs/kluctl_project.md b/docs/kluctl_project.md index 4c2c962c8..273c47a9b 100644 --- a/docs/kluctl_project.md +++ b/docs/kluctl_project.md @@ -203,6 +203,27 @@ secretsConfig: passwordField: field-name # This is optional and defaults to GenericField1 ``` +#### systemEnvVars +Load secrets from environment variables. Children of `systemEnvVars` can be arbitrary yaml, e.g. dictionaries or lists. +The leaf values are used to get a value from the system environment. + +Example: +```yaml +secretsConfig: + secretSets: + - name: prod + sources: + - systemEnvVars: + var1: ENV_VAR_NAME1 + someDict: + var2: ENV_VAR_NAME2 + someList: + - var3: ENV_VAR_NAME3 +``` + +The above example will make 3 secret variables available: `secrets.var1`, `secrets.someDict.var2` and +`secrets.someList[0].var3`, each having the values of the environment variables specified by the leaf values. + #### awsSecretsManager [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) integration. Loads a secrets YAML from an AWS Secrets Manager secret. The secret can either be specified via an ARN or via a secretName and region combination. An AWS @@ -308,9 +329,9 @@ once and not for all dynamic targets. # Separating kluctl projects and deployment projects -As seen in the `.kluctl.yml` documentation, deployment projects can rely in other repositories then the kluctl project. +As seen in the `.kluctl.yml` documentation, deployment projects can exist in other repositories then the kluctl project. This is a desired pattern in some circumstances, for example when you want to share a single deployment project with -multiple teams that all manage their own clusters. This way each team can have its own minimalistic kluct project which +multiple teams that all manage their own clusters. This way each team can have its own minimalistic kluctl project which points to the deployment project and the teams clusters configuration. This way secret sources can also differ between teams and sharing can be reduced to a minimum if desired. diff --git a/docs/sealed-secrets.md b/docs/sealed-secrets.md index c7a91e786..8678168bd 100644 --- a/docs/sealed-secrets.md +++ b/docs/sealed-secrets.md @@ -9,20 +9,23 @@ The integration consists of two parts: # Requirements -The cluster must have the [sealed-secrets operator](https://github.com/bitnami-labs/sealed-secrets) installed. This -can either be done through the [bootstrap command](./commands.md#bootstrap) or through some other/custom means. +The Sealed Secrets integration relies on the [sealed-secrets operator](https://github.com/bitnami-labs/sealed-secrets) +being installed. Installing the operator is the responsibility of you (or whoever is managing/operating the cluster). + +kluctl can however perform sealing of secrets without an existing sealed-secrets operator installation. This is solved +by automatically pre-provisioning a key onto the cluster that is compatible with the operator. # Sealing of .sealme files Sealing is done via the [seal command](./commands.md#seal). It must be done before the actual deployment is performed. The `seal` command recursively searches for files that end with `.sealme`, renders them with the -[Jinja2 templating](./jinja2-templating.md) engine and then seals them by invoking `kubeseal` (part of -[sealed secrets](https://github.com/bitnami-labs/sealed-secrets)). +[Jinja2 templating](./jinja2-templating.md) engine. The rendered secret resource is then converted/encrypted into a +sealed secret. The `.sealme` files itself have to be [Kubernetes Secrets](https://kubernetes.io/docs/concepts/configuration/secret/), but without any actual secret data inside. The secret data is referenced via Jinja2 variables and is expected to be provided only at the time of sealing. This means, that the sensitive secret data must only be in clear text while sealing. -Afterwards, the sensitive data can be removed locally, and the sealed secrets can be added to version control. +Afterwards the sealed secrets can be added to version control. Example file (the name could be for example `db-secrets.yml.sealme`): ```yaml @@ -92,6 +95,10 @@ Sealed secrets are stored together with hashes of all individual secret entries. unnecessary re-sealing in future [seal](./commands.md#seal) invocations. If you want to force re-sealing, use the [--force-reseal](./commands.md#seal) option. +Hashing of secrets is done with bcrypt and the cluster id as salt. The cluster id is currently defined as the sha256 hash +of the cluster CA certificate. This will cause re-sealing of all secrets in case a cluster is set up from scratch +(which causes key from the sealed secrets operator to get wiped as well). + # Clusters and namespaces Sealed secrets are usually only decryptable by one cluster, simply because each cluster has its own set of randomly generated public/private key pairs. This means, that a secret that was sealed for your production cluster can't be diff --git a/docs/tags.md b/docs/tags.md index 143f2904d..ec89159c4 100644 --- a/docs/tags.md +++ b/docs/tags.md @@ -8,16 +8,15 @@ Tags are useful when only one or more specific kustomize deployments need to be ## Default tags -[kustomizeDirs](./deployments.md#kustomizedirs) and [includes](./deployments.md#includes) in deployment projects can -have an optional list of tags assigned. +[deployment items](./deployments.md#deployments) in deployment projects can have an optional list of tags assigned. If this list is completely omitted, one single entry is added by default. This single entry equals to the last element -of the `path` in the `kustomizeDirs`/`includes` entry. +of the `path` in the `deployments` entry. Consider the following example: ```yaml -kustomizeDirs: +deployments: - path: nginx - path: some/subdir ``` @@ -30,25 +29,25 @@ or even conflicting tags (e.g. `subdir` is really a bad tag), in which case you' ## Tag inheritance -Deployment projects and kustomize deployments inherit the tags of their parents. For example, if a deployment project -has a [tags](./deployments.md#tags-2) field defined, all `kustomizeDirs` entries and all `includes` entries would -inherit all these tags. Also, the sub-deployment projects included via `includes` inherit the tags of the `includes` -entry (which might have been inherited as described before), leading to further inheritance by deeper `kustomizeDirs` -and `includes`. +Deployment projects and deployments items inherit the tags of their parents. For example, if a deployment project +has a [tags](./deployments.md#tags-deployment-project) property defined, all `deployments` entries would +inherit all these tags. Also, the sub-deployment projects included via deployment items of type +[include](./deployments.md#includes) inherit the tags of the deployment project. These included sub-deployments also +inherit the [tags](./deployments.md#tags-deployment-item) specified by the deployment item itself. Consider the following example `deployment.yml`: ```yaml -includes: - - path: sub-deployment1 +deployments: + - include: sub-deployment1 tags: - tag1 - tag2 - - path: sub-deployment2 + - include: sub-deployment2 tags: - tag3 - tag4 - - path: subdir/subsub + - include: subdir/subsub ``` Any kustomize deployment found in `sub-deployment1` would now inherit `tag1` and `tag2`. If `sub-deployment1` performs From 5430c6af75e1299c567978d63e196296b6daa0fe Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 3 Mar 2022 10:17:41 +0100 Subject: [PATCH 0428/2916] fix: Update modules --- go.mod | 81 ++++++++++++++++++++++---------------------- go.sum | 104 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+), 40 deletions(-) diff --git a/go.mod b/go.mod index 484364468..f715c56e1 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,10 @@ go 1.17 require ( github.com/alecthomas/kong v0.4.1 - github.com/aws/aws-sdk-go v1.27.0 + github.com/aws/aws-sdk-go v1.43.10 github.com/bitnami-labs/sealed-secrets v0.17.3 - github.com/evanphx/json-patch v4.12.0+incompatible + github.com/docker/distribution v2.8.0+incompatible + github.com/evanphx/json-patch v5.6.0+incompatible github.com/gammazero/workerpool v1.1.2 github.com/go-git/go-git/v5 v5.4.2 github.com/go-playground/validator/v10 v10.10.0 @@ -18,33 +19,34 @@ require ( github.com/hexops/gotextdiff v1.0.3 github.com/jinzhu/copier v0.3.5 github.com/mitchellh/go-ps v1.0.0 - github.com/ohler55/ojg v1.12.12 + github.com/ohler55/ojg v1.12.14 github.com/pkg/errors v0.9.1 github.com/r3labs/diff/v2 v2.15.0 github.com/sirupsen/logrus v1.8.1 github.com/whilp/git-urls v1.0.0 - golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 - golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e + golang.org/x/crypto v0.0.0-20220214200702-86341886e292 + golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b - k8s.io/api v0.23.3 - k8s.io/apimachinery v0.23.3 - k8s.io/client-go v0.23.3 + k8s.io/api v0.23.4 + k8s.io/apimachinery v0.23.4 + k8s.io/client-go v0.23.4 sigs.k8s.io/kind v0.11.1 - sigs.k8s.io/kustomize/api v0.11.1 + sigs.k8s.io/kustomize/api v0.11.2 sigs.k8s.io/structured-merge-diff/v4 v4.2.1 ) require ( - cloud.google.com/go v0.99.0 // indirect + cloud.google.com/go v0.100.2 // indirect + cloud.google.com/go/compute v1.5.0 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect - github.com/Azure/go-autorest/autorest v0.11.20 // indirect - github.com/Azure/go-autorest/autorest/adal v0.9.15 // indirect + github.com/Azure/go-autorest/autorest v0.11.24 // indirect + github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect - github.com/BurntSushi/toml v0.3.1 // indirect - github.com/Microsoft/go-winio v0.5.1 // indirect - github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 // indirect + github.com/BurntSushi/toml v1.0.0 // indirect + github.com/Microsoft/go-winio v0.5.2 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20220113124808-70ae35bab23f // indirect github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/acomagu/bufpipe v1.0.3 // indirect @@ -52,41 +54,40 @@ require ( github.com/containerd/stargz-snapshotter/estargz v0.10.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/docker/cli v20.10.12+incompatible // indirect - github.com/docker/distribution v2.7.1+incompatible // indirect github.com/docker/docker v20.10.12+incompatible // indirect github.com/docker/docker-credential-helpers v0.6.4 // indirect github.com/emirpasic/gods v1.12.0 // indirect - github.com/evanphx/json-patch/v5 v5.2.0 // indirect + github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/fatih/color v1.13.0 // indirect - github.com/gammazero/deque v0.1.0 // indirect - github.com/go-errors/errors v1.0.1 // indirect + github.com/gammazero/deque v0.1.1 // indirect + github.com/go-errors/errors v1.4.2 // indirect github.com/go-git/gcfg v1.5.0 // indirect github.com/go-git/go-billy/v5 v5.3.1 // indirect - github.com/go-logr/logr v1.2.0 // indirect + github.com/go-logr/logr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect - github.com/go-openapi/jsonreference v0.19.5 // indirect - github.com/go-openapi/swag v0.19.14 // indirect + github.com/go-openapi/jsonreference v0.19.6 // indirect + github.com/go-openapi/swag v0.21.1 // indirect github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt/v4 v4.0.0 // indirect + github.com/golang-jwt/jwt/v4 v4.3.0 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/btree v1.0.1 // indirect - github.com/google/go-cmp v0.5.6 // indirect - github.com/google/gofuzz v1.1.0 // indirect + github.com/google/go-cmp v0.5.7 // indirect + github.com/google/gofuzz v1.2.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/googleapis/gnostic v0.5.5 // indirect - github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect + github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect - github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect + github.com/kevinburke/ssh_config v1.1.0 // indirect github.com/klauspost/compress v1.13.6 // indirect github.com/leodido/go-urn v1.2.1 // indirect - github.com/mailru/easyjson v0.7.6 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect @@ -98,31 +99,31 @@ require ( github.com/pelletier/go-toml v1.9.4 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/sergi/go-diff v1.1.0 // indirect + github.com/sergi/go-diff v1.2.0 // indirect github.com/spf13/cobra v1.3.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/testify v1.7.0 // indirect github.com/vbatts/tar-split v0.11.2 // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect - github.com/xanzy/ssh-agent v0.3.0 // indirect - github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect - go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect - golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d // indirect - golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect + github.com/xanzy/ssh-agent v0.3.1 // indirect + github.com/xlab/treeprint v1.1.0 // indirect + go.starlark.net v0.0.0-20220302181546-5411bad688d1 // indirect + golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect + golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.7 // indirect - golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect + golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.27.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/klog/v2 v2.30.0 // indirect - k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect - k8s.io/utils v0.0.0-20211116205334-6203023598ed // indirect - sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect + k8s.io/klog/v2 v2.40.1 // indirect + k8s.io/kube-openapi v0.0.0-20220124234850-424119656bbf // indirect + k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect + sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect sigs.k8s.io/kustomize/kyaml v0.13.3 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index abed9d001..0e9a2756d 100644 --- a/go.sum +++ b/go.sum @@ -29,12 +29,18 @@ cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Ud cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM= cloud.google.com/go v0.99.0 h1:y/cM2iqGgGi5D5DQZl6D9STN/3dR/Vx5Mp8s752oJTY= cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go v0.100.2 h1:t9Iw5QH5v4XtlEQaCtUY7x6sCABps8sW0acw7e2WQ6Y= +cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= +cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= +cloud.google.com/go/compute v1.5.0 h1:b1zWmYuuHz7gO9kDcM/EpHGr06UgsYNRpNJzI2kFiLM= +cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= @@ -58,11 +64,15 @@ github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKn github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= github.com/Azure/go-autorest/autorest v0.11.20 h1:s8H1PbCZSqg/DH7JMlOz6YMig6htWLNPsjDdlLqCx3M= github.com/Azure/go-autorest/autorest v0.11.20/go.mod h1:o3tqFY+QR40VOlk+pV4d77mORO64jOXSgEnPQgLK6JY= +github.com/Azure/go-autorest/autorest v0.11.24 h1:1fIGgHKqVm54KIPT+q8Zmd1QlVsmHqeUGso5qm2BqqE= +github.com/Azure/go-autorest/autorest v0.11.24/go.mod h1:G6kyRlFnTuSbEYkQGawPfsCswgme4iYf6rfSKUDzbCc= github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= github.com/Azure/go-autorest/autorest/adal v0.9.15 h1:X+p2GF0GWyOiSmqohIaEeuNFNDY4I4EOlVuUQvFdWMk= github.com/Azure/go-autorest/autorest/adal v0.9.15/go.mod h1:tGMin8I49Yij6AQ+rvV+Xa/zwxYQB5hmsd6DkfAx2+A= +github.com/Azure/go-autorest/autorest/adal v0.9.18 h1:kLnPsRjzZZUF3K5REu/Kc+qMQrvuza2bwSnNdhmzLfQ= +github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= @@ -75,6 +85,8 @@ github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUM github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU= +github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= @@ -86,8 +98,11 @@ github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugX github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY= github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= @@ -102,6 +117,8 @@ github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb0 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C67SkzkDfmQuVln04ygHj3vjZfd9FL+GmQQ= github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= +github.com/ProtonMail/go-crypto v0.0.0-20220113124808-70ae35bab23f h1:J2FzIrXN82q5uyUraeJpLIm7U6PffRwje2ORho5yIik= +github.com/ProtonMail/go-crypto v0.0.0-20220113124808-70ae35bab23f/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= @@ -146,6 +163,8 @@ github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQ github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/aws/aws-sdk-go v1.27.0 h1:0xphMHGMLBrPMfxR2AmVjZKcMEESEgWF8Kru94BNByk= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.43.10 h1:lFX6gzTBltYBnlJBjd2DWRCmqn2CbTcs6PW99/Dme7k= +github.com/aws/aws-sdk-go v1.43.10/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -330,6 +349,8 @@ github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TT github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/distribution v2.8.0+incompatible h1:l9EaZDICImO1ngI+uTifW+ZYvvz7fKISBAKpg+MbWbY= +github.com/docker/distribution v2.8.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v20.10.12+incompatible h1:CEeNmFM0QZIsJCZKMkZx0ZcahTiewkrgiwfYD+dfl1U= github.com/docker/docker v20.10.12+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.6.4 h1:axCks+yV+2MR3/kZhAmy07yC56WZ2Pwu/fKWtKuZB0o= @@ -370,8 +391,12 @@ github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLi github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= +github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.2.0 h1:8ozOH5xxoMYDt5/u+yMTsVXydVCbTORFnOOoq2lumco= github.com/evanphx/json-patch/v5 v5.2.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= +github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= +github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= @@ -390,6 +415,8 @@ github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5 github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= github.com/gammazero/deque v0.1.0 h1:f9LnNmq66VDeuAlSAapemq/U7hJ2jpIWa4c09q8Dlik= github.com/gammazero/deque v0.1.0/go.mod h1:KQw7vFau1hHuM8xmI9RbgKFbAsQFWmBpqQ2KenFLk6M= +github.com/gammazero/deque v0.1.1 h1:xRVkDuSvDmFuMGf3IquHuRc2jlL0+v/WpFCWaauzwbE= +github.com/gammazero/deque v0.1.1/go.mod h1:KQw7vFau1hHuM8xmI9RbgKFbAsQFWmBpqQ2KenFLk6M= github.com/gammazero/workerpool v1.1.2 h1:vuioDQbgrz4HoaCi2q1HLlOXdpbap5AET7xu5/qj87g= github.com/gammazero/workerpool v1.1.2/go.mod h1:UelbXcO0zCIGFcufcirHhq2/xtLXJdQ29qZNlXG9OjQ= github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= @@ -400,6 +427,8 @@ github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= @@ -424,6 +453,8 @@ github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTg github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0 h1:QK40JKJyMdUDz+h+xvCsru/bJhvG0UxvePV0ufL/AcE= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.2 h1:ahHml/yUpnlb96Rp8HCvtYVPY8ZYpxq3g7UYchIYwbs= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= @@ -434,6 +465,8 @@ github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwoh github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM= github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= +github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= +github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= @@ -442,6 +475,8 @@ github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU= +github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= @@ -479,6 +514,9 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.0.0 h1:RAqyYixv1p7uEnocuy8P1nru5wprCh/MH2BIlW5z5/o= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang-jwt/jwt/v4 v4.3.0 h1:kHL1vqdqWNfATmA0FNMdmZNMyZI1U6O31X4rlIPoBog= +github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -534,12 +572,16 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-containerregistry v0.8.0 h1:mtR24eN6rapCN+shds82qFEIWWmg64NPMuyCNT7/Ogc= github.com/google/go-containerregistry v0.8.0/go.mod h1:wW5v71NHGnQyb4k+gSshjxidrC7lN33MdWEn+Mz9TsI= github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -586,6 +628,8 @@ github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoA github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= @@ -663,6 +707,9 @@ github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= @@ -683,6 +730,8 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kevinburke/ssh_config v1.1.0 h1:pH/t1WS9NzT8go394IqZeJTMHVm6Cr6ZJ6AQ+mdNo/o= +github.com/kevinburke/ssh_config v1.1.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -722,6 +771,8 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= @@ -802,6 +853,8 @@ github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/ohler55/ojg v1.12.12 h1:hepbQFn7GHAecTPmwS3j5dCiOLsOpzPLvhiqnlAVAoE= github.com/ohler55/ojg v1.12.12/go.mod h1:LBbIVRAgoFbYBXQhRhuEpaJIqq+goSO63/FQ+nyJU88= +github.com/ohler55/ojg v1.12.14 h1:4c9+t9iSu12QaOHK7GfIULanFEiVj4d6/wxQuvFuvig= +github.com/ohler55/ojg v1.12.14/go.mod h1:LBbIVRAgoFbYBXQhRhuEpaJIqq+goSO63/FQ+nyJU88= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= @@ -948,6 +1001,8 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= @@ -1041,12 +1096,16 @@ github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= +github.com/xanzy/ssh-agent v0.3.1 h1:AmzO1SSWxw73zxFZPRwaMN1MohDw8UyHnmuxyceTEGo= +github.com/xanzy/ssh-agent v0.3.1/go.mod h1:QIE4lCeL7nkC25x+yA3LBIYfwCc1TFziCtG7cBAac6w= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca h1:1CFlNzQhALwjS9mBAUkycX616GzgsuYUOCHA5+HSlXI= github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= +github.com/xlab/treeprint v1.1.0 h1:G/1DjNkPpfZCFt9CSh6b5/nY4VimlbHF3Rh4obvtzDk= +github.com/xlab/treeprint v1.1.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -1081,6 +1140,8 @@ go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee331t6JAXeK2bcyhLOOc= go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= +go.starlark.net v0.0.0-20220302181546-5411bad688d1 h1:i0Sz4b+qJi5xwOaFZqZ+RNHkIpaKLDofei/Glt+PMNc= +go.starlark.net v0.0.0-20220302181546-5411bad688d1/go.mod h1:t3mmBBPzAVvK0L0n1drDmrQsJ8FoIx4INCqVMTr/Zo0= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -1113,8 +1174,12 @@ golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 h1:/pEO3GD/ABYAjuakUS6xSEmmlyVS4kxBNkeA9tLJiTI= golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE= +golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1208,6 +1273,7 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= +golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -1217,6 +1283,9 @@ golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d h1:62NvYBuaanGXR2ZOfwDFkhhl6X1DUgf8qg3GuQvxZsE= golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1236,6 +1305,8 @@ golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b h1:clP8eMhB30EHdc0bd2Twtq6kgU7yl5ub2cQLSdrv1Dg= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1338,6 +1409,7 @@ golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1357,8 +1429,13 @@ golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1384,6 +1461,8 @@ golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 h1:M73Iuj3xbbb9Uk1DYhzydthsj6oOd6l9bpuFcNoUvTs= +golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1496,6 +1575,9 @@ google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdr google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU= google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw= +google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= +google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= +google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1574,6 +1656,11 @@ google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ6 google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -1609,6 +1696,7 @@ google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9K google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -1685,6 +1773,8 @@ k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= k8s.io/api v0.22.1/go.mod h1:bh13rkTp3F1XEaLGykbyRD2QaTTzPm0e/BMd8ptFONY= k8s.io/api v0.23.3 h1:KNrME8KHGr12Ozjf8ytOewKzZh6hl/hHUZeHddT3a38= k8s.io/api v0.23.3/go.mod h1:w258XdGyvCmnBj/vGzQMj6kzdufJZVUwEM1U2fRJwSQ= +k8s.io/api v0.23.4 h1:85gnfXQOWbJa1SiWGpE9EEtHs0UVvDyIsSMpEtl2D4E= +k8s.io/api v0.23.4/go.mod h1:i77F4JfyNNrhOjZF7OwwNJS5Y1S9dpwvb9iYRYRczfI= k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.2/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= @@ -1692,6 +1782,8 @@ k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MA k8s.io/apimachinery v0.22.1/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= k8s.io/apimachinery v0.23.3 h1:7IW6jxNzrXTsP0c8yXz2E5Yx/WTzVPTsHIx/2Vm0cIk= k8s.io/apimachinery v0.23.3/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM= +k8s.io/apimachinery v0.23.4 h1:fhnuMd/xUL3Cjfl64j5ULKZ1/J9n8NuQEgNL+WXWfdM= +k8s.io/apimachinery v0.23.4/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM= k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= @@ -1701,6 +1793,8 @@ k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= k8s.io/client-go v0.22.1/go.mod h1:BquC5A4UOo4qVDUtoc04/+Nxp1MeHcVc1HJm1KmG8kk= k8s.io/client-go v0.23.3 h1:23QYUmCQ/W6hW78xIwm3XqZrrKZM+LWDqW2zfo+szJs= k8s.io/client-go v0.23.3/go.mod h1:47oMd+YvAOqZM7pcQ6neJtBiFH7alOyfunYN48VsmwE= +k8s.io/client-go v0.23.4 h1:YVWvPeerA2gpUudLelvsolzH7c2sFoXXR5wM/sWqNFU= +k8s.io/client-go v0.23.4/go.mod h1:PKnIL4pqLuvYUK1WU7RLTMYKPiIh7MYShLshtRY9cj0= k8s.io/code-generator v0.16.8/go.mod h1:wFdrXdVi/UC+xIfLi+4l9elsTT/uEF61IfcN2wOLULQ= k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= @@ -1722,17 +1816,23 @@ k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= k8s.io/klog/v2 v2.30.0 h1:bUO6drIvCIsvZ/XFgfxoGFQU/a4Qkh0iAlvUR7vlHJw= k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.40.1 h1:P4RRucWk/lFOlDdkAr3mc7iWFkgKrZY9qZMAgek06S4= +k8s.io/klog/v2 v2.40.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 h1:E3J9oCLlaobFUqsjG9DfKbP2BmgwBL2p7pn0A3dG9W4= k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= +k8s.io/kube-openapi v0.0.0-20220124234850-424119656bbf h1:M9XBsiMslw2lb2ZzglC0TOkBPK5NQi0/noUrdnoFwUg= +k8s.io/kube-openapi v0.0.0-20220124234850-424119656bbf/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210707171843-4b05e18ac7d9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20211116205334-6203023598ed h1:ck1fRPWPJWsMd8ZRFsWc6mh/zHp5fZ/shhbrgPUxDAE= k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 h1:HNSDgDCrr/6Ly3WEGKZftiE7IY19Vz2GdbOCyI4qqhc= +k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= @@ -1745,10 +1845,14 @@ sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyz sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 h1:fD1pz4yfdADVNfFmcP2aBEtudwUQ1AlLnRBALr33v3s= sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= +sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 h1:kDi4JBNAsJWfz1aEXhO8Jg87JJaPNLh5tIzYHgStQ9Y= +sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY= sigs.k8s.io/kind v0.11.1 h1:pVzOkhUwMBrCB0Q/WllQDO3v14Y+o2V0tFgjTqIUjwA= sigs.k8s.io/kind v0.11.1/go.mod h1:fRpgVhtqAWrtLB9ED7zQahUimpUXuG/iHT88xYqEGIA= sigs.k8s.io/kustomize/api v0.11.1 h1:/Vutu+gAqVo8skw1xCZrsZD39SN4Adg+z7FrSTw9pds= sigs.k8s.io/kustomize/api v0.11.1/go.mod h1:GZuhith5YcqxIDe0GnRJNx5xxPTjlwaLTt/e+ChUtJA= +sigs.k8s.io/kustomize/api v0.11.2 h1:6YvCJHFDwsLwAX7zNHBxMZi3k7dGIXI8G9l0saYQI0E= +sigs.k8s.io/kustomize/api v0.11.2/go.mod h1:GZuhith5YcqxIDe0GnRJNx5xxPTjlwaLTt/e+ChUtJA= sigs.k8s.io/kustomize/kyaml v0.13.3 h1:tNNQIC+8cc+aXFTVg+RtQAOsjwUdYBZRAgYOVI3RBc4= sigs.k8s.io/kustomize/kyaml v0.13.3/go.mod h1:/ya3Gk4diiQzlE4mBh7wykyLRFZNvqlbh+JnwQ9Vhrc= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e h1:4Z09Hglb792X0kfOBBJUPFEyvVfQWrYT/l8h5EKA6JQ= From 123a6a31a6f812a803decdc1fe04e8b9673a2324 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 3 Mar 2022 11:42:06 +0100 Subject: [PATCH 0429/2916] fix: Fix --help output --- cmd/kluctl/commands/root.go | 23 +++++++++++------------ docs/commands.md | 28 ++++++++++++++-------------- e2e/cmd_wrapper_test.go | 2 +- hack/replace-commands-help/main.go | 7 ++----- 4 files changed, 28 insertions(+), 32 deletions(-) diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index bfa139b67..3b43016ec 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -58,7 +58,7 @@ var flagGroups = []kong.Group{ {Key: "images", Title: "Image arguments:", Description: "Control fixed images and update behaviour."}, {Key: "inclusion", Title: "Inclusion/Exclusion arguments:", Description: "Control inclusion/exclusion."}, {Key: "misc", Title: "Misc arguments:", Description: "Command specific arguments."}, - {Key: "global", Title: "Global arguments:", Description: "Global arguments."}, + {Key: "global", Title: "Global arguments:"}, } var globalFlagGroup = &flagGroups[len(flagGroups)-1] @@ -139,7 +139,7 @@ The missing glue to put together large Kubernetes deployments, composed of multiple smaller parts (Helm/Kustomize/...) in a manageable and unified way.` } -func ParseArgs(args []string, options ...kong.Option) (*kong.Context, error) { +func ParseArgs(args []string, options ...kong.Option) (*kong.Kong, *kong.Context, error) { var cli cli helpOption := kong.HelpOptions{ @@ -151,6 +151,8 @@ func ParseArgs(args []string, options ...kong.Option) (*kong.Context, error) { var options2 []kong.Option options2 = append(options2, helpOption) options2 = append(options2, kong.ExplicitGroups(flagGroups)) + options2 = append(options2, kong.ShortUsageOnError()) + options2 = append(options2, kong.Name("kluctl")) options2 = append(options2, options...) parser, err := kong.New(&cli, options2...) @@ -159,22 +161,19 @@ func ParseArgs(args []string, options ...kong.Option) (*kong.Context, error) { } parser.Model.HelpFlag.Group = globalFlagGroup ctx, err := parser.Parse(args) - return ctx, err + return parser, ctx, err } func Execute() { confOption := kong.Configuration(kong.JSON, "/etc/kluctl.json", "~/.kluctl/config.json") - ctx, err := ExecuteWithArgs(os.Args[1:], confOption) - if err != nil && ctx == nil { - log.Fatal(err) - } - ctx.FatalIfErrorf(err) + parser, _, err := ExecuteWithArgs(os.Args[1:], confOption) + parser.FatalIfErrorf(err) } -func ExecuteWithArgs(args []string, options ...kong.Option) (*kong.Context, error) { - ctx, err := ParseArgs(args, options...) +func ExecuteWithArgs(args []string, options ...kong.Option) (*kong.Kong, *kong.Context, error) { + parser, ctx, err := ParseArgs(args, options...) if err != nil { - return nil, err + return parser, ctx, err } - return ctx, ctx.Run() + return parser, ctx, ctx.Run() } diff --git a/docs/commands.md b/docs/commands.md index c40515087..0277e0bb8 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -98,7 +98,7 @@ The following commands are available: ## deploy -Usage: replace-commands-help deploy +Usage: kluctl deploy @@ -163,7 +163,7 @@ and outputs them instead. This option modifies the behaviour to immediately abor ## diff -Usage: replace-commands-help diff +Usage: kluctl diff @@ -199,7 +199,7 @@ Misc arguments: ## delete -Usage: replace-commands-help delete +Usage: kluctl delete @@ -228,7 +228,7 @@ They have the same meaning as described in [deploy](#deploy). ## prune -Usage: replace-commands-help prune +Usage: kluctl prune @@ -257,7 +257,7 @@ They have the same meaning as described in [deploy](#deploy). ## list-images -Usage: replace-commands-help list-images +Usage: kluctl list-images @@ -280,7 +280,7 @@ Misc arguments: ## poke-images -Usage: replace-commands-help poke-images +Usage: kluctl poke-images @@ -306,7 +306,7 @@ Misc arguments: ## downscale -Usage: replace-commands-help downscale +Usage: kluctl downscale @@ -335,7 +335,7 @@ Misc arguments: ## render -Usage: replace-commands-help render +Usage: kluctl render @@ -357,7 +357,7 @@ Misc arguments: ## validate -Usage: replace-commands-help validate +Usage: kluctl validate @@ -383,7 +383,7 @@ Misc arguments: ## seal -Usage: replace-commands-help seal +Usage: kluctl seal @@ -409,7 +409,7 @@ Misc arguments: ## helm-pull -Usage: replace-commands-help helm-pull +Usage: kluctl helm-pull @@ -420,7 +420,7 @@ The following sets of arguments are available: ## helm-update -Usage: replace-commands-help helm-update +Usage: kluctl helm-update @@ -441,7 +441,7 @@ Misc arguments: ## list-targets -Usage: replace-commands-help list-targets +Usage: kluctl list-targets @@ -458,7 +458,7 @@ Misc arguments: ## archive -Usage: replace-commands-help archive +Usage: kluctl archive diff --git a/e2e/cmd_wrapper_test.go b/e2e/cmd_wrapper_test.go index 07be7bb3d..28a8ead34 100644 --- a/e2e/cmd_wrapper_test.go +++ b/e2e/cmd_wrapper_test.go @@ -42,7 +42,7 @@ func doRunCmd() error { _, _ = os.Stderr.WriteString(stdoutEndMarker + "\n") }() - _, err = commands.ExecuteWithArgs(cmdArgs) + _, _, err = commands.ExecuteWithArgs(cmdArgs) return err } diff --git a/hack/replace-commands-help/main.go b/hack/replace-commands-help/main.go index f95b878ce..5f1d7b66b 100644 --- a/hack/replace-commands-help/main.go +++ b/hack/replace-commands-help/main.go @@ -113,11 +113,8 @@ func getHelpSection(command string, section string) []string { } helpBuf := bytes.NewBuffer(nil) - ctx, err := commands.ParseArgs([]string{command, "--help"}, kong.Exit(exitFunc), kong.Writers(helpBuf, helpBuf)) - if err != nil { - log.Fatal(err) - } - _ = ctx + parser, _, err := commands.ParseArgs([]string{command, "--help"}, kong.Exit(exitFunc), kong.Writers(helpBuf, helpBuf)) + parser.FatalIfErrorf(err) lines := strings.Split(helpBuf.String(), "\n") From 2cb1e2a0217ac98519e6ccb64e2760cfcbe89cc6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 3 Mar 2022 11:42:21 +0100 Subject: [PATCH 0430/2916] feat: Implement version command --- cmd/kluctl/commands/cmd_version.go | 14 ++++++++++++++ cmd/kluctl/commands/root.go | 2 ++ 2 files changed, 16 insertions(+) create mode 100644 cmd/kluctl/commands/cmd_version.go diff --git a/cmd/kluctl/commands/cmd_version.go b/cmd/kluctl/commands/cmd_version.go new file mode 100644 index 000000000..7b68c602a --- /dev/null +++ b/cmd/kluctl/commands/cmd_version.go @@ -0,0 +1,14 @@ +package commands + +import ( + "github.com/codablock/kluctl/pkg/version" + "os" +) + +type versionCmd struct { +} + +func (cmd *versionCmd) Run() error { + _, err := os.Stdout.WriteString(version.Version + "\n") + return err +} diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index 3b43016ec..e389e3c50 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -51,6 +51,8 @@ type cli struct { Render renderCmd `cmd:"" help:"Renders all resources and configuration files"` Seal sealCmd `cmd:"" help:"Seal secrets based on target's sealingConfig"` Validate validateCmd `cmd:"" help:"Validates the already deployed deployment"` + + Version versionCmd `cmd:"" help:"Print kluctl version"` } var flagGroups = []kong.Group{ From 7d4043b43691157e133f6254d826c8d723340f64 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 3 Mar 2022 11:42:34 +0100 Subject: [PATCH 0431/2916] fix: Run gofmt --- e2e/project.go | 4 ++-- pkg/git/http-server/http.go | 4 ++-- pkg/registries/registries.go | 14 +++++++------- pkg/utils/env.go | 3 +-- pkg/utils/lib_wrapper/lib_wrapper_unix.go | 2 +- pkg/utils/process/process_posix.go | 2 +- pkg/utils/process/process_windows.go | 2 +- 7 files changed, 15 insertions(+), 16 deletions(-) diff --git a/e2e/project.go b/e2e/project.go index 2a009e35d..230698589 100644 --- a/e2e/project.go +++ b/e2e/project.go @@ -37,7 +37,7 @@ type testProject struct { baseDir string - gitServer *http_server.Server + gitServer *http_server.Server gitHttpServer *http.Server gitServerPort int } @@ -90,7 +90,7 @@ func (p *testProject) initGitServer() { p.gitServer = http_server.New(p.baseDir) p.gitHttpServer = &http.Server{ - Addr: "127.0.0.1:0", + Addr: "127.0.0.1:0", Handler: p.gitServer, } diff --git a/pkg/git/http-server/http.go b/pkg/git/http-server/http.go index d2ec7ddf0..ef5eab9ca 100644 --- a/pkg/git/http-server/http.go +++ b/pkg/git/http-server/http.go @@ -22,7 +22,7 @@ type service struct { } type Server struct { - baseDir string + baseDir string services []service } @@ -188,4 +188,4 @@ func gitCommand(name string, args ...string) (*exec.Cmd, io.Reader) { cmd.Stderr = cmd.Stdout return cmd, r -} \ No newline at end of file +} diff --git a/pkg/registries/registries.go b/pkg/registries/registries.go index 86eab8cc4..e4ded5986 100644 --- a/pkg/registries/registries.go +++ b/pkg/registries/registries.go @@ -40,10 +40,10 @@ var globalMyKeychain myKeychain type myKeychain struct { cachedResponses utils.ThreadSafeMultiCache - authRealms map[string]bool - authErrors map[string]bool - init sync.Once - mutex sync.Mutex + authRealms map[string]bool + authErrors map[string]bool + init sync.Once + mutex sync.Mutex } func (kc *myKeychain) Resolve(resource authn.Resource) (authn.Authenticator, error) { @@ -55,8 +55,8 @@ func (kc *myKeychain) Resolve(resource authn.Resource) (authn.Authenticator, err username, _ := m["USERNAME"] password, _ := m["PASSWORD"] return authn.FromConfig(authn.AuthConfig{ - Username: username, - Password: password, + Username: username, + Password: password, }), nil } } @@ -69,7 +69,7 @@ func (kc *myKeychain) realmFromRequest(req *http.Request) string { } func (kc *myKeychain) RoundTripCached(req *http.Request, extraKey string, onNew func(res *http.Response) error) (*http.Response, error) { - resI, err := kc.cachedResponses.Get(req.Host, req.URL.Path + "#" + extraKey, func() (interface{}, error) { + resI, err := kc.cachedResponses.Get(req.Host, req.URL.Path+"#"+extraKey, func() (interface{}, error) { res, err := remote.DefaultTransport.RoundTrip(req) if err != nil { return nil, err diff --git a/pkg/utils/env.go b/pkg/utils/env.go index f0f609f07..91def48c2 100644 --- a/pkg/utils/env.go +++ b/pkg/utils/env.go @@ -9,7 +9,6 @@ import ( "strings" ) - func ParseEnvConfigSets(prefix string) map[int]map[string]string { ret := make(map[int]map[string]string) @@ -47,4 +46,4 @@ func ParseEnvConfigSets(prefix string) map[int]map[string]string { } } return ret -} \ No newline at end of file +} diff --git a/pkg/utils/lib_wrapper/lib_wrapper_unix.go b/pkg/utils/lib_wrapper/lib_wrapper_unix.go index bb96aff1f..76a549918 100644 --- a/pkg/utils/lib_wrapper/lib_wrapper_unix.go +++ b/pkg/utils/lib_wrapper/lib_wrapper_unix.go @@ -18,7 +18,7 @@ func LoadModule(pth string) *LibWrapper { cPth := C.CString(pth) defer C.free(unsafe.Pointer(cPth)) - mod := C.dlopen(cPth, C.RTLD_LAZY | C.RTLD_GLOBAL) + mod := C.dlopen(cPth, C.RTLD_LAZY|C.RTLD_GLOBAL) if mod == nil { log.Panicf("dlopen for %s failed", pth) } diff --git a/pkg/utils/process/process_posix.go b/pkg/utils/process/process_posix.go index 17937422e..a5e09baf5 100644 --- a/pkg/utils/process/process_posix.go +++ b/pkg/utils/process/process_posix.go @@ -1,4 +1,4 @@ -// +build !windows +//go:build !windows package process diff --git a/pkg/utils/process/process_windows.go b/pkg/utils/process/process_windows.go index 1694a8e7c..e0ef9e9e0 100644 --- a/pkg/utils/process/process_windows.go +++ b/pkg/utils/process/process_windows.go @@ -1,4 +1,4 @@ -// +build windows +//go:build windows package process From 6d8d14a74fc5144ee6d48032facf361b9b923d70 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 3 Mar 2022 12:08:18 +0100 Subject: [PATCH 0432/2916] fix: BeforeApply in kong cli must be AfterApply --- cmd/kluctl/commands/root.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index e389e3c50..59eeda5b1 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -125,7 +125,7 @@ func (c *cli) checkNewVersion() { } } -func (c *cli) BeforeApply() error { +func (c *cli) AfterApply() error { if err := c.setupLogs(); err != nil { return err } From 7da7a4b1b70bbb7ced6dd3b85810820081cc7ef8 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 3 Mar 2022 12:09:34 +0100 Subject: [PATCH 0433/2916] test: Skip update check for tests --- e2e/project.go | 1 + 1 file changed, 1 insertion(+) diff --git a/e2e/project.go b/e2e/project.go index 230698589..b49c10676 100644 --- a/e2e/project.go +++ b/e2e/project.go @@ -498,6 +498,7 @@ const stdoutEndMarker = "========= stdout end =========" func (p *testProject) Kluctl(argsIn ...string) (string, string, error) { var args []string args = append(args, argsIn...) + args = append(args, "--no-update-check") cwd := "" if p.kluctlProjectExternal { From 632a844ed0a0e44669138c6a183b6032cb909c80 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 3 Mar 2022 12:09:44 +0100 Subject: [PATCH 0434/2916] test: Debug logging for windows tests --- e2e/project.go | 1 + 1 file changed, 1 insertion(+) diff --git a/e2e/project.go b/e2e/project.go index b49c10676..ba3fe62bd 100644 --- a/e2e/project.go +++ b/e2e/project.go @@ -520,6 +520,7 @@ func (p *testProject) Kluctl(argsIn ...string) (string, string, error) { sep := ":" if runtime.GOOS == "windows" { sep = ";" + args = append(args, "-vdebug") } env := os.Environ() env = append(env, fmt.Sprintf("KUBECONFIG=%s", strings.Join(p.kubeconfigs, sep))) From 9dfbaaf1cc3f7b95bb3d43b640b9ec4682f767b0 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 3 Mar 2022 12:34:09 +0100 Subject: [PATCH 0435/2916] test: Use t.Log for kluct output printing --- e2e/cmd_wrapper_util.go | 16 ++++++++-------- e2e/project.go | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/e2e/cmd_wrapper_util.go b/e2e/cmd_wrapper_util.go index f72f36a9d..4b22db929 100644 --- a/e2e/cmd_wrapper_util.go +++ b/e2e/cmd_wrapper_util.go @@ -6,9 +6,10 @@ import ( "io" "os" "os/exec" + "testing" ) -func runWrappedCmd(testName string, cwd string, env []string, args []string) (string, string, error) { +func runWrappedCmd(t *testing.T, testName string, cwd string, env []string, args []string) (string, string, error) { executable, err := os.Executable() if err != nil { return "", "", err @@ -34,7 +35,7 @@ func runWrappedCmd(testName string, cwd string, env []string, args []string) (st return "", "", err } - stdReader := func(std *os.File, buf io.StringWriter, pipe io.Reader) { + stdReader := func(testLogPrefix string, buf io.StringWriter, pipe io.Reader) { scanner := bufio.NewScanner(pipe) inMarker := false for scanner.Scan() { @@ -44,16 +45,15 @@ func runWrappedCmd(testName string, cwd string, env []string, args []string) (st inMarker = true continue } - _, _ = std.WriteString(l + "\n") + t.Log(testLogPrefix + l) } else { if l == stdoutEndMarker { inMarker = false continue } - l += "\n" - _, _ = std.WriteString(l) - _, _ = buf.WriteString(l) + t.Log(testLogPrefix + l) + _, _ = buf.WriteString(l + "\n") } } } @@ -61,8 +61,8 @@ func runWrappedCmd(testName string, cwd string, env []string, args []string) (st stdoutBuf := bytes.NewBuffer(nil) stderrBuf := bytes.NewBuffer(nil) - go stdReader(os.Stdout, stdoutBuf, stdoutPipe) - go stdReader(os.Stderr, stderrBuf, stderrPipe) + go stdReader("stdout: ", stdoutBuf, stdoutPipe) + go stdReader("stderr: ", stderrBuf, stderrPipe) err = cmd.Run() return stdoutBuf.String(), stderrBuf.String(), err diff --git a/e2e/project.go b/e2e/project.go index ba3fe62bd..f7dea9fa1 100644 --- a/e2e/project.go +++ b/e2e/project.go @@ -527,7 +527,7 @@ func (p *testProject) Kluctl(argsIn ...string) (string, string, error) { log.Infof("Runnning kluctl: %s", strings.Join(args, " ")) - stdout, stderr, err := runWrappedCmd("TestKluctlWrapper", cwd, env, args) + stdout, stderr, err := runWrappedCmd(p.t,"TestKluctlWrapper", cwd, env, args) return stdout, stderr, err } From 678dca2ee65e42a5edf2d1a559700ec4c76afb6b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 3 Mar 2022 12:34:26 +0100 Subject: [PATCH 0436/2916] test: Run e2e tests in parallel again --- .github/workflows/build-and-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index 1beeed080..e92ec1ee6 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -293,7 +293,7 @@ jobs: - name: Run e2e tests shell: bash run: | - go test ./e2e/... -parallel 1 -v + go test ./e2e/... -parallel 4 -v - name: Delete kind cluster shell: bash run: | From c71cdb00770f6359ad08b203fdc68f8cc4bc8df3 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 3 Mar 2022 14:19:36 +0100 Subject: [PATCH 0437/2916] ci: Don't run tests on tags --- .github/workflows/build-and-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index e92ec1ee6..e69e67658 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -158,6 +158,7 @@ jobs: pkg tests: + if: !startsWith(github.ref, 'refs/tags/') strategy: matrix: include: @@ -315,7 +316,6 @@ jobs: needs: - version - build - - tests steps: - name: Checkout uses: actions/checkout@v2 From bce081dda35fa351ef9c40506d15f81aa15d88a1 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 3 Mar 2022 14:19:55 +0100 Subject: [PATCH 0438/2916] ci: Fix release job --- .github/workflows/build-and-release.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index e69e67658..02d512743 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -328,10 +328,9 @@ jobs: - name: Release uses: softprops/action-gh-release@v1 with: - draft: false - prerelease: true + draft: true body_path: changelog/CHANGELOG.md files: | - kluctl-linux-* - kluctl-darwin-* - kluctl-windows-* + dist-*/kluctl-linux-* + dist-*/kluctl-darwin-* + dist-*/kluctl-windows-* From 30970a5b16f7f9ec61fc8a71f6c6fa8820e3a1c6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 3 Mar 2022 14:20:09 +0100 Subject: [PATCH 0439/2916] tests: Use t.Log in more places --- e2e/project.go | 4 ++-- e2e/utils_test.go | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/e2e/project.go b/e2e/project.go index f7dea9fa1..475d29b3c 100644 --- a/e2e/project.go +++ b/e2e/project.go @@ -525,7 +525,7 @@ func (p *testProject) Kluctl(argsIn ...string) (string, string, error) { env := os.Environ() env = append(env, fmt.Sprintf("KUBECONFIG=%s", strings.Join(p.kubeconfigs, sep))) - log.Infof("Runnning kluctl: %s", strings.Join(args, " ")) + p.t.Logf("Runnning kluctl: %s", strings.Join(args, " ")) stdout, stderr, err := runWrappedCmd(p.t,"TestKluctlWrapper", cwd, env, args) return stdout, stderr, err @@ -534,7 +534,7 @@ func (p *testProject) Kluctl(argsIn ...string) (string, string, error) { func (p *testProject) KluctlMust(argsIn ...string) (string, string) { stdout, stderr, err := p.Kluctl(argsIn...) if err != nil { - log.Error(stderr) + p.t.Logf(stderr) p.t.Fatal(fmt.Errorf("kluctl failed: %w", err)) } return stdout, stderr diff --git a/e2e/utils_test.go b/e2e/utils_test.go index 8f6ed81fc..81f14b2d1 100644 --- a/e2e/utils_test.go +++ b/e2e/utils_test.go @@ -3,7 +3,6 @@ package e2e import ( "github.com/codablock/kluctl/pkg/utils/uo" "github.com/codablock/kluctl/pkg/validation" - log "github.com/sirupsen/logrus" "os/exec" "reflect" "strings" @@ -22,7 +21,7 @@ func deleteTestNamespaces(k *KindCluster) { } func waitForReadiness(t *testing.T, k *KindCluster, namespace string, resource string, timeout time.Duration) bool { - log.Infof("Waiting for readiness: %s/%s", namespace, resource) + t.Logf("Waiting for readiness: %s/%s", namespace, resource) startTime := time.Now() for time.Now().Sub(startTime) < timeout { From d3b2dc741818d8e0b871a0e420aef25059154c98 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 3 Mar 2022 14:21:27 +0100 Subject: [PATCH 0440/2916] ci: Fix workflow syntag error --- .github/workflows/build-and-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index 02d512743..947375b87 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -158,7 +158,7 @@ jobs: pkg tests: - if: !startsWith(github.ref, 'refs/tags/') + if: "!startsWith(github.ref, 'refs/tags/')" strategy: matrix: include: From 150d7f36b6ec455a73b37f3e62a31e9e000284e6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 3 Mar 2022 17:25:14 +0100 Subject: [PATCH 0441/2916] fix: Remove some unnecessary files from the embedded python dist --- hack/build-python-unix.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/hack/build-python-unix.sh b/hack/build-python-unix.sh index 5b8bc3ff1..d82dc9580 100755 --- a/hack/build-python-unix.sh +++ b/hack/build-python-unix.sh @@ -27,10 +27,11 @@ if [ "$os" = "darwin" ]; then export CPPFLAGS="-I$(brew --prefix readline)/include" export LDFLAGS="-L$(brew --prefix readline)/lib" fi -./configure $CONFIGURE_FLAGS --enable-shared --prefix $DIR/../build-python/$os/cpython-install +./configure $CONFIGURE_FLAGS --enable-shared --disable-test-modules --without-static-libpython --prefix $DIR/../build-python/$os/cpython-install make -j4 make install cd .. cd cpython-install -tar czf $DIR/../pkg/python/python-lib-$os.tar.gz lib +find . -name __pycache__ -exec rm -rf {} \; +find . -name '*.a' -exec rm {} \; From 77d004d56297085e66253fa8bb91c2b277eb236c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 3 Mar 2022 17:25:28 +0100 Subject: [PATCH 0442/2916] fix: Only mirror tags and branches --- pkg/git/mirrored_repo.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/git/mirrored_repo.go b/pkg/git/mirrored_repo.go index 1cc7d4784..9cd018f38 100644 --- a/pkg/git/mirrored_repo.go +++ b/pkg/git/mirrored_repo.go @@ -199,7 +199,8 @@ func (g *MirroredGitRepo) cloneOrUpdate() error { Name: "origin", URLs: []string{g.url.String()}, Fetch: []config.RefSpec{ - "+refs/*:refs/*", // same as with "git clone --mirror" + "+refs/heads/*:refs/heads/*", + "+refs/tags/*:refs/tags/*", }, }) if err != nil { From 9f86565304f67aab0e94eb49614a7448604178d4 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 3 Mar 2022 17:26:47 +0100 Subject: [PATCH 0443/2916] perf: Cache registry auth tokens --- pkg/registries/registries.go | 125 ++++++++++++++++++++++++++++++++--- 1 file changed, 115 insertions(+), 10 deletions(-) diff --git a/pkg/registries/registries.go b/pkg/registries/registries.go index e4ded5986..6aa80d145 100644 --- a/pkg/registries/registries.go +++ b/pkg/registries/registries.go @@ -5,15 +5,22 @@ import ( "bytes" "fmt" "github.com/codablock/kluctl/pkg/utils" + "github.com/codablock/kluctl/pkg/utils/uo" "github.com/docker/distribution/registry/client/auth/challenge" + "github.com/golang-jwt/jwt/v4" "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/v1/remote" + log "github.com/sirupsen/logrus" + "io/ioutil" "net/http" "net/http/httputil" "os" + "path/filepath" "strconv" + "strings" "sync" + "time" ) func ListImageTags(image string) ([]string, error) { @@ -68,34 +75,132 @@ func (kc *myKeychain) realmFromRequest(req *http.Request) string { return fmt.Sprintf("%s://%s%s", req.URL.Scheme, req.URL.Host, req.URL.Path) } -func (kc *myKeychain) RoundTripCached(req *http.Request, extraKey string, onNew func(res *http.Response) error) (*http.Response, error) { - resI, err := kc.cachedResponses.Get(req.Host, req.URL.Path+"#"+extraKey, func() (interface{}, error) { - res, err := remote.DefaultTransport.RoundTrip(req) +func (kc *myKeychain) getCachePath(key string) string { + return filepath.Join(utils.GetTmpBaseDir(), "registries-cache", key[0:2], key[2:4], key) +} + +func (kc *myKeychain) checkInvalidToken(resBody []byte) bool { + j, err := uo.FromString(string(resBody)) + if err != nil { + return false + } + + tokenStr, ok, _ := j.GetNestedString("token") + if !ok { + return false + } + + _, err = jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) { + return nil, nil + }) + if err != nil { + if vErr, ok := err.(*jwt.ValidationError); ok { + if vErr.Errors & ^jwt.ValidationErrorSignatureInvalid == 0 { + // invalid signature errors are expected as we did not provide a key + return false + } + } + return true + } + return false +} + +func (kc *myKeychain) readCachedResponse(key string) []byte { + cachePath := kc.getCachePath(key) + st, err := os.Stat(cachePath) + + if err != nil { + return nil + } + + if time.Now().Sub(st.ModTime()) > 55 * time.Minute { + return nil + } + + b, err := ioutil.ReadFile(cachePath) + if err != nil { + return nil + } + + res, err := http.ReadResponse(bufio.NewReader(bytes.NewReader(b)), nil) + + if strings.HasPrefix(res.Header.Get("Content-Type"), "application/json") { + jb, err := ioutil.ReadAll(res.Body) if err != nil { - return nil, err + return nil + } + if kc.checkInvalidToken(jb) { + return nil + } + } + + return b +} + +func (kc *myKeychain) writeCachedResponse(key string, data []byte) { + cachePath := kc.getCachePath(key) + if !utils.Exists(filepath.Dir(cachePath)) { + err := os.MkdirAll(filepath.Dir(cachePath), 0o700) + if err != nil { + log.Warningf("writeCachedResponse failed: %v", err) + return } + } + + err := ioutil.WriteFile(cachePath + ".tmp", data, 0o600) + if err != nil { + log.Warningf("writeCachedResponse failed: %v", err) + return + } + err = os.Rename(cachePath + ".tmp", cachePath) + if err != nil { + log.Warningf("writeCachedResponse failed: %v", err) + return + } +} + +func (kc *myKeychain) RoundTripCached(req *http.Request, extraKey string, onNew func(res *http.Response) error) (*http.Response, error) { + key := fmt.Sprintf("%s\n%s\n%s\n", req.Host, req.URL.Path, extraKey) + key = utils.Sha256String(key) + + isNew := false + resI, err := kc.cachedResponses.Get(req.Host, key, func() (interface{}, error) { + isNew = true - if onNew != nil { - err = onNew(res) + b := kc.readCachedResponse(key) + if b == nil { + res, err := remote.DefaultTransport.RoundTrip(req) + if err != nil { + return nil, err + } + b, err = httputil.DumpResponse(res, true) if err != nil { return nil, err } + + kc.writeCachedResponse(key, b) } - return httputil.DumpResponse(res, true) + return b, nil }) - if err != nil { return nil, err } resBytes, _ := resI.([]byte) - r := bufio.NewReader(bytes.NewReader(resBytes)) - res, err := http.ReadResponse(r, req) + res, err := http.ReadResponse(bufio.NewReader(bytes.NewReader(resBytes)), req) if err != nil { return res, err } + if isNew && onNew != nil { + err = onNew(res) + if err != nil { + return nil, err + } + res, _ = http.ReadResponse(bufio.NewReader(bytes.NewReader(resBytes)), req) + } + return res, err } From 6449c81a42cf0f59b20d2194d08c435619553732 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 3 Mar 2022 21:43:20 +0100 Subject: [PATCH 0444/2916] build: Fix deleting of unnecessary files in the embedded python dist --- hack/build-python-unix.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hack/build-python-unix.sh b/hack/build-python-unix.sh index d82dc9580..74008378f 100755 --- a/hack/build-python-unix.sh +++ b/hack/build-python-unix.sh @@ -33,5 +33,5 @@ make install cd .. cd cpython-install -find . -name __pycache__ -exec rm -rf {} \; -find . -name '*.a' -exec rm {} \; +find . -name __pycache__ | xargs rm -rf +find . -name '*.a' | xargs rm From 55cb37867a99a1e2579606987485c2c93d9460ef Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 3 Mar 2022 21:43:50 +0100 Subject: [PATCH 0445/2916] build: Remove a few unneeded python packages --- hack/build-python-unix.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/hack/build-python-unix.sh b/hack/build-python-unix.sh index 74008378f..70a6c73e5 100755 --- a/hack/build-python-unix.sh +++ b/hack/build-python-unix.sh @@ -35,3 +35,7 @@ cd .. cd cpython-install find . -name __pycache__ | xargs rm -rf find . -name '*.a' | xargs rm + +for i in ensurepip idlelib distutils pydoc_data asyncio email tkinter lib2to3 xml multiprocessing unittest; do + rm -rf lib/python3.10/$i +done From 64d00f45ff47c26f9976ac2a11947e19882b5c6a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 3 Mar 2022 21:45:28 +0100 Subject: [PATCH 0446/2916] build: Disble ensurepip from python build --- hack/build-python-unix.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/build-python-unix.sh b/hack/build-python-unix.sh index 70a6c73e5..73116cdce 100755 --- a/hack/build-python-unix.sh +++ b/hack/build-python-unix.sh @@ -27,7 +27,7 @@ if [ "$os" = "darwin" ]; then export CPPFLAGS="-I$(brew --prefix readline)/include" export LDFLAGS="-L$(brew --prefix readline)/lib" fi -./configure $CONFIGURE_FLAGS --enable-shared --disable-test-modules --without-static-libpython --prefix $DIR/../build-python/$os/cpython-install +./configure $CONFIGURE_FLAGS --enable-shared --disable-test-modules --without-static-libpython --with-ensurepip=no --prefix $DIR/../build-python/$os/cpython-install make -j4 make install From ec4af4b20d7c1413510b246ddb2f9f14c3bb6083 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 3 Mar 2022 22:02:53 +0100 Subject: [PATCH 0447/2916] build: Fix empty xargs rm call --- hack/build-python-unix.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hack/build-python-unix.sh b/hack/build-python-unix.sh index 73116cdce..0a87b47fe 100755 --- a/hack/build-python-unix.sh +++ b/hack/build-python-unix.sh @@ -33,8 +33,8 @@ make install cd .. cd cpython-install -find . -name __pycache__ | xargs rm -rf -find . -name '*.a' | xargs rm +find . -name __pycache__ | xargs rm -r -rf +find . -name '*.a' | xargs -r rm for i in ensurepip idlelib distutils pydoc_data asyncio email tkinter lib2to3 xml multiprocessing unittest; do rm -rf lib/python3.10/$i From e99fca4477fce441ab06621903f6f6959c983304 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 3 Mar 2022 22:16:19 +0100 Subject: [PATCH 0448/2916] build: Fix xargs call --- hack/build-python-unix.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/build-python-unix.sh b/hack/build-python-unix.sh index 0a87b47fe..5cd46d421 100755 --- a/hack/build-python-unix.sh +++ b/hack/build-python-unix.sh @@ -33,7 +33,7 @@ make install cd .. cd cpython-install -find . -name __pycache__ | xargs rm -r -rf +find . -name __pycache__ | xargs -r rm -rf find . -name '*.a' | xargs -r rm for i in ensurepip idlelib distutils pydoc_data asyncio email tkinter lib2to3 xml multiprocessing unittest; do From a4d73f7fbefcef58b7b58662b1c2210ff96a0df7 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 3 Mar 2022 22:16:39 +0100 Subject: [PATCH 0449/2916] ci: strip binaries --- .github/workflows/build-and-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index 947375b87..09a12a061 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -138,7 +138,7 @@ jobs: - name: Build kluctl shell: bash run: | - go build ./cmd/kluctl + go build -ldflags "-s -w" ./cmd/kluctl EXE="" if [ "${{ runner.os }}" = "Windows" ]; then EXE=".exe" From a8902542d8275dff57d876b6448cc5b861100b5b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 3 Mar 2022 22:57:24 +0100 Subject: [PATCH 0450/2916] build: Move filtering of __pycache__ and *.a files into tar-python.sh --- hack/build-python-unix.sh | 2 -- pkg/python/tar-python.sh | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/hack/build-python-unix.sh b/hack/build-python-unix.sh index 5cd46d421..79957c710 100755 --- a/hack/build-python-unix.sh +++ b/hack/build-python-unix.sh @@ -33,8 +33,6 @@ make install cd .. cd cpython-install -find . -name __pycache__ | xargs -r rm -rf -find . -name '*.a' | xargs -r rm for i in ensurepip idlelib distutils pydoc_data asyncio email tkinter lib2to3 xml multiprocessing unittest; do rm -rf lib/python3.10/$i diff --git a/pkg/python/tar-python.sh b/pkg/python/tar-python.sh index 9b94a481d..c76bb357f 100755 --- a/pkg/python/tar-python.sh +++ b/pkg/python/tar-python.sh @@ -6,4 +6,4 @@ DIR=$(cd $(dirname $0) && pwd) cd $DIR/../../build-python/$2 -tar czf $DIR/$1 $3 +tar --exclude '*/__pycache__' --exclude '*.a' -czf $DIR/$1 $3 From 479baabe6fe947163c65b1724d901e64de2e38bb Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 3 Mar 2022 23:56:18 +0100 Subject: [PATCH 0451/2916] fix: Perform controlled single-threaded init of kustomize openapi stuff --- pkg/deployment/deployment_item.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 14f5c1f84..1f5026899 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -15,7 +15,10 @@ import ( "path/filepath" "sigs.k8s.io/kustomize/api/filesys" "sigs.k8s.io/kustomize/api/krusty" + "sigs.k8s.io/kustomize/kyaml/openapi" + yaml2 "sigs.k8s.io/kustomize/kyaml/yaml" "strings" + "sync" ) const sealmeExt = ".sealme" @@ -294,6 +297,8 @@ func (di *deploymentItem) buildKustomize() error { return err } + waitForOpenapiInitDone() + ko := krusty.MakeDefaultOptions() k := krusty.MakeKustomizer(ko) @@ -366,3 +371,26 @@ func (di *deploymentItem) postprocessAndLoadObjects(k *k8s.K8sCluster) error { return nil } + +var openapiInitDoneMutex sync.Mutex +var openapiInitDoneOnce sync.Once + +func waitForOpenapiInitDone() { + openapiInitDoneOnce.Do(func() { + openapiInitDoneMutex.Lock() + openapiInitDoneMutex.Unlock() + }) +} + +func init() { + openapiInitDoneMutex.Lock() + go func() { + // we do a single call to IsNamespaceScoped to enforce openapi schema initialization + // this is required here to ensure that it is later not done in parallel which would cause race conditions + openapi.IsNamespaceScoped(yaml2.TypeMeta{ + APIVersion: "", + Kind: "ConfigMap", + }) + openapiInitDoneMutex.Unlock() + }() +} \ No newline at end of file From 93545b8ac9c001c679d56903ce2f631992fe0c57 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 4 Mar 2022 08:10:51 +0100 Subject: [PATCH 0452/2916] fix: Only cache results with error code < 500 --- pkg/registries/registries.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/registries/registries.go b/pkg/registries/registries.go index 6aa80d145..b500ab387 100644 --- a/pkg/registries/registries.go +++ b/pkg/registries/registries.go @@ -178,7 +178,9 @@ func (kc *myKeychain) RoundTripCached(req *http.Request, extraKey string, onNew return nil, err } - kc.writeCachedResponse(key, b) + if res.StatusCode < 500 { + kc.writeCachedResponse(key, b) + } } return b, nil From 0aee24ed194f89048429b9df59ba756059206d6e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 4 Mar 2022 11:15:17 +0100 Subject: [PATCH 0453/2916] feat: Support setting flags/args via env variables --- cmd/kluctl/commands/root.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index 59eeda5b1..1a5879817 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -168,7 +168,8 @@ func ParseArgs(args []string, options ...kong.Option) (*kong.Kong, *kong.Context func Execute() { confOption := kong.Configuration(kong.JSON, "/etc/kluctl.json", "~/.kluctl/config.json") - parser, _, err := ExecuteWithArgs(os.Args[1:], confOption) + envOption := kong.DefaultEnvars("KLUCTL") + parser, _, err := ExecuteWithArgs(os.Args[1:], confOption, envOption) parser.FatalIfErrorf(err) } From 2a6dcb4c5b470e059cebed7229c1b9c73fd8f3ac Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 4 Mar 2022 14:09:10 +0100 Subject: [PATCH 0454/2916] fix: Fix typo in ArchiveMetadata struct --- pkg/types/metadata.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/types/metadata.go b/pkg/types/metadata.go index 7e588ddaf..f17fcde5f 100644 --- a/pkg/types/metadata.go +++ b/pkg/types/metadata.go @@ -6,6 +6,6 @@ type InvolvedRepo struct { } type ArchiveMetadata struct { - InvolvedRepos map[string][]InvolvedRepo `yaml:"involvedRepo"` + InvolvedRepos map[string][]InvolvedRepo `yaml:"involvedRepos"` Targets []*DynamicTarget `yaml:"targets"` } From abed846656d9b4209006b14ba0dc99e53f2b3e62 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 7 Mar 2022 09:59:38 +0100 Subject: [PATCH 0455/2916] fix: In PoorMansClone, auto-detect if a ref is a branch or a tag --- pkg/git/mirrored_repo.go | 6 +++++- pkg/git/poor_mans_clone.go | 14 +++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/pkg/git/mirrored_repo.go b/pkg/git/mirrored_repo.go index 9cd018f38..b168bd71c 100644 --- a/pkg/git/mirrored_repo.go +++ b/pkg/git/mirrored_repo.go @@ -245,7 +245,11 @@ func (g *MirroredGitRepo) CloneProject(ref string, targetDir string) error { log.Debugf("Cloning git project: url='%s', ref='%s', target='%s'", g.url.String(), ref, targetDir) - return PoorMansClone(g.mirrorDir, targetDir, ref) + err := PoorMansClone(g.mirrorDir, targetDir, ref) + if err != nil { + return fmt.Errorf("failed to clone %s from %s: %w", ref, g.url.String(), err) + } + return nil } func buildRemoteName(u git_url.GitUrl) string { diff --git a/pkg/git/poor_mans_clone.go b/pkg/git/poor_mans_clone.go index b91f101c5..e8ae58375 100644 --- a/pkg/git/poor_mans_clone.go +++ b/pkg/git/poor_mans_clone.go @@ -1,6 +1,7 @@ package git import ( + "fmt" "github.com/codablock/kluctl/pkg/utils" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/config" @@ -9,6 +10,7 @@ import ( "os" "path/filepath" "runtime" + "strings" ) // PoorMansClone poor mans clone from a local repo, which does not rely on go-git using git-upload-pack @@ -80,7 +82,17 @@ func PoorMansClone(sourceDir string, targetDir string, ref string) error { } var ref2 plumbing.ReferenceName if ref != "" { - ref2 = plumbing.NewBranchReferenceName(ref) + if strings.HasPrefix(ref,"refs/heads") { + ref2 = plumbing.ReferenceName(ref) + } else { + if _, err := r.Reference(plumbing.NewBranchReferenceName(ref), true); err == nil { + ref2 = plumbing.NewBranchReferenceName(ref) + } else if _, err := r.Reference(plumbing.NewTagReferenceName(ref), true); err == nil { + ref2 = plumbing.NewTagReferenceName(ref) + } else { + return fmt.Errorf("ref %s not found", ref) + } + } } else { x, err := r.Reference("HEAD", true) if err != nil { From 43238df06a13e33e1af40ec3e9030be621d6ce99 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 7 Mar 2022 10:00:45 +0100 Subject: [PATCH 0456/2916] fix: Cleanup remotely deleted refs from local mirror --- pkg/git/mirrored_repo.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/pkg/git/mirrored_repo.go b/pkg/git/mirrored_repo.go index b168bd71c..8435b65da 100644 --- a/pkg/git/mirrored_repo.go +++ b/pkg/git/mirrored_repo.go @@ -146,6 +146,10 @@ func (g *MirroredGitRepo) update(repoDir string) error { if err != nil { return err } + remoteRefsMap := make(map[plumbing.ReferenceName]bool) + for _, reference := range g.remoteRefs { + remoteRefsMap[reference.Name()] = true + } err = remote.Fetch(&git.FetchOptions{ Auth: auth, @@ -157,6 +161,27 @@ func (g *MirroredGitRepo) update(repoDir string) error { return err } + localRemoteRefs, err := r.References() + if err != nil { + return err + } + var toDelete []plumbing.Reference + err = localRemoteRefs.ForEach(func(reference *plumbing.Reference) error { + if _, ok := remoteRefsMap[reference.Name()]; !ok { + toDelete = append(toDelete, *reference) + } + return nil + }) + if err != nil { + return err + } + for _, ref := range toDelete { + err = r.Storer.RemoveReference(ref.Name()) + if err != nil { + return err + } + } + // update default branch, referenced via HEAD // we assume that HEAD is a symbolic ref and don't care about old git versions for _, ref := range g.remoteRefs { From 16216f23a4642b59e17091982731fe99eee80583 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 7 Mar 2022 10:04:40 +0100 Subject: [PATCH 0457/2916] fix: Match ref names without refs/heads prefix against branches and tags --- pkg/kluctl_project/load_targets.go | 36 ++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/pkg/kluctl_project/load_targets.go b/pkg/kluctl_project/load_targets.go index c5889e9a7..da155b4c3 100644 --- a/pkg/kluctl_project/load_targets.go +++ b/pkg/kluctl_project/load_targets.go @@ -12,6 +12,7 @@ import ( "path/filepath" "reflect" "regexp" + "strings" "sync" ) @@ -145,7 +146,6 @@ func (c *KluctlProjectContext) prepareDynamicTargetsExternal(baseTarget *types.T if defaultBranch == nil { return nil, fmt.Errorf("git project %v seems to have no default branch", baseTarget.TargetConfig.Project.Url.String()) } - *defaultBranch = (*defaultBranch)[len("refs/heads/"):] if baseTarget.TargetConfig.Ref == nil && baseTarget.TargetConfig.RefPattern == nil { // use default branch of repo @@ -161,17 +161,16 @@ func (c *KluctlProjectContext) prepareDynamicTargetsExternal(baseTarget *types.T refPattern = targetConfigRef } - compiledRefPattern, err := regexp.Compile(fmt.Sprintf("^refs/heads/%s$", *refPattern)) - if err != nil { - return nil, fmt.Errorf("invalid ref pattern %s: %w", *refPattern, err) - } - var dynamicTargets []*dynamicTargetInfo - for fullRefName := range refs { - if !compiledRefPattern.MatchString(fullRefName) { + for ref := range refs { + ref := ref + m, err := c.matchRef(ref, *refPattern) + if err != nil { + return nil, err + } + if !m { continue } - ref := fullRefName[len("refs/heads/"):] cloneDir, err := c.buildCloneDir(baseTarget.TargetConfig.Project.Url, ref) if err != nil { @@ -190,6 +189,25 @@ func (c *KluctlProjectContext) prepareDynamicTargetsExternal(baseTarget *types.T return dynamicTargets, nil } +func (c *KluctlProjectContext) matchRef(s string, pattern string) (bool, error) { + if strings.HasPrefix(pattern, "refs/") { + p, err := regexp.Compile(fmt.Sprintf("^%s$", pattern)) + if err != nil { + return false, err + } + return p.MatchString(s), nil + } + p1, err := regexp.Compile(fmt.Sprintf("^refs/heads/%s$", pattern)) + if err != nil { + return false, err + } + p2, err := regexp.Compile(fmt.Sprintf("^refs/tags/%s$", pattern)) + if err != nil { + return false, err + } + return p1.MatchString(s) || p2.MatchString(s), nil +} + func (c *KluctlProjectContext) cloneDynamicTargets(dynamicTargets []*dynamicTargetInfo) error { wp := utils.NewDebuggerAwareWorkerPool(8) defer wp.StopWait(false) From 354fb21e90e2e7dd4f994183259dfbf8ba98d78b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 7 Mar 2022 13:56:41 +0100 Subject: [PATCH 0458/2916] fix: Fix yaml output --- cmd/kluctl/commands/command_result.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/kluctl/commands/command_result.go b/cmd/kluctl/commands/command_result.go index 6e476ed42..f22df2791 100644 --- a/cmd/kluctl/commands/command_result.go +++ b/cmd/kluctl/commands/command_result.go @@ -172,7 +172,7 @@ func outputHelper(output []string, cb func(format string) (string, error)) error output = []string{"text"} } for _, o := range output { - s := strings.SplitN(o, "=", 1) + s := strings.SplitN(o, "=", 2) format := s[0] var path *string if len(s) > 1 { @@ -234,7 +234,7 @@ func outputYamlResult(output []string, result interface{}, multiDoc bool) error func outputResult(f *string, result string) error { w := os.Stdout if f != nil && *f != "-" { - f, err := os.Open(*f) + f, err := os.Create(*f) if err != nil { return err } From 5c6bc63bc17b17219352c3aa072db19f25b9c60c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 7 Mar 2022 13:57:08 +0100 Subject: [PATCH 0459/2916] fix: Use short ref names in targets --- pkg/kluctl_project/load_targets.go | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/pkg/kluctl_project/load_targets.go b/pkg/kluctl_project/load_targets.go index da155b4c3..4967a9b7c 100644 --- a/pkg/kluctl_project/load_targets.go +++ b/pkg/kluctl_project/load_targets.go @@ -163,8 +163,7 @@ func (c *KluctlProjectContext) prepareDynamicTargetsExternal(baseTarget *types.T var dynamicTargets []*dynamicTargetInfo for ref := range refs { - ref := ref - m, err := c.matchRef(ref, *refPattern) + m, refShortName, err := c.matchRef(ref, *refPattern) if err != nil { return nil, err } @@ -172,7 +171,7 @@ func (c *KluctlProjectContext) prepareDynamicTargetsExternal(baseTarget *types.T continue } - cloneDir, err := c.buildCloneDir(baseTarget.TargetConfig.Project.Url, ref) + cloneDir, err := c.buildCloneDir(baseTarget.TargetConfig.Project.Url, refShortName) if err != nil { return nil, err } @@ -181,7 +180,7 @@ func (c *KluctlProjectContext) prepareDynamicTargetsExternal(baseTarget *types.T baseTarget: baseTarget, dir: cloneDir, gitProject: baseTarget.TargetConfig.Project, - ref: &ref, + ref: &refShortName, refPattern: refPattern, defaultBranch: *defaultBranch, }) @@ -189,23 +188,29 @@ func (c *KluctlProjectContext) prepareDynamicTargetsExternal(baseTarget *types.T return dynamicTargets, nil } -func (c *KluctlProjectContext) matchRef(s string, pattern string) (bool, error) { +func (c *KluctlProjectContext) matchRef(s string, pattern string) (bool, string, error) { if strings.HasPrefix(pattern, "refs/") { p, err := regexp.Compile(fmt.Sprintf("^%s$", pattern)) if err != nil { - return false, err + return false, "", err } - return p.MatchString(s), nil + return p.MatchString(s), s, nil } p1, err := regexp.Compile(fmt.Sprintf("^refs/heads/%s$", pattern)) if err != nil { - return false, err + return false, "", err } p2, err := regexp.Compile(fmt.Sprintf("^refs/tags/%s$", pattern)) if err != nil { - return false, err + return false, "", err + } + if p1.MatchString(s) { + return true, s[len("refs/heads/"):], nil + } else if p2.MatchString(s) { + return true, s[len("refs/tags/"):], nil + } else { + return false, "", nil } - return p1.MatchString(s) || p2.MatchString(s), nil } func (c *KluctlProjectContext) cloneDynamicTargets(dynamicTargets []*dynamicTargetInfo) error { From 6e7761b6a1a63fb11397a2aa0233de39888e69b4 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 7 Mar 2022 13:57:24 +0100 Subject: [PATCH 0460/2916] fix: Fix ValidateResult ready field name --- pkg/types/command_result.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/types/command_result.go b/pkg/types/command_result.go index 301096afd..ea292bcb3 100644 --- a/pkg/types/command_result.go +++ b/pkg/types/command_result.go @@ -41,7 +41,7 @@ type ValidateResultEntry struct { } type ValidateResult struct { - Ready bool `yaml:"ref"` + Ready bool `yaml:"ready"` Warnings []DeploymentError `yaml:"warnings,omitempty"` Errors []DeploymentError `yaml:"errors,omitempty"` Results []ValidateResultEntry `yaml:"results,omitempty"` From 00a61bf0ba6e21722442b34ef8d87a937a031c64 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 7 Mar 2022 16:56:21 +0100 Subject: [PATCH 0461/2916] fix: Inline gvk in ObjectRef --- pkg/types/ref.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/types/ref.go b/pkg/types/ref.go index 543334a25..33176e1b1 100644 --- a/pkg/types/ref.go +++ b/pkg/types/ref.go @@ -8,7 +8,7 @@ import ( ) type ObjectRef struct { - GVK schema.GroupVersionKind + GVK schema.GroupVersionKind `yaml:"gvk,inline"` Name string Namespace string } From 35d3c0ef91aa99970729b986b35e9b9b5d520c68 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 8 Mar 2022 10:19:57 +0100 Subject: [PATCH 0462/2916] perf: Better paralellism when resolving images --- go.mod | 6 ++-- pkg/deployment/deployment_collection.go | 47 +++++++++++++++++-------- 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/go.mod b/go.mod index f715c56e1..29806318c 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/gobwas/glob v0.2.3 github.com/goccy/go-yaml v1.9.5 github.com/gofrs/flock v0.8.1 + github.com/golang-jwt/jwt/v4 v4.3.0 github.com/google/go-containerregistry v0.8.0 github.com/hashicorp/go-version v1.4.0 github.com/hexops/gotextdiff v1.0.3 @@ -25,6 +26,7 @@ require ( github.com/sirupsen/logrus v1.8.1 github.com/whilp/git-urls v1.0.0 golang.org/x/crypto v0.0.0-20220214200702-86341886e292 + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b k8s.io/api v0.23.4 @@ -32,6 +34,7 @@ require ( k8s.io/client-go v0.23.4 sigs.k8s.io/kind v0.11.1 sigs.k8s.io/kustomize/api v0.11.2 + sigs.k8s.io/kustomize/kyaml v0.13.3 sigs.k8s.io/structured-merge-diff/v4 v4.2.1 ) @@ -70,7 +73,6 @@ require ( github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt/v4 v4.3.0 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/btree v1.0.1 // indirect github.com/google/go-cmp v0.5.7 // indirect @@ -110,7 +112,6 @@ require ( go.starlark.net v0.0.0-20220302181546-5411bad688d1 // indirect golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect @@ -124,6 +125,5 @@ require ( k8s.io/kube-openapi v0.0.0-20220124234850-424119656bbf // indirect k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect - sigs.k8s.io/kustomize/kyaml v0.13.3 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index a196560b1..acfeb969f 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -1,6 +1,7 @@ package deployment import ( + "context" "fmt" "github.com/codablock/kluctl/pkg/diff" "github.com/codablock/kluctl/pkg/k8s" @@ -10,6 +11,7 @@ import ( "github.com/codablock/kluctl/pkg/utils/uo" "github.com/codablock/kluctl/pkg/validation" log "github.com/sirupsen/logrus" + "golang.org/x/sync/semaphore" "io/fs" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -194,28 +196,45 @@ func (c *DeploymentCollection) resolveSealedSecrets() error { func (c *DeploymentCollection) buildKustomizeObjects(k *k8s.K8sCluster) error { log.Infof("Building kustomize objects") - wp := utils.NewDebuggerAwareWorkerPool(8) - defer wp.StopWait(false) + var wg sync.WaitGroup + var errs []error + var mutex sync.Mutex + sem := semaphore.NewWeighted(16) for _, d_ := range c.deployments { d := d_ - wp.Submit(func() error { + + wg.Add(1) + go func() { err := d.buildKustomize() if err != nil { - return fmt.Errorf("building kustomize objects for %s failed. %w", *d.dir, err) - } - err = d.postprocessAndLoadObjects(k) - if err != nil { - return fmt.Errorf("postprocessing kustomize objects for %s failed. %w", *d.dir, err) + mutex.Lock() + errs = append(errs, fmt.Errorf("building kustomize objects for %s failed. %w", *d.dir, err)) + mutex.Unlock() + } else { + wg.Add(1) + go func() { + _ = sem.Acquire(context.Background(), 1) + defer sem.Release(1) + + err := d.postprocessAndLoadObjects(k) + if err != nil { + mutex.Lock() + errs = append(errs, fmt.Errorf("postprocessing kustomize objects for %s failed. %w", *d.dir, err)) + mutex.Unlock() + } + wg.Done() + }() } - return nil - }) - } - err := wp.StopWait(false) - if err != nil { - return err + + wg.Done() + }() } + wg.Wait() + if len(errs) != 0 { + return utils.NewErrorList(errs) + } return nil } From 7bb9251c9d51af0e279e052247ff9487a81ea76b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 8 Mar 2022 10:32:45 +0100 Subject: [PATCH 0463/2916] perf: Cache keychain resolve calls --- pkg/registries/registries.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/pkg/registries/registries.go b/pkg/registries/registries.go index b500ab387..0db3b863e 100644 --- a/pkg/registries/registries.go +++ b/pkg/registries/registries.go @@ -46,6 +46,7 @@ func ListImageTags(image string) ([]string, error) { var globalMyKeychain myKeychain type myKeychain struct { + cachedAuth utils.ThreadSafeCache cachedResponses utils.ThreadSafeMultiCache authRealms map[string]bool authErrors map[string]bool @@ -53,7 +54,7 @@ type myKeychain struct { mutex sync.Mutex } -func (kc *myKeychain) Resolve(resource authn.Resource) (authn.Authenticator, error) { +func (kc *myKeychain) doResolve(resource authn.Resource) (authn.Authenticator, error) { registry := resource.RegistryStr() for _, m := range utils.ParseEnvConfigSets("KLUCTL_REGISTRY") { @@ -71,6 +72,18 @@ func (kc *myKeychain) Resolve(resource authn.Resource) (authn.Authenticator, err return authn.DefaultKeychain.Resolve(resource) } +func (kc *myKeychain) Resolve(resource authn.Resource) (authn.Authenticator, error) { + registry := resource.RegistryStr() + + ret, err := kc.cachedAuth.Get(registry, func() (interface{}, error) { + return kc.doResolve(resource) + }) + if err != nil { + return nil, err + } + return ret.(authn.Authenticator), nil +} + func (kc *myKeychain) realmFromRequest(req *http.Request) string { return fmt.Sprintf("%s://%s%s", req.URL.Scheme, req.URL.Host, req.URL.Path) } From 4e72935905f0d54ddbda52b7f93e380b217dd189 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 8 Mar 2022 10:33:01 +0100 Subject: [PATCH 0464/2916] fix: 'yal' -> 'yaml' in formatCommandResult --- cmd/kluctl/commands/command_result.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmd/kluctl/commands/command_result.go b/cmd/kluctl/commands/command_result.go index f22df2791..556e5cab5 100644 --- a/cmd/kluctl/commands/command_result.go +++ b/cmd/kluctl/commands/command_result.go @@ -104,7 +104,7 @@ func formatCommandResult(cr *types.CommandResult, format string) (string, error) switch format { case "text": return formatCommandResultText(cr), nil - case "yal": + case "yaml": return formatCommandResultYaml(cr) default: return "", fmt.Errorf("invalid format: %s", format) @@ -179,6 +179,9 @@ func outputHelper(output []string, cb func(format string) (string, error)) error path = &s[1] } r, err := cb(format) + if err != nil { + return err + } err = outputResult(path, r) if err != nil { From 910ca9091e505afaa3c3211f05285088d7a6b70f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 8 Mar 2022 13:57:38 +0100 Subject: [PATCH 0465/2916] refactor: Completely switch to uo.UnstructuredObject --- .../commands/cmd_check_image_updates.go | 4 +- cmd/kluctl/commands/cmd_delete.go | 6 +- cmd/kluctl/commands/command_result.go | 19 +-- e2e/project.go | 2 +- pkg/{k8s => deployment}/all_objects.go | 5 +- pkg/deployment/apply_utils.go | 71 ++++++----- pkg/{k8s => deployment}/delete_utils.go | 26 +++-- pkg/deployment/deployment_collection.go | 110 +++++++++--------- pkg/deployment/deployment_item.go | 20 ++-- pkg/deployment/downscale_utils.go | 26 ++--- pkg/deployment/helm_chart.go | 9 +- pkg/deployment/hooks_util.go | 30 ++--- pkg/deployment/images.go | 16 +-- pkg/diff/diff.go | 3 +- pkg/diff/managed_fields.go | 21 ++-- pkg/diff/normalize.go | 21 ++-- pkg/git/poor_mans_clone.go | 2 +- pkg/jinja2/vars.go | 10 +- pkg/k8s/k8s_cluster.go | 66 ++++++----- pkg/registries/registries.go | 8 +- pkg/seal/bootstrap.go | 10 +- pkg/seal/sealer.go | 5 +- pkg/types/command_result.go | 33 +++--- pkg/types/{ => k8s}/ref.go | 2 +- pkg/types/target_config.go | 21 ++-- pkg/utils/uo/k8s_fields.go | 30 +++++ pkg/utils/uo/nested_fields.go | 19 +++ pkg/utils/utils.go | 6 - pkg/validation/validation.go | 28 +++-- 29 files changed, 336 insertions(+), 293 deletions(-) rename pkg/{k8s => deployment}/all_objects.go (82%) rename pkg/{k8s => deployment}/delete_utils.go (78%) rename pkg/types/{ => k8s}/ref.go (98%) diff --git a/cmd/kluctl/commands/cmd_check_image_updates.go b/cmd/kluctl/commands/cmd_check_image_updates.go index 67a1e1e64..bd6e06dfc 100644 --- a/cmd/kluctl/commands/cmd_check_image_updates.go +++ b/cmd/kluctl/commands/cmd_check_image_updates.go @@ -3,7 +3,7 @@ package commands import ( "github.com/codablock/kluctl/cmd/kluctl/args" "github.com/codablock/kluctl/pkg/registries" - "github.com/codablock/kluctl/pkg/types" + "github.com/codablock/kluctl/pkg/types/k8s" "github.com/codablock/kluctl/pkg/utils" "github.com/codablock/kluctl/pkg/utils/versions" log "github.com/sirupsen/logrus" @@ -24,7 +24,7 @@ func (cmd *checkImageUpdatesCmd) Help() string { } func (cmd *checkImageUpdatesCmd) Run() error { - var renderedImages map[types.ObjectRef][]string + var renderedImages map[k8s.ObjectRef][]string ptArgs := projectTargetCommandArgs{ projectFlags: cmd.ProjectFlags, targetFlags: cmd.TargetFlags, diff --git a/cmd/kluctl/commands/cmd_delete.go b/cmd/kluctl/commands/cmd_delete.go index db2e5565c..4aa9b0b8f 100644 --- a/cmd/kluctl/commands/cmd_delete.go +++ b/cmd/kluctl/commands/cmd_delete.go @@ -3,8 +3,10 @@ package commands import ( "fmt" "github.com/codablock/kluctl/cmd/kluctl/args" + "github.com/codablock/kluctl/pkg/deployment" "github.com/codablock/kluctl/pkg/k8s" "github.com/codablock/kluctl/pkg/types" + k8s2 "github.com/codablock/kluctl/pkg/types/k8s" "os" ) @@ -56,7 +58,7 @@ func (cmd *deleteCmd) Run() error { }) } -func confirmedDeleteObjects(k *k8s.K8sCluster, refs []types.ObjectRef, dryRun bool, forceYes bool) (*types.CommandResult, error) { +func confirmedDeleteObjects(k *k8s.K8sCluster, refs []k8s2.ObjectRef, dryRun bool, forceYes bool) (*types.CommandResult, error) { if len(refs) != 0 { _, _ = os.Stderr.WriteString("The following objects will be deleted:\n") for _, ref := range refs { @@ -69,5 +71,5 @@ func confirmedDeleteObjects(k *k8s.K8sCluster, refs []types.ObjectRef, dryRun bo } } - return k8s.DeleteObjects(k, refs, true) + return deployment.DeleteObjects(k, refs, true) } diff --git a/cmd/kluctl/commands/command_result.go b/cmd/kluctl/commands/command_result.go index 556e5cab5..ee4b5f6ee 100644 --- a/cmd/kluctl/commands/command_result.go +++ b/cmd/kluctl/commands/command_result.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "github.com/codablock/kluctl/pkg/types" + "github.com/codablock/kluctl/pkg/types/k8s" "github.com/codablock/kluctl/pkg/utils" "github.com/codablock/kluctl/pkg/yaml" "io" @@ -21,23 +22,23 @@ func formatCommandResultText(cr *types.CommandResult) string { if len(cr.NewObjects) != 0 { buf.WriteString("\nNew objects:\n") - var refs []types.ObjectRef + var refs []k8s.ObjectRef for _, o := range cr.NewObjects { - refs = append(refs, types.RefFromObject(o)) + refs = append(refs, o.GetK8sRef()) } prettyObjectRefs(buf, refs) } if len(cr.ChangedObjects) != 0 { buf.WriteString("\nChanged objects:\n") - var refs []types.ObjectRef + var refs []k8s.ObjectRef for _, co := range cr.ChangedObjects { - refs = append(refs, types.RefFromObject(co.NewObject)) + refs = append(refs, co.NewObject.GetK8sRef()) } prettyObjectRefs(buf, refs) buf.WriteString("\n") for _, co := range cr.ChangedObjects { - prettyChanges(buf, types.RefFromObject(co.NewObject), co.Changes) + prettyChanges(buf, co.NewObject.GetK8sRef(), co.Changes) } } @@ -48,9 +49,9 @@ func formatCommandResultText(cr *types.CommandResult) string { if len(cr.HookObjects) != 0 { buf.WriteString("\nApplied hooks:\n") - var refs []types.ObjectRef + var refs []k8s.ObjectRef for _, o := range cr.HookObjects { - refs = append(refs, types.RefFromObject(o)) + refs = append(refs, o.GetK8sRef()) } prettyObjectRefs(buf, refs) } @@ -67,7 +68,7 @@ func formatCommandResultText(cr *types.CommandResult) string { return buf.String() } -func prettyObjectRefs(buf io.StringWriter, refs []types.ObjectRef) { +func prettyObjectRefs(buf io.StringWriter, refs []k8s.ObjectRef) { for _, ref := range refs { _, _ = buf.WriteString(fmt.Sprintf(" %s\n", ref.String())) } @@ -79,7 +80,7 @@ func prettyErrors(buf io.StringWriter, errors []types.DeploymentError) { } } -func prettyChanges(buf io.StringWriter, ref types.ObjectRef, changes []types.Change) { +func prettyChanges(buf io.StringWriter, ref k8s.ObjectRef, changes []types.Change) { _, _ = buf.WriteString(fmt.Sprintf("Diff for object %s\n", ref.String())) var t utils.PrettyTable diff --git a/e2e/project.go b/e2e/project.go index 475d29b3c..ef4569a8f 100644 --- a/e2e/project.go +++ b/e2e/project.go @@ -527,7 +527,7 @@ func (p *testProject) Kluctl(argsIn ...string) (string, string, error) { p.t.Logf("Runnning kluctl: %s", strings.Join(args, " ")) - stdout, stderr, err := runWrappedCmd(p.t,"TestKluctlWrapper", cwd, env, args) + stdout, stderr, err := runWrappedCmd(p.t, "TestKluctlWrapper", cwd, env, args) return stdout, stderr, err } diff --git a/pkg/k8s/all_objects.go b/pkg/deployment/all_objects.go similarity index 82% rename from pkg/k8s/all_objects.go rename to pkg/deployment/all_objects.go index 514317d10..3704f9f92 100644 --- a/pkg/k8s/all_objects.go +++ b/pkg/deployment/all_objects.go @@ -1,12 +1,13 @@ -package k8s +package deployment import ( + "github.com/codablock/kluctl/pkg/k8s" "github.com/codablock/kluctl/pkg/utils" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "strings" ) -func GetIncludedObjectsMetadata(k *K8sCluster, verbs []string, labels map[string]string, inclusion *utils.Inclusion) ([]*v1.PartialObjectMetadata, error) { +func GetIncludedObjectsMetadata(k *k8s.K8sCluster, verbs []string, labels map[string]string, inclusion *utils.Inclusion) ([]*v1.PartialObjectMetadata, error) { objects, err := k.ListAllObjectsMetadata(verbs, "", labels) if err != nil { return nil, err diff --git a/pkg/deployment/apply_utils.go b/pkg/deployment/apply_utils.go index aab0cf86b..d5daede53 100644 --- a/pkg/deployment/apply_utils.go +++ b/pkg/deployment/apply_utils.go @@ -5,14 +5,13 @@ import ( "fmt" "github.com/codablock/kluctl/pkg/diff" "github.com/codablock/kluctl/pkg/k8s" - "github.com/codablock/kluctl/pkg/types" + k8s2 "github.com/codablock/kluctl/pkg/types/k8s" "github.com/codablock/kluctl/pkg/utils" "github.com/codablock/kluctl/pkg/utils/uo" "github.com/codablock/kluctl/pkg/validation" log "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "sync" "time" ) @@ -31,10 +30,10 @@ type applyUtil struct { k *k8s.K8sCluster o applyUtilOptions - appliedObjects map[types.ObjectRef]*unstructured.Unstructured - appliedHookObjects map[types.ObjectRef]*unstructured.Unstructured - deletedObjects map[types.ObjectRef]bool - deletedHookObjects map[types.ObjectRef]bool + appliedObjects map[k8s2.ObjectRef]*uo.UnstructuredObject + appliedHookObjects map[k8s2.ObjectRef]*uo.UnstructuredObject + deletedObjects map[k8s2.ObjectRef]bool + deletedHookObjects map[k8s2.ObjectRef]bool abortSignal bool deployedNewCRD bool mutex sync.Mutex @@ -45,19 +44,19 @@ func newApplyUtil(deploymentCollection *DeploymentCollection, k *k8s.K8sCluster, deploymentCollection: deploymentCollection, k: k, o: o, - appliedObjects: map[types.ObjectRef]*unstructured.Unstructured{}, - appliedHookObjects: map[types.ObjectRef]*unstructured.Unstructured{}, - deletedObjects: map[types.ObjectRef]bool{}, - deletedHookObjects: map[types.ObjectRef]bool{}, + appliedObjects: map[k8s2.ObjectRef]*uo.UnstructuredObject{}, + appliedHookObjects: map[k8s2.ObjectRef]*uo.UnstructuredObject{}, + deletedObjects: map[k8s2.ObjectRef]bool{}, + deletedHookObjects: map[k8s2.ObjectRef]bool{}, deployedNewCRD: true, // assume someone deployed CRDs in the meantime } } -func (a *applyUtil) handleResult(appliedObject *unstructured.Unstructured, hook bool) { +func (a *applyUtil) handleResult(appliedObject *uo.UnstructuredObject, hook bool) { a.mutex.Lock() defer a.mutex.Unlock() - ref := types.RefFromObject(appliedObject) + ref := appliedObject.GetK8sRef() if hook { a.appliedHookObjects[ref] = appliedObject } else { @@ -65,15 +64,15 @@ func (a *applyUtil) handleResult(appliedObject *unstructured.Unstructured, hook } } -func (a *applyUtil) handleApiWarnings(ref types.ObjectRef, warnings []k8s.ApiWarning) { +func (a *applyUtil) handleApiWarnings(ref k8s2.ObjectRef, warnings []k8s.ApiWarning) { a.deploymentCollection.addApiWarnings(ref, warnings) } -func (a *applyUtil) handleWarning(ref types.ObjectRef, warning error) { +func (a *applyUtil) handleWarning(ref k8s2.ObjectRef, warning error) { a.deploymentCollection.addWarning(ref, warning) } -func (a *applyUtil) handleError(ref types.ObjectRef, err error) { +func (a *applyUtil) handleError(ref k8s2.ObjectRef, err error) { a.mutex.Lock() defer a.mutex.Unlock() @@ -84,11 +83,11 @@ func (a *applyUtil) handleError(ref types.ObjectRef, err error) { a.deploymentCollection.addError(ref, err) } -func (a *applyUtil) hadError(ref types.ObjectRef) bool { +func (a *applyUtil) hadError(ref k8s2.ObjectRef) bool { return a.deploymentCollection.hadError(ref) } -func (a *applyUtil) deleteObject(ref types.ObjectRef, hook bool) bool { +func (a *applyUtil) deleteObject(ref k8s2.ObjectRef, hook bool) bool { o := k8s.DeleteOptions{ ForceDryRun: a.o.dryRun, } @@ -108,8 +107,8 @@ func (a *applyUtil) deleteObject(ref types.ObjectRef, hook bool) bool { return true } -func (a *applyUtil) retryApplyForceReplace(x *unstructured.Unstructured, hook bool, applyError error) { - ref := types.RefFromObject(x) +func (a *applyUtil) retryApplyForceReplace(x *uo.UnstructuredObject, hook bool, applyError error) { + ref := x.GetK8sRef() log2 := log.WithField("ref", ref) if !a.o.forceReplaceOnError { @@ -139,8 +138,8 @@ func (a *applyUtil) retryApplyForceReplace(x *unstructured.Unstructured, hook bo } } -func (a *applyUtil) retryApplyWithReplace(x *unstructured.Unstructured, hook bool, remoteObject *unstructured.Unstructured, applyError error) { - ref := types.RefFromObject(x) +func (a *applyUtil) retryApplyWithReplace(x *uo.UnstructuredObject, hook bool, remoteObject *uo.UnstructuredObject, applyError error) { + ref := x.GetK8sRef() log2 := log.WithField("ref", ref) if !a.o.replaceOnError || remoteObject == nil { @@ -150,9 +149,9 @@ func (a *applyUtil) retryApplyWithReplace(x *unstructured.Unstructured, hook boo log2.Warningf("Patching failed, retrying with replace instead of patch") - rv := remoteObject.GetResourceVersion() - x2 := uo.CopyUnstructured(x) - x2.SetResourceVersion(rv) + rv := remoteObject.GetK8sResourceVersion() + x2 := x.Clone() + x2.SetK8sResourceVersion(rv) o := k8s.UpdateOptions{ ForceDryRun: a.o.dryRun, @@ -167,15 +166,15 @@ func (a *applyUtil) retryApplyWithReplace(x *unstructured.Unstructured, hook boo a.handleResult(r, hook) } -func (a *applyUtil) retryApplyWithConflicts(x *unstructured.Unstructured, hook bool, remoteObject *unstructured.Unstructured, applyError error) { - ref := types.RefFromObject(x) +func (a *applyUtil) retryApplyWithConflicts(x *uo.UnstructuredObject, hook bool, remoteObject *uo.UnstructuredObject, applyError error) { + ref := x.GetK8sRef() if remoteObject == nil { a.handleError(ref, applyError) return } - var x2 *unstructured.Unstructured + var x2 *uo.UnstructuredObject if !a.o.forceApply { var statusError *errors.StatusError if !errors2.As(applyError, &statusError) { @@ -210,8 +209,8 @@ func (a *applyUtil) retryApplyWithConflicts(x *unstructured.Unstructured, hook b a.handleResult(r, hook) } -func (a *applyUtil) applyObject(x *unstructured.Unstructured, replaced bool, hook bool) { - ref := types.RefFromObject(x) +func (a *applyUtil) applyObject(x *uo.UnstructuredObject, replaced bool, hook bool) { + ref := x.GetK8sRef() log2 := log.WithField("ref", ref) log2.Debugf("applying object") @@ -247,7 +246,7 @@ func (a *applyUtil) applyObject(x *unstructured.Unstructured, replaced bool, hoo } } -func (a *applyUtil) handleNewCRDs(x *unstructured.Unstructured, err error) (bool, error) { +func (a *applyUtil) handleNewCRDs(x *uo.UnstructuredObject, err error) (bool, error) { if err != nil && meta.IsNoMatchError(err) { // maybe this was a resource for which the CRD was only deployed recently, so we should do rediscovery and then // retry the patch @@ -260,7 +259,7 @@ func (a *applyUtil) handleNewCRDs(x *unstructured.Unstructured, err error) (bool return true, nil } } else if err == nil { - ref := types.RefFromObject(x) + ref := x.GetK8sRef() if ref.GVK.Group == "apiextensions.k8s.io" && ref.GVK.Kind == "CustomResourceDefinition" { // this is a freshly deployed CRD, so we must perform rediscovery in case an api resource can't be found a.deployedNewCRD = true @@ -271,7 +270,7 @@ func (a *applyUtil) handleNewCRDs(x *unstructured.Unstructured, err error) (bool return false, err } -func (a *applyUtil) waitHook(ref types.ObjectRef) bool { +func (a *applyUtil) waitHook(ref k8s2.ObjectRef) bool { if a.o.dryRun { return true } @@ -335,10 +334,10 @@ func (a *applyUtil) applyDeploymentItem(d *deploymentItem) { return } - var toDelete []types.ObjectRef + var toDelete []k8s2.ObjectRef for _, x := range d.config.DeleteObjects { for _, gvk := range a.k.GetGVKs(x.Group, x.Version, x.Kind) { - ref := types.ObjectRef{ + ref := k8s2.ObjectRef{ GVK: gvk, Name: x.Name, Namespace: x.Namespace, @@ -355,7 +354,7 @@ func (a *applyUtil) applyDeploymentItem(d *deploymentItem) { initialDeploy := true for _, o := range d.objects { - if a.deploymentCollection.getRemoteObject(types.RefFromObject(o)) != nil { + if a.deploymentCollection.getRemoteObject(o.GetK8sRef()) != nil { initialDeploy = false } } @@ -368,7 +367,7 @@ func (a *applyUtil) applyDeploymentItem(d *deploymentItem) { h.runHooks(d, []string{"pre-deploy-upgrade", "pre-deploy"}) } - var applyObjects []*unstructured.Unstructured + var applyObjects []*uo.UnstructuredObject for _, o := range d.objects { if h.getHook(o) != nil { continue diff --git a/pkg/k8s/delete_utils.go b/pkg/deployment/delete_utils.go similarity index 78% rename from pkg/k8s/delete_utils.go rename to pkg/deployment/delete_utils.go index 7f8c4c317..42351d18e 100644 --- a/pkg/k8s/delete_utils.go +++ b/pkg/deployment/delete_utils.go @@ -1,7 +1,9 @@ -package k8s +package deployment import ( + "github.com/codablock/kluctl/pkg/k8s" "github.com/codablock/kluctl/pkg/types" + k8s2 "github.com/codablock/kluctl/pkg/types/k8s" "github.com/codablock/kluctl/pkg/utils" log "github.com/sirupsen/logrus" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -38,13 +40,13 @@ var deleteOrder = [][]string{ nil, } -func objectRefForExclusion(k *K8sCluster, ref types.ObjectRef) types.ObjectRef { +func objectRefForExclusion(k *k8s.K8sCluster, ref k8s2.ObjectRef) k8s2.ObjectRef { ref = k.RemoveNamespaceFromRefIfNeeded(ref) ref.GVK.Version = "" return ref } -func filterObjectsForDelete(k *K8sCluster, objects []*v1.PartialObjectMetadata, apiFilter []string, inclusion *utils.Inclusion, excludedObjects map[types.ObjectRef]bool) ([]*v1.PartialObjectMetadata, error) { +func filterObjectsForDelete(k *k8s.K8sCluster, objects []*v1.PartialObjectMetadata, apiFilter []string, inclusion *utils.Inclusion, excludedObjects map[k8s2.ObjectRef]bool) ([]*v1.PartialObjectMetadata, error) { filteredResources := make(map[schema.GroupKind]bool) for _, gk := range k.GetFilteredGKs(apiFilter) { filteredResources[gk] = true @@ -54,7 +56,7 @@ func filterObjectsForDelete(k *K8sCluster, objects []*v1.PartialObjectMetadata, var ret []*v1.PartialObjectMetadata for _, o := range objects { - ref := types.RefFromPartialObject(o) + ref := k8s2.RefFromPartialObject(o) if _, ok := filteredResources[ref.GVK.GroupKind()]; !ok { continue } @@ -110,10 +112,10 @@ func filterObjectsForDelete(k *K8sCluster, objects []*v1.PartialObjectMetadata, return ret, nil } -func FindObjectsForDelete(k *K8sCluster, labels map[string]string, inclusion *utils.Inclusion, excludedObjects []types.ObjectRef) ([]types.ObjectRef, error) { +func FindObjectsForDelete(k *k8s.K8sCluster, labels map[string]string, inclusion *utils.Inclusion, excludedObjects []k8s2.ObjectRef) ([]k8s2.ObjectRef, error) { log.Infof("Getting all cluster objects matching deleteByLabels") - excludedObjectsMap := make(map[types.ObjectRef]bool) + excludedObjectsMap := make(map[k8s2.ObjectRef]bool) for _, ref := range excludedObjects { excludedObjectsMap[objectRefForExclusion(k, ref)] = true } @@ -123,7 +125,7 @@ func FindObjectsForDelete(k *K8sCluster, labels map[string]string, inclusion *ut return nil, err } - var ret []types.ObjectRef + var ret []k8s2.ObjectRef for _, filter := range deleteOrder { l, err := filterObjectsForDelete(k, allClusterObjects, filter, inclusion, excludedObjectsMap) @@ -131,7 +133,7 @@ func FindObjectsForDelete(k *K8sCluster, labels map[string]string, inclusion *ut return nil, err } for _, o := range l { - ref := types.RefFromPartialObject(o) + ref := k8s2.RefFromPartialObject(o) excludedObjectsMap[objectRefForExclusion(k, ref)] = true ret = append(ret, ref) } @@ -140,7 +142,7 @@ func FindObjectsForDelete(k *K8sCluster, labels map[string]string, inclusion *ut return ret, nil } -func DeleteObjects(k *K8sCluster, refs []types.ObjectRef, doWait bool) (*types.CommandResult, error) { +func DeleteObjects(k *k8s.K8sCluster, refs []k8s2.ObjectRef, doWait bool) (*types.CommandResult, error) { wp := utils.NewDebuggerAwareWorkerPool(8) defer wp.StopWait(false) @@ -148,7 +150,7 @@ func DeleteObjects(k *K8sCluster, refs []types.ObjectRef, doWait bool) (*types.C namespaceNames := make(map[string]bool) var mutex sync.Mutex - handleResult := func(ref types.ObjectRef, apiWarnings []ApiWarning, err error) { + handleResult := func(ref k8s2.ObjectRef, apiWarnings []k8s.ApiWarning, err error) { mutex.Lock() defer mutex.Unlock() @@ -173,7 +175,7 @@ func DeleteObjects(k *K8sCluster, refs []types.ObjectRef, doWait bool) (*types.C if ref.GVK.GroupVersion().String() == "v1" && ref.GVK.Kind == "Namespace" { namespaceNames[ref.Name] = true wp.Submit(func() error { - apiWarnings, err := k.DeleteSingleObject(ref, DeleteOptions{NoWait: !doWait, IgnoreNotFoundError: true}) + apiWarnings, err := k.DeleteSingleObject(ref, k8s.DeleteOptions{NoWait: !doWait, IgnoreNotFoundError: true}) handleResult(ref, apiWarnings, err) return nil }) @@ -194,7 +196,7 @@ func DeleteObjects(k *K8sCluster, refs []types.ObjectRef, doWait bool) (*types.C continue } wp.Submit(func() error { - apiWarnings, err := k.DeleteSingleObject(ref, DeleteOptions{NoWait: !doWait, IgnoreNotFoundError: true}) + apiWarnings, err := k.DeleteSingleObject(ref, k8s.DeleteOptions{NoWait: !doWait, IgnoreNotFoundError: true}) handleResult(ref, apiWarnings, err) return nil }) diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index acfeb969f..43272eaba 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -7,6 +7,7 @@ import ( "github.com/codablock/kluctl/pkg/k8s" "github.com/codablock/kluctl/pkg/seal" "github.com/codablock/kluctl/pkg/types" + k8s2 "github.com/codablock/kluctl/pkg/types/k8s" "github.com/codablock/kluctl/pkg/utils" "github.com/codablock/kluctl/pkg/utils/uo" "github.com/codablock/kluctl/pkg/validation" @@ -14,7 +15,6 @@ import ( "golang.org/x/sync/semaphore" "io/fs" "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "path/filepath" "reflect" "strings" @@ -30,10 +30,10 @@ type DeploymentCollection struct { forSeal bool deployments []*deploymentItem - remoteObjects map[types.ObjectRef]*unstructured.Unstructured + remoteObjects map[k8s2.ObjectRef]*uo.UnstructuredObject - errors map[types.ObjectRef]map[types.DeploymentError]bool - warnings map[types.ObjectRef]map[types.DeploymentError]bool + errors map[k8s2.ObjectRef]map[types.DeploymentError]bool + warnings map[k8s2.ObjectRef]map[types.DeploymentError]bool mutex sync.Mutex } @@ -44,9 +44,9 @@ func NewDeploymentCollection(project *DeploymentProject, images *Images, inclusi inclusion: inclusion, RenderDir: renderDir, forSeal: forSeal, - remoteObjects: map[types.ObjectRef]*unstructured.Unstructured{}, - errors: map[types.ObjectRef]map[types.DeploymentError]bool{}, - warnings: map[types.ObjectRef]map[types.DeploymentError]bool{}, + remoteObjects: map[k8s2.ObjectRef]*uo.UnstructuredObject{}, + errors: map[k8s2.ObjectRef]map[types.DeploymentError]bool{}, + warnings: map[k8s2.ObjectRef]map[types.DeploymentError]bool{}, } indexes := make(map[string]int) @@ -243,8 +243,8 @@ func (c *DeploymentCollection) updateRemoteObjects(k *k8s.K8sCluster) error { return nil } - notFoundRefsMap := make(map[types.ObjectRef]bool) - var notFoundRefsList []types.ObjectRef + notFoundRefsMap := make(map[k8s2.ObjectRef]bool) + var notFoundRefsList []k8s2.ObjectRef for ref := range c.localObjectsByRef() { if _, ok := c.remoteObjects[ref]; !ok { if _, ok = notFoundRefsMap[ref]; !ok { @@ -264,27 +264,27 @@ func (c *DeploymentCollection) updateRemoteObjects(k *k8s.K8sCluster) error { return err } for _, o := range r { - c.remoteObjects[types.RefFromObject(o)] = o + c.remoteObjects[o.GetK8sRef()] = o } return nil } -func (c *DeploymentCollection) ForgetRemoteObject(ref types.ObjectRef) { +func (c *DeploymentCollection) ForgetRemoteObject(ref k8s2.ObjectRef) { delete(c.remoteObjects, ref) } -func (c *DeploymentCollection) localObjectsByRef() map[types.ObjectRef]bool { - ret := make(map[types.ObjectRef]bool) +func (c *DeploymentCollection) localObjectsByRef() map[k8s2.ObjectRef]bool { + ret := make(map[k8s2.ObjectRef]bool) for _, d := range c.deployments { for _, o := range d.objects { - ret[types.RefFromObject(o)] = true + ret[o.GetK8sRef()] = true } } return ret } -func (c *DeploymentCollection) localObjectRefs() []types.ObjectRef { - var ret []types.ObjectRef +func (c *DeploymentCollection) localObjectRefs() []k8s2.ObjectRef { + var ret []k8s2.ObjectRef for ref := range c.localObjectsByRef() { ret = append(ret, ref) } @@ -325,7 +325,7 @@ func (c *DeploymentCollection) Deploy(k *k8s.K8sCluster, forceApply bool, replac if err != nil { return nil, err } - var appliedHookObjectsList []*unstructured.Unstructured + var appliedHookObjectsList []*uo.UnstructuredObject for _, o := range appliedHookObjects { appliedHookObjectsList = append(appliedHookObjectsList, o) } @@ -362,7 +362,7 @@ func (c *DeploymentCollection) Diff(k *k8s.K8sCluster, forceApply bool, replaceO if err != nil { return nil, err } - var appliedHookObjectsList []*unstructured.Unstructured + var appliedHookObjectsList []*uo.UnstructuredObject for _, o := range appliedHookObjects { appliedHookObjectsList = append(appliedHookObjectsList, o) } @@ -386,17 +386,17 @@ func (c *DeploymentCollection) Diff(k *k8s.K8sCluster, forceApply bool, replaceO } func (c *DeploymentCollection) PokeImages(k *k8s.K8sCluster) (*types.CommandResult, error) { - allObjects := make(map[types.ObjectRef]*unstructured.Unstructured) + allObjects := make(map[k8s2.ObjectRef]*uo.UnstructuredObject) for _, d := range c.deployments { if !d.checkInclusionForDeploy() { continue } for _, o := range d.objects { - allObjects[types.RefFromObject(o)] = o + allObjects[o.GetK8sRef()] = o } } - containersAndImages := make(map[types.ObjectRef][]types.FixedImage) + containersAndImages := make(map[k8s2.ObjectRef][]types.FixedImage) for _, fi := range c.images.seenImages { _, ok := allObjects[*fi.Object] if !ok { @@ -410,9 +410,8 @@ func (c *DeploymentCollection) PokeImages(k *k8s.K8sCluster) (*types.CommandResu wp := utils.NewWorkerPoolWithErrors(8) defer wp.StopWait(false) - doPokeImage := func(images []types.FixedImage, o *unstructured.Unstructured) (*unstructured.Unstructured, error) { - u := uo.FromUnstructured(o) - containers, _, _ := u.GetNestedObjectList("spec", "template", "spec", "containers") + doPokeImage := func(images []types.FixedImage, o *uo.UnstructuredObject) (*uo.UnstructuredObject, error) { + containers, _, _ := o.GetNestedObjectList("spec", "template", "spec", "containers") for _, image := range images { for _, c := range containers { @@ -422,17 +421,17 @@ func (c *DeploymentCollection) PokeImages(k *k8s.K8sCluster) (*types.CommandResu } } } - return u.ToUnstructured(), nil + return o, nil } - appliedObjects := make(map[types.ObjectRef]*unstructured.Unstructured) + appliedObjects := make(map[k8s2.ObjectRef]*uo.UnstructuredObject) var mutex sync.Mutex for ref, containers := range containersAndImages { ref := ref containers := containers wp.Submit(func() error { - newObject, err := c.doReplaceObject(k, ref, func(o *unstructured.Unstructured) (*unstructured.Unstructured, error) { + newObject, err := c.doReplaceObject(k, ref, func(o *uo.UnstructuredObject) (*uo.UnstructuredObject, error) { return doPokeImage(containers, o) }) if err != nil { @@ -466,8 +465,8 @@ func (c *DeploymentCollection) Downscale(k *k8s.K8sCluster) (*types.CommandResul wp := utils.NewWorkerPoolWithErrors(8) defer wp.StopWait(false) - appliedObjects := make(map[types.ObjectRef]*unstructured.Unstructured) - var deletedObjects []types.ObjectRef + appliedObjects := make(map[k8s2.ObjectRef]*uo.UnstructuredObject) + var deletedObjects []k8s2.ObjectRef var mutex sync.Mutex for _, d := range c.deployments { @@ -476,7 +475,7 @@ func (c *DeploymentCollection) Downscale(k *k8s.K8sCluster) (*types.CommandResul } for _, o := range d.objects { o := o - ref := types.RefFromObject(o) + ref := o.GetK8sRef() if isDownscaleDelete(o) { wp.Submit(func() error { apiWarnings, err := k.DeleteSingleObject(ref, k8s.DeleteOptions{IgnoreNotFoundError: true}) @@ -491,7 +490,7 @@ func (c *DeploymentCollection) Downscale(k *k8s.K8sCluster) (*types.CommandResul }) } else { wp.Submit(func() error { - o2, err := c.doReplaceObject(k, ref, func(remote *unstructured.Unstructured) (*unstructured.Unstructured, error) { + o2, err := c.doReplaceObject(k, ref, func(remote *uo.UnstructuredObject) (*uo.UnstructuredObject, error) { return downscaleObject(remote, o) }) if err != nil { @@ -550,7 +549,7 @@ func (c *DeploymentCollection) Validate(k *k8s.K8sCluster) *types.ValidateResult continue } - ref := types.RefFromObject(o) + ref := o.GetK8sRef() remoteObject := c.getRemoteObject(ref) if remoteObject == nil { @@ -567,29 +566,29 @@ func (c *DeploymentCollection) Validate(k *k8s.K8sCluster) *types.ValidateResult return &result } -func (c *DeploymentCollection) FindDeleteObjects(k *k8s.K8sCluster) ([]types.ObjectRef, error) { +func (c *DeploymentCollection) FindDeleteObjects(k *k8s.K8sCluster) ([]k8s2.ObjectRef, error) { labels := c.project.getDeleteByLabels() - return k8s.FindObjectsForDelete(k, labels, c.inclusion, nil) + return FindObjectsForDelete(k, labels, c.inclusion, nil) } -func (c *DeploymentCollection) FindOrphanObjects(k *k8s.K8sCluster) ([]types.ObjectRef, error) { +func (c *DeploymentCollection) FindOrphanObjects(k *k8s.K8sCluster) ([]k8s2.ObjectRef, error) { log.Infof("Searching for orphan objects") labels := c.project.getDeleteByLabels() - return k8s.FindObjectsForDelete(k, labels, c.inclusion, c.localObjectRefs()) + return FindObjectsForDelete(k, labels, c.inclusion, c.localObjectRefs()) } -func (c *DeploymentCollection) doApply(k *k8s.K8sCluster, o applyUtilOptions) (map[types.ObjectRef]*unstructured.Unstructured, map[types.ObjectRef]*unstructured.Unstructured, []types.ObjectRef, error) { +func (c *DeploymentCollection) doApply(k *k8s.K8sCluster, o applyUtilOptions) (map[k8s2.ObjectRef]*uo.UnstructuredObject, map[k8s2.ObjectRef]*uo.UnstructuredObject, []k8s2.ObjectRef, error) { au := newApplyUtil(c, k, o) au.applyDeployments() - var deletedObjects []types.ObjectRef + var deletedObjects []k8s2.ObjectRef for ref := range au.deletedObjects { deletedObjects = append(deletedObjects, ref) } return au.appliedObjects, au.appliedHookObjects, deletedObjects, nil } -func (c *DeploymentCollection) doDiff(k *k8s.K8sCluster, appliedObjects map[types.ObjectRef]*unstructured.Unstructured, ignoreTags bool, ignoreLabels bool, ignoreAnnotations bool) ([]*unstructured.Unstructured, []*types.ChangedObject, error) { - var newObjects []*unstructured.Unstructured +func (c *DeploymentCollection) doDiff(k *k8s.K8sCluster, appliedObjects map[k8s2.ObjectRef]*uo.UnstructuredObject, ignoreTags bool, ignoreLabels bool, ignoreAnnotations bool) ([]*uo.UnstructuredObject, []*types.ChangedObject, error) { + var newObjects []*uo.UnstructuredObject var changedObjects []*types.ChangedObject var mutex sync.Mutex @@ -604,7 +603,7 @@ func (c *DeploymentCollection) doDiff(k *k8s.K8sCluster, appliedObjects map[type ignoreForDiffs := d.project.getIgnoreForDiffs(ignoreTags, ignoreLabels, ignoreAnnotations) for _, o := range d.objects { o := o - ref := types.RefFromObject(o) + ref := o.GetK8sRef() ao, ok := appliedObjects[ref] if !ok { // if we can't even find it in appliedObjects, it probably ran into an error @@ -652,10 +651,10 @@ func (c *DeploymentCollection) doDiff(k *k8s.K8sCluster, appliedObjects map[type return newObjects, changedObjects, nil } -func (c *DeploymentCollection) doReplaceObject(k *k8s.K8sCluster, ref types.ObjectRef, callback func(o *unstructured.Unstructured) (*unstructured.Unstructured, error)) (*unstructured.Unstructured, error) { +func (c *DeploymentCollection) doReplaceObject(k *k8s.K8sCluster, ref k8s2.ObjectRef, callback func(o *uo.UnstructuredObject) (*uo.UnstructuredObject, error)) (*uo.UnstructuredObject, error) { firstCall := true for true { - var remote *unstructured.Unstructured + var remote *uo.UnstructuredObject if firstCall { remote = c.getRemoteObject(ref) } else { @@ -671,7 +670,7 @@ func (c *DeploymentCollection) doReplaceObject(k *k8s.K8sCluster, ref types.Obje } firstCall = false - remoteCopy := uo.CopyUnstructured(remote) + remoteCopy := remote.Clone() modified, err := callback(remoteCopy) if err != nil { return nil, err @@ -695,7 +694,7 @@ func (c *DeploymentCollection) doReplaceObject(k *k8s.K8sCluster, ref types.Obje return nil, fmt.Errorf("unexpected end of loop") } -func (c *DeploymentCollection) addWarning(ref types.ObjectRef, warning error) { +func (c *DeploymentCollection) addWarning(ref k8s2.ObjectRef, warning error) { de := types.DeploymentError{ Ref: ref, Error: warning.Error(), @@ -710,7 +709,7 @@ func (c *DeploymentCollection) addWarning(ref types.ObjectRef, warning error) { m[de] = true } -func (c *DeploymentCollection) addError(ref types.ObjectRef, err error) { +func (c *DeploymentCollection) addError(ref k8s2.ObjectRef, err error) { de := types.DeploymentError{ Ref: ref, Error: err.Error(), @@ -725,13 +724,13 @@ func (c *DeploymentCollection) addError(ref types.ObjectRef, err error) { m[de] = true } -func (c *DeploymentCollection) addApiWarnings(ref types.ObjectRef, warnings []k8s.ApiWarning) { +func (c *DeploymentCollection) addApiWarnings(ref k8s2.ObjectRef, warnings []k8s.ApiWarning) { for _, w := range warnings { c.addWarning(ref, fmt.Errorf(w.Text)) } } -func (c *DeploymentCollection) hadError(ref types.ObjectRef) bool { +func (c *DeploymentCollection) hadError(ref k8s2.ObjectRef) bool { c.mutex.Lock() defer c.mutex.Unlock() _, ok := c.errors[ref] @@ -765,22 +764,21 @@ func (c *DeploymentCollection) warningsList() []types.DeploymentError { func (c *DeploymentCollection) clearErrorsAndWarnings() { c.mutex.Lock() defer c.mutex.Unlock() - c.warnings = map[types.ObjectRef]map[types.DeploymentError]bool{} - c.errors = map[types.ObjectRef]map[types.DeploymentError]bool{} + c.warnings = map[k8s2.ObjectRef]map[types.DeploymentError]bool{} + c.errors = map[k8s2.ObjectRef]map[types.DeploymentError]bool{} } -func (c *DeploymentCollection) getRemoteObject(ref types.ObjectRef) *unstructured.Unstructured { +func (c *DeploymentCollection) getRemoteObject(ref k8s2.ObjectRef) *uo.UnstructuredObject { o, _ := c.remoteObjects[ref] return o } -func (c *DeploymentCollection) FindRenderedImages() map[types.ObjectRef][]string { - ret := make(map[types.ObjectRef][]string) +func (c *DeploymentCollection) FindRenderedImages() map[k8s2.ObjectRef][]string { + ret := make(map[k8s2.ObjectRef][]string) for _, d := range c.deployments { for _, o := range d.objects { - ref := types.RefFromObject(o) - u := uo.FromUnstructured(o) - l, ok, _ := u.GetNestedObjectList("spec", "template", "spec", "containers") + ref := o.GetK8sRef() + l, ok, _ := o.GetNestedObjectList("spec", "template", "spec", "containers") if !ok { continue } diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 1f5026899..3df049f03 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -9,7 +9,6 @@ import ( "github.com/codablock/kluctl/pkg/yaml" "io/fs" "io/ioutil" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "os" "path" "path/filepath" @@ -30,7 +29,7 @@ type deploymentItem struct { dir *string index int - objects []*unstructured.Unstructured + objects []*uo.UnstructuredObject relProjectDir string relToRootItemDir string @@ -172,12 +171,11 @@ func (di *deploymentItem) resolveSealedSecrets() error { sealedSecretsDir := di.project.getSealedSecretsDir() baseSourcePath := di.project.sealedSecretsDir - var y map[string]interface{} - err := yaml.ReadYamlFile(filepath.Join(di.renderedDir, "kustomization.yml"), &y) + y, err := uo.FromFile(filepath.Join(di.renderedDir, "kustomization.yml")) if err != nil { return err } - l, _, err := unstructured.NestedStringSlice(y, "resources") + l, _, err := y.GetNestedStringList("resources") if err != nil { return err } @@ -335,15 +333,13 @@ func (di *deploymentItem) postprocessAndLoadObjects(k *k8s.K8sCluster) error { return err } - di.objects = []*unstructured.Unstructured{} + di.objects = []*uo.UnstructuredObject{} for _, o := range objects { m, ok := o.(map[string]interface{}) if !ok { return fmt.Errorf("object is not a map") } - di.objects = append(di.objects, &unstructured.Unstructured{ - Object: m, - }) + di.objects = append(di.objects, uo.FromMap(m)) } for _, o := range di.objects { @@ -352,9 +348,9 @@ func (di *deploymentItem) postprocessAndLoadObjects(k *k8s.K8sCluster) error { } // Set common labels/annotations - o.SetLabels(uo.CopyMergeStrMap(o.GetLabels(), di.getCommonLabels())) + o.SetK8sLabels(uo.CopyMergeStrMap(o.GetK8sLabels(), di.getCommonLabels())) commonAnnotations := di.getCommonAnnotations() - o.SetAnnotations(uo.CopyMergeStrMap(o.GetAnnotations(), commonAnnotations)) + o.SetK8sAnnotations(uo.CopyMergeStrMap(o.GetK8sAnnotations(), commonAnnotations)) // Resolve image placeholders err = di.collection.images.ResolvePlaceholders(k, o, di.relRenderedDir, di.getTags().ListKeys()) @@ -393,4 +389,4 @@ func init() { }) openapiInitDoneMutex.Unlock() }() -} \ No newline at end of file +} diff --git a/pkg/deployment/downscale_utils.go b/pkg/deployment/downscale_utils.go index 7b4b34c55..d34f590d5 100644 --- a/pkg/deployment/downscale_utils.go +++ b/pkg/deployment/downscale_utils.go @@ -2,11 +2,9 @@ package deployment import ( "fmt" - "github.com/codablock/kluctl/pkg/types" "github.com/codablock/kluctl/pkg/utils/uo" "github.com/codablock/kluctl/pkg/yaml" jsonpatch "github.com/evanphx/json-patch" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" "regexp" "strconv" @@ -18,25 +16,25 @@ var ( downscaleAnnotationIgnore = "kluctl.io/downscale-ignore" ) -func isDownscaleDelete(o *unstructured.Unstructured) bool { - a, _ := o.GetAnnotations()[downscaleAnnotationDelete] +func isDownscaleDelete(o *uo.UnstructuredObject) bool { + a, _ := o.GetK8sAnnotations()[downscaleAnnotationDelete] b, _ := strconv.ParseBool(a) return b } -func isDownscaleIgnore(o *unstructured.Unstructured) bool { - a, _ := o.GetAnnotations()[downscaleAnnotationIgnore] +func isDownscaleIgnore(o *uo.UnstructuredObject) bool { + a, _ := o.GetK8sAnnotations()[downscaleAnnotationIgnore] b, _ := strconv.ParseBool(a) return b } -func downscaleObject(remote *unstructured.Unstructured, local *unstructured.Unstructured) (*unstructured.Unstructured, error) { +func downscaleObject(remote *uo.UnstructuredObject, local *uo.UnstructuredObject) (*uo.UnstructuredObject, error) { ret := remote if isDownscaleIgnore(local) { return ret, nil } var patch jsonpatch.Patch - for k, v := range local.GetAnnotations() { + for k, v := range local.GetK8sAnnotations() { if downscaleAnnotationPatchRegex.MatchString(k) { j, err := yaml.ConvertYamlToJson([]byte(v)) if err != nil { @@ -63,26 +61,26 @@ func downscaleObject(remote *unstructured.Unstructured, local *unstructured.Unst if err != nil { return nil, err } - ret = &unstructured.Unstructured{} + ret = &uo.UnstructuredObject{} err = yaml.ReadYamlBytes(j, &ret.Object) if err != nil { return nil, err } } - ref := types.RefFromObject(remote) + ref := remote.GetK8sRef() switch ref.GVK.GroupKind() { case schema.GroupKind{Group: "apps", Kind: "Deployment"}: fallthrough case schema.GroupKind{Group: "apps", Kind: "StatefulSet"}: - ret = uo.CopyUnstructured(ret) - err := uo.FromUnstructured(ret).SetNestedField(0, "spec", "replicas") + ret = ret.Clone() + err := ret.SetNestedField(0, "spec", "replicas") if err != nil { return nil, err } case schema.GroupKind{Group: "batch", Kind: "CronJob"}: - ret = uo.CopyUnstructured(ret) - err := uo.FromUnstructured(ret).SetNestedField(true, "spec", "suspend") + ret = ret.Clone() + err := ret.SetNestedField(true, "spec", "suspend") if err != nil { return nil, err } diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index 30819ac72..36e5a7876 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -5,10 +5,10 @@ import ( "github.com/codablock/kluctl/pkg/k8s" "github.com/codablock/kluctl/pkg/types" "github.com/codablock/kluctl/pkg/utils" + "github.com/codablock/kluctl/pkg/utils/uo" "github.com/codablock/kluctl/pkg/utils/versions" "github.com/codablock/kluctl/pkg/yaml" "io/ioutil" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "os" "os/exec" "path/filepath" @@ -200,12 +200,13 @@ func (c *helmChart) Render(k *k8s.K8sCluster) error { if !ok { return fmt.Errorf("object is not a map") } + o := uo.FromMap(m) // "helm install" will deploy resources to the given namespace automatically, but "helm template" does not // add the necessary namespace in the rendered resources - _, found, _ := unstructured.NestedString(m, "metadata", "namespace") - if !found && k.IsNamespaced((&unstructured.Unstructured{Object: m}).GroupVersionKind()) { - _ = unstructured.SetNestedField(m, namespace, "metadata", "namespace") + ns := o.GetK8sNamespace() + if ns == "" && k.IsNamespaced(o.GetK8sGVK()) { + o.SetK8sNamespace(namespace) } } rendered, err = yaml.WriteYamlAllBytes(parsed) diff --git a/pkg/deployment/hooks_util.go b/pkg/deployment/hooks_util.go index dd468e805..ea57b2122 100644 --- a/pkg/deployment/hooks_util.go +++ b/pkg/deployment/hooks_util.go @@ -2,10 +2,10 @@ package deployment import ( "fmt" - "github.com/codablock/kluctl/pkg/types" + "github.com/codablock/kluctl/pkg/types/k8s" "github.com/codablock/kluctl/pkg/utils" + "github.com/codablock/kluctl/pkg/utils/uo" log "github.com/sirupsen/logrus" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "sort" "strconv" "strings" @@ -36,7 +36,7 @@ type hooksUtil struct { } type hook struct { - object *unstructured.Unstructured + object *uo.UnstructuredObject hooks map[string]bool weight int deletePolicies map[string]bool @@ -61,7 +61,7 @@ func (u *hooksUtil) runHooks(d *deploymentItem, hooks []string) { if len(l) != 0 && log.IsLevelEnabled(log.DebugLevel) { doLog(log.DebugLevel, "Sorted hooks:") for _, h := range l { - doLog(log.DebugLevel, " %s", types.RefFromObject(h.object).String()) + doLog(log.DebugLevel, " %s", h.object.GetK8sRef().String()) } } @@ -79,7 +79,7 @@ func (u *hooksUtil) runHooks(d *deploymentItem, hooks []string) { } doDeleteForPolicy := func(h *hook) bool { - ref := types.RefFromObject(h.object) + ref := h.object.GetK8sRef() var dpStr []string for p := range h.deletePolicies { dpStr = append(dpStr, p) @@ -95,12 +95,12 @@ func (u *hooksUtil) runHooks(d *deploymentItem, hooks []string) { doDeleteForPolicy(h) } - waitResults := make(map[types.ObjectRef]bool) + waitResults := make(map[k8s.ObjectRef]bool) if len(applyObjects) != 0 { doLog(log.InfoLevel, "Applying %d hooks", len(applyObjects)) } for _, h := range applyObjects { - ref := types.RefFromObject(h.object) + ref := h.object.GetK8sRef() _, replaced := h.deletePolicies["before-hook-creation"] doLog(log.DebugLevel, "Applying hook %s", ref.String()) u.a.applyObject(h.object, replaced, true) @@ -117,7 +117,7 @@ func (u *hooksUtil) runHooks(d *deploymentItem, hooks []string) { var deleteAfterObjects []*hook for i := len(applyObjects) - 1; i >= 0; i-- { h := applyObjects[i] - ref := types.RefFromObject(h.object) + ref := h.object.GetK8sRef() waitResult, ok := waitResults[ref] if !ok { continue @@ -141,11 +141,11 @@ func (u *hooksUtil) runHooks(d *deploymentItem, hooks []string) { } } -func (u *hooksUtil) getHook(o *unstructured.Unstructured) *hook { - ref := types.RefFromObject(o) +func (u *hooksUtil) getHook(o *uo.UnstructuredObject) *hook { + ref := o.GetK8sRef() getSet := func(name string) map[string]bool { ret := make(map[string]bool) - a, ok := o.GetAnnotations()[name] + a, ok := o.GetK8sAnnotations()[name] if !ok { return ret } @@ -184,9 +184,9 @@ func (u *hooksUtil) getHook(o *unstructured.Unstructured) *hook { helmCompatibility("pre-delete", "pre-delete") helmCompatibility("post-delete", "post-delete") - weightStr, ok := o.GetAnnotations()["kluctl.io/hook-weight"] + weightStr, ok := o.GetK8sAnnotations()["kluctl.io/hook-weight"] if !ok { - weightStr, ok = o.GetAnnotations()["helm.sh/hook-weight"] + weightStr, ok = o.GetK8sAnnotations()["helm.sh/hook-weight"] } if !ok { weightStr = "0" @@ -210,7 +210,7 @@ func (u *hooksUtil) getHook(o *unstructured.Unstructured) *hook { } } - waitStr, ok := o.GetAnnotations()["kluctl.io/hook-wait"] + waitStr, ok := o.GetK8sAnnotations()["kluctl.io/hook-wait"] if !ok { waitStr = "true" } @@ -233,7 +233,7 @@ func (u *hooksUtil) getHook(o *unstructured.Unstructured) *hook { } } -func (u *hooksUtil) getSortedHooksList(objects []*unstructured.Unstructured) []*hook { +func (u *hooksUtil) getSortedHooksList(objects []*uo.UnstructuredObject) []*hook { var ret []*hook for _, o := range objects { h := u.getHook(o) diff --git a/pkg/deployment/images.go b/pkg/deployment/images.go index ae7791fa3..12001706f 100644 --- a/pkg/deployment/images.go +++ b/pkg/deployment/images.go @@ -7,12 +7,12 @@ import ( "github.com/codablock/kluctl/pkg/k8s" "github.com/codablock/kluctl/pkg/registries" "github.com/codablock/kluctl/pkg/types" + k8s2 "github.com/codablock/kluctl/pkg/types/k8s" "github.com/codablock/kluctl/pkg/utils" "github.com/codablock/kluctl/pkg/utils/uo" "github.com/codablock/kluctl/pkg/utils/versions" "github.com/codablock/kluctl/pkg/yaml" "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "strings" "sync" ) @@ -144,11 +144,11 @@ func (images *Images) extractContainerName(parent interface{}) string { return "" } -func (images *Images) ResolvePlaceholders(k *k8s.K8sCluster, o *unstructured.Unstructured, deploymentDir string, tags []string) error { - ref := types.RefFromObject(o) - deployment := fmt.Sprintf("%s/%s", o.GetKind(), o.GetName()) +func (images *Images) ResolvePlaceholders(k *k8s.K8sCluster, o *uo.UnstructuredObject, deploymentDir string, tags []string) error { + ref := o.GetK8sRef() + deployment := fmt.Sprintf("%s/%s", ref.GVK.Kind, ref.Name) - var remoteObject *unstructured.Unstructured + var remoteObject *uo.UnstructuredObject triedRemoteObject := false err := uo.NewObjectIterator(o.Object).IterateLeafs(func(it *uo.ObjectIterator) error { @@ -174,7 +174,7 @@ func (images *Images) ResolvePlaceholders(k *k8s.K8sCluster, o *unstructured.Uns if !triedRemoteObject { triedRemoteObject = true - remoteObject, _, err = k.GetSingleObject(types.RefFromObject(o)) + remoteObject, _, err = k.GetSingleObject(o.GetK8sRef()) if err != nil && !errors.IsNotFound(err) { return err } @@ -182,7 +182,7 @@ func (images *Images) ResolvePlaceholders(k *k8s.K8sCluster, o *unstructured.Uns var deployed *string if remoteObject != nil && ph.startOffset == 0 && ph.endOffset == len(s) { - x, found, _ := uo.FromUnstructured(remoteObject).GetNestedField(it.KeyPath()...) + x, found, _ := remoteObject.GetNestedField(it.KeyPath()...) if found { if y, ok := x.(string); ok { deployed = &y @@ -212,7 +212,7 @@ func (images *Images) ResolvePlaceholders(k *k8s.K8sCluster, o *unstructured.Uns return nil } -func (images *Images) resolveImage(ph *placeHolder, ref types.ObjectRef, deployment string, container string, deployed *string, deploymentDir string, tags []string) (*string, error) { +func (images *Images) resolveImage(ph *placeHolder, ref k8s2.ObjectRef, deployment string, container string, deployed *string, deploymentDir string, tags []string) (*string, error) { fixed := images.GetFixedImage(ph.Image, ref.Namespace, deployment, container) registry, err := images.GetLatestImageFromRegistry(ph.Image, ph.LatestVersion) diff --git a/pkg/diff/diff.go b/pkg/diff/diff.go index b035dbe46..f69823483 100644 --- a/pkg/diff/diff.go +++ b/pkg/diff/diff.go @@ -9,7 +9,6 @@ import ( "github.com/hexops/gotextdiff/myers" "github.com/hexops/gotextdiff/span" diff2 "github.com/r3labs/diff/v2" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "reflect" "strconv" "strings" @@ -38,7 +37,7 @@ func convertPath(path []string, o interface{}) (string, error) { return uo.KeyListToJsonPath(ret), nil } -func Diff(oldObject *unstructured.Unstructured, newObject *unstructured.Unstructured) ([]types.Change, error) { +func Diff(oldObject *uo.UnstructuredObject, newObject *uo.UnstructuredObject) ([]types.Change, error) { differ, err := diff2.NewDiffer(diff2.AllowTypeMismatch(true)) if err != nil { return nil, err diff --git a/pkg/diff/managed_fields.go b/pkg/diff/managed_fields.go index 54692bd9f..50056f087 100644 --- a/pkg/diff/managed_fields.go +++ b/pkg/diff/managed_fields.go @@ -6,7 +6,6 @@ import ( "github.com/codablock/kluctl/pkg/utils/uo" log "github.com/sirupsen/logrus" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "reflect" "regexp" "sigs.k8s.io/structured-merge-diff/v4/fieldpath" @@ -61,7 +60,7 @@ func checkListItemMatch(o interface{}, pathElement fieldpath.PathElement, index } } -func convertToKeyList(remote *unstructured.Unstructured, path fieldpath.Path) ([]interface{}, bool, error) { +func convertToKeyList(remote *uo.UnstructuredObject, path fieldpath.Path) ([]interface{}, bool, error) { var ret []interface{} var o interface{} = remote.Object for _, e := range path { @@ -101,8 +100,8 @@ func convertToKeyList(remote *unstructured.Unstructured, path fieldpath.Path) ([ return ret, true, nil } -func ResolveFieldManagerConflicts(local *unstructured.Unstructured, remote *unstructured.Unstructured, conflictStatus metav1.Status) (*unstructured.Unstructured, []LostOwnership, error) { - managedFields := remote.GetManagedFields() +func ResolveFieldManagerConflicts(local *uo.UnstructuredObject, remote *uo.UnstructuredObject, conflictStatus metav1.Status) (*uo.UnstructuredObject, []LostOwnership, error) { + managedFields := remote.GetK8sManagedFields() // "stupid" because the string representation in "details.causes.field" might be ambiguous as k8s does not escape dots fieldsAsStupidStrings := make(map[string][]fieldpath.Path) @@ -122,15 +121,15 @@ func ResolveFieldManagerConflicts(local *unstructured.Unstructured, remote *unst }) } - ret := uo.CopyUnstructured(local) + ret := local.Clone() forceApplyAll := false - if x, ok := local.GetAnnotations()[`metadata.annotations["kluctl.io/force-apply"]`]; ok { - forceApplyAll, _ = strconv.ParseBool(x) + if x := local.GetK8sAnnotation("kluctl.io/force-apply"); x != nil { + forceApplyAll, _ = strconv.ParseBool(*x) } forceApplyFields := make(map[string]bool) - for k, v := range local.GetAnnotations() { + for k, v := range local.GetK8sAnnotations() { if !forceApplyFieldAnnotationRegex.MatchString(k) { continue } @@ -138,7 +137,7 @@ func ResolveFieldManagerConflicts(local *unstructured.Unstructured, remote *unst if err != nil { return nil, nil, err } - fields, err := j.ListMatchingFields(uo.FromUnstructured(ret)) + fields, err := j.ListMatchingFields(ret) if err != nil { return nil, nil, err } @@ -168,14 +167,14 @@ func ResolveFieldManagerConflicts(local *unstructured.Unstructured, remote *unst if !found { return nil, nil, fmt.Errorf("field '%s' not found in remote object", cause.Field) } - localValue, found, err := uo.FromUnstructured(local).GetNestedField(p...) + localValue, found, err := local.GetNestedField(p...) if err != nil { return nil, nil, err } if !found { return nil, nil, fmt.Errorf("field '%s' not found in local object", cause.Field) } - remoteValue, found, err := uo.FromUnstructured(remote).GetNestedField(p...) + remoteValue, found, err :=remote.GetNestedField(p...) if !found { log.Fatalf("field '%s' not found in remote object...which can't be!", cause.Field) } diff --git a/pkg/diff/normalize.go b/pkg/diff/normalize.go index be09e4f96..0f44f8568 100644 --- a/pkg/diff/normalize.go +++ b/pkg/diff/normalize.go @@ -5,7 +5,6 @@ import ( "github.com/codablock/kluctl/pkg/k8s" "github.com/codablock/kluctl/pkg/types" "github.com/codablock/kluctl/pkg/utils/uo" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "regexp" "strconv" "strings" @@ -95,7 +94,7 @@ func normalizeServiceAccount(o *uo.UnstructuredObject) { } func normalizeMetadata(k *k8s.K8sCluster, o *uo.UnstructuredObject) { - k.RemoveNamespaceIfNeeded(o.ToUnstructured()) + k.RemoveNamespaceIfNeeded(o) // We don't care about managedFields when diffing (they just produce noise) _ = o.RemoveNestedField("metadata", "managedFields") @@ -124,12 +123,12 @@ func normalizeMisc(o *uo.UnstructuredObject) { var ignoreDiffFieldAnnotationRegex = regexp.MustCompile(`^kluctl.io/ignore-diff-field(-\d*)?$`) // NormalizeObject Performs some deterministic sorting and other normalizations to avoid ugly diffs due to order changes -func NormalizeObject(k *k8s.K8sCluster, o_ *unstructured.Unstructured, ignoreForDiffs []*types.IgnoreForDiffItemConfig, localObject *unstructured.Unstructured) *unstructured.Unstructured { - gvk := o_.GroupVersionKind() - name := o_.GetName() - ns := o_.GetNamespace() +func NormalizeObject(k *k8s.K8sCluster, o_ *uo.UnstructuredObject, ignoreForDiffs []*types.IgnoreForDiffItemConfig, localObject *uo.UnstructuredObject) *uo.UnstructuredObject { + gvk := o_.GetK8sGVK() + name := o_.GetK8sName() + ns := o_.GetK8sNamespace() - o := uo.FromUnstructured(o_).Clone() + o := o_.Clone() normalizeMetadata(k, o) normalizeMisc(o) @@ -172,13 +171,13 @@ func NormalizeObject(k *k8s.K8sCluster, o_ *unstructured.Unstructured, ignoreFor } } - ignoreAll, _ := strconv.ParseBool(localObject.GetAnnotations()["kluctl.io/ignore-diff"]) + ignoreAll, _ := strconv.ParseBool(localObject.GetK8sAnnotations()["kluctl.io/ignore-diff"]) if ignoreAll { // Return empty object so that diffs will always be empty - return &unstructured.Unstructured{Object: map[string]interface{}{}} + return &uo.UnstructuredObject{Object: map[string]interface{}{}} } - for k, v := range localObject.GetAnnotations() { + for k, v := range localObject.GetK8sAnnotations() { if ignoreDiffFieldAnnotationRegex.MatchString(k) { j, err := uo.NewMyJsonPath(v) if err != nil { @@ -188,5 +187,5 @@ func NormalizeObject(k *k8s.K8sCluster, o_ *unstructured.Unstructured, ignoreFor } } - return o.ToUnstructured() + return o } diff --git a/pkg/git/poor_mans_clone.go b/pkg/git/poor_mans_clone.go index e8ae58375..bd13f0498 100644 --- a/pkg/git/poor_mans_clone.go +++ b/pkg/git/poor_mans_clone.go @@ -82,7 +82,7 @@ func PoorMansClone(sourceDir string, targetDir string, ref string) error { } var ref2 plumbing.ReferenceName if ref != "" { - if strings.HasPrefix(ref,"refs/heads") { + if strings.HasPrefix(ref, "refs/heads") { ref2 = plumbing.ReferenceName(ref) } else { if _, err := r.Reference(plumbing.NewBranchReferenceName(ref), true); err == nil { diff --git a/pkg/jinja2/vars.go b/pkg/jinja2/vars.go index 4784e9416..29bb791f8 100644 --- a/pkg/jinja2/vars.go +++ b/pkg/jinja2/vars.go @@ -4,9 +4,9 @@ import ( "fmt" "github.com/codablock/kluctl/pkg/k8s" "github.com/codablock/kluctl/pkg/types" + k8s2 "github.com/codablock/kluctl/pkg/types/k8s" "github.com/codablock/kluctl/pkg/utils/uo" "github.com/codablock/kluctl/pkg/yaml" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) type VarsCtx struct { @@ -57,13 +57,13 @@ func (vc *VarsCtx) LoadVarsList(k *k8s.K8sCluster, searchDirs []string, varsList return err } } else if v.ClusterConfigMap != nil { - ref := types.NewObjectRef("", "v1", "ConfigMap", v.ClusterConfigMap.Name, v.ClusterConfigMap.Namespace) + ref := k8s2.NewObjectRef("", "v1", "ConfigMap", v.ClusterConfigMap.Name, v.ClusterConfigMap.Namespace) err := vc.loadVarsFromK8sObject(k, ref, v.ClusterConfigMap.Key) if err != nil { return err } } else if v.ClusterSecret != nil { - ref := types.NewObjectRef("", "v1", "Secret", v.ClusterSecret.Name, v.ClusterSecret.Namespace) + ref := k8s2.NewObjectRef("", "v1", "Secret", v.ClusterSecret.Name, v.ClusterSecret.Namespace) err := vc.loadVarsFromK8sObject(k, ref, v.ClusterSecret.Key) if err != nil { return err @@ -86,13 +86,13 @@ func (vc *VarsCtx) loadVarsFile(p string, searchDirs []string) error { return nil } -func (vc *VarsCtx) loadVarsFromK8sObject(k *k8s.K8sCluster, ref types.ObjectRef, key string) error { +func (vc *VarsCtx) loadVarsFromK8sObject(k *k8s.K8sCluster, ref k8s2.ObjectRef, key string) error { o, _, err := k.GetSingleObject(ref) if err != nil { return err } - value, found, err := unstructured.NestedString(o.UnstructuredContent(), "data", key) + value, found, err := o.GetNestedString("data", key) if err != nil { return err } diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index b9e4c80a7..68e30ca2d 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -3,15 +3,15 @@ package k8s import ( "context" "fmt" - types2 "github.com/codablock/kluctl/pkg/types" + "github.com/codablock/kluctl/pkg/types/k8s" "github.com/codablock/kluctl/pkg/utils" "github.com/codablock/kluctl/pkg/utils/uo" + "github.com/codablock/kluctl/pkg/yaml" goversion "github.com/hashicorp/go-version" log "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/discovery/cached/disk" @@ -241,7 +241,7 @@ func (k *K8sCluster) IsNamespaced(gvk schema.GroupVersionKind) bool { return r.Namespaced } -func (k *K8sCluster) ShouldRemoveNamespace(ref types2.ObjectRef) bool { +func (k *K8sCluster) ShouldRemoveNamespace(ref k8s.ObjectRef) bool { k.mutex.Lock() defer k.mutex.Unlock() r, ok := k.allResources[ref.GVK] @@ -258,15 +258,15 @@ func (k *K8sCluster) ShouldRemoveNamespace(ref types2.ObjectRef) bool { return true } -func (k *K8sCluster) RemoveNamespaceIfNeeded(o *unstructured.Unstructured) { - ref := types2.RefFromObject(o) +func (k *K8sCluster) RemoveNamespaceIfNeeded(o *uo.UnstructuredObject) { + ref := o.GetK8sRef() if !k.ShouldRemoveNamespace(ref) { return } - o.SetNamespace("") + o.SetK8sNamespace("") } -func (k *K8sCluster) RemoveNamespaceFromRefIfNeeded(ref types2.ObjectRef) types2.ObjectRef { +func (k *K8sCluster) RemoveNamespaceFromRefIfNeeded(ref k8s.ObjectRef) k8s.ObjectRef { if !k.ShouldRemoveNamespace(ref) { return ref } @@ -412,8 +412,8 @@ func (k *K8sCluster) buildLabelSelector(labels map[string]string) string { return ret } -func (k *K8sCluster) ListObjects(gvk schema.GroupVersionKind, namespace string) (*unstructured.UnstructuredList, []ApiWarning, error) { - var result *unstructured.UnstructuredList +func (k *K8sCluster) ListObjects(gvk schema.GroupVersionKind, namespace string) ([]*uo.UnstructuredObject, []ApiWarning, error) { + var result []*uo.UnstructuredObject apiWarnings, err := k.withDynamicClientForGVK(gvk, namespace, func(r dynamic.ResourceInterface) error { o := v1.ListOptions{} @@ -421,7 +421,9 @@ func (k *K8sCluster) ListObjects(gvk schema.GroupVersionKind, namespace string) if err != nil { return err } - result = x + for _, o := range x.Items { + result = append(result, uo.FromUnstructured(&o)) + } return nil }) return result, apiWarnings, err @@ -495,26 +497,26 @@ func (k *K8sCluster) ListAllObjectsMetadata(verbs []string, namespace string, la return ret, nil } -func (k *K8sCluster) GetSingleObject(ref types2.ObjectRef) (*unstructured.Unstructured, []ApiWarning, error) { - var result *unstructured.Unstructured +func (k *K8sCluster) GetSingleObject(ref k8s.ObjectRef) (*uo.UnstructuredObject, []ApiWarning, error) { + var result *uo.UnstructuredObject apiWarnings, err := k.withDynamicClientForGVK(ref.GVK, ref.Namespace, func(r dynamic.ResourceInterface) error { o := v1.GetOptions{} x, err := r.Get(context.Background(), ref.Name, o) if err != nil { return err } - result = x + result = uo.FromUnstructured(x) return nil }) return result, apiWarnings, err } -func (k *K8sCluster) GetObjectsByRefs(refs []types2.ObjectRef) ([]*unstructured.Unstructured, map[types2.ObjectRef][]ApiWarning, error) { +func (k *K8sCluster) GetObjectsByRefs(refs []k8s.ObjectRef) ([]*uo.UnstructuredObject, map[k8s.ObjectRef][]ApiWarning, error) { wp := utils.NewWorkerPoolWithErrors(32) defer wp.StopWait(false) - var ret []*unstructured.Unstructured - retApiWarnings := make(map[types2.ObjectRef][]ApiWarning) + var ret []*uo.UnstructuredObject + retApiWarnings := make(map[k8s.ObjectRef][]ApiWarning) var mutex sync.Mutex for _, ref_ := range refs { @@ -550,7 +552,7 @@ type DeleteOptions struct { IgnoreNotFoundError bool } -func (k *K8sCluster) DeleteSingleObject(ref types2.ObjectRef, options DeleteOptions) ([]ApiWarning, error) { +func (k *K8sCluster) DeleteSingleObject(ref k8s.ObjectRef, options DeleteOptions) ([]ApiWarning, error) { dryRun := k.DryRun || options.ForceDryRun pp := v1.DeletePropagationForeground @@ -580,7 +582,7 @@ func (k *K8sCluster) DeleteSingleObject(ref types2.ObjectRef, options DeleteOpti }) } -func (k *K8sCluster) waitForDeletedObject(r dynamic.ResourceInterface, ref types2.ObjectRef) error { +func (k *K8sCluster) waitForDeletedObject(r dynamic.ResourceInterface, ref k8s.ObjectRef) error { for true { o := v1.GetOptions{} _, err := r.Get(context.Background(), ref.Name, o) @@ -597,7 +599,7 @@ func (k *K8sCluster) waitForDeletedObject(r dynamic.ResourceInterface, ref types var v1_21, _ = goversion.NewVersion("1.21") var v1_1000, _ = goversion.NewVersion("1.1000") -func (k *K8sCluster) FixObjectForPatch(o *unstructured.Unstructured) *unstructured.Unstructured { +func (k *K8sCluster) FixObjectForPatch(o *uo.UnstructuredObject) *uo.UnstructuredObject { // A bug in versions < 1.20 cause errors when applying resources that have some fields omitted which have // default values. We need to fix these resources. // UPDATE even though https://github.com/kubernetes-sigs/structured-merge-diff/issues/130 says it's fixed, the @@ -609,7 +611,7 @@ func (k *K8sCluster) FixObjectForPatch(o *unstructured.Unstructured) *unstructur return o } - o = uo.CopyUnstructured(o) + o = o.Clone() fixPorts := func(p string) { if !needsDefaultsFix { @@ -685,12 +687,12 @@ type PatchOptions struct { ForceApply bool } -func (k *K8sCluster) PatchObject(o *unstructured.Unstructured, options PatchOptions) (*unstructured.Unstructured, []ApiWarning, error) { +func (k *K8sCluster) PatchObject(o *uo.UnstructuredObject, options PatchOptions) (*uo.UnstructuredObject, []ApiWarning, error) { dryRun := k.DryRun || options.ForceDryRun - ref := types2.RefFromObject(o) + ref := o.GetK8sRef() log2 := log.WithField("ref", ref) - data, err := o.MarshalJSON() + data, err := yaml.WriteYamlBytes(o) if err != nil { return nil, nil, err } @@ -706,13 +708,13 @@ func (k *K8sCluster) PatchObject(o *unstructured.Unstructured, options PatchOpti } log2.Debugf("patching") - var result *unstructured.Unstructured + var result *uo.UnstructuredObject apiWarnings, err := k.withDynamicClientForGVK(ref.GVK, ref.Namespace, func(r dynamic.ResourceInterface) error { x, err := r.Patch(context.Background(), ref.Name, types.ApplyPatchType, data, po) if err != nil { return fmt.Errorf("failed to patch %s: %w", ref.String(), err) } - result = x + result = uo.FromUnstructured(x) return nil }) return result, apiWarnings, err @@ -722,26 +724,26 @@ type UpdateOptions struct { ForceDryRun bool } -func (k *K8sCluster) UpdateObject(o *unstructured.Unstructured, options UpdateOptions) (*unstructured.Unstructured, []ApiWarning, error) { +func (k *K8sCluster) UpdateObject(o *uo.UnstructuredObject, options UpdateOptions) (*uo.UnstructuredObject, []ApiWarning, error) { dryRun := k.DryRun || options.ForceDryRun - ref := types2.RefFromObject(o) + ref := o.GetK8sRef() log2 := log.WithField("ref", ref) - uo := v1.UpdateOptions{ + updateOpts := v1.UpdateOptions{ FieldManager: "kluctl", } if dryRun { - uo.DryRun = []string{"All"} + updateOpts.DryRun = []string{"All"} } log2.Debugf("updating") - var result *unstructured.Unstructured + var result *uo.UnstructuredObject apiWarnings, err := k.withDynamicClientForGVK(ref.GVK, ref.Namespace, func(r dynamic.ResourceInterface) error { - x, err := r.Update(context.Background(), o, uo) + x, err := r.Update(context.Background(), o.ToUnstructured(), updateOpts) if err != nil { return err } - result = x + result = uo.FromUnstructured(x) return nil }) return result, apiWarnings, err diff --git a/pkg/registries/registries.go b/pkg/registries/registries.go index 0db3b863e..200f6786e 100644 --- a/pkg/registries/registries.go +++ b/pkg/registries/registries.go @@ -46,7 +46,7 @@ func ListImageTags(image string) ([]string, error) { var globalMyKeychain myKeychain type myKeychain struct { - cachedAuth utils.ThreadSafeCache + cachedAuth utils.ThreadSafeCache cachedResponses utils.ThreadSafeMultiCache authRealms map[string]bool authErrors map[string]bool @@ -126,7 +126,7 @@ func (kc *myKeychain) readCachedResponse(key string) []byte { return nil } - if time.Now().Sub(st.ModTime()) > 55 * time.Minute { + if time.Now().Sub(st.ModTime()) > 55*time.Minute { return nil } @@ -160,12 +160,12 @@ func (kc *myKeychain) writeCachedResponse(key string, data []byte) { } } - err := ioutil.WriteFile(cachePath + ".tmp", data, 0o600) + err := ioutil.WriteFile(cachePath+".tmp", data, 0o600) if err != nil { log.Warningf("writeCachedResponse failed: %v", err) return } - err = os.Rename(cachePath + ".tmp", cachePath) + err = os.Rename(cachePath+".tmp", cachePath) if err != nil { log.Warningf("writeCachedResponse failed: %v", err) return diff --git a/pkg/seal/bootstrap.go b/pkg/seal/bootstrap.go index 69d707630..3e9b7d2cc 100644 --- a/pkg/seal/bootstrap.go +++ b/pkg/seal/bootstrap.go @@ -7,7 +7,7 @@ import ( "encoding/pem" "github.com/bitnami-labs/sealed-secrets/pkg/crypto" "github.com/codablock/kluctl/pkg/k8s" - "github.com/codablock/kluctl/pkg/types" + k8s2 "github.com/codablock/kluctl/pkg/types/k8s" "github.com/codablock/kluctl/pkg/utils/uo" log "github.com/sirupsen/logrus" v1 "k8s.io/api/core/v1" @@ -22,7 +22,7 @@ const secretName = "sealed-secrets-key-kluctl-bootstrap" const configMapName = "sealed-secrets-key-kluctl-bootstrap" func BootstrapSealedSecrets(k *k8s.K8sCluster, namespace string) error { - existing, _, err := k.GetSingleObject(types.ObjectRef{ + existing, _, err := k.GetSingleObject(k8s2.ObjectRef{ GVK: schema.GroupVersionKind{Group: "apiextensions.k8s.io", Version: "v1", Kind: "CustomResourceDefinition"}, Name: "sealedsecrets.bitnami.com", }) @@ -31,7 +31,7 @@ func BootstrapSealedSecrets(k *k8s.K8sCluster, namespace string) error { return nil } - existing, _, err = k.GetSingleObject(types.ObjectRef{ + existing, _, err = k.GetSingleObject(k8s2.ObjectRef{ GVK: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "ConfigMap"}, Name: configMapName, Namespace: namespace, @@ -82,11 +82,11 @@ func writeKey(k *k8s.K8sCluster, key *rsa.PrivateKey, certs []*x509.Certificate, v1.TLSCertKey: string(certbytes), } - _, _, err := k.PatchObject(secret.ToUnstructured(), k8s.PatchOptions{}) + _, _, err := k.PatchObject(secret, k8s.PatchOptions{}) if err != nil { return err } - _, _, err = k.PatchObject(configMap.ToUnstructured(), k8s.PatchOptions{}) + _, _, err = k.PatchObject(configMap, k8s.PatchOptions{}) if err != nil { return err } diff --git a/pkg/seal/sealer.go b/pkg/seal/sealer.go index af04ae796..546a03122 100644 --- a/pkg/seal/sealer.go +++ b/pkg/seal/sealer.go @@ -9,6 +9,7 @@ import ( "github.com/bitnami-labs/sealed-secrets/pkg/crypto" "github.com/codablock/kluctl/pkg/k8s" "github.com/codablock/kluctl/pkg/types" + k8s2 "github.com/codablock/kluctl/pkg/types/k8s" "github.com/codablock/kluctl/pkg/utils" "github.com/codablock/kluctl/pkg/utils/uo" "github.com/codablock/kluctl/pkg/yaml" @@ -55,7 +56,7 @@ func NewSealer(k *k8s.K8sCluster, sealedSecretsNamespace string, sealedSecretsCo // We treat the hashed kube-root-ca.crt as cluster id for now. We also accept that it might change when keys // get rotated. func getClusterId(k *k8s.K8sCluster) (string, error) { - o, _, err := k.GetSingleObject(types.ObjectRef{ + o, _, err := k.GetSingleObject(k8s2.ObjectRef{ GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, Name: "kube-root-ca.crt", Namespace: "kube-system", @@ -63,7 +64,7 @@ func getClusterId(k *k8s.K8sCluster) (string, error) { if err != nil { return "", fmt.Errorf("failed to retrieve kube-root-ca.crt: %w", err) } - kubeRootCA, ok, err := uo.FromUnstructured(o).GetNestedString("data", "ca.crt") + kubeRootCA, ok, err := o.GetNestedString("data", "ca.crt") if err != nil { return "", fmt.Errorf("failed to retrieve kube-root-ca.crt: %w", err) } diff --git a/pkg/types/command_result.go b/pkg/types/command_result.go index ea292bcb3..0e5cecf5d 100644 --- a/pkg/types/command_result.go +++ b/pkg/types/command_result.go @@ -1,7 +1,8 @@ package types import ( - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "github.com/codablock/kluctl/pkg/types/k8s" + "github.com/codablock/kluctl/pkg/utils/uo" ) type Change struct { @@ -13,30 +14,30 @@ type Change struct { } type ChangedObject struct { - NewObject *unstructured.Unstructured `yaml:"newObject,omitempty"` - OldObject *unstructured.Unstructured `yaml:"oldObject,omitempty"` - Changes []Change `yaml:"changes,omitempty"` + NewObject *uo.UnstructuredObject `yaml:"newObject,omitempty"` + OldObject *uo.UnstructuredObject `yaml:"oldObject,omitempty"` + Changes []Change `yaml:"changes,omitempty"` } type DeploymentError struct { - Ref ObjectRef `yaml:"ref"` - Error string `yaml:"error"` + Ref k8s.ObjectRef `yaml:"ref"` + Error string `yaml:"error"` } type CommandResult struct { - NewObjects []*unstructured.Unstructured `yaml:"newObjects,omitempty"` - ChangedObjects []*ChangedObject `yaml:"changedObjects,omitempty"` - HookObjects []*unstructured.Unstructured `yaml:"hook-objects,omitempty"` - OrphanObjects []ObjectRef `yaml:"orphanObjects,omitempty"` - DeletedObjects []ObjectRef `yaml:"deletedObjects,omitempty"` - Errors []DeploymentError `yaml:"errors,omitempty"` - Warnings []DeploymentError `yaml:"warnings,omitempty"` - SeenImages []FixedImage `yaml:"seenImages,omitempty"` + NewObjects []*uo.UnstructuredObject `yaml:"newObjects,omitempty"` + ChangedObjects []*ChangedObject `yaml:"changedObjects,omitempty"` + HookObjects []*uo.UnstructuredObject `yaml:"hookObjects,omitempty"` + OrphanObjects []k8s.ObjectRef `yaml:"orphanObjects,omitempty"` + DeletedObjects []k8s.ObjectRef `yaml:"deletedObjects,omitempty"` + Errors []DeploymentError `yaml:"errors,omitempty"` + Warnings []DeploymentError `yaml:"warnings,omitempty"` + SeenImages []FixedImage `yaml:"seenImages,omitempty"` } type ValidateResultEntry struct { - Ref ObjectRef `yaml:"ref"` - Annotation string `yaml:"annotation"` + Ref k8s.ObjectRef `yaml:"ref"` + Annotation string `yaml:"annotation"` Message string `yaml:"message"` } diff --git a/pkg/types/ref.go b/pkg/types/k8s/ref.go similarity index 98% rename from pkg/types/ref.go rename to pkg/types/k8s/ref.go index 33176e1b1..2bed9b695 100644 --- a/pkg/types/ref.go +++ b/pkg/types/k8s/ref.go @@ -1,4 +1,4 @@ -package types +package k8s import ( "fmt" diff --git a/pkg/types/target_config.go b/pkg/types/target_config.go index 7f8dee841..794b7d5a7 100644 --- a/pkg/types/target_config.go +++ b/pkg/types/target_config.go @@ -1,16 +1,19 @@ package types -import "github.com/codablock/kluctl/pkg/utils/uo" +import ( + "github.com/codablock/kluctl/pkg/types/k8s" + "github.com/codablock/kluctl/pkg/utils/uo" +) type FixedImage struct { - Image string `yaml:"image" validate:"required"` - ResultImage string `yaml:"resultImage" validate:"required"` - DeployedImage *string `yaml:"deployedImage,omitempty"` - RegistryImage *string `yaml:"registryImage,omitempty"` - Namespace *string `yaml:"namespace,omitempty"` - Object *ObjectRef `yaml:"object,omitempty"` - Deployment *string `yaml:"deployment,omitempty"` - Container *string `yaml:"container,omitempty"` + Image string `yaml:"image" validate:"required"` + ResultImage string `yaml:"resultImage" validate:"required"` + DeployedImage *string `yaml:"deployedImage,omitempty"` + RegistryImage *string `yaml:"registryImage,omitempty"` + Namespace *string `yaml:"namespace,omitempty"` + Object *k8s.ObjectRef `yaml:"object,omitempty"` + Deployment *string `yaml:"deployment,omitempty"` + Container *string `yaml:"container,omitempty"` VersionFilter *string `yaml:"versionFilter,omitempty"` DeployTags []string `yaml:"deployTags,omitempty"` DeploymentDir *string `yaml:"deploymentDir,omitempty"` diff --git a/pkg/utils/uo/k8s_fields.go b/pkg/utils/uo/k8s_fields.go index 6ee9044f9..341b6bf10 100644 --- a/pkg/utils/uo/k8s_fields.go +++ b/pkg/utils/uo/k8s_fields.go @@ -1,7 +1,9 @@ package uo import ( + "github.com/codablock/kluctl/pkg/types/k8s" log "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" ) @@ -78,6 +80,14 @@ func (uo *UnstructuredObject) SetK8sNamespace(namespace string) { } +func (uo *UnstructuredObject) GetK8sRef() k8s.ObjectRef { + return k8s.ObjectRef{ + GVK: uo.GetK8sGVK(), + Name: uo.GetK8sName(), + Namespace: uo.GetK8sNamespace(), + } +} + func (uo *UnstructuredObject) GetK8sLabels() map[string]string { ret, ok, err := uo.GetNestedStringMapCopy("metadata", "labels") if err != nil { @@ -149,3 +159,23 @@ func (uo *UnstructuredObject) SetK8sAnnotation(name string, value string) { log.Fatal(err) } } + +func (uo *UnstructuredObject) GetK8sResourceVersion() string { + ret, _, _ := uo.GetNestedString("metadata", "resourceVersion") + return ret +} + +func (uo *UnstructuredObject) SetK8sResourceVersion(rv string) { + if rv == "" { + _ = uo.RemoveNestedField("metadata", "resourceVersion") + } else { + err := uo.SetNestedField(rv, "metadata", "resourceVersion") + if err != nil { + log.Fatal(err) + } + } +} + +func (uo *UnstructuredObject) GetK8sManagedFields() []metav1.ManagedFieldsEntry { + return uo.ToUnstructured().GetManagedFields() +} diff --git a/pkg/utils/uo/nested_fields.go b/pkg/utils/uo/nested_fields.go index 77b9412e7..0de90bab6 100644 --- a/pkg/utils/uo/nested_fields.go +++ b/pkg/utils/uo/nested_fields.go @@ -123,6 +123,25 @@ func (uo *UnstructuredObject) GetNestedList(keys ...interface{}) ([]interface{}, return l, true, nil } +func (uo *UnstructuredObject) GetNestedStringList(keys ...interface{}) ([]string, bool, error) { + l, found, err := uo.GetNestedList(keys...) + if err != nil { + return nil, false, err + } + if !found { + return nil, false, nil + } + ret := make([]string, len(l)) + for i, x := range l { + s, ok := x.(string) + if !ok { + return nil, false, fmt.Errorf("value at index %s is not a slice of strings", KeyListToJsonPath(keys)) + } + ret[i] = s + } + return ret, true, nil +} + func (uo *UnstructuredObject) GetNestedObject(keys ...interface{}) (*UnstructuredObject, bool, error) { a, found, err := uo.GetNestedField(keys...) if err != nil { diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 0ab99d994..5f303199b 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -34,12 +34,6 @@ func DeepCopy(dst interface{}, src interface{}) error { }) } -func CloneStringSlice(s []string) []string { - n := make([]string, len(s), len(s)) - copy(n, s) - return n -} - func FindStrInSlice(a []string, s string) int { for i, v := range a { if v == s { diff --git a/pkg/validation/validation.go b/pkg/validation/validation.go index 469cecca5..8e7cfbbe1 100644 --- a/pkg/validation/validation.go +++ b/pkg/validation/validation.go @@ -4,7 +4,6 @@ import ( "fmt" "github.com/codablock/kluctl/pkg/types" "github.com/codablock/kluctl/pkg/utils/uo" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" "strconv" "strings" @@ -32,8 +31,8 @@ func (c condition) getMessage(def string) string { return c.message } -func ValidateObject(o *unstructured.Unstructured, notReadyIsError bool) (ret types.ValidateResult) { - ref := types.RefFromObject(o) +func ValidateObject(o *uo.UnstructuredObject, notReadyIsError bool) (ret types.ValidateResult) { + ref := o.GetK8sRef() // We assume all is good in case no validation is performed ret.Ready = true @@ -53,7 +52,7 @@ func ValidateObject(o *unstructured.Unstructured, notReadyIsError bool) (ret typ } }() - for k, v := range o.GetAnnotations() { + for k, v := range o.GetK8sAnnotations() { if strings.HasPrefix(k, resultAnnotation) { ret.Results = append(ret.Results, types.ValidateResultEntry{ Ref: ref, @@ -63,8 +62,7 @@ func ValidateObject(o *unstructured.Unstructured, notReadyIsError bool) (ret typ } } - u := uo.FromUnstructured(o) - status, ok, _ := u.GetNestedObject("status") + status, ok, _ := o.GetNestedObject("status") if !ok { return } @@ -209,7 +207,7 @@ func ValidateObject(o *unstructured.Unstructured, notReadyIsError bool) (ret typ return i, nil } - switch u.GetK8sGVK().GroupKind() { + switch o.GetK8sGVK().GroupKind() { case schema.GroupKind{Group: "", Kind: "Pod"}: c := getCondition("Ready", false, false) if c.status != "True" { @@ -237,13 +235,13 @@ func ValidateObject(o *unstructured.Unstructured, notReadyIsError bool) (ret typ addNotReady("Volume is not bound") } case schema.GroupKind{Group: "", Kind: "Service"}: - svcType, _, _ := u.GetNestedString("spec", "type") + svcType, _, _ := o.GetNestedString("spec", "type") if svcType != "ExternalName" { - clusterIP, _, _ := u.GetNestedString("spec", "clusterIP") + clusterIP, _, _ := o.GetNestedString("spec", "clusterIP") if clusterIP == "" { addError("Service does not have a cluster IP") } else if svcType == "LoadBalancer" { - externalIPs, _, _ := u.GetNestedList("spec", "externalIPs") + externalIPs, _, _ := o.GetNestedList("spec", "externalIPs") if len(externalIPs) == 0 { ingress, _, _ := status.GetNestedList("loadBalancer", "ingress") if len(ingress) == 0 { @@ -253,14 +251,14 @@ func ValidateObject(o *unstructured.Unstructured, notReadyIsError bool) (ret typ } } case schema.GroupKind{Group: "apps", Kind: "DaemonSet"}: - updateStrategyType, _, _ := u.GetNestedString("spec", "updateStrategy", "type") + updateStrategyType, _, _ := o.GetNestedString("spec", "updateStrategy", "type") if updateStrategyType == "RollingUpdate" { updatedNumberScheduled := getStatusFieldInt("updatedNumberScheduled", true, true, 0) desiredNumberScheduled := getStatusFieldInt("desiredNumberScheduled", true, true, 0) if updatedNumberScheduled != desiredNumberScheduled { addNotReady(fmt.Sprintf("DaemonSet is not ready. %d out of %d expected pods have been scheduled", updatedNumberScheduled, desiredNumberScheduled)) } else { - maxUnavailableI, _, _ := u.GetNestedField("spec", "updateStrategy", "maxUnavailable") + maxUnavailableI, _, _ := o.GetNestedField("spec", "updateStrategy", "maxUnavailable") if maxUnavailableI == nil { maxUnavailableI = 1 } @@ -286,10 +284,10 @@ func ValidateObject(o *unstructured.Unstructured, notReadyIsError bool) (ret typ } } case schema.GroupKind{Group: "apps", Kind: "StatefulSet"}: - updateStrategyType, _, _ := u.GetNestedString("spec", "updateStrategy", "type") + updateStrategyType, _, _ := o.GetNestedString("spec", "updateStrategy", "type") if updateStrategyType == "RollingUpdate" { - partition, _, _ := u.GetNestedInt("spec", "updateStrategy", "rollingUpdate", "partition") - replicas, ok, _ := u.GetNestedInt("spec", "replicas") + partition, _, _ := o.GetNestedInt("spec", "updateStrategy", "rollingUpdate", "partition") + replicas, ok, _ := o.GetNestedInt("spec", "replicas") if !ok { replicas = 1 } From e4f24eb70915376e2a784d1937ff6527966ff432 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 8 Mar 2022 14:30:51 +0100 Subject: [PATCH 0466/2916] fix: Store refs along with objects --- cmd/kluctl/commands/command_result.go | 4 ++-- pkg/deployment/deployment_collection.go | 23 ++++++++++++++++------- pkg/diff/managed_fields.go | 2 +- pkg/types/command_result.go | 24 +++++++++++++++--------- pkg/types/target_config.go | 6 +++--- 5 files changed, 37 insertions(+), 22 deletions(-) diff --git a/cmd/kluctl/commands/command_result.go b/cmd/kluctl/commands/command_result.go index ee4b5f6ee..dd6d36aa8 100644 --- a/cmd/kluctl/commands/command_result.go +++ b/cmd/kluctl/commands/command_result.go @@ -24,7 +24,7 @@ func formatCommandResultText(cr *types.CommandResult) string { buf.WriteString("\nNew objects:\n") var refs []k8s.ObjectRef for _, o := range cr.NewObjects { - refs = append(refs, o.GetK8sRef()) + refs = append(refs, o.Ref) } prettyObjectRefs(buf, refs) } @@ -51,7 +51,7 @@ func formatCommandResultText(cr *types.CommandResult) string { buf.WriteString("\nApplied hooks:\n") var refs []k8s.ObjectRef for _, o := range cr.HookObjects { - refs = append(refs, o.GetK8sRef()) + refs = append(refs, o.Ref) } prettyObjectRefs(buf, refs) } diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index 43272eaba..dc7447287 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -325,9 +325,12 @@ func (c *DeploymentCollection) Deploy(k *k8s.K8sCluster, forceApply bool, replac if err != nil { return nil, err } - var appliedHookObjectsList []*uo.UnstructuredObject + var appliedHookObjectsList []*types.RefAndObject for _, o := range appliedHookObjects { - appliedHookObjectsList = append(appliedHookObjectsList, o) + appliedHookObjectsList = append(appliedHookObjectsList, &types.RefAndObject{ + Ref: o.GetK8sRef(), + Object: o, + }) } newObjects, changedObjects, err := c.doDiff(k, appliedObjects, false, false, false) if err != nil { @@ -362,9 +365,12 @@ func (c *DeploymentCollection) Diff(k *k8s.K8sCluster, forceApply bool, replaceO if err != nil { return nil, err } - var appliedHookObjectsList []*uo.UnstructuredObject + var appliedHookObjectsList []*types.RefAndObject for _, o := range appliedHookObjects { - appliedHookObjectsList = append(appliedHookObjectsList, o) + appliedHookObjectsList = append(appliedHookObjectsList, &types.RefAndObject{ + Ref: o.GetK8sRef(), + Object: o, + }) } newObjects, changedObjects, err := c.doDiff(k, appliedObjects, ignoreTags, ignoreLabels, ignoreAnnotations) if err != nil { @@ -587,8 +593,8 @@ func (c *DeploymentCollection) doApply(k *k8s.K8sCluster, o applyUtilOptions) (m return au.appliedObjects, au.appliedHookObjects, deletedObjects, nil } -func (c *DeploymentCollection) doDiff(k *k8s.K8sCluster, appliedObjects map[k8s2.ObjectRef]*uo.UnstructuredObject, ignoreTags bool, ignoreLabels bool, ignoreAnnotations bool) ([]*uo.UnstructuredObject, []*types.ChangedObject, error) { - var newObjects []*uo.UnstructuredObject +func (c *DeploymentCollection) doDiff(k *k8s.K8sCluster, appliedObjects map[k8s2.ObjectRef]*uo.UnstructuredObject, ignoreTags bool, ignoreLabels bool, ignoreAnnotations bool) ([]*types.RefAndObject, []*types.ChangedObject, error) { + var newObjects []*types.RefAndObject var changedObjects []*types.ChangedObject var mutex sync.Mutex @@ -612,7 +618,10 @@ func (c *DeploymentCollection) doDiff(k *k8s.K8sCluster, appliedObjects map[k8s2 ro, _ := c.remoteObjects[ref] if ao != nil && ro == nil { - newObjects = append(newObjects, ao) + newObjects = append(newObjects, &types.RefAndObject{ + Ref: ao.GetK8sRef(), + Object: ao, + }) } else if ao == nil && ro != nil { // deleted? continue diff --git a/pkg/diff/managed_fields.go b/pkg/diff/managed_fields.go index 50056f087..bffaf0c67 100644 --- a/pkg/diff/managed_fields.go +++ b/pkg/diff/managed_fields.go @@ -174,7 +174,7 @@ func ResolveFieldManagerConflicts(local *uo.UnstructuredObject, remote *uo.Unstr if !found { return nil, nil, fmt.Errorf("field '%s' not found in local object", cause.Field) } - remoteValue, found, err :=remote.GetNestedField(p...) + remoteValue, found, err := remote.GetNestedField(p...) if !found { log.Fatalf("field '%s' not found in remote object...which can't be!", cause.Field) } diff --git a/pkg/types/command_result.go b/pkg/types/command_result.go index 0e5cecf5d..55fcb7e0f 100644 --- a/pkg/types/command_result.go +++ b/pkg/types/command_result.go @@ -14,31 +14,37 @@ type Change struct { } type ChangedObject struct { + Ref k8s.ObjectRef `yaml:"ref"` NewObject *uo.UnstructuredObject `yaml:"newObject,omitempty"` OldObject *uo.UnstructuredObject `yaml:"oldObject,omitempty"` Changes []Change `yaml:"changes,omitempty"` } +type RefAndObject struct { + Ref k8s.ObjectRef `yaml:"ref"` + Object *uo.UnstructuredObject `yaml:"object,omitempty"` +} + type DeploymentError struct { Ref k8s.ObjectRef `yaml:"ref"` Error string `yaml:"error"` } type CommandResult struct { - NewObjects []*uo.UnstructuredObject `yaml:"newObjects,omitempty"` - ChangedObjects []*ChangedObject `yaml:"changedObjects,omitempty"` - HookObjects []*uo.UnstructuredObject `yaml:"hookObjects,omitempty"` - OrphanObjects []k8s.ObjectRef `yaml:"orphanObjects,omitempty"` - DeletedObjects []k8s.ObjectRef `yaml:"deletedObjects,omitempty"` - Errors []DeploymentError `yaml:"errors,omitempty"` - Warnings []DeploymentError `yaml:"warnings,omitempty"` - SeenImages []FixedImage `yaml:"seenImages,omitempty"` + NewObjects []*RefAndObject `yaml:"newObjects,omitempty"` + ChangedObjects []*ChangedObject `yaml:"changedObjects,omitempty"` + HookObjects []*RefAndObject `yaml:"hookObjects,omitempty"` + OrphanObjects []k8s.ObjectRef `yaml:"orphanObjects,omitempty"` + DeletedObjects []k8s.ObjectRef `yaml:"deletedObjects,omitempty"` + Errors []DeploymentError `yaml:"errors,omitempty"` + Warnings []DeploymentError `yaml:"warnings,omitempty"` + SeenImages []FixedImage `yaml:"seenImages,omitempty"` } type ValidateResultEntry struct { Ref k8s.ObjectRef `yaml:"ref"` Annotation string `yaml:"annotation"` - Message string `yaml:"message"` + Message string `yaml:"message"` } type ValidateResult struct { diff --git a/pkg/types/target_config.go b/pkg/types/target_config.go index 794b7d5a7..e88b8919e 100644 --- a/pkg/types/target_config.go +++ b/pkg/types/target_config.go @@ -14,9 +14,9 @@ type FixedImage struct { Object *k8s.ObjectRef `yaml:"object,omitempty"` Deployment *string `yaml:"deployment,omitempty"` Container *string `yaml:"container,omitempty"` - VersionFilter *string `yaml:"versionFilter,omitempty"` - DeployTags []string `yaml:"deployTags,omitempty"` - DeploymentDir *string `yaml:"deploymentDir,omitempty"` + VersionFilter *string `yaml:"versionFilter,omitempty"` + DeployTags []string `yaml:"deployTags,omitempty"` + DeploymentDir *string `yaml:"deploymentDir,omitempty"` } type FixedImagesConfig struct { From 9bc669129c741789b10e8d227eda9a120206818f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 8 Mar 2022 15:08:47 +0100 Subject: [PATCH 0467/2916] fix: Add missing seenImages and refs to changed objects --- pkg/deployment/deployment_collection.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index dc7447287..249f92e36 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -348,6 +348,7 @@ func (c *DeploymentCollection) Deploy(k *k8s.K8sCluster, forceApply bool, replac OrphanObjects: orphanObjects, Errors: c.errorsList(), Warnings: c.warningsList(), + SeenImages: c.images.seenImages, }, nil } @@ -388,6 +389,7 @@ func (c *DeploymentCollection) Diff(k *k8s.K8sCluster, forceApply bool, replaceO OrphanObjects: orphanObjects, Errors: c.errorsList(), Warnings: c.warningsList(), + SeenImages: c.images.seenImages, }, nil } @@ -464,6 +466,7 @@ func (c *DeploymentCollection) PokeImages(k *k8s.K8sCluster) (*types.CommandResu ChangedObjects: changedObjects, Errors: c.errorsList(), Warnings: c.warningsList(), + SeenImages: c.images.seenImages, }, nil } @@ -526,6 +529,7 @@ func (c *DeploymentCollection) Downscale(k *k8s.K8sCluster) (*types.CommandResul DeletedObjects: deletedObjects, Errors: c.errorsList(), Warnings: c.warningsList(), + SeenImages: c.images.seenImages, }, nil } @@ -642,6 +646,7 @@ func (c *DeploymentCollection) doDiff(k *k8s.K8sCluster, appliedObjects map[k8s2 mutex.Lock() defer mutex.Unlock() changedObjects = append(changedObjects, &types.ChangedObject{ + Ref: ref, NewObject: ao, OldObject: ro, Changes: changes, From 7cecfd97dfcfd8ca8fec5c0fa32ff3b201dd56cc Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 8 Mar 2022 15:46:24 +0100 Subject: [PATCH 0468/2916] tests: Fix compilation of e2e tests --- e2e/utils_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/utils_test.go b/e2e/utils_test.go index 81f14b2d1..fe828d1ed 100644 --- a/e2e/utils_test.go +++ b/e2e/utils_test.go @@ -30,7 +30,7 @@ func waitForReadiness(t *testing.T, k *KindCluster, namespace string, resource s t.Fatal(err) } - v := validation.ValidateObject(y.ToUnstructured(), true) + v := validation.ValidateObject(y, true) if v.Ready { return true } From e5737fe3a0d036ab752d2094a0d2a686a258ab04 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 8 Mar 2022 15:46:51 +0100 Subject: [PATCH 0469/2916] fix: Treat lists as yaml in objectToDiffableStringNoType --- pkg/diff/diff.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkg/diff/diff.go b/pkg/diff/diff.go index f69823483..b511977be 100644 --- a/pkg/diff/diff.go +++ b/pkg/diff/diff.go @@ -153,7 +153,14 @@ func objectToDiffableStringNoType(o interface{}) (string, error) { return v, nil } + isYaml := false if _, ok := o.(map[string]interface{}); ok { + isYaml = true + } else if _, ok := o.([]interface{}); ok { + isYaml = true + } + + if isYaml { b, err := yaml.WriteYamlString(o) if err != nil { return "", err From 9772aeae53b494e504d36d569ab6dc4115b9de5b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 8 Mar 2022 16:15:20 +0100 Subject: [PATCH 0470/2916] fix: Fix ambiguous path detection --- pkg/diff/managed_fields.go | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/pkg/diff/managed_fields.go b/pkg/diff/managed_fields.go index bffaf0c67..73e653d49 100644 --- a/pkg/diff/managed_fields.go +++ b/pkg/diff/managed_fields.go @@ -103,9 +103,14 @@ func convertToKeyList(remote *uo.UnstructuredObject, path fieldpath.Path) ([]int func ResolveFieldManagerConflicts(local *uo.UnstructuredObject, remote *uo.UnstructuredObject, conflictStatus metav1.Status) (*uo.UnstructuredObject, []LostOwnership, error) { managedFields := remote.GetK8sManagedFields() - // "stupid" because the string representation in "details.causes.field" might be ambiguous as k8s does not escape dots - fieldsAsStupidStrings := make(map[string][]fieldpath.Path) - managersByFields := make(map[string][]string) + type managersByField struct { + // "stupid" because the string representation of field pathes might be ambiguous as k8s does not escape dots + stupidPath string + pathes []fieldpath.Path + managers []string + } + + managersByFields := make(map[string]*managersByField) for _, mf := range managedFields { fieldSet := fieldpath.NewSet() @@ -116,8 +121,21 @@ func ResolveFieldManagerConflicts(local *uo.UnstructuredObject, remote *uo.Unstr fieldSet.Iterate(func(path fieldpath.Path) { s := path.String() - fieldsAsStupidStrings[s] = append(fieldsAsStupidStrings[s], path) - managersByFields[s] = append(managersByFields[s], mf.Manager) + if _, ok := managersByFields[s]; !ok { + managersByFields[s] = &managersByField{stupidPath: s} + } + m, _ := managersByFields[s] + found := false + for _, p := range m.pathes { + if p.Equals(path) { + found = true + break + } + } + if !found { + m.pathes = append(m.pathes, path) + } + m.managers = append(m.managers, mf.Manager) }) } @@ -152,15 +170,15 @@ func ResolveFieldManagerConflicts(local *uo.UnstructuredObject, remote *uo.Unstr return nil, nil, fmt.Errorf("unknown type %s", cause.Type) } - mfPath, ok := fieldsAsStupidStrings[cause.Field] + mf, ok := managersByFields[cause.Field] if !ok { return nil, nil, fmt.Errorf("could not find matching field for path '%s'", cause.Field) } - if len(mfPath) != 1 { + if len(mf.pathes) != 1 { return nil, nil, fmt.Errorf("field path '%s' is ambiguous", cause.Field) } - p, found, err := convertToKeyList(remote, mfPath[0]) + p, found, err := convertToKeyList(remote, mf.pathes[0]) if err != nil { return nil, nil, err } @@ -181,8 +199,7 @@ func ResolveFieldManagerConflicts(local *uo.UnstructuredObject, remote *uo.Unstr overwrite := true if !forceApplyAll { - mfbf, _ := managersByFields[mfPath[0].String()] - for _, mfn := range mfbf { + for _, mfn := range mf.managers { found := false for _, oa := range overwriteAllowedManagers { if oa.MatchString(mfn) { From 2cc79ba07456801d732475c9169f747be6051ab2 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 8 Mar 2022 17:00:32 +0100 Subject: [PATCH 0471/2916] fix: Fix concurrent map access --- pkg/deployment/apply_utils.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/deployment/apply_utils.go b/pkg/deployment/apply_utils.go index d5daede53..1277416a8 100644 --- a/pkg/deployment/apply_utils.go +++ b/pkg/deployment/apply_utils.go @@ -99,6 +99,9 @@ func (a *applyUtil) deleteObject(ref k8s2.ObjectRef, hook bool) bool { } return false } + + a.mutex.Lock() + defer a.mutex.Unlock() if hook { a.deletedHookObjects[ref] = true } else { From 294de1995d4e9c62d21b8b3418f2a7b32b3a5e6c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 8 Mar 2022 17:01:04 +0100 Subject: [PATCH 0472/2916] fix: Force write of sealed secrets bootstrap keys --- pkg/k8s/k8s_cluster.go | 6 ++++++ pkg/seal/bootstrap.go | 4 ++-- pkg/seal/fetch_cert.go | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index 68e30ca2d..3d1196717 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -147,6 +147,12 @@ func NewK8sCluster(context string, dryRun bool) (*K8sCluster, error) { return k, nil } +func (k *K8sCluster) ReadWrite() *K8sCluster { + k2 := *k + k2.DryRun = false + return &k2 +} + func (k *K8sCluster) Context() string { return k.context } diff --git a/pkg/seal/bootstrap.go b/pkg/seal/bootstrap.go index 3e9b7d2cc..4deeb3618 100644 --- a/pkg/seal/bootstrap.go +++ b/pkg/seal/bootstrap.go @@ -82,11 +82,11 @@ func writeKey(k *k8s.K8sCluster, key *rsa.PrivateKey, certs []*x509.Certificate, v1.TLSCertKey: string(certbytes), } - _, _, err := k.PatchObject(secret, k8s.PatchOptions{}) + _, _, err := k.ReadWrite().PatchObject(secret, k8s.PatchOptions{}) if err != nil { return err } - _, _, err = k.PatchObject(configMap, k8s.PatchOptions{}) + _, _, err = k.ReadWrite().PatchObject(configMap, k8s.PatchOptions{}) if err != nil { return err } diff --git a/pkg/seal/fetch_cert.go b/pkg/seal/fetch_cert.go index 9cae7926a..4547712b5 100644 --- a/pkg/seal/fetch_cert.go +++ b/pkg/seal/fetch_cert.go @@ -33,7 +33,7 @@ func fetchCert(k *k8s.K8sCluster, namespace string, controllerName string) (*rsa log.Warningf("Failed to retrieve public certificate from sealed-secrets-controller, re-trying with bootstrap secret") s, err = openCertFromBootstrap(client, namespace) if err != nil { - return fmt.Errorf("failed to retrieve selaed secrets public key: %w", err) + return fmt.Errorf("failed to retrieve sealed secrets public key: %w", err) } } } From 9b3e62ba8ae16cfa00270ee395afafd46035f1c3 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 8 Mar 2022 17:01:18 +0100 Subject: [PATCH 0473/2916] fix: Fix crash in SealFile --- pkg/seal/sealer.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pkg/seal/sealer.go b/pkg/seal/sealer.go index 546a03122..ee0880a36 100644 --- a/pkg/seal/sealer.go +++ b/pkg/seal/sealer.go @@ -198,12 +198,14 @@ func (s *Sealer) SealFile(p string, targetFile string) error { if err != nil { return err } - for k, v := range stringData.Object { - s, ok := v.(string) - if !ok { - return fmt.Errorf("%s is not a string", k) + if ok { + for k, v := range stringData.Object { + s, ok := v.(string) + if !ok { + return fmt.Errorf("%s is not a string", k) + } + secrets[k] = []byte(s) } - secrets[k] = []byte(s) } resultSecretHashes := make(map[string]string) From 6bf5a9610526f53b02bff2b6d7439fb99861b177 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 8 Mar 2022 17:01:32 +0100 Subject: [PATCH 0474/2916] fix: Support AWS SSO configs --- pkg/utils/aws/secrets_manager.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/utils/aws/secrets_manager.go b/pkg/utils/aws/secrets_manager.go index 8e0f0358e..ff8edac5d 100644 --- a/pkg/utils/aws/secrets_manager.go +++ b/pkg/utils/aws/secrets_manager.go @@ -14,6 +14,7 @@ func GetAwsSecretsManagerSecret(profile *string, region *string, secretName stri // Environment variable always takes precedence if _, ok := os.LookupEnv("AWS_PROFILE"); !ok && region != nil { opts.Profile = *profile + opts.SharedConfigState = session.SharedConfigEnable } s, err := session.NewSessionWithOptions(opts) if err != nil { From 43864bd5b831b5348fd3c3347929377c72ad51a4 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 8 Mar 2022 17:37:29 +0100 Subject: [PATCH 0475/2916] fix: Add a few missing mutex locks --- pkg/deployment/apply_utils.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/deployment/apply_utils.go b/pkg/deployment/apply_utils.go index 1277416a8..1e151ed39 100644 --- a/pkg/deployment/apply_utils.go +++ b/pkg/deployment/apply_utils.go @@ -253,6 +253,8 @@ func (a *applyUtil) handleNewCRDs(x *uo.UnstructuredObject, err error) (bool, er if err != nil && meta.IsNoMatchError(err) { // maybe this was a resource for which the CRD was only deployed recently, so we should do rediscovery and then // retry the patch + a.mutex.Lock() + defer a.mutex.Unlock() if a.deployedNewCRD { a.deployedNewCRD = false err = a.k.RediscoverResources() @@ -265,6 +267,8 @@ func (a *applyUtil) handleNewCRDs(x *uo.UnstructuredObject, err error) (bool, er ref := x.GetK8sRef() if ref.GVK.Group == "apiextensions.k8s.io" && ref.GVK.Kind == "CustomResourceDefinition" { // this is a freshly deployed CRD, so we must perform rediscovery in case an api resource can't be found + a.mutex.Lock() + defer a.mutex.Unlock() a.deployedNewCRD = true return true, nil } From d69d80645136b76f9ce43da5204c936a98b9cba8 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 8 Mar 2022 17:37:53 +0100 Subject: [PATCH 0476/2916] fix: Better progress reporting when things take longer then usual --- pkg/deployment/apply_utils.go | 14 +++++++++++++- pkg/diff/managed_fields.go | 4 ++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/pkg/deployment/apply_utils.go b/pkg/deployment/apply_utils.go index 1e151ed39..6c9178e68 100644 --- a/pkg/deployment/apply_utils.go +++ b/pkg/deployment/apply_utils.go @@ -285,6 +285,7 @@ func (a *applyUtil) waitHook(ref k8s2.ObjectRef) bool { log2 := log.WithField("ref", ref) log2.Debugf("Waiting for hook to get ready") + lastLogTime := time.Now() didLog := false startTime := time.Now() for true { @@ -328,6 +329,10 @@ func (a *applyUtil) waitHook(ref k8s2.ObjectRef) bool { if !didLog { log2.Infof("Waiting for hook to get ready...") didLog = true + lastLogTime = time.Now() + } else if didLog && time.Now().Sub(lastLogTime) >= 10*time.Second { + log2.Infof("Still waiting for hook to get ready (%s)...", time.Now().Sub(lastLogTime).String()) + lastLogTime = time.Now() } time.Sleep(500 * time.Millisecond) @@ -385,8 +390,15 @@ func (a *applyUtil) applyDeploymentItem(d *deploymentItem) { if len(applyObjects) != 0 { a.doLog(d, log.InfoLevel, "Applying %d objects", len(applyObjects)) } - for _, o := range applyObjects { + startTime := time.Now() + didLog := false + for i, o := range applyObjects { a.applyObject(o, false, false) + if time.Now().Sub(startTime) >= 10*time.Second || (didLog && i == len(applyObjects)-1) { + a.doLog(d, log.InfoLevel, "...applied %d of %d objects", i+1, len(applyObjects)) + startTime = time.Now() + didLog = true + } } if initialDeploy { diff --git a/pkg/diff/managed_fields.go b/pkg/diff/managed_fields.go index 73e653d49..321679535 100644 --- a/pkg/diff/managed_fields.go +++ b/pkg/diff/managed_fields.go @@ -106,8 +106,8 @@ func ResolveFieldManagerConflicts(local *uo.UnstructuredObject, remote *uo.Unstr type managersByField struct { // "stupid" because the string representation of field pathes might be ambiguous as k8s does not escape dots stupidPath string - pathes []fieldpath.Path - managers []string + pathes []fieldpath.Path + managers []string } managersByFields := make(map[string]*managersByField) From 3427357c91158a925adff92fd52417370633aab1 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 9 Mar 2022 11:29:10 +0100 Subject: [PATCH 0477/2916] feat: Support passing git credentials via env vars --- pkg/git/auth/auth_provider.go | 1 + pkg/git/auth/env_auth_provider.go | 49 ++++++++++++++++++++++++++++ pkg/git/auth/git_credentials_file.go | 4 +++ pkg/git/git-url/url.go | 4 +++ 4 files changed, 58 insertions(+) create mode 100644 pkg/git/auth/env_auth_provider.go diff --git a/pkg/git/auth/auth_provider.go b/pkg/git/auth/auth_provider.go index f49133e51..777fcf20b 100644 --- a/pkg/git/auth/auth_provider.go +++ b/pkg/git/auth/auth_provider.go @@ -26,5 +26,6 @@ func BuildAuth(gitUrl git_url.GitUrl) transport.AuthMethod { } func init() { + RegisterAuthProvider(&GitEnvAuthProvider{}) RegisterAuthProvider(&GitCredentialsFileAuthProvider{}) } diff --git a/pkg/git/auth/env_auth_provider.go b/pkg/git/auth/env_auth_provider.go new file mode 100644 index 000000000..7c2174fdd --- /dev/null +++ b/pkg/git/auth/env_auth_provider.go @@ -0,0 +1,49 @@ +package auth + +import ( + git_url "github.com/codablock/kluctl/pkg/git/git-url" + "github.com/codablock/kluctl/pkg/utils" + "github.com/go-git/go-git/v5/plumbing/transport" + "github.com/go-git/go-git/v5/plumbing/transport/http" + "github.com/go-git/go-git/v5/plumbing/transport/ssh" + log "github.com/sirupsen/logrus" + "strings" +) + +type GitEnvAuthProvider struct { +} + +func (a *GitEnvAuthProvider) BuildAuth(gitUrl git_url.GitUrl) transport.AuthMethod { + for _, m := range utils.ParseEnvConfigSets("KLUCTL_GIT") { + host, _ := m["HOST"] + pathPrefix, _ := m["PATH_PREFIX"] + username, _ := m["USERNAME"] + password, _ := m["PASSWORD"] + ssh_key, _ := m["SSH_KEY"] + + if host != gitUrl.Hostname() { + continue + } + if !strings.HasPrefix(gitUrl.Path, pathPrefix) { + continue + } + if username == "" { + continue + } + + if !gitUrl.IsSsh() { + return &http.BasicAuth{ + Username: username, + Password: password, + } + } else if ssh_key != "" { + a, err := ssh.NewPublicKeysFromFile(username, ssh_key, "") + if err != nil { + log.Debugf("Failed to parse private key %s: %v", ssh_key, err) + } else { + return a + } + } + } + return nil +} \ No newline at end of file diff --git a/pkg/git/auth/git_credentials_file.go b/pkg/git/auth/git_credentials_file.go index 82fb9851c..b407bccd4 100644 --- a/pkg/git/auth/git_credentials_file.go +++ b/pkg/git/auth/git_credentials_file.go @@ -16,6 +16,10 @@ type GitCredentialsFileAuthProvider struct { } func (a *GitCredentialsFileAuthProvider) BuildAuth(gitUrl git_url.GitUrl) transport.AuthMethod { + if gitUrl.Scheme != "http" && gitUrl.Scheme != "https" { + return nil + } + home, err := os.UserHomeDir() if err != nil { log.Warningf("Could not determine home directory: %v", err) diff --git a/pkg/git/git-url/url.go b/pkg/git/git-url/url.go index 55fdd956b..e185553de 100644 --- a/pkg/git/git-url/url.go +++ b/pkg/git/git-url/url.go @@ -37,6 +37,10 @@ func (u GitUrl) MarshalYAML() (interface{}, error) { return u.String(), nil } +func (u *GitUrl) IsSsh() bool { + return u.Scheme == "ssh" || u.Scheme == "git" || u.Scheme == "git+ssh" +} + func (u *GitUrl) NormalizePort() string { port := u.Port() if port == "" { From 1717861fda0047f6c18df5fb99162011b0679144 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 9 Mar 2022 11:29:31 +0100 Subject: [PATCH 0478/2916] feat: Support default identities for git+ssh --- pkg/git/auth/auth_provider.go | 1 + pkg/git/auth/ssh_auth_provider.go | 93 +++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 pkg/git/auth/ssh_auth_provider.go diff --git a/pkg/git/auth/auth_provider.go b/pkg/git/auth/auth_provider.go index 777fcf20b..f01b56a3e 100644 --- a/pkg/git/auth/auth_provider.go +++ b/pkg/git/auth/auth_provider.go @@ -28,4 +28,5 @@ func BuildAuth(gitUrl git_url.GitUrl) transport.AuthMethod { func init() { RegisterAuthProvider(&GitEnvAuthProvider{}) RegisterAuthProvider(&GitCredentialsFileAuthProvider{}) + RegisterAuthProvider(&GitSshAuthProvider{}) } diff --git a/pkg/git/auth/ssh_auth_provider.go b/pkg/git/auth/ssh_auth_provider.go new file mode 100644 index 000000000..c77bd010c --- /dev/null +++ b/pkg/git/auth/ssh_auth_provider.go @@ -0,0 +1,93 @@ +package auth + +import ( + "fmt" + git_url "github.com/codablock/kluctl/pkg/git/git-url" + "github.com/go-git/go-git/v5/plumbing/transport" + git_ssh "github.com/go-git/go-git/v5/plumbing/transport/ssh" + log "github.com/sirupsen/logrus" + sshagent "github.com/xanzy/ssh-agent" + "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh/agent" + "io/ioutil" + "os/user" + "path/filepath" +) + +type GitSshAuthProvider struct { +} + +type sshDefaultIdentityAndAgent struct { + user string + defaultIdentity ssh.Signer + agent agent.Agent + git_ssh.HostKeyCallbackHelper +} + +func (a *sshDefaultIdentityAndAgent) String() string { + return fmt.Sprintf("user: %s, name: %s", a.user, a.Name()) +} + +func (a *sshDefaultIdentityAndAgent) Name() string { + return "ssh-default-identity-and-agent" +} + +func (a *sshDefaultIdentityAndAgent) ClientConfig() (*ssh.ClientConfig, error) { + return a.SetHostKeyCallback(&ssh.ClientConfig{ + User: a.user, + Auth: []ssh.AuthMethod{ssh.PublicKeysCallback(a.Signers)}, + }) +} + +func (a *sshDefaultIdentityAndAgent) Signers() ([]ssh.Signer, error) { + var ret []ssh.Signer + if a.defaultIdentity != nil { + ret = append(ret, a.defaultIdentity) + } + if a.agent != nil { + s, err := a.agent.Signers() + if err != nil { + return nil, err + } + ret = append(ret, s...) + } + return ret, nil +} + +func (a *GitSshAuthProvider) BuildAuth(gitUrl git_url.GitUrl) transport.AuthMethod { + if !gitUrl.IsSsh() { + return nil + } + if gitUrl.User == nil { + return nil + } + + auth := &sshDefaultIdentityAndAgent{ + user: gitUrl.User.Username(), + } + + u, err := user.Current() + if err != nil { + log.Debugf("No current user: %v", err) + } else { + pemBytes, err := ioutil.ReadFile(filepath.Join(u.HomeDir, ".ssh", "id_rsa")) + if err != nil { + log.Debugf("Failed to read default identity file for url %s: %v", gitUrl.String(), err) + } else { + signer, err := ssh.ParsePrivateKey(pemBytes) + if err != nil { + log.Debugf("Failed to parse default identity for url %s: %v", gitUrl.String(), err) + } + auth.defaultIdentity = signer + } + } + + agent, _, err := sshagent.New() + if err != nil { + log.Debugf("Failed to connect to ssh agent for url %s: %v", gitUrl.String(), err) + } else { + auth.agent = agent + } + + return auth +} From 0ee035fa922936d6145d1f85ce8f5a9ce34203a1 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 9 Mar 2022 12:22:05 +0100 Subject: [PATCH 0479/2916] fix: Fix crash when loading multi-clusters projects --- pkg/types/git_project.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/types/git_project.go b/pkg/types/git_project.go index d72a12552..e9d3f8f1f 100644 --- a/pkg/types/git_project.go +++ b/pkg/types/git_project.go @@ -45,7 +45,7 @@ func (gp *ExternalProjects) UnmarshalYAML(unmarshal func(interface{}) error) err return nil } // try as array - return unmarshal(gp.Projects) + return unmarshal(&gp.Projects) } func init() { From dd6f579e550ff90f2c360586f6dc0cc0a0528b6b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 9 Mar 2022 12:39:17 +0100 Subject: [PATCH 0480/2916] refactor: Rename buildRemoteName to buildMirrorRepoName --- pkg/git/mirrored_repo.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/pkg/git/mirrored_repo.go b/pkg/git/mirrored_repo.go index 8435b65da..6ee635213 100644 --- a/pkg/git/mirrored_repo.go +++ b/pkg/git/mirrored_repo.go @@ -20,7 +20,6 @@ var cacheBaseDir = filepath.Join(utils.GetTmpBaseDir(), "git-cache") type MirroredGitRepo struct { url git_url.GitUrl - remoteName string mirrorDir string hasLock bool @@ -31,11 +30,10 @@ type MirroredGitRepo struct { } func NewMirroredGitRepo(u git_url.GitUrl) (*MirroredGitRepo, error) { - remoteName := buildRemoteName(u) + mirrorRepoName := buildMirrorRepoName(u) o := &MirroredGitRepo{ url: u, - remoteName: remoteName, - mirrorDir: filepath.Join(cacheBaseDir, remoteName), + mirrorDir: filepath.Join(cacheBaseDir, mirrorRepoName), } if !utils.IsDirectory(o.mirrorDir) { @@ -277,7 +275,7 @@ func (g *MirroredGitRepo) CloneProject(ref string, targetDir string) error { return nil } -func buildRemoteName(u git_url.GitUrl) string { +func buildMirrorRepoName(u git_url.GitUrl) string { r := filepath.Base(u.Path) if strings.HasSuffix(r, ".git") { r = r[:len(r)-len(".git")] From 6c8b95c6b86b80b58c689e1765536d1d2c2aad24 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 9 Mar 2022 22:00:14 +0100 Subject: [PATCH 0481/2916] fix: Print correct time in waitHook --- pkg/deployment/apply_utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/deployment/apply_utils.go b/pkg/deployment/apply_utils.go index 6c9178e68..90c8a0ba2 100644 --- a/pkg/deployment/apply_utils.go +++ b/pkg/deployment/apply_utils.go @@ -331,7 +331,7 @@ func (a *applyUtil) waitHook(ref k8s2.ObjectRef) bool { didLog = true lastLogTime = time.Now() } else if didLog && time.Now().Sub(lastLogTime) >= 10*time.Second { - log2.Infof("Still waiting for hook to get ready (%s)...", time.Now().Sub(lastLogTime).String()) + log2.Infof("Still waiting for hook to get ready (%s)...", time.Now().Sub(startTime).String()) lastLogTime = time.Now() } From 789a0c40d7d8063a9a2a930e760ff2ee5d67104e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 9 Mar 2022 22:00:49 +0100 Subject: [PATCH 0482/2916] feat: Recursivly exclude chart templates --- pkg/deployment/deployment_item.go | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 3df049f03..d73b63d9c 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -10,7 +10,6 @@ import ( "io/fs" "io/ioutil" "os" - "path" "path/filepath" "sigs.k8s.io/kustomize/api/filesys" "sigs.k8s.io/kustomize/api/krusty" @@ -120,10 +119,26 @@ func (di *deploymentItem) render(k *k8s.K8sCluster, wp *utils.WorkerPoolWithErro var excludePatterns []string excludePatterns = append(excludePatterns, di.project.config.TemplateExcludes...) - if utils.IsFile(filepath.Join(*di.dir, "helm-chart.yml")) { - // never try to render helm charts - excludePatterns = append(excludePatterns, path.Join(strings.ReplaceAll(di.relToProjectItemDir, string(os.PathSeparator), "/"), "charts/**")) + err = filepath.WalkDir(*di.dir, func(p string, d fs.DirEntry, err error) error { + if d.IsDir() { + relDir, err := filepath.Rel(*di.dir, p) + if err != nil { + return err + } + if utils.IsFile(filepath.Join(p, "helm-chart.yml")) { + // never try to render helm charts + ep := filepath.Join(di.relToProjectItemDir, relDir, "charts/**") + ep = strings.ReplaceAll(ep, string(os.PathSeparator), "/") + excludePatterns = append(excludePatterns, ep) + return filepath.SkipDir + } + } + return nil + }) + if err != nil { + return err } + if !di.collection.forSeal { // .sealme files are rendered while sealing and not while deploying excludePatterns = append(excludePatterns, "**.sealme") From 293fb1121abe22fef563238fe952d29ac84d0354 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 9 Mar 2022 22:02:54 +0100 Subject: [PATCH 0483/2916] fix: Fix GitEnvAuthProvider to not return empty passwords in case of ssh creds --- pkg/git/auth/env_auth_provider.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/git/auth/env_auth_provider.go b/pkg/git/auth/env_auth_provider.go index 7c2174fdd..d5a46785d 100644 --- a/pkg/git/auth/env_auth_provider.go +++ b/pkg/git/auth/env_auth_provider.go @@ -31,12 +31,12 @@ func (a *GitEnvAuthProvider) BuildAuth(gitUrl git_url.GitUrl) transport.AuthMeth continue } - if !gitUrl.IsSsh() { + if !gitUrl.IsSsh() && password != "" { return &http.BasicAuth{ Username: username, Password: password, } - } else if ssh_key != "" { + } else if gitUrl.IsSsh() && ssh_key != "" { a, err := ssh.NewPublicKeysFromFile(username, ssh_key, "") if err != nil { log.Debugf("Failed to parse private key %s: %v", ssh_key, err) From 87ffe5a91dfddf1a10b5122cf42884e8c14701b7 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 9 Mar 2022 22:03:48 +0100 Subject: [PATCH 0484/2916] fix: Don't always reseal --- pkg/seal/sealer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/seal/sealer.go b/pkg/seal/sealer.go index ee0880a36..14597a69f 100644 --- a/pkg/seal/sealer.go +++ b/pkg/seal/sealer.go @@ -227,7 +227,7 @@ func (s *Sealer) SealFile(p string, targetFile string) error { result.SetNestedField(metadata.Object, "spec", "template", "metadata") } - resealAll := true + resealAll := false if s.forceReseal { resealAll = true log.Infof("Forcing reseal of secrets in %s", secretName) From b0b6521aac2f31e9ad7ad2e5204618aa571ad942 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 9 Mar 2022 22:04:20 +0100 Subject: [PATCH 0485/2916] fix: Set default values for not set filter args --- pkg/utils/versions/latest_version_parse.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/pkg/utils/versions/latest_version_parse.go b/pkg/utils/versions/latest_version_parse.go index cf02f81e4..ab2e53715 100644 --- a/pkg/utils/versions/latest_version_parse.go +++ b/pkg/utils/versions/latest_version_parse.go @@ -127,7 +127,7 @@ func parseRegexFilter(p *preparsed) (LatestVersionFilter, error) { func parseSemVerFilter(p *preparsed) (LatestVersionFilter, error) { args := []*arg{ - {name: "allow_no_nums", tok: scanner.Ident, isBool: true}, + {name: "allow_no_nums", tok: scanner.Ident, value: false, isBool: true}, } err := parseArgs(p, args) if err != nil { @@ -140,7 +140,7 @@ func parseSemVerFilter(p *preparsed) (LatestVersionFilter, error) { func parsePrefixFilter(p *preparsed) (LatestVersionFilter, error) { args := []*arg{ {name: "prefix", tok: scanner.String, required: true}, - {name: "suffix", tok: scanner.Ident}, + {name: "suffix", tok: scanner.Ident, value: ""}, } err := parseArgs(p, args) if err != nil { @@ -239,11 +239,15 @@ func parseArgs(p *preparsed, args []*arg) error { if a.required && !a.found { return fmt.Errorf("required arg %s not found", a.name) } - if a.isBool { - if a.tok != scanner.Ident || (a.value != "true" && a.value != "false") { + if a.found && a.isBool { + if a.tok != scanner.Ident { return fmt.Errorf("invalid value for arg %s, must be a bool", a.name) } - a.value, _ = strconv.ParseBool(a.value.(string)) + b, err := strconv.ParseBool(a.value.(string)) + if err != nil { + return fmt.Errorf("invalid value for arg %s, must be a bool", a.name) + } + a.value = b } } From 683e8f70a91865a124c2a50146baebc88ebc7df9 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 10 Mar 2022 13:55:52 +0100 Subject: [PATCH 0486/2916] docs: Add example about recursive templateExcludes --- docs/deployments.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/deployments.md b/docs/deployments.md index ab4a08a8b..f8b10d440 100644 --- a/docs/deployments.md +++ b/docs/deployments.md @@ -316,6 +316,12 @@ A list of file patterns to exclude from Jinja2 rendering/templating. This is imp resources containing sequences of characters that are misinterpreted by Jinja2. An example would be a configuration file that includes Go templates, which will in most cases make Jinja2 templating fail. +Recursive patterns can be specified with double-wildcards, e.g.: +```yaml +templateExcludes: + - path/to/excludes/** +``` + ### ignoreForDiff A list of objects and fields to ignore while performing diffs. Consider the following example: From 672dcbacc4ae01be8098846b9239e970f4cd2539 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 10 Mar 2022 21:42:02 +0100 Subject: [PATCH 0487/2916] fix: Take username into account when choosing git credentials --- pkg/git/auth/env_auth_provider.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/git/auth/env_auth_provider.go b/pkg/git/auth/env_auth_provider.go index d5a46785d..00f71a656 100644 --- a/pkg/git/auth/env_auth_provider.go +++ b/pkg/git/auth/env_auth_provider.go @@ -30,6 +30,9 @@ func (a *GitEnvAuthProvider) BuildAuth(gitUrl git_url.GitUrl) transport.AuthMeth if username == "" { continue } + if gitUrl.User != nil && gitUrl.User.Username() != "" && gitUrl.User.Username() != username { + continue + } if !gitUrl.IsSsh() && password != "" { return &http.BasicAuth{ From eccd2d5dd70a26ce8aa54ffd4184f03f17cfc46a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 10 Mar 2022 21:42:16 +0100 Subject: [PATCH 0488/2916] fix: Add username to normalized repo keys --- pkg/git/git-url/url.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/git/git-url/url.go b/pkg/git/git-url/url.go index e185553de..452df12ee 100644 --- a/pkg/git/git-url/url.go +++ b/pkg/git/git-url/url.go @@ -97,5 +97,9 @@ func (u *GitUrl) Normalize() *GitUrl { func (u *GitUrl) NormalizedRepoKey() string { u2 := u.Normalize() - return fmt.Sprintf("%s:%s", u2.Host, u2.Path) + username := "" + if u.User != nil && u.User.Username() != "" { + username = u.User.Username() + "@" + } + return fmt.Sprintf("%s%s:%s", username, u2.Host, u2.Path) } From 68ec6568242b51146cb2d7cb1da167fffb72cb19 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 11 Mar 2022 10:37:59 +0100 Subject: [PATCH 0489/2916] fix: Copy path before storing it in managersByFields --- pkg/diff/managed_fields.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/diff/managed_fields.go b/pkg/diff/managed_fields.go index 321679535..c74b06c09 100644 --- a/pkg/diff/managed_fields.go +++ b/pkg/diff/managed_fields.go @@ -120,6 +120,7 @@ func ResolveFieldManagerConflicts(local *uo.UnstructuredObject, remote *uo.Unstr } fieldSet.Iterate(func(path fieldpath.Path) { + path = path.Copy() s := path.String() if _, ok := managersByFields[s]; !ok { managersByFields[s] = &managersByField{stupidPath: s} From 19ed411f0e5beef8a73a2ac00ce1dd26f2f4b05b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 11 Mar 2022 10:39:41 +0100 Subject: [PATCH 0490/2916] feat: Allow to disable ssh host key checking via an environment variable --- docs/commands.md | 3 ++- pkg/git/auth/env_auth_provider.go | 1 + pkg/git/auth/ssh_auth_provider.go | 30 +++++++++++++++++++++++++++--- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/docs/commands.md b/docs/commands.md index 0277e0bb8..5e2e11a57 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -89,10 +89,11 @@ underscores. As an example, `--project=my-project` can also be specified with th A few additional environment variables are supported which do not belong to an option/argument. These are: 1. `KLUCTL_REGISTRY__HOST`, `KLUCTL_REGISTRY__USERNAME`, and so on. See [registries](./images.md#supported-image-registries-and-authentication) for details. -2. `KLUCTL_GIT_TIMEOUT`. Specifies how long to wait on git subprocesses to finish until they are killed. +2. `KLUCTL_SSH_DISABLE_STRICT_HOST_KEY_CHECKING`. Disable ssh host key checking when accessing git repositories. 3. `KLUCTL_NO_THREADS`. Do not use multithreading while performing work. This is only useful for debugging purposes. 4. `KLUCTL_IGNORE_DEBUGGER`. Pretend that there is no debugger attached when automatically deciding if multi-threading should be enabled or not. + # Commands The following commands are available: diff --git a/pkg/git/auth/env_auth_provider.go b/pkg/git/auth/env_auth_provider.go index 00f71a656..261836686 100644 --- a/pkg/git/auth/env_auth_provider.go +++ b/pkg/git/auth/env_auth_provider.go @@ -44,6 +44,7 @@ func (a *GitEnvAuthProvider) BuildAuth(gitUrl git_url.GitUrl) transport.AuthMeth if err != nil { log.Debugf("Failed to parse private key %s: %v", ssh_key, err) } else { + a.HostKeyCallback = getHostKeyCallback() return a } } diff --git a/pkg/git/auth/ssh_auth_provider.go b/pkg/git/auth/ssh_auth_provider.go index c77bd010c..58bf79cfb 100644 --- a/pkg/git/auth/ssh_auth_provider.go +++ b/pkg/git/auth/ssh_auth_provider.go @@ -10,8 +10,11 @@ import ( "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/agent" "io/ioutil" + "net" + "os" "os/user" "path/filepath" + "strconv" ) type GitSshAuthProvider struct { @@ -21,7 +24,6 @@ type sshDefaultIdentityAndAgent struct { user string defaultIdentity ssh.Signer agent agent.Agent - git_ssh.HostKeyCallbackHelper } func (a *sshDefaultIdentityAndAgent) String() string { @@ -33,10 +35,32 @@ func (a *sshDefaultIdentityAndAgent) Name() string { } func (a *sshDefaultIdentityAndAgent) ClientConfig() (*ssh.ClientConfig, error) { - return a.SetHostKeyCallback(&ssh.ClientConfig{ + cc := &ssh.ClientConfig{ User: a.user, Auth: []ssh.AuthMethod{ssh.PublicKeysCallback(a.Signers)}, - }) + } + cc.HostKeyCallback = getHostKeyCallback() + return cc, nil +} + +func getHostKeyCallback() ssh.HostKeyCallback { + hostKeyChecking := true + if x, ok := os.LookupEnv("KLUCTL_SSH_DISABLE_STRICT_HOST_KEY_CHECKING"); ok { + if b, err := strconv.ParseBool(x); err == nil && b { + hostKeyChecking = false + } + } + if hostKeyChecking { + cb, err := git_ssh.NewKnownHostsCallback() + if err != nil { + return nil + } + return cb + } else { + return func(hostname string, remote net.Addr, key ssh.PublicKey) error { + return nil + } + } } func (a *sshDefaultIdentityAndAgent) Signers() ([]ssh.Signer, error) { From e40c0133b9d904be8dc1a370fdaf03bb1721b1dc Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 11 Mar 2022 10:40:00 +0100 Subject: [PATCH 0491/2916] build: Fix pip call --- pkg/jinja2/python_src/pip-wheel.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/jinja2/python_src/pip-wheel.sh b/pkg/jinja2/python_src/pip-wheel.sh index 409950a3d..55d19ae74 100755 --- a/pkg/jinja2/python_src/pip-wheel.sh +++ b/pkg/jinja2/python_src/pip-wheel.sh @@ -6,4 +6,4 @@ mkdir -p $DIR/wheel cd $DIR/wheel rm *.whl -pip wheel -r ../requirements.txt +pip3 wheel -r ../requirements.txt From 3e2cb853c4fac7366e8d5717768a6e1ce285756b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 14 Mar 2022 10:54:34 +0100 Subject: [PATCH 0492/2916] fix: Always pass path relative to rootDir into jinja2 renderer --- pkg/jinja2/jinja2.go | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/pkg/jinja2/jinja2.go b/pkg/jinja2/jinja2.go index b03dff2dc..9a94e58a7 100644 --- a/pkg/jinja2/jinja2.go +++ b/pkg/jinja2/jinja2.go @@ -2,6 +2,7 @@ package jinja2 import "C" import ( + "fmt" "github.com/codablock/kluctl/pkg/utils" "github.com/codablock/kluctl/pkg/utils/uo" "github.com/gobwas/glob" @@ -206,14 +207,25 @@ func (j *Jinja2) RenderDirectory(rootDir string, searchDirs []string, relSourceD sourcePath := filepath.Clean(filepath.Join(subdir, relPath)) targetPath := filepath.Join(targetDir, relPath) - if strings.Index(sourcePath, ".sealme") != -1 { - sourcePath += "" - } - if !j.needsRender(sourcePath, excludePatterns) { return utils.CopyFile(p, targetPath) } + found := false + for _, searchDir := range searchDirs { + if utils.Exists(filepath.Join(searchDir, sourcePath)) { + sourcePath = filepath.Clean(filepath.Join(searchDir, sourcePath)) + sourcePath, err = filepath.Rel(rootDir, sourcePath) + if err != nil { + return err + } + found = true + } + } + if !found { + return fmt.Errorf("did not find %s in searchDirs", relPath) + } + // jinja2 templates are using / even on Windows sourcePath = strings.ReplaceAll(sourcePath, "\\", "/") From a1adce1388b45dcbb6c378da5ed0d64e2124330e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 14 Mar 2022 10:56:21 +0100 Subject: [PATCH 0493/2916] fix: Return simple error message in case of TemplateError --- pkg/jinja2/python_src/jinja2_utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/jinja2/python_src/jinja2_utils.py b/pkg/jinja2/python_src/jinja2_utils.py index 2ac936b99..373afa280 100644 --- a/pkg/jinja2/python_src/jinja2_utils.py +++ b/pkg/jinja2/python_src/jinja2_utils.py @@ -121,6 +121,8 @@ def add_jinja2_filters(jinja2_env): def extract_template_error(e): try: raise e + except TemplateNotFound as e2: + return "template %s not found" % str(e2) except: etype, value, tb = sys.exc_info() extracted_tb = traceback.extract_tb(tb) From b0b9c8b8bb19381336bbee0873584eb5f115657c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 14 Mar 2022 11:44:45 +0100 Subject: [PATCH 0494/2916] refactor: Refactor ListAllObjectsMetadata to also be able to return full objects --- pkg/deployment/all_objects.go | 14 +++++----- pkg/deployment/delete_utils.go | 16 ++++++------ pkg/k8s/k8s_cluster.go | 47 +++++++++++++++++++++++----------- pkg/types/k8s/ref.go | 18 ------------- pkg/utils/uo/k8s_fields.go | 5 ++++ 5 files changed, 52 insertions(+), 48 deletions(-) diff --git a/pkg/deployment/all_objects.go b/pkg/deployment/all_objects.go index 3704f9f92..15abf4e0c 100644 --- a/pkg/deployment/all_objects.go +++ b/pkg/deployment/all_objects.go @@ -3,17 +3,17 @@ package deployment import ( "github.com/codablock/kluctl/pkg/k8s" "github.com/codablock/kluctl/pkg/utils" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/codablock/kluctl/pkg/utils/uo" "strings" ) -func GetIncludedObjectsMetadata(k *k8s.K8sCluster, verbs []string, labels map[string]string, inclusion *utils.Inclusion) ([]*v1.PartialObjectMetadata, error) { - objects, err := k.ListAllObjectsMetadata(verbs, "", labels) +func GetIncludedObjectsMetadata(k *k8s.K8sCluster, verbs []string, labels map[string]string, inclusion *utils.Inclusion) ([]*uo.UnstructuredObject, error) { + objects, err := k.ListAllObjects(verbs, "", labels, true) if err != nil { return nil, err } - var ret []*v1.PartialObjectMetadata + var ret []*uo.UnstructuredObject for _, o := range objects { var iv []utils.InclusionEntry @@ -24,7 +24,7 @@ func GetIncludedObjectsMetadata(k *k8s.K8sCluster, verbs []string, labels map[st }) } - annotations := o.GetAnnotations() + annotations := o.GetK8sAnnotations() if annotations != nil { if itemDir, ok := annotations["kluctl.io/kustomize_dir"]; ok { iv = append(iv, utils.InclusionEntry{ @@ -43,8 +43,8 @@ func GetIncludedObjectsMetadata(k *k8s.K8sCluster, verbs []string, labels map[st return ret, nil } -func getTagsFromObject(o *v1.PartialObjectMetadata) map[string]bool { - labels := o.GetLabels() +func getTagsFromObject(o *uo.UnstructuredObject) map[string]bool { + labels := o.GetK8sLabels() if len(labels) == 0 { return nil } diff --git a/pkg/deployment/delete_utils.go b/pkg/deployment/delete_utils.go index 42351d18e..1cb7175df 100644 --- a/pkg/deployment/delete_utils.go +++ b/pkg/deployment/delete_utils.go @@ -5,8 +5,8 @@ import ( "github.com/codablock/kluctl/pkg/types" k8s2 "github.com/codablock/kluctl/pkg/types/k8s" "github.com/codablock/kluctl/pkg/utils" + "github.com/codablock/kluctl/pkg/utils/uo" log "github.com/sirupsen/logrus" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "strconv" "sync" @@ -46,24 +46,24 @@ func objectRefForExclusion(k *k8s.K8sCluster, ref k8s2.ObjectRef) k8s2.ObjectRef return ref } -func filterObjectsForDelete(k *k8s.K8sCluster, objects []*v1.PartialObjectMetadata, apiFilter []string, inclusion *utils.Inclusion, excludedObjects map[k8s2.ObjectRef]bool) ([]*v1.PartialObjectMetadata, error) { +func filterObjectsForDelete(k *k8s.K8sCluster, objects []*uo.UnstructuredObject, apiFilter []string, inclusion *utils.Inclusion, excludedObjects map[k8s2.ObjectRef]bool) ([]*uo.UnstructuredObject, error) { filteredResources := make(map[schema.GroupKind]bool) for _, gk := range k.GetFilteredGKs(apiFilter) { filteredResources[gk] = true } inclusionHasTags := inclusion.HasType("tags") - var ret []*v1.PartialObjectMetadata + var ret []*uo.UnstructuredObject for _, o := range objects { - ref := k8s2.RefFromPartialObject(o) + ref := o.GetK8sRef() if _, ok := filteredResources[ref.GVK.GroupKind()]; !ok { continue } - annotations := o.GetAnnotations() - ownerRefs := o.GetOwnerReferences() - managedFields := o.GetManagedFields() + annotations := o.GetK8sAnnotations() + ownerRefs := o.GetK8sOwnerReferences() + managedFields := o.GetK8sManagedFields() // exclude when explicitly requested skipDelete, err := strconv.ParseBool(annotations["kluctl.io/skip-delete"]) @@ -133,7 +133,7 @@ func FindObjectsForDelete(k *k8s.K8sCluster, labels map[string]string, inclusion return nil, err } for _, o := range l { - ref := k8s2.RefFromPartialObject(o) + ref := o.GetK8sRef() excludedObjectsMap[objectRefForExclusion(k, ref)] = true ret = append(ret, ref) } diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index 3d1196717..40908d039 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -2,6 +2,7 @@ package k8s import ( "context" + "encoding/json" "fmt" "github.com/codablock/kluctl/pkg/types/k8s" "github.com/codablock/kluctl/pkg/utils" @@ -377,7 +378,7 @@ func (k *K8sCluster) withDynamicClientForGVK(gvk schema.GroupVersionKind, namesp p.warnings = nil - if namespaced { + if namespaced && namespace != "" { err = cb(p.dynamicClient.Resource(*gvr).Namespace(namespace)) return append([]ApiWarning(nil), p.warnings...), err } else { @@ -397,7 +398,7 @@ func (k *K8sCluster) withMetadataClientForGVK(gvk schema.GroupVersionKind, names p.warnings = nil - if namespaced { + if namespaced && namespace != "" { err = cb(p.metadataClient.Resource(*gvr).Namespace(namespace)) return append([]ApiWarning(nil), p.warnings...), err } else { @@ -418,11 +419,13 @@ func (k *K8sCluster) buildLabelSelector(labels map[string]string) string { return ret } -func (k *K8sCluster) ListObjects(gvk schema.GroupVersionKind, namespace string) ([]*uo.UnstructuredObject, []ApiWarning, error) { +func (k *K8sCluster) ListObjects(gvk schema.GroupVersionKind, namespace string, labels map[string]string) ([]*uo.UnstructuredObject, []ApiWarning, error) { var result []*uo.UnstructuredObject apiWarnings, err := k.withDynamicClientForGVK(gvk, namespace, func(r dynamic.ResourceInterface) error { - o := v1.ListOptions{} + o := v1.ListOptions{ + LabelSelector: k.buildLabelSelector(labels), + } x, err := r.List(context.Background(), o) if err != nil { return err @@ -435,8 +438,8 @@ func (k *K8sCluster) ListObjects(gvk schema.GroupVersionKind, namespace string) return result, apiWarnings, err } -func (k *K8sCluster) ListObjectsMetadata(gvk schema.GroupVersionKind, namespace string, labels map[string]string) (*v1.PartialObjectMetadataList, []ApiWarning, error) { - var result *v1.PartialObjectMetadataList +func (k *K8sCluster) ListObjectsMetadata(gvk schema.GroupVersionKind, namespace string, labels map[string]string) ([]*uo.UnstructuredObject, []ApiWarning, error) { + var result []*uo.UnstructuredObject apiWarnings, err := k.withMetadataClientForGVK(gvk, namespace, func(r metadata.ResourceInterface) error { o := v1.ListOptions{ @@ -446,17 +449,28 @@ func (k *K8sCluster) ListObjectsMetadata(gvk schema.GroupVersionKind, namespace if err != nil { return err } - result = x + for _, o := range x.Items { + b, err := json.Marshal(o) + if err != nil { + log.Panic(err) + } + u, err := uo.FromString(string(b)) + if err != nil { + log.Panic(err) + } + u.SetK8sGVK(gvk) + result = append(result, u) + } return nil }) return result, apiWarnings, err } -func (k *K8sCluster) ListAllObjectsMetadata(verbs []string, namespace string, labels map[string]string) ([]*v1.PartialObjectMetadata, error) { +func (k *K8sCluster) ListAllObjects(verbs []string, namespace string, labels map[string]string, onlyMetadata bool) ([]*uo.UnstructuredObject, error) { wp := utils.NewWorkerPoolWithErrors(8) defer wp.StopWait(false) - var ret []*v1.PartialObjectMetadata + var ret []*uo.UnstructuredObject var retApiWarnings []ApiWarning var mutex sync.Mutex @@ -478,17 +492,20 @@ func (k *K8sCluster) ListAllObjectsMetadata(verbs []string, namespace string, la Kind: ar.Kind, } wp.Submit(func() error { - lm, apiWarnings, err := k.ListObjectsMetadata(gvk, namespace, labels) + var l []*uo.UnstructuredObject + var apiWarnings []ApiWarning + var err error + if onlyMetadata { + l, apiWarnings, err = k.ListObjectsMetadata(gvk, namespace, labels) + } else { + l, apiWarnings, err = k.ListObjects(gvk, namespace, labels) + } if err != nil { return err } mutex.Lock() defer mutex.Unlock() - for _, o := range lm.Items { - o := o - o.SetGroupVersionKind(gvk) - ret = append(ret, &o) - } + ret = append(ret, l...) retApiWarnings = append(retApiWarnings, apiWarnings...) return nil }) diff --git a/pkg/types/k8s/ref.go b/pkg/types/k8s/ref.go index 2bed9b695..426ea6d79 100644 --- a/pkg/types/k8s/ref.go +++ b/pkg/types/k8s/ref.go @@ -2,8 +2,6 @@ package k8s import ( "fmt" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" ) @@ -32,19 +30,3 @@ func NewObjectRef(group string, version string, kind string, name string, namesp Namespace: namespace, } } - -func RefFromObject(o *unstructured.Unstructured) ObjectRef { - return ObjectRef{ - GVK: o.GroupVersionKind(), - Name: o.GetName(), - Namespace: o.GetNamespace(), - } -} - -func RefFromPartialObject(o *v1.PartialObjectMetadata) ObjectRef { - return ObjectRef{ - GVK: o.GroupVersionKind(), - Name: o.GetName(), - Namespace: o.GetNamespace(), - } -} diff --git a/pkg/utils/uo/k8s_fields.go b/pkg/utils/uo/k8s_fields.go index 341b6bf10..1cbec1435 100644 --- a/pkg/utils/uo/k8s_fields.go +++ b/pkg/utils/uo/k8s_fields.go @@ -176,6 +176,11 @@ func (uo *UnstructuredObject) SetK8sResourceVersion(rv string) { } } +func (uo *UnstructuredObject) GetK8sOwnerReferences() []*UnstructuredObject { + ret, _, _ := uo.GetNestedObjectList("metadata", "ownerReferences") + return ret +} + func (uo *UnstructuredObject) GetK8sManagedFields() []metav1.ManagedFieldsEntry { return uo.ToUnstructured().GetManagedFields() } From da6ffed4d0e0197b259da35d980d0eab4828dbf0 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 14 Mar 2022 11:54:59 +0100 Subject: [PATCH 0495/2916] refactor: Introduce OnceByKey --- pkg/deployment/deployment_project.go | 8 +++----- pkg/utils/once_by_key.go | 13 +++++++++++++ 2 files changed, 16 insertions(+), 5 deletions(-) create mode 100644 pkg/utils/once_by_key.go diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go index c2ddb64fe..891d7a933 100644 --- a/pkg/deployment/deployment_project.go +++ b/pkg/deployment/deployment_project.go @@ -10,11 +10,9 @@ import ( log "github.com/sirupsen/logrus" "path/filepath" "strings" - "sync" ) -var kustomizeDirsDeprecatedOnce sync.Once -var includesDeprecatedOnce sync.Once +var warnOnce utils.OnceByKey type DeploymentProject struct { VarsCtx *jinja2.VarsCtx @@ -85,14 +83,14 @@ func (p *DeploymentProject) loadConfig(k *k8s.K8sCluster) error { return fmt.Errorf("using 'deployments' and 'kustomizeDirs' at the same time is not allowed") } if len(p.config.KustomizeDirs) != 0 { - kustomizeDirsDeprecatedOnce.Do(func() { + warnOnce.Do("kustomizeDirs", func() { log.Warningf("'kustomizeDirs' is deprecated, use 'deployments' instead") }) p.config.Deployments = p.config.KustomizeDirs p.config.KustomizeDirs = nil } if len(p.config.Includes) != 0 { - includesDeprecatedOnce.Do(func() { + warnOnce.Do("includes", func() { log.Warningf("'includes' is deprecated, use 'deployments' instead") }) for _, inc := range p.config.Includes { diff --git a/pkg/utils/once_by_key.go b/pkg/utils/once_by_key.go new file mode 100644 index 000000000..9ea31cfdf --- /dev/null +++ b/pkg/utils/once_by_key.go @@ -0,0 +1,13 @@ +package utils + +import "sync" + +type OnceByKey struct { + m sync.Map +} + +func (o *OnceByKey) Do(key string, cb func()) { + o2, _ := o.m.LoadOrStore(key, &sync.Once{}) + o3 := o2.(*sync.Once) + o3.Do(cb) +} From 45b90e923d888e885a15545ddc91e86915c7767a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 14 Mar 2022 12:02:30 +0100 Subject: [PATCH 0496/2916] feat: Deprecate deleteByLabels --- cmd/kluctl/commands/cmd_delete.go | 2 +- cmd/kluctl/commands/cmd_prune.go | 2 +- docs/deployments.md | 22 +++------------------- e2e/project.go | 1 - examples/simple/deployment.yml | 4 ---- pkg/deployment/delete_utils.go | 2 +- pkg/deployment/deployment_collection.go | 4 ++-- pkg/deployment/deployment_project.go | 21 ++++++++++----------- 8 files changed, 18 insertions(+), 40 deletions(-) diff --git a/cmd/kluctl/commands/cmd_delete.go b/cmd/kluctl/commands/cmd_delete.go index 4aa9b0b8f..394212815 100644 --- a/cmd/kluctl/commands/cmd_delete.go +++ b/cmd/kluctl/commands/cmd_delete.go @@ -22,7 +22,7 @@ type deleteCmd struct { } func (cmd *deleteCmd) Help() string { - return `Objects are located based on 'deleteByLabels'', configured in 'deployment.yml' + return `Objects are located based on 'commonLabels'', configured in 'deployment.yml' WARNING: This command will also delete objects which are not part of your deployment project (anymore). It really only decides based on the 'deleteByLabel' labels and does NOT diff --git a/cmd/kluctl/commands/cmd_prune.go b/cmd/kluctl/commands/cmd_prune.go index 913dcf995..92a5b7948 100644 --- a/cmd/kluctl/commands/cmd_prune.go +++ b/cmd/kluctl/commands/cmd_prune.go @@ -19,7 +19,7 @@ type pruneCmd struct { func (cmd *pruneCmd) Help() string { return `"Searching works by: - 1. Search the cluster for all objects match 'deleteByLabels', as configured in 'deployment.yml'' + 1. Search the cluster for all objects match 'commonLabels', as configured in 'deployment.yml'' 2. Render the local target and list all objects. 3. Remove all objects from the list of 1. that are part of the list in 2.` } diff --git a/docs/deployments.md b/docs/deployments.md index f8b10d440..139f7bc9b 100644 --- a/docs/deployments.md +++ b/docs/deployments.md @@ -87,10 +87,6 @@ commonLabels: my.prefix/environment: "{{ args.environment }}" my.prefix/deployment-project: k8s-deployment-airsea -deleteByLabels: - my.prefix/environment: "{{ args.environment }}" - my.prefix/deployment-project: k8s-deployment-airsea - args: - name: environment ``` @@ -244,13 +240,6 @@ commonLabels: my.prefix/environment-name: {{ args.environment }} my.prefix/label-1: value-1 my.prefix/label-2: value-2 - -# PLEASE read through the documentation for this field to understand why it should match commonLabels! -deleteByLabels: - my.prefix/deployment-name: my-deployment-project-name - my.prefix/environment-name: {{ args.environment }} - my.prefix/label-1: value-1 - my.prefix/label-2: value-2 ``` Every resource deployed by the kustomize deployment `nginx` will now get the two provided labels attached. All included @@ -260,20 +249,15 @@ down. In case an included sub-deployment project also contains `commonLabels`, both dictionaries of common labels are merged inside the included sub-deployment project. In case of conflicts, the included common labels override the inherited. +The root deployment's `commonLabels` is also used to identify objects to be deleted when performing `kluctl delete` +or `kluctl prune` operations + Please note that these `commonLabels` are not related to `commonLabels` supported in `kustomization.yml` files. It was decided to not rely on this feature but instead attach labels manually to resources right before sending them to kubernetes. This is due to an [implementation detail](https://github.com/kubernetes-sigs/kustomize/issues/1009) in kustomize which causes `commonLabels` to also be applied to label selectors, which makes otherwise editable resources read-only when it comes to `commonLabels`. -### deleteByLabels -A dictionary of labels used to filter resources when performing `kluctl delete` or `kluctl prune` operations. -It should usually match `commonLabels`, but can also omit parts of `commonLabels` (DANGEROUS!!!). It should however -never add labels that are not present in `commonLabels`. - -Having `deleteByLabels` correct is crucial, as it might other lead to unrelated matches when searching for objects -to delete. This might then cause deletion of object that are NOT related to your deployment project. - ### overrideNamespace A string that is used as the default namespace for all kustomize deployments which don't have a `namespace` set in their `kustomization.yml`. diff --git a/e2e/project.go b/e2e/project.go index ef4569a8f..9180a0f33 100644 --- a/e2e/project.go +++ b/e2e/project.go @@ -229,7 +229,6 @@ func (p *testProject) updateDeploymentYaml(dir string, update func(o *uo.Unstruc p.updateYaml(p.getDeploymentDir(), filepath.Join(dir, "deployment.yml"), func(o *uo.UnstructuredObject) error { if dir == "." { o.SetNestedField(p.projectName, "commonLabels", "project_name") - o.SetNestedField(p.projectName, "deleteByLabels", "project_name") } return update(o) }, "") diff --git a/examples/simple/deployment.yml b/examples/simple/deployment.yml index 403d4d293..eb31c9fb9 100644 --- a/examples/simple/deployment.yml +++ b/examples/simple/deployment.yml @@ -5,10 +5,6 @@ commonLabels: my.prefix/environment: "{{ args.environment }}" my.prefix/deployment-project: k8s-deployment-nginx -deleteByLabels: - my.prefix/environment: "{{ args.environment }}" - my.prefix/deployment-project: k8s-deployment-nginx - args: - name: environment diff --git a/pkg/deployment/delete_utils.go b/pkg/deployment/delete_utils.go index 1cb7175df..af5673d70 100644 --- a/pkg/deployment/delete_utils.go +++ b/pkg/deployment/delete_utils.go @@ -113,7 +113,7 @@ func filterObjectsForDelete(k *k8s.K8sCluster, objects []*uo.UnstructuredObject, } func FindObjectsForDelete(k *k8s.K8sCluster, labels map[string]string, inclusion *utils.Inclusion, excludedObjects []k8s2.ObjectRef) ([]k8s2.ObjectRef, error) { - log.Infof("Getting all cluster objects matching deleteByLabels") + log.Infof("Getting all cluster objects matching commonLabels") excludedObjectsMap := make(map[k8s2.ObjectRef]bool) for _, ref := range excludedObjects { diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index 249f92e36..b4676103f 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -577,13 +577,13 @@ func (c *DeploymentCollection) Validate(k *k8s.K8sCluster) *types.ValidateResult } func (c *DeploymentCollection) FindDeleteObjects(k *k8s.K8sCluster) ([]k8s2.ObjectRef, error) { - labels := c.project.getDeleteByLabels() + labels := c.project.getCommonLabels() return FindObjectsForDelete(k, labels, c.inclusion, nil) } func (c *DeploymentCollection) FindOrphanObjects(k *k8s.K8sCluster) ([]k8s2.ObjectRef, error) { log.Infof("Searching for orphan objects") - labels := c.project.getDeleteByLabels() + labels := c.project.getCommonLabels() return FindObjectsForDelete(k, labels, c.inclusion, c.localObjectRefs()) } diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go index 891d7a933..35cbfe8d4 100644 --- a/pkg/deployment/deployment_project.go +++ b/pkg/deployment/deployment_project.go @@ -9,6 +9,7 @@ import ( "github.com/codablock/kluctl/pkg/utils/uo" log "github.com/sirupsen/logrus" "path/filepath" + "reflect" "strings" ) @@ -121,7 +122,15 @@ func (p *DeploymentProject) loadConfig(k *k8s.K8sCluster) error { } if len(p.getCommonLabels()) == 0 { - return fmt.Errorf("no commonLabels/deleteByLabels in root deployment. This is not allowed") + return fmt.Errorf("no commonLabels in root deployment. This is not allowed") + } + if len(p.config.DeleteByLabels) != 0 { + warnOnce.Do("deleteByLabels", func() { + log.Warningf("'deleteByLabels' is deprecated and ignored from now on") + }) + if !reflect.DeepEqual(p.config.CommonLabels, p.config.DeleteByLabels) { + return fmt.Errorf("commonLabels and deleteByLabels do not match") + } } if len(p.config.Args) != 0 && p.parentProject != nil { return fmt.Errorf("only the root deployment.yml can define args") @@ -261,16 +270,6 @@ func (p *DeploymentProject) getCommonLabels() map[string]string { return ret } -func (p *DeploymentProject) getDeleteByLabels() map[string]string { - ret := make(map[string]string) - parents := p.getParents() - for i, _ := range parents { - d := parents[len(parents)-i-1] - uo.MergeStrMap(ret, d.p.config.DeleteByLabels) - } - return ret -} - func (p *DeploymentProject) getOverrideNamespace() *string { for _, e := range p.getParents() { if e.p.config.OverrideNamespace != nil { From 097ba6ff152baf3aee602ef6c2447ca930353799 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 14 Mar 2022 12:18:51 +0100 Subject: [PATCH 0497/2916] refactor: Rely on ListAllObjects when retrieving remote objects --- pkg/deployment/all_objects.go | 2 +- pkg/deployment/deployment_collection.go | 38 +++++++++++++++++-------- pkg/k8s/k8s_cluster.go | 12 ++++---- pkg/types/k8s/ref.go | 6 +++- 4 files changed, 39 insertions(+), 19 deletions(-) diff --git a/pkg/deployment/all_objects.go b/pkg/deployment/all_objects.go index 15abf4e0c..2c70ad328 100644 --- a/pkg/deployment/all_objects.go +++ b/pkg/deployment/all_objects.go @@ -8,7 +8,7 @@ import ( ) func GetIncludedObjectsMetadata(k *k8s.K8sCluster, verbs []string, labels map[string]string, inclusion *utils.Inclusion) ([]*uo.UnstructuredObject, error) { - objects, err := k.ListAllObjects(verbs, "", labels, true) + objects, _, err := k.ListAllObjects(verbs, "", labels, true) if err != nil { return nil, err } diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index b4676103f..9e28eeb5c 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -243,6 +243,19 @@ func (c *DeploymentCollection) updateRemoteObjects(k *k8s.K8sCluster) error { return nil } + log.Infof("Getting remote objects by commonLabels") + allObjects, apiWarnings, err := k.ListAllObjects([]string{"get"}, "", c.project.getCommonLabels(), false) + for gvk, aw := range apiWarnings { + c.addApiWarnings(k8s2.ObjectRef{GVK: gvk}, aw) + } + if err != nil { + return err + } + + for _, o := range allObjects { + c.remoteObjects[o.GetK8sRef()] = o + } + notFoundRefsMap := make(map[k8s2.ObjectRef]bool) var notFoundRefsList []k8s2.ObjectRef for ref := range c.localObjectsByRef() { @@ -254,17 +267,18 @@ func (c *DeploymentCollection) updateRemoteObjects(k *k8s.K8sCluster) error { } } - log.Infof("Updating %d remote objects", len(notFoundRefsList)) - - r, apiWarnings, err := k.GetObjectsByRefs(notFoundRefsList) - for ref, aw := range apiWarnings { - c.addApiWarnings(ref, aw) - } - if err != nil { - return err - } - for _, o := range r { - c.remoteObjects[o.GetK8sRef()] = o + if len(notFoundRefsList) != 0 { + log.Infof("Getting %d additional remote objects", len(notFoundRefsList)) + r, apiWarnings, err := k.GetObjectsByRefs(notFoundRefsList) + for ref, aw := range apiWarnings { + c.addApiWarnings(ref, aw) + } + if err != nil { + return err + } + for _, o := range r { + c.remoteObjects[o.GetK8sRef()] = o + } } return nil } @@ -619,7 +633,7 @@ func (c *DeploymentCollection) doDiff(k *k8s.K8sCluster, appliedObjects map[k8s2 // if we can't even find it in appliedObjects, it probably ran into an error continue } - ro, _ := c.remoteObjects[ref] + ro := c.getRemoteObject(ref) if ao != nil && ro == nil { newObjects = append(newObjects, &types.RefAndObject{ diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index 40908d039..dbe5938f8 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -466,12 +466,12 @@ func (k *K8sCluster) ListObjectsMetadata(gvk schema.GroupVersionKind, namespace return result, apiWarnings, err } -func (k *K8sCluster) ListAllObjects(verbs []string, namespace string, labels map[string]string, onlyMetadata bool) ([]*uo.UnstructuredObject, error) { +func (k *K8sCluster) ListAllObjects(verbs []string, namespace string, labels map[string]string, onlyMetadata bool) ([]*uo.UnstructuredObject, map[schema.GroupVersionKind][]ApiWarning, error) { wp := utils.NewWorkerPoolWithErrors(8) defer wp.StopWait(false) var ret []*uo.UnstructuredObject - var retApiWarnings []ApiWarning + retApiWarnings := make(map[schema.GroupVersionKind][]ApiWarning) var mutex sync.Mutex k.mutex.Lock() @@ -506,7 +506,9 @@ func (k *K8sCluster) ListAllObjects(verbs []string, namespace string, labels map mutex.Lock() defer mutex.Unlock() ret = append(ret, l...) - retApiWarnings = append(retApiWarnings, apiWarnings...) + if len(apiWarnings) != 0 { + retApiWarnings[gvk] = apiWarnings + } return nil }) } @@ -515,9 +517,9 @@ func (k *K8sCluster) ListAllObjects(verbs []string, namespace string, labels map err := wp.StopWait(false) if err != nil { - return nil, err + return nil, retApiWarnings, err } - return ret, nil + return ret, retApiWarnings, nil } func (k *K8sCluster) GetSingleObject(ref k8s.ObjectRef) (*uo.UnstructuredObject, []ApiWarning, error) { diff --git a/pkg/types/k8s/ref.go b/pkg/types/k8s/ref.go index 426ea6d79..d166f88bd 100644 --- a/pkg/types/k8s/ref.go +++ b/pkg/types/k8s/ref.go @@ -15,7 +15,11 @@ func (r ObjectRef) String() string { if r.Namespace != "" { return fmt.Sprintf("%s/%s/%s", r.Namespace, r.GVK.Kind, r.Name) } else { - return fmt.Sprintf("%s/%s", r.GVK.Kind, r.Name) + if r.Name != "" { + return fmt.Sprintf("%s/%s", r.GVK.Kind, r.Name) + } else { + return r.GVK.Kind + } } } From 419f5df12c706391762b539dc02baebb2c3e6205 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 14 Mar 2022 14:46:22 +0100 Subject: [PATCH 0498/2916] refactor: Reuse remoteObjects for delete/orphan logic --- pkg/deployment/all_objects.go | 59 ------------------------- pkg/deployment/delete_utils.go | 15 ++----- pkg/deployment/deployment_collection.go | 40 +++++++++++++++-- pkg/utils/uo/k8s_fields.go | 38 ++++++++++++++++ 4 files changed, 77 insertions(+), 75 deletions(-) delete mode 100644 pkg/deployment/all_objects.go diff --git a/pkg/deployment/all_objects.go b/pkg/deployment/all_objects.go deleted file mode 100644 index 2c70ad328..000000000 --- a/pkg/deployment/all_objects.go +++ /dev/null @@ -1,59 +0,0 @@ -package deployment - -import ( - "github.com/codablock/kluctl/pkg/k8s" - "github.com/codablock/kluctl/pkg/utils" - "github.com/codablock/kluctl/pkg/utils/uo" - "strings" -) - -func GetIncludedObjectsMetadata(k *k8s.K8sCluster, verbs []string, labels map[string]string, inclusion *utils.Inclusion) ([]*uo.UnstructuredObject, error) { - objects, _, err := k.ListAllObjects(verbs, "", labels, true) - if err != nil { - return nil, err - } - - var ret []*uo.UnstructuredObject - - for _, o := range objects { - var iv []utils.InclusionEntry - for t := range getTagsFromObject(o) { - iv = append(iv, utils.InclusionEntry{ - Type: "tag", - Value: t, - }) - } - - annotations := o.GetK8sAnnotations() - if annotations != nil { - if itemDir, ok := annotations["kluctl.io/kustomize_dir"]; ok { - iv = append(iv, utils.InclusionEntry{ - Type: "deploymentItemDir", - Value: itemDir, - }) - } - } - - if inclusion.CheckIncluded(iv, false) { - ret = append(ret, o) - } - - } - - return ret, nil -} - -func getTagsFromObject(o *uo.UnstructuredObject) map[string]bool { - labels := o.GetK8sLabels() - if len(labels) == 0 { - return nil - } - - tags := make(map[string]bool) - for n, v := range labels { - if strings.HasPrefix(n, "kluctl.io/tag") { - tags[v] = true - } - } - return tags -} diff --git a/pkg/deployment/delete_utils.go b/pkg/deployment/delete_utils.go index af5673d70..89d85a110 100644 --- a/pkg/deployment/delete_utils.go +++ b/pkg/deployment/delete_utils.go @@ -6,7 +6,6 @@ import ( k8s2 "github.com/codablock/kluctl/pkg/types/k8s" "github.com/codablock/kluctl/pkg/utils" "github.com/codablock/kluctl/pkg/utils/uo" - log "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/runtime/schema" "strconv" "sync" @@ -46,13 +45,12 @@ func objectRefForExclusion(k *k8s.K8sCluster, ref k8s2.ObjectRef) k8s2.ObjectRef return ref } -func filterObjectsForDelete(k *k8s.K8sCluster, objects []*uo.UnstructuredObject, apiFilter []string, inclusion *utils.Inclusion, excludedObjects map[k8s2.ObjectRef]bool) ([]*uo.UnstructuredObject, error) { +func filterObjectsForDelete(k *k8s.K8sCluster, objects []*uo.UnstructuredObject, apiFilter []string, inclusionHasTags bool, excludedObjects map[k8s2.ObjectRef]bool) ([]*uo.UnstructuredObject, error) { filteredResources := make(map[schema.GroupKind]bool) for _, gk := range k.GetFilteredGKs(apiFilter) { filteredResources[gk] = true } - inclusionHasTags := inclusion.HasType("tags") var ret []*uo.UnstructuredObject for _, o := range objects { @@ -112,23 +110,16 @@ func filterObjectsForDelete(k *k8s.K8sCluster, objects []*uo.UnstructuredObject, return ret, nil } -func FindObjectsForDelete(k *k8s.K8sCluster, labels map[string]string, inclusion *utils.Inclusion, excludedObjects []k8s2.ObjectRef) ([]k8s2.ObjectRef, error) { - log.Infof("Getting all cluster objects matching commonLabels") - +func FindObjectsForDelete(k *k8s.K8sCluster, allClusterObjects []*uo.UnstructuredObject, inclusionHasTags bool, excludedObjects []k8s2.ObjectRef) ([]k8s2.ObjectRef, error) { excludedObjectsMap := make(map[k8s2.ObjectRef]bool) for _, ref := range excludedObjects { excludedObjectsMap[objectRefForExclusion(k, ref)] = true } - allClusterObjects, err := GetIncludedObjectsMetadata(k, []string{"delete"}, labels, inclusion) - if err != nil { - return nil, err - } - var ret []k8s2.ObjectRef for _, filter := range deleteOrder { - l, err := filterObjectsForDelete(k, allClusterObjects, filter, inclusion, excludedObjectsMap) + l, err := filterObjectsForDelete(k, allClusterObjects, filter, inclusionHasTags, excludedObjectsMap) if err != nil { return nil, err } diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index 9e28eeb5c..4f9d03dde 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -591,14 +591,12 @@ func (c *DeploymentCollection) Validate(k *k8s.K8sCluster) *types.ValidateResult } func (c *DeploymentCollection) FindDeleteObjects(k *k8s.K8sCluster) ([]k8s2.ObjectRef, error) { - labels := c.project.getCommonLabels() - return FindObjectsForDelete(k, labels, c.inclusion, nil) + return FindObjectsForDelete(k, c.getFilteredRemoteObjects(c.inclusion), c.inclusion.HasType("tags"), nil) } func (c *DeploymentCollection) FindOrphanObjects(k *k8s.K8sCluster) ([]k8s2.ObjectRef, error) { log.Infof("Searching for orphan objects") - labels := c.project.getCommonLabels() - return FindObjectsForDelete(k, labels, c.inclusion, c.localObjectRefs()) + return FindObjectsForDelete(k, c.getFilteredRemoteObjects(c.inclusion), c.inclusion.HasType("tags"), c.localObjectRefs()) } func (c *DeploymentCollection) doApply(k *k8s.K8sCluster, o applyUtilOptions) (map[k8s2.ObjectRef]*uo.UnstructuredObject, map[k8s2.ObjectRef]*uo.UnstructuredObject, []k8s2.ObjectRef, error) { @@ -801,6 +799,40 @@ func (c *DeploymentCollection) getRemoteObject(ref k8s2.ObjectRef) *uo.Unstructu return o } +func (c *DeploymentCollection) getFilteredRemoteObjects(inclusion *utils.Inclusion) []*uo.UnstructuredObject { + var ret []*uo.UnstructuredObject + + c.mutex.Lock() + defer c.mutex.Unlock() + + for _, o := range c.remoteObjects { + iv := c.getInclusionEntries(o) + if inclusion.CheckIncluded(iv, false) { + ret = append(ret, o) + } + } + + return ret +} + +func (c *DeploymentCollection) getInclusionEntries(o *uo.UnstructuredObject) []utils.InclusionEntry { + var iv []utils.InclusionEntry + for _, v := range o.GetK8sLabelsWithRegex("^kluctl.io/tag-\\d+$") { + iv = append(iv, utils.InclusionEntry{ + Type: "tag", + Value: v, + }) + } + + if itemDir := o.GetK8sAnnotation("kluctl.io/kustomize_dir"); itemDir != nil { + iv = append(iv, utils.InclusionEntry{ + Type: "deploymentItemDir", + Value: *itemDir, + }) + } + return iv +} + func (c *DeploymentCollection) FindRenderedImages() map[k8s2.ObjectRef][]string { ret := make(map[k8s2.ObjectRef][]string) for _, d := range c.deployments { diff --git a/pkg/utils/uo/k8s_fields.go b/pkg/utils/uo/k8s_fields.go index 1cbec1435..717e1078d 100644 --- a/pkg/utils/uo/k8s_fields.go +++ b/pkg/utils/uo/k8s_fields.go @@ -5,6 +5,8 @@ import ( log "github.com/sirupsen/logrus" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" + "reflect" + "regexp" ) func (uo *UnstructuredObject) GetK8sGVK() schema.GroupVersionKind { @@ -124,6 +126,18 @@ func (uo *UnstructuredObject) SetK8sLabel(name string, value string) { } } +func (uo *UnstructuredObject) GetK8sLabelsWithRegex(r interface{}) map[string]string { + p := uo.getRegexp(r) + + ret := make(map[string]string) + for k, v := range uo.GetK8sLabels() { + if p.MatchString(k) { + ret[k] = v + } + } + return ret +} + func (uo *UnstructuredObject) GetK8sAnnotations() map[string]string { ret, ok, err := uo.GetNestedStringMapCopy("metadata", "annotations") if err != nil { @@ -160,6 +174,18 @@ func (uo *UnstructuredObject) SetK8sAnnotation(name string, value string) { } } +func (uo *UnstructuredObject) GetK8sAnnotationsWithRegex(r interface{}) map[string]string { + p := uo.getRegexp(r) + + ret := make(map[string]string) + for k, v := range uo.GetK8sAnnotations() { + if p.MatchString(k) { + ret[k] = v + } + } + return ret +} + func (uo *UnstructuredObject) GetK8sResourceVersion() string { ret, _, _ := uo.GetNestedString("metadata", "resourceVersion") return ret @@ -184,3 +210,15 @@ func (uo *UnstructuredObject) GetK8sOwnerReferences() []*UnstructuredObject { func (uo *UnstructuredObject) GetK8sManagedFields() []metav1.ManagedFieldsEntry { return uo.ToUnstructured().GetManagedFields() } + +func (ui *UnstructuredObject) getRegexp(r interface{}) *regexp.Regexp { + if x, ok := r.(*regexp.Regexp); ok { + return x + } else { + if x, ok := r.(string); ok { + return regexp.MustCompile(x) + } + } + log.Panicf("unknown type %s", reflect.TypeOf(r).String()) + return nil +} \ No newline at end of file From 1b974fc52665f939f9096e54f2bc1eaa9fd250b1 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 14 Mar 2022 14:46:54 +0100 Subject: [PATCH 0499/2916] refactor: Use GetK8sAnnotation and GetK8sAnnotionsWithRegex whenever possible --- pkg/deployment/downscale_utils.go | 20 +++++++++----------- pkg/deployment/hooks_util.go | 28 +++++++++++++++------------- pkg/diff/managed_fields.go | 5 +---- pkg/diff/normalize.go | 12 +++++------- pkg/validation/validation.go | 17 ++++++++--------- 5 files changed, 38 insertions(+), 44 deletions(-) diff --git a/pkg/deployment/downscale_utils.go b/pkg/deployment/downscale_utils.go index d34f590d5..882879e4a 100644 --- a/pkg/deployment/downscale_utils.go +++ b/pkg/deployment/downscale_utils.go @@ -34,18 +34,16 @@ func downscaleObject(remote *uo.UnstructuredObject, local *uo.UnstructuredObject return ret, nil } var patch jsonpatch.Patch - for k, v := range local.GetK8sAnnotations() { - if downscaleAnnotationPatchRegex.MatchString(k) { - j, err := yaml.ConvertYamlToJson([]byte(v)) - if err != nil { - return nil, fmt.Errorf("invalid jsonpatch json/yaml: %w", err) - } - p, err := jsonpatch.DecodePatch(j) - if err != nil { - return nil, fmt.Errorf("invalid jsonpatch: %w", err) - } - patch = append(patch, p...) + for _, v := range local.GetK8sAnnotationsWithRegex(downscaleAnnotationPatchRegex) { + j, err := yaml.ConvertYamlToJson([]byte(v)) + if err != nil { + return nil, fmt.Errorf("invalid jsonpatch json/yaml: %w", err) + } + p, err := jsonpatch.DecodePatch(j) + if err != nil { + return nil, fmt.Errorf("invalid jsonpatch: %w", err) } + patch = append(patch, p...) } if len(patch) != 0 { diff --git a/pkg/deployment/hooks_util.go b/pkg/deployment/hooks_util.go index ea57b2122..6b7b03319 100644 --- a/pkg/deployment/hooks_util.go +++ b/pkg/deployment/hooks_util.go @@ -145,11 +145,11 @@ func (u *hooksUtil) getHook(o *uo.UnstructuredObject) *hook { ref := o.GetK8sRef() getSet := func(name string) map[string]bool { ret := make(map[string]bool) - a, ok := o.GetK8sAnnotations()[name] - if !ok { + a := o.GetK8sAnnotation(name) + if a == nil { return ret } - for _, x := range strings.Split(a, ",") { + for _, x := range strings.Split(*a, ",") { if x != "" { ret[x] = true } @@ -184,14 +184,15 @@ func (u *hooksUtil) getHook(o *uo.UnstructuredObject) *hook { helmCompatibility("pre-delete", "pre-delete") helmCompatibility("post-delete", "post-delete") - weightStr, ok := o.GetK8sAnnotations()["kluctl.io/hook-weight"] - if !ok { - weightStr, ok = o.GetK8sAnnotations()["helm.sh/hook-weight"] + weightStr := o.GetK8sAnnotation("kluctl.io/hook-weight") + if weightStr == nil { + weightStr = o.GetK8sAnnotation("helm.sh/hook-weight") } - if !ok { - weightStr = "0" + if weightStr == nil { + x := "0" + weightStr = &x } - weight, err := strconv.ParseInt(weightStr, 10, 32) + weight, err := strconv.ParseInt(*weightStr, 10, 32) if err != nil { u.a.handleError(ref, fmt.Errorf("failed to parse hook weight: %w", err)) } @@ -210,11 +211,12 @@ func (u *hooksUtil) getHook(o *uo.UnstructuredObject) *hook { } } - waitStr, ok := o.GetK8sAnnotations()["kluctl.io/hook-wait"] - if !ok { - waitStr = "true" + waitStr := o.GetK8sAnnotation("kluctl.io/hook-wait") + if waitStr == nil { + x := "true" + waitStr = &x } - wait, err := strconv.ParseBool(waitStr) + wait, err := strconv.ParseBool(*waitStr) if err != nil { u.a.handleError(ref, fmt.Errorf("failed to parse %s as bool", waitStr)) wait = true diff --git a/pkg/diff/managed_fields.go b/pkg/diff/managed_fields.go index c74b06c09..fb33ccef4 100644 --- a/pkg/diff/managed_fields.go +++ b/pkg/diff/managed_fields.go @@ -148,10 +148,7 @@ func ResolveFieldManagerConflicts(local *uo.UnstructuredObject, remote *uo.Unstr } forceApplyFields := make(map[string]bool) - for k, v := range local.GetK8sAnnotations() { - if !forceApplyFieldAnnotationRegex.MatchString(k) { - continue - } + for _, v := range local.GetK8sAnnotationsWithRegex(forceApplyFieldAnnotationRegex) { j, err := uo.NewMyJsonPath(v) if err != nil { return nil, nil, err diff --git a/pkg/diff/normalize.go b/pkg/diff/normalize.go index 0f44f8568..90e7c0641 100644 --- a/pkg/diff/normalize.go +++ b/pkg/diff/normalize.go @@ -177,14 +177,12 @@ func NormalizeObject(k *k8s.K8sCluster, o_ *uo.UnstructuredObject, ignoreForDiff return &uo.UnstructuredObject{Object: map[string]interface{}{}} } - for k, v := range localObject.GetK8sAnnotations() { - if ignoreDiffFieldAnnotationRegex.MatchString(k) { - j, err := uo.NewMyJsonPath(v) - if err != nil { - continue - } - _ = j.Del(o.Object) + for _, v := range localObject.GetK8sAnnotationsWithRegex(ignoreDiffFieldAnnotationRegex) { + j, err := uo.NewMyJsonPath(v) + if err != nil { + continue } + _ = j.Del(o.Object) } return o diff --git a/pkg/validation/validation.go b/pkg/validation/validation.go index 8e7cfbbe1..ae9503968 100644 --- a/pkg/validation/validation.go +++ b/pkg/validation/validation.go @@ -5,11 +5,12 @@ import ( "github.com/codablock/kluctl/pkg/types" "github.com/codablock/kluctl/pkg/utils/uo" "k8s.io/apimachinery/pkg/runtime/schema" + "regexp" "strconv" "strings" ) -const resultAnnotation = "validate-result.kluctl.io/" +var resultAnnotation = regexp.MustCompile("^validate-result.kluctl.io/.*") type validationFailed struct { } @@ -52,14 +53,12 @@ func ValidateObject(o *uo.UnstructuredObject, notReadyIsError bool) (ret types.V } }() - for k, v := range o.GetK8sAnnotations() { - if strings.HasPrefix(k, resultAnnotation) { - ret.Results = append(ret.Results, types.ValidateResultEntry{ - Ref: ref, - Annotation: k, - Message: v, - }) - } + for k, v := range o.GetK8sAnnotationsWithRegex(resultAnnotation) { + ret.Results = append(ret.Results, types.ValidateResultEntry{ + Ref: ref, + Annotation: k, + Message: v, + }) } status, ok, _ := o.GetNestedObject("status") From 2284a85cd0bd398bff0481db7e38af7e175d3218 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 14 Mar 2022 15:32:26 +0100 Subject: [PATCH 0500/2916] feat: Implement kluctl.io/diff-name annotation --- docs/annotations.md | 36 ++++++++++++++++++- pkg/deployment/deployment_collection.go | 47 ++++++++++++++++++++----- pkg/utils/uo/k8s_fields.go | 13 +++++++ 3 files changed, 87 insertions(+), 9 deletions(-) diff --git a/docs/annotations.md b/docs/annotations.md index e48b96be5..147003eed 100644 --- a/docs/annotations.md +++ b/docs/annotations.md @@ -17,11 +17,45 @@ This tag is especially useful and required on resources that would otherwise cau do not match the specified inclusion/exclusion tags. Namespaces are the most prominent example of such resources, as they most likely don't match exclusion tags, but cascaded deletion would still cause deletion of the excluded resources. +### kluctl.io/diff-name +This annotation will override the name of the object when looking for the in-cluster version of an object used for +diffs. This is useful when you are forced to use new names for the same objects whenever the content changes, e.g. +for all kinds of immutable resource types. + +Example (filename job.yml): +```yaml +apiVersion: batch/v1 +kind: Job +metadata: + name: myjob-{{ load_sha256("job.yml", 6) }} + annotations: + kluctl.io/diff-name: myjob +spec: + template: + spec: + containers: + - name: hello + image: busybox + command: ["sh", "-c", "echo hello"] + restartPolicy: Never +``` + +Without the `kluctl.io/diff-name` annotation, any change to the `job.yml` would be treated as a new object in resulting +diffs from various commands. This is due to the inclusion of the file hash in the job name. This would make it very hard +to figure out what exactly changed in an object. + +With the `kluctl.io/diff-name` annotation, kluctl will pick an existing job from the cluster with the same diff-name +and use it for the diff, making it a lot easier to analyze changes. If multiple objects match, the one with the youngest +`creationTimestamp` is chosen. + +Please note that this will not cause old objects (with the same diff-name) to be prunes. You still have to regularely +prune the deployment. + ### kluctl.io/downscale-patch Describes how a [downscale](./commands.md#downscale) on a resource can be done via a json patch. This is useful on CRD based resources where no automatic downscale can be performed by kluctl. -Example: +Example (filename job.yml): ```yaml apiVersion: kibana.k8s.elastic.co/v1 kind: Kibana diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index 4f9d03dde..2a5f7a921 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -31,6 +31,7 @@ type DeploymentCollection struct { deployments []*deploymentItem remoteObjects map[k8s2.ObjectRef]*uo.UnstructuredObject + remoteDiffObjects map[k8s2.ObjectRef]*uo.UnstructuredObject errors map[k8s2.ObjectRef]map[types.DeploymentError]bool warnings map[k8s2.ObjectRef]map[types.DeploymentError]bool @@ -45,6 +46,7 @@ func NewDeploymentCollection(project *DeploymentProject, images *Images, inclusi RenderDir: renderDir, forSeal: forSeal, remoteObjects: map[k8s2.ObjectRef]*uo.UnstructuredObject{}, + remoteDiffObjects: map[k8s2.ObjectRef]*uo.UnstructuredObject{}, errors: map[k8s2.ObjectRef]map[types.DeploymentError]bool{}, warnings: map[k8s2.ObjectRef]map[types.DeploymentError]bool{}, } @@ -253,7 +255,7 @@ func (c *DeploymentCollection) updateRemoteObjects(k *k8s.K8sCluster) error { } for _, o := range allObjects { - c.remoteObjects[o.GetK8sRef()] = o + c.addRemoteObject(o) } notFoundRefsMap := make(map[k8s2.ObjectRef]bool) @@ -277,14 +279,48 @@ func (c *DeploymentCollection) updateRemoteObjects(k *k8s.K8sCluster) error { return err } for _, o := range r { - c.remoteObjects[o.GetK8sRef()] = o + c.addRemoteObject(o) } } return nil } +func (c *DeploymentCollection) addRemoteObject(o *uo.UnstructuredObject) { + diffName := o.GetK8sAnnotation("kluctl.io/diff-name") + if diffName == nil { + x := o.GetK8sName() + diffName = &x + } + diffRef := o.GetK8sRef() + diffRef.Name = *diffName + oldCreationTime := time.Time{} + if old, ok := c.remoteDiffObjects[diffRef]; ok { + oldCreationTime = old.GetK8sCreationTime() + } + if oldCreationTime.IsZero() || o.GetK8sCreationTime().After(oldCreationTime) { + c.remoteDiffObjects[diffRef] = o + } + c.remoteObjects[o.GetK8sRef()] = o +} + +func (c *DeploymentCollection) getRemoteObject(ref k8s2.ObjectRef) *uo.UnstructuredObject { + o, _ := c.remoteObjects[ref] + return o +} + +func (c *DeploymentCollection) getRemoteObjectForDiff(localObject *uo.UnstructuredObject) *uo.UnstructuredObject { + ref := localObject.GetK8sRef() + diffName := localObject.GetK8sAnnotation("kluctl.io/diff-name") + if diffName != nil { + ref.Name = *diffName + } + o, _ := c.remoteDiffObjects[ref] + return o +} + func (c *DeploymentCollection) ForgetRemoteObject(ref k8s2.ObjectRef) { delete(c.remoteObjects, ref) + delete(c.remoteDiffObjects, ref) } func (c *DeploymentCollection) localObjectsByRef() map[k8s2.ObjectRef]bool { @@ -631,7 +667,7 @@ func (c *DeploymentCollection) doDiff(k *k8s.K8sCluster, appliedObjects map[k8s2 // if we can't even find it in appliedObjects, it probably ran into an error continue } - ro := c.getRemoteObject(ref) + ro := c.getRemoteObjectForDiff(o) if ao != nil && ro == nil { newObjects = append(newObjects, &types.RefAndObject{ @@ -794,11 +830,6 @@ func (c *DeploymentCollection) clearErrorsAndWarnings() { c.errors = map[k8s2.ObjectRef]map[types.DeploymentError]bool{} } -func (c *DeploymentCollection) getRemoteObject(ref k8s2.ObjectRef) *uo.UnstructuredObject { - o, _ := c.remoteObjects[ref] - return o -} - func (c *DeploymentCollection) getFilteredRemoteObjects(inclusion *utils.Inclusion) []*uo.UnstructuredObject { var ret []*uo.UnstructuredObject diff --git a/pkg/utils/uo/k8s_fields.go b/pkg/utils/uo/k8s_fields.go index 717e1078d..2f74d2726 100644 --- a/pkg/utils/uo/k8s_fields.go +++ b/pkg/utils/uo/k8s_fields.go @@ -7,6 +7,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "reflect" "regexp" + "time" ) func (uo *UnstructuredObject) GetK8sGVK() schema.GroupVersionKind { @@ -211,6 +212,18 @@ func (uo *UnstructuredObject) GetK8sManagedFields() []metav1.ManagedFieldsEntry return uo.ToUnstructured().GetManagedFields() } +func (uo *UnstructuredObject) GetK8sCreationTime() time.Time { + v, ok, _ := uo.GetNestedString("metadata", "creationTimestamp") + if !ok { + return time.Time{} + } + t, err := time.Parse(time.RFC3339, v) + if err != nil { + return time.Time{} + } + return t +} + func (ui *UnstructuredObject) getRegexp(r interface{}) *regexp.Regexp { if x, ok := r.(*regexp.Regexp); ok { return x From b2705c34830614d07e4f538dc73fc4877c3d063a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 14 Mar 2022 16:49:30 +0100 Subject: [PATCH 0501/2916] refactor: Move collection of errors/warnings into own util object --- pkg/deployment/apply_utils.go | 14 ++- pkg/deployment/deployment_collection.go | 159 +++++++----------------- pkg/deployment/errors_holder.go | 113 +++++++++++++++++ 3 files changed, 164 insertions(+), 122 deletions(-) create mode 100644 pkg/deployment/errors_holder.go diff --git a/pkg/deployment/apply_utils.go b/pkg/deployment/apply_utils.go index 90c8a0ba2..96667541d 100644 --- a/pkg/deployment/apply_utils.go +++ b/pkg/deployment/apply_utils.go @@ -26,6 +26,7 @@ type applyUtilOptions struct { } type applyUtil struct { + dew *deploymentErrorsAndWarnings deploymentCollection *DeploymentCollection k *k8s.K8sCluster o applyUtilOptions @@ -39,8 +40,9 @@ type applyUtil struct { mutex sync.Mutex } -func newApplyUtil(deploymentCollection *DeploymentCollection, k *k8s.K8sCluster, o applyUtilOptions) *applyUtil { +func newApplyUtil(dew *deploymentErrorsAndWarnings, deploymentCollection *DeploymentCollection, k *k8s.K8sCluster, o applyUtilOptions) *applyUtil { return &applyUtil{ + dew: dew, deploymentCollection: deploymentCollection, k: k, o: o, @@ -65,11 +67,11 @@ func (a *applyUtil) handleResult(appliedObject *uo.UnstructuredObject, hook bool } func (a *applyUtil) handleApiWarnings(ref k8s2.ObjectRef, warnings []k8s.ApiWarning) { - a.deploymentCollection.addApiWarnings(ref, warnings) + a.dew.addApiWarnings(ref, warnings) } func (a *applyUtil) handleWarning(ref k8s2.ObjectRef, warning error) { - a.deploymentCollection.addWarning(ref, warning) + a.dew.addWarning(ref, warning) } func (a *applyUtil) handleError(ref k8s2.ObjectRef, err error) { @@ -80,11 +82,11 @@ func (a *applyUtil) handleError(ref k8s2.ObjectRef, err error) { a.abortSignal = true } - a.deploymentCollection.addError(ref, err) + a.dew.addError(ref, err) } func (a *applyUtil) hadError(ref k8s2.ObjectRef) bool { - return a.deploymentCollection.hadError(ref) + return a.dew.hadError(ref) } func (a *applyUtil) deleteObject(ref k8s2.ObjectRef, hook bool) bool { @@ -191,7 +193,7 @@ func (a *applyUtil) retryApplyWithConflicts(x *uo.UnstructuredObject, hook bool, return } for _, lo := range lostOwnership { - a.deploymentCollection.addWarning(ref, fmt.Errorf("%s. Not updating field '%s' as we lost field ownership", lo.Message, lo.Field)) + a.dew.addWarning(ref, fmt.Errorf("%s. Not updating field '%s' as we lost field ownership", lo.Message, lo.Field)) } x2 = x3 } else { diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index 2a5f7a921..0f0d0b2e2 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -33,9 +33,7 @@ type DeploymentCollection struct { remoteObjects map[k8s2.ObjectRef]*uo.UnstructuredObject remoteDiffObjects map[k8s2.ObjectRef]*uo.UnstructuredObject - errors map[k8s2.ObjectRef]map[types.DeploymentError]bool - warnings map[k8s2.ObjectRef]map[types.DeploymentError]bool - mutex sync.Mutex + mutex sync.Mutex } func NewDeploymentCollection(project *DeploymentProject, images *Images, inclusion *utils.Inclusion, renderDir string, forSeal bool) (*DeploymentCollection, error) { @@ -47,8 +45,6 @@ func NewDeploymentCollection(project *DeploymentProject, images *Images, inclusi forSeal: forSeal, remoteObjects: map[k8s2.ObjectRef]*uo.UnstructuredObject{}, remoteDiffObjects: map[k8s2.ObjectRef]*uo.UnstructuredObject{}, - errors: map[k8s2.ObjectRef]map[types.DeploymentError]bool{}, - warnings: map[k8s2.ObjectRef]map[types.DeploymentError]bool{}, } indexes := make(map[string]int) @@ -240,7 +236,7 @@ func (c *DeploymentCollection) buildKustomizeObjects(k *k8s.K8sCluster) error { return nil } -func (c *DeploymentCollection) updateRemoteObjects(k *k8s.K8sCluster) error { +func (c *DeploymentCollection) updateRemoteObjects(k *k8s.K8sCluster, dew *deploymentErrorsAndWarnings) error { if k == nil { return nil } @@ -248,7 +244,7 @@ func (c *DeploymentCollection) updateRemoteObjects(k *k8s.K8sCluster) error { log.Infof("Getting remote objects by commonLabels") allObjects, apiWarnings, err := k.ListAllObjects([]string{"get"}, "", c.project.getCommonLabels(), false) for gvk, aw := range apiWarnings { - c.addApiWarnings(k8s2.ObjectRef{GVK: gvk}, aw) + dew.addApiWarnings(k8s2.ObjectRef{GVK: gvk}, aw) } if err != nil { return err @@ -273,7 +269,7 @@ func (c *DeploymentCollection) updateRemoteObjects(k *k8s.K8sCluster) error { log.Infof("Getting %d additional remote objects", len(notFoundRefsList)) r, apiWarnings, err := k.GetObjectsByRefs(notFoundRefsList) for ref, aw := range apiWarnings { - c.addApiWarnings(ref, aw) + dew.addApiWarnings(ref, aw) } if err != nil { return err @@ -342,6 +338,8 @@ func (c *DeploymentCollection) localObjectRefs() []k8s2.ObjectRef { } func (c *DeploymentCollection) Prepare(k *k8s.K8sCluster) error { + dew := NewDeploymentErrorsAndWarnings() + err := c.RenderDeployments(k) if err != nil { return err @@ -354,15 +352,19 @@ func (c *DeploymentCollection) Prepare(k *k8s.K8sCluster) error { if err != nil { return err } - err = c.updateRemoteObjects(k) + err = c.updateRemoteObjects(k, dew) if err != nil { return err } + if len(dew.errors) != 0 { + return dew.getMultiError() + } return nil } func (c *DeploymentCollection) Deploy(k *k8s.K8sCluster, forceApply bool, replaceOnError bool, forceReplaceOnError, abortOnError bool, hookTimeout time.Duration) (*types.CommandResult, error) { - c.clearErrorsAndWarnings() + dew := NewDeploymentErrorsAndWarnings() + o := applyUtilOptions{ forceApply: forceApply, replaceOnError: replaceOnError, @@ -371,7 +373,7 @@ func (c *DeploymentCollection) Deploy(k *k8s.K8sCluster, forceApply bool, replac abortOnError: abortOnError, hookTimeout: hookTimeout, } - appliedObjects, appliedHookObjects, deletedObjects, err := c.doApply(k, o) + appliedObjects, appliedHookObjects, deletedObjects, err := c.doApply(dew, k, o) if err != nil { return nil, err } @@ -396,14 +398,15 @@ func (c *DeploymentCollection) Deploy(k *k8s.K8sCluster, forceApply bool, replac DeletedObjects: deletedObjects, HookObjects: appliedHookObjectsList, OrphanObjects: orphanObjects, - Errors: c.errorsList(), - Warnings: c.warningsList(), + Errors: dew.getErrorsList(), + Warnings: dew.getWarningsList(), SeenImages: c.images.seenImages, }, nil } func (c *DeploymentCollection) Diff(k *k8s.K8sCluster, forceApply bool, replaceOnError bool, forceReplaceOnError bool, ignoreTags bool, ignoreLabels bool, ignoreAnnotations bool) (*types.CommandResult, error) { - c.clearErrorsAndWarnings() + dew := NewDeploymentErrorsAndWarnings() + o := applyUtilOptions{ forceApply: forceApply, replaceOnError: replaceOnError, @@ -412,7 +415,7 @@ func (c *DeploymentCollection) Diff(k *k8s.K8sCluster, forceApply bool, replaceO abortOnError: false, hookTimeout: 0, } - appliedObjects, appliedHookObjects, deletedObjects, err := c.doApply(k, o) + appliedObjects, appliedHookObjects, deletedObjects, err := c.doApply(dew, k, o) if err != nil { return nil, err } @@ -437,13 +440,15 @@ func (c *DeploymentCollection) Diff(k *k8s.K8sCluster, forceApply bool, replaceO DeletedObjects: deletedObjects, HookObjects: appliedHookObjectsList, OrphanObjects: orphanObjects, - Errors: c.errorsList(), - Warnings: c.warningsList(), + Errors: dew.getErrorsList(), + Warnings: dew.getWarningsList(), SeenImages: c.images.seenImages, }, nil } func (c *DeploymentCollection) PokeImages(k *k8s.K8sCluster) (*types.CommandResult, error) { + dew := NewDeploymentErrorsAndWarnings() + allObjects := make(map[k8s2.ObjectRef]*uo.UnstructuredObject) for _, d := range c.deployments { if !d.checkInclusionForDeploy() { @@ -458,7 +463,7 @@ func (c *DeploymentCollection) PokeImages(k *k8s.K8sCluster) (*types.CommandResu for _, fi := range c.images.seenImages { _, ok := allObjects[*fi.Object] if !ok { - c.addError(*fi.Object, fmt.Errorf("object not found while trying to associate image with deployed object")) + dew.addError(*fi.Object, fmt.Errorf("object not found while trying to associate image with deployed object")) continue } @@ -489,11 +494,11 @@ func (c *DeploymentCollection) PokeImages(k *k8s.K8sCluster) (*types.CommandResu ref := ref containers := containers wp.Submit(func() error { - newObject, err := c.doReplaceObject(k, ref, func(o *uo.UnstructuredObject) (*uo.UnstructuredObject, error) { + newObject, err := c.doReplaceObject(k, dew, ref, func(o *uo.UnstructuredObject) (*uo.UnstructuredObject, error) { return doPokeImage(containers, o) }) if err != nil { - c.addError(ref, err) + dew.addError(ref, err) } else { mutex.Lock() defer mutex.Unlock() @@ -514,13 +519,15 @@ func (c *DeploymentCollection) PokeImages(k *k8s.K8sCluster) (*types.CommandResu return &types.CommandResult{ NewObjects: newObjects, ChangedObjects: changedObjects, - Errors: c.errorsList(), - Warnings: c.warningsList(), + Errors: dew.getErrorsList(), + Warnings: dew.getWarningsList(), SeenImages: c.images.seenImages, }, nil } func (c *DeploymentCollection) Downscale(k *k8s.K8sCluster) (*types.CommandResult, error) { + dew := NewDeploymentErrorsAndWarnings() + wp := utils.NewWorkerPoolWithErrors(8) defer wp.StopWait(false) @@ -538,7 +545,7 @@ func (c *DeploymentCollection) Downscale(k *k8s.K8sCluster) (*types.CommandResul if isDownscaleDelete(o) { wp.Submit(func() error { apiWarnings, err := k.DeleteSingleObject(ref, k8s.DeleteOptions{IgnoreNotFoundError: true}) - c.addApiWarnings(ref, apiWarnings) + dew.addApiWarnings(ref, apiWarnings) if err != nil { return err } @@ -549,7 +556,7 @@ func (c *DeploymentCollection) Downscale(k *k8s.K8sCluster) (*types.CommandResul }) } else { wp.Submit(func() error { - o2, err := c.doReplaceObject(k, ref, func(remote *uo.UnstructuredObject) (*uo.UnstructuredObject, error) { + o2, err := c.doReplaceObject(k, dew, ref, func(remote *uo.UnstructuredObject) (*uo.UnstructuredObject, error) { return downscaleObject(remote, o) }) if err != nil { @@ -577,8 +584,8 @@ func (c *DeploymentCollection) Downscale(k *k8s.K8sCluster) (*types.CommandResul NewObjects: newObjects, ChangedObjects: changedObjects, DeletedObjects: deletedObjects, - Errors: c.errorsList(), - Warnings: c.warningsList(), + Errors: dew.getErrorsList(), + Warnings: dew.getWarningsList(), SeenImages: c.images.seenImages, }, nil } @@ -586,18 +593,9 @@ func (c *DeploymentCollection) Downscale(k *k8s.K8sCluster) (*types.CommandResul func (c *DeploymentCollection) Validate(k *k8s.K8sCluster) *types.ValidateResult { var result types.ValidateResult - for _, m := range c.warnings { - for e := range m { - result.Warnings = append(result.Warnings, e) - } - } - for _, m := range c.errors { - for e := range m { - result.Errors = append(result.Errors, e) - } - } + dew := NewDeploymentErrorsAndWarnings() - a := newApplyUtil(c, k, applyUtilOptions{}) + a := newApplyUtil(dew, c, k, applyUtilOptions{}) h := hooksUtil{a: a} for _, d := range c.deployments { if !d.checkInclusionForDeploy() { @@ -623,6 +621,9 @@ func (c *DeploymentCollection) Validate(k *k8s.K8sCluster) *types.ValidateResult } } + result.Warnings = append(result.Warnings, dew.getWarningsList()...) + result.Errors = append(result.Errors, dew.getErrorsList()...) + return &result } @@ -635,8 +636,8 @@ func (c *DeploymentCollection) FindOrphanObjects(k *k8s.K8sCluster) ([]k8s2.Obje return FindObjectsForDelete(k, c.getFilteredRemoteObjects(c.inclusion), c.inclusion.HasType("tags"), c.localObjectRefs()) } -func (c *DeploymentCollection) doApply(k *k8s.K8sCluster, o applyUtilOptions) (map[k8s2.ObjectRef]*uo.UnstructuredObject, map[k8s2.ObjectRef]*uo.UnstructuredObject, []k8s2.ObjectRef, error) { - au := newApplyUtil(c, k, o) +func (c *DeploymentCollection) doApply(dew *deploymentErrorsAndWarnings, k *k8s.K8sCluster, o applyUtilOptions) (map[k8s2.ObjectRef]*uo.UnstructuredObject, map[k8s2.ObjectRef]*uo.UnstructuredObject, []k8s2.ObjectRef, error) { + au := newApplyUtil(dew, c, k, o) au.applyDeployments() var deletedObjects []k8s2.ObjectRef for ref := range au.deletedObjects { @@ -713,7 +714,7 @@ func (c *DeploymentCollection) doDiff(k *k8s.K8sCluster, appliedObjects map[k8s2 return newObjects, changedObjects, nil } -func (c *DeploymentCollection) doReplaceObject(k *k8s.K8sCluster, ref k8s2.ObjectRef, callback func(o *uo.UnstructuredObject) (*uo.UnstructuredObject, error)) (*uo.UnstructuredObject, error) { +func (c *DeploymentCollection) doReplaceObject(k *k8s.K8sCluster, dew *deploymentErrorsAndWarnings, ref k8s2.ObjectRef, callback func(o *uo.UnstructuredObject) (*uo.UnstructuredObject, error)) (*uo.UnstructuredObject, error) { firstCall := true for true { var remote *uo.UnstructuredObject @@ -721,7 +722,7 @@ func (c *DeploymentCollection) doReplaceObject(k *k8s.K8sCluster, ref k8s2.Objec remote = c.getRemoteObject(ref) } else { o2, apiWarnings, err := k.GetSingleObject(ref) - c.addApiWarnings(ref, apiWarnings) + dew.addApiWarnings(ref, apiWarnings) if err != nil && !errors.IsNotFound(err) { return nil, err } @@ -742,7 +743,7 @@ func (c *DeploymentCollection) doReplaceObject(k *k8s.K8sCluster, ref k8s2.Objec } result, apiWarnings, err := k.UpdateObject(modified, k8s.UpdateOptions{}) - c.addApiWarnings(ref, apiWarnings) + dew.addApiWarnings(ref, apiWarnings) if err != nil { if errors.IsConflict(err) { log.Warningf("Conflict while patching %s. Retrying...", ref.String()) @@ -756,80 +757,6 @@ func (c *DeploymentCollection) doReplaceObject(k *k8s.K8sCluster, ref k8s2.Objec return nil, fmt.Errorf("unexpected end of loop") } -func (c *DeploymentCollection) addWarning(ref k8s2.ObjectRef, warning error) { - de := types.DeploymentError{ - Ref: ref, - Error: warning.Error(), - } - c.mutex.Lock() - defer c.mutex.Unlock() - m, ok := c.warnings[ref] - if !ok { - m = make(map[types.DeploymentError]bool) - c.warnings[ref] = m - } - m[de] = true -} - -func (c *DeploymentCollection) addError(ref k8s2.ObjectRef, err error) { - de := types.DeploymentError{ - Ref: ref, - Error: err.Error(), - } - c.mutex.Lock() - defer c.mutex.Unlock() - m, ok := c.errors[ref] - if !ok { - m = make(map[types.DeploymentError]bool) - c.errors[ref] = m - } - m[de] = true -} - -func (c *DeploymentCollection) addApiWarnings(ref k8s2.ObjectRef, warnings []k8s.ApiWarning) { - for _, w := range warnings { - c.addWarning(ref, fmt.Errorf(w.Text)) - } -} - -func (c *DeploymentCollection) hadError(ref k8s2.ObjectRef) bool { - c.mutex.Lock() - defer c.mutex.Unlock() - _, ok := c.errors[ref] - return ok -} - -func (c *DeploymentCollection) errorsList() []types.DeploymentError { - c.mutex.Lock() - defer c.mutex.Unlock() - var l []types.DeploymentError - for _, m := range c.errors { - for de := range m { - l = append(l, de) - } - } - return l -} - -func (c *DeploymentCollection) warningsList() []types.DeploymentError { - c.mutex.Lock() - defer c.mutex.Unlock() - var l []types.DeploymentError - for _, m := range c.warnings { - for de := range m { - l = append(l, de) - } - } - return l -} - -func (c *DeploymentCollection) clearErrorsAndWarnings() { - c.mutex.Lock() - defer c.mutex.Unlock() - c.warnings = map[k8s2.ObjectRef]map[types.DeploymentError]bool{} - c.errors = map[k8s2.ObjectRef]map[types.DeploymentError]bool{} -} - func (c *DeploymentCollection) getFilteredRemoteObjects(inclusion *utils.Inclusion) []*uo.UnstructuredObject { var ret []*uo.UnstructuredObject diff --git a/pkg/deployment/errors_holder.go b/pkg/deployment/errors_holder.go new file mode 100644 index 000000000..0317349f7 --- /dev/null +++ b/pkg/deployment/errors_holder.go @@ -0,0 +1,113 @@ +package deployment + +import ( + "errors" + "fmt" + k8s2 "github.com/codablock/kluctl/pkg/k8s" + "github.com/codablock/kluctl/pkg/types" + "github.com/codablock/kluctl/pkg/types/k8s" + "github.com/codablock/kluctl/pkg/utils" + "sync" +) + +type deploymentErrorsAndWarnings struct { + errors map[k8s.ObjectRef]map[types.DeploymentError]bool + warnings map[k8s.ObjectRef]map[types.DeploymentError]bool + mutex sync.Mutex +} + +func NewDeploymentErrorsAndWarnings() *deploymentErrorsAndWarnings { + dew := &deploymentErrorsAndWarnings{} + dew.init() + return dew +} + +func (dew *deploymentErrorsAndWarnings) init() { + dew.mutex.Lock() + defer dew.mutex.Unlock() + dew.warnings = map[k8s.ObjectRef]map[types.DeploymentError]bool{} + dew.errors = map[k8s.ObjectRef]map[types.DeploymentError]bool{} +} + +func (dew *deploymentErrorsAndWarnings) addWarning(ref k8s.ObjectRef, warning error) { + de := types.DeploymentError{ + Ref: ref, + Error: warning.Error(), + } + dew.mutex.Lock() + defer dew.mutex.Unlock() + m, ok := dew.warnings[ref] + if !ok { + m = make(map[types.DeploymentError]bool) + dew.warnings[ref] = m + } + m[de] = true +} + +func (dew *deploymentErrorsAndWarnings) addError(ref k8s.ObjectRef, err error) { + de := types.DeploymentError{ + Ref: ref, + Error: err.Error(), + } + dew.mutex.Lock() + defer dew.mutex.Unlock() + m, ok := dew.errors[ref] + if !ok { + m = make(map[types.DeploymentError]bool) + dew.errors[ref] = m + } + m[de] = true +} + +func (dew *deploymentErrorsAndWarnings) addApiWarnings(ref k8s.ObjectRef, warnings []k8s2.ApiWarning) { + for _, w := range warnings { + dew.addWarning(ref, fmt.Errorf(w.Text)) + } +} + +func (dew *deploymentErrorsAndWarnings) hadError(ref k8s.ObjectRef) bool { + dew.mutex.Lock() + defer dew.mutex.Unlock() + _, ok := dew.errors[ref] + return ok +} + +func (dew *deploymentErrorsAndWarnings) getErrorsList() []types.DeploymentError { + dew.mutex.Lock() + defer dew.mutex.Unlock() + var ret []types.DeploymentError + for _, m := range dew.errors { + for e := range m { + ret = append(ret, e) + } + } + return ret +} + +func (dew *deploymentErrorsAndWarnings) getWarningsList() []types.DeploymentError { + dew.mutex.Lock() + defer dew.mutex.Unlock() + var ret []types.DeploymentError + for _, m := range dew.warnings { + for e := range m { + ret = append(ret, e) + } + } + return ret +} + +func (dew *deploymentErrorsAndWarnings) getPlainErrorsList() []error { + var ret []error + for _, e := range dew.getErrorsList() { + ret = append(ret, errors.New(e.Error)) + } + return ret +} + +func (dew *deploymentErrorsAndWarnings) getMultiError() error { + l := dew.getPlainErrorsList() + if len(l) == 0 { + return nil + } + return utils.NewErrorList(l) +} From 0daf5b079dbbfe3f23bfec173363e27fdd040d30 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 14 Mar 2022 16:51:33 +0100 Subject: [PATCH 0502/2916] refactor: Move diff code into own util object --- pkg/deployment/deployment_collection.go | 146 ++++-------------------- pkg/deployment/diff_utils.go | 134 ++++++++++++++++++++++ 2 files changed, 159 insertions(+), 121 deletions(-) create mode 100644 pkg/deployment/diff_utils.go diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index 0f0d0b2e2..068130897 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -3,7 +3,6 @@ package deployment import ( "context" "fmt" - "github.com/codablock/kluctl/pkg/diff" "github.com/codablock/kluctl/pkg/k8s" "github.com/codablock/kluctl/pkg/seal" "github.com/codablock/kluctl/pkg/types" @@ -31,8 +30,6 @@ type DeploymentCollection struct { deployments []*deploymentItem remoteObjects map[k8s2.ObjectRef]*uo.UnstructuredObject - remoteDiffObjects map[k8s2.ObjectRef]*uo.UnstructuredObject - mutex sync.Mutex } @@ -44,7 +41,6 @@ func NewDeploymentCollection(project *DeploymentProject, images *Images, inclusi RenderDir: renderDir, forSeal: forSeal, remoteObjects: map[k8s2.ObjectRef]*uo.UnstructuredObject{}, - remoteDiffObjects: map[k8s2.ObjectRef]*uo.UnstructuredObject{}, } indexes := make(map[string]int) @@ -282,20 +278,6 @@ func (c *DeploymentCollection) updateRemoteObjects(k *k8s.K8sCluster, dew *deplo } func (c *DeploymentCollection) addRemoteObject(o *uo.UnstructuredObject) { - diffName := o.GetK8sAnnotation("kluctl.io/diff-name") - if diffName == nil { - x := o.GetK8sName() - diffName = &x - } - diffRef := o.GetK8sRef() - diffRef.Name = *diffName - oldCreationTime := time.Time{} - if old, ok := c.remoteDiffObjects[diffRef]; ok { - oldCreationTime = old.GetK8sCreationTime() - } - if oldCreationTime.IsZero() || o.GetK8sCreationTime().After(oldCreationTime) { - c.remoteDiffObjects[diffRef] = o - } c.remoteObjects[o.GetK8sRef()] = o } @@ -304,19 +286,8 @@ func (c *DeploymentCollection) getRemoteObject(ref k8s2.ObjectRef) *uo.Unstructu return o } -func (c *DeploymentCollection) getRemoteObjectForDiff(localObject *uo.UnstructuredObject) *uo.UnstructuredObject { - ref := localObject.GetK8sRef() - diffName := localObject.GetK8sAnnotation("kluctl.io/diff-name") - if diffName != nil { - ref.Name = *diffName - } - o, _ := c.remoteDiffObjects[ref] - return o -} - func (c *DeploymentCollection) ForgetRemoteObject(ref k8s2.ObjectRef) { delete(c.remoteObjects, ref) - delete(c.remoteDiffObjects, ref) } func (c *DeploymentCollection) localObjectsByRef() map[k8s2.ObjectRef]bool { @@ -384,17 +355,17 @@ func (c *DeploymentCollection) Deploy(k *k8s.K8sCluster, forceApply bool, replac Object: o, }) } - newObjects, changedObjects, err := c.doDiff(k, appliedObjects, false, false, false) - if err != nil { - return nil, err - } + + du := NewDiffUtil(dew, c.deployments, c.remoteObjects, appliedObjects) + du.diff(k) + orphanObjects, err := c.FindOrphanObjects(k) if err != nil { return nil, err } return &types.CommandResult{ - NewObjects: newObjects, - ChangedObjects: changedObjects, + NewObjects: du.newObjects, + ChangedObjects: du.changedObjects, DeletedObjects: deletedObjects, HookObjects: appliedHookObjectsList, OrphanObjects: orphanObjects, @@ -426,17 +397,20 @@ func (c *DeploymentCollection) Diff(k *k8s.K8sCluster, forceApply bool, replaceO Object: o, }) } - newObjects, changedObjects, err := c.doDiff(k, appliedObjects, ignoreTags, ignoreLabels, ignoreAnnotations) - if err != nil { - return nil, err - } + + du := NewDiffUtil(dew, c.deployments, c.remoteObjects, appliedObjects) + du.ignoreTags = ignoreTags + du.ignoreLabels = ignoreLabels + du.ignoreAnnotations = ignoreAnnotations + du.diff(k) + orphanObjects, err := c.FindOrphanObjects(k) if err != nil { return nil, err } return &types.CommandResult{ - NewObjects: newObjects, - ChangedObjects: changedObjects, + NewObjects: du.newObjects, + ChangedObjects: du.changedObjects, DeletedObjects: deletedObjects, HookObjects: appliedHookObjectsList, OrphanObjects: orphanObjects, @@ -512,13 +486,12 @@ func (c *DeploymentCollection) PokeImages(k *k8s.K8sCluster) (*types.CommandResu return nil, err } - newObjects, changedObjects, err := c.doDiff(k, appliedObjects, false, false, false) - if err != nil { - return nil, err - } + du := NewDiffUtil(dew, c.deployments, c.remoteObjects, appliedObjects) + du.diff(k) + return &types.CommandResult{ - NewObjects: newObjects, - ChangedObjects: changedObjects, + NewObjects: du.newObjects, + ChangedObjects: du.changedObjects, Errors: dew.getErrorsList(), Warnings: dew.getWarningsList(), SeenImages: c.images.seenImages, @@ -576,13 +549,12 @@ func (c *DeploymentCollection) Downscale(k *k8s.K8sCluster) (*types.CommandResul return nil, err } - newObjects, changedObjects, err := c.doDiff(k, appliedObjects, false, false, false) - if err != nil { - return nil, err - } + du := NewDiffUtil(dew, c.deployments, c.remoteObjects, appliedObjects) + du.diff(k) + return &types.CommandResult{ - NewObjects: newObjects, - ChangedObjects: changedObjects, + NewObjects: du.newObjects, + ChangedObjects: du.changedObjects, DeletedObjects: deletedObjects, Errors: dew.getErrorsList(), Warnings: dew.getWarningsList(), @@ -646,74 +618,6 @@ func (c *DeploymentCollection) doApply(dew *deploymentErrorsAndWarnings, k *k8s. return au.appliedObjects, au.appliedHookObjects, deletedObjects, nil } -func (c *DeploymentCollection) doDiff(k *k8s.K8sCluster, appliedObjects map[k8s2.ObjectRef]*uo.UnstructuredObject, ignoreTags bool, ignoreLabels bool, ignoreAnnotations bool) ([]*types.RefAndObject, []*types.ChangedObject, error) { - var newObjects []*types.RefAndObject - var changedObjects []*types.ChangedObject - var mutex sync.Mutex - - wp := utils.NewDebuggerAwareWorkerPool(8) - defer wp.StopWait(false) - - for _, d := range c.deployments { - if !d.checkInclusionForDeploy() { - continue - } - - ignoreForDiffs := d.project.getIgnoreForDiffs(ignoreTags, ignoreLabels, ignoreAnnotations) - for _, o := range d.objects { - o := o - ref := o.GetK8sRef() - ao, ok := appliedObjects[ref] - if !ok { - // if we can't even find it in appliedObjects, it probably ran into an error - continue - } - ro := c.getRemoteObjectForDiff(o) - - if ao != nil && ro == nil { - newObjects = append(newObjects, &types.RefAndObject{ - Ref: ao.GetK8sRef(), - Object: ao, - }) - } else if ao == nil && ro != nil { - // deleted? - continue - } else if ao == nil && ro == nil { - // did not apply? (e.g. in downscale command) - continue - } else { - wp.Submit(func() error { - nao := diff.NormalizeObject(k, ao, ignoreForDiffs, o) - nro := diff.NormalizeObject(k, ro, ignoreForDiffs, o) - changes, err := diff.Diff(nro, nao) - if err != nil { - return err - } - if len(changes) == 0 { - return nil - } - mutex.Lock() - defer mutex.Unlock() - changedObjects = append(changedObjects, &types.ChangedObject{ - Ref: ref, - NewObject: ao, - OldObject: ro, - Changes: changes, - }) - return nil - }) - } - } - } - - err := wp.StopWait(false) - if err != nil { - return nil, nil, err - } - - return newObjects, changedObjects, nil -} - func (c *DeploymentCollection) doReplaceObject(k *k8s.K8sCluster, dew *deploymentErrorsAndWarnings, ref k8s2.ObjectRef, callback func(o *uo.UnstructuredObject) (*uo.UnstructuredObject, error)) (*uo.UnstructuredObject, error) { firstCall := true for true { diff --git a/pkg/deployment/diff_utils.go b/pkg/deployment/diff_utils.go new file mode 100644 index 000000000..a65c90f34 --- /dev/null +++ b/pkg/deployment/diff_utils.go @@ -0,0 +1,134 @@ +package deployment + +import ( + "github.com/codablock/kluctl/pkg/diff" + "github.com/codablock/kluctl/pkg/k8s" + "github.com/codablock/kluctl/pkg/types" + k8s2 "github.com/codablock/kluctl/pkg/types/k8s" + "github.com/codablock/kluctl/pkg/utils/uo" + "sync" + "time" +) + +type diffUtil struct { + dew *deploymentErrorsAndWarnings + deployments []*deploymentItem + appliedObjects map[k8s2.ObjectRef]*uo.UnstructuredObject + remoteObjects map[k8s2.ObjectRef]*uo.UnstructuredObject + + ignoreTags bool + ignoreLabels bool + ignoreAnnotations bool + + remoteDiffObjects map[k8s2.ObjectRef]*uo.UnstructuredObject + newObjects []*types.RefAndObject + changedObjects []*types.ChangedObject + mutex sync.Mutex +} + +func NewDiffUtil(dew *deploymentErrorsAndWarnings, deployments []*deploymentItem, remoteObjects map[k8s2.ObjectRef]*uo.UnstructuredObject, appliedObjects map[k8s2.ObjectRef]*uo.UnstructuredObject) *diffUtil { + return &diffUtil{ + dew: dew, + deployments: deployments, + remoteObjects: remoteObjects, + appliedObjects: appliedObjects, + } +} + +func (u *diffUtil) diff(k *k8s.K8sCluster) { + var wg sync.WaitGroup + + u.calcRemoteObjectsForDiff() + + for _, d := range u.deployments { + if !d.checkInclusionForDeploy() { + continue + } + + ignoreForDiffs := d.project.getIgnoreForDiffs(u.ignoreTags, u.ignoreLabels, u.ignoreAnnotations) + for _, o := range d.objects { + o := o + ref := o.GetK8sRef() + ao, ok := u.appliedObjects[ref] + if !ok { + // if we can't even find it in appliedObjects, it probably ran into an error + continue + } + ro := u.getRemoteObjectForDiff(o) + + wg.Add(1) + go func() { + defer wg.Done() + u.diffObject(k, o, ao, ro, ignoreForDiffs) + }() + } + } + wg.Wait() +} + +func (u *diffUtil) diffObject(k *k8s.K8sCluster, lo *uo.UnstructuredObject, ao *uo.UnstructuredObject, ro *uo.UnstructuredObject, ignoreForDiffs []*types.IgnoreForDiffItemConfig) { + if ao != nil && ro == nil { + u.mutex.Lock() + defer u.mutex.Unlock() + u.newObjects = append(u.newObjects, &types.RefAndObject{ + Ref: ao.GetK8sRef(), + Object: ao, + }) + } else if ao == nil && ro != nil { + // deleted? + return + } else if ao == nil && ro == nil { + // did not apply? (e.g. in downscale command) + return + } else { + nao := diff.NormalizeObject(k, ao, ignoreForDiffs, lo) + nro := diff.NormalizeObject(k, ro, ignoreForDiffs, lo) + changes, err := diff.Diff(nro, nao) + if err != nil { + u.dew.addError(lo.GetK8sRef(), err) + return + } + if len(changes) == 0 { + return + } + + u.mutex.Lock() + defer u.mutex.Unlock() + u.changedObjects = append(u.changedObjects, &types.ChangedObject{ + Ref: lo.GetK8sRef(), + NewObject: ao, + OldObject: ro, + Changes: changes, + }) + } +} + +func (u *diffUtil) calcRemoteObjectsForDiff() { + u.remoteDiffObjects = make(map[k8s2.ObjectRef]*uo.UnstructuredObject) + for _, o := range u.remoteObjects { + diffName := o.GetK8sAnnotation("kluctl.io/diff-name") + if diffName == nil { + x := o.GetK8sName() + diffName = &x + } + diffRef := o.GetK8sRef() + diffRef.Name = *diffName + oldCreationTime := time.Time{} + if old, ok := u.remoteDiffObjects[diffRef]; ok { + oldCreationTime = old.GetK8sCreationTime() + } + if oldCreationTime.IsZero() || o.GetK8sCreationTime().After(oldCreationTime) { + u.remoteDiffObjects[diffRef] = o + } + } +} + +func (u *diffUtil) getRemoteObjectForDiff(localObject *uo.UnstructuredObject) *uo.UnstructuredObject { + ref := localObject.GetK8sRef() + diffName := localObject.GetK8sAnnotation("kluctl.io/diff-name") + if diffName != nil { + ref.Name = *diffName + } + o, _ := u.remoteDiffObjects[ref] + return o +} From 5a67f1dd84b942e1c2c43c7abb2320c979037519 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 14 Mar 2022 16:52:13 +0100 Subject: [PATCH 0503/2916] cleanup: Run gofmt --- pkg/git/auth/env_auth_provider.go | 2 +- pkg/git/mirrored_repo.go | 8 ++++---- pkg/utils/uo/k8s_fields.go | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/git/auth/env_auth_provider.go b/pkg/git/auth/env_auth_provider.go index 261836686..5182f2a52 100644 --- a/pkg/git/auth/env_auth_provider.go +++ b/pkg/git/auth/env_auth_provider.go @@ -50,4 +50,4 @@ func (a *GitEnvAuthProvider) BuildAuth(gitUrl git_url.GitUrl) transport.AuthMeth } } return nil -} \ No newline at end of file +} diff --git a/pkg/git/mirrored_repo.go b/pkg/git/mirrored_repo.go index 6ee635213..db2d67ff9 100644 --- a/pkg/git/mirrored_repo.go +++ b/pkg/git/mirrored_repo.go @@ -19,8 +19,8 @@ import "github.com/gofrs/flock" var cacheBaseDir = filepath.Join(utils.GetTmpBaseDir(), "git-cache") type MirroredGitRepo struct { - url git_url.GitUrl - mirrorDir string + url git_url.GitUrl + mirrorDir string hasLock bool hasUpdated bool @@ -32,8 +32,8 @@ type MirroredGitRepo struct { func NewMirroredGitRepo(u git_url.GitUrl) (*MirroredGitRepo, error) { mirrorRepoName := buildMirrorRepoName(u) o := &MirroredGitRepo{ - url: u, - mirrorDir: filepath.Join(cacheBaseDir, mirrorRepoName), + url: u, + mirrorDir: filepath.Join(cacheBaseDir, mirrorRepoName), } if !utils.IsDirectory(o.mirrorDir) { diff --git a/pkg/utils/uo/k8s_fields.go b/pkg/utils/uo/k8s_fields.go index 2f74d2726..2815078ac 100644 --- a/pkg/utils/uo/k8s_fields.go +++ b/pkg/utils/uo/k8s_fields.go @@ -234,4 +234,4 @@ func (ui *UnstructuredObject) getRegexp(r interface{}) *regexp.Regexp { } log.Panicf("unknown type %s", reflect.TypeOf(r).String()) return nil -} \ No newline at end of file +} From 9443956c2ec8c829dcd2530aee6a009a6085a790 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 14 Mar 2022 16:59:23 +0100 Subject: [PATCH 0504/2916] refactor: Remove doApply --- pkg/deployment/apply_utils.go | 20 +++++++++++ pkg/deployment/deployment_collection.go | 48 ++++++------------------- 2 files changed, 30 insertions(+), 38 deletions(-) diff --git a/pkg/deployment/apply_utils.go b/pkg/deployment/apply_utils.go index 96667541d..50e0dc422 100644 --- a/pkg/deployment/apply_utils.go +++ b/pkg/deployment/apply_utils.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/codablock/kluctl/pkg/diff" "github.com/codablock/kluctl/pkg/k8s" + "github.com/codablock/kluctl/pkg/types" k8s2 "github.com/codablock/kluctl/pkg/types/k8s" "github.com/codablock/kluctl/pkg/utils" "github.com/codablock/kluctl/pkg/utils/uo" @@ -441,3 +442,22 @@ func (a *applyUtil) doLog(d *deploymentItem, level log.Level, s string, f ...int s = fmt.Sprintf("%s: %s", d.relToProjectItemDir, fmt.Sprintf(s, f...)) log.StandardLogger().Logf(level, s) } + +func (a *applyUtil) getDeletedObjectsList() []k8s2.ObjectRef { + var ret []k8s2.ObjectRef + for ref := range a.deletedObjects { + ret = append(ret, ref) + } + return ret +} + +func (a *applyUtil) getAppliedHookObjects() []*types.RefAndObject { + var ret []*types.RefAndObject + for _, o := range a.appliedHookObjects { + ret = append(ret, &types.RefAndObject{ + Ref: o.GetK8sRef(), + Object: o, + }) + } + return ret +} \ No newline at end of file diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index 068130897..885a6e767 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -344,19 +344,10 @@ func (c *DeploymentCollection) Deploy(k *k8s.K8sCluster, forceApply bool, replac abortOnError: abortOnError, hookTimeout: hookTimeout, } - appliedObjects, appliedHookObjects, deletedObjects, err := c.doApply(dew, k, o) - if err != nil { - return nil, err - } - var appliedHookObjectsList []*types.RefAndObject - for _, o := range appliedHookObjects { - appliedHookObjectsList = append(appliedHookObjectsList, &types.RefAndObject{ - Ref: o.GetK8sRef(), - Object: o, - }) - } + au := newApplyUtil(dew, c, k, o) + au.applyDeployments() - du := NewDiffUtil(dew, c.deployments, c.remoteObjects, appliedObjects) + du := NewDiffUtil(dew, c.deployments, c.remoteObjects, au.appliedObjects) du.diff(k) orphanObjects, err := c.FindOrphanObjects(k) @@ -366,8 +357,8 @@ func (c *DeploymentCollection) Deploy(k *k8s.K8sCluster, forceApply bool, replac return &types.CommandResult{ NewObjects: du.newObjects, ChangedObjects: du.changedObjects, - DeletedObjects: deletedObjects, - HookObjects: appliedHookObjectsList, + DeletedObjects: au.getDeletedObjectsList(), + HookObjects: au.getAppliedHookObjects(), OrphanObjects: orphanObjects, Errors: dew.getErrorsList(), Warnings: dew.getWarningsList(), @@ -386,19 +377,10 @@ func (c *DeploymentCollection) Diff(k *k8s.K8sCluster, forceApply bool, replaceO abortOnError: false, hookTimeout: 0, } - appliedObjects, appliedHookObjects, deletedObjects, err := c.doApply(dew, k, o) - if err != nil { - return nil, err - } - var appliedHookObjectsList []*types.RefAndObject - for _, o := range appliedHookObjects { - appliedHookObjectsList = append(appliedHookObjectsList, &types.RefAndObject{ - Ref: o.GetK8sRef(), - Object: o, - }) - } + au := newApplyUtil(dew, c, k, o) + au.applyDeployments() - du := NewDiffUtil(dew, c.deployments, c.remoteObjects, appliedObjects) + du := NewDiffUtil(dew, c.deployments, c.remoteObjects, au.appliedObjects) du.ignoreTags = ignoreTags du.ignoreLabels = ignoreLabels du.ignoreAnnotations = ignoreAnnotations @@ -411,8 +393,8 @@ func (c *DeploymentCollection) Diff(k *k8s.K8sCluster, forceApply bool, replaceO return &types.CommandResult{ NewObjects: du.newObjects, ChangedObjects: du.changedObjects, - DeletedObjects: deletedObjects, - HookObjects: appliedHookObjectsList, + DeletedObjects: au.getDeletedObjectsList(), + HookObjects: au.getAppliedHookObjects(), OrphanObjects: orphanObjects, Errors: dew.getErrorsList(), Warnings: dew.getWarningsList(), @@ -608,16 +590,6 @@ func (c *DeploymentCollection) FindOrphanObjects(k *k8s.K8sCluster) ([]k8s2.Obje return FindObjectsForDelete(k, c.getFilteredRemoteObjects(c.inclusion), c.inclusion.HasType("tags"), c.localObjectRefs()) } -func (c *DeploymentCollection) doApply(dew *deploymentErrorsAndWarnings, k *k8s.K8sCluster, o applyUtilOptions) (map[k8s2.ObjectRef]*uo.UnstructuredObject, map[k8s2.ObjectRef]*uo.UnstructuredObject, []k8s2.ObjectRef, error) { - au := newApplyUtil(dew, c, k, o) - au.applyDeployments() - var deletedObjects []k8s2.ObjectRef - for ref := range au.deletedObjects { - deletedObjects = append(deletedObjects, ref) - } - return au.appliedObjects, au.appliedHookObjects, deletedObjects, nil -} - func (c *DeploymentCollection) doReplaceObject(k *k8s.K8sCluster, dew *deploymentErrorsAndWarnings, ref k8s2.ObjectRef, callback func(o *uo.UnstructuredObject) (*uo.UnstructuredObject, error)) (*uo.UnstructuredObject, error) { firstCall := true for true { From f623b85efd368adab43e2f6581f4de7485ea74dc Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 14 Mar 2022 17:26:24 +0100 Subject: [PATCH 0505/2916] refactor: Move deployment utils into own package --- cmd/kluctl/commands/cmd_delete.go | 4 +- pkg/deployment/deployment_collection.go | 204 +++++++++--------- pkg/deployment/deployment_item.go | 88 ++++---- pkg/deployment/deployment_project.go | 2 +- pkg/deployment/{ => utils}/apply_utils.go | 175 +++++++-------- pkg/deployment/{ => utils}/delete_utils.go | 2 +- pkg/deployment/{ => utils}/diff_utils.go | 35 +-- pkg/deployment/{ => utils}/downscale_utils.go | 6 +- pkg/deployment/{ => utils}/errors_holder.go | 30 +-- pkg/deployment/{ => utils}/hooks_util.go | 43 ++-- 10 files changed, 299 insertions(+), 290 deletions(-) rename pkg/deployment/{ => utils}/apply_utils.go (67%) rename pkg/deployment/{ => utils}/delete_utils.go (99%) rename pkg/deployment/{ => utils}/diff_utils.go (75%) rename pkg/deployment/{ => utils}/downscale_utils.go (93%) rename pkg/deployment/{ => utils}/errors_holder.go (69%) rename pkg/deployment/{ => utils}/hooks_util.go (84%) diff --git a/cmd/kluctl/commands/cmd_delete.go b/cmd/kluctl/commands/cmd_delete.go index 394212815..8cbac66b9 100644 --- a/cmd/kluctl/commands/cmd_delete.go +++ b/cmd/kluctl/commands/cmd_delete.go @@ -3,7 +3,7 @@ package commands import ( "fmt" "github.com/codablock/kluctl/cmd/kluctl/args" - "github.com/codablock/kluctl/pkg/deployment" + "github.com/codablock/kluctl/pkg/deployment/utils" "github.com/codablock/kluctl/pkg/k8s" "github.com/codablock/kluctl/pkg/types" k8s2 "github.com/codablock/kluctl/pkg/types/k8s" @@ -71,5 +71,5 @@ func confirmedDeleteObjects(k *k8s.K8sCluster, refs []k8s2.ObjectRef, dryRun boo } } - return deployment.DeleteObjects(k, refs, true) + return utils.DeleteObjects(k, refs, true) } diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index 885a6e767..3e520f6e5 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -3,6 +3,7 @@ package deployment import ( "context" "fmt" + utils2 "github.com/codablock/kluctl/pkg/deployment/utils" "github.com/codablock/kluctl/pkg/k8s" "github.com/codablock/kluctl/pkg/seal" "github.com/codablock/kluctl/pkg/types" @@ -28,7 +29,7 @@ type DeploymentCollection struct { RenderDir string forSeal bool - deployments []*deploymentItem + Deployments []*DeploymentItem remoteObjects map[k8s2.ObjectRef]*uo.UnstructuredObject mutex sync.Mutex } @@ -48,11 +49,11 @@ func NewDeploymentCollection(project *DeploymentProject, images *Images, inclusi if err != nil { return nil, err } - dc.deployments = deployments + dc.Deployments = deployments return dc, nil } -func (c *DeploymentCollection) createBarrierDummy(project *DeploymentProject) *deploymentItem { +func (c *DeploymentCollection) createBarrierDummy(project *DeploymentProject) *DeploymentItem { b := true tmpDiConfig := &types.DeploymentItemConfig{ Barrier: &b, @@ -86,8 +87,8 @@ func findDeploymentItemIndex(project *DeploymentProject, pth *string, indexes ma return index, dir2 } -func (c *DeploymentCollection) collectDeployments(project *DeploymentProject, indexes map[string]int) ([]*deploymentItem, error) { - var ret []*deploymentItem +func (c *DeploymentCollection) collectDeployments(project *DeploymentProject, indexes map[string]int) ([]*DeploymentItem, error) { + var ret []*DeploymentItem for i, diConfig := range project.config.Deployments { if diConfig.Include != nil { @@ -122,7 +123,7 @@ func (c *DeploymentCollection) RenderDeployments(k *k8s.K8sCluster) error { wp := utils.NewDebuggerAwareWorkerPool(16) defer wp.StopWait(false) - for _, d := range c.deployments { + for _, d := range c.Deployments { err := d.render(k, wp) if err != nil { return err @@ -133,7 +134,7 @@ func (c *DeploymentCollection) RenderDeployments(k *k8s.K8sCluster) error { return err } - for _, d := range c.deployments { + for _, d := range c.Deployments { err := d.renderHelmCharts(k, wp) if err != nil { return err @@ -178,7 +179,7 @@ func (c *DeploymentCollection) resolveSealedSecrets() error { return nil } - for _, d := range c.deployments { + for _, d := range c.Deployments { err := d.resolveSealedSecrets() if err != nil { return err @@ -195,7 +196,7 @@ func (c *DeploymentCollection) buildKustomizeObjects(k *k8s.K8sCluster) error { var mutex sync.Mutex sem := semaphore.NewWeighted(16) - for _, d_ := range c.deployments { + for _, d_ := range c.Deployments { d := d_ wg.Add(1) @@ -232,7 +233,7 @@ func (c *DeploymentCollection) buildKustomizeObjects(k *k8s.K8sCluster) error { return nil } -func (c *DeploymentCollection) updateRemoteObjects(k *k8s.K8sCluster, dew *deploymentErrorsAndWarnings) error { +func (c *DeploymentCollection) updateRemoteObjects(k *k8s.K8sCluster, dew *utils2.DeploymentErrorsAndWarnings) error { if k == nil { return nil } @@ -240,7 +241,7 @@ func (c *DeploymentCollection) updateRemoteObjects(k *k8s.K8sCluster, dew *deplo log.Infof("Getting remote objects by commonLabels") allObjects, apiWarnings, err := k.ListAllObjects([]string{"get"}, "", c.project.getCommonLabels(), false) for gvk, aw := range apiWarnings { - dew.addApiWarnings(k8s2.ObjectRef{GVK: gvk}, aw) + dew.AddApiWarnings(k8s2.ObjectRef{GVK: gvk}, aw) } if err != nil { return err @@ -265,7 +266,7 @@ func (c *DeploymentCollection) updateRemoteObjects(k *k8s.K8sCluster, dew *deplo log.Infof("Getting %d additional remote objects", len(notFoundRefsList)) r, apiWarnings, err := k.GetObjectsByRefs(notFoundRefsList) for ref, aw := range apiWarnings { - dew.addApiWarnings(ref, aw) + dew.AddApiWarnings(ref, aw) } if err != nil { return err @@ -281,7 +282,7 @@ func (c *DeploymentCollection) addRemoteObject(o *uo.UnstructuredObject) { c.remoteObjects[o.GetK8sRef()] = o } -func (c *DeploymentCollection) getRemoteObject(ref k8s2.ObjectRef) *uo.UnstructuredObject { +func (c *DeploymentCollection) GetRemoteObject(ref k8s2.ObjectRef) *uo.UnstructuredObject { o, _ := c.remoteObjects[ref] return o } @@ -292,8 +293,8 @@ func (c *DeploymentCollection) ForgetRemoteObject(ref k8s2.ObjectRef) { func (c *DeploymentCollection) localObjectsByRef() map[k8s2.ObjectRef]bool { ret := make(map[k8s2.ObjectRef]bool) - for _, d := range c.deployments { - for _, o := range d.objects { + for _, d := range c.Deployments { + for _, o := range d.Objects { ret[o.GetK8sRef()] = true } } @@ -309,7 +310,7 @@ func (c *DeploymentCollection) localObjectRefs() []k8s2.ObjectRef { } func (c *DeploymentCollection) Prepare(k *k8s.K8sCluster) error { - dew := NewDeploymentErrorsAndWarnings() + dew := utils2.NewDeploymentErrorsAndWarnings() err := c.RenderDeployments(k) if err != nil { @@ -327,90 +328,91 @@ func (c *DeploymentCollection) Prepare(k *k8s.K8sCluster) error { if err != nil { return err } - if len(dew.errors) != 0 { - return dew.getMultiError() + err = dew.GetMultiError() + if err != nil { + return err } return nil } func (c *DeploymentCollection) Deploy(k *k8s.K8sCluster, forceApply bool, replaceOnError bool, forceReplaceOnError, abortOnError bool, hookTimeout time.Duration) (*types.CommandResult, error) { - dew := NewDeploymentErrorsAndWarnings() + dew := utils2.NewDeploymentErrorsAndWarnings() - o := applyUtilOptions{ - forceApply: forceApply, - replaceOnError: replaceOnError, - forceReplaceOnError: forceReplaceOnError, - dryRun: k.DryRun, - abortOnError: abortOnError, - hookTimeout: hookTimeout, + o := utils2.ApplyUtilOptions{ + ForceApply: forceApply, + ReplaceOnError: replaceOnError, + ForceReplaceOnError: forceReplaceOnError, + DryRun: k.DryRun, + AbortOnError: abortOnError, + HookTimeout: hookTimeout, } - au := newApplyUtil(dew, c, k, o) - au.applyDeployments() + au := utils2.NewApplyUtil(dew, c, k, o) + au.ApplyDeployments() - du := NewDiffUtil(dew, c.deployments, c.remoteObjects, au.appliedObjects) - du.diff(k) + du := utils2.NewDiffUtil(dew, c.Deployments, c.remoteObjects, au.AppliedObjects) + du.Diff(k) orphanObjects, err := c.FindOrphanObjects(k) if err != nil { return nil, err } return &types.CommandResult{ - NewObjects: du.newObjects, - ChangedObjects: du.changedObjects, - DeletedObjects: au.getDeletedObjectsList(), - HookObjects: au.getAppliedHookObjects(), + NewObjects: du.NewObjects, + ChangedObjects: du.ChangedObjects, + DeletedObjects: au.GetDeletedObjectsList(), + HookObjects: au.GetAppliedHookObjects(), OrphanObjects: orphanObjects, - Errors: dew.getErrorsList(), - Warnings: dew.getWarningsList(), + Errors: dew.GetErrorsList(), + Warnings: dew.GetWarningsList(), SeenImages: c.images.seenImages, }, nil } func (c *DeploymentCollection) Diff(k *k8s.K8sCluster, forceApply bool, replaceOnError bool, forceReplaceOnError bool, ignoreTags bool, ignoreLabels bool, ignoreAnnotations bool) (*types.CommandResult, error) { - dew := NewDeploymentErrorsAndWarnings() + dew := utils2.NewDeploymentErrorsAndWarnings() - o := applyUtilOptions{ - forceApply: forceApply, - replaceOnError: replaceOnError, - forceReplaceOnError: forceReplaceOnError, - dryRun: true, - abortOnError: false, - hookTimeout: 0, + o := utils2.ApplyUtilOptions{ + ForceApply: forceApply, + ReplaceOnError: replaceOnError, + ForceReplaceOnError: forceReplaceOnError, + DryRun: true, + AbortOnError: false, + HookTimeout: 0, } - au := newApplyUtil(dew, c, k, o) - au.applyDeployments() + au := utils2.NewApplyUtil(dew, c, k, o) + au.ApplyDeployments() - du := NewDiffUtil(dew, c.deployments, c.remoteObjects, au.appliedObjects) - du.ignoreTags = ignoreTags - du.ignoreLabels = ignoreLabels - du.ignoreAnnotations = ignoreAnnotations - du.diff(k) + du := utils2.NewDiffUtil(dew, c.Deployments, c.remoteObjects, au.AppliedObjects) + du.IgnoreTags = ignoreTags + du.IgnoreLabels = ignoreLabels + du.IgnoreAnnotations = ignoreAnnotations + du.Diff(k) orphanObjects, err := c.FindOrphanObjects(k) if err != nil { return nil, err } return &types.CommandResult{ - NewObjects: du.newObjects, - ChangedObjects: du.changedObjects, - DeletedObjects: au.getDeletedObjectsList(), - HookObjects: au.getAppliedHookObjects(), + NewObjects: du.NewObjects, + ChangedObjects: du.ChangedObjects, + DeletedObjects: au.GetDeletedObjectsList(), + HookObjects: au.GetAppliedHookObjects(), OrphanObjects: orphanObjects, - Errors: dew.getErrorsList(), - Warnings: dew.getWarningsList(), + Errors: dew.GetErrorsList(), + Warnings: dew.GetWarningsList(), SeenImages: c.images.seenImages, }, nil } func (c *DeploymentCollection) PokeImages(k *k8s.K8sCluster) (*types.CommandResult, error) { - dew := NewDeploymentErrorsAndWarnings() + dew := utils2.NewDeploymentErrorsAndWarnings() allObjects := make(map[k8s2.ObjectRef]*uo.UnstructuredObject) - for _, d := range c.deployments { - if !d.checkInclusionForDeploy() { + for _, d := range c.Deployments { + if !d.CheckInclusionForDeploy() { continue } - for _, o := range d.objects { + for _, o := range d.Objects { allObjects[o.GetK8sRef()] = o } } @@ -419,7 +421,7 @@ func (c *DeploymentCollection) PokeImages(k *k8s.K8sCluster) (*types.CommandResu for _, fi := range c.images.seenImages { _, ok := allObjects[*fi.Object] if !ok { - dew.addError(*fi.Object, fmt.Errorf("object not found while trying to associate image with deployed object")) + dew.AddError(*fi.Object, fmt.Errorf("object not found while trying to associate image with deployed object")) continue } @@ -454,7 +456,7 @@ func (c *DeploymentCollection) PokeImages(k *k8s.K8sCluster) (*types.CommandResu return doPokeImage(containers, o) }) if err != nil { - dew.addError(ref, err) + dew.AddError(ref, err) } else { mutex.Lock() defer mutex.Unlock() @@ -468,20 +470,20 @@ func (c *DeploymentCollection) PokeImages(k *k8s.K8sCluster) (*types.CommandResu return nil, err } - du := NewDiffUtil(dew, c.deployments, c.remoteObjects, appliedObjects) - du.diff(k) + du := utils2.NewDiffUtil(dew, c.Deployments, c.remoteObjects, appliedObjects) + du.Diff(k) return &types.CommandResult{ - NewObjects: du.newObjects, - ChangedObjects: du.changedObjects, - Errors: dew.getErrorsList(), - Warnings: dew.getWarningsList(), + NewObjects: du.NewObjects, + ChangedObjects: du.ChangedObjects, + Errors: dew.GetErrorsList(), + Warnings: dew.GetWarningsList(), SeenImages: c.images.seenImages, }, nil } func (c *DeploymentCollection) Downscale(k *k8s.K8sCluster) (*types.CommandResult, error) { - dew := NewDeploymentErrorsAndWarnings() + dew := utils2.NewDeploymentErrorsAndWarnings() wp := utils.NewWorkerPoolWithErrors(8) defer wp.StopWait(false) @@ -490,17 +492,17 @@ func (c *DeploymentCollection) Downscale(k *k8s.K8sCluster) (*types.CommandResul var deletedObjects []k8s2.ObjectRef var mutex sync.Mutex - for _, d := range c.deployments { - if !d.checkInclusionForDeploy() { + for _, d := range c.Deployments { + if !d.CheckInclusionForDeploy() { continue } - for _, o := range d.objects { + for _, o := range d.Objects { o := o ref := o.GetK8sRef() - if isDownscaleDelete(o) { + if utils2.IsDownscaleDelete(o) { wp.Submit(func() error { apiWarnings, err := k.DeleteSingleObject(ref, k8s.DeleteOptions{IgnoreNotFoundError: true}) - dew.addApiWarnings(ref, apiWarnings) + dew.AddApiWarnings(ref, apiWarnings) if err != nil { return err } @@ -512,7 +514,7 @@ func (c *DeploymentCollection) Downscale(k *k8s.K8sCluster) (*types.CommandResul } else { wp.Submit(func() error { o2, err := c.doReplaceObject(k, dew, ref, func(remote *uo.UnstructuredObject) (*uo.UnstructuredObject, error) { - return downscaleObject(remote, o) + return utils2.DownscaleObject(remote, o) }) if err != nil { return err @@ -531,15 +533,15 @@ func (c *DeploymentCollection) Downscale(k *k8s.K8sCluster) (*types.CommandResul return nil, err } - du := NewDiffUtil(dew, c.deployments, c.remoteObjects, appliedObjects) - du.diff(k) + du := utils2.NewDiffUtil(dew, c.Deployments, c.remoteObjects, appliedObjects) + du.Diff(k) return &types.CommandResult{ - NewObjects: du.newObjects, - ChangedObjects: du.changedObjects, + NewObjects: du.NewObjects, + ChangedObjects: du.ChangedObjects, DeletedObjects: deletedObjects, - Errors: dew.getErrorsList(), - Warnings: dew.getWarningsList(), + Errors: dew.GetErrorsList(), + Warnings: dew.GetWarningsList(), SeenImages: c.images.seenImages, }, nil } @@ -547,23 +549,23 @@ func (c *DeploymentCollection) Downscale(k *k8s.K8sCluster) (*types.CommandResul func (c *DeploymentCollection) Validate(k *k8s.K8sCluster) *types.ValidateResult { var result types.ValidateResult - dew := NewDeploymentErrorsAndWarnings() + dew := utils2.NewDeploymentErrorsAndWarnings() - a := newApplyUtil(dew, c, k, applyUtilOptions{}) - h := hooksUtil{a: a} - for _, d := range c.deployments { - if !d.checkInclusionForDeploy() { + a := utils2.NewApplyUtil(dew, c, k, utils2.ApplyUtilOptions{}) + h := utils2.NewHooksUtil(a) + for _, d := range c.Deployments { + if !d.CheckInclusionForDeploy() { continue } - for _, o := range d.objects { - hook := h.getHook(o) - if hook != nil && !hook.isPersistent() { + for _, o := range d.Objects { + hook := h.GetHook(o) + if hook != nil && !hook.IsPersistent() { continue } ref := o.GetK8sRef() - remoteObject := c.getRemoteObject(ref) + remoteObject := c.GetRemoteObject(ref) if remoteObject == nil { result.Errors = append(result.Errors, types.DeploymentError{Ref: ref, Error: "object not found"}) continue @@ -575,30 +577,30 @@ func (c *DeploymentCollection) Validate(k *k8s.K8sCluster) *types.ValidateResult } } - result.Warnings = append(result.Warnings, dew.getWarningsList()...) - result.Errors = append(result.Errors, dew.getErrorsList()...) + result.Warnings = append(result.Warnings, dew.GetWarningsList()...) + result.Errors = append(result.Errors, dew.GetErrorsList()...) return &result } func (c *DeploymentCollection) FindDeleteObjects(k *k8s.K8sCluster) ([]k8s2.ObjectRef, error) { - return FindObjectsForDelete(k, c.getFilteredRemoteObjects(c.inclusion), c.inclusion.HasType("tags"), nil) + return utils2.FindObjectsForDelete(k, c.getFilteredRemoteObjects(c.inclusion), c.inclusion.HasType("tags"), nil) } func (c *DeploymentCollection) FindOrphanObjects(k *k8s.K8sCluster) ([]k8s2.ObjectRef, error) { log.Infof("Searching for orphan objects") - return FindObjectsForDelete(k, c.getFilteredRemoteObjects(c.inclusion), c.inclusion.HasType("tags"), c.localObjectRefs()) + return utils2.FindObjectsForDelete(k, c.getFilteredRemoteObjects(c.inclusion), c.inclusion.HasType("tags"), c.localObjectRefs()) } -func (c *DeploymentCollection) doReplaceObject(k *k8s.K8sCluster, dew *deploymentErrorsAndWarnings, ref k8s2.ObjectRef, callback func(o *uo.UnstructuredObject) (*uo.UnstructuredObject, error)) (*uo.UnstructuredObject, error) { +func (c *DeploymentCollection) doReplaceObject(k *k8s.K8sCluster, dew *utils2.DeploymentErrorsAndWarnings, ref k8s2.ObjectRef, callback func(o *uo.UnstructuredObject) (*uo.UnstructuredObject, error)) (*uo.UnstructuredObject, error) { firstCall := true for true { var remote *uo.UnstructuredObject if firstCall { - remote = c.getRemoteObject(ref) + remote = c.GetRemoteObject(ref) } else { o2, apiWarnings, err := k.GetSingleObject(ref) - dew.addApiWarnings(ref, apiWarnings) + dew.AddApiWarnings(ref, apiWarnings) if err != nil && !errors.IsNotFound(err) { return nil, err } @@ -619,7 +621,7 @@ func (c *DeploymentCollection) doReplaceObject(k *k8s.K8sCluster, dew *deploymen } result, apiWarnings, err := k.UpdateObject(modified, k8s.UpdateOptions{}) - dew.addApiWarnings(ref, apiWarnings) + dew.AddApiWarnings(ref, apiWarnings) if err != nil { if errors.IsConflict(err) { log.Warningf("Conflict while patching %s. Retrying...", ref.String()) @@ -669,8 +671,8 @@ func (c *DeploymentCollection) getInclusionEntries(o *uo.UnstructuredObject) []u func (c *DeploymentCollection) FindRenderedImages() map[k8s2.ObjectRef][]string { ret := make(map[k8s2.ObjectRef][]string) - for _, d := range c.deployments { - for _, o := range d.objects { + for _, d := range c.Deployments { + for _, o := range d.Objects { ref := o.GetK8sRef() l, ok, _ := o.GetNestedObjectList("spec", "template", "spec", "containers") if !ok { diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index d73b63d9c..a73529544 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -21,37 +21,37 @@ import ( const sealmeExt = ".sealme" -type deploymentItem struct { - project *DeploymentProject +type DeploymentItem struct { + Project *DeploymentProject collection *DeploymentCollection - config *types.DeploymentItemConfig + Config *types.DeploymentItemConfig dir *string index int - objects []*uo.UnstructuredObject + Objects []*uo.UnstructuredObject relProjectDir string relToRootItemDir string - relToProjectItemDir string + RelToProjectItemDir string relRenderedDir string renderedDir string renderedYamlPath string } -func NewDeploymentItem(project *DeploymentProject, collection *DeploymentCollection, config *types.DeploymentItemConfig, dir *string, index int) (*deploymentItem, error) { - di := &deploymentItem{ - project: project, +func NewDeploymentItem(project *DeploymentProject, collection *DeploymentCollection, config *types.DeploymentItemConfig, dir *string, index int) (*DeploymentItem, error) { + di := &DeploymentItem{ + Project: project, collection: collection, - config: config, + Config: config, dir: dir, index: index, } var err error - rootProject := di.project.getRootProject() + rootProject := di.Project.getRootProject() - di.relProjectDir, err = filepath.Rel(rootProject.dir, di.project.dir) + di.relProjectDir, err = filepath.Rel(rootProject.dir, di.Project.dir) if err != nil { return nil, err } @@ -62,7 +62,7 @@ func NewDeploymentItem(project *DeploymentProject, collection *DeploymentCollect return nil, err } - di.relToProjectItemDir, err = filepath.Rel(di.relProjectDir, di.relToRootItemDir) + di.RelToProjectItemDir, err = filepath.Rel(di.relProjectDir, di.relToRootItemDir) if err != nil { return nil, err } @@ -78,8 +78,8 @@ func NewDeploymentItem(project *DeploymentProject, collection *DeploymentCollect return di, nil } -func (di *deploymentItem) getCommonLabels() map[string]string { - l := di.project.getCommonLabels() +func (di *DeploymentItem) getCommonLabels() map[string]string { + l := di.Project.getCommonLabels() i := 0 for _, t := range di.getTags().ListKeys() { l[fmt.Sprintf("kluctl.io/tag-%d", i)] = t @@ -88,37 +88,37 @@ func (di *deploymentItem) getCommonLabels() map[string]string { return l } -func (di *deploymentItem) getCommonAnnotations() map[string]string { +func (di *DeploymentItem) getCommonAnnotations() map[string]string { // TODO change it to kluctl.io/deployment_dir a := map[string]string{ "kluctl.io/kustomize_dir": strings.ReplaceAll(di.relToRootItemDir, string(os.PathSeparator), "/"), } - if di.config.SkipDeleteIfTags != nil && *di.config.SkipDeleteIfTags { + if di.Config.SkipDeleteIfTags != nil && *di.Config.SkipDeleteIfTags { a["kluctl.io/skip-delete-if-tags"] = "true" } return a } -func (di *deploymentItem) render(k *k8s.K8sCluster, wp *utils.WorkerPoolWithErrors) error { +func (di *DeploymentItem) render(k *k8s.K8sCluster, wp *utils.WorkerPoolWithErrors) error { if di.dir == nil { return nil } - rootDir := di.project.getRootProject().dir + rootDir := di.Project.getRootProject().dir err := os.MkdirAll(di.renderedDir, 0o700) if err != nil { return err } - varsCtx := di.project.VarsCtx.Copy() - err = varsCtx.LoadVarsList(k, di.project.getRenderSearchDirs(), di.config.Vars) + varsCtx := di.Project.VarsCtx.Copy() + err = varsCtx.LoadVarsList(k, di.Project.getRenderSearchDirs(), di.Config.Vars) if err != nil { return err } var excludePatterns []string - excludePatterns = append(excludePatterns, di.project.config.TemplateExcludes...) + excludePatterns = append(excludePatterns, di.Project.config.TemplateExcludes...) err = filepath.WalkDir(*di.dir, func(p string, d fs.DirEntry, err error) error { if d.IsDir() { relDir, err := filepath.Rel(*di.dir, p) @@ -127,7 +127,7 @@ func (di *deploymentItem) render(k *k8s.K8sCluster, wp *utils.WorkerPoolWithErro } if utils.IsFile(filepath.Join(p, "helm-chart.yml")) { // never try to render helm charts - ep := filepath.Join(di.relToProjectItemDir, relDir, "charts/**") + ep := filepath.Join(di.RelToProjectItemDir, relDir, "charts/**") ep = strings.ReplaceAll(ep, string(os.PathSeparator), "/") excludePatterns = append(excludePatterns, ep) return filepath.SkipDir @@ -145,13 +145,13 @@ func (di *deploymentItem) render(k *k8s.K8sCluster, wp *utils.WorkerPoolWithErro } wp.Submit(func() error { - return varsCtx.RenderDirectory(rootDir, di.project.getRenderSearchDirs(), di.relProjectDir, excludePatterns, di.relToProjectItemDir, di.renderedDir) + return varsCtx.RenderDirectory(rootDir, di.Project.getRenderSearchDirs(), di.relProjectDir, excludePatterns, di.RelToProjectItemDir, di.renderedDir) }) return nil } -func (di *deploymentItem) renderHelmCharts(k *k8s.K8sCluster, wp *utils.WorkerPoolWithErrors) error { +func (di *DeploymentItem) renderHelmCharts(k *k8s.K8sCluster, wp *utils.WorkerPoolWithErrors) error { if di.dir == nil { return nil } @@ -176,15 +176,15 @@ func (di *deploymentItem) renderHelmCharts(k *k8s.K8sCluster, wp *utils.WorkerPo return nil } -func (di *deploymentItem) resolveSealedSecrets() error { +func (di *DeploymentItem) resolveSealedSecrets() error { if di.dir == nil { return nil } // TODO check for bootstrap - sealedSecretsDir := di.project.getSealedSecretsDir() - baseSourcePath := di.project.sealedSecretsDir + sealedSecretsDir := di.Project.getSealedSecretsDir() + baseSourcePath := di.Project.sealedSecretsDir y, err := uo.FromFile(filepath.Join(di.renderedDir, "kustomization.yml")) if err != nil { @@ -205,7 +205,7 @@ func (di *deploymentItem) resolveSealedSecrets() error { } fname := filepath.Base(p) - baseError := fmt.Sprintf("failed to resolve SealedSecret %s", filepath.Clean(filepath.Join(di.project.dir, resource))) + baseError := fmt.Sprintf("failed to resolve SealedSecret %s", filepath.Clean(filepath.Join(di.Project.dir, resource))) if sealedSecretsDir == nil { return fmt.Errorf("%s. Sealed secrets dir could not be determined", baseError) } @@ -226,15 +226,15 @@ func (di *deploymentItem) resolveSealedSecrets() error { return nil } -func (di *deploymentItem) getTags() *utils.OrderedMap { - tags := di.project.getTags() - for _, t := range di.config.Tags { +func (di *DeploymentItem) getTags() *utils.OrderedMap { + tags := di.Project.getTags() + for _, t := range di.Config.Tags { tags.Set(t, true) } return tags } -func (di *deploymentItem) buildInclusionEntries() []utils.InclusionEntry { +func (di *DeploymentItem) buildInclusionEntries() []utils.InclusionEntry { var values []utils.InclusionEntry for _, t := range di.getTags().ListKeys() { values = append(values, utils.InclusionEntry{Type: "tag", Value: t}) @@ -246,30 +246,30 @@ func (di *deploymentItem) buildInclusionEntries() []utils.InclusionEntry { return values } -func (di *deploymentItem) checkInclusionForDeploy() bool { +func (di *DeploymentItem) CheckInclusionForDeploy() bool { if di.collection.inclusion == nil { return true } - if di.config.OnlyRender != nil && *di.config.OnlyRender { + if di.Config.OnlyRender != nil && *di.Config.OnlyRender { return true } - if di.config.AlwaysDeploy != nil && *di.config.AlwaysDeploy { + if di.Config.AlwaysDeploy != nil && *di.Config.AlwaysDeploy { return true } values := di.buildInclusionEntries() return di.collection.inclusion.CheckIncluded(values, false) } -func (di *deploymentItem) checkInclusionForDelete() bool { +func (di *DeploymentItem) checkInclusionForDelete() bool { if di.collection.inclusion == nil { return true } - skipDeleteIfTags := di.config.SkipDeleteIfTags != nil && *di.config.SkipDeleteIfTags + skipDeleteIfTags := di.Config.SkipDeleteIfTags != nil && *di.Config.SkipDeleteIfTags values := di.buildInclusionEntries() return di.collection.inclusion.CheckIncluded(values, skipDeleteIfTags) } -func (di *deploymentItem) prepareKustomizationYaml() error { +func (di *DeploymentItem) prepareKustomizationYaml() error { if di.dir == nil { return nil } @@ -285,7 +285,7 @@ func (di *deploymentItem) prepareKustomizationYaml() error { return err } - overrideNamespace := di.project.getOverrideNamespace() + overrideNamespace := di.Project.getOverrideNamespace() if overrideNamespace != nil { if _, ok := kustomizeYaml["namespace"]; !ok { kustomizeYaml["namespace"] = *overrideNamespace @@ -300,7 +300,7 @@ func (di *deploymentItem) prepareKustomizationYaml() error { return nil } -func (di *deploymentItem) buildKustomize() error { +func (di *DeploymentItem) buildKustomize() error { if di.dir == nil { return nil } @@ -338,7 +338,7 @@ func (di *deploymentItem) buildKustomize() error { return nil } -func (di *deploymentItem) postprocessAndLoadObjects(k *k8s.K8sCluster) error { +func (di *DeploymentItem) postprocessAndLoadObjects(k *k8s.K8sCluster) error { if di.dir == nil { return nil } @@ -348,16 +348,16 @@ func (di *deploymentItem) postprocessAndLoadObjects(k *k8s.K8sCluster) error { return err } - di.objects = []*uo.UnstructuredObject{} + di.Objects = []*uo.UnstructuredObject{} for _, o := range objects { m, ok := o.(map[string]interface{}) if !ok { return fmt.Errorf("object is not a map") } - di.objects = append(di.objects, uo.FromMap(m)) + di.Objects = append(di.Objects, uo.FromMap(m)) } - for _, o := range di.objects { + for _, o := range di.Objects { if k != nil { k.RemoveNamespaceIfNeeded(o) } diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go index 35cbfe8d4..162e28e16 100644 --- a/pkg/deployment/deployment_project.go +++ b/pkg/deployment/deployment_project.go @@ -294,7 +294,7 @@ func (p *DeploymentProject) getTags() *utils.OrderedMap { return &tags } -func (p *DeploymentProject) getIgnoreForDiffs(ignoreTags, ignoreLabels, ignoreAnnotations bool) []*types.IgnoreForDiffItemConfig { +func (p *DeploymentProject) GetIgnoreForDiffs(ignoreTags, ignoreLabels, ignoreAnnotations bool) []*types.IgnoreForDiffItemConfig { var ret []*types.IgnoreForDiffItemConfig for _, e := range p.getParents() { ret = append(ret, e.p.config.IgnoreForDiff...) diff --git a/pkg/deployment/apply_utils.go b/pkg/deployment/utils/apply_utils.go similarity index 67% rename from pkg/deployment/apply_utils.go rename to pkg/deployment/utils/apply_utils.go index 50e0dc422..d36e5cd00 100644 --- a/pkg/deployment/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -1,8 +1,9 @@ -package deployment +package utils import ( errors2 "errors" "fmt" + "github.com/codablock/kluctl/pkg/deployment" "github.com/codablock/kluctl/pkg/diff" "github.com/codablock/kluctl/pkg/k8s" "github.com/codablock/kluctl/pkg/types" @@ -17,22 +18,22 @@ import ( "time" ) -type applyUtilOptions struct { - forceApply bool - replaceOnError bool - forceReplaceOnError bool - dryRun bool - abortOnError bool - hookTimeout time.Duration +type ApplyUtilOptions struct { + ForceApply bool + ReplaceOnError bool + ForceReplaceOnError bool + DryRun bool + AbortOnError bool + HookTimeout time.Duration } -type applyUtil struct { - dew *deploymentErrorsAndWarnings - deploymentCollection *DeploymentCollection +type ApplyUtil struct { + dew *DeploymentErrorsAndWarnings + deploymentCollection *deployment.DeploymentCollection k *k8s.K8sCluster - o applyUtilOptions + o ApplyUtilOptions - appliedObjects map[k8s2.ObjectRef]*uo.UnstructuredObject + AppliedObjects map[k8s2.ObjectRef]*uo.UnstructuredObject appliedHookObjects map[k8s2.ObjectRef]*uo.UnstructuredObject deletedObjects map[k8s2.ObjectRef]bool deletedHookObjects map[k8s2.ObjectRef]bool @@ -41,13 +42,13 @@ type applyUtil struct { mutex sync.Mutex } -func newApplyUtil(dew *deploymentErrorsAndWarnings, deploymentCollection *DeploymentCollection, k *k8s.K8sCluster, o applyUtilOptions) *applyUtil { - return &applyUtil{ +func NewApplyUtil(dew *DeploymentErrorsAndWarnings, deploymentCollection *deployment.DeploymentCollection, k *k8s.K8sCluster, o ApplyUtilOptions) *ApplyUtil { + return &ApplyUtil{ dew: dew, deploymentCollection: deploymentCollection, k: k, o: o, - appliedObjects: map[k8s2.ObjectRef]*uo.UnstructuredObject{}, + AppliedObjects: map[k8s2.ObjectRef]*uo.UnstructuredObject{}, appliedHookObjects: map[k8s2.ObjectRef]*uo.UnstructuredObject{}, deletedObjects: map[k8s2.ObjectRef]bool{}, deletedHookObjects: map[k8s2.ObjectRef]bool{}, @@ -55,7 +56,7 @@ func newApplyUtil(dew *deploymentErrorsAndWarnings, deploymentCollection *Deploy } } -func (a *applyUtil) handleResult(appliedObject *uo.UnstructuredObject, hook bool) { +func (a *ApplyUtil) handleResult(appliedObject *uo.UnstructuredObject, hook bool) { a.mutex.Lock() defer a.mutex.Unlock() @@ -63,42 +64,42 @@ func (a *applyUtil) handleResult(appliedObject *uo.UnstructuredObject, hook bool if hook { a.appliedHookObjects[ref] = appliedObject } else { - a.appliedObjects[ref] = appliedObject + a.AppliedObjects[ref] = appliedObject } } -func (a *applyUtil) handleApiWarnings(ref k8s2.ObjectRef, warnings []k8s.ApiWarning) { - a.dew.addApiWarnings(ref, warnings) +func (a *ApplyUtil) handleApiWarnings(ref k8s2.ObjectRef, warnings []k8s.ApiWarning) { + a.dew.AddApiWarnings(ref, warnings) } -func (a *applyUtil) handleWarning(ref k8s2.ObjectRef, warning error) { - a.dew.addWarning(ref, warning) +func (a *ApplyUtil) HandleWarning(ref k8s2.ObjectRef, warning error) { + a.dew.AddWarning(ref, warning) } -func (a *applyUtil) handleError(ref k8s2.ObjectRef, err error) { +func (a *ApplyUtil) HandleError(ref k8s2.ObjectRef, err error) { a.mutex.Lock() defer a.mutex.Unlock() - if a.o.abortOnError { + if a.o.AbortOnError { a.abortSignal = true } - a.dew.addError(ref, err) + a.dew.AddError(ref, err) } -func (a *applyUtil) hadError(ref k8s2.ObjectRef) bool { - return a.dew.hadError(ref) +func (a *ApplyUtil) HadError(ref k8s2.ObjectRef) bool { + return a.dew.HadError(ref) } -func (a *applyUtil) deleteObject(ref k8s2.ObjectRef, hook bool) bool { +func (a *ApplyUtil) DeleteObject(ref k8s2.ObjectRef, hook bool) bool { o := k8s.DeleteOptions{ - ForceDryRun: a.o.dryRun, + ForceDryRun: a.o.DryRun, } apiWarnings, err := a.k.DeleteSingleObject(ref, o) a.handleApiWarnings(ref, apiWarnings) if err != nil { if !errors.IsNotFound(err) { - a.handleError(ref, err) + a.HandleError(ref, err) } return false } @@ -113,29 +114,29 @@ func (a *applyUtil) deleteObject(ref k8s2.ObjectRef, hook bool) bool { return true } -func (a *applyUtil) retryApplyForceReplace(x *uo.UnstructuredObject, hook bool, applyError error) { +func (a *ApplyUtil) retryApplyForceReplace(x *uo.UnstructuredObject, hook bool, applyError error) { ref := x.GetK8sRef() log2 := log.WithField("ref", ref) - if !a.o.forceReplaceOnError { - a.handleError(ref, applyError) + if !a.o.ForceReplaceOnError { + a.HandleError(ref, applyError) return } log2.Warningf("Patching failed, retrying by deleting and re-applying") - if !a.deleteObject(ref, hook) { + if !a.DeleteObject(ref, hook) { return } - if !a.o.dryRun { + if !a.o.DryRun { o := k8s.PatchOptions{ - ForceDryRun: a.o.dryRun, + ForceDryRun: a.o.DryRun, } r, apiWarnings, err := a.k.PatchObject(x, o) a.handleApiWarnings(ref, apiWarnings) if err != nil { - a.handleError(ref, err) + a.HandleError(ref, err) return } a.handleResult(r, hook) @@ -144,12 +145,12 @@ func (a *applyUtil) retryApplyForceReplace(x *uo.UnstructuredObject, hook bool, } } -func (a *applyUtil) retryApplyWithReplace(x *uo.UnstructuredObject, hook bool, remoteObject *uo.UnstructuredObject, applyError error) { +func (a *ApplyUtil) retryApplyWithReplace(x *uo.UnstructuredObject, hook bool, remoteObject *uo.UnstructuredObject, applyError error) { ref := x.GetK8sRef() log2 := log.WithField("ref", ref) - if !a.o.replaceOnError || remoteObject == nil { - a.handleError(ref, applyError) + if !a.o.ReplaceOnError || remoteObject == nil { + a.HandleError(ref, applyError) return } @@ -160,7 +161,7 @@ func (a *applyUtil) retryApplyWithReplace(x *uo.UnstructuredObject, hook bool, r x2.SetK8sResourceVersion(rv) o := k8s.UpdateOptions{ - ForceDryRun: a.o.dryRun, + ForceDryRun: a.o.DryRun, } r, apiWarnings, err := a.k.UpdateObject(x, o) @@ -172,29 +173,29 @@ func (a *applyUtil) retryApplyWithReplace(x *uo.UnstructuredObject, hook bool, r a.handleResult(r, hook) } -func (a *applyUtil) retryApplyWithConflicts(x *uo.UnstructuredObject, hook bool, remoteObject *uo.UnstructuredObject, applyError error) { +func (a *ApplyUtil) retryApplyWithConflicts(x *uo.UnstructuredObject, hook bool, remoteObject *uo.UnstructuredObject, applyError error) { ref := x.GetK8sRef() if remoteObject == nil { - a.handleError(ref, applyError) + a.HandleError(ref, applyError) return } var x2 *uo.UnstructuredObject - if !a.o.forceApply { + if !a.o.ForceApply { var statusError *errors.StatusError if !errors2.As(applyError, &statusError) { - a.handleError(ref, applyError) + a.HandleError(ref, applyError) return } x3, lostOwnership, err := diff.ResolveFieldManagerConflicts(x, remoteObject, statusError.ErrStatus) if err != nil { - a.handleError(ref, err) + a.HandleError(ref, err) return } for _, lo := range lostOwnership { - a.dew.addWarning(ref, fmt.Errorf("%s. Not updating field '%s' as we lost field ownership", lo.Message, lo.Field)) + a.dew.AddWarning(ref, fmt.Errorf("%s. Not updating field '%s' as we lost field ownership", lo.Message, lo.Field)) } x2 = x3 } else { @@ -202,28 +203,28 @@ func (a *applyUtil) retryApplyWithConflicts(x *uo.UnstructuredObject, hook bool, } options := k8s.PatchOptions{ - ForceDryRun: a.o.dryRun, + ForceDryRun: a.o.DryRun, ForceApply: true, } r, apiWarnings, err := a.k.PatchObject(x2, options) a.handleApiWarnings(ref, apiWarnings) if err != nil { // We didn't manage to solve it, better to abort (and not retry with replace!) - a.handleError(ref, err) + a.HandleError(ref, err) return } a.handleResult(r, hook) } -func (a *applyUtil) applyObject(x *uo.UnstructuredObject, replaced bool, hook bool) { +func (a *ApplyUtil) ApplyObject(x *uo.UnstructuredObject, replaced bool, hook bool) { ref := x.GetK8sRef() log2 := log.WithField("ref", ref) log2.Debugf("applying object") x = a.k.FixObjectForPatch(x) - remoteObject := a.deploymentCollection.getRemoteObject(ref) + remoteObject := a.deploymentCollection.GetRemoteObject(ref) - if a.o.dryRun && replaced && remoteObject != nil { + if a.o.DryRun && replaced && remoteObject != nil { // Let's simulate that this object was deleted in dry-run mode. If we'd actually try a dry-run apply with // this object, it might fail as it is expected to not exist. a.handleResult(x, hook) @@ -231,7 +232,7 @@ func (a *applyUtil) applyObject(x *uo.UnstructuredObject, replaced bool, hook bo } options := k8s.PatchOptions{ - ForceDryRun: a.o.dryRun, + ForceDryRun: a.o.DryRun, } r, apiWarnings, err := a.k.PatchObject(x, options) retry, err := a.handleNewCRDs(r, err) @@ -242,17 +243,17 @@ func (a *applyUtil) applyObject(x *uo.UnstructuredObject, replaced bool, hook bo if err == nil { a.handleResult(r, hook) } else if meta.IsNoMatchError(err) { - a.handleError(ref, err) + a.HandleError(ref, err) } else if errors.IsConflict(err) { a.retryApplyWithConflicts(x, hook, remoteObject, err) } else if errors.IsInternalError(err) { - a.handleError(ref, err) + a.HandleError(ref, err) } else { a.retryApplyWithReplace(x, hook, remoteObject, err) } } -func (a *applyUtil) handleNewCRDs(x *uo.UnstructuredObject, err error) (bool, error) { +func (a *ApplyUtil) handleNewCRDs(x *uo.UnstructuredObject, err error) (bool, error) { if err != nil && meta.IsNoMatchError(err) { // maybe this was a resource for which the CRD was only deployed recently, so we should do rediscovery and then // retry the patch @@ -280,8 +281,8 @@ func (a *applyUtil) handleNewCRDs(x *uo.UnstructuredObject, err error) (bool, er return false, err } -func (a *applyUtil) waitHook(ref k8s2.ObjectRef) bool { - if a.o.dryRun { +func (a *ApplyUtil) WaitHook(ref k8s2.ObjectRef) bool { + if a.o.DryRun { return true } @@ -299,10 +300,10 @@ func (a *applyUtil) waitHook(ref k8s2.ObjectRef) bool { if didLog { log2.Warningf("Cancelled waiting for hook as it disappeared while waiting for it") } - a.handleError(ref, fmt.Errorf("object disappeared while waiting for it to become ready")) + a.HandleError(ref, fmt.Errorf("object disappeared while waiting for it to become ready")) return false } - a.handleError(ref, err) + a.HandleError(ref, err) return false } v := validation.ValidateObject(o, false) @@ -317,15 +318,15 @@ func (a *applyUtil) waitHook(ref k8s2.ObjectRef) bool { log2.Warningf("Cancelled waiting for hook due to errors") } for _, e := range v.Errors { - a.handleError(ref, fmt.Errorf(e.Error)) + a.HandleError(ref, fmt.Errorf(e.Error)) } return false } - if a.o.hookTimeout != 0 && time.Now().Sub(startTime) >= a.o.hookTimeout { + if a.o.HookTimeout != 0 && time.Now().Sub(startTime) >= a.o.HookTimeout { err := fmt.Errorf("timed out while waiting for hook") log2.Warningf(err.Error()) - a.handleError(ref, err) + a.HandleError(ref, err) return false } @@ -343,14 +344,14 @@ func (a *applyUtil) waitHook(ref k8s2.ObjectRef) bool { return false } -func (a *applyUtil) applyDeploymentItem(d *deploymentItem) { - if !d.checkInclusionForDeploy() { - a.doLog(d, log.InfoLevel, "Skipping") +func (a *ApplyUtil) applyDeploymentItem(d *deployment.DeploymentItem) { + if !d.CheckInclusionForDeploy() { + a.DoLog(d, log.InfoLevel, "Skipping") return } var toDelete []k8s2.ObjectRef - for _, x := range d.config.DeleteObjects { + for _, x := range d.Config.DeleteObjects { for _, gvk := range a.k.GetGVKs(x.Group, x.Version, x.Kind) { ref := k8s2.ObjectRef{ GVK: gvk, @@ -363,62 +364,62 @@ func (a *applyUtil) applyDeploymentItem(d *deploymentItem) { if len(toDelete) != 0 { log.Infof("Deleting %d objects", len(toDelete)) for _, ref := range toDelete { - a.deleteObject(ref, false) + a.DeleteObject(ref, false) } } initialDeploy := true - for _, o := range d.objects { - if a.deploymentCollection.getRemoteObject(o.GetK8sRef()) != nil { + for _, o := range d.Objects { + if a.deploymentCollection.GetRemoteObject(o.GetK8sRef()) != nil { initialDeploy = false } } - h := hooksUtil{a: a} + h := HooksUtil{a: a} if initialDeploy { - h.runHooks(d, []string{"pre-deploy-initial", "pre-deploy"}) + h.RunHooks(d, []string{"pre-deploy-initial", "pre-deploy"}) } else { - h.runHooks(d, []string{"pre-deploy-upgrade", "pre-deploy"}) + h.RunHooks(d, []string{"pre-deploy-upgrade", "pre-deploy"}) } var applyObjects []*uo.UnstructuredObject - for _, o := range d.objects { - if h.getHook(o) != nil { + for _, o := range d.Objects { + if h.GetHook(o) != nil { continue } applyObjects = append(applyObjects, o) } if len(applyObjects) != 0 { - a.doLog(d, log.InfoLevel, "Applying %d objects", len(applyObjects)) + a.DoLog(d, log.InfoLevel, "Applying %d objects", len(applyObjects)) } startTime := time.Now() didLog := false for i, o := range applyObjects { - a.applyObject(o, false, false) + a.ApplyObject(o, false, false) if time.Now().Sub(startTime) >= 10*time.Second || (didLog && i == len(applyObjects)-1) { - a.doLog(d, log.InfoLevel, "...applied %d of %d objects", i+1, len(applyObjects)) + a.DoLog(d, log.InfoLevel, "...applied %d of %d objects", i+1, len(applyObjects)) startTime = time.Now() didLog = true } } if initialDeploy { - h.runHooks(d, []string{"post-deploy-initial", "post-deploy"}) + h.RunHooks(d, []string{"post-deploy-initial", "post-deploy"}) } else { - h.runHooks(d, []string{"post-deploy-upgrade", "post-deploy"}) + h.RunHooks(d, []string{"post-deploy-upgrade", "post-deploy"}) } } -func (a *applyUtil) applyDeployments() { +func (a *ApplyUtil) ApplyDeployments() { log.Infof("Running server-side apply for all objects") wp := utils.NewDebuggerAwareWorkerPool(16) defer wp.StopWait(false) previousWasBarrier := false - for _, d_ := range a.deploymentCollection.deployments { + for _, d_ := range a.deploymentCollection.Deployments { d := d_ if a.abortSignal { break @@ -428,7 +429,7 @@ func (a *applyUtil) applyDeployments() { _ = wp.StopWait(true) } - previousWasBarrier = d.config.Barrier != nil && *d.config.Barrier + previousWasBarrier = d.Config.Barrier != nil && *d.Config.Barrier wp.Submit(func() error { a.applyDeploymentItem(d) @@ -438,12 +439,12 @@ func (a *applyUtil) applyDeployments() { _ = wp.StopWait(false) } -func (a *applyUtil) doLog(d *deploymentItem, level log.Level, s string, f ...interface{}) { - s = fmt.Sprintf("%s: %s", d.relToProjectItemDir, fmt.Sprintf(s, f...)) +func (a *ApplyUtil) DoLog(d *deployment.DeploymentItem, level log.Level, s string, f ...interface{}) { + s = fmt.Sprintf("%s: %s", d.RelToProjectItemDir, fmt.Sprintf(s, f...)) log.StandardLogger().Logf(level, s) } -func (a *applyUtil) getDeletedObjectsList() []k8s2.ObjectRef { +func (a *ApplyUtil) GetDeletedObjectsList() []k8s2.ObjectRef { var ret []k8s2.ObjectRef for ref := range a.deletedObjects { ret = append(ret, ref) @@ -451,7 +452,7 @@ func (a *applyUtil) getDeletedObjectsList() []k8s2.ObjectRef { return ret } -func (a *applyUtil) getAppliedHookObjects() []*types.RefAndObject { +func (a *ApplyUtil) GetAppliedHookObjects() []*types.RefAndObject { var ret []*types.RefAndObject for _, o := range a.appliedHookObjects { ret = append(ret, &types.RefAndObject{ diff --git a/pkg/deployment/delete_utils.go b/pkg/deployment/utils/delete_utils.go similarity index 99% rename from pkg/deployment/delete_utils.go rename to pkg/deployment/utils/delete_utils.go index 89d85a110..f4f3be234 100644 --- a/pkg/deployment/delete_utils.go +++ b/pkg/deployment/utils/delete_utils.go @@ -1,4 +1,4 @@ -package deployment +package utils import ( "github.com/codablock/kluctl/pkg/k8s" diff --git a/pkg/deployment/diff_utils.go b/pkg/deployment/utils/diff_utils.go similarity index 75% rename from pkg/deployment/diff_utils.go rename to pkg/deployment/utils/diff_utils.go index a65c90f34..b9f164d56 100644 --- a/pkg/deployment/diff_utils.go +++ b/pkg/deployment/utils/diff_utils.go @@ -1,6 +1,7 @@ -package deployment +package utils import ( + "github.com/codablock/kluctl/pkg/deployment" "github.com/codablock/kluctl/pkg/diff" "github.com/codablock/kluctl/pkg/k8s" "github.com/codablock/kluctl/pkg/types" @@ -11,22 +12,22 @@ import ( ) type diffUtil struct { - dew *deploymentErrorsAndWarnings - deployments []*deploymentItem + dew *DeploymentErrorsAndWarnings + deployments []*deployment.DeploymentItem appliedObjects map[k8s2.ObjectRef]*uo.UnstructuredObject remoteObjects map[k8s2.ObjectRef]*uo.UnstructuredObject - ignoreTags bool - ignoreLabels bool - ignoreAnnotations bool + IgnoreTags bool + IgnoreLabels bool + IgnoreAnnotations bool remoteDiffObjects map[k8s2.ObjectRef]*uo.UnstructuredObject - newObjects []*types.RefAndObject - changedObjects []*types.ChangedObject - mutex sync.Mutex + NewObjects []*types.RefAndObject + ChangedObjects []*types.ChangedObject + mutex sync.Mutex } -func NewDiffUtil(dew *deploymentErrorsAndWarnings, deployments []*deploymentItem, remoteObjects map[k8s2.ObjectRef]*uo.UnstructuredObject, appliedObjects map[k8s2.ObjectRef]*uo.UnstructuredObject) *diffUtil { +func NewDiffUtil(dew *DeploymentErrorsAndWarnings, deployments []*deployment.DeploymentItem, remoteObjects map[k8s2.ObjectRef]*uo.UnstructuredObject, appliedObjects map[k8s2.ObjectRef]*uo.UnstructuredObject) *diffUtil { return &diffUtil{ dew: dew, deployments: deployments, @@ -35,18 +36,18 @@ func NewDiffUtil(dew *deploymentErrorsAndWarnings, deployments []*deploymentItem } } -func (u *diffUtil) diff(k *k8s.K8sCluster) { +func (u *diffUtil) Diff(k *k8s.K8sCluster) { var wg sync.WaitGroup u.calcRemoteObjectsForDiff() for _, d := range u.deployments { - if !d.checkInclusionForDeploy() { + if !d.CheckInclusionForDeploy() { continue } - ignoreForDiffs := d.project.getIgnoreForDiffs(u.ignoreTags, u.ignoreLabels, u.ignoreAnnotations) - for _, o := range d.objects { + ignoreForDiffs := d.Project.GetIgnoreForDiffs(u.IgnoreTags, u.IgnoreLabels, u.IgnoreAnnotations) + for _, o := range d.Objects { o := o ref := o.GetK8sRef() ao, ok := u.appliedObjects[ref] @@ -70,7 +71,7 @@ func (u *diffUtil) diffObject(k *k8s.K8sCluster, lo *uo.UnstructuredObject, ao * if ao != nil && ro == nil { u.mutex.Lock() defer u.mutex.Unlock() - u.newObjects = append(u.newObjects, &types.RefAndObject{ + u.NewObjects = append(u.NewObjects, &types.RefAndObject{ Ref: ao.GetK8sRef(), Object: ao, }) @@ -85,7 +86,7 @@ func (u *diffUtil) diffObject(k *k8s.K8sCluster, lo *uo.UnstructuredObject, ao * nro := diff.NormalizeObject(k, ro, ignoreForDiffs, lo) changes, err := diff.Diff(nro, nao) if err != nil { - u.dew.addError(lo.GetK8sRef(), err) + u.dew.AddError(lo.GetK8sRef(), err) return } if len(changes) == 0 { @@ -94,7 +95,7 @@ func (u *diffUtil) diffObject(k *k8s.K8sCluster, lo *uo.UnstructuredObject, ao * u.mutex.Lock() defer u.mutex.Unlock() - u.changedObjects = append(u.changedObjects, &types.ChangedObject{ + u.ChangedObjects = append(u.ChangedObjects, &types.ChangedObject{ Ref: lo.GetK8sRef(), NewObject: ao, OldObject: ro, diff --git a/pkg/deployment/downscale_utils.go b/pkg/deployment/utils/downscale_utils.go similarity index 93% rename from pkg/deployment/downscale_utils.go rename to pkg/deployment/utils/downscale_utils.go index 882879e4a..5497dcc9e 100644 --- a/pkg/deployment/downscale_utils.go +++ b/pkg/deployment/utils/downscale_utils.go @@ -1,4 +1,4 @@ -package deployment +package utils import ( "fmt" @@ -16,7 +16,7 @@ var ( downscaleAnnotationIgnore = "kluctl.io/downscale-ignore" ) -func isDownscaleDelete(o *uo.UnstructuredObject) bool { +func IsDownscaleDelete(o *uo.UnstructuredObject) bool { a, _ := o.GetK8sAnnotations()[downscaleAnnotationDelete] b, _ := strconv.ParseBool(a) return b @@ -28,7 +28,7 @@ func isDownscaleIgnore(o *uo.UnstructuredObject) bool { return b } -func downscaleObject(remote *uo.UnstructuredObject, local *uo.UnstructuredObject) (*uo.UnstructuredObject, error) { +func DownscaleObject(remote *uo.UnstructuredObject, local *uo.UnstructuredObject) (*uo.UnstructuredObject, error) { ret := remote if isDownscaleIgnore(local) { return ret, nil diff --git a/pkg/deployment/errors_holder.go b/pkg/deployment/utils/errors_holder.go similarity index 69% rename from pkg/deployment/errors_holder.go rename to pkg/deployment/utils/errors_holder.go index 0317349f7..d0a5e3de2 100644 --- a/pkg/deployment/errors_holder.go +++ b/pkg/deployment/utils/errors_holder.go @@ -1,4 +1,4 @@ -package deployment +package utils import ( "errors" @@ -10,26 +10,26 @@ import ( "sync" ) -type deploymentErrorsAndWarnings struct { +type DeploymentErrorsAndWarnings struct { errors map[k8s.ObjectRef]map[types.DeploymentError]bool warnings map[k8s.ObjectRef]map[types.DeploymentError]bool mutex sync.Mutex } -func NewDeploymentErrorsAndWarnings() *deploymentErrorsAndWarnings { - dew := &deploymentErrorsAndWarnings{} +func NewDeploymentErrorsAndWarnings() *DeploymentErrorsAndWarnings { + dew := &DeploymentErrorsAndWarnings{} dew.init() return dew } -func (dew *deploymentErrorsAndWarnings) init() { +func (dew *DeploymentErrorsAndWarnings) init() { dew.mutex.Lock() defer dew.mutex.Unlock() dew.warnings = map[k8s.ObjectRef]map[types.DeploymentError]bool{} dew.errors = map[k8s.ObjectRef]map[types.DeploymentError]bool{} } -func (dew *deploymentErrorsAndWarnings) addWarning(ref k8s.ObjectRef, warning error) { +func (dew *DeploymentErrorsAndWarnings) AddWarning(ref k8s.ObjectRef, warning error) { de := types.DeploymentError{ Ref: ref, Error: warning.Error(), @@ -44,7 +44,7 @@ func (dew *deploymentErrorsAndWarnings) addWarning(ref k8s.ObjectRef, warning er m[de] = true } -func (dew *deploymentErrorsAndWarnings) addError(ref k8s.ObjectRef, err error) { +func (dew *DeploymentErrorsAndWarnings) AddError(ref k8s.ObjectRef, err error) { de := types.DeploymentError{ Ref: ref, Error: err.Error(), @@ -59,20 +59,20 @@ func (dew *deploymentErrorsAndWarnings) addError(ref k8s.ObjectRef, err error) { m[de] = true } -func (dew *deploymentErrorsAndWarnings) addApiWarnings(ref k8s.ObjectRef, warnings []k8s2.ApiWarning) { +func (dew *DeploymentErrorsAndWarnings) AddApiWarnings(ref k8s.ObjectRef, warnings []k8s2.ApiWarning) { for _, w := range warnings { - dew.addWarning(ref, fmt.Errorf(w.Text)) + dew.AddWarning(ref, fmt.Errorf(w.Text)) } } -func (dew *deploymentErrorsAndWarnings) hadError(ref k8s.ObjectRef) bool { +func (dew *DeploymentErrorsAndWarnings) HadError(ref k8s.ObjectRef) bool { dew.mutex.Lock() defer dew.mutex.Unlock() _, ok := dew.errors[ref] return ok } -func (dew *deploymentErrorsAndWarnings) getErrorsList() []types.DeploymentError { +func (dew *DeploymentErrorsAndWarnings) GetErrorsList() []types.DeploymentError { dew.mutex.Lock() defer dew.mutex.Unlock() var ret []types.DeploymentError @@ -84,7 +84,7 @@ func (dew *deploymentErrorsAndWarnings) getErrorsList() []types.DeploymentError return ret } -func (dew *deploymentErrorsAndWarnings) getWarningsList() []types.DeploymentError { +func (dew *DeploymentErrorsAndWarnings) GetWarningsList() []types.DeploymentError { dew.mutex.Lock() defer dew.mutex.Unlock() var ret []types.DeploymentError @@ -96,15 +96,15 @@ func (dew *deploymentErrorsAndWarnings) getWarningsList() []types.DeploymentErro return ret } -func (dew *deploymentErrorsAndWarnings) getPlainErrorsList() []error { +func (dew *DeploymentErrorsAndWarnings) getPlainErrorsList() []error { var ret []error - for _, e := range dew.getErrorsList() { + for _, e := range dew.GetErrorsList() { ret = append(ret, errors.New(e.Error)) } return ret } -func (dew *deploymentErrorsAndWarnings) getMultiError() error { +func (dew *DeploymentErrorsAndWarnings) GetMultiError() error { l := dew.getPlainErrorsList() if len(l) == 0 { return nil diff --git a/pkg/deployment/hooks_util.go b/pkg/deployment/utils/hooks_util.go similarity index 84% rename from pkg/deployment/hooks_util.go rename to pkg/deployment/utils/hooks_util.go index 6b7b03319..5922af2d6 100644 --- a/pkg/deployment/hooks_util.go +++ b/pkg/deployment/utils/hooks_util.go @@ -1,7 +1,8 @@ -package deployment +package utils import ( "fmt" + "github.com/codablock/kluctl/pkg/deployment" "github.com/codablock/kluctl/pkg/types/k8s" "github.com/codablock/kluctl/pkg/utils" "github.com/codablock/kluctl/pkg/utils/uo" @@ -31,8 +32,12 @@ var supportedHelmHooks = []string{ "pre-rollback", "post-rollback", } -type hooksUtil struct { - a *applyUtil +type HooksUtil struct { + a *ApplyUtil +} + +func NewHooksUtil(a *ApplyUtil) *HooksUtil { + return &HooksUtil{a: a} } type hook struct { @@ -43,13 +48,13 @@ type hook struct { wait bool } -func (u *hooksUtil) runHooks(d *deploymentItem, hooks []string) { +func (u *HooksUtil) RunHooks(d *deployment.DeploymentItem, hooks []string) { doLog := func(level log.Level, s string, f ...interface{}) { - u.a.doLog(d, level, s, f...) + u.a.DoLog(d, level, s, f...) } var l []*hook - for _, h := range u.getSortedHooksList(d.objects) { + for _, h := range u.getSortedHooksList(d.Objects) { for h2 := range h.hooks { if utils.FindStrInSlice(hooks, h2) != -1 { l = append(l, h) @@ -85,7 +90,7 @@ func (u *hooksUtil) runHooks(d *deploymentItem, hooks []string) { dpStr = append(dpStr, p) } doLog(log.DebugLevel, "Deleting hook %s due to hook-delete-policy %s", ref.String(), strings.Join(dpStr, ",")) - return u.a.deleteObject(ref, true) + return u.a.DeleteObject(ref, true) } if len(deleteBeforeObjects) != 0 { @@ -103,15 +108,15 @@ func (u *hooksUtil) runHooks(d *deploymentItem, hooks []string) { ref := h.object.GetK8sRef() _, replaced := h.deletePolicies["before-hook-creation"] doLog(log.DebugLevel, "Applying hook %s", ref.String()) - u.a.applyObject(h.object, replaced, true) + u.a.ApplyObject(h.object, replaced, true) - if u.a.hadError(ref) { + if u.a.HadError(ref) { continue } if !h.wait { continue } - waitResults[ref] = u.a.waitHook(ref) + waitResults[ref] = u.a.WaitHook(ref) } var deleteAfterObjects []*hook @@ -141,7 +146,7 @@ func (u *hooksUtil) runHooks(d *deploymentItem, hooks []string) { } } -func (u *hooksUtil) getHook(o *uo.UnstructuredObject) *hook { +func (u *HooksUtil) GetHook(o *uo.UnstructuredObject) *hook { ref := o.GetK8sRef() getSet := func(name string) map[string]bool { ret := make(map[string]bool) @@ -160,14 +165,14 @@ func (u *hooksUtil) getHook(o *uo.UnstructuredObject) *hook { hooks := getSet("kluctl.io/hook") for h := range hooks { if utils.FindStrInSlice(supportedKluctlHooks, h) == -1 { - u.a.handleError(ref, fmt.Errorf("unsupported kluctl.io/hook '%s'", h)) + u.a.HandleError(ref, fmt.Errorf("unsupported kluctl.io/hook '%s'", h)) } } helmHooks := getSet("helm.sh/hook") for h := range helmHooks { if utils.FindStrInSlice(supportedHelmHooks, h) == -1 { - u.a.handleWarning(ref, fmt.Errorf("unsupported helm.sh/hook '%s'", h)) + u.a.HandleWarning(ref, fmt.Errorf("unsupported helm.sh/hook '%s'", h)) } } @@ -194,7 +199,7 @@ func (u *hooksUtil) getHook(o *uo.UnstructuredObject) *hook { } weight, err := strconv.ParseInt(*weightStr, 10, 32) if err != nil { - u.a.handleError(ref, fmt.Errorf("failed to parse hook weight: %w", err)) + u.a.HandleError(ref, fmt.Errorf("failed to parse hook weight: %w", err)) } deletePolicy := getSet("kluctl.io/hook-delete-policy") @@ -207,7 +212,7 @@ func (u *hooksUtil) getHook(o *uo.UnstructuredObject) *hook { for p := range deletePolicy { if utils.FindStrInSlice(supportedKluctlDeletePolicies, p) == -1 { - u.a.handleError(ref, fmt.Errorf("unsupported kluctl.io/hook-delete-policy '%s'", p)) + u.a.HandleError(ref, fmt.Errorf("unsupported kluctl.io/hook-delete-policy '%s'", p)) } } @@ -218,7 +223,7 @@ func (u *hooksUtil) getHook(o *uo.UnstructuredObject) *hook { } wait, err := strconv.ParseBool(*waitStr) if err != nil { - u.a.handleError(ref, fmt.Errorf("failed to parse %s as bool", waitStr)) + u.a.HandleError(ref, fmt.Errorf("failed to parse %s as bool", waitStr)) wait = true } @@ -235,10 +240,10 @@ func (u *hooksUtil) getHook(o *uo.UnstructuredObject) *hook { } } -func (u *hooksUtil) getSortedHooksList(objects []*uo.UnstructuredObject) []*hook { +func (u *HooksUtil) getSortedHooksList(objects []*uo.UnstructuredObject) []*hook { var ret []*hook for _, o := range objects { - h := u.getHook(o) + h := u.GetHook(o) if h == nil { continue } @@ -250,7 +255,7 @@ func (u *hooksUtil) getSortedHooksList(objects []*uo.UnstructuredObject) []*hook return ret } -func (h *hook) isPersistent() bool { +func (h *hook) IsPersistent() bool { for p := range h.deletePolicies { if p != "before-hook-creation" && p != "hook-failed" { return false From e4923dc51abe63a41728499739079d8751fed019 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 14 Mar 2022 17:34:08 +0100 Subject: [PATCH 0506/2916] refactor: Move diff command into commands package --- cmd/kluctl/commands/cmd_diff.go | 10 +++- pkg/deployment/commands/diff.go | 61 +++++++++++++++++++++++ pkg/deployment/deployment_collection.go | 66 ++++++------------------- 3 files changed, 85 insertions(+), 52 deletions(-) create mode 100644 pkg/deployment/commands/diff.go diff --git a/cmd/kluctl/commands/cmd_diff.go b/cmd/kluctl/commands/cmd_diff.go index 2f79be3b7..80453ef29 100644 --- a/cmd/kluctl/commands/cmd_diff.go +++ b/cmd/kluctl/commands/cmd_diff.go @@ -3,6 +3,7 @@ package commands import ( "fmt" "github.com/codablock/kluctl/cmd/kluctl/args" + "github.com/codablock/kluctl/pkg/deployment/commands" ) type diffCmd struct { @@ -35,7 +36,14 @@ func (cmd *diffCmd) Run() error { renderOutputDirFlags: cmd.RenderOutputDirFlags, } return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { - result, err := ctx.deploymentCollection.Diff(ctx.k, cmd.ForceApply, cmd.ReplaceOnError, cmd.ForceReplaceOnError, cmd.IgnoreTags, cmd.IgnoreLabels, cmd.IgnoreAnnotations) + cmd2 := commands.NewDiffCommand(ctx.deploymentCollection) + cmd2.ForceApply = cmd.ForceApply + cmd2.ReplaceOnError = cmd.ReplaceOnError + cmd2.ForceReplaceOnError = cmd.ForceReplaceOnError + cmd2.IgnoreTags = cmd.IgnoreTags + cmd2.IgnoreLabels = cmd.IgnoreLabels + cmd2.IgnoreAnnotations = cmd.IgnoreAnnotations + result, err := cmd2.Run(ctx.k) if err != nil { return err } diff --git a/pkg/deployment/commands/diff.go b/pkg/deployment/commands/diff.go new file mode 100644 index 000000000..24a941087 --- /dev/null +++ b/pkg/deployment/commands/diff.go @@ -0,0 +1,61 @@ +package commands + +import ( + "github.com/codablock/kluctl/pkg/deployment" + "github.com/codablock/kluctl/pkg/deployment/utils" + "github.com/codablock/kluctl/pkg/k8s" + "github.com/codablock/kluctl/pkg/types" +) + +type DiffCommand struct { + c *deployment.DeploymentCollection + + ForceApply bool + ReplaceOnError bool + ForceReplaceOnError bool + IgnoreTags bool + IgnoreLabels bool + IgnoreAnnotations bool +} + +func NewDiffCommand(c *deployment.DeploymentCollection) *DiffCommand { + return &DiffCommand{ + c: c, + } +} + +func (cmd *DiffCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, error) { + dew := utils.NewDeploymentErrorsAndWarnings() + + o := utils.ApplyUtilOptions{ + ForceApply: cmd.ForceApply, + ReplaceOnError: cmd.ReplaceOnError, + ForceReplaceOnError: cmd.ForceReplaceOnError, + DryRun: true, + AbortOnError: false, + HookTimeout: 0, + } + au := utils.NewApplyUtil(dew, cmd.c, k, o) + au.ApplyDeployments() + + du := utils.NewDiffUtil(dew, cmd.c.Deployments, cmd.c.RemoteObjects, au.AppliedObjects) + du.IgnoreTags = cmd.IgnoreTags + du.IgnoreLabels = cmd.IgnoreLabels + du.IgnoreAnnotations = cmd.IgnoreAnnotations + du.Diff(k) + + orphanObjects, err := cmd.c.FindOrphanObjects(k) + if err != nil { + return nil, err + } + return &types.CommandResult{ + NewObjects: du.NewObjects, + ChangedObjects: du.ChangedObjects, + DeletedObjects: au.GetDeletedObjectsList(), + HookObjects: au.GetAppliedHookObjects(), + OrphanObjects: orphanObjects, + Errors: dew.GetErrorsList(), + Warnings: dew.GetWarningsList(), + SeenImages: cmd.c.Images.SeenImages(false), + }, nil +} diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index 3e520f6e5..c0f4c62b4 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -30,7 +30,7 @@ type DeploymentCollection struct { forSeal bool Deployments []*DeploymentItem - remoteObjects map[k8s2.ObjectRef]*uo.UnstructuredObject + RemoteObjects map[k8s2.ObjectRef]*uo.UnstructuredObject mutex sync.Mutex } @@ -41,7 +41,7 @@ func NewDeploymentCollection(project *DeploymentProject, images *Images, inclusi inclusion: inclusion, RenderDir: renderDir, forSeal: forSeal, - remoteObjects: map[k8s2.ObjectRef]*uo.UnstructuredObject{}, + RemoteObjects: map[k8s2.ObjectRef]*uo.UnstructuredObject{}, } indexes := make(map[string]int) @@ -239,7 +239,7 @@ func (c *DeploymentCollection) updateRemoteObjects(k *k8s.K8sCluster, dew *utils } log.Infof("Getting remote objects by commonLabels") - allObjects, apiWarnings, err := k.ListAllObjects([]string{"get"}, "", c.project.getCommonLabels(), false) + allObjects, apiWarnings, err := k.ListAllObjects([]string{"get"}, "", c.Project.getCommonLabels(), false) for gvk, aw := range apiWarnings { dew.AddApiWarnings(k8s2.ObjectRef{GVK: gvk}, aw) } @@ -254,7 +254,7 @@ func (c *DeploymentCollection) updateRemoteObjects(k *k8s.K8sCluster, dew *utils notFoundRefsMap := make(map[k8s2.ObjectRef]bool) var notFoundRefsList []k8s2.ObjectRef for ref := range c.localObjectsByRef() { - if _, ok := c.remoteObjects[ref]; !ok { + if _, ok := c.RemoteObjects[ref]; !ok { if _, ok = notFoundRefsMap[ref]; !ok { notFoundRefsMap[ref] = true notFoundRefsList = append(notFoundRefsList, ref) @@ -279,16 +279,16 @@ func (c *DeploymentCollection) updateRemoteObjects(k *k8s.K8sCluster, dew *utils } func (c *DeploymentCollection) addRemoteObject(o *uo.UnstructuredObject) { - c.remoteObjects[o.GetK8sRef()] = o + c.RemoteObjects[o.GetK8sRef()] = o } func (c *DeploymentCollection) GetRemoteObject(ref k8s2.ObjectRef) *uo.UnstructuredObject { - o, _ := c.remoteObjects[ref] + o, _ := c.RemoteObjects[ref] return o } func (c *DeploymentCollection) ForgetRemoteObject(ref k8s2.ObjectRef) { - delete(c.remoteObjects, ref) + delete(c.RemoteObjects, ref) } func (c *DeploymentCollection) localObjectsByRef() map[k8s2.ObjectRef]bool { @@ -349,7 +349,7 @@ func (c *DeploymentCollection) Deploy(k *k8s.K8sCluster, forceApply bool, replac au := utils2.NewApplyUtil(dew, c, k, o) au.ApplyDeployments() - du := utils2.NewDiffUtil(dew, c.Deployments, c.remoteObjects, au.AppliedObjects) + du := utils2.NewDiffUtil(dew, c.Deployments, c.RemoteObjects, au.AppliedObjects) du.Diff(k) orphanObjects, err := c.FindOrphanObjects(k) @@ -364,43 +364,7 @@ func (c *DeploymentCollection) Deploy(k *k8s.K8sCluster, forceApply bool, replac OrphanObjects: orphanObjects, Errors: dew.GetErrorsList(), Warnings: dew.GetWarningsList(), - SeenImages: c.images.seenImages, - }, nil -} - -func (c *DeploymentCollection) Diff(k *k8s.K8sCluster, forceApply bool, replaceOnError bool, forceReplaceOnError bool, ignoreTags bool, ignoreLabels bool, ignoreAnnotations bool) (*types.CommandResult, error) { - dew := utils2.NewDeploymentErrorsAndWarnings() - - o := utils2.ApplyUtilOptions{ - ForceApply: forceApply, - ReplaceOnError: replaceOnError, - ForceReplaceOnError: forceReplaceOnError, - DryRun: true, - AbortOnError: false, - HookTimeout: 0, - } - au := utils2.NewApplyUtil(dew, c, k, o) - au.ApplyDeployments() - - du := utils2.NewDiffUtil(dew, c.Deployments, c.remoteObjects, au.AppliedObjects) - du.IgnoreTags = ignoreTags - du.IgnoreLabels = ignoreLabels - du.IgnoreAnnotations = ignoreAnnotations - du.Diff(k) - - orphanObjects, err := c.FindOrphanObjects(k) - if err != nil { - return nil, err - } - return &types.CommandResult{ - NewObjects: du.NewObjects, - ChangedObjects: du.ChangedObjects, - DeletedObjects: au.GetDeletedObjectsList(), - HookObjects: au.GetAppliedHookObjects(), - OrphanObjects: orphanObjects, - Errors: dew.GetErrorsList(), - Warnings: dew.GetWarningsList(), - SeenImages: c.images.seenImages, + SeenImages: c.Images.seenImages, }, nil } @@ -418,7 +382,7 @@ func (c *DeploymentCollection) PokeImages(k *k8s.K8sCluster) (*types.CommandResu } containersAndImages := make(map[k8s2.ObjectRef][]types.FixedImage) - for _, fi := range c.images.seenImages { + for _, fi := range c.Images.seenImages { _, ok := allObjects[*fi.Object] if !ok { dew.AddError(*fi.Object, fmt.Errorf("object not found while trying to associate image with deployed object")) @@ -470,7 +434,7 @@ func (c *DeploymentCollection) PokeImages(k *k8s.K8sCluster) (*types.CommandResu return nil, err } - du := utils2.NewDiffUtil(dew, c.Deployments, c.remoteObjects, appliedObjects) + du := utils2.NewDiffUtil(dew, c.Deployments, c.RemoteObjects, appliedObjects) du.Diff(k) return &types.CommandResult{ @@ -478,7 +442,7 @@ func (c *DeploymentCollection) PokeImages(k *k8s.K8sCluster) (*types.CommandResu ChangedObjects: du.ChangedObjects, Errors: dew.GetErrorsList(), Warnings: dew.GetWarningsList(), - SeenImages: c.images.seenImages, + SeenImages: c.Images.seenImages, }, nil } @@ -533,7 +497,7 @@ func (c *DeploymentCollection) Downscale(k *k8s.K8sCluster) (*types.CommandResul return nil, err } - du := utils2.NewDiffUtil(dew, c.Deployments, c.remoteObjects, appliedObjects) + du := utils2.NewDiffUtil(dew, c.Deployments, c.RemoteObjects, appliedObjects) du.Diff(k) return &types.CommandResult{ @@ -542,7 +506,7 @@ func (c *DeploymentCollection) Downscale(k *k8s.K8sCluster) (*types.CommandResul DeletedObjects: deletedObjects, Errors: dew.GetErrorsList(), Warnings: dew.GetWarningsList(), - SeenImages: c.images.seenImages, + SeenImages: c.Images.seenImages, }, nil } @@ -641,7 +605,7 @@ func (c *DeploymentCollection) getFilteredRemoteObjects(inclusion *utils.Inclusi c.mutex.Lock() defer c.mutex.Unlock() - for _, o := range c.remoteObjects { + for _, o := range c.RemoteObjects { iv := c.getInclusionEntries(o) if inclusion.CheckIncluded(iv, false) { ret = append(ret, o) From 71a1838c087616ae9984ddbc4197e109a2f92d94 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 14 Mar 2022 17:40:34 +0100 Subject: [PATCH 0507/2916] refactor: Move seal command to commands package --- cmd/kluctl/commands/cmd_seal.go | 5 ++- pkg/deployment/commands/seal.go | 46 +++++++++++++++++++++ pkg/deployment/deployment_collection.go | 39 +++--------------- pkg/deployment/deployment_item.go | 10 ++--- pkg/deployment/deployment_project.go | 54 ++++++++++++------------- 5 files changed, 87 insertions(+), 67 deletions(-) create mode 100644 pkg/deployment/commands/seal.go diff --git a/cmd/kluctl/commands/cmd_seal.go b/cmd/kluctl/commands/cmd_seal.go index 5046060dc..590ff7459 100644 --- a/cmd/kluctl/commands/cmd_seal.go +++ b/cmd/kluctl/commands/cmd_seal.go @@ -3,6 +3,7 @@ package commands import ( "fmt" "github.com/codablock/kluctl/cmd/kluctl/args" + "github.com/codablock/kluctl/pkg/deployment/commands" "github.com/codablock/kluctl/pkg/kluctl_project" "github.com/codablock/kluctl/pkg/seal" "github.com/codablock/kluctl/pkg/types" @@ -105,7 +106,9 @@ func (cmd *sealCmd) runCmdSealForTarget(p *kluctl_project.KluctlProjectContext, return err } - err = ctx.deploymentCollection.Seal(sealer) + cmd2 := commands.NewSealCommand(ctx.deploymentCollection) + err = cmd2.Run(sealer) + if err != nil { return err } diff --git a/pkg/deployment/commands/seal.go b/pkg/deployment/commands/seal.go new file mode 100644 index 000000000..17307f29c --- /dev/null +++ b/pkg/deployment/commands/seal.go @@ -0,0 +1,46 @@ +package commands + +import ( + "fmt" + "github.com/codablock/kluctl/pkg/deployment" + "github.com/codablock/kluctl/pkg/seal" + "io/fs" + "path/filepath" + "strings" +) + +type SealCommand struct { + c *deployment.DeploymentCollection +} + +func NewSealCommand(c *deployment.DeploymentCollection) *SealCommand { + return &SealCommand{ + c: c, + } +} + +func (cmd *SealCommand) Run(sealer *seal.Sealer) error { + if cmd.c.Project.Config.SealedSecrets.OutputPattern == nil { + return fmt.Errorf("sealedSecrets.outputPattern is not defined") + } + + err := filepath.WalkDir(cmd.c.RenderDir, func(p string, d fs.DirEntry, err error) error { + if !strings.HasSuffix(p, deployment.SealmeExt) { + return nil + } + + relPath, err := filepath.Rel(cmd.c.RenderDir, p) + if err != nil { + return err + } + targetDir := filepath.Join(cmd.c.Project.SealedSecretsDir, filepath.Dir(relPath)) + targetFile := filepath.Join(targetDir, *cmd.c.Project.Config.SealedSecrets.OutputPattern, filepath.Base(p)) + targetFile = targetFile[:len(targetFile)-len(deployment.SealmeExt)] + err = sealer.SealFile(p, targetFile) + if err != nil { + return fmt.Errorf("failed sealing %s: %w", filepath.Base(p), err) + } + return nil + }) + return err +} \ No newline at end of file diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index c0f4c62b4..cb64b2e26 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -5,7 +5,6 @@ import ( "fmt" utils2 "github.com/codablock/kluctl/pkg/deployment/utils" "github.com/codablock/kluctl/pkg/k8s" - "github.com/codablock/kluctl/pkg/seal" "github.com/codablock/kluctl/pkg/types" k8s2 "github.com/codablock/kluctl/pkg/types/k8s" "github.com/codablock/kluctl/pkg/utils" @@ -13,18 +12,16 @@ import ( "github.com/codablock/kluctl/pkg/validation" log "github.com/sirupsen/logrus" "golang.org/x/sync/semaphore" - "io/fs" "k8s.io/apimachinery/pkg/api/errors" "path/filepath" "reflect" - "strings" "sync" "time" ) type DeploymentCollection struct { - project *DeploymentProject - images *Images + Project *DeploymentProject + Images *Images inclusion *utils.Inclusion RenderDir string forSeal bool @@ -36,8 +33,8 @@ type DeploymentCollection struct { func NewDeploymentCollection(project *DeploymentProject, images *Images, inclusion *utils.Inclusion, renderDir string, forSeal bool) (*DeploymentCollection, error) { dc := &DeploymentCollection{ - project: project, - images: images, + Project: project, + Images: images, inclusion: inclusion, RenderDir: renderDir, forSeal: forSeal, @@ -90,7 +87,7 @@ func findDeploymentItemIndex(project *DeploymentProject, pth *string, indexes ma func (c *DeploymentCollection) collectDeployments(project *DeploymentProject, indexes map[string]int) ([]*DeploymentItem, error) { var ret []*DeploymentItem - for i, diConfig := range project.config.Deployments { + for i, diConfig := range project.Config.Deployments { if diConfig.Include != nil { includedProject, ok := project.includes[i] if !ok { @@ -148,32 +145,6 @@ func (c *DeploymentCollection) RenderDeployments(k *k8s.K8sCluster) error { return nil } -func (c *DeploymentCollection) Seal(sealer *seal.Sealer) error { - if c.project.config.SealedSecrets.OutputPattern == nil { - return fmt.Errorf("sealedSecrets.outputPattern is not defined") - } - - err := filepath.WalkDir(c.RenderDir, func(p string, d fs.DirEntry, err error) error { - if !strings.HasSuffix(p, sealmeExt) { - return nil - } - - relPath, err := filepath.Rel(c.RenderDir, p) - if err != nil { - return err - } - targetDir := filepath.Join(c.project.sealedSecretsDir, filepath.Dir(relPath)) - targetFile := filepath.Join(targetDir, *c.project.config.SealedSecrets.OutputPattern, filepath.Base(p)) - targetFile = targetFile[:len(targetFile)-len(sealmeExt)] - err = sealer.SealFile(p, targetFile) - if err != nil { - return fmt.Errorf("failed sealing %s: %w", filepath.Base(p), err) - } - return nil - }) - return err -} - func (c *DeploymentCollection) resolveSealedSecrets() error { if c.forSeal { return nil diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index a73529544..6f8b00ae1 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -19,7 +19,7 @@ import ( "sync" ) -const sealmeExt = ".sealme" +const SealmeExt = ".sealme" type DeploymentItem struct { Project *DeploymentProject @@ -118,7 +118,7 @@ func (di *DeploymentItem) render(k *k8s.K8sCluster, wp *utils.WorkerPoolWithErro } var excludePatterns []string - excludePatterns = append(excludePatterns, di.Project.config.TemplateExcludes...) + excludePatterns = append(excludePatterns, di.Project.Config.TemplateExcludes...) err = filepath.WalkDir(*di.dir, func(p string, d fs.DirEntry, err error) error { if d.IsDir() { relDir, err := filepath.Rel(*di.dir, p) @@ -184,7 +184,7 @@ func (di *DeploymentItem) resolveSealedSecrets() error { // TODO check for bootstrap sealedSecretsDir := di.Project.getSealedSecretsDir() - baseSourcePath := di.Project.sealedSecretsDir + baseSourcePath := di.Project.SealedSecretsDir y, err := uo.FromFile(filepath.Join(di.renderedDir, "kustomization.yml")) if err != nil { @@ -196,7 +196,7 @@ func (di *DeploymentItem) resolveSealedSecrets() error { } for _, resource := range l { p := filepath.Join(di.renderedDir, resource) - if utils.Exists(p) || !utils.Exists(p+sealmeExt) { + if utils.Exists(p) || !utils.Exists(p+SealmeExt) { continue } relDir, err := filepath.Rel(di.renderedDir, filepath.Dir(p)) @@ -368,7 +368,7 @@ func (di *DeploymentItem) postprocessAndLoadObjects(k *k8s.K8sCluster) error { o.SetK8sAnnotations(uo.CopyMergeStrMap(o.GetK8sAnnotations(), commonAnnotations)) // Resolve image placeholders - err = di.collection.images.ResolvePlaceholders(k, o, di.relRenderedDir, di.getTags().ListKeys()) + err = di.collection.Images.ResolvePlaceholders(k, o, di.relRenderedDir, di.getTags().ListKeys()) if err != nil { return err } diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go index 162e28e16..e0edd77df 100644 --- a/pkg/deployment/deployment_project.go +++ b/pkg/deployment/deployment_project.go @@ -18,9 +18,9 @@ var warnOnce utils.OnceByKey type DeploymentProject struct { VarsCtx *jinja2.VarsCtx dir string - sealedSecretsDir string + SealedSecretsDir string - config types.DeploymentProjectConfig + Config types.DeploymentProjectConfig includes map[int]*DeploymentProject @@ -32,7 +32,7 @@ func NewDeploymentProject(k *k8s.K8sCluster, varsCtx *jinja2.VarsCtx, dir string dp := &DeploymentProject{ VarsCtx: varsCtx.Copy(), dir: dir, - sealedSecretsDir: sealedSecretsDir, + SealedSecretsDir: sealedSecretsDir, parentProject: parentProject, includes: map[int]*DeploymentProject{}, } @@ -69,43 +69,43 @@ func (p *DeploymentProject) loadConfig(k *k8s.K8sCluster) error { return fmt.Errorf("%s not found", p.dir) } - err := p.VarsCtx.RenderYamlFile("deployment.yml", p.getRenderSearchDirs(), &p.config) + err := p.VarsCtx.RenderYamlFile("deployment.yml", p.getRenderSearchDirs(), &p.Config) if err != nil { return fmt.Errorf("failed to load deployment.yml: %w", err) } - err = p.VarsCtx.LoadVarsList(k, p.getRenderSearchDirs(), p.config.Vars) + err = p.VarsCtx.LoadVarsList(k, p.getRenderSearchDirs(), p.Config.Vars) if err != nil { return fmt.Errorf("failed to load deployment.yml vars: %w", err) } // TODO remove obsolete code - if len(p.config.Deployments) != 0 && (len(p.config.KustomizeDirs) != 0 || len(p.config.Includes) != 0) { + if len(p.Config.Deployments) != 0 && (len(p.Config.KustomizeDirs) != 0 || len(p.Config.Includes) != 0) { return fmt.Errorf("using 'deployments' and 'kustomizeDirs' at the same time is not allowed") } - if len(p.config.KustomizeDirs) != 0 { + if len(p.Config.KustomizeDirs) != 0 { warnOnce.Do("kustomizeDirs", func() { log.Warningf("'kustomizeDirs' is deprecated, use 'deployments' instead") }) - p.config.Deployments = p.config.KustomizeDirs - p.config.KustomizeDirs = nil + p.Config.Deployments = p.Config.KustomizeDirs + p.Config.KustomizeDirs = nil } - if len(p.config.Includes) != 0 { + if len(p.Config.Includes) != 0 { warnOnce.Do("includes", func() { log.Warningf("'includes' is deprecated, use 'deployments' instead") }) - for _, inc := range p.config.Includes { + for _, inc := range p.Config.Includes { c := *inc c.Include = c.Path c.Path = nil - p.config.Deployments = append(p.config.Deployments, &c) + p.Config.Deployments = append(p.Config.Deployments, &c) } - p.config.Includes = nil + p.Config.Includes = nil } // If there are no explicit tags set, interpret the path as a tag, which allows to // enable/disable single deployments via included/excluded tags - for _, item := range p.config.Deployments { + for _, item := range p.Config.Deployments { if len(item.Tags) != 0 { continue } @@ -124,15 +124,15 @@ func (p *DeploymentProject) loadConfig(k *k8s.K8sCluster) error { if len(p.getCommonLabels()) == 0 { return fmt.Errorf("no commonLabels in root deployment. This is not allowed") } - if len(p.config.DeleteByLabels) != 0 { + if len(p.Config.DeleteByLabels) != 0 { warnOnce.Do("deleteByLabels", func() { log.Warningf("'deleteByLabels' is deprecated and ignored from now on") }) - if !reflect.DeepEqual(p.config.CommonLabels, p.config.DeleteByLabels) { + if !reflect.DeepEqual(p.Config.CommonLabels, p.Config.DeleteByLabels) { return fmt.Errorf("commonLabels and deleteByLabels do not match") } } - if len(p.config.Args) != 0 && p.parentProject != nil { + if len(p.Config.Args) != 0 && p.parentProject != nil { return fmt.Errorf("only the root deployment.yml can define args") } return nil @@ -140,7 +140,7 @@ func (p *DeploymentProject) loadConfig(k *k8s.K8sCluster) error { func (p *DeploymentProject) checkDeploymentDirs() error { rootProject := p.getRootProject() - for _, di := range p.config.Deployments { + for _, di := range p.Config.Deployments { if di.Path == nil && di.Include == nil { continue } @@ -182,7 +182,7 @@ func (p *DeploymentProject) checkDeploymentDirs() error { } func (p *DeploymentProject) loadIncludes(k *k8s.K8sCluster) error { - for i, inc := range p.config.Deployments { + for i, inc := range p.Config.Deployments { if inc.Include == nil { continue } @@ -195,7 +195,7 @@ func (p *DeploymentProject) loadIncludes(k *k8s.K8sCluster) error { return err } - newProject, err := NewDeploymentProject(k, varsCtx, incDir, p.sealedSecretsDir, p) + newProject, err := NewDeploymentProject(k, varsCtx, incDir, p.SealedSecretsDir, p) if err != nil { return err } @@ -208,10 +208,10 @@ func (p *DeploymentProject) loadIncludes(k *k8s.K8sCluster) error { func (p *DeploymentProject) getSealedSecretsDir() *string { root := p.getRootProject() - if root.config.SealedSecrets == nil { + if root.Config.SealedSecrets == nil { return nil } - return root.config.SealedSecrets.OutputPattern + return root.Config.SealedSecrets.OutputPattern } func (p *DeploymentProject) getRootProject() *DeploymentProject { @@ -265,15 +265,15 @@ func (p *DeploymentProject) getCommonLabels() map[string]string { parents := p.getParents() for i, _ := range parents { d := parents[len(parents)-i-1] - uo.MergeStrMap(ret, d.p.config.CommonLabels) + uo.MergeStrMap(ret, d.p.Config.CommonLabels) } return ret } func (p *DeploymentProject) getOverrideNamespace() *string { for _, e := range p.getParents() { - if e.p.config.OverrideNamespace != nil { - return e.p.config.OverrideNamespace + if e.p.Config.OverrideNamespace != nil { + return e.p.Config.OverrideNamespace } } return nil @@ -287,7 +287,7 @@ func (p *DeploymentProject) getTags() *utils.OrderedMap { tags.Set(t, true) } } - for _, t := range e.p.config.Tags { + for _, t := range e.p.Config.Tags { tags.Set(t, true) } } @@ -297,7 +297,7 @@ func (p *DeploymentProject) getTags() *utils.OrderedMap { func (p *DeploymentProject) GetIgnoreForDiffs(ignoreTags, ignoreLabels, ignoreAnnotations bool) []*types.IgnoreForDiffItemConfig { var ret []*types.IgnoreForDiffItemConfig for _, e := range p.getParents() { - ret = append(ret, e.p.config.IgnoreForDiff...) + ret = append(ret, e.p.Config.IgnoreForDiff...) } if ignoreTags { ret = append(ret, &types.IgnoreForDiffItemConfig{FieldPath: []string{`metadata.labels."kluctl.io/tag-*"`}}) From 95fbcd5a2bb9f2d98fc3b82e1b7f8611d38a3c5b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 14 Mar 2022 17:45:14 +0100 Subject: [PATCH 0508/2916] refactor: Move deploy command to commands package --- cmd/kluctl/commands/cmd_deploy.go | 10 ++++- pkg/deployment/commands/deploy.go | 58 +++++++++++++++++++++++++ pkg/deployment/deployment_collection.go | 34 --------------- 3 files changed, 67 insertions(+), 35 deletions(-) create mode 100644 pkg/deployment/commands/deploy.go diff --git a/cmd/kluctl/commands/cmd_deploy.go b/cmd/kluctl/commands/cmd_deploy.go index fe63b5b26..34ea8a07b 100644 --- a/cmd/kluctl/commands/cmd_deploy.go +++ b/cmd/kluctl/commands/cmd_deploy.go @@ -3,6 +3,7 @@ package commands import ( "fmt" "github.com/codablock/kluctl/cmd/kluctl/args" + "github.com/codablock/kluctl/pkg/deployment/commands" ) type deployCmd struct { @@ -50,7 +51,14 @@ func (cmd *deployCmd) runCmdDeploy(ctx *commandCtx) error { } } - result, err := ctx.deploymentCollection.Deploy(ctx.k, cmd.ForceApply, cmd.ReplaceOnError, cmd.ForceReplaceOnError, cmd.AbortOnError, cmd.HookTimeout) + cmd2 := commands.NewDeployCommand(ctx.deploymentCollection) + cmd2.ForceApply = cmd.ForceApply + cmd2.ReplaceOnError = cmd.ReplaceOnError + cmd2.ForceReplaceOnError = cmd.ForceReplaceOnError + cmd2.AbortOnError = cmd.AbortOnError + cmd2.HookTimeout = cmd.HookTimeout + + result, err := cmd2.Run(ctx.k) if err != nil { return err } diff --git a/pkg/deployment/commands/deploy.go b/pkg/deployment/commands/deploy.go new file mode 100644 index 000000000..4a379b86d --- /dev/null +++ b/pkg/deployment/commands/deploy.go @@ -0,0 +1,58 @@ +package commands + +import ( + "github.com/codablock/kluctl/pkg/deployment" + utils2 "github.com/codablock/kluctl/pkg/deployment/utils" + "github.com/codablock/kluctl/pkg/k8s" + "github.com/codablock/kluctl/pkg/types" + "time" +) + +type DeployCommand struct { + c *deployment.DeploymentCollection + + ForceApply bool + ReplaceOnError bool + ForceReplaceOnError bool + AbortOnError bool + HookTimeout time.Duration +} + +func NewDeployCommand(c *deployment.DeploymentCollection) *DeployCommand { + return &DeployCommand{ + c: c, + } +} + +func (cmd *DeployCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, error) { + dew := utils2.NewDeploymentErrorsAndWarnings() + + o := utils2.ApplyUtilOptions{ + ForceApply: cmd.ForceApply, + ReplaceOnError: cmd.ReplaceOnError, + ForceReplaceOnError: cmd.ForceReplaceOnError, + DryRun: k.DryRun, + AbortOnError: cmd.AbortOnError, + HookTimeout: cmd.HookTimeout, + } + au := utils2.NewApplyUtil(dew, cmd.c, k, o) + au.ApplyDeployments() + + du := utils2.NewDiffUtil(dew, cmd.c.Deployments, cmd.c.RemoteObjects, au.AppliedObjects) + du.Diff(k) + + orphanObjects, err := cmd.c.FindOrphanObjects(k) + if err != nil { + return nil, err + } + return &types.CommandResult{ + NewObjects: du.NewObjects, + ChangedObjects: du.ChangedObjects, + DeletedObjects: au.GetDeletedObjectsList(), + HookObjects: au.GetAppliedHookObjects(), + OrphanObjects: orphanObjects, + Errors: dew.GetErrorsList(), + Warnings: dew.GetWarningsList(), + SeenImages: cmd.c.Images.SeenImages(false), + }, nil +} \ No newline at end of file diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index cb64b2e26..f7607f31d 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -16,7 +16,6 @@ import ( "path/filepath" "reflect" "sync" - "time" ) type DeploymentCollection struct { @@ -306,39 +305,6 @@ func (c *DeploymentCollection) Prepare(k *k8s.K8sCluster) error { return nil } -func (c *DeploymentCollection) Deploy(k *k8s.K8sCluster, forceApply bool, replaceOnError bool, forceReplaceOnError, abortOnError bool, hookTimeout time.Duration) (*types.CommandResult, error) { - dew := utils2.NewDeploymentErrorsAndWarnings() - - o := utils2.ApplyUtilOptions{ - ForceApply: forceApply, - ReplaceOnError: replaceOnError, - ForceReplaceOnError: forceReplaceOnError, - DryRun: k.DryRun, - AbortOnError: abortOnError, - HookTimeout: hookTimeout, - } - au := utils2.NewApplyUtil(dew, c, k, o) - au.ApplyDeployments() - - du := utils2.NewDiffUtil(dew, c.Deployments, c.RemoteObjects, au.AppliedObjects) - du.Diff(k) - - orphanObjects, err := c.FindOrphanObjects(k) - if err != nil { - return nil, err - } - return &types.CommandResult{ - NewObjects: du.NewObjects, - ChangedObjects: du.ChangedObjects, - DeletedObjects: au.GetDeletedObjectsList(), - HookObjects: au.GetAppliedHookObjects(), - OrphanObjects: orphanObjects, - Errors: dew.GetErrorsList(), - Warnings: dew.GetWarningsList(), - SeenImages: c.Images.seenImages, - }, nil -} - func (c *DeploymentCollection) PokeImages(k *k8s.K8sCluster) (*types.CommandResult, error) { dew := utils2.NewDeploymentErrorsAndWarnings() From 6984a233b4f764978845508412cc5b4827f407ed Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 14 Mar 2022 21:30:05 +0100 Subject: [PATCH 0509/2916] cleanup: Run gofmt --- pkg/deployment/commands/deploy.go | 10 +++++----- pkg/deployment/commands/diff.go | 6 +++--- pkg/deployment/commands/seal.go | 2 +- pkg/deployment/utils/apply_utils.go | 6 +++--- pkg/deployment/utils/diff_utils.go | 6 +++--- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/pkg/deployment/commands/deploy.go b/pkg/deployment/commands/deploy.go index 4a379b86d..466ebc3e6 100644 --- a/pkg/deployment/commands/deploy.go +++ b/pkg/deployment/commands/deploy.go @@ -11,11 +11,11 @@ import ( type DeployCommand struct { c *deployment.DeploymentCollection - ForceApply bool - ReplaceOnError bool + ForceApply bool + ReplaceOnError bool ForceReplaceOnError bool - AbortOnError bool - HookTimeout time.Duration + AbortOnError bool + HookTimeout time.Duration } func NewDeployCommand(c *deployment.DeploymentCollection) *DeployCommand { @@ -55,4 +55,4 @@ func (cmd *DeployCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, error) { Warnings: dew.GetWarningsList(), SeenImages: cmd.c.Images.SeenImages(false), }, nil -} \ No newline at end of file +} diff --git a/pkg/deployment/commands/diff.go b/pkg/deployment/commands/diff.go index 24a941087..d08c1fdc9 100644 --- a/pkg/deployment/commands/diff.go +++ b/pkg/deployment/commands/diff.go @@ -13,9 +13,9 @@ type DiffCommand struct { ForceApply bool ReplaceOnError bool ForceReplaceOnError bool - IgnoreTags bool - IgnoreLabels bool - IgnoreAnnotations bool + IgnoreTags bool + IgnoreLabels bool + IgnoreAnnotations bool } func NewDiffCommand(c *deployment.DeploymentCollection) *DiffCommand { diff --git a/pkg/deployment/commands/seal.go b/pkg/deployment/commands/seal.go index 17307f29c..02c20e055 100644 --- a/pkg/deployment/commands/seal.go +++ b/pkg/deployment/commands/seal.go @@ -43,4 +43,4 @@ func (cmd *SealCommand) Run(sealer *seal.Sealer) error { return nil }) return err -} \ No newline at end of file +} diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index d36e5cd00..8539cdb17 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -22,9 +22,9 @@ type ApplyUtilOptions struct { ForceApply bool ReplaceOnError bool ForceReplaceOnError bool - DryRun bool - AbortOnError bool - HookTimeout time.Duration + DryRun bool + AbortOnError bool + HookTimeout time.Duration } type ApplyUtil struct { diff --git a/pkg/deployment/utils/diff_utils.go b/pkg/deployment/utils/diff_utils.go index b9f164d56..173bf4d7b 100644 --- a/pkg/deployment/utils/diff_utils.go +++ b/pkg/deployment/utils/diff_utils.go @@ -22,9 +22,9 @@ type diffUtil struct { IgnoreAnnotations bool remoteDiffObjects map[k8s2.ObjectRef]*uo.UnstructuredObject - NewObjects []*types.RefAndObject - ChangedObjects []*types.ChangedObject - mutex sync.Mutex + NewObjects []*types.RefAndObject + ChangedObjects []*types.ChangedObject + mutex sync.Mutex } func NewDiffUtil(dew *DeploymentErrorsAndWarnings, deployments []*deployment.DeploymentItem, remoteObjects map[k8s2.ObjectRef]*uo.UnstructuredObject, appliedObjects map[k8s2.ObjectRef]*uo.UnstructuredObject) *diffUtil { From b640e7646494849cbea2e2d6b8cb424b40df279f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 14 Mar 2022 21:30:57 +0100 Subject: [PATCH 0510/2916] refactor: Move poke-images to commands package --- cmd/kluctl/commands/cmd_poke_images.go | 5 +- pkg/deployment/commands/poke_images.go | 89 +++++++++++++++++ pkg/deployment/deployment_collection.go | 123 ------------------------ pkg/deployment/utils/apply_utils.go | 52 +++++++++- 4 files changed, 144 insertions(+), 125 deletions(-) create mode 100644 pkg/deployment/commands/poke_images.go diff --git a/cmd/kluctl/commands/cmd_poke_images.go b/cmd/kluctl/commands/cmd_poke_images.go index 1d79bdcde..c69c53a35 100644 --- a/cmd/kluctl/commands/cmd_poke_images.go +++ b/cmd/kluctl/commands/cmd_poke_images.go @@ -3,6 +3,7 @@ package commands import ( "fmt" "github.com/codablock/kluctl/cmd/kluctl/args" + "github.com/codablock/kluctl/pkg/deployment/commands" ) type pokeImagesCmd struct { @@ -40,7 +41,9 @@ func (cmd *pokeImagesCmd) Run() error { } } - result, err := ctx.deploymentCollection.PokeImages(ctx.k) + cmd2 := commands.NewPokeImagesCommand(ctx.deploymentCollection) + + result, err := cmd2.Run(ctx.k) if err != nil { return err } diff --git a/pkg/deployment/commands/poke_images.go b/pkg/deployment/commands/poke_images.go new file mode 100644 index 000000000..db290012b --- /dev/null +++ b/pkg/deployment/commands/poke_images.go @@ -0,0 +1,89 @@ +package commands + +import ( + "fmt" + "github.com/codablock/kluctl/pkg/deployment" + utils2 "github.com/codablock/kluctl/pkg/deployment/utils" + "github.com/codablock/kluctl/pkg/k8s" + "github.com/codablock/kluctl/pkg/types" + k8s2 "github.com/codablock/kluctl/pkg/types/k8s" + "github.com/codablock/kluctl/pkg/utils/uo" + "sync" +) + +type PokeImagesCommand struct { + c *deployment.DeploymentCollection +} + +func NewPokeImagesCommand(c *deployment.DeploymentCollection) *PokeImagesCommand { + return &PokeImagesCommand{ + c: c, + } +} + +func (cmd *PokeImagesCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, error) { + var wg sync.WaitGroup + + dew := utils2.NewDeploymentErrorsAndWarnings() + + allObjects := make(map[k8s2.ObjectRef]*uo.UnstructuredObject) + for _, d := range cmd.c.Deployments { + if !d.CheckInclusionForDeploy() { + continue + } + for _, o := range d.Objects { + allObjects[o.GetK8sRef()] = o + } + } + + containersAndImages := make(map[k8s2.ObjectRef][]types.FixedImage) + for _, fi := range cmd.c.Images.SeenImages(false) { + _, ok := allObjects[*fi.Object] + if !ok { + dew.AddError(*fi.Object, fmt.Errorf("object not found while trying to associate image with deployed object")) + continue + } + + containersAndImages[*fi.Object] = append(containersAndImages[*fi.Object], fi) + } + + doPokeImage := func(images []types.FixedImage, o *uo.UnstructuredObject) (*uo.UnstructuredObject, error) { + containers, _, _ := o.GetNestedObjectList("spec", "template", "spec", "containers") + + for _, image := range images { + for _, c := range containers { + containerName, _, _ := c.GetNestedString("name") + if image.Container != nil && containerName == *image.Container { + c.SetNestedField(image.ResultImage, "image") + } + } + } + return o, nil + } + + au := utils2.NewApplyUtil(dew, cmd.c, k, utils2.ApplyUtilOptions{}) + + for ref, containers := range containersAndImages { + ref := ref + containers := containers + wg.Add(1) + go func() { + defer wg.Done() + au.ReplaceObject(ref, cmd.c.GetRemoteObject(ref), func(o *uo.UnstructuredObject) (*uo.UnstructuredObject, error) { + return doPokeImage(containers, o) + }) + }() + } + wg.Wait() + + du := utils2.NewDiffUtil(dew, cmd.c.Deployments, cmd.c.RemoteObjects, au.AppliedObjects) + du.Diff(k) + + return &types.CommandResult{ + NewObjects: du.NewObjects, + ChangedObjects: du.ChangedObjects, + Errors: dew.GetErrorsList(), + Warnings: dew.GetWarningsList(), + SeenImages: cmd.c.Images.SeenImages(false), + }, nil +} diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index f7607f31d..0bf563cc5 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -12,9 +12,7 @@ import ( "github.com/codablock/kluctl/pkg/validation" log "github.com/sirupsen/logrus" "golang.org/x/sync/semaphore" - "k8s.io/apimachinery/pkg/api/errors" "path/filepath" - "reflect" "sync" ) @@ -305,84 +303,6 @@ func (c *DeploymentCollection) Prepare(k *k8s.K8sCluster) error { return nil } -func (c *DeploymentCollection) PokeImages(k *k8s.K8sCluster) (*types.CommandResult, error) { - dew := utils2.NewDeploymentErrorsAndWarnings() - - allObjects := make(map[k8s2.ObjectRef]*uo.UnstructuredObject) - for _, d := range c.Deployments { - if !d.CheckInclusionForDeploy() { - continue - } - for _, o := range d.Objects { - allObjects[o.GetK8sRef()] = o - } - } - - containersAndImages := make(map[k8s2.ObjectRef][]types.FixedImage) - for _, fi := range c.Images.seenImages { - _, ok := allObjects[*fi.Object] - if !ok { - dew.AddError(*fi.Object, fmt.Errorf("object not found while trying to associate image with deployed object")) - continue - } - - containersAndImages[*fi.Object] = append(containersAndImages[*fi.Object], fi) - } - - wp := utils.NewWorkerPoolWithErrors(8) - defer wp.StopWait(false) - - doPokeImage := func(images []types.FixedImage, o *uo.UnstructuredObject) (*uo.UnstructuredObject, error) { - containers, _, _ := o.GetNestedObjectList("spec", "template", "spec", "containers") - - for _, image := range images { - for _, c := range containers { - containerName, _, _ := c.GetNestedString("name") - if image.Container != nil && containerName == *image.Container { - c.SetNestedField(image.ResultImage, "image") - } - } - } - return o, nil - } - - appliedObjects := make(map[k8s2.ObjectRef]*uo.UnstructuredObject) - var mutex sync.Mutex - - for ref, containers := range containersAndImages { - ref := ref - containers := containers - wp.Submit(func() error { - newObject, err := c.doReplaceObject(k, dew, ref, func(o *uo.UnstructuredObject) (*uo.UnstructuredObject, error) { - return doPokeImage(containers, o) - }) - if err != nil { - dew.AddError(ref, err) - } else { - mutex.Lock() - defer mutex.Unlock() - appliedObjects[ref] = newObject - } - return nil - }) - } - err := wp.StopWait(false) - if err != nil { - return nil, err - } - - du := utils2.NewDiffUtil(dew, c.Deployments, c.RemoteObjects, appliedObjects) - du.Diff(k) - - return &types.CommandResult{ - NewObjects: du.NewObjects, - ChangedObjects: du.ChangedObjects, - Errors: dew.GetErrorsList(), - Warnings: dew.GetWarningsList(), - SeenImages: c.Images.seenImages, - }, nil -} - func (c *DeploymentCollection) Downscale(k *k8s.K8sCluster) (*types.CommandResult, error) { dew := utils2.NewDeploymentErrorsAndWarnings() @@ -493,49 +413,6 @@ func (c *DeploymentCollection) FindOrphanObjects(k *k8s.K8sCluster) ([]k8s2.Obje return utils2.FindObjectsForDelete(k, c.getFilteredRemoteObjects(c.inclusion), c.inclusion.HasType("tags"), c.localObjectRefs()) } -func (c *DeploymentCollection) doReplaceObject(k *k8s.K8sCluster, dew *utils2.DeploymentErrorsAndWarnings, ref k8s2.ObjectRef, callback func(o *uo.UnstructuredObject) (*uo.UnstructuredObject, error)) (*uo.UnstructuredObject, error) { - firstCall := true - for true { - var remote *uo.UnstructuredObject - if firstCall { - remote = c.GetRemoteObject(ref) - } else { - o2, apiWarnings, err := k.GetSingleObject(ref) - dew.AddApiWarnings(ref, apiWarnings) - if err != nil && !errors.IsNotFound(err) { - return nil, err - } - remote = o2 - } - if remote == nil { - return remote, nil - } - firstCall = false - - remoteCopy := remote.Clone() - modified, err := callback(remoteCopy) - if err != nil { - return nil, err - } - if reflect.DeepEqual(remote.Object, modified.Object) { - return remote, nil - } - - result, apiWarnings, err := k.UpdateObject(modified, k8s.UpdateOptions{}) - dew.AddApiWarnings(ref, apiWarnings) - if err != nil { - if errors.IsConflict(err) { - log.Warningf("Conflict while patching %s. Retrying...", ref.String()) - continue - } else { - return nil, err - } - } - return result, nil - } - return nil, fmt.Errorf("unexpected end of loop") -} - func (c *DeploymentCollection) getFilteredRemoteObjects(inclusion *utils.Inclusion) []*uo.UnstructuredObject { var ret []*uo.UnstructuredObject diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index 8539cdb17..15523ab99 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -14,6 +14,7 @@ import ( log "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" + "reflect" "sync" "time" ) @@ -439,6 +440,55 @@ func (a *ApplyUtil) ApplyDeployments() { _ = wp.StopWait(false) } +func (a *ApplyUtil) ReplaceObject(ref k8s2.ObjectRef, firstVersion *uo.UnstructuredObject, callback func(o *uo.UnstructuredObject) (*uo.UnstructuredObject, error)) { + firstCall := true + for true { + var remote *uo.UnstructuredObject + if firstCall && firstVersion != nil { + remote = firstVersion + } else { + o2, apiWarnings, err := a.k.GetSingleObject(ref) + a.dew.AddApiWarnings(ref, apiWarnings) + if err != nil && !errors.IsNotFound(err) { + a.HandleError(ref, err) + return + } + remote = o2 + } + if remote == nil { + a.handleResult(remote, false) + return + } + firstCall = false + + remoteCopy := remote.Clone() + modified, err := callback(remoteCopy) + if err != nil { + a.HandleError(ref, err) + return + } + if reflect.DeepEqual(remote.Object, modified.Object) { + a.handleResult(remote, false) + return + } + + result, apiWarnings, err := a.k.UpdateObject(modified, k8s.UpdateOptions{}) + a.dew.AddApiWarnings(ref, apiWarnings) + if err != nil { + if errors.IsConflict(err) { + log.Warningf("Conflict while patching %s. Retrying...", ref.String()) + continue + } else { + a.HandleError(ref, err) + return + } + } + a.handleResult(result, false) + return + } + a.HandleError(ref, fmt.Errorf("unexpected end of loop")) +} + func (a *ApplyUtil) DoLog(d *deployment.DeploymentItem, level log.Level, s string, f ...interface{}) { s = fmt.Sprintf("%s: %s", d.RelToProjectItemDir, fmt.Sprintf(s, f...)) log.StandardLogger().Logf(level, s) @@ -461,4 +511,4 @@ func (a *ApplyUtil) GetAppliedHookObjects() []*types.RefAndObject { }) } return ret -} \ No newline at end of file +} From 709be538cf711e7bfc1c35880bb230c01422f9ef Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 14 Mar 2022 21:44:08 +0100 Subject: [PATCH 0511/2916] refactor: Move downscale to commands package --- cmd/kluctl/commands/cmd_downscale.go | 6 ++- pkg/deployment/commands/downscale.go | 68 +++++++++++++++++++++++++ pkg/deployment/deployment_collection.go | 64 ----------------------- 3 files changed, 73 insertions(+), 65 deletions(-) create mode 100644 pkg/deployment/commands/downscale.go diff --git a/cmd/kluctl/commands/cmd_downscale.go b/cmd/kluctl/commands/cmd_downscale.go index 6149a3883..72676ec4c 100644 --- a/cmd/kluctl/commands/cmd_downscale.go +++ b/cmd/kluctl/commands/cmd_downscale.go @@ -3,6 +3,7 @@ package commands import ( "fmt" "github.com/codablock/kluctl/cmd/kluctl/args" + "github.com/codablock/kluctl/pkg/deployment/commands" ) type downscaleCmd struct { @@ -39,7 +40,10 @@ func (cmd *downscaleCmd) Run() error { return fmt.Errorf("aborted") } } - result, err := ctx.deploymentCollection.Downscale(ctx.k) + + cmd2 := commands.NewDownscaleCommand(ctx.deploymentCollection) + + result, err := cmd2.Run(ctx.k) if err != nil { return err } diff --git a/pkg/deployment/commands/downscale.go b/pkg/deployment/commands/downscale.go new file mode 100644 index 000000000..c3ae717c2 --- /dev/null +++ b/pkg/deployment/commands/downscale.go @@ -0,0 +1,68 @@ +package commands + +import ( + "github.com/codablock/kluctl/pkg/deployment" + utils2 "github.com/codablock/kluctl/pkg/deployment/utils" + "github.com/codablock/kluctl/pkg/k8s" + "github.com/codablock/kluctl/pkg/types" + k8s2 "github.com/codablock/kluctl/pkg/types/k8s" + "github.com/codablock/kluctl/pkg/utils/uo" + "sync" +) + +type DownscaleCommand struct { + c *deployment.DeploymentCollection +} + +func NewDownscaleCommand(c *deployment.DeploymentCollection) *DownscaleCommand { + return &DownscaleCommand{ + c: c, + } +} + +func (cmd *DownscaleCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, error) { + var wg sync.WaitGroup + + dew := utils2.NewDeploymentErrorsAndWarnings() + au := utils2.NewApplyUtil(dew, cmd.c, k, utils2.ApplyUtilOptions{}) + + appliedObjects := make(map[k8s2.ObjectRef]*uo.UnstructuredObject) + var deletedObjects []k8s2.ObjectRef + + for _, d := range cmd.c.Deployments { + if !d.CheckInclusionForDeploy() { + continue + } + for _, o := range d.Objects { + o := o + ref := o.GetK8sRef() + wg.Add(1) + if utils2.IsDownscaleDelete(o) { + go func() { + defer wg.Done() + au.DeleteObject(ref, false) + }() + } else { + go func() { + defer wg.Done() + au.ReplaceObject(ref, cmd.c.GetRemoteObject(ref), func(remote *uo.UnstructuredObject) (*uo.UnstructuredObject, error) { + return utils2.DownscaleObject(remote, o) + }) + }() + } + } + } + wg.Wait() + + du := utils2.NewDiffUtil(dew, cmd.c.Deployments, cmd.c.RemoteObjects, appliedObjects) + du.Diff(k) + + return &types.CommandResult{ + NewObjects: du.NewObjects, + ChangedObjects: du.ChangedObjects, + DeletedObjects: deletedObjects, + Errors: dew.GetErrorsList(), + Warnings: dew.GetWarningsList(), + SeenImages: cmd.c.Images.SeenImages(false), + }, nil +} \ No newline at end of file diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index 0bf563cc5..805bcc133 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -303,70 +303,6 @@ func (c *DeploymentCollection) Prepare(k *k8s.K8sCluster) error { return nil } -func (c *DeploymentCollection) Downscale(k *k8s.K8sCluster) (*types.CommandResult, error) { - dew := utils2.NewDeploymentErrorsAndWarnings() - - wp := utils.NewWorkerPoolWithErrors(8) - defer wp.StopWait(false) - - appliedObjects := make(map[k8s2.ObjectRef]*uo.UnstructuredObject) - var deletedObjects []k8s2.ObjectRef - var mutex sync.Mutex - - for _, d := range c.Deployments { - if !d.CheckInclusionForDeploy() { - continue - } - for _, o := range d.Objects { - o := o - ref := o.GetK8sRef() - if utils2.IsDownscaleDelete(o) { - wp.Submit(func() error { - apiWarnings, err := k.DeleteSingleObject(ref, k8s.DeleteOptions{IgnoreNotFoundError: true}) - dew.AddApiWarnings(ref, apiWarnings) - if err != nil { - return err - } - mutex.Lock() - defer mutex.Unlock() - deletedObjects = append(deletedObjects, ref) - return nil - }) - } else { - wp.Submit(func() error { - o2, err := c.doReplaceObject(k, dew, ref, func(remote *uo.UnstructuredObject) (*uo.UnstructuredObject, error) { - return utils2.DownscaleObject(remote, o) - }) - if err != nil { - return err - } - mutex.Lock() - defer mutex.Unlock() - appliedObjects[ref] = o2 - return nil - }) - } - } - } - - err := wp.StopWait(false) - if err != nil { - return nil, err - } - - du := utils2.NewDiffUtil(dew, c.Deployments, c.RemoteObjects, appliedObjects) - du.Diff(k) - - return &types.CommandResult{ - NewObjects: du.NewObjects, - ChangedObjects: du.ChangedObjects, - DeletedObjects: deletedObjects, - Errors: dew.GetErrorsList(), - Warnings: dew.GetWarningsList(), - SeenImages: c.Images.seenImages, - }, nil -} - func (c *DeploymentCollection) Validate(k *k8s.K8sCluster) *types.ValidateResult { var result types.ValidateResult From bd09633cf2d83e4d2c63b43103c4f29f0df02ff4 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 14 Mar 2022 21:47:54 +0100 Subject: [PATCH 0512/2916] refactor: Move validate to commands package --- cmd/kluctl/commands/cmd_validate.go | 5 ++- pkg/deployment/commands/validate.go | 56 +++++++++++++++++++++++++ pkg/deployment/deployment_collection.go | 38 ----------------- 3 files changed, 60 insertions(+), 39 deletions(-) create mode 100644 pkg/deployment/commands/validate.go diff --git a/cmd/kluctl/commands/cmd_validate.go b/cmd/kluctl/commands/cmd_validate.go index 29581316b..e5c548a07 100644 --- a/cmd/kluctl/commands/cmd_validate.go +++ b/cmd/kluctl/commands/cmd_validate.go @@ -3,6 +3,7 @@ package commands import ( "fmt" "github.com/codablock/kluctl/cmd/kluctl/args" + "github.com/codablock/kluctl/pkg/deployment/commands" "os" "time" ) @@ -37,7 +38,9 @@ func (cmd *validateCmd) Run() error { return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { startTime := time.Now() for true { - result := ctx.deploymentCollection.Validate(ctx.k) + cmd2 := commands.NewValidateCommand(ctx.deploymentCollection) + + result := cmd2.Run(ctx.k) failed := len(result.Errors) != 0 || (cmd.WarningsAsErrors && len(result.Warnings) != 0) err := outputValidateResult(cmd.Output, result) diff --git a/pkg/deployment/commands/validate.go b/pkg/deployment/commands/validate.go new file mode 100644 index 000000000..1912b5e78 --- /dev/null +++ b/pkg/deployment/commands/validate.go @@ -0,0 +1,56 @@ +package commands + +import ( + "github.com/codablock/kluctl/pkg/deployment" + utils2 "github.com/codablock/kluctl/pkg/deployment/utils" + "github.com/codablock/kluctl/pkg/k8s" + "github.com/codablock/kluctl/pkg/types" + "github.com/codablock/kluctl/pkg/validation" +) + +type ValidateCommand struct { + c *deployment.DeploymentCollection +} + +func NewValidateCommand(c *deployment.DeploymentCollection) *ValidateCommand { + return &ValidateCommand{ + c: c, + } +} + +func (cmd *ValidateCommand) Run(k *k8s.K8sCluster) *types.ValidateResult { + var result types.ValidateResult + + dew := utils2.NewDeploymentErrorsAndWarnings() + + a := utils2.NewApplyUtil(dew, cmd.c, k, utils2.ApplyUtilOptions{}) + h := utils2.NewHooksUtil(a) + for _, d := range cmd.c.Deployments { + if !d.CheckInclusionForDeploy() { + continue + } + for _, o := range d.Objects { + hook := h.GetHook(o) + if hook != nil && !hook.IsPersistent() { + continue + } + + ref := o.GetK8sRef() + + remoteObject := cmd.c.GetRemoteObject(ref) + if remoteObject == nil { + result.Errors = append(result.Errors, types.DeploymentError{Ref: ref, Error: "object not found"}) + continue + } + r := validation.ValidateObject(remoteObject, true) + result.Errors = append(result.Errors, r.Errors...) + result.Warnings = append(result.Warnings, r.Warnings...) + result.Results = append(result.Results, r.Results...) + } + } + + result.Warnings = append(result.Warnings, dew.GetWarningsList()...) + result.Errors = append(result.Errors, dew.GetErrorsList()...) + + return &result +} diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index 805bcc133..45a9e830a 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -9,7 +9,6 @@ import ( k8s2 "github.com/codablock/kluctl/pkg/types/k8s" "github.com/codablock/kluctl/pkg/utils" "github.com/codablock/kluctl/pkg/utils/uo" - "github.com/codablock/kluctl/pkg/validation" log "github.com/sirupsen/logrus" "golang.org/x/sync/semaphore" "path/filepath" @@ -303,43 +302,6 @@ func (c *DeploymentCollection) Prepare(k *k8s.K8sCluster) error { return nil } -func (c *DeploymentCollection) Validate(k *k8s.K8sCluster) *types.ValidateResult { - var result types.ValidateResult - - dew := utils2.NewDeploymentErrorsAndWarnings() - - a := utils2.NewApplyUtil(dew, c, k, utils2.ApplyUtilOptions{}) - h := utils2.NewHooksUtil(a) - for _, d := range c.Deployments { - if !d.CheckInclusionForDeploy() { - continue - } - for _, o := range d.Objects { - hook := h.GetHook(o) - if hook != nil && !hook.IsPersistent() { - continue - } - - ref := o.GetK8sRef() - - remoteObject := c.GetRemoteObject(ref) - if remoteObject == nil { - result.Errors = append(result.Errors, types.DeploymentError{Ref: ref, Error: "object not found"}) - continue - } - r := validation.ValidateObject(remoteObject, true) - result.Errors = append(result.Errors, r.Errors...) - result.Warnings = append(result.Warnings, r.Warnings...) - result.Results = append(result.Results, r.Results...) - } - } - - result.Warnings = append(result.Warnings, dew.GetWarningsList()...) - result.Errors = append(result.Errors, dew.GetErrorsList()...) - - return &result -} - func (c *DeploymentCollection) FindDeleteObjects(k *k8s.K8sCluster) ([]k8s2.ObjectRef, error) { return utils2.FindObjectsForDelete(k, c.getFilteredRemoteObjects(c.inclusion), c.inclusion.HasType("tags"), nil) } From 1aca57fa93ed5ec1ed2eec787f3b60642de8cc54 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 14 Mar 2022 22:36:25 +0100 Subject: [PATCH 0513/2916] refactor: Introduce RemoteObjectsUtil and move delete/prune to commands package --- cmd/kluctl/commands/cmd_delete.go | 4 +- cmd/kluctl/commands/cmd_prune.go | 4 +- cmd/kluctl/commands/cmd_validate.go | 16 ++- pkg/deployment/commands/delete.go | 30 ++++ pkg/deployment/commands/deploy.go | 12 +- pkg/deployment/commands/diff.go | 12 +- pkg/deployment/commands/downscale.go | 18 ++- pkg/deployment/commands/poke_images.go | 12 +- pkg/deployment/commands/prune.go | 34 +++++ pkg/deployment/commands/validate.go | 35 +++-- pkg/deployment/deployment_collection.go | 141 ++----------------- pkg/deployment/deployment_item.go | 10 +- pkg/deployment/deployment_project.go | 4 +- pkg/deployment/utils/apply_utils.go | 8 +- pkg/deployment/utils/diff_utils.go | 8 +- pkg/deployment/utils/errors_holder.go | 4 +- pkg/deployment/utils/remote_objects_utils.go | 119 ++++++++++++++++ 17 files changed, 293 insertions(+), 178 deletions(-) create mode 100644 pkg/deployment/commands/delete.go create mode 100644 pkg/deployment/commands/prune.go create mode 100644 pkg/deployment/utils/remote_objects_utils.go diff --git a/cmd/kluctl/commands/cmd_delete.go b/cmd/kluctl/commands/cmd_delete.go index 8cbac66b9..cbb029f49 100644 --- a/cmd/kluctl/commands/cmd_delete.go +++ b/cmd/kluctl/commands/cmd_delete.go @@ -3,6 +3,7 @@ package commands import ( "fmt" "github.com/codablock/kluctl/cmd/kluctl/args" + "github.com/codablock/kluctl/pkg/deployment/commands" "github.com/codablock/kluctl/pkg/deployment/utils" "github.com/codablock/kluctl/pkg/k8s" "github.com/codablock/kluctl/pkg/types" @@ -39,7 +40,8 @@ func (cmd *deleteCmd) Run() error { dryRunArgs: &cmd.DryRunFlags, } return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { - objects, err := ctx.deploymentCollection.FindDeleteObjects(ctx.k) + cmd2 := commands.NewDeleteCommand(ctx.deploymentCollection) + objects, err := cmd2.Run(ctx.k) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_prune.go b/cmd/kluctl/commands/cmd_prune.go index 92a5b7948..5db8234e2 100644 --- a/cmd/kluctl/commands/cmd_prune.go +++ b/cmd/kluctl/commands/cmd_prune.go @@ -3,6 +3,7 @@ package commands import ( "fmt" "github.com/codablock/kluctl/cmd/kluctl/args" + "github.com/codablock/kluctl/pkg/deployment/commands" ) type pruneCmd struct { @@ -39,7 +40,8 @@ func (cmd *pruneCmd) Run() error { } func (cmd *pruneCmd) runCmdPrune(ctx *commandCtx) error { - objects, err := ctx.deploymentCollection.FindOrphanObjects(ctx.k) + cmd2 := commands.NewPruneCommand(ctx.deploymentCollection) + objects, err := cmd2.Run(ctx.k) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_validate.go b/cmd/kluctl/commands/cmd_validate.go index e5c548a07..0f5cf1f6b 100644 --- a/cmd/kluctl/commands/cmd_validate.go +++ b/cmd/kluctl/commands/cmd_validate.go @@ -37,13 +37,15 @@ func (cmd *validateCmd) Run() error { } return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { startTime := time.Now() + cmd2 := commands.NewValidateCommand(ctx.deploymentCollection) for true { - cmd2 := commands.NewValidateCommand(ctx.deploymentCollection) - - result := cmd2.Run(ctx.k) + result, err := cmd2.Run(ctx.k) + if err != nil { + return err + } failed := len(result.Errors) != 0 || (cmd.WarningsAsErrors && len(result.Warnings) != 0) - err := outputValidateResult(cmd.Output, result) + err = outputValidateResult(cmd.Output, result) if err != nil { return err } @@ -61,13 +63,13 @@ func (cmd *validateCmd) Run() error { // Need to force re-requesting these objects for _, e := range result.Results { - ctx.deploymentCollection.ForgetRemoteObject(e.Ref) + cmd2.ForgetRemoteObject(e.Ref) } for _, e := range result.Warnings { - ctx.deploymentCollection.ForgetRemoteObject(e.Ref) + cmd2.ForgetRemoteObject(e.Ref) } for _, e := range result.Errors { - ctx.deploymentCollection.ForgetRemoteObject(e.Ref) + cmd2.ForgetRemoteObject(e.Ref) } } return nil diff --git a/pkg/deployment/commands/delete.go b/pkg/deployment/commands/delete.go new file mode 100644 index 000000000..9ab1bece7 --- /dev/null +++ b/pkg/deployment/commands/delete.go @@ -0,0 +1,30 @@ +package commands + +import ( + "github.com/codablock/kluctl/pkg/deployment" + utils2 "github.com/codablock/kluctl/pkg/deployment/utils" + "github.com/codablock/kluctl/pkg/k8s" + k8s2 "github.com/codablock/kluctl/pkg/types/k8s" +) + +type DeleteCommand struct { + c *deployment.DeploymentCollection +} + +func NewDeleteCommand(c *deployment.DeploymentCollection) *DeleteCommand { + return &DeleteCommand{ + c: c, + } +} + +func (cmd *DeleteCommand) Run(k *k8s.K8sCluster) ([]k8s2.ObjectRef, error) { + dew := utils2.NewDeploymentErrorsAndWarnings() + + ru := utils2.NewRemoteObjectsUtil(dew) + err := ru.UpdateRemoteObjects(k, cmd.c.Project.GetCommonLabels(), cmd.c.LocalObjectRefs()) + if err != nil { + return nil, err + } + + return utils2.FindObjectsForDelete(k, ru.GetFilteredRemoteObjects(cmd.c.Inclusion), cmd.c.Inclusion.HasType("tags"), nil) +} diff --git a/pkg/deployment/commands/deploy.go b/pkg/deployment/commands/deploy.go index 466ebc3e6..ffb953119 100644 --- a/pkg/deployment/commands/deploy.go +++ b/pkg/deployment/commands/deploy.go @@ -27,6 +27,12 @@ func NewDeployCommand(c *deployment.DeploymentCollection) *DeployCommand { func (cmd *DeployCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, error) { dew := utils2.NewDeploymentErrorsAndWarnings() + ru := utils2.NewRemoteObjectsUtil(dew) + err := ru.UpdateRemoteObjects(k, cmd.c.Project.GetCommonLabels(), cmd.c.LocalObjectRefs()) + if err != nil { + return nil, err + } + o := utils2.ApplyUtilOptions{ ForceApply: cmd.ForceApply, ReplaceOnError: cmd.ReplaceOnError, @@ -35,13 +41,13 @@ func (cmd *DeployCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, error) { AbortOnError: cmd.AbortOnError, HookTimeout: cmd.HookTimeout, } - au := utils2.NewApplyUtil(dew, cmd.c, k, o) + au := utils2.NewApplyUtil(dew, cmd.c, ru, k, o) au.ApplyDeployments() - du := utils2.NewDiffUtil(dew, cmd.c.Deployments, cmd.c.RemoteObjects, au.AppliedObjects) + du := utils2.NewDiffUtil(dew, cmd.c.Deployments, ru, au.AppliedObjects) du.Diff(k) - orphanObjects, err := cmd.c.FindOrphanObjects(k) + orphanObjects, err := FindOrphanObjects(k, ru, cmd.c) if err != nil { return nil, err } diff --git a/pkg/deployment/commands/diff.go b/pkg/deployment/commands/diff.go index d08c1fdc9..bd39426ae 100644 --- a/pkg/deployment/commands/diff.go +++ b/pkg/deployment/commands/diff.go @@ -27,6 +27,12 @@ func NewDiffCommand(c *deployment.DeploymentCollection) *DiffCommand { func (cmd *DiffCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, error) { dew := utils.NewDeploymentErrorsAndWarnings() + ru := utils.NewRemoteObjectsUtil(dew) + err := ru.UpdateRemoteObjects(k, cmd.c.Project.GetCommonLabels(), cmd.c.LocalObjectRefs()) + if err != nil { + return nil, err + } + o := utils.ApplyUtilOptions{ ForceApply: cmd.ForceApply, ReplaceOnError: cmd.ReplaceOnError, @@ -35,16 +41,16 @@ func (cmd *DiffCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, error) { AbortOnError: false, HookTimeout: 0, } - au := utils.NewApplyUtil(dew, cmd.c, k, o) + au := utils.NewApplyUtil(dew, cmd.c, ru, k, o) au.ApplyDeployments() - du := utils.NewDiffUtil(dew, cmd.c.Deployments, cmd.c.RemoteObjects, au.AppliedObjects) + du := utils.NewDiffUtil(dew, cmd.c.Deployments, ru, au.AppliedObjects) du.IgnoreTags = cmd.IgnoreTags du.IgnoreLabels = cmd.IgnoreLabels du.IgnoreAnnotations = cmd.IgnoreAnnotations du.Diff(k) - orphanObjects, err := cmd.c.FindOrphanObjects(k) + orphanObjects, err := FindOrphanObjects(k, ru, cmd.c) if err != nil { return nil, err } diff --git a/pkg/deployment/commands/downscale.go b/pkg/deployment/commands/downscale.go index c3ae717c2..8263374f2 100644 --- a/pkg/deployment/commands/downscale.go +++ b/pkg/deployment/commands/downscale.go @@ -24,10 +24,16 @@ func (cmd *DownscaleCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, error var wg sync.WaitGroup dew := utils2.NewDeploymentErrorsAndWarnings() - au := utils2.NewApplyUtil(dew, cmd.c, k, utils2.ApplyUtilOptions{}) + + ru := utils2.NewRemoteObjectsUtil(dew) + err := ru.UpdateRemoteObjects(k, cmd.c.Project.GetCommonLabels(), cmd.c.LocalObjectRefs()) + if err != nil { + return nil, err + } + + au := utils2.NewApplyUtil(dew, cmd.c, ru, k, utils2.ApplyUtilOptions{}) appliedObjects := make(map[k8s2.ObjectRef]*uo.UnstructuredObject) - var deletedObjects []k8s2.ObjectRef for _, d := range cmd.c.Deployments { if !d.CheckInclusionForDeploy() { @@ -45,7 +51,7 @@ func (cmd *DownscaleCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, error } else { go func() { defer wg.Done() - au.ReplaceObject(ref, cmd.c.GetRemoteObject(ref), func(remote *uo.UnstructuredObject) (*uo.UnstructuredObject, error) { + au.ReplaceObject(ref, ru.GetRemoteObject(ref), func(remote *uo.UnstructuredObject) (*uo.UnstructuredObject, error) { return utils2.DownscaleObject(remote, o) }) }() @@ -54,15 +60,15 @@ func (cmd *DownscaleCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, error } wg.Wait() - du := utils2.NewDiffUtil(dew, cmd.c.Deployments, cmd.c.RemoteObjects, appliedObjects) + du := utils2.NewDiffUtil(dew, cmd.c.Deployments, ru, appliedObjects) du.Diff(k) return &types.CommandResult{ NewObjects: du.NewObjects, ChangedObjects: du.ChangedObjects, - DeletedObjects: deletedObjects, + DeletedObjects: au.GetDeletedObjectsList(), Errors: dew.GetErrorsList(), Warnings: dew.GetWarningsList(), SeenImages: cmd.c.Images.SeenImages(false), }, nil -} \ No newline at end of file +} diff --git a/pkg/deployment/commands/poke_images.go b/pkg/deployment/commands/poke_images.go index db290012b..df96f335b 100644 --- a/pkg/deployment/commands/poke_images.go +++ b/pkg/deployment/commands/poke_images.go @@ -26,6 +26,12 @@ func (cmd *PokeImagesCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, erro dew := utils2.NewDeploymentErrorsAndWarnings() + ru := utils2.NewRemoteObjectsUtil(dew) + err := ru.UpdateRemoteObjects(k, cmd.c.Project.GetCommonLabels(), cmd.c.LocalObjectRefs()) + if err != nil { + return nil, err + } + allObjects := make(map[k8s2.ObjectRef]*uo.UnstructuredObject) for _, d := range cmd.c.Deployments { if !d.CheckInclusionForDeploy() { @@ -61,7 +67,7 @@ func (cmd *PokeImagesCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, erro return o, nil } - au := utils2.NewApplyUtil(dew, cmd.c, k, utils2.ApplyUtilOptions{}) + au := utils2.NewApplyUtil(dew, cmd.c, ru, k, utils2.ApplyUtilOptions{}) for ref, containers := range containersAndImages { ref := ref @@ -69,14 +75,14 @@ func (cmd *PokeImagesCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, erro wg.Add(1) go func() { defer wg.Done() - au.ReplaceObject(ref, cmd.c.GetRemoteObject(ref), func(o *uo.UnstructuredObject) (*uo.UnstructuredObject, error) { + au.ReplaceObject(ref, ru.GetRemoteObject(ref), func(o *uo.UnstructuredObject) (*uo.UnstructuredObject, error) { return doPokeImage(containers, o) }) }() } wg.Wait() - du := utils2.NewDiffUtil(dew, cmd.c.Deployments, cmd.c.RemoteObjects, au.AppliedObjects) + du := utils2.NewDiffUtil(dew, cmd.c.Deployments, ru, au.AppliedObjects) du.Diff(k) return &types.CommandResult{ diff --git a/pkg/deployment/commands/prune.go b/pkg/deployment/commands/prune.go new file mode 100644 index 000000000..c9ea049a3 --- /dev/null +++ b/pkg/deployment/commands/prune.go @@ -0,0 +1,34 @@ +package commands + +import ( + "github.com/codablock/kluctl/pkg/deployment" + utils2 "github.com/codablock/kluctl/pkg/deployment/utils" + "github.com/codablock/kluctl/pkg/k8s" + k8s2 "github.com/codablock/kluctl/pkg/types/k8s" +) + +type PruneCommand struct { + c *deployment.DeploymentCollection +} + +func NewPruneCommand(c *deployment.DeploymentCollection) *PruneCommand { + return &PruneCommand{ + c: c, + } +} + +func (cmd *PruneCommand) Run(k *k8s.K8sCluster) ([]k8s2.ObjectRef, error) { + dew := utils2.NewDeploymentErrorsAndWarnings() + + ru := utils2.NewRemoteObjectsUtil(dew) + err := ru.UpdateRemoteObjects(k, cmd.c.Project.GetCommonLabels(), cmd.c.LocalObjectRefs()) + if err != nil { + return nil, err + } + + return FindOrphanObjects(k, ru, cmd.c) +} + +func FindOrphanObjects(k *k8s.K8sCluster, ru *utils2.RemoteObjectUtils, c *deployment.DeploymentCollection) ([]k8s2.ObjectRef, error) { + return utils2.FindObjectsForDelete(k, ru.GetFilteredRemoteObjects(c.Inclusion), c.Inclusion.HasType("tags"), c.LocalObjectRefs()) +} diff --git a/pkg/deployment/commands/validate.go b/pkg/deployment/commands/validate.go index 1912b5e78..15efd18ca 100644 --- a/pkg/deployment/commands/validate.go +++ b/pkg/deployment/commands/validate.go @@ -5,25 +5,36 @@ import ( utils2 "github.com/codablock/kluctl/pkg/deployment/utils" "github.com/codablock/kluctl/pkg/k8s" "github.com/codablock/kluctl/pkg/types" + k8s2 "github.com/codablock/kluctl/pkg/types/k8s" "github.com/codablock/kluctl/pkg/validation" ) type ValidateCommand struct { - c *deployment.DeploymentCollection + c *deployment.DeploymentCollection + dew *utils2.DeploymentErrorsAndWarnings + ru *utils2.RemoteObjectUtils } func NewValidateCommand(c *deployment.DeploymentCollection) *ValidateCommand { - return &ValidateCommand{ - c: c, + cmd := &ValidateCommand{ + c: c, + dew: utils2.NewDeploymentErrorsAndWarnings(), } + cmd.ru = utils2.NewRemoteObjectsUtil(cmd.dew) + return cmd } -func (cmd *ValidateCommand) Run(k *k8s.K8sCluster) *types.ValidateResult { +func (cmd *ValidateCommand) Run(k *k8s.K8sCluster) (*types.ValidateResult, error) { var result types.ValidateResult - dew := utils2.NewDeploymentErrorsAndWarnings() + cmd.dew.Init() - a := utils2.NewApplyUtil(dew, cmd.c, k, utils2.ApplyUtilOptions{}) + err := cmd.ru.UpdateRemoteObjects(k, cmd.c.Project.GetCommonLabels(), cmd.c.LocalObjectRefs()) + if err != nil { + return nil, err + } + + a := utils2.NewApplyUtil(cmd.dew, cmd.c, cmd.ru, k, utils2.ApplyUtilOptions{}) h := utils2.NewHooksUtil(a) for _, d := range cmd.c.Deployments { if !d.CheckInclusionForDeploy() { @@ -37,7 +48,7 @@ func (cmd *ValidateCommand) Run(k *k8s.K8sCluster) *types.ValidateResult { ref := o.GetK8sRef() - remoteObject := cmd.c.GetRemoteObject(ref) + remoteObject := cmd.ru.GetRemoteObject(ref) if remoteObject == nil { result.Errors = append(result.Errors, types.DeploymentError{Ref: ref, Error: "object not found"}) continue @@ -49,8 +60,12 @@ func (cmd *ValidateCommand) Run(k *k8s.K8sCluster) *types.ValidateResult { } } - result.Warnings = append(result.Warnings, dew.GetWarningsList()...) - result.Errors = append(result.Errors, dew.GetErrorsList()...) + result.Warnings = append(result.Warnings, cmd.dew.GetWarningsList()...) + result.Errors = append(result.Errors, cmd.dew.GetErrorsList()...) + + return &result, nil +} - return &result +func (cmd *ValidateCommand) ForgetRemoteObject(ref k8s2.ObjectRef) { + cmd.ru.ForgetRemoteObject(ref) } diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index 45a9e830a..46f1e9394 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -3,12 +3,10 @@ package deployment import ( "context" "fmt" - utils2 "github.com/codablock/kluctl/pkg/deployment/utils" "github.com/codablock/kluctl/pkg/k8s" "github.com/codablock/kluctl/pkg/types" k8s2 "github.com/codablock/kluctl/pkg/types/k8s" "github.com/codablock/kluctl/pkg/utils" - "github.com/codablock/kluctl/pkg/utils/uo" log "github.com/sirupsen/logrus" "golang.org/x/sync/semaphore" "path/filepath" @@ -16,25 +14,23 @@ import ( ) type DeploymentCollection struct { - Project *DeploymentProject - Images *Images - inclusion *utils.Inclusion + Project *DeploymentProject + Images *Images + Inclusion *utils.Inclusion RenderDir string forSeal bool - Deployments []*DeploymentItem - RemoteObjects map[k8s2.ObjectRef]*uo.UnstructuredObject - mutex sync.Mutex + Deployments []*DeploymentItem + mutex sync.Mutex } func NewDeploymentCollection(project *DeploymentProject, images *Images, inclusion *utils.Inclusion, renderDir string, forSeal bool) (*DeploymentCollection, error) { dc := &DeploymentCollection{ - Project: project, - Images: images, - inclusion: inclusion, - RenderDir: renderDir, - forSeal: forSeal, - RemoteObjects: map[k8s2.ObjectRef]*uo.UnstructuredObject{}, + Project: project, + Images: images, + Inclusion: inclusion, + RenderDir: renderDir, + forSeal: forSeal, } indexes := make(map[string]int) @@ -200,65 +196,7 @@ func (c *DeploymentCollection) buildKustomizeObjects(k *k8s.K8sCluster) error { return nil } -func (c *DeploymentCollection) updateRemoteObjects(k *k8s.K8sCluster, dew *utils2.DeploymentErrorsAndWarnings) error { - if k == nil { - return nil - } - - log.Infof("Getting remote objects by commonLabels") - allObjects, apiWarnings, err := k.ListAllObjects([]string{"get"}, "", c.Project.getCommonLabels(), false) - for gvk, aw := range apiWarnings { - dew.AddApiWarnings(k8s2.ObjectRef{GVK: gvk}, aw) - } - if err != nil { - return err - } - - for _, o := range allObjects { - c.addRemoteObject(o) - } - - notFoundRefsMap := make(map[k8s2.ObjectRef]bool) - var notFoundRefsList []k8s2.ObjectRef - for ref := range c.localObjectsByRef() { - if _, ok := c.RemoteObjects[ref]; !ok { - if _, ok = notFoundRefsMap[ref]; !ok { - notFoundRefsMap[ref] = true - notFoundRefsList = append(notFoundRefsList, ref) - } - } - } - - if len(notFoundRefsList) != 0 { - log.Infof("Getting %d additional remote objects", len(notFoundRefsList)) - r, apiWarnings, err := k.GetObjectsByRefs(notFoundRefsList) - for ref, aw := range apiWarnings { - dew.AddApiWarnings(ref, aw) - } - if err != nil { - return err - } - for _, o := range r { - c.addRemoteObject(o) - } - } - return nil -} - -func (c *DeploymentCollection) addRemoteObject(o *uo.UnstructuredObject) { - c.RemoteObjects[o.GetK8sRef()] = o -} - -func (c *DeploymentCollection) GetRemoteObject(ref k8s2.ObjectRef) *uo.UnstructuredObject { - o, _ := c.RemoteObjects[ref] - return o -} - -func (c *DeploymentCollection) ForgetRemoteObject(ref k8s2.ObjectRef) { - delete(c.RemoteObjects, ref) -} - -func (c *DeploymentCollection) localObjectsByRef() map[k8s2.ObjectRef]bool { +func (c *DeploymentCollection) LocalObjectsByRef() map[k8s2.ObjectRef]bool { ret := make(map[k8s2.ObjectRef]bool) for _, d := range c.Deployments { for _, o := range d.Objects { @@ -268,17 +206,15 @@ func (c *DeploymentCollection) localObjectsByRef() map[k8s2.ObjectRef]bool { return ret } -func (c *DeploymentCollection) localObjectRefs() []k8s2.ObjectRef { +func (c *DeploymentCollection) LocalObjectRefs() []k8s2.ObjectRef { var ret []k8s2.ObjectRef - for ref := range c.localObjectsByRef() { + for ref := range c.LocalObjectsByRef() { ret = append(ret, ref) } return ret } func (c *DeploymentCollection) Prepare(k *k8s.K8sCluster) error { - dew := utils2.NewDeploymentErrorsAndWarnings() - err := c.RenderDeployments(k) if err != nil { return err @@ -291,60 +227,9 @@ func (c *DeploymentCollection) Prepare(k *k8s.K8sCluster) error { if err != nil { return err } - err = c.updateRemoteObjects(k, dew) - if err != nil { - return err - } - err = dew.GetMultiError() - if err != nil { - return err - } return nil } -func (c *DeploymentCollection) FindDeleteObjects(k *k8s.K8sCluster) ([]k8s2.ObjectRef, error) { - return utils2.FindObjectsForDelete(k, c.getFilteredRemoteObjects(c.inclusion), c.inclusion.HasType("tags"), nil) -} - -func (c *DeploymentCollection) FindOrphanObjects(k *k8s.K8sCluster) ([]k8s2.ObjectRef, error) { - log.Infof("Searching for orphan objects") - return utils2.FindObjectsForDelete(k, c.getFilteredRemoteObjects(c.inclusion), c.inclusion.HasType("tags"), c.localObjectRefs()) -} - -func (c *DeploymentCollection) getFilteredRemoteObjects(inclusion *utils.Inclusion) []*uo.UnstructuredObject { - var ret []*uo.UnstructuredObject - - c.mutex.Lock() - defer c.mutex.Unlock() - - for _, o := range c.RemoteObjects { - iv := c.getInclusionEntries(o) - if inclusion.CheckIncluded(iv, false) { - ret = append(ret, o) - } - } - - return ret -} - -func (c *DeploymentCollection) getInclusionEntries(o *uo.UnstructuredObject) []utils.InclusionEntry { - var iv []utils.InclusionEntry - for _, v := range o.GetK8sLabelsWithRegex("^kluctl.io/tag-\\d+$") { - iv = append(iv, utils.InclusionEntry{ - Type: "tag", - Value: v, - }) - } - - if itemDir := o.GetK8sAnnotation("kluctl.io/kustomize_dir"); itemDir != nil { - iv = append(iv, utils.InclusionEntry{ - Type: "deploymentItemDir", - Value: *itemDir, - }) - } - return iv -} - func (c *DeploymentCollection) FindRenderedImages() map[k8s2.ObjectRef][]string { ret := make(map[k8s2.ObjectRef][]string) for _, d := range c.Deployments { diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 6f8b00ae1..d808dc3d7 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -79,7 +79,7 @@ func NewDeploymentItem(project *DeploymentProject, collection *DeploymentCollect } func (di *DeploymentItem) getCommonLabels() map[string]string { - l := di.Project.getCommonLabels() + l := di.Project.GetCommonLabels() i := 0 for _, t := range di.getTags().ListKeys() { l[fmt.Sprintf("kluctl.io/tag-%d", i)] = t @@ -247,7 +247,7 @@ func (di *DeploymentItem) buildInclusionEntries() []utils.InclusionEntry { } func (di *DeploymentItem) CheckInclusionForDeploy() bool { - if di.collection.inclusion == nil { + if di.collection.Inclusion == nil { return true } if di.Config.OnlyRender != nil && *di.Config.OnlyRender { @@ -257,16 +257,16 @@ func (di *DeploymentItem) CheckInclusionForDeploy() bool { return true } values := di.buildInclusionEntries() - return di.collection.inclusion.CheckIncluded(values, false) + return di.collection.Inclusion.CheckIncluded(values, false) } func (di *DeploymentItem) checkInclusionForDelete() bool { - if di.collection.inclusion == nil { + if di.collection.Inclusion == nil { return true } skipDeleteIfTags := di.Config.SkipDeleteIfTags != nil && *di.Config.SkipDeleteIfTags values := di.buildInclusionEntries() - return di.collection.inclusion.CheckIncluded(values, skipDeleteIfTags) + return di.collection.Inclusion.CheckIncluded(values, skipDeleteIfTags) } func (di *DeploymentItem) prepareKustomizationYaml() error { diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go index e0edd77df..8d4d83e04 100644 --- a/pkg/deployment/deployment_project.go +++ b/pkg/deployment/deployment_project.go @@ -121,7 +121,7 @@ func (p *DeploymentProject) loadConfig(k *k8s.K8sCluster) error { return err } - if len(p.getCommonLabels()) == 0 { + if len(p.GetCommonLabels()) == 0 { return fmt.Errorf("no commonLabels in root deployment. This is not allowed") } if len(p.Config.DeleteByLabels) != 0 { @@ -260,7 +260,7 @@ func (p *DeploymentProject) getRenderSearchDirs() []string { return ret } -func (p *DeploymentProject) getCommonLabels() map[string]string { +func (p *DeploymentProject) GetCommonLabels() map[string]string { ret := make(map[string]string) parents := p.getParents() for i, _ := range parents { diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index 15523ab99..861ca461d 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -31,6 +31,7 @@ type ApplyUtilOptions struct { type ApplyUtil struct { dew *DeploymentErrorsAndWarnings deploymentCollection *deployment.DeploymentCollection + ru *RemoteObjectUtils k *k8s.K8sCluster o ApplyUtilOptions @@ -43,10 +44,11 @@ type ApplyUtil struct { mutex sync.Mutex } -func NewApplyUtil(dew *DeploymentErrorsAndWarnings, deploymentCollection *deployment.DeploymentCollection, k *k8s.K8sCluster, o ApplyUtilOptions) *ApplyUtil { +func NewApplyUtil(dew *DeploymentErrorsAndWarnings, deploymentCollection *deployment.DeploymentCollection, ru *RemoteObjectUtils, k *k8s.K8sCluster, o ApplyUtilOptions) *ApplyUtil { return &ApplyUtil{ dew: dew, deploymentCollection: deploymentCollection, + ru: ru, k: k, o: o, AppliedObjects: map[k8s2.ObjectRef]*uo.UnstructuredObject{}, @@ -223,7 +225,7 @@ func (a *ApplyUtil) ApplyObject(x *uo.UnstructuredObject, replaced bool, hook bo log2.Debugf("applying object") x = a.k.FixObjectForPatch(x) - remoteObject := a.deploymentCollection.GetRemoteObject(ref) + remoteObject := a.ru.GetRemoteObject(ref) if a.o.DryRun && replaced && remoteObject != nil { // Let's simulate that this object was deleted in dry-run mode. If we'd actually try a dry-run apply with @@ -371,7 +373,7 @@ func (a *ApplyUtil) applyDeploymentItem(d *deployment.DeploymentItem) { initialDeploy := true for _, o := range d.Objects { - if a.deploymentCollection.GetRemoteObject(o.GetK8sRef()) != nil { + if a.ru.GetRemoteObject(o.GetK8sRef()) != nil { initialDeploy = false } } diff --git a/pkg/deployment/utils/diff_utils.go b/pkg/deployment/utils/diff_utils.go index 173bf4d7b..414e32f03 100644 --- a/pkg/deployment/utils/diff_utils.go +++ b/pkg/deployment/utils/diff_utils.go @@ -15,7 +15,7 @@ type diffUtil struct { dew *DeploymentErrorsAndWarnings deployments []*deployment.DeploymentItem appliedObjects map[k8s2.ObjectRef]*uo.UnstructuredObject - remoteObjects map[k8s2.ObjectRef]*uo.UnstructuredObject + ru *RemoteObjectUtils IgnoreTags bool IgnoreLabels bool @@ -27,11 +27,11 @@ type diffUtil struct { mutex sync.Mutex } -func NewDiffUtil(dew *DeploymentErrorsAndWarnings, deployments []*deployment.DeploymentItem, remoteObjects map[k8s2.ObjectRef]*uo.UnstructuredObject, appliedObjects map[k8s2.ObjectRef]*uo.UnstructuredObject) *diffUtil { +func NewDiffUtil(dew *DeploymentErrorsAndWarnings, deployments []*deployment.DeploymentItem, ru *RemoteObjectUtils, appliedObjects map[k8s2.ObjectRef]*uo.UnstructuredObject) *diffUtil { return &diffUtil{ dew: dew, deployments: deployments, - remoteObjects: remoteObjects, + ru: ru, appliedObjects: appliedObjects, } } @@ -106,7 +106,7 @@ func (u *diffUtil) diffObject(k *k8s.K8sCluster, lo *uo.UnstructuredObject, ao * func (u *diffUtil) calcRemoteObjectsForDiff() { u.remoteDiffObjects = make(map[k8s2.ObjectRef]*uo.UnstructuredObject) - for _, o := range u.remoteObjects { + for _, o := range u.ru.remoteObjects { diffName := o.GetK8sAnnotation("kluctl.io/diff-name") if diffName == nil { x := o.GetK8sName() diff --git a/pkg/deployment/utils/errors_holder.go b/pkg/deployment/utils/errors_holder.go index d0a5e3de2..38660ba3c 100644 --- a/pkg/deployment/utils/errors_holder.go +++ b/pkg/deployment/utils/errors_holder.go @@ -18,11 +18,11 @@ type DeploymentErrorsAndWarnings struct { func NewDeploymentErrorsAndWarnings() *DeploymentErrorsAndWarnings { dew := &DeploymentErrorsAndWarnings{} - dew.init() + dew.Init() return dew } -func (dew *DeploymentErrorsAndWarnings) init() { +func (dew *DeploymentErrorsAndWarnings) Init() { dew.mutex.Lock() defer dew.mutex.Unlock() dew.warnings = map[k8s.ObjectRef]map[types.DeploymentError]bool{} diff --git a/pkg/deployment/utils/remote_objects_utils.go b/pkg/deployment/utils/remote_objects_utils.go new file mode 100644 index 000000000..92b6cc9ea --- /dev/null +++ b/pkg/deployment/utils/remote_objects_utils.go @@ -0,0 +1,119 @@ +package utils + +import ( + "github.com/codablock/kluctl/pkg/k8s" + k8s2 "github.com/codablock/kluctl/pkg/types/k8s" + "github.com/codablock/kluctl/pkg/utils" + "github.com/codablock/kluctl/pkg/utils/uo" + log "github.com/sirupsen/logrus" + "sync" +) + +type RemoteObjectUtils struct { + dew *DeploymentErrorsAndWarnings + remoteObjects map[k8s2.ObjectRef]*uo.UnstructuredObject + mutex sync.Mutex +} + +func NewRemoteObjectsUtil(dew *DeploymentErrorsAndWarnings) *RemoteObjectUtils { + return &RemoteObjectUtils{ + dew: dew, + remoteObjects: map[k8s2.ObjectRef]*uo.UnstructuredObject{}, + } +} + +func (u *RemoteObjectUtils) UpdateRemoteObjects(k *k8s.K8sCluster, labels map[string]string, refs []k8s2.ObjectRef) error { + if k == nil { + return nil + } + + log.Infof("Getting remote objects by commonLabels") + allObjects, apiWarnings, err := k.ListAllObjects([]string{"get"}, "", labels, false) + for gvk, aw := range apiWarnings { + u.dew.AddApiWarnings(k8s2.ObjectRef{GVK: gvk}, aw) + } + if err != nil { + return err + } + + u.mutex.Lock() + for _, o := range allObjects { + u.remoteObjects[o.GetK8sRef()] = o + } + + notFoundRefsMap := make(map[k8s2.ObjectRef]bool) + var notFoundRefsList []k8s2.ObjectRef + for _, ref := range refs { + if _, ok := u.remoteObjects[ref]; !ok { + if _, ok = notFoundRefsMap[ref]; !ok { + notFoundRefsMap[ref] = true + notFoundRefsList = append(notFoundRefsList, ref) + } + } + } + u.mutex.Unlock() + + if len(notFoundRefsList) != 0 { + log.Infof("Getting %d additional remote objects", len(notFoundRefsList)) + r, apiWarnings, err := k.GetObjectsByRefs(notFoundRefsList) + for ref, aw := range apiWarnings { + u.dew.AddApiWarnings(ref, aw) + } + if err != nil { + return err + } + u.mutex.Lock() + for _, o := range r { + u.remoteObjects[o.GetK8sRef()] = o + } + u.mutex.Unlock() + } + return nil +} + +func (u *RemoteObjectUtils) GetRemoteObject(ref k8s2.ObjectRef) *uo.UnstructuredObject { + u.mutex.Lock() + defer u.mutex.Unlock() + o, _ := u.remoteObjects[ref] + return o +} + +func (u *RemoteObjectUtils) ForgetRemoteObject(ref k8s2.ObjectRef) { + u.mutex.Lock() + defer u.mutex.Unlock() + delete(u.remoteObjects, ref) +} + +func (u *RemoteObjectUtils) GetFilteredRemoteObjects(inclusion *utils.Inclusion) []*uo.UnstructuredObject { + var ret []*uo.UnstructuredObject + + u.mutex.Lock() + defer u.mutex.Unlock() + + for _, o := range u.remoteObjects { + iv := u.getInclusionEntries(o) + if inclusion.CheckIncluded(iv, false) { + ret = append(ret, o) + } + } + + return ret +} + +func (u *RemoteObjectUtils) getInclusionEntries(o *uo.UnstructuredObject) []utils.InclusionEntry { + var iv []utils.InclusionEntry + for _, v := range o.GetK8sLabelsWithRegex("^kluctl.io/tag-\\d+$") { + iv = append(iv, utils.InclusionEntry{ + Type: "tag", + Value: v, + }) + } + + if itemDir := o.GetK8sAnnotation("kluctl.io/kustomize_dir"); itemDir != nil { + iv = append(iv, utils.InclusionEntry{ + Type: "deploymentItemDir", + Value: *itemDir, + }) + } + return iv +} From 37c7d7461c85debf9b4e35f47e8a35ac6b2df912 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Mar 2022 08:14:07 +0100 Subject: [PATCH 0514/2916] refacotor: Only hold deployment items in ApplyUtil --- pkg/deployment/commands/deploy.go | 2 +- pkg/deployment/commands/diff.go | 2 +- pkg/deployment/commands/downscale.go | 2 +- pkg/deployment/commands/poke_images.go | 2 +- pkg/deployment/commands/validate.go | 2 +- pkg/deployment/utils/apply_utils.go | 34 +++++++++++++------------- 6 files changed, 22 insertions(+), 22 deletions(-) diff --git a/pkg/deployment/commands/deploy.go b/pkg/deployment/commands/deploy.go index ffb953119..d075ace7a 100644 --- a/pkg/deployment/commands/deploy.go +++ b/pkg/deployment/commands/deploy.go @@ -41,7 +41,7 @@ func (cmd *DeployCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, error) { AbortOnError: cmd.AbortOnError, HookTimeout: cmd.HookTimeout, } - au := utils2.NewApplyUtil(dew, cmd.c, ru, k, o) + au := utils2.NewApplyUtil(dew, cmd.c.Deployments, ru, k, o) au.ApplyDeployments() du := utils2.NewDiffUtil(dew, cmd.c.Deployments, ru, au.AppliedObjects) diff --git a/pkg/deployment/commands/diff.go b/pkg/deployment/commands/diff.go index bd39426ae..f42490d35 100644 --- a/pkg/deployment/commands/diff.go +++ b/pkg/deployment/commands/diff.go @@ -41,7 +41,7 @@ func (cmd *DiffCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, error) { AbortOnError: false, HookTimeout: 0, } - au := utils.NewApplyUtil(dew, cmd.c, ru, k, o) + au := utils.NewApplyUtil(dew, cmd.c.Deployments, ru, k, o) au.ApplyDeployments() du := utils.NewDiffUtil(dew, cmd.c.Deployments, ru, au.AppliedObjects) diff --git a/pkg/deployment/commands/downscale.go b/pkg/deployment/commands/downscale.go index 8263374f2..06bd7d75c 100644 --- a/pkg/deployment/commands/downscale.go +++ b/pkg/deployment/commands/downscale.go @@ -31,7 +31,7 @@ func (cmd *DownscaleCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, error return nil, err } - au := utils2.NewApplyUtil(dew, cmd.c, ru, k, utils2.ApplyUtilOptions{}) + au := utils2.NewApplyUtil(dew, cmd.c.Deployments, ru, k, utils2.ApplyUtilOptions{}) appliedObjects := make(map[k8s2.ObjectRef]*uo.UnstructuredObject) diff --git a/pkg/deployment/commands/poke_images.go b/pkg/deployment/commands/poke_images.go index df96f335b..7197e421c 100644 --- a/pkg/deployment/commands/poke_images.go +++ b/pkg/deployment/commands/poke_images.go @@ -67,7 +67,7 @@ func (cmd *PokeImagesCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, erro return o, nil } - au := utils2.NewApplyUtil(dew, cmd.c, ru, k, utils2.ApplyUtilOptions{}) + au := utils2.NewApplyUtil(dew, cmd.c.Deployments, ru, k, utils2.ApplyUtilOptions{}) for ref, containers := range containersAndImages { ref := ref diff --git a/pkg/deployment/commands/validate.go b/pkg/deployment/commands/validate.go index 15efd18ca..0ddde389b 100644 --- a/pkg/deployment/commands/validate.go +++ b/pkg/deployment/commands/validate.go @@ -34,7 +34,7 @@ func (cmd *ValidateCommand) Run(k *k8s.K8sCluster) (*types.ValidateResult, error return nil, err } - a := utils2.NewApplyUtil(cmd.dew, cmd.c, cmd.ru, k, utils2.ApplyUtilOptions{}) + a := utils2.NewApplyUtil(cmd.dew, cmd.c.Deployments, cmd.ru, k, utils2.ApplyUtilOptions{}) h := utils2.NewHooksUtil(a) for _, d := range cmd.c.Deployments { if !d.CheckInclusionForDeploy() { diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index 861ca461d..f27fcd449 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -29,11 +29,11 @@ type ApplyUtilOptions struct { } type ApplyUtil struct { - dew *DeploymentErrorsAndWarnings - deploymentCollection *deployment.DeploymentCollection - ru *RemoteObjectUtils - k *k8s.K8sCluster - o ApplyUtilOptions + dew *DeploymentErrorsAndWarnings + deployments []*deployment.DeploymentItem + ru *RemoteObjectUtils + k *k8s.K8sCluster + o ApplyUtilOptions AppliedObjects map[k8s2.ObjectRef]*uo.UnstructuredObject appliedHookObjects map[k8s2.ObjectRef]*uo.UnstructuredObject @@ -44,18 +44,18 @@ type ApplyUtil struct { mutex sync.Mutex } -func NewApplyUtil(dew *DeploymentErrorsAndWarnings, deploymentCollection *deployment.DeploymentCollection, ru *RemoteObjectUtils, k *k8s.K8sCluster, o ApplyUtilOptions) *ApplyUtil { +func NewApplyUtil(dew *DeploymentErrorsAndWarnings, deployments []*deployment.DeploymentItem, ru *RemoteObjectUtils, k *k8s.K8sCluster, o ApplyUtilOptions) *ApplyUtil { return &ApplyUtil{ - dew: dew, - deploymentCollection: deploymentCollection, - ru: ru, - k: k, - o: o, - AppliedObjects: map[k8s2.ObjectRef]*uo.UnstructuredObject{}, - appliedHookObjects: map[k8s2.ObjectRef]*uo.UnstructuredObject{}, - deletedObjects: map[k8s2.ObjectRef]bool{}, - deletedHookObjects: map[k8s2.ObjectRef]bool{}, - deployedNewCRD: true, // assume someone deployed CRDs in the meantime + dew: dew, + deployments: deployments, + ru: ru, + k: k, + o: o, + AppliedObjects: map[k8s2.ObjectRef]*uo.UnstructuredObject{}, + appliedHookObjects: map[k8s2.ObjectRef]*uo.UnstructuredObject{}, + deletedObjects: map[k8s2.ObjectRef]bool{}, + deletedHookObjects: map[k8s2.ObjectRef]bool{}, + deployedNewCRD: true, // assume someone deployed CRDs in the meantime } } @@ -422,7 +422,7 @@ func (a *ApplyUtil) ApplyDeployments() { defer wp.StopWait(false) previousWasBarrier := false - for _, d_ := range a.deploymentCollection.Deployments { + for _, d_ := range a.deployments { d := d_ if a.abortSignal { break From d222b4d651614f91f5462104c797c24939310a76 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Mar 2022 09:08:51 +0100 Subject: [PATCH 0515/2916] refactor: Remove the need for k8s cluster in Diff() --- pkg/deployment/commands/deploy.go | 2 +- pkg/deployment/commands/diff.go | 2 +- pkg/deployment/commands/downscale.go | 2 +- pkg/deployment/commands/poke_images.go | 2 +- pkg/deployment/utils/diff_utils.go | 11 +++++------ pkg/diff/normalize.go | 9 +++------ 6 files changed, 12 insertions(+), 16 deletions(-) diff --git a/pkg/deployment/commands/deploy.go b/pkg/deployment/commands/deploy.go index d075ace7a..1d7315fa4 100644 --- a/pkg/deployment/commands/deploy.go +++ b/pkg/deployment/commands/deploy.go @@ -45,7 +45,7 @@ func (cmd *DeployCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, error) { au.ApplyDeployments() du := utils2.NewDiffUtil(dew, cmd.c.Deployments, ru, au.AppliedObjects) - du.Diff(k) + du.Diff() orphanObjects, err := FindOrphanObjects(k, ru, cmd.c) if err != nil { diff --git a/pkg/deployment/commands/diff.go b/pkg/deployment/commands/diff.go index f42490d35..5ed6d1e31 100644 --- a/pkg/deployment/commands/diff.go +++ b/pkg/deployment/commands/diff.go @@ -48,7 +48,7 @@ func (cmd *DiffCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, error) { du.IgnoreTags = cmd.IgnoreTags du.IgnoreLabels = cmd.IgnoreLabels du.IgnoreAnnotations = cmd.IgnoreAnnotations - du.Diff(k) + du.Diff() orphanObjects, err := FindOrphanObjects(k, ru, cmd.c) if err != nil { diff --git a/pkg/deployment/commands/downscale.go b/pkg/deployment/commands/downscale.go index 06bd7d75c..80f72795d 100644 --- a/pkg/deployment/commands/downscale.go +++ b/pkg/deployment/commands/downscale.go @@ -61,7 +61,7 @@ func (cmd *DownscaleCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, error wg.Wait() du := utils2.NewDiffUtil(dew, cmd.c.Deployments, ru, appliedObjects) - du.Diff(k) + du.Diff() return &types.CommandResult{ NewObjects: du.NewObjects, diff --git a/pkg/deployment/commands/poke_images.go b/pkg/deployment/commands/poke_images.go index 7197e421c..5316f15a7 100644 --- a/pkg/deployment/commands/poke_images.go +++ b/pkg/deployment/commands/poke_images.go @@ -83,7 +83,7 @@ func (cmd *PokeImagesCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, erro wg.Wait() du := utils2.NewDiffUtil(dew, cmd.c.Deployments, ru, au.AppliedObjects) - du.Diff(k) + du.Diff() return &types.CommandResult{ NewObjects: du.NewObjects, diff --git a/pkg/deployment/utils/diff_utils.go b/pkg/deployment/utils/diff_utils.go index 414e32f03..5b6305572 100644 --- a/pkg/deployment/utils/diff_utils.go +++ b/pkg/deployment/utils/diff_utils.go @@ -3,7 +3,6 @@ package utils import ( "github.com/codablock/kluctl/pkg/deployment" "github.com/codablock/kluctl/pkg/diff" - "github.com/codablock/kluctl/pkg/k8s" "github.com/codablock/kluctl/pkg/types" k8s2 "github.com/codablock/kluctl/pkg/types/k8s" "github.com/codablock/kluctl/pkg/utils/uo" @@ -36,7 +35,7 @@ func NewDiffUtil(dew *DeploymentErrorsAndWarnings, deployments []*deployment.Dep } } -func (u *diffUtil) Diff(k *k8s.K8sCluster) { +func (u *diffUtil) Diff() { var wg sync.WaitGroup u.calcRemoteObjectsForDiff() @@ -60,14 +59,14 @@ func (u *diffUtil) Diff(k *k8s.K8sCluster) { wg.Add(1) go func() { defer wg.Done() - u.diffObject(k, o, ao, ro, ignoreForDiffs) + u.diffObject(o, ao, ro, ignoreForDiffs) }() } } wg.Wait() } -func (u *diffUtil) diffObject(k *k8s.K8sCluster, lo *uo.UnstructuredObject, ao *uo.UnstructuredObject, ro *uo.UnstructuredObject, ignoreForDiffs []*types.IgnoreForDiffItemConfig) { +func (u *diffUtil) diffObject(lo *uo.UnstructuredObject, ao *uo.UnstructuredObject, ro *uo.UnstructuredObject, ignoreForDiffs []*types.IgnoreForDiffItemConfig) { if ao != nil && ro == nil { u.mutex.Lock() defer u.mutex.Unlock() @@ -82,8 +81,8 @@ func (u *diffUtil) diffObject(k *k8s.K8sCluster, lo *uo.UnstructuredObject, ao * // did not apply? (e.g. in downscale command) return } else { - nao := diff.NormalizeObject(k, ao, ignoreForDiffs, lo) - nro := diff.NormalizeObject(k, ro, ignoreForDiffs, lo) + nao := diff.NormalizeObject(ao, ignoreForDiffs, lo) + nro := diff.NormalizeObject(ro, ignoreForDiffs, lo) changes, err := diff.Diff(nro, nao) if err != nil { u.dew.AddError(lo.GetK8sRef(), err) diff --git a/pkg/diff/normalize.go b/pkg/diff/normalize.go index 90e7c0641..066335596 100644 --- a/pkg/diff/normalize.go +++ b/pkg/diff/normalize.go @@ -2,7 +2,6 @@ package diff import ( "fmt" - "github.com/codablock/kluctl/pkg/k8s" "github.com/codablock/kluctl/pkg/types" "github.com/codablock/kluctl/pkg/utils/uo" "regexp" @@ -93,9 +92,7 @@ func normalizeServiceAccount(o *uo.UnstructuredObject) { _ = o.SetNestedField(newSecrets, "secrets") } -func normalizeMetadata(k *k8s.K8sCluster, o *uo.UnstructuredObject) { - k.RemoveNamespaceIfNeeded(o) - +func normalizeMetadata(o *uo.UnstructuredObject) { // We don't care about managedFields when diffing (they just produce noise) _ = o.RemoveNestedField("metadata", "managedFields") _ = o.RemoveNestedField("metadata", "annotations", "managedFields", "kubectl.kubernetes.io/last-applied-configuration") @@ -123,13 +120,13 @@ func normalizeMisc(o *uo.UnstructuredObject) { var ignoreDiffFieldAnnotationRegex = regexp.MustCompile(`^kluctl.io/ignore-diff-field(-\d*)?$`) // NormalizeObject Performs some deterministic sorting and other normalizations to avoid ugly diffs due to order changes -func NormalizeObject(k *k8s.K8sCluster, o_ *uo.UnstructuredObject, ignoreForDiffs []*types.IgnoreForDiffItemConfig, localObject *uo.UnstructuredObject) *uo.UnstructuredObject { +func NormalizeObject(o_ *uo.UnstructuredObject, ignoreForDiffs []*types.IgnoreForDiffItemConfig, localObject *uo.UnstructuredObject) *uo.UnstructuredObject { gvk := o_.GetK8sGVK() name := o_.GetK8sName() ns := o_.GetK8sNamespace() o := o_.Clone() - normalizeMetadata(k, o) + normalizeMetadata(o) normalizeMisc(o) switch gvk.Kind { From 9543bfc4fce73ef999efdce255c84e84a6d769dc Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Mar 2022 10:24:51 +0100 Subject: [PATCH 0516/2916] refactor: Remove the need for DeploymentCollection as member of DeploymentItem --- pkg/deployment/deployment_collection.go | 4 ++-- pkg/deployment/deployment_item.go | 21 ++++++++++----------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index 46f1e9394..9b6fa7d5c 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -113,7 +113,7 @@ func (c *DeploymentCollection) RenderDeployments(k *k8s.K8sCluster) error { defer wp.StopWait(false) for _, d := range c.Deployments { - err := d.render(k, wp) + err := d.render(k, c.forSeal, wp) if err != nil { return err } @@ -175,7 +175,7 @@ func (c *DeploymentCollection) buildKustomizeObjects(k *k8s.K8sCluster) error { _ = sem.Acquire(context.Background(), 1) defer sem.Release(1) - err := d.postprocessAndLoadObjects(k) + err := d.postprocessAndLoadObjects(k, c.Images) if err != nil { mutex.Lock() errs = append(errs, fmt.Errorf("postprocessing kustomize objects for %s failed. %w", *d.dir, err)) diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index d808dc3d7..f37ef2e71 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -23,7 +23,7 @@ const SealmeExt = ".sealme" type DeploymentItem struct { Project *DeploymentProject - collection *DeploymentCollection + Inclusion *utils.Inclusion Config *types.DeploymentItemConfig dir *string index int @@ -41,7 +41,6 @@ type DeploymentItem struct { func NewDeploymentItem(project *DeploymentProject, collection *DeploymentCollection, config *types.DeploymentItemConfig, dir *string, index int) (*DeploymentItem, error) { di := &DeploymentItem{ Project: project, - collection: collection, Config: config, dir: dir, index: index, @@ -72,7 +71,7 @@ func NewDeploymentItem(project *DeploymentProject, collection *DeploymentCollect di.relRenderedDir = fmt.Sprintf("%s-%d", di.relRenderedDir, di.index) } - di.renderedDir = filepath.Join(di.collection.RenderDir, di.relRenderedDir) + di.renderedDir = filepath.Join(collection.RenderDir, di.relRenderedDir) di.renderedYamlPath = filepath.Join(di.renderedDir, ".rendered.yml") } return di, nil @@ -99,7 +98,7 @@ func (di *DeploymentItem) getCommonAnnotations() map[string]string { return a } -func (di *DeploymentItem) render(k *k8s.K8sCluster, wp *utils.WorkerPoolWithErrors) error { +func (di *DeploymentItem) render(k *k8s.K8sCluster, forSeal bool, wp *utils.WorkerPoolWithErrors) error { if di.dir == nil { return nil } @@ -139,7 +138,7 @@ func (di *DeploymentItem) render(k *k8s.K8sCluster, wp *utils.WorkerPoolWithErro return err } - if !di.collection.forSeal { + if !forSeal { // .sealme files are rendered while sealing and not while deploying excludePatterns = append(excludePatterns, "**.sealme") } @@ -247,7 +246,7 @@ func (di *DeploymentItem) buildInclusionEntries() []utils.InclusionEntry { } func (di *DeploymentItem) CheckInclusionForDeploy() bool { - if di.collection.Inclusion == nil { + if di.Inclusion == nil { return true } if di.Config.OnlyRender != nil && *di.Config.OnlyRender { @@ -257,16 +256,16 @@ func (di *DeploymentItem) CheckInclusionForDeploy() bool { return true } values := di.buildInclusionEntries() - return di.collection.Inclusion.CheckIncluded(values, false) + return di.Inclusion.CheckIncluded(values, false) } func (di *DeploymentItem) checkInclusionForDelete() bool { - if di.collection.Inclusion == nil { + if di.Inclusion == nil { return true } skipDeleteIfTags := di.Config.SkipDeleteIfTags != nil && *di.Config.SkipDeleteIfTags values := di.buildInclusionEntries() - return di.collection.Inclusion.CheckIncluded(values, skipDeleteIfTags) + return di.Inclusion.CheckIncluded(values, skipDeleteIfTags) } func (di *DeploymentItem) prepareKustomizationYaml() error { @@ -338,7 +337,7 @@ func (di *DeploymentItem) buildKustomize() error { return nil } -func (di *DeploymentItem) postprocessAndLoadObjects(k *k8s.K8sCluster) error { +func (di *DeploymentItem) postprocessAndLoadObjects(k *k8s.K8sCluster, images *Images) error { if di.dir == nil { return nil } @@ -368,7 +367,7 @@ func (di *DeploymentItem) postprocessAndLoadObjects(k *k8s.K8sCluster) error { o.SetK8sAnnotations(uo.CopyMergeStrMap(o.GetK8sAnnotations(), commonAnnotations)) // Resolve image placeholders - err = di.collection.Images.ResolvePlaceholders(k, o, di.relRenderedDir, di.getTags().ListKeys()) + err = images.ResolvePlaceholders(k, o, di.relRenderedDir, di.getTags().ListKeys()) if err != nil { return err } From d7b7dad78f1197d2aae71fa2816d3609a6eb952e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Mar 2022 10:25:21 +0100 Subject: [PATCH 0517/2916] fix: Fix invalid error in GetHook --- pkg/deployment/utils/hooks_util.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/deployment/utils/hooks_util.go b/pkg/deployment/utils/hooks_util.go index 5922af2d6..df4e9244d 100644 --- a/pkg/deployment/utils/hooks_util.go +++ b/pkg/deployment/utils/hooks_util.go @@ -223,7 +223,7 @@ func (u *HooksUtil) GetHook(o *uo.UnstructuredObject) *hook { } wait, err := strconv.ParseBool(*waitStr) if err != nil { - u.a.HandleError(ref, fmt.Errorf("failed to parse %s as bool", waitStr)) + u.a.HandleError(ref, fmt.Errorf("failed to parse %s as bool", *waitStr)) wait = true } From 2c492a94ff50371b635b21c97ecfcfb2edb60da0 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Mar 2022 10:25:43 +0100 Subject: [PATCH 0518/2916] fix: Always enable SharedConfigState in GetAwsSecretsManagerSecret --- pkg/utils/aws/secrets_manager.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/utils/aws/secrets_manager.go b/pkg/utils/aws/secrets_manager.go index ff8edac5d..bd440439b 100644 --- a/pkg/utils/aws/secrets_manager.go +++ b/pkg/utils/aws/secrets_manager.go @@ -11,10 +11,10 @@ import ( func GetAwsSecretsManagerSecret(profile *string, region *string, secretName string) (string, error) { var opts session.Options + opts.SharedConfigState = session.SharedConfigEnable // Environment variable always takes precedence if _, ok := os.LookupEnv("AWS_PROFILE"); !ok && region != nil { opts.Profile = *profile - opts.SharedConfigState = session.SharedConfigEnable } s, err := session.NewSessionWithOptions(opts) if err != nil { From a8502dd6d227cae87ccd05405357ec1712ff2e00 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Mar 2022 10:26:04 +0100 Subject: [PATCH 0519/2916] tests: Implement first diffUtil test --- pkg/deployment/utils/diff_utils_test.go | 80 +++++++++++++++++++++++++ pkg/utils/uo/uo.go | 8 +++ 2 files changed, 88 insertions(+) create mode 100644 pkg/deployment/utils/diff_utils_test.go diff --git a/pkg/deployment/utils/diff_utils_test.go b/pkg/deployment/utils/diff_utils_test.go new file mode 100644 index 000000000..ac75f17b4 --- /dev/null +++ b/pkg/deployment/utils/diff_utils_test.go @@ -0,0 +1,80 @@ +package utils + +import ( + "github.com/codablock/kluctl/pkg/deployment" + k8s2 "github.com/codablock/kluctl/pkg/types/k8s" + "github.com/codablock/kluctl/pkg/utils/uo" + "github.com/stretchr/testify/assert" + "testing" +) + +type diffTestConfig struct { + name string + lo []*uo.UnstructuredObject + ro []*uo.UnstructuredObject + ao []*uo.UnstructuredObject + a func(t *testing.T, dtc *diffTestConfig) + + dew *DeploymentErrorsAndWarnings + ru *RemoteObjectUtils + du *diffUtil +} + +func (dtc *diffTestConfig) newRemoteObjects(dew *DeploymentErrorsAndWarnings) *RemoteObjectUtils { + ru := NewRemoteObjectsUtil(dew) + for _, ro := range dtc.ro { + ru.remoteObjects[ro.GetK8sRef()] = ro + } + return ru +} + +func (dtc *diffTestConfig) newDeploymentItems() []*deployment.DeploymentItem { + return []*deployment.DeploymentItem{ + { + Objects: dtc.lo, + }, + } +} + +func (dtc *diffTestConfig) appliedObjectsMap() map[k8s2.ObjectRef]*uo.UnstructuredObject { + ret := make(map[k8s2.ObjectRef]*uo.UnstructuredObject) + for _, o := range dtc.ao { + ret[o.GetK8sRef()] = o + } + return ret +} + +func newTestConfigMap(name string, data map[string]interface{}) *uo.UnstructuredObject { + o := uo.New() + o.SetK8sGVKs("", "v1", "ConfigMap") + o.SetK8sName(name) + o.SetK8sNamespace("default") + o.SetNestedField(data, "data") + return o +} + +func TestDiff(t *testing.T) { + tests := []*diffTestConfig{ + { + name: "One new object", + lo: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{})}, + ro: nil, + ao: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{})}, + a: func(t *testing.T, dtc *diffTestConfig) { + assert.Len(t, dtc.du.NewObjects, 1) + assert.Len(t, dtc.du.ChangedObjects, 0) + assert.Equal(t, dtc.lo[0], dtc.du.NewObjects[0].Object) + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + test.dew = NewDeploymentErrorsAndWarnings() + test.ru = test.newRemoteObjects(test.dew) + test.du = NewDiffUtil(test.dew, test.newDeploymentItems(), test.ru, test.appliedObjectsMap()) + test.du.Diff() + test.a(t, test) + }) + } +} diff --git a/pkg/utils/uo/uo.go b/pkg/utils/uo/uo.go index 28df2b5a6..f29f9f586 100644 --- a/pkg/utils/uo/uo.go +++ b/pkg/utils/uo/uo.go @@ -72,6 +72,14 @@ func FromString(s string) (*UnstructuredObject, error) { return o, nil } +func FromStringMust(s string) *UnstructuredObject { + o, err := FromString(s) + if err != nil { + log.Panic(err) + } + return o +} + func FromFile(p string) (*UnstructuredObject, error) { o := New() err := yaml.ReadYamlFile(p, &o.Object) From d5f38c6c67213685c1249f4cce6a6d77571ac1a0 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Mar 2022 12:23:18 +0100 Subject: [PATCH 0520/2916] cleanup: Run gofmt --- pkg/deployment/deployment_item.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index f37ef2e71..70455d99f 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -22,11 +22,11 @@ import ( const SealmeExt = ".sealme" type DeploymentItem struct { - Project *DeploymentProject - Inclusion *utils.Inclusion - Config *types.DeploymentItemConfig - dir *string - index int + Project *DeploymentProject + Inclusion *utils.Inclusion + Config *types.DeploymentItemConfig + dir *string + index int Objects []*uo.UnstructuredObject @@ -40,10 +40,10 @@ type DeploymentItem struct { func NewDeploymentItem(project *DeploymentProject, collection *DeploymentCollection, config *types.DeploymentItemConfig, dir *string, index int) (*DeploymentItem, error) { di := &DeploymentItem{ - Project: project, - Config: config, - dir: dir, - index: index, + Project: project, + Config: config, + dir: dir, + index: index, } var err error From b900cb46a8ceefb87e2eb1450558f7e8b29cfc84 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Mar 2022 12:23:40 +0100 Subject: [PATCH 0521/2916] fix: Stable sort diff changes --- pkg/diff/diff.go | 139 ++++++++++++++++++++++++++++++----------------- 1 file changed, 88 insertions(+), 51 deletions(-) diff --git a/pkg/diff/diff.go b/pkg/diff/diff.go index b511977be..66dd1bd5a 100644 --- a/pkg/diff/diff.go +++ b/pkg/diff/diff.go @@ -9,7 +9,9 @@ import ( "github.com/hexops/gotextdiff/myers" "github.com/hexops/gotextdiff/span" diff2 "github.com/r3labs/diff/v2" + "log" "reflect" + "sort" "strconv" "strings" ) @@ -49,62 +51,97 @@ func Diff(oldObject *uo.UnstructuredObject, newObject *uo.UnstructuredObject) ([ var changes []types.Change for _, c := range cl { - switch c.Type { - case "create": - ud, err := buildUnifiedDiff(notPresent, c.To, false) - if err != nil { - return nil, err - } - p, err := convertPath(c.Path, newObject.Object) - if err != nil { - return nil, err - } - changes = append(changes, types.Change{ - Type: "insert", - JsonPath: p, - NewValue: c.To, - UnifiedDiff: ud, - }) - case "delete": - ud, err := buildUnifiedDiff(c.From, notPresent, false) - if err != nil { - return nil, err - } - p, err := convertPath(c.Path, oldObject.Object) - if err != nil { - return nil, err - } - changes = append(changes, types.Change{ - Type: "delete", - JsonPath: p, - OldValue: c.From, - UnifiedDiff: ud, - }) - case "update": - showType := false - if reflect.TypeOf(c.From) != reflect.TypeOf(c.To) { - showType = true - } - ud, err := buildUnifiedDiff(c.From, c.To, showType) - if err != nil { - return nil, err - } - p, err := convertPath(c.Path, newObject.Object) - if err != nil { - return nil, err - } - changes = append(changes, types.Change{ - Type: "update", - JsonPath: p, - NewValue: c.To, - OldValue: c.From, - UnifiedDiff: ud, - }) + c2, err := convertChange(c, oldObject, newObject) + if err != nil { + return nil, err } + changes = append(changes, *c2) } + + // The result of the above diff call is not stable + stableSortChanges(changes) return changes, nil } +func convertChange(c diff2.Change, oldObject *uo.UnstructuredObject, newObject *uo.UnstructuredObject) (*types.Change, error) { + switch c.Type { + case "create": + ud, err := buildUnifiedDiff(notPresent, c.To, false) + if err != nil { + return nil, err + } + p, err := convertPath(c.Path, newObject.Object) + if err != nil { + return nil, err + } + return &types.Change{ + Type: "insert", + JsonPath: p, + NewValue: c.To, + UnifiedDiff: ud, + }, nil + case "delete": + ud, err := buildUnifiedDiff(c.From, notPresent, false) + if err != nil { + return nil, err + } + p, err := convertPath(c.Path, oldObject.Object) + if err != nil { + return nil, err + } + return &types.Change{ + Type: "delete", + JsonPath: p, + OldValue: c.From, + UnifiedDiff: ud, + }, nil + case "update": + showType := false + if reflect.TypeOf(c.From) != reflect.TypeOf(c.To) { + showType = true + } + ud, err := buildUnifiedDiff(c.From, c.To, showType) + if err != nil { + return nil, err + } + p, err := convertPath(c.Path, newObject.Object) + if err != nil { + return nil, err + } + return &types.Change{ + Type: "update", + JsonPath: p, + NewValue: c.To, + OldValue: c.From, + UnifiedDiff: ud, + }, nil + } + return nil, fmt.Errorf("unknown change type %s", c.Type) +} + +func stableSortChanges(changes []types.Change) { + changesStrs := make([]string, len(changes)) + changesIndexes := make([]int, len(changes)) + for i, _ := range changes { + y, err := yaml.WriteYamlString(changes[i]) + if err != nil { + log.Panic(err) + } + changesStrs[i] = y + changesIndexes[i] = i + } + + sort.SliceStable(changesIndexes, func(i, j int) bool { + return changesStrs[changesIndexes[i]] < changesStrs[changesIndexes[j]] + }) + + changesSorted := make([]types.Change, len(changes)) + for i, _ := range changes { + changesSorted[i] = changes[changesIndexes[i]] + } + copy(changes, changesSorted) +} + func buildUnifiedDiff(a interface{}, b interface{}, showType bool) (string, error) { aStr, err := objectToDiffableString(a, showType) if err != nil { From a5b32dc68a5cbd63b754ec4a0b7e9636172be72f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Mar 2022 12:24:20 +0100 Subject: [PATCH 0522/2916] tests: More diff tests --- pkg/deployment/utils/diff_utils_test.go | 107 +++++++++++++++++++++++- 1 file changed, 106 insertions(+), 1 deletion(-) diff --git a/pkg/deployment/utils/diff_utils_test.go b/pkg/deployment/utils/diff_utils_test.go index ac75f17b4..517d40871 100644 --- a/pkg/deployment/utils/diff_utils_test.go +++ b/pkg/deployment/utils/diff_utils_test.go @@ -2,6 +2,7 @@ package utils import ( "github.com/codablock/kluctl/pkg/deployment" + "github.com/codablock/kluctl/pkg/types" k8s2 "github.com/codablock/kluctl/pkg/types/k8s" "github.com/codablock/kluctl/pkg/utils/uo" "github.com/stretchr/testify/assert" @@ -57,8 +58,8 @@ func TestDiff(t *testing.T) { tests := []*diffTestConfig{ { name: "One new object", - lo: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{})}, ro: nil, + lo: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{})}, ao: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{})}, a: func(t *testing.T, dtc *diffTestConfig) { assert.Len(t, dtc.du.NewObjects, 1) @@ -66,6 +67,110 @@ func TestDiff(t *testing.T) { assert.Equal(t, dtc.lo[0], dtc.du.NewObjects[0].Object) }, }, + { + name: "One deleted object", + ro: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{})}, + lo: nil, + ao: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{})}, + a: func(t *testing.T, dtc *diffTestConfig) { + assert.Len(t, dtc.du.NewObjects, 0) + assert.Len(t, dtc.du.ChangedObjects, 0) + }, + }, + { + name: "One changed object (changed field)", + ro: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v1"})}, + lo: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v2"})}, + ao: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v2"})}, + a: func(t *testing.T, dtc *diffTestConfig) { + assert.Len(t, dtc.du.NewObjects, 0) + assert.Len(t, dtc.du.ChangedObjects, 1) + assert.Equal(t, dtc.du.ChangedObjects[0].NewObject, dtc.ao[0]) + assert.Equal(t, []types.Change{ + types.Change{Type: "update", JsonPath: "data.d1", OldValue: "v1", NewValue: "v2", UnifiedDiff: "-v1\n+v2"}, + }, dtc.du.ChangedObjects[0].Changes) + }, + }, + { + name: "One changed object (new field)", + ro: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v1"})}, + lo: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v1", "d2": "v2"})}, + ao: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v1", "d2": "v2"})}, + a: func(t *testing.T, dtc *diffTestConfig) { + assert.Len(t, dtc.du.NewObjects, 0) + assert.Len(t, dtc.du.ChangedObjects, 1) + assert.Equal(t, dtc.du.ChangedObjects[0].NewObject, dtc.ao[0]) + assert.Equal(t, []types.Change{ + types.Change{Type: "insert", JsonPath: "data.d2", OldValue: interface{}(nil), NewValue: "v2", UnifiedDiff: "+v2"}, + }, dtc.du.ChangedObjects[0].Changes) + }, + }, + { + name: "One changed object (removed field)", + ro: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v1", "d2": "v2"})}, + lo: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v1"})}, + ao: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v1"})}, + a: func(t *testing.T, dtc *diffTestConfig) { + assert.Len(t, dtc.du.NewObjects, 0) + assert.Len(t, dtc.du.ChangedObjects, 1) + assert.Equal(t, dtc.du.ChangedObjects[0].NewObject, dtc.ao[0]) + assert.Equal(t, []types.Change{ + types.Change{Type: "delete", JsonPath: "data.d2", OldValue: "v2", NewValue: interface{}(nil), UnifiedDiff: "-v2"}, + }, dtc.du.ChangedObjects[0].Changes) + }, + }, + { + name: "One changed object (new + removed field)", + ro: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v1"})}, + lo: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d2": "v2"})}, + ao: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d2": "v2"})}, + a: func(t *testing.T, dtc *diffTestConfig) { + assert.Len(t, dtc.du.NewObjects, 0) + assert.Len(t, dtc.du.ChangedObjects, 1) + assert.Equal(t, dtc.du.ChangedObjects[0].NewObject, dtc.ao[0]) + assert.Equal(t, []types.Change{ + types.Change{Type: "delete", JsonPath: "data.d1", OldValue: "v1", NewValue: interface{}(nil), UnifiedDiff: "-v1"}, + types.Change{Type: "insert", JsonPath: "data.d2", OldValue: interface{}(nil), NewValue: "v2", UnifiedDiff: "+v2"}, + }, dtc.du.ChangedObjects[0].Changes) + }, + }, + { + name: "One changed object (mixed changes)", + ro: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v1", "d2": "v2"})}, + lo: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v12", "d3": "v3"})}, + ao: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v12", "d3": "v3"})}, + a: func(t *testing.T, dtc *diffTestConfig) { + assert.Len(t, dtc.du.NewObjects, 0) + assert.Len(t, dtc.du.ChangedObjects, 1) + assert.Equal(t, dtc.du.ChangedObjects[0].NewObject, dtc.ao[0]) + assert.Equal(t, []types.Change{ + types.Change{Type: "delete", JsonPath: "data.d2", OldValue: "v2", NewValue: interface{}(nil), UnifiedDiff: "-v2"}, + types.Change{Type: "insert", JsonPath: "data.d3", OldValue: interface{}(nil), NewValue: "v3", UnifiedDiff: "+v3"}, + types.Change{Type: "update", JsonPath: "data.d1", OldValue: "v1", NewValue: "v12", UnifiedDiff: "-v1\n+v12"}, + }, dtc.du.ChangedObjects[0].Changes) + }, + }, + { + name: "Two changed objects + One new object", + ro: []*uo.UnstructuredObject{newTestConfigMap("test1", map[string]interface{}{"d1": "v1", "d2": "v2"}), newTestConfigMap("test2", map[string]interface{}{"xd1": "xv1", "xd2": "xv2"})}, + lo: []*uo.UnstructuredObject{newTestConfigMap("test1", map[string]interface{}{"d1": "v1", "d2": "v3"}), newTestConfigMap("test2", map[string]interface{}{"xd3": "xv2"}), newTestConfigMap("test3", map[string]interface{}{"yd3": "yv2"})}, + ao: []*uo.UnstructuredObject{newTestConfigMap("test1", map[string]interface{}{"d1": "v1", "d2": "v3"}), newTestConfigMap("test2", map[string]interface{}{"xd3": "xv2"}), newTestConfigMap("test3", map[string]interface{}{"yd3": "yv2"})}, + a: func(t *testing.T, dtc *diffTestConfig) { + assert.Len(t, dtc.du.NewObjects, 1) + assert.Len(t, dtc.du.ChangedObjects, 2) + assert.Equal(t, dtc.du.ChangedObjects[0].NewObject, dtc.ao[0]) + assert.Equal(t, dtc.du.ChangedObjects[1].NewObject, dtc.ao[1]) + assert.Equal(t, dtc.du.NewObjects[0].Object, dtc.ao[2]) + assert.Equal(t, []types.Change{ + types.Change{Type: "update", JsonPath: "data.d2", OldValue: "v2", NewValue: "v3", UnifiedDiff: "-v2\n+v3"}, + }, dtc.du.ChangedObjects[0].Changes) + assert.Equal(t, []types.Change{ + types.Change{Type: "delete", JsonPath: "data.xd1", OldValue: "xv1", NewValue: interface{}(nil), UnifiedDiff: "-xv1"}, + types.Change{Type: "delete", JsonPath: "data.xd2", OldValue: "xv2", NewValue: interface{}(nil), UnifiedDiff: "-xv2"}, + types.Change{Type: "insert", JsonPath: "data.xd3", OldValue: interface{}(nil), NewValue: "xv2", UnifiedDiff: "+xv2"}, + }, dtc.du.ChangedObjects[1].Changes) + }, + }, } for _, test := range tests { From 64d7171b3eb93308146ec7f1f342662075a7861b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Mar 2022 13:42:18 +0100 Subject: [PATCH 0523/2916] fix: Stable sort new/changed files in diffUtil --- pkg/deployment/utils/diff_utils.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkg/deployment/utils/diff_utils.go b/pkg/deployment/utils/diff_utils.go index 5b6305572..4fd4508a1 100644 --- a/pkg/deployment/utils/diff_utils.go +++ b/pkg/deployment/utils/diff_utils.go @@ -6,6 +6,7 @@ import ( "github.com/codablock/kluctl/pkg/types" k8s2 "github.com/codablock/kluctl/pkg/types/k8s" "github.com/codablock/kluctl/pkg/utils/uo" + "sort" "sync" "time" ) @@ -64,6 +65,13 @@ func (u *diffUtil) Diff() { } } wg.Wait() + + sort.Slice(u.NewObjects, func(i, j int) bool { + return u.NewObjects[i].Ref.String() < u.NewObjects[j].Ref.String() + }) + sort.Slice(u.ChangedObjects, func(i, j int) bool { + return u.ChangedObjects[i].Ref.String() < u.ChangedObjects[j].Ref.String() + }) } func (u *diffUtil) diffObject(lo *uo.UnstructuredObject, ao *uo.UnstructuredObject, ro *uo.UnstructuredObject, ignoreForDiffs []*types.IgnoreForDiffItemConfig) { From ff8f213de93bc20adf137e4aae31fb4d27e427a0 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Mar 2022 16:55:20 +0100 Subject: [PATCH 0524/2916] feat: Allow to override labels in delete command --- cmd/kluctl/commands/cmd_delete.go | 11 +++++++++++ pkg/deployment/commands/delete.go | 13 +++++++++++-- pkg/deployment/commands/prune.go | 2 +- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/cmd/kluctl/commands/cmd_delete.go b/cmd/kluctl/commands/cmd_delete.go index cbb029f49..ad4db676a 100644 --- a/cmd/kluctl/commands/cmd_delete.go +++ b/cmd/kluctl/commands/cmd_delete.go @@ -3,6 +3,7 @@ package commands import ( "fmt" "github.com/codablock/kluctl/cmd/kluctl/args" + "github.com/codablock/kluctl/pkg/deployment" "github.com/codablock/kluctl/pkg/deployment/commands" "github.com/codablock/kluctl/pkg/deployment/utils" "github.com/codablock/kluctl/pkg/k8s" @@ -20,6 +21,8 @@ type deleteCmd struct { args.YesFlags args.DryRunFlags args.OutputFormatFlags + + DeleteByLabel []string `group:"misc" short:"l" help:"Override the labels used to find objects for deletion."` } func (cmd *deleteCmd) Help() string { @@ -41,6 +44,14 @@ func (cmd *deleteCmd) Run() error { } return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { cmd2 := commands.NewDeleteCommand(ctx.deploymentCollection) + + deleteByLabels, err := deployment.ParseArgs(cmd.DeleteByLabel) + if err != nil { + return err + } + + cmd2.OverrideDeleteByLabels = deleteByLabels + objects, err := cmd2.Run(ctx.k) if err != nil { return err diff --git a/pkg/deployment/commands/delete.go b/pkg/deployment/commands/delete.go index 9ab1bece7..dbc1d6646 100644 --- a/pkg/deployment/commands/delete.go +++ b/pkg/deployment/commands/delete.go @@ -8,7 +8,8 @@ import ( ) type DeleteCommand struct { - c *deployment.DeploymentCollection + c *deployment.DeploymentCollection + OverrideDeleteByLabels map[string]string } func NewDeleteCommand(c *deployment.DeploymentCollection) *DeleteCommand { @@ -21,7 +22,15 @@ func (cmd *DeleteCommand) Run(k *k8s.K8sCluster) ([]k8s2.ObjectRef, error) { dew := utils2.NewDeploymentErrorsAndWarnings() ru := utils2.NewRemoteObjectsUtil(dew) - err := ru.UpdateRemoteObjects(k, cmd.c.Project.GetCommonLabels(), cmd.c.LocalObjectRefs()) + + var labels map[string]string + if len(cmd.OverrideDeleteByLabels) != 0 { + labels = cmd.OverrideDeleteByLabels + } else { + labels = cmd.c.Project.GetCommonLabels() + } + + err := ru.UpdateRemoteObjects(k, labels, nil) if err != nil { return nil, err } diff --git a/pkg/deployment/commands/prune.go b/pkg/deployment/commands/prune.go index c9ea049a3..fa9f2bd11 100644 --- a/pkg/deployment/commands/prune.go +++ b/pkg/deployment/commands/prune.go @@ -21,7 +21,7 @@ func (cmd *PruneCommand) Run(k *k8s.K8sCluster) ([]k8s2.ObjectRef, error) { dew := utils2.NewDeploymentErrorsAndWarnings() ru := utils2.NewRemoteObjectsUtil(dew) - err := ru.UpdateRemoteObjects(k, cmd.c.Project.GetCommonLabels(), cmd.c.LocalObjectRefs()) + err := ru.UpdateRemoteObjects(k, cmd.c.Project.GetCommonLabels(), nil) if err != nil { return nil, err } From 1286265e09d00d70df782ff51e29510741de9352 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Mar 2022 16:55:35 +0100 Subject: [PATCH 0525/2916] fix: Check for SkipUpdate was negated --- cmd/kluctl/commands/cmd_helm_update.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go index ddf8b91df..07b251f98 100644 --- a/cmd/kluctl/commands/cmd_helm_update.go +++ b/cmd/kluctl/commands/cmd_helm_update.go @@ -41,7 +41,7 @@ func (cmd *helmUpdateCmd) Run() error { log.Infof("Chart %s has new version %s available. Old version is %s.", p, newVersion, *chart.Config.ChartVersion) if cmd.Upgrade { - if chart.Config.SkipUpdate != nil && !*chart.Config.SkipUpdate { + if chart.Config.SkipUpdate != nil && *chart.Config.SkipUpdate { log.Infof("NOT upgrading chart %s as skipUpdate was set to true", p) return nil } From 0470703368fcc85ccd0cd2b9db6c399b649b5423 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Mar 2022 16:56:07 +0100 Subject: [PATCH 0526/2916] fix: Show diff-name in diffs in case it was overridden via kluctl.io/diff-name --- cmd/kluctl/commands/command_result.go | 4 ++-- pkg/deployment/utils/diff_utils.go | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cmd/kluctl/commands/command_result.go b/cmd/kluctl/commands/command_result.go index dd6d36aa8..41d3dbeee 100644 --- a/cmd/kluctl/commands/command_result.go +++ b/cmd/kluctl/commands/command_result.go @@ -32,13 +32,13 @@ func formatCommandResultText(cr *types.CommandResult) string { buf.WriteString("\nChanged objects:\n") var refs []k8s.ObjectRef for _, co := range cr.ChangedObjects { - refs = append(refs, co.NewObject.GetK8sRef()) + refs = append(refs, co.Ref) } prettyObjectRefs(buf, refs) buf.WriteString("\n") for _, co := range cr.ChangedObjects { - prettyChanges(buf, co.NewObject.GetK8sRef(), co.Changes) + prettyChanges(buf, co.Ref, co.Changes) } } diff --git a/pkg/deployment/utils/diff_utils.go b/pkg/deployment/utils/diff_utils.go index 4fd4508a1..8ccf6f637 100644 --- a/pkg/deployment/utils/diff_utils.go +++ b/pkg/deployment/utils/diff_utils.go @@ -55,12 +55,12 @@ func (u *diffUtil) Diff() { // if we can't even find it in appliedObjects, it probably ran into an error continue } - ro := u.getRemoteObjectForDiff(o) + diffRef, ro := u.getRemoteObjectForDiff(o) wg.Add(1) go func() { defer wg.Done() - u.diffObject(o, ao, ro, ignoreForDiffs) + u.diffObject(o, diffRef, ao, ro, ignoreForDiffs) }() } } @@ -74,7 +74,7 @@ func (u *diffUtil) Diff() { }) } -func (u *diffUtil) diffObject(lo *uo.UnstructuredObject, ao *uo.UnstructuredObject, ro *uo.UnstructuredObject, ignoreForDiffs []*types.IgnoreForDiffItemConfig) { +func (u *diffUtil) diffObject(lo *uo.UnstructuredObject, diffRef k8s2.ObjectRef, ao *uo.UnstructuredObject, ro *uo.UnstructuredObject, ignoreForDiffs []*types.IgnoreForDiffItemConfig) { if ao != nil && ro == nil { u.mutex.Lock() defer u.mutex.Unlock() @@ -103,7 +103,7 @@ func (u *diffUtil) diffObject(lo *uo.UnstructuredObject, ao *uo.UnstructuredObje u.mutex.Lock() defer u.mutex.Unlock() u.ChangedObjects = append(u.ChangedObjects, &types.ChangedObject{ - Ref: lo.GetK8sRef(), + Ref: diffRef, NewObject: ao, OldObject: ro, Changes: changes, @@ -131,12 +131,12 @@ func (u *diffUtil) calcRemoteObjectsForDiff() { } } -func (u *diffUtil) getRemoteObjectForDiff(localObject *uo.UnstructuredObject) *uo.UnstructuredObject { +func (u *diffUtil) getRemoteObjectForDiff(localObject *uo.UnstructuredObject) (k8s2.ObjectRef, *uo.UnstructuredObject) { ref := localObject.GetK8sRef() diffName := localObject.GetK8sAnnotation("kluctl.io/diff-name") if diffName != nil { ref.Name = *diffName } o, _ := u.remoteDiffObjects[ref] - return o + return ref, o } From 623debfbeb8d4d854c22d62a4bb0626a05576e93 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Mar 2022 16:56:27 +0100 Subject: [PATCH 0527/2916] fix: Fix uses of SplitN --- pkg/deployment/external_args.go | 2 +- pkg/utils/aws/arn.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/deployment/external_args.go b/pkg/deployment/external_args.go index e32029648..582e5592c 100644 --- a/pkg/deployment/external_args.go +++ b/pkg/deployment/external_args.go @@ -20,7 +20,7 @@ func ParseArgs(argsList []string) (map[string]string, error) { return nil, fmt.Errorf("invalid --arg argument. Must be --arg=some_var_name=value") } - s := strings.SplitN(arg, "=", 1) + s := strings.SplitN(arg, "=", 2) name := s[0] value := s[1] args[name] = value diff --git a/pkg/utils/aws/arn.go b/pkg/utils/aws/arn.go index 16b79bdf2..a8f88ee45 100644 --- a/pkg/utils/aws/arn.go +++ b/pkg/utils/aws/arn.go @@ -30,11 +30,11 @@ func ParseArn(arn string) (Arn, error) { result.Resource = elements[5] if strings.Index(result.Resource, "/") != 0 { - s := strings.SplitN(result.Resource, "/", 1) + s := strings.SplitN(result.Resource, "/", 2) result.ResourceType = s[0] result.Resource = s[1] } else if strings.Index(result.Resource, ":") != 0 { - s := strings.SplitN(result.Resource, ":", 1) + s := strings.SplitN(result.Resource, ":", 2) result.ResourceType = s[0] result.Resource = s[1] } From ac6fa4e4e47fc1639b73612ca639e2283d957419 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 16 Mar 2022 08:03:48 +0100 Subject: [PATCH 0528/2916] fix: Pass inclusion to deployment items --- pkg/deployment/deployment_item.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 70455d99f..89b0a9f83 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -40,10 +40,11 @@ type DeploymentItem struct { func NewDeploymentItem(project *DeploymentProject, collection *DeploymentCollection, config *types.DeploymentItemConfig, dir *string, index int) (*DeploymentItem, error) { di := &DeploymentItem{ - Project: project, - Config: config, - dir: dir, - index: index, + Project: project, + Inclusion: collection.Inclusion, + Config: config, + dir: dir, + index: index, } var err error From 80f9aaa2567e720a8d7f1007b2a2e368f484bb5b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 16 Mar 2022 09:16:44 +0100 Subject: [PATCH 0529/2916] docs: Run replace-commands-help --- docs/commands.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/commands.md b/docs/commands.md index 5e2e11a57..99337c7c1 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -215,12 +215,14 @@ In addition, the following arguments are available: Misc arguments: Command specific arguments. - -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you would answer - 'yes'. - --dry-run Performs all kubernetes API calls in dry-run mode. - -o, --output-format=OUTPUT-FORMAT,... Specify output format and target file, in the format 'format=path'. Format - can either be 'text' or 'yaml'. Can be specified multiple times. The actual - format for yaml is currently not documented and subject to change. + -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you would answer + 'yes'. + --dry-run Performs all kubernetes API calls in dry-run mode. + -o, --output-format=OUTPUT-FORMAT,... Specify output format and target file, in the format 'format=path'. + Format can either be 'text' or 'yaml'. Can be specified multiple times. + The actual format for yaml is currently not documented and subject to + change. + -l, --delete-by-label=DELETE-BY-LABEL,... Override the labels used to find objects for deletion. ``` From 86df441b343c48269869c5caef35647551a7a8ab Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 16 Mar 2022 10:02:33 +0100 Subject: [PATCH 0530/2916] refactor: Don't use workerpool in DeleteObjects --- pkg/deployment/utils/delete_utils.go | 37 +++++++++++++++++----------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/pkg/deployment/utils/delete_utils.go b/pkg/deployment/utils/delete_utils.go index f4f3be234..288341e0b 100644 --- a/pkg/deployment/utils/delete_utils.go +++ b/pkg/deployment/utils/delete_utils.go @@ -1,11 +1,12 @@ package utils import ( + "context" "github.com/codablock/kluctl/pkg/k8s" "github.com/codablock/kluctl/pkg/types" k8s2 "github.com/codablock/kluctl/pkg/types/k8s" - "github.com/codablock/kluctl/pkg/utils" "github.com/codablock/kluctl/pkg/utils/uo" + "golang.org/x/sync/semaphore" "k8s.io/apimachinery/pkg/runtime/schema" "strconv" "sync" @@ -134,8 +135,8 @@ func FindObjectsForDelete(k *k8s.K8sCluster, allClusterObjects []*uo.Unstructure } func DeleteObjects(k *k8s.K8sCluster, refs []k8s2.ObjectRef, doWait bool) (*types.CommandResult, error) { - wp := utils.NewDebuggerAwareWorkerPool(8) - defer wp.StopWait(false) + var wg sync.WaitGroup + sem := semaphore.NewWeighted(8) var ret types.CommandResult namespaceNames := make(map[string]bool) @@ -165,17 +166,18 @@ func DeleteObjects(k *k8s.K8sCluster, refs []k8s2.ObjectRef, doWait bool) (*type ref := ref_ if ref.GVK.GroupVersion().String() == "v1" && ref.GVK.Kind == "Namespace" { namespaceNames[ref.Name] = true - wp.Submit(func() error { + wg.Add(1) + go func() { + defer wg.Done() + _ = sem.Acquire(context.Background(), 1) + defer sem.Release(1) + apiWarnings, err := k.DeleteSingleObject(ref, k8s.DeleteOptions{NoWait: !doWait, IgnoreNotFoundError: true}) handleResult(ref, apiWarnings, err) - return nil - }) + }() } } - err := wp.StopWait(true) - if err != nil { - return nil, err - } + wg.Wait() for _, ref_ := range refs { ref := ref_ @@ -186,12 +188,17 @@ func DeleteObjects(k *k8s.K8sCluster, refs []k8s2.ObjectRef, doWait bool) (*type // already deleted via namespace continue } - wp.Submit(func() error { + wg.Add(1) + go func() { + defer wg.Done() + _ = sem.Acquire(context.Background(), 1) + defer sem.Release(1) + apiWarnings, err := k.DeleteSingleObject(ref, k8s.DeleteOptions{NoWait: !doWait, IgnoreNotFoundError: true}) handleResult(ref, apiWarnings, err) - return nil - }) + }() } - err = wp.StopWait(false) - return &ret, err + wg.Wait() + + return &ret, nil } From 1bf746dda61b38fc952249aec02714f617026bfa Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 16 Mar 2022 10:15:41 +0100 Subject: [PATCH 0531/2916] fix: Don't hold connection while waiting for deleted object to disappear --- pkg/k8s/k8s_cluster.go | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index dbe5938f8..989b87f00 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -588,7 +588,7 @@ func (k *K8sCluster) DeleteSingleObject(ref k8s.ObjectRef, options DeleteOptions o.DryRun = []string{"All"} } - return k.withDynamicClientForGVK(ref.GVK, ref.Namespace, func(r dynamic.ResourceInterface) error { + apiWarnings, err := k.withDynamicClientForGVK(ref.GVK, ref.Namespace, func(r dynamic.ResourceInterface) error { err := r.Delete(context.Background(), ref.Name, o) if err != nil { if options.IgnoreNotFoundError && errors.IsNotFound(err) { @@ -596,21 +596,25 @@ func (k *K8sCluster) DeleteSingleObject(ref k8s.ObjectRef, options DeleteOptions } return err } - - if !dryRun && !options.NoWait { - err = k.waitForDeletedObject(r, ref) - if err != nil { - return err - } - } return nil }) + if err != nil { + return apiWarnings, err + } + + if !dryRun && !options.NoWait { + err = k.waitForDeletedObject(ref) + if err != nil { + return apiWarnings, err + } + } + return apiWarnings, nil } -func (k *K8sCluster) waitForDeletedObject(r dynamic.ResourceInterface, ref k8s.ObjectRef) error { +func (k *K8sCluster) waitForDeletedObject(ref k8s.ObjectRef) error { for true { - o := v1.GetOptions{} - _, err := r.Get(context.Background(), ref.Name, o) + _, _, err := k.GetSingleObject(ref) + if err != nil { if errors.IsNotFound(err) { return nil From c25d79fde888c5108a85923383f67e018e5a7e02 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 16 Mar 2022 10:16:09 +0100 Subject: [PATCH 0532/2916] fix: Sleep between gets in waitForDeletedObject --- pkg/k8s/k8s_cluster.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index 989b87f00..e7726b215 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -621,6 +621,8 @@ func (k *K8sCluster) waitForDeletedObject(ref k8s.ObjectRef) error { } return err } + + time.Sleep(time.Millisecond * 100) } return nil } From e5bf3cabea345fa8503d0a9ce7b9753267659392 Mon Sep 17 00:00:00 2001 From: Marcel Frehe Date: Wed, 16 Mar 2022 10:20:53 +0100 Subject: [PATCH 0533/2916] docs: fix minor typos in `docs/deployments.md` --- docs/deployments.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/deployments.md b/docs/deployments.md index 139f7bc9b..cdb493e32 100644 --- a/docs/deployments.md +++ b/docs/deployments.md @@ -20,7 +20,7 @@ Some visualized files/directories have links attached, follow them to get more i
 -- project-dir/
-   |-- deploymentyml
+   |-- deployment.yml
    |-- .gitignore
    |-- kustomize-deployment1/
    |   |-- kustomization.yml
@@ -64,7 +64,7 @@ inside `deployment.yml`.
 
 Documentation on available variables, methods and filters is available in [jinja2-templating](./jinja2-templating.md)
 
-## Conatainer image versions
+## Container image versions
 
 Please read [images](./images.md) about dynamic image versions.
 

From 1f444447eb020213463f77f93dfc579e00b79335 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Thu, 17 Mar 2022 11:07:43 +0100
Subject: [PATCH 0534/2916] refactor: Use simple WaitGroup in ApplyDeployments

---
 pkg/deployment/utils/apply_utils.go | 25 ++++++++++++-------------
 1 file changed, 12 insertions(+), 13 deletions(-)

diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go
index f27fcd449..0fd97b8bd 100644
--- a/pkg/deployment/utils/apply_utils.go
+++ b/pkg/deployment/utils/apply_utils.go
@@ -418,28 +418,27 @@ func (a *ApplyUtil) applyDeploymentItem(d *deployment.DeploymentItem) {
 func (a *ApplyUtil) ApplyDeployments() {
 	log.Infof("Running server-side apply for all objects")
 
-	wp := utils.NewDebuggerAwareWorkerPool(16)
-	defer wp.StopWait(false)
+	var wg sync.WaitGroup
 
-	previousWasBarrier := false
 	for _, d_ := range a.deployments {
 		d := d_
 		if a.abortSignal {
 			break
 		}
-		if previousWasBarrier {
-			log.Infof("Waiting on barrier...")
-			_ = wp.StopWait(true)
-		}
 
-		previousWasBarrier = d.Config.Barrier != nil && *d.Config.Barrier
-
-		wp.Submit(func() error {
+		wg.Add(1)
+		go func() {
+			defer wg.Done()
 			a.applyDeploymentItem(d)
-			return nil
-		})
+		}()
+
+		barrier := d.Config.Barrier != nil && *d.Config.Barrier
+		if barrier {
+			log.Infof("Waiting on barrier...")
+			wg.Wait()
+		}
 	}
-	_ = wp.StopWait(false)
+	wg.Wait()
 }
 
 func (a *ApplyUtil) ReplaceObject(ref k8s2.ObjectRef, firstVersion *uo.UnstructuredObject, callback func(o *uo.UnstructuredObject) (*uo.UnstructuredObject, error)) {

From 591f8abc7060d86e32cade76148c91d54eb34031 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Thu, 17 Mar 2022 11:08:22 +0100
Subject: [PATCH 0535/2916] refactor: Rename WaitHook to WaitReadiness

---
 pkg/deployment/commands/deploy.go   |  2 +-
 pkg/deployment/commands/diff.go     |  2 +-
 pkg/deployment/utils/apply_utils.go | 18 +++++++++---------
 pkg/deployment/utils/hooks_util.go  |  2 +-
 4 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/pkg/deployment/commands/deploy.go b/pkg/deployment/commands/deploy.go
index 1d7315fa4..d748f5c02 100644
--- a/pkg/deployment/commands/deploy.go
+++ b/pkg/deployment/commands/deploy.go
@@ -39,7 +39,7 @@ func (cmd *DeployCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, error) {
 		ForceReplaceOnError: cmd.ForceReplaceOnError,
 		DryRun:              k.DryRun,
 		AbortOnError:        cmd.AbortOnError,
-		HookTimeout:         cmd.HookTimeout,
+		WaitObjectTimeout:   cmd.HookTimeout,
 	}
 	au := utils2.NewApplyUtil(dew, cmd.c.Deployments, ru, k, o)
 	au.ApplyDeployments()
diff --git a/pkg/deployment/commands/diff.go b/pkg/deployment/commands/diff.go
index 5ed6d1e31..80b808c49 100644
--- a/pkg/deployment/commands/diff.go
+++ b/pkg/deployment/commands/diff.go
@@ -39,7 +39,7 @@ func (cmd *DiffCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, error) {
 		ForceReplaceOnError: cmd.ForceReplaceOnError,
 		DryRun:              true,
 		AbortOnError:        false,
-		HookTimeout:         0,
+		WaitObjectTimeout:   0,
 	}
 	au := utils.NewApplyUtil(dew, cmd.c.Deployments, ru, k, o)
 	au.ApplyDeployments()
diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go
index 0fd97b8bd..4228d939f 100644
--- a/pkg/deployment/utils/apply_utils.go
+++ b/pkg/deployment/utils/apply_utils.go
@@ -25,7 +25,7 @@ type ApplyUtilOptions struct {
 	ForceReplaceOnError bool
 	DryRun              bool
 	AbortOnError        bool
-	HookTimeout         time.Duration
+	WaitObjectTimeout   time.Duration
 }
 
 type ApplyUtil struct {
@@ -284,13 +284,13 @@ func (a *ApplyUtil) handleNewCRDs(x *uo.UnstructuredObject, err error) (bool, er
 	return false, err
 }
 
-func (a *ApplyUtil) WaitHook(ref k8s2.ObjectRef) bool {
+func (a *ApplyUtil) WaitReadiness(ref k8s2.ObjectRef) bool {
 	if a.o.DryRun {
 		return true
 	}
 
 	log2 := log.WithField("ref", ref)
-	log2.Debugf("Waiting for hook to get ready")
+	log2.Debugf("Waiting for object to get ready")
 
 	lastLogTime := time.Now()
 	didLog := false
@@ -301,7 +301,7 @@ func (a *ApplyUtil) WaitHook(ref k8s2.ObjectRef) bool {
 		if err != nil {
 			if errors.IsNotFound(err) {
 				if didLog {
-					log2.Warningf("Cancelled waiting for hook as it disappeared while waiting for it")
+					log2.Warningf("Cancelled waiting for object as it disappeared while waiting for it")
 				}
 				a.HandleError(ref, fmt.Errorf("object disappeared while waiting for it to become ready"))
 				return false
@@ -312,13 +312,13 @@ func (a *ApplyUtil) WaitHook(ref k8s2.ObjectRef) bool {
 		v := validation.ValidateObject(o, false)
 		if v.Ready {
 			if didLog {
-				log2.Infof("Finished waiting for hook")
+				log2.Infof("Finished waiting for object")
 			}
 			return true
 		}
 		if len(v.Errors) != 0 {
 			if didLog {
-				log2.Warningf("Cancelled waiting for hook due to errors")
+				log2.Warningf("Cancelled waiting for object due to errors")
 			}
 			for _, e := range v.Errors {
 				a.HandleError(ref, fmt.Errorf(e.Error))
@@ -326,15 +326,15 @@ func (a *ApplyUtil) WaitHook(ref k8s2.ObjectRef) bool {
 			return false
 		}
 
-		if a.o.HookTimeout != 0 && time.Now().Sub(startTime) >= a.o.HookTimeout {
-			err := fmt.Errorf("timed out while waiting for hook")
+		if a.o.WaitObjectTimeout != 0 && time.Now().Sub(startTime) >= a.o.WaitObjectTimeout {
+			err := fmt.Errorf("timed out while waiting for object")
 			log2.Warningf(err.Error())
 			a.HandleError(ref, err)
 			return false
 		}
 
 		if !didLog {
-			log2.Infof("Waiting for hook to get ready...")
+			log2.Infof("Waiting for object to get ready...")
 			didLog = true
 			lastLogTime = time.Now()
 		} else if didLog && time.Now().Sub(lastLogTime) >= 10*time.Second {
diff --git a/pkg/deployment/utils/hooks_util.go b/pkg/deployment/utils/hooks_util.go
index df4e9244d..8fe63a24f 100644
--- a/pkg/deployment/utils/hooks_util.go
+++ b/pkg/deployment/utils/hooks_util.go
@@ -116,7 +116,7 @@ func (u *HooksUtil) RunHooks(d *deployment.DeploymentItem, hooks []string) {
 		if !h.wait {
 			continue
 		}
-		waitResults[ref] = u.a.WaitHook(ref)
+		waitResults[ref] = u.a.WaitReadiness(ref)
 	}
 
 	var deleteAfterObjects []*hook

From 881ae9b73d8983f0dace6bb4d0ca9b2fdd5d566d Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Thu, 17 Mar 2022 11:09:02 +0100
Subject: [PATCH 0536/2916] refactor: Use uo.UnstructoredObject to read/modify
 kustomization.yml

---
 pkg/deployment/deployment_item.go | 13 ++++++++-----
 1 file changed, 8 insertions(+), 5 deletions(-)

diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go
index 89b0a9f83..3069960f4 100644
--- a/pkg/deployment/deployment_item.go
+++ b/pkg/deployment/deployment_item.go
@@ -279,21 +279,24 @@ func (di *DeploymentItem) prepareKustomizationYaml() error {
 		return nil
 	}
 
-	var kustomizeYaml map[string]interface{}
-	err := yaml.ReadYamlFile(kustomizeYamlPath, &kustomizeYaml)
+	ky, err := uo.FromFile(kustomizeYamlPath)
 	if err != nil {
 		return err
 	}
 
 	overrideNamespace := di.Project.getOverrideNamespace()
 	if overrideNamespace != nil {
-		if _, ok := kustomizeYaml["namespace"]; !ok {
-			kustomizeYaml["namespace"] = *overrideNamespace
+		_, ok, err := ky.GetNestedString("namespace")
+		if err != nil {
+			return err
+		}
+		if !ok {
+			ky.SetNestedField(*overrideNamespace, "namespace")
 		}
 	}
 
 	// Save modified kustomize.yml
-	err = yaml.WriteYamlFile(kustomizeYamlPath, kustomizeYaml)
+	err = yaml.WriteYamlFile(kustomizeYamlPath, ky)
 	if err != nil {
 		return err
 	}

From 44cb2c720996f6184d8b4429f46206ad8fd06a88 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Thu, 17 Mar 2022 11:16:52 +0100
Subject: [PATCH 0537/2916] feat: Allow to enable barriers on deployment item
 level

---
 docs/annotations.md                 | 23 ++++++++++++++++++++++-
 pkg/deployment/deployment_item.go   |  5 +++++
 pkg/deployment/utils/apply_utils.go |  2 +-
 pkg/utils/utils.go                  | 12 ++++++++++++
 4 files changed, 40 insertions(+), 2 deletions(-)

diff --git a/docs/annotations.md b/docs/annotations.md
index 147003eed..ef6d17361 100644
--- a/docs/annotations.md
+++ b/docs/annotations.md
@@ -1,4 +1,4 @@
-# Annotations
+# Annotations on object level
 
 kluctl supports multiple annotations that influence individual commands. These are:
 
@@ -124,3 +124,24 @@ are added to the validation result, which is then returned by the validate comma
 The annotation key is dynamic, meaning that all annotations that begin with `validate-result.kluctl.io/` are taken
 into account.
 
+# Annotations on kustomize deployment level
+
+In addition to kluctl.io annotations which can be set on object/resource level, you can also set a few annotations
+inside the kustomization.yml itself.
+
+Example:
+```yaml
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+
+metadata:
+  annotations:
+    kluctl.io/barrier: "true"
+
+resources:
+  - deployment.yml
+```
+
+### kluctl.io/barrier
+If set to `true`, kluctl will wait for all previous objects to be applied (but not necessarily ready). This has the
+same effect as [barrier](./deployments.md#barriers) from deployment projects.
diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go
index 3069960f4..1402faf9d 100644
--- a/pkg/deployment/deployment_item.go
+++ b/pkg/deployment/deployment_item.go
@@ -28,6 +28,9 @@ type DeploymentItem struct {
 	dir       *string
 	index     int
 
+	// These values come from the metadata of the kustomization.yml
+	Barrier       bool
+
 	Objects []*uo.UnstructuredObject
 
 	relProjectDir       string
@@ -295,6 +298,8 @@ func (di *DeploymentItem) prepareKustomizationYaml() error {
 		}
 	}
 
+	di.Barrier = utils.ParseBoolOrFalse(ky.GetK8sAnnotation("kluctl.io/barrier"))
+
 	// Save modified kustomize.yml
 	err = yaml.WriteYamlFile(kustomizeYamlPath, ky)
 	if err != nil {
diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go
index 4228d939f..5ad9441d9 100644
--- a/pkg/deployment/utils/apply_utils.go
+++ b/pkg/deployment/utils/apply_utils.go
@@ -432,7 +432,7 @@ func (a *ApplyUtil) ApplyDeployments() {
 			a.applyDeploymentItem(d)
 		}()
 
-		barrier := d.Config.Barrier != nil && *d.Config.Barrier
+		barrier := (d.Config.Barrier != nil && *d.Config.Barrier) || d.Barrier
 		if barrier {
 			log.Infof("Waiting on barrier...")
 			wg.Wait()
diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go
index 5f303199b..9a6a0ecf0 100644
--- a/pkg/utils/utils.go
+++ b/pkg/utils/utils.go
@@ -7,6 +7,7 @@ import (
 	"log"
 	"os"
 	"path/filepath"
+	"strconv"
 	"sync"
 )
 
@@ -42,3 +43,14 @@ func FindStrInSlice(a []string, s string) int {
 	}
 	return -1
 }
+
+func ParseBoolOrFalse(s *string) bool {
+	if s == nil {
+		return false
+	}
+	b, err := strconv.ParseBool(*s)
+	if err != nil {
+		return false
+	}
+	return b
+}

From 47753a112da558de7498f6f9df74dc4b37b8898f Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Thu, 17 Mar 2022 11:30:32 +0100
Subject: [PATCH 0538/2916] feat: Allow to wait for readiness of objects

---
 docs/annotations.md                 | 5 +++++
 docs/deployments.md                 | 7 ++++++-
 pkg/deployment/deployment_item.go   | 2 ++
 pkg/deployment/utils/apply_utils.go | 5 +++++
 pkg/types/deployment.go             | 4 ++++
 5 files changed, 22 insertions(+), 1 deletion(-)

diff --git a/docs/annotations.md b/docs/annotations.md
index ef6d17361..046f341bc 100644
--- a/docs/annotations.md
+++ b/docs/annotations.md
@@ -137,6 +137,7 @@ kind: Kustomization
 metadata:
   annotations:
     kluctl.io/barrier: "true"
+    kluctl.io/wait-readiness: "true"
 
 resources:
   - deployment.yml
@@ -145,3 +146,7 @@ resources:
 ### kluctl.io/barrier
 If set to `true`, kluctl will wait for all previous objects to be applied (but not necessarily ready). This has the
 same effect as [barrier](./deployments.md#barriers) from deployment projects.
+
+### kluctl.io/wait-readiness
+If set to `true`, kluctl will wait for readiness of all objects from this kustomization project. Readiness is defined
+the same as in [hook readiness](./hooks.md#hook-readiness).
diff --git a/docs/deployments.md b/docs/deployments.md
index cdb493e32..95f3324a9 100644
--- a/docs/deployments.md
+++ b/docs/deployments.md
@@ -111,12 +111,17 @@ Please see [Kustomize integration](./kustomize-integration.md) for more details.
 Example:
 ```yaml
 deployments:
-- path: path/to/deployment
+- path: path/to/deployment1
+- path: path/to/deployment2
+  waitReadiness: true
 ```
 
 The `path` must point to a directory relative to the directory containing the `deployment.yml`. Only directories
 that are part of the kluctl project are allowed. The directory must contain a valid `kustomization.yml`.
 
+`waitReadiness` is optional and if set to `true` instructs kluctl to wait for readiness of each individual object
+of the kustomize deployment. Readiness is defined the same as in [hook readiness](./hooks.md#hook-readiness).
+
 #### Includes
 
 Specifies a sub-deployment project to be included. The included sub-deployment project will inherit many properties
diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go
index 1402faf9d..cac96677a 100644
--- a/pkg/deployment/deployment_item.go
+++ b/pkg/deployment/deployment_item.go
@@ -30,6 +30,7 @@ type DeploymentItem struct {
 
 	// These values come from the metadata of the kustomization.yml
 	Barrier       bool
+	WaitReadiness bool
 
 	Objects []*uo.UnstructuredObject
 
@@ -299,6 +300,7 @@ func (di *DeploymentItem) prepareKustomizationYaml() error {
 	}
 
 	di.Barrier = utils.ParseBoolOrFalse(ky.GetK8sAnnotation("kluctl.io/barrier"))
+	di.WaitReadiness = utils.ParseBoolOrFalse(ky.GetK8sAnnotation("kluctl.io/wait-readiness"))
 
 	// Save modified kustomize.yml
 	err = yaml.WriteYamlFile(kustomizeYamlPath, ky)
diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go
index 5ad9441d9..50649ea8d 100644
--- a/pkg/deployment/utils/apply_utils.go
+++ b/pkg/deployment/utils/apply_utils.go
@@ -406,6 +406,11 @@ func (a *ApplyUtil) applyDeploymentItem(d *deployment.DeploymentItem) {
 			startTime = time.Now()
 			didLog = true
 		}
+
+		waitReadiness := (d.Config.WaitReadiness != nil && *d.Config.WaitReadiness) || d.WaitReadiness || utils.ParseBoolOrFalse(o.GetK8sAnnotation("kluctl.io/wait-readiness"))
+		if waitReadiness {
+			a.WaitReadiness(o.GetK8sRef())
+		}
 	}
 
 	if initialDeploy {
diff --git a/pkg/types/deployment.go b/pkg/types/deployment.go
index d90f82685..6b203b57e 100644
--- a/pkg/types/deployment.go
+++ b/pkg/types/deployment.go
@@ -11,6 +11,7 @@ type DeploymentItemConfig struct {
 	Include          *string                  `yaml:"include,omitempty"`
 	Tags             []string                 `yaml:"tags,omitempty"`
 	Barrier          *bool                    `yaml:"barrier,omitempty"`
+	WaitReadiness    *bool                    `yaml:"waitReadiness,omitempty"`
 	Vars             []*VarsListItem          `yaml:"vars,omitempty"`
 	SkipDeleteIfTags *bool                    `yaml:"skipDeleteIfTags,omitempty"`
 	OnlyRender       *bool                    `yaml:"onlyRender,omitempty"`
@@ -23,6 +24,9 @@ func ValidateDeploymentItemConfig(sl validator.StructLevel) {
 	if s.Path != nil && s.Include != nil {
 		sl.ReportError(s, "path", "Path", "pathinclude", "path and include can not be set at the same time")
 	}
+	if s.Path == nil && s.WaitReadiness != nil {
+		sl.ReportError(s, "waitReadiness", "WaitReadiness", "waitreadiness", "only kustomize deployments are allowed to have waitReadiness set")
+	}
 }
 
 type DeleteObjectItemConfig struct {

From d7d26f5510f0608bba3bc1f1a8674c6d2942a1e3 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Thu, 17 Mar 2022 13:41:56 +0100
Subject: [PATCH 0539/2916] refactor: Better control about how to react on
 missing fields in ValidateObject

---
 pkg/validation/validation.go | 86 ++++++++++++++++++++----------------
 1 file changed, 47 insertions(+), 39 deletions(-)

diff --git a/pkg/validation/validation.go b/pkg/validation/validation.go
index ae9503968..811856ddb 100644
--- a/pkg/validation/validation.go
+++ b/pkg/validation/validation.go
@@ -32,6 +32,14 @@ func (c condition) getMessage(def string) string {
 	return c.message
 }
 
+type errorReaction int
+const (
+	reactIgnore errorReaction = iota
+	reactError
+	reactWarning
+	reactNotReady
+)
+
 func ValidateObject(o *uo.UnstructuredObject, notReadyIsError bool) (ret types.ValidateResult) {
 	ref := o.GetK8sRef()
 
@@ -87,7 +95,20 @@ func ValidateObject(o *uo.UnstructuredObject, notReadyIsError bool) (ret types.V
 		ret.Ready = false
 	}
 
-	findConditions := func(typ string, doError bool, doRaise bool) []condition {
+	reactToError := func(err error, er errorReaction, doRaise bool) {
+		if er == reactError {
+			addError(err.Error())
+		} else if er == reactWarning {
+			addWarning(err.Error())
+		} else if er == reactNotReady {
+			addNotReady(err.Error())
+		}
+		if doRaise {
+			panic(&validationFailed{})
+		}
+	}
+
+	findConditions := func(typ string, er errorReaction, doRaise bool) []condition {
 		var ret []condition
 		l, ok, _ := status.GetNestedObjectList("conditions")
 		if ok {
@@ -105,18 +126,14 @@ func ValidateObject(o *uo.UnstructuredObject, notReadyIsError bool) (ret types.V
 				}
 			}
 		}
-		if len(ret) == 0 && doError {
-			err := fmt.Errorf("%s condition not in status", typ)
-			addError(err.Error())
-			if doRaise {
-				panic(&validationFailed{})
-			}
+		if len(ret) == 0 {
+			reactToError(fmt.Errorf("%s condition not in status", typ), er, doRaise)
 		}
 		return ret
 	}
 
-	getCondition := func(typ string, doError bool, doRaise bool) condition {
-		c := findConditions(typ, doError, doRaise)
+	getCondition := func(typ string, er errorReaction, doRaise bool) condition {
+		c := findConditions(typ, er, doRaise)
 		if len(c) == 0 {
 			return condition{}
 		}
@@ -129,22 +146,18 @@ func ValidateObject(o *uo.UnstructuredObject, notReadyIsError bool) (ret types.V
 		}
 		return c[0]
 	}
-	getStatusField := func(field string, doError bool, doRaise bool, def interface{}) interface{} {
+	getStatusField := func(field string, er errorReaction, doRaise bool, def interface{}) interface{} {
 		v, ok, _ := status.GetNestedField(field)
-		if !ok && doError {
-			err := fmt.Errorf("%s field not in status or empty", field)
-			addError(err.Error())
-			if doRaise {
-				panic(&validationFailed{})
-			}
+		if !ok {
+			reactToError(fmt.Errorf("%s field not in status or empty", field), er, doRaise)
 		}
 		if !ok {
 			return def
 		}
 		return v
 	}
-	getStatusFieldStr := func(field string, doError bool, doRaise bool, def string) string {
-		v := getStatusField(field, doError, doRaise, def)
+	getStatusFieldStr := func(field string, er errorReaction, doRaise bool, def string) string {
+		v := getStatusField(field, er, doRaise, def)
 		if s, ok := v.(string); ok {
 			return s
 		} else {
@@ -156,8 +169,8 @@ func ValidateObject(o *uo.UnstructuredObject, notReadyIsError bool) (ret types.V
 		}
 		return def
 	}
-	getStatusFieldInt := func(field string, doError bool, doRaise bool, def int64) int64 {
-		v := getStatusField(field, doError, doRaise, def)
+	getStatusFieldInt := func(field string, er errorReaction, doRaise bool, def int64) int64 {
+		v := getStatusField(field, er, doRaise, def)
 		if i, ok := v.(int64); ok {
 			return i
 		} else if i, ok := v.(uint64); ok {
@@ -166,12 +179,7 @@ func ValidateObject(o *uo.UnstructuredObject, notReadyIsError bool) (ret types.V
 			return int64(i)
 		} else {
 			err := fmt.Errorf("%s field is not an int", field)
-			if doError {
-				addError(err.Error())
-			}
-			if doRaise {
-				panic(&validationFailed{})
-			}
+			reactToError(err, er, doRaise)
 		}
 		return def
 	}
@@ -208,28 +216,28 @@ func ValidateObject(o *uo.UnstructuredObject, notReadyIsError bool) (ret types.V
 
 	switch o.GetK8sGVK().GroupKind() {
 	case schema.GroupKind{Group: "", Kind: "Pod"}:
-		c := getCondition("Ready", false, false)
+		c := getCondition("Ready", reactIgnore, false)
 		if c.status != "True" {
 			addNotReady(c.getMessage("Not ready"))
 		}
 	case schema.GroupKind{Group: "batch", Kind: "Job"}:
-		c := getCondition("Failed", false, false)
+		c := getCondition("Failed", reactIgnore, false)
 		if c.status == "True" {
 			addError(c.getMessage("Failed"))
 		} else {
-			c = getCondition("Complete", false, false)
+			c = getCondition("Complete", reactIgnore, false)
 			if c.status != "True" {
 				addNotReady(c.getMessage("Not completed"))
 			}
 		}
 	case schema.GroupKind{Group: "apps", Kind: "Deployment"}:
-		readyReplicas := getStatusFieldInt("readyReplicas", true, true, 0)
-		replicas := getStatusFieldInt("replicas", true, true, 0)
+		readyReplicas := getStatusFieldInt("readyReplicas", reactNotReady, true, 0)
+		replicas := getStatusFieldInt("replicas", reactNotReady, true, 0)
 		if readyReplicas < replicas {
 			addNotReady(fmt.Sprintf("readyReplicas (%d) is less then replicas (%d)", readyReplicas, replicas))
 		}
 	case schema.GroupKind{Group: "", Kind: "PersistentVolumeClaim"}:
-		phase := getStatusFieldStr("phase", true, true, "")
+		phase := getStatusFieldStr("phase", reactNotReady, true, "")
 		if phase != "Bound" {
 			addNotReady("Volume is not bound")
 		}
@@ -252,8 +260,8 @@ func ValidateObject(o *uo.UnstructuredObject, notReadyIsError bool) (ret types.V
 	case schema.GroupKind{Group: "apps", Kind: "DaemonSet"}:
 		updateStrategyType, _, _ := o.GetNestedString("spec", "updateStrategy", "type")
 		if updateStrategyType == "RollingUpdate" {
-			updatedNumberScheduled := getStatusFieldInt("updatedNumberScheduled", true, true, 0)
-			desiredNumberScheduled := getStatusFieldInt("desiredNumberScheduled", true, true, 0)
+			updatedNumberScheduled := getStatusFieldInt("updatedNumberScheduled", reactNotReady, true, 0)
+			desiredNumberScheduled := getStatusFieldInt("desiredNumberScheduled", reactNotReady, true, 0)
 			if updatedNumberScheduled != desiredNumberScheduled {
 				addNotReady(fmt.Sprintf("DaemonSet is not ready. %d out of %d expected pods have been scheduled", updatedNumberScheduled, desiredNumberScheduled))
 			} else {
@@ -266,7 +274,7 @@ func ValidateObject(o *uo.UnstructuredObject, notReadyIsError bool) (ret types.V
 					maxUnavailable = desiredNumberScheduled
 				}
 				expectedReady := desiredNumberScheduled - maxUnavailable
-				numberReady := getStatusFieldInt("numberReady", true, true, 0)
+				numberReady := getStatusFieldInt("numberReady", reactNotReady, true, 0)
 				if numberReady < expectedReady {
 					addNotReady(fmt.Sprintf("DaemonSet is not ready. %d out of %d expected pods are ready", numberReady, expectedReady))
 				}
@@ -275,9 +283,9 @@ func ValidateObject(o *uo.UnstructuredObject, notReadyIsError bool) (ret types.V
 	case schema.GroupKind{Group: "apiextensions.k8s.io", Kind: "CustomResourceDefinition"}:
 		// This is based on how Helm check for ready CRDs.
 		// See https://github.com/helm/helm/blob/249d1b5fb98541f5fb89ab11019b6060d6b169f1/pkg/kube/ready.go#L342
-		c := getCondition("Established", false, false)
+		c := getCondition("Established", reactIgnore, false)
 		if c.status != "True" {
-			c = getCondition("NamesAccepted", true, true)
+			c = getCondition("NamesAccepted", reactNotReady, true)
 			if c.status != "False" {
 				addNotReady("CRD is not ready")
 			}
@@ -290,12 +298,12 @@ func ValidateObject(o *uo.UnstructuredObject, notReadyIsError bool) (ret types.V
 			if !ok {
 				replicas = 1
 			}
-			updatedReplicas := getStatusFieldInt("updatedReplicas", true, true, 0)
+			updatedReplicas := getStatusFieldInt("updatedReplicas", reactNotReady, true, 0)
 			expectedReplicas := replicas - partition
 			if updatedReplicas != expectedReplicas {
 				addNotReady(fmt.Sprintf("StatefulSet is not ready. %d out of %d expected pods have been scheduled", updatedReplicas, expectedReplicas))
 			} else {
-				readyReplicas := getStatusFieldInt("readyReplicas", true, true, 0)
+				readyReplicas := getStatusFieldInt("readyReplicas", reactNotReady, true, 0)
 				if readyReplicas != replicas {
 					addNotReady(fmt.Sprintf("StatefulSet is not ready. %d out of %d expected pods are ready", readyReplicas, replicas))
 				}

From b4a46ca582c334a0a993e64921be9ac5d163ec73 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Thu, 17 Mar 2022 14:41:33 +0100
Subject: [PATCH 0540/2916] fix: Output of list-images must be of
 FixedImagesConfig format

---
 cmd/kluctl/commands/cmd_list_images.go | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/cmd/kluctl/commands/cmd_list_images.go b/cmd/kluctl/commands/cmd_list_images.go
index b59ec9b84..d08080247 100644
--- a/cmd/kluctl/commands/cmd_list_images.go
+++ b/cmd/kluctl/commands/cmd_list_images.go
@@ -2,6 +2,7 @@ package commands
 
 import (
 	"github.com/codablock/kluctl/cmd/kluctl/args"
+	"github.com/codablock/kluctl/pkg/types"
 )
 
 type listImagesCmd struct {
@@ -31,7 +32,9 @@ func (cmd *listImagesCmd) Run() error {
 		inclusionFlags: cmd.InclusionFlags,
 	}
 	return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error {
-		result := ctx.images.SeenImages(cmd.Simple)
+		result := types.FixedImagesConfig{
+			Images: ctx.images.SeenImages(cmd.Simple),
+		}
 		return outputYamlResult(cmd.Output, result, false)
 	})
 }

From 782719da5ccea206384cacee515cf23e112844e0 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Thu, 17 Mar 2022 14:41:49 +0100
Subject: [PATCH 0541/2916] fix: Fix crash in GetLatestImageFromRegistry

---
 pkg/deployment/images.go | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/pkg/deployment/images.go b/pkg/deployment/images.go
index 12001706f..e3f4d1210 100644
--- a/pkg/deployment/images.go
+++ b/pkg/deployment/images.go
@@ -90,6 +90,9 @@ func (images *Images) GetLatestImageFromRegistry(image string, latestVersion str
 	}
 
 	tags = versions.Filter(lv, tags)
+	if len(tags) == 0 {
+		return nil, fmt.Errorf("no tag matched latest_version: %s", latestVersion)
+	}
 
 	latest := lv.Latest(tags)
 	result := fmt.Sprintf("%s:%s", image, latest)

From a2264f32ea9f9a20ff540b769f5a08439ea14a36 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Thu, 17 Mar 2022 14:43:23 +0100
Subject: [PATCH 0542/2916] fix: Don't accept objects without status field if
 the spec says it should have one

---
 e2e/utils_test.go                   |  2 +-
 pkg/deployment/commands/validate.go |  2 +-
 pkg/deployment/utils/apply_utils.go |  2 +-
 pkg/k8s/k8s_cluster.go              | 90 ++++++++++++++++++++++++++---
 pkg/validation/validation.go        | 31 ++++++++--
 5 files changed, 111 insertions(+), 16 deletions(-)

diff --git a/e2e/utils_test.go b/e2e/utils_test.go
index fe828d1ed..25d39b303 100644
--- a/e2e/utils_test.go
+++ b/e2e/utils_test.go
@@ -30,7 +30,7 @@ func waitForReadiness(t *testing.T, k *KindCluster, namespace string, resource s
 			t.Fatal(err)
 		}
 
-		v := validation.ValidateObject(y, true)
+		v := validation.ValidateObject(k, y, true)
 		if v.Ready {
 			return true
 		}
diff --git a/pkg/deployment/commands/validate.go b/pkg/deployment/commands/validate.go
index 0ddde389b..210b6de19 100644
--- a/pkg/deployment/commands/validate.go
+++ b/pkg/deployment/commands/validate.go
@@ -53,7 +53,7 @@ func (cmd *ValidateCommand) Run(k *k8s.K8sCluster) (*types.ValidateResult, error
 				result.Errors = append(result.Errors, types.DeploymentError{Ref: ref, Error: "object not found"})
 				continue
 			}
-			r := validation.ValidateObject(remoteObject, true)
+			r := validation.ValidateObject(k, remoteObject, true)
 			result.Errors = append(result.Errors, r.Errors...)
 			result.Warnings = append(result.Warnings, r.Warnings...)
 			result.Results = append(result.Results, r.Results...)
diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go
index 50649ea8d..460a473e3 100644
--- a/pkg/deployment/utils/apply_utils.go
+++ b/pkg/deployment/utils/apply_utils.go
@@ -309,7 +309,7 @@ func (a *ApplyUtil) WaitReadiness(ref k8s2.ObjectRef) bool {
 			a.HandleError(ref, err)
 			return false
 		}
-		v := validation.ValidateObject(o, false)
+		v := validation.ValidateObject(a.k, o, false)
 		if v.Ready {
 			if didLog {
 				log2.Infof("Finished waiting for object")
diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go
index e7726b215..da3ffb8c7 100644
--- a/pkg/k8s/k8s_cluster.go
+++ b/pkg/k8s/k8s_cluster.go
@@ -45,9 +45,13 @@ type K8sCluster struct {
 
 	ServerVersion *goversion.Version
 
-	allResources       map[schema.GroupVersionKind]v1.APIResource
-	preferredResources map[schema.GroupKind]v1.APIResource
-	mutex              sync.Mutex
+	allResources       map[schema.GroupVersionKind]*v1.APIResource
+	preferredResources map[schema.GroupKind]*v1.APIResource
+	crdCache           map[k8s.ObjectRef]struct {
+		crd *uo.UnstructuredObject
+		err error
+	}
+	mutex sync.Mutex
 }
 
 type parallelClientEntry struct {
@@ -164,8 +168,12 @@ func (k *K8sCluster) updateResources(doLock bool) error {
 		defer k.mutex.Unlock()
 	}
 
-	k.allResources = map[schema.GroupVersionKind]v1.APIResource{}
-	k.preferredResources = map[schema.GroupKind]v1.APIResource{}
+	k.allResources = map[schema.GroupVersionKind]*v1.APIResource{}
+	k.preferredResources = map[schema.GroupKind]*v1.APIResource{}
+	k.crdCache = map[k8s.ObjectRef]struct {
+		crd *uo.UnstructuredObject
+		err error
+	}{}
 
 	_, arls, err := k.discovery.ServerGroupsAndResources()
 	if err != nil {
@@ -197,7 +205,7 @@ func (k *K8sCluster) updateResources(doLock bool) error {
 			if _, ok := k.allResources[gvk]; ok {
 				ok = false
 			}
-			k.allResources[gvk] = ar
+			k.allResources[gvk] = &ar
 		}
 	}
 
@@ -224,7 +232,7 @@ func (k *K8sCluster) updateResources(doLock bool) error {
 			if _, ok := deprecatedResources[gk]; ok {
 				continue
 			}
-			k.preferredResources[gk] = ar
+			k.preferredResources[gk] = &ar
 		}
 	}
 	return nil
@@ -361,6 +369,74 @@ func (k *K8sCluster) getGVRForGVK(gvk schema.GroupVersionKind) (*schema.GroupVer
 	}, ar.Namespaced, nil
 }
 
+func (k *K8sCluster) GetApiResourceForGVK(gvk schema.GroupVersionKind) *v1.APIResource {
+	k.mutex.Lock()
+	defer k.mutex.Unlock()
+
+	ar, _ := k.allResources[gvk]
+	return ar
+}
+
+func (k *K8sCluster) GetCRDForGVK(gvk schema.GroupVersionKind) (*uo.UnstructuredObject, error) {
+	ar := k.GetApiResourceForGVK(gvk)
+	if ar == nil {
+		return nil, fmt.Errorf("APIResource for %s not found", gvk.String())
+	}
+
+	crdRef := k8s.ObjectRef{
+		GVK:  schema.GroupVersionKind{Group: "apiextensions.k8s.io", Version: "v1", Kind: "CustomResourceDefinition"},
+		Name: fmt.Sprintf("%s.%s", ar.Name, ar.Group),
+	}
+
+	k.mutex.Lock()
+	x, ok := k.crdCache[crdRef]
+	k.mutex.Unlock()
+	if ok {
+		return x.crd, x.err
+	}
+
+	crd, _, err := k.GetSingleObject(crdRef)
+
+	k.mutex.Lock()
+	x.crd = crd
+	x.err = err
+	k.crdCache[crdRef] = x
+	k.mutex.Unlock()
+
+	return crd, err
+}
+
+func (k *K8sCluster) GetSchemaForGVK(gvk schema.GroupVersionKind) (*uo.UnstructuredObject, error) {
+	crd, err := k.GetCRDForGVK(gvk)
+	if err != nil {
+		return nil, err
+	}
+
+	versions, ok, err := crd.GetNestedObjectList("spec", "versions")
+	if err != nil {
+		return nil, err
+	}
+	if !ok {
+		return nil, fmt.Errorf("versions not found in CRD")
+	}
+
+	for _, version := range versions {
+		name, _, _ := version.GetNestedString("name")
+		if name != gvk.Version {
+			continue
+		}
+		s, ok, err := version.GetNestedObject("schema", "openAPIV3Schema")
+		if err != nil {
+			return nil, err
+		}
+		if !ok {
+			return nil, fmt.Errorf("version %s has no schema", name)
+		}
+		return s, nil
+	}
+	return nil, fmt.Errorf("schema for %s not found", gvk.String())
+}
+
 func (k *K8sCluster) WithCoreV1(cb func(client *corev1.CoreV1Client) error) error {
 	p := <-k.clientPool
 	defer func() { k.clientPool <- p }()
diff --git a/pkg/validation/validation.go b/pkg/validation/validation.go
index 811856ddb..e6b36544c 100644
--- a/pkg/validation/validation.go
+++ b/pkg/validation/validation.go
@@ -2,8 +2,10 @@ package validation
 
 import (
 	"fmt"
+	"github.com/codablock/kluctl/pkg/k8s"
 	"github.com/codablock/kluctl/pkg/types"
 	"github.com/codablock/kluctl/pkg/utils/uo"
+	"k8s.io/apimachinery/pkg/api/errors"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"regexp"
 	"strconv"
@@ -33,6 +35,7 @@ func (c condition) getMessage(def string) string {
 }
 
 type errorReaction int
+
 const (
 	reactIgnore errorReaction = iota
 	reactError
@@ -40,7 +43,7 @@ const (
 	reactNotReady
 )
 
-func ValidateObject(o *uo.UnstructuredObject, notReadyIsError bool) (ret types.ValidateResult) {
+func ValidateObject(k *k8s.K8sCluster, o *uo.UnstructuredObject, notReadyIsError bool) (ret types.ValidateResult) {
 	ref := o.GetK8sRef()
 
 	// We assume all is good in case no validation is performed
@@ -69,16 +72,12 @@ func ValidateObject(o *uo.UnstructuredObject, notReadyIsError bool) (ret types.V
 		})
 	}
 
-	status, ok, _ := o.GetNestedObject("status")
-	if !ok {
-		return
-	}
-
 	addError := func(message string) {
 		ret.Errors = append(ret.Errors, types.DeploymentError{
 			Ref:   ref,
 			Error: message,
 		})
+		ret.Ready = false
 	}
 	addWarning := func(message string) {
 		ret.Warnings = append(ret.Warnings, types.DeploymentError{
@@ -108,6 +107,26 @@ func ValidateObject(o *uo.UnstructuredObject, notReadyIsError bool) (ret types.V
 		}
 	}
 
+	status, _, _ := o.GetNestedObject("status")
+	if status == nil {
+		s, err := k.GetSchemaForGVK(ref.GVK)
+		if err != nil && !errors.IsNotFound(err) {
+			addError(err.Error())
+			return
+		}
+		if s == nil {
+			return
+		} else {
+			_, ok, _ := s.GetNestedObject("properties", "status")
+			if !ok {
+				// it has no status, so all is good
+				return
+			}
+			addNotReady("no status available yet")
+			return
+		}
+	}
+
 	findConditions := func(typ string, er errorReaction, doRaise bool) []condition {
 		var ret []condition
 		l, ok, _ := status.GetNestedObjectList("conditions")

From 80e8c5720378b7052d6ea4300dc8669ef37f08fa Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Thu, 17 Mar 2022 14:44:16 +0100
Subject: [PATCH 0543/2916] feat: Implement cluster-api MD validation

---
 pkg/validation/validation.go | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/pkg/validation/validation.go b/pkg/validation/validation.go
index e6b36544c..6c807b85b 100644
--- a/pkg/validation/validation.go
+++ b/pkg/validation/validation.go
@@ -328,6 +328,25 @@ func ValidateObject(k *k8s.K8sCluster, o *uo.UnstructuredObject, notReadyIsError
 				}
 			}
 		}
+	case schema.GroupKind{Group: "cluster.x-k8s.io", Kind: "MachineDeployment"}:
+		c := getCondition("Ready", reactNotReady, true)
+		if c.status != "True" {
+			addNotReady(c.getMessage("Not ready"))
+		}
+		c = getCondition("Available", reactNotReady, true)
+		if c.status != "True" {
+			addNotReady(c.getMessage("Not ready"))
+		}
+
+		readyReplicas := getStatusFieldInt("readyReplicas", reactNotReady, true, 0)
+		replicas := getStatusFieldInt("replicas", reactNotReady, true, 0)
+		unavailableReplicas := getStatusFieldInt("unavailableReplicas", reactIgnore, false, 0)
+		if readyReplicas < replicas {
+			addNotReady(fmt.Sprintf("readyReplicas (%d) is less then replicas (%d)", readyReplicas, replicas))
+		}
+		if unavailableReplicas != 0 {
+			addNotReady(fmt.Sprintf("unavailableReplicas (%d) != 0", unavailableReplicas))
+		}
 	}
 	return
 }

From ce6b73c4520054f130e43254591187d1407906e1 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Thu, 17 Mar 2022 14:59:16 +0100
Subject: [PATCH 0544/2916] tests: Allow nil for K8sCluster in ValidateObject

---
 e2e/utils_test.go            | 2 +-
 pkg/validation/validation.go | 4 ++++
 2 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/e2e/utils_test.go b/e2e/utils_test.go
index 25d39b303..fd1954557 100644
--- a/e2e/utils_test.go
+++ b/e2e/utils_test.go
@@ -30,7 +30,7 @@ func waitForReadiness(t *testing.T, k *KindCluster, namespace string, resource s
 			t.Fatal(err)
 		}
 
-		v := validation.ValidateObject(k, y, true)
+		v := validation.ValidateObject(nil, y, true)
 		if v.Ready {
 			return true
 		}
diff --git a/pkg/validation/validation.go b/pkg/validation/validation.go
index 6c807b85b..99da49c5a 100644
--- a/pkg/validation/validation.go
+++ b/pkg/validation/validation.go
@@ -109,6 +109,10 @@ func ValidateObject(k *k8s.K8sCluster, o *uo.UnstructuredObject, notReadyIsError
 
 	status, _, _ := o.GetNestedObject("status")
 	if status == nil {
+		if k == nil {
+			// can't really say anything...
+			return
+		}
 		s, err := k.GetSchemaForGVK(ref.GVK)
 		if err != nil && !errors.IsNotFound(err) {
 			addError(err.Error())

From 1eb4986349d71b7165c031c7e84d537fa1591c16 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Thu, 17 Mar 2022 14:59:32 +0100
Subject: [PATCH 0545/2916] feat: Implement --no-wait for deploy command

---
 cmd/kluctl/commands/cmd_deploy.go   | 3 +++
 pkg/deployment/commands/deploy.go   | 2 ++
 pkg/deployment/utils/apply_utils.go | 5 +++--
 3 files changed, 8 insertions(+), 2 deletions(-)

diff --git a/cmd/kluctl/commands/cmd_deploy.go b/cmd/kluctl/commands/cmd_deploy.go
index 34ea8a07b..b05ae3ff2 100644
--- a/cmd/kluctl/commands/cmd_deploy.go
+++ b/cmd/kluctl/commands/cmd_deploy.go
@@ -20,6 +20,8 @@ type deployCmd struct {
 	args.HookFlags
 	args.OutputFlags
 	args.RenderOutputDirFlags
+
+	NoWait bool `group:"misc" help:"Don't wait for objects readiness'"`
 }
 
 func (cmd *deployCmd) Help() string {
@@ -57,6 +59,7 @@ func (cmd *deployCmd) runCmdDeploy(ctx *commandCtx) error {
 	cmd2.ForceReplaceOnError = cmd.ForceReplaceOnError
 	cmd2.AbortOnError = cmd.AbortOnError
 	cmd2.HookTimeout = cmd.HookTimeout
+	cmd2.NoWait = cmd.NoWait
 
 	result, err := cmd2.Run(ctx.k)
 	if err != nil {
diff --git a/pkg/deployment/commands/deploy.go b/pkg/deployment/commands/deploy.go
index d748f5c02..92fe108e3 100644
--- a/pkg/deployment/commands/deploy.go
+++ b/pkg/deployment/commands/deploy.go
@@ -16,6 +16,7 @@ type DeployCommand struct {
 	ForceReplaceOnError bool
 	AbortOnError        bool
 	HookTimeout         time.Duration
+	NoWait              bool
 }
 
 func NewDeployCommand(c *deployment.DeploymentCollection) *DeployCommand {
@@ -40,6 +41,7 @@ func (cmd *DeployCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, error) {
 		DryRun:              k.DryRun,
 		AbortOnError:        cmd.AbortOnError,
 		WaitObjectTimeout:   cmd.HookTimeout,
+		NoWait:              cmd.NoWait,
 	}
 	au := utils2.NewApplyUtil(dew, cmd.c.Deployments, ru, k, o)
 	au.ApplyDeployments()
diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go
index 460a473e3..1712f75a4 100644
--- a/pkg/deployment/utils/apply_utils.go
+++ b/pkg/deployment/utils/apply_utils.go
@@ -26,6 +26,7 @@ type ApplyUtilOptions struct {
 	DryRun              bool
 	AbortOnError        bool
 	WaitObjectTimeout   time.Duration
+	NoWait              bool
 }
 
 type ApplyUtil struct {
@@ -338,7 +339,7 @@ func (a *ApplyUtil) WaitReadiness(ref k8s2.ObjectRef) bool {
 			didLog = true
 			lastLogTime = time.Now()
 		} else if didLog && time.Now().Sub(lastLogTime) >= 10*time.Second {
-			log2.Infof("Still waiting for hook to get ready (%s)...", time.Now().Sub(startTime).String())
+			log2.Infof("Still waiting for object to get ready (%s)...", time.Now().Sub(startTime).String())
 			lastLogTime = time.Now()
 		}
 
@@ -408,7 +409,7 @@ func (a *ApplyUtil) applyDeploymentItem(d *deployment.DeploymentItem) {
 		}
 
 		waitReadiness := (d.Config.WaitReadiness != nil && *d.Config.WaitReadiness) || d.WaitReadiness || utils.ParseBoolOrFalse(o.GetK8sAnnotation("kluctl.io/wait-readiness"))
-		if waitReadiness {
+		if !a.o.NoWait && waitReadiness {
 			a.WaitReadiness(o.GetK8sRef())
 		}
 	}

From b4906960f5a1b46f02948ef8939cafc86173fe2f Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Thu, 17 Mar 2022 18:16:53 +0100
Subject: [PATCH 0546/2916] docs: Run replace-commands-help

---
 docs/commands.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/docs/commands.md b/docs/commands.md
index 99337c7c1..d6825c240 100644
--- a/docs/commands.md
+++ b/docs/commands.md
@@ -128,6 +128,7 @@ Misc arguments:
   -o, --output=OUTPUT,...           Specify output target file. Can be specified multiple times
       --render-output-dir=STRING    Specifies the target directory to render the project into. If omitted, a temporary
                                     directory is used.
+      --no-wait                     Don't wait for objects readiness'
 
 ```
 

From 0a9f9a0f63f18e62579d79ac95cfa598db2c0e91 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Fri, 18 Mar 2022 12:51:40 +0100
Subject: [PATCH 0547/2916] fix: Store filelist and hash in binary and check if
 extracted tmp dir was modified

This fixes issues with MacOS deleting "unused" tmp files at night.
---
 pkg/python/.gitignore               |   3 +-
 pkg/python/lib.go                   |  36 +++-------
 pkg/python/lib_darwin.go            |   4 +-
 pkg/python/lib_linux.go             |   4 +-
 pkg/python/lib_windows.go           |   4 +-
 pkg/python/tar-python.sh            |   9 ---
 pkg/utils/embed_util/extract.go     | 108 ++++++++++++++++++++++++++++
 pkg/utils/embed_util/packer/main.go |  97 +++++++++++++++++++++++++
 8 files changed, 221 insertions(+), 44 deletions(-)
 delete mode 100755 pkg/python/tar-python.sh
 create mode 100644 pkg/utils/embed_util/extract.go
 create mode 100644 pkg/utils/embed_util/packer/main.go

diff --git a/pkg/python/.gitignore b/pkg/python/.gitignore
index aeeb43173..1cdf75058 100644
--- a/pkg/python/.gitignore
+++ b/pkg/python/.gitignore
@@ -1 +1,2 @@
-python-lib-*.tar.gz
\ No newline at end of file
+python-lib-*.tar.gz
+*.files
diff --git a/pkg/python/lib.go b/pkg/python/lib.go
index 4ed630fe4..7136e6be8 100644
--- a/pkg/python/lib.go
+++ b/pkg/python/lib.go
@@ -1,16 +1,11 @@
 package python
 
 import (
-	"bytes"
-	"compress/gzip"
-	"crypto/sha256"
-	"encoding/hex"
 	"fmt"
 	"github.com/codablock/kluctl/pkg/utils"
+	"github.com/codablock/kluctl/pkg/utils/embed_util"
 	"github.com/codablock/kluctl/pkg/utils/lib_wrapper"
-	"io/ioutil"
 	"log"
-	"os"
 	"path/filepath"
 	"runtime"
 	"unsafe"
@@ -60,39 +55,24 @@ func init() {
 }
 
 func decompressLib() string {
-	tgz, err := pythonLib.ReadFile(fmt.Sprintf("python-lib-%s.tar.gz", runtime.GOOS))
+	tarName := fmt.Sprintf("python-lib-%s.tar.gz", runtime.GOOS)
+	tgz, err := pythonLib.Open(tarName)
 	if err != nil {
 		log.Panic(err)
 	}
+	defer tgz.Close()
 
-	hash := sha256.Sum256(tgz)
-	hashStr := hex.EncodeToString(hash[:])
-
-	libPath := filepath.Join(utils.GetTmpBaseDir(), fmt.Sprintf("python-libs-%s-%s", runtime.GOOS, hashStr[:16]))
-	if utils.Exists(libPath) {
-		return libPath
-	}
-
-	g, err := gzip.NewReader(bytes.NewReader(tgz))
+	fileList, err := pythonLib.Open(tarName + ".files")
 	if err != nil {
 		log.Panic(err)
 	}
-	defer g.Close()
+	defer fileList.Close()
 
-	tmpLibPath, err := ioutil.TempDir(utils.GetTmpBaseDir(), "python-libs-tmp-")
+	libPath := filepath.Join(utils.GetTmpBaseDir(), fmt.Sprintf("python-libs-%s", runtime.GOOS))
+	err = embed_util.ExtractTarToTmp(tgz, fileList, libPath)
 	if err != nil {
 		log.Panic(err)
 	}
-	defer os.RemoveAll(tmpLibPath)
 
-	err = utils.ExtractTarGzStream(g, tmpLibPath)
-	if err != nil {
-		log.Panic(err)
-	}
-
-	err = os.Rename(tmpLibPath, libPath)
-	if err != nil && !os.IsExist(err) {
-		log.Panic(err)
-	}
 	return libPath
 }
diff --git a/pkg/python/lib_darwin.go b/pkg/python/lib_darwin.go
index 5181aebf9..5a9c1253b 100644
--- a/pkg/python/lib_darwin.go
+++ b/pkg/python/lib_darwin.go
@@ -2,7 +2,7 @@ package python
 
 import "embed"
 
-//go:generate bash ./tar-python.sh python-lib-darwin.tar.gz darwin/cpython-install lib
+//go:generate go run ../utils/embed_util/packer python-lib-darwin.tar.gz ../../build-python/darwin/cpython-install lib
 
-//go:embed python-lib-darwin.tar.gz
+//go:embed python-lib-darwin.tar.gz python-lib-darwin.tar.gz.files
 var pythonLib embed.FS
diff --git a/pkg/python/lib_linux.go b/pkg/python/lib_linux.go
index e6f94ba58..1106f1462 100644
--- a/pkg/python/lib_linux.go
+++ b/pkg/python/lib_linux.go
@@ -2,7 +2,7 @@ package python
 
 import "embed"
 
-//go:generate bash ./tar-python.sh python-lib-linux.tar.gz linux/cpython-install lib
+//go:generate go run ../utils/embed_util/packer python-lib-linux.tar.gz ../../build-python/linux/cpython-install lib
 
-//go:embed python-lib-linux.tar.gz
+//go:embed python-lib-linux.tar.gz python-lib-linux.tar.gz.files
 var pythonLib embed.FS
diff --git a/pkg/python/lib_windows.go b/pkg/python/lib_windows.go
index b7b53e6f6..129aece2a 100644
--- a/pkg/python/lib_windows.go
+++ b/pkg/python/lib_windows.go
@@ -2,7 +2,7 @@ package python
 
 import "embed"
 
-//go:generate bash ./tar-python.sh python-lib-windows.tar.gz windows '*'
+//go:generate go run ../utils/embed_util/packer python-lib-windows.tar.gz ../../build-python/windows *
 
-//go:embed python-lib-windows.tar.gz
+//go:embed python-lib-windows.tar.gz python-lib-windows.tar.gz.files
 var pythonLib embed.FS
diff --git a/pkg/python/tar-python.sh b/pkg/python/tar-python.sh
deleted file mode 100755
index c76bb357f..000000000
--- a/pkg/python/tar-python.sh
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/usr/bin/env bash
-
-set -e
-
-DIR=$(cd $(dirname $0) && pwd)
-
-cd $DIR/../../build-python/$2
-
-tar --exclude '*/__pycache__' --exclude '*.a' -czf $DIR/$1 $3
diff --git a/pkg/utils/embed_util/extract.go b/pkg/utils/embed_util/extract.go
new file mode 100644
index 000000000..a5872f64d
--- /dev/null
+++ b/pkg/utils/embed_util/extract.go
@@ -0,0 +1,108 @@
+package embed_util
+
+import (
+	"github.com/codablock/kluctl/pkg/utils"
+	"io"
+	"io/fs"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strconv"
+	"strings"
+)
+
+func ExtractTarToTmp(r io.Reader, fileListR io.Reader, targetPath string) error {
+	fileList, err := ioutil.ReadAll(fileListR)
+	if err != nil {
+		return err
+	}
+
+	needsExtract, expectedTarGzHash, err := checkExtractNeeded(targetPath, string(fileList))
+	if err != nil {
+		return err
+	}
+	if !needsExtract {
+		return nil
+	}
+
+	tmpTargetPath := targetPath + ".tmp"
+	err = os.MkdirAll(tmpTargetPath , 0o700)
+	if err != nil {
+		return err
+	}
+
+	err = utils.ExtractTarGzStream(r, tmpTargetPath)
+	if err != nil {
+		return err
+	}
+
+	err = ioutil.WriteFile(filepath.Join(tmpTargetPath, ".tar-gz-hash"), []byte(expectedTarGzHash), 0o600)
+	if err != nil {
+		return err
+	}
+
+	_ = os.RemoveAll(targetPath)
+	err = os.Rename(tmpTargetPath, targetPath)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func checkExtractNeeded(targetPath string, fileListStr string) (bool, string, error) {
+	fileList := strings.Split(fileListStr, "\n")
+	expectedHash := fileList[0]
+	fileList = fileList[1:]
+
+	if !utils.Exists(targetPath) {
+		return true, expectedHash, nil
+	}
+
+	existingHash, err := ioutil.ReadFile(filepath.Join(targetPath, ".tar-gz-hash"))
+	if err != nil {
+		return true, expectedHash, nil
+	}
+
+	if strings.TrimSpace(expectedHash) != strings.TrimSpace(string(existingHash)) {
+		return true, expectedHash, nil
+	}
+
+	tarFilesMap := make(map[string]int64)
+	for _, l := range fileList {
+		s := strings.Split(l, ":")
+		fname := strings.TrimSpace(s[0])
+		size, err := strconv.ParseInt(strings.TrimSpace(s[1]), 10, 64)
+		if err != nil {
+			return false, expectedHash, err
+		}
+		tarFilesMap[fname] = size
+	}
+
+	existingFiles := make(map[string]int64)
+	err = filepath.Walk(targetPath, func(path string, info fs.FileInfo, err error) error {
+		if !info.Mode().IsRegular() && !info.IsDir() {
+			return nil
+		}
+		relPath, err := filepath.Rel(targetPath, path)
+		if err != nil {
+			return err
+		}
+		if info.IsDir() {
+			existingFiles[relPath] = 0
+		} else {
+			existingFiles[relPath] = info.Size()
+		}
+		return nil
+	})
+	if err != nil {
+		return false, "", err
+	}
+
+	for fname, size := range tarFilesMap {
+		if s, ok := existingFiles[fname]; !ok || s != size {
+			return true, expectedHash, nil
+		}
+	}
+	return false, expectedHash, nil
+}
diff --git a/pkg/utils/embed_util/packer/main.go b/pkg/utils/embed_util/packer/main.go
new file mode 100644
index 000000000..26539679a
--- /dev/null
+++ b/pkg/utils/embed_util/packer/main.go
@@ -0,0 +1,97 @@
+package main
+
+import (
+	"archive/tar"
+	"bytes"
+	"compress/gzip"
+	"crypto/sha256"
+	"encoding/hex"
+	"fmt"
+	"github.com/codablock/kluctl/pkg/utils"
+	"github.com/gobwas/glob"
+	log "github.com/sirupsen/logrus"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"sort"
+	"strings"
+)
+
+func main()  {
+	out := os.Args[1]
+	dir := os.Args[2]
+	pattern := os.Args[3]
+
+	fileList, tgz := writeTar(dir, pattern)
+
+	hash := sha256.Sum256(tgz)
+	err := ioutil.WriteFile(out, tgz, 0o600)
+	if err != nil {
+		log.Panic(err)
+	}
+
+	fileListStr := strings.Join(fileList, "\n")
+	fileListStr = hex.EncodeToString(hash[:]) + "\n" + fileListStr
+	err = ioutil.WriteFile(out + ".files", []byte(fileListStr), 0o600)
+	if err != nil {
+		log.Panic(err)
+	}
+}
+
+func writeTar(dir string, pattern string) ([]string, []byte) {
+	b := bytes.NewBuffer(nil)
+	gz := gzip.NewWriter(b)
+	t := tar.NewWriter(gz)
+
+	var rootNames []string
+	dirs, err := os.ReadDir(dir)
+	if err != nil {
+		log.Panic(err)
+	}
+	p := glob.MustCompile(pattern)
+	for _, d := range dirs {
+		if p.Match(d.Name()) {
+			rootNames = append(rootNames, d.Name())
+		}
+	}
+	sort.Strings(rootNames)
+
+	excludes := []glob.Glob{
+		glob.MustCompile("__pycache__"),
+		glob.MustCompile("**/__pycache__"),
+		glob.MustCompile("**.a"),
+	}
+
+	var fileList []string
+
+	for _, d := range rootNames {
+		err := utils.AddToTar(t, filepath.Join(dir, d), d, func(h *tar.Header) (*tar.Header, error) {
+			for _, e := range excludes {
+				if e.Match(h.Name) {
+					return nil, nil
+				}
+			}
+
+			s := h.Size
+			if h.FileInfo().IsDir() {
+				s = 0
+			}
+			fileList = append(fileList, fmt.Sprintf("%s: %d", h.Name, s))
+			return h, nil
+		})
+		if err != nil {
+			log.Panic(err)
+		}
+	}
+
+	err = t.Close()
+	if err != nil {
+		log.Panic(err)
+	}
+	err = gz.Close()
+	if err != nil {
+		log.Panic(err)
+	}
+
+	return fileList, b.Bytes()
+}

From 62cbe9b06c0e1637cd6b3a3904c478a048c0d4fe Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Fri, 18 Mar 2022 12:52:31 +0100
Subject: [PATCH 0548/2916] fix: Move gzip reader into ExtractTarGzStream

---
 pkg/utils/tar.go | 16 ++++++++++------
 1 file changed, 10 insertions(+), 6 deletions(-)

diff --git a/pkg/utils/tar.go b/pkg/utils/tar.go
index 67e17719d..4b92b00c2 100644
--- a/pkg/utils/tar.go
+++ b/pkg/utils/tar.go
@@ -18,17 +18,21 @@ func ExtractTarGzFile(tarGzPath string, targetPath string) error {
 	}
 	defer f.Close()
 
-	gz, err := gzip.NewReader(f)
+	err = ExtractTarGzStream(f, targetPath)
 	if err != nil {
-		return fmt.Errorf("archive %v could not be opened: %w", tarGzPath, err)
+		return fmt.Errorf("archive %v could not be extracted: %w", tarGzPath, err)
 	}
-	defer gz.Close()
-
-	return ExtractTarGzStream(gz, targetPath)
+	return nil
 }
 
 func ExtractTarGzStream(r io.Reader, targetPath string) error {
-	tarReader := tar.NewReader(r)
+	gz, err := gzip.NewReader(r)
+	if err != nil {
+		return err
+	}
+	defer gz.Close()
+
+	tarReader := tar.NewReader(gz)
 	for true {
 		header, err := tarReader.Next()
 		if err == io.EOF {

From cecde21eaeef62977c1d11ee169e4749577d6754 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Fri, 18 Mar 2022 13:08:23 +0100
Subject: [PATCH 0549/2916] fix: Use embed_util for jinja2-src

---
 pkg/jinja2/.gitignore              |  2 +
 pkg/jinja2/jinja2.go               |  1 +
 pkg/jinja2/jinja2_renderer.go      |  4 ++
 pkg/jinja2/python_src/pip-wheel.sh |  5 ++
 pkg/jinja2/source.go               | 95 ++++--------------------------
 5 files changed, 22 insertions(+), 85 deletions(-)
 create mode 100644 pkg/jinja2/.gitignore

diff --git a/pkg/jinja2/.gitignore b/pkg/jinja2/.gitignore
new file mode 100644
index 000000000..c23b5c5b7
--- /dev/null
+++ b/pkg/jinja2/.gitignore
@@ -0,0 +1,2 @@
+*.tar.gz
+*.files
\ No newline at end of file
diff --git a/pkg/jinja2/jinja2.go b/pkg/jinja2/jinja2.go
index 9a94e58a7..a3d59cb52 100644
--- a/pkg/jinja2/jinja2.go
+++ b/pkg/jinja2/jinja2.go
@@ -15,6 +15,7 @@ import (
 )
 
 //go:generate bash ./python_src/pip-wheel.sh
+//go:generate go run ../utils/embed_util/packer python_src.tar.gz python_src *
 
 type Jinja2 struct {
 	pj        *pythonJinja2Renderer
diff --git a/pkg/jinja2/jinja2_renderer.go b/pkg/jinja2/jinja2_renderer.go
index 2f04eb051..5d45de3e0 100644
--- a/pkg/jinja2/jinja2_renderer.go
+++ b/pkg/jinja2/jinja2_renderer.go
@@ -30,6 +30,10 @@ func addSrcToPath() {
 		if err != nil {
 			log.Fatal(err)
 		}
+		err = p.AppendSysPath(filepath.Join(pythonSrcExtracted, "wheel"))
+		if err != nil {
+			log.Fatal(err)
+		}
 	})
 }
 
diff --git a/pkg/jinja2/python_src/pip-wheel.sh b/pkg/jinja2/python_src/pip-wheel.sh
index 55d19ae74..23ab5e408 100755
--- a/pkg/jinja2/python_src/pip-wheel.sh
+++ b/pkg/jinja2/python_src/pip-wheel.sh
@@ -7,3 +7,8 @@ cd $DIR/wheel
 
 rm *.whl
 pip3 wheel -r ../requirements.txt
+
+for f in *.whl; do
+  unzip $f
+  rm $f
+done
diff --git a/pkg/jinja2/source.go b/pkg/jinja2/source.go
index 8b1f8b195..0d2d18d2b 100644
--- a/pkg/jinja2/source.go
+++ b/pkg/jinja2/source.go
@@ -1,21 +1,14 @@
 package jinja2
 
 import (
-	"archive/zip"
-	"crypto/sha256"
 	"embed"
-	"encoding/hex"
-	"fmt"
 	"github.com/codablock/kluctl/pkg/utils"
+	"github.com/codablock/kluctl/pkg/utils/embed_util"
 	log "github.com/sirupsen/logrus"
-	"io"
-	"io/fs"
-	"os"
 	"path/filepath"
-	"strings"
 )
 
-//go:embed python_src
+//go:embed python_src.tar.gz python_src.tar.gz.files
 var pythonSrc embed.FS
 var pythonSrcExtracted string
 
@@ -28,91 +21,23 @@ func init() {
 }
 
 func extractSource() (string, error) {
-	h := sha256.New()
-	err := fs.WalkDir(pythonSrc, ".", func(path string, d fs.DirEntry, err error) error {
-		h.Write([]byte(path))
-		if !d.IsDir() {
-			b, err := pythonSrc.ReadFile(path)
-			if err != nil {
-				log.Panic(err)
-			}
-			h.Write(b)
-		}
-		return nil
-	})
-	if err != nil {
-		log.Panic(err)
-	}
-	hash := hex.EncodeToString(h.Sum(nil))
-
-	srcDir := filepath.Join(utils.GetTmpBaseDir(), fmt.Sprintf("jinja2-src-%s", hash[:16]))
-	if utils.Exists(srcDir) {
-		return srcDir, nil
-	}
-	srcDirTmp := srcDir + ".tmp"
-	defer os.RemoveAll(srcDirTmp)
-
-	err = utils.FsCopyDir(pythonSrc, "python_src", srcDirTmp)
+	tgz, err := pythonSrc.Open("python_src.tar.gz")
 	if err != nil {
 		return "", err
 	}
+	defer tgz.Close()
 
-	err = filepath.WalkDir(filepath.Join(srcDirTmp, "wheel"), func(p string, d fs.DirEntry, err error) error {
-		if !strings.HasSuffix(p, ".whl") {
-			return nil
-		}
-		return unzipFile(p, srcDirTmp)
-	})
+	fileList, err := pythonSrc.Open("python_src.tar.gz.files")
 	if err != nil {
 		return "", err
 	}
+	defer fileList.Close()
 
-	err = os.Rename(srcDirTmp, srcDir)
-	if err != nil && !os.IsExist(err) {
-		return "", err
-	}
-
-	return srcDir, nil
-}
-
-func unzipFile(src string, target string) error {
-	r, err := zip.OpenReader(src)
+	libPath := filepath.Join(utils.GetTmpBaseDir(), "jinja2-src")
+	err = embed_util.ExtractTarToTmp(tgz, fileList, libPath)
 	if err != nil {
-		return err
+		return "", err
 	}
-	defer r.Close()
 
-	for _, f := range r.File {
-		filePath := filepath.Join(target, f.Name)
-		if f.FileInfo().IsDir() {
-			err = os.MkdirAll(filePath, 0o700)
-			if err != nil {
-				return err
-			}
-			continue
-		}
-		if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil {
-			return err
-		}
-
-		err = func() error {
-			dstFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
-			if err != nil {
-				return err
-			}
-			defer dstFile.Close()
-
-			fileInArchive, err := f.Open()
-			if err != nil {
-				return err
-			}
-			defer fileInArchive.Close()
-
-			if _, err := io.Copy(dstFile, fileInArchive); err != nil {
-				return err
-			}
-			return nil
-		}()
-	}
-	return nil
+	return libPath, nil
 }

From 21f16ea7284061e908092d4049e1ace9fda65112 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Fri, 18 Mar 2022 17:44:21 +0100
Subject: [PATCH 0550/2916] fix: Use file lock when extracting embedded files

---
 pkg/utils/embed_util/extract.go | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/pkg/utils/embed_util/extract.go b/pkg/utils/embed_util/extract.go
index a5872f64d..388e8b29a 100644
--- a/pkg/utils/embed_util/extract.go
+++ b/pkg/utils/embed_util/extract.go
@@ -2,6 +2,7 @@ package embed_util
 
 import (
 	"github.com/codablock/kluctl/pkg/utils"
+	"github.com/gofrs/flock"
 	"io"
 	"io/fs"
 	"io/ioutil"
@@ -17,6 +18,13 @@ func ExtractTarToTmp(r io.Reader, fileListR io.Reader, targetPath string) error
 		return err
 	}
 
+	fl := flock.New(targetPath + ".lock")
+	err = fl.Lock()
+	if err != nil {
+		return err
+	}
+	defer fl.Unlock()
+
 	needsExtract, expectedTarGzHash, err := checkExtractNeeded(targetPath, string(fileList))
 	if err != nil {
 		return err

From c2089cec8f61d7029dfa7390d4c4ac90630ff72f Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Sat, 19 Mar 2022 01:10:50 +0100
Subject: [PATCH 0551/2916] build: Remove all cgo related python embedding and
 switch to calling python binaries

---
 .github/workflows/build-and-release.yml      |  61 +---
 .gitignore                                   |   2 +-
 hack/build-python-unix.sh                    |  39 ---
 hack/build-python-windows.sh                 |  17 -
 hack/download-python.sh                      |  43 +++
 pkg/jinja2/jinja2.go                         |  50 ++-
 pkg/jinja2/jinja2_renderer.go                | 200 +++++------
 pkg/jinja2/python_src/main.py                |  29 ++
 pkg/jinja2/python_src/pip-wheel.sh           |   2 +-
 pkg/python/.gitignore                        |   2 +-
 pkg/python/cmd.go                            |  24 ++
 pkg/python/dict.go                           |  25 --
 pkg/python/embed.go                          |  39 +++
 pkg/python/embed_darwin.go                   |   8 +
 pkg/python/embed_linux.go                    |   8 +
 pkg/python/embed_windows.go                  |   8 +
 pkg/python/interpreter.go                    |  76 ----
 pkg/python/lib.go                            |  78 -----
 pkg/python/lib_darwin.go                     |   8 -
 pkg/python/lib_linux.go                      |   8 -
 pkg/python/lib_windows.go                    |   8 -
 pkg/python/libpython_wrapper.go              |  50 ---
 pkg/python/libpython_wrapper_impl.go         | 350 -------------------
 pkg/python/list.go                           |  28 --
 pkg/python/object.go                         |  53 ---
 pkg/utils/embed_util/extract.go              |   2 +-
 pkg/utils/embed_util/packer/main.go          |  42 ++-
 pkg/utils/lib_wrapper/gen/lib_wrapper_gen.go | 347 ------------------
 pkg/utils/lib_wrapper/lib_wrapper.go         |  27 --
 pkg/utils/lib_wrapper/lib_wrapper_unix.go    |  39 ---
 pkg/utils/lib_wrapper/lib_wrapper_windows.go |  30 --
 pkg/utils/tar.go                             |  13 +-
 32 files changed, 351 insertions(+), 1365 deletions(-)
 delete mode 100755 hack/build-python-unix.sh
 delete mode 100755 hack/build-python-windows.sh
 create mode 100755 hack/download-python.sh
 create mode 100644 pkg/jinja2/python_src/main.py
 create mode 100644 pkg/python/cmd.go
 delete mode 100644 pkg/python/dict.go
 create mode 100644 pkg/python/embed.go
 create mode 100644 pkg/python/embed_darwin.go
 create mode 100644 pkg/python/embed_linux.go
 create mode 100644 pkg/python/embed_windows.go
 delete mode 100644 pkg/python/interpreter.go
 delete mode 100644 pkg/python/lib.go
 delete mode 100644 pkg/python/lib_darwin.go
 delete mode 100644 pkg/python/lib_linux.go
 delete mode 100644 pkg/python/lib_windows.go
 delete mode 100644 pkg/python/libpython_wrapper.go
 delete mode 100644 pkg/python/libpython_wrapper_impl.go
 delete mode 100644 pkg/python/list.go
 delete mode 100644 pkg/python/object.go
 delete mode 100644 pkg/utils/lib_wrapper/gen/lib_wrapper_gen.go
 delete mode 100644 pkg/utils/lib_wrapper/lib_wrapper.go
 delete mode 100644 pkg/utils/lib_wrapper/lib_wrapper_unix.go
 delete mode 100644 pkg/utils/lib_wrapper/lib_wrapper_windows.go

diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml
index 09a12a061..947f1f093 100644
--- a/.github/workflows/build-and-release.yml
+++ b/.github/workflows/build-and-release.yml
@@ -74,8 +74,6 @@ jobs:
             binary-suffix: linux-amd64
           - os: macos-10.15
             binary-suffix: darwin-amd64
-          - os: windows-2019
-            binary-suffix: windows-amd64
         os: [ubuntu-18.04, macos-10.15, windows-2019]
     runs-on: ${{ matrix.os }}
     needs:
@@ -96,64 +94,43 @@ jobs:
           key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
           restore-keys: |
             ${{ runner.os }}-go-
-      - name: Install dependencies on Linux
-        if: runner.os == 'Linux'
-        run: |
-          echo "deb-src http://archive.ubuntu.com/ubuntu/ bionic main restricted" | sudo tee /etc/apt/sources.list.d/deb-src.list
-          sudo apt update
-          sudo apt-get build-dep -y python3
-      - name: Install dependencies on Mac
-        if: runner.os == 'macOS'
-        run: |
-          brew install openssl
-          brew install readline
-      - name: Cache Python build
-        id: python-cache
-        uses: actions/cache@v2
-        with:
-          path: build-python/*/cpython-install
-          key: build-python-${{ runner.os }}-${{ hashFiles('hack/build-python-*') }}
-      - name: Build python (non-Windows)
-        if: runner.os != 'Windows' && steps.python-cache.outputs.cache-hit != 'true'
-        shell: bash
+      - name: Download python
         run: |
-          ./hack/build-python-unix.sh
-      - name: Build python (Windows)
-        if: runner.os == 'Windows'
-        shell: bash
-        run: |
-          ./hack/build-python-windows.sh
+          ./hack/download-python.sh linux
+          ./hack/download-python.sh darwin
+          ./hack/download-python.sh windows
       - name: Write Version to version.go
         run: |
           sed -ibak 's/0.0.0/${{ needs.version.outputs.version }}/g' pkg/version/version.go
           cat pkg/version/version.go
       - name: Go Mod Vendor
-        shell: bash
         run: |
           go mod vendor
       - name: Go Generate
-        shell: bash
         run: |
           go generate ./...
-      - name: Build kluctl
-        shell: bash
+      - name: Build kluctl (linux)
         run: |
-          go build -ldflags "-s -w" ./cmd/kluctl
-          EXE=""
-          if [ "${{ runner.os }}" = "Windows" ]; then
-            EXE=".exe"
-          fi
-          mv kluctl$EXE kluctl-${{ matrix.binary-suffix }}$EXE
+          export CGO_ENABLED=0
+          export GOARCH=amd64
+          GOOS=linux go build -ldflags "-s -w" ./cmd/kluctl
+          mv kluctl kluctl-linux-amd64
+          GOOS=darwin go build -ldflags "-s -w" ./cmd/kluctl
+          mv kluctl kluctl-darwin-amd64
+          GOOS=windows go build -ldflags "-s -w" ./cmd/kluctl
+          mv kluctl.exe kluctl-windows-amd64.exe
       - name: Upload dist artifact
         uses: actions/upload-artifact@v2
         with:
-          name: dist-${{ matrix.binary-suffix }}
+          name: dist
           path: |
-            kluctl-${{ matrix.binary-suffix }}*
+            kluctl-linux-amd64
+            kluctl-darwin-amd64
+            kluctl-windows-amd64.exe
       - name: Upload pkg artifact
         uses: actions/upload-artifact@v2
         with:
-          name: pkg-${{ matrix.binary-suffix }}
+          name: pkg
           path: |
             pkg
 
@@ -258,7 +235,7 @@ jobs:
       - name: Download pkg artifacts
         uses: actions/download-artifact@v2
         with:
-          name: pkg-${{ matrix.binary-suffix }}
+          name: pkg
           path: pkg
       - name: Port-forward docker
         shell: bash
diff --git a/.gitignore b/.gitignore
index a0d685139..bafcc0f04 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,6 +8,6 @@
 .sealed-secrets
 
 /vendor
-/build-python
+/download-python
 /kluctl
 /kluctl.exe
diff --git a/hack/build-python-unix.sh b/hack/build-python-unix.sh
deleted file mode 100755
index 79957c710..000000000
--- a/hack/build-python-unix.sh
+++ /dev/null
@@ -1,39 +0,0 @@
-#!/usr/bin/env bash
-
-set -e
-
-DIR=$(cd $(dirname $0) && pwd)
-cd $DIR/..
-
-case "$(uname -s)" in
-    Linux*)     os=linux;;
-    Darwin*)    os=darwin;;
-    MINGW*)     os=windows;;
-    *)          echo "unknown os"; exit 1;
-esac
-
-mkdir -p build-python/$os
-cd build-python/$os
-
-PYTHON_VERSION=3.10.2
-
-if [ ! -d cpython ]; then
-  git clone -bv$PYTHON_VERSION --single-branch --depth 1 https://github.com/python/cpython.git cpython
-fi
-
-cd cpython
-
-if [ "$os" = "darwin" ]; then
-  export CPPFLAGS="-I$(brew --prefix readline)/include"
-  export LDFLAGS="-L$(brew --prefix readline)/lib"
-fi
-./configure $CONFIGURE_FLAGS --enable-shared --disable-test-modules --without-static-libpython --with-ensurepip=no --prefix $DIR/../build-python/$os/cpython-install
-make -j4
-make install
-
-cd ..
-cd cpython-install
-
-for i in ensurepip idlelib distutils pydoc_data asyncio email tkinter lib2to3 xml multiprocessing unittest; do
-  rm -rf lib/python3.10/$i
-done
diff --git a/hack/build-python-windows.sh b/hack/build-python-windows.sh
deleted file mode 100755
index 9e2afd3b2..000000000
--- a/hack/build-python-windows.sh
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/usr/bin/env bash
-
-set -e
-
-DIR=$(cd $(dirname $0) && pwd)
-cd $DIR/..
-
-mkdir -p build-python/windows
-cd build-python/windows
-
-PYTHON_VERSION=3.10.2
-
-curl -L -o python.zip https://www.python.org/ftp/python/$PYTHON_VERSION/python-$PYTHON_VERSION-embed-amd64.zip
-unzip -o python.zip
-rm python.zip
-
-tar czf $DIR/../pkg/python/python-lib-windows.tar.gz *
diff --git a/hack/download-python.sh b/hack/download-python.sh
new file mode 100755
index 000000000..d78a9040a
--- /dev/null
+++ b/hack/download-python.sh
@@ -0,0 +1,43 @@
+#!/usr/bin/env bash
+
+set -e
+
+DIR=$(cd $(dirname $0) && pwd)
+cd $DIR/..
+
+if [ "$1" != "" ]; then
+  os=$1
+else
+  case "$(uname -s)" in
+      Linux*)     os=linux;;
+      Darwin*)    os=darwin;;
+      MINGW*)     os=windows;;
+      *)          echo "unknown os"; exit 1;
+  esac
+fi
+
+arch=x86_64
+
+case "$os" in
+    linux*)     python_dist=unknown-linux-gnu-pgo-full;;
+    darwin*)    python_dist=apple-darwin-pgo-full;;
+    windows*)   python_dist=pc-windows-msvc-shared-pgo-full;;
+esac
+
+mkdir -p download-python/$os
+cd download-python/$os
+
+PYTHON_STANDALONE_VERSION=20220227
+PYTHON_VERSION=3.10.2
+
+if [ ! -d python ]; then
+  curl -L -o python.tar.zst https://github.com/indygreg/python-build-standalone/releases/download/$PYTHON_STANDALONE_VERSION/cpython-$PYTHON_VERSION+$PYTHON_STANDALONE_VERSION-$arch-$python_dist.tar.zst
+  tar -xf python.tar.zst
+fi
+
+cd python/install
+
+for i in test site-packages venv ensurepip idlelib distutils pydoc_data asyncio email tkinter lib2to3 xml multiprocessing unittest; do
+  rm -rf lib/python3.*/$i
+  rm -rf Lib/$i
+done
diff --git a/pkg/jinja2/jinja2.go b/pkg/jinja2/jinja2.go
index a3d59cb52..46a479965 100644
--- a/pkg/jinja2/jinja2.go
+++ b/pkg/jinja2/jinja2.go
@@ -1,6 +1,5 @@
 package jinja2
 
-import "C"
 import (
 	"fmt"
 	"github.com/codablock/kluctl/pkg/utils"
@@ -17,8 +16,10 @@ import (
 //go:generate bash ./python_src/pip-wheel.sh
 //go:generate go run ../utils/embed_util/packer python_src.tar.gz python_src *
 
+var paralellism = 4
+
 type Jinja2 struct {
-	pj        *pythonJinja2Renderer
+	pj        chan *pythonJinja2Renderer
 	globCache map[string]interface{}
 	mutex     sync.Mutex
 }
@@ -40,33 +41,54 @@ func (m *Jinja2Error) Error() string {
 }
 
 func NewJinja2() (*Jinja2, error) {
-	pj, err := newPythonJinja2Renderer()
-	if err != nil {
-		return nil, err
-	}
+	var wg sync.WaitGroup
+	var mutex sync.Mutex
+	var err error
 
 	j := &Jinja2{
-		pj:        pj,
+		pj:        make(chan *pythonJinja2Renderer, paralellism),
 		globCache: map[string]interface{}{},
 	}
 
+	for i := 0; i < paralellism; i++ {
+		wg.Add(1)
+		go func() {
+			defer wg.Done()
+			pj, err2 := newPythonJinja2Renderer()
+			if err2 != nil {
+				mutex.Lock()
+				defer mutex.Unlock()
+				err = err2
+				return
+			}
+			j.pj <- pj
+		}()
+	}
+	wg.Wait()
+	if err != nil {
+		return nil, err
+	}
+
 	return j, nil
 }
 
 func (j *Jinja2) Close() {
-	j.pj.Close()
+	for i := 0; i < paralellism; i++ {
+		pj := <-j.pj
+		pj.Close()
+	}
 }
 
 func (j *Jinja2) RenderStrings(jobs []*RenderJob, searchDirs []string, vars *uo.UnstructuredObject) error {
-	j.mutex.Lock()
-	defer j.mutex.Unlock()
-	return j.pj.renderHelper(jobs, searchDirs, vars, true)
+	pj := <-j.pj
+	defer func() {j.pj <- pj}()
+	return pj.renderHelper(jobs, searchDirs, vars, true)
 }
 
 func (j *Jinja2) RenderFiles(jobs []*RenderJob, searchDirs []string, vars *uo.UnstructuredObject) error {
-	j.mutex.Lock()
-	defer j.mutex.Unlock()
-	return j.pj.renderHelper(jobs, searchDirs, vars, false)
+	pj := <-j.pj
+	defer func() {j.pj <- pj}()
+	return pj.renderHelper(jobs, searchDirs, vars, false)
 }
 
 func (j *Jinja2) RenderString(template string, searchDirs []string, vars *uo.UnstructuredObject) (string, error) {
diff --git a/pkg/jinja2/jinja2_renderer.go b/pkg/jinja2/jinja2_renderer.go
index 5d45de3e0..911308458 100644
--- a/pkg/jinja2/jinja2_renderer.go
+++ b/pkg/jinja2/jinja2_renderer.go
@@ -1,102 +1,84 @@
 package jinja2
 
 import (
+	"bufio"
 	"bytes"
 	"encoding/json"
 	"fmt"
 	"github.com/codablock/kluctl/pkg/python"
 	"github.com/codablock/kluctl/pkg/utils/uo"
+	"io"
 	"io/ioutil"
-	"log"
+	"os"
+	"os/exec"
 	"path/filepath"
 	"strings"
-	"sync"
 )
 
 type pythonJinja2Renderer struct {
-	p        *python.PythonInterpreter
-	renderer *python.PyObject
-}
-
-var addSrcToPathOnce sync.Once
+	cmd    *exec.Cmd
+	stdin  io.WriteCloser
+	stdout io.ReadCloser
 
-func addSrcToPath() {
-	addSrcToPathOnce.Do(func() {
-		p, err := python.MainPythonInterpreter()
-		if err != nil {
-			log.Fatal(err)
-		}
-		err = p.AppendSysPath(pythonSrcExtracted)
-		if err != nil {
-			log.Fatal(err)
-		}
-		err = p.AppendSysPath(filepath.Join(pythonSrcExtracted, "wheel"))
-		if err != nil {
-			log.Fatal(err)
-		}
-	})
+	stdoutReader *bufio.Reader
 }
 
 func newPythonJinja2Renderer() (*pythonJinja2Renderer, error) {
-	addSrcToPath()
-
 	isOk := false
-	p, err := python.MainPythonInterpreter()
-	if err != nil {
-		return nil, err
-	}
+	j := &pythonJinja2Renderer{}
 	defer func() {
 		if !isOk {
-			p.Stop()
+			j.Close()
 		}
 	}()
 
-	j := &pythonJinja2Renderer{
-		p: p,
-	}
-
-	err = p.Run(func() error {
-		mod := python.PythonWrapper.PyImport_ImportModule("jinja2_renderer")
-		if mod == nil {
-			python.PythonWrapper.PyErr_Print()
-			return fmt.Errorf("unexpected error")
-		}
-		defer mod.DecRef()
+	args := []string{filepath.Join(pythonSrcExtracted, "main.py")}
+	j.cmd = python.PythonCmd(args)
+	j.cmd.Stderr = os.Stderr
+	j.cmd.Env = append(j.cmd.Env, fmt.Sprintf("PYTHONPATH=%s/wheel", pythonSrcExtracted))
 
-		clazz := mod.GetAttrString("Jinja2Renderer")
-		if clazz == nil {
-			python.PythonWrapper.PyErr_Print()
-			return fmt.Errorf("unexpected error")
-		}
-		defer clazz.DecRef()
+	stdout, err := j.cmd.StdoutPipe()
+	if err != nil {
+		return nil, err
+	}
+	j.stdout = stdout
 
-		renderer := clazz.CallObject(nil)
-		if renderer == nil {
-			python.PythonWrapper.PyErr_Print()
-			return fmt.Errorf("unexpected error")
-		}
+	stdin, err := j.cmd.StdinPipe()
+	if err != nil {
+		return nil, err
+	}
+	j.stdin = stdin
 
-		j.renderer = renderer
-		return nil
-	})
+	err = j.cmd.Start()
 	if err != nil {
 		return nil, err
 	}
 
+	j.stdoutReader = bufio.NewReader(j.stdout)
+
 	isOk = true
+
 	return j, nil
 }
 
 func (j *pythonJinja2Renderer) Close() {
-	_ = j.p.Run(func() error {
-		if j.renderer != nil {
-			j.renderer.DecRef()
-			j.renderer = nil
-		}
-		return nil
-	})
+	if j.stdin != nil {
+		args := jinja2Args{Cmd: "exit"}
+		_ = json.NewEncoder(j.stdin).Encode(args)
 
-	j.p.Stop()
+		_ = j.stdin.Close()
+		j.stdin = nil
+	}
+	if j.stdout != nil {
+		_ = j.stdout.Close()
+		j.stdout = nil
+	}
+	if j.cmd != nil {
+		if j.cmd.Process != nil {
+			_ = j.cmd.Process.Kill()
+		}
+		j.cmd = nil
+	}
 }
 
 func (j *pythonJinja2Renderer) isMaybeTemplate(template string, searchDirs []string, isString bool) (bool, *string) {
@@ -121,13 +103,19 @@ func (j *pythonJinja2Renderer) isMaybeTemplate(template string, searchDirs []str
 	return true, nil
 }
 
-func (j *pythonJinja2Renderer) renderHelper(jobs []*RenderJob, searchDirs []string, vars *uo.UnstructuredObject, isString bool) error {
-	return j.p.Run(func() error {
-		return j.renderHelper2(jobs, searchDirs, vars, isString)
-	})
+type jinja2Args struct {
+	Cmd        string   `json:"cmd"`
+	Templates  []string `json:"templates"`
+	SearchDirs []string `json:"searchDirs"`
+	Vars       string   `json:"vars"`
 }
 
-func (j *pythonJinja2Renderer) renderHelper2(jobs []*RenderJob, searchDirs []string, vars *uo.UnstructuredObject, isString bool) error {
+type jinja2Result struct {
+	Result *string `json:"result,omitempty"`
+	Error  *string `json:"error,omitempty"`
+}
+
+func (j *pythonJinja2Renderer) renderHelper(jobs []*RenderJob, searchDirs []string, vars *uo.UnstructuredObject, isString bool) error {
 	varsStr, err := json.Marshal(vars.Object)
 	if err != nil {
 		return err
@@ -135,21 +123,14 @@ func (j *pythonJinja2Renderer) renderHelper2(jobs []*RenderJob, searchDirs []str
 
 	var processedJobs []*RenderJob
 
-	pyTemplates := python.PyList_New(0)
-	pySearchDirs := python.PyList_New(0)
-	pyVars := python.PythonWrapper.PyUnicode_FromString(string(varsStr))
-
-	defer func() {
-		for i := 0; i < pyTemplates.Length(); i++ {
-			pyTemplates.GetItem(i).DecRef()
-		}
-		for i := 0; i < pySearchDirs.Length(); i++ {
-			pySearchDirs.GetItem(i).DecRef()
-		}
-		pyTemplates.DecRef()
-		pySearchDirs.DecRef()
-		pyVars.DecRef()
-	}()
+	var jargs jinja2Args
+	if isString {
+		jargs.Cmd = "render-strings"
+	} else {
+		jargs.Cmd = "render-files"
+	}
+	jargs.Vars = string(varsStr)
+	jargs.SearchDirs = searchDirs
 
 	for _, job := range jobs {
 		if ist, r := j.isMaybeTemplate(job.Template, searchDirs, isString); !ist {
@@ -157,43 +138,50 @@ func (j *pythonJinja2Renderer) renderHelper2(jobs []*RenderJob, searchDirs []str
 			continue
 		}
 		processedJobs = append(processedJobs, job)
-		pyTemplates.Append(python.PythonWrapper.PyUnicode_FromString(job.Template))
+		jargs.Templates = append(jargs.Templates, job.Template)
 	}
 	if len(processedJobs) == 0 {
 		return nil
 	}
 
-	for _, sd := range searchDirs {
-		pySearchDirs.Append(python.PythonWrapper.PyUnicode_FromString(sd))
+	b, err := json.Marshal(jargs)
+	if err != nil {
+		j.Close()
+		return err
 	}
+	b = append(b, '\n')
 
-	var pyFuncName *python.PyObject
-	var pyResult *python.PyList
-	if isString {
-		pyFuncName = python.PythonWrapper.PyUnicode_FromString("RenderStrings")
-	} else {
-		pyFuncName = python.PythonWrapper.PyUnicode_FromString("RenderFiles")
+	_, err = j.stdin.Write(b)
+	if err != nil {
+		j.Close()
+		return err
 	}
-	defer pyFuncName.DecRef()
-	pyResult = python.PyList_FromObject(j.renderer.CallMethodObjArgs(pyFuncName, pyTemplates, pySearchDirs, pyVars))
-	if pyResult == nil {
-		python.PythonWrapper.PyErr_Print()
-		return fmt.Errorf("unexpected exception while calling python code: %w", err)
+
+	line := bytes.NewBuffer(nil)
+	for true {
+		l, p, err := j.stdoutReader.ReadLine()
+		if err != nil {
+			return err
+		}
+		line.Write(l)
+		if !p {
+			break
+		}
+	}
+	var result []jinja2Result
+	err = json.Unmarshal(line.Bytes(), &result)
+	if err != nil {
+		return err
 	}
 
-	for i := 0; i < pyResult.Length(); i++ {
-		item := python.PyDict_FromObject(pyResult.GetItem(i))
-		r := item.GetItemString("result")
-		if r != nil {
-			resultStr := python.PythonWrapper.PyUnicode_AsUTF8(r)
-			processedJobs[i].Result = &resultStr
+	for i, item := range result {
+		if item.Result != nil {
+			processedJobs[i].Result = item.Result
 		} else {
-			e := item.GetItemString("error")
-			if e == nil {
+			if item.Error == nil {
 				return fmt.Errorf("missing result and error from item at index %d", i)
 			}
-			eStr := python.PythonWrapper.PyUnicode_AsUTF8(e)
-			processedJobs[i].Error = &Jinja2Error{eStr}
+			processedJobs[i].Error = &Jinja2Error{*item.Error}
 		}
 	}
 
diff --git a/pkg/jinja2/python_src/main.py b/pkg/jinja2/python_src/main.py
new file mode 100644
index 000000000..5da3e18c6
--- /dev/null
+++ b/pkg/jinja2/python_src/main.py
@@ -0,0 +1,29 @@
+import json
+import sys
+
+from jinja2_renderer import Jinja2Renderer
+
+
+def main():
+    r = Jinja2Renderer()
+
+    while True:
+        args = sys.stdin.readline()
+        args = json.loads(args)
+
+        if args["cmd"] == "render-strings":
+            result = r.RenderStrings(args["templates"], args["searchDirs"] or [], args["vars"])
+        elif args["cmd"] == "render-files":
+            result = r.RenderFiles(args["templates"], args["searchDirs"] or [], args["vars"])
+        elif args["cmd"] == "exit":
+            break
+        else:
+            raise Exception("invalid cmd")
+
+        result = json.dumps(result)
+
+        sys.stdout.write(result + "\n")
+        sys.stdout.flush()
+
+if __name__ == "__main__":
+    main()
diff --git a/pkg/jinja2/python_src/pip-wheel.sh b/pkg/jinja2/python_src/pip-wheel.sh
index 23ab5e408..4dfe7d70a 100755
--- a/pkg/jinja2/python_src/pip-wheel.sh
+++ b/pkg/jinja2/python_src/pip-wheel.sh
@@ -2,10 +2,10 @@
 
 DIR=$(cd $(dirname $0) && pwd)
 
+rm -rf $DIR/wheel
 mkdir -p $DIR/wheel
 cd $DIR/wheel
 
-rm *.whl
 pip3 wheel -r ../requirements.txt
 
 for f in *.whl; do
diff --git a/pkg/python/.gitignore b/pkg/python/.gitignore
index 1cdf75058..01ec3b178 100644
--- a/pkg/python/.gitignore
+++ b/pkg/python/.gitignore
@@ -1,2 +1,2 @@
-python-lib-*.tar.gz
+*.tar.gz
 *.files
diff --git a/pkg/python/cmd.go b/pkg/python/cmd.go
new file mode 100644
index 000000000..2ba8d7765
--- /dev/null
+++ b/pkg/python/cmd.go
@@ -0,0 +1,24 @@
+package python
+
+import (
+	"fmt"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"runtime"
+)
+
+func PythonCmd(args []string) *exec.Cmd {
+	var exePath string
+	if runtime.GOOS == "windows" {
+		exePath = filepath.Join(embeddedPythonPath, "python3.exe")
+	} else {
+		exePath = filepath.Join(embeddedPythonPath, "bin/python3")
+	}
+
+	cmd := exec.Command(exePath, args...)
+	cmd.Env = os.Environ()
+	cmd.Env = append(cmd.Env, fmt.Sprintf("PYTHONHOME=%s", embeddedPythonPath))
+
+	return cmd
+}
diff --git a/pkg/python/dict.go b/pkg/python/dict.go
deleted file mode 100644
index 59930ddcf..000000000
--- a/pkg/python/dict.go
+++ /dev/null
@@ -1,25 +0,0 @@
-package python
-
-import "C"
-import "unsafe"
-
-type PyDict = PyObject
-
-func PyDict_New() *PyDict {
-	return PythonWrapper.PyDict_New()
-}
-
-func PyDict_FromPointer(p unsafe.Pointer) *PyDict {
-	if p == nil {
-		return nil
-	}
-	return &PyDict{p: p}
-}
-
-func PyDict_FromObject(o *PyObject) *PyDict {
-	return o
-}
-
-func (d *PyDict) GetItemString(key string) *PyObject {
-	return PythonWrapper.PyDict_GetItemString(d, key)
-}
diff --git a/pkg/python/embed.go b/pkg/python/embed.go
new file mode 100644
index 000000000..1bf7ec166
--- /dev/null
+++ b/pkg/python/embed.go
@@ -0,0 +1,39 @@
+package python
+
+import (
+	"fmt"
+	"github.com/codablock/kluctl/pkg/utils"
+	"github.com/codablock/kluctl/pkg/utils/embed_util"
+	"log"
+	"path/filepath"
+	"runtime"
+)
+
+var embeddedPythonPath string
+
+func init() {
+	embeddedPythonPath = decompressPython()
+}
+
+func decompressPython() string {
+	tarName := fmt.Sprintf("python-%s.tar.gz", runtime.GOOS)
+	tgz, err := pythonLib.Open(tarName)
+	if err != nil {
+		log.Panic(err)
+	}
+	defer tgz.Close()
+
+	fileList, err := pythonLib.Open(tarName + ".files")
+	if err != nil {
+		log.Panic(err)
+	}
+	defer fileList.Close()
+
+	path := filepath.Join(utils.GetTmpBaseDir(), fmt.Sprintf("python-%s", runtime.GOOS))
+	err = embed_util.ExtractTarToTmp(tgz, fileList, path)
+	if err != nil {
+		log.Panic(err)
+	}
+
+	return path
+}
diff --git a/pkg/python/embed_darwin.go b/pkg/python/embed_darwin.go
new file mode 100644
index 000000000..ee5252620
--- /dev/null
+++ b/pkg/python/embed_darwin.go
@@ -0,0 +1,8 @@
+package python
+
+import "embed"
+
+//go:generate go run ../utils/embed_util/packer python-darwin.tar.gz ../../download-python/darwin/python/install bin lib/*.dylib lib/python3.*
+
+//go:embed python-darwin.tar.gz python-darwin.tar.gz.files
+var pythonLib embed.FS
diff --git a/pkg/python/embed_linux.go b/pkg/python/embed_linux.go
new file mode 100644
index 000000000..6299f3562
--- /dev/null
+++ b/pkg/python/embed_linux.go
@@ -0,0 +1,8 @@
+package python
+
+import "embed"
+
+//go:generate go run ../utils/embed_util/packer python-linux.tar.gz ../../download-python/linux/python/install bin lib/*.so lib/python3.*
+
+//go:embed python-linux.tar.gz python-linux.tar.gz.files
+var pythonLib embed.FS
diff --git a/pkg/python/embed_windows.go b/pkg/python/embed_windows.go
new file mode 100644
index 000000000..09a5af595
--- /dev/null
+++ b/pkg/python/embed_windows.go
@@ -0,0 +1,8 @@
+package python
+
+import "embed"
+
+//go:generate go run ../utils/embed_util/packer python-windows.tar.gz ../../download-python/windows/python/install Lib DLLs *.dll *.exe
+
+//go:embed python-windows.tar.gz python-windows.tar.gz.files
+var pythonLib embed.FS
diff --git a/pkg/python/interpreter.go b/pkg/python/interpreter.go
deleted file mode 100644
index be1cb0cf2..000000000
--- a/pkg/python/interpreter.go
+++ /dev/null
@@ -1,76 +0,0 @@
-package python
-
-import "C"
-
-import (
-	"fmt"
-	"runtime"
-)
-
-type PythonInterpreter struct {
-	threadState *PythonThreadState
-}
-
-func NewPythonInterpreter() (*PythonInterpreter, error) {
-	p := &PythonInterpreter{}
-
-	PythonWrapper.PyEval_AcquireThread(mainThreadState)
-	p.threadState = PythonWrapper.Py_NewInterpreter()
-	if p.threadState == nil {
-		PythonWrapper.PyErr_Print()
-		return nil, fmt.Errorf("failed to initialize sub-interpreter")
-	}
-	PythonWrapper.PyEval_ReleaseThread(p.threadState)
-
-	return p, nil
-}
-
-func MainPythonInterpreter() (*PythonInterpreter, error) {
-	p := &PythonInterpreter{
-		threadState: mainThreadState,
-	}
-	return p, nil
-}
-
-func (p *PythonInterpreter) Stop() {
-	if p.threadState != mainThreadState {
-		PythonWrapper.PyEval_AcquireThread(p.threadState)
-		PythonWrapper.Py_EndInterpreter(p.threadState)
-	}
-	p.threadState = nil
-}
-
-func (p *PythonInterpreter) Lock() {
-	runtime.LockOSThread()
-	if p.threadState != nil {
-		PythonWrapper.PyEval_AcquireThread(p.threadState)
-	}
-}
-
-func (p *PythonInterpreter) Unlock() {
-	if p.threadState != nil {
-		PythonWrapper.PyEval_ReleaseThread(p.threadState)
-	}
-	runtime.UnlockOSThread()
-}
-
-func (p *PythonInterpreter) Run(fun func() error) error {
-	p.Lock()
-	defer p.Unlock()
-
-	return fun()
-}
-
-func (p *PythonInterpreter) AppendSysPath(pth string) error {
-	return p.Run(func() error {
-		sys := PythonWrapper.PyImport_ImportModule("sys")
-		defer sys.DecRef()
-		l := PyList_FromObject(sys.GetAttrString("path"))
-		defer l.DecRef()
-
-		l.Append(PythonWrapper.PyUnicode_FromString(pth))
-		return nil
-	})
-}
-
-var mainThreadState *PythonThreadState
diff --git a/pkg/python/lib.go b/pkg/python/lib.go
deleted file mode 100644
index 7136e6be8..000000000
--- a/pkg/python/lib.go
+++ /dev/null
@@ -1,78 +0,0 @@
-package python
-
-import (
-	"fmt"
-	"github.com/codablock/kluctl/pkg/utils"
-	"github.com/codablock/kluctl/pkg/utils/embed_util"
-	"github.com/codablock/kluctl/pkg/utils/lib_wrapper"
-	"log"
-	"path/filepath"
-	"runtime"
-	"unsafe"
-)
-
-var pythonModule *lib_wrapper.LibWrapper
-var PythonWrapper LibPythonWrapper
-
-type PythonThreadState struct {
-	P unsafe.Pointer
-}
-
-func PythonThreadState_FromPointer(p unsafe.Pointer) *PythonThreadState {
-	if p == nil {
-		return nil
-	}
-	return &PythonThreadState{P: p}
-}
-
-func (p *PythonThreadState) GetPointer() unsafe.Pointer {
-	if p == nil {
-		return nil
-	}
-	return p.P
-}
-
-func init() {
-	libPath := decompressLib()
-
-	var module string
-	if runtime.GOOS == "windows" {
-		module = "python310.dll"
-	} else if runtime.GOOS == "darwin" {
-		module = "lib/libpython3.10.dylib"
-	} else {
-		module = "lib/libpython3.10.so"
-	}
-
-	pythonModule = lib_wrapper.LoadModule(filepath.Join(libPath, module))
-	PythonWrapper = New_LibPythonWrapper(pythonModule)
-
-	l := PythonWrapper.Py_DecodeLocale(libPath, nil)
-	PythonWrapper.Py_SetPythonHome(l)
-
-	PythonWrapper.Py_InitializeEx(0)
-	mainThreadState = PythonWrapper.PyEval_SaveThread()
-}
-
-func decompressLib() string {
-	tarName := fmt.Sprintf("python-lib-%s.tar.gz", runtime.GOOS)
-	tgz, err := pythonLib.Open(tarName)
-	if err != nil {
-		log.Panic(err)
-	}
-	defer tgz.Close()
-
-	fileList, err := pythonLib.Open(tarName + ".files")
-	if err != nil {
-		log.Panic(err)
-	}
-	defer fileList.Close()
-
-	libPath := filepath.Join(utils.GetTmpBaseDir(), fmt.Sprintf("python-libs-%s", runtime.GOOS))
-	err = embed_util.ExtractTarToTmp(tgz, fileList, libPath)
-	if err != nil {
-		log.Panic(err)
-	}
-
-	return libPath
-}
diff --git a/pkg/python/lib_darwin.go b/pkg/python/lib_darwin.go
deleted file mode 100644
index 5a9c1253b..000000000
--- a/pkg/python/lib_darwin.go
+++ /dev/null
@@ -1,8 +0,0 @@
-package python
-
-import "embed"
-
-//go:generate go run ../utils/embed_util/packer python-lib-darwin.tar.gz ../../build-python/darwin/cpython-install lib
-
-//go:embed python-lib-darwin.tar.gz python-lib-darwin.tar.gz.files
-var pythonLib embed.FS
diff --git a/pkg/python/lib_linux.go b/pkg/python/lib_linux.go
deleted file mode 100644
index 1106f1462..000000000
--- a/pkg/python/lib_linux.go
+++ /dev/null
@@ -1,8 +0,0 @@
-package python
-
-import "embed"
-
-//go:generate go run ../utils/embed_util/packer python-lib-linux.tar.gz ../../build-python/linux/cpython-install lib
-
-//go:embed python-lib-linux.tar.gz python-lib-linux.tar.gz.files
-var pythonLib embed.FS
diff --git a/pkg/python/lib_windows.go b/pkg/python/lib_windows.go
deleted file mode 100644
index 129aece2a..000000000
--- a/pkg/python/lib_windows.go
+++ /dev/null
@@ -1,8 +0,0 @@
-package python
-
-import "embed"
-
-//go:generate go run ../utils/embed_util/packer python-lib-windows.tar.gz ../../build-python/windows *
-
-//go:embed python-lib-windows.tar.gz python-lib-windows.tar.gz.files
-var pythonLib embed.FS
diff --git a/pkg/python/libpython_wrapper.go b/pkg/python/libpython_wrapper.go
deleted file mode 100644
index 368aefbf7..000000000
--- a/pkg/python/libpython_wrapper.go
+++ /dev/null
@@ -1,50 +0,0 @@
-package python
-
-import "unsafe"
-
-type ssize_t uint
-
-func (s *ssize_t) GetPointer() unsafe.Pointer {
-	return unsafe.Pointer(s)
-}
-
-//go:generate go run .../lib_wrapper/gen --input libpython_wrapper.go --output libpython_wrapper_impl.go
-type LibPythonWrapper interface {
-	Py_Initialize()
-	Py_InitializeEx(initsigs int)
-
-	Py_DecodeLocale(s string, size *ssize_t) unsafe.Pointer
-	Py_SetPythonHome(l unsafe.Pointer)
-
-	Py_NewInterpreter() *PythonThreadState
-	Py_EndInterpreter(o *PythonThreadState)
-
-	PyEval_AcquireThread(o *PythonThreadState)
-	PyEval_ReleaseThread(o *PythonThreadState)
-	PyEval_SaveThread() *PythonThreadState
-	PyThreadState_Swap(o *PythonThreadState) *PythonThreadState
-	PyEval_ReleaseLock()
-
-	PyImport_ImportModule(name string) *PyObject
-
-	Py_IncRef(o *PyObject)
-	Py_DecRef(o *PyObject)
-	PyObject_Length(p *PyObject) int
-	PyObject_GetAttrString(p *PyObject, name string) *PyObject
-	PyObject_SetAttrString(p *PyObject, name string, p2 *PyObject) int
-	PyObject_CallObject(p *PyObject, args *PyObject) *PyObject
-	PyObject_CallMethodObjArgs(p *PyObject, name *PyObject, cargs_vargs []*PyObject) *PyObject
-
-	PyList_New(len ssize_t) *PyList
-	PyList_GetItem(o *PyList, i ssize_t) *PyObject
-	PyList_SetItem(o *PyList, i ssize_t, e *PyObject) int
-	PyList_Append(o *PyList, e *PyObject) int
-
-	PyDict_New() *PyDict
-	PyDict_GetItemString(p *PyDict, key string) *PyObject
-
-	PyUnicode_FromString(pth string) *PyObject
-	PyUnicode_AsUTF8(r *PyObject) string
-
-	PyErr_Print()
-}
diff --git a/pkg/python/libpython_wrapper_impl.go b/pkg/python/libpython_wrapper_impl.go
deleted file mode 100644
index f49703ef2..000000000
--- a/pkg/python/libpython_wrapper_impl.go
+++ /dev/null
@@ -1,350 +0,0 @@
-package python
-
-/*
-#include 
-#include 
-#include 
-void _trampoline_Py_Initialize(void* f) {
-	typedef void (*F)();
-	((F)f)();
-}
-void _trampoline_Py_InitializeEx(void* f, int initsigs) {
-	typedef void (*F)(int);
-	((F)f)(initsigs);
-}
-void* _trampoline_Py_DecodeLocale(void* f, char* s, void* size) {
-	typedef void* (*F)(char*, void*);
-	return ((F)f)(s, size);
-}
-void _trampoline_Py_SetPythonHome(void* f, void* l) {
-	typedef void (*F)(void*);
-	((F)f)(l);
-}
-void* _trampoline_Py_NewInterpreter(void* f) {
-	typedef void* (*F)();
-	return ((F)f)();
-}
-void _trampoline_Py_EndInterpreter(void* f, void* o) {
-	typedef void (*F)(void*);
-	((F)f)(o);
-}
-void _trampoline_PyEval_AcquireThread(void* f, void* o) {
-	typedef void (*F)(void*);
-	((F)f)(o);
-}
-void _trampoline_PyEval_ReleaseThread(void* f, void* o) {
-	typedef void (*F)(void*);
-	((F)f)(o);
-}
-void* _trampoline_PyEval_SaveThread(void* f) {
-	typedef void* (*F)();
-	return ((F)f)();
-}
-void* _trampoline_PyThreadState_Swap(void* f, void* o) {
-	typedef void* (*F)(void*);
-	return ((F)f)(o);
-}
-void _trampoline_PyEval_ReleaseLock(void* f) {
-	typedef void (*F)();
-	((F)f)();
-}
-void* _trampoline_PyImport_ImportModule(void* f, char* name) {
-	typedef void* (*F)(char*);
-	return ((F)f)(name);
-}
-void _trampoline_Py_IncRef(void* f, void* o) {
-	typedef void (*F)(void*);
-	((F)f)(o);
-}
-void _trampoline_Py_DecRef(void* f, void* o) {
-	typedef void (*F)(void*);
-	((F)f)(o);
-}
-int _trampoline_PyObject_Length(void* f, void* p) {
-	typedef int (*F)(void*);
-	return ((F)f)(p);
-}
-void* _trampoline_PyObject_GetAttrString(void* f, void* p, char* name) {
-	typedef void* (*F)(void*, char*);
-	return ((F)f)(p, name);
-}
-int _trampoline_PyObject_SetAttrString(void* f, void* p, char* name, void* p2) {
-	typedef int (*F)(void*, char*, void*);
-	return ((F)f)(p, name, p2);
-}
-void* _trampoline_PyObject_CallObject(void* f, void* p, void* args) {
-	typedef void* (*F)(void*, void*);
-	return ((F)f)(p, args);
-}
-void* _trampoline_PyObject_CallMethodObjArgs(void* f, void* p, void* name, int cargs_vargs_len, void** cargs_vargs) {
-	typedef void* (*F)(void*, void*, ...);
-	switch(cargs_vargs_len) {
-	case 0: return ((F)f)(p, name);
-	case 1: return ((F)f)(p, name, cargs_vargs[0]);
-	case 2: return ((F)f)(p, name, cargs_vargs[0], cargs_vargs[1]);
-	case 3: return ((F)f)(p, name, cargs_vargs[0], cargs_vargs[1], cargs_vargs[2]);
-	case 4: return ((F)f)(p, name, cargs_vargs[0], cargs_vargs[1], cargs_vargs[2], cargs_vargs[3]);
-	case 5: return ((F)f)(p, name, cargs_vargs[0], cargs_vargs[1], cargs_vargs[2], cargs_vargs[3], cargs_vargs[4]);
-	case 6: return ((F)f)(p, name, cargs_vargs[0], cargs_vargs[1], cargs_vargs[2], cargs_vargs[3], cargs_vargs[4], cargs_vargs[5]);
-	case 7: return ((F)f)(p, name, cargs_vargs[0], cargs_vargs[1], cargs_vargs[2], cargs_vargs[3], cargs_vargs[4], cargs_vargs[5], cargs_vargs[6]);
-	case 8: return ((F)f)(p, name, cargs_vargs[0], cargs_vargs[1], cargs_vargs[2], cargs_vargs[3], cargs_vargs[4], cargs_vargs[5], cargs_vargs[6], cargs_vargs[7]);
-	case 9: return ((F)f)(p, name, cargs_vargs[0], cargs_vargs[1], cargs_vargs[2], cargs_vargs[3], cargs_vargs[4], cargs_vargs[5], cargs_vargs[6], cargs_vargs[7], cargs_vargs[8]);
-	default: assert(0);
-	}
-}
-void* _trampoline_PyList_New(void* f, ssize_t len) {
-	typedef void* (*F)(ssize_t);
-	return ((F)f)(len);
-}
-void* _trampoline_PyList_GetItem(void* f, void* o, ssize_t i) {
-	typedef void* (*F)(void*, ssize_t);
-	return ((F)f)(o, i);
-}
-int _trampoline_PyList_SetItem(void* f, void* o, ssize_t i, void* e) {
-	typedef int (*F)(void*, ssize_t, void*);
-	return ((F)f)(o, i, e);
-}
-int _trampoline_PyList_Append(void* f, void* o, void* e) {
-	typedef int (*F)(void*, void*);
-	return ((F)f)(o, e);
-}
-void* _trampoline_PyDict_New(void* f) {
-	typedef void* (*F)();
-	return ((F)f)();
-}
-void* _trampoline_PyDict_GetItemString(void* f, void* p, char* key) {
-	typedef void* (*F)(void*, char*);
-	return ((F)f)(p, key);
-}
-void* _trampoline_PyUnicode_FromString(void* f, char* pth) {
-	typedef void* (*F)(char*);
-	return ((F)f)(pth);
-}
-char* _trampoline_PyUnicode_AsUTF8(void* f, void* r) {
-	typedef char* (*F)(void*);
-	return ((F)f)(r);
-}
-void _trampoline_PyErr_Print(void* f) {
-	typedef void (*F)();
-	((F)f)();
-}
-*/
-import "C"
-
-import "unsafe"
-import "github.com/codablock/kluctl/pkg/utils/lib_wrapper"
-
-type LibPythonWrapperImpl struct {
-	module                           *lib_wrapper.LibWrapper
-	_func_Py_Initialize              lib_wrapper.FunctionPtr
-	_func_Py_InitializeEx            lib_wrapper.FunctionPtr
-	_func_Py_DecodeLocale            lib_wrapper.FunctionPtr
-	_func_Py_SetPythonHome           lib_wrapper.FunctionPtr
-	_func_Py_NewInterpreter          lib_wrapper.FunctionPtr
-	_func_Py_EndInterpreter          lib_wrapper.FunctionPtr
-	_func_PyEval_AcquireThread       lib_wrapper.FunctionPtr
-	_func_PyEval_ReleaseThread       lib_wrapper.FunctionPtr
-	_func_PyEval_SaveThread          lib_wrapper.FunctionPtr
-	_func_PyThreadState_Swap         lib_wrapper.FunctionPtr
-	_func_PyEval_ReleaseLock         lib_wrapper.FunctionPtr
-	_func_PyImport_ImportModule      lib_wrapper.FunctionPtr
-	_func_Py_IncRef                  lib_wrapper.FunctionPtr
-	_func_Py_DecRef                  lib_wrapper.FunctionPtr
-	_func_PyObject_Length            lib_wrapper.FunctionPtr
-	_func_PyObject_GetAttrString     lib_wrapper.FunctionPtr
-	_func_PyObject_SetAttrString     lib_wrapper.FunctionPtr
-	_func_PyObject_CallObject        lib_wrapper.FunctionPtr
-	_func_PyObject_CallMethodObjArgs lib_wrapper.FunctionPtr
-	_func_PyList_New                 lib_wrapper.FunctionPtr
-	_func_PyList_GetItem             lib_wrapper.FunctionPtr
-	_func_PyList_SetItem             lib_wrapper.FunctionPtr
-	_func_PyList_Append              lib_wrapper.FunctionPtr
-	_func_PyDict_New                 lib_wrapper.FunctionPtr
-	_func_PyDict_GetItemString       lib_wrapper.FunctionPtr
-	_func_PyUnicode_FromString       lib_wrapper.FunctionPtr
-	_func_PyUnicode_AsUTF8           lib_wrapper.FunctionPtr
-	_func_PyErr_Print                lib_wrapper.FunctionPtr
-}
-
-func New_LibPythonWrapper(module *lib_wrapper.LibWrapper) LibPythonWrapper {
-	w := &LibPythonWrapperImpl{module: module}
-	w._func_Py_Initialize = w.module.GetFunc("Py_Initialize")
-	w._func_Py_InitializeEx = w.module.GetFunc("Py_InitializeEx")
-	w._func_Py_DecodeLocale = w.module.GetFunc("Py_DecodeLocale")
-	w._func_Py_SetPythonHome = w.module.GetFunc("Py_SetPythonHome")
-	w._func_Py_NewInterpreter = w.module.GetFunc("Py_NewInterpreter")
-	w._func_Py_EndInterpreter = w.module.GetFunc("Py_EndInterpreter")
-	w._func_PyEval_AcquireThread = w.module.GetFunc("PyEval_AcquireThread")
-	w._func_PyEval_ReleaseThread = w.module.GetFunc("PyEval_ReleaseThread")
-	w._func_PyEval_SaveThread = w.module.GetFunc("PyEval_SaveThread")
-	w._func_PyThreadState_Swap = w.module.GetFunc("PyThreadState_Swap")
-	w._func_PyEval_ReleaseLock = w.module.GetFunc("PyEval_ReleaseLock")
-	w._func_PyImport_ImportModule = w.module.GetFunc("PyImport_ImportModule")
-	w._func_Py_IncRef = w.module.GetFunc("Py_IncRef")
-	w._func_Py_DecRef = w.module.GetFunc("Py_DecRef")
-	w._func_PyObject_Length = w.module.GetFunc("PyObject_Length")
-	w._func_PyObject_GetAttrString = w.module.GetFunc("PyObject_GetAttrString")
-	w._func_PyObject_SetAttrString = w.module.GetFunc("PyObject_SetAttrString")
-	w._func_PyObject_CallObject = w.module.GetFunc("PyObject_CallObject")
-	w._func_PyObject_CallMethodObjArgs = w.module.GetFunc("PyObject_CallMethodObjArgs")
-	w._func_PyList_New = w.module.GetFunc("PyList_New")
-	w._func_PyList_GetItem = w.module.GetFunc("PyList_GetItem")
-	w._func_PyList_SetItem = w.module.GetFunc("PyList_SetItem")
-	w._func_PyList_Append = w.module.GetFunc("PyList_Append")
-	w._func_PyDict_New = w.module.GetFunc("PyDict_New")
-	w._func_PyDict_GetItemString = w.module.GetFunc("PyDict_GetItemString")
-	w._func_PyUnicode_FromString = w.module.GetFunc("PyUnicode_FromString")
-	w._func_PyUnicode_AsUTF8 = w.module.GetFunc("PyUnicode_AsUTF8")
-	w._func_PyErr_Print = w.module.GetFunc("PyErr_Print")
-	return w
-}
-func (w *LibPythonWrapperImpl) Py_Initialize() {
-	C._trampoline_Py_Initialize(unsafe.Pointer(w._func_Py_Initialize))
-}
-func (w *LibPythonWrapperImpl) Py_InitializeEx(initsigs int) {
-	_c_initsigs := C.int(initsigs)
-	C._trampoline_Py_InitializeEx(unsafe.Pointer(w._func_Py_InitializeEx), _c_initsigs)
-}
-func (w *LibPythonWrapperImpl) Py_DecodeLocale(s string, size *ssize_t) unsafe.Pointer {
-	_c_s := C.CString(s)
-	defer C.free(unsafe.Pointer(_c_s))
-	_c_size := (size).GetPointer()
-	_g_ret := unsafe.Pointer(C._trampoline_Py_DecodeLocale(unsafe.Pointer(w._func_Py_DecodeLocale), _c_s, _c_size))
-	return _g_ret
-}
-func (w *LibPythonWrapperImpl) Py_SetPythonHome(l unsafe.Pointer) {
-	C._trampoline_Py_SetPythonHome(unsafe.Pointer(w._func_Py_SetPythonHome), l)
-}
-func (w *LibPythonWrapperImpl) Py_NewInterpreter() *PythonThreadState {
-	_g_ret := PythonThreadState_FromPointer(C._trampoline_Py_NewInterpreter(unsafe.Pointer(w._func_Py_NewInterpreter)))
-	return _g_ret
-}
-func (w *LibPythonWrapperImpl) Py_EndInterpreter(o *PythonThreadState) {
-	_c_o := (o).GetPointer()
-	C._trampoline_Py_EndInterpreter(unsafe.Pointer(w._func_Py_EndInterpreter), _c_o)
-}
-func (w *LibPythonWrapperImpl) PyEval_AcquireThread(o *PythonThreadState) {
-	_c_o := (o).GetPointer()
-	C._trampoline_PyEval_AcquireThread(unsafe.Pointer(w._func_PyEval_AcquireThread), _c_o)
-}
-func (w *LibPythonWrapperImpl) PyEval_ReleaseThread(o *PythonThreadState) {
-	_c_o := (o).GetPointer()
-	C._trampoline_PyEval_ReleaseThread(unsafe.Pointer(w._func_PyEval_ReleaseThread), _c_o)
-}
-func (w *LibPythonWrapperImpl) PyEval_SaveThread() *PythonThreadState {
-	_g_ret := PythonThreadState_FromPointer(C._trampoline_PyEval_SaveThread(unsafe.Pointer(w._func_PyEval_SaveThread)))
-	return _g_ret
-}
-func (w *LibPythonWrapperImpl) PyThreadState_Swap(o *PythonThreadState) *PythonThreadState {
-	_c_o := (o).GetPointer()
-	_g_ret := PythonThreadState_FromPointer(C._trampoline_PyThreadState_Swap(unsafe.Pointer(w._func_PyThreadState_Swap), _c_o))
-	return _g_ret
-}
-func (w *LibPythonWrapperImpl) PyEval_ReleaseLock() {
-	C._trampoline_PyEval_ReleaseLock(unsafe.Pointer(w._func_PyEval_ReleaseLock))
-}
-func (w *LibPythonWrapperImpl) PyImport_ImportModule(name string) *PyObject {
-	_c_name := C.CString(name)
-	defer C.free(unsafe.Pointer(_c_name))
-	_g_ret := PyObject_FromPointer(C._trampoline_PyImport_ImportModule(unsafe.Pointer(w._func_PyImport_ImportModule), _c_name))
-	return _g_ret
-}
-func (w *LibPythonWrapperImpl) Py_IncRef(o *PyObject) {
-	_c_o := (o).GetPointer()
-	C._trampoline_Py_IncRef(unsafe.Pointer(w._func_Py_IncRef), _c_o)
-}
-func (w *LibPythonWrapperImpl) Py_DecRef(o *PyObject) {
-	_c_o := (o).GetPointer()
-	C._trampoline_Py_DecRef(unsafe.Pointer(w._func_Py_DecRef), _c_o)
-}
-func (w *LibPythonWrapperImpl) PyObject_Length(p *PyObject) int {
-	_c_p := (p).GetPointer()
-	_g_ret := int(C._trampoline_PyObject_Length(unsafe.Pointer(w._func_PyObject_Length), _c_p))
-	return _g_ret
-}
-func (w *LibPythonWrapperImpl) PyObject_GetAttrString(p *PyObject, name string) *PyObject {
-	_c_p := (p).GetPointer()
-	_c_name := C.CString(name)
-	defer C.free(unsafe.Pointer(_c_name))
-	_g_ret := PyObject_FromPointer(C._trampoline_PyObject_GetAttrString(unsafe.Pointer(w._func_PyObject_GetAttrString), _c_p, _c_name))
-	return _g_ret
-}
-func (w *LibPythonWrapperImpl) PyObject_SetAttrString(p *PyObject, name string, p2 *PyObject) int {
-	_c_p := (p).GetPointer()
-	_c_name := C.CString(name)
-	defer C.free(unsafe.Pointer(_c_name))
-	_c_p2 := (p2).GetPointer()
-	_g_ret := int(C._trampoline_PyObject_SetAttrString(unsafe.Pointer(w._func_PyObject_SetAttrString), _c_p, _c_name, _c_p2))
-	return _g_ret
-}
-func (w *LibPythonWrapperImpl) PyObject_CallObject(p *PyObject, args *PyObject) *PyObject {
-	_c_p := (p).GetPointer()
-	_c_args := (args).GetPointer()
-	_g_ret := PyObject_FromPointer(C._trampoline_PyObject_CallObject(unsafe.Pointer(w._func_PyObject_CallObject), _c_p, _c_args))
-	return _g_ret
-}
-func (w *LibPythonWrapperImpl) PyObject_CallMethodObjArgs(p *PyObject, name *PyObject, cargs_vargs []*PyObject) *PyObject {
-	_c_p := (p).GetPointer()
-	_c_name := (name).GetPointer()
-	_c_cargs_vargs_ := cargs_vargs
-	var _c_cargs_vargs *unsafe.Pointer
-	_c_cargs_vargs__ := make([]unsafe.Pointer, len(_c_cargs_vargs_), len(_c_cargs_vargs_))
-	for i, _ := range _c_cargs_vargs_ {
-		_c_cargs_vargs__[i] = _c_cargs_vargs_[i].GetPointer()
-	}
-	if len(_c_cargs_vargs_) != 0 {
-		_c_cargs_vargs = &_c_cargs_vargs__[0]
-	}
-	_c_cargs_vargs_len := len(_c_cargs_vargs_)
-	_g_ret := PyObject_FromPointer(C._trampoline_PyObject_CallMethodObjArgs(unsafe.Pointer(w._func_PyObject_CallMethodObjArgs), _c_p, _c_name, C.int(_c_cargs_vargs_len), _c_cargs_vargs))
-	return _g_ret
-}
-func (w *LibPythonWrapperImpl) PyList_New(len ssize_t) *PyList {
-	_c_len := C.ssize_t(len)
-	_g_ret := PyList_FromPointer(C._trampoline_PyList_New(unsafe.Pointer(w._func_PyList_New), _c_len))
-	return _g_ret
-}
-func (w *LibPythonWrapperImpl) PyList_GetItem(o *PyList, i ssize_t) *PyObject {
-	_c_o := (o).GetPointer()
-	_c_i := C.ssize_t(i)
-	_g_ret := PyObject_FromPointer(C._trampoline_PyList_GetItem(unsafe.Pointer(w._func_PyList_GetItem), _c_o, _c_i))
-	return _g_ret
-}
-func (w *LibPythonWrapperImpl) PyList_SetItem(o *PyList, i ssize_t, e *PyObject) int {
-	_c_o := (o).GetPointer()
-	_c_i := C.ssize_t(i)
-	_c_e := (e).GetPointer()
-	_g_ret := int(C._trampoline_PyList_SetItem(unsafe.Pointer(w._func_PyList_SetItem), _c_o, _c_i, _c_e))
-	return _g_ret
-}
-func (w *LibPythonWrapperImpl) PyList_Append(o *PyList, e *PyObject) int {
-	_c_o := (o).GetPointer()
-	_c_e := (e).GetPointer()
-	_g_ret := int(C._trampoline_PyList_Append(unsafe.Pointer(w._func_PyList_Append), _c_o, _c_e))
-	return _g_ret
-}
-func (w *LibPythonWrapperImpl) PyDict_New() *PyDict {
-	_g_ret := PyDict_FromPointer(C._trampoline_PyDict_New(unsafe.Pointer(w._func_PyDict_New)))
-	return _g_ret
-}
-func (w *LibPythonWrapperImpl) PyDict_GetItemString(p *PyDict, key string) *PyObject {
-	_c_p := (p).GetPointer()
-	_c_key := C.CString(key)
-	defer C.free(unsafe.Pointer(_c_key))
-	_g_ret := PyObject_FromPointer(C._trampoline_PyDict_GetItemString(unsafe.Pointer(w._func_PyDict_GetItemString), _c_p, _c_key))
-	return _g_ret
-}
-func (w *LibPythonWrapperImpl) PyUnicode_FromString(pth string) *PyObject {
-	_c_pth := C.CString(pth)
-	defer C.free(unsafe.Pointer(_c_pth))
-	_g_ret := PyObject_FromPointer(C._trampoline_PyUnicode_FromString(unsafe.Pointer(w._func_PyUnicode_FromString), _c_pth))
-	return _g_ret
-}
-func (w *LibPythonWrapperImpl) PyUnicode_AsUTF8(r *PyObject) string {
-	_c_r := (r).GetPointer()
-	_g_ret := C.GoString(C._trampoline_PyUnicode_AsUTF8(unsafe.Pointer(w._func_PyUnicode_AsUTF8), _c_r))
-	return _g_ret
-}
-func (w *LibPythonWrapperImpl) PyErr_Print() {
-	C._trampoline_PyErr_Print(unsafe.Pointer(w._func_PyErr_Print))
-}
diff --git a/pkg/python/list.go b/pkg/python/list.go
deleted file mode 100644
index 56015d4d0..000000000
--- a/pkg/python/list.go
+++ /dev/null
@@ -1,28 +0,0 @@
-package python
-
-import "unsafe"
-
-type PyList = PyObject
-
-func PyList_New(len int) *PyList {
-	return PythonWrapper.PyList_New(ssize_t(len))
-}
-
-func PyList_FromPointer(p unsafe.Pointer) *PyList {
-	if p == nil {
-		return nil
-	}
-	return &PyList{p: p}
-}
-
-func PyList_FromObject(l *PyObject) *PyList {
-	return l
-}
-
-func (l *PyList) GetItem(pos int) *PyObject {
-	return PythonWrapper.PyList_GetItem(l, ssize_t(pos))
-}
-
-func (l *PyList) Append(item *PyObject) int {
-	return PythonWrapper.PyList_Append(l, item)
-}
diff --git a/pkg/python/object.go b/pkg/python/object.go
deleted file mode 100644
index 8f3a8ed68..000000000
--- a/pkg/python/object.go
+++ /dev/null
@@ -1,53 +0,0 @@
-package python
-
-import (
-	"unsafe"
-)
-
-type PyObject struct {
-	p unsafe.Pointer
-}
-
-func PyObject_FromPointer(p unsafe.Pointer) *PyObject {
-	if p == nil {
-		return nil
-	}
-	return &PyObject{p: p}
-}
-
-func (p *PyObject) GetPointer() unsafe.Pointer {
-	if p == nil {
-		return nil
-	}
-	return p.p
-}
-
-func (p *PyObject) IncRef() {
-	PythonWrapper.Py_IncRef(p)
-}
-
-func (p *PyObject) DecRef() {
-	PythonWrapper.Py_DecRef(p)
-}
-
-func (p *PyObject) Length() int {
-	return PythonWrapper.PyObject_Length(p)
-}
-
-func (p *PyObject) GetAttrString(attr_name string) *PyObject {
-	return PythonWrapper.PyObject_GetAttrString(p, attr_name)
-}
-
-func (p *PyObject) SetAttrString(attr_name string, v *PyObject) int {
-	return PythonWrapper.PyObject_SetAttrString(p, attr_name, v)
-}
-
-func (p *PyObject) CallObject(args *PyObject) *PyObject {
-	return PythonWrapper.PyObject_CallObject(p, args)
-}
-
-func (p *PyObject) CallMethodObjArgs(name *PyObject, args ...*PyObject) *PyObject {
-	args = append([]*PyObject{}, args...)
-	args = append(args, nil)
-	return PythonWrapper.PyObject_CallMethodObjArgs(p, name, args)
-}
diff --git a/pkg/utils/embed_util/extract.go b/pkg/utils/embed_util/extract.go
index 388e8b29a..281d36400 100644
--- a/pkg/utils/embed_util/extract.go
+++ b/pkg/utils/embed_util/extract.go
@@ -34,7 +34,7 @@ func ExtractTarToTmp(r io.Reader, fileListR io.Reader, targetPath string) error
 	}
 
 	tmpTargetPath := targetPath + ".tmp"
-	err = os.MkdirAll(tmpTargetPath , 0o700)
+	err = os.MkdirAll(tmpTargetPath, 0o700)
 	if err != nil {
 		return err
 	}
diff --git a/pkg/utils/embed_util/packer/main.go b/pkg/utils/embed_util/packer/main.go
index 26539679a..f0869a0e0 100644
--- a/pkg/utils/embed_util/packer/main.go
+++ b/pkg/utils/embed_util/packer/main.go
@@ -10,6 +10,7 @@ import (
 	"github.com/codablock/kluctl/pkg/utils"
 	"github.com/gobwas/glob"
 	log "github.com/sirupsen/logrus"
+	"io/fs"
 	"io/ioutil"
 	"os"
 	"path/filepath"
@@ -17,12 +18,12 @@ import (
 	"strings"
 )
 
-func main()  {
+func main() {
 	out := os.Args[1]
 	dir := os.Args[2]
-	pattern := os.Args[3]
+	patterns := os.Args[3:]
 
-	fileList, tgz := writeTar(dir, pattern)
+	fileList, tgz := writeTar(dir, patterns)
 
 	hash := sha256.Sum256(tgz)
 	err := ioutil.WriteFile(out, tgz, 0o600)
@@ -32,34 +33,47 @@ func main()  {
 
 	fileListStr := strings.Join(fileList, "\n")
 	fileListStr = hex.EncodeToString(hash[:]) + "\n" + fileListStr
-	err = ioutil.WriteFile(out + ".files", []byte(fileListStr), 0o600)
+	err = ioutil.WriteFile(out+".files", []byte(fileListStr), 0o600)
 	if err != nil {
 		log.Panic(err)
 	}
 }
 
-func writeTar(dir string, pattern string) ([]string, []byte) {
+func writeTar(dir string, patterns []string) ([]string, []byte) {
 	b := bytes.NewBuffer(nil)
 	gz := gzip.NewWriter(b)
 	t := tar.NewWriter(gz)
 
-	var rootNames []string
-	dirs, err := os.ReadDir(dir)
-	if err != nil {
-		log.Panic(err)
+	var globs []glob.Glob
+	for _, p := range patterns {
+		globs = append(globs, glob.MustCompile(p, '/'))
 	}
-	p := glob.MustCompile(pattern)
-	for _, d := range dirs {
-		if p.Match(d.Name()) {
-			rootNames = append(rootNames, d.Name())
+
+	var rootNames []string
+	err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
+		rel, err := filepath.Rel(dir, path)
+		if err != nil {
+			return err
 		}
-	}
+		match := false
+		for _, p := range globs {
+			if p.Match(rel) {
+				match = true
+				break
+			}
+		}
+		if match {
+			rootNames = append(rootNames, rel)
+		}
+		return nil
+	})
 	sort.Strings(rootNames)
 
 	excludes := []glob.Glob{
 		glob.MustCompile("__pycache__"),
 		glob.MustCompile("**/__pycache__"),
 		glob.MustCompile("**.a"),
+		glob.MustCompile("**.pdb"),
 	}
 
 	var fileList []string
diff --git a/pkg/utils/lib_wrapper/gen/lib_wrapper_gen.go b/pkg/utils/lib_wrapper/gen/lib_wrapper_gen.go
deleted file mode 100644
index 69e1ac539..000000000
--- a/pkg/utils/lib_wrapper/gen/lib_wrapper_gen.go
+++ /dev/null
@@ -1,347 +0,0 @@
-package main
-
-import (
-	"flag"
-	"fmt"
-	log "github.com/sirupsen/logrus"
-	"go/ast"
-	"go/format"
-	"go/parser"
-	"go/token"
-	"io/ioutil"
-	"reflect"
-	"strings"
-)
-
-var inputFile = flag.String("input", "", "input .go file")
-var outputFile = flag.String("output", "", "output .go file")
-
-func main() {
-	flag.Parse()
-
-	if *inputFile == "" || *outputFile == "" {
-		log.Fatalf("Missing parameters")
-	}
-
-	src, err := ioutil.ReadFile(*inputFile)
-	if err != nil {
-		log.Fatal(err)
-	}
-
-	fset := token.NewFileSet()
-	f, err := parser.ParseFile(fset, *inputFile, src, 0)
-	if err != nil {
-		log.Fatal(err)
-	}
-
-	v := &collector{}
-	ast.Walk(v, f)
-
-	goCode := ""
-	cCode := ""
-	for _, g := range v.gens {
-		gc, cc := g.generateImpl()
-
-		goCode += gc
-		cCode += cc
-	}
-
-	goFile := "package python\n"
-	goFile += "/*\n"
-	goFile += "#include \n"
-	goFile += "#include \n"
-	goFile += "#include \n"
-	goFile += cCode
-	goFile += "*/\n"
-	goFile += "import \"C\"\n"
-	goFile += "\n"
-	goFile += "import \"unsafe\"\n"
-	goFile += "import \"github.com/codablock/kluctl/pkg/utils/lib_wrapper\"\n"
-	goFile += goCode
-
-	formatted, err := format.Source([]byte(goFile))
-	if err != nil {
-		log.Fatal(err)
-	}
-
-	err = ioutil.WriteFile(*outputFile, []byte(formatted), 0o600)
-	if err != nil {
-		log.Fatal(err)
-	}
-}
-
-type collector struct {
-	gens []*generator
-}
-
-func (v *collector) Visit(n ast.Node) ast.Visitor {
-	ce, ok := n.(*ast.TypeSpec)
-	if !ok {
-		return v
-	}
-	it, ok := ce.Type.(*ast.InterfaceType)
-	if !ok {
-		return v
-	}
-
-	v.gens = append(v.gens, &generator{
-		typ:   ce,
-		iface: it,
-	})
-
-	return v
-}
-
-type generator struct {
-	typ   *ast.TypeSpec
-	iface *ast.InterfaceType
-}
-
-func (g *generator) generateImpl() (string, string) {
-
-	structDecls := "module *lib_wrapper.LibWrapper\n"
-	initCalls := ""
-	goFuncs := ""
-	cFuncs := ""
-
-	for _, m := range g.iface.Methods.List {
-		ft, _ := m.Type.(*ast.FuncType)
-
-		structDecls += fmt.Sprintf("_func_%s lib_wrapper.FunctionPtr\n", m.Names[0].String())
-		initCalls += fmt.Sprintf("w._func_%s = w.module.GetFunc(\"%s\")\n", m.Names[0].String(), m.Names[0].String())
-		goFuncs += g.generateGoFunc(m.Names[0].String(), ft)
-		cFuncs += g.generateCFunc(m.Names[0].String(), ft)
-	}
-
-	var goCode string
-
-	goCode += fmt.Sprintf("type %sImpl struct {\n", g.typ.Name.String())
-	goCode += indent(structDecls, 1)
-	goCode += "}\n"
-
-	goCode += fmt.Sprintf("func New_%s(module *lib_wrapper.LibWrapper) %s {\n", g.typ.Name.String(), g.typ.Name.String())
-	goCode += fmt.Sprintf("\tw := &%sImpl{module: module}\n", g.typ.Name.String())
-	goCode += indent(initCalls, 1)
-	goCode += "\treturn w\n"
-	goCode += "}\n"
-
-	goCode += goFuncs
-
-	return goCode, cFuncs
-}
-
-func exprToString(e ast.Expr) string {
-	if x, ok := e.(*ast.Ident); ok {
-		return x.String()
-	} else if x, ok := e.(*ast.SelectorExpr); ok {
-		return fmt.Sprintf("%s.%s", exprToString(x.X), exprToString(x.Sel))
-	} else if x, ok := e.(*ast.ArrayType); ok {
-		return fmt.Sprintf("[]%s", exprToString(x.Elt))
-	} else if x, ok := e.(*ast.StarExpr); ok {
-		return fmt.Sprintf("*%s", x.X)
-	}
-	log.Fatalf("unknown type %s", reflect.TypeOf(e).String())
-	return ""
-}
-
-func fieldListToString(fl *ast.FieldList) string {
-	var args []string
-	for _, p := range fl.List {
-		var names []string
-		for _, n := range p.Names {
-			names = append(names, n.String())
-		}
-		var a string
-		if len(names) == 0 {
-			a = exprToString(p.Type)
-		} else {
-			a = fmt.Sprintf("%s %s", strings.Join(names, ", "), exprToString(p.Type))
-		}
-		args = append(args, a)
-	}
-	return strings.Join(args, ", ")
-}
-
-func (g *generator) generateGoFunc(name string, m *ast.FuncType) string {
-	implName := g.typ.Name.String() + "Impl"
-
-	funcStr := fmt.Sprintf("func (w* %s) %s(%s)", implName, name, fieldListToString(m.Params))
-
-	if m.Results != nil && len(m.Results.List) != 0 {
-		if len(m.Results.List) != 1 || len(m.Results.List[0].Names) != 0 {
-			log.Fatalf("%s: only one result allowed", name)
-		}
-		funcStr += fmt.Sprintf(" (%s)", fieldListToString(m.Results))
-	}
-
-	funcStr += " {\n"
-	funcStr += indent(g.generateTrampolineCall(name, m), 1)
-	funcStr += "}\n"
-
-	return funcStr
-}
-
-func (g *generator) generateTrampolineCall(name string, m *ast.FuncType) string {
-	result := ""
-
-	funcArgs := []string{fmt.Sprintf("unsafe.Pointer(w._func_%s)", name)}
-	for _, p := range m.Params.List {
-		for _, n := range p.Names {
-			t := exprToString(p.Type)
-			a, stmt := g.generateToCConversion(n.String(), n.String(), t)
-			result += stmt
-			funcArgs = append(funcArgs, a...)
-		}
-	}
-	call := fmt.Sprintf("C._trampoline_%s(%s)", name, strings.Join(funcArgs, ", "))
-
-	if m.Results != nil && len(m.Results.List) != 0 {
-		t := exprToString(m.Results.List[0].Type)
-		a, stmt := g.generateToGoConversion("ret", call, t)
-		result += stmt
-		result += fmt.Sprintf("return %s\n", a)
-	} else {
-		result += fmt.Sprintf("%s\n", call)
-	}
-	return result
-}
-
-func (g *generator) generateToCConversion(name string, a string, t string) ([]string, string) {
-	cn := fmt.Sprintf("_c_%s", name)
-	switch t {
-	case "int", "ssize_t":
-		stmt := fmt.Sprintf("%s := C.%s(%s)\n", cn, t, a)
-		return []string{cn}, stmt
-	case "string":
-		stmt := fmt.Sprintf("%s := C.CString(%s)\n", cn, a)
-		stmt += fmt.Sprintf("defer C.free(unsafe.Pointer(%s))\n", cn)
-		return []string{cn}, stmt
-	case "unsafe.Pointer":
-		return []string{a}, ""
-	}
-	if strings.HasPrefix(t, "[]") {
-		t = t[2:]
-		stmt := fmt.Sprintf("%s_ := %s\n", cn, a)
-		if t == "unsafe.Pointer" {
-			stmt += fmt.Sprintf("var %s *unsafe.Pointer\n", cn)
-			stmt += fmt.Sprintf("if len(%s_) != 0 { %s = &%s_[0] }\n", cn, cn, cn)
-		} else {
-			stmt += fmt.Sprintf("var %s *unsafe.Pointer\n", cn)
-			stmt += fmt.Sprintf("%s__ := make([]unsafe.Pointer, len(%s_), len(%s_))\n", cn, cn, cn)
-			stmt += fmt.Sprintf("for i, _ := range %s_ { %s__[i] = %s_[i].GetPointer() }\n", cn, cn, cn)
-			stmt += fmt.Sprintf("if len(%s_) != 0 { %s = &%s__[0] }\n", cn, cn, cn)
-		}
-		if strings.HasSuffix(name, "_vargs") {
-			stmt += fmt.Sprintf("%s_len := len(%s_)\n", cn, cn)
-			return []string{fmt.Sprintf("C.int(%s_len)", cn), cn}, stmt
-		} else {
-			return []string{cn}, stmt
-		}
-	}
-	if strings.HasPrefix(t, "*") {
-		stmt := fmt.Sprintf("%s := (%s).GetPointer()\n", cn, a)
-		return []string{cn}, stmt
-	}
-	log.Fatalf("unknown type %s", t)
-	return nil, ""
-}
-
-func (g *generator) generateToGoConversion(name string, a string, t string) (string, string) {
-	gn := fmt.Sprintf("_g_%s", name)
-	switch t {
-	case "int", "ssize_t":
-		stmt := fmt.Sprintf("%s := int(%s)\n", gn, a)
-		return gn, stmt
-	case "string":
-		stmt := fmt.Sprintf("%s := C.GoString(%s)\n", gn, a)
-		return gn, stmt
-	case "unsafe.Pointer":
-		stmt := fmt.Sprintf("%s := unsafe.Pointer(%s)\n", gn, a)
-		return gn, stmt
-	}
-	if strings.HasPrefix(t, "*") {
-		stmt := fmt.Sprintf("%s := %s_FromPointer(%s)\n", gn, t[1:], a)
-		return gn, stmt
-	}
-	log.Fatalf("unknown type %s", t)
-	return "", ""
-}
-
-func (g *generator) generateCFunc(name string, m *ast.FuncType) string {
-	returnType := "void"
-	returnPrefix := ""
-	if m.Results != nil && len(m.Results.List) != 0 {
-		if len(m.Results.List) != 1 || len(m.Results.List[0].Names) != 0 {
-			log.Fatalf("%s: only one result allowed", name)
-		}
-		returnType = g.getCType(exprToString(m.Results.List[0].Type))
-		returnPrefix = "return "
-	}
-
-	var paramDecls []string
-	var typedefParams []string
-	var callArgs []string
-	vargs_param := ""
-
-	paramDecls = append(paramDecls, "void* f")
-	for _, p := range m.Params.List {
-		n := p.Names[0].String()
-		t := g.getCType(exprToString(p.Type))
-		if strings.HasSuffix(n, "_vargs") {
-			paramDecls = append(paramDecls, fmt.Sprintf("int %s_len", n), fmt.Sprintf("%s %s", t, n))
-			typedefParams = append(typedefParams, "...")
-			vargs_param = n
-		} else {
-			paramDecls = append(paramDecls, fmt.Sprintf("%s %s", t, n))
-			typedefParams = append(typedefParams, t)
-			callArgs = append(callArgs, n)
-		}
-	}
-
-	funcStr := fmt.Sprintf("%s _trampoline_%s(%s) {\n", returnType, name, strings.Join(paramDecls, ", "))
-	body := fmt.Sprintf("typedef %s (*F)(%s);\n", returnType, strings.Join(typedefParams, ", "))
-	if vargs_param == "" {
-		body += fmt.Sprintf("%s((F)f)(%s);\n", returnPrefix, strings.Join(callArgs, ", "))
-	} else {
-		body += fmt.Sprintf("switch(%s_len) {\n", vargs_param)
-		for i := 0; i < 10; i++ {
-			body += fmt.Sprintf("case %d: %s((F)f)(%s);\n", i, returnPrefix, strings.Join(callArgs, ", "))
-			callArgs = append(callArgs, fmt.Sprintf("%s[%d]", vargs_param, i))
-		}
-		body += "default: assert(0);\n"
-		body += "}\n"
-	}
-	funcStr += indent(body, 1)
-	funcStr += "}\n"
-	return funcStr
-}
-
-func (g *generator) getCType(t string) string {
-	switch t {
-	case "int", "ssize_t":
-		return t
-	case "string":
-		return "char*"
-	case "unsafe.Pointer":
-		return "void*"
-	}
-	if strings.HasPrefix(t, "[]") {
-		return "void**"
-	}
-	if strings.HasPrefix(t, "*") {
-		return "void*"
-	}
-	log.Fatalf("unknown type %s", t)
-	return ""
-}
-
-func indent(s string, i int) string {
-	l := strings.Split(s, "\n")
-	is := strings.Repeat("\t", i)
-	for i, _ := range l {
-		if l[i] != "" {
-			l[i] = is + l[i]
-		}
-	}
-	return strings.Join(l, "\n")
-}
diff --git a/pkg/utils/lib_wrapper/lib_wrapper.go b/pkg/utils/lib_wrapper/lib_wrapper.go
deleted file mode 100644
index 78cb3ddd7..000000000
--- a/pkg/utils/lib_wrapper/lib_wrapper.go
+++ /dev/null
@@ -1,27 +0,0 @@
-package lib_wrapper
-
-import (
-	"log"
-	"sync"
-	"unsafe"
-)
-
-type FunctionPtr unsafe.Pointer
-
-type LibWrapper struct {
-	module  unsafe.Pointer
-	funcMap sync.Map
-}
-
-func (lw *LibWrapper) GetFunc(funcName string) FunctionPtr {
-	f, ok := lw.funcMap.Load(funcName)
-	if !ok {
-		f = lw.loadFunc(funcName)
-		f, _ = lw.funcMap.LoadOrStore(funcName, f)
-	}
-	f2, ok := f.(FunctionPtr)
-	if !ok {
-		log.Panicf("f2 != FunctionPtr")
-	}
-	return f2
-}
diff --git a/pkg/utils/lib_wrapper/lib_wrapper_unix.go b/pkg/utils/lib_wrapper/lib_wrapper_unix.go
deleted file mode 100644
index 76a549918..000000000
--- a/pkg/utils/lib_wrapper/lib_wrapper_unix.go
+++ /dev/null
@@ -1,39 +0,0 @@
-//go:build darwin || linux
-// +build darwin linux
-
-package lib_wrapper
-
-/*
-#cgo LDFLAGS: -ldl
-#include 
-#include 
-*/
-import "C"
-import (
-	log "github.com/sirupsen/logrus"
-	"unsafe"
-)
-
-func LoadModule(pth string) *LibWrapper {
-	cPth := C.CString(pth)
-	defer C.free(unsafe.Pointer(cPth))
-
-	mod := C.dlopen(cPth, C.RTLD_LAZY|C.RTLD_GLOBAL)
-	if mod == nil {
-		log.Panicf("dlopen for %s failed", pth)
-	}
-	return &LibWrapper{
-		module: unsafe.Pointer(mod),
-	}
-}
-
-func (lw *LibWrapper) loadFunc(funcName string) FunctionPtr {
-	cFuncName := C.CString(funcName)
-	defer C.free(unsafe.Pointer(cFuncName))
-
-	f := C.dlsym(lw.module, cFuncName)
-	if f == nil {
-		log.Panicf("dlsym for %s failed", funcName)
-	}
-	return FunctionPtr(f)
-}
diff --git a/pkg/utils/lib_wrapper/lib_wrapper_windows.go b/pkg/utils/lib_wrapper/lib_wrapper_windows.go
deleted file mode 100644
index 8020e53bf..000000000
--- a/pkg/utils/lib_wrapper/lib_wrapper_windows.go
+++ /dev/null
@@ -1,30 +0,0 @@
-//go:build windows
-// +build windows
-
-package lib_wrapper
-
-import "C"
-import (
-	log "github.com/sirupsen/logrus"
-	"syscall"
-	"unsafe"
-)
-
-func LoadModule(pth string) *LibWrapper {
-	mod, err := syscall.LoadLibrary(pth)
-	if err != nil {
-		log.Panicf("LoadLibrary for %s failed: %v", pth, err)
-	}
-	return &LibWrapper{
-		module: unsafe.Pointer(mod),
-	}
-}
-
-func (lw *LibWrapper) loadFunc(funcName string) FunctionPtr {
-	mod := syscall.Handle(lw.module)
-	f, err := syscall.GetProcAddress(mod, funcName)
-	if err != nil {
-		log.Panicf("GetProcAddress for %s failed", funcName)
-	}
-	return FunctionPtr(f)
-}
diff --git a/pkg/utils/tar.go b/pkg/utils/tar.go
index 4b92b00c2..22a39dfd1 100644
--- a/pkg/utils/tar.go
+++ b/pkg/utils/tar.go
@@ -47,11 +47,16 @@ func ExtractTarGzStream(r io.Reader, targetPath string) error {
 
 		switch header.Typeflag {
 		case tar.TypeDir:
-			if err := os.Mkdir(filepath.Join(targetPath, header.Name), 0755); err != nil {
+			if err := os.MkdirAll(filepath.Join(targetPath, header.Name), 0755); err != nil {
 				return fmt.Errorf("ExtractTarGz: Mkdir() failed: %w", err)
 			}
 		case tar.TypeReg:
-			outFile, err := os.Create(filepath.Join(targetPath, header.Name))
+			p := filepath.Join(targetPath, header.Name)
+			err := os.MkdirAll(filepath.Dir(p), 0755)
+			if err != nil {
+				return err
+			}
+			outFile, err := os.Create(p)
 			if err != nil {
 				return fmt.Errorf("ExtractTarGz: Create() failed: %w", err)
 			}
@@ -60,6 +65,10 @@ func ExtractTarGzStream(r io.Reader, targetPath string) error {
 			if err != nil {
 				return fmt.Errorf("ExtractTarGz: Copy() failed: %w", err)
 			}
+			err = os.Chmod(p, header.FileInfo().Mode())
+			if err != nil {
+				return fmt.Errorf("ExtractTarGz: Chmod() failed: %w", err)
+			}
 		case tar.TypeSymlink:
 			if err := os.Symlink(header.Linkname, filepath.Join(targetPath, header.Name)); err != nil {
 				return fmt.Errorf("ExtractTarGz: Symlink() failed: %w", err)

From 1bf9b1b935ef87f902d9c12a1469c42386f808ea Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Sat, 19 Mar 2022 01:12:01 +0100
Subject: [PATCH 0552/2916] ci: Only run build once

---
 .github/workflows/build-and-release.yml | 11 +----------
 1 file changed, 1 insertion(+), 10 deletions(-)

diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml
index 947f1f093..3e5697a59 100644
--- a/.github/workflows/build-and-release.yml
+++ b/.github/workflows/build-and-release.yml
@@ -66,16 +66,7 @@ jobs:
             CHANGELOG.md
 
   build:
-    strategy:
-      fail-fast: false
-      matrix:
-        include:
-          - os: ubuntu-18.04
-            binary-suffix: linux-amd64
-          - os: macos-10.15
-            binary-suffix: darwin-amd64
-        os: [ubuntu-18.04, macos-10.15, windows-2019]
-    runs-on: ${{ matrix.os }}
+    runs-on: ubuntu-latest
     needs:
       - version
     steps:

From 0ee0eb8dfea41ac51a0711f17fb6931800fdf7af Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Sat, 19 Mar 2022 01:13:40 +0100
Subject: [PATCH 0553/2916] ci: Install zstd

---
 .github/workflows/build-and-release.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml
index 3e5697a59..b93934428 100644
--- a/.github/workflows/build-and-release.yml
+++ b/.github/workflows/build-and-release.yml
@@ -23,7 +23,7 @@ jobs:
       - name: Install packages
         run: |
           sudo apt update
-          sudo apt install -y git
+          sudo apt install -y git zstd
       - name: Checkout
         uses: actions/checkout@v2
         with:

From 60deadbd54a0c480677909836936a854ce96a409 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Sat, 19 Mar 2022 01:17:59 +0100
Subject: [PATCH 0554/2916] ci: Split go build and invoke generate before build

---
 .github/workflows/build-and-release.yml | 20 +++++++++++++++++---
 1 file changed, 17 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml
index b93934428..6bbeb63dd 100644
--- a/.github/workflows/build-and-release.yml
+++ b/.github/workflows/build-and-release.yml
@@ -104,11 +104,25 @@ jobs:
         run: |
           export CGO_ENABLED=0
           export GOARCH=amd64
-          GOOS=linux go build -ldflags "-s -w" ./cmd/kluctl
+          export GOOS=linux
+          go generate ./pkg/python
+          go build -ldflags "-s -w" ./cmd/kluctl
           mv kluctl kluctl-linux-amd64
-          GOOS=darwin go build -ldflags "-s -w" ./cmd/kluctl
+      - name: Build kluctl (darwin)
+        run: |
+          export CGO_ENABLED=0
+          export GOARCH=amd64
+          export GOOS=darwin
+          go generate ./pkg/python
+          go build -ldflags "-s -w" ./cmd/kluctl
           mv kluctl kluctl-darwin-amd64
-          GOOS=windows go build -ldflags "-s -w" ./cmd/kluctl
+      - name: Build kluctl (windows)
+        run: |
+          export CGO_ENABLED=0
+          export GOARCH=amd64
+          export GOOS=windows
+          go generate ./pkg/python
+          go build -ldflags "-s -w" ./cmd/kluctl
           mv kluctl.exe kluctl-windows-amd64.exe
       - name: Upload dist artifact
         uses: actions/upload-artifact@v2

From 4261bdca1988b9340bd1dc16cf386898c1997acb Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Sat, 19 Mar 2022 01:23:45 +0100
Subject: [PATCH 0555/2916] ci: Fix go generate for python

---
 .github/workflows/build-and-release.yml | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml
index 6bbeb63dd..be7e27196 100644
--- a/.github/workflows/build-and-release.yml
+++ b/.github/workflows/build-and-release.yml
@@ -23,7 +23,7 @@ jobs:
       - name: Install packages
         run: |
           sudo apt update
-          sudo apt install -y git zstd
+          sudo apt install -y git
       - name: Checkout
         uses: actions/checkout@v2
         with:
@@ -100,12 +100,13 @@ jobs:
       - name: Go Generate
         run: |
           go generate ./...
+          go generate -tags darwin ./pkg/python
+          go generate -tags windows ./pkg/python
       - name: Build kluctl (linux)
         run: |
           export CGO_ENABLED=0
           export GOARCH=amd64
           export GOOS=linux
-          go generate ./pkg/python
           go build -ldflags "-s -w" ./cmd/kluctl
           mv kluctl kluctl-linux-amd64
       - name: Build kluctl (darwin)
@@ -113,7 +114,6 @@ jobs:
           export CGO_ENABLED=0
           export GOARCH=amd64
           export GOOS=darwin
-          go generate ./pkg/python
           go build -ldflags "-s -w" ./cmd/kluctl
           mv kluctl kluctl-darwin-amd64
       - name: Build kluctl (windows)
@@ -121,7 +121,6 @@ jobs:
           export CGO_ENABLED=0
           export GOARCH=amd64
           export GOOS=windows
-          go generate ./pkg/python
           go build -ldflags "-s -w" ./cmd/kluctl
           mv kluctl.exe kluctl-windows-amd64.exe
       - name: Upload dist artifact

From de07d197a38feafe32eea5d5f3cf156b3c4c5ea9 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Sun, 20 Mar 2022 23:01:06 +0100
Subject: [PATCH 0556/2916] fix: Include versiones python .so files

---
 pkg/python/embed_linux.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pkg/python/embed_linux.go b/pkg/python/embed_linux.go
index 6299f3562..db8d55096 100644
--- a/pkg/python/embed_linux.go
+++ b/pkg/python/embed_linux.go
@@ -2,7 +2,7 @@ package python
 
 import "embed"
 
-//go:generate go run ../utils/embed_util/packer python-linux.tar.gz ../../download-python/linux/python/install bin lib/*.so lib/python3.*
+//go:generate go run ../utils/embed_util/packer python-linux.tar.gz ../../download-python/linux/python/install bin lib/*.so* lib/python3.*
 
 //go:embed python-linux.tar.gz python-linux.tar.gz.files
 var pythonLib embed.FS

From e060e579fce9ebb89a8a618436bfb0315b4a4059 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Sun, 20 Mar 2022 23:01:45 +0100
Subject: [PATCH 0557/2916] fix: Use Lstat instead of stat in AddToTar

---
 pkg/utils/tar.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pkg/utils/tar.go b/pkg/utils/tar.go
index 22a39dfd1..9ddfba8fe 100644
--- a/pkg/utils/tar.go
+++ b/pkg/utils/tar.go
@@ -81,7 +81,7 @@ func ExtractTarGzStream(r io.Reader, targetPath string) error {
 }
 
 func AddToTar(tw *tar.Writer, pth string, name string, filter func(h *tar.Header) (*tar.Header, error)) error {
-	fi, err := os.Stat(pth)
+	fi, err := os.Lstat(pth)
 	if err != nil {
 		return err
 	}

From e2496cd1810156829ef86478476ec46cd3a00d59 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Sun, 20 Mar 2022 23:16:09 +0100
Subject: [PATCH 0558/2916] fix: Fix symlink creation in ExtractTarGzStream

---
 pkg/utils/tar.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pkg/utils/tar.go b/pkg/utils/tar.go
index 9ddfba8fe..ff9a37690 100644
--- a/pkg/utils/tar.go
+++ b/pkg/utils/tar.go
@@ -70,7 +70,7 @@ func ExtractTarGzStream(r io.Reader, targetPath string) error {
 				return fmt.Errorf("ExtractTarGz: Chmod() failed: %w", err)
 			}
 		case tar.TypeSymlink:
-			if err := os.Symlink(header.Linkname, filepath.Join(targetPath, header.Name)); err != nil {
+			if err := os.Symlink(filepath.Join(targetPath, header.Name), header.Linkname); err != nil {
 				return fmt.Errorf("ExtractTarGz: Symlink() failed: %w", err)
 			}
 		default:

From 2cb3dca800c8af9d679bd2d7486c7328dcde54aa Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Sun, 20 Mar 2022 23:40:19 +0100
Subject: [PATCH 0559/2916] Revert "fix: Fix symlink creation in
 ExtractTarGzStream"

This reverts commit e2496cd1810156829ef86478476ec46cd3a00d59.
---
 pkg/utils/tar.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pkg/utils/tar.go b/pkg/utils/tar.go
index ff9a37690..9ddfba8fe 100644
--- a/pkg/utils/tar.go
+++ b/pkg/utils/tar.go
@@ -70,7 +70,7 @@ func ExtractTarGzStream(r io.Reader, targetPath string) error {
 				return fmt.Errorf("ExtractTarGz: Chmod() failed: %w", err)
 			}
 		case tar.TypeSymlink:
-			if err := os.Symlink(filepath.Join(targetPath, header.Name), header.Linkname); err != nil {
+			if err := os.Symlink(header.Linkname, filepath.Join(targetPath, header.Name)); err != nil {
 				return fmt.Errorf("ExtractTarGz: Symlink() failed: %w", err)
 			}
 		default:

From 6aea465ee33c306152f59e395f6b5cb09877e617 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Sun, 20 Mar 2022 23:53:23 +0100
Subject: [PATCH 0560/2916] fix: More symlink related fixes

---
 pkg/kluctl_project/load.go          | 2 +-
 pkg/utils/embed_util/extract.go     | 2 +-
 pkg/utils/embed_util/packer/main.go | 8 ++------
 pkg/utils/tar.go                    | 8 ++++++--
 4 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/pkg/kluctl_project/load.go b/pkg/kluctl_project/load.go
index 84467a545..fba1e6668 100644
--- a/pkg/kluctl_project/load.go
+++ b/pkg/kluctl_project/load.go
@@ -218,7 +218,7 @@ func (c *KluctlProjectContext) CreateTGZArchive(archivePath string, metadataPath
 	tw := tar.NewWriter(gz)
 	defer tw.Close()
 
-	filter := func(h *tar.Header) (*tar.Header, error) {
+	filter := func(h *tar.Header, size int64) (*tar.Header, error) {
 		if strings.HasSuffix(strings.ToLower(h.Name), ".git") {
 			return nil, nil
 		}
diff --git a/pkg/utils/embed_util/extract.go b/pkg/utils/embed_util/extract.go
index 281d36400..34bcc5edc 100644
--- a/pkg/utils/embed_util/extract.go
+++ b/pkg/utils/embed_util/extract.go
@@ -89,7 +89,7 @@ func checkExtractNeeded(targetPath string, fileListStr string) (bool, string, er
 
 	existingFiles := make(map[string]int64)
 	err = filepath.Walk(targetPath, func(path string, info fs.FileInfo, err error) error {
-		if !info.Mode().IsRegular() && !info.IsDir() {
+		if !info.Mode().IsRegular() && info.Mode().Type() != fs.ModeSymlink && info.Mode().Type() != fs.ModeDir {
 			return nil
 		}
 		relPath, err := filepath.Rel(targetPath, path)
diff --git a/pkg/utils/embed_util/packer/main.go b/pkg/utils/embed_util/packer/main.go
index f0869a0e0..fa86f4ddf 100644
--- a/pkg/utils/embed_util/packer/main.go
+++ b/pkg/utils/embed_util/packer/main.go
@@ -79,18 +79,14 @@ func writeTar(dir string, patterns []string) ([]string, []byte) {
 	var fileList []string
 
 	for _, d := range rootNames {
-		err := utils.AddToTar(t, filepath.Join(dir, d), d, func(h *tar.Header) (*tar.Header, error) {
+		err := utils.AddToTar(t, filepath.Join(dir, d), d, func(h *tar.Header, size int64) (*tar.Header, error) {
 			for _, e := range excludes {
 				if e.Match(h.Name) {
 					return nil, nil
 				}
 			}
 
-			s := h.Size
-			if h.FileInfo().IsDir() {
-				s = 0
-			}
-			fileList = append(fileList, fmt.Sprintf("%s: %d", h.Name, s))
+			fileList = append(fileList, fmt.Sprintf("%s: %d", h.Name, size))
 			return h, nil
 		})
 		if err != nil {
diff --git a/pkg/utils/tar.go b/pkg/utils/tar.go
index 9ddfba8fe..45ea3cfd7 100644
--- a/pkg/utils/tar.go
+++ b/pkg/utils/tar.go
@@ -80,7 +80,7 @@ func ExtractTarGzStream(r io.Reader, targetPath string) error {
 	return nil
 }
 
-func AddToTar(tw *tar.Writer, pth string, name string, filter func(h *tar.Header) (*tar.Header, error)) error {
+func AddToTar(tw *tar.Writer, pth string, name string, filter func(h *tar.Header, size int64) (*tar.Header, error)) error {
 	fi, err := os.Lstat(pth)
 	if err != nil {
 		return err
@@ -102,7 +102,11 @@ func AddToTar(tw *tar.Writer, pth string, name string, filter func(h *tar.Header
 	h.Name = strings.ReplaceAll(name, string(os.PathSeparator), "/")
 
 	if filter != nil {
-		h, err = filter(h)
+		s := fi.Size()
+		if fi.IsDir() {
+			s = 0
+		}
+		h, err = filter(h, s)
 		if err != nil {
 			return err
 		}

From 2ed0c0d77ebe8b6120b2912fa2c94012661d6578 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Mon, 21 Mar 2022 00:13:00 +0100
Subject: [PATCH 0561/2916] fix: Create parent dir for all tar entry types

---
 pkg/utils/tar.go | 15 ++++++++-------
 1 file changed, 8 insertions(+), 7 deletions(-)

diff --git a/pkg/utils/tar.go b/pkg/utils/tar.go
index 45ea3cfd7..17c085845 100644
--- a/pkg/utils/tar.go
+++ b/pkg/utils/tar.go
@@ -45,17 +45,18 @@ func ExtractTarGzStream(r io.Reader, targetPath string) error {
 
 		header.Name = strings.ReplaceAll(header.Name, "/", string(os.PathSeparator))
 
+		p := filepath.Join(targetPath, header.Name)
+		err = os.MkdirAll(filepath.Dir(p), 0755)
+		if err != nil {
+			return err
+		}
+
 		switch header.Typeflag {
 		case tar.TypeDir:
-			if err := os.MkdirAll(filepath.Join(targetPath, header.Name), 0755); err != nil {
+			if err := os.MkdirAll(p, 0755); err != nil {
 				return fmt.Errorf("ExtractTarGz: Mkdir() failed: %w", err)
 			}
 		case tar.TypeReg:
-			p := filepath.Join(targetPath, header.Name)
-			err := os.MkdirAll(filepath.Dir(p), 0755)
-			if err != nil {
-				return err
-			}
 			outFile, err := os.Create(p)
 			if err != nil {
 				return fmt.Errorf("ExtractTarGz: Create() failed: %w", err)
@@ -70,7 +71,7 @@ func ExtractTarGzStream(r io.Reader, targetPath string) error {
 				return fmt.Errorf("ExtractTarGz: Chmod() failed: %w", err)
 			}
 		case tar.TypeSymlink:
-			if err := os.Symlink(header.Linkname, filepath.Join(targetPath, header.Name)); err != nil {
+			if err := os.Symlink(header.Linkname, p); err != nil {
 				return fmt.Errorf("ExtractTarGz: Symlink() failed: %w", err)
 			}
 		default:

From bba059246cc02c363d435e2e59e0514dcb0104c2 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Mon, 21 Mar 2022 00:15:11 +0100
Subject: [PATCH 0562/2916] fix: Fix PythonCmd for windows (use python.exe
 instead of python3.exe)

---
 pkg/python/cmd.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pkg/python/cmd.go b/pkg/python/cmd.go
index 2ba8d7765..eea32c554 100644
--- a/pkg/python/cmd.go
+++ b/pkg/python/cmd.go
@@ -11,7 +11,7 @@ import (
 func PythonCmd(args []string) *exec.Cmd {
 	var exePath string
 	if runtime.GOOS == "windows" {
-		exePath = filepath.Join(embeddedPythonPath, "python3.exe")
+		exePath = filepath.Join(embeddedPythonPath, "python.exe")
 	} else {
 		exePath = filepath.Join(embeddedPythonPath, "bin/python3")
 	}

From e8195b493faefa0fb071e17d1eb67d471cd44ae7 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Mon, 21 Mar 2022 08:39:25 +0100
Subject: [PATCH 0563/2916] fix: No need to extra to .tmp dir as we're using a
 flock now

---
 pkg/utils/embed_util/extract.go | 12 +++++-------
 1 file changed, 5 insertions(+), 7 deletions(-)

diff --git a/pkg/utils/embed_util/extract.go b/pkg/utils/embed_util/extract.go
index 34bcc5edc..6fd89dbcf 100644
--- a/pkg/utils/embed_util/extract.go
+++ b/pkg/utils/embed_util/extract.go
@@ -33,24 +33,22 @@ func ExtractTarToTmp(r io.Reader, fileListR io.Reader, targetPath string) error
 		return nil
 	}
 
-	tmpTargetPath := targetPath + ".tmp"
-	err = os.MkdirAll(tmpTargetPath, 0o700)
-	if err != nil {
+	err = os.RemoveAll(targetPath)
+	if err != nil && !os.IsNotExist(err) {
 		return err
 	}
 
-	err = utils.ExtractTarGzStream(r, tmpTargetPath)
+	err = os.MkdirAll(targetPath, 0o700)
 	if err != nil {
 		return err
 	}
 
-	err = ioutil.WriteFile(filepath.Join(tmpTargetPath, ".tar-gz-hash"), []byte(expectedTarGzHash), 0o600)
+	err = utils.ExtractTarGzStream(r, targetPath)
 	if err != nil {
 		return err
 	}
 
-	_ = os.RemoveAll(targetPath)
-	err = os.Rename(tmpTargetPath, targetPath)
+	err = ioutil.WriteFile(filepath.Join(targetPath, ".tar-gz-hash"), []byte(expectedTarGzHash), 0o600)
 	if err != nil {
 		return err
 	}

From d33e8ed18cbed9ff2031bf5d270b3d52d91860b4 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Mon, 21 Mar 2022 09:22:42 +0100
Subject: [PATCH 0564/2916] fix: Reintroduce hash as part tmp dir in embedded
 tar extraction

---
 pkg/utils/embed_util/extract.go     | 10 +++++++---
 pkg/utils/embed_util/packer/main.go |  7 ++++++-
 pkg/utils/tar.go                    | 29 +++++++++++++++++++++++++++++
 pkg/utils/utils.go                  |  6 +++++-
 4 files changed, 47 insertions(+), 5 deletions(-)

diff --git a/pkg/utils/embed_util/extract.go b/pkg/utils/embed_util/extract.go
index 6fd89dbcf..c4623d012 100644
--- a/pkg/utils/embed_util/extract.go
+++ b/pkg/utils/embed_util/extract.go
@@ -1,6 +1,7 @@
 package embed_util
 
 import (
+	"fmt"
 	"github.com/codablock/kluctl/pkg/utils"
 	"github.com/gofrs/flock"
 	"io"
@@ -12,12 +13,14 @@ import (
 	"strings"
 )
 
-func ExtractTarToTmp(r io.Reader, fileListR io.Reader, targetPath string) error {
+func ExtractTarToTmp(r io.Reader, fileListR io.Reader, targetPrefix string) error {
 	fileList, err := ioutil.ReadAll(fileListR)
 	if err != nil {
 		return err
 	}
 
+	targetPath := fmt.Sprintf("%s-%s", utils.Sha256Bytes(fileList)[:16])
+
 	fl := flock.New(targetPath + ".lock")
 	err = fl.Lock()
 	if err != nil {
@@ -76,9 +79,10 @@ func checkExtractNeeded(targetPath string, fileListStr string) (bool, string, er
 
 	tarFilesMap := make(map[string]int64)
 	for _, l := range fileList {
-		s := strings.Split(l, ":")
+		s := strings.SplitN(l, ":", 2)
 		fname := strings.TrimSpace(s[0])
-		size, err := strconv.ParseInt(strings.TrimSpace(s[1]), 10, 64)
+		sh := strings.SplitN(strings.TrimSpace(s[1]), " ", 2)
+		size, err := strconv.ParseInt(strings.TrimSpace(sh[0]), 10, 64)
 		if err != nil {
 			return false, expectedHash, err
 		}
diff --git a/pkg/utils/embed_util/packer/main.go b/pkg/utils/embed_util/packer/main.go
index fa86f4ddf..a0f347b15 100644
--- a/pkg/utils/embed_util/packer/main.go
+++ b/pkg/utils/embed_util/packer/main.go
@@ -86,7 +86,12 @@ func writeTar(dir string, patterns []string) ([]string, []byte) {
 				}
 			}
 
-			fileList = append(fileList, fmt.Sprintf("%s: %d", h.Name, size))
+			hashStr, err := utils.HashTarEntry(dir, h.Name)
+			if err != nil {
+				return nil, err
+			}
+
+			fileList = append(fileList, fmt.Sprintf("%s: %d %s", h.Name, size, hashStr))
 			return h, nil
 		})
 		if err != nil {
diff --git a/pkg/utils/tar.go b/pkg/utils/tar.go
index 17c085845..e3bfdba6c 100644
--- a/pkg/utils/tar.go
+++ b/pkg/utils/tar.go
@@ -6,6 +6,7 @@ import (
 	"fmt"
 	"io"
 	"io/fs"
+	"io/ioutil"
 	"os"
 	"path/filepath"
 	"strings"
@@ -152,3 +153,31 @@ func AddToTar(tw *tar.Writer, pth string, name string, filter func(h *tar.Header
 		return fmt.Errorf("unsupported file type/mode %s", fi.Mode().String())
 	}
 }
+
+func HashTarEntry(dir string, name string) (string, error) {
+	p := filepath.Join(dir, strings.ReplaceAll(name, "/", string(os.PathSeparator)))
+	st, err := os.Lstat(p)
+	if err != nil {
+		return "", err
+	}
+	var hashData []byte
+	if st.Mode().Type() == fs.ModeDir {
+		hashData = []byte(strings.ReplaceAll(name, string(os.PathSeparator), "/"))
+	} else if st.Mode().Type() == fs.ModeSymlink {
+		l, err := os.Readlink(p)
+		if err != nil {
+			return "", err
+		}
+		hashData = []byte(l)
+	} else if st.Mode().IsRegular() {
+		var err error
+		hashData, err = ioutil.ReadFile(p)
+		if err != nil {
+			return "", err
+		}
+	} else {
+		return "", fmt.Errorf("unknown type %s", st.Mode().Type())
+	}
+	hashStr := Sha256Bytes(hashData)
+	return hashStr, nil
+}
\ No newline at end of file
diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go
index 9a6a0ecf0..632d84706 100644
--- a/pkg/utils/utils.go
+++ b/pkg/utils/utils.go
@@ -25,7 +25,11 @@ func GetTmpBaseDir() string {
 }
 
 func Sha256String(data string) string {
-	h := sha256.Sum256([]byte(data))
+	return Sha256Bytes([]byte(data))
+}
+
+func Sha256Bytes(data []byte) string {
+	h := sha256.Sum256(data)
 	return hex.EncodeToString(h[:])
 }
 

From 1324945d597193974cffbe07da3345e48f337492 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Mon, 21 Mar 2022 09:46:06 +0100
Subject: [PATCH 0565/2916] test: Less verbose git http server

---
 pkg/git/http-server/http.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pkg/git/http-server/http.go b/pkg/git/http-server/http.go
index ef5eab9ca..6bc654462 100644
--- a/pkg/git/http-server/http.go
+++ b/pkg/git/http-server/http.go
@@ -55,7 +55,7 @@ func (s *Server) findService(req *http.Request) (*service, string) {
 }
 
 func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	log.Info("request", r.Method+" "+r.Host+r.URL.String())
+	log.Debugf("request %s", r.Method+" "+r.Host+r.URL.String())
 
 	// Find the git subservice to handle the request
 	svc, repoUrlPath := s.findService(r)

From 80aa80c11f7a2364dcdb2bb1d74435acda7d744c Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Mon, 21 Mar 2022 09:46:17 +0100
Subject: [PATCH 0566/2916] fix: Fix missing argument to Sprintf

---
 pkg/utils/embed_util/extract.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pkg/utils/embed_util/extract.go b/pkg/utils/embed_util/extract.go
index c4623d012..c4a3ae727 100644
--- a/pkg/utils/embed_util/extract.go
+++ b/pkg/utils/embed_util/extract.go
@@ -19,7 +19,7 @@ func ExtractTarToTmp(r io.Reader, fileListR io.Reader, targetPrefix string) erro
 		return err
 	}
 
-	targetPath := fmt.Sprintf("%s-%s", utils.Sha256Bytes(fileList)[:16])
+	targetPath := fmt.Sprintf("%s-%s", targetPrefix, utils.Sha256Bytes(fileList)[:16])
 
 	fl := flock.New(targetPath + ".lock")
 	err = fl.Lock()

From c70bddcb364ad520de4e37564d0f6f48a4a50c62 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Mon, 21 Mar 2022 09:46:54 +0100
Subject: [PATCH 0567/2916] ci: Precompile e2e test and then only run them in
 e2e job

---
 .github/workflows/build-and-release.yml | 62 +++++++++----------------
 1 file changed, 23 insertions(+), 39 deletions(-)

diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml
index be7e27196..d56276514 100644
--- a/.github/workflows/build-and-release.yml
+++ b/.github/workflows/build-and-release.yml
@@ -97,32 +97,49 @@ jobs:
       - name: Go Mod Vendor
         run: |
           go mod vendor
+      - name: Check docs
+        run: |
+          go run ./hack/replace-commands-help --commands-md ./docs/commands.md
+
+          if [ "$(git status --porcelain -- docs)" != "" ]; then
+            echo "commands.md is not up-to-date. Run "./hack/replace-commands-help --commands-md ./docs/commands.md" to fix this!"
+            exit 1
+          fi
       - name: Go Generate
         run: |
           go generate ./...
           go generate -tags darwin ./pkg/python
           go generate -tags windows ./pkg/python
+      - name: Run unit tests
+        run: |
+          go test ./cmd/... ./pkg/... -v
       - name: Build kluctl (linux)
         run: |
           export CGO_ENABLED=0
           export GOARCH=amd64
           export GOOS=linux
           go build -ldflags "-s -w" ./cmd/kluctl
+          go test -c ./e2e
           mv kluctl kluctl-linux-amd64
+          mv e2e.test e2e.test-linux-amd64
       - name: Build kluctl (darwin)
         run: |
           export CGO_ENABLED=0
           export GOARCH=amd64
           export GOOS=darwin
           go build -ldflags "-s -w" ./cmd/kluctl
+          go test -c ./e2e
           mv kluctl kluctl-darwin-amd64
+          mv e2e.test e2e.test-darwin-amd64
       - name: Build kluctl (windows)
         run: |
           export CGO_ENABLED=0
           export GOARCH=amd64
           export GOOS=windows
           go build -ldflags "-s -w" ./cmd/kluctl
+          go test -c ./e2e
           mv kluctl.exe kluctl-windows-amd64.exe
+          mv e2e.test e2e.test-windows-amd64.exe
       - name: Upload dist artifact
         uses: actions/upload-artifact@v2
         with:
@@ -131,12 +148,9 @@ jobs:
             kluctl-linux-amd64
             kluctl-darwin-amd64
             kluctl-windows-amd64.exe
-      - name: Upload pkg artifact
-        uses: actions/upload-artifact@v2
-        with:
-          name: pkg
-          path: |
-            pkg
+            e2e.test-linux-amd64
+            e2e.test-darwin-amd64
+            e2e.test-windows-amd64.exe
 
   tests:
     if: "!startsWith(github.ref, 'refs/tags/')"
@@ -159,15 +173,6 @@ jobs:
     steps:
       - name: Checkout
         uses: actions/checkout@v2
-      - uses: actions/setup-go@v2
-        with:
-          go-version: '1.17.6'
-      - uses: actions/cache@v2
-        with:
-          path: ~/go/pkg/mod
-          key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
-          restore-keys: |
-            ${{ runner.os }}-go-
       - name: Setup TOOLS envs
         shell: bash
         run: |
@@ -236,11 +241,6 @@ jobs:
           kubectl version || true
           kind version || true
           helm version || true
-      - name: Download pkg artifacts
-        uses: actions/download-artifact@v2
-        with:
-          name: pkg
-          path: pkg
       - name: Port-forward docker
         shell: bash
         run: |
@@ -264,32 +264,16 @@ jobs:
           echo "KIND_KUBECONFIG=$(pwd)/kind-kubeconfig" >> $GITHUB_ENV
 
           ./hack/start-kind-cluster.sh "$KIND_CLUSTER_NAME" "$PORT"
-      - name: Go Mod Vendor
-        shell: bash
-        run: |
-          go mod vendor
-      - name: Run tests
-        shell: bash
-        run: |
-          go test ./cmd/... ./pkg/... -v
+      - name: Download dist artifacts
+        uses: actions/download-artifact@v2
       - name: Run e2e tests
         shell: bash
         run: |
-          go test ./e2e/... -parallel 4 -v
+          ./e2e.test-${{ matrix.binary-suffix }}$TOOLS_EXE -test.v
       - name: Delete kind cluster
         shell: bash
         run: |
           kind delete cluster --name $KIND_CLUSTER_NAME
-      - name: Check docs
-        if: runner.os != 'Windows'
-        shell: bash
-        run: |
-          go run ./hack/replace-commands-help --commands-md ./docs/commands.md
-
-          if [ "$(git status --porcelain -- docs)" != "" ]; then
-            echo "commands.md is not up-to-date. Run "./hack/replace-commands-help --commands-md ./docs/commands.md" to fix this!"
-            exit 1
-          fi
 
   release:
     runs-on: ubuntu-latest

From 82ff3fb0281ed4c56acd52940c4bd4f626c37f10 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Mon, 21 Mar 2022 09:48:41 +0100
Subject: [PATCH 0568/2916] ci: Run go generate before checking docs

---
 .github/workflows/build-and-release.yml | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml
index d56276514..9f7beea00 100644
--- a/.github/workflows/build-and-release.yml
+++ b/.github/workflows/build-and-release.yml
@@ -97,6 +97,11 @@ jobs:
       - name: Go Mod Vendor
         run: |
           go mod vendor
+      - name: Go Generate
+        run: |
+          go generate ./...
+          go generate -tags darwin ./pkg/python
+          go generate -tags windows ./pkg/python
       - name: Check docs
         run: |
           go run ./hack/replace-commands-help --commands-md ./docs/commands.md
@@ -105,11 +110,6 @@ jobs:
             echo "commands.md is not up-to-date. Run "./hack/replace-commands-help --commands-md ./docs/commands.md" to fix this!"
             exit 1
           fi
-      - name: Go Generate
-        run: |
-          go generate ./...
-          go generate -tags darwin ./pkg/python
-          go generate -tags windows ./pkg/python
       - name: Run unit tests
         run: |
           go test ./cmd/... ./pkg/... -v

From 27b4501b025ccc0cd4b93f47e98ce6ae4d98d5e5 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Mon, 21 Mar 2022 09:58:13 +0100
Subject: [PATCH 0569/2916] ci: Fix mv

---
 .github/workflows/build-and-release.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml
index 9f7beea00..1120012f2 100644
--- a/.github/workflows/build-and-release.yml
+++ b/.github/workflows/build-and-release.yml
@@ -139,7 +139,7 @@ jobs:
           go build -ldflags "-s -w" ./cmd/kluctl
           go test -c ./e2e
           mv kluctl.exe kluctl-windows-amd64.exe
-          mv e2e.test e2e.test-windows-amd64.exe
+          mv e2e.test.exe e2e.test-windows-amd64.exe
       - name: Upload dist artifact
         uses: actions/upload-artifact@v2
         with:

From dc8b3385050e8a2a950b9e08affe830f6d083dfc Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Mon, 21 Mar 2022 10:16:39 +0100
Subject: [PATCH 0570/2916] fix: Fix uses of dist artifacts

---
 .github/workflows/build-and-release.yml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml
index 1120012f2..374dfd319 100644
--- a/.github/workflows/build-and-release.yml
+++ b/.github/workflows/build-and-release.yml
@@ -269,7 +269,7 @@ jobs:
       - name: Run e2e tests
         shell: bash
         run: |
-          ./e2e.test-${{ matrix.binary-suffix }}$TOOLS_EXE -test.v
+          ./dist/e2e.test-${{ matrix.binary-suffix }}$TOOLS_EXE -test.v
       - name: Delete kind cluster
         shell: bash
         run: |
@@ -296,6 +296,6 @@ jobs:
           draft: true
           body_path: changelog/CHANGELOG.md
           files: |
-            dist-*/kluctl-linux-*
-            dist-*/kluctl-darwin-*
-            dist-*/kluctl-windows-*
+            dist/kluctl-linux-*
+            dist/kluctl-darwin-*
+            dist/kluctl-windows-*

From f080d48aed1874f61249e949bde341f76cc768e5 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Mon, 21 Mar 2022 10:17:32 +0100
Subject: [PATCH 0571/2916] ci: Pass TOOLS_EXE env var to other stages

---
 .github/workflows/build-and-release.yml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml
index 374dfd319..ad639eb15 100644
--- a/.github/workflows/build-and-release.yml
+++ b/.github/workflows/build-and-release.yml
@@ -192,6 +192,7 @@ jobs:
           else
             TOOLS_OS=linux
           fi
+          echo "TOOLS_EXE=$TOOLS_EXE" >> $GITHUB_ENV
           echo "TOOLS_OS=$TOOLS_OS" >> $GITHUB_ENV
           echo "TOOLS_TARGET_DIR=$TOOLS_TARGET_DIR" >> $GITHUB_ENV
           echo "$TOOLS_TARGET_DIR" >> $GITHUB_PATH

From bd7c95d3443109685ee3306b3c2cfddd3c40d359 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Mon, 21 Mar 2022 10:27:07 +0100
Subject: [PATCH 0572/2916] ci: Fix permissions of dist executables

---
 .github/workflows/build-and-release.yml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml
index ad639eb15..c51692dca 100644
--- a/.github/workflows/build-and-release.yml
+++ b/.github/workflows/build-and-release.yml
@@ -270,6 +270,7 @@ jobs:
       - name: Run e2e tests
         shell: bash
         run: |
+          chmod +x ./dist/*
           ./dist/e2e.test-${{ matrix.binary-suffix }}$TOOLS_EXE -test.v
       - name: Delete kind cluster
         shell: bash

From 1fcffb88bd9cfef62f54605d8cafbf73f7c3dda7 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Mon, 21 Mar 2022 15:22:36 +0100
Subject: [PATCH 0573/2916] refactor: Move AskForConfirmation to utils package

---
 cmd/kluctl/commands/cmd_delete.go                  | 3 ++-
 cmd/kluctl/commands/cmd_deploy.go                  | 3 ++-
 cmd/kluctl/commands/cmd_downscale.go               | 3 ++-
 cmd/kluctl/commands/cmd_poke_images.go             | 3 ++-
 {cmd/kluctl/commands => pkg/utils}/confirmation.go | 7 +++----
 5 files changed, 11 insertions(+), 8 deletions(-)
 rename {cmd/kluctl/commands => pkg/utils}/confirmation.go (83%)

diff --git a/cmd/kluctl/commands/cmd_delete.go b/cmd/kluctl/commands/cmd_delete.go
index ad4db676a..3f89e0288 100644
--- a/cmd/kluctl/commands/cmd_delete.go
+++ b/cmd/kluctl/commands/cmd_delete.go
@@ -9,6 +9,7 @@ import (
 	"github.com/codablock/kluctl/pkg/k8s"
 	"github.com/codablock/kluctl/pkg/types"
 	k8s2 "github.com/codablock/kluctl/pkg/types/k8s"
+	utils2 "github.com/codablock/kluctl/pkg/utils"
 	"os"
 )
 
@@ -78,7 +79,7 @@ func confirmedDeleteObjects(k *k8s.K8sCluster, refs []k8s2.ObjectRef, dryRun boo
 			_, _ = os.Stderr.WriteString(fmt.Sprintf("  %s\n", ref.String()))
 		}
 		if !forceYes && !dryRun {
-			if !AskForConfirmation(fmt.Sprintf("Do you really want to delete %d objects?", len(refs))) {
+			if !utils2.AskForConfirmation(fmt.Sprintf("Do you really want to delete %d objects?", len(refs))) {
 				return nil, fmt.Errorf("aborted")
 			}
 		}
diff --git a/cmd/kluctl/commands/cmd_deploy.go b/cmd/kluctl/commands/cmd_deploy.go
index b05ae3ff2..1af706831 100644
--- a/cmd/kluctl/commands/cmd_deploy.go
+++ b/cmd/kluctl/commands/cmd_deploy.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"github.com/codablock/kluctl/cmd/kluctl/args"
 	"github.com/codablock/kluctl/pkg/deployment/commands"
+	"github.com/codablock/kluctl/pkg/utils"
 )
 
 type deployCmd struct {
@@ -48,7 +49,7 @@ func (cmd *deployCmd) Run() error {
 
 func (cmd *deployCmd) runCmdDeploy(ctx *commandCtx) error {
 	if !cmd.Yes && !cmd.DryRun {
-		if !AskForConfirmation(fmt.Sprintf("Do you really want to deploy to the context/cluster %s?", ctx.k.Context())) {
+		if !utils.AskForConfirmation(fmt.Sprintf("Do you really want to deploy to the context/cluster %s?", ctx.k.Context())) {
 			return fmt.Errorf("aborted")
 		}
 	}
diff --git a/cmd/kluctl/commands/cmd_downscale.go b/cmd/kluctl/commands/cmd_downscale.go
index 72676ec4c..37ae3515f 100644
--- a/cmd/kluctl/commands/cmd_downscale.go
+++ b/cmd/kluctl/commands/cmd_downscale.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"github.com/codablock/kluctl/cmd/kluctl/args"
 	"github.com/codablock/kluctl/pkg/deployment/commands"
+	"github.com/codablock/kluctl/pkg/utils"
 )
 
 type downscaleCmd struct {
@@ -36,7 +37,7 @@ func (cmd *downscaleCmd) Run() error {
 	}
 	return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error {
 		if !cmd.Yes && !cmd.DryRun {
-			if !AskForConfirmation(fmt.Sprintf("Do you really want to downscale on context/cluster %s?", ctx.k.Context())) {
+			if !utils.AskForConfirmation(fmt.Sprintf("Do you really want to downscale on context/cluster %s?", ctx.k.Context())) {
 				return fmt.Errorf("aborted")
 			}
 		}
diff --git a/cmd/kluctl/commands/cmd_poke_images.go b/cmd/kluctl/commands/cmd_poke_images.go
index c69c53a35..d3de84c64 100644
--- a/cmd/kluctl/commands/cmd_poke_images.go
+++ b/cmd/kluctl/commands/cmd_poke_images.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"github.com/codablock/kluctl/cmd/kluctl/args"
 	"github.com/codablock/kluctl/pkg/deployment/commands"
+	"github.com/codablock/kluctl/pkg/utils"
 )
 
 type pokeImagesCmd struct {
@@ -36,7 +37,7 @@ func (cmd *pokeImagesCmd) Run() error {
 	}
 	return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error {
 		if !cmd.Yes && !cmd.DryRun {
-			if !AskForConfirmation(fmt.Sprintf("Do you really want to poke images to the context/cluster %s?", ctx.k.Context())) {
+			if !utils.AskForConfirmation(fmt.Sprintf("Do you really want to poke images to the context/cluster %s?", ctx.k.Context())) {
 				return fmt.Errorf("aborted")
 			}
 		}
diff --git a/cmd/kluctl/commands/confirmation.go b/pkg/utils/confirmation.go
similarity index 83%
rename from cmd/kluctl/commands/confirmation.go
rename to pkg/utils/confirmation.go
index 423d42cac..256182b0b 100644
--- a/cmd/kluctl/commands/confirmation.go
+++ b/pkg/utils/confirmation.go
@@ -1,8 +1,7 @@
-package commands
+package utils
 
 import (
 	"fmt"
-	"github.com/codablock/kluctl/pkg/utils"
 	log "github.com/sirupsen/logrus"
 	"os"
 )
@@ -25,9 +24,9 @@ func AskForConfirmation(prompt string) bool {
 	}
 	okayResponses := []string{"y", "Y", "yes", "Yes", "YES"}
 	nokayResponses := []string{"n", "N", "no", "No", "NO"}
-	if utils.FindStrInSlice(okayResponses, response) != -1 {
+	if FindStrInSlice(okayResponses, response) != -1 {
 		return true
-	} else if utils.FindStrInSlice(nokayResponses, response) != -1 || response == "" {
+	} else if FindStrInSlice(nokayResponses, response) != -1 || response == "" {
 		return false
 	} else {
 		fmt.Println("Please type yes or no and then press enter:")

From a4584a5baec6a06a1bfcae070e8e76ea55d035e5 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Mon, 21 Mar 2022 15:23:30 +0100
Subject: [PATCH 0574/2916] fix: Fix support for known_hosts on Windows and
 implement new host prompt

---
 pkg/git/auth/env_auth_provider.go |   2 +-
 pkg/git/auth/goph/hosts.go        | 121 ++++++++++++++++++++++++++++++
 pkg/git/auth/ssh_auth_provider.go |  26 +------
 pkg/git/auth/ssh_known_hosts.go   |  75 ++++++++++++++++++
 4 files changed, 198 insertions(+), 26 deletions(-)
 create mode 100644 pkg/git/auth/goph/hosts.go
 create mode 100644 pkg/git/auth/ssh_known_hosts.go

diff --git a/pkg/git/auth/env_auth_provider.go b/pkg/git/auth/env_auth_provider.go
index 5182f2a52..fbbca4990 100644
--- a/pkg/git/auth/env_auth_provider.go
+++ b/pkg/git/auth/env_auth_provider.go
@@ -44,7 +44,7 @@ func (a *GitEnvAuthProvider) BuildAuth(gitUrl git_url.GitUrl) transport.AuthMeth
 			if err != nil {
 				log.Debugf("Failed to parse private key %s: %v", ssh_key, err)
 			} else {
-				a.HostKeyCallback = getHostKeyCallback()
+				a.HostKeyCallback = verifyHost
 				return a
 			}
 		}
diff --git a/pkg/git/auth/goph/hosts.go b/pkg/git/auth/goph/hosts.go
new file mode 100644
index 000000000..e2d9bec1e
--- /dev/null
+++ b/pkg/git/auth/goph/hosts.go
@@ -0,0 +1,121 @@
+// Copyright 2020 Mohammed El Bahja. All rights reserved.
+// Use of this source code is governed by a MIT license.
+
+package goph
+
+import (
+	"errors"
+	"fmt"
+	"net"
+	"os"
+
+	"golang.org/x/crypto/ssh"
+	"golang.org/x/crypto/ssh/knownhosts"
+)
+
+// DefaultKnownHosts returns host key callback from default known hosts path, and error if any.
+func DefaultKnownHosts() (ssh.HostKeyCallback, error) {
+
+	path, err := DefaultKnownHostsPath()
+	if err != nil {
+		return nil, err
+	}
+
+	return KnownHosts(path)
+}
+
+// KnownHosts returns host key callback from a custom known hosts path.
+func KnownHosts(file string) (ssh.HostKeyCallback, error) {
+	return knownhosts.New(file)
+}
+
+// CheckKnownHost checks is host in known hosts file.
+// it returns is the host found in known_hosts file and error, if the host found in
+// known_hosts file and error not nil that means public key mismatch, maybe MAN IN THE MIDDLE ATTACK! you should not handshake.
+func CheckKnownHost(host string, remote net.Addr, key ssh.PublicKey, knownFile string) (found bool, err error) {
+
+	var keyErr *knownhosts.KeyError
+
+	// Fallback to default known_hosts file
+	if knownFile == "" {
+		path, err := DefaultKnownHostsPath()
+		if err != nil {
+			return false, err
+		}
+
+		knownFile = path
+	}
+
+	// Get host key callback
+	callback, err := KnownHosts(knownFile)
+
+	if err != nil {
+		return false, err
+	}
+
+	// check if host already exists.
+	err = callback(host, remote, key)
+
+	// Known host already exists.
+	if err == nil {
+		return true, nil
+	}
+
+	// Make sure that the error returned from the callback is host not in file error.
+	// If keyErr.Want is greater than 0 length, that means host is in file with different key.
+	if errors.As(err, &keyErr) && len(keyErr.Want) > 0 {
+		return true, keyErr
+	}
+
+	// Some other error occurred and safest way to handle is to pass it back to user.
+	if err != nil {
+		return false, err
+	}
+
+	// Key is not trusted because it is not in the file.
+	return false, nil
+}
+
+// AddKnownHost add a a host to known hosts file.
+func AddKnownHost(host string, remote net.Addr, key ssh.PublicKey, knownFile string) (err error) {
+
+	// Fallback to default known_hosts file
+	if knownFile == "" {
+		path, err := DefaultKnownHostsPath()
+		if err != nil {
+			return err
+		}
+
+		knownFile = path
+	}
+
+	f, err := os.OpenFile(knownFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
+	if err != nil {
+		return err
+	}
+
+	defer f.Close()
+
+	remoteNormalized := knownhosts.Normalize(remote.String())
+	hostNormalized := knownhosts.Normalize(host)
+	addresses := []string{remoteNormalized}
+
+	if hostNormalized != remoteNormalized {
+		addresses = append(addresses, hostNormalized)
+	}
+
+	_, err = f.WriteString(knownhosts.Line(addresses, key) + "\n")
+
+	return err
+}
+
+// DefaultKnownHostsPath returns default user knows hosts file.
+func DefaultKnownHostsPath() (string, error) {
+
+	home, err := os.UserHomeDir()
+	if err != nil {
+		return "", err
+	}
+
+	return fmt.Sprintf("%s/.ssh/known_hosts", home), err
+}
diff --git a/pkg/git/auth/ssh_auth_provider.go b/pkg/git/auth/ssh_auth_provider.go
index 58bf79cfb..51e1f04d8 100644
--- a/pkg/git/auth/ssh_auth_provider.go
+++ b/pkg/git/auth/ssh_auth_provider.go
@@ -4,17 +4,13 @@ import (
 	"fmt"
 	git_url "github.com/codablock/kluctl/pkg/git/git-url"
 	"github.com/go-git/go-git/v5/plumbing/transport"
-	git_ssh "github.com/go-git/go-git/v5/plumbing/transport/ssh"
 	log "github.com/sirupsen/logrus"
 	sshagent "github.com/xanzy/ssh-agent"
 	"golang.org/x/crypto/ssh"
 	"golang.org/x/crypto/ssh/agent"
 	"io/ioutil"
-	"net"
-	"os"
 	"os/user"
 	"path/filepath"
-	"strconv"
 )
 
 type GitSshAuthProvider struct {
@@ -39,30 +35,10 @@ func (a *sshDefaultIdentityAndAgent) ClientConfig() (*ssh.ClientConfig, error) {
 		User: a.user,
 		Auth: []ssh.AuthMethod{ssh.PublicKeysCallback(a.Signers)},
 	}
-	cc.HostKeyCallback = getHostKeyCallback()
+	cc.HostKeyCallback = verifyHost
 	return cc, nil
 }
 
-func getHostKeyCallback() ssh.HostKeyCallback {
-	hostKeyChecking := true
-	if x, ok := os.LookupEnv("KLUCTL_SSH_DISABLE_STRICT_HOST_KEY_CHECKING"); ok {
-		if b, err := strconv.ParseBool(x); err == nil && b {
-			hostKeyChecking = false
-		}
-	}
-	if hostKeyChecking {
-		cb, err := git_ssh.NewKnownHostsCallback()
-		if err != nil {
-			return nil
-		}
-		return cb
-	} else {
-		return func(hostname string, remote net.Addr, key ssh.PublicKey) error {
-			return nil
-		}
-	}
-}
-
 func (a *sshDefaultIdentityAndAgent) Signers() ([]ssh.Signer, error) {
 	var ret []ssh.Signer
 	if a.defaultIdentity != nil {
diff --git a/pkg/git/auth/ssh_known_hosts.go b/pkg/git/auth/ssh_known_hosts.go
new file mode 100644
index 000000000..e23e005bb
--- /dev/null
+++ b/pkg/git/auth/ssh_known_hosts.go
@@ -0,0 +1,75 @@
+package auth
+
+import (
+	"fmt"
+	"github.com/codablock/kluctl/pkg/git/auth/goph"
+	"github.com/codablock/kluctl/pkg/utils"
+	"github.com/mattn/go-isatty"
+	log "github.com/sirupsen/logrus"
+	"golang.org/x/crypto/ssh"
+	"net"
+	"os"
+	"path/filepath"
+	"strconv"
+)
+
+func verifyHost(host string, remote net.Addr, key ssh.PublicKey) error {
+	hostKeyChecking := true
+	if x, ok := os.LookupEnv("KLUCTL_SSH_DISABLE_STRICT_HOST_KEY_CHECKING"); ok {
+		if b, err := strconv.ParseBool(x); err == nil && b {
+			hostKeyChecking = false
+		}
+	}
+	if !hostKeyChecking {
+		return nil
+	}
+
+	allowAdd := false
+	files := filepath.SplitList(os.Getenv("SSH_KNOWN_HOSTS"))
+	if len(files) == 0 {
+		home, err := os.UserHomeDir()
+		if err != nil {
+			return err
+		}
+
+		f := filepath.Join(home, ".ssh", "known_hosts")
+		if !utils.Exists(filepath.Dir(f)) {
+			err = os.MkdirAll(filepath.Dir(f), 0o700)
+			if err != nil {
+				return err
+			}
+		}
+
+		files = append(files, f)
+		allowAdd = true
+	}
+
+	for _, f := range files {
+		hostFound, err := goph.CheckKnownHost(host, remote, key, f)
+		if hostFound && err != nil {
+			return err
+		}
+		if hostFound && err == nil {
+			return nil
+		}
+	}
+	if !allowAdd {
+		return fmt.Errorf("host not found and SSH_KNOWN_HOSTS has been set")
+	}
+
+	if !askIsHostTrusted(host, key) {
+		return fmt.Errorf("aborted")
+	}
+
+	return goph.AddKnownHost(host, remote, key, "")
+}
+
+func askIsHostTrusted(host string, key ssh.PublicKey) bool {
+	if !isatty.IsTerminal(os.Stderr.Fd()) {
+		log.Warningf("Not a terminal, suppressed prompt for unknown host")
+		return false
+	}
+
+	prompt := fmt.Sprintf("Unknown Host: %s\nFingerprint: %s\nWould you like to add it? ", host, ssh.FingerprintSHA256(key))
+	return utils.AskForConfirmation(prompt)
+}

From 634d9cefbf886b8ad57a0cb71260cc2baa0a708b Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Mon, 21 Mar 2022 15:23:56 +0100
Subject: [PATCH 0575/2916] fix: Don't raise unnecesarely in jinja2 renderer
 when exiting

---
 pkg/jinja2/python_src/main.py | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/pkg/jinja2/python_src/main.py b/pkg/jinja2/python_src/main.py
index 5da3e18c6..1c46fce20 100644
--- a/pkg/jinja2/python_src/main.py
+++ b/pkg/jinja2/python_src/main.py
@@ -9,6 +9,8 @@ def main():
 
     while True:
         args = sys.stdin.readline()
+        if not args:
+            break
         args = json.loads(args)
 
         if args["cmd"] == "render-strings":

From 7fbb72a7fdedd0d860d5fed2b36fa01d76edbc97 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Mon, 21 Mar 2022 15:24:35 +0100
Subject: [PATCH 0576/2916] fix: Use correct path after extraction of embedded
 tar.gz

---
 pkg/jinja2/jinja2.go            |  4 ++--
 pkg/jinja2/source.go            |  2 +-
 pkg/python/embed.go             |  2 +-
 pkg/utils/embed_util/extract.go | 20 ++++++++++----------
 pkg/utils/tar.go                |  2 +-
 5 files changed, 15 insertions(+), 15 deletions(-)

diff --git a/pkg/jinja2/jinja2.go b/pkg/jinja2/jinja2.go
index 46a479965..eda28e357 100644
--- a/pkg/jinja2/jinja2.go
+++ b/pkg/jinja2/jinja2.go
@@ -81,13 +81,13 @@ func (j *Jinja2) Close() {
 
 func (j *Jinja2) RenderStrings(jobs []*RenderJob, searchDirs []string, vars *uo.UnstructuredObject) error {
 	pj := <-j.pj
-	defer func() {j.pj <- pj}()
+	defer func() { j.pj <- pj }()
 	return pj.renderHelper(jobs, searchDirs, vars, true)
 }
 
 func (j *Jinja2) RenderFiles(jobs []*RenderJob, searchDirs []string, vars *uo.UnstructuredObject) error {
 	pj := <-j.pj
-	defer func() {j.pj <- pj}()
+	defer func() { j.pj <- pj }()
 	return pj.renderHelper(jobs, searchDirs, vars, false)
 }
 
diff --git a/pkg/jinja2/source.go b/pkg/jinja2/source.go
index 0d2d18d2b..7f9924920 100644
--- a/pkg/jinja2/source.go
+++ b/pkg/jinja2/source.go
@@ -34,7 +34,7 @@ func extractSource() (string, error) {
 	defer fileList.Close()
 
 	libPath := filepath.Join(utils.GetTmpBaseDir(), "jinja2-src")
-	err = embed_util.ExtractTarToTmp(tgz, fileList, libPath)
+	libPath, err = embed_util.ExtractTarToTmp(tgz, fileList, libPath)
 	if err != nil {
 		return "", err
 	}
diff --git a/pkg/python/embed.go b/pkg/python/embed.go
index 1bf7ec166..1ee3cf3d1 100644
--- a/pkg/python/embed.go
+++ b/pkg/python/embed.go
@@ -30,7 +30,7 @@ func decompressPython() string {
 	defer fileList.Close()
 
 	path := filepath.Join(utils.GetTmpBaseDir(), fmt.Sprintf("python-%s", runtime.GOOS))
-	err = embed_util.ExtractTarToTmp(tgz, fileList, path)
+	path, err = embed_util.ExtractTarToTmp(tgz, fileList, path)
 	if err != nil {
 		log.Panic(err)
 	}
diff --git a/pkg/utils/embed_util/extract.go b/pkg/utils/embed_util/extract.go
index c4a3ae727..55b954f7d 100644
--- a/pkg/utils/embed_util/extract.go
+++ b/pkg/utils/embed_util/extract.go
@@ -13,10 +13,10 @@ import (
 	"strings"
 )
 
-func ExtractTarToTmp(r io.Reader, fileListR io.Reader, targetPrefix string) error {
+func ExtractTarToTmp(r io.Reader, fileListR io.Reader, targetPrefix string) (string, error) {
 	fileList, err := ioutil.ReadAll(fileListR)
 	if err != nil {
-		return err
+		return "", err
 	}
 
 	targetPath := fmt.Sprintf("%s-%s", targetPrefix, utils.Sha256Bytes(fileList)[:16])
@@ -24,39 +24,39 @@ func ExtractTarToTmp(r io.Reader, fileListR io.Reader, targetPrefix string) erro
 	fl := flock.New(targetPath + ".lock")
 	err = fl.Lock()
 	if err != nil {
-		return err
+		return "", err
 	}
 	defer fl.Unlock()
 
 	needsExtract, expectedTarGzHash, err := checkExtractNeeded(targetPath, string(fileList))
 	if err != nil {
-		return err
+		return "", err
 	}
 	if !needsExtract {
-		return nil
+		return targetPath, nil
 	}
 
 	err = os.RemoveAll(targetPath)
 	if err != nil && !os.IsNotExist(err) {
-		return err
+		return "", err
 	}
 
 	err = os.MkdirAll(targetPath, 0o700)
 	if err != nil {
-		return err
+		return "", err
 	}
 
 	err = utils.ExtractTarGzStream(r, targetPath)
 	if err != nil {
-		return err
+		return "", err
 	}
 
 	err = ioutil.WriteFile(filepath.Join(targetPath, ".tar-gz-hash"), []byte(expectedTarGzHash), 0o600)
 	if err != nil {
-		return err
+		return "", err
 	}
 
-	return nil
+	return targetPath, nil
 }
 
 func checkExtractNeeded(targetPath string, fileListStr string) (bool, string, error) {
diff --git a/pkg/utils/tar.go b/pkg/utils/tar.go
index e3bfdba6c..7fa7761b8 100644
--- a/pkg/utils/tar.go
+++ b/pkg/utils/tar.go
@@ -180,4 +180,4 @@ func HashTarEntry(dir string, name string) (string, error) {
 	}
 	hashStr := Sha256Bytes(hashData)
 	return hashStr, nil
-}
\ No newline at end of file
+}

From 2360c80532aaf77c737b00a85bb865d90eb65e41 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Mon, 21 Mar 2022 15:28:26 +0100
Subject: [PATCH 0577/2916] build: Run go mod vendor

---
 go.mod | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/go.mod b/go.mod
index 29806318c..0996ff741 100644
--- a/go.mod
+++ b/go.mod
@@ -19,12 +19,15 @@ require (
 	github.com/hashicorp/go-version v1.4.0
 	github.com/hexops/gotextdiff v1.0.3
 	github.com/jinzhu/copier v0.3.5
+	github.com/mattn/go-isatty v0.0.14
 	github.com/mitchellh/go-ps v1.0.0
 	github.com/ohler55/ojg v1.12.14
 	github.com/pkg/errors v0.9.1
 	github.com/r3labs/diff/v2 v2.15.0
 	github.com/sirupsen/logrus v1.8.1
+	github.com/stretchr/testify v1.7.0
 	github.com/whilp/git-urls v1.0.0
+	github.com/xanzy/ssh-agent v0.3.1
 	golang.org/x/crypto v0.0.0-20220214200702-86341886e292
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
 	golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9
@@ -91,7 +94,6 @@ require (
 	github.com/leodido/go-urn v1.2.1 // indirect
 	github.com/mailru/easyjson v0.7.7 // indirect
 	github.com/mattn/go-colorable v0.1.12 // indirect
-	github.com/mattn/go-isatty v0.0.14 // indirect
 	github.com/mitchellh/go-homedir v1.1.0 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
@@ -104,10 +106,8 @@ require (
 	github.com/sergi/go-diff v1.2.0 // indirect
 	github.com/spf13/cobra v1.3.0 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
-	github.com/stretchr/testify v1.7.0 // indirect
 	github.com/vbatts/tar-split v0.11.2 // indirect
 	github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
-	github.com/xanzy/ssh-agent v0.3.1 // indirect
 	github.com/xlab/treeprint v1.1.0 // indirect
 	go.starlark.net v0.0.0-20220302181546-5411bad688d1 // indirect
 	golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect

From ef630c834eca66ee002680a86f3bdfaa1c124bdd Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Mon, 21 Mar 2022 17:01:42 +0100
Subject: [PATCH 0578/2916] fix: Fix checkExtractNeeded to use / as separator
 on Windows

---
 pkg/utils/embed_util/extract.go | 1 +
 1 file changed, 1 insertion(+)

diff --git a/pkg/utils/embed_util/extract.go b/pkg/utils/embed_util/extract.go
index 55b954f7d..02d4a47ac 100644
--- a/pkg/utils/embed_util/extract.go
+++ b/pkg/utils/embed_util/extract.go
@@ -98,6 +98,7 @@ func checkExtractNeeded(targetPath string, fileListStr string) (bool, string, er
 		if err != nil {
 			return err
 		}
+		relPath = strings.ReplaceAll(relPath, string(os.PathSeparator), "/")
 		if info.IsDir() {
 			existingFiles[relPath] = 0
 		} else {

From fbf1ef2d058850b665bac6ac46d640865658cbb4 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Wed, 23 Mar 2022 08:36:59 +0100
Subject: [PATCH 0579/2916] docs: Use different label in example

---
 docs/deployments.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/deployments.md b/docs/deployments.md
index 95f3324a9..819ac562b 100644
--- a/docs/deployments.md
+++ b/docs/deployments.md
@@ -85,7 +85,7 @@ deployments:
 
 commonLabels:
   my.prefix/environment: "{{ args.environment }}"
-  my.prefix/deployment-project: k8s-deployment-airsea
+  my.prefix/deployment-project: my-deployment-project
 
 args:
 - name: environment

From 641534935555a57543a195802d59cc6fdcbba8f6 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Wed, 23 Mar 2022 09:50:13 +0100
Subject: [PATCH 0580/2916] fix: Honor IdentityFile from ssh config

---
 pkg/git/auth/ssh_auth_provider.go | 35 +++++++++++++++++++++++++------
 pkg/utils/fs.go                   |  8 +++++++
 2 files changed, 37 insertions(+), 6 deletions(-)

diff --git a/pkg/git/auth/ssh_auth_provider.go b/pkg/git/auth/ssh_auth_provider.go
index 51e1f04d8..7375d78f2 100644
--- a/pkg/git/auth/ssh_auth_provider.go
+++ b/pkg/git/auth/ssh_auth_provider.go
@@ -3,7 +3,9 @@ package auth
 import (
 	"fmt"
 	git_url "github.com/codablock/kluctl/pkg/git/git-url"
+	"github.com/codablock/kluctl/pkg/utils"
 	"github.com/go-git/go-git/v5/plumbing/transport"
+	git_ssh "github.com/go-git/go-git/v5/plumbing/transport/ssh"
 	log "github.com/sirupsen/logrus"
 	sshagent "github.com/xanzy/ssh-agent"
 	"golang.org/x/crypto/ssh"
@@ -17,6 +19,7 @@ type GitSshAuthProvider struct {
 }
 
 type sshDefaultIdentityAndAgent struct {
+	hostname        string
 	user            string
 	defaultIdentity ssh.Signer
 	agent           agent.Agent
@@ -41,6 +44,16 @@ func (a *sshDefaultIdentityAndAgent) ClientConfig() (*ssh.ClientConfig, error) {
 
 func (a *sshDefaultIdentityAndAgent) Signers() ([]ssh.Signer, error) {
 	var ret []ssh.Signer
+	identityFromConfig := git_ssh.DefaultSSHConfig.Get(a.hostname, "IdentityFile")
+	if identityFromConfig != "" {
+		identityFromConfig = utils.ExpandPath(identityFromConfig)
+		signer, err := readKey(identityFromConfig)
+		if err != nil {
+			return nil, err
+		}
+		ret = append(ret, signer)
+		return ret, nil
+	}
 	if a.defaultIdentity != nil {
 		ret = append(ret, a.defaultIdentity)
 	}
@@ -63,6 +76,7 @@ func (a *GitSshAuthProvider) BuildAuth(gitUrl git_url.GitUrl) transport.AuthMeth
 	}
 
 	auth := &sshDefaultIdentityAndAgent{
+		hostname: gitUrl.Hostname(),
 		user: gitUrl.User.Username(),
 	}
 
@@ -70,14 +84,10 @@ func (a *GitSshAuthProvider) BuildAuth(gitUrl git_url.GitUrl) transport.AuthMeth
 	if err != nil {
 		log.Debugf("No current user: %v", err)
 	} else {
-		pemBytes, err := ioutil.ReadFile(filepath.Join(u.HomeDir, ".ssh", "id_rsa"))
+		signer, err := readKey(filepath.Join(u.HomeDir, ".ssh", "id_rsa"))
 		if err != nil {
 			log.Debugf("Failed to read default identity file for url %s: %v", gitUrl.String(), err)
-		} else {
-			signer, err := ssh.ParsePrivateKey(pemBytes)
-			if err != nil {
-				log.Debugf("Failed to parse default identity for url %s: %v", gitUrl.String(), err)
-			}
+		} else if signer != nil {
 			auth.defaultIdentity = signer
 		}
 	}
@@ -91,3 +101,16 @@ func (a *GitSshAuthProvider) BuildAuth(gitUrl git_url.GitUrl) transport.AuthMeth
 
 	return auth
 }
+
+func readKey(path string) (ssh.Signer, error) {
+	pemBytes, err := ioutil.ReadFile(path)
+	if err != nil {
+		return nil, err
+	} else {
+		signer, err := ssh.ParsePrivateKey(pemBytes)
+		if err != nil {
+			return nil, err
+		}
+		return signer, nil
+	}
+}
\ No newline at end of file
diff --git a/pkg/utils/fs.go b/pkg/utils/fs.go
index b4fe0fe6d..da44faa2b 100644
--- a/pkg/utils/fs.go
+++ b/pkg/utils/fs.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"io"
 	"io/fs"
+	"k8s.io/client-go/util/homedir"
 	"os"
 	"path"
 	"path/filepath"
@@ -119,3 +120,10 @@ func FsCopyDir(srcFs fs.FS, src string, dst string) error {
 func CopyDir(src string, dst string) error {
 	return FsCopyDir(os.DirFS(src), ".", dst)
 }
+
+func ExpandPath(p string) string {
+	if strings.HasPrefix(p, "~/") {
+		p = homedir.HomeDir() + p[1:]
+	}
+	return p
+}
\ No newline at end of file

From 946b1f8b7cf327f7b962d2504b33ac0664fd0167 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Mon, 21 Mar 2022 22:47:03 +0100
Subject: [PATCH 0581/2916] ci: Use zerotier to provide docker for mac and
 windows

---
 .github/workflows/build-and-release.yml | 93 ++++++++++++++++++++++---
 hack/start-kind-cluster.sh              |  7 +-
 hack/zerotier-create-network.sh         | 29 ++++++++
 hack/zerotier-delete-network.sh         | 16 +++++
 hack/zerotier-join-network.sh           | 28 ++++++++
 hack/zerotier-setup-docker-host.sh      | 29 ++++++++
 6 files changed, 188 insertions(+), 14 deletions(-)
 create mode 100755 hack/zerotier-create-network.sh
 create mode 100755 hack/zerotier-delete-network.sh
 create mode 100755 hack/zerotier-join-network.sh
 create mode 100755 hack/zerotier-setup-docker-host.sh

diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml
index c51692dca..22aaa6a08 100644
--- a/.github/workflows/build-and-release.yml
+++ b/.github/workflows/build-and-release.yml
@@ -152,6 +152,57 @@ jobs:
             e2e.test-darwin-amd64
             e2e.test-windows-amd64.exe
 
+  docker-host:
+    runs-on: ubuntu-latest
+    needs:
+      - build
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+      - name: Install zerotier
+        if: runner.os == 'Linux'
+        run: |
+          sudo apt update
+          sudo apt install -y gpg jq
+          curl -s https://install.zerotier.com | sudo bash
+      - name: Stop docker
+        run: |
+          # Ensure docker is down and that the test jobs can wait for it to be available again after joining the network.
+          # Otherwise they might join and then docker restarts
+          sudo systemctl stop docker
+      - name: Create network
+        run: |
+          export CI_ZEROTIER_API_KEY=${{ secrets.CI_ZEROTIER_API_KEY }}
+          ./hack/zerotier-create-network.sh $GITHUB_RUN_ID
+      - name: Join network
+        run: |
+          export CI_ZEROTIER_API_KEY=${{ secrets.CI_ZEROTIER_API_KEY }}
+          ./hack/zerotier-join-network.sh $GITHUB_RUN_ID docker
+      - name: Restart ssh
+        run: |
+          sudo systemctl restart ssh
+      - name: Restart docker
+        run: |
+          sudo sed -i 's|-H fd://|-H fd:// -H tcp://0.0.0.0:2375|g' /lib/systemd/system/docker.service
+          sudo systemctl daemon-reload
+          sudo systemctl start docker
+      - name: Wait for other jobs to finish
+        run: |
+          export GITHUB_TOKEN="${{ secrets.GITHUB_TOKEN }}"
+          while true; do
+            JOBS=$(gh api /repos/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID/jobs | jq '.jobs[] | select(.name | startswith("tests "))')
+            NON_COMPLETED=$(echo $JOBS | jq '. | select(.status != "completed")')
+            if [ "$NON_COMPLETED" == "" ]; then
+              break
+            fi
+            sleep 5
+          done
+      - name: Delete network
+        if: always()
+        run: |
+          export CI_ZEROTIER_API_KEY=${{ secrets.CI_ZEROTIER_API_KEY }}
+          ./hack/zerotier-delete-network.sh $GITHUB_RUN_ID
+
   tests:
     if: "!startsWith(github.ref, 'refs/tags/')"
     strategy:
@@ -165,7 +216,6 @@ jobs:
             binary-suffix: windows-amd64
         os: [ubuntu-18.04, macos-10.15, windows-2019]
       fail-fast: false
-    concurrency: docker.ci.kluctl.io-${{ matrix.binary-suffix }}
     needs:
       - version
       - build
@@ -173,6 +223,37 @@ jobs:
     steps:
       - name: Checkout
         uses: actions/checkout@v2
+      - name: Install zerotier (linux)
+        if: runner.os == 'Linux'
+        run: |
+          sudo apt update
+          sudo apt install -y gpg jq
+          curl -s https://install.zerotier.com | sudo bash
+      - name: Install zerotier (macOS)
+        if: runner.os == 'macOS'
+        run: |
+          brew install zerotier-one
+      - name: Install zerotier (windows)
+        if: runner.os == 'Windows'
+        shell: bash
+        run: |
+          choco install zerotier-one
+          echo "#!/usr/bin/env bash" > /usr/bin/zerotier-cli
+          echo '/c/ProgramData/ZeroTier/One/zerotier-one_x64.exe -q "$@"' >> /usr/bin/zerotier-cli
+          chmod +x /usr/bin/zerotier-cli
+
+          choco install netcat
+          echo /c/ProgramData/chocolatey/bin >> $GITHUB_PATH
+      - name: Join network
+        shell: bash
+        run: |
+          export CI_ZEROTIER_API_KEY=${{ secrets.CI_ZEROTIER_API_KEY }}
+          ./hack/zerotier-join-network.sh $GITHUB_RUN_ID ${{ matrix.binary-suffix }}
+      - name: Determine DOCKER_HOST
+        shell: bash
+        run: |
+          export CI_ZEROTIER_API_KEY=${{ secrets.CI_ZEROTIER_API_KEY }}
+          ./hack/zerotier-setup-docker-host.sh $GITHUB_RUN_ID
       - name: Setup TOOLS envs
         shell: bash
         run: |
@@ -242,14 +323,6 @@ jobs:
           kubectl version || true
           kind version || true
           helm version || true
-      - name: Port-forward docker
-        shell: bash
-        run: |
-          echo "${{ secrets.CI_SSH_KEY }}" > kluctl-ci.pem
-          chmod og-rwx kluctl-ci.pem
-          ls -lah kluctl-ci.pem
-          ./hack/setup-docker-port-forward.sh
-          echo "DOCKER_HOST=localhost:2375" >> $GITHUB_ENV
       - name: Start kind cluster
         shell: bash
         run: |
@@ -264,7 +337,7 @@ jobs:
           echo "KIND_CLUSTER_NAME=$KIND_CLUSTER_NAME" >> $GITHUB_ENV
           echo "KIND_KUBECONFIG=$(pwd)/kind-kubeconfig" >> $GITHUB_ENV
 
-          ./hack/start-kind-cluster.sh "$KIND_CLUSTER_NAME" "$PORT"
+          ./hack/start-kind-cluster.sh "$KIND_CLUSTER_NAME" "$DOCKER_IP" "$PORT"
       - name: Download dist artifacts
         uses: actions/download-artifact@v2
       - name: Run e2e tests
diff --git a/hack/start-kind-cluster.sh b/hack/start-kind-cluster.sh
index c3e2aa406..232347f0d 100755
--- a/hack/start-kind-cluster.sh
+++ b/hack/start-kind-cluster.sh
@@ -3,7 +3,8 @@
 set -e
 
 NAME=$1
-PORT=$2
+IP=$2
+PORT=$3
 
 cat << EOF > kind-cluster.yml
 kind: Cluster
@@ -18,10 +19,8 @@ export KUBECONFIG=$KIND_KUBECONFIG
 kind delete cluster --name $NAME || true
 kind create cluster --config kind-cluster.yml --name $NAME
 
-# Rewrite cluster info to point to docker.ci.kluctl.io
+# Rewrite cluster info to point to docker host
 # This also fully disables TLS verification
-IP=$(nslookup docker.ci.kluctl.io | grep Address | tail -n1 | sed 's/Address://g' | awk '{print $1}')
-echo IP=$IP
 kubectl config view -ojson --raw \
   | jq ".clusters[0].cluster.\"insecure-skip-tls-verify\"=true" \
   | jq "del(.clusters[0].cluster.\"certificate-authority-data\")" \
diff --git a/hack/zerotier-create-network.sh b/hack/zerotier-create-network.sh
new file mode 100755
index 000000000..000e77af9
--- /dev/null
+++ b/hack/zerotier-create-network.sh
@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+
+set -e
+
+NETWORK_NAME=$1
+
+NETWORK=$(curl -H"Authorization: bearer $CI_ZEROTIER_API_KEY" -XPOST -d"{}" https://my.zerotier.com/api/v1/network)
+NETWORK_ID=$(echo $NETWORK | jq '.config.id' -r)
+
+echo "Configuring network"
+cat << EOF | curl -H"Authorization: bearer $CI_ZEROTIER_API_KEY" -XPOST -d@- https://my.zerotier.com/api/v1/network/$NETWORK_ID
+{
+  "config": {
+    "private": false,
+    "name": "$NETWORK_NAME",
+    "ipAssignmentPools": [
+      {
+        "ipRangeStart":"10.147.17.1",
+        "ipRangeEnd":"10.147.17.254"
+      }
+    ],
+    "routes": [
+      {
+        "target":"10.147.17.0/24"
+      }
+    ]
+  }
+}
+EOF
diff --git a/hack/zerotier-delete-network.sh b/hack/zerotier-delete-network.sh
new file mode 100755
index 000000000..a8c22a7f7
--- /dev/null
+++ b/hack/zerotier-delete-network.sh
@@ -0,0 +1,16 @@
+#!/usr/bin/env bash
+
+set -e
+
+NETWORK_NAME=$1
+
+echo "Searching network "
+NETWORKS=$(curl -H"Authorization: bearer $CI_ZEROTIER_API_KEY" https://my.zerotier.com/api/v1/network)
+NETWORK_ID=$(echo $NETWORKS | jq ".[] | select(.config.name == \"$NETWORK_NAME\") | .config.id" -r)
+
+if [ "$NETWORK_ID" = "" ]; then
+  exit 0
+fi
+
+echo "Deleting network"
+curl -H"Authorization: bearer $CI_ZEROTIER_API_KEY" -XDELETE https://my.zerotier.com/api/v1/network/$NETWORK_ID
diff --git a/hack/zerotier-join-network.sh b/hack/zerotier-join-network.sh
new file mode 100755
index 000000000..3f2094e41
--- /dev/null
+++ b/hack/zerotier-join-network.sh
@@ -0,0 +1,28 @@
+#!/usr/bin/env bash
+
+set -e
+
+NETWORK_NAME=$1
+MEMBER_NAME=$2
+
+while true; do
+  echo "Searching network"
+  NETWORKS=$(curl -H"Authorization: bearer $CI_ZEROTIER_API_KEY" https://my.zerotier.com/api/v1/network)
+  NETWORK_ID=$(echo $NETWORKS | jq ".[] | select(.config.name == \"$NETWORK_NAME\") | .config.id" -r)
+  if [ "$NETWORK_ID" != "" ]; then
+    break
+  fi
+  sleep 5
+done
+
+SUDO=
+if which sudo &> /dev/null; then
+  SUDO=sudo
+fi
+
+echo "Joining network"
+$SUDO zerotier-cli join $NETWORK_ID
+MEMBER_ID=$($SUDO zerotier-cli status | awk '{print $3}')
+
+echo "Renaming member $MEMBER_ID"
+curl -H"Authorization: bearer $CI_ZEROTIER_API_KEY" -XPOST -d"{\"name\": \"$MEMBER_NAME\"}" https://my.zerotier.com/api/v1/network/$NETWORK_ID/member/$MEMBER_ID
diff --git a/hack/zerotier-setup-docker-host.sh b/hack/zerotier-setup-docker-host.sh
new file mode 100755
index 000000000..4c06dbfc1
--- /dev/null
+++ b/hack/zerotier-setup-docker-host.sh
@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+
+set -e
+
+NETWORK_NAME=$1
+
+echo "Searching network "
+NETWORKS=$(curl -H"Authorization: bearer $CI_ZEROTIER_API_KEY" https://my.zerotier.com/api/v1/network)
+NETWORK_ID=$(echo $NETWORKS | jq ".[] | select(.config.name == \"$NETWORK_NAME\") | .config.id" -r)
+
+echo "Waiting for IP to be assigned to docker member"
+while true; do
+  MEMBERS=$(curl -H"Authorization: bearer $CI_ZEROTIER_API_KEY" https://my.zerotier.com/api/v1/network/$NETWORK_ID/member)
+  IP=$(echo $MEMBERS | jq ".[] | select(.name == \"docker\") | .config.ipAssignments[0]" -r)
+  if [ "$IP" != "null" ]; then
+    break
+  fi
+  sleep 5
+  echo "Still waiting..."
+done
+
+echo "DOCKER_IP=$IP" >> $GITHUB_ENV
+echo "DOCKER_HOST=tcp://$IP:2375" >> $GITHUB_ENV
+
+echo "Waiting for docker to become available"
+while ! nc -z $IP 2375; do
+  sleep 5
+  echo "Still waiting..."
+done

From 368a43fe45025a18472269c8990f9da8849d2f79 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Wed, 23 Mar 2022 11:24:23 +0100
Subject: [PATCH 0582/2916] ci: Remote tmate support

---
 .github/workflows/build-and-release.yml | 12 ------------
 1 file changed, 12 deletions(-)

diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml
index 22aaa6a08..f0c8f4179 100644
--- a/.github/workflows/build-and-release.yml
+++ b/.github/workflows/build-and-release.yml
@@ -6,12 +6,6 @@ on:
       - '*'
     tags:
       - 'v*'
-  workflow_dispatch:
-    inputs:
-      debug_enabled:
-        description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
-        required: false
-        default: false
 
 jobs:
   version:
@@ -282,12 +276,6 @@ jobs:
         shell: bash
         run: |
           choco install openssh
-      # Enable tmate debugging of manually-triggered workflows if the input option was provided
-      - name: Setup tmate session
-        uses: mxschmitt/action-tmate@v3
-        if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }}
-        with:
-          limit-access-to-actor: true
       - name: Provide required tools versions
         shell: bash
         run: |

From affe962015c936c56c81983516bc00cfd5b2ac38 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Wed, 23 Mar 2022 14:03:47 +0100
Subject: [PATCH 0583/2916] fix: Simulate recreate when performing dryRun (e.g.
 diffs)

---
 pkg/deployment/utils/apply_utils.go | 21 ++++++++----
 pkg/utils/rand.go                   | 34 +++++++++++++++++++
 pkg/utils/uo/uo.go                  | 52 +++++++++++++++++++++++++++++
 3 files changed, 101 insertions(+), 6 deletions(-)
 create mode 100644 pkg/utils/rand.go

diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go
index 1712f75a4..31dc7dfdb 100644
--- a/pkg/deployment/utils/apply_utils.go
+++ b/pkg/deployment/utils/apply_utils.go
@@ -67,9 +67,8 @@ func (a *ApplyUtil) handleResult(appliedObject *uo.UnstructuredObject, hook bool
 	ref := appliedObject.GetK8sRef()
 	if hook {
 		a.appliedHookObjects[ref] = appliedObject
-	} else {
-		a.AppliedObjects[ref] = appliedObject
 	}
+	a.AppliedObjects[ref] = appliedObject
 }
 
 func (a *ApplyUtil) handleApiWarnings(ref k8s2.ObjectRef, warnings []k8s.ApiWarning) {
@@ -228,11 +227,15 @@ func (a *ApplyUtil) ApplyObject(x *uo.UnstructuredObject, replaced bool, hook bo
 	x = a.k.FixObjectForPatch(x)
 	remoteObject := a.ru.GetRemoteObject(ref)
 
+	usesDummyName := false
 	if a.o.DryRun && replaced && remoteObject != nil {
-		// Let's simulate that this object was deleted in dry-run mode. If we'd actually try a dry-run apply with
-		// this object, it might fail as it is expected to not exist.
-		a.handleResult(x, hook)
-		return
+		// The object got deleted before, which was however only simulated when in dry-run mode. This means, that
+		// trying to patch it will either fail or give different results then when actually re-creating it. To simulate
+		// re-creation, we use a temporary name for the dry-run patch and then undo the rename after getting the patch
+		// result
+		usesDummyName = true
+		x = x.Clone()
+		x.SetK8sName(fmt.Sprintf("%s-%s", x.GetK8sName(), utils.RandomString(8)))
 	}
 
 	options := k8s.PatchOptions{
@@ -243,6 +246,12 @@ func (a *ApplyUtil) ApplyObject(x *uo.UnstructuredObject, replaced bool, hook bo
 	if retry {
 		r, apiWarnings, err = a.k.PatchObject(x, options)
 	}
+	if r != nil && usesDummyName {
+		tmpName := r.GetK8sName()
+		realName := remoteObject.GetK8sName()
+		_ = r.ReplaceKeys(tmpName, realName)
+		_ = r.ReplaceValues(tmpName, realName)
+	}
 	a.handleApiWarnings(ref, apiWarnings)
 	if err == nil {
 		a.handleResult(r, hook)
diff --git a/pkg/utils/rand.go b/pkg/utils/rand.go
new file mode 100644
index 000000000..7e113042f
--- /dev/null
+++ b/pkg/utils/rand.go
@@ -0,0 +1,34 @@
+package utils
+
+import "math/rand"
+
+const (
+	// We omit vowels from the set of available characters to reduce the chances
+	// of "bad words" being formed.
+	alphanums = "bcdfghjklmnpqrstvwxz2456789"
+	// No. of bits required to index into alphanums string.
+	alphanumsIdxBits = 5
+	// Mask used to extract last alphanumsIdxBits of an int.
+	alphanumsIdxMask = 1<>= alphanumsIdxBits
+		remaining--
+	}
+	return string(b)
+}
diff --git a/pkg/utils/uo/uo.go b/pkg/utils/uo/uo.go
index f29f9f586..6097808cb 100644
--- a/pkg/utils/uo/uo.go
+++ b/pkg/utils/uo/uo.go
@@ -6,6 +6,7 @@ import (
 	"github.com/jinzhu/copier"
 	log "github.com/sirupsen/logrus"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"reflect"
 )
 
 type UnstructuredObject struct {
@@ -139,3 +140,54 @@ func (uo *UnstructuredObject) NewIterator() *ObjectIterator {
 func (uo *UnstructuredObject) Clear() {
 	uo.Object = make(map[string]interface{})
 }
+
+type uoParentKeyValue struct {
+	parent interface{}
+	key    interface{}
+	value  interface{}
+}
+
+func (uo *UnstructuredObject) ReplaceKeys(oldKey string, newKey string) error {
+	var toDelete []uoParentKeyValue
+	var toSet []uoParentKeyValue
+	err := uo.NewIterator().IterateLeafs(func(it *ObjectIterator) error {
+		keyStr, ok := it.Key().(string)
+		if ok && keyStr == oldKey {
+			toDelete = append(toDelete, uoParentKeyValue{
+				parent: it.Parent(),
+				key:    it.Key(),
+			})
+			toSet = append(toSet, uoParentKeyValue{
+				parent: it.Parent(),
+				key:    newKey,
+				value:  it.Value(),
+			})
+		}
+		return nil
+	})
+	if err != nil {
+		return err
+	}
+	for _, p := range toDelete {
+		delete(p.parent.(map[string]interface{}), p.key.(string))
+	}
+	for _, p := range toSet {
+		err = SetChild(p.parent, p.key, p.value)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (uo *UnstructuredObject) ReplaceValues(oldValue interface{}, newValue interface{}) error {
+	return uo.NewIterator().IterateLeafs(func(it *ObjectIterator) error {
+		if reflect.DeepEqual(it.Value(), oldValue) {
+			err := it.SetValue(newValue)
+			if err != nil {
+				return err
+			}
+		}
+		return nil
+	})
+}

From 37adcf1ee2b0e09f5099cb16baca5e40ee9849f7 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Wed, 23 Mar 2022 21:39:27 +0100
Subject: [PATCH 0584/2916] chore: Fix typo

---
 pkg/seal/secrets_loader.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/pkg/seal/secrets_loader.go b/pkg/seal/secrets_loader.go
index 3517fe1c7..55ebf0262 100644
--- a/pkg/seal/secrets_loader.go
+++ b/pkg/seal/secrets_loader.go
@@ -30,7 +30,7 @@ func (s *SecretsLoader) LoadSecrets(source *types.SecretSource) (*uo.Unstructure
 	} else if source.SystemEnvVars != nil {
 		return s.loadSecretsSystemEnvs(source)
 	} else if source.AwsSecretsManager != nil {
-		return s.loadSecretsAwsSecertsManager(source)
+		return s.loadSecretsAwsSecretsManager(source)
 	} else {
 		return nil, fmt.Errorf("invalid secrets entry")
 	}
@@ -92,7 +92,7 @@ func (s *SecretsLoader) loadSecretsSystemEnvs(source *types.SecretSource) (*uo.U
 	return secrets, nil
 }
 
-func (s *SecretsLoader) loadSecretsAwsSecertsManager(source *types.SecretSource) (*uo.UnstructuredObject, error) {
+func (s *SecretsLoader) loadSecretsAwsSecretsManager(source *types.SecretSource) (*uo.UnstructuredObject, error) {
 	secret, err := aws.GetAwsSecretsManagerSecret(source.AwsSecretsManager.Profile, source.AwsSecretsManager.Region, source.AwsSecretsManager.SecretName)
 	if err != nil {
 		return nil, err

From 066a1157cadeacd71519f3eab8ce42dd82cef3bf Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Wed, 23 Mar 2022 21:42:14 +0100
Subject: [PATCH 0585/2916] refactor: Move IsTerminal into AskForConfirmation

---
 pkg/git/auth/ssh_auth_provider.go         | 4 ++--
 pkg/git/auth/ssh_known_hosts.go           | 7 -------
 pkg/utils/{confirmation.go => prompts.go} | 6 ++++++
 3 files changed, 8 insertions(+), 9 deletions(-)
 rename pkg/utils/{confirmation.go => prompts.go} (88%)

diff --git a/pkg/git/auth/ssh_auth_provider.go b/pkg/git/auth/ssh_auth_provider.go
index 7375d78f2..e09bf934d 100644
--- a/pkg/git/auth/ssh_auth_provider.go
+++ b/pkg/git/auth/ssh_auth_provider.go
@@ -77,7 +77,7 @@ func (a *GitSshAuthProvider) BuildAuth(gitUrl git_url.GitUrl) transport.AuthMeth
 
 	auth := &sshDefaultIdentityAndAgent{
 		hostname: gitUrl.Hostname(),
-		user: gitUrl.User.Username(),
+		user:     gitUrl.User.Username(),
 	}
 
 	u, err := user.Current()
@@ -113,4 +113,4 @@ func readKey(path string) (ssh.Signer, error) {
 		}
 		return signer, nil
 	}
-}
\ No newline at end of file
+}
diff --git a/pkg/git/auth/ssh_known_hosts.go b/pkg/git/auth/ssh_known_hosts.go
index e23e005bb..1a0d8f3dd 100644
--- a/pkg/git/auth/ssh_known_hosts.go
+++ b/pkg/git/auth/ssh_known_hosts.go
@@ -4,8 +4,6 @@ import (
 	"fmt"
 	"github.com/codablock/kluctl/pkg/git/auth/goph"
 	"github.com/codablock/kluctl/pkg/utils"
-	"github.com/mattn/go-isatty"
-	log "github.com/sirupsen/logrus"
 	"golang.org/x/crypto/ssh"
 	"net"
 	"os"
@@ -65,11 +63,6 @@ func verifyHost(host string, remote net.Addr, key ssh.PublicKey) error {
 }
 
 func askIsHostTrusted(host string, key ssh.PublicKey) bool {
-	if !isatty.IsTerminal(os.Stderr.Fd()) {
-		log.Warningf("Not a terminal, suppressed prompt for unknown host")
-		return false
-	}
-
 	prompt := fmt.Sprintf("Unknown Host: %s\nFingerprint: %s\nWould you like to add it? ", host, ssh.FingerprintSHA256(key))
 	return utils.AskForConfirmation(prompt)
 }
diff --git a/pkg/utils/confirmation.go b/pkg/utils/prompts.go
similarity index 88%
rename from pkg/utils/confirmation.go
rename to pkg/utils/prompts.go
index 256182b0b..bc5bf7f64 100644
--- a/pkg/utils/confirmation.go
+++ b/pkg/utils/prompts.go
@@ -2,6 +2,7 @@ package utils
 
 import (
 	"fmt"
+	"github.com/mattn/go-isatty"
 	log "github.com/sirupsen/logrus"
 	"os"
 )
@@ -12,6 +13,11 @@ import (
 // until it gets a valid response from the user. Typically, you should use fmt to print out a question
 // before calling askForConfirmation. E.g. fmt.Println("WARNING: Are you sure? (yes/no)")
 func AskForConfirmation(prompt string) bool {
+	if !isatty.IsTerminal(os.Stderr.Fd()) {
+		log.Warningf("Not a terminal, suppressed prompt: %s", prompt)
+		return false
+	}
+
 	_, err := os.Stderr.WriteString(prompt + " (y/N) ")
 	if err != nil {
 		log.Fatal(err)

From 94019917518b9c50bd4467c0892ded7e1097044b Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Thu, 24 Mar 2022 08:13:21 +0100
Subject: [PATCH 0586/2916] chore: Add e2e test binaries to .gitignore

---
 .gitignore | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.gitignore b/.gitignore
index bafcc0f04..1c81eae77 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,3 +11,4 @@
 /download-python
 /kluctl
 /kluctl.exe
+/e2e.test*
\ No newline at end of file

From cf73ee609bb62c03ab66940732f3617d1cab8346 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Thu, 24 Mar 2022 08:28:44 +0100
Subject: [PATCH 0587/2916] ci: No need to delete kind clusters anymore

---
 .github/workflows/build-and-release.yml | 4 ----
 hack/start-kind-cluster.sh              | 1 -
 2 files changed, 5 deletions(-)

diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml
index f0c8f4179..f08e484b1 100644
--- a/.github/workflows/build-and-release.yml
+++ b/.github/workflows/build-and-release.yml
@@ -333,10 +333,6 @@ jobs:
         run: |
           chmod +x ./dist/*
           ./dist/e2e.test-${{ matrix.binary-suffix }}$TOOLS_EXE -test.v
-      - name: Delete kind cluster
-        shell: bash
-        run: |
-          kind delete cluster --name $KIND_CLUSTER_NAME
 
   release:
     runs-on: ubuntu-latest
diff --git a/hack/start-kind-cluster.sh b/hack/start-kind-cluster.sh
index 232347f0d..3c7e737ee 100755
--- a/hack/start-kind-cluster.sh
+++ b/hack/start-kind-cluster.sh
@@ -16,7 +16,6 @@ EOF
 
 rm -f $(pwd)/kind-kubeconfig
 export KUBECONFIG=$KIND_KUBECONFIG
-kind delete cluster --name $NAME || true
 kind create cluster --config kind-cluster.yml --name $NAME
 
 # Rewrite cluster info to point to docker host

From efc25f7512c0cf4ce525c82a6991e0b27da3b301 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Thu, 24 Mar 2022 08:43:50 +0100
Subject: [PATCH 0588/2916] ci: Also cache go build cache

---
 .github/workflows/build-and-release.yml | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml
index f08e484b1..7b4f62c32 100644
--- a/.github/workflows/build-and-release.yml
+++ b/.github/workflows/build-and-release.yml
@@ -75,7 +75,9 @@ jobs:
           python-version: 3.10.2
       - uses: actions/cache@v2
         with:
-          path: ~/go/pkg/mod
+          path: |
+            ~/go/pkg/mod
+            ~/.cache/go-build
           key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
           restore-keys: |
             ${{ runner.os }}-go-

From 95e2025f95d96e1ec77a3d1d2de397048517f612 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Thu, 24 Mar 2022 08:44:04 +0100
Subject: [PATCH 0589/2916] ci: Stop stripping binaries

---
 .github/workflows/build-and-release.yml | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml
index 7b4f62c32..32dc5f760 100644
--- a/.github/workflows/build-and-release.yml
+++ b/.github/workflows/build-and-release.yml
@@ -114,7 +114,7 @@ jobs:
           export CGO_ENABLED=0
           export GOARCH=amd64
           export GOOS=linux
-          go build -ldflags "-s -w" ./cmd/kluctl
+          go build ./cmd/kluctl
           go test -c ./e2e
           mv kluctl kluctl-linux-amd64
           mv e2e.test e2e.test-linux-amd64
@@ -123,7 +123,7 @@ jobs:
           export CGO_ENABLED=0
           export GOARCH=amd64
           export GOOS=darwin
-          go build -ldflags "-s -w" ./cmd/kluctl
+          go build ./cmd/kluctl
           go test -c ./e2e
           mv kluctl kluctl-darwin-amd64
           mv e2e.test e2e.test-darwin-amd64
@@ -132,7 +132,7 @@ jobs:
           export CGO_ENABLED=0
           export GOARCH=amd64
           export GOOS=windows
-          go build -ldflags "-s -w" ./cmd/kluctl
+          go build ./cmd/kluctl
           go test -c ./e2e
           mv kluctl.exe kluctl-windows-amd64.exe
           mv e2e.test.exe e2e.test-windows-amd64.exe

From fba87bb18d19adbc10fb5239137d8f13b0320a56 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Thu, 24 Mar 2022 09:04:13 +0100
Subject: [PATCH 0590/2916] ci: Move version steps into build job

---
 .github/workflows/build-and-release.yml | 84 +++++++------------------
 1 file changed, 24 insertions(+), 60 deletions(-)

diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml
index 32dc5f760..38ee611f2 100644
--- a/.github/workflows/build-and-release.yml
+++ b/.github/workflows/build-and-release.yml
@@ -8,61 +8,10 @@ on:
       - 'v*'
 
 jobs:
-  version:
+  build:
     runs-on: ubuntu-latest
     outputs:
       version: ${{ steps.get_version.outputs.version }}
-      tags: ${{ steps.gen_tags.outputs.tags }}
-    steps:
-      - name: Install packages
-        run: |
-          sudo apt update
-          sudo apt install -y git
-      - name: Checkout
-        uses: actions/checkout@v2
-        with:
-          fetch-depth: 0
-      - name: Get sv4git
-        run: |
-          wget -O /tmp/sv4git.zip https://github.com/bvieira/sv4git/releases/download/v2.4.0/git-sv_2.4.0_linux_amd64.tar.gz
-          cd /usr/local/bin
-          tar xzf /tmp/sv4git.zip
-          chmod +x git-sv
-      - name: Get Version
-        id: get_version
-        run: |
-          VERSION="$(git sv next-version)"
-          if [[ "${{ github.ref }}" != refs/tags/* ]]; then
-            VERSION="$VERSION-dev-$(git rev-parse --short HEAD)"
-          fi
-          echo "VERSION=$VERSION"
-          echo "::set-output name=version::$VERSION"
-      - name: Generate tags
-        id: gen_tags
-        run: |
-          IMAGE=codablock/kluctl
-          if [[ "${{ github.ref }}" == refs/tags/* ]]; then
-            TAGS="$IMAGE:$(echo "${{ github.ref }}" | sed 's|refs/tags/||')"
-          else
-            TAGS="$IMAGE:$(echo "${{ github.ref }}" | sed 's|refs/heads/||')"
-          fi
-          echo TAGS=$TAGS
-          echo "::set-output name=tags::$TAGS"
-      - name: Generate Changelog
-        id: gen_changelog
-        run: |
-          git sv changelog -n 1 > CHANGELOG.md
-      - name: Upload Changelog
-        uses: actions/upload-artifact@v2
-        with:
-          name: changelog
-          path: |
-            CHANGELOG.md
-
-  build:
-    runs-on: ubuntu-latest
-    needs:
-      - version
     steps:
       - name: Checkout
         uses: actions/checkout@v2
@@ -86,9 +35,30 @@ jobs:
           ./hack/download-python.sh linux
           ./hack/download-python.sh darwin
           ./hack/download-python.sh windows
+      - name: Get Version
+        id: get_version
+        run: |
+          go install github.com/bvieira/sv4git/v2/cmd/git-sv@v2.7.0
+          KLUCTL_VERSION="$(git sv next-version)"
+          if [[ "${{ github.ref }}" != refs/tags/* ]]; then
+            KLUCTL_VERSION="$KLUCTL_VERSION-dev-$(git rev-parse --short HEAD)"
+          fi
+          echo "KLUCTL_VERSION=$KLUCTL_VERSION"
+          echo "KLUCTL_VERSION=$KLUCTL_VERSION" >> $GITHUB_ENV
+          echo "::set-output name=version::$VERSION"
+      - name: Generate Changelog
+        id: gen_changelog
+        run: |
+          git sv changelog -n 1 > CHANGELOG.md
+      - name: Upload Changelog
+        uses: actions/upload-artifact@v2
+        with:
+          name: changelog
+          path: |
+            CHANGELOG.md
       - name: Write Version to version.go
         run: |
-          sed -ibak 's/0.0.0/${{ needs.version.outputs.version }}/g' pkg/version/version.go
+          sed -ibak "s/0.0.0/$KLUCTL_VERSION/g" pkg/version/version.go
           cat pkg/version/version.go
       - name: Go Mod Vendor
         run: |
@@ -213,7 +183,6 @@ jobs:
         os: [ubuntu-18.04, macos-10.15, windows-2019]
       fail-fast: false
     needs:
-      - version
       - build
     runs-on: ${{ matrix.os }}
     steps:
@@ -340,16 +309,11 @@ jobs:
     runs-on: ubuntu-latest
     if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
     needs:
-      - version
       - build
     steps:
       - name: Checkout
         uses: actions/checkout@v2
-      - name: Download changelog artifacts
-        uses: actions/download-artifact@v2
-        with:
-          name: changelog
-      - name: Download dist artifacts
+      - name: Download artifacts
         uses: actions/download-artifact@v2
       - name: Release
         uses: softprops/action-gh-release@v1

From ad8fb1516accef648be9214bdf7cbc1531a5348c Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Thu, 24 Mar 2022 09:09:03 +0100
Subject: [PATCH 0591/2916] ci: Set fetch-depth to 0

---
 .github/workflows/build-and-release.yml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml
index 38ee611f2..50affd659 100644
--- a/.github/workflows/build-and-release.yml
+++ b/.github/workflows/build-and-release.yml
@@ -15,6 +15,8 @@ jobs:
     steps:
       - name: Checkout
         uses: actions/checkout@v2
+        with:
+          fetch-depth: 0
       - uses: actions/setup-go@v2
         with:
           go-version: '1.17.6'

From a3f5403e34701cd64a8f02cbe21c9104fa288506 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Thu, 24 Mar 2022 12:40:33 +0100
Subject: [PATCH 0592/2916] fix: Support both yml and yaml extensions for
 project related files

---
 cmd/kluctl/commands/cmd_helm_pull.go   |  2 +-
 cmd/kluctl/commands/cmd_helm_update.go |  2 +-
 pkg/deployment/deployment_item.go      |  8 +++----
 pkg/deployment/deployment_project.go   | 11 +++++----
 pkg/deployment/external_args.go        |  4 ++--
 pkg/deployment/helm_chart.go           |  2 +-
 pkg/kluctl_project/load.go             |  8 +++----
 pkg/kluctl_project/load_targets.go     |  2 +-
 pkg/types/cluster_config.go            |  2 +-
 pkg/yaml/yaml.go                       | 32 ++++++++++++++++++++++++++
 10 files changed, 53 insertions(+), 20 deletions(-)

diff --git a/cmd/kluctl/commands/cmd_helm_pull.go b/cmd/kluctl/commands/cmd_helm_pull.go
index 4f4ff5e4b..babb24afe 100644
--- a/cmd/kluctl/commands/cmd_helm_pull.go
+++ b/cmd/kluctl/commands/cmd_helm_pull.go
@@ -24,7 +24,7 @@ func (cmd *helmPullCmd) Run() error {
 	}
 	err := filepath.WalkDir(rootPath, func(p string, d fs.DirEntry, err error) error {
 		fname := filepath.Base(p)
-		if fname == "helm-chart.yml" {
+		if fname == "helm-chart.yml" || fname == "helm-chart.yaml" {
 			log.Infof("Pulling for %s", p)
 			chart, err := deployment.NewHelmChart(p)
 			if err != nil {
diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go
index 07b251f98..2ce823324 100644
--- a/cmd/kluctl/commands/cmd_helm_update.go
+++ b/cmd/kluctl/commands/cmd_helm_update.go
@@ -26,7 +26,7 @@ func (cmd *helmUpdateCmd) Run() error {
 	}
 	err := filepath.WalkDir(rootPath, func(p string, d fs.DirEntry, err error) error {
 		fname := filepath.Base(p)
-		if fname == "helm-chart.yml" {
+		if fname == "helm-chart.yml" || fname == "helm-chart.yaml" {
 			chart, err := deployment.NewHelmChart(p)
 			if err != nil {
 				return err
diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go
index cac96677a..89b56a4af 100644
--- a/pkg/deployment/deployment_item.go
+++ b/pkg/deployment/deployment_item.go
@@ -129,7 +129,7 @@ func (di *DeploymentItem) render(k *k8s.K8sCluster, forSeal bool, wp *utils.Work
 			if err != nil {
 				return err
 			}
-			if utils.IsFile(filepath.Join(p, "helm-chart.yml")) {
+			if yaml.Exists(filepath.Join(p, "helm-chart.yml")) {
 				// never try to render helm charts
 				ep := filepath.Join(di.RelToProjectItemDir, relDir, "charts/**")
 				ep = strings.ReplaceAll(ep, string(os.PathSeparator), "/")
@@ -161,7 +161,7 @@ func (di *DeploymentItem) renderHelmCharts(k *k8s.K8sCluster, wp *utils.WorkerPo
 	}
 
 	err := filepath.Walk(di.renderedDir, func(p string, info fs.FileInfo, err error) error {
-		if !strings.HasSuffix(p, "helm-chart.yml") {
+		if !strings.HasSuffix(p, "helm-chart.yml") && !strings.HasSuffix(p, "helm-chart.yaml") {
 			return nil
 		}
 
@@ -190,7 +190,7 @@ func (di *DeploymentItem) resolveSealedSecrets() error {
 	sealedSecretsDir := di.Project.getSealedSecretsDir()
 	baseSourcePath := di.Project.SealedSecretsDir
 
-	y, err := uo.FromFile(filepath.Join(di.renderedDir, "kustomization.yml"))
+	y, err := uo.FromFile(yaml.FixPathExt(filepath.Join(di.renderedDir, "kustomization.yml")))
 	if err != nil {
 		return err
 	}
@@ -278,7 +278,7 @@ func (di *DeploymentItem) prepareKustomizationYaml() error {
 		return nil
 	}
 
-	kustomizeYamlPath := filepath.Join(di.renderedDir, "kustomization.yml")
+	kustomizeYamlPath := yaml.FixPathExt(filepath.Join(di.renderedDir, "kustomization.yml"))
 	if !utils.IsFile(kustomizeYamlPath) {
 		return nil
 	}
diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go
index 8d4d83e04..350e3e7d0 100644
--- a/pkg/deployment/deployment_project.go
+++ b/pkg/deployment/deployment_project.go
@@ -7,6 +7,7 @@ import (
 	"github.com/codablock/kluctl/pkg/types"
 	"github.com/codablock/kluctl/pkg/utils"
 	"github.com/codablock/kluctl/pkg/utils/uo"
+	"github.com/codablock/kluctl/pkg/yaml"
 	log "github.com/sirupsen/logrus"
 	"path/filepath"
 	"reflect"
@@ -62,14 +63,14 @@ func (p *DeploymentProject) MergeSecretsIntoAllChildren(vars *uo.UnstructuredObj
 
 func (p *DeploymentProject) loadConfig(k *k8s.K8sCluster) error {
 	configPath := filepath.Join(p.dir, "deployment.yml")
-	if !utils.Exists(configPath) {
-		if utils.Exists(filepath.Join(p.dir, "kustomization.yml")) {
+	if !yaml.Exists(configPath) {
+		if yaml.Exists(filepath.Join(p.dir, "kustomization.yml")) {
 			return fmt.Errorf("deployment.yml not found but folder %s contains a kustomization.yml", p.dir)
 		}
 		return fmt.Errorf("%s not found", p.dir)
 	}
 
-	err := p.VarsCtx.RenderYamlFile("deployment.yml", p.getRenderSearchDirs(), &p.Config)
+	err := p.VarsCtx.RenderYamlFile(yaml.FixNameExt(p.dir, "deployment.yml"), p.getRenderSearchDirs(), &p.Config)
 	if err != nil {
 		return fmt.Errorf("failed to load deployment.yml: %w", err)
 	}
@@ -170,9 +171,9 @@ func (p *DeploymentProject) checkDeploymentDirs() error {
 		}
 
 		if di.Path != nil {
-			pth = filepath.Join(diDir, "kustomization.yml")
+			pth = yaml.FixPathExt(filepath.Join(diDir, "kustomization.yml"))
 		} else {
-			pth = filepath.Join(diDir, "deployment.yml")
+			pth = yaml.FixPathExt(filepath.Join(diDir, "deployment.yml"))
 		}
 		if !utils.IsFile(pth) {
 			return fmt.Errorf("%s not found or not a file", pth)
diff --git a/pkg/deployment/external_args.go b/pkg/deployment/external_args.go
index 582e5592c..649066183 100644
--- a/pkg/deployment/external_args.go
+++ b/pkg/deployment/external_args.go
@@ -47,14 +47,14 @@ func CheckRequiredDeployArgs(dir string, varsCtx *jinja2.VarsCtx, deployArgs *uo
 
 	var conf types.DeploymentProjectConfig
 
-	err := yaml.ReadYamlFile(filepath.Join(dir, "deployment.yml"), &conf)
+	err := yaml.ReadYamlFile(yaml.FixPathExt(filepath.Join(dir, "deployment.yml")), &conf)
 	if err != nil {
 		// If that failed, it might be that conditional jinja blocks are present in the config, so lets try loading
 		// the config in rendered form. If it fails due to missing args now, we can't help much with better error
 		// messages anymore.
 		varsCtx2 := varsCtx.Copy()
 		varsCtx2.UpdateChild("args", deployArgs)
-		err = varsCtx2.RenderYamlFile("deployment.yml", []string{dir}, &conf)
+		err = varsCtx2.RenderYamlFile(yaml.FixNameExt(dir,"deployment.yml"), []string{dir}, &conf)
 		if err != nil {
 			return err
 		}
diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go
index 36e5a7876..d5ff98735 100644
--- a/pkg/deployment/helm_chart.go
+++ b/pkg/deployment/helm_chart.go
@@ -157,7 +157,7 @@ func (c *helmChart) Render(k *k8s.K8sCluster) error {
 	}
 	dir := filepath.Dir(c.configFile)
 	chartDir := filepath.Join(dir, "charts", chartName)
-	valuesPath := filepath.Join(dir, "helm-values.yml")
+	valuesPath := yaml.FixPathExt(filepath.Join(dir, "helm-values.yml"))
 	outputPath := filepath.Join(dir, c.Config.Output)
 
 	args := []string{"template", c.Config.ReleaseName, chartDir}
diff --git a/pkg/kluctl_project/load.go b/pkg/kluctl_project/load.go
index fba1e6668..865744ff9 100644
--- a/pkg/kluctl_project/load.go
+++ b/pkg/kluctl_project/load.go
@@ -46,7 +46,7 @@ func (c *KluctlProjectContext) mergeClustersDirs(mergedClustersDir string, clust
 func (c *KluctlProjectContext) getConfigPath(projectDir string) string {
 	configPath := c.loadArgs.ProjectConfig
 	if configPath == "" {
-		p := filepath.Join(projectDir, ".kluctl.yml")
+		p := yaml.FixPathExt(filepath.Join(projectDir, ".kluctl.yml"))
 		if utils.IsFile(p) {
 			configPath = p
 		}
@@ -185,7 +185,7 @@ func loadKluctlProjectFromArchive(args LoadKluctlProjectArgs, tmpDir string) (*K
 	if args.FromArchiveMetadata != "" {
 		metdataPath = args.FromArchiveMetadata
 	} else {
-		metdataPath = filepath.Join(dir, "metadata.yml")
+		metdataPath = yaml.FixPathExt(filepath.Join(dir, "metadata.yml"))
 	}
 
 	var metadata types2.ArchiveMetadata
@@ -196,7 +196,7 @@ func loadKluctlProjectFromArchive(args LoadKluctlProjectArgs, tmpDir string) (*K
 
 	p := NewKluctlProjectContext(
 		LoadKluctlProjectArgs{
-			ProjectConfig:      filepath.Join(dir, ".kluctl.yml"),
+			ProjectConfig:      yaml.FixPathExt(filepath.Join(dir, ".kluctl.yml")),
 			LocalClusters:      filepath.Join(dir, "clusters"),
 			LocalDeployment:    filepath.Join(dir, "deployment"),
 			LocalSealedSecrets: filepath.Join(dir, "sealed-secrets"),
@@ -261,7 +261,7 @@ func (c *KluctlProjectContext) CreateTGZArchive(archivePath string, metadataPath
 		}
 	}
 
-	if err = utils.AddToTar(tw, c.getConfigPath(c.ProjectDir), ".kluctl.yml", filter); err != nil {
+	if err = utils.AddToTar(tw, c.getConfigPath(c.ProjectDir), yaml.FixNameExt(c.ProjectDir,".kluctl.yml"), filter); err != nil {
 		return err
 	}
 	if err = utils.AddToTar(tw, c.ProjectDir, "kluctl-project", filter); err != nil {
diff --git a/pkg/kluctl_project/load_targets.go b/pkg/kluctl_project/load_targets.go
index 4967a9b7c..50643e6d0 100644
--- a/pkg/kluctl_project/load_targets.go
+++ b/pkg/kluctl_project/load_targets.go
@@ -307,7 +307,7 @@ func (c *KluctlProjectContext) buildDynamicTarget(targetInfo *dynamicTargetInfo)
 		return &target, nil
 	}
 
-	configFile := "target-config.yml"
+	configFile := yaml.FixNameExt(targetInfo.dir, "target-config.yml")
 	if targetInfo.baseTarget.TargetConfig.File != nil {
 		configFile = *targetInfo.baseTarget.TargetConfig.File
 	}
diff --git a/pkg/types/cluster_config.go b/pkg/types/cluster_config.go
index dcee38010..956ba7527 100644
--- a/pkg/types/cluster_config.go
+++ b/pkg/types/cluster_config.go
@@ -59,7 +59,7 @@ func LoadClusterConfig(clusterDir string, clusterName string) (*ClusterConfig, e
 		return nil, fmt.Errorf("cluster name must be specified")
 	}
 
-	p := filepath.Join(clusterDir, fmt.Sprintf("%s.yml", clusterName))
+	p := yaml.FixPathExt(filepath.Join(clusterDir, fmt.Sprintf("%s.yml", clusterName)))
 	if !utils.IsFile(p) {
 		return nil, fmt.Errorf("cluster config for %s not found", clusterName)
 	}
diff --git a/pkg/yaml/yaml.go b/pkg/yaml/yaml.go
index bd532e111..39aee6984 100644
--- a/pkg/yaml/yaml.go
+++ b/pkg/yaml/yaml.go
@@ -5,10 +5,12 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
+	"github.com/codablock/kluctl/pkg/utils"
 	"github.com/goccy/go-yaml"
 	yaml3 "gopkg.in/yaml.v3"
 	"io"
 	"os"
+	"path/filepath"
 	"strings"
 )
 
@@ -166,3 +168,33 @@ func ConvertYamlToJson(b []byte) ([]byte, error) {
 	}
 	return b, nil
 }
+
+func FixNameExt(dir string, name string) string {
+	p := filepath.Join(dir, name)
+	p = FixPathExt(p)
+	return filepath.Base(p)
+}
+
+func FixPathExt(p string) string {
+	if utils.Exists(p) {
+		return p
+	}
+	var p2 string
+	if strings.HasSuffix(p, ".yml") {
+		p2 = p[:len(p)-4] + ".yaml"
+	} else if strings.HasSuffix(p, ".yaml") {
+		p2 = p[:len(p)-5] + ".yml"
+	} else {
+		return p
+	}
+
+	if utils.Exists(p2) {
+		return p2
+	}
+	return p
+}
+
+func Exists(p string) bool {
+	p = FixPathExt(p)
+	return utils.Exists(p)
+}

From ccee8f58d490f54e4ee9c9eaa79791fc937174ae Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Thu, 24 Mar 2022 13:57:53 +0100
Subject: [PATCH 0593/2916] feat: Move kluctl project to own orga

---
 cmd/kluctl/args/images.go                      |  4 ++--
 cmd/kluctl/args/inclusion.go                   |  2 +-
 cmd/kluctl/commands/cmd_archive.go             |  4 ++--
 cmd/kluctl/commands/cmd_check_image_updates.go | 10 +++++-----
 cmd/kluctl/commands/cmd_delete.go              | 16 ++++++++--------
 cmd/kluctl/commands/cmd_deploy.go              |  6 +++---
 cmd/kluctl/commands/cmd_diff.go                |  4 ++--
 cmd/kluctl/commands/cmd_downscale.go           |  6 +++---
 cmd/kluctl/commands/cmd_helm_pull.go           |  2 +-
 cmd/kluctl/commands/cmd_helm_update.go         |  2 +-
 cmd/kluctl/commands/cmd_list_images.go         |  4 ++--
 cmd/kluctl/commands/cmd_list_targets.go        |  6 +++---
 cmd/kluctl/commands/cmd_poke_images.go         |  6 +++---
 cmd/kluctl/commands/cmd_prune.go               |  4 ++--
 cmd/kluctl/commands/cmd_render.go              |  4 ++--
 cmd/kluctl/commands/cmd_seal.go                | 12 ++++++------
 cmd/kluctl/commands/cmd_validate.go            |  4 ++--
 cmd/kluctl/commands/cmd_version.go             |  2 +-
 cmd/kluctl/commands/command_result.go          |  8 ++++----
 cmd/kluctl/commands/root.go                    | 12 ++++++------
 cmd/kluctl/commands/utils.go                   | 16 ++++++++--------
 cmd/kluctl/main.go                             |  2 +-
 docs/kluctl_project.md                         |  6 +++---
 e2e/cmd_wrapper_test.go                        |  2 +-
 e2e/external_projects_test.go                  |  2 +-
 e2e/hooks_test.go                              |  2 +-
 e2e/kind_cluster.go                            |  6 +++---
 e2e/project.go                                 |  8 ++++----
 e2e/utils_resources.go                         |  2 +-
 e2e/utils_test.go                              |  4 ++--
 go.mod                                         |  2 +-
 hack/replace-commands-help/main.go             |  2 +-
 pkg/deployment/commands/delete.go              |  8 ++++----
 pkg/deployment/commands/deploy.go              |  8 ++++----
 pkg/deployment/commands/diff.go                |  8 ++++----
 pkg/deployment/commands/downscale.go           | 12 ++++++------
 pkg/deployment/commands/poke_images.go         | 12 ++++++------
 pkg/deployment/commands/prune.go               |  8 ++++----
 pkg/deployment/commands/seal.go                |  4 ++--
 pkg/deployment/commands/validate.go            | 12 ++++++------
 pkg/deployment/deployment_collection.go        |  8 ++++----
 pkg/deployment/deployment_item.go              | 10 +++++-----
 pkg/deployment/deployment_project.go           | 12 ++++++------
 pkg/deployment/external_args.go                |  8 ++++----
 pkg/deployment/helm_chart.go                   | 12 ++++++------
 pkg/deployment/images.go                       | 16 ++++++++--------
 pkg/deployment/utils/apply_utils.go            | 16 ++++++++--------
 pkg/deployment/utils/delete_utils.go           |  8 ++++----
 pkg/deployment/utils/diff_utils.go             | 10 +++++-----
 pkg/deployment/utils/diff_utils_test.go        |  8 ++++----
 pkg/deployment/utils/downscale_utils.go        |  4 ++--
 pkg/deployment/utils/errors_holder.go          |  8 ++++----
 pkg/deployment/utils/hooks_util.go             |  8 ++++----
 pkg/deployment/utils/remote_objects_utils.go   |  8 ++++----
 pkg/diff/diff.go                               |  6 +++---
 pkg/diff/managed_fields.go                     |  2 +-
 pkg/diff/normalize.go                          |  4 ++--
 pkg/git/auth/auth_provider.go                  |  2 +-
 pkg/git/auth/env_auth_provider.go              |  4 ++--
 pkg/git/auth/git_credentials_file.go           |  4 ++--
 pkg/git/auth/ssh_auth_provider.go              |  4 ++--
 pkg/git/auth/ssh_known_hosts.go                |  4 ++--
 pkg/git/http-server/http.go                    |  2 +-
 pkg/git/http-server/utils.go                   |  2 +-
 pkg/git/mirrored_repo.go                       |  6 +++---
 pkg/git/poor_mans_clone.go                     |  2 +-
 pkg/jinja2/jinja2.go                           |  4 ++--
 pkg/jinja2/jinja2_renderer.go                  |  4 ++--
 pkg/jinja2/source.go                           |  4 ++--
 pkg/jinja2/vars.go                             | 10 +++++-----
 pkg/k8s/k8s_cluster.go                         |  8 ++++----
 pkg/kluctl_project/git.go                      |  8 ++++----
 pkg/kluctl_project/load.go                     |  6 +++---
 pkg/kluctl_project/load_targets.go             | 12 ++++++------
 pkg/kluctl_project/project.go                  |  8 ++++----
 pkg/python/embed.go                            |  4 ++--
 pkg/registries/registries.go                   |  4 ++--
 pkg/seal/bootstrap.go                          |  6 +++---
 pkg/seal/fetch_cert.go                         |  2 +-
 pkg/seal/sealer.go                             | 12 ++++++------
 pkg/seal/secrets_loader.go                     | 10 +++++-----
 pkg/types/cluster_config.go                    |  6 +++---
 pkg/types/command_result.go                    |  4 ++--
 pkg/types/deployment.go                        |  4 ++--
 pkg/types/git_project.go                       |  4 ++--
 pkg/types/kluctl_project.go                    |  4 ++--
 pkg/types/secrets_source.go                    |  2 +-
 pkg/types/target_config.go                     |  4 ++--
 pkg/utils/embed_util/extract.go                |  2 +-
 pkg/utils/embed_util/packer/main.go            |  2 +-
 pkg/utils/uo/k8s_fields.go                     |  2 +-
 pkg/utils/uo/unstructured.go                   |  2 +-
 pkg/utils/uo/uo.go                             |  2 +-
 pkg/utils/versions/latest_version.go           |  2 +-
 pkg/utils/versions/looseversion.go             |  2 +-
 pkg/validation/validation.go                   |  6 +++---
 pkg/yaml/yaml.go                               |  2 +-
 97 files changed, 287 insertions(+), 287 deletions(-)

diff --git a/cmd/kluctl/args/images.go b/cmd/kluctl/args/images.go
index fc2d39ece..6aabbeac2 100644
--- a/cmd/kluctl/args/images.go
+++ b/cmd/kluctl/args/images.go
@@ -2,8 +2,8 @@ package args
 
 import (
 	"fmt"
-	"github.com/codablock/kluctl/pkg/types"
-	"github.com/codablock/kluctl/pkg/yaml"
+	"github.com/kluctl/kluctl/pkg/types"
+	"github.com/kluctl/kluctl/pkg/yaml"
 	"strings"
 )
 
diff --git a/cmd/kluctl/args/inclusion.go b/cmd/kluctl/args/inclusion.go
index 993f10af7..85670f009 100644
--- a/cmd/kluctl/args/inclusion.go
+++ b/cmd/kluctl/args/inclusion.go
@@ -2,7 +2,7 @@ package args
 
 import (
 	"fmt"
-	"github.com/codablock/kluctl/pkg/utils"
+	"github.com/kluctl/kluctl/pkg/utils"
 	"os"
 	"path/filepath"
 	"strings"
diff --git a/cmd/kluctl/commands/cmd_archive.go b/cmd/kluctl/commands/cmd_archive.go
index 7fa30624b..dbe3bb83c 100644
--- a/cmd/kluctl/commands/cmd_archive.go
+++ b/cmd/kluctl/commands/cmd_archive.go
@@ -1,8 +1,8 @@
 package commands
 
 import (
-	"github.com/codablock/kluctl/cmd/kluctl/args"
-	"github.com/codablock/kluctl/pkg/kluctl_project"
+	"github.com/kluctl/kluctl/cmd/kluctl/args"
+	"github.com/kluctl/kluctl/pkg/kluctl_project"
 )
 
 type archiveCmd struct {
diff --git a/cmd/kluctl/commands/cmd_check_image_updates.go b/cmd/kluctl/commands/cmd_check_image_updates.go
index bd6e06dfc..85ecfce71 100644
--- a/cmd/kluctl/commands/cmd_check_image_updates.go
+++ b/cmd/kluctl/commands/cmd_check_image_updates.go
@@ -1,11 +1,11 @@
 package commands
 
 import (
-	"github.com/codablock/kluctl/cmd/kluctl/args"
-	"github.com/codablock/kluctl/pkg/registries"
-	"github.com/codablock/kluctl/pkg/types/k8s"
-	"github.com/codablock/kluctl/pkg/utils"
-	"github.com/codablock/kluctl/pkg/utils/versions"
+	"github.com/kluctl/kluctl/cmd/kluctl/args"
+	"github.com/kluctl/kluctl/pkg/registries"
+	"github.com/kluctl/kluctl/pkg/types/k8s"
+	"github.com/kluctl/kluctl/pkg/utils"
+	"github.com/kluctl/kluctl/pkg/utils/versions"
 	log "github.com/sirupsen/logrus"
 	"os"
 	"regexp"
diff --git a/cmd/kluctl/commands/cmd_delete.go b/cmd/kluctl/commands/cmd_delete.go
index 3f89e0288..8374968cf 100644
--- a/cmd/kluctl/commands/cmd_delete.go
+++ b/cmd/kluctl/commands/cmd_delete.go
@@ -2,14 +2,14 @@ package commands
 
 import (
 	"fmt"
-	"github.com/codablock/kluctl/cmd/kluctl/args"
-	"github.com/codablock/kluctl/pkg/deployment"
-	"github.com/codablock/kluctl/pkg/deployment/commands"
-	"github.com/codablock/kluctl/pkg/deployment/utils"
-	"github.com/codablock/kluctl/pkg/k8s"
-	"github.com/codablock/kluctl/pkg/types"
-	k8s2 "github.com/codablock/kluctl/pkg/types/k8s"
-	utils2 "github.com/codablock/kluctl/pkg/utils"
+	"github.com/kluctl/kluctl/cmd/kluctl/args"
+	"github.com/kluctl/kluctl/pkg/deployment"
+	"github.com/kluctl/kluctl/pkg/deployment/commands"
+	"github.com/kluctl/kluctl/pkg/deployment/utils"
+	"github.com/kluctl/kluctl/pkg/k8s"
+	"github.com/kluctl/kluctl/pkg/types"
+	k8s2 "github.com/kluctl/kluctl/pkg/types/k8s"
+	utils2 "github.com/kluctl/kluctl/pkg/utils"
 	"os"
 )
 
diff --git a/cmd/kluctl/commands/cmd_deploy.go b/cmd/kluctl/commands/cmd_deploy.go
index 1af706831..09b3030b1 100644
--- a/cmd/kluctl/commands/cmd_deploy.go
+++ b/cmd/kluctl/commands/cmd_deploy.go
@@ -2,9 +2,9 @@ package commands
 
 import (
 	"fmt"
-	"github.com/codablock/kluctl/cmd/kluctl/args"
-	"github.com/codablock/kluctl/pkg/deployment/commands"
-	"github.com/codablock/kluctl/pkg/utils"
+	"github.com/kluctl/kluctl/cmd/kluctl/args"
+	"github.com/kluctl/kluctl/pkg/deployment/commands"
+	"github.com/kluctl/kluctl/pkg/utils"
 )
 
 type deployCmd struct {
diff --git a/cmd/kluctl/commands/cmd_diff.go b/cmd/kluctl/commands/cmd_diff.go
index 80453ef29..840617dd9 100644
--- a/cmd/kluctl/commands/cmd_diff.go
+++ b/cmd/kluctl/commands/cmd_diff.go
@@ -2,8 +2,8 @@ package commands
 
 import (
 	"fmt"
-	"github.com/codablock/kluctl/cmd/kluctl/args"
-	"github.com/codablock/kluctl/pkg/deployment/commands"
+	"github.com/kluctl/kluctl/cmd/kluctl/args"
+	"github.com/kluctl/kluctl/pkg/deployment/commands"
 )
 
 type diffCmd struct {
diff --git a/cmd/kluctl/commands/cmd_downscale.go b/cmd/kluctl/commands/cmd_downscale.go
index 37ae3515f..184533519 100644
--- a/cmd/kluctl/commands/cmd_downscale.go
+++ b/cmd/kluctl/commands/cmd_downscale.go
@@ -2,9 +2,9 @@ package commands
 
 import (
 	"fmt"
-	"github.com/codablock/kluctl/cmd/kluctl/args"
-	"github.com/codablock/kluctl/pkg/deployment/commands"
-	"github.com/codablock/kluctl/pkg/utils"
+	"github.com/kluctl/kluctl/cmd/kluctl/args"
+	"github.com/kluctl/kluctl/pkg/deployment/commands"
+	"github.com/kluctl/kluctl/pkg/utils"
 )
 
 type downscaleCmd struct {
diff --git a/cmd/kluctl/commands/cmd_helm_pull.go b/cmd/kluctl/commands/cmd_helm_pull.go
index babb24afe..b2dcc01b8 100644
--- a/cmd/kluctl/commands/cmd_helm_pull.go
+++ b/cmd/kluctl/commands/cmd_helm_pull.go
@@ -1,7 +1,7 @@
 package commands
 
 import (
-	"github.com/codablock/kluctl/pkg/deployment"
+	"github.com/kluctl/kluctl/pkg/deployment"
 	log "github.com/sirupsen/logrus"
 	"io/fs"
 	"path/filepath"
diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go
index 2ce823324..dc6c768b5 100644
--- a/cmd/kluctl/commands/cmd_helm_update.go
+++ b/cmd/kluctl/commands/cmd_helm_update.go
@@ -2,7 +2,7 @@ package commands
 
 import (
 	"fmt"
-	"github.com/codablock/kluctl/pkg/deployment"
+	"github.com/kluctl/kluctl/pkg/deployment"
 	"github.com/go-git/go-git/v5"
 	log "github.com/sirupsen/logrus"
 	"io/fs"
diff --git a/cmd/kluctl/commands/cmd_list_images.go b/cmd/kluctl/commands/cmd_list_images.go
index d08080247..eeeb58986 100644
--- a/cmd/kluctl/commands/cmd_list_images.go
+++ b/cmd/kluctl/commands/cmd_list_images.go
@@ -1,8 +1,8 @@
 package commands
 
 import (
-	"github.com/codablock/kluctl/cmd/kluctl/args"
-	"github.com/codablock/kluctl/pkg/types"
+	"github.com/kluctl/kluctl/cmd/kluctl/args"
+	"github.com/kluctl/kluctl/pkg/types"
 )
 
 type listImagesCmd struct {
diff --git a/cmd/kluctl/commands/cmd_list_targets.go b/cmd/kluctl/commands/cmd_list_targets.go
index 3920f91c6..93984dbe0 100644
--- a/cmd/kluctl/commands/cmd_list_targets.go
+++ b/cmd/kluctl/commands/cmd_list_targets.go
@@ -1,9 +1,9 @@
 package commands
 
 import (
-	"github.com/codablock/kluctl/cmd/kluctl/args"
-	"github.com/codablock/kluctl/pkg/kluctl_project"
-	"github.com/codablock/kluctl/pkg/types"
+	"github.com/kluctl/kluctl/cmd/kluctl/args"
+	"github.com/kluctl/kluctl/pkg/kluctl_project"
+	"github.com/kluctl/kluctl/pkg/types"
 )
 
 type listTargetsCmd struct {
diff --git a/cmd/kluctl/commands/cmd_poke_images.go b/cmd/kluctl/commands/cmd_poke_images.go
index d3de84c64..641713e45 100644
--- a/cmd/kluctl/commands/cmd_poke_images.go
+++ b/cmd/kluctl/commands/cmd_poke_images.go
@@ -2,9 +2,9 @@ package commands
 
 import (
 	"fmt"
-	"github.com/codablock/kluctl/cmd/kluctl/args"
-	"github.com/codablock/kluctl/pkg/deployment/commands"
-	"github.com/codablock/kluctl/pkg/utils"
+	"github.com/kluctl/kluctl/cmd/kluctl/args"
+	"github.com/kluctl/kluctl/pkg/deployment/commands"
+	"github.com/kluctl/kluctl/pkg/utils"
 )
 
 type pokeImagesCmd struct {
diff --git a/cmd/kluctl/commands/cmd_prune.go b/cmd/kluctl/commands/cmd_prune.go
index 5db8234e2..5881089ac 100644
--- a/cmd/kluctl/commands/cmd_prune.go
+++ b/cmd/kluctl/commands/cmd_prune.go
@@ -2,8 +2,8 @@ package commands
 
 import (
 	"fmt"
-	"github.com/codablock/kluctl/cmd/kluctl/args"
-	"github.com/codablock/kluctl/pkg/deployment/commands"
+	"github.com/kluctl/kluctl/cmd/kluctl/args"
+	"github.com/kluctl/kluctl/pkg/deployment/commands"
 )
 
 type pruneCmd struct {
diff --git a/cmd/kluctl/commands/cmd_render.go b/cmd/kluctl/commands/cmd_render.go
index 9fe846f1a..96c3093e4 100644
--- a/cmd/kluctl/commands/cmd_render.go
+++ b/cmd/kluctl/commands/cmd_render.go
@@ -1,8 +1,8 @@
 package commands
 
 import (
-	"github.com/codablock/kluctl/cmd/kluctl/args"
-	"github.com/codablock/kluctl/pkg/utils"
+	"github.com/kluctl/kluctl/cmd/kluctl/args"
+	"github.com/kluctl/kluctl/pkg/utils"
 	log "github.com/sirupsen/logrus"
 	"io/ioutil"
 )
diff --git a/cmd/kluctl/commands/cmd_seal.go b/cmd/kluctl/commands/cmd_seal.go
index 590ff7459..08b89809e 100644
--- a/cmd/kluctl/commands/cmd_seal.go
+++ b/cmd/kluctl/commands/cmd_seal.go
@@ -2,12 +2,12 @@ package commands
 
 import (
 	"fmt"
-	"github.com/codablock/kluctl/cmd/kluctl/args"
-	"github.com/codablock/kluctl/pkg/deployment/commands"
-	"github.com/codablock/kluctl/pkg/kluctl_project"
-	"github.com/codablock/kluctl/pkg/seal"
-	"github.com/codablock/kluctl/pkg/types"
-	"github.com/codablock/kluctl/pkg/utils/uo"
+	"github.com/kluctl/kluctl/cmd/kluctl/args"
+	"github.com/kluctl/kluctl/pkg/deployment/commands"
+	"github.com/kluctl/kluctl/pkg/kluctl_project"
+	"github.com/kluctl/kluctl/pkg/seal"
+	"github.com/kluctl/kluctl/pkg/types"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
 	log "github.com/sirupsen/logrus"
 )
 
diff --git a/cmd/kluctl/commands/cmd_validate.go b/cmd/kluctl/commands/cmd_validate.go
index 0f5cf1f6b..f97c00f20 100644
--- a/cmd/kluctl/commands/cmd_validate.go
+++ b/cmd/kluctl/commands/cmd_validate.go
@@ -2,8 +2,8 @@ package commands
 
 import (
 	"fmt"
-	"github.com/codablock/kluctl/cmd/kluctl/args"
-	"github.com/codablock/kluctl/pkg/deployment/commands"
+	"github.com/kluctl/kluctl/cmd/kluctl/args"
+	"github.com/kluctl/kluctl/pkg/deployment/commands"
 	"os"
 	"time"
 )
diff --git a/cmd/kluctl/commands/cmd_version.go b/cmd/kluctl/commands/cmd_version.go
index 7b68c602a..734083a64 100644
--- a/cmd/kluctl/commands/cmd_version.go
+++ b/cmd/kluctl/commands/cmd_version.go
@@ -1,7 +1,7 @@
 package commands
 
 import (
-	"github.com/codablock/kluctl/pkg/version"
+	"github.com/kluctl/kluctl/pkg/version"
 	"os"
 )
 
diff --git a/cmd/kluctl/commands/command_result.go b/cmd/kluctl/commands/command_result.go
index 41d3dbeee..12dc4f46d 100644
--- a/cmd/kluctl/commands/command_result.go
+++ b/cmd/kluctl/commands/command_result.go
@@ -3,10 +3,10 @@ package commands
 import (
 	"bytes"
 	"fmt"
-	"github.com/codablock/kluctl/pkg/types"
-	"github.com/codablock/kluctl/pkg/types/k8s"
-	"github.com/codablock/kluctl/pkg/utils"
-	"github.com/codablock/kluctl/pkg/yaml"
+	"github.com/kluctl/kluctl/pkg/types"
+	"github.com/kluctl/kluctl/pkg/types/k8s"
+	"github.com/kluctl/kluctl/pkg/utils"
+	"github.com/kluctl/kluctl/pkg/yaml"
 	"io"
 	"os"
 	"strings"
diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go
index 1a5879817..e1efc9e0e 100644
--- a/cmd/kluctl/commands/root.go
+++ b/cmd/kluctl/commands/root.go
@@ -17,11 +17,11 @@ package commands
 
 import (
 	"github.com/alecthomas/kong"
-	"github.com/codablock/kluctl/pkg/utils"
-	"github.com/codablock/kluctl/pkg/utils/uo"
-	"github.com/codablock/kluctl/pkg/utils/versions"
-	"github.com/codablock/kluctl/pkg/version"
-	"github.com/codablock/kluctl/pkg/yaml"
+	"github.com/kluctl/kluctl/pkg/utils"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
+	"github.com/kluctl/kluctl/pkg/utils/versions"
+	"github.com/kluctl/kluctl/pkg/version"
+	"github.com/kluctl/kluctl/pkg/yaml"
 	log "github.com/sirupsen/logrus"
 	"net/http"
 	"os"
@@ -30,7 +30,7 @@ import (
 	"time"
 )
 
-const latestReleaseUrl = "https://api.github.com/repos/codablock/kluctl/releases/latest"
+const latestReleaseUrl = "https://api.github.com/repos/kluctl/kluctl/releases/latest"
 
 type cli struct {
 	Verbosity     string `group:"global" short:"v" help:"Log level (debug, info, warn, error, fatal, panic)." default:"info"`
diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go
index 355745329..4e825de0b 100644
--- a/cmd/kluctl/commands/utils.go
+++ b/cmd/kluctl/commands/utils.go
@@ -2,14 +2,14 @@ package commands
 
 import (
 	"fmt"
-	"github.com/codablock/kluctl/cmd/kluctl/args"
-	"github.com/codablock/kluctl/pkg/deployment"
-	git_url "github.com/codablock/kluctl/pkg/git/git-url"
-	"github.com/codablock/kluctl/pkg/jinja2"
-	"github.com/codablock/kluctl/pkg/k8s"
-	"github.com/codablock/kluctl/pkg/kluctl_project"
-	"github.com/codablock/kluctl/pkg/types"
-	"github.com/codablock/kluctl/pkg/utils/uo"
+	"github.com/kluctl/kluctl/cmd/kluctl/args"
+	"github.com/kluctl/kluctl/pkg/deployment"
+	git_url "github.com/kluctl/kluctl/pkg/git/git-url"
+	"github.com/kluctl/kluctl/pkg/jinja2"
+	"github.com/kluctl/kluctl/pkg/k8s"
+	"github.com/kluctl/kluctl/pkg/kluctl_project"
+	"github.com/kluctl/kluctl/pkg/types"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
 	"io/ioutil"
 	"os"
 	"path/filepath"
diff --git a/cmd/kluctl/main.go b/cmd/kluctl/main.go
index c9805f137..fbc4cdde9 100644
--- a/cmd/kluctl/main.go
+++ b/cmd/kluctl/main.go
@@ -15,7 +15,7 @@ limitations under the License.
 */
 package main
 
-import "github.com/codablock/kluctl/cmd/kluctl/commands"
+import "github.com/kluctl/kluctl/cmd/kluctl/commands"
 
 func main() {
 	commands.Execute()
diff --git a/docs/kluctl_project.md b/docs/kluctl_project.md
index 273c47a9b..f0622885d 100644
--- a/docs/kluctl_project.md
+++ b/docs/kluctl_project.md
@@ -13,18 +13,18 @@ An example .kluctl.yml looks like this:
 # This is optional. If omitted, the same directory where `.kluctl.yml` is located will be used as root deployment
 deployment:
   project:
-    url: https://github.com/codablock/kluctl-example
+    url: https://github.com/kluctl/kluctl-example
 
 # This is optional. If omitted, `/clusters` will be used
 clusters:
   project:
-    url: https://github.com/codablock/kluctl-example-clusters
+    url: https://github.com/kluctl/kluctl-example-clusters
     subdir: clusters
 
 # This is optional. If omitted, `/.sealed-secrets` will be used
 sealedSecrets:
   project:
-    url: https://github.com/codablock/kluctl-example
+    url: https://github.com/kluctl/kluctl-example
     subdir: .sealed-secrets
 
 targets:
diff --git a/e2e/cmd_wrapper_test.go b/e2e/cmd_wrapper_test.go
index 28a8ead34..0176c9475 100644
--- a/e2e/cmd_wrapper_test.go
+++ b/e2e/cmd_wrapper_test.go
@@ -2,7 +2,7 @@ package e2e
 
 import (
 	"flag"
-	"github.com/codablock/kluctl/cmd/kluctl/commands"
+	"github.com/kluctl/kluctl/cmd/kluctl/commands"
 	log "github.com/sirupsen/logrus"
 	"os"
 	"strings"
diff --git a/e2e/external_projects_test.go b/e2e/external_projects_test.go
index a21f58449..9429aa372 100644
--- a/e2e/external_projects_test.go
+++ b/e2e/external_projects_test.go
@@ -2,7 +2,7 @@ package e2e
 
 import (
 	"fmt"
-	"github.com/codablock/kluctl/pkg/utils/uo"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
 	"testing"
 	"time"
 )
diff --git a/e2e/hooks_test.go b/e2e/hooks_test.go
index a503f0cf9..d5cf16b6f 100644
--- a/e2e/hooks_test.go
+++ b/e2e/hooks_test.go
@@ -3,7 +3,7 @@ package e2e
 import (
 	"encoding/base64"
 	"fmt"
-	"github.com/codablock/kluctl/pkg/utils/uo"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
 	"testing"
 )
 
diff --git a/e2e/kind_cluster.go b/e2e/kind_cluster.go
index 8db400c58..9bbfd0e43 100644
--- a/e2e/kind_cluster.go
+++ b/e2e/kind_cluster.go
@@ -2,9 +2,9 @@ package e2e
 
 import (
 	"fmt"
-	"github.com/codablock/kluctl/pkg/utils"
-	"github.com/codablock/kluctl/pkg/utils/uo"
-	"github.com/codablock/kluctl/pkg/yaml"
+	"github.com/kluctl/kluctl/pkg/utils"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
+	"github.com/kluctl/kluctl/pkg/yaml"
 	"github.com/pkg/errors"
 	log "github.com/sirupsen/logrus"
 	"k8s.io/client-go/rest"
diff --git a/e2e/project.go b/e2e/project.go
index 9180a0f33..d42c282ef 100644
--- a/e2e/project.go
+++ b/e2e/project.go
@@ -3,10 +3,10 @@ package e2e
 import (
 	"context"
 	"fmt"
-	http_server "github.com/codablock/kluctl/pkg/git/http-server"
-	"github.com/codablock/kluctl/pkg/utils"
-	"github.com/codablock/kluctl/pkg/utils/uo"
-	"github.com/codablock/kluctl/pkg/yaml"
+	http_server "github.com/kluctl/kluctl/pkg/git/http-server"
+	"github.com/kluctl/kluctl/pkg/utils"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
+	"github.com/kluctl/kluctl/pkg/yaml"
 	"github.com/go-git/go-git/v5"
 	log "github.com/sirupsen/logrus"
 	"io/ioutil"
diff --git a/e2e/utils_resources.go b/e2e/utils_resources.go
index 91d747392..aaff14756 100644
--- a/e2e/utils_resources.go
+++ b/e2e/utils_resources.go
@@ -2,7 +2,7 @@ package e2e
 
 import (
 	"bytes"
-	"github.com/codablock/kluctl/pkg/utils/uo"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
 	"text/template"
 )
 
diff --git a/e2e/utils_test.go b/e2e/utils_test.go
index fd1954557..9ee86e96b 100644
--- a/e2e/utils_test.go
+++ b/e2e/utils_test.go
@@ -1,8 +1,8 @@
 package e2e
 
 import (
-	"github.com/codablock/kluctl/pkg/utils/uo"
-	"github.com/codablock/kluctl/pkg/validation"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
+	"github.com/kluctl/kluctl/pkg/validation"
 	"os/exec"
 	"reflect"
 	"strings"
diff --git a/go.mod b/go.mod
index 0996ff741..8028d078b 100644
--- a/go.mod
+++ b/go.mod
@@ -1,4 +1,4 @@
-module github.com/codablock/kluctl
+module github.com/kluctl/kluctl
 
 go 1.17
 
diff --git a/hack/replace-commands-help/main.go b/hack/replace-commands-help/main.go
index 5f1d7b66b..dbaed434a 100644
--- a/hack/replace-commands-help/main.go
+++ b/hack/replace-commands-help/main.go
@@ -12,7 +12,7 @@ import (
     "strings"
     "syscall"
 
-    "github.com/codablock/kluctl/cmd/kluctl/commands"
+    "github.com/kluctl/kluctl/cmd/kluctl/commands"
 )
 
 var commandsMDPath = flag.String("commands-md", "", "Path to commands.md")
diff --git a/pkg/deployment/commands/delete.go b/pkg/deployment/commands/delete.go
index dbc1d6646..aaa60c889 100644
--- a/pkg/deployment/commands/delete.go
+++ b/pkg/deployment/commands/delete.go
@@ -1,10 +1,10 @@
 package commands
 
 import (
-	"github.com/codablock/kluctl/pkg/deployment"
-	utils2 "github.com/codablock/kluctl/pkg/deployment/utils"
-	"github.com/codablock/kluctl/pkg/k8s"
-	k8s2 "github.com/codablock/kluctl/pkg/types/k8s"
+	"github.com/kluctl/kluctl/pkg/deployment"
+	utils2 "github.com/kluctl/kluctl/pkg/deployment/utils"
+	"github.com/kluctl/kluctl/pkg/k8s"
+	k8s2 "github.com/kluctl/kluctl/pkg/types/k8s"
 )
 
 type DeleteCommand struct {
diff --git a/pkg/deployment/commands/deploy.go b/pkg/deployment/commands/deploy.go
index 92fe108e3..b46700219 100644
--- a/pkg/deployment/commands/deploy.go
+++ b/pkg/deployment/commands/deploy.go
@@ -1,10 +1,10 @@
 package commands
 
 import (
-	"github.com/codablock/kluctl/pkg/deployment"
-	utils2 "github.com/codablock/kluctl/pkg/deployment/utils"
-	"github.com/codablock/kluctl/pkg/k8s"
-	"github.com/codablock/kluctl/pkg/types"
+	"github.com/kluctl/kluctl/pkg/deployment"
+	utils2 "github.com/kluctl/kluctl/pkg/deployment/utils"
+	"github.com/kluctl/kluctl/pkg/k8s"
+	"github.com/kluctl/kluctl/pkg/types"
 	"time"
 )
 
diff --git a/pkg/deployment/commands/diff.go b/pkg/deployment/commands/diff.go
index 80b808c49..93b910a1c 100644
--- a/pkg/deployment/commands/diff.go
+++ b/pkg/deployment/commands/diff.go
@@ -1,10 +1,10 @@
 package commands
 
 import (
-	"github.com/codablock/kluctl/pkg/deployment"
-	"github.com/codablock/kluctl/pkg/deployment/utils"
-	"github.com/codablock/kluctl/pkg/k8s"
-	"github.com/codablock/kluctl/pkg/types"
+	"github.com/kluctl/kluctl/pkg/deployment"
+	"github.com/kluctl/kluctl/pkg/deployment/utils"
+	"github.com/kluctl/kluctl/pkg/k8s"
+	"github.com/kluctl/kluctl/pkg/types"
 )
 
 type DiffCommand struct {
diff --git a/pkg/deployment/commands/downscale.go b/pkg/deployment/commands/downscale.go
index 80f72795d..02efcb24d 100644
--- a/pkg/deployment/commands/downscale.go
+++ b/pkg/deployment/commands/downscale.go
@@ -1,12 +1,12 @@
 package commands
 
 import (
-	"github.com/codablock/kluctl/pkg/deployment"
-	utils2 "github.com/codablock/kluctl/pkg/deployment/utils"
-	"github.com/codablock/kluctl/pkg/k8s"
-	"github.com/codablock/kluctl/pkg/types"
-	k8s2 "github.com/codablock/kluctl/pkg/types/k8s"
-	"github.com/codablock/kluctl/pkg/utils/uo"
+	"github.com/kluctl/kluctl/pkg/deployment"
+	utils2 "github.com/kluctl/kluctl/pkg/deployment/utils"
+	"github.com/kluctl/kluctl/pkg/k8s"
+	"github.com/kluctl/kluctl/pkg/types"
+	k8s2 "github.com/kluctl/kluctl/pkg/types/k8s"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
 	"sync"
 )
 
diff --git a/pkg/deployment/commands/poke_images.go b/pkg/deployment/commands/poke_images.go
index 5316f15a7..01a207e50 100644
--- a/pkg/deployment/commands/poke_images.go
+++ b/pkg/deployment/commands/poke_images.go
@@ -2,12 +2,12 @@ package commands
 
 import (
 	"fmt"
-	"github.com/codablock/kluctl/pkg/deployment"
-	utils2 "github.com/codablock/kluctl/pkg/deployment/utils"
-	"github.com/codablock/kluctl/pkg/k8s"
-	"github.com/codablock/kluctl/pkg/types"
-	k8s2 "github.com/codablock/kluctl/pkg/types/k8s"
-	"github.com/codablock/kluctl/pkg/utils/uo"
+	"github.com/kluctl/kluctl/pkg/deployment"
+	utils2 "github.com/kluctl/kluctl/pkg/deployment/utils"
+	"github.com/kluctl/kluctl/pkg/k8s"
+	"github.com/kluctl/kluctl/pkg/types"
+	k8s2 "github.com/kluctl/kluctl/pkg/types/k8s"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
 	"sync"
 )
 
diff --git a/pkg/deployment/commands/prune.go b/pkg/deployment/commands/prune.go
index fa9f2bd11..c434551df 100644
--- a/pkg/deployment/commands/prune.go
+++ b/pkg/deployment/commands/prune.go
@@ -1,10 +1,10 @@
 package commands
 
 import (
-	"github.com/codablock/kluctl/pkg/deployment"
-	utils2 "github.com/codablock/kluctl/pkg/deployment/utils"
-	"github.com/codablock/kluctl/pkg/k8s"
-	k8s2 "github.com/codablock/kluctl/pkg/types/k8s"
+	"github.com/kluctl/kluctl/pkg/deployment"
+	utils2 "github.com/kluctl/kluctl/pkg/deployment/utils"
+	"github.com/kluctl/kluctl/pkg/k8s"
+	k8s2 "github.com/kluctl/kluctl/pkg/types/k8s"
 )
 
 type PruneCommand struct {
diff --git a/pkg/deployment/commands/seal.go b/pkg/deployment/commands/seal.go
index 02c20e055..2d3b2f6df 100644
--- a/pkg/deployment/commands/seal.go
+++ b/pkg/deployment/commands/seal.go
@@ -2,8 +2,8 @@ package commands
 
 import (
 	"fmt"
-	"github.com/codablock/kluctl/pkg/deployment"
-	"github.com/codablock/kluctl/pkg/seal"
+	"github.com/kluctl/kluctl/pkg/deployment"
+	"github.com/kluctl/kluctl/pkg/seal"
 	"io/fs"
 	"path/filepath"
 	"strings"
diff --git a/pkg/deployment/commands/validate.go b/pkg/deployment/commands/validate.go
index 210b6de19..94b1add25 100644
--- a/pkg/deployment/commands/validate.go
+++ b/pkg/deployment/commands/validate.go
@@ -1,12 +1,12 @@
 package commands
 
 import (
-	"github.com/codablock/kluctl/pkg/deployment"
-	utils2 "github.com/codablock/kluctl/pkg/deployment/utils"
-	"github.com/codablock/kluctl/pkg/k8s"
-	"github.com/codablock/kluctl/pkg/types"
-	k8s2 "github.com/codablock/kluctl/pkg/types/k8s"
-	"github.com/codablock/kluctl/pkg/validation"
+	"github.com/kluctl/kluctl/pkg/deployment"
+	utils2 "github.com/kluctl/kluctl/pkg/deployment/utils"
+	"github.com/kluctl/kluctl/pkg/k8s"
+	"github.com/kluctl/kluctl/pkg/types"
+	k8s2 "github.com/kluctl/kluctl/pkg/types/k8s"
+	"github.com/kluctl/kluctl/pkg/validation"
 )
 
 type ValidateCommand struct {
diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go
index 9b6fa7d5c..6268920dc 100644
--- a/pkg/deployment/deployment_collection.go
+++ b/pkg/deployment/deployment_collection.go
@@ -3,10 +3,10 @@ package deployment
 import (
 	"context"
 	"fmt"
-	"github.com/codablock/kluctl/pkg/k8s"
-	"github.com/codablock/kluctl/pkg/types"
-	k8s2 "github.com/codablock/kluctl/pkg/types/k8s"
-	"github.com/codablock/kluctl/pkg/utils"
+	"github.com/kluctl/kluctl/pkg/k8s"
+	"github.com/kluctl/kluctl/pkg/types"
+	k8s2 "github.com/kluctl/kluctl/pkg/types/k8s"
+	"github.com/kluctl/kluctl/pkg/utils"
 	log "github.com/sirupsen/logrus"
 	"golang.org/x/sync/semaphore"
 	"path/filepath"
diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go
index 89b56a4af..2f7c1e26b 100644
--- a/pkg/deployment/deployment_item.go
+++ b/pkg/deployment/deployment_item.go
@@ -2,11 +2,11 @@ package deployment
 
 import (
 	"fmt"
-	"github.com/codablock/kluctl/pkg/k8s"
-	"github.com/codablock/kluctl/pkg/types"
-	"github.com/codablock/kluctl/pkg/utils"
-	"github.com/codablock/kluctl/pkg/utils/uo"
-	"github.com/codablock/kluctl/pkg/yaml"
+	"github.com/kluctl/kluctl/pkg/k8s"
+	"github.com/kluctl/kluctl/pkg/types"
+	"github.com/kluctl/kluctl/pkg/utils"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
+	"github.com/kluctl/kluctl/pkg/yaml"
 	"io/fs"
 	"io/ioutil"
 	"os"
diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go
index 350e3e7d0..1b82a17e5 100644
--- a/pkg/deployment/deployment_project.go
+++ b/pkg/deployment/deployment_project.go
@@ -2,12 +2,12 @@ package deployment
 
 import (
 	"fmt"
-	"github.com/codablock/kluctl/pkg/jinja2"
-	"github.com/codablock/kluctl/pkg/k8s"
-	"github.com/codablock/kluctl/pkg/types"
-	"github.com/codablock/kluctl/pkg/utils"
-	"github.com/codablock/kluctl/pkg/utils/uo"
-	"github.com/codablock/kluctl/pkg/yaml"
+	"github.com/kluctl/kluctl/pkg/jinja2"
+	"github.com/kluctl/kluctl/pkg/k8s"
+	"github.com/kluctl/kluctl/pkg/types"
+	"github.com/kluctl/kluctl/pkg/utils"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
+	"github.com/kluctl/kluctl/pkg/yaml"
 	log "github.com/sirupsen/logrus"
 	"path/filepath"
 	"reflect"
diff --git a/pkg/deployment/external_args.go b/pkg/deployment/external_args.go
index 649066183..b9aa526f0 100644
--- a/pkg/deployment/external_args.go
+++ b/pkg/deployment/external_args.go
@@ -2,10 +2,10 @@ package deployment
 
 import (
 	"fmt"
-	"github.com/codablock/kluctl/pkg/jinja2"
-	"github.com/codablock/kluctl/pkg/types"
-	"github.com/codablock/kluctl/pkg/utils/uo"
-	"github.com/codablock/kluctl/pkg/yaml"
+	"github.com/kluctl/kluctl/pkg/jinja2"
+	"github.com/kluctl/kluctl/pkg/types"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
+	"github.com/kluctl/kluctl/pkg/yaml"
 	"path/filepath"
 	"regexp"
 	"strings"
diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go
index d5ff98735..83a48bb7e 100644
--- a/pkg/deployment/helm_chart.go
+++ b/pkg/deployment/helm_chart.go
@@ -2,12 +2,12 @@ package deployment
 
 import (
 	"fmt"
-	"github.com/codablock/kluctl/pkg/k8s"
-	"github.com/codablock/kluctl/pkg/types"
-	"github.com/codablock/kluctl/pkg/utils"
-	"github.com/codablock/kluctl/pkg/utils/uo"
-	"github.com/codablock/kluctl/pkg/utils/versions"
-	"github.com/codablock/kluctl/pkg/yaml"
+	"github.com/kluctl/kluctl/pkg/k8s"
+	"github.com/kluctl/kluctl/pkg/types"
+	"github.com/kluctl/kluctl/pkg/utils"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
+	"github.com/kluctl/kluctl/pkg/utils/versions"
+	"github.com/kluctl/kluctl/pkg/yaml"
 	"io/ioutil"
 	"os"
 	"os/exec"
diff --git a/pkg/deployment/images.go b/pkg/deployment/images.go
index e3f4d1210..25c04a7c9 100644
--- a/pkg/deployment/images.go
+++ b/pkg/deployment/images.go
@@ -4,14 +4,14 @@ import (
 	"bytes"
 	"encoding/base64"
 	"fmt"
-	"github.com/codablock/kluctl/pkg/k8s"
-	"github.com/codablock/kluctl/pkg/registries"
-	"github.com/codablock/kluctl/pkg/types"
-	k8s2 "github.com/codablock/kluctl/pkg/types/k8s"
-	"github.com/codablock/kluctl/pkg/utils"
-	"github.com/codablock/kluctl/pkg/utils/uo"
-	"github.com/codablock/kluctl/pkg/utils/versions"
-	"github.com/codablock/kluctl/pkg/yaml"
+	"github.com/kluctl/kluctl/pkg/k8s"
+	"github.com/kluctl/kluctl/pkg/registries"
+	"github.com/kluctl/kluctl/pkg/types"
+	k8s2 "github.com/kluctl/kluctl/pkg/types/k8s"
+	"github.com/kluctl/kluctl/pkg/utils"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
+	"github.com/kluctl/kluctl/pkg/utils/versions"
+	"github.com/kluctl/kluctl/pkg/yaml"
 	"k8s.io/apimachinery/pkg/api/errors"
 	"strings"
 	"sync"
diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go
index 31dc7dfdb..15dfb0a6d 100644
--- a/pkg/deployment/utils/apply_utils.go
+++ b/pkg/deployment/utils/apply_utils.go
@@ -3,14 +3,14 @@ package utils
 import (
 	errors2 "errors"
 	"fmt"
-	"github.com/codablock/kluctl/pkg/deployment"
-	"github.com/codablock/kluctl/pkg/diff"
-	"github.com/codablock/kluctl/pkg/k8s"
-	"github.com/codablock/kluctl/pkg/types"
-	k8s2 "github.com/codablock/kluctl/pkg/types/k8s"
-	"github.com/codablock/kluctl/pkg/utils"
-	"github.com/codablock/kluctl/pkg/utils/uo"
-	"github.com/codablock/kluctl/pkg/validation"
+	"github.com/kluctl/kluctl/pkg/deployment"
+	"github.com/kluctl/kluctl/pkg/diff"
+	"github.com/kluctl/kluctl/pkg/k8s"
+	"github.com/kluctl/kluctl/pkg/types"
+	k8s2 "github.com/kluctl/kluctl/pkg/types/k8s"
+	"github.com/kluctl/kluctl/pkg/utils"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
+	"github.com/kluctl/kluctl/pkg/validation"
 	log "github.com/sirupsen/logrus"
 	"k8s.io/apimachinery/pkg/api/errors"
 	"k8s.io/apimachinery/pkg/api/meta"
diff --git a/pkg/deployment/utils/delete_utils.go b/pkg/deployment/utils/delete_utils.go
index 288341e0b..01b554be0 100644
--- a/pkg/deployment/utils/delete_utils.go
+++ b/pkg/deployment/utils/delete_utils.go
@@ -2,10 +2,10 @@ package utils
 
 import (
 	"context"
-	"github.com/codablock/kluctl/pkg/k8s"
-	"github.com/codablock/kluctl/pkg/types"
-	k8s2 "github.com/codablock/kluctl/pkg/types/k8s"
-	"github.com/codablock/kluctl/pkg/utils/uo"
+	"github.com/kluctl/kluctl/pkg/k8s"
+	"github.com/kluctl/kluctl/pkg/types"
+	k8s2 "github.com/kluctl/kluctl/pkg/types/k8s"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
 	"golang.org/x/sync/semaphore"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"strconv"
diff --git a/pkg/deployment/utils/diff_utils.go b/pkg/deployment/utils/diff_utils.go
index 8ccf6f637..611523ed4 100644
--- a/pkg/deployment/utils/diff_utils.go
+++ b/pkg/deployment/utils/diff_utils.go
@@ -1,11 +1,11 @@
 package utils
 
 import (
-	"github.com/codablock/kluctl/pkg/deployment"
-	"github.com/codablock/kluctl/pkg/diff"
-	"github.com/codablock/kluctl/pkg/types"
-	k8s2 "github.com/codablock/kluctl/pkg/types/k8s"
-	"github.com/codablock/kluctl/pkg/utils/uo"
+	"github.com/kluctl/kluctl/pkg/deployment"
+	"github.com/kluctl/kluctl/pkg/diff"
+	"github.com/kluctl/kluctl/pkg/types"
+	k8s2 "github.com/kluctl/kluctl/pkg/types/k8s"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
 	"sort"
 	"sync"
 	"time"
diff --git a/pkg/deployment/utils/diff_utils_test.go b/pkg/deployment/utils/diff_utils_test.go
index 517d40871..2e245c5eb 100644
--- a/pkg/deployment/utils/diff_utils_test.go
+++ b/pkg/deployment/utils/diff_utils_test.go
@@ -1,10 +1,10 @@
 package utils
 
 import (
-	"github.com/codablock/kluctl/pkg/deployment"
-	"github.com/codablock/kluctl/pkg/types"
-	k8s2 "github.com/codablock/kluctl/pkg/types/k8s"
-	"github.com/codablock/kluctl/pkg/utils/uo"
+	"github.com/kluctl/kluctl/pkg/deployment"
+	"github.com/kluctl/kluctl/pkg/types"
+	k8s2 "github.com/kluctl/kluctl/pkg/types/k8s"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
 	"github.com/stretchr/testify/assert"
 	"testing"
 )
diff --git a/pkg/deployment/utils/downscale_utils.go b/pkg/deployment/utils/downscale_utils.go
index 5497dcc9e..1be9b3974 100644
--- a/pkg/deployment/utils/downscale_utils.go
+++ b/pkg/deployment/utils/downscale_utils.go
@@ -2,8 +2,8 @@ package utils
 
 import (
 	"fmt"
-	"github.com/codablock/kluctl/pkg/utils/uo"
-	"github.com/codablock/kluctl/pkg/yaml"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
+	"github.com/kluctl/kluctl/pkg/yaml"
 	jsonpatch "github.com/evanphx/json-patch"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"regexp"
diff --git a/pkg/deployment/utils/errors_holder.go b/pkg/deployment/utils/errors_holder.go
index 38660ba3c..5a6ef2ccd 100644
--- a/pkg/deployment/utils/errors_holder.go
+++ b/pkg/deployment/utils/errors_holder.go
@@ -3,10 +3,10 @@ package utils
 import (
 	"errors"
 	"fmt"
-	k8s2 "github.com/codablock/kluctl/pkg/k8s"
-	"github.com/codablock/kluctl/pkg/types"
-	"github.com/codablock/kluctl/pkg/types/k8s"
-	"github.com/codablock/kluctl/pkg/utils"
+	k8s2 "github.com/kluctl/kluctl/pkg/k8s"
+	"github.com/kluctl/kluctl/pkg/types"
+	"github.com/kluctl/kluctl/pkg/types/k8s"
+	"github.com/kluctl/kluctl/pkg/utils"
 	"sync"
 )
 
diff --git a/pkg/deployment/utils/hooks_util.go b/pkg/deployment/utils/hooks_util.go
index 8fe63a24f..fd56db75a 100644
--- a/pkg/deployment/utils/hooks_util.go
+++ b/pkg/deployment/utils/hooks_util.go
@@ -2,10 +2,10 @@ package utils
 
 import (
 	"fmt"
-	"github.com/codablock/kluctl/pkg/deployment"
-	"github.com/codablock/kluctl/pkg/types/k8s"
-	"github.com/codablock/kluctl/pkg/utils"
-	"github.com/codablock/kluctl/pkg/utils/uo"
+	"github.com/kluctl/kluctl/pkg/deployment"
+	"github.com/kluctl/kluctl/pkg/types/k8s"
+	"github.com/kluctl/kluctl/pkg/utils"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
 	log "github.com/sirupsen/logrus"
 	"sort"
 	"strconv"
diff --git a/pkg/deployment/utils/remote_objects_utils.go b/pkg/deployment/utils/remote_objects_utils.go
index 92b6cc9ea..fdf3c8eb7 100644
--- a/pkg/deployment/utils/remote_objects_utils.go
+++ b/pkg/deployment/utils/remote_objects_utils.go
@@ -1,10 +1,10 @@
 package utils
 
 import (
-	"github.com/codablock/kluctl/pkg/k8s"
-	k8s2 "github.com/codablock/kluctl/pkg/types/k8s"
-	"github.com/codablock/kluctl/pkg/utils"
-	"github.com/codablock/kluctl/pkg/utils/uo"
+	"github.com/kluctl/kluctl/pkg/k8s"
+	k8s2 "github.com/kluctl/kluctl/pkg/types/k8s"
+	"github.com/kluctl/kluctl/pkg/utils"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
 	log "github.com/sirupsen/logrus"
 	"sync"
 )
diff --git a/pkg/diff/diff.go b/pkg/diff/diff.go
index 66dd1bd5a..53a1d0e7a 100644
--- a/pkg/diff/diff.go
+++ b/pkg/diff/diff.go
@@ -2,9 +2,9 @@ package diff
 
 import (
 	"fmt"
-	"github.com/codablock/kluctl/pkg/types"
-	"github.com/codablock/kluctl/pkg/utils/uo"
-	"github.com/codablock/kluctl/pkg/yaml"
+	"github.com/kluctl/kluctl/pkg/types"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
+	"github.com/kluctl/kluctl/pkg/yaml"
 	"github.com/hexops/gotextdiff"
 	"github.com/hexops/gotextdiff/myers"
 	"github.com/hexops/gotextdiff/span"
diff --git a/pkg/diff/managed_fields.go b/pkg/diff/managed_fields.go
index fb33ccef4..294c3bbb3 100644
--- a/pkg/diff/managed_fields.go
+++ b/pkg/diff/managed_fields.go
@@ -3,7 +3,7 @@ package diff
 import (
 	"bytes"
 	"fmt"
-	"github.com/codablock/kluctl/pkg/utils/uo"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
 	log "github.com/sirupsen/logrus"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"reflect"
diff --git a/pkg/diff/normalize.go b/pkg/diff/normalize.go
index 066335596..53a4b57f8 100644
--- a/pkg/diff/normalize.go
+++ b/pkg/diff/normalize.go
@@ -2,8 +2,8 @@ package diff
 
 import (
 	"fmt"
-	"github.com/codablock/kluctl/pkg/types"
-	"github.com/codablock/kluctl/pkg/utils/uo"
+	"github.com/kluctl/kluctl/pkg/types"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
 	"regexp"
 	"strconv"
 	"strings"
diff --git a/pkg/git/auth/auth_provider.go b/pkg/git/auth/auth_provider.go
index f01b56a3e..11ca4bea3 100644
--- a/pkg/git/auth/auth_provider.go
+++ b/pkg/git/auth/auth_provider.go
@@ -1,7 +1,7 @@
 package auth
 
 import (
-	git_url "github.com/codablock/kluctl/pkg/git/git-url"
+	git_url "github.com/kluctl/kluctl/pkg/git/git-url"
 	"github.com/go-git/go-git/v5/plumbing/transport"
 )
 
diff --git a/pkg/git/auth/env_auth_provider.go b/pkg/git/auth/env_auth_provider.go
index fbbca4990..bb8835b29 100644
--- a/pkg/git/auth/env_auth_provider.go
+++ b/pkg/git/auth/env_auth_provider.go
@@ -1,8 +1,8 @@
 package auth
 
 import (
-	git_url "github.com/codablock/kluctl/pkg/git/git-url"
-	"github.com/codablock/kluctl/pkg/utils"
+	git_url "github.com/kluctl/kluctl/pkg/git/git-url"
+	"github.com/kluctl/kluctl/pkg/utils"
 	"github.com/go-git/go-git/v5/plumbing/transport"
 	"github.com/go-git/go-git/v5/plumbing/transport/http"
 	"github.com/go-git/go-git/v5/plumbing/transport/ssh"
diff --git a/pkg/git/auth/git_credentials_file.go b/pkg/git/auth/git_credentials_file.go
index b407bccd4..8a4f895fc 100644
--- a/pkg/git/auth/git_credentials_file.go
+++ b/pkg/git/auth/git_credentials_file.go
@@ -2,8 +2,8 @@ package auth
 
 import (
 	"bufio"
-	git_url "github.com/codablock/kluctl/pkg/git/git-url"
-	"github.com/codablock/kluctl/pkg/utils"
+	git_url "github.com/kluctl/kluctl/pkg/git/git-url"
+	"github.com/kluctl/kluctl/pkg/utils"
 	"github.com/go-git/go-git/v5/plumbing/transport"
 	"github.com/go-git/go-git/v5/plumbing/transport/http"
 	log "github.com/sirupsen/logrus"
diff --git a/pkg/git/auth/ssh_auth_provider.go b/pkg/git/auth/ssh_auth_provider.go
index e09bf934d..600609dab 100644
--- a/pkg/git/auth/ssh_auth_provider.go
+++ b/pkg/git/auth/ssh_auth_provider.go
@@ -2,8 +2,8 @@ package auth
 
 import (
 	"fmt"
-	git_url "github.com/codablock/kluctl/pkg/git/git-url"
-	"github.com/codablock/kluctl/pkg/utils"
+	git_url "github.com/kluctl/kluctl/pkg/git/git-url"
+	"github.com/kluctl/kluctl/pkg/utils"
 	"github.com/go-git/go-git/v5/plumbing/transport"
 	git_ssh "github.com/go-git/go-git/v5/plumbing/transport/ssh"
 	log "github.com/sirupsen/logrus"
diff --git a/pkg/git/auth/ssh_known_hosts.go b/pkg/git/auth/ssh_known_hosts.go
index 1a0d8f3dd..8e6891aec 100644
--- a/pkg/git/auth/ssh_known_hosts.go
+++ b/pkg/git/auth/ssh_known_hosts.go
@@ -2,8 +2,8 @@ package auth
 
 import (
 	"fmt"
-	"github.com/codablock/kluctl/pkg/git/auth/goph"
-	"github.com/codablock/kluctl/pkg/utils"
+	"github.com/kluctl/kluctl/pkg/git/auth/goph"
+	"github.com/kluctl/kluctl/pkg/utils"
 	"golang.org/x/crypto/ssh"
 	"net"
 	"os"
diff --git a/pkg/git/http-server/http.go b/pkg/git/http-server/http.go
index 6bc654462..8bc76c64a 100644
--- a/pkg/git/http-server/http.go
+++ b/pkg/git/http-server/http.go
@@ -4,7 +4,7 @@ package http_server
 import (
 	"compress/gzip"
 	"fmt"
-	process2 "github.com/codablock/kluctl/pkg/utils/process"
+	process2 "github.com/kluctl/kluctl/pkg/utils/process"
 	log "github.com/sirupsen/logrus"
 	"io"
 	"net/http"
diff --git a/pkg/git/http-server/utils.go b/pkg/git/http-server/utils.go
index e39401304..5edd5b027 100644
--- a/pkg/git/http-server/utils.go
+++ b/pkg/git/http-server/utils.go
@@ -2,7 +2,7 @@ package http_server
 
 import (
 	"fmt"
-	process2 "github.com/codablock/kluctl/pkg/utils/process"
+	process2 "github.com/kluctl/kluctl/pkg/utils/process"
 	"io"
 	"log"
 	"net/http"
diff --git a/pkg/git/mirrored_repo.go b/pkg/git/mirrored_repo.go
index db2d67ff9..6c3d60c2f 100644
--- a/pkg/git/mirrored_repo.go
+++ b/pkg/git/mirrored_repo.go
@@ -2,9 +2,9 @@ package git
 
 import (
 	"fmt"
-	auth2 "github.com/codablock/kluctl/pkg/git/auth"
-	git_url "github.com/codablock/kluctl/pkg/git/git-url"
-	"github.com/codablock/kluctl/pkg/utils"
+	auth2 "github.com/kluctl/kluctl/pkg/git/auth"
+	git_url "github.com/kluctl/kluctl/pkg/git/git-url"
+	"github.com/kluctl/kluctl/pkg/utils"
 	"github.com/go-git/go-git/v5"
 	"github.com/go-git/go-git/v5/config"
 	"github.com/go-git/go-git/v5/plumbing"
diff --git a/pkg/git/poor_mans_clone.go b/pkg/git/poor_mans_clone.go
index bd13f0498..0288a45e0 100644
--- a/pkg/git/poor_mans_clone.go
+++ b/pkg/git/poor_mans_clone.go
@@ -2,7 +2,7 @@ package git
 
 import (
 	"fmt"
-	"github.com/codablock/kluctl/pkg/utils"
+	"github.com/kluctl/kluctl/pkg/utils"
 	"github.com/go-git/go-git/v5"
 	"github.com/go-git/go-git/v5/config"
 	"github.com/go-git/go-git/v5/plumbing"
diff --git a/pkg/jinja2/jinja2.go b/pkg/jinja2/jinja2.go
index eda28e357..b8bcfa8d9 100644
--- a/pkg/jinja2/jinja2.go
+++ b/pkg/jinja2/jinja2.go
@@ -2,8 +2,8 @@ package jinja2
 
 import (
 	"fmt"
-	"github.com/codablock/kluctl/pkg/utils"
-	"github.com/codablock/kluctl/pkg/utils/uo"
+	"github.com/kluctl/kluctl/pkg/utils"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
 	"github.com/gobwas/glob"
 	"io/fs"
 	"io/ioutil"
diff --git a/pkg/jinja2/jinja2_renderer.go b/pkg/jinja2/jinja2_renderer.go
index 911308458..7db691279 100644
--- a/pkg/jinja2/jinja2_renderer.go
+++ b/pkg/jinja2/jinja2_renderer.go
@@ -5,8 +5,8 @@ import (
 	"bytes"
 	"encoding/json"
 	"fmt"
-	"github.com/codablock/kluctl/pkg/python"
-	"github.com/codablock/kluctl/pkg/utils/uo"
+	"github.com/kluctl/kluctl/pkg/python"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
 	"io"
 	"io/ioutil"
 	"os"
diff --git a/pkg/jinja2/source.go b/pkg/jinja2/source.go
index 7f9924920..7bae0a194 100644
--- a/pkg/jinja2/source.go
+++ b/pkg/jinja2/source.go
@@ -2,8 +2,8 @@ package jinja2
 
 import (
 	"embed"
-	"github.com/codablock/kluctl/pkg/utils"
-	"github.com/codablock/kluctl/pkg/utils/embed_util"
+	"github.com/kluctl/kluctl/pkg/utils"
+	"github.com/kluctl/kluctl/pkg/utils/embed_util"
 	log "github.com/sirupsen/logrus"
 	"path/filepath"
 )
diff --git a/pkg/jinja2/vars.go b/pkg/jinja2/vars.go
index 29bb791f8..9d81e61e3 100644
--- a/pkg/jinja2/vars.go
+++ b/pkg/jinja2/vars.go
@@ -2,11 +2,11 @@ package jinja2
 
 import (
 	"fmt"
-	"github.com/codablock/kluctl/pkg/k8s"
-	"github.com/codablock/kluctl/pkg/types"
-	k8s2 "github.com/codablock/kluctl/pkg/types/k8s"
-	"github.com/codablock/kluctl/pkg/utils/uo"
-	"github.com/codablock/kluctl/pkg/yaml"
+	"github.com/kluctl/kluctl/pkg/k8s"
+	"github.com/kluctl/kluctl/pkg/types"
+	k8s2 "github.com/kluctl/kluctl/pkg/types/k8s"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
+	"github.com/kluctl/kluctl/pkg/yaml"
 )
 
 type VarsCtx struct {
diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go
index da3ffb8c7..77827a514 100644
--- a/pkg/k8s/k8s_cluster.go
+++ b/pkg/k8s/k8s_cluster.go
@@ -4,10 +4,10 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
-	"github.com/codablock/kluctl/pkg/types/k8s"
-	"github.com/codablock/kluctl/pkg/utils"
-	"github.com/codablock/kluctl/pkg/utils/uo"
-	"github.com/codablock/kluctl/pkg/yaml"
+	"github.com/kluctl/kluctl/pkg/types/k8s"
+	"github.com/kluctl/kluctl/pkg/utils"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
+	"github.com/kluctl/kluctl/pkg/yaml"
 	goversion "github.com/hashicorp/go-version"
 	log "github.com/sirupsen/logrus"
 	"k8s.io/apimachinery/pkg/api/errors"
diff --git a/pkg/kluctl_project/git.go b/pkg/kluctl_project/git.go
index 66121ff19..dd7c992d5 100644
--- a/pkg/kluctl_project/git.go
+++ b/pkg/kluctl_project/git.go
@@ -2,10 +2,10 @@ package kluctl_project
 
 import (
 	"fmt"
-	"github.com/codablock/kluctl/pkg/git"
-	git_url "github.com/codablock/kluctl/pkg/git/git-url"
-	types2 "github.com/codablock/kluctl/pkg/types"
-	"github.com/codablock/kluctl/pkg/utils"
+	"github.com/kluctl/kluctl/pkg/git"
+	git_url "github.com/kluctl/kluctl/pkg/git/git-url"
+	types2 "github.com/kluctl/kluctl/pkg/types"
+	"github.com/kluctl/kluctl/pkg/utils"
 	log "github.com/sirupsen/logrus"
 	"os"
 	"path/filepath"
diff --git a/pkg/kluctl_project/load.go b/pkg/kluctl_project/load.go
index 865744ff9..1fb88901f 100644
--- a/pkg/kluctl_project/load.go
+++ b/pkg/kluctl_project/load.go
@@ -4,9 +4,9 @@ import (
 	"archive/tar"
 	"compress/gzip"
 	"fmt"
-	types2 "github.com/codablock/kluctl/pkg/types"
-	"github.com/codablock/kluctl/pkg/utils"
-	"github.com/codablock/kluctl/pkg/yaml"
+	types2 "github.com/kluctl/kluctl/pkg/types"
+	"github.com/kluctl/kluctl/pkg/utils"
+	"github.com/kluctl/kluctl/pkg/yaml"
 	log "github.com/sirupsen/logrus"
 	"io/ioutil"
 	"os"
diff --git a/pkg/kluctl_project/load_targets.go b/pkg/kluctl_project/load_targets.go
index 50643e6d0..f3087140b 100644
--- a/pkg/kluctl_project/load_targets.go
+++ b/pkg/kluctl_project/load_targets.go
@@ -2,12 +2,12 @@ package kluctl_project
 
 import (
 	"fmt"
-	git_url "github.com/codablock/kluctl/pkg/git/git-url"
-	"github.com/codablock/kluctl/pkg/jinja2"
-	"github.com/codablock/kluctl/pkg/types"
-	"github.com/codablock/kluctl/pkg/utils"
-	"github.com/codablock/kluctl/pkg/utils/uo"
-	"github.com/codablock/kluctl/pkg/yaml"
+	git_url "github.com/kluctl/kluctl/pkg/git/git-url"
+	"github.com/kluctl/kluctl/pkg/jinja2"
+	"github.com/kluctl/kluctl/pkg/types"
+	"github.com/kluctl/kluctl/pkg/utils"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
+	"github.com/kluctl/kluctl/pkg/yaml"
 	log "github.com/sirupsen/logrus"
 	"path/filepath"
 	"reflect"
diff --git a/pkg/kluctl_project/project.go b/pkg/kluctl_project/project.go
index c716a6935..055727a54 100644
--- a/pkg/kluctl_project/project.go
+++ b/pkg/kluctl_project/project.go
@@ -2,10 +2,10 @@ package kluctl_project
 
 import (
 	"fmt"
-	"github.com/codablock/kluctl/pkg/git"
-	git_url "github.com/codablock/kluctl/pkg/git/git-url"
-	"github.com/codablock/kluctl/pkg/jinja2"
-	"github.com/codablock/kluctl/pkg/types"
+	"github.com/kluctl/kluctl/pkg/git"
+	git_url "github.com/kluctl/kluctl/pkg/git/git-url"
+	"github.com/kluctl/kluctl/pkg/jinja2"
+	"github.com/kluctl/kluctl/pkg/types"
 	"regexp"
 )
 
diff --git a/pkg/python/embed.go b/pkg/python/embed.go
index 1ee3cf3d1..c9310de48 100644
--- a/pkg/python/embed.go
+++ b/pkg/python/embed.go
@@ -2,8 +2,8 @@ package python
 
 import (
 	"fmt"
-	"github.com/codablock/kluctl/pkg/utils"
-	"github.com/codablock/kluctl/pkg/utils/embed_util"
+	"github.com/kluctl/kluctl/pkg/utils"
+	"github.com/kluctl/kluctl/pkg/utils/embed_util"
 	"log"
 	"path/filepath"
 	"runtime"
diff --git a/pkg/registries/registries.go b/pkg/registries/registries.go
index 200f6786e..8d81a79a7 100644
--- a/pkg/registries/registries.go
+++ b/pkg/registries/registries.go
@@ -4,8 +4,8 @@ import (
 	"bufio"
 	"bytes"
 	"fmt"
-	"github.com/codablock/kluctl/pkg/utils"
-	"github.com/codablock/kluctl/pkg/utils/uo"
+	"github.com/kluctl/kluctl/pkg/utils"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
 	"github.com/docker/distribution/registry/client/auth/challenge"
 	"github.com/golang-jwt/jwt/v4"
 	"github.com/google/go-containerregistry/pkg/authn"
diff --git a/pkg/seal/bootstrap.go b/pkg/seal/bootstrap.go
index 4deeb3618..ca1807a16 100644
--- a/pkg/seal/bootstrap.go
+++ b/pkg/seal/bootstrap.go
@@ -6,9 +6,9 @@ import (
 	"encoding/base64"
 	"encoding/pem"
 	"github.com/bitnami-labs/sealed-secrets/pkg/crypto"
-	"github.com/codablock/kluctl/pkg/k8s"
-	k8s2 "github.com/codablock/kluctl/pkg/types/k8s"
-	"github.com/codablock/kluctl/pkg/utils/uo"
+	"github.com/kluctl/kluctl/pkg/k8s"
+	k8s2 "github.com/kluctl/kluctl/pkg/types/k8s"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
 	log "github.com/sirupsen/logrus"
 	v1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/runtime/schema"
diff --git a/pkg/seal/fetch_cert.go b/pkg/seal/fetch_cert.go
index 4547712b5..ef341ba4a 100644
--- a/pkg/seal/fetch_cert.go
+++ b/pkg/seal/fetch_cert.go
@@ -5,7 +5,7 @@ import (
 	"crypto/rsa"
 	"errors"
 	"fmt"
-	"github.com/codablock/kluctl/pkg/k8s"
+	"github.com/kluctl/kluctl/pkg/k8s"
 	log "github.com/sirupsen/logrus"
 	"io/ioutil"
 	v12 "k8s.io/api/core/v1"
diff --git a/pkg/seal/sealer.go b/pkg/seal/sealer.go
index 14597a69f..9c715006a 100644
--- a/pkg/seal/sealer.go
+++ b/pkg/seal/sealer.go
@@ -7,12 +7,12 @@ import (
 	"encoding/hex"
 	"fmt"
 	"github.com/bitnami-labs/sealed-secrets/pkg/crypto"
-	"github.com/codablock/kluctl/pkg/k8s"
-	"github.com/codablock/kluctl/pkg/types"
-	k8s2 "github.com/codablock/kluctl/pkg/types/k8s"
-	"github.com/codablock/kluctl/pkg/utils"
-	"github.com/codablock/kluctl/pkg/utils/uo"
-	"github.com/codablock/kluctl/pkg/yaml"
+	"github.com/kluctl/kluctl/pkg/k8s"
+	"github.com/kluctl/kluctl/pkg/types"
+	k8s2 "github.com/kluctl/kluctl/pkg/types/k8s"
+	"github.com/kluctl/kluctl/pkg/utils"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
+	"github.com/kluctl/kluctl/pkg/yaml"
 	log "github.com/sirupsen/logrus"
 	"golang.org/x/crypto/scrypt"
 	"k8s.io/apimachinery/pkg/runtime/schema"
diff --git a/pkg/seal/secrets_loader.go b/pkg/seal/secrets_loader.go
index 55ebf0262..d8eccb47a 100644
--- a/pkg/seal/secrets_loader.go
+++ b/pkg/seal/secrets_loader.go
@@ -2,11 +2,11 @@ package seal
 
 import (
 	"fmt"
-	"github.com/codablock/kluctl/pkg/kluctl_project"
-	"github.com/codablock/kluctl/pkg/types"
-	"github.com/codablock/kluctl/pkg/utils"
-	"github.com/codablock/kluctl/pkg/utils/aws"
-	"github.com/codablock/kluctl/pkg/utils/uo"
+	"github.com/kluctl/kluctl/pkg/kluctl_project"
+	"github.com/kluctl/kluctl/pkg/types"
+	"github.com/kluctl/kluctl/pkg/utils"
+	"github.com/kluctl/kluctl/pkg/utils/aws"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
 	"os"
 	"path/filepath"
 	"strings"
diff --git a/pkg/types/cluster_config.go b/pkg/types/cluster_config.go
index 956ba7527..6b1f33b3b 100644
--- a/pkg/types/cluster_config.go
+++ b/pkg/types/cluster_config.go
@@ -2,9 +2,9 @@ package types
 
 import (
 	"fmt"
-	"github.com/codablock/kluctl/pkg/utils"
-	"github.com/codablock/kluctl/pkg/utils/uo"
-	"github.com/codablock/kluctl/pkg/yaml"
+	"github.com/kluctl/kluctl/pkg/utils"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
+	"github.com/kluctl/kluctl/pkg/yaml"
 	"path/filepath"
 )
 
diff --git a/pkg/types/command_result.go b/pkg/types/command_result.go
index 55fcb7e0f..ed3c97f21 100644
--- a/pkg/types/command_result.go
+++ b/pkg/types/command_result.go
@@ -1,8 +1,8 @@
 package types
 
 import (
-	"github.com/codablock/kluctl/pkg/types/k8s"
-	"github.com/codablock/kluctl/pkg/utils/uo"
+	"github.com/kluctl/kluctl/pkg/types/k8s"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
 )
 
 type Change struct {
diff --git a/pkg/types/deployment.go b/pkg/types/deployment.go
index 6b203b57e..771a1584a 100644
--- a/pkg/types/deployment.go
+++ b/pkg/types/deployment.go
@@ -1,8 +1,8 @@
 package types
 
 import (
-	"github.com/codablock/kluctl/pkg/utils/uo"
-	"github.com/codablock/kluctl/pkg/yaml"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
+	"github.com/kluctl/kluctl/pkg/yaml"
 	"github.com/go-playground/validator/v10"
 )
 
diff --git a/pkg/types/git_project.go b/pkg/types/git_project.go
index e9d3f8f1f..5edfb63ca 100644
--- a/pkg/types/git_project.go
+++ b/pkg/types/git_project.go
@@ -1,8 +1,8 @@
 package types
 
 import (
-	git_url "github.com/codablock/kluctl/pkg/git/git-url"
-	"github.com/codablock/kluctl/pkg/yaml"
+	git_url "github.com/kluctl/kluctl/pkg/git/git-url"
+	"github.com/kluctl/kluctl/pkg/yaml"
 	"github.com/go-playground/validator/v10"
 	"strings"
 )
diff --git a/pkg/types/kluctl_project.go b/pkg/types/kluctl_project.go
index 32d6fb43d..917f1570b 100644
--- a/pkg/types/kluctl_project.go
+++ b/pkg/types/kluctl_project.go
@@ -1,8 +1,8 @@
 package types
 
 import (
-	"github.com/codablock/kluctl/pkg/utils/uo"
-	"github.com/codablock/kluctl/pkg/yaml"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
+	"github.com/kluctl/kluctl/pkg/yaml"
 )
 
 type DynamicArg struct {
diff --git a/pkg/types/secrets_source.go b/pkg/types/secrets_source.go
index 176a069c3..9b7ec827c 100644
--- a/pkg/types/secrets_source.go
+++ b/pkg/types/secrets_source.go
@@ -1,7 +1,7 @@
 package types
 
 import (
-	"github.com/codablock/kluctl/pkg/utils/uo"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
 	"github.com/go-playground/validator/v10"
 )
 
diff --git a/pkg/types/target_config.go b/pkg/types/target_config.go
index e88b8919e..1b439f532 100644
--- a/pkg/types/target_config.go
+++ b/pkg/types/target_config.go
@@ -1,8 +1,8 @@
 package types
 
 import (
-	"github.com/codablock/kluctl/pkg/types/k8s"
-	"github.com/codablock/kluctl/pkg/utils/uo"
+	"github.com/kluctl/kluctl/pkg/types/k8s"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
 )
 
 type FixedImage struct {
diff --git a/pkg/utils/embed_util/extract.go b/pkg/utils/embed_util/extract.go
index 02d4a47ac..cf753c496 100644
--- a/pkg/utils/embed_util/extract.go
+++ b/pkg/utils/embed_util/extract.go
@@ -2,7 +2,7 @@ package embed_util
 
 import (
 	"fmt"
-	"github.com/codablock/kluctl/pkg/utils"
+	"github.com/kluctl/kluctl/pkg/utils"
 	"github.com/gofrs/flock"
 	"io"
 	"io/fs"
diff --git a/pkg/utils/embed_util/packer/main.go b/pkg/utils/embed_util/packer/main.go
index a0f347b15..912e114a1 100644
--- a/pkg/utils/embed_util/packer/main.go
+++ b/pkg/utils/embed_util/packer/main.go
@@ -7,7 +7,7 @@ import (
 	"crypto/sha256"
 	"encoding/hex"
 	"fmt"
-	"github.com/codablock/kluctl/pkg/utils"
+	"github.com/kluctl/kluctl/pkg/utils"
 	"github.com/gobwas/glob"
 	log "github.com/sirupsen/logrus"
 	"io/fs"
diff --git a/pkg/utils/uo/k8s_fields.go b/pkg/utils/uo/k8s_fields.go
index 2815078ac..68d07dc61 100644
--- a/pkg/utils/uo/k8s_fields.go
+++ b/pkg/utils/uo/k8s_fields.go
@@ -1,7 +1,7 @@
 package uo
 
 import (
-	"github.com/codablock/kluctl/pkg/types/k8s"
+	"github.com/kluctl/kluctl/pkg/types/k8s"
 	log "github.com/sirupsen/logrus"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime/schema"
diff --git a/pkg/utils/uo/unstructured.go b/pkg/utils/uo/unstructured.go
index 709d402ac..1dd8c4adf 100644
--- a/pkg/utils/uo/unstructured.go
+++ b/pkg/utils/uo/unstructured.go
@@ -2,7 +2,7 @@ package uo
 
 import (
 	"fmt"
-	"github.com/codablock/kluctl/pkg/utils"
+	"github.com/kluctl/kluctl/pkg/utils"
 	log "github.com/sirupsen/logrus"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 )
diff --git a/pkg/utils/uo/uo.go b/pkg/utils/uo/uo.go
index 6097808cb..6d372fb9d 100644
--- a/pkg/utils/uo/uo.go
+++ b/pkg/utils/uo/uo.go
@@ -2,7 +2,7 @@ package uo
 
 import (
 	"fmt"
-	"github.com/codablock/kluctl/pkg/yaml"
+	"github.com/kluctl/kluctl/pkg/yaml"
 	"github.com/jinzhu/copier"
 	log "github.com/sirupsen/logrus"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
diff --git a/pkg/utils/versions/latest_version.go b/pkg/utils/versions/latest_version.go
index 68c3779ec..7fb73a228 100644
--- a/pkg/utils/versions/latest_version.go
+++ b/pkg/utils/versions/latest_version.go
@@ -2,7 +2,7 @@ package versions
 
 import (
 	"fmt"
-	"github.com/codablock/kluctl/pkg/utils"
+	"github.com/kluctl/kluctl/pkg/utils"
 	"regexp"
 	"strconv"
 )
diff --git a/pkg/utils/versions/looseversion.go b/pkg/utils/versions/looseversion.go
index 79b36c3ea..c0fb8c169 100644
--- a/pkg/utils/versions/looseversion.go
+++ b/pkg/utils/versions/looseversion.go
@@ -1,7 +1,7 @@
 package versions
 
 import (
-	"github.com/codablock/kluctl/pkg/utils"
+	"github.com/kluctl/kluctl/pkg/utils"
 	"regexp"
 	"sort"
 	"strconv"
diff --git a/pkg/validation/validation.go b/pkg/validation/validation.go
index 99da49c5a..41e08c323 100644
--- a/pkg/validation/validation.go
+++ b/pkg/validation/validation.go
@@ -2,9 +2,9 @@ package validation
 
 import (
 	"fmt"
-	"github.com/codablock/kluctl/pkg/k8s"
-	"github.com/codablock/kluctl/pkg/types"
-	"github.com/codablock/kluctl/pkg/utils/uo"
+	"github.com/kluctl/kluctl/pkg/k8s"
+	"github.com/kluctl/kluctl/pkg/types"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
 	"k8s.io/apimachinery/pkg/api/errors"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"regexp"
diff --git a/pkg/yaml/yaml.go b/pkg/yaml/yaml.go
index 39aee6984..762628ea2 100644
--- a/pkg/yaml/yaml.go
+++ b/pkg/yaml/yaml.go
@@ -5,7 +5,7 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
-	"github.com/codablock/kluctl/pkg/utils"
+	"github.com/kluctl/kluctl/pkg/utils"
 	"github.com/goccy/go-yaml"
 	yaml3 "gopkg.in/yaml.v3"
 	"io"

From be72f5dc0a2b48ceb6ecdd2c24397b40176feca3 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Thu, 24 Mar 2022 15:10:12 +0100
Subject: [PATCH 0594/2916] fix: Ignore non-existing identity files

The default value for "IdentityFile" is ~/.ssh/identity, which does not
exist on most systems, so we have to ignore not-exist errors.
---
 pkg/git/auth/ssh_auth_provider.go | 13 ++++++++-----
 1 file changed, 8 insertions(+), 5 deletions(-)

diff --git a/pkg/git/auth/ssh_auth_provider.go b/pkg/git/auth/ssh_auth_provider.go
index 600609dab..d739ba34e 100644
--- a/pkg/git/auth/ssh_auth_provider.go
+++ b/pkg/git/auth/ssh_auth_provider.go
@@ -2,15 +2,16 @@ package auth
 
 import (
 	"fmt"
-	git_url "github.com/kluctl/kluctl/pkg/git/git-url"
-	"github.com/kluctl/kluctl/pkg/utils"
 	"github.com/go-git/go-git/v5/plumbing/transport"
 	git_ssh "github.com/go-git/go-git/v5/plumbing/transport/ssh"
+	git_url "github.com/kluctl/kluctl/pkg/git/git-url"
+	"github.com/kluctl/kluctl/pkg/utils"
 	log "github.com/sirupsen/logrus"
 	sshagent "github.com/xanzy/ssh-agent"
 	"golang.org/x/crypto/ssh"
 	"golang.org/x/crypto/ssh/agent"
 	"io/ioutil"
+	"os"
 	"os/user"
 	"path/filepath"
 )
@@ -48,11 +49,13 @@ func (a *sshDefaultIdentityAndAgent) Signers() ([]ssh.Signer, error) {
 	if identityFromConfig != "" {
 		identityFromConfig = utils.ExpandPath(identityFromConfig)
 		signer, err := readKey(identityFromConfig)
-		if err != nil {
+		if err != nil && !os.IsNotExist(err) {
 			return nil, err
 		}
-		ret = append(ret, signer)
-		return ret, nil
+		if err == nil {
+			ret = append(ret, signer)
+			return ret, nil
+		}
 	}
 	if a.defaultIdentity != nil {
 		ret = append(ret, a.defaultIdentity)

From df5af9d8234a167dcd3175dbae55c05c99a70248 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Thu, 24 Mar 2022 15:10:36 +0100
Subject: [PATCH 0595/2916] chore: Run gofmt

---
 cmd/kluctl/commands/cmd_helm_update.go  | 2 +-
 e2e/project.go                          | 2 +-
 pkg/deployment/external_args.go         | 2 +-
 pkg/deployment/utils/downscale_utils.go | 2 +-
 pkg/diff/diff.go                        | 6 +++---
 pkg/git/auth/auth_provider.go           | 2 +-
 pkg/git/auth/env_auth_provider.go       | 4 ++--
 pkg/git/auth/git_credentials_file.go    | 4 ++--
 pkg/git/mirrored_repo.go                | 6 +++---
 pkg/git/poor_mans_clone.go              | 2 +-
 pkg/jinja2/jinja2.go                    | 2 +-
 pkg/k8s/k8s_cluster.go                  | 2 +-
 pkg/kluctl_project/load.go              | 2 +-
 pkg/registries/registries.go            | 4 ++--
 pkg/types/deployment.go                 | 2 +-
 pkg/types/git_project.go                | 2 +-
 pkg/types/secrets_source.go             | 2 +-
 pkg/utils/embed_util/extract.go         | 2 +-
 pkg/utils/embed_util/packer/main.go     | 2 +-
 pkg/utils/fs.go                         | 2 +-
 pkg/utils/uo/uo.go                      | 2 +-
 pkg/yaml/yaml.go                        | 2 +-
 22 files changed, 29 insertions(+), 29 deletions(-)

diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go
index dc6c768b5..89f5eefcc 100644
--- a/cmd/kluctl/commands/cmd_helm_update.go
+++ b/cmd/kluctl/commands/cmd_helm_update.go
@@ -2,8 +2,8 @@ package commands
 
 import (
 	"fmt"
-	"github.com/kluctl/kluctl/pkg/deployment"
 	"github.com/go-git/go-git/v5"
+	"github.com/kluctl/kluctl/pkg/deployment"
 	log "github.com/sirupsen/logrus"
 	"io/fs"
 	"path/filepath"
diff --git a/e2e/project.go b/e2e/project.go
index d42c282ef..fd9e60f5d 100644
--- a/e2e/project.go
+++ b/e2e/project.go
@@ -3,11 +3,11 @@ package e2e
 import (
 	"context"
 	"fmt"
+	"github.com/go-git/go-git/v5"
 	http_server "github.com/kluctl/kluctl/pkg/git/http-server"
 	"github.com/kluctl/kluctl/pkg/utils"
 	"github.com/kluctl/kluctl/pkg/utils/uo"
 	"github.com/kluctl/kluctl/pkg/yaml"
-	"github.com/go-git/go-git/v5"
 	log "github.com/sirupsen/logrus"
 	"io/ioutil"
 	"net"
diff --git a/pkg/deployment/external_args.go b/pkg/deployment/external_args.go
index b9aa526f0..70e203353 100644
--- a/pkg/deployment/external_args.go
+++ b/pkg/deployment/external_args.go
@@ -54,7 +54,7 @@ func CheckRequiredDeployArgs(dir string, varsCtx *jinja2.VarsCtx, deployArgs *uo
 		// messages anymore.
 		varsCtx2 := varsCtx.Copy()
 		varsCtx2.UpdateChild("args", deployArgs)
-		err = varsCtx2.RenderYamlFile(yaml.FixNameExt(dir,"deployment.yml"), []string{dir}, &conf)
+		err = varsCtx2.RenderYamlFile(yaml.FixNameExt(dir, "deployment.yml"), []string{dir}, &conf)
 		if err != nil {
 			return err
 		}
diff --git a/pkg/deployment/utils/downscale_utils.go b/pkg/deployment/utils/downscale_utils.go
index 1be9b3974..64dd5e87d 100644
--- a/pkg/deployment/utils/downscale_utils.go
+++ b/pkg/deployment/utils/downscale_utils.go
@@ -2,9 +2,9 @@ package utils
 
 import (
 	"fmt"
+	jsonpatch "github.com/evanphx/json-patch"
 	"github.com/kluctl/kluctl/pkg/utils/uo"
 	"github.com/kluctl/kluctl/pkg/yaml"
-	jsonpatch "github.com/evanphx/json-patch"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"regexp"
 	"strconv"
diff --git a/pkg/diff/diff.go b/pkg/diff/diff.go
index 53a1d0e7a..1ae783d7a 100644
--- a/pkg/diff/diff.go
+++ b/pkg/diff/diff.go
@@ -2,12 +2,12 @@ package diff
 
 import (
 	"fmt"
-	"github.com/kluctl/kluctl/pkg/types"
-	"github.com/kluctl/kluctl/pkg/utils/uo"
-	"github.com/kluctl/kluctl/pkg/yaml"
 	"github.com/hexops/gotextdiff"
 	"github.com/hexops/gotextdiff/myers"
 	"github.com/hexops/gotextdiff/span"
+	"github.com/kluctl/kluctl/pkg/types"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
+	"github.com/kluctl/kluctl/pkg/yaml"
 	diff2 "github.com/r3labs/diff/v2"
 	"log"
 	"reflect"
diff --git a/pkg/git/auth/auth_provider.go b/pkg/git/auth/auth_provider.go
index 11ca4bea3..63f61e18a 100644
--- a/pkg/git/auth/auth_provider.go
+++ b/pkg/git/auth/auth_provider.go
@@ -1,8 +1,8 @@
 package auth
 
 import (
-	git_url "github.com/kluctl/kluctl/pkg/git/git-url"
 	"github.com/go-git/go-git/v5/plumbing/transport"
+	git_url "github.com/kluctl/kluctl/pkg/git/git-url"
 )
 
 type GitAuthProvider interface {
diff --git a/pkg/git/auth/env_auth_provider.go b/pkg/git/auth/env_auth_provider.go
index bb8835b29..902b65212 100644
--- a/pkg/git/auth/env_auth_provider.go
+++ b/pkg/git/auth/env_auth_provider.go
@@ -1,11 +1,11 @@
 package auth
 
 import (
-	git_url "github.com/kluctl/kluctl/pkg/git/git-url"
-	"github.com/kluctl/kluctl/pkg/utils"
 	"github.com/go-git/go-git/v5/plumbing/transport"
 	"github.com/go-git/go-git/v5/plumbing/transport/http"
 	"github.com/go-git/go-git/v5/plumbing/transport/ssh"
+	git_url "github.com/kluctl/kluctl/pkg/git/git-url"
+	"github.com/kluctl/kluctl/pkg/utils"
 	log "github.com/sirupsen/logrus"
 	"strings"
 )
diff --git a/pkg/git/auth/git_credentials_file.go b/pkg/git/auth/git_credentials_file.go
index 8a4f895fc..e2a497bee 100644
--- a/pkg/git/auth/git_credentials_file.go
+++ b/pkg/git/auth/git_credentials_file.go
@@ -2,10 +2,10 @@ package auth
 
 import (
 	"bufio"
-	git_url "github.com/kluctl/kluctl/pkg/git/git-url"
-	"github.com/kluctl/kluctl/pkg/utils"
 	"github.com/go-git/go-git/v5/plumbing/transport"
 	"github.com/go-git/go-git/v5/plumbing/transport/http"
+	git_url "github.com/kluctl/kluctl/pkg/git/git-url"
+	"github.com/kluctl/kluctl/pkg/utils"
 	log "github.com/sirupsen/logrus"
 	giturls "github.com/whilp/git-urls"
 	"os"
diff --git a/pkg/git/mirrored_repo.go b/pkg/git/mirrored_repo.go
index 6c3d60c2f..f3bb1473f 100644
--- a/pkg/git/mirrored_repo.go
+++ b/pkg/git/mirrored_repo.go
@@ -2,12 +2,12 @@ package git
 
 import (
 	"fmt"
-	auth2 "github.com/kluctl/kluctl/pkg/git/auth"
-	git_url "github.com/kluctl/kluctl/pkg/git/git-url"
-	"github.com/kluctl/kluctl/pkg/utils"
 	"github.com/go-git/go-git/v5"
 	"github.com/go-git/go-git/v5/config"
 	"github.com/go-git/go-git/v5/plumbing"
+	auth2 "github.com/kluctl/kluctl/pkg/git/auth"
+	git_url "github.com/kluctl/kluctl/pkg/git/git-url"
+	"github.com/kluctl/kluctl/pkg/utils"
 	log "github.com/sirupsen/logrus"
 	"io/ioutil"
 	"os"
diff --git a/pkg/git/poor_mans_clone.go b/pkg/git/poor_mans_clone.go
index 0288a45e0..53c6d41e4 100644
--- a/pkg/git/poor_mans_clone.go
+++ b/pkg/git/poor_mans_clone.go
@@ -2,10 +2,10 @@ package git
 
 import (
 	"fmt"
-	"github.com/kluctl/kluctl/pkg/utils"
 	"github.com/go-git/go-git/v5"
 	"github.com/go-git/go-git/v5/config"
 	"github.com/go-git/go-git/v5/plumbing"
+	"github.com/kluctl/kluctl/pkg/utils"
 	"io/ioutil"
 	"os"
 	"path/filepath"
diff --git a/pkg/jinja2/jinja2.go b/pkg/jinja2/jinja2.go
index b8bcfa8d9..eae023375 100644
--- a/pkg/jinja2/jinja2.go
+++ b/pkg/jinja2/jinja2.go
@@ -2,9 +2,9 @@ package jinja2
 
 import (
 	"fmt"
+	"github.com/gobwas/glob"
 	"github.com/kluctl/kluctl/pkg/utils"
 	"github.com/kluctl/kluctl/pkg/utils/uo"
-	"github.com/gobwas/glob"
 	"io/fs"
 	"io/ioutil"
 	"os"
diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go
index 77827a514..4fd950862 100644
--- a/pkg/k8s/k8s_cluster.go
+++ b/pkg/k8s/k8s_cluster.go
@@ -4,11 +4,11 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
+	goversion "github.com/hashicorp/go-version"
 	"github.com/kluctl/kluctl/pkg/types/k8s"
 	"github.com/kluctl/kluctl/pkg/utils"
 	"github.com/kluctl/kluctl/pkg/utils/uo"
 	"github.com/kluctl/kluctl/pkg/yaml"
-	goversion "github.com/hashicorp/go-version"
 	log "github.com/sirupsen/logrus"
 	"k8s.io/apimachinery/pkg/api/errors"
 	"k8s.io/apimachinery/pkg/api/meta"
diff --git a/pkg/kluctl_project/load.go b/pkg/kluctl_project/load.go
index 1fb88901f..c8e6db7e2 100644
--- a/pkg/kluctl_project/load.go
+++ b/pkg/kluctl_project/load.go
@@ -261,7 +261,7 @@ func (c *KluctlProjectContext) CreateTGZArchive(archivePath string, metadataPath
 		}
 	}
 
-	if err = utils.AddToTar(tw, c.getConfigPath(c.ProjectDir), yaml.FixNameExt(c.ProjectDir,".kluctl.yml"), filter); err != nil {
+	if err = utils.AddToTar(tw, c.getConfigPath(c.ProjectDir), yaml.FixNameExt(c.ProjectDir, ".kluctl.yml"), filter); err != nil {
 		return err
 	}
 	if err = utils.AddToTar(tw, c.ProjectDir, "kluctl-project", filter); err != nil {
diff --git a/pkg/registries/registries.go b/pkg/registries/registries.go
index 8d81a79a7..2f6fa6d94 100644
--- a/pkg/registries/registries.go
+++ b/pkg/registries/registries.go
@@ -4,13 +4,13 @@ import (
 	"bufio"
 	"bytes"
 	"fmt"
-	"github.com/kluctl/kluctl/pkg/utils"
-	"github.com/kluctl/kluctl/pkg/utils/uo"
 	"github.com/docker/distribution/registry/client/auth/challenge"
 	"github.com/golang-jwt/jwt/v4"
 	"github.com/google/go-containerregistry/pkg/authn"
 	"github.com/google/go-containerregistry/pkg/name"
 	"github.com/google/go-containerregistry/pkg/v1/remote"
+	"github.com/kluctl/kluctl/pkg/utils"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
 	log "github.com/sirupsen/logrus"
 	"io/ioutil"
 	"net/http"
diff --git a/pkg/types/deployment.go b/pkg/types/deployment.go
index 771a1584a..08fc2fa3c 100644
--- a/pkg/types/deployment.go
+++ b/pkg/types/deployment.go
@@ -1,9 +1,9 @@
 package types
 
 import (
+	"github.com/go-playground/validator/v10"
 	"github.com/kluctl/kluctl/pkg/utils/uo"
 	"github.com/kluctl/kluctl/pkg/yaml"
-	"github.com/go-playground/validator/v10"
 )
 
 type DeploymentItemConfig struct {
diff --git a/pkg/types/git_project.go b/pkg/types/git_project.go
index 5edfb63ca..7d7c74d4b 100644
--- a/pkg/types/git_project.go
+++ b/pkg/types/git_project.go
@@ -1,9 +1,9 @@
 package types
 
 import (
+	"github.com/go-playground/validator/v10"
 	git_url "github.com/kluctl/kluctl/pkg/git/git-url"
 	"github.com/kluctl/kluctl/pkg/yaml"
-	"github.com/go-playground/validator/v10"
 	"strings"
 )
 
diff --git a/pkg/types/secrets_source.go b/pkg/types/secrets_source.go
index 9b7ec827c..5490926f0 100644
--- a/pkg/types/secrets_source.go
+++ b/pkg/types/secrets_source.go
@@ -1,8 +1,8 @@
 package types
 
 import (
-	"github.com/kluctl/kluctl/pkg/utils/uo"
 	"github.com/go-playground/validator/v10"
+	"github.com/kluctl/kluctl/pkg/utils/uo"
 )
 
 type SecretSourceAwsSecretsManager struct {
diff --git a/pkg/utils/embed_util/extract.go b/pkg/utils/embed_util/extract.go
index cf753c496..21b5deda2 100644
--- a/pkg/utils/embed_util/extract.go
+++ b/pkg/utils/embed_util/extract.go
@@ -2,8 +2,8 @@ package embed_util
 
 import (
 	"fmt"
-	"github.com/kluctl/kluctl/pkg/utils"
 	"github.com/gofrs/flock"
+	"github.com/kluctl/kluctl/pkg/utils"
 	"io"
 	"io/fs"
 	"io/ioutil"
diff --git a/pkg/utils/embed_util/packer/main.go b/pkg/utils/embed_util/packer/main.go
index 912e114a1..60bf426cc 100644
--- a/pkg/utils/embed_util/packer/main.go
+++ b/pkg/utils/embed_util/packer/main.go
@@ -7,8 +7,8 @@ import (
 	"crypto/sha256"
 	"encoding/hex"
 	"fmt"
-	"github.com/kluctl/kluctl/pkg/utils"
 	"github.com/gobwas/glob"
+	"github.com/kluctl/kluctl/pkg/utils"
 	log "github.com/sirupsen/logrus"
 	"io/fs"
 	"io/ioutil"
diff --git a/pkg/utils/fs.go b/pkg/utils/fs.go
index da44faa2b..30dbcfc1c 100644
--- a/pkg/utils/fs.go
+++ b/pkg/utils/fs.go
@@ -126,4 +126,4 @@ func ExpandPath(p string) string {
 		p = homedir.HomeDir() + p[1:]
 	}
 	return p
-}
\ No newline at end of file
+}
diff --git a/pkg/utils/uo/uo.go b/pkg/utils/uo/uo.go
index 6d372fb9d..cae1b10be 100644
--- a/pkg/utils/uo/uo.go
+++ b/pkg/utils/uo/uo.go
@@ -2,8 +2,8 @@ package uo
 
 import (
 	"fmt"
-	"github.com/kluctl/kluctl/pkg/yaml"
 	"github.com/jinzhu/copier"
+	"github.com/kluctl/kluctl/pkg/yaml"
 	log "github.com/sirupsen/logrus"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"reflect"
diff --git a/pkg/yaml/yaml.go b/pkg/yaml/yaml.go
index 762628ea2..adc37aac6 100644
--- a/pkg/yaml/yaml.go
+++ b/pkg/yaml/yaml.go
@@ -5,8 +5,8 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
-	"github.com/kluctl/kluctl/pkg/utils"
 	"github.com/goccy/go-yaml"
+	"github.com/kluctl/kluctl/pkg/utils"
 	yaml3 "gopkg.in/yaml.v3"
 	"io"
 	"os"

From d0ad656daa233688ee89c912ac5f1133f4bca98c Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Fri, 25 Mar 2022 12:01:08 +0100
Subject: [PATCH 0596/2916] ci: Don't run docker-host job when releasing

---
 .github/workflows/build-and-release.yml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml
index 50affd659..d9ae6d735 100644
--- a/.github/workflows/build-and-release.yml
+++ b/.github/workflows/build-and-release.yml
@@ -121,6 +121,7 @@ jobs:
             e2e.test-windows-amd64.exe
 
   docker-host:
+    if: "!startsWith(github.ref, 'refs/tags/')"
     runs-on: ubuntu-latest
     needs:
       - build

From 530b8be5cd38bdc4b7a8523a1ac699366ea3ce75 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Fri, 25 Mar 2022 12:02:43 +0100
Subject: [PATCH 0597/2916] refactor: Don't use global list of git auth
 providers

---
 pkg/git/auth/auth_provider.go | 22 +++++++++++++---------
 pkg/git/mirrored_repo.go      | 14 +++++++-------
 pkg/kluctl_project/git.go     |  4 ++--
 pkg/kluctl_project/project.go | 17 ++++++++++-------
 4 files changed, 32 insertions(+), 25 deletions(-)

diff --git a/pkg/git/auth/auth_provider.go b/pkg/git/auth/auth_provider.go
index 63f61e18a..99a7ba4e5 100644
--- a/pkg/git/auth/auth_provider.go
+++ b/pkg/git/auth/auth_provider.go
@@ -9,14 +9,16 @@ type GitAuthProvider interface {
 	BuildAuth(gitUrl git_url.GitUrl) transport.AuthMethod
 }
 
-var authProviders []GitAuthProvider
+type GitAuthProviders struct {
+	authProviders []GitAuthProvider
+}
 
-func RegisterAuthProvider(p GitAuthProvider) {
-	authProviders = append(authProviders, p)
+func (a *GitAuthProviders) RegisterAuthProvider(p GitAuthProvider) {
+	a.authProviders = append(a.authProviders, p)
 }
 
-func BuildAuth(gitUrl git_url.GitUrl) transport.AuthMethod {
-	for _, p := range authProviders {
+func (a *GitAuthProviders) BuildAuth(gitUrl git_url.GitUrl) transport.AuthMethod {
+	for _, p := range a.authProviders {
 		auth := p.BuildAuth(gitUrl)
 		if auth != nil {
 			return auth
@@ -25,8 +27,10 @@ func BuildAuth(gitUrl git_url.GitUrl) transport.AuthMethod {
 	return nil
 }
 
-func init() {
-	RegisterAuthProvider(&GitEnvAuthProvider{})
-	RegisterAuthProvider(&GitCredentialsFileAuthProvider{})
-	RegisterAuthProvider(&GitSshAuthProvider{})
+func NewDefaultAuthProviders() *GitAuthProviders {
+	a := &GitAuthProviders{}
+	a.RegisterAuthProvider(&GitEnvAuthProvider{})
+	a.RegisterAuthProvider(&GitCredentialsFileAuthProvider{})
+	a.RegisterAuthProvider(&GitSshAuthProvider{})
+	return a
 }
diff --git a/pkg/git/mirrored_repo.go b/pkg/git/mirrored_repo.go
index f3bb1473f..8288c5198 100644
--- a/pkg/git/mirrored_repo.go
+++ b/pkg/git/mirrored_repo.go
@@ -124,14 +124,14 @@ func (g *MirroredGitRepo) cleanupMirrorDir() error {
 	return nil
 }
 
-func (g *MirroredGitRepo) update(repoDir string) error {
+func (g *MirroredGitRepo) update(repoDir string, authProviders *auth2.GitAuthProviders) error {
 	log.Infof("Updating mirror repo: url='%v'", g.url.String())
 	r, err := git.PlainOpen(repoDir)
 	if err != nil {
 		return err
 	}
 
-	auth := auth2.BuildAuth(g.url)
+	auth := authProviders.BuildAuth(g.url)
 
 	remote, err := r.Remote("origin")
 	if err != nil {
@@ -195,10 +195,10 @@ func (g *MirroredGitRepo) update(repoDir string) error {
 	return nil
 }
 
-func (g *MirroredGitRepo) cloneOrUpdate() error {
+func (g *MirroredGitRepo) cloneOrUpdate(authProviders *auth2.GitAuthProviders) error {
 	initMarker := filepath.Join(g.mirrorDir, ".cache2.init")
 	if utils.IsFile(initMarker) {
-		return g.update(g.mirrorDir)
+		return g.update(g.mirrorDir, authProviders)
 	}
 	err := g.cleanupMirrorDir()
 	if err != nil {
@@ -230,7 +230,7 @@ func (g *MirroredGitRepo) cloneOrUpdate() error {
 		return err
 	}
 
-	err = g.update(tmpMirrorDir)
+	err = g.update(tmpMirrorDir, authProviders)
 	if err != nil {
 		return err
 	}
@@ -252,8 +252,8 @@ func (g *MirroredGitRepo) cloneOrUpdate() error {
 	return nil
 }
 
-func (g *MirroredGitRepo) Update() error {
-	err := g.cloneOrUpdate()
+func (g *MirroredGitRepo) Update(authProviders *auth2.GitAuthProviders) error {
+	err := g.cloneOrUpdate(authProviders)
 	if err != nil {
 		return err
 	}
diff --git a/pkg/kluctl_project/git.go b/pkg/kluctl_project/git.go
index dd7c992d5..179bf4725 100644
--- a/pkg/kluctl_project/git.go
+++ b/pkg/kluctl_project/git.go
@@ -30,7 +30,7 @@ func (c *KluctlProjectContext) updateGitCaches() error {
 	doUpdateRepo := func(repo *git.MirroredGitRepo) error {
 		return repo.WithLock(func() error {
 			if !repo.HasUpdated() {
-				return repo.Update()
+				return repo.Update(c.gitAuthProviders)
 			}
 			return nil
 		})
@@ -155,7 +155,7 @@ func (c *KluctlProjectContext) cloneGitProject(gitProject types2.ExternalProject
 
 	err = mr.MaybeWithLock(doLock, func() error {
 		if !mr.HasUpdated() {
-			err = mr.Update()
+			err = mr.Update(c.gitAuthProviders)
 			if err != nil {
 				return err
 			}
diff --git a/pkg/kluctl_project/project.go b/pkg/kluctl_project/project.go
index 055727a54..0a73a2ece 100644
--- a/pkg/kluctl_project/project.go
+++ b/pkg/kluctl_project/project.go
@@ -3,6 +3,7 @@ package kluctl_project
 import (
 	"fmt"
 	"github.com/kluctl/kluctl/pkg/git"
+	auth2 "github.com/kluctl/kluctl/pkg/git/auth"
 	git_url "github.com/kluctl/kluctl/pkg/git/git-url"
 	"github.com/kluctl/kluctl/pkg/jinja2"
 	"github.com/kluctl/kluctl/pkg/types"
@@ -33,8 +34,9 @@ type KluctlProjectContext struct {
 	ClustersDir      string
 	SealedSecretsDir string
 
-	involvedRepos  map[string][]types.InvolvedRepo
-	DynamicTargets []*types.DynamicTarget
+	gitAuthProviders *auth2.GitAuthProviders
+	involvedRepos    map[string][]types.InvolvedRepo
+	DynamicTargets   []*types.DynamicTarget
 
 	mirroredRepos map[string]*git.MirroredGitRepo
 
@@ -43,11 +45,12 @@ type KluctlProjectContext struct {
 
 func NewKluctlProjectContext(loadArgs LoadKluctlProjectArgs, tmpDir string) *KluctlProjectContext {
 	o := &KluctlProjectContext{
-		loadArgs:      loadArgs,
-		TmpDir:        tmpDir,
-		involvedRepos: make(map[string][]types.InvolvedRepo),
-		mirroredRepos: make(map[string]*git.MirroredGitRepo),
-		J2:            loadArgs.J2,
+		loadArgs:         loadArgs,
+		TmpDir:           tmpDir,
+		gitAuthProviders: auth2.NewDefaultAuthProviders(),
+		involvedRepos:    make(map[string][]types.InvolvedRepo),
+		mirroredRepos:    make(map[string]*git.MirroredGitRepo),
+		J2:               loadArgs.J2,
 	}
 	return o
 }

From 71e7b737332c338daf548ea143a9b64c768de529 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Fri, 25 Mar 2022 12:06:26 +0100
Subject: [PATCH 0598/2916] fix: Support multiple IdentityFile entries

---
 pkg/git/auth/ssh_auth_provider.go | 10 ++++------
 1 file changed, 4 insertions(+), 6 deletions(-)

diff --git a/pkg/git/auth/ssh_auth_provider.go b/pkg/git/auth/ssh_auth_provider.go
index d739ba34e..1c71a7338 100644
--- a/pkg/git/auth/ssh_auth_provider.go
+++ b/pkg/git/auth/ssh_auth_provider.go
@@ -3,7 +3,7 @@ package auth
 import (
 	"fmt"
 	"github.com/go-git/go-git/v5/plumbing/transport"
-	git_ssh "github.com/go-git/go-git/v5/plumbing/transport/ssh"
+	"github.com/kevinburke/ssh_config"
 	git_url "github.com/kluctl/kluctl/pkg/git/git-url"
 	"github.com/kluctl/kluctl/pkg/utils"
 	log "github.com/sirupsen/logrus"
@@ -45,16 +45,14 @@ func (a *sshDefaultIdentityAndAgent) ClientConfig() (*ssh.ClientConfig, error) {
 
 func (a *sshDefaultIdentityAndAgent) Signers() ([]ssh.Signer, error) {
 	var ret []ssh.Signer
-	identityFromConfig := git_ssh.DefaultSSHConfig.Get(a.hostname, "IdentityFile")
-	if identityFromConfig != "" {
-		identityFromConfig = utils.ExpandPath(identityFromConfig)
-		signer, err := readKey(identityFromConfig)
+	for _, id := range ssh_config.GetAll(a.hostname, "IdentityFile") {
+		id = utils.ExpandPath(id)
+		signer, err := readKey(id)
 		if err != nil && !os.IsNotExist(err) {
 			return nil, err
 		}
 		if err == nil {
 			ret = append(ret, signer)
-			return ret, nil
 		}
 	}
 	if a.defaultIdentity != nil {

From 6a75d9096344847b6ed2236eac311515d7ceddac Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Fri, 25 Mar 2022 12:17:21 +0100
Subject: [PATCH 0599/2916] refactor: Gather signers in BuildAuth

---
 pkg/git/auth/ssh_auth_provider.go | 76 ++++++++++++++++---------------
 1 file changed, 39 insertions(+), 37 deletions(-)

diff --git a/pkg/git/auth/ssh_auth_provider.go b/pkg/git/auth/ssh_auth_provider.go
index 1c71a7338..4d7576ec4 100644
--- a/pkg/git/auth/ssh_auth_provider.go
+++ b/pkg/git/auth/ssh_auth_provider.go
@@ -9,7 +9,6 @@ import (
 	log "github.com/sirupsen/logrus"
 	sshagent "github.com/xanzy/ssh-agent"
 	"golang.org/x/crypto/ssh"
-	"golang.org/x/crypto/ssh/agent"
 	"io/ioutil"
 	"os"
 	"os/user"
@@ -20,10 +19,9 @@ type GitSshAuthProvider struct {
 }
 
 type sshDefaultIdentityAndAgent struct {
-	hostname        string
-	user            string
-	defaultIdentity ssh.Signer
-	agent           agent.Agent
+	hostname string
+	user     string
+	signers  []ssh.Signer
 }
 
 func (a *sshDefaultIdentityAndAgent) String() string {
@@ -44,28 +42,47 @@ func (a *sshDefaultIdentityAndAgent) ClientConfig() (*ssh.ClientConfig, error) {
 }
 
 func (a *sshDefaultIdentityAndAgent) Signers() ([]ssh.Signer, error) {
-	var ret []ssh.Signer
-	for _, id := range ssh_config.GetAll(a.hostname, "IdentityFile") {
+	return a.signers, nil
+}
+
+func (a *sshDefaultIdentityAndAgent) addDefaultIdentity(gitUrl git_url.GitUrl) {
+	u, err := user.Current()
+	if err != nil {
+		log.Debugf("No current user: %v", err)
+	} else {
+		signer, err := readKey(filepath.Join(u.HomeDir, ".ssh", "id_rsa"))
+		if err != nil {
+			log.Debugf("Failed to read default identity file for url %s: %v", gitUrl.String(), err)
+		} else if signer != nil {
+			a.signers = append(a.signers, signer)
+		}
+	}
+}
+
+func (a *sshDefaultIdentityAndAgent) addConfigIdentities(gitUrl git_url.GitUrl) {
+	for _, id := range ssh_config.GetAll(gitUrl.Hostname(), "IdentityFile") {
 		id = utils.ExpandPath(id)
 		signer, err := readKey(id)
 		if err != nil && !os.IsNotExist(err) {
-			return nil, err
+			log.Debugf("Failed to read key %s for url %s: %v", id, gitUrl.String(), err)
+		} else if err == nil {
+			a.signers = append(a.signers, signer)
 		}
-		if err == nil {
-			ret = append(ret, signer)
-		}
-	}
-	if a.defaultIdentity != nil {
-		ret = append(ret, a.defaultIdentity)
 	}
-	if a.agent != nil {
-		s, err := a.agent.Signers()
+}
+
+func (a *sshDefaultIdentityAndAgent) addAgentIdentities(gitUrl git_url.GitUrl) {
+	agent, _, err := sshagent.New()
+	if err != nil {
+		log.Debugf("Failed to connect to ssh agent for url %s: %v", gitUrl.String(), err)
+	} else {
+		signers, err := agent.Signers()
 		if err != nil {
-			return nil, err
+			log.Debugf("Failed to get signers from ssh agent for url %s: %v", gitUrl.String(), err)
+			return
 		}
-		ret = append(ret, s...)
+		a.signers = append(a.signers, signers...)
 	}
-	return ret, nil
 }
 
 func (a *GitSshAuthProvider) BuildAuth(gitUrl git_url.GitUrl) transport.AuthMethod {
@@ -81,24 +98,9 @@ func (a *GitSshAuthProvider) BuildAuth(gitUrl git_url.GitUrl) transport.AuthMeth
 		user:     gitUrl.User.Username(),
 	}
 
-	u, err := user.Current()
-	if err != nil {
-		log.Debugf("No current user: %v", err)
-	} else {
-		signer, err := readKey(filepath.Join(u.HomeDir, ".ssh", "id_rsa"))
-		if err != nil {
-			log.Debugf("Failed to read default identity file for url %s: %v", gitUrl.String(), err)
-		} else if signer != nil {
-			auth.defaultIdentity = signer
-		}
-	}
-
-	agent, _, err := sshagent.New()
-	if err != nil {
-		log.Debugf("Failed to connect to ssh agent for url %s: %v", gitUrl.String(), err)
-	} else {
-		auth.agent = agent
-	}
+	auth.addDefaultIdentity(gitUrl)
+	auth.addConfigIdentities(gitUrl)
+	auth.addAgentIdentities(gitUrl)
 
 	return auth
 }

From 488d2e1b367398f88d579d4a79a2a78834bcb644 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Fri, 25 Mar 2022 12:37:01 +0100
Subject: [PATCH 0600/2916] fix: Support ssh key passphrases

---
 pkg/git/auth/ssh_auth_provider.go | 63 ++++++++++++++++++++++++-------
 pkg/utils/prompts.go              | 25 ++++++++++++
 2 files changed, 75 insertions(+), 13 deletions(-)

diff --git a/pkg/git/auth/ssh_auth_provider.go b/pkg/git/auth/ssh_auth_provider.go
index 4d7576ec4..d5a04b8ae 100644
--- a/pkg/git/auth/ssh_auth_provider.go
+++ b/pkg/git/auth/ssh_auth_provider.go
@@ -13,15 +13,19 @@ import (
 	"os"
 	"os/user"
 	"path/filepath"
+	"sync"
 )
 
 type GitSshAuthProvider struct {
+	passphrases map[string][]byte
+	passphrasesMutex sync.Mutex
 }
 
 type sshDefaultIdentityAndAgent struct {
-	hostname string
-	user     string
-	signers  []ssh.Signer
+	authProvider *GitSshAuthProvider
+	hostname     string
+	user         string
+	signers      []ssh.Signer
 }
 
 func (a *sshDefaultIdentityAndAgent) String() string {
@@ -50,9 +54,9 @@ func (a *sshDefaultIdentityAndAgent) addDefaultIdentity(gitUrl git_url.GitUrl) {
 	if err != nil {
 		log.Debugf("No current user: %v", err)
 	} else {
-		signer, err := readKey(filepath.Join(u.HomeDir, ".ssh", "id_rsa"))
-		if err != nil {
-			log.Debugf("Failed to read default identity file for url %s: %v", gitUrl.String(), err)
+		signer, err := a.authProvider.readKey(filepath.Join(u.HomeDir, ".ssh", "id_rsa"))
+		if err != nil && !os.IsNotExist(err) {
+			log.Warningf("Failed to read default identity file for url %s: %v", gitUrl.String(), err)
 		} else if signer != nil {
 			a.signers = append(a.signers, signer)
 		}
@@ -62,9 +66,9 @@ func (a *sshDefaultIdentityAndAgent) addDefaultIdentity(gitUrl git_url.GitUrl) {
 func (a *sshDefaultIdentityAndAgent) addConfigIdentities(gitUrl git_url.GitUrl) {
 	for _, id := range ssh_config.GetAll(gitUrl.Hostname(), "IdentityFile") {
 		id = utils.ExpandPath(id)
-		signer, err := readKey(id)
+		signer, err := a.authProvider.readKey(id)
 		if err != nil && !os.IsNotExist(err) {
-			log.Debugf("Failed to read key %s for url %s: %v", id, gitUrl.String(), err)
+			log.Warningf("Failed to read key %s for url %s: %v", id, gitUrl.String(), err)
 		} else if err == nil {
 			a.signers = append(a.signers, signer)
 		}
@@ -74,11 +78,11 @@ func (a *sshDefaultIdentityAndAgent) addConfigIdentities(gitUrl git_url.GitUrl)
 func (a *sshDefaultIdentityAndAgent) addAgentIdentities(gitUrl git_url.GitUrl) {
 	agent, _, err := sshagent.New()
 	if err != nil {
-		log.Debugf("Failed to connect to ssh agent for url %s: %v", gitUrl.String(), err)
+		log.Warningf("Failed to connect to ssh agent for url %s: %v", gitUrl.String(), err)
 	} else {
 		signers, err := agent.Signers()
 		if err != nil {
-			log.Debugf("Failed to get signers from ssh agent for url %s: %v", gitUrl.String(), err)
+			log.Warningf("Failed to get signers from ssh agent for url %s: %v", gitUrl.String(), err)
 			return
 		}
 		a.signers = append(a.signers, signers...)
@@ -94,6 +98,7 @@ func (a *GitSshAuthProvider) BuildAuth(gitUrl git_url.GitUrl) transport.AuthMeth
 	}
 
 	auth := &sshDefaultIdentityAndAgent{
+		authProvider: a,
 		hostname: gitUrl.Hostname(),
 		user:     gitUrl.User.Username(),
 	}
@@ -105,15 +110,47 @@ func (a *GitSshAuthProvider) BuildAuth(gitUrl git_url.GitUrl) transport.AuthMeth
 	return auth
 }
 
-func readKey(path string) (ssh.Signer, error) {
+func (a *GitSshAuthProvider) readKey(path string) (ssh.Signer, error) {
 	pemBytes, err := ioutil.ReadFile(path)
 	if err != nil {
 		return nil, err
 	} else {
 		signer, err := ssh.ParsePrivateKey(pemBytes)
-		if err != nil {
-			return nil, err
+		if err == nil {
+			return signer, nil
+		}
+
+		if _, ok := err.(*ssh.PassphraseMissingError); ok {
+			passphrase := a.getPassphrase(path)
+			if passphrase == nil {
+				return nil, err
+			}
+			return ssh.ParsePrivateKeyWithPassphrase(pemBytes, passphrase)
 		}
+
 		return signer, nil
 	}
 }
+
+func (a *GitSshAuthProvider) getPassphrase(path string) []byte {
+	a.passphrasesMutex.Lock()
+	defer a.passphrasesMutex.Unlock()
+
+	if a.passphrases == nil {
+		a.passphrases = map[string][]byte{}
+	}
+
+	passphrase, ok := a.passphrases[path]
+	if ok {
+		return passphrase
+	}
+
+	passphraseStr, err := utils.AskForPassword(fmt.Sprintf("Enter passphrase for key '%s'", path))
+	if err != nil {
+		log.Warning(err)
+		a.passphrases[path] = nil
+		return nil
+	}
+	a.passphrases[path] = passphrase
+	return []byte(passphraseStr)
+}
diff --git a/pkg/utils/prompts.go b/pkg/utils/prompts.go
index bc5bf7f64..5662fe1aa 100644
--- a/pkg/utils/prompts.go
+++ b/pkg/utils/prompts.go
@@ -4,7 +4,10 @@ import (
 	"fmt"
 	"github.com/mattn/go-isatty"
 	log "github.com/sirupsen/logrus"
+	"golang.org/x/term"
 	"os"
+	"strings"
+	"syscall"
 )
 
 // AskForConfirmation uses Scanln to parse user input. A user must type in "yes" or "no" and
@@ -39,3 +42,25 @@ func AskForConfirmation(prompt string) bool {
 		return AskForConfirmation(prompt)
 	}
 }
+
+func AskForPassword(prompt string) (string, error) {
+	if !isatty.IsTerminal(os.Stderr.Fd()) {
+		err := fmt.Errorf("not a terminal, suppressed credentials prompt: %s", prompt)
+		log.Warning(err)
+		return "", err
+	}
+
+	_, err := fmt.Fprintf(os.Stderr, "%s: ", prompt)
+	if err != nil {
+		return "", err
+	}
+
+	bytePassword, err := term.ReadPassword(int(syscall.Stdin))
+	_, _ = fmt.Fprintf(os.Stderr, "\n")
+	if err != nil {
+		return "", err
+	}
+
+	password := string(bytePassword)
+	return strings.TrimSpace(password), nil
+}

From 80a09dfc6c06c5fc7002d088e213664f4e047b09 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Thu, 31 Mar 2022 09:11:44 +0200
Subject: [PATCH 0601/2916] fix: Don't print exception on CTRL+C

---
 pkg/jinja2/python_src/main.py | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/pkg/jinja2/python_src/main.py b/pkg/jinja2/python_src/main.py
index 1c46fce20..c1d7e54be 100644
--- a/pkg/jinja2/python_src/main.py
+++ b/pkg/jinja2/python_src/main.py
@@ -28,4 +28,7 @@ def main():
         sys.stdout.flush()
 
 if __name__ == "__main__":
-    main()
+    try:
+        main()
+    except KeyboardInterrupt as e:
+        pass

From 9e99518223e5df4c8498012e5ba2442b04ce2bb9 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Mon, 4 Apr 2022 12:14:41 +0200
Subject: [PATCH 0602/2916] fix: Copy text/scanner and modify it to treat ' as
 string

---
 pkg/utils/python_scanner/README.md         |   2 +
 pkg/utils/python_scanner/scanner.go        | 794 +++++++++++++++++++++
 pkg/utils/versions/latest_version_parse.go |   2 +-
 3 files changed, 797 insertions(+), 1 deletion(-)
 create mode 100644 pkg/utils/python_scanner/README.md
 create mode 100644 pkg/utils/python_scanner/scanner.go

diff --git a/pkg/utils/python_scanner/README.md b/pkg/utils/python_scanner/README.md
new file mode 100644
index 000000000..3ae1e4c85
--- /dev/null
+++ b/pkg/utils/python_scanner/README.md
@@ -0,0 +1,2 @@
+This package is a copy of golangs text/scanner package, with the difference that it interprets ' the same way as ", so
+that it's a little bit more like python.
\ No newline at end of file
diff --git a/pkg/utils/python_scanner/scanner.go b/pkg/utils/python_scanner/scanner.go
new file mode 100644
index 000000000..c6f385484
--- /dev/null
+++ b/pkg/utils/python_scanner/scanner.go
@@ -0,0 +1,794 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package scanner provides a scanner and tokenizer for UTF-8-encoded text.
+// It takes an io.Reader providing the source, which then can be tokenized
+// through repeated calls to the Scan function. For compatibility with
+// existing tools, the NUL character is not allowed. If the first character
+// in the source is a UTF-8 encoded byte order mark (BOM), it is discarded.
+//
+// By default, a Scanner skips white space and Go comments and recognizes all
+// literals as defined by the Go language specification. It may be
+// customized to recognize only a subset of those literals and to recognize
+// different identifier and white space characters.
+package python_scanner
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"os"
+	"unicode"
+	"unicode/utf8"
+)
+
+// Position is a value that represents a source position.
+// A position is valid if Line > 0.
+type Position struct {
+	Filename string // filename, if any
+	Offset   int    // byte offset, starting at 0
+	Line     int    // line number, starting at 1
+	Column   int    // column number, starting at 1 (character count per line)
+}
+
+// IsValid reports whether the position is valid.
+func (pos *Position) IsValid() bool { return pos.Line > 0 }
+
+func (pos Position) String() string {
+	s := pos.Filename
+	if s == "" {
+		s = ""
+	}
+	if pos.IsValid() {
+		s += fmt.Sprintf(":%d:%d", pos.Line, pos.Column)
+	}
+	return s
+}
+
+// Predefined mode bits to control recognition of tokens. For instance,
+// to configure a Scanner such that it only recognizes (Go) identifiers,
+// integers, and skips comments, set the Scanner's Mode field to:
+//
+//	ScanIdents | ScanInts | SkipComments
+//
+// With the exceptions of comments, which are skipped if SkipComments is
+// set, unrecognized tokens are not ignored. Instead, the scanner simply
+// returns the respective individual characters (or possibly sub-tokens).
+// For instance, if the mode is ScanIdents (not ScanStrings), the string
+// "foo" is scanned as the token sequence '"' Ident '"'.
+//
+// Use GoTokens to configure the Scanner such that it accepts all Go
+// literal tokens including Go identifiers. Comments will be skipped.
+//
+const (
+	ScanIdents     = 1 << -Ident
+	ScanInts       = 1 << -Int
+	ScanFloats     = 1 << -Float // includes Ints and hexadecimal floats
+	ScanChars      = 1 << -Char
+	ScanStrings    = 1 << -String
+	ScanRawStrings = 1 << -RawString
+	ScanComments   = 1 << -Comment
+	SkipComments   = 1 << -skipComment // if set with ScanComments, comments become white space
+	GoTokens       = ScanIdents | ScanFloats | ScanChars | ScanStrings | ScanRawStrings | ScanComments | SkipComments
+)
+
+// The result of Scan is one of these tokens or a Unicode character.
+const (
+	EOF = -(iota + 1)
+	Ident
+	Int
+	Float
+	Char
+	String
+	RawString
+	Comment
+
+	// internal use only
+	skipComment
+)
+
+var tokenString = map[rune]string{
+	EOF:       "EOF",
+	Ident:     "Ident",
+	Int:       "Int",
+	Float:     "Float",
+	Char:      "Char",
+	String:    "String",
+	RawString: "RawString",
+	Comment:   "Comment",
+}
+
+// TokenString returns a printable string for a token or Unicode character.
+func TokenString(tok rune) string {
+	if s, found := tokenString[tok]; found {
+		return s
+	}
+	return fmt.Sprintf("%q", string(tok))
+}
+
+// GoWhitespace is the default value for the Scanner's Whitespace field.
+// Its value selects Go's white space characters.
+const GoWhitespace = 1<<'\t' | 1<<'\n' | 1<<'\r' | 1<<' '
+
+const bufLen = 1024 // at least utf8.UTFMax
+
+// A Scanner implements reading of Unicode characters and tokens from an io.Reader.
+type Scanner struct {
+	// Input
+	src io.Reader
+
+	// Source buffer
+	srcBuf [bufLen + 1]byte // +1 for sentinel for common case of s.next()
+	srcPos int              // reading position (srcBuf index)
+	srcEnd int              // source end (srcBuf index)
+
+	// Source position
+	srcBufOffset int // byte offset of srcBuf[0] in source
+	line         int // line count
+	column       int // character count
+	lastLineLen  int // length of last line in characters (for correct column reporting)
+	lastCharLen  int // length of last character in bytes
+
+	// Token text buffer
+	// Typically, token text is stored completely in srcBuf, but in general
+	// the token text's head may be buffered in tokBuf while the token text's
+	// tail is stored in srcBuf.
+	tokBuf bytes.Buffer // token text head that is not in srcBuf anymore
+	tokPos int          // token text tail position (srcBuf index); valid if >= 0
+	tokEnd int          // token text tail end (srcBuf index)
+
+	// One character look-ahead
+	ch rune // character before current srcPos
+
+	// Error is called for each error encountered. If no Error
+	// function is set, the error is reported to os.Stderr.
+	Error func(s *Scanner, msg string)
+
+	// ErrorCount is incremented by one for each error encountered.
+	ErrorCount int
+
+	// The Mode field controls which tokens are recognized. For instance,
+	// to recognize Ints, set the ScanInts bit in Mode. The field may be
+	// changed at any time.
+	Mode uint
+
+	// The Whitespace field controls which characters are recognized
+	// as white space. To recognize a character ch <= ' ' as white space,
+	// set the ch'th bit in Whitespace (the Scanner's behavior is undefined
+	// for values ch > ' '). The field may be changed at any time.
+	Whitespace uint64
+
+	// IsIdentRune is a predicate controlling the characters accepted
+	// as the ith rune in an identifier. The set of valid characters
+	// must not intersect with the set of white space characters.
+	// If no IsIdentRune function is set, regular Go identifiers are
+	// accepted instead. The field may be changed at any time.
+	IsIdentRune func(ch rune, i int) bool
+
+	// Start position of most recently scanned token; set by Scan.
+	// Calling Init or Next invalidates the position (Line == 0).
+	// The Filename field is always left untouched by the Scanner.
+	// If an error is reported (via Error) and Position is invalid,
+	// the scanner is not inside a token. Call Pos to obtain an error
+	// position in that case, or to obtain the position immediately
+	// after the most recently scanned token.
+	Position
+}
+
+// Init initializes a Scanner with a new source and returns s.
+// Error is set to nil, ErrorCount is set to 0, Mode is set to GoTokens,
+// and Whitespace is set to GoWhitespace.
+func (s *Scanner) Init(src io.Reader) *Scanner {
+	s.src = src
+
+	// initialize source buffer
+	// (the first call to next() will fill it by calling src.Read)
+	s.srcBuf[0] = utf8.RuneSelf // sentinel
+	s.srcPos = 0
+	s.srcEnd = 0
+
+	// initialize source position
+	s.srcBufOffset = 0
+	s.line = 1
+	s.column = 0
+	s.lastLineLen = 0
+	s.lastCharLen = 0
+
+	// initialize token text buffer
+	// (required for first call to next()).
+	s.tokPos = -1
+
+	// initialize one character look-ahead
+	s.ch = -2 // no char read yet, not EOF
+
+	// initialize public fields
+	s.Error = nil
+	s.ErrorCount = 0
+	s.Mode = GoTokens
+	s.Whitespace = GoWhitespace
+	s.Line = 0 // invalidate token position
+
+	return s
+}
+
+// next reads and returns the next Unicode character. It is designed such
+// that only a minimal amount of work needs to be done in the common ASCII
+// case (one test to check for both ASCII and end-of-buffer, and one test
+// to check for newlines).
+func (s *Scanner) next() rune {
+	ch, width := rune(s.srcBuf[s.srcPos]), 1
+
+	if ch >= utf8.RuneSelf {
+		// uncommon case: not ASCII or not enough bytes
+		for s.srcPos+utf8.UTFMax > s.srcEnd && !utf8.FullRune(s.srcBuf[s.srcPos:s.srcEnd]) {
+			// not enough bytes: read some more, but first
+			// save away token text if any
+			if s.tokPos >= 0 {
+				s.tokBuf.Write(s.srcBuf[s.tokPos:s.srcPos])
+				s.tokPos = 0
+				// s.tokEnd is set by Scan()
+			}
+			// move unread bytes to beginning of buffer
+			copy(s.srcBuf[0:], s.srcBuf[s.srcPos:s.srcEnd])
+			s.srcBufOffset += s.srcPos
+			// read more bytes
+			// (an io.Reader must return io.EOF when it reaches
+			// the end of what it is reading - simply returning
+			// n == 0 will make this loop retry forever; but the
+			// error is in the reader implementation in that case)
+			i := s.srcEnd - s.srcPos
+			n, err := s.src.Read(s.srcBuf[i:bufLen])
+			s.srcPos = 0
+			s.srcEnd = i + n
+			s.srcBuf[s.srcEnd] = utf8.RuneSelf // sentinel
+			if err != nil {
+				if err != io.EOF {
+					s.error(err.Error())
+				}
+				if s.srcEnd == 0 {
+					if s.lastCharLen > 0 {
+						// previous character was not EOF
+						s.column++
+					}
+					s.lastCharLen = 0
+					return EOF
+				}
+				// If err == EOF, we won't be getting more
+				// bytes; break to avoid infinite loop. If
+				// err is something else, we don't know if
+				// we can get more bytes; thus also break.
+				break
+			}
+		}
+		// at least one byte
+		ch = rune(s.srcBuf[s.srcPos])
+		if ch >= utf8.RuneSelf {
+			// uncommon case: not ASCII
+			ch, width = utf8.DecodeRune(s.srcBuf[s.srcPos:s.srcEnd])
+			if ch == utf8.RuneError && width == 1 {
+				// advance for correct error position
+				s.srcPos += width
+				s.lastCharLen = width
+				s.column++
+				s.error("invalid UTF-8 encoding")
+				return ch
+			}
+		}
+	}
+
+	// advance
+	s.srcPos += width
+	s.lastCharLen = width
+	s.column++
+
+	// special situations
+	switch ch {
+	case 0:
+		// for compatibility with other tools
+		s.error("invalid character NUL")
+	case '\n':
+		s.line++
+		s.lastLineLen = s.column
+		s.column = 0
+	}
+
+	return ch
+}
+
+// Next reads and returns the next Unicode character.
+// It returns EOF at the end of the source. It reports
+// a read error by calling s.Error, if not nil; otherwise
+// it prints an error message to os.Stderr. Next does not
+// update the Scanner's Position field; use Pos() to
+// get the current position.
+func (s *Scanner) Next() rune {
+	s.tokPos = -1 // don't collect token text
+	s.Line = 0    // invalidate token position
+	ch := s.Peek()
+	if ch != EOF {
+		s.ch = s.next()
+	}
+	return ch
+}
+
+// Peek returns the next Unicode character in the source without advancing
+// the scanner. It returns EOF if the scanner's position is at the last
+// character of the source.
+func (s *Scanner) Peek() rune {
+	if s.ch == -2 {
+		// this code is only run for the very first character
+		s.ch = s.next()
+		if s.ch == '\uFEFF' {
+			s.ch = s.next() // ignore BOM
+		}
+	}
+	return s.ch
+}
+
+func (s *Scanner) error(msg string) {
+	s.tokEnd = s.srcPos - s.lastCharLen // make sure token text is terminated
+	s.ErrorCount++
+	if s.Error != nil {
+		s.Error(s, msg)
+		return
+	}
+	pos := s.Position
+	if !pos.IsValid() {
+		pos = s.Pos()
+	}
+	fmt.Fprintf(os.Stderr, "%s: %s\n", pos, msg)
+}
+
+func (s *Scanner) errorf(format string, args ...interface{}) {
+	s.error(fmt.Sprintf(format, args...))
+}
+
+func (s *Scanner) isIdentRune(ch rune, i int) bool {
+	if s.IsIdentRune != nil {
+		return s.IsIdentRune(ch, i)
+	}
+	return ch == '_' || unicode.IsLetter(ch) || unicode.IsDigit(ch) && i > 0
+}
+
+func (s *Scanner) scanIdentifier() rune {
+	// we know the zero'th rune is OK; start scanning at the next one
+	ch := s.next()
+	for i := 1; s.isIdentRune(ch, i); i++ {
+		ch = s.next()
+	}
+	return ch
+}
+
+func lower(ch rune) rune     { return ('a' - 'A') | ch } // returns lower-case ch iff ch is ASCII letter
+func isDecimal(ch rune) bool { return '0' <= ch && ch <= '9' }
+func isHex(ch rune) bool     { return '0' <= ch && ch <= '9' || 'a' <= lower(ch) && lower(ch) <= 'f' }
+
+// digits accepts the sequence { digit | '_' } starting with ch0.
+// If base <= 10, digits accepts any decimal digit but records
+// the first invalid digit >= base in *invalid if *invalid == 0.
+// digits returns the first rune that is not part of the sequence
+// anymore, and a bitset describing whether the sequence contained
+// digits (bit 0 is set), or separators '_' (bit 1 is set).
+func (s *Scanner) digits(ch0 rune, base int, invalid *rune) (ch rune, digsep int) {
+	ch = ch0
+	if base <= 10 {
+		max := rune('0' + base)
+		for isDecimal(ch) || ch == '_' {
+			ds := 1
+			if ch == '_' {
+				ds = 2
+			} else if ch >= max && *invalid == 0 {
+				*invalid = ch
+			}
+			digsep |= ds
+			ch = s.next()
+		}
+	} else {
+		for isHex(ch) || ch == '_' {
+			ds := 1
+			if ch == '_' {
+				ds = 2
+			}
+			digsep |= ds
+			ch = s.next()
+		}
+	}
+	return
+}
+
+func (s *Scanner) scanNumber(ch rune, seenDot bool) (rune, rune) {
+	base := 10         // number base
+	prefix := rune(0)  // one of 0 (decimal), '0' (0-octal), 'x', 'o', or 'b'
+	digsep := 0        // bit 0: digit present, bit 1: '_' present
+	invalid := rune(0) // invalid digit in literal, or 0
+
+	// integer part
+	var tok rune
+	var ds int
+	if !seenDot {
+		tok = Int
+		if ch == '0' {
+			ch = s.next()
+			switch lower(ch) {
+			case 'x':
+				ch = s.next()
+				base, prefix = 16, 'x'
+			case 'o':
+				ch = s.next()
+				base, prefix = 8, 'o'
+			case 'b':
+				ch = s.next()
+				base, prefix = 2, 'b'
+			default:
+				base, prefix = 8, '0'
+				digsep = 1 // leading 0
+			}
+		}
+		ch, ds = s.digits(ch, base, &invalid)
+		digsep |= ds
+		if ch == '.' && s.Mode&ScanFloats != 0 {
+			ch = s.next()
+			seenDot = true
+		}
+	}
+
+	// fractional part
+	if seenDot {
+		tok = Float
+		if prefix == 'o' || prefix == 'b' {
+			s.error("invalid radix point in " + litname(prefix))
+		}
+		ch, ds = s.digits(ch, base, &invalid)
+		digsep |= ds
+	}
+
+	if digsep&1 == 0 {
+		s.error(litname(prefix) + " has no digits")
+	}
+
+	// exponent
+	if e := lower(ch); (e == 'e' || e == 'p') && s.Mode&ScanFloats != 0 {
+		switch {
+		case e == 'e' && prefix != 0 && prefix != '0':
+			s.errorf("%q exponent requires decimal mantissa", ch)
+		case e == 'p' && prefix != 'x':
+			s.errorf("%q exponent requires hexadecimal mantissa", ch)
+		}
+		ch = s.next()
+		tok = Float
+		if ch == '+' || ch == '-' {
+			ch = s.next()
+		}
+		ch, ds = s.digits(ch, 10, nil)
+		digsep |= ds
+		if ds&1 == 0 {
+			s.error("exponent has no digits")
+		}
+	} else if prefix == 'x' && tok == Float {
+		s.error("hexadecimal mantissa requires a 'p' exponent")
+	}
+
+	if tok == Int && invalid != 0 {
+		s.errorf("invalid digit %q in %s", invalid, litname(prefix))
+	}
+
+	if digsep&2 != 0 {
+		s.tokEnd = s.srcPos - s.lastCharLen // make sure token text is terminated
+		if i := invalidSep(s.TokenText()); i >= 0 {
+			s.error("'_' must separate successive digits")
+		}
+	}
+
+	return tok, ch
+}
+
+func litname(prefix rune) string {
+	switch prefix {
+	default:
+		return "decimal literal"
+	case 'x':
+		return "hexadecimal literal"
+	case 'o', '0':
+		return "octal literal"
+	case 'b':
+		return "binary literal"
+	}
+}
+
+// invalidSep returns the index of the first invalid separator in x, or -1.
+func invalidSep(x string) int {
+	x1 := ' ' // prefix char, we only care if it's 'x'
+	d := '.'  // digit, one of '_', '0' (a digit), or '.' (anything else)
+	i := 0
+
+	// a prefix counts as a digit
+	if len(x) >= 2 && x[0] == '0' {
+		x1 = lower(rune(x[1]))
+		if x1 == 'x' || x1 == 'o' || x1 == 'b' {
+			d = '0'
+			i = 2
+		}
+	}
+
+	// mantissa and exponent
+	for ; i < len(x); i++ {
+		p := d // previous digit
+		d = rune(x[i])
+		switch {
+		case d == '_':
+			if p != '0' {
+				return i
+			}
+		case isDecimal(d) || x1 == 'x' && isHex(d):
+			d = '0'
+		default:
+			if p == '_' {
+				return i - 1
+			}
+			d = '.'
+		}
+	}
+	if d == '_' {
+		return len(x) - 1
+	}
+
+	return -1
+}
+
+func digitVal(ch rune) int {
+	switch {
+	case '0' <= ch && ch <= '9':
+		return int(ch - '0')
+	case 'a' <= lower(ch) && lower(ch) <= 'f':
+		return int(lower(ch) - 'a' + 10)
+	}
+	return 16 // larger than any legal digit val
+}
+
+func (s *Scanner) scanDigits(ch rune, base, n int) rune {
+	for n > 0 && digitVal(ch) < base {
+		ch = s.next()
+		n--
+	}
+	if n > 0 {
+		s.error("invalid char escape")
+	}
+	return ch
+}
+
+func (s *Scanner) scanEscape(quote rune) rune {
+	ch := s.next() // read character after '/'
+	switch ch {
+	case 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', quote:
+		// nothing to do
+		ch = s.next()
+	case '0', '1', '2', '3', '4', '5', '6', '7':
+		ch = s.scanDigits(ch, 8, 3)
+	case 'x':
+		ch = s.scanDigits(s.next(), 16, 2)
+	case 'u':
+		ch = s.scanDigits(s.next(), 16, 4)
+	case 'U':
+		ch = s.scanDigits(s.next(), 16, 8)
+	default:
+		s.error("invalid char escape")
+	}
+	return ch
+}
+
+func (s *Scanner) scanString(quote rune) (n int) {
+	ch := s.next() // read character after quote
+	for ch != quote {
+		if ch == '\n' || ch < 0 {
+			s.error("literal not terminated")
+			return
+		}
+		if ch == '\\' {
+			ch = s.scanEscape(quote)
+		} else {
+			ch = s.next()
+		}
+		n++
+	}
+	return
+}
+
+func (s *Scanner) scanRawString() {
+	ch := s.next() // read character after '`'
+	for ch != '`' {
+		if ch < 0 {
+			s.error("literal not terminated")
+			return
+		}
+		ch = s.next()
+	}
+}
+
+func (s *Scanner) scanChar() {
+	if s.scanString('\'') != 1 {
+		s.error("invalid char literal")
+	}
+}
+
+func (s *Scanner) scanComment(ch rune) rune {
+	// ch == '/' || ch == '*'
+	if ch == '/' {
+		// line comment
+		ch = s.next() // read character after "//"
+		for ch != '\n' && ch >= 0 {
+			ch = s.next()
+		}
+		return ch
+	}
+
+	// general comment
+	ch = s.next() // read character after "/*"
+	for {
+		if ch < 0 {
+			s.error("comment not terminated")
+			break
+		}
+		ch0 := ch
+		ch = s.next()
+		if ch0 == '*' && ch == '/' {
+			ch = s.next()
+			break
+		}
+	}
+	return ch
+}
+
+// Scan reads the next token or Unicode character from source and returns it.
+// It only recognizes tokens t for which the respective Mode bit (1<<-t) is set.
+// It returns EOF at the end of the source. It reports scanner errors (read and
+// token errors) by calling s.Error, if not nil; otherwise it prints an error
+// message to os.Stderr.
+func (s *Scanner) Scan() rune {
+	ch := s.Peek()
+
+	// reset token text position
+	s.tokPos = -1
+	s.Line = 0
+
+redo:
+	// skip white space
+	for s.Whitespace&(1< 0 {
+		// common case: last character was not a '\n'
+		s.Line = s.line
+		s.Column = s.column
+	} else {
+		// last character was a '\n'
+		// (we cannot be at the beginning of the source
+		// since we have called next() at least once)
+		s.Line = s.line - 1
+		s.Column = s.lastLineLen
+	}
+
+	// determine token value
+	tok := ch
+	switch {
+	case s.isIdentRune(ch, 0):
+		if s.Mode&ScanIdents != 0 {
+			tok = Ident
+			ch = s.scanIdentifier()
+		} else {
+			ch = s.next()
+		}
+	case isDecimal(ch):
+		if s.Mode&(ScanInts|ScanFloats) != 0 {
+			tok, ch = s.scanNumber(ch, false)
+		} else {
+			ch = s.next()
+		}
+	default:
+		switch ch {
+		case EOF:
+			break
+		case '"':
+			if s.Mode&ScanStrings != 0 {
+				s.scanString('"')
+				tok = String
+			}
+			ch = s.next()
+		case '\'':
+			// This is the difference to golang's text/scanner package, we handle ' as a string and not as a char
+			if s.Mode&ScanStrings != 0 {
+				s.scanString('\'')
+				tok = String
+			}
+			ch = s.next()
+		case '.':
+			ch = s.next()
+			if isDecimal(ch) && s.Mode&ScanFloats != 0 {
+				tok, ch = s.scanNumber(ch, true)
+			}
+		case '/':
+			ch = s.next()
+			if (ch == '/' || ch == '*') && s.Mode&ScanComments != 0 {
+				if s.Mode&SkipComments != 0 {
+					s.tokPos = -1 // don't collect token text
+					ch = s.scanComment(ch)
+					goto redo
+				}
+				ch = s.scanComment(ch)
+				tok = Comment
+			}
+		case '`':
+			if s.Mode&ScanRawStrings != 0 {
+				s.scanRawString()
+				tok = RawString
+			}
+			ch = s.next()
+		default:
+			ch = s.next()
+		}
+	}
+
+	// end of token text
+	s.tokEnd = s.srcPos - s.lastCharLen
+
+	s.ch = ch
+	return tok
+}
+
+// Pos returns the position of the character immediately after
+// the character or token returned by the last call to Next or Scan.
+// Use the Scanner's Position field for the start position of the most
+// recently scanned token.
+func (s *Scanner) Pos() (pos Position) {
+	pos.Filename = s.Filename
+	pos.Offset = s.srcBufOffset + s.srcPos - s.lastCharLen
+	switch {
+	case s.column > 0:
+		// common case: last character was not a '\n'
+		pos.Line = s.line
+		pos.Column = s.column
+	case s.lastLineLen > 0:
+		// last character was a '\n'
+		pos.Line = s.line - 1
+		pos.Column = s.lastLineLen
+	default:
+		// at the beginning of the source
+		pos.Line = 1
+		pos.Column = 1
+	}
+	return
+}
+
+// TokenText returns the string corresponding to the most recently scanned token.
+// Valid after calling Scan and in calls of Scanner.Error.
+func (s *Scanner) TokenText() string {
+	if s.tokPos < 0 {
+		// no token text
+		return ""
+	}
+
+	if s.tokEnd < s.tokPos {
+		// if EOF was reached, s.tokEnd is set to -1 (s.srcPos == 0)
+		s.tokEnd = s.tokPos
+	}
+	// s.tokEnd >= s.tokPos
+
+	if s.tokBuf.Len() == 0 {
+		// common case: the entire token text is still in srcBuf
+		return string(s.srcBuf[s.tokPos:s.tokEnd])
+	}
+
+	// part of the token text was saved in tokBuf: save the rest in
+	// tokBuf as well and return its content
+	s.tokBuf.Write(s.srcBuf[s.tokPos:s.tokEnd])
+	s.tokPos = s.tokEnd // ensure idempotency of TokenText() call
+	return s.tokBuf.String()
+}
diff --git a/pkg/utils/versions/latest_version_parse.go b/pkg/utils/versions/latest_version_parse.go
index ab2e53715..407bf6a0a 100644
--- a/pkg/utils/versions/latest_version_parse.go
+++ b/pkg/utils/versions/latest_version_parse.go
@@ -2,10 +2,10 @@ package versions
 
 import (
 	"fmt"
+	scanner "github.com/kluctl/kluctl/pkg/utils/python_scanner"
 	log "github.com/sirupsen/logrus"
 	"strconv"
 	"strings"
-	"text/scanner"
 )
 
 type tokenAndText struct {

From 70cc610adccc8ce81a8aedf634e186ab99eda7a1 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Mon, 4 Apr 2022 12:15:07 +0200
Subject: [PATCH 0603/2916] tests: Add tests for ParseLatestVersion

---
 pkg/utils/versions/latest_version.go          | 21 ++++++++-
 pkg/utils/versions/latest_version_parse.go    |  2 +-
 .../versions/latest_version_parse_test.go     | 47 +++++++++++++++++++
 3 files changed, 67 insertions(+), 3 deletions(-)
 create mode 100644 pkg/utils/versions/latest_version_parse_test.go

diff --git a/pkg/utils/versions/latest_version.go b/pkg/utils/versions/latest_version.go
index 7fb73a228..654928f6c 100644
--- a/pkg/utils/versions/latest_version.go
+++ b/pkg/utils/versions/latest_version.go
@@ -3,6 +3,7 @@ package versions
 import (
 	"fmt"
 	"github.com/kluctl/kluctl/pkg/utils"
+	"log"
 	"regexp"
 	"strconv"
 )
@@ -39,6 +40,14 @@ func NewRegexVersionFilter(pattern string) (LatestVersionFilter, error) {
 	}, nil
 }
 
+func NewRegexVersionFilterMust(pattern string) LatestVersionFilter {
+	ret, err := NewRegexVersionFilter(pattern)
+	if err != nil {
+		log.Fatal(err)
+	}
+	return ret
+}
+
 func (f *regexVersionFilter) Match(version string) bool {
 	return f.pattern.MatchString(version)
 }
@@ -102,6 +111,14 @@ func NewPrefixVersionFilter(prefix string, suffix LatestVersionFilter) (LatestVe
 	}, nil
 }
 
+func NewPrefixVersionFilterMust(prefix string, suffix LatestVersionFilter) LatestVersionFilter {
+	ret, err := NewPrefixVersionFilter(prefix, suffix)
+	if err != nil {
+		log.Fatal(err)
+	}
+	return ret
+}
+
 func (f *prefixVersionFilter) Match(version string) bool {
 	groups := f.pattern.FindStringSubmatch(version)
 	if groups == nil {
@@ -134,9 +151,9 @@ type numberVersionFilter struct {
 	LatestVersionFilter
 }
 
-func NewNumberVersionFilter() (LatestVersionFilter, error) {
+func NewNumberVersionFilter() LatestVersionFilter {
 	f, _ := NewRegexVersionFilter("[0-9]+")
-	return &numberVersionFilter{f}, nil
+	return &numberVersionFilter{f}
 }
 
 func (f *numberVersionFilter) String() string {
diff --git a/pkg/utils/versions/latest_version_parse.go b/pkg/utils/versions/latest_version_parse.go
index 407bf6a0a..220bb08f0 100644
--- a/pkg/utils/versions/latest_version_parse.go
+++ b/pkg/utils/versions/latest_version_parse.go
@@ -160,7 +160,7 @@ func parsePrefixFilter(p *preparsed) (LatestVersionFilter, error) {
 }
 
 func parseNumberFilter(p *preparsed) (LatestVersionFilter, error) {
-	return NewNumberVersionFilter()
+	return NewNumberVersionFilter(), nil
 }
 
 type arg struct {
diff --git a/pkg/utils/versions/latest_version_parse_test.go b/pkg/utils/versions/latest_version_parse_test.go
new file mode 100644
index 000000000..9e5d5e2a3
--- /dev/null
+++ b/pkg/utils/versions/latest_version_parse_test.go
@@ -0,0 +1,47 @@
+package versions
+
+import (
+	"fmt"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestParse(t *testing.T) {
+
+	type testCase struct {
+		s string
+		expectedFilter LatestVersionFilter
+		expectedErr error
+	}
+
+	testCases := []testCase{
+		{s: "regex('a*')", expectedFilter: NewRegexVersionFilterMust("a*")},
+		{s: "regex(\"a*\")", expectedFilter: NewRegexVersionFilterMust("a*")},
+		{s: "regex(a*\")", expectedErr: fmt.Errorf("unexpected token 42, expected ')' or ','")},
+		{s: "semver()", expectedFilter: NewLooseSemVerVersionFilter(false)},
+		{s: "semver(allow_no_nums)", expectedErr: fmt.Errorf("invalid value for arg allow_no_nums, must be a bool")},
+		{s: "semver(allow_no_nums=false)", expectedFilter: NewLooseSemVerVersionFilter(false)},
+		{s: "semver(allow_no_nums=true)", expectedFilter: NewLooseSemVerVersionFilter(true)},
+		{s: "prefix('')", expectedFilter: NewPrefixVersionFilterMust("", NewLooseSemVerVersionFilter(false))},
+		{s: "prefix('a')", expectedFilter: NewPrefixVersionFilterMust("a", NewLooseSemVerVersionFilter(false))},
+		{s: "prefix('a', 'b')", expectedErr: fmt.Errorf("unexpected argument type for , expected -2, got -6")},
+		{s: "prefix('a', regex('a*'))", expectedFilter: NewPrefixVersionFilterMust("a", NewRegexVersionFilterMust("a*"))},
+		{s: "prefix('a', suffi=regex('a*'))", expectedErr: fmt.Errorf("unkown arg suffi")},
+		{s: "prefix('a', suffix=regex('a*'))", expectedFilter: NewPrefixVersionFilterMust("a", NewRegexVersionFilterMust("a*"))},
+		{s: "number()", expectedFilter: NewNumberVersionFilter()},
+		{s: "number(a=1)", expectedErr: fmt.Errorf("unexpected token -2, expected (")},
+	}
+
+	for _, tc := range testCases {
+		tc := tc
+		t.Run(tc.s, func(t *testing.T) {
+			f, err := ParseLatestVersion(tc.s)
+			assert.Equal(t, tc.expectedFilter, f)
+			if tc.expectedErr != nil {
+				assert.Error(t, err, tc.expectedErr)
+			} else if err != nil {
+				assert.Fail(t, "unexpected error", err)
+			}
+		})
+	}
+}

From 3ed4cb7d0a5c1c115b4617f74e128b26e8316f2b Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Mon, 4 Apr 2022 12:50:09 +0200
Subject: [PATCH 0604/2916] fix: Prefer agent keys and defer passphrase prompt
 until the key is tried

This prevents unnecessary prompts for protected keys when they were already
added to the agent.
---
 pkg/git/auth/ssh_auth_provider.go | 136 ++++++++++++++++++++++++------
 1 file changed, 108 insertions(+), 28 deletions(-)

diff --git a/pkg/git/auth/ssh_auth_provider.go b/pkg/git/auth/ssh_auth_provider.go
index d5a04b8ae..f4891d4a7 100644
--- a/pkg/git/auth/ssh_auth_provider.go
+++ b/pkg/git/auth/ssh_auth_provider.go
@@ -9,6 +9,7 @@ import (
 	log "github.com/sirupsen/logrus"
 	sshagent "github.com/xanzy/ssh-agent"
 	"golang.org/x/crypto/ssh"
+	"io"
 	"io/ioutil"
 	"os"
 	"os/user"
@@ -103,13 +104,116 @@ func (a *GitSshAuthProvider) BuildAuth(gitUrl git_url.GitUrl) transport.AuthMeth
 		user:     gitUrl.User.Username(),
 	}
 
+	// Try agent identities first. They might be unencrypted already, making passphrase prompts unnecessary
+	auth.addAgentIdentities(gitUrl)
 	auth.addDefaultIdentity(gitUrl)
 	auth.addConfigIdentities(gitUrl)
-	auth.addAgentIdentities(gitUrl)
 
 	return auth
 }
 
+// we defer asking for passphrase so that we don't unnecessarily ask for passphrases for keys that are already provided
+// by ssh-agent
+type deferredPassphraseKey struct {
+	a *GitSshAuthProvider
+	path string
+	err error
+	parsed ssh.Signer
+	mutex sync.Mutex
+}
+
+func (k *deferredPassphraseKey) getPassphrase() ([]byte, error) {
+	k.a.passphrasesMutex.Lock()
+	defer k.a.passphrasesMutex.Unlock()
+
+	if k.a.passphrases == nil {
+		k.a.passphrases = map[string][]byte{}
+	}
+
+	passphrase, ok := k.a.passphrases[k.path]
+	if ok {
+		return passphrase, nil
+	}
+
+	passphraseStr, err := utils.AskForPassword(fmt.Sprintf("Enter passphrase for key '%s'", k.path))
+	if err != nil {
+		k.a.passphrases[k.path] = nil
+		return nil, err
+	}
+	k.a.passphrases[k.path] = passphrase
+	return []byte(passphraseStr), nil
+}
+
+func (k *deferredPassphraseKey) parse() {
+	passphrase, err := k.getPassphrase()
+	if err != nil {
+		k.err = err
+		log.Warningf("Failed to parse key %s: %v", k.path, err)
+		return
+	}
+
+	pemBytes, err := ioutil.ReadFile(k.path)
+	if err != nil {
+		k.err = err
+		log.Warningf("Failed to parse key %s: %v", k.path, err)
+		return
+	}
+
+	s, err := ssh.ParsePrivateKeyWithPassphrase(pemBytes, passphrase)
+	if err != nil {
+		k.err = err
+		log.Warningf("Failed to parse key %s: %v", k.path, err)
+		return
+	}
+	k.parsed = s
+}
+
+type dummyPublicKey struct {
+}
+
+func (k *dummyPublicKey) Type() string {
+	return "dummy"
+}
+
+// Marshal returns the serialized key data in SSH wire format,
+// with the name prefix. To unmarshal the returned data, use
+// the ParsePublicKey function.
+func (k *dummyPublicKey) Marshal() []byte {
+	return []byte{}
+}
+
+// Verify that sig is a signature on the given data using this
+// key. This function will hash the data appropriately first.
+func (k *dummyPublicKey) Verify(data []byte, sig *ssh.Signature) error {
+	return fmt.Errorf("this is a dummy key")
+}
+
+func (k *deferredPassphraseKey) PublicKey() ssh.PublicKey {
+	k.mutex.Lock()
+	defer k.mutex.Unlock()
+
+	if k.parsed == nil && k.err == nil {
+		k.parse()
+	}
+	if k.err != nil {
+		return &dummyPublicKey{}
+	}
+	return k.parsed.PublicKey()
+}
+
+func (k *deferredPassphraseKey) Sign(rand io.Reader, data []byte) (*ssh.Signature, error) {
+	k.mutex.Lock()
+	defer k.mutex.Unlock()
+
+	if k.parsed == nil && k.err == nil {
+		k.parse()
+	}
+	if k.err != nil {
+		return nil, k.err
+	}
+	return k.parsed.Sign(rand, data)
+}
+
 func (a *GitSshAuthProvider) readKey(path string) (ssh.Signer, error) {
 	pemBytes, err := ioutil.ReadFile(path)
 	if err != nil {
@@ -121,36 +225,12 @@ func (a *GitSshAuthProvider) readKey(path string) (ssh.Signer, error) {
 		}
 
 		if _, ok := err.(*ssh.PassphraseMissingError); ok {
-			passphrase := a.getPassphrase(path)
-			if passphrase == nil {
-				return nil, err
+			signer = &deferredPassphraseKey{
+				a:      a,
+				path:   path,
 			}
-			return ssh.ParsePrivateKeyWithPassphrase(pemBytes, passphrase)
 		}
 
 		return signer, nil
 	}
 }
-
-func (a *GitSshAuthProvider) getPassphrase(path string) []byte {
-	a.passphrasesMutex.Lock()
-	defer a.passphrasesMutex.Unlock()
-
-	if a.passphrases == nil {
-		a.passphrases = map[string][]byte{}
-	}
-
-	passphrase, ok := a.passphrases[path]
-	if ok {
-		return passphrase
-	}
-
-	passphraseStr, err := utils.AskForPassword(fmt.Sprintf("Enter passphrase for key '%s'", path))
-	if err != nil {
-		log.Warning(err)
-		a.passphrases[path] = nil
-		return nil
-	}
-	a.passphrases[path] = passphrase
-	return []byte(passphraseStr)
-}

From e230d4547f2b7c10aba5a92cc6b45d677afada7f Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Mon, 4 Apr 2022 13:22:35 +0200
Subject: [PATCH 0605/2916] fix: Better error message when auth for registries
 fails

---
 pkg/deployment/deployment_collection.go |  2 +-
 pkg/registries/registries.go            | 25 +++++++++++++++++++++++--
 2 files changed, 24 insertions(+), 3 deletions(-)

diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go
index 6268920dc..d32fa4c46 100644
--- a/pkg/deployment/deployment_collection.go
+++ b/pkg/deployment/deployment_collection.go
@@ -178,7 +178,7 @@ func (c *DeploymentCollection) buildKustomizeObjects(k *k8s.K8sCluster) error {
 					err := d.postprocessAndLoadObjects(k, c.Images)
 					if err != nil {
 						mutex.Lock()
-						errs = append(errs, fmt.Errorf("postprocessing kustomize objects for %s failed. %w", *d.dir, err))
+						errs = append(errs, fmt.Errorf("postprocessing kustomize objects for %s failed: %w", *d.dir, err))
 						mutex.Unlock()
 					}
 					wg.Done()
diff --git a/pkg/registries/registries.go b/pkg/registries/registries.go
index 2f6fa6d94..18b1f2467 100644
--- a/pkg/registries/registries.go
+++ b/pkg/registries/registries.go
@@ -9,12 +9,14 @@ import (
 	"github.com/google/go-containerregistry/pkg/authn"
 	"github.com/google/go-containerregistry/pkg/name"
 	"github.com/google/go-containerregistry/pkg/v1/remote"
+	"github.com/google/go-containerregistry/pkg/v1/remote/transport"
 	"github.com/kluctl/kluctl/pkg/utils"
 	"github.com/kluctl/kluctl/pkg/utils/uo"
 	log "github.com/sirupsen/logrus"
 	"io/ioutil"
 	"net/http"
 	"net/http/httputil"
+	"net/url"
 	"os"
 	"path/filepath"
 	"strconv"
@@ -23,6 +25,14 @@ import (
 	"time"
 )
 
+type noAuthRetryError struct {
+	msg string
+}
+
+func (e *noAuthRetryError) Error() string {
+	return e.msg
+}
+
 func ListImageTags(image string) ([]string, error) {
 	var nameOpts []name.Option
 	remoteOpts := []remote.Option{
@@ -40,7 +50,18 @@ func ListImageTags(image string) ([]string, error) {
 		repo, err = name.NewRepository(image, nameOpts...)
 	}
 
-	return remote.List(repo, remoteOpts...)
+	ret, err := remote.List(repo, remoteOpts...)
+	if e, ok := err.(*transport.Error); ok && (e.StatusCode == http.StatusUnauthorized || e.StatusCode == http.StatusForbidden) {
+		return nil, fmt.Errorf("failed to authenticate against image registry %s, " +
+			"please make sure that you provided credentials, e.g. via 'docker login' or via environment variables: %w", repo.Registry, err)
+	}
+	if e, ok := err.(*url.Error); ok {
+		if _, ok := e.Err.(*noAuthRetryError); ok {
+			// we explicitly ignore these errors as we assume that the original auth error is handled by another request
+			return nil, nil
+		}
+	}
+	return ret, err
 }
 
 var globalMyKeychain myKeychain
@@ -276,7 +297,7 @@ func (kc *myKeychain) RoundTrip(req *http.Request) (*http.Response, error) {
 	kc.mutex.Unlock()
 
 	if isAuthError {
-		return nil, fmt.Errorf("previous auth request for %s gave an error, we won't retry", realm)
+		return nil, &noAuthRetryError{fmt.Sprintf("previous auth request for %s gave an error, we won't retry", realm)}
 	}
 
 	if isAuthRealm {

From 39c53dd33033f7f7d501b4d4d44cd9e0fda2fbab Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Mon, 4 Apr 2022 21:43:36 +0200
Subject: [PATCH 0606/2916] refactor: Remove ShouldRemoveNamespace

---
 pkg/k8s/k8s_cluster.go | 27 ++++-----------------------
 1 file changed, 4 insertions(+), 23 deletions(-)

diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go
index 4fd950862..296060a1e 100644
--- a/pkg/k8s/k8s_cluster.go
+++ b/pkg/k8s/k8s_cluster.go
@@ -256,36 +256,17 @@ func (k *K8sCluster) IsNamespaced(gvk schema.GroupVersionKind) bool {
 	return r.Namespaced
 }
 
-func (k *K8sCluster) ShouldRemoveNamespace(ref k8s.ObjectRef) bool {
-	k.mutex.Lock()
-	defer k.mutex.Unlock()
-	r, ok := k.allResources[ref.GVK]
-	if !ok {
-		// we don't know, so don't remove the NS
-		return false
-	}
-	if ref.Namespace == "" {
-		return false
-	}
-	if r.Namespaced {
-		return false
-	}
-	return true
-}
-
 func (k *K8sCluster) RemoveNamespaceIfNeeded(o *uo.UnstructuredObject) {
 	ref := o.GetK8sRef()
-	if !k.ShouldRemoveNamespace(ref) {
-		return
+	if !k.IsNamespaced(ref.GVK) && ref.Namespace != "" {
+		o.SetK8sNamespace("")
 	}
-	o.SetK8sNamespace("")
 }
 
 func (k *K8sCluster) RemoveNamespaceFromRefIfNeeded(ref k8s.ObjectRef) k8s.ObjectRef {
-	if !k.ShouldRemoveNamespace(ref) {
-		return ref
+	if !k.IsNamespaced(ref.GVK) && ref.Namespace != "" {
+		ref.Namespace = ""
 	}
-	ref.Namespace = ""
 	return ref
 }
 

From 296db369f9da08ce35052fae53f0291a9f76f736 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Mon, 4 Apr 2022 21:47:32 +0200
Subject: [PATCH 0607/2916] fix: Set namespace to "default" when needed

---
 pkg/deployment/deployment_item.go    |  2 +-
 pkg/deployment/utils/delete_utils.go |  2 +-
 pkg/k8s/k8s_cluster.go               | 14 ++++++++++----
 3 files changed, 12 insertions(+), 6 deletions(-)

diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go
index 2f7c1e26b..3fb24d3d0 100644
--- a/pkg/deployment/deployment_item.go
+++ b/pkg/deployment/deployment_item.go
@@ -369,7 +369,7 @@ func (di *DeploymentItem) postprocessAndLoadObjects(k *k8s.K8sCluster, images *I
 
 	for _, o := range di.Objects {
 		if k != nil {
-			k.RemoveNamespaceIfNeeded(o)
+			k.FixNamespace(o)
 		}
 
 		// Set common labels/annotations
diff --git a/pkg/deployment/utils/delete_utils.go b/pkg/deployment/utils/delete_utils.go
index 01b554be0..f83cce908 100644
--- a/pkg/deployment/utils/delete_utils.go
+++ b/pkg/deployment/utils/delete_utils.go
@@ -41,7 +41,7 @@ var deleteOrder = [][]string{
 }
 
 func objectRefForExclusion(k *k8s.K8sCluster, ref k8s2.ObjectRef) k8s2.ObjectRef {
-	ref = k.RemoveNamespaceFromRefIfNeeded(ref)
+	ref = k.FixNamespaceInRef(ref)
 	ref.GVK.Version = ""
 	return ref
 }
diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go
index 296060a1e..e8a821759 100644
--- a/pkg/k8s/k8s_cluster.go
+++ b/pkg/k8s/k8s_cluster.go
@@ -256,16 +256,22 @@ func (k *K8sCluster) IsNamespaced(gvk schema.GroupVersionKind) bool {
 	return r.Namespaced
 }
 
-func (k *K8sCluster) RemoveNamespaceIfNeeded(o *uo.UnstructuredObject) {
+func (k *K8sCluster) FixNamespace(o *uo.UnstructuredObject) {
 	ref := o.GetK8sRef()
-	if !k.IsNamespaced(ref.GVK) && ref.Namespace != "" {
+	namespaced := k.IsNamespaced(ref.GVK)
+	if !namespaced && ref.Namespace != "" {
 		o.SetK8sNamespace("")
+	} else if namespaced && ref.Namespace == "" {
+		o.SetK8sNamespace("default")
 	}
 }
 
-func (k *K8sCluster) RemoveNamespaceFromRefIfNeeded(ref k8s.ObjectRef) k8s.ObjectRef {
-	if !k.IsNamespaced(ref.GVK) && ref.Namespace != "" {
+func (k *K8sCluster) FixNamespaceInRef(ref k8s.ObjectRef) k8s.ObjectRef {
+	namespaced := k.IsNamespaced(ref.GVK)
+	if !namespaced && ref.Namespace != "" {
 		ref.Namespace = ""
+	} else if namespaced && ref.Namespace == "" {
+		ref.Namespace = "default"
 	}
 	return ref
 }

From 2082f546524a5fe42a100cb25febc678779c7c68 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Tue, 5 Apr 2022 10:52:44 +0200
Subject: [PATCH 0608/2916] fix: Allow to override hook-timeout via annotation

---
 pkg/deployment/utils/apply_utils.go | 12 ++++++++----
 pkg/deployment/utils/hooks_util.go  | 16 +++++++++++++++-
 2 files changed, 23 insertions(+), 5 deletions(-)

diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go
index 15dfb0a6d..c0187c343 100644
--- a/pkg/deployment/utils/apply_utils.go
+++ b/pkg/deployment/utils/apply_utils.go
@@ -294,12 +294,16 @@ func (a *ApplyUtil) handleNewCRDs(x *uo.UnstructuredObject, err error) (bool, er
 	return false, err
 }
 
-func (a *ApplyUtil) WaitReadiness(ref k8s2.ObjectRef) bool {
+func (a *ApplyUtil) WaitReadiness(ref k8s2.ObjectRef, timeout time.Duration) bool {
 	if a.o.DryRun {
 		return true
 	}
 
-	log2 := log.WithField("ref", ref)
+	if timeout == 0 {
+		timeout = a.o.WaitObjectTimeout
+	}
+
+	log2 := log.WithField("ref", ref).WithField("timeout", timeout.String())
 	log2.Debugf("Waiting for object to get ready")
 
 	lastLogTime := time.Now()
@@ -336,7 +340,7 @@ func (a *ApplyUtil) WaitReadiness(ref k8s2.ObjectRef) bool {
 			return false
 		}
 
-		if a.o.WaitObjectTimeout != 0 && time.Now().Sub(startTime) >= a.o.WaitObjectTimeout {
+		if timeout > 0 && time.Now().Sub(startTime) >= timeout {
 			err := fmt.Errorf("timed out while waiting for object")
 			log2.Warningf(err.Error())
 			a.HandleError(ref, err)
@@ -419,7 +423,7 @@ func (a *ApplyUtil) applyDeploymentItem(d *deployment.DeploymentItem) {
 
 		waitReadiness := (d.Config.WaitReadiness != nil && *d.Config.WaitReadiness) || d.WaitReadiness || utils.ParseBoolOrFalse(o.GetK8sAnnotation("kluctl.io/wait-readiness"))
 		if !a.o.NoWait && waitReadiness {
-			a.WaitReadiness(o.GetK8sRef())
+			a.WaitReadiness(o.GetK8sRef(), 0)
 		}
 	}
 
diff --git a/pkg/deployment/utils/hooks_util.go b/pkg/deployment/utils/hooks_util.go
index fd56db75a..cb83914e9 100644
--- a/pkg/deployment/utils/hooks_util.go
+++ b/pkg/deployment/utils/hooks_util.go
@@ -10,6 +10,7 @@ import (
 	"sort"
 	"strconv"
 	"strings"
+	"time"
 )
 
 var supportedKluctlHooks = []string{
@@ -46,6 +47,7 @@ type hook struct {
 	weight         int
 	deletePolicies map[string]bool
 	wait           bool
+	timeout        time.Duration
 }
 
 func (u *HooksUtil) RunHooks(d *deployment.DeploymentItem, hooks []string) {
@@ -116,7 +118,7 @@ func (u *HooksUtil) RunHooks(d *deployment.DeploymentItem, hooks []string) {
 		if !h.wait {
 			continue
 		}
-		waitResults[ref] = u.a.WaitReadiness(ref)
+		waitResults[ref] = u.a.WaitReadiness(ref, h.timeout)
 	}
 
 	var deleteAfterObjects []*hook
@@ -227,6 +229,17 @@ func (u *HooksUtil) GetHook(o *uo.UnstructuredObject) *hook {
 		wait = true
 	}
 
+	timeoutStr := o.GetK8sAnnotation("kluctl.io/hook-timeout")
+	var timeout time.Duration
+	if timeoutStr != nil {
+		t, err := time.ParseDuration(*timeoutStr)
+		if err != nil {
+			u.a.HandleError(ref, fmt.Errorf("failed to parse duration: %w", err))
+		} else {
+			timeout = t
+		}
+	}
+
 	if len(hooks) == 0 {
 		return nil
 	}
@@ -237,6 +250,7 @@ func (u *HooksUtil) GetHook(o *uo.UnstructuredObject) *hook {
 		weight:         int(weight),
 		deletePolicies: deletePolicy,
 		wait:           wait,
+		timeout:        timeout,
 	}
 }
 

From 1fcb2052c7f93b1c54fa2a323f4b67f680c77477 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Tue, 5 Apr 2022 10:53:00 +0200
Subject: [PATCH 0609/2916] chore: Run gofmt

---
 pkg/git/auth/ssh_auth_provider.go              | 18 +++++++++---------
 pkg/registries/registries.go                   |  2 +-
 .../versions/latest_version_parse_test.go      |  4 ++--
 3 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/pkg/git/auth/ssh_auth_provider.go b/pkg/git/auth/ssh_auth_provider.go
index f4891d4a7..d195fe85c 100644
--- a/pkg/git/auth/ssh_auth_provider.go
+++ b/pkg/git/auth/ssh_auth_provider.go
@@ -18,7 +18,7 @@ import (
 )
 
 type GitSshAuthProvider struct {
-	passphrases map[string][]byte
+	passphrases      map[string][]byte
 	passphrasesMutex sync.Mutex
 }
 
@@ -100,8 +100,8 @@ func (a *GitSshAuthProvider) BuildAuth(gitUrl git_url.GitUrl) transport.AuthMeth
 
 	auth := &sshDefaultIdentityAndAgent{
 		authProvider: a,
-		hostname: gitUrl.Hostname(),
-		user:     gitUrl.User.Username(),
+		hostname:     gitUrl.Hostname(),
+		user:         gitUrl.User.Username(),
 	}
 
 	// Try agent identities first. They might be unencrypted already, making passphrase prompts unnecessary
@@ -115,11 +115,11 @@ func (a *GitSshAuthProvider) BuildAuth(gitUrl git_url.GitUrl) transport.AuthMeth
 // we defer asking for passphrase so that we don't unnecessarily ask for passphrases for keys that are already provided
 // by ssh-agent
 type deferredPassphraseKey struct {
-	a *GitSshAuthProvider
-	path string
-	err error
+	a      *GitSshAuthProvider
+	path   string
+	err    error
 	parsed ssh.Signer
-	mutex sync.Mutex
+	mutex  sync.Mutex
 }
 
 func (k *deferredPassphraseKey) getPassphrase() ([]byte, error) {
@@ -226,8 +226,8 @@ func (a *GitSshAuthProvider) readKey(path string) (ssh.Signer, error) {
 
 		if _, ok := err.(*ssh.PassphraseMissingError); ok {
 			signer = &deferredPassphraseKey{
-				a:      a,
-				path:   path,
+				a:    a,
+				path: path,
 			}
 		}
 
diff --git a/pkg/registries/registries.go b/pkg/registries/registries.go
index 18b1f2467..e886010a5 100644
--- a/pkg/registries/registries.go
+++ b/pkg/registries/registries.go
@@ -52,7 +52,7 @@ func ListImageTags(image string) ([]string, error) {
 
 	ret, err := remote.List(repo, remoteOpts...)
 	if e, ok := err.(*transport.Error); ok && (e.StatusCode == http.StatusUnauthorized || e.StatusCode == http.StatusForbidden) {
-		return nil, fmt.Errorf("failed to authenticate against image registry %s, " +
+		return nil, fmt.Errorf("failed to authenticate against image registry %s, "+
 			"please make sure that you provided credentials, e.g. via 'docker login' or via environment variables: %w", repo.Registry, err)
 	}
 	if e, ok := err.(*url.Error); ok {
diff --git a/pkg/utils/versions/latest_version_parse_test.go b/pkg/utils/versions/latest_version_parse_test.go
index 9e5d5e2a3..31ba9c483 100644
--- a/pkg/utils/versions/latest_version_parse_test.go
+++ b/pkg/utils/versions/latest_version_parse_test.go
@@ -9,9 +9,9 @@ import (
 func TestParse(t *testing.T) {
 
 	type testCase struct {
-		s string
+		s              string
 		expectedFilter LatestVersionFilter
-		expectedErr error
+		expectedErr    error
 	}
 
 	testCases := []testCase{

From d1e0659c6ec9cdeb19d23c9d5f90bc3ab682bcc2 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Tue, 5 Apr 2022 10:53:09 +0200
Subject: [PATCH 0610/2916] fix: Fix crash when COLUMNS is too small

---
 pkg/utils/prettytable.go | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/pkg/utils/prettytable.go b/pkg/utils/prettytable.go
index 2a7b2af0d..2a679196a 100644
--- a/pkg/utils/prettytable.go
+++ b/pkg/utils/prettytable.go
@@ -84,7 +84,11 @@ func (t *PrettyTable) Render(limitWidths []int) string {
 	if len(limitWidths) < cols {
 		tw := getTermWidth()
 		// last column should use all remaining space
-		widths[len(limitWidths)] = tw - widthSum - (cols-1)*3 - 4
+		tw = tw - widthSum - (cols-1)*3 - 4
+		if tw <= 0 {
+			tw = 1
+		}
+		widths[len(limitWidths)] = tw
 	}
 
 	hsep := "+-"

From 9014b8f68c0d7f021cbf364ac8889fb27a2b3f84 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Tue, 5 Apr 2022 10:56:07 +0200
Subject: [PATCH 0611/2916] fix: Better reporting of elapsed time while waiting
 for objects

---
 pkg/deployment/utils/apply_utils.go | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go
index c0187c343..d775e1efc 100644
--- a/pkg/deployment/utils/apply_utils.go
+++ b/pkg/deployment/utils/apply_utils.go
@@ -310,12 +310,14 @@ func (a *ApplyUtil) WaitReadiness(ref k8s2.ObjectRef, timeout time.Duration) boo
 	didLog := false
 	startTime := time.Now()
 	for true {
+		log3 := log2.WithField("elapsed", (time.Second * time.Duration(time.Now().Sub(startTime).Seconds())).String())
+
 		o, apiWarnings, err := a.k.GetSingleObject(ref)
 		a.handleApiWarnings(ref, apiWarnings)
 		if err != nil {
 			if errors.IsNotFound(err) {
 				if didLog {
-					log2.Warningf("Cancelled waiting for object as it disappeared while waiting for it")
+					log3.Warningf("Cancelled waiting for object as it disappeared while waiting for it")
 				}
 				a.HandleError(ref, fmt.Errorf("object disappeared while waiting for it to become ready"))
 				return false
@@ -326,13 +328,13 @@ func (a *ApplyUtil) WaitReadiness(ref k8s2.ObjectRef, timeout time.Duration) boo
 		v := validation.ValidateObject(a.k, o, false)
 		if v.Ready {
 			if didLog {
-				log2.Infof("Finished waiting for object")
+				log3.Infof("Finished waiting for object")
 			}
 			return true
 		}
 		if len(v.Errors) != 0 {
 			if didLog {
-				log2.Warningf("Cancelled waiting for object due to errors")
+				log3.Warningf("Cancelled waiting for object due to errors")
 			}
 			for _, e := range v.Errors {
 				a.HandleError(ref, fmt.Errorf(e.Error))
@@ -342,17 +344,17 @@ func (a *ApplyUtil) WaitReadiness(ref k8s2.ObjectRef, timeout time.Duration) boo
 
 		if timeout > 0 && time.Now().Sub(startTime) >= timeout {
 			err := fmt.Errorf("timed out while waiting for object")
-			log2.Warningf(err.Error())
+			log3.Warningf(err.Error())
 			a.HandleError(ref, err)
 			return false
 		}
 
 		if !didLog {
-			log2.Infof("Waiting for object to get ready...")
+			log3.Infof("Waiting for object to get ready...")
 			didLog = true
 			lastLogTime = time.Now()
 		} else if didLog && time.Now().Sub(lastLogTime) >= 10*time.Second {
-			log2.Infof("Still waiting for object to get ready (%s)...", time.Now().Sub(startTime).String())
+			log3.Infof("Still waiting for object to get ready...")
 			lastLogTime = time.Now()
 		}
 

From 41fd97b7d552190a9dafdfba4b196f006d03b316 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Tue, 5 Apr 2022 11:58:37 +0200
Subject: [PATCH 0612/2916] fix: Fix validation of Pods to report readiness
 when they are completed

---
 pkg/validation/validation.go | 24 +++++++++++++++++++++---
 1 file changed, 21 insertions(+), 3 deletions(-)

diff --git a/pkg/validation/validation.go b/pkg/validation/validation.go
index 41e08c323..eae6adf76 100644
--- a/pkg/validation/validation.go
+++ b/pkg/validation/validation.go
@@ -95,6 +95,9 @@ func ValidateObject(k *k8s.K8sCluster, o *uo.UnstructuredObject, notReadyIsError
 	}
 
 	reactToError := func(err error, er errorReaction, doRaise bool) {
+		if err == nil {
+			return
+		}
 		if er == reactError {
 			addError(err.Error())
 		} else if er == reactWarning {
@@ -239,10 +242,25 @@ func ValidateObject(k *k8s.K8sCluster, o *uo.UnstructuredObject, notReadyIsError
 
 	switch o.GetK8sGVK().GroupKind() {
 	case schema.GroupKind{Group: "", Kind: "Pod"}:
-		c := getCondition("Ready", reactIgnore, false)
-		if c.status != "True" {
-			addNotReady(c.getMessage("Not ready"))
+		containerStatuses, _, err := status.GetNestedObjectList("containerStatuses")
+		reactToError(err, reactError, true)
+		for _, cs := range containerStatuses {
+			containerName, _, err := cs.GetNestedString("name")
+			reactToError(err, reactError, true)
+			terminateReason, ok, err := cs.GetNestedString("state", "terminated", "reason")
+			reactToError(err, reactError, true)
+			if ok && terminateReason == "Error" {
+				addError(fmt.Sprintf("container %s exited with error", containerName))
+			}
+		}
+
+		rc := getCondition("Ready", reactNotReady, false)
+		if rc.status == "False" && rc.reason == "PodCompleted" {
+			// pod exited
+			return
 		}
+		// pod is still running, so it is not ready
+		addNotReady("Not ready")
 	case schema.GroupKind{Group: "batch", Kind: "Job"}:
 		c := getCondition("Failed", reactIgnore, false)
 		if c.status == "True" {

From 003d279008adbd5a7967416313c5069675805742 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Tue, 5 Apr 2022 14:11:46 +0200
Subject: [PATCH 0613/2916] tests: Rely on externally built kluctl executable
 in e2e tests

Stop using the tests wrapper hack.
---
 .github/workflows/build-and-release.yml |  1 +
 e2e/cmd_wrapper_test.go                 | 59 ---------------------
 e2e/cmd_wrapper_util.go                 | 69 -------------------------
 e2e/project.go                          | 19 ++++++-
 e2e/{utils_test.go => utils.go}         | 33 ++++++++++++
 5 files changed, 52 insertions(+), 129 deletions(-)
 delete mode 100644 e2e/cmd_wrapper_test.go
 delete mode 100644 e2e/cmd_wrapper_util.go
 rename e2e/{utils_test.go => utils.go} (75%)

diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml
index d9ae6d735..b97ccaa55 100644
--- a/.github/workflows/build-and-release.yml
+++ b/.github/workflows/build-and-release.yml
@@ -306,6 +306,7 @@ jobs:
         shell: bash
         run: |
           chmod +x ./dist/*
+          export KLUCTL_EXE=./dist/kluctl-${{ matrix.binary-suffix }}$TOOLS_EXE
           ./dist/e2e.test-${{ matrix.binary-suffix }}$TOOLS_EXE -test.v
 
   release:
diff --git a/e2e/cmd_wrapper_test.go b/e2e/cmd_wrapper_test.go
deleted file mode 100644
index 0176c9475..000000000
--- a/e2e/cmd_wrapper_test.go
+++ /dev/null
@@ -1,59 +0,0 @@
-package e2e
-
-import (
-	"flag"
-	"github.com/kluctl/kluctl/cmd/kluctl/commands"
-	log "github.com/sirupsen/logrus"
-	"os"
-	"strings"
-	"testing"
-)
-
-type arrayFlags []string
-
-func (i *arrayFlags) String() string {
-	return strings.Join(*i, " ")
-}
-
-func (i *arrayFlags) Set(value string) error {
-	*i = append(*i, value)
-	return nil
-}
-
-var cmdArgs arrayFlags
-
-func init() {
-	flag.Var(&cmdArgs, "karg", "")
-}
-
-func doRunCmd() error {
-	log.Infof("Started wrapper: %s", cmdArgs.String())
-
-	_, err := os.Stdout.WriteString(stdoutStartMarker + "\n")
-	if err != nil {
-		return err
-	}
-	_, err = os.Stderr.WriteString(stdoutStartMarker + "\n")
-	if err != nil {
-		return err
-	}
-	defer func() {
-		_, _ = os.Stdout.WriteString(stdoutEndMarker + "\n")
-		_, _ = os.Stderr.WriteString(stdoutEndMarker + "\n")
-	}()
-
-	_, _, err = commands.ExecuteWithArgs(cmdArgs)
-	return err
-}
-
-func TestKluctlWrapper(t *testing.T) {
-	if len(cmdArgs) == 0 {
-		return
-	}
-
-	err := doRunCmd()
-	if err != nil {
-		t.Fatal(err)
-	}
-	os.Exit(0)
-}
diff --git a/e2e/cmd_wrapper_util.go b/e2e/cmd_wrapper_util.go
deleted file mode 100644
index 4b22db929..000000000
--- a/e2e/cmd_wrapper_util.go
+++ /dev/null
@@ -1,69 +0,0 @@
-package e2e
-
-import (
-	"bufio"
-	"bytes"
-	"io"
-	"os"
-	"os/exec"
-	"testing"
-)
-
-func runWrappedCmd(t *testing.T, testName string, cwd string, env []string, args []string) (string, string, error) {
-	executable, err := os.Executable()
-	if err != nil {
-		return "", "", err
-	}
-
-	var args2 []string
-	args2 = append(args2, "-test.run", testName)
-	for _, a := range args {
-		args2 = append(args2, "-karg", a)
-	}
-
-	cmd := exec.Command(executable, args2...)
-	cmd.Env = env
-	cmd.Dir = cwd
-
-	stdoutPipe, err := cmd.StdoutPipe()
-	if err != nil {
-		return "", "", err
-	}
-	stderrPipe, err := cmd.StderrPipe()
-	if err != nil {
-		_ = stdoutPipe.Close()
-		return "", "", err
-	}
-
-	stdReader := func(testLogPrefix string, buf io.StringWriter, pipe io.Reader) {
-		scanner := bufio.NewScanner(pipe)
-		inMarker := false
-		for scanner.Scan() {
-			l := scanner.Text()
-			if !inMarker {
-				if l == stdoutStartMarker {
-					inMarker = true
-					continue
-				}
-				t.Log(testLogPrefix + l)
-			} else {
-				if l == stdoutEndMarker {
-					inMarker = false
-					continue
-				}
-
-				t.Log(testLogPrefix + l)
-				_, _ = buf.WriteString(l + "\n")
-			}
-		}
-	}
-
-	stdoutBuf := bytes.NewBuffer(nil)
-	stderrBuf := bytes.NewBuffer(nil)
-
-	go stdReader("stdout: ", stdoutBuf, stdoutPipe)
-	go stdReader("stderr: ", stderrBuf, stderrPipe)
-
-	err = cmd.Run()
-	return stdoutBuf.String(), stderrBuf.String(), err
-}
diff --git a/e2e/project.go b/e2e/project.go
index fd9e60f5d..7b2b0aa6c 100644
--- a/e2e/project.go
+++ b/e2e/project.go
@@ -13,6 +13,7 @@ import (
 	"net"
 	"net/http"
 	"os"
+	"os/exec"
 	"path/filepath"
 	"reflect"
 	"runtime"
@@ -526,7 +527,23 @@ func (p *testProject) Kluctl(argsIn ...string) (string, string, error) {
 
 	p.t.Logf("Runnning kluctl: %s", strings.Join(args, " "))
 
-	stdout, stderr, err := runWrappedCmd(p.t, "TestKluctlWrapper", cwd, env, args)
+	kluctlExe := os.Getenv("KLUCTL_EXE")
+	if kluctlExe == "" {
+		curDir, _ := os.Getwd()
+		for i, p := range env {
+			x := strings.SplitN(p, "=", 2)
+			if x[0] == "PATH" {
+				env[i] = fmt.Sprintf("PATH=%s%c%s%c%s", curDir, os.PathListSeparator, filepath.Join(curDir, ".."), os.PathListSeparator, x[1])
+			}
+		}
+		kluctlExe = "kluctl"
+	}
+
+	cmd := exec.Command(kluctlExe, args...)
+	cmd.Dir = cwd
+	cmd.Env = env
+
+	stdout, stderr, err := runHelper(p.t, cmd)
 	return stdout, stderr, err
 }
 
diff --git a/e2e/utils_test.go b/e2e/utils.go
similarity index 75%
rename from e2e/utils_test.go
rename to e2e/utils.go
index 9ee86e96b..ad2c8930e 100644
--- a/e2e/utils_test.go
+++ b/e2e/utils.go
@@ -1,8 +1,11 @@
 package e2e
 
 import (
+	"bufio"
+	"bytes"
 	"github.com/kluctl/kluctl/pkg/utils/uo"
 	"github.com/kluctl/kluctl/pkg/validation"
+	"io"
 	"os/exec"
 	"reflect"
 	"strings"
@@ -86,6 +89,36 @@ func assertNestedFieldEquals(t *testing.T, o *uo.UnstructuredObject, expected in
 	}
 }
 
+func runHelper(t *testing.T, cmd *exec.Cmd) (string, string, error) {
+	stdoutPipe, err := cmd.StdoutPipe()
+	if err != nil {
+		return "", "", err
+	}
+	stderrPipe, err := cmd.StderrPipe()
+	if err != nil {
+		_ = stdoutPipe.Close()
+		return "", "", err
+	}
+
+	stdReader := func(testLogPrefix string, buf io.StringWriter, pipe io.Reader) {
+		scanner := bufio.NewScanner(pipe)
+		for scanner.Scan() {
+			l := scanner.Text()
+			t.Log(testLogPrefix + l)
+			_, _ = buf.WriteString(l + "\n")
+		}
+	}
+
+	stdoutBuf := bytes.NewBuffer(nil)
+	stderrBuf := bytes.NewBuffer(nil)
+
+	go stdReader("stdout: ", stdoutBuf, stdoutPipe)
+	go stdReader("stderr: ", stderrBuf, stderrPipe)
+
+	err = cmd.Run()
+	return stdoutBuf.String(), stderrBuf.String(), err
+}
+
 func init() {
 	deleteTestNamespaces(defaultKindCluster)
 }

From 3ec1410a8ffce093b803a18f1450b8f51ff3e84f Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Tue, 5 Apr 2022 14:28:17 +0200
Subject: [PATCH 0614/2916] tests: Use abs path when running kluctl

---
 e2e/project.go | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/e2e/project.go b/e2e/project.go
index 7b2b0aa6c..bde8aa916 100644
--- a/e2e/project.go
+++ b/e2e/project.go
@@ -537,6 +537,12 @@ func (p *testProject) Kluctl(argsIn ...string) (string, string, error) {
 			}
 		}
 		kluctlExe = "kluctl"
+	} else {
+		p, err := filepath.Abs(kluctlExe)
+		if err != nil {
+			return "", "", err
+		}
+		kluctlExe = p
 	}
 
 	cmd := exec.Command(kluctlExe, args...)

From 406f3aae9c981f96f64762343440912e2682c4a3 Mon Sep 17 00:00:00 2001
From: Mathias Gebbe 
Date: Tue, 5 Apr 2022 22:49:43 +0200
Subject: [PATCH 0615/2916] feat: add sha256 check sums

---
 .github/workflows/build-and-release.yml | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml
index b97ccaa55..a316d08ac 100644
--- a/.github/workflows/build-and-release.yml
+++ b/.github/workflows/build-and-release.yml
@@ -89,6 +89,7 @@ jobs:
           go build ./cmd/kluctl
           go test -c ./e2e
           mv kluctl kluctl-linux-amd64
+          sha256sum -z kluctl-linux-amd64 > kluctl-linux-amd64.sha256
           mv e2e.test e2e.test-linux-amd64
       - name: Build kluctl (darwin)
         run: |
@@ -98,6 +99,7 @@ jobs:
           go build ./cmd/kluctl
           go test -c ./e2e
           mv kluctl kluctl-darwin-amd64
+          sha256sum -z kluctl-darwin-amd64 > kluctl-darwin-amd64.sha256
           mv e2e.test e2e.test-darwin-amd64
       - name: Build kluctl (windows)
         run: |
@@ -107,6 +109,7 @@ jobs:
           go build ./cmd/kluctl
           go test -c ./e2e
           mv kluctl.exe kluctl-windows-amd64.exe
+          sha256sum -z kluctl-windows-amd64.exe > kluctl-windows-amd64.exe.sha256
           mv e2e.test.exe e2e.test-windows-amd64.exe
       - name: Upload dist artifact
         uses: actions/upload-artifact@v2
@@ -328,3 +331,4 @@ jobs:
             dist/kluctl-linux-*
             dist/kluctl-darwin-*
             dist/kluctl-windows-*
+            dist/*.sha256

From 7d7d206d007589040949fcd862c821fa1dc51fc7 Mon Sep 17 00:00:00 2001
From: Mathias Gebbe 
Date: Tue, 5 Apr 2022 23:24:37 +0200
Subject: [PATCH 0616/2916] fix: add upload-artifact

---
 .github/workflows/build-and-release.yml | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml
index a316d08ac..5d44fe6d7 100644
--- a/.github/workflows/build-and-release.yml
+++ b/.github/workflows/build-and-release.yml
@@ -117,8 +117,11 @@ jobs:
           name: dist
           path: |
             kluctl-linux-amd64
+            kluctl-linux-amd64.sha256
             kluctl-darwin-amd64
+            kluctl-darwin-amd64.sha256
             kluctl-windows-amd64.exe
+            kluctl-windows-amd64.exe.sha256
             e2e.test-linux-amd64
             e2e.test-darwin-amd64
             e2e.test-windows-amd64.exe

From fa5c55ad71a727020aca0ea833905352ae94295c Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Wed, 6 Apr 2022 12:57:53 +0200
Subject: [PATCH 0617/2916] feat: Make target available to templating context

---
 cmd/kluctl/commands/utils.go | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go
index 4e825de0b..0be2ed76d 100644
--- a/cmd/kluctl/commands/utils.go
+++ b/cmd/kluctl/commands/utils.go
@@ -151,6 +151,12 @@ func withProjectTargetCommandContext(args projectTargetCommandArgs, p *kluctl_pr
 
 	varsCtx.UpdateChild("args", allArgs)
 
+	targetVars, err := uo.FromStruct(target)
+	if err != nil {
+		return err
+	}
+	varsCtx.UpdateChild("target", targetVars)
+
 	renderOutputDir := args.renderOutputDirFlags.RenderOutputDir
 	if renderOutputDir == "" {
 		tmpDir, err := ioutil.TempDir(p.TmpDir, "rendered")

From 1aad7e1ada73f1b92066a09c2a4d2a4633c159d3 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Wed, 6 Apr 2022 12:58:18 +0200
Subject: [PATCH 0618/2916] fix: Don't return NotFound errors from
 ListAllObjects

---
 pkg/k8s/k8s_cluster.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go
index e8a821759..d949e049a 100644
--- a/pkg/k8s/k8s_cluster.go
+++ b/pkg/k8s/k8s_cluster.go
@@ -563,7 +563,7 @@ func (k *K8sCluster) ListAllObjects(verbs []string, namespace string, labels map
 			} else {
 				l, apiWarnings, err = k.ListObjects(gvk, namespace, labels)
 			}
-			if err != nil {
+			if err != nil && !errors.IsNotFound(err) {
 				return err
 			}
 			mutex.Lock()

From d44b0b4cde8c4581b34bfb476d4404f65c801517 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Wed, 6 Apr 2022 12:58:36 +0200
Subject: [PATCH 0619/2916] fix: Use kluctl instead of kluctl-j2-server for
 jinja2 temp dir

---
 pkg/jinja2/python_src/jinja2_cache.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pkg/jinja2/python_src/jinja2_cache.py b/pkg/jinja2/python_src/jinja2_cache.py
index d869d30ed..669c0a2dc 100644
--- a/pkg/jinja2/python_src/jinja2_cache.py
+++ b/pkg/jinja2/python_src/jinja2_cache.py
@@ -11,7 +11,7 @@
 from jinja2.bccache import Bucket
 
 def get_tmp_base_dir():
-    dir = os.path.join(tempfile.gettempdir(), "kluctl-j2-server")
+    dir = os.path.join(tempfile.gettempdir(), "kluctl")
     os.makedirs(dir, exist_ok=True)
     return dir
 

From aeaa324bb86797e809e1e7111bb6fddbb34eba80 Mon Sep 17 00:00:00 2001
From: Mathias Gebbe 
Date: Thu, 7 Apr 2022 08:09:37 +0200
Subject: [PATCH 0620/2916] feat: modify to have only one checksum.txt

---
 .github/workflows/build-and-release.yml | 13 ++++++-------
 1 file changed, 6 insertions(+), 7 deletions(-)

diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml
index 5d44fe6d7..5e6c0024d 100644
--- a/.github/workflows/build-and-release.yml
+++ b/.github/workflows/build-and-release.yml
@@ -89,7 +89,6 @@ jobs:
           go build ./cmd/kluctl
           go test -c ./e2e
           mv kluctl kluctl-linux-amd64
-          sha256sum -z kluctl-linux-amd64 > kluctl-linux-amd64.sha256
           mv e2e.test e2e.test-linux-amd64
       - name: Build kluctl (darwin)
         run: |
@@ -99,7 +98,6 @@ jobs:
           go build ./cmd/kluctl
           go test -c ./e2e
           mv kluctl kluctl-darwin-amd64
-          sha256sum -z kluctl-darwin-amd64 > kluctl-darwin-amd64.sha256
           mv e2e.test e2e.test-darwin-amd64
       - name: Build kluctl (windows)
         run: |
@@ -109,22 +107,23 @@ jobs:
           go build ./cmd/kluctl
           go test -c ./e2e
           mv kluctl.exe kluctl-windows-amd64.exe
-          sha256sum -z kluctl-windows-amd64.exe > kluctl-windows-amd64.exe.sha256
           mv e2e.test.exe e2e.test-windows-amd64.exe
       - name: Upload dist artifact
         uses: actions/upload-artifact@v2
+        run: |
+          sha256sum kluctl-linux-amd64 > checksums.txt
+          sha256sum kluctl-darwin-amd64 >> checksums.txt
+          sha256sum kluctl-windows-amd64.exe >> checksums.txt
         with:
           name: dist
           path: |
             kluctl-linux-amd64
-            kluctl-linux-amd64.sha256
             kluctl-darwin-amd64
-            kluctl-darwin-amd64.sha256
             kluctl-windows-amd64.exe
-            kluctl-windows-amd64.exe.sha256
             e2e.test-linux-amd64
             e2e.test-darwin-amd64
             e2e.test-windows-amd64.exe
+            checksums.txt
 
   docker-host:
     if: "!startsWith(github.ref, 'refs/tags/')"
@@ -334,4 +333,4 @@ jobs:
             dist/kluctl-linux-*
             dist/kluctl-darwin-*
             dist/kluctl-windows-*
-            dist/*.sha256
+            dist/checksums.txt
\ No newline at end of file

From d0bb15bd017398e422f2657cace6994846bbbdc0 Mon Sep 17 00:00:00 2001
From: Mathias Gebbe 
Date: Thu, 7 Apr 2022 08:55:50 +0200
Subject: [PATCH 0621/2916] fix: run and uses not allowed, missing line break

---
 .github/workflows/build-and-release.yml | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml
index 5e6c0024d..63afba934 100644
--- a/.github/workflows/build-and-release.yml
+++ b/.github/workflows/build-and-release.yml
@@ -108,12 +108,13 @@ jobs:
           go test -c ./e2e
           mv kluctl.exe kluctl-windows-amd64.exe
           mv e2e.test.exe e2e.test-windows-amd64.exe
-      - name: Upload dist artifact
-        uses: actions/upload-artifact@v2
+      - name: Build checksums.txt
         run: |
           sha256sum kluctl-linux-amd64 > checksums.txt
           sha256sum kluctl-darwin-amd64 >> checksums.txt
           sha256sum kluctl-windows-amd64.exe >> checksums.txt
+      - name: Upload dist artifact
+        uses: actions/upload-artifact@v2
         with:
           name: dist
           path: |
@@ -333,4 +334,5 @@ jobs:
             dist/kluctl-linux-*
             dist/kluctl-darwin-*
             dist/kluctl-windows-*
-            dist/checksums.txt
\ No newline at end of file
+            dist/checksums.txt
+

From 31933fb14efc4ff2fc26d5eaf3f34d0be927398d Mon Sep 17 00:00:00 2001
From: Mathias Gebbe 
Date: Thu, 7 Apr 2022 09:02:50 +0200
Subject: [PATCH 0622/2916] fix: try to build checksums in the release job

---
 .github/workflows/build-and-release.yml | 13 ++++++-------
 1 file changed, 6 insertions(+), 7 deletions(-)

diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml
index 63afba934..795a2a1b7 100644
--- a/.github/workflows/build-and-release.yml
+++ b/.github/workflows/build-and-release.yml
@@ -108,11 +108,6 @@ jobs:
           go test -c ./e2e
           mv kluctl.exe kluctl-windows-amd64.exe
           mv e2e.test.exe e2e.test-windows-amd64.exe
-      - name: Build checksums.txt
-        run: |
-          sha256sum kluctl-linux-amd64 > checksums.txt
-          sha256sum kluctl-darwin-amd64 >> checksums.txt
-          sha256sum kluctl-windows-amd64.exe >> checksums.txt
       - name: Upload dist artifact
         uses: actions/upload-artifact@v2
         with:
@@ -124,7 +119,6 @@ jobs:
             e2e.test-linux-amd64
             e2e.test-darwin-amd64
             e2e.test-windows-amd64.exe
-            checksums.txt
 
   docker-host:
     if: "!startsWith(github.ref, 'refs/tags/')"
@@ -325,6 +319,11 @@ jobs:
         uses: actions/checkout@v2
       - name: Download artifacts
         uses: actions/download-artifact@v2
+      - name: Build checksums.txt
+        run: |
+          sha256sum kluctl-linux-amd64 > checksums.txt
+          sha256sum kluctl-darwin-amd64 >> checksums.txt
+          sha256sum kluctl-windows-amd64.exe >> checksums.txt
       - name: Release
         uses: softprops/action-gh-release@v1
         with:
@@ -334,5 +333,5 @@ jobs:
             dist/kluctl-linux-*
             dist/kluctl-darwin-*
             dist/kluctl-windows-*
-            dist/checksums.txt
+            checksums.txt
 

From eac6b32a5c10e9b6d4c6ed0a3f3717eef483b2d8 Mon Sep 17 00:00:00 2001
From: Alexander Block 
Date: Thu, 7 Apr 2022 17:47:02 +0200
Subject: [PATCH 0623/2916] docs: Remove documentation and examples and add
 link to kluctl.io

---
 README.md                                     | 104 +---
 docs/annotations.md                           | 152 ------
 docs/cluster-config.md                        |  51 --
 docs/commands.md                              | 479 ------------------
 docs/deployments.md                           | 340 -------------
 docs/getting-started.md                       |  44 --
 docs/helm-integration.md                      | 130 -----
 docs/hooks.md                                 |  43 --
 docs/images.md                                | 129 -----
 docs/jinja2-templating.md                     | 283 -----------
 docs/kluctl_project.md                        | 337 ------------
 docs/kustomize-integration.md                 |  11 -
 docs/sealed-secrets.md                        | 138 -----
 docs/tags.md                                  |  79 ---
 .../simple-with-external-repos/.kluctl.yml    |  14 -
 examples/simple/.kluctl.yml                   |   5 -
 examples/simple/clusters/kind.yml             |   3 -
 examples/simple/deployment.yml                |  10 -
 examples/simple/deployment/deployment.yml     |   4 -
 examples/simple/deployment/nginx/deploy.yml   |  26 -
 .../simple/deployment/nginx/kustomization.yml |   5 -
 21 files changed, 2 insertions(+), 2385 deletions(-)
 delete mode 100644 docs/annotations.md
 delete mode 100644 docs/cluster-config.md
 delete mode 100644 docs/commands.md
 delete mode 100644 docs/deployments.md
 delete mode 100644 docs/getting-started.md
 delete mode 100644 docs/helm-integration.md
 delete mode 100644 docs/hooks.md
 delete mode 100644 docs/images.md
 delete mode 100644 docs/jinja2-templating.md
 delete mode 100644 docs/kluctl_project.md
 delete mode 100644 docs/kustomize-integration.md
 delete mode 100644 docs/sealed-secrets.md
 delete mode 100644 docs/tags.md
 delete mode 100644 examples/simple-with-external-repos/.kluctl.yml
 delete mode 100644 examples/simple/.kluctl.yml
 delete mode 100644 examples/simple/clusters/kind.yml
 delete mode 100644 examples/simple/deployment.yml
 delete mode 100644 examples/simple/deployment/deployment.yml
 delete mode 100644 examples/simple/deployment/nginx/deploy.yml
 delete mode 100644 examples/simple/deployment/nginx/kustomization.yml

diff --git a/README.md b/README.md
index e6ebe1135..e76e01268 100644
--- a/README.md
+++ b/README.md
@@ -24,108 +24,8 @@ kluctl works completely local. In its simplest form, there is no need for any op
 As long as the target cluster kubeconfig is present locally, you are able to execute it from everywhere, including your
 CI/CD pipelines or your laptop.
 
-## Motivation/History
-
-kluctl was created after multiple incarnations of complex multi-environment (e.g. dev, test, prod) deployments, including everything
-from monitoring, persistency and the actual custom services. The philosophy of these deployments was always
-"what belongs together, should be put together", meaning that only as much Git repositories were involved as necessary.
-
-The problems to solve turned out to be always the same:
-* Dozens of Helm Charts, kustomize deployments and standalone Kubernetes deployments needed to be orchestrated in a way
-that they work together (services need to connect to the correct databases, and so on)
-* (Encrypted) Secrets needed to be managed and orchestrated for multiple environments and clusters
-* Updates of components was always risky and required keeping track of what actually changed since the last deployment
-* Available tools (Helm, Kustomize) were not suitable to solve this on its own in an easy/natural way
-* A lot of bash scripting was required to put things together
-
-When this got more and more complex, and the bash scripts started to become a mess (as "simple" Bash scripts always tend to become),
-kluctl was started from scratch. It now tries to solve the mentioned problems and provide a useful set of features (commands)
-in a sane way.
-
-## Installation
-
-kluctl can currently only be installed this way:
-1. Download a standalone binary from the latest release and make it available in your PATH, either by copying it into `/usr/local/bin` or by modifying the PATH variable
-
-Future releases will include packaged releases for homebrew and other established package managers (contributions are welcome).
+![](https://kluctl.io/asciinema/kluctl.gif)
 
 ## Documentation
 
-### Getting started
-
-You can find a basic Getting Started documentation [here](./docs/getting-started.md).
-
-### kluctl project config (.kluctl.yml)
-
-The [.kluctl.yml](./docs/kluctl_project.md) file is the central configuration file that defines your kluctl project.
-It declares what (clusters, secrets, deployment projects, ...) is needed for your deployment, where to find it and what
-targets are available to invoke commands against.
-
-### Deployment projects
-
-A deployment project is a collection of actual (kustomize) deployments. Documentation about the project structure and
-individual features can be found [here](./docs/deployments.md).
-
-### Command line interface
-
-The command line interface is documented [here](./docs/commands.md)
-
-## Concepts and Features
-
-### Declarative
-
-All deployments are defined in a declarative way. No coding is required to implement deployments that support all
-kinds of flavors, target environments, configurations, and so on.
-
-The entrypoint for the deployment is always the `deployment.yml` file, which then declares what else needs to be
-included and/or deployed.
-
-### Single source of truth
-
-Deployments realized with kluctl are meant to be the single source of truth for everything that belongs to your
-applications. This starts with base infrastructure that needs to be deployed inside Kubernetes, for example ingress
-controllers, operators, networking, storage, monitoring ... and then finally leads to your applications, including all third-party
-applications (e.g. databases) being deploying with the same tooling.
-
-You are of course free to split up your deployments however you want. For example, it might make sense to split
-base infrastructure deployments and application deployments, so that the applications itself get decoupled from the
-base infrastructure.
-
-### Jinja2 based templating engine
-
-Kubernetes resources and all other involved configuration is based on [Jinja2](https://palletsprojects.com/p/jinja/)
-templates. Jinja2 context variables are usually passed though [kluctl targets](./docs/kluctl_project.md#targets)
-but can also be overridden via CLI.
-
-Jinja2 macros allow unifying of heavily repeated deployments (e.g. your 100 microservices) in a convenient way.
-
-### Unified CLI (command line interface)
-
-Deploying your application and all of its dependencies is done via a unified command line interface. It's always
-the same, no matter how large (single nginx or 100 microservices) or flexible (test env, uat env, prod env, local env...)
-your deployment actually is.
-
-### Secure management of (sealed) secrets in Git
-
-Maintaining secrets inside Git is a complex and dangerous task, but at the same time has many advantages when done
-properly. Encrypting such secrets is a must, but there are multiple more or less secure ways to do so.
-
-kluctl has builtin support for [sealed-secrets](https://github.com/bitnami-labs/sealed-secrets). This means,
-it can plug in sealed secrets into your deployment in a dynamic and configurable way, targeting multiple clusters,
-environments, configurations and so on.
-
-sealed-secrets is public-key crypto based, allowing to target individual clusters or namespaces in a secure way,
-meaning that only the targeted environments are able to decrypt secrets. It also means that the private-key needed
-for decryption never has to be present while deploying.
-
-### Kustomize integration
-
-Individual deployments are handled by [kustomize](https://kustomize.io/). This allows to run patches and strategic
-merges against deployments, which is especially useful when third-party deployments are involved which need
-customization. In most cases however, the involved `kustomization.yml` only contains a list kubernetes resources
-to deploy.
-
-### Helm integration
-
-A kustomize deployment can also be based on a Helm Chart. Helm Charts can be pulled into the deployment project
-and then configured via Jinja2 templating.
+Documentation can be found here: https://kluctl.io
diff --git a/docs/annotations.md b/docs/annotations.md
deleted file mode 100644
index 046f341bc..000000000
--- a/docs/annotations.md
+++ /dev/null
@@ -1,152 +0,0 @@
-# Annotations on object level
-
-kluctl supports multiple annotations that influence individual commands. These are:
-
-## Deployment related
-These annotations control details about how deployments should be handled.
-
-### kluctl.io/skip-delete
-If set to "true", the annotated resource will not be deleted when [delete](./commands.md#delete) or 
-[prune](./commands.md#prune) is called.
-
-### kluctl.io/skip-delete-if-tags
-If set to "true", the annotated resource will not be deleted when [delete](./commands.md#delete) or 
-[prune](./commands.md#prune) is called and inclusion/exclusion tags are used at the same time.
-
-This tag is especially useful and required on resources that would otherwise cause cascaded deletions of resources that
-do not match the specified inclusion/exclusion tags. Namespaces are the most prominent example of such resources, as
-they most likely don't match exclusion tags, but cascaded deletion would still cause deletion of the excluded resources.
-
-### kluctl.io/diff-name
-This annotation will override the name of the object when looking for the in-cluster version of an object used for
-diffs. This is useful when you are forced to use new names for the same objects whenever the content changes, e.g.
-for all kinds of immutable resource types.
-
-Example (filename job.yml):
-```yaml
-apiVersion: batch/v1
-kind: Job
-metadata:
-  name: myjob-{{ load_sha256("job.yml", 6) }}
-  annotations:
-    kluctl.io/diff-name: myjob
-spec:
-  template:
-    spec:
-      containers:
-      - name: hello
-        image: busybox
-        command: ["sh",  "-c", "echo hello"]
-      restartPolicy: Never
-```
-
-Without the `kluctl.io/diff-name` annotation, any change to the `job.yml` would be treated as a new object in resulting
-diffs from various commands. This is due to the inclusion of the file hash in the job name. This would make it very hard
-to figure out what exactly changed in an object.
-
-With the `kluctl.io/diff-name` annotation, kluctl will pick an existing job from the cluster with the same diff-name
-and use it for the diff, making it a lot easier to analyze changes. If multiple objects match, the one with the youngest
-`creationTimestamp` is chosen.
-
-Please note that this will not cause old objects (with the same diff-name) to be prunes. You still have to regularely
-prune the deployment.
-
-### kluctl.io/downscale-patch
-Describes how a [downscale](./commands.md#downscale) on a resource can be done via a json patch. This is useful on
-CRD based resources where no automatic downscale can be performed by kluctl.
-
-Example (filename job.yml):
-```yaml
-apiVersion: kibana.k8s.elastic.co/v1
-kind: Kibana
-metadata:
-  name: kibana
-  annotations:
-    kluctl.io/downscale-patch: |
-      - op: replace
-        path: /spec/count
-        value: 0
-spec:
-  version: 7.14.1
-  count: 1
-```
-
-If more than one patch needs to be specified, add `-xxx` to the annotation key, where `xxx` is an arbitrary number.
-
-### kluctl.io/downscale-ignore
-If set to "true", the resource will be ignored while [downscale](./commands.md#downscale) is executed.
-
-### kluctl.io/downscale-delete
-If set to "true", the resource will be deleted while [downscale](./commands.md#downscale) is executed.
-
-### kluctl.io/force-apply
-If set to "true", the whole resource will be force-applied, meaning that all fields will be overwritten in case of
-field manager conflicts.
-
-### kluctl.io/force-apply-field
-Specifies a [JSON Path](https://goessner.net/articles/JsonPath/) for fields that should be force-applied. Matching
-fields will be overwritten in case of field manager conflicts.
-
-If more than one field needs to be specified, add `-xxx` to the annotation key, where `xxx` is an arbitrary number.
-
-### kluctl.io/ignore-diff
-If set to "true", the whole resource will be ignored while calculating diffs.
-
-### kluctl.io/ignore-diff-field
-Specifies a [JSON Path](https://goessner.net/articles/JsonPath/) for fields that should be ignored while calculating
-diffs.
-
-If more than one field needs to be specified, add `-xxx` to the annotation key, where `xxx` is an arbitrary number.
-
-## Hooks related
-See [hooks](./hooks.md) for more details.
-
-### kluctl.io/hook
-Declares a resource to be a hook, which is deployed/executed as described in [hooks](./hooks.md). The value of the
-annotation determines when the hook is deployed/executed.
-
-### kluctl.io/hook-weight
-Specifies a weight for the hook, used to determine deployment/execution order.
-
-### kluctl.io/hook-delete-policy
-Defines when to delete the hook resource.
-
-### kluctl.io/hook-wait
-Defines whether kluctl should wait for hook-completion.
-
-## Validation related
-The following annotations influence the [validate](./commands.md#validate) command.
-
-### validate-result.kluctl.io/xxx
-If this annotation is found on a resource that is checked while validation, the key and the value of the annotation
-are added to the validation result, which is then returned by the validate command.
-
-The annotation key is dynamic, meaning that all annotations that begin with `validate-result.kluctl.io/` are taken
-into account.
-
-# Annotations on kustomize deployment level
-
-In addition to kluctl.io annotations which can be set on object/resource level, you can also set a few annotations
-inside the kustomization.yml itself.
-
-Example:
-```yaml
-apiVersion: kustomize.config.k8s.io/v1beta1
-kind: Kustomization
-
-metadata:
-  annotations:
-    kluctl.io/barrier: "true"
-    kluctl.io/wait-readiness: "true"
-
-resources:
-  - deployment.yml
-```
-
-### kluctl.io/barrier
-If set to `true`, kluctl will wait for all previous objects to be applied (but not necessarily ready). This has the
-same effect as [barrier](./deployments.md#barriers) from deployment projects.
-
-### kluctl.io/wait-readiness
-If set to `true`, kluctl will wait for readiness of all objects from this kustomization project. Readiness is defined
-the same as in [hook readiness](./hooks.md#hook-readiness).
diff --git a/docs/cluster-config.md b/docs/cluster-config.md
deleted file mode 100644
index 721eb9984..000000000
--- a/docs/cluster-config.md
+++ /dev/null
@@ -1,51 +0,0 @@
-# Kubernetes Clusters
-
-kluctl is able to perform the same deployment to different clusters, with each cluster optionally having some
-individual/specific configuration. For example each cluster might need its own credentials to access AWS Route53
-for DNS management. You might also configure very specific things needed by you, for example a dev cluster could
-be configured to have only 1 ingress controller replica while the production has 3 replicas.
-
-This cluster configuration is done via a cluster yaml file. The name of the config file must match the desired
-cluster name. The cluster name is then used to identify this cluster in all future CLI invocations.
-
-All cluster configurations must be placed into the same directory. kluctl will then look into this directory
-and try to find the correct one via the cluster name. The default directory location is `/clusters`
-and can be overriden by the `--cluster-dir` argument.
-
-## Minimal cluster config
-
-Let's assume you want to add a cluster with the name "test.example.com". You'd create the file
-`/clusters/test.example.com.yml` and fill it with the following minimal configuration:
-
-```yaml
-cluster:
-  name: test.example.com
-  context: test.example.com
-```
-
-The `context` refers to the kubeconfig context that is later used to connect to the cluster. This means, that you must
-have that same cluster configured in your kubeconfig, referred by the given context name. The name and context do not
-have to match, but it is recommended to use the same name.
-
-## Using cluster config in templates
-
-This configuration is later available whenever Jinja2 templates are involved. This means, that you for example can
-use `{{ cluster.name }}` to get the cluster name into one of your deployment configurations/resources.
-
-## Custom cluster configuration
-
-The configuration also allows adding as much custom configuration as you need below the `cluster` dictionary.
-For example:
-
-```yaml
-cluster:
-  name: test.example.com
-  context: test.example.com
-  ingress_config:
-    replicas: 1
-    external_auth:
-      url: https://example.com/auth
-```
-
-This is then also available in all Jinja2 templates (e.g. `{{ cluster.ingress_config.replicas }}`). In the above example, it would allow to configure a test cluster
-differently from the production cluster when it comes to ingress controller configuration.
diff --git a/docs/commands.md b/docs/commands.md
deleted file mode 100644
index d6825c240..000000000
--- a/docs/commands.md
+++ /dev/null
@@ -1,479 +0,0 @@
-# Command line interface
-
-kluctl offers a unified command line interface that allows to standardize all your deployments. Every project,
-no matter how different it is from other projects, is managed the same way.
-
-You can always call `kluctl --help` or `kluctl  --help` for a help prompt.
-
-# Common command arguments
-A few sets of arguments are common between multiple commands. These arguments are still part of the command itself and
-must be placed *after* the command name.
-
-The following sets of common arguments are available:
-
-## project arguments
-
-```
-Project arguments:
-  Define where and how to load the kluctl project and its components from.
-
-  -p, --project-url=STRING              Git url of the kluctl project. If not specified, the current directory will be
-                                        used instead of a remote Git project
-  -b, --project-ref=STRING              Git ref of the kluctl project. Only used when --project-url was given.
-  -c, --project-config=STRING           Location of the .kluctl.yml config file. Defaults to $PROJECT/.kluctl.yml
-      --local-clusters=STRING           Local clusters directory. Overrides the project from .kluctl.yml
-      --local-deployment=STRING         Local deployment directory. Overrides the project from .kluctl.yml
-      --local-sealed-secrets=STRING     Local sealed-secrets directory. Overrides the project from .kluctl.yml
-      --from-archive=STRING             Load project (.kluctl.yml, cluster, ...) from archive. Given path can either be
-                                        an archive file or a directory with the extracted contents.
-      --from-archive-metadata=STRING    Specify where to load metadata (targets, ...) from. If not specified, metadata
-                                        is assumed to be part of the archive.
-      --cluster=STRING                  Specify/Override cluster
-  -t, --target=STRING                   Target name to run command for. Target must exist in .kluctl.yml.
-  -a, --arg=ARG,...                     Template argument in the form name=value
-
-```
-
-
-These arguments control where and how to load the kluctl project and deployment project.
-
-## image arguments
-
-```
-Image arguments:
-  Control fixed images and update behaviour.
-
-  -F, --fixed-image=FIXED-IMAGE,...    Pin an image to a given version. Expects
-                                       '--fixed-image=image<:namespace:deployment:container>=result'
-      --fixed-images-file=STRING       Use .yml file to pin image versions. See output of list-images sub-command or
-                                       read the documentation for details about the output format
-  -u, --update-images                  This causes kluctl to prefer the latest image found in registries, based on the
-                                       'latest_image' filters provided to 'images.get_image(...)' calls. Use this flag
-                                       if you want to update to the latest versions/tags of all images. '-u' takes
-                                       precedence over '--fixed-image/--fixed-images-file', meaning that the latest
-                                       images are used even if an older image is given via fixed images.
-
-```
-
-
-These arguments control image versions requested by `images.get_image(...)` [calls](./images.md#imagesget_image).
-
-## Inclusion/Exclusion arguments
-
-```
-Inclusion/Exclusion arguments:
-  Control inclusion/exclusion.
-
-  -I, --include-tag=INCLUDE-TAG,...    Include deployments with given tag.
-  -E, --exclude-tag=EXCLUDE-TAG,...    Exclude deployments with given tag. Exclusion has precedence over inclusion,
-                                       meaning that explicitly excluded deployments will always be excluded even if an
-                                       inclusion rule would match the same deployment.
-      --include-deployment-dir=INCLUDE-DEPLOYMENT-DIR,...
-                                       Include deployment dir. The path must be relative to the root deployment project.
-      --exclude-deployment-dir=EXCLUDE-DEPLOYMENT-DIR,...
-                                       Exclude deployment dir. The path must be relative to the root deployment project.
-                                       Exclusion has precedence over inclusion, same as in --exclude-tag
-
-```
-
-
-These arguments control inclusion/exclusion based on tags and kustomize depoyment pathes.
-
-# Environment variables
-All options/arguments accepted by kluctl can also be specified via environment variables. The name of the environment
-variables always start with `KLUCTL_` and end witht the option/argument in uppercase and dashes replaced with
-underscores. As an example, `--project=my-project` can also be specified with the environment variable
-`KLUCTL_PROJECT=my-project`.
-
-## Additional environment variables
-A few additional environment variables are supported which do not belong to an option/argument. These are:
-
-1. `KLUCTL_REGISTRY__HOST`, `KLUCTL_REGISTRY__USERNAME`, and so on. See [registries](./images.md#supported-image-registries-and-authentication) for details.
-2. `KLUCTL_SSH_DISABLE_STRICT_HOST_KEY_CHECKING`. Disable ssh host key checking when accessing git repositories.
-3. `KLUCTL_NO_THREADS`. Do not use multithreading while performing work. This is only useful for debugging purposes.
-4. `KLUCTL_IGNORE_DEBUGGER`. Pretend that there is no debugger attached when automatically deciding if multi-threading should be enabled or not.
-
-
-# Commands
-The following commands are available:
-
-## deploy
-
-Usage: kluctl deploy
-
-
-
-The following sets of arguments are available:
-1. [project arguments](#project-arguments)
-1. [image arguments](#image-arguments)
-1. [inclusion/exclusion arguments](#inclusionexclusion-arguments)
-
-In addition, the following arguments are available:
-
-```
-Misc arguments:
-  Command specific arguments.
-
-  -y, --yes                         Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'.
-      --dry-run                     Performs all kubernetes API calls in dry-run mode.
-      --force-apply                 Force conflict resolution when applying. See documentation for details
-      --replace-on-error            When patching an object fails, try to replace it. See documentation for more
-                                    details.
-      --force-replace-on-error      Same as --replace-on-error, but also try to delete and re-create objects. See
-                                    documentation for more details.
-      --abort-on-error              Abort deploying when an error occurs instead of trying the remaining deployments
-      --hook-timeout=5m             Maximum time to wait for hook readiness. The timeout is meant per-hook. Timeouts are
-                                    in the duration format (1s, 1m, 1h, ...). If not specified, a default timeout of 5m
-                                    is used.
-  -o, --output=OUTPUT,...           Specify output target file. Can be specified multiple times
-      --render-output-dir=STRING    Specifies the target directory to render the project into. If omitted, a temporary
-                                    directory is used.
-      --no-wait                     Don't wait for objects readiness'
-
-```
-
-
-### --parallel
-kluctl runs deployments sequentially and in-order by default. This options allows kluctl to perform all deployments
-in parallel, which speeds up the deployment significantly.
-
-Due to the nature of parallel deployments, no guarantees can't be made in regard to deployment order. This means for
-example, that objects that are meant to be deployed into a namespace might be deployed before the namespace is deployed,
-resulting in failure.
-
-### --force-apply
-kluctl implements deployments via [server-side apply](https://kubernetes.io/docs/reference/using-api/server-side-apply/)
-and a custom automatic conflict resolution algorithm. This algurithm is an automatic implementation of the
-"[Don't overwrite value, give up management claim](https://kubernetes.io/docs/reference/using-api/server-side-apply/#conflicts)"
-method. It should work in most cases, but might still fail. In case of such failure, you can use `--force-apply` to
-use the "Overwrite value, become sole manager" strategy instead.
-
-Please note that this is a risky operation which might overwrite fields which were initially managed by kluctl but were
-then overtaken by other managers (e.g. by operators). Always use this option with caution and perform a dry-run
-before to ensure nothing unexpected gets overwritten.
-
-### --replace-on-error
-In some situations, updating Kubernetes objects is not possible, for example when modified fields are read-only. Jobs
-are a good example where this might be the case. In such cases, you can use `--replace-on-error` to instruct kluctl to
-retry an update by deleting and then recreating the object.
-
-Please note that this is a potentially risky operation, especially when an object carries some kind of important state.
-
-### --abort-on-error
-kluctl does not abort a command when an individual object fails can not be updated. It collects all errors and warnings
-and outputs them instead. This option modifies the behaviour to immediately abort the command.
-
-## diff
-
-Usage: kluctl diff
-
-
-
-The following sets of arguments are available:
-1. [project arguments](#project-arguments)
-1. [image arguments](#image-arguments)
-1. [inclusion/exclusion arguments](#inclusionexclusion-arguments)
-
-In addition, the following arguments are available:
-
-```
-Misc arguments:
-  Command specific arguments.
-
-      --force-apply                        Force conflict resolution when applying. See documentation for details
-      --replace-on-error                   When patching an object fails, try to replace it. See documentation for more
-                                           details.
-      --force-replace-on-error             Same as --replace-on-error, but also try to delete and re-create objects. See
-                                           documentation for more details.
-      --ignore-tags                        Ignores changes in tags when diffing
-      --ignore-labels                      Ignores changes in labels when diffing
-      --ignore-annotations                 Ignores changes in annotations when diffing
-  -o, --output-format=OUTPUT-FORMAT,...    Specify output format and target file, in the format 'format=path'. Format
-                                           can either be 'text' or 'yaml'. Can be specified multiple times. The actual
-                                           format for yaml is currently not documented and subject to change.
-      --render-output-dir=STRING           Specifies the target directory to render the project into. If omitted, a
-                                           temporary directory is used.
-
-```
-
-
-`--force-apply` and `--replace-on-error` have the same meaning as in [deploy](#deploy).
-
-## delete
-
-Usage: kluctl delete
-
-
-
-The following sets of arguments are available:
-1. [project arguments](#project-arguments)
-1. [image arguments](#image-arguments)
-1. [inclusion/exclusion arguments](#inclusionexclusion-arguments)
-
-In addition, the following arguments are available:
-
-```
-Misc arguments:
-  Command specific arguments.
-
-  -y, --yes                                    Suppresses 'Are you sure?' questions and proceeds as if you would answer
-                                               'yes'.
-      --dry-run                                Performs all kubernetes API calls in dry-run mode.
-  -o, --output-format=OUTPUT-FORMAT,...        Specify output format and target file, in the format 'format=path'.
-                                               Format can either be 'text' or 'yaml'. Can be specified multiple times.
-                                               The actual format for yaml is currently not documented and subject to
-                                               change.
-  -l, --delete-by-label=DELETE-BY-LABEL,...    Override the labels used to find objects for deletion.
-
-```
-
-
-They have the same meaning as described in [deploy](#deploy).
-
-## prune
-
-Usage: kluctl prune
-
-
-
-The following sets of arguments are available:
-1. [project arguments](#project-arguments)
-1. [image arguments](#image-arguments)
-1. [inclusion/exclusion arguments](#inclusionexclusion-arguments)
-
-In addition, the following arguments are available:
-
-```
-Misc arguments:
-  Command specific arguments.
-
-  -y, --yes                                Suppresses 'Are you sure?' questions and proceeds as if you would answer
-                                           'yes'.
-      --dry-run                            Performs all kubernetes API calls in dry-run mode.
-  -o, --output-format=OUTPUT-FORMAT,...    Specify output format and target file, in the format 'format=path'. Format
-                                           can either be 'text' or 'yaml'. Can be specified multiple times. The actual
-                                           format for yaml is currently not documented and subject to change.
-
-```
-
-
-They have the same meaning as described in [deploy](#deploy).
-
-## list-images
-
-Usage: kluctl list-images
-
-
-
-The following sets of arguments are available:
-1. [project arguments](#project-arguments)
-1. [image arguments](#image-arguments)
-1. [inclusion/exclusion arguments](#inclusionexclusion-arguments)
-
-In addition, the following arguments are available:
-
-```
-Misc arguments:
-  Command specific arguments.
-
-  -o, --output=OUTPUT,...    Specify output target file. Can be specified multiple times
-      --simple               Output a simplified version of the images list
-
-```
-
-
-## poke-images
-
-Usage: kluctl poke-images
-
-
-
-The following sets of arguments are available:
-1. [project arguments](#project-arguments)
-1. [image arguments](#image-arguments)
-1. [inclusion/exclusion arguments](#inclusionexclusion-arguments)
-
-In addition, the following arguments are available:
-
-```
-Misc arguments:
-  Command specific arguments.
-
-  -y, --yes                         Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'.
-      --dry-run                     Performs all kubernetes API calls in dry-run mode.
-  -o, --output=OUTPUT,...           Specify output target file. Can be specified multiple times
-      --render-output-dir=STRING    Specifies the target directory to render the project into. If omitted, a temporary
-                                    directory is used.
-
-```
-
-
-## downscale
-
-Usage: kluctl downscale
-
-
-
-The following sets of arguments are available:
-1. [project arguments](#project-arguments)
-1. [image arguments](#image-arguments)
-1. [inclusion/exclusion arguments](#inclusionexclusion-arguments)
-
-In addition, the following arguments are available:
-
-```
-Misc arguments:
-  Command specific arguments.
-
-  -y, --yes                                Suppresses 'Are you sure?' questions and proceeds as if you would answer
-                                           'yes'.
-      --dry-run                            Performs all kubernetes API calls in dry-run mode.
-  -o, --output-format=OUTPUT-FORMAT,...    Specify output format and target file, in the format 'format=path'. Format
-                                           can either be 'text' or 'yaml'. Can be specified multiple times. The actual
-                                           format for yaml is currently not documented and subject to change.
-      --render-output-dir=STRING           Specifies the target directory to render the project into. If omitted, a
-                                           temporary directory is used.
-
-```
-
-
-## render
-
-Usage: kluctl render
-
-
-
-The following sets of arguments are available:
-1. [project arguments](#project-arguments)
-1. [image arguments](#image-arguments)
-
-In addition, the following arguments are available:
-
-```
-Misc arguments:
-  Command specific arguments.
-
-  --render-output-dir=STRING    Specifies the target directory to render the project into. If omitted, a temporary
-                                directory is used.
-
-```
-
-
-## validate
-
-Usage: kluctl validate
-
-
-
-The following sets of arguments are available:
-1. [project arguments](#project-arguments)
-1. [image arguments](#image-arguments)
-
-In addition, the following arguments are available:
-
-```
-Misc arguments:
-  Command specific arguments.
-
-  -o, --output=OUTPUT,...           Specify output target file. Can be specified multiple times
-      --render-output-dir=STRING    Specifies the target directory to render the project into. If omitted, a temporary
-                                    directory is used.
-      --wait=DURATION               Wait for the given amount of time until the deployment validates
-      --sleep=5s                    Sleep duration between validation attempts
-      --warnings-as-errors          Consider warnings as failures
-
-```
-
-
-## seal
-
-Usage: kluctl seal
-
-
-
-See [sealed-secrets](./sealed-secrets.md) for more details.
-
-The following sets of arguments are available:
-1. [project arguments](#project-arguments) (except `-a`)
-
-In addition, the following arguments are available:
-
-```
-Misc arguments:
-  Command specific arguments.
-
-  --secrets-dir=STRING    Specifies where to find unencrypted secret files. The given directory is NOT meant to be part
-                          of your source repository! The given path only matters for secrets of type 'path'. Defaults to
-                          the current working directory.
-  --force-reseal          Lets kluctl ignore secret hashes found in already sealed secrets and thus forces resealing of
-                          those.
-
-```
-
-
-## helm-pull
-
-Usage: kluctl helm-pull
-
-
-
-See [helm-integration](./helm-integration.md) for more details.
-
-The following sets of arguments are available:
-1. [project arguments](#project-arguments) (except `-a`)
-
-## helm-update
-
-Usage: kluctl helm-update
-
-
-
-The following sets of arguments are available:
-1. [project arguments](#project-arguments) (except `-a`)
-
-In addition, the following arguments are available:
-
-```
-Misc arguments:
-  Command specific arguments.
-
-  --upgrade    Write new versions into helm-chart.yml and perform helm-pull afterwards
-  --commit     Create a git commit for every updated chart
-
-```
-
-
-## list-targets
-
-Usage: kluctl list-targets
-
-
-
-The following arguments are available:
-
-```
-Misc arguments:
-  Command specific arguments.
-
-  -o, --output=OUTPUT,...    Specify output target file. Can be specified multiple times
-
-```
-
-
-## archive
-
-Usage: kluctl archive
-
-
-
-The following arguments are available:
-
-```
-Misc arguments:
-  Command specific arguments.
-
-  --output-archive=STRING     Path to .tgz to write project to.
-  --output-metadata=STRING    Path to .yml to write metadata to. If not specified, metadata is written into the archive.
-
-```
-
diff --git a/docs/deployments.md b/docs/deployments.md
deleted file mode 100644
index 819ac562b..000000000
--- a/docs/deployments.md
+++ /dev/null
@@ -1,340 +0,0 @@
-# Deployment projects
-
-A deployment project defines all deployments, resources and configuration required to deploy our application and/or
-base infrastructure. It consists of multiple yaml files interpreted by kluctl and other resources interpreted by
-external tools (e.g. Helm or Kustomize).
-
-## Basic structure
-
-The following visualization shows the basic structure of a deployment project. The entry point of every deployment
-project is the `deployment.yml` file, which then includes further sub-deployments and kustomize deployments. It also
-provides some additional configuration required for multiple kluctl features to work as expected.
-
-As can be seen, sub-deployments can include other sub-deployments, allowing you to structure the deployment project
-as you need. You can for example use this to group persistency related deployments and non-persistent deployments.
-
-Each level in this structure recursively adds [tags](./tags.md) to each deployed resources, allowing you to control
-precisely what is deployed in the future.
-
-Some visualized files/directories have links attached, follow them to get more information.
-
-
--- project-dir/
-   |-- deployment.yml
-   |-- .gitignore
-   |-- kustomize-deployment1/
-   |   |-- kustomization.yml
-   |   `-- resource.yml
-   |-- sub-deployment/
-   |   |-- deployment.yml
-   |   |-- kustomize-deployment2/
-   |   |   |-- kustomization.yml
-   |   |   |-- resource1.yml
-   |   |   `-- ...
-   |   |-- kustomize-deployment3/
-   |   |   |-- kustomization.yml
-   |   |   |-- resource1.yml
-   |   |   |-- resource2.yml.jinja2
-   |   |   |-- patch1.yml
-   |   |   `-- ...
-   |   |-- kustomize-with-helm-deployment/
-   |   |   |-- charts/
-   |   |   |   `-- ...
-   |   |   |-- kustomization.yml
-   |   |   |-- helm-chart.yml
-   |   |   `-- helm-values.yml
-   |   `-- subsub-deployment/
-   |       |-- deployment.yml
-   |       |-- ... kustomize deployments
-   |       `-- ... subsubsub deployments
-   `-- sub-deployment/
-       `-- ...
-
- -## Jinja2 Templating - -Every file that is below the deployment project directory is also considered a [Jinja2](https://palletsprojects.com/p/jinja/) -template. This means, that it is rendered by Jinja2 before it is interpreted by kluctl or sent to kubernetes. - -The only implicit exception is when Helm charts got pulled into the deployment project, as conflicts between Helms -templates and kluctl's templating would otherwise be guaranteed. - -In case you need to exclude one or more files from Jinja2 rendering/templating, use the property [templateExcludes](#templateexcludes) -inside `deployment.yml`. - -Documentation on available variables, methods and filters is available in [jinja2-templating](./jinja2-templating.md) - -## Container image versions - -Please read [images](./images.md) about dynamic image versions. - -## deployment.yml - -The `deployment.yml` file is the entrypoint for the deployment project. Included sub-deployments also provide a -`deployment.yml` file with the same structure as the initial one. - -An example `deployment.yml` looks like this: -```yaml -sealedSecrets: - outputPattern: "{{ cluster.name }}/{{ args.environment }}" - -deployments: -- path: nginx -- path: my-app -- include: monitoring - -commonLabels: - my.prefix/environment: "{{ args.environment }}" - my.prefix/deployment-project: my-deployment-project - -args: -- name: environment -``` - -The following sub-chapters describe the available properties/fields in the `deployment.yml` - -### sealedSecrets -`sealedSecrets` configures how sealed secrets are stored while sealing and located while rendering. -See [Sealed Secrets](./sealed-secrets.md#outputpattern-and-location-of-stored-sealed-secrets) for details. - -### deployments - -`deployments` is a list of deployment items. Multiple deployment types are supported, which is documented further down. -Individual deployments are performed in parallel, unless a [barrier](#barriers) is encountered which causes kluctl to -wait for all previous deployments to finish. - -#### [kustomize](https://kustomize.io/) deployments - -Specifies a [kustomize](https://kustomize.io/) deployment. -Please see [Kustomize integration](./kustomize-integration.md) for more details. - -Example: -```yaml -deployments: -- path: path/to/deployment1 -- path: path/to/deployment2 - waitReadiness: true -``` - -The `path` must point to a directory relative to the directory containing the `deployment.yml`. Only directories -that are part of the kluctl project are allowed. The directory must contain a valid `kustomization.yml`. - -`waitReadiness` is optional and if set to `true` instructs kluctl to wait for readiness of each individual object -of the kustomize deployment. Readiness is defined the same as in [hook readiness](./hooks.md#hook-readiness). - -#### Includes - -Specifies a sub-deployment project to be included. The included sub-deployment project will inherit many properties -of the parent project, e.g. tags, commonLabels and so on. - -Example: -```yaml -deployments: -- include: path/to/sub-deploment -``` - -The `path` must point to a directory relative to the directory containing the `deployment.yml`. Only directories -that are part of the kluctl project are allowed. The directory must contain a valid `deployment.yml`. - -#### Barriers -Causes kluctl to wait until all previous kustomize deployments have been applied. This is useful when -upcoming deployments need the current or previous deployments to be finished beforehand. Previous deployments also -include all sub-deployments from included deployments. - -Example: -```yaml -deployments: -- path: kustomizeDeployment1 -- path: kustomizeDeployment2 -- include: subDeployment1 -- barrier: true -# At this point, it's ensured that kustomizeDeployment1, kustomizeDeployment2 and all sub-deployments from -# subDeployment1 are fully deployed. -- path: kustomizeDeployment3 -``` - -### deployments common properties -All entries in `deployments` can have the following common properties: - -#### vars (deployment item) -A list of additional sets of variables to be added to the Jinja2 context the corresponding (and sub-deployments in case -of includes). - -See [jinja2-templating](./jinja2-templating.md#vars-from-deploymentyml) for more details. - -Example: -```yaml -deployments: -- path: kustomizeDeployment1 - vars: - - file: vars1.yml - - values: - var1: value1 -- path: kustomizeDeployment2 -# all sub-deployments of this include will have the given variables available in their Jinj2 context. -- include: subDeployment1 - vars: - - file: vars2.yml -``` - -#### tags (deployment item) -A list of tags the deployment should have. See [tags](./tags.md) for more details. For includes, this means that all -sub-deployments will get these tags applied to. If not specified, the default tags logic as described in [tags](./tags.md) -is applied. - -Example: - -```yaml -deployments: -- path: kustomizeDeployment1 - tags: - - tag1 - - tag2 -- path: kustomizeDeployment2 - tags: - - tag3 -# all sub-deployments of this include will get tag4 applied -- include: subDeployment1 - tags: - - tag4 -``` - -#### alwaysDeploy -Forces a deployment to be included everytime, ignoring inclusion/exclusion sets from the command line. -See [Deploying with tag inclusion/exclusion](./tags.md#deploying-with-tag-inclusionexclusion) for details. - -```yaml -deployments: -- path: kustomizeDeployment1 - alwaysDeploy: true -- path: kustomizeDeployment2 -``` - -#### skipDeleteIfTags -Forces exclusion of a deployment whenever inclusion/exclusion tags are specified via command line. -See [Deleting with tag inclusion/exclusion](./tags.md#deleting-with-tag-inclusionexclusion) for details. - -```yaml -deployments: -- path: kustomizeDeployment1 - skipDeleteIfTags: true -- path: kustomizeDeployment2 -``` - -### vars (deployment project) -A list of additional sets of variables to be added to the Jinja2 context of all deployments (and sub-deployment) of the -current deployment project. - -See [jinja2-templating](./jinja2-templating.md#vars-from-deploymentyml) for more details. - -### commonLabels -A dictionary of [labels](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/) and values to be -added to all resources deployed by any of the kustomize deployments in this deployment project. - -This feature is mainly meant to make it possible to identify all objects in a kubernetes cluster that were once deployed -through a specific deployment project. - -Consider the following example `deployment.yml`: -```yaml -deployments: - - path: nginx - - include: sub-deployment1 - -commonLabels: - my.prefix/deployment-name: my-deployment-project-name - my.prefix/environment-name: {{ args.environment }} - my.prefix/label-1: value-1 - my.prefix/label-2: value-2 -``` - -Every resource deployed by the kustomize deployment `nginx` will now get the two provided labels attached. All included -sub-deployment projects (e.g. `sub-deployment1`) will also recursively inherit these labels and pass them to further -down. - -In case an included sub-deployment project also contains `commonLabels`, both dictionaries of common labels are merged -inside the included sub-deployment project. In case of conflicts, the included common labels override the inherited. - -The root deployment's `commonLabels` is also used to identify objects to be deleted when performing `kluctl delete` -or `kluctl prune` operations - -Please note that these `commonLabels` are not related to `commonLabels` supported in `kustomization.yml` files. It was -decided to not rely on this feature but instead attach labels manually to resources right before sending them to -kubernetes. This is due to an [implementation detail](https://github.com/kubernetes-sigs/kustomize/issues/1009) in -kustomize which causes `commonLabels` to also be applied to label selectors, which makes otherwise editable resources -read-only when it comes to `commonLabels`. - -### overrideNamespace -A string that is used as the default namespace for all kustomize deployments which don't have a `namespace` set in their -`kustomization.yml`. - -### tags (deployment project) -A list of common tags which are applied to all kustomize deployments and sub-deployment includes. - -See [tags](./tags.md) for more details. - -### args -A list of arguments that can or must be passed to most kluctl operations. Each of these arguments is then available -in Jinja2 templating via the global `args` object. Only the root `deployment.yml` can contain such argument definitions. - -An example looks like this: -```yaml -deployments: - - path: nginx - -args: - - name: environment - - name: enable_debug - default: "false" -``` - -These arguments can then be used in templating, e.g. by using `{{ args.environment }}`. - -When calling kluctl, most of the commands will then require you to specify at least `-a environment=xxx` and optionally -`-a enable_debug=true` - -The following sub chapters describe the fields for argument entries. - -#### name -The name of the argument. - -#### default -If specified, the argument becomes optional and will use the given value as default when not specified. - -### templateExcludes -A list of file patterns to exclude from Jinja2 rendering/templating. This is important if you encounter issues with -resources containing sequences of characters that are misinterpreted by Jinja2. An example would be a configuration file -that includes Go templates, which will in most cases make Jinja2 templating fail. - -Recursive patterns can be specified with double-wildcards, e.g.: -```yaml -templateExcludes: - - path/to/excludes/** -``` - -### ignoreForDiff - -A list of objects and fields to ignore while performing diffs. Consider the following example: - -```yaml -deployments: - - ... - -ignoreForDiff: - - group: apps - kind: Deployment - namespace: my-namespace - name: my-deployment - fieldPath: spec.replicas -``` - -This will remove the `spec.replicas` field from every resource that matches the object. -`group`, `kind`, `namespace` and `name` can be omitted, which results in all objects matching. `fieldPath` must be a -valid [JSON Path](https://goessner.net/articles/JsonPath/). `fieldPath` may also be a list of JSON paths. - -The JSON Path implementation used in kluctl has extended support for wildcards in field -names, allowing you to also specify paths like `metadata.labels.my-prefix-*`. - -# Order of deployment -Deployments are done in parallel, meaning that there are usually no order guarantees. The only way to somehow control -order, is by placing [barriers](#barrier) between kustomize deployments. You should however not overuse barriers, as -they negatively impact the speed of kluctl. diff --git a/docs/getting-started.md b/docs/getting-started.md deleted file mode 100644 index 5ace95a26..000000000 --- a/docs/getting-started.md +++ /dev/null @@ -1,44 +0,0 @@ -# Getting started - -This is a short documentation on how to get started before actually deploying your own infrastructure and/or applications. - -## Additional tools needed - -You need to install a set of command line tools to fully use kluctl. These are: - -1. [helm](https://github.com/helm/helm/releases)
- Download the binaries for your system, make them executable and make them globally available - (modify your PATH or copy it into /usr/local/bin) - -All of these tools must be in your PATH, so that kluctl can easily invoke them. - -## Get a Kubernetes cluster - -The first step is of course: You need a kubernetes cluster. It doesn't really matter where this cluster is hosted, if -it's a managed cluster, or a self-hosted cluster, kops or kubespray based, AWS, GCE, Azure, ... and so on. kluctl -is completely independent of how Kubernetes is deployed and where it is hosted. - -There is however a minimum Kubernetes version that must be met: 1.20.0. This is due to the heavy use of server-side apply -which was not stable enough in older versions of Kubernetes. - -## Prepare your kubeconfig - -Your local kubeconfig should be configured to have access to the Kubernetes cluster via a dedicated context. The context -name should match with the name that you want to use for the cluster from now on. Let's assume the name is `test.example.com`, -then you'd have to ensure that the kubeconfig context `test.example.com` is correctly pointing and authorized for this -cluster. - -See [Configure Access to Multiple Clusters](https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/) for documentation -on how to manage multiple clusters with a single kubeconfig. Depending on the Kubernets provisioning/deployment tooling -you used, you might also be able to directly export the context into your local kubeconfig. For example, -[kops](https://github.com/kubernetes/kops/blob/master/docs/cli/kops_export.md) is able to export and merge the kubeconfig -for a given cluster. - -## Example projects - -Now you can play around with the projects within the `examples` folder. In order to have fun with a very simple example, just install [kind](https://kind.sigs.k8s.io/), create a [cluster](https://kind.sigs.k8s.io/docs/user/quick-start/#creating-a-cluster) and run the following commands: -``` -cd examples/simple -kluctl diff --target dev -kluctl deploy --target dev -``` \ No newline at end of file diff --git a/docs/helm-integration.md b/docs/helm-integration.md deleted file mode 100644 index 9b7075605..000000000 --- a/docs/helm-integration.md +++ /dev/null @@ -1,130 +0,0 @@ -# Helm Integration - -kluctl offers a simple-to-use Helm integration, which allows you to reuse many common third-party Helm Charts. - -The integration is split into 2 parts/steps/layers. The first is the management and pulling of the Helm Charts, while -the second part handles configuration/customization and deployment of the chart. - -## How it works - -Helm charts are not directly deployed via Helm. Instead, kluctl renders the Helm Chart into a single file and then -hands over the rendered yaml to [kustomize](https://kustomize.io/). Rendering is done in combination with a provided -`helm-values.yml`, which contains the necessary values to configure the Helm Chart. - -The resulting rendered yaml is then referred by your `kustomization.yml`, from which point on the -[kustomize integration](kustomize-integration.md) takes over. This means, that you can perform all desired -customization (patches, namespace override, ...) as if you provided your own resources via yaml files. - -### Helm hooks - -[Helm Hooks](https://helm.sh/docs/topics/charts_hooks/) are implemented by mapping them to [kluctl hooks](./hooks.md), -based on the following mapping table: - -| Helm hook | kluctl hook | -|---------------|---------------------| -| pre-install | pre-deploy-initial | -| post-install | post-deploy-initial | -| pre-delete | Not supported | -| post-delete | Not supported | -| pre-upgrade | pre-deploy-upgrade | -| post-upgrade | post-deploy-upgrade | -| pre-rollback | Not supported | -| post-rollback | Not supported | -| test | Not supported | - -Please note that this is a best effort approach and not 100% compatible to how Helm would run hooks. - -## helm-chart.yml - -The `helm-chart.yml` defines where to get the chart from, which version should be pulled, the rendered output file name, -and a few more Helm options. After this file is added to your project, you need to invoke the `helm-pull` command -to pull the Helm Chart into your local project. It is advised to put the pulled Helm Chart into version control, so -that deployments will always be based on the exact same Chart (Helm does not guarantee this when pulling). - -Example `helm-chart.yml`: - -```yaml -helmChart: - repo: https://charts.bitnami.com/bitnami - chartName: redis - chartVersion: 12.1.1 - skipUpdate: false - releaseName: redis-cache - namespace: "{{ my.jinja2.var }}" - output: deploy.yml -``` - -When running the `helm-pull` command, it will recursively search for all `helm-chart.yml` files and then pull the -chart from the specified repository with the specified version. The pull chart will then be located in the sub-directory -`charts` below the same directory as the `helm-chart.yml` - -The same filename that was specified in `output` must then be referred in a `kustomization.yml` as a normal local -resource. - -`helmChart` inside `helm-chart.yml` supports the following fields: - -### repo -The url to the Helm repository where the Helm Chart is located. You can use hub.helm.sh to search for repositories and -charts and then use the repos found there. - -oci based repositories are also supported, for example: -```yaml -helmChart: - repo: oci://r.myreg.io/mycharts/pepper - chartName: pepper - chartVersion: 1.2.3 - releaseName: pepper - namespace: pepper - output: deploy.yml -``` - -### chartName -The name of the chart that can be found in the repository. - -### chartVersion -The version of the chart. - -### skipUpdate -Skip this Helm Chart when the [helm-update](./commands.md#helm-update) command is called. If omitted, defaults to `false`. - -### releaseName -The name of the Helm Release. - -### namespace -The namespace that this Helm Chart is going to be deployed to. Please note that this should match the namespace -that you're actually deploying the kustomize deployment to. This means, that either `namespace` in `kustomization.yml` -or `overrideNamespace` in `deployment.yml` should match the namespace given here. The namespace should also be existing -already at the point in time when the kustomize deployment is deployed. - -### output -This is the file name into which the Helm Chart is rendered into. Your `kustomization.yml` should include this same -file. The file should not be existing in your project, as it is created on-the-fly while deploying. - -### skipCRDs -If set to `true`, kluctl will pass `--skip-crds` to Helm when rendering the deployment. If set to `false` (which is -the default), kluctl will pass `--include-crds` to Helm. - -## helm-values.yml -This file should be present when you need to pass custom Helm Value to Helm while rendering the deployment. Please -read the documentation of the used Helm Charts for details on what is supported. - -## Updates to helm-charts -In case a Helm Chart needs to be updated, you can either do this manually by replacing the [chartVersion](#chartversion) -value in `helm-chart.yml` and the calling the [helm-pull](./commands.md#helm-pull) command or by simply invoking -[helm-update](./commands.md#helm-update) with `--upgrade` and/or `--commit` being set. - -## Jinja2 Templating - -Both `helm-chart.yml` and `helm-values.yml` are rendered by the [Jinaj2 templating](./jinja2-templating.md) before they -are actually used. This means, that you can use all available Jinja2 variables at that point, which can for example be -seen in the above `helm-chart.yml` example for the namespace. - -There are however one exception that leads to a small limitation. When `helm-pull` reads the `helm-chart.yml`, it does -NOT render the file via Jinja2. This is because it can not know how to properly render Jinja2 as it does have no -information about the targeted environment (there are no `-a` arguments set) at that point. - -This exception leads to the limitation that the `helm-chart.yml` MUST be valid yaml even in case it is not rendered -via Jinja2. This makes using control statements (if/for/...) impossible in this file. It also makes it a requirement -to use quotes around values that contain templates (e.g. the namespace in the above example). - -`helm-values.yml` is not subject to these limitations as it is only interpreted while deploying. diff --git a/docs/hooks.md b/docs/hooks.md deleted file mode 100644 index 0cea73244..000000000 --- a/docs/hooks.md +++ /dev/null @@ -1,43 +0,0 @@ -# Hooks - -kluctl supports hooks in a similar fashion as known from Helm Charts. Hooks are executed/deployed before and/or after the -actual deployment of a kustomize deployment. - -To mark a resource as a hook, add the `kluctl.io/hook` annotation to a resource. The value of the annotation must be -a comma separated list of hook names. Possible value are described in the next chapter. - -## Hook types - -| Hook Type | Description | -|---|---| -| pre-deploy-initial | Executed right before the initial deployment is performed. | -| post-deploy-initial | Executed right after the initial deployment is performed. | -| pre-deploy-upgrade | Executed right before a non-initial deployment is performed. | -| post-deploy-upgrade | Executed right after a non-initial deployment is performed. | -| pre-deploy | Executed right before any (initial and non-initial) deployment is performed.| -| post-deploy | Executed right after any (initial and non-initial) deployment is performed. | - -A deployment is considered to be an "initial" deployment if none of the resources related to the current kustomize -deployment are found on the cluster at the time of deployment. - -If you need to execute hooks for every deployment, independent of its "initial" state, use -`pre-deploy-initial,pre-deploy` to indicate that it should be executed all the time. - -## Hook deletion - -Hook resources are by default deleted right before creation (if they already existed before). This behavior can be -changed by setting the `kluctl.io/hook-delete-policy` to a comma separated list of the following values: - -| Policy | Description | -|---|---| -| before-hook-creation | The default behavior, which means that the hook resource is deleted right before (re-)creation. | -| hook-succeeded | Delete the hook resource directly after it got "ready" | -| hook-failed | Delete the hook resource when it failed to get "ready" | - -## Hook readiness - -After each deployment/execution of the hooks that belong to a deployment stage (before/after deployment), kluctl -waits for the hook resources to become "ready". Readiness depends on the resource kind, e.g. for a Job, kluctl would -wait until it finishes successfully. - -It is possible to disable waiting for hook readiness by setting the annotation `kluctl.io/hook-wait` to "false". diff --git a/docs/images.md b/docs/images.md deleted file mode 100644 index a6981def8..000000000 --- a/docs/images.md +++ /dev/null @@ -1,129 +0,0 @@ -# Image versions/tags - -There are usually 2 different scenarios where Container Images need to be specified: -1. When deploying third party applications like nginx, redis, ... (e.g. via the [Helm integration](./helm-integration.md)).
- * In this case, image versions/tags rarely change, and if they do, this is an explicit change to the deployment. -1. When deploying your own applications.
- * In this case, image versions/tags might change very rapidly, sometimes multiple times per hour. It would be too much - effort and overhead when this would be managed explicitly via your deployment. Even with Jinja2 templating, this - would be hard to maintain. - -kluctl offers a better solution for the second case. - -## Dynamic versions/tags - -kluctl is able to ask the used container registry for a list of tags/versions available for an image. It then can -sort the list of images via a configurable order and then use the latest image for your deployment. - -It however only does this when the involved resource (e.g. a `Deployment` or `StatefulSet`) is not yet deployed. In case -it is already deployed, the already deployed image will be reused to avoid undesired re-deployment/re-starting of -otherwise unchanged resources. - -## images.get_image() - -This is solved via a Jinja2 function that is available in all templates/resources. The function is part of the global -`images` object and expects the following arguments: - -`images.get_image(image, latest_version)` - -* image - * The image location, excluding the tag. Please see [supported image registries](#supported-image-registries-and-authentication) to - understand which registries are supported.` -* latest_version - * Configures how tags/versions are sorted and thus how the latest image is determined. Can be: - * `version.semver()`
- Filters and sorts by loose semantic versioning. Versions must start with a number. It allows unlimited - `.` inside the version. It treats versions with a suffix as less then versions without a suffix - (e.g. 1.0-rc1 < 1.0). Two versions which only differ by suffix are sorted semantically. - * `version.prefix(prefix)`
- Only allows tags with the given prefix and then applies the same logic as images.semver() to whatever - follows right after the prefix. You can override the handling of the right part by providing `suffix=xxx`, - while `xxx` is another version filter, e.g. `version.prefix("master-", suffix=version.number()) - * `version.number()`
- Only allows plain numbers as version numbers sorts them accordingly. - * `version.regex(regex)`
- Only allows versions/tags that match the given regex. Sorting is done the same way as in version.semver(), - except that versions do not necessarily need to start with a number. - -The mentioned version filters can be specified either via native Python objects (Jinja is mostly using python for -expressions) or via strings. For example, - -`images.get_version("my-image", version.prefix("master-", suffix=version.number()))` - -is the same as - -`images.get_version("my-image", "prefix('master-', suffix=number())")`. - -If no version_filter is specified, then it defaults to `version.semver()`. - -Example deployment: - -```yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: my-deployment -spec: - template: - spec: - containers: - - name: c1 - image: "{{ images.get_image('registry.gitlab.com/my-group/my-project') }}" -``` - -## Always using the latest images -If you want to use the latest image no matter if an older version is already deployed, use the `-u` flag to your -`deploy`, `diff` or `list-images` commands. - -You can restrict updating to individual images by using `-I/--include-tag`. This is useful when using CI/CD for example, -where you often want to perform a deployment that only updates a single application/service from your large deployment. - -## Fixed images via CLI -The described `images.get_image` logic however leads to a loosely defined state on your target cluster/environment. This -might be fine in a CI/CD environment, but might be undesired when deploying to production. In that case, it might be -desirable to explicitly define which versions need to be deployed. - -To achieve this, you can use the `-F FIXED_IMAGE` [argument](./commands.md#image-arguments). -`FIXED_IMAGE` must be in the form of `-F image<:namespace:deployment:container>=result`. For example, to pin the image -`registry.gitlab.com/my-group/my-project` to the tag `1.1.2` you'd have to specify -`-F registry.gitlab.com/my-group/my-project=registry.gitlab.com/my-group/my-project:1.1.2`. - -## Fixed images via a yaml file - -As an alternative to specifying each fixed image via CLI (`--fixed-images-file=`), you can also specify a single -yaml file via CLI which then contains a list of entries that define image/deployment -> imageResult mappings. - -An example fixed-images files looks like this: - -```yaml -images: - - image: registry.gitlab.com/my-group/my-project - resultImage: registry.gitlab.com/my-group/my-project:1.1.0 - - image: registry.gitlab.com/my-group/my-project2 - resultImage: registry.gitlab.com/my-group/my-project2:2.0.0 - - deployment: StatefulSet/my-sts - resultImage: registry.gitlab.com/my-group/my-project3:1.0.0 -``` - -You can also take an existing deployment and export the already deployed image versions into a fixed-images file by -using the `list-images` command. It will produce a compatible fixed-images file based on the calls to -`images.get_image` if a deployment would be performed with the given arguments. The result of that call is quite -expressive, as it contains all the information gathered while images were collected. Use `--simple` to only return -a list with image -> resultImage mappings. - -## Supported image registries and authentication -All [v2 API](https://docs.docker.com/registry/spec/api/) based image registries are supported, including the Docker Hub, -Gitlab, and many more. Private registries will need credentials to be setup correctly. This can be done by locally -logging in via `docker login ` or by specifying the following environment variables: - -Simply set the following environment variables to pass credentials to you private repository: -1. KLUCTL_REGISTRY_HOST=registry.example.com -2. KLUCTL_REGISTRY_USERNAME=username -3. KLUCTL_REGISTRY_PASSWORD=password - -You can also pass credentials for more registries by adding an index to the environment variables, -e.g. "KLUCTL_REGISTRY_1_HOST=registry.gitlab.com" - -In case your registry uses self-signed TLS certificates, it is currently required to disable TLS verification for these. -You can do this via `KLUCTL_REGISTRY_TLSVERIFY=1`/`KLUCTL_REGISTRY__TLSVERIFY=1` for the corresponding -`KLUCTL_REGISTRY_HOST`/`KLUCTL_REGISTRY__HOST` or by globally disabling it via `KLUCTL_REGISTRY_DEFAULT_TLSVERIFY=1`. diff --git a/docs/jinja2-templating.md b/docs/jinja2-templating.md deleted file mode 100644 index 80cdd4097..000000000 --- a/docs/jinja2-templating.md +++ /dev/null @@ -1,283 +0,0 @@ -# Jinja2 Templating - -kluctl uses a Jinja2 Templating engine to pre-process/render every involved configuration file and resource before -actually interpreting it. Only files that are explicitly excluded via [templateExcludes](./deployments.md#templateexcludes) -are not rendered via Jinja2. - -Generally, everything that is possible with Jinja2 is possible in kluctl configuration/resources. Please -read into the [Jinja2 documentation](https://jinja.palletsprojects.com/en/3.0.x/templates/) to understand what exactly -is possible and how to use it. - -## vars from deployment.yml - -There are multiple places in deployment projects (deployment.yml) where additional variables can be loaded into -future Jinja2 contexts. - -The first place where vars can be specified is the deployment root, as documented [here](./deployments.md#vars-deployment-project). -These vars are visible for all deployments inside the deployment project, including sub-deployments from includes. - -The second place to specify variables is in the deployment items, as documented [here](./deployments.md#vars-deployment-item). - -The variables loaded for each entry in `vars` are not available inside the `deployment.yml` file itself. -However, each entry in `vars` can use all variables defined before that specific entry is processed. Consider the -following example. - -```yaml -vars: -- file: vars1.yml -- file: vars2.yml -``` - -`vars2.yml` can now use variables that are defined in `vars1.yml`. At all times, variables defined by -parents of the current sub-deployment project can be used in the current vars file. - -Different types of vars entries are possible: - -### file -This loads variables from a yaml file. Assume the following yaml file with the name `vars1.yml`: -```yaml -my_vars: - a: 1 - b: "b" - c: - - l1 - - l2 -``` - -This file can be loaded via: - -```yaml -vars: - - file: vars1.yml -``` - -After which all included deployments and sub-deployments can use the jinja2 variables from `vars1.yml`. - -### values -An inline definition of variables. Example: - -```yaml -vars: - - values: - a: 1 - b: c -``` - -These variables can then be used in all deployments and sub-deployments. - -### clusterConfigMap -Loads a configmap from the target's cluster and loads the specified key's value as a yaml file into the jinja2 variables -context. - -Assume the following configmap to be deployed to the target cluster: -```yaml -apiVersion: v1 -kind: ConfigMap -metadata: - name: my-vars - namespace: my-namespace -data: - vars: | - a: 1 - b: "b" - c: - - l1 - - l2 -``` - -This configmap can be loaded via: - -```yaml -vars: - - clusterConfigMap: - name: my-vars - namespace: my-namespace - key: vars -``` - -It assumes that the configmap is already deployed before the kluctl deployment happens. This might for example be -useful to store meta information about the cluster itself and then make it available to kluctl deployments. - -### clusterSecret -Same as clusterConfigMap, but for secrets. - -## Global variables -There are multiple variables available which are pre-defined by kluctl. These are: - -### cluster -This is the cluster definition as found in the cluster yaml that belongs to the chosen target cluster. See -[cluster config](./cluster-config.md) for details on what this variable contains. - -### args -This is a dictionary of arguments given via command line. It contains every argument defined in -[deployment args](./deployments.md#args). - -### images -This global object provides the dynamic images features described in [images](./images.md). - -### version -This global object defines latest version filters for `images.get_image(...)`. See [images](./images.md) for details. - -### secrets -This global object is only available while [sealing](./sealed-secrets.md#jinja2-templating) and contains the loaded -secrets defined via the current sealing run. - -## Global filters -In addition to the [builtin Jinja2 filters](https://jinja.palletsprojects.com/en/2.11.x/templates/#list-of-builtin-filters), -kluctl provides a few additional filters: - -### b64encode -Encodes the input value as base64. Example: `{{ "test" | b64encode }}` will result in `dGVzdA==`. - -### b64decode -Decodes an input base64 encoded string. Example `{{ my.source.var | b64decode }}`. - -### from_yaml -Parses a yaml string and returns an object. Please note that json is valid yaml, meaning that you can also use this -filter to parse json. - -### to_yaml -Converts a variable/object into its yaml representation. Please note that in most cases the resulting string will not -be properly indented, which will require you to also use the `indent` filter. Example: - -```yaml -apiVersion: v1 -kind: ConfigMap -metadata: - name: my-config -data: - config.yaml: | - {{ my_config | to_yaml | indent(4) }} -``` - -### to_json -Same as `to_yaml`, but with json as output. Please note that json is always valid yaml, meaning that you can also use -`to_json` in yaml files. Consider the following example: - -```yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: my-deployment -spec: - template: - spec: - containers: - - name: c1 - image: my-image - env: {{ my_list_of_env_entries | to_json }} -``` - -This would render json into a yaml file, which is still a valid yaml file. Compare this to how this would have to be -solved with `to_yaml`: - -```yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: my-deployment -spec: - template: - spec: - containers: - - name: c1 - image: my-image - env: - {{ my_list_of_env_entries | to_yaml | indent(10) }} -``` - -The required indention filter is the part that makes this error-prone and hard to maintain. Consider using `to_json` -whenever you can. - -### render -Renders the input string with the current Jinja2 context. Example: -``` -{% set a="{{ my_var }}" %} -{{ a | render }} -``` - -## Global functions -In addition to the provided -[builtin global functions](https://jinja.palletsprojects.com/en/2.11.x/templates/#list-of-global-functions), -kluctl also provides a few global functions: - -### load_template(file) -Loads the given file into memory, renders it with the current Jinja2 context and then returns it as a string. Example: -``` -{% set a=load_template('file.yml') %} -{{ a }} -``` - -The filename given to `load_template` is treated as relative to the template that is currently rendered. - -### load_sha256(file, digest_len) -Loads the given file into memory, renders it and calculates the sha256 hash of the result. - -The filename given to `load_sha256` is treated the same as in `load_template`. Recursive loading/calculating of hashes -is allowed and is solved by replacing `load_sha256` invocations with currently loaded templates with dummy strings. -This also allows to calculate the hash of the currently rendered template, for example: - -``` -apiVersion: v1 -kind: ConfigMap -metadata: - name: my-config-{{ load_sha256("configmap.yml") }} -data: -``` - -`digest_len` is an optional parameter that allows to limit the length of the returned hex digest. - -### get_var(field_path, default) -Convenience method to navigate through the current context variables via a -[JSON Path](https://goessner.net/articles/JsonPath/). Let's assume you currently have these variables defines -(e.g. via [vars](./deployments.md#vars-deployment-project)): -```yaml -my: - deep: - var: value -``` -Then `{{ get_var('my.deep.var', 'my-default') }}` would return `value`. -When any of the elements inside the field path are non-existent, the given default value is returned instead. - -The `field_path` parameter can also be a list of pathes, which are then tried one after the another, returning the first -result that gives a value that is not None. For example, `{{ get_var(['non.existing.var', my.deep.var'], 'my-default') }}` -would also return `value`. - -### merge_dict(d1, d2) -Clones d1 and then recursively merges d2 into it and returns the result. Values inside d2 will override values in d1. - -### update_dict(d1, d2) -Same as `merge_dict`, but merging is performed in-place into d1. - -### raise(msg) -Raises a python exception with the given message. This causes the current command to abort. - -### debug_print(msg) -Prints a line to stderr. - -## Includes and imports -Standard Jinja2 [includes](https://jinja.palletsprojects.com/en/2.11.x/templates/#include) and -[imports](https://jinja.palletsprojects.com/en/2.11.x/templates/#import) can be used in all templates. - -The path given to include/import is treated as relative to the template that is currently rendered. - -## Macros - -[Jinja2 macros](https://jinja.palletsprojects.com/en/2.11.x/templates/#macros) are fully supported. When writing -macros that produce yaml resources, you must use the `---` yaml separator in case you want to produce multiple resources -in one go. - -# Why no Go Templating - -kluctl started as a python project and was then migrated to be a Go project. In the python world, Jinja2 is the obvious -choice when it comes to templating. In the Go world, of course Go Templates would be the first choice. - -When the migration to Go was performed, it was a conscious and opinionated decision to stick with Jinja2 templating. -The reason is that I (@codablock) believe that Go Templates are hard to read and write and at the same time quite limited -in their features (without extensive work). It never felt natural to write Go Templates. - -This "feeling" was confirmed by multiple users of kluctl when it started and users described as "relieving" to not -be forced to use Go Templates. - -The above is my personal experience and opinion. I'm still quite open for contributions in regard to Go Templating -support, as long as Jinja2 support is kept. diff --git a/docs/kluctl_project.md b/docs/kluctl_project.md deleted file mode 100644 index f0622885d..000000000 --- a/docs/kluctl_project.md +++ /dev/null @@ -1,337 +0,0 @@ -# .kluctl.yml project - -The `.kluctl.yml` is the central configuration and entry point for your deployments. It defines where the actual -[deployment project](./deployments.md) is located, whre the [cluster configuration](./cluster-config.md) is located, -where [sealed secrets](./sealed-secrets.md) and unencrypted secrets are localed and which targets are available to -invoke [commands](./commands.md) on. - -## Example - -An example .kluctl.yml looks like this: - -```yaml -# This is optional. If omitted, the same directory where `.kluctl.yml` is located will be used as root deployment -deployment: - project: - url: https://github.com/kluctl/kluctl-example - -# This is optional. If omitted, `/clusters` will be used -clusters: - project: - url: https://github.com/kluctl/kluctl-example-clusters - subdir: clusters - -# This is optional. If omitted, `/.sealed-secrets` will be used -sealedSecrets: - project: - url: https://github.com/kluctl/kluctl-example - subdir: .sealed-secrets - -targets: - # test cluster, dev env - - name: dev - cluster: test.example.com - args: - environment_name: dev - sealingConfig: - secretSets: - - non-prod - # test cluster, test env - - name: test - cluster: test.example.com - args: - environment_name: test - sealingConfig: - secretSets: - - non-prod - # prod cluster, prod env - - name: prod - cluster: prod.example.com - args: - environment_name: prod - sealingConfig: - secretSets: - - prod - -# This is only required if you actually need sealed secrets -secretsConfig: - secretSets: - - name: prod - sources: - # This file should not be part of version control! - - path: .secrets-prod.yml - - name: non-prod - sources: - # This file should not be part of version control! - - path: .secrets-non-prod.yml -``` - -## Allowed fields - -The following fields are allowed in `.kluctl.yml`: - -### deployment - -Specifies the git project where the [deployment project](./deployments.md) is located. If it is omitted, the base -directory of the `.kluctl.yml` project is used as the deployment project root. - -It has the following form: -```yaml -deployment: - project: - url: - ref: - subdir: -``` - -* `url` specifies the git clone url of the project. -* `ref` is optional and specifies which tag/branch to use. If omitted, the repositories default branch is used. -* `subdir` is optional and specifies the subdirectory to use. If omitted, the repository root is used. - -### clusters - -Specifies the git project where the [cluster configuration](./cluster-config.md) is located. If it is omitted, the -`clusters` subdirectory of the `.kluctl.yml` project is used as the clusters config root. - -It has the same form as in [deployment](#deployment), except that it is called `clusters`. - -### sealedSecrets - -Specifies the git project where the [sealed secrets](./cluster-config.md) are located. If it is omitted, the -`.sealed-secrets` subdirectory of the `.kluctl.yml` project is used as the sealed secrets location. - -It has the same form as in [deployment](#deployment), except that it is called `sealedSecrets`. - -### targets - -Specifies a list of targets for which commands can be invoked. A target puts together environment/target specific -configuration and the target cluster. Multiple targets can exist which target the same cluster but with differing -configuration (via `args`). Target entries also specifies which secrets to use while [sealing](./sealed-secrets.md). - -Each value found in the target definition is rendered with a simple Jinja2 context that only contains the target itself -and cluster configuration. The rendering process is retried 10 times until it finally succeeds, allowing you to reference -the target itself in complex ways. This is especially useful when using [dynamic targets](#dynamic-targets). - -Target entries have the following form: -```yaml -targets: -... - - name: - cluster: - args: - arg1: - arg2: - ... - sealingConfig: - secretSets: - - -... -``` - -* `name` specifies the name of the target. The name must be unique. It is referred in all commands via the - [-t](./commands.md#project-arguments) option. -* `cluster` specifies the name of the target cluster. The cluster must exist in the [cluster configuration](./cluster-config.md) - specified via [clusters](#clusters). -* `args` specifies a map of arguments to be passed to the deployment project when it is rendered. Allowed argument names - are configured via [deployment args](./deployments.md#args) -* `sealingConfig` configures how sealing is performed when the [seal command] (./commands.md#seal) is invoked for this target. - It has the following form: - ```yaml - sealingConfig: - dynamicSealing: - args: - arg1: - secretSets: - - - ``` - * `dynamicSealing` specifies weather sealing should happen per [dynamic target](#dynamic-targets) or only once. This - field is optional and default to `true`. - * `args` allows to add extra arguments to the target args. These are only used while sealing and may override - arguments which are already configured for the target. - * `secretSets` specifies a list of secret set names, which all must exist in the [secretsConfig](#secretsconfig). - -### secretsConfig - -This configures how secrets are retrieved while sealing. It is basically a list of named secret sets which can be -referenced from targets. - -It has the following form: -```yaml -... -secretsConfig: - secretSets: - - name: - sources: - - ... -... -``` - -* `name` specifies the name of the secret set. The name can be used in targets to refer to this secret set. -* `sources` specifies a list of secret sources. See below on which sources are supported. - -### Supported sources - -#### path -A simple local file based source. The path must be relative and multiple places are tried to find the file: -1. Relative to the deployment project root -2. The path provided via [--secrets-dir](./commands.md#seal) - -Example: -```yaml -secretsConfig: - secretSets: - - name: prod - sources: - - path: .secrets-non-prod.yml -``` - -#### passwordstate -[Passwordstate](https://www.clickstudios.com.au/passwordstate.aspx) integration. It requires API access rights and will -prompt for login information while sealing. The password entry must have a field with the name given in `passwordField` -(defaults to `GenericField1`) which contains the yaml file with the actual secrets. - -Example: -```yaml -secretsConfig: - secretSets: - - name: prod - sources: - - passwordstate: - host: passwordstate.example.com - passwordList: /My/Path/To/The/List/name-of-the-list - passwordTitle: title-of-the-password - passwordField: field-name # This is optional and defaults to GenericField1 -``` - -#### systemEnvVars -Load secrets from environment variables. Children of `systemEnvVars` can be arbitrary yaml, e.g. dictionaries or lists. -The leaf values are used to get a value from the system environment. - -Example: -```yaml -secretsConfig: - secretSets: - - name: prod - sources: - - systemEnvVars: - var1: ENV_VAR_NAME1 - someDict: - var2: ENV_VAR_NAME2 - someList: - - var3: ENV_VAR_NAME3 -``` - -The above example will make 3 secret variables available: `secrets.var1`, `secrets.someDict.var2` and -`secrets.someList[0].var3`, each having the values of the environment variables specified by the leaf values. - -#### awsSecretsManager -[AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) integration. Loads a secrets YAML from an AWS Secrets -Manager secret. The secret can either be specified via an ARN or via a secretName and region combination. An AWS -config profile can also be specified (which must exist while sealing). - -Example using an ARN: -```yaml -secretsConfig: - secretSets: - - name: prod - sources: - - awsSecretsManager: - secretName: arn:aws:secretsmanager:eu-central-1:12345678:secret:secret-name-XYZ - profile: my-prod-profile -``` - -Example using a secret name and region: -```yaml -secretsConfig: - secretSets: - - name: prod - sources: - - awsSecretsManager: - secretName: secret-name - region: eu-central-1 - profile: my-prod-profile -``` - -The advantage of the latter is that the auto-generated suffix in the ARN (which might not be known at the time of -writing the configuration) doesn't have to be specified. - -## Dynamic Targets - -Targets can also be "dynamic", meaning that additional configuration can be sourced from another git repository. -This can be based on a single target repository and branch, or on a target repository and branch/ref pattern, resulting -in multiple dynamic targets being created from one target definition. - -Please note that a single entry in `target` might end up with multiple dynamic targets, meaning that the name must be -made unique between these dynamic targets. This can be achieved by using templating in the `name` field. As an example, -`{{ target.targetConfig.ref }}` can be used to set the target name to the branch name of the dynamic target. - -Dynamic targets have the following form: -```yaml -targets: -... - - name: - cluster: - args: ... - arg1: - arg2: - ... - targetConfig: - project: - url: - ref: - refPattern: - file: - sealingConfig: - dynamicSealing: - secretSets: - - -... -``` - -* `name` specifies the name of the dynamic target, same as with normal targets, except that you have to ensure that the - name is unique between all targets. See above for details. -* `targetConfig` specifies where to look for dynamic targets and their addional configuration. It has the following form: - ```yaml - ... - targetConfig: - project: - url: - ref: - refPattern: - ... - ``` - * `url` specifies the specifies the git clone url of the target configuration project. - * `ref` specifies the branch or tag to use. If this is specified, using `refPattern` is forbidden. This will result - in one single dynamic target. - * `refPattern` specifies a regex pattern to use when looking for candidate branches and tags. If this is specified, - using `ref` is forbidden. This will result in multiple dynamic targets. Each dynamic target will have `ref` set to - the actual branch name it belong to. This allows using of `{{ target.targetConfig.ref }}` in all other target fields. - * `file` specifies the config file name to read externalized target config from. -* `dynamicSealing` in `sealingConfig` allows to disable sealing per dynamic target and instead reverts to sealing only once. - See [A note on sealing](#a-note-on-sealing) for details. -* all other fields are the same as for normal targets - -### Simple dynamic targets - -A simplified form of dynamic targets is to store target config inside the same directory/project as the `.kluctl.yml`. -This can be done by omitting `project`, `ref` and `refPattern` from `targetConfig` and only specify `file`. - -### A note on sealing - -When sealing dynamic targets, it is very likely that it is not known yet which dynamic targets will actually exist in -the future. This requires some special care when sealing secrets for these targets. Sealed secrets are usually namespace -scoped, which might need to be changed to cluster-wide scoping so that the same sealed secret can be deployed into -multiple targets (assuming you deploy to different namespaces for each target). When you do this, watch out to not -compromise security, e.g. by sealing production level secrets with a cluster-wide scope! - -It is also very likely required to set `target.sealingConfig.dynamicSealing` to `false`, so that sealing is only performed -once and not for all dynamic targets. - -# Separating kluctl projects and deployment projects - -As seen in the `.kluctl.yml` documentation, deployment projects can exist in other repositories then the kluctl project. -This is a desired pattern in some circumstances, for example when you want to share a single deployment project with -multiple teams that all manage their own clusters. This way each team can have its own minimalistic kluctl project which -points to the deployment project and the teams clusters configuration. - -This way secret sources can also differ between teams and sharing can be reduced to a minimum if desired. diff --git a/docs/kustomize-integration.md b/docs/kustomize-integration.md deleted file mode 100644 index 5d3b349fd..000000000 --- a/docs/kustomize-integration.md +++ /dev/null @@ -1,11 +0,0 @@ -# Kustomize integration - -kluctl uses [kustomize](https://kustomize.io/) to render final resources. This means, that the finest/lowest -level in kluctl is represented with kustomize deployments. These kustomize deployments can then perform further -customization, e.g. patching and more. You can also use kustomize to easily generate ConfigMaps or secrets from files. - -Generally, everything is possible via `kustomization.yml`, is thus possible in kluctl. - -We advise to read the kustomize -[reference](https://kubectl.docs.kubernetes.io/references/kustomize/). You can also look into the official kustomize -[example](https://github.com/kubernetes-sigs/kustomize/tree/master/examples). diff --git a/docs/sealed-secrets.md b/docs/sealed-secrets.md deleted file mode 100644 index 8678168bd..000000000 --- a/docs/sealed-secrets.md +++ /dev/null @@ -1,138 +0,0 @@ -# Sealed Secrets integration - -kluctl has an integration for [sealed secrets](https://github.com/bitnami-labs/sealed-secrets), allowing you to -securely store secrets for multiple target clusters and/or environments inside version control. - -The integration consists of two parts: -1. Sealing of secrets -1. Automatically choosing and deploying the correct secrets for a target - -# Requirements - -The Sealed Secrets integration relies on the [sealed-secrets operator](https://github.com/bitnami-labs/sealed-secrets) -being installed. Installing the operator is the responsibility of you (or whoever is managing/operating the cluster). - -kluctl can however perform sealing of secrets without an existing sealed-secrets operator installation. This is solved -by automatically pre-provisioning a key onto the cluster that is compatible with the operator. - -# Sealing of .sealme files - -Sealing is done via the [seal command](./commands.md#seal). It must be done before the actual deployment is performed. -The `seal` command recursively searches for files that end with `.sealme`, renders them with the -[Jinja2 templating](./jinja2-templating.md) engine. The rendered secret resource is then converted/encrypted into a -sealed secret. - -The `.sealme` files itself have to be [Kubernetes Secrets](https://kubernetes.io/docs/concepts/configuration/secret/), -but without any actual secret data inside. The secret data is referenced via Jinja2 variables and is expected to be -provided only at the time of sealing. This means, that the sensitive secret data must only be in clear text while sealing. -Afterwards the sealed secrets can be added to version control. - -Example file (the name could be for example `db-secrets.yml.sealme`): -```yaml -kind: Secret -apiVersion: v1 -metadata: - name: db-secrets - namespace: {{ my.namespace.variable }} -stringData: - DB_URL: {{ secrets.database.url }} - DB_USERNAME: {{ secrets.database.username }} - DB_PASSWORD: {{ secrets.database.password }} -``` - -While sealing, the full Jinja2 Context (same as in [Jinja2 Templating](./jinja2-templating.md)) is available. -Additionally, the global `secrets` object/variable is available which contains the sensitive secrets. - -## Secret Sources - -Secrets are only loaded while sealing. Available secret sets and sources are configured via -[.kluctl.yml](./kluctl_project.md#supported-sources). The secrets used per target are configured via the -[secrets config](./kluctl_project.md#secretsconfig) of the targets. - -# Using sealed secrets - -After sealing a secret, it can be used inside kustomize deployments. While deploying, kluctl will look for resources -included from `kustomization.yml` which are not existent but for which a file with a `.sealme` extension exists. If such -a file is found, the appropriate sealed secrets is located based on the -[outputPattern](#outputpattern-and-location-of-stored-sealed-secrets). - -An example `kustomization.yml`: -```yaml -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization - -resources: -# please note that we do not specify the .sealme suffix here -- db-secrets.yml -- my-deployments.yml -``` - -# outputPattern and location of stored sealed secrets -The root [deployment project](./deployments.md) must specify the outputPattern to use when storing/loading -sealed secrets. The output pattern must be a Jinja2 Template that is rendered with the full -[Jinja2 Context](./jinja2-templating.md) available for the deployment.yml. - -This template usually at least contains the cluster name. If your deployment can be deployed multiple times to the -same cluster (via different targets), you should also add something to differentiate the targets. - -As an example, `{{ cluster.name }}/{{ args.environment }}` works well, assuming that you differentiate targets via -`args.environment`. - -The final storage location for the sealed secret is: - -`///` - -with: -* `base_dir`: The base directory for sealed secrets is configured in the [.kluctl.yml](./kluctl_project.md#sealedsecrets) config -file. If not specified, the base directory defaults to the subdirectory `.sealed-secrets` in the kluctl project root -diretory. -* `rendered_output_pattern`: The rendered outputPattern as described above. -* `relative_sealme_file_dir`: The relative path from the deployment root directory. -* `file_name`: The filename of the sealed secret, excluding the `.sealme` extension. - -# Content Hashes and re-sealing -Sealed secrets are stored together with hashes of all individual secret entries. These hashes are then used to avoid -unnecessary re-sealing in future [seal](./commands.md#seal) invocations. If you want to force re-sealing, use the -[--force-reseal](./commands.md#seal) option. - -Hashing of secrets is done with bcrypt and the cluster id as salt. The cluster id is currently defined as the sha256 hash -of the cluster CA certificate. This will cause re-sealing of all secrets in case a cluster is set up from scratch -(which causes key from the sealed secrets operator to get wiped as well). - -# Clusters and namespaces -Sealed secrets are usually only decryptable by one cluster, simply because each cluster has its own set of randomly -generated public/private key pairs. This means, that a secret that was sealed for your production cluster can't be -unsealed on your test cluster. - -In addition, sealed secrets can be bound to a single namespace, making them undecryptable for any other namespace. -To limit a sealed secret to a namespace, simply fill the `metadata.namespace` field of the input secret (which is in -the `.sealme` file). This way, the sealed secret can only be deployed to a single namespace. - -You can also use [Scopes](https://github.com/bitnami-labs/sealed-secrets#scopes) to lift/limit restrictions. - -# Using reflectors/replicators -In case a sealed secrets needs to be deployed to more than one namespace, some form of replication must be used. You'd -then seal the secret for a single namespace and use a reflection/replication controller to reflect the unsealed secret -into one or multiple other namespaces. Example controllers that can accomplish this are the -[Mittwald kubernetes-reflector](https://github.com/mittwald/kubernetes-replicator) and the -[Emberstack Kubernetes Reflector](https://github.com/emberstack/kubernetes-reflector). - -Consider the following example (using the Mittwald replicator): -```yaml -kind: Secret -apiVersion: v1 -metadata: - name: db-secrets - namespace: {{ my.namespace.variable }} - annotations: - replicator.v1.mittwald.de/replicate-to: '{{ my.namespace.variable }}-.*' -stringData: - DB_URL: {{ secrets.database.url }} - DB_USERNAME: {{ secrets.database.username }} - DB_PASSWORD: {{ secrets.database.password }} -``` - -The above example would cause automatic replication into every namespace that matches the replicate-to pattern. - -Please watch out for security implications. In the above example, everyone who has the right to create a namespace that -matches the pattern will get access to the secret. diff --git a/docs/tags.md b/docs/tags.md deleted file mode 100644 index ec89159c4..000000000 --- a/docs/tags.md +++ /dev/null @@ -1,79 +0,0 @@ -# Deployment tags - -Every kustomize deployment has a set of tags assigned to it. These tags are defined in multiple places, which is -documented in [deployments](./deployments.md). Look for the `tags` field, which is available in multiple places per -deployment project. - -Tags are useful when only one or more specific kustomize deployments need to be deployed or deleted. - -## Default tags - -[deployment items](./deployments.md#deployments) in deployment projects can have an optional list of tags assigned. - -If this list is completely omitted, one single entry is added by default. This single entry equals to the last element -of the `path` in the `deployments` entry. - -Consider the following example: - -```yaml -deployments: - - path: nginx - - path: some/subdir -``` - -In this example, two kustomize deployments are defined. The first would get the tag `nginx` while the second -would get the tag `subdir`. - -In most cases this heuristic is enough to get proper tags with which you can work. It might however lead to strange -or even conflicting tags (e.g. `subdir` is really a bad tag), in which case you'd have to explicitly set tags. - -## Tag inheritance - -Deployment projects and deployments items inherit the tags of their parents. For example, if a deployment project -has a [tags](./deployments.md#tags-deployment-project) property defined, all `deployments` entries would -inherit all these tags. Also, the sub-deployment projects included via deployment items of type -[include](./deployments.md#includes) inherit the tags of the deployment project. These included sub-deployments also -inherit the [tags](./deployments.md#tags-deployment-item) specified by the deployment item itself. - -Consider the following example `deployment.yml`: - -```yaml -deployments: - - include: sub-deployment1 - tags: - - tag1 - - tag2 - - include: sub-deployment2 - tags: - - tag3 - - tag4 - - include: subdir/subsub -``` - -Any kustomize deployment found in `sub-deployment1` would now inherit `tag1` and `tag2`. If `sub-deployment1` performs -any further includes, these would also inherit these two tags. Inheriting is additive and recursive. - -The last sub-deployment project in the example is subject to the same default-tags logic as described -in [Default tags](#default-tags), meaning that it will get the default tag `subsub`. - -## Deploying with tag inclusion/exclusion - -Special care needs to be taken when trying to deploy only a specific part of your deployment which requires some base -resources to be deployed as well. - -Imagine a large deployment is able to deploy 10 applications, but you only want to deploy one of them. When using tags -to achieve this, there might be some base resources (e.g. Namespaces) which are needed no matter if everything or just -this single application is deployed. In that case, you'd need to set [alwaysDeploy](./deployments.md#alwaysdeploy) -to `true`. - -## Deleting with tag inclusion/exclusion - -Also, in most cases, even more special care has to be taken for the same types of resources as decribed before. - -Imagine a kustomize deployment being responsible for namespaces deployments. If you now want to delete everything except -deployments that have the `persistency` tag assigned, the exclusion logic would NOT exclude deletion of the namespace. -This would ultimately lead to everything being deleted, and the exclusion tag having no effect. - -In such a case, you'd need to set [skipDeleteIfTags](./deployments.md#skipdeleteiftags) to `true` as well. - -In most cases, setting `alwaysDeploy` to `true` also requires setting `skipDeleteIfTags` to `true`. diff --git a/examples/simple-with-external-repos/.kluctl.yml b/examples/simple-with-external-repos/.kluctl.yml deleted file mode 100644 index 4edb18728..000000000 --- a/examples/simple-with-external-repos/.kluctl.yml +++ /dev/null @@ -1,14 +0,0 @@ -clusters: - project: - url: https://github.com/AljoschaP/kluctl-external-clusters - subdir: clusters - -deployment: - project: - url: https://github.com/AljoschaP/kluctl-external-deployments - -targets: - - name: dev-external - cluster: kind - args: - environment: dev-external \ No newline at end of file diff --git a/examples/simple/.kluctl.yml b/examples/simple/.kluctl.yml deleted file mode 100644 index b41b5e672..000000000 --- a/examples/simple/.kluctl.yml +++ /dev/null @@ -1,5 +0,0 @@ -targets: - - name: dev - cluster: kind - args: - environment: dev \ No newline at end of file diff --git a/examples/simple/clusters/kind.yml b/examples/simple/clusters/kind.yml deleted file mode 100644 index a16a76def..000000000 --- a/examples/simple/clusters/kind.yml +++ /dev/null @@ -1,3 +0,0 @@ -cluster: - name: kind - context: kind-kind \ No newline at end of file diff --git a/examples/simple/deployment.yml b/examples/simple/deployment.yml deleted file mode 100644 index eb31c9fb9..000000000 --- a/examples/simple/deployment.yml +++ /dev/null @@ -1,10 +0,0 @@ -includes: -- path: deployment - -commonLabels: - my.prefix/environment: "{{ args.environment }}" - my.prefix/deployment-project: k8s-deployment-nginx - -args: -- name: environment - diff --git a/examples/simple/deployment/deployment.yml b/examples/simple/deployment/deployment.yml deleted file mode 100644 index b5a54253e..000000000 --- a/examples/simple/deployment/deployment.yml +++ /dev/null @@ -1,4 +0,0 @@ -kustomizeDirs: -- path: nginx - -overrideNamespace: "{{ args.environment }}-nginx" \ No newline at end of file diff --git a/examples/simple/deployment/nginx/deploy.yml b/examples/simple/deployment/nginx/deploy.yml deleted file mode 100644 index 4a47e1c31..000000000 --- a/examples/simple/deployment/nginx/deploy.yml +++ /dev/null @@ -1,26 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: {{ args.environment }}-nginx ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - labels: - app: nginx -spec: - replicas: 3 - selector: - matchLabels: - app: nginx - template: - metadata: - labels: - app: nginx - spec: - containers: - - name: nginx - image: nginx:1.14.2 - ports: - - containerPort: 80 \ No newline at end of file diff --git a/examples/simple/deployment/nginx/kustomization.yml b/examples/simple/deployment/nginx/kustomization.yml deleted file mode 100644 index 8bac1b456..000000000 --- a/examples/simple/deployment/nginx/kustomization.yml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization - -resources: - - deploy.yml \ No newline at end of file From 2c1763f935c57f65798d9b5dd6f06b6d6ea17a41 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 7 Apr 2022 17:58:42 +0200 Subject: [PATCH 0624/2916] ci: Remove check docs step --- .github/workflows/build-and-release.yml | 8 -- hack/replace-commands-help/main.go | 145 ------------------------ 2 files changed, 153 deletions(-) delete mode 100644 hack/replace-commands-help/main.go diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index b97ccaa55..69778bad2 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -70,14 +70,6 @@ jobs: go generate ./... go generate -tags darwin ./pkg/python go generate -tags windows ./pkg/python - - name: Check docs - run: | - go run ./hack/replace-commands-help --commands-md ./docs/commands.md - - if [ "$(git status --porcelain -- docs)" != "" ]; then - echo "commands.md is not up-to-date. Run "./hack/replace-commands-help --commands-md ./docs/commands.md" to fix this!" - exit 1 - fi - name: Run unit tests run: | go test ./cmd/... ./pkg/... -v diff --git a/hack/replace-commands-help/main.go b/hack/replace-commands-help/main.go deleted file mode 100644 index dbaed434a..000000000 --- a/hack/replace-commands-help/main.go +++ /dev/null @@ -1,145 +0,0 @@ -package main - -import ( - "bytes" - "flag" - "fmt" - "github.com/alecthomas/kong" - log "github.com/sirupsen/logrus" - "io/ioutil" - "reflect" - "regexp" - "strings" - "syscall" - - "github.com/kluctl/kluctl/cmd/kluctl/commands" -) - -var commandsMDPath = flag.String("commands-md", "", "Path to commands.md") - -type section struct { - start int - end int - command string - section string - code bool -} - -func main() { - _ = syscall.Setenv("COLUMNS", "120") - - flag.Parse() - text, err := ioutil.ReadFile(*commandsMDPath) - if err != nil { - log.Fatal(err) - } - lines := strings.Split(string(text), "\n") - - var newLines []string - pos := 0 - for true { - s := findNextSection(lines, pos) - if s == nil { - newLines = append(newLines, lines[pos:]...) - break - } - - newLines = append(newLines, lines[pos:s.start+1]...) - - s2 := getHelpSection(s.command, s.section) - - if s.code { - newLines = append(newLines, "```") - } - newLines = append(newLines, s2...) - if s.code { - newLines = append(newLines, "```") - } - - newLines = append(newLines, lines[s.end]) - pos = s.end + 1 - } - - if !reflect.DeepEqual(lines, newLines) { - err = ioutil.WriteFile(*commandsMDPath, []byte(strings.Join(newLines, "\n")), 0o600) - if err != nil { - log.Fatal(err) - } - } -} - -var beginPattern = regexp.MustCompile(``) -var endPattern = regexp.MustCompile(``) -func findNextSection(lines []string, start int) *section { - - for i := start; i < len(lines); i++ { - m := beginPattern.FindSubmatch([]byte(lines[i])) - if m == nil { - continue - } - - var s section - s.start = i - s.command = string(m[1]) - s.section = string(m[2]) - s.code = string(m[3]) == "true" - - for j := i + 1; j < len(lines); j++ { - m = endPattern.FindSubmatch([]byte(lines[j])) - if m == nil { - continue - } - s.end = j - return &s - } - } - return nil -} - -func countIndent(str string) int { - for i := 0; i < len(str); i++ { - if str[i] != ' ' { - return i - } - } - return 0 -} - -func getHelpSection(command string, section string) []string { - log.Infof("Getting section '%s' from command '%s'", section, command) - - exitFunc := func(x int) { - _ = command - } - - helpBuf := bytes.NewBuffer(nil) - parser, _, err := commands.ParseArgs([]string{command, "--help"}, kong.Exit(exitFunc), kong.Writers(helpBuf, helpBuf)) - parser.FatalIfErrorf(err) - - lines := strings.Split(helpBuf.String(), "\n") - - sectionStart := -1 - for i := 0; i < len(lines); i++ { - indent := countIndent(lines[i]) - if strings.HasPrefix(lines[i][indent:], fmt.Sprintf("%s:", section)) { - sectionStart = i - break - } - } - if sectionStart == -1 { - log.Fatalf("Section %s not found in command %s", section, command) - } - - var ret []string - ret = append(ret, lines[sectionStart]) - sectionIndent := countIndent(lines[sectionStart]) - for i := sectionStart + 1; i < len(lines); i++ { - indent := countIndent(lines[i]) - if lines[i] != "" && indent <= sectionIndent { - break - } - ret = append(ret, lines[i]) - } - return ret -} - From 0d10dfde597a20c89611cbd301477215213cb783 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 8 Apr 2022 09:39:48 +0200 Subject: [PATCH 0625/2916] fix: Fix helm-update --upgrade --commit to work with sub-dirs in git --- cmd/kluctl/commands/cmd_helm_update.go | 20 +++++++++++++++++--- pkg/git/utils.go | 21 +++++++++++++++++++++ 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go index 89f5eefcc..daa48b4c7 100644 --- a/cmd/kluctl/commands/cmd_helm_update.go +++ b/cmd/kluctl/commands/cmd_helm_update.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/go-git/go-git/v5" "github.com/kluctl/kluctl/pkg/deployment" + git2 "github.com/kluctl/kluctl/pkg/git" log "github.com/sirupsen/logrus" "io/fs" "path/filepath" @@ -24,7 +25,12 @@ func (cmd *helmUpdateCmd) Run() error { if cmd.LocalDeployment != "" { rootPath = cmd.LocalDeployment } - err := filepath.WalkDir(rootPath, func(p string, d fs.DirEntry, err error) error { + gitRootPath, err := git2.DetectGitRepositoryRoot(rootPath) + if err != nil { + return err + } + + err = filepath.WalkDir(rootPath, func(p string, d fs.DirEntry, err error) error { fname := filepath.Base(p) if fname == "helm-chart.yml" || fname == "helm-chart.yaml" { chart, err := deployment.NewHelmChart(p) @@ -89,7 +95,7 @@ func (cmd *helmUpdateCmd) Run() error { if cmd.Commit { msg := fmt.Sprintf("Updated helm chart %s from %s to %s", filepath.Dir(p), oldVersion, newVersion) log.Infof("Committing: %s", msg) - r, err := git.PlainOpen(rootPath) + r, err := git.PlainOpen(gitRootPath) if err != nil { return err } @@ -98,7 +104,15 @@ func (cmd *helmUpdateCmd) Run() error { return err } for p := range gitFiles { - _, err = wt.Add(p) + absPath, err := filepath.Abs(filepath.Join(rootPath, p)) + if err != nil { + return err + } + relToGit, err := filepath.Rel(gitRootPath, absPath) + if err != nil { + return err + } + _, err = wt.Add(relToGit) if err != nil { return err } diff --git a/pkg/git/utils.go b/pkg/git/utils.go index 3f9d72066..11e9f8cf2 100644 --- a/pkg/git/utils.go +++ b/pkg/git/utils.go @@ -1,7 +1,10 @@ package git import ( + "fmt" "github.com/go-git/go-git/v5" + "github.com/kluctl/kluctl/pkg/utils" + "path/filepath" ) type GitRepoInfo struct { @@ -22,3 +25,21 @@ func GetGitRepoInfo(path string) (ri GitRepoInfo, err error) { ri.CheckedOutCommit = head.Hash().String() return } + +func DetectGitRepositoryRoot(path string) (string, error) { + path, err := filepath.Abs(path) + if err != nil { + return "", err + } + for true { + if utils.Exists(filepath.Join(path, ".git")) { + break + } + old := path + path = filepath.Dir(path) + if old == path { + return "", fmt.Errorf("could not detect git repository root") + } + } + return path, nil +} From 71898c36b12368827a20f68cdc7e8914cc863084 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 8 Apr 2022 09:54:26 +0200 Subject: [PATCH 0626/2916] deps: Run go get -u --- go.mod | 75 +++++++++---------- go.sum | 233 +++++++++++++++++++++------------------------------------ 2 files changed, 121 insertions(+), 187 deletions(-) diff --git a/go.mod b/go.mod index 8028d078b..6c1029446 100644 --- a/go.mod +++ b/go.mod @@ -3,73 +3,74 @@ module github.com/kluctl/kluctl go 1.17 require ( - github.com/alecthomas/kong v0.4.1 - github.com/aws/aws-sdk-go v1.43.10 - github.com/bitnami-labs/sealed-secrets v0.17.3 - github.com/docker/distribution v2.8.0+incompatible + github.com/alecthomas/kong v0.5.0 + github.com/aws/aws-sdk-go v1.43.35 + github.com/bitnami-labs/sealed-secrets v0.17.4 + github.com/docker/distribution v2.8.1+incompatible github.com/evanphx/json-patch v5.6.0+incompatible github.com/gammazero/workerpool v1.1.2 github.com/go-git/go-git/v5 v5.4.2 - github.com/go-playground/validator/v10 v10.10.0 + github.com/go-playground/validator/v10 v10.10.1 github.com/gobwas/glob v0.2.3 github.com/goccy/go-yaml v1.9.5 github.com/gofrs/flock v0.8.1 - github.com/golang-jwt/jwt/v4 v4.3.0 + github.com/golang-jwt/jwt/v4 v4.4.1 github.com/google/go-containerregistry v0.8.0 github.com/hashicorp/go-version v1.4.0 github.com/hexops/gotextdiff v1.0.3 github.com/jinzhu/copier v0.3.5 + github.com/kevinburke/ssh_config v1.2.0 github.com/mattn/go-isatty v0.0.14 github.com/mitchellh/go-ps v1.0.0 - github.com/ohler55/ojg v1.12.14 + github.com/ohler55/ojg v1.13.1 github.com/pkg/errors v0.9.1 - github.com/r3labs/diff/v2 v2.15.0 + github.com/r3labs/diff/v2 v2.15.1 github.com/sirupsen/logrus v1.8.1 - github.com/stretchr/testify v1.7.0 + github.com/stretchr/testify v1.7.1 github.com/whilp/git-urls v1.0.0 github.com/xanzy/ssh-agent v0.3.1 - golang.org/x/crypto v0.0.0-20220214200702-86341886e292 + golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 + golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12 + golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b - k8s.io/api v0.23.4 - k8s.io/apimachinery v0.23.4 - k8s.io/client-go v0.23.4 - sigs.k8s.io/kind v0.11.1 - sigs.k8s.io/kustomize/api v0.11.2 - sigs.k8s.io/kustomize/kyaml v0.13.3 + k8s.io/api v0.24.0-beta.0 + k8s.io/apimachinery v0.24.0-beta.0 + k8s.io/client-go v0.24.0-beta.0 + sigs.k8s.io/kind v0.12.0 + sigs.k8s.io/kustomize/api v0.11.4 + sigs.k8s.io/kustomize/kyaml v0.13.6 sigs.k8s.io/structured-merge-diff/v4 v4.2.1 ) require ( - cloud.google.com/go v0.100.2 // indirect cloud.google.com/go/compute v1.5.0 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect - github.com/Azure/go-autorest/autorest v0.11.24 // indirect + github.com/Azure/go-autorest/autorest v0.11.25 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect - github.com/BurntSushi/toml v1.0.0 // indirect + github.com/BurntSushi/toml v1.1.0 // indirect github.com/Microsoft/go-winio v0.5.2 // indirect - github.com/ProtonMail/go-crypto v0.0.0-20220113124808-70ae35bab23f // indirect + github.com/ProtonMail/go-crypto v0.0.0-20220407094043-a94812496cf5 // indirect github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/acomagu/bufpipe v1.0.3 // indirect github.com/alessio/shellescape v1.4.1 // indirect - github.com/containerd/stargz-snapshotter/estargz v0.10.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/docker/cli v20.10.12+incompatible // indirect - github.com/docker/docker v20.10.12+incompatible // indirect + github.com/docker/cli v20.10.14+incompatible // indirect + github.com/docker/docker v20.10.14+incompatible // indirect github.com/docker/docker-credential-helpers v0.6.4 // indirect - github.com/emirpasic/gods v1.12.0 // indirect + github.com/emicklei/go-restful v2.15.0+incompatible // indirect + github.com/emirpasic/gods v1.12.1 // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/fatih/color v1.13.0 // indirect github.com/gammazero/deque v0.1.1 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-git/gcfg v1.5.0 // indirect github.com/go-git/go-billy/v5 v5.3.1 // indirect - github.com/go-logr/logr v1.2.2 // indirect + github.com/go-logr/logr v1.2.3 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.19.6 // indirect github.com/go-openapi/swag v0.21.1 // indirect @@ -78,10 +79,9 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/btree v1.0.1 // indirect - github.com/google/go-cmp v0.5.7 // indirect + github.com/google/gnostic v0.6.8 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/googleapis/gnostic v0.5.5 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect @@ -89,8 +89,6 @@ require ( github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/kevinburke/ssh_config v1.1.0 // indirect - github.com/klauspost/compress v1.13.6 // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.12 // indirect @@ -98,31 +96,30 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2-0.20211117181255-693428a734f5 // indirect github.com/pelletier/go-toml v1.9.4 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/sergi/go-diff v1.2.0 // indirect - github.com/spf13/cobra v1.3.0 // indirect + github.com/spf13/cobra v1.4.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/vbatts/tar-split v0.11.2 // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/xlab/treeprint v1.1.0 // indirect - go.starlark.net v0.0.0-20220302181546-5411bad688d1 // indirect - golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect - golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect - golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect + go.starlark.net v0.0.0-20220328144851-d1966c6b9fcd // indirect + golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3 // indirect + golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.27.1 // indirect + google.golang.org/protobuf v1.28.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/klog/v2 v2.40.1 // indirect - k8s.io/kube-openapi v0.0.0-20220124234850-424119656bbf // indirect + k8s.io/klog/v2 v2.60.1 // indirect + k8s.io/kube-openapi v0.0.0-20220401212409-b28bf2818661 // indirect k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect sigs.k8s.io/yaml v1.3.0 // indirect diff --git a/go.sum b/go.sum index 0e9a2756d..e3a22e638 100644 --- a/go.sum +++ b/go.sum @@ -27,7 +27,6 @@ cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+Y cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM= -cloud.google.com/go v0.99.0 h1:y/cM2iqGgGi5D5DQZl6D9STN/3dR/Vx5Mp8s752oJTY= cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= cloud.google.com/go v0.100.2 h1:t9Iw5QH5v4XtlEQaCtUY7x6sCABps8sW0acw7e2WQ6Y= cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= @@ -62,31 +61,28 @@ github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= -github.com/Azure/go-autorest/autorest v0.11.20 h1:s8H1PbCZSqg/DH7JMlOz6YMig6htWLNPsjDdlLqCx3M= -github.com/Azure/go-autorest/autorest v0.11.20/go.mod h1:o3tqFY+QR40VOlk+pV4d77mORO64jOXSgEnPQgLK6JY= -github.com/Azure/go-autorest/autorest v0.11.24 h1:1fIGgHKqVm54KIPT+q8Zmd1QlVsmHqeUGso5qm2BqqE= -github.com/Azure/go-autorest/autorest v0.11.24/go.mod h1:G6kyRlFnTuSbEYkQGawPfsCswgme4iYf6rfSKUDzbCc= +github.com/Azure/go-autorest/autorest v0.11.25 h1:yp+V8DGur2aIUE87ebP8twPLz6k68jtJTlg61mEoByA= +github.com/Azure/go-autorest/autorest v0.11.25/go.mod h1:7l8ybrIdUmGqZMTD0sRtAr8NvbHjfofbf8RSP2q7w7U= github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= -github.com/Azure/go-autorest/autorest/adal v0.9.15 h1:X+p2GF0GWyOiSmqohIaEeuNFNDY4I4EOlVuUQvFdWMk= -github.com/Azure/go-autorest/autorest/adal v0.9.15/go.mod h1:tGMin8I49Yij6AQ+rvV+Xa/zwxYQB5hmsd6DkfAx2+A= github.com/Azure/go-autorest/autorest/adal v0.9.18 h1:kLnPsRjzZZUF3K5REu/Kc+qMQrvuza2bwSnNdhmzLfQ= github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw= +github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU= github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU= -github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= +github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= @@ -99,7 +95,6 @@ github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JP github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY= github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= @@ -115,14 +110,11 @@ github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5 github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C67SkzkDfmQuVln04ygHj3vjZfd9FL+GmQQ= github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= -github.com/ProtonMail/go-crypto v0.0.0-20220113124808-70ae35bab23f h1:J2FzIrXN82q5uyUraeJpLIm7U6PffRwje2ORho5yIik= -github.com/ProtonMail/go-crypto v0.0.0-20220113124808-70ae35bab23f/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= -github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/ProtonMail/go-crypto v0.0.0-20220407094043-a94812496cf5 h1:cSHEbLj0GZeHM1mWG84qEnGFojNEQ83W7cwaPRjcwXU= +github.com/ProtonMail/go-crypto v0.0.0-20220407094043-a94812496cf5/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= @@ -132,8 +124,8 @@ github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/ github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= -github.com/alecthomas/kong v0.4.1 h1:0sFnMts+ijOiFuSHsMB9MlDi3NGINBkx9KIw1/gcuDw= -github.com/alecthomas/kong v0.4.1/go.mod h1:uzxf/HUh0tj43x1AyJROl3JT7SgsZ5m+icOv1csRhc0= +github.com/alecthomas/kong v0.5.0 h1:u8Kdw+eeml93qtMZ04iei0CFYve/WPcA5IFh+9wSskE= +github.com/alecthomas/kong v0.5.0/go.mod h1:uzxf/HUh0tj43x1AyJROl3JT7SgsZ5m+icOv1csRhc0= github.com/alecthomas/repr v0.0.0-20210801044451-80ca428c5142 h1:8Uy0oSf5co/NZXje7U1z8Mpep++QJOldL2hs/sBQf48= github.com/alecthomas/repr v0.0.0-20210801044451-80ca428c5142/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -161,10 +153,9 @@ github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6l github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= -github.com/aws/aws-sdk-go v1.27.0 h1:0xphMHGMLBrPMfxR2AmVjZKcMEESEgWF8Kru94BNByk= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.43.10 h1:lFX6gzTBltYBnlJBjd2DWRCmqn2CbTcs6PW99/Dme7k= -github.com/aws/aws-sdk-go v1.43.10/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.43.35 h1:Ko1HiU7c7C8cZ5nvwp4GoLl08nmdQtZVZHxhrD8icwk= +github.com/aws/aws-sdk-go v1.43.35/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -174,16 +165,16 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= github.com/bitnami-labs/flagenv v0.0.0-20190607135054-a87af7a1d6fc/go.mod h1:OeW4NPgFPO7+t8q1Vn2Yv+rkO+4kEQzlDskwm7C7PXs= github.com/bitnami-labs/pflagenv v0.0.0-20190702160147-b4d9f048d98f/go.mod h1:Lw3ejf6HTt4DqBIAXlkOIvFjnpj8Zq+zD/UtH29ILFA= -github.com/bitnami-labs/sealed-secrets v0.17.3 h1:ebus6Rbz9MLhWcHVkFiwDpVkrecvjzOibXdkmJkRhss= -github.com/bitnami-labs/sealed-secrets v0.17.3/go.mod h1:EMXakbe/TMdfzATuEH+pTA2K29aZhNgyBNTdQvDc/eU= +github.com/bitnami-labs/sealed-secrets v0.17.4 h1:DFZCyR8ntNKINRaneKPBrnEXnejWz48fE8htx83w0yQ= +github.com/bitnami-labs/sealed-secrets v0.17.4/go.mod h1:xL3ifFPr9lIS6QoCfqj8shGgLriGjQke5o/X4NFUZfE= github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= -github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= @@ -308,7 +299,6 @@ github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgU github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= @@ -334,7 +324,6 @@ github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I= github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg= -github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -343,16 +332,17 @@ github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= -github.com/docker/cli v20.10.12+incompatible h1:lZlz0uzG+GH+c0plStMUdF/qk3ppmgnswpR5EbqzVGA= github.com/docker/cli v20.10.12+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v20.10.14+incompatible h1:dSBKJOVesDgHo7rbxlYjYsXe7gPzrTT+/cKQgpDAazg= +github.com/docker/cli v20.10.14+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/distribution v2.8.0+incompatible h1:l9EaZDICImO1ngI+uTifW+ZYvvz7fKISBAKpg+MbWbY= -github.com/docker/distribution v2.8.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v20.10.12+incompatible h1:CEeNmFM0QZIsJCZKMkZx0ZcahTiewkrgiwfYD+dfl1U= +github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= +github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v20.10.12+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v20.10.14+incompatible h1:+T9/PRYWNDo5SZl5qS1r9Mo/0Q8AwxKKPtu9S1yxM0w= +github.com/docker/docker v20.10.14+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.6.4 h1:axCks+yV+2MR3/kZhAmy07yC56WZ2Pwu/fKWtKuZB0o= github.com/docker/docker-credential-helpers v0.6.4/go.mod h1:ofX3UI0Gz1TteYBjtgs07O36Pyasyp66D2uKT7H8W1c= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= @@ -373,8 +363,11 @@ github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaB github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= +github.com/emicklei/go-restful v2.15.0+incompatible h1:8KpYO/Xl/ZudZs5RNOEhWMBY4hmzlZhhRd9cu+jrZP4= +github.com/emicklei/go-restful v2.15.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= +github.com/emirpasic/gods v1.12.1 h1:KEXpRg94qvWNpl3F8PRlzJRFhy1kr6SiBiFH6X2Nwp8= +github.com/emirpasic/gods v1.12.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -389,12 +382,9 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch/v5 v5.2.0 h1:8ozOH5xxoMYDt5/u+yMTsVXydVCbTORFnOOoq2lumco= -github.com/evanphx/json-patch/v5 v5.2.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= @@ -402,6 +392,7 @@ github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= @@ -413,7 +404,6 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4 github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= -github.com/gammazero/deque v0.1.0 h1:f9LnNmq66VDeuAlSAapemq/U7hJ2jpIWa4c09q8Dlik= github.com/gammazero/deque v0.1.0/go.mod h1:KQw7vFau1hHuM8xmI9RbgKFbAsQFWmBpqQ2KenFLk6M= github.com/gammazero/deque v0.1.1 h1:xRVkDuSvDmFuMGf3IquHuRc2jlL0+v/WpFCWaauzwbE= github.com/gammazero/deque v0.1.1/go.mod h1:KQw7vFau1hHuM8xmI9RbgKFbAsQFWmBpqQ2KenFLk6M= @@ -425,7 +415,6 @@ github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2H github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= -github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= @@ -450,30 +439,22 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v1.2.0 h1:QK40JKJyMdUDz+h+xvCsru/bJhvG0UxvePV0ufL/AcE= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.2 h1:ahHml/yUpnlb96Rp8HCvtYVPY8ZYpxq3g7UYchIYwbs= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= -github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM= github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= -github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= -github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU= github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= @@ -486,8 +467,8 @@ github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+ github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= -github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0= -github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= +github.com/go-playground/validator/v10 v10.10.1 h1:uA0+amWMiglNZKZ9FJRKUAe9U3RX91eVn1JYXMWt7ig= +github.com/go-playground/validator/v10 v10.10.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= @@ -512,11 +493,10 @@ github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v4 v4.0.0 h1:RAqyYixv1p7uEnocuy8P1nru5wprCh/MH2BIlW5z5/o= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= -github.com/golang-jwt/jwt/v4 v4.3.0 h1:kHL1vqdqWNfATmA0FNMdmZNMyZI1U6O31X4rlIPoBog= -github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ= +github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -533,7 +513,6 @@ github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -559,6 +538,9 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= +github.com/google/gnostic v0.6.8 h1:bT56GPYBWh1tvBuBEd94qcS3+60b+y0HQur0ITkGuCk= +github.com/google/gnostic v0.6.8/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -570,15 +552,12 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-containerregistry v0.8.0 h1:mtR24eN6rapCN+shds82qFEIWWmg64NPMuyCNT7/Ogc= github.com/google/go-containerregistry v0.8.0/go.mod h1:wW5v71NHGnQyb4k+gSshjxidrC7lN33MdWEn+Mz9TsI= -github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -611,10 +590,8 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= -github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= -github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw= github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= @@ -626,7 +603,6 @@ github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB7 github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= @@ -705,16 +681,15 @@ github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg= github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -728,10 +703,9 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= -github.com/kevinburke/ssh_config v1.1.0 h1:pH/t1WS9NzT8go394IqZeJTMHVm6Cr6ZJ6AQ+mdNo/o= -github.com/kevinburke/ssh_config v1.1.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -763,13 +737,10 @@ github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0U github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= -github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= @@ -826,7 +797,6 @@ github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXy github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= @@ -836,6 +806,7 @@ github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -851,10 +822,8 @@ github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/ohler55/ojg v1.12.12 h1:hepbQFn7GHAecTPmwS3j5dCiOLsOpzPLvhiqnlAVAoE= -github.com/ohler55/ojg v1.12.12/go.mod h1:LBbIVRAgoFbYBXQhRhuEpaJIqq+goSO63/FQ+nyJU88= -github.com/ohler55/ojg v1.12.14 h1:4c9+t9iSu12QaOHK7GfIULanFEiVj4d6/wxQuvFuvig= -github.com/ohler55/ojg v1.12.14/go.mod h1:LBbIVRAgoFbYBXQhRhuEpaJIqq+goSO63/FQ+nyJU88= +github.com/ohler55/ojg v1.13.1 h1:56I8UTpsg5CS73uA1/8JcXfyTf4WptmrMxeT3xYzG/g= +github.com/ohler55/ojg v1.13.1/go.mod h1:shYhKYyOC3s9/YgQPGueT8fk04IFxeyxqgZSKp6ILaI= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= @@ -934,7 +903,6 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= -github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= @@ -980,8 +948,8 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/r3labs/diff/v2 v2.15.0 h1:3TEoJ6dBqESl1YgL+7curys5PvuEnwrtjkFNskgUvfg= -github.com/r3labs/diff/v2 v2.15.0/go.mod h1:I8noH9Fc2fjSaMxqF3G2lhDdC0b+JXCfyx85tWFM9kc= +github.com/r3labs/diff/v2 v2.15.1 h1:EOrVqPUzi+njlumoqJwiS/TgGgmZo83619FNDB9xQUg= +github.com/r3labs/diff/v2 v2.15.1/go.mod h1:I8noH9Fc2fjSaMxqF3G2lhDdC0b+JXCfyx85tWFM9kc= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= @@ -999,7 +967,6 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0 github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= -github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= @@ -1029,10 +996,10 @@ github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= -github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= -github.com/spf13/cobra v1.3.0 h1:R7cSvGu+Vv+qX0gW5R/85dx2kmmJT5z5NM8ifdYjdn0= github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4= +github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= +github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -1043,7 +1010,6 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= -github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= @@ -1056,15 +1022,15 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= @@ -1094,15 +1060,14 @@ github.com/whilp/git-urls v1.0.0 h1:95f6UMWN5FKW71ECsXRUd3FVYiXdrE7aX4NZKcPmIjU= github.com/whilp/git-urls v1.0.0/go.mod h1:J16SAmobsqc3Qcy98brfl5f5+e0clUvg1krgwk/qCfE= github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= -github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= github.com/xanzy/ssh-agent v0.3.1 h1:AmzO1SSWxw73zxFZPRwaMN1MohDw8UyHnmuxyceTEGo= github.com/xanzy/ssh-agent v0.3.1/go.mod h1:QIE4lCeL7nkC25x+yA3LBIYfwCc1TFziCtG7cBAac6w= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca h1:1CFlNzQhALwjS9mBAUkycX616GzgsuYUOCHA5+HSlXI= github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= github.com/xlab/treeprint v1.1.0 h1:G/1DjNkPpfZCFt9CSh6b5/nY4VimlbHF3Rh4obvtzDk= github.com/xlab/treeprint v1.1.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= @@ -1112,6 +1077,7 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= @@ -1138,10 +1104,9 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee331t6JAXeK2bcyhLOOc= go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= -go.starlark.net v0.0.0-20220302181546-5411bad688d1 h1:i0Sz4b+qJi5xwOaFZqZ+RNHkIpaKLDofei/Glt+PMNc= -go.starlark.net v0.0.0-20220302181546-5411bad688d1/go.mod h1:t3mmBBPzAVvK0L0n1drDmrQsJ8FoIx4INCqVMTr/Zo0= +go.starlark.net v0.0.0-20220328144851-d1966c6b9fcd h1:Uo/x0Ir5vQJ+683GXB9Ug+4fcjsbp7z7Ul8UaZbhsRM= +go.starlark.net v0.0.0-20220328144851-d1966c6b9fcd/go.mod h1:t3mmBBPzAVvK0L0n1drDmrQsJ8FoIx4INCqVMTr/Zo0= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -1169,17 +1134,15 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 h1:/pEO3GD/ABYAjuakUS6xSEmmlyVS4kxBNkeA9tLJiTI= -golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 h1:tkVvjkPTB7pnW3jnid7kNyAMPVWllTNOf/qKDze4p9o= +golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1219,7 +1182,6 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= -golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1275,17 +1237,15 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d h1:62NvYBuaanGXR2ZOfwDFkhhl6X1DUgf8qg3GuQvxZsE= -golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3 h1:EN5+DfgmRMvRUrMGERW2gQl3Vc+Z7ZMnI/xdEpPSf0c= +golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1303,10 +1263,9 @@ golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b h1:clP8eMhB30EHdc0bd2Twtq6kgU7yl5ub2cQLSdrv1Dg= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a h1:qfl7ob3DIEs3Ml9oLuPwY2N04gymzAW04WsUQHIClgM= +golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1319,7 +1278,6 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1420,6 +1378,7 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1430,19 +1389,15 @@ golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs= -golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12 h1:QyVthZKMsyaQwBTJE04jdNN0Pp5Fn9Qga0mrgxyERQM= +golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1459,14 +1414,13 @@ golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 h1:M73Iuj3xbbb9Uk1DYhzydthsj6oOd6l9bpuFcNoUvTs= golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1486,7 +1440,6 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1531,6 +1484,7 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.6-0.20210820212750-d4cc65f0b2ff/go.mod h1:YD9qOF0M9xpSpdWTBbzEl5e/RnCefISl8E5Noe10jFM= golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1657,6 +1611,7 @@ google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ6 google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= @@ -1710,8 +1665,9 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -1728,7 +1684,6 @@ gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= @@ -1770,32 +1725,25 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= -k8s.io/api v0.22.1/go.mod h1:bh13rkTp3F1XEaLGykbyRD2QaTTzPm0e/BMd8ptFONY= -k8s.io/api v0.23.3 h1:KNrME8KHGr12Ozjf8ytOewKzZh6hl/hHUZeHddT3a38= -k8s.io/api v0.23.3/go.mod h1:w258XdGyvCmnBj/vGzQMj6kzdufJZVUwEM1U2fRJwSQ= -k8s.io/api v0.23.4 h1:85gnfXQOWbJa1SiWGpE9EEtHs0UVvDyIsSMpEtl2D4E= k8s.io/api v0.23.4/go.mod h1:i77F4JfyNNrhOjZF7OwwNJS5Y1S9dpwvb9iYRYRczfI= +k8s.io/api v0.24.0-beta.0 h1:7knNqNYI1Az5hWcebdyUff4ETyCZkvmUT1N2hi/qS/Y= +k8s.io/api v0.24.0-beta.0/go.mod h1:D7w5dDA57yCeRJnl0vPuRj6KBAwWYxea4Dwo5kgJGIY= k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= -k8s.io/apimachinery v0.20.2/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= -k8s.io/apimachinery v0.22.1/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= -k8s.io/apimachinery v0.23.3 h1:7IW6jxNzrXTsP0c8yXz2E5Yx/WTzVPTsHIx/2Vm0cIk= -k8s.io/apimachinery v0.23.3/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM= -k8s.io/apimachinery v0.23.4 h1:fhnuMd/xUL3Cjfl64j5ULKZ1/J9n8NuQEgNL+WXWfdM= k8s.io/apimachinery v0.23.4/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM= +k8s.io/apimachinery v0.24.0-beta.0 h1:69KiS/m3i2oi3FaCVX6whePxOelsJkhIfO0J5fGDYv8= +k8s.io/apimachinery v0.24.0-beta.0/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= -k8s.io/client-go v0.22.1/go.mod h1:BquC5A4UOo4qVDUtoc04/+Nxp1MeHcVc1HJm1KmG8kk= -k8s.io/client-go v0.23.3 h1:23QYUmCQ/W6hW78xIwm3XqZrrKZM+LWDqW2zfo+szJs= -k8s.io/client-go v0.23.3/go.mod h1:47oMd+YvAOqZM7pcQ6neJtBiFH7alOyfunYN48VsmwE= -k8s.io/client-go v0.23.4 h1:YVWvPeerA2gpUudLelvsolzH7c2sFoXXR5wM/sWqNFU= k8s.io/client-go v0.23.4/go.mod h1:PKnIL4pqLuvYUK1WU7RLTMYKPiIh7MYShLshtRY9cj0= -k8s.io/code-generator v0.16.8/go.mod h1:wFdrXdVi/UC+xIfLi+4l9elsTT/uEF61IfcN2wOLULQ= +k8s.io/client-go v0.24.0-beta.0 h1:ISWwVXNtOr2f1O5afJGi66vxAzC6Gb/3+VWlz4WseFc= +k8s.io/client-go v0.24.0-beta.0/go.mod h1:D4rgRqnNPdFCFMMrcCqCOAouzIwJkPuKXr3zWThEExM= +k8s.io/code-generator v0.23.4/go.mod h1:S0Q1JVA+kSzTI1oUvbKAxZY/DYbA/ZUb4Uknog12ETk= k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= @@ -1803,33 +1751,26 @@ k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM= k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= k8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc= -k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= -k8s.io/klog/v2 v2.30.0 h1:bUO6drIvCIsvZ/XFgfxoGFQU/a4Qkh0iAlvUR7vlHJw= k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/klog/v2 v2.40.1 h1:P4RRucWk/lFOlDdkAr3mc7iWFkgKrZY9qZMAgek06S4= k8s.io/klog/v2 v2.40.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= +k8s.io/klog/v2 v2.60.1 h1:VW25q3bZx9uE3vvdL6M8ezOX79vA2Aq1nEWLqNQclHc= +k8s.io/klog/v2 v2.60.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= -k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 h1:E3J9oCLlaobFUqsjG9DfKbP2BmgwBL2p7pn0A3dG9W4= k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= -k8s.io/kube-openapi v0.0.0-20220124234850-424119656bbf h1:M9XBsiMslw2lb2ZzglC0TOkBPK5NQi0/noUrdnoFwUg= -k8s.io/kube-openapi v0.0.0-20220124234850-424119656bbf/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= +k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42/go.mod h1:Z/45zLw8lUo4wdiUkI+v/ImEGAvu3WatcZl3lPMR4Rk= +k8s.io/kube-openapi v0.0.0-20220401212409-b28bf2818661 h1:nqYOUleKLC/0P1zbU29F5q6aoezM6MOAVz+iyfQbZ5M= +k8s.io/kube-openapi v0.0.0-20220401212409-b28bf2818661/go.mod h1:daOouuuwd9JXpv1L7Y34iV3yf6nxzipkKMWWlqlvK9M= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210707171843-4b05e18ac7d9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20211116205334-6203023598ed h1:ck1fRPWPJWsMd8ZRFsWc6mh/zHp5fZ/shhbrgPUxDAE= k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 h1:HNSDgDCrr/6Ly3WEGKZftiE7IY19Vz2GdbOCyI4qqhc= k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= @@ -1843,23 +1784,19 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= -sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 h1:fD1pz4yfdADVNfFmcP2aBEtudwUQ1AlLnRBALr33v3s= sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 h1:kDi4JBNAsJWfz1aEXhO8Jg87JJaPNLh5tIzYHgStQ9Y= sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY= -sigs.k8s.io/kind v0.11.1 h1:pVzOkhUwMBrCB0Q/WllQDO3v14Y+o2V0tFgjTqIUjwA= -sigs.k8s.io/kind v0.11.1/go.mod h1:fRpgVhtqAWrtLB9ED7zQahUimpUXuG/iHT88xYqEGIA= -sigs.k8s.io/kustomize/api v0.11.1 h1:/Vutu+gAqVo8skw1xCZrsZD39SN4Adg+z7FrSTw9pds= -sigs.k8s.io/kustomize/api v0.11.1/go.mod h1:GZuhith5YcqxIDe0GnRJNx5xxPTjlwaLTt/e+ChUtJA= -sigs.k8s.io/kustomize/api v0.11.2 h1:6YvCJHFDwsLwAX7zNHBxMZi3k7dGIXI8G9l0saYQI0E= -sigs.k8s.io/kustomize/api v0.11.2/go.mod h1:GZuhith5YcqxIDe0GnRJNx5xxPTjlwaLTt/e+ChUtJA= -sigs.k8s.io/kustomize/kyaml v0.13.3 h1:tNNQIC+8cc+aXFTVg+RtQAOsjwUdYBZRAgYOVI3RBc4= -sigs.k8s.io/kustomize/kyaml v0.13.3/go.mod h1:/ya3Gk4diiQzlE4mBh7wykyLRFZNvqlbh+JnwQ9Vhrc= +sigs.k8s.io/kind v0.12.0 h1:LFynXwQkH1MrWI8pM1FQty0oUwEKjU5EkMaVZaPld8E= +sigs.k8s.io/kind v0.12.0/go.mod h1:EcgDSBVxz8Bvm19fx8xkioFrf9dC30fMJdOTXBSGNoM= +sigs.k8s.io/kustomize/api v0.11.4 h1:/0Mr3kfBBNcNPOW5Qwk/3eb8zkswCwnqQxxKtmrTkRo= +sigs.k8s.io/kustomize/api v0.11.4/go.mod h1:k+8RsqYbgpkIrJ4p9jcdPqe8DprLxFUUO0yNOq8C+xI= +sigs.k8s.io/kustomize/kyaml v0.13.6 h1:eF+wsn4J7GOAXlvajv6OknSunxpcOBQQqsnPxObtkGs= +sigs.k8s.io/kustomize/kyaml v0.13.6/go.mod h1:yHP031rn1QX1lr/Xd934Ri/xdVNG8BE2ECa78Ht/kEg= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e h1:4Z09Hglb792X0kfOBBJUPFEyvVfQWrYT/l8h5EKA6JQ= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y= sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= From 790ca0a80eb143a5979c8b977b05d9efd251c21f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 11 Apr 2022 18:00:17 +0200 Subject: [PATCH 0627/2916] fix: Implement a workaround for CRDs that don't set status --- pkg/validation/validation.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pkg/validation/validation.go b/pkg/validation/validation.go index eae6adf76..3a9585813 100644 --- a/pkg/validation/validation.go +++ b/pkg/validation/validation.go @@ -10,6 +10,7 @@ import ( "regexp" "strconv" "strings" + "time" ) var resultAnnotation = regexp.MustCompile("^validate-result.kluctl.io/.*") @@ -129,6 +130,14 @@ func ValidateObject(k *k8s.K8sCluster, o *uo.UnstructuredObject, notReadyIsError // it has no status, so all is good return } + + age := time.Now().Sub(o.GetK8sCreationTime()) + if age > 15 * time.Second { + // TODO this is a hack for CRDs that pretend that a status should be there but the corresponding + // controllers/operators don't set it for whatever reason (e.g. cilium) + return + } + addNotReady("no status available yet") return } From 6091cd6d1e91b0c7fa6f50f7910d60026fd7a200 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aljoscha=20P=C3=B6rtner?= Date: Mon, 11 Apr 2022 20:05:29 +0200 Subject: [PATCH 0628/2916] chore: add template for bug reports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Aljoscha Pörtner --- .github/ISSUE_TEMPLATE/BUG.yml | 69 ++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/BUG.yml diff --git a/.github/ISSUE_TEMPLATE/BUG.yml b/.github/ISSUE_TEMPLATE/BUG.yml new file mode 100644 index 000000000..1dd5cccc3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/BUG.yml @@ -0,0 +1,69 @@ +name: Bug +description: File a bug report +body: + - type: markdown + attributes: + value: | + Before opening a bug report, please take a look if your finding is already reported in the issues. + + --- + + Thank you for taking the time to file a bug report. To address this bug as fast as possible, we need some information. + - type: input + id: os + attributes: + label: Operating system + description: "Which operating system do you use? Please provide the version as well." + placeholder: "Ubuntu 20.04, macOS Big Sur 11.5.2" + validations: + required: true + - type: input + id: kluctl + attributes: + label: Kluctl Version + description: "Please provide the full kluctl version. (Output of `kluctl version`)" + placeholder: "2.4.2" + validations: + required: true + - type: input + id: kubernetes + attributes: + label: Kubernetes Version + description: "Please provide the full kubernetes version. (Output of `kubectl version`-> Server Version)" + placeholder: "v1.22.0" + validations: + required: true + - type: input + id: kubectl + attributes: + label: kubectl Version + description: "Please provide the full kubectl version. (Output of `kubectl version` -> Client Version)" + placeholder: "v1.22.0" + validations: + required: true + - type: input + id: helm + attributes: + label: Helm Version + description: "Please provide the full Helm version. (Output of `helm version`)" + placeholder: "v3.6.3" + validations: + required: true + - type: textarea + id: bug-description + attributes: + label: Bug description + description: What happened? + validations: + required: true + - type: textarea + id: steps + attributes: + label: Steps to reproduce + description: Which steps do we need to take to reproduce this error? + - type: textarea + id: logs + attributes: + label: Relevant log output + description: If applicable, provide relevant log output. No need for backticks here (`kluctl -v debug`). + render: shell \ No newline at end of file From 74e9e6b995e3d0a8b3b1b1233a1a566a1db950cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aljoscha=20P=C3=B6rtner?= Date: Mon, 11 Apr 2022 20:28:58 +0200 Subject: [PATCH 0629/2916] chore: add template for feature request MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Aljoscha Pörtner --- .github/ISSUE_TEMPLATE/FEATURE.yml | 56 ++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/FEATURE.yml diff --git a/.github/ISSUE_TEMPLATE/FEATURE.yml b/.github/ISSUE_TEMPLATE/FEATURE.yml new file mode 100644 index 000000000..4ae589d54 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/FEATURE.yml @@ -0,0 +1,56 @@ +name: Feature +description: Create a feature request +body: + - type: markdown + attributes: + value: | + Before opening a feature request, please take a look if your feature is already requested in the issues. + + --- + + Thank you for taking the time to create a feature request. We try to divide the work that needs to be done with the help of [user stories](https://www.agilealliance.org/glossary/user-stories/). + In order to help us with this, we kindly ask you to answer the questions below. + - type: checkboxes + id: command + attributes: + label: Command + description: Which command will be affected by the feature request + options: + - archive + - check-image-updates + - delete + - deploy + - diff + - downscale + - helm-pull + - helm-update + - list-images + - list-targets + - poke-images + - prune + - render + - seal + - validate + - version + - None of the above (e.g. new command) + - type: input + id: persona + attributes: + label: Who are you? + description: In order to help us understand for whom we are implementing the feature, please tell us a little bit about you. For example, "Max, 25, Platform Engineer at Company" + validations: + required: false + - type: textarea + id: what + attributes: + label: What do you want to do? + description: Now tell us what your intent is. What would you like to do? + validations: + required: true + - type: textarea + id: why + attributes: + label: Why do you need that? + description: Now tell us why do you need that feature? What is the overall benefit of the feature? + validations: + required: true \ No newline at end of file From f995e94ddfea1543a5188fccab36e9872ded5e65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aljoscha=20P=C3=B6rtner?= Date: Mon, 11 Apr 2022 20:50:43 +0200 Subject: [PATCH 0630/2916] chore: add pull request template MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Aljoscha Pörtner --- .github/PULL_REQUEST_TEMPLATE/PULL_REQUEST.md | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE/PULL_REQUEST.md diff --git a/.github/PULL_REQUEST_TEMPLATE/PULL_REQUEST.md b/.github/PULL_REQUEST_TEMPLATE/PULL_REQUEST.md new file mode 100644 index 000000000..dc3dd31cb --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/PULL_REQUEST.md @@ -0,0 +1,29 @@ +# Description + +Please include a summary of the changes and the related issue. Please also include relevant motivation and context. List any dependencies that are required for this change. + +Fixes # (issue) + +## Type of change + +Please delete options that are not relevant. + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] This change requires a documentation update +- [ ] This change requires a new example + +All Submissions: + +* [ ] A corresponding issue exists +* [ ] Have you added an explanation of what your changes do and why you'd like us to include them? +* [ ] Have you followed the guidelines in our Contributing document? +* [ ] Have you checked to ensure there aren't other open Pull Requests for the same update/change? +* [ ] I have performed a self-review of my code +* [ ] I have commented my code, particularly in hard-to-understand areas +* [ ] Have you lint your code locally before submission? +* [ ] I have added tests that prove my fix is effective or that my feature works +* [ ] New and existing unit tests pass locally with my changes +* [ ] Any dependent changes have been merged and published in downstream modules +* [ ] All commits are signed off which certify that you created the patch and that you agree to the [Developer Certificate of Origin](https://developercertificate.org/) From 80db5a1bf575a76076b5a5bee5fbbd3f8313bd63 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 12 Apr 2022 18:01:22 +0200 Subject: [PATCH 0631/2916] fix: Honor included kustomize deployments when resolving sealed secrets --- pkg/deployment/deployment_collection.go | 2 +- pkg/deployment/deployment_item.go | 21 +++++++++++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index d32fa4c46..b84c9e11e 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -143,7 +143,7 @@ func (c *DeploymentCollection) resolveSealedSecrets() error { } for _, d := range c.Deployments { - err := d.resolveSealedSecrets() + err := d.resolveSealedSecrets("") if err != nil { return err } diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 3fb24d3d0..cc2e90a4f 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -180,7 +180,7 @@ func (di *DeploymentItem) renderHelmCharts(k *k8s.K8sCluster, wp *utils.WorkerPo return nil } -func (di *DeploymentItem) resolveSealedSecrets() error { +func (di *DeploymentItem) resolveSealedSecrets(subdir string) error { if di.dir == nil { return nil } @@ -190,7 +190,9 @@ func (di *DeploymentItem) resolveSealedSecrets() error { sealedSecretsDir := di.Project.getSealedSecretsDir() baseSourcePath := di.Project.SealedSecretsDir - y, err := uo.FromFile(yaml.FixPathExt(filepath.Join(di.renderedDir, "kustomization.yml"))) + renderedDir := filepath.Join(di.renderedDir, subdir) + + y, err := uo.FromFile(yaml.FixPathExt(filepath.Join(renderedDir, "kustomization.yml"))) if err != nil { return err } @@ -199,11 +201,18 @@ func (di *DeploymentItem) resolveSealedSecrets() error { return err } for _, resource := range l { - p := filepath.Join(di.renderedDir, resource) + p := filepath.Join(renderedDir, resource) + if utils.IsDirectory(p) { + err = di.resolveSealedSecrets(filepath.Join(subdir, resource)) + if err != nil { + return err + } + continue + } if utils.Exists(p) || !utils.Exists(p+SealmeExt) { continue } - relDir, err := filepath.Rel(di.renderedDir, filepath.Dir(p)) + relDir, err := filepath.Rel(renderedDir, filepath.Dir(p)) if err != nil { return err } @@ -213,8 +222,8 @@ func (di *DeploymentItem) resolveSealedSecrets() error { if sealedSecretsDir == nil { return fmt.Errorf("%s. Sealed secrets dir could not be determined", baseError) } - sourcePath := filepath.Clean(filepath.Join(baseSourcePath, di.relRenderedDir, relDir, *sealedSecretsDir, fname)) - targetPath := filepath.Join(di.renderedDir, relDir, fname) + sourcePath := filepath.Clean(filepath.Join(baseSourcePath, filepath.Join(di.relRenderedDir, subdir), relDir, *sealedSecretsDir, fname)) + targetPath := filepath.Join(renderedDir, relDir, fname) if !utils.IsFile(sourcePath) { return fmt.Errorf("%s. %s not found. You might need to seal it first", baseError, sourcePath) } From 23ebe910d72ef8e2d7219117c17b389b1c34f0d3 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 12 Apr 2022 18:02:12 +0200 Subject: [PATCH 0632/2916] feat: Allow to load clusters/sealedSecrets/deployment from local git repo --- pkg/kluctl_project/git.go | 4 ++-- pkg/kluctl_project/load.go | 29 ++++++++++++++++++++++++++--- pkg/kluctl_project/load_targets.go | 2 +- pkg/types/git_project.go | 13 ++++++++++++- 4 files changed, 41 insertions(+), 7 deletions(-) diff --git a/pkg/kluctl_project/git.go b/pkg/kluctl_project/git.go index 179bf4725..e54e25e95 100644 --- a/pkg/kluctl_project/git.go +++ b/pkg/kluctl_project/git.go @@ -58,7 +58,7 @@ func (c *KluctlProjectContext) updateGitCaches() error { return nil } doUpdateExternalProject := func(p *types2.ExternalProject) error { - if p == nil { + if p == nil || p.Project == nil { return nil } return doUpdateGitProject(p.Project.Url) @@ -200,7 +200,7 @@ func (c *KluctlProjectContext) cloneKluctlProject() (gitProjectInfo, error) { return c.localProject(p), err } return c.cloneGitProject(types2.ExternalProject{ - Project: types2.GitProject{ + Project: &types2.GitProject{ Url: *c.loadArgs.ProjectUrl, Ref: c.loadArgs.ProjectRef, }, diff --git a/pkg/kluctl_project/load.go b/pkg/kluctl_project/load.go index c8e6db7e2..eb187b3ad 100644 --- a/pkg/kluctl_project/load.go +++ b/pkg/kluctl_project/load.go @@ -4,6 +4,7 @@ import ( "archive/tar" "compress/gzip" "fmt" + "github.com/kluctl/kluctl/pkg/git" types2 "github.com/kluctl/kluctl/pkg/types" "github.com/kluctl/kluctl/pkg/utils" "github.com/kluctl/kluctl/pkg/yaml" @@ -80,10 +81,32 @@ func (c *KluctlProjectContext) load(allowGit bool) error { if localDir != "" { return c.localProject(localDir), nil } - if ep == nil { + if ep == nil || ep.Project == nil { p := kluctlProjectInfo.dir - if defaultGitSubDir != "" { - p = filepath.Join(p, defaultGitSubDir) + if ep != nil { + if filepath.IsAbs(*ep.Path) { + return gitProjectInfo{}, fmt.Errorf("only paths relative to the git project root are allowed") + } + // we only allow relative paths pointing into the root git project + gitRoot, err := git.DetectGitRepositoryRoot(p) + if err != nil { + return gitProjectInfo{}, fmt.Errorf("could not determine git project root: %w", err) + } + gitRoot, err = filepath.Abs(gitRoot) + if err != nil { + return gitProjectInfo{}, err + } + p, err = filepath.Abs(filepath.Join(p, *ep.Path)) + if err != nil { + return gitProjectInfo{}, err + } + if !strings.HasPrefix(p, gitRoot) { + return gitProjectInfo{}, fmt.Errorf("path '%s' is not inside git project root '%s'", *ep.Path, gitRoot) + } + } else { + if defaultGitSubDir != "" { + p = filepath.Join(p, defaultGitSubDir) + } } return c.localProject(p), nil } diff --git a/pkg/kluctl_project/load_targets.go b/pkg/kluctl_project/load_targets.go index f3087140b..a25a205c2 100644 --- a/pkg/kluctl_project/load_targets.go +++ b/pkg/kluctl_project/load_targets.go @@ -241,7 +241,7 @@ func (c *KluctlProjectContext) cloneDynamicTargets(dynamicTargets []*dynamicTarg uniqueClones[targetInfo.dir] = nil wp.Submit(func() error { - ep := types.ExternalProject{Project: *targetInfo.gitProject} + ep := types.ExternalProject{Project: targetInfo.gitProject} ep.Project.Ref = *targetInfo.ref gi, err := c.cloneGitProject(ep, "", false, false) diff --git a/pkg/types/git_project.go b/pkg/types/git_project.go index 7d7c74d4b..c480a3424 100644 --- a/pkg/types/git_project.go +++ b/pkg/types/git_project.go @@ -30,7 +30,17 @@ func ValidateGitProject(sl validator.StructLevel) { } type ExternalProject struct { - Project GitProject `yaml:"project"` + Project *GitProject `yaml:"project,omitempty"` + Path *string `yaml:"path,omitempty"` +} + +func ValidateExternalProject(sl validator.StructLevel) { + p := sl.Current().Interface().(ExternalProject) + if p.Project == nil && p.Path == nil { + sl.ReportError(p, ".", ".", "empty", "either project or path must be set") + } else if p.Project != nil && p.Path != nil { + sl.ReportError(p, ".", ".", "empty", "only one of project or path can be set") + } } type ExternalProjects struct { @@ -50,4 +60,5 @@ func (gp *ExternalProjects) UnmarshalYAML(unmarshal func(interface{}) error) err func init() { yaml.Validator.RegisterStructValidation(ValidateGitProject, GitProject{}) + yaml.Validator.RegisterStructValidation(ValidateExternalProject, ExternalProject{}) } From d2c661cd1ba639a54acc03cd290929a45a8d02ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aljoscha=20P=C3=B6rtner?= Date: Wed, 13 Apr 2022 06:23:01 +0200 Subject: [PATCH 0633/2916] chore: move and rename pull request template MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Aljoscha Pörtner --- .../PULL_REQUEST.md => pull_request_template.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{PULL_REQUEST_TEMPLATE/PULL_REQUEST.md => pull_request_template.md} (100%) diff --git a/.github/PULL_REQUEST_TEMPLATE/PULL_REQUEST.md b/.github/pull_request_template.md similarity index 100% rename from .github/PULL_REQUEST_TEMPLATE/PULL_REQUEST.md rename to .github/pull_request_template.md From 42f548107b0e972b71153082bdfa55e422793540 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aljoscha=20P=C3=B6rtner?= Date: Wed, 13 Apr 2022 06:23:24 +0200 Subject: [PATCH 0634/2916] chore: fix template for feature requests --- .github/ISSUE_TEMPLATE/FEATURE.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/FEATURE.yml b/.github/ISSUE_TEMPLATE/FEATURE.yml index 4ae589d54..79b4a5631 100644 --- a/.github/ISSUE_TEMPLATE/FEATURE.yml +++ b/.github/ISSUE_TEMPLATE/FEATURE.yml @@ -14,7 +14,7 @@ body: id: command attributes: label: Command - description: Which command will be affected by the feature request + description: Which command will be affected by the feature request? (If you want to propose a new command, just leave all unchecked) options: - archive - check-image-updates @@ -32,7 +32,6 @@ body: - seal - validate - version - - None of the above (e.g. new command) - type: input id: persona attributes: From 0771915ee75c3189336efefe03d956eefe7d3fdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aljoscha=20P=C3=B6rtner?= Date: Wed, 13 Apr 2022 06:32:58 +0200 Subject: [PATCH 0635/2916] chore: put all options in feature request template in quotes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Aljoscha Pörtner --- .github/ISSUE_TEMPLATE/FEATURE.yml | 32 +++++++++++++++--------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/FEATURE.yml b/.github/ISSUE_TEMPLATE/FEATURE.yml index 79b4a5631..d5f2aebd7 100644 --- a/.github/ISSUE_TEMPLATE/FEATURE.yml +++ b/.github/ISSUE_TEMPLATE/FEATURE.yml @@ -16,22 +16,22 @@ body: label: Command description: Which command will be affected by the feature request? (If you want to propose a new command, just leave all unchecked) options: - - archive - - check-image-updates - - delete - - deploy - - diff - - downscale - - helm-pull - - helm-update - - list-images - - list-targets - - poke-images - - prune - - render - - seal - - validate - - version + - "archive" + - "check-image-updates" + - "delete" + - "deploy" + - "diff" + - "downscale" + - "helm-pull" + - "helm-update" + - "list-images" + - "list-targets" + - "poke-images" + - "prune" + - "render" + - "seal" + - "validate" + - "version" - type: input id: persona attributes: From df118e95c8d8072a5d6d80f20f92ec4d704ef2d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aljoscha=20P=C3=B6rtner?= Date: Wed, 13 Apr 2022 06:48:18 +0200 Subject: [PATCH 0636/2916] chore: put label in front of options in feature request MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Aljoscha Pörtner --- .github/ISSUE_TEMPLATE/FEATURE.yml | 32 +++++++++++++++--------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/FEATURE.yml b/.github/ISSUE_TEMPLATE/FEATURE.yml index d5f2aebd7..4bdf4b33c 100644 --- a/.github/ISSUE_TEMPLATE/FEATURE.yml +++ b/.github/ISSUE_TEMPLATE/FEATURE.yml @@ -16,22 +16,22 @@ body: label: Command description: Which command will be affected by the feature request? (If you want to propose a new command, just leave all unchecked) options: - - "archive" - - "check-image-updates" - - "delete" - - "deploy" - - "diff" - - "downscale" - - "helm-pull" - - "helm-update" - - "list-images" - - "list-targets" - - "poke-images" - - "prune" - - "render" - - "seal" - - "validate" - - "version" + - label: "archive" + - label: "check-image-updates" + - label: "delete" + - label: "deploy" + - label: "diff" + - label: "downscale" + - label: "helm-pull" + - label: "helm-update" + - label: "list-images" + - label: "list-targets" + - label: "poke-images" + - label: "prune" + - label: "render" + - label: "seal" + - label: "validate" + - label: "version" - type: input id: persona attributes: From 4a18c8b363668b20688d5fa057bd3c379a1f6c60 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 13 Apr 2022 08:52:22 +0200 Subject: [PATCH 0637/2916] feat: Perform a diff before a deploy and only ask for confirmation after the diff --- cmd/kluctl/commands/cmd_deploy.go | 34 +++++++++++++++++++++------ pkg/deployment/commands/deploy.go | 38 ++++++++++++++++++++++++++++--- 2 files changed, 62 insertions(+), 10 deletions(-) diff --git a/cmd/kluctl/commands/cmd_deploy.go b/cmd/kluctl/commands/cmd_deploy.go index 09b3030b1..fabd1952b 100644 --- a/cmd/kluctl/commands/cmd_deploy.go +++ b/cmd/kluctl/commands/cmd_deploy.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/kluctl/kluctl/cmd/kluctl/args" "github.com/kluctl/kluctl/pkg/deployment/commands" + "github.com/kluctl/kluctl/pkg/types" "github.com/kluctl/kluctl/pkg/utils" ) @@ -48,12 +49,6 @@ func (cmd *deployCmd) Run() error { } func (cmd *deployCmd) runCmdDeploy(ctx *commandCtx) error { - if !cmd.Yes && !cmd.DryRun { - if !utils.AskForConfirmation(fmt.Sprintf("Do you really want to deploy to the context/cluster %s?", ctx.k.Context())) { - return fmt.Errorf("aborted") - } - } - cmd2 := commands.NewDeployCommand(ctx.deploymentCollection) cmd2.ForceApply = cmd.ForceApply cmd2.ReplaceOnError = cmd.ReplaceOnError @@ -62,7 +57,12 @@ func (cmd *deployCmd) runCmdDeploy(ctx *commandCtx) error { cmd2.HookTimeout = cmd.HookTimeout cmd2.NoWait = cmd.NoWait - result, err := cmd2.Run(ctx.k) + cb := cmd.diffResultCb + if cmd.Yes || cmd.DryRun { + cb = nil + } + + result, err := cmd2.Run(ctx.k, cb) if err != nil { return err } @@ -75,3 +75,23 @@ func (cmd *deployCmd) runCmdDeploy(ctx *commandCtx) error { } return nil } + +func (cmd *deployCmd) diffResultCb(diffResult *types.CommandResult) error { + err := outputCommandResult(nil, diffResult) + if err != nil { + return err + } + if cmd.Yes || cmd.DryRun { + return nil + } + if len(diffResult.Errors) != 0 { + if !utils.AskForConfirmation("\nThe diff resulted in errors, do you still want to proceed?") { + return fmt.Errorf("aborted") + } + } else { + if !utils.AskForConfirmation("\nThe diff succeeded, do you want to proceed?") { + return fmt.Errorf("aborted") + } + } + return nil +} diff --git a/pkg/deployment/commands/deploy.go b/pkg/deployment/commands/deploy.go index b46700219..030728701 100644 --- a/pkg/deployment/commands/deploy.go +++ b/pkg/deployment/commands/deploy.go @@ -25,7 +25,7 @@ func NewDeployCommand(c *deployment.DeploymentCollection) *DeployCommand { } } -func (cmd *DeployCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, error) { +func (cmd *DeployCommand) Run(k *k8s.K8sCluster, diffResultCb func (diffResult *types.CommandResult) error) (*types.CommandResult, error) { dew := utils2.NewDeploymentErrorsAndWarnings() ru := utils2.NewRemoteObjectsUtil(dew) @@ -34,15 +34,47 @@ func (cmd *DeployCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, error) { return nil, err } + // prepare for a diff o := utils2.ApplyUtilOptions{ ForceApply: cmd.ForceApply, ReplaceOnError: cmd.ReplaceOnError, ForceReplaceOnError: cmd.ForceReplaceOnError, - DryRun: k.DryRun, - AbortOnError: cmd.AbortOnError, + DryRun: true, + AbortOnError: false, WaitObjectTimeout: cmd.HookTimeout, NoWait: cmd.NoWait, } + + if diffResultCb != nil { + au := utils2.NewApplyUtil(dew, cmd.c.Deployments, ru, k, o) + au.ApplyDeployments() + + du := utils2.NewDiffUtil(dew, cmd.c.Deployments, ru, au.AppliedObjects) + du.Diff() + + diffResult := &types.CommandResult{ + NewObjects: du.NewObjects, + ChangedObjects: du.ChangedObjects, + DeletedObjects: au.GetDeletedObjectsList(), + HookObjects: au.GetAppliedHookObjects(), + Errors: dew.GetErrorsList(), + Warnings: dew.GetWarningsList(), + SeenImages: cmd.c.Images.SeenImages(false), + } + + err := diffResultCb(diffResult) + if err != nil { + return nil, err + } + } + + // clear errors and warnings + dew.Init() + + // modify options to become a deploy + o.DryRun = k.DryRun + o.AbortOnError = cmd.AbortOnError + au := utils2.NewApplyUtil(dew, cmd.c.Deployments, ru, k, o) au.ApplyDeployments() From 40eeecbf0f413d89d827919f021a301350a6b353 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 13 Apr 2022 08:52:41 +0200 Subject: [PATCH 0638/2916] fix: Fix accidental reuse of gitProject object --- pkg/kluctl_project/load_targets.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/kluctl_project/load_targets.go b/pkg/kluctl_project/load_targets.go index a25a205c2..5dfdd8f3f 100644 --- a/pkg/kluctl_project/load_targets.go +++ b/pkg/kluctl_project/load_targets.go @@ -241,8 +241,9 @@ func (c *KluctlProjectContext) cloneDynamicTargets(dynamicTargets []*dynamicTarg uniqueClones[targetInfo.dir] = nil wp.Submit(func() error { - ep := types.ExternalProject{Project: targetInfo.gitProject} - ep.Project.Ref = *targetInfo.ref + gitProject := *targetInfo.gitProject + gitProject.Ref = *targetInfo.ref + ep := types.ExternalProject{Project: &gitProject} gi, err := c.cloneGitProject(ep, "", false, false) mutex.Lock() From 3d626af3eaf29a75f738dabf1f1fa7d11d29116a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 13 Apr 2022 12:17:08 +0200 Subject: [PATCH 0639/2916] feat: Implement multi-line progress for apply operations --- go.mod | 5 + go.sum | 10 ++ pkg/deployment/commands/deploy.go | 2 +- pkg/deployment/utils/apply_utils.go | 140 +++++++++++++++++--------- pkg/deployment/utils/errors_holder.go | 20 ++++ pkg/deployment/utils/hooks_util.go | 44 ++++---- pkg/deployment/utils/progress.go | 102 +++++++++++++++++++ pkg/types/git_project.go | 2 +- pkg/utils/prettytable.go | 4 +- pkg/utils/utils.go | 4 + pkg/validation/validation.go | 2 +- 11 files changed, 259 insertions(+), 76 deletions(-) create mode 100644 pkg/deployment/utils/progress.go diff --git a/go.mod b/go.mod index 6c1029446..df57618b9 100644 --- a/go.mod +++ b/go.mod @@ -27,6 +27,7 @@ require ( github.com/r3labs/diff/v2 v2.15.1 github.com/sirupsen/logrus v1.8.1 github.com/stretchr/testify v1.7.1 + github.com/vbauerster/mpb/v7 v7.4.1 github.com/whilp/git-urls v1.0.0 github.com/xanzy/ssh-agent v0.3.1 golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 @@ -56,6 +57,8 @@ require ( github.com/ProtonMail/go-crypto v0.0.0-20220407094043-a94812496cf5 // indirect github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect + github.com/VividCortex/ewma v1.2.0 // indirect + github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect github.com/acomagu/bufpipe v1.0.3 // indirect github.com/alessio/shellescape v1.4.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect @@ -92,6 +95,7 @@ require ( github.com/leodido/go-urn v1.2.1 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-runewidth v0.0.13 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect @@ -102,6 +106,7 @@ require ( github.com/pelletier/go-toml v1.9.4 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rivo/uniseg v0.2.0 // indirect github.com/sergi/go-diff v1.2.0 // indirect github.com/spf13/cobra v1.4.0 // indirect github.com/spf13/pflag v1.0.5 // indirect diff --git a/go.sum b/go.sum index e3a22e638..9a35f7377 100644 --- a/go.sum +++ b/go.sum @@ -120,7 +120,11 @@ github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdko github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= +github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= +github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= @@ -763,6 +767,8 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= @@ -952,6 +958,8 @@ github.com/r3labs/diff/v2 v2.15.1 h1:EOrVqPUzi+njlumoqJwiS/TgGgmZo83619FNDB9xQUg github.com/r3labs/diff/v2 v2.15.1/go.mod h1:I8noH9Fc2fjSaMxqF3G2lhDdC0b+JXCfyx85tWFM9kc= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -1048,6 +1056,8 @@ github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/vbatts/tar-split v0.11.2 h1:Via6XqJr0hceW4wff3QRzD5gAk/tatMw/4ZA7cTlIME= github.com/vbatts/tar-split v0.11.2/go.mod h1:vV3ZuO2yWSVsz+pfFzDG/upWH1JhjOiEaWq6kXyQ3VI= +github.com/vbauerster/mpb/v7 v7.4.1 h1:NhLMWQ3gNg2KJR8oeA9lO8Xvq+eNPmixDmB6JEQOUdA= +github.com/vbauerster/mpb/v7 v7.4.1/go.mod h1:Ygg2mV9Vj9sQBWqsK2m2pidcf9H3s6bNKtqd3/M4gBo= github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= diff --git a/pkg/deployment/commands/deploy.go b/pkg/deployment/commands/deploy.go index 030728701..4324d45ea 100644 --- a/pkg/deployment/commands/deploy.go +++ b/pkg/deployment/commands/deploy.go @@ -25,7 +25,7 @@ func NewDeployCommand(c *deployment.DeploymentCollection) *DeployCommand { } } -func (cmd *DeployCommand) Run(k *k8s.K8sCluster, diffResultCb func (diffResult *types.CommandResult) error) (*types.CommandResult, error) { +func (cmd *DeployCommand) Run(k *k8s.K8sCluster, diffResultCb func(diffResult *types.CommandResult) error) (*types.CommandResult, error) { dew := utils2.NewDeploymentErrorsAndWarnings() ru := utils2.NewRemoteObjectsUtil(dew) diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index d775e1efc..20659f3ea 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -1,6 +1,7 @@ package utils import ( + "context" errors2 "errors" "fmt" "github.com/kluctl/kluctl/pkg/deployment" @@ -12,9 +13,13 @@ import ( "github.com/kluctl/kluctl/pkg/utils/uo" "github.com/kluctl/kluctl/pkg/validation" log "github.com/sirupsen/logrus" + "github.com/vbauerster/mpb/v7" + "golang.org/x/sync/semaphore" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" + "os" "reflect" + "strings" "sync" "time" ) @@ -294,7 +299,7 @@ func (a *ApplyUtil) handleNewCRDs(x *uo.UnstructuredObject, err error) (bool, er return false, err } -func (a *ApplyUtil) WaitReadiness(ref k8s2.ObjectRef, timeout time.Duration) bool { +func (a *ApplyUtil) WaitReadiness(ref k8s2.ObjectRef, timeout time.Duration, pctx *progressCtx) bool { if a.o.DryRun { return true } @@ -303,23 +308,22 @@ func (a *ApplyUtil) WaitReadiness(ref k8s2.ObjectRef, timeout time.Duration) boo timeout = a.o.WaitObjectTimeout } - log2 := log.WithField("ref", ref).WithField("timeout", timeout.String()) - log2.Debugf("Waiting for object to get ready") + pctx.Debugf("Waiting for %s to get ready", ref.String()) lastLogTime := time.Now() didLog := false startTime := time.Now() for true { - log3 := log2.WithField("elapsed", (time.Second * time.Duration(time.Now().Sub(startTime).Seconds())).String()) + elapsed := time.Second * time.Duration(time.Now().Sub(startTime).Seconds()) o, apiWarnings, err := a.k.GetSingleObject(ref) a.handleApiWarnings(ref, apiWarnings) if err != nil { if errors.IsNotFound(err) { if didLog { - log3.Warningf("Cancelled waiting for object as it disappeared while waiting for it") + pctx.Warningf("Cancelled waiting for %s as it disappeared while waiting for it (%ss elapsed)", ref.String(), elapsed) } - a.HandleError(ref, fmt.Errorf("object disappeared while waiting for it to become ready")) + a.HandleError(ref, fmt.Errorf("%s disappeared while waiting for it to become ready", ref.String())) return false } a.HandleError(ref, err) @@ -328,13 +332,13 @@ func (a *ApplyUtil) WaitReadiness(ref k8s2.ObjectRef, timeout time.Duration) boo v := validation.ValidateObject(a.k, o, false) if v.Ready { if didLog { - log3.Infof("Finished waiting for object") + pctx.Infof("Finished waiting for %s (%ss elapsed)", ref.String(), elapsed) } return true } if len(v.Errors) != 0 { if didLog { - log3.Warningf("Cancelled waiting for object due to errors") + pctx.Warningf("Cancelled waiting for %s due to errors (%ss elapsed)", ref.String(), elapsed) } for _, e := range v.Errors { a.HandleError(ref, fmt.Errorf(e.Error)) @@ -343,18 +347,20 @@ func (a *ApplyUtil) WaitReadiness(ref k8s2.ObjectRef, timeout time.Duration) boo } if timeout > 0 && time.Now().Sub(startTime) >= timeout { - err := fmt.Errorf("timed out while waiting for object") - log3.Warningf(err.Error()) + err := fmt.Errorf("timed out while waiting for %s", ref.String()) + pctx.Warningf("%s (%ss elapsed)", err.Error(), elapsed) a.HandleError(ref, err) return false } + pctx.SetStatus(fmt.Sprintf("Waiting for %s to get ready... (%ss elapsed)", ref.String(), elapsed)) + if !didLog { - log3.Infof("Waiting for object to get ready...") + pctx.Infof("Waiting for %s to get ready... (%ss elapsed)", ref.String(), elapsed) didLog = true lastLogTime = time.Now() } else if didLog && time.Now().Sub(lastLogTime) >= 10*time.Second { - log3.Infof("Still waiting for object to get ready...") + pctx.Infof("Still waiting for %s to get ready... (%ss elapsed)", ref.String(), elapsed) lastLogTime = time.Now() } @@ -363,12 +369,7 @@ func (a *ApplyUtil) WaitReadiness(ref k8s2.ObjectRef, timeout time.Duration) boo return false } -func (a *ApplyUtil) applyDeploymentItem(d *deployment.DeploymentItem) { - if !d.CheckInclusionForDeploy() { - a.DoLog(d, log.InfoLevel, "Skipping") - return - } - +func (a *ApplyUtil) applyDeploymentItem(d *deployment.DeploymentItem, pctx *progressCtx) { var toDelete []k8s2.ObjectRef for _, x := range d.Config.DeleteObjects { for _, gvk := range a.k.GetGVKs(x.Group, x.Version, x.Kind) { @@ -380,12 +381,8 @@ func (a *ApplyUtil) applyDeploymentItem(d *deployment.DeploymentItem) { toDelete = append(toDelete, ref) } } - if len(toDelete) != 0 { - log.Infof("Deleting %d objects", len(toDelete)) - for _, ref := range toDelete { - a.DeleteObject(ref, false) - } - } + + h := HooksUtil{a: a} initialDeploy := true for _, o := range d.Objects { @@ -394,14 +391,6 @@ func (a *ApplyUtil) applyDeploymentItem(d *deployment.DeploymentItem) { } } - h := HooksUtil{a: a} - - if initialDeploy { - h.RunHooks(d, []string{"pre-deploy-initial", "pre-deploy"}) - } else { - h.RunHooks(d, []string{"pre-deploy-upgrade", "pre-deploy"}) - } - var applyObjects []*uo.UnstructuredObject for _, o := range d.Objects { if h.GetHook(o) != nil { @@ -410,36 +399,96 @@ func (a *ApplyUtil) applyDeploymentItem(d *deployment.DeploymentItem) { applyObjects = append(applyObjects, o) } + var preHooks []*hook + var postHooks []*hook + if initialDeploy { + preHooks = h.DetermineHooks(d, []string{"pre-deploy-initial", "pre-deploy"}) + postHooks = h.DetermineHooks(d, []string{"post-deploy-initial", "post-deploy"}) + } else { + postHooks = h.DetermineHooks(d, []string{"pre-deploy-upgrade", "pre-deploy"}) + postHooks = h.DetermineHooks(d, []string{"post-deploy-upgrade", "post-deploy"}) + } + + total := len(applyObjects) + len(preHooks) + len(postHooks) + pctx.SetTotal(int64(total)) + if !d.CheckInclusionForDeploy() { + pctx.InfofAndStatus("Skipped") + pctx.Finish() + return + } + + if len(toDelete) != 0 { + pctx.Infof("Deleting %d objects", len(toDelete)) + for i, ref := range toDelete { + pctx.SetStatus(fmt.Sprintf("Deleting object %s (%d of %d)", ref.String(), i+1, len(toDelete))) + a.DeleteObject(ref, false) + pctx.Increment() + } + } + + h.RunHooks(preHooks, pctx) + if len(applyObjects) != 0 { - a.DoLog(d, log.InfoLevel, "Applying %d objects", len(applyObjects)) + pctx.Infof("Applying %d objects", len(applyObjects)) } startTime := time.Now() didLog := false for i, o := range applyObjects { + if a.abortSignal { + break + } + + ref := o.GetK8sRef() + pctx.SetStatus(fmt.Sprintf("Applying object %s (%d of %d)", ref.String(), i+1, len(applyObjects))) a.ApplyObject(o, false, false) + pctx.Increment() if time.Now().Sub(startTime) >= 10*time.Second || (didLog && i == len(applyObjects)-1) { - a.DoLog(d, log.InfoLevel, "...applied %d of %d objects", i+1, len(applyObjects)) + pctx.Infof("...applied %d of %d objects", i+1, len(applyObjects)) startTime = time.Now() didLog = true } waitReadiness := (d.Config.WaitReadiness != nil && *d.Config.WaitReadiness) || d.WaitReadiness || utils.ParseBoolOrFalse(o.GetK8sAnnotation("kluctl.io/wait-readiness")) if !a.o.NoWait && waitReadiness { - a.WaitReadiness(o.GetK8sRef(), 0) + a.WaitReadiness(o.GetK8sRef(), 0, pctx) } } - if initialDeploy { - h.RunHooks(d, []string{"post-deploy-initial", "post-deploy"}) - } else { - h.RunHooks(d, []string{"post-deploy-upgrade", "post-deploy"}) + h.RunHooks(postHooks, pctx) + + finalStatus := "" + if len(a.AppliedObjects) != 0 { + finalStatus += fmt.Sprintf(" Applied %d objects.", len(a.AppliedObjects)) + } + if len(a.appliedHookObjects) != 0 { + finalStatus += fmt.Sprintf(" Applied %d hooks.", len(a.appliedHookObjects)) + } + if len(a.deletedObjects) != 0 { + finalStatus += fmt.Sprintf(" Deleted %d objects.", len(a.deletedObjects)) + } + if len(a.deletedHookObjects) != 0 { + finalStatus += fmt.Sprintf(" Deleted %d hooks.", len(a.deletedHookObjects)) + } + if a.dew.ErrorCount() != 0 { + finalStatus += fmt.Sprintf(" Encountered %d errors.", a.dew.ErrorCount()) } + if a.dew.WarningCount() != 0 { + finalStatus += fmt.Sprintf(" Encountered %d warnings.", a.dew.WarningCount()) + } + + pctx.SetStatus(strings.TrimSpace(finalStatus)) + pctx.Finish() } func (a *ApplyUtil) ApplyDeployments() { log.Infof("Running server-side apply for all objects") var wg sync.WaitGroup + sem := semaphore.NewWeighted(8) + p := mpb.New( + mpb.WithWidth(utils.GetTermWidth()), + mpb.WithOutput(os.Stderr), + ) for _, d_ := range a.deployments { d := d_ @@ -447,10 +496,15 @@ func (a *ApplyUtil) ApplyDeployments() { break } + pctx := newProgressCtx(p, d.RelToProjectItemDir) + wg.Add(1) go func() { defer wg.Done() - a.applyDeploymentItem(d) + _ = sem.Acquire(context.Background(), 1) + defer sem.Release(1) + + a.applyDeploymentItem(d, pctx) }() barrier := (d.Config.Barrier != nil && *d.Config.Barrier) || d.Barrier @@ -460,6 +514,7 @@ func (a *ApplyUtil) ApplyDeployments() { } } wg.Wait() + p.Wait() } func (a *ApplyUtil) ReplaceObject(ref k8s2.ObjectRef, firstVersion *uo.UnstructuredObject, callback func(o *uo.UnstructuredObject) (*uo.UnstructuredObject, error)) { @@ -511,11 +566,6 @@ func (a *ApplyUtil) ReplaceObject(ref k8s2.ObjectRef, firstVersion *uo.Unstructu a.HandleError(ref, fmt.Errorf("unexpected end of loop")) } -func (a *ApplyUtil) DoLog(d *deployment.DeploymentItem, level log.Level, s string, f ...interface{}) { - s = fmt.Sprintf("%s: %s", d.RelToProjectItemDir, fmt.Sprintf(s, f...)) - log.StandardLogger().Logf(level, s) -} - func (a *ApplyUtil) GetDeletedObjectsList() []k8s2.ObjectRef { var ret []k8s2.ObjectRef for ref := range a.deletedObjects { diff --git a/pkg/deployment/utils/errors_holder.go b/pkg/deployment/utils/errors_holder.go index 5a6ef2ccd..64d74301e 100644 --- a/pkg/deployment/utils/errors_holder.go +++ b/pkg/deployment/utils/errors_holder.go @@ -72,6 +72,26 @@ func (dew *DeploymentErrorsAndWarnings) HadError(ref k8s.ObjectRef) bool { return ok } +func (dew *DeploymentErrorsAndWarnings) ErrorCount() int { + dew.mutex.Lock() + defer dew.mutex.Unlock() + count := 0 + for _, m := range dew.errors { + count += len(m) + } + return count +} + +func (dew *DeploymentErrorsAndWarnings) WarningCount() int { + dew.mutex.Lock() + defer dew.mutex.Unlock() + count := 0 + for _, m := range dew.warnings { + count += len(m) + } + return count +} + func (dew *DeploymentErrorsAndWarnings) GetErrorsList() []types.DeploymentError { dew.mutex.Lock() defer dew.mutex.Unlock() diff --git a/pkg/deployment/utils/hooks_util.go b/pkg/deployment/utils/hooks_util.go index cb83914e9..869a1a4f1 100644 --- a/pkg/deployment/utils/hooks_util.go +++ b/pkg/deployment/utils/hooks_util.go @@ -6,7 +6,6 @@ import ( "github.com/kluctl/kluctl/pkg/types/k8s" "github.com/kluctl/kluctl/pkg/utils" "github.com/kluctl/kluctl/pkg/utils/uo" - log "github.com/sirupsen/logrus" "sort" "strconv" "strings" @@ -50,11 +49,7 @@ type hook struct { timeout time.Duration } -func (u *HooksUtil) RunHooks(d *deployment.DeploymentItem, hooks []string) { - doLog := func(level log.Level, s string, f ...interface{}) { - u.a.DoLog(d, level, s, f...) - } - +func (u *HooksUtil) DetermineHooks(d *deployment.DeploymentItem, hooks []string) []*hook { var l []*hook for _, h := range u.getSortedHooksList(d.Objects) { for h2 := range h.hooks { @@ -64,18 +59,14 @@ func (u *HooksUtil) RunHooks(d *deployment.DeploymentItem, hooks []string) { } } } + return l +} - if len(l) != 0 && log.IsLevelEnabled(log.DebugLevel) { - doLog(log.DebugLevel, "Sorted hooks:") - for _, h := range l { - doLog(log.DebugLevel, " %s", h.object.GetK8sRef().String()) - } - } - +func (u *HooksUtil) RunHooks(hooks []*hook, pctx *progressCtx) { var deleteBeforeObjects []*hook var applyObjects []*hook - for _, h := range l { + for _, h := range hooks { if u.a.abortSignal { return } @@ -85,32 +76,33 @@ func (u *HooksUtil) RunHooks(d *deployment.DeploymentItem, hooks []string) { applyObjects = append(applyObjects, h) } - doDeleteForPolicy := func(h *hook) bool { + doDeleteForPolicy := func(h *hook, i int, cnt int) bool { ref := h.object.GetK8sRef() var dpStr []string for p := range h.deletePolicies { dpStr = append(dpStr, p) } - doLog(log.DebugLevel, "Deleting hook %s due to hook-delete-policy %s", ref.String(), strings.Join(dpStr, ",")) + pctx.InfofAndStatus("Deleting hook %s due to hook-delete-policy %s (%d of %d)", ref.String(), strings.Join(dpStr, ","), i+1, cnt) return u.a.DeleteObject(ref, true) } if len(deleteBeforeObjects) != 0 { - doLog(log.InfoLevel, "Deleting %d hooks before hook execution", len(deleteBeforeObjects)) + pctx.Infof("Deleting %d hooks before hook execution", len(deleteBeforeObjects)) } - for _, h := range deleteBeforeObjects { - doDeleteForPolicy(h) + for i, h := range deleteBeforeObjects { + doDeleteForPolicy(h, i, len(deleteBeforeObjects)) } waitResults := make(map[k8s.ObjectRef]bool) if len(applyObjects) != 0 { - doLog(log.InfoLevel, "Applying %d hooks", len(applyObjects)) + pctx.Infof("Applying %d hooks", len(applyObjects)) } - for _, h := range applyObjects { + for i, h := range applyObjects { ref := h.object.GetK8sRef() _, replaced := h.deletePolicies["before-hook-creation"] - doLog(log.DebugLevel, "Applying hook %s", ref.String()) + pctx.DebugfAndStatus("Applying hook %s (%d of %d)", ref.String(), i+1, len(applyObjects)) u.a.ApplyObject(h.object, replaced, true) + pctx.Increment() if u.a.HadError(ref) { continue @@ -118,7 +110,7 @@ func (u *HooksUtil) RunHooks(d *deployment.DeploymentItem, hooks []string) { if !h.wait { continue } - waitResults[ref] = u.a.WaitReadiness(ref, h.timeout) + waitResults[ref] = u.a.WaitReadiness(ref, h.timeout, pctx) } var deleteAfterObjects []*hook @@ -141,10 +133,10 @@ func (u *HooksUtil) RunHooks(d *deployment.DeploymentItem, hooks []string) { } if len(deleteAfterObjects) != 0 { - doLog(log.InfoLevel, "Deleting %d hooks after hook execution", len(deleteAfterObjects)) + pctx.Infof("Deleting %d hooks after hook execution", len(deleteAfterObjects)) } - for _, h := range deleteAfterObjects { - doDeleteForPolicy(h) + for i, h := range deleteAfterObjects { + doDeleteForPolicy(h, i, len(deleteAfterObjects)) } } diff --git a/pkg/deployment/utils/progress.go b/pkg/deployment/utils/progress.go new file mode 100644 index 000000000..0f89c30e7 --- /dev/null +++ b/pkg/deployment/utils/progress.go @@ -0,0 +1,102 @@ +package utils + +import ( + "fmt" + "github.com/mattn/go-isatty" + log "github.com/sirupsen/logrus" + "github.com/vbauerster/mpb/v7" + "github.com/vbauerster/mpb/v7/decor" + "os" + "strings" + "sync" +) + +type progressCtx struct { + bar *mpb.Bar + total int64 + name string + status string + mutex sync.Mutex +} + +func newProgressCtx(p *mpb.Progress, name string) *progressCtx { + pctx := &progressCtx{ + status: "Initializing...", + total: -1, + name: name, + } + if !isatty.IsTerminal(os.Stderr.Fd()) || name == "" { + return pctx + } + pctx.bar = p.AddBar(-1, + mpb.BarWidth(40), + mpb.PrependDecorators(decor.Name(name+": ", decor.WCSyncSpaceR)), + mpb.AppendDecorators(decor.Any(pctx.DecorFunc)), + ) + return pctx +} + +func (ctx *progressCtx) Logf(level log.Level, s string, args ...interface{}) { + if ctx.bar == nil { + s = fmt.Sprintf("%s: %s", ctx.name, s) + log.StandardLogger().Logf(level, s, args...) + } +} + +func (ctx *progressCtx) Infof(s string, args ...interface{}) { + ctx.Logf(log.InfoLevel, s, args...) +} + +func (ctx *progressCtx) Warningf(s string, args ...interface{}) { + ctx.Logf(log.WarnLevel, s, args...) +} + +func (ctx *progressCtx) Debugf(s string, args ...interface{}) { + ctx.Logf(log.DebugLevel, s, args...) +} + +func (ctx *progressCtx) InfofAndStatus(s string, args ...interface{}) { + ctx.Infof(s, args...) + ctx.SetStatus(fmt.Sprintf(s, args...)) +} + +func (ctx *progressCtx) DebugfAndStatus(s string, args ...interface{}) { + ctx.Debugf(s, args...) + ctx.SetStatus(fmt.Sprintf(s, args...)) +} + +func (ctx *progressCtx) SetStatus(s string) { + ctx.mutex.Lock() + defer ctx.mutex.Unlock() + ctx.status = s +} + +func (ctx *progressCtx) DecorFunc(st decor.Statistics) string { + ctx.mutex.Lock() + defer ctx.mutex.Unlock() + fillerLen := st.AvailableWidth - len(ctx.status) - 40 + if fillerLen < 0 { + fillerLen = 0 + } + return fmt.Sprintf("%s%s", ctx.status, strings.Repeat(" ", fillerLen)) +} + +func (ctx *progressCtx) SetTotal(total int64) { + ctx.total = total + if ctx.bar != nil { + ctx.bar.SetTotal(total, false) + ctx.bar.EnableTriggerComplete() + } +} + +func (ctx *progressCtx) Increment() { + if ctx.bar != nil { + ctx.bar.Increment() + } +} + +func (ctx *progressCtx) Finish() { + if ctx.bar != nil { + ctx.bar.SetCurrent(ctx.total) + } +} diff --git a/pkg/types/git_project.go b/pkg/types/git_project.go index c480a3424..db60413da 100644 --- a/pkg/types/git_project.go +++ b/pkg/types/git_project.go @@ -31,7 +31,7 @@ func ValidateGitProject(sl validator.StructLevel) { type ExternalProject struct { Project *GitProject `yaml:"project,omitempty"` - Path *string `yaml:"path,omitempty"` + Path *string `yaml:"path,omitempty"` } func ValidateExternalProject(sl validator.StructLevel) { diff --git a/pkg/utils/prettytable.go b/pkg/utils/prettytable.go index 2a679196a..cb9858530 100644 --- a/pkg/utils/prettytable.go +++ b/pkg/utils/prettytable.go @@ -25,7 +25,7 @@ func (t *PrettyTable) SortRows(col int) { }) } -func getTermWidth() int { +func GetTermWidth() int { if c, ok := os.LookupEnv("COLUMNS"); ok { tw, err := strconv.ParseInt(c, 10, 32) if err == nil { @@ -82,7 +82,7 @@ func (t *PrettyTable) Render(limitWidths []int) string { } if len(limitWidths) < cols { - tw := getTermWidth() + tw := GetTermWidth() // last column should use all remaining space tw = tw - widthSum - (cols-1)*3 - 4 if tw <= 0 { diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 632d84706..aa5433d9e 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -58,3 +58,7 @@ func ParseBoolOrFalse(s *string) bool { } return b } + +func StrPtr(s string) *string { + return &s +} diff --git a/pkg/validation/validation.go b/pkg/validation/validation.go index 3a9585813..de1e18d4e 100644 --- a/pkg/validation/validation.go +++ b/pkg/validation/validation.go @@ -132,7 +132,7 @@ func ValidateObject(k *k8s.K8sCluster, o *uo.UnstructuredObject, notReadyIsError } age := time.Now().Sub(o.GetK8sCreationTime()) - if age > 15 * time.Second { + if age > 15*time.Second { // TODO this is a hack for CRDs that pretend that a status should be there but the corresponding // controllers/operators don't set it for whatever reason (e.g. cilium) return From 0a717d4f1e1fa560dbb458a6f2a9f11b1226761d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 13 Apr 2022 13:47:52 +0200 Subject: [PATCH 0640/2916] fix: Use one ApplyUtil per deployment item --- pkg/deployment/commands/deploy.go | 14 +- pkg/deployment/commands/diff.go | 8 +- pkg/deployment/commands/downscale.go | 6 +- pkg/deployment/commands/poke_images.go | 6 +- pkg/deployment/commands/validate.go | 5 +- pkg/deployment/utils/apply_utils.go | 218 ++++++++++++++++--------- pkg/deployment/utils/errors_holder.go | 20 --- pkg/deployment/utils/hooks_util.go | 18 +- pkg/deployment/utils/progress.go | 14 +- 9 files changed, 182 insertions(+), 127 deletions(-) diff --git a/pkg/deployment/commands/deploy.go b/pkg/deployment/commands/deploy.go index 4324d45ea..e90591635 100644 --- a/pkg/deployment/commands/deploy.go +++ b/pkg/deployment/commands/deploy.go @@ -35,7 +35,7 @@ func (cmd *DeployCommand) Run(k *k8s.K8sCluster, diffResultCb func(diffResult *t } // prepare for a diff - o := utils2.ApplyUtilOptions{ + o := &utils2.ApplyUtilOptions{ ForceApply: cmd.ForceApply, ReplaceOnError: cmd.ReplaceOnError, ForceReplaceOnError: cmd.ForceReplaceOnError, @@ -46,16 +46,16 @@ func (cmd *DeployCommand) Run(k *k8s.K8sCluster, diffResultCb func(diffResult *t } if diffResultCb != nil { - au := utils2.NewApplyUtil(dew, cmd.c.Deployments, ru, k, o) + au := utils2.NewApplyDeploymentsUtil(dew, cmd.c.Deployments, ru, k, o) au.ApplyDeployments() - du := utils2.NewDiffUtil(dew, cmd.c.Deployments, ru, au.AppliedObjects) + du := utils2.NewDiffUtil(dew, cmd.c.Deployments, ru, au.GetAppliedObjectsMap()) du.Diff() diffResult := &types.CommandResult{ NewObjects: du.NewObjects, ChangedObjects: du.ChangedObjects, - DeletedObjects: au.GetDeletedObjectsList(), + DeletedObjects: au.GetDeletedObjects(), HookObjects: au.GetAppliedHookObjects(), Errors: dew.GetErrorsList(), Warnings: dew.GetWarningsList(), @@ -75,10 +75,10 @@ func (cmd *DeployCommand) Run(k *k8s.K8sCluster, diffResultCb func(diffResult *t o.DryRun = k.DryRun o.AbortOnError = cmd.AbortOnError - au := utils2.NewApplyUtil(dew, cmd.c.Deployments, ru, k, o) + au := utils2.NewApplyDeploymentsUtil(dew, cmd.c.Deployments, ru, k, o) au.ApplyDeployments() - du := utils2.NewDiffUtil(dew, cmd.c.Deployments, ru, au.AppliedObjects) + du := utils2.NewDiffUtil(dew, cmd.c.Deployments, ru, au.GetAppliedObjectsMap()) du.Diff() orphanObjects, err := FindOrphanObjects(k, ru, cmd.c) @@ -88,7 +88,7 @@ func (cmd *DeployCommand) Run(k *k8s.K8sCluster, diffResultCb func(diffResult *t return &types.CommandResult{ NewObjects: du.NewObjects, ChangedObjects: du.ChangedObjects, - DeletedObjects: au.GetDeletedObjectsList(), + DeletedObjects: au.GetDeletedObjects(), HookObjects: au.GetAppliedHookObjects(), OrphanObjects: orphanObjects, Errors: dew.GetErrorsList(), diff --git a/pkg/deployment/commands/diff.go b/pkg/deployment/commands/diff.go index 93b910a1c..106c98048 100644 --- a/pkg/deployment/commands/diff.go +++ b/pkg/deployment/commands/diff.go @@ -33,7 +33,7 @@ func (cmd *DiffCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, error) { return nil, err } - o := utils.ApplyUtilOptions{ + o := &utils.ApplyUtilOptions{ ForceApply: cmd.ForceApply, ReplaceOnError: cmd.ReplaceOnError, ForceReplaceOnError: cmd.ForceReplaceOnError, @@ -41,10 +41,10 @@ func (cmd *DiffCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, error) { AbortOnError: false, WaitObjectTimeout: 0, } - au := utils.NewApplyUtil(dew, cmd.c.Deployments, ru, k, o) + au := utils.NewApplyDeploymentsUtil(dew, cmd.c.Deployments, ru, k, o) au.ApplyDeployments() - du := utils.NewDiffUtil(dew, cmd.c.Deployments, ru, au.AppliedObjects) + du := utils.NewDiffUtil(dew, cmd.c.Deployments, ru, au.GetAppliedObjectsMap()) du.IgnoreTags = cmd.IgnoreTags du.IgnoreLabels = cmd.IgnoreLabels du.IgnoreAnnotations = cmd.IgnoreAnnotations @@ -57,7 +57,7 @@ func (cmd *DiffCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, error) { return &types.CommandResult{ NewObjects: du.NewObjects, ChangedObjects: du.ChangedObjects, - DeletedObjects: au.GetDeletedObjectsList(), + DeletedObjects: au.GetDeletedObjects(), HookObjects: au.GetAppliedHookObjects(), OrphanObjects: orphanObjects, Errors: dew.GetErrorsList(), diff --git a/pkg/deployment/commands/downscale.go b/pkg/deployment/commands/downscale.go index 02efcb24d..dd84f9e13 100644 --- a/pkg/deployment/commands/downscale.go +++ b/pkg/deployment/commands/downscale.go @@ -31,7 +31,7 @@ func (cmd *DownscaleCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, error return nil, err } - au := utils2.NewApplyUtil(dew, cmd.c.Deployments, ru, k, utils2.ApplyUtilOptions{}) + ad := utils2.NewApplyDeploymentsUtil(dew, cmd.c.Deployments, ru, k, &utils2.ApplyUtilOptions{}) appliedObjects := make(map[k8s2.ObjectRef]*uo.UnstructuredObject) @@ -39,6 +39,8 @@ func (cmd *DownscaleCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, error if !d.CheckInclusionForDeploy() { continue } + pctx := utils2.NewProgressCtx(nil, d.RelToProjectItemDir) + au := ad.NewApplyUtil(pctx) for _, o := range d.Objects { o := o ref := o.GetK8sRef() @@ -66,7 +68,7 @@ func (cmd *DownscaleCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, error return &types.CommandResult{ NewObjects: du.NewObjects, ChangedObjects: du.ChangedObjects, - DeletedObjects: au.GetDeletedObjectsList(), + DeletedObjects: ad.GetDeletedObjects(), Errors: dew.GetErrorsList(), Warnings: dew.GetWarningsList(), SeenImages: cmd.c.Images.SeenImages(false), diff --git a/pkg/deployment/commands/poke_images.go b/pkg/deployment/commands/poke_images.go index 01a207e50..be2fe14f1 100644 --- a/pkg/deployment/commands/poke_images.go +++ b/pkg/deployment/commands/poke_images.go @@ -67,7 +67,7 @@ func (cmd *PokeImagesCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, erro return o, nil } - au := utils2.NewApplyUtil(dew, cmd.c.Deployments, ru, k, utils2.ApplyUtilOptions{}) + ad := utils2.NewApplyDeploymentsUtil(dew, cmd.c.Deployments, ru, k, &utils2.ApplyUtilOptions{}) for ref, containers := range containersAndImages { ref := ref @@ -75,6 +75,8 @@ func (cmd *PokeImagesCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, erro wg.Add(1) go func() { defer wg.Done() + pctx := utils2.NewProgressCtx(nil, ref.String()) + au := ad.NewApplyUtil(pctx) au.ReplaceObject(ref, ru.GetRemoteObject(ref), func(o *uo.UnstructuredObject) (*uo.UnstructuredObject, error) { return doPokeImage(containers, o) }) @@ -82,7 +84,7 @@ func (cmd *PokeImagesCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, erro } wg.Wait() - du := utils2.NewDiffUtil(dew, cmd.c.Deployments, ru, au.AppliedObjects) + du := utils2.NewDiffUtil(dew, cmd.c.Deployments, ru, ad.GetAppliedObjectsMap()) du.Diff() return &types.CommandResult{ diff --git a/pkg/deployment/commands/validate.go b/pkg/deployment/commands/validate.go index 94b1add25..6cf14afdc 100644 --- a/pkg/deployment/commands/validate.go +++ b/pkg/deployment/commands/validate.go @@ -34,12 +34,13 @@ func (cmd *ValidateCommand) Run(k *k8s.K8sCluster) (*types.ValidateResult, error return nil, err } - a := utils2.NewApplyUtil(cmd.dew, cmd.c.Deployments, cmd.ru, k, utils2.ApplyUtilOptions{}) - h := utils2.NewHooksUtil(a) + ad := utils2.NewApplyDeploymentsUtil(cmd.dew, cmd.c.Deployments, cmd.ru, k, &utils2.ApplyUtilOptions{}) for _, d := range cmd.c.Deployments { if !d.CheckInclusionForDeploy() { continue } + au := ad.NewApplyUtil(utils2.NewProgressCtx(nil, d.RelToProjectItemDir)) + h := utils2.NewHooksUtil(au) for _, o := range d.Objects { hook := h.GetHook(o) if hook != nil && !hook.IsPersistent() { diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index 20659f3ea..c0f0819a0 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -21,6 +21,7 @@ import ( "reflect" "strings" "sync" + "sync/atomic" "time" ) @@ -35,34 +36,66 @@ type ApplyUtilOptions struct { } type ApplyUtil struct { + dew *DeploymentErrorsAndWarnings + errorCount int + warningCount int + appliedObjects map[k8s2.ObjectRef]*uo.UnstructuredObject + appliedHookObjects map[k8s2.ObjectRef]*uo.UnstructuredObject + deletedObjects map[k8s2.ObjectRef]bool + deletedHookObjects map[k8s2.ObjectRef]bool + mutex sync.Mutex + + abortSignal *atomic.Value + deployedNewCRD *atomic.Value + + ru *RemoteObjectUtils + k *k8s.K8sCluster + o *ApplyUtilOptions + pctx *progressCtx +} + +type ApplyDeploymentsUtil struct { dew *DeploymentErrorsAndWarnings deployments []*deployment.DeploymentItem ru *RemoteObjectUtils k *k8s.K8sCluster - o ApplyUtilOptions + o *ApplyUtilOptions - AppliedObjects map[k8s2.ObjectRef]*uo.UnstructuredObject - appliedHookObjects map[k8s2.ObjectRef]*uo.UnstructuredObject - deletedObjects map[k8s2.ObjectRef]bool - deletedHookObjects map[k8s2.ObjectRef]bool - abortSignal bool - deployedNewCRD bool - mutex sync.Mutex + abortSignal atomic.Value + deployedNewCRD atomic.Value + + results []*ApplyUtil +} + +func NewApplyDeploymentsUtil(dew *DeploymentErrorsAndWarnings, deployments []*deployment.DeploymentItem, ru *RemoteObjectUtils, k *k8s.K8sCluster, o *ApplyUtilOptions) *ApplyDeploymentsUtil { + ret := &ApplyDeploymentsUtil{ + dew: dew, + deployments: deployments, + ru: ru, + k: k, + o: o, + } + ret.abortSignal.Store(false) + ret.deployedNewCRD.Store(false) + return ret } -func NewApplyUtil(dew *DeploymentErrorsAndWarnings, deployments []*deployment.DeploymentItem, ru *RemoteObjectUtils, k *k8s.K8sCluster, o ApplyUtilOptions) *ApplyUtil { - return &ApplyUtil{ - dew: dew, - deployments: deployments, - ru: ru, - k: k, - o: o, - AppliedObjects: map[k8s2.ObjectRef]*uo.UnstructuredObject{}, +func (ad *ApplyDeploymentsUtil) NewApplyUtil(pctx *progressCtx) *ApplyUtil { + ret := &ApplyUtil{ + dew: ad.dew, + appliedObjects: map[k8s2.ObjectRef]*uo.UnstructuredObject{}, appliedHookObjects: map[k8s2.ObjectRef]*uo.UnstructuredObject{}, deletedObjects: map[k8s2.ObjectRef]bool{}, deletedHookObjects: map[k8s2.ObjectRef]bool{}, - deployedNewCRD: true, // assume someone deployed CRDs in the meantime - } + abortSignal: &ad.abortSignal, + deployedNewCRD: &ad.deployedNewCRD, + ru: ad.ru, + k: ad.k, + o: ad.o, + pctx: pctx, + } + ad.results = append(ad.results, ret) + return ret } func (a *ApplyUtil) handleResult(appliedObject *uo.UnstructuredObject, hook bool) { @@ -73,26 +106,35 @@ func (a *ApplyUtil) handleResult(appliedObject *uo.UnstructuredObject, hook bool if hook { a.appliedHookObjects[ref] = appliedObject } - a.AppliedObjects[ref] = appliedObject + a.appliedObjects[ref] = appliedObject } func (a *ApplyUtil) handleApiWarnings(ref k8s2.ObjectRef, warnings []k8s.ApiWarning) { + a.mutex.Lock() + defer a.mutex.Unlock() + a.dew.AddApiWarnings(ref, warnings) + a.warningCount += len(warnings) } func (a *ApplyUtil) HandleWarning(ref k8s2.ObjectRef, warning error) { + a.mutex.Lock() + defer a.mutex.Unlock() + a.dew.AddWarning(ref, warning) + a.warningCount++ } func (a *ApplyUtil) HandleError(ref k8s2.ObjectRef, err error) { a.mutex.Lock() defer a.mutex.Unlock() - if a.o.AbortOnError { - a.abortSignal = true + if a.o.AbortOnError && a.abortSignal != nil { + a.abortSignal.Store(true) } a.dew.AddError(ref, err) + a.errorCount++ } func (a *ApplyUtil) HadError(ref k8s2.ObjectRef) bool { @@ -275,10 +317,8 @@ func (a *ApplyUtil) handleNewCRDs(x *uo.UnstructuredObject, err error) (bool, er if err != nil && meta.IsNoMatchError(err) { // maybe this was a resource for which the CRD was only deployed recently, so we should do rediscovery and then // retry the patch - a.mutex.Lock() - defer a.mutex.Unlock() - if a.deployedNewCRD { - a.deployedNewCRD = false + if a.deployedNewCRD.Load().(bool) { + a.deployedNewCRD.Store(false) err = a.k.RediscoverResources() if err != nil { return false, err @@ -289,9 +329,7 @@ func (a *ApplyUtil) handleNewCRDs(x *uo.UnstructuredObject, err error) (bool, er ref := x.GetK8sRef() if ref.GVK.Group == "apiextensions.k8s.io" && ref.GVK.Kind == "CustomResourceDefinition" { // this is a freshly deployed CRD, so we must perform rediscovery in case an api resource can't be found - a.mutex.Lock() - defer a.mutex.Unlock() - a.deployedNewCRD = true + a.deployedNewCRD.Store(true) return true, nil } return false, nil @@ -299,7 +337,7 @@ func (a *ApplyUtil) handleNewCRDs(x *uo.UnstructuredObject, err error) (bool, er return false, err } -func (a *ApplyUtil) WaitReadiness(ref k8s2.ObjectRef, timeout time.Duration, pctx *progressCtx) bool { +func (a *ApplyUtil) WaitReadiness(ref k8s2.ObjectRef, timeout time.Duration) bool { if a.o.DryRun { return true } @@ -308,20 +346,20 @@ func (a *ApplyUtil) WaitReadiness(ref k8s2.ObjectRef, timeout time.Duration, pct timeout = a.o.WaitObjectTimeout } - pctx.Debugf("Waiting for %s to get ready", ref.String()) + a.pctx.Debugf("Waiting for %s to get ready", ref.String()) lastLogTime := time.Now() didLog := false startTime := time.Now() for true { - elapsed := time.Second * time.Duration(time.Now().Sub(startTime).Seconds()) + elapsed := int(time.Now().Sub(startTime).Seconds()) o, apiWarnings, err := a.k.GetSingleObject(ref) a.handleApiWarnings(ref, apiWarnings) if err != nil { if errors.IsNotFound(err) { if didLog { - pctx.Warningf("Cancelled waiting for %s as it disappeared while waiting for it (%ss elapsed)", ref.String(), elapsed) + a.pctx.Warningf("Cancelled waiting for %s as it disappeared while waiting for it (%ds elapsed)", ref.String(), elapsed) } a.HandleError(ref, fmt.Errorf("%s disappeared while waiting for it to become ready", ref.String())) return false @@ -332,13 +370,13 @@ func (a *ApplyUtil) WaitReadiness(ref k8s2.ObjectRef, timeout time.Duration, pct v := validation.ValidateObject(a.k, o, false) if v.Ready { if didLog { - pctx.Infof("Finished waiting for %s (%ss elapsed)", ref.String(), elapsed) + a.pctx.Infof("Finished waiting for %s (%ds elapsed)", ref.String(), elapsed) } return true } if len(v.Errors) != 0 { if didLog { - pctx.Warningf("Cancelled waiting for %s due to errors (%ss elapsed)", ref.String(), elapsed) + a.pctx.Warningf("Cancelled waiting for %s due to errors (%ds elapsed)", ref.String(), elapsed) } for _, e := range v.Errors { a.HandleError(ref, fmt.Errorf(e.Error)) @@ -348,19 +386,19 @@ func (a *ApplyUtil) WaitReadiness(ref k8s2.ObjectRef, timeout time.Duration, pct if timeout > 0 && time.Now().Sub(startTime) >= timeout { err := fmt.Errorf("timed out while waiting for %s", ref.String()) - pctx.Warningf("%s (%ss elapsed)", err.Error(), elapsed) + a.pctx.Warningf("%s (%ds elapsed)", err.Error(), elapsed) a.HandleError(ref, err) return false } - pctx.SetStatus(fmt.Sprintf("Waiting for %s to get ready... (%ss elapsed)", ref.String(), elapsed)) + a.pctx.SetStatus(fmt.Sprintf("Waiting for %s to get ready... (%ds elapsed)", ref.String(), elapsed)) if !didLog { - pctx.Infof("Waiting for %s to get ready... (%ss elapsed)", ref.String(), elapsed) + a.pctx.Infof("Waiting for %s to get ready... (%ds elapsed)", ref.String(), elapsed) didLog = true lastLogTime = time.Now() } else if didLog && time.Now().Sub(lastLogTime) >= 10*time.Second { - pctx.Infof("Still waiting for %s to get ready... (%ss elapsed)", ref.String(), elapsed) + a.pctx.Infof("Still waiting for %s to get ready... (%ds elapsed)", ref.String(), elapsed) lastLogTime = time.Now() } @@ -369,7 +407,7 @@ func (a *ApplyUtil) WaitReadiness(ref k8s2.ObjectRef, timeout time.Duration, pct return false } -func (a *ApplyUtil) applyDeploymentItem(d *deployment.DeploymentItem, pctx *progressCtx) { +func (a *ApplyUtil) applyDeploymentItem(d *deployment.DeploymentItem) { var toDelete []k8s2.ObjectRef for _, x := range d.Config.DeleteObjects { for _, gvk := range a.k.GetGVKs(x.Group, x.Version, x.Kind) { @@ -410,55 +448,55 @@ func (a *ApplyUtil) applyDeploymentItem(d *deployment.DeploymentItem, pctx *prog } total := len(applyObjects) + len(preHooks) + len(postHooks) - pctx.SetTotal(int64(total)) + a.pctx.SetTotal(int64(total)) if !d.CheckInclusionForDeploy() { - pctx.InfofAndStatus("Skipped") - pctx.Finish() + a.pctx.InfofAndStatus("Skipped") + a.pctx.Finish() return } if len(toDelete) != 0 { - pctx.Infof("Deleting %d objects", len(toDelete)) + a.pctx.Infof("Deleting %d objects", len(toDelete)) for i, ref := range toDelete { - pctx.SetStatus(fmt.Sprintf("Deleting object %s (%d of %d)", ref.String(), i+1, len(toDelete))) + a.pctx.SetStatus(fmt.Sprintf("Deleting object %s (%d of %d)", ref.String(), i+1, len(toDelete))) a.DeleteObject(ref, false) - pctx.Increment() + a.pctx.Increment() } } - h.RunHooks(preHooks, pctx) + h.RunHooks(preHooks) if len(applyObjects) != 0 { - pctx.Infof("Applying %d objects", len(applyObjects)) + a.pctx.Infof("Applying %d objects", len(applyObjects)) } startTime := time.Now() didLog := false for i, o := range applyObjects { - if a.abortSignal { + if a.abortSignal.Load().(bool) { break } ref := o.GetK8sRef() - pctx.SetStatus(fmt.Sprintf("Applying object %s (%d of %d)", ref.String(), i+1, len(applyObjects))) + a.pctx.SetStatus(fmt.Sprintf("Applying object %s (%d of %d)", ref.String(), i+1, len(applyObjects))) a.ApplyObject(o, false, false) - pctx.Increment() + a.pctx.Increment() if time.Now().Sub(startTime) >= 10*time.Second || (didLog && i == len(applyObjects)-1) { - pctx.Infof("...applied %d of %d objects", i+1, len(applyObjects)) + a.pctx.Infof("...applied %d of %d objects", i+1, len(applyObjects)) startTime = time.Now() didLog = true } waitReadiness := (d.Config.WaitReadiness != nil && *d.Config.WaitReadiness) || d.WaitReadiness || utils.ParseBoolOrFalse(o.GetK8sAnnotation("kluctl.io/wait-readiness")) if !a.o.NoWait && waitReadiness { - a.WaitReadiness(o.GetK8sRef(), 0, pctx) + a.WaitReadiness(o.GetK8sRef(), 0) } } - h.RunHooks(postHooks, pctx) + h.RunHooks(postHooks) finalStatus := "" - if len(a.AppliedObjects) != 0 { - finalStatus += fmt.Sprintf(" Applied %d objects.", len(a.AppliedObjects)) + if len(a.appliedObjects) != 0 { + finalStatus += fmt.Sprintf(" Applied %d objects.", len(a.appliedObjects)) } if len(a.appliedHookObjects) != 0 { finalStatus += fmt.Sprintf(" Applied %d hooks.", len(a.appliedHookObjects)) @@ -469,18 +507,18 @@ func (a *ApplyUtil) applyDeploymentItem(d *deployment.DeploymentItem, pctx *prog if len(a.deletedHookObjects) != 0 { finalStatus += fmt.Sprintf(" Deleted %d hooks.", len(a.deletedHookObjects)) } - if a.dew.ErrorCount() != 0 { - finalStatus += fmt.Sprintf(" Encountered %d errors.", a.dew.ErrorCount()) + if a.errorCount != 0 { + finalStatus += fmt.Sprintf(" Encountered %d errors.", a.errorCount) } - if a.dew.WarningCount() != 0 { - finalStatus += fmt.Sprintf(" Encountered %d warnings.", a.dew.WarningCount()) + if a.warningCount != 0 { + finalStatus += fmt.Sprintf(" Encountered %d warnings.", a.warningCount) } - pctx.SetStatus(strings.TrimSpace(finalStatus)) - pctx.Finish() + a.pctx.SetStatus(strings.TrimSpace(finalStatus)) + a.pctx.Finish() } -func (a *ApplyUtil) ApplyDeployments() { +func (a *ApplyDeploymentsUtil) ApplyDeployments() { log.Infof("Running server-side apply for all objects") var wg sync.WaitGroup @@ -492,11 +530,12 @@ func (a *ApplyUtil) ApplyDeployments() { for _, d_ := range a.deployments { d := d_ - if a.abortSignal { + if a.abortSignal.Load().(bool) { break } - pctx := newProgressCtx(p, d.RelToProjectItemDir) + pctx := NewProgressCtx(p, d.RelToProjectItemDir) + a2 := a.NewApplyUtil(pctx) wg.Add(1) go func() { @@ -504,7 +543,7 @@ func (a *ApplyUtil) ApplyDeployments() { _ = sem.Acquire(context.Background(), 1) defer sem.Release(1) - a.applyDeploymentItem(d, pctx) + a2.applyDeploymentItem(d) }() barrier := (d.Config.Barrier != nil && *d.Config.Barrier) || d.Barrier @@ -566,21 +605,50 @@ func (a *ApplyUtil) ReplaceObject(ref k8s2.ObjectRef, firstVersion *uo.Unstructu a.HandleError(ref, fmt.Errorf("unexpected end of loop")) } -func (a *ApplyUtil) GetDeletedObjectsList() []k8s2.ObjectRef { - var ret []k8s2.ObjectRef - for ref := range a.deletedObjects { - ret = append(ret, ref) +func (ad *ApplyDeploymentsUtil) GetAppliedObjects() []*types.RefAndObject { + var ret []*types.RefAndObject + for _, a := range ad.results { + for _, o := range a.appliedObjects { + ret = append(ret, &types.RefAndObject{ + Ref: o.GetK8sRef(), + Object: o, + }) + } + } + return ret +} + +func (ad *ApplyDeploymentsUtil) GetAppliedObjectsMap() map[k8s2.ObjectRef]*uo.UnstructuredObject { + ret := make(map[k8s2.ObjectRef]*uo.UnstructuredObject) + for _, ro := range ad.GetAppliedObjects() { + ret[ro.Ref] = ro.Object } return ret } -func (a *ApplyUtil) GetAppliedHookObjects() []*types.RefAndObject { +func (ad *ApplyDeploymentsUtil) GetAppliedHookObjects() []*types.RefAndObject { var ret []*types.RefAndObject - for _, o := range a.appliedHookObjects { - ret = append(ret, &types.RefAndObject{ - Ref: o.GetK8sRef(), - Object: o, - }) + for _, a := range ad.results { + for _, o := range a.appliedHookObjects { + ret = append(ret, &types.RefAndObject{ + Ref: o.GetK8sRef(), + Object: o, + }) + } + } + return ret +} + +func (ad *ApplyDeploymentsUtil) GetDeletedObjects() []k8s2.ObjectRef { + var ret []k8s2.ObjectRef + m := make(map[k8s2.ObjectRef]bool) + for _, a := range ad.results { + for ref := range a.deletedObjects { + if _, ok := m[ref]; !ok { + ret = append(ret, ref) + m[ref] = true + } + } } return ret } diff --git a/pkg/deployment/utils/errors_holder.go b/pkg/deployment/utils/errors_holder.go index 64d74301e..5a6ef2ccd 100644 --- a/pkg/deployment/utils/errors_holder.go +++ b/pkg/deployment/utils/errors_holder.go @@ -72,26 +72,6 @@ func (dew *DeploymentErrorsAndWarnings) HadError(ref k8s.ObjectRef) bool { return ok } -func (dew *DeploymentErrorsAndWarnings) ErrorCount() int { - dew.mutex.Lock() - defer dew.mutex.Unlock() - count := 0 - for _, m := range dew.errors { - count += len(m) - } - return count -} - -func (dew *DeploymentErrorsAndWarnings) WarningCount() int { - dew.mutex.Lock() - defer dew.mutex.Unlock() - count := 0 - for _, m := range dew.warnings { - count += len(m) - } - return count -} - func (dew *DeploymentErrorsAndWarnings) GetErrorsList() []types.DeploymentError { dew.mutex.Lock() defer dew.mutex.Unlock() diff --git a/pkg/deployment/utils/hooks_util.go b/pkg/deployment/utils/hooks_util.go index 869a1a4f1..23426ab5a 100644 --- a/pkg/deployment/utils/hooks_util.go +++ b/pkg/deployment/utils/hooks_util.go @@ -62,12 +62,12 @@ func (u *HooksUtil) DetermineHooks(d *deployment.DeploymentItem, hooks []string) return l } -func (u *HooksUtil) RunHooks(hooks []*hook, pctx *progressCtx) { +func (u *HooksUtil) RunHooks(hooks []*hook) { var deleteBeforeObjects []*hook var applyObjects []*hook for _, h := range hooks { - if u.a.abortSignal { + if u.a.abortSignal.Load().(bool) { return } if _, ok := h.deletePolicies["before-hook-creation"]; ok { @@ -82,12 +82,12 @@ func (u *HooksUtil) RunHooks(hooks []*hook, pctx *progressCtx) { for p := range h.deletePolicies { dpStr = append(dpStr, p) } - pctx.InfofAndStatus("Deleting hook %s due to hook-delete-policy %s (%d of %d)", ref.String(), strings.Join(dpStr, ","), i+1, cnt) + u.a.pctx.InfofAndStatus("Deleting hook %s due to hook-delete-policy %s (%d of %d)", ref.String(), strings.Join(dpStr, ","), i+1, cnt) return u.a.DeleteObject(ref, true) } if len(deleteBeforeObjects) != 0 { - pctx.Infof("Deleting %d hooks before hook execution", len(deleteBeforeObjects)) + u.a.pctx.Infof("Deleting %d hooks before hook execution", len(deleteBeforeObjects)) } for i, h := range deleteBeforeObjects { doDeleteForPolicy(h, i, len(deleteBeforeObjects)) @@ -95,14 +95,14 @@ func (u *HooksUtil) RunHooks(hooks []*hook, pctx *progressCtx) { waitResults := make(map[k8s.ObjectRef]bool) if len(applyObjects) != 0 { - pctx.Infof("Applying %d hooks", len(applyObjects)) + u.a.pctx.Infof("Applying %d hooks", len(applyObjects)) } for i, h := range applyObjects { ref := h.object.GetK8sRef() _, replaced := h.deletePolicies["before-hook-creation"] - pctx.DebugfAndStatus("Applying hook %s (%d of %d)", ref.String(), i+1, len(applyObjects)) + u.a.pctx.DebugfAndStatus("Applying hook %s (%d of %d)", ref.String(), i+1, len(applyObjects)) u.a.ApplyObject(h.object, replaced, true) - pctx.Increment() + u.a.pctx.Increment() if u.a.HadError(ref) { continue @@ -110,7 +110,7 @@ func (u *HooksUtil) RunHooks(hooks []*hook, pctx *progressCtx) { if !h.wait { continue } - waitResults[ref] = u.a.WaitReadiness(ref, h.timeout, pctx) + waitResults[ref] = u.a.WaitReadiness(ref, h.timeout) } var deleteAfterObjects []*hook @@ -133,7 +133,7 @@ func (u *HooksUtil) RunHooks(hooks []*hook, pctx *progressCtx) { } if len(deleteAfterObjects) != 0 { - pctx.Infof("Deleting %d hooks after hook execution", len(deleteAfterObjects)) + u.a.pctx.Infof("Deleting %d hooks after hook execution", len(deleteAfterObjects)) } for i, h := range deleteAfterObjects { doDeleteForPolicy(h, i, len(deleteAfterObjects)) diff --git a/pkg/deployment/utils/progress.go b/pkg/deployment/utils/progress.go index 0f89c30e7..53ec5537b 100644 --- a/pkg/deployment/utils/progress.go +++ b/pkg/deployment/utils/progress.go @@ -19,7 +19,7 @@ type progressCtx struct { mutex sync.Mutex } -func newProgressCtx(p *mpb.Progress, name string) *progressCtx { +func NewProgressCtx(p *mpb.Progress, name string) *progressCtx { pctx := &progressCtx{ status: "Initializing...", total: -1, @@ -28,11 +28,13 @@ func newProgressCtx(p *mpb.Progress, name string) *progressCtx { if !isatty.IsTerminal(os.Stderr.Fd()) || name == "" { return pctx } - pctx.bar = p.AddBar(-1, - mpb.BarWidth(40), - mpb.PrependDecorators(decor.Name(name+": ", decor.WCSyncSpaceR)), - mpb.AppendDecorators(decor.Any(pctx.DecorFunc)), - ) + if p != nil { + pctx.bar = p.AddBar(-1, + mpb.BarWidth(40), + mpb.PrependDecorators(decor.Name(name+": ", decor.WCSyncSpaceR)), + mpb.AppendDecorators(decor.Any(pctx.DecorFunc)), + ) + } return pctx } From a6f32eb519d0fd6b678057a26a0943c08758c145 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 13 Apr 2022 14:13:18 +0200 Subject: [PATCH 0641/2916] fix: Also show barriers in progress --- pkg/deployment/commands/downscale.go | 3 +-- pkg/deployment/commands/poke_images.go | 3 +-- pkg/deployment/utils/apply_utils.go | 21 ++++++++++++++++++++- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/pkg/deployment/commands/downscale.go b/pkg/deployment/commands/downscale.go index dd84f9e13..bd703c89f 100644 --- a/pkg/deployment/commands/downscale.go +++ b/pkg/deployment/commands/downscale.go @@ -39,8 +39,7 @@ func (cmd *DownscaleCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, error if !d.CheckInclusionForDeploy() { continue } - pctx := utils2.NewProgressCtx(nil, d.RelToProjectItemDir) - au := ad.NewApplyUtil(pctx) + au := ad.NewApplyUtil(utils2.NewProgressCtx(nil, d.RelToProjectItemDir)) for _, o := range d.Objects { o := o ref := o.GetK8sRef() diff --git a/pkg/deployment/commands/poke_images.go b/pkg/deployment/commands/poke_images.go index be2fe14f1..7945def10 100644 --- a/pkg/deployment/commands/poke_images.go +++ b/pkg/deployment/commands/poke_images.go @@ -75,8 +75,7 @@ func (cmd *PokeImagesCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, erro wg.Add(1) go func() { defer wg.Done() - pctx := utils2.NewProgressCtx(nil, ref.String()) - au := ad.NewApplyUtil(pctx) + au := ad.NewApplyUtil(utils2.NewProgressCtx(nil, ref.String())) au.ReplaceObject(ref, ru.GetRemoteObject(ref), func(o *uo.UnstructuredObject) (*uo.UnstructuredObject, error) { return doPokeImage(containers, o) }) diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index c0f0819a0..6d290cd1f 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -548,8 +548,27 @@ func (a *ApplyDeploymentsUtil) ApplyDeployments() { barrier := (d.Config.Barrier != nil && *d.Config.Barrier) || d.Barrier if barrier { - log.Infof("Waiting on barrier...") + bpctx := NewProgressCtx(p, "") + bpctx.SetTotal(1) + bpctx.InfofAndStatus("Waiting on barrier...") + startTime := time.Now() + stopCh := make(chan int) + go func() { + for { + select { + case <-stopCh: + bpctx.SetStatus(fmt.Sprintf("Finished waiting (%ds elapsed)", int(time.Now().Sub(startTime).Seconds()))) + bpctx.Increment() + stopCh <- 1 + return + case <-time.After(time.Second): + bpctx.SetStatus(fmt.Sprintf("Waiting on barrier... (%ds elapsed)", int(time.Now().Sub(startTime).Seconds()))) + } + } + }() wg.Wait() + stopCh <- 1 + <-stopCh } } wg.Wait() From 5f5d1f506aa14b807648f6eb695eb7cfd7b9b51f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 13 Apr 2022 17:18:33 +0200 Subject: [PATCH 0642/2916] fix: Remove a few plain log calls --- pkg/deployment/utils/apply_utils.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index 6d290cd1f..c2c992db7 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -166,14 +166,15 @@ func (a *ApplyUtil) DeleteObject(ref k8s2.ObjectRef, hook bool) bool { func (a *ApplyUtil) retryApplyForceReplace(x *uo.UnstructuredObject, hook bool, applyError error) { ref := x.GetK8sRef() - log2 := log.WithField("ref", ref) if !a.o.ForceReplaceOnError { a.HandleError(ref, applyError) return } - log2.Warningf("Patching failed, retrying by deleting and re-applying") + warn := fmt.Errorf("patching %s failed, retrying by deleting and re-applying", ref.String()) + a.HandleWarning(ref, warn) + a.pctx.Warningf(warn.Error()) if !a.DeleteObject(ref, hook) { return @@ -197,14 +198,15 @@ func (a *ApplyUtil) retryApplyForceReplace(x *uo.UnstructuredObject, hook bool, func (a *ApplyUtil) retryApplyWithReplace(x *uo.UnstructuredObject, hook bool, remoteObject *uo.UnstructuredObject, applyError error) { ref := x.GetK8sRef() - log2 := log.WithField("ref", ref) if !a.o.ReplaceOnError || remoteObject == nil { a.HandleError(ref, applyError) return } - log2.Warningf("Patching failed, retrying with replace instead of patch") + warn := fmt.Errorf("patching %s failed, retrying with replace instead of patch", ref.String()) + a.HandleWarning(ref, warn) + a.pctx.Warningf(warn.Error()) rv := remoteObject.GetK8sResourceVersion() x2 := x.Clone() @@ -268,8 +270,6 @@ func (a *ApplyUtil) retryApplyWithConflicts(x *uo.UnstructuredObject, hook bool, func (a *ApplyUtil) ApplyObject(x *uo.UnstructuredObject, replaced bool, hook bool) { ref := x.GetK8sRef() - log2 := log.WithField("ref", ref) - log2.Debugf("applying object") x = a.k.FixObjectForPatch(x) remoteObject := a.ru.GetRemoteObject(ref) @@ -611,7 +611,7 @@ func (a *ApplyUtil) ReplaceObject(ref k8s2.ObjectRef, firstVersion *uo.Unstructu a.dew.AddApiWarnings(ref, apiWarnings) if err != nil { if errors.IsConflict(err) { - log.Warningf("Conflict while patching %s. Retrying...", ref.String()) + log.Debugf("Conflict while patching %s. Retrying...", ref.String()) continue } else { a.HandleError(ref, err) From 3e260393a16a732a33337f85bf6fef5eaa50360a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 13 Apr 2022 21:53:50 +0200 Subject: [PATCH 0643/2916] fix: Fix premature completion of bars --- pkg/deployment/utils/apply_utils.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index c2c992db7..b611d01e7 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -447,7 +447,8 @@ func (a *ApplyUtil) applyDeploymentItem(d *deployment.DeploymentItem) { postHooks = h.DetermineHooks(d, []string{"post-deploy-upgrade", "post-deploy"}) } - total := len(applyObjects) + len(preHooks) + len(postHooks) + // +1 to ensure that we don't prematurely complete the bar (which would happen as we don't count for waiting) + total := len(applyObjects) + len(preHooks) + len(postHooks) + 1 a.pctx.SetTotal(int64(total)) if !d.CheckInclusionForDeploy() { a.pctx.InfofAndStatus("Skipped") @@ -534,13 +535,14 @@ func (a *ApplyDeploymentsUtil) ApplyDeployments() { break } + _ = sem.Acquire(context.Background(), 1) + pctx := NewProgressCtx(p, d.RelToProjectItemDir) a2 := a.NewApplyUtil(pctx) wg.Add(1) go func() { defer wg.Done() - _ = sem.Acquire(context.Background(), 1) defer sem.Release(1) a2.applyDeploymentItem(d) @@ -553,13 +555,14 @@ func (a *ApplyDeploymentsUtil) ApplyDeployments() { bpctx.InfofAndStatus("Waiting on barrier...") startTime := time.Now() stopCh := make(chan int) + doneCh := make(chan int) go func() { for { select { case <-stopCh: bpctx.SetStatus(fmt.Sprintf("Finished waiting (%ds elapsed)", int(time.Now().Sub(startTime).Seconds()))) - bpctx.Increment() - stopCh <- 1 + bpctx.Finish() + doneCh <- 1 return case <-time.After(time.Second): bpctx.SetStatus(fmt.Sprintf("Waiting on barrier... (%ds elapsed)", int(time.Now().Sub(startTime).Seconds()))) @@ -568,7 +571,7 @@ func (a *ApplyDeploymentsUtil) ApplyDeployments() { }() wg.Wait() stopCh <- 1 - <-stopCh + <-doneCh } } wg.Wait() From b45a0347010ebad7ba338958908685b30ea4180f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 13 Apr 2022 21:54:49 +0200 Subject: [PATCH 0644/2916] fix: Properly pop completed bars --- pkg/deployment/utils/apply_utils.go | 1 + pkg/deployment/utils/progress.go | 2 ++ 2 files changed, 3 insertions(+) diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index b611d01e7..850f76294 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -527,6 +527,7 @@ func (a *ApplyDeploymentsUtil) ApplyDeployments() { p := mpb.New( mpb.WithWidth(utils.GetTermWidth()), mpb.WithOutput(os.Stderr), + mpb.PopCompletedMode(), ) for _, d_ := range a.deployments { diff --git a/pkg/deployment/utils/progress.go b/pkg/deployment/utils/progress.go index 53ec5537b..72e94ef4e 100644 --- a/pkg/deployment/utils/progress.go +++ b/pkg/deployment/utils/progress.go @@ -99,6 +99,8 @@ func (ctx *progressCtx) Increment() { func (ctx *progressCtx) Finish() { if ctx.bar != nil { + // make sure that the bar es rendered on top so that it can be properly popped + ctx.bar.SetPriority(math.MinInt) ctx.bar.SetCurrent(ctx.total) } } From e891264cf063e462d048ea08593561617d6e5a25 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 13 Apr 2022 21:55:26 +0200 Subject: [PATCH 0645/2916] fix: Fix width of name in bars --- pkg/deployment/commands/downscale.go | 2 +- pkg/deployment/commands/poke_images.go | 2 +- pkg/deployment/commands/validate.go | 2 +- pkg/deployment/utils/apply_utils.go | 12 ++++++++++-- pkg/deployment/utils/progress.go | 17 ++++++++++------- 5 files changed, 23 insertions(+), 12 deletions(-) diff --git a/pkg/deployment/commands/downscale.go b/pkg/deployment/commands/downscale.go index bd703c89f..5bede4036 100644 --- a/pkg/deployment/commands/downscale.go +++ b/pkg/deployment/commands/downscale.go @@ -39,7 +39,7 @@ func (cmd *DownscaleCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, error if !d.CheckInclusionForDeploy() { continue } - au := ad.NewApplyUtil(utils2.NewProgressCtx(nil, d.RelToProjectItemDir)) + au := ad.NewApplyUtil(utils2.NewProgressCtx(nil, d.RelToProjectItemDir, 0)) for _, o := range d.Objects { o := o ref := o.GetK8sRef() diff --git a/pkg/deployment/commands/poke_images.go b/pkg/deployment/commands/poke_images.go index 7945def10..91ecf397e 100644 --- a/pkg/deployment/commands/poke_images.go +++ b/pkg/deployment/commands/poke_images.go @@ -75,7 +75,7 @@ func (cmd *PokeImagesCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, erro wg.Add(1) go func() { defer wg.Done() - au := ad.NewApplyUtil(utils2.NewProgressCtx(nil, ref.String())) + au := ad.NewApplyUtil(utils2.NewProgressCtx(nil, ref.String(), 0)) au.ReplaceObject(ref, ru.GetRemoteObject(ref), func(o *uo.UnstructuredObject) (*uo.UnstructuredObject, error) { return doPokeImage(containers, o) }) diff --git a/pkg/deployment/commands/validate.go b/pkg/deployment/commands/validate.go index 6cf14afdc..a067d7832 100644 --- a/pkg/deployment/commands/validate.go +++ b/pkg/deployment/commands/validate.go @@ -39,7 +39,7 @@ func (cmd *ValidateCommand) Run(k *k8s.K8sCluster) (*types.ValidateResult, error if !d.CheckInclusionForDeploy() { continue } - au := ad.NewApplyUtil(utils2.NewProgressCtx(nil, d.RelToProjectItemDir)) + au := ad.NewApplyUtil(utils2.NewProgressCtx(nil, d.RelToProjectItemDir, 0)) h := utils2.NewHooksUtil(au) for _, o := range d.Objects { hook := h.GetHook(o) diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index 850f76294..48c4314d1 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -530,6 +530,13 @@ func (a *ApplyDeploymentsUtil) ApplyDeployments() { mpb.PopCompletedMode(), ) + maxNameLen := 0 + for _, d := range a.deployments { + if len(d.RelToProjectItemDir) > maxNameLen { + maxNameLen = len(d.RelToProjectItemDir) + } + } + for _, d_ := range a.deployments { d := d_ if a.abortSignal.Load().(bool) { @@ -538,7 +545,7 @@ func (a *ApplyDeploymentsUtil) ApplyDeployments() { _ = sem.Acquire(context.Background(), 1) - pctx := NewProgressCtx(p, d.RelToProjectItemDir) + pctx := NewProgressCtx(p, d.RelToProjectItemDir, maxNameLen) a2 := a.NewApplyUtil(pctx) wg.Add(1) @@ -551,8 +558,9 @@ func (a *ApplyDeploymentsUtil) ApplyDeployments() { barrier := (d.Config.Barrier != nil && *d.Config.Barrier) || d.Barrier if barrier { - bpctx := NewProgressCtx(p, "") + bpctx := NewProgressCtx(p, "", maxNameLen) bpctx.SetTotal(1) + bpctx.InfofAndStatus("Waiting on barrier...") startTime := time.Now() stopCh := make(chan int) diff --git a/pkg/deployment/utils/progress.go b/pkg/deployment/utils/progress.go index 72e94ef4e..04e83fdcb 100644 --- a/pkg/deployment/utils/progress.go +++ b/pkg/deployment/utils/progress.go @@ -6,6 +6,7 @@ import ( log "github.com/sirupsen/logrus" "github.com/vbauerster/mpb/v7" "github.com/vbauerster/mpb/v7/decor" + "math" "os" "strings" "sync" @@ -19,7 +20,7 @@ type progressCtx struct { mutex sync.Mutex } -func NewProgressCtx(p *mpb.Progress, name string) *progressCtx { +func NewProgressCtx(p *mpb.Progress, name string, maxNameWidth int) *progressCtx { pctx := &progressCtx{ status: "Initializing...", total: -1, @@ -28,10 +29,16 @@ func NewProgressCtx(p *mpb.Progress, name string) *progressCtx { if !isatty.IsTerminal(os.Stderr.Fd()) || name == "" { return pctx } + + name += ":" + if len(name) < maxNameWidth + 1 { + name += strings.Repeat(" ", maxNameWidth - len(name) + 1) + } + if p != nil { pctx.bar = p.AddBar(-1, mpb.BarWidth(40), - mpb.PrependDecorators(decor.Name(name+": ", decor.WCSyncSpaceR)), + mpb.PrependDecorators(decor.Name(name)), mpb.AppendDecorators(decor.Any(pctx.DecorFunc)), ) } @@ -76,11 +83,7 @@ func (ctx *progressCtx) SetStatus(s string) { func (ctx *progressCtx) DecorFunc(st decor.Statistics) string { ctx.mutex.Lock() defer ctx.mutex.Unlock() - fillerLen := st.AvailableWidth - len(ctx.status) - 40 - if fillerLen < 0 { - fillerLen = 0 - } - return fmt.Sprintf("%s%s", ctx.status, strings.Repeat(" ", fillerLen)) + return ctx.status } func (ctx *progressCtx) SetTotal(total int64) { From dce3c91a39446ae04af7bcd9a6d9069e97e9a7cf Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 13 Apr 2022 22:19:30 +0200 Subject: [PATCH 0646/2916] fix: Add elapsed time to progress bars --- pkg/deployment/utils/apply_utils.go | 7 +++--- pkg/deployment/utils/progress.go | 37 +++++++++++++++++++---------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index 48c4314d1..10109f9a4 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -391,7 +391,7 @@ func (a *ApplyUtil) WaitReadiness(ref k8s2.ObjectRef, timeout time.Duration) boo return false } - a.pctx.SetStatus(fmt.Sprintf("Waiting for %s to get ready... (%ds elapsed)", ref.String(), elapsed)) + a.pctx.SetStatus(fmt.Sprintf("Waiting for %s to get ready...", ref.String())) if !didLog { a.pctx.Infof("Waiting for %s to get ready... (%ds elapsed)", ref.String(), elapsed) @@ -562,19 +562,18 @@ func (a *ApplyDeploymentsUtil) ApplyDeployments() { bpctx.SetTotal(1) bpctx.InfofAndStatus("Waiting on barrier...") - startTime := time.Now() stopCh := make(chan int) doneCh := make(chan int) go func() { for { select { case <-stopCh: - bpctx.SetStatus(fmt.Sprintf("Finished waiting (%ds elapsed)", int(time.Now().Sub(startTime).Seconds()))) + bpctx.SetStatus(fmt.Sprintf("Finished waiting")) bpctx.Finish() doneCh <- 1 return case <-time.After(time.Second): - bpctx.SetStatus(fmt.Sprintf("Waiting on barrier... (%ds elapsed)", int(time.Now().Sub(startTime).Seconds()))) + bpctx.SetStatus(fmt.Sprintf("Waiting on barrier...")) } } }() diff --git a/pkg/deployment/utils/progress.go b/pkg/deployment/utils/progress.go index 04e83fdcb..61253bf97 100644 --- a/pkg/deployment/utils/progress.go +++ b/pkg/deployment/utils/progress.go @@ -10,36 +10,41 @@ import ( "os" "strings" "sync" + "time" ) type progressCtx struct { - bar *mpb.Bar - total int64 - name string - status string - mutex sync.Mutex + bar *mpb.Bar + total int64 + name string + status string + startTime time.Time + mutex sync.Mutex } func NewProgressCtx(p *mpb.Progress, name string, maxNameWidth int) *progressCtx { pctx := &progressCtx{ - status: "Initializing...", - total: -1, - name: name, + status: "Initializing...", + total: -1, + name: name, + startTime: time.Now(), } if !isatty.IsTerminal(os.Stderr.Fd()) || name == "" { return pctx } name += ":" - if len(name) < maxNameWidth + 1 { - name += strings.Repeat(" ", maxNameWidth - len(name) + 1) + if len(name) < maxNameWidth+1 { + name += strings.Repeat(" ", maxNameWidth-len(name)+1) } + name += " " if p != nil { pctx.bar = p.AddBar(-1, mpb.BarWidth(40), mpb.PrependDecorators(decor.Name(name)), - mpb.AppendDecorators(decor.Any(pctx.DecorFunc)), + mpb.PrependDecorators(decor.Any(pctx.ElapsedDecorFunc)), + mpb.AppendDecorators(decor.Any(pctx.StatusDecorFunc)), ) } return pctx @@ -80,12 +85,20 @@ func (ctx *progressCtx) SetStatus(s string) { ctx.status = s } -func (ctx *progressCtx) DecorFunc(st decor.Statistics) string { +func (ctx *progressCtx) StatusDecorFunc(st decor.Statistics) string { ctx.mutex.Lock() defer ctx.mutex.Unlock() return ctx.status } +func (ctx *progressCtx) ElapsedDecorFunc(st decor.Statistics) string { + s := fmt.Sprintf("%.3fs", time.Now().Sub(ctx.startTime).Seconds()) + if len(s) < 8 { + s = strings.Repeat(" ", 8 - len(s)) + s + } + return s +} + func (ctx *progressCtx) SetTotal(total int64) { ctx.total = total if ctx.bar != nil { From 76817e2fbd7d61c9873bbedd7b48747a57de65d9 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 13 Apr 2022 22:40:29 +0200 Subject: [PATCH 0647/2916] fix: Fix hook execution --- pkg/deployment/utils/apply_utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index 10109f9a4..7cfa1efe5 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -443,7 +443,7 @@ func (a *ApplyUtil) applyDeploymentItem(d *deployment.DeploymentItem) { preHooks = h.DetermineHooks(d, []string{"pre-deploy-initial", "pre-deploy"}) postHooks = h.DetermineHooks(d, []string{"post-deploy-initial", "post-deploy"}) } else { - postHooks = h.DetermineHooks(d, []string{"pre-deploy-upgrade", "pre-deploy"}) + preHooks = h.DetermineHooks(d, []string{"pre-deploy-upgrade", "pre-deploy"}) postHooks = h.DetermineHooks(d, []string{"post-deploy-upgrade", "post-deploy"}) } From ebdf0cfa42c72093b180d5d67bf12bafdea0e830 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 13 Apr 2022 23:37:12 +0200 Subject: [PATCH 0648/2916] fix: Simulate creation of objects in dry runs when namespace does not exist --- pkg/deployment/utils/apply_utils.go | 22 ++++++++++++++++---- pkg/deployment/utils/remote_objects_utils.go | 14 +++++++++++++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index 7cfa1efe5..35f70850b 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -17,6 +17,7 @@ import ( "golang.org/x/sync/semaphore" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime/schema" "os" "reflect" "strings" @@ -273,6 +274,13 @@ func (a *ApplyUtil) ApplyObject(x *uo.UnstructuredObject, replaced bool, hook bo x = a.k.FixObjectForPatch(x) remoteObject := a.ru.GetRemoteObject(ref) + var remoteNamespace *uo.UnstructuredObject + if ref.Namespace != "" { + remoteNamespace = a.ru.GetRemoteObject(k8s2.ObjectRef{ + GVK: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Namespace"}, + Name: ref.Namespace, + }) + } usesDummyName := false if a.o.DryRun && replaced && remoteObject != nil { @@ -282,7 +290,13 @@ func (a *ApplyUtil) ApplyObject(x *uo.UnstructuredObject, replaced bool, hook bo // result usesDummyName = true x = x.Clone() - x.SetK8sName(fmt.Sprintf("%s-%s", x.GetK8sName(), utils.RandomString(8))) + x.SetK8sName(fmt.Sprintf("%s-%s", ref.Name, utils.RandomString(8))) + } else if a.o.DryRun && remoteNamespace == nil && ref.Namespace != "" { + // The namespace does not exist, so let's pretend we deploy it to the default namespace with a dummy name + usesDummyName = true + x = x.Clone() + x.SetK8sName(fmt.Sprintf("%s-%s", ref.Name, utils.RandomString(8))) + x.SetK8sNamespace("default") } options := k8s.PatchOptions{ @@ -295,9 +309,9 @@ func (a *ApplyUtil) ApplyObject(x *uo.UnstructuredObject, replaced bool, hook bo } if r != nil && usesDummyName { tmpName := r.GetK8sName() - realName := remoteObject.GetK8sName() - _ = r.ReplaceKeys(tmpName, realName) - _ = r.ReplaceValues(tmpName, realName) + _ = r.ReplaceKeys(tmpName, ref.Name) + _ = r.ReplaceValues(tmpName, ref.Name) + r.SetK8sNamespace(ref.Namespace) } a.handleApiWarnings(ref, apiWarnings) if err == nil { diff --git a/pkg/deployment/utils/remote_objects_utils.go b/pkg/deployment/utils/remote_objects_utils.go index fdf3c8eb7..b83a37a7b 100644 --- a/pkg/deployment/utils/remote_objects_utils.go +++ b/pkg/deployment/utils/remote_objects_utils.go @@ -6,6 +6,7 @@ import ( "github.com/kluctl/kluctl/pkg/utils" "github.com/kluctl/kluctl/pkg/utils/uo" log "github.com/sirupsen/logrus" + "k8s.io/apimachinery/pkg/runtime/schema" "sync" ) @@ -68,6 +69,19 @@ func (u *RemoteObjectUtils) UpdateRemoteObjects(k *k8s.K8sCluster, labels map[st } u.mutex.Unlock() } + + log.Infof("Getting namespaces") + r, _, err := k.ListObjects(schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: "Namespace", + }, "", nil) + if err != nil { + return err + } + for _, o := range r { + u.remoteObjects[o.GetK8sRef()] = o + } return nil } From 471533842e3518e9a2894d740abcc599e43d377c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 13 Apr 2022 23:55:12 +0200 Subject: [PATCH 0649/2916] fix: Handle remoteNamespaces separately in RemoteObjectUtils --- pkg/deployment/utils/apply_utils.go | 6 +----- pkg/deployment/utils/progress.go | 2 +- pkg/deployment/utils/remote_objects_utils.go | 21 ++++++++++++++------ 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index 35f70850b..a9aafc2b3 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -17,7 +17,6 @@ import ( "golang.org/x/sync/semaphore" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/apimachinery/pkg/runtime/schema" "os" "reflect" "strings" @@ -276,10 +275,7 @@ func (a *ApplyUtil) ApplyObject(x *uo.UnstructuredObject, replaced bool, hook bo remoteObject := a.ru.GetRemoteObject(ref) var remoteNamespace *uo.UnstructuredObject if ref.Namespace != "" { - remoteNamespace = a.ru.GetRemoteObject(k8s2.ObjectRef{ - GVK: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Namespace"}, - Name: ref.Namespace, - }) + remoteNamespace = a.ru.GetRemoteNamespace(ref.Namespace) } usesDummyName := false diff --git a/pkg/deployment/utils/progress.go b/pkg/deployment/utils/progress.go index 61253bf97..8ac3a0f81 100644 --- a/pkg/deployment/utils/progress.go +++ b/pkg/deployment/utils/progress.go @@ -94,7 +94,7 @@ func (ctx *progressCtx) StatusDecorFunc(st decor.Statistics) string { func (ctx *progressCtx) ElapsedDecorFunc(st decor.Statistics) string { s := fmt.Sprintf("%.3fs", time.Now().Sub(ctx.startTime).Seconds()) if len(s) < 8 { - s = strings.Repeat(" ", 8 - len(s)) + s + s = strings.Repeat(" ", 8-len(s)) + s } return s } diff --git a/pkg/deployment/utils/remote_objects_utils.go b/pkg/deployment/utils/remote_objects_utils.go index b83a37a7b..262a5baba 100644 --- a/pkg/deployment/utils/remote_objects_utils.go +++ b/pkg/deployment/utils/remote_objects_utils.go @@ -11,15 +11,17 @@ import ( ) type RemoteObjectUtils struct { - dew *DeploymentErrorsAndWarnings - remoteObjects map[k8s2.ObjectRef]*uo.UnstructuredObject - mutex sync.Mutex + dew *DeploymentErrorsAndWarnings + remoteObjects map[k8s2.ObjectRef]*uo.UnstructuredObject + remoteNamespaces map[string]*uo.UnstructuredObject + mutex sync.Mutex } func NewRemoteObjectsUtil(dew *DeploymentErrorsAndWarnings) *RemoteObjectUtils { return &RemoteObjectUtils{ - dew: dew, - remoteObjects: map[k8s2.ObjectRef]*uo.UnstructuredObject{}, + dew: dew, + remoteObjects: map[k8s2.ObjectRef]*uo.UnstructuredObject{}, + remoteNamespaces: map[string]*uo.UnstructuredObject{}, } } @@ -80,7 +82,7 @@ func (u *RemoteObjectUtils) UpdateRemoteObjects(k *k8s.K8sCluster, labels map[st return err } for _, o := range r { - u.remoteObjects[o.GetK8sRef()] = o + u.remoteNamespaces[o.GetK8sName()] = o } return nil } @@ -92,6 +94,13 @@ func (u *RemoteObjectUtils) GetRemoteObject(ref k8s2.ObjectRef) *uo.Unstructured return o } +func (u *RemoteObjectUtils) GetRemoteNamespace(name string) *uo.UnstructuredObject { + u.mutex.Lock() + defer u.mutex.Unlock() + o, _ := u.remoteNamespaces[name] + return o +} + func (u *RemoteObjectUtils) ForgetRemoteObject(ref k8s2.ObjectRef) { u.mutex.Lock() defer u.mutex.Unlock() From 627d7fad0d17f70e63e37a7109a72a1ad0560d3f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 14 Apr 2022 10:32:31 +0200 Subject: [PATCH 0650/2916] fix: Properly fix namespaces of List items when rendering Helm charts --- pkg/deployment/deployment_item.go | 2 +- pkg/deployment/helm_chart.go | 9 ++++++--- pkg/k8s/k8s_cluster.go | 4 ++-- pkg/k8s/utils.go | 31 +++++++++++++++++++++++++++++++ 4 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 pkg/k8s/utils.go diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index cc2e90a4f..e2c1dc62e 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -378,7 +378,7 @@ func (di *DeploymentItem) postprocessAndLoadObjects(k *k8s.K8sCluster, images *I for _, o := range di.Objects { if k != nil { - k.FixNamespace(o) + k.FixNamespace(o, "default") } // Set common labels/annotations diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index 83a48bb7e..3b0dbaac6 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -204,9 +204,12 @@ func (c *helmChart) Render(k *k8s.K8sCluster) error { // "helm install" will deploy resources to the given namespace automatically, but "helm template" does not // add the necessary namespace in the rendered resources - ns := o.GetK8sNamespace() - if ns == "" && k.IsNamespaced(o.GetK8sGVK()) { - o.SetK8sNamespace(namespace) + err = k8s.UnwrapListItems(o, true, func(o *uo.UnstructuredObject) error { + k.FixNamespace(o, namespace) + return nil + }) + if err != nil { + return err } } rendered, err = yaml.WriteYamlAllBytes(parsed) diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index d949e049a..bb28c94d7 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -256,13 +256,13 @@ func (k *K8sCluster) IsNamespaced(gvk schema.GroupVersionKind) bool { return r.Namespaced } -func (k *K8sCluster) FixNamespace(o *uo.UnstructuredObject) { +func (k *K8sCluster) FixNamespace(o *uo.UnstructuredObject, def string) { ref := o.GetK8sRef() namespaced := k.IsNamespaced(ref.GVK) if !namespaced && ref.Namespace != "" { o.SetK8sNamespace("") } else if namespaced && ref.Namespace == "" { - o.SetK8sNamespace("default") + o.SetK8sNamespace(def) } } diff --git a/pkg/k8s/utils.go b/pkg/k8s/utils.go new file mode 100644 index 000000000..f70e14fa2 --- /dev/null +++ b/pkg/k8s/utils.go @@ -0,0 +1,31 @@ +package k8s + +import ( + "github.com/kluctl/kluctl/pkg/utils/uo" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +func UnwrapListItems(o *uo.UnstructuredObject, withListCallback bool, cb func(o *uo.UnstructuredObject) error) error { + listGvk := schema.GroupVersionKind{Group: "", Version: "v1", Kind: "List"} + if o.GetK8sGVK() == listGvk { + if withListCallback { + err := cb(o) + if err != nil { + return err + } + } + items, _, err := o.GetNestedObjectList("items") + if err != nil { + return err + } + for _, x := range items { + err = cb(x) + if err != nil { + return err + } + } + return nil + } else { + return cb(o) + } +} From be8d8c36196fad3c7d74a0b55cbd3316889d7abe Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 14 Apr 2022 11:12:42 +0200 Subject: [PATCH 0651/2916] fix: Apply commonLabels/commonAnnotations to List items as well --- pkg/deployment/deployment_item.go | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index e2c1dc62e..4eb16cd43 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -377,17 +377,23 @@ func (di *DeploymentItem) postprocessAndLoadObjects(k *k8s.K8sCluster, images *I } for _, o := range di.Objects { - if k != nil { + commonLabels := di.getCommonLabels() + commonAnnotations := di.getCommonAnnotations() + + err = k8s.UnwrapListItems(o, true, func(o *uo.UnstructuredObject) error { k.FixNamespace(o, "default") - } - // Set common labels/annotations - o.SetK8sLabels(uo.CopyMergeStrMap(o.GetK8sLabels(), di.getCommonLabels())) - commonAnnotations := di.getCommonAnnotations() - o.SetK8sAnnotations(uo.CopyMergeStrMap(o.GetK8sAnnotations(), commonAnnotations)) + // Set common labels/annotations + o.SetK8sLabels(uo.CopyMergeStrMap(o.GetK8sLabels(), commonLabels)) + o.SetK8sAnnotations(uo.CopyMergeStrMap(o.GetK8sAnnotations(), commonAnnotations)) - // Resolve image placeholders - err = images.ResolvePlaceholders(k, o, di.relRenderedDir, di.getTags().ListKeys()) + // Resolve image placeholders + err = images.ResolvePlaceholders(k, o, di.relRenderedDir, di.getTags().ListKeys()) + if err != nil { + return err + } + return nil + }) if err != nil { return err } From af503d01fb816abf2a6a1663378c94ed1ad2b039 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 14 Apr 2022 11:34:01 +0200 Subject: [PATCH 0652/2916] ci: Update go-version used in CI builds/releases --- .github/workflows/build-and-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index 69778bad2..a54604aa6 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -19,7 +19,7 @@ jobs: fetch-depth: 0 - uses: actions/setup-go@v2 with: - go-version: '1.17.6' + go-version: '1.17.9' - name: Set up Python uses: actions/setup-python@v2 with: From 2c3930fc7221bc9e6dc47d8ba887fac45eb8a29d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 14 Apr 2022 11:34:23 +0200 Subject: [PATCH 0653/2916] build: Run go get -u --- go.mod | 26 ++++++++++++-------------- go.sum | 50 ++++++++++++++++++++++++-------------------------- 2 files changed, 36 insertions(+), 40 deletions(-) diff --git a/go.mod b/go.mod index df57618b9..30c91dca7 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.17 require ( github.com/alecthomas/kong v0.5.0 - github.com/aws/aws-sdk-go v1.43.35 + github.com/aws/aws-sdk-go v1.43.39 github.com/bitnami-labs/sealed-secrets v0.17.4 github.com/docker/distribution v2.8.1+incompatible github.com/evanphx/json-patch v5.6.0+incompatible @@ -22,7 +22,7 @@ require ( github.com/kevinburke/ssh_config v1.2.0 github.com/mattn/go-isatty v0.0.14 github.com/mitchellh/go-ps v1.0.0 - github.com/ohler55/ojg v1.13.1 + github.com/ohler55/ojg v1.14.0 github.com/pkg/errors v0.9.1 github.com/r3labs/diff/v2 v2.15.1 github.com/sirupsen/logrus v1.8.1 @@ -30,10 +30,10 @@ require ( github.com/vbauerster/mpb/v7 v7.4.1 github.com/whilp/git-urls v1.0.0 github.com/xanzy/ssh-agent v0.3.1 - golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 + golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12 - golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 + golang.org/x/sys v0.0.0-20220412211240-33da011f77ad + golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b k8s.io/api v0.24.0-beta.0 k8s.io/apimachinery v0.24.0-beta.0 @@ -55,8 +55,6 @@ require ( github.com/BurntSushi/toml v1.1.0 // indirect github.com/Microsoft/go-winio v0.5.2 // indirect github.com/ProtonMail/go-crypto v0.0.0-20220407094043-a94812496cf5 // indirect - github.com/PuerkitoBio/purell v1.1.1 // indirect - github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/VividCortex/ewma v1.2.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect github.com/acomagu/bufpipe v1.0.3 // indirect @@ -66,7 +64,7 @@ require ( github.com/docker/docker v20.10.14+incompatible // indirect github.com/docker/docker-credential-helpers v0.6.4 // indirect github.com/emicklei/go-restful v2.15.0+incompatible // indirect - github.com/emirpasic/gods v1.12.1 // indirect + github.com/emirpasic/gods v1.17.0 // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/fatih/color v1.13.0 // indirect github.com/gammazero/deque v0.1.1 // indirect @@ -75,7 +73,7 @@ require ( github.com/go-git/go-billy/v5 v5.3.1 // indirect github.com/go-logr/logr v1.2.3 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect - github.com/go-openapi/jsonreference v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.0 // indirect github.com/go-openapi/swag v0.21.1 // indirect github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect @@ -113,18 +111,18 @@ require ( github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/xlab/treeprint v1.1.0 // indirect go.starlark.net v0.0.0-20220328144851-d1966c6b9fcd // indirect - golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3 // indirect - golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a // indirect + golang.org/x/net v0.0.0-20220412020605-290c469a71a5 // indirect + golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect golang.org/x/text v0.3.7 // indirect - golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect - golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect + golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.28.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/klog/v2 v2.60.1 // indirect - k8s.io/kube-openapi v0.0.0-20220401212409-b28bf2818661 // indirect + k8s.io/kube-openapi v0.0.0-20220413171646-5e7f5fdc6da6 // indirect k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect sigs.k8s.io/yaml v1.3.0 // indirect diff --git a/go.sum b/go.sum index 9a35f7377..2acb29ed5 100644 --- a/go.sum +++ b/go.sum @@ -113,9 +113,7 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= github.com/ProtonMail/go-crypto v0.0.0-20220407094043-a94812496cf5 h1:cSHEbLj0GZeHM1mWG84qEnGFojNEQ83W7cwaPRjcwXU= github.com/ProtonMail/go-crypto v0.0.0-20220407094043-a94812496cf5/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= -github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= @@ -158,8 +156,8 @@ github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:l github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.43.35 h1:Ko1HiU7c7C8cZ5nvwp4GoLl08nmdQtZVZHxhrD8icwk= -github.com/aws/aws-sdk-go v1.43.35/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.43.39 h1:5W8pton/8OuS5hpbAkzfr7e+meAAFkK7LsUehB39L3I= +github.com/aws/aws-sdk-go v1.43.39/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -370,8 +368,8 @@ github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT github.com/emicklei/go-restful v2.15.0+incompatible h1:8KpYO/Xl/ZudZs5RNOEhWMBY4hmzlZhhRd9cu+jrZP4= github.com/emicklei/go-restful v2.15.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= -github.com/emirpasic/gods v1.12.1 h1:KEXpRg94qvWNpl3F8PRlzJRFhy1kr6SiBiFH6X2Nwp8= -github.com/emirpasic/gods v1.12.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/emirpasic/gods v1.17.0 h1:qOswSAaPBdCkBIAXmvwe0V9mv4UtNQHN3zxRHmw3sKQ= +github.com/emirpasic/gods v1.17.0/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -453,8 +451,8 @@ github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34 github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= -github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= -github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= +github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= @@ -828,8 +826,8 @@ github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/ohler55/ojg v1.13.1 h1:56I8UTpsg5CS73uA1/8JcXfyTf4WptmrMxeT3xYzG/g= -github.com/ohler55/ojg v1.13.1/go.mod h1:shYhKYyOC3s9/YgQPGueT8fk04IFxeyxqgZSKp6ILaI= +github.com/ohler55/ojg v1.14.0 h1:DyHomsCwofNswmKj7BLMdx51xnKbXxgIo1rVWCaBcNk= +github.com/ohler55/ojg v1.14.0/go.mod h1:3+GH+0PggMKocQtbZCrFifal3yRpHiBT4QUkxFJI6e8= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= @@ -1151,8 +1149,8 @@ golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 h1:tkVvjkPTB7pnW3jnid7kNyAMPVWllTNOf/qKDze4p9o= -golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= +golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1245,7 +1243,6 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= -golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -1254,8 +1251,8 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3 h1:EN5+DfgmRMvRUrMGERW2gQl3Vc+Z7ZMnI/xdEpPSf0c= -golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220412020605-290c469a71a5 h1:bRb386wvrE+oBNdF1d/Xh9mQrfQ4ecYhW5qJ5GvTGT4= +golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1274,8 +1271,8 @@ golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a h1:qfl7ob3DIEs3Ml9oLuPwY2N04gymzAW04WsUQHIClgM= -golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 h1:OSnWWcOd/CtWQC2cYSBgbTSJv3ciqd8r54ySIW2y3RE= +golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1377,7 +1374,6 @@ golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1402,12 +1398,13 @@ golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12 h1:QyVthZKMsyaQwBTJE04jdNN0Pp5Fn9Qga0mrgxyERQM= -golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 h1:EH1Deb8WZJ0xc0WK//leUHXcX9aLE5SymusoTmMZye8= +golang.org/x/term v0.0.0-20220411215600-e5f449aeb171/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1426,8 +1423,8 @@ golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 h1:M73Iuj3xbbb9Uk1DYhzydthsj6oOd6l9bpuFcNoUvTs= -golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220411224347-583f2d630306 h1:+gHMid33q6pen7kv9xvT+JRinntgeXO2AeZVd0AWD3w= +golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1499,8 +1496,9 @@ golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f h1:GGU+dLjvlC3qDwqYgL6UgRmHXhOOgns0bZu2Ty5mm6U= +golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= @@ -1776,8 +1774,8 @@ k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAG k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42/go.mod h1:Z/45zLw8lUo4wdiUkI+v/ImEGAvu3WatcZl3lPMR4Rk= -k8s.io/kube-openapi v0.0.0-20220401212409-b28bf2818661 h1:nqYOUleKLC/0P1zbU29F5q6aoezM6MOAVz+iyfQbZ5M= -k8s.io/kube-openapi v0.0.0-20220401212409-b28bf2818661/go.mod h1:daOouuuwd9JXpv1L7Y34iV3yf6nxzipkKMWWlqlvK9M= +k8s.io/kube-openapi v0.0.0-20220413171646-5e7f5fdc6da6 h1:nBQrWPlrNIiw0BsX6a6MKr1itkm0ZS0Nl97kNLitFfI= +k8s.io/kube-openapi v0.0.0-20220413171646-5e7f5fdc6da6/go.mod h1:daOouuuwd9JXpv1L7Y34iV3yf6nxzipkKMWWlqlvK9M= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= From 1021c0745f37e1af975419b5d72107bc21fdf53d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aljoscha=20P=C3=B6rtner?= Date: Fri, 15 Apr 2022 16:01:56 +0200 Subject: [PATCH 0654/2916] build: add Makefile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Aljoscha Pörtner --- Makefile | 113 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..7444c30f0 --- /dev/null +++ b/Makefile @@ -0,0 +1,113 @@ +# Based on the work of Thomas Poignant (thomaspoignant) +# https://gist.github.com/thomaspoignant/5b72d579bd5f311904d973652180c705 +GOCMD=go +GOTEST=$(GOCMD) test +GOVET=$(GOCMD) vet +BINARY_NAME=kluctl +TEST_BINARY_NAME=kluctl-e2e +REQUIRED_ENV_VARS=GOOS GOARCH +EXPORT_RESULT?=false + +GREEN := $(shell tput -Txterm setaf 2) +YELLOW := $(shell tput -Txterm setaf 3) +WHITE := $(shell tput -Txterm setaf 7) +CYAN := $(shell tput -Txterm setaf 6) +RESET := $(shell tput -Txterm sgr0) + +.PHONY: all test build vendor check-env check-kubectl check-helm check-kind + +all: help + +## Check: +check-env: ## Checks if required environment variables are set + @test $${GOOS?Please set environment variable GOOS} + @test $${GOARCH?Please set environment variable GOARCH} + +check-kubectl: ## Checks if kubectl is installed + kubectl version --client=true + +check-helm: ## Checks if helm is installed + helm version + +check-kind: ## Checks if kind is installed + kind version + +## Build: +build: vendor python generate build-go ## Run the complete build pipeline + +build-go: check-env ## Build your project and put the output binary in out/bin/ + mkdir -p out/bin + CGO_ENBALED=0 GO111MODULE=on GOARCH=$(GOARCH) GOOS=$(GOOS) $(GOCMD) build -mod vendor -o out/bin/$(BINARY_NAME) ./cmd/kluctl + +clean: ## Remove build related file + rm -fr ./bin + rm -fr ./out + rm -f ./junit-report.xml checkstyle-report.xml ./coverage.xml ./profile.cov yamllint-checkstyle.xml + +vendor: ## Copy of all packages needed to support builds and tests in the vendor directory + $(GOCMD) mod vendor + +python: check-env ## Download python for Jinja2 support + ./hack/download-python.sh $(GOARCH) + +generate: ## Generating Jinja2 support + $(GOCMD) generate ./... + $(GOCMD) generate -tags $(GOOS) ./pkg/python + +## Test: +test: test-unit test-e2e ## Runs the complete test suite + +test-e2e: check-env check-kubectl check-helm check-kind ## Runs the end to end tests + CGO_ENBALED=0 GO111MODULE=on GOARCH=$(GOARCH) GOOS=$(GOOS) $(GOCMD) test -o out/bin/$(TEST_BINARY_NAME) ./e2e + +test-unit: ## Run the unit tests of the project +ifeq ($(EXPORT_RESULT), true) + mkdir -p reports/test-unit + GO111MODULE=off $(GOCMD) get -u github.com/jstemmer/go-junit-report + $(eval OUTPUT_OPTIONS = | tee /dev/tty | go-junit-report -set-exit-code > reports/test-unit/junit-report.xml) +endif + $(GOTEST) -v -race $(shell go list ./... | grep -v /e2e/ | grep -v /vendor/) $(OUTPUT_OPTIONS) + +coverage-unit: ## Run the unit tests of the project and export the coverage + $(GOTEST) -cover -covermode=count -coverprofile=reports/coverage-unit/profile.cov $(shell go list ./... | grep -v /e2e/ | grep -v /vendor/) + $(GOCMD) tool cover -func profile.cov +ifeq ($(EXPORT_RESULT), true) + mkdir -p reports/coverage-unit + GO111MODULE=off $(GOCMD) get -u github.com/AlekSi/gocov-xml + GO111MODULE=off $(GOCMD) get -u github.com/axw/gocov/gocov + gocov convert reports/coverage-unit/profile.cov | gocov-xml > reports/coverage-unit/coverage.xml +endif + +## Lint: +lint: lint-go ## Run all available linters + +lint-go: ## Use golintci-lint on your project +ifeq ($(EXPORT_RESULT), true) + mkdir -p reports/lint-go + $(eval OUTPUT_OPTIONS = $(shell echo "--out-format checkstyle ./... | tee /dev/tty > reports/lint-go/checkstyle-report.xml" )) +else + $(eval OUTPUT_OPTIONS = $(shell echo "")) +endif + docker run --rm -v $(shell pwd):/app -w /app golangci/golangci-lint:latest-alpine golangci-lint run $(OUTPUT_OPTIONS) + + +## Release: +version: ## Write next version into version file + $(GOCMD) install github.com/bvieira/sv4git/v2/cmd/git-sv@v2.7.0 + $(eval KLUCTL_VERSION:=$(shell git sv next-version)) + sed -ibak "s/0.0.0/$(KLUCTL_VERSION)/g" pkg/version/version.go + +changelog: ## Generating changelog + git sv changelog -n 1 > CHANGELOG.md + +## Help: +help: ## Show this help. + @echo '' + @echo 'Usage:' + @echo ' ${YELLOW}make${RESET} ${GREEN}${RESET}' + @echo '' + @echo 'Targets:' + @awk 'BEGIN {FS = ":.*?## "} { \ + if (/^[0-9a-zA-Z_-]+:.*?##.*$$/) {printf " ${YELLOW}%-20s${GREEN}%s${RESET}\n", $$1, $$2} \ + else if (/^## .*$$/) {printf " ${CYAN}%s${RESET}\n", substr($$1,4)} \ + }' $(MAKEFILE_LIST) \ No newline at end of file From 004098af65d97302537e44c3d5b58cf49f5a5388 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aljoscha=20P=C3=B6rtner?= Date: Fri, 15 Apr 2022 16:08:27 +0200 Subject: [PATCH 0655/2916] build: add configuration for golangci MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Aljoscha Pörtner --- .golangci.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .golangci.yml diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 000000000..990178f1b --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,26 @@ +run: + concurrency: 4 + timeout: 5m + issues-exit-code: 2 +output: + format: "colored-line-number" +linters: + disable-all: true + enable: + - deadcode + - errcheck + - gosimple + - govet + - ineffassign + - staticcheck + - structcheck + - typecheck + - unused + - varcheck + - goconst + - gofmt + - goheader + - goimports + - gomnd + - gosec + - wsl \ No newline at end of file From cf386fa5a1a70c96bcb767e43a9682528c4b7041 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aljoscha=20P=C3=B6rtner?= Date: Fri, 15 Apr 2022 16:08:52 +0200 Subject: [PATCH 0656/2916] chore: add /out and /reports to gitignore MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Aljoscha Pörtner --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 1c81eae77..ac7a47bd5 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,6 @@ /download-python /kluctl /kluctl.exe -/e2e.test* \ No newline at end of file +/e2e.test* +/out +/reports \ No newline at end of file From ef2c32ed56818f50d674208e3c45137f4d4e7640 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aljoscha=20P=C3=B6rtner?= Date: Fri, 15 Apr 2022 16:16:50 +0200 Subject: [PATCH 0657/2916] build: add reports and download-python to clean target MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Aljoscha Pörtner --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 7444c30f0..6be0074e8 100644 --- a/Makefile +++ b/Makefile @@ -42,7 +42,8 @@ build-go: check-env ## Build your project and put the output binary in out/bin/ clean: ## Remove build related file rm -fr ./bin rm -fr ./out - rm -f ./junit-report.xml checkstyle-report.xml ./coverage.xml ./profile.cov yamllint-checkstyle.xml + rm -fr ./reports + rm -fr ./download-python vendor: ## Copy of all packages needed to support builds and tests in the vendor directory $(GOCMD) mod vendor From ec2f521bd2c71b4c277bd2fc0c8e38bfebeedbd0 Mon Sep 17 00:00:00 2001 From: JJGadgets Date: Sat, 16 Apr 2022 02:48:02 +0800 Subject: [PATCH 0658/2916] fix README typo "CI/CI" should be "CI/CD" --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e76e01268..8a798176c 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Use kluctl to: * Deploy the same deployment to multiple environments (dev, test, prod, ...), with flexible differences in configuration * Manage multiple target clusters (in multiple clouds or bare-metal if you want) * Manage encrypted secrets for multiple target environments and clusters (based on [Sealed Secrets](https://github.com/bitnami-labs/sealed-secrets)) -* Integrate it into your CI/CI pipelines and avoid putting too much logic into your shell scripts +* Integrate it into your CI/CD pipelines and avoid putting too much logic into your shell scripts kluctl tries to be as flexible as possible, while keeping it as simple as possible. It reuses established tools (e.g. kustomize and Helm), making it possible to re-use a large set of available third-party deployments. From 1f08f7c96835756935898aefecd6ca3e5adcf249 Mon Sep 17 00:00:00 2001 From: JJGadgets Date: Sat, 16 Apr 2022 02:56:58 +0800 Subject: [PATCH 0659/2916] improve README I feel like having the info about tools compatibility and no cluster-side components should be placed above the list of features, especially given the length of the features list. --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 8a798176c..627ac52d1 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,13 @@ kluctl is the missing glue that puts together your (and any third-party) deploym Kubernetes deployment, while making it fully manageable (deploy, diff, prune, delete, ...) via one unified command line interface. +kluctl tries to be as flexible as possible, while remaining as simple as possible. It reuses established +tools (e.g. kustomize and Helm), making it possible to re-use a large set of available third-party deployments. + +kluctl works completely locally, on the same machines that `kubectl` runs on. kluctl does not rely on any operators or other cluster-side components. +As long as the target cluster's kubeconfig is present locally, you are able to execute it from everywhere, including your +CI/CD pipelines or your laptop. + Use kluctl to: * Organize large and complex deployments, consisting of many Helm charts and kustomize deployments * Do the same for small and simple deployments, as the overhead is small @@ -17,13 +24,6 @@ Use kluctl to: * Manage encrypted secrets for multiple target environments and clusters (based on [Sealed Secrets](https://github.com/bitnami-labs/sealed-secrets)) * Integrate it into your CI/CD pipelines and avoid putting too much logic into your shell scripts -kluctl tries to be as flexible as possible, while keeping it as simple as possible. It reuses established -tools (e.g. kustomize and Helm), making it possible to re-use a large set of available third-party deployments. - -kluctl works completely local. In its simplest form, there is no need for any operators or other server-side components. -As long as the target cluster kubeconfig is present locally, you are able to execute it from everywhere, including your -CI/CD pipelines or your laptop. - ![](https://kluctl.io/asciinema/kluctl.gif) ## Documentation From d26eb9651259ae84a67fb621e187bfe3662116d5 Mon Sep 17 00:00:00 2001 From: Mathias Gebbe Date: Sun, 17 Apr 2022 13:25:17 +0200 Subject: [PATCH 0660/2916] fix: add missing change dir --- .github/workflows/build-and-release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index 795a2a1b7..41790a262 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -321,6 +321,7 @@ jobs: uses: actions/download-artifact@v2 - name: Build checksums.txt run: | + cd dist sha256sum kluctl-linux-amd64 > checksums.txt sha256sum kluctl-darwin-amd64 >> checksums.txt sha256sum kluctl-windows-amd64.exe >> checksums.txt @@ -333,5 +334,4 @@ jobs: dist/kluctl-linux-* dist/kluctl-darwin-* dist/kluctl-windows-* - checksums.txt - + dist/checksums.txt From 140579f3cbe91c8de3c51ddefcb03fdfe7d66d39 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 19 Apr 2022 15:04:38 +0200 Subject: [PATCH 0661/2916] refactor: Modify LoadKluctlProject to not be callback based anymore --- cmd/kluctl/commands/utils.go | 16 +++++++++++-- pkg/kluctl_project/load.go | 45 +++++++++++++++-------------------- pkg/kluctl_project/project.go | 6 ++--- 3 files changed, 35 insertions(+), 32 deletions(-) diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 0be2ed76d..0db35e851 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -9,6 +9,7 @@ import ( "github.com/kluctl/kluctl/pkg/k8s" "github.com/kluctl/kluctl/pkg/kluctl_project" "github.com/kluctl/kluctl/pkg/types" + "github.com/kluctl/kluctl/pkg/utils" "github.com/kluctl/kluctl/pkg/utils/uo" "io/ioutil" "os" @@ -24,6 +25,13 @@ func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, cb func(p *kluctl return err } } + + tmpDir, err := ioutil.TempDir(utils.GetTmpBaseDir(), "project-") + if err != nil { + return fmt.Errorf("creating temporary project directory failed: %w", err) + } + defer os.RemoveAll(tmpDir) + j2, err := jinja2.NewJinja2() if err != nil { return err @@ -39,9 +47,13 @@ func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, cb func(p *kluctl LocalSealedSecrets: projectFlags.LocalSealedSecrets, FromArchive: projectFlags.FromArchive, FromArchiveMetadata: projectFlags.FromArchiveMetadata, - J2: j2, } - return kluctl_project.LoadKluctlProject(loadArgs, cb) + + p, err := kluctl_project.LoadKluctlProject(loadArgs, tmpDir, j2) + if err != nil { + return err + } + return cb(p) } type projectTargetCommandArgs struct { diff --git a/pkg/kluctl_project/load.go b/pkg/kluctl_project/load.go index eb187b3ad..238932081 100644 --- a/pkg/kluctl_project/load.go +++ b/pkg/kluctl_project/load.go @@ -5,6 +5,7 @@ import ( "compress/gzip" "fmt" "github.com/kluctl/kluctl/pkg/git" + "github.com/kluctl/kluctl/pkg/jinja2" types2 "github.com/kluctl/kluctl/pkg/types" "github.com/kluctl/kluctl/pkg/utils" "github.com/kluctl/kluctl/pkg/yaml" @@ -158,41 +159,35 @@ func (c *KluctlProjectContext) load(allowGit bool) error { return nil } -func LoadKluctlProject(args LoadKluctlProjectArgs, cb func(ctx *KluctlProjectContext) error) error { - tmpDir, err := ioutil.TempDir(utils.GetTmpBaseDir(), "project-") - if err != nil { - return fmt.Errorf("creating temporary project directory failed: %w", err) - } - defer os.RemoveAll(tmpDir) - +func LoadKluctlProject(args LoadKluctlProjectArgs, tmpDir string, j2 *jinja2.Jinja2) (*KluctlProjectContext, error) { if args.FromArchive != "" { if args.ProjectUrl != nil || args.ProjectRef != "" || args.ProjectConfig != "" || args.LocalClusters != "" || args.LocalDeployment != "" || args.LocalSealedSecrets != "" { - return fmt.Errorf("--from-archive can not be combined with any other project related option") + return nil, fmt.Errorf("--from-archive can not be combined with any other project related option") } - project, err := loadKluctlProjectFromArchive(args, tmpDir) + project, err := loadKluctlProjectFromArchive(args, tmpDir, j2) if err != nil { - return err + return nil, err } err = project.load(false) if err != nil { - return err + return nil, err } - return cb(project) + return project, nil } else { - p := NewKluctlProjectContext(args, tmpDir) - err = p.load(true) + p := NewKluctlProjectContext(args, tmpDir, j2) + err := p.load(true) if err != nil { - return err + return nil, err } err = p.loadTargets() if err != nil { - return err + return nil, err } - return cb(p) + return p, nil } } -func loadKluctlProjectFromArchive(args LoadKluctlProjectArgs, tmpDir string) (*KluctlProjectContext, error) { +func loadKluctlProjectFromArchive(args LoadKluctlProjectArgs, tmpDir string, j2 *jinja2.Jinja2) (*KluctlProjectContext, error) { var dir string if utils.IsFile(args.FromArchive) { err := utils.ExtractTarGzFile(args.FromArchive, tmpDir) @@ -217,14 +212,12 @@ func loadKluctlProjectFromArchive(args LoadKluctlProjectArgs, tmpDir string) (*K return nil, err } - p := NewKluctlProjectContext( - LoadKluctlProjectArgs{ - ProjectConfig: yaml.FixPathExt(filepath.Join(dir, ".kluctl.yml")), - LocalClusters: filepath.Join(dir, "clusters"), - LocalDeployment: filepath.Join(dir, "deployment"), - LocalSealedSecrets: filepath.Join(dir, "sealed-secrets"), - J2: args.J2, - }, dir) + p := NewKluctlProjectContext(LoadKluctlProjectArgs{ + ProjectConfig: yaml.FixPathExt(filepath.Join(dir, ".kluctl.yml")), + LocalClusters: filepath.Join(dir, "clusters"), + LocalDeployment: filepath.Join(dir, "deployment"), + LocalSealedSecrets: filepath.Join(dir, "sealed-secrets"), + }, dir, j2) p.involvedRepos = metadata.InvolvedRepos p.DynamicTargets = metadata.Targets return p, nil diff --git a/pkg/kluctl_project/project.go b/pkg/kluctl_project/project.go index 0a73a2ece..8f44afee7 100644 --- a/pkg/kluctl_project/project.go +++ b/pkg/kluctl_project/project.go @@ -19,8 +19,6 @@ type LoadKluctlProjectArgs struct { LocalSealedSecrets string FromArchive string FromArchiveMetadata string - - J2 *jinja2.Jinja2 } type KluctlProjectContext struct { @@ -43,14 +41,14 @@ type KluctlProjectContext struct { J2 *jinja2.Jinja2 } -func NewKluctlProjectContext(loadArgs LoadKluctlProjectArgs, tmpDir string) *KluctlProjectContext { +func NewKluctlProjectContext(loadArgs LoadKluctlProjectArgs, tmpDir string, j2 *jinja2.Jinja2) *KluctlProjectContext { o := &KluctlProjectContext{ loadArgs: loadArgs, TmpDir: tmpDir, gitAuthProviders: auth2.NewDefaultAuthProviders(), involvedRepos: make(map[string][]types.InvolvedRepo), mirroredRepos: make(map[string]*git.MirroredGitRepo), - J2: loadArgs.J2, + J2: j2, } return o } From a792dfbb27c60931f10568d5b530aed6186c684a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 19 Apr 2022 15:57:32 +0200 Subject: [PATCH 0662/2916] refactor: Split withProjectTargetCommandContext into withProjectTargetCommandContext and NewTargetContext --- cmd/kluctl/args/images.go | 4 +- .../commands/cmd_check_image_updates.go | 2 +- cmd/kluctl/commands/cmd_delete.go | 6 +- cmd/kluctl/commands/cmd_deploy.go | 4 +- cmd/kluctl/commands/cmd_diff.go | 4 +- cmd/kluctl/commands/cmd_downscale.go | 6 +- cmd/kluctl/commands/cmd_poke_images.go | 6 +- cmd/kluctl/commands/cmd_prune.go | 6 +- cmd/kluctl/commands/cmd_render.go | 2 +- cmd/kluctl/commands/cmd_seal.go | 27 ++-- cmd/kluctl/commands/cmd_validate.go | 4 +- cmd/kluctl/commands/utils.go | 126 +++--------------- pkg/kluctl_project/target_context.go | 119 +++++++++++++++++ 13 files changed, 172 insertions(+), 144 deletions(-) create mode 100644 pkg/kluctl_project/target_context.go diff --git a/cmd/kluctl/args/images.go b/cmd/kluctl/args/images.go index 6aabbeac2..f53ec2c58 100644 --- a/cmd/kluctl/args/images.go +++ b/cmd/kluctl/args/images.go @@ -13,7 +13,7 @@ type ImageFlags struct { UpdateImages bool `group:"images" short:"u" help:"This causes kluctl to prefer the latest image found in registries, based on the 'latest_image' filters provided to 'images.get_image(...)' calls. Use this flag if you want to update to the latest versions/tags of all images. '-u' takes precedence over '--fixed-image/--fixed-images-file', meaning that the latest images are used even if an older image is given via fixed images."` } -func (args *ImageFlags) LoadFixedImagesFromArgs() (*types.FixedImagesConfig, error) { +func (args *ImageFlags) LoadFixedImagesFromArgs() ([]types.FixedImage, error) { var ret types.FixedImagesConfig if args.FixedImagesFile != "" { @@ -31,7 +31,7 @@ func (args *ImageFlags) LoadFixedImagesFromArgs() (*types.FixedImagesConfig, err ret.Images = append(ret.Images, *e) } - return &ret, nil + return ret.Images, nil } func buildFixedImageEntryFromArg(arg string) (*types.FixedImage, error) { diff --git a/cmd/kluctl/commands/cmd_check_image_updates.go b/cmd/kluctl/commands/cmd_check_image_updates.go index 85ecfce71..74538ce07 100644 --- a/cmd/kluctl/commands/cmd_check_image_updates.go +++ b/cmd/kluctl/commands/cmd_check_image_updates.go @@ -30,7 +30,7 @@ func (cmd *checkImageUpdatesCmd) Run() error { targetFlags: cmd.TargetFlags, } err := withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { - renderedImages = ctx.deploymentCollection.FindRenderedImages() + renderedImages = ctx.targetCtx.DeploymentCollection.FindRenderedImages() return nil }) if err != nil { diff --git a/cmd/kluctl/commands/cmd_delete.go b/cmd/kluctl/commands/cmd_delete.go index 8374968cf..ae16159b6 100644 --- a/cmd/kluctl/commands/cmd_delete.go +++ b/cmd/kluctl/commands/cmd_delete.go @@ -44,7 +44,7 @@ func (cmd *deleteCmd) Run() error { dryRunArgs: &cmd.DryRunFlags, } return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { - cmd2 := commands.NewDeleteCommand(ctx.deploymentCollection) + cmd2 := commands.NewDeleteCommand(ctx.targetCtx.DeploymentCollection) deleteByLabels, err := deployment.ParseArgs(cmd.DeleteByLabel) if err != nil { @@ -53,11 +53,11 @@ func (cmd *deleteCmd) Run() error { cmd2.OverrideDeleteByLabels = deleteByLabels - objects, err := cmd2.Run(ctx.k) + objects, err := cmd2.Run(ctx.targetCtx.K) if err != nil { return err } - result, err := confirmedDeleteObjects(ctx.k, objects, cmd.DryRun, cmd.Yes) + result, err := confirmedDeleteObjects(ctx.targetCtx.K, objects, cmd.DryRun, cmd.Yes) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_deploy.go b/cmd/kluctl/commands/cmd_deploy.go index fabd1952b..0f4d22915 100644 --- a/cmd/kluctl/commands/cmd_deploy.go +++ b/cmd/kluctl/commands/cmd_deploy.go @@ -49,7 +49,7 @@ func (cmd *deployCmd) Run() error { } func (cmd *deployCmd) runCmdDeploy(ctx *commandCtx) error { - cmd2 := commands.NewDeployCommand(ctx.deploymentCollection) + cmd2 := commands.NewDeployCommand(ctx.targetCtx.DeploymentCollection) cmd2.ForceApply = cmd.ForceApply cmd2.ReplaceOnError = cmd.ReplaceOnError cmd2.ForceReplaceOnError = cmd.ForceReplaceOnError @@ -62,7 +62,7 @@ func (cmd *deployCmd) runCmdDeploy(ctx *commandCtx) error { cb = nil } - result, err := cmd2.Run(ctx.k, cb) + result, err := cmd2.Run(ctx.targetCtx.K, cb) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_diff.go b/cmd/kluctl/commands/cmd_diff.go index 840617dd9..374926510 100644 --- a/cmd/kluctl/commands/cmd_diff.go +++ b/cmd/kluctl/commands/cmd_diff.go @@ -36,14 +36,14 @@ func (cmd *diffCmd) Run() error { renderOutputDirFlags: cmd.RenderOutputDirFlags, } return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { - cmd2 := commands.NewDiffCommand(ctx.deploymentCollection) + cmd2 := commands.NewDiffCommand(ctx.targetCtx.DeploymentCollection) cmd2.ForceApply = cmd.ForceApply cmd2.ReplaceOnError = cmd.ReplaceOnError cmd2.ForceReplaceOnError = cmd.ForceReplaceOnError cmd2.IgnoreTags = cmd.IgnoreTags cmd2.IgnoreLabels = cmd.IgnoreLabels cmd2.IgnoreAnnotations = cmd.IgnoreAnnotations - result, err := cmd2.Run(ctx.k) + result, err := cmd2.Run(ctx.targetCtx.K) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_downscale.go b/cmd/kluctl/commands/cmd_downscale.go index 184533519..d4afda0e6 100644 --- a/cmd/kluctl/commands/cmd_downscale.go +++ b/cmd/kluctl/commands/cmd_downscale.go @@ -37,14 +37,14 @@ func (cmd *downscaleCmd) Run() error { } return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { if !cmd.Yes && !cmd.DryRun { - if !utils.AskForConfirmation(fmt.Sprintf("Do you really want to downscale on context/cluster %s?", ctx.k.Context())) { + if !utils.AskForConfirmation(fmt.Sprintf("Do you really want to downscale on context/cluster %s?", ctx.targetCtx.K.Context())) { return fmt.Errorf("aborted") } } - cmd2 := commands.NewDownscaleCommand(ctx.deploymentCollection) + cmd2 := commands.NewDownscaleCommand(ctx.targetCtx.DeploymentCollection) - result, err := cmd2.Run(ctx.k) + result, err := cmd2.Run(ctx.targetCtx.K) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_poke_images.go b/cmd/kluctl/commands/cmd_poke_images.go index 641713e45..0857ab8c0 100644 --- a/cmd/kluctl/commands/cmd_poke_images.go +++ b/cmd/kluctl/commands/cmd_poke_images.go @@ -37,14 +37,14 @@ func (cmd *pokeImagesCmd) Run() error { } return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { if !cmd.Yes && !cmd.DryRun { - if !utils.AskForConfirmation(fmt.Sprintf("Do you really want to poke images to the context/cluster %s?", ctx.k.Context())) { + if !utils.AskForConfirmation(fmt.Sprintf("Do you really want to poke images to the context/cluster %s?", ctx.targetCtx.K.Context())) { return fmt.Errorf("aborted") } } - cmd2 := commands.NewPokeImagesCommand(ctx.deploymentCollection) + cmd2 := commands.NewPokeImagesCommand(ctx.targetCtx.DeploymentCollection) - result, err := cmd2.Run(ctx.k) + result, err := cmd2.Run(ctx.targetCtx.K) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_prune.go b/cmd/kluctl/commands/cmd_prune.go index 5881089ac..3712b8b3d 100644 --- a/cmd/kluctl/commands/cmd_prune.go +++ b/cmd/kluctl/commands/cmd_prune.go @@ -40,12 +40,12 @@ func (cmd *pruneCmd) Run() error { } func (cmd *pruneCmd) runCmdPrune(ctx *commandCtx) error { - cmd2 := commands.NewPruneCommand(ctx.deploymentCollection) - objects, err := cmd2.Run(ctx.k) + cmd2 := commands.NewPruneCommand(ctx.targetCtx.DeploymentCollection) + objects, err := cmd2.Run(ctx.targetCtx.K) if err != nil { return err } - result, err := confirmedDeleteObjects(ctx.k, objects, cmd.DryRun, cmd.Yes) + result, err := confirmedDeleteObjects(ctx.targetCtx.K, objects, cmd.DryRun, cmd.Yes) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_render.go b/cmd/kluctl/commands/cmd_render.go index 96c3093e4..abd550b00 100644 --- a/cmd/kluctl/commands/cmd_render.go +++ b/cmd/kluctl/commands/cmd_render.go @@ -37,7 +37,7 @@ func (cmd *renderCmd) Run() error { renderOutputDirFlags: cmd.RenderOutputDirFlags, } return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { - log.Infof("Rendered into %s", ctx.deploymentCollection.RenderDir) + log.Infof("Rendered into %s", ctx.targetCtx.DeploymentCollection.RenderDir) return nil }) } diff --git a/cmd/kluctl/commands/cmd_seal.go b/cmd/kluctl/commands/cmd_seal.go index 08b89809e..d48510ab2 100644 --- a/cmd/kluctl/commands/cmd_seal.go +++ b/cmd/kluctl/commands/cmd_seal.go @@ -29,7 +29,7 @@ If no '--target' is specified, sealing is performed for all targets.` } func findSecretsEntry(ctx *commandCtx, name string) (*types.SecretSet, error) { - for _, e := range ctx.kluctlProject.Config.SecretsConfig.SecretSets { + for _, e := range ctx.targetCtx.KluctlProject.Config.SecretsConfig.SecretSets { if e.Name == name { return &e, nil } @@ -46,7 +46,7 @@ func loadSecrets(ctx *commandCtx, target *types.Target, secretsLoader *seal.Secr } for _, source := range secretEntry.Sources { var renderedSource types.SecretSource - err = ctx.kluctlProject.J2.RenderStruct(&renderedSource, &source, ctx.deploymentProject.VarsCtx.Vars) + err = ctx.targetCtx.KluctlProject.J2.RenderStruct(&renderedSource, &source, ctx.targetCtx.DeploymentProject.VarsCtx.Vars) if err != nil { return err } @@ -57,25 +57,26 @@ func loadSecrets(ctx *commandCtx, target *types.Target, secretsLoader *seal.Secr secrets.Merge(s) } } - ctx.deploymentProject.MergeSecretsIntoAllChildren(secrets) + ctx.targetCtx.DeploymentProject.MergeSecretsIntoAllChildren(secrets) return nil } -func (cmd *sealCmd) runCmdSealForTarget(p *kluctl_project.KluctlProjectContext, target *types.Target, secretsLoader *seal.SecretsLoader) error { - log.Infof("Sealing for target %s", target.Name) +func (cmd *sealCmd) runCmdSealForTarget(p *kluctl_project.KluctlProjectContext, targetName string, secretsLoader *seal.SecretsLoader) error { + log.Infof("Sealing for target %s", targetName) ptArgs := projectTargetCommandArgs{ projectFlags: cmd.ProjectFlags, targetFlags: cmd.TargetFlags, } + ptArgs.targetFlags.Target = targetName // pass forSeal=True so that .sealme files are rendered as well - return withProjectTargetCommandContext(ptArgs, p, target, true, func(ctx *commandCtx) error { - err := loadSecrets(ctx, target, secretsLoader) + return withProjectTargetCommandContext(ptArgs, p, true, func(ctx *commandCtx) error { + err := loadSecrets(ctx, ctx.targetCtx.Target, secretsLoader) if err != nil { return err } - err = ctx.deploymentCollection.RenderDeployments(ctx.k) + err = ctx.targetCtx.DeploymentCollection.RenderDeployments(ctx.targetCtx.K) if err != nil { return err } @@ -91,22 +92,22 @@ func (cmd *sealCmd) runCmdSealForTarget(p *kluctl_project.KluctlProjectContext, } } if p.Config.SecretsConfig == nil || p.Config.SecretsConfig.SealedSecrets == nil || p.Config.SecretsConfig.SealedSecrets.Bootstrap == nil || *p.Config.SecretsConfig.SealedSecrets.Bootstrap { - err = seal.BootstrapSealedSecrets(ctx.k, sealedSecretsNamespace) + err = seal.BootstrapSealedSecrets(ctx.targetCtx.K, sealedSecretsNamespace) if err != nil { return err } } - clusterConfig, err := p.LoadClusterConfig(target.Cluster) + clusterConfig, err := p.LoadClusterConfig(ctx.targetCtx.Target.Cluster) if err != nil { return err } - sealer, err := seal.NewSealer(ctx.k, sealedSecretsNamespace, sealedSecretsControllerName, clusterConfig.Cluster, cmd.ForceReseal) + sealer, err := seal.NewSealer(ctx.targetCtx.K, sealedSecretsNamespace, sealedSecretsControllerName, clusterConfig.Cluster, cmd.ForceReseal) if err != nil { return err } - cmd2 := commands.NewSealCommand(ctx.deploymentCollection) + cmd2 := commands.NewSealCommand(ctx.targetCtx.DeploymentCollection) err = cmd2.Run(sealer) if err != nil { @@ -153,7 +154,7 @@ func (cmd *sealCmd) Run() error { sealTarget = baseTarget } - err := cmd.runCmdSealForTarget(p, sealTarget, secretsLoader) + err := cmd.runCmdSealForTarget(p, sealTarget.Name, secretsLoader) if err != nil { log.Warningf("Sealing for target %s failed: %v", sealTarget.Name, err) hadError = true diff --git a/cmd/kluctl/commands/cmd_validate.go b/cmd/kluctl/commands/cmd_validate.go index f97c00f20..6008a4269 100644 --- a/cmd/kluctl/commands/cmd_validate.go +++ b/cmd/kluctl/commands/cmd_validate.go @@ -37,9 +37,9 @@ func (cmd *validateCmd) Run() error { } return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { startTime := time.Now() - cmd2 := commands.NewValidateCommand(ctx.deploymentCollection) + cmd2 := commands.NewValidateCommand(ctx.targetCtx.DeploymentCollection) for true { - result, err := cmd2.Run(ctx.k) + result, err := cmd2.Run(ctx.targetCtx.K) if err != nil { return err } diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 0db35e851..27fa9a832 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -6,14 +6,10 @@ import ( "github.com/kluctl/kluctl/pkg/deployment" git_url "github.com/kluctl/kluctl/pkg/git/git-url" "github.com/kluctl/kluctl/pkg/jinja2" - "github.com/kluctl/kluctl/pkg/k8s" "github.com/kluctl/kluctl/pkg/kluctl_project" - "github.com/kluctl/kluctl/pkg/types" "github.com/kluctl/kluctl/pkg/utils" - "github.com/kluctl/kluctl/pkg/utils/uo" "io/ioutil" "os" - "path/filepath" ) func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, cb func(p *kluctl_project.KluctlProjectContext) error) error { @@ -67,62 +63,27 @@ type projectTargetCommandArgs struct { } type commandCtx struct { - kluctlProject *kluctl_project.KluctlProjectContext - target *types.Target - clusterConfig *types.ClusterConfig - k *k8s.K8sCluster - images *deployment.Images - deploymentProject *deployment.DeploymentProject - deploymentCollection *deployment.DeploymentCollection + targetCtx *kluctl_project.TargetContext + images *deployment.Images } func withProjectCommandContext(args projectTargetCommandArgs, cb func(ctx *commandCtx) error) error { return withKluctlProjectFromArgs(args.projectFlags, func(p *kluctl_project.KluctlProjectContext) error { - var target *types.Target - if args.targetFlags.Target != "" { - t, err := p.FindDynamicTarget(args.targetFlags.Target) - if err != nil { - return err - } - target = t.Target - } - return withProjectTargetCommandContext(args, p, target, false, cb) + return withProjectTargetCommandContext(args, p, false, cb) }) } -func withProjectTargetCommandContext(args projectTargetCommandArgs, p *kluctl_project.KluctlProjectContext, target *types.Target, forSeal bool, cb func(ctx *commandCtx) error) error { - deploymentDir, err := filepath.Abs(p.DeploymentDir) - if err != nil { - return err - } - - clusterName := args.projectFlags.Cluster - if clusterName == "" { - if target == nil { - return fmt.Errorf("you must specify an existing --cluster when not providing a --target") - } - clusterName = target.Cluster - } - - clusterConfig, err := p.LoadClusterConfig(clusterName) - if err != nil { - return err - } - - k, err := k8s.NewK8sCluster(clusterConfig.Cluster.Context, args.dryRunArgs == nil || args.dryRunArgs.DryRun) +func withProjectTargetCommandContext(args projectTargetCommandArgs, p *kluctl_project.KluctlProjectContext, forSeal bool, cb func(ctx *commandCtx) error) error { + images, err := deployment.NewImages(args.imageFlags.UpdateImages) if err != nil { return err } - - varsCtx := jinja2.NewVarsCtx(p.J2) - err = varsCtx.UpdateChildFromStruct("cluster", clusterConfig.Cluster) + fixedImages, err := args.imageFlags.LoadFixedImagesFromArgs() if err != nil { return err } - - images, err := deployment.NewImages(args.imageFlags.UpdateImages) - if err != nil { - return err + for _, fi := range fixedImages { + images.AddFixedImage(fi) } inclusion, err := args.inclusionFlags.ParseInclusionFromArgs() @@ -130,44 +91,10 @@ func withProjectTargetCommandContext(args projectTargetCommandArgs, p *kluctl_pr return err } - allArgs := uo.New() - optionArgs, err := deployment.ParseArgs(args.argsFlags.Arg) if err != nil { return err } - if target != nil { - for argName, argValue := range optionArgs { - err = p.CheckDynamicArg(target, argName, argValue) - if err != nil { - return err - } - } - } - allArgs.Merge(deployment.ConvertArgsToVars(optionArgs)) - if target != nil { - if target.Args != nil { - allArgs.Merge(target.Args) - } - if forSeal { - if target.SealingConfig.Args != nil { - allArgs.Merge(target.SealingConfig.Args) - } - } - } - - err = deployment.CheckRequiredDeployArgs(deploymentDir, varsCtx, allArgs) - if err != nil { - return err - } - - varsCtx.UpdateChild("args", allArgs) - - targetVars, err := uo.FromStruct(target) - if err != nil { - return err - } - varsCtx.UpdateChild("target", targetVars) renderOutputDir := args.renderOutputDirFlags.RenderOutputDir if renderOutputDir == "" { @@ -179,44 +106,25 @@ func withProjectTargetCommandContext(args projectTargetCommandArgs, p *kluctl_pr renderOutputDir = tmpDir } - d, err := deployment.NewDeploymentProject(k, varsCtx, deploymentDir, p.SealedSecretsDir, nil) + ctx, err := p.NewTargetContext(args.targetFlags.Target, args.projectFlags.Cluster, + args.dryRunArgs == nil || args.dryRunArgs.DryRun, + optionArgs, forSeal, images, inclusion, + renderOutputDir) if err != nil { return err } - c, err := deployment.NewDeploymentCollection(d, images, inclusion, renderOutputDir, forSeal) - if err != nil { - return err - } - - fixedImages, err := args.imageFlags.LoadFixedImagesFromArgs() - if err != nil { - return err - } - if target != nil { - for _, fi := range target.Images { - images.AddFixedImage(fi) - } - } - for _, fi := range fixedImages.Images { - images.AddFixedImage(fi) - } if !forSeal { - err = c.Prepare(k) + err = ctx.DeploymentCollection.Prepare(ctx.K) if err != nil { return err } } - ctx := &commandCtx{ - kluctlProject: p, - target: target, - clusterConfig: clusterConfig, - k: k, - images: images, - deploymentProject: d, - deploymentCollection: c, + cmdCtx := &commandCtx{ + targetCtx: ctx, + images: images, } - return cb(ctx) + return cb(cmdCtx) } diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go new file mode 100644 index 000000000..d863fe8c2 --- /dev/null +++ b/pkg/kluctl_project/target_context.go @@ -0,0 +1,119 @@ +package kluctl_project + +import ( + "fmt" + "github.com/kluctl/kluctl/pkg/deployment" + "github.com/kluctl/kluctl/pkg/jinja2" + "github.com/kluctl/kluctl/pkg/k8s" + "github.com/kluctl/kluctl/pkg/types" + "github.com/kluctl/kluctl/pkg/utils" + "github.com/kluctl/kluctl/pkg/utils/uo" + "path/filepath" +) + +type TargetContext struct { + KluctlProject *KluctlProjectContext + Target *types.Target + ClusterConfig *types.ClusterConfig + K *k8s.K8sCluster + DeploymentProject *deployment.DeploymentProject + DeploymentCollection *deployment.DeploymentCollection +} + +func (p *KluctlProjectContext) NewTargetContext(targetName string, clusterName string, dryRun bool, args map[string]string, forSeal bool, images *deployment.Images, inclusion *utils.Inclusion, renderOutputDir string) (*TargetContext, error) { + deploymentDir, err := filepath.Abs(p.DeploymentDir) + if err != nil { + return nil, err + } + + var target *types.Target + if targetName != "" { + t, err := p.FindDynamicTarget(targetName) + if err != nil { + return nil, err + } + target = t.Target + + for _, fi := range target.Images { + images.AddFixedImage(fi) + } + } + + if clusterName == "" { + if target == nil { + return nil, fmt.Errorf("you must specify an existing --cluster when not providing a --target") + } + clusterName = target.Cluster + } + + clusterConfig, err := p.LoadClusterConfig(clusterName) + if err != nil { + return nil, err + } + + k, err := k8s.NewK8sCluster(clusterConfig.Cluster.Context, dryRun) + if err != nil { + return nil, err + } + + varsCtx := jinja2.NewVarsCtx(p.J2) + err = varsCtx.UpdateChildFromStruct("cluster", clusterConfig.Cluster) + if err != nil { + return nil, err + } + + allArgs := uo.New() + + if target != nil { + for argName, argValue := range args { + err = p.CheckDynamicArg(target, argName, argValue) + if err != nil { + return nil, err + } + } + } + allArgs.Merge(deployment.ConvertArgsToVars(args)) + if target != nil { + if target.Args != nil { + allArgs.Merge(target.Args) + } + if forSeal { + if target.SealingConfig.Args != nil { + allArgs.Merge(target.SealingConfig.Args) + } + } + } + + err = deployment.CheckRequiredDeployArgs(deploymentDir, varsCtx, allArgs) + if err != nil { + return nil, err + } + + varsCtx.UpdateChild("args", allArgs) + + targetVars, err := uo.FromStruct(target) + if err != nil { + return nil, err + } + varsCtx.UpdateChild("target", targetVars) + + d, err := deployment.NewDeploymentProject(k, varsCtx, deploymentDir, p.SealedSecretsDir, nil) + if err != nil { + return nil, err + } + c, err := deployment.NewDeploymentCollection(d, images, inclusion, renderOutputDir, forSeal) + if err != nil { + return nil, err + } + + ctx := &TargetContext{ + KluctlProject: p, + Target: target, + ClusterConfig: clusterConfig, + K: k, + DeploymentProject: d, + DeploymentCollection: c, + } + + return ctx, nil +} From a45494f36c9931da7fc048b9b429235665fdb5c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aljoscha=20P=C3=B6rtner?= Date: Tue, 19 Apr 2022 20:51:07 +0200 Subject: [PATCH 0663/2916] refactor: remove envs and download python for each arch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Aljoscha Pörtner --- Makefile | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index 6be0074e8..b74eb7b59 100644 --- a/Makefile +++ b/Makefile @@ -14,14 +14,11 @@ WHITE := $(shell tput -Txterm setaf 7) CYAN := $(shell tput -Txterm setaf 6) RESET := $(shell tput -Txterm sgr0) -.PHONY: all test build vendor check-env check-kubectl check-helm check-kind +.PHONY: all test build vendor check-kubectl check-helm check-kind all: help ## Check: -check-env: ## Checks if required environment variables are set - @test $${GOOS?Please set environment variable GOOS} - @test $${GOARCH?Please set environment variable GOARCH} check-kubectl: ## Checks if kubectl is installed kubectl version --client=true @@ -35,9 +32,9 @@ check-kind: ## Checks if kind is installed ## Build: build: vendor python generate build-go ## Run the complete build pipeline -build-go: check-env ## Build your project and put the output binary in out/bin/ +build-go: ## Build your project and put the output binary in out/bin/ mkdir -p out/bin - CGO_ENBALED=0 GO111MODULE=on GOARCH=$(GOARCH) GOOS=$(GOOS) $(GOCMD) build -mod vendor -o out/bin/$(BINARY_NAME) ./cmd/kluctl + CGO_ENBALED=0 GO111MODULE=on $(GOCMD) build -mod vendor -o out/bin/$(BINARY_NAME) ./cmd/kluctl clean: ## Remove build related file rm -fr ./bin @@ -48,18 +45,20 @@ clean: ## Remove build related file vendor: ## Copy of all packages needed to support builds and tests in the vendor directory $(GOCMD) mod vendor -python: check-env ## Download python for Jinja2 support - ./hack/download-python.sh $(GOARCH) +python: ## Download python for Jinja2 support + ./hack/download-python.sh linux + ./hack/download-python.sh windows + ./hack/download-python.sh darwin generate: ## Generating Jinja2 support $(GOCMD) generate ./... - $(GOCMD) generate -tags $(GOOS) ./pkg/python + $(GOCMD) generate ./pkg/python ## Test: test: test-unit test-e2e ## Runs the complete test suite -test-e2e: check-env check-kubectl check-helm check-kind ## Runs the end to end tests - CGO_ENBALED=0 GO111MODULE=on GOARCH=$(GOARCH) GOOS=$(GOOS) $(GOCMD) test -o out/bin/$(TEST_BINARY_NAME) ./e2e +test-e2e: check-kubectl check-helm check-kind ## Runs the end to end tests + CGO_ENBALED=0 GO111MODULE=on $(GOCMD) test -o out/bin/$(TEST_BINARY_NAME) ./e2e test-unit: ## Run the unit tests of the project ifeq ($(EXPORT_RESULT), true) From d878db43a5a6efae966367601783e85995b54627 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 19 Apr 2022 22:56:25 +0200 Subject: [PATCH 0664/2916] fix: Fix module to include v2 --- cmd/kluctl/args/images.go | 4 ++-- cmd/kluctl/args/inclusion.go | 2 +- cmd/kluctl/commands/cmd_archive.go | 4 ++-- cmd/kluctl/commands/cmd_check_image_updates.go | 10 +++++----- cmd/kluctl/commands/cmd_delete.go | 16 ++++++++-------- cmd/kluctl/commands/cmd_deploy.go | 8 ++++---- cmd/kluctl/commands/cmd_diff.go | 4 ++-- cmd/kluctl/commands/cmd_downscale.go | 6 +++--- cmd/kluctl/commands/cmd_helm_pull.go | 2 +- cmd/kluctl/commands/cmd_helm_update.go | 4 ++-- cmd/kluctl/commands/cmd_list_images.go | 4 ++-- cmd/kluctl/commands/cmd_list_targets.go | 6 +++--- cmd/kluctl/commands/cmd_poke_images.go | 6 +++--- cmd/kluctl/commands/cmd_prune.go | 4 ++-- cmd/kluctl/commands/cmd_render.go | 4 ++-- cmd/kluctl/commands/cmd_seal.go | 12 ++++++------ cmd/kluctl/commands/cmd_validate.go | 4 ++-- cmd/kluctl/commands/cmd_version.go | 2 +- cmd/kluctl/commands/command_result.go | 8 ++++---- cmd/kluctl/commands/root.go | 10 +++++----- cmd/kluctl/commands/utils.go | 12 ++++++------ cmd/kluctl/main.go | 2 +- e2e/external_projects_test.go | 2 +- e2e/hooks_test.go | 2 +- e2e/kind_cluster.go | 6 +++--- e2e/project.go | 8 ++++---- e2e/utils.go | 4 ++-- e2e/utils_resources.go | 2 +- go.mod | 2 +- pkg/deployment/commands/delete.go | 8 ++++---- pkg/deployment/commands/deploy.go | 8 ++++---- pkg/deployment/commands/diff.go | 8 ++++---- pkg/deployment/commands/downscale.go | 12 ++++++------ pkg/deployment/commands/poke_images.go | 12 ++++++------ pkg/deployment/commands/prune.go | 8 ++++---- pkg/deployment/commands/seal.go | 4 ++-- pkg/deployment/commands/validate.go | 12 ++++++------ pkg/deployment/deployment_collection.go | 8 ++++---- pkg/deployment/deployment_item.go | 10 +++++----- pkg/deployment/deployment_project.go | 12 ++++++------ pkg/deployment/external_args.go | 8 ++++---- pkg/deployment/helm_chart.go | 12 ++++++------ pkg/deployment/images.go | 16 ++++++++-------- pkg/deployment/utils/apply_utils.go | 16 ++++++++-------- pkg/deployment/utils/delete_utils.go | 8 ++++---- pkg/deployment/utils/diff_utils.go | 10 +++++----- pkg/deployment/utils/diff_utils_test.go | 8 ++++---- pkg/deployment/utils/downscale_utils.go | 4 ++-- pkg/deployment/utils/errors_holder.go | 8 ++++---- pkg/deployment/utils/hooks_util.go | 8 ++++---- pkg/deployment/utils/remote_objects_utils.go | 8 ++++---- pkg/diff/diff.go | 6 +++--- pkg/diff/managed_fields.go | 2 +- pkg/diff/normalize.go | 4 ++-- pkg/git/auth/auth_provider.go | 2 +- pkg/git/auth/env_auth_provider.go | 4 ++-- pkg/git/auth/git_credentials_file.go | 4 ++-- pkg/git/auth/ssh_auth_provider.go | 4 ++-- pkg/git/auth/ssh_known_hosts.go | 4 ++-- pkg/git/http-server/http.go | 2 +- pkg/git/http-server/utils.go | 2 +- pkg/git/mirrored_repo.go | 6 +++--- pkg/git/poor_mans_clone.go | 2 +- pkg/git/utils.go | 2 +- pkg/jinja2/jinja2.go | 4 ++-- pkg/jinja2/jinja2_renderer.go | 4 ++-- pkg/jinja2/source.go | 4 ++-- pkg/jinja2/vars.go | 10 +++++----- pkg/k8s/k8s_cluster.go | 8 ++++---- pkg/k8s/utils.go | 2 +- pkg/kluctl_project/git.go | 8 ++++---- pkg/kluctl_project/load.go | 10 +++++----- pkg/kluctl_project/load_targets.go | 12 ++++++------ pkg/kluctl_project/project.go | 10 +++++----- pkg/kluctl_project/target_context.go | 12 ++++++------ pkg/python/embed.go | 4 ++-- pkg/registries/registries.go | 4 ++-- pkg/seal/bootstrap.go | 6 +++--- pkg/seal/fetch_cert.go | 2 +- pkg/seal/sealer.go | 12 ++++++------ pkg/seal/secrets_loader.go | 10 +++++----- pkg/types/cluster_config.go | 6 +++--- pkg/types/command_result.go | 4 ++-- pkg/types/deployment.go | 4 ++-- pkg/types/git_project.go | 4 ++-- pkg/types/kluctl_project.go | 4 ++-- pkg/types/secrets_source.go | 2 +- pkg/types/target_config.go | 4 ++-- pkg/utils/embed_util/extract.go | 2 +- pkg/utils/embed_util/packer/main.go | 2 +- pkg/utils/uo/k8s_fields.go | 2 +- pkg/utils/uo/unstructured.go | 2 +- pkg/utils/uo/uo.go | 2 +- pkg/utils/versions/latest_version.go | 2 +- pkg/utils/versions/latest_version_parse.go | 2 +- pkg/utils/versions/looseversion.go | 2 +- pkg/validation/validation.go | 6 +++--- pkg/yaml/yaml.go | 2 +- 98 files changed, 293 insertions(+), 293 deletions(-) diff --git a/cmd/kluctl/args/images.go b/cmd/kluctl/args/images.go index f53ec2c58..bb9de4568 100644 --- a/cmd/kluctl/args/images.go +++ b/cmd/kluctl/args/images.go @@ -2,8 +2,8 @@ package args import ( "fmt" - "github.com/kluctl/kluctl/pkg/types" - "github.com/kluctl/kluctl/pkg/yaml" + "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/yaml" "strings" ) diff --git a/cmd/kluctl/args/inclusion.go b/cmd/kluctl/args/inclusion.go index 85670f009..060a6af97 100644 --- a/cmd/kluctl/args/inclusion.go +++ b/cmd/kluctl/args/inclusion.go @@ -2,7 +2,7 @@ package args import ( "fmt" - "github.com/kluctl/kluctl/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils" "os" "path/filepath" "strings" diff --git a/cmd/kluctl/commands/cmd_archive.go b/cmd/kluctl/commands/cmd_archive.go index dbe3bb83c..90977ace4 100644 --- a/cmd/kluctl/commands/cmd_archive.go +++ b/cmd/kluctl/commands/cmd_archive.go @@ -1,8 +1,8 @@ package commands import ( - "github.com/kluctl/kluctl/cmd/kluctl/args" - "github.com/kluctl/kluctl/pkg/kluctl_project" + "github.com/kluctl/kluctl/v2/cmd/kluctl/args" + "github.com/kluctl/kluctl/v2/pkg/kluctl_project" ) type archiveCmd struct { diff --git a/cmd/kluctl/commands/cmd_check_image_updates.go b/cmd/kluctl/commands/cmd_check_image_updates.go index 74538ce07..8d65ee305 100644 --- a/cmd/kluctl/commands/cmd_check_image_updates.go +++ b/cmd/kluctl/commands/cmd_check_image_updates.go @@ -1,11 +1,11 @@ package commands import ( - "github.com/kluctl/kluctl/cmd/kluctl/args" - "github.com/kluctl/kluctl/pkg/registries" - "github.com/kluctl/kluctl/pkg/types/k8s" - "github.com/kluctl/kluctl/pkg/utils" - "github.com/kluctl/kluctl/pkg/utils/versions" + "github.com/kluctl/kluctl/v2/cmd/kluctl/args" + "github.com/kluctl/kluctl/v2/pkg/registries" + "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/versions" log "github.com/sirupsen/logrus" "os" "regexp" diff --git a/cmd/kluctl/commands/cmd_delete.go b/cmd/kluctl/commands/cmd_delete.go index ae16159b6..c078332de 100644 --- a/cmd/kluctl/commands/cmd_delete.go +++ b/cmd/kluctl/commands/cmd_delete.go @@ -2,14 +2,14 @@ package commands import ( "fmt" - "github.com/kluctl/kluctl/cmd/kluctl/args" - "github.com/kluctl/kluctl/pkg/deployment" - "github.com/kluctl/kluctl/pkg/deployment/commands" - "github.com/kluctl/kluctl/pkg/deployment/utils" - "github.com/kluctl/kluctl/pkg/k8s" - "github.com/kluctl/kluctl/pkg/types" - k8s2 "github.com/kluctl/kluctl/pkg/types/k8s" - utils2 "github.com/kluctl/kluctl/pkg/utils" + "github.com/kluctl/kluctl/v2/cmd/kluctl/args" + "github.com/kluctl/kluctl/v2/pkg/deployment" + "github.com/kluctl/kluctl/v2/pkg/deployment/commands" + "github.com/kluctl/kluctl/v2/pkg/deployment/utils" + "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/types" + k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" + utils2 "github.com/kluctl/kluctl/v2/pkg/utils" "os" ) diff --git a/cmd/kluctl/commands/cmd_deploy.go b/cmd/kluctl/commands/cmd_deploy.go index 0f4d22915..15dcea2f2 100644 --- a/cmd/kluctl/commands/cmd_deploy.go +++ b/cmd/kluctl/commands/cmd_deploy.go @@ -2,10 +2,10 @@ package commands import ( "fmt" - "github.com/kluctl/kluctl/cmd/kluctl/args" - "github.com/kluctl/kluctl/pkg/deployment/commands" - "github.com/kluctl/kluctl/pkg/types" - "github.com/kluctl/kluctl/pkg/utils" + "github.com/kluctl/kluctl/v2/cmd/kluctl/args" + "github.com/kluctl/kluctl/v2/pkg/deployment/commands" + "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/utils" ) type deployCmd struct { diff --git a/cmd/kluctl/commands/cmd_diff.go b/cmd/kluctl/commands/cmd_diff.go index 374926510..0f7f70b63 100644 --- a/cmd/kluctl/commands/cmd_diff.go +++ b/cmd/kluctl/commands/cmd_diff.go @@ -2,8 +2,8 @@ package commands import ( "fmt" - "github.com/kluctl/kluctl/cmd/kluctl/args" - "github.com/kluctl/kluctl/pkg/deployment/commands" + "github.com/kluctl/kluctl/v2/cmd/kluctl/args" + "github.com/kluctl/kluctl/v2/pkg/deployment/commands" ) type diffCmd struct { diff --git a/cmd/kluctl/commands/cmd_downscale.go b/cmd/kluctl/commands/cmd_downscale.go index d4afda0e6..e5383e3b2 100644 --- a/cmd/kluctl/commands/cmd_downscale.go +++ b/cmd/kluctl/commands/cmd_downscale.go @@ -2,9 +2,9 @@ package commands import ( "fmt" - "github.com/kluctl/kluctl/cmd/kluctl/args" - "github.com/kluctl/kluctl/pkg/deployment/commands" - "github.com/kluctl/kluctl/pkg/utils" + "github.com/kluctl/kluctl/v2/cmd/kluctl/args" + "github.com/kluctl/kluctl/v2/pkg/deployment/commands" + "github.com/kluctl/kluctl/v2/pkg/utils" ) type downscaleCmd struct { diff --git a/cmd/kluctl/commands/cmd_helm_pull.go b/cmd/kluctl/commands/cmd_helm_pull.go index b2dcc01b8..38aec11c5 100644 --- a/cmd/kluctl/commands/cmd_helm_pull.go +++ b/cmd/kluctl/commands/cmd_helm_pull.go @@ -1,7 +1,7 @@ package commands import ( - "github.com/kluctl/kluctl/pkg/deployment" + "github.com/kluctl/kluctl/v2/pkg/deployment" log "github.com/sirupsen/logrus" "io/fs" "path/filepath" diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go index daa48b4c7..f74b41aaa 100644 --- a/cmd/kluctl/commands/cmd_helm_update.go +++ b/cmd/kluctl/commands/cmd_helm_update.go @@ -3,8 +3,8 @@ package commands import ( "fmt" "github.com/go-git/go-git/v5" - "github.com/kluctl/kluctl/pkg/deployment" - git2 "github.com/kluctl/kluctl/pkg/git" + "github.com/kluctl/kluctl/v2/pkg/deployment" + git2 "github.com/kluctl/kluctl/v2/pkg/git" log "github.com/sirupsen/logrus" "io/fs" "path/filepath" diff --git a/cmd/kluctl/commands/cmd_list_images.go b/cmd/kluctl/commands/cmd_list_images.go index eeeb58986..80bd70382 100644 --- a/cmd/kluctl/commands/cmd_list_images.go +++ b/cmd/kluctl/commands/cmd_list_images.go @@ -1,8 +1,8 @@ package commands import ( - "github.com/kluctl/kluctl/cmd/kluctl/args" - "github.com/kluctl/kluctl/pkg/types" + "github.com/kluctl/kluctl/v2/cmd/kluctl/args" + "github.com/kluctl/kluctl/v2/pkg/types" ) type listImagesCmd struct { diff --git a/cmd/kluctl/commands/cmd_list_targets.go b/cmd/kluctl/commands/cmd_list_targets.go index 93984dbe0..29f9036f3 100644 --- a/cmd/kluctl/commands/cmd_list_targets.go +++ b/cmd/kluctl/commands/cmd_list_targets.go @@ -1,9 +1,9 @@ package commands import ( - "github.com/kluctl/kluctl/cmd/kluctl/args" - "github.com/kluctl/kluctl/pkg/kluctl_project" - "github.com/kluctl/kluctl/pkg/types" + "github.com/kluctl/kluctl/v2/cmd/kluctl/args" + "github.com/kluctl/kluctl/v2/pkg/kluctl_project" + "github.com/kluctl/kluctl/v2/pkg/types" ) type listTargetsCmd struct { diff --git a/cmd/kluctl/commands/cmd_poke_images.go b/cmd/kluctl/commands/cmd_poke_images.go index 0857ab8c0..a6898ea11 100644 --- a/cmd/kluctl/commands/cmd_poke_images.go +++ b/cmd/kluctl/commands/cmd_poke_images.go @@ -2,9 +2,9 @@ package commands import ( "fmt" - "github.com/kluctl/kluctl/cmd/kluctl/args" - "github.com/kluctl/kluctl/pkg/deployment/commands" - "github.com/kluctl/kluctl/pkg/utils" + "github.com/kluctl/kluctl/v2/cmd/kluctl/args" + "github.com/kluctl/kluctl/v2/pkg/deployment/commands" + "github.com/kluctl/kluctl/v2/pkg/utils" ) type pokeImagesCmd struct { diff --git a/cmd/kluctl/commands/cmd_prune.go b/cmd/kluctl/commands/cmd_prune.go index 3712b8b3d..2ef240e88 100644 --- a/cmd/kluctl/commands/cmd_prune.go +++ b/cmd/kluctl/commands/cmd_prune.go @@ -2,8 +2,8 @@ package commands import ( "fmt" - "github.com/kluctl/kluctl/cmd/kluctl/args" - "github.com/kluctl/kluctl/pkg/deployment/commands" + "github.com/kluctl/kluctl/v2/cmd/kluctl/args" + "github.com/kluctl/kluctl/v2/pkg/deployment/commands" ) type pruneCmd struct { diff --git a/cmd/kluctl/commands/cmd_render.go b/cmd/kluctl/commands/cmd_render.go index abd550b00..a686da313 100644 --- a/cmd/kluctl/commands/cmd_render.go +++ b/cmd/kluctl/commands/cmd_render.go @@ -1,8 +1,8 @@ package commands import ( - "github.com/kluctl/kluctl/cmd/kluctl/args" - "github.com/kluctl/kluctl/pkg/utils" + "github.com/kluctl/kluctl/v2/cmd/kluctl/args" + "github.com/kluctl/kluctl/v2/pkg/utils" log "github.com/sirupsen/logrus" "io/ioutil" ) diff --git a/cmd/kluctl/commands/cmd_seal.go b/cmd/kluctl/commands/cmd_seal.go index d48510ab2..185c07f44 100644 --- a/cmd/kluctl/commands/cmd_seal.go +++ b/cmd/kluctl/commands/cmd_seal.go @@ -2,12 +2,12 @@ package commands import ( "fmt" - "github.com/kluctl/kluctl/cmd/kluctl/args" - "github.com/kluctl/kluctl/pkg/deployment/commands" - "github.com/kluctl/kluctl/pkg/kluctl_project" - "github.com/kluctl/kluctl/pkg/seal" - "github.com/kluctl/kluctl/pkg/types" - "github.com/kluctl/kluctl/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/cmd/kluctl/args" + "github.com/kluctl/kluctl/v2/pkg/deployment/commands" + "github.com/kluctl/kluctl/v2/pkg/kluctl_project" + "github.com/kluctl/kluctl/v2/pkg/seal" + "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" log "github.com/sirupsen/logrus" ) diff --git a/cmd/kluctl/commands/cmd_validate.go b/cmd/kluctl/commands/cmd_validate.go index 6008a4269..4a9bf23e4 100644 --- a/cmd/kluctl/commands/cmd_validate.go +++ b/cmd/kluctl/commands/cmd_validate.go @@ -2,8 +2,8 @@ package commands import ( "fmt" - "github.com/kluctl/kluctl/cmd/kluctl/args" - "github.com/kluctl/kluctl/pkg/deployment/commands" + "github.com/kluctl/kluctl/v2/cmd/kluctl/args" + "github.com/kluctl/kluctl/v2/pkg/deployment/commands" "os" "time" ) diff --git a/cmd/kluctl/commands/cmd_version.go b/cmd/kluctl/commands/cmd_version.go index 734083a64..de05a2d49 100644 --- a/cmd/kluctl/commands/cmd_version.go +++ b/cmd/kluctl/commands/cmd_version.go @@ -1,7 +1,7 @@ package commands import ( - "github.com/kluctl/kluctl/pkg/version" + "github.com/kluctl/kluctl/v2/pkg/version" "os" ) diff --git a/cmd/kluctl/commands/command_result.go b/cmd/kluctl/commands/command_result.go index 12dc4f46d..bd2e8cd59 100644 --- a/cmd/kluctl/commands/command_result.go +++ b/cmd/kluctl/commands/command_result.go @@ -3,10 +3,10 @@ package commands import ( "bytes" "fmt" - "github.com/kluctl/kluctl/pkg/types" - "github.com/kluctl/kluctl/pkg/types/k8s" - "github.com/kluctl/kluctl/pkg/utils" - "github.com/kluctl/kluctl/pkg/yaml" + "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/yaml" "io" "os" "strings" diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index e1efc9e0e..0b20a914b 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -17,11 +17,11 @@ package commands import ( "github.com/alecthomas/kong" - "github.com/kluctl/kluctl/pkg/utils" - "github.com/kluctl/kluctl/pkg/utils/uo" - "github.com/kluctl/kluctl/pkg/utils/versions" - "github.com/kluctl/kluctl/pkg/version" - "github.com/kluctl/kluctl/pkg/yaml" + "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/utils/versions" + "github.com/kluctl/kluctl/v2/pkg/version" + "github.com/kluctl/kluctl/v2/pkg/yaml" log "github.com/sirupsen/logrus" "net/http" "os" diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 27fa9a832..bfcbf341e 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -2,12 +2,12 @@ package commands import ( "fmt" - "github.com/kluctl/kluctl/cmd/kluctl/args" - "github.com/kluctl/kluctl/pkg/deployment" - git_url "github.com/kluctl/kluctl/pkg/git/git-url" - "github.com/kluctl/kluctl/pkg/jinja2" - "github.com/kluctl/kluctl/pkg/kluctl_project" - "github.com/kluctl/kluctl/pkg/utils" + "github.com/kluctl/kluctl/v2/cmd/kluctl/args" + "github.com/kluctl/kluctl/v2/pkg/deployment" + git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" + "github.com/kluctl/kluctl/v2/pkg/jinja2" + "github.com/kluctl/kluctl/v2/pkg/kluctl_project" + "github.com/kluctl/kluctl/v2/pkg/utils" "io/ioutil" "os" ) diff --git a/cmd/kluctl/main.go b/cmd/kluctl/main.go index fbc4cdde9..e95adcccb 100644 --- a/cmd/kluctl/main.go +++ b/cmd/kluctl/main.go @@ -15,7 +15,7 @@ limitations under the License. */ package main -import "github.com/kluctl/kluctl/cmd/kluctl/commands" +import "github.com/kluctl/kluctl/v2/cmd/kluctl/commands" func main() { commands.Execute() diff --git a/e2e/external_projects_test.go b/e2e/external_projects_test.go index 9429aa372..87a972634 100644 --- a/e2e/external_projects_test.go +++ b/e2e/external_projects_test.go @@ -2,7 +2,7 @@ package e2e import ( "fmt" - "github.com/kluctl/kluctl/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" "testing" "time" ) diff --git a/e2e/hooks_test.go b/e2e/hooks_test.go index d5cf16b6f..7e6b4557e 100644 --- a/e2e/hooks_test.go +++ b/e2e/hooks_test.go @@ -3,7 +3,7 @@ package e2e import ( "encoding/base64" "fmt" - "github.com/kluctl/kluctl/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" "testing" ) diff --git a/e2e/kind_cluster.go b/e2e/kind_cluster.go index 9bbfd0e43..f420001a2 100644 --- a/e2e/kind_cluster.go +++ b/e2e/kind_cluster.go @@ -2,9 +2,9 @@ package e2e import ( "fmt" - "github.com/kluctl/kluctl/pkg/utils" - "github.com/kluctl/kluctl/pkg/utils/uo" - "github.com/kluctl/kluctl/pkg/yaml" + "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/yaml" "github.com/pkg/errors" log "github.com/sirupsen/logrus" "k8s.io/client-go/rest" diff --git a/e2e/project.go b/e2e/project.go index bde8aa916..c19bd3b67 100644 --- a/e2e/project.go +++ b/e2e/project.go @@ -4,10 +4,10 @@ import ( "context" "fmt" "github.com/go-git/go-git/v5" - http_server "github.com/kluctl/kluctl/pkg/git/http-server" - "github.com/kluctl/kluctl/pkg/utils" - "github.com/kluctl/kluctl/pkg/utils/uo" - "github.com/kluctl/kluctl/pkg/yaml" + http_server "github.com/kluctl/kluctl/v2/pkg/git/http-server" + "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/yaml" log "github.com/sirupsen/logrus" "io/ioutil" "net" diff --git a/e2e/utils.go b/e2e/utils.go index ad2c8930e..1a1abc672 100644 --- a/e2e/utils.go +++ b/e2e/utils.go @@ -3,8 +3,8 @@ package e2e import ( "bufio" "bytes" - "github.com/kluctl/kluctl/pkg/utils/uo" - "github.com/kluctl/kluctl/pkg/validation" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/validation" "io" "os/exec" "reflect" diff --git a/e2e/utils_resources.go b/e2e/utils_resources.go index aaff14756..255e07476 100644 --- a/e2e/utils_resources.go +++ b/e2e/utils_resources.go @@ -2,7 +2,7 @@ package e2e import ( "bytes" - "github.com/kluctl/kluctl/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" "text/template" ) diff --git a/go.mod b/go.mod index 30c91dca7..7b7f6a544 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/kluctl/kluctl +module github.com/kluctl/kluctl/v2 go 1.17 diff --git a/pkg/deployment/commands/delete.go b/pkg/deployment/commands/delete.go index aaa60c889..872e8a7ad 100644 --- a/pkg/deployment/commands/delete.go +++ b/pkg/deployment/commands/delete.go @@ -1,10 +1,10 @@ package commands import ( - "github.com/kluctl/kluctl/pkg/deployment" - utils2 "github.com/kluctl/kluctl/pkg/deployment/utils" - "github.com/kluctl/kluctl/pkg/k8s" - k8s2 "github.com/kluctl/kluctl/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/deployment" + utils2 "github.com/kluctl/kluctl/v2/pkg/deployment/utils" + "github.com/kluctl/kluctl/v2/pkg/k8s" + k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" ) type DeleteCommand struct { diff --git a/pkg/deployment/commands/deploy.go b/pkg/deployment/commands/deploy.go index e90591635..ed762c0e1 100644 --- a/pkg/deployment/commands/deploy.go +++ b/pkg/deployment/commands/deploy.go @@ -1,10 +1,10 @@ package commands import ( - "github.com/kluctl/kluctl/pkg/deployment" - utils2 "github.com/kluctl/kluctl/pkg/deployment/utils" - "github.com/kluctl/kluctl/pkg/k8s" - "github.com/kluctl/kluctl/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/deployment" + utils2 "github.com/kluctl/kluctl/v2/pkg/deployment/utils" + "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/types" "time" ) diff --git a/pkg/deployment/commands/diff.go b/pkg/deployment/commands/diff.go index 106c98048..c7e35a3d5 100644 --- a/pkg/deployment/commands/diff.go +++ b/pkg/deployment/commands/diff.go @@ -1,10 +1,10 @@ package commands import ( - "github.com/kluctl/kluctl/pkg/deployment" - "github.com/kluctl/kluctl/pkg/deployment/utils" - "github.com/kluctl/kluctl/pkg/k8s" - "github.com/kluctl/kluctl/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/deployment" + "github.com/kluctl/kluctl/v2/pkg/deployment/utils" + "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/types" ) type DiffCommand struct { diff --git a/pkg/deployment/commands/downscale.go b/pkg/deployment/commands/downscale.go index 5bede4036..edd25944e 100644 --- a/pkg/deployment/commands/downscale.go +++ b/pkg/deployment/commands/downscale.go @@ -1,12 +1,12 @@ package commands import ( - "github.com/kluctl/kluctl/pkg/deployment" - utils2 "github.com/kluctl/kluctl/pkg/deployment/utils" - "github.com/kluctl/kluctl/pkg/k8s" - "github.com/kluctl/kluctl/pkg/types" - k8s2 "github.com/kluctl/kluctl/pkg/types/k8s" - "github.com/kluctl/kluctl/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/deployment" + utils2 "github.com/kluctl/kluctl/v2/pkg/deployment/utils" + "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/types" + k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" "sync" ) diff --git a/pkg/deployment/commands/poke_images.go b/pkg/deployment/commands/poke_images.go index 91ecf397e..9e2fc9173 100644 --- a/pkg/deployment/commands/poke_images.go +++ b/pkg/deployment/commands/poke_images.go @@ -2,12 +2,12 @@ package commands import ( "fmt" - "github.com/kluctl/kluctl/pkg/deployment" - utils2 "github.com/kluctl/kluctl/pkg/deployment/utils" - "github.com/kluctl/kluctl/pkg/k8s" - "github.com/kluctl/kluctl/pkg/types" - k8s2 "github.com/kluctl/kluctl/pkg/types/k8s" - "github.com/kluctl/kluctl/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/deployment" + utils2 "github.com/kluctl/kluctl/v2/pkg/deployment/utils" + "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/types" + k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" "sync" ) diff --git a/pkg/deployment/commands/prune.go b/pkg/deployment/commands/prune.go index c434551df..873dc3a11 100644 --- a/pkg/deployment/commands/prune.go +++ b/pkg/deployment/commands/prune.go @@ -1,10 +1,10 @@ package commands import ( - "github.com/kluctl/kluctl/pkg/deployment" - utils2 "github.com/kluctl/kluctl/pkg/deployment/utils" - "github.com/kluctl/kluctl/pkg/k8s" - k8s2 "github.com/kluctl/kluctl/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/deployment" + utils2 "github.com/kluctl/kluctl/v2/pkg/deployment/utils" + "github.com/kluctl/kluctl/v2/pkg/k8s" + k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" ) type PruneCommand struct { diff --git a/pkg/deployment/commands/seal.go b/pkg/deployment/commands/seal.go index 2d3b2f6df..3a91f8b87 100644 --- a/pkg/deployment/commands/seal.go +++ b/pkg/deployment/commands/seal.go @@ -2,8 +2,8 @@ package commands import ( "fmt" - "github.com/kluctl/kluctl/pkg/deployment" - "github.com/kluctl/kluctl/pkg/seal" + "github.com/kluctl/kluctl/v2/pkg/deployment" + "github.com/kluctl/kluctl/v2/pkg/seal" "io/fs" "path/filepath" "strings" diff --git a/pkg/deployment/commands/validate.go b/pkg/deployment/commands/validate.go index a067d7832..3a94cc032 100644 --- a/pkg/deployment/commands/validate.go +++ b/pkg/deployment/commands/validate.go @@ -1,12 +1,12 @@ package commands import ( - "github.com/kluctl/kluctl/pkg/deployment" - utils2 "github.com/kluctl/kluctl/pkg/deployment/utils" - "github.com/kluctl/kluctl/pkg/k8s" - "github.com/kluctl/kluctl/pkg/types" - k8s2 "github.com/kluctl/kluctl/pkg/types/k8s" - "github.com/kluctl/kluctl/pkg/validation" + "github.com/kluctl/kluctl/v2/pkg/deployment" + utils2 "github.com/kluctl/kluctl/v2/pkg/deployment/utils" + "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/types" + k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/validation" ) type ValidateCommand struct { diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index b84c9e11e..887e903bf 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -3,10 +3,10 @@ package deployment import ( "context" "fmt" - "github.com/kluctl/kluctl/pkg/k8s" - "github.com/kluctl/kluctl/pkg/types" - k8s2 "github.com/kluctl/kluctl/pkg/types/k8s" - "github.com/kluctl/kluctl/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/types" + k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/utils" log "github.com/sirupsen/logrus" "golang.org/x/sync/semaphore" "path/filepath" diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 4eb16cd43..e723c480e 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -2,11 +2,11 @@ package deployment import ( "fmt" - "github.com/kluctl/kluctl/pkg/k8s" - "github.com/kluctl/kluctl/pkg/types" - "github.com/kluctl/kluctl/pkg/utils" - "github.com/kluctl/kluctl/pkg/utils/uo" - "github.com/kluctl/kluctl/pkg/yaml" + "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/yaml" "io/fs" "io/ioutil" "os" diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go index 1b82a17e5..8193ff13d 100644 --- a/pkg/deployment/deployment_project.go +++ b/pkg/deployment/deployment_project.go @@ -2,12 +2,12 @@ package deployment import ( "fmt" - "github.com/kluctl/kluctl/pkg/jinja2" - "github.com/kluctl/kluctl/pkg/k8s" - "github.com/kluctl/kluctl/pkg/types" - "github.com/kluctl/kluctl/pkg/utils" - "github.com/kluctl/kluctl/pkg/utils/uo" - "github.com/kluctl/kluctl/pkg/yaml" + "github.com/kluctl/kluctl/v2/pkg/jinja2" + "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/yaml" log "github.com/sirupsen/logrus" "path/filepath" "reflect" diff --git a/pkg/deployment/external_args.go b/pkg/deployment/external_args.go index 70e203353..b6db19f41 100644 --- a/pkg/deployment/external_args.go +++ b/pkg/deployment/external_args.go @@ -2,10 +2,10 @@ package deployment import ( "fmt" - "github.com/kluctl/kluctl/pkg/jinja2" - "github.com/kluctl/kluctl/pkg/types" - "github.com/kluctl/kluctl/pkg/utils/uo" - "github.com/kluctl/kluctl/pkg/yaml" + "github.com/kluctl/kluctl/v2/pkg/jinja2" + "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/yaml" "path/filepath" "regexp" "strings" diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index 3b0dbaac6..0b26b404c 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -2,12 +2,12 @@ package deployment import ( "fmt" - "github.com/kluctl/kluctl/pkg/k8s" - "github.com/kluctl/kluctl/pkg/types" - "github.com/kluctl/kluctl/pkg/utils" - "github.com/kluctl/kluctl/pkg/utils/uo" - "github.com/kluctl/kluctl/pkg/utils/versions" - "github.com/kluctl/kluctl/pkg/yaml" + "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/utils/versions" + "github.com/kluctl/kluctl/v2/pkg/yaml" "io/ioutil" "os" "os/exec" diff --git a/pkg/deployment/images.go b/pkg/deployment/images.go index 25c04a7c9..654c72025 100644 --- a/pkg/deployment/images.go +++ b/pkg/deployment/images.go @@ -4,14 +4,14 @@ import ( "bytes" "encoding/base64" "fmt" - "github.com/kluctl/kluctl/pkg/k8s" - "github.com/kluctl/kluctl/pkg/registries" - "github.com/kluctl/kluctl/pkg/types" - k8s2 "github.com/kluctl/kluctl/pkg/types/k8s" - "github.com/kluctl/kluctl/pkg/utils" - "github.com/kluctl/kluctl/pkg/utils/uo" - "github.com/kluctl/kluctl/pkg/utils/versions" - "github.com/kluctl/kluctl/pkg/yaml" + "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/registries" + "github.com/kluctl/kluctl/v2/pkg/types" + k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/utils/versions" + "github.com/kluctl/kluctl/v2/pkg/yaml" "k8s.io/apimachinery/pkg/api/errors" "strings" "sync" diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index a9aafc2b3..ebbca95c1 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -4,14 +4,14 @@ import ( "context" errors2 "errors" "fmt" - "github.com/kluctl/kluctl/pkg/deployment" - "github.com/kluctl/kluctl/pkg/diff" - "github.com/kluctl/kluctl/pkg/k8s" - "github.com/kluctl/kluctl/pkg/types" - k8s2 "github.com/kluctl/kluctl/pkg/types/k8s" - "github.com/kluctl/kluctl/pkg/utils" - "github.com/kluctl/kluctl/pkg/utils/uo" - "github.com/kluctl/kluctl/pkg/validation" + "github.com/kluctl/kluctl/v2/pkg/deployment" + "github.com/kluctl/kluctl/v2/pkg/diff" + "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/types" + k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/validation" log "github.com/sirupsen/logrus" "github.com/vbauerster/mpb/v7" "golang.org/x/sync/semaphore" diff --git a/pkg/deployment/utils/delete_utils.go b/pkg/deployment/utils/delete_utils.go index f83cce908..655f4a1ca 100644 --- a/pkg/deployment/utils/delete_utils.go +++ b/pkg/deployment/utils/delete_utils.go @@ -2,10 +2,10 @@ package utils import ( "context" - "github.com/kluctl/kluctl/pkg/k8s" - "github.com/kluctl/kluctl/pkg/types" - k8s2 "github.com/kluctl/kluctl/pkg/types/k8s" - "github.com/kluctl/kluctl/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/types" + k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" "golang.org/x/sync/semaphore" "k8s.io/apimachinery/pkg/runtime/schema" "strconv" diff --git a/pkg/deployment/utils/diff_utils.go b/pkg/deployment/utils/diff_utils.go index 611523ed4..3690ecdd8 100644 --- a/pkg/deployment/utils/diff_utils.go +++ b/pkg/deployment/utils/diff_utils.go @@ -1,11 +1,11 @@ package utils import ( - "github.com/kluctl/kluctl/pkg/deployment" - "github.com/kluctl/kluctl/pkg/diff" - "github.com/kluctl/kluctl/pkg/types" - k8s2 "github.com/kluctl/kluctl/pkg/types/k8s" - "github.com/kluctl/kluctl/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/deployment" + "github.com/kluctl/kluctl/v2/pkg/diff" + "github.com/kluctl/kluctl/v2/pkg/types" + k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" "sort" "sync" "time" diff --git a/pkg/deployment/utils/diff_utils_test.go b/pkg/deployment/utils/diff_utils_test.go index 2e245c5eb..bb494fcb4 100644 --- a/pkg/deployment/utils/diff_utils_test.go +++ b/pkg/deployment/utils/diff_utils_test.go @@ -1,10 +1,10 @@ package utils import ( - "github.com/kluctl/kluctl/pkg/deployment" - "github.com/kluctl/kluctl/pkg/types" - k8s2 "github.com/kluctl/kluctl/pkg/types/k8s" - "github.com/kluctl/kluctl/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/deployment" + "github.com/kluctl/kluctl/v2/pkg/types" + k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/stretchr/testify/assert" "testing" ) diff --git a/pkg/deployment/utils/downscale_utils.go b/pkg/deployment/utils/downscale_utils.go index 64dd5e87d..aed46dd92 100644 --- a/pkg/deployment/utils/downscale_utils.go +++ b/pkg/deployment/utils/downscale_utils.go @@ -3,8 +3,8 @@ package utils import ( "fmt" jsonpatch "github.com/evanphx/json-patch" - "github.com/kluctl/kluctl/pkg/utils/uo" - "github.com/kluctl/kluctl/pkg/yaml" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/yaml" "k8s.io/apimachinery/pkg/runtime/schema" "regexp" "strconv" diff --git a/pkg/deployment/utils/errors_holder.go b/pkg/deployment/utils/errors_holder.go index 5a6ef2ccd..92c184dda 100644 --- a/pkg/deployment/utils/errors_holder.go +++ b/pkg/deployment/utils/errors_holder.go @@ -3,10 +3,10 @@ package utils import ( "errors" "fmt" - k8s2 "github.com/kluctl/kluctl/pkg/k8s" - "github.com/kluctl/kluctl/pkg/types" - "github.com/kluctl/kluctl/pkg/types/k8s" - "github.com/kluctl/kluctl/pkg/utils" + k8s2 "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/utils" "sync" ) diff --git a/pkg/deployment/utils/hooks_util.go b/pkg/deployment/utils/hooks_util.go index 23426ab5a..bee3b6811 100644 --- a/pkg/deployment/utils/hooks_util.go +++ b/pkg/deployment/utils/hooks_util.go @@ -2,10 +2,10 @@ package utils import ( "fmt" - "github.com/kluctl/kluctl/pkg/deployment" - "github.com/kluctl/kluctl/pkg/types/k8s" - "github.com/kluctl/kluctl/pkg/utils" - "github.com/kluctl/kluctl/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/deployment" + "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" "sort" "strconv" "strings" diff --git a/pkg/deployment/utils/remote_objects_utils.go b/pkg/deployment/utils/remote_objects_utils.go index 262a5baba..71df19fbe 100644 --- a/pkg/deployment/utils/remote_objects_utils.go +++ b/pkg/deployment/utils/remote_objects_utils.go @@ -1,10 +1,10 @@ package utils import ( - "github.com/kluctl/kluctl/pkg/k8s" - k8s2 "github.com/kluctl/kluctl/pkg/types/k8s" - "github.com/kluctl/kluctl/pkg/utils" - "github.com/kluctl/kluctl/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/k8s" + k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" log "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/runtime/schema" "sync" diff --git a/pkg/diff/diff.go b/pkg/diff/diff.go index 1ae783d7a..e490578a5 100644 --- a/pkg/diff/diff.go +++ b/pkg/diff/diff.go @@ -5,9 +5,9 @@ import ( "github.com/hexops/gotextdiff" "github.com/hexops/gotextdiff/myers" "github.com/hexops/gotextdiff/span" - "github.com/kluctl/kluctl/pkg/types" - "github.com/kluctl/kluctl/pkg/utils/uo" - "github.com/kluctl/kluctl/pkg/yaml" + "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/yaml" diff2 "github.com/r3labs/diff/v2" "log" "reflect" diff --git a/pkg/diff/managed_fields.go b/pkg/diff/managed_fields.go index 294c3bbb3..5ac3f6261 100644 --- a/pkg/diff/managed_fields.go +++ b/pkg/diff/managed_fields.go @@ -3,7 +3,7 @@ package diff import ( "bytes" "fmt" - "github.com/kluctl/kluctl/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" log "github.com/sirupsen/logrus" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "reflect" diff --git a/pkg/diff/normalize.go b/pkg/diff/normalize.go index 53a4b57f8..f2599dc97 100644 --- a/pkg/diff/normalize.go +++ b/pkg/diff/normalize.go @@ -2,8 +2,8 @@ package diff import ( "fmt" - "github.com/kluctl/kluctl/pkg/types" - "github.com/kluctl/kluctl/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" "regexp" "strconv" "strings" diff --git a/pkg/git/auth/auth_provider.go b/pkg/git/auth/auth_provider.go index 99a7ba4e5..6c8872bd6 100644 --- a/pkg/git/auth/auth_provider.go +++ b/pkg/git/auth/auth_provider.go @@ -2,7 +2,7 @@ package auth import ( "github.com/go-git/go-git/v5/plumbing/transport" - git_url "github.com/kluctl/kluctl/pkg/git/git-url" + git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" ) type GitAuthProvider interface { diff --git a/pkg/git/auth/env_auth_provider.go b/pkg/git/auth/env_auth_provider.go index 902b65212..2755ab710 100644 --- a/pkg/git/auth/env_auth_provider.go +++ b/pkg/git/auth/env_auth_provider.go @@ -4,8 +4,8 @@ import ( "github.com/go-git/go-git/v5/plumbing/transport" "github.com/go-git/go-git/v5/plumbing/transport/http" "github.com/go-git/go-git/v5/plumbing/transport/ssh" - git_url "github.com/kluctl/kluctl/pkg/git/git-url" - "github.com/kluctl/kluctl/pkg/utils" + git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" + "github.com/kluctl/kluctl/v2/pkg/utils" log "github.com/sirupsen/logrus" "strings" ) diff --git a/pkg/git/auth/git_credentials_file.go b/pkg/git/auth/git_credentials_file.go index e2a497bee..49fbe894e 100644 --- a/pkg/git/auth/git_credentials_file.go +++ b/pkg/git/auth/git_credentials_file.go @@ -4,8 +4,8 @@ import ( "bufio" "github.com/go-git/go-git/v5/plumbing/transport" "github.com/go-git/go-git/v5/plumbing/transport/http" - git_url "github.com/kluctl/kluctl/pkg/git/git-url" - "github.com/kluctl/kluctl/pkg/utils" + git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" + "github.com/kluctl/kluctl/v2/pkg/utils" log "github.com/sirupsen/logrus" giturls "github.com/whilp/git-urls" "os" diff --git a/pkg/git/auth/ssh_auth_provider.go b/pkg/git/auth/ssh_auth_provider.go index d195fe85c..6da3e0e98 100644 --- a/pkg/git/auth/ssh_auth_provider.go +++ b/pkg/git/auth/ssh_auth_provider.go @@ -4,8 +4,8 @@ import ( "fmt" "github.com/go-git/go-git/v5/plumbing/transport" "github.com/kevinburke/ssh_config" - git_url "github.com/kluctl/kluctl/pkg/git/git-url" - "github.com/kluctl/kluctl/pkg/utils" + git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" + "github.com/kluctl/kluctl/v2/pkg/utils" log "github.com/sirupsen/logrus" sshagent "github.com/xanzy/ssh-agent" "golang.org/x/crypto/ssh" diff --git a/pkg/git/auth/ssh_known_hosts.go b/pkg/git/auth/ssh_known_hosts.go index 8e6891aec..2ad01941d 100644 --- a/pkg/git/auth/ssh_known_hosts.go +++ b/pkg/git/auth/ssh_known_hosts.go @@ -2,8 +2,8 @@ package auth import ( "fmt" - "github.com/kluctl/kluctl/pkg/git/auth/goph" - "github.com/kluctl/kluctl/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/git/auth/goph" + "github.com/kluctl/kluctl/v2/pkg/utils" "golang.org/x/crypto/ssh" "net" "os" diff --git a/pkg/git/http-server/http.go b/pkg/git/http-server/http.go index 8bc76c64a..c9da65ee0 100644 --- a/pkg/git/http-server/http.go +++ b/pkg/git/http-server/http.go @@ -4,7 +4,7 @@ package http_server import ( "compress/gzip" "fmt" - process2 "github.com/kluctl/kluctl/pkg/utils/process" + process2 "github.com/kluctl/kluctl/v2/pkg/utils/process" log "github.com/sirupsen/logrus" "io" "net/http" diff --git a/pkg/git/http-server/utils.go b/pkg/git/http-server/utils.go index 5edd5b027..4b1ee02f4 100644 --- a/pkg/git/http-server/utils.go +++ b/pkg/git/http-server/utils.go @@ -2,7 +2,7 @@ package http_server import ( "fmt" - process2 "github.com/kluctl/kluctl/pkg/utils/process" + process2 "github.com/kluctl/kluctl/v2/pkg/utils/process" "io" "log" "net/http" diff --git a/pkg/git/mirrored_repo.go b/pkg/git/mirrored_repo.go index 8288c5198..82b822ee2 100644 --- a/pkg/git/mirrored_repo.go +++ b/pkg/git/mirrored_repo.go @@ -5,9 +5,9 @@ import ( "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/config" "github.com/go-git/go-git/v5/plumbing" - auth2 "github.com/kluctl/kluctl/pkg/git/auth" - git_url "github.com/kluctl/kluctl/pkg/git/git-url" - "github.com/kluctl/kluctl/pkg/utils" + auth2 "github.com/kluctl/kluctl/v2/pkg/git/auth" + git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" + "github.com/kluctl/kluctl/v2/pkg/utils" log "github.com/sirupsen/logrus" "io/ioutil" "os" diff --git a/pkg/git/poor_mans_clone.go b/pkg/git/poor_mans_clone.go index 53c6d41e4..851b6ae04 100644 --- a/pkg/git/poor_mans_clone.go +++ b/pkg/git/poor_mans_clone.go @@ -5,7 +5,7 @@ import ( "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/config" "github.com/go-git/go-git/v5/plumbing" - "github.com/kluctl/kluctl/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils" "io/ioutil" "os" "path/filepath" diff --git a/pkg/git/utils.go b/pkg/git/utils.go index 11e9f8cf2..d3826b03d 100644 --- a/pkg/git/utils.go +++ b/pkg/git/utils.go @@ -3,7 +3,7 @@ package git import ( "fmt" "github.com/go-git/go-git/v5" - "github.com/kluctl/kluctl/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils" "path/filepath" ) diff --git a/pkg/jinja2/jinja2.go b/pkg/jinja2/jinja2.go index eae023375..431619904 100644 --- a/pkg/jinja2/jinja2.go +++ b/pkg/jinja2/jinja2.go @@ -3,8 +3,8 @@ package jinja2 import ( "fmt" "github.com/gobwas/glob" - "github.com/kluctl/kluctl/pkg/utils" - "github.com/kluctl/kluctl/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" "io/fs" "io/ioutil" "os" diff --git a/pkg/jinja2/jinja2_renderer.go b/pkg/jinja2/jinja2_renderer.go index 7db691279..e3299137f 100644 --- a/pkg/jinja2/jinja2_renderer.go +++ b/pkg/jinja2/jinja2_renderer.go @@ -5,8 +5,8 @@ import ( "bytes" "encoding/json" "fmt" - "github.com/kluctl/kluctl/pkg/python" - "github.com/kluctl/kluctl/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/python" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" "io" "io/ioutil" "os" diff --git a/pkg/jinja2/source.go b/pkg/jinja2/source.go index 7bae0a194..78321257f 100644 --- a/pkg/jinja2/source.go +++ b/pkg/jinja2/source.go @@ -2,8 +2,8 @@ package jinja2 import ( "embed" - "github.com/kluctl/kluctl/pkg/utils" - "github.com/kluctl/kluctl/pkg/utils/embed_util" + "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/embed_util" log "github.com/sirupsen/logrus" "path/filepath" ) diff --git a/pkg/jinja2/vars.go b/pkg/jinja2/vars.go index 9d81e61e3..b130cafb3 100644 --- a/pkg/jinja2/vars.go +++ b/pkg/jinja2/vars.go @@ -2,11 +2,11 @@ package jinja2 import ( "fmt" - "github.com/kluctl/kluctl/pkg/k8s" - "github.com/kluctl/kluctl/pkg/types" - k8s2 "github.com/kluctl/kluctl/pkg/types/k8s" - "github.com/kluctl/kluctl/pkg/utils/uo" - "github.com/kluctl/kluctl/pkg/yaml" + "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/types" + k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/yaml" ) type VarsCtx struct { diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index bb28c94d7..fec67980f 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -5,10 +5,10 @@ import ( "encoding/json" "fmt" goversion "github.com/hashicorp/go-version" - "github.com/kluctl/kluctl/pkg/types/k8s" - "github.com/kluctl/kluctl/pkg/utils" - "github.com/kluctl/kluctl/pkg/utils/uo" - "github.com/kluctl/kluctl/pkg/yaml" + "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/yaml" log "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" diff --git a/pkg/k8s/utils.go b/pkg/k8s/utils.go index f70e14fa2..bd0c74dcd 100644 --- a/pkg/k8s/utils.go +++ b/pkg/k8s/utils.go @@ -1,7 +1,7 @@ package k8s import ( - "github.com/kluctl/kluctl/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" "k8s.io/apimachinery/pkg/runtime/schema" ) diff --git a/pkg/kluctl_project/git.go b/pkg/kluctl_project/git.go index e54e25e95..e7cdcf650 100644 --- a/pkg/kluctl_project/git.go +++ b/pkg/kluctl_project/git.go @@ -2,10 +2,10 @@ package kluctl_project import ( "fmt" - "github.com/kluctl/kluctl/pkg/git" - git_url "github.com/kluctl/kluctl/pkg/git/git-url" - types2 "github.com/kluctl/kluctl/pkg/types" - "github.com/kluctl/kluctl/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/git" + git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" + types2 "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/utils" log "github.com/sirupsen/logrus" "os" "path/filepath" diff --git a/pkg/kluctl_project/load.go b/pkg/kluctl_project/load.go index 238932081..82e3eb165 100644 --- a/pkg/kluctl_project/load.go +++ b/pkg/kluctl_project/load.go @@ -4,11 +4,11 @@ import ( "archive/tar" "compress/gzip" "fmt" - "github.com/kluctl/kluctl/pkg/git" - "github.com/kluctl/kluctl/pkg/jinja2" - types2 "github.com/kluctl/kluctl/pkg/types" - "github.com/kluctl/kluctl/pkg/utils" - "github.com/kluctl/kluctl/pkg/yaml" + "github.com/kluctl/kluctl/v2/pkg/git" + "github.com/kluctl/kluctl/v2/pkg/jinja2" + types2 "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/yaml" log "github.com/sirupsen/logrus" "io/ioutil" "os" diff --git a/pkg/kluctl_project/load_targets.go b/pkg/kluctl_project/load_targets.go index 5dfdd8f3f..6cfcd76c2 100644 --- a/pkg/kluctl_project/load_targets.go +++ b/pkg/kluctl_project/load_targets.go @@ -2,12 +2,12 @@ package kluctl_project import ( "fmt" - git_url "github.com/kluctl/kluctl/pkg/git/git-url" - "github.com/kluctl/kluctl/pkg/jinja2" - "github.com/kluctl/kluctl/pkg/types" - "github.com/kluctl/kluctl/pkg/utils" - "github.com/kluctl/kluctl/pkg/utils/uo" - "github.com/kluctl/kluctl/pkg/yaml" + git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" + "github.com/kluctl/kluctl/v2/pkg/jinja2" + "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/yaml" log "github.com/sirupsen/logrus" "path/filepath" "reflect" diff --git a/pkg/kluctl_project/project.go b/pkg/kluctl_project/project.go index 8f44afee7..552240a22 100644 --- a/pkg/kluctl_project/project.go +++ b/pkg/kluctl_project/project.go @@ -2,11 +2,11 @@ package kluctl_project import ( "fmt" - "github.com/kluctl/kluctl/pkg/git" - auth2 "github.com/kluctl/kluctl/pkg/git/auth" - git_url "github.com/kluctl/kluctl/pkg/git/git-url" - "github.com/kluctl/kluctl/pkg/jinja2" - "github.com/kluctl/kluctl/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/git" + auth2 "github.com/kluctl/kluctl/v2/pkg/git/auth" + git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" + "github.com/kluctl/kluctl/v2/pkg/jinja2" + "github.com/kluctl/kluctl/v2/pkg/types" "regexp" ) diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index d863fe8c2..257967bad 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -2,12 +2,12 @@ package kluctl_project import ( "fmt" - "github.com/kluctl/kluctl/pkg/deployment" - "github.com/kluctl/kluctl/pkg/jinja2" - "github.com/kluctl/kluctl/pkg/k8s" - "github.com/kluctl/kluctl/pkg/types" - "github.com/kluctl/kluctl/pkg/utils" - "github.com/kluctl/kluctl/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/deployment" + "github.com/kluctl/kluctl/v2/pkg/jinja2" + "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" "path/filepath" ) diff --git a/pkg/python/embed.go b/pkg/python/embed.go index c9310de48..b4dd1e637 100644 --- a/pkg/python/embed.go +++ b/pkg/python/embed.go @@ -2,8 +2,8 @@ package python import ( "fmt" - "github.com/kluctl/kluctl/pkg/utils" - "github.com/kluctl/kluctl/pkg/utils/embed_util" + "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/embed_util" "log" "path/filepath" "runtime" diff --git a/pkg/registries/registries.go b/pkg/registries/registries.go index e886010a5..86ccb63e4 100644 --- a/pkg/registries/registries.go +++ b/pkg/registries/registries.go @@ -10,8 +10,8 @@ import ( "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/google/go-containerregistry/pkg/v1/remote/transport" - "github.com/kluctl/kluctl/pkg/utils" - "github.com/kluctl/kluctl/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" log "github.com/sirupsen/logrus" "io/ioutil" "net/http" diff --git a/pkg/seal/bootstrap.go b/pkg/seal/bootstrap.go index ca1807a16..02865c4e9 100644 --- a/pkg/seal/bootstrap.go +++ b/pkg/seal/bootstrap.go @@ -6,9 +6,9 @@ import ( "encoding/base64" "encoding/pem" "github.com/bitnami-labs/sealed-secrets/pkg/crypto" - "github.com/kluctl/kluctl/pkg/k8s" - k8s2 "github.com/kluctl/kluctl/pkg/types/k8s" - "github.com/kluctl/kluctl/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/k8s" + k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" log "github.com/sirupsen/logrus" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime/schema" diff --git a/pkg/seal/fetch_cert.go b/pkg/seal/fetch_cert.go index ef341ba4a..4e5e27d5a 100644 --- a/pkg/seal/fetch_cert.go +++ b/pkg/seal/fetch_cert.go @@ -5,7 +5,7 @@ import ( "crypto/rsa" "errors" "fmt" - "github.com/kluctl/kluctl/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/k8s" log "github.com/sirupsen/logrus" "io/ioutil" v12 "k8s.io/api/core/v1" diff --git a/pkg/seal/sealer.go b/pkg/seal/sealer.go index 9c715006a..75f8bb187 100644 --- a/pkg/seal/sealer.go +++ b/pkg/seal/sealer.go @@ -7,12 +7,12 @@ import ( "encoding/hex" "fmt" "github.com/bitnami-labs/sealed-secrets/pkg/crypto" - "github.com/kluctl/kluctl/pkg/k8s" - "github.com/kluctl/kluctl/pkg/types" - k8s2 "github.com/kluctl/kluctl/pkg/types/k8s" - "github.com/kluctl/kluctl/pkg/utils" - "github.com/kluctl/kluctl/pkg/utils/uo" - "github.com/kluctl/kluctl/pkg/yaml" + "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/types" + k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/yaml" log "github.com/sirupsen/logrus" "golang.org/x/crypto/scrypt" "k8s.io/apimachinery/pkg/runtime/schema" diff --git a/pkg/seal/secrets_loader.go b/pkg/seal/secrets_loader.go index d8eccb47a..ca4a0ac21 100644 --- a/pkg/seal/secrets_loader.go +++ b/pkg/seal/secrets_loader.go @@ -2,11 +2,11 @@ package seal import ( "fmt" - "github.com/kluctl/kluctl/pkg/kluctl_project" - "github.com/kluctl/kluctl/pkg/types" - "github.com/kluctl/kluctl/pkg/utils" - "github.com/kluctl/kluctl/pkg/utils/aws" - "github.com/kluctl/kluctl/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/kluctl_project" + "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/aws" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" "os" "path/filepath" "strings" diff --git a/pkg/types/cluster_config.go b/pkg/types/cluster_config.go index 6b1f33b3b..0c22be6b1 100644 --- a/pkg/types/cluster_config.go +++ b/pkg/types/cluster_config.go @@ -2,9 +2,9 @@ package types import ( "fmt" - "github.com/kluctl/kluctl/pkg/utils" - "github.com/kluctl/kluctl/pkg/utils/uo" - "github.com/kluctl/kluctl/pkg/yaml" + "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/yaml" "path/filepath" ) diff --git a/pkg/types/command_result.go b/pkg/types/command_result.go index ed3c97f21..5142d73c0 100644 --- a/pkg/types/command_result.go +++ b/pkg/types/command_result.go @@ -1,8 +1,8 @@ package types import ( - "github.com/kluctl/kluctl/pkg/types/k8s" - "github.com/kluctl/kluctl/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" ) type Change struct { diff --git a/pkg/types/deployment.go b/pkg/types/deployment.go index 08fc2fa3c..e2069d00f 100644 --- a/pkg/types/deployment.go +++ b/pkg/types/deployment.go @@ -2,8 +2,8 @@ package types import ( "github.com/go-playground/validator/v10" - "github.com/kluctl/kluctl/pkg/utils/uo" - "github.com/kluctl/kluctl/pkg/yaml" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/yaml" ) type DeploymentItemConfig struct { diff --git a/pkg/types/git_project.go b/pkg/types/git_project.go index db60413da..6d6cbc4a3 100644 --- a/pkg/types/git_project.go +++ b/pkg/types/git_project.go @@ -2,8 +2,8 @@ package types import ( "github.com/go-playground/validator/v10" - git_url "github.com/kluctl/kluctl/pkg/git/git-url" - "github.com/kluctl/kluctl/pkg/yaml" + git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" + "github.com/kluctl/kluctl/v2/pkg/yaml" "strings" ) diff --git a/pkg/types/kluctl_project.go b/pkg/types/kluctl_project.go index 917f1570b..b0ea0257f 100644 --- a/pkg/types/kluctl_project.go +++ b/pkg/types/kluctl_project.go @@ -1,8 +1,8 @@ package types import ( - "github.com/kluctl/kluctl/pkg/utils/uo" - "github.com/kluctl/kluctl/pkg/yaml" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/yaml" ) type DynamicArg struct { diff --git a/pkg/types/secrets_source.go b/pkg/types/secrets_source.go index 5490926f0..c29f63615 100644 --- a/pkg/types/secrets_source.go +++ b/pkg/types/secrets_source.go @@ -2,7 +2,7 @@ package types import ( "github.com/go-playground/validator/v10" - "github.com/kluctl/kluctl/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" ) type SecretSourceAwsSecretsManager struct { diff --git a/pkg/types/target_config.go b/pkg/types/target_config.go index 1b439f532..4f27a0cf6 100644 --- a/pkg/types/target_config.go +++ b/pkg/types/target_config.go @@ -1,8 +1,8 @@ package types import ( - "github.com/kluctl/kluctl/pkg/types/k8s" - "github.com/kluctl/kluctl/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" ) type FixedImage struct { diff --git a/pkg/utils/embed_util/extract.go b/pkg/utils/embed_util/extract.go index 21b5deda2..dc38f75f4 100644 --- a/pkg/utils/embed_util/extract.go +++ b/pkg/utils/embed_util/extract.go @@ -3,7 +3,7 @@ package embed_util import ( "fmt" "github.com/gofrs/flock" - "github.com/kluctl/kluctl/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils" "io" "io/fs" "io/ioutil" diff --git a/pkg/utils/embed_util/packer/main.go b/pkg/utils/embed_util/packer/main.go index 60bf426cc..f4d4ba5e3 100644 --- a/pkg/utils/embed_util/packer/main.go +++ b/pkg/utils/embed_util/packer/main.go @@ -8,7 +8,7 @@ import ( "encoding/hex" "fmt" "github.com/gobwas/glob" - "github.com/kluctl/kluctl/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils" log "github.com/sirupsen/logrus" "io/fs" "io/ioutil" diff --git a/pkg/utils/uo/k8s_fields.go b/pkg/utils/uo/k8s_fields.go index 68d07dc61..be195b0f1 100644 --- a/pkg/utils/uo/k8s_fields.go +++ b/pkg/utils/uo/k8s_fields.go @@ -1,7 +1,7 @@ package uo import ( - "github.com/kluctl/kluctl/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/types/k8s" log "github.com/sirupsen/logrus" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" diff --git a/pkg/utils/uo/unstructured.go b/pkg/utils/uo/unstructured.go index 1dd8c4adf..118b76df1 100644 --- a/pkg/utils/uo/unstructured.go +++ b/pkg/utils/uo/unstructured.go @@ -2,7 +2,7 @@ package uo import ( "fmt" - "github.com/kluctl/kluctl/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils" log "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) diff --git a/pkg/utils/uo/uo.go b/pkg/utils/uo/uo.go index cae1b10be..ffb513110 100644 --- a/pkg/utils/uo/uo.go +++ b/pkg/utils/uo/uo.go @@ -3,7 +3,7 @@ package uo import ( "fmt" "github.com/jinzhu/copier" - "github.com/kluctl/kluctl/pkg/yaml" + "github.com/kluctl/kluctl/v2/pkg/yaml" log "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "reflect" diff --git a/pkg/utils/versions/latest_version.go b/pkg/utils/versions/latest_version.go index 654928f6c..7cd524a9c 100644 --- a/pkg/utils/versions/latest_version.go +++ b/pkg/utils/versions/latest_version.go @@ -2,7 +2,7 @@ package versions import ( "fmt" - "github.com/kluctl/kluctl/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils" "log" "regexp" "strconv" diff --git a/pkg/utils/versions/latest_version_parse.go b/pkg/utils/versions/latest_version_parse.go index 220bb08f0..6fe06ed23 100644 --- a/pkg/utils/versions/latest_version_parse.go +++ b/pkg/utils/versions/latest_version_parse.go @@ -2,7 +2,7 @@ package versions import ( "fmt" - scanner "github.com/kluctl/kluctl/pkg/utils/python_scanner" + scanner "github.com/kluctl/kluctl/v2/pkg/utils/python_scanner" log "github.com/sirupsen/logrus" "strconv" "strings" diff --git a/pkg/utils/versions/looseversion.go b/pkg/utils/versions/looseversion.go index c0fb8c169..11101ea03 100644 --- a/pkg/utils/versions/looseversion.go +++ b/pkg/utils/versions/looseversion.go @@ -1,7 +1,7 @@ package versions import ( - "github.com/kluctl/kluctl/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils" "regexp" "sort" "strconv" diff --git a/pkg/validation/validation.go b/pkg/validation/validation.go index de1e18d4e..52584cedf 100644 --- a/pkg/validation/validation.go +++ b/pkg/validation/validation.go @@ -2,9 +2,9 @@ package validation import ( "fmt" - "github.com/kluctl/kluctl/pkg/k8s" - "github.com/kluctl/kluctl/pkg/types" - "github.com/kluctl/kluctl/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime/schema" "regexp" diff --git a/pkg/yaml/yaml.go b/pkg/yaml/yaml.go index adc37aac6..271c761fe 100644 --- a/pkg/yaml/yaml.go +++ b/pkg/yaml/yaml.go @@ -6,7 +6,7 @@ import ( "errors" "fmt" "github.com/goccy/go-yaml" - "github.com/kluctl/kluctl/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils" yaml3 "gopkg.in/yaml.v3" "io" "os" From 6e6f65928562079cd2d8d16acd030654bf2ed7cd Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 20 Apr 2022 10:02:41 +0200 Subject: [PATCH 0665/2916] fix: Properly use --output-format for deploy and poke-images --- cmd/kluctl/commands/cmd_deploy.go | 4 ++-- cmd/kluctl/commands/cmd_poke_images.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/kluctl/commands/cmd_deploy.go b/cmd/kluctl/commands/cmd_deploy.go index 15dcea2f2..e234d8b49 100644 --- a/cmd/kluctl/commands/cmd_deploy.go +++ b/cmd/kluctl/commands/cmd_deploy.go @@ -20,7 +20,7 @@ type deployCmd struct { args.ReplaceOnErrorFlags args.AbortOnErrorFlags args.HookFlags - args.OutputFlags + args.OutputFormatFlags args.RenderOutputDirFlags NoWait bool `group:"misc" help:"Don't wait for objects readiness'"` @@ -66,7 +66,7 @@ func (cmd *deployCmd) runCmdDeploy(ctx *commandCtx) error { if err != nil { return err } - err = outputCommandResult(cmd.Output, result) + err = outputCommandResult(cmd.OutputFormat, result) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_poke_images.go b/cmd/kluctl/commands/cmd_poke_images.go index a6898ea11..33c62ced3 100644 --- a/cmd/kluctl/commands/cmd_poke_images.go +++ b/cmd/kluctl/commands/cmd_poke_images.go @@ -15,7 +15,7 @@ type pokeImagesCmd struct { args.InclusionFlags args.YesFlags args.DryRunFlags - args.OutputFlags + args.OutputFormatFlags args.RenderOutputDirFlags } @@ -48,7 +48,7 @@ func (cmd *pokeImagesCmd) Run() error { if err != nil { return err } - err = outputCommandResult(cmd.Output, result) + err = outputCommandResult(cmd.OutputFormat, result) if err != nil { return err } From b2ad31c19c0fc74f7522d56bb5c9d58fd2d99122 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 20 Apr 2022 10:07:57 +0200 Subject: [PATCH 0666/2916] fix: Add missing --render-output-dir flag to delete/list-images and prune --- cmd/kluctl/commands/cmd_delete.go | 14 ++++++++------ cmd/kluctl/commands/cmd_list_images.go | 12 +++++++----- cmd/kluctl/commands/cmd_prune.go | 14 ++++++++------ 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/cmd/kluctl/commands/cmd_delete.go b/cmd/kluctl/commands/cmd_delete.go index c078332de..ebb02bfea 100644 --- a/cmd/kluctl/commands/cmd_delete.go +++ b/cmd/kluctl/commands/cmd_delete.go @@ -22,6 +22,7 @@ type deleteCmd struct { args.YesFlags args.DryRunFlags args.OutputFormatFlags + args.RenderOutputDirFlags DeleteByLabel []string `group:"misc" short:"l" help:"Override the labels used to find objects for deletion."` } @@ -36,12 +37,13 @@ take the local target/state into account!` func (cmd *deleteCmd) Run() error { ptArgs := projectTargetCommandArgs{ - projectFlags: cmd.ProjectFlags, - targetFlags: cmd.TargetFlags, - argsFlags: cmd.ArgsFlags, - imageFlags: cmd.ImageFlags, - inclusionFlags: cmd.InclusionFlags, - dryRunArgs: &cmd.DryRunFlags, + projectFlags: cmd.ProjectFlags, + targetFlags: cmd.TargetFlags, + argsFlags: cmd.ArgsFlags, + imageFlags: cmd.ImageFlags, + inclusionFlags: cmd.InclusionFlags, + dryRunArgs: &cmd.DryRunFlags, + renderOutputDirFlags: cmd.RenderOutputDirFlags, } return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { cmd2 := commands.NewDeleteCommand(ctx.targetCtx.DeploymentCollection) diff --git a/cmd/kluctl/commands/cmd_list_images.go b/cmd/kluctl/commands/cmd_list_images.go index 80bd70382..cfd9ef56f 100644 --- a/cmd/kluctl/commands/cmd_list_images.go +++ b/cmd/kluctl/commands/cmd_list_images.go @@ -12,6 +12,7 @@ type listImagesCmd struct { args.ImageFlags args.InclusionFlags args.OutputFlags + args.RenderOutputDirFlags Simple bool `group:"misc" help:"Output a simplified version of the images list"` } @@ -25,11 +26,12 @@ as described in for the deploy command.` func (cmd *listImagesCmd) Run() error { ptArgs := projectTargetCommandArgs{ - projectFlags: cmd.ProjectFlags, - targetFlags: cmd.TargetFlags, - argsFlags: cmd.ArgsFlags, - imageFlags: cmd.ImageFlags, - inclusionFlags: cmd.InclusionFlags, + projectFlags: cmd.ProjectFlags, + targetFlags: cmd.TargetFlags, + argsFlags: cmd.ArgsFlags, + imageFlags: cmd.ImageFlags, + inclusionFlags: cmd.InclusionFlags, + renderOutputDirFlags: cmd.RenderOutputDirFlags, } return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { result := types.FixedImagesConfig{ diff --git a/cmd/kluctl/commands/cmd_prune.go b/cmd/kluctl/commands/cmd_prune.go index 2ef240e88..e24578871 100644 --- a/cmd/kluctl/commands/cmd_prune.go +++ b/cmd/kluctl/commands/cmd_prune.go @@ -15,6 +15,7 @@ type pruneCmd struct { args.YesFlags args.DryRunFlags args.OutputFormatFlags + args.RenderOutputDirFlags } func (cmd *pruneCmd) Help() string { @@ -27,12 +28,13 @@ func (cmd *pruneCmd) Help() string { func (cmd *pruneCmd) Run() error { ptArgs := projectTargetCommandArgs{ - projectFlags: cmd.ProjectFlags, - targetFlags: cmd.TargetFlags, - argsFlags: cmd.ArgsFlags, - imageFlags: cmd.ImageFlags, - inclusionFlags: cmd.InclusionFlags, - dryRunArgs: &cmd.DryRunFlags, + projectFlags: cmd.ProjectFlags, + targetFlags: cmd.TargetFlags, + argsFlags: cmd.ArgsFlags, + imageFlags: cmd.ImageFlags, + inclusionFlags: cmd.InclusionFlags, + dryRunArgs: &cmd.DryRunFlags, + renderOutputDirFlags: cmd.RenderOutputDirFlags, } return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { return cmd.runCmdPrune(ctx) From ffc0f8295530928701464b753fdda7dd311558e9 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 20 Apr 2022 17:20:46 +0200 Subject: [PATCH 0667/2916] chore: Update kluctl logo --- README.md | 2 +- logo/kluctl.png | Bin 12230 -> 0 bytes logo/kluctl.svg | 1 + 3 files changed, 2 insertions(+), 1 deletion(-) delete mode 100644 logo/kluctl.png create mode 100644 logo/kluctl.svg diff --git a/README.md b/README.md index 627ac52d1..6f71b363c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # kluctl -![kluctl](logo/kluctl.png) +kluctl kluctl is the missing glue that puts together your (and any third-party) deployments into one large declarative Kubernetes deployment, while making it fully manageable (deploy, diff, prune, delete, ...) via one unified command diff --git a/logo/kluctl.png b/logo/kluctl.png deleted file mode 100644 index 8e3ad6e30a2d15febde0b1663c11a341f312a9e2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12230 zcmcgygMuyw^t`ww<$`bMEJk>yAiu)rVxnOvDffgzWJns3ru0#SLC(QsRU4Rz80l_=D^E z;ITF#fFGen1b9#6^hnS%*{*Oh(#N7x!WORc)b<=XRbn|@f@&e-N>B(>7VCQQ7 z-020sql;DYrZf}yJ;-C|eQmFljVW)pk)?6jZM#FZ9*d-})E2IfpTg+Hpo963qH_7> zjrVmXuQ}Pkz7o*!=u{XKyd2FBr+;Z@H(Gbo*Ct@@ zplJ-eqHR|tQdM(?i5d=m8ag%}=x(na_%s?9i%n3+LC9CWZtzP^c4^R9O{rF1oZPoS|N)7u@{@|iOyJ>V1t zmc=nCs~;&6uFIV8u7w>h=^bW14VROH;=qeFldIG2^!l%8@WQxw(1<$Xj5G@k=I%N6 zlT9DU;j}Ctw-;qOQRHwOD>i8kxH$ES7IoQb*!v;1)gZJz#^osHFj?cg(CHPOz$sBP z}qk#TM(>bh4c6*o5^%?&^GRK#t|3jPK$4czLkcwAaF0vFV7Jnok%&68n~1YVn(j zzM#SGvA_!Fc-cT8Pa7$Ji;@ucfeA-IV8o?YMA!o>G&o9}=DxiqxrY4=gH}0{D*N%W z7kxp%LzLUL`fpj?*^@34bchbPr^R2+Evze>QYscGd6msX=ZG%hX`2}bw$Q9Wu z`R#odn*qyQv=v61-r_hk>~vmtY@q7?fd={2CDe@;xnZwR`V)x;RbBPeKYQwQFyhfG zGOBfXvFcp-wA8EvPB$mFS?9j?v(WI`sWZ$}>ToCUe5Z>AEG^N+!zee&R78aC^{z{4 zg_ppzu8Z0;cuk@$Z;VID9E0;tF5;)$y$TdE`-2J;TyR3g%qwF8U!IUx2)3suAu_E=wH#73m%+Xy7SXGWQ zP5x(xzuqeQfZYRIC}R5U6%h^l6T*PzifS|{F7`&p(7-vG@|^KMnj(0TB>18?o~t#f zB=az_`Fu$r@T6~0Zd3ie&~w9Thp9SuyM~7f!VUM-HMAR!=c5Fgj#*254*rZ5=!G{m zm7qac^Vc8%n384|>MlOCMUu#3r5E$iyI17$5^L z+j_FwNCX5pE!z#@aWOTrO7Rv=8rSkI~W}O&Pb+ev(H7mTDyVr zC!GoIy8ju;)5_`e6^6E57t0=wIi3xrkOj-^^0InP_Yejl$!;8~DdNua?H|cJ2fwA3 zG8E+$h1n`DJGq3}@62~ZT@#Q!WJ8XZTMeYiT(3Fb?GtD|cge^_gOEYUvj5{f^N?a+ zI}DtN*8WP`Gx+6175K+@zT%6@PBivGWzVvEdRv>7s7mj&)2K<`tEte{K?4utRmHY1 zEQS}<2Y5`P37EZRg9(@k1C3l1ZP$OHJvrG`095!cJ~zBu&&2;=RL=xUIe}Nd%;NDX z8+&N0>0O4PzovB59ITHqd>)U5>|b?jMfIC?Yy7joX`(%6Tl36ht?SBu3tMYEY!+Qsa<4i=mrXkRfPEmR{c{&ZJ(6|I;m z$@HhEz?E$LuH@Nwuf?F{4I@Sv$Iev9!MRwLn>5~j{zwEYik^>n^DScpCIYkoKaO_h z_;TA}Q5NZ$OtvnayA*-PZ7i4D;e%s+ThS}&ftM#M=@bb#gkq0gyL2*Ir8svowkGj7 z{}JB5Se&V!Tx07KhF));0p1Uvn~7F~9`dxrv^4~eBq$Ao;NnEbW3Wf)jUqJyf(O#K zS?^SyW6WohtK~A;{7wXmPbN$Yi}T$=(9v2r8KPs@!CG)QOTh75I19Oda{>n2Y6aBD zZs4b%zh~ie>B%ZYgKz&L&F4>EPMQyK4caDwVXj6C8Hk%xGE3a4{A-f0n~dq9jq`Ax zqMCVlF2xpMwCw3>RM&zGmaJXW(?^4`euqk9 z-aG@573+&wWX^LE>*=LuD3iZKM)98i1Y8k)S^d={uJK(Me=3_26*;aGl z?3y43A+H}rM*N4*eab8`T;hfr%2rOqP8M^VZt!+pJ?mMRXui6@_-u|J1WW33q@xtR zm{@VPF*x?jWBf(pGY>{}BQ$PYIFlvt$~g_Ih)uZ-tF+1(mq~|yO=s&1fRbP~Em8h8D)W`at0nKt-SadLkX2tQDFQNv5yHCycmcN7hHtr9*R_cYv z70`?cs&`U&OSqCd!UX@a6TCIj?9XUG15;?9b=8F{9eyKO8qrddIT+D8xp)BWi3+xT z#m$8VRuMWB7B@+{6)@ysz_tGdS~mBl6?s$IYkRgeG_#n86Ai#GV)6d!W9&w73e>OFHG)zN!KSKo__|G4f^KjlE-|I8QzG*h4zAk}u$b{XiC|ARru3+mWs zWdjU1)?(9jLC9j`c9AumaJ`i+oGP7;T{J z_CJpu1lS(a+g7<|OvK*lG`-&$+}$W##$kxSi+c3>dhY2N8d-8_PVN}Mwsw~C)Z)-& z0}!};G>EP3(RecrBx;4`I`}}MKi~WOi)D90N9HUt+cdHi-!h5V&aU-h5StU89-o8; z<7wS82CLB=v?swv8fZ{m9)z;-L=FII^z*BJX7Lr;EP-vd%Bg)lzyRwyhP~d|UP?3x zI9W>EVWy*E69kw3^N%$t4Ddce??)xJ<3B#K-Xj_VcsZJ!lm74{^Zd)h({G&CsW+~h z!F~|Bd!V6sb+Q=i%=yJHr1!nkJ(Q!bf{fQFht0`d*sVQt+9Sj z9hs5hIqkiY>d9Q*rjeU(0z!3dEj&=Yv?w}D=@c`S-gcpBl5B)!ur@W17XhUd(=a5 z3!Mif6Ep?#b|9<>1K zNmu&bJJHg+-FKpK#TFha8(^@ywz%xg3YP=njoPJr6P@${<)ZLQO~Hy0!Y(Dgp#V!r z`pd9E1UPY@LX*vDAG>`^)62tZj4_k&SHkLJtZIzC=3ZYj_PTvO{J!im$22$5fglFOfDUIpZ6y{pFxE+ z5;410flf?#bYC^4^ODx1+4tH{JV!N&rFbn>3AUOMX`lU7kc$$8zMD*DYo0bQoD^6q z>uDC#ZxPmAyd8nSV0LCnFoLtj!2HC~2nb7%Qp#MCMo(43ANamgO4174Tbw}p# zmwB|z;nUT#NQm@)@xWZ#Um~Ha(f&r~+@!e85w@iS^Zu6zuQoGhzB;>*(XKWly0>2g z9gi4{=D>E^H{n1)TiE2_`TvAQ>d)P~fsTKsqZrBwpE_n`6El&+aHLb~^>h>dY#9va z-0Oc|Y?8bDGuBHgVp^O{0*7nO;&r_q=kG<7Ol^)5e_rRr%jzi)uwY?wxh@u#ixpQ) z$UPlUKnJ?mH|G$t$}xpavPjW;d$jg*R1O2xPOrJgZoz!F`p}6@vGGlN_M018rY?^k z@h6d2#3#H*gX(nW8=0{J3_oEPgV|WE8s9=zwyS_eV{r-c+Rq<3EhrwJ?9n)=^|Zz& zZ!T%ta?V{&cKq2A(g1m&)h)>S?@FuaDRTR1Bi*0FIKW!$^wcOyUK+w+jsofibyjL7 zEb7L_&!%Q5wDrcuCN|_16tv=oi7Ki+-MpmdX>$fR5gu37d24jPr-?V?bDyPS=0h<8We)S#C+oA!nKJ`-9Y|^IC8&Pgh|e| zHTY|0LrC~*ZtZ3>?A&KEdO(M#r4X%vI}&WdveTN&IUggxZ<46qp2h;B5)y*5T9&W| zUbNpP0`I)wtlURkX9YJK3Ncxtjs|q;(mYURF?J0Q8Z@inQUVo0PtT*-E}_4ag5q2n z5wtegOdi>}vUh@wg()Su*3-S1s46R)HiiLPE^c;sDRVgCIWtw3U8te~^0H*d-xG98 z!)ncEMdex=vR4fi6$Q!~z^rS_>kTzT9j9xDEw$qL3_Ui=#%W)i9m3&h%&_rN^MSM` zSy*0fZXRk&FaGy$|Fd-u*m!eu?Kp~d>yAS;0<93BZUuNW3t{`&=#3`rT^9MrF#gOQ z__&Ku`PZkiuFL&NLROuQewcu`6CDWoUqVHYi2IKw%ej>=xAlB)i{{;fp zcl`7DYt6?U0s&aOpa65SyM#RRdGKDyaaVivaATs{afbQa_kEyA+2!Ez&O%o-3(KTE zZ=c31sRcK2ZzUDonnWSeGXAGEjmKsx3CWbcYe5)lReI+szdUa6?3*~F2k|+iE9jg1 zk+N-N97a;hc=3<-vdsQ1n^1%vbnMJ=- zfmt;2fYkb{j<&XTjgcAqq&4p8nSaw|Kt^6(v7-6-SY(frXM<6>m)XzNvx zZj@lTLE$k+q3(ITDn}8`6~*d-WU2uf!`u0v_M%Lqwk@WEYaI)4LqOf&9X8`n&CzvN zNqi9i{zSq{#Vk&>OwN`sDuz{l7a}q5-zO(05k2Kaq3B6W1+%|=dSXVdR*3H>yMtOg zIK2p#-xeLW7G{m;$@ntbdy5@Anbg9z(o0608%Bf+`Ov%KS|7X+Ex>t~pxWpt6qxpGpU0hvV3w3WvNr{O` zUGMJh?(gr0yittnvBo9EzgNC>6lJ9npTSN~jg=!rNs0HaaoUOark${Dr7FS4CmS37 z{*f%XAYB2)=CIApO^0+fT<#CVUI(L~w&Z{d$^n4Lcwsr!HyYQ=1T4g1wF5DJw*B7x zpfAL|EoJbI(Og*a#{6LSuTrFUQDzIe*1eCjh-w7BV7D)U-gK!K_`5=EZl0q}KCZ~4 zRfJl>s5L2mi}lEwmL?<%X?iy~cw{7=Hj0g5keGOPZ*MR7+|QX*cwAEs04J%c(cnr08k4T<(56^03k^1@lVAZji0rXXg5Lp1#*S1gmn#{e&Jb|w- z*1K^UVZr_-Cnu*1g-(&zb?kne?+5Z5)^=+fc-UB_E6Yy&iCr{=xK^6vV`Em; z^;2e+JzEr2_Cu)lH&2{*YMi~ji<2&Jd~kAsz>Dcx*z;}-YRwq^;}(P_?ZWe-v4S+{!X7dr8+uYBr`-fA)XfuS zx|j9hZiL`9?hoR$ZmSoIPu?S~@1CoVU+=N~-dpyxeePN))*vdt>qe~KG_W(ba(!NR zgTfXfjwc^ul6fQ{|LxA<<8x!*QpKw^LL5X++#FvJPesU^{uh0xYYr5*nFrnnZaPJX zdF&htZ(pEG;wvPVo-nGbxFd=VP`;rs0G==KXWQ7Zwoi z4~pic5YgnWJF%o4P<99`Vry$q#8UEj?tKAPQ9feO31xWX9U&zvuB)(bS>_;@d?MqGg6qqO&Y zduzO7=6gOHY#ncJ9Ub#K>Y78pe*NOSG&Ii=|CFzZm6vB$D9Q%29~!njINf(^*&0Lf z(#zY6;HIcHUkx&nv9c=hyRiT1l9N3cnKuKaMNo;#k;Ch^eA~H2z^=m0|IRW8M@jWD zO|CR^>3=Tb628L|Yk;!PR~0Cq>+6kW+O^qMD&j0I!$r(ef0%Z6Lug?I-@75}9?t!h z)p}q3U6bM?ti5{rlpo(q4$et$I-0gC!qU_tAt51#wkkEVfM>~lltmQ4Pf4aYgqz>G z*%4!L*F_Qrv&Cwtc18L%lqKWXSQ@2Lu0Umdg5GN8;zawpN#0UN_qg1LAAP$Wtxo) z)=@iN4aUwB<3x~)i_}zB`^be{L{aB^GH&;OO1Uvgv;%HLsp|_=e2=Bd~l!b=$gux z*&zg}k!p^?OJTTH6vAHKEN}`6MG8I-8U6Gg3zXyYb;e96aItM{=%WZV!;LwT&zJ)n zjJI|mFc?Q#_br$LT8$d4a>L+#Y)#}qB($&h7WtfJ<+sJ|MZsttgpxak5Q=v~61%cS zzhq;HCJ$UAj1Z)n8&L*YKsLu9OQu^zUqRGIlqb3eLAggP3j&c=GUa97cV(S(lKgP25LT~uk zmR9RE7|X~QJO1~YIWhvV@j1@L<*V*&UJj_rR*`Od^)HWTQ+*Ct4HzFcczFxbpVBkz zOY0A$|A;UllJb6D_1IeFczgP|{X-s_%(3fl5r*DAl8{xmq@DeBR z4wQ7F@;V!t*dxZb^Vd2b#{uFV^;+4FmIqzza;PSJ#YD9wmJT6qn?y!NFd-fC284B$rc^Rt~o6%4~oP zN!qlsGN1?F9;8iY{W8yBy1mGC?X4nhoe)F2&4coB(-qqCOG`@&h=hc$orh>l zTH5>Pr4Vm#35k;vc?Sn$V+a12IDljlG($n5f)wuvohT)@8d?E8nHy7|ct60VSiT)Wvht3?(N9T9AMh5JmseJnu=MqH?Hu?`g};1ec#F`l&?U*$o)=R9!Gt^_ z2=7K?q-)JPKPlRSs@+{d!LqU-5`h$9)M8vPi2$wgJMIq?8!lL?Ip2RykgShu>5mUW z?5FC?AG(XEg9{HKu_~$#k0v&P#&|w)(;&Cs$09)JZ22ToJ?S$3qU9BlRt`NKmWQuJ zv;5MMO{FZ$U2}Z3EsgVg>SVDgC?sm}NwjWANSQALLLr8p1t>s56th4V4JR)Tp{MOp zMJTFkr<*X!l!EH%2l#;xa;rU8SF8e^eq7K90$o`g$y5+}Z!meaFFVVx+c0tOq!Uml ztI~DYt3Jkhqv$No`scImPcUo{LCf&#A^dcmEBvX$&IAgo24MaZ4L&P^K z=FObTlED64#ftnp2ZaM%PfN9z0 zdzl#3AHI`H;Kx&p=I2kli{&I4zP!RGqO(7pEa7^7EDsr6gB1uMpc$H%Y7GR`dFZmm zro(LD)zMn9p#da)z@p3m_;a)O2Z>E`3vDwtiugNIg_@B`{NY*khP-^bIvW!NetRl5 zHtr%NR%+%8t)cP{!-r8$5LuQx*EBeHW%dzKmOoO2i9jYph%kG;VFlVTZOPq0Po9_r3=Bg7S6_MBz@%V~d-GK;MxeQCNHBnv9#WyQI*sqzAw?U=J zTwEYDYgDh18nm)0Ahu}aU=Gz?I3Vka^K*AsQ8CYYYVb85+hZfYz|H~x_XG$xGXv?e zN=Dw^=zc?sW58Yu&e$`8&z~dOgpsl~$>{>BTi=s}-1Egh^pi2gA2Z~B8v%{ol^w{8 zzn%ti7{|?{&WP1I>AQ^$;v5o6|C&9K9_C-Pyc`x5N)SAxsgZ7XcKEE`);6e9v2?5t zwSQJy@u(Dmj94vmLp6ApTVm-MIWK)6p^s2nDvc1<*Pq{?o~(^yk8FcU-pryUyGE)_ zq7r`kJ^`NioRWu(9H}L=FZe)u5Sq+VeRY8tp@7p4HNoHNHnp{w-B6 z=SR$RbX;8xldT=TXT#tlxf&>xjZI6PJB9T2UOEnbsQAT=g01{`T4$fFujTAdw$EUF z3{8$hPHpmZY=9D<$ay3rm(g{mw-g8zih?54t1<~-Wnp|8uXe(pw{BjuVv%i_b2h6#{#eRji29E z+d^)4$kUkv54CoLBjvd{K0EMTlzX%jep=&f6Sm^Xgw=!_2IjEq%R;$es5^EIY9iVM3fsx<bnV z8%R6#L7wL16X)jJ+S=OD(5=d(c||AT_nA`qq6zGL)PMv57*vVkmprp?S%@6)ZA!Ud z5>iZ=P8Nv*Ud;QhI-59SMQ>k0zP)HhQ&Z*W7D0n?qcQJIIPp9bd0P}&ZzLnr*eLIR zG2N)5(sH-~Z0;xaOn{+=h8{shWn|J_em?fF`T2&1b^F;!4rV{6aY2pMr{`w$GtNuP zw!*S3PCq1aQohM(#@jRGhSh%hbb8t(n^ER+6%b9}R4>95N2E@hg|wg^+m0uNMJciU zaXs-P@5;=f@9X8}KHS6uW#rPVGHPq<)jwt9H5Ckeidi2c>yD-iij8Y1KT5d%NYPSz z+h8@V-qTH)bF}<@0VShkOsf4jzsO-m7HO7Uid>^pc?@e2OzL0uihif~j>XvE{LoM% zxG6tB4>fz=*%rAlm)GH>lC1mmV>-oG{X42F-qMjG<}XE~hJ8U(^czutoN4LjY-dY& zEFI1$VR>MnYxxwpw)V(ay^*|0IwC^Ml(FLJgXsC&4cQ%s9?rJTpXs3@$uP!*(Soa$ zKzaduI2<@bX~yjn>b23BJ+*SHXr`qD?5>-MiCx3K@#~m&+sbo&{b#AeM87szt+|Lp z>DjPLwO9AXH$0>UNB&Eo_uS+Ku8{^$!_E$NUprZa7J6{DAhX=vlU@rZZRTFxGYpjJUg~d27htNMVdbXNXJym5vr2Z64LcAi1 z6r*vkS%?;6XfU3w7-m;nenb0K7*Mt+AaVCG;s^6SWkYk2I*`|=s%l4A4I3*5xTk8PN{Ot9>BG#kY7ggwQWV5kg(yLYu9kZW*YoXvJ-(wytB{z)(AZW;^SlCit6nJ+EhLao;kG83sRWiSQB5Z!-13lL|LbGTgEEcb_CA5PynmBr^)dQ?`s|09*vy0T(_9}+ZR zbvJ^MFZBM%=sY$K8B0=*qYmcly?^ooxk&1Q zk-1Q(SuhdlA@w)Zm(Wp=sUq;WD-Ce-(6j*TIHw>uOJR1lwMw-9Sp~ndBy3QS0Rp4? zP=bXtc%@hcCDLvk_GOO~ZxnN84W-qv00VZ2-1-Si8X1a${B@6v`iZ5}fB^*!iDbnv zaI&{ICPqp(J`~mlr_Q%nk{n8}ZxPhhF;pmqcoF39yMdtAu=hn(C$HU;@d%rmj0`*v z?$E>pI&MpO!k33OHps&jagHw#bQHOTTlTsTM-yRfS>mC^BnL8rV za6?ZMnhRQgh}YHzx7OSPLVLrsKJQaA{eSsY5gaSK!8Uj+F!M{4`9FEla)Olz8bDOA~)o-9?Um`80KoOQg>Rf2y+Y8J$K z_w?G*1?QK$It9aXjv}a8z^!yV1MQLOKDLkq>y8DFwS%59_{!77b1$HlJ3uBH-~OOb z8+5>dZ*6YiNlL*66kzv~hle0AM&tWDQifSOXTk5to8?lR_x72Q+n`SmZF{%wU1Q#r zCWBbX9L^iX4h}+cT2@e(uRQmtdb+!|dOXJ_SFR6;fLJuRi7*l{bgIM@IlXsRJ428G zq~XABm>j@XbE0le?`^kWQB`2L9GyCCp(uST8^=T3{KLa70`8N6I1Z~9z$+uS(bLm+ z624NJ=&~E^tWR+GGUFTWkiIDC`95mxC!10DeM`>m@$M@cO-*TLP=P|Q;&UP9B7vw@ z?*c7M)YJWKFYoVfm^>p8lJF4;O5L^Ltv!8O1^%)bl_Hdbw&-fJ&(?Z5FjngtbR&Uy z80o_d;>vKIW@K787?a{HlKE}ZaPD_cVaX#(E?q9Lm$vc~c)VCFAY&wDUcp55)}XF5 zf1<9PXcQw~91(+*ZlOng@Z&f*+)a|I87*%K^w2F#|K-MXql}EaVpxZnF!neAB2So# u4J{`0Wcb^b!_Qz+9I6oY|2rFgAt&0kpJc \ No newline at end of file From 520e096056b04b844bc33424aa526f6e7307a1c9 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 20 Apr 2022 17:24:51 +0200 Subject: [PATCH 0668/2916] fix: Actually allow directories with --from-archive --- cmd/kluctl/args/project.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/kluctl/args/project.go b/cmd/kluctl/args/project.go index 2dc50b9ae..9153d27f8 100644 --- a/cmd/kluctl/args/project.go +++ b/cmd/kluctl/args/project.go @@ -8,7 +8,7 @@ type ProjectFlags struct { LocalClusters string `group:"project" help:"Local clusters directory. Overrides the project from .kluctl.yml" type:"existingdir"` LocalDeployment string `group:"project" help:"Local deployment directory. Overrides the project from .kluctl.yml" type:"existingdir"` LocalSealedSecrets string `group:"project" help:"Local sealed-secrets directory. Overrides the project from .kluctl.yml" type:"existingdir"` - FromArchive string `group:"project" help:"Load project (.kluctl.yml, cluster, ...) from archive. Given path can either be an archive file or a directory with the extracted contents." type:"existingfile"` + FromArchive string `group:"project" help:"Load project (.kluctl.yml, cluster, ...) from archive. Given path can either be an archive file or a directory with the extracted contents." type:"existing"` FromArchiveMetadata string `group:"project" help:"Specify where to load metadata (targets, ...) from. If not specified, metadata is assumed to be part of the archive." type:"existingfile"` Cluster string `group:"project" help:"Specify/Override cluster"` } From 239d99624024a22a10f0f6ad6a666728b63abd35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aljoscha=20P=C3=B6rtner?= Date: Thu, 21 Apr 2022 06:47:18 +0200 Subject: [PATCH 0669/2916] build: rely on python download script for detection of arch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Aljoscha Pörtner --- Makefile | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Makefile b/Makefile index b74eb7b59..c17bb50b0 100644 --- a/Makefile +++ b/Makefile @@ -46,9 +46,7 @@ vendor: ## Copy of all packages needed to support builds and tests in the vendor $(GOCMD) mod vendor python: ## Download python for Jinja2 support - ./hack/download-python.sh linux - ./hack/download-python.sh windows - ./hack/download-python.sh darwin + ./hack/download-python.sh generate: ## Generating Jinja2 support $(GOCMD) generate ./... From 49b466bcc14b4f97bb08e427f72541e71e9a3609 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 21 Apr 2022 12:12:52 +0200 Subject: [PATCH 0670/2916] fix: Make metadata.yml sorting stable --- pkg/kluctl_project/git.go | 5 +++++ pkg/kluctl_project/load_targets.go | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/pkg/kluctl_project/git.go b/pkg/kluctl_project/git.go index e7cdcf650..4fd28013e 100644 --- a/pkg/kluctl_project/git.go +++ b/pkg/kluctl_project/git.go @@ -10,6 +10,7 @@ import ( "os" "path/filepath" "reflect" + "sort" "strings" "sync" ) @@ -223,5 +224,9 @@ func (c *KluctlProjectContext) addInvolvedRepo(u git_url.GitUrl, refPattern stri } if !found { c.involvedRepos[repoKey] = append(c.involvedRepos[repoKey], *e) + s := c.involvedRepos[repoKey] + sort.SliceStable(s, func(i, j int) bool { + return s[i].RefPattern < s[j].RefPattern + }) } } diff --git a/pkg/kluctl_project/load_targets.go b/pkg/kluctl_project/load_targets.go index 6cfcd76c2..1d86d5d5e 100644 --- a/pkg/kluctl_project/load_targets.go +++ b/pkg/kluctl_project/load_targets.go @@ -12,6 +12,7 @@ import ( "path/filepath" "reflect" "regexp" + "sort" "strings" "sync" ) @@ -69,6 +70,9 @@ func (c *KluctlProjectContext) loadTargets() error { }) } } + sort.SliceStable(c.DynamicTargets, func(i, j int) bool { + return c.DynamicTargets[i].Target.Name < c.DynamicTargets[j].Target.Name + }) return nil } From 9efae3d847f4fc4c78f4cd13f0ab3b78e8720e1e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 21 Apr 2022 18:14:01 +0200 Subject: [PATCH 0671/2916] build: Move downloading of python into generate stage and via go --- .github/workflows/build-and-release.yml | 8 +- Makefile | 7 +- go.mod | 1 + hack/download-python.sh | 43 -------- pkg/python/download/main.go | 132 ++++++++++++++++++++++++ pkg/python/embed_darwin.go | 8 -- pkg/python/embed_darwin_amd64.go | 9 ++ pkg/python/embed_darwin_arm64.go | 9 ++ pkg/python/embed_linux.go | 8 -- pkg/python/embed_linux_amd64.go | 9 ++ pkg/python/embed_linux_arm64.go | 9 ++ pkg/python/embed_windows.go | 8 -- pkg/python/embed_windows_amd64.go | 9 ++ pkg/utils/embed_util/packer/main.go | 1 + pkg/utils/tar.go | 20 ++-- 15 files changed, 194 insertions(+), 87 deletions(-) delete mode 100755 hack/download-python.sh create mode 100644 pkg/python/download/main.go delete mode 100644 pkg/python/embed_darwin.go create mode 100644 pkg/python/embed_darwin_amd64.go create mode 100644 pkg/python/embed_darwin_arm64.go delete mode 100644 pkg/python/embed_linux.go create mode 100644 pkg/python/embed_linux_amd64.go create mode 100644 pkg/python/embed_linux_arm64.go delete mode 100644 pkg/python/embed_windows.go create mode 100644 pkg/python/embed_windows_amd64.go diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index 4cd3c7c64..26e3bcfc7 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -32,11 +32,6 @@ jobs: key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- - - name: Download python - run: | - ./hack/download-python.sh linux - ./hack/download-python.sh darwin - ./hack/download-python.sh windows - name: Get Version id: get_version run: | @@ -68,8 +63,7 @@ jobs: - name: Go Generate run: | go generate ./... - go generate -tags darwin ./pkg/python - go generate -tags windows ./pkg/python + go generate -tags linux,darwin,windows,amd64,arm64 ./pkg/python - name: Run unit tests run: | go test ./cmd/... ./pkg/... -v diff --git a/Makefile b/Makefile index c17bb50b0..f73aa2b4c 100644 --- a/Makefile +++ b/Makefile @@ -45,12 +45,9 @@ clean: ## Remove build related file vendor: ## Copy of all packages needed to support builds and tests in the vendor directory $(GOCMD) mod vendor -python: ## Download python for Jinja2 support - ./hack/download-python.sh - -generate: ## Generating Jinja2 support +generate: ## Generating Python and Jinja2 support $(GOCMD) generate ./... - $(GOCMD) generate ./pkg/python + $(GOCMD) generate -tags linux,darwin,windows,amd64,arm64 ./pkg/python ## Test: test: test-unit test-e2e ## Runs the complete test suite diff --git a/go.mod b/go.mod index 7b7f6a544..56cc82387 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( github.com/hexops/gotextdiff v1.0.3 github.com/jinzhu/copier v0.3.5 github.com/kevinburke/ssh_config v1.2.0 + github.com/klauspost/compress v1.13.6 github.com/mattn/go-isatty v0.0.14 github.com/mitchellh/go-ps v1.0.0 github.com/ohler55/ojg v1.14.0 diff --git a/hack/download-python.sh b/hack/download-python.sh deleted file mode 100755 index d78a9040a..000000000 --- a/hack/download-python.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env bash - -set -e - -DIR=$(cd $(dirname $0) && pwd) -cd $DIR/.. - -if [ "$1" != "" ]; then - os=$1 -else - case "$(uname -s)" in - Linux*) os=linux;; - Darwin*) os=darwin;; - MINGW*) os=windows;; - *) echo "unknown os"; exit 1; - esac -fi - -arch=x86_64 - -case "$os" in - linux*) python_dist=unknown-linux-gnu-pgo-full;; - darwin*) python_dist=apple-darwin-pgo-full;; - windows*) python_dist=pc-windows-msvc-shared-pgo-full;; -esac - -mkdir -p download-python/$os -cd download-python/$os - -PYTHON_STANDALONE_VERSION=20220227 -PYTHON_VERSION=3.10.2 - -if [ ! -d python ]; then - curl -L -o python.tar.zst https://github.com/indygreg/python-build-standalone/releases/download/$PYTHON_STANDALONE_VERSION/cpython-$PYTHON_VERSION+$PYTHON_STANDALONE_VERSION-$arch-$python_dist.tar.zst - tar -xf python.tar.zst -fi - -cd python/install - -for i in test site-packages venv ensurepip idlelib distutils pydoc_data asyncio email tkinter lib2to3 xml multiprocessing unittest; do - rm -rf lib/python3.*/$i - rm -rf Lib/$i -done diff --git a/pkg/python/download/main.go b/pkg/python/download/main.go new file mode 100644 index 000000000..0987a2015 --- /dev/null +++ b/pkg/python/download/main.go @@ -0,0 +1,132 @@ +package main + +import ( + "fmt" + "github.com/klauspost/compress/zstd" + "github.com/kluctl/kluctl/v2/pkg/utils" + log "github.com/sirupsen/logrus" + "io/ioutil" + "net/http" + "os" + "path/filepath" +) + +const ( + pythonVersionBase = "3.10" + pythonVersionFull = "3.10.3" + pythonStandaloneVersion = "20220318" +) + +var pythonDists = map[string]string{ + "linux": "unknown-linux-gnu-lto-full", + "darwin": "apple-darwin-lto-full", + "windows": "pc-windows-msvc-shared-pgo-full", +} + +var archMapping = map[string]string{ + "amd64": "x86_64", + "386": "i686", + "arm64": "aarch64", +} + +var removeLibs = []string{ + "site-packages", + "venv", + "ensurepip", + "idlelib", + "distutils", + "pydoc_data", + "asyncio", + "email", + "tkinter", + "lib2to3", + "xml", + "multiprocessing", + "unittest", +} + +func main() { + osName := os.Args[1] + arch := os.Args[2] + extractPath := os.Args[3] + + dist, ok := pythonDists[osName] + if !ok { + log.Panicf("no dist for %s", osName) + } + + downloadPath := download(osName, arch, dist) + + os.RemoveAll(extractPath) + decompress(downloadPath, extractPath) + + for _, lib := range removeLibs { + _ = os.RemoveAll(filepath.Join(extractPath, "python", "install", "lib", fmt.Sprintf("python%s", pythonVersionBase), lib)) + _ = os.RemoveAll(filepath.Join(extractPath, "python", "install", "Lib", lib)) + } +} + +func download(osName, arch, dist string) string { + pythonArch, ok := archMapping[arch] + if !ok { + log.Errorf("arch %s not supported", arch) + os.Exit(1) + } + fname := fmt.Sprintf("cpython-%s+%s-%s-%s.tar.zst", pythonVersionFull, pythonStandaloneVersion, pythonArch, dist) + downloadPath := filepath.Join(os.TempDir(), fname) + downloadUrl := fmt.Sprintf("https://github.com/indygreg/python-build-standalone/releases/download/%s/%s", pythonStandaloneVersion, fname) + + if _, err := os.Stat(downloadPath); err == nil { + log.Infof("skipping download of %s", downloadUrl) + return downloadPath + } + + log.Infof("downloading %s", downloadUrl) + + r, err := http.Get(downloadUrl) + if err != nil { + log.Errorf("download failed: %v", err) + os.Exit(1) + } + if r.StatusCode == http.StatusNotFound { + log.Errorf("404 not found") + os.Exit(1) + } + defer r.Body.Close() + + fileData, err := ioutil.ReadAll(r.Body) + + err = ioutil.WriteFile(downloadPath, fileData, 0o640) + if err != nil { + log.Errorf("writing file failed: %v", err) + os.Remove(downloadPath) + os.Exit(1) + } + + return downloadPath +} + +func decompress(archivePath string, targetPath string) string { + f, err := os.Open(archivePath) + if err != nil { + log.Errorf("opening file failed: %v", err) + os.Exit(1) + } + defer f.Close() + + z, err := zstd.NewReader(f) + if err != nil { + log.Errorf("decompression failed: %v", err) + os.Exit(1) + } + defer z.Close() + + log.Infof("decompressing %s", archivePath) + err = utils.ExtractTarStream(z, targetPath) + if err != nil { + log.Errorf("decompression failed: %v", err) + os.Exit(1) + } + + return targetPath +} \ No newline at end of file diff --git a/pkg/python/embed_darwin.go b/pkg/python/embed_darwin.go deleted file mode 100644 index ee5252620..000000000 --- a/pkg/python/embed_darwin.go +++ /dev/null @@ -1,8 +0,0 @@ -package python - -import "embed" - -//go:generate go run ../utils/embed_util/packer python-darwin.tar.gz ../../download-python/darwin/python/install bin lib/*.dylib lib/python3.* - -//go:embed python-darwin.tar.gz python-darwin.tar.gz.files -var pythonLib embed.FS diff --git a/pkg/python/embed_darwin_amd64.go b/pkg/python/embed_darwin_amd64.go new file mode 100644 index 000000000..4fb6ecf9a --- /dev/null +++ b/pkg/python/embed_darwin_amd64.go @@ -0,0 +1,9 @@ +package python + +import "embed" + +//go:generate go run ./download darwin amd64 ../../download-python/darwin/amd64 +//go:generate go run ../utils/embed_util/packer python-darwin-amd64.tar.gz ../../download-python/darwin/amd64/python/install bin lib/*.dylib lib/python3.* + +//go:embed python-darwin-amd64.tar.gz python-darwin-amd64.tar.gz.files +var pythonLib embed.FS diff --git a/pkg/python/embed_darwin_arm64.go b/pkg/python/embed_darwin_arm64.go new file mode 100644 index 000000000..18aa21233 --- /dev/null +++ b/pkg/python/embed_darwin_arm64.go @@ -0,0 +1,9 @@ +package python + +import "embed" + +//go:generate go run ./download darwin arm64 ../../download-python/darwin/arm64 +//go:generate go run ../utils/embed_util/packer python-darwin-arm64.tar.gz ../../download-python/darwin/arm64/python/install bin lib/*.dylib lib/python3.* + +//go:embed python-darwin-arm64.tar.gz python-darwin-arm64.tar.gz.files +var pythonLib embed.FS diff --git a/pkg/python/embed_linux.go b/pkg/python/embed_linux.go deleted file mode 100644 index db8d55096..000000000 --- a/pkg/python/embed_linux.go +++ /dev/null @@ -1,8 +0,0 @@ -package python - -import "embed" - -//go:generate go run ../utils/embed_util/packer python-linux.tar.gz ../../download-python/linux/python/install bin lib/*.so* lib/python3.* - -//go:embed python-linux.tar.gz python-linux.tar.gz.files -var pythonLib embed.FS diff --git a/pkg/python/embed_linux_amd64.go b/pkg/python/embed_linux_amd64.go new file mode 100644 index 000000000..5961a767d --- /dev/null +++ b/pkg/python/embed_linux_amd64.go @@ -0,0 +1,9 @@ +package python + +import "embed" + +//go:generate go run ./download linux amd64 ../../download-python/linux/amd64 +//go:generate go run ../utils/embed_util/packer python-linux-amd64.tar.gz ../../download-python/linux/amd64/python/install bin lib/*.so* lib/python3.* + +//go:embed python-linux-amd64.tar.gz python-linux-amd64.tar.gz.files +var pythonLib embed.FS diff --git a/pkg/python/embed_linux_arm64.go b/pkg/python/embed_linux_arm64.go new file mode 100644 index 000000000..40be4e8b0 --- /dev/null +++ b/pkg/python/embed_linux_arm64.go @@ -0,0 +1,9 @@ +package python + +import "embed" + +//go:generate go run ./download linux arm64 ../../download-python/linux/arm64 +//go:generate go run ../utils/embed_util/packer python-linux-arm64.tar.gz ../../download-python/linux/arm64/python/install bin lib/*.so* lib/python3.* + +//go:embed python-linux-arm64.tar.gz python-linux-arm64.tar.gz.files +var pythonLib embed.FS diff --git a/pkg/python/embed_windows.go b/pkg/python/embed_windows.go deleted file mode 100644 index 09a5af595..000000000 --- a/pkg/python/embed_windows.go +++ /dev/null @@ -1,8 +0,0 @@ -package python - -import "embed" - -//go:generate go run ../utils/embed_util/packer python-windows.tar.gz ../../download-python/windows/python/install Lib DLLs *.dll *.exe - -//go:embed python-windows.tar.gz python-windows.tar.gz.files -var pythonLib embed.FS diff --git a/pkg/python/embed_windows_amd64.go b/pkg/python/embed_windows_amd64.go new file mode 100644 index 000000000..dfa2a12a3 --- /dev/null +++ b/pkg/python/embed_windows_amd64.go @@ -0,0 +1,9 @@ +package python + +import "embed" + +//go:generate go run ./download windows amd64 ../../download-python/windows/amd64 +//go:generate go run ../utils/embed_util/packer python-windows-amd64.tar.gz ../../download-python/windows/amd64/python/install Lib DLLs *.dll *.exe + +//go:embed python-windows-amd64.tar.gz python-windows-amd64.tar.gz.files +var pythonLib embed.FS diff --git a/pkg/utils/embed_util/packer/main.go b/pkg/utils/embed_util/packer/main.go index f4d4ba5e3..39cb9e2cf 100644 --- a/pkg/utils/embed_util/packer/main.go +++ b/pkg/utils/embed_util/packer/main.go @@ -23,6 +23,7 @@ func main() { dir := os.Args[2] patterns := os.Args[3:] + log.Infof("writing tar %s", out) fileList, tgz := writeTar(dir, patterns) hash := sha256.Sum256(tgz) diff --git a/pkg/utils/tar.go b/pkg/utils/tar.go index 7fa7761b8..0cd6807ed 100644 --- a/pkg/utils/tar.go +++ b/pkg/utils/tar.go @@ -33,7 +33,11 @@ func ExtractTarGzStream(r io.Reader, targetPath string) error { } defer gz.Close() - tarReader := tar.NewReader(gz) + return ExtractTarStream(gz, targetPath) +} + +func ExtractTarStream(r io.Reader, targetPath string) error { + tarReader := tar.NewReader(r) for true { header, err := tarReader.Next() if err == io.EOF { @@ -41,7 +45,7 @@ func ExtractTarGzStream(r io.Reader, targetPath string) error { } if err != nil { - return fmt.Errorf("ExtractTarGz: Next() failed: %w", err) + return fmt.Errorf("ExtractTarStream: Next() failed: %w", err) } header.Name = strings.ReplaceAll(header.Name, "/", string(os.PathSeparator)) @@ -55,28 +59,28 @@ func ExtractTarGzStream(r io.Reader, targetPath string) error { switch header.Typeflag { case tar.TypeDir: if err := os.MkdirAll(p, 0755); err != nil { - return fmt.Errorf("ExtractTarGz: Mkdir() failed: %w", err) + return fmt.Errorf("ExtractTarStream: Mkdir() failed: %w", err) } case tar.TypeReg: outFile, err := os.Create(p) if err != nil { - return fmt.Errorf("ExtractTarGz: Create() failed: %w", err) + return fmt.Errorf("ExtractTarStream: Create() failed: %w", err) } _, err = io.Copy(outFile, tarReader) _ = outFile.Close() if err != nil { - return fmt.Errorf("ExtractTarGz: Copy() failed: %w", err) + return fmt.Errorf("ExtractTarStream: Copy() failed: %w", err) } err = os.Chmod(p, header.FileInfo().Mode()) if err != nil { - return fmt.Errorf("ExtractTarGz: Chmod() failed: %w", err) + return fmt.Errorf("ExtractTarStream: Chmod() failed: %w", err) } case tar.TypeSymlink: if err := os.Symlink(header.Linkname, p); err != nil { - return fmt.Errorf("ExtractTarGz: Symlink() failed: %w", err) + return fmt.Errorf("ExtractTarStream: Symlink() failed: %w", err) } default: - return fmt.Errorf("ExtractTarGz: uknown type %v in %v", header.Typeflag, header.Name) + return fmt.Errorf("ExtractTarStream: uknown type %v in %v", header.Typeflag, header.Name) } } return nil From cd56bbfdfe514475133a0c5e4411917fcb1ad2cf Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 21 Apr 2022 18:25:41 +0200 Subject: [PATCH 0672/2916] fix: Fix extracting of python --- pkg/python/embed.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/python/embed.go b/pkg/python/embed.go index b4dd1e637..e2654a685 100644 --- a/pkg/python/embed.go +++ b/pkg/python/embed.go @@ -16,7 +16,7 @@ func init() { } func decompressPython() string { - tarName := fmt.Sprintf("python-%s.tar.gz", runtime.GOOS) + tarName := fmt.Sprintf("python-%s-%s.tar.gz", runtime.GOOS, runtime.GOARCH) tgz, err := pythonLib.Open(tarName) if err != nil { log.Panic(err) From fc0a8c9cec75ceaf4486bb65098303235e394585 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 21 Apr 2022 21:50:22 +0200 Subject: [PATCH 0673/2916] build: Use goreleaser for releases --- .github/workflows/release.yml | 34 ++++++++++ .../{build-and-release.yml => tests.yml} | 26 ------- .gitignore | 3 +- .goreleaser.yaml | 67 +++++++++++++++++++ Dockerfile | 3 + 5 files changed, 106 insertions(+), 27 deletions(-) create mode 100644 .github/workflows/release.yml rename .github/workflows/{build-and-release.yml => tests.yml} (92%) create mode 100644 .goreleaser.yaml create mode 100644 Dockerfile diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..da7b6fba8 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,34 @@ +name: goreleaser + +on: + push: + # run only against tags + tags: + - 'v*' + +permissions: + contents: write + packages: write + +jobs: + goreleaser: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Fetch all tags + run: git fetch --force --tags + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.17.9 + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v2 + with: + distribution: goreleaser + version: latest + args: release --rm-dist + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/tests.yml similarity index 92% rename from .github/workflows/build-and-release.yml rename to .github/workflows/tests.yml index 26e3bcfc7..7b23d3510 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/tests.yml @@ -295,29 +295,3 @@ jobs: export KLUCTL_EXE=./dist/kluctl-${{ matrix.binary-suffix }}$TOOLS_EXE ./dist/e2e.test-${{ matrix.binary-suffix }}$TOOLS_EXE -test.v - release: - runs-on: ubuntu-latest - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') - needs: - - build - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Download artifacts - uses: actions/download-artifact@v2 - - name: Build checksums.txt - run: | - cd dist - sha256sum kluctl-linux-amd64 > checksums.txt - sha256sum kluctl-darwin-amd64 >> checksums.txt - sha256sum kluctl-windows-amd64.exe >> checksums.txt - - name: Release - uses: softprops/action-gh-release@v1 - with: - draft: true - body_path: changelog/CHANGELOG.md - files: | - dist/kluctl-linux-* - dist/kluctl-darwin-* - dist/kluctl-windows-* - dist/checksums.txt diff --git a/.gitignore b/.gitignore index ac7a47bd5..6cebf73ac 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,5 @@ /kluctl.exe /e2e.test* /out -/reports \ No newline at end of file +/reports +dist/ diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 000000000..2fde674b2 --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,67 @@ +before: + hooks: + - go mod tidy + - go generate ./... + - go generate -tags linux,darwin,windows,amd64,arm64 ./pkg/python +builds: + - id: kluctl-unix + env: + - CGO_ENABLED=0 + goos: + - linux + - darwin + goarch: + - amd64 + - arm64 + - id: kluctl-windows + env: + - CGO_ENABLED=0 + goos: + - windows + goarch: + - amd64 +archives: + - replacements: + darwin: Darwin + linux: Linux + windows: Windows + 386: i386 + amd64: x86_64 +checksum: + name_template: 'checksums.txt' +snapshot: + name_template: "{{ incpatch .Version }}-next" +changelog: + sort: asc + filters: + exclude: + - '^docs:' + - '^test:' + - '^chore:' + +release: + draft: true + prerelease: auto + name_template: "{{.ProjectName}}-v{{.Version}}" + +dockers: + - id: linux-amd64 + goos: linux + goarch: amd64 + build_flag_templates: + - "--platform=linux/amd64" + image_templates: + - "ghcr.io/kluctl/kluctl:{{ .Version }}-amd64" + - id: linux-arm64 + goos: linux + goarch: arm64 + build_flag_templates: + - "--platform=linux/arm64" + image_templates: + - "ghcr.io/kluctl/kluctl:{{ .Version }}-arm64" + +docker_manifests: + - name_template: ghcr.io/kluctl/kluctl:{{ .Version }} + image_templates: + - "ghcr.io/kluctl/kluctl:{{ .Version }}-amd64" + - "ghcr.io/kluctl/kluctl:{{ .Version }}-arm64" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..8e44c42ff --- /dev/null +++ b/Dockerfile @@ -0,0 +1,3 @@ +FROM alpine +COPY kluctl / +ENTRYPOINT ["/kluctl"] From 424c9334447ea1bd39a10ac4ed0a7f6424afe875 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 21 Apr 2022 21:51:29 +0200 Subject: [PATCH 0674/2916] build: Move main.go to root dir --- .github/workflows/tests.yml | 6 +++--- cmd/kluctl/main.go => main.go | 0 2 files changed, 3 insertions(+), 3 deletions(-) rename cmd/kluctl/main.go => main.go (100%) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7b23d3510..076c4276c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -72,7 +72,7 @@ jobs: export CGO_ENABLED=0 export GOARCH=amd64 export GOOS=linux - go build ./cmd/kluctl + go build go test -c ./e2e mv kluctl kluctl-linux-amd64 mv e2e.test e2e.test-linux-amd64 @@ -81,7 +81,7 @@ jobs: export CGO_ENABLED=0 export GOARCH=amd64 export GOOS=darwin - go build ./cmd/kluctl + go build go test -c ./e2e mv kluctl kluctl-darwin-amd64 mv e2e.test e2e.test-darwin-amd64 @@ -90,7 +90,7 @@ jobs: export CGO_ENABLED=0 export GOARCH=amd64 export GOOS=windows - go build ./cmd/kluctl + go build go test -c ./e2e mv kluctl.exe kluctl-windows-amd64.exe mv e2e.test.exe e2e.test-windows-amd64.exe diff --git a/cmd/kluctl/main.go b/main.go similarity index 100% rename from cmd/kluctl/main.go rename to main.go From 912ee942c02ee1ef83e8f0be4e2e297134b53615 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 21 Apr 2022 22:17:21 +0200 Subject: [PATCH 0675/2916] build: Use version provided by goreleaser --- .github/workflows/tests.yml | 27 --------------------------- cmd/kluctl/commands/cmd_version.go | 2 +- cmd/kluctl/commands/root.go | 4 ++-- main.go | 8 +++++++- pkg/version/version.go | 10 +++++++++- 5 files changed, 19 insertions(+), 32 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 076c4276c..c22bbadb3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,8 +10,6 @@ on: jobs: build: runs-on: ubuntu-latest - outputs: - version: ${{ steps.get_version.outputs.version }} steps: - name: Checkout uses: actions/checkout@v2 @@ -32,31 +30,6 @@ jobs: key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- - - name: Get Version - id: get_version - run: | - go install github.com/bvieira/sv4git/v2/cmd/git-sv@v2.7.0 - KLUCTL_VERSION="$(git sv next-version)" - if [[ "${{ github.ref }}" != refs/tags/* ]]; then - KLUCTL_VERSION="$KLUCTL_VERSION-dev-$(git rev-parse --short HEAD)" - fi - echo "KLUCTL_VERSION=$KLUCTL_VERSION" - echo "KLUCTL_VERSION=$KLUCTL_VERSION" >> $GITHUB_ENV - echo "::set-output name=version::$VERSION" - - name: Generate Changelog - id: gen_changelog - run: | - git sv changelog -n 1 > CHANGELOG.md - - name: Upload Changelog - uses: actions/upload-artifact@v2 - with: - name: changelog - path: | - CHANGELOG.md - - name: Write Version to version.go - run: | - sed -ibak "s/0.0.0/$KLUCTL_VERSION/g" pkg/version/version.go - cat pkg/version/version.go - name: Go Mod Vendor run: | go mod vendor diff --git a/cmd/kluctl/commands/cmd_version.go b/cmd/kluctl/commands/cmd_version.go index de05a2d49..98da1b398 100644 --- a/cmd/kluctl/commands/cmd_version.go +++ b/cmd/kluctl/commands/cmd_version.go @@ -9,6 +9,6 @@ type versionCmd struct { } func (cmd *versionCmd) Run() error { - _, err := os.Stdout.WriteString(version.Version + "\n") + _, err := os.Stdout.WriteString(version.GetVersion() + "\n") return err } diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index 0b20a914b..4027e612c 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -81,7 +81,7 @@ func (c *cli) checkNewVersion() { if c.NoUpdateCheck { return } - if version.Version == "0.0.0" { + if version.GetVersion() == "(devel)" { return } @@ -119,7 +119,7 @@ func (c *cli) checkNewVersion() { latestVersionStr = latestVersionStr[1:] } latestVersion := versions.LooseVersion(latestVersionStr) - localVersion := versions.LooseVersion(version.Version) + localVersion := versions.LooseVersion(version.GetVersion()) if localVersion.Less(latestVersion, true) { log.Warningf("You are using an outdated version (%v) of kluctl. You should update soon to version %v", localVersion, latestVersion) } diff --git a/main.go b/main.go index e95adcccb..fb256f4c4 100644 --- a/main.go +++ b/main.go @@ -15,8 +15,14 @@ limitations under the License. */ package main -import "github.com/kluctl/kluctl/v2/cmd/kluctl/commands" +import ( + "github.com/kluctl/kluctl/v2/cmd/kluctl/commands" + version2 "github.com/kluctl/kluctl/v2/pkg/version" +) + +var version = "0.0.0" func main() { + version2.SetVersion(version) commands.Execute() } diff --git a/pkg/version/version.go b/pkg/version/version.go index 415100dcc..f948678a6 100644 --- a/pkg/version/version.go +++ b/pkg/version/version.go @@ -1,3 +1,11 @@ package version -var Version = "0.0.0" +var version = "0.0.0" + +func SetVersion(v string) { + version = v +} + +func GetVersion() string { + return version +} From b80c289402cf635f592aac48afe2336b4fa40ac0 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 21 Apr 2022 22:18:07 +0200 Subject: [PATCH 0676/2916] build: Fix "make build" to not depend on non-existing target --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index f73aa2b4c..dc4f6521c 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ check-kind: ## Checks if kind is installed kind version ## Build: -build: vendor python generate build-go ## Run the complete build pipeline +build: vendor generate build-go ## Run the complete build pipeline build-go: ## Build your project and put the output binary in out/bin/ mkdir -p out/bin From 1792931295772a4ce3f95f1980f3402a605fdcde Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 21 Apr 2022 22:18:25 +0200 Subject: [PATCH 0677/2916] build: Install python in release job --- .github/workflows/release.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index da7b6fba8..681f86b49 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,6 +24,10 @@ jobs: uses: actions/setup-go@v2 with: go-version: 1.17.9 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3.10.2 - name: Run GoReleaser uses: goreleaser/goreleaser-action@v2 with: From 76297c2d4a631d97536bd686228d3cc190e0bd03 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 21 Apr 2022 22:18:41 +0200 Subject: [PATCH 0678/2916] build: Rename dist artifact to binaries --- .github/workflows/tests.yml | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c22bbadb3..d0d67de68 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,11 +1,9 @@ -name: build +name: tests on: push: branches: - '*' - tags: - - 'v*' jobs: build: @@ -67,10 +65,10 @@ jobs: go test -c ./e2e mv kluctl.exe kluctl-windows-amd64.exe mv e2e.test.exe e2e.test-windows-amd64.exe - - name: Upload dist artifact + - name: Upload binaries uses: actions/upload-artifact@v2 with: - name: dist + name: binaries path: | kluctl-linux-amd64 kluctl-darwin-amd64 @@ -259,12 +257,11 @@ jobs: echo "KIND_KUBECONFIG=$(pwd)/kind-kubeconfig" >> $GITHUB_ENV ./hack/start-kind-cluster.sh "$KIND_CLUSTER_NAME" "$DOCKER_IP" "$PORT" - - name: Download dist artifacts + - name: Download artifacts uses: actions/download-artifact@v2 - name: Run e2e tests shell: bash run: | - chmod +x ./dist/* - export KLUCTL_EXE=./dist/kluctl-${{ matrix.binary-suffix }}$TOOLS_EXE - ./dist/e2e.test-${{ matrix.binary-suffix }}$TOOLS_EXE -test.v - + chmod +x ./binaries/* + export KLUCTL_EXE=./binaries/kluctl-${{ matrix.binary-suffix }}$TOOLS_EXE + ./binaries/e2e.test-${{ matrix.binary-suffix }}$TOOLS_EXE -test.v From 6dac10ce6fc82cb3a6bc63c550baf89b65c0c5f6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 21 Apr 2022 23:03:44 +0200 Subject: [PATCH 0679/2916] build: Skip extraction to speed up generate --- pkg/python/download/main.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/pkg/python/download/main.go b/pkg/python/download/main.go index 0987a2015..b61e93840 100644 --- a/pkg/python/download/main.go +++ b/pkg/python/download/main.go @@ -56,14 +56,23 @@ func main() { } downloadPath := download(osName, arch, dist) + archiveBytes, _ := ioutil.ReadFile(downloadPath) + hash := utils.Sha256Bytes(archiveBytes) - os.RemoveAll(extractPath) - decompress(downloadPath, extractPath) + if utils.Exists(filepath.Join(extractPath, hash)) { + log.Infof("skipping extract") + return + } + + _ = os.RemoveAll(extractPath) + extract(downloadPath, extractPath) for _, lib := range removeLibs { _ = os.RemoveAll(filepath.Join(extractPath, "python", "install", "lib", fmt.Sprintf("python%s", pythonVersionBase), lib)) _ = os.RemoveAll(filepath.Join(extractPath, "python", "install", "Lib", lib)) } + + _ = utils.Touch(filepath.Join(extractPath, hash)) } func download(osName, arch, dist string) string { @@ -106,7 +115,7 @@ func download(osName, arch, dist string) string { return downloadPath } -func decompress(archivePath string, targetPath string) string { +func extract(archivePath string, targetPath string) string { f, err := os.Open(archivePath) if err != nil { log.Errorf("opening file failed: %v", err) From 888ccd053ddb97f4d727abf208a9252a21b6aaf0 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 21 Apr 2022 23:04:09 +0200 Subject: [PATCH 0680/2916] build: No need to duplicate builds (we can use ignore) --- .goreleaser.yaml | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 2fde674b2..94593898d 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -4,22 +4,19 @@ before: - go generate ./... - go generate -tags linux,darwin,windows,amd64,arm64 ./pkg/python builds: - - id: kluctl-unix + - id: kluctl env: - CGO_ENABLED=0 goos: - linux - darwin - goarch: - - amd64 - - arm64 - - id: kluctl-windows - env: - - CGO_ENABLED=0 - goos: - windows goarch: - amd64 + - arm64 + ignore: + - goos: windows + goarch: arm64 archives: - replacements: darwin: Darwin From 3c27643a0c621476bc4ac5340ed550ccc049f4f6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 21 Apr 2022 23:08:56 +0200 Subject: [PATCH 0681/2916] build: No need to generate for arm64 for tests --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d0d67de68..e0d2548be 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -34,7 +34,7 @@ jobs: - name: Go Generate run: | go generate ./... - go generate -tags linux,darwin,windows,amd64,arm64 ./pkg/python + go generate -tags linux,darwin,windows,amd64 ./pkg/python - name: Run unit tests run: | go test ./cmd/... ./pkg/... -v From 0afaa38a450068c748794b4967d781bace5b9d86 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 21 Apr 2022 23:13:34 +0200 Subject: [PATCH 0682/2916] build: Fix make build --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index dc4f6521c..386304320 100644 --- a/Makefile +++ b/Makefile @@ -34,7 +34,7 @@ build: vendor generate build-go ## Run the complete build pipeline build-go: ## Build your project and put the output binary in out/bin/ mkdir -p out/bin - CGO_ENBALED=0 GO111MODULE=on $(GOCMD) build -mod vendor -o out/bin/$(BINARY_NAME) ./cmd/kluctl + CGO_ENBALED=0 GO111MODULE=on $(GOCMD) build -mod vendor -o out/bin/$(BINARY_NAME) clean: ## Remove build related file rm -fr ./bin From b54f008c15d5805205f5ccc535323384c14035bc Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 21 Apr 2022 23:58:22 +0200 Subject: [PATCH 0683/2916] build: Add homebrew release --- .github/workflows/release.yml | 1 + .goreleaser.yaml | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 681f86b49..d58ed932d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -36,3 +36,4 @@ jobs: args: release --rm-dist env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }} diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 94593898d..31e60d0e1 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -62,3 +62,17 @@ docker_manifests: image_templates: - "ghcr.io/kluctl/kluctl:{{ .Version }}-amd64" - "ghcr.io/kluctl/kluctl:{{ .Version }}-arm64" + +brews: + - name: kluctl + tap: + owner: kluctl + name: homebrew-tap + token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}" + folder: Formula + homepage: "https://kluctl.io/" + description: "kluctl" + install: | + bin.install "kluctl" + test: | + system "#{bin}/kluctl version" From ad816f20e47569f58789616ab193f8f06b68563e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 22 Apr 2022 00:04:19 +0200 Subject: [PATCH 0684/2916] build: Immediately release instead of creating a draft --- .goreleaser.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 31e60d0e1..14d80e4f5 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -37,7 +37,6 @@ changelog: - '^chore:' release: - draft: true prerelease: auto name_template: "{{.ProjectName}}-v{{.Version}}" From 27e8bb305998e377c51e7fe93b1b1c83e71e9070 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 22 Apr 2022 00:20:37 +0200 Subject: [PATCH 0685/2916] build: Add go cache to goreleaser workflow --- .github/workflows/release.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d58ed932d..a5a72b116 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,6 +28,14 @@ jobs: uses: actions/setup-python@v2 with: python-version: 3.10.2 + - uses: actions/cache@v2 + with: + path: | + ~/go/pkg/mod + ~/.cache/go-build + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- - name: Run GoReleaser uses: goreleaser/goreleaser-action@v2 with: From ad14a743c72408c9869b599819bee75d30665997 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 22 Apr 2022 00:22:36 +0200 Subject: [PATCH 0686/2916] build: Use own cache key for goreleaser --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a5a72b116..3bff60fef 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,9 +33,9 @@ jobs: path: | ~/go/pkg/mod ~/.cache/go-build - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + key: ${{ runner.os }}-goreleaser-${{ hashFiles('**/go.sum') }} restore-keys: | - ${{ runner.os }}-go- + ${{ runner.os }}-goreleaser- - name: Run GoReleaser uses: goreleaser/goreleaser-action@v2 with: From 803a6fd8e607e593f95eb6695eaa785e88ad251d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 22 Apr 2022 00:31:51 +0200 Subject: [PATCH 0687/2916] build: Add opencontainers labels to docker images --- .goreleaser.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 14d80e4f5..5bba04646 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -45,6 +45,13 @@ dockers: goos: linux goarch: amd64 build_flag_templates: + - "--pull" + - "--build-arg=ARCH=linux/amd64" + - "--label=org.opencontainers.image.created={{ .Date }}" + - "--label=org.opencontainers.image.name={{ .ProjectName }}" + - "--label=org.opencontainers.image.revision={{ .FullCommit }}" + - "--label=org.opencontainers.image.version={{ .Version }}" + - "--label=org.opencontainers.image.source={{ .GitURL }}" - "--platform=linux/amd64" image_templates: - "ghcr.io/kluctl/kluctl:{{ .Version }}-amd64" @@ -52,6 +59,13 @@ dockers: goos: linux goarch: arm64 build_flag_templates: + - "--pull" + - "--build-arg=ARCH=linux/arm64" + - "--label=org.opencontainers.image.created={{ .Date }}" + - "--label=org.opencontainers.image.name={{ .ProjectName }}" + - "--label=org.opencontainers.image.revision={{ .FullCommit }}" + - "--label=org.opencontainers.image.version={{ .Version }}" + - "--label=org.opencontainers.image.source={{ .GitURL }}" - "--platform=linux/arm64" image_templates: - "ghcr.io/kluctl/kluctl:{{ .Version }}-arm64" From 0cd6851b5ff377a0abfa6ef122927d2aa513ce73 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 22 Apr 2022 00:33:58 +0200 Subject: [PATCH 0688/2916] build: Setup docker and login to ghcr --- .github/workflows/release.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3bff60fef..a304b20d6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,6 +28,17 @@ jobs: uses: actions/setup-python@v2 with: python-version: 3.10.2 + - name: Setup QEMU + uses: docker/setup-qemu-action@v1 + - name: Setup Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v1 + - name: Login to GitHub Container Registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: fluxcdbot + password: ${{ secrets.GHCR_TOKEN }} - uses: actions/cache@v2 with: path: | From 9316fe4370a3bd7e5b5fc326d984fdf7d19b8d29 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 22 Apr 2022 00:36:03 +0200 Subject: [PATCH 0689/2916] build: Use correct username for ghcr login --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a304b20d6..1f423ea0d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -37,7 +37,7 @@ jobs: uses: docker/login-action@v1 with: registry: ghcr.io - username: fluxcdbot + username: kluctlbot password: ${{ secrets.GHCR_TOKEN }} - uses: actions/cache@v2 with: From 8e6cb2651eb4eaad751d6ee33ae189d47bd98e92 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 22 Apr 2022 00:59:19 +0200 Subject: [PATCH 0690/2916] build: Fix HOMEBREW_TAP_GITHUB_TOKEN --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1f423ea0d..0f2953895 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -55,4 +55,4 @@ jobs: args: release --rm-dist env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }} + HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }} From dc218a424e2f4da3833e04acb56b9dc4a1453677 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 22 Apr 2022 09:34:22 +0200 Subject: [PATCH 0691/2916] build: Use tag instead of version in release names/tags --- .goreleaser.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 5bba04646..851a78bfc 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -38,7 +38,7 @@ changelog: release: prerelease: auto - name_template: "{{.ProjectName}}-v{{.Version}}" + name_template: "{{.ProjectName}}-{{.Tag}}" dockers: - id: linux-amd64 @@ -54,7 +54,7 @@ dockers: - "--label=org.opencontainers.image.source={{ .GitURL }}" - "--platform=linux/amd64" image_templates: - - "ghcr.io/kluctl/kluctl:{{ .Version }}-amd64" + - "ghcr.io/kluctl/kluctl:{{ .Tag }}-amd64" - id: linux-arm64 goos: linux goarch: arm64 @@ -68,13 +68,13 @@ dockers: - "--label=org.opencontainers.image.source={{ .GitURL }}" - "--platform=linux/arm64" image_templates: - - "ghcr.io/kluctl/kluctl:{{ .Version }}-arm64" + - "ghcr.io/kluctl/kluctl:{{ .Tag }}-arm64" docker_manifests: - - name_template: ghcr.io/kluctl/kluctl:{{ .Version }} + - name_template: ghcr.io/kluctl/kluctl:{{ .Tag }} image_templates: - - "ghcr.io/kluctl/kluctl:{{ .Version }}-amd64" - - "ghcr.io/kluctl/kluctl:{{ .Version }}-arm64" + - "ghcr.io/kluctl/kluctl:{{ .Tag }}-amd64" + - "ghcr.io/kluctl/kluctl:{{ .Tag }}-arm64" brews: - name: kluctl From d3ebe24ac3faa8968af03f1e30d74f3fea8d7fb4 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 22 Apr 2022 09:39:23 +0200 Subject: [PATCH 0692/2916] fix: Use $TMPDIR/kluctl-workdir as base for temporary files This avoids clashes in case someone has put a file into /tmp/ with the name kluctl, which is not that unlikely to happen. --- pkg/utils/utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index aa5433d9e..ae338d920 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -14,7 +14,7 @@ import ( var createTmpBaseDirOnce sync.Once func GetTmpBaseDir() string { - dir := filepath.Join(os.TempDir(), "kluctl") + dir := filepath.Join(os.TempDir(), "kluctl-workdir") createTmpBaseDirOnce.Do(func() { err := os.MkdirAll(dir, 0o700) if err != nil { From c7847ae653352b373f18f7c51e666a288a9eee1f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 22 Apr 2022 10:04:13 +0200 Subject: [PATCH 0693/2916] build: Add SBOM to releases --- .github/workflows/release.yml | 2 ++ .goreleaser.yaml | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0f2953895..b91fdad0a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,6 +33,8 @@ jobs: - name: Setup Docker Buildx id: buildx uses: docker/setup-buildx-action@v1 + - name: Setup Syft + uses: anchore/sbom-action/download-syft@v0 - name: Login to GitHub Container Registry uses: docker/login-action@v1 with: diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 851a78bfc..3fd73cc23 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -24,6 +24,11 @@ archives: windows: Windows 386: i386 amd64: x86_64 +sboms: + - id: source + artifacts: source + documents: + - "{{ .ProjectName }}_v{{ .Version }}_sbom.spdx.json" checksum: name_template: 'checksums.txt' snapshot: From cb3bed6b7414ad8cf53ddd28f29c15aab743c071 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 22 Apr 2022 10:05:31 +0200 Subject: [PATCH 0694/2916] build: Fix versions in release artifacts and stop replacing os/arch --- .goreleaser.yaml | 52 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 3fd73cc23..6fb8940d7 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -4,26 +4,44 @@ before: - go generate ./... - go generate -tags linux,darwin,windows,amd64,arm64 ./pkg/python builds: - - id: kluctl - env: - - CGO_ENABLED=0 + - <<: &build_defaults + binary: kluctl + env: + - CGO_ENABLED=0 + id: linux goos: - linux + goarch: + - amd64 + - arm64 + goarm: + - 7 + - <<: *build_defaults + id: darwin + goos: - darwin - - windows goarch: - amd64 - arm64 - ignore: - - goos: windows - goarch: arm64 + - <<: *build_defaults + id: windows + goos: + - windows + goarch: + - amd64 archives: - - replacements: - darwin: Darwin - linux: Linux - windows: Windows - 386: i386 - amd64: x86_64 + - name_template: "{{ .Binary }}_v{{ .Version }}_{{ .Os }}_{{ .Arch }}" + id: nix + builds: [linux, darwin] + format: tar.gz + files: + - none* + - name_template: "{{ .Binary }}_v{{ .Version }}_{{ .Os }}_{{ .Arch }}" + id: windows + builds: [windows] + format: zip + files: + - none* sboms: - id: source artifacts: source @@ -59,7 +77,7 @@ dockers: - "--label=org.opencontainers.image.source={{ .GitURL }}" - "--platform=linux/amd64" image_templates: - - "ghcr.io/kluctl/kluctl:{{ .Tag }}-amd64" + - "ghcr.io/kluctl/kluctl:v{{ .Version }}-amd64" - id: linux-arm64 goos: linux goarch: arm64 @@ -73,13 +91,13 @@ dockers: - "--label=org.opencontainers.image.source={{ .GitURL }}" - "--platform=linux/arm64" image_templates: - - "ghcr.io/kluctl/kluctl:{{ .Tag }}-arm64" + - "ghcr.io/kluctl/kluctl:v{{ .Version }}-arm64" docker_manifests: - name_template: ghcr.io/kluctl/kluctl:{{ .Tag }} image_templates: - - "ghcr.io/kluctl/kluctl:{{ .Tag }}-amd64" - - "ghcr.io/kluctl/kluctl:{{ .Tag }}-arm64" + - "ghcr.io/kluctl/kluctl:v{{ .Version }}-amd64" + - "ghcr.io/kluctl/kluctl:v{{ .Version }}-arm64" brews: - name: kluctl From cf56e5a8afaadbd83c121439a6276bde9863eb16 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 22 Apr 2022 10:05:41 +0200 Subject: [PATCH 0695/2916] build: Add source to releases --- .goreleaser.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 6fb8940d7..c29d0d594 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -42,6 +42,9 @@ archives: format: zip files: - none* +source: + enabled: true + name_template: '{{ .ProjectName }}_v{{ .Version }}_source_code' sboms: - id: source artifacts: source From e0157fcb8548b66d5e03f8640ee9c912fbc13104 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 22 Apr 2022 10:51:35 +0200 Subject: [PATCH 0696/2916] build: Put binaries below ./bin when using make build --- .gitignore | 2 +- Makefile | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 6cebf73ac..3158e3eb0 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,6 @@ /kluctl /kluctl.exe /e2e.test* -/out +/bin /reports dist/ diff --git a/Makefile b/Makefile index 386304320..6372eb9d5 100644 --- a/Makefile +++ b/Makefile @@ -32,9 +32,9 @@ check-kind: ## Checks if kind is installed ## Build: build: vendor generate build-go ## Run the complete build pipeline -build-go: ## Build your project and put the output binary in out/bin/ - mkdir -p out/bin - CGO_ENBALED=0 GO111MODULE=on $(GOCMD) build -mod vendor -o out/bin/$(BINARY_NAME) +build-go: ## Build your project and put the output binary in ./bin/ + mkdir -p ./bin + CGO_ENBALED=0 GO111MODULE=on $(GOCMD) build -mod vendor -o ./bin/$(BINARY_NAME) clean: ## Remove build related file rm -fr ./bin @@ -53,7 +53,7 @@ generate: ## Generating Python and Jinja2 support test: test-unit test-e2e ## Runs the complete test suite test-e2e: check-kubectl check-helm check-kind ## Runs the end to end tests - CGO_ENBALED=0 GO111MODULE=on $(GOCMD) test -o out/bin/$(TEST_BINARY_NAME) ./e2e + CGO_ENBALED=0 GO111MODULE=on $(GOCMD) test -o ./bin/$(TEST_BINARY_NAME) ./e2e test-unit: ## Run the unit tests of the project ifeq ($(EXPORT_RESULT), true) From 144310b777a36f033cc3d0f4a601e5f74d9cf9bd Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 22 Apr 2022 10:53:46 +0200 Subject: [PATCH 0697/2916] build: Change checksums.txt to {{ .ProjectName }}_v{{ .Version }}_checksums.txt --- .goreleaser.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index c29d0d594..e526c43db 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -51,7 +51,7 @@ sboms: documents: - "{{ .ProjectName }}_v{{ .Version }}_sbom.spdx.json" checksum: - name_template: 'checksums.txt' + name_template: '{{ .ProjectName }}_v{{ .Version }}_checksums.txt' snapshot: name_template: "{{ incpatch .Version }}-next" changelog: From c0d0fe7e9ff4e4a097d2cc09cfcc4320c46ad549 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 22 Apr 2022 11:06:25 +0200 Subject: [PATCH 0698/2916] docs: Add install script --- install/README.md | 41 ++++++++++ install/kluctl.sh | 193 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 234 insertions(+) create mode 100644 install/README.md create mode 100755 install/kluctl.sh diff --git a/install/README.md b/install/README.md new file mode 100644 index 000000000..cf80366eb --- /dev/null +++ b/install/README.md @@ -0,0 +1,41 @@ +# kluctl Installation + +Binaries for macOS and Linux AMD64 are available for download on the +[release page](https://github.com/kluctl/kluctl/releases). + +To install the latest release run: + +```bash +curl -s https://raw.githubusercontent.com/kluctl/kluctl/main/install/kluctl.sh | sudo bash +``` + +The install script does the following: +* attempts to detect your OS +* downloads and unpacks the release tar file in a temporary directory +* copies the kluctl binary to `/usr/local/bin` +* removes the temporary directory + +## Alternative installation methods + +See https://kluctl.io/getting-started/installation for alternative installation methods. + +## Build from source + +Clone the repository: + +```bash +git clone https://github.com/kluctl/kluctl +cd kluctl +``` + +Build the `kluctl` binary (requires go >= 1.17 and python >= 3.10): + +```bash +make build +``` + +Run the binary: + +```bash +./bin/kluctl -h +``` diff --git a/install/kluctl.sh b/install/kluctl.sh new file mode 100755 index 000000000..b8a34066b --- /dev/null +++ b/install/kluctl.sh @@ -0,0 +1,193 @@ +#!/usr/bin/env bash +set -e + +DEFAULT_BIN_DIR="/usr/local/bin" +BIN_DIR=${1:-"${DEFAULT_BIN_DIR}"} +GITHUB_REPO="kluctl/kluctl" + +# Helper functions for logs +info() { + echo '[INFO] ' "$@" +} + +warn() { + echo '[WARN] ' "$@" >&2 +} + +fatal() { + echo '[ERROR] ' "$@" >&2 + exit 1 +} + +# Set os, fatal if operating system not supported +setup_verify_os() { + if [[ -z "${OS}" ]]; then + OS=$(uname) + fi + case ${OS} in + Darwin) + OS=darwin + ;; + Linux) + OS=linux + ;; + *) + fatal "Unsupported operating system ${OS}" + esac +} + +# Set arch, fatal if architecture not supported +setup_verify_arch() { + if [[ -z "${ARCH}" ]]; then + ARCH=$(uname -m) + fi + case ${ARCH} in + arm64|aarch64|armv8l) + ARCH=arm64 + ;; + amd64) + ARCH=amd64 + ;; + x86_64) + ARCH=amd64 + ;; + *) + fatal "Unsupported architecture ${ARCH}" + esac +} + +# Verify existence of downloader executable +verify_downloader() { + # Return failure if it doesn't exist or is no executable + [[ -x "$(which "$1")" ]] || return 1 + + # Set verified executable as our downloader program and return success + DOWNLOADER=$1 + return 0 +} + +# Create tempory directory and cleanup when done +setup_tmp() { + TMP_DIR=$(mktemp -d -t kluctl-install.XXXXXXXXXX) + TMP_METADATA="${TMP_DIR}/kluctl.json" + TMP_HASH="${TMP_DIR}/kluctl.hash" + TMP_BIN="${TMP_DIR}/kluctl.tar.gz" + cleanup() { + local code=$? + set +e + trap - EXIT + rm -rf "${TMP_DIR}" + exit ${code} + } + trap cleanup INT EXIT +} + +# Find version from Github metadata +get_release_version() { + if [[ -n "${kluctl_VERSION}" ]]; then + SUFFIX_URL="tags/v${kluctl_VERSION}" + else + SUFFIX_URL="latest" + fi + + METADATA_URL="https://api.github.com/repos/${GITHUB_REPO}/releases/${SUFFIX_URL}" + + info "Downloading metadata ${METADATA_URL}" + download "${TMP_METADATA}" "${METADATA_URL}" + + VERSION_KLUCTL=$(grep '"tag_name":' "${TMP_METADATA}" | sed -E 's/.*"([^"]+)".*/\1/' | cut -c 2-) + if [[ -n "${VERSION_KLUCTL}" ]]; then + info "Using ${VERSION_KLUCTL} as release" + else + fatal "Unable to determine release version" + fi +} + +# Download from file from URL +download() { + [[ $# -eq 2 ]] || fatal 'download needs exactly 2 arguments' + + case $DOWNLOADER in + curl) + curl -o "$1" -sfL "$2" + ;; + wget) + wget -qO "$1" "$2" + ;; + *) + fatal "Incorrect executable '${DOWNLOADER}'" + ;; + esac + + # Abort if download command failed + [[ $? -eq 0 ]] || fatal 'Download failed' +} + +# Download hash from Github URL +download_hash() { + HASH_URL="https://github.com/${GITHUB_REPO}/releases/download/v${VERSION_KLUCTL}/kluctl_v${VERSION_KLUCTL}_checksums.txt" + + info "Downloading hash ${HASH_URL}" + download "${TMP_HASH}" "${HASH_URL}" + HASH_EXPECTED=$(grep " kluctl_v${VERSION_KLUCTL}_${OS}_${ARCH}.tar.gz$" "${TMP_HASH}") + HASH_EXPECTED=${HASH_EXPECTED%%[[:blank:]]*} +} + +# Download binary from Github URL +download_binary() { + BIN_URL="https://github.com/${GITHUB_REPO}/releases/download/v${VERSION_KLUCTL}/kluctl_v${VERSION_KLUCTL}_${OS}_${ARCH}.tar.gz" + info "Downloading binary ${BIN_URL}" + download "${TMP_BIN}" "${BIN_URL}" +} + +compute_sha256sum() { + cmd=$(which sha256sum shasum | head -n 1) + case $(basename "$cmd") in + sha256sum) + sha256sum "$1" | cut -f 1 -d ' ' + ;; + shasum) + shasum -a 256 "$1" | cut -f 1 -d ' ' + ;; + *) + fatal "Can not find sha256sum or shasum to compute checksum" + ;; + esac +} + +# Verify downloaded binary hash +verify_binary() { + info "Verifying binary download" + HASH_BIN=$(compute_sha256sum "${TMP_BIN}") + HASH_BIN=${HASH_BIN%%[[:blank:]]*} + if [[ "${HASH_EXPECTED}" != "${HASH_BIN}" ]]; then + fatal "Download sha256 does not match ${HASH_EXPECTED}, got ${HASH_BIN}" + fi +} + +# Setup permissions and move binary +setup_binary() { + chmod 755 "${TMP_BIN}" + info "Installing kluctl to ${BIN_DIR}/kluctl" + tar -xzof "${TMP_BIN}" -C "${TMP_DIR}" + + local CMD_MOVE="mv -f \"${TMP_DIR}/kluctl\" \"${BIN_DIR}\"" + if [[ -w "${BIN_DIR}" ]]; then + eval "${CMD_MOVE}" + else + eval "sudo ${CMD_MOVE}" + fi +} + +# Run the install process +{ + setup_verify_os + setup_verify_arch + verify_downloader curl || verify_downloader wget || fatal 'Can not find curl or wget for downloading files' + setup_tmp + get_release_version + download_hash + download_binary + verify_binary + setup_binary +} \ No newline at end of file From 1539b364c05fcd1ef106f947919fe6c19393ae5a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 22 Apr 2022 11:30:59 +0200 Subject: [PATCH 0699/2916] build: Add helm to docker image --- .goreleaser.yaml | 4 ++-- Dockerfile | 16 ++++++++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index e526c43db..7ec26ccc2 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -72,7 +72,7 @@ dockers: goarch: amd64 build_flag_templates: - "--pull" - - "--build-arg=ARCH=linux/amd64" + - "--build-arg=ARCH=linux-amd64" - "--label=org.opencontainers.image.created={{ .Date }}" - "--label=org.opencontainers.image.name={{ .ProjectName }}" - "--label=org.opencontainers.image.revision={{ .FullCommit }}" @@ -86,7 +86,7 @@ dockers: goarch: arm64 build_flag_templates: - "--pull" - - "--build-arg=ARCH=linux/arm64" + - "--build-arg=ARCH=linux-arm64" - "--label=org.opencontainers.image.created={{ .Date }}" - "--label=org.opencontainers.image.name={{ .ProjectName }}" - "--label=org.opencontainers.image.revision={{ .FullCommit }}" diff --git a/Dockerfile b/Dockerfile index 8e44c42ff..991b24a50 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,15 @@ +FROM alpine:3.15 as builder + +RUN apk add --no-cache ca-certificates curl + +ARG ARCH=linux-amd64 + +ENV HELM_VERSION=v3.8.2 +RUN wget -O helm.tar.gz https://get.helm.sh/helm-$HELM_VERSION-$ARCH.tar.gz && \ + tar xzf helm.tar.gz && \ + mv $ARCH/helm / + FROM alpine -COPY kluctl / -ENTRYPOINT ["/kluctl"] +COPY --from=builder /helm /usr/bin +COPY kluctl /usr/bin/ +ENTRYPOINT ["/usr/bin/kluctl"] From 32d92d7c887c60ae1bc2e54edfa01b67f12fa26a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 22 Apr 2022 12:22:56 +0200 Subject: [PATCH 0700/2916] docs: No need to use sudo when running installer script It does this internally when needed. --- install/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/README.md b/install/README.md index cf80366eb..422e42d38 100644 --- a/install/README.md +++ b/install/README.md @@ -6,7 +6,7 @@ Binaries for macOS and Linux AMD64 are available for download on the To install the latest release run: ```bash -curl -s https://raw.githubusercontent.com/kluctl/kluctl/main/install/kluctl.sh | sudo bash +curl -s https://raw.githubusercontent.com/kluctl/kluctl/main/install/kluctl.sh | bash ``` The install script does the following: From f1e4f643c98b3c11dc75884480a26dd8f0763729 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 22 Apr 2022 16:54:11 +0200 Subject: [PATCH 0701/2916] Update README.md with text from https://kluctl.io/docs --- README.md | 58 ++++++++++++++++++++++++++++++++--------------- install/README.md | 2 +- 2 files changed, 41 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 6f71b363c..099728ed9 100644 --- a/README.md +++ b/README.md @@ -2,30 +2,52 @@ kluctl -kluctl is the missing glue that puts together your (and any third-party) deployments into one large declarative + + +Kluctl is the missing glue that puts together your (and any third-party) deployments into one large declarative Kubernetes deployment, while making it fully manageable (deploy, diff, prune, delete, ...) via one unified command line interface. -kluctl tries to be as flexible as possible, while remaining as simple as possible. It reuses established -tools (e.g. kustomize and Helm), making it possible to re-use a large set of available third-party deployments. +Kluctl tries to be as flexible as possible, while remaining as simple as possible. It reuses established +tools (e.g. Kustomize and Helm), making it possible to re-use a large set of available third-party deployments. -kluctl works completely locally, on the same machines that `kubectl` runs on. kluctl does not rely on any operators or other cluster-side components. -As long as the target cluster's kubeconfig is present locally, you are able to execute it from everywhere, including your -CI/CD pipelines or your laptop. +Kluctl is centered around "targets", which can be a cluster or a specific environment (e.g. test, dev, prod, ...) on one +or multiple clusters. Targets can be deployed, diffed, pruned, deleted, and so on. The idea is to have the same set of +operations for every target, no matter how simple or complex the deployment and/or target is. -Use kluctl to: -* Organize large and complex deployments, consisting of many Helm charts and kustomize deployments -* Do the same for small and simple deployments, as the overhead is small -* Always know what the state of your deployments is by being able to run diffs on the whole deployment -* Always know what you actually changed after performing a deployment -* Keep your clusters clean by issuing regular prune calls -* Deploy the same deployment to multiple environments (dev, test, prod, ...), with flexible differences in configuration -* Manage multiple target clusters (in multiple clouds or bare-metal if you want) -* Manage encrypted secrets for multiple target environments and clusters (based on [Sealed Secrets](https://github.com/bitnami-labs/sealed-secrets)) -* Integrate it into your CI/CD pipelines and avoid putting too much logic into your shell scripts +Kluctl does not depend on external operators/controllers and allows to use the same deployment wherever you want, +as long as access to the kluctl project and clusters is available. This means, that you can use it from your +local machine, from your CI/CD pipelines or any automation platform/system that allows to call custom tools. -![](https://kluctl.io/asciinema/kluctl.gif) +Flux support is in development and will come soon. + +## Installation + +See [installation](./install). ## Documentation -Documentation can be found here: https://kluctl.io +Documentation can be found here: https://kluctl.io/docs + +## Kluctl in Short + + + +| | | +| --- | --- | +| 💪 Kluctl handles all your deployments | You can manage all your deployments with Kluctl, including infrastructure related and your applications. | +| 🪶 Complex or simple, all the same | You can manage complex and simple deployments with Kluctl. Simple deployments are lightweight while complex deployment are easily manageable. | +| 🤖 Native git support | Kluctl has native Git support integrated, meaning that it can easily deploy remote Kluctl projects or externalize parts (e.g. cluster configs) of your Kluctl project. | +| 🪐 Multiple environments | Deploy the same deployment to multiple environments (dev, test, prod, ...), with flexible differences in configuration. | +| 🌌 Multiple clusters | Manage multiple target clusters (in multiple clouds or bare-metal if you want). | +| 🔩 Configuration and Templating | Kluctl allows to use templating in nearly all places, making it easy to have dynamic configuration. | +| ⎈ Helm and Kustomize | The Helm and Kustomize integrations allow you to reuse plenty of third-party charts and kustomizations. | +| 🔍 See what's different | Always know what the state of your deployments is by being able to run diffs on the whole deployment. | +| 🔎 See what happened | Always know what you actually changed after performing a deployment. | +| 💥 Know what went wrong | Kluctl will show you what part of your deployment failed and why. | +| 👐 Live and let live | Kluctl tries to not interfere with any other tools or operators. This is possible due to it's use of server-side-apply. | +| 🧹 Keep it clean | Keep your clusters clean by issuing regular prune calls. | +| 🔐 Encrypted Secrets | Manage encrypted secrets for multiple target environments and clusters. | + +## Demo +![](https://kluctl.io/asciinema/kluctl.gif) diff --git a/install/README.md b/install/README.md index 422e42d38..09f38f6e3 100644 --- a/install/README.md +++ b/install/README.md @@ -17,7 +17,7 @@ The install script does the following: ## Alternative installation methods -See https://kluctl.io/getting-started/installation for alternative installation methods. +See https://kluctl.io/docs/installation for alternative installation methods. ## Build from source From f8823bc0abda98dfb25d1171137613a368fc80de Mon Sep 17 00:00:00 2001 From: Mathias Gebbe Date: Sun, 24 Apr 2022 00:33:05 +0200 Subject: [PATCH 0702/2916] fix: runtime error on seal command when missing SealedSecret (nil) --- pkg/deployment/commands/seal.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/deployment/commands/seal.go b/pkg/deployment/commands/seal.go index 3a91f8b87..cc255a91c 100644 --- a/pkg/deployment/commands/seal.go +++ b/pkg/deployment/commands/seal.go @@ -20,7 +20,7 @@ func NewSealCommand(c *deployment.DeploymentCollection) *SealCommand { } func (cmd *SealCommand) Run(sealer *seal.Sealer) error { - if cmd.c.Project.Config.SealedSecrets.OutputPattern == nil { + if cmd.c.Project.Config.SealedSecrets == nil || cmd.c.Project.Config.SealedSecrets.OutputPattern == nil { return fmt.Errorf("sealedSecrets.outputPattern is not defined") } From 6a723d1e5fe0ab667065124bf1fcdaf1f743466a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 26 Apr 2022 11:36:21 +0200 Subject: [PATCH 0703/2916] fix: Allow to save metadata externally for all commands --- cmd/kluctl/args/project.go | 1 + cmd/kluctl/commands/cmd_archive.go | 5 ++--- cmd/kluctl/commands/utils.go | 12 ++++++++++++ pkg/kluctl_project/load.go | 30 +++++++++++++++--------------- pkg/types/metadata.go | 2 +- 5 files changed, 31 insertions(+), 19 deletions(-) diff --git a/cmd/kluctl/args/project.go b/cmd/kluctl/args/project.go index 9153d27f8..8b6f0d583 100644 --- a/cmd/kluctl/args/project.go +++ b/cmd/kluctl/args/project.go @@ -10,6 +10,7 @@ type ProjectFlags struct { LocalSealedSecrets string `group:"project" help:"Local sealed-secrets directory. Overrides the project from .kluctl.yml" type:"existingdir"` FromArchive string `group:"project" help:"Load project (.kluctl.yml, cluster, ...) from archive. Given path can either be an archive file or a directory with the extracted contents." type:"existing"` FromArchiveMetadata string `group:"project" help:"Specify where to load metadata (targets, ...) from. If not specified, metadata is assumed to be part of the archive." type:"existingfile"` + OutputMetadata string `group:"project" help:"Specify the output path for the project metadata to be written to. When used with the 'archive' command, it will also cause the archive to not include the metadata.yaml file." type:"file"` Cluster string `group:"project" help:"Specify/Override cluster"` } diff --git a/cmd/kluctl/commands/cmd_archive.go b/cmd/kluctl/commands/cmd_archive.go index 90977ace4..bcbeb9f42 100644 --- a/cmd/kluctl/commands/cmd_archive.go +++ b/cmd/kluctl/commands/cmd_archive.go @@ -8,8 +8,7 @@ import ( type archiveCmd struct { args.ProjectFlags - OutputArchive string `group:"misc" help:"Path to .tgz to write project to." type:"path"` - OutputMetadata string `group:"misc" help:"Path to .yml to write metadata to. If not specified, metadata is written into the archive."` + OutputArchive string `group:"misc" help:"Path to .tgz to write project to." type:"path"` } func (cmd *archiveCmd) Help() string { @@ -18,6 +17,6 @@ func (cmd *archiveCmd) Help() string { func (cmd *archiveCmd) Run() error { return withKluctlProjectFromArgs(cmd.ProjectFlags, func(p *kluctl_project.KluctlProjectContext) error { - return p.CreateTGZArchive(cmd.OutputArchive, cmd.OutputMetadata) + return p.CreateTGZArchive(cmd.OutputArchive, cmd.ProjectFlags.OutputMetadata == "") }) } diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index bfcbf341e..829ab5bbb 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -8,6 +8,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/jinja2" "github.com/kluctl/kluctl/v2/pkg/kluctl_project" "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/yaml" "io/ioutil" "os" ) @@ -49,6 +50,17 @@ func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, cb func(p *kluctl if err != nil { return err } + if projectFlags.OutputMetadata != "" { + md := p.GetMetadata() + b, err := yaml.WriteYamlBytes(md) + if err != nil { + return err + } + err = ioutil.WriteFile(projectFlags.OutputMetadata, b, 0o640) + if err != nil { + return err + } + } return cb(p) } diff --git a/pkg/kluctl_project/load.go b/pkg/kluctl_project/load.go index 82e3eb165..48dffe726 100644 --- a/pkg/kluctl_project/load.go +++ b/pkg/kluctl_project/load.go @@ -206,7 +206,7 @@ func loadKluctlProjectFromArchive(args LoadKluctlProjectArgs, tmpDir string, j2 metdataPath = yaml.FixPathExt(filepath.Join(dir, "metadata.yml")) } - var metadata types2.ArchiveMetadata + var metadata types2.ProjectMetadata err := yaml.ReadYamlFile(metdataPath, &metadata) if err != nil { return nil, err @@ -223,7 +223,15 @@ func loadKluctlProjectFromArchive(args LoadKluctlProjectArgs, tmpDir string, j2 return p, nil } -func (c *KluctlProjectContext) CreateTGZArchive(archivePath string, metadataPath string) error { +func (c *KluctlProjectContext) GetMetadata() *types2.ProjectMetadata { + md := &types2.ProjectMetadata{ + InvolvedRepos: c.involvedRepos, + Targets: c.DynamicTargets, + } + return md +} + +func (c *KluctlProjectContext) CreateTGZArchive(archivePath string, embedMetadata bool) error { f, err := os.Create(archivePath) if err != nil { return err @@ -248,23 +256,15 @@ func (c *KluctlProjectContext) CreateTGZArchive(archivePath string, metadataPath return h, nil } - md := types2.ArchiveMetadata{ - InvolvedRepos: c.involvedRepos, - Targets: c.DynamicTargets, - } - mdStr, err := yaml.WriteYamlBytes(md) - if err != nil { - return err - } - - if metadataPath != "" { - err = ioutil.WriteFile(metadataPath, mdStr, 0o666) + if embedMetadata { + md := c.GetMetadata() + mdStr, err := yaml.WriteYamlBytes(md) if err != nil { return err } - } else { + err = tw.WriteHeader(&tar.Header{ - Name: "metadata.yml", + Name: "metadata.yaml", Size: int64(len(mdStr)), Mode: 0o666 | tar.TypeReg, }) diff --git a/pkg/types/metadata.go b/pkg/types/metadata.go index f17fcde5f..638f4bca9 100644 --- a/pkg/types/metadata.go +++ b/pkg/types/metadata.go @@ -5,7 +5,7 @@ type InvolvedRepo struct { Refs map[string]string `yaml:"refs"` } -type ArchiveMetadata struct { +type ProjectMetadata struct { InvolvedRepos map[string][]InvolvedRepo `yaml:"involvedRepos"` Targets []*DynamicTarget `yaml:"targets"` } From e29239fbabba3753e1adc0e693c216a7476fe5aa Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 26 Apr 2022 16:19:35 +0200 Subject: [PATCH 0704/2916] fix: Use $TMPDIR/kluctl-workdir as tmp base dir for jinja2 renderer --- pkg/jinja2/python_src/jinja2_cache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/jinja2/python_src/jinja2_cache.py b/pkg/jinja2/python_src/jinja2_cache.py index 669c0a2dc..8d37ec2d4 100644 --- a/pkg/jinja2/python_src/jinja2_cache.py +++ b/pkg/jinja2/python_src/jinja2_cache.py @@ -11,7 +11,7 @@ from jinja2.bccache import Bucket def get_tmp_base_dir(): - dir = os.path.join(tempfile.gettempdir(), "kluctl") + dir = os.path.join(tempfile.gettempdir(), "kluctl-workdir") os.makedirs(dir, exist_ok=True) return dir From 7f31f3d156b3e8cb4aa824851350d30cf8cf0c07 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 26 Apr 2022 11:44:28 +0200 Subject: [PATCH 0705/2916] build: Put generate logic into go program instead of fully relying on go:generate This is needed to allow other applications depend on kluctl. These would still have to run the generate programs, but at least it's possible then. --- pkg/jinja2/embed/python_src.dummy | 0 pkg/jinja2/generate/main.go | 56 ++++++++ pkg/jinja2/jinja2.go | 3 - pkg/jinja2/python_src/dummy.go | 1 + pkg/jinja2/python_src/pip-wheel.sh | 14 -- pkg/jinja2/source.go | 8 +- pkg/jinja2/tools.go | 9 ++ pkg/python/embed.go | 4 +- pkg/python/embed/python-darwin-amd64.dummy | 0 pkg/python/embed/python-darwin-arm64.dummy | 0 pkg/python/embed/python-linux-amd64.dummy | 0 pkg/python/embed/python-linux-arm64.dummy | 0 pkg/python/embed/python-windows-amd64.dummy | 0 pkg/python/embed_darwin_amd64.go | 5 +- pkg/python/embed_darwin_arm64.go | 5 +- pkg/python/embed_linux_amd64.go | 5 +- pkg/python/embed_linux_arm64.go | 5 +- pkg/python/embed_windows_amd64.go | 5 +- pkg/python/{download => generate}/main.go | 70 ++++++++-- pkg/python/tools.go | 8 ++ pkg/utils/embed_util/extract.go | 42 ++++-- pkg/utils/embed_util/packer/main.go | 113 --------------- pkg/utils/embed_util/packer/packer.go | 146 ++++++++++++++++++++ 23 files changed, 324 insertions(+), 175 deletions(-) create mode 100644 pkg/jinja2/embed/python_src.dummy create mode 100644 pkg/jinja2/generate/main.go create mode 100644 pkg/jinja2/python_src/dummy.go delete mode 100755 pkg/jinja2/python_src/pip-wheel.sh create mode 100644 pkg/jinja2/tools.go create mode 100644 pkg/python/embed/python-darwin-amd64.dummy create mode 100644 pkg/python/embed/python-darwin-arm64.dummy create mode 100644 pkg/python/embed/python-linux-amd64.dummy create mode 100644 pkg/python/embed/python-linux-arm64.dummy create mode 100644 pkg/python/embed/python-windows-amd64.dummy rename pkg/python/{download => generate}/main.go (64%) create mode 100644 pkg/python/tools.go delete mode 100644 pkg/utils/embed_util/packer/main.go create mode 100644 pkg/utils/embed_util/packer/packer.go diff --git a/pkg/jinja2/embed/python_src.dummy b/pkg/jinja2/embed/python_src.dummy new file mode 100644 index 000000000..e69de29bb diff --git a/pkg/jinja2/generate/main.go b/pkg/jinja2/generate/main.go new file mode 100644 index 000000000..a767884b3 --- /dev/null +++ b/pkg/jinja2/generate/main.go @@ -0,0 +1,56 @@ +package main + +import ( + "github.com/kluctl/kluctl/v2/pkg/utils/embed_util/packer" + log "github.com/sirupsen/logrus" + "os" + "os/exec" + "path/filepath" + "strings" +) + +func main() { + pipWheel() +} + +func pipWheel() { + _ = os.RemoveAll("python_src/wheel") + _ = os.MkdirAll("python_src/wheel", 0o700) + + cmd := exec.Command("pip3", "wheel", "-r", "../requirements.txt") + cmd.Dir = "python_src/wheel" + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err := cmd.Run() + if err != nil { + log.Panic(err) + } + + wheels, err := os.ReadDir("python_src/wheel") + if err != nil { + log.Panic(err) + } + for _, w := range wheels { + if !strings.HasSuffix(w.Name(), ".whl") { + continue + } + + cmd = exec.Command("unzip", w.Name()) + cmd.Dir = "python_src/wheel" + + err = cmd.Run() + if err != nil { + log.Panic(err) + } + + err = os.Remove(filepath.Join("python_src/wheel", w.Name())) + if err != nil { + log.Panic(err) + } + } + + err = packer.Pack("embed/python_src.tar.gz", "python_src", "*") + if err != nil { + log.Panic(err) + } +} diff --git a/pkg/jinja2/jinja2.go b/pkg/jinja2/jinja2.go index 431619904..58f115727 100644 --- a/pkg/jinja2/jinja2.go +++ b/pkg/jinja2/jinja2.go @@ -13,9 +13,6 @@ import ( "sync" ) -//go:generate bash ./python_src/pip-wheel.sh -//go:generate go run ../utils/embed_util/packer python_src.tar.gz python_src * - var paralellism = 4 type Jinja2 struct { diff --git a/pkg/jinja2/python_src/dummy.go b/pkg/jinja2/python_src/dummy.go new file mode 100644 index 000000000..60d276519 --- /dev/null +++ b/pkg/jinja2/python_src/dummy.go @@ -0,0 +1 @@ +package python_src diff --git a/pkg/jinja2/python_src/pip-wheel.sh b/pkg/jinja2/python_src/pip-wheel.sh deleted file mode 100755 index 4dfe7d70a..000000000 --- a/pkg/jinja2/python_src/pip-wheel.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash - -DIR=$(cd $(dirname $0) && pwd) - -rm -rf $DIR/wheel -mkdir -p $DIR/wheel -cd $DIR/wheel - -pip3 wheel -r ../requirements.txt - -for f in *.whl; do - unzip $f - rm $f -done diff --git a/pkg/jinja2/source.go b/pkg/jinja2/source.go index 78321257f..f07623b74 100644 --- a/pkg/jinja2/source.go +++ b/pkg/jinja2/source.go @@ -8,7 +8,9 @@ import ( "path/filepath" ) -//go:embed python_src.tar.gz python_src.tar.gz.files +//go:generate go run ./generate + +//go:embed embed/python_src.* var pythonSrc embed.FS var pythonSrcExtracted string @@ -21,13 +23,13 @@ func init() { } func extractSource() (string, error) { - tgz, err := pythonSrc.Open("python_src.tar.gz") + tgz, err := pythonSrc.Open("embed/python_src.tar.gz") if err != nil { return "", err } defer tgz.Close() - fileList, err := pythonSrc.Open("python_src.tar.gz.files") + fileList, err := pythonSrc.Open("embed/python_src.tar.gz.files") if err != nil { return "", err } diff --git a/pkg/jinja2/tools.go b/pkg/jinja2/tools.go new file mode 100644 index 000000000..839cd9103 --- /dev/null +++ b/pkg/jinja2/tools.go @@ -0,0 +1,9 @@ +//go:build tools +// +build tools + +package tools + +import ( + _ "github.com/kluctl/kluctl/v2/pkg/jinja2/generate" + _ "github.com/kluctl/kluctl/v2/pkg/jinja2/python_src" +) diff --git a/pkg/python/embed.go b/pkg/python/embed.go index e2654a685..4a67eee0d 100644 --- a/pkg/python/embed.go +++ b/pkg/python/embed.go @@ -9,6 +9,8 @@ import ( "runtime" ) +//go:generate go run ./generate + var embeddedPythonPath string func init() { @@ -16,7 +18,7 @@ func init() { } func decompressPython() string { - tarName := fmt.Sprintf("python-%s-%s.tar.gz", runtime.GOOS, runtime.GOARCH) + tarName := fmt.Sprintf("embed/python-%s-%s.tar.gz", runtime.GOOS, runtime.GOARCH) tgz, err := pythonLib.Open(tarName) if err != nil { log.Panic(err) diff --git a/pkg/python/embed/python-darwin-amd64.dummy b/pkg/python/embed/python-darwin-amd64.dummy new file mode 100644 index 000000000..e69de29bb diff --git a/pkg/python/embed/python-darwin-arm64.dummy b/pkg/python/embed/python-darwin-arm64.dummy new file mode 100644 index 000000000..e69de29bb diff --git a/pkg/python/embed/python-linux-amd64.dummy b/pkg/python/embed/python-linux-amd64.dummy new file mode 100644 index 000000000..e69de29bb diff --git a/pkg/python/embed/python-linux-arm64.dummy b/pkg/python/embed/python-linux-arm64.dummy new file mode 100644 index 000000000..e69de29bb diff --git a/pkg/python/embed/python-windows-amd64.dummy b/pkg/python/embed/python-windows-amd64.dummy new file mode 100644 index 000000000..e69de29bb diff --git a/pkg/python/embed_darwin_amd64.go b/pkg/python/embed_darwin_amd64.go index 4fb6ecf9a..110c5af57 100644 --- a/pkg/python/embed_darwin_amd64.go +++ b/pkg/python/embed_darwin_amd64.go @@ -2,8 +2,5 @@ package python import "embed" -//go:generate go run ./download darwin amd64 ../../download-python/darwin/amd64 -//go:generate go run ../utils/embed_util/packer python-darwin-amd64.tar.gz ../../download-python/darwin/amd64/python/install bin lib/*.dylib lib/python3.* - -//go:embed python-darwin-amd64.tar.gz python-darwin-amd64.tar.gz.files +//go:embed embed/python-darwin-amd64.* var pythonLib embed.FS diff --git a/pkg/python/embed_darwin_arm64.go b/pkg/python/embed_darwin_arm64.go index 18aa21233..f14b56a5f 100644 --- a/pkg/python/embed_darwin_arm64.go +++ b/pkg/python/embed_darwin_arm64.go @@ -2,8 +2,5 @@ package python import "embed" -//go:generate go run ./download darwin arm64 ../../download-python/darwin/arm64 -//go:generate go run ../utils/embed_util/packer python-darwin-arm64.tar.gz ../../download-python/darwin/arm64/python/install bin lib/*.dylib lib/python3.* - -//go:embed python-darwin-arm64.tar.gz python-darwin-arm64.tar.gz.files +//go:embed embed/python-darwin-arm64.* var pythonLib embed.FS diff --git a/pkg/python/embed_linux_amd64.go b/pkg/python/embed_linux_amd64.go index 5961a767d..99838aee9 100644 --- a/pkg/python/embed_linux_amd64.go +++ b/pkg/python/embed_linux_amd64.go @@ -2,8 +2,5 @@ package python import "embed" -//go:generate go run ./download linux amd64 ../../download-python/linux/amd64 -//go:generate go run ../utils/embed_util/packer python-linux-amd64.tar.gz ../../download-python/linux/amd64/python/install bin lib/*.so* lib/python3.* - -//go:embed python-linux-amd64.tar.gz python-linux-amd64.tar.gz.files +//go:embed embed/python-linux-amd64.* var pythonLib embed.FS diff --git a/pkg/python/embed_linux_arm64.go b/pkg/python/embed_linux_arm64.go index 40be4e8b0..158c5087e 100644 --- a/pkg/python/embed_linux_arm64.go +++ b/pkg/python/embed_linux_arm64.go @@ -2,8 +2,5 @@ package python import "embed" -//go:generate go run ./download linux arm64 ../../download-python/linux/arm64 -//go:generate go run ../utils/embed_util/packer python-linux-arm64.tar.gz ../../download-python/linux/arm64/python/install bin lib/*.so* lib/python3.* - -//go:embed python-linux-arm64.tar.gz python-linux-arm64.tar.gz.files +//go:embed embed/python-linux-arm64.* var pythonLib embed.FS diff --git a/pkg/python/embed_windows_amd64.go b/pkg/python/embed_windows_amd64.go index dfa2a12a3..65e5e2c9c 100644 --- a/pkg/python/embed_windows_amd64.go +++ b/pkg/python/embed_windows_amd64.go @@ -2,8 +2,5 @@ package python import "embed" -//go:generate go run ./download windows amd64 ../../download-python/windows/amd64 -//go:generate go run ../utils/embed_util/packer python-windows-amd64.tar.gz ../../download-python/windows/amd64/python/install Lib DLLs *.dll *.exe - -//go:embed python-windows-amd64.tar.gz python-windows-amd64.tar.gz.files +//go:embed embed/python-windows-amd64.* var pythonLib embed.FS diff --git a/pkg/python/download/main.go b/pkg/python/generate/main.go similarity index 64% rename from pkg/python/download/main.go rename to pkg/python/generate/main.go index b61e93840..6c2ef6085 100644 --- a/pkg/python/download/main.go +++ b/pkg/python/generate/main.go @@ -4,11 +4,13 @@ import ( "fmt" "github.com/klauspost/compress/zstd" "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/embed_util/packer" log "github.com/sirupsen/logrus" "io/ioutil" "net/http" "os" "path/filepath" + "sync" ) const ( @@ -18,14 +20,14 @@ const ( ) var pythonDists = map[string]string{ - "linux": "unknown-linux-gnu-lto-full", - "darwin": "apple-darwin-lto-full", + "linux": "unknown-linux-gnu-lto-full", + "darwin": "apple-darwin-lto-full", "windows": "pc-windows-msvc-shared-pgo-full", } var archMapping = map[string]string{ "amd64": "x86_64", - "386": "i686", + "386": "i686", "arm64": "aarch64", } @@ -45,11 +47,49 @@ var removeLibs = []string{ "unittest", } +var downloadLock sync.Mutex + func main() { - osName := os.Args[1] - arch := os.Args[2] - extractPath := os.Args[3] + var wg sync.WaitGroup + + nixPatterns := []string{ + "bin", + "lib/*.so*", + "lib/*.dylib", + "lib/python3.*", + } + winPatterns := []string{ + "Lib", + "DLLs", + "*.dll", + "*.exe", + } + type job struct { + os string + arch string + packSubDir string + out string + patterns []string + } + jobs := []job{ + {"linux", "amd64", "python/install", "embed/python-linux-amd64.tar.gz", nixPatterns}, + {"linux", "arm64", "python/install", "embed/python-linux-arm64.tar.gz", nixPatterns}, + {"darwin", "amd64", "python/install", "embed/python-darwin-amd64.tar.gz", nixPatterns}, + {"darwin", "arm64", "python/install", "embed/python-darwin-arm64.tar.gz", nixPatterns}, + {"windows", "amd64", "python/install", "embed/python-windows-amd64.tar.gz", winPatterns}, + } + for _, j := range jobs { + j := j + wg.Add(1) + go func() { + downloadAndPack(j.os, j.arch, j.packSubDir, j.out, j.patterns) + wg.Done() + }() + } + wg.Wait() +} +func downloadAndPack(osName string, arch string, packSubdir string, out string, patterns []string) { dist, ok := pythonDists[osName] if !ok { log.Panicf("no dist for %s", osName) @@ -59,8 +99,10 @@ func main() { archiveBytes, _ := ioutil.ReadFile(downloadPath) hash := utils.Sha256Bytes(archiveBytes) + extractPath := downloadPath + ".extracted" if utils.Exists(filepath.Join(extractPath, hash)) { - log.Infof("skipping extract") + log.Infof("skipping extract of %s", extractPath) + pack(out, filepath.Join(extractPath, packSubdir), patterns) return } @@ -73,9 +115,21 @@ func main() { } _ = utils.Touch(filepath.Join(extractPath, hash)) + + pack(out, filepath.Join(extractPath, packSubdir), patterns) +} + +func pack(out string, dir string, patterns []string) { + err := packer.Pack(out, dir, patterns...) + if err != nil { + log.Panic(err) + } } func download(osName, arch, dist string) string { + downloadLock.Lock() + defer downloadLock.Unlock() + pythonArch, ok := archMapping[arch] if !ok { log.Errorf("arch %s not supported", arch) @@ -138,4 +192,4 @@ func extract(archivePath string, targetPath string) string { } return targetPath -} \ No newline at end of file +} diff --git a/pkg/python/tools.go b/pkg/python/tools.go new file mode 100644 index 000000000..48d6d8a70 --- /dev/null +++ b/pkg/python/tools.go @@ -0,0 +1,8 @@ +//go:build tools +// +build tools + +package tools + +import ( + _ "github.com/kluctl/kluctl/v2/pkg/python/generate" +) diff --git a/pkg/utils/embed_util/extract.go b/pkg/utils/embed_util/extract.go index dc38f75f4..020f28596 100644 --- a/pkg/utils/embed_util/extract.go +++ b/pkg/utils/embed_util/extract.go @@ -60,9 +60,10 @@ func ExtractTarToTmp(r io.Reader, fileListR io.Reader, targetPrefix string) (str } func checkExtractNeeded(targetPath string, fileListStr string) (bool, string, error) { - fileList := strings.Split(fileListStr, "\n") - expectedHash := fileList[0] - fileList = fileList[1:] + expectedHash, tarFilesMap, err := ReadFileList(fileListStr) + if err != nil { + return false, "", err + } if !utils.Exists(targetPath) { return true, expectedHash, nil @@ -77,6 +78,24 @@ func checkExtractNeeded(targetPath string, fileListStr string) (bool, string, er return true, expectedHash, nil } + existingFiles, err := BuildFileList(targetPath) + if err != nil { + return false, "", err + } + + for fname, size := range tarFilesMap { + if s, ok := existingFiles[fname]; !ok || s != size { + return true, expectedHash, nil + } + } + return false, expectedHash, nil +} + +func ReadFileList(fileListStr string) (string, map[string]int64, error) { + fileList := strings.Split(fileListStr, "\n") + expectedHash := fileList[0] + fileList = fileList[1:] + tarFilesMap := make(map[string]int64) for _, l := range fileList { s := strings.SplitN(l, ":", 2) @@ -84,13 +103,16 @@ func checkExtractNeeded(targetPath string, fileListStr string) (bool, string, er sh := strings.SplitN(strings.TrimSpace(s[1]), " ", 2) size, err := strconv.ParseInt(strings.TrimSpace(sh[0]), 10, 64) if err != nil { - return false, expectedHash, err + return expectedHash, tarFilesMap, err } tarFilesMap[fname] = size } + return "", tarFilesMap, nil +} +func BuildFileList(targetPath string) (map[string]int64, error) { existingFiles := make(map[string]int64) - err = filepath.Walk(targetPath, func(path string, info fs.FileInfo, err error) error { + err := filepath.Walk(targetPath, func(path string, info fs.FileInfo, err error) error { if !info.Mode().IsRegular() && info.Mode().Type() != fs.ModeSymlink && info.Mode().Type() != fs.ModeDir { return nil } @@ -107,13 +129,7 @@ func checkExtractNeeded(targetPath string, fileListStr string) (bool, string, er return nil }) if err != nil { - return false, "", err + return nil, err } - - for fname, size := range tarFilesMap { - if s, ok := existingFiles[fname]; !ok || s != size { - return true, expectedHash, nil - } - } - return false, expectedHash, nil + return existingFiles, nil } diff --git a/pkg/utils/embed_util/packer/main.go b/pkg/utils/embed_util/packer/main.go deleted file mode 100644 index 39cb9e2cf..000000000 --- a/pkg/utils/embed_util/packer/main.go +++ /dev/null @@ -1,113 +0,0 @@ -package main - -import ( - "archive/tar" - "bytes" - "compress/gzip" - "crypto/sha256" - "encoding/hex" - "fmt" - "github.com/gobwas/glob" - "github.com/kluctl/kluctl/v2/pkg/utils" - log "github.com/sirupsen/logrus" - "io/fs" - "io/ioutil" - "os" - "path/filepath" - "sort" - "strings" -) - -func main() { - out := os.Args[1] - dir := os.Args[2] - patterns := os.Args[3:] - - log.Infof("writing tar %s", out) - fileList, tgz := writeTar(dir, patterns) - - hash := sha256.Sum256(tgz) - err := ioutil.WriteFile(out, tgz, 0o600) - if err != nil { - log.Panic(err) - } - - fileListStr := strings.Join(fileList, "\n") - fileListStr = hex.EncodeToString(hash[:]) + "\n" + fileListStr - err = ioutil.WriteFile(out+".files", []byte(fileListStr), 0o600) - if err != nil { - log.Panic(err) - } -} - -func writeTar(dir string, patterns []string) ([]string, []byte) { - b := bytes.NewBuffer(nil) - gz := gzip.NewWriter(b) - t := tar.NewWriter(gz) - - var globs []glob.Glob - for _, p := range patterns { - globs = append(globs, glob.MustCompile(p, '/')) - } - - var rootNames []string - err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { - rel, err := filepath.Rel(dir, path) - if err != nil { - return err - } - match := false - for _, p := range globs { - if p.Match(rel) { - match = true - break - } - } - if match { - rootNames = append(rootNames, rel) - } - return nil - }) - sort.Strings(rootNames) - - excludes := []glob.Glob{ - glob.MustCompile("__pycache__"), - glob.MustCompile("**/__pycache__"), - glob.MustCompile("**.a"), - glob.MustCompile("**.pdb"), - } - - var fileList []string - - for _, d := range rootNames { - err := utils.AddToTar(t, filepath.Join(dir, d), d, func(h *tar.Header, size int64) (*tar.Header, error) { - for _, e := range excludes { - if e.Match(h.Name) { - return nil, nil - } - } - - hashStr, err := utils.HashTarEntry(dir, h.Name) - if err != nil { - return nil, err - } - - fileList = append(fileList, fmt.Sprintf("%s: %d %s", h.Name, size, hashStr)) - return h, nil - }) - if err != nil { - log.Panic(err) - } - } - - err = t.Close() - if err != nil { - log.Panic(err) - } - err = gz.Close() - if err != nil { - log.Panic(err) - } - - return fileList, b.Bytes() -} diff --git a/pkg/utils/embed_util/packer/packer.go b/pkg/utils/embed_util/packer/packer.go new file mode 100644 index 000000000..38846957a --- /dev/null +++ b/pkg/utils/embed_util/packer/packer.go @@ -0,0 +1,146 @@ +package packer + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "crypto/sha256" + "encoding/hex" + "fmt" + "github.com/gobwas/glob" + "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/embed_util" + log "github.com/sirupsen/logrus" + "io/fs" + "io/ioutil" + "path/filepath" + "reflect" + "strings" +) + +func Pack(out string, dir string, patterns ...string) error { + fileList, err := findFiles(dir, patterns) + if err != nil { + return err + } + + if utils.Exists(out) && utils.Exists(out+".files") { + existingFileListStr, err := ioutil.ReadFile(out + ".files") + if err == nil { + _, existingFileList, err := embed_util.ReadFileList(string(existingFileListStr)) + if err == nil { + if reflect.DeepEqual(existingFileList, fileList) { + log.Infof("Skipping packing of %s", out) + return nil + } + } + } + } + + log.Infof("writing tar %s with %d files", out, len(fileList)) + tgz, err := writeTar(dir, fileList) + if err != nil { + return err + } + + hash := sha256.Sum256(tgz) + err = ioutil.WriteFile(out, tgz, 0o600) + if err != nil { + return err + } + + var fileList2 []string + for f, l := range fileList { + fileList2 = append(fileList2, fmt.Sprintf("%s: %d", f, l)) + } + + fileListStr := strings.Join(fileList2, "\n") + fileListStr = hex.EncodeToString(hash[:]) + "\n" + fileListStr + err = ioutil.WriteFile(out+".files", []byte(fileListStr), 0o600) + return err +} + +func findFiles(dir string, patterns []string) (map[string]int64, error) { + var globs []glob.Glob + for _, p := range patterns { + globs = append(globs, glob.MustCompile(p, '/')) + } + + var rootNames []string + err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { + rel, err := filepath.Rel(dir, path) + if err != nil { + return err + } + match := false + for _, p := range globs { + if p.Match(rel) { + match = true + break + } + } + if match { + rootNames = append(rootNames, rel) + } + return nil + }) + if err != nil { + return nil, err + } + + excludes := []glob.Glob{ + glob.MustCompile("__pycache__"), + glob.MustCompile("**/__pycache__"), + glob.MustCompile("**.a"), + glob.MustCompile("**.pdb"), + } + + fileList := make(map[string]int64) + for _, d := range rootNames { + err = filepath.Walk(filepath.Join(dir, d), func(path string, info fs.FileInfo, err error) error { + if !info.Mode().IsRegular() && info.Mode().Type() != fs.ModeSymlink { + return nil + } + + relPath, err := filepath.Rel(dir, path) + if err != nil { + return err + } + for _, e := range excludes { + if e.Match(relPath) { + return nil + } + } + fileList[relPath] = info.Size() + return nil + }) + if err != nil { + return nil, err + } + } + return fileList, err +} + +func writeTar(dir string, fileList map[string]int64) ([]byte, error) { + b := bytes.NewBuffer(nil) + gz := gzip.NewWriter(b) + t := tar.NewWriter(gz) + + for f, _ := range fileList { + err := utils.AddToTar(t, filepath.Join(dir, f), f, nil) + if err != nil { + return nil, err + } + } + + err := t.Close() + if err != nil { + return nil, err + } + err = gz.Close() + if err != nil { + return nil, err + } + + return b.Bytes(), nil +} From ce1d53acdd3b532ac9775219fd4c538abf170d71 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 26 Apr 2022 16:24:39 +0200 Subject: [PATCH 0706/2916] fix: Check for version 0.0.0 in checkNewVersion --- cmd/kluctl/commands/root.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index 4027e612c..9cfa4bf32 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -81,7 +81,7 @@ func (c *cli) checkNewVersion() { if c.NoUpdateCheck { return } - if version.GetVersion() == "(devel)" { + if version.GetVersion() == "0.0.0" { return } From ed6af42e74b6f511d5cb513982020e857a14c4e7 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 26 Apr 2022 16:34:48 +0200 Subject: [PATCH 0707/2916] build: No need to run go generate with other tags --- .github/workflows/tests.yml | 1 - .goreleaser.yaml | 1 - 2 files changed, 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e0d2548be..f1e9db9ee 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -34,7 +34,6 @@ jobs: - name: Go Generate run: | go generate ./... - go generate -tags linux,darwin,windows,amd64 ./pkg/python - name: Run unit tests run: | go test ./cmd/... ./pkg/... -v diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 7ec26ccc2..8967d7158 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -2,7 +2,6 @@ before: hooks: - go mod tidy - go generate ./... - - go generate -tags linux,darwin,windows,amd64,arm64 ./pkg/python builds: - <<: &build_defaults binary: kluctl From f39ad505f91f1da4f282c10cd23c31e204a55aaa Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 26 Apr 2022 18:08:35 +0200 Subject: [PATCH 0708/2916] build: Use utils.GetTmpBaseDir() for generation/downloads --- pkg/python/generate/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/python/generate/main.go b/pkg/python/generate/main.go index 6c2ef6085..f3ffc0816 100644 --- a/pkg/python/generate/main.go +++ b/pkg/python/generate/main.go @@ -136,7 +136,7 @@ func download(osName, arch, dist string) string { os.Exit(1) } fname := fmt.Sprintf("cpython-%s+%s-%s-%s.tar.zst", pythonVersionFull, pythonStandaloneVersion, pythonArch, dist) - downloadPath := filepath.Join(os.TempDir(), fname) + downloadPath := filepath.Join(utils.GetTmpBaseDir(), fname) downloadUrl := fmt.Sprintf("https://github.com/indygreg/python-build-standalone/releases/download/%s/%s", pythonStandaloneVersion, fname) if _, err := os.Stat(downloadPath); err == nil { From 2377430e02941cf565efcf3bed0fc0ced8149719 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 26 Apr 2022 18:08:59 +0200 Subject: [PATCH 0709/2916] fix: Fix embed_util false positive extracts --- pkg/utils/embed_util/extract.go | 2 +- pkg/utils/embed_util/packer/packer.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/utils/embed_util/extract.go b/pkg/utils/embed_util/extract.go index 020f28596..16ed318ba 100644 --- a/pkg/utils/embed_util/extract.go +++ b/pkg/utils/embed_util/extract.go @@ -107,7 +107,7 @@ func ReadFileList(fileListStr string) (string, map[string]int64, error) { } tarFilesMap[fname] = size } - return "", tarFilesMap, nil + return expectedHash, tarFilesMap, nil } func BuildFileList(targetPath string) (map[string]int64, error) { diff --git a/pkg/utils/embed_util/packer/packer.go b/pkg/utils/embed_util/packer/packer.go index 38846957a..9714cedda 100644 --- a/pkg/utils/embed_util/packer/packer.go +++ b/pkg/utils/embed_util/packer/packer.go @@ -93,6 +93,7 @@ func findFiles(dir string, patterns []string) (map[string]int64, error) { glob.MustCompile("**/__pycache__"), glob.MustCompile("**.a"), glob.MustCompile("**.pdb"), + glob.MustCompile("**.pyc"), } fileList := make(map[string]int64) From 6933396b9e84b37a33927af82be919bb336f550f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 27 Apr 2022 10:57:59 +0200 Subject: [PATCH 0710/2916] build: Upgrade to go 1.18.1 --- .github/workflows/release.yml | 2 +- .github/workflows/tests.yml | 2 +- go.mod | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b91fdad0a..11588e3a3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,7 +23,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v2 with: - go-version: 1.17.9 + go-version: 1.18.1 - name: Set up Python uses: actions/setup-python@v2 with: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f1e9db9ee..932808d7f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -15,7 +15,7 @@ jobs: fetch-depth: 0 - uses: actions/setup-go@v2 with: - go-version: '1.17.9' + go-version: '1.18.1' - name: Set up Python uses: actions/setup-python@v2 with: diff --git a/go.mod b/go.mod index 56cc82387..0ff894db8 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/kluctl/kluctl/v2 -go 1.17 +go 1.18 require ( github.com/alecthomas/kong v0.5.0 From 528baa96ae4552ef02670e0302fce61030898ae4 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 27 Apr 2022 10:58:31 +0200 Subject: [PATCH 0711/2916] build: Upgrade vendor packages --- go.mod | 26 +++---- go.sum | 225 +++++++++++++-------------------------------------------- 2 files changed, 62 insertions(+), 189 deletions(-) diff --git a/go.mod b/go.mod index 0ff894db8..73da526f4 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,8 @@ go 1.18 require ( github.com/alecthomas/kong v0.5.0 - github.com/aws/aws-sdk-go v1.43.39 - github.com/bitnami-labs/sealed-secrets v0.17.4 + github.com/aws/aws-sdk-go v1.44.1 + github.com/bitnami-labs/sealed-secrets v0.17.5 github.com/docker/distribution v2.8.1+incompatible github.com/evanphx/json-patch v5.6.0+incompatible github.com/gammazero/workerpool v1.1.2 @@ -20,7 +20,7 @@ require ( github.com/hexops/gotextdiff v1.0.3 github.com/jinzhu/copier v0.3.5 github.com/kevinburke/ssh_config v1.2.0 - github.com/klauspost/compress v1.13.6 + github.com/klauspost/compress v1.15.2 github.com/mattn/go-isatty v0.0.14 github.com/mitchellh/go-ps v1.0.0 github.com/ohler55/ojg v1.14.0 @@ -33,12 +33,12 @@ require ( github.com/xanzy/ssh-agent v0.3.1 golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - golang.org/x/sys v0.0.0-20220412211240-33da011f77ad + golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b - k8s.io/api v0.24.0-beta.0 - k8s.io/apimachinery v0.24.0-beta.0 - k8s.io/client-go v0.24.0-beta.0 + k8s.io/api v0.25.0-alpha.0 + k8s.io/apimachinery v0.25.0-alpha.0 + k8s.io/client-go v0.25.0-alpha.0 sigs.k8s.io/kind v0.12.0 sigs.k8s.io/kustomize/api v0.11.4 sigs.k8s.io/kustomize/kyaml v0.13.6 @@ -46,9 +46,9 @@ require ( ) require ( - cloud.google.com/go/compute v1.5.0 // indirect + cloud.google.com/go/compute v1.6.1 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect - github.com/Azure/go-autorest/autorest v0.11.25 // indirect + github.com/Azure/go-autorest/autorest v0.11.27 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect @@ -65,7 +65,7 @@ require ( github.com/docker/docker v20.10.14+incompatible // indirect github.com/docker/docker-credential-helpers v0.6.4 // indirect github.com/emicklei/go-restful v2.15.0+incompatible // indirect - github.com/emirpasic/gods v1.17.0 // indirect + github.com/emirpasic/gods v1.18.1 // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/fatih/color v1.13.0 // indirect github.com/gammazero/deque v0.1.1 // indirect @@ -81,7 +81,7 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/btree v1.0.1 // indirect - github.com/google/gnostic v0.6.8 // indirect + github.com/google/gnostic v0.6.9 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect @@ -102,7 +102,7 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2-0.20211117181255-693428a734f5 // indirect - github.com/pelletier/go-toml v1.9.4 // indirect + github.com/pelletier/go-toml v1.9.5 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect @@ -112,7 +112,7 @@ require ( github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/xlab/treeprint v1.1.0 // indirect go.starlark.net v0.0.0-20220328144851-d1966c6b9fcd // indirect - golang.org/x/net v0.0.0-20220412020605-290c469a71a5 // indirect + golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect diff --git a/go.sum b/go.sum index 2acb29ed5..fe612bf11 100644 --- a/go.sum +++ b/go.sum @@ -28,7 +28,6 @@ cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM= cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= -cloud.google.com/go v0.100.2 h1:t9Iw5QH5v4XtlEQaCtUY7x6sCABps8sW0acw7e2WQ6Y= cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= @@ -38,8 +37,10 @@ cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4g cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= -cloud.google.com/go/compute v1.5.0 h1:b1zWmYuuHz7gO9kDcM/EpHGr06UgsYNRpNJzI2kFiLM= cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= +cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= +cloud.google.com/go/compute v1.6.1 h1:2sMmt8prCn7DPaG4Pmh0N3Inmc8cT8ae5k1M6VJ9Wqc= +cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= @@ -61,8 +62,8 @@ github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= -github.com/Azure/go-autorest/autorest v0.11.25 h1:yp+V8DGur2aIUE87ebP8twPLz6k68jtJTlg61mEoByA= -github.com/Azure/go-autorest/autorest v0.11.25/go.mod h1:7l8ybrIdUmGqZMTD0sRtAr8NvbHjfofbf8RSP2q7w7U= +github.com/Azure/go-autorest/autorest v0.11.27 h1:F3R3q42aWytozkV8ihzcgMO4OA4cuqr3bNlsEuF6//A= +github.com/Azure/go-autorest/autorest v0.11.27/go.mod h1:7l8ybrIdUmGqZMTD0sRtAr8NvbHjfofbf8RSP2q7w7U= github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= @@ -85,7 +86,6 @@ github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= -github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= @@ -116,16 +116,12 @@ github.com/ProtonMail/go-crypto v0.0.0-20220407094043-a94812496cf5/go.mod h1:z4/ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= -github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= -github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= -github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= -github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/alecthomas/kong v0.5.0 h1:u8Kdw+eeml93qtMZ04iei0CFYve/WPcA5IFh+9wSskE= github.com/alecthomas/kong v0.5.0/go.mod h1:uzxf/HUh0tj43x1AyJROl3JT7SgsZ5m+icOv1csRhc0= github.com/alecthomas/repr v0.0.0-20210801044451-80ca428c5142 h1:8Uy0oSf5co/NZXje7U1z8Mpep++QJOldL2hs/sBQf48= @@ -134,15 +130,12 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -151,24 +144,18 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= -github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.43.39 h1:5W8pton/8OuS5hpbAkzfr7e+meAAFkK7LsUehB39L3I= -github.com/aws/aws-sdk-go v1.43.39/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= -github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/aws/aws-sdk-go v1.44.1 h1:w34ZmPT6K4NTd7Yap1P7SLXPTii0ABmBz3KEh4KJdKc= +github.com/aws/aws-sdk-go v1.44.1/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= -github.com/bitnami-labs/flagenv v0.0.0-20190607135054-a87af7a1d6fc/go.mod h1:OeW4NPgFPO7+t8q1Vn2Yv+rkO+4kEQzlDskwm7C7PXs= -github.com/bitnami-labs/pflagenv v0.0.0-20190702160147-b4d9f048d98f/go.mod h1:Lw3ejf6HTt4DqBIAXlkOIvFjnpj8Zq+zD/UtH29ILFA= -github.com/bitnami-labs/sealed-secrets v0.17.4 h1:DFZCyR8ntNKINRaneKPBrnEXnejWz48fE8htx83w0yQ= -github.com/bitnami-labs/sealed-secrets v0.17.4/go.mod h1:xL3ifFPr9lIS6QoCfqj8shGgLriGjQke5o/X4NFUZfE= +github.com/bitnami-labs/sealed-secrets v0.17.5 h1:v5ENZRSrgog3GnFr8fWfVtrUTPlZlNlbsjaro9mn6YY= +github.com/bitnami-labs/sealed-secrets v0.17.5/go.mod h1:ZgGUqKixr/SRpsG8LVXnuneyZG7DOX/+eCRvXi8SVjo= github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= @@ -180,8 +167,6 @@ github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx2 github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= -github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -200,7 +185,6 @@ github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJ github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= -github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -213,7 +197,6 @@ github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= -github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= @@ -358,19 +341,14 @@ github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZ github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= -github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= -github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.15.0+incompatible h1:8KpYO/Xl/ZudZs5RNOEhWMBY4hmzlZhhRd9cu+jrZP4= github.com/emicklei/go-restful v2.15.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= -github.com/emirpasic/gods v1.17.0 h1:qOswSAaPBdCkBIAXmvwe0V9mv4UtNQHN3zxRHmw3sKQ= -github.com/emirpasic/gods v1.17.0/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= -github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -398,8 +376,6 @@ github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= -github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -435,10 +411,8 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2 github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -453,7 +427,6 @@ github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL9 github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= -github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= @@ -471,7 +444,6 @@ github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-playground/validator/v10 v10.10.1 h1:uA0+amWMiglNZKZ9FJRKUAe9U3RX91eVn1JYXMWt7ig= github.com/go-playground/validator/v10 v10.10.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= -github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= @@ -484,11 +456,9 @@ github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= -github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= @@ -533,16 +503,14 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= -github.com/google/gnostic v0.6.8 h1:bT56GPYBWh1tvBuBEd94qcS3+60b+y0HQur0ITkGuCk= -github.com/google/gnostic v0.6.8/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E= +github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0= +github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -592,15 +560,13 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= +github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= -github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= -github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= @@ -615,10 +581,8 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -640,7 +604,6 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.4.0 h1:aAQzgqIrRKRa7w75CKpbBxYsmUoPjzVm1W59ca1L0J4= github.com/hashicorp/go-version v1.4.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= @@ -661,7 +624,6 @@ github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpT github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -673,7 +635,6 @@ github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= @@ -683,7 +644,6 @@ github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg= github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= @@ -691,10 +651,8 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfC github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -704,7 +662,6 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= @@ -714,8 +671,9 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.15.2 h1:3WH+AG7s2+T8o3nrM/8u2rdqUEcQhmga7smjrT41nAw= +github.com/klauspost/compress v1.15.2/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -734,10 +692,7 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= -github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= -github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= -github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -790,7 +745,6 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= -github.com/mkmik/multierror v0.3.0/go.mod h1:wjBYXRpDhh+8mIp+iLBOq0kZ3Y4ICTncojwvP8LUYLQ= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= @@ -813,44 +767,31 @@ github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8m github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= -github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= -github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= -github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= -github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= -github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= -github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/ohler55/ojg v1.14.0 h1:DyHomsCwofNswmKj7BLMdx51xnKbXxgIo1rVWCaBcNk= github.com/ohler55/ojg v1.14.0/go.mod h1:3+GH+0PggMKocQtbZCrFifal3yRpHiBT4QUkxFJI6e8= -github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= -github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA= github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= -github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= @@ -877,35 +818,22 @@ github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mo github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= -github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= -github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= -github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= -github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= -github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= -github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= -github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= -github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -914,34 +842,25 @@ github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSg github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= -github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU= github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -954,8 +873,6 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/r3labs/diff/v2 v2.15.1 h1:EOrVqPUzi+njlumoqJwiS/TgGgmZo83619FNDB9xQUg= github.com/r3labs/diff/v2 v2.15.1/go.mod h1:I8noH9Fc2fjSaMxqF3G2lhDdC0b+JXCfyx85tWFM9kc= -github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= @@ -969,7 +886,6 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= -github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= @@ -990,7 +906,6 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1 github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= @@ -1009,7 +924,6 @@ github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v0.0.0-20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -1020,9 +934,6 @@ github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= -github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= -github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= -github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -1042,7 +953,6 @@ github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= -github.com/throttled/throttled v2.2.2+incompatible/go.mod h1:0BjlrEGQmvxps+HuXLsyRdqpSRvJpq0PNIsOtqP9Nos= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= @@ -1085,7 +995,6 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= @@ -1093,7 +1002,6 @@ github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= -go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= @@ -1102,8 +1010,6 @@ go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3 go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs= go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= -go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= -go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -1117,14 +1023,10 @@ go.starlark.net v0.0.0-20220328144851-d1966c6b9fcd h1:Uo/x0Ir5vQJ+683GXB9Ug+4fcj go.starlark.net v0.0.0-20220328144851-d1966c6b9fcd/go.mod h1:t3mmBBPzAVvK0L0n1drDmrQsJ8FoIx4INCqVMTr/Zo0= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -1152,9 +1054,7 @@ golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= @@ -1199,7 +1099,6 @@ golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -1248,11 +1147,13 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220412020605-290c469a71a5 h1:bRb386wvrE+oBNdF1d/Xh9mQrfQ4ecYhW5qJ5GvTGT4= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1271,6 +1172,8 @@ golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 h1:OSnWWcOd/CtWQC2cYSBgbTSJv3ciqd8r54ySIW2y3RE= golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1292,7 +1195,6 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1325,7 +1227,6 @@ golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1347,7 +1248,6 @@ golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1362,7 +1262,6 @@ golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1384,10 +1283,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1398,10 +1295,12 @@ golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc= +golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 h1:EH1Deb8WZJ0xc0WK//leUHXcX9aLE5SymusoTmMZye8= golang.org/x/term v0.0.0-20220411215600-e5f449aeb171/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1421,16 +1320,13 @@ golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220411224347-583f2d630306 h1:+gHMid33q6pen7kv9xvT+JRinntgeXO2AeZVd0AWD3w= golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -1448,8 +1344,6 @@ golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1458,7 +1352,6 @@ golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -1491,7 +1384,6 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.6-0.20210820212750-d4cc65f0b2ff/go.mod h1:YD9qOF0M9xpSpdWTBbzEl5e/RnCefISl8E5Noe10jFM= golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1499,11 +1391,7 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f h1:GGU+dLjvlC3qDwqYgL6UgRmHXhOOgns0bZu2Ty5mm6U= golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= -gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= -gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= -google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -1540,8 +1428,10 @@ google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFd google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= +google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= +google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= +google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= @@ -1556,7 +1446,6 @@ google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= -google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= @@ -1624,14 +1513,18 @@ google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ6 google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= +google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= @@ -1660,6 +1553,7 @@ google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9K google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -1688,7 +1582,6 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= @@ -1722,7 +1615,6 @@ gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81 gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= -honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1733,25 +1625,21 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= -k8s.io/api v0.23.4/go.mod h1:i77F4JfyNNrhOjZF7OwwNJS5Y1S9dpwvb9iYRYRczfI= -k8s.io/api v0.24.0-beta.0 h1:7knNqNYI1Az5hWcebdyUff4ETyCZkvmUT1N2hi/qS/Y= -k8s.io/api v0.24.0-beta.0/go.mod h1:D7w5dDA57yCeRJnl0vPuRj6KBAwWYxea4Dwo5kgJGIY= +k8s.io/api v0.25.0-alpha.0 h1:BiYeMLWoLcGGWE46gdnlwluFa23+Hr3I2Qp8U6c2wYY= +k8s.io/api v0.25.0-alpha.0/go.mod h1:sOibYBePcsE/DBjbbi+Z+FCG9lFPR7xuKSR2r6RTCNs= k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= -k8s.io/apimachinery v0.23.4/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM= -k8s.io/apimachinery v0.24.0-beta.0 h1:69KiS/m3i2oi3FaCVX6whePxOelsJkhIfO0J5fGDYv8= -k8s.io/apimachinery v0.24.0-beta.0/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= +k8s.io/apimachinery v0.25.0-alpha.0 h1:gAzcXIp+FkB3w8+m34na2qxSScwQWKtryRU8JfkS/NU= +k8s.io/apimachinery v0.25.0-alpha.0/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= -k8s.io/client-go v0.23.4/go.mod h1:PKnIL4pqLuvYUK1WU7RLTMYKPiIh7MYShLshtRY9cj0= -k8s.io/client-go v0.24.0-beta.0 h1:ISWwVXNtOr2f1O5afJGi66vxAzC6Gb/3+VWlz4WseFc= -k8s.io/client-go v0.24.0-beta.0/go.mod h1:D4rgRqnNPdFCFMMrcCqCOAouzIwJkPuKXr3zWThEExM= -k8s.io/code-generator v0.23.4/go.mod h1:S0Q1JVA+kSzTI1oUvbKAxZY/DYbA/ZUb4Uknog12ETk= +k8s.io/client-go v0.25.0-alpha.0 h1:IwIWODi6Uz7QlOU8J2tv8APdDtytoowveKF9IoKpHa4= +k8s.io/client-go v0.25.0-alpha.0/go.mod h1:V7vCXDCdD8Goobi4oQhsSNXtlWfyBJi+LHFaiYWpR5s= k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= @@ -1761,38 +1649,26 @@ k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= k8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= -k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/klog/v2 v2.40.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/klog/v2 v2.60.1 h1:VW25q3bZx9uE3vvdL6M8ezOX79vA2Aq1nEWLqNQclHc= k8s.io/klog/v2 v2.60.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= -k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42/go.mod h1:Z/45zLw8lUo4wdiUkI+v/ImEGAvu3WatcZl3lPMR4Rk= k8s.io/kube-openapi v0.0.0-20220413171646-5e7f5fdc6da6 h1:nBQrWPlrNIiw0BsX6a6MKr1itkm0ZS0Nl97kNLitFfI= k8s.io/kube-openapi v0.0.0-20220413171646-5e7f5fdc6da6/go.mod h1:daOouuuwd9JXpv1L7Y34iV3yf6nxzipkKMWWlqlvK9M= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 h1:HNSDgDCrr/6Ly3WEGKZftiE7IY19Vz2GdbOCyI4qqhc= k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= -modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= -modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= -modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= -modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= -sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 h1:kDi4JBNAsJWfz1aEXhO8Jg87JJaPNLh5tIzYHgStQ9Y= sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY= sigs.k8s.io/kind v0.12.0 h1:LFynXwQkH1MrWI8pM1FQty0oUwEKjU5EkMaVZaPld8E= @@ -1801,8 +1677,6 @@ sigs.k8s.io/kustomize/api v0.11.4 h1:/0Mr3kfBBNcNPOW5Qwk/3eb8zkswCwnqQxxKtmrTkRo sigs.k8s.io/kustomize/api v0.11.4/go.mod h1:k+8RsqYbgpkIrJ4p9jcdPqe8DprLxFUUO0yNOq8C+xI= sigs.k8s.io/kustomize/kyaml v0.13.6 h1:eF+wsn4J7GOAXlvajv6OknSunxpcOBQQqsnPxObtkGs= sigs.k8s.io/kustomize/kyaml v0.13.6/go.mod h1:yHP031rn1QX1lr/Xd934Ri/xdVNG8BE2ECa78Ht/kEg= -sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e h1:4Z09Hglb792X0kfOBBJUPFEyvVfQWrYT/l8h5EKA6JQ= -sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y= @@ -1811,4 +1685,3 @@ sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= -sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= From f2fa88ff1fc50e1931e0329a49c5f724fe911f68 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 27 Apr 2022 10:59:11 +0200 Subject: [PATCH 0712/2916] refactor: Allow to pass GitAuthProviders to NewKluctlProjectContext --- cmd/kluctl/commands/utils.go | 2 ++ pkg/kluctl_project/git.go | 4 ++-- pkg/kluctl_project/project.go | 18 +++++++++--------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 829ab5bbb..b8181e29c 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/deployment" + "github.com/kluctl/kluctl/v2/pkg/git/auth" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/jinja2" "github.com/kluctl/kluctl/v2/pkg/kluctl_project" @@ -44,6 +45,7 @@ func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, cb func(p *kluctl LocalSealedSecrets: projectFlags.LocalSealedSecrets, FromArchive: projectFlags.FromArchive, FromArchiveMetadata: projectFlags.FromArchiveMetadata, + GitAuthProviders: auth.NewDefaultAuthProviders(), } p, err := kluctl_project.LoadKluctlProject(loadArgs, tmpDir, j2) diff --git a/pkg/kluctl_project/git.go b/pkg/kluctl_project/git.go index 4fd28013e..93abdfc9b 100644 --- a/pkg/kluctl_project/git.go +++ b/pkg/kluctl_project/git.go @@ -31,7 +31,7 @@ func (c *KluctlProjectContext) updateGitCaches() error { doUpdateRepo := func(repo *git.MirroredGitRepo) error { return repo.WithLock(func() error { if !repo.HasUpdated() { - return repo.Update(c.gitAuthProviders) + return repo.Update(c.loadArgs.GitAuthProviders) } return nil }) @@ -156,7 +156,7 @@ func (c *KluctlProjectContext) cloneGitProject(gitProject types2.ExternalProject err = mr.MaybeWithLock(doLock, func() error { if !mr.HasUpdated() { - err = mr.Update(c.gitAuthProviders) + err = mr.Update(c.loadArgs.GitAuthProviders) if err != nil { return err } diff --git a/pkg/kluctl_project/project.go b/pkg/kluctl_project/project.go index 552240a22..c44c64555 100644 --- a/pkg/kluctl_project/project.go +++ b/pkg/kluctl_project/project.go @@ -19,6 +19,8 @@ type LoadKluctlProjectArgs struct { LocalSealedSecrets string FromArchive string FromArchiveMetadata string + + GitAuthProviders *auth2.GitAuthProviders } type KluctlProjectContext struct { @@ -32,9 +34,8 @@ type KluctlProjectContext struct { ClustersDir string SealedSecretsDir string - gitAuthProviders *auth2.GitAuthProviders - involvedRepos map[string][]types.InvolvedRepo - DynamicTargets []*types.DynamicTarget + involvedRepos map[string][]types.InvolvedRepo + DynamicTargets []*types.DynamicTarget mirroredRepos map[string]*git.MirroredGitRepo @@ -43,12 +44,11 @@ type KluctlProjectContext struct { func NewKluctlProjectContext(loadArgs LoadKluctlProjectArgs, tmpDir string, j2 *jinja2.Jinja2) *KluctlProjectContext { o := &KluctlProjectContext{ - loadArgs: loadArgs, - TmpDir: tmpDir, - gitAuthProviders: auth2.NewDefaultAuthProviders(), - involvedRepos: make(map[string][]types.InvolvedRepo), - mirroredRepos: make(map[string]*git.MirroredGitRepo), - J2: j2, + loadArgs: loadArgs, + TmpDir: tmpDir, + involvedRepos: make(map[string][]types.InvolvedRepo), + mirroredRepos: make(map[string]*git.MirroredGitRepo), + J2: j2, } return o } From 4af3416e0484b6f9586c645b03757fc89fd4bf9b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 27 Apr 2022 11:24:02 +0200 Subject: [PATCH 0713/2916] refactor: Introduce ListAuthProvider and use it in GitEnvAuthProvider --- pkg/git/auth/env_auth_provider.go | 47 ++++++----------- pkg/git/auth/list_auth_provider.go | 81 ++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 31 deletions(-) create mode 100644 pkg/git/auth/list_auth_provider.go diff --git a/pkg/git/auth/env_auth_provider.go b/pkg/git/auth/env_auth_provider.go index 2755ab710..908d525f7 100644 --- a/pkg/git/auth/env_auth_provider.go +++ b/pkg/git/auth/env_auth_provider.go @@ -2,52 +2,37 @@ package auth import ( "github.com/go-git/go-git/v5/plumbing/transport" - "github.com/go-git/go-git/v5/plumbing/transport/http" - "github.com/go-git/go-git/v5/plumbing/transport/ssh" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/utils" log "github.com/sirupsen/logrus" - "strings" + "io/ioutil" ) type GitEnvAuthProvider struct { } func (a *GitEnvAuthProvider) BuildAuth(gitUrl git_url.GitUrl) transport.AuthMethod { - for _, m := range utils.ParseEnvConfigSets("KLUCTL_GIT") { - host, _ := m["HOST"] - pathPrefix, _ := m["PATH_PREFIX"] - username, _ := m["USERNAME"] - password, _ := m["PASSWORD"] - ssh_key, _ := m["SSH_KEY"] + var la ListAuthProvider - if host != gitUrl.Hostname() { - continue - } - if !strings.HasPrefix(gitUrl.Path, pathPrefix) { - continue - } - if username == "" { - continue - } - if gitUrl.User != nil && gitUrl.User.Username() != "" && gitUrl.User.Username() != username { - continue + for _, m := range utils.ParseEnvConfigSets("KLUCTL_GIT") { + e := AuthEntry{ + Host: m["HOST"], + PathPrefix: m["PATH_PREFIX"], + Username: m["USERNAME"], + Password: m["PASSWORD"], } - if !gitUrl.IsSsh() && password != "" { - return &http.BasicAuth{ - Username: username, - Password: password, - } - } else if gitUrl.IsSsh() && ssh_key != "" { - a, err := ssh.NewPublicKeysFromFile(username, ssh_key, "") + ssh_key_path, _ := m["SSH_KEY"] + if ssh_key_path != "" { + ssh_key_path = utils.ExpandPath(ssh_key_path) + b, err := ioutil.ReadFile(ssh_key_path) if err != nil { - log.Debugf("Failed to parse private key %s: %v", ssh_key, err) + log.Debugf("Failed to read key %s: %w", ssh_key_path, err) } else { - a.HostKeyCallback = verifyHost - return a + e.SshKey = b } } + la.AddEntry(e) } - return nil + return la.BuildAuth(gitUrl) } diff --git a/pkg/git/auth/list_auth_provider.go b/pkg/git/auth/list_auth_provider.go new file mode 100644 index 000000000..7360afb9e --- /dev/null +++ b/pkg/git/auth/list_auth_provider.go @@ -0,0 +1,81 @@ +package auth + +import ( + "github.com/go-git/go-git/v5/plumbing/transport" + "github.com/go-git/go-git/v5/plumbing/transport/http" + "github.com/go-git/go-git/v5/plumbing/transport/ssh" + git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" + log "github.com/sirupsen/logrus" + "strings" +) + +type ListAuthProvider struct { + entries []AuthEntry +} + +type AuthEntry struct { + Host string + PathPrefix string + Username string + Password string + SshKey []byte +} + +func (a *ListAuthProvider) AddEntry(e AuthEntry) { + a.entries = append(a.entries, e) +} + +func (a *ListAuthProvider) BuildAuth(gitUrl git_url.GitUrl) transport.AuthMethod { + for _, e := range a.entries { + if e.Host != "*" && e.Host != gitUrl.Hostname() { + continue + } + if !strings.HasPrefix(gitUrl.Path, e.PathPrefix) { + continue + } + if e.Username == "" { + continue + } + + username := "" + if gitUrl.User != nil { + username = gitUrl.User.Username() + } + + if username != "" && e.Username != "*" && username != e.Username { + continue + } + + if username == "" { + username = e.Username + } + + if e.Username == "*" { + // can't use "*" as username + continue + } + + if gitUrl.IsSsh() { + if e.SshKey == nil { + continue + } + a, err := ssh.NewPublicKeys(username, e.SshKey, "") + if err != nil { + log.Debugf("Failed to parse private key: %v", err) + } else { + a.HostKeyCallback = verifyHost + return a + } + } else { + if e.Password == "" { + // empty password is not accepted + continue + } + return &http.BasicAuth{ + Username: username, + Password: e.Password, + } + } + } + return nil +} From 2556344fa80982bfc25afad5e90d4c05def1d420 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 27 Apr 2022 11:29:33 +0200 Subject: [PATCH 0714/2916] refactor: Allow to add auth providers to the front of the list --- pkg/git/auth/auth_provider.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/pkg/git/auth/auth_provider.go b/pkg/git/auth/auth_provider.go index 6c8872bd6..c8a5baacf 100644 --- a/pkg/git/auth/auth_provider.go +++ b/pkg/git/auth/auth_provider.go @@ -13,8 +13,12 @@ type GitAuthProviders struct { authProviders []GitAuthProvider } -func (a *GitAuthProviders) RegisterAuthProvider(p GitAuthProvider) { - a.authProviders = append(a.authProviders, p) +func (a *GitAuthProviders) RegisterAuthProvider(p GitAuthProvider, last bool) { + if last { + a.authProviders = append(a.authProviders, p) + } else { + a.authProviders = append([]GitAuthProvider{p}, a.authProviders...) + } } func (a *GitAuthProviders) BuildAuth(gitUrl git_url.GitUrl) transport.AuthMethod { @@ -29,8 +33,8 @@ func (a *GitAuthProviders) BuildAuth(gitUrl git_url.GitUrl) transport.AuthMethod func NewDefaultAuthProviders() *GitAuthProviders { a := &GitAuthProviders{} - a.RegisterAuthProvider(&GitEnvAuthProvider{}) - a.RegisterAuthProvider(&GitCredentialsFileAuthProvider{}) - a.RegisterAuthProvider(&GitSshAuthProvider{}) + a.RegisterAuthProvider(&GitEnvAuthProvider{}, true) + a.RegisterAuthProvider(&GitCredentialsFileAuthProvider{}, true) + a.RegisterAuthProvider(&GitSshAuthProvider{}, true) return a } From faa19d22f3c6b587366f900b6bed96f3f88c47ea Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 27 Apr 2022 11:33:45 +0200 Subject: [PATCH 0715/2916] fix: log.Debugf does not support %w --- pkg/git/auth/env_auth_provider.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/git/auth/env_auth_provider.go b/pkg/git/auth/env_auth_provider.go index 908d525f7..362a506bc 100644 --- a/pkg/git/auth/env_auth_provider.go +++ b/pkg/git/auth/env_auth_provider.go @@ -27,7 +27,7 @@ func (a *GitEnvAuthProvider) BuildAuth(gitUrl git_url.GitUrl) transport.AuthMeth ssh_key_path = utils.ExpandPath(ssh_key_path) b, err := ioutil.ReadFile(ssh_key_path) if err != nil { - log.Debugf("Failed to read key %s: %w", ssh_key_path, err) + log.Debugf("Failed to read key %s: %v", ssh_key_path, err) } else { e.SshKey = b } From c637cb73b2ec99cdbad128e62a063fffec2de762 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 27 Apr 2022 11:59:58 +0200 Subject: [PATCH 0716/2916] feat: Support passing CA bundle and known_hosts for git --- pkg/git/auth/auth_provider.go | 13 +++++--- pkg/git/auth/env_auth_provider.go | 13 ++++++-- pkg/git/auth/git_credentials_file.go | 27 ++++++++-------- pkg/git/auth/list_auth_provider.go | 24 +++++++++----- pkg/git/auth/ssh_auth_provider.go | 13 ++++---- pkg/git/auth/ssh_known_hosts.go | 47 +++++++++++++++++++--------- pkg/git/mirrored_repo.go | 6 ++-- 7 files changed, 94 insertions(+), 49 deletions(-) diff --git a/pkg/git/auth/auth_provider.go b/pkg/git/auth/auth_provider.go index c8a5baacf..677f4df07 100644 --- a/pkg/git/auth/auth_provider.go +++ b/pkg/git/auth/auth_provider.go @@ -5,8 +5,13 @@ import ( git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" ) +type AuthMethodAndCA struct { + AuthMethod transport.AuthMethod + CABundle []byte +} + type GitAuthProvider interface { - BuildAuth(gitUrl git_url.GitUrl) transport.AuthMethod + BuildAuth(gitUrl git_url.GitUrl) AuthMethodAndCA } type GitAuthProviders struct { @@ -21,14 +26,14 @@ func (a *GitAuthProviders) RegisterAuthProvider(p GitAuthProvider, last bool) { } } -func (a *GitAuthProviders) BuildAuth(gitUrl git_url.GitUrl) transport.AuthMethod { +func (a *GitAuthProviders) BuildAuth(gitUrl git_url.GitUrl) AuthMethodAndCA { for _, p := range a.authProviders { auth := p.BuildAuth(gitUrl) - if auth != nil { + if auth.AuthMethod != nil { return auth } } - return nil + return AuthMethodAndCA{} } func NewDefaultAuthProviders() *GitAuthProviders { diff --git a/pkg/git/auth/env_auth_provider.go b/pkg/git/auth/env_auth_provider.go index 362a506bc..a57fa44f3 100644 --- a/pkg/git/auth/env_auth_provider.go +++ b/pkg/git/auth/env_auth_provider.go @@ -1,7 +1,6 @@ package auth import ( - "github.com/go-git/go-git/v5/plumbing/transport" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/utils" log "github.com/sirupsen/logrus" @@ -11,7 +10,7 @@ import ( type GitEnvAuthProvider struct { } -func (a *GitEnvAuthProvider) BuildAuth(gitUrl git_url.GitUrl) transport.AuthMethod { +func (a *GitEnvAuthProvider) BuildAuth(gitUrl git_url.GitUrl) AuthMethodAndCA { var la ListAuthProvider for _, m := range utils.ParseEnvConfigSets("KLUCTL_GIT") { @@ -32,6 +31,16 @@ func (a *GitEnvAuthProvider) BuildAuth(gitUrl git_url.GitUrl) transport.AuthMeth e.SshKey = b } } + ca_bundle_path := m["CA_BUNDLE"] + if ca_bundle_path != "" { + ca_bundle_path = utils.ExpandPath(ca_bundle_path) + b, err := ioutil.ReadFile(ca_bundle_path) + if err != nil { + log.Debugf("Failed to read ca bundle %s: %v", ca_bundle_path, err) + } else { + e.CABundle = b + } + } la.AddEntry(e) } return la.BuildAuth(gitUrl) diff --git a/pkg/git/auth/git_credentials_file.go b/pkg/git/auth/git_credentials_file.go index 49fbe894e..ea665c16f 100644 --- a/pkg/git/auth/git_credentials_file.go +++ b/pkg/git/auth/git_credentials_file.go @@ -2,7 +2,6 @@ package auth import ( "bufio" - "github.com/go-git/go-git/v5/plumbing/transport" "github.com/go-git/go-git/v5/plumbing/transport/http" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/utils" @@ -15,36 +14,36 @@ import ( type GitCredentialsFileAuthProvider struct { } -func (a *GitCredentialsFileAuthProvider) BuildAuth(gitUrl git_url.GitUrl) transport.AuthMethod { +func (a *GitCredentialsFileAuthProvider) BuildAuth(gitUrl git_url.GitUrl) AuthMethodAndCA { if gitUrl.Scheme != "http" && gitUrl.Scheme != "https" { - return nil + return AuthMethodAndCA{} } home, err := os.UserHomeDir() if err != nil { log.Warningf("Could not determine home directory: %v", err) - return nil + return AuthMethodAndCA{} } auth := a.tryBuildAuth(gitUrl, filepath.Join(home, ".git-credentials")) - if auth != nil { - return auth + if auth.AuthMethod != nil { + return *auth } if xdgHome, ok := os.LookupEnv("XDG_CONFIG_HOME"); ok && xdgHome != "" { auth = a.tryBuildAuth(gitUrl, filepath.Join(xdgHome, ".git-credentials")) if auth != nil { - return auth + return *auth } } else { auth = a.tryBuildAuth(gitUrl, filepath.Join(home, ".config/.git-credentials")) if auth != nil { - return auth + return *auth } } - return nil + return AuthMethodAndCA{} } -func (a *GitCredentialsFileAuthProvider) tryBuildAuth(gitUrl git_url.GitUrl, gitCredentialsPath string) transport.AuthMethod { +func (a *GitCredentialsFileAuthProvider) tryBuildAuth(gitUrl git_url.GitUrl, gitCredentialsPath string) *AuthMethodAndCA { if !utils.IsFile(gitCredentialsPath) { return nil } @@ -70,9 +69,11 @@ func (a *GitCredentialsFileAuthProvider) tryBuildAuth(gitUrl git_url.GitUrl, git } if password, ok := u.User.Password(); ok { if u.User.Username() != "" { - return &http.BasicAuth{ - Username: u.User.Username(), - Password: password, + return &AuthMethodAndCA{ + AuthMethod: &http.BasicAuth{ + Username: u.User.Username(), + Password: password, + }, } } } diff --git a/pkg/git/auth/list_auth_provider.go b/pkg/git/auth/list_auth_provider.go index 7360afb9e..d469e500f 100644 --- a/pkg/git/auth/list_auth_provider.go +++ b/pkg/git/auth/list_auth_provider.go @@ -1,7 +1,6 @@ package auth import ( - "github.com/go-git/go-git/v5/plumbing/transport" "github.com/go-git/go-git/v5/plumbing/transport/http" "github.com/go-git/go-git/v5/plumbing/transport/ssh" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" @@ -18,14 +17,18 @@ type AuthEntry struct { PathPrefix string Username string Password string + SshKey []byte + KnownHosts []byte + + CABundle []byte } func (a *ListAuthProvider) AddEntry(e AuthEntry) { a.entries = append(a.entries, e) } -func (a *ListAuthProvider) BuildAuth(gitUrl git_url.GitUrl) transport.AuthMethod { +func (a *ListAuthProvider) BuildAuth(gitUrl git_url.GitUrl) AuthMethodAndCA { for _, e := range a.entries { if e.Host != "*" && e.Host != gitUrl.Hostname() { continue @@ -63,19 +66,24 @@ func (a *ListAuthProvider) BuildAuth(gitUrl git_url.GitUrl) transport.AuthMethod if err != nil { log.Debugf("Failed to parse private key: %v", err) } else { - a.HostKeyCallback = verifyHost - return a + a.HostKeyCallback = buildVerifyHostCallback(e.KnownHosts) + return AuthMethodAndCA{ + AuthMethod: a, + } } } else { if e.Password == "" { // empty password is not accepted continue } - return &http.BasicAuth{ - Username: username, - Password: e.Password, + return AuthMethodAndCA{ + AuthMethod: &http.BasicAuth{ + Username: username, + Password: e.Password, + }, + CABundle: e.CABundle, } } } - return nil + return AuthMethodAndCA{} } diff --git a/pkg/git/auth/ssh_auth_provider.go b/pkg/git/auth/ssh_auth_provider.go index 6da3e0e98..d3c2a64ca 100644 --- a/pkg/git/auth/ssh_auth_provider.go +++ b/pkg/git/auth/ssh_auth_provider.go @@ -2,7 +2,6 @@ package auth import ( "fmt" - "github.com/go-git/go-git/v5/plumbing/transport" "github.com/kevinburke/ssh_config" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/utils" @@ -42,7 +41,7 @@ func (a *sshDefaultIdentityAndAgent) ClientConfig() (*ssh.ClientConfig, error) { User: a.user, Auth: []ssh.AuthMethod{ssh.PublicKeysCallback(a.Signers)}, } - cc.HostKeyCallback = verifyHost + cc.HostKeyCallback = buildVerifyHostCallback(nil) return cc, nil } @@ -90,12 +89,12 @@ func (a *sshDefaultIdentityAndAgent) addAgentIdentities(gitUrl git_url.GitUrl) { } } -func (a *GitSshAuthProvider) BuildAuth(gitUrl git_url.GitUrl) transport.AuthMethod { +func (a *GitSshAuthProvider) BuildAuth(gitUrl git_url.GitUrl) AuthMethodAndCA { if !gitUrl.IsSsh() { - return nil + return AuthMethodAndCA{} } if gitUrl.User == nil { - return nil + return AuthMethodAndCA{} } auth := &sshDefaultIdentityAndAgent{ @@ -109,7 +108,9 @@ func (a *GitSshAuthProvider) BuildAuth(gitUrl git_url.GitUrl) transport.AuthMeth auth.addDefaultIdentity(gitUrl) auth.addConfigIdentities(gitUrl) - return auth + return AuthMethodAndCA{ + AuthMethod: auth, + } } // we defer asking for passphrase so that we don't unnecessarily ask for passphrases for keys that are already provided diff --git a/pkg/git/auth/ssh_known_hosts.go b/pkg/git/auth/ssh_known_hosts.go index 2ad01941d..2e0b06785 100644 --- a/pkg/git/auth/ssh_known_hosts.go +++ b/pkg/git/auth/ssh_known_hosts.go @@ -11,7 +11,13 @@ import ( "strconv" ) -func verifyHost(host string, remote net.Addr, key ssh.PublicKey) error { +func buildVerifyHostCallback(knownHosts []byte) func(hostname string, remote net.Addr, key ssh.PublicKey) error { + return func(hostname string, remote net.Addr, key ssh.PublicKey) error { + return verifyHost(hostname, remote, key, knownHosts) + } +} + +func verifyHost(host string, remote net.Addr, key ssh.PublicKey, knownHosts []byte) error { hostKeyChecking := true if x, ok := os.LookupEnv("KLUCTL_SSH_DISABLE_STRICT_HOST_KEY_CHECKING"); ok { if b, err := strconv.ParseBool(x); err == nil && b { @@ -23,23 +29,36 @@ func verifyHost(host string, remote net.Addr, key ssh.PublicKey) error { } allowAdd := false - files := filepath.SplitList(os.Getenv("SSH_KNOWN_HOSTS")) - if len(files) == 0 { - home, err := os.UserHomeDir() - if err != nil { - return err - } - - f := filepath.Join(home, ".ssh", "known_hosts") - if !utils.Exists(filepath.Dir(f)) { - err = os.MkdirAll(filepath.Dir(f), 0o700) + var files []string + if knownHosts == nil { + files = filepath.SplitList(os.Getenv("SSH_KNOWN_HOSTS")) + if len(files) == 0 { + home, err := os.UserHomeDir() if err != nil { return err } - } - files = append(files, f) - allowAdd = true + f := filepath.Join(home, ".ssh", "known_hosts") + if !utils.Exists(filepath.Dir(f)) { + err = os.MkdirAll(filepath.Dir(f), 0o700) + if err != nil { + return err + } + } + + files = append(files, f) + allowAdd = true + } + } else { + tmpFile, err := os.CreateTemp(utils.GetTmpBaseDir(), "known_hosts-") + if err != nil { + return err + } + defer func() { + _ = tmpFile.Close() + _ = os.Remove(tmpFile.Name()) + }() + files = append(files, tmpFile.Name()) } for _, f := range files { diff --git a/pkg/git/mirrored_repo.go b/pkg/git/mirrored_repo.go index 82b822ee2..bd7fe5672 100644 --- a/pkg/git/mirrored_repo.go +++ b/pkg/git/mirrored_repo.go @@ -139,7 +139,8 @@ func (g *MirroredGitRepo) update(repoDir string, authProviders *auth2.GitAuthPro } g.remoteRefs, err = remote.List(&git.ListOptions{ - Auth: auth, + Auth: auth.AuthMethod, + CABundle: auth.CABundle, }) if err != nil { return err @@ -150,7 +151,8 @@ func (g *MirroredGitRepo) update(repoDir string, authProviders *auth2.GitAuthPro } err = remote.Fetch(&git.FetchOptions{ - Auth: auth, + Auth: auth.AuthMethod, + CABundle: auth.CABundle, Progress: os.Stdout, Tags: git.AllTags, Force: true, From 2838933fa4a32373e7e98da355e795faf50da9dd Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 27 Apr 2022 15:29:58 +0200 Subject: [PATCH 0717/2916] feat: Use vendored Helm dependency instead of relying on an installed helm binary --- go.mod | 77 +++++- go.sum | 464 +++++++++++++++++++++++++++++++---- pkg/deployment/helm_chart.go | 305 +++++++++++++---------- 3 files changed, 664 insertions(+), 182 deletions(-) diff --git a/go.mod b/go.mod index 73da526f4..6aec263c0 100644 --- a/go.mod +++ b/go.mod @@ -36,17 +36,27 @@ require ( golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b - k8s.io/api v0.25.0-alpha.0 - k8s.io/apimachinery v0.25.0-alpha.0 - k8s.io/client-go v0.25.0-alpha.0 + helm.sh/helm/v3 v3.8.2 + k8s.io/api v0.24.0-rc.1 + k8s.io/apimachinery v0.24.0-rc.1 + k8s.io/client-go v0.24.0-rc.1 sigs.k8s.io/kind v0.12.0 sigs.k8s.io/kustomize/api v0.11.4 sigs.k8s.io/kustomize/kyaml v0.13.6 sigs.k8s.io/structured-merge-diff/v4 v4.2.1 ) +replace ( + k8s.io/api => k8s.io/api v0.23.5 + k8s.io/apimachinery => k8s.io/apimachinery v0.23.5 + k8s.io/cli-runtime => k8s.io/cli-runtime v0.23.5 + k8s.io/client-go => k8s.io/client-go v0.23.5 + k8s.io/component-base => k8s.io/component-base v0.23.5 +) + require ( cloud.google.com/go/compute v1.6.1 // indirect + github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest v0.11.27 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect @@ -54,24 +64,39 @@ require ( github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/BurntSushi/toml v1.1.0 // indirect + github.com/MakeNowJust/heredoc v1.0.0 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.1.1 // indirect + github.com/Masterminds/sprig/v3 v3.2.2 // indirect + github.com/Masterminds/squirrel v1.5.2 // indirect github.com/Microsoft/go-winio v0.5.2 // indirect github.com/ProtonMail/go-crypto v0.0.0-20220407094043-a94812496cf5 // indirect github.com/VividCortex/ewma v1.2.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect github.com/acomagu/bufpipe v1.0.3 // indirect github.com/alessio/shellescape v1.4.1 // indirect + github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 // indirect + github.com/containerd/containerd v1.6.3 // indirect + github.com/cyphar/filepath-securejoin v0.2.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/docker/cli v20.10.14+incompatible // indirect github.com/docker/docker v20.10.14+incompatible // indirect github.com/docker/docker-credential-helpers v0.6.4 // indirect - github.com/emicklei/go-restful v2.15.0+incompatible // indirect + github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/go-metrics v0.0.1 // indirect + github.com/docker/go-units v0.4.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect + github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect github.com/fatih/color v1.13.0 // indirect github.com/gammazero/deque v0.1.1 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-git/gcfg v1.5.0 // indirect github.com/go-git/go-billy/v5 v5.3.1 // indirect + github.com/go-gorp/gorp/v3 v3.0.2 // indirect github.com/go-logr/logr v1.2.3 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.20.0 // indirect @@ -81,35 +106,63 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/btree v1.0.1 // indirect - github.com/google/gnostic v0.6.9 // indirect + github.com/google/go-cmp v0.5.8 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/googleapis/gnostic v0.5.5 // indirect + github.com/gorilla/mux v1.8.0 // indirect + github.com/gosuri/uitable v0.0.4 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect + github.com/huandu/xstrings v1.3.2 // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/jmoiron/sqlx v1.3.5 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect + github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/leodido/go-urn v1.2.1 // indirect + github.com/lib/pq v1.10.5 // indirect + github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/moby/locker v1.0.1 // indirect + github.com/moby/spdystream v0.2.0 // indirect + github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect - github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/morikuni/aec v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.0.2-0.20211117181255-693428a734f5 // indirect + github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_golang v1.12.1 // indirect + github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/common v0.34.0 // indirect + github.com/prometheus/procfs v0.7.3 // indirect github.com/rivo/uniseg v0.2.0 // indirect + github.com/rubenv/sql-migrate v1.1.1 // indirect + github.com/russross/blackfriday v1.6.0 // indirect github.com/sergi/go-diff v1.2.0 // indirect + github.com/shopspring/decimal v1.3.1 // indirect + github.com/spf13/cast v1.4.1 // indirect github.com/spf13/cobra v1.4.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xlab/treeprint v1.1.0 // indirect go.starlark.net v0.0.0-20220328144851-d1966c6b9fcd // indirect golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect @@ -118,13 +171,21 @@ require ( golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto v0.0.0-20220426171045-31bebdecfb46 // indirect + google.golang.org/grpc v1.46.0 // indirect google.golang.org/protobuf v1.28.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect + k8s.io/apiextensions-apiserver v0.23.6 // indirect + k8s.io/apiserver v0.23.6 // indirect + k8s.io/cli-runtime v0.24.0-rc.1 // indirect + k8s.io/component-base v0.24.0-rc.1 // indirect k8s.io/klog/v2 v2.60.1 // indirect - k8s.io/kube-openapi v0.0.0-20220413171646-5e7f5fdc6da6 // indirect + k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect + k8s.io/kubectl v0.23.5 // indirect k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect + oras.land/oras-go v1.1.1 // indirect sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index fe612bf11..800475ba0 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,5 @@ bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= +bazil.org/fuse v0.0.0-20200407214033-5883e5a4b512/go.mod h1:FbcW6z/2VytnFDhZfumh8Ss8zxHE6qpMP5sHTRe0EaM= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -55,27 +56,30 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20210715213245-6c3934b029d8/go.mod h1:CzsSbkDixRphAF5hS6wbMKq0eI6ccJRb7/A0M6JBnwg= github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v56.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= +github.com/Azure/go-autorest/autorest v0.11.20/go.mod h1:o3tqFY+QR40VOlk+pV4d77mORO64jOXSgEnPQgLK6JY= github.com/Azure/go-autorest/autorest v0.11.27 h1:F3R3q42aWytozkV8ihzcgMO4OA4cuqr3bNlsEuF6//A= github.com/Azure/go-autorest/autorest v0.11.27/go.mod h1:7l8ybrIdUmGqZMTD0sRtAr8NvbHjfofbf8RSP2q7w7U= -github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= -github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= +github.com/Azure/go-autorest/autorest/adal v0.9.15/go.mod h1:tGMin8I49Yij6AQ+rvV+Xa/zwxYQB5hmsd6DkfAx2+A= github.com/Azure/go-autorest/autorest/adal v0.9.18 h1:kLnPsRjzZZUF3K5REu/Kc+qMQrvuza2bwSnNdhmzLfQ= github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= -github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw= github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU= -github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= @@ -85,7 +89,24 @@ github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= +github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= +github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= +github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= +github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= +github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= +github.com/Masterminds/squirrel v1.5.2 h1:UiOEi2ZX4RCSkpiNDQN5kro/XIBpSRk9iTqdIRPzUXE= +github.com/Masterminds/squirrel v1.5.2/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= +github.com/Masterminds/vcs v1.13.3/go.mod h1:TiE7xuEjl1N4j016moRd6vezp6e6Lz23gypeXfzXeW8= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= @@ -105,16 +126,24 @@ github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg3 github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00= github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600= +github.com/Microsoft/hcsshim v0.8.20/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= +github.com/Microsoft/hcsshim v0.8.21/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= github.com/Microsoft/hcsshim v0.8.23/go.mod h1:4zegtUJth7lAvFyc6cH2gGQ5B3OFQim01nnU2M8jKDg= +github.com/Microsoft/hcsshim v0.9.2 h1:wB06W5aYFfUB3IvootYAY2WnOmIdgPGfqSI6tufQNnY= +github.com/Microsoft/hcsshim v0.9.2/go.mod h1:7pLA8lDk46WKDWlVsENo92gC0XFa8rbKfyFRBqxEbCc= github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= github.com/ProtonMail/go-crypto v0.0.0-20220407094043-a94812496cf5 h1:cSHEbLj0GZeHM1mWG84qEnGFojNEQ83W7cwaPRjcwXU= github.com/ProtonMail/go-crypto v0.0.0-20220407094043-a94812496cf5/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= +github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= @@ -130,12 +159,15 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= +github.com/alexflint/go-filemutex v1.1.0/go.mod h1:7P4iRhttt/nUvUOrYIhcpMzv2G6CY9UnI16Z+UJqRyk= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210826220005-b48c857c3a0e/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -145,36 +177,56 @@ github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgI github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= +github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= +github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= +github.com/aws/aws-sdk-go v1.34.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/aws/aws-sdk-go v1.44.1 h1:w34ZmPT6K4NTd7Yap1P7SLXPTii0ABmBz3KEh4KJdKc= github.com/aws/aws-sdk-go v1.44.1/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= github.com/bitnami-labs/sealed-secrets v0.17.5 h1:v5ENZRSrgog3GnFr8fWfVtrUTPlZlNlbsjaro9mn6YY= github.com/bitnami-labs/sealed-secrets v0.17.5/go.mod h1:ZgGUqKixr/SRpsG8LVXnuneyZG7DOX/+eCRvXi8SVjo= github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= +github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= +github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd h1:rFt+Y/IK1aEZkEHchZRSq9OQbsSzIT/OrI8YFFmRIng= github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= +github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembjv71DPz3uX/V/6MMlSyD9JBQ6kQ= github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= +github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= +github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 h1:7aWHqerlJ41y6FOsEUvknqgXnGmJyJSbjhAWq5pO4F8= +github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= +github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -183,6 +235,7 @@ github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLI github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= +github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= @@ -197,6 +250,9 @@ github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= +github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= +github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= @@ -211,11 +267,14 @@ github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4S github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU= +github.com/containerd/cgroups v1.0.3 h1:ADZftAkglvCiD44c77s5YmMqaP2pzVCFZvBmAlBdAP4= +github.com/containerd/cgroups v1.0.3/go.mod h1:/ofk34relqNjSGyqPrmEULrO4Sc8LJhvJmWbUCUKqj8= github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= @@ -229,7 +288,12 @@ github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7 github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU= github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s= +github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= +github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c= github.com/containerd/containerd v1.5.8/go.mod h1:YdFSv5bTFLpG2HIYmfqDpSYYTDX+mc5qtSuYx1YUb/s= +github.com/containerd/containerd v1.6.1/go.mod h1:1nJz5xCZPusx6jJU8Frfct988y0NpumIq9ODB0kLtoE= +github.com/containerd/containerd v1.6.3 h1:JfgUEIAH07xDWk6kqz0P3ArZt+KJ9YeihSC9uyFtSKg= +github.com/containerd/containerd v1.6.3/go.mod h1:gCVGrYRYFm2E8GmuUIbj/NGD7DLZQLzSJQazjVKDOig= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= @@ -237,6 +301,7 @@ github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cE github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y= github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ= github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= +github.com/containerd/continuity v0.2.2/go.mod h1:pWygW9u7LtS1o4N/Tn0FoCFDIXZ7rxcMX7HX1Dmibvk= github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= @@ -245,6 +310,8 @@ github.com/containerd/fifo v0.0.0-20210316144830-115abcc95a1d/go.mod h1:ocF/ME1S github.com/containerd/fifo v1.0.0/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= github.com/containerd/go-cni v1.0.1/go.mod h1:+vUpYxKvAF72G9i1WoDOiPGRtQpqsNW/ZHtSlv++smU= github.com/containerd/go-cni v1.0.2/go.mod h1:nrNABBHzu0ZwCug9Ije8hL2xBCYh/pjfMb1aZGrrohk= +github.com/containerd/go-cni v1.1.0/go.mod h1:Rflh2EJ/++BA2/vY5ao3K6WJRR/bZKsX123aPk+kUtA= +github.com/containerd/go-cni v1.1.3/go.mod h1:Rflh2EJ/++BA2/vY5ao3K6WJRR/bZKsX123aPk+kUtA= github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= github.com/containerd/go-runc v0.0.0-20190911050354-e029b79d8cda/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g= @@ -254,9 +321,11 @@ github.com/containerd/imgcrypt v1.0.1/go.mod h1:mdd8cEPW7TPgNG4FpuP3sGBiQ7Yi/zak github.com/containerd/imgcrypt v1.0.4-0.20210301171431-0ae5c75f59ba/go.mod h1:6TNsg0ctmizkrOgXRNQjAPFWpMYRWuiB6dSF4Pfa5SA= github.com/containerd/imgcrypt v1.1.1-0.20210312161619-7ed62a527887/go.mod h1:5AZJNI6sLHJljKuI9IHnw1pWqo/F0nGDOuR9zgTs7ow= github.com/containerd/imgcrypt v1.1.1/go.mod h1:xpLnwiQmEUJPvQoAapeb2SNCxz7Xr6PJrXQb0Dpc4ms= +github.com/containerd/imgcrypt v1.1.3/go.mod h1:/TPA1GIDXMzbj01yd8pIbQiLdQxed5ue1wb8bP7PQu4= github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c= github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= +github.com/containerd/stargz-snapshotter/estargz v0.4.1/go.mod h1:x7Q9dg9QYb4+ELgxmo4gBUeJB0tl5dqH1Sdz0nJU1QM= github.com/containerd/stargz-snapshotter/estargz v0.10.1 h1:hd1EoVjI2Ax8Cr64tdYqnJ4i4pZU49FkEf5kU8KxQng= github.com/containerd/stargz-snapshotter/estargz v0.10.1/go.mod h1:aE5PCyhFMwR8sbrErO5eM2GcvkyXTTJremG883D4qF0= github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= @@ -277,15 +346,20 @@ github.com/containerd/zfs v1.0.0/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNR github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= +github.com/containernetworking/cni v1.0.1/go.mod h1:AKuhXbN5EzmD4yTNtfSsX3tPcmtrBI6QcRV0NiNt15Y= github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM= github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8= +github.com/containernetworking/plugins v1.0.1/go.mod h1:QHCfGpaTwYTbbH+nZXKVTxNBDZcxSOplJT5ico8/FLE= github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc= github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4= github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= +github.com/containers/ocicrypt v1.1.2/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= +github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -302,8 +376,11 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsr github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= +github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI= +github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= @@ -312,11 +389,17 @@ github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7h github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= +github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/distribution/distribution/v3 v3.0.0-20211118083504-a29a3c99a684 h1:DBZ2sN7CK6dgvHVpQsQj4sRMCbWTmd17l+5SUCjnQSY= +github.com/distribution/distribution/v3 v3.0.0-20211118083504-a29a3c99a684/go.mod h1:UfCu3YXJJCI+IdnqGgYP82dk2+Joxmv+mUTVBES6wac= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= +github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v20.10.11+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v20.10.12+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v20.10.14+incompatible h1:dSBKJOVesDgHo7rbxlYjYsXe7gPzrTT+/cKQgpDAazg= github.com/docker/cli v20.10.14+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= @@ -325,27 +408,33 @@ github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v20.10.11+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v20.10.12+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v20.10.14+incompatible h1:+T9/PRYWNDo5SZl5qS1r9Mo/0Q8AwxKKPtu9S1yxM0w= github.com/docker/docker v20.10.14+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= github.com/docker/docker-credential-helpers v0.6.4 h1:axCks+yV+2MR3/kZhAmy07yC56WZ2Pwu/fKWtKuZB0o= github.com/docker/docker-credential-helpers v0.6.4/go.mod h1:ofX3UI0Gz1TteYBjtgs07O36Pyasyp66D2uKT7H8W1c= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= +github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= +github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= -github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful v2.15.0+incompatible h1:8KpYO/Xl/ZudZs5RNOEhWMBY4hmzlZhhRd9cu+jrZP4= -github.com/emicklei/go-restful v2.15.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= @@ -358,6 +447,7 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= @@ -367,12 +457,17 @@ github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCv github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= +github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= +github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4= +github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc= +github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= +github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= +github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= @@ -382,6 +477,7 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4 github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= +github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= github.com/gammazero/deque v0.1.0/go.mod h1:KQw7vFau1hHuM8xmI9RbgKFbAsQFWmBpqQ2KenFLk6M= github.com/gammazero/deque v0.1.1 h1:xRVkDuSvDmFuMGf3IquHuRc2jlL0+v/WpFCWaauzwbE= github.com/gammazero/deque v0.1.1/go.mod h1:KQw7vFau1hHuM8xmI9RbgKFbAsQFWmBpqQ2KenFLk6M= @@ -389,6 +485,7 @@ github.com/gammazero/workerpool v1.1.2 h1:vuioDQbgrz4HoaCi2q1HLlOXdpbap5AET7xu5/ github.com/gammazero/workerpool v1.1.2/go.mod h1:UelbXcO0zCIGFcufcirHhq2/xtLXJdQ29qZNlXG9OjQ= github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= +github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= @@ -408,26 +505,42 @@ github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gorp/gorp/v3 v3.0.2 h1:ULqJXIekoqMx29FI5ekXXFoH1dT2Vc8UhnRzBg+Emz4= +github.com/go-gorp/gorp/v3 v3.0.2/go.mod h1:BJ3q1ejpV8cVALtcXvXaXyTOlMmJhWDxTmncaR6rwBY= github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-logr/zapr v1.2.0/go.mod h1:Qa4Bsj2Vb+FAVeAKsLD8RLQ+YRJB8YDmOAKxaBQf7Ro= +github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= +github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= @@ -444,7 +557,21 @@ github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-playground/validator/v10 v10.10.1 h1:uA0+amWMiglNZKZ9FJRKUAe9U3RX91eVn1JYXMWt7ig= github.com/go-playground/validator/v10 v10.10.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/gobuffalo/logger v1.0.3/go.mod h1:SoeejUwldiS7ZsyCBphOGURmWdwUFXs0J7TCjEhjKxM= +github.com/gobuffalo/logger v1.0.6 h1:nnZNpxYo0zx+Aj9RfMPBm+x9zAU2OayFh/xrAWi34HU= +github.com/gobuffalo/logger v1.0.6/go.mod h1:J31TBEHR1QLV2683OXTAItYIg8pv2JMHnF/quuAbMjs= +github.com/gobuffalo/packd v1.0.0/go.mod h1:6VTc4htmJRFB7u1m/4LeMTWjFoYrUiBkU9Fdec9hrhI= +github.com/gobuffalo/packd v1.0.1 h1:U2wXfRr4E9DH8IdsDLlRFwTZTK7hLfq9qT/QHXGVe/0= +github.com/gobuffalo/packd v1.0.1/go.mod h1:PP2POP3p3RXGz7Jh6eYEf93S7vA2za6xM7QT85L4+VY= +github.com/gobuffalo/packr/v2 v2.8.1/go.mod h1:c/PLlOuTU+p3SybaJATW3H6lX/iK7xEz5OeMf+NnJpg= +github.com/gobuffalo/packr/v2 v2.8.3 h1:xE1yzvnO56cUC0sTpKR3DIbxZgB54AftTFMhB2XEWlY= +github.com/gobuffalo/packr/v2 v2.8.3/go.mod h1:0SahksCVcx4IMnigTjiFuyldmTrdTctXsOdiU5KwbKc= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/goccy/go-yaml v1.9.5 h1:Eh/+3uk9kLxG4koCX6lRMAPS1OaMSAi+FJcya0INdB0= @@ -454,8 +581,11 @@ github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblf github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godror/godror v0.24.2/go.mod h1:wZv/9vPiUib6tkoDl+AZ/QLf5YZgMravZ7jxH2eQWAE= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -469,12 +599,15 @@ github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzw github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ= github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -504,13 +637,17 @@ github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= +github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= +github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/cel-go v0.9.0/go.mod h1:U7ayypeSkw23szu4GaQTPJGx66c20mx8JklMSxrmI1w= +github.com/google/cel-spec v0.6.0/go.mod h1:Nwjgxy5CbjlPrtCWjeDjUyKMl8w41YBYGjsyDdqk0xA= +github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= -github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0= -github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -523,8 +660,10 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-containerregistry v0.5.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0= github.com/google/go-containerregistry v0.8.0 h1:mtR24eN6rapCN+shds82qFEIWWmg64NPMuyCNT7/Ogc= github.com/google/go-containerregistry v0.8.0/go.mod h1:wW5v71NHGnQyb4k+gSshjxidrC7lN33MdWEn+Mz9TsI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -556,6 +695,8 @@ github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= @@ -564,18 +705,27 @@ github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/Oth github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= +github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw= +github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= +github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= +github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= +github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= @@ -586,6 +736,7 @@ github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyN github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= @@ -597,6 +748,7 @@ github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iP github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= @@ -624,6 +776,9 @@ github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpT github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= +github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -635,7 +790,9 @@ github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/intel/goresctrl v0.2.0/go.mod h1:+CZdzouYFn5EsxgqAQTEzMfwKwuc0fVdMrT9FCCAVRQ= github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= +github.com/j-keck/arping v1.0.2/go.mod h1:aJbELhR92bSk7tp79AWM/ftfc90EfEi2bQJrbBFOsPw= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -644,13 +801,20 @@ github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg= github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jmoiron/sqlx v1.3.4/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= +github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= +github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= +github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -662,6 +826,10 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/karrick/godirwalk v1.15.8/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= +github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw= +github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= @@ -677,6 +845,7 @@ github.com/klauspost/compress v1.15.2/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHU github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kortschak/utter v1.0.1/go.mod h1:vSmSjbyrlKjjsL71193LmzBOKgwePk9DH6uFaWHIInc= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -689,18 +858,39 @@ github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.5 h1:J+gdV2cUmX7ZqL2B0lFcW0m+egaHC2V3lpO8nWxyYiQ= +github.com/lib/pq v1.10.5/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= +github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo= +github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/markbates/errx v1.1.0 h1:QDFeR+UP95dO12JgW+tgi2UVfo0V8YBHiUIOaeBPiEI= +github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc= +github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY= +github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI= +github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= +github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= @@ -719,12 +909,22 @@ github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOA github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= +github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= +github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= +github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= @@ -732,12 +932,19 @@ github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WT github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/mitchellh/cli v1.1.2/go.mod h1:6iaV0fGdElS6dPBx0EApTxHrcWvmJphyh2n8YBLPPZ4= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= @@ -745,13 +952,24 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= +github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= +github.com/moby/sys/mountinfo v0.5.0 h1:2Ks8/r6lopsxWi9m58nlwjaeSzUX9iiL1vj5qB/9ObI= +github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= +github.com/moby/sys/signal v0.6.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn88Kg8Tg= github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ= -github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= +github.com/moby/sys/symlink v0.2.0/go.mod h1:7uZVF2dqJjG/NsClqul95CqKOBRQyYSNnJ6BMgR/gFs= github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= +github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A= +github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc= +github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -761,36 +979,46 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/ohler55/ojg v1.14.0 h1:DyHomsCwofNswmKj7BLMdx51xnKbXxgIo1rVWCaBcNk= github.com/ohler55/ojg v1.14.0/go.mod h1:3+GH+0PggMKocQtbZCrFifal3yRpHiBT4QUkxFJI6e8= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= +github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= @@ -800,14 +1028,17 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/image-spec v1.0.2-0.20211117181255-693428a734f5 h1:q37d91F6BO4Jp1UqWiun0dUFYaqv6WsKTLTCaWv+8LY= github.com/opencontainers/image-spec v1.0.2-0.20211117181255-693428a734f5/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec= +github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0= github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= +github.com/opencontainers/runc v1.1.0/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= @@ -818,6 +1049,8 @@ github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mo github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= +github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= @@ -828,6 +1061,8 @@ github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3v github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc= +github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -839,6 +1074,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/poy/onpar v0.0.0-20190519213022-ee068f8ea4d1 h1:oL4IBbcqwhhNWh31bjOX8C/OCy0zs9906d/VUru+bqg= +github.com/poy/onpar v0.0.0-20190519213022-ee068f8ea4d1/go.mod h1:nSbFQvMj97ZyhFRSJYtut+msi4sOY6zJDGCdSc+/rZU= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -847,10 +1084,14 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= @@ -859,6 +1100,12 @@ github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8 github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.28.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.34.0 h1:RBmGO9d/FVjqHT0yUGQwBJhkwKV+wPCn7KGpvfab0uE= +github.com/prometheus/common v0.34.0/go.mod h1:gB3sOl7P0TvJabZpLY5uQMpUqRCPPCyRLCZYc7JZTNE= github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -868,8 +1115,9 @@ github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDa github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/r3labs/diff/v2 v2.15.1 h1:EOrVqPUzi+njlumoqJwiS/TgGgmZo83619FNDB9xQUg= github.com/r3labs/diff/v2 v2.15.1/go.mod h1:I8noH9Fc2fjSaMxqF3G2lhDdC0b+JXCfyx85tWFM9kc= @@ -878,20 +1126,34 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/rubenv/sql-migrate v0.0.0-20210614095031-55d5740dbbcc/go.mod h1:HFLT6i9iR4QBOF5rdCyjddC9t59ArqWJV2xx+jwcCMo= +github.com/rubenv/sql-migrate v1.1.1 h1:haR5Hn8hbW9/SpAICrXoZqXnywS7Q5WijwkQENPeNWY= +github.com/rubenv/sql-migrate v1.1.1/go.mod h1:/7TZymwxN8VWumcIxw1jjHEcR1djpdkMHQPT4FWdnbQ= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= +github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= +github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= +github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= +github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= @@ -906,6 +1168,7 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1 github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= @@ -913,10 +1176,13 @@ github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY52 github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4= github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= @@ -930,6 +1196,7 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= @@ -955,7 +1222,9 @@ github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= @@ -969,9 +1238,11 @@ github.com/vbauerster/mpb/v7 v7.4.1/go.mod h1:Ygg2mV9Vj9sQBWqsK2m2pidcf9H3s6bNKt github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= +github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/whilp/git-urls v1.0.0 h1:95f6UMWN5FKW71ECsXRUd3FVYiXdrE7aX4NZKcPmIjU= @@ -982,8 +1253,12 @@ github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6e github.com/xanzy/ssh-agent v0.3.1 h1:AmzO1SSWxw73zxFZPRwaMN1MohDw8UyHnmuxyceTEGo= github.com/xanzy/ssh-agent v0.3.1/go.mod h1:QIE4lCeL7nkC25x+yA3LBIYfwCc1TFziCtG7cBAac6w= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= @@ -995,13 +1270,20 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI= github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= +github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE= github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= +github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= +github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= +github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= @@ -1009,6 +1291,10 @@ go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3 go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs= +go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= +go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE= +go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc= +go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4= go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= @@ -1016,24 +1302,49 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.28.0/go.mod h1:vEhqr0m4eTc+DWxfsXoXue2GBgV2uUwVznkGIHW/e5w= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= +go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= +go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs= +go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0/go.mod h1:VpP4/RMn8bv8gNo9uK7/IMY4mtWLELsS+JIP0inH0h4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0/go.mod h1:hO1KLR7jcKaDDKDkvI9dP/FIhpmna5lkqPUQdEjFAM8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.3.0/go.mod h1:keUU7UfnwWTWpJ+FWnyqmogPa82nuU5VUANFq49hlMY= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.3.0/go.mod h1:QNX1aly8ehqqX1LEa6YniTU7VY9I6R3X/oPxhGdTceE= +go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= +go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= +go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= +go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs= +go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= +go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= +go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= +go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v0.11.0/go.mod h1:QpEjXPrNQzrFDZgoTo49dgHR9RYRSrg3NAKnUGl9YpQ= go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= go.starlark.net v0.0.0-20220328144851-d1966c6b9fcd h1:Uo/x0Ir5vQJ+683GXB9Ug+4fcjsbp7z7Ul8UaZbhsRM= go.starlark.net v0.0.0-20220328144851-d1966c6b9fcd/go.mod h1:t3mmBBPzAVvK0L0n1drDmrQsJ8FoIx4INCqVMTr/Zo0= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -1041,16 +1352,20 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1134,6 +1449,7 @@ golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -1142,12 +1458,17 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= @@ -1209,6 +1530,7 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1248,6 +1570,7 @@ golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1256,6 +1579,7 @@ golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1263,6 +1587,7 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1274,25 +1599,34 @@ golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210903071746-97244b99971b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1300,7 +1634,9 @@ golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc= golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 h1:EH1Deb8WZJ0xc0WK//leUHXcX9aLE5SymusoTmMZye8= golang.org/x/term v0.0.0-20220411215600-e5f449aeb171/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1318,13 +1654,14 @@ golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220411224347-583f2d630306 h1:+gHMid33q6pen7kv9xvT+JRinntgeXO2AeZVd0AWD3w= golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -1340,10 +1677,12 @@ golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190706070813-72ffa07ba3db/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1361,21 +1700,25 @@ golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200308013534-11ec41452d41/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200616133436-c1934b75d054/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20200916195026-c9a70fc28ce3/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= @@ -1384,6 +1727,8 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.6-0.20210820212750-d4cc65f0b2ff/go.mod h1:YD9qOF0M9xpSpdWTBbzEl5e/RnCefISl8E5Noe10jFM= +golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1463,17 +1808,20 @@ google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201102152239-715cce707fb0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= @@ -1520,6 +1868,8 @@ google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220426171045-31bebdecfb46 h1:G1IeWbjrqEq9ChWxEuRPJu6laA67+XgTFHVSAvepr38= +google.golang.org/genproto v0.0.0-20220426171045-31bebdecfb46/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -1554,6 +1904,8 @@ google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ5 google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.46.0 h1:oCjezcn6g6A75TGoKYBPgKmVBLexhYLM6MebdrPApP8= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -1583,8 +1935,10 @@ gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qS gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= +gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= @@ -1615,6 +1969,8 @@ gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81 gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= +helm.sh/helm/v3 v3.8.2 h1:HDhe2nKek976VLMPZlIgJbNqwcqvHYBp1qy+sXQ4jiY= +helm.sh/helm/v3 v3.8.2/go.mod h1:NxtE2KObf2PrzDl6SIamPFPKyAqWi10iWuvKlQn/Yao= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1622,63 +1978,89 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= -k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= -k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= -k8s.io/api v0.25.0-alpha.0 h1:BiYeMLWoLcGGWE46gdnlwluFa23+Hr3I2Qp8U6c2wYY= -k8s.io/api v0.25.0-alpha.0/go.mod h1:sOibYBePcsE/DBjbbi+Z+FCG9lFPR7xuKSR2r6RTCNs= -k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= -k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= -k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= -k8s.io/apimachinery v0.25.0-alpha.0 h1:gAzcXIp+FkB3w8+m34na2qxSScwQWKtryRU8JfkS/NU= -k8s.io/apimachinery v0.25.0-alpha.0/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= +k8s.io/api v0.23.5 h1:zno3LUiMubxD/V1Zw3ijyKO3wxrhbUF1Ck+VjBvfaoA= +k8s.io/api v0.23.5/go.mod h1:Na4XuKng8PXJ2JsploYYrivXrINeTaycCGcYgF91Xm8= +k8s.io/apiextensions-apiserver v0.23.5/go.mod h1:ntcPWNXS8ZPKN+zTXuzYMeg731CP0heCTl6gYBxLcuQ= +k8s.io/apiextensions-apiserver v0.23.6 h1:v58cQ6Z0/GK1IXYr+oW0fnYl52o9LTY0WgoWvI8uv5Q= +k8s.io/apiextensions-apiserver v0.23.6/go.mod h1:YVh17Mphv183THQJA5spNFp9XfoidFyL3WoDgZxQIZU= +k8s.io/apimachinery v0.23.5 h1:Va7dwhp8wgkUPWsEXk6XglXWU4IKYLKNlv8VkX7SDM0= +k8s.io/apimachinery v0.23.5/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM= k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= -k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= -k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= -k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= -k8s.io/client-go v0.25.0-alpha.0 h1:IwIWODi6Uz7QlOU8J2tv8APdDtytoowveKF9IoKpHa4= -k8s.io/client-go v0.25.0-alpha.0/go.mod h1:V7vCXDCdD8Goobi4oQhsSNXtlWfyBJi+LHFaiYWpR5s= -k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= -k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= -k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= +k8s.io/apiserver v0.22.5/go.mod h1:s2WbtgZAkTKt679sYtSudEQrTGWUSQAPe6MupLnlmaQ= +k8s.io/apiserver v0.23.5/go.mod h1:7wvMtGJ42VRxzgVI7jkbKvMbuCbVbgsWFT7RyXiRNTw= +k8s.io/apiserver v0.23.6 h1:p94LiXcsSnpSDIl4cv98liBuFKcaygSCNopFNfMg/Ac= +k8s.io/apiserver v0.23.6/go.mod h1:5PU32F82tfErXPmf7FXhd/UcuLfh97tGepjKUgJ2atg= +k8s.io/cli-runtime v0.23.5 h1:Z7XUpGoJZYZB2uNjQfJjMbyDKyVkoBGye62Ap0sWQHY= +k8s.io/cli-runtime v0.23.5/go.mod h1:oY6QDF2qo9xndSq32tqcmRp2UyXssdGrLfjAVymgbx4= +k8s.io/client-go v0.23.5 h1:zUXHmEuqx0RY4+CsnkOn5l0GU+skkRXKGJrhmE2SLd8= +k8s.io/client-go v0.23.5/go.mod h1:flkeinTO1CirYgzMPRWxUCnV0G4Fbu2vLhYCObnt/r4= +k8s.io/code-generator v0.19.7/go.mod h1:lwEq3YnLYb/7uVXLorOJfxg+cUu2oihFhHZ0n9NIla0= +k8s.io/code-generator v0.23.5/go.mod h1:S0Q1JVA+kSzTI1oUvbKAxZY/DYbA/ZUb4Uknog12ETk= +k8s.io/code-generator v0.23.6/go.mod h1:S0Q1JVA+kSzTI1oUvbKAxZY/DYbA/ZUb4Uknog12ETk= +k8s.io/component-base v0.23.5 h1:8qgP5R6jG1BBSXmRYW+dsmitIrpk8F/fPEvgDenMCCE= +k8s.io/component-base v0.23.5/go.mod h1:c5Nq44KZyt1aLl0IpHX82fhsn84Sb0jjzwjpcA42bY0= +k8s.io/component-helpers v0.23.5/go.mod h1:5riXJgjTIs+ZB8xnf5M2anZ8iQuq37a0B/0BgoPQuSM= k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM= k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= k8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc= +k8s.io/cri-api v0.23.1/go.mod h1:REJE3PSU0h/LOV1APBrupxrEJqnoxZC8KWzkBUHwrK4= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20200428234225-8167cfdcfc14/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20201113003025-83324d819ded/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= +k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/klog/v2 v2.60.1 h1:VW25q3bZx9uE3vvdL6M8ezOX79vA2Aq1nEWLqNQclHc= k8s.io/klog/v2 v2.60.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= -k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42/go.mod h1:Z/45zLw8lUo4wdiUkI+v/ImEGAvu3WatcZl3lPMR4Rk= -k8s.io/kube-openapi v0.0.0-20220413171646-5e7f5fdc6da6 h1:nBQrWPlrNIiw0BsX6a6MKr1itkm0ZS0Nl97kNLitFfI= -k8s.io/kube-openapi v0.0.0-20220413171646-5e7f5fdc6da6/go.mod h1:daOouuuwd9JXpv1L7Y34iV3yf6nxzipkKMWWlqlvK9M= +k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= +k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 h1:E3J9oCLlaobFUqsjG9DfKbP2BmgwBL2p7pn0A3dG9W4= +k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= +k8s.io/kubectl v0.23.5 h1:DmDULqCaF4qstj0Im143XmncvqWtJxHzK8IrW2BzlU0= +k8s.io/kubectl v0.23.5/go.mod h1:lLgw7cVY8xbd7o637vOXPca/w6HC205KsPCRDYRCxwE= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= +k8s.io/metrics v0.23.5/go.mod h1:WNAtV2a5BYbmDS8+7jSqYYV6E3efuGTpIwJ8PTD1wgs= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 h1:HNSDgDCrr/6Ly3WEGKZftiE7IY19Vz2GdbOCyI4qqhc= k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +oras.land/oras-go v1.1.1 h1:gI00ftziRivKXaw1BdMeEoIA4uBgga33iVlOsEwefFs= +oras.land/oras-go v1.1.1/go.mod h1:n2TE1ummt9MUyprGhT+Q7kGZUF4kVUpYysPFxeV2IpQ= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.30/go.mod h1:fEO7lRTdivWO2qYVCVG7dEADOMo/MLDCVr8So2g88Uw= +sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 h1:kDi4JBNAsJWfz1aEXhO8Jg87JJaPNLh5tIzYHgStQ9Y= sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY= sigs.k8s.io/kind v0.12.0 h1:LFynXwQkH1MrWI8pM1FQty0oUwEKjU5EkMaVZaPld8E= sigs.k8s.io/kind v0.12.0/go.mod h1:EcgDSBVxz8Bvm19fx8xkioFrf9dC30fMJdOTXBSGNoM= +sigs.k8s.io/kustomize/api v0.10.1/go.mod h1:2FigT1QN6xKdcnGS2Ppp1uIWrtWN28Ms8A3OZUZhwr8= sigs.k8s.io/kustomize/api v0.11.4 h1:/0Mr3kfBBNcNPOW5Qwk/3eb8zkswCwnqQxxKtmrTkRo= sigs.k8s.io/kustomize/api v0.11.4/go.mod h1:k+8RsqYbgpkIrJ4p9jcdPqe8DprLxFUUO0yNOq8C+xI= +sigs.k8s.io/kustomize/cmd/config v0.10.2/go.mod h1:K2aW7nXJ0AaT+VA/eO0/dzFLxmpFcTzudmAgDwPY1HQ= +sigs.k8s.io/kustomize/kustomize/v4 v4.4.1/go.mod h1:qOKJMMz2mBP+vcS7vK+mNz4HBLjaQSWRY22EF6Tb7Io= +sigs.k8s.io/kustomize/kyaml v0.13.0/go.mod h1:FTJxEZ86ScK184NpGSAQcfEqee0nul8oLCK30D47m4E= sigs.k8s.io/kustomize/kyaml v0.13.6 h1:eF+wsn4J7GOAXlvajv6OknSunxpcOBQQqsnPxObtkGs= sigs.k8s.io/kustomize/kyaml v0.13.6/go.mod h1:yHP031rn1QX1lr/Xd934Ri/xdVNG8BE2ECa78Ht/kEg= +sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y= sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index 0b26b404c..e080d8075 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -4,13 +4,23 @@ import ( "fmt" "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/types" - "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/utils/versions" "github.com/kluctl/kluctl/v2/pkg/yaml" + "github.com/pkg/errors" + log "github.com/sirupsen/logrus" + "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/chart/loader" + "helm.sh/helm/v3/pkg/chartutil" + "helm.sh/helm/v3/pkg/cli" + "helm.sh/helm/v3/pkg/cli/values" + "helm.sh/helm/v3/pkg/getter" + "helm.sh/helm/v3/pkg/registry" + "helm.sh/helm/v3/pkg/release" + "helm.sh/helm/v3/pkg/repo" "io/ioutil" "os" - "os/exec" "path/filepath" "regexp" "sort" @@ -36,35 +46,8 @@ func NewHelmChart(configFile string) (*helmChart, error) { return hc, nil } -func (c *helmChart) withRepoContext(cb func(repoName string) error) error { - needRepo := false - repoName := "stable" - - if c.Config.Repo != nil && *c.Config.Repo != "stable" { - needRepo = true - repoName = fmt.Sprintf("kluctl-%s", utils.Sha256String(*c.Config.Repo))[:16] - } - - if needRepo { - _, _ = c.doHelm([]string{"repo", "remove", repoName}, true) - _, err := c.doHelm([]string{"repo", "add", repoName, *c.Config.Repo}, false) - if err != nil { - return err - } - defer func() { - _, _ = c.doHelm([]string{"repo", "remove", repoName}, true) - }() - } else { - _, err := c.doHelm([]string{"repo", "update"}, false) - if err != nil { - return err - } - } - return cb(repoName) -} - func (c *helmChart) GetChartName() (string, error) { - if c.Config.Repo != nil && strings.HasPrefix(*c.Config.Repo, "oci://") { + if c.Config.Repo != nil && registry.IsOCI(*c.Config.Repo) { s := strings.Split(*c.Config.Repo, "/") chartName := s[len(s)-1] if m, _ := regexp.MatchString(`[a-zA-Z_-]+`, chartName); !m { @@ -78,6 +61,16 @@ func (c *helmChart) GetChartName() (string, error) { return *c.Config.ChartName, nil } +func (c *helmChart) buildHelmConfig() (*action.Configuration, error) { + rc, err := registry.NewClient() + if err != nil { + return nil, err + } + return &action.Configuration{ + RegistryClient: rc, + }, nil +} + func (c *helmChart) Pull() error { chartName, err := c.GetChartName() if err != nil { @@ -89,25 +82,37 @@ func (c *helmChart) Pull() error { rmDir := filepath.Join(targetDir, chartName) _ = os.RemoveAll(rmDir) - var args []string - if c.Config.Repo != nil && strings.HasPrefix(*c.Config.Repo, "oci://") { - args = []string{"pull", *c.Config.Repo, "--destination", targetDir, "--untar"} - args = append(args, "--version") - args = append(args, *c.Config.ChartVersion) - _, err = c.doHelm(args, false) + cfg, err := c.buildHelmConfig() + if err != nil { return err + } + a := action.NewPullWithOpts(action.WithConfig(cfg)) + a.Settings = cli.New() + a.Untar = true + a.DestDir = targetDir + a.Version = *c.Config.ChartVersion + + var out string + if registry.IsOCI(*c.Config.Repo) { + out, err = a.Run(*c.Config.Repo) } else { - return c.withRepoContext(func(repoName string) error { - args = []string{"pull", fmt.Sprintf("%s/%s", repoName, chartName), "--destination", targetDir, "--untar"} - args = append(args, "--version", *c.Config.ChartVersion) - _, err = c.doHelm(args, false) - return err - }) + a.RepoURL = *c.Config.Repo + out, err = a.Run(chartName) } + // a bug in the Pull command causes this directory to be created by accident + _ = os.RemoveAll(rmDir + fmt.Sprintf("-%s.tar.gz", a.Version)) + _ = os.RemoveAll(rmDir + fmt.Sprintf("-%s.tgz", a.Version)) + if out != "" { + log.Info(out) + } + if err != nil { + return err + } + return nil } func (c *helmChart) CheckUpdate() (string, bool, error) { - if c.Config.Repo != nil && strings.HasPrefix(*c.Config.Repo, "oci://") { + if c.Config.Repo != nil && registry.IsOCI(*c.Config.Repo) { return "", false, nil } chartName, err := c.GetChartName() @@ -115,37 +120,39 @@ func (c *helmChart) CheckUpdate() (string, bool, error) { return "", false, err } var latestVersion string - err = c.withRepoContext(func(repoName string) error { - chartName := fmt.Sprintf("%s/%s", repoName, chartName) - args := []string{"search", "repo", chartName, "-oyaml", "-l"} - stdout, err := c.doHelm(args, false) - if err != nil { - return err - } - // ensure we didn't get partial matches - var lm []map[string]string - var ls versions.LooseVersionSlice - err = yaml.ReadYamlBytes(stdout, &lm) - if err != nil { - return err - } - for _, x := range lm { - if n, ok := x["name"]; ok && n == chartName { - if v, ok := x["version"]; ok { - ls = append(ls, versions.LooseVersion(v)) - } - } - } - if len(ls) == 0 { - return fmt.Errorf("helm chart %s not found in repository", chartName) - } - sort.Sort(ls) - latestVersion = string(ls[len(ls)-1]) - return nil - }) + + settings := cli.New() + e := repo.Entry{ + URL: *c.Config.Repo, + Name: chartName, + } + r, err := repo.NewChartRepository(&e, getter.All(settings)) + if err != nil { + return "", false, err + } + + indexFile, err := r.DownloadIndexFile() + if err != nil { + return "", false, err + } + + index, err := repo.LoadIndexFile(indexFile) if err != nil { return "", false, err } + + indexEntry, ok := index.Entries[*c.Config.ChartName] + if !ok || len(indexEntry) == 0 { + return "", false, fmt.Errorf("helm chart %s not found in repo index", *c.Config.ChartName) + } + + var ls versions.LooseVersionSlice + for _, x := range indexEntry { + ls = append(ls, versions.LooseVersion(x.Version)) + } + sort.Stable(ls) + latestVersion = string(ls[len(ls)-1]) + updated := latestVersion != *c.Config.ChartVersion return latestVersion, updated, nil } @@ -160,48 +167,109 @@ func (c *helmChart) Render(k *k8s.K8sCluster) error { valuesPath := yaml.FixPathExt(filepath.Join(dir, "helm-values.yml")) outputPath := filepath.Join(dir, c.Config.Output) - args := []string{"template", c.Config.ReleaseName, chartDir} + gvs, err := k.GetAllGroupVersions() + if err != nil { + return err + } + + cfg, err := c.buildHelmConfig() + if err != nil { + return err + } + + settings := cli.New() + valueOpts := values.Options{ + ValueFiles: []string{valuesPath}, + } + + kubeVersion, err := chartutil.ParseKubeVersion(k.ServerVersion.String()) + if err != nil { + return err + } namespace := "default" if c.Config.Namespace != nil { namespace = *c.Config.Namespace } - if utils.Exists(valuesPath) { - args = append(args, "-f", valuesPath) - } - args = append(args, "-n", namespace) + client := action.NewInstall(cfg) + client.DryRun = true + client.Namespace = namespace + client.ReleaseName = c.Config.ReleaseName + client.Replace = true + client.ClientOnly = true + client.KubeVersion = kubeVersion if c.Config.SkipCRDs != nil && *c.Config.SkipCRDs { - args = append(args, "--skip-crds") + client.SkipCRDs = true } else { - args = append(args, "--include-crds") + client.IncludeCRDs = true + } + for _, gv := range gvs { + client.APIVersions = append(client.APIVersions, gv) } - args = append(args, "--skip-tests") - gvs, err := k.GetAllGroupVersions() + p := getter.All(settings) + vals, err := valueOpts.MergeValues(p) if err != nil { return err } - for _, gv := range gvs { - args = append(args, fmt.Sprintf("--api-versions=%s", gv)) + + // Check chart dependencies to make sure all are present in /charts + chartRequested, err := loader.Load(chartDir) + if err != nil { + return err } - args = append(args, fmt.Sprintf("--kube-version=%s", k.ServerVersion.String())) - rendered, err := c.doHelm(args, false) + if err := checkIfInstallable(chartRequested); err != nil { + return err + } + + if chartRequested.Metadata.Deprecated { + log.Warningf("Chart %s is deprecated", *c.Config.ChartName) + } + + rel, err := client.Run(chartRequested, vals) if err != nil { return err } - parsed, err := yaml.ReadYamlAllBytes(rendered) + var parsed []*uo.UnstructuredObject - for _, o := range parsed { - m, ok := o.(map[string]interface{}) - if !ok { - return fmt.Errorf("object is not a map") + doParse := func(s string) error { + m, err := yaml.ReadYamlAllString(s) + if err != nil { + return err } - o := uo.FromMap(m) + for _, x := range m { + x2, ok := x.(map[string]interface{}) + if !ok { + return fmt.Errorf("yaml object is not a map") + } + parsed = append(parsed, uo.FromMap(x2)) + } + return nil + } + err = doParse(rel.Manifest) + if err != nil { + return err + } + + if !client.DisableHooks { + for _, m := range rel.Hooks { + if isTestHook(m) { + continue + } + err = doParse(m.Manifest) + if err != nil { + return err + } + } + } + + var fixed []interface{} + for _, o := range parsed { // "helm install" will deploy resources to the given namespace automatically, but "helm template" does not // add the necessary namespace in the rendered resources err = k8s.UnwrapListItems(o, true, func(o *uo.UnstructuredObject) error { @@ -211,8 +279,9 @@ func (c *helmChart) Render(k *k8s.K8sCluster) error { if err != nil { return err } + fixed = append(fixed, o) } - rendered, err = yaml.WriteYamlAllBytes(parsed) + rendered, err := yaml.WriteYamlAllBytes(fixed) if err != nil { return err } @@ -224,51 +293,21 @@ func (c *helmChart) Render(k *k8s.K8sCluster) error { return nil } -func (c *helmChart) doHelm(args []string, ignoreStdErr bool) ([]byte, error) { - cmd := exec.Command("helm", args...) - cmd.Env = append(os.Environ(), "HELM_EXPERIMENTAL_OCI=true") - - if ignoreStdErr { - stderrPipe, err := cmd.StderrPipe() - if err != nil { - return nil, err - } - go func() { - buf := make([]byte, 1024) - for true { - n, err := stderrPipe.Read(buf) - if err != nil || n == 0 { - return - } - } - }() - } else { - cmd.Stderr = os.Stderr - } - - stdoutPipe, err := cmd.StdoutPipe() - if err != nil { - return nil, err - } - - err = cmd.Start() - if err != nil { - return nil, err +func checkIfInstallable(ch *chart.Chart) error { + switch ch.Metadata.Type { + case "", "application": + return nil } - defer cmd.Process.Kill() + return errors.Errorf("%s charts are not installable", ch.Metadata.Type) +} - stdout, err := ioutil.ReadAll(stdoutPipe) - if err != nil { - return nil, err - } - ps, err := cmd.Process.Wait() - if err != nil { - return nil, err - } - if ps.ExitCode() != 0 { - return nil, fmt.Errorf("helm returned non-zero exit code %d", ps.ExitCode()) +func isTestHook(h *release.Hook) bool { + for _, e := range h.Events { + if e == release.HookTest { + return true + } } - return stdout, nil + return false } func (c *helmChart) Save() error { From ffa66e02529d312bdee4f1d7739a66f6166dae80 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 27 Apr 2022 17:16:35 +0200 Subject: [PATCH 0718/2916] build: Remove traces of helm installation --- .github/ISSUE_TEMPLATE/BUG.yml | 8 -------- .github/workflows/tests.yml | 5 ----- 2 files changed, 13 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/BUG.yml b/.github/ISSUE_TEMPLATE/BUG.yml index 1dd5cccc3..be2370e3a 100644 --- a/.github/ISSUE_TEMPLATE/BUG.yml +++ b/.github/ISSUE_TEMPLATE/BUG.yml @@ -41,14 +41,6 @@ body: placeholder: "v1.22.0" validations: required: true - - type: input - id: helm - attributes: - label: Helm Version - description: "Please provide the full Helm version. (Output of `helm version`)" - placeholder: "v3.6.3" - validations: - required: true - type: textarea id: bug-description attributes: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 932808d7f..1ad4ff67a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -211,7 +211,6 @@ jobs: run: | echo "KUBECTL_VERSION=1.21.5" >> $GITHUB_ENV echo "KIND_VERSION=0.11.1" >> $GITHUB_ENV - echo "HELM_VERSION=3.6.3" >> $GITHUB_ENV echo "DOCKER_VERSION=20.10.9" >> $GITHUB_ENV - name: Download required tools shell: bash @@ -220,9 +219,6 @@ jobs: $SUDO mv kubectl$TOOLS_EXE "$TOOLS_TARGET_DIR/" curl -L -o kind$TOOLS_EXE https://github.com/kubernetes-sigs/kind/releases/download/v${KIND_VERSION}/kind-${TOOLS_OS}-amd64 && \ $SUDO mv kind$TOOLS_EXE "$TOOLS_TARGET_DIR/" - curl -L -o helm.tar.gz https://get.helm.sh/helm-v$HELM_VERSION-${TOOLS_OS}-amd64.tar.gz && \ - tar xzf helm.tar.gz && \ - $SUDO mv ${TOOLS_OS}-amd64/helm$TOOLS_EXE "$TOOLS_TARGET_DIR/" if [ "${{ runner.os }}" == "macOS" ]; then curl -L -o docker.tar.gz https://download.docker.com/mac/static/stable/x86_64/docker-$DOCKER_VERSION.tgz tar xzf docker.tar.gz @@ -240,7 +236,6 @@ jobs: run: | kubectl version || true kind version || true - helm version || true - name: Start kind cluster shell: bash run: | From 98f296647e8617d15990d5719d129109c0401218 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 27 Apr 2022 17:16:44 +0200 Subject: [PATCH 0719/2916] fix: Fix crash in GitCredentialsFileAuthProvider --- pkg/git/auth/git_credentials_file.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/git/auth/git_credentials_file.go b/pkg/git/auth/git_credentials_file.go index ea665c16f..b3a3b9f97 100644 --- a/pkg/git/auth/git_credentials_file.go +++ b/pkg/git/auth/git_credentials_file.go @@ -25,7 +25,7 @@ func (a *GitCredentialsFileAuthProvider) BuildAuth(gitUrl git_url.GitUrl) AuthMe return AuthMethodAndCA{} } auth := a.tryBuildAuth(gitUrl, filepath.Join(home, ".git-credentials")) - if auth.AuthMethod != nil { + if auth != nil { return *auth } From d3ad0d0344e9b8e0e27cf6df4fbe3aa973c6df29 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 1 Apr 2022 15:23:58 +0200 Subject: [PATCH 0720/2916] fix: Allow better debugging of ssh auth errors --- pkg/git/auth/ssh_auth_provider.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/pkg/git/auth/ssh_auth_provider.go b/pkg/git/auth/ssh_auth_provider.go index d3c2a64ca..b37d242f1 100644 --- a/pkg/git/auth/ssh_auth_provider.go +++ b/pkg/git/auth/ssh_auth_provider.go @@ -50,32 +50,39 @@ func (a *sshDefaultIdentityAndAgent) Signers() ([]ssh.Signer, error) { } func (a *sshDefaultIdentityAndAgent) addDefaultIdentity(gitUrl git_url.GitUrl) { + log.Debugf("trying to add default identity") u, err := user.Current() if err != nil { log.Debugf("No current user: %v", err) } else { - signer, err := a.authProvider.readKey(filepath.Join(u.HomeDir, ".ssh", "id_rsa")) + path := filepath.Join(u.HomeDir, ".ssh", "id_rsa") + signer, err := a.authProvider.readKey(path) if err != nil && !os.IsNotExist(err) { log.Warningf("Failed to read default identity file for url %s: %v", gitUrl.String(), err) } else if signer != nil { + log.Debugf("...added '%s' as default identity", path) a.signers = append(a.signers, signer) } } } func (a *sshDefaultIdentityAndAgent) addConfigIdentities(gitUrl git_url.GitUrl) { + log.Debugf("trying to add identities from ssh config") for _, id := range ssh_config.GetAll(gitUrl.Hostname(), "IdentityFile") { - id = utils.ExpandPath(id) - signer, err := a.authProvider.readKey(id) + expanded := utils.ExpandPath(id) + log.Debugf("...trying '%s' (expanded: '%s')", id, expanded) + signer, err := a.authProvider.readKey(expanded) if err != nil && !os.IsNotExist(err) { log.Warningf("Failed to read key %s for url %s: %v", id, gitUrl.String(), err) } else if err == nil { + log.Debugf("...added '%s' from ssh config", expanded) a.signers = append(a.signers, signer) } } } func (a *sshDefaultIdentityAndAgent) addAgentIdentities(gitUrl git_url.GitUrl) { + log.Debugf("trying to add agent keys") agent, _, err := sshagent.New() if err != nil { log.Warningf("Failed to connect to ssh agent for url %s: %v", gitUrl.String(), err) @@ -85,6 +92,7 @@ func (a *sshDefaultIdentityAndAgent) addAgentIdentities(gitUrl git_url.GitUrl) { log.Warningf("Failed to get signers from ssh agent for url %s: %v", gitUrl.String(), err) return } + log.Debugf("...added %d agent keys", len(signers)) a.signers = append(a.signers, signers...) } } From cc1be2ab170aeed08757bfabdd1a68d8b61e38ad Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 27 Apr 2022 18:00:52 +0200 Subject: [PATCH 0721/2916] fix: Fix matching of patterns on windows --- pkg/utils/embed_util/packer/packer.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/utils/embed_util/packer/packer.go b/pkg/utils/embed_util/packer/packer.go index 9714cedda..d02da2d7f 100644 --- a/pkg/utils/embed_util/packer/packer.go +++ b/pkg/utils/embed_util/packer/packer.go @@ -13,6 +13,7 @@ import ( log "github.com/sirupsen/logrus" "io/fs" "io/ioutil" + "os" "path/filepath" "reflect" "strings" @@ -74,7 +75,7 @@ func findFiles(dir string, patterns []string) (map[string]int64, error) { } match := false for _, p := range globs { - if p.Match(rel) { + if p.Match(strings.ReplaceAll(rel, string(os.PathSeparator), "/")) { match = true break } From 36366c4071c511980c56984197ab0515c7db899b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 27 Apr 2022 22:54:06 +0200 Subject: [PATCH 0722/2916] fix: Support BOMs in yaml files --- pkg/yaml/yaml.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pkg/yaml/yaml.go b/pkg/yaml/yaml.go index 271c761fe..1726b4dae 100644 --- a/pkg/yaml/yaml.go +++ b/pkg/yaml/yaml.go @@ -7,6 +7,8 @@ import ( "fmt" "github.com/goccy/go-yaml" "github.com/kluctl/kluctl/v2/pkg/utils" + "golang.org/x/text/encoding/unicode" + "golang.org/x/text/transform" yaml3 "gopkg.in/yaml.v3" "io" "os" @@ -18,8 +20,9 @@ func newYamlDecoder(r io.Reader) *yaml.Decoder { return yaml.NewDecoder(r, yaml.Strict(), yaml.Validator(Validator)) } -func newYamlEncoder(w io.Writer) *yaml.Encoder { - return yaml.NewEncoder(w) +func newUnicodeReader(r io.Reader) io.Reader { + utf16bom := unicode.BOMOverride(unicode.UTF8.NewDecoder()) + return transform.NewReader(r, utf16bom) } func ReadYamlFile(p string, o interface{}) error { @@ -45,6 +48,8 @@ func ReadYamlBytes(b []byte, o interface{}) error { } func ReadYamlStream(r io.Reader, o interface{}) error { + r = newUnicodeReader(r) + var err error if _, ok := o.(*map[string]interface{}); ok { // much faster @@ -80,6 +85,8 @@ func ReadYamlAllBytes(b []byte) ([]interface{}, error) { } func ReadYamlAllStream(r io.Reader) ([]interface{}, error) { + r = newUnicodeReader(r) + // yaml.v3 is much faster then go-yaml d := yaml3.NewDecoder(r) From 9f5365d7f7d0d21715186b86fb10105a45ec16b5 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 27 Apr 2022 23:24:55 +0200 Subject: [PATCH 0723/2916] fix: Show which chart failed --- pkg/deployment/helm_chart.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index e080d8075..343c87b19 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -158,6 +158,18 @@ func (c *helmChart) CheckUpdate() (string, bool, error) { } func (c *helmChart) Render(k *k8s.K8sCluster) error { + chartName, err := c.GetChartName() + if err != nil { + return err + } + err = c.doRender(k) + if err != nil { + return fmt.Errorf("rendering helm chart %s for release %s has failed: %w", chartName, c.Config.ReleaseName, err) + } + return nil +} + +func (c *helmChart) doRender(k *k8s.K8sCluster) error { chartName, err := c.GetChartName() if err != nil { return err From c701aeda77082125e7d28cce837caed5f7fe7a22 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 28 Apr 2022 09:04:30 +0200 Subject: [PATCH 0724/2916] fix: Fallback to kubeconfig CA when kube-root-ca.crt is unavailable --- pkg/k8s/k8s_cluster.go | 6 ++++++ pkg/seal/sealer.go | 26 +++++++++++++++++++------- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index fec67980f..d9d9aa93b 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -40,6 +40,7 @@ type K8sCluster struct { context string DryRun bool + restConfig *rest.Config discovery *disk.CachedDiscoveryClient clientPool chan *parallelClientEntry @@ -92,6 +93,7 @@ func NewK8sCluster(context string, dryRun bool) (*K8sCluster, error) { } config.QPS = 10 config.Burst = 20 + k.restConfig = config apiHost, err := url.Parse(config.Host) if err != nil { @@ -162,6 +164,10 @@ func (k *K8sCluster) Context() string { return k.context } +func (k *K8sCluster) GetCA() []byte { + return k.restConfig.CAData +} + func (k *K8sCluster) updateResources(doLock bool) error { if doLock { k.mutex.Lock() diff --git a/pkg/seal/sealer.go b/pkg/seal/sealer.go index 75f8bb187..92dd6953d 100644 --- a/pkg/seal/sealer.go +++ b/pkg/seal/sealer.go @@ -15,6 +15,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/yaml" log "github.com/sirupsen/logrus" "golang.org/x/crypto/scrypt" + "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime/schema" "os" "path/filepath" @@ -61,15 +62,26 @@ func getClusterId(k *k8s.K8sCluster) (string, error) { Name: "kube-root-ca.crt", Namespace: "kube-system", }) - if err != nil { + if err != nil && !errors.IsNotFound(err) { return "", fmt.Errorf("failed to retrieve kube-root-ca.crt: %w", err) } - kubeRootCA, ok, err := o.GetNestedString("data", "ca.crt") - if err != nil { - return "", fmt.Errorf("failed to retrieve kube-root-ca.crt: %w", err) - } - if !ok { - return "", fmt.Errorf("failed to retrieve kube-root-ca.crt: ca.crt key is missing") + + var kubeRootCA string + if o != nil { + x, ok, err := o.GetNestedString("data", "ca.crt") + if err != nil { + return "", fmt.Errorf("failed to retrieve kube-root-ca.crt: %w", err) + } + if !ok { + return "", fmt.Errorf("failed to retrieve kube-root-ca.crt: ca.crt key is missing") + } + kubeRootCA = x + } else { + // fall-back to CA from kubeconfig + ca := k.GetCA() + if ca != nil { + kubeRootCA = string(ca) + } } return utils.Sha256String(kubeRootCA), nil } From f02fc7c234b372762676e850d82a4f4364cc1cb4 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 24 Mar 2022 08:14:15 +0100 Subject: [PATCH 0725/2916] fix: Ensure only one "accept host" prompts is shown at a time --- pkg/git/auth/ssh_known_hosts.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkg/git/auth/ssh_known_hosts.go b/pkg/git/auth/ssh_known_hosts.go index 2e0b06785..6a4225e5e 100644 --- a/pkg/git/auth/ssh_known_hosts.go +++ b/pkg/git/auth/ssh_known_hosts.go @@ -9,8 +9,11 @@ import ( "os" "path/filepath" "strconv" + "sync" ) +var askHostMutex sync.Mutex + func buildVerifyHostCallback(knownHosts []byte) func(hostname string, remote net.Addr, key ssh.PublicKey) error { return func(hostname string, remote net.Addr, key ssh.PublicKey) error { return verifyHost(hostname, remote, key, knownHosts) @@ -18,6 +21,10 @@ func buildVerifyHostCallback(knownHosts []byte) func(hostname string, remote net } func verifyHost(host string, remote net.Addr, key ssh.PublicKey, knownHosts []byte) error { + // Ensure only one prompt happens at a time + askHostMutex.Lock() + defer askHostMutex.Unlock() + hostKeyChecking := true if x, ok := os.LookupEnv("KLUCTL_SSH_DISABLE_STRICT_HOST_KEY_CHECKING"); ok { if b, err := strconv.ParseBool(x); err == nil && b { From f9da55be0f76c10380cfdb4598a8369a2b6979a4 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 28 Apr 2022 10:16:43 +0200 Subject: [PATCH 0726/2916] feat: Implememt http based secrets source --- pkg/seal/secrets_loader.go | 10 +++ pkg/seal/secrets_loader_http.go | 138 ++++++++++++++++++++++++++++++++ pkg/types/secrets_source.go | 12 +++ pkg/types/url.go | 27 +++++++ pkg/utils/prompts.go | 29 +++++++ 5 files changed, 216 insertions(+) create mode 100644 pkg/seal/secrets_loader_http.go create mode 100644 pkg/types/url.go diff --git a/pkg/seal/secrets_loader.go b/pkg/seal/secrets_loader.go index ca4a0ac21..8241d9547 100644 --- a/pkg/seal/secrets_loader.go +++ b/pkg/seal/secrets_loader.go @@ -12,15 +12,23 @@ import ( "strings" ) +type usernamePassword struct { + username string + password string +} + type SecretsLoader struct { project *kluctl_project.KluctlProjectContext secretsDir string + + credentialsCache map[string]usernamePassword } func NewSecretsLoader(p *kluctl_project.KluctlProjectContext, secretsDir string) *SecretsLoader { return &SecretsLoader{ project: p, secretsDir: secretsDir, + credentialsCache: map[string]usernamePassword{}, } } @@ -29,6 +37,8 @@ func (s *SecretsLoader) LoadSecrets(source *types.SecretSource) (*uo.Unstructure return s.loadSecretsFile(source) } else if source.SystemEnvVars != nil { return s.loadSecretsSystemEnvs(source) + } else if source.Http != nil { + return s.loadSecretsHttp(source) } else if source.AwsSecretsManager != nil { return s.loadSecretsAwsSecretsManager(source) } else { diff --git a/pkg/seal/secrets_loader_http.go b/pkg/seal/secrets_loader_http.go new file mode 100644 index 000000000..5678d8e2d --- /dev/null +++ b/pkg/seal/secrets_loader_http.go @@ -0,0 +1,138 @@ +package seal + +import ( + "fmt" + "github.com/docker/distribution/registry/client/auth/challenge" + "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/yaml" + "io" + "io/ioutil" + "net/http" + "strings" +) + +func (s *SecretsLoader) doHttp(httpSource *types.SecretSourceHttp, username string, password string) (*http.Response, string, error) { + client := &http.Client{} + + method := "GET" + if httpSource.Method != nil { + method = *httpSource.Method + } + + var reqBody io.Reader + if httpSource.Body != nil { + reqBody = strings.NewReader(*httpSource.Body) + } + + req, err := http.NewRequest(method, httpSource.Url.String(), reqBody) + if err != nil { + return nil, "", err + } + + if username != "" || password != "" { + req.SetBasicAuth(username, password) + } + + for k, v := range httpSource.Headers { + req.Header.Set(k, v) + } + resp, err := client.Do(req) + if err != nil { + return nil, "", err + } + defer resp.Body.Close() + + respBody, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, "", err + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return resp, string(respBody), fmt.Errorf("http request to %s failed with status code %d", httpSource.Url.String(), resp.StatusCode) + } + + return resp, string(respBody), nil +} + +func (s *SecretsLoader) loadSecretsHttp(source *types.SecretSource) (*uo.UnstructuredObject, error) { + resp, respBody, err := s.doHttp(source.Http, "", "") + if err != nil { + chgs := challenge.ResponseChallenges(resp) + if len(chgs) == 0 { + return nil, err + } + + var realms []string + for _, chg := range chgs { + if x, ok := chg.Parameters["realm"]; ok { + if x != "" { + realms = append(realms, x) + } + } + } + + credsKey := fmt.Sprintf("%s|%s", source.Http.Url.Host, strings.Join(realms, "+")) + creds, ok := s.credentialsCache[credsKey] + if !ok { + username, password, err := utils.AskForCredentials(fmt.Sprintf("Please enter credentials for host '%s'", source.Http.Url.Host)) + if err != nil { + return nil, err + } + creds = usernamePassword{ + username: username, + password: password, + } + s.credentialsCache[credsKey] = creds + } + + resp, respBody, err = s.doHttp(source.Http, creds.username, creds.password) + if err != nil { + return nil, err + } + } + + var respObj interface{} + var secrets *uo.UnstructuredObject + + err = yaml.ReadYamlString(respBody, &respObj) + if err != nil { + return nil, err + } + if err != nil { + return nil, err + } + if source.Http.JsonPath != nil { + p, err := uo.NewMyJsonPath(*source.Http.JsonPath) + if err != nil { + return nil, err + } + x, ok := p.GetFirst(respObj) + if !ok { + return nil, fmt.Errorf("%s not found in result from http request %s", *source.Http.JsonPath, source.Http.Url.String()) + } + s, ok := x.(string) + if !ok { + return nil, fmt.Errorf("%s in result of http request %s is not a string", *source.Http.JsonPath, source.Http.Url.String()) + } + secrets, err = uo.FromString(s) + if err != nil { + return nil, err + } + } else { + x, ok := respObj.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("result of http request %s is not an object", source.Http.Url.String()) + } + secrets = uo.FromMap(x) + } + secrets, ok, err := secrets.GetNestedObject("secrets") + if err != nil { + return nil, err + } + if !ok { + return uo.New(), nil + } + return secrets, nil +} diff --git a/pkg/types/secrets_source.go b/pkg/types/secrets_source.go index c29f63615..2575ecbde 100644 --- a/pkg/types/secrets_source.go +++ b/pkg/types/secrets_source.go @@ -5,6 +5,14 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils/uo" ) +type SecretSourceHttp struct { + Url YamlUrl `yaml:"url,omitempty" validate:"required"` + Method *string `yaml:"method,omitempty"` + Body *string `yaml:"body,omitempty"` + Headers map[string]string `yaml:"headers,omitempty"` + JsonPath *string `yaml:"jsonPath,omitempty"` +} + type SecretSourceAwsSecretsManager struct { // Name or ARN of the secret. In case a name is given, the region must be specified as well SecretName string `yaml:"secretName" validate:"required"` @@ -17,6 +25,7 @@ type SecretSourceAwsSecretsManager struct { type SecretSource struct { Path *string `yaml:"path,omitempty"` SystemEnvVars *uo.UnstructuredObject `yaml:"systemEnvVars,omitempty"` + Http *SecretSourceHttp `yaml:"http,omitempty"` AwsSecretsManager *SecretSourceAwsSecretsManager `yaml:"awsSecretsManager,omitempty"` } @@ -29,6 +38,9 @@ func ValidateSecretSource(sl validator.StructLevel) { if s.SystemEnvVars != nil { count += 1 } + if s.Http != nil { + count += 1 + } if s.AwsSecretsManager != nil { count += 1 } diff --git a/pkg/types/url.go b/pkg/types/url.go new file mode 100644 index 000000000..703f8dfba --- /dev/null +++ b/pkg/types/url.go @@ -0,0 +1,27 @@ +package types + +import ( + "net/url" +) + +type YamlUrl struct { + url.URL +} + +func (u *YamlUrl) UnmarshalYAML(unmarshal func(interface{}) error) error { + var s string + err := unmarshal(&s) + if err != nil { + return err + } + u2, err := url.Parse(s) + if err != nil { + return err + } + u.URL = *u2 + return err +} + +func (u YamlUrl) MarshalYAML() (interface{}, error) { + return u.String(), nil +} diff --git a/pkg/utils/prompts.go b/pkg/utils/prompts.go index 5662fe1aa..06cecf885 100644 --- a/pkg/utils/prompts.go +++ b/pkg/utils/prompts.go @@ -1,6 +1,7 @@ package utils import ( + "bufio" "fmt" "github.com/mattn/go-isatty" log "github.com/sirupsen/logrus" @@ -64,3 +65,31 @@ func AskForPassword(prompt string) (string, error) { password := string(bytePassword) return strings.TrimSpace(password), nil } + +func AskForCredentials(prompt string) (string, string, error) { + if !isatty.IsTerminal(os.Stderr.Fd()) { + err := fmt.Errorf("not a terminal, suppressed credentials prompt: %s", prompt) + log.Warning(err) + return "", "", err + } + + reader := bufio.NewReader(os.Stdin) + + _, err := fmt.Fprintf(os.Stderr, prompt+"\n") + if err != nil { + return "", "", err + } + + fmt.Fprint(os.Stderr, "Enter Username: ") + username, err := reader.ReadString('\n') + if err != nil { + return "", "", err + } + + password, err := AskForPassword("Enter Password") + if err != nil { + return "", "", err + } + + return strings.TrimSpace(username), strings.TrimSpace(password), nil +} From 5db07dc5688fdab1ea6f44711ac4b15096b56bdc Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 28 Apr 2022 10:17:24 +0200 Subject: [PATCH 0727/2916] feat: Add NTLM support for http secrets --- go.mod | 1 + go.sum | 2 ++ pkg/seal/secrets_loader_http.go | 11 ++++++++++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 6aec263c0..ab56a0893 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/kluctl/kluctl/v2 go 1.18 require ( + github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e github.com/alecthomas/kong v0.5.0 github.com/aws/aws-sdk-go v1.44.1 github.com/bitnami-labs/sealed-secrets v0.17.5 diff --git a/go.sum b/go.sum index 800475ba0..492559201 100644 --- a/go.sum +++ b/go.sum @@ -84,6 +84,8 @@ github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+Z github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= +github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e h1:ZU22z/2YRFLyf/P4ZwUYSdNCWsMEI0VeyrFoI2rAhJQ= +github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= diff --git a/pkg/seal/secrets_loader_http.go b/pkg/seal/secrets_loader_http.go index 5678d8e2d..25454a746 100644 --- a/pkg/seal/secrets_loader_http.go +++ b/pkg/seal/secrets_loader_http.go @@ -1,7 +1,9 @@ package seal import ( + "crypto/tls" "fmt" + "github.com/Azure/go-ntlmssp" "github.com/docker/distribution/registry/client/auth/challenge" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" @@ -14,7 +16,14 @@ import ( ) func (s *SecretsLoader) doHttp(httpSource *types.SecretSourceHttp, username string, password string) (*http.Response, string, error) { - client := &http.Client{} + client := &http.Client{ + Transport: ntlmssp.Negotiator{ + RoundTripper: &http.Transport{ + // This disables HTTP2.0 support, as it does not play well together with NTLM + TLSNextProto: make(map[string]func(string, *tls.Conn) http.RoundTripper), + }, + }, + } method := "GET" if httpSource.Method != nil { From 2f2dfcb553a7e7f8b0fdbfddf80406de19485a48 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 28 Apr 2022 10:54:29 +0200 Subject: [PATCH 0728/2916] tests: Log validation errors in e2e tests --- e2e/utils.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/e2e/utils.go b/e2e/utils.go index 1a1abc672..622ac374f 100644 --- a/e2e/utils.go +++ b/e2e/utils.go @@ -3,8 +3,10 @@ package e2e import ( "bufio" "bytes" + "fmt" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/validation" + log "github.com/sirupsen/logrus" "io" "os/exec" "reflect" @@ -37,6 +39,18 @@ func waitForReadiness(t *testing.T, k *KindCluster, namespace string, resource s if v.Ready { return true } + + if log.IsLevelEnabled(log.DebugLevel) { + errTxt := "" + for _, e := range v.Errors { + if errTxt != "" { + errTxt += "\n" + } + errTxt += fmt.Sprintf("%s: %s", e.Ref.String(), e.Error) + } + log.Debugf("validation failed. errors:\n%s", errTxt) + } + time.Sleep(1 * time.Second) } return false } From 074a253b67fb9dc53a8b1c569e0ab4bccb5d6b56 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 28 Apr 2022 11:50:56 +0200 Subject: [PATCH 0729/2916] fix: Add timeout to git/load operations --- cmd/kluctl/args/project.go | 4 +++ cmd/kluctl/commands/utils.go | 6 ++++- pkg/git/mirrored_repo.go | 43 ++++++++++++++++-------------- pkg/kluctl_project/git.go | 21 ++++++++------- pkg/kluctl_project/load.go | 17 ++++++------ pkg/kluctl_project/load_targets.go | 11 ++++---- 6 files changed, 58 insertions(+), 44 deletions(-) diff --git a/cmd/kluctl/args/project.go b/cmd/kluctl/args/project.go index 8b6f0d583..d6f555799 100644 --- a/cmd/kluctl/args/project.go +++ b/cmd/kluctl/args/project.go @@ -1,5 +1,7 @@ package args +import "time" + type ProjectFlags struct { ProjectUrl string `group:"project" short:"p" help:"Git url of the kluctl project. If not specified, the current directory will be used instead of a remote Git project"` ProjectRef string `group:"project" short:"b" help:"Git ref of the kluctl project. Only used when --project-url was given."` @@ -12,6 +14,8 @@ type ProjectFlags struct { FromArchiveMetadata string `group:"project" help:"Specify where to load metadata (targets, ...) from. If not specified, metadata is assumed to be part of the archive." type:"existingfile"` OutputMetadata string `group:"project" help:"Specify the output path for the project metadata to be written to. When used with the 'archive' command, it will also cause the archive to not include the metadata.yaml file." type:"file"` Cluster string `group:"project" help:"Specify/Override cluster"` + + LoadTimeout time.Duration `group:"project" help:"Specify timeout for project loading. This will especially limit the time spent in git operations." default:"1m"` } type ArgsFlags struct { diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index b8181e29c..3275ae470 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -1,6 +1,7 @@ package commands import ( + "context" "fmt" "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/deployment" @@ -12,6 +13,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/yaml" "io/ioutil" "os" + "time" ) func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, cb func(p *kluctl_project.KluctlProjectContext) error) error { @@ -48,7 +50,9 @@ func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, cb func(p *kluctl GitAuthProviders: auth.NewDefaultAuthProviders(), } - p, err := kluctl_project.LoadKluctlProject(loadArgs, tmpDir, j2) + loadCtx, cancel := context.WithDeadline(context.Background(), time.Now().Add(projectFlags.LoadTimeout)) + defer cancel() + p, err := kluctl_project.LoadKluctlProject(loadCtx, loadArgs, tmpDir, j2) if err != nil { return err } diff --git a/pkg/git/mirrored_repo.go b/pkg/git/mirrored_repo.go index bd7fe5672..b6db9ed33 100644 --- a/pkg/git/mirrored_repo.go +++ b/pkg/git/mirrored_repo.go @@ -1,6 +1,7 @@ package git import ( + "context" "fmt" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/config" @@ -13,6 +14,7 @@ import ( "os" "path/filepath" "strings" + "time" ) import "github.com/gofrs/flock" @@ -22,7 +24,6 @@ type MirroredGitRepo struct { url git_url.GitUrl mirrorDir string - hasLock bool hasUpdated bool fileLock *flock.Flock @@ -50,26 +51,28 @@ func (g *MirroredGitRepo) HasUpdated() bool { return g.hasUpdated } -func (g *MirroredGitRepo) Lock() error { - err := g.fileLock.Lock() +func (g *MirroredGitRepo) Lock(ctx context.Context) error { + ok, err := g.fileLock.TryLockContext(ctx, time.Millisecond*100) if err != nil { - return err + return fmt.Errorf("locking of %s failed: %w", g.fileLock.Path(), err) + } + if !ok { + return fmt.Errorf("locking of %s failed: unkown reason", g.fileLock.Path()) } - g.hasLock = true return nil } func (g *MirroredGitRepo) Unlock() error { err := g.fileLock.Unlock() if err != nil { + log.Warningf("Unlock of %s failed: %v", g.fileLock.Path(), err) return err } - g.hasLock = false return nil } -func (g *MirroredGitRepo) WithLock(cb func() error) error { - err := g.Lock() +func (g *MirroredGitRepo) WithLock(ctx context.Context, cb func() error) error { + err := g.Lock(ctx) if err != nil { return err } @@ -77,9 +80,9 @@ func (g *MirroredGitRepo) WithLock(cb func() error) error { return cb() } -func (g *MirroredGitRepo) MaybeWithLock(lock bool, cb func() error) error { +func (g *MirroredGitRepo) MaybeWithLock(ctx context.Context, lock bool, cb func() error) error { if lock { - return g.WithLock(cb) + return g.WithLock(ctx, cb) } return cb() } @@ -124,7 +127,7 @@ func (g *MirroredGitRepo) cleanupMirrorDir() error { return nil } -func (g *MirroredGitRepo) update(repoDir string, authProviders *auth2.GitAuthProviders) error { +func (g *MirroredGitRepo) update(ctx context.Context, repoDir string, authProviders *auth2.GitAuthProviders) error { log.Infof("Updating mirror repo: url='%v'", g.url.String()) r, err := git.PlainOpen(repoDir) if err != nil { @@ -138,7 +141,7 @@ func (g *MirroredGitRepo) update(repoDir string, authProviders *auth2.GitAuthPro return err } - g.remoteRefs, err = remote.List(&git.ListOptions{ + g.remoteRefs, err = remote.ListContext(ctx, &git.ListOptions{ Auth: auth.AuthMethod, CABundle: auth.CABundle, }) @@ -150,7 +153,7 @@ func (g *MirroredGitRepo) update(repoDir string, authProviders *auth2.GitAuthPro remoteRefsMap[reference.Name()] = true } - err = remote.Fetch(&git.FetchOptions{ + err = remote.FetchContext(ctx, &git.FetchOptions{ Auth: auth.AuthMethod, CABundle: auth.CABundle, Progress: os.Stdout, @@ -197,10 +200,10 @@ func (g *MirroredGitRepo) update(repoDir string, authProviders *auth2.GitAuthPro return nil } -func (g *MirroredGitRepo) cloneOrUpdate(authProviders *auth2.GitAuthProviders) error { +func (g *MirroredGitRepo) cloneOrUpdate(ctx context.Context, authProviders *auth2.GitAuthProviders) error { initMarker := filepath.Join(g.mirrorDir, ".cache2.init") if utils.IsFile(initMarker) { - return g.update(g.mirrorDir, authProviders) + return g.update(ctx, g.mirrorDir, authProviders) } err := g.cleanupMirrorDir() if err != nil { @@ -232,7 +235,7 @@ func (g *MirroredGitRepo) cloneOrUpdate(authProviders *auth2.GitAuthProviders) e return err } - err = g.update(tmpMirrorDir, authProviders) + err = g.update(ctx, tmpMirrorDir, authProviders) if err != nil { return err } @@ -254,8 +257,8 @@ func (g *MirroredGitRepo) cloneOrUpdate(authProviders *auth2.GitAuthProviders) e return nil } -func (g *MirroredGitRepo) Update(authProviders *auth2.GitAuthProviders) error { - err := g.cloneOrUpdate(authProviders) +func (g *MirroredGitRepo) Update(ctx context.Context, authProviders *auth2.GitAuthProviders) error { + err := g.cloneOrUpdate(ctx, authProviders) if err != nil { return err } @@ -263,8 +266,8 @@ func (g *MirroredGitRepo) Update(authProviders *auth2.GitAuthProviders) error { return nil } -func (g *MirroredGitRepo) CloneProject(ref string, targetDir string) error { - if !g.hasLock || !g.hasUpdated { +func (g *MirroredGitRepo) CloneProject(ctx context.Context, ref string, targetDir string) error { + if !g.fileLock.Locked() || !g.hasUpdated { log.Fatalf("tried to clone from a project that is not locked/updated") } diff --git a/pkg/kluctl_project/git.go b/pkg/kluctl_project/git.go index 93abdfc9b..acd7f2a7f 100644 --- a/pkg/kluctl_project/git.go +++ b/pkg/kluctl_project/git.go @@ -1,6 +1,7 @@ package kluctl_project import ( + "context" "fmt" "github.com/kluctl/kluctl/v2/pkg/git" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" @@ -15,7 +16,7 @@ import ( "sync" ) -func (c *KluctlProjectContext) updateGitCaches() error { +func (c *KluctlProjectContext) updateGitCaches(ctx context.Context) error { var waitGroup sync.WaitGroup var firstError error var firstErrorLock sync.Mutex @@ -29,9 +30,9 @@ func (c *KluctlProjectContext) updateGitCaches() error { } doUpdateRepo := func(repo *git.MirroredGitRepo) error { - return repo.WithLock(func() error { + return repo.WithLock(ctx, func() error { if !repo.HasUpdated() { - return repo.Update(c.loadArgs.GitAuthProviders) + return repo.Update(ctx, c.loadArgs.GitAuthProviders) } return nil }) @@ -120,7 +121,7 @@ func (c *KluctlProjectContext) buildCloneDir(u git_url.GitUrl, ref string) (stri return cloneDir, nil } -func (c *KluctlProjectContext) cloneGitProject(gitProject types2.ExternalProject, defaultGitSubDir string, doAddInvolvedRepo bool, doLock bool) (result gitProjectInfo, err error) { +func (c *KluctlProjectContext) cloneGitProject(ctx context.Context, gitProject types2.ExternalProject, defaultGitSubDir string, doAddInvolvedRepo bool, doLock bool) (result gitProjectInfo, err error) { err = os.MkdirAll(filepath.Join(c.TmpDir, "git"), 0o700) if err != nil { return @@ -147,21 +148,21 @@ func (c *KluctlProjectContext) cloneGitProject(gitProject types2.ExternalProject return } c.mirroredRepos[gitProject.Project.Url.NormalizedRepoKey()] = mr - err = mr.Lock() + err = mr.Lock(ctx) if err != nil { return } defer mr.Unlock() } - err = mr.MaybeWithLock(doLock, func() error { + err = mr.MaybeWithLock(ctx, doLock, func() error { if !mr.HasUpdated() { - err = mr.Update(c.loadArgs.GitAuthProviders) + err = mr.Update(ctx, c.loadArgs.GitAuthProviders) if err != nil { return err } } - return mr.CloneProject(gitProject.Project.Ref, targetDir) + return mr.CloneProject(ctx, gitProject.Project.Ref, targetDir) }) if err != nil { return @@ -192,7 +193,7 @@ func (c *KluctlProjectContext) localProject(dir string) gitProjectInfo { } } -func (c *KluctlProjectContext) cloneKluctlProject() (gitProjectInfo, error) { +func (c *KluctlProjectContext) cloneKluctlProject(ctx context.Context) (gitProjectInfo, error) { if c.loadArgs.ProjectUrl == nil { p, err := os.Getwd() if err != nil { @@ -200,7 +201,7 @@ func (c *KluctlProjectContext) cloneKluctlProject() (gitProjectInfo, error) { } return c.localProject(p), err } - return c.cloneGitProject(types2.ExternalProject{ + return c.cloneGitProject(ctx, types2.ExternalProject{ Project: &types2.GitProject{ Url: *c.loadArgs.ProjectUrl, Ref: c.loadArgs.ProjectRef, diff --git a/pkg/kluctl_project/load.go b/pkg/kluctl_project/load.go index 48dffe726..1db05eb24 100644 --- a/pkg/kluctl_project/load.go +++ b/pkg/kluctl_project/load.go @@ -3,6 +3,7 @@ package kluctl_project import ( "archive/tar" "compress/gzip" + "context" "fmt" "github.com/kluctl/kluctl/v2/pkg/git" "github.com/kluctl/kluctl/v2/pkg/jinja2" @@ -56,8 +57,8 @@ func (c *KluctlProjectContext) getConfigPath(projectDir string) string { return configPath } -func (c *KluctlProjectContext) load(allowGit bool) error { - kluctlProjectInfo, err := c.cloneKluctlProject() +func (c *KluctlProjectContext) load(ctx context.Context, allowGit bool) error { + kluctlProjectInfo, err := c.cloneKluctlProject(ctx) if err != nil { return err } @@ -72,7 +73,7 @@ func (c *KluctlProjectContext) load(allowGit bool) error { } if allowGit { - err = c.updateGitCaches() + err = c.updateGitCaches(ctx) if err != nil { return err } @@ -115,7 +116,7 @@ func (c *KluctlProjectContext) load(allowGit bool) error { return gitProjectInfo{}, fmt.Errorf("tried to load something from git while it was not allowed") } - return c.cloneGitProject(*ep, defaultGitSubDir, true, true) + return c.cloneGitProject(ctx, *ep, defaultGitSubDir, true, true) } deploymentInfo, err := doClone(c.Config.Deployment, "", c.loadArgs.LocalDeployment) @@ -159,7 +160,7 @@ func (c *KluctlProjectContext) load(allowGit bool) error { return nil } -func LoadKluctlProject(args LoadKluctlProjectArgs, tmpDir string, j2 *jinja2.Jinja2) (*KluctlProjectContext, error) { +func LoadKluctlProject(ctx context.Context, args LoadKluctlProjectArgs, tmpDir string, j2 *jinja2.Jinja2) (*KluctlProjectContext, error) { if args.FromArchive != "" { if args.ProjectUrl != nil || args.ProjectRef != "" || args.ProjectConfig != "" || args.LocalClusters != "" || args.LocalDeployment != "" || args.LocalSealedSecrets != "" { return nil, fmt.Errorf("--from-archive can not be combined with any other project related option") @@ -168,18 +169,18 @@ func LoadKluctlProject(args LoadKluctlProjectArgs, tmpDir string, j2 *jinja2.Jin if err != nil { return nil, err } - err = project.load(false) + err = project.load(ctx, false) if err != nil { return nil, err } return project, nil } else { p := NewKluctlProjectContext(args, tmpDir, j2) - err := p.load(true) + err := p.load(ctx, true) if err != nil { return nil, err } - err = p.loadTargets() + err = p.loadTargets(ctx) if err != nil { return nil, err } diff --git a/pkg/kluctl_project/load_targets.go b/pkg/kluctl_project/load_targets.go index 1d86d5d5e..bfafa94d5 100644 --- a/pkg/kluctl_project/load_targets.go +++ b/pkg/kluctl_project/load_targets.go @@ -1,6 +1,7 @@ package kluctl_project import ( + "context" "fmt" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/jinja2" @@ -26,7 +27,7 @@ type dynamicTargetInfo struct { defaultBranch string } -func (c *KluctlProjectContext) loadTargets() error { +func (c *KluctlProjectContext) loadTargets(ctx context.Context) error { targetNames := make(map[string]bool) c.DynamicTargets = nil @@ -39,7 +40,7 @@ func (c *KluctlProjectContext) loadTargets() error { targetInfos = append(targetInfos, l...) } - err := c.cloneDynamicTargets(targetInfos) + err := c.cloneDynamicTargets(ctx, targetInfos) if err != nil { return err } @@ -217,13 +218,13 @@ func (c *KluctlProjectContext) matchRef(s string, pattern string) (bool, string, } } -func (c *KluctlProjectContext) cloneDynamicTargets(dynamicTargets []*dynamicTargetInfo) error { +func (c *KluctlProjectContext) cloneDynamicTargets(ctx context.Context, dynamicTargets []*dynamicTargetInfo) error { wp := utils.NewDebuggerAwareWorkerPool(8) defer wp.StopWait(false) // lock all involved repos first for _, mr := range c.mirroredRepos { - err := mr.Lock() + err := mr.Lock(ctx) if err != nil { return err } @@ -249,7 +250,7 @@ func (c *KluctlProjectContext) cloneDynamicTargets(dynamicTargets []*dynamicTarg gitProject.Ref = *targetInfo.ref ep := types.ExternalProject{Project: &gitProject} - gi, err := c.cloneGitProject(ep, "", false, false) + gi, err := c.cloneGitProject(ctx, ep, "", false, false) mutex.Lock() defer mutex.Unlock() if err != nil { From 9f81fa8fd3b7ddd70540bfebf9b1596ebaa1a1e0 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 28 Apr 2022 12:00:53 +0200 Subject: [PATCH 0730/2916] tests: Show which resource failed in waitForReadiness --- e2e/utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/utils.go b/e2e/utils.go index 622ac374f..e52c131ac 100644 --- a/e2e/utils.go +++ b/e2e/utils.go @@ -48,7 +48,7 @@ func waitForReadiness(t *testing.T, k *KindCluster, namespace string, resource s } errTxt += fmt.Sprintf("%s: %s", e.Ref.String(), e.Error) } - log.Debugf("validation failed. errors:\n%s", errTxt) + log.Debugf("validation failed for %s/%s. errors:\n%s", namespace, resource, errTxt) } time.Sleep(1 * time.Second) } From 75c514a8be6fd4f487300dd1bb9a366380154780 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 28 Apr 2022 12:22:25 +0200 Subject: [PATCH 0731/2916] build: Less verbose CHANGELOGs --- .goreleaser.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 8967d7158..20c4e885e 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -57,9 +57,11 @@ changelog: sort: asc filters: exclude: - - '^docs:' - - '^test:' + - '^doc.*:' + - '^test.*:' - '^chore:' + - '^build:' + - '^refactor:' release: prerelease: auto From f99211ba0cce0e269f0813dc0df15bdeb228ecb5 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 28 Apr 2022 15:43:11 +0200 Subject: [PATCH 0732/2916] fix: Make working/project dir configurable while loading projects --- cmd/kluctl/commands/utils.go | 6 ++++++ pkg/kluctl_project/git.go | 6 +----- pkg/kluctl_project/project.go | 1 + 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 3275ae470..8ad5e6ad0 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -38,7 +38,13 @@ func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, cb func(p *kluctl } defer j2.Close() + cwd, err := os.Getwd() + if err != nil { + return err + } + loadArgs := kluctl_project.LoadKluctlProjectArgs{ + ProjectDir: cwd, ProjectUrl: url, ProjectRef: projectFlags.ProjectRef, ProjectConfig: projectFlags.ProjectConfig, diff --git a/pkg/kluctl_project/git.go b/pkg/kluctl_project/git.go index acd7f2a7f..3a6b56a00 100644 --- a/pkg/kluctl_project/git.go +++ b/pkg/kluctl_project/git.go @@ -195,11 +195,7 @@ func (c *KluctlProjectContext) localProject(dir string) gitProjectInfo { func (c *KluctlProjectContext) cloneKluctlProject(ctx context.Context) (gitProjectInfo, error) { if c.loadArgs.ProjectUrl == nil { - p, err := os.Getwd() - if err != nil { - return gitProjectInfo{}, err - } - return c.localProject(p), err + return c.localProject(c.loadArgs.ProjectDir), nil } return c.cloneGitProject(ctx, types2.ExternalProject{ Project: &types2.GitProject{ diff --git a/pkg/kluctl_project/project.go b/pkg/kluctl_project/project.go index c44c64555..bacb21ae2 100644 --- a/pkg/kluctl_project/project.go +++ b/pkg/kluctl_project/project.go @@ -11,6 +11,7 @@ import ( ) type LoadKluctlProjectArgs struct { + ProjectDir string ProjectUrl *git_url.GitUrl ProjectRef string ProjectConfig string From 798a13e2a792338c2e346311795ce3693889207e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 28 Apr 2022 17:31:22 +0200 Subject: [PATCH 0733/2916] refactor: Allow to pass clientConfig from outside --- cmd/kluctl/commands/cmd_downscale.go | 2 +- cmd/kluctl/commands/cmd_poke_images.go | 2 +- cmd/kluctl/commands/utils.go | 10 ++++++++- pkg/jinja2/vars.go | 2 +- pkg/k8s/k8s_cluster.go | 31 ++++++++------------------ pkg/kluctl_project/target_context.go | 10 +++++++-- 6 files changed, 29 insertions(+), 28 deletions(-) diff --git a/cmd/kluctl/commands/cmd_downscale.go b/cmd/kluctl/commands/cmd_downscale.go index e5383e3b2..7df0a744b 100644 --- a/cmd/kluctl/commands/cmd_downscale.go +++ b/cmd/kluctl/commands/cmd_downscale.go @@ -37,7 +37,7 @@ func (cmd *downscaleCmd) Run() error { } return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { if !cmd.Yes && !cmd.DryRun { - if !utils.AskForConfirmation(fmt.Sprintf("Do you really want to downscale on context/cluster %s?", ctx.targetCtx.K.Context())) { + if !utils.AskForConfirmation(fmt.Sprintf("Do you really want to downscale on context/cluster %s?", ctx.targetCtx.ClusterConfig.Cluster.Context)) { return fmt.Errorf("aborted") } } diff --git a/cmd/kluctl/commands/cmd_poke_images.go b/cmd/kluctl/commands/cmd_poke_images.go index 33c62ced3..a65f2e079 100644 --- a/cmd/kluctl/commands/cmd_poke_images.go +++ b/cmd/kluctl/commands/cmd_poke_images.go @@ -37,7 +37,7 @@ func (cmd *pokeImagesCmd) Run() error { } return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { if !cmd.Yes && !cmd.DryRun { - if !utils.AskForConfirmation(fmt.Sprintf("Do you really want to poke images to the context/cluster %s?", ctx.targetCtx.K.Context())) { + if !utils.AskForConfirmation(fmt.Sprintf("Do you really want to poke images to the context/cluster %s?", ctx.targetCtx.ClusterConfig.Cluster.Context)) { return fmt.Errorf("aborted") } } diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 8ad5e6ad0..9c449aa91 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -12,6 +12,8 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/yaml" "io/ioutil" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" "os" "time" ) @@ -130,7 +132,13 @@ func withProjectTargetCommandContext(args projectTargetCommandArgs, p *kluctl_pr renderOutputDir = tmpDir } - ctx, err := p.NewTargetContext(args.targetFlags.Target, args.projectFlags.Cluster, + clientConfigGetter := func(context string) (*rest.Config, error) { + configLoadingRules := clientcmd.NewDefaultClientConfigLoadingRules() + configOverrides := &clientcmd.ConfigOverrides{CurrentContext: context} + return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(configLoadingRules, configOverrides).ClientConfig() + } + + ctx, err := p.NewTargetContext(clientConfigGetter, args.targetFlags.Target, args.projectFlags.Cluster, args.dryRunArgs == nil || args.dryRunArgs.DryRun, optionArgs, forSeal, images, inclusion, renderOutputDir) diff --git a/pkg/jinja2/vars.go b/pkg/jinja2/vars.go index b130cafb3..1c42f4c38 100644 --- a/pkg/jinja2/vars.go +++ b/pkg/jinja2/vars.go @@ -97,7 +97,7 @@ func (vc *VarsCtx) loadVarsFromK8sObject(k *k8s.K8sCluster, ref k8s2.ObjectRef, return err } if !found { - return fmt.Errorf("key %s not found in %s on cluster %s", key, ref.String(), k.Context()) + return fmt.Errorf("key %s not found in %s on cluster", key, ref.String()) } err = vc.loadVarsFromString(value) diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index d9d9aa93b..59871f052 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -21,7 +21,6 @@ import ( "k8s.io/client-go/metadata" _ "k8s.io/client-go/plugin/pkg/client/auth" "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" "net/http" "net/url" "path/filepath" @@ -37,8 +36,7 @@ var ( ) type K8sCluster struct { - context string - DryRun bool + DryRun bool restConfig *rest.Config discovery *disk.CachedDiscoveryClient @@ -78,30 +76,22 @@ func (p *parallelClientEntry) HandleWarningHeader(code int, agent string, text s }) } -func NewK8sCluster(context string, dryRun bool) (*K8sCluster, error) { +func NewK8sCluster(configIn *rest.Config, dryRun bool) (*K8sCluster, error) { k := &K8sCluster{ - context: context, - DryRun: dryRun, + DryRun: dryRun, } - configLoadingRules := clientcmd.NewDefaultClientConfigLoadingRules() - configOverrides := &clientcmd.ConfigOverrides{CurrentContext: context} + k.restConfig = rest.CopyConfig(configIn) + k.restConfig.QPS = 10 + k.restConfig.Burst = 20 - config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(configLoadingRules, configOverrides).ClientConfig() - if err != nil { - return nil, err - } - config.QPS = 10 - config.Burst = 20 - k.restConfig = config - - apiHost, err := url.Parse(config.Host) + apiHost, err := url.Parse(k.restConfig.Host) if err != nil { return nil, err } discoveryCacheDir := filepath.Join(utils.GetTmpBaseDir(), "kube-cache/discovery", apiHost.Hostname()) httpCacheDir := filepath.Join(utils.GetTmpBaseDir(), "kube-cache/http", apiHost.Hostname()) - discovery2, err := disk.NewCachedDiscoveryClientForConfig(dynamic.ConfigFor(config), discoveryCacheDir, httpCacheDir, time.Hour*24) + discovery2, err := disk.NewCachedDiscoveryClientForConfig(dynamic.ConfigFor(k.restConfig), discoveryCacheDir, httpCacheDir, time.Hour*24) if err != nil { return nil, err } @@ -111,6 +101,7 @@ func NewK8sCluster(context string, dryRun bool) (*K8sCluster, error) { k.clientPool = make(chan *parallelClientEntry, parallelClients) for i := 0; i < parallelClients; i++ { p := ¶llelClientEntry{} + config := rest.CopyConfig(k.restConfig) config.WarningHandler = p p.http, err = rest.HTTPClientFor(config) @@ -160,10 +151,6 @@ func (k *K8sCluster) ReadWrite() *K8sCluster { return &k2 } -func (k *K8sCluster) Context() string { - return k.context -} - func (k *K8sCluster) GetCA() []byte { return k.restConfig.CAData } diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index 257967bad..29b662338 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -8,6 +8,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "k8s.io/client-go/rest" "path/filepath" ) @@ -20,7 +21,7 @@ type TargetContext struct { DeploymentCollection *deployment.DeploymentCollection } -func (p *KluctlProjectContext) NewTargetContext(targetName string, clusterName string, dryRun bool, args map[string]string, forSeal bool, images *deployment.Images, inclusion *utils.Inclusion, renderOutputDir string) (*TargetContext, error) { +func (p *KluctlProjectContext) NewTargetContext(clientConfigGetter func(context string) (*rest.Config, error), targetName string, clusterName string, dryRun bool, args map[string]string, forSeal bool, images *deployment.Images, inclusion *utils.Inclusion, renderOutputDir string) (*TargetContext, error) { deploymentDir, err := filepath.Abs(p.DeploymentDir) if err != nil { return nil, err @@ -51,7 +52,12 @@ func (p *KluctlProjectContext) NewTargetContext(targetName string, clusterName s return nil, err } - k, err := k8s.NewK8sCluster(clusterConfig.Cluster.Context, dryRun) + clientConfig, err := clientConfigGetter(clusterConfig.Cluster.Context) + if err != nil { + return nil, err + } + + k, err := k8s.NewK8sCluster(clientConfig, dryRun) if err != nil { return nil, err } From ce2ff99362aba95a3c1e87969b71cc16e18d86b6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 28 Apr 2022 17:31:39 +0200 Subject: [PATCH 0734/2916] fix: Ignore missing clusters/sealedSecrets dirs when creating an archive --- pkg/kluctl_project/load.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pkg/kluctl_project/load.go b/pkg/kluctl_project/load.go index 1db05eb24..511a514fd 100644 --- a/pkg/kluctl_project/load.go +++ b/pkg/kluctl_project/load.go @@ -287,11 +287,15 @@ func (c *KluctlProjectContext) CreateTGZArchive(archivePath string, embedMetadat if err = utils.AddToTar(tw, c.DeploymentDir, "deployment", filter); err != nil { return err } - if err = utils.AddToTar(tw, c.ClustersDir, "clusters", filter); err != nil { - return err + if utils.Exists(c.ClustersDir) { + if err = utils.AddToTar(tw, c.ClustersDir, "clusters", filter); err != nil { + return err + } } - if err = utils.AddToTar(tw, c.SealedSecretsDir, "sealed-secrets", filter); err != nil { - return err + if utils.Exists(c.SealedSecretsDir) { + if err = utils.AddToTar(tw, c.SealedSecretsDir, "sealed-secrets", filter); err != nil { + return err + } } return nil From 1c97e464d4ce137532634d1588d58e3190c887bb Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 29 Apr 2022 08:03:56 +0200 Subject: [PATCH 0735/2916] fix: Check for username instead of e.Username in BuildAuth --- pkg/git/auth/list_auth_provider.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/git/auth/list_auth_provider.go b/pkg/git/auth/list_auth_provider.go index d469e500f..b929675af 100644 --- a/pkg/git/auth/list_auth_provider.go +++ b/pkg/git/auth/list_auth_provider.go @@ -53,7 +53,7 @@ func (a *ListAuthProvider) BuildAuth(gitUrl git_url.GitUrl) AuthMethodAndCA { username = e.Username } - if e.Username == "*" { + if username == "*" { // can't use "*" as username continue } From 6d874e4ec742b6287331a0c67953a16597b54654 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 29 Apr 2022 08:29:09 +0200 Subject: [PATCH 0736/2916] fix: Actually write known_hosts file when given --- pkg/git/auth/ssh_known_hosts.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/git/auth/ssh_known_hosts.go b/pkg/git/auth/ssh_known_hosts.go index 6a4225e5e..e5d522d6e 100644 --- a/pkg/git/auth/ssh_known_hosts.go +++ b/pkg/git/auth/ssh_known_hosts.go @@ -65,6 +65,10 @@ func verifyHost(host string, remote net.Addr, key ssh.PublicKey, knownHosts []by _ = tmpFile.Close() _ = os.Remove(tmpFile.Name()) }() + _, err = tmpFile.Write(knownHosts) + if err != nil { + return err + } files = append(files, tmpFile.Name()) } From c6987e0affe47feabe0be34def470ef917fd1152 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 29 Apr 2022 13:32:14 +0200 Subject: [PATCH 0737/2916] refactor: Wrap ListImageTags into RegistryHelper --- .../commands/cmd_check_image_updates.go | 4 +- cmd/kluctl/commands/utils.go | 4 +- pkg/deployment/images.go | 6 +- pkg/registries/registries.go | 104 +++++++++--------- 4 files changed, 63 insertions(+), 55 deletions(-) diff --git a/cmd/kluctl/commands/cmd_check_image_updates.go b/cmd/kluctl/commands/cmd_check_image_updates.go index 8d65ee305..6fd22dfd7 100644 --- a/cmd/kluctl/commands/cmd_check_image_updates.go +++ b/cmd/kluctl/commands/cmd_check_image_updates.go @@ -37,6 +37,8 @@ func (cmd *checkImageUpdatesCmd) Run() error { return err } + rh := registries.NewRegistryHelper() + wg := utils.NewWorkerPoolWithErrors(8) defer wg.StopWait(false) @@ -52,7 +54,7 @@ func (cmd *checkImageUpdatesCmd) Run() error { repo := s[0] if _, ok := imageTags[repo]; !ok { wg.Submit(func() error { - tags, err := registries.ListImageTags(repo) + tags, err := rh.ListImageTags(repo) mutex.Lock() defer mutex.Unlock() if err != nil { diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 9c449aa91..7b0af0754 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -9,6 +9,7 @@ import ( git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/jinja2" "github.com/kluctl/kluctl/v2/pkg/kluctl_project" + "github.com/kluctl/kluctl/v2/pkg/registries" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/yaml" "io/ioutil" @@ -100,7 +101,8 @@ func withProjectCommandContext(args projectTargetCommandArgs, cb func(ctx *comma } func withProjectTargetCommandContext(args projectTargetCommandArgs, p *kluctl_project.KluctlProjectContext, forSeal bool, cb func(ctx *commandCtx) error) error { - images, err := deployment.NewImages(args.imageFlags.UpdateImages) + rh := registries.NewRegistryHelper() + images, err := deployment.NewImages(rh, args.imageFlags.UpdateImages) if err != nil { return err } diff --git a/pkg/deployment/images.go b/pkg/deployment/images.go index 654c72025..076a211a6 100644 --- a/pkg/deployment/images.go +++ b/pkg/deployment/images.go @@ -18,6 +18,7 @@ import ( ) type Images struct { + rh *registries.RegistryHelper updateImages bool fixedImages []types.FixedImage seenImages []types.FixedImage @@ -26,8 +27,9 @@ type Images struct { registryCache utils.ThreadSafeMultiCache } -func NewImages(updateImages bool) (*Images, error) { +func NewImages(rh *registries.RegistryHelper, updateImages bool) (*Images, error) { return &Images{ + rh: rh, updateImages: updateImages, }, nil } @@ -73,7 +75,7 @@ func (images *Images) GetFixedImage(image string, namespace string, deployment s func (images *Images) GetLatestImageFromRegistry(image string, latestVersion string) (*string, error) { ret, err := images.registryCache.Get(image, "tag", func() (interface{}, error) { - return registries.ListImageTags(image) + return images.rh.ListImageTags(image) }) if err != nil { return nil, err diff --git a/pkg/registries/registries.go b/pkg/registries/registries.go index 86ccb63e4..727453199 100644 --- a/pkg/registries/registries.go +++ b/pkg/registries/registries.go @@ -33,11 +33,24 @@ func (e *noAuthRetryError) Error() string { return e.msg } -func ListImageTags(image string) ([]string, error) { +type RegistryHelper struct { + cachedAuth utils.ThreadSafeCache + cachedResponses utils.ThreadSafeMultiCache + authRealms map[string]bool + authErrors map[string]bool + init sync.Once + mutex sync.Mutex +} + +func NewRegistryHelper() *RegistryHelper { + return &RegistryHelper{} +} + +func (rh *RegistryHelper) ListImageTags(image string) ([]string, error) { var nameOpts []name.Option remoteOpts := []remote.Option{ - remote.WithAuthFromKeychain(&globalMyKeychain), - remote.WithTransport(&globalMyKeychain), + remote.WithAuthFromKeychain(rh), + remote.WithTransport(rh), } repo, err := name.NewRepository(image, nameOpts...) @@ -64,18 +77,7 @@ func ListImageTags(image string) ([]string, error) { return ret, err } -var globalMyKeychain myKeychain - -type myKeychain struct { - cachedAuth utils.ThreadSafeCache - cachedResponses utils.ThreadSafeMultiCache - authRealms map[string]bool - authErrors map[string]bool - init sync.Once - mutex sync.Mutex -} - -func (kc *myKeychain) doResolve(resource authn.Resource) (authn.Authenticator, error) { +func (rh *RegistryHelper) doResolve(resource authn.Resource) (authn.Authenticator, error) { registry := resource.RegistryStr() for _, m := range utils.ParseEnvConfigSets("KLUCTL_REGISTRY") { @@ -93,11 +95,11 @@ func (kc *myKeychain) doResolve(resource authn.Resource) (authn.Authenticator, e return authn.DefaultKeychain.Resolve(resource) } -func (kc *myKeychain) Resolve(resource authn.Resource) (authn.Authenticator, error) { +func (rh *RegistryHelper) Resolve(resource authn.Resource) (authn.Authenticator, error) { registry := resource.RegistryStr() - ret, err := kc.cachedAuth.Get(registry, func() (interface{}, error) { - return kc.doResolve(resource) + ret, err := rh.cachedAuth.Get(registry, func() (interface{}, error) { + return rh.doResolve(resource) }) if err != nil { return nil, err @@ -105,15 +107,15 @@ func (kc *myKeychain) Resolve(resource authn.Resource) (authn.Authenticator, err return ret.(authn.Authenticator), nil } -func (kc *myKeychain) realmFromRequest(req *http.Request) string { +func (rh *RegistryHelper) realmFromRequest(req *http.Request) string { return fmt.Sprintf("%s://%s%s", req.URL.Scheme, req.URL.Host, req.URL.Path) } -func (kc *myKeychain) getCachePath(key string) string { +func (rh *RegistryHelper) getCachePath(key string) string { return filepath.Join(utils.GetTmpBaseDir(), "registries-cache", key[0:2], key[2:4], key) } -func (kc *myKeychain) checkInvalidToken(resBody []byte) bool { +func (rh *RegistryHelper) checkInvalidToken(resBody []byte) bool { j, err := uo.FromString(string(resBody)) if err != nil { return false @@ -139,8 +141,8 @@ func (kc *myKeychain) checkInvalidToken(resBody []byte) bool { return false } -func (kc *myKeychain) readCachedResponse(key string) []byte { - cachePath := kc.getCachePath(key) +func (rh *RegistryHelper) readCachedResponse(key string) []byte { + cachePath := rh.getCachePath(key) st, err := os.Stat(cachePath) if err != nil { @@ -163,7 +165,7 @@ func (kc *myKeychain) readCachedResponse(key string) []byte { if err != nil { return nil } - if kc.checkInvalidToken(jb) { + if rh.checkInvalidToken(jb) { return nil } } @@ -171,8 +173,8 @@ func (kc *myKeychain) readCachedResponse(key string) []byte { return b } -func (kc *myKeychain) writeCachedResponse(key string, data []byte) { - cachePath := kc.getCachePath(key) +func (rh *RegistryHelper) writeCachedResponse(key string, data []byte) { + cachePath := rh.getCachePath(key) if !utils.Exists(filepath.Dir(cachePath)) { err := os.MkdirAll(filepath.Dir(cachePath), 0o700) if err != nil { @@ -193,15 +195,15 @@ func (kc *myKeychain) writeCachedResponse(key string, data []byte) { } } -func (kc *myKeychain) RoundTripCached(req *http.Request, extraKey string, onNew func(res *http.Response) error) (*http.Response, error) { +func (rh *RegistryHelper) RoundTripCached(req *http.Request, extraKey string, onNew func(res *http.Response) error) (*http.Response, error) { key := fmt.Sprintf("%s\n%s\n%s\n", req.Host, req.URL.Path, extraKey) key = utils.Sha256String(key) isNew := false - resI, err := kc.cachedResponses.Get(req.Host, key, func() (interface{}, error) { + resI, err := rh.cachedResponses.Get(req.Host, key, func() (interface{}, error) { isNew = true - b := kc.readCachedResponse(key) + b := rh.readCachedResponse(key) if b == nil { res, err := remote.DefaultTransport.RoundTrip(req) if err != nil { @@ -213,7 +215,7 @@ func (kc *myKeychain) RoundTripCached(req *http.Request, extraKey string, onNew } if res.StatusCode < 500 { - kc.writeCachedResponse(key, b) + rh.writeCachedResponse(key, b) } } @@ -240,22 +242,22 @@ func (kc *myKeychain) RoundTripCached(req *http.Request, extraKey string, onNew return res, err } -func (kc *myKeychain) RoundTripInfoReq(req *http.Request) (*http.Response, error) { - return kc.RoundTripCached(req, "info", func(res *http.Response) error { - kc.mutex.Lock() - defer kc.mutex.Unlock() +func (rh *RegistryHelper) RoundTripInfoReq(req *http.Request) (*http.Response, error) { + return rh.RoundTripCached(req, "info", func(res *http.Response) error { + rh.mutex.Lock() + defer rh.mutex.Unlock() chgs := challenge.ResponseChallenges(res) for _, chg := range chgs { if realm, ok := chg.Parameters["realm"]; ok { - kc.authRealms[realm] = true + rh.authRealms[realm] = true } } return nil }) } -func (kc *myKeychain) RoundTripAuth(req *http.Request) (*http.Response, error) { +func (rh *RegistryHelper) RoundTripAuth(req *http.Request) (*http.Response, error) { b := bytes.NewBuffer(nil) err := req.Header.Write(b) if err != nil { @@ -266,42 +268,42 @@ func (kc *myKeychain) RoundTripAuth(req *http.Request) (*http.Response, error) { hash := utils.Sha256String(b.String()) - return kc.RoundTripCached(req, hash, func(res *http.Response) error { - kc.mutex.Lock() - defer kc.mutex.Unlock() + return rh.RoundTripCached(req, hash, func(res *http.Response) error { + rh.mutex.Lock() + defer rh.mutex.Unlock() if res.StatusCode == http.StatusUnauthorized || res.StatusCode == http.StatusForbidden { // if auth fails once for a registry, we must not retry any auth on that registry as we could easily run // into an IP block - kc.authErrors[kc.realmFromRequest(req)] = true + rh.authErrors[rh.realmFromRequest(req)] = true } return nil }) } -func (kc *myKeychain) RoundTrip(req *http.Request) (*http.Response, error) { - kc.init.Do(func() { - kc.authRealms = make(map[string]bool) - kc.authErrors = make(map[string]bool) +func (rh *RegistryHelper) RoundTrip(req *http.Request) (*http.Response, error) { + rh.init.Do(func() { + rh.authRealms = make(map[string]bool) + rh.authErrors = make(map[string]bool) }) if req.URL.Path == "/v2/" { - return kc.RoundTripInfoReq(req) + return rh.RoundTripInfoReq(req) } - kc.mutex.Lock() - realm := kc.realmFromRequest(req) - _, isAuthRealm := kc.authRealms[realm] - _, isAuthError := kc.authErrors[realm] - kc.mutex.Unlock() + rh.mutex.Lock() + realm := rh.realmFromRequest(req) + _, isAuthRealm := rh.authRealms[realm] + _, isAuthError := rh.authErrors[realm] + rh.mutex.Unlock() if isAuthError { return nil, &noAuthRetryError{fmt.Sprintf("previous auth request for %s gave an error, we won't retry", realm)} } if isAuthRealm { - return kc.RoundTripAuth(req) + return rh.RoundTripAuth(req) } resp, err := remote.DefaultTransport.RoundTrip(req) From 6a4a6b435e86ebaa1718a20ddfa3fa9a6dec923e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 29 Apr 2022 13:55:23 +0200 Subject: [PATCH 0738/2916] refactor: Allow passing credentials to RegistryHelper --- cmd/kluctl/commands/utils.go | 4 ++ pkg/registries/registries.go | 92 +++++++++++++++++++++++------------- 2 files changed, 63 insertions(+), 33 deletions(-) diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 7b0af0754..ac4944473 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -102,6 +102,10 @@ func withProjectCommandContext(args projectTargetCommandArgs, cb func(ctx *comma func withProjectTargetCommandContext(args projectTargetCommandArgs, p *kluctl_project.KluctlProjectContext, forSeal bool, cb func(ctx *commandCtx) error) error { rh := registries.NewRegistryHelper() + err := rh.ParseAuthEntriesFromEnv() + if err != nil { + return fmt.Errorf("failed to parse registry auth from environment: %w", err) + } images, err := deployment.NewImages(rh, args.imageFlags.UpdateImages) if err != nil { return err diff --git a/pkg/registries/registries.go b/pkg/registries/registries.go index 727453199..2dca60875 100644 --- a/pkg/registries/registries.go +++ b/pkg/registries/registries.go @@ -34,6 +34,8 @@ func (e *noAuthRetryError) Error() string { } type RegistryHelper struct { + authEntries []AuthEntry + cachedAuth utils.ThreadSafeCache cachedResponses utils.ThreadSafeMultiCache authRealms map[string]bool @@ -42,6 +44,13 @@ type RegistryHelper struct { mutex sync.Mutex } +type AuthEntry struct { + Registry string + Username string + Password string + Insecure bool +} + func NewRegistryHelper() *RegistryHelper { return &RegistryHelper{} } @@ -58,7 +67,8 @@ func (rh *RegistryHelper) ListImageTags(image string) ([]string, error) { return nil, err } - if isRegistryInsecure(repo.RegistryStr()) { + e := rh.findAuthEntry(repo.RegistryStr()) + if e != nil && e.Insecure { nameOpts = append(nameOpts, name.Insecure) repo, err = name.NewRepository(image, nameOpts...) } @@ -77,20 +87,58 @@ func (rh *RegistryHelper) ListImageTags(image string) ([]string, error) { return ret, err } -func (rh *RegistryHelper) doResolve(resource authn.Resource) (authn.Authenticator, error) { - registry := resource.RegistryStr() +func (rh *RegistryHelper) AddAuthEntry(e AuthEntry) { + rh.authEntries = append(rh.authEntries, e) +} + +func (rh *RegistryHelper) ParseAuthEntriesFromEnv() error { + defaultTlsVerify := true + if x, ok := os.LookupEnv("KLUCTL_REGISTRY_DEFAULT_TLSVERIFY"); ok { + b, err := strconv.ParseBool(x) + if err != nil { + return fmt.Errorf("failed to parse KLUCTL_REGISTRY_DEFAULT_TLSVERIFY: %w", err) + } + defaultTlsVerify = b + } for _, m := range utils.ParseEnvConfigSets("KLUCTL_REGISTRY") { - host, _ := m["HOST"] - if host == registry { - username, _ := m["USERNAME"] - password, _ := m["PASSWORD"] - return authn.FromConfig(authn.AuthConfig{ - Username: username, - Password: password, - }), nil + e := AuthEntry{ + Registry: m["HOST"], + Username: m["USERNAME"], + Password: m["PASSWORD"], + Insecure: !defaultTlsVerify, + } + tlsverifyStr, ok := m["TLSVERIFY"] + if ok { + tlsverify, err := strconv.ParseBool(tlsverifyStr) + if err != nil { + return fmt.Errorf("failed to parse TLSVERIFY from env: %w", err) + } + e.Insecure = !tlsverify + } + + rh.AddAuthEntry(e) + } + return nil +} + +func (rh *RegistryHelper) findAuthEntry(registry string) *AuthEntry { + for _, e := range rh.authEntries { + if e.Registry == "*" || e.Registry == registry { + return &e } } + return nil +} + +func (rh *RegistryHelper) doResolve(resource authn.Resource) (authn.Authenticator, error) { + e := rh.findAuthEntry(resource.RegistryStr()) + if e != nil { + return authn.FromConfig(authn.AuthConfig{ + Username: e.Username, + Password: e.Password, + }), nil + } return authn.DefaultKeychain.Resolve(resource) } @@ -309,25 +357,3 @@ func (rh *RegistryHelper) RoundTrip(req *http.Request) (*http.Response, error) { resp, err := remote.DefaultTransport.RoundTrip(req) return resp, err } - -func isRegistryInsecure(registry string) bool { - for _, m := range utils.ParseEnvConfigSets("KLUCTL_REGISTRY") { - host, _ := m["HOST"] - if host == registry { - tlsverifyStr, ok := m["TLSVERIFY"] - if ok { - tlsverify, err := strconv.ParseBool(tlsverifyStr) - if err == nil { - return !tlsverify - } - } - } - } - - if x, ok := os.LookupEnv("KLUCTL_REGISTRY_DEFAULT_TLSVERIFY"); ok { - if b, err := strconv.ParseBool(x); err == nil { - return !b - } - } - return false -} From fb064e9ca8411908d556a78705d9b01c36bc68ad Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 29 Apr 2022 14:57:12 +0200 Subject: [PATCH 0739/2916] fix: Allow to configure CA bundles for registries --- pkg/registries/registries.go | 89 ++++++++++++++++++++++++++++++------ 1 file changed, 76 insertions(+), 13 deletions(-) diff --git a/pkg/registries/registries.go b/pkg/registries/registries.go index 2dca60875..b8123fd25 100644 --- a/pkg/registries/registries.go +++ b/pkg/registries/registries.go @@ -3,6 +3,8 @@ package registries import ( "bufio" "bytes" + "context" + "crypto/x509" "fmt" "github.com/docker/distribution/registry/client/auth/challenge" "github.com/golang-jwt/jwt/v4" @@ -33,21 +35,27 @@ func (e *noAuthRetryError) Error() string { return e.msg } +type transportKeyType int + +var transportKey transportKeyType + type RegistryHelper struct { authEntries []AuthEntry - cachedAuth utils.ThreadSafeCache - cachedResponses utils.ThreadSafeMultiCache - authRealms map[string]bool - authErrors map[string]bool - init sync.Once - mutex sync.Mutex + cachedTransports utils.ThreadSafeCache + cachedAuth utils.ThreadSafeCache + cachedResponses utils.ThreadSafeMultiCache + authRealms map[string]bool + authErrors map[string]bool + init sync.Once + mutex sync.Mutex } type AuthEntry struct { Registry string Username string Password string + CABundle []byte Insecure bool } @@ -57,16 +65,22 @@ func NewRegistryHelper() *RegistryHelper { func (rh *RegistryHelper) ListImageTags(image string) ([]string, error) { var nameOpts []name.Option - remoteOpts := []remote.Option{ - remote.WithAuthFromKeychain(rh), - remote.WithTransport(rh), + repo, err := name.NewRepository(image, nameOpts...) + if err != nil { + return nil, err } - repo, err := name.NewRepository(image, nameOpts...) + t, err := rh.buildTransport(repo.RegistryStr()) if err != nil { return nil, err } + remoteOpts := []remote.Option{ + remote.WithAuthFromKeychain(rh), + remote.WithTransport(rh), + remote.WithContext(context.WithValue(context.Background(), transportKey, t)), + } + e := rh.findAuthEntry(repo.RegistryStr()) if e != nil && e.Insecure { nameOpts = append(nameOpts, name.Insecure) @@ -117,6 +131,17 @@ func (rh *RegistryHelper) ParseAuthEntriesFromEnv() error { e.Insecure = !tlsverify } + ca_bundle_path := m["CA_BUNDLE"] + if ca_bundle_path != "" { + ca_bundle_path = utils.ExpandPath(ca_bundle_path) + b, err := ioutil.ReadFile(ca_bundle_path) + if err != nil { + return fmt.Errorf("failed to read ca bundle %s: %w", ca_bundle_path, err) + } else { + e.CABundle = b + } + } + rh.AddAuthEntry(e) } return nil @@ -131,6 +156,45 @@ func (rh *RegistryHelper) findAuthEntry(registry string) *AuthEntry { return nil } +func (rh *RegistryHelper) loadCA(registry string) (*x509.CertPool, error) { + e := rh.findAuthEntry(registry) + if e == nil || e.CABundle == nil { + return nil, nil + } + + p := x509.NewCertPool() + if !p.AppendCertsFromPEM(e.CABundle) { + return nil, fmt.Errorf("failed to load CA for %s", registry) + } + + return p, nil +} + +func (rh *RegistryHelper) buildTransport(registry string) (http.RoundTripper, error) { + ret, err := rh.cachedTransports.Get(registry, func() (interface{}, error) { + ca, err := rh.loadCA(registry) + if err != nil { + return nil, err + } + if ca == nil { + return remote.DefaultTransport, nil + } + + t := remote.DefaultTransport.Clone() + t.TLSClientConfig.RootCAs = ca + return t, nil + }) + if err != nil { + return nil, err + } + return ret.(http.RoundTripper), nil +} + +func (rh *RegistryHelper) getTransport(req *http.Request) http.RoundTripper { + t := req.Context().Value(transportKey).(http.RoundTripper) + return t +} + func (rh *RegistryHelper) doResolve(resource authn.Resource) (authn.Authenticator, error) { e := rh.findAuthEntry(resource.RegistryStr()) if e != nil { @@ -253,7 +317,7 @@ func (rh *RegistryHelper) RoundTripCached(req *http.Request, extraKey string, on b := rh.readCachedResponse(key) if b == nil { - res, err := remote.DefaultTransport.RoundTrip(req) + res, err := rh.getTransport(req).RoundTrip(req) if err != nil { return nil, err } @@ -354,6 +418,5 @@ func (rh *RegistryHelper) RoundTrip(req *http.Request) (*http.Response, error) { return rh.RoundTripAuth(req) } - resp, err := remote.DefaultTransport.RoundTrip(req) - return resp, err + return rh.getTransport(req).RoundTrip(req) } From df53812fcb12d9c5e873524d0b934f957556c9de Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 29 Apr 2022 18:31:14 +0200 Subject: [PATCH 0740/2916] fix: Also support "auth" field in registry auth --- pkg/registries/registries.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/registries/registries.go b/pkg/registries/registries.go index b8123fd25..da1c32496 100644 --- a/pkg/registries/registries.go +++ b/pkg/registries/registries.go @@ -55,6 +55,7 @@ type AuthEntry struct { Registry string Username string Password string + Auth string CABundle []byte Insecure bool } @@ -120,6 +121,7 @@ func (rh *RegistryHelper) ParseAuthEntriesFromEnv() error { Registry: m["HOST"], Username: m["USERNAME"], Password: m["PASSWORD"], + Auth: m["AUTH"], Insecure: !defaultTlsVerify, } tlsverifyStr, ok := m["TLSVERIFY"] @@ -201,6 +203,7 @@ func (rh *RegistryHelper) doResolve(resource authn.Resource) (authn.Authenticato return authn.FromConfig(authn.AuthConfig{ Username: e.Username, Password: e.Password, + Auth: e.Auth, }), nil } From 5cee2a141b3d7020bff55914e242f4bb806e7f0a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 2 May 2022 16:43:18 +0200 Subject: [PATCH 0741/2916] feat: Switch from kong to cobra --- cmd/kluctl/args/cobra_types.go | 87 +++++++ cmd/kluctl/args/images.go | 8 +- cmd/kluctl/args/misc.go | 2 +- cmd/kluctl/args/project.go | 16 +- cmd/kluctl/commands/cmd_render.go | 2 +- cmd/kluctl/commands/cobra_utils.go | 305 ++++++++++++++++++++++++ cmd/kluctl/commands/root.go | 85 +++---- cmd/kluctl/commands/utils.go | 16 +- go.mod | 16 +- go.sum | 12 +- pkg/deployment/deployment_collection.go | 5 +- pkg/deployment/utils/errors_holder.go | 5 +- pkg/jinja2/jinja2.go | 8 +- pkg/utils/errorlist.go | 5 +- pkg/utils/workerpool.go | 2 +- 15 files changed, 486 insertions(+), 88 deletions(-) create mode 100644 cmd/kluctl/args/cobra_types.go create mode 100644 cmd/kluctl/commands/cobra_utils.go diff --git a/cmd/kluctl/args/cobra_types.go b/cmd/kluctl/args/cobra_types.go new file mode 100644 index 000000000..7989383a1 --- /dev/null +++ b/cmd/kluctl/args/cobra_types.go @@ -0,0 +1,87 @@ +package args + +import ( + "fmt" + "github.com/kluctl/kluctl/v2/pkg/utils" +) + +type existingPathType string + +func (s *existingPathType) Set(val string) error { + if val != "-" { + val = utils.ExpandPath(val) + } + if !utils.Exists(val) { + return fmt.Errorf("%s does not exist", val) + } + *s = existingPathType(val) + return nil +} +func (s *existingPathType) Type() string { + return "existingpath" +} + +func (s *existingPathType) String() string { return string(*s) } + +type existingFileType string + +func (s *existingFileType) Set(val string) error { + if val != "-" { + val = utils.ExpandPath(val) + } + if !utils.Exists(val) { + return fmt.Errorf("%s does not exist", val) + } + if utils.IsDirectory(val) { + return fmt.Errorf("%s exists but is a directory", val) + } + *s = existingFileType(val) + return nil +} +func (s *existingFileType) Type() string { + return "existingfile" +} + +func (s *existingFileType) String() string { return string(*s) } + +type existingDirType string + +func (s *existingDirType) Set(val string) error { + if val != "-" { + val = utils.ExpandPath(val) + } + if !utils.Exists(val) { + return fmt.Errorf("%s does not exist", val) + } + if !utils.IsDirectory(val) { + return fmt.Errorf("%s exists but is not a directory", val) + } + *s = existingDirType(val) + return nil +} +func (s *existingDirType) Type() string { + return "existingdir" +} + +func (s *existingDirType) String() string { return string(*s) } + +type pathType string + +func (s *pathType) Set(val string) error { + if val != "-" { + val = utils.ExpandPath(val) + } + if !utils.Exists(val) { + return fmt.Errorf("%s does not exist", val) + } + if !utils.IsDirectory(val) { + return fmt.Errorf("%s exists but is not a directory", val) + } + *s = pathType(val) + return nil +} +func (s *pathType) Type() string { + return "path" +} + +func (s *pathType) String() string { return string(*s) } diff --git a/cmd/kluctl/args/images.go b/cmd/kluctl/args/images.go index bb9de4568..3cdcdb9f7 100644 --- a/cmd/kluctl/args/images.go +++ b/cmd/kluctl/args/images.go @@ -8,16 +8,16 @@ import ( ) type ImageFlags struct { - FixedImage []string `group:"images" short:"F" help:"Pin an image to a given version. Expects '--fixed-image=image<:namespace:deployment:container>=result'"` - FixedImagesFile string `group:"images" help:"Use .yml file to pin image versions. See output of list-images sub-command or read the documentation for details about the output format" type:"existingfile"` - UpdateImages bool `group:"images" short:"u" help:"This causes kluctl to prefer the latest image found in registries, based on the 'latest_image' filters provided to 'images.get_image(...)' calls. Use this flag if you want to update to the latest versions/tags of all images. '-u' takes precedence over '--fixed-image/--fixed-images-file', meaning that the latest images are used even if an older image is given via fixed images."` + FixedImage []string `group:"images" short:"F" help:"Pin an image to a given version. Expects '--fixed-image=image<:namespace:deployment:container>=result'"` + FixedImagesFile existingFileType `group:"images" help:"Use .yml file to pin image versions. See output of list-images sub-command or read the documentation for details about the output format"` + UpdateImages bool `group:"images" short:"u" help:"This causes kluctl to prefer the latest image found in registries, based on the 'latest_image' filters provided to 'images.get_image(...)' calls. Use this flag if you want to update to the latest versions/tags of all images. '-u' takes precedence over '--fixed-image/--fixed-images-file', meaning that the latest images are used even if an older image is given via fixed images."` } func (args *ImageFlags) LoadFixedImagesFromArgs() ([]types.FixedImage, error) { var ret types.FixedImagesConfig if args.FixedImagesFile != "" { - err := yaml.ReadYamlFile(args.FixedImagesFile, &ret) + err := yaml.ReadYamlFile(args.FixedImagesFile.String(), &ret) if err != nil { return nil, err } diff --git a/cmd/kluctl/args/misc.go b/cmd/kluctl/args/misc.go index a9fc026a8..c4972ca44 100644 --- a/cmd/kluctl/args/misc.go +++ b/cmd/kluctl/args/misc.go @@ -44,5 +44,5 @@ type OutputFlags struct { } type RenderOutputDirFlags struct { - RenderOutputDir string `group:"misc" help:"Specifies the target directory to render the project into. If omitted, a temporary directory is used." type:"path"` + RenderOutputDir pathType `group:"misc" help:"Specifies the target directory to render the project into. If omitted, a temporary directory is used."` } diff --git a/cmd/kluctl/args/project.go b/cmd/kluctl/args/project.go index d6f555799..167f04ea7 100644 --- a/cmd/kluctl/args/project.go +++ b/cmd/kluctl/args/project.go @@ -6,14 +6,14 @@ type ProjectFlags struct { ProjectUrl string `group:"project" short:"p" help:"Git url of the kluctl project. If not specified, the current directory will be used instead of a remote Git project"` ProjectRef string `group:"project" short:"b" help:"Git ref of the kluctl project. Only used when --project-url was given."` - ProjectConfig string `group:"project" short:"c" help:"Location of the .kluctl.yml config file. Defaults to $PROJECT/.kluctl.yml" type:"existingfile"` - LocalClusters string `group:"project" help:"Local clusters directory. Overrides the project from .kluctl.yml" type:"existingdir"` - LocalDeployment string `group:"project" help:"Local deployment directory. Overrides the project from .kluctl.yml" type:"existingdir"` - LocalSealedSecrets string `group:"project" help:"Local sealed-secrets directory. Overrides the project from .kluctl.yml" type:"existingdir"` - FromArchive string `group:"project" help:"Load project (.kluctl.yml, cluster, ...) from archive. Given path can either be an archive file or a directory with the extracted contents." type:"existing"` - FromArchiveMetadata string `group:"project" help:"Specify where to load metadata (targets, ...) from. If not specified, metadata is assumed to be part of the archive." type:"existingfile"` - OutputMetadata string `group:"project" help:"Specify the output path for the project metadata to be written to. When used with the 'archive' command, it will also cause the archive to not include the metadata.yaml file." type:"file"` - Cluster string `group:"project" help:"Specify/Override cluster"` + ProjectConfig existingFileType `group:"project" short:"c" help:"Location of the .kluctl.yml config file. Defaults to $PROJECT/.kluctl.yml"` + LocalClusters existingDirType `group:"project" help:"Local clusters directory. Overrides the project from .kluctl.yml"` + LocalDeployment existingDirType `group:"project" help:"Local deployment directory. Overrides the project from .kluctl.yml"` + LocalSealedSecrets existingDirType `group:"project" help:"Local sealed-secrets directory. Overrides the project from .kluctl.yml" ` + FromArchive existingPathType `group:"project" help:"Load project (.kluctl.yml, cluster, ...) from archive. Given path can either be an archive file or a directory with the extracted contents."` + FromArchiveMetadata existingFileType `group:"project" help:"Specify where to load metadata (targets, ...) from. If not specified, metadata is assumed to be part of the archive." ` + OutputMetadata pathType `group:"project" help:"Specify the output path for the project metadata to be written to. When used with the 'archive' command, it will also cause the archive to not include the metadata.yaml file."` + Cluster string `group:"project" help:"Specify/Override cluster"` LoadTimeout time.Duration `group:"project" help:"Specify timeout for project loading. This will especially limit the time spent in git operations." default:"1m"` } diff --git a/cmd/kluctl/commands/cmd_render.go b/cmd/kluctl/commands/cmd_render.go index a686da313..849175838 100644 --- a/cmd/kluctl/commands/cmd_render.go +++ b/cmd/kluctl/commands/cmd_render.go @@ -26,7 +26,7 @@ func (cmd *renderCmd) Run() error { if err != nil { return err } - cmd.RenderOutputDir = p + _ = cmd.RenderOutputDir.Set(p) } ptArgs := projectTargetCommandArgs{ diff --git a/cmd/kluctl/commands/cobra_utils.go b/cmd/kluctl/commands/cobra_utils.go new file mode 100644 index 000000000..083e07693 --- /dev/null +++ b/cmd/kluctl/commands/cobra_utils.go @@ -0,0 +1,305 @@ +package commands + +import ( + "fmt" + "github.com/kluctl/kluctl/v2/pkg/utils" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/spf13/viper" + "reflect" + "regexp" + "strconv" + "strings" + "time" + "unicode" +) + +type helpProvider interface { + Help() string +} + +type runProvider interface { + Run() error +} + +type rootCommand struct { + rootCmd *commandAndGroups + groupInfos []groupInfo +} + +type commandAndGroups struct { + parent *commandAndGroups + cmd *cobra.Command + groups map[string]string +} + +type groupInfo struct { + group string + title string + description string +} + +func buildRootCobraCmd(cmdStruct interface{}, name string, short string, long string, groupInfos []groupInfo) (*cobra.Command, error) { + c := &rootCommand{ + groupInfos: groupInfos, + } + + cmd, err := c.buildCobraCmd(nil, cmdStruct, name, short, long) + if err != nil { + return nil, err + } + c.rootCmd = cmd + + return cmd.cmd, nil +} + +func (c *rootCommand) buildCobraCmd(parent *commandAndGroups, cmdStruct interface{}, name string, short string, long string) (*commandAndGroups, error) { + cg := &commandAndGroups{ + parent: parent, + groups: map[string]string{}, + cmd: &cobra.Command{ + Use: name, + Short: short, + Long: long, + }, + } + + runP, ok := cmdStruct.(runProvider) + if ok { + cg.cmd.RunE = func(cmd *cobra.Command, args []string) error { + return runP.Run() + } + } + + err := c.buildCobraSubCommands(cg, cmdStruct) + if err != nil { + return nil, err + } + err = c.buildCobraArgs(cg, cmdStruct) + if err != nil { + return nil, err + } + + cg.cmd.SetHelpFunc(func(command *cobra.Command, i []string) { + c.helpFunc(cg) + }) + + return cg, nil +} + +func (c *rootCommand) buildCobraSubCommands(cg *commandAndGroups, cmdStruct interface{}) error { + v := reflect.ValueOf(cmdStruct).Elem() + t := v.Type() + for i := 0; i < t.NumField(); i++ { + f := t.Field(i) + v2 := v.Field(i).Addr().Interface() + name := buildCobraName(f.Name) + + if _, ok := f.Tag.Lookup("cmd"); ok { + // subcommand + shortHelp := f.Tag.Get("help") + longHelp := "" + if hp, ok := v2.(helpProvider); ok { + longHelp = hp.Help() + } + + c2, err := c.buildCobraCmd(cg, v2, name, shortHelp, longHelp) + if err != nil { + return err + } + cg.cmd.AddCommand(c2.cmd) + } + } + return nil +} + +func (c *rootCommand) buildCobraArgs(cg *commandAndGroups, cmdStruct interface{}) error { + v := reflect.ValueOf(cmdStruct).Elem() + t := v.Type() + for i := 0; i < t.NumField(); i++ { + f := t.Field(i) + if _, ok := f.Tag.Lookup("cmd"); ok { + continue + } + + err := c.buildCobraArg(cg, f, v.Field(i)) + if err != nil { + return err + } + } + return nil +} + +func (c *rootCommand) buildCobraArg(cg *commandAndGroups, f reflect.StructField, v reflect.Value) error { + v2 := v.Addr().Interface() + name := buildCobraName(f.Name) + + help := f.Tag.Get("help") + shortFlag := f.Tag.Get("short") + defaultValue := f.Tag.Get("default") + + group := f.Tag.Get("group") + if group != "" { + cg.groups[name] = group + } + + switch v2.(type) { + case pflag.Value: + cg.cmd.PersistentFlags().VarPF(v2.(pflag.Value), name, shortFlag, help) + case *string: + cg.cmd.PersistentFlags().StringVarP(v2.(*string), name, shortFlag, defaultValue, help) + case *[]string: + if defaultValue != "" { + return fmt.Errorf("default not supported for slices") + } + cg.cmd.PersistentFlags().StringSliceVarP(v2.(*[]string), name, shortFlag, nil, help) + case *bool: + parsedDefault := false + if defaultValue != "" { + x, err := strconv.ParseBool(defaultValue) + if err != nil { + return err + } + parsedDefault = x + } + cg.cmd.PersistentFlags().BoolVarP(v2.(*bool), name, shortFlag, parsedDefault, help) + case *time.Duration: + var parsedDefault time.Duration + if defaultValue != "" { + x, err := time.ParseDuration(defaultValue) + if err != nil { + return err + } + parsedDefault = x + } + cg.cmd.PersistentFlags().DurationVarP(v2.(*time.Duration), name, shortFlag, parsedDefault, help) + default: + if f.Anonymous { + return c.buildCobraArgs(cg, v2) + } + return fmt.Errorf("unknown type %s", f.Type.Name()) + } + + return nil +} + +func copyViperValuesToCobraCmd(cmd *cobra.Command) error { + for cmd != nil { + err := copyViperValuesToCobraFlags(cmd.PersistentFlags()) + if err != nil { + return err + } + cmd = cmd.Parent() + } + return nil +} + +func copyViperValuesToCobraFlags(flags *pflag.FlagSet) error { + var retErr []error + flags.VisitAll(func(flag *pflag.Flag) { + if flag.Changed { + return + } + v := viper.Get(flag.Name) + if v != nil { + s, ok := v.(string) + if !ok { + retErr = append(retErr, fmt.Errorf("viper flag %s is not a string", flag.Name)) + return + } + err := flag.Value.Set(s) + if err != nil { + retErr = append(retErr, err) + } + } + }) + return utils.NewErrorListOrNil(retErr) +} + +func (c *rootCommand) helpFunc(cg *commandAndGroups) { + cmd := cg.cmd + + termWidth := utils.GetTermWidth() + + h := "Usage: " + if cmd.Runnable() { + h += cmd.UseLine() + } + if cmd.HasAvailableSubCommands() { + h += fmt.Sprintf("%s [command]", cmd.CommandPath()) + } + + h += "\n\n" + if cmd.Short != "" { + h += strings.TrimRightFunc(cmd.Short, unicode.IsSpace) + "\n" + } + if cmd.Long != "" { + h += strings.TrimRightFunc(cmd.Long, unicode.IsSpace) + "\n" + } + + flagsByGroups := c.buildGroupedFlagSets(cg) + + for _, g := range c.groupInfos { + fl, ok := flagsByGroups[g.group] + if !ok { + continue + } + h += "\n" + h += g.title + "\n" + if g.description != "" { + h += " " + g.description + "\n\n" + } + usages := fl.FlagUsagesWrapped(termWidth) + usages = strings.TrimRightFunc(usages, unicode.IsSpace) + usages += "\n" + h += usages + } + + if cmd.HasAvailableSubCommands() { + h += "\nCommands:\n" + for _, subCmd := range cmd.Commands() { + h += " " + rpad(subCmd.Name(), subCmd.NamePadding()) + " " + subCmd.Short + "\n" + } + h += fmt.Sprintf("\nUse \"%s [command] --help\" for more information about a command.\n", cmd.CommandPath()) + } + + _, _ = cmd.OutOrStdout().Write([]byte(h)) +} + +func (c *rootCommand) buildGroupedFlagSets(cg *commandAndGroups) map[string]*pflag.FlagSet { + flagsByGroups := make(map[string]*pflag.FlagSet) + + x := cg + for x != nil { + x.cmd.PersistentFlags().VisitAll(func(flag *pflag.Flag) { + group, ok := x.groups[flag.Name] + if !ok { + log.Panicf("group for %s not found", flag.Name) + } + fl, ok := flagsByGroups[group] + if !ok { + fl = pflag.NewFlagSet(group, pflag.PanicOnError) + flagsByGroups[group] = fl + } + fl.AddFlag(flag) + }) + x = x.parent + } + + return flagsByGroups +} + +func rpad(s string, padding int) string { + template := fmt.Sprintf("%%-%ds", padding) + return fmt.Sprintf(template, s) +} + +var matchFirstCap = regexp.MustCompile("(.)([A-Z][a-z]+)") +var matchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])") + +func buildCobraName(fieldName string) string { + n := matchFirstCap.ReplaceAllString(fieldName, "${1}-${2}") + n = matchAllCap.ReplaceAllString(n, "${1}-${2}") + return strings.ToLower(n) +} diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index 9cfa4bf32..68a8fbb2a 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -16,13 +16,14 @@ limitations under the License. package commands import ( - "github.com/alecthomas/kong" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/utils/versions" "github.com/kluctl/kluctl/v2/pkg/version" "github.com/kluctl/kluctl/v2/pkg/yaml" log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/spf13/viper" "net/http" "os" "path/filepath" @@ -55,12 +56,12 @@ type cli struct { Version versionCmd `cmd:"" help:"Print kluctl version"` } -var flagGroups = []kong.Group{ - {Key: "project", Title: "Project arguments:", Description: "Define where and how to load the kluctl project and its components from."}, - {Key: "images", Title: "Image arguments:", Description: "Control fixed images and update behaviour."}, - {Key: "inclusion", Title: "Inclusion/Exclusion arguments:", Description: "Control inclusion/exclusion."}, - {Key: "misc", Title: "Misc arguments:", Description: "Command specific arguments."}, - {Key: "global", Title: "Global arguments:"}, +var flagGroups = []groupInfo{ + {group: "global", title: "Global arguments:"}, + {group: "project", title: "Project arguments:", description: "Define where and how to load the kluctl project and its components from."}, + {group: "images", title: "Image arguments:", description: "Control fixed images and update behaviour."}, + {group: "inclusion", title: "Inclusion/Exclusion arguments:", description: "Control inclusion/exclusion."}, + {group: "misc", title: "Misc arguments:", description: "Command specific arguments."}, } var globalFlagGroup = &flagGroups[len(flagGroups)-1] @@ -125,7 +126,7 @@ func (c *cli) checkNewVersion() { } } -func (c *cli) AfterApply() error { +func (c *cli) preRun() error { if err := c.setupLogs(); err != nil { return err } @@ -133,50 +134,50 @@ func (c *cli) AfterApply() error { return nil } -func (c *cli) Help() string { - return ` -Deploy and manage complex deployments on Kubernetes +func initViper() { + viper.SetConfigName("config") + viper.SetConfigType("yaml") + viper.AddConfigPath("/etc/kluctl/") + viper.AddConfigPath("$HOME/.kluctl") + if err := viper.ReadInConfig(); err != nil { + if _, ok := err.(viper.ConfigFileNotFoundError); !ok { + log.Error(err) + os.Exit(1) + } + } -The missing glue to put together large Kubernetes deployments, -composed of multiple smaller parts (Helm/Kustomize/...) in a manageable and unified way.` + viper.SetEnvPrefix("kluctl") + viper.AutomaticEnv() } -func ParseArgs(args []string, options ...kong.Option) (*kong.Kong, *kong.Context, error) { - var cli cli +func Execute() { + root := cli{} + rootCmd, err := buildRootCobraCmd(&root, "kluctl", + "Deploy and manage complex deployments on Kubernetes", + `The missing glue to put together large Kubernetes deployments, +composed of multiple smaller parts (Helm/Kustomize/...) in a manageable and unified way.`, + flagGroups) - helpOption := kong.HelpOptions{ - Compact: true, - Summary: true, - WrapUpperBound: 120, + if err != nil { + log.Fatal(err) } - var options2 []kong.Option - options2 = append(options2, helpOption) - options2 = append(options2, kong.ExplicitGroups(flagGroups)) - options2 = append(options2, kong.ShortUsageOnError()) - options2 = append(options2, kong.Name("kluctl")) - options2 = append(options2, options...) + rootCmd.Version = version.GetVersion() + rootCmd.SilenceUsage = true - parser, err := kong.New(&cli, options2...) - if err != nil { - panic(err) + rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error { + err := copyViperValuesToCobraCmd(cmd) + if err != nil { + return err + } + return root.preRun() } - parser.Model.HelpFlag.Group = globalFlagGroup - ctx, err := parser.Parse(args) - return parser, ctx, err -} -func Execute() { - confOption := kong.Configuration(kong.JSON, "/etc/kluctl.json", "~/.kluctl/config.json") - envOption := kong.DefaultEnvars("KLUCTL") - parser, _, err := ExecuteWithArgs(os.Args[1:], confOption, envOption) - parser.FatalIfErrorf(err) -} + initViper() -func ExecuteWithArgs(args []string, options ...kong.Option) (*kong.Kong, *kong.Context, error) { - parser, ctx, err := ParseArgs(args, options...) + err = rootCmd.Execute() if err != nil { - return parser, ctx, err + log.Errorf("%v", err) + os.Exit(1) } - return parser, ctx, ctx.Run() } diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index ac4944473..ddbcc2b21 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -50,12 +50,12 @@ func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, cb func(p *kluctl ProjectDir: cwd, ProjectUrl: url, ProjectRef: projectFlags.ProjectRef, - ProjectConfig: projectFlags.ProjectConfig, - LocalClusters: projectFlags.LocalClusters, - LocalDeployment: projectFlags.LocalDeployment, - LocalSealedSecrets: projectFlags.LocalSealedSecrets, - FromArchive: projectFlags.FromArchive, - FromArchiveMetadata: projectFlags.FromArchiveMetadata, + ProjectConfig: projectFlags.ProjectConfig.String(), + LocalClusters: projectFlags.LocalClusters.String(), + LocalDeployment: projectFlags.LocalDeployment.String(), + LocalSealedSecrets: projectFlags.LocalSealedSecrets.String(), + FromArchive: projectFlags.FromArchive.String(), + FromArchiveMetadata: projectFlags.FromArchiveMetadata.String(), GitAuthProviders: auth.NewDefaultAuthProviders(), } @@ -71,7 +71,7 @@ func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, cb func(p *kluctl if err != nil { return err } - err = ioutil.WriteFile(projectFlags.OutputMetadata, b, 0o640) + err = ioutil.WriteFile(projectFlags.OutputMetadata.String(), b, 0o640) if err != nil { return err } @@ -128,7 +128,7 @@ func withProjectTargetCommandContext(args projectTargetCommandArgs, p *kluctl_pr return err } - renderOutputDir := args.renderOutputDirFlags.RenderOutputDir + renderOutputDir := args.renderOutputDirFlags.RenderOutputDir.String() if renderOutputDir == "" { tmpDir, err := ioutil.TempDir(p.TmpDir, "rendered") if err != nil { diff --git a/go.mod b/go.mod index ab56a0893..f432a9b25 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,6 @@ go 1.18 require ( github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e - github.com/alecthomas/kong v0.5.0 github.com/aws/aws-sdk-go v1.44.1 github.com/bitnami-labs/sealed-secrets v0.17.5 github.com/docker/distribution v2.8.1+incompatible @@ -28,6 +27,9 @@ require ( github.com/pkg/errors v0.9.1 github.com/r3labs/diff/v2 v2.15.1 github.com/sirupsen/logrus v1.8.1 + github.com/spf13/cobra v1.4.0 + github.com/spf13/pflag v1.0.5 + github.com/spf13/viper v1.10.0 github.com/stretchr/testify v1.7.1 github.com/vbauerster/mpb/v7 v7.4.1 github.com/whilp/git-urls v1.0.0 @@ -36,6 +38,7 @@ require ( golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 + golang.org/x/text v0.3.7 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b helm.sh/helm/v3 v3.8.2 k8s.io/api v0.24.0-rc.1 @@ -93,6 +96,7 @@ require ( github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect github.com/fatih/color v1.13.0 // indirect + github.com/fsnotify/fsnotify v1.5.1 // indirect github.com/gammazero/deque v0.1.1 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-git/gcfg v1.5.0 // indirect @@ -115,6 +119,7 @@ require ( github.com/gorilla/mux v1.8.0 // indirect github.com/gosuri/uitable v0.0.4 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect github.com/huandu/xstrings v1.3.2 // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect @@ -128,6 +133,7 @@ require ( github.com/leodido/go-urn v1.2.1 // indirect github.com/lib/pq v1.10.5 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect + github.com/magiconair/properties v1.8.5 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect @@ -135,6 +141,7 @@ require ( github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/mitchellh/mapstructure v1.4.3 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moby/locker v1.0.1 // indirect github.com/moby/spdystream v0.2.0 // indirect @@ -157,9 +164,10 @@ require ( github.com/russross/blackfriday v1.6.0 // indirect github.com/sergi/go-diff v1.2.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect + github.com/spf13/afero v1.6.0 // indirect github.com/spf13/cast v1.4.1 // indirect - github.com/spf13/cobra v1.4.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/subosito/gotenv v1.2.0 // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect @@ -168,7 +176,6 @@ require ( go.starlark.net v0.0.0-20220328144851-d1966c6b9fcd // indirect golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect - golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect google.golang.org/appengine v1.6.7 // indirect @@ -176,6 +183,7 @@ require ( google.golang.org/grpc v1.46.0 // indirect google.golang.org/protobuf v1.28.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/ini.v1 v1.66.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/apiextensions-apiserver v0.23.6 // indirect diff --git a/go.sum b/go.sum index 492559201..3c91b81d5 100644 --- a/go.sum +++ b/go.sum @@ -153,10 +153,6 @@ github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpH github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= -github.com/alecthomas/kong v0.5.0 h1:u8Kdw+eeml93qtMZ04iei0CFYve/WPcA5IFh+9wSskE= -github.com/alecthomas/kong v0.5.0/go.mod h1:uzxf/HUh0tj43x1AyJROl3JT7SgsZ5m+icOv1csRhc0= -github.com/alecthomas/repr v0.0.0-20210801044451-80ca428c5142 h1:8Uy0oSf5co/NZXje7U1z8Mpep++QJOldL2hs/sBQf48= -github.com/alecthomas/repr v0.0.0-20210801044451-80ca428c5142/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -764,6 +760,7 @@ github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= @@ -879,6 +876,7 @@ github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -952,6 +950,7 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= @@ -1175,6 +1174,7 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= +github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= @@ -1190,6 +1190,7 @@ github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -1200,6 +1201,7 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= +github.com/spf13/viper v1.10.0 h1:mXH0UwHS4D2HwWZa75im4xIQynLfblmWV7qcWpfv0yk= github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= @@ -1217,6 +1219,7 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= @@ -1942,6 +1945,7 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI= gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index 887e903bf..2d9333612 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -190,10 +190,7 @@ func (c *DeploymentCollection) buildKustomizeObjects(k *k8s.K8sCluster) error { } wg.Wait() - if len(errs) != 0 { - return utils.NewErrorList(errs) - } - return nil + return utils.NewErrorListOrNil(errs) } func (c *DeploymentCollection) LocalObjectsByRef() map[k8s2.ObjectRef]bool { diff --git a/pkg/deployment/utils/errors_holder.go b/pkg/deployment/utils/errors_holder.go index 92c184dda..9b702a378 100644 --- a/pkg/deployment/utils/errors_holder.go +++ b/pkg/deployment/utils/errors_holder.go @@ -106,8 +106,5 @@ func (dew *DeploymentErrorsAndWarnings) getPlainErrorsList() []error { func (dew *DeploymentErrorsAndWarnings) GetMultiError() error { l := dew.getPlainErrorsList() - if len(l) == 0 { - return nil - } - return utils.NewErrorList(l) + return utils.NewErrorListOrNil(l) } diff --git a/pkg/jinja2/jinja2.go b/pkg/jinja2/jinja2.go index 58f115727..bcdd6d4b3 100644 --- a/pkg/jinja2/jinja2.go +++ b/pkg/jinja2/jinja2.go @@ -161,7 +161,7 @@ func (j *Jinja2) RenderStruct(dst interface{}, src interface{}, vars *uo.Unstruc } } if len(errors) != 0 { - return utils.NewErrorList(errors) + return utils.NewErrorListOrNil(errors) } err = m.ToStruct(dst) @@ -277,9 +277,5 @@ func (j *Jinja2) RenderDirectory(rootDir string, searchDirs []string, relSourceD return err } } - if len(errors) != 0 { - return utils.NewErrorList(errors) - } - - return nil + return utils.NewErrorListOrNil(errors) } diff --git a/pkg/utils/errorlist.go b/pkg/utils/errorlist.go index 374564cc5..0191aa7d1 100644 --- a/pkg/utils/errorlist.go +++ b/pkg/utils/errorlist.go @@ -4,7 +4,10 @@ type errorList struct { errors []error } -func NewErrorList(errors []error) *errorList { +func NewErrorListOrNil(errors []error) error { + if len(errors) == 0 { + return nil + } return &errorList{errors: errors} } diff --git a/pkg/utils/workerpool.go b/pkg/utils/workerpool.go index 5621723e6..fe71e32b9 100644 --- a/pkg/utils/workerpool.go +++ b/pkg/utils/workerpool.go @@ -70,7 +70,7 @@ func (wp *WorkerPoolWithErrors) StopWait(restart bool) error { if len(wp.errors) == 0 { return nil } - err := NewErrorList(wp.errors) + err := NewErrorListOrNil(wp.errors) wp.errors = nil return err } From 5866bbfd209d13da3970888dfc4138eb3214457a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 3 May 2022 10:58:27 +0200 Subject: [PATCH 0742/2916] fix: Only fetch when refs have changed --- pkg/git/mirrored_repo.go | 58 ++++++++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/pkg/git/mirrored_repo.go b/pkg/git/mirrored_repo.go index b6db9ed33..a5ec3a3ba 100644 --- a/pkg/git/mirrored_repo.go +++ b/pkg/git/mirrored_repo.go @@ -148,36 +148,54 @@ func (g *MirroredGitRepo) update(ctx context.Context, repoDir string, authProvid if err != nil { return err } - remoteRefsMap := make(map[plumbing.ReferenceName]bool) + remoteRefsMap := make(map[plumbing.ReferenceName]*plumbing.Reference) for _, reference := range g.remoteRefs { - remoteRefsMap[reference.Name()] = true - } - - err = remote.FetchContext(ctx, &git.FetchOptions{ - Auth: auth.AuthMethod, - CABundle: auth.CABundle, - Progress: os.Stdout, - Tags: git.AllTags, - Force: true, - }) - if err != nil && err != git.NoErrAlreadyUpToDate { - return err + remoteRefsMap[reference.Name()] = reference } localRemoteRefs, err := r.References() if err != nil { return err } - var toDelete []plumbing.Reference - err = localRemoteRefs.ForEach(func(reference *plumbing.Reference) error { - if _, ok := remoteRefsMap[reference.Name()]; !ok { - toDelete = append(toDelete, *reference) - } + + localRemoteRefsMap := make(map[plumbing.ReferenceName]*plumbing.Reference) + _ = localRemoteRefs.ForEach(func(reference *plumbing.Reference) error { + localRemoteRefsMap[reference.Name()] = reference return nil }) - if err != nil { - return err + + var toDelete []*plumbing.Reference + changed := false + for name, ref := range remoteRefsMap { + if name.String() != "HEAD" && !strings.HasPrefix(name.String(), "refs/heads/") && !strings.HasPrefix(name.String(), "refs/tags/") { + // we only fetch branches and tags + continue + } + if x, ok := localRemoteRefsMap[name]; !ok { + changed = true + } else if *x != *ref { + changed = true + } + } + for name, ref := range localRemoteRefsMap { + if _, ok := remoteRefsMap[name]; !ok { + toDelete = append(toDelete, ref) + } } + + if changed { + err = remote.FetchContext(ctx, &git.FetchOptions{ + Auth: auth.AuthMethod, + CABundle: auth.CABundle, + Progress: os.Stdout, + Tags: git.AllTags, + Force: true, + }) + if err != nil && err != git.NoErrAlreadyUpToDate { + return err + } + } + for _, ref := range toDelete { err = r.Storer.RemoveReference(ref.Name()) if err != nil { From 2cb9d8970e0349a25d6b43097c1b6c91ed4284dd Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 3 May 2022 11:35:21 +0200 Subject: [PATCH 0743/2916] feat: Allow to specify git cache update interval --- cmd/kluctl/args/project.go | 3 +- cmd/kluctl/commands/utils.go | 1 + pkg/git/mirrored_repo.go | 76 +++++++++++++++++++++++------- pkg/kluctl_project/git.go | 25 ++++++---- pkg/kluctl_project/load_targets.go | 5 +- pkg/kluctl_project/project.go | 3 ++ 6 files changed, 85 insertions(+), 28 deletions(-) diff --git a/cmd/kluctl/args/project.go b/cmd/kluctl/args/project.go index 167f04ea7..67da88a05 100644 --- a/cmd/kluctl/args/project.go +++ b/cmd/kluctl/args/project.go @@ -15,7 +15,8 @@ type ProjectFlags struct { OutputMetadata pathType `group:"project" help:"Specify the output path for the project metadata to be written to. When used with the 'archive' command, it will also cause the archive to not include the metadata.yaml file."` Cluster string `group:"project" help:"Specify/Override cluster"` - LoadTimeout time.Duration `group:"project" help:"Specify timeout for project loading. This will especially limit the time spent in git operations." default:"1m"` + LoadTimeout time.Duration `group:"project" help:"Specify timeout for project loading. This will especially limit the time spent in git operations." default:"1m"` + GitCacheUpdateInterval time.Duration `group:"project" help:"Specify the time to wait between git cache updates. Defaults to not wait at all and always updating caches."` } type ArgsFlags struct { diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index ddbcc2b21..39516c6f8 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -57,6 +57,7 @@ func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, cb func(p *kluctl FromArchive: projectFlags.FromArchive.String(), FromArchiveMetadata: projectFlags.FromArchiveMetadata.String(), GitAuthProviders: auth.NewDefaultAuthProviders(), + GitUpdateInterval: projectFlags.GitCacheUpdateInterval, } loadCtx, cancel := context.WithDeadline(context.Background(), time.Now().Add(projectFlags.LoadTimeout)) diff --git a/pkg/git/mirrored_repo.go b/pkg/git/mirrored_repo.go index a5ec3a3ba..0afba2cd9 100644 --- a/pkg/git/mirrored_repo.go +++ b/pkg/git/mirrored_repo.go @@ -26,8 +26,6 @@ type MirroredGitRepo struct { hasUpdated bool fileLock *flock.Flock - - remoteRefs []*plumbing.Reference } func NewMirroredGitRepo(u git_url.GitUrl) (*MirroredGitRepo, error) { @@ -51,6 +49,10 @@ func (g *MirroredGitRepo) HasUpdated() bool { return g.hasUpdated } +func (g *MirroredGitRepo) SetUpdated(u bool) { + g.hasUpdated = u +} + func (g *MirroredGitRepo) Lock(ctx context.Context) error { ok, err := g.fileLock.TryLockContext(ctx, time.Millisecond*100) if err != nil { @@ -87,24 +89,62 @@ func (g *MirroredGitRepo) MaybeWithLock(ctx context.Context, lock bool, cb func( return cb() } -func (g *MirroredGitRepo) RemoteRefHashesMap() map[string]string { - refs := make(map[string]string) - for _, r := range g.remoteRefs { - refs[r.Name().String()] = r.Hash().String() +func (g *MirroredGitRepo) LastUpdateTime() time.Time { + s, err := ioutil.ReadFile(filepath.Join(g.mirrorDir, ".update-time")) + if err != nil { + return time.Time{} } - return refs + t, err := time.Parse(time.RFC3339Nano, string(s)) + if err != nil { + return time.Time{} + } + return t } -func (g *MirroredGitRepo) DefaultRef() *string { - for _, ref := range g.remoteRefs { - if ref.Name() == "HEAD" { - if ref.Type() == plumbing.SymbolicReference { - s := string(ref.Target()) - return &s +func (g *MirroredGitRepo) RemoteRefHashesMap() (map[string]string, error) { + r, err := git.PlainOpen(g.mirrorDir) + if err != nil { + return nil, err + } + + localRemoteRefs, err := r.References() + if err != nil { + return nil, err + } + + refs := make(map[string]string) + err = localRemoteRefs.ForEach(func(reference *plumbing.Reference) error { + name := reference.Name().String() + hash := reference.Hash().String() + if reference.Hash().IsZero() { + reference, err = r.Reference(reference.Name(), true) + if err != nil { + return err } + hash = reference.Hash().String() } + refs[name] = hash + return nil + }) + if err != nil { + return nil, err } - return nil + return refs, nil +} + +func (g *MirroredGitRepo) DefaultRef() *string { + r, err := git.PlainOpen(g.mirrorDir) + if err != nil { + return nil + } + + ref, err := r.Reference("HEAD", false) + if err != nil { + return nil + } + + s := ref.Target().String() + return &s } func (g *MirroredGitRepo) buildRepositoryObject() (*git.Repository, error) { @@ -141,7 +181,7 @@ func (g *MirroredGitRepo) update(ctx context.Context, repoDir string, authProvid return err } - g.remoteRefs, err = remote.ListContext(ctx, &git.ListOptions{ + remoteRefs, err := remote.ListContext(ctx, &git.ListOptions{ Auth: auth.AuthMethod, CABundle: auth.CABundle, }) @@ -149,7 +189,7 @@ func (g *MirroredGitRepo) update(ctx context.Context, repoDir string, authProvid return err } remoteRefsMap := make(map[plumbing.ReferenceName]*plumbing.Reference) - for _, reference := range g.remoteRefs { + for _, reference := range remoteRefs { remoteRefsMap[reference.Name()] = reference } @@ -205,7 +245,7 @@ func (g *MirroredGitRepo) update(ctx context.Context, repoDir string, authProvid // update default branch, referenced via HEAD // we assume that HEAD is a symbolic ref and don't care about old git versions - for _, ref := range g.remoteRefs { + for _, ref := range remoteRefs { if ref.Name() == "HEAD" { err = r.Storer.SetReference(ref) if err != nil { @@ -215,6 +255,8 @@ func (g *MirroredGitRepo) update(ctx context.Context, repoDir string, authProvid } } + _ = ioutil.WriteFile(filepath.Join(g.mirrorDir, ".update-time"), []byte(time.Now().Format(time.RFC3339Nano)), 0644) + return nil } diff --git a/pkg/kluctl_project/git.go b/pkg/kluctl_project/git.go index 3a6b56a00..23072f103 100644 --- a/pkg/kluctl_project/git.go +++ b/pkg/kluctl_project/git.go @@ -14,8 +14,20 @@ import ( "sort" "strings" "sync" + "time" ) +func (c *KluctlProjectContext) updateGitCache(ctx context.Context, mr *git.MirroredGitRepo) error { + if mr.HasUpdated() { + return nil + } + if time.Now().Sub(mr.LastUpdateTime()) <= c.loadArgs.GitUpdateInterval { + mr.SetUpdated(true) + return nil + } + return mr.Update(ctx, c.loadArgs.GitAuthProviders) +} + func (c *KluctlProjectContext) updateGitCaches(ctx context.Context) error { var waitGroup sync.WaitGroup var firstError error @@ -31,10 +43,7 @@ func (c *KluctlProjectContext) updateGitCaches(ctx context.Context) error { doUpdateRepo := func(repo *git.MirroredGitRepo) error { return repo.WithLock(ctx, func() error { - if !repo.HasUpdated() { - return repo.Update(ctx, c.loadArgs.GitAuthProviders) - } - return nil + return c.updateGitCache(ctx, repo) }) } doUpdateGitProject := func(u git_url.GitUrl) error { @@ -156,11 +165,9 @@ func (c *KluctlProjectContext) cloneGitProject(ctx context.Context, gitProject t } err = mr.MaybeWithLock(ctx, doLock, func() error { - if !mr.HasUpdated() { - err = mr.Update(ctx, c.loadArgs.GitAuthProviders) - if err != nil { - return err - } + err := c.updateGitCache(ctx, mr) + if err != nil { + return err } return mr.CloneProject(ctx, gitProject.Project.Ref, targetDir) }) diff --git a/pkg/kluctl_project/load_targets.go b/pkg/kluctl_project/load_targets.go index bfafa94d5..ec20c2f0f 100644 --- a/pkg/kluctl_project/load_targets.go +++ b/pkg/kluctl_project/load_targets.go @@ -157,7 +157,10 @@ func (c *KluctlProjectContext) prepareDynamicTargetsExternal(baseTarget *types.T targetConfigRef = defaultBranch } - refs := mr.RemoteRefHashesMap() + refs, err := mr.RemoteRefHashesMap() + if err != nil { + return nil, err + } if targetConfigRef != nil { if _, ok := refs[fmt.Sprintf("refs/heads/%s", *targetConfigRef)]; !ok { diff --git a/pkg/kluctl_project/project.go b/pkg/kluctl_project/project.go index bacb21ae2..dce44c7fc 100644 --- a/pkg/kluctl_project/project.go +++ b/pkg/kluctl_project/project.go @@ -8,6 +8,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/jinja2" "github.com/kluctl/kluctl/v2/pkg/types" "regexp" + "time" ) type LoadKluctlProjectArgs struct { @@ -22,6 +23,8 @@ type LoadKluctlProjectArgs struct { FromArchiveMetadata string GitAuthProviders *auth2.GitAuthProviders + + GitUpdateInterval time.Duration } type KluctlProjectContext struct { From ec4dd0966b0a991fb9c42d6ee14461e6a968d5fb Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 3 May 2022 11:42:36 +0200 Subject: [PATCH 0744/2916] feat: Implement completion for target names --- cmd/kluctl/commands/cobra_utils.go | 5 ++++ cmd/kluctl/commands/completion.go | 41 ++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 cmd/kluctl/commands/completion.go diff --git a/cmd/kluctl/commands/cobra_utils.go b/cmd/kluctl/commands/cobra_utils.go index 083e07693..a339e2485 100644 --- a/cmd/kluctl/commands/cobra_utils.go +++ b/cmd/kluctl/commands/cobra_utils.go @@ -81,6 +81,11 @@ func (c *rootCommand) buildCobraCmd(parent *commandAndGroups, cmdStruct interfac return nil, err } + err = RegisterFlagCompletionFuncs(cmdStruct, cg.cmd) + if err != nil { + return nil, err + } + cg.cmd.SetHelpFunc(func(command *cobra.Command, i []string) { c.helpFunc(cg) }) diff --git a/cmd/kluctl/commands/completion.go b/cmd/kluctl/commands/completion.go new file mode 100644 index 000000000..971d33293 --- /dev/null +++ b/cmd/kluctl/commands/completion.go @@ -0,0 +1,41 @@ +package commands + +import ( + "github.com/kluctl/kluctl/v2/cmd/kluctl/args" + "github.com/kluctl/kluctl/v2/pkg/kluctl_project" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "reflect" + "time" +) + +func RegisterFlagCompletionFuncs(cmd interface{}, ccmd *cobra.Command) error { + v := reflect.ValueOf(cmd).Elem() + projectFlags := v.FieldByName("ProjectFlags") + targetFlags := v.FieldByName("TargetFlags") + + if projectFlags.IsValid() && targetFlags.IsValid() { + _ = ccmd.RegisterFlagCompletionFunc("target", buildTargetCompletionFunc(projectFlags.Addr().Interface().(*args.ProjectFlags))) + } + + return nil +} + +func buildTargetCompletionFunc(projectArgs *args.ProjectFlags) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + var ret []string + // let's not update git caches too often + projectArgs.GitCacheUpdateInterval = time.Second * 60 + err := withKluctlProjectFromArgs(*projectArgs, func(p *kluctl_project.KluctlProjectContext) error { + for _, t := range p.DynamicTargets { + ret = append(ret, t.Target.Name) + } + return nil + }) + if err != nil { + log.Error(err) + return nil, cobra.ShellCompDirectiveError + } + return ret, cobra.ShellCompDirectiveDefault + } +} From 6fbd652f95383fde0d48fb4a4a0eb43dd87e2d1f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 3 May 2022 11:45:14 +0200 Subject: [PATCH 0745/2916] build: Install shell completion via homebrew packages --- .goreleaser.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 20c4e885e..ea3c4cf97 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -116,3 +116,12 @@ brews: bin.install "kluctl" test: | system "#{bin}/kluctl version" + + bash_output = Utils.safe_popen_read(bin/"kluctl", "completion", "bash") + (bash_completion/"kluctl").write bash_output + + zsh_output = Utils.safe_popen_read(bin/"kluctl", "completion", "zsh") + (zsh_completion/"_kluctl").write zsh_output + + fish_output = Utils.safe_popen_read(bin/"kluctl", "completion", "fish") + (fish_completion/"kluctl.fish").write fish_output From 9cca84ea32492b35f6e22b35bb466d0270aaf147 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 3 May 2022 11:56:56 +0200 Subject: [PATCH 0746/2916] feat: Implement autocompletion for file/dir based args --- cmd/kluctl/args/images.go | 2 +- cmd/kluctl/args/project.go | 6 +++--- cmd/kluctl/commands/cobra_utils.go | 14 +++++++++++++- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/cmd/kluctl/args/images.go b/cmd/kluctl/args/images.go index 3cdcdb9f7..70c681705 100644 --- a/cmd/kluctl/args/images.go +++ b/cmd/kluctl/args/images.go @@ -9,7 +9,7 @@ import ( type ImageFlags struct { FixedImage []string `group:"images" short:"F" help:"Pin an image to a given version. Expects '--fixed-image=image<:namespace:deployment:container>=result'"` - FixedImagesFile existingFileType `group:"images" help:"Use .yml file to pin image versions. See output of list-images sub-command or read the documentation for details about the output format"` + FixedImagesFile existingFileType `group:"images" help:"Use .yml file to pin image versions. See output of list-images sub-command or read the documentation for details about the output format" exts:"yml,yaml"` UpdateImages bool `group:"images" short:"u" help:"This causes kluctl to prefer the latest image found in registries, based on the 'latest_image' filters provided to 'images.get_image(...)' calls. Use this flag if you want to update to the latest versions/tags of all images. '-u' takes precedence over '--fixed-image/--fixed-images-file', meaning that the latest images are used even if an older image is given via fixed images."` } diff --git a/cmd/kluctl/args/project.go b/cmd/kluctl/args/project.go index 67da88a05..81ea403fa 100644 --- a/cmd/kluctl/args/project.go +++ b/cmd/kluctl/args/project.go @@ -6,12 +6,12 @@ type ProjectFlags struct { ProjectUrl string `group:"project" short:"p" help:"Git url of the kluctl project. If not specified, the current directory will be used instead of a remote Git project"` ProjectRef string `group:"project" short:"b" help:"Git ref of the kluctl project. Only used when --project-url was given."` - ProjectConfig existingFileType `group:"project" short:"c" help:"Location of the .kluctl.yml config file. Defaults to $PROJECT/.kluctl.yml"` + ProjectConfig existingFileType `group:"project" short:"c" help:"Location of the .kluctl.yml config file. Defaults to $PROJECT/.kluctl.yml" exts:"yml,yaml"` LocalClusters existingDirType `group:"project" help:"Local clusters directory. Overrides the project from .kluctl.yml"` LocalDeployment existingDirType `group:"project" help:"Local deployment directory. Overrides the project from .kluctl.yml"` LocalSealedSecrets existingDirType `group:"project" help:"Local sealed-secrets directory. Overrides the project from .kluctl.yml" ` - FromArchive existingPathType `group:"project" help:"Load project (.kluctl.yml, cluster, ...) from archive. Given path can either be an archive file or a directory with the extracted contents."` - FromArchiveMetadata existingFileType `group:"project" help:"Specify where to load metadata (targets, ...) from. If not specified, metadata is assumed to be part of the archive." ` + FromArchive existingPathType `group:"project" help:"Load project (.kluctl.yml, cluster, ...) from archive. Given path can either be an archive file or a directory with the extracted contents." exts:"tar.gz,tgz"` + FromArchiveMetadata existingFileType `group:"project" help:"Specify where to load metadata (targets, ...) from. If not specified, metadata is assumed to be part of the archive." exts:"yml,yaml"` OutputMetadata pathType `group:"project" help:"Specify the output path for the project metadata to be written to. When used with the 'archive' command, it will also cause the archive to not include the metadata.yaml file."` Cluster string `group:"project" help:"Specify/Override cluster"` diff --git a/cmd/kluctl/commands/cobra_utils.go b/cmd/kluctl/commands/cobra_utils.go index a339e2485..17ed89eee 100644 --- a/cmd/kluctl/commands/cobra_utils.go +++ b/cmd/kluctl/commands/cobra_utils.go @@ -151,7 +151,19 @@ func (c *rootCommand) buildCobraArg(cg *commandAndGroups, f reflect.StructField, switch v2.(type) { case pflag.Value: - cg.cmd.PersistentFlags().VarPF(v2.(pflag.Value), name, shortFlag, help) + v3 := v2.(pflag.Value) + cg.cmd.PersistentFlags().VarP(v3, name, shortFlag, help) + switch v3.Type() { + case "existingfile": + exts := strings.Split(f.Tag.Get("exts"), ",") + _ = cg.cmd.MarkPersistentFlagFilename(name, exts...) + case "existingdir": + _ = cg.cmd.MarkPersistentFlagDirname(name) + case "existingpath": + exts := strings.Split(f.Tag.Get("exts"), ",") + _ = cg.cmd.MarkPersistentFlagFilename(name, exts...) + _ = cg.cmd.MarkPersistentFlagDirname(name) + } case *string: cg.cmd.PersistentFlags().StringVarP(v2.(*string), name, shortFlag, defaultValue, help) case *[]string: From 7e5136fc6f6b5e9cc6dd9b1e60df4d5405580931 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 3 May 2022 12:23:45 +0200 Subject: [PATCH 0747/2916] feat: Implement autocompletion for --cluster --- cmd/kluctl/commands/completion.go | 48 +++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/cmd/kluctl/commands/completion.go b/cmd/kluctl/commands/completion.go index 971d33293..80bffe0c5 100644 --- a/cmd/kluctl/commands/completion.go +++ b/cmd/kluctl/commands/completion.go @@ -3,8 +3,12 @@ package commands import ( "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/kluctl_project" + "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/yaml" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "os" + "path/filepath" "reflect" "time" ) @@ -14,6 +18,10 @@ func RegisterFlagCompletionFuncs(cmd interface{}, ccmd *cobra.Command) error { projectFlags := v.FieldByName("ProjectFlags") targetFlags := v.FieldByName("TargetFlags") + if projectFlags.IsValid() { + _ = ccmd.RegisterFlagCompletionFunc("cluster", buildClusterCompletionFunc(projectFlags.Addr().Interface().(*args.ProjectFlags))) + } + if projectFlags.IsValid() && targetFlags.IsValid() { _ = ccmd.RegisterFlagCompletionFunc("target", buildTargetCompletionFunc(projectFlags.Addr().Interface().(*args.ProjectFlags))) } @@ -21,12 +29,46 @@ func RegisterFlagCompletionFuncs(cmd interface{}, ccmd *cobra.Command) error { return nil } +func withProjectForCompletion(projectArgs *args.ProjectFlags, cb func(p *kluctl_project.KluctlProjectContext) error) error { + // let's not update git caches too often + projectArgs.GitCacheUpdateInterval = time.Second * 60 + return withKluctlProjectFromArgs(*projectArgs, func(p *kluctl_project.KluctlProjectContext) error { + return cb(p) + }) +} + +func buildClusterCompletionFunc(projectArgs *args.ProjectFlags) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + var ret []string + err := withProjectForCompletion(projectArgs, func(p *kluctl_project.KluctlProjectContext) error { + dents, err := os.ReadDir(p.ClustersDir) + if err != nil { + return err + } + for _, de := range dents { + var config types.ClusterConfig + err = yaml.ReadYamlFile(filepath.Join(p.ClustersDir, de.Name()), &config) + if err != nil { + continue + } + if config.Cluster.Name != "" { + ret = append(ret, config.Cluster.Name) + } + } + return nil + }) + if err != nil { + log.Error(err) + return nil, cobra.ShellCompDirectiveError + } + return ret, cobra.ShellCompDirectiveDefault + } +} + func buildTargetCompletionFunc(projectArgs *args.ProjectFlags) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { var ret []string - // let's not update git caches too often - projectArgs.GitCacheUpdateInterval = time.Second * 60 - err := withKluctlProjectFromArgs(*projectArgs, func(p *kluctl_project.KluctlProjectContext) error { + err := withProjectForCompletion(projectArgs, func(p *kluctl_project.KluctlProjectContext) error { for _, t := range p.DynamicTargets { ret = append(ret, t.Target.Name) } From 99f76680c55ecaffd7efb7c801cfa533e3c2fef7 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 3 May 2022 14:20:44 +0200 Subject: [PATCH 0748/2916] feat: Implement autocompletion for inclusion flags --- cmd/kluctl/commands/cmd_seal.go | 3 +- cmd/kluctl/commands/completion.go | 91 +++++++++++++++++++++++++++- cmd/kluctl/commands/utils.go | 16 +++-- pkg/deployment/deployment_item.go | 31 +++++----- pkg/deployment/deployment_project.go | 8 +-- pkg/jinja2/vars.go | 4 ++ pkg/kluctl_project/target_context.go | 9 ++- pkg/utils/ordered_map.go | 12 ++++ 8 files changed, 140 insertions(+), 34 deletions(-) diff --git a/cmd/kluctl/commands/cmd_seal.go b/cmd/kluctl/commands/cmd_seal.go index 185c07f44..ce918cf90 100644 --- a/cmd/kluctl/commands/cmd_seal.go +++ b/cmd/kluctl/commands/cmd_seal.go @@ -67,11 +67,12 @@ func (cmd *sealCmd) runCmdSealForTarget(p *kluctl_project.KluctlProjectContext, ptArgs := projectTargetCommandArgs{ projectFlags: cmd.ProjectFlags, targetFlags: cmd.TargetFlags, + forSeal: true, } ptArgs.targetFlags.Target = targetName // pass forSeal=True so that .sealme files are rendered as well - return withProjectTargetCommandContext(ptArgs, p, true, func(ctx *commandCtx) error { + return withProjectTargetCommandContext(ptArgs, p, func(ctx *commandCtx) error { err := loadSecrets(ctx, ctx.targetCtx.Target, secretsLoader) if err != nil { return err diff --git a/cmd/kluctl/commands/completion.go b/cmd/kluctl/commands/completion.go index 80bffe0c5..fcc341c2c 100644 --- a/cmd/kluctl/commands/completion.go +++ b/cmd/kluctl/commands/completion.go @@ -4,19 +4,22 @@ import ( "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/kluctl_project" "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/yaml" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "os" "path/filepath" "reflect" + "sync" "time" ) -func RegisterFlagCompletionFuncs(cmd interface{}, ccmd *cobra.Command) error { - v := reflect.ValueOf(cmd).Elem() +func RegisterFlagCompletionFuncs(cmdStruct interface{}, ccmd *cobra.Command) error { + v := reflect.ValueOf(cmdStruct).Elem() projectFlags := v.FieldByName("ProjectFlags") targetFlags := v.FieldByName("TargetFlags") + inclusionFlags := v.FieldByName("InclusionFlags") if projectFlags.IsValid() { _ = ccmd.RegisterFlagCompletionFunc("cluster", buildClusterCompletionFunc(projectFlags.Addr().Interface().(*args.ProjectFlags))) @@ -26,6 +29,15 @@ func RegisterFlagCompletionFuncs(cmd interface{}, ccmd *cobra.Command) error { _ = ccmd.RegisterFlagCompletionFunc("target", buildTargetCompletionFunc(projectFlags.Addr().Interface().(*args.ProjectFlags))) } + if projectFlags.IsValid() && inclusionFlags.IsValid() { + tagsFunc := buildInclusionCompletionFunc(cmdStruct, false) + dirsFunc := buildInclusionCompletionFunc(cmdStruct, true) + _ = ccmd.RegisterFlagCompletionFunc("include-tag", tagsFunc) + _ = ccmd.RegisterFlagCompletionFunc("exclude-tag", tagsFunc) + _ = ccmd.RegisterFlagCompletionFunc("include-deployment-dir", dirsFunc) + _ = ccmd.RegisterFlagCompletionFunc("exclude-deployment-dir", dirsFunc) + } + return nil } @@ -81,3 +93,78 @@ func buildTargetCompletionFunc(projectArgs *args.ProjectFlags) func(cmd *cobra.C return ret, cobra.ShellCompDirectiveDefault } } + +func buildAutocompleteProjectTargetCommandArgs(cmdStruct interface{}) projectTargetCommandArgs { + ptArgs := projectTargetCommandArgs{} + + cmdV := reflect.ValueOf(cmdStruct).Elem() + if cmdV.FieldByName("ProjectFlags").IsValid() { + ptArgs.projectFlags = cmdV.FieldByName("ProjectFlags").Interface().(args.ProjectFlags) + } + if cmdV.FieldByName("TargetFlags").IsValid() { + ptArgs.targetFlags = cmdV.FieldByName("TargetFlags").Interface().(args.TargetFlags) + } + if cmdV.FieldByName("ArgsFlags").IsValid() { + ptArgs.argsFlags = cmdV.FieldByName("ArgsFlags").Interface().(args.ArgsFlags) + } + if cmdV.FieldByName("ImageFlags").IsValid() { + ptArgs.imageFlags = cmdV.FieldByName("ImageFlags").Interface().(args.ImageFlags) + } + if cmdV.FieldByName("InclusionFlags").IsValid() { + ptArgs.inclusionFlags = cmdV.FieldByName("InclusionFlags").Interface().(args.InclusionFlags) + } + + ptArgs.forCompletion = true + return ptArgs +} + +func buildInclusionCompletionFunc(cmdStruct interface{}, forDirs bool) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return func(cmd *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) { + ptArgs := buildAutocompleteProjectTargetCommandArgs(cmdStruct) + + var tags utils.OrderedMap + var deploymentItemDirs utils.OrderedMap + var mutex sync.Mutex + + err := withProjectForCompletion(&ptArgs.projectFlags, func(p *kluctl_project.KluctlProjectContext) error { + var targets []string + if ptArgs.targetFlags.Target == "" { + for _, t := range p.DynamicTargets { + targets = append(targets, t.Target.Name) + } + } else { + targets = append(targets, ptArgs.targetFlags.Target) + } + + var wg sync.WaitGroup + for _, t := range targets { + ptArgs := ptArgs + ptArgs.targetFlags.Target = t + wg.Add(1) + go func() { + _ = withProjectTargetCommandContext(ptArgs, p, func(ctx *commandCtx) error { + mutex.Lock() + defer mutex.Unlock() + for _, di := range ctx.targetCtx.DeploymentCollection.Deployments { + tags.Merge(di.Tags) + deploymentItemDirs.Set(di.RelToRootItemDir, true) + } + return nil + }) + wg.Done() + }() + } + wg.Wait() + return nil + }) + if err != nil { + log.Error(err) + return nil, cobra.ShellCompDirectiveError + } + if forDirs { + return deploymentItemDirs.ListKeys(), cobra.ShellCompDirectiveDefault + } else { + return tags.ListKeys(), cobra.ShellCompDirectiveDefault + } + } +} diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 39516c6f8..a3ba20d4c 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -88,6 +88,9 @@ type projectTargetCommandArgs struct { inclusionFlags args.InclusionFlags dryRunArgs *args.DryRunFlags renderOutputDirFlags args.RenderOutputDirFlags + + forSeal bool + forCompletion bool } type commandCtx struct { @@ -97,11 +100,11 @@ type commandCtx struct { func withProjectCommandContext(args projectTargetCommandArgs, cb func(ctx *commandCtx) error) error { return withKluctlProjectFromArgs(args.projectFlags, func(p *kluctl_project.KluctlProjectContext) error { - return withProjectTargetCommandContext(args, p, false, cb) + return withProjectTargetCommandContext(args, p, cb) }) } -func withProjectTargetCommandContext(args projectTargetCommandArgs, p *kluctl_project.KluctlProjectContext, forSeal bool, cb func(ctx *commandCtx) error) error { +func withProjectTargetCommandContext(args projectTargetCommandArgs, p *kluctl_project.KluctlProjectContext, cb func(ctx *commandCtx) error) error { rh := registries.NewRegistryHelper() err := rh.ParseAuthEntriesFromEnv() if err != nil { @@ -140,20 +143,23 @@ func withProjectTargetCommandContext(args projectTargetCommandArgs, p *kluctl_pr } clientConfigGetter := func(context string) (*rest.Config, error) { + if args.forCompletion { + return nil, nil + } configLoadingRules := clientcmd.NewDefaultClientConfigLoadingRules() configOverrides := &clientcmd.ConfigOverrides{CurrentContext: context} return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(configLoadingRules, configOverrides).ClientConfig() } ctx, err := p.NewTargetContext(clientConfigGetter, args.targetFlags.Target, args.projectFlags.Cluster, - args.dryRunArgs == nil || args.dryRunArgs.DryRun, - optionArgs, forSeal, images, inclusion, + args.dryRunArgs == nil || args.dryRunArgs.DryRun || args.forCompletion, + optionArgs, args.forSeal, images, inclusion, renderOutputDir) if err != nil { return err } - if !forSeal { + if !args.forSeal && !args.forCompletion { err = ctx.DeploymentCollection.Prepare(ctx.K) if err != nil { return err diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index e723c480e..57e9eea52 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -33,9 +33,10 @@ type DeploymentItem struct { WaitReadiness bool Objects []*uo.UnstructuredObject + Tags *utils.OrderedMap relProjectDir string - relToRootItemDir string + RelToRootItemDir string RelToProjectItemDir string relRenderedDir string renderedDir string @@ -53,6 +54,10 @@ func NewDeploymentItem(project *DeploymentProject, collection *DeploymentCollect var err error + // collect tags + di.Tags = di.Project.getTags() + di.Tags.SetMultiple(di.Config.Tags, true) + rootProject := di.Project.getRootProject() di.relProjectDir, err = filepath.Rel(rootProject.dir, di.Project.dir) @@ -61,17 +66,17 @@ func NewDeploymentItem(project *DeploymentProject, collection *DeploymentCollect } if di.dir != nil { - di.relToRootItemDir, err = filepath.Rel(rootProject.dir, *di.dir) + di.RelToRootItemDir, err = filepath.Rel(rootProject.dir, *di.dir) if err != nil { return nil, err } - di.RelToProjectItemDir, err = filepath.Rel(di.relProjectDir, di.relToRootItemDir) + di.RelToProjectItemDir, err = filepath.Rel(di.relProjectDir, di.RelToRootItemDir) if err != nil { return nil, err } - di.relRenderedDir = di.relToRootItemDir + di.relRenderedDir = di.RelToRootItemDir if di.index != 0 { di.relRenderedDir = fmt.Sprintf("%s-%d", di.relRenderedDir, di.index) } @@ -85,7 +90,7 @@ func NewDeploymentItem(project *DeploymentProject, collection *DeploymentCollect func (di *DeploymentItem) getCommonLabels() map[string]string { l := di.Project.GetCommonLabels() i := 0 - for _, t := range di.getTags().ListKeys() { + for _, t := range di.Tags.ListKeys() { l[fmt.Sprintf("kluctl.io/tag-%d", i)] = t i += 1 } @@ -95,7 +100,7 @@ func (di *DeploymentItem) getCommonLabels() map[string]string { func (di *DeploymentItem) getCommonAnnotations() map[string]string { // TODO change it to kluctl.io/deployment_dir a := map[string]string{ - "kluctl.io/kustomize_dir": strings.ReplaceAll(di.relToRootItemDir, string(os.PathSeparator), "/"), + "kluctl.io/kustomize_dir": strings.ReplaceAll(di.RelToRootItemDir, string(os.PathSeparator), "/"), } if di.Config.SkipDeleteIfTags != nil && *di.Config.SkipDeleteIfTags { a["kluctl.io/skip-delete-if-tags"] = "true" @@ -239,21 +244,13 @@ func (di *DeploymentItem) resolveSealedSecrets(subdir string) error { return nil } -func (di *DeploymentItem) getTags() *utils.OrderedMap { - tags := di.Project.getTags() - for _, t := range di.Config.Tags { - tags.Set(t, true) - } - return tags -} - func (di *DeploymentItem) buildInclusionEntries() []utils.InclusionEntry { var values []utils.InclusionEntry - for _, t := range di.getTags().ListKeys() { + for _, t := range di.Tags.ListKeys() { values = append(values, utils.InclusionEntry{Type: "tag", Value: t}) } if di.dir != nil { - dir := strings.ReplaceAll(di.relToRootItemDir, string(os.PathSeparator), "/") + dir := strings.ReplaceAll(di.RelToRootItemDir, string(os.PathSeparator), "/") values = append(values, utils.InclusionEntry{Type: "deploymentItemDir", Value: dir}) } return values @@ -388,7 +385,7 @@ func (di *DeploymentItem) postprocessAndLoadObjects(k *k8s.K8sCluster, images *I o.SetK8sAnnotations(uo.CopyMergeStrMap(o.GetK8sAnnotations(), commonAnnotations)) // Resolve image placeholders - err = images.ResolvePlaceholders(k, o, di.relRenderedDir, di.getTags().ListKeys()) + err = images.ResolvePlaceholders(k, o, di.relRenderedDir, di.Tags.ListKeys()) if err != nil { return err } diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go index 8193ff13d..fe093ab20 100644 --- a/pkg/deployment/deployment_project.go +++ b/pkg/deployment/deployment_project.go @@ -284,13 +284,9 @@ func (p *DeploymentProject) getTags() *utils.OrderedMap { var tags utils.OrderedMap for _, e := range p.getParents() { if e.inc != nil { - for _, t := range e.inc.Tags { - tags.Set(t, true) - } - } - for _, t := range e.p.Config.Tags { - tags.Set(t, true) + tags.SetMultiple(e.inc.Tags, true) } + tags.SetMultiple(e.p.Config.Tags, true) } return &tags } diff --git a/pkg/jinja2/vars.go b/pkg/jinja2/vars.go index 1c42f4c38..75dc2af49 100644 --- a/pkg/jinja2/vars.go +++ b/pkg/jinja2/vars.go @@ -87,6 +87,10 @@ func (vc *VarsCtx) loadVarsFile(p string, searchDirs []string) error { } func (vc *VarsCtx) loadVarsFromK8sObject(k *k8s.K8sCluster, ref k8s2.ObjectRef, key string) error { + if k == nil { + return nil + } + o, _, err := k.GetSingleObject(ref) if err != nil { return err diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index 29b662338..745b55131 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -57,9 +57,12 @@ func (p *KluctlProjectContext) NewTargetContext(clientConfigGetter func(context return nil, err } - k, err := k8s.NewK8sCluster(clientConfig, dryRun) - if err != nil { - return nil, err + var k *k8s.K8sCluster + if clientConfig != nil { + k, err = k8s.NewK8sCluster(clientConfig, dryRun) + if err != nil { + return nil, err + } } varsCtx := jinja2.NewVarsCtx(p.J2) diff --git a/pkg/utils/ordered_map.go b/pkg/utils/ordered_map.go index ab845989e..b19de7aff 100644 --- a/pkg/utils/ordered_map.go +++ b/pkg/utils/ordered_map.go @@ -23,6 +23,12 @@ func (s *OrderedMap) Set(k string, v interface{}) bool { return true } +func (s *OrderedMap) SetMultiple(k []string, v interface{}) { + for _, x := range k { + s.Set(x, v) + } +} + func (s *OrderedMap) Has(v string) bool { _, ok := s.m[v] return ok @@ -51,3 +57,9 @@ func (s *OrderedMap) ListValues() []interface{} { } return l } + +func (s *OrderedMap) Merge(other *OrderedMap) { + for _, e := range other.l { + s.Set(e.k, e.v) + } +} From 15fa6405d70c1ac182d77d775326859ef99bb997 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 3 May 2022 15:50:26 +0200 Subject: [PATCH 0749/2916] fix: Fix crash when parsing --fixed-image --- cmd/kluctl/args/images.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/kluctl/args/images.go b/cmd/kluctl/args/images.go index 70c681705..f9eb511be 100644 --- a/cmd/kluctl/args/images.go +++ b/cmd/kluctl/args/images.go @@ -49,13 +49,13 @@ func buildFixedImageEntryFromArg(arg string) (*types.FixedImage, error) { } if len(s) >= 2 { - e.Namespace = &s[2] + e.Namespace = &s[1] } if len(s) >= 3 { - e.Deployment = &s[3] + e.Deployment = &s[2] } if len(s) >= 4 { - e.Container = &s[4] + e.Container = &s[3] } if len(s) >= 5 { return nil, fmt.Errorf("--fixed-image expects 'image<:namespace:deployment:container>=result'") From 37ee758723c0d207bfd81fb03cbd1db7c1dc73ef Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 3 May 2022 15:51:12 +0200 Subject: [PATCH 0750/2916] feat: Allow non-strict template rendering --- pkg/jinja2/jinja2.go | 10 +++++-- pkg/jinja2/jinja2_renderer.go | 4 ++- pkg/jinja2/python_src/jinja2_renderer.py | 34 +++++++++++++++++------- pkg/jinja2/python_src/main.py | 4 +-- 4 files changed, 38 insertions(+), 14 deletions(-) diff --git a/pkg/jinja2/jinja2.go b/pkg/jinja2/jinja2.go index bcdd6d4b3..fa04d3cd7 100644 --- a/pkg/jinja2/jinja2.go +++ b/pkg/jinja2/jinja2.go @@ -17,6 +17,7 @@ var paralellism = 4 type Jinja2 struct { pj chan *pythonJinja2Renderer + strict bool globCache map[string]interface{} mutex sync.Mutex } @@ -44,6 +45,7 @@ func NewJinja2() (*Jinja2, error) { j := &Jinja2{ pj: make(chan *pythonJinja2Renderer, paralellism), + strict: true, globCache: map[string]interface{}{}, } @@ -76,16 +78,20 @@ func (j *Jinja2) Close() { } } +func (j *Jinja2) SetStrict(strict bool) { + j.strict = strict +} + func (j *Jinja2) RenderStrings(jobs []*RenderJob, searchDirs []string, vars *uo.UnstructuredObject) error { pj := <-j.pj defer func() { j.pj <- pj }() - return pj.renderHelper(jobs, searchDirs, vars, true) + return pj.renderHelper(jobs, searchDirs, vars, true, j.strict) } func (j *Jinja2) RenderFiles(jobs []*RenderJob, searchDirs []string, vars *uo.UnstructuredObject) error { pj := <-j.pj defer func() { j.pj <- pj }() - return pj.renderHelper(jobs, searchDirs, vars, false) + return pj.renderHelper(jobs, searchDirs, vars, false, j.strict) } func (j *Jinja2) RenderString(template string, searchDirs []string, vars *uo.UnstructuredObject) (string, error) { diff --git a/pkg/jinja2/jinja2_renderer.go b/pkg/jinja2/jinja2_renderer.go index e3299137f..f52869396 100644 --- a/pkg/jinja2/jinja2_renderer.go +++ b/pkg/jinja2/jinja2_renderer.go @@ -108,6 +108,7 @@ type jinja2Args struct { Templates []string `json:"templates"` SearchDirs []string `json:"searchDirs"` Vars string `json:"vars"` + Strict bool `json:"strict"` } type jinja2Result struct { @@ -115,7 +116,7 @@ type jinja2Result struct { Error *string `json:"error,omitempty"` } -func (j *pythonJinja2Renderer) renderHelper(jobs []*RenderJob, searchDirs []string, vars *uo.UnstructuredObject, isString bool) error { +func (j *pythonJinja2Renderer) renderHelper(jobs []*RenderJob, searchDirs []string, vars *uo.UnstructuredObject, isString bool, strict bool) error { varsStr, err := json.Marshal(vars.Object) if err != nil { return err @@ -131,6 +132,7 @@ func (j *pythonJinja2Renderer) renderHelper(jobs []*RenderJob, searchDirs []stri } jargs.Vars = string(varsStr) jargs.SearchDirs = searchDirs + jargs.Strict = strict for _, job := range jobs { if ist, r := j.isMaybeTemplate(job.Template, searchDirs, isString); !ist { diff --git a/pkg/jinja2/python_src/jinja2_renderer.py b/pkg/jinja2/python_src/jinja2_renderer.py index b5ca3411e..735379917 100644 --- a/pkg/jinja2/python_src/jinja2_renderer.py +++ b/pkg/jinja2/python_src/jinja2_renderer.py @@ -1,7 +1,7 @@ import base64 import json -from jinja2 import StrictUndefined, FileSystemLoader +from jinja2 import StrictUndefined, FileSystemLoader, ChainableUndefined from dict_utils import merge_dict from jinja2_cache import KluctlBytecodeCache @@ -13,6 +13,21 @@ end_placeholder = "_end_get_imageXXXXX" +class NullUndefined(ChainableUndefined): + def _return_self(self, other): + return self + + __add__ = __radd__ = __sub__ = __rsub__ = _return_self + __mul__ = __rmul__ = __div__ = __rdiv__ = _return_self + __truediv__ = __rtruediv__ = _return_self + __floordiv__ = __rfloordiv__ = _return_self + __mod__ = __rmod__ = _return_self + __pos__ = __neg__ = _return_self + __call__ = __getitem__ = _return_self + __lt__ = __le__ = __gt__ = __ge__ = _return_self + __int__ = __float__ = __complex__ = _return_self + __pow__ = __rpow__ = _return_self + class Jinja2Renderer: def get_image_wrapper(self, image, latest_version=None): @@ -53,12 +68,13 @@ def regex(r): } return vars - def build_env(self, vars_str, search_dirs): + def build_env(self, vars_str, search_dirs, strict): vars = json.loads(vars_str) image_vars = self.build_images_vars() merge_dict(vars, image_vars, clone=False) - environment = KluctlJinja2Environment(loader=FileSystemLoader(search_dirs), undefined=StrictUndefined, + environment = KluctlJinja2Environment(loader=FileSystemLoader(search_dirs), + undefined=StrictUndefined if strict else NullUndefined, cache_size=10000, bytecode_cache=jinja2_cache, auto_reload=False) merge_dict(environment.globals, vars, clone=False) @@ -66,8 +82,8 @@ def build_env(self, vars_str, search_dirs): add_jinja2_filters(environment) return environment - def render_helper(self, templates, search_dirs, vars, is_string): - env = self.build_env(vars, search_dirs) + def render_helper(self, templates, search_dirs, vars, is_string, strict): + env = self.build_env(vars, search_dirs, strict) result = [] @@ -87,17 +103,17 @@ def render_helper(self, templates, search_dirs, vars, is_string): return result - def RenderStrings(self, templates, search_dirs, vars): + def RenderStrings(self, templates, search_dirs, vars, strict): try: - return self.render_helper(templates, search_dirs, vars, True) + return self.render_helper(templates, search_dirs, vars, True, strict) except Exception as e: return [{ "error": str(e) }] * len(templates) - def RenderFiles(self, templates, search_dirs, vars): + def RenderFiles(self, templates, search_dirs, vars, strict): try: - return self.render_helper(templates, search_dirs, vars, False) + return self.render_helper(templates, search_dirs, vars, False, strict) except Exception as e: return [{ "error": str(e) diff --git a/pkg/jinja2/python_src/main.py b/pkg/jinja2/python_src/main.py index c1d7e54be..4f35ec9b7 100644 --- a/pkg/jinja2/python_src/main.py +++ b/pkg/jinja2/python_src/main.py @@ -14,9 +14,9 @@ def main(): args = json.loads(args) if args["cmd"] == "render-strings": - result = r.RenderStrings(args["templates"], args["searchDirs"] or [], args["vars"]) + result = r.RenderStrings(args["templates"], args["searchDirs"] or [], args["vars"], args["strict"]) elif args["cmd"] == "render-files": - result = r.RenderFiles(args["templates"], args["searchDirs"] or [], args["vars"]) + result = r.RenderFiles(args["templates"], args["searchDirs"] or [], args["vars"], args["strict"]) elif args["cmd"] == "exit": break else: From cf96fef8afecc6c551ee788eba208d1fc80bb764 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 3 May 2022 16:01:15 +0200 Subject: [PATCH 0751/2916] fix: Allow offline rendering (images and k8s) --- cmd/kluctl/commands/utils.go | 2 +- pkg/deployment/helm_chart.go | 33 ++++++++++++++++++++------------- pkg/deployment/images.go | 8 +++++++- 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index a3ba20d4c..9ac8ac5f5 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -110,7 +110,7 @@ func withProjectTargetCommandContext(args projectTargetCommandArgs, p *kluctl_pr if err != nil { return fmt.Errorf("failed to parse registry auth from environment: %w", err) } - images, err := deployment.NewImages(rh, args.imageFlags.UpdateImages) + images, err := deployment.NewImages(rh, args.imageFlags.UpdateImages, args.forCompletion) if err != nil { return err } diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index 343c87b19..23a89200f 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -179,11 +179,13 @@ func (c *helmChart) doRender(k *k8s.K8sCluster) error { valuesPath := yaml.FixPathExt(filepath.Join(dir, "helm-values.yml")) outputPath := filepath.Join(dir, c.Config.Output) - gvs, err := k.GetAllGroupVersions() - if err != nil { - return err + var gvs []string + if k != nil { + gvs, err = k.GetAllGroupVersions() + if err != nil { + return err + } } - cfg, err := c.buildHelmConfig() if err != nil { return err @@ -194,9 +196,12 @@ func (c *helmChart) doRender(k *k8s.K8sCluster) error { ValueFiles: []string{valuesPath}, } - kubeVersion, err := chartutil.ParseKubeVersion(k.ServerVersion.String()) - if err != nil { - return err + var kubeVersion *chartutil.KubeVersion + if k != nil { + kubeVersion, err = chartutil.ParseKubeVersion(k.ServerVersion.String()) + if err != nil { + return err + } } namespace := "default" @@ -284,12 +289,14 @@ func (c *helmChart) doRender(k *k8s.K8sCluster) error { for _, o := range parsed { // "helm install" will deploy resources to the given namespace automatically, but "helm template" does not // add the necessary namespace in the rendered resources - err = k8s.UnwrapListItems(o, true, func(o *uo.UnstructuredObject) error { - k.FixNamespace(o, namespace) - return nil - }) - if err != nil { - return err + if k != nil { + err = k8s.UnwrapListItems(o, true, func(o *uo.UnstructuredObject) error { + k.FixNamespace(o, namespace) + return nil + }) + if err != nil { + return err + } } fixed = append(fixed, o) } diff --git a/pkg/deployment/images.go b/pkg/deployment/images.go index 076a211a6..38d663216 100644 --- a/pkg/deployment/images.go +++ b/pkg/deployment/images.go @@ -20,6 +20,7 @@ import ( type Images struct { rh *registries.RegistryHelper updateImages bool + offline bool fixedImages []types.FixedImage seenImages []types.FixedImage mutex sync.Mutex @@ -27,10 +28,11 @@ type Images struct { registryCache utils.ThreadSafeMultiCache } -func NewImages(rh *registries.RegistryHelper, updateImages bool) (*Images, error) { +func NewImages(rh *registries.RegistryHelper, updateImages bool, offline bool) (*Images, error) { return &Images{ rh: rh, updateImages: updateImages, + offline: offline, }, nil } @@ -74,6 +76,10 @@ func (images *Images) GetFixedImage(image string, namespace string, deployment s } func (images *Images) GetLatestImageFromRegistry(image string, latestVersion string) (*string, error) { + if images.offline { + return nil, nil + } + ret, err := images.registryCache.Get(image, "tag", func() (interface{}, error) { return images.rh.ListImageTags(image) }) From 54be2a55275e848dcb0c83941d5f08af9383945e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 3 May 2022 16:01:37 +0200 Subject: [PATCH 0752/2916] f strict --- cmd/kluctl/commands/cmd_archive.go | 2 +- cmd/kluctl/commands/cmd_list_targets.go | 2 +- cmd/kluctl/commands/cmd_seal.go | 2 +- cmd/kluctl/commands/utils.go | 6 ++++-- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/cmd/kluctl/commands/cmd_archive.go b/cmd/kluctl/commands/cmd_archive.go index bcbeb9f42..5c5325613 100644 --- a/cmd/kluctl/commands/cmd_archive.go +++ b/cmd/kluctl/commands/cmd_archive.go @@ -16,7 +16,7 @@ func (cmd *archiveCmd) Help() string { } func (cmd *archiveCmd) Run() error { - return withKluctlProjectFromArgs(cmd.ProjectFlags, func(p *kluctl_project.KluctlProjectContext) error { + return withKluctlProjectFromArgs(cmd.ProjectFlags, true, func(p *kluctl_project.KluctlProjectContext) error { return p.CreateTGZArchive(cmd.OutputArchive, cmd.ProjectFlags.OutputMetadata == "") }) } diff --git a/cmd/kluctl/commands/cmd_list_targets.go b/cmd/kluctl/commands/cmd_list_targets.go index 29f9036f3..ba4493622 100644 --- a/cmd/kluctl/commands/cmd_list_targets.go +++ b/cmd/kluctl/commands/cmd_list_targets.go @@ -16,7 +16,7 @@ func (cmd *listTargetsCmd) Help() string { } func (cmd *listTargetsCmd) Run() error { - return withKluctlProjectFromArgs(cmd.ProjectFlags, func(p *kluctl_project.KluctlProjectContext) error { + return withKluctlProjectFromArgs(cmd.ProjectFlags, true, func(p *kluctl_project.KluctlProjectContext) error { var result []*types.Target for _, t := range p.DynamicTargets { result = append(result, t.Target) diff --git a/cmd/kluctl/commands/cmd_seal.go b/cmd/kluctl/commands/cmd_seal.go index ce918cf90..d840403d0 100644 --- a/cmd/kluctl/commands/cmd_seal.go +++ b/cmd/kluctl/commands/cmd_seal.go @@ -119,7 +119,7 @@ func (cmd *sealCmd) runCmdSealForTarget(p *kluctl_project.KluctlProjectContext, } func (cmd *sealCmd) Run() error { - return withKluctlProjectFromArgs(cmd.ProjectFlags, func(p *kluctl_project.KluctlProjectContext) error { + return withKluctlProjectFromArgs(cmd.ProjectFlags, true, func(p *kluctl_project.KluctlProjectContext) error { hadError := false secretsLoader := seal.NewSecretsLoader(p, cmd.SecretsDir) diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 9ac8ac5f5..3d0d860de 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -19,7 +19,7 @@ import ( "time" ) -func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, cb func(p *kluctl_project.KluctlProjectContext) error) error { +func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, strictTemplates bool, cb func(p *kluctl_project.KluctlProjectContext) error) error { var url *git_url.GitUrl if projectFlags.ProjectUrl != "" { var err error @@ -41,6 +41,8 @@ func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, cb func(p *kluctl } defer j2.Close() + j2.SetStrict(strictTemplates) + cwd, err := os.Getwd() if err != nil { return err @@ -99,7 +101,7 @@ type commandCtx struct { } func withProjectCommandContext(args projectTargetCommandArgs, cb func(ctx *commandCtx) error) error { - return withKluctlProjectFromArgs(args.projectFlags, func(p *kluctl_project.KluctlProjectContext) error { + return withKluctlProjectFromArgs(args.projectFlags, true, func(p *kluctl_project.KluctlProjectContext) error { return withProjectTargetCommandContext(args, p, cb) }) } From fd49ddb0545a9d4ac6271191fb06bd2c3c438fc2 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 3 May 2022 16:01:58 +0200 Subject: [PATCH 0753/2916] fix: Continue on errors in postprocessAndLoadObjects --- pkg/deployment/deployment_item.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 57e9eea52..336dff69c 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -373,12 +373,15 @@ func (di *DeploymentItem) postprocessAndLoadObjects(k *k8s.K8sCluster, images *I di.Objects = append(di.Objects, uo.FromMap(m)) } + var errList []error for _, o := range di.Objects { commonLabels := di.getCommonLabels() commonAnnotations := di.getCommonAnnotations() - err = k8s.UnwrapListItems(o, true, func(o *uo.UnstructuredObject) error { - k.FixNamespace(o, "default") + _ = k8s.UnwrapListItems(o, true, func(o *uo.UnstructuredObject) error { + if k != nil { + k.FixNamespace(o, "default") + } // Set common labels/annotations o.SetK8sLabels(uo.CopyMergeStrMap(o.GetK8sLabels(), commonLabels)) @@ -387,13 +390,14 @@ func (di *DeploymentItem) postprocessAndLoadObjects(k *k8s.K8sCluster, images *I // Resolve image placeholders err = images.ResolvePlaceholders(k, o, di.relRenderedDir, di.Tags.ListKeys()) if err != nil { - return err + errList = append(errList, err) } return nil }) - if err != nil { - return err - } + } + + if len(errList) != 0 { + return utils.NewErrorListOrNil(errList) } // Need to write it back to disk in case it is needed externally From bdb95ddc6ee02393baa6de6b134f0e59e9d975ae Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 3 May 2022 16:02:24 +0200 Subject: [PATCH 0754/2916] fix: Record seenImages even if resolving failed --- pkg/deployment/images.go | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/pkg/deployment/images.go b/pkg/deployment/images.go index 38d663216..1becba949 100644 --- a/pkg/deployment/images.go +++ b/pkg/deployment/images.go @@ -239,23 +239,23 @@ func (images *Images) resolveImage(ph *placeHolder, ref k8s2.ObjectRef, deployme result = fixed } + si := types.FixedImage{ + Image: ph.Image, + DeployedImage: deployed, + RegistryImage: registry, + Namespace: &ref.Namespace, + Object: &ref, + Deployment: &deployment, + Container: &ph.Container, + VersionFilter: &ph.LatestVersion, + DeployTags: tags, + DeploymentDir: &deploymentDir, + } if result != nil { - si := types.FixedImage{ - Image: ph.Image, - ResultImage: *result, - DeployedImage: deployed, - RegistryImage: registry, - Namespace: &ref.Namespace, - Object: &ref, - Deployment: &deployment, - Container: &container, - VersionFilter: &ph.LatestVersion, - DeployTags: tags, - DeploymentDir: &deploymentDir, - } - images.mutex.Lock() - images.seenImages = append(images.seenImages, si) - images.mutex.Unlock() + si.ResultImage = *result } + images.mutex.Lock() + images.seenImages = append(images.seenImages, si) + images.mutex.Unlock() return result, nil } From 286fa24d032bfd3b8ef44b80b67491f3b5672ec5 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 3 May 2022 16:02:47 +0200 Subject: [PATCH 0755/2916] refactor: Split ResolvePlaceholders into ResolvePlaceholders and FindPlaceholders --- pkg/deployment/images.go | 105 +++++++++++++++++++------------- pkg/utils/uo/object_iterator.go | 6 ++ 2 files changed, 70 insertions(+), 41 deletions(-) diff --git a/pkg/deployment/images.go b/pkg/deployment/images.go index 1becba949..517087561 100644 --- a/pkg/deployment/images.go +++ b/pkg/deployment/images.go @@ -114,8 +114,12 @@ type placeHolder struct { Image string `yaml:"image"` LatestVersion string `yaml:"latestVersion"` - startOffset int - endOffset int + Container string + + FieldPath []interface{} + FieldValue string + StartOffset int + EndOffset int } func (images *Images) parsePlaceholder(s string, offset int) (*placeHolder, error) { @@ -137,8 +141,9 @@ func (images *Images) parsePlaceholder(s string, offset int) (*placeHolder, erro if err != nil { return nil, err } - ph.startOffset = start - ph.endOffset = end + len(endPlaceholder) + ph.FieldValue = s + ph.StartOffset = start + ph.EndOffset = end + len(endPlaceholder) return &ph, nil } @@ -155,19 +160,15 @@ func (images *Images) extractContainerName(parent interface{}) string { return "" } -func (images *Images) ResolvePlaceholders(k *k8s.K8sCluster, o *uo.UnstructuredObject, deploymentDir string, tags []string) error { - ref := o.GetK8sRef() - deployment := fmt.Sprintf("%s/%s", ref.GVK.Kind, ref.Name) - - var remoteObject *uo.UnstructuredObject - triedRemoteObject := false +func (images *Images) FindPlaceholders(o *uo.UnstructuredObject) ([]placeHolder, error) { + var ret []placeHolder err := uo.NewObjectIterator(o.Object).IterateLeafs(func(it *uo.ObjectIterator) error { s, ok := it.Value().(string) if !ok { return nil } - newS := "" + container := images.extractContainerName(it.Parent()) offset := 0 @@ -177,54 +178,76 @@ func (images *Images) ResolvePlaceholders(k *k8s.K8sCluster, o *uo.UnstructuredO return err } if ph == nil { - newS += s[offset:] break - } else { - newS += s[offset:ph.startOffset] } + ph.FieldPath = it.KeyPathCopy() + ph.Container = container + ret = append(ret, *ph) + offset = ph.EndOffset + } + return nil + }) + if err != nil { + return nil, err + } + return ret, nil +} + +func (images *Images) ResolvePlaceholders(k *k8s.K8sCluster, o *uo.UnstructuredObject, deploymentDir string, tags []string) error { + placeholders, err := images.FindPlaceholders(o) + if err != nil { + return err + } - if !triedRemoteObject { - triedRemoteObject = true + ref := o.GetK8sRef() + deployment := fmt.Sprintf("%s/%s", ref.GVK.Kind, ref.Name) + + var remoteObject *uo.UnstructuredObject + triedRemoteObject := false + + // iterate backwards so that replacements are easy + for i := len(placeholders) - 1; i >= 0; i-- { + ph := placeholders[i] + + if !triedRemoteObject { + triedRemoteObject = true + if k != nil { remoteObject, _, err = k.GetSingleObject(o.GetK8sRef()) if err != nil && !errors.IsNotFound(err) { return err } } + } - var deployed *string - if remoteObject != nil && ph.startOffset == 0 && ph.endOffset == len(s) { - x, found, _ := remoteObject.GetNestedField(it.KeyPath()...) - if found { - if y, ok := x.(string); ok { - deployed = &y - } + var deployed *string + if remoteObject != nil && ph.StartOffset == 0 && ph.EndOffset == len(ph.FieldValue) { + x, found, _ := remoteObject.GetNestedField(ph.FieldPath...) + if found { + if y, ok := x.(string); ok { + deployed = &y } } + } - resultImage, err := images.resolveImage(ph, ref, deployment, container, deployed, deploymentDir, tags) - if err != nil { - return err - } - if resultImage == nil { - return fmt.Errorf("failed to find image for %s and latest version %s", ph.Image, ph.LatestVersion) - } - newS += *resultImage + resultImage, err := images.resolveImage(ph, ref, deployment, deployed, deploymentDir, tags) + if err != nil { + return err + } + if resultImage == nil { + return fmt.Errorf("failed to find image for %s and latest version %s", ph.Image, ph.LatestVersion) + } - offset = ph.endOffset - if offset >= len(s) { - break - } + ph.FieldValue = ph.FieldValue[:ph.StartOffset] + *resultImage + ph.FieldValue[ph.EndOffset:] + err = o.SetNestedField(ph.FieldValue, ph.FieldPath...) + if err != nil { + return err } - return it.SetValue(newS) - }) - if err != nil { - return err } return nil } -func (images *Images) resolveImage(ph *placeHolder, ref k8s2.ObjectRef, deployment string, container string, deployed *string, deploymentDir string, tags []string) (*string, error) { - fixed := images.GetFixedImage(ph.Image, ref.Namespace, deployment, container) +func (images *Images) resolveImage(ph placeHolder, ref k8s2.ObjectRef, deployment string, deployed *string, deploymentDir string, tags []string) (*string, error) { + fixed := images.GetFixedImage(ph.Image, ref.Namespace, deployment, ph.Container) registry, err := images.GetLatestImageFromRegistry(ph.Image, ph.LatestVersion) if err != nil { diff --git a/pkg/utils/uo/object_iterator.go b/pkg/utils/uo/object_iterator.go index 7c127af98..be922ee93 100644 --- a/pkg/utils/uo/object_iterator.go +++ b/pkg/utils/uo/object_iterator.go @@ -21,6 +21,12 @@ func (it *ObjectIterator) KeyPath() []interface{} { return it.keys } +func (it *ObjectIterator) KeyPathCopy() []interface{} { + ret := make([]interface{}, len(it.keys)) + copy(ret, it.keys) + return ret +} + func (it *ObjectIterator) Key() interface{} { if len(it.keys) == 0 { return nil From 06d1cf2e881b3d97ce9e1c75b0cf5e1937bf70a9 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 3 May 2022 16:03:03 +0200 Subject: [PATCH 0756/2916] feat: Implement --fixed-image completion --- cmd/kluctl/commands/completion.go | 72 ++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/cmd/kluctl/commands/completion.go b/cmd/kluctl/commands/completion.go index fcc341c2c..48c5f8afb 100644 --- a/cmd/kluctl/commands/completion.go +++ b/cmd/kluctl/commands/completion.go @@ -11,6 +11,7 @@ import ( "os" "path/filepath" "reflect" + "strings" "sync" "time" ) @@ -20,6 +21,7 @@ func RegisterFlagCompletionFuncs(cmdStruct interface{}, ccmd *cobra.Command) err projectFlags := v.FieldByName("ProjectFlags") targetFlags := v.FieldByName("TargetFlags") inclusionFlags := v.FieldByName("InclusionFlags") + imageFlags := v.FieldByName("ImageFlags") if projectFlags.IsValid() { _ = ccmd.RegisterFlagCompletionFunc("cluster", buildClusterCompletionFunc(projectFlags.Addr().Interface().(*args.ProjectFlags))) @@ -38,13 +40,17 @@ func RegisterFlagCompletionFuncs(cmdStruct interface{}, ccmd *cobra.Command) err _ = ccmd.RegisterFlagCompletionFunc("exclude-deployment-dir", dirsFunc) } + if imageFlags.IsValid() { + _ = ccmd.RegisterFlagCompletionFunc("fixed-image", buildImagesCompletionFunc(cmdStruct)) + } + return nil } func withProjectForCompletion(projectArgs *args.ProjectFlags, cb func(p *kluctl_project.KluctlProjectContext) error) error { // let's not update git caches too often projectArgs.GitCacheUpdateInterval = time.Second * 60 - return withKluctlProjectFromArgs(*projectArgs, func(p *kluctl_project.KluctlProjectContext) error { + return withKluctlProjectFromArgs(*projectArgs, false, func(p *kluctl_project.KluctlProjectContext) error { return cb(p) }) } @@ -168,3 +174,67 @@ func buildInclusionCompletionFunc(cmdStruct interface{}, forDirs bool) func(cmd } } } + +func buildImagesCompletionFunc(cmdStruct interface{}) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return func(cmd *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) { + ptArgs := buildAutocompleteProjectTargetCommandArgs(cmdStruct) + + if strings.Index(toComplete, "=") != -1 { + return nil, cobra.ShellCompDirectiveDefault + } + + var images utils.OrderedMap + var mutex sync.Mutex + + err := withProjectForCompletion(&ptArgs.projectFlags, func(p *kluctl_project.KluctlProjectContext) error { + var targets []string + if ptArgs.targetFlags.Target == "" { + for _, t := range p.DynamicTargets { + targets = append(targets, t.Target.Name) + } + } else { + targets = append(targets, ptArgs.targetFlags.Target) + } + + var wg sync.WaitGroup + for _, t := range targets { + ptArgs := ptArgs + ptArgs.targetFlags.Target = t + wg.Add(1) + go func() { + _ = withProjectTargetCommandContext(ptArgs, p, func(ctx *commandCtx) error { + err := ctx.targetCtx.DeploymentCollection.Prepare(nil) + if err != nil { + log.Error(err) + } + + mutex.Lock() + defer mutex.Unlock() + for _, si := range ctx.images.SeenImages(false) { + str := si.Image + if si.Namespace != nil { + str += ":" + *si.Namespace + } + if si.Deployment != nil { + str += ":" + *si.Deployment + } + if si.Container != nil { + str += ":" + *si.Container + } + images.Set(str, true) + } + return nil + }) + wg.Done() + }() + } + wg.Wait() + return nil + }) + if err != nil { + log.Error(err) + return nil, cobra.ShellCompDirectiveError + } + return images.ListKeys(), cobra.ShellCompDirectiveNoSpace + } +} From c7ed62bf0aa7397b14249788170c3db45b0f8dc4 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 4 May 2022 09:05:04 +0200 Subject: [PATCH 0757/2916] fix: Properly handle args via env vars with dashes --- cmd/kluctl/commands/root.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index 68a8fbb2a..a4f1c35bc 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -147,6 +147,7 @@ func initViper() { } viper.SetEnvPrefix("kluctl") + viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) viper.AutomaticEnv() } From 2df3e9cb4f10555850e8d5dbe7ec0ae0df5095df Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 4 May 2022 09:31:19 +0200 Subject: [PATCH 0758/2916] fix: Fix unexpected log line when skipping deployment items --- pkg/deployment/commands/downscale.go | 2 +- pkg/deployment/commands/poke_images.go | 2 +- pkg/deployment/commands/validate.go | 2 +- pkg/deployment/utils/apply_utils.go | 28 ++++++++++++++++++++++---- pkg/deployment/utils/progress.go | 8 +++++--- 5 files changed, 32 insertions(+), 10 deletions(-) diff --git a/pkg/deployment/commands/downscale.go b/pkg/deployment/commands/downscale.go index edd25944e..f12d86d6b 100644 --- a/pkg/deployment/commands/downscale.go +++ b/pkg/deployment/commands/downscale.go @@ -39,7 +39,7 @@ func (cmd *DownscaleCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, error if !d.CheckInclusionForDeploy() { continue } - au := ad.NewApplyUtil(utils2.NewProgressCtx(nil, d.RelToProjectItemDir, 0)) + au := ad.NewApplyUtil(utils2.NewProgressCtx(nil, d.RelToProjectItemDir, 0, true)) for _, o := range d.Objects { o := o ref := o.GetK8sRef() diff --git a/pkg/deployment/commands/poke_images.go b/pkg/deployment/commands/poke_images.go index 9e2fc9173..d325da3b2 100644 --- a/pkg/deployment/commands/poke_images.go +++ b/pkg/deployment/commands/poke_images.go @@ -75,7 +75,7 @@ func (cmd *PokeImagesCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, erro wg.Add(1) go func() { defer wg.Done() - au := ad.NewApplyUtil(utils2.NewProgressCtx(nil, ref.String(), 0)) + au := ad.NewApplyUtil(utils2.NewProgressCtx(nil, ref.String(), 0, true)) au.ReplaceObject(ref, ru.GetRemoteObject(ref), func(o *uo.UnstructuredObject) (*uo.UnstructuredObject, error) { return doPokeImage(containers, o) }) diff --git a/pkg/deployment/commands/validate.go b/pkg/deployment/commands/validate.go index 3a94cc032..5fa33f251 100644 --- a/pkg/deployment/commands/validate.go +++ b/pkg/deployment/commands/validate.go @@ -39,7 +39,7 @@ func (cmd *ValidateCommand) Run(k *k8s.K8sCluster) (*types.ValidateResult, error if !d.CheckInclusionForDeploy() { continue } - au := ad.NewApplyUtil(utils2.NewProgressCtx(nil, d.RelToProjectItemDir, 0)) + au := ad.NewApplyUtil(utils2.NewProgressCtx(nil, d.RelToProjectItemDir, 0, true)) h := utils2.NewHooksUtil(au) for _, o := range d.Objects { hook := h.GetHook(o) diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index ebbca95c1..3f69a8416 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -529,6 +529,17 @@ func (a *ApplyUtil) applyDeploymentItem(d *deployment.DeploymentItem) { a.pctx.Finish() } +func (a *ApplyDeploymentsUtil) buildProgressName(d *deployment.DeploymentItem) *string { + if d.RelToProjectItemDir != "" { + return &d.RelToProjectItemDir + } + if len(d.Config.DeleteObjects) != 0 { + s := "" + return &s + } + return nil +} + func (a *ApplyDeploymentsUtil) ApplyDeployments() { log.Infof("Running server-side apply for all objects") @@ -542,8 +553,11 @@ func (a *ApplyDeploymentsUtil) ApplyDeployments() { maxNameLen := 0 for _, d := range a.deployments { - if len(d.RelToProjectItemDir) > maxNameLen { - maxNameLen = len(d.RelToProjectItemDir) + name := a.buildProgressName(d) + if name != nil { + if len(*name) > maxNameLen { + maxNameLen = len(*name) + } } } @@ -555,7 +569,13 @@ func (a *ApplyDeploymentsUtil) ApplyDeployments() { _ = sem.Acquire(context.Background(), 1) - pctx := NewProgressCtx(p, d.RelToProjectItemDir, maxNameLen) + progressName := a.buildProgressName(d) + var pctx *progressCtx + if progressName != nil { + pctx = NewProgressCtx(p, *progressName, maxNameLen, true) + } else { + pctx = NewProgressCtx(nil, "", 0, false) + } a2 := a.NewApplyUtil(pctx) wg.Add(1) @@ -568,7 +588,7 @@ func (a *ApplyDeploymentsUtil) ApplyDeployments() { barrier := (d.Config.Barrier != nil && *d.Config.Barrier) || d.Barrier if barrier { - bpctx := NewProgressCtx(p, "", maxNameLen) + bpctx := NewProgressCtx(p, "", maxNameLen, true) bpctx.SetTotal(1) bpctx.InfofAndStatus("Waiting on barrier...") diff --git a/pkg/deployment/utils/progress.go b/pkg/deployment/utils/progress.go index 8ac3a0f81..c5eadbbfc 100644 --- a/pkg/deployment/utils/progress.go +++ b/pkg/deployment/utils/progress.go @@ -15,6 +15,7 @@ import ( type progressCtx struct { bar *mpb.Bar + doLog bool total int64 name string status string @@ -22,14 +23,15 @@ type progressCtx struct { mutex sync.Mutex } -func NewProgressCtx(p *mpb.Progress, name string, maxNameWidth int) *progressCtx { +func NewProgressCtx(p *mpb.Progress, name string, maxNameWidth int, doLog bool) *progressCtx { pctx := &progressCtx{ status: "Initializing...", total: -1, name: name, startTime: time.Now(), } - if !isatty.IsTerminal(os.Stderr.Fd()) || name == "" { + if !isatty.IsTerminal(os.Stderr.Fd()) || p == nil { + pctx.doLog = doLog return pctx } @@ -51,7 +53,7 @@ func NewProgressCtx(p *mpb.Progress, name string, maxNameWidth int) *progressCtx } func (ctx *progressCtx) Logf(level log.Level, s string, args ...interface{}) { - if ctx.bar == nil { + if ctx.doLog { s = fmt.Sprintf("%s: %s", ctx.name, s) log.StandardLogger().Logf(level, s, args...) } From 2b74f16874771d294706db6f5a11412cfb803220 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 5 May 2022 10:46:19 +0200 Subject: [PATCH 0759/2916] build: Upgrade python to 3.10.4 --- pkg/python/generate/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/python/generate/main.go b/pkg/python/generate/main.go index f3ffc0816..dc68cc409 100644 --- a/pkg/python/generate/main.go +++ b/pkg/python/generate/main.go @@ -15,8 +15,8 @@ import ( const ( pythonVersionBase = "3.10" - pythonVersionFull = "3.10.3" - pythonStandaloneVersion = "20220318" + pythonVersionFull = "3.10.4" + pythonStandaloneVersion = "20220502" ) var pythonDists = map[string]string{ From 6c3830b6e820559017659b4c7cc3cbf5a6ccaa01 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 5 May 2022 10:52:06 +0200 Subject: [PATCH 0760/2916] fix: Use debian:bullseye-slim for docker images --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 991b24a50..cd4912bac 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,7 +9,8 @@ RUN wget -O helm.tar.gz https://get.helm.sh/helm-$HELM_VERSION-$ARCH.tar.gz && \ tar xzf helm.tar.gz && \ mv $ARCH/helm / -FROM alpine +# We must use a glibc based distro due to embedded python not supporting musl libc for aarch64 +FROM debian:bullseye-slim COPY --from=builder /helm /usr/bin COPY kluctl /usr/bin/ ENTRYPOINT ["/usr/bin/kluctl"] From a34a20a06c3529bf696549d4146b9c429b43ec86 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 5 May 2022 16:04:59 +0200 Subject: [PATCH 0761/2916] docs: Mention that go 1.18 is the minimum go version now --- install/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/README.md b/install/README.md index 09f38f6e3..a02fc9390 100644 --- a/install/README.md +++ b/install/README.md @@ -28,7 +28,7 @@ git clone https://github.com/kluctl/kluctl cd kluctl ``` -Build the `kluctl` binary (requires go >= 1.17 and python >= 3.10): +Build the `kluctl` binary (requires go >= 1.18 and python >= 3.10): ```bash make build From 861c75fd209e4b2702f30c882eb1f9ca19d778f9 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 6 May 2022 10:04:39 +0200 Subject: [PATCH 0762/2916] refactor: Use filepath.ToSlash and filepath.FromSlash whenever possible --- cmd/kluctl/args/inclusion.go | 6 ++---- pkg/deployment/deployment_item.go | 6 +++--- pkg/jinja2/jinja2.go | 2 +- pkg/kluctl_project/git.go | 2 +- pkg/utils/embed_util/extract.go | 2 +- pkg/utils/embed_util/packer/packer.go | 3 +-- pkg/utils/fs.go | 4 ++-- pkg/utils/tar.go | 9 ++++----- 8 files changed, 15 insertions(+), 19 deletions(-) diff --git a/cmd/kluctl/args/inclusion.go b/cmd/kluctl/args/inclusion.go index 060a6af97..1a63195c2 100644 --- a/cmd/kluctl/args/inclusion.go +++ b/cmd/kluctl/args/inclusion.go @@ -3,9 +3,7 @@ package args import ( "fmt" "github.com/kluctl/kluctl/v2/pkg/utils" - "os" "path/filepath" - "strings" ) type InclusionFlags struct { @@ -27,13 +25,13 @@ func (args *InclusionFlags) ParseInclusionFromArgs() (*utils.Inclusion, error) { if filepath.IsAbs(dir) { return nil, fmt.Errorf("--include-deployment-dir path must be relative") } - inclusion.AddInclude("deploymentItemDir", strings.ReplaceAll(dir, string(os.PathSeparator), "/")) + inclusion.AddInclude("deploymentItemDir", filepath.ToSlash(dir)) } for _, dir := range args.ExcludeDeploymentDir { if filepath.IsAbs(dir) { return nil, fmt.Errorf("--exclude-deployment-dir path must be relative") } - inclusion.AddExclude("deploymentItemDir", strings.ReplaceAll(dir, string(os.PathSeparator), "/")) + inclusion.AddExclude("deploymentItemDir", filepath.ToSlash(dir)) } return inclusion, nil } diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 336dff69c..17f6f746c 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -100,7 +100,7 @@ func (di *DeploymentItem) getCommonLabels() map[string]string { func (di *DeploymentItem) getCommonAnnotations() map[string]string { // TODO change it to kluctl.io/deployment_dir a := map[string]string{ - "kluctl.io/kustomize_dir": strings.ReplaceAll(di.RelToRootItemDir, string(os.PathSeparator), "/"), + "kluctl.io/kustomize_dir": filepath.ToSlash(di.RelToRootItemDir), } if di.Config.SkipDeleteIfTags != nil && *di.Config.SkipDeleteIfTags { a["kluctl.io/skip-delete-if-tags"] = "true" @@ -137,7 +137,7 @@ func (di *DeploymentItem) render(k *k8s.K8sCluster, forSeal bool, wp *utils.Work if yaml.Exists(filepath.Join(p, "helm-chart.yml")) { // never try to render helm charts ep := filepath.Join(di.RelToProjectItemDir, relDir, "charts/**") - ep = strings.ReplaceAll(ep, string(os.PathSeparator), "/") + ep = filepath.ToSlash(ep) excludePatterns = append(excludePatterns, ep) return filepath.SkipDir } @@ -250,7 +250,7 @@ func (di *DeploymentItem) buildInclusionEntries() []utils.InclusionEntry { values = append(values, utils.InclusionEntry{Type: "tag", Value: t}) } if di.dir != nil { - dir := strings.ReplaceAll(di.RelToRootItemDir, string(os.PathSeparator), "/") + dir := filepath.ToSlash(di.RelToRootItemDir) values = append(values, utils.InclusionEntry{Type: "deploymentItemDir", Value: dir}) } return values diff --git a/pkg/jinja2/jinja2.go b/pkg/jinja2/jinja2.go index fa04d3cd7..023e89c08 100644 --- a/pkg/jinja2/jinja2.go +++ b/pkg/jinja2/jinja2.go @@ -198,7 +198,7 @@ func (j *Jinja2) getGlob(pattern string) (glob.Glob, error) { return g.(glob.Glob), nil } func (j *Jinja2) needsRender(path string, excludedPatterns []string) bool { - path = strings.ReplaceAll(path, string(os.PathSeparator), "/") + path = filepath.ToSlash(path) for _, p := range excludedPatterns { g, err := j.getGlob(p) diff --git a/pkg/kluctl_project/git.go b/pkg/kluctl_project/git.go index 23072f103..255dcb7cf 100644 --- a/pkg/kluctl_project/git.go +++ b/pkg/kluctl_project/git.go @@ -122,7 +122,7 @@ func (c *KluctlProjectContext) buildCloneDir(u git_url.GitUrl, ref string) (stri } ref = strings.ReplaceAll(ref, "/", "-") ref = strings.ReplaceAll(ref, "\\", "-") - urlPath := strings.ReplaceAll(u.Path, "/", string(os.PathSeparator)) + urlPath := filepath.FromSlash(u.Path) baseName := filepath.Base(urlPath) urlHash := utils.Sha256String(fmt.Sprintf("%s:%s", u.Host, u.Path))[:16] cloneDir := filepath.Join(c.TmpDir, fmt.Sprintf("%s-%s", baseName, urlHash), ref) diff --git a/pkg/utils/embed_util/extract.go b/pkg/utils/embed_util/extract.go index 16ed318ba..22267376c 100644 --- a/pkg/utils/embed_util/extract.go +++ b/pkg/utils/embed_util/extract.go @@ -120,7 +120,7 @@ func BuildFileList(targetPath string) (map[string]int64, error) { if err != nil { return err } - relPath = strings.ReplaceAll(relPath, string(os.PathSeparator), "/") + relPath = filepath.ToSlash(relPath) if info.IsDir() { existingFiles[relPath] = 0 } else { diff --git a/pkg/utils/embed_util/packer/packer.go b/pkg/utils/embed_util/packer/packer.go index d02da2d7f..fd6fdd077 100644 --- a/pkg/utils/embed_util/packer/packer.go +++ b/pkg/utils/embed_util/packer/packer.go @@ -13,7 +13,6 @@ import ( log "github.com/sirupsen/logrus" "io/fs" "io/ioutil" - "os" "path/filepath" "reflect" "strings" @@ -75,7 +74,7 @@ func findFiles(dir string, patterns []string) (map[string]int64, error) { } match := false for _, p := range globs { - if p.Match(strings.ReplaceAll(rel, string(os.PathSeparator), "/")) { + if p.Match(filepath.ToSlash(rel)) { match = true break } diff --git a/pkg/utils/fs.go b/pkg/utils/fs.go index 30dbcfc1c..aa776b51c 100644 --- a/pkg/utils/fs.go +++ b/pkg/utils/fs.go @@ -58,7 +58,7 @@ func CopyFile(src string, dst string) error { } func FsCopyFile(srcFs fs.FS, src, dst string) error { - src = strings.ReplaceAll(src, string(os.PathSeparator), "/") + src = filepath.ToSlash(src) source, err := srcFs.Open(src) if err != nil { return err @@ -92,7 +92,7 @@ func FsCopyDir(srcFs fs.FS, src string, dst string) error { var err error var fds []fs.DirEntry - src = strings.ReplaceAll(src, string(os.PathSeparator), "/") + src = filepath.ToSlash(src) if fds, err = fs.ReadDir(srcFs, src); err != nil { return err diff --git a/pkg/utils/tar.go b/pkg/utils/tar.go index 0cd6807ed..627f531bf 100644 --- a/pkg/utils/tar.go +++ b/pkg/utils/tar.go @@ -9,7 +9,6 @@ import ( "io/ioutil" "os" "path/filepath" - "strings" ) func ExtractTarGzFile(tarGzPath string, targetPath string) error { @@ -48,7 +47,7 @@ func ExtractTarStream(r io.Reader, targetPath string) error { return fmt.Errorf("ExtractTarStream: Next() failed: %w", err) } - header.Name = strings.ReplaceAll(header.Name, "/", string(os.PathSeparator)) + header.Name = filepath.FromSlash(header.Name) p := filepath.Join(targetPath, header.Name) err = os.MkdirAll(filepath.Dir(p), 0755) @@ -105,7 +104,7 @@ func AddToTar(tw *tar.Writer, pth string, name string, filter func(h *tar.Header if err != nil { return err } - h.Name = strings.ReplaceAll(name, string(os.PathSeparator), "/") + h.Name = filepath.ToSlash(name) if filter != nil { s := fi.Size() @@ -159,14 +158,14 @@ func AddToTar(tw *tar.Writer, pth string, name string, filter func(h *tar.Header } func HashTarEntry(dir string, name string) (string, error) { - p := filepath.Join(dir, strings.ReplaceAll(name, "/", string(os.PathSeparator))) + p := filepath.Join(dir, filepath.FromSlash(name)) st, err := os.Lstat(p) if err != nil { return "", err } var hashData []byte if st.Mode().Type() == fs.ModeDir { - hashData = []byte(strings.ReplaceAll(name, string(os.PathSeparator), "/")) + hashData = []byte(filepath.ToSlash(name)) } else if st.Mode().Type() == fs.ModeSymlink { l, err := os.Readlink(p) if err != nil { From 1a0a4f926c29ae8c63fd82b86ad1f6ef420151af Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 6 May 2022 10:06:09 +0200 Subject: [PATCH 0763/2916] fix: Use SecureJoin in places where user-provided paths are joined --- pkg/deployment/commands/seal.go | 8 ++++-- pkg/deployment/deployment_item.go | 16 ++++++++--- pkg/deployment/helm_chart.go | 43 ++++++++++++++++++++++-------- pkg/git/mirrored_repo.go | 2 ++ pkg/jinja2/jinja2.go | 8 ++++-- pkg/kluctl_project/git.go | 11 ++++++-- pkg/kluctl_project/load.go | 10 ++++--- pkg/kluctl_project/load_targets.go | 7 +++-- pkg/seal/secrets_loader.go | 27 ++++++++++--------- pkg/types/cluster_config.go | 7 ++++- pkg/utils/fs.go | 30 +++++++++++++++++++++ pkg/utils/tar.go | 6 ++++- 12 files changed, 136 insertions(+), 39 deletions(-) diff --git a/pkg/deployment/commands/seal.go b/pkg/deployment/commands/seal.go index cc255a91c..69110cf0d 100644 --- a/pkg/deployment/commands/seal.go +++ b/pkg/deployment/commands/seal.go @@ -2,6 +2,7 @@ package commands import ( "fmt" + securejoin "github.com/cyphar/filepath-securejoin" "github.com/kluctl/kluctl/v2/pkg/deployment" "github.com/kluctl/kluctl/v2/pkg/seal" "io/fs" @@ -33,8 +34,11 @@ func (cmd *SealCommand) Run(sealer *seal.Sealer) error { if err != nil { return err } - targetDir := filepath.Join(cmd.c.Project.SealedSecretsDir, filepath.Dir(relPath)) - targetFile := filepath.Join(targetDir, *cmd.c.Project.Config.SealedSecrets.OutputPattern, filepath.Base(p)) + relTargetFile := filepath.Join(filepath.Dir(relPath), *cmd.c.Project.Config.SealedSecrets.OutputPattern, filepath.Base(p)) + targetFile, err := securejoin.SecureJoin(cmd.c.Project.SealedSecretsDir, relTargetFile) + if err != nil { + return err + } targetFile = targetFile[:len(targetFile)-len(deployment.SealmeExt)] err = sealer.SealFile(p, targetFile) if err != nil { diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 17f6f746c..acb5e7aa3 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -2,6 +2,7 @@ package deployment import ( "fmt" + securejoin "github.com/cyphar/filepath-securejoin" "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" @@ -190,13 +191,17 @@ func (di *DeploymentItem) resolveSealedSecrets(subdir string) error { return nil } - // TODO check for bootstrap - sealedSecretsDir := di.Project.getSealedSecretsDir() baseSourcePath := di.Project.SealedSecretsDir renderedDir := filepath.Join(di.renderedDir, subdir) + // ensure we're not leaving the project + _, err := securejoin.SecureJoin(di.Project.getRootProject().dir, subdir) + if err != nil { + return err + } + y, err := uo.FromFile(yaml.FixPathExt(filepath.Join(renderedDir, "kustomization.yml"))) if err != nil { return err @@ -227,7 +232,12 @@ func (di *DeploymentItem) resolveSealedSecrets(subdir string) error { if sealedSecretsDir == nil { return fmt.Errorf("%s. Sealed secrets dir could not be determined", baseError) } - sourcePath := filepath.Clean(filepath.Join(baseSourcePath, filepath.Join(di.relRenderedDir, subdir), relDir, *sealedSecretsDir, fname)) + // ensure we're not leaving the .sealed-secrets dir + sourcePath, err := securejoin.SecureJoin(baseSourcePath, filepath.Join(di.relRenderedDir, subdir, relDir, *sealedSecretsDir, fname)) + if err != nil { + return err + } + sourcePath = filepath.Clean(sourcePath) targetPath := filepath.Join(renderedDir, relDir, fname) if !utils.IsFile(sourcePath) { return fmt.Errorf("%s. %s not found. You might need to seal it first", baseError, sourcePath) diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index 23a89200f..8c719c2a2 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -2,6 +2,7 @@ package deployment import ( "fmt" + securejoin "github.com/cyphar/filepath-securejoin" "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils/uo" @@ -61,6 +62,22 @@ func (c *helmChart) GetChartName() (string, error) { return *c.Config.ChartName, nil } +func (c *helmChart) GetChartDir() (string, error) { + chartName, err := c.GetChartName() + if err != nil { + return "", err + } + + dir := filepath.Dir(c.configFile) + targetDir := filepath.Join(dir, "charts") + return securejoin.SecureJoin(targetDir, chartName) +} + +func (c *helmChart) GetOutputPath() (string, error) { + dir := filepath.Dir(c.configFile) + return securejoin.SecureJoin(dir, c.Config.Output) +} + func (c *helmChart) buildHelmConfig() (*action.Configuration, error) { rc, err := registry.NewClient() if err != nil { @@ -77,10 +94,13 @@ func (c *helmChart) Pull() error { return err } - dir := filepath.Dir(c.configFile) - targetDir := filepath.Join(dir, "charts") - rmDir := filepath.Join(targetDir, chartName) - _ = os.RemoveAll(rmDir) + chartDir, err := c.GetChartDir() + if err != nil { + return err + } + + targetDir := filepath.Join(filepath.Dir(c.configFile), "charts") + _ = os.RemoveAll(chartDir) cfg, err := c.buildHelmConfig() if err != nil { @@ -100,8 +120,8 @@ func (c *helmChart) Pull() error { out, err = a.Run(chartName) } // a bug in the Pull command causes this directory to be created by accident - _ = os.RemoveAll(rmDir + fmt.Sprintf("-%s.tar.gz", a.Version)) - _ = os.RemoveAll(rmDir + fmt.Sprintf("-%s.tgz", a.Version)) + _ = os.RemoveAll(chartDir + fmt.Sprintf("-%s.tar.gz", a.Version)) + _ = os.RemoveAll(chartDir + fmt.Sprintf("-%s.tgz", a.Version)) if out != "" { log.Info(out) } @@ -170,14 +190,15 @@ func (c *helmChart) Render(k *k8s.K8sCluster) error { } func (c *helmChart) doRender(k *k8s.K8sCluster) error { - chartName, err := c.GetChartName() + chartDir, err := c.GetChartDir() if err != nil { return err } - dir := filepath.Dir(c.configFile) - chartDir := filepath.Join(dir, "charts", chartName) - valuesPath := yaml.FixPathExt(filepath.Join(dir, "helm-values.yml")) - outputPath := filepath.Join(dir, c.Config.Output) + outputPath, err := c.GetOutputPath() + if err != nil { + return err + } + valuesPath := yaml.FixPathExt(filepath.Join(filepath.Dir(c.configFile), "helm-values.yml")) var gvs []string if k != nil { diff --git a/pkg/git/mirrored_repo.go b/pkg/git/mirrored_repo.go index 0afba2cd9..fc8d40fdc 100644 --- a/pkg/git/mirrored_repo.go +++ b/pkg/git/mirrored_repo.go @@ -342,6 +342,8 @@ func (g *MirroredGitRepo) CloneProject(ctx context.Context, ref string, targetDi func buildMirrorRepoName(u git_url.GitUrl) string { r := filepath.Base(u.Path) + r = strings.ReplaceAll(r, "/", "-") + r = strings.ReplaceAll(r, "\\", "-") if strings.HasSuffix(r, ".git") { r = r[:len(r)-len(".git")] } diff --git a/pkg/jinja2/jinja2.go b/pkg/jinja2/jinja2.go index 023e89c08..83e176870 100644 --- a/pkg/jinja2/jinja2.go +++ b/pkg/jinja2/jinja2.go @@ -2,6 +2,7 @@ package jinja2 import ( "fmt" + securejoin "github.com/cyphar/filepath-securejoin" "github.com/gobwas/glob" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" @@ -213,11 +214,14 @@ func (j *Jinja2) needsRender(path string, excludedPatterns []string) bool { } func (j *Jinja2) RenderDirectory(rootDir string, searchDirs []string, relSourceDir string, excludePatterns []string, subdir string, targetDir string, vars *uo.UnstructuredObject) error { - walkDir := filepath.Join(rootDir, relSourceDir, subdir) + walkDir, err := securejoin.SecureJoin(rootDir, filepath.Join(relSourceDir, subdir)) + if err != nil { + return err + } var jobs []*RenderJob - err := filepath.WalkDir(walkDir, func(p string, d fs.DirEntry, err error) error { + err = filepath.WalkDir(walkDir, func(p string, d fs.DirEntry, err error) error { relPath, err := filepath.Rel(walkDir, p) if err != nil { return err diff --git a/pkg/kluctl_project/git.go b/pkg/kluctl_project/git.go index 255dcb7cf..edf405701 100644 --- a/pkg/kluctl_project/git.go +++ b/pkg/kluctl_project/git.go @@ -3,6 +3,7 @@ package kluctl_project import ( "context" "fmt" + securejoin "github.com/cyphar/filepath-securejoin" "github.com/kluctl/kluctl/v2/pkg/git" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" types2 "github.com/kluctl/kluctl/v2/pkg/types" @@ -125,7 +126,10 @@ func (c *KluctlProjectContext) buildCloneDir(u git_url.GitUrl, ref string) (stri urlPath := filepath.FromSlash(u.Path) baseName := filepath.Base(urlPath) urlHash := utils.Sha256String(fmt.Sprintf("%s:%s", u.Host, u.Path))[:16] - cloneDir := filepath.Join(c.TmpDir, fmt.Sprintf("%s-%s", baseName, urlHash), ref) + cloneDir, err := securejoin.SecureJoin(c.TmpDir, filepath.Join(fmt.Sprintf("%s-%s", baseName, urlHash), ref)) + if err != nil { + return "", err + } log.Tracef("buildCloneDir: ref=%s, urlPath=%s, baseName=%s, urlHash=%s, cloneDir=%s", ref, urlPath, baseName, urlHash, cloneDir) return cloneDir, nil } @@ -147,7 +151,10 @@ func (c *KluctlProjectContext) cloneGitProject(ctx context.Context, gitProject t } dir := targetDir if subDir != "" { - dir = filepath.Join(dir, subDir) + dir, err = securejoin.SecureJoin(dir, subDir) + if err != nil { + return gitProjectInfo{}, err + } } mr, ok := c.mirroredRepos[gitProject.Project.Url.NormalizedRepoKey()] diff --git a/pkg/kluctl_project/load.go b/pkg/kluctl_project/load.go index 511a514fd..26efa5dd0 100644 --- a/pkg/kluctl_project/load.go +++ b/pkg/kluctl_project/load.go @@ -5,6 +5,7 @@ import ( "compress/gzip" "context" "fmt" + securejoin "github.com/cyphar/filepath-securejoin" "github.com/kluctl/kluctl/v2/pkg/git" "github.com/kluctl/kluctl/v2/pkg/jinja2" types2 "github.com/kluctl/kluctl/v2/pkg/types" @@ -84,6 +85,7 @@ func (c *KluctlProjectContext) load(ctx context.Context, allowGit bool) error { return c.localProject(localDir), nil } if ep == nil || ep.Project == nil { + // the ExternalProject is pointing to the same repo that the root project is located in p := kluctlProjectInfo.dir if ep != nil { if filepath.IsAbs(*ep.Path) { @@ -98,12 +100,14 @@ func (c *KluctlProjectContext) load(ctx context.Context, allowGit bool) error { if err != nil { return gitProjectInfo{}, err } - p, err = filepath.Abs(filepath.Join(p, *ep.Path)) + relToGitRoot, err := filepath.Rel(gitRoot, p) if err != nil { return gitProjectInfo{}, err } - if !strings.HasPrefix(p, gitRoot) { - return gitProjectInfo{}, fmt.Errorf("path '%s' is not inside git project root '%s'", *ep.Path, gitRoot) + relToGitRoot = filepath.Join(relToGitRoot, *ep.Path) + p, err = securejoin.SecureJoin(gitRoot, relToGitRoot) + if err != nil { + return gitProjectInfo{}, fmt.Errorf("path '%s' is not inside git project root '%s': %w", relToGitRoot, gitRoot, err) } } else { if defaultGitSubDir != "" { diff --git a/pkg/kluctl_project/load_targets.go b/pkg/kluctl_project/load_targets.go index ec20c2f0f..0148e6785 100644 --- a/pkg/kluctl_project/load_targets.go +++ b/pkg/kluctl_project/load_targets.go @@ -3,6 +3,7 @@ package kluctl_project import ( "context" "fmt" + securejoin "github.com/cyphar/filepath-securejoin" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/jinja2" "github.com/kluctl/kluctl/v2/pkg/types" @@ -10,7 +11,6 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" log "github.com/sirupsen/logrus" - "path/filepath" "reflect" "regexp" "sort" @@ -320,7 +320,10 @@ func (c *KluctlProjectContext) buildDynamicTarget(targetInfo *dynamicTargetInfo) if targetInfo.baseTarget.TargetConfig.File != nil { configFile = *targetInfo.baseTarget.TargetConfig.File } - configPath := filepath.Join(targetInfo.dir, configFile) + configPath, err := securejoin.SecureJoin(targetInfo.dir, configFile) + if err != nil { + return nil, err + } if !utils.IsFile(configPath) { return nil, fmt.Errorf("no target config file with name %s found in target", configFile) } diff --git a/pkg/seal/secrets_loader.go b/pkg/seal/secrets_loader.go index 8241d9547..ef0e37fe9 100644 --- a/pkg/seal/secrets_loader.go +++ b/pkg/seal/secrets_loader.go @@ -2,6 +2,7 @@ package seal import ( "fmt" + securejoin "github.com/cyphar/filepath-securejoin" "github.com/kluctl/kluctl/v2/pkg/kluctl_project" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" @@ -9,7 +10,6 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils/uo" "os" "path/filepath" - "strings" ) type usernamePassword struct { @@ -26,8 +26,8 @@ type SecretsLoader struct { func NewSecretsLoader(p *kluctl_project.KluctlProjectContext, secretsDir string) *SecretsLoader { return &SecretsLoader{ - project: p, - secretsDir: secretsDir, + project: p, + secretsDir: secretsDir, credentialsCache: map[string]usernamePassword{}, } } @@ -48,21 +48,24 @@ func (s *SecretsLoader) LoadSecrets(source *types.SecretSource) (*uo.Unstructure func (s *SecretsLoader) loadSecretsFile(source *types.SecretSource) (*uo.UnstructuredObject, error) { var p string + var err error if utils.Exists(filepath.Join(s.project.DeploymentDir, *source.Path)) { - p = filepath.Join(s.project.DeploymentDir, *source.Path) + p, err = securejoin.SecureJoin(s.project.DeploymentDir, *source.Path) + if err != nil { + return nil, err + } } else if utils.Exists(filepath.Join(s.secretsDir, *source.Path)) { - p = filepath.Join(s.secretsDir, *source.Path) - } - if p == "" || !utils.Exists(p) { + p, err = securejoin.SecureJoin(s.secretsDir, *source.Path) + if err != nil { + return nil, err + } + } else { return nil, fmt.Errorf("secrets file %s does not exist", *source.Path) } - abs, err := filepath.Abs(p) + err = utils.CheckInDir(s.project.DeploymentDir, p) if err != nil { - return nil, err - } - if !strings.HasPrefix(abs, s.project.DeploymentDir) { - return nil, fmt.Errorf("secrets file %s is not part of the deployment project", *source.Path) + return nil, fmt.Errorf("secrets file %s is not part of the deployment project: %w", *source.Path, err) } secrets, err := uo.FromFile(p) diff --git a/pkg/types/cluster_config.go b/pkg/types/cluster_config.go index 0c22be6b1..fe8ef11b2 100644 --- a/pkg/types/cluster_config.go +++ b/pkg/types/cluster_config.go @@ -64,8 +64,13 @@ func LoadClusterConfig(clusterDir string, clusterName string) (*ClusterConfig, e return nil, fmt.Errorf("cluster config for %s not found", clusterName) } + err := utils.CheckInDir(clusterDir, p) + if err != nil { + return nil, fmt.Errorf("cluster config for %s is not in cluster dir", clusterName) + } + var config ClusterConfig - err := yaml.ReadYamlFile(p, &config) + err = yaml.ReadYamlFile(p, &config) if err != nil { return nil, err } diff --git a/pkg/utils/fs.go b/pkg/utils/fs.go index aa776b51c..422538a73 100644 --- a/pkg/utils/fs.go +++ b/pkg/utils/fs.go @@ -37,6 +37,36 @@ func IsDirectory(path string) bool { return fileInfo.IsDir() } +func CheckInDir(root string, path string) error { + absRoot, err := filepath.Abs(filepath.Clean(root)) + if err != nil { + return err + } + absPath, err := filepath.Abs(filepath.Clean(path)) + if err != nil { + return err + } + + absRoot, err = filepath.EvalSymlinks(absRoot) + if err != nil { + return err + } + + absPath, err = filepath.EvalSymlinks(absPath) + if err != nil { + return err + } + + if absRoot == absPath { + return nil + } + + if !strings.HasPrefix(absPath, absRoot+string(os.PathSeparator)) { + return fmt.Errorf("path %s is not inside directory %s", path, root) + } + return nil +} + func Touch(path string) error { f, err := os.Create(path) if err != nil { diff --git a/pkg/utils/tar.go b/pkg/utils/tar.go index 627f531bf..0425dc6e6 100644 --- a/pkg/utils/tar.go +++ b/pkg/utils/tar.go @@ -4,6 +4,7 @@ import ( "archive/tar" "compress/gzip" "fmt" + securejoin "github.com/cyphar/filepath-securejoin" "io" "io/fs" "io/ioutil" @@ -49,7 +50,10 @@ func ExtractTarStream(r io.Reader, targetPath string) error { header.Name = filepath.FromSlash(header.Name) - p := filepath.Join(targetPath, header.Name) + p, err := securejoin.SecureJoin(targetPath, header.Name) + if err != nil { + return err + } err = os.MkdirAll(filepath.Dir(p), 0755) if err != nil { return err From 51c6b4fd037c3f1b100d06bd62d376b85e179c8b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 6 May 2022 10:06:21 +0200 Subject: [PATCH 0764/2916] fix: Stop using http caching for k8s clients This caused errors to be cached which should have been resolved after CRDs got deployed. --- pkg/k8s/k8s_cluster.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index 59871f052..33265da1d 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -90,8 +90,7 @@ func NewK8sCluster(configIn *rest.Config, dryRun bool) (*K8sCluster, error) { return nil, err } discoveryCacheDir := filepath.Join(utils.GetTmpBaseDir(), "kube-cache/discovery", apiHost.Hostname()) - httpCacheDir := filepath.Join(utils.GetTmpBaseDir(), "kube-cache/http", apiHost.Hostname()) - discovery2, err := disk.NewCachedDiscoveryClientForConfig(dynamic.ConfigFor(k.restConfig), discoveryCacheDir, httpCacheDir, time.Hour*24) + discovery2, err := disk.NewCachedDiscoveryClientForConfig(dynamic.ConfigFor(k.restConfig), discoveryCacheDir, "", time.Hour*24) if err != nil { return nil, err } From 22ff5ecf437c7e9102f311238e5e256d83f6d39e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 6 May 2022 10:52:00 +0200 Subject: [PATCH 0765/2916] refactor: Allow to pass RepoRoot from outside --- cmd/kluctl/commands/utils.go | 8 ++++ pkg/kluctl_project/git.go | 24 +++++------ pkg/kluctl_project/load.go | 64 +++++++++++++++--------------- pkg/kluctl_project/load_targets.go | 3 +- pkg/kluctl_project/project.go | 1 + 5 files changed, 52 insertions(+), 48 deletions(-) diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 3d0d860de..5e90ea31a 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/deployment" + "github.com/kluctl/kluctl/v2/pkg/git" "github.com/kluctl/kluctl/v2/pkg/git/auth" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/jinja2" @@ -12,6 +13,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/registries" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/yaml" + log "github.com/sirupsen/logrus" "io/ioutil" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" @@ -48,7 +50,13 @@ func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, strictTemplates b return err } + repoRoot, err := git.DetectGitRepositoryRoot(cwd) + if err != nil { + log.Warning("Failed to detect git project root. This might cause follow-up errors") + } + loadArgs := kluctl_project.LoadKluctlProjectArgs{ + RepoRoot: repoRoot, ProjectDir: cwd, ProjectUrl: url, ProjectRef: projectFlags.ProjectRef, diff --git a/pkg/kluctl_project/git.go b/pkg/kluctl_project/git.go index edf405701..0c5a641fd 100644 --- a/pkg/kluctl_project/git.go +++ b/pkg/kluctl_project/git.go @@ -134,18 +134,18 @@ func (c *KluctlProjectContext) buildCloneDir(u git_url.GitUrl, ref string) (stri return cloneDir, nil } -func (c *KluctlProjectContext) cloneGitProject(ctx context.Context, gitProject types2.ExternalProject, defaultGitSubDir string, doAddInvolvedRepo bool, doLock bool) (result gitProjectInfo, err error) { +func (c *KluctlProjectContext) cloneGitProject(ctx context.Context, gitProject *types2.GitProject, defaultGitSubDir string, doAddInvolvedRepo bool, doLock bool) (result gitProjectInfo, err error) { err = os.MkdirAll(filepath.Join(c.TmpDir, "git"), 0o700) if err != nil { return } - targetDir, err := c.buildCloneDir(gitProject.Project.Url, gitProject.Project.Ref) + targetDir, err := c.buildCloneDir(gitProject.Url, gitProject.Ref) if err != nil { return } - subDir := gitProject.Project.SubDir + subDir := gitProject.SubDir if subDir == "" { subDir = defaultGitSubDir } @@ -157,13 +157,13 @@ func (c *KluctlProjectContext) cloneGitProject(ctx context.Context, gitProject t } } - mr, ok := c.mirroredRepos[gitProject.Project.Url.NormalizedRepoKey()] + mr, ok := c.mirroredRepos[gitProject.Url.NormalizedRepoKey()] if !ok { - mr, err = git.NewMirroredGitRepo(gitProject.Project.Url) + mr, err = git.NewMirroredGitRepo(gitProject.Url) if err != nil { return } - c.mirroredRepos[gitProject.Project.Url.NormalizedRepoKey()] = mr + c.mirroredRepos[gitProject.Url.NormalizedRepoKey()] = mr err = mr.Lock(ctx) if err != nil { return @@ -176,7 +176,7 @@ func (c *KluctlProjectContext) cloneGitProject(ctx context.Context, gitProject t if err != nil { return err } - return mr.CloneProject(ctx, gitProject.Project.Ref, targetDir) + return mr.CloneProject(ctx, gitProject.Ref, targetDir) }) if err != nil { return @@ -187,7 +187,7 @@ func (c *KluctlProjectContext) cloneGitProject(ctx context.Context, gitProject t return } - result.url = gitProject.Project.Url + result.url = gitProject.Url result.ref = ri.CheckedOutRef result.commit = ri.CheckedOutCommit result.dir = dir @@ -211,11 +211,9 @@ func (c *KluctlProjectContext) cloneKluctlProject(ctx context.Context) (gitProje if c.loadArgs.ProjectUrl == nil { return c.localProject(c.loadArgs.ProjectDir), nil } - return c.cloneGitProject(ctx, types2.ExternalProject{ - Project: &types2.GitProject{ - Url: *c.loadArgs.ProjectUrl, - Ref: c.loadArgs.ProjectRef, - }, + return c.cloneGitProject(ctx, &types2.GitProject{ + Url: *c.loadArgs.ProjectUrl, + Ref: c.loadArgs.ProjectRef, }, "", true, true) } diff --git a/pkg/kluctl_project/load.go b/pkg/kluctl_project/load.go index 26efa5dd0..aa40f45fb 100644 --- a/pkg/kluctl_project/load.go +++ b/pkg/kluctl_project/load.go @@ -5,8 +5,6 @@ import ( "compress/gzip" "context" "fmt" - securejoin "github.com/cyphar/filepath-securejoin" - "github.com/kluctl/kluctl/v2/pkg/git" "github.com/kluctl/kluctl/v2/pkg/jinja2" types2 "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" @@ -84,43 +82,43 @@ func (c *KluctlProjectContext) load(ctx context.Context, allowGit bool) error { if localDir != "" { return c.localProject(localDir), nil } - if ep == nil || ep.Project == nil { - // the ExternalProject is pointing to the same repo that the root project is located in + + if ep == nil { + // no ExternalProject provided, so we point into the kluctl project + defaultGitSubDir p := kluctlProjectInfo.dir - if ep != nil { - if filepath.IsAbs(*ep.Path) { - return gitProjectInfo{}, fmt.Errorf("only paths relative to the git project root are allowed") - } - // we only allow relative paths pointing into the root git project - gitRoot, err := git.DetectGitRepositoryRoot(p) - if err != nil { - return gitProjectInfo{}, fmt.Errorf("could not determine git project root: %w", err) - } - gitRoot, err = filepath.Abs(gitRoot) - if err != nil { - return gitProjectInfo{}, err - } - relToGitRoot, err := filepath.Rel(gitRoot, p) - if err != nil { - return gitProjectInfo{}, err - } - relToGitRoot = filepath.Join(relToGitRoot, *ep.Path) - p, err = securejoin.SecureJoin(gitRoot, relToGitRoot) - if err != nil { - return gitProjectInfo{}, fmt.Errorf("path '%s' is not inside git project root '%s': %w", relToGitRoot, gitRoot, err) - } - } else { - if defaultGitSubDir != "" { - p = filepath.Join(p, defaultGitSubDir) - } + if defaultGitSubDir != "" { + p = filepath.Join(p, defaultGitSubDir) } return c.localProject(p), nil } - if !allowGit { - return gitProjectInfo{}, fmt.Errorf("tried to load something from git while it was not allowed") + + if ep.Project != nil { + // pointing to an actual external project, so let's try to clone it + if !allowGit { + return gitProjectInfo{}, fmt.Errorf("tried to load something from git while it was not allowed") + } + return c.cloneGitProject(ctx, ep.Project, defaultGitSubDir, true, true) } - return c.cloneGitProject(ctx, *ep, defaultGitSubDir, true, true) + // ExternalProject was provided but without an external repo url, so point into the kluctl project. + // We also allow to leave the kluctl project dir in case RepoRoot is provided via loadArgs, as long as + // the pointed to directory does not leave the RepoRoot + + repoRoot := c.loadArgs.RepoRoot + if repoRoot == "" { + // don't allow to leave the project dir + repoRoot = kluctlProjectInfo.dir + } + + p := filepath.Join(kluctlProjectInfo.dir, *ep.Path) + err = utils.CheckInDir(repoRoot, p) + if err != nil { + if c.loadArgs.RepoRoot == "" { + return gitProjectInfo{}, fmt.Errorf("it looks like your kluctl project is not part of a Git repository, which means you are not allowed to use paths that leave your kluctl project") + } + return gitProjectInfo{}, fmt.Errorf("path '%s' is not inside git project root '%s': %w", p, repoRoot, err) + } + return c.localProject(p), nil } deploymentInfo, err := doClone(c.Config.Deployment, "", c.loadArgs.LocalDeployment) diff --git a/pkg/kluctl_project/load_targets.go b/pkg/kluctl_project/load_targets.go index 0148e6785..1d48ed529 100644 --- a/pkg/kluctl_project/load_targets.go +++ b/pkg/kluctl_project/load_targets.go @@ -251,9 +251,8 @@ func (c *KluctlProjectContext) cloneDynamicTargets(ctx context.Context, dynamicT wp.Submit(func() error { gitProject := *targetInfo.gitProject gitProject.Ref = *targetInfo.ref - ep := types.ExternalProject{Project: &gitProject} - gi, err := c.cloneGitProject(ctx, ep, "", false, false) + gi, err := c.cloneGitProject(ctx, &gitProject, "", false, false) mutex.Lock() defer mutex.Unlock() if err != nil { diff --git a/pkg/kluctl_project/project.go b/pkg/kluctl_project/project.go index dce44c7fc..0357f5d34 100644 --- a/pkg/kluctl_project/project.go +++ b/pkg/kluctl_project/project.go @@ -12,6 +12,7 @@ import ( ) type LoadKluctlProjectArgs struct { + RepoRoot string ProjectDir string ProjectUrl *git_url.GitUrl ProjectRef string From be7594c0c95c9ff9044c447587e8b9be3f7441c7 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 6 May 2022 10:53:22 +0200 Subject: [PATCH 0766/2916] fix: Run go mod tidy --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index f432a9b25..4c81d771f 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e github.com/aws/aws-sdk-go v1.44.1 github.com/bitnami-labs/sealed-secrets v0.17.5 + github.com/cyphar/filepath-securejoin v0.2.3 github.com/docker/distribution v2.8.1+incompatible github.com/evanphx/json-patch v5.6.0+incompatible github.com/gammazero/workerpool v1.1.2 @@ -84,7 +85,6 @@ require ( github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 // indirect github.com/containerd/containerd v1.6.3 // indirect - github.com/cyphar/filepath-securejoin v0.2.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/docker/cli v20.10.14+incompatible // indirect github.com/docker/docker v20.10.14+incompatible // indirect From 45b5d7e163f60975895f9c1eb13c916fe6d79d74 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 6 May 2022 16:16:47 +0200 Subject: [PATCH 0767/2916] fix: Don't try to evaluate symlinks in CheckInDir --- pkg/utils/fs.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/pkg/utils/fs.go b/pkg/utils/fs.go index 422538a73..ac567cdb8 100644 --- a/pkg/utils/fs.go +++ b/pkg/utils/fs.go @@ -47,16 +47,6 @@ func CheckInDir(root string, path string) error { return err } - absRoot, err = filepath.EvalSymlinks(absRoot) - if err != nil { - return err - } - - absPath, err = filepath.EvalSymlinks(absPath) - if err != nil { - return err - } - if absRoot == absPath { return nil } From c865e82d54306e3acd52879f1c162f74256ea00d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 6 May 2022 21:57:33 +0200 Subject: [PATCH 0768/2916] feat: Rewrite kluctl project loading and archive handling This fixes external projects pointing to the root repo via relative sub pathes. --- cmd/kluctl/commands/cmd_archive.go | 4 +- cmd/kluctl/commands/cmd_list_targets.go | 2 +- cmd/kluctl/commands/cmd_seal.go | 4 +- cmd/kluctl/commands/completion.go | 12 +- cmd/kluctl/commands/utils.go | 9 +- pkg/kluctl_project/git.go | 98 ++---- pkg/kluctl_project/load.go | 287 +----------------- pkg/kluctl_project/project.go | 66 ++-- pkg/kluctl_project/project_archive.go | 129 ++++++++ pkg/kluctl_project/project_load.go | 259 ++++++++++++++++ pkg/kluctl_project/target_context.go | 6 +- .../{load_targets.go => targets.go} | 18 +- pkg/seal/secrets_loader.go | 4 +- pkg/types/metadata.go | 10 + pkg/yaml/yaml.go | 22 ++ 15 files changed, 509 insertions(+), 421 deletions(-) create mode 100644 pkg/kluctl_project/project_archive.go create mode 100644 pkg/kluctl_project/project_load.go rename pkg/kluctl_project/{load_targets.go => targets.go} (90%) diff --git a/cmd/kluctl/commands/cmd_archive.go b/cmd/kluctl/commands/cmd_archive.go index 5c5325613..250571c51 100644 --- a/cmd/kluctl/commands/cmd_archive.go +++ b/cmd/kluctl/commands/cmd_archive.go @@ -16,7 +16,7 @@ func (cmd *archiveCmd) Help() string { } func (cmd *archiveCmd) Run() error { - return withKluctlProjectFromArgs(cmd.ProjectFlags, true, func(p *kluctl_project.KluctlProjectContext) error { - return p.CreateTGZArchive(cmd.OutputArchive, cmd.ProjectFlags.OutputMetadata == "") + return withKluctlProjectFromArgs(cmd.ProjectFlags, true, func(p *kluctl_project.LoadedKluctlProject) error { + return p.WriteArchive(cmd.OutputArchive, cmd.ProjectFlags.OutputMetadata == "") }) } diff --git a/cmd/kluctl/commands/cmd_list_targets.go b/cmd/kluctl/commands/cmd_list_targets.go index ba4493622..c54241e94 100644 --- a/cmd/kluctl/commands/cmd_list_targets.go +++ b/cmd/kluctl/commands/cmd_list_targets.go @@ -16,7 +16,7 @@ func (cmd *listTargetsCmd) Help() string { } func (cmd *listTargetsCmd) Run() error { - return withKluctlProjectFromArgs(cmd.ProjectFlags, true, func(p *kluctl_project.KluctlProjectContext) error { + return withKluctlProjectFromArgs(cmd.ProjectFlags, true, func(p *kluctl_project.LoadedKluctlProject) error { var result []*types.Target for _, t := range p.DynamicTargets { result = append(result, t.Target) diff --git a/cmd/kluctl/commands/cmd_seal.go b/cmd/kluctl/commands/cmd_seal.go index d840403d0..bdabda126 100644 --- a/cmd/kluctl/commands/cmd_seal.go +++ b/cmd/kluctl/commands/cmd_seal.go @@ -61,7 +61,7 @@ func loadSecrets(ctx *commandCtx, target *types.Target, secretsLoader *seal.Secr return nil } -func (cmd *sealCmd) runCmdSealForTarget(p *kluctl_project.KluctlProjectContext, targetName string, secretsLoader *seal.SecretsLoader) error { +func (cmd *sealCmd) runCmdSealForTarget(p *kluctl_project.LoadedKluctlProject, targetName string, secretsLoader *seal.SecretsLoader) error { log.Infof("Sealing for target %s", targetName) ptArgs := projectTargetCommandArgs{ @@ -119,7 +119,7 @@ func (cmd *sealCmd) runCmdSealForTarget(p *kluctl_project.KluctlProjectContext, } func (cmd *sealCmd) Run() error { - return withKluctlProjectFromArgs(cmd.ProjectFlags, true, func(p *kluctl_project.KluctlProjectContext) error { + return withKluctlProjectFromArgs(cmd.ProjectFlags, true, func(p *kluctl_project.LoadedKluctlProject) error { hadError := false secretsLoader := seal.NewSecretsLoader(p, cmd.SecretsDir) diff --git a/cmd/kluctl/commands/completion.go b/cmd/kluctl/commands/completion.go index 48c5f8afb..a316051bb 100644 --- a/cmd/kluctl/commands/completion.go +++ b/cmd/kluctl/commands/completion.go @@ -47,10 +47,10 @@ func RegisterFlagCompletionFuncs(cmdStruct interface{}, ccmd *cobra.Command) err return nil } -func withProjectForCompletion(projectArgs *args.ProjectFlags, cb func(p *kluctl_project.KluctlProjectContext) error) error { +func withProjectForCompletion(projectArgs *args.ProjectFlags, cb func(p *kluctl_project.LoadedKluctlProject) error) error { // let's not update git caches too often projectArgs.GitCacheUpdateInterval = time.Second * 60 - return withKluctlProjectFromArgs(*projectArgs, false, func(p *kluctl_project.KluctlProjectContext) error { + return withKluctlProjectFromArgs(*projectArgs, false, func(p *kluctl_project.LoadedKluctlProject) error { return cb(p) }) } @@ -58,7 +58,7 @@ func withProjectForCompletion(projectArgs *args.ProjectFlags, cb func(p *kluctl_ func buildClusterCompletionFunc(projectArgs *args.ProjectFlags) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { var ret []string - err := withProjectForCompletion(projectArgs, func(p *kluctl_project.KluctlProjectContext) error { + err := withProjectForCompletion(projectArgs, func(p *kluctl_project.LoadedKluctlProject) error { dents, err := os.ReadDir(p.ClustersDir) if err != nil { return err @@ -86,7 +86,7 @@ func buildClusterCompletionFunc(projectArgs *args.ProjectFlags) func(cmd *cobra. func buildTargetCompletionFunc(projectArgs *args.ProjectFlags) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { var ret []string - err := withProjectForCompletion(projectArgs, func(p *kluctl_project.KluctlProjectContext) error { + err := withProjectForCompletion(projectArgs, func(p *kluctl_project.LoadedKluctlProject) error { for _, t := range p.DynamicTargets { ret = append(ret, t.Target.Name) } @@ -132,7 +132,7 @@ func buildInclusionCompletionFunc(cmdStruct interface{}, forDirs bool) func(cmd var deploymentItemDirs utils.OrderedMap var mutex sync.Mutex - err := withProjectForCompletion(&ptArgs.projectFlags, func(p *kluctl_project.KluctlProjectContext) error { + err := withProjectForCompletion(&ptArgs.projectFlags, func(p *kluctl_project.LoadedKluctlProject) error { var targets []string if ptArgs.targetFlags.Target == "" { for _, t := range p.DynamicTargets { @@ -186,7 +186,7 @@ func buildImagesCompletionFunc(cmdStruct interface{}) func(cmd *cobra.Command, a var images utils.OrderedMap var mutex sync.Mutex - err := withProjectForCompletion(&ptArgs.projectFlags, func(p *kluctl_project.KluctlProjectContext) error { + err := withProjectForCompletion(&ptArgs.projectFlags, func(p *kluctl_project.LoadedKluctlProject) error { var targets []string if ptArgs.targetFlags.Target == "" { for _, t := range p.DynamicTargets { diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 5e90ea31a..e1adddebb 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -21,7 +21,7 @@ import ( "time" ) -func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, strictTemplates bool, cb func(p *kluctl_project.KluctlProjectContext) error) error { +func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, strictTemplates bool, cb func(p *kluctl_project.LoadedKluctlProject) error) error { var url *git_url.GitUrl if projectFlags.ProjectUrl != "" { var err error @@ -51,7 +51,7 @@ func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, strictTemplates b } repoRoot, err := git.DetectGitRepositoryRoot(cwd) - if err != nil { + if err != nil && projectFlags.FromArchive == "" { log.Warning("Failed to detect git project root. This might cause follow-up errors") } @@ -66,6 +66,7 @@ func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, strictTemplates b LocalSealedSecrets: projectFlags.LocalSealedSecrets.String(), FromArchive: projectFlags.FromArchive.String(), FromArchiveMetadata: projectFlags.FromArchiveMetadata.String(), + AllowGitClone: projectFlags.FromArchive == "", GitAuthProviders: auth.NewDefaultAuthProviders(), GitUpdateInterval: projectFlags.GitCacheUpdateInterval, } @@ -109,12 +110,12 @@ type commandCtx struct { } func withProjectCommandContext(args projectTargetCommandArgs, cb func(ctx *commandCtx) error) error { - return withKluctlProjectFromArgs(args.projectFlags, true, func(p *kluctl_project.KluctlProjectContext) error { + return withKluctlProjectFromArgs(args.projectFlags, true, func(p *kluctl_project.LoadedKluctlProject) error { return withProjectTargetCommandContext(args, p, cb) }) } -func withProjectTargetCommandContext(args projectTargetCommandArgs, p *kluctl_project.KluctlProjectContext, cb func(ctx *commandCtx) error) error { +func withProjectTargetCommandContext(args projectTargetCommandArgs, p *kluctl_project.LoadedKluctlProject, cb func(ctx *commandCtx) error) error { rh := registries.NewRegistryHelper() err := rh.ParseAuthEntriesFromEnv() if err != nil { diff --git a/pkg/kluctl_project/git.go b/pkg/kluctl_project/git.go index 0c5a641fd..ee8ba490c 100644 --- a/pkg/kluctl_project/git.go +++ b/pkg/kluctl_project/git.go @@ -3,12 +3,10 @@ package kluctl_project import ( "context" "fmt" - securejoin "github.com/cyphar/filepath-securejoin" "github.com/kluctl/kluctl/v2/pkg/git" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" types2 "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" - log "github.com/sirupsen/logrus" "os" "path/filepath" "reflect" @@ -18,7 +16,7 @@ import ( "time" ) -func (c *KluctlProjectContext) updateGitCache(ctx context.Context, mr *git.MirroredGitRepo) error { +func (c *LoadedKluctlProject) updateGitCache(ctx context.Context, mr *git.MirroredGitRepo) error { if mr.HasUpdated() { return nil } @@ -29,7 +27,7 @@ func (c *KluctlProjectContext) updateGitCache(ctx context.Context, mr *git.Mirro return mr.Update(ctx, c.loadArgs.GitAuthProviders) } -func (c *KluctlProjectContext) updateGitCaches(ctx context.Context) error { +func (c *LoadedKluctlProject) updateGitCaches(ctx context.Context) error { var waitGroup sync.WaitGroup var firstError error var firstErrorLock sync.Mutex @@ -110,53 +108,12 @@ func (c *KluctlProjectContext) updateGitCaches(ctx context.Context) error { return firstError } -type gitProjectInfo struct { - url git_url.GitUrl - ref string - commit string - dir string -} - -func (c *KluctlProjectContext) buildCloneDir(u git_url.GitUrl, ref string) (string, error) { - if ref == "" { - ref = "HEAD" - } - ref = strings.ReplaceAll(ref, "/", "-") - ref = strings.ReplaceAll(ref, "\\", "-") - urlPath := filepath.FromSlash(u.Path) - baseName := filepath.Base(urlPath) - urlHash := utils.Sha256String(fmt.Sprintf("%s:%s", u.Host, u.Path))[:16] - cloneDir, err := securejoin.SecureJoin(c.TmpDir, filepath.Join(fmt.Sprintf("%s-%s", baseName, urlHash), ref)) - if err != nil { - return "", err - } - log.Tracef("buildCloneDir: ref=%s, urlPath=%s, baseName=%s, urlHash=%s, cloneDir=%s", ref, urlPath, baseName, urlHash, cloneDir) - return cloneDir, nil -} - -func (c *KluctlProjectContext) cloneGitProject(ctx context.Context, gitProject *types2.GitProject, defaultGitSubDir string, doAddInvolvedRepo bool, doLock bool) (result gitProjectInfo, err error) { +func (c *LoadedKluctlProject) cloneGitProject(ctx context.Context, gitProject *types2.GitProject, targetDir string, doLock bool) (info git.GitRepoInfo, err error) { err = os.MkdirAll(filepath.Join(c.TmpDir, "git"), 0o700) if err != nil { return } - targetDir, err := c.buildCloneDir(gitProject.Url, gitProject.Ref) - if err != nil { - return - } - - subDir := gitProject.SubDir - if subDir == "" { - subDir = defaultGitSubDir - } - dir := targetDir - if subDir != "" { - dir, err = securejoin.SecureJoin(dir, subDir) - if err != nil { - return gitProjectInfo{}, err - } - } - mr, ok := c.mirroredRepos[gitProject.Url.NormalizedRepoKey()] if !ok { mr, err = git.NewMirroredGitRepo(gitProject.Url) @@ -182,42 +139,35 @@ func (c *KluctlProjectContext) cloneGitProject(ctx context.Context, gitProject * return } - ri, err := git.GetGitRepoInfo(targetDir) - if err != nil { - return - } - - result.url = gitProject.Url - result.ref = ri.CheckedOutRef - result.commit = ri.CheckedOutCommit - result.dir = dir - - if doAddInvolvedRepo { - c.addInvolvedRepo(result.url, result.ref, map[string]string{ - result.ref: result.commit, - }) - } - + info, err = git.GetGitRepoInfo(targetDir) return } -func (c *KluctlProjectContext) localProject(dir string) gitProjectInfo { - return gitProjectInfo{ - dir: dir, +func (c *LoadedKluctlProject) buildCloneDir(u git_url.GitUrl, ref string) (string, error) { + baseDir := c.TmpDir + if c.archiveDir != "" { + baseDir = c.archiveDir } -} + baseDir = filepath.Join(baseDir, "git") -func (c *KluctlProjectContext) cloneKluctlProject(ctx context.Context) (gitProjectInfo, error) { - if c.loadArgs.ProjectUrl == nil { - return c.localProject(c.loadArgs.ProjectDir), nil + if ref == "" { + ref = "HEAD" } - return c.cloneGitProject(ctx, &types2.GitProject{ - Url: *c.loadArgs.ProjectUrl, - Ref: c.loadArgs.ProjectRef, - }, "", true, true) + ref = strings.ReplaceAll(ref, "/", "-") + ref = strings.ReplaceAll(ref, "\\", "-") + urlPath := filepath.FromSlash(u.Path) + baseName := filepath.Base(urlPath) + urlHash := utils.Sha256String(fmt.Sprintf("%s:%s", u.Host, u.Path))[:16] + cloneDir := filepath.Join(fmt.Sprintf("%s-%s", baseName, urlHash), ref) + cloneDir = filepath.Join(baseDir, cloneDir) + err := utils.CheckInDir(baseDir, cloneDir) + if err != nil { + return "", err + } + return cloneDir, nil } -func (c *KluctlProjectContext) addInvolvedRepo(u git_url.GitUrl, refPattern string, refs map[string]string) { +func (c *LoadedKluctlProject) addInvolvedRepo(u git_url.GitUrl, refPattern string, refs map[string]string) { repoKey := u.NormalizedRepoKey() irs, _ := c.involvedRepos[repoKey] e := &types2.InvolvedRepo{ diff --git a/pkg/kluctl_project/load.go b/pkg/kluctl_project/load.go index aa40f45fb..56a1da171 100644 --- a/pkg/kluctl_project/load.go +++ b/pkg/kluctl_project/load.go @@ -1,184 +1,34 @@ package kluctl_project import ( - "archive/tar" - "compress/gzip" "context" "fmt" + "github.com/kluctl/kluctl/v2/pkg/git" "github.com/kluctl/kluctl/v2/pkg/jinja2" - types2 "github.com/kluctl/kluctl/v2/pkg/types" - "github.com/kluctl/kluctl/v2/pkg/utils" - "github.com/kluctl/kluctl/v2/pkg/yaml" - log "github.com/sirupsen/logrus" - "io/ioutil" - "os" - "path/filepath" - "strings" - "time" + "github.com/kluctl/kluctl/v2/pkg/types" ) -func (c *KluctlProjectContext) mergeClustersDirs(mergedClustersDir string, clustersInfos []gitProjectInfo) error { - err := os.MkdirAll(mergedClustersDir, 0o700) - if err != nil { - return err - } - - for _, ci := range clustersInfos { - if !utils.IsDirectory(ci.dir) { - log.Warningf("Cluster dir '%s' does not exist", ci.dir) - continue - } - files, err := ioutil.ReadDir(ci.dir) - if err != nil { - return err - } - for _, fi := range files { - p := filepath.Join(ci.dir, fi.Name()) - if utils.IsFile(p) { - err = utils.CopyFile(p, filepath.Join(mergedClustersDir, fi.Name())) - if err != nil { - return err - } - } - } - } - return nil -} - -func (c *KluctlProjectContext) getConfigPath(projectDir string) string { - configPath := c.loadArgs.ProjectConfig - if configPath == "" { - p := yaml.FixPathExt(filepath.Join(projectDir, ".kluctl.yml")) - if utils.IsFile(p) { - configPath = p - } - } - return configPath -} +func LoadKluctlProject(ctx context.Context, args LoadKluctlProjectArgs, tmpDir string, j2 *jinja2.Jinja2) (*LoadedKluctlProject, error) { + p := &LoadedKluctlProject{ + loadArgs: args, + TmpDir: tmpDir, + J2: j2, -func (c *KluctlProjectContext) load(ctx context.Context, allowGit bool) error { - kluctlProjectInfo, err := c.cloneKluctlProject(ctx) - if err != nil { - return err + involvedRepos: map[string][]types.InvolvedRepo{}, + mirroredRepos: map[string]*git.MirroredGitRepo{}, } - configPath := c.getConfigPath(kluctlProjectInfo.dir) - - if configPath != "" { - err = yaml.ReadYamlFile(configPath, &c.Config) - if err != nil { - return err - } - } - - if allowGit { - err = c.updateGitCaches(ctx) - if err != nil { - return err - } - } - - doClone := func(ep *types2.ExternalProject, defaultGitSubDir string, localDir string) (gitProjectInfo, error) { - if localDir != "" { - return c.localProject(localDir), nil - } - - if ep == nil { - // no ExternalProject provided, so we point into the kluctl project + defaultGitSubDir - p := kluctlProjectInfo.dir - if defaultGitSubDir != "" { - p = filepath.Join(p, defaultGitSubDir) - } - return c.localProject(p), nil - } - - if ep.Project != nil { - // pointing to an actual external project, so let's try to clone it - if !allowGit { - return gitProjectInfo{}, fmt.Errorf("tried to load something from git while it was not allowed") - } - return c.cloneGitProject(ctx, ep.Project, defaultGitSubDir, true, true) - } - - // ExternalProject was provided but without an external repo url, so point into the kluctl project. - // We also allow to leave the kluctl project dir in case RepoRoot is provided via loadArgs, as long as - // the pointed to directory does not leave the RepoRoot - - repoRoot := c.loadArgs.RepoRoot - if repoRoot == "" { - // don't allow to leave the project dir - repoRoot = kluctlProjectInfo.dir - } - - p := filepath.Join(kluctlProjectInfo.dir, *ep.Path) - err = utils.CheckInDir(repoRoot, p) - if err != nil { - if c.loadArgs.RepoRoot == "" { - return gitProjectInfo{}, fmt.Errorf("it looks like your kluctl project is not part of a Git repository, which means you are not allowed to use paths that leave your kluctl project") - } - return gitProjectInfo{}, fmt.Errorf("path '%s' is not inside git project root '%s': %w", p, repoRoot, err) - } - return c.localProject(p), nil - } - - deploymentInfo, err := doClone(c.Config.Deployment, "", c.loadArgs.LocalDeployment) - if err != nil { - return err - } - sealedSecretsInfo, err := doClone(c.Config.SealedSecrets, ".sealed-secrets", c.loadArgs.LocalSealedSecrets) - if err != nil { - return err - } - var clustersInfos []gitProjectInfo - if c.loadArgs.LocalClusters != "" { - clustersInfos = append(clustersInfos, c.localProject(c.loadArgs.LocalClusters)) - } else if len(c.Config.Clusters.Projects) != 0 { - for _, ep := range c.Config.Clusters.Projects { - info, err := doClone(&ep, "clusters", "") - if err != nil { - return err - } - clustersInfos = append(clustersInfos, info) - } - } else { - ci, err := doClone(nil, "clusters", "") - if err != nil { - return err - } - clustersInfos = append(clustersInfos, ci) - } - - mergedClustersDir := filepath.Join(c.TmpDir, "merged-clusters") - err = c.mergeClustersDirs(mergedClustersDir, clustersInfos) - if err != nil { - return err - } - - c.ProjectDir = kluctlProjectInfo.dir - c.DeploymentDir = deploymentInfo.dir - c.ClustersDir = mergedClustersDir - c.SealedSecretsDir = sealedSecretsInfo.dir - - return nil -} - -func LoadKluctlProject(ctx context.Context, args LoadKluctlProjectArgs, tmpDir string, j2 *jinja2.Jinja2) (*KluctlProjectContext, error) { if args.FromArchive != "" { if args.ProjectUrl != nil || args.ProjectRef != "" || args.ProjectConfig != "" || args.LocalClusters != "" || args.LocalDeployment != "" || args.LocalSealedSecrets != "" { return nil, fmt.Errorf("--from-archive can not be combined with any other project related option") } - project, err := loadKluctlProjectFromArchive(args, tmpDir, j2) - if err != nil { - return nil, err - } - err = project.load(ctx, false) + err := p.loadFromArchive(ctx) if err != nil { return nil, err } - return project, nil + return p, nil } else { - p := NewKluctlProjectContext(args, tmpDir, j2) - err := p.load(ctx, true) + err := p.loadKluctlProject(ctx) if err != nil { return nil, err } @@ -189,116 +39,3 @@ func LoadKluctlProject(ctx context.Context, args LoadKluctlProjectArgs, tmpDir s return p, nil } } - -func loadKluctlProjectFromArchive(args LoadKluctlProjectArgs, tmpDir string, j2 *jinja2.Jinja2) (*KluctlProjectContext, error) { - var dir string - if utils.IsFile(args.FromArchive) { - err := utils.ExtractTarGzFile(args.FromArchive, tmpDir) - if err != nil { - return nil, fmt.Errorf("failed to extract archive %v: %w", args.FromArchive, err) - } - dir = tmpDir - } else { - dir = args.FromArchive - } - - var metdataPath string - if args.FromArchiveMetadata != "" { - metdataPath = args.FromArchiveMetadata - } else { - metdataPath = yaml.FixPathExt(filepath.Join(dir, "metadata.yml")) - } - - var metadata types2.ProjectMetadata - err := yaml.ReadYamlFile(metdataPath, &metadata) - if err != nil { - return nil, err - } - - p := NewKluctlProjectContext(LoadKluctlProjectArgs{ - ProjectConfig: yaml.FixPathExt(filepath.Join(dir, ".kluctl.yml")), - LocalClusters: filepath.Join(dir, "clusters"), - LocalDeployment: filepath.Join(dir, "deployment"), - LocalSealedSecrets: filepath.Join(dir, "sealed-secrets"), - }, dir, j2) - p.involvedRepos = metadata.InvolvedRepos - p.DynamicTargets = metadata.Targets - return p, nil -} - -func (c *KluctlProjectContext) GetMetadata() *types2.ProjectMetadata { - md := &types2.ProjectMetadata{ - InvolvedRepos: c.involvedRepos, - Targets: c.DynamicTargets, - } - return md -} - -func (c *KluctlProjectContext) CreateTGZArchive(archivePath string, embedMetadata bool) error { - f, err := os.Create(archivePath) - if err != nil { - return err - } - defer f.Close() - gz := gzip.NewWriter(f) - defer gz.Close() - tw := tar.NewWriter(gz) - defer tw.Close() - - filter := func(h *tar.Header, size int64) (*tar.Header, error) { - if strings.HasSuffix(strings.ToLower(h.Name), ".git") { - return nil, nil - } - h.Uid = 0 - h.Gid = 0 - h.Uname = "" - h.Gname = "" - h.ModTime = time.Time{} - h.ChangeTime = time.Time{} - h.AccessTime = time.Time{} - return h, nil - } - - if embedMetadata { - md := c.GetMetadata() - mdStr, err := yaml.WriteYamlBytes(md) - if err != nil { - return err - } - - err = tw.WriteHeader(&tar.Header{ - Name: "metadata.yaml", - Size: int64(len(mdStr)), - Mode: 0o666 | tar.TypeReg, - }) - if err != nil { - return err - } - _, err = tw.Write(mdStr) - if err != nil { - return err - } - } - - if err = utils.AddToTar(tw, c.getConfigPath(c.ProjectDir), yaml.FixNameExt(c.ProjectDir, ".kluctl.yml"), filter); err != nil { - return err - } - if err = utils.AddToTar(tw, c.ProjectDir, "kluctl-project", filter); err != nil { - return err - } - if err = utils.AddToTar(tw, c.DeploymentDir, "deployment", filter); err != nil { - return err - } - if utils.Exists(c.ClustersDir) { - if err = utils.AddToTar(tw, c.ClustersDir, "clusters", filter); err != nil { - return err - } - } - if utils.Exists(c.SealedSecretsDir) { - if err = utils.AddToTar(tw, c.SealedSecretsDir, "sealed-secrets", filter); err != nil { - return err - } - } - - return nil -} diff --git a/pkg/kluctl_project/project.go b/pkg/kluctl_project/project.go index 0357f5d34..06d555e00 100644 --- a/pkg/kluctl_project/project.go +++ b/pkg/kluctl_project/project.go @@ -3,62 +3,42 @@ package kluctl_project import ( "fmt" "github.com/kluctl/kluctl/v2/pkg/git" - auth2 "github.com/kluctl/kluctl/v2/pkg/git/auth" - git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/jinja2" - "github.com/kluctl/kluctl/v2/pkg/types" + types2 "github.com/kluctl/kluctl/v2/pkg/types" "regexp" - "time" ) -type LoadKluctlProjectArgs struct { - RepoRoot string - ProjectDir string - ProjectUrl *git_url.GitUrl - ProjectRef string - ProjectConfig string - LocalClusters string - LocalDeployment string - LocalSealedSecrets string - FromArchive string - FromArchiveMetadata string - - GitAuthProviders *auth2.GitAuthProviders - - GitUpdateInterval time.Duration -} - -type KluctlProjectContext struct { +type LoadedKluctlProject struct { loadArgs LoadKluctlProjectArgs - TmpDir string - Config types.KluctlProject + TmpDir string + archiveDir string + + projectRootDir string + ProjectDir string - ProjectDir string DeploymentDir string ClustersDir string - SealedSecretsDir string - - involvedRepos map[string][]types.InvolvedRepo - DynamicTargets []*types.DynamicTarget + sealedSecretsDir string + involvedRepos map[string][]types2.InvolvedRepo mirroredRepos map[string]*git.MirroredGitRepo + Config types2.KluctlProject + DynamicTargets []*types2.DynamicTarget + J2 *jinja2.Jinja2 } -func NewKluctlProjectContext(loadArgs LoadKluctlProjectArgs, tmpDir string, j2 *jinja2.Jinja2) *KluctlProjectContext { - o := &KluctlProjectContext{ - loadArgs: loadArgs, - TmpDir: tmpDir, - involvedRepos: make(map[string][]types.InvolvedRepo), - mirroredRepos: make(map[string]*git.MirroredGitRepo), - J2: j2, +func (c *LoadedKluctlProject) GetMetadata() *types2.ProjectMetadata { + md := &types2.ProjectMetadata{ + InvolvedRepos: c.involvedRepos, + Targets: c.DynamicTargets, } - return o + return md } -func (c *KluctlProjectContext) FindBaseTarget(name string) (*types.Target, error) { +func (c *LoadedKluctlProject) FindBaseTarget(name string) (*types2.Target, error) { for _, target := range c.Config.Targets { if target.Name == name { return target, nil @@ -67,7 +47,7 @@ func (c *KluctlProjectContext) FindBaseTarget(name string) (*types.Target, error return nil, fmt.Errorf("target %s not existent in kluctl project config", name) } -func (c *KluctlProjectContext) FindDynamicTarget(name string) (*types.DynamicTarget, error) { +func (c *LoadedKluctlProject) FindDynamicTarget(name string) (*types2.DynamicTarget, error) { for _, target := range c.DynamicTargets { if target.Target.Name == name { return target, nil @@ -76,12 +56,12 @@ func (c *KluctlProjectContext) FindDynamicTarget(name string) (*types.DynamicTar return nil, fmt.Errorf("target %s not existent in kluctl project config", name) } -func (c *KluctlProjectContext) LoadClusterConfig(clusterName string) (*types.ClusterConfig, error) { - return types.LoadClusterConfig(c.ClustersDir, clusterName) +func (c *LoadedKluctlProject) LoadClusterConfig(clusterName string) (*types2.ClusterConfig, error) { + return types2.LoadClusterConfig(c.ClustersDir, clusterName) } -func (c *KluctlProjectContext) CheckDynamicArg(target *types.Target, argName string, argValue string) error { - var dynArg *types.DynamicArg +func (c *LoadedKluctlProject) CheckDynamicArg(target *types2.Target, argName string, argValue string) error { + var dynArg *types2.DynamicArg for _, x := range target.DynamicArgs { if x.Name == argName { dynArg = &x diff --git a/pkg/kluctl_project/project_archive.go b/pkg/kluctl_project/project_archive.go new file mode 100644 index 000000000..864e6a9c2 --- /dev/null +++ b/pkg/kluctl_project/project_archive.go @@ -0,0 +1,129 @@ +package kluctl_project + +import ( + "archive/tar" + "compress/gzip" + "context" + "fmt" + types2 "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/yaml" + "os" + "path/filepath" + "strings" + "time" +) + +func (c *LoadedKluctlProject) loadFromArchive(ctx context.Context) error { + var dir string + if utils.IsFile(c.loadArgs.FromArchive) { + dir = filepath.Join(c.TmpDir, "archive") + err := utils.ExtractTarGzFile(c.loadArgs.FromArchive, dir) + if err != nil { + return fmt.Errorf("failed to extract archive %v: %w", c.loadArgs.FromArchive, err) + } + } else { + dir = c.loadArgs.FromArchive + } + + var pmdPath string + if c.loadArgs.FromArchiveMetadata != "" { + pmdPath = c.loadArgs.FromArchiveMetadata + } else { + pmdPath = yaml.FixPathExt(filepath.Join(dir, "project-metadata.yml")) + } + + var pmd types2.ProjectMetadata + err := yaml.ReadYamlFile(pmdPath, &pmd) + if err != nil { + return err + } + + var amd types2.ArchiveMetadata + err = yaml.ReadYamlFile(yaml.FixPathExt(filepath.Join(dir, "archive-metadata.yml")), &amd) + if err != nil { + return err + } + + c.loadArgs.RepoRoot = filepath.Join(dir, amd.ProjectRootDir) + c.loadArgs.ProjectDir = filepath.Join(dir, amd.ProjectRootDir, amd.ProjectSubDir) + + err = utils.CheckInDir(dir, c.loadArgs.RepoRoot) + if err != nil { + return err + } + err = utils.CheckInDir(dir, c.loadArgs.ProjectDir) + if err != nil { + return err + } + + c.archiveDir = dir + c.involvedRepos = pmd.InvolvedRepos + c.DynamicTargets = pmd.Targets + + err = c.loadKluctlProject(ctx) + if err != nil { + return err + } + return nil +} + +func (c *LoadedKluctlProject) WriteArchive(archivePath string, embedProjectMetadata bool) error { + f, err := os.Create(archivePath) + if err != nil { + return err + } + defer f.Close() + gz := gzip.NewWriter(f) + defer gz.Close() + tw := tar.NewWriter(gz) + defer tw.Close() + + filter := func(h *tar.Header, size int64) (*tar.Header, error) { + if strings.HasSuffix(strings.ToLower(h.Name), ".git") { + return nil, nil + } + h.Uid = 0 + h.Gid = 0 + h.Uname = "" + h.Gname = "" + h.ModTime = time.Time{} + h.ChangeTime = time.Time{} + h.AccessTime = time.Time{} + return h, nil + } + + if embedProjectMetadata { + err = yaml.WriteYamlToTar(tw, c.GetMetadata(), "project-metadata.yaml") + if err != nil { + return nil + } + } + + amd := types2.ArchiveMetadata{ + ProjectRootDir: "kluctl-project", + } + + amd.ProjectSubDir, err = filepath.Rel(c.projectRootDir, c.ProjectDir) + if err != nil { + return err + } + + err = yaml.WriteYamlToTar(tw, &amd, "archive-metadata.yaml") + if err != nil { + return nil + } + + if err = utils.AddToTar(tw, c.projectRootDir, "kluctl-project", filter); err != nil { + return err + } + gitReposBaseDir := filepath.Join(c.TmpDir, "git") + if utils.Exists(gitReposBaseDir) { + err = utils.AddToTar(tw, gitReposBaseDir, "git", filter) + if err != nil { + return err + } + } + + return nil +} diff --git a/pkg/kluctl_project/project_load.go b/pkg/kluctl_project/project_load.go new file mode 100644 index 000000000..75f24ac15 --- /dev/null +++ b/pkg/kluctl_project/project_load.go @@ -0,0 +1,259 @@ +package kluctl_project + +import ( + "context" + "fmt" + "github.com/kluctl/kluctl/v2/pkg/git" + auth2 "github.com/kluctl/kluctl/v2/pkg/git/auth" + git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" + types2 "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/yaml" + log "github.com/sirupsen/logrus" + "io/ioutil" + "os" + "path/filepath" + "time" +) + +type LoadKluctlProjectArgs struct { + RepoRoot string + ProjectDir string + ProjectUrl *git_url.GitUrl + ProjectRef string + ProjectConfig string + LocalClusters string + LocalDeployment string + LocalSealedSecrets string + FromArchive string + FromArchiveMetadata string + + AllowGitClone bool + GitAuthProviders *auth2.GitAuthProviders + + GitUpdateInterval time.Duration +} + +type gitProjectInfo struct { + url git_url.GitUrl + ref string + commit string + repoRoot string + dir string +} + +func (c *LoadedKluctlProject) getConfigPath() string { + configPath := c.loadArgs.ProjectConfig + if configPath == "" { + p := yaml.FixPathExt(filepath.Join(c.ProjectDir, ".kluctl.yml")) + if utils.IsFile(p) { + configPath = p + } + } + return configPath +} + +func (c *LoadedKluctlProject) localProject(dir string) gitProjectInfo { + return gitProjectInfo{ + dir: dir, + } +} + +func (c *LoadedKluctlProject) loadGitProject(ctx context.Context, gitProject *types2.GitProject, defaultSubDir string, doLock bool, doAddInvolvedRepo bool) (ret gitProjectInfo, err error) { + cloneDir, err := c.buildCloneDir(gitProject.Url, gitProject.Ref) + if err != nil { + return + } + + if c.archiveDir != "" { + var md types2.GitRepoMetadata + err = yaml.ReadYamlFile(filepath.Join(cloneDir, ".git-metadata.yaml"), &md) + if err != nil { + return + } + ret.url = gitProject.Url + ret.ref = gitProject.Ref + ret.commit = md.Commit + ret.repoRoot = cloneDir + } else { + if !c.loadArgs.AllowGitClone { + err = fmt.Errorf("tried to load an external project from git, which is not allowed") + return + } + + var ri git.GitRepoInfo + ri, err = c.cloneGitProject(ctx, gitProject, cloneDir, doLock) + if err != nil { + return + } + + var md types2.GitRepoMetadata + md.Ref = ri.CheckedOutRef + md.Commit = ri.CheckedOutCommit + err = yaml.WriteYamlFile(filepath.Join(cloneDir, ".git-metadata.yaml"), &md) + if err != nil { + return gitProjectInfo{}, err + } + + ret.url = gitProject.Url + ret.ref = ri.CheckedOutRef + ret.commit = ri.CheckedOutCommit + ret.repoRoot = cloneDir + + if doAddInvolvedRepo { + c.addInvolvedRepo(ret.url, ret.ref, map[string]string{ + ret.ref: ret.commit, + }) + } + } + + subDir := gitProject.SubDir + if subDir == "" { + subDir = defaultSubDir + } + ret.dir = filepath.Join(ret.repoRoot, subDir) + err = utils.CheckInDir(ret.repoRoot, ret.dir) + if err != nil { + return + } + + return +} + +func (c *LoadedKluctlProject) loadExternalProject(ctx context.Context, ep *types2.ExternalProject, defaultGitSubDir string, localDir string) (gitProjectInfo, error) { + if localDir != "" { + return c.localProject(localDir), nil + } + + if ep == nil { + // no ExternalProject provided, so we point into the kluctl project + defaultGitSubDir + p := filepath.Join(c.ProjectDir, defaultGitSubDir) + return c.localProject(p), nil + } + + if ep.Project != nil { + // pointing to an actual external project, so let's try to clone it + return c.loadGitProject(ctx, ep.Project, defaultGitSubDir, true, true) + } + + // ExternalProject was provided but without an external repo url, so point into the kluctl project. + // We also allow to leave the kluctl project dir but limit it to the git project + + p := filepath.Join(c.ProjectDir, *ep.Path) + err := utils.CheckInDir(c.projectRootDir, p) + if err != nil { + return gitProjectInfo{}, fmt.Errorf("path '%s' is not inside git project root '%s': %w", p, c.projectRootDir, err) + } + return c.localProject(p), nil +} + +func (c *LoadedKluctlProject) loadKluctlProject(ctx context.Context) error { + var err error + + if c.loadArgs.ProjectUrl == nil { + c.projectRootDir = c.loadArgs.RepoRoot + c.ProjectDir = c.loadArgs.ProjectDir + err = utils.CheckInDir(c.projectRootDir, c.ProjectDir) + if err != nil { + return err + } + } else { + gi, err := c.loadGitProject(ctx, &types2.GitProject{ + Url: *c.loadArgs.ProjectUrl, + Ref: c.loadArgs.ProjectRef, + }, "", true, true) + if err != nil { + return err + } + c.projectRootDir = gi.repoRoot + c.ProjectDir = gi.dir + } + + configPath := c.getConfigPath() + + if configPath != "" { + err = yaml.ReadYamlFile(configPath, &c.Config) + if err != nil { + return err + } + } + + if c.loadArgs.AllowGitClone { + err = os.MkdirAll(filepath.Join(c.TmpDir, "git"), 0o755) + if err != nil { + return err + } + + err = c.updateGitCaches(ctx) + if err != nil { + return err + } + } + + deploymentInfo, err := c.loadExternalProject(ctx, c.Config.Deployment, "", c.loadArgs.LocalDeployment) + if err != nil { + return err + } + sealedSecretsInfo, err := c.loadExternalProject(ctx, c.Config.SealedSecrets, ".sealed-secrets", c.loadArgs.LocalSealedSecrets) + if err != nil { + return err + } + var clustersInfos []gitProjectInfo + if c.loadArgs.LocalClusters != "" { + clustersInfos = append(clustersInfos, c.localProject(c.loadArgs.LocalClusters)) + } else if len(c.Config.Clusters.Projects) != 0 { + for _, ep := range c.Config.Clusters.Projects { + info, err := c.loadExternalProject(ctx, &ep, "clusters", "") + if err != nil { + return err + } + clustersInfos = append(clustersInfos, info) + } + } else { + ci, err := c.loadExternalProject(ctx, nil, "clusters", "") + if err != nil { + return err + } + clustersInfos = append(clustersInfos, ci) + } + + mergedClustersDir := filepath.Join(c.TmpDir, "merged-clusters") + err = c.mergeClustersDirs(mergedClustersDir, clustersInfos) + if err != nil { + return err + } + + c.DeploymentDir = deploymentInfo.dir + c.ClustersDir = mergedClustersDir + c.sealedSecretsDir = sealedSecretsInfo.dir + + return nil +} + +func (c *LoadedKluctlProject) mergeClustersDirs(mergedClustersDir string, clustersInfos []gitProjectInfo) error { + err := os.MkdirAll(mergedClustersDir, 0o700) + if err != nil { + return err + } + + for _, ci := range clustersInfos { + if !utils.IsDirectory(ci.dir) { + log.Warningf("Cluster dir '%s' does not exist", ci.dir) + continue + } + files, err := ioutil.ReadDir(ci.dir) + if err != nil { + return err + } + for _, fi := range files { + p := filepath.Join(ci.dir, fi.Name()) + if utils.IsFile(p) { + err = utils.CopyFile(p, filepath.Join(mergedClustersDir, fi.Name())) + if err != nil { + return err + } + } + } + } + return nil +} diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index 745b55131..e2038eb75 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -13,7 +13,7 @@ import ( ) type TargetContext struct { - KluctlProject *KluctlProjectContext + KluctlProject *LoadedKluctlProject Target *types.Target ClusterConfig *types.ClusterConfig K *k8s.K8sCluster @@ -21,7 +21,7 @@ type TargetContext struct { DeploymentCollection *deployment.DeploymentCollection } -func (p *KluctlProjectContext) NewTargetContext(clientConfigGetter func(context string) (*rest.Config, error), targetName string, clusterName string, dryRun bool, args map[string]string, forSeal bool, images *deployment.Images, inclusion *utils.Inclusion, renderOutputDir string) (*TargetContext, error) { +func (p *LoadedKluctlProject) NewTargetContext(clientConfigGetter func(context string) (*rest.Config, error), targetName string, clusterName string, dryRun bool, args map[string]string, forSeal bool, images *deployment.Images, inclusion *utils.Inclusion, renderOutputDir string) (*TargetContext, error) { deploymentDir, err := filepath.Abs(p.DeploymentDir) if err != nil { return nil, err @@ -106,7 +106,7 @@ func (p *KluctlProjectContext) NewTargetContext(clientConfigGetter func(context } varsCtx.UpdateChild("target", targetVars) - d, err := deployment.NewDeploymentProject(k, varsCtx, deploymentDir, p.SealedSecretsDir, nil) + d, err := deployment.NewDeploymentProject(k, varsCtx, deploymentDir, p.sealedSecretsDir, nil) if err != nil { return nil, err } diff --git a/pkg/kluctl_project/load_targets.go b/pkg/kluctl_project/targets.go similarity index 90% rename from pkg/kluctl_project/load_targets.go rename to pkg/kluctl_project/targets.go index 1d48ed529..426560204 100644 --- a/pkg/kluctl_project/load_targets.go +++ b/pkg/kluctl_project/targets.go @@ -27,7 +27,7 @@ type dynamicTargetInfo struct { defaultBranch string } -func (c *KluctlProjectContext) loadTargets(ctx context.Context) error { +func (c *LoadedKluctlProject) loadTargets(ctx context.Context) error { targetNames := make(map[string]bool) c.DynamicTargets = nil @@ -77,7 +77,7 @@ func (c *KluctlProjectContext) loadTargets(ctx context.Context) error { return nil } -func (c *KluctlProjectContext) renderTarget(target *types.Target) (*types.Target, error) { +func (c *LoadedKluctlProject) renderTarget(target *types.Target) (*types.Target, error) { // Try rendering the target multiple times, until all values can be rendered successfully. This allows the target // to reference itself in complex ways. We'll also try loading the cluster vars in each iteration. @@ -111,7 +111,7 @@ func (c *KluctlProjectContext) renderTarget(target *types.Target) (*types.Target return curTarget, nil } -func (c *KluctlProjectContext) prepareDynamicTargets(baseTarget *types.Target) ([]*dynamicTargetInfo, error) { +func (c *LoadedKluctlProject) prepareDynamicTargets(baseTarget *types.Target) ([]*dynamicTargetInfo, error) { if baseTarget.TargetConfig != nil && baseTarget.TargetConfig.Project != nil { return c.prepareDynamicTargetsExternal(baseTarget) } else { @@ -119,7 +119,7 @@ func (c *KluctlProjectContext) prepareDynamicTargets(baseTarget *types.Target) ( } } -func (c *KluctlProjectContext) prepareDynamicTargetsSimple(baseTarget *types.Target) ([]*dynamicTargetInfo, error) { +func (c *LoadedKluctlProject) prepareDynamicTargetsSimple(baseTarget *types.Target) ([]*dynamicTargetInfo, error) { if baseTarget.TargetConfig != nil { if baseTarget.TargetConfig.Ref != nil || baseTarget.TargetConfig.RefPattern != nil { return nil, fmt.Errorf("'ref' and/or 'refPattern' are not allowed for non-external dynamic targets") @@ -134,7 +134,7 @@ func (c *KluctlProjectContext) prepareDynamicTargetsSimple(baseTarget *types.Tar return dynamicTargets, nil } -func (c *KluctlProjectContext) prepareDynamicTargetsExternal(baseTarget *types.Target) ([]*dynamicTargetInfo, error) { +func (c *LoadedKluctlProject) prepareDynamicTargetsExternal(baseTarget *types.Target) ([]*dynamicTargetInfo, error) { mr, ok := c.mirroredRepos[baseTarget.TargetConfig.Project.Url.NormalizedRepoKey()] if !ok { return nil, fmt.Errorf("repo not found in mirroredRepos, this is unexpected and probably a bug") @@ -196,7 +196,7 @@ func (c *KluctlProjectContext) prepareDynamicTargetsExternal(baseTarget *types.T return dynamicTargets, nil } -func (c *KluctlProjectContext) matchRef(s string, pattern string) (bool, string, error) { +func (c *LoadedKluctlProject) matchRef(s string, pattern string) (bool, string, error) { if strings.HasPrefix(pattern, "refs/") { p, err := regexp.Compile(fmt.Sprintf("^%s$", pattern)) if err != nil { @@ -221,7 +221,7 @@ func (c *KluctlProjectContext) matchRef(s string, pattern string) (bool, string, } } -func (c *KluctlProjectContext) cloneDynamicTargets(ctx context.Context, dynamicTargets []*dynamicTargetInfo) error { +func (c *LoadedKluctlProject) cloneDynamicTargets(ctx context.Context, dynamicTargets []*dynamicTargetInfo) error { wp := utils.NewDebuggerAwareWorkerPool(8) defer wp.StopWait(false) @@ -252,7 +252,7 @@ func (c *KluctlProjectContext) cloneDynamicTargets(ctx context.Context, dynamicT gitProject := *targetInfo.gitProject gitProject.Ref = *targetInfo.ref - gi, err := c.cloneGitProject(ctx, &gitProject, "", false, false) + gi, err := c.loadGitProject(ctx, &gitProject, "", false, false) mutex.Lock() defer mutex.Unlock() if err != nil { @@ -305,7 +305,7 @@ func (c *KluctlProjectContext) cloneDynamicTargets(ctx context.Context, dynamicT return nil } -func (c *KluctlProjectContext) buildDynamicTarget(targetInfo *dynamicTargetInfo) (*types.Target, error) { +func (c *LoadedKluctlProject) buildDynamicTarget(targetInfo *dynamicTargetInfo) (*types.Target, error) { var target types.Target err := utils.DeepCopy(&target, targetInfo.baseTarget) if err != nil { diff --git a/pkg/seal/secrets_loader.go b/pkg/seal/secrets_loader.go index ef0e37fe9..fb863224c 100644 --- a/pkg/seal/secrets_loader.go +++ b/pkg/seal/secrets_loader.go @@ -18,13 +18,13 @@ type usernamePassword struct { } type SecretsLoader struct { - project *kluctl_project.KluctlProjectContext + project *kluctl_project.LoadedKluctlProject secretsDir string credentialsCache map[string]usernamePassword } -func NewSecretsLoader(p *kluctl_project.KluctlProjectContext, secretsDir string) *SecretsLoader { +func NewSecretsLoader(p *kluctl_project.LoadedKluctlProject, secretsDir string) *SecretsLoader { return &SecretsLoader{ project: p, secretsDir: secretsDir, diff --git a/pkg/types/metadata.go b/pkg/types/metadata.go index 638f4bca9..0742a92ad 100644 --- a/pkg/types/metadata.go +++ b/pkg/types/metadata.go @@ -9,3 +9,13 @@ type ProjectMetadata struct { InvolvedRepos map[string][]InvolvedRepo `yaml:"involvedRepos"` Targets []*DynamicTarget `yaml:"targets"` } + +type GitRepoMetadata struct { + Ref string `yaml:"ref"` + Commit string `yaml:"commit"` +} + +type ArchiveMetadata struct { + ProjectRootDir string `yaml:"projectRootDir"` + ProjectSubDir string `yaml:"projectSubDir"` +} diff --git a/pkg/yaml/yaml.go b/pkg/yaml/yaml.go index 1726b4dae..2c24f990a 100644 --- a/pkg/yaml/yaml.go +++ b/pkg/yaml/yaml.go @@ -1,6 +1,7 @@ package yaml import ( + "archive/tar" "bytes" "encoding/json" "errors" @@ -163,6 +164,27 @@ func WriteYamlAllStream(w io.Writer, l []interface{}) error { return nil } +func WriteYamlToTar(tw *tar.Writer, o interface{}, name string) error { + str, err := WriteYamlBytes(o) + if err != nil { + return err + } + + err = tw.WriteHeader(&tar.Header{ + Name: name, + Size: int64(len(str)), + Mode: 0o666 | tar.TypeReg, + }) + if err != nil { + return err + } + _, err = tw.Write(str) + if err != nil { + return err + } + return nil +} + func ConvertYamlToJson(b []byte) ([]byte, error) { var x interface{} err := ReadYamlBytes(b, &x) From e524b81e4848277e78d5e6c3b0323ec9dc47ae3c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 6 May 2022 23:35:24 +0200 Subject: [PATCH 0769/2916] fix: Ignore GroupDiscoveryFailedError from discovery client Orphan apirources cause discovery errors that must be ignored. Luckily the returned list is still complete and valid. --- pkg/k8s/k8s_cluster.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index 33265da1d..5c901aafa 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -15,6 +15,7 @@ import ( v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/discovery" "k8s.io/client-go/discovery/cached/disk" "k8s.io/client-go/dynamic" corev1 "k8s.io/client-go/kubernetes/typed/core/v1" @@ -168,7 +169,7 @@ func (k *K8sCluster) updateResources(doLock bool) error { }{} _, arls, err := k.discovery.ServerGroupsAndResources() - if err != nil { + if err != nil && !discovery.IsGroupDiscoveryFailedError(err) { return err } for _, arl := range arls { @@ -202,6 +203,9 @@ func (k *K8sCluster) updateResources(doLock bool) error { } arls, err = k.discovery.ServerPreferredResources() + if err != nil && !discovery.IsGroupDiscoveryFailedError(err) { + return err + } for _, arl := range arls { for _, ar := range arl.APIResources { if strings.Index(ar.Name, "/") != -1 { From d8b92b9e35b772312ed8e9eb6f8bcefb4e09d79f Mon Sep 17 00:00:00 2001 From: Mathias Gebbe Date: Mon, 9 May 2022 09:46:30 +0200 Subject: [PATCH 0770/2916] feat: add sorted output for list images (#27) * feat: add sorted output for list images * refactor: remove empty line * feat: always sort image-list output but only if we have more than 1 image * refactor: remove null and len check No need to check for nil or > 1 as a nil slice is by definition an empty slice. * refactor: move sort image to SeenImages --- pkg/deployment/images.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/deployment/images.go b/pkg/deployment/images.go index 517087561..f7ef098e7 100644 --- a/pkg/deployment/images.go +++ b/pkg/deployment/images.go @@ -13,6 +13,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils/versions" "github.com/kluctl/kluctl/v2/pkg/yaml" "k8s.io/apimachinery/pkg/api/errors" + "sort" "strings" "sync" ) @@ -52,6 +53,9 @@ func (images *Images) SeenImages(simple bool) []types.FixedImage { ret = append(ret, fi) } } + sort.Slice(ret, func(i, j int) bool { + return ret[i].Image < ret[j].Image + }) return ret } From a29832737e9e9e24691f06f91c431dd05872e837 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sat, 7 May 2022 17:18:40 +0200 Subject: [PATCH 0771/2916] refactor: Use dynamic client for fetchCert and friends --- pkg/seal/fetch_cert.go | 74 ++++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 36 deletions(-) diff --git a/pkg/seal/fetch_cert.go b/pkg/seal/fetch_cert.go index 4e5e27d5a..5af09d342 100644 --- a/pkg/seal/fetch_cert.go +++ b/pkg/seal/fetch_cert.go @@ -1,60 +1,52 @@ package seal import ( - "context" "crypto/rsa" "errors" "fmt" "github.com/kluctl/kluctl/v2/pkg/k8s" + k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" log "github.com/sirupsen/logrus" "io/ioutil" v12 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - v1 "k8s.io/client-go/kubernetes/typed/core/v1" "k8s.io/client-go/util/cert" ) func fetchCert(k *k8s.K8sCluster, namespace string, controllerName string) (*rsa.PublicKey, error) { - var certData []byte - - err := k.WithCoreV1(func(client *v1.CoreV1Client) error { - s, err := openCertFromController(client, namespace, controllerName) - if err != nil { - if controllerName == "sealed-secrets-controller" { - s2, err2 := openCertFromController(client, namespace, "sealed-secrets") - if err2 == nil { - log.Warningf("Looks like you have sealed-secrets controller installed with name 'sealed-secrets', which comes from a legacy kluctl version that deployed it with a non-default name. Please consider re-deploying sealed-secrets operator manually.") - err = nil - s = s2 - } + certData, err := openCertFromController(k, namespace, controllerName) + if err != nil { + if controllerName == "sealed-secrets-controller" { + s2, err2 := openCertFromController(k, namespace, "sealed-secrets") + if err2 == nil { + log.Warningf("Looks like you have sealed-secrets controller installed with name 'sealed-secrets', which comes from a legacy kluctl version that deployed it with a non-default name. Please consider re-deploying sealed-secrets operator manually.") + err = nil + certData = s2 } + } + if err != nil { + log.Warningf("Failed to retrieve public certificate from sealed-secrets-controller, re-trying with bootstrap secret") + certData, err = openCertFromBootstrap(k, namespace) if err != nil { - log.Warningf("Failed to retrieve public certificate from sealed-secrets-controller, re-trying with bootstrap secret") - s, err = openCertFromBootstrap(client, namespace) - if err != nil { - return fmt.Errorf("failed to retrieve sealed secrets public key: %w", err) - } + return nil, fmt.Errorf("failed to retrieve sealed secrets public key: %w", err) } } - certData = s - return nil - }) - if err != nil { - return nil, err } - cert, err := parseKey(certData) - return cert, err + return parseKey(certData) } -func openCertFromBootstrap(c *v1.CoreV1Client, namespace string) ([]byte, error) { - cm, err := c.ConfigMaps(namespace).Get(context.Background(), configMapName, metav1.GetOptions{}) +func openCertFromBootstrap(k *k8s.K8sCluster, namespace string) ([]byte, error) { + ref := k8s2.NewObjectRef("", "v1", "ConfigMap", configMapName, namespace) + cm, _, err := k.GetSingleObject(ref) if err != nil { return nil, err } - v, ok := cm.Data[v12.TLSCertKey] + v, ok, err := cm.GetNestedString("data", v12.TLSCertKey) + if err != nil { + return nil, err + } if !ok { return nil, fmt.Errorf("%s key not found in ConfigMap %s", v12.TLSCertKey, configMapName) } @@ -62,12 +54,12 @@ func openCertFromBootstrap(c *v1.CoreV1Client, namespace string) ([]byte, error) return []byte(v), nil } -func openCertFromController(c v1.CoreV1Interface, namespace, name string) ([]byte, error) { - portName, err := getServicePortName(c, namespace, name) +func openCertFromController(k *k8s.K8sCluster, namespace, name string) ([]byte, error) { + portName, err := getServicePortName(k, namespace, name) if err != nil { return nil, err } - r, err := c.Services(namespace).ProxyGet("http", name, portName, "/v1/cert.pem", nil).Stream(context.Background()) + r, err := k.ProxyGet("http", namespace, name, portName, "/v1/cert.pem", nil) if err != nil { return nil, fmt.Errorf("cannot fetch certificate: %v", err) } @@ -81,12 +73,22 @@ func openCertFromController(c v1.CoreV1Interface, namespace, name string) ([]byt return cert, nil } -func getServicePortName(client v1.CoreV1Interface, namespace, serviceName string) (string, error) { - service, err := client.Services(namespace).Get(context.Background(), serviceName, metav1.GetOptions{}) +func getServicePortName(k *k8s.K8sCluster, namespace, serviceName string) (string, error) { + ref := k8s2.NewObjectRef("", "v1", "Service", serviceName, namespace) + service, _, err := k.GetSingleObject(ref) if err != nil { return "", fmt.Errorf("cannot get sealed secret service: %v", err) } - return service.Spec.Ports[0].Name, nil + + n, ok, err := service.GetNestedString("spec", "ports", 0, "name") + if err != nil { + return "", err + } + if !ok { + return "", fmt.Errorf("spec.ports[0].name not in service object %s", serviceName) + } + + return n, nil } func parseKey(data []byte) (*rsa.PublicKey, error) { From e6435625df446edd53151f29ed3a38ebe16da267 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sat, 7 May 2022 17:20:37 +0200 Subject: [PATCH 0772/2916] fix: Properly pass around and use context --- cmd/kluctl/args/project.go | 2 +- cmd/kluctl/commands/cmd_archive.go | 3 +- .../commands/cmd_check_image_updates.go | 18 +-- cmd/kluctl/commands/cmd_deploy.go | 2 +- cmd/kluctl/commands/cmd_diff.go | 2 +- cmd/kluctl/commands/cmd_downscale.go | 2 +- cmd/kluctl/commands/cmd_list_targets.go | 3 +- cmd/kluctl/commands/cmd_poke_images.go | 2 +- cmd/kluctl/commands/cmd_seal.go | 9 +- cmd/kluctl/commands/cmd_validate.go | 2 +- cmd/kluctl/commands/completion.go | 19 +-- cmd/kluctl/commands/utils.go | 26 ++-- pkg/deployment/commands/deploy.go | 7 +- pkg/deployment/commands/diff.go | 5 +- pkg/deployment/commands/downscale.go | 5 +- pkg/deployment/commands/poke_images.go | 5 +- pkg/deployment/commands/validate.go | 5 +- pkg/deployment/deployment_collection.go | 5 +- pkg/deployment/utils/apply_utils.go | 5 +- pkg/git/mirrored_repo.go | 35 +++-- pkg/k8s/k8s_cluster.go | 141 +++++++++++------- pkg/kluctl_project/git.go | 25 ++-- pkg/kluctl_project/load.go | 7 +- pkg/kluctl_project/project.go | 3 + pkg/kluctl_project/project_archive.go | 5 +- pkg/kluctl_project/project_load.go | 23 ++- pkg/kluctl_project/target_context.go | 11 +- pkg/kluctl_project/targets.go | 11 +- pkg/registries/registries.go | 10 +- 29 files changed, 229 insertions(+), 169 deletions(-) diff --git a/cmd/kluctl/args/project.go b/cmd/kluctl/args/project.go index 81ea403fa..120e1fabd 100644 --- a/cmd/kluctl/args/project.go +++ b/cmd/kluctl/args/project.go @@ -15,7 +15,7 @@ type ProjectFlags struct { OutputMetadata pathType `group:"project" help:"Specify the output path for the project metadata to be written to. When used with the 'archive' command, it will also cause the archive to not include the metadata.yaml file."` Cluster string `group:"project" help:"Specify/Override cluster"` - LoadTimeout time.Duration `group:"project" help:"Specify timeout for project loading. This will especially limit the time spent in git operations." default:"1m"` + Timeout time.Duration `group:"project" help:"Specify timeout for all operations, including loading of the project, all external api calls and waiting for readiness." default:"5m"` GitCacheUpdateInterval time.Duration `group:"project" help:"Specify the time to wait between git cache updates. Defaults to not wait at all and always updating caches."` } diff --git a/cmd/kluctl/commands/cmd_archive.go b/cmd/kluctl/commands/cmd_archive.go index 250571c51..a6a2f349d 100644 --- a/cmd/kluctl/commands/cmd_archive.go +++ b/cmd/kluctl/commands/cmd_archive.go @@ -1,6 +1,7 @@ package commands import ( + "context" "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/kluctl_project" ) @@ -16,7 +17,7 @@ func (cmd *archiveCmd) Help() string { } func (cmd *archiveCmd) Run() error { - return withKluctlProjectFromArgs(cmd.ProjectFlags, true, func(p *kluctl_project.LoadedKluctlProject) error { + return withKluctlProjectFromArgs(cmd.ProjectFlags, true, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { return p.WriteArchive(cmd.OutputArchive, cmd.ProjectFlags.OutputMetadata == "") }) } diff --git a/cmd/kluctl/commands/cmd_check_image_updates.go b/cmd/kluctl/commands/cmd_check_image_updates.go index 6fd22dfd7..c8956e026 100644 --- a/cmd/kluctl/commands/cmd_check_image_updates.go +++ b/cmd/kluctl/commands/cmd_check_image_updates.go @@ -3,7 +3,6 @@ package commands import ( "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/registries" - "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/versions" log "github.com/sirupsen/logrus" @@ -24,20 +23,19 @@ func (cmd *checkImageUpdatesCmd) Help() string { } func (cmd *checkImageUpdatesCmd) Run() error { - var renderedImages map[k8s.ObjectRef][]string ptArgs := projectTargetCommandArgs{ projectFlags: cmd.ProjectFlags, targetFlags: cmd.TargetFlags, } - err := withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { - renderedImages = ctx.targetCtx.DeploymentCollection.FindRenderedImages() - return nil + return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { + return runCheckImageUpdates(ctx) }) - if err != nil { - return err - } +} + +func runCheckImageUpdates(ctx *commandCtx) error { + renderedImages := ctx.targetCtx.DeploymentCollection.FindRenderedImages() - rh := registries.NewRegistryHelper() + rh := registries.NewRegistryHelper(ctx.ctx) wg := utils.NewWorkerPoolWithErrors(8) defer wg.StopWait(false) @@ -67,7 +65,7 @@ func (cmd *checkImageUpdatesCmd) Run() error { } } } - err = wg.StopWait(false) + err := wg.StopWait(false) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_deploy.go b/cmd/kluctl/commands/cmd_deploy.go index e234d8b49..c99d69319 100644 --- a/cmd/kluctl/commands/cmd_deploy.go +++ b/cmd/kluctl/commands/cmd_deploy.go @@ -62,7 +62,7 @@ func (cmd *deployCmd) runCmdDeploy(ctx *commandCtx) error { cb = nil } - result, err := cmd2.Run(ctx.targetCtx.K, cb) + result, err := cmd2.Run(ctx.ctx, ctx.targetCtx.K, cb) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_diff.go b/cmd/kluctl/commands/cmd_diff.go index 0f7f70b63..7b2efec67 100644 --- a/cmd/kluctl/commands/cmd_diff.go +++ b/cmd/kluctl/commands/cmd_diff.go @@ -43,7 +43,7 @@ func (cmd *diffCmd) Run() error { cmd2.IgnoreTags = cmd.IgnoreTags cmd2.IgnoreLabels = cmd.IgnoreLabels cmd2.IgnoreAnnotations = cmd.IgnoreAnnotations - result, err := cmd2.Run(ctx.targetCtx.K) + result, err := cmd2.Run(ctx.ctx, ctx.targetCtx.K) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_downscale.go b/cmd/kluctl/commands/cmd_downscale.go index 7df0a744b..78523c4b9 100644 --- a/cmd/kluctl/commands/cmd_downscale.go +++ b/cmd/kluctl/commands/cmd_downscale.go @@ -44,7 +44,7 @@ func (cmd *downscaleCmd) Run() error { cmd2 := commands.NewDownscaleCommand(ctx.targetCtx.DeploymentCollection) - result, err := cmd2.Run(ctx.targetCtx.K) + result, err := cmd2.Run(ctx.ctx, ctx.targetCtx.K) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_list_targets.go b/cmd/kluctl/commands/cmd_list_targets.go index c54241e94..64921e6f7 100644 --- a/cmd/kluctl/commands/cmd_list_targets.go +++ b/cmd/kluctl/commands/cmd_list_targets.go @@ -1,6 +1,7 @@ package commands import ( + "context" "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/kluctl_project" "github.com/kluctl/kluctl/v2/pkg/types" @@ -16,7 +17,7 @@ func (cmd *listTargetsCmd) Help() string { } func (cmd *listTargetsCmd) Run() error { - return withKluctlProjectFromArgs(cmd.ProjectFlags, true, func(p *kluctl_project.LoadedKluctlProject) error { + return withKluctlProjectFromArgs(cmd.ProjectFlags, true, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { var result []*types.Target for _, t := range p.DynamicTargets { result = append(result, t.Target) diff --git a/cmd/kluctl/commands/cmd_poke_images.go b/cmd/kluctl/commands/cmd_poke_images.go index a65f2e079..88c2ab7ff 100644 --- a/cmd/kluctl/commands/cmd_poke_images.go +++ b/cmd/kluctl/commands/cmd_poke_images.go @@ -44,7 +44,7 @@ func (cmd *pokeImagesCmd) Run() error { cmd2 := commands.NewPokeImagesCommand(ctx.targetCtx.DeploymentCollection) - result, err := cmd2.Run(ctx.targetCtx.K) + result, err := cmd2.Run(ctx.ctx, ctx.targetCtx.K) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_seal.go b/cmd/kluctl/commands/cmd_seal.go index bdabda126..5df4a45f1 100644 --- a/cmd/kluctl/commands/cmd_seal.go +++ b/cmd/kluctl/commands/cmd_seal.go @@ -1,6 +1,7 @@ package commands import ( + "context" "fmt" "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/deployment/commands" @@ -61,7 +62,7 @@ func loadSecrets(ctx *commandCtx, target *types.Target, secretsLoader *seal.Secr return nil } -func (cmd *sealCmd) runCmdSealForTarget(p *kluctl_project.LoadedKluctlProject, targetName string, secretsLoader *seal.SecretsLoader) error { +func (cmd *sealCmd) runCmdSealForTarget(ctx context.Context, p *kluctl_project.LoadedKluctlProject, targetName string, secretsLoader *seal.SecretsLoader) error { log.Infof("Sealing for target %s", targetName) ptArgs := projectTargetCommandArgs{ @@ -72,7 +73,7 @@ func (cmd *sealCmd) runCmdSealForTarget(p *kluctl_project.LoadedKluctlProject, t ptArgs.targetFlags.Target = targetName // pass forSeal=True so that .sealme files are rendered as well - return withProjectTargetCommandContext(ptArgs, p, func(ctx *commandCtx) error { + return withProjectTargetCommandContext(ctx, ptArgs, p, func(ctx *commandCtx) error { err := loadSecrets(ctx, ctx.targetCtx.Target, secretsLoader) if err != nil { return err @@ -119,7 +120,7 @@ func (cmd *sealCmd) runCmdSealForTarget(p *kluctl_project.LoadedKluctlProject, t } func (cmd *sealCmd) Run() error { - return withKluctlProjectFromArgs(cmd.ProjectFlags, true, func(p *kluctl_project.LoadedKluctlProject) error { + return withKluctlProjectFromArgs(cmd.ProjectFlags, true, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { hadError := false secretsLoader := seal.NewSecretsLoader(p, cmd.SecretsDir) @@ -155,7 +156,7 @@ func (cmd *sealCmd) Run() error { sealTarget = baseTarget } - err := cmd.runCmdSealForTarget(p, sealTarget.Name, secretsLoader) + err := cmd.runCmdSealForTarget(ctx, p, sealTarget.Name, secretsLoader) if err != nil { log.Warningf("Sealing for target %s failed: %v", sealTarget.Name, err) hadError = true diff --git a/cmd/kluctl/commands/cmd_validate.go b/cmd/kluctl/commands/cmd_validate.go index 4a9bf23e4..6f59906e2 100644 --- a/cmd/kluctl/commands/cmd_validate.go +++ b/cmd/kluctl/commands/cmd_validate.go @@ -39,7 +39,7 @@ func (cmd *validateCmd) Run() error { startTime := time.Now() cmd2 := commands.NewValidateCommand(ctx.targetCtx.DeploymentCollection) for true { - result, err := cmd2.Run(ctx.targetCtx.K) + result, err := cmd2.Run(ctx.ctx, ctx.targetCtx.K) if err != nil { return err } diff --git a/cmd/kluctl/commands/completion.go b/cmd/kluctl/commands/completion.go index a316051bb..99cfdd5b1 100644 --- a/cmd/kluctl/commands/completion.go +++ b/cmd/kluctl/commands/completion.go @@ -1,6 +1,7 @@ package commands import ( + "context" "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/kluctl_project" "github.com/kluctl/kluctl/v2/pkg/types" @@ -47,18 +48,18 @@ func RegisterFlagCompletionFuncs(cmdStruct interface{}, ccmd *cobra.Command) err return nil } -func withProjectForCompletion(projectArgs *args.ProjectFlags, cb func(p *kluctl_project.LoadedKluctlProject) error) error { +func withProjectForCompletion(projectArgs *args.ProjectFlags, cb func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error) error { // let's not update git caches too often projectArgs.GitCacheUpdateInterval = time.Second * 60 - return withKluctlProjectFromArgs(*projectArgs, false, func(p *kluctl_project.LoadedKluctlProject) error { - return cb(p) + return withKluctlProjectFromArgs(*projectArgs, false, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { + return cb(ctx, p) }) } func buildClusterCompletionFunc(projectArgs *args.ProjectFlags) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { var ret []string - err := withProjectForCompletion(projectArgs, func(p *kluctl_project.LoadedKluctlProject) error { + err := withProjectForCompletion(projectArgs, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { dents, err := os.ReadDir(p.ClustersDir) if err != nil { return err @@ -86,7 +87,7 @@ func buildClusterCompletionFunc(projectArgs *args.ProjectFlags) func(cmd *cobra. func buildTargetCompletionFunc(projectArgs *args.ProjectFlags) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { var ret []string - err := withProjectForCompletion(projectArgs, func(p *kluctl_project.LoadedKluctlProject) error { + err := withProjectForCompletion(projectArgs, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { for _, t := range p.DynamicTargets { ret = append(ret, t.Target.Name) } @@ -132,7 +133,7 @@ func buildInclusionCompletionFunc(cmdStruct interface{}, forDirs bool) func(cmd var deploymentItemDirs utils.OrderedMap var mutex sync.Mutex - err := withProjectForCompletion(&ptArgs.projectFlags, func(p *kluctl_project.LoadedKluctlProject) error { + err := withProjectForCompletion(&ptArgs.projectFlags, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { var targets []string if ptArgs.targetFlags.Target == "" { for _, t := range p.DynamicTargets { @@ -148,7 +149,7 @@ func buildInclusionCompletionFunc(cmdStruct interface{}, forDirs bool) func(cmd ptArgs.targetFlags.Target = t wg.Add(1) go func() { - _ = withProjectTargetCommandContext(ptArgs, p, func(ctx *commandCtx) error { + _ = withProjectTargetCommandContext(ctx, ptArgs, p, func(ctx *commandCtx) error { mutex.Lock() defer mutex.Unlock() for _, di := range ctx.targetCtx.DeploymentCollection.Deployments { @@ -186,7 +187,7 @@ func buildImagesCompletionFunc(cmdStruct interface{}) func(cmd *cobra.Command, a var images utils.OrderedMap var mutex sync.Mutex - err := withProjectForCompletion(&ptArgs.projectFlags, func(p *kluctl_project.LoadedKluctlProject) error { + err := withProjectForCompletion(&ptArgs.projectFlags, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { var targets []string if ptArgs.targetFlags.Target == "" { for _, t := range p.DynamicTargets { @@ -202,7 +203,7 @@ func buildImagesCompletionFunc(cmdStruct interface{}) func(cmd *cobra.Command, a ptArgs.targetFlags.Target = t wg.Add(1) go func() { - _ = withProjectTargetCommandContext(ptArgs, p, func(ctx *commandCtx) error { + _ = withProjectTargetCommandContext(ctx, ptArgs, p, func(ctx *commandCtx) error { err := ctx.targetCtx.DeploymentCollection.Prepare(nil) if err != nil { log.Error(err) diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index e1adddebb..a599aad54 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -18,10 +18,9 @@ import ( "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" "os" - "time" ) -func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, strictTemplates bool, cb func(p *kluctl_project.LoadedKluctlProject) error) error { +func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, strictTemplates bool, cb func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error) error { var url *git_url.GitUrl if projectFlags.ProjectUrl != "" { var err error @@ -71,9 +70,10 @@ func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, strictTemplates b GitUpdateInterval: projectFlags.GitCacheUpdateInterval, } - loadCtx, cancel := context.WithDeadline(context.Background(), time.Now().Add(projectFlags.LoadTimeout)) + ctx := context.Background() + ctx, cancel := context.WithTimeout(ctx, projectFlags.Timeout) defer cancel() - p, err := kluctl_project.LoadKluctlProject(loadCtx, loadArgs, tmpDir, j2) + p, err := kluctl_project.LoadKluctlProject(ctx, loadArgs, tmpDir, j2) if err != nil { return err } @@ -88,7 +88,7 @@ func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, strictTemplates b return err } } - return cb(p) + return cb(ctx, p) } type projectTargetCommandArgs struct { @@ -105,18 +105,19 @@ type projectTargetCommandArgs struct { } type commandCtx struct { + ctx context.Context targetCtx *kluctl_project.TargetContext images *deployment.Images } func withProjectCommandContext(args projectTargetCommandArgs, cb func(ctx *commandCtx) error) error { - return withKluctlProjectFromArgs(args.projectFlags, true, func(p *kluctl_project.LoadedKluctlProject) error { - return withProjectTargetCommandContext(args, p, cb) + return withKluctlProjectFromArgs(args.projectFlags, true, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { + return withProjectTargetCommandContext(ctx, args, p, cb) }) } -func withProjectTargetCommandContext(args projectTargetCommandArgs, p *kluctl_project.LoadedKluctlProject, cb func(ctx *commandCtx) error) error { - rh := registries.NewRegistryHelper() +func withProjectTargetCommandContext(ctx context.Context, args projectTargetCommandArgs, p *kluctl_project.LoadedKluctlProject, cb func(ctx *commandCtx) error) error { + rh := registries.NewRegistryHelper(ctx) err := rh.ParseAuthEntriesFromEnv() if err != nil { return fmt.Errorf("failed to parse registry auth from environment: %w", err) @@ -162,7 +163,7 @@ func withProjectTargetCommandContext(args projectTargetCommandArgs, p *kluctl_pr return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(configLoadingRules, configOverrides).ClientConfig() } - ctx, err := p.NewTargetContext(clientConfigGetter, args.targetFlags.Target, args.projectFlags.Cluster, + targetCtx, err := p.NewTargetContext(ctx, clientConfigGetter, args.targetFlags.Target, args.projectFlags.Cluster, args.dryRunArgs == nil || args.dryRunArgs.DryRun || args.forCompletion, optionArgs, args.forSeal, images, inclusion, renderOutputDir) @@ -171,14 +172,15 @@ func withProjectTargetCommandContext(args projectTargetCommandArgs, p *kluctl_pr } if !args.forSeal && !args.forCompletion { - err = ctx.DeploymentCollection.Prepare(ctx.K) + err = targetCtx.DeploymentCollection.Prepare(targetCtx.K) if err != nil { return err } } cmdCtx := &commandCtx{ - targetCtx: ctx, + ctx: ctx, + targetCtx: targetCtx, images: images, } diff --git a/pkg/deployment/commands/deploy.go b/pkg/deployment/commands/deploy.go index ed762c0e1..d61461d70 100644 --- a/pkg/deployment/commands/deploy.go +++ b/pkg/deployment/commands/deploy.go @@ -1,6 +1,7 @@ package commands import ( + "context" "github.com/kluctl/kluctl/v2/pkg/deployment" utils2 "github.com/kluctl/kluctl/v2/pkg/deployment/utils" "github.com/kluctl/kluctl/v2/pkg/k8s" @@ -25,7 +26,7 @@ func NewDeployCommand(c *deployment.DeploymentCollection) *DeployCommand { } } -func (cmd *DeployCommand) Run(k *k8s.K8sCluster, diffResultCb func(diffResult *types.CommandResult) error) (*types.CommandResult, error) { +func (cmd *DeployCommand) Run(ctx context.Context, k *k8s.K8sCluster, diffResultCb func(diffResult *types.CommandResult) error) (*types.CommandResult, error) { dew := utils2.NewDeploymentErrorsAndWarnings() ru := utils2.NewRemoteObjectsUtil(dew) @@ -46,7 +47,7 @@ func (cmd *DeployCommand) Run(k *k8s.K8sCluster, diffResultCb func(diffResult *t } if diffResultCb != nil { - au := utils2.NewApplyDeploymentsUtil(dew, cmd.c.Deployments, ru, k, o) + au := utils2.NewApplyDeploymentsUtil(ctx, dew, cmd.c.Deployments, ru, k, o) au.ApplyDeployments() du := utils2.NewDiffUtil(dew, cmd.c.Deployments, ru, au.GetAppliedObjectsMap()) @@ -75,7 +76,7 @@ func (cmd *DeployCommand) Run(k *k8s.K8sCluster, diffResultCb func(diffResult *t o.DryRun = k.DryRun o.AbortOnError = cmd.AbortOnError - au := utils2.NewApplyDeploymentsUtil(dew, cmd.c.Deployments, ru, k, o) + au := utils2.NewApplyDeploymentsUtil(ctx, dew, cmd.c.Deployments, ru, k, o) au.ApplyDeployments() du := utils2.NewDiffUtil(dew, cmd.c.Deployments, ru, au.GetAppliedObjectsMap()) diff --git a/pkg/deployment/commands/diff.go b/pkg/deployment/commands/diff.go index c7e35a3d5..5dafbe374 100644 --- a/pkg/deployment/commands/diff.go +++ b/pkg/deployment/commands/diff.go @@ -1,6 +1,7 @@ package commands import ( + "context" "github.com/kluctl/kluctl/v2/pkg/deployment" "github.com/kluctl/kluctl/v2/pkg/deployment/utils" "github.com/kluctl/kluctl/v2/pkg/k8s" @@ -24,7 +25,7 @@ func NewDiffCommand(c *deployment.DeploymentCollection) *DiffCommand { } } -func (cmd *DiffCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, error) { +func (cmd *DiffCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*types.CommandResult, error) { dew := utils.NewDeploymentErrorsAndWarnings() ru := utils.NewRemoteObjectsUtil(dew) @@ -41,7 +42,7 @@ func (cmd *DiffCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, error) { AbortOnError: false, WaitObjectTimeout: 0, } - au := utils.NewApplyDeploymentsUtil(dew, cmd.c.Deployments, ru, k, o) + au := utils.NewApplyDeploymentsUtil(ctx, dew, cmd.c.Deployments, ru, k, o) au.ApplyDeployments() du := utils.NewDiffUtil(dew, cmd.c.Deployments, ru, au.GetAppliedObjectsMap()) diff --git a/pkg/deployment/commands/downscale.go b/pkg/deployment/commands/downscale.go index f12d86d6b..3f0ee568d 100644 --- a/pkg/deployment/commands/downscale.go +++ b/pkg/deployment/commands/downscale.go @@ -1,6 +1,7 @@ package commands import ( + "context" "github.com/kluctl/kluctl/v2/pkg/deployment" utils2 "github.com/kluctl/kluctl/v2/pkg/deployment/utils" "github.com/kluctl/kluctl/v2/pkg/k8s" @@ -20,7 +21,7 @@ func NewDownscaleCommand(c *deployment.DeploymentCollection) *DownscaleCommand { } } -func (cmd *DownscaleCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, error) { +func (cmd *DownscaleCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*types.CommandResult, error) { var wg sync.WaitGroup dew := utils2.NewDeploymentErrorsAndWarnings() @@ -31,7 +32,7 @@ func (cmd *DownscaleCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, error return nil, err } - ad := utils2.NewApplyDeploymentsUtil(dew, cmd.c.Deployments, ru, k, &utils2.ApplyUtilOptions{}) + ad := utils2.NewApplyDeploymentsUtil(ctx, dew, cmd.c.Deployments, ru, k, &utils2.ApplyUtilOptions{}) appliedObjects := make(map[k8s2.ObjectRef]*uo.UnstructuredObject) diff --git a/pkg/deployment/commands/poke_images.go b/pkg/deployment/commands/poke_images.go index d325da3b2..9c8a50ad6 100644 --- a/pkg/deployment/commands/poke_images.go +++ b/pkg/deployment/commands/poke_images.go @@ -1,6 +1,7 @@ package commands import ( + "context" "fmt" "github.com/kluctl/kluctl/v2/pkg/deployment" utils2 "github.com/kluctl/kluctl/v2/pkg/deployment/utils" @@ -21,7 +22,7 @@ func NewPokeImagesCommand(c *deployment.DeploymentCollection) *PokeImagesCommand } } -func (cmd *PokeImagesCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, error) { +func (cmd *PokeImagesCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*types.CommandResult, error) { var wg sync.WaitGroup dew := utils2.NewDeploymentErrorsAndWarnings() @@ -67,7 +68,7 @@ func (cmd *PokeImagesCommand) Run(k *k8s.K8sCluster) (*types.CommandResult, erro return o, nil } - ad := utils2.NewApplyDeploymentsUtil(dew, cmd.c.Deployments, ru, k, &utils2.ApplyUtilOptions{}) + ad := utils2.NewApplyDeploymentsUtil(ctx, dew, cmd.c.Deployments, ru, k, &utils2.ApplyUtilOptions{}) for ref, containers := range containersAndImages { ref := ref diff --git a/pkg/deployment/commands/validate.go b/pkg/deployment/commands/validate.go index 5fa33f251..8e35d5e54 100644 --- a/pkg/deployment/commands/validate.go +++ b/pkg/deployment/commands/validate.go @@ -1,6 +1,7 @@ package commands import ( + "context" "github.com/kluctl/kluctl/v2/pkg/deployment" utils2 "github.com/kluctl/kluctl/v2/pkg/deployment/utils" "github.com/kluctl/kluctl/v2/pkg/k8s" @@ -24,7 +25,7 @@ func NewValidateCommand(c *deployment.DeploymentCollection) *ValidateCommand { return cmd } -func (cmd *ValidateCommand) Run(k *k8s.K8sCluster) (*types.ValidateResult, error) { +func (cmd *ValidateCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*types.ValidateResult, error) { var result types.ValidateResult cmd.dew.Init() @@ -34,7 +35,7 @@ func (cmd *ValidateCommand) Run(k *k8s.K8sCluster) (*types.ValidateResult, error return nil, err } - ad := utils2.NewApplyDeploymentsUtil(cmd.dew, cmd.c.Deployments, cmd.ru, k, &utils2.ApplyUtilOptions{}) + ad := utils2.NewApplyDeploymentsUtil(ctx, cmd.dew, cmd.c.Deployments, cmd.ru, k, &utils2.ApplyUtilOptions{}) for _, d := range cmd.c.Deployments { if !d.CheckInclusionForDeploy() { continue diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index 2d9333612..80ea9ac7e 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -14,6 +14,8 @@ import ( ) type DeploymentCollection struct { + ctx context.Context + Project *DeploymentProject Images *Images Inclusion *utils.Inclusion @@ -24,8 +26,9 @@ type DeploymentCollection struct { mutex sync.Mutex } -func NewDeploymentCollection(project *DeploymentProject, images *Images, inclusion *utils.Inclusion, renderDir string, forSeal bool) (*DeploymentCollection, error) { +func NewDeploymentCollection(ctx context.Context, project *DeploymentProject, images *Images, inclusion *utils.Inclusion, renderDir string, forSeal bool) (*DeploymentCollection, error) { dc := &DeploymentCollection{ + ctx: ctx, Project: project, Images: images, Inclusion: inclusion, diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index 3f69a8416..ce8c0b31e 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -55,6 +55,8 @@ type ApplyUtil struct { } type ApplyDeploymentsUtil struct { + ctx context.Context + dew *DeploymentErrorsAndWarnings deployments []*deployment.DeploymentItem ru *RemoteObjectUtils @@ -67,8 +69,9 @@ type ApplyDeploymentsUtil struct { results []*ApplyUtil } -func NewApplyDeploymentsUtil(dew *DeploymentErrorsAndWarnings, deployments []*deployment.DeploymentItem, ru *RemoteObjectUtils, k *k8s.K8sCluster, o *ApplyUtilOptions) *ApplyDeploymentsUtil { +func NewApplyDeploymentsUtil(ctx context.Context, dew *DeploymentErrorsAndWarnings, deployments []*deployment.DeploymentItem, ru *RemoteObjectUtils, k *k8s.K8sCluster, o *ApplyUtilOptions) *ApplyDeploymentsUtil { ret := &ApplyDeploymentsUtil{ + ctx: ctx, dew: dew, deployments: deployments, ru: ru, diff --git a/pkg/git/mirrored_repo.go b/pkg/git/mirrored_repo.go index fc8d40fdc..8940c6047 100644 --- a/pkg/git/mirrored_repo.go +++ b/pkg/git/mirrored_repo.go @@ -21,6 +21,8 @@ import "github.com/gofrs/flock" var cacheBaseDir = filepath.Join(utils.GetTmpBaseDir(), "git-cache") type MirroredGitRepo struct { + ctx context.Context + url git_url.GitUrl mirrorDir string @@ -28,9 +30,10 @@ type MirroredGitRepo struct { fileLock *flock.Flock } -func NewMirroredGitRepo(u git_url.GitUrl) (*MirroredGitRepo, error) { +func NewMirroredGitRepo(ctx context.Context, u git_url.GitUrl) (*MirroredGitRepo, error) { mirrorRepoName := buildMirrorRepoName(u) o := &MirroredGitRepo{ + ctx: ctx, url: u, mirrorDir: filepath.Join(cacheBaseDir, mirrorRepoName), } @@ -53,8 +56,8 @@ func (g *MirroredGitRepo) SetUpdated(u bool) { g.hasUpdated = u } -func (g *MirroredGitRepo) Lock(ctx context.Context) error { - ok, err := g.fileLock.TryLockContext(ctx, time.Millisecond*100) +func (g *MirroredGitRepo) Lock() error { + ok, err := g.fileLock.TryLockContext(g.ctx, time.Millisecond*100) if err != nil { return fmt.Errorf("locking of %s failed: %w", g.fileLock.Path(), err) } @@ -73,8 +76,8 @@ func (g *MirroredGitRepo) Unlock() error { return nil } -func (g *MirroredGitRepo) WithLock(ctx context.Context, cb func() error) error { - err := g.Lock(ctx) +func (g *MirroredGitRepo) WithLock(cb func() error) error { + err := g.Lock() if err != nil { return err } @@ -82,9 +85,9 @@ func (g *MirroredGitRepo) WithLock(ctx context.Context, cb func() error) error { return cb() } -func (g *MirroredGitRepo) MaybeWithLock(ctx context.Context, lock bool, cb func() error) error { +func (g *MirroredGitRepo) MaybeWithLock(lock bool, cb func() error) error { if lock { - return g.WithLock(ctx, cb) + return g.WithLock(cb) } return cb() } @@ -167,7 +170,7 @@ func (g *MirroredGitRepo) cleanupMirrorDir() error { return nil } -func (g *MirroredGitRepo) update(ctx context.Context, repoDir string, authProviders *auth2.GitAuthProviders) error { +func (g *MirroredGitRepo) update(repoDir string, authProviders *auth2.GitAuthProviders) error { log.Infof("Updating mirror repo: url='%v'", g.url.String()) r, err := git.PlainOpen(repoDir) if err != nil { @@ -181,7 +184,7 @@ func (g *MirroredGitRepo) update(ctx context.Context, repoDir string, authProvid return err } - remoteRefs, err := remote.ListContext(ctx, &git.ListOptions{ + remoteRefs, err := remote.ListContext(g.ctx, &git.ListOptions{ Auth: auth.AuthMethod, CABundle: auth.CABundle, }) @@ -224,7 +227,7 @@ func (g *MirroredGitRepo) update(ctx context.Context, repoDir string, authProvid } if changed { - err = remote.FetchContext(ctx, &git.FetchOptions{ + err = remote.FetchContext(g.ctx, &git.FetchOptions{ Auth: auth.AuthMethod, CABundle: auth.CABundle, Progress: os.Stdout, @@ -260,10 +263,10 @@ func (g *MirroredGitRepo) update(ctx context.Context, repoDir string, authProvid return nil } -func (g *MirroredGitRepo) cloneOrUpdate(ctx context.Context, authProviders *auth2.GitAuthProviders) error { +func (g *MirroredGitRepo) cloneOrUpdate(authProviders *auth2.GitAuthProviders) error { initMarker := filepath.Join(g.mirrorDir, ".cache2.init") if utils.IsFile(initMarker) { - return g.update(ctx, g.mirrorDir, authProviders) + return g.update(g.mirrorDir, authProviders) } err := g.cleanupMirrorDir() if err != nil { @@ -295,7 +298,7 @@ func (g *MirroredGitRepo) cloneOrUpdate(ctx context.Context, authProviders *auth return err } - err = g.update(ctx, tmpMirrorDir, authProviders) + err = g.update(tmpMirrorDir, authProviders) if err != nil { return err } @@ -317,8 +320,8 @@ func (g *MirroredGitRepo) cloneOrUpdate(ctx context.Context, authProviders *auth return nil } -func (g *MirroredGitRepo) Update(ctx context.Context, authProviders *auth2.GitAuthProviders) error { - err := g.cloneOrUpdate(ctx, authProviders) +func (g *MirroredGitRepo) Update(authProviders *auth2.GitAuthProviders) error { + err := g.cloneOrUpdate(authProviders) if err != nil { return err } @@ -326,7 +329,7 @@ func (g *MirroredGitRepo) Update(ctx context.Context, authProviders *auth2.GitAu return nil } -func (g *MirroredGitRepo) CloneProject(ctx context.Context, ref string, targetDir string) error { +func (g *MirroredGitRepo) CloneProject(ref string, targetDir string) error { if !g.fileLock.Locked() || !g.hasUpdated { log.Fatalf("tried to clone from a project that is not locked/updated") } diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index 5c901aafa..ea633652a 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -10,6 +10,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" log "github.com/sirupsen/logrus" + "io" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -37,6 +38,8 @@ var ( ) type K8sCluster struct { + ctx context.Context + DryRun bool restConfig *rest.Config @@ -77,8 +80,9 @@ func (p *parallelClientEntry) HandleWarningHeader(code int, agent string, text s }) } -func NewK8sCluster(configIn *rest.Config, dryRun bool) (*K8sCluster, error) { +func NewK8sCluster(ctx context.Context, configIn *rest.Config, dryRun bool) (*K8sCluster, error) { k := &K8sCluster{ + ctx: ctx, DryRun: dryRun, } @@ -168,10 +172,34 @@ func (k *K8sCluster) updateResources(doLock bool) error { err error }{} - _, arls, err := k.discovery.ServerGroupsAndResources() - if err != nil && !discovery.IsGroupDiscoveryFailedError(err) { - return err + // the discovery client doesn't support cancellation, so we need to run it in the background and wait for it + var arls []*v1.APIResourceList + var preferredArls []*v1.APIResourceList + finished := make(chan error) + go func() { + var err error + _, arls, err = k.discovery.ServerGroupsAndResources() + if err != nil && !discovery.IsGroupDiscoveryFailedError(err) { + finished <- err + return + } + preferredArls, err = k.discovery.ServerPreferredResources() + if err != nil && !discovery.IsGroupDiscoveryFailedError(err) { + finished <- err + return + } + finished <- nil + }() + + select { + case err := <-finished: + if err != nil { + return err + } + case <-k.ctx.Done(): + return fmt.Errorf("failed listing api resources: %w", k.ctx.Err()) } + for _, arl := range arls { for _, ar := range arl.APIResources { if strings.Index(ar.Name, "/") != -1 { @@ -202,11 +230,7 @@ func (k *K8sCluster) updateResources(doLock bool) error { } } - arls, err = k.discovery.ServerPreferredResources() - if err != nil && !discovery.IsGroupDiscoveryFailedError(err) { - return err - } - for _, arl := range arls { + for _, arl := range preferredArls { for _, ar := range arl.APIResources { if strings.Index(ar.Name, "/") != -1 { // skip subresources @@ -420,50 +444,46 @@ func (k *K8sCluster) GetSchemaForGVK(gvk schema.GroupVersionKind) (*uo.Unstructu return nil, fmt.Errorf("schema for %s not found", gvk.String()) } -func (k *K8sCluster) WithCoreV1(cb func(client *corev1.CoreV1Client) error) error { - p := <-k.clientPool - defer func() { k.clientPool <- p }() - return cb(p.corev1) +func (k *K8sCluster) withClientFromPool(cb func(p *parallelClientEntry) error) ([]ApiWarning, error) { + select { + case p := <-k.clientPool: + defer func() { k.clientPool <- p }() + p.warnings = nil + err := cb(p) + return append([]ApiWarning(nil), p.warnings...), err + case <-k.ctx.Done(): + return nil, fmt.Errorf("failed waiting for free client: %w", k.ctx.Err()) + } } func (k *K8sCluster) withDynamicClientForGVK(gvk schema.GroupVersionKind, namespace string, cb func(r dynamic.ResourceInterface) error) ([]ApiWarning, error) { - gvr, namespaced, err := k.getGVRForGVK(gvk) - if err != nil { - return nil, err - } - - p := <-k.clientPool - defer func() { k.clientPool <- p }() - - p.warnings = nil + return k.withClientFromPool(func(p *parallelClientEntry) error { + gvr, namespaced, err := k.getGVRForGVK(gvk) + if err != nil { + return err + } - if namespaced && namespace != "" { - err = cb(p.dynamicClient.Resource(*gvr).Namespace(namespace)) - return append([]ApiWarning(nil), p.warnings...), err - } else { - err = cb(p.dynamicClient.Resource(*gvr)) - return append([]ApiWarning(nil), p.warnings...), err - } + if namespaced && namespace != "" { + return cb(p.dynamicClient.Resource(*gvr).Namespace(namespace)) + } else { + return cb(p.dynamicClient.Resource(*gvr)) + } + }) } func (k *K8sCluster) withMetadataClientForGVK(gvk schema.GroupVersionKind, namespace string, cb func(r metadata.ResourceInterface) error) ([]ApiWarning, error) { - gvr, namespaced, err := k.getGVRForGVK(gvk) - if err != nil { - return nil, err - } - - p := <-k.clientPool - defer func() { k.clientPool <- p }() - - p.warnings = nil + return k.withClientFromPool(func(p *parallelClientEntry) error { + gvr, namespaced, err := k.getGVRForGVK(gvk) + if err != nil { + return err + } - if namespaced && namespace != "" { - err = cb(p.metadataClient.Resource(*gvr).Namespace(namespace)) - return append([]ApiWarning(nil), p.warnings...), err - } else { - err = cb(p.metadataClient.Resource(*gvr)) - return append([]ApiWarning(nil), p.warnings...), err - } + if namespaced && namespace != "" { + return cb(p.metadataClient.Resource(*gvr).Namespace(namespace)) + } else { + return cb(p.metadataClient.Resource(*gvr)) + } + }) } func (k *K8sCluster) buildLabelSelector(labels map[string]string) string { @@ -485,7 +505,7 @@ func (k *K8sCluster) ListObjects(gvk schema.GroupVersionKind, namespace string, o := v1.ListOptions{ LabelSelector: k.buildLabelSelector(labels), } - x, err := r.List(context.Background(), o) + x, err := r.List(k.ctx, o) if err != nil { return err } @@ -504,7 +524,7 @@ func (k *K8sCluster) ListObjectsMetadata(gvk schema.GroupVersionKind, namespace o := v1.ListOptions{ LabelSelector: k.buildLabelSelector(labels), } - x, err := r.List(context.Background(), o) + x, err := r.List(k.ctx, o) if err != nil { return err } @@ -585,7 +605,7 @@ func (k *K8sCluster) GetSingleObject(ref k8s.ObjectRef) (*uo.UnstructuredObject, var result *uo.UnstructuredObject apiWarnings, err := k.withDynamicClientForGVK(ref.GVK, ref.Namespace, func(r dynamic.ResourceInterface) error { o := v1.GetOptions{} - x, err := r.Get(context.Background(), ref.Name, o) + x, err := r.Get(k.ctx, ref.Name, o) if err != nil { return err } @@ -648,7 +668,7 @@ func (k *K8sCluster) DeleteSingleObject(ref k8s.ObjectRef, options DeleteOptions } apiWarnings, err := k.withDynamicClientForGVK(ref.GVK, ref.Namespace, func(r dynamic.ResourceInterface) error { - err := r.Delete(context.Background(), ref.Name, o) + err := r.Delete(k.ctx, ref.Name, o) if err != nil { if options.IgnoreNotFoundError && errors.IsNotFound(err) { return nil @@ -681,7 +701,12 @@ func (k *K8sCluster) waitForDeletedObject(ref k8s.ObjectRef) error { return err } - time.Sleep(time.Millisecond * 100) + select { + case <-time.After(500 * time.Millisecond): + continue + case <-k.ctx.Done(): + return fmt.Errorf("failed waiting for deletion of %s: %w", ref.String(), k.ctx.Err()) + } } return nil } @@ -800,7 +825,7 @@ func (k *K8sCluster) PatchObject(o *uo.UnstructuredObject, options PatchOptions) log2.Debugf("patching") var result *uo.UnstructuredObject apiWarnings, err := k.withDynamicClientForGVK(ref.GVK, ref.Namespace, func(r dynamic.ResourceInterface) error { - x, err := r.Patch(context.Background(), ref.Name, types.ApplyPatchType, data, po) + x, err := r.Patch(k.ctx, ref.Name, types.ApplyPatchType, data, po) if err != nil { return fmt.Errorf("failed to patch %s: %w", ref.String(), err) } @@ -829,7 +854,7 @@ func (k *K8sCluster) UpdateObject(o *uo.UnstructuredObject, options UpdateOption log2.Debugf("updating") var result *uo.UnstructuredObject apiWarnings, err := k.withDynamicClientForGVK(ref.GVK, ref.Namespace, func(r dynamic.ResourceInterface) error { - x, err := r.Update(context.Background(), o.ToUnstructured(), updateOpts) + x, err := r.Update(k.ctx, o.ToUnstructured(), updateOpts) if err != nil { return err } @@ -838,3 +863,15 @@ func (k *K8sCluster) UpdateObject(o *uo.UnstructuredObject, options UpdateOption }) return result, apiWarnings, err } + +func (k *K8sCluster) ProxyGet(scheme, namespace, name, port, path string, params map[string]string) (io.ReadCloser, error) { + var ret rest.ResponseWrapper + _, err := k.withClientFromPool(func(p *parallelClientEntry) error { + ret = p.corev1.Services(namespace).ProxyGet(scheme, name, port, path, params) + return nil + }) + if err != nil { + return nil, err + } + return ret.Stream(k.ctx) +} diff --git a/pkg/kluctl_project/git.go b/pkg/kluctl_project/git.go index ee8ba490c..1be037384 100644 --- a/pkg/kluctl_project/git.go +++ b/pkg/kluctl_project/git.go @@ -1,7 +1,6 @@ package kluctl_project import ( - "context" "fmt" "github.com/kluctl/kluctl/v2/pkg/git" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" @@ -16,7 +15,7 @@ import ( "time" ) -func (c *LoadedKluctlProject) updateGitCache(ctx context.Context, mr *git.MirroredGitRepo) error { +func (c *LoadedKluctlProject) updateGitCache(mr *git.MirroredGitRepo) error { if mr.HasUpdated() { return nil } @@ -24,10 +23,10 @@ func (c *LoadedKluctlProject) updateGitCache(ctx context.Context, mr *git.Mirror mr.SetUpdated(true) return nil } - return mr.Update(ctx, c.loadArgs.GitAuthProviders) + return mr.Update(c.loadArgs.GitAuthProviders) } -func (c *LoadedKluctlProject) updateGitCaches(ctx context.Context) error { +func (c *LoadedKluctlProject) updateGitCaches() error { var waitGroup sync.WaitGroup var firstError error var firstErrorLock sync.Mutex @@ -41,8 +40,8 @@ func (c *LoadedKluctlProject) updateGitCaches(ctx context.Context) error { } doUpdateRepo := func(repo *git.MirroredGitRepo) error { - return repo.WithLock(ctx, func() error { - return c.updateGitCache(ctx, repo) + return repo.WithLock(func() error { + return c.updateGitCache(repo) }) } doUpdateGitProject := func(u git_url.GitUrl) error { @@ -50,7 +49,7 @@ func (c *LoadedKluctlProject) updateGitCaches(ctx context.Context) error { if ok { return nil } - mr, err := git.NewMirroredGitRepo(u) + mr, err := git.NewMirroredGitRepo(c.ctx, u) if err != nil { return err } @@ -108,7 +107,7 @@ func (c *LoadedKluctlProject) updateGitCaches(ctx context.Context) error { return firstError } -func (c *LoadedKluctlProject) cloneGitProject(ctx context.Context, gitProject *types2.GitProject, targetDir string, doLock bool) (info git.GitRepoInfo, err error) { +func (c *LoadedKluctlProject) cloneGitProject(gitProject *types2.GitProject, targetDir string, doLock bool) (info git.GitRepoInfo, err error) { err = os.MkdirAll(filepath.Join(c.TmpDir, "git"), 0o700) if err != nil { return @@ -116,24 +115,24 @@ func (c *LoadedKluctlProject) cloneGitProject(ctx context.Context, gitProject *t mr, ok := c.mirroredRepos[gitProject.Url.NormalizedRepoKey()] if !ok { - mr, err = git.NewMirroredGitRepo(gitProject.Url) + mr, err = git.NewMirroredGitRepo(c.ctx, gitProject.Url) if err != nil { return } c.mirroredRepos[gitProject.Url.NormalizedRepoKey()] = mr - err = mr.Lock(ctx) + err = mr.Lock() if err != nil { return } defer mr.Unlock() } - err = mr.MaybeWithLock(ctx, doLock, func() error { - err := c.updateGitCache(ctx, mr) + err = mr.MaybeWithLock(doLock, func() error { + err := c.updateGitCache(mr) if err != nil { return err } - return mr.CloneProject(ctx, gitProject.Ref, targetDir) + return mr.CloneProject(gitProject.Ref, targetDir) }) if err != nil { return diff --git a/pkg/kluctl_project/load.go b/pkg/kluctl_project/load.go index 56a1da171..398a6c958 100644 --- a/pkg/kluctl_project/load.go +++ b/pkg/kluctl_project/load.go @@ -10,6 +10,7 @@ import ( func LoadKluctlProject(ctx context.Context, args LoadKluctlProjectArgs, tmpDir string, j2 *jinja2.Jinja2) (*LoadedKluctlProject, error) { p := &LoadedKluctlProject{ + ctx: ctx, loadArgs: args, TmpDir: tmpDir, J2: j2, @@ -22,17 +23,17 @@ func LoadKluctlProject(ctx context.Context, args LoadKluctlProjectArgs, tmpDir s if args.ProjectUrl != nil || args.ProjectRef != "" || args.ProjectConfig != "" || args.LocalClusters != "" || args.LocalDeployment != "" || args.LocalSealedSecrets != "" { return nil, fmt.Errorf("--from-archive can not be combined with any other project related option") } - err := p.loadFromArchive(ctx) + err := p.loadFromArchive() if err != nil { return nil, err } return p, nil } else { - err := p.loadKluctlProject(ctx) + err := p.loadKluctlProject() if err != nil { return nil, err } - err = p.loadTargets(ctx) + err = p.loadTargets() if err != nil { return nil, err } diff --git a/pkg/kluctl_project/project.go b/pkg/kluctl_project/project.go index 06d555e00..3b0d0efff 100644 --- a/pkg/kluctl_project/project.go +++ b/pkg/kluctl_project/project.go @@ -1,6 +1,7 @@ package kluctl_project import ( + "context" "fmt" "github.com/kluctl/kluctl/v2/pkg/git" "github.com/kluctl/kluctl/v2/pkg/jinja2" @@ -9,6 +10,8 @@ import ( ) type LoadedKluctlProject struct { + ctx context.Context + loadArgs LoadKluctlProjectArgs TmpDir string diff --git a/pkg/kluctl_project/project_archive.go b/pkg/kluctl_project/project_archive.go index 864e6a9c2..d9e94f053 100644 --- a/pkg/kluctl_project/project_archive.go +++ b/pkg/kluctl_project/project_archive.go @@ -3,7 +3,6 @@ package kluctl_project import ( "archive/tar" "compress/gzip" - "context" "fmt" types2 "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" @@ -14,7 +13,7 @@ import ( "time" ) -func (c *LoadedKluctlProject) loadFromArchive(ctx context.Context) error { +func (c *LoadedKluctlProject) loadFromArchive() error { var dir string if utils.IsFile(c.loadArgs.FromArchive) { dir = filepath.Join(c.TmpDir, "archive") @@ -61,7 +60,7 @@ func (c *LoadedKluctlProject) loadFromArchive(ctx context.Context) error { c.involvedRepos = pmd.InvolvedRepos c.DynamicTargets = pmd.Targets - err = c.loadKluctlProject(ctx) + err = c.loadKluctlProject() if err != nil { return err } diff --git a/pkg/kluctl_project/project_load.go b/pkg/kluctl_project/project_load.go index 75f24ac15..c4d8b9527 100644 --- a/pkg/kluctl_project/project_load.go +++ b/pkg/kluctl_project/project_load.go @@ -1,7 +1,6 @@ package kluctl_project import ( - "context" "fmt" "github.com/kluctl/kluctl/v2/pkg/git" auth2 "github.com/kluctl/kluctl/v2/pkg/git/auth" @@ -59,7 +58,7 @@ func (c *LoadedKluctlProject) localProject(dir string) gitProjectInfo { } } -func (c *LoadedKluctlProject) loadGitProject(ctx context.Context, gitProject *types2.GitProject, defaultSubDir string, doLock bool, doAddInvolvedRepo bool) (ret gitProjectInfo, err error) { +func (c *LoadedKluctlProject) loadGitProject(gitProject *types2.GitProject, defaultSubDir string, doLock bool, doAddInvolvedRepo bool) (ret gitProjectInfo, err error) { cloneDir, err := c.buildCloneDir(gitProject.Url, gitProject.Ref) if err != nil { return @@ -82,7 +81,7 @@ func (c *LoadedKluctlProject) loadGitProject(ctx context.Context, gitProject *ty } var ri git.GitRepoInfo - ri, err = c.cloneGitProject(ctx, gitProject, cloneDir, doLock) + ri, err = c.cloneGitProject(gitProject, cloneDir, doLock) if err != nil { return } @@ -120,7 +119,7 @@ func (c *LoadedKluctlProject) loadGitProject(ctx context.Context, gitProject *ty return } -func (c *LoadedKluctlProject) loadExternalProject(ctx context.Context, ep *types2.ExternalProject, defaultGitSubDir string, localDir string) (gitProjectInfo, error) { +func (c *LoadedKluctlProject) loadExternalProject(ep *types2.ExternalProject, defaultGitSubDir string, localDir string) (gitProjectInfo, error) { if localDir != "" { return c.localProject(localDir), nil } @@ -133,7 +132,7 @@ func (c *LoadedKluctlProject) loadExternalProject(ctx context.Context, ep *types if ep.Project != nil { // pointing to an actual external project, so let's try to clone it - return c.loadGitProject(ctx, ep.Project, defaultGitSubDir, true, true) + return c.loadGitProject(ep.Project, defaultGitSubDir, true, true) } // ExternalProject was provided but without an external repo url, so point into the kluctl project. @@ -147,7 +146,7 @@ func (c *LoadedKluctlProject) loadExternalProject(ctx context.Context, ep *types return c.localProject(p), nil } -func (c *LoadedKluctlProject) loadKluctlProject(ctx context.Context) error { +func (c *LoadedKluctlProject) loadKluctlProject() error { var err error if c.loadArgs.ProjectUrl == nil { @@ -158,7 +157,7 @@ func (c *LoadedKluctlProject) loadKluctlProject(ctx context.Context) error { return err } } else { - gi, err := c.loadGitProject(ctx, &types2.GitProject{ + gi, err := c.loadGitProject(&types2.GitProject{ Url: *c.loadArgs.ProjectUrl, Ref: c.loadArgs.ProjectRef, }, "", true, true) @@ -184,17 +183,17 @@ func (c *LoadedKluctlProject) loadKluctlProject(ctx context.Context) error { return err } - err = c.updateGitCaches(ctx) + err = c.updateGitCaches() if err != nil { return err } } - deploymentInfo, err := c.loadExternalProject(ctx, c.Config.Deployment, "", c.loadArgs.LocalDeployment) + deploymentInfo, err := c.loadExternalProject(c.Config.Deployment, "", c.loadArgs.LocalDeployment) if err != nil { return err } - sealedSecretsInfo, err := c.loadExternalProject(ctx, c.Config.SealedSecrets, ".sealed-secrets", c.loadArgs.LocalSealedSecrets) + sealedSecretsInfo, err := c.loadExternalProject(c.Config.SealedSecrets, ".sealed-secrets", c.loadArgs.LocalSealedSecrets) if err != nil { return err } @@ -203,14 +202,14 @@ func (c *LoadedKluctlProject) loadKluctlProject(ctx context.Context) error { clustersInfos = append(clustersInfos, c.localProject(c.loadArgs.LocalClusters)) } else if len(c.Config.Clusters.Projects) != 0 { for _, ep := range c.Config.Clusters.Projects { - info, err := c.loadExternalProject(ctx, &ep, "clusters", "") + info, err := c.loadExternalProject(&ep, "clusters", "") if err != nil { return err } clustersInfos = append(clustersInfos, info) } } else { - ci, err := c.loadExternalProject(ctx, nil, "clusters", "") + ci, err := c.loadExternalProject(nil, "clusters", "") if err != nil { return err } diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index e2038eb75..50d8a7f27 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -1,6 +1,7 @@ package kluctl_project import ( + "context" "fmt" "github.com/kluctl/kluctl/v2/pkg/deployment" "github.com/kluctl/kluctl/v2/pkg/jinja2" @@ -21,7 +22,7 @@ type TargetContext struct { DeploymentCollection *deployment.DeploymentCollection } -func (p *LoadedKluctlProject) NewTargetContext(clientConfigGetter func(context string) (*rest.Config, error), targetName string, clusterName string, dryRun bool, args map[string]string, forSeal bool, images *deployment.Images, inclusion *utils.Inclusion, renderOutputDir string) (*TargetContext, error) { +func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, clientConfigGetter func(context string) (*rest.Config, error), targetName string, clusterName string, dryRun bool, args map[string]string, forSeal bool, images *deployment.Images, inclusion *utils.Inclusion, renderOutputDir string) (*TargetContext, error) { deploymentDir, err := filepath.Abs(p.DeploymentDir) if err != nil { return nil, err @@ -59,7 +60,7 @@ func (p *LoadedKluctlProject) NewTargetContext(clientConfigGetter func(context s var k *k8s.K8sCluster if clientConfig != nil { - k, err = k8s.NewK8sCluster(clientConfig, dryRun) + k, err = k8s.NewK8sCluster(ctx, clientConfig, dryRun) if err != nil { return nil, err } @@ -110,12 +111,12 @@ func (p *LoadedKluctlProject) NewTargetContext(clientConfigGetter func(context s if err != nil { return nil, err } - c, err := deployment.NewDeploymentCollection(d, images, inclusion, renderOutputDir, forSeal) + c, err := deployment.NewDeploymentCollection(ctx, d, images, inclusion, renderOutputDir, forSeal) if err != nil { return nil, err } - ctx := &TargetContext{ + targetCtx := &TargetContext{ KluctlProject: p, Target: target, ClusterConfig: clusterConfig, @@ -124,5 +125,5 @@ func (p *LoadedKluctlProject) NewTargetContext(clientConfigGetter func(context s DeploymentCollection: c, } - return ctx, nil + return targetCtx, nil } diff --git a/pkg/kluctl_project/targets.go b/pkg/kluctl_project/targets.go index 426560204..baaff34d2 100644 --- a/pkg/kluctl_project/targets.go +++ b/pkg/kluctl_project/targets.go @@ -1,7 +1,6 @@ package kluctl_project import ( - "context" "fmt" securejoin "github.com/cyphar/filepath-securejoin" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" @@ -27,7 +26,7 @@ type dynamicTargetInfo struct { defaultBranch string } -func (c *LoadedKluctlProject) loadTargets(ctx context.Context) error { +func (c *LoadedKluctlProject) loadTargets() error { targetNames := make(map[string]bool) c.DynamicTargets = nil @@ -40,7 +39,7 @@ func (c *LoadedKluctlProject) loadTargets(ctx context.Context) error { targetInfos = append(targetInfos, l...) } - err := c.cloneDynamicTargets(ctx, targetInfos) + err := c.cloneDynamicTargets(targetInfos) if err != nil { return err } @@ -221,13 +220,13 @@ func (c *LoadedKluctlProject) matchRef(s string, pattern string) (bool, string, } } -func (c *LoadedKluctlProject) cloneDynamicTargets(ctx context.Context, dynamicTargets []*dynamicTargetInfo) error { +func (c *LoadedKluctlProject) cloneDynamicTargets(dynamicTargets []*dynamicTargetInfo) error { wp := utils.NewDebuggerAwareWorkerPool(8) defer wp.StopWait(false) // lock all involved repos first for _, mr := range c.mirroredRepos { - err := mr.Lock(ctx) + err := mr.Lock() if err != nil { return err } @@ -252,7 +251,7 @@ func (c *LoadedKluctlProject) cloneDynamicTargets(ctx context.Context, dynamicTa gitProject := *targetInfo.gitProject gitProject.Ref = *targetInfo.ref - gi, err := c.loadGitProject(ctx, &gitProject, "", false, false) + gi, err := c.loadGitProject(&gitProject, "", false, false) mutex.Lock() defer mutex.Unlock() if err != nil { diff --git a/pkg/registries/registries.go b/pkg/registries/registries.go index da1c32496..93f11f95b 100644 --- a/pkg/registries/registries.go +++ b/pkg/registries/registries.go @@ -40,6 +40,8 @@ type transportKeyType int var transportKey transportKeyType type RegistryHelper struct { + ctx context.Context + authEntries []AuthEntry cachedTransports utils.ThreadSafeCache @@ -60,8 +62,10 @@ type AuthEntry struct { Insecure bool } -func NewRegistryHelper() *RegistryHelper { - return &RegistryHelper{} +func NewRegistryHelper(ctx context.Context) *RegistryHelper { + return &RegistryHelper{ + ctx: ctx, + } } func (rh *RegistryHelper) ListImageTags(image string) ([]string, error) { @@ -79,7 +83,7 @@ func (rh *RegistryHelper) ListImageTags(image string) ([]string, error) { remoteOpts := []remote.Option{ remote.WithAuthFromKeychain(rh), remote.WithTransport(rh), - remote.WithContext(context.WithValue(context.Background(), transportKey, t)), + remote.WithContext(context.WithValue(rh.ctx, transportKey, t)), } e := rh.findAuthEntry(repo.RegistryStr()) From 132b0833058848fc58edbeab7c781f6f5adf3778 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sat, 7 May 2022 18:14:20 +0200 Subject: [PATCH 0773/2916] fix: Proper timeout/deadline handling in ApplyUtil --- cmd/kluctl/args/misc.go | 2 +- cmd/kluctl/args/project.go | 2 +- cmd/kluctl/commands/cmd_deploy.go | 2 +- pkg/deployment/commands/deploy.go | 4 +-- pkg/deployment/commands/diff.go | 2 +- pkg/deployment/commands/downscale.go | 2 +- pkg/deployment/commands/poke_images.go | 2 +- pkg/deployment/commands/validate.go | 2 +- pkg/deployment/utils/apply_utils.go | 47 +++++++++++++++++++------- 9 files changed, 44 insertions(+), 21 deletions(-) diff --git a/cmd/kluctl/args/misc.go b/cmd/kluctl/args/misc.go index c4972ca44..86ef05ae5 100644 --- a/cmd/kluctl/args/misc.go +++ b/cmd/kluctl/args/misc.go @@ -22,7 +22,7 @@ type ReplaceOnErrorFlags struct { } type HookFlags struct { - HookTimeout time.Duration `group:"misc" help:"Maximum time to wait for hook readiness. The timeout is meant per-hook. Timeouts are in the duration format (1s, 1m, 1h, ...). If not specified, a default timeout of 5m is used." default:"5m"` + ReadinessTimeout time.Duration `group:"misc" help:"Maximum time to wait for object readiness. The timeout is meant per-object. Timeouts are in the duration format (1s, 1m, 1h, ...). If not specified, a default timeout of 5m is used." default:"5m"` } type IgnoreFlags struct { diff --git a/cmd/kluctl/args/project.go b/cmd/kluctl/args/project.go index 120e1fabd..ab9da298f 100644 --- a/cmd/kluctl/args/project.go +++ b/cmd/kluctl/args/project.go @@ -15,7 +15,7 @@ type ProjectFlags struct { OutputMetadata pathType `group:"project" help:"Specify the output path for the project metadata to be written to. When used with the 'archive' command, it will also cause the archive to not include the metadata.yaml file."` Cluster string `group:"project" help:"Specify/Override cluster"` - Timeout time.Duration `group:"project" help:"Specify timeout for all operations, including loading of the project, all external api calls and waiting for readiness." default:"5m"` + Timeout time.Duration `group:"project" help:"Specify timeout for all operations, including loading of the project, all external api calls and waiting for readiness." default:"10m"` GitCacheUpdateInterval time.Duration `group:"project" help:"Specify the time to wait between git cache updates. Defaults to not wait at all and always updating caches."` } diff --git a/cmd/kluctl/commands/cmd_deploy.go b/cmd/kluctl/commands/cmd_deploy.go index c99d69319..a0db7c000 100644 --- a/cmd/kluctl/commands/cmd_deploy.go +++ b/cmd/kluctl/commands/cmd_deploy.go @@ -54,7 +54,7 @@ func (cmd *deployCmd) runCmdDeploy(ctx *commandCtx) error { cmd2.ReplaceOnError = cmd.ReplaceOnError cmd2.ForceReplaceOnError = cmd.ForceReplaceOnError cmd2.AbortOnError = cmd.AbortOnError - cmd2.HookTimeout = cmd.HookTimeout + cmd2.ReadinessTimeout = cmd.ReadinessTimeout cmd2.NoWait = cmd.NoWait cb := cmd.diffResultCb diff --git a/pkg/deployment/commands/deploy.go b/pkg/deployment/commands/deploy.go index d61461d70..575fb26a0 100644 --- a/pkg/deployment/commands/deploy.go +++ b/pkg/deployment/commands/deploy.go @@ -16,7 +16,7 @@ type DeployCommand struct { ReplaceOnError bool ForceReplaceOnError bool AbortOnError bool - HookTimeout time.Duration + ReadinessTimeout time.Duration NoWait bool } @@ -42,7 +42,7 @@ func (cmd *DeployCommand) Run(ctx context.Context, k *k8s.K8sCluster, diffResult ForceReplaceOnError: cmd.ForceReplaceOnError, DryRun: true, AbortOnError: false, - WaitObjectTimeout: cmd.HookTimeout, + ReadinessTimeout: cmd.ReadinessTimeout, NoWait: cmd.NoWait, } diff --git a/pkg/deployment/commands/diff.go b/pkg/deployment/commands/diff.go index 5dafbe374..0bdd5b9e1 100644 --- a/pkg/deployment/commands/diff.go +++ b/pkg/deployment/commands/diff.go @@ -40,7 +40,7 @@ func (cmd *DiffCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*types.Comm ForceReplaceOnError: cmd.ForceReplaceOnError, DryRun: true, AbortOnError: false, - WaitObjectTimeout: 0, + ReadinessTimeout: 0, } au := utils.NewApplyDeploymentsUtil(ctx, dew, cmd.c.Deployments, ru, k, o) au.ApplyDeployments() diff --git a/pkg/deployment/commands/downscale.go b/pkg/deployment/commands/downscale.go index 3f0ee568d..ce51e6e35 100644 --- a/pkg/deployment/commands/downscale.go +++ b/pkg/deployment/commands/downscale.go @@ -40,7 +40,7 @@ func (cmd *DownscaleCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*types if !d.CheckInclusionForDeploy() { continue } - au := ad.NewApplyUtil(utils2.NewProgressCtx(nil, d.RelToProjectItemDir, 0, true)) + au := ad.NewApplyUtil(ctx, utils2.NewProgressCtx(nil, d.RelToProjectItemDir, 0, true)) for _, o := range d.Objects { o := o ref := o.GetK8sRef() diff --git a/pkg/deployment/commands/poke_images.go b/pkg/deployment/commands/poke_images.go index 9c8a50ad6..e2dc24b23 100644 --- a/pkg/deployment/commands/poke_images.go +++ b/pkg/deployment/commands/poke_images.go @@ -76,7 +76,7 @@ func (cmd *PokeImagesCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*type wg.Add(1) go func() { defer wg.Done() - au := ad.NewApplyUtil(utils2.NewProgressCtx(nil, ref.String(), 0, true)) + au := ad.NewApplyUtil(ctx, utils2.NewProgressCtx(nil, ref.String(), 0, true)) au.ReplaceObject(ref, ru.GetRemoteObject(ref), func(o *uo.UnstructuredObject) (*uo.UnstructuredObject, error) { return doPokeImage(containers, o) }) diff --git a/pkg/deployment/commands/validate.go b/pkg/deployment/commands/validate.go index 8e35d5e54..11716e66d 100644 --- a/pkg/deployment/commands/validate.go +++ b/pkg/deployment/commands/validate.go @@ -40,7 +40,7 @@ func (cmd *ValidateCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*types. if !d.CheckInclusionForDeploy() { continue } - au := ad.NewApplyUtil(utils2.NewProgressCtx(nil, d.RelToProjectItemDir, 0, true)) + au := ad.NewApplyUtil(ctx, utils2.NewProgressCtx(nil, d.RelToProjectItemDir, 0, true)) h := utils2.NewHooksUtil(au) for _, o := range d.Objects { hook := h.GetHook(o) diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index ce8c0b31e..b14afa455 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -31,11 +31,13 @@ type ApplyUtilOptions struct { ForceReplaceOnError bool DryRun bool AbortOnError bool - WaitObjectTimeout time.Duration + ReadinessTimeout time.Duration NoWait bool } type ApplyUtil struct { + ctx context.Context + dew *DeploymentErrorsAndWarnings errorCount int warningCount int @@ -83,8 +85,9 @@ func NewApplyDeploymentsUtil(ctx context.Context, dew *DeploymentErrorsAndWarnin return ret } -func (ad *ApplyDeploymentsUtil) NewApplyUtil(pctx *progressCtx) *ApplyUtil { +func (ad *ApplyDeploymentsUtil) NewApplyUtil(ctx context.Context, pctx *progressCtx) *ApplyUtil { ret := &ApplyUtil{ + ctx: ctx, dew: ad.dew, appliedObjects: map[k8s2.ObjectRef]*uo.UnstructuredObject{}, appliedHookObjects: map[k8s2.ObjectRef]*uo.UnstructuredObject{}, @@ -132,6 +135,10 @@ func (a *ApplyUtil) HandleError(ref k8s2.ObjectRef, err error) { a.mutex.Lock() defer a.mutex.Unlock() + if errors2.Is(err, context.DeadlineExceeded) || errors2.Is(err, context.Canceled) { + a.abortSignal.Store(true) + } + if a.o.AbortOnError && a.abortSignal != nil { a.abortSignal.Store(true) } @@ -356,8 +363,9 @@ func (a *ApplyUtil) WaitReadiness(ref k8s2.ObjectRef, timeout time.Duration) boo } if timeout == 0 { - timeout = a.o.WaitObjectTimeout + timeout = a.o.ReadinessTimeout } + timeoutTimer := time.NewTimer(timeout) a.pctx.Debugf("Waiting for %s to get ready", ref.String()) @@ -397,13 +405,6 @@ func (a *ApplyUtil) WaitReadiness(ref k8s2.ObjectRef, timeout time.Duration) boo return false } - if timeout > 0 && time.Now().Sub(startTime) >= timeout { - err := fmt.Errorf("timed out while waiting for %s", ref.String()) - a.pctx.Warningf("%s (%ds elapsed)", err.Error(), elapsed) - a.HandleError(ref, err) - return false - } - a.pctx.SetStatus(fmt.Sprintf("Waiting for %s to get ready...", ref.String())) if !didLog { @@ -415,7 +416,20 @@ func (a *ApplyUtil) WaitReadiness(ref k8s2.ObjectRef, timeout time.Duration) boo lastLogTime = time.Now() } - time.Sleep(500 * time.Millisecond) + select { + case <-time.After(500 * time.Millisecond): + continue + case <-timeoutTimer.C: + err := fmt.Errorf("timed out while waiting for readiness of %s", ref.String()) + a.pctx.Warningf("%s (%ds elapsed)", err.Error(), elapsed) + a.HandleError(ref, err) + return false + case <-a.ctx.Done(): + err := fmt.Errorf("failed waiting for readiness of %s: %w", ref.String(), err) + a.pctx.Warningf("%s (%ds elapsed)", err.Error(), elapsed) + a.HandleError(ref, err) + return false + } } return false } @@ -505,6 +519,9 @@ func (a *ApplyUtil) applyDeploymentItem(d *deployment.DeploymentItem) { a.WaitReadiness(o.GetK8sRef(), 0) } } + if a.abortSignal.Load().(bool) { + return + } h.RunHooks(postHooks) @@ -579,7 +596,7 @@ func (a *ApplyDeploymentsUtil) ApplyDeployments() { } else { pctx = NewProgressCtx(nil, "", 0, false) } - a2 := a.NewApplyUtil(pctx) + a2 := a.NewApplyUtil(a.ctx, pctx) wg.Add(1) go func() { @@ -587,6 +604,7 @@ func (a *ApplyDeploymentsUtil) ApplyDeployments() { defer sem.Release(1) a2.applyDeploymentItem(d) + pctx.Finish() }() barrier := (d.Config.Barrier != nil && *d.Config.Barrier) || d.Barrier @@ -622,6 +640,11 @@ func (a *ApplyDeploymentsUtil) ApplyDeployments() { func (a *ApplyUtil) ReplaceObject(ref k8s2.ObjectRef, firstVersion *uo.UnstructuredObject, callback func(o *uo.UnstructuredObject) (*uo.UnstructuredObject, error)) { firstCall := true for true { + if a.ctx.Err() != nil { + a.HandleError(ref, fmt.Errorf("failed replacing %s: %w", ref.String(), a.ctx.Err())) + return + } + var remote *uo.UnstructuredObject if firstCall && firstVersion != nil { remote = firstVersion From 5b5d61e709aef30957e44329387db3f6e4e4ac6e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sun, 8 May 2022 14:19:53 +0200 Subject: [PATCH 0774/2916] fix: Support int flags in buildCobraArg --- cmd/kluctl/commands/cobra_utils.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/cmd/kluctl/commands/cobra_utils.go b/cmd/kluctl/commands/cobra_utils.go index 17ed89eee..77260d052 100644 --- a/cmd/kluctl/commands/cobra_utils.go +++ b/cmd/kluctl/commands/cobra_utils.go @@ -181,6 +181,16 @@ func (c *rootCommand) buildCobraArg(cg *commandAndGroups, f reflect.StructField, parsedDefault = x } cg.cmd.PersistentFlags().BoolVarP(v2.(*bool), name, shortFlag, parsedDefault, help) + case *int: + parsedDefault := 0 + if defaultValue != "" { + x, err := strconv.ParseInt(defaultValue, 0, 32) + if err != nil { + return err + } + parsedDefault = int(x) + } + cg.cmd.PersistentFlags().IntVarP(v2.(*int), name, shortFlag, parsedDefault, help) case *time.Duration: var parsedDefault time.Duration if defaultValue != "" { From 7c367052712dda93fe391aea25ac35bdf9c58375 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sun, 8 May 2022 14:20:05 +0200 Subject: [PATCH 0775/2916] refactor: Remove unused code --- pkg/utils/uo/unstructured.go | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/pkg/utils/uo/unstructured.go b/pkg/utils/uo/unstructured.go index 118b76df1..f0793e843 100644 --- a/pkg/utils/uo/unstructured.go +++ b/pkg/utils/uo/unstructured.go @@ -2,20 +2,8 @@ package uo import ( "fmt" - "github.com/kluctl/kluctl/v2/pkg/utils" - log "github.com/sirupsen/logrus" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) -func CopyUnstructured(u *unstructured.Unstructured) *unstructured.Unstructured { - var ret unstructured.Unstructured - err := utils.DeepCopy(&ret.Object, &u.Object) - if err != nil { - log.Fatal(err) - } - return &ret -} - func MergeStrMap(a map[string]string, b map[string]string) { for k, v := range b { a[k] = v From 11cfc884212210df903a0fd8debb32d3e0de7d75 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sun, 8 May 2022 14:19:32 +0200 Subject: [PATCH 0776/2916] refactor: Use custom status handling instead of logrus for user facing status --- .../commands/cmd_check_image_updates.go | 6 +- cmd/kluctl/commands/cmd_delete.go | 2 +- cmd/kluctl/commands/cmd_helm_pull.go | 12 +- cmd/kluctl/commands/cmd_helm_update.go | 30 +++-- cmd/kluctl/commands/cmd_prune.go | 2 +- cmd/kluctl/commands/cmd_render.go | 4 +- cmd/kluctl/commands/cmd_seal.go | 32 +++--- cmd/kluctl/commands/cmd_validate.go | 2 +- cmd/kluctl/commands/cobra_utils.go | 3 +- cmd/kluctl/commands/completion.go | 12 +- cmd/kluctl/commands/root.go | 36 +++--- cmd/kluctl/commands/utils.go | 7 +- pkg/deployment/commands/delete.go | 5 +- pkg/deployment/commands/deploy.go | 2 +- pkg/deployment/commands/diff.go | 2 +- pkg/deployment/commands/downscale.go | 4 +- pkg/deployment/commands/poke_images.go | 4 +- pkg/deployment/commands/prune.go | 5 +- pkg/deployment/commands/validate.go | 6 +- pkg/deployment/deployment_collection.go | 23 +++- pkg/deployment/deployment_item.go | 2 +- pkg/deployment/deployment_project.go | 16 ++- pkg/deployment/helm_chart.go | 15 +-- pkg/deployment/utils/apply_utils.go | 12 +- pkg/deployment/utils/progress.go | 24 ++-- pkg/deployment/utils/remote_objects_utils.go | 14 ++- pkg/diff/diff.go | 3 +- pkg/diff/managed_fields.go | 3 +- pkg/git/auth/auth_provider.go | 7 +- pkg/git/auth/env_auth_provider.go | 11 +- pkg/git/auth/git_credentials_file.go | 17 +-- pkg/git/auth/list_auth_provider.go | 7 +- pkg/git/auth/ssh_auth_provider.go | 45 ++++---- pkg/git/mirrored_repo.go | 27 ++--- pkg/jinja2/generate/main.go | 11 +- pkg/jinja2/source.go | 3 +- pkg/k8s/k8s_cluster.go | 14 +-- pkg/kluctl_project/project_load.go | 4 +- pkg/kluctl_project/target_context.go | 2 +- pkg/kluctl_project/targets.go | 6 +- pkg/python/embed.go | 7 +- pkg/registries/registries.go | 8 +- pkg/seal/bootstrap.go | 7 +- pkg/seal/fetch_cert.go | 9 +- pkg/seal/sealer.go | 23 ++-- pkg/status/status.go | 107 ++++++++++++++++++ pkg/status/status_handler.go | 67 +++++++++++ pkg/utils/env.go | 3 +- pkg/utils/prompts.go | 13 ++- pkg/utils/uo/jsonpath.go | 3 +- pkg/utils/uo/k8s_fields.go | 38 +++---- pkg/utils/uo/object_iterator.go | 8 +- pkg/utils/uo/uo.go | 5 +- pkg/utils/utils.go | 3 +- pkg/utils/versions/latest_version.go | 5 +- pkg/utils/versions/latest_version_parse.go | 3 +- 56 files changed, 488 insertions(+), 263 deletions(-) create mode 100644 pkg/status/status.go create mode 100644 pkg/status/status_handler.go diff --git a/cmd/kluctl/commands/cmd_check_image_updates.go b/cmd/kluctl/commands/cmd_check_image_updates.go index c8956e026..1da6883a3 100644 --- a/cmd/kluctl/commands/cmd_check_image_updates.go +++ b/cmd/kluctl/commands/cmd_check_image_updates.go @@ -3,9 +3,9 @@ package commands import ( "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/registries" + "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/versions" - log "github.com/sirupsen/logrus" "os" "regexp" "sort" @@ -80,7 +80,7 @@ func runCheckImageUpdates(ctx *commandCtx) error { for _, image := range images { s := strings.SplitN(image, ":", 2) if len(s) == 1 { - log.Warningf("%s: Ignoring image %s as it doesn't specify a tag", ref.String(), image) + status.Warning(ctx.ctx, "%s: Ignoring image %s as it doesn't specify a tag", ref.String(), image) continue } repo := s[0] @@ -88,7 +88,7 @@ func runCheckImageUpdates(ctx *commandCtx) error { repoTags, _ := imageTags[repo].([]string) err, _ := imageTags[repo].(error) if err != nil { - log.Warningf("%s: Failed to list tags for %s. %v", ref.String(), repo, err) + status.Warning(ctx.ctx, "%s: Failed to list tags for %s. %v", ref.String(), repo, err) continue } diff --git a/cmd/kluctl/commands/cmd_delete.go b/cmd/kluctl/commands/cmd_delete.go index ebb02bfea..2cd7607fa 100644 --- a/cmd/kluctl/commands/cmd_delete.go +++ b/cmd/kluctl/commands/cmd_delete.go @@ -55,7 +55,7 @@ func (cmd *deleteCmd) Run() error { cmd2.OverrideDeleteByLabels = deleteByLabels - objects, err := cmd2.Run(ctx.targetCtx.K) + objects, err := cmd2.Run(ctx.ctx, ctx.targetCtx.K) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_helm_pull.go b/cmd/kluctl/commands/cmd_helm_pull.go index 38aec11c5..6d5d5b6fe 100644 --- a/cmd/kluctl/commands/cmd_helm_pull.go +++ b/cmd/kluctl/commands/cmd_helm_pull.go @@ -1,8 +1,9 @@ package commands import ( + "context" "github.com/kluctl/kluctl/v2/pkg/deployment" - log "github.com/sirupsen/logrus" + "github.com/kluctl/kluctl/v2/pkg/status" "io/fs" "path/filepath" ) @@ -17,7 +18,7 @@ func (cmd *helmPullCmd) Help() string { pulling is only needed when really required (e.g. when the chart version changes).` } -func (cmd *helmPullCmd) Run() error { +func (cmd *helmPullCmd) Run(ctx context.Context) error { rootPath := "." if cmd.LocalDeployment != "" { rootPath = cmd.LocalDeployment @@ -25,15 +26,18 @@ func (cmd *helmPullCmd) Run() error { err := filepath.WalkDir(rootPath, func(p string, d fs.DirEntry, err error) error { fname := filepath.Base(p) if fname == "helm-chart.yml" || fname == "helm-chart.yaml" { - log.Infof("Pulling for %s", p) + s := status.Start(ctx, "Pulling for %s", p) chart, err := deployment.NewHelmChart(p) if err != nil { + s.FailedWithMessage(err.Error()) return err } - err = chart.Pull() + err = chart.Pull(ctx) if err != nil { + s.FailedWithMessage(err.Error()) return err } + s.Success() } return nil }) diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go index f74b41aaa..b9f9d8caf 100644 --- a/cmd/kluctl/commands/cmd_helm_update.go +++ b/cmd/kluctl/commands/cmd_helm_update.go @@ -1,11 +1,12 @@ package commands import ( + "context" "fmt" "github.com/go-git/go-git/v5" "github.com/kluctl/kluctl/v2/pkg/deployment" git2 "github.com/kluctl/kluctl/v2/pkg/git" - log "github.com/sirupsen/logrus" + "github.com/kluctl/kluctl/v2/pkg/status" "io/fs" "path/filepath" ) @@ -20,7 +21,7 @@ func (cmd *helmUpdateCmd) Help() string { return `Optionally performs the actual upgrade and/or add a commit to version control.` } -func (cmd *helmUpdateCmd) Run() error { +func (cmd *helmUpdateCmd) Run(ctx context.Context) error { rootPath := "." if cmd.LocalDeployment != "" { rootPath = cmd.LocalDeployment @@ -37,6 +38,11 @@ func (cmd *helmUpdateCmd) Run() error { if err != nil { return err } + + statusPrefix := filepath.Base(p) + s := status.Start(ctx, "%s: Checking for updates", statusPrefix) + defer s.Failed() + newVersion, updated, err := chart.CheckUpdate() if err != nil { return err @@ -44,11 +50,12 @@ func (cmd *helmUpdateCmd) Run() error { if !updated { return nil } - log.Infof("Chart %s has new version %s available. Old version is %s.", p, newVersion, *chart.Config.ChartVersion) + s.Update(fmt.Sprintf("Chart has new version %s available. Old version is %s.", newVersion, *chart.Config.ChartVersion)) if cmd.Upgrade { if chart.Config.SkipUpdate != nil && *chart.Config.SkipUpdate { - log.Infof("NOT upgrading chart %s as skipUpdate was set to true", p) + s.Update("%s: NOT upgrading chart as skipUpdate was set to true", statusPrefix) + s.Success() return nil } @@ -75,8 +82,10 @@ func (cmd *helmUpdateCmd) Run() error { return err } - log.Infof("Pulling for %s", p) - err = chart.Pull() + s.Update("%s: Pulling new version", statusPrefix) + defer s.Failed() + + err = chart.Pull(ctx) if err != nil { return err } @@ -93,8 +102,10 @@ func (cmd *helmUpdateCmd) Run() error { } if cmd.Commit { - msg := fmt.Sprintf("Updated helm chart %s from %s to %s", filepath.Dir(p), oldVersion, newVersion) - log.Infof("Committing: %s", msg) + commitMsg := fmt.Sprintf("Updated helm chart %s from %s to %s", filepath.Dir(p), oldVersion, newVersion) + + s.Update(fmt.Sprintf("%s: Updating chart from %s to %s", statusPrefix, oldVersion, newVersion)) + r, err := git.PlainOpen(gitRootPath) if err != nil { return err @@ -117,11 +128,12 @@ func (cmd *helmUpdateCmd) Run() error { return err } } - _, err = wt.Commit(msg, &git.CommitOptions{}) + _, err = wt.Commit(commitMsg, &git.CommitOptions{}) if err != nil { return err } } + s.Success() } } return nil diff --git a/cmd/kluctl/commands/cmd_prune.go b/cmd/kluctl/commands/cmd_prune.go index e24578871..602d3f3a2 100644 --- a/cmd/kluctl/commands/cmd_prune.go +++ b/cmd/kluctl/commands/cmd_prune.go @@ -43,7 +43,7 @@ func (cmd *pruneCmd) Run() error { func (cmd *pruneCmd) runCmdPrune(ctx *commandCtx) error { cmd2 := commands.NewPruneCommand(ctx.targetCtx.DeploymentCollection) - objects, err := cmd2.Run(ctx.targetCtx.K) + objects, err := cmd2.Run(ctx.ctx, ctx.targetCtx.K) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_render.go b/cmd/kluctl/commands/cmd_render.go index 849175838..c22b242af 100644 --- a/cmd/kluctl/commands/cmd_render.go +++ b/cmd/kluctl/commands/cmd_render.go @@ -2,8 +2,8 @@ package commands import ( "github.com/kluctl/kluctl/v2/cmd/kluctl/args" + "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/utils" - log "github.com/sirupsen/logrus" "io/ioutil" ) @@ -37,7 +37,7 @@ func (cmd *renderCmd) Run() error { renderOutputDirFlags: cmd.RenderOutputDirFlags, } return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { - log.Infof("Rendered into %s", ctx.targetCtx.DeploymentCollection.RenderDir) + status.Info(ctx.ctx, "Rendered into %s", ctx.targetCtx.DeploymentCollection.RenderDir) return nil }) } diff --git a/cmd/kluctl/commands/cmd_seal.go b/cmd/kluctl/commands/cmd_seal.go index 5df4a45f1..b7a4c01f4 100644 --- a/cmd/kluctl/commands/cmd_seal.go +++ b/cmd/kluctl/commands/cmd_seal.go @@ -7,9 +7,9 @@ import ( "github.com/kluctl/kluctl/v2/pkg/deployment/commands" "github.com/kluctl/kluctl/v2/pkg/kluctl_project" "github.com/kluctl/kluctl/v2/pkg/seal" + "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils/uo" - log "github.com/sirupsen/logrus" ) type sealCmd struct { @@ -63,7 +63,13 @@ func loadSecrets(ctx *commandCtx, target *types.Target, secretsLoader *seal.Secr } func (cmd *sealCmd) runCmdSealForTarget(ctx context.Context, p *kluctl_project.LoadedKluctlProject, targetName string, secretsLoader *seal.SecretsLoader) error { - log.Infof("Sealing for target %s", targetName) + s := status.Start(ctx, "%s: Sealing for target", targetName) + defer s.FailedWithMessage("%s: Sealing failed", targetName) + + doFail := func(err error) error { + s.FailedWithMessage(fmt.Sprintf("Sealing failed: %v", err)) + return err + } ptArgs := projectTargetCommandArgs{ projectFlags: cmd.ProjectFlags, @@ -76,11 +82,11 @@ func (cmd *sealCmd) runCmdSealForTarget(ctx context.Context, p *kluctl_project.L return withProjectTargetCommandContext(ctx, ptArgs, p, func(ctx *commandCtx) error { err := loadSecrets(ctx, ctx.targetCtx.Target, secretsLoader) if err != nil { - return err + return doFail(err) } err = ctx.targetCtx.DeploymentCollection.RenderDeployments(ctx.targetCtx.K) if err != nil { - return err + return doFail(err) } sealedSecretsNamespace := "kube-system" @@ -94,28 +100,29 @@ func (cmd *sealCmd) runCmdSealForTarget(ctx context.Context, p *kluctl_project.L } } if p.Config.SecretsConfig == nil || p.Config.SecretsConfig.SealedSecrets == nil || p.Config.SecretsConfig.SealedSecrets.Bootstrap == nil || *p.Config.SecretsConfig.SealedSecrets.Bootstrap { - err = seal.BootstrapSealedSecrets(ctx.targetCtx.K, sealedSecretsNamespace) + err = seal.BootstrapSealedSecrets(ctx.ctx, ctx.targetCtx.K, sealedSecretsNamespace) if err != nil { - return err + return doFail(err) } } clusterConfig, err := p.LoadClusterConfig(ctx.targetCtx.Target.Cluster) if err != nil { - return err + return doFail(err) } - sealer, err := seal.NewSealer(ctx.targetCtx.K, sealedSecretsNamespace, sealedSecretsControllerName, clusterConfig.Cluster, cmd.ForceReseal) + sealer, err := seal.NewSealer(ctx.ctx, ctx.targetCtx.K, sealedSecretsNamespace, sealedSecretsControllerName, clusterConfig.Cluster, cmd.ForceReseal) if err != nil { - return err + return doFail(err) } cmd2 := commands.NewSealCommand(ctx.targetCtx.DeploymentCollection) err = cmd2.Run(sealer) if err != nil { - return err + return doFail(err) } - return err + s.Success() + return nil }) } @@ -135,7 +142,7 @@ func (cmd *sealCmd) Run() error { continue } if target.Target.SealingConfig == nil { - log.Infof("Target %s has no sealingConfig", target.Target.Name) + status.Info(ctx, "Target %s has no sealingConfig", target.Target.Name) continue } noTargetMatch = false @@ -158,7 +165,6 @@ func (cmd *sealCmd) Run() error { err := cmd.runCmdSealForTarget(ctx, p, sealTarget.Name, secretsLoader) if err != nil { - log.Warningf("Sealing for target %s failed: %v", sealTarget.Name, err) hadError = true } } diff --git a/cmd/kluctl/commands/cmd_validate.go b/cmd/kluctl/commands/cmd_validate.go index 6f59906e2..308f1abfb 100644 --- a/cmd/kluctl/commands/cmd_validate.go +++ b/cmd/kluctl/commands/cmd_validate.go @@ -37,7 +37,7 @@ func (cmd *validateCmd) Run() error { } return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { startTime := time.Now() - cmd2 := commands.NewValidateCommand(ctx.targetCtx.DeploymentCollection) + cmd2 := commands.NewValidateCommand(ctx.ctx, ctx.targetCtx.DeploymentCollection) for true { result, err := cmd2.Run(ctx.ctx, ctx.targetCtx.K) if err != nil { diff --git a/cmd/kluctl/commands/cobra_utils.go b/cmd/kluctl/commands/cobra_utils.go index 77260d052..c45c33f14 100644 --- a/cmd/kluctl/commands/cobra_utils.go +++ b/cmd/kluctl/commands/cobra_utils.go @@ -3,7 +3,6 @@ package commands import ( "fmt" "github.com/kluctl/kluctl/v2/pkg/utils" - log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/spf13/viper" @@ -302,7 +301,7 @@ func (c *rootCommand) buildGroupedFlagSets(cg *commandAndGroups) map[string]*pfl x.cmd.PersistentFlags().VisitAll(func(flag *pflag.Flag) { group, ok := x.groups[flag.Name] if !ok { - log.Panicf("group for %s not found", flag.Name) + panic(fmt.Sprintf("group for %s not found", flag.Name)) } fl, ok := flagsByGroups[group] if !ok { diff --git a/cmd/kluctl/commands/completion.go b/cmd/kluctl/commands/completion.go index 99cfdd5b1..1e2b383a4 100644 --- a/cmd/kluctl/commands/completion.go +++ b/cmd/kluctl/commands/completion.go @@ -4,10 +4,10 @@ import ( "context" "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/kluctl_project" + "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/yaml" - log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "os" "path/filepath" @@ -77,7 +77,7 @@ func buildClusterCompletionFunc(projectArgs *args.ProjectFlags) func(cmd *cobra. return nil }) if err != nil { - log.Error(err) + status.Error(cliCtx, err.Error()) return nil, cobra.ShellCompDirectiveError } return ret, cobra.ShellCompDirectiveDefault @@ -94,7 +94,7 @@ func buildTargetCompletionFunc(projectArgs *args.ProjectFlags) func(cmd *cobra.C return nil }) if err != nil { - log.Error(err) + status.Error(cliCtx, err.Error()) return nil, cobra.ShellCompDirectiveError } return ret, cobra.ShellCompDirectiveDefault @@ -165,7 +165,7 @@ func buildInclusionCompletionFunc(cmdStruct interface{}, forDirs bool) func(cmd return nil }) if err != nil { - log.Error(err) + status.Error(cliCtx, err.Error()) return nil, cobra.ShellCompDirectiveError } if forDirs { @@ -206,7 +206,7 @@ func buildImagesCompletionFunc(cmdStruct interface{}) func(cmd *cobra.Command, a _ = withProjectTargetCommandContext(ctx, ptArgs, p, func(ctx *commandCtx) error { err := ctx.targetCtx.DeploymentCollection.Prepare(nil) if err != nil { - log.Error(err) + status.Error(cliCtx, err.Error()) } mutex.Lock() @@ -233,7 +233,7 @@ func buildImagesCompletionFunc(cmdStruct interface{}) func(cmd *cobra.Command, a return nil }) if err != nil { - log.Error(err) + status.Error(cliCtx, err.Error()) return nil, cobra.ShellCompDirectiveError } return images.ListKeys(), cobra.ShellCompDirectiveNoSpace diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index a4f1c35bc..44e4ef541 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -16,12 +16,14 @@ limitations under the License. package commands import ( + "context" + "fmt" + "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/utils/versions" "github.com/kluctl/kluctl/v2/pkg/version" "github.com/kluctl/kluctl/v2/pkg/yaml" - log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/viper" "net/http" @@ -34,8 +36,8 @@ import ( const latestReleaseUrl = "https://api.github.com/repos/kluctl/kluctl/releases/latest" type cli struct { - Verbosity string `group:"global" short:"v" help:"Log level (debug, info, warn, error, fatal, panic)." default:"info"` - NoUpdateCheck bool `group:"global" help:"Disable update check on startup"` + Debug bool `group:"global" help:"Enable debug logging"` + NoUpdateCheck bool `group:"global" help:"Disable update check on startup"` Archive archiveCmd `cmd:"" help:"Write project and all related components into single tgz"` CheckImageUpdates checkImageUpdatesCmd `cmd:"" help:"Render deployment and check if any images have new tags available"` @@ -63,14 +65,12 @@ var flagGroups = []groupInfo{ {group: "inclusion", title: "Inclusion/Exclusion arguments:", description: "Control inclusion/exclusion."}, {group: "misc", title: "Misc arguments:", description: "Command specific arguments."}, } -var globalFlagGroup = &flagGroups[len(flagGroups)-1] -func (c *cli) setupLogs() error { - lvl, err := log.ParseLevel(c.Verbosity) - if err != nil { - return err - } - log.SetLevel(lvl) +var cliCtx = context.Background() + +func (c *cli) setupStatusHandler() error { + sh := status.NewMultiLineStatusHandler(os.Stderr, c.Debug) + cliCtx = status.NewContext(cliCtx, sh) return nil } @@ -98,7 +98,8 @@ func (c *cli) checkNewVersion() { versionCheckState.LastVersionCheck = time.Now() _ = yaml.WriteYamlFile(versionCheckPath, &versionCheckState) - log.Debugf("Checking for new kluctl version") + s := status.Start(cliCtx, "Checking for new kluctl version") + defer s.Failed() r, err := http.Get(latestReleaseUrl) if err != nil { @@ -122,12 +123,15 @@ func (c *cli) checkNewVersion() { latestVersion := versions.LooseVersion(latestVersionStr) localVersion := versions.LooseVersion(version.GetVersion()) if localVersion.Less(latestVersion, true) { - log.Warningf("You are using an outdated version (%v) of kluctl. You should update soon to version %v", localVersion, latestVersion) + s.Update(fmt.Sprintf("You are using an outdated version (%v) of kluctl. You should update soon to version %v", localVersion, latestVersion)) + } else { + s.Update("Your kluctl version is up-to-date") } + s.Success() } func (c *cli) preRun() error { - if err := c.setupLogs(); err != nil { + if err := c.setupStatusHandler(); err != nil { return err } c.checkNewVersion() @@ -141,7 +145,7 @@ func initViper() { viper.AddConfigPath("$HOME/.kluctl") if err := viper.ReadInConfig(); err != nil { if _, ok := err.(viper.ConfigFileNotFoundError); !ok { - log.Error(err) + status.Error(cliCtx, err.Error()) os.Exit(1) } } @@ -160,7 +164,7 @@ composed of multiple smaller parts (Helm/Kustomize/...) in a manageable and unif flagGroups) if err != nil { - log.Fatal(err) + panic(err) } rootCmd.Version = version.GetVersion() @@ -178,7 +182,7 @@ composed of multiple smaller parts (Helm/Kustomize/...) in a manageable and unif err = rootCmd.Execute() if err != nil { - log.Errorf("%v", err) + status.Error(cliCtx, err.Error()) os.Exit(1) } } diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index a599aad54..f0ee358a7 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -11,9 +11,9 @@ import ( "github.com/kluctl/kluctl/v2/pkg/jinja2" "github.com/kluctl/kluctl/v2/pkg/kluctl_project" "github.com/kluctl/kluctl/v2/pkg/registries" + "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/yaml" - log "github.com/sirupsen/logrus" "io/ioutil" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" @@ -51,7 +51,7 @@ func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, strictTemplates b repoRoot, err := git.DetectGitRepositoryRoot(cwd) if err != nil && projectFlags.FromArchive == "" { - log.Warning("Failed to detect git project root. This might cause follow-up errors") + status.Warning(cliCtx, "", "Failed to detect git project root. This might cause follow-up errors") } loadArgs := kluctl_project.LoadKluctlProjectArgs{ @@ -70,8 +70,7 @@ func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, strictTemplates b GitUpdateInterval: projectFlags.GitCacheUpdateInterval, } - ctx := context.Background() - ctx, cancel := context.WithTimeout(ctx, projectFlags.Timeout) + ctx, cancel := context.WithTimeout(cliCtx, projectFlags.Timeout) defer cancel() p, err := kluctl_project.LoadKluctlProject(ctx, loadArgs, tmpDir, j2) if err != nil { diff --git a/pkg/deployment/commands/delete.go b/pkg/deployment/commands/delete.go index 872e8a7ad..d794d355e 100644 --- a/pkg/deployment/commands/delete.go +++ b/pkg/deployment/commands/delete.go @@ -1,6 +1,7 @@ package commands import ( + "context" "github.com/kluctl/kluctl/v2/pkg/deployment" utils2 "github.com/kluctl/kluctl/v2/pkg/deployment/utils" "github.com/kluctl/kluctl/v2/pkg/k8s" @@ -18,10 +19,10 @@ func NewDeleteCommand(c *deployment.DeploymentCollection) *DeleteCommand { } } -func (cmd *DeleteCommand) Run(k *k8s.K8sCluster) ([]k8s2.ObjectRef, error) { +func (cmd *DeleteCommand) Run(ctx context.Context, k *k8s.K8sCluster) ([]k8s2.ObjectRef, error) { dew := utils2.NewDeploymentErrorsAndWarnings() - ru := utils2.NewRemoteObjectsUtil(dew) + ru := utils2.NewRemoteObjectsUtil(ctx, dew) var labels map[string]string if len(cmd.OverrideDeleteByLabels) != 0 { diff --git a/pkg/deployment/commands/deploy.go b/pkg/deployment/commands/deploy.go index 575fb26a0..0fd1823ca 100644 --- a/pkg/deployment/commands/deploy.go +++ b/pkg/deployment/commands/deploy.go @@ -29,7 +29,7 @@ func NewDeployCommand(c *deployment.DeploymentCollection) *DeployCommand { func (cmd *DeployCommand) Run(ctx context.Context, k *k8s.K8sCluster, diffResultCb func(diffResult *types.CommandResult) error) (*types.CommandResult, error) { dew := utils2.NewDeploymentErrorsAndWarnings() - ru := utils2.NewRemoteObjectsUtil(dew) + ru := utils2.NewRemoteObjectsUtil(ctx, dew) err := ru.UpdateRemoteObjects(k, cmd.c.Project.GetCommonLabels(), cmd.c.LocalObjectRefs()) if err != nil { return nil, err diff --git a/pkg/deployment/commands/diff.go b/pkg/deployment/commands/diff.go index 0bdd5b9e1..dd811a84a 100644 --- a/pkg/deployment/commands/diff.go +++ b/pkg/deployment/commands/diff.go @@ -28,7 +28,7 @@ func NewDiffCommand(c *deployment.DeploymentCollection) *DiffCommand { func (cmd *DiffCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*types.CommandResult, error) { dew := utils.NewDeploymentErrorsAndWarnings() - ru := utils.NewRemoteObjectsUtil(dew) + ru := utils.NewRemoteObjectsUtil(ctx, dew) err := ru.UpdateRemoteObjects(k, cmd.c.Project.GetCommonLabels(), cmd.c.LocalObjectRefs()) if err != nil { return nil, err diff --git a/pkg/deployment/commands/downscale.go b/pkg/deployment/commands/downscale.go index ce51e6e35..60cadd857 100644 --- a/pkg/deployment/commands/downscale.go +++ b/pkg/deployment/commands/downscale.go @@ -26,7 +26,7 @@ func (cmd *DownscaleCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*types dew := utils2.NewDeploymentErrorsAndWarnings() - ru := utils2.NewRemoteObjectsUtil(dew) + ru := utils2.NewRemoteObjectsUtil(ctx, dew) err := ru.UpdateRemoteObjects(k, cmd.c.Project.GetCommonLabels(), cmd.c.LocalObjectRefs()) if err != nil { return nil, err @@ -40,7 +40,7 @@ func (cmd *DownscaleCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*types if !d.CheckInclusionForDeploy() { continue } - au := ad.NewApplyUtil(ctx, utils2.NewProgressCtx(nil, d.RelToProjectItemDir, 0, true)) + au := ad.NewApplyUtil(ctx, utils2.NewProgressCtx(ctx, nil, d.RelToProjectItemDir, 0, true)) for _, o := range d.Objects { o := o ref := o.GetK8sRef() diff --git a/pkg/deployment/commands/poke_images.go b/pkg/deployment/commands/poke_images.go index e2dc24b23..c794e8a8a 100644 --- a/pkg/deployment/commands/poke_images.go +++ b/pkg/deployment/commands/poke_images.go @@ -27,7 +27,7 @@ func (cmd *PokeImagesCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*type dew := utils2.NewDeploymentErrorsAndWarnings() - ru := utils2.NewRemoteObjectsUtil(dew) + ru := utils2.NewRemoteObjectsUtil(ctx, dew) err := ru.UpdateRemoteObjects(k, cmd.c.Project.GetCommonLabels(), cmd.c.LocalObjectRefs()) if err != nil { return nil, err @@ -76,7 +76,7 @@ func (cmd *PokeImagesCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*type wg.Add(1) go func() { defer wg.Done() - au := ad.NewApplyUtil(ctx, utils2.NewProgressCtx(nil, ref.String(), 0, true)) + au := ad.NewApplyUtil(ctx, utils2.NewProgressCtx(ctx, nil, ref.String(), 0, true)) au.ReplaceObject(ref, ru.GetRemoteObject(ref), func(o *uo.UnstructuredObject) (*uo.UnstructuredObject, error) { return doPokeImage(containers, o) }) diff --git a/pkg/deployment/commands/prune.go b/pkg/deployment/commands/prune.go index 873dc3a11..bc2336e74 100644 --- a/pkg/deployment/commands/prune.go +++ b/pkg/deployment/commands/prune.go @@ -1,6 +1,7 @@ package commands import ( + "context" "github.com/kluctl/kluctl/v2/pkg/deployment" utils2 "github.com/kluctl/kluctl/v2/pkg/deployment/utils" "github.com/kluctl/kluctl/v2/pkg/k8s" @@ -17,10 +18,10 @@ func NewPruneCommand(c *deployment.DeploymentCollection) *PruneCommand { } } -func (cmd *PruneCommand) Run(k *k8s.K8sCluster) ([]k8s2.ObjectRef, error) { +func (cmd *PruneCommand) Run(ctx context.Context, k *k8s.K8sCluster) ([]k8s2.ObjectRef, error) { dew := utils2.NewDeploymentErrorsAndWarnings() - ru := utils2.NewRemoteObjectsUtil(dew) + ru := utils2.NewRemoteObjectsUtil(ctx, dew) err := ru.UpdateRemoteObjects(k, cmd.c.Project.GetCommonLabels(), nil) if err != nil { return nil, err diff --git a/pkg/deployment/commands/validate.go b/pkg/deployment/commands/validate.go index 11716e66d..fbe9102ec 100644 --- a/pkg/deployment/commands/validate.go +++ b/pkg/deployment/commands/validate.go @@ -16,12 +16,12 @@ type ValidateCommand struct { ru *utils2.RemoteObjectUtils } -func NewValidateCommand(c *deployment.DeploymentCollection) *ValidateCommand { +func NewValidateCommand(ctx context.Context, c *deployment.DeploymentCollection) *ValidateCommand { cmd := &ValidateCommand{ c: c, dew: utils2.NewDeploymentErrorsAndWarnings(), } - cmd.ru = utils2.NewRemoteObjectsUtil(cmd.dew) + cmd.ru = utils2.NewRemoteObjectsUtil(ctx, cmd.dew) return cmd } @@ -40,7 +40,7 @@ func (cmd *ValidateCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*types. if !d.CheckInclusionForDeploy() { continue } - au := ad.NewApplyUtil(ctx, utils2.NewProgressCtx(nil, d.RelToProjectItemDir, 0, true)) + au := ad.NewApplyUtil(ctx, utils2.NewProgressCtx(ctx, nil, d.RelToProjectItemDir, 0, true)) h := utils2.NewHooksUtil(au) for _, o := range d.Objects { hook := h.GetHook(o) diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index 80ea9ac7e..9c26fa86e 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -4,10 +4,10 @@ import ( "context" "fmt" "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/utils" - log "github.com/sirupsen/logrus" "golang.org/x/sync/semaphore" "path/filepath" "sync" @@ -52,7 +52,7 @@ func (c *DeploymentCollection) createBarrierDummy(project *DeploymentProject) *D } di, err := NewDeploymentItem(project, c, tmpDiConfig, nil, 0) if err != nil { - log.Fatal(err) + panic(err) } return di } @@ -67,7 +67,7 @@ func findDeploymentItemIndex(project *DeploymentProject, pth *string, indexes ma absDir, err := filepath.Abs(dir) if err != nil { // we pre-checked directories, so this should not happen - log.Fatal(err) + panic(err) } if _, ok := indexes[absDir]; !ok { @@ -86,7 +86,7 @@ func (c *DeploymentCollection) collectDeployments(project *DeploymentProject, in if diConfig.Include != nil { includedProject, ok := project.includes[i] if !ok { - log.Fatalf("Did not find find index %d in project.includes", i) + panic(fmt.Sprintf("Did not find find index %d in project.includes", i)) } ret2, err := c.collectDeployments(includedProject, indexes) if err != nil { @@ -110,7 +110,8 @@ func (c *DeploymentCollection) collectDeployments(project *DeploymentProject, in } func (c *DeploymentCollection) RenderDeployments(k *k8s.K8sCluster) error { - log.Infof("Rendering templates and Helm charts") + s := status.Start(c.ctx, "Rendering templates") + defer s.Failed() wp := utils.NewDebuggerAwareWorkerPool(16) defer wp.StopWait(false) @@ -125,6 +126,10 @@ func (c *DeploymentCollection) RenderDeployments(k *k8s.K8sCluster) error { if err != nil { return err } + s.Success() + + s = status.Start(c.ctx, "Rendering Helm Charts") + defer s.Failed() for _, d := range c.Deployments { err := d.renderHelmCharts(k, wp) @@ -137,6 +142,7 @@ func (c *DeploymentCollection) RenderDeployments(k *k8s.K8sCluster) error { return err } + s.Success() return nil } @@ -155,7 +161,8 @@ func (c *DeploymentCollection) resolveSealedSecrets() error { } func (c *DeploymentCollection) buildKustomizeObjects(k *k8s.K8sCluster) error { - log.Infof("Building kustomize objects") + s := status.Start(c.ctx, "Building kustomize objects") + defer s.Failed() var wg sync.WaitGroup var errs []error @@ -193,6 +200,10 @@ func (c *DeploymentCollection) buildKustomizeObjects(k *k8s.K8sCluster) error { } wg.Wait() + if len(errs) == 0 { + s.Success() + } + return utils.NewErrorListOrNil(errs) } diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index acb5e7aa3..1904a8842 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -176,7 +176,7 @@ func (di *DeploymentItem) renderHelmCharts(k *k8s.K8sCluster, wp *utils.WorkerPo if err != nil { return err } - return chart.Render(k) + return chart.Render(di.Project.ctx, k) }) return nil }) diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go index fe093ab20..f0c0cedd3 100644 --- a/pkg/deployment/deployment_project.go +++ b/pkg/deployment/deployment_project.go @@ -1,14 +1,15 @@ package deployment import ( + "context" "fmt" "github.com/kluctl/kluctl/v2/pkg/jinja2" "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" - log "github.com/sirupsen/logrus" "path/filepath" "reflect" "strings" @@ -17,6 +18,8 @@ import ( var warnOnce utils.OnceByKey type DeploymentProject struct { + ctx context.Context + VarsCtx *jinja2.VarsCtx dir string SealedSecretsDir string @@ -29,8 +32,9 @@ type DeploymentProject struct { parentProjectInclude *types.DeploymentItemConfig } -func NewDeploymentProject(k *k8s.K8sCluster, varsCtx *jinja2.VarsCtx, dir string, sealedSecretsDir string, parentProject *DeploymentProject) (*DeploymentProject, error) { +func NewDeploymentProject(ctx context.Context, k *k8s.K8sCluster, varsCtx *jinja2.VarsCtx, dir string, sealedSecretsDir string, parentProject *DeploymentProject) (*DeploymentProject, error) { dp := &DeploymentProject{ + ctx: ctx, VarsCtx: varsCtx.Copy(), dir: dir, SealedSecretsDir: sealedSecretsDir, @@ -86,14 +90,14 @@ func (p *DeploymentProject) loadConfig(k *k8s.K8sCluster) error { } if len(p.Config.KustomizeDirs) != 0 { warnOnce.Do("kustomizeDirs", func() { - log.Warningf("'kustomizeDirs' is deprecated, use 'deployments' instead") + status.Warning(p.ctx, "'kustomizeDirs' is deprecated, use 'deployments' instead") }) p.Config.Deployments = p.Config.KustomizeDirs p.Config.KustomizeDirs = nil } if len(p.Config.Includes) != 0 { warnOnce.Do("includes", func() { - log.Warningf("'includes' is deprecated, use 'deployments' instead") + status.Warning(p.ctx, "'includes' is deprecated, use 'deployments' instead") }) for _, inc := range p.Config.Includes { c := *inc @@ -127,7 +131,7 @@ func (p *DeploymentProject) loadConfig(k *k8s.K8sCluster) error { } if len(p.Config.DeleteByLabels) != 0 { warnOnce.Do("deleteByLabels", func() { - log.Warningf("'deleteByLabels' is deprecated and ignored from now on") + status.Warning(p.ctx, "'deleteByLabels' is deprecated and ignored from now on") }) if !reflect.DeepEqual(p.Config.CommonLabels, p.Config.DeleteByLabels) { return fmt.Errorf("commonLabels and deleteByLabels do not match") @@ -196,7 +200,7 @@ func (p *DeploymentProject) loadIncludes(k *k8s.K8sCluster) error { return err } - newProject, err := NewDeploymentProject(k, varsCtx, incDir, p.SealedSecretsDir, p) + newProject, err := NewDeploymentProject(p.ctx, k, varsCtx, incDir, p.SealedSecretsDir, p) if err != nil { return err } diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index 8c719c2a2..eb97d66b1 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -1,15 +1,16 @@ package deployment import ( + "context" "fmt" securejoin "github.com/cyphar/filepath-securejoin" "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/utils/versions" "github.com/kluctl/kluctl/v2/pkg/yaml" "github.com/pkg/errors" - log "github.com/sirupsen/logrus" "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart/loader" @@ -88,7 +89,7 @@ func (c *helmChart) buildHelmConfig() (*action.Configuration, error) { }, nil } -func (c *helmChart) Pull() error { +func (c *helmChart) Pull(ctx context.Context) error { chartName, err := c.GetChartName() if err != nil { return err @@ -123,7 +124,7 @@ func (c *helmChart) Pull() error { _ = os.RemoveAll(chartDir + fmt.Sprintf("-%s.tar.gz", a.Version)) _ = os.RemoveAll(chartDir + fmt.Sprintf("-%s.tgz", a.Version)) if out != "" { - log.Info(out) + status.PlainText(ctx, out) } if err != nil { return err @@ -177,19 +178,19 @@ func (c *helmChart) CheckUpdate() (string, bool, error) { return latestVersion, updated, nil } -func (c *helmChart) Render(k *k8s.K8sCluster) error { +func (c *helmChart) Render(ctx context.Context, k *k8s.K8sCluster) error { chartName, err := c.GetChartName() if err != nil { return err } - err = c.doRender(k) + err = c.doRender(ctx, k) if err != nil { return fmt.Errorf("rendering helm chart %s for release %s has failed: %w", chartName, c.Config.ReleaseName, err) } return nil } -func (c *helmChart) doRender(k *k8s.K8sCluster) error { +func (c *helmChart) doRender(ctx context.Context, k *k8s.K8sCluster) error { chartDir, err := c.GetChartDir() if err != nil { return err @@ -264,7 +265,7 @@ func (c *helmChart) doRender(k *k8s.K8sCluster) error { } if chartRequested.Metadata.Deprecated { - log.Warningf("Chart %s is deprecated", *c.Config.ChartName) + status.Warning(ctx, "Chart %s is deprecated", *c.Config.ChartName) } rel, err := client.Run(chartRequested, vals) diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index b14afa455..b8c78acbb 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -7,12 +7,12 @@ import ( "github.com/kluctl/kluctl/v2/pkg/deployment" "github.com/kluctl/kluctl/v2/pkg/diff" "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/validation" - log "github.com/sirupsen/logrus" "github.com/vbauerster/mpb/v7" "golang.org/x/sync/semaphore" "k8s.io/apimachinery/pkg/api/errors" @@ -561,7 +561,7 @@ func (a *ApplyDeploymentsUtil) buildProgressName(d *deployment.DeploymentItem) * } func (a *ApplyDeploymentsUtil) ApplyDeployments() { - log.Infof("Running server-side apply for all objects") + status.Info(a.ctx, "Running server-side apply for all objects") var wg sync.WaitGroup sem := semaphore.NewWeighted(8) @@ -592,9 +592,9 @@ func (a *ApplyDeploymentsUtil) ApplyDeployments() { progressName := a.buildProgressName(d) var pctx *progressCtx if progressName != nil { - pctx = NewProgressCtx(p, *progressName, maxNameLen, true) + pctx = NewProgressCtx(a.ctx, p, *progressName, maxNameLen, true) } else { - pctx = NewProgressCtx(nil, "", 0, false) + pctx = NewProgressCtx(a.ctx, nil, "", 0, false) } a2 := a.NewApplyUtil(a.ctx, pctx) @@ -609,7 +609,7 @@ func (a *ApplyDeploymentsUtil) ApplyDeployments() { barrier := (d.Config.Barrier != nil && *d.Config.Barrier) || d.Barrier if barrier { - bpctx := NewProgressCtx(p, "", maxNameLen, true) + bpctx := NewProgressCtx(a.ctx, p, "", maxNameLen, true) bpctx.SetTotal(1) bpctx.InfofAndStatus("Waiting on barrier...") @@ -678,7 +678,7 @@ func (a *ApplyUtil) ReplaceObject(ref k8s2.ObjectRef, firstVersion *uo.Unstructu a.dew.AddApiWarnings(ref, apiWarnings) if err != nil { if errors.IsConflict(err) { - log.Debugf("Conflict while patching %s. Retrying...", ref.String()) + status.Trace(a.ctx, "Conflict while patching %s. Retrying...", ref.String()) continue } else { a.HandleError(ref, err) diff --git a/pkg/deployment/utils/progress.go b/pkg/deployment/utils/progress.go index c5eadbbfc..34d03a61c 100644 --- a/pkg/deployment/utils/progress.go +++ b/pkg/deployment/utils/progress.go @@ -1,9 +1,10 @@ package utils import ( + "context" "fmt" + "github.com/kluctl/kluctl/v2/pkg/status" "github.com/mattn/go-isatty" - log "github.com/sirupsen/logrus" "github.com/vbauerster/mpb/v7" "github.com/vbauerster/mpb/v7/decor" "math" @@ -14,6 +15,7 @@ import ( ) type progressCtx struct { + ctx context.Context bar *mpb.Bar doLog bool total int64 @@ -23,8 +25,9 @@ type progressCtx struct { mutex sync.Mutex } -func NewProgressCtx(p *mpb.Progress, name string, maxNameWidth int, doLog bool) *progressCtx { +func NewProgressCtx(ctx context.Context, p *mpb.Progress, name string, maxNameWidth int, doLog bool) *progressCtx { pctx := &progressCtx{ + ctx: ctx, status: "Initializing...", total: -1, name: name, @@ -52,23 +55,22 @@ func NewProgressCtx(p *mpb.Progress, name string, maxNameWidth int, doLog bool) return pctx } -func (ctx *progressCtx) Logf(level log.Level, s string, args ...interface{}) { +func (ctx *progressCtx) Infof(s string, args ...interface{}) { if ctx.doLog { - s = fmt.Sprintf("%s: %s", ctx.name, s) - log.StandardLogger().Logf(level, s, args...) + status.Info(ctx.ctx, s, args...) } } -func (ctx *progressCtx) Infof(s string, args ...interface{}) { - ctx.Logf(log.InfoLevel, s, args...) -} - func (ctx *progressCtx) Warningf(s string, args ...interface{}) { - ctx.Logf(log.WarnLevel, s, args...) + if ctx.doLog { + status.Warning(ctx.ctx, s, args...) + } } func (ctx *progressCtx) Debugf(s string, args ...interface{}) { - ctx.Logf(log.DebugLevel, s, args...) + if ctx.doLog { + status.Trace(ctx.ctx, s, args...) + } } func (ctx *progressCtx) InfofAndStatus(s string, args ...interface{}) { diff --git a/pkg/deployment/utils/remote_objects_utils.go b/pkg/deployment/utils/remote_objects_utils.go index 71df19fbe..65ddf867e 100644 --- a/pkg/deployment/utils/remote_objects_utils.go +++ b/pkg/deployment/utils/remote_objects_utils.go @@ -1,24 +1,27 @@ package utils import ( + "context" "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/status" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" - log "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/runtime/schema" "sync" ) type RemoteObjectUtils struct { + ctx context.Context dew *DeploymentErrorsAndWarnings remoteObjects map[k8s2.ObjectRef]*uo.UnstructuredObject remoteNamespaces map[string]*uo.UnstructuredObject mutex sync.Mutex } -func NewRemoteObjectsUtil(dew *DeploymentErrorsAndWarnings) *RemoteObjectUtils { +func NewRemoteObjectsUtil(ctx context.Context, dew *DeploymentErrorsAndWarnings) *RemoteObjectUtils { return &RemoteObjectUtils{ + ctx: ctx, dew: dew, remoteObjects: map[k8s2.ObjectRef]*uo.UnstructuredObject{}, remoteNamespaces: map[string]*uo.UnstructuredObject{}, @@ -30,7 +33,8 @@ func (u *RemoteObjectUtils) UpdateRemoteObjects(k *k8s.K8sCluster, labels map[st return nil } - log.Infof("Getting remote objects by commonLabels") + status.Info(u.ctx, "Getting remote objects by commonLabels") + allObjects, apiWarnings, err := k.ListAllObjects([]string{"get"}, "", labels, false) for gvk, aw := range apiWarnings { u.dew.AddApiWarnings(k8s2.ObjectRef{GVK: gvk}, aw) @@ -57,7 +61,7 @@ func (u *RemoteObjectUtils) UpdateRemoteObjects(k *k8s.K8sCluster, labels map[st u.mutex.Unlock() if len(notFoundRefsList) != 0 { - log.Infof("Getting %d additional remote objects", len(notFoundRefsList)) + status.Info(u.ctx, "Getting %d additional remote objects", len(notFoundRefsList)) r, apiWarnings, err := k.GetObjectsByRefs(notFoundRefsList) for ref, aw := range apiWarnings { u.dew.AddApiWarnings(ref, aw) @@ -72,7 +76,7 @@ func (u *RemoteObjectUtils) UpdateRemoteObjects(k *k8s.K8sCluster, labels map[st u.mutex.Unlock() } - log.Infof("Getting namespaces") + status.Info(u.ctx, "Getting namespaces") r, _, err := k.ListObjects(schema.GroupVersionKind{ Group: "", Version: "v1", diff --git a/pkg/diff/diff.go b/pkg/diff/diff.go index e490578a5..99eb13188 100644 --- a/pkg/diff/diff.go +++ b/pkg/diff/diff.go @@ -9,7 +9,6 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" diff2 "github.com/r3labs/diff/v2" - "log" "reflect" "sort" "strconv" @@ -125,7 +124,7 @@ func stableSortChanges(changes []types.Change) { for i, _ := range changes { y, err := yaml.WriteYamlString(changes[i]) if err != nil { - log.Panic(err) + panic(err) } changesStrs[i] = y changesIndexes[i] = i diff --git a/pkg/diff/managed_fields.go b/pkg/diff/managed_fields.go index 5ac3f6261..3a0772165 100644 --- a/pkg/diff/managed_fields.go +++ b/pkg/diff/managed_fields.go @@ -4,7 +4,6 @@ import ( "bytes" "fmt" "github.com/kluctl/kluctl/v2/pkg/utils/uo" - log "github.com/sirupsen/logrus" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "reflect" "regexp" @@ -192,7 +191,7 @@ func ResolveFieldManagerConflicts(local *uo.UnstructuredObject, remote *uo.Unstr } remoteValue, found, err := remote.GetNestedField(p...) if !found { - log.Fatalf("field '%s' not found in remote object...which can't be!", cause.Field) + panic(fmt.Sprintf("field '%s' not found in remote object...which can't be!", cause.Field)) } overwrite := true diff --git a/pkg/git/auth/auth_provider.go b/pkg/git/auth/auth_provider.go index 677f4df07..2a3f91757 100644 --- a/pkg/git/auth/auth_provider.go +++ b/pkg/git/auth/auth_provider.go @@ -1,6 +1,7 @@ package auth import ( + "context" "github.com/go-git/go-git/v5/plumbing/transport" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" ) @@ -11,7 +12,7 @@ type AuthMethodAndCA struct { } type GitAuthProvider interface { - BuildAuth(gitUrl git_url.GitUrl) AuthMethodAndCA + BuildAuth(ctx context.Context, gitUrl git_url.GitUrl) AuthMethodAndCA } type GitAuthProviders struct { @@ -26,9 +27,9 @@ func (a *GitAuthProviders) RegisterAuthProvider(p GitAuthProvider, last bool) { } } -func (a *GitAuthProviders) BuildAuth(gitUrl git_url.GitUrl) AuthMethodAndCA { +func (a *GitAuthProviders) BuildAuth(ctx context.Context, gitUrl git_url.GitUrl) AuthMethodAndCA { for _, p := range a.authProviders { - auth := p.BuildAuth(gitUrl) + auth := p.BuildAuth(ctx, gitUrl) if auth.AuthMethod != nil { return auth } diff --git a/pkg/git/auth/env_auth_provider.go b/pkg/git/auth/env_auth_provider.go index a57fa44f3..f05516e00 100644 --- a/pkg/git/auth/env_auth_provider.go +++ b/pkg/git/auth/env_auth_provider.go @@ -1,16 +1,17 @@ package auth import ( + "context" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" + "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/utils" - log "github.com/sirupsen/logrus" "io/ioutil" ) type GitEnvAuthProvider struct { } -func (a *GitEnvAuthProvider) BuildAuth(gitUrl git_url.GitUrl) AuthMethodAndCA { +func (a *GitEnvAuthProvider) BuildAuth(ctx context.Context, gitUrl git_url.GitUrl) AuthMethodAndCA { var la ListAuthProvider for _, m := range utils.ParseEnvConfigSets("KLUCTL_GIT") { @@ -26,7 +27,7 @@ func (a *GitEnvAuthProvider) BuildAuth(gitUrl git_url.GitUrl) AuthMethodAndCA { ssh_key_path = utils.ExpandPath(ssh_key_path) b, err := ioutil.ReadFile(ssh_key_path) if err != nil { - log.Debugf("Failed to read key %s: %v", ssh_key_path, err) + status.Trace(ctx, "Failed to read key %s: %v", ssh_key_path, err) } else { e.SshKey = b } @@ -36,12 +37,12 @@ func (a *GitEnvAuthProvider) BuildAuth(gitUrl git_url.GitUrl) AuthMethodAndCA { ca_bundle_path = utils.ExpandPath(ca_bundle_path) b, err := ioutil.ReadFile(ca_bundle_path) if err != nil { - log.Debugf("Failed to read ca bundle %s: %v", ca_bundle_path, err) + status.Trace(ctx, "Failed to read ca bundle %s: %v", ca_bundle_path, err) } else { e.CABundle = b } } la.AddEntry(e) } - return la.BuildAuth(gitUrl) + return la.BuildAuth(ctx, gitUrl) } diff --git a/pkg/git/auth/git_credentials_file.go b/pkg/git/auth/git_credentials_file.go index b3a3b9f97..7ee6775c3 100644 --- a/pkg/git/auth/git_credentials_file.go +++ b/pkg/git/auth/git_credentials_file.go @@ -2,10 +2,11 @@ package auth import ( "bufio" + "context" "github.com/go-git/go-git/v5/plumbing/transport/http" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" + "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/utils" - log "github.com/sirupsen/logrus" giturls "github.com/whilp/git-urls" "os" "path/filepath" @@ -14,28 +15,28 @@ import ( type GitCredentialsFileAuthProvider struct { } -func (a *GitCredentialsFileAuthProvider) BuildAuth(gitUrl git_url.GitUrl) AuthMethodAndCA { +func (a *GitCredentialsFileAuthProvider) BuildAuth(ctx context.Context, gitUrl git_url.GitUrl) AuthMethodAndCA { if gitUrl.Scheme != "http" && gitUrl.Scheme != "https" { return AuthMethodAndCA{} } home, err := os.UserHomeDir() if err != nil { - log.Warningf("Could not determine home directory: %v", err) + status.Warning(ctx, "Could not determine home directory: %v", err) return AuthMethodAndCA{} } - auth := a.tryBuildAuth(gitUrl, filepath.Join(home, ".git-credentials")) + auth := a.tryBuildAuth(ctx, gitUrl, filepath.Join(home, ".git-credentials")) if auth != nil { return *auth } if xdgHome, ok := os.LookupEnv("XDG_CONFIG_HOME"); ok && xdgHome != "" { - auth = a.tryBuildAuth(gitUrl, filepath.Join(xdgHome, ".git-credentials")) + auth = a.tryBuildAuth(ctx, gitUrl, filepath.Join(xdgHome, ".git-credentials")) if auth != nil { return *auth } } else { - auth = a.tryBuildAuth(gitUrl, filepath.Join(home, ".config/.git-credentials")) + auth = a.tryBuildAuth(ctx, gitUrl, filepath.Join(home, ".config/.git-credentials")) if auth != nil { return *auth } @@ -43,14 +44,14 @@ func (a *GitCredentialsFileAuthProvider) BuildAuth(gitUrl git_url.GitUrl) AuthMe return AuthMethodAndCA{} } -func (a *GitCredentialsFileAuthProvider) tryBuildAuth(gitUrl git_url.GitUrl, gitCredentialsPath string) *AuthMethodAndCA { +func (a *GitCredentialsFileAuthProvider) tryBuildAuth(ctx context.Context, gitUrl git_url.GitUrl, gitCredentialsPath string) *AuthMethodAndCA { if !utils.IsFile(gitCredentialsPath) { return nil } f, err := os.Open(gitCredentialsPath) if err != nil { - log.Warningf("Failed to open %s: %v", gitCredentialsPath, err) + status.Warning(ctx, "Failed to open %s: %v", gitCredentialsPath, err) return nil } defer f.Close() diff --git a/pkg/git/auth/list_auth_provider.go b/pkg/git/auth/list_auth_provider.go index b929675af..241a8d5b2 100644 --- a/pkg/git/auth/list_auth_provider.go +++ b/pkg/git/auth/list_auth_provider.go @@ -1,10 +1,11 @@ package auth import ( + "context" "github.com/go-git/go-git/v5/plumbing/transport/http" "github.com/go-git/go-git/v5/plumbing/transport/ssh" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" - log "github.com/sirupsen/logrus" + "github.com/kluctl/kluctl/v2/pkg/status" "strings" ) @@ -28,7 +29,7 @@ func (a *ListAuthProvider) AddEntry(e AuthEntry) { a.entries = append(a.entries, e) } -func (a *ListAuthProvider) BuildAuth(gitUrl git_url.GitUrl) AuthMethodAndCA { +func (a *ListAuthProvider) BuildAuth(ctx context.Context, gitUrl git_url.GitUrl) AuthMethodAndCA { for _, e := range a.entries { if e.Host != "*" && e.Host != gitUrl.Hostname() { continue @@ -64,7 +65,7 @@ func (a *ListAuthProvider) BuildAuth(gitUrl git_url.GitUrl) AuthMethodAndCA { } a, err := ssh.NewPublicKeys(username, e.SshKey, "") if err != nil { - log.Debugf("Failed to parse private key: %v", err) + status.Trace(ctx, "Failed to parse private key: %v", err) } else { a.HostKeyCallback = buildVerifyHostCallback(e.KnownHosts) return AuthMethodAndCA{ diff --git a/pkg/git/auth/ssh_auth_provider.go b/pkg/git/auth/ssh_auth_provider.go index b37d242f1..686e90b47 100644 --- a/pkg/git/auth/ssh_auth_provider.go +++ b/pkg/git/auth/ssh_auth_provider.go @@ -1,11 +1,12 @@ package auth import ( + "context" "fmt" "github.com/kevinburke/ssh_config" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" + "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/utils" - log "github.com/sirupsen/logrus" sshagent "github.com/xanzy/ssh-agent" "golang.org/x/crypto/ssh" "io" @@ -22,6 +23,7 @@ type GitSshAuthProvider struct { } type sshDefaultIdentityAndAgent struct { + ctx context.Context authProvider *GitSshAuthProvider hostname string user string @@ -50,54 +52,54 @@ func (a *sshDefaultIdentityAndAgent) Signers() ([]ssh.Signer, error) { } func (a *sshDefaultIdentityAndAgent) addDefaultIdentity(gitUrl git_url.GitUrl) { - log.Debugf("trying to add default identity") + status.Trace(a.ctx, "trying to add default identity") u, err := user.Current() if err != nil { - log.Debugf("No current user: %v", err) + status.Trace(a.ctx, "No current user: %v", err) } else { path := filepath.Join(u.HomeDir, ".ssh", "id_rsa") - signer, err := a.authProvider.readKey(path) + signer, err := a.authProvider.readKey(a.ctx, path) if err != nil && !os.IsNotExist(err) { - log.Warningf("Failed to read default identity file for url %s: %v", gitUrl.String(), err) + status.Warning(a.ctx, "Failed to read default identity file for url %s: %v", gitUrl.String(), err) } else if signer != nil { - log.Debugf("...added '%s' as default identity", path) + status.Trace(a.ctx, "...added '%s' as default identity", path) a.signers = append(a.signers, signer) } } } func (a *sshDefaultIdentityAndAgent) addConfigIdentities(gitUrl git_url.GitUrl) { - log.Debugf("trying to add identities from ssh config") + status.Trace(a.ctx, "trying to add identities from ssh config") for _, id := range ssh_config.GetAll(gitUrl.Hostname(), "IdentityFile") { expanded := utils.ExpandPath(id) - log.Debugf("...trying '%s' (expanded: '%s')", id, expanded) - signer, err := a.authProvider.readKey(expanded) + status.Trace(a.ctx, "...trying '%s' (expanded: '%s')", id, expanded) + signer, err := a.authProvider.readKey(a.ctx, expanded) if err != nil && !os.IsNotExist(err) { - log.Warningf("Failed to read key %s for url %s: %v", id, gitUrl.String(), err) + status.Warning(a.ctx, "Failed to read key %s for url %s: %v", id, gitUrl.String(), err) } else if err == nil { - log.Debugf("...added '%s' from ssh config", expanded) + status.Trace(a.ctx, "...added '%s' from ssh config", expanded) a.signers = append(a.signers, signer) } } } func (a *sshDefaultIdentityAndAgent) addAgentIdentities(gitUrl git_url.GitUrl) { - log.Debugf("trying to add agent keys") + status.Trace(a.ctx, "trying to add agent keys") agent, _, err := sshagent.New() if err != nil { - log.Warningf("Failed to connect to ssh agent for url %s: %v", gitUrl.String(), err) + status.Warning(a.ctx, "Failed to connect to ssh agent for url %s: %v", gitUrl.String(), err) } else { signers, err := agent.Signers() if err != nil { - log.Warningf("Failed to get signers from ssh agent for url %s: %v", gitUrl.String(), err) + status.Warning(a.ctx, "Failed to get signers from ssh agent for url %s: %v", gitUrl.String(), err) return } - log.Debugf("...added %d agent keys", len(signers)) + status.Trace(a.ctx, "...added %d agent keys", len(signers)) a.signers = append(a.signers, signers...) } } -func (a *GitSshAuthProvider) BuildAuth(gitUrl git_url.GitUrl) AuthMethodAndCA { +func (a *GitSshAuthProvider) BuildAuth(ctx context.Context, gitUrl git_url.GitUrl) AuthMethodAndCA { if !gitUrl.IsSsh() { return AuthMethodAndCA{} } @@ -106,6 +108,7 @@ func (a *GitSshAuthProvider) BuildAuth(gitUrl git_url.GitUrl) AuthMethodAndCA { } auth := &sshDefaultIdentityAndAgent{ + ctx: ctx, authProvider: a, hostname: gitUrl.Hostname(), user: gitUrl.User.Username(), @@ -124,6 +127,7 @@ func (a *GitSshAuthProvider) BuildAuth(gitUrl git_url.GitUrl) AuthMethodAndCA { // we defer asking for passphrase so that we don't unnecessarily ask for passphrases for keys that are already provided // by ssh-agent type deferredPassphraseKey struct { + ctx context.Context a *GitSshAuthProvider path string err error @@ -157,21 +161,21 @@ func (k *deferredPassphraseKey) parse() { passphrase, err := k.getPassphrase() if err != nil { k.err = err - log.Warningf("Failed to parse key %s: %v", k.path, err) + status.Warning(k.ctx, "Failed to parse key %s: %v", k.path, err) return } pemBytes, err := ioutil.ReadFile(k.path) if err != nil { k.err = err - log.Warningf("Failed to parse key %s: %v", k.path, err) + status.Warning(k.ctx, "Failed to parse key %s: %v", k.path, err) return } s, err := ssh.ParsePrivateKeyWithPassphrase(pemBytes, passphrase) if err != nil { k.err = err - log.Warningf("Failed to parse key %s: %v", k.path, err) + status.Warning(k.ctx, "Failed to parse key %s: %v", k.path, err) return } k.parsed = s @@ -223,7 +227,7 @@ func (k *deferredPassphraseKey) Sign(rand io.Reader, data []byte) (*ssh.Signatur return k.parsed.Sign(rand, data) } -func (a *GitSshAuthProvider) readKey(path string) (ssh.Signer, error) { +func (a *GitSshAuthProvider) readKey(ctx context.Context, path string) (ssh.Signer, error) { pemBytes, err := ioutil.ReadFile(path) if err != nil { return nil, err @@ -235,6 +239,7 @@ func (a *GitSshAuthProvider) readKey(path string) (ssh.Signer, error) { if _, ok := err.(*ssh.PassphraseMissingError); ok { signer = &deferredPassphraseKey{ + ctx: ctx, a: a, path: path, } diff --git a/pkg/git/mirrored_repo.go b/pkg/git/mirrored_repo.go index 8940c6047..2eeb19bfd 100644 --- a/pkg/git/mirrored_repo.go +++ b/pkg/git/mirrored_repo.go @@ -8,8 +8,8 @@ import ( "github.com/go-git/go-git/v5/plumbing" auth2 "github.com/kluctl/kluctl/v2/pkg/git/auth" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" + "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/utils" - log "github.com/sirupsen/logrus" "io/ioutil" "os" "path/filepath" @@ -70,7 +70,7 @@ func (g *MirroredGitRepo) Lock() error { func (g *MirroredGitRepo) Unlock() error { err := g.fileLock.Unlock() if err != nil { - log.Warningf("Unlock of %s failed: %v", g.fileLock.Path(), err) + status.Warning(g.ctx, "Unlock of %s failed: %v", g.fileLock.Path(), err) return err } return nil @@ -170,14 +170,13 @@ func (g *MirroredGitRepo) cleanupMirrorDir() error { return nil } -func (g *MirroredGitRepo) update(repoDir string, authProviders *auth2.GitAuthProviders) error { - log.Infof("Updating mirror repo: url='%v'", g.url.String()) +func (g *MirroredGitRepo) update(s *status.StatusContext, repoDir string, authProviders *auth2.GitAuthProviders) error { r, err := git.PlainOpen(repoDir) if err != nil { return err } - auth := authProviders.BuildAuth(g.url) + auth := authProviders.BuildAuth(g.ctx, g.url) remote, err := r.Remote("origin") if err != nil { @@ -260,21 +259,20 @@ func (g *MirroredGitRepo) update(repoDir string, authProviders *auth2.GitAuthPro _ = ioutil.WriteFile(filepath.Join(g.mirrorDir, ".update-time"), []byte(time.Now().Format(time.RFC3339Nano)), 0644) + s.Success() return nil } -func (g *MirroredGitRepo) cloneOrUpdate(authProviders *auth2.GitAuthProviders) error { +func (g *MirroredGitRepo) cloneOrUpdate(s *status.StatusContext, authProviders *auth2.GitAuthProviders) error { initMarker := filepath.Join(g.mirrorDir, ".cache2.init") if utils.IsFile(initMarker) { - return g.update(g.mirrorDir, authProviders) + return g.update(s, g.mirrorDir, authProviders) } err := g.cleanupMirrorDir() if err != nil { return err } - log.Infof("Cloning mirror repo at %v", g.mirrorDir) - tmpMirrorDir, err := ioutil.TempDir(utils.GetTmpBaseDir(), "mirror-") if err != nil { return err @@ -298,7 +296,7 @@ func (g *MirroredGitRepo) cloneOrUpdate(authProviders *auth2.GitAuthProviders) e return err } - err = g.update(tmpMirrorDir, authProviders) + err = g.update(s, tmpMirrorDir, authProviders) if err != nil { return err } @@ -321,20 +319,23 @@ func (g *MirroredGitRepo) cloneOrUpdate(authProviders *auth2.GitAuthProviders) e } func (g *MirroredGitRepo) Update(authProviders *auth2.GitAuthProviders) error { - err := g.cloneOrUpdate(authProviders) + s := status.Start(g.ctx, "Updating git cache for %s", g.url.String()) + err := g.cloneOrUpdate(s, authProviders) if err != nil { + s.FailedWithMessage(err.Error()) return err } g.hasUpdated = true + s.Success() return nil } func (g *MirroredGitRepo) CloneProject(ref string, targetDir string) error { if !g.fileLock.Locked() || !g.hasUpdated { - log.Fatalf("tried to clone from a project that is not locked/updated") + panic("tried to clone from a project that is not locked/updated") } - log.Debugf("Cloning git project: url='%s', ref='%s', target='%s'", g.url.String(), ref, targetDir) + status.Trace(g.ctx, "Cloning git project: url='%s', ref='%s', target='%s'", g.url.String(), ref, targetDir) err := PoorMansClone(g.mirrorDir, targetDir, ref) if err != nil { diff --git a/pkg/jinja2/generate/main.go b/pkg/jinja2/generate/main.go index a767884b3..9da3d4f4e 100644 --- a/pkg/jinja2/generate/main.go +++ b/pkg/jinja2/generate/main.go @@ -2,7 +2,6 @@ package main import ( "github.com/kluctl/kluctl/v2/pkg/utils/embed_util/packer" - log "github.com/sirupsen/logrus" "os" "os/exec" "path/filepath" @@ -23,12 +22,12 @@ func pipWheel() { cmd.Stderr = os.Stderr err := cmd.Run() if err != nil { - log.Panic(err) + panic(err) } wheels, err := os.ReadDir("python_src/wheel") if err != nil { - log.Panic(err) + panic(err) } for _, w := range wheels { if !strings.HasSuffix(w.Name(), ".whl") { @@ -40,17 +39,17 @@ func pipWheel() { err = cmd.Run() if err != nil { - log.Panic(err) + panic(err) } err = os.Remove(filepath.Join("python_src/wheel", w.Name())) if err != nil { - log.Panic(err) + panic(err) } } err = packer.Pack("embed/python_src.tar.gz", "python_src", "*") if err != nil { - log.Panic(err) + panic(err) } } diff --git a/pkg/jinja2/source.go b/pkg/jinja2/source.go index f07623b74..d9ab49bb4 100644 --- a/pkg/jinja2/source.go +++ b/pkg/jinja2/source.go @@ -4,7 +4,6 @@ import ( "embed" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/embed_util" - log "github.com/sirupsen/logrus" "path/filepath" ) @@ -17,7 +16,7 @@ var pythonSrcExtracted string func init() { srcDir, err := extractSource() if err != nil { - log.Panic(err) + panic(err) } pythonSrcExtracted = srcDir } diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index ea633652a..b6f4939b6 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -5,11 +5,11 @@ import ( "encoding/json" "fmt" goversion "github.com/hashicorp/go-version" + "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" - log "github.com/sirupsen/logrus" "io" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" @@ -531,11 +531,11 @@ func (k *K8sCluster) ListObjectsMetadata(gvk schema.GroupVersionKind, namespace for _, o := range x.Items { b, err := json.Marshal(o) if err != nil { - log.Panic(err) + panic(err) } u, err := uo.FromString(string(b)) if err != nil { - log.Panic(err) + panic(err) } u.SetK8sGVK(gvk) result = append(result, u) @@ -805,7 +805,6 @@ type PatchOptions struct { func (k *K8sCluster) PatchObject(o *uo.UnstructuredObject, options PatchOptions) (*uo.UnstructuredObject, []ApiWarning, error) { dryRun := k.DryRun || options.ForceDryRun ref := o.GetK8sRef() - log2 := log.WithField("ref", ref) data, err := yaml.WriteYamlBytes(o) if err != nil { @@ -822,7 +821,8 @@ func (k *K8sCluster) PatchObject(o *uo.UnstructuredObject, options PatchOptions) po.Force = &options.ForceApply } - log2.Debugf("patching") + status.Trace(k.ctx, "patching %s", ref.String()) + var result *uo.UnstructuredObject apiWarnings, err := k.withDynamicClientForGVK(ref.GVK, ref.Namespace, func(r dynamic.ResourceInterface) error { x, err := r.Patch(k.ctx, ref.Name, types.ApplyPatchType, data, po) @@ -842,7 +842,6 @@ type UpdateOptions struct { func (k *K8sCluster) UpdateObject(o *uo.UnstructuredObject, options UpdateOptions) (*uo.UnstructuredObject, []ApiWarning, error) { dryRun := k.DryRun || options.ForceDryRun ref := o.GetK8sRef() - log2 := log.WithField("ref", ref) updateOpts := v1.UpdateOptions{ FieldManager: "kluctl", @@ -851,7 +850,8 @@ func (k *K8sCluster) UpdateObject(o *uo.UnstructuredObject, options UpdateOption updateOpts.DryRun = []string{"All"} } - log2.Debugf("updating") + status.Trace(k.ctx, "updating %s", ref.String()) + var result *uo.UnstructuredObject apiWarnings, err := k.withDynamicClientForGVK(ref.GVK, ref.Namespace, func(r dynamic.ResourceInterface) error { x, err := r.Update(k.ctx, o.ToUnstructured(), updateOpts) diff --git a/pkg/kluctl_project/project_load.go b/pkg/kluctl_project/project_load.go index c4d8b9527..4c5b74fb6 100644 --- a/pkg/kluctl_project/project_load.go +++ b/pkg/kluctl_project/project_load.go @@ -5,10 +5,10 @@ import ( "github.com/kluctl/kluctl/v2/pkg/git" auth2 "github.com/kluctl/kluctl/v2/pkg/git/auth" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" + "github.com/kluctl/kluctl/v2/pkg/status" types2 "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/yaml" - log "github.com/sirupsen/logrus" "io/ioutil" "os" "path/filepath" @@ -237,7 +237,7 @@ func (c *LoadedKluctlProject) mergeClustersDirs(mergedClustersDir string, cluste for _, ci := range clustersInfos { if !utils.IsDirectory(ci.dir) { - log.Warningf("Cluster dir '%s' does not exist", ci.dir) + status.Warning(c.ctx, "Cluster dir '%s' does not exist", ci.dir) continue } files, err := ioutil.ReadDir(ci.dir) diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index 50d8a7f27..bf4d1b369 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -107,7 +107,7 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, clientConfig } varsCtx.UpdateChild("target", targetVars) - d, err := deployment.NewDeploymentProject(k, varsCtx, deploymentDir, p.sealedSecretsDir, nil) + d, err := deployment.NewDeploymentProject(ctx, k, varsCtx, deploymentDir, p.sealedSecretsDir, nil) if err != nil { return nil, err } diff --git a/pkg/kluctl_project/targets.go b/pkg/kluctl_project/targets.go index baaff34d2..8492422b2 100644 --- a/pkg/kluctl_project/targets.go +++ b/pkg/kluctl_project/targets.go @@ -5,11 +5,11 @@ import ( securejoin "github.com/cyphar/filepath-securejoin" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/jinja2" + "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" - log "github.com/sirupsen/logrus" "reflect" "regexp" "sort" @@ -51,7 +51,7 @@ func (c *LoadedKluctlProject) loadTargets() error { if targetInfo.refPattern == nil { return err } - log.Warningf("Failed to load dynamic target config for project: %v", err) + status.Warning(c.ctx, "Failed to load dynamic target config for project: %v", err) continue } @@ -61,7 +61,7 @@ func (c *LoadedKluctlProject) loadTargets() error { } if _, ok := targetNames[target.Name]; ok { - log.Warningf("Duplicate target %s", target.Name) + status.Warning(c.ctx, "Duplicate target %s", target.Name) } else { targetNames[target.Name] = true c.DynamicTargets = append(c.DynamicTargets, &types.DynamicTarget{ diff --git a/pkg/python/embed.go b/pkg/python/embed.go index 4a67eee0d..500e68ed8 100644 --- a/pkg/python/embed.go +++ b/pkg/python/embed.go @@ -4,7 +4,6 @@ import ( "fmt" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/embed_util" - "log" "path/filepath" "runtime" ) @@ -21,20 +20,20 @@ func decompressPython() string { tarName := fmt.Sprintf("embed/python-%s-%s.tar.gz", runtime.GOOS, runtime.GOARCH) tgz, err := pythonLib.Open(tarName) if err != nil { - log.Panic(err) + panic(err) } defer tgz.Close() fileList, err := pythonLib.Open(tarName + ".files") if err != nil { - log.Panic(err) + panic(err) } defer fileList.Close() path := filepath.Join(utils.GetTmpBaseDir(), fmt.Sprintf("python-%s", runtime.GOOS)) path, err = embed_util.ExtractTarToTmp(tgz, fileList, path) if err != nil { - log.Panic(err) + panic(err) } return path diff --git a/pkg/registries/registries.go b/pkg/registries/registries.go index 93f11f95b..9f2171d83 100644 --- a/pkg/registries/registries.go +++ b/pkg/registries/registries.go @@ -12,9 +12,9 @@ import ( "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/google/go-containerregistry/pkg/v1/remote/transport" + "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" - log "github.com/sirupsen/logrus" "io/ioutil" "net/http" "net/http/httputil" @@ -297,19 +297,19 @@ func (rh *RegistryHelper) writeCachedResponse(key string, data []byte) { if !utils.Exists(filepath.Dir(cachePath)) { err := os.MkdirAll(filepath.Dir(cachePath), 0o700) if err != nil { - log.Warningf("writeCachedResponse failed: %v", err) + status.Warning(rh.ctx, "writeCachedResponse failed: %v", err) return } } err := ioutil.WriteFile(cachePath+".tmp", data, 0o600) if err != nil { - log.Warningf("writeCachedResponse failed: %v", err) + status.Warning(rh.ctx, "writeCachedResponse failed: %v", err) return } err = os.Rename(cachePath+".tmp", cachePath) if err != nil { - log.Warningf("writeCachedResponse failed: %v", err) + status.Warning(rh.ctx, "writeCachedResponse failed: %v", err) return } } diff --git a/pkg/seal/bootstrap.go b/pkg/seal/bootstrap.go index 02865c4e9..84761159d 100644 --- a/pkg/seal/bootstrap.go +++ b/pkg/seal/bootstrap.go @@ -1,15 +1,16 @@ package seal import ( + "context" "crypto/rsa" "crypto/x509" "encoding/base64" "encoding/pem" "github.com/bitnami-labs/sealed-secrets/pkg/crypto" "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/status" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/utils/uo" - log "github.com/sirupsen/logrus" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime/schema" certUtil "k8s.io/client-go/util/cert" @@ -21,7 +22,7 @@ const sealedSecretsKeyLabel = "sealedsecrets.bitnami.com/sealed-secrets-key" const secretName = "sealed-secrets-key-kluctl-bootstrap" const configMapName = "sealed-secrets-key-kluctl-bootstrap" -func BootstrapSealedSecrets(k *k8s.K8sCluster, namespace string) error { +func BootstrapSealedSecrets(ctx context.Context, k *k8s.K8sCluster, namespace string) error { existing, _, err := k.GetSingleObject(k8s2.ObjectRef{ GVK: schema.GroupVersionKind{Group: "apiextensions.k8s.io", Version: "v1", Kind: "CustomResourceDefinition"}, Name: "sealedsecrets.bitnami.com", @@ -41,7 +42,7 @@ func BootstrapSealedSecrets(k *k8s.K8sCluster, namespace string) error { return nil } - log.Infof("Bootstrapping sealed-secrets with a self-generated key") + status.Info(ctx, "Bootstrapping sealed-secrets with a self-generated key") key, cert, err := crypto.GeneratePrivateKeyAndCert(2048, 10*365*24*time.Hour, "bootstrap.kluctl.io") if err != nil { diff --git a/pkg/seal/fetch_cert.go b/pkg/seal/fetch_cert.go index 5af09d342..95b1c154a 100644 --- a/pkg/seal/fetch_cert.go +++ b/pkg/seal/fetch_cert.go @@ -1,31 +1,32 @@ package seal import ( + "context" "crypto/rsa" "errors" "fmt" "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/status" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" - log "github.com/sirupsen/logrus" "io/ioutil" v12 "k8s.io/api/core/v1" "k8s.io/client-go/util/cert" ) -func fetchCert(k *k8s.K8sCluster, namespace string, controllerName string) (*rsa.PublicKey, error) { +func fetchCert(ctx context.Context, k *k8s.K8sCluster, namespace string, controllerName string) (*rsa.PublicKey, error) { certData, err := openCertFromController(k, namespace, controllerName) if err != nil { if controllerName == "sealed-secrets-controller" { s2, err2 := openCertFromController(k, namespace, "sealed-secrets") if err2 == nil { - log.Warningf("Looks like you have sealed-secrets controller installed with name 'sealed-secrets', which comes from a legacy kluctl version that deployed it with a non-default name. Please consider re-deploying sealed-secrets operator manually.") + status.Warning(ctx, "Looks like you have sealed-secrets controller installed with name 'sealed-secrets', which comes from a legacy kluctl version that deployed it with a non-default name. Please consider re-deploying sealed-secrets operator manually.") err = nil certData = s2 } } if err != nil { - log.Warningf("Failed to retrieve public certificate from sealed-secrets-controller, re-trying with bootstrap secret") + status.Warning(ctx, "Failed to retrieve public certificate from sealed-secrets-controller, re-trying with bootstrap secret") certData, err = openCertFromBootstrap(k, namespace) if err != nil { return nil, fmt.Errorf("failed to retrieve sealed secrets public key: %w", err) diff --git a/pkg/seal/sealer.go b/pkg/seal/sealer.go index 92dd6953d..93d1af66f 100644 --- a/pkg/seal/sealer.go +++ b/pkg/seal/sealer.go @@ -1,6 +1,7 @@ package seal import ( + "context" "crypto/rand" "crypto/rsa" "encoding/base64" @@ -8,12 +9,12 @@ import ( "fmt" "github.com/bitnami-labs/sealed-secrets/pkg/crypto" "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" - log "github.com/sirupsen/logrus" "golang.org/x/crypto/scrypt" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime/schema" @@ -27,18 +28,20 @@ const hashAnnotation = "kluctl.io/sealedsecret-hashes" const clusterIdAnnotation = "kluctl.io/sealedsecret-cluster-id" type Sealer struct { + ctx context.Context clusterConfig *types.ClusterConfig2 forceReseal bool cert *rsa.PublicKey clusterId string } -func NewSealer(k *k8s.K8sCluster, sealedSecretsNamespace string, sealedSecretsControllerName string, clusterConfig *types.ClusterConfig2, forceReseal bool) (*Sealer, error) { +func NewSealer(ctx context.Context, k *k8s.K8sCluster, sealedSecretsNamespace string, sealedSecretsControllerName string, clusterConfig *types.ClusterConfig2, forceReseal bool) (*Sealer, error) { s := &Sealer{ + ctx: ctx, clusterConfig: clusterConfig, forceReseal: forceReseal, } - cert, err := fetchCert(k, sealedSecretsNamespace, sealedSecretsControllerName) + cert, err := fetchCert(ctx, k, sealedSecretsNamespace, sealedSecretsControllerName) if err != nil { return nil, err } @@ -96,7 +99,7 @@ func (s *Sealer) doHash(key string, secret []byte, secretName string, secretName } h, err := scrypt.Key(secret, []byte(salt), 1<<14, 8, 1, 64) if err != nil { - log.Fatal(err) + panic(err) } return hex.EncodeToString(h) } @@ -242,10 +245,10 @@ func (s *Sealer) SealFile(p string, targetFile string) error { resealAll := false if s.forceReseal { resealAll = true - log.Infof("Forcing reseal of secrets in %s", secretName) + status.Info(s.ctx, "Forcing reseal of secrets in %s", secretName) } else if existingClusterId != s.clusterId { resealAll = true - log.Infof("Target cluster for secret %s has changed, forcing reseal", secretName) + status.Info(s.ctx, "Target cluster for secret %s has changed, forcing reseal", secretName) } for k, v := range secrets { @@ -254,19 +257,19 @@ func (s *Sealer) SealFile(p string, targetFile string) error { doEncrypt := resealAll if !doEncrypt && hash != existingHash { - log.Infof("Secret %s and key %s has changed, resealing", secretName, k) + status.Info(s.ctx, "Secret %s and key %s has changed, resealing", secretName, k) doEncrypt = true } if !doEncrypt { e, ok, _ := existingContent.GetNestedString("spec", "encryptedData", k) if ok { - log.Debugf("Secret %s and key %s is unchanged", secretName, k) + status.Trace(s.ctx, "Secret %s and key %s is unchanged", secretName, k) result.SetNestedField(e, "spec", "encryptedData", k) resultSecretHashes[k] = hash continue } else { - log.Infof("Old encrypted secret %s and key %s not found", secretName, k) + status.Info(s.ctx, "Old encrypted secret %s and key %s not found", secretName, k) doEncrypt = true } } @@ -287,7 +290,7 @@ func (s *Sealer) SealFile(p string, targetFile string) error { result.SetK8sAnnotation(clusterIdAnnotation, s.clusterId) if reflect.DeepEqual(existingContent, result) { - log.Infof("Skipped %s as it did not change", baseName) + status.Info(s.ctx, "Skipped %s as it did not change", baseName) return nil } diff --git a/pkg/status/status.go b/pkg/status/status.go new file mode 100644 index 000000000..a9b8b4df6 --- /dev/null +++ b/pkg/status/status.go @@ -0,0 +1,107 @@ +package status + +import ( + "context" + "fmt" +) + +// StatusContext is used to report user-facing status/progress +type StatusContext struct { + ctx context.Context + sh StatusHandler + sl StatusLine + finished bool + failed bool +} + +type StatusLine interface { + Update(message string) + End(success bool) +} + +type StatusHandler interface { + StartStatus(message string) StatusLine + + Info(message string) + Warning(message string) + Error(message string) + Trace(message string) + + PlainText(text string) +} + +type contextKey struct{} + +func NewContext(ctx context.Context, slh StatusHandler) context.Context { + return context.WithValue(ctx, contextKey{}, slh) +} + +func FromContext(ctx context.Context) StatusHandler { + v := ctx.Value(contextKey{}) + if v == nil { + return nil + } + return v.(StatusHandler) +} + +func Start(ctx context.Context, status string, args ...any) *StatusContext { + sh := FromContext(ctx) + s := &StatusContext{ + ctx: ctx, + sh: sh, + } + + s.sl = sh.StartStatus(fmt.Sprintf(status, args...)) + + return s +} + +func (s *StatusContext) Update(message string, args ...any) { + s.sl.Update(fmt.Sprintf(message, args...)) +} + +func (s *StatusContext) Failed() { + if s.finished { + return + } + s.sl.End(false) + s.failed = true +} + +func (s *StatusContext) FailedWithMessage(msg string, args ...any) { + if s.finished { + return + } + s.Update(msg, args...) + s.Failed() +} + +func (s *StatusContext) Success() { + s.sl.End(true) + s.finished = true +} + +func PlainText(ctx context.Context, text string) { + slh := FromContext(ctx) + slh.PlainText(text) +} + +func Info(ctx context.Context, status string, args ...any) { + slh := FromContext(ctx) + slh.Info(fmt.Sprintf(status, args...)) +} + +func Warning(ctx context.Context, status string, args ...any) { + slh := FromContext(ctx) + slh.Warning(fmt.Sprintf(status, args...)) +} + +func Trace(ctx context.Context, status string, args ...any) { + slh := FromContext(ctx) + slh.Trace(fmt.Sprintf(status, args...)) +} + +func Error(ctx context.Context, status string, args ...any) { + slh := FromContext(ctx) + slh.Error(fmt.Sprintf(status, args...)) +} diff --git a/pkg/status/status_handler.go b/pkg/status/status_handler.go new file mode 100644 index 000000000..15b08848c --- /dev/null +++ b/pkg/status/status_handler.go @@ -0,0 +1,67 @@ +package status + +import ( + "fmt" + "io" +) + +type MultiLineStatusHandler struct { + out io.Writer + trace bool +} + +type statusLine struct { + slh *MultiLineStatusHandler + message string +} + +func NewMultiLineStatusHandler(out io.Writer, trace bool) *MultiLineStatusHandler { + sh := &MultiLineStatusHandler{ + out: out, + trace: trace, + } + return sh +} + +func (s *MultiLineStatusHandler) StartStatus(message string) StatusLine { + sl := &statusLine{ + slh: s, + message: message, + } + s.writeOut(sl.message) + return sl +} + +func (s *MultiLineStatusHandler) writeOut(message string) { + _, _ = fmt.Fprintf(s.out, "%s\n", message) +} + +func (s *MultiLineStatusHandler) Info(message string) { + s.writeOut(message) +} + +func (s *MultiLineStatusHandler) Warning(message string) { + s.writeOut(message) +} + +func (s *MultiLineStatusHandler) Error(message string) { + s.writeOut(message) +} + +func (s *MultiLineStatusHandler) Trace(message string) { + if s.trace { + s.writeOut(message) + } +} + +func (s *MultiLineStatusHandler) PlainText(text string) { + s.writeOut(text) +} + +func (sl *statusLine) Update(message string) { + sl.message = message + sl.slh.writeOut(sl.message) +} + +func (sl *statusLine) End(success bool) { +} diff --git a/pkg/utils/env.go b/pkg/utils/env.go index 91def48c2..5dbca1e69 100644 --- a/pkg/utils/env.go +++ b/pkg/utils/env.go @@ -2,7 +2,6 @@ package utils import ( "fmt" - log "github.com/sirupsen/logrus" "os" "regexp" "strconv" @@ -18,7 +17,7 @@ func ParseEnvConfigSets(prefix string) map[int]map[string]string { for _, e := range os.Environ() { eq := strings.Index(e, "=") if eq == -1 { - log.Panicf("unexpected env var %s", e) + panic(fmt.Sprintf("unexpected env var %s", e)) } n := e[:eq] v := e[eq+1:] diff --git a/pkg/utils/prompts.go b/pkg/utils/prompts.go index 06cecf885..f95098796 100644 --- a/pkg/utils/prompts.go +++ b/pkg/utils/prompts.go @@ -4,13 +4,16 @@ import ( "bufio" "fmt" "github.com/mattn/go-isatty" - log "github.com/sirupsen/logrus" "golang.org/x/term" "os" "strings" "syscall" ) +func doWarn(f string, args ...any) { + _, _ = fmt.Fprintf(os.Stderr, f, args...) +} + // AskForConfirmation uses Scanln to parse user input. A user must type in "yes" or "no" and // then press enter. It has fuzzy matching, so "y", "Y", "yes", "YES", and "Yes" all count as // confirmations. If the input is not recognized, it will ask again. The function does not return @@ -18,13 +21,13 @@ import ( // before calling askForConfirmation. E.g. fmt.Println("WARNING: Are you sure? (yes/no)") func AskForConfirmation(prompt string) bool { if !isatty.IsTerminal(os.Stderr.Fd()) { - log.Warningf("Not a terminal, suppressed prompt: %s", prompt) + doWarn("Not a terminal, suppressed prompt: %s", prompt) return false } _, err := os.Stderr.WriteString(prompt + " (y/N) ") if err != nil { - log.Fatal(err) + panic(err) } var response string @@ -47,7 +50,7 @@ func AskForConfirmation(prompt string) bool { func AskForPassword(prompt string) (string, error) { if !isatty.IsTerminal(os.Stderr.Fd()) { err := fmt.Errorf("not a terminal, suppressed credentials prompt: %s", prompt) - log.Warning(err) + doWarn(err.Error()) return "", err } @@ -69,7 +72,7 @@ func AskForPassword(prompt string) (string, error) { func AskForCredentials(prompt string) (string, string, error) { if !isatty.IsTerminal(os.Stderr.Fd()) { err := fmt.Errorf("not a terminal, suppressed credentials prompt: %s", prompt) - log.Warning(err) + doWarn(err.Error()) return "", "", err } diff --git a/pkg/utils/uo/jsonpath.go b/pkg/utils/uo/jsonpath.go index a7bdbb760..b375aca7e 100644 --- a/pkg/utils/uo/jsonpath.go +++ b/pkg/utils/uo/jsonpath.go @@ -3,7 +3,6 @@ package uo import ( "fmt" "github.com/ohler55/ojg/jp" - log "github.com/sirupsen/logrus" "regexp" "strings" ) @@ -58,7 +57,7 @@ func NewMyJsonPath(p string) (*MyJsonPath, error) { func NewMyJsonPathMust(p string) *MyJsonPath { j, err := NewMyJsonPath(p) if err != nil { - log.Fatal(err) + panic(err) } return j } diff --git a/pkg/utils/uo/k8s_fields.go b/pkg/utils/uo/k8s_fields.go index be195b0f1..69c295757 100644 --- a/pkg/utils/uo/k8s_fields.go +++ b/pkg/utils/uo/k8s_fields.go @@ -1,8 +1,8 @@ package uo import ( + "fmt" "github.com/kluctl/kluctl/v2/pkg/types/k8s" - log "github.com/sirupsen/logrus" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "reflect" @@ -13,15 +13,15 @@ import ( func (uo *UnstructuredObject) GetK8sGVK() schema.GroupVersionKind { kind, _, err := uo.GetNestedString("kind") if err != nil { - log.Fatal(err) + panic(err) } apiVersion, _, err := uo.GetNestedString("apiVersion") if err != nil { - log.Fatal(err) + panic(err) } gv, err := schema.ParseGroupVersion(apiVersion) if err != nil { - log.Fatal(err) + panic(err) } return schema.GroupVersionKind{ Group: gv.Group, @@ -33,11 +33,11 @@ func (uo *UnstructuredObject) GetK8sGVK() schema.GroupVersionKind { func (uo *UnstructuredObject) SetK8sGVK(gvk schema.GroupVersionKind) { err := uo.SetNestedField(gvk.GroupVersion().String(), "apiVersion") if err != nil { - log.Fatal(err) + panic(err) } err = uo.SetNestedField(gvk.Kind, "kind") if err != nil { - log.Fatal(err) + panic(err) } } @@ -48,7 +48,7 @@ func (uo *UnstructuredObject) SetK8sGVKs(g string, v string, k string) { func (uo *UnstructuredObject) GetK8sName() string { s, _, err := uo.GetNestedString("metadata", "name") if err != nil { - log.Fatal(err) + panic(err) } return s } @@ -56,14 +56,14 @@ func (uo *UnstructuredObject) GetK8sName() string { func (uo *UnstructuredObject) SetK8sName(name string) { err := uo.SetNestedField(name, "metadata", "name") if err != nil { - log.Fatal(err) + panic(err) } } func (uo *UnstructuredObject) GetK8sNamespace() string { s, _, err := uo.GetNestedString("metadata", "namespace") if err != nil { - log.Fatal(err) + panic(err) } return s } @@ -72,12 +72,12 @@ func (uo *UnstructuredObject) SetK8sNamespace(namespace string) { if namespace != "" { err := uo.SetNestedField(namespace, "metadata", "namespace") if err != nil { - log.Fatal(err) + panic(err) } } else { err := uo.RemoveNestedField("metadata", "namespace") if err != nil { - log.Fatal(err) + panic(err) } } @@ -94,7 +94,7 @@ func (uo *UnstructuredObject) GetK8sRef() k8s.ObjectRef { func (uo *UnstructuredObject) GetK8sLabels() map[string]string { ret, ok, err := uo.GetNestedStringMapCopy("metadata", "labels") if err != nil { - log.Fatal(err) + panic(err) } if !ok { return map[string]string{} @@ -112,7 +112,7 @@ func (uo *UnstructuredObject) SetK8sLabels(labels map[string]string) { func (uo *UnstructuredObject) GetK8sLabel(name string) *string { ret, ok, err := uo.GetNestedString("metadata", "labels", name) if err != nil { - log.Fatal(err) + panic(err) } if !ok { return nil @@ -123,7 +123,7 @@ func (uo *UnstructuredObject) GetK8sLabel(name string) *string { func (uo *UnstructuredObject) SetK8sLabel(name string, value string) { err := uo.SetNestedField(value, "metadata", "labels", name) if err != nil { - log.Fatal(err) + panic(err) } } @@ -142,7 +142,7 @@ func (uo *UnstructuredObject) GetK8sLabelsWithRegex(r interface{}) map[string]st func (uo *UnstructuredObject) GetK8sAnnotations() map[string]string { ret, ok, err := uo.GetNestedStringMapCopy("metadata", "annotations") if err != nil { - log.Fatal(err) + panic(err) } if !ok { return map[string]string{} @@ -153,7 +153,7 @@ func (uo *UnstructuredObject) GetK8sAnnotations() map[string]string { func (uo *UnstructuredObject) GetK8sAnnotation(name string) *string { ret, ok, err := uo.GetNestedString("metadata", "annotations", name) if err != nil { - log.Fatal(err) + panic(err) } if !ok { return nil @@ -171,7 +171,7 @@ func (uo *UnstructuredObject) SetK8sAnnotations(annotations map[string]string) { func (uo *UnstructuredObject) SetK8sAnnotation(name string, value string) { err := uo.SetNestedField(value, "metadata", "annotations", name) if err != nil { - log.Fatal(err) + panic(err) } } @@ -198,7 +198,7 @@ func (uo *UnstructuredObject) SetK8sResourceVersion(rv string) { } else { err := uo.SetNestedField(rv, "metadata", "resourceVersion") if err != nil { - log.Fatal(err) + panic(err) } } } @@ -232,6 +232,6 @@ func (ui *UnstructuredObject) getRegexp(r interface{}) *regexp.Regexp { return regexp.MustCompile(x) } } - log.Panicf("unknown type %s", reflect.TypeOf(r).String()) + panic(fmt.Sprintf("unknown type %s", reflect.TypeOf(r).String())) return nil } diff --git a/pkg/utils/uo/object_iterator.go b/pkg/utils/uo/object_iterator.go index be922ee93..debb5c227 100644 --- a/pkg/utils/uo/object_iterator.go +++ b/pkg/utils/uo/object_iterator.go @@ -1,9 +1,5 @@ package uo -import ( - log "github.com/sirupsen/logrus" -) - type ObjectIteratorFunc func(it *ObjectIterator) error type ObjectIterator struct { path []interface{} @@ -72,7 +68,7 @@ func (it *ObjectIterator) iterateInterface() error { func (it *ObjectIterator) iterateMap() error { m, ok := it.Value().(map[string]interface{}) if !ok { - log.Fatalf("!ok") + panic("!ok") } for k, v := range m { @@ -92,7 +88,7 @@ func (it *ObjectIterator) iterateMap() error { func (it *ObjectIterator) iterateList() error { l, ok := it.Value().([]interface{}) if !ok { - log.Fatalf("!ok") + panic("!ok") } for i, e := range l { diff --git a/pkg/utils/uo/uo.go b/pkg/utils/uo/uo.go index ffb513110..0356a98ac 100644 --- a/pkg/utils/uo/uo.go +++ b/pkg/utils/uo/uo.go @@ -4,7 +4,6 @@ import ( "fmt" "github.com/jinzhu/copier" "github.com/kluctl/kluctl/v2/pkg/yaml" - log "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "reflect" ) @@ -76,7 +75,7 @@ func FromString(s string) (*UnstructuredObject, error) { func FromStringMust(s string) *UnstructuredObject { o, err := FromString(s) if err != nil { - log.Panic(err) + panic(err) } return o } @@ -112,7 +111,7 @@ func (uo *UnstructuredObject) Clone() *UnstructuredObject { DeepCopy: true, }) if err != nil { - log.Fatal(err) + panic(err) } return FromMap(c) } diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index ae338d920..4bcd0bf70 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -4,7 +4,6 @@ import ( "crypto/sha256" "encoding/hex" "github.com/jinzhu/copier" - "log" "os" "path/filepath" "strconv" @@ -18,7 +17,7 @@ func GetTmpBaseDir() string { createTmpBaseDirOnce.Do(func() { err := os.MkdirAll(dir, 0o700) if err != nil { - log.Fatal(err) + panic(err) } }) return dir diff --git a/pkg/utils/versions/latest_version.go b/pkg/utils/versions/latest_version.go index 7cd524a9c..24edbc5b2 100644 --- a/pkg/utils/versions/latest_version.go +++ b/pkg/utils/versions/latest_version.go @@ -3,7 +3,6 @@ package versions import ( "fmt" "github.com/kluctl/kluctl/v2/pkg/utils" - "log" "regexp" "strconv" ) @@ -43,7 +42,7 @@ func NewRegexVersionFilter(pattern string) (LatestVersionFilter, error) { func NewRegexVersionFilterMust(pattern string) LatestVersionFilter { ret, err := NewRegexVersionFilter(pattern) if err != nil { - log.Fatal(err) + panic(err) } return ret } @@ -114,7 +113,7 @@ func NewPrefixVersionFilter(prefix string, suffix LatestVersionFilter) (LatestVe func NewPrefixVersionFilterMust(prefix string, suffix LatestVersionFilter) LatestVersionFilter { ret, err := NewPrefixVersionFilter(prefix, suffix) if err != nil { - log.Fatal(err) + panic(err) } return ret } diff --git a/pkg/utils/versions/latest_version_parse.go b/pkg/utils/versions/latest_version_parse.go index 6fe06ed23..8317e46e6 100644 --- a/pkg/utils/versions/latest_version_parse.go +++ b/pkg/utils/versions/latest_version_parse.go @@ -3,7 +3,6 @@ package versions import ( "fmt" scanner "github.com/kluctl/kluctl/v2/pkg/utils/python_scanner" - log "github.com/sirupsen/logrus" "strconv" "strings" ) @@ -27,7 +26,7 @@ func (p *preparsed) CurToken() rune { func (p *preparsed) CurTokenText() string { if p.nextPos > len(p.tokens) { - log.Panicf("CurTokenText at EOF") + panic("CurTokenText at EOF") } return p.tokens[p.nextPos-1].text } From 65db7632a10789ad8f7bcff6888c42d5e7443748 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 9 May 2022 11:45:12 +0200 Subject: [PATCH 0777/2916] refactor: Use mpb library for status reporting --- cmd/kluctl/commands/root.go | 2 +- pkg/status/status_handler.go | 70 +++++++++++++++++++++++++++++------- 2 files changed, 59 insertions(+), 13 deletions(-) diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index 44e4ef541..a35f2c063 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -69,7 +69,7 @@ var flagGroups = []groupInfo{ var cliCtx = context.Background() func (c *cli) setupStatusHandler() error { - sh := status.NewMultiLineStatusHandler(os.Stderr, c.Debug) + sh := status.NewMultiLineStatusHandler(cliCtx, os.Stderr, c.Debug) cliCtx = status.NewContext(cliCtx, sh) return nil } diff --git a/pkg/status/status_handler.go b/pkg/status/status_handler.go index 15b08848c..7f6bd0c39 100644 --- a/pkg/status/status_handler.go +++ b/pkg/status/status_handler.go @@ -1,25 +1,46 @@ package status import ( + "context" "fmt" + "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/vbauerster/mpb/v7" + "github.com/vbauerster/mpb/v7/decor" "io" + "math" ) type MultiLineStatusHandler struct { - out io.Writer - trace bool + out io.Writer + progress *mpb.Progress + stop chan any + trace bool } type statusLine struct { slh *MultiLineStatusHandler + bar *mpb.Bar + filler mpb.BarFiller message string + + finished bool + success bool } -func NewMultiLineStatusHandler(out io.Writer, trace bool) *MultiLineStatusHandler { +func NewMultiLineStatusHandler(ctx context.Context, out io.Writer, trace bool) *MultiLineStatusHandler { sh := &MultiLineStatusHandler{ out: out, trace: trace, + stop: make(chan any), } + + sh.progress = mpb.NewWithContext( + ctx, + mpb.WithWidth(utils.GetTermWidth()), + mpb.WithOutput(out), + mpb.PopCompletedMode(), + ) + return sh } @@ -28,40 +49,65 @@ func (s *MultiLineStatusHandler) StartStatus(message string) StatusLine { slh: s, message: message, } - s.writeOut(sl.message) + sl.filler = mpb.SpinnerStyle().PositionLeft().Build() + sl.bar = s.progress.Add(1, sl, + mpb.BarWidth(1), + mpb.AppendDecorators(decor.Any(sl.DecorMessage, decor.WCSyncWidthR)), + ) + + s.writeLog(sl.message) return sl } -func (s *MultiLineStatusHandler) writeOut(message string) { - _, _ = fmt.Fprintf(s.out, "%s\n", message) +func (sl *statusLine) DecorMessage(s decor.Statistics) string { + return sl.message +} + +func (sl *statusLine) Fill(w io.Writer, reqWidth int, stat decor.Statistics) { + if !sl.finished { + sl.filler.Fill(w, reqWidth, stat) + } else if sl.success { + fmt.Fprintf(w, "✓") + } else { + fmt.Fprintf(w, "✗") + } +} + +func (s *MultiLineStatusHandler) writeLog(message string) { + //_, _ = fmt.Fprintf(s.out, "%s\n", message) } func (s *MultiLineStatusHandler) Info(message string) { - s.writeOut(message) + s.writeLog(message) } func (s *MultiLineStatusHandler) Warning(message string) { - s.writeOut(message) + s.writeLog(message) } func (s *MultiLineStatusHandler) Error(message string) { - s.writeOut(message) + s.writeLog(message) } func (s *MultiLineStatusHandler) Trace(message string) { if s.trace { - s.writeOut(message) + s.writeLog(message) } } func (s *MultiLineStatusHandler) PlainText(text string) { - s.writeOut(text) + s.writeLog(text) } func (sl *statusLine) Update(message string) { sl.message = message - sl.slh.writeOut(sl.message) + sl.slh.writeLog(sl.message) } func (sl *statusLine) End(success bool) { + sl.finished = true + sl.success = success + // make sure that the bar es rendered on top so that it can be properly popped + sl.bar.SetPriority(math.MinInt) + sl.bar.Increment() } From 46d44006be9b591ba4c24eb4e7f6b97d9eafc57c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 9 May 2022 14:43:23 +0200 Subject: [PATCH 0778/2916] fix: Few status reporting improvements --- pkg/deployment/deployment_collection.go | 44 +++++++++++++++---------- pkg/kluctl_project/project_load.go | 5 +++ pkg/kluctl_project/target_context.go | 4 +++ 3 files changed, 36 insertions(+), 17 deletions(-) diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index 9c26fa86e..fc6d76ef8 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -161,14 +161,12 @@ func (c *DeploymentCollection) resolveSealedSecrets() error { } func (c *DeploymentCollection) buildKustomizeObjects(k *k8s.K8sCluster) error { - s := status.Start(c.ctx, "Building kustomize objects") - defer s.Failed() - var wg sync.WaitGroup var errs []error var mutex sync.Mutex sem := semaphore.NewWeighted(16) + s := status.Start(c.ctx, "Building kustomize objects") for _, d_ := range c.Deployments { d := d_ @@ -179,20 +177,32 @@ func (c *DeploymentCollection) buildKustomizeObjects(k *k8s.K8sCluster) error { mutex.Lock() errs = append(errs, fmt.Errorf("building kustomize objects for %s failed. %w", *d.dir, err)) mutex.Unlock() - } else { - wg.Add(1) - go func() { - _ = sem.Acquire(context.Background(), 1) - defer sem.Release(1) - - err := d.postprocessAndLoadObjects(k, c.Images) - if err != nil { - mutex.Lock() - errs = append(errs, fmt.Errorf("postprocessing kustomize objects for %s failed: %w", *d.dir, err)) - mutex.Unlock() - } - wg.Done() - }() + } + wg.Done() + }() + } + wg.Wait() + + if len(errs) != 0 { + s.Failed() + return utils.NewErrorListOrNil(errs) + } + s.Success() + + s = status.Start(c.ctx, "Postprocessing objects") + for _, d_ := range c.Deployments { + d := d_ + + wg.Add(1) + go func() { + _ = sem.Acquire(context.Background(), 1) + defer sem.Release(1) + + err := d.postprocessAndLoadObjects(k, c.Images) + if err != nil { + mutex.Lock() + errs = append(errs, fmt.Errorf("postprocessing kustomize objects for %s failed: %w", *d.dir, err)) + mutex.Unlock() } wg.Done() diff --git a/pkg/kluctl_project/project_load.go b/pkg/kluctl_project/project_load.go index 4c5b74fb6..bd99e8291 100644 --- a/pkg/kluctl_project/project_load.go +++ b/pkg/kluctl_project/project_load.go @@ -189,6 +189,9 @@ func (c *LoadedKluctlProject) loadKluctlProject() error { } } + s := status.Start(c.ctx, "Loading kluctl project") + defer s.Failed() + deploymentInfo, err := c.loadExternalProject(c.Config.Deployment, "", c.loadArgs.LocalDeployment) if err != nil { return err @@ -226,6 +229,8 @@ func (c *LoadedKluctlProject) loadKluctlProject() error { c.ClustersDir = mergedClustersDir c.sealedSecretsDir = sealedSecretsInfo.dir + s.Success() + return nil } diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index bf4d1b369..1d6398ae0 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -6,6 +6,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/deployment" "github.com/kluctl/kluctl/v2/pkg/jinja2" "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" @@ -60,10 +61,13 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, clientConfig var k *k8s.K8sCluster if clientConfig != nil { + s := status.Start(ctx, "Initializing k8s client") k, err = k8s.NewK8sCluster(ctx, clientConfig, dryRun) if err != nil { + s.Failed() return nil, err } + s.Success() } varsCtx := jinja2.NewVarsCtx(p.J2) From 0949a6ba0a1b0d9ce81d92cb0b74312beea72e3f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 9 May 2022 14:44:03 +0200 Subject: [PATCH 0779/2916] fix: Stop printing git progress --- pkg/git/mirrored_repo.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/git/mirrored_repo.go b/pkg/git/mirrored_repo.go index 2eeb19bfd..874942200 100644 --- a/pkg/git/mirrored_repo.go +++ b/pkg/git/mirrored_repo.go @@ -229,7 +229,6 @@ func (g *MirroredGitRepo) update(s *status.StatusContext, repoDir string, authPr err = remote.FetchContext(g.ctx, &git.FetchOptions{ Auth: auth.AuthMethod, CABundle: auth.CABundle, - Progress: os.Stdout, Tags: git.AllTags, Force: true, }) @@ -259,7 +258,6 @@ func (g *MirroredGitRepo) update(s *status.StatusContext, repoDir string, authPr _ = ioutil.WriteFile(filepath.Join(g.mirrorDir, ".update-time"), []byte(time.Now().Format(time.RFC3339Nano)), 0644) - s.Success() return nil } From 4873a78afdf3aeafd1d411b143300fba82da06d7 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 9 May 2022 14:45:52 +0200 Subject: [PATCH 0780/2916] fix: Redirect klog output to status handler --- cmd/kluctl/commands/root.go | 5 +++ pkg/status/utils.go | 65 +++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 pkg/status/utils.go diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index a35f2c063..795c5451c 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -26,6 +26,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/yaml" "github.com/spf13/cobra" "github.com/spf13/viper" + "k8s.io/klog/v2" "net/http" "os" "path/filepath" @@ -71,6 +72,10 @@ var cliCtx = context.Background() func (c *cli) setupStatusHandler() error { sh := status.NewMultiLineStatusHandler(cliCtx, os.Stderr, c.Debug) cliCtx = status.NewContext(cliCtx, sh) + + klog.LogToStderr(false) + klog.SetOutput(status.NewLineRedirector(sh.Info)) + return nil } diff --git a/pkg/status/utils.go b/pkg/status/utils.go new file mode 100644 index 000000000..b6d6fbb64 --- /dev/null +++ b/pkg/status/utils.go @@ -0,0 +1,65 @@ +package status + +import ( + "bufio" + "io" +) + +func NewLineRedirector(cb func(line string)) io.WriteCloser { + r, w := io.Pipe() + + go func() { + br := bufio.NewReader(r) + scanner := bufio.NewScanner(&replaceRReader{reader: br}) + for scanner.Scan() { + msg := scanner.Text() + if msg == "" { + continue + } + cb(msg) + } + }() + + return w +} + +type replaceRReader struct { + reader *bufio.Reader + lastR bool +} + +func (r *replaceRReader) Read(p []byte) (int, error) { + written := 0 + for true { + b, err := r.reader.ReadByte() + if err != nil { + if err == io.EOF { + if written == 0 { + return 0, err + } + return written, nil + } + return 0, err + } + + if b == '\r' { + p[written] = '\n' + written++ + r.lastR = true + break + } else if b == '\n' { + if r.lastR { + continue + } + p[written] = '\n' + written++ + r.lastR = false + break + } else { + p[written] = b + written++ + r.lastR = false + } + } + return written, nil +} From ccb8ad8b7fa0190f42eb80f8db472b1da9c67529 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 9 May 2022 16:16:33 +0200 Subject: [PATCH 0781/2916] feat: Use status handler for deployment progress reporting --- pkg/deployment/commands/downscale.go | 2 +- pkg/deployment/commands/poke_images.go | 2 +- pkg/deployment/commands/validate.go | 2 +- pkg/deployment/utils/apply_utils.go | 110 +++++++--------- pkg/deployment/utils/hooks_util.go | 12 +- pkg/deployment/utils/progress.go | 126 ------------------- pkg/deployment/utils/remote_objects_utils.go | 10 +- pkg/status/status.go | 115 ++++++++++++++++- pkg/status/status_handler.go | 81 +++++++----- 9 files changed, 228 insertions(+), 232 deletions(-) delete mode 100644 pkg/deployment/utils/progress.go diff --git a/pkg/deployment/commands/downscale.go b/pkg/deployment/commands/downscale.go index 60cadd857..82e89b2cc 100644 --- a/pkg/deployment/commands/downscale.go +++ b/pkg/deployment/commands/downscale.go @@ -40,7 +40,7 @@ func (cmd *DownscaleCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*types if !d.CheckInclusionForDeploy() { continue } - au := ad.NewApplyUtil(ctx, utils2.NewProgressCtx(ctx, nil, d.RelToProjectItemDir, 0, true)) + au := ad.NewApplyUtil(ctx, nil) for _, o := range d.Objects { o := o ref := o.GetK8sRef() diff --git a/pkg/deployment/commands/poke_images.go b/pkg/deployment/commands/poke_images.go index c794e8a8a..03ae6dbc3 100644 --- a/pkg/deployment/commands/poke_images.go +++ b/pkg/deployment/commands/poke_images.go @@ -76,7 +76,7 @@ func (cmd *PokeImagesCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*type wg.Add(1) go func() { defer wg.Done() - au := ad.NewApplyUtil(ctx, utils2.NewProgressCtx(ctx, nil, ref.String(), 0, true)) + au := ad.NewApplyUtil(ctx, nil) au.ReplaceObject(ref, ru.GetRemoteObject(ref), func(o *uo.UnstructuredObject) (*uo.UnstructuredObject, error) { return doPokeImage(containers, o) }) diff --git a/pkg/deployment/commands/validate.go b/pkg/deployment/commands/validate.go index fbe9102ec..d9a63720d 100644 --- a/pkg/deployment/commands/validate.go +++ b/pkg/deployment/commands/validate.go @@ -40,7 +40,7 @@ func (cmd *ValidateCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*types. if !d.CheckInclusionForDeploy() { continue } - au := ad.NewApplyUtil(ctx, utils2.NewProgressCtx(ctx, nil, d.RelToProjectItemDir, 0, true)) + au := ad.NewApplyUtil(ctx, nil) h := utils2.NewHooksUtil(au) for _, o := range d.Objects { hook := h.GetHook(o) diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index b8c78acbb..a93028eae 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -13,11 +13,9 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/validation" - "github.com/vbauerster/mpb/v7" "golang.org/x/sync/semaphore" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" - "os" "reflect" "strings" "sync" @@ -53,7 +51,7 @@ type ApplyUtil struct { ru *RemoteObjectUtils k *k8s.K8sCluster o *ApplyUtilOptions - pctx *progressCtx + sctx *status.StatusContext } type ApplyDeploymentsUtil struct { @@ -85,7 +83,7 @@ func NewApplyDeploymentsUtil(ctx context.Context, dew *DeploymentErrorsAndWarnin return ret } -func (ad *ApplyDeploymentsUtil) NewApplyUtil(ctx context.Context, pctx *progressCtx) *ApplyUtil { +func (ad *ApplyDeploymentsUtil) NewApplyUtil(ctx context.Context, statusCtx *status.StatusContext) *ApplyUtil { ret := &ApplyUtil{ ctx: ctx, dew: ad.dew, @@ -98,7 +96,7 @@ func (ad *ApplyDeploymentsUtil) NewApplyUtil(ctx context.Context, pctx *progress ru: ad.ru, k: ad.k, o: ad.o, - pctx: pctx, + sctx: statusCtx, } ad.results = append(ad.results, ret) return ret @@ -184,7 +182,7 @@ func (a *ApplyUtil) retryApplyForceReplace(x *uo.UnstructuredObject, hook bool, warn := fmt.Errorf("patching %s failed, retrying by deleting and re-applying", ref.String()) a.HandleWarning(ref, warn) - a.pctx.Warningf(warn.Error()) + status.Warning(a.ctx, warn.Error()) if !a.DeleteObject(ref, hook) { return @@ -216,7 +214,7 @@ func (a *ApplyUtil) retryApplyWithReplace(x *uo.UnstructuredObject, hook bool, r warn := fmt.Errorf("patching %s failed, retrying with replace instead of patch", ref.String()) a.HandleWarning(ref, warn) - a.pctx.Warningf(warn.Error()) + status.Warning(a.ctx, warn.Error()) rv := remoteObject.GetK8sResourceVersion() x2 := x.Clone() @@ -367,7 +365,7 @@ func (a *ApplyUtil) WaitReadiness(ref k8s2.ObjectRef, timeout time.Duration) boo } timeoutTimer := time.NewTimer(timeout) - a.pctx.Debugf("Waiting for %s to get ready", ref.String()) + status.Trace(a.ctx, "Waiting for %s to get ready", ref.String()) lastLogTime := time.Now() didLog := false @@ -380,7 +378,7 @@ func (a *ApplyUtil) WaitReadiness(ref k8s2.ObjectRef, timeout time.Duration) boo if err != nil { if errors.IsNotFound(err) { if didLog { - a.pctx.Warningf("Cancelled waiting for %s as it disappeared while waiting for it (%ds elapsed)", ref.String(), elapsed) + status.Warning(a.ctx, "Cancelled waiting for %s as it disappeared while waiting for it (%ds elapsed)", ref.String(), elapsed) } a.HandleError(ref, fmt.Errorf("%s disappeared while waiting for it to become ready", ref.String())) return false @@ -391,13 +389,13 @@ func (a *ApplyUtil) WaitReadiness(ref k8s2.ObjectRef, timeout time.Duration) boo v := validation.ValidateObject(a.k, o, false) if v.Ready { if didLog { - a.pctx.Infof("Finished waiting for %s (%ds elapsed)", ref.String(), elapsed) + a.sctx.InfoFallback("Finished waiting for %s (%ds elapsed)", ref.String(), elapsed) } return true } if len(v.Errors) != 0 { if didLog { - a.pctx.Warningf("Cancelled waiting for %s due to errors (%ds elapsed)", ref.String(), elapsed) + status.Warning(a.ctx, "Cancelled waiting for %s due to errors (%ds elapsed)", ref.String(), elapsed) } for _, e := range v.Errors { a.HandleError(ref, fmt.Errorf(e.Error)) @@ -405,14 +403,14 @@ func (a *ApplyUtil) WaitReadiness(ref k8s2.ObjectRef, timeout time.Duration) boo return false } - a.pctx.SetStatus(fmt.Sprintf("Waiting for %s to get ready...", ref.String())) + a.sctx.Update(fmt.Sprintf("Waiting for %s to get ready...", ref.String())) if !didLog { - a.pctx.Infof("Waiting for %s to get ready... (%ds elapsed)", ref.String(), elapsed) + a.sctx.InfoFallback("Waiting for %s to get ready... (%ds elapsed)", ref.String(), elapsed) didLog = true lastLogTime = time.Now() } else if didLog && time.Now().Sub(lastLogTime) >= 10*time.Second { - a.pctx.Infof("Still waiting for %s to get ready... (%ds elapsed)", ref.String(), elapsed) + a.sctx.InfoFallback("Still waiting for %s to get ready... (%ds elapsed)", ref.String(), elapsed) lastLogTime = time.Now() } @@ -421,12 +419,12 @@ func (a *ApplyUtil) WaitReadiness(ref k8s2.ObjectRef, timeout time.Duration) boo continue case <-timeoutTimer.C: err := fmt.Errorf("timed out while waiting for readiness of %s", ref.String()) - a.pctx.Warningf("%s (%ds elapsed)", err.Error(), elapsed) + status.Warning(a.ctx, "%s (%ds elapsed)", err.Error(), elapsed) a.HandleError(ref, err) return false case <-a.ctx.Done(): err := fmt.Errorf("failed waiting for readiness of %s: %w", ref.String(), err) - a.pctx.Warningf("%s (%ds elapsed)", err.Error(), elapsed) + status.Warning(a.ctx, "%s (%ds elapsed)", err.Error(), elapsed) a.HandleError(ref, err) return false } @@ -476,26 +474,26 @@ func (a *ApplyUtil) applyDeploymentItem(d *deployment.DeploymentItem) { // +1 to ensure that we don't prematurely complete the bar (which would happen as we don't count for waiting) total := len(applyObjects) + len(preHooks) + len(postHooks) + 1 - a.pctx.SetTotal(int64(total)) + a.sctx.SetTotal(total) if !d.CheckInclusionForDeploy() { - a.pctx.InfofAndStatus("Skipped") - a.pctx.Finish() + a.sctx.UpdateAndInfoFallback("Skipped") + a.sctx.Success() return } if len(toDelete) != 0 { - a.pctx.Infof("Deleting %d objects", len(toDelete)) + a.sctx.InfoFallback("Deleting %d objects", len(toDelete)) for i, ref := range toDelete { - a.pctx.SetStatus(fmt.Sprintf("Deleting object %s (%d of %d)", ref.String(), i+1, len(toDelete))) + a.sctx.Update(fmt.Sprintf("Deleting object %s (%d of %d)", ref.String(), i+1, len(toDelete))) a.DeleteObject(ref, false) - a.pctx.Increment() + a.sctx.Increment() } } h.RunHooks(preHooks) if len(applyObjects) != 0 { - a.pctx.Infof("Applying %d objects", len(applyObjects)) + a.sctx.InfoFallback("Applying %d objects", len(applyObjects)) } startTime := time.Now() didLog := false @@ -505,11 +503,11 @@ func (a *ApplyUtil) applyDeploymentItem(d *deployment.DeploymentItem) { } ref := o.GetK8sRef() - a.pctx.SetStatus(fmt.Sprintf("Applying object %s (%d of %d)", ref.String(), i+1, len(applyObjects))) + a.sctx.Update(fmt.Sprintf("Applying object %s (%d of %d)", ref.String(), i+1, len(applyObjects))) a.ApplyObject(o, false, false) - a.pctx.Increment() + a.sctx.Increment() if time.Now().Sub(startTime) >= 10*time.Second || (didLog && i == len(applyObjects)-1) { - a.pctx.Infof("...applied %d of %d objects", i+1, len(applyObjects)) + a.sctx.InfoFallback("...applied %d of %d objects", i+1, len(applyObjects)) startTime = time.Now() didLog = true } @@ -545,8 +543,13 @@ func (a *ApplyUtil) applyDeploymentItem(d *deployment.DeploymentItem) { finalStatus += fmt.Sprintf(" Encountered %d warnings.", a.warningCount) } - a.pctx.SetStatus(strings.TrimSpace(finalStatus)) - a.pctx.Finish() + a.sctx.Update(strings.TrimSpace(finalStatus)) + + if a.errorCount == 0 { + a.sctx.Success() + } else { + a.sctx.Failed() + } } func (a *ApplyDeploymentsUtil) buildProgressName(d *deployment.DeploymentItem) *string { @@ -561,15 +564,11 @@ func (a *ApplyDeploymentsUtil) buildProgressName(d *deployment.DeploymentItem) * } func (a *ApplyDeploymentsUtil) ApplyDeployments() { - status.Info(a.ctx, "Running server-side apply for all objects") + s := status.Start(a.ctx, "Running server-side apply for all objects") + defer s.Failed() var wg sync.WaitGroup sem := semaphore.NewWeighted(8) - p := mpb.New( - mpb.WithWidth(utils.GetTermWidth()), - mpb.WithOutput(os.Stderr), - mpb.PopCompletedMode(), - ) maxNameLen := 0 for _, d := range a.deployments { @@ -590,13 +589,15 @@ func (a *ApplyDeploymentsUtil) ApplyDeployments() { _ = sem.Acquire(context.Background(), 1) progressName := a.buildProgressName(d) - var pctx *progressCtx + var sctx *status.StatusContext if progressName != nil { - pctx = NewProgressCtx(a.ctx, p, *progressName, maxNameLen, true) - } else { - pctx = NewProgressCtx(a.ctx, nil, "", 0, false) + sctx = status.StartWithOptions(a.ctx, + status.WithTotal(-1), + status.WithPrefix(*progressName), + status.WithStatus("Initializing..."), + ) } - a2 := a.NewApplyUtil(a.ctx, pctx) + a2 := a.NewApplyUtil(a.ctx, sctx) wg.Add(1) go func() { @@ -604,37 +605,22 @@ func (a *ApplyDeploymentsUtil) ApplyDeployments() { defer sem.Release(1) a2.applyDeploymentItem(d) - pctx.Finish() + + // if success was not signalled, get into failed status + sctx.Failed() }() barrier := (d.Config.Barrier != nil && *d.Config.Barrier) || d.Barrier if barrier { - bpctx := NewProgressCtx(a.ctx, p, "", maxNameLen, true) - bpctx.SetTotal(1) - - bpctx.InfofAndStatus("Waiting on barrier...") - stopCh := make(chan int) - doneCh := make(chan int) - go func() { - for { - select { - case <-stopCh: - bpctx.SetStatus(fmt.Sprintf("Finished waiting")) - bpctx.Finish() - doneCh <- 1 - return - case <-time.After(time.Second): - bpctx.SetStatus(fmt.Sprintf("Waiting on barrier...")) - } - } - }() + sctx := status.Start(a.ctx, "", status.WithTotal(1)) + sctx.UpdateAndInfoFallback("Waiting on barrier...") wg.Wait() - stopCh <- 1 - <-doneCh + sctx.UpdateAndInfoFallback(fmt.Sprintf("Finished waiting")) + sctx.Success() } } wg.Wait() - p.Wait() + s.Success() } func (a *ApplyUtil) ReplaceObject(ref k8s2.ObjectRef, firstVersion *uo.UnstructuredObject, callback func(o *uo.UnstructuredObject) (*uo.UnstructuredObject, error)) { diff --git a/pkg/deployment/utils/hooks_util.go b/pkg/deployment/utils/hooks_util.go index bee3b6811..b811962c7 100644 --- a/pkg/deployment/utils/hooks_util.go +++ b/pkg/deployment/utils/hooks_util.go @@ -82,12 +82,12 @@ func (u *HooksUtil) RunHooks(hooks []*hook) { for p := range h.deletePolicies { dpStr = append(dpStr, p) } - u.a.pctx.InfofAndStatus("Deleting hook %s due to hook-delete-policy %s (%d of %d)", ref.String(), strings.Join(dpStr, ","), i+1, cnt) + u.a.sctx.UpdateAndInfoFallback("Deleting hook %s due to hook-delete-policy %s (%d of %d)", ref.String(), strings.Join(dpStr, ","), i+1, cnt) return u.a.DeleteObject(ref, true) } if len(deleteBeforeObjects) != 0 { - u.a.pctx.Infof("Deleting %d hooks before hook execution", len(deleteBeforeObjects)) + u.a.sctx.InfoFallback("Deleting %d hooks before hook execution", len(deleteBeforeObjects)) } for i, h := range deleteBeforeObjects { doDeleteForPolicy(h, i, len(deleteBeforeObjects)) @@ -95,14 +95,14 @@ func (u *HooksUtil) RunHooks(hooks []*hook) { waitResults := make(map[k8s.ObjectRef]bool) if len(applyObjects) != 0 { - u.a.pctx.Infof("Applying %d hooks", len(applyObjects)) + u.a.sctx.InfoFallback("Applying %d hooks", len(applyObjects)) } for i, h := range applyObjects { ref := h.object.GetK8sRef() _, replaced := h.deletePolicies["before-hook-creation"] - u.a.pctx.DebugfAndStatus("Applying hook %s (%d of %d)", ref.String(), i+1, len(applyObjects)) + u.a.sctx.UpdateAndInfoFallback("Applying hook %s (%d of %d)", ref.String(), i+1, len(applyObjects)) u.a.ApplyObject(h.object, replaced, true) - u.a.pctx.Increment() + u.a.sctx.Increment() if u.a.HadError(ref) { continue @@ -133,7 +133,7 @@ func (u *HooksUtil) RunHooks(hooks []*hook) { } if len(deleteAfterObjects) != 0 { - u.a.pctx.Infof("Deleting %d hooks after hook execution", len(deleteAfterObjects)) + u.a.sctx.InfoFallback("Deleting %d hooks after hook execution", len(deleteAfterObjects)) } for i, h := range deleteAfterObjects { doDeleteForPolicy(h, i, len(deleteAfterObjects)) diff --git a/pkg/deployment/utils/progress.go b/pkg/deployment/utils/progress.go deleted file mode 100644 index 34d03a61c..000000000 --- a/pkg/deployment/utils/progress.go +++ /dev/null @@ -1,126 +0,0 @@ -package utils - -import ( - "context" - "fmt" - "github.com/kluctl/kluctl/v2/pkg/status" - "github.com/mattn/go-isatty" - "github.com/vbauerster/mpb/v7" - "github.com/vbauerster/mpb/v7/decor" - "math" - "os" - "strings" - "sync" - "time" -) - -type progressCtx struct { - ctx context.Context - bar *mpb.Bar - doLog bool - total int64 - name string - status string - startTime time.Time - mutex sync.Mutex -} - -func NewProgressCtx(ctx context.Context, p *mpb.Progress, name string, maxNameWidth int, doLog bool) *progressCtx { - pctx := &progressCtx{ - ctx: ctx, - status: "Initializing...", - total: -1, - name: name, - startTime: time.Now(), - } - if !isatty.IsTerminal(os.Stderr.Fd()) || p == nil { - pctx.doLog = doLog - return pctx - } - - name += ":" - if len(name) < maxNameWidth+1 { - name += strings.Repeat(" ", maxNameWidth-len(name)+1) - } - name += " " - - if p != nil { - pctx.bar = p.AddBar(-1, - mpb.BarWidth(40), - mpb.PrependDecorators(decor.Name(name)), - mpb.PrependDecorators(decor.Any(pctx.ElapsedDecorFunc)), - mpb.AppendDecorators(decor.Any(pctx.StatusDecorFunc)), - ) - } - return pctx -} - -func (ctx *progressCtx) Infof(s string, args ...interface{}) { - if ctx.doLog { - status.Info(ctx.ctx, s, args...) - } -} - -func (ctx *progressCtx) Warningf(s string, args ...interface{}) { - if ctx.doLog { - status.Warning(ctx.ctx, s, args...) - } -} - -func (ctx *progressCtx) Debugf(s string, args ...interface{}) { - if ctx.doLog { - status.Trace(ctx.ctx, s, args...) - } -} - -func (ctx *progressCtx) InfofAndStatus(s string, args ...interface{}) { - ctx.Infof(s, args...) - ctx.SetStatus(fmt.Sprintf(s, args...)) -} - -func (ctx *progressCtx) DebugfAndStatus(s string, args ...interface{}) { - ctx.Debugf(s, args...) - ctx.SetStatus(fmt.Sprintf(s, args...)) -} - -func (ctx *progressCtx) SetStatus(s string) { - ctx.mutex.Lock() - defer ctx.mutex.Unlock() - ctx.status = s -} - -func (ctx *progressCtx) StatusDecorFunc(st decor.Statistics) string { - ctx.mutex.Lock() - defer ctx.mutex.Unlock() - return ctx.status -} - -func (ctx *progressCtx) ElapsedDecorFunc(st decor.Statistics) string { - s := fmt.Sprintf("%.3fs", time.Now().Sub(ctx.startTime).Seconds()) - if len(s) < 8 { - s = strings.Repeat(" ", 8-len(s)) + s - } - return s -} - -func (ctx *progressCtx) SetTotal(total int64) { - ctx.total = total - if ctx.bar != nil { - ctx.bar.SetTotal(total, false) - ctx.bar.EnableTriggerComplete() - } -} - -func (ctx *progressCtx) Increment() { - if ctx.bar != nil { - ctx.bar.Increment() - } -} - -func (ctx *progressCtx) Finish() { - if ctx.bar != nil { - // make sure that the bar es rendered on top so that it can be properly popped - ctx.bar.SetPriority(math.MinInt) - ctx.bar.SetCurrent(ctx.total) - } -} diff --git a/pkg/deployment/utils/remote_objects_utils.go b/pkg/deployment/utils/remote_objects_utils.go index 65ddf867e..ea2c3339a 100644 --- a/pkg/deployment/utils/remote_objects_utils.go +++ b/pkg/deployment/utils/remote_objects_utils.go @@ -33,7 +33,8 @@ func (u *RemoteObjectUtils) UpdateRemoteObjects(k *k8s.K8sCluster, labels map[st return nil } - status.Info(u.ctx, "Getting remote objects by commonLabels") + s := status.Start(u.ctx, "Getting remote objects by commonLabels") + defer s.Failed() allObjects, apiWarnings, err := k.ListAllObjects([]string{"get"}, "", labels, false) for gvk, aw := range apiWarnings { @@ -61,7 +62,7 @@ func (u *RemoteObjectUtils) UpdateRemoteObjects(k *k8s.K8sCluster, labels map[st u.mutex.Unlock() if len(notFoundRefsList) != 0 { - status.Info(u.ctx, "Getting %d additional remote objects", len(notFoundRefsList)) + s.UpdateAndInfoFallback("Getting %d additional remote objects", len(notFoundRefsList)) r, apiWarnings, err := k.GetObjectsByRefs(notFoundRefsList) for ref, aw := range apiWarnings { u.dew.AddApiWarnings(ref, aw) @@ -76,7 +77,7 @@ func (u *RemoteObjectUtils) UpdateRemoteObjects(k *k8s.K8sCluster, labels map[st u.mutex.Unlock() } - status.Info(u.ctx, "Getting namespaces") + s.UpdateAndInfoFallback("Getting namespaces") r, _, err := k.ListObjects(schema.GroupVersionKind{ Group: "", Version: "v1", @@ -88,6 +89,9 @@ func (u *RemoteObjectUtils) UpdateRemoteObjects(k *k8s.K8sCluster, labels map[st for _, o := range r { u.remoteNamespaces[o.GetK8sName()] = o } + + s.Success() + return nil } diff --git a/pkg/status/status.go b/pkg/status/status.go index a9b8b4df6..41914412b 100644 --- a/pkg/status/status.go +++ b/pkg/status/status.go @@ -12,15 +12,24 @@ type StatusContext struct { sl StatusLine finished bool failed bool + + prefix string + startMessage string + startPriority int + startTotal int + disableLogs bool } type StatusLine interface { + SetTotal(total int) + Increment() + Update(message string) End(success bool) } type StatusHandler interface { - StartStatus(message string) StatusLine + StartStatus(total int, message string) StatusLine Info(message string) Warning(message string) @@ -28,6 +37,7 @@ type StatusHandler interface { Trace(message string) PlainText(text string) + InfoFallback(sprintf string) } type contextKey struct{} @@ -44,23 +54,109 @@ func FromContext(ctx context.Context) StatusHandler { return v.(StatusHandler) } -func Start(ctx context.Context, status string, args ...any) *StatusContext { +type Option func(s *StatusContext) + +func WithPrefix(prefix string) Option { + return func(s *StatusContext) { + s.prefix = prefix + } +} + +func WithStatus(message string, args ...any) Option { + return func(s *StatusContext) { + s.startMessage = fmt.Sprintf(message, args...) + } +} + +func WithPriority(p int) Option { + return func(s *StatusContext) { + s.startPriority = p + } +} + +func WithTotal(t int) Option { + return func(s *StatusContext) { + s.startTotal = t + } +} + +func WithDisableLogs() Option { + return func(s *StatusContext) { + s.disableLogs = true + } +} + +func StartWithOptions(ctx context.Context, opts ...Option) *StatusContext { sh := FromContext(ctx) s := &StatusContext{ ctx: ctx, sh: sh, } - s.sl = sh.StartStatus(fmt.Sprintf(status, args...)) + for _, o := range opts { + o(s) + } + + s.sl = sh.StartStatus(s.startTotal, s.buildMessage(s.startMessage)) return s } +func Start(ctx context.Context, status string, args ...any) *StatusContext { + return StartWithOptions(ctx, + WithTotal(1), + WithStatus(status, args...), + ) +} + +func (s *StatusContext) buildMessage(message string, args ...any) string { + m := fmt.Sprintf(message, args...) + if s.prefix == "" { + return m + } + return fmt.Sprintf("%s: %s", s.prefix, m) +} + +func (s *StatusContext) SetTotal(total int) { + if s == nil { + return + } + s.sl.SetTotal(total) +} + +func (s *StatusContext) Increment() { + if s == nil { + return + } + s.sl.Increment() +} + func (s *StatusContext) Update(message string, args ...any) { - s.sl.Update(fmt.Sprintf(message, args...)) + if s == nil { + return + } + s.sl.Update(s.buildMessage(message, args...)) +} + +func (s *StatusContext) InfoFallback(message string, args ...any) { + if s == nil { + return + } + InfoFallback(s.ctx, s.buildMessage(message, args...)) +} + +func (s *StatusContext) UpdateAndInfoFallback(message string, args ...any) { + if s == nil { + return + } + s.Update(message, args...) + s.InfoFallback(message, args...) } func (s *StatusContext) Failed() { + if s == nil { + return + } if s.finished { return } @@ -69,6 +165,9 @@ func (s *StatusContext) Failed() { } func (s *StatusContext) FailedWithMessage(msg string, args ...any) { + if s == nil { + return + } if s.finished { return } @@ -77,6 +176,9 @@ func (s *StatusContext) FailedWithMessage(msg string, args ...any) { } func (s *StatusContext) Success() { + if s == nil { + return + } s.sl.End(true) s.finished = true } @@ -91,6 +193,11 @@ func Info(ctx context.Context, status string, args ...any) { slh.Info(fmt.Sprintf(status, args...)) } +func InfoFallback(ctx context.Context, status string, args ...any) { + slh := FromContext(ctx) + slh.InfoFallback(fmt.Sprintf(status, args...)) +} + func Warning(ctx context.Context, status string, args ...any) { slh := FromContext(ctx) slh.Warning(fmt.Sprintf(status, args...)) diff --git a/pkg/status/status_handler.go b/pkg/status/status_handler.go index 7f6bd0c39..0443aeec4 100644 --- a/pkg/status/status_handler.go +++ b/pkg/status/status_handler.go @@ -2,7 +2,6 @@ package status import ( "context" - "fmt" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/vbauerster/mpb/v7" "github.com/vbauerster/mpb/v7/decor" @@ -19,12 +18,12 @@ type MultiLineStatusHandler struct { type statusLine struct { slh *MultiLineStatusHandler + total int bar *mpb.Bar filler mpb.BarFiller message string - finished bool - success bool + barOverride string } func NewMultiLineStatusHandler(ctx context.Context, out io.Writer, trace bool) *MultiLineStatusHandler { @@ -44,18 +43,29 @@ func NewMultiLineStatusHandler(ctx context.Context, out io.Writer, trace bool) * return sh } -func (s *MultiLineStatusHandler) StartStatus(message string) StatusLine { +func (s *MultiLineStatusHandler) StartStatus(total int, message string) StatusLine { + return s.startStatus(total, message, 0, "") +} + +func (s *MultiLineStatusHandler) startStatus(total int, message string, priority int, barOverride string) *statusLine { sl := &statusLine{ - slh: s, - message: message, + slh: s, + total: total, + message: message, + barOverride: barOverride, } sl.filler = mpb.SpinnerStyle().PositionLeft().Build() - sl.bar = s.progress.Add(1, sl, + + opts := []mpb.BarOption{ mpb.BarWidth(1), mpb.AppendDecorators(decor.Any(sl.DecorMessage, decor.WCSyncWidthR)), - ) + } + if priority != 0 { + opts = append(opts, mpb.BarPriority(priority)) + } + + sl.bar = s.progress.Add(int64(total), sl, opts...) - s.writeLog(sl.message) return sl } @@ -64,50 +74,65 @@ func (sl *statusLine) DecorMessage(s decor.Statistics) string { } func (sl *statusLine) Fill(w io.Writer, reqWidth int, stat decor.Statistics) { - if !sl.finished { - sl.filler.Fill(w, reqWidth, stat) - } else if sl.success { - fmt.Fprintf(w, "✓") - } else { - fmt.Fprintf(w, "✗") + if sl.barOverride != "" { + _, _ = io.WriteString(w, sl.barOverride) + return } -} -func (s *MultiLineStatusHandler) writeLog(message string) { - //_, _ = fmt.Fprintf(s.out, "%s\n", message) + sl.filler.Fill(w, reqWidth, stat) } func (s *MultiLineStatusHandler) Info(message string) { - s.writeLog(message) + s.startStatus(1, message, math.MinInt, "ⓘ").end("ⓘ") +} + +func (s *MultiLineStatusHandler) InfoFallback(message string) { + // no fallback needed } func (s *MultiLineStatusHandler) Warning(message string) { - s.writeLog(message) + s.startStatus(1, message, math.MinInt, "⚠").end("⚠") } func (s *MultiLineStatusHandler) Error(message string) { - s.writeLog(message) + s.startStatus(1, message, math.MinInt, "✗").end("✗") } func (s *MultiLineStatusHandler) Trace(message string) { if s.trace { - s.writeLog(message) + s.Info(message) } } func (s *MultiLineStatusHandler) PlainText(text string) { - s.writeLog(text) + s.Info(text) +} + +func (sl *statusLine) SetTotal(total int) { + sl.total = total + sl.bar.SetTotal(int64(total), false) + sl.bar.EnableTriggerComplete() +} + +func (sl *statusLine) Increment() { + sl.bar.Increment() } func (sl *statusLine) Update(message string) { sl.message = message - sl.slh.writeLog(sl.message) } -func (sl *statusLine) End(success bool) { - sl.finished = true - sl.success = success +func (sl *statusLine) end(barOverride string) { + sl.barOverride = barOverride // make sure that the bar es rendered on top so that it can be properly popped sl.bar.SetPriority(math.MinInt) - sl.bar.Increment() + sl.bar.SetCurrent(int64(sl.total)) +} + +func (sl *statusLine) End(success bool) { + if success { + sl.end("✓") + } else { + sl.end("✗") + } } From cbe9e577718e6d22b24353de08a582ccafe5b288 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 9 May 2022 16:39:09 +0200 Subject: [PATCH 0782/2916] fix: Propery stop status reporting before exit --- cmd/kluctl/commands/root.go | 4 ++++ pkg/status/status.go | 1 + pkg/status/status_handler.go | 4 ++++ 3 files changed, 9 insertions(+) diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index 795c5451c..da3ab1d28 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -186,6 +186,10 @@ composed of multiple smaller parts (Helm/Kustomize/...) in a manageable and unif initViper() err = rootCmd.Execute() + + sh := status.FromContext(cliCtx) + sh.Stop() + if err != nil { status.Error(cliCtx, err.Error()) os.Exit(1) diff --git a/pkg/status/status.go b/pkg/status/status.go index 41914412b..734e233d0 100644 --- a/pkg/status/status.go +++ b/pkg/status/status.go @@ -29,6 +29,7 @@ type StatusLine interface { } type StatusHandler interface { + Stop() StartStatus(total int, message string) StatusLine Info(message string) diff --git a/pkg/status/status_handler.go b/pkg/status/status_handler.go index 0443aeec4..1f8c89bdc 100644 --- a/pkg/status/status_handler.go +++ b/pkg/status/status_handler.go @@ -43,6 +43,10 @@ func NewMultiLineStatusHandler(ctx context.Context, out io.Writer, trace bool) * return sh } +func (s *MultiLineStatusHandler) Stop() { + s.progress.Wait() +} + func (s *MultiLineStatusHandler) StartStatus(total int, message string) StatusLine { return s.startStatus(total, message, 0, "") } From dce7349822c44be68fcc0dc2879bd96b16873211 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 9 May 2022 16:40:03 +0200 Subject: [PATCH 0783/2916] feat: Support colors in status reports --- pkg/deployment/utils/apply_utils.go | 2 +- pkg/status/status.go | 22 +++++++++++++++++++--- pkg/status/status_handler.go | 22 ++++++++++++++-------- pkg/status/utils.go | 13 +++++++++++++ 4 files changed, 47 insertions(+), 12 deletions(-) diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index a93028eae..581245aef 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -477,7 +477,7 @@ func (a *ApplyUtil) applyDeploymentItem(d *deployment.DeploymentItem) { a.sctx.SetTotal(total) if !d.CheckInclusionForDeploy() { a.sctx.UpdateAndInfoFallback("Skipped") - a.sctx.Success() + a.sctx.Warning() return } diff --git a/pkg/status/status.go b/pkg/status/status.go index 734e233d0..4a49fa72c 100644 --- a/pkg/status/status.go +++ b/pkg/status/status.go @@ -20,12 +20,20 @@ type StatusContext struct { disableLogs bool } +type EndResult int + +const ( + EndSuccess EndResult = iota + EndWarning + EndError +) + type StatusLine interface { SetTotal(total int) Increment() Update(message string) - End(success bool) + End(result EndResult) } type StatusHandler interface { @@ -161,7 +169,7 @@ func (s *StatusContext) Failed() { if s.finished { return } - s.sl.End(false) + s.sl.End(EndError) s.failed = true } @@ -180,7 +188,15 @@ func (s *StatusContext) Success() { if s == nil { return } - s.sl.End(true) + s.sl.End(EndSuccess) + s.finished = true +} + +func (s *StatusContext) Warning() { + if s == nil { + return + } + s.sl.End(EndWarning) s.finished = true } diff --git a/pkg/status/status_handler.go b/pkg/status/status_handler.go index 1f8c89bdc..a1bd50036 100644 --- a/pkg/status/status_handler.go +++ b/pkg/status/status_handler.go @@ -87,7 +87,8 @@ func (sl *statusLine) Fill(w io.Writer, reqWidth int, stat decor.Statistics) { } func (s *MultiLineStatusHandler) Info(message string) { - s.startStatus(1, message, math.MinInt, "ⓘ").end("ⓘ") + o := withColor("green", "ⓘ") + s.startStatus(1, message, math.MinInt, o).end(o) } func (s *MultiLineStatusHandler) InfoFallback(message string) { @@ -95,11 +96,13 @@ func (s *MultiLineStatusHandler) InfoFallback(message string) { } func (s *MultiLineStatusHandler) Warning(message string) { - s.startStatus(1, message, math.MinInt, "⚠").end("⚠") + o := withColor("yellow", "⚠") + s.startStatus(1, message, math.MinInt, o).end(o) } func (s *MultiLineStatusHandler) Error(message string) { - s.startStatus(1, message, math.MinInt, "✗").end("✗") + o := withColor("red", "✗") + s.startStatus(1, message, math.MinInt, o).end(o) } func (s *MultiLineStatusHandler) Trace(message string) { @@ -133,10 +136,13 @@ func (sl *statusLine) end(barOverride string) { sl.bar.SetCurrent(int64(sl.total)) } -func (sl *statusLine) End(success bool) { - if success { - sl.end("✓") - } else { - sl.end("✗") +func (sl *statusLine) End(result EndResult) { + switch result { + case EndSuccess: + sl.end(withColor("green", "✓")) + case EndWarning: + sl.end(withColor("yellow", "⚠")) + case EndError: + sl.end(withColor("red", "✗")) } } diff --git a/pkg/status/utils.go b/pkg/status/utils.go index b6d6fbb64..0a2edfc82 100644 --- a/pkg/status/utils.go +++ b/pkg/status/utils.go @@ -2,6 +2,7 @@ package status import ( "bufio" + "fmt" "io" ) @@ -63,3 +64,15 @@ func (r *replaceRReader) Read(p []byte) (int, error) { } return written, nil } + +func withColor(c string, s string) string { + switch c { + case "red": + c = "\x1b[31m" + case "green": + c = "\x1b[32m" + case "yellow": + c = "\x1b[33m" + } + return fmt.Sprintf("%s%s\x1b[0m", c, s) +} From b7f22b91800b5262a3205362eae0da6634caefdf Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 9 May 2022 17:03:49 +0200 Subject: [PATCH 0784/2916] feat: Implement simple status reporter for non-terminal sessions --- cmd/kluctl/commands/root.go | 8 ++- pkg/deployment/utils/apply_utils.go | 4 +- pkg/deployment/utils/diff_utils_test.go | 3 +- pkg/status/simple_status_handler.go | 75 +++++++++++++++++++++++++ pkg/status/status.go | 5 +- 5 files changed, 89 insertions(+), 6 deletions(-) create mode 100644 pkg/status/simple_status_handler.go diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index da3ab1d28..306ad2f4d 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -24,6 +24,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils/versions" "github.com/kluctl/kluctl/v2/pkg/version" "github.com/kluctl/kluctl/v2/pkg/yaml" + "github.com/mattn/go-isatty" "github.com/spf13/cobra" "github.com/spf13/viper" "k8s.io/klog/v2" @@ -70,7 +71,12 @@ var flagGroups = []groupInfo{ var cliCtx = context.Background() func (c *cli) setupStatusHandler() error { - sh := status.NewMultiLineStatusHandler(cliCtx, os.Stderr, c.Debug) + var sh status.StatusHandler + if isatty.IsTerminal(os.Stderr.Fd()) { + sh = status.NewMultiLineStatusHandler(cliCtx, os.Stderr, c.Debug) + } else { + sh = status.NewSimpleStatusHandler(os.Stderr, c.Debug, "") + } cliCtx = status.NewContext(cliCtx, sh) klog.LogToStderr(false) diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index 581245aef..e72211935 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -594,7 +594,6 @@ func (a *ApplyDeploymentsUtil) ApplyDeployments() { sctx = status.StartWithOptions(a.ctx, status.WithTotal(-1), status.WithPrefix(*progressName), - status.WithStatus("Initializing..."), ) } a2 := a.NewApplyUtil(a.ctx, sctx) @@ -612,8 +611,7 @@ func (a *ApplyDeploymentsUtil) ApplyDeployments() { barrier := (d.Config.Barrier != nil && *d.Config.Barrier) || d.Barrier if barrier { - sctx := status.Start(a.ctx, "", status.WithTotal(1)) - sctx.UpdateAndInfoFallback("Waiting on barrier...") + sctx := status.StartWithOptions(a.ctx, status.WithStatus("Waiting on barrier..."), status.WithTotal(1)) wg.Wait() sctx.UpdateAndInfoFallback(fmt.Sprintf("Finished waiting")) sctx.Success() diff --git a/pkg/deployment/utils/diff_utils_test.go b/pkg/deployment/utils/diff_utils_test.go index bb494fcb4..0e1aa9d81 100644 --- a/pkg/deployment/utils/diff_utils_test.go +++ b/pkg/deployment/utils/diff_utils_test.go @@ -1,6 +1,7 @@ package utils import ( + "context" "github.com/kluctl/kluctl/v2/pkg/deployment" "github.com/kluctl/kluctl/v2/pkg/types" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" @@ -22,7 +23,7 @@ type diffTestConfig struct { } func (dtc *diffTestConfig) newRemoteObjects(dew *DeploymentErrorsAndWarnings) *RemoteObjectUtils { - ru := NewRemoteObjectsUtil(dew) + ru := NewRemoteObjectsUtil(context.TODO(), dew) for _, ro := range dtc.ro { ru.remoteObjects[ro.GetK8sRef()] = ro } diff --git a/pkg/status/simple_status_handler.go b/pkg/status/simple_status_handler.go new file mode 100644 index 000000000..9cd40fa59 --- /dev/null +++ b/pkg/status/simple_status_handler.go @@ -0,0 +1,75 @@ +package status + +import ( + "fmt" + "io" +) + +type simpleStatusHandler struct { + out io.Writer + trace bool + prefix string +} + +type simpleStatusLine struct { +} + +func NewSimpleStatusHandler(out io.Writer, trace bool, prefix string) StatusHandler { + return &simpleStatusHandler{ + out: out, + trace: trace, + prefix: prefix, + } +} + +func (s *simpleStatusHandler) Stop() { +} + +func (s *simpleStatusHandler) StartStatus(total int, message string) StatusLine { + if message != "" { + s.Info(message) + } + return &simpleStatusLine{} +} + +func (s *simpleStatusHandler) Info(message string) { + if s.prefix == "" { + fmt.Fprintf(s.out, "%s\n", message) + } else { + fmt.Fprintf(s.out, "%s: %s\n", s.prefix, message) + } +} + +func (s *simpleStatusHandler) Warning(message string) { + s.InfoFallback(message) +} + +func (s *simpleStatusHandler) Error(message string) { + s.InfoFallback(message) +} + +func (s *simpleStatusHandler) Trace(message string) { + if s.trace { + s.Info(message) + } +} + +func (s *simpleStatusHandler) PlainText(text string) { + _, _ = io.WriteString(s.out, text) +} + +func (s *simpleStatusHandler) InfoFallback(message string) { + s.Info(message) +} + +func (sl *simpleStatusLine) SetTotal(total int) { +} + +func (sl *simpleStatusLine) Increment() { +} + +func (sl *simpleStatusLine) Update(message string) { +} + +func (sl *simpleStatusLine) End(result EndResult) { +} diff --git a/pkg/status/status.go b/pkg/status/status.go index 4a49fa72c..5219a8fc9 100644 --- a/pkg/status/status.go +++ b/pkg/status/status.go @@ -46,7 +46,7 @@ type StatusHandler interface { Trace(message string) PlainText(text string) - InfoFallback(sprintf string) + InfoFallback(message string) } type contextKey struct{} @@ -119,6 +119,9 @@ func Start(ctx context.Context, status string, args ...any) *StatusContext { } func (s *StatusContext) buildMessage(message string, args ...any) string { + if message == "" { + return "" + } m := fmt.Sprintf(message, args...) if s.prefix == "" { return m From e45a8191f96cd442684a8dd3e5c4e4e18b94c5f4 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 9 May 2022 17:25:35 +0200 Subject: [PATCH 0785/2916] fix: Fix invalid call to status.Warning --- cmd/kluctl/commands/utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index f0ee358a7..30395bc61 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -51,7 +51,7 @@ func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, strictTemplates b repoRoot, err := git.DetectGitRepositoryRoot(cwd) if err != nil && projectFlags.FromArchive == "" { - status.Warning(cliCtx, "", "Failed to detect git project root. This might cause follow-up errors") + status.Warning(cliCtx, "Failed to detect git project root. This might cause follow-up errors") } loadArgs := kluctl_project.LoadKluctlProjectArgs{ From f29b38f53817381644e9d94e7399531b361ec689 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 9 May 2022 17:34:03 +0200 Subject: [PATCH 0786/2916] refactor: Pass callback instead of io.Writer to simpleStatusHandler --- cmd/kluctl/commands/root.go | 4 +++- pkg/status/simple_status_handler.go | 25 +++++++------------------ 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index 306ad2f4d..be41c6de7 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -75,7 +75,9 @@ func (c *cli) setupStatusHandler() error { if isatty.IsTerminal(os.Stderr.Fd()) { sh = status.NewMultiLineStatusHandler(cliCtx, os.Stderr, c.Debug) } else { - sh = status.NewSimpleStatusHandler(os.Stderr, c.Debug, "") + sh = status.NewSimpleStatusHandler(func(message string) { + _, _ = fmt.Fprintf(os.Stderr, "%s\n", message) + }, c.Debug) } cliCtx = status.NewContext(cliCtx, sh) diff --git a/pkg/status/simple_status_handler.go b/pkg/status/simple_status_handler.go index 9cd40fa59..8c15e1626 100644 --- a/pkg/status/simple_status_handler.go +++ b/pkg/status/simple_status_handler.go @@ -1,24 +1,17 @@ package status -import ( - "fmt" - "io" -) - type simpleStatusHandler struct { - out io.Writer - trace bool - prefix string + cb func(message string) + trace bool } type simpleStatusLine struct { } -func NewSimpleStatusHandler(out io.Writer, trace bool, prefix string) StatusHandler { +func NewSimpleStatusHandler(cb func(message string), trace bool) StatusHandler { return &simpleStatusHandler{ - out: out, - trace: trace, - prefix: prefix, + cb: cb, + trace: trace, } } @@ -33,11 +26,7 @@ func (s *simpleStatusHandler) StartStatus(total int, message string) StatusLine } func (s *simpleStatusHandler) Info(message string) { - if s.prefix == "" { - fmt.Fprintf(s.out, "%s\n", message) - } else { - fmt.Fprintf(s.out, "%s: %s\n", s.prefix, message) - } + s.cb(message) } func (s *simpleStatusHandler) Warning(message string) { @@ -55,7 +44,7 @@ func (s *simpleStatusHandler) Trace(message string) { } func (s *simpleStatusHandler) PlainText(text string) { - _, _ = io.WriteString(s.out, text) + s.Info(text) } func (s *simpleStatusHandler) InfoFallback(message string) { From 380548c8daf6e8457acac27f14afaf64b98f9b36 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 9 May 2022 17:40:13 +0200 Subject: [PATCH 0787/2916] tests: Fix windows tests --- e2e/project.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/project.go b/e2e/project.go index c19bd3b67..6068a7ceb 100644 --- a/e2e/project.go +++ b/e2e/project.go @@ -520,7 +520,7 @@ func (p *testProject) Kluctl(argsIn ...string) (string, string, error) { sep := ":" if runtime.GOOS == "windows" { sep = ";" - args = append(args, "-vdebug") + args = append(args, "--debug") } env := os.Environ() env = append(env, fmt.Sprintf("KUBECONFIG=%s", strings.Join(p.kubeconfigs, sep))) From 35f752cdd46a5a908096b2ab1ff8be7b0e2532f5 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 9 May 2022 17:54:19 +0200 Subject: [PATCH 0788/2916] fix: Fix crash due to reuse of status handler --- cmd/kluctl/commands/root.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index be41c6de7..491bff9fa 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -196,10 +196,11 @@ composed of multiple smaller parts (Helm/Kustomize/...) in a manageable and unif err = rootCmd.Execute() sh := status.FromContext(cliCtx) - sh.Stop() if err != nil { status.Error(cliCtx, err.Error()) + sh.Stop() os.Exit(1) } + sh.Stop() } From e21c0b78e58b1b21264e16e2a3c4a4b96da7b0b1 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 10 May 2022 11:43:16 +0200 Subject: [PATCH 0789/2916] fix: Fix and enforce Run() implementation in commands --- cmd/kluctl/commands/cmd_helm_pull.go | 7 +++---- cmd/kluctl/commands/cmd_helm_update.go | 7 +++---- cmd/kluctl/commands/cobra_utils.go | 9 +++++---- cmd/kluctl/commands/root.go | 4 ++++ 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/cmd/kluctl/commands/cmd_helm_pull.go b/cmd/kluctl/commands/cmd_helm_pull.go index 6d5d5b6fe..5739b54ed 100644 --- a/cmd/kluctl/commands/cmd_helm_pull.go +++ b/cmd/kluctl/commands/cmd_helm_pull.go @@ -1,7 +1,6 @@ package commands import ( - "context" "github.com/kluctl/kluctl/v2/pkg/deployment" "github.com/kluctl/kluctl/v2/pkg/status" "io/fs" @@ -18,7 +17,7 @@ func (cmd *helmPullCmd) Help() string { pulling is only needed when really required (e.g. when the chart version changes).` } -func (cmd *helmPullCmd) Run(ctx context.Context) error { +func (cmd *helmPullCmd) Run() error { rootPath := "." if cmd.LocalDeployment != "" { rootPath = cmd.LocalDeployment @@ -26,13 +25,13 @@ func (cmd *helmPullCmd) Run(ctx context.Context) error { err := filepath.WalkDir(rootPath, func(p string, d fs.DirEntry, err error) error { fname := filepath.Base(p) if fname == "helm-chart.yml" || fname == "helm-chart.yaml" { - s := status.Start(ctx, "Pulling for %s", p) + s := status.Start(cliCtx, "Pulling for %s", p) chart, err := deployment.NewHelmChart(p) if err != nil { s.FailedWithMessage(err.Error()) return err } - err = chart.Pull(ctx) + err = chart.Pull(cliCtx) if err != nil { s.FailedWithMessage(err.Error()) return err diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go index b9f9d8caf..2d9f7e5f6 100644 --- a/cmd/kluctl/commands/cmd_helm_update.go +++ b/cmd/kluctl/commands/cmd_helm_update.go @@ -1,7 +1,6 @@ package commands import ( - "context" "fmt" "github.com/go-git/go-git/v5" "github.com/kluctl/kluctl/v2/pkg/deployment" @@ -21,7 +20,7 @@ func (cmd *helmUpdateCmd) Help() string { return `Optionally performs the actual upgrade and/or add a commit to version control.` } -func (cmd *helmUpdateCmd) Run(ctx context.Context) error { +func (cmd *helmUpdateCmd) Run() error { rootPath := "." if cmd.LocalDeployment != "" { rootPath = cmd.LocalDeployment @@ -40,7 +39,7 @@ func (cmd *helmUpdateCmd) Run(ctx context.Context) error { } statusPrefix := filepath.Base(p) - s := status.Start(ctx, "%s: Checking for updates", statusPrefix) + s := status.Start(cliCtx, "%s: Checking for updates", statusPrefix) defer s.Failed() newVersion, updated, err := chart.CheckUpdate() @@ -85,7 +84,7 @@ func (cmd *helmUpdateCmd) Run(ctx context.Context) error { s.Update("%s: Pulling new version", statusPrefix) defer s.Failed() - err = chart.Pull(ctx) + err = chart.Pull(cliCtx) if err != nil { return err } diff --git a/cmd/kluctl/commands/cobra_utils.go b/cmd/kluctl/commands/cobra_utils.go index c45c33f14..493b15605 100644 --- a/cmd/kluctl/commands/cobra_utils.go +++ b/cmd/kluctl/commands/cobra_utils.go @@ -65,10 +65,11 @@ func (c *rootCommand) buildCobraCmd(parent *commandAndGroups, cmdStruct interfac } runP, ok := cmdStruct.(runProvider) - if ok { - cg.cmd.RunE = func(cmd *cobra.Command, args []string) error { - return runP.Run() - } + if !ok { + panic(fmt.Sprintf("%s does not implement runProvider", name)) + } + cg.cmd.RunE = func(cmd *cobra.Command, args []string) error { + return runP.Run() } err := c.buildCobraSubCommands(cg, cmdStruct) diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index 491bff9fa..f5c40918b 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -151,6 +151,10 @@ func (c *cli) preRun() error { return nil } +func (c *cli) Run() error { + return nil +} + func initViper() { viper.SetConfigName("config") viper.SetConfigType("yaml") From 930a64a4e6cac108d85496491ffd5fac1fbd18df Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 10 May 2022 11:43:51 +0200 Subject: [PATCH 0790/2916] fix: Always initialize status handler even if no command is run --- cmd/kluctl/commands/root.go | 15 ++++++--------- pkg/status/simple_status_handler.go | 4 ++++ pkg/status/status.go | 1 + pkg/status/status_handler.go | 4 ++++ 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index f5c40918b..dad3ae8bf 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -70,21 +70,19 @@ var flagGroups = []groupInfo{ var cliCtx = context.Background() -func (c *cli) setupStatusHandler() error { +func setupStatusHandler() { var sh status.StatusHandler if isatty.IsTerminal(os.Stderr.Fd()) { - sh = status.NewMultiLineStatusHandler(cliCtx, os.Stderr, c.Debug) + sh = status.NewMultiLineStatusHandler(cliCtx, os.Stderr, false) } else { sh = status.NewSimpleStatusHandler(func(message string) { _, _ = fmt.Fprintf(os.Stderr, "%s\n", message) - }, c.Debug) + }, false) } cliCtx = status.NewContext(cliCtx, sh) klog.LogToStderr(false) klog.SetOutput(status.NewLineRedirector(sh.Info)) - - return nil } type VersionCheckState struct { @@ -144,9 +142,7 @@ func (c *cli) checkNewVersion() { } func (c *cli) preRun() error { - if err := c.setupStatusHandler(); err != nil { - return err - } + status.FromContext(cliCtx).SetTrace(c.Debug) c.checkNewVersion() return nil } @@ -195,9 +191,10 @@ composed of multiple smaller parts (Helm/Kustomize/...) in a manageable and unif return root.preRun() } + setupStatusHandler() initViper() - err = rootCmd.Execute() + err = rootCmd.ExecuteContext(cliCtx) sh := status.FromContext(cliCtx) diff --git a/pkg/status/simple_status_handler.go b/pkg/status/simple_status_handler.go index 8c15e1626..21a55a6ba 100644 --- a/pkg/status/simple_status_handler.go +++ b/pkg/status/simple_status_handler.go @@ -15,6 +15,10 @@ func NewSimpleStatusHandler(cb func(message string), trace bool) StatusHandler { } } +func (s *simpleStatusHandler) SetTrace(trace bool) { + s.trace = trace +} + func (s *simpleStatusHandler) Stop() { } diff --git a/pkg/status/status.go b/pkg/status/status.go index 5219a8fc9..d58c774f2 100644 --- a/pkg/status/status.go +++ b/pkg/status/status.go @@ -37,6 +37,7 @@ type StatusLine interface { } type StatusHandler interface { + SetTrace(trace bool) Stop() StartStatus(total int, message string) StatusLine diff --git a/pkg/status/status_handler.go b/pkg/status/status_handler.go index a1bd50036..c0205333a 100644 --- a/pkg/status/status_handler.go +++ b/pkg/status/status_handler.go @@ -43,6 +43,10 @@ func NewMultiLineStatusHandler(ctx context.Context, out io.Writer, trace bool) * return sh } +func (s *MultiLineStatusHandler) SetTrace(trace bool) { + s.trace = trace +} + func (s *MultiLineStatusHandler) Stop() { s.progress.Wait() } From 64a316a81213c0502c82890e8c162660bc5ab1f3 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 10 May 2022 13:41:13 +0200 Subject: [PATCH 0791/2916] refactor: Move clientPool initialization into own function --- pkg/k8s/k8s_cluster.go | 62 +++++++++++++++++++++++++++--------------- 1 file changed, 40 insertions(+), 22 deletions(-) diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index b6f4939b6..b0b40d311 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -80,6 +80,8 @@ func (p *parallelClientEntry) HandleWarningHeader(code int, agent string, text s }) } +const parallelClients = 16 + func NewK8sCluster(ctx context.Context, configIn *rest.Config, dryRun bool) (*K8sCluster, error) { k := &K8sCluster{ ctx: ctx, @@ -101,7 +103,39 @@ func NewK8sCluster(ctx context.Context, configIn *rest.Config, dryRun bool) (*K8 } k.discovery = discovery2 - parallelClients := 16 + err = k.initClientPool() + if err != nil { + return nil, err + } + + v, err := k.discovery.ServerVersion() + if err != nil { + return nil, err + } + v2, err := goversion.NewVersion(v.String()) + if err != nil { + return nil, err + } + k.ServerVersion = v2 + + err = k.updateResources(true) + if err != nil { + return nil, err + } + + return k, nil +} + +func (k *K8sCluster) initClientPool() error { + var err error + + if k.clientPool != nil { + for i := 0; i < parallelClients; i++ { + p := <-k.clientPool + p.http.CloseIdleConnections() + } + } + k.clientPool = make(chan *parallelClientEntry, parallelClients) for i := 0; i < parallelClients; i++ { p := ¶llelClientEntry{} @@ -110,43 +144,27 @@ func NewK8sCluster(ctx context.Context, configIn *rest.Config, dryRun bool) (*K8 p.http, err = rest.HTTPClientFor(config) if err != nil { - return nil, err + return err } p.corev1, err = corev1.NewForConfigAndClient(config, p.http) if err != nil { - return nil, err + return err } p.dynamicClient, err = dynamic.NewForConfigAndClient(config, p.http) if err != nil { - return nil, err + return err } p.metadataClient, err = metadata.NewForConfigAndClient(config, p.http) if err != nil { - return nil, err + return err } k.clientPool <- p } - - v, err := k.discovery.ServerVersion() - if err != nil { - return nil, err - } - v2, err := goversion.NewVersion(v.String()) - if err != nil { - return nil, err - } - k.ServerVersion = v2 - - err = k.updateResources(true) - if err != nil { - return nil, err - } - - return k, nil + return nil } func (k *K8sCluster) ReadWrite() *K8sCluster { From 393fa8f934ea3a40a9855f3749c0309cfeb8442f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 10 May 2022 13:42:33 +0200 Subject: [PATCH 0792/2916] fix: Take CRDs into account when checking wether an object is namespaced --- pkg/deployment/deployment_collection.go | 31 +++++++++++++-- pkg/deployment/deployment_item.go | 52 +++++++++++++++++++++++-- pkg/k8s/k8s_cluster.go | 30 +++++++++----- 3 files changed, 96 insertions(+), 17 deletions(-) diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index fc6d76ef8..def63872c 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -166,6 +166,12 @@ func (c *DeploymentCollection) buildKustomizeObjects(k *k8s.K8sCluster) error { var mutex sync.Mutex sem := semaphore.NewWeighted(16) + handleError := func(err error) { + mutex.Lock() + errs = append(errs, err) + mutex.Unlock() + } + s := status.Start(c.ctx, "Building kustomize objects") for _, d_ := range c.Deployments { d := d_ @@ -174,9 +180,7 @@ func (c *DeploymentCollection) buildKustomizeObjects(k *k8s.K8sCluster) error { go func() { err := d.buildKustomize() if err != nil { - mutex.Lock() - errs = append(errs, fmt.Errorf("building kustomize objects for %s failed. %w", *d.dir, err)) - mutex.Unlock() + handleError(fmt.Errorf("building kustomize objects for %s failed. %w", *d.dir, err)) } wg.Done() }() @@ -190,6 +194,25 @@ func (c *DeploymentCollection) buildKustomizeObjects(k *k8s.K8sCluster) error { s.Success() s = status.Start(c.ctx, "Postprocessing objects") + for _, d_ := range c.Deployments { + d := d_ + + wg.Add(1) + go func() { + err := d.loadObjects() + if err != nil { + handleError(fmt.Errorf("loading objects failed: %w", err)) + } else { + err = d.postprocessCRDs(k) + if err != nil { + handleError(fmt.Errorf("postprocessing CRDs failed: %w", err)) + } + } + wg.Done() + }() + } + wg.Wait() + for _, d_ := range c.Deployments { d := d_ @@ -198,7 +221,7 @@ func (c *DeploymentCollection) buildKustomizeObjects(k *k8s.K8sCluster) error { _ = sem.Acquire(context.Background(), 1) defer sem.Release(1) - err := d.postprocessAndLoadObjects(k, c.Images) + err := d.postprocessObjects(k, c.Images) if err != nil { mutex.Lock() errs = append(errs, fmt.Errorf("postprocessing kustomize objects for %s failed: %w", *d.dir, err)) diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 1904a8842..c3f41e749 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -10,6 +10,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/yaml" "io/fs" "io/ioutil" + "k8s.io/apimachinery/pkg/runtime/schema" "os" "path/filepath" "sigs.k8s.io/kustomize/api/filesys" @@ -364,7 +365,7 @@ func (di *DeploymentItem) buildKustomize() error { return nil } -func (di *DeploymentItem) postprocessAndLoadObjects(k *k8s.K8sCluster, images *Images) error { +func (di *DeploymentItem) loadObjects() error { if di.dir == nil { return nil } @@ -382,6 +383,49 @@ func (di *DeploymentItem) postprocessAndLoadObjects(k *k8s.K8sCluster, images *I } di.Objects = append(di.Objects, uo.FromMap(m)) } + return nil +} + +var crdGV = schema.GroupKind{Group: "apiextensions.k8s.io", Kind: "CustomResourceDefinition"} + +// postprocessCRDs will set namespace overrides into the K8sCluster so that future IsNamespaced return the correct +// value even if the CRD is not deployed yet. +func (di *DeploymentItem) postprocessCRDs(k *k8s.K8sCluster) error { + if di.dir == nil { + return nil + } + + for _, o := range di.Objects { + gvk := o.GetK8sGVK() + if gvk.GroupKind() != crdGV { + continue + } + + scope, _, err := o.GetNestedString("spec", "scope") + if err != nil { + return err + } + namespaced := strings.ToLower(scope) == "namespaced" + group, _, err := o.GetNestedString("spec", "group") + if err != nil { + return err + } + kind, _, err := o.GetNestedString("spec", "names", "kind") + if err != nil { + return err + } + + k.SetNamespaced(schema.GroupKind{Group: group, Kind: kind}, namespaced) + } + return nil +} + +func (di *DeploymentItem) postprocessObjects(k *k8s.K8sCluster, images *Images) error { + if di.dir == nil { + return nil + } + + var objects []interface{} var errList []error for _, o := range di.Objects { @@ -398,12 +442,14 @@ func (di *DeploymentItem) postprocessAndLoadObjects(k *k8s.K8sCluster, images *I o.SetK8sAnnotations(uo.CopyMergeStrMap(o.GetK8sAnnotations(), commonAnnotations)) // Resolve image placeholders - err = images.ResolvePlaceholders(k, o, di.relRenderedDir, di.Tags.ListKeys()) + err := images.ResolvePlaceholders(k, o, di.relRenderedDir, di.Tags.ListKeys()) if err != nil { errList = append(errList, err) } return nil }) + + objects = append(objects, o.Object) } if len(errList) != 0 { @@ -411,7 +457,7 @@ func (di *DeploymentItem) postprocessAndLoadObjects(k *k8s.K8sCluster, images *I } // Need to write it back to disk in case it is needed externally - err = yaml.WriteYamlAllFile(di.renderedYamlPath, objects) + err := yaml.WriteYamlAllFile(di.renderedYamlPath, objects) if err != nil { return err } diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index b0b40d311..f7a6a8596 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -48,9 +48,10 @@ type K8sCluster struct { ServerVersion *goversion.Version - allResources map[schema.GroupVersionKind]*v1.APIResource - preferredResources map[schema.GroupKind]*v1.APIResource - crdCache map[k8s.ObjectRef]struct { + allResources map[schema.GroupVersionKind]*v1.APIResource + preferredResources map[schema.GroupKind]*v1.APIResource + namespacedResources map[schema.GroupKind]bool + crdCache map[k8s.ObjectRef]struct { crd *uo.UnstructuredObject err error } @@ -84,8 +85,9 @@ const parallelClients = 16 func NewK8sCluster(ctx context.Context, configIn *rest.Config, dryRun bool) (*K8sCluster, error) { k := &K8sCluster{ - ctx: ctx, - DryRun: dryRun, + ctx: ctx, + DryRun: dryRun, + namespacedResources: map[schema.GroupKind]bool{}, } k.restConfig = rest.CopyConfig(configIn) @@ -284,19 +286,27 @@ func (k *K8sCluster) RediscoverResources() error { return k.updateResources(false) } -func (k *K8sCluster) IsNamespaced(gvk schema.GroupVersionKind) bool { +func (k *K8sCluster) SetNamespaced(gv schema.GroupKind, namespaced bool) { k.mutex.Lock() defer k.mutex.Unlock() - r, ok := k.allResources[gvk] + k.namespacedResources[gv] = namespaced +} + +func (k *K8sCluster) IsNamespaced(gv schema.GroupKind) bool { + k.mutex.Lock() + defer k.mutex.Unlock() + + r, ok := k.preferredResources[gv] if !ok { - return false + n, _ := k.namespacedResources[gv] + return n } return r.Namespaced } func (k *K8sCluster) FixNamespace(o *uo.UnstructuredObject, def string) { ref := o.GetK8sRef() - namespaced := k.IsNamespaced(ref.GVK) + namespaced := k.IsNamespaced(ref.GVK.GroupKind()) if !namespaced && ref.Namespace != "" { o.SetK8sNamespace("") } else if namespaced && ref.Namespace == "" { @@ -305,7 +315,7 @@ func (k *K8sCluster) FixNamespace(o *uo.UnstructuredObject, def string) { } func (k *K8sCluster) FixNamespaceInRef(ref k8s.ObjectRef) k8s.ObjectRef { - namespaced := k.IsNamespaced(ref.GVK) + namespaced := k.IsNamespaced(ref.GVK.GroupKind()) if !namespaced && ref.Namespace != "" { ref.Namespace = "" } else if namespaced && ref.Namespace == "" { From 5051001787d076b624c202ea4db2f71420ba0f9f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 10 May 2022 14:00:31 +0200 Subject: [PATCH 0793/2916] fix: Redirect log output to status handler --- cmd/kluctl/commands/root.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index dad3ae8bf..56f91a2d6 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -28,6 +28,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" "k8s.io/klog/v2" + "log" "net/http" "os" "path/filepath" @@ -83,6 +84,7 @@ func setupStatusHandler() { klog.LogToStderr(false) klog.SetOutput(status.NewLineRedirector(sh.Info)) + log.SetOutput(status.NewLineRedirector(sh.Info)) } type VersionCheckState struct { From e84c83f7024636cd1dc6c1d0ad993c3f33361589 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 10 May 2022 14:00:47 +0200 Subject: [PATCH 0794/2916] fix: Fix crash when diffing empty slices --- pkg/diff/diff.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pkg/diff/diff.go b/pkg/diff/diff.go index 99eb13188..3746c3c5f 100644 --- a/pkg/diff/diff.go +++ b/pkg/diff/diff.go @@ -173,14 +173,18 @@ func objectToDiffableString(o interface{}, showType bool) (string, error) { return "", err } if showType { - s += fmt.Sprintf(" (type: %s)", reflect.TypeOf(o).Name()) + t := "" + if o != nil { + t = reflect.TypeOf(o).Name() + } + s += fmt.Sprintf(" (type: %s)", t) } return s, nil } func objectToDiffableStringNoType(o interface{}) (string, error) { if o == nil { - return "null", nil + return "", nil } if o == notPresent { return "", nil From 6a29d878aa0da3094cff1fa4857ed7ba5d98ae60 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 10 May 2022 15:07:55 +0200 Subject: [PATCH 0795/2916] refactor: Move out resources related code into own class --- pkg/deployment/deployment_item.go | 4 +- pkg/deployment/helm_chart.go | 4 +- pkg/deployment/utils/apply_utils.go | 4 +- pkg/deployment/utils/delete_utils.go | 4 +- pkg/k8s/k8s_cluster.go | 358 ++------------------------ pkg/k8s/resources.go | 370 +++++++++++++++++++++++++++ pkg/validation/validation.go | 2 +- 7 files changed, 399 insertions(+), 347 deletions(-) create mode 100644 pkg/k8s/resources.go diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index c3f41e749..52c563251 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -415,7 +415,7 @@ func (di *DeploymentItem) postprocessCRDs(k *k8s.K8sCluster) error { return err } - k.SetNamespaced(schema.GroupKind{Group: group, Kind: kind}, namespaced) + k.Resources.SetNamespaced(schema.GroupKind{Group: group, Kind: kind}, namespaced) } return nil } @@ -434,7 +434,7 @@ func (di *DeploymentItem) postprocessObjects(k *k8s.K8sCluster, images *Images) _ = k8s.UnwrapListItems(o, true, func(o *uo.UnstructuredObject) error { if k != nil { - k.FixNamespace(o, "default") + k.Resources.FixNamespace(o, "default") } // Set common labels/annotations diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index eb97d66b1..84aac2481 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -203,7 +203,7 @@ func (c *helmChart) doRender(ctx context.Context, k *k8s.K8sCluster) error { var gvs []string if k != nil { - gvs, err = k.GetAllGroupVersions() + gvs, err = k.Resources.GetAllGroupVersions() if err != nil { return err } @@ -313,7 +313,7 @@ func (c *helmChart) doRender(ctx context.Context, k *k8s.K8sCluster) error { // add the necessary namespace in the rendered resources if k != nil { err = k8s.UnwrapListItems(o, true, func(o *uo.UnstructuredObject) error { - k.FixNamespace(o, namespace) + k.Resources.FixNamespace(o, namespace) return nil }) if err != nil { diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index e72211935..af13ea6d9 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -337,7 +337,7 @@ func (a *ApplyUtil) handleNewCRDs(x *uo.UnstructuredObject, err error) (bool, er // retry the patch if a.deployedNewCRD.Load().(bool) { a.deployedNewCRD.Store(false) - err = a.k.RediscoverResources() + err = a.k.Resources.RediscoverResources() if err != nil { return false, err } @@ -435,7 +435,7 @@ func (a *ApplyUtil) WaitReadiness(ref k8s2.ObjectRef, timeout time.Duration) boo func (a *ApplyUtil) applyDeploymentItem(d *deployment.DeploymentItem) { var toDelete []k8s2.ObjectRef for _, x := range d.Config.DeleteObjects { - for _, gvk := range a.k.GetGVKs(x.Group, x.Version, x.Kind) { + for _, gvk := range a.k.Resources.GetGVKs(x.Group, x.Version, x.Kind) { ref := k8s2.ObjectRef{ GVK: gvk, Name: x.Name, diff --git a/pkg/deployment/utils/delete_utils.go b/pkg/deployment/utils/delete_utils.go index 655f4a1ca..803b050f1 100644 --- a/pkg/deployment/utils/delete_utils.go +++ b/pkg/deployment/utils/delete_utils.go @@ -41,14 +41,14 @@ var deleteOrder = [][]string{ } func objectRefForExclusion(k *k8s.K8sCluster, ref k8s2.ObjectRef) k8s2.ObjectRef { - ref = k.FixNamespaceInRef(ref) + ref = k.Resources.FixNamespaceInRef(ref) ref.GVK.Version = "" return ref } func filterObjectsForDelete(k *k8s.K8sCluster, objects []*uo.UnstructuredObject, apiFilter []string, inclusionHasTags bool, excludedObjects map[k8s2.ObjectRef]bool) ([]*uo.UnstructuredObject, error) { filteredResources := make(map[schema.GroupKind]bool) - for _, gk := range k.GetFilteredGKs(apiFilter) { + for _, gk := range k.Resources.GetFilteredGKs(apiFilter) { filteredResources[gk] = true } diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index f7a6a8596..06b4d6615 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -16,17 +16,12 @@ import ( v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/discovery" - "k8s.io/client-go/discovery/cached/disk" "k8s.io/client-go/dynamic" corev1 "k8s.io/client-go/kubernetes/typed/core/v1" "k8s.io/client-go/metadata" _ "k8s.io/client-go/plugin/pkg/client/auth" "k8s.io/client-go/rest" "net/http" - "net/url" - "path/filepath" - "strings" "sync" "time" ) @@ -43,19 +38,11 @@ type K8sCluster struct { DryRun bool restConfig *rest.Config - discovery *disk.CachedDiscoveryClient clientPool chan *parallelClientEntry ServerVersion *goversion.Version - allResources map[schema.GroupVersionKind]*v1.APIResource - preferredResources map[schema.GroupKind]*v1.APIResource - namespacedResources map[schema.GroupKind]bool - crdCache map[k8s.ObjectRef]struct { - crd *uo.UnstructuredObject - err error - } - mutex sync.Mutex + Resources *k8sResources } type parallelClientEntry struct { @@ -84,33 +71,28 @@ func (p *parallelClientEntry) HandleWarningHeader(code int, agent string, text s const parallelClients = 16 func NewK8sCluster(ctx context.Context, configIn *rest.Config, dryRun bool) (*K8sCluster, error) { - k := &K8sCluster{ - ctx: ctx, - DryRun: dryRun, - namespacedResources: map[schema.GroupKind]bool{}, - } - - k.restConfig = rest.CopyConfig(configIn) - k.restConfig.QPS = 10 - k.restConfig.Burst = 20 + restConfig := rest.CopyConfig(configIn) + restConfig.QPS = 10 + restConfig.Burst = 20 - apiHost, err := url.Parse(k.restConfig.Host) + resources, err := newK8sResources(ctx, restConfig) if err != nil { return nil, err } - discoveryCacheDir := filepath.Join(utils.GetTmpBaseDir(), "kube-cache/discovery", apiHost.Hostname()) - discovery2, err := disk.NewCachedDiscoveryClientForConfig(dynamic.ConfigFor(k.restConfig), discoveryCacheDir, "", time.Hour*24) - if err != nil { - return nil, err + + k := &K8sCluster{ + ctx: ctx, + DryRun: dryRun, + restConfig: restConfig, + Resources: resources, } - k.discovery = discovery2 err = k.initClientPool() if err != nil { return nil, err } - v, err := k.discovery.ServerVersion() + v, err := k.Resources.discovery.ServerVersion() if err != nil { return nil, err } @@ -120,7 +102,7 @@ func NewK8sCluster(ctx context.Context, configIn *rest.Config, dryRun bool) (*K8 } k.ServerVersion = v2 - err = k.updateResources(true) + err = k.Resources.updateResources(true) if err != nil { return nil, err } @@ -179,299 +161,6 @@ func (k *K8sCluster) GetCA() []byte { return k.restConfig.CAData } -func (k *K8sCluster) updateResources(doLock bool) error { - if doLock { - k.mutex.Lock() - defer k.mutex.Unlock() - } - - k.allResources = map[schema.GroupVersionKind]*v1.APIResource{} - k.preferredResources = map[schema.GroupKind]*v1.APIResource{} - k.crdCache = map[k8s.ObjectRef]struct { - crd *uo.UnstructuredObject - err error - }{} - - // the discovery client doesn't support cancellation, so we need to run it in the background and wait for it - var arls []*v1.APIResourceList - var preferredArls []*v1.APIResourceList - finished := make(chan error) - go func() { - var err error - _, arls, err = k.discovery.ServerGroupsAndResources() - if err != nil && !discovery.IsGroupDiscoveryFailedError(err) { - finished <- err - return - } - preferredArls, err = k.discovery.ServerPreferredResources() - if err != nil && !discovery.IsGroupDiscoveryFailedError(err) { - finished <- err - return - } - finished <- nil - }() - - select { - case err := <-finished: - if err != nil { - return err - } - case <-k.ctx.Done(): - return fmt.Errorf("failed listing api resources: %w", k.ctx.Err()) - } - - for _, arl := range arls { - for _, ar := range arl.APIResources { - if strings.Index(ar.Name, "/") != -1 { - // skip subresources - continue - } - gv, err := schema.ParseGroupVersion(arl.GroupVersion) - if err != nil { - continue - } - - ar := ar - ar.Group = gv.Group - ar.Version = gv.Version - - gvk := schema.GroupVersionKind{ - Group: ar.Group, - Version: ar.Version, - Kind: ar.Kind, - } - if _, ok := deprecatedResources[gvk.GroupKind()]; ok { - continue - } - if _, ok := k.allResources[gvk]; ok { - ok = false - } - k.allResources[gvk] = &ar - } - } - - for _, arl := range preferredArls { - for _, ar := range arl.APIResources { - if strings.Index(ar.Name, "/") != -1 { - // skip subresources - continue - } - gv, err := schema.ParseGroupVersion(arl.GroupVersion) - if err != nil { - continue - } - - ar := ar - ar.Group = gv.Group - ar.Version = gv.Version - - gk := schema.GroupKind{ - Group: ar.Group, - Kind: ar.Kind, - } - if _, ok := deprecatedResources[gk]; ok { - continue - } - k.preferredResources[gk] = &ar - } - } - return nil -} - -func (k *K8sCluster) RediscoverResources() error { - k.mutex.Lock() - defer k.mutex.Unlock() - - k.discovery.Invalidate() - return k.updateResources(false) -} - -func (k *K8sCluster) SetNamespaced(gv schema.GroupKind, namespaced bool) { - k.mutex.Lock() - defer k.mutex.Unlock() - k.namespacedResources[gv] = namespaced -} - -func (k *K8sCluster) IsNamespaced(gv schema.GroupKind) bool { - k.mutex.Lock() - defer k.mutex.Unlock() - - r, ok := k.preferredResources[gv] - if !ok { - n, _ := k.namespacedResources[gv] - return n - } - return r.Namespaced -} - -func (k *K8sCluster) FixNamespace(o *uo.UnstructuredObject, def string) { - ref := o.GetK8sRef() - namespaced := k.IsNamespaced(ref.GVK.GroupKind()) - if !namespaced && ref.Namespace != "" { - o.SetK8sNamespace("") - } else if namespaced && ref.Namespace == "" { - o.SetK8sNamespace(def) - } -} - -func (k *K8sCluster) FixNamespaceInRef(ref k8s.ObjectRef) k8s.ObjectRef { - namespaced := k.IsNamespaced(ref.GVK.GroupKind()) - if !namespaced && ref.Namespace != "" { - ref.Namespace = "" - } else if namespaced && ref.Namespace == "" { - ref.Namespace = "default" - } - return ref -} - -func (k *K8sCluster) GetAllGroupVersions() ([]string, error) { - k.mutex.Lock() - defer k.mutex.Unlock() - - m := make(map[string]bool) - var l []string - - for gvk, _ := range k.allResources { - gv := gvk.GroupVersion().String() - if _, ok := m[gv]; !ok { - m[gv] = true - l = append(l, gv) - } - } - return l, nil -} - -func (k *K8sCluster) GetFilteredGKs(filters []string) []schema.GroupKind { - k.mutex.Lock() - defer k.mutex.Unlock() - - m := make(map[schema.GroupKind]bool) - var l []schema.GroupKind - for gk, ar := range k.preferredResources { - found := len(filters) == 0 - for _, f := range filters { - if ar.Name == f || ar.Group == f || ar.Kind == f { - found = true - break - } - } - if found { - if _, ok := m[gk]; !ok { - m[gk] = true - l = append(l, gk) - } - } - } - return l -} - -func (k *K8sCluster) GetGVKs(group *string, version *string, kind *string) []schema.GroupVersionKind { - k.mutex.Lock() - defer k.mutex.Unlock() - - var ret []schema.GroupVersionKind - for gvk := range k.allResources { - if group != nil && *group != gvk.Group { - continue - } - if version != nil && *version != gvk.Version { - continue - } - if kind != nil && *kind != gvk.Kind { - continue - } - ret = append(ret, gvk) - } - return ret -} - -func (k *K8sCluster) getGVRForGVK(gvk schema.GroupVersionKind) (*schema.GroupVersionResource, bool, error) { - k.mutex.Lock() - defer k.mutex.Unlock() - - ar, ok := k.allResources[gvk] - if !ok { - return nil, false, &meta.NoKindMatchError{ - GroupKind: gvk.GroupKind(), - SearchedVersions: []string{gvk.Version}, - } - } - - return &schema.GroupVersionResource{ - Group: ar.Group, - Version: ar.Version, - Resource: ar.Name, - }, ar.Namespaced, nil -} - -func (k *K8sCluster) GetApiResourceForGVK(gvk schema.GroupVersionKind) *v1.APIResource { - k.mutex.Lock() - defer k.mutex.Unlock() - - ar, _ := k.allResources[gvk] - return ar -} - -func (k *K8sCluster) GetCRDForGVK(gvk schema.GroupVersionKind) (*uo.UnstructuredObject, error) { - ar := k.GetApiResourceForGVK(gvk) - if ar == nil { - return nil, fmt.Errorf("APIResource for %s not found", gvk.String()) - } - - crdRef := k8s.ObjectRef{ - GVK: schema.GroupVersionKind{Group: "apiextensions.k8s.io", Version: "v1", Kind: "CustomResourceDefinition"}, - Name: fmt.Sprintf("%s.%s", ar.Name, ar.Group), - } - - k.mutex.Lock() - x, ok := k.crdCache[crdRef] - k.mutex.Unlock() - if ok { - return x.crd, x.err - } - - crd, _, err := k.GetSingleObject(crdRef) - - k.mutex.Lock() - x.crd = crd - x.err = err - k.crdCache[crdRef] = x - k.mutex.Unlock() - - return crd, err -} - -func (k *K8sCluster) GetSchemaForGVK(gvk schema.GroupVersionKind) (*uo.UnstructuredObject, error) { - crd, err := k.GetCRDForGVK(gvk) - if err != nil { - return nil, err - } - - versions, ok, err := crd.GetNestedObjectList("spec", "versions") - if err != nil { - return nil, err - } - if !ok { - return nil, fmt.Errorf("versions not found in CRD") - } - - for _, version := range versions { - name, _, _ := version.GetNestedString("name") - if name != gvk.Version { - continue - } - s, ok, err := version.GetNestedObject("schema", "openAPIV3Schema") - if err != nil { - return nil, err - } - if !ok { - return nil, fmt.Errorf("version %s has no schema", name) - } - return s, nil - } - return nil, fmt.Errorf("schema for %s not found", gvk.String()) -} - func (k *K8sCluster) withClientFromPool(cb func(p *parallelClientEntry) error) ([]ApiWarning, error) { select { case p := <-k.clientPool: @@ -486,7 +175,7 @@ func (k *K8sCluster) withClientFromPool(cb func(p *parallelClientEntry) error) ( func (k *K8sCluster) withDynamicClientForGVK(gvk schema.GroupVersionKind, namespace string, cb func(r dynamic.ResourceInterface) error) ([]ApiWarning, error) { return k.withClientFromPool(func(p *parallelClientEntry) error { - gvr, namespaced, err := k.getGVRForGVK(gvk) + gvr, namespaced, err := k.Resources.getGVRForGVK(gvk) if err != nil { return err } @@ -501,7 +190,7 @@ func (k *K8sCluster) withDynamicClientForGVK(gvk schema.GroupVersionKind, namesp func (k *K8sCluster) withMetadataClientForGVK(gvk schema.GroupVersionKind, namespace string, cb func(r metadata.ResourceInterface) error) ([]ApiWarning, error) { return k.withClientFromPool(func(p *parallelClientEntry) error { - gvr, namespaced, err := k.getGVRForGVK(gvk) + gvr, namespaced, err := k.Resources.getGVRForGVK(gvk) if err != nil { return err } @@ -581,8 +270,7 @@ func (k *K8sCluster) ListAllObjects(verbs []string, namespace string, labels map retApiWarnings := make(map[schema.GroupVersionKind][]ApiWarning) var mutex sync.Mutex - k.mutex.Lock() - for _, ar := range k.preferredResources { + filter := func(ar *v1.APIResource) bool { foundVerb := false for _, v := range verbs { if utils.FindStrInSlice(ar.Verbs, v) != -1 { @@ -590,14 +278,10 @@ func (k *K8sCluster) ListAllObjects(verbs []string, namespace string, labels map break } } - if !foundVerb { - continue - } - gvk := schema.GroupVersionKind{ - Group: ar.Group, - Version: ar.Version, - Kind: ar.Kind, - } + return foundVerb + } + + for _, gvk := range k.Resources.GetFilteredPreferredGVKs(filter) { wp.Submit(func() error { var l []*uo.UnstructuredObject var apiWarnings []ApiWarning @@ -619,8 +303,6 @@ func (k *K8sCluster) ListAllObjects(verbs []string, namespace string, labels map return nil }) } - // release it early and let the goroutines finish (deadlocking otherwise) - k.mutex.Unlock() err := wp.StopWait(false) if err != nil { diff --git a/pkg/k8s/resources.go b/pkg/k8s/resources.go new file mode 100644 index 000000000..d671e2048 --- /dev/null +++ b/pkg/k8s/resources.go @@ -0,0 +1,370 @@ +package k8s + +import ( + "context" + "fmt" + "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "k8s.io/apimachinery/pkg/api/meta" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/discovery" + "k8s.io/client-go/discovery/cached/disk" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/rest" + "net/url" + "path/filepath" + "strings" + "sync" + "time" +) + +type crdCacheEntry struct { + crd *uo.UnstructuredObject + err error +} + +type k8sResources struct { + ctx context.Context + discovery *disk.CachedDiscoveryClient + + allResources map[schema.GroupVersionKind]*v1.APIResource + preferredResources map[schema.GroupKind]*v1.APIResource + namespacedResources map[schema.GroupKind]bool + crdCache map[k8s.ObjectRef]crdCacheEntry + mutex sync.Mutex +} + +func newK8sResources(ctx context.Context, config *rest.Config) (*k8sResources, error) { + k := &k8sResources{ + ctx: ctx, + allResources: map[schema.GroupVersionKind]*v1.APIResource{}, + preferredResources: map[schema.GroupKind]*v1.APIResource{}, + namespacedResources: map[schema.GroupKind]bool{}, + crdCache: map[k8s.ObjectRef]crdCacheEntry{}, + mutex: sync.Mutex{}, + } + + apiHost, err := url.Parse(config.Host) + if err != nil { + return nil, err + } + discoveryCacheDir := filepath.Join(utils.GetTmpBaseDir(), "kube-cache/discovery", apiHost.Hostname()) + discovery2, err := disk.NewCachedDiscoveryClientForConfig(dynamic.ConfigFor(config), discoveryCacheDir, "", time.Hour*24) + if err != nil { + return nil, err + } + k.discovery = discovery2 + + return k, nil +} + +func (k *k8sResources) updateResources(doLock bool) error { + if doLock { + k.mutex.Lock() + defer k.mutex.Unlock() + } + + k.allResources = map[schema.GroupVersionKind]*v1.APIResource{} + k.preferredResources = map[schema.GroupKind]*v1.APIResource{} + k.crdCache = map[k8s.ObjectRef]crdCacheEntry{} + + // the discovery client doesn't support cancellation, so we need to run it in the background and wait for it + var arls []*v1.APIResourceList + var preferredArls []*v1.APIResourceList + finished := make(chan error) + go func() { + var err error + _, arls, err = k.discovery.ServerGroupsAndResources() + if err != nil && !discovery.IsGroupDiscoveryFailedError(err) { + finished <- err + return + } + preferredArls, err = k.discovery.ServerPreferredResources() + if err != nil && !discovery.IsGroupDiscoveryFailedError(err) { + finished <- err + return + } + finished <- nil + }() + + select { + case err := <-finished: + if err != nil { + return err + } + case <-k.ctx.Done(): + return fmt.Errorf("failed listing api resources: %w", k.ctx.Err()) + } + + for _, arl := range arls { + for _, ar := range arl.APIResources { + if strings.Index(ar.Name, "/") != -1 { + // skip subresources + continue + } + gv, err := schema.ParseGroupVersion(arl.GroupVersion) + if err != nil { + continue + } + + ar := ar + ar.Group = gv.Group + ar.Version = gv.Version + + gvk := schema.GroupVersionKind{ + Group: ar.Group, + Version: ar.Version, + Kind: ar.Kind, + } + if _, ok := deprecatedResources[gvk.GroupKind()]; ok { + continue + } + if _, ok := k.allResources[gvk]; ok { + ok = false + } + k.allResources[gvk] = &ar + } + } + + for _, arl := range preferredArls { + for _, ar := range arl.APIResources { + if strings.Index(ar.Name, "/") != -1 { + // skip subresources + continue + } + gv, err := schema.ParseGroupVersion(arl.GroupVersion) + if err != nil { + continue + } + + ar := ar + ar.Group = gv.Group + ar.Version = gv.Version + + gk := schema.GroupKind{ + Group: ar.Group, + Kind: ar.Kind, + } + if _, ok := deprecatedResources[gk]; ok { + continue + } + k.preferredResources[gk] = &ar + } + } + return nil +} + +func (k *k8sResources) RediscoverResources() error { + k.mutex.Lock() + defer k.mutex.Unlock() + + k.discovery.Invalidate() + return k.updateResources(false) +} + +func (k *k8sResources) SetNamespaced(gv schema.GroupKind, namespaced bool) { + k.mutex.Lock() + defer k.mutex.Unlock() + k.namespacedResources[gv] = namespaced +} + +func (k *k8sResources) IsNamespaced(gv schema.GroupKind) bool { + k.mutex.Lock() + defer k.mutex.Unlock() + + r, ok := k.preferredResources[gv] + if !ok { + n, _ := k.namespacedResources[gv] + return n + } + return r.Namespaced +} + +func (k *k8sResources) FixNamespace(o *uo.UnstructuredObject, def string) { + ref := o.GetK8sRef() + namespaced := k.IsNamespaced(ref.GVK.GroupKind()) + if !namespaced && ref.Namespace != "" { + o.SetK8sNamespace("") + } else if namespaced && ref.Namespace == "" { + o.SetK8sNamespace(def) + } +} + +func (k *k8sResources) FixNamespaceInRef(ref k8s.ObjectRef) k8s.ObjectRef { + namespaced := k.IsNamespaced(ref.GVK.GroupKind()) + if !namespaced && ref.Namespace != "" { + ref.Namespace = "" + } else if namespaced && ref.Namespace == "" { + ref.Namespace = "default" + } + return ref +} + +func (k *k8sResources) GetAllGroupVersions() ([]string, error) { + k.mutex.Lock() + defer k.mutex.Unlock() + + m := make(map[string]bool) + var l []string + + for gvk, _ := range k.allResources { + gv := gvk.GroupVersion().String() + if _, ok := m[gv]; !ok { + m[gv] = true + l = append(l, gv) + } + } + return l, nil +} + +func (k *k8sResources) GetFilteredPreferredGVKs(filter func(ar *v1.APIResource) bool) []schema.GroupVersionKind { + k.mutex.Lock() + defer k.mutex.Unlock() + + var ret []schema.GroupVersionKind + for _, ar := range k.preferredResources { + if !filter(ar) { + continue + } + gvk := schema.GroupVersionKind{ + Group: ar.Group, + Version: ar.Version, + Kind: ar.Kind, + } + ret = append(ret, gvk) + } + return ret +} + +func (k *k8sResources) GetFilteredGKs(filters []string) []schema.GroupKind { + k.mutex.Lock() + defer k.mutex.Unlock() + + m := make(map[schema.GroupKind]bool) + var l []schema.GroupKind + for gk, ar := range k.preferredResources { + found := len(filters) == 0 + for _, f := range filters { + if ar.Name == f || ar.Group == f || ar.Kind == f { + found = true + break + } + } + if found { + if _, ok := m[gk]; !ok { + m[gk] = true + l = append(l, gk) + } + } + } + return l +} + +func (k *k8sResources) GetGVKs(group *string, version *string, kind *string) []schema.GroupVersionKind { + k.mutex.Lock() + defer k.mutex.Unlock() + + var ret []schema.GroupVersionKind + for gvk := range k.allResources { + if group != nil && *group != gvk.Group { + continue + } + if version != nil && *version != gvk.Version { + continue + } + if kind != nil && *kind != gvk.Kind { + continue + } + ret = append(ret, gvk) + } + return ret +} + +func (k *k8sResources) getGVRForGVK(gvk schema.GroupVersionKind) (*schema.GroupVersionResource, bool, error) { + k.mutex.Lock() + defer k.mutex.Unlock() + + ar, ok := k.allResources[gvk] + if !ok { + return nil, false, &meta.NoKindMatchError{ + GroupKind: gvk.GroupKind(), + SearchedVersions: []string{gvk.Version}, + } + } + + return &schema.GroupVersionResource{ + Group: ar.Group, + Version: ar.Version, + Resource: ar.Name, + }, ar.Namespaced, nil +} + +func (k *k8sResources) GetApiResourceForGVK(gvk schema.GroupVersionKind) *v1.APIResource { + k.mutex.Lock() + defer k.mutex.Unlock() + + ar, _ := k.allResources[gvk] + return ar +} + +func (k *k8sResources) GetCRDForGVK(k2 *K8sCluster, gvk schema.GroupVersionKind) (*uo.UnstructuredObject, error) { + ar := k.GetApiResourceForGVK(gvk) + if ar == nil { + return nil, fmt.Errorf("APIResource for %s not found", gvk.String()) + } + + crdRef := k8s.ObjectRef{ + GVK: schema.GroupVersionKind{Group: "apiextensions.k8s.io", Version: "v1", Kind: "CustomResourceDefinition"}, + Name: fmt.Sprintf("%s.%s", ar.Name, ar.Group), + } + + k.mutex.Lock() + x, ok := k.crdCache[crdRef] + k.mutex.Unlock() + if ok { + return x.crd, x.err + } + + crd, _, err := k2.GetSingleObject(crdRef) + + k.mutex.Lock() + x.crd = crd + x.err = err + k.crdCache[crdRef] = x + k.mutex.Unlock() + + return crd, err +} + +func (k *k8sResources) GetSchemaForGVK(k2 *K8sCluster, gvk schema.GroupVersionKind) (*uo.UnstructuredObject, error) { + crd, err := k.GetCRDForGVK(k2, gvk) + if err != nil { + return nil, err + } + + versions, ok, err := crd.GetNestedObjectList("spec", "versions") + if err != nil { + return nil, err + } + if !ok { + return nil, fmt.Errorf("versions not found in CRD") + } + + for _, version := range versions { + name, _, _ := version.GetNestedString("name") + if name != gvk.Version { + continue + } + s, ok, err := version.GetNestedObject("schema", "openAPIV3Schema") + if err != nil { + return nil, err + } + if !ok { + return nil, fmt.Errorf("version %s has no schema", name) + } + return s, nil + } + return nil, fmt.Errorf("schema for %s not found", gvk.String()) +} diff --git a/pkg/validation/validation.go b/pkg/validation/validation.go index 52584cedf..0331074bc 100644 --- a/pkg/validation/validation.go +++ b/pkg/validation/validation.go @@ -117,7 +117,7 @@ func ValidateObject(k *k8s.K8sCluster, o *uo.UnstructuredObject, notReadyIsError // can't really say anything... return } - s, err := k.GetSchemaForGVK(ref.GVK) + s, err := k.Resources.GetSchemaForGVK(k, ref.GVK) if err != nil && !errors.IsNotFound(err) { addError(err.Error()) return From 735e2f99d6d0cff8debf44af2e6e28c943acce0e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 10 May 2022 15:10:30 +0200 Subject: [PATCH 0796/2916] refactor: Return GroupVersion from GetAllGroupVersions --- pkg/deployment/helm_chart.go | 5 +++-- pkg/k8s/resources.go | 8 ++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index 84aac2481..2e2727fb8 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -22,6 +22,7 @@ import ( "helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/repo" "io/ioutil" + "k8s.io/apimachinery/pkg/runtime/schema" "os" "path/filepath" "regexp" @@ -201,7 +202,7 @@ func (c *helmChart) doRender(ctx context.Context, k *k8s.K8sCluster) error { } valuesPath := yaml.FixPathExt(filepath.Join(filepath.Dir(c.configFile), "helm-values.yml")) - var gvs []string + var gvs []schema.GroupVersion if k != nil { gvs, err = k.Resources.GetAllGroupVersions() if err != nil { @@ -245,7 +246,7 @@ func (c *helmChart) doRender(ctx context.Context, k *k8s.K8sCluster) error { client.IncludeCRDs = true } for _, gv := range gvs { - client.APIVersions = append(client.APIVersions, gv) + client.APIVersions = append(client.APIVersions, gv.String()) } p := getter.All(settings) diff --git a/pkg/k8s/resources.go b/pkg/k8s/resources.go index d671e2048..69e9e33ff 100644 --- a/pkg/k8s/resources.go +++ b/pkg/k8s/resources.go @@ -202,15 +202,15 @@ func (k *k8sResources) FixNamespaceInRef(ref k8s.ObjectRef) k8s.ObjectRef { return ref } -func (k *k8sResources) GetAllGroupVersions() ([]string, error) { +func (k *k8sResources) GetAllGroupVersions() ([]schema.GroupVersion, error) { k.mutex.Lock() defer k.mutex.Unlock() - m := make(map[string]bool) - var l []string + m := make(map[schema.GroupVersion]bool) + var l []schema.GroupVersion for gvk, _ := range k.allResources { - gv := gvk.GroupVersion().String() + gv := gvk.GroupVersion() if _, ok := m[gv]; !ok { m[gv] = true l = append(l, gv) From 0cde1a7bf521e06f558d3e7ed0d8a0b9ca065d62 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 10 May 2022 15:13:48 +0200 Subject: [PATCH 0797/2916] refactor: Get rid of GetFilteredGKs and use GetFilteredPreferredGVKs instead --- pkg/deployment/utils/delete_utils.go | 17 +++++++++++++++-- pkg/k8s/resources.go | 24 ------------------------ 2 files changed, 15 insertions(+), 26 deletions(-) diff --git a/pkg/deployment/utils/delete_utils.go b/pkg/deployment/utils/delete_utils.go index 803b050f1..b79f93d5f 100644 --- a/pkg/deployment/utils/delete_utils.go +++ b/pkg/deployment/utils/delete_utils.go @@ -7,6 +7,7 @@ import ( k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "golang.org/x/sync/semaphore" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "strconv" "sync" @@ -47,9 +48,21 @@ func objectRefForExclusion(k *k8s.K8sCluster, ref k8s2.ObjectRef) k8s2.ObjectRef } func filterObjectsForDelete(k *k8s.K8sCluster, objects []*uo.UnstructuredObject, apiFilter []string, inclusionHasTags bool, excludedObjects map[k8s2.ObjectRef]bool) ([]*uo.UnstructuredObject, error) { + filterFunc := func(ar *v1.APIResource) bool { + if len(apiFilter) == 0 { + return true + } + for _, f := range apiFilter { + if ar.Name == f || ar.Group == f || ar.Kind == f { + return true + } + } + return false + } + filteredResources := make(map[schema.GroupKind]bool) - for _, gk := range k.Resources.GetFilteredGKs(apiFilter) { - filteredResources[gk] = true + for _, gvk := range k.Resources.GetFilteredPreferredGVKs(filterFunc) { + filteredResources[gvk.GroupKind()] = true } var ret []*uo.UnstructuredObject diff --git a/pkg/k8s/resources.go b/pkg/k8s/resources.go index 69e9e33ff..39f537f48 100644 --- a/pkg/k8s/resources.go +++ b/pkg/k8s/resources.go @@ -238,30 +238,6 @@ func (k *k8sResources) GetFilteredPreferredGVKs(filter func(ar *v1.APIResource) return ret } -func (k *k8sResources) GetFilteredGKs(filters []string) []schema.GroupKind { - k.mutex.Lock() - defer k.mutex.Unlock() - - m := make(map[schema.GroupKind]bool) - var l []schema.GroupKind - for gk, ar := range k.preferredResources { - found := len(filters) == 0 - for _, f := range filters { - if ar.Name == f || ar.Group == f || ar.Kind == f { - found = true - break - } - } - if found { - if _, ok := m[gk]; !ok { - m[gk] = true - l = append(l, gk) - } - } - } - return l -} - func (k *k8sResources) GetGVKs(group *string, version *string, kind *string) []schema.GroupVersionKind { k.mutex.Lock() defer k.mutex.Unlock() From 3c096381c7996702a9e0f950d8c42e86f721d4cd Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 10 May 2022 15:18:55 +0200 Subject: [PATCH 0798/2916] refactor: Get rid of GetGVKs and use GetFilteredGVKs instead --- pkg/deployment/utils/apply_utils.go | 2 +- pkg/k8s/resources.go | 34 ++++++++++++++++++++--------- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index af13ea6d9..12274398d 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -435,7 +435,7 @@ func (a *ApplyUtil) WaitReadiness(ref k8s2.ObjectRef, timeout time.Duration) boo func (a *ApplyUtil) applyDeploymentItem(d *deployment.DeploymentItem) { var toDelete []k8s2.ObjectRef for _, x := range d.Config.DeleteObjects { - for _, gvk := range a.k.Resources.GetGVKs(x.Group, x.Version, x.Kind) { + for _, gvk := range a.k.Resources.GetFilteredGVKs(k8s.BuildGVKFilter(x.Group, x.Version, x.Kind)) { ref := k8s2.ObjectRef{ GVK: gvk, Name: x.Name, diff --git a/pkg/k8s/resources.go b/pkg/k8s/resources.go index 39f537f48..739c18aae 100644 --- a/pkg/k8s/resources.go +++ b/pkg/k8s/resources.go @@ -219,12 +219,12 @@ func (k *k8sResources) GetAllGroupVersions() ([]schema.GroupVersion, error) { return l, nil } -func (k *k8sResources) GetFilteredPreferredGVKs(filter func(ar *v1.APIResource) bool) []schema.GroupVersionKind { +func (k *k8sResources) GetFilteredGVKs(filter func(ar *v1.APIResource) bool) []schema.GroupVersionKind { k.mutex.Lock() defer k.mutex.Unlock() var ret []schema.GroupVersionKind - for _, ar := range k.preferredResources { + for _, ar := range k.allResources { if !filter(ar) { continue } @@ -238,26 +238,40 @@ func (k *k8sResources) GetFilteredPreferredGVKs(filter func(ar *v1.APIResource) return ret } -func (k *k8sResources) GetGVKs(group *string, version *string, kind *string) []schema.GroupVersionKind { +func (k *k8sResources) GetFilteredPreferredGVKs(filter func(ar *v1.APIResource) bool) []schema.GroupVersionKind { k.mutex.Lock() defer k.mutex.Unlock() var ret []schema.GroupVersionKind - for gvk := range k.allResources { - if group != nil && *group != gvk.Group { - continue - } - if version != nil && *version != gvk.Version { + for _, ar := range k.preferredResources { + if !filter(ar) { continue } - if kind != nil && *kind != gvk.Kind { - continue + gvk := schema.GroupVersionKind{ + Group: ar.Group, + Version: ar.Version, + Kind: ar.Kind, } ret = append(ret, gvk) } return ret } +func BuildGVKFilter(group *string, version *string, kind *string) func(ar *v1.APIResource) bool { + return func(ar *v1.APIResource) bool { + if group != nil && *group != ar.Group { + return false + } + if version != nil && *version != ar.Version { + return false + } + if kind != nil && *kind != ar.Kind { + return false + } + return true + } +} + func (k *k8sResources) getGVRForGVK(gvk schema.GroupVersionKind) (*schema.GroupVersionResource, bool, error) { k.mutex.Lock() defer k.mutex.Unlock() From 75d39cee9700d8642e1e0c7667da0645f8aef7b8 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 10 May 2022 15:26:00 +0200 Subject: [PATCH 0799/2916] refactor: Get rid of GetApiResourceForGVK and use GetGVRForGVK instead --- pkg/k8s/k8s_cluster.go | 4 ++-- pkg/k8s/resources.go | 18 +++++------------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index 06b4d6615..b5fa4a81a 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -175,7 +175,7 @@ func (k *K8sCluster) withClientFromPool(cb func(p *parallelClientEntry) error) ( func (k *K8sCluster) withDynamicClientForGVK(gvk schema.GroupVersionKind, namespace string, cb func(r dynamic.ResourceInterface) error) ([]ApiWarning, error) { return k.withClientFromPool(func(p *parallelClientEntry) error { - gvr, namespaced, err := k.Resources.getGVRForGVK(gvk) + gvr, namespaced, err := k.Resources.GetGVRForGVK(gvk) if err != nil { return err } @@ -190,7 +190,7 @@ func (k *K8sCluster) withDynamicClientForGVK(gvk schema.GroupVersionKind, namesp func (k *K8sCluster) withMetadataClientForGVK(gvk schema.GroupVersionKind, namespace string, cb func(r metadata.ResourceInterface) error) ([]ApiWarning, error) { return k.withClientFromPool(func(p *parallelClientEntry) error { - gvr, namespaced, err := k.Resources.getGVRForGVK(gvk) + gvr, namespaced, err := k.Resources.GetGVRForGVK(gvk) if err != nil { return err } diff --git a/pkg/k8s/resources.go b/pkg/k8s/resources.go index 739c18aae..67b98505f 100644 --- a/pkg/k8s/resources.go +++ b/pkg/k8s/resources.go @@ -272,7 +272,7 @@ func BuildGVKFilter(group *string, version *string, kind *string) func(ar *v1.AP } } -func (k *k8sResources) getGVRForGVK(gvk schema.GroupVersionKind) (*schema.GroupVersionResource, bool, error) { +func (k *k8sResources) GetGVRForGVK(gvk schema.GroupVersionKind) (*schema.GroupVersionResource, bool, error) { k.mutex.Lock() defer k.mutex.Unlock() @@ -291,23 +291,15 @@ func (k *k8sResources) getGVRForGVK(gvk schema.GroupVersionKind) (*schema.GroupV }, ar.Namespaced, nil } -func (k *k8sResources) GetApiResourceForGVK(gvk schema.GroupVersionKind) *v1.APIResource { - k.mutex.Lock() - defer k.mutex.Unlock() - - ar, _ := k.allResources[gvk] - return ar -} - func (k *k8sResources) GetCRDForGVK(k2 *K8sCluster, gvk schema.GroupVersionKind) (*uo.UnstructuredObject, error) { - ar := k.GetApiResourceForGVK(gvk) - if ar == nil { - return nil, fmt.Errorf("APIResource for %s not found", gvk.String()) + gvr, _, err := k.GetGVRForGVK(gvk) + if err != nil { + return nil, err } crdRef := k8s.ObjectRef{ GVK: schema.GroupVersionKind{Group: "apiextensions.k8s.io", Version: "v1", Kind: "CustomResourceDefinition"}, - Name: fmt.Sprintf("%s.%s", ar.Name, ar.Group), + Name: fmt.Sprintf("%s.%s", gvr.Resource, gvr.Group), } k.mutex.Lock() From e22509b7bf11a5c0759c0d578023bbca01781114 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 10 May 2022 15:30:55 +0200 Subject: [PATCH 0800/2916] refactor: Implement and use GetPreferredResource --- pkg/k8s/resources.go | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/pkg/k8s/resources.go b/pkg/k8s/resources.go index 67b98505f..6100ad0a8 100644 --- a/pkg/k8s/resources.go +++ b/pkg/k8s/resources.go @@ -171,15 +171,15 @@ func (k *k8sResources) SetNamespaced(gv schema.GroupKind, namespaced bool) { } func (k *k8sResources) IsNamespaced(gv schema.GroupKind) bool { - k.mutex.Lock() - defer k.mutex.Unlock() + ar := k.GetPreferredResource(gv) + if ar == nil { + k.mutex.Lock() + defer k.mutex.Unlock() - r, ok := k.preferredResources[gv] - if !ok { n, _ := k.namespacedResources[gv] return n } - return r.Namespaced + return ar.Namespaced } func (k *k8sResources) FixNamespace(o *uo.UnstructuredObject, def string) { @@ -219,6 +219,17 @@ func (k *k8sResources) GetAllGroupVersions() ([]schema.GroupVersion, error) { return l, nil } +func (k *k8sResources) GetPreferredResource(gk schema.GroupKind) *v1.APIResource { + k.mutex.Lock() + defer k.mutex.Unlock() + + ar, ok := k.preferredResources[gk] + if !ok { + return nil + } + return ar +} + func (k *k8sResources) GetFilteredGVKs(filter func(ar *v1.APIResource) bool) []schema.GroupVersionKind { k.mutex.Lock() defer k.mutex.Unlock() From 5a7d27dc18ea68cf51cbb92d8d7f0d3142a196b8 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 10 May 2022 23:04:06 +0200 Subject: [PATCH 0801/2916] fix: Update api resources from CRDs, including not deployed ones --- pkg/deployment/deployment_item.go | 15 +- pkg/deployment/utils/apply_utils.go | 36 +---- pkg/k8s/k8s_cluster.go | 19 ++- pkg/k8s/resources.go | 241 +++++++++++++++++++--------- pkg/validation/validation.go | 2 +- 5 files changed, 179 insertions(+), 134 deletions(-) diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 52c563251..7f4912747 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -388,7 +388,7 @@ func (di *DeploymentItem) loadObjects() error { var crdGV = schema.GroupKind{Group: "apiextensions.k8s.io", Kind: "CustomResourceDefinition"} -// postprocessCRDs will set namespace overrides into the K8sCluster so that future IsNamespaced return the correct +// postprocessCRDs will update api resources from freshly deployed CRDs // value even if the CRD is not deployed yet. func (di *DeploymentItem) postprocessCRDs(k *k8s.K8sCluster) error { if di.dir == nil { @@ -401,21 +401,10 @@ func (di *DeploymentItem) postprocessCRDs(k *k8s.K8sCluster) error { continue } - scope, _, err := o.GetNestedString("spec", "scope") + err := k.Resources.UpdateResourcesFromCRD(o) if err != nil { return err } - namespaced := strings.ToLower(scope) == "namespaced" - group, _, err := o.GetNestedString("spec", "group") - if err != nil { - return err - } - kind, _, err := o.GetNestedString("spec", "names", "kind") - if err != nil { - return err - } - - k.Resources.SetNamespaced(schema.GroupKind{Group: group, Kind: kind}, namespaced) } return nil } diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index 12274398d..ff10287a5 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -45,8 +45,7 @@ type ApplyUtil struct { deletedHookObjects map[k8s2.ObjectRef]bool mutex sync.Mutex - abortSignal *atomic.Value - deployedNewCRD *atomic.Value + abortSignal *atomic.Value ru *RemoteObjectUtils k *k8s.K8sCluster @@ -63,8 +62,7 @@ type ApplyDeploymentsUtil struct { k *k8s.K8sCluster o *ApplyUtilOptions - abortSignal atomic.Value - deployedNewCRD atomic.Value + abortSignal atomic.Value results []*ApplyUtil } @@ -79,7 +77,6 @@ func NewApplyDeploymentsUtil(ctx context.Context, dew *DeploymentErrorsAndWarnin o: o, } ret.abortSignal.Store(false) - ret.deployedNewCRD.Store(false) return ret } @@ -92,7 +89,6 @@ func (ad *ApplyDeploymentsUtil) NewApplyUtil(ctx context.Context, statusCtx *sta deletedObjects: map[k8s2.ObjectRef]bool{}, deletedHookObjects: map[k8s2.ObjectRef]bool{}, abortSignal: &ad.abortSignal, - deployedNewCRD: &ad.deployedNewCRD, ru: ad.ru, k: ad.k, o: ad.o, @@ -307,10 +303,6 @@ func (a *ApplyUtil) ApplyObject(x *uo.UnstructuredObject, replaced bool, hook bo ForceDryRun: a.o.DryRun, } r, apiWarnings, err := a.k.PatchObject(x, options) - retry, err := a.handleNewCRDs(r, err) - if retry { - r, apiWarnings, err = a.k.PatchObject(x, options) - } if r != nil && usesDummyName { tmpName := r.GetK8sName() _ = r.ReplaceKeys(tmpName, ref.Name) @@ -331,30 +323,6 @@ func (a *ApplyUtil) ApplyObject(x *uo.UnstructuredObject, replaced bool, hook bo } } -func (a *ApplyUtil) handleNewCRDs(x *uo.UnstructuredObject, err error) (bool, error) { - if err != nil && meta.IsNoMatchError(err) { - // maybe this was a resource for which the CRD was only deployed recently, so we should do rediscovery and then - // retry the patch - if a.deployedNewCRD.Load().(bool) { - a.deployedNewCRD.Store(false) - err = a.k.Resources.RediscoverResources() - if err != nil { - return false, err - } - return true, nil - } - } else if err == nil { - ref := x.GetK8sRef() - if ref.GVK.Group == "apiextensions.k8s.io" && ref.GVK.Kind == "CustomResourceDefinition" { - // this is a freshly deployed CRD, so we must perform rediscovery in case an api resource can't be found - a.deployedNewCRD.Store(true) - return true, nil - } - return false, nil - } - return false, err -} - func (a *ApplyUtil) WaitReadiness(ref k8s2.ObjectRef, timeout time.Duration) bool { if a.o.DryRun { return true diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index b5fa4a81a..b34e828dc 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -71,20 +71,21 @@ func (p *parallelClientEntry) HandleWarningHeader(code int, agent string, text s const parallelClients = 16 func NewK8sCluster(ctx context.Context, configIn *rest.Config, dryRun bool) (*K8sCluster, error) { + var err error + restConfig := rest.CopyConfig(configIn) restConfig.QPS = 10 restConfig.Burst = 20 - resources, err := newK8sResources(ctx, restConfig) - if err != nil { - return nil, err - } - k := &K8sCluster{ ctx: ctx, DryRun: dryRun, restConfig: restConfig, - Resources: resources, + } + + k.Resources, err = newK8sResources(ctx, restConfig, k) + if err != nil { + return nil, err } err = k.initClientPool() @@ -102,7 +103,11 @@ func NewK8sCluster(ctx context.Context, configIn *rest.Config, dryRun bool) (*K8 } k.ServerVersion = v2 - err = k.Resources.updateResources(true) + err = k.Resources.updateResources() + if err != nil { + return nil, err + } + err = k.Resources.updateResourcesFromCRDs() if err != nil { return nil, err } diff --git a/pkg/k8s/resources.go b/pkg/k8s/resources.go index 6100ad0a8..266b7d090 100644 --- a/pkg/k8s/resources.go +++ b/pkg/k8s/resources.go @@ -8,42 +8,40 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils/uo" "k8s.io/apimachinery/pkg/api/meta" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/version" "k8s.io/client-go/discovery" "k8s.io/client-go/discovery/cached/disk" "k8s.io/client-go/dynamic" "k8s.io/client-go/rest" "net/url" "path/filepath" + "sort" "strings" "sync" "time" ) -type crdCacheEntry struct { - crd *uo.UnstructuredObject - err error -} - type k8sResources struct { ctx context.Context + k *K8sCluster discovery *disk.CachedDiscoveryClient - allResources map[schema.GroupVersionKind]*v1.APIResource - preferredResources map[schema.GroupKind]*v1.APIResource - namespacedResources map[schema.GroupKind]bool - crdCache map[k8s.ObjectRef]crdCacheEntry - mutex sync.Mutex + allResources map[schema.GroupVersionKind]v1.APIResource + preferredResources map[schema.GroupKind]v1.APIResource + crds map[schema.GroupKind]*uo.UnstructuredObject + mutex sync.Mutex } -func newK8sResources(ctx context.Context, config *rest.Config) (*k8sResources, error) { +func newK8sResources(ctx context.Context, config *rest.Config, k2 *K8sCluster) (*k8sResources, error) { k := &k8sResources{ - ctx: ctx, - allResources: map[schema.GroupVersionKind]*v1.APIResource{}, - preferredResources: map[schema.GroupKind]*v1.APIResource{}, - namespacedResources: map[schema.GroupKind]bool{}, - crdCache: map[k8s.ObjectRef]crdCacheEntry{}, - mutex: sync.Mutex{}, + ctx: ctx, + k: k2, + allResources: map[schema.GroupVersionKind]v1.APIResource{}, + preferredResources: map[schema.GroupKind]v1.APIResource{}, + crds: map[schema.GroupKind]*uo.UnstructuredObject{}, + mutex: sync.Mutex{}, } apiHost, err := url.Parse(config.Host) @@ -60,15 +58,13 @@ func newK8sResources(ctx context.Context, config *rest.Config) (*k8sResources, e return k, nil } -func (k *k8sResources) updateResources(doLock bool) error { - if doLock { - k.mutex.Lock() - defer k.mutex.Unlock() - } +func (k *k8sResources) updateResources() error { + k.mutex.Lock() + defer k.mutex.Unlock() - k.allResources = map[schema.GroupVersionKind]*v1.APIResource{} - k.preferredResources = map[schema.GroupKind]*v1.APIResource{} - k.crdCache = map[k8s.ObjectRef]crdCacheEntry{} + k.allResources = map[schema.GroupVersionKind]v1.APIResource{} + k.preferredResources = map[schema.GroupKind]v1.APIResource{} + k.crds = map[schema.GroupKind]*uo.UnstructuredObject{} // the discovery client doesn't support cancellation, so we need to run it in the background and wait for it var arls []*v1.APIResourceList @@ -124,7 +120,7 @@ func (k *k8sResources) updateResources(doLock bool) error { if _, ok := k.allResources[gvk]; ok { ok = false } - k.allResources[gvk] = &ar + k.allResources[gvk] = ar } } @@ -150,53 +146,160 @@ func (k *k8sResources) updateResources(doLock bool) error { if _, ok := deprecatedResources[gk]; ok { continue } - k.preferredResources[gk] = &ar + k.preferredResources[gk] = ar } } return nil } -func (k *k8sResources) RediscoverResources() error { - k.mutex.Lock() - defer k.mutex.Unlock() +var crdGK = schema.GroupKind{Group: "apiextensions.k8s.io", Kind: "CustomResourceDefinition"} - k.discovery.Invalidate() - return k.updateResources(false) +func (k *k8sResources) updateResourcesFromCRDs() error { + ar := k.GetPreferredResource(crdGK) + if ar == nil { + return fmt.Errorf("api resource for CRDs not found") + } + gvr, _, err := k.GetGVRForGVK(schema.GroupVersionKind{ + Group: ar.Group, + Version: ar.Version, + Kind: ar.Kind, + }) + if err != nil { + return err + } + + var crds *unstructured.UnstructuredList + _, err = k.k.withClientFromPool(func(p *parallelClientEntry) error { + var err error + crds, err = p.dynamicClient.Resource(*gvr).List(k.ctx, v1.ListOptions{}) + return err + }) + if err != nil { + return err + } + + for _, x := range crds.Items { + x2 := x + crd := uo.FromUnstructured(&x2) + err = k.UpdateResourcesFromCRD(crd) + if err != nil { + return err + } + } + + return nil } -func (k *k8sResources) SetNamespaced(gv schema.GroupKind, namespaced bool) { +func (k *k8sResources) UpdateResourcesFromCRD(crd *uo.UnstructuredObject) error { + var err error + var ar v1.APIResource + ar.Group, _, err = crd.GetNestedString("spec", "group") + if err != nil { + return err + } + ar.Name, _, err = crd.GetNestedString("spec", "names", "plural") + if err != nil { + return err + } + ar.Kind, _, err = crd.GetNestedString("spec", "names", "kind") + if err != nil { + return err + } + ar.SingularName, _, err = crd.GetNestedString("spec", "names", "singular") + if err != nil { + return err + } + scope, _, err := crd.GetNestedString("spec", "scope") + if err != nil { + return err + } + ar.Namespaced = strings.ToLower(scope) == "namespaced" + ar.ShortNames, _, err = crd.GetNestedStringList("spec", "names", "shortNames") + if err != nil { + return err + } + ar.Categories, _, err = crd.GetNestedStringList("spec", "names", "categories") + if err != nil { + return err + } + versions, _, err := crd.GetNestedObjectList("spec", "versions") + if err != nil { + return err + } + + ar.Verbs = []string{"delete", "deletecollection", "get", "list", "patch", "create", "update", "watch"} + + gk := schema.GroupKind{ + Group: ar.Group, + Kind: ar.Kind, + } + k.mutex.Lock() defer k.mutex.Unlock() - k.namespacedResources[gv] = namespaced + + k.crds[gk] = crd + + var versionStrs []string + for _, v := range versions { + name, _, err := v.GetNestedString("name") + if err != nil { + return err + } + versionStrs = append(versionStrs, name) + } + + // Sort the same way as api discovery does it. The first entry is then the preferred version + sort.Slice(versionStrs, func(i, j int) bool { + return version.CompareKubeAwareVersionStrings(versionStrs[i], versionStrs[j]) > 0 + }) + + for i, v := range versionStrs { + ar2 := ar + ar2.Version = v + gvk := schema.GroupVersionKind{ + Group: gk.Group, + Version: ar2.Version, + Kind: gk.Kind, + } + k.allResources[gvk] = ar2 + + if i == 0 { + k.preferredResources[gk] = ar2 + } + } + + return nil } -func (k *k8sResources) IsNamespaced(gv schema.GroupKind) bool { +func (k *k8sResources) IsNamespaced(gv schema.GroupKind) *bool { ar := k.GetPreferredResource(gv) if ar == nil { - k.mutex.Lock() - defer k.mutex.Unlock() - - n, _ := k.namespacedResources[gv] - return n + return nil } - return ar.Namespaced + return &ar.Namespaced } func (k *k8sResources) FixNamespace(o *uo.UnstructuredObject, def string) { ref := o.GetK8sRef() namespaced := k.IsNamespaced(ref.GVK.GroupKind()) - if !namespaced && ref.Namespace != "" { + if namespaced == nil { + return + } + if !*namespaced && ref.Namespace != "" { o.SetK8sNamespace("") - } else if namespaced && ref.Namespace == "" { + } else if *namespaced && ref.Namespace == "" { o.SetK8sNamespace(def) } } func (k *k8sResources) FixNamespaceInRef(ref k8s.ObjectRef) k8s.ObjectRef { namespaced := k.IsNamespaced(ref.GVK.GroupKind()) - if !namespaced && ref.Namespace != "" { + if namespaced == nil { + return ref + } + if !*namespaced && ref.Namespace != "" { ref.Namespace = "" - } else if namespaced && ref.Namespace == "" { + } else if *namespaced && ref.Namespace == "" { ref.Namespace = "default" } return ref @@ -227,7 +330,7 @@ func (k *k8sResources) GetPreferredResource(gk schema.GroupKind) *v1.APIResource if !ok { return nil } - return ar + return &ar } func (k *k8sResources) GetFilteredGVKs(filter func(ar *v1.APIResource) bool) []schema.GroupVersionKind { @@ -236,7 +339,7 @@ func (k *k8sResources) GetFilteredGVKs(filter func(ar *v1.APIResource) bool) []s var ret []schema.GroupVersionKind for _, ar := range k.allResources { - if !filter(ar) { + if !filter(&ar) { continue } gvk := schema.GroupVersionKind{ @@ -255,7 +358,7 @@ func (k *k8sResources) GetFilteredPreferredGVKs(filter func(ar *v1.APIResource) var ret []schema.GroupVersionKind for _, ar := range k.preferredResources { - if !filter(ar) { + if !filter(&ar) { continue } gvk := schema.GroupVersionKind{ @@ -302,39 +405,19 @@ func (k *k8sResources) GetGVRForGVK(gvk schema.GroupVersionKind) (*schema.GroupV }, ar.Namespaced, nil } -func (k *k8sResources) GetCRDForGVK(k2 *K8sCluster, gvk schema.GroupVersionKind) (*uo.UnstructuredObject, error) { - gvr, _, err := k.GetGVRForGVK(gvk) - if err != nil { - return nil, err - } - - crdRef := k8s.ObjectRef{ - GVK: schema.GroupVersionKind{Group: "apiextensions.k8s.io", Version: "v1", Kind: "CustomResourceDefinition"}, - Name: fmt.Sprintf("%s.%s", gvr.Resource, gvr.Group), - } - +func (k *k8sResources) GetCRDForGK(gk schema.GroupKind) *uo.UnstructuredObject { k.mutex.Lock() - x, ok := k.crdCache[crdRef] - k.mutex.Unlock() - if ok { - return x.crd, x.err - } - - crd, _, err := k2.GetSingleObject(crdRef) + defer k.mutex.Unlock() - k.mutex.Lock() - x.crd = crd - x.err = err - k.crdCache[crdRef] = x - k.mutex.Unlock() + crd, _ := k.crds[gk] - return crd, err + return crd } -func (k *k8sResources) GetSchemaForGVK(k2 *K8sCluster, gvk schema.GroupVersionKind) (*uo.UnstructuredObject, error) { - crd, err := k.GetCRDForGVK(k2, gvk) - if err != nil { - return nil, err +func (k *k8sResources) GetSchemaForGVK(gvk schema.GroupVersionKind) (*uo.UnstructuredObject, error) { + crd := k.GetCRDForGK(gvk.GroupKind()) + if crd == nil { + return nil, nil } versions, ok, err := crd.GetNestedObjectList("spec", "versions") @@ -345,12 +428,12 @@ func (k *k8sResources) GetSchemaForGVK(k2 *K8sCluster, gvk schema.GroupVersionKi return nil, fmt.Errorf("versions not found in CRD") } - for _, version := range versions { - name, _, _ := version.GetNestedString("name") + for _, v := range versions { + name, _, _ := v.GetNestedString("name") if name != gvk.Version { continue } - s, ok, err := version.GetNestedObject("schema", "openAPIV3Schema") + s, ok, err := v.GetNestedObject("schema", "openAPIV3Schema") if err != nil { return nil, err } diff --git a/pkg/validation/validation.go b/pkg/validation/validation.go index 0331074bc..6e631470f 100644 --- a/pkg/validation/validation.go +++ b/pkg/validation/validation.go @@ -117,7 +117,7 @@ func ValidateObject(k *k8s.K8sCluster, o *uo.UnstructuredObject, notReadyIsError // can't really say anything... return } - s, err := k.Resources.GetSchemaForGVK(k, ref.GVK) + s, err := k.Resources.GetSchemaForGVK(ref.GVK) if err != nil && !errors.IsNotFound(err) { addError(err.Error()) return From 2e9a7c47cac94e9d702e0abc3fa0f1dcfdd6c9bb Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 10 May 2022 23:22:59 +0200 Subject: [PATCH 0802/2916] refactor: Move client pool handling into own class --- pkg/k8s/client.go | 141 +++++++++++++++++++++++++++++++++++++++++ pkg/k8s/k8s_cluster.go | 130 +++---------------------------------- pkg/k8s/resources.go | 12 ++-- 3 files changed, 157 insertions(+), 126 deletions(-) create mode 100644 pkg/k8s/client.go diff --git a/pkg/k8s/client.go b/pkg/k8s/client.go new file mode 100644 index 000000000..fbda2dbd5 --- /dev/null +++ b/pkg/k8s/client.go @@ -0,0 +1,141 @@ +package k8s + +import ( + "context" + "fmt" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/dynamic" + corev1 "k8s.io/client-go/kubernetes/typed/core/v1" + "k8s.io/client-go/metadata" + "k8s.io/client-go/rest" + "net/http" +) + +type k8sClients struct { + ctx context.Context + restConfig *rest.Config + clientPool chan *parallelClientEntry + count int +} + +type parallelClientEntry struct { + http *http.Client + corev1 *corev1.CoreV1Client + dynamicClient dynamic.Interface + metadataClient metadata.Interface + + warnings []ApiWarning +} + +type ApiWarning struct { + Code int + Agent string + Text string +} + +func (p *parallelClientEntry) HandleWarningHeader(code int, agent string, text string) { + p.warnings = append(p.warnings, ApiWarning{ + Code: code, + Agent: agent, + Text: text, + }) +} + +func newK8sClients(ctx context.Context, restConfig *rest.Config, count int) (*k8sClients, error) { + var err error + + k := &k8sClients{ + ctx: ctx, + restConfig: restConfig, + clientPool: make(chan *parallelClientEntry, count), + count: count, + } + + for i := 0; i < count; i++ { + p := ¶llelClientEntry{} + config := rest.CopyConfig(k.restConfig) + config.WarningHandler = p + + p.http, err = rest.HTTPClientFor(config) + if err != nil { + return nil, err + } + + p.corev1, err = corev1.NewForConfigAndClient(config, p.http) + if err != nil { + return nil, err + } + + p.dynamicClient, err = dynamic.NewForConfigAndClient(config, p.http) + if err != nil { + return nil, err + } + + p.metadataClient, err = metadata.NewForConfigAndClient(config, p.http) + if err != nil { + return nil, err + } + + k.clientPool <- p + } + return k, nil +} + +func (k *k8sClients) close() { + if k.clientPool != nil { + for i := 0; i < k.count; i++ { + p := <-k.clientPool + p.http.CloseIdleConnections() + } + } + k.clientPool = nil + k.count = 0 +} + +func (k *k8sClients) withClientFromPool(cb func(p *parallelClientEntry) error) ([]ApiWarning, error) { + select { + case p := <-k.clientPool: + defer func() { k.clientPool <- p }() + p.warnings = nil + err := cb(p) + return append([]ApiWarning(nil), p.warnings...), err + case <-k.ctx.Done(): + return nil, fmt.Errorf("failed waiting for free client: %w", k.ctx.Err()) + } +} + +func (k *k8sClients) withDynamicClientForGVR(gvr *schema.GroupVersionResource, namespace string, cb func(r dynamic.ResourceInterface) error) ([]ApiWarning, error) { + return k.withClientFromPool(func(p *parallelClientEntry) error { + if namespace != "" { + return cb(p.dynamicClient.Resource(*gvr).Namespace(namespace)) + } else { + return cb(p.dynamicClient.Resource(*gvr)) + } + }) +} + +func (k *k8sClients) withMetadataClientForGVR(gvr *schema.GroupVersionResource, namespace string, cb func(r metadata.ResourceInterface) error) ([]ApiWarning, error) { + return k.withClientFromPool(func(p *parallelClientEntry) error { + if namespace != "" { + return cb(p.metadataClient.Resource(*gvr).Namespace(namespace)) + } else { + return cb(p.metadataClient.Resource(*gvr)) + } + }) +} + +func (k *k8sClients) withDynamicClientForGVK(resources *k8sResources, gvk schema.GroupVersionKind, namespace string, cb func(r dynamic.ResourceInterface) error) ([]ApiWarning, error) { + gvr, err := resources.GetGVRForGVK(gvk) + if err != nil { + return nil, err + } + return k.withDynamicClientForGVR(gvr, namespace, cb) +} + +func (k *k8sClients) withMetadataClientForGVK(resources *k8sResources, gvk schema.GroupVersionKind, namespace string, cb func(r metadata.ResourceInterface) error) ([]ApiWarning, error) { + gvr, err := resources.GetGVRForGVK(gvk) + if err != nil { + return nil, err + } + return k.withMetadataClientForGVR(gvr, namespace, cb) +} diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index b34e828dc..3a7993f2f 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -17,11 +17,9 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/dynamic" - corev1 "k8s.io/client-go/kubernetes/typed/core/v1" "k8s.io/client-go/metadata" _ "k8s.io/client-go/plugin/pkg/client/auth" "k8s.io/client-go/rest" - "net/http" "sync" "time" ) @@ -38,38 +36,13 @@ type K8sCluster struct { DryRun bool restConfig *rest.Config - clientPool chan *parallelClientEntry + clients *k8sClients ServerVersion *goversion.Version Resources *k8sResources } -type parallelClientEntry struct { - http *http.Client - corev1 *corev1.CoreV1Client - dynamicClient dynamic.Interface - metadataClient metadata.Interface - - warnings []ApiWarning -} - -type ApiWarning struct { - Code int - Agent string - Text string -} - -func (p *parallelClientEntry) HandleWarningHeader(code int, agent string, text string) { - p.warnings = append(p.warnings, ApiWarning{ - Code: code, - Agent: agent, - Text: text, - }) -} - -const parallelClients = 16 - func NewK8sCluster(ctx context.Context, configIn *rest.Config, dryRun bool) (*K8sCluster, error) { var err error @@ -88,7 +61,7 @@ func NewK8sCluster(ctx context.Context, configIn *rest.Config, dryRun bool) (*K8 return nil, err } - err = k.initClientPool() + k.clients, err = newK8sClients(ctx, restConfig, 16) if err != nil { return nil, err } @@ -107,7 +80,7 @@ func NewK8sCluster(ctx context.Context, configIn *rest.Config, dryRun bool) (*K8 if err != nil { return nil, err } - err = k.Resources.updateResourcesFromCRDs() + err = k.Resources.updateResourcesFromCRDs(k.clients) if err != nil { return nil, err } @@ -115,47 +88,6 @@ func NewK8sCluster(ctx context.Context, configIn *rest.Config, dryRun bool) (*K8 return k, nil } -func (k *K8sCluster) initClientPool() error { - var err error - - if k.clientPool != nil { - for i := 0; i < parallelClients; i++ { - p := <-k.clientPool - p.http.CloseIdleConnections() - } - } - - k.clientPool = make(chan *parallelClientEntry, parallelClients) - for i := 0; i < parallelClients; i++ { - p := ¶llelClientEntry{} - config := rest.CopyConfig(k.restConfig) - config.WarningHandler = p - - p.http, err = rest.HTTPClientFor(config) - if err != nil { - return err - } - - p.corev1, err = corev1.NewForConfigAndClient(config, p.http) - if err != nil { - return err - } - - p.dynamicClient, err = dynamic.NewForConfigAndClient(config, p.http) - if err != nil { - return err - } - - p.metadataClient, err = metadata.NewForConfigAndClient(config, p.http) - if err != nil { - return err - } - - k.clientPool <- p - } - return nil -} - func (k *K8sCluster) ReadWrite() *K8sCluster { k2 := *k k2.DryRun = false @@ -166,48 +98,6 @@ func (k *K8sCluster) GetCA() []byte { return k.restConfig.CAData } -func (k *K8sCluster) withClientFromPool(cb func(p *parallelClientEntry) error) ([]ApiWarning, error) { - select { - case p := <-k.clientPool: - defer func() { k.clientPool <- p }() - p.warnings = nil - err := cb(p) - return append([]ApiWarning(nil), p.warnings...), err - case <-k.ctx.Done(): - return nil, fmt.Errorf("failed waiting for free client: %w", k.ctx.Err()) - } -} - -func (k *K8sCluster) withDynamicClientForGVK(gvk schema.GroupVersionKind, namespace string, cb func(r dynamic.ResourceInterface) error) ([]ApiWarning, error) { - return k.withClientFromPool(func(p *parallelClientEntry) error { - gvr, namespaced, err := k.Resources.GetGVRForGVK(gvk) - if err != nil { - return err - } - - if namespaced && namespace != "" { - return cb(p.dynamicClient.Resource(*gvr).Namespace(namespace)) - } else { - return cb(p.dynamicClient.Resource(*gvr)) - } - }) -} - -func (k *K8sCluster) withMetadataClientForGVK(gvk schema.GroupVersionKind, namespace string, cb func(r metadata.ResourceInterface) error) ([]ApiWarning, error) { - return k.withClientFromPool(func(p *parallelClientEntry) error { - gvr, namespaced, err := k.Resources.GetGVRForGVK(gvk) - if err != nil { - return err - } - - if namespaced && namespace != "" { - return cb(p.metadataClient.Resource(*gvr).Namespace(namespace)) - } else { - return cb(p.metadataClient.Resource(*gvr)) - } - }) -} - func (k *K8sCluster) buildLabelSelector(labels map[string]string) string { ret := "" @@ -223,7 +113,7 @@ func (k *K8sCluster) buildLabelSelector(labels map[string]string) string { func (k *K8sCluster) ListObjects(gvk schema.GroupVersionKind, namespace string, labels map[string]string) ([]*uo.UnstructuredObject, []ApiWarning, error) { var result []*uo.UnstructuredObject - apiWarnings, err := k.withDynamicClientForGVK(gvk, namespace, func(r dynamic.ResourceInterface) error { + apiWarnings, err := k.clients.withDynamicClientForGVK(k.Resources, gvk, namespace, func(r dynamic.ResourceInterface) error { o := v1.ListOptions{ LabelSelector: k.buildLabelSelector(labels), } @@ -242,7 +132,7 @@ func (k *K8sCluster) ListObjects(gvk schema.GroupVersionKind, namespace string, func (k *K8sCluster) ListObjectsMetadata(gvk schema.GroupVersionKind, namespace string, labels map[string]string) ([]*uo.UnstructuredObject, []ApiWarning, error) { var result []*uo.UnstructuredObject - apiWarnings, err := k.withMetadataClientForGVK(gvk, namespace, func(r metadata.ResourceInterface) error { + apiWarnings, err := k.clients.withMetadataClientForGVK(k.Resources, gvk, namespace, func(r metadata.ResourceInterface) error { o := v1.ListOptions{ LabelSelector: k.buildLabelSelector(labels), } @@ -318,7 +208,7 @@ func (k *K8sCluster) ListAllObjects(verbs []string, namespace string, labels map func (k *K8sCluster) GetSingleObject(ref k8s.ObjectRef) (*uo.UnstructuredObject, []ApiWarning, error) { var result *uo.UnstructuredObject - apiWarnings, err := k.withDynamicClientForGVK(ref.GVK, ref.Namespace, func(r dynamic.ResourceInterface) error { + apiWarnings, err := k.clients.withDynamicClientForGVK(k.Resources, ref.GVK, ref.Namespace, func(r dynamic.ResourceInterface) error { o := v1.GetOptions{} x, err := r.Get(k.ctx, ref.Name, o) if err != nil { @@ -382,7 +272,7 @@ func (k *K8sCluster) DeleteSingleObject(ref k8s.ObjectRef, options DeleteOptions o.DryRun = []string{"All"} } - apiWarnings, err := k.withDynamicClientForGVK(ref.GVK, ref.Namespace, func(r dynamic.ResourceInterface) error { + apiWarnings, err := k.clients.withDynamicClientForGVK(k.Resources, ref.GVK, ref.Namespace, func(r dynamic.ResourceInterface) error { err := r.Delete(k.ctx, ref.Name, o) if err != nil { if options.IgnoreNotFoundError && errors.IsNotFound(err) { @@ -539,7 +429,7 @@ func (k *K8sCluster) PatchObject(o *uo.UnstructuredObject, options PatchOptions) status.Trace(k.ctx, "patching %s", ref.String()) var result *uo.UnstructuredObject - apiWarnings, err := k.withDynamicClientForGVK(ref.GVK, ref.Namespace, func(r dynamic.ResourceInterface) error { + apiWarnings, err := k.clients.withDynamicClientForGVK(k.Resources, ref.GVK, ref.Namespace, func(r dynamic.ResourceInterface) error { x, err := r.Patch(k.ctx, ref.Name, types.ApplyPatchType, data, po) if err != nil { return fmt.Errorf("failed to patch %s: %w", ref.String(), err) @@ -568,7 +458,7 @@ func (k *K8sCluster) UpdateObject(o *uo.UnstructuredObject, options UpdateOption status.Trace(k.ctx, "updating %s", ref.String()) var result *uo.UnstructuredObject - apiWarnings, err := k.withDynamicClientForGVK(ref.GVK, ref.Namespace, func(r dynamic.ResourceInterface) error { + apiWarnings, err := k.clients.withDynamicClientForGVK(k.Resources, ref.GVK, ref.Namespace, func(r dynamic.ResourceInterface) error { x, err := r.Update(k.ctx, o.ToUnstructured(), updateOpts) if err != nil { return err @@ -581,7 +471,7 @@ func (k *K8sCluster) UpdateObject(o *uo.UnstructuredObject, options UpdateOption func (k *K8sCluster) ProxyGet(scheme, namespace, name, port, path string, params map[string]string) (io.ReadCloser, error) { var ret rest.ResponseWrapper - _, err := k.withClientFromPool(func(p *parallelClientEntry) error { + _, err := k.clients.withClientFromPool(func(p *parallelClientEntry) error { ret = p.corev1.Services(namespace).ProxyGet(scheme, name, port, path, params) return nil }) diff --git a/pkg/k8s/resources.go b/pkg/k8s/resources.go index 266b7d090..359b9451b 100644 --- a/pkg/k8s/resources.go +++ b/pkg/k8s/resources.go @@ -154,12 +154,12 @@ func (k *k8sResources) updateResources() error { var crdGK = schema.GroupKind{Group: "apiextensions.k8s.io", Kind: "CustomResourceDefinition"} -func (k *k8sResources) updateResourcesFromCRDs() error { +func (k *k8sResources) updateResourcesFromCRDs(clients *k8sClients) error { ar := k.GetPreferredResource(crdGK) if ar == nil { return fmt.Errorf("api resource for CRDs not found") } - gvr, _, err := k.GetGVRForGVK(schema.GroupVersionKind{ + gvr, err := k.GetGVRForGVK(schema.GroupVersionKind{ Group: ar.Group, Version: ar.Version, Kind: ar.Kind, @@ -169,7 +169,7 @@ func (k *k8sResources) updateResourcesFromCRDs() error { } var crds *unstructured.UnstructuredList - _, err = k.k.withClientFromPool(func(p *parallelClientEntry) error { + _, err = clients.withClientFromPool(func(p *parallelClientEntry) error { var err error crds, err = p.dynamicClient.Resource(*gvr).List(k.ctx, v1.ListOptions{}) return err @@ -386,13 +386,13 @@ func BuildGVKFilter(group *string, version *string, kind *string) func(ar *v1.AP } } -func (k *k8sResources) GetGVRForGVK(gvk schema.GroupVersionKind) (*schema.GroupVersionResource, bool, error) { +func (k *k8sResources) GetGVRForGVK(gvk schema.GroupVersionKind) (*schema.GroupVersionResource, error) { k.mutex.Lock() defer k.mutex.Unlock() ar, ok := k.allResources[gvk] if !ok { - return nil, false, &meta.NoKindMatchError{ + return nil, &meta.NoKindMatchError{ GroupKind: gvk.GroupKind(), SearchedVersions: []string{gvk.Version}, } @@ -402,7 +402,7 @@ func (k *k8sResources) GetGVRForGVK(gvk schema.GroupVersionKind) (*schema.GroupV Group: ar.Group, Version: ar.Version, Resource: ar.Name, - }, ar.Namespaced, nil + }, nil } func (k *k8sResources) GetCRDForGK(gk schema.GroupKind) *uo.UnstructuredObject { From a61cdf06046228bb3af3f964e823b42e369d1890 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 10 May 2022 23:50:31 +0200 Subject: [PATCH 0803/2916] refactor: Don't rely on dynamic client for CRD retrieval --- pkg/k8s/resources.go | 47 +++++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/pkg/k8s/resources.go b/pkg/k8s/resources.go index 359b9451b..3010d295a 100644 --- a/pkg/k8s/resources.go +++ b/pkg/k8s/resources.go @@ -6,9 +6,10 @@ import ( "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/api/meta" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/version" "k8s.io/client-go/discovery" @@ -21,12 +22,14 @@ import ( "strings" "sync" "time" + + apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1" ) type k8sResources struct { - ctx context.Context - k *K8sCluster - discovery *disk.CachedDiscoveryClient + ctx context.Context + restConfig *rest.Config + discovery *disk.CachedDiscoveryClient allResources map[schema.GroupVersionKind]v1.APIResource preferredResources map[schema.GroupKind]v1.APIResource @@ -34,10 +37,10 @@ type k8sResources struct { mutex sync.Mutex } -func newK8sResources(ctx context.Context, config *rest.Config, k2 *K8sCluster) (*k8sResources, error) { +func newK8sResources(ctx context.Context, config *rest.Config) (*k8sResources, error) { k := &k8sResources{ ctx: ctx, - k: k2, + restConfig: config, allResources: map[schema.GroupVersionKind]v1.APIResource{}, preferredResources: map[schema.GroupKind]v1.APIResource{}, crds: map[schema.GroupKind]*uo.UnstructuredObject{}, @@ -155,32 +158,26 @@ func (k *k8sResources) updateResources() error { var crdGK = schema.GroupKind{Group: "apiextensions.k8s.io", Kind: "CustomResourceDefinition"} func (k *k8sResources) updateResourcesFromCRDs(clients *k8sClients) error { - ar := k.GetPreferredResource(crdGK) - if ar == nil { - return fmt.Errorf("api resource for CRDs not found") - } - gvr, err := k.GetGVRForGVK(schema.GroupVersionKind{ - Group: ar.Group, - Version: ar.Version, - Kind: ar.Kind, - }) - if err != nil { - return err - } + var crdList *apiextensionsv1.CustomResourceDefinitionList + _, err := clients.withClientFromPool(func(p *parallelClientEntry) error { + c, err := apiextensionsclient.NewForConfigAndClient(k.restConfig, p.http) + if err != nil { + return err + } - var crds *unstructured.UnstructuredList - _, err = clients.withClientFromPool(func(p *parallelClientEntry) error { - var err error - crds, err = p.dynamicClient.Resource(*gvr).List(k.ctx, v1.ListOptions{}) + crdList, err = c.CustomResourceDefinitions().List(k.ctx, v1.ListOptions{}) return err }) if err != nil { return err } - for _, x := range crds.Items { - x2 := x - crd := uo.FromUnstructured(&x2) + for _, x := range crdList.Items { + u, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&x) + if err != nil { + return err + } + crd := uo.FromMap(u) err = k.UpdateResourcesFromCRD(crd) if err != nil { return err From 919e5ee2c3376bf5b662a7d156755e3d3c530950 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 10 May 2022 23:51:16 +0200 Subject: [PATCH 0804/2916] refactor: Run updateResources and updateResourcesFromCRDs in parallel --- pkg/k8s/k8s_cluster.go | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index 3a7993f2f..ecf422fc3 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -56,7 +56,7 @@ func NewK8sCluster(ctx context.Context, configIn *rest.Config, dryRun bool) (*K8 restConfig: restConfig, } - k.Resources, err = newK8sResources(ctx, restConfig, k) + k.Resources, err = newK8sResources(ctx, restConfig) if err != nil { return nil, err } @@ -76,13 +76,26 @@ func NewK8sCluster(ctx context.Context, configIn *rest.Config, dryRun bool) (*K8 } k.ServerVersion = v2 - err = k.Resources.updateResources() - if err != nil { - return nil, err - } - err = k.Resources.updateResourcesFromCRDs(k.clients) - if err != nil { - return nil, err + var wg sync.WaitGroup + wg.Add(2) + + var err1 error + var err2 error + go func() { + err1 = k.Resources.updateResources() + wg.Done() + }() + go func() { + err2 = k.Resources.updateResourcesFromCRDs(k.clients) + wg.Done() + }() + wg.Wait() + + if err1 != nil { + return nil, err1 + } + if err2 != nil { + return nil, err2 } return k, nil From 828e0265d354dafa68fab8ab43e9e73f4d6ba990 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 10 May 2022 23:57:02 +0200 Subject: [PATCH 0805/2916] fix: Fix loop variable capture --- pkg/k8s/k8s_cluster.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index ecf422fc3..3813ac550 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -190,6 +190,7 @@ func (k *K8sCluster) ListAllObjects(verbs []string, namespace string, labels map } for _, gvk := range k.Resources.GetFilteredPreferredGVKs(filter) { + gvk := gvk wp.Submit(func() error { var l []*uo.UnstructuredObject var apiWarnings []ApiWarning From 25253f767132b9a6727b01c50ed75fe82e075c74 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 12 May 2022 13:22:55 +0200 Subject: [PATCH 0806/2916] fix: Fix hang after failed postprocessing stage --- pkg/deployment/deployment_collection.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index def63872c..4b845757b 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -235,6 +235,8 @@ func (c *DeploymentCollection) buildKustomizeObjects(k *k8s.K8sCluster) error { if len(errs) == 0 { s.Success() + } else { + s.Failed() } return utils.NewErrorListOrNil(errs) From c88700b6e9c907b89f8e784816c955ccae21fb54 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 12 May 2022 14:55:28 +0200 Subject: [PATCH 0807/2916] fix: Add --offline-images flag to allow disabling registry queries --- cmd/kluctl/args/images.go | 1 + cmd/kluctl/commands/utils.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/kluctl/args/images.go b/cmd/kluctl/args/images.go index f9eb511be..f11fba0a4 100644 --- a/cmd/kluctl/args/images.go +++ b/cmd/kluctl/args/images.go @@ -11,6 +11,7 @@ type ImageFlags struct { FixedImage []string `group:"images" short:"F" help:"Pin an image to a given version. Expects '--fixed-image=image<:namespace:deployment:container>=result'"` FixedImagesFile existingFileType `group:"images" help:"Use .yml file to pin image versions. See output of list-images sub-command or read the documentation for details about the output format" exts:"yml,yaml"` UpdateImages bool `group:"images" short:"u" help:"This causes kluctl to prefer the latest image found in registries, based on the 'latest_image' filters provided to 'images.get_image(...)' calls. Use this flag if you want to update to the latest versions/tags of all images. '-u' takes precedence over '--fixed-image/--fixed-images-file', meaning that the latest images are used even if an older image is given via fixed images."` + OfflineImages bool `group:"images" help:"Omit contacting image registries and do not query for latest image tags."` } func (args *ImageFlags) LoadFixedImagesFromArgs() ([]types.FixedImage, error) { diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 30395bc61..c6f69df50 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -121,7 +121,7 @@ func withProjectTargetCommandContext(ctx context.Context, args projectTargetComm if err != nil { return fmt.Errorf("failed to parse registry auth from environment: %w", err) } - images, err := deployment.NewImages(rh, args.imageFlags.UpdateImages, args.forCompletion) + images, err := deployment.NewImages(rh, args.imageFlags.UpdateImages, args.imageFlags.OfflineImages || args.forCompletion) if err != nil { return err } From b6ea0929119b406957201b1c3006f85be803cf20 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 12 May 2022 14:56:01 +0200 Subject: [PATCH 0808/2916] refactor: Introduce KeyPath type --- pkg/diff/diff.go | 4 ++-- pkg/diff/managed_fields.go | 8 ++++---- pkg/k8s/k8s_cluster.go | 8 ++++---- pkg/kluctl_project/targets.go | 2 +- pkg/seal/secrets_loader.go | 6 +++--- pkg/utils/uo/jsonpath.go | 10 ++++++---- pkg/utils/uo/nested_fields.go | 12 ++++++------ pkg/utils/uo/object_iterator.go | 12 ++++-------- 8 files changed, 30 insertions(+), 32 deletions(-) diff --git a/pkg/diff/diff.go b/pkg/diff/diff.go index 3746c3c5f..5781b9312 100644 --- a/pkg/diff/diff.go +++ b/pkg/diff/diff.go @@ -18,7 +18,7 @@ import ( var notPresent = struct{}{} func convertPath(path []string, o interface{}) (string, error) { - var ret []interface{} + var ret uo.KeyPath for _, p := range path { if i, err := strconv.ParseInt(p, 10, 32); err == nil { x, found, _ := uo.GetChild(o, int(i)) @@ -35,7 +35,7 @@ func convertPath(path []string, o interface{}) (string, error) { ret = append(ret, p) o = x } - return uo.KeyListToJsonPath(ret), nil + return ret.ToJsonPath(), nil } func Diff(oldObject *uo.UnstructuredObject, newObject *uo.UnstructuredObject) ([]types.Change, error) { diff --git a/pkg/diff/managed_fields.go b/pkg/diff/managed_fields.go index 3a0772165..36200458d 100644 --- a/pkg/diff/managed_fields.go +++ b/pkg/diff/managed_fields.go @@ -59,7 +59,7 @@ func checkListItemMatch(o interface{}, pathElement fieldpath.PathElement, index } } -func convertToKeyList(remote *uo.UnstructuredObject, path fieldpath.Path) ([]interface{}, bool, error) { +func convertToKeyList(remote *uo.UnstructuredObject, path fieldpath.Path) (uo.KeyPath, bool, error) { var ret []interface{} var o interface{} = remote.Object for _, e := range path { @@ -157,7 +157,7 @@ func ResolveFieldManagerConflicts(local *uo.UnstructuredObject, remote *uo.Unstr return nil, nil, err } for _, f := range fields { - forceApplyFields[uo.KeyListToJsonPath(f)] = true + forceApplyFields[f.ToJsonPath()] = true } } @@ -209,13 +209,13 @@ func ResolveFieldManagerConflicts(local *uo.UnstructuredObject, remote *uo.Unstr break } } - if _, ok := forceApplyFields[uo.KeyListToJsonPath(p)]; ok { + if _, ok := forceApplyFields[p.ToJsonPath()]; ok { overwrite = true } } if !overwrite { - j, err := uo.NewMyJsonPath(uo.KeyListToJsonPath(p)) + j, err := uo.NewMyJsonPath(p.ToJsonPath()) if err != nil { return nil, nil, err } diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index 3813ac550..160a0ad8b 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -352,7 +352,7 @@ func (k *K8sCluster) FixObjectForPatch(o *uo.UnstructuredObject) *uo.Unstructure return } - ports, found, _ := uo.NewMyJsonPathMust(p).GetFirstListOfMaps(o) + ports, found, _ := uo.NewMyJsonPathMust(p).GetFirstListOfMaps(o.Object) if !found { return } @@ -368,7 +368,7 @@ func (k *K8sCluster) FixObjectForPatch(o *uo.UnstructuredObject) *uo.Unstructure if !needsTypeConversionFix { return } - d, found, _ := uo.NewMyJsonPathMust(p).GetFirstMap(o) + d, found, _ := uo.NewMyJsonPathMust(p).GetFirstMap(o.Object) if !found { return } @@ -389,7 +389,7 @@ func (k *K8sCluster) FixObjectForPatch(o *uo.UnstructuredObject) *uo.Unstructure } fixContainers := func(p string) { - containers, found, _ := uo.NewMyJsonPathMust(p).GetFirstListOfMaps(o) + containers, found, _ := uo.NewMyJsonPathMust(p).GetFirstListOfMaps(o.Object) if !found { return } @@ -399,7 +399,7 @@ func (k *K8sCluster) FixObjectForPatch(o *uo.UnstructuredObject) *uo.Unstructure } fixLimits := func(p string) { - limits, found, _ := uo.NewMyJsonPathMust(p).GetFirstListOfMaps(o) + limits, found, _ := uo.NewMyJsonPathMust(p).GetFirstListOfMaps(o.Object) if !found { return } diff --git a/pkg/kluctl_project/targets.go b/pkg/kluctl_project/targets.go index 8492422b2..8e139fa64 100644 --- a/pkg/kluctl_project/targets.go +++ b/pkg/kluctl_project/targets.go @@ -336,7 +336,7 @@ func (c *LoadedKluctlProject) buildDynamicTarget(targetInfo *dynamicTargetInfo) if targetConfig.Args != nil { err = targetConfig.Args.NewIterator().IterateLeafs(func(it *uo.ObjectIterator) error { strValue := fmt.Sprintf("%v", it.Value()) - err := c.CheckDynamicArg(&target, it.JsonPath(), strValue) + err := c.CheckDynamicArg(&target, it.KeyPath().ToJsonPath(), strValue) if err != nil { return err } diff --git a/pkg/seal/secrets_loader.go b/pkg/seal/secrets_loader.go index fb863224c..ff0a13431 100644 --- a/pkg/seal/secrets_loader.go +++ b/pkg/seal/secrets_loader.go @@ -87,15 +87,15 @@ func (s *SecretsLoader) loadSecretsSystemEnvs(source *types.SecretSource) (*uo.U err := source.SystemEnvVars.NewIterator().IterateLeafs(func(it *uo.ObjectIterator) error { envName, ok := it.Value().(string) if !ok { - return fmt.Errorf("value at %s is not a string", it.JsonPath()) + return fmt.Errorf("value at %s is not a string", it.KeyPath().ToJsonPath()) } envValue, ok := os.LookupEnv(envName) if !ok { - return fmt.Errorf("environment variable %s not found for secret %s", envName, it.JsonPath()) + return fmt.Errorf("environment variable %s not found for secret %s", envName, it.KeyPath().ToJsonPath()) } err := secrets.SetNestedField(envValue, it.KeyPath()...) if err != nil { - return fmt.Errorf("failed to set secret %s: %w", it.JsonPath(), err) + return fmt.Errorf("failed to set secret %s: %w", it.KeyPath().ToJsonPath(), err) } return nil }) diff --git a/pkg/utils/uo/jsonpath.go b/pkg/utils/uo/jsonpath.go index b375aca7e..3843259f2 100644 --- a/pkg/utils/uo/jsonpath.go +++ b/pkg/utils/uo/jsonpath.go @@ -9,9 +9,11 @@ import ( var isSimpleIdentifier = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]+$`) -func KeyListToJsonPath(keys []interface{}) string { +type KeyPath []interface{} + +func (kl KeyPath) ToJsonPath() string { p := "" - for _, k := range keys { + for _, k := range kl { if i, ok := k.(int); ok { p = fmt.Sprintf("%s[%d]", p, i) } else if s, ok := k.(string); ok { @@ -62,8 +64,8 @@ func NewMyJsonPathMust(p string) *MyJsonPath { return j } -func (j *MyJsonPath) ListMatchingFields(o *UnstructuredObject) ([][]interface{}, error) { - var ret [][]interface{} +func (j *MyJsonPath) ListMatchingFields(o *UnstructuredObject) ([]KeyPath, error) { + var ret []KeyPath o = o.Clone() magic := struct{}{} diff --git a/pkg/utils/uo/nested_fields.go b/pkg/utils/uo/nested_fields.go index 0de90bab6..325c581f3 100644 --- a/pkg/utils/uo/nested_fields.go +++ b/pkg/utils/uo/nested_fields.go @@ -84,7 +84,7 @@ func (uo *UnstructuredObject) GetNestedString(keys ...interface{}) (string, bool } s, ok := v.(string) if !ok { - return "", false, fmt.Errorf("value at %s is not a string", KeyListToJsonPath(keys)) + return "", false, fmt.Errorf("value at %s is not a string", KeyPath(keys).ToJsonPath()) } return s, true, nil } @@ -102,7 +102,7 @@ func (uo *UnstructuredObject) GetNestedInt(keys ...interface{}) (int64, bool, er if i2, ok := v.(int); ok { i = int64(i2) } else { - return 0, false, fmt.Errorf("value at %s is not an int", KeyListToJsonPath(keys)) + return 0, false, fmt.Errorf("value at %s is not an int", KeyPath(keys).ToJsonPath()) } } return i, true, nil @@ -118,7 +118,7 @@ func (uo *UnstructuredObject) GetNestedList(keys ...interface{}) ([]interface{}, } l, ok := v.([]interface{}) if !ok { - return nil, false, fmt.Errorf("value at %s is not a slice", KeyListToJsonPath(keys)) + return nil, false, fmt.Errorf("value at %s is not a slice", KeyPath(keys).ToJsonPath()) } return l, true, nil } @@ -135,7 +135,7 @@ func (uo *UnstructuredObject) GetNestedStringList(keys ...interface{}) ([]string for i, x := range l { s, ok := x.(string) if !ok { - return nil, false, fmt.Errorf("value at index %s is not a slice of strings", KeyListToJsonPath(keys)) + return nil, false, fmt.Errorf("value at index %s is not a slice of strings", KeyPath(keys).ToJsonPath()) } ret[i] = s } @@ -209,13 +209,13 @@ func (uo *UnstructuredObject) GetNestedStringMapCopy(keys ...interface{}) (map[s } m, ok := v.(map[string]interface{}) if !ok { - return nil, false, fmt.Errorf("value at %s is not a map", KeyListToJsonPath(keys)) + return nil, false, fmt.Errorf("value at %s is not a map", KeyPath(keys).ToJsonPath()) } ret := make(map[string]string) for k, v := range m { s, ok := v.(string) if !ok { - return nil, false, fmt.Errorf("value at %s.%s is not a string", KeyListToJsonPath(keys), k) + return nil, false, fmt.Errorf("value at %s.%s is not a string", KeyPath(keys).ToJsonPath(), k) } ret[k] = s } diff --git a/pkg/utils/uo/object_iterator.go b/pkg/utils/uo/object_iterator.go index debb5c227..f1859aa95 100644 --- a/pkg/utils/uo/object_iterator.go +++ b/pkg/utils/uo/object_iterator.go @@ -3,7 +3,7 @@ package uo type ObjectIteratorFunc func(it *ObjectIterator) error type ObjectIterator struct { path []interface{} - keys []interface{} + keys KeyPath cb ObjectIteratorFunc } @@ -13,12 +13,12 @@ func NewObjectIterator(o map[string]interface{}) *ObjectIterator { } } -func (it *ObjectIterator) KeyPath() []interface{} { +func (it *ObjectIterator) KeyPath() KeyPath { return it.keys } -func (it *ObjectIterator) KeyPathCopy() []interface{} { - ret := make([]interface{}, len(it.keys)) +func (it *ObjectIterator) KeyPathCopy() KeyPath { + ret := make(KeyPath, len(it.keys)) copy(ret, it.keys) return ret } @@ -45,10 +45,6 @@ func (it *ObjectIterator) SetValue(v interface{}) error { return SetChild(it.Parent(), it.Key(), v) } -func (it *ObjectIterator) JsonPath() string { - return KeyListToJsonPath(it.keys) -} - func (it *ObjectIterator) IterateLeafs(cb ObjectIteratorFunc) error { it.cb = cb return it.iterateInterface() From ad4316a37cad9d6a0b62124e5b97def115508d21 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 12 May 2022 15:09:48 +0200 Subject: [PATCH 0809/2916] fix: Add missing dot in concatenated json path --- pkg/k8s/k8s_cluster.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index 160a0ad8b..3bbfe7a0c 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -384,8 +384,8 @@ func (k *K8sCluster) FixObjectForPatch(o *uo.UnstructuredObject) *uo.Unstructure fixContainer := func(p string) { fixPorts(p + ".ports") - fixStringType(p+"resources.limits", "cpu") - fixStringType(p+"resources.requests", "cpu") + fixStringType(p+".resources.limits", "cpu") + fixStringType(p+".resources.requests", "cpu") } fixContainers := func(p string) { From 487974f47eee61bb79e58cc23c5486b32e740818 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 12 May 2022 15:10:18 +0200 Subject: [PATCH 0810/2916] fix: Make MyJsonPath work with UnstructuredObject --- pkg/diff/managed_fields.go | 2 +- pkg/diff/normalize.go | 4 +-- pkg/k8s/k8s_cluster.go | 17 ++++++------ pkg/seal/secrets_loader_http.go | 2 +- pkg/utils/uo/jsonpath.go | 48 +++++++++++++++++++++++---------- 5 files changed, 47 insertions(+), 26 deletions(-) diff --git a/pkg/diff/managed_fields.go b/pkg/diff/managed_fields.go index 36200458d..8dd6ce197 100644 --- a/pkg/diff/managed_fields.go +++ b/pkg/diff/managed_fields.go @@ -219,7 +219,7 @@ func ResolveFieldManagerConflicts(local *uo.UnstructuredObject, remote *uo.Unstr if err != nil { return nil, nil, err } - err = j.Del(ret.Object) + err = j.Del(ret) if err != nil { return nil, nil, err } diff --git a/pkg/diff/normalize.go b/pkg/diff/normalize.go index f2599dc97..dd69cf073 100644 --- a/pkg/diff/normalize.go +++ b/pkg/diff/normalize.go @@ -164,7 +164,7 @@ func NormalizeObject(o_ *uo.UnstructuredObject, ignoreForDiffs []*types.IgnoreFo if err != nil { continue } - _ = jp.Del(o.Object) + _ = jp.Del(o) } } @@ -179,7 +179,7 @@ func NormalizeObject(o_ *uo.UnstructuredObject, ignoreForDiffs []*types.IgnoreFo if err != nil { continue } - _ = j.Del(o.Object) + _ = j.Del(o) } return o diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index 3bbfe7a0c..51fb487b9 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -352,14 +352,15 @@ func (k *K8sCluster) FixObjectForPatch(o *uo.UnstructuredObject) *uo.Unstructure return } - ports, found, _ := uo.NewMyJsonPathMust(p).GetFirstListOfMaps(o.Object) + ports, found, _ := uo.NewMyJsonPathMust(p).GetFirstListOfObjects(o) if !found { return } for _, port := range ports { - if _, ok := port["protocol"]; !ok { - port["protocol"] = "TCP" + _, ok, _ := port.GetNestedField("protocol") + if !ok { + _ = port.SetNestedField("TCP", "protocol") } } } @@ -368,17 +369,17 @@ func (k *K8sCluster) FixObjectForPatch(o *uo.UnstructuredObject) *uo.Unstructure if !needsTypeConversionFix { return } - d, found, _ := uo.NewMyJsonPathMust(p).GetFirstMap(o.Object) + d, found, _ := uo.NewMyJsonPathMust(p).GetFirstObject(o) if !found { return } - v, ok := d[k] + v, ok, _ := d.GetNestedField(k) if !ok { return } _, ok = v.(string) if !ok { - d[k] = fmt.Sprintf("%v", v) + _ = d.SetNestedField(fmt.Sprintf("%v", v), k) } } @@ -389,7 +390,7 @@ func (k *K8sCluster) FixObjectForPatch(o *uo.UnstructuredObject) *uo.Unstructure } fixContainers := func(p string) { - containers, found, _ := uo.NewMyJsonPathMust(p).GetFirstListOfMaps(o.Object) + containers, found, _ := uo.NewMyJsonPathMust(p).GetFirstListOfObjects(o) if !found { return } @@ -399,7 +400,7 @@ func (k *K8sCluster) FixObjectForPatch(o *uo.UnstructuredObject) *uo.Unstructure } fixLimits := func(p string) { - limits, found, _ := uo.NewMyJsonPathMust(p).GetFirstListOfMaps(o.Object) + limits, found, _ := uo.NewMyJsonPathMust(p).GetFirstListOfObjects(o) if !found { return } diff --git a/pkg/seal/secrets_loader_http.go b/pkg/seal/secrets_loader_http.go index 25454a746..da0bba85d 100644 --- a/pkg/seal/secrets_loader_http.go +++ b/pkg/seal/secrets_loader_http.go @@ -117,7 +117,7 @@ func (s *SecretsLoader) loadSecretsHttp(source *types.SecretSource) (*uo.Unstruc if err != nil { return nil, err } - x, ok := p.GetFirst(respObj) + x, ok := p.GetFirstFromAny(respObj) if !ok { return nil, fmt.Errorf("%s not found in result from http request %s", *source.Http.JsonPath, source.Http.Url.String()) } diff --git a/pkg/utils/uo/jsonpath.go b/pkg/utils/uo/jsonpath.go index 3843259f2..5263f8639 100644 --- a/pkg/utils/uo/jsonpath.go +++ b/pkg/utils/uo/jsonpath.go @@ -87,11 +87,11 @@ func (j *MyJsonPath) ListMatchingFields(o *UnstructuredObject) ([]KeyPath, error return ret, nil } -func (j *MyJsonPath) Get(o interface{}) []interface{} { - return j.exp.Get(o) +func (j *MyJsonPath) Get(o *UnstructuredObject) []interface{} { + return j.exp.Get(o.Object) } -func (j *MyJsonPath) GetFirst(o interface{}) (interface{}, bool) { +func (j *MyJsonPath) GetFirst(o *UnstructuredObject) (interface{}, bool) { l := j.Get(o) if len(l) == 0 { return nil, false @@ -99,30 +99,50 @@ func (j *MyJsonPath) GetFirst(o interface{}) (interface{}, bool) { return l[0], true } -func (j *MyJsonPath) GetFirstMap(o interface{}) (map[string]interface{}, bool, error) { - o, found := j.GetFirst(o) +func (j *MyJsonPath) GetFromAny(o any) []interface{} { + return j.exp.Get(o) +} + +func (j *MyJsonPath) GetFirstFromAny(o any) (interface{}, bool) { + l := j.GetFromAny(o) + if len(l) == 0 { + return nil, false + } + return l[0], true +} + +func (j *MyJsonPath) GetFirstObject(o *UnstructuredObject) (*UnstructuredObject, bool, error) { + x, found := j.GetFirst(o) if !found { return nil, false, nil } - m, ok := o.(map[string]interface{}) + m, ok := x.(map[string]interface{}) if !ok { return nil, false, fmt.Errorf("child is not a map") } - return m, true, nil + return FromMap(m), true, nil } -func (j *MyJsonPath) GetFirstListOfMaps(o interface{}) ([]map[string]interface{}, bool, error) { - o, found := j.GetFirst(o) +func (j *MyJsonPath) GetFirstListOfObjects(o *UnstructuredObject) ([]*UnstructuredObject, bool, error) { + x, found := j.GetFirst(o) if !found { return nil, false, nil } - m, ok := o.([]map[string]interface{}) + l, ok := x.([]interface{}) if !ok { - return nil, false, fmt.Errorf("child is not a list of maps") + return nil, false, fmt.Errorf("child is not a list") + } + var ret []*UnstructuredObject + for _, x := range l { + m, ok := x.(map[string]interface{}) + if !ok { + return nil, false, fmt.Errorf("child is not a list of maps") + } + ret = append(ret, FromMap(m)) } - return m, true, nil + return ret, true, nil } -func (j *MyJsonPath) Del(o interface{}) error { - return j.exp.Del(o) +func (j *MyJsonPath) Del(o *UnstructuredObject) error { + return j.exp.Del(o.Object) } From a50be74a1a484cd1f4bfca175f3d805b6af16b72 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 12 May 2022 15:18:08 +0200 Subject: [PATCH 0811/2916] fix: Fix crash in loadSecretsHttp in case host is not reachable --- pkg/seal/secrets_loader_http.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/seal/secrets_loader_http.go b/pkg/seal/secrets_loader_http.go index da0bba85d..01582a9d1 100644 --- a/pkg/seal/secrets_loader_http.go +++ b/pkg/seal/secrets_loader_http.go @@ -67,7 +67,7 @@ func (s *SecretsLoader) doHttp(httpSource *types.SecretSourceHttp, username stri func (s *SecretsLoader) loadSecretsHttp(source *types.SecretSource) (*uo.UnstructuredObject, error) { resp, respBody, err := s.doHttp(source.Http, "", "") - if err != nil { + if err != nil && resp != nil && resp.StatusCode == http.StatusUnauthorized { chgs := challenge.ResponseChallenges(resp) if len(chgs) == 0 { return nil, err @@ -100,6 +100,8 @@ func (s *SecretsLoader) loadSecretsHttp(source *types.SecretSource) (*uo.Unstruc if err != nil { return nil, err } + } else if err != nil { + return nil, err } var respObj interface{} From e814c6fa86f8bf27d4b698c9e4d813ca308558b5 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 12 May 2022 16:48:17 +0200 Subject: [PATCH 0812/2916] fix: Add workaround to fix progress printing when asking for continuation --- cmd/kluctl/commands/cmd_deploy.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmd/kluctl/commands/cmd_deploy.go b/cmd/kluctl/commands/cmd_deploy.go index a0db7c000..1e514ca9b 100644 --- a/cmd/kluctl/commands/cmd_deploy.go +++ b/cmd/kluctl/commands/cmd_deploy.go @@ -6,6 +6,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/deployment/commands" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" + "time" ) type deployCmd struct { @@ -77,6 +78,9 @@ func (cmd *deployCmd) runCmdDeploy(ctx *commandCtx) error { } func (cmd *deployCmd) diffResultCb(diffResult *types.CommandResult) error { + // workaround to ensure that progress has been completely written/updated + time.Sleep(130 * time.Millisecond) + err := outputCommandResult(nil, diffResult) if err != nil { return err From 5b7949b2246b25340822126d6d61b7649167d660 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 12 May 2022 16:48:43 +0200 Subject: [PATCH 0813/2916] feat: Make target.name the default output pattern for sealed secrets --- cmd/kluctl/commands/cmd_seal.go | 9 ++++++++- pkg/deployment/commands/seal.go | 14 ++++++-------- pkg/deployment/deployment_item.go | 6 ++---- pkg/deployment/deployment_project.go | 19 +++++++++++-------- pkg/kluctl_project/target_context.go | 3 +++ 5 files changed, 30 insertions(+), 21 deletions(-) diff --git a/cmd/kluctl/commands/cmd_seal.go b/cmd/kluctl/commands/cmd_seal.go index b7a4c01f4..5cd3d78d3 100644 --- a/cmd/kluctl/commands/cmd_seal.go +++ b/cmd/kluctl/commands/cmd_seal.go @@ -115,7 +115,14 @@ func (cmd *sealCmd) runCmdSealForTarget(ctx context.Context, p *kluctl_project.L return doFail(err) } - cmd2 := commands.NewSealCommand(ctx.targetCtx.DeploymentCollection) + outputPattern := targetName + if ctx.targetCtx.DeploymentProject.Config.SealedSecrets != nil && ctx.targetCtx.DeploymentProject.Config.SealedSecrets.OutputPattern != nil { + // the outputPattern is rendered already at this point, meaning that for example + // '{{ cluster.name }}/{{ target.name }}' will already be rendered to 'my-cluster/my-target' + outputPattern = *ctx.targetCtx.DeploymentProject.Config.SealedSecrets.OutputPattern + } + + cmd2 := commands.NewSealCommand(ctx.targetCtx.DeploymentCollection, outputPattern) err = cmd2.Run(sealer) if err != nil { diff --git a/pkg/deployment/commands/seal.go b/pkg/deployment/commands/seal.go index 69110cf0d..0397133ca 100644 --- a/pkg/deployment/commands/seal.go +++ b/pkg/deployment/commands/seal.go @@ -11,20 +11,18 @@ import ( ) type SealCommand struct { - c *deployment.DeploymentCollection + c *deployment.DeploymentCollection + outputPattern string } -func NewSealCommand(c *deployment.DeploymentCollection) *SealCommand { +func NewSealCommand(c *deployment.DeploymentCollection, outputPattern string) *SealCommand { return &SealCommand{ - c: c, + c: c, + outputPattern: outputPattern, } } func (cmd *SealCommand) Run(sealer *seal.Sealer) error { - if cmd.c.Project.Config.SealedSecrets == nil || cmd.c.Project.Config.SealedSecrets.OutputPattern == nil { - return fmt.Errorf("sealedSecrets.outputPattern is not defined") - } - err := filepath.WalkDir(cmd.c.RenderDir, func(p string, d fs.DirEntry, err error) error { if !strings.HasSuffix(p, deployment.SealmeExt) { return nil @@ -34,7 +32,7 @@ func (cmd *SealCommand) Run(sealer *seal.Sealer) error { if err != nil { return err } - relTargetFile := filepath.Join(filepath.Dir(relPath), *cmd.c.Project.Config.SealedSecrets.OutputPattern, filepath.Base(p)) + relTargetFile := filepath.Join(filepath.Dir(relPath), cmd.outputPattern, filepath.Base(p)) targetFile, err := securejoin.SecureJoin(cmd.c.Project.SealedSecretsDir, relTargetFile) if err != nil { return err diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 7f4912747..e4ccacd3a 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -230,11 +230,9 @@ func (di *DeploymentItem) resolveSealedSecrets(subdir string) error { fname := filepath.Base(p) baseError := fmt.Sprintf("failed to resolve SealedSecret %s", filepath.Clean(filepath.Join(di.Project.dir, resource))) - if sealedSecretsDir == nil { - return fmt.Errorf("%s. Sealed secrets dir could not be determined", baseError) - } + // ensure we're not leaving the .sealed-secrets dir - sourcePath, err := securejoin.SecureJoin(baseSourcePath, filepath.Join(di.relRenderedDir, subdir, relDir, *sealedSecretsDir, fname)) + sourcePath, err := securejoin.SecureJoin(baseSourcePath, filepath.Join(di.relRenderedDir, subdir, relDir, sealedSecretsDir, fname)) if err != nil { return err } diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go index f0c0cedd3..3446c5ca4 100644 --- a/pkg/deployment/deployment_project.go +++ b/pkg/deployment/deployment_project.go @@ -20,9 +20,11 @@ var warnOnce utils.OnceByKey type DeploymentProject struct { ctx context.Context - VarsCtx *jinja2.VarsCtx - dir string - SealedSecretsDir string + VarsCtx *jinja2.VarsCtx + dir string + + SealedSecretsDir string + DefaultSealedSecretsOutputPattern string Config types.DeploymentProjectConfig @@ -200,7 +202,8 @@ func (p *DeploymentProject) loadIncludes(k *k8s.K8sCluster) error { return err } - newProject, err := NewDeploymentProject(p.ctx, k, varsCtx, incDir, p.SealedSecretsDir, p) + newProject, err := NewDeploymentProject(p.ctx, k, varsCtx, incDir, + p.SealedSecretsDir, p) if err != nil { return err } @@ -211,12 +214,12 @@ func (p *DeploymentProject) loadIncludes(k *k8s.K8sCluster) error { return nil } -func (p *DeploymentProject) getSealedSecretsDir() *string { +func (p *DeploymentProject) getSealedSecretsDir() string { root := p.getRootProject() - if root.Config.SealedSecrets == nil { - return nil + if root.Config.SealedSecrets == nil || root.Config.SealedSecrets.OutputPattern == nil { + return root.DefaultSealedSecretsOutputPattern } - return root.Config.SealedSecrets.OutputPattern + return *root.Config.SealedSecrets.OutputPattern } func (p *DeploymentProject) getRootProject() *DeploymentProject { diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index 1d6398ae0..3d3bd22f4 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -115,6 +115,9 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, clientConfig if err != nil { return nil, err } + + d.DefaultSealedSecretsOutputPattern = targetName + c, err := deployment.NewDeploymentCollection(ctx, d, images, inclusion, renderOutputDir, forSeal) if err != nil { return nil, err From 41260f4e8d5f5ec98002fc934bacd3139d4afe60 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 12 May 2022 16:48:55 +0200 Subject: [PATCH 0814/2916] tests: Fix e2e tests compilation --- e2e/utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/utils.go b/e2e/utils.go index e52c131ac..97df8657c 100644 --- a/e2e/utils.go +++ b/e2e/utils.go @@ -96,7 +96,7 @@ func assertNestedFieldEquals(t *testing.T, o *uo.UnstructuredObject, expected in t.Fatal(err) } if !ok { - t.Fatalf("field %s not found in object", uo.KeyListToJsonPath(keys)) + t.Fatalf("field %s not found in object", uo.KeyPath(keys).ToJsonPath()) } if !reflect.DeepEqual(v, expected) { t.Fatalf("%v != %v", v, expected) From 04d547f67eb60712036303fc69388eaa82b092d8 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 17 May 2022 18:21:02 +0200 Subject: [PATCH 0815/2916] feat: Make cluster per target optional and default to the current context --- cmd/kluctl/commands/cmd_archive.go | 2 +- cmd/kluctl/commands/cmd_list_targets.go | 2 +- cmd/kluctl/commands/cmd_seal.go | 6 +-- cmd/kluctl/commands/completion.go | 2 +- cmd/kluctl/commands/utils.go | 42 ++++++++++++++----- pkg/kluctl_project/project.go | 4 -- pkg/kluctl_project/project_load.go | 4 ++ pkg/kluctl_project/target_context.go | 56 +++++++++++++++++++------ pkg/kluctl_project/targets.go | 2 +- pkg/types/kluctl_project.go | 2 +- 10 files changed, 88 insertions(+), 34 deletions(-) diff --git a/cmd/kluctl/commands/cmd_archive.go b/cmd/kluctl/commands/cmd_archive.go index a6a2f349d..d5c22af07 100644 --- a/cmd/kluctl/commands/cmd_archive.go +++ b/cmd/kluctl/commands/cmd_archive.go @@ -17,7 +17,7 @@ func (cmd *archiveCmd) Help() string { } func (cmd *archiveCmd) Run() error { - return withKluctlProjectFromArgs(cmd.ProjectFlags, true, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { + return withKluctlProjectFromArgs(cmd.ProjectFlags, true, false, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { return p.WriteArchive(cmd.OutputArchive, cmd.ProjectFlags.OutputMetadata == "") }) } diff --git a/cmd/kluctl/commands/cmd_list_targets.go b/cmd/kluctl/commands/cmd_list_targets.go index 64921e6f7..f8a6b5626 100644 --- a/cmd/kluctl/commands/cmd_list_targets.go +++ b/cmd/kluctl/commands/cmd_list_targets.go @@ -17,7 +17,7 @@ func (cmd *listTargetsCmd) Help() string { } func (cmd *listTargetsCmd) Run() error { - return withKluctlProjectFromArgs(cmd.ProjectFlags, true, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { + return withKluctlProjectFromArgs(cmd.ProjectFlags, true, false, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { var result []*types.Target for _, t := range p.DynamicTargets { result = append(result, t.Target) diff --git a/cmd/kluctl/commands/cmd_seal.go b/cmd/kluctl/commands/cmd_seal.go index 5cd3d78d3..ca2a93981 100644 --- a/cmd/kluctl/commands/cmd_seal.go +++ b/cmd/kluctl/commands/cmd_seal.go @@ -106,7 +106,7 @@ func (cmd *sealCmd) runCmdSealForTarget(ctx context.Context, p *kluctl_project.L } } - clusterConfig, err := p.LoadClusterConfig(ctx.targetCtx.Target.Cluster) + clusterConfig, _, err := p.LoadClusterConfig(ctx.targetCtx.Target.Cluster) if err != nil { return doFail(err) } @@ -134,7 +134,7 @@ func (cmd *sealCmd) runCmdSealForTarget(ctx context.Context, p *kluctl_project.L } func (cmd *sealCmd) Run() error { - return withKluctlProjectFromArgs(cmd.ProjectFlags, true, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { + return withKluctlProjectFromArgs(cmd.ProjectFlags, true, false, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { hadError := false secretsLoader := seal.NewSecretsLoader(p, cmd.SecretsDir) @@ -145,7 +145,7 @@ func (cmd *sealCmd) Run() error { if cmd.Target != "" && cmd.Target != target.Target.Name { continue } - if cmd.Cluster != "" && cmd.Cluster != target.Target.Cluster { + if cmd.Cluster != "" && target.Target.Cluster != nil && cmd.Cluster != *target.Target.Cluster { continue } if target.Target.SealingConfig == nil { diff --git a/cmd/kluctl/commands/completion.go b/cmd/kluctl/commands/completion.go index 1e2b383a4..a541ab565 100644 --- a/cmd/kluctl/commands/completion.go +++ b/cmd/kluctl/commands/completion.go @@ -51,7 +51,7 @@ func RegisterFlagCompletionFuncs(cmdStruct interface{}, ccmd *cobra.Command) err func withProjectForCompletion(projectArgs *args.ProjectFlags, cb func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error) error { // let's not update git caches too often projectArgs.GitCacheUpdateInterval = time.Second * 60 - return withKluctlProjectFromArgs(*projectArgs, false, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { + return withKluctlProjectFromArgs(*projectArgs, false, true, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { return cb(ctx, p) }) } diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index c6f69df50..c283ea0de 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -17,10 +17,11 @@ import ( "io/ioutil" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/tools/clientcmd/api" "os" ) -func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, strictTemplates bool, cb func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error) error { +func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, strictTemplates bool, forCompletion bool, cb func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error) error { var url *git_url.GitUrl if projectFlags.ProjectUrl != "" { var err error @@ -68,6 +69,7 @@ func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, strictTemplates b AllowGitClone: projectFlags.FromArchive == "", GitAuthProviders: auth.NewDefaultAuthProviders(), GitUpdateInterval: projectFlags.GitCacheUpdateInterval, + ClientConfigGetter: clientConfigGetter(forCompletion), } ctx, cancel := context.WithTimeout(cliCtx, projectFlags.Timeout) @@ -110,7 +112,7 @@ type commandCtx struct { } func withProjectCommandContext(args projectTargetCommandArgs, cb func(ctx *commandCtx) error) error { - return withKluctlProjectFromArgs(args.projectFlags, true, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { + return withKluctlProjectFromArgs(args.projectFlags, true, false, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { return withProjectTargetCommandContext(ctx, args, p, cb) }) } @@ -153,16 +155,12 @@ func withProjectTargetCommandContext(ctx context.Context, args projectTargetComm renderOutputDir = tmpDir } - clientConfigGetter := func(context string) (*rest.Config, error) { - if args.forCompletion { - return nil, nil - } - configLoadingRules := clientcmd.NewDefaultClientConfigLoadingRules() - configOverrides := &clientcmd.ConfigOverrides{CurrentContext: context} - return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(configLoadingRules, configOverrides).ClientConfig() + var clusterName *string + if args.projectFlags.Cluster != "" { + clusterName = &args.projectFlags.Cluster } - targetCtx, err := p.NewTargetContext(ctx, clientConfigGetter, args.targetFlags.Target, args.projectFlags.Cluster, + targetCtx, err := p.NewTargetContext(ctx, args.targetFlags.Target, clusterName, args.dryRunArgs == nil || args.dryRunArgs.DryRun || args.forCompletion, optionArgs, args.forSeal, images, inclusion, renderOutputDir) @@ -185,3 +183,27 @@ func withProjectTargetCommandContext(ctx context.Context, args projectTargetComm return cb(cmdCtx) } + +func clientConfigGetter(forCompletion bool) func(context *string) (*rest.Config, *api.Config, error) { + return func(context *string) (*rest.Config, *api.Config, error) { + if forCompletion { + return nil, nil, nil + } + + configLoadingRules := clientcmd.NewDefaultClientConfigLoadingRules() + configOverrides := &clientcmd.ConfigOverrides{} + if context != nil { + configOverrides.CurrentContext = *context + } + clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(configLoadingRules, configOverrides) + rawConfig, err := clientConfig.RawConfig() + if err != nil { + return nil, nil, err + } + restConfig, err := clientConfig.ClientConfig() + if err != nil { + return nil, nil, err + } + return restConfig, &rawConfig, nil + } +} diff --git a/pkg/kluctl_project/project.go b/pkg/kluctl_project/project.go index 3b0d0efff..6592651d2 100644 --- a/pkg/kluctl_project/project.go +++ b/pkg/kluctl_project/project.go @@ -59,10 +59,6 @@ func (c *LoadedKluctlProject) FindDynamicTarget(name string) (*types2.DynamicTar return nil, fmt.Errorf("target %s not existent in kluctl project config", name) } -func (c *LoadedKluctlProject) LoadClusterConfig(clusterName string) (*types2.ClusterConfig, error) { - return types2.LoadClusterConfig(c.ClustersDir, clusterName) -} - func (c *LoadedKluctlProject) CheckDynamicArg(target *types2.Target, argName string, argValue string) error { var dynArg *types2.DynamicArg for _, x := range target.DynamicArgs { diff --git a/pkg/kluctl_project/project_load.go b/pkg/kluctl_project/project_load.go index bd99e8291..a5b9ca83e 100644 --- a/pkg/kluctl_project/project_load.go +++ b/pkg/kluctl_project/project_load.go @@ -10,6 +10,8 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/yaml" "io/ioutil" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd/api" "os" "path/filepath" "time" @@ -31,6 +33,8 @@ type LoadKluctlProjectArgs struct { GitAuthProviders *auth2.GitAuthProviders GitUpdateInterval time.Duration + + ClientConfigGetter func(context *string) (*rest.Config, *api.Config, error) } type gitProjectInfo struct { diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index 3d3bd22f4..7e647c2d2 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -11,6 +11,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd/api" "path/filepath" ) @@ -23,7 +24,7 @@ type TargetContext struct { DeploymentCollection *deployment.DeploymentCollection } -func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, clientConfigGetter func(context string) (*rest.Config, error), targetName string, clusterName string, dryRun bool, args map[string]string, forSeal bool, images *deployment.Images, inclusion *utils.Inclusion, renderOutputDir string) (*TargetContext, error) { +func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName string, clusterName *string, dryRun bool, args map[string]string, forSeal bool, images *deployment.Images, inclusion *utils.Inclusion, renderOutputDir string) (*TargetContext, error) { deploymentDir, err := filepath.Abs(p.DeploymentDir) if err != nil { return nil, err @@ -42,26 +43,18 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, clientConfig } } - if clusterName == "" { - if target == nil { - return nil, fmt.Errorf("you must specify an existing --cluster when not providing a --target") - } + if clusterName == nil && target != nil { clusterName = target.Cluster } - clusterConfig, err := p.LoadClusterConfig(clusterName) - if err != nil { - return nil, err - } - - clientConfig, err := clientConfigGetter(clusterConfig.Cluster.Context) + clusterConfig, clientConfig, err := p.LoadClusterConfig(clusterName) if err != nil { return nil, err } var k *k8s.K8sCluster if clientConfig != nil { - s := status.Start(ctx, "Initializing k8s client") + s := status.Start(ctx, fmt.Sprintf("Initializing k8s client for context %s", clusterConfig.Cluster.Context)) k, err = k8s.NewK8sCluster(ctx, clientConfig, dryRun) if err != nil { s.Failed() @@ -134,3 +127,42 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, clientConfig return targetCtx, nil } + +func (p *LoadedKluctlProject) LoadClusterConfig(clusterName *string) (*types.ClusterConfig, *rest.Config, error) { + var err error + var clusterConfig *types.ClusterConfig + + if clusterName != nil { + clusterConfig, err = types.LoadClusterConfig(p.ClustersDir, *clusterName) + if err != nil { + return nil, nil, err + } + } + + var clientConfig *rest.Config + if clusterConfig != nil { + clientConfig, _, err = p.loadArgs.ClientConfigGetter(&clusterConfig.Cluster.Context) + if err != nil { + return nil, nil, err + } + } else { + var rawConfig *api.Config + clientConfig, rawConfig, err = p.loadArgs.ClientConfigGetter(nil) + if err != nil { + return nil, nil, err + } + ctx, ok := rawConfig.Contexts[rawConfig.CurrentContext] + if !ok { + return nil, nil, fmt.Errorf("context %s not found", rawConfig.CurrentContext) + } + clusterConfig = &types.ClusterConfig{ + Cluster: &types.ClusterConfig2{ + Name: ctx.Cluster, + Context: rawConfig.CurrentContext, + Vars: uo.New(), + }, + } + } + + return clusterConfig, clientConfig, nil +} diff --git a/pkg/kluctl_project/targets.go b/pkg/kluctl_project/targets.go index 8e139fa64..c911cb5a8 100644 --- a/pkg/kluctl_project/targets.go +++ b/pkg/kluctl_project/targets.go @@ -89,7 +89,7 @@ func (c *LoadedKluctlProject) renderTarget(target *types.Target) (*types.Target, return nil, err } - cc, err := types.LoadClusterConfig(c.ClustersDir, target.Cluster) + cc, _, err := c.LoadClusterConfig(target.Cluster) if err == nil { err = varsCtx.UpdateChildFromStruct("cluster", cc.Cluster) if err != nil { diff --git a/pkg/types/kluctl_project.go b/pkg/types/kluctl_project.go index b0ea0257f..ad2982be6 100644 --- a/pkg/types/kluctl_project.go +++ b/pkg/types/kluctl_project.go @@ -29,7 +29,7 @@ type SealingConfig struct { type Target struct { Name string `yaml:"name" validate:"required"` - Cluster string `yaml:"cluster" validate:"required"` + Cluster *string `yaml:"cluster,omitempty"` Args *uo.UnstructuredObject `yaml:"args,omitempty"` DynamicArgs []DynamicArg `yaml:"dynamicArgs,omitempty"` TargetConfig *ExternalTargetConfig `yaml:"targetConfig,omitempty"` From 1389ac304319da892b1a0580669feba1d937d4be Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 17 May 2022 18:27:54 +0200 Subject: [PATCH 0816/2916] fix: Don't swallow sealing errors --- cmd/kluctl/commands/cmd_seal.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/kluctl/commands/cmd_seal.go b/cmd/kluctl/commands/cmd_seal.go index ca2a93981..45446a91a 100644 --- a/cmd/kluctl/commands/cmd_seal.go +++ b/cmd/kluctl/commands/cmd_seal.go @@ -173,6 +173,7 @@ func (cmd *sealCmd) Run() error { err := cmd.runCmdSealForTarget(ctx, p, sealTarget.Name, secretsLoader) if err != nil { hadError = true + status.Error(ctx, err.Error()) } } if hadError { From 1ca9f460ca4a00c5b8022e6eea0621da71f0dd9b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 18 May 2022 10:26:55 +0200 Subject: [PATCH 0817/2916] refactor: Introduce MirroredGitRepoCollection for git cache handling --- pkg/git/repo_collection.go | 90 ++++++++++++++++++++++++++++++ pkg/kluctl_project/git.go | 55 +++--------------- pkg/kluctl_project/load.go | 5 +- pkg/kluctl_project/project.go | 4 +- pkg/kluctl_project/project_load.go | 8 +-- pkg/kluctl_project/targets.go | 17 ++---- 6 files changed, 111 insertions(+), 68 deletions(-) create mode 100644 pkg/git/repo_collection.go diff --git a/pkg/git/repo_collection.go b/pkg/git/repo_collection.go new file mode 100644 index 000000000..fbdf14ed6 --- /dev/null +++ b/pkg/git/repo_collection.go @@ -0,0 +1,90 @@ +package git + +import ( + "context" + "fmt" + "github.com/kluctl/kluctl/v2/pkg/git/auth" + git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" + "sync" + "time" +) + +type MirroredGitRepoCollection struct { + ctx context.Context + authProviders *auth.GitAuthProviders + updateInterval time.Duration + repos map[string]*entry + mutex sync.Mutex +} + +type entry struct { + mr *MirroredGitRepo + updateMutex sync.Mutex +} + +func NewMirroredGitRepoCollection(ctx context.Context, authProviders *auth.GitAuthProviders, updateInterval time.Duration) *MirroredGitRepoCollection { + return &MirroredGitRepoCollection{ + ctx: ctx, + authProviders: authProviders, + updateInterval: updateInterval, + repos: map[string]*entry{}, + } +} + +func (g *MirroredGitRepoCollection) UnlockAll() { + g.mutex.Lock() + defer g.mutex.Unlock() + + for _, e := range g.repos { + _ = e.mr.Unlock() + } +} + +func (g *MirroredGitRepoCollection) GetMirroredGitRepo(url git_url.GitUrl, allowCreate bool, lockRepo bool, update bool) (*MirroredGitRepo, error) { + e, err := func() (*entry, error) { + g.mutex.Lock() + defer g.mutex.Unlock() + + e, ok := g.repos[url.NormalizedRepoKey()] + if !ok { + if !allowCreate { + return nil, fmt.Errorf("git repo %s not found", url.NormalizedRepoKey()) + } + mr, err := NewMirroredGitRepo(g.ctx, url) + if err != nil { + return nil, err + } + e = &entry{ + mr: mr, + } + g.repos[url.NormalizedRepoKey()] = e + + if lockRepo { + err = e.mr.Lock() + if err != nil { + return nil, err + } + } + } + return e, nil + }() + if err != nil { + return nil, err + } + + e.updateMutex.Lock() + defer e.updateMutex.Unlock() + + if update && !e.mr.HasUpdated() { + if time.Now().Sub(e.mr.LastUpdateTime()) <= g.updateInterval { + e.mr.SetUpdated(true) + } else { + err = e.mr.Update(g.authProviders) + if err != nil { + return nil, err + } + } + } + + return e.mr, nil +} diff --git a/pkg/kluctl_project/git.go b/pkg/kluctl_project/git.go index 1be037384..b64c6ebb7 100644 --- a/pkg/kluctl_project/git.go +++ b/pkg/kluctl_project/git.go @@ -12,20 +12,8 @@ import ( "sort" "strings" "sync" - "time" ) -func (c *LoadedKluctlProject) updateGitCache(mr *git.MirroredGitRepo) error { - if mr.HasUpdated() { - return nil - } - if time.Now().Sub(mr.LastUpdateTime()) <= c.loadArgs.GitUpdateInterval { - mr.SetUpdated(true) - return nil - } - return mr.Update(c.loadArgs.GitAuthProviders) -} - func (c *LoadedKluctlProject) updateGitCaches() error { var waitGroup sync.WaitGroup var firstError error @@ -39,26 +27,12 @@ func (c *LoadedKluctlProject) updateGitCaches() error { } } - doUpdateRepo := func(repo *git.MirroredGitRepo) error { - return repo.WithLock(func() error { - return c.updateGitCache(repo) - }) - } doUpdateGitProject := func(u git_url.GitUrl) error { - mr, ok := c.mirroredRepos[u.NormalizedRepoKey()] - if ok { - return nil - } - mr, err := git.NewMirroredGitRepo(c.ctx, u) - if err != nil { - return err - } - c.mirroredRepos[u.NormalizedRepoKey()] = mr - waitGroup.Add(1) go func() { defer waitGroup.Done() - err := doUpdateRepo(mr) + + _, err := c.grc.GetMirroredGitRepo(u, true, true, true) if err != nil { doError(fmt.Errorf("failed to update git project %v: %v", u.String(), err)) } @@ -107,33 +81,18 @@ func (c *LoadedKluctlProject) updateGitCaches() error { return firstError } -func (c *LoadedKluctlProject) cloneGitProject(gitProject *types2.GitProject, targetDir string, doLock bool) (info git.GitRepoInfo, err error) { +func (c *LoadedKluctlProject) cloneGitProject(gitProject *types2.GitProject, targetDir string) (info git.GitRepoInfo, err error) { err = os.MkdirAll(filepath.Join(c.TmpDir, "git"), 0o700) if err != nil { return } - mr, ok := c.mirroredRepos[gitProject.Url.NormalizedRepoKey()] - if !ok { - mr, err = git.NewMirroredGitRepo(c.ctx, gitProject.Url) - if err != nil { - return - } - c.mirroredRepos[gitProject.Url.NormalizedRepoKey()] = mr - err = mr.Lock() - if err != nil { - return - } - defer mr.Unlock() + mr, err := c.grc.GetMirroredGitRepo(gitProject.Url, true, true, true) + if err != nil { + return git.GitRepoInfo{}, err } - err = mr.MaybeWithLock(doLock, func() error { - err := c.updateGitCache(mr) - if err != nil { - return err - } - return mr.CloneProject(gitProject.Ref, targetDir) - }) + err = mr.CloneProject(gitProject.Ref, targetDir) if err != nil { return } diff --git a/pkg/kluctl_project/load.go b/pkg/kluctl_project/load.go index 398a6c958..5938483bd 100644 --- a/pkg/kluctl_project/load.go +++ b/pkg/kluctl_project/load.go @@ -9,14 +9,17 @@ import ( ) func LoadKluctlProject(ctx context.Context, args LoadKluctlProjectArgs, tmpDir string, j2 *jinja2.Jinja2) (*LoadedKluctlProject, error) { + grc := git.NewMirroredGitRepoCollection(ctx, args.GitAuthProviders, args.GitUpdateInterval) + defer grc.UnlockAll() + p := &LoadedKluctlProject{ ctx: ctx, loadArgs: args, TmpDir: tmpDir, J2: j2, + grc: grc, involvedRepos: map[string][]types.InvolvedRepo{}, - mirroredRepos: map[string]*git.MirroredGitRepo{}, } if args.FromArchive != "" { diff --git a/pkg/kluctl_project/project.go b/pkg/kluctl_project/project.go index 6592651d2..5196cc387 100644 --- a/pkg/kluctl_project/project.go +++ b/pkg/kluctl_project/project.go @@ -25,12 +25,12 @@ type LoadedKluctlProject struct { sealedSecretsDir string involvedRepos map[string][]types2.InvolvedRepo - mirroredRepos map[string]*git.MirroredGitRepo Config types2.KluctlProject DynamicTargets []*types2.DynamicTarget - J2 *jinja2.Jinja2 + J2 *jinja2.Jinja2 + grc *git.MirroredGitRepoCollection } func (c *LoadedKluctlProject) GetMetadata() *types2.ProjectMetadata { diff --git a/pkg/kluctl_project/project_load.go b/pkg/kluctl_project/project_load.go index a5b9ca83e..c1d9e3be9 100644 --- a/pkg/kluctl_project/project_load.go +++ b/pkg/kluctl_project/project_load.go @@ -62,7 +62,7 @@ func (c *LoadedKluctlProject) localProject(dir string) gitProjectInfo { } } -func (c *LoadedKluctlProject) loadGitProject(gitProject *types2.GitProject, defaultSubDir string, doLock bool, doAddInvolvedRepo bool) (ret gitProjectInfo, err error) { +func (c *LoadedKluctlProject) loadGitProject(gitProject *types2.GitProject, defaultSubDir string, doAddInvolvedRepo bool) (ret gitProjectInfo, err error) { cloneDir, err := c.buildCloneDir(gitProject.Url, gitProject.Ref) if err != nil { return @@ -85,7 +85,7 @@ func (c *LoadedKluctlProject) loadGitProject(gitProject *types2.GitProject, defa } var ri git.GitRepoInfo - ri, err = c.cloneGitProject(gitProject, cloneDir, doLock) + ri, err = c.cloneGitProject(gitProject, cloneDir) if err != nil { return } @@ -136,7 +136,7 @@ func (c *LoadedKluctlProject) loadExternalProject(ep *types2.ExternalProject, de if ep.Project != nil { // pointing to an actual external project, so let's try to clone it - return c.loadGitProject(ep.Project, defaultGitSubDir, true, true) + return c.loadGitProject(ep.Project, defaultGitSubDir, true) } // ExternalProject was provided but without an external repo url, so point into the kluctl project. @@ -164,7 +164,7 @@ func (c *LoadedKluctlProject) loadKluctlProject() error { gi, err := c.loadGitProject(&types2.GitProject{ Url: *c.loadArgs.ProjectUrl, Ref: c.loadArgs.ProjectRef, - }, "", true, true) + }, "", true) if err != nil { return err } diff --git a/pkg/kluctl_project/targets.go b/pkg/kluctl_project/targets.go index c911cb5a8..bf43b50b9 100644 --- a/pkg/kluctl_project/targets.go +++ b/pkg/kluctl_project/targets.go @@ -134,9 +134,9 @@ func (c *LoadedKluctlProject) prepareDynamicTargetsSimple(baseTarget *types.Targ } func (c *LoadedKluctlProject) prepareDynamicTargetsExternal(baseTarget *types.Target) ([]*dynamicTargetInfo, error) { - mr, ok := c.mirroredRepos[baseTarget.TargetConfig.Project.Url.NormalizedRepoKey()] - if !ok { - return nil, fmt.Errorf("repo not found in mirroredRepos, this is unexpected and probably a bug") + mr, err := c.grc.GetMirroredGitRepo(baseTarget.TargetConfig.Project.Url, true, true, true) + if err != nil { + return nil, err } if baseTarget.TargetConfig.Ref != nil && baseTarget.TargetConfig.RefPattern != nil { @@ -224,15 +224,6 @@ func (c *LoadedKluctlProject) cloneDynamicTargets(dynamicTargets []*dynamicTarge wp := utils.NewDebuggerAwareWorkerPool(8) defer wp.StopWait(false) - // lock all involved repos first - for _, mr := range c.mirroredRepos { - err := mr.Lock() - if err != nil { - return err - } - defer mr.Unlock() - } - uniqueClones := make(map[string]interface{}) var mutex sync.Mutex @@ -251,7 +242,7 @@ func (c *LoadedKluctlProject) cloneDynamicTargets(dynamicTargets []*dynamicTarge gitProject := *targetInfo.gitProject gitProject.Ref = *targetInfo.ref - gi, err := c.loadGitProject(&gitProject, "", false, false) + gi, err := c.loadGitProject(&gitProject, "", false) mutex.Lock() defer mutex.Unlock() if err != nil { From 2c2eb04d22306915586e9530aaa743e08f59d8ef Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 18 May 2022 11:36:50 +0200 Subject: [PATCH 0818/2916] fix: Override CurrentContext in api.Config as it is not done by RawConfig() --- cmd/kluctl/commands/utils.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index c283ea0de..a7ad73631 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -200,6 +200,9 @@ func clientConfigGetter(forCompletion bool) func(context *string) (*rest.Config, if err != nil { return nil, nil, err } + if context != nil { + rawConfig.CurrentContext = *context + } restConfig, err := clientConfig.ClientConfig() if err != nil { return nil, nil, err From d5f79f7ff004e2ecce7064fb265902d57fb04ae6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 18 May 2022 11:38:39 +0200 Subject: [PATCH 0819/2916] feat: Allow to set target cluster via target.context --- cmd/kluctl/commands/cmd_seal.go | 2 +- pkg/kluctl_project/target_context.go | 8 +++++--- pkg/kluctl_project/targets.go | 2 +- pkg/types/kluctl_project.go | 1 + 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/cmd/kluctl/commands/cmd_seal.go b/cmd/kluctl/commands/cmd_seal.go index 45446a91a..a25d36236 100644 --- a/cmd/kluctl/commands/cmd_seal.go +++ b/cmd/kluctl/commands/cmd_seal.go @@ -106,7 +106,7 @@ func (cmd *sealCmd) runCmdSealForTarget(ctx context.Context, p *kluctl_project.L } } - clusterConfig, _, err := p.LoadClusterConfig(ctx.targetCtx.Target.Cluster) + clusterConfig, _, err := p.LoadClusterConfig(ctx.targetCtx.Target.Cluster, ctx.targetCtx.Target.Context) if err != nil { return doFail(err) } diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index 7e647c2d2..7900fc972 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -43,11 +43,13 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName s } } + var contextName *string if clusterName == nil && target != nil { clusterName = target.Cluster + contextName = target.Context } - clusterConfig, clientConfig, err := p.LoadClusterConfig(clusterName) + clusterConfig, clientConfig, err := p.LoadClusterConfig(clusterName, contextName) if err != nil { return nil, err } @@ -128,7 +130,7 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName s return targetCtx, nil } -func (p *LoadedKluctlProject) LoadClusterConfig(clusterName *string) (*types.ClusterConfig, *rest.Config, error) { +func (p *LoadedKluctlProject) LoadClusterConfig(clusterName *string, contextName *string) (*types.ClusterConfig, *rest.Config, error) { var err error var clusterConfig *types.ClusterConfig @@ -147,7 +149,7 @@ func (p *LoadedKluctlProject) LoadClusterConfig(clusterName *string) (*types.Clu } } else { var rawConfig *api.Config - clientConfig, rawConfig, err = p.loadArgs.ClientConfigGetter(nil) + clientConfig, rawConfig, err = p.loadArgs.ClientConfigGetter(contextName) if err != nil { return nil, nil, err } diff --git a/pkg/kluctl_project/targets.go b/pkg/kluctl_project/targets.go index bf43b50b9..0eca76935 100644 --- a/pkg/kluctl_project/targets.go +++ b/pkg/kluctl_project/targets.go @@ -89,7 +89,7 @@ func (c *LoadedKluctlProject) renderTarget(target *types.Target) (*types.Target, return nil, err } - cc, _, err := c.LoadClusterConfig(target.Cluster) + cc, _, err := c.LoadClusterConfig(target.Cluster, target.Context) if err == nil { err = varsCtx.UpdateChildFromStruct("cluster", cc.Cluster) if err != nil { diff --git a/pkg/types/kluctl_project.go b/pkg/types/kluctl_project.go index ad2982be6..dc1276557 100644 --- a/pkg/types/kluctl_project.go +++ b/pkg/types/kluctl_project.go @@ -30,6 +30,7 @@ type SealingConfig struct { type Target struct { Name string `yaml:"name" validate:"required"` Cluster *string `yaml:"cluster,omitempty"` + Context *string `yaml:"context,omitempty"` Args *uo.UnstructuredObject `yaml:"args,omitempty"` DynamicArgs []DynamicArg `yaml:"dynamicArgs,omitempty"` TargetConfig *ExternalTargetConfig `yaml:"targetConfig,omitempty"` From 0acb6fddfdaeabdc913d5bafce0882a89be82bdf Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 18 May 2022 11:39:17 +0200 Subject: [PATCH 0820/2916] fix: Merge target vars into varsCtx before checking required args --- pkg/kluctl_project/target_context.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index 7900fc972..156b3ed12 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -70,6 +70,11 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName s if err != nil { return nil, err } + targetVars, err := uo.FromStruct(target) + if err != nil { + return nil, err + } + varsCtx.UpdateChild("target", targetVars) allArgs := uo.New() @@ -100,12 +105,6 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName s varsCtx.UpdateChild("args", allArgs) - targetVars, err := uo.FromStruct(target) - if err != nil { - return nil, err - } - varsCtx.UpdateChild("target", targetVars) - d, err := deployment.NewDeploymentProject(ctx, k, varsCtx, deploymentDir, p.sealedSecretsDir, nil) if err != nil { return nil, err From 911faab7bf0a91da6e6b9d65edfc9a5cb8f42ad6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 18 May 2022 11:42:06 +0200 Subject: [PATCH 0821/2916] feat: Allow to load vars from git --- pkg/git/mirrored_repo.go | 46 ++++++++++++++++++++++++++++ pkg/jinja2/vars.go | 34 +++++++++++++++++++- pkg/kluctl_project/target_context.go | 2 +- pkg/kluctl_project/targets.go | 2 +- pkg/types/deployment.go | 11 +++++++ 5 files changed, 92 insertions(+), 3 deletions(-) diff --git a/pkg/git/mirrored_repo.go b/pkg/git/mirrored_repo.go index 874942200..a18128a5b 100644 --- a/pkg/git/mirrored_repo.go +++ b/pkg/git/mirrored_repo.go @@ -6,6 +6,7 @@ import ( "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/config" "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/object" auth2 "github.com/kluctl/kluctl/v2/pkg/git/auth" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/status" @@ -342,6 +343,51 @@ func (g *MirroredGitRepo) CloneProject(ref string, targetDir string) error { return nil } +func (g *MirroredGitRepo) ReadFile(ref string, path string) ([]byte, error) { + if !g.fileLock.Locked() || !g.hasUpdated { + panic("tried to read a file from a project that is not locked/updated") + } + + doError := func(err error) ([]byte, error) { + return nil, fmt.Errorf("failed to read file from git repostory: %w", err) + } + + r, err := git.PlainOpen(g.mirrorDir) + if err != nil { + return nil, err + } + + if ref == "" { + ref = "HEAD" + } + + h, err := r.ResolveRevision(plumbing.Revision(ref)) + if err != nil { + return doError(err) + } + + commit, err := object.GetCommit(r.Storer, *h) + if err != nil { + return doError(err) + } + tree, err := commit.Tree() + if err != nil { + return doError(err) + } + + f, err := tree.File(path) + if err != nil { + return doError(err) + } + reader, err := f.Reader() + if err != nil { + return doError(err) + } + defer reader.Close() + + return ioutil.ReadAll(reader) +} + func buildMirrorRepoName(u git_url.GitUrl) string { r := filepath.Base(u.Path) r = strings.ReplaceAll(r, "/", "-") diff --git a/pkg/jinja2/vars.go b/pkg/jinja2/vars.go index 75dc2af49..c1d97103b 100644 --- a/pkg/jinja2/vars.go +++ b/pkg/jinja2/vars.go @@ -2,21 +2,27 @@ package jinja2 import ( "fmt" + "github.com/kluctl/kluctl/v2/pkg/git" "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/types" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" + "io/ioutil" + "os" ) type VarsCtx struct { J2 *Jinja2 + grc *git.MirroredGitRepoCollection Vars *uo.UnstructuredObject } -func NewVarsCtx(j2 *Jinja2) *VarsCtx { +func NewVarsCtx(j2 *Jinja2, grc *git.MirroredGitRepoCollection) *VarsCtx { vc := &VarsCtx{ J2: j2, + grc: grc, Vars: uo.New(), } return vc @@ -25,6 +31,7 @@ func NewVarsCtx(j2 *Jinja2) *VarsCtx { func (vc *VarsCtx) Copy() *VarsCtx { cp := &VarsCtx{ J2: vc.J2, + grc: vc.grc, Vars: vc.Vars.Clone(), } return cp @@ -56,6 +63,11 @@ func (vc *VarsCtx) LoadVarsList(k *k8s.K8sCluster, searchDirs []string, varsList if err != nil { return err } + } else if v.Git != nil { + err := vc.loadVarsGitFile(v.Git) + if err != nil { + return err + } } else if v.ClusterConfigMap != nil { ref := k8s2.NewObjectRef("", "v1", "ConfigMap", v.ClusterConfigMap.Name, v.ClusterConfigMap.Namespace) err := vc.loadVarsFromK8sObject(k, ref, v.ClusterConfigMap.Key) @@ -86,6 +98,26 @@ func (vc *VarsCtx) loadVarsFile(p string, searchDirs []string) error { return nil } +func (vc *VarsCtx) loadVarsGitFile(gitFile *types.VarsListItemGit) error { + mr, err := vc.grc.GetMirroredGitRepo(gitFile.Url, true, true, true) + if err != nil { + return fmt.Errorf("failed to load vars from git repository %s: %w", gitFile.Url.String(), err) + } + + tmpDir, err := ioutil.TempDir(utils.GetTmpBaseDir(), "git-vars") + if err != nil { + return err + } + defer os.RemoveAll(tmpDir) + + file, err := mr.ReadFile(gitFile.Ref, gitFile.Path) + if err != nil { + return fmt.Errorf("failed to load vars from git repository %s and path %s: %w", gitFile.Url.String(), gitFile.Path, err) + } + + return vc.loadVarsFromString(string(file)) +} + func (vc *VarsCtx) loadVarsFromK8sObject(k *k8s.K8sCluster, ref k8s2.ObjectRef, key string) error { if k == nil { return nil diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index 156b3ed12..ba4fcf7f2 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -65,7 +65,7 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName s s.Success() } - varsCtx := jinja2.NewVarsCtx(p.J2) + varsCtx := jinja2.NewVarsCtx(p.J2, p.grc) err = varsCtx.UpdateChildFromStruct("cluster", clusterConfig.Cluster) if err != nil { return nil, err diff --git a/pkg/kluctl_project/targets.go b/pkg/kluctl_project/targets.go index 0eca76935..4224a0c09 100644 --- a/pkg/kluctl_project/targets.go +++ b/pkg/kluctl_project/targets.go @@ -83,7 +83,7 @@ func (c *LoadedKluctlProject) renderTarget(target *types.Target) (*types.Target, var errors []error curTarget := target for i := 0; i < 10; i++ { - varsCtx := jinja2.NewVarsCtx(c.J2) + varsCtx := jinja2.NewVarsCtx(c.J2, c.grc) err := varsCtx.UpdateChildFromStruct("target", curTarget) if err != nil { return nil, err diff --git a/pkg/types/deployment.go b/pkg/types/deployment.go index e2069d00f..8a84675b3 100644 --- a/pkg/types/deployment.go +++ b/pkg/types/deployment.go @@ -2,6 +2,7 @@ package types import ( "github.com/go-playground/validator/v10" + git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" ) @@ -49,6 +50,12 @@ type DeploymentArg struct { Default interface{} `yaml:"default,omitempty"` } +type VarsListItemGit struct { + Url git_url.GitUrl `yaml:"url" validate:"required"` + Ref string `yaml:"ref,omitempty"` + Path string `yaml:"path" validate:"required"` +} + type VarsListItemClusterConfigMapOrSecret struct { Name string `yaml:"name" validate:"required"` Namespace string `yaml:"namespace,omitempty"` @@ -58,6 +65,7 @@ type VarsListItemClusterConfigMapOrSecret struct { type VarsListItem struct { Values *uo.UnstructuredObject `yaml:"values,omitempty"` File *string `yaml:"file,omitempty"` + Git *VarsListItemGit `yaml:"git,omitempty"` ClusterConfigMap *VarsListItemClusterConfigMapOrSecret `yaml:"clusterConfigMap,omitempty"` ClusterSecret *VarsListItemClusterConfigMapOrSecret `yaml:"clusterSecret,omitempty"` } @@ -71,6 +79,9 @@ func ValidateVarsListItem(sl validator.StructLevel) { if s.File != nil { count += 1 } + if s.Git != nil { + count += 1 + } if s.ClusterConfigMap != nil { count += 1 } From 45af6d4d9a0c9ca5f95df55a80fc6e13b79b13ce Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 18 May 2022 11:44:36 +0200 Subject: [PATCH 0822/2916] refactor: Move VarsCtx into own package --- pkg/deployment/deployment_project.go | 6 +++--- pkg/deployment/external_args.go | 4 ++-- pkg/kluctl_project/target_context.go | 4 ++-- pkg/kluctl_project/targets.go | 4 ++-- pkg/{jinja2 => vars}/vars.go | 7 ++++--- 5 files changed, 13 insertions(+), 12 deletions(-) rename pkg/{jinja2 => vars}/vars.go (96%) diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go index 3446c5ca4..5004917fb 100644 --- a/pkg/deployment/deployment_project.go +++ b/pkg/deployment/deployment_project.go @@ -3,12 +3,12 @@ package deployment import ( "context" "fmt" - "github.com/kluctl/kluctl/v2/pkg/jinja2" "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/vars" "github.com/kluctl/kluctl/v2/pkg/yaml" "path/filepath" "reflect" @@ -20,7 +20,7 @@ var warnOnce utils.OnceByKey type DeploymentProject struct { ctx context.Context - VarsCtx *jinja2.VarsCtx + VarsCtx *vars.VarsCtx dir string SealedSecretsDir string @@ -34,7 +34,7 @@ type DeploymentProject struct { parentProjectInclude *types.DeploymentItemConfig } -func NewDeploymentProject(ctx context.Context, k *k8s.K8sCluster, varsCtx *jinja2.VarsCtx, dir string, sealedSecretsDir string, parentProject *DeploymentProject) (*DeploymentProject, error) { +func NewDeploymentProject(ctx context.Context, k *k8s.K8sCluster, varsCtx *vars.VarsCtx, dir string, sealedSecretsDir string, parentProject *DeploymentProject) (*DeploymentProject, error) { dp := &DeploymentProject{ ctx: ctx, VarsCtx: varsCtx.Copy(), diff --git a/pkg/deployment/external_args.go b/pkg/deployment/external_args.go index b6db19f41..e3c6600ed 100644 --- a/pkg/deployment/external_args.go +++ b/pkg/deployment/external_args.go @@ -2,9 +2,9 @@ package deployment import ( "fmt" - "github.com/kluctl/kluctl/v2/pkg/jinja2" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/vars" "github.com/kluctl/kluctl/v2/pkg/yaml" "path/filepath" "regexp" @@ -40,7 +40,7 @@ func ConvertArgsToVars(args map[string]string) *uo.UnstructuredObject { return vars } -func CheckRequiredDeployArgs(dir string, varsCtx *jinja2.VarsCtx, deployArgs *uo.UnstructuredObject) error { +func CheckRequiredDeployArgs(dir string, varsCtx *vars.VarsCtx, deployArgs *uo.UnstructuredObject) error { // First try to load the config without templating to avoid getting errors while rendering because required // args were not set. Otherwise we won't be able to iterator through the 'args' array in the deployment.yml // when the rendering error is actually args related. diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index ba4fcf7f2..29dbc2278 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -4,12 +4,12 @@ import ( "context" "fmt" "github.com/kluctl/kluctl/v2/pkg/deployment" - "github.com/kluctl/kluctl/v2/pkg/jinja2" "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/vars" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd/api" "path/filepath" @@ -65,7 +65,7 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName s s.Success() } - varsCtx := jinja2.NewVarsCtx(p.J2, p.grc) + varsCtx := vars.NewVarsCtx(p.J2, p.grc) err = varsCtx.UpdateChildFromStruct("cluster", clusterConfig.Cluster) if err != nil { return nil, err diff --git a/pkg/kluctl_project/targets.go b/pkg/kluctl_project/targets.go index 4224a0c09..d5bbdb66b 100644 --- a/pkg/kluctl_project/targets.go +++ b/pkg/kluctl_project/targets.go @@ -4,11 +4,11 @@ import ( "fmt" securejoin "github.com/cyphar/filepath-securejoin" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" - "github.com/kluctl/kluctl/v2/pkg/jinja2" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/vars" "github.com/kluctl/kluctl/v2/pkg/yaml" "reflect" "regexp" @@ -83,7 +83,7 @@ func (c *LoadedKluctlProject) renderTarget(target *types.Target) (*types.Target, var errors []error curTarget := target for i := 0; i < 10; i++ { - varsCtx := jinja2.NewVarsCtx(c.J2, c.grc) + varsCtx := vars.NewVarsCtx(c.J2, c.grc) err := varsCtx.UpdateChildFromStruct("target", curTarget) if err != nil { return nil, err diff --git a/pkg/jinja2/vars.go b/pkg/vars/vars.go similarity index 96% rename from pkg/jinja2/vars.go rename to pkg/vars/vars.go index c1d97103b..67f818de5 100644 --- a/pkg/jinja2/vars.go +++ b/pkg/vars/vars.go @@ -1,8 +1,9 @@ -package jinja2 +package vars import ( "fmt" "github.com/kluctl/kluctl/v2/pkg/git" + "github.com/kluctl/kluctl/v2/pkg/jinja2" "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/types" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" @@ -14,12 +15,12 @@ import ( ) type VarsCtx struct { - J2 *Jinja2 + J2 *jinja2.Jinja2 grc *git.MirroredGitRepoCollection Vars *uo.UnstructuredObject } -func NewVarsCtx(j2 *Jinja2, grc *git.MirroredGitRepoCollection) *VarsCtx { +func NewVarsCtx(j2 *jinja2.Jinja2, grc *git.MirroredGitRepoCollection) *VarsCtx { vc := &VarsCtx{ J2: j2, grc: grc, From af1b74f921c145841f71f9f0d6b2497b4b76c17f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 18 May 2022 12:21:42 +0200 Subject: [PATCH 0823/2916] feat: Remove deprecated fields from deployment config --- pkg/deployment/deployment_project.go | 37 +--------------------------- pkg/types/deployment.go | 5 ---- 2 files changed, 1 insertion(+), 41 deletions(-) diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go index 5004917fb..5b6638843 100644 --- a/pkg/deployment/deployment_project.go +++ b/pkg/deployment/deployment_project.go @@ -4,19 +4,15 @@ import ( "context" "fmt" "github.com/kluctl/kluctl/v2/pkg/k8s" - "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/vars" "github.com/kluctl/kluctl/v2/pkg/yaml" "path/filepath" - "reflect" "strings" ) -var warnOnce utils.OnceByKey - type DeploymentProject struct { ctx context.Context @@ -86,30 +82,6 @@ func (p *DeploymentProject) loadConfig(k *k8s.K8sCluster) error { return fmt.Errorf("failed to load deployment.yml vars: %w", err) } - // TODO remove obsolete code - if len(p.Config.Deployments) != 0 && (len(p.Config.KustomizeDirs) != 0 || len(p.Config.Includes) != 0) { - return fmt.Errorf("using 'deployments' and 'kustomizeDirs' at the same time is not allowed") - } - if len(p.Config.KustomizeDirs) != 0 { - warnOnce.Do("kustomizeDirs", func() { - status.Warning(p.ctx, "'kustomizeDirs' is deprecated, use 'deployments' instead") - }) - p.Config.Deployments = p.Config.KustomizeDirs - p.Config.KustomizeDirs = nil - } - if len(p.Config.Includes) != 0 { - warnOnce.Do("includes", func() { - status.Warning(p.ctx, "'includes' is deprecated, use 'deployments' instead") - }) - for _, inc := range p.Config.Includes { - c := *inc - c.Include = c.Path - c.Path = nil - p.Config.Deployments = append(p.Config.Deployments, &c) - } - p.Config.Includes = nil - } - // If there are no explicit tags set, interpret the path as a tag, which allows to // enable/disable single deployments via included/excluded tags for _, item := range p.Config.Deployments { @@ -131,14 +103,7 @@ func (p *DeploymentProject) loadConfig(k *k8s.K8sCluster) error { if len(p.GetCommonLabels()) == 0 { return fmt.Errorf("no commonLabels in root deployment. This is not allowed") } - if len(p.Config.DeleteByLabels) != 0 { - warnOnce.Do("deleteByLabels", func() { - status.Warning(p.ctx, "'deleteByLabels' is deprecated and ignored from now on") - }) - if !reflect.DeepEqual(p.Config.CommonLabels, p.Config.DeleteByLabels) { - return fmt.Errorf("commonLabels and deleteByLabels do not match") - } - } + if len(p.Config.Args) != 0 && p.parentProject != nil { return fmt.Errorf("only the root deployment.yml can define args") } diff --git a/pkg/types/deployment.go b/pkg/types/deployment.go index 8a84675b3..754f54080 100644 --- a/pkg/types/deployment.go +++ b/pkg/types/deployment.go @@ -132,12 +132,7 @@ type DeploymentProjectConfig struct { Deployments []*DeploymentItemConfig `yaml:"deployments,omitempty"` - // Obsolete - KustomizeDirs []*DeploymentItemConfig `yaml:"kustomizeDirs,omitempty"` - Includes []*DeploymentItemConfig `yaml:"includes,omitempty"` - CommonLabels map[string]string `yaml:"commonLabels,omitempty"` - DeleteByLabels map[string]string `yaml:"deleteByLabels,omitempty"` OverrideNamespace *string `yaml:"overrideNamespace,omitempty"` Tags []string `yaml:"tags,omitempty"` From b939b8ab019eaa8d66120d4be65a2e1ddc0cffd6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 18 May 2022 12:22:13 +0200 Subject: [PATCH 0824/2916] feat: Deprecate cluster configuration and warn about removal in the future --- pkg/kluctl_project/project_load.go | 3 ++- pkg/kluctl_project/target_context.go | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/kluctl_project/project_load.go b/pkg/kluctl_project/project_load.go index c1d9e3be9..b3811864e 100644 --- a/pkg/kluctl_project/project_load.go +++ b/pkg/kluctl_project/project_load.go @@ -206,8 +206,10 @@ func (c *LoadedKluctlProject) loadKluctlProject() error { } var clustersInfos []gitProjectInfo if c.loadArgs.LocalClusters != "" { + status.Warning(c.ctx, "--local-clusters is deprecated and will be removed in an upcoming version. Use variables loaded from git instead.") clustersInfos = append(clustersInfos, c.localProject(c.loadArgs.LocalClusters)) } else if len(c.Config.Clusters.Projects) != 0 { + status.Warning(c.ctx, "'clusters' is deprecated and will be removed in an upcoming version. Use variables loaded from git instead.") for _, ep := range c.Config.Clusters.Projects { info, err := c.loadExternalProject(&ep, "clusters", "") if err != nil { @@ -246,7 +248,6 @@ func (c *LoadedKluctlProject) mergeClustersDirs(mergedClustersDir string, cluste for _, ci := range clustersInfos { if !utils.IsDirectory(ci.dir) { - status.Warning(c.ctx, "Cluster dir '%s' does not exist", ci.dir) continue } files, err := ioutil.ReadDir(ci.dir) diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index 29dbc2278..dbcf672f6 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -134,6 +134,8 @@ func (p *LoadedKluctlProject) LoadClusterConfig(clusterName *string, contextName var clusterConfig *types.ClusterConfig if clusterName != nil { + status.Warning(p.ctx, "Cluster configurations have been deprecated and support for them will be removed in a future kluctl release.") + clusterConfig, err = types.LoadClusterConfig(p.ClustersDir, *clusterName) if err != nil { return nil, nil, err From 021ee097f76f4f7ef9c8d3117b1eccad5ff6ec68 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 18 May 2022 12:41:41 +0200 Subject: [PATCH 0825/2916] refactor: Rename SecretsSource to VarsSource --- cmd/kluctl/commands/cmd_seal.go | 2 +- pkg/seal/secrets_loader.go | 8 ++++---- pkg/seal/secrets_loader_http.go | 4 ++-- pkg/types/kluctl_project.go | 6 +++--- pkg/types/{secrets_source.go => vars_source.go} | 16 ++++++++-------- 5 files changed, 18 insertions(+), 18 deletions(-) rename pkg/types/{secrets_source.go => vars_source.go} (72%) diff --git a/cmd/kluctl/commands/cmd_seal.go b/cmd/kluctl/commands/cmd_seal.go index a25d36236..85d8518f3 100644 --- a/cmd/kluctl/commands/cmd_seal.go +++ b/cmd/kluctl/commands/cmd_seal.go @@ -46,7 +46,7 @@ func loadSecrets(ctx *commandCtx, target *types.Target, secretsLoader *seal.Secr return err } for _, source := range secretEntry.Sources { - var renderedSource types.SecretSource + var renderedSource types.VarsSource err = ctx.targetCtx.KluctlProject.J2.RenderStruct(&renderedSource, &source, ctx.targetCtx.DeploymentProject.VarsCtx.Vars) if err != nil { return err diff --git a/pkg/seal/secrets_loader.go b/pkg/seal/secrets_loader.go index ff0a13431..e49537647 100644 --- a/pkg/seal/secrets_loader.go +++ b/pkg/seal/secrets_loader.go @@ -32,7 +32,7 @@ func NewSecretsLoader(p *kluctl_project.LoadedKluctlProject, secretsDir string) } } -func (s *SecretsLoader) LoadSecrets(source *types.SecretSource) (*uo.UnstructuredObject, error) { +func (s *SecretsLoader) LoadSecrets(source *types.VarsSource) (*uo.UnstructuredObject, error) { if source.Path != nil { return s.loadSecretsFile(source) } else if source.SystemEnvVars != nil { @@ -46,7 +46,7 @@ func (s *SecretsLoader) LoadSecrets(source *types.SecretSource) (*uo.Unstructure } } -func (s *SecretsLoader) loadSecretsFile(source *types.SecretSource) (*uo.UnstructuredObject, error) { +func (s *SecretsLoader) loadSecretsFile(source *types.VarsSource) (*uo.UnstructuredObject, error) { var p string var err error if utils.Exists(filepath.Join(s.project.DeploymentDir, *source.Path)) { @@ -82,7 +82,7 @@ func (s *SecretsLoader) loadSecretsFile(source *types.SecretSource) (*uo.Unstruc return secrets, nil } -func (s *SecretsLoader) loadSecretsSystemEnvs(source *types.SecretSource) (*uo.UnstructuredObject, error) { +func (s *SecretsLoader) loadSecretsSystemEnvs(source *types.VarsSource) (*uo.UnstructuredObject, error) { secrets := uo.New() err := source.SystemEnvVars.NewIterator().IterateLeafs(func(it *uo.ObjectIterator) error { envName, ok := it.Value().(string) @@ -105,7 +105,7 @@ func (s *SecretsLoader) loadSecretsSystemEnvs(source *types.SecretSource) (*uo.U return secrets, nil } -func (s *SecretsLoader) loadSecretsAwsSecretsManager(source *types.SecretSource) (*uo.UnstructuredObject, error) { +func (s *SecretsLoader) loadSecretsAwsSecretsManager(source *types.VarsSource) (*uo.UnstructuredObject, error) { secret, err := aws.GetAwsSecretsManagerSecret(source.AwsSecretsManager.Profile, source.AwsSecretsManager.Region, source.AwsSecretsManager.SecretName) if err != nil { return nil, err diff --git a/pkg/seal/secrets_loader_http.go b/pkg/seal/secrets_loader_http.go index 01582a9d1..a4fb00e23 100644 --- a/pkg/seal/secrets_loader_http.go +++ b/pkg/seal/secrets_loader_http.go @@ -15,7 +15,7 @@ import ( "strings" ) -func (s *SecretsLoader) doHttp(httpSource *types.SecretSourceHttp, username string, password string) (*http.Response, string, error) { +func (s *SecretsLoader) doHttp(httpSource *types.VarsSourceHttp, username string, password string) (*http.Response, string, error) { client := &http.Client{ Transport: ntlmssp.Negotiator{ RoundTripper: &http.Transport{ @@ -65,7 +65,7 @@ func (s *SecretsLoader) doHttp(httpSource *types.SecretSourceHttp, username stri return resp, string(respBody), nil } -func (s *SecretsLoader) loadSecretsHttp(source *types.SecretSource) (*uo.UnstructuredObject, error) { +func (s *SecretsLoader) loadSecretsHttp(source *types.VarsSource) (*uo.UnstructuredObject, error) { resp, respBody, err := s.doHttp(source.Http, "", "") if err != nil && resp != nil && resp.StatusCode == http.StatusUnauthorized { chgs := challenge.ResponseChallenges(resp) diff --git a/pkg/types/kluctl_project.go b/pkg/types/kluctl_project.go index dc1276557..b51b500c2 100644 --- a/pkg/types/kluctl_project.go +++ b/pkg/types/kluctl_project.go @@ -44,8 +44,8 @@ type DynamicTarget struct { } type SecretSet struct { - Name string `yaml:"name" validate:"required"` - Sources []SecretSource `yaml:"sources" validate:"required,gt=0"` + Name string `yaml:"name" validate:"required"` + Sources []VarsSource `yaml:"sources" validate:"required,gt=0"` } type GlobalSealedSecretsConfig struct { @@ -68,5 +68,5 @@ type KluctlProject struct { } func init() { - yaml.Validator.RegisterStructValidation(ValidateSecretSource, SecretSource{}) + yaml.Validator.RegisterStructValidation(ValidateSecretSource, VarsSource{}) } diff --git a/pkg/types/secrets_source.go b/pkg/types/vars_source.go similarity index 72% rename from pkg/types/secrets_source.go rename to pkg/types/vars_source.go index 2575ecbde..dec89ddd3 100644 --- a/pkg/types/secrets_source.go +++ b/pkg/types/vars_source.go @@ -5,7 +5,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils/uo" ) -type SecretSourceHttp struct { +type VarsSourceHttp struct { Url YamlUrl `yaml:"url,omitempty" validate:"required"` Method *string `yaml:"method,omitempty"` Body *string `yaml:"body,omitempty"` @@ -13,7 +13,7 @@ type SecretSourceHttp struct { JsonPath *string `yaml:"jsonPath,omitempty"` } -type SecretSourceAwsSecretsManager struct { +type VarsSourceAwsSecretsManager struct { // Name or ARN of the secret. In case a name is given, the region must be specified as well SecretName string `yaml:"secretName" validate:"required"` // The aws region @@ -22,15 +22,15 @@ type SecretSourceAwsSecretsManager struct { Profile *string `yaml:"profile,omitempty"` } -type SecretSource struct { - Path *string `yaml:"path,omitempty"` - SystemEnvVars *uo.UnstructuredObject `yaml:"systemEnvVars,omitempty"` - Http *SecretSourceHttp `yaml:"http,omitempty"` - AwsSecretsManager *SecretSourceAwsSecretsManager `yaml:"awsSecretsManager,omitempty"` +type VarsSource struct { + Path *string `yaml:"path,omitempty"` + SystemEnvVars *uo.UnstructuredObject `yaml:"systemEnvVars,omitempty"` + Http *VarsSourceHttp `yaml:"http,omitempty"` + AwsSecretsManager *VarsSourceAwsSecretsManager `yaml:"awsSecretsManager,omitempty"` } func ValidateSecretSource(sl validator.StructLevel) { - s := sl.Current().Interface().(SecretSource) + s := sl.Current().Interface().(VarsSource) count := 0 if s.Path != nil { count += 1 From 9334eb4046b5d393e8011b95a30c3d7d79e048c0 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 18 May 2022 14:36:40 +0200 Subject: [PATCH 0826/2916] refactor: Move varsCtx building into buildVars --- cmd/kluctl/commands/cmd_downscale.go | 2 +- cmd/kluctl/commands/cmd_poke_images.go | 2 +- pkg/kluctl_project/target_context.go | 85 +++++++++++++++----------- 3 files changed, 51 insertions(+), 38 deletions(-) diff --git a/cmd/kluctl/commands/cmd_downscale.go b/cmd/kluctl/commands/cmd_downscale.go index 78523c4b9..d3a0ffc10 100644 --- a/cmd/kluctl/commands/cmd_downscale.go +++ b/cmd/kluctl/commands/cmd_downscale.go @@ -37,7 +37,7 @@ func (cmd *downscaleCmd) Run() error { } return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { if !cmd.Yes && !cmd.DryRun { - if !utils.AskForConfirmation(fmt.Sprintf("Do you really want to downscale on context/cluster %s?", ctx.targetCtx.ClusterConfig.Cluster.Context)) { + if !utils.AskForConfirmation(fmt.Sprintf("Do you really want to downscale on context/cluster %s?", ctx.targetCtx.ClusterContext)) { return fmt.Errorf("aborted") } } diff --git a/cmd/kluctl/commands/cmd_poke_images.go b/cmd/kluctl/commands/cmd_poke_images.go index 88c2ab7ff..c7121e515 100644 --- a/cmd/kluctl/commands/cmd_poke_images.go +++ b/cmd/kluctl/commands/cmd_poke_images.go @@ -37,7 +37,7 @@ func (cmd *pokeImagesCmd) Run() error { } return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { if !cmd.Yes && !cmd.DryRun { - if !utils.AskForConfirmation(fmt.Sprintf("Do you really want to poke images to the context/cluster %s?", ctx.targetCtx.ClusterConfig.Cluster.Context)) { + if !utils.AskForConfirmation(fmt.Sprintf("Do you really want to poke images to the context/cluster %s?", ctx.targetCtx.ClusterContext)) { return fmt.Errorf("aborted") } } diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index dbcf672f6..038452a3d 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -18,7 +18,7 @@ import ( type TargetContext struct { KluctlProject *LoadedKluctlProject Target *types.Target - ClusterConfig *types.ClusterConfig + ClusterContext string K *k8s.K8sCluster DeploymentProject *deployment.DeploymentProject DeploymentCollection *deployment.DeploymentCollection @@ -43,20 +43,14 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName s } } - var contextName *string - if clusterName == nil && target != nil { - clusterName = target.Cluster - contextName = target.Context - } - - clusterConfig, clientConfig, err := p.LoadClusterConfig(clusterName, contextName) + varsCtx, clientConfig, clusterContext, err := p.buildVars(target, clusterName, args, forSeal) if err != nil { return nil, err } var k *k8s.K8sCluster if clientConfig != nil { - s := status.Start(ctx, fmt.Sprintf("Initializing k8s client for context %s", clusterConfig.Cluster.Context)) + s := status.Start(ctx, fmt.Sprintf("Initializing k8s client")) k, err = k8s.NewK8sCluster(ctx, clientConfig, dryRun) if err != nil { s.Failed() @@ -65,14 +59,54 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName s s.Success() } + d, err := deployment.NewDeploymentProject(ctx, k, varsCtx, deploymentDir, p.sealedSecretsDir, nil) + if err != nil { + return nil, err + } + + d.DefaultSealedSecretsOutputPattern = targetName + + c, err := deployment.NewDeploymentCollection(ctx, d, images, inclusion, renderOutputDir, forSeal) + if err != nil { + return nil, err + } + + targetCtx := &TargetContext{ + KluctlProject: p, + Target: target, + ClusterContext: clusterContext, + K: k, + DeploymentProject: d, + DeploymentCollection: c, + } + + return targetCtx, nil +} + +func (p *LoadedKluctlProject) buildVars(target *types.Target, clusterName *string, args map[string]string, forSeal bool) (*vars.VarsCtx, *rest.Config, string, error) { + doError := func(err error) (*vars.VarsCtx, *rest.Config, string, error) { + return nil, nil, "", err + } + + var contextName *string + if clusterName == nil && target != nil { + clusterName = target.Cluster + contextName = target.Context + } + + clusterConfig, clientConfig, err := p.LoadClusterConfig(clusterName, contextName) + if err != nil { + return doError(err) + } + varsCtx := vars.NewVarsCtx(p.J2, p.grc) err = varsCtx.UpdateChildFromStruct("cluster", clusterConfig.Cluster) if err != nil { - return nil, err + return doError(err) } targetVars, err := uo.FromStruct(target) if err != nil { - return nil, err + return doError(err) } varsCtx.UpdateChild("target", targetVars) @@ -82,7 +116,7 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName s for argName, argValue := range args { err = p.CheckDynamicArg(target, argName, argValue) if err != nil { - return nil, err + return doError(err) } } } @@ -98,35 +132,14 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName s } } - err = deployment.CheckRequiredDeployArgs(deploymentDir, varsCtx, allArgs) + err = deployment.CheckRequiredDeployArgs(p.DeploymentDir, varsCtx, allArgs) if err != nil { - return nil, err + return doError(err) } varsCtx.UpdateChild("args", allArgs) - d, err := deployment.NewDeploymentProject(ctx, k, varsCtx, deploymentDir, p.sealedSecretsDir, nil) - if err != nil { - return nil, err - } - - d.DefaultSealedSecretsOutputPattern = targetName - - c, err := deployment.NewDeploymentCollection(ctx, d, images, inclusion, renderOutputDir, forSeal) - if err != nil { - return nil, err - } - - targetCtx := &TargetContext{ - KluctlProject: p, - Target: target, - ClusterConfig: clusterConfig, - K: k, - DeploymentProject: d, - DeploymentCollection: c, - } - - return targetCtx, nil + return varsCtx, clientConfig, clusterConfig.Cluster.Context, nil } func (p *LoadedKluctlProject) LoadClusterConfig(clusterName *string, contextName *string) (*types.ClusterConfig, *rest.Config, error) { From 07785dd3d89f28df12838eae720ad049b8917e58 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 18 May 2022 14:43:46 +0200 Subject: [PATCH 0827/2916] feat: Remove --secrets-dir in seal command --- cmd/kluctl/commands/cmd_seal.go | 5 ++--- pkg/seal/secrets_loader.go | 11 ++--------- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/cmd/kluctl/commands/cmd_seal.go b/cmd/kluctl/commands/cmd_seal.go index 85d8518f3..b2ae41aee 100644 --- a/cmd/kluctl/commands/cmd_seal.go +++ b/cmd/kluctl/commands/cmd_seal.go @@ -16,8 +16,7 @@ type sealCmd struct { args.ProjectFlags args.TargetFlags - SecretsDir string `group:"misc" help:"Specifies where to find unencrypted secret files. The given directory is NOT meant to be part of your source repository! The given path only matters for secrets of type 'path'. Defaults to the current working directory."` - ForceReseal bool `group:"misc" help:"Lets kluctl ignore secret hashes found in already sealed secrets and thus forces resealing of those."` + ForceReseal bool `group:"misc" help:"Lets kluctl ignore secret hashes found in already sealed secrets and thus forces resealing of those."` } func (cmd *sealCmd) Help() string { @@ -137,7 +136,7 @@ func (cmd *sealCmd) Run() error { return withKluctlProjectFromArgs(cmd.ProjectFlags, true, false, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { hadError := false - secretsLoader := seal.NewSecretsLoader(p, cmd.SecretsDir) + secretsLoader := seal.NewSecretsLoader(p) baseTargets := make(map[string]bool) noTargetMatch := true diff --git a/pkg/seal/secrets_loader.go b/pkg/seal/secrets_loader.go index e49537647..f1bb1b1cc 100644 --- a/pkg/seal/secrets_loader.go +++ b/pkg/seal/secrets_loader.go @@ -18,16 +18,14 @@ type usernamePassword struct { } type SecretsLoader struct { - project *kluctl_project.LoadedKluctlProject - secretsDir string + project *kluctl_project.LoadedKluctlProject credentialsCache map[string]usernamePassword } -func NewSecretsLoader(p *kluctl_project.LoadedKluctlProject, secretsDir string) *SecretsLoader { +func NewSecretsLoader(p *kluctl_project.LoadedKluctlProject) *SecretsLoader { return &SecretsLoader{ project: p, - secretsDir: secretsDir, credentialsCache: map[string]usernamePassword{}, } } @@ -54,11 +52,6 @@ func (s *SecretsLoader) loadSecretsFile(source *types.VarsSource) (*uo.Unstructu if err != nil { return nil, err } - } else if utils.Exists(filepath.Join(s.secretsDir, *source.Path)) { - p, err = securejoin.SecureJoin(s.secretsDir, *source.Path) - if err != nil { - return nil, err - } } else { return nil, fmt.Errorf("secrets file %s does not exist", *source.Path) } From 409b86e9c417cd8f9250b8dbca4cd7287d4a9e27 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 18 May 2022 14:55:49 +0200 Subject: [PATCH 0828/2916] refactor: Move loading of secrets into kluctl project loading code --- cmd/kluctl/commands/cmd_seal.go | 47 ++----------------- .../secrets_loader.go | 7 ++- .../secrets_loader_http.go | 2 +- pkg/kluctl_project/target_context.go | 40 ++++++++++++++++ 4 files changed, 47 insertions(+), 49 deletions(-) rename pkg/{seal => kluctl_project}/secrets_loader.go (94%) rename pkg/{seal => kluctl_project}/secrets_loader_http.go (99%) diff --git a/cmd/kluctl/commands/cmd_seal.go b/cmd/kluctl/commands/cmd_seal.go index b2ae41aee..6749b559d 100644 --- a/cmd/kluctl/commands/cmd_seal.go +++ b/cmd/kluctl/commands/cmd_seal.go @@ -8,8 +8,6 @@ import ( "github.com/kluctl/kluctl/v2/pkg/kluctl_project" "github.com/kluctl/kluctl/v2/pkg/seal" "github.com/kluctl/kluctl/v2/pkg/status" - "github.com/kluctl/kluctl/v2/pkg/types" - "github.com/kluctl/kluctl/v2/pkg/utils/uo" ) type sealCmd struct { @@ -28,40 +26,7 @@ kubeseal on each '.sealme' file and stores secrets in the directory specified by If no '--target' is specified, sealing is performed for all targets.` } -func findSecretsEntry(ctx *commandCtx, name string) (*types.SecretSet, error) { - for _, e := range ctx.targetCtx.KluctlProject.Config.SecretsConfig.SecretSets { - if e.Name == name { - return &e, nil - } - } - return nil, fmt.Errorf("secret Set with name %s was not found", name) -} - -func loadSecrets(ctx *commandCtx, target *types.Target, secretsLoader *seal.SecretsLoader) error { - secrets := uo.New() - for _, secretSetName := range target.SealingConfig.SecretSets { - secretEntry, err := findSecretsEntry(ctx, secretSetName) - if err != nil { - return err - } - for _, source := range secretEntry.Sources { - var renderedSource types.VarsSource - err = ctx.targetCtx.KluctlProject.J2.RenderStruct(&renderedSource, &source, ctx.targetCtx.DeploymentProject.VarsCtx.Vars) - if err != nil { - return err - } - s, err := secretsLoader.LoadSecrets(&renderedSource) - if err != nil { - return err - } - secrets.Merge(s) - } - } - ctx.targetCtx.DeploymentProject.MergeSecretsIntoAllChildren(secrets) - return nil -} - -func (cmd *sealCmd) runCmdSealForTarget(ctx context.Context, p *kluctl_project.LoadedKluctlProject, targetName string, secretsLoader *seal.SecretsLoader) error { +func (cmd *sealCmd) runCmdSealForTarget(ctx context.Context, p *kluctl_project.LoadedKluctlProject, targetName string) error { s := status.Start(ctx, "%s: Sealing for target", targetName) defer s.FailedWithMessage("%s: Sealing failed", targetName) @@ -79,11 +44,7 @@ func (cmd *sealCmd) runCmdSealForTarget(ctx context.Context, p *kluctl_project.L // pass forSeal=True so that .sealme files are rendered as well return withProjectTargetCommandContext(ctx, ptArgs, p, func(ctx *commandCtx) error { - err := loadSecrets(ctx, ctx.targetCtx.Target, secretsLoader) - if err != nil { - return doFail(err) - } - err = ctx.targetCtx.DeploymentCollection.RenderDeployments(ctx.targetCtx.K) + err := ctx.targetCtx.DeploymentCollection.RenderDeployments(ctx.targetCtx.K) if err != nil { return doFail(err) } @@ -136,8 +97,6 @@ func (cmd *sealCmd) Run() error { return withKluctlProjectFromArgs(cmd.ProjectFlags, true, false, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { hadError := false - secretsLoader := seal.NewSecretsLoader(p) - baseTargets := make(map[string]bool) noTargetMatch := true for _, target := range p.DynamicTargets { @@ -169,7 +128,7 @@ func (cmd *sealCmd) Run() error { sealTarget = baseTarget } - err := cmd.runCmdSealForTarget(ctx, p, sealTarget.Name, secretsLoader) + err := cmd.runCmdSealForTarget(ctx, p, sealTarget.Name) if err != nil { hadError = true status.Error(ctx, err.Error()) diff --git a/pkg/seal/secrets_loader.go b/pkg/kluctl_project/secrets_loader.go similarity index 94% rename from pkg/seal/secrets_loader.go rename to pkg/kluctl_project/secrets_loader.go index f1bb1b1cc..091f13476 100644 --- a/pkg/seal/secrets_loader.go +++ b/pkg/kluctl_project/secrets_loader.go @@ -1,9 +1,8 @@ -package seal +package kluctl_project import ( "fmt" securejoin "github.com/cyphar/filepath-securejoin" - "github.com/kluctl/kluctl/v2/pkg/kluctl_project" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/aws" @@ -18,12 +17,12 @@ type usernamePassword struct { } type SecretsLoader struct { - project *kluctl_project.LoadedKluctlProject + project *LoadedKluctlProject credentialsCache map[string]usernamePassword } -func NewSecretsLoader(p *kluctl_project.LoadedKluctlProject) *SecretsLoader { +func NewSecretsLoader(p *LoadedKluctlProject) *SecretsLoader { return &SecretsLoader{ project: p, credentialsCache: map[string]usernamePassword{}, diff --git a/pkg/seal/secrets_loader_http.go b/pkg/kluctl_project/secrets_loader_http.go similarity index 99% rename from pkg/seal/secrets_loader_http.go rename to pkg/kluctl_project/secrets_loader_http.go index a4fb00e23..07b225c88 100644 --- a/pkg/seal/secrets_loader_http.go +++ b/pkg/kluctl_project/secrets_loader_http.go @@ -1,4 +1,4 @@ -package seal +package kluctl_project import ( "crypto/tls" diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index 038452a3d..611acdb88 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -59,6 +59,13 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName s s.Success() } + if forSeal { + err = p.loadSecrets(target, varsCtx) + if err != nil { + return nil, err + } + } + d, err := deployment.NewDeploymentProject(ctx, k, varsCtx, deploymentDir, p.sealedSecretsDir, nil) if err != nil { return nil, err @@ -142,6 +149,39 @@ func (p *LoadedKluctlProject) buildVars(target *types.Target, clusterName *strin return varsCtx, clientConfig, clusterConfig.Cluster.Context, nil } +func (p *LoadedKluctlProject) findSecretsEntry(name string) (*types.SecretSet, error) { + for _, e := range p.Config.SecretsConfig.SecretSets { + if e.Name == name { + return &e, nil + } + } + return nil, fmt.Errorf("secret Set with name %s was not found", name) +} + +func (p *LoadedKluctlProject) loadSecrets(target *types.Target, varsCtx *vars.VarsCtx) error { + secretsLoader := NewSecretsLoader(p) + + for _, secretSetName := range target.SealingConfig.SecretSets { + secretEntry, err := p.findSecretsEntry(secretSetName) + if err != nil { + return err + } + for _, source := range secretEntry.Sources { + var renderedSource types.VarsSource + err = p.J2.RenderStruct(&renderedSource, &source, varsCtx.Vars) + if err != nil { + return err + } + s, err := secretsLoader.LoadSecrets(&renderedSource) + if err != nil { + return err + } + varsCtx.Vars.MergeChild("secrets", s) + } + } + return nil +} + func (p *LoadedKluctlProject) LoadClusterConfig(clusterName *string, contextName *string) (*types.ClusterConfig, *rest.Config, error) { var err error var clusterConfig *types.ClusterConfig From 08c4b8d66a449747dcfd0225a8f2424ef63ace98 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 18 May 2022 15:47:48 +0200 Subject: [PATCH 0829/2916] refactor: Start unifying secrets and vars loading --- pkg/kluctl_project/secrets_loader.go | 118 ---------------- pkg/kluctl_project/target_context.go | 21 +-- pkg/types/kluctl_project.go | 4 +- pkg/vars/vars_loader.go | 126 ++++++++++++++++++ .../vars_loader_http.go} | 50 +++---- 5 files changed, 162 insertions(+), 157 deletions(-) delete mode 100644 pkg/kluctl_project/secrets_loader.go create mode 100644 pkg/vars/vars_loader.go rename pkg/{kluctl_project/secrets_loader_http.go => vars/vars_loader_http.go} (73%) diff --git a/pkg/kluctl_project/secrets_loader.go b/pkg/kluctl_project/secrets_loader.go deleted file mode 100644 index 091f13476..000000000 --- a/pkg/kluctl_project/secrets_loader.go +++ /dev/null @@ -1,118 +0,0 @@ -package kluctl_project - -import ( - "fmt" - securejoin "github.com/cyphar/filepath-securejoin" - "github.com/kluctl/kluctl/v2/pkg/types" - "github.com/kluctl/kluctl/v2/pkg/utils" - "github.com/kluctl/kluctl/v2/pkg/utils/aws" - "github.com/kluctl/kluctl/v2/pkg/utils/uo" - "os" - "path/filepath" -) - -type usernamePassword struct { - username string - password string -} - -type SecretsLoader struct { - project *LoadedKluctlProject - - credentialsCache map[string]usernamePassword -} - -func NewSecretsLoader(p *LoadedKluctlProject) *SecretsLoader { - return &SecretsLoader{ - project: p, - credentialsCache: map[string]usernamePassword{}, - } -} - -func (s *SecretsLoader) LoadSecrets(source *types.VarsSource) (*uo.UnstructuredObject, error) { - if source.Path != nil { - return s.loadSecretsFile(source) - } else if source.SystemEnvVars != nil { - return s.loadSecretsSystemEnvs(source) - } else if source.Http != nil { - return s.loadSecretsHttp(source) - } else if source.AwsSecretsManager != nil { - return s.loadSecretsAwsSecretsManager(source) - } else { - return nil, fmt.Errorf("invalid secrets entry") - } -} - -func (s *SecretsLoader) loadSecretsFile(source *types.VarsSource) (*uo.UnstructuredObject, error) { - var p string - var err error - if utils.Exists(filepath.Join(s.project.DeploymentDir, *source.Path)) { - p, err = securejoin.SecureJoin(s.project.DeploymentDir, *source.Path) - if err != nil { - return nil, err - } - } else { - return nil, fmt.Errorf("secrets file %s does not exist", *source.Path) - } - - err = utils.CheckInDir(s.project.DeploymentDir, p) - if err != nil { - return nil, fmt.Errorf("secrets file %s is not part of the deployment project: %w", *source.Path, err) - } - - secrets, err := uo.FromFile(p) - if err != nil { - return nil, err - } - secrets, ok, err := secrets.GetNestedObject("secrets") - if err != nil { - return nil, err - } - if !ok { - return uo.New(), nil - } - return secrets, nil -} - -func (s *SecretsLoader) loadSecretsSystemEnvs(source *types.VarsSource) (*uo.UnstructuredObject, error) { - secrets := uo.New() - err := source.SystemEnvVars.NewIterator().IterateLeafs(func(it *uo.ObjectIterator) error { - envName, ok := it.Value().(string) - if !ok { - return fmt.Errorf("value at %s is not a string", it.KeyPath().ToJsonPath()) - } - envValue, ok := os.LookupEnv(envName) - if !ok { - return fmt.Errorf("environment variable %s not found for secret %s", envName, it.KeyPath().ToJsonPath()) - } - err := secrets.SetNestedField(envValue, it.KeyPath()...) - if err != nil { - return fmt.Errorf("failed to set secret %s: %w", it.KeyPath().ToJsonPath(), err) - } - return nil - }) - if err != nil { - return nil, err - } - return secrets, nil -} - -func (s *SecretsLoader) loadSecretsAwsSecretsManager(source *types.VarsSource) (*uo.UnstructuredObject, error) { - secret, err := aws.GetAwsSecretsManagerSecret(source.AwsSecretsManager.Profile, source.AwsSecretsManager.Region, source.AwsSecretsManager.SecretName) - if err != nil { - return nil, err - } - - secrets, err := uo.FromString(secret) - if err != nil { - return nil, fmt.Errorf("failed to parse yaml from AWS Secrets Manager (secretName=%s): %w", source.AwsSecretsManager.SecretName, err) - } - secrets, ok, err := secrets.GetNestedObject("secrets") - if err != nil { - return nil, err - } - if !ok { - return uo.New(), nil - } - return secrets, nil -} diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index 611acdb88..d3e86dc44 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -60,7 +60,7 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName s } if forSeal { - err = p.loadSecrets(target, varsCtx) + err = p.loadSecrets(k, target, varsCtx) if err != nil { return nil, err } @@ -158,25 +158,18 @@ func (p *LoadedKluctlProject) findSecretsEntry(name string) (*types.SecretSet, e return nil, fmt.Errorf("secret Set with name %s was not found", name) } -func (p *LoadedKluctlProject) loadSecrets(target *types.Target, varsCtx *vars.VarsCtx) error { - secretsLoader := NewSecretsLoader(p) +func (p *LoadedKluctlProject) loadSecrets(k *k8s.K8sCluster, target *types.Target, varsCtx *vars.VarsCtx) error { + searchDirs := []string{p.DeploymentDir} + secretsLoader := vars.NewVarsLoader(k, varsCtx, searchDirs, "secrets") for _, secretSetName := range target.SealingConfig.SecretSets { secretEntry, err := p.findSecretsEntry(secretSetName) if err != nil { return err } - for _, source := range secretEntry.Sources { - var renderedSource types.VarsSource - err = p.J2.RenderStruct(&renderedSource, &source, varsCtx.Vars) - if err != nil { - return err - } - s, err := secretsLoader.LoadSecrets(&renderedSource) - if err != nil { - return err - } - varsCtx.Vars.MergeChild("secrets", s) + err = secretsLoader.LoadVarsList(secretEntry.Sources) + if err != nil { + return err } } return nil diff --git a/pkg/types/kluctl_project.go b/pkg/types/kluctl_project.go index b51b500c2..c3084e7f3 100644 --- a/pkg/types/kluctl_project.go +++ b/pkg/types/kluctl_project.go @@ -44,8 +44,8 @@ type DynamicTarget struct { } type SecretSet struct { - Name string `yaml:"name" validate:"required"` - Sources []VarsSource `yaml:"sources" validate:"required,gt=0"` + Name string `yaml:"name" validate:"required"` + Sources []*VarsSource `yaml:"sources" validate:"required,gt=0"` } type GlobalSealedSecretsConfig struct { diff --git a/pkg/vars/vars_loader.go b/pkg/vars/vars_loader.go new file mode 100644 index 000000000..87bd01764 --- /dev/null +++ b/pkg/vars/vars_loader.go @@ -0,0 +1,126 @@ +package vars + +import ( + "fmt" + "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/utils/aws" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "os" +) + +type usernamePassword struct { + username string + password string +} + +type VarsLoader struct { + k *k8s.K8sCluster + varsCtx *VarsCtx + searchDirs []string + rootKey string + + credentialsCache map[string]usernamePassword +} + +func NewVarsLoader(k *k8s.K8sCluster, varsCtx *VarsCtx, searchDirs []string, rootKey string) *VarsLoader { + return &VarsLoader{ + k: k, + varsCtx: varsCtx, + searchDirs: searchDirs, + rootKey: rootKey, + credentialsCache: map[string]usernamePassword{}, + } +} + +func (s *VarsLoader) LoadVarsList(varsList []*types.VarsSource) error { + for _, source := range varsList { + err := s.LoadVars(source) + if err != nil { + return err + } + } + return nil +} + +func (s *VarsLoader) LoadVars(sourceIn *types.VarsSource) error { + var source types.VarsSource + + err := s.varsCtx.J2.RenderStruct(&source, sourceIn, s.varsCtx.Vars) + if err != nil { + return err + } + + if source.Path != nil { + return s.loadFile(&source) + } else if source.SystemEnvVars != nil { + return s.loadSystemEnvs(&source) + } else if source.Http != nil { + return s.loadHttp(&source) + } else if source.AwsSecretsManager != nil { + return s.loadAwsSecretsManager(&source) + } else { + return fmt.Errorf("invalid vars source") + } +} + +func (s *VarsLoader) mergeVars(newVars *uo.UnstructuredObject) { + if s.rootKey == "" { + s.varsCtx.Update(newVars) + } else { + s.varsCtx.UpdateChild(s.rootKey, newVars) + } +} + +func (s *VarsLoader) loadFile(source *types.VarsSource) error { + var newVars uo.UnstructuredObject + err := s.varsCtx.RenderYamlFile(*source.Path, s.searchDirs, &newVars) + if err != nil { + return fmt.Errorf("failed to load vars from %s: %w", *source.Path, err) + } + s.mergeVars(&newVars) + return nil +} + +func (s *VarsLoader) loadSystemEnvs(source *types.VarsSource) error { + newVars := uo.New() + err := source.SystemEnvVars.NewIterator().IterateLeafs(func(it *uo.ObjectIterator) error { + envName, ok := it.Value().(string) + if !ok { + return fmt.Errorf("value at %s is not a string", it.KeyPath().ToJsonPath()) + } + envValue, ok := os.LookupEnv(envName) + if !ok { + return fmt.Errorf("environment variable %s not found for %s", envName, it.KeyPath().ToJsonPath()) + } + err := newVars.SetNestedField(envValue, it.KeyPath()...) + if err != nil { + return fmt.Errorf("failed to set value for %s: %w", it.KeyPath().ToJsonPath(), err) + } + return nil + }) + if err != nil { + return err + } + s.mergeVars(newVars) + return nil +} + +func (s *VarsLoader) loadAwsSecretsManager(source *types.VarsSource) error { + secret, err := aws.GetAwsSecretsManagerSecret(source.AwsSecretsManager.Profile, source.AwsSecretsManager.Region, source.AwsSecretsManager.SecretName) + if err != nil { + return err + } + + newVars, err := uo.FromString(secret) + if err != nil { + return fmt.Errorf("failed to parse yaml from AWS Secrets Manager (secretName=%s): %w", source.AwsSecretsManager.SecretName, err) + } + if s.rootKey != "" { + newVars, _, err = newVars.GetNestedObject(s.rootKey) + } + if newVars != nil { + s.mergeVars(newVars) + } + return nil +} diff --git a/pkg/kluctl_project/secrets_loader_http.go b/pkg/vars/vars_loader_http.go similarity index 73% rename from pkg/kluctl_project/secrets_loader_http.go rename to pkg/vars/vars_loader_http.go index 07b225c88..2d6b5fb1a 100644 --- a/pkg/kluctl_project/secrets_loader_http.go +++ b/pkg/vars/vars_loader_http.go @@ -1,4 +1,4 @@ -package kluctl_project +package vars import ( "crypto/tls" @@ -15,7 +15,7 @@ import ( "strings" ) -func (s *SecretsLoader) doHttp(httpSource *types.VarsSourceHttp, username string, password string) (*http.Response, string, error) { +func (s *VarsLoader) doHttp(httpSource *types.VarsSourceHttp, username string, password string) (*http.Response, string, error) { client := &http.Client{ Transport: ntlmssp.Negotiator{ RoundTripper: &http.Transport{ @@ -65,12 +65,12 @@ func (s *SecretsLoader) doHttp(httpSource *types.VarsSourceHttp, username string return resp, string(respBody), nil } -func (s *SecretsLoader) loadSecretsHttp(source *types.VarsSource) (*uo.UnstructuredObject, error) { +func (s *VarsLoader) loadHttp(source *types.VarsSource) error { resp, respBody, err := s.doHttp(source.Http, "", "") if err != nil && resp != nil && resp.StatusCode == http.StatusUnauthorized { chgs := challenge.ResponseChallenges(resp) if len(chgs) == 0 { - return nil, err + return err } var realms []string @@ -87,7 +87,7 @@ func (s *SecretsLoader) loadSecretsHttp(source *types.VarsSource) (*uo.Unstructu if !ok { username, password, err := utils.AskForCredentials(fmt.Sprintf("Please enter credentials for host '%s'", source.Http.Url.Host)) if err != nil { - return nil, err + return err } creds = usernamePassword{ username: username, @@ -98,52 +98,56 @@ func (s *SecretsLoader) loadSecretsHttp(source *types.VarsSource) (*uo.Unstructu resp, respBody, err = s.doHttp(source.Http, creds.username, creds.password) if err != nil { - return nil, err + return err } } else if err != nil { - return nil, err + return err } var respObj interface{} - var secrets *uo.UnstructuredObject + var newVars *uo.UnstructuredObject err = yaml.ReadYamlString(respBody, &respObj) if err != nil { - return nil, err + return err } if err != nil { - return nil, err + return err } if source.Http.JsonPath != nil { p, err := uo.NewMyJsonPath(*source.Http.JsonPath) if err != nil { - return nil, err + return err } x, ok := p.GetFirstFromAny(respObj) if !ok { - return nil, fmt.Errorf("%s not found in result from http request %s", *source.Http.JsonPath, source.Http.Url.String()) + return fmt.Errorf("%s not found in result from http request %s", *source.Http.JsonPath, source.Http.Url.String()) } s, ok := x.(string) if !ok { - return nil, fmt.Errorf("%s in result of http request %s is not a string", *source.Http.JsonPath, source.Http.Url.String()) + return fmt.Errorf("%s in result of http request %s is not a string", *source.Http.JsonPath, source.Http.Url.String()) } - secrets, err = uo.FromString(s) + newVars, err = uo.FromString(s) if err != nil { - return nil, err + return err } } else { x, ok := respObj.(map[string]interface{}) if !ok { - return nil, fmt.Errorf("result of http request %s is not an object", source.Http.Url.String()) + return fmt.Errorf("result of http request %s is not an object", source.Http.Url.String()) } - secrets = uo.FromMap(x) + newVars = uo.FromMap(x) } - secrets, ok, err := secrets.GetNestedObject("secrets") - if err != nil { - return nil, err + + if s.rootKey != "" { + newVars, _, err = newVars.GetNestedObject(s.rootKey) + if err != nil { + return err + } } - if !ok { - return uo.New(), nil + + if newVars != nil { + s.mergeVars(newVars) } - return secrets, nil + return nil } From 0fe611a7b5aa499d40ff19a4274a23fee4d94eb2 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 18 May 2022 18:04:53 +0200 Subject: [PATCH 0830/2916] feat: Unify loading of vars and secrets --- pkg/deployment/deployment_collection.go | 2 +- pkg/deployment/deployment_item.go | 5 +- pkg/deployment/deployment_project.go | 25 ++-- pkg/kluctl_project/target_context.go | 11 +- pkg/types/deployment.go | 52 +------- pkg/types/kluctl_project.go | 5 - pkg/types/vars_source.go | 49 ++++++-- pkg/vars/vars.go | 120 ------------------- pkg/vars/vars_loader.go | 152 ++++++++++++++++++------ pkg/vars/vars_loader_http.go | 18 +-- 10 files changed, 192 insertions(+), 247 deletions(-) diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index 4b845757b..64b100788 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -117,7 +117,7 @@ func (c *DeploymentCollection) RenderDeployments(k *k8s.K8sCluster) error { defer wp.StopWait(false) for _, d := range c.Deployments { - err := d.render(k, c.forSeal, wp) + err := d.render(c.forSeal, wp) if err != nil { return err } diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index e4ccacd3a..15463a61a 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -110,7 +110,7 @@ func (di *DeploymentItem) getCommonAnnotations() map[string]string { return a } -func (di *DeploymentItem) render(k *k8s.K8sCluster, forSeal bool, wp *utils.WorkerPoolWithErrors) error { +func (di *DeploymentItem) render(forSeal bool, wp *utils.WorkerPoolWithErrors) error { if di.dir == nil { return nil } @@ -123,7 +123,8 @@ func (di *DeploymentItem) render(k *k8s.K8sCluster, forSeal bool, wp *utils.Work } varsCtx := di.Project.VarsCtx.Copy() - err = varsCtx.LoadVarsList(k, di.Project.getRenderSearchDirs(), di.Config.Vars) + + err = di.Project.loadVarsList(varsCtx, di.Config.Vars) if err != nil { return err } diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go index 5b6638843..6ac7de718 100644 --- a/pkg/deployment/deployment_project.go +++ b/pkg/deployment/deployment_project.go @@ -16,8 +16,10 @@ import ( type DeploymentProject struct { ctx context.Context - VarsCtx *vars.VarsCtx - dir string + varsLoader *vars.VarsLoader + VarsCtx *vars.VarsCtx + + dir string SealedSecretsDir string DefaultSealedSecretsOutputPattern string @@ -30,9 +32,10 @@ type DeploymentProject struct { parentProjectInclude *types.DeploymentItemConfig } -func NewDeploymentProject(ctx context.Context, k *k8s.K8sCluster, varsCtx *vars.VarsCtx, dir string, sealedSecretsDir string, parentProject *DeploymentProject) (*DeploymentProject, error) { +func NewDeploymentProject(ctx context.Context, k *k8s.K8sCluster, varsLoader *vars.VarsLoader, varsCtx *vars.VarsCtx, dir string, sealedSecretsDir string, parentProject *DeploymentProject) (*DeploymentProject, error) { dp := &DeploymentProject{ ctx: ctx, + varsLoader: varsLoader, VarsCtx: varsCtx.Copy(), dir: dir, SealedSecretsDir: sealedSecretsDir, @@ -44,7 +47,7 @@ func NewDeploymentProject(ctx context.Context, k *k8s.K8sCluster, varsCtx *vars. return nil, fmt.Errorf("%s does not exist or is not a directory", dir) } - err := dp.loadConfig(k) + err := dp.loadConfig() if err != nil { return nil, fmt.Errorf("failed to load deployment config for %s: %w", dir, err) } @@ -57,13 +60,11 @@ func NewDeploymentProject(ctx context.Context, k *k8s.K8sCluster, varsCtx *vars. return dp, nil } -func (p *DeploymentProject) MergeSecretsIntoAllChildren(vars *uo.UnstructuredObject) { - for _, c := range p.getChildren(true, true) { - c.VarsCtx.UpdateChild("secrets", vars) - } +func (p *DeploymentProject) loadVarsList(varsCtx *vars.VarsCtx, varsList []*types.VarsSource) error { + return p.varsLoader.LoadVarsList(varsCtx, varsList, p.getRenderSearchDirs(), "") } -func (p *DeploymentProject) loadConfig(k *k8s.K8sCluster) error { +func (p *DeploymentProject) loadConfig() error { configPath := filepath.Join(p.dir, "deployment.yml") if !yaml.Exists(configPath) { if yaml.Exists(filepath.Join(p.dir, "kustomization.yml")) { @@ -77,7 +78,7 @@ func (p *DeploymentProject) loadConfig(k *k8s.K8sCluster) error { return fmt.Errorf("failed to load deployment.yml: %w", err) } - err = p.VarsCtx.LoadVarsList(k, p.getRenderSearchDirs(), p.Config.Vars) + err = p.loadVarsList(p.VarsCtx, p.Config.Vars) if err != nil { return fmt.Errorf("failed to load deployment.yml vars: %w", err) } @@ -162,12 +163,12 @@ func (p *DeploymentProject) loadIncludes(k *k8s.K8sCluster) error { incDir := filepath.Join(p.dir, *inc.Include) varsCtx := p.VarsCtx.Copy() - err := varsCtx.LoadVarsList(k, p.getRenderSearchDirs(), inc.Vars) + err := p.loadVarsList(varsCtx, inc.Vars) if err != nil { return err } - newProject, err := NewDeploymentProject(p.ctx, k, varsCtx, incDir, + newProject, err := NewDeploymentProject(p.ctx, k, p.varsLoader, varsCtx, incDir, p.SealedSecretsDir, p) if err != nil { return err diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index d3e86dc44..e8a5def9a 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -59,14 +59,16 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName s s.Success() } + varsLoader := vars.NewVarsLoader(ctx, k, p.grc) + if forSeal { - err = p.loadSecrets(k, target, varsCtx) + err = p.loadSecrets(target, varsCtx, varsLoader) if err != nil { return nil, err } } - d, err := deployment.NewDeploymentProject(ctx, k, varsCtx, deploymentDir, p.sealedSecretsDir, nil) + d, err := deployment.NewDeploymentProject(ctx, k, varsLoader, varsCtx, deploymentDir, p.sealedSecretsDir, nil) if err != nil { return nil, err } @@ -158,16 +160,15 @@ func (p *LoadedKluctlProject) findSecretsEntry(name string) (*types.SecretSet, e return nil, fmt.Errorf("secret Set with name %s was not found", name) } -func (p *LoadedKluctlProject) loadSecrets(k *k8s.K8sCluster, target *types.Target, varsCtx *vars.VarsCtx) error { +func (p *LoadedKluctlProject) loadSecrets(target *types.Target, varsCtx *vars.VarsCtx, varsLoader *vars.VarsLoader) error { searchDirs := []string{p.DeploymentDir} - secretsLoader := vars.NewVarsLoader(k, varsCtx, searchDirs, "secrets") for _, secretSetName := range target.SealingConfig.SecretSets { secretEntry, err := p.findSecretsEntry(secretSetName) if err != nil { return err } - err = secretsLoader.LoadVarsList(secretEntry.Sources) + err = varsLoader.LoadVarsList(varsCtx, secretEntry.Sources, searchDirs, "secrets") if err != nil { return err } diff --git a/pkg/types/deployment.go b/pkg/types/deployment.go index 754f54080..c9e3dce4b 100644 --- a/pkg/types/deployment.go +++ b/pkg/types/deployment.go @@ -2,8 +2,6 @@ package types import ( "github.com/go-playground/validator/v10" - git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" - "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" ) @@ -13,7 +11,7 @@ type DeploymentItemConfig struct { Tags []string `yaml:"tags,omitempty"` Barrier *bool `yaml:"barrier,omitempty"` WaitReadiness *bool `yaml:"waitReadiness,omitempty"` - Vars []*VarsListItem `yaml:"vars,omitempty"` + Vars []*VarsSource `yaml:"vars,omitempty"` SkipDeleteIfTags *bool `yaml:"skipDeleteIfTags,omitempty"` OnlyRender *bool `yaml:"onlyRender,omitempty"` AlwaysDeploy *bool `yaml:"alwaysDeploy,omitempty"` @@ -50,51 +48,6 @@ type DeploymentArg struct { Default interface{} `yaml:"default,omitempty"` } -type VarsListItemGit struct { - Url git_url.GitUrl `yaml:"url" validate:"required"` - Ref string `yaml:"ref,omitempty"` - Path string `yaml:"path" validate:"required"` -} - -type VarsListItemClusterConfigMapOrSecret struct { - Name string `yaml:"name" validate:"required"` - Namespace string `yaml:"namespace,omitempty"` - Key string `yaml:"key" validate:"required"` -} - -type VarsListItem struct { - Values *uo.UnstructuredObject `yaml:"values,omitempty"` - File *string `yaml:"file,omitempty"` - Git *VarsListItemGit `yaml:"git,omitempty"` - ClusterConfigMap *VarsListItemClusterConfigMapOrSecret `yaml:"clusterConfigMap,omitempty"` - ClusterSecret *VarsListItemClusterConfigMapOrSecret `yaml:"clusterSecret,omitempty"` -} - -func ValidateVarsListItem(sl validator.StructLevel) { - s := sl.Current().Interface().(VarsListItem) - count := 0 - if s.Values != nil { - count += 1 - } - if s.File != nil { - count += 1 - } - if s.Git != nil { - count += 1 - } - if s.ClusterConfigMap != nil { - count += 1 - } - if s.ClusterSecret != nil { - count += 1 - } - if count == 0 { - sl.ReportError(s, "self", "self", "invalidvars", "unknown vars type") - } else if count != 1 { - sl.ReportError(s, "self", "self", "invalidvars", "more then one vars type") - } -} - type SealedSecretsConfig struct { OutputPattern *string `yaml:"outputPattern,omitempty"` } @@ -127,7 +80,7 @@ type IgnoreForDiffItemConfig struct { type DeploymentProjectConfig struct { Args []*DeploymentArg `yaml:"args,omitempty"` - Vars []*VarsListItem `yaml:"vars,omitempty"` + Vars []*VarsSource `yaml:"vars,omitempty"` SealedSecrets *SealedSecretsConfig `yaml:"sealedSecrets,omitempty"` Deployments []*DeploymentItemConfig `yaml:"deployments,omitempty"` @@ -141,7 +94,6 @@ type DeploymentProjectConfig struct { } func init() { - yaml.Validator.RegisterStructValidation(ValidateVarsListItem, VarsListItem{}) yaml.Validator.RegisterStructValidation(ValidateDeploymentItemConfig, DeploymentItemConfig{}) yaml.Validator.RegisterStructValidation(ValidateDeleteObjectItemConfig, DeleteObjectItemConfig{}) } diff --git a/pkg/types/kluctl_project.go b/pkg/types/kluctl_project.go index c3084e7f3..12420ca83 100644 --- a/pkg/types/kluctl_project.go +++ b/pkg/types/kluctl_project.go @@ -2,7 +2,6 @@ package types import ( "github.com/kluctl/kluctl/v2/pkg/utils/uo" - "github.com/kluctl/kluctl/v2/pkg/yaml" ) type DynamicArg struct { @@ -66,7 +65,3 @@ type KluctlProject struct { Targets []*Target `yaml:"targets,omitempty"` SecretsConfig *SecretsConfig `yaml:"secretsConfig,omitempty"` } - -func init() { - yaml.Validator.RegisterStructValidation(ValidateSecretSource, VarsSource{}) -} diff --git a/pkg/types/vars_source.go b/pkg/types/vars_source.go index dec89ddd3..1dfa61beb 100644 --- a/pkg/types/vars_source.go +++ b/pkg/types/vars_source.go @@ -2,9 +2,23 @@ package types import ( "github.com/go-playground/validator/v10" + git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/yaml" ) +type VarsSourceGit struct { + Url git_url.GitUrl `yaml:"url" validate:"required"` + Ref string `yaml:"ref,omitempty"` + Path string `yaml:"path" validate:"required"` +} + +type VarsSourceClusterConfigMapOrSecret struct { + Name string `yaml:"name" validate:"required"` + Namespace string `yaml:"namespace,omitempty"` + Key string `yaml:"key" validate:"required"` +} + type VarsSourceHttp struct { Url YamlUrl `yaml:"url,omitempty" validate:"required"` Method *string `yaml:"method,omitempty"` @@ -23,18 +37,35 @@ type VarsSourceAwsSecretsManager struct { } type VarsSource struct { - Path *string `yaml:"path,omitempty"` - SystemEnvVars *uo.UnstructuredObject `yaml:"systemEnvVars,omitempty"` - Http *VarsSourceHttp `yaml:"http,omitempty"` - AwsSecretsManager *VarsSourceAwsSecretsManager `yaml:"awsSecretsManager,omitempty"` + Values *uo.UnstructuredObject `yaml:"values,omitempty"` + File *string `yaml:"file,omitempty"` + Path *string `yaml:"path,omitempty"` + Git *VarsSourceGit `yaml:"git,omitempty"` + ClusterConfigMap *VarsSourceClusterConfigMapOrSecret `yaml:"clusterConfigMap,omitempty"` + ClusterSecret *VarsSourceClusterConfigMapOrSecret `yaml:"clusterSecret,omitempty"` + SystemEnvVars *uo.UnstructuredObject `yaml:"systemEnvVars,omitempty"` + Http *VarsSourceHttp `yaml:"http,omitempty"` + AwsSecretsManager *VarsSourceAwsSecretsManager `yaml:"awsSecretsManager,omitempty"` } -func ValidateSecretSource(sl validator.StructLevel) { +func ValidateVarsSource(sl validator.StructLevel) { s := sl.Current().Interface().(VarsSource) count := 0 if s.Path != nil { count += 1 } + if s.File != nil { + count += 1 + } + if s.Git != nil { + count += 1 + } + if s.ClusterConfigMap != nil { + count += 1 + } + if s.ClusterSecret != nil { + count += 1 + } if s.SystemEnvVars != nil { count += 1 } @@ -45,8 +76,12 @@ func ValidateSecretSource(sl validator.StructLevel) { count += 1 } if count == 0 { - sl.ReportError(s, "self", "self", "invalidsource", "unknown secret source type") + sl.ReportError(s, "self", "self", "invalidsource", "unknown vars source type") } else if count != 1 { - sl.ReportError(s, "self", "self", "invalidsource", "more then one secret source type") + sl.ReportError(s, "self", "self", "invalidsource", "more then one vars source type") } } + +func init() { + yaml.Validator.RegisterStructValidation(ValidateVarsSource, VarsSource{}) +} diff --git a/pkg/vars/vars.go b/pkg/vars/vars.go index 67f818de5..b94ccff98 100644 --- a/pkg/vars/vars.go +++ b/pkg/vars/vars.go @@ -1,17 +1,10 @@ package vars import ( - "fmt" "github.com/kluctl/kluctl/v2/pkg/git" "github.com/kluctl/kluctl/v2/pkg/jinja2" - "github.com/kluctl/kluctl/v2/pkg/k8s" - "github.com/kluctl/kluctl/v2/pkg/types" - k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" - "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" - "io/ioutil" - "os" ) type VarsCtx struct { @@ -55,119 +48,6 @@ func (vc *VarsCtx) UpdateChildFromStruct(child string, o interface{}) error { return nil } -func (vc *VarsCtx) LoadVarsList(k *k8s.K8sCluster, searchDirs []string, varsList []*types.VarsListItem) error { - for _, v := range varsList { - if v.Values != nil { - vc.Update(v.Values) - } else if v.File != nil { - err := vc.loadVarsFile(*v.File, searchDirs) - if err != nil { - return err - } - } else if v.Git != nil { - err := vc.loadVarsGitFile(v.Git) - if err != nil { - return err - } - } else if v.ClusterConfigMap != nil { - ref := k8s2.NewObjectRef("", "v1", "ConfigMap", v.ClusterConfigMap.Name, v.ClusterConfigMap.Namespace) - err := vc.loadVarsFromK8sObject(k, ref, v.ClusterConfigMap.Key) - if err != nil { - return err - } - } else if v.ClusterSecret != nil { - ref := k8s2.NewObjectRef("", "v1", "Secret", v.ClusterSecret.Name, v.ClusterSecret.Namespace) - err := vc.loadVarsFromK8sObject(k, ref, v.ClusterSecret.Key) - if err != nil { - return err - } - } else { - return fmt.Errorf("invalid vars entry: %v", v) - } - } - - return nil -} - -func (vc *VarsCtx) loadVarsFile(p string, searchDirs []string) error { - var newVars uo.UnstructuredObject - err := vc.RenderYamlFile(p, searchDirs, &newVars) - if err != nil { - return fmt.Errorf("failed to load vars from %s: %w", p, err) - } - vc.Update(&newVars) - return nil -} - -func (vc *VarsCtx) loadVarsGitFile(gitFile *types.VarsListItemGit) error { - mr, err := vc.grc.GetMirroredGitRepo(gitFile.Url, true, true, true) - if err != nil { - return fmt.Errorf("failed to load vars from git repository %s: %w", gitFile.Url.String(), err) - } - - tmpDir, err := ioutil.TempDir(utils.GetTmpBaseDir(), "git-vars") - if err != nil { - return err - } - defer os.RemoveAll(tmpDir) - - file, err := mr.ReadFile(gitFile.Ref, gitFile.Path) - if err != nil { - return fmt.Errorf("failed to load vars from git repository %s and path %s: %w", gitFile.Url.String(), gitFile.Path, err) - } - - return vc.loadVarsFromString(string(file)) -} - -func (vc *VarsCtx) loadVarsFromK8sObject(k *k8s.K8sCluster, ref k8s2.ObjectRef, key string) error { - if k == nil { - return nil - } - - o, _, err := k.GetSingleObject(ref) - if err != nil { - return err - } - - value, found, err := o.GetNestedString("data", key) - if err != nil { - return err - } - if !found { - return fmt.Errorf("key %s not found in %s on cluster", key, ref.String()) - } - - err = vc.loadVarsFromString(value) - if err != nil { - return fmt.Errorf("failed to load vars from kubernetes object %s and key %s: %w", ref.String(), key, err) - } - return nil -} - -func (vc *VarsCtx) loadVarsFromString(s string) error { - var newVars uo.UnstructuredObject - err := vc.renderYamlString(s, &newVars) - if err != nil { - return err - } - vc.Update(&newVars) - return nil -} - -func (vc *VarsCtx) renderYamlString(s string, out interface{}) error { - ret, err := vc.J2.RenderString(s, nil, vc.Vars) - if err != nil { - return err - } - - err = yaml.ReadYamlString(ret, out) - if err != nil { - return err - } - - return nil -} - func (vc *VarsCtx) RenderYamlFile(p string, searchDirs []string, out interface{}) error { ret, err := vc.J2.RenderFile(p, searchDirs, vc.Vars) if err != nil { diff --git a/pkg/vars/vars_loader.go b/pkg/vars/vars_loader.go index 87bd01764..df011336f 100644 --- a/pkg/vars/vars_loader.go +++ b/pkg/vars/vars_loader.go @@ -1,11 +1,18 @@ package vars import ( + "context" "fmt" + "github.com/kluctl/kluctl/v2/pkg/git" "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" + k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/aws" "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/yaml" + "io/ioutil" "os" ) @@ -15,27 +22,25 @@ type usernamePassword struct { } type VarsLoader struct { - k *k8s.K8sCluster - varsCtx *VarsCtx - searchDirs []string - rootKey string + ctx context.Context + k *k8s.K8sCluster + grc *git.MirroredGitRepoCollection credentialsCache map[string]usernamePassword } -func NewVarsLoader(k *k8s.K8sCluster, varsCtx *VarsCtx, searchDirs []string, rootKey string) *VarsLoader { +func NewVarsLoader(ctx context.Context, k *k8s.K8sCluster, grc *git.MirroredGitRepoCollection) *VarsLoader { return &VarsLoader{ + ctx: ctx, k: k, - varsCtx: varsCtx, - searchDirs: searchDirs, - rootKey: rootKey, + grc: grc, credentialsCache: map[string]usernamePassword{}, } } -func (s *VarsLoader) LoadVarsList(varsList []*types.VarsSource) error { +func (v *VarsLoader) LoadVarsList(varsCtx *VarsCtx, varsList []*types.VarsSource, searchDirs []string, rootKey string) error { for _, source := range varsList { - err := s.LoadVars(source) + err := v.LoadVars(varsCtx, source, searchDirs, rootKey) if err != nil { return err } @@ -43,46 +48,58 @@ func (s *VarsLoader) LoadVarsList(varsList []*types.VarsSource) error { return nil } -func (s *VarsLoader) LoadVars(sourceIn *types.VarsSource) error { +func (v *VarsLoader) LoadVars(varsCtx *VarsCtx, sourceIn *types.VarsSource, searchDirs []string, rootKey string) error { var source types.VarsSource - err := s.varsCtx.J2.RenderStruct(&source, sourceIn, s.varsCtx.Vars) + err := varsCtx.J2.RenderStruct(&source, sourceIn, varsCtx.Vars) if err != nil { return err } - if source.Path != nil { - return s.loadFile(&source) + if source.Values != nil { + v.mergeVars(varsCtx, source.Values, rootKey) + } else if source.Path != nil { + status.Warning(v.ctx, "'path' is deprecated as vars source, use 'file' instead") + return v.loadFile(varsCtx, *source.Path, searchDirs, rootKey) + } else if source.File != nil { + return v.loadFile(varsCtx, *source.File, searchDirs, rootKey) + } else if source.Git != nil { + return v.loadGit(varsCtx, source.Git, rootKey) + } else if source.ClusterConfigMap != nil { + ref := k8s2.NewObjectRef("", "v1", "ConfigMap", source.ClusterConfigMap.Name, source.ClusterConfigMap.Namespace) + return v.loadFromK8sObject(varsCtx, ref, source.ClusterConfigMap.Key, rootKey) + } else if source.ClusterSecret != nil { + ref := k8s2.NewObjectRef("", "v1", "Secret", source.ClusterSecret.Name, source.ClusterSecret.Namespace) + return v.loadFromK8sObject(varsCtx, ref, source.ClusterSecret.Key, rootKey) } else if source.SystemEnvVars != nil { - return s.loadSystemEnvs(&source) + return v.loadSystemEnvs(varsCtx, &source, rootKey) } else if source.Http != nil { - return s.loadHttp(&source) + return v.loadHttp(varsCtx, &source, rootKey) } else if source.AwsSecretsManager != nil { - return s.loadAwsSecretsManager(&source) - } else { - return fmt.Errorf("invalid vars source") + return v.loadAwsSecretsManager(varsCtx, &source, rootKey) } + return fmt.Errorf("invalid vars source") } -func (s *VarsLoader) mergeVars(newVars *uo.UnstructuredObject) { - if s.rootKey == "" { - s.varsCtx.Update(newVars) +func (v *VarsLoader) mergeVars(varsCtx *VarsCtx, newVars *uo.UnstructuredObject, rootKey string) { + if rootKey == "" { + varsCtx.Update(newVars) } else { - s.varsCtx.UpdateChild(s.rootKey, newVars) + varsCtx.UpdateChild(rootKey, newVars) } } -func (s *VarsLoader) loadFile(source *types.VarsSource) error { +func (v *VarsLoader) loadFile(varsCtx *VarsCtx, path string, searchDirs []string, rootKey string) error { var newVars uo.UnstructuredObject - err := s.varsCtx.RenderYamlFile(*source.Path, s.searchDirs, &newVars) + err := varsCtx.RenderYamlFile(path, searchDirs, &newVars) if err != nil { - return fmt.Errorf("failed to load vars from %s: %w", *source.Path, err) + return fmt.Errorf("failed to load vars from %s: %w", path, err) } - s.mergeVars(&newVars) + v.mergeVars(varsCtx, &newVars, rootKey) return nil } -func (s *VarsLoader) loadSystemEnvs(source *types.VarsSource) error { +func (v *VarsLoader) loadSystemEnvs(varsCtx *VarsCtx, source *types.VarsSource, rootKey string) error { newVars := uo.New() err := source.SystemEnvVars.NewIterator().IterateLeafs(func(it *uo.ObjectIterator) error { envName, ok := it.Value().(string) @@ -102,25 +119,88 @@ func (s *VarsLoader) loadSystemEnvs(source *types.VarsSource) error { if err != nil { return err } - s.mergeVars(newVars) + v.mergeVars(varsCtx, newVars, rootKey) return nil } -func (s *VarsLoader) loadAwsSecretsManager(source *types.VarsSource) error { +func (v *VarsLoader) loadAwsSecretsManager(varsCtx *VarsCtx, source *types.VarsSource, rootKey string) error { secret, err := aws.GetAwsSecretsManagerSecret(source.AwsSecretsManager.Profile, source.AwsSecretsManager.Region, source.AwsSecretsManager.SecretName) if err != nil { return err } + return v.loadFromString(varsCtx, secret, rootKey) +} + +func (v *VarsLoader) loadGit(varsCtx *VarsCtx, gitFile *types.VarsSourceGit, rootKey string) error { + mr, err := v.grc.GetMirroredGitRepo(gitFile.Url, true, true, true) + if err != nil { + return fmt.Errorf("failed to load vars from git repository %s: %w", gitFile.Url.String(), err) + } - newVars, err := uo.FromString(secret) + tmpDir, err := ioutil.TempDir(utils.GetTmpBaseDir(), "git-vars") if err != nil { - return fmt.Errorf("failed to parse yaml from AWS Secrets Manager (secretName=%s): %w", source.AwsSecretsManager.SecretName, err) + return err } - if s.rootKey != "" { - newVars, _, err = newVars.GetNestedObject(s.rootKey) + defer os.RemoveAll(tmpDir) + + file, err := mr.ReadFile(gitFile.Ref, gitFile.Path) + if err != nil { + return fmt.Errorf("failed to load vars from git repository %s and path %s: %w", gitFile.Url.String(), gitFile.Path, err) } - if newVars != nil { - s.mergeVars(newVars) + + return v.loadFromString(varsCtx, string(file), rootKey) +} + +func (v *VarsLoader) loadFromK8sObject(varsCtx *VarsCtx, ref k8s2.ObjectRef, key string, rootKey string) error { + if v.k == nil { + return nil } + + o, _, err := v.k.GetSingleObject(ref) + if err != nil { + return err + } + + value, found, err := o.GetNestedString("data", key) + if err != nil { + return err + } + if !found { + return fmt.Errorf("key %s not found in %s on cluster", key, ref.String()) + } + + err = v.loadFromString(varsCtx, value, rootKey) + if err != nil { + return fmt.Errorf("failed to load vars from kubernetes object %s and key %s: %w", ref.String(), key, err) + } + return nil +} + +func (v *VarsLoader) loadFromString(varsCtx *VarsCtx, s string, rootKey string) error { + newVars := uo.New() + err := v.renderYamlString(varsCtx, s, newVars) + if err != nil { + return err + } + + if rootKey != "" { + newVars, _, err = newVars.GetNestedObject(rootKey) + } + + v.mergeVars(varsCtx, newVars, rootKey) + return nil +} + +func (v *VarsLoader) renderYamlString(varsCtx *VarsCtx, s string, out interface{}) error { + ret, err := varsCtx.J2.RenderString(s, nil, varsCtx.Vars) + if err != nil { + return err + } + + err = yaml.ReadYamlString(ret, out) + if err != nil { + return err + } + return nil } diff --git a/pkg/vars/vars_loader_http.go b/pkg/vars/vars_loader_http.go index 2d6b5fb1a..18cd60695 100644 --- a/pkg/vars/vars_loader_http.go +++ b/pkg/vars/vars_loader_http.go @@ -15,7 +15,7 @@ import ( "strings" ) -func (s *VarsLoader) doHttp(httpSource *types.VarsSourceHttp, username string, password string) (*http.Response, string, error) { +func (v *VarsLoader) doHttp(httpSource *types.VarsSourceHttp, username string, password string) (*http.Response, string, error) { client := &http.Client{ Transport: ntlmssp.Negotiator{ RoundTripper: &http.Transport{ @@ -65,8 +65,8 @@ func (s *VarsLoader) doHttp(httpSource *types.VarsSourceHttp, username string, p return resp, string(respBody), nil } -func (s *VarsLoader) loadHttp(source *types.VarsSource) error { - resp, respBody, err := s.doHttp(source.Http, "", "") +func (v *VarsLoader) loadHttp(varsCtx *VarsCtx, source *types.VarsSource, rootKey string) error { + resp, respBody, err := v.doHttp(source.Http, "", "") if err != nil && resp != nil && resp.StatusCode == http.StatusUnauthorized { chgs := challenge.ResponseChallenges(resp) if len(chgs) == 0 { @@ -83,7 +83,7 @@ func (s *VarsLoader) loadHttp(source *types.VarsSource) error { } credsKey := fmt.Sprintf("%s|%s", source.Http.Url.Host, strings.Join(realms, "+")) - creds, ok := s.credentialsCache[credsKey] + creds, ok := v.credentialsCache[credsKey] if !ok { username, password, err := utils.AskForCredentials(fmt.Sprintf("Please enter credentials for host '%s'", source.Http.Url.Host)) if err != nil { @@ -93,10 +93,10 @@ func (s *VarsLoader) loadHttp(source *types.VarsSource) error { username: username, password: password, } - s.credentialsCache[credsKey] = creds + v.credentialsCache[credsKey] = creds } - resp, respBody, err = s.doHttp(source.Http, creds.username, creds.password) + resp, respBody, err = v.doHttp(source.Http, creds.username, creds.password) if err != nil { return err } @@ -139,15 +139,15 @@ func (s *VarsLoader) loadHttp(source *types.VarsSource) error { newVars = uo.FromMap(x) } - if s.rootKey != "" { - newVars, _, err = newVars.GetNestedObject(s.rootKey) + if rootKey != "" { + newVars, _, err = newVars.GetNestedObject(rootKey) if err != nil { return err } } if newVars != nil { - s.mergeVars(newVars) + v.mergeVars(varsCtx, newVars, rootKey) } return nil } From b5a746eb7616ec08accebd3e00ef3394a81824c0 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 18 May 2022 18:22:29 +0200 Subject: [PATCH 0831/2916] feat: Rename sources to vars in secretSets --- pkg/kluctl_project/target_context.go | 7 ++++++- pkg/types/kluctl_project.go | 20 ++++++++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index e8a5def9a..f8d73fce4 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -168,7 +168,12 @@ func (p *LoadedKluctlProject) loadSecrets(target *types.Target, varsCtx *vars.Va if err != nil { return err } - err = varsLoader.LoadVarsList(varsCtx, secretEntry.Sources, searchDirs, "secrets") + if len(secretEntry.Sources) != 0 { + status.Warning(p.ctx, "'sources' in secretSets is deprecated, use 'vars' instead") + err = varsLoader.LoadVarsList(varsCtx, secretEntry.Sources, searchDirs, "secrets") + } else { + err = varsLoader.LoadVarsList(varsCtx, secretEntry.Vars, searchDirs, "secrets") + } if err != nil { return err } diff --git a/pkg/types/kluctl_project.go b/pkg/types/kluctl_project.go index 12420ca83..e25426570 100644 --- a/pkg/types/kluctl_project.go +++ b/pkg/types/kluctl_project.go @@ -1,7 +1,9 @@ package types import ( + "github.com/go-playground/validator/v10" "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/yaml" ) type DynamicArg struct { @@ -43,8 +45,18 @@ type DynamicTarget struct { } type SecretSet struct { - Name string `yaml:"name" validate:"required"` - Sources []*VarsSource `yaml:"sources" validate:"required,gt=0"` + Name string `yaml:"name" validate:"required"` + // TODO deprecated, use vars instead + Sources []*VarsSource `yaml:"sources,omitempty"` + Vars []*VarsSource `yaml:"vars,omitempty"` +} + +func ValidateSecretSet(sl validator.StructLevel) { + s := sl.Current().Interface().(SecretSet) + + if len(s.Sources) != 0 && len(s.Vars) != 0 { + sl.ReportError(s, "vars", "vars", "invalidsource", "sources and vars can't be set at the same time") + } } type GlobalSealedSecretsConfig struct { @@ -65,3 +77,7 @@ type KluctlProject struct { Targets []*Target `yaml:"targets,omitempty"` SecretsConfig *SecretsConfig `yaml:"secretsConfig,omitempty"` } + +func init() { + yaml.Validator.RegisterStructValidation(ValidateSecretSet, SecretSet{}) +} From 3e8b4fd7a590ad60f4339e18f72bb4f9565a679e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 19 May 2022 10:00:45 +0200 Subject: [PATCH 0832/2916] fix: Fix status reporting in helm-update 1. Add missing prefix when new versions are detected 2. Use proper prefix (dirName instead of baseName) 3. Properly report success when no new version is detected 4. Show skipUpdate info when new versions are detected --- cmd/kluctl/commands/cmd_helm_update.go | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go index 2d9f7e5f6..59e3df5d9 100644 --- a/cmd/kluctl/commands/cmd_helm_update.go +++ b/cmd/kluctl/commands/cmd_helm_update.go @@ -38,21 +38,31 @@ func (cmd *helmUpdateCmd) Run() error { return err } - statusPrefix := filepath.Base(p) + statusPrefix := filepath.Base(filepath.Dir(p)) s := status.Start(cliCtx, "%s: Checking for updates", statusPrefix) defer s.Failed() + skipUpdate := chart.Config.SkipUpdate != nil && *chart.Config.SkipUpdate + newVersion, updated, err := chart.CheckUpdate() if err != nil { return err } if !updated { + s.Update("%s: Version %s is already up-to-date.", statusPrefix, *chart.Config.ChartVersion) + s.Success() return nil } - s.Update(fmt.Sprintf("Chart has new version %s available. Old version is %s.", newVersion, *chart.Config.ChartVersion)) + msg := fmt.Sprintf("%s: Chart has new version %s available. Old version is %s.", statusPrefix, newVersion, *chart.Config.ChartVersion) + if skipUpdate { + msg += " skipUpdate is set to true." + } + s.Update(msg) - if cmd.Upgrade { - if chart.Config.SkipUpdate != nil && *chart.Config.SkipUpdate { + if !cmd.Upgrade { + s.Success() + } else { + if skipUpdate { s.Update("%s: NOT upgrading chart as skipUpdate was set to true", statusPrefix) s.Success() return nil From c203138f652ae0fe73430555f273eb93357a569f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 19 May 2022 10:43:57 +0200 Subject: [PATCH 0833/2916] docs: Add a few badges to README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 099728ed9..ba92599c6 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # kluctl +[![tests](https://github.com/kluctl/kluctl/workflows/tests/badge.svg)](https://github.com/kluctl/kluctl/actions) +[![license](https://img.shields.io/github/license/kluctl/kluctl.svg)](https://github.com/kluctl/kluctl/blob/main/LICENSE) +[![release](https://img.shields.io/github/release/kluctl/kluctl/all.svg)](https://github.com/kluctl/kluctl/releases) + kluctl From 94e2cb275b9b0deb306ed0aa0e3df31bb2e2104e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 19 May 2022 10:46:38 +0200 Subject: [PATCH 0834/2916] docs: Update README.md with contents from kluctl.io/docs --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ba92599c6..6cd3ba354 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Kluctl does not depend on external operators/controllers and allows to use the s as long as access to the kluctl project and clusters is available. This means, that you can use it from your local machine, from your CI/CD pipelines or any automation platform/system that allows to call custom tools. -Flux support is in development and will come soon. +Flux support is in alpha statium and available via the [flux-kluctl-controller](https://github.com/kluctl/flux-kluctl-controller). ## Installation @@ -41,7 +41,7 @@ Documentation can be found here: https://kluctl.io/docs | --- | --- | | 💪 Kluctl handles all your deployments | You can manage all your deployments with Kluctl, including infrastructure related and your applications. | | 🪶 Complex or simple, all the same | You can manage complex and simple deployments with Kluctl. Simple deployments are lightweight while complex deployment are easily manageable. | -| 🤖 Native git support | Kluctl has native Git support integrated, meaning that it can easily deploy remote Kluctl projects or externalize parts (e.g. cluster configs) of your Kluctl project. | +| 🤖 Native git support | Kluctl has native Git support integrated, meaning that it can easily deploy remote Kluctl projects or externalize parts (e.g. configuration) of your Kluctl project. | | 🪐 Multiple environments | Deploy the same deployment to multiple environments (dev, test, prod, ...), with flexible differences in configuration. | | 🌌 Multiple clusters | Manage multiple target clusters (in multiple clouds or bare-metal if you want). | | 🔩 Configuration and Templating | Kluctl allows to use templating in nearly all places, making it easy to have dynamic configuration. | From b4b4969b122001219001bb1ef03e92ed8a86584a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 19 May 2022 11:41:03 +0200 Subject: [PATCH 0835/2916] fix: Switch go go-internal based lockedfile This will hopefull fix the deadlocking tests seen on Windows. --- go.mod | 6 ++-- go.sum | 1 - pkg/git/mirrored_repo.go | 60 +++++++++++++++++++-------------- pkg/utils/embed_util/extract.go | 7 ++-- 4 files changed, 41 insertions(+), 33 deletions(-) diff --git a/go.mod b/go.mod index 4c81d771f..4958074bb 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,6 @@ require ( github.com/go-playground/validator/v10 v10.10.1 github.com/gobwas/glob v0.2.3 github.com/goccy/go-yaml v1.9.5 - github.com/gofrs/flock v0.8.1 github.com/golang-jwt/jwt/v4 v4.4.1 github.com/google/go-containerregistry v0.8.0 github.com/hashicorp/go-version v1.4.0 @@ -27,6 +26,7 @@ require ( github.com/ohler55/ojg v1.14.0 github.com/pkg/errors v0.9.1 github.com/r3labs/diff/v2 v2.15.1 + github.com/rogpeppe/go-internal v1.8.0 github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v1.4.0 github.com/spf13/pflag v1.0.5 @@ -43,8 +43,10 @@ require ( gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b helm.sh/helm/v3 v3.8.2 k8s.io/api v0.24.0-rc.1 + k8s.io/apiextensions-apiserver v0.23.6 k8s.io/apimachinery v0.24.0-rc.1 k8s.io/client-go v0.24.0-rc.1 + k8s.io/klog/v2 v2.60.1 sigs.k8s.io/kind v0.12.0 sigs.k8s.io/kustomize/api v0.11.4 sigs.k8s.io/kustomize/kyaml v0.13.6 @@ -186,11 +188,9 @@ require ( gopkg.in/ini.v1 v1.66.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/apiextensions-apiserver v0.23.6 // indirect k8s.io/apiserver v0.23.6 // indirect k8s.io/cli-runtime v0.24.0-rc.1 // indirect k8s.io/component-base v0.24.0-rc.1 // indirect - k8s.io/klog/v2 v2.60.1 // indirect k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect k8s.io/kubectl v0.23.5 // indirect k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect diff --git a/go.sum b/go.sum index 3c91b81d5..dc7ef08f9 100644 --- a/go.sum +++ b/go.sum @@ -581,7 +581,6 @@ github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godror/godror v0.24.2/go.mod h1:wZv/9vPiUib6tkoDl+AZ/QLf5YZgMravZ7jxH2eQWAE= -github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= diff --git a/pkg/git/mirrored_repo.go b/pkg/git/mirrored_repo.go index a18128a5b..60abfe25e 100644 --- a/pkg/git/mirrored_repo.go +++ b/pkg/git/mirrored_repo.go @@ -11,13 +11,14 @@ import ( git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/rogpeppe/go-internal/lockedfile" "io/ioutil" "os" "path/filepath" "strings" + "sync" "time" ) -import "github.com/gofrs/flock" var cacheBaseDir = filepath.Join(utils.GetTmpBaseDir(), "git-cache") @@ -28,7 +29,11 @@ type MirroredGitRepo struct { mirrorDir string hasUpdated bool - fileLock *flock.Flock + + fileLock *lockedfile.File + fileLockPath string + + mutex sync.Mutex } func NewMirroredGitRepo(ctx context.Context, u git_url.GitUrl) (*MirroredGitRepo, error) { @@ -45,7 +50,8 @@ func NewMirroredGitRepo(ctx context.Context, u git_url.GitUrl) (*MirroredGitRepo return nil, fmt.Errorf("failed to create mirror repo for %v: %w", u.String(), err) } } - o.fileLock = flock.New(filepath.Join(o.mirrorDir, ".cache.lock")) + + o.fileLockPath = filepath.Join(o.mirrorDir, ".cache.lock") return o, nil } @@ -58,39 +64,43 @@ func (g *MirroredGitRepo) SetUpdated(u bool) { } func (g *MirroredGitRepo) Lock() error { - ok, err := g.fileLock.TryLockContext(g.ctx, time.Millisecond*100) - if err != nil { - return fmt.Errorf("locking of %s failed: %w", g.fileLock.Path(), err) + g.mutex.Lock() + defer g.mutex.Unlock() + + if g.fileLock != nil { + return fmt.Errorf("file %s already locked", g.fileLockPath) } - if !ok { - return fmt.Errorf("locking of %s failed: unkown reason", g.fileLock.Path()) + + var err error + g.fileLock, err = lockedfile.Create(g.fileLockPath) + if err != nil { + return fmt.Errorf("locking of %s failed: %w", g.fileLockPath, err) } + return nil } func (g *MirroredGitRepo) Unlock() error { - err := g.fileLock.Unlock() - if err != nil { - status.Warning(g.ctx, "Unlock of %s failed: %v", g.fileLock.Path(), err) - return err + g.mutex.Lock() + defer g.mutex.Unlock() + + if g.fileLock == nil { + return fmt.Errorf("file %s is not locked", g.fileLockPath) } - return nil -} -func (g *MirroredGitRepo) WithLock(cb func() error) error { - err := g.Lock() + err := g.fileLock.Close() if err != nil { + status.Warning(g.ctx, "Unlock of %s failed: %v", g.fileLockPath, err) return err } - defer g.Unlock() - return cb() + g.fileLock = nil + return nil } -func (g *MirroredGitRepo) MaybeWithLock(lock bool, cb func() error) error { - if lock { - return g.WithLock(cb) - } - return cb() +func (g *MirroredGitRepo) IsLocked() bool { + g.mutex.Lock() + defer g.mutex.Unlock() + return g.fileLock != nil } func (g *MirroredGitRepo) LastUpdateTime() time.Time { @@ -330,7 +340,7 @@ func (g *MirroredGitRepo) Update(authProviders *auth2.GitAuthProviders) error { } func (g *MirroredGitRepo) CloneProject(ref string, targetDir string) error { - if !g.fileLock.Locked() || !g.hasUpdated { + if !g.IsLocked() || !g.hasUpdated { panic("tried to clone from a project that is not locked/updated") } @@ -344,7 +354,7 @@ func (g *MirroredGitRepo) CloneProject(ref string, targetDir string) error { } func (g *MirroredGitRepo) ReadFile(ref string, path string) ([]byte, error) { - if !g.fileLock.Locked() || !g.hasUpdated { + if !g.IsLocked() || !g.hasUpdated { panic("tried to read a file from a project that is not locked/updated") } diff --git a/pkg/utils/embed_util/extract.go b/pkg/utils/embed_util/extract.go index 22267376c..e3c5dc0d4 100644 --- a/pkg/utils/embed_util/extract.go +++ b/pkg/utils/embed_util/extract.go @@ -2,8 +2,8 @@ package embed_util import ( "fmt" - "github.com/gofrs/flock" "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/rogpeppe/go-internal/lockedfile" "io" "io/fs" "io/ioutil" @@ -21,12 +21,11 @@ func ExtractTarToTmp(r io.Reader, fileListR io.Reader, targetPrefix string) (str targetPath := fmt.Sprintf("%s-%s", targetPrefix, utils.Sha256Bytes(fileList)[:16]) - fl := flock.New(targetPath + ".lock") - err = fl.Lock() + fl, err := lockedfile.Create(targetPath + ".lock") if err != nil { return "", err } - defer fl.Unlock() + defer fl.Close() needsExtract, expectedTarGzHash, err := checkExtractNeeded(targetPath, string(fileList)) if err != nil { From 5e605e3140a52743724be06cf9751b10e3fe4227 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 19 May 2022 11:41:26 +0200 Subject: [PATCH 0836/2916] fix: Hold git locks for the whole time git is accessed and then release all locks --- cmd/kluctl/commands/utils.go | 14 ++++++++++---- pkg/git/repo_collection.go | 8 ++++++-- pkg/kluctl_project/git.go | 4 ++-- pkg/kluctl_project/load.go | 6 +----- pkg/kluctl_project/project.go | 2 +- pkg/kluctl_project/project_load.go | 8 ++------ pkg/kluctl_project/target_context.go | 4 ++-- pkg/kluctl_project/targets.go | 4 ++-- 8 files changed, 26 insertions(+), 24 deletions(-) diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index a7ad73631..8dab17d0d 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -55,6 +55,12 @@ func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, strictTemplates b status.Warning(cliCtx, "Failed to detect git project root. This might cause follow-up errors") } + ctx, cancel := context.WithTimeout(cliCtx, projectFlags.Timeout) + defer cancel() + + grc := git.NewMirroredGitRepoCollection(ctx, auth.NewDefaultAuthProviders(), projectFlags.GitCacheUpdateInterval) + defer grc.Clear() + loadArgs := kluctl_project.LoadKluctlProjectArgs{ RepoRoot: repoRoot, ProjectDir: cwd, @@ -67,13 +73,10 @@ func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, strictTemplates b FromArchive: projectFlags.FromArchive.String(), FromArchiveMetadata: projectFlags.FromArchiveMetadata.String(), AllowGitClone: projectFlags.FromArchive == "", - GitAuthProviders: auth.NewDefaultAuthProviders(), - GitUpdateInterval: projectFlags.GitCacheUpdateInterval, + GRC: grc, ClientConfigGetter: clientConfigGetter(forCompletion), } - ctx, cancel := context.WithTimeout(cliCtx, projectFlags.Timeout) - defer cancel() p, err := kluctl_project.LoadKluctlProject(ctx, loadArgs, tmpDir, j2) if err != nil { return err @@ -181,6 +184,9 @@ func withProjectTargetCommandContext(ctx context.Context, args projectTargetComm images: images, } + // we can assume that all git access is done at this point, so we can clear grc and thus unlock all repos + p.GRC.Clear() + return cb(cmdCtx) } diff --git a/pkg/git/repo_collection.go b/pkg/git/repo_collection.go index fbdf14ed6..0269bf69f 100644 --- a/pkg/git/repo_collection.go +++ b/pkg/git/repo_collection.go @@ -31,13 +31,17 @@ func NewMirroredGitRepoCollection(ctx context.Context, authProviders *auth.GitAu } } -func (g *MirroredGitRepoCollection) UnlockAll() { +func (g *MirroredGitRepoCollection) Clear() { g.mutex.Lock() defer g.mutex.Unlock() for _, e := range g.repos { - _ = e.mr.Unlock() + if e.mr.IsLocked() { + _ = e.mr.Unlock() + } } + + g.repos = map[string]*entry{} } func (g *MirroredGitRepoCollection) GetMirroredGitRepo(url git_url.GitUrl, allowCreate bool, lockRepo bool, update bool) (*MirroredGitRepo, error) { diff --git a/pkg/kluctl_project/git.go b/pkg/kluctl_project/git.go index b64c6ebb7..3d3a2584a 100644 --- a/pkg/kluctl_project/git.go +++ b/pkg/kluctl_project/git.go @@ -32,7 +32,7 @@ func (c *LoadedKluctlProject) updateGitCaches() error { go func() { defer waitGroup.Done() - _, err := c.grc.GetMirroredGitRepo(u, true, true, true) + _, err := c.GRC.GetMirroredGitRepo(u, true, true, true) if err != nil { doError(fmt.Errorf("failed to update git project %v: %v", u.String(), err)) } @@ -87,7 +87,7 @@ func (c *LoadedKluctlProject) cloneGitProject(gitProject *types2.GitProject, tar return } - mr, err := c.grc.GetMirroredGitRepo(gitProject.Url, true, true, true) + mr, err := c.GRC.GetMirroredGitRepo(gitProject.Url, true, true, true) if err != nil { return git.GitRepoInfo{}, err } diff --git a/pkg/kluctl_project/load.go b/pkg/kluctl_project/load.go index 5938483bd..801d66f6d 100644 --- a/pkg/kluctl_project/load.go +++ b/pkg/kluctl_project/load.go @@ -3,21 +3,17 @@ package kluctl_project import ( "context" "fmt" - "github.com/kluctl/kluctl/v2/pkg/git" "github.com/kluctl/kluctl/v2/pkg/jinja2" "github.com/kluctl/kluctl/v2/pkg/types" ) func LoadKluctlProject(ctx context.Context, args LoadKluctlProjectArgs, tmpDir string, j2 *jinja2.Jinja2) (*LoadedKluctlProject, error) { - grc := git.NewMirroredGitRepoCollection(ctx, args.GitAuthProviders, args.GitUpdateInterval) - defer grc.UnlockAll() - p := &LoadedKluctlProject{ ctx: ctx, loadArgs: args, TmpDir: tmpDir, J2: j2, - grc: grc, + GRC: args.GRC, involvedRepos: map[string][]types.InvolvedRepo{}, } diff --git a/pkg/kluctl_project/project.go b/pkg/kluctl_project/project.go index 5196cc387..98a460568 100644 --- a/pkg/kluctl_project/project.go +++ b/pkg/kluctl_project/project.go @@ -30,7 +30,7 @@ type LoadedKluctlProject struct { DynamicTargets []*types2.DynamicTarget J2 *jinja2.Jinja2 - grc *git.MirroredGitRepoCollection + GRC *git.MirroredGitRepoCollection } func (c *LoadedKluctlProject) GetMetadata() *types2.ProjectMetadata { diff --git a/pkg/kluctl_project/project_load.go b/pkg/kluctl_project/project_load.go index b3811864e..f241a485d 100644 --- a/pkg/kluctl_project/project_load.go +++ b/pkg/kluctl_project/project_load.go @@ -3,7 +3,6 @@ package kluctl_project import ( "fmt" "github.com/kluctl/kluctl/v2/pkg/git" - auth2 "github.com/kluctl/kluctl/v2/pkg/git/auth" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/status" types2 "github.com/kluctl/kluctl/v2/pkg/types" @@ -14,7 +13,6 @@ import ( "k8s.io/client-go/tools/clientcmd/api" "os" "path/filepath" - "time" ) type LoadKluctlProjectArgs struct { @@ -29,10 +27,8 @@ type LoadKluctlProjectArgs struct { FromArchive string FromArchiveMetadata string - AllowGitClone bool - GitAuthProviders *auth2.GitAuthProviders - - GitUpdateInterval time.Duration + AllowGitClone bool + GRC *git.MirroredGitRepoCollection ClientConfigGetter func(context *string) (*rest.Config, *api.Config, error) } diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index f8d73fce4..27a0b163e 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -59,7 +59,7 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName s s.Success() } - varsLoader := vars.NewVarsLoader(ctx, k, p.grc) + varsLoader := vars.NewVarsLoader(ctx, k, p.GRC) if forSeal { err = p.loadSecrets(target, varsCtx, varsLoader) @@ -108,7 +108,7 @@ func (p *LoadedKluctlProject) buildVars(target *types.Target, clusterName *strin return doError(err) } - varsCtx := vars.NewVarsCtx(p.J2, p.grc) + varsCtx := vars.NewVarsCtx(p.J2, p.GRC) err = varsCtx.UpdateChildFromStruct("cluster", clusterConfig.Cluster) if err != nil { return doError(err) diff --git a/pkg/kluctl_project/targets.go b/pkg/kluctl_project/targets.go index d5bbdb66b..f7c18262f 100644 --- a/pkg/kluctl_project/targets.go +++ b/pkg/kluctl_project/targets.go @@ -83,7 +83,7 @@ func (c *LoadedKluctlProject) renderTarget(target *types.Target) (*types.Target, var errors []error curTarget := target for i := 0; i < 10; i++ { - varsCtx := vars.NewVarsCtx(c.J2, c.grc) + varsCtx := vars.NewVarsCtx(c.J2, c.GRC) err := varsCtx.UpdateChildFromStruct("target", curTarget) if err != nil { return nil, err @@ -134,7 +134,7 @@ func (c *LoadedKluctlProject) prepareDynamicTargetsSimple(baseTarget *types.Targ } func (c *LoadedKluctlProject) prepareDynamicTargetsExternal(baseTarget *types.Target) ([]*dynamicTargetInfo, error) { - mr, err := c.grc.GetMirroredGitRepo(baseTarget.TargetConfig.Project.Url, true, true, true) + mr, err := c.GRC.GetMirroredGitRepo(baseTarget.TargetConfig.Project.Url, true, true, true) if err != nil { return nil, err } From 79bec8bd6a0361743ccce384b91986e12cfec1fc Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 19 May 2022 12:23:01 +0200 Subject: [PATCH 0837/2916] build: Create draft releases instead of directly publishing them --- .goreleaser.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index ea3c4cf97..029ace461 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -64,6 +64,7 @@ changelog: - '^refactor:' release: + draft: true prerelease: auto name_template: "{{.ProjectName}}-{{.Tag}}" From 52720402e8cbd5c2a312fa7a74dcffee4fe62c6a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 19 May 2022 13:56:53 +0200 Subject: [PATCH 0838/2916] fix: Remove KluctlBytecodeCache from jinja2 renderer This cache implementation caused errors when the same file was present in two different projects. Turns out rendering is actually so fast that we really don't need that cache. --- pkg/jinja2/python_src/jinja2_cache.py | 170 ----------------------- pkg/jinja2/python_src/jinja2_renderer.py | 4 +- 2 files changed, 1 insertion(+), 173 deletions(-) delete mode 100644 pkg/jinja2/python_src/jinja2_cache.py diff --git a/pkg/jinja2/python_src/jinja2_cache.py b/pkg/jinja2/python_src/jinja2_cache.py deleted file mode 100644 index 8d37ec2d4..000000000 --- a/pkg/jinja2/python_src/jinja2_cache.py +++ /dev/null @@ -1,170 +0,0 @@ -import errno -import fnmatch -import os -import stat -import tempfile -import typing -from datetime import datetime -from types import CodeType - -from jinja2 import BytecodeCache -from jinja2.bccache import Bucket - -def get_tmp_base_dir(): - dir = os.path.join(tempfile.gettempdir(), "kluctl-workdir") - os.makedirs(dir, exist_ok=True) - return dir - -""" -A bytecode cache that is able to share bytecode between different directories. It does this by removing filenames -from dumped code and then later re-adding them after loading. - -This cache is also thread and multi-process safe. -""" -class KluctlBytecodeCache(BytecodeCache): - def __init__(self, max_cache_files): - self.cache_dir = self._get_cache_dir() - self.max_cache_files = max_cache_files - self.did_touch = set() - self.clear_old_entries() - - def _get_cache_dir(self) -> str: - def _unsafe_dir() -> "te.NoReturn": - raise RuntimeError( - "Cannot determine safe temp directory. You " - "need to explicitly provide one." - ) - - tmpdir = os.path.join(get_tmp_base_dir(), "jinja2-cache") - - # On windows the temporary directory is used specific unless - # explicitly forced otherwise. We can just use that. - if os.name == "nt": - os.makedirs(tmpdir, exist_ok=True) - return tmpdir - if not hasattr(os, "getuid"): - _unsafe_dir() - - tmpdir = os.path.join(tmpdir, str(os.getuid())) - - try: - os.makedirs(tmpdir, exist_ok=True, mode=stat.S_IRWXU) - except OSError as e: - if e.errno != errno.EEXIST: - raise - try: - os.chmod(tmpdir, stat.S_IRWXU) - actual_dir_stat = os.lstat(tmpdir) - if ( - actual_dir_stat.st_uid != os.getuid() - or not stat.S_ISDIR(actual_dir_stat.st_mode) - or stat.S_IMODE(actual_dir_stat.st_mode) != stat.S_IRWXU - ): - _unsafe_dir() - except OSError as e: - if e.errno != errno.EEXIST: - raise - - actual_dir_stat = os.lstat(tmpdir) - if ( - actual_dir_stat.st_uid != os.getuid() - or not stat.S_ISDIR(actual_dir_stat.st_mode) - or stat.S_IMODE(actual_dir_stat.st_mode) != stat.S_IRWXU - ): - _unsafe_dir() - - return tmpdir - - def get_cache_key(self, name: str, filename: typing.Optional[typing.Union[str]] = None): - return (name, filename) - - def _get_cache_filename(self, bucket: Bucket) -> str: - return os.path.join(self.cache_dir, bucket.checksum[:2], bucket.checksum) + ".cache" - - def touch_loaded_marker(self, bucket): - filename = self._get_cache_filename(bucket) + ".loaded" - if filename in self.did_touch: - return - try: - with open(filename + ".tmp", mode="wt") as f: - f.write(str(datetime.utcnow())) - os.rename(filename + ".tmp", filename) - except: - # Failure here it "ok" and is mostly happening on Windows here (permission denied for opened files...ugh..) - pass - self.did_touch.add(filename) - - def replace_code_filenames(self, code, old, new): - co_filename = code.co_filename - co_consts = [] - if co_filename == old: - co_filename = new - - for c in code.co_consts: - if isinstance(c, CodeType): - co_consts.append(self.replace_code_filenames(c, old, new)) - else: - co_consts.append(c) - - return code.replace(co_filename=co_filename, co_consts=tuple(co_consts)) - - def load_bytecode(self, bucket: Bucket) -> None: - filename = self._get_cache_filename(bucket) - - if os.path.exists(filename): - for i in range(4): - try: - with open(filename, "rb") as f: - bucket.load_bytecode(f) - if bucket.code is not None: - bucket.code = self.replace_code_filenames(bucket.code, "", bucket.key[1]) - except PermissionError: - # Retry. Windows is still failing from time to time... - continue - self.touch_loaded_marker(bucket) - - def dump_bytecode(self, bucket: Bucket) -> None: - # thread/multi-process safe version of super().dump_bytecode() - - try: - filename = self._get_cache_filename(bucket) - os.makedirs(os.path.dirname(filename), exist_ok=True) - - with open(filename + ".tmp", "wb") as f: - orig_code = bucket.code - bucket.code = self.replace_code_filenames(bucket.code, bucket.key[1], "") - bucket.write_bytecode(f) - bucket.code = orig_code - os.rename(filename + ".tmp", filename) - self.touch_loaded_marker(bucket) - except: - # Failure here it "ok" and is mostly happening on Windows here (permission denied for opened files...ugh..) - pass - - def clear_old_entries(self): - files = [] - for d in os.listdir(self.cache_dir): - path = os.path.join(self.cache_dir, d) - if not os.path.isdir(path): - continue - for n in os.listdir(path): - if not fnmatch.fnmatch(n, "*.cache"): - continue - files.append(os.path.join(path, n)) - - if len(files) <= self.max_cache_files: - return - times = {} - for f in files: - try: - with open(f + ".loaded", mode="rt") as f2: - times[f] = datetime.fromisoformat(f2.read()) - except: - times[f] = datetime.min - files.sort(key=lambda f: times[f], reverse=True) - for f in files[self.max_cache_files:]: - try: - os.remove(f) - os.remove(f + ".loaded") - except: - pass diff --git a/pkg/jinja2/python_src/jinja2_renderer.py b/pkg/jinja2/python_src/jinja2_renderer.py index 735379917..52e868b30 100644 --- a/pkg/jinja2/python_src/jinja2_renderer.py +++ b/pkg/jinja2/python_src/jinja2_renderer.py @@ -4,10 +4,8 @@ from jinja2 import StrictUndefined, FileSystemLoader, ChainableUndefined from dict_utils import merge_dict -from jinja2_cache import KluctlBytecodeCache from jinja2_utils import KluctlJinja2Environment, add_jinja2_filters, extract_template_error -jinja2_cache = KluctlBytecodeCache(max_cache_files=10000) begin_placeholder = "XXXXXbegin_get_image_" end_placeholder = "_end_get_imageXXXXX" @@ -76,7 +74,7 @@ def build_env(self, vars_str, search_dirs, strict): environment = KluctlJinja2Environment(loader=FileSystemLoader(search_dirs), undefined=StrictUndefined if strict else NullUndefined, cache_size=10000, - bytecode_cache=jinja2_cache, auto_reload=False) + auto_reload=False) merge_dict(environment.globals, vars, clone=False) add_jinja2_filters(environment) From 3d960f6247dec5c8816b900f9bb25e4474de9e04 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 19 May 2022 13:58:16 +0200 Subject: [PATCH 0839/2916] fix: Report struct validation errors as tags The "param" of ReportError is ignored later when the error is printed, so we can't use it for custom error messages. The output looks a little bit strange now, but better this way than having it omitted completely. --- pkg/types/deployment.go | 6 +++--- pkg/types/git_project.go | 6 +++--- pkg/types/kluctl_project.go | 2 +- pkg/types/vars_source.go | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pkg/types/deployment.go b/pkg/types/deployment.go index c9e3dce4b..b129aa8b8 100644 --- a/pkg/types/deployment.go +++ b/pkg/types/deployment.go @@ -21,10 +21,10 @@ type DeploymentItemConfig struct { func ValidateDeploymentItemConfig(sl validator.StructLevel) { s := sl.Current().Interface().(DeploymentItemConfig) if s.Path != nil && s.Include != nil { - sl.ReportError(s, "path", "Path", "pathinclude", "path and include can not be set at the same time") + sl.ReportError(s, "path", "Path", "path and include can not be set at the same time", "") } if s.Path == nil && s.WaitReadiness != nil { - sl.ReportError(s, "waitReadiness", "WaitReadiness", "waitreadiness", "only kustomize deployments are allowed to have waitReadiness set") + sl.ReportError(s, "waitReadiness", "WaitReadiness", "only kustomize deployments are allowed to have waitReadiness set", "") } } @@ -39,7 +39,7 @@ type DeleteObjectItemConfig struct { func ValidateDeleteObjectItemConfig(sl validator.StructLevel) { s := sl.Current().Interface().(DeleteObjectItemConfig) if s.Group == nil && s.Version == nil && s.Kind == nil { - sl.ReportError(s, "self", "self", "missingfield", "at least one of group/version/kind must be set") + sl.ReportError(s, "self", "self", "at least one of group/version/kind must be set", "") } } diff --git a/pkg/types/git_project.go b/pkg/types/git_project.go index 6d6cbc4a3..efa7c6dc8 100644 --- a/pkg/types/git_project.go +++ b/pkg/types/git_project.go @@ -25,7 +25,7 @@ func (gp *GitProject) UnmarshalYAML(unmarshal func(interface{}) error) error { func ValidateGitProject(sl validator.StructLevel) { gp := sl.Current().Interface().(GitProject) if strings.Index(gp.SubDir, "/") != -1 { - sl.ReportError(gp.SubDir, "subDir", "SubDir", "subdirinvalid", "/ is not allowed in subdir") + sl.ReportError(gp.SubDir, "subDir", "SubDir", "/ is not allowed in subDir", "") } } @@ -37,9 +37,9 @@ type ExternalProject struct { func ValidateExternalProject(sl validator.StructLevel) { p := sl.Current().Interface().(ExternalProject) if p.Project == nil && p.Path == nil { - sl.ReportError(p, ".", ".", "empty", "either project or path must be set") + sl.ReportError(p, ".", ".", "either project or path must be set", "") } else if p.Project != nil && p.Path != nil { - sl.ReportError(p, ".", ".", "empty", "only one of project or path can be set") + sl.ReportError(p, ".", ".", "only one of project or path can be set", "") } } diff --git a/pkg/types/kluctl_project.go b/pkg/types/kluctl_project.go index e25426570..9ecb1fcdd 100644 --- a/pkg/types/kluctl_project.go +++ b/pkg/types/kluctl_project.go @@ -55,7 +55,7 @@ func ValidateSecretSet(sl validator.StructLevel) { s := sl.Current().Interface().(SecretSet) if len(s.Sources) != 0 && len(s.Vars) != 0 { - sl.ReportError(s, "vars", "vars", "invalidsource", "sources and vars can't be set at the same time") + sl.ReportError(s, "vars", "vars", "sources and vars can't be set at the same time", "") } } diff --git a/pkg/types/vars_source.go b/pkg/types/vars_source.go index 1dfa61beb..60fee3214 100644 --- a/pkg/types/vars_source.go +++ b/pkg/types/vars_source.go @@ -76,9 +76,9 @@ func ValidateVarsSource(sl validator.StructLevel) { count += 1 } if count == 0 { - sl.ReportError(s, "self", "self", "invalidsource", "unknown vars source type") + sl.ReportError(s, "self", "self", "unknown vars source type", "") } else if count != 1 { - sl.ReportError(s, "self", "self", "invalidsource", "more then one vars source type") + sl.ReportError(s, "self", "self", "more then one vars source type", "") } } From 6d47b4ce6916e1c15e8969bc9a11a222825c31fa Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 19 May 2022 13:59:07 +0200 Subject: [PATCH 0840/2916] fix: Fix validation of VarsSource Use reflection for counting the number of set fields, as otherwise we'll miss fields again in the future (e.g. as happened with Values). --- pkg/types/vars_source.go | 31 ++++++++----------------------- pkg/vars/vars_loader.go | 1 + 2 files changed, 9 insertions(+), 23 deletions(-) diff --git a/pkg/types/vars_source.go b/pkg/types/vars_source.go index 60fee3214..84e15f393 100644 --- a/pkg/types/vars_source.go +++ b/pkg/types/vars_source.go @@ -5,6 +5,7 @@ import ( git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" + "reflect" ) type VarsSourceGit struct { @@ -50,31 +51,15 @@ type VarsSource struct { func ValidateVarsSource(sl validator.StructLevel) { s := sl.Current().Interface().(VarsSource) + count := 0 - if s.Path != nil { - count += 1 - } - if s.File != nil { - count += 1 - } - if s.Git != nil { - count += 1 - } - if s.ClusterConfigMap != nil { - count += 1 - } - if s.ClusterSecret != nil { - count += 1 - } - if s.SystemEnvVars != nil { - count += 1 - } - if s.Http != nil { - count += 1 - } - if s.AwsSecretsManager != nil { - count += 1 + v := reflect.ValueOf(s) + for i := 0; i < v.NumField(); i++ { + if !v.Field(i).IsNil() { + count += 1 + } } + if count == 0 { sl.ReportError(s, "self", "self", "unknown vars source type", "") } else if count != 1 { diff --git a/pkg/vars/vars_loader.go b/pkg/vars/vars_loader.go index df011336f..e6f912b29 100644 --- a/pkg/vars/vars_loader.go +++ b/pkg/vars/vars_loader.go @@ -58,6 +58,7 @@ func (v *VarsLoader) LoadVars(varsCtx *VarsCtx, sourceIn *types.VarsSource, sear if source.Values != nil { v.mergeVars(varsCtx, source.Values, rootKey) + return nil } else if source.Path != nil { status.Warning(v.ctx, "'path' is deprecated as vars source, use 'file' instead") return v.loadFile(varsCtx, *source.Path, searchDirs, rootKey) From 3d5319f915a9752b09c0796c95e4f567bcde1c75 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 19 May 2022 14:26:38 +0200 Subject: [PATCH 0841/2916] refactor: No need to pass grc to VarsCtx --- pkg/kluctl_project/target_context.go | 2 +- pkg/kluctl_project/targets.go | 2 +- pkg/vars/vars.go | 6 +----- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index 27a0b163e..f18b0a880 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -108,7 +108,7 @@ func (p *LoadedKluctlProject) buildVars(target *types.Target, clusterName *strin return doError(err) } - varsCtx := vars.NewVarsCtx(p.J2, p.GRC) + varsCtx := vars.NewVarsCtx(p.J2) err = varsCtx.UpdateChildFromStruct("cluster", clusterConfig.Cluster) if err != nil { return doError(err) diff --git a/pkg/kluctl_project/targets.go b/pkg/kluctl_project/targets.go index f7c18262f..d8a20bcef 100644 --- a/pkg/kluctl_project/targets.go +++ b/pkg/kluctl_project/targets.go @@ -83,7 +83,7 @@ func (c *LoadedKluctlProject) renderTarget(target *types.Target) (*types.Target, var errors []error curTarget := target for i := 0; i < 10; i++ { - varsCtx := vars.NewVarsCtx(c.J2, c.GRC) + varsCtx := vars.NewVarsCtx(c.J2) err := varsCtx.UpdateChildFromStruct("target", curTarget) if err != nil { return nil, err diff --git a/pkg/vars/vars.go b/pkg/vars/vars.go index b94ccff98..c29d4ba18 100644 --- a/pkg/vars/vars.go +++ b/pkg/vars/vars.go @@ -1,7 +1,6 @@ package vars import ( - "github.com/kluctl/kluctl/v2/pkg/git" "github.com/kluctl/kluctl/v2/pkg/jinja2" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" @@ -9,14 +8,12 @@ import ( type VarsCtx struct { J2 *jinja2.Jinja2 - grc *git.MirroredGitRepoCollection Vars *uo.UnstructuredObject } -func NewVarsCtx(j2 *jinja2.Jinja2, grc *git.MirroredGitRepoCollection) *VarsCtx { +func NewVarsCtx(j2 *jinja2.Jinja2) *VarsCtx { vc := &VarsCtx{ J2: j2, - grc: grc, Vars: uo.New(), } return vc @@ -25,7 +22,6 @@ func NewVarsCtx(j2 *jinja2.Jinja2, grc *git.MirroredGitRepoCollection) *VarsCtx func (vc *VarsCtx) Copy() *VarsCtx { cp := &VarsCtx{ J2: vc.J2, - grc: vc.grc, Vars: vc.Vars.Clone(), } return cp From 5cec077190d5dbc3ba6cab1b723a0f0c2b0e1067 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 19 May 2022 14:26:45 +0200 Subject: [PATCH 0842/2916] tests: Add vars_test.go --- pkg/vars/vars_test.go | 66 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 pkg/vars/vars_test.go diff --git a/pkg/vars/vars_test.go b/pkg/vars/vars_test.go new file mode 100644 index 000000000..b922b7279 --- /dev/null +++ b/pkg/vars/vars_test.go @@ -0,0 +1,66 @@ +package vars + +import ( + "github.com/kluctl/kluctl/v2/pkg/jinja2" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestVarsCtx(t *testing.T) { + j2, err := jinja2.NewJinja2() + if err != nil { + t.Fatal(err) + } + defer j2.Close() + + varsCtx := NewVarsCtx(j2) + varsCtx.Update(uo.FromMap(map[string]interface{}{ + "test1": map[string]interface{}{ + "test2": 42, + }, + })) + v, _, _ := varsCtx.Vars.GetNestedInt("test1", "test2") + assert.Equal(t, int64(42), v) +} + +func TestVarsCtxChild(t *testing.T) { + j2, err := jinja2.NewJinja2() + if err != nil { + t.Fatal(err) + } + defer j2.Close() + + varsCtx := NewVarsCtx(j2) + varsCtx.UpdateChild("child", uo.FromMap(map[string]interface{}{ + "test1": map[string]interface{}{ + "test2": 42, + }, + })) + v, _, _ := varsCtx.Vars.GetNestedInt("child", "test1", "test2") + assert.Equal(t, int64(42), v) +} + +func TestVarsCtxStruct(t *testing.T) { + j2, err := jinja2.NewJinja2() + if err != nil { + t.Fatal(err) + } + defer j2.Close() + + varsCtx := NewVarsCtx(j2) + + s := struct { + Test1 struct { + Test2 int + } + }{ + Test1: struct{ Test2 int }{Test2: 42}, + } + + err = varsCtx.UpdateChildFromStruct("child", s) + assert.NoError(t, err) + + v, _, _ := varsCtx.Vars.GetNestedInt("child", "test1", "test2") + assert.Equal(t, int64(42), v) +} From a3fcd851255bbb9d43643e2dcb687b55f34a6203 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 19 May 2022 16:09:41 +0200 Subject: [PATCH 0843/2916] refactor: Move test git server code into own package to make it reusable --- e2e/project.go | 235 +++++------------------------- internal/test-utils/git_server.go | 223 ++++++++++++++++++++++++++++ 2 files changed, 259 insertions(+), 199 deletions(-) create mode 100644 internal/test-utils/git_server.go diff --git a/e2e/project.go b/e2e/project.go index 6068a7ceb..fa5f18e8c 100644 --- a/e2e/project.go +++ b/e2e/project.go @@ -1,17 +1,12 @@ package e2e import ( - "context" "fmt" "github.com/go-git/go-git/v5" - http_server "github.com/kluctl/kluctl/v2/pkg/git/http-server" + test_utils "github.com/kluctl/kluctl/v2/internal/test-utils" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" - log "github.com/sirupsen/logrus" - "io/ioutil" - "net" - "net/http" "os" "os/exec" "path/filepath" @@ -36,49 +31,37 @@ type testProject struct { kubeconfigs []string - baseDir string - - gitServer *http_server.Server - gitHttpServer *http.Server - gitServerPort int + gitServer *test_utils.GitServer } func (p *testProject) init(t *testing.T, projectName string) { p.t = t + p.gitServer = test_utils.NewGitServer(t) p.projectName = projectName - baseDir, err := ioutil.TempDir(os.TempDir(), "kluctl-e2e-") - if err != nil { - p.t.Fatal(err) - } - p.baseDir = baseDir - - _ = os.MkdirAll(p.getKluctlProjectDir(), 0o700) - _ = os.MkdirAll(filepath.Join(p.getClustersDir(), "clusters"), 0o700) - _ = os.MkdirAll(filepath.Join(p.getSealedSecretsDir(), ".sealed-secrets"), 0o700) - _ = os.MkdirAll(p.getDeploymentDir(), 0o700) - - p.initGitServer() - p.gitInit(p.getKluctlProjectDir()) + p.gitServer.GitInit(p.getKluctlProjectRepo()) if p.clustersExternal { - p.gitInit(p.getClustersDir()) + p.gitServer.GitInit(p.getClustersRepo()) } if p.deploymentExternal { - p.gitInit(p.getDeploymentDir()) + p.gitServer.GitInit(p.getDeploymentRepo()) } if p.sealedSecretsExternal { - p.gitInit(p.getSealedSecretsDir()) + p.gitServer.GitInit(p.getSealedSecretsRepo()) } + _ = os.MkdirAll(filepath.Join(p.gitServer.LocalRepoDir(p.getClustersRepo()), "clusters"), 0o700) + _ = os.MkdirAll(filepath.Join(p.gitServer.LocalRepoDir(p.getSealedSecretsRepo()), ".sealed-secrets"), 0o700) + p.updateKluctlYaml(func(o *uo.UnstructuredObject) error { if p.clustersExternal { - o.SetNestedField(p.buildLocalGitUrl(p.getClustersDir()), "clusters", "project") + o.SetNestedField(p.gitServer.LocalGitUrl(p.getClustersRepo()), "clusters", "project") } if p.deploymentExternal { - o.SetNestedField(p.buildLocalGitUrl(p.getDeploymentDir()), "deployment", "project") + o.SetNestedField(p.gitServer.LocalGitUrl(p.getDeploymentRepo()), "deployment", "project") } if p.sealedSecretsExternal { - o.SetNestedField(p.buildLocalGitUrl(p.getSealedSecretsDir()), "sealedSecrets", "project") + o.SetNestedField(p.gitServer.LocalGitUrl(p.getSealedSecretsRepo()), "sealedSecrets", "project") } return nil }) @@ -87,147 +70,19 @@ func (p *testProject) init(t *testing.T, projectName string) { }) } -func (p *testProject) initGitServer() { - p.gitServer = http_server.New(p.baseDir) - - p.gitHttpServer = &http.Server{ - Addr: "127.0.0.1:0", - Handler: p.gitServer, - } - - ln, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - log.Fatal(err) - } - a := ln.Addr().(*net.TCPAddr) - p.gitServerPort = a.Port - - go func() { - _ = p.gitHttpServer.Serve(ln) - }() -} - func (p *testProject) cleanup() { - if p.gitHttpServer != nil { - _ = p.gitHttpServer.Shutdown(context.Background()) - p.gitHttpServer = nil + if p.gitServer != nil { + p.gitServer.Cleanup() p.gitServer = nil } - - if p.baseDir == "" { - return - } - _ = os.RemoveAll(p.baseDir) - p.baseDir = "" -} - -func (p *testProject) gitInit(dir string) { - err := os.MkdirAll(dir, 0o700) - if err != nil { - p.t.Fatal(err) - } - - r, err := git.PlainInit(dir, false) - if err != nil { - p.t.Fatal(err) - } - config, err := r.Config() - if err != nil { - p.t.Fatal(err) - } - wt, err := r.Worktree() - if err != nil { - p.t.Fatal(err) - } - - config.User.Name = "Test User" - config.User.Email = "no@mail.com" - config.Author = config.User - config.Committer = config.User - err = r.SetConfig(config) - if err != nil { - p.t.Fatal(err) - } - err = utils.Touch(filepath.Join(dir, ".dummy")) - if err != nil { - p.t.Fatal(err) - } - _, err = wt.Add(".dummy") - if err != nil { - p.t.Fatal(err) - } - _, err = wt.Commit("initial", &git.CommitOptions{}) - if err != nil { - p.t.Fatal(err) - } -} - -func (p *testProject) commitFiles(repo string, add []string, all bool, message string) { - r, err := git.PlainOpen(repo) - if err != nil { - p.t.Fatal(err) - } - wt, err := r.Worktree() - if err != nil { - p.t.Fatal(err) - } - for _, a := range add { - _, err = wt.Add(a) - if err != nil { - p.t.Fatal(err) - } - } - _, err = wt.Commit(message, &git.CommitOptions{ - All: all, - }) - if err != nil { - p.t.Fatal(err) - } -} - -func (p *testProject) commitYaml(y *uo.UnstructuredObject, repo string, pth string, message string) { - err := yaml.WriteYamlFile(filepath.Join(repo, pth), y) - if err != nil { - p.t.Fatal(err) - } - if message == "" { - relPath, err := filepath.Rel(p.baseDir, repo) - if err != nil { - p.t.Fatal(err) - } - message = fmt.Sprintf("update %s", filepath.Join(relPath, pth)) - } - p.commitFiles(repo, []string{pth}, false, message) -} - -func (p *testProject) updateYaml(repo string, pth string, update func(o *uo.UnstructuredObject) error, message string) { - if !strings.HasPrefix(repo, p.baseDir) { - p.t.Fatal() - } - o := uo.New() - if utils.Exists(filepath.Join(repo, pth)) { - err := yaml.ReadYamlFile(filepath.Join(repo, pth), o) - if err != nil { - p.t.Fatal(err) - } - } - orig := o.Clone() - err := update(o) - if err != nil { - p.t.Fatal(err) - } - if reflect.DeepEqual(o, orig) { - return - } - p.commitYaml(o, repo, pth, message) } func (p *testProject) updateKluctlYaml(update func(o *uo.UnstructuredObject) error) { - p.updateYaml(p.getKluctlProjectDir(), ".kluctl.yml", update, "") + p.gitServer.UpdateYaml(p.getKluctlProjectRepo(), ".kluctl.yml", update, "") } func (p *testProject) updateDeploymentYaml(dir string, update func(o *uo.UnstructuredObject) error) { - p.updateYaml(p.getDeploymentDir(), filepath.Join(dir, "deployment.yml"), func(o *uo.UnstructuredObject) error { + p.gitServer.UpdateYaml(p.getDeploymentRepo(), filepath.Join(dir, "deployment.yml"), func(o *uo.UnstructuredObject) error { if dir == "." { o.SetNestedField(p.projectName, "commonLabels", "project_name") } @@ -236,7 +91,7 @@ func (p *testProject) updateDeploymentYaml(dir string, update func(o *uo.Unstruc } func (p *testProject) getDeploymentYaml(dir string) *uo.UnstructuredObject { - o, err := uo.FromFile(filepath.Join(p.getDeploymentDir(), dir, "deployment.yml")) + o, err := uo.FromFile(filepath.Join(p.gitServer.LocalRepoDir(p.getDeploymentRepo()), dir, "deployment.yml")) if err != nil { p.t.Fatal(err) } @@ -268,24 +123,17 @@ func (p *testProject) listDeploymentItemPathes(dir string, fullPath bool) []stri } func (p *testProject) updateKustomizeDeployment(dir string, update func(o *uo.UnstructuredObject, wt *git.Worktree) error) { - r, err := git.PlainOpen(p.getDeploymentDir()) - if err != nil { - p.t.Fatal(err) - } - wt, err := r.Worktree() - if err != nil { - p.t.Fatal(err) - } + wt := p.gitServer.GetWorktree(p.getDeploymentRepo()) pth := filepath.Join(dir, "kustomization.yml") - p.updateYaml(p.getDeploymentDir(), pth, func(o *uo.UnstructuredObject) error { + p.gitServer.UpdateYaml(p.getDeploymentRepo(), pth, func(o *uo.UnstructuredObject) error { return update(o, wt) }, fmt.Sprintf("Update kustomization.yml for %s", dir)) } func (p *testProject) updateCluster(name string, context string, vars *uo.UnstructuredObject) { pth := filepath.Join("clusters", fmt.Sprintf("%s.yml", name)) - p.updateYaml(p.getClustersDir(), pth, func(o *uo.UnstructuredObject) error { + p.gitServer.UpdateYaml(p.getClustersRepo(), pth, func(o *uo.UnstructuredObject) error { o.Clear() o.SetNestedField(name, "cluster", "name") o.SetNestedField(context, "cluster", "context") @@ -379,7 +227,7 @@ func (p *testProject) addKustomizeDeployment(dir string, resources []kustomizeRe p.addDeploymentIncludes(deploymentDir) } - absKustomizeDir := filepath.Join(p.getDeploymentDir(), dir) + absKustomizeDir := filepath.Join(p.gitServer.LocalRepoDir(p.getDeploymentRepo()), dir) err := os.MkdirAll(absKustomizeDir, 0o700) if err != nil { @@ -438,7 +286,7 @@ func (p *testProject) addKustomizeResources(dir string, resources []kustomizeRes for _, r := range resources { l = append(l, r.name) x := p.convertInterfaceToList(r.content) - err := yaml.WriteYamlAllFile(filepath.Join(p.getDeploymentDir(), dir, r.name), x) + err := yaml.WriteYamlAllFile(filepath.Join(p.gitServer.LocalRepoDir(p.getDeploymentRepo()), dir, r.name), x) if err != nil { return err } @@ -467,34 +315,31 @@ func (p *testProject) deleteKustomizeDeployment(dir string) { }) } -func (p *testProject) getKluctlProjectDir() string { - return filepath.Join(p.baseDir, "kluctl-project") +func (p *testProject) getKluctlProjectRepo() string { + return "kluctl-project" } -func (p *testProject) getClustersDir() string { +func (p *testProject) getClustersRepo() string { if p.clustersExternal { - return filepath.Join(p.baseDir, "external-clusters") + return "external-clusters" } - return p.getKluctlProjectDir() + return p.getKluctlProjectRepo() } -func (p *testProject) getDeploymentDir() string { +func (p *testProject) getDeploymentRepo() string { if p.deploymentExternal { - return filepath.Join(p.baseDir, "external-deployment") + return "external-deployment" } - return p.getKluctlProjectDir() + return p.getKluctlProjectRepo() } -func (p *testProject) getSealedSecretsDir() string { +func (p *testProject) getSealedSecretsRepo() string { if p.sealedSecretsExternal { - return filepath.Join(p.baseDir, "external-sealed-secrets") + return "external-sealed-secrets" } - return p.getKluctlProjectDir() + return p.getKluctlProjectRepo() } -const stdoutStartMarker = "========= stdout start =========" -const stdoutEndMarker = "========= stdout end =========" - func (p *testProject) Kluctl(argsIn ...string) (string, string, error) { var args []string args = append(args, argsIn...) @@ -502,9 +347,9 @@ func (p *testProject) Kluctl(argsIn ...string) (string, string, error) { cwd := "" if p.kluctlProjectExternal { - args = append(args, "--project-url", p.buildLocalGitUrl(p.getKluctlProjectDir())) + args = append(args, "--project-url", p.gitServer.LocalGitUrl(p.getKluctlProjectRepo())) } else { - cwd = p.getKluctlProjectDir() + cwd = p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()) } if p.localClusters != nil { @@ -561,11 +406,3 @@ func (p *testProject) KluctlMust(argsIn ...string) (string, string) { } return stdout, stderr } - -func (p *testProject) buildLocalGitUrl(repo string) string { - relPth, err := filepath.Rel(p.baseDir, repo) - if err != nil { - log.Panic(err) - } - return fmt.Sprintf("http://localhost:%d/%s/.git", p.gitServerPort, relPth) -} diff --git a/internal/test-utils/git_server.go b/internal/test-utils/git_server.go new file mode 100644 index 000000000..29727fdeb --- /dev/null +++ b/internal/test-utils/git_server.go @@ -0,0 +1,223 @@ +package test_utils + +import ( + "context" + "fmt" + "github.com/go-git/go-git/v5" + http_server "github.com/kluctl/kluctl/v2/pkg/git/http-server" + "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/yaml" + "io/ioutil" + "log" + "net" + "net/http" + "os" + "path/filepath" + "reflect" + "testing" +) + +type GitServer struct { + t *testing.T + + baseDir string + + gitServer *http_server.Server + gitHttpServer *http.Server + gitServerPort int +} + +func NewGitServer(t *testing.T) *GitServer { + p := &GitServer{ + t: t, + } + + baseDir, err := ioutil.TempDir(os.TempDir(), "kluctl-tests-") + if err != nil { + p.t.Fatal(err) + } + p.baseDir = baseDir + + p.initGitServer() + + t.Cleanup(func() { + p.Cleanup() + }) + + return p +} + +func (p *GitServer) initGitServer() { + p.gitServer = http_server.New(p.baseDir) + + p.gitHttpServer = &http.Server{ + Addr: "127.0.0.1:0", + Handler: p.gitServer, + } + + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + log.Fatal(err) + } + a := ln.Addr().(*net.TCPAddr) + p.gitServerPort = a.Port + + go func() { + _ = p.gitHttpServer.Serve(ln) + }() +} + +func (p *GitServer) Cleanup() { + if p.gitHttpServer != nil { + _ = p.gitHttpServer.Shutdown(context.Background()) + p.gitHttpServer = nil + p.gitServer = nil + } + + if p.baseDir == "" { + return + } + _ = os.RemoveAll(p.baseDir) + p.baseDir = "" +} + +func (p *GitServer) GitInit(repo string) { + dir := p.LocalRepoDir(repo) + + err := os.MkdirAll(dir, 0o700) + if err != nil { + p.t.Fatal(err) + } + + r, err := git.PlainInit(dir, false) + if err != nil { + p.t.Fatal(err) + } + config, err := r.Config() + if err != nil { + p.t.Fatal(err) + } + wt, err := r.Worktree() + if err != nil { + p.t.Fatal(err) + } + + config.User.Name = "Test User" + config.User.Email = "no@mail.com" + config.Author = config.User + config.Committer = config.User + err = r.SetConfig(config) + if err != nil { + p.t.Fatal(err) + } + err = utils.Touch(filepath.Join(dir, ".dummy")) + if err != nil { + p.t.Fatal(err) + } + _, err = wt.Add(".dummy") + if err != nil { + p.t.Fatal(err) + } + _, err = wt.Commit("initial", &git.CommitOptions{}) + if err != nil { + p.t.Fatal(err) + } +} + +func (p *GitServer) CommitFiles(repo string, add []string, all bool, message string) { + r, err := git.PlainOpen(p.LocalRepoDir(repo)) + if err != nil { + p.t.Fatal(err) + } + wt, err := r.Worktree() + if err != nil { + p.t.Fatal(err) + } + for _, a := range add { + _, err = wt.Add(a) + if err != nil { + p.t.Fatal(err) + } + } + _, err = wt.Commit(message, &git.CommitOptions{ + All: all, + }) + if err != nil { + p.t.Fatal(err) + } +} + +func (p *GitServer) CommitYaml(repo string, pth string, message string, y *uo.UnstructuredObject) { + fullPath := filepath.Join(p.LocalRepoDir(repo), pth) + + err := yaml.WriteYamlFile(fullPath, y) + if err != nil { + p.t.Fatal(err) + } + if message == "" { + message = fmt.Sprintf("update %s", filepath.Join(repo, pth)) + } + p.CommitFiles(repo, []string{pth}, false, message) +} + +func (p *GitServer) UpdateYaml(repo string, pth string, update func(o *uo.UnstructuredObject) error, message string) { + fullPath := filepath.Join(p.LocalRepoDir(repo), pth) + + o := uo.New() + if utils.Exists(fullPath) { + err := yaml.ReadYamlFile(fullPath, o) + if err != nil { + p.t.Fatal(err) + } + } + orig := o.Clone() + err := update(o) + if err != nil { + p.t.Fatal(err) + } + if reflect.DeepEqual(o, orig) { + return + } + p.CommitYaml(repo, pth, message, o) +} + +func (p *GitServer) convertInterfaceToList(x interface{}) []interface{} { + var ret []interface{} + if l, ok := x.([]interface{}); ok { + return l + } + if l, ok := x.([]*uo.UnstructuredObject); ok { + for _, y := range l { + ret = append(ret, y) + } + return ret + } + if l, ok := x.([]map[string]interface{}); ok { + for _, y := range l { + ret = append(ret, y) + } + return ret + } + return []interface{}{x} +} + +func (p *GitServer) LocalGitUrl(repo string) string { + return fmt.Sprintf("http://localhost:%d/%s/.git", p.gitServerPort, repo) +} + +func (p *GitServer) LocalRepoDir(repo string) string { + return filepath.Join(p.baseDir, repo) +} + +func (p *GitServer) GetWorktree(repo string) *git.Worktree { + r, err := git.PlainOpen(p.LocalRepoDir(repo)) + if err != nil { + p.t.Fatal(err) + } + wt, err := r.Worktree() + if err != nil { + p.t.Fatal(err) + } + return wt +} From 7a9102fb841247c5643b8e5667919fb1e2689ddb Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 19 May 2022 17:21:30 +0200 Subject: [PATCH 0844/2916] refactor: Use dynamic client for CRD discovery --- pkg/k8s/resources.go | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/pkg/k8s/resources.go b/pkg/k8s/resources.go index 3010d295a..6cdf84ec6 100644 --- a/pkg/k8s/resources.go +++ b/pkg/k8s/resources.go @@ -155,17 +155,13 @@ func (k *k8sResources) updateResources() error { return nil } -var crdGK = schema.GroupKind{Group: "apiextensions.k8s.io", Kind: "CustomResourceDefinition"} +var crdGVR = schema.GroupVersionResource{Group: "apiextensions.k8s.io", Version: "v1", Resource: "customresourcedefinitions"} func (k *k8sResources) updateResourcesFromCRDs(clients *k8sClients) error { - var crdList *apiextensionsv1.CustomResourceDefinitionList - _, err := clients.withClientFromPool(func(p *parallelClientEntry) error { - c, err := apiextensionsclient.NewForConfigAndClient(k.restConfig, p.http) - if err != nil { - return err - } - - crdList, err = c.CustomResourceDefinitions().List(k.ctx, v1.ListOptions{}) + var crdList *unstructured.UnstructuredList + _, err := clients.withDynamicClientForGVR(&crdGVR, "", func(r dynamic.ResourceInterface) error { + var err error + crdList, err = r.List(k.ctx, v1.ListOptions{}) return err }) if err != nil { @@ -173,11 +169,8 @@ func (k *k8sResources) updateResourcesFromCRDs(clients *k8sClients) error { } for _, x := range crdList.Items { - u, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&x) - if err != nil { - return err - } - crd := uo.FromMap(u) + x := x + crd := uo.FromUnstructured(&x) err = k.UpdateResourcesFromCRD(crd) if err != nil { return err From a2209cb2687cc6056bbae600e0a2fe5a42a106a5 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 19 May 2022 17:45:15 +0200 Subject: [PATCH 0845/2916] refactor: Split out k8s client creation to allow fake clients --- pkg/k8s/client.go | 40 +++++-------- pkg/k8s/client_factory.go | 87 ++++++++++++++++++++++++++++ pkg/k8s/k8s_cluster.go | 22 +++---- pkg/k8s/resources.go | 28 ++------- pkg/kluctl_project/target_context.go | 6 +- 5 files changed, 122 insertions(+), 61 deletions(-) create mode 100644 pkg/k8s/client_factory.go diff --git a/pkg/k8s/client.go b/pkg/k8s/client.go index fbda2dbd5..76f01135e 100644 --- a/pkg/k8s/client.go +++ b/pkg/k8s/client.go @@ -7,20 +7,17 @@ import ( "k8s.io/client-go/dynamic" corev1 "k8s.io/client-go/kubernetes/typed/core/v1" "k8s.io/client-go/metadata" - "k8s.io/client-go/rest" - "net/http" ) type k8sClients struct { - ctx context.Context - restConfig *rest.Config - clientPool chan *parallelClientEntry - count int + ctx context.Context + clientFactory ClientFactory + clientPool chan *parallelClientEntry + count int } type parallelClientEntry struct { - http *http.Client - corev1 *corev1.CoreV1Client + corev1 corev1.CoreV1Interface dynamicClient dynamic.Interface metadataClient metadata.Interface @@ -41,37 +38,30 @@ func (p *parallelClientEntry) HandleWarningHeader(code int, agent string, text s }) } -func newK8sClients(ctx context.Context, restConfig *rest.Config, count int) (*k8sClients, error) { +func newK8sClients(ctx context.Context, clientFactory ClientFactory, count int) (*k8sClients, error) { var err error k := &k8sClients{ - ctx: ctx, - restConfig: restConfig, - clientPool: make(chan *parallelClientEntry, count), - count: count, + ctx: ctx, + clientFactory: clientFactory, + clientPool: make(chan *parallelClientEntry, count), + count: count, } for i := 0; i < count; i++ { p := ¶llelClientEntry{} - config := rest.CopyConfig(k.restConfig) - config.WarningHandler = p - p.http, err = rest.HTTPClientFor(config) + p.corev1, err = clientFactory.CoreV1Client(p) if err != nil { return nil, err } - p.corev1, err = corev1.NewForConfigAndClient(config, p.http) + p.dynamicClient, err = clientFactory.DynamicClient(p) if err != nil { return nil, err } - p.dynamicClient, err = dynamic.NewForConfigAndClient(config, p.http) - if err != nil { - return nil, err - } - - p.metadataClient, err = metadata.NewForConfigAndClient(config, p.http) + p.metadataClient, err = clientFactory.MetadataClient(p) if err != nil { return nil, err } @@ -82,10 +72,10 @@ func newK8sClients(ctx context.Context, restConfig *rest.Config, count int) (*k8 } func (k *k8sClients) close() { + k.clientFactory.CloseIdleConnections() if k.clientPool != nil { for i := 0; i < k.count; i++ { - p := <-k.clientPool - p.http.CloseIdleConnections() + _ = <-k.clientPool } } k.clientPool = nil diff --git a/pkg/k8s/client_factory.go b/pkg/k8s/client_factory.go new file mode 100644 index 000000000..0205c475c --- /dev/null +++ b/pkg/k8s/client_factory.go @@ -0,0 +1,87 @@ +package k8s + +import ( + "github.com/kluctl/kluctl/v2/pkg/utils" + "k8s.io/client-go/discovery" + "k8s.io/client-go/discovery/cached/disk" + "k8s.io/client-go/dynamic" + corev1 "k8s.io/client-go/kubernetes/typed/core/v1" + "k8s.io/client-go/metadata" + "k8s.io/client-go/rest" + "net/http" + "net/url" + "path/filepath" + "time" +) + +type ClientFactory interface { + GetCA() []byte + + CloseIdleConnections() + + DiscoveryClient() (discovery.DiscoveryInterface, error) + CoreV1Client(wh rest.WarningHandler) (corev1.CoreV1Interface, error) + DynamicClient(wh rest.WarningHandler) (dynamic.Interface, error) + MetadataClient(wh rest.WarningHandler) (metadata.Interface, error) +} + +type realClientFactory struct { + config *rest.Config + httpClient *http.Client +} + +func (r *realClientFactory) GetCA() []byte { + return r.config.CAData +} + +func (r *realClientFactory) DiscoveryClient() (discovery.DiscoveryInterface, error) { + + apiHost, err := url.Parse(r.config.Host) + if err != nil { + return nil, err + } + discoveryCacheDir := filepath.Join(utils.GetTmpBaseDir(), "kube-cache/discovery", apiHost.Hostname()) + discovery2, err := disk.NewCachedDiscoveryClientForConfig(dynamic.ConfigFor(r.config), discoveryCacheDir, "", time.Hour*24) + if err != nil { + return nil, err + } + return discovery2, nil +} + +func (r *realClientFactory) CoreV1Client(wh rest.WarningHandler) (corev1.CoreV1Interface, error) { + config := rest.CopyConfig(r.config) + config.WarningHandler = wh + return corev1.NewForConfigAndClient(config, r.httpClient) +} + +func (r *realClientFactory) DynamicClient(wh rest.WarningHandler) (dynamic.Interface, error) { + config := rest.CopyConfig(r.config) + config.WarningHandler = wh + return dynamic.NewForConfigAndClient(config, r.httpClient) +} + +func (r *realClientFactory) MetadataClient(wh rest.WarningHandler) (metadata.Interface, error) { + config := rest.CopyConfig(r.config) + config.WarningHandler = wh + return metadata.NewForConfigAndClient(config, r.httpClient) +} + +func (r *realClientFactory) CloseIdleConnections() { + r.httpClient.CloseIdleConnections() +} + +func NewClientFactory(configIn *rest.Config) (ClientFactory, error) { + restConfig := rest.CopyConfig(configIn) + restConfig.QPS = 10 + restConfig.Burst = 20 + + httpClient, err := rest.HTTPClientFor(restConfig) + if err != nil { + return nil, err + } + + return &realClientFactory{ + config: restConfig, + httpClient: httpClient, + }, nil +} diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index 51fb487b9..5e30e45c6 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -35,33 +35,29 @@ type K8sCluster struct { DryRun bool - restConfig *rest.Config - clients *k8sClients + clientFactory ClientFactory + clients *k8sClients ServerVersion *goversion.Version Resources *k8sResources } -func NewK8sCluster(ctx context.Context, configIn *rest.Config, dryRun bool) (*K8sCluster, error) { +func NewK8sCluster(ctx context.Context, clientFactory ClientFactory, dryRun bool) (*K8sCluster, error) { var err error - restConfig := rest.CopyConfig(configIn) - restConfig.QPS = 10 - restConfig.Burst = 20 - k := &K8sCluster{ - ctx: ctx, - DryRun: dryRun, - restConfig: restConfig, + ctx: ctx, + DryRun: dryRun, + clientFactory: clientFactory, } - k.Resources, err = newK8sResources(ctx, restConfig) + k.Resources, err = newK8sResources(ctx, clientFactory) if err != nil { return nil, err } - k.clients, err = newK8sClients(ctx, restConfig, 16) + k.clients, err = newK8sClients(ctx, clientFactory, 16) if err != nil { return nil, err } @@ -108,7 +104,7 @@ func (k *K8sCluster) ReadWrite() *K8sCluster { } func (k *K8sCluster) GetCA() []byte { - return k.restConfig.CAData + return k.clientFactory.GetCA() } func (k *K8sCluster) buildLabelSelector(labels map[string]string) string { diff --git a/pkg/k8s/resources.go b/pkg/k8s/resources.go index 6cdf84ec6..c1338ecee 100644 --- a/pkg/k8s/resources.go +++ b/pkg/k8s/resources.go @@ -4,32 +4,22 @@ import ( "context" "fmt" "github.com/kluctl/kluctl/v2/pkg/types/k8s" - "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/api/meta" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/version" "k8s.io/client-go/discovery" - "k8s.io/client-go/discovery/cached/disk" "k8s.io/client-go/dynamic" - "k8s.io/client-go/rest" - "net/url" - "path/filepath" "sort" "strings" "sync" - "time" - - apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1" ) type k8sResources struct { - ctx context.Context - restConfig *rest.Config - discovery *disk.CachedDiscoveryClient + ctx context.Context + discovery discovery.DiscoveryInterface allResources map[schema.GroupVersionKind]v1.APIResource preferredResources map[schema.GroupKind]v1.APIResource @@ -37,26 +27,20 @@ type k8sResources struct { mutex sync.Mutex } -func newK8sResources(ctx context.Context, config *rest.Config) (*k8sResources, error) { +func newK8sResources(ctx context.Context, clientFactory ClientFactory) (*k8sResources, error) { k := &k8sResources{ ctx: ctx, - restConfig: config, allResources: map[schema.GroupVersionKind]v1.APIResource{}, preferredResources: map[schema.GroupKind]v1.APIResource{}, crds: map[schema.GroupKind]*uo.UnstructuredObject{}, mutex: sync.Mutex{}, } - apiHost, err := url.Parse(config.Host) - if err != nil { - return nil, err - } - discoveryCacheDir := filepath.Join(utils.GetTmpBaseDir(), "kube-cache/discovery", apiHost.Hostname()) - discovery2, err := disk.NewCachedDiscoveryClientForConfig(dynamic.ConfigFor(config), discoveryCacheDir, "", time.Hour*24) + var err error + k.discovery, err = clientFactory.DiscoveryClient() if err != nil { return nil, err } - k.discovery = discovery2 return k, nil } diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index f18b0a880..eae3b64e5 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -51,7 +51,11 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName s var k *k8s.K8sCluster if clientConfig != nil { s := status.Start(ctx, fmt.Sprintf("Initializing k8s client")) - k, err = k8s.NewK8sCluster(ctx, clientConfig, dryRun) + clientFactory, err := k8s.NewClientFactory(clientConfig) + if err != nil { + return nil, err + } + k, err = k8s.NewK8sCluster(ctx, clientFactory, dryRun) if err != nil { s.Failed() return nil, err From fa80323c466ae123e0fa273d0362795c5b3b6f82 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 19 May 2022 17:45:38 +0200 Subject: [PATCH 0846/2916] fix: Allow uint as well in GetNestedInt --- pkg/utils/uo/nested_fields.go | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/pkg/utils/uo/nested_fields.go b/pkg/utils/uo/nested_fields.go index 325c581f3..b3927dc9d 100644 --- a/pkg/utils/uo/nested_fields.go +++ b/pkg/utils/uo/nested_fields.go @@ -1,6 +1,9 @@ package uo -import "fmt" +import ( + "fmt" + "reflect" +) func (uo *UnstructuredObject) GetNestedField(keys ...interface{}) (interface{}, bool, error) { var o interface{} = uo.Object @@ -97,15 +100,14 @@ func (uo *UnstructuredObject) GetNestedInt(keys ...interface{}) (int64, bool, er if !found { return 0, false, nil } - i, ok := v.(int64) - if !ok { - if i2, ok := v.(int); ok { - i = int64(i2) - } else { - return 0, false, fmt.Errorf("value at %s is not an int", KeyPath(keys).ToJsonPath()) - } + vv := reflect.ValueOf(v) + if vv.CanInt() { + return vv.Int(), true, nil + } else if vv.CanUint() { + return int64(vv.Uint()), true, nil + } else { + return 0, false, fmt.Errorf("value at %s is not an int", KeyPath(keys).ToJsonPath()) } - return i, true, nil } func (uo *UnstructuredObject) GetNestedList(keys ...interface{}) ([]interface{}, bool, error) { From 603dd593fbf3f14eee82623df06db0175acde1ab Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 19 May 2022 17:46:18 +0200 Subject: [PATCH 0847/2916] fix: Add NoopStatusHandler so that status handling is ignored while testing --- pkg/status/noop.go | 49 ++++++++++++++++++++++++++++++++++++++++++++ pkg/status/status.go | 2 +- 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 pkg/status/noop.go diff --git a/pkg/status/noop.go b/pkg/status/noop.go new file mode 100644 index 000000000..1f30341a0 --- /dev/null +++ b/pkg/status/noop.go @@ -0,0 +1,49 @@ +package status + +type NoopStatusHandler struct { +} + +type NoopStatusLine struct { +} + +func (n NoopStatusHandler) SetTrace(trace bool) { +} + +func (n NoopStatusHandler) Stop() { +} + +func (n NoopStatusHandler) StartStatus(total int, message string) StatusLine { + return &NoopStatusLine{} +} + +func (n NoopStatusHandler) Info(message string) { +} + +func (n NoopStatusHandler) Warning(message string) { +} + +func (n NoopStatusHandler) Error(message string) { +} + +func (n NoopStatusHandler) Trace(message string) { +} + +func (n NoopStatusHandler) PlainText(text string) { +} + +func (n NoopStatusHandler) InfoFallback(message string) { +} + +var _ StatusHandler = &NoopStatusHandler{} + +func (n NoopStatusLine) SetTotal(total int) { +} + +func (n NoopStatusLine) Increment() { +} + +func (n NoopStatusLine) Update(message string) { +} + +func (n NoopStatusLine) End(result EndResult) { +} diff --git a/pkg/status/status.go b/pkg/status/status.go index d58c774f2..f1f60395c 100644 --- a/pkg/status/status.go +++ b/pkg/status/status.go @@ -59,7 +59,7 @@ func NewContext(ctx context.Context, slh StatusHandler) context.Context { func FromContext(ctx context.Context) StatusHandler { v := ctx.Value(contextKey{}) if v == nil { - return nil + return &NoopStatusHandler{} } return v.(StatusHandler) } From f7ee0228cefd95566f50786122225a1586a8cdea Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 20 May 2022 14:51:15 +0200 Subject: [PATCH 0848/2916] refactor: Use semver to compare kubernetes versions This fixes issues with k8s versions while testing. --- go.mod | 3 +-- go.sum | 2 -- pkg/k8s/k8s_cluster.go | 22 ++++++++++------------ 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/go.mod b/go.mod index 4958074bb..8ba0c8880 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.18 require ( github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e + github.com/Masterminds/semver/v3 v3.1.1 github.com/aws/aws-sdk-go v1.44.1 github.com/bitnami-labs/sealed-secrets v0.17.5 github.com/cyphar/filepath-securejoin v0.2.3 @@ -16,7 +17,6 @@ require ( github.com/goccy/go-yaml v1.9.5 github.com/golang-jwt/jwt/v4 v4.4.1 github.com/google/go-containerregistry v0.8.0 - github.com/hashicorp/go-version v1.4.0 github.com/hexops/gotextdiff v1.0.3 github.com/jinzhu/copier v0.3.5 github.com/kevinburke/ssh_config v1.2.0 @@ -73,7 +73,6 @@ require ( github.com/BurntSushi/toml v1.1.0 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/semver/v3 v3.1.1 // indirect github.com/Masterminds/sprig/v3 v3.2.2 // indirect github.com/Masterminds/squirrel v1.5.2 // indirect github.com/Microsoft/go-winio v0.5.2 // indirect diff --git a/go.sum b/go.sum index dc7ef08f9..fe9ab20ed 100644 --- a/go.sum +++ b/go.sum @@ -753,8 +753,6 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.4.0 h1:aAQzgqIrRKRa7w75CKpbBxYsmUoPjzVm1W59ca1L0J4= -github.com/hashicorp/go-version v1.4.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index 5e30e45c6..289e3d53a 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -4,7 +4,7 @@ import ( "context" "encoding/json" "fmt" - goversion "github.com/hashicorp/go-version" + "github.com/Masterminds/semver/v3" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/utils" @@ -16,6 +16,7 @@ import ( v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/version" "k8s.io/client-go/dynamic" "k8s.io/client-go/metadata" _ "k8s.io/client-go/plugin/pkg/client/auth" @@ -38,7 +39,7 @@ type K8sCluster struct { clientFactory ClientFactory clients *k8sClients - ServerVersion *goversion.Version + ServerVersion *version.Info Resources *k8sResources } @@ -66,11 +67,7 @@ func NewK8sCluster(ctx context.Context, clientFactory ClientFactory, dryRun bool if err != nil { return nil, err } - v2, err := goversion.NewVersion(v.String()) - if err != nil { - return nil, err - } - k.ServerVersion = v2 + k.ServerVersion = v var wg sync.WaitGroup wg.Add(2) @@ -326,17 +323,18 @@ func (k *K8sCluster) waitForDeletedObject(ref k8s.ObjectRef) error { return nil } -var v1_21, _ = goversion.NewVersion("1.21") -var v1_1000, _ = goversion.NewVersion("1.1000") - func (k *K8sCluster) FixObjectForPatch(o *uo.UnstructuredObject) *uo.UnstructuredObject { // A bug in versions < 1.20 cause errors when applying resources that have some fields omitted which have // default values. We need to fix these resources. // UPDATE even though https://github.com/kubernetes-sigs/structured-merge-diff/issues/130 says it's fixed, the // issue is still present. - needsDefaultsFix := k.ServerVersion.LessThan(v1_21) || true + k8sVersion, err := semver.NewVersion(k.ServerVersion.String()) + if err != nil { + return o + } + needsDefaultsFix := k8sVersion.LessThan(semver.MustParse("1.21")) || true // TODO check when this is actually fixed (see https://github.com/kubernetes/kubernetes/issues/94275) - needsTypeConversionFix := k.ServerVersion.LessThan(v1_1000) + needsTypeConversionFix := k8sVersion.LessThan(semver.MustParse("1.100")) if !needsDefaultsFix && !needsTypeConversionFix { return o } From 3ed29312d874368ab02ea4d426b33c8178afad85 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 20 May 2022 14:52:35 +0200 Subject: [PATCH 0849/2916] fix: Stop using ServerPreferredResources() from discovery client Instead, use a custom implementation of the preferred version detection. ServerPreferredResources() is not implemented in the fake discovery client, making it unusable while testing. --- pkg/k8s/resources.go | 51 +++++++++++++++----------------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/pkg/k8s/resources.go b/pkg/k8s/resources.go index c1338ecee..e8a3d843e 100644 --- a/pkg/k8s/resources.go +++ b/pkg/k8s/resources.go @@ -54,17 +54,12 @@ func (k *k8sResources) updateResources() error { k.crds = map[schema.GroupKind]*uo.UnstructuredObject{} // the discovery client doesn't support cancellation, so we need to run it in the background and wait for it + var ags []*v1.APIGroup var arls []*v1.APIResourceList - var preferredArls []*v1.APIResourceList finished := make(chan error) go func() { var err error - _, arls, err = k.discovery.ServerGroupsAndResources() - if err != nil && !discovery.IsGroupDiscoveryFailedError(err) { - finished <- err - return - } - preferredArls, err = k.discovery.ServerPreferredResources() + ags, arls, err = k.discovery.ServerGroupsAndResources() if err != nil && !discovery.IsGroupDiscoveryFailedError(err) { finished <- err return @@ -82,6 +77,17 @@ func (k *k8sResources) updateResources() error { } for _, arl := range arls { + var ag *v1.APIGroup + for _, x := range ags { + if x.Name == arl.GroupVersionKind().Group { + ag = x + break + } + } + if ag == nil { + continue + } + for _, ar := range arl.APIResources { if strings.Index(ar.Name, "/") != -1 { // skip subresources @@ -104,38 +110,15 @@ func (k *k8sResources) updateResources() error { if _, ok := deprecatedResources[gvk.GroupKind()]; ok { continue } - if _, ok := k.allResources[gvk]; ok { - ok = false - } - k.allResources[gvk] = ar - } - } - - for _, arl := range preferredArls { - for _, ar := range arl.APIResources { - if strings.Index(ar.Name, "/") != -1 { - // skip subresources - continue - } - gv, err := schema.ParseGroupVersion(arl.GroupVersion) - if err != nil { - continue - } - ar := ar - ar.Group = gv.Group - ar.Version = gv.Version + k.allResources[gvk] = ar - gk := schema.GroupKind{ - Group: ar.Group, - Kind: ar.Kind, + if gvk.Version == ag.PreferredVersion.Version { + k.preferredResources[gvk.GroupKind()] = ar } - if _, ok := deprecatedResources[gk]; ok { - continue - } - k.preferredResources[gk] = ar } } + return nil } From b2b7d0d0f4c5139d7e0b43b7db92b3cb5d5f61c1 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 20 May 2022 14:53:07 +0200 Subject: [PATCH 0850/2916] refactor: Move kyaml background init into utils package --- pkg/deployment/deployment_item.go | 28 +--------------------------- pkg/utils/init_kyaml.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 27 deletions(-) create mode 100644 pkg/utils/init_kyaml.go diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 15463a61a..7051ce9c3 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -15,10 +15,7 @@ import ( "path/filepath" "sigs.k8s.io/kustomize/api/filesys" "sigs.k8s.io/kustomize/api/krusty" - "sigs.k8s.io/kustomize/kyaml/openapi" - yaml2 "sigs.k8s.io/kustomize/kyaml/yaml" "strings" - "sync" ) const SealmeExt = ".sealme" @@ -336,7 +333,7 @@ func (di *DeploymentItem) buildKustomize() error { return err } - waitForOpenapiInitDone() + utils.WaitForOpenapiInitDone() ko := krusty.MakeDefaultOptions() k := krusty.MakeKustomizer(ko) @@ -452,26 +449,3 @@ func (di *DeploymentItem) postprocessObjects(k *k8s.K8sCluster, images *Images) return nil } - -var openapiInitDoneMutex sync.Mutex -var openapiInitDoneOnce sync.Once - -func waitForOpenapiInitDone() { - openapiInitDoneOnce.Do(func() { - openapiInitDoneMutex.Lock() - openapiInitDoneMutex.Unlock() - }) -} - -func init() { - openapiInitDoneMutex.Lock() - go func() { - // we do a single call to IsNamespaceScoped to enforce openapi schema initialization - // this is required here to ensure that it is later not done in parallel which would cause race conditions - openapi.IsNamespaceScoped(yaml2.TypeMeta{ - APIVersion: "", - Kind: "ConfigMap", - }) - openapiInitDoneMutex.Unlock() - }() -} diff --git a/pkg/utils/init_kyaml.go b/pkg/utils/init_kyaml.go new file mode 100644 index 000000000..a25bddbb0 --- /dev/null +++ b/pkg/utils/init_kyaml.go @@ -0,0 +1,30 @@ +package utils + +import ( + "sigs.k8s.io/kustomize/kyaml/openapi" + yaml2 "sigs.k8s.io/kustomize/kyaml/yaml" + "sync" +) + +var openapiInitDoneMutex sync.Mutex +var openapiInitDoneOnce sync.Once + +func WaitForOpenapiInitDone() { + openapiInitDoneOnce.Do(func() { + openapiInitDoneMutex.Lock() + openapiInitDoneMutex.Unlock() + }) +} + +func init() { + openapiInitDoneMutex.Lock() + go func() { + // we do a single call to IsNamespaceScoped to enforce openapi schema initialization + // this is required here to ensure that it is later not done in parallel which would cause race conditions + openapi.IsNamespaceScoped(yaml2.TypeMeta{ + APIVersion: "", + Kind: "ConfigMap", + }) + openapiInitDoneMutex.Unlock() + }() +} From d6f236c6c579ce3c1efff8e0618bb1200b80fff4 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sun, 22 May 2022 16:44:07 +0200 Subject: [PATCH 0851/2916] fix: Fix broken ARN parsing --- pkg/utils/aws/arn.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/utils/aws/arn.go b/pkg/utils/aws/arn.go index a8f88ee45..3b7141e99 100644 --- a/pkg/utils/aws/arn.go +++ b/pkg/utils/aws/arn.go @@ -17,7 +17,7 @@ type Arn struct { func ParseArn(arn string) (Arn, error) { // http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html - elements := strings.SplitN(arn, ":", 5) + elements := strings.SplitN(arn, ":", 6) if len(elements) < 6 { return Arn{}, fmt.Errorf("%s is not a valid arn", arn) } @@ -29,11 +29,11 @@ func ParseArn(arn string) (Arn, error) { result.Account = elements[4] result.Resource = elements[5] - if strings.Index(result.Resource, "/") != 0 { + if strings.Index(result.Resource, "/") != -1 { s := strings.SplitN(result.Resource, "/", 2) result.ResourceType = s[0] result.Resource = s[1] - } else if strings.Index(result.Resource, ":") != 0 { + } else if strings.Index(result.Resource, ":") != -1 { s := strings.SplitN(result.Resource, ":", 2) result.ResourceType = s[0] result.Resource = s[1] From a79d1722816e8548af6b2aa1baf25314ca77e3b7 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sun, 22 May 2022 16:45:39 +0200 Subject: [PATCH 0852/2916] refactor: Introduce AWS client factory for VarsLoader This allows mocking of AWS clients. --- pkg/kluctl_project/target_context.go | 3 +- pkg/{utils => vars}/aws/arn.go | 0 pkg/vars/aws/clientfactory.go | 44 ++++++++++++++++++++++ pkg/{utils => vars}/aws/secrets_manager.go | 23 +++-------- pkg/vars/vars_loader.go | 12 ++++-- 5 files changed, 61 insertions(+), 21 deletions(-) rename pkg/{utils => vars}/aws/arn.go (100%) create mode 100644 pkg/vars/aws/clientfactory.go rename pkg/{utils => vars}/aws/secrets_manager.go (55%) diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index eae3b64e5..400d9a118 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -10,6 +10,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/vars" + "github.com/kluctl/kluctl/v2/pkg/vars/aws" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd/api" "path/filepath" @@ -63,7 +64,7 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName s s.Success() } - varsLoader := vars.NewVarsLoader(ctx, k, p.GRC) + varsLoader := vars.NewVarsLoader(ctx, k, p.GRC, aws.NewClientFactory()) if forSeal { err = p.loadSecrets(target, varsCtx, varsLoader) diff --git a/pkg/utils/aws/arn.go b/pkg/vars/aws/arn.go similarity index 100% rename from pkg/utils/aws/arn.go rename to pkg/vars/aws/arn.go diff --git a/pkg/vars/aws/clientfactory.go b/pkg/vars/aws/clientfactory.go new file mode 100644 index 000000000..db7e43d0e --- /dev/null +++ b/pkg/vars/aws/clientfactory.go @@ -0,0 +1,44 @@ +package aws + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/secretsmanager" + "github.com/aws/aws-sdk-go/service/secretsmanager/secretsmanageriface" + "os" +) + +type AwsClientFactory interface { + SecretsManagerClient(profile *string, region *string) (secretsmanageriface.SecretsManagerAPI, error) +} + +type awsClientFactory struct { +} + +func (a *awsClientFactory) getSession(profile *string) (*session.Session, error) { + var opts session.Options + opts.SharedConfigState = session.SharedConfigEnable + // Environment variable always takes precedence + if _, ok := os.LookupEnv("AWS_PROFILE"); !ok && profile != nil { + opts.Profile = *profile + } + s, err := session.NewSessionWithOptions(opts) + if err != nil { + return nil, err + } + + return s, nil +} + +func (a *awsClientFactory) SecretsManagerClient(profile *string, region *string) (secretsmanageriface.SecretsManagerAPI, error) { + s, err := a.getSession(profile) + if err != nil { + return nil, err + } + + return secretsmanager.New(s, &aws.Config{Region: region}), nil +} + +func NewClientFactory() AwsClientFactory { + return &awsClientFactory{} +} diff --git a/pkg/utils/aws/secrets_manager.go b/pkg/vars/aws/secrets_manager.go similarity index 55% rename from pkg/utils/aws/secrets_manager.go rename to pkg/vars/aws/secrets_manager.go index bd440439b..1a57bd5ae 100644 --- a/pkg/utils/aws/secrets_manager.go +++ b/pkg/vars/aws/secrets_manager.go @@ -2,25 +2,10 @@ package aws import ( "fmt" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/secretsmanager" - - "os" ) -func GetAwsSecretsManagerSecret(profile *string, region *string, secretName string) (string, error) { - var opts session.Options - opts.SharedConfigState = session.SharedConfigEnable - // Environment variable always takes precedence - if _, ok := os.LookupEnv("AWS_PROFILE"); !ok && region != nil { - opts.Profile = *profile - } - s, err := session.NewSessionWithOptions(opts) - if err != nil { - return "", err - } - +func GetAwsSecretsManagerSecret(aws AwsClientFactory, profile *string, region *string, secretName string) (string, error) { if region == nil { arn, err := ParseArn(secretName) if err != nil { @@ -29,7 +14,11 @@ func GetAwsSecretsManagerSecret(profile *string, region *string, secretName stri region = &arn.Region } - smClient := secretsmanager.New(s, &aws.Config{Region: region}) + smClient, err := aws.SecretsManagerClient(profile, region) + if err != nil { + return "", fmt.Errorf("getting secret %s from AWS secrets manager failed: %w", secretName, err) + } + r, err := smClient.GetSecretValue(&secretsmanager.GetSecretValueInput{ SecretId: &secretName, }) diff --git a/pkg/vars/vars_loader.go b/pkg/vars/vars_loader.go index e6f912b29..9900ce36b 100644 --- a/pkg/vars/vars_loader.go +++ b/pkg/vars/vars_loader.go @@ -9,8 +9,8 @@ import ( "github.com/kluctl/kluctl/v2/pkg/types" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/utils" - "github.com/kluctl/kluctl/v2/pkg/utils/aws" "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/vars/aws" "github.com/kluctl/kluctl/v2/pkg/yaml" "io/ioutil" "os" @@ -25,15 +25,17 @@ type VarsLoader struct { ctx context.Context k *k8s.K8sCluster grc *git.MirroredGitRepoCollection + aws aws.AwsClientFactory credentialsCache map[string]usernamePassword } -func NewVarsLoader(ctx context.Context, k *k8s.K8sCluster, grc *git.MirroredGitRepoCollection) *VarsLoader { +func NewVarsLoader(ctx context.Context, k *k8s.K8sCluster, grc *git.MirroredGitRepoCollection, aws aws.AwsClientFactory) *VarsLoader { return &VarsLoader{ ctx: ctx, k: k, grc: grc, + aws: aws, credentialsCache: map[string]usernamePassword{}, } } @@ -125,7 +127,11 @@ func (v *VarsLoader) loadSystemEnvs(varsCtx *VarsCtx, source *types.VarsSource, } func (v *VarsLoader) loadAwsSecretsManager(varsCtx *VarsCtx, source *types.VarsSource, rootKey string) error { - secret, err := aws.GetAwsSecretsManagerSecret(source.AwsSecretsManager.Profile, source.AwsSecretsManager.Region, source.AwsSecretsManager.SecretName) + if v.aws == nil { + return fmt.Errorf("no AWS client factory provided") + } + + secret, err := aws.GetAwsSecretsManagerSecret(v.aws, source.AwsSecretsManager.Profile, source.AwsSecretsManager.Region, source.AwsSecretsManager.SecretName) if err != nil { return err } From 5a9b84ad8fc091a570a97a141ba406edc8786caa Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sun, 22 May 2022 16:46:03 +0200 Subject: [PATCH 0853/2916] fix: Fix base64 decooding of vars loaded from k8s secrets --- pkg/vars/vars_loader.go | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/pkg/vars/vars_loader.go b/pkg/vars/vars_loader.go index 9900ce36b..6e1310bee 100644 --- a/pkg/vars/vars_loader.go +++ b/pkg/vars/vars_loader.go @@ -2,6 +2,7 @@ package vars import ( "context" + "encoding/base64" "fmt" "github.com/kluctl/kluctl/v2/pkg/git" "github.com/kluctl/kluctl/v2/pkg/k8s" @@ -70,10 +71,10 @@ func (v *VarsLoader) LoadVars(varsCtx *VarsCtx, sourceIn *types.VarsSource, sear return v.loadGit(varsCtx, source.Git, rootKey) } else if source.ClusterConfigMap != nil { ref := k8s2.NewObjectRef("", "v1", "ConfigMap", source.ClusterConfigMap.Name, source.ClusterConfigMap.Namespace) - return v.loadFromK8sObject(varsCtx, ref, source.ClusterConfigMap.Key, rootKey) + return v.loadFromK8sObject(varsCtx, ref, source.ClusterConfigMap.Key, rootKey, false) } else if source.ClusterSecret != nil { ref := k8s2.NewObjectRef("", "v1", "Secret", source.ClusterSecret.Name, source.ClusterSecret.Namespace) - return v.loadFromK8sObject(varsCtx, ref, source.ClusterSecret.Key, rootKey) + return v.loadFromK8sObject(varsCtx, ref, source.ClusterSecret.Key, rootKey, true) } else if source.SystemEnvVars != nil { return v.loadSystemEnvs(varsCtx, &source, rootKey) } else if source.Http != nil { @@ -158,7 +159,7 @@ func (v *VarsLoader) loadGit(varsCtx *VarsCtx, gitFile *types.VarsSourceGit, roo return v.loadFromString(varsCtx, string(file), rootKey) } -func (v *VarsLoader) loadFromK8sObject(varsCtx *VarsCtx, ref k8s2.ObjectRef, key string, rootKey string) error { +func (v *VarsLoader) loadFromK8sObject(varsCtx *VarsCtx, ref k8s2.ObjectRef, key string, rootKey string, base64Decode bool) error { if v.k == nil { return nil } @@ -168,7 +169,7 @@ func (v *VarsLoader) loadFromK8sObject(varsCtx *VarsCtx, ref k8s2.ObjectRef, key return err } - value, found, err := o.GetNestedString("data", key) + f, found, err := o.GetNestedField("data", key) if err != nil { return err } @@ -176,6 +177,21 @@ func (v *VarsLoader) loadFromK8sObject(varsCtx *VarsCtx, ref k8s2.ObjectRef, key return fmt.Errorf("key %s not found in %s on cluster", key, ref.String()) } + var value string + if b, ok := f.([]byte); ok { + value = string(b) + } else if s, ok := f.(string); ok { + if base64Decode { + b, err := base64.StdEncoding.DecodeString(s) + if err != nil { + return err + } + value = string(b) + } else { + value = s + } + } + err = v.loadFromString(varsCtx, value, rootKey) if err != nil { return fmt.Errorf("failed to load vars from kubernetes object %s and key %s: %w", ref.String(), key, err) From 4782041c2094dae9f5bdac4c7055362b140a2b1c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sun, 22 May 2022 16:46:24 +0200 Subject: [PATCH 0854/2916] tests: Implement VarsLoader tests --- pkg/k8s/fake_client_factory.go | 106 +++++++ pkg/vars/aws/fake_clientfactory.go | 39 +++ pkg/vars/vars_loader_test.go | 460 +++++++++++++++++++++++++++++ pkg/vars/vars_test.go | 25 +- 4 files changed, 617 insertions(+), 13 deletions(-) create mode 100644 pkg/k8s/fake_client_factory.go create mode 100644 pkg/vars/aws/fake_clientfactory.go create mode 100644 pkg/vars/vars_loader_test.go diff --git a/pkg/k8s/fake_client_factory.go b/pkg/k8s/fake_client_factory.go new file mode 100644 index 000000000..8af606ad5 --- /dev/null +++ b/pkg/k8s/fake_client_factory.go @@ -0,0 +1,106 @@ +package k8s + +import ( + "github.com/kluctl/kluctl/v2/pkg/utils" + v1 "k8s.io/api/core/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/discovery" + "k8s.io/client-go/dynamic" + fake_dynamic "k8s.io/client-go/dynamic/fake" + "k8s.io/client-go/kubernetes/fake" + corev1 "k8s.io/client-go/kubernetes/typed/core/v1" + "k8s.io/client-go/metadata" + metadata_fake "k8s.io/client-go/metadata/fake" + "k8s.io/client-go/rest" + "sigs.k8s.io/kustomize/kyaml/openapi" + "sigs.k8s.io/kustomize/kyaml/yaml" + "strings" +) + +type fakeClientFactory struct { + clientSet *fake.Clientset + objects []runtime.Object + scheme *runtime.Scheme +} + +func (f *fakeClientFactory) GetCA() []byte { + return []byte{} +} + +func (f *fakeClientFactory) CloseIdleConnections() { +} + +func (f *fakeClientFactory) DiscoveryClient() (discovery.DiscoveryInterface, error) { + return f.clientSet.Discovery(), nil +} + +func (f *fakeClientFactory) CoreV1Client(wh rest.WarningHandler) (corev1.CoreV1Interface, error) { + return f.clientSet.CoreV1(), nil +} + +func (f *fakeClientFactory) DynamicClient(wh rest.WarningHandler) (dynamic.Interface, error) { + return fake_dynamic.NewSimpleDynamicClient(f.scheme, f.objects...), nil +} + +func (f *fakeClientFactory) MetadataClient(wh rest.WarningHandler) (metadata.Interface, error) { + return metadata_fake.NewSimpleMetadataClient(f.scheme, f.objects...), nil +} + +func NewFakeClientFactory(objects ...runtime.Object) ClientFactory { + scheme := runtime.NewScheme() + _ = v1.AddToScheme(scheme) + _ = apiextensionsv1.AddToScheme(scheme) + clientSet := fake.NewSimpleClientset(objects...) + + clientSet.Fake.Resources = ConvertSchemeToAPIResources(scheme) + + return &fakeClientFactory{ + clientSet: clientSet, + objects: objects, + scheme: scheme, + } +} + +func ConvertSchemeToAPIResources(s *runtime.Scheme) []*metav1.APIResourceList { + utils.WaitForOpenapiInitDone() + + m := map[schema.GroupVersion][]metav1.APIResource{} + + for gvk, _ := range s.AllKnownTypes() { + // we misuse kyaml here + n, _ := openapi.IsNamespaceScoped(yaml.TypeMeta{ + APIVersion: gvk.GroupVersion().String(), + Kind: gvk.Kind, + }) + + ar := metav1.APIResource{ + Name: buildPluralName(gvk.Kind), + Namespaced: n, + Group: gvk.Group, + Version: gvk.Version, + Kind: gvk.Kind, + Verbs: []string{"delete", "deletecollection", "get", "list", "patch", "create", "update", "watch"}, + } + + l, _ := m[gvk.GroupVersion()] + l = append(l, ar) + m[gvk.GroupVersion()] = l + } + + var ret []*metav1.APIResourceList + for gv, arl := range m { + ret = append(ret, &metav1.APIResourceList{ + GroupVersion: gv.String(), + APIResources: arl, + }) + } + + return ret +} + +func buildPluralName(n string) string { + return strings.ToLower(n) + "s" +} diff --git a/pkg/vars/aws/fake_clientfactory.go b/pkg/vars/aws/fake_clientfactory.go new file mode 100644 index 000000000..219dc1259 --- /dev/null +++ b/pkg/vars/aws/fake_clientfactory.go @@ -0,0 +1,39 @@ +package aws + +import ( + "fmt" + "github.com/aws/aws-sdk-go/service/secretsmanager" + "github.com/aws/aws-sdk-go/service/secretsmanager/secretsmanageriface" +) + +type FakeAwsClientFactory struct { + secretsmanageriface.SecretsManagerAPI + + Secrets map[string]string +} + +func (f *FakeAwsClientFactory) GetSecretValue(in *secretsmanager.GetSecretValueInput) (*secretsmanager.GetSecretValueOutput, error) { + name := *in.SecretId + arn, err := ParseArn(*in.SecretId) + if err == nil { + name = arn.Resource + } + + s, ok := f.Secrets[name] + if ok { + return &secretsmanager.GetSecretValueOutput{ + Name: &name, + SecretString: &s, + }, nil + } + + return nil, fmt.Errorf("secret %s not found", *in.SecretId) +} + +func (f *FakeAwsClientFactory) SecretsManagerClient(profile *string, region *string) (secretsmanageriface.SecretsManagerAPI, error) { + return f, nil +} + +func NewFakeClientFactory() *FakeAwsClientFactory { + return &FakeAwsClientFactory{} +} diff --git a/pkg/vars/vars_loader_test.go b/pkg/vars/vars_loader_test.go new file mode 100644 index 000000000..763b6c1ae --- /dev/null +++ b/pkg/vars/vars_loader_test.go @@ -0,0 +1,460 @@ +package vars + +import ( + "context" + git2 "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing" + test_utils "github.com/kluctl/kluctl/v2/internal/test-utils" + "github.com/kluctl/kluctl/v2/pkg/git" + "github.com/kluctl/kluctl/v2/pkg/git/auth" + git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" + "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/vars/aws" + "github.com/stretchr/testify/assert" + "io/ioutil" + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "net/http" + "net/http/httptest" + "net/url" + "os" + "path/filepath" + "testing" +) + +func newTestDir(t *testing.T) string { + tmp, err := ioutil.TempDir("", "") + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + _ = os.RemoveAll(tmp) + }) + return tmp +} + +func newGRC(t *testing.T) *git.MirroredGitRepoCollection { + grc := git.NewMirroredGitRepoCollection(context.TODO(), auth.NewDefaultAuthProviders(), 0) + t.Cleanup(func() { + grc.Clear() + }) + return grc +} + +func testVarsLoader(t *testing.T, test func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory), objects ...runtime.Object) { + k, err := k8s.NewK8sCluster(context.TODO(), k8s.NewFakeClientFactory(objects...), false) + if err != nil { + t.Fatal(err) + } + grc := newGRC(t) + fakeAws := aws.NewFakeClientFactory() + + vl := NewVarsLoader(context.TODO(), k, grc, fakeAws) + vc := NewVarsCtx(newJinja2Must(t)) + + test(vl, vc, fakeAws) +} + +func TestVarsLoader_Values(t *testing.T) { + testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { + err := vl.LoadVars(vc, &types.VarsSource{ + Values: uo.FromStringMust(`{"test1": {"test2": 42}}`), + }, nil, "") + assert.NoError(t, err) + + v, _, _ := vc.Vars.GetNestedInt("test1", "test2") + assert.Equal(t, int64(42), v) + }) +} + +func TestVarsLoader_File(t *testing.T) { + d := newTestDir(t) + _ = ioutil.WriteFile(filepath.Join(d, "test.yaml"), []byte(`{"test1": {"test2": 42}}`), 0o600) + + testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { + err := vl.LoadVars(vc, &types.VarsSource{ + File: utils.StrPtr("test.yaml"), + }, []string{d}, "") + assert.NoError(t, err) + + v, _, _ := vc.Vars.GetNestedInt("test1", "test2") + assert.Equal(t, int64(42), v) + }) +} + +func TestVarsLoader_FileWithLoad(t *testing.T) { + d := newTestDir(t) + _ = ioutil.WriteFile(filepath.Join(d, "test.yaml"), []byte(`{"test1": {"test2": {{ load_template("test2.txt") }}}}`), 0o600) + _ = ioutil.WriteFile(filepath.Join(d, "test2.txt"), []byte(`42`), 0o600) + + testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { + err := vl.LoadVars(vc, &types.VarsSource{ + File: utils.StrPtr("test.yaml"), + }, []string{d}, "") + assert.NoError(t, err) + + v, _, _ := vc.Vars.GetNestedInt("test1", "test2") + assert.Equal(t, int64(42), v) + }) +} + +func TestVarsLoader_FileWithLoadSubDir(t *testing.T) { + d := newTestDir(t) + _ = os.Mkdir(filepath.Join(d, "subdir"), 0o700) + _ = ioutil.WriteFile(filepath.Join(d, "test.yaml"), []byte(`{"test1": {"test2": {{ load_template("test2.txt") }}}}`), 0o600) + _ = ioutil.WriteFile(filepath.Join(d, "subdir/test2.txt"), []byte(`42`), 0o600) + + testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { + err := vl.LoadVars(vc, &types.VarsSource{ + File: utils.StrPtr("test.yaml"), + }, []string{d, filepath.Join(d, "subdir")}, "") + assert.NoError(t, err) + + v, _, _ := vc.Vars.GetNestedInt("test1", "test2") + assert.Equal(t, int64(42), v) + }) +} + +func TestVarsLoader_FileWithLoadNotExists(t *testing.T) { + d := newTestDir(t) + _ = ioutil.WriteFile(filepath.Join(d, "test.yaml"), []byte(`{"test1": {"test2": {{load_template("test3.txt")}}}}`), 0o600) + + testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { + err := vl.LoadVars(vc, &types.VarsSource{ + File: utils.StrPtr("test.yaml"), + }, []string{d}, "") + assert.EqualError(t, err, "failed to load vars from test.yaml: template test3.txt not found") + }) +} + +func TestVarsLoader_Git(t *testing.T) { + gs := test_utils.NewGitServer(t) + gs.GitInit("repo") + gs.UpdateYaml("repo", "test.yaml", func(o *uo.UnstructuredObject) error { + *o = *uo.FromStringMust(`{"test1": {"test2": 42}}`) + return nil + }, "") + + testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { + url, _ := git_url.Parse(gs.LocalGitUrl("repo")) + err := vl.LoadVars(vc, &types.VarsSource{ + Git: &types.VarsSourceGit{ + Url: *url, + Path: "test.yaml", + }, + }, nil, "") + assert.NoError(t, err) + + v, _, _ := vc.Vars.GetNestedInt("test1", "test2") + assert.Equal(t, int64(42), v) + }) +} + +func TestVarsLoader_GitBranch(t *testing.T) { + gs := test_utils.NewGitServer(t) + gs.GitInit("repo") + + wt := gs.GetWorktree("repo") + err := wt.Checkout(&git2.CheckoutOptions{ + Branch: plumbing.NewBranchReferenceName("testbranch"), + Create: true, + }) + assert.NoError(t, err) + + gs.UpdateYaml("repo", "test.yaml", func(o *uo.UnstructuredObject) error { + *o = *uo.FromStringMust(`{"test1": {"test2": 42}}`) + return nil + }, "") + + err = wt.Checkout(&git2.CheckoutOptions{ + Branch: plumbing.Master, + }) + assert.NoError(t, err) + + testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { + url, _ := git_url.Parse(gs.LocalGitUrl("repo")) + err = vl.LoadVars(vc, &types.VarsSource{ + Git: &types.VarsSourceGit{ + Url: *url, + Path: "test.yaml", + Ref: "testbranch", + }, + }, nil, "") + assert.NoError(t, err) + + v, _, _ := vc.Vars.GetNestedInt("test1", "test2") + assert.Equal(t, int64(42), v) + }) +} + +func TestVarsLoader_ClusterConfigMap(t *testing.T) { + cm := corev1.ConfigMap{ + ObjectMeta: v1.ObjectMeta{Name: "cm", Namespace: "ns"}, + Data: map[string]string{ + "vars": `{"test1": {"test2": 42}}`, + }, + } + + testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { + err := vl.LoadVars(vc, &types.VarsSource{ + ClusterConfigMap: &types.VarsSourceClusterConfigMapOrSecret{ + Name: "cm", + Namespace: "ns", + Key: "vars", + }, + }, nil, "") + assert.NoError(t, err) + + v, _, _ := vc.Vars.GetNestedInt("test1", "test2") + assert.Equal(t, int64(42), v) + }, &cm) + + testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { + err := vl.LoadVars(vc, &types.VarsSource{ + ClusterConfigMap: &types.VarsSourceClusterConfigMapOrSecret{ + Name: "cm1", + Namespace: "ns", + Key: "vars1", + }, + }, nil, "") + assert.EqualError(t, err, "configmaps \"cm1\" not found") + + err = vl.LoadVars(vc, &types.VarsSource{ + ClusterConfigMap: &types.VarsSourceClusterConfigMapOrSecret{ + Name: "cm", + Namespace: "ns", + Key: "vars1", + }, + }, nil, "") + assert.EqualError(t, err, "key vars1 not found in ns/ConfigMap/cm on cluster") + }, &cm) +} + +func TestVarsLoader_ClusterSecret(t *testing.T) { + secret := corev1.Secret{ + ObjectMeta: v1.ObjectMeta{Name: "s", Namespace: "ns"}, + Data: map[string][]byte{ + "vars": []byte(`{"test1": {"test2": 42}}`), + }, + } + + testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { + err := vl.LoadVars(vc, &types.VarsSource{ + ClusterSecret: &types.VarsSourceClusterConfigMapOrSecret{ + Name: "s", + Namespace: "ns", + Key: "vars", + }, + }, nil, "") + assert.NoError(t, err) + + v, _, _ := vc.Vars.GetNestedInt("test1", "test2") + assert.Equal(t, int64(42), v) + }, &secret) + + testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { + err := vl.LoadVars(vc, &types.VarsSource{ + ClusterSecret: &types.VarsSourceClusterConfigMapOrSecret{ + Name: "s1", + Namespace: "ns", + Key: "vars1", + }, + }, nil, "") + assert.EqualError(t, err, "secrets \"s1\" not found") + + err = vl.LoadVars(vc, &types.VarsSource{ + ClusterSecret: &types.VarsSourceClusterConfigMapOrSecret{ + Name: "s", + Namespace: "ns", + Key: "vars1", + }, + }, nil, "") + assert.EqualError(t, err, "key vars1 not found in ns/Secret/s on cluster") + }, &secret) +} + +func TestVarsLoader_SystemEnv(t *testing.T) { + t.Setenv("TEST1", "42") + t.Setenv("TEST2", "43") + t.Setenv("TEST4", "44") + + testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { + err := vl.LoadVars(vc, &types.VarsSource{ + SystemEnvVars: uo.FromMap(map[string]interface{}{ + "test1": "TEST1", + "test2": "TEST2", + "test3": map[string]interface{}{ + "test4": "TEST4", + }, + }), + }, nil, "") + assert.NoError(t, err) + + v, _, _ := vc.Vars.GetNestedString("test1") + assert.Equal(t, "42", v) + + v, _, _ = vc.Vars.GetNestedString("test2") + assert.Equal(t, "43", v) + + v, _, _ = vc.Vars.GetNestedString("test3", "test4") + assert.Equal(t, "44", v) + }) + + testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { + err := vl.LoadVars(vc, &types.VarsSource{ + SystemEnvVars: uo.FromMap(map[string]interface{}{ + "test5": "TEST5", + }), + }, nil, "") + assert.EqualError(t, err, "environment variable TEST5 not found for test5") + }) +} + +func TestVarsLoader_Http_GET(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write([]byte(`{"test1": {"test2": 42}}`)) + })) + defer ts.Close() + + u, _ := url.Parse(ts.URL) + + testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { + err := vl.LoadVars(vc, &types.VarsSource{ + Http: &types.VarsSourceHttp{ + Url: types.YamlUrl{URL: *u}, + }, + }, nil, "") + assert.NoError(t, err) + + v, _, _ := vc.Vars.GetNestedInt("test1", "test2") + assert.Equal(t, int64(42), v) + }) +} + +func TestVarsLoader_Http_POST(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != "POST" { + w.WriteHeader(542) + return + } + body, _ := ioutil.ReadAll(r.Body) + if string(body) != "body" { + w.WriteHeader(543) + return + } + if r.Header.Get("h") != "h" { + w.WriteHeader(544) + return + } + _, _ = w.Write([]byte(`{"test1": {"test2": 42}}`)) + })) + defer ts.Close() + + u, _ := url.Parse(ts.URL) + + testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { + err := vl.LoadVars(vc, &types.VarsSource{ + Http: &types.VarsSourceHttp{ + Url: types.YamlUrl{URL: *u}, + }, + }, nil, "") + assert.ErrorContains(t, err, "failed with status code 542") + + err = vl.LoadVars(vc, &types.VarsSource{ + Http: &types.VarsSourceHttp{ + Url: types.YamlUrl{URL: *u}, + Method: utils.StrPtr("POST"), + }, + }, nil, "") + assert.ErrorContains(t, err, "failed with status code 543") + + err = vl.LoadVars(vc, &types.VarsSource{ + Http: &types.VarsSourceHttp{ + Url: types.YamlUrl{URL: *u}, + Method: utils.StrPtr("POST"), + Body: utils.StrPtr("body"), + }, + }, nil, "") + assert.ErrorContains(t, err, "failed with status code 544") + + err = vl.LoadVars(vc, &types.VarsSource{ + Http: &types.VarsSourceHttp{ + Url: types.YamlUrl{URL: *u}, + Method: utils.StrPtr("POST"), + Body: utils.StrPtr("body"), + Headers: map[string]string{"h": "h"}, + }, + }, nil, "") + + v, _, _ := vc.Vars.GetNestedInt("test1", "test2") + assert.Equal(t, int64(42), v) + }) +} + +func TestVarsLoader_Http_JsonPath(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write([]byte(`{"test1": "{\"test2\": 42}"}`)) + })) + defer ts.Close() + + u, _ := url.Parse(ts.URL) + + testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { + err := vl.LoadVars(vc, &types.VarsSource{ + Http: &types.VarsSourceHttp{ + Url: types.YamlUrl{URL: *u}, + JsonPath: utils.StrPtr("test1"), + }, + }, nil, "") + assert.NoError(t, err) + + v, _, _ := vc.Vars.GetNestedInt("test2") + assert.Equal(t, int64(42), v) + }) +} + +func TestVarsLoader_AwsSecretsManager(t *testing.T) { + testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { + aws.Secrets = map[string]string{ + "secret": `{"test1": {"test2": 42}}`, + } + + err := vl.LoadVars(vc, &types.VarsSource{ + AwsSecretsManager: &types.VarsSourceAwsSecretsManager{ + SecretName: "secret", + }, + }, nil, "") + assert.EqualError(t, err, "when omitting the AWS region, the secret name must be a valid ARN") + + err = vl.LoadVars(vc, &types.VarsSource{ + AwsSecretsManager: &types.VarsSourceAwsSecretsManager{ + SecretName: "secret", + Region: utils.StrPtr("eu-central1"), + }, + }, nil, "") + assert.NoError(t, err) + + v, _, _ := vc.Vars.GetNestedInt("test1", "test2") + assert.Equal(t, int64(42), v) + }) + + testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { + aws.Secrets = map[string]string{ + "secret": `{"test1": {"test2": 42}}`, + } + + err := vl.LoadVars(vc, &types.VarsSource{ + AwsSecretsManager: &types.VarsSourceAwsSecretsManager{ + SecretName: "arn:aws:secretsmanager:eu-central-1:12345:secret:secret", + }, + }, nil, "") + assert.NoError(t, err) + + v, _, _ := vc.Vars.GetNestedInt("test1", "test2") + assert.Equal(t, int64(42), v) + }) +} diff --git a/pkg/vars/vars_test.go b/pkg/vars/vars_test.go index b922b7279..66e7bb092 100644 --- a/pkg/vars/vars_test.go +++ b/pkg/vars/vars_test.go @@ -7,12 +7,19 @@ import ( "testing" ) -func TestVarsCtx(t *testing.T) { +func newJinja2Must(t *testing.T) *jinja2.Jinja2 { j2, err := jinja2.NewJinja2() if err != nil { t.Fatal(err) } - defer j2.Close() + t.Cleanup(func() { + j2.Close() + }) + return j2 +} + +func TestVarsCtx(t *testing.T) { + j2 := newJinja2Must(t) varsCtx := NewVarsCtx(j2) varsCtx.Update(uo.FromMap(map[string]interface{}{ @@ -25,11 +32,7 @@ func TestVarsCtx(t *testing.T) { } func TestVarsCtxChild(t *testing.T) { - j2, err := jinja2.NewJinja2() - if err != nil { - t.Fatal(err) - } - defer j2.Close() + j2 := newJinja2Must(t) varsCtx := NewVarsCtx(j2) varsCtx.UpdateChild("child", uo.FromMap(map[string]interface{}{ @@ -42,11 +45,7 @@ func TestVarsCtxChild(t *testing.T) { } func TestVarsCtxStruct(t *testing.T) { - j2, err := jinja2.NewJinja2() - if err != nil { - t.Fatal(err) - } - defer j2.Close() + j2 := newJinja2Must(t) varsCtx := NewVarsCtx(j2) @@ -58,7 +57,7 @@ func TestVarsCtxStruct(t *testing.T) { Test1: struct{ Test2 int }{Test2: 42}, } - err = varsCtx.UpdateChildFromStruct("child", s) + err := varsCtx.UpdateChildFromStruct("child", s) assert.NoError(t, err) v, _, _ := varsCtx.Vars.GetNestedInt("child", "test1", "test2") From b992f22ff89b3226e8648b6e45c30c5707897e9d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sun, 22 May 2022 16:55:40 +0200 Subject: [PATCH 0855/2916] feat: Make helm-values.yml optional This removes the need to provide an empty helm-values.yml in case no values need to be overridden. Fixes #30 --- pkg/deployment/helm_chart.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index 2e2727fb8..509773443 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -7,6 +7,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/utils/versions" "github.com/kluctl/kluctl/v2/pkg/yaml" @@ -215,8 +216,10 @@ func (c *helmChart) doRender(ctx context.Context, k *k8s.K8sCluster) error { } settings := cli.New() - valueOpts := values.Options{ - ValueFiles: []string{valuesPath}, + valueOpts := values.Options{} + + if utils.Exists(valuesPath) { + valueOpts.ValueFiles = append(valueOpts.ValueFiles, valuesPath) } var kubeVersion *chartutil.KubeVersion From 3b1297d98152983ab8d2d0535dc7315b42af853d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sun, 22 May 2022 17:51:42 +0200 Subject: [PATCH 0856/2916] feat: Load Helm credentials from repositories config --- pkg/deployment/helm_chart.go | 51 +++++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index 509773443..cc6d43776 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -115,6 +115,17 @@ func (c *helmChart) Pull(ctx context.Context) error { a.DestDir = targetDir a.Version = *c.Config.ChartVersion + s := c.getRepoSettings(a.Settings, *c.Config.Repo) + if s != nil { + a.Username = s.Username + a.Password = s.Password + a.CertFile = s.CertFile + a.CaFile = s.CAFile + a.KeyFile = s.KeyFile + a.InsecureSkipTLSverify = s.InsecureSkipTLSverify + a.PassCredentialsAll = s.PassCredentialsAll + } + var out string if registry.IsOCI(*c.Config.Repo) { out, err = a.Run(*c.Config.Repo) @@ -145,11 +156,14 @@ func (c *helmChart) CheckUpdate() (string, bool, error) { var latestVersion string settings := cli.New() - e := repo.Entry{ - URL: *c.Config.Repo, - Name: chartName, + e := c.getRepoSettings(settings, *c.Config.Repo) + if e == nil { + e = &repo.Entry{ + URL: *c.Config.Repo, + } } - r, err := repo.NewChartRepository(&e, getter.All(settings)) + + r, err := repo.NewChartRepository(e, getter.All(settings)) if err != nil { return "", false, err } @@ -164,9 +178,9 @@ func (c *helmChart) CheckUpdate() (string, bool, error) { return "", false, err } - indexEntry, ok := index.Entries[*c.Config.ChartName] + indexEntry, ok := index.Entries[chartName] if !ok || len(indexEntry) == 0 { - return "", false, fmt.Errorf("helm chart %s not found in repo index", *c.Config.ChartName) + return "", false, fmt.Errorf("helm chart %s not found in repo index", chartName) } var ls versions.LooseVersionSlice @@ -338,6 +352,31 @@ func (c *helmChart) doRender(ctx context.Context, k *k8s.K8sCluster) error { return nil } +func (c *helmChart) getRepoSettings(env *cli.EnvSettings, repoUrl string) *repo.Entry { + f, err := repo.LoadFile(env.RepositoryConfig) + if err != nil { + return nil + } + + removeTrailingSlash := func(s string) string { + if len(s) == 0 { + return s + } + if s[len(s)-1] == '/' { + return s[:len(s)-1] + } + return s + } + repoUrl = removeTrailingSlash(repoUrl) + + for _, e := range f.Repositories { + if removeTrailingSlash(e.URL) == repoUrl { + return e + } + } + return nil +} + func checkIfInstallable(ch *chart.Chart) error { switch ch.Metadata.Type { case "", "application": From 3acd4a2871fe00af8484eeee4e1309d68faff3fb Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sun, 22 May 2022 17:55:59 +0200 Subject: [PATCH 0857/2916] refactor: No need to use pointers for booleans with default "false" value --- cmd/kluctl/commands/cmd_helm_update.go | 6 ++---- pkg/deployment/deployment_collection.go | 5 ++--- pkg/deployment/deployment_item.go | 9 ++++----- pkg/deployment/helm_chart.go | 2 +- pkg/deployment/utils/apply_utils.go | 4 ++-- pkg/types/deployment.go | 12 ++++++------ pkg/types/helm_chart.go | 4 ++-- 7 files changed, 19 insertions(+), 23 deletions(-) diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go index 59e3df5d9..17dc10c71 100644 --- a/cmd/kluctl/commands/cmd_helm_update.go +++ b/cmd/kluctl/commands/cmd_helm_update.go @@ -42,8 +42,6 @@ func (cmd *helmUpdateCmd) Run() error { s := status.Start(cliCtx, "%s: Checking for updates", statusPrefix) defer s.Failed() - skipUpdate := chart.Config.SkipUpdate != nil && *chart.Config.SkipUpdate - newVersion, updated, err := chart.CheckUpdate() if err != nil { return err @@ -54,7 +52,7 @@ func (cmd *helmUpdateCmd) Run() error { return nil } msg := fmt.Sprintf("%s: Chart has new version %s available. Old version is %s.", statusPrefix, newVersion, *chart.Config.ChartVersion) - if skipUpdate { + if chart.Config.SkipUpdate { msg += " skipUpdate is set to true." } s.Update(msg) @@ -62,7 +60,7 @@ func (cmd *helmUpdateCmd) Run() error { if !cmd.Upgrade { s.Success() } else { - if skipUpdate { + if chart.Config.SkipUpdate { s.Update("%s: NOT upgrading chart as skipUpdate was set to true", statusPrefix) s.Success() return nil diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index 64b100788..03883a86c 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -46,9 +46,8 @@ func NewDeploymentCollection(ctx context.Context, project *DeploymentProject, im } func (c *DeploymentCollection) createBarrierDummy(project *DeploymentProject) *DeploymentItem { - b := true tmpDiConfig := &types.DeploymentItemConfig{ - Barrier: &b, + Barrier: true, } di, err := NewDeploymentItem(project, c, tmpDiConfig, nil, 0) if err != nil { @@ -93,7 +92,7 @@ func (c *DeploymentCollection) collectDeployments(project *DeploymentProject, in return nil, err } ret = append(ret, ret2...) - if diConfig.Barrier != nil && *diConfig.Barrier { + if diConfig.Barrier { ret = append(ret, c.createBarrierDummy(project)) } } else { diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 7051ce9c3..82038c71b 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -101,7 +101,7 @@ func (di *DeploymentItem) getCommonAnnotations() map[string]string { a := map[string]string{ "kluctl.io/kustomize_dir": filepath.ToSlash(di.RelToRootItemDir), } - if di.Config.SkipDeleteIfTags != nil && *di.Config.SkipDeleteIfTags { + if di.Config.SkipDeleteIfTags { a["kluctl.io/skip-delete-if-tags"] = "true" } return a @@ -267,10 +267,10 @@ func (di *DeploymentItem) CheckInclusionForDeploy() bool { if di.Inclusion == nil { return true } - if di.Config.OnlyRender != nil && *di.Config.OnlyRender { + if di.Config.OnlyRender { return true } - if di.Config.AlwaysDeploy != nil && *di.Config.AlwaysDeploy { + if di.Config.AlwaysDeploy { return true } values := di.buildInclusionEntries() @@ -281,9 +281,8 @@ func (di *DeploymentItem) checkInclusionForDelete() bool { if di.Inclusion == nil { return true } - skipDeleteIfTags := di.Config.SkipDeleteIfTags != nil && *di.Config.SkipDeleteIfTags values := di.buildInclusionEntries() - return di.Inclusion.CheckIncluded(values, skipDeleteIfTags) + return di.Inclusion.CheckIncluded(values, di.Config.SkipDeleteIfTags) } func (di *DeploymentItem) prepareKustomizationYaml() error { diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index cc6d43776..93ba7e85a 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -257,7 +257,7 @@ func (c *helmChart) doRender(ctx context.Context, k *k8s.K8sCluster) error { client.ClientOnly = true client.KubeVersion = kubeVersion - if c.Config.SkipCRDs != nil && *c.Config.SkipCRDs { + if c.Config.SkipCRDs { client.SkipCRDs = true } else { client.IncludeCRDs = true diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index ff10287a5..46b246351 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -480,7 +480,7 @@ func (a *ApplyUtil) applyDeploymentItem(d *deployment.DeploymentItem) { didLog = true } - waitReadiness := (d.Config.WaitReadiness != nil && *d.Config.WaitReadiness) || d.WaitReadiness || utils.ParseBoolOrFalse(o.GetK8sAnnotation("kluctl.io/wait-readiness")) + waitReadiness := d.Config.WaitReadiness || d.WaitReadiness || utils.ParseBoolOrFalse(o.GetK8sAnnotation("kluctl.io/wait-readiness")) if !a.o.NoWait && waitReadiness { a.WaitReadiness(o.GetK8sRef(), 0) } @@ -577,7 +577,7 @@ func (a *ApplyDeploymentsUtil) ApplyDeployments() { sctx.Failed() }() - barrier := (d.Config.Barrier != nil && *d.Config.Barrier) || d.Barrier + barrier := d.Config.Barrier || d.Barrier if barrier { sctx := status.StartWithOptions(a.ctx, status.WithStatus("Waiting on barrier..."), status.WithTotal(1)) wg.Wait() diff --git a/pkg/types/deployment.go b/pkg/types/deployment.go index b129aa8b8..b9db6bb96 100644 --- a/pkg/types/deployment.go +++ b/pkg/types/deployment.go @@ -9,12 +9,12 @@ type DeploymentItemConfig struct { Path *string `yaml:"path,omitempty"` Include *string `yaml:"include,omitempty"` Tags []string `yaml:"tags,omitempty"` - Barrier *bool `yaml:"barrier,omitempty"` - WaitReadiness *bool `yaml:"waitReadiness,omitempty"` + Barrier bool `yaml:"barrier,omitempty"` + WaitReadiness bool `yaml:"waitReadiness,omitempty"` Vars []*VarsSource `yaml:"vars,omitempty"` - SkipDeleteIfTags *bool `yaml:"skipDeleteIfTags,omitempty"` - OnlyRender *bool `yaml:"onlyRender,omitempty"` - AlwaysDeploy *bool `yaml:"alwaysDeploy,omitempty"` + SkipDeleteIfTags bool `yaml:"skipDeleteIfTags,omitempty"` + OnlyRender bool `yaml:"onlyRender,omitempty"` + AlwaysDeploy bool `yaml:"alwaysDeploy,omitempty"` DeleteObjects []DeleteObjectItemConfig `yaml:"deleteObjects,omitempty"` } @@ -23,7 +23,7 @@ func ValidateDeploymentItemConfig(sl validator.StructLevel) { if s.Path != nil && s.Include != nil { sl.ReportError(s, "path", "Path", "path and include can not be set at the same time", "") } - if s.Path == nil && s.WaitReadiness != nil { + if s.Path == nil && s.WaitReadiness { sl.ReportError(s, "waitReadiness", "WaitReadiness", "only kustomize deployments are allowed to have waitReadiness set", "") } } diff --git a/pkg/types/helm_chart.go b/pkg/types/helm_chart.go index c8e0e91f3..88a960a12 100644 --- a/pkg/types/helm_chart.go +++ b/pkg/types/helm_chart.go @@ -7,8 +7,8 @@ type HelmChartConfig2 struct { ReleaseName string `yaml:"releaseName" validate:"required"` Namespace *string `yaml:"namespace,omitempty"` Output string `yaml:"output" validate:"required"` - SkipCRDs *bool `yaml:"skipCRDs,omitempty"` - SkipUpdate *bool `yaml:"skipUpdate,omitempty"` + SkipCRDs bool `yaml:"skipCRDs,omitempty"` + SkipUpdate bool `yaml:"skipUpdate,omitempty"` } type HelmChartConfig struct { From 754bae1bd4bf309ebb75ec4cc70b22c108b4d55c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 23 May 2022 09:29:52 +0200 Subject: [PATCH 0858/2916] feat: Allow to specify helm repository credentials via CLI Fixes: #31 --- cmd/kluctl/args/helm_credentials.go | 81 ++++++++++++++++++++++++++ cmd/kluctl/commands/cmd_helm_pull.go | 19 ++++++ cmd/kluctl/commands/cmd_helm_update.go | 18 +++++- pkg/deployment/helm_chart.go | 49 +++++----------- pkg/types/helm_chart.go | 17 +++--- 5 files changed, 138 insertions(+), 46 deletions(-) create mode 100644 cmd/kluctl/args/helm_credentials.go diff --git a/cmd/kluctl/args/helm_credentials.go b/cmd/kluctl/args/helm_credentials.go new file mode 100644 index 000000000..23db78c6d --- /dev/null +++ b/cmd/kluctl/args/helm_credentials.go @@ -0,0 +1,81 @@ +package args + +import ( + "helm.sh/helm/v3/pkg/cli" + "helm.sh/helm/v3/pkg/repo" + "strings" +) + +type HelmCredentials struct { + Username []string `group:"misc" help:"Specify username to use for Helm Repository authentication. Must be in the form --username=:, where must match the id specified in the helm-chart.yaml."` + Password []string `group:"misc" help:"Specify password to use for Helm Repository authentication. Must be in the form --password=:, where must match the id specified in the helm-chart.yaml."` + KeyFile []string `group:"misc" help:"Specify client certificate to use for Helm Repository authentication. Must be in the form --key-file=:, where must match the id specified in the helm-chart.yaml."` + InsecureSkipTlsVerify []string `group:"misc" help:"Controls skipping of TLS verification. Must be in the form --insecure-skip-tls-verify=, where must match the id specified in the helm-chart.yaml."` +} + +func (c *HelmCredentials) FindCredentials(repoUrl string, credentialsId *string) *repo.Entry { + if credentialsId != nil { + splitIdAndValue := func(s string) (string, bool) { + x := strings.SplitN(s, ":", 2) + if len(x) < 0 { + return "", false + } + if x[0] != *credentialsId { + return "", false + } + return x[1], true + } + + var e repo.Entry + for _, x := range c.Username { + if v, ok := splitIdAndValue(x); ok { + e.Username = v + } + } + for _, x := range c.Password { + if v, ok := splitIdAndValue(x); ok { + e.Password = v + } + } + for _, x := range c.KeyFile { + if v, ok := splitIdAndValue(x); ok { + e.KeyFile = v + } + } + for _, x := range c.InsecureSkipTlsVerify { + if x == *credentialsId { + e.InsecureSkipTLSverify = true + } + } + + if e != (repo.Entry{}) { + return &e + } + } + + env := cli.New() + + f, err := repo.LoadFile(env.RepositoryConfig) + if err != nil { + return nil + } + + removeTrailingSlash := func(s string) string { + if len(s) == 0 { + return s + } + if s[len(s)-1] == '/' { + return s[:len(s)-1] + } + return s + } + repoUrl = removeTrailingSlash(repoUrl) + + for _, e := range f.Repositories { + if removeTrailingSlash(e.URL) == repoUrl { + return e + } + } + + return nil +} diff --git a/cmd/kluctl/commands/cmd_helm_pull.go b/cmd/kluctl/commands/cmd_helm_pull.go index 5739b54ed..d727ee594 100644 --- a/cmd/kluctl/commands/cmd_helm_pull.go +++ b/cmd/kluctl/commands/cmd_helm_pull.go @@ -1,6 +1,8 @@ package commands import ( + "fmt" + "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/deployment" "github.com/kluctl/kluctl/v2/pkg/status" "io/fs" @@ -8,6 +10,8 @@ import ( ) type helmPullCmd struct { + args.HelmCredentials + LocalDeployment string `group:"project" help:"Local deployment directory. Defaults to current directory"` } @@ -22,6 +26,7 @@ func (cmd *helmPullCmd) Run() error { if cmd.LocalDeployment != "" { rootPath = cmd.LocalDeployment } + err := filepath.WalkDir(rootPath, func(p string, d fs.DirEntry, err error) error { fname := filepath.Base(p) if fname == "helm-chart.yml" || fname == "helm-chart.yaml" { @@ -31,6 +36,15 @@ func (cmd *helmPullCmd) Run() error { s.FailedWithMessage(err.Error()) return err } + + creds := cmd.HelmCredentials.FindCredentials(*chart.Config.Repo, chart.Config.CredentialsId) + if chart.Config.CredentialsId != nil && creds == nil { + err := fmt.Errorf("no credentials provided for %s", p) + s.FailedWithMessage(err.Error()) + return err + } + chart.SetCredentials(creds) + err = chart.Pull(cliCtx) if err != nil { s.FailedWithMessage(err.Error()) @@ -40,5 +54,10 @@ func (cmd *helmPullCmd) Run() error { } return nil }) + + if err != nil { + return fmt.Errorf("command failed") + } + return err } diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go index 17dc10c71..fb6b35972 100644 --- a/cmd/kluctl/commands/cmd_helm_update.go +++ b/cmd/kluctl/commands/cmd_helm_update.go @@ -3,6 +3,7 @@ package commands import ( "fmt" "github.com/go-git/go-git/v5" + "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/deployment" git2 "github.com/kluctl/kluctl/v2/pkg/git" "github.com/kluctl/kluctl/v2/pkg/status" @@ -11,6 +12,8 @@ import ( ) type helmUpdateCmd struct { + args.HelmCredentials + LocalDeployment string `group:"project" help:"Local deployment directory. Defaults to current directory"` Upgrade bool `group:"misc" help:"Write new versions into helm-chart.yml and perform helm-pull afterwards"` Commit bool `group:"misc" help:"Create a git commit for every updated chart"` @@ -33,14 +36,23 @@ func (cmd *helmUpdateCmd) Run() error { err = filepath.WalkDir(rootPath, func(p string, d fs.DirEntry, err error) error { fname := filepath.Base(p) if fname == "helm-chart.yml" || fname == "helm-chart.yaml" { + statusPrefix := filepath.Base(filepath.Dir(p)) + s := status.Start(cliCtx, "%s: Checking for updates", statusPrefix) + defer s.Failed() + chart, err := deployment.NewHelmChart(p) if err != nil { + s.Update("%s: Error while loading helm-chart.yaml: %v", statusPrefix, err) return err } - statusPrefix := filepath.Base(filepath.Dir(p)) - s := status.Start(cliCtx, "%s: Checking for updates", statusPrefix) - defer s.Failed() + creds := cmd.HelmCredentials.FindCredentials(*chart.Config.Repo, chart.Config.CredentialsId) + if chart.Config.CredentialsId != nil && creds == nil { + err := fmt.Errorf("%s: No credentials provided", statusPrefix) + s.FailedWithMessage(err.Error()) + return err + } + chart.SetCredentials(creds) newVersion, updated, err := chart.CheckUpdate() if err != nil { diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index 93ba7e85a..7c42f2f1b 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -32,8 +32,9 @@ import ( ) type helmChart struct { - configFile string - Config *types.HelmChartConfig + configFile string + Config *types.HelmChartConfig + credentials *repo.Entry } func NewHelmChart(configFile string) (*helmChart, error) { @@ -115,15 +116,14 @@ func (c *helmChart) Pull(ctx context.Context) error { a.DestDir = targetDir a.Version = *c.Config.ChartVersion - s := c.getRepoSettings(a.Settings, *c.Config.Repo) - if s != nil { - a.Username = s.Username - a.Password = s.Password - a.CertFile = s.CertFile - a.CaFile = s.CAFile - a.KeyFile = s.KeyFile - a.InsecureSkipTLSverify = s.InsecureSkipTLSverify - a.PassCredentialsAll = s.PassCredentialsAll + if c.credentials != nil { + a.Username = c.credentials.Username + a.Password = c.credentials.Password + a.CertFile = c.credentials.CertFile + a.CaFile = c.credentials.CAFile + a.KeyFile = c.credentials.KeyFile + a.InsecureSkipTLSverify = c.credentials.InsecureSkipTLSverify + a.PassCredentialsAll = c.credentials.PassCredentialsAll } var out string @@ -156,7 +156,7 @@ func (c *helmChart) CheckUpdate() (string, bool, error) { var latestVersion string settings := cli.New() - e := c.getRepoSettings(settings, *c.Config.Repo) + e := c.credentials if e == nil { e = &repo.Entry{ URL: *c.Config.Repo, @@ -352,29 +352,8 @@ func (c *helmChart) doRender(ctx context.Context, k *k8s.K8sCluster) error { return nil } -func (c *helmChart) getRepoSettings(env *cli.EnvSettings, repoUrl string) *repo.Entry { - f, err := repo.LoadFile(env.RepositoryConfig) - if err != nil { - return nil - } - - removeTrailingSlash := func(s string) string { - if len(s) == 0 { - return s - } - if s[len(s)-1] == '/' { - return s[:len(s)-1] - } - return s - } - repoUrl = removeTrailingSlash(repoUrl) - - for _, e := range f.Repositories { - if removeTrailingSlash(e.URL) == repoUrl { - return e - } - } - return nil +func (c *helmChart) SetCredentials(credentials *repo.Entry) { + c.credentials = credentials } func checkIfInstallable(ch *chart.Chart) error { diff --git a/pkg/types/helm_chart.go b/pkg/types/helm_chart.go index 88a960a12..3b78cd353 100644 --- a/pkg/types/helm_chart.go +++ b/pkg/types/helm_chart.go @@ -1,14 +1,15 @@ package types type HelmChartConfig2 struct { - Repo *string `yaml:"repo" validate:"required"` - ChartName *string `yaml:"chartName,omitempty"` - ChartVersion *string `yaml:"chartVersion" validate:"required"` - ReleaseName string `yaml:"releaseName" validate:"required"` - Namespace *string `yaml:"namespace,omitempty"` - Output string `yaml:"output" validate:"required"` - SkipCRDs bool `yaml:"skipCRDs,omitempty"` - SkipUpdate bool `yaml:"skipUpdate,omitempty"` + Repo *string `yaml:"repo" validate:"required"` + CredentialsId *string `yaml:"credentialsId,omitempty"` + ChartName *string `yaml:"chartName,omitempty"` + ChartVersion *string `yaml:"chartVersion" validate:"required"` + ReleaseName string `yaml:"releaseName" validate:"required"` + Namespace *string `yaml:"namespace,omitempty"` + Output string `yaml:"output" validate:"required"` + SkipCRDs bool `yaml:"skipCRDs,omitempty"` + SkipUpdate bool `yaml:"skipUpdate,omitempty"` } type HelmChartConfig struct { From 660ccf8e295bd707a5134636afd99f5f0c5d64e0 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 23 May 2022 09:30:45 +0200 Subject: [PATCH 0859/2916] fix: Don't let cobra print errors (rely on status reporting instead) --- cmd/kluctl/commands/root.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index 56f91a2d6..f157d961e 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -184,6 +184,7 @@ composed of multiple smaller parts (Helm/Kustomize/...) in a manageable and unif rootCmd.Version = version.GetVersion() rootCmd.SilenceUsage = true + rootCmd.SilenceErrors = true rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error { err := copyViperValuesToCobraCmd(cmd) From 60f85b3e057d4401f96c984404fc271b958e2f9b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 23 May 2022 10:25:18 +0200 Subject: [PATCH 0860/2916] docs: Replace .yml with .yaml in help strings --- cmd/kluctl/args/images.go | 2 +- cmd/kluctl/args/project.go | 12 ++++++------ cmd/kluctl/commands/cmd_delete.go | 2 +- cmd/kluctl/commands/cmd_helm_pull.go | 2 +- cmd/kluctl/commands/cmd_helm_update.go | 2 +- cmd/kluctl/commands/cmd_prune.go | 2 +- cmd/kluctl/commands/root.go | 6 +++--- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/cmd/kluctl/args/images.go b/cmd/kluctl/args/images.go index f11fba0a4..13aaf244d 100644 --- a/cmd/kluctl/args/images.go +++ b/cmd/kluctl/args/images.go @@ -9,7 +9,7 @@ import ( type ImageFlags struct { FixedImage []string `group:"images" short:"F" help:"Pin an image to a given version. Expects '--fixed-image=image<:namespace:deployment:container>=result'"` - FixedImagesFile existingFileType `group:"images" help:"Use .yml file to pin image versions. See output of list-images sub-command or read the documentation for details about the output format" exts:"yml,yaml"` + FixedImagesFile existingFileType `group:"images" help:"Use .yaml file to pin image versions. See output of list-images sub-command or read the documentation for details about the output format" exts:"yml,yaml"` UpdateImages bool `group:"images" short:"u" help:"This causes kluctl to prefer the latest image found in registries, based on the 'latest_image' filters provided to 'images.get_image(...)' calls. Use this flag if you want to update to the latest versions/tags of all images. '-u' takes precedence over '--fixed-image/--fixed-images-file', meaning that the latest images are used even if an older image is given via fixed images."` OfflineImages bool `group:"images" help:"Omit contacting image registries and do not query for latest image tags."` } diff --git a/cmd/kluctl/args/project.go b/cmd/kluctl/args/project.go index ab9da298f..c7479b869 100644 --- a/cmd/kluctl/args/project.go +++ b/cmd/kluctl/args/project.go @@ -6,11 +6,11 @@ type ProjectFlags struct { ProjectUrl string `group:"project" short:"p" help:"Git url of the kluctl project. If not specified, the current directory will be used instead of a remote Git project"` ProjectRef string `group:"project" short:"b" help:"Git ref of the kluctl project. Only used when --project-url was given."` - ProjectConfig existingFileType `group:"project" short:"c" help:"Location of the .kluctl.yml config file. Defaults to $PROJECT/.kluctl.yml" exts:"yml,yaml"` - LocalClusters existingDirType `group:"project" help:"Local clusters directory. Overrides the project from .kluctl.yml"` - LocalDeployment existingDirType `group:"project" help:"Local deployment directory. Overrides the project from .kluctl.yml"` - LocalSealedSecrets existingDirType `group:"project" help:"Local sealed-secrets directory. Overrides the project from .kluctl.yml" ` - FromArchive existingPathType `group:"project" help:"Load project (.kluctl.yml, cluster, ...) from archive. Given path can either be an archive file or a directory with the extracted contents." exts:"tar.gz,tgz"` + ProjectConfig existingFileType `group:"project" short:"c" help:"Location of the .kluctl.yaml config file. Defaults to $PROJECT/.kluctl.yaml" exts:"yml,yaml"` + LocalClusters existingDirType `group:"project" help:"Local clusters directory. Overrides the project from .kluctl.yaml"` + LocalDeployment existingDirType `group:"project" help:"Local deployment directory. Overrides the project from .kluctl.yaml"` + LocalSealedSecrets existingDirType `group:"project" help:"Local sealed-secrets directory. Overrides the project from .kluctl.yaml" ` + FromArchive existingPathType `group:"project" help:"Load project (.kluctl.yaml, cluster, ...) from archive. Given path can either be an archive file or a directory with the extracted contents." exts:"tar.gz,tgz"` FromArchiveMetadata existingFileType `group:"project" help:"Specify where to load metadata (targets, ...) from. If not specified, metadata is assumed to be part of the archive." exts:"yml,yaml"` OutputMetadata pathType `group:"project" help:"Specify the output path for the project metadata to be written to. When used with the 'archive' command, it will also cause the archive to not include the metadata.yaml file."` Cluster string `group:"project" help:"Specify/Override cluster"` @@ -24,5 +24,5 @@ type ArgsFlags struct { } type TargetFlags struct { - Target string `group:"project" short:"t" help:"Target name to run command for. Target must exist in .kluctl.yml."` + Target string `group:"project" short:"t" help:"Target name to run command for. Target must exist in .kluctl.yaml."` } diff --git a/cmd/kluctl/commands/cmd_delete.go b/cmd/kluctl/commands/cmd_delete.go index 2cd7607fa..4bb602290 100644 --- a/cmd/kluctl/commands/cmd_delete.go +++ b/cmd/kluctl/commands/cmd_delete.go @@ -28,7 +28,7 @@ type deleteCmd struct { } func (cmd *deleteCmd) Help() string { - return `Objects are located based on 'commonLabels'', configured in 'deployment.yml' + return `Objects are located based on 'commonLabels', configured in 'deployment.yaml' WARNING: This command will also delete objects which are not part of your deployment project (anymore). It really only decides based on the 'deleteByLabel' labels and does NOT diff --git a/cmd/kluctl/commands/cmd_helm_pull.go b/cmd/kluctl/commands/cmd_helm_pull.go index d727ee594..90d8c9fc8 100644 --- a/cmd/kluctl/commands/cmd_helm_pull.go +++ b/cmd/kluctl/commands/cmd_helm_pull.go @@ -17,7 +17,7 @@ type helmPullCmd struct { func (cmd *helmPullCmd) Help() string { return `The Helm charts are stored under the sub-directory 'charts/' next to the -'helm-chart.yml'. These Helm charts are meant to be added to version control so that +'helm-chart.yaml'. These Helm charts are meant to be added to version control so that pulling is only needed when really required (e.g. when the chart version changes).` } diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go index fb6b35972..55c30b8e8 100644 --- a/cmd/kluctl/commands/cmd_helm_update.go +++ b/cmd/kluctl/commands/cmd_helm_update.go @@ -15,7 +15,7 @@ type helmUpdateCmd struct { args.HelmCredentials LocalDeployment string `group:"project" help:"Local deployment directory. Defaults to current directory"` - Upgrade bool `group:"misc" help:"Write new versions into helm-chart.yml and perform helm-pull afterwards"` + Upgrade bool `group:"misc" help:"Write new versions into helm-chart.yaml and perform helm-pull afterwards"` Commit bool `group:"misc" help:"Create a git commit for every updated chart"` } diff --git a/cmd/kluctl/commands/cmd_prune.go b/cmd/kluctl/commands/cmd_prune.go index 602d3f3a2..14d4d8790 100644 --- a/cmd/kluctl/commands/cmd_prune.go +++ b/cmd/kluctl/commands/cmd_prune.go @@ -21,7 +21,7 @@ type pruneCmd struct { func (cmd *pruneCmd) Help() string { return `"Searching works by: - 1. Search the cluster for all objects match 'commonLabels', as configured in 'deployment.yml'' + 1. Search the cluster for all objects match 'commonLabels', as configured in 'deployment.yaml' 2. Render the local target and list all objects. 3. Remove all objects from the list of 1. that are part of the list in 2.` } diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index f157d961e..9c8e70711 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -48,8 +48,8 @@ type cli struct { Deploy deployCmd `cmd:"" help:"Deploys a target to the corresponding cluster"` Diff diffCmd `cmd:"" help:"Perform a diff between the locally rendered target and the already deployed target"` Downscale downscaleCmd `cmd:"" help:"Downscale all deployments"` - HelmPull helmPullCmd `cmd:"" help:"Recursively searches for 'helm-chart.yml' files and pulls the specified Helm charts"` - HelmUpdate helmUpdateCmd `cmd:"" help:"Recursively searches for 'helm-chart.yml'' files and checks for new available versions"` + HelmPull helmPullCmd `cmd:"" help:"Recursively searches for 'helm-chart.yaml' files and pulls the specified Helm charts"` + HelmUpdate helmUpdateCmd `cmd:"" help:"Recursively searches for 'helm-chart.yaml' files and checks for new available versions"` ListImages listImagesCmd `cmd:"" help:"Renders the target and outputs all images used via 'images.get_image(...)"` ListTargets listTargetsCmd `cmd:"" help:"Outputs a yaml list with all target, including dynamic targets"` PokeImages pokeImagesCmd `cmd:"" help:"Replace all images in target"` @@ -99,7 +99,7 @@ func (c *cli) checkNewVersion() { return } - versionCheckPath := filepath.Join(utils.GetTmpBaseDir(), "version_check.yml") + versionCheckPath := filepath.Join(utils.GetTmpBaseDir(), "version_check.yaml") var versionCheckState VersionCheckState err := yaml.ReadYamlFile(versionCheckPath, &versionCheckState) if err == nil { From 912ca491b55fe7637fa8827fad6f591feaa5ff74 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 23 May 2022 10:25:50 +0200 Subject: [PATCH 0861/2916] feat: Make helm-chart.yaml output field optional Fixes: #33 --- pkg/deployment/helm_chart.go | 6 +++++- pkg/types/helm_chart.go | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index 7c42f2f1b..6d48b661c 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -79,7 +79,11 @@ func (c *helmChart) GetChartDir() (string, error) { func (c *helmChart) GetOutputPath() (string, error) { dir := filepath.Dir(c.configFile) - return securejoin.SecureJoin(dir, c.Config.Output) + output := "helm-rendered.yaml" + if c.Config.Output != nil { + output = *c.Config.Output + } + return securejoin.SecureJoin(dir, output) } func (c *helmChart) buildHelmConfig() (*action.Configuration, error) { diff --git a/pkg/types/helm_chart.go b/pkg/types/helm_chart.go index 3b78cd353..d5e9e4445 100644 --- a/pkg/types/helm_chart.go +++ b/pkg/types/helm_chart.go @@ -7,7 +7,7 @@ type HelmChartConfig2 struct { ChartVersion *string `yaml:"chartVersion" validate:"required"` ReleaseName string `yaml:"releaseName" validate:"required"` Namespace *string `yaml:"namespace,omitempty"` - Output string `yaml:"output" validate:"required"` + Output *string `yaml:"output,omitempty"` SkipCRDs bool `yaml:"skipCRDs,omitempty"` SkipUpdate bool `yaml:"skipUpdate,omitempty"` } From 545187732c6197346f84f7852f8ad6b1f6ec366c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 23 May 2022 10:42:20 +0200 Subject: [PATCH 0862/2916] feat: Show error when output file from helm-chart.yaml is not included --- pkg/deployment/deployment_item.go | 45 +++++++++++++++++++++++++------ pkg/deployment/helm_chart.go | 12 ++++++--- 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 82038c71b..783084673 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -175,6 +175,22 @@ func (di *DeploymentItem) renderHelmCharts(k *k8s.K8sCluster, wp *utils.WorkerPo if err != nil { return err } + + ky, err := di.readKustoimizationYaml() + if err == nil && ky != nil { + resources, _, _ := ky.GetNestedStringList("resources") + found := false + for _, r := range resources { + if r == chart.GetOutputPath() { + found = true + break + } + } + if !found { + return fmt.Errorf("%s/kustomization.yaml does not include the rendered helm chart: %s", di.relRenderedDir, chart.GetOutputPath()) + } + } + return chart.Render(di.Project.ctx, k) }) return nil @@ -285,20 +301,37 @@ func (di *DeploymentItem) checkInclusionForDelete() bool { return di.Inclusion.CheckIncluded(values, di.Config.SkipDeleteIfTags) } -func (di *DeploymentItem) prepareKustomizationYaml() error { +func (di *DeploymentItem) readKustoimizationYaml() (*uo.UnstructuredObject, error) { if di.dir == nil { - return nil + return nil, nil } kustomizeYamlPath := yaml.FixPathExt(filepath.Join(di.renderedDir, "kustomization.yml")) if !utils.IsFile(kustomizeYamlPath) { - return nil + return nil, nil } ky, err := uo.FromFile(kustomizeYamlPath) + if err != nil { + return nil, err + } + + return ky, err +} + +func (di *DeploymentItem) writeKustomizationYaml(ky *uo.UnstructuredObject) error { + kustomizeYamlPath := yaml.FixPathExt(filepath.Join(di.renderedDir, "kustomization.yml")) + return yaml.WriteYamlFile(kustomizeYamlPath, ky) +} + +func (di *DeploymentItem) prepareKustomizationYaml() error { + ky, err := di.readKustoimizationYaml() if err != nil { return err } + if ky == nil { + return nil + } overrideNamespace := di.Project.getOverrideNamespace() if overrideNamespace != nil { @@ -315,11 +348,7 @@ func (di *DeploymentItem) prepareKustomizationYaml() error { di.WaitReadiness = utils.ParseBoolOrFalse(ky.GetK8sAnnotation("kluctl.io/wait-readiness")) // Save modified kustomize.yml - err = yaml.WriteYamlFile(kustomizeYamlPath, ky) - if err != nil { - return err - } - return nil + return di.writeKustomizationYaml(ky) } func (di *DeploymentItem) buildKustomize() error { diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index 6d48b661c..b41313276 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -77,13 +77,17 @@ func (c *helmChart) GetChartDir() (string, error) { return securejoin.SecureJoin(targetDir, chartName) } -func (c *helmChart) GetOutputPath() (string, error) { - dir := filepath.Dir(c.configFile) +func (c *helmChart) GetOutputPath() string { output := "helm-rendered.yaml" if c.Config.Output != nil { output = *c.Config.Output } - return securejoin.SecureJoin(dir, output) + return output +} + +func (c *helmChart) GetFullOutputPath() (string, error) { + dir := filepath.Dir(c.configFile) + return securejoin.SecureJoin(dir, c.GetOutputPath()) } func (c *helmChart) buildHelmConfig() (*action.Configuration, error) { @@ -215,7 +219,7 @@ func (c *helmChart) doRender(ctx context.Context, k *k8s.K8sCluster) error { if err != nil { return err } - outputPath, err := c.GetOutputPath() + outputPath, err := c.GetFullOutputPath() if err != nil { return err } From 76a47b0fc19dcef3c5d5ca4fc077aef69feaa1ed Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 23 May 2022 10:42:47 +0200 Subject: [PATCH 0863/2916] fix: Show cluster config warning only once --- pkg/kluctl_project/project.go | 3 +++ pkg/kluctl_project/target_context.go | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/kluctl_project/project.go b/pkg/kluctl_project/project.go index 98a460568..bb59ce9b2 100644 --- a/pkg/kluctl_project/project.go +++ b/pkg/kluctl_project/project.go @@ -6,6 +6,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/git" "github.com/kluctl/kluctl/v2/pkg/jinja2" types2 "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/utils" "regexp" ) @@ -31,6 +32,8 @@ type LoadedKluctlProject struct { J2 *jinja2.Jinja2 GRC *git.MirroredGitRepoCollection + + warnOnce utils.OnceByKey } func (c *LoadedKluctlProject) GetMetadata() *types2.ProjectMetadata { diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index 400d9a118..d42151d32 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -191,7 +191,9 @@ func (p *LoadedKluctlProject) LoadClusterConfig(clusterName *string, contextName var clusterConfig *types.ClusterConfig if clusterName != nil { - status.Warning(p.ctx, "Cluster configurations have been deprecated and support for them will be removed in a future kluctl release.") + p.warnOnce.Do("cluster-config", func() { + status.Warning(p.ctx, "Cluster configurations have been deprecated and support for them will be removed in a future kluctl release.") + }) clusterConfig, err = types.LoadClusterConfig(p.ClustersDir, *clusterName) if err != nil { From 22be586f40e10538f96f82601da232faf327f7a6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 23 May 2022 10:51:44 +0200 Subject: [PATCH 0864/2916] build: Upgrade kustomize to 4.5.5 --- go.mod | 7 ++++--- go.sum | 11 ++++++----- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 8ba0c8880..81f4d83f7 100644 --- a/go.mod +++ b/go.mod @@ -48,8 +48,8 @@ require ( k8s.io/client-go v0.24.0-rc.1 k8s.io/klog/v2 v2.60.1 sigs.k8s.io/kind v0.12.0 - sigs.k8s.io/kustomize/api v0.11.4 - sigs.k8s.io/kustomize/kyaml v0.13.6 + sigs.k8s.io/kustomize/api v0.11.5 + sigs.k8s.io/kustomize/kyaml v0.13.7 sigs.k8s.io/structured-merge-diff/v4 v4.2.1 ) @@ -112,6 +112,7 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/btree v1.0.1 // indirect + github.com/google/gnostic v0.5.7-v3refs // indirect github.com/google/go-cmp v0.5.8 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect @@ -190,7 +191,7 @@ require ( k8s.io/apiserver v0.23.6 // indirect k8s.io/cli-runtime v0.24.0-rc.1 // indirect k8s.io/component-base v0.24.0-rc.1 // indirect - k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect + k8s.io/kube-openapi v0.0.0-20220401212409-b28bf2818661 // indirect k8s.io/kubectl v0.23.5 // indirect k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect oras.land/oras-go v1.1.1 // indirect diff --git a/go.sum b/go.sum index fe9ab20ed..f79fe67a1 100644 --- a/go.sum +++ b/go.sum @@ -2025,8 +2025,9 @@ k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= -k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 h1:E3J9oCLlaobFUqsjG9DfKbP2BmgwBL2p7pn0A3dG9W4= k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= +k8s.io/kube-openapi v0.0.0-20220401212409-b28bf2818661 h1:nqYOUleKLC/0P1zbU29F5q6aoezM6MOAVz+iyfQbZ5M= +k8s.io/kube-openapi v0.0.0-20220401212409-b28bf2818661/go.mod h1:daOouuuwd9JXpv1L7Y34iV3yf6nxzipkKMWWlqlvK9M= k8s.io/kubectl v0.23.5 h1:DmDULqCaF4qstj0Im143XmncvqWtJxHzK8IrW2BzlU0= k8s.io/kubectl v0.23.5/go.mod h1:lLgw7cVY8xbd7o637vOXPca/w6HC205KsPCRDYRCxwE= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= @@ -2053,13 +2054,13 @@ sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2/go.mod h1:B+TnT182UBxE84DiCz sigs.k8s.io/kind v0.12.0 h1:LFynXwQkH1MrWI8pM1FQty0oUwEKjU5EkMaVZaPld8E= sigs.k8s.io/kind v0.12.0/go.mod h1:EcgDSBVxz8Bvm19fx8xkioFrf9dC30fMJdOTXBSGNoM= sigs.k8s.io/kustomize/api v0.10.1/go.mod h1:2FigT1QN6xKdcnGS2Ppp1uIWrtWN28Ms8A3OZUZhwr8= -sigs.k8s.io/kustomize/api v0.11.4 h1:/0Mr3kfBBNcNPOW5Qwk/3eb8zkswCwnqQxxKtmrTkRo= -sigs.k8s.io/kustomize/api v0.11.4/go.mod h1:k+8RsqYbgpkIrJ4p9jcdPqe8DprLxFUUO0yNOq8C+xI= +sigs.k8s.io/kustomize/api v0.11.5 h1:vLDp++YAX7iy2y2CVPJNy9pk9CY8XaUKgHkjbVtnWag= +sigs.k8s.io/kustomize/api v0.11.5/go.mod h1:2UDpxS6AonWXow2ZbySd4AjUxmdXLeTlvGBC46uSiq8= sigs.k8s.io/kustomize/cmd/config v0.10.2/go.mod h1:K2aW7nXJ0AaT+VA/eO0/dzFLxmpFcTzudmAgDwPY1HQ= sigs.k8s.io/kustomize/kustomize/v4 v4.4.1/go.mod h1:qOKJMMz2mBP+vcS7vK+mNz4HBLjaQSWRY22EF6Tb7Io= sigs.k8s.io/kustomize/kyaml v0.13.0/go.mod h1:FTJxEZ86ScK184NpGSAQcfEqee0nul8oLCK30D47m4E= -sigs.k8s.io/kustomize/kyaml v0.13.6 h1:eF+wsn4J7GOAXlvajv6OknSunxpcOBQQqsnPxObtkGs= -sigs.k8s.io/kustomize/kyaml v0.13.6/go.mod h1:yHP031rn1QX1lr/Xd934Ri/xdVNG8BE2ECa78Ht/kEg= +sigs.k8s.io/kustomize/kyaml v0.13.7 h1:/EZ/nPaLUzeJKF/BuJ4QCuMVJWiEVoI8iftOHY3g3tk= +sigs.k8s.io/kustomize/kyaml v0.13.7/go.mod h1:6K+IUOuir3Y7nucPRAjw9yth04KSWBnP5pqUTGwj/qU= sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= From 3eb682a11f9bc81d1cfac8d86b9b2a9a888ed323 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 23 May 2022 11:01:11 +0200 Subject: [PATCH 0865/2916] build: Update all dependencies --- go.mod | 94 +++--- go.sum | 1027 +++++++++++++++++++++++--------------------------------- 2 files changed, 467 insertions(+), 654 deletions(-) diff --git a/go.mod b/go.mod index 81f4d83f7..f9447d54d 100644 --- a/go.mod +++ b/go.mod @@ -5,68 +5,60 @@ go 1.18 require ( github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e github.com/Masterminds/semver/v3 v3.1.1 - github.com/aws/aws-sdk-go v1.44.1 + github.com/aws/aws-sdk-go v1.44.19 github.com/bitnami-labs/sealed-secrets v0.17.5 github.com/cyphar/filepath-securejoin v0.2.3 github.com/docker/distribution v2.8.1+incompatible github.com/evanphx/json-patch v5.6.0+incompatible github.com/gammazero/workerpool v1.1.2 github.com/go-git/go-git/v5 v5.4.2 - github.com/go-playground/validator/v10 v10.10.1 + github.com/go-playground/validator/v10 v10.11.0 github.com/gobwas/glob v0.2.3 github.com/goccy/go-yaml v1.9.5 github.com/golang-jwt/jwt/v4 v4.4.1 - github.com/google/go-containerregistry v0.8.0 + github.com/google/go-containerregistry v0.9.0 github.com/hexops/gotextdiff v1.0.3 github.com/jinzhu/copier v0.3.5 github.com/kevinburke/ssh_config v1.2.0 - github.com/klauspost/compress v1.15.2 + github.com/klauspost/compress v1.15.4 github.com/mattn/go-isatty v0.0.14 github.com/mitchellh/go-ps v1.0.0 github.com/ohler55/ojg v1.14.0 github.com/pkg/errors v0.9.1 github.com/r3labs/diff/v2 v2.15.1 - github.com/rogpeppe/go-internal v1.8.0 + github.com/rogpeppe/go-internal v1.8.1 github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v1.4.0 github.com/spf13/pflag v1.0.5 - github.com/spf13/viper v1.10.0 + github.com/spf13/viper v1.11.0 github.com/stretchr/testify v1.7.1 github.com/vbauerster/mpb/v7 v7.4.1 github.com/whilp/git-urls v1.0.0 github.com/xanzy/ssh-agent v0.3.1 - golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 + golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898 + golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 + golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 golang.org/x/text v0.3.7 - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b - helm.sh/helm/v3 v3.8.2 - k8s.io/api v0.24.0-rc.1 - k8s.io/apiextensions-apiserver v0.23.6 - k8s.io/apimachinery v0.24.0-rc.1 - k8s.io/client-go v0.24.0-rc.1 + gopkg.in/yaml.v3 v3.0.0 + helm.sh/helm/v3 v3.9.0 + k8s.io/api v0.24.1-rc.0 + k8s.io/apiextensions-apiserver v0.24.0 + k8s.io/apimachinery v0.24.1-rc.0 + k8s.io/client-go v0.24.1-rc.0 k8s.io/klog/v2 v2.60.1 - sigs.k8s.io/kind v0.12.0 + sigs.k8s.io/kind v0.14.0 sigs.k8s.io/kustomize/api v0.11.5 sigs.k8s.io/kustomize/kyaml v0.13.7 sigs.k8s.io/structured-merge-diff/v4 v4.2.1 ) -replace ( - k8s.io/api => k8s.io/api v0.23.5 - k8s.io/apimachinery => k8s.io/apimachinery v0.23.5 - k8s.io/cli-runtime => k8s.io/cli-runtime v0.23.5 - k8s.io/client-go => k8s.io/client-go v0.23.5 - k8s.io/component-base => k8s.io/component-base v0.23.5 -) - require ( cloud.google.com/go/compute v1.6.1 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest v0.11.27 // indirect - github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect + github.com/Azure/go-autorest/autorest/adal v0.9.20 // indirect github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect @@ -74,9 +66,9 @@ require ( github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/sprig/v3 v3.2.2 // indirect - github.com/Masterminds/squirrel v1.5.2 // indirect + github.com/Masterminds/squirrel v1.5.3 // indirect github.com/Microsoft/go-winio v0.5.2 // indirect - github.com/ProtonMail/go-crypto v0.0.0-20220407094043-a94812496cf5 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20220517143526-88bb52951d5b // indirect github.com/VividCortex/ewma v1.2.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect github.com/acomagu/bufpipe v1.0.3 // indirect @@ -85,19 +77,20 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 // indirect - github.com/containerd/containerd v1.6.3 // indirect + github.com/containerd/containerd v1.6.4 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/docker/cli v20.10.14+incompatible // indirect - github.com/docker/docker v20.10.14+incompatible // indirect + github.com/docker/cli v20.10.16+incompatible // indirect + github.com/docker/docker v20.10.16+incompatible // indirect github.com/docker/docker-credential-helpers v0.6.4 // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/go-units v0.4.0 // indirect + github.com/emicklei/go-restful v2.9.5+incompatible // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect github.com/fatih/color v1.13.0 // indirect - github.com/fsnotify/fsnotify v1.5.1 // indirect + github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/gammazero/deque v0.1.1 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-git/gcfg v1.5.0 // indirect @@ -112,12 +105,11 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/btree v1.0.1 // indirect - github.com/google/gnostic v0.5.7-v3refs // indirect + github.com/google/gnostic v0.6.9 // indirect github.com/google/go-cmp v0.5.8 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.3.0 // indirect - github.com/googleapis/gnostic v0.5.5 // indirect github.com/gorilla/mux v1.8.0 // indirect github.com/gosuri/uitable v0.0.4 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect @@ -133,9 +125,9 @@ require ( github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/leodido/go-urn v1.2.1 // indirect - github.com/lib/pq v1.10.5 // indirect + github.com/lib/pq v1.10.6 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect - github.com/magiconair/properties v1.8.5 // indirect + github.com/magiconair/properties v1.8.6 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect @@ -143,7 +135,7 @@ require ( github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect - github.com/mitchellh/mapstructure v1.4.3 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moby/locker v1.0.1 // indirect github.com/moby/spdystream v0.2.0 // indirect @@ -152,12 +144,14 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/morikuni/aec v1.0.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect + github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 // indirect github.com/pelletier/go-toml v1.9.5 // indirect + github.com/pelletier/go-toml/v2 v2.0.1 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.12.1 // indirect + github.com/prometheus/client_golang v1.12.2 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.34.0 // indirect github.com/prometheus/procfs v0.7.3 // indirect @@ -166,8 +160,8 @@ require ( github.com/russross/blackfriday v1.6.0 // indirect github.com/sergi/go-diff v1.2.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect - github.com/spf13/afero v1.6.0 // indirect - github.com/spf13/cast v1.4.1 // indirect + github.com/spf13/afero v1.8.2 // indirect + github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/subosito/gotenv v1.2.0 // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect @@ -176,23 +170,23 @@ require ( github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xlab/treeprint v1.1.0 // indirect go.starlark.net v0.0.0-20220328144851-d1966c6b9fcd // indirect - golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect + golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 // indirect golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect - golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect + golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20220426171045-31bebdecfb46 // indirect - google.golang.org/grpc v1.46.0 // indirect + google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd // indirect + google.golang.org/grpc v1.46.2 // indirect google.golang.org/protobuf v1.28.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/ini.v1 v1.66.2 // indirect + gopkg.in/ini.v1 v1.66.4 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/apiserver v0.23.6 // indirect - k8s.io/cli-runtime v0.24.0-rc.1 // indirect - k8s.io/component-base v0.24.0-rc.1 // indirect - k8s.io/kube-openapi v0.0.0-20220401212409-b28bf2818661 // indirect - k8s.io/kubectl v0.23.5 // indirect + k8s.io/apiserver v0.24.0 // indirect + k8s.io/cli-runtime v0.24.0 // indirect + k8s.io/component-base v0.24.0 // indirect + k8s.io/kube-openapi v0.0.0-20220413171646-5e7f5fdc6da6 // indirect + k8s.io/kubectl v0.24.0 // indirect k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect oras.land/oras-go v1.1.1 // indirect sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect diff --git a/go.sum b/go.sum index f79fe67a1..851e2a68e 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,11 @@ -bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= -bazil.org/fuse v0.0.0-20200407214033-5883e5a4b512/go.mod h1:FbcW6z/2VytnFDhZfumh8Ss8zxHE6qpMP5sHTRe0EaM= +4d63.com/gochecknoglobals v0.1.0/go.mod h1:wfdC5ZjKSPr7CybKEcgJhUOgeAQW1+7WcyK8OvUilfo= +bitbucket.org/creachadair/shell v0.0.6/go.mod h1:8Qqi/cYk7vPnsOePHroKXDJYmb5x7ENhtiFtfZq8K+M= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= @@ -13,10 +14,12 @@ cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6 cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.60.0/go.mod h1:yw2G51M9IfRboUH61Us8GqCeF1PzPblB823Mn2q2eAU= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= @@ -27,7 +30,6 @@ cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aD cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= -cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM= cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= @@ -45,41 +47,39 @@ cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLq cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= -cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY= +cloud.google.com/go/firestore v1.6.0/go.mod h1:afJwI0vaXwAG54kI7A//lP/lSPDkQORQuMkv56TxEPU= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/pubsub v1.5.0/go.mod h1:ZEwJccE3z93Z2HWvstpri00jOg7oO4UZDtKhwDwqF0w= +cloud.google.com/go/spanner v1.7.0/go.mod h1:sd3K2gZ9Fd0vMPLXzeCrF6fq4i63Q7aTLW/lBIfBkIk= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +contrib.go.opencensus.io/exporter/stackdriver v0.13.4/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20210715213245-6c3934b029d8/go.mod h1:CzsSbkDixRphAF5hS6wbMKq0eI6ccJRb7/A0M6JBnwg= -github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go v56.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Antonboom/errname v0.1.5/go.mod h1:DugbBstvPFQbv/5uLcRRzfrNqKE9tVdVCqWCLp6Cifo= +github.com/Antonboom/nilnil v0.1.0/go.mod h1:PhHLvRPSghY5Y7mX4TW+BHZQYo1A8flE5H20D3IPZBo= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= -github.com/Azure/go-autorest/autorest v0.11.20/go.mod h1:o3tqFY+QR40VOlk+pV4d77mORO64jOXSgEnPQgLK6JY= github.com/Azure/go-autorest/autorest v0.11.27 h1:F3R3q42aWytozkV8ihzcgMO4OA4cuqr3bNlsEuF6//A= github.com/Azure/go-autorest/autorest v0.11.27/go.mod h1:7l8ybrIdUmGqZMTD0sRtAr8NvbHjfofbf8RSP2q7w7U= github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= -github.com/Azure/go-autorest/autorest/adal v0.9.15/go.mod h1:tGMin8I49Yij6AQ+rvV+Xa/zwxYQB5hmsd6DkfAx2+A= -github.com/Azure/go-autorest/autorest/adal v0.9.18 h1:kLnPsRjzZZUF3K5REu/Kc+qMQrvuza2bwSnNdhmzLfQ= github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= +github.com/Azure/go-autorest/autorest/adal v0.9.20 h1:gJ3E98kMpFB1MFqQCvA1yFab8vthOeD4VlFRQULxahg= +github.com/Azure/go-autorest/autorest/adal v0.9.20/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw= github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU= -github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= @@ -88,65 +88,45 @@ github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e h1:ZU22z/2YRFLyf/ github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= -github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= -github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/sprig v2.15.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= -github.com/Masterminds/squirrel v1.5.2 h1:UiOEi2ZX4RCSkpiNDQN5kro/XIBpSRk9iTqdIRPzUXE= -github.com/Masterminds/squirrel v1.5.2/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= -github.com/Masterminds/vcs v1.13.3/go.mod h1:TiE7xuEjl1N4j016moRd6vezp6e6Lz23gypeXfzXeW8= -github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= +github.com/Masterminds/squirrel v1.5.3 h1:YPpoceAcxuzIljlr5iWpNKaql7hLeG1KLSrhvdHpkZc= +github.com/Masterminds/squirrel v1.5.3/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= -github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= -github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= -github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= -github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= -github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= -github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= -github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= -github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00= -github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600= -github.com/Microsoft/hcsshim v0.8.20/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= -github.com/Microsoft/hcsshim v0.8.21/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= -github.com/Microsoft/hcsshim v0.8.23/go.mod h1:4zegtUJth7lAvFyc6cH2gGQ5B3OFQim01nnU2M8jKDg= github.com/Microsoft/hcsshim v0.9.2 h1:wB06W5aYFfUB3IvootYAY2WnOmIdgPGfqSI6tufQNnY= -github.com/Microsoft/hcsshim v0.9.2/go.mod h1:7pLA8lDk46WKDWlVsENo92gC0XFa8rbKfyFRBqxEbCc= -github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= -github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM= github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= -github.com/ProtonMail/go-crypto v0.0.0-20220407094043-a94812496cf5 h1:cSHEbLj0GZeHM1mWG84qEnGFojNEQ83W7cwaPRjcwXU= -github.com/ProtonMail/go-crypto v0.0.0-20220407094043-a94812496cf5/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= -github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/ProtonMail/go-crypto v0.0.0-20220517143526-88bb52951d5b h1:lcbBNuQhppsc7A5gjdHmdlqUqJfgGMylBdGyDs0j7G8= +github.com/ProtonMail/go-crypto v0.0.0-20220517143526-88bb52951d5b/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= -github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= +github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= @@ -160,60 +140,55 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= -github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= -github.com/alexflint/go-filemutex v1.1.0/go.mod h1:7P4iRhttt/nUvUOrYIhcpMzv2G6CY9UnI16Z+UJqRyk= +github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE= +github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= +github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210826220005-b48c857c3a0e/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= +github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= -github.com/aws/aws-sdk-go v1.34.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= -github.com/aws/aws-sdk-go v1.44.1 h1:w34ZmPT6K4NTd7Yap1P7SLXPTii0ABmBz3KEh4KJdKc= -github.com/aws/aws-sdk-go v1.44.1/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/ashanbrown/forbidigo v1.2.0/go.mod h1:vVW7PEdqEFqapJe95xHkTfB1+XvZXBFg8t0sG2FIxmI= +github.com/ashanbrown/makezero v0.0.0-20210520155254-b6261585ddde/go.mod h1:oG9Dnez7/ESBqc4EdrdNlryeo7d0KcW1ftXHm7nU/UU= +github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.36.30/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= +github.com/aws/aws-sdk-go v1.44.19 h1:dhI6p4l6kisnA7gBAM8sP5YIk0bZ9HNAj7yrK7kcfdU= +github.com/aws/aws-sdk-go v1.44.19/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= github.com/bitnami-labs/sealed-secrets v0.17.5 h1:v5ENZRSrgog3GnFr8fWfVtrUTPlZlNlbsjaro9mn6YY= github.com/bitnami-labs/sealed-secrets v0.17.5/go.mod h1:ZgGUqKixr/SRpsG8LVXnuneyZG7DOX/+eCRvXi8SVjo= -github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= -github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= -github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= -github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= -github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= +github.com/bkielbasa/cyclop v1.2.0/go.mod h1:qOI0yy6A7dYC4Zgsa72Ppm9kONl0RoIlPbzot9mhmeI= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/blizzy78/varnamelen v0.3.0/go.mod h1:hbwRdBvoBqxk34XyQ6HA0UH3G0/1TKuv5AC4eaBT0Ec= +github.com/bombsimon/wsl/v3 v3.3.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc= +github.com/breml/bidichk v0.1.1/go.mod h1:zbfeitpevDUGI7V91Uzzuwrn4Vls8MoBMrwtt78jmso= github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= -github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= -github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd h1:rFt+Y/IK1aEZkEHchZRSq9OQbsSzIT/OrI8YFFmRIng= -github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembjv71DPz3uX/V/6MMlSyD9JBQ6kQ= -github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o= -github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= -github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= -github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/butuzov/ireturn v0.1.1/go.mod h1:Wh6Zl3IMtTpaIKbmwzqi6olnM9ptYQxxVacMsOEFPoc= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= @@ -222,20 +197,11 @@ github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cb github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 h1:7aWHqerlJ41y6FOsEUvknqgXnGmJyJSbjhAWq5pO4F8= github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= -github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= -github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= -github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= +github.com/charithe/durationcheck v0.0.9/go.mod h1:SSbRIBVfMjCi/kEB6K65XEA83D6prSM8ap1UCpNKtgg= +github.com/chavacava/garif v0.0.0-20210405164556-e8a0a408d6af/go.mod h1:Qjyv4H3//PWVzTeCezG2b9IRn6myJxJSr4TD/xo6ojU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= -github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc= -github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= -github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= -github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= -github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= -github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= -github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -246,129 +212,28 @@ github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= -github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= -github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= -github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= -github.com/containerd/aufs v1.0.0/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= -github.com/containerd/btrfs v0.0.0-20201111183144-404b9149801e/go.mod h1:jg2QkJcsabfHugurUvvPhS3E08Oxiuh5W/g1ybB4e0E= -github.com/containerd/btrfs v0.0.0-20210316141732-918d888fb676/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= -github.com/containerd/btrfs v1.0.0/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= -github.com/containerd/cgroups v0.0.0-20190717030353-c4b9ac5c7601/go.mod h1:X9rLEHIqSf/wfK8NsPqxJmeZgW4pcfzdXITDrUSJ6uI= -github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= -github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM= -github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= -github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= -github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= -github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU= github.com/containerd/cgroups v1.0.3 h1:ADZftAkglvCiD44c77s5YmMqaP2pzVCFZvBmAlBdAP4= -github.com/containerd/cgroups v1.0.3/go.mod h1:/ofk34relqNjSGyqPrmEULrO4Sc8LJhvJmWbUCUKqj8= -github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= -github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= -github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= -github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= -github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= -github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= -github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.1-0.20191213020239-082f7e3aed57/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.0-beta.2.0.20200729163537-40b22ef07410/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.9/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ= -github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU= -github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= -github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s= -github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= -github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c= -github.com/containerd/containerd v1.5.8/go.mod h1:YdFSv5bTFLpG2HIYmfqDpSYYTDX+mc5qtSuYx1YUb/s= -github.com/containerd/containerd v1.6.1/go.mod h1:1nJz5xCZPusx6jJU8Frfct988y0NpumIq9ODB0kLtoE= -github.com/containerd/containerd v1.6.3 h1:JfgUEIAH07xDWk6kqz0P3ArZt+KJ9YeihSC9uyFtSKg= -github.com/containerd/containerd v1.6.3/go.mod h1:gCVGrYRYFm2E8GmuUIbj/NGD7DLZQLzSJQazjVKDOig= -github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo= -github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y= -github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ= -github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= -github.com/containerd/continuity v0.2.2/go.mod h1:pWygW9u7LtS1o4N/Tn0FoCFDIXZ7rxcMX7HX1Dmibvk= -github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= -github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= -github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= -github.com/containerd/fifo v0.0.0-20201026212402-0724c46b320c/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= -github.com/containerd/fifo v0.0.0-20210316144830-115abcc95a1d/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= -github.com/containerd/fifo v1.0.0/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= -github.com/containerd/go-cni v1.0.1/go.mod h1:+vUpYxKvAF72G9i1WoDOiPGRtQpqsNW/ZHtSlv++smU= -github.com/containerd/go-cni v1.0.2/go.mod h1:nrNABBHzu0ZwCug9Ije8hL2xBCYh/pjfMb1aZGrrohk= -github.com/containerd/go-cni v1.1.0/go.mod h1:Rflh2EJ/++BA2/vY5ao3K6WJRR/bZKsX123aPk+kUtA= -github.com/containerd/go-cni v1.1.3/go.mod h1:Rflh2EJ/++BA2/vY5ao3K6WJRR/bZKsX123aPk+kUtA= -github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= -github.com/containerd/go-runc v0.0.0-20190911050354-e029b79d8cda/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= -github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g= -github.com/containerd/go-runc v0.0.0-20201020171139-16b287bc67d0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= -github.com/containerd/go-runc v1.0.0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= -github.com/containerd/imgcrypt v1.0.1/go.mod h1:mdd8cEPW7TPgNG4FpuP3sGBiQ7Yi/zak9TYCG3juvb0= -github.com/containerd/imgcrypt v1.0.4-0.20210301171431-0ae5c75f59ba/go.mod h1:6TNsg0ctmizkrOgXRNQjAPFWpMYRWuiB6dSF4Pfa5SA= -github.com/containerd/imgcrypt v1.1.1-0.20210312161619-7ed62a527887/go.mod h1:5AZJNI6sLHJljKuI9IHnw1pWqo/F0nGDOuR9zgTs7ow= -github.com/containerd/imgcrypt v1.1.1/go.mod h1:xpLnwiQmEUJPvQoAapeb2SNCxz7Xr6PJrXQb0Dpc4ms= -github.com/containerd/imgcrypt v1.1.3/go.mod h1:/TPA1GIDXMzbj01yd8pIbQiLdQxed5ue1wb8bP7PQu4= -github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c= -github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= -github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= -github.com/containerd/stargz-snapshotter/estargz v0.4.1/go.mod h1:x7Q9dg9QYb4+ELgxmo4gBUeJB0tl5dqH1Sdz0nJU1QM= -github.com/containerd/stargz-snapshotter/estargz v0.10.1 h1:hd1EoVjI2Ax8Cr64tdYqnJ4i4pZU49FkEf5kU8KxQng= -github.com/containerd/stargz-snapshotter/estargz v0.10.1/go.mod h1:aE5PCyhFMwR8sbrErO5eM2GcvkyXTTJremG883D4qF0= -github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= -github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= -github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8= -github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= -github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= -github.com/containerd/ttrpc v1.1.0/go.mod h1:XX4ZTnoOId4HklF4edwc4DcqskFZuvXB1Evzy5KFQpQ= -github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= -github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk= -github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg= -github.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcDZXTn6oPz9s= -github.com/containerd/zfs v0.0.0-20200918131355-0a33824f23a2/go.mod h1:8IgZOBdv8fAgXddBT4dBXJPtxyRsejFIpXoklgxgEjw= -github.com/containerd/zfs v0.0.0-20210301145711-11e8f1707f62/go.mod h1:A9zfAbMlQwE+/is6hi0Xw8ktpL+6glmqZYtevJgaB8Y= -github.com/containerd/zfs v0.0.0-20210315114300-dde8f0fda960/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= -github.com/containerd/zfs v0.0.0-20210324211415-d5c4544f0433/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= -github.com/containerd/zfs v1.0.0/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= -github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= -github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= -github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= -github.com/containernetworking/cni v1.0.1/go.mod h1:AKuhXbN5EzmD4yTNtfSsX3tPcmtrBI6QcRV0NiNt15Y= -github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM= -github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8= -github.com/containernetworking/plugins v1.0.1/go.mod h1:QHCfGpaTwYTbbH+nZXKVTxNBDZcxSOplJT5ico8/FLE= -github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc= -github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4= -github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= -github.com/containers/ocicrypt v1.1.2/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= +github.com/containerd/containerd v1.6.4 h1:SEDZBp10mhCp+hkO3Njz/YhGrI7ah3edNcUlRdUPOgg= +github.com/containerd/containerd v1.6.4/go.mod h1:oWOqbuJUZmOVafhA0lj2NAXbiO1u7F0K5l1bUgdyo94= +github.com/containerd/stargz-snapshotter/estargz v0.11.4 h1:LjrYUZpyOhiSaU7hHrdR82/RBoxfGWSaC0VeSSMXqnk= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= -github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= -github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= -github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= +github.com/coreos/go-systemd v0.0.0-20190620071333-e64a0ec8b42a/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= @@ -376,62 +241,43 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI= github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= -github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= -github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= -github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= -github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I= +github.com/daixiang0/gci v0.2.9/go.mod h1:+4dZ7TISfSmqfAGv59ePaHfNzgGtIkHAhhdKggP1JAc= github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg= +github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= +github.com/denis-tingajkin/go-header v0.4.2/go.mod h1:eLRHAVXzE5atsKAnNRDB90WHCFFnBUn4RN0nRcs1LJA= github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= -github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= -github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/distribution/distribution/v3 v3.0.0-20211118083504-a29a3c99a684 h1:DBZ2sN7CK6dgvHVpQsQj4sRMCbWTmd17l+5SUCjnQSY= -github.com/distribution/distribution/v3 v3.0.0-20211118083504-a29a3c99a684/go.mod h1:UfCu3YXJJCI+IdnqGgYP82dk2+Joxmv+mUTVBES6wac= -github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= -github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/cli v20.10.11+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/cli v20.10.12+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/cli v20.10.14+incompatible h1:dSBKJOVesDgHo7rbxlYjYsXe7gPzrTT+/cKQgpDAazg= -github.com/docker/cli v20.10.14+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= -github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/cli v20.10.16+incompatible h1:aLQ8XowgKpR3/IysPj8qZQJBVQ+Qws61icFuZl6iKYs= +github.com/docker/cli v20.10.16+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v20.10.11+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v20.10.12+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v20.10.14+incompatible h1:+T9/PRYWNDo5SZl5qS1r9Mo/0Q8AwxKKPtu9S1yxM0w= -github.com/docker/docker v20.10.14+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= +github.com/docker/docker v20.10.16+incompatible h1:2Db6ZR/+FUR3hqPMwnogOPHFn405crbpxvWzKovETOQ= +github.com/docker/docker v20.10.16+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.6.4 h1:axCks+yV+2MR3/kZhAmy07yC56WZ2Pwu/fKWtKuZB0o= github.com/docker/docker-credential-helpers v0.6.4/go.mod h1:ofX3UI0Gz1TteYBjtgs07O36Pyasyp66D2uKT7H8W1c= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= -github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= -github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= -github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= @@ -444,11 +290,11 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= -github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= +github.com/envoyproxy/protoc-gen-validate v0.0.14/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= -github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/esimonov/ifshort v1.0.3/go.mod h1:yZqNJUrNn20K8Q9n2CrjTKYyVEmX209Hgu+M1LBpeZE= +github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY= github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= @@ -464,30 +310,33 @@ github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= +github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= -github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= +github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/fullstorydev/grpcurl v1.6.0/go.mod h1:ZQ+ayqbKMJNhzLmbpCiurTVlaK2M/3nqZCxaQ2Ze/sM= github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= +github.com/fzipp/gocyclo v0.3.1/go.mod h1:DJHO6AUmbdqj2ET4Z9iArSuwWgYDRryYt2wASxc7x3E= github.com/gammazero/deque v0.1.0/go.mod h1:KQw7vFau1hHuM8xmI9RbgKFbAsQFWmBpqQ2KenFLk6M= github.com/gammazero/deque v0.1.1 h1:xRVkDuSvDmFuMGf3IquHuRc2jlL0+v/WpFCWaauzwbE= github.com/gammazero/deque v0.1.1/go.mod h1:KQw7vFau1hHuM8xmI9RbgKFbAsQFWmBpqQ2KenFLk6M= github.com/gammazero/workerpool v1.1.2 h1:vuioDQbgrz4HoaCi2q1HLlOXdpbap5AET7xu5/qj87g= github.com/gammazero/workerpool v1.1.2/go.mod h1:UelbXcO0zCIGFcufcirHhq2/xtLXJdQ29qZNlXG9OjQ= -github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= -github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/go-critic/go-critic v0.6.1/go.mod h1:SdNCfU0yF3UBjtaZGw6586/WocupMOJuiqgom5DsQxM= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= @@ -505,7 +354,6 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gorp/gorp/v3 v3.0.2 h1:ULqJXIekoqMx29FI5ekXXFoH1dT2Vc8UhnRzBg+Emz4= github.com/go-gorp/gorp/v3 v3.0.2/go.mod h1:BJ3q1ejpV8cVALtcXvXaXyTOlMmJhWDxTmncaR6rwBY= -github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= @@ -516,30 +364,19 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.2.0/go.mod h1:Qa4Bsj2Vb+FAVeAKsLD8RLQ+YRJB8YDmOAKxaBQf7Ro= -github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= -github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= -github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= -github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= -github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= -github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= -github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU= @@ -553,41 +390,43 @@ github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+ github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= -github.com/go-playground/validator/v10 v10.10.1 h1:uA0+amWMiglNZKZ9FJRKUAe9U3RX91eVn1JYXMWt7ig= -github.com/go-playground/validator/v10 v10.10.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= +github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw= +github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= +github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/gobuffalo/logger v1.0.3/go.mod h1:SoeejUwldiS7ZsyCBphOGURmWdwUFXs0J7TCjEhjKxM= +github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4= +github.com/go-toolsmith/astcopy v1.0.0/go.mod h1:vrgyG+5Bxrnz4MZWPF+pI4R8h3qKRjjyvV/DSez4WVQ= +github.com/go-toolsmith/astequal v1.0.0/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= +github.com/go-toolsmith/astequal v1.0.1/go.mod h1:4oGA3EZXTVItV/ipGiOx7NWkY5veFfcsOJVS2YxltLw= +github.com/go-toolsmith/astfmt v1.0.0/go.mod h1:cnWmsOAuq4jJY6Ct5YWlVLmcmLMn1JUPuQIHCY7CJDw= +github.com/go-toolsmith/astinfo v0.0.0-20180906194353-9809ff7efb21/go.mod h1:dDStQCHtmZpYOmjRP/8gHHnCCch3Zz3oEgCdZVdtweU= +github.com/go-toolsmith/astp v1.0.0/go.mod h1:RSyrtpVlfTFGDYRbrjyWP1pYu//tSFcvdYrA8meBmLI= +github.com/go-toolsmith/pkgload v1.0.0/go.mod h1:5eFArkbO80v7Z0kdngIxsRXRMTaX4Ilcwuh3clNrQJc= +github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= +github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU= +github.com/go-toolsmith/typep v1.0.2/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU= +github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= github.com/gobuffalo/logger v1.0.6 h1:nnZNpxYo0zx+Aj9RfMPBm+x9zAU2OayFh/xrAWi34HU= github.com/gobuffalo/logger v1.0.6/go.mod h1:J31TBEHR1QLV2683OXTAItYIg8pv2JMHnF/quuAbMjs= -github.com/gobuffalo/packd v1.0.0/go.mod h1:6VTc4htmJRFB7u1m/4LeMTWjFoYrUiBkU9Fdec9hrhI= github.com/gobuffalo/packd v1.0.1 h1:U2wXfRr4E9DH8IdsDLlRFwTZTK7hLfq9qT/QHXGVe/0= github.com/gobuffalo/packd v1.0.1/go.mod h1:PP2POP3p3RXGz7Jh6eYEf93S7vA2za6xM7QT85L4+VY= -github.com/gobuffalo/packr/v2 v2.8.1/go.mod h1:c/PLlOuTU+p3SybaJATW3H6lX/iK7xEz5OeMf+NnJpg= github.com/gobuffalo/packr/v2 v2.8.3 h1:xE1yzvnO56cUC0sTpKR3DIbxZgB54AftTFMhB2XEWlY= github.com/gobuffalo/packr/v2 v2.8.3/go.mod h1:0SahksCVcx4IMnigTjiFuyldmTrdTctXsOdiU5KwbKc= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/goccy/go-yaml v1.9.5 h1:Eh/+3uk9kLxG4koCX6lRMAPS1OaMSAi+FJcya0INdB0= github.com/goccy/go-yaml v1.9.5/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA= -github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= -github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= -github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= -github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godror/godror v0.24.2/go.mod h1:wZv/9vPiUib6tkoDl+AZ/QLf5YZgMravZ7jxH2eQWAE= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= -github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= -github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -615,6 +454,7 @@ github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -634,17 +474,29 @@ github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4= +github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= +github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8= +github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU= +github.com/golangci/golangci-lint v1.43.0/go.mod h1:VIFlUqidx5ggxDfQagdvd9E67UjMXtTHBkBQ7sHoC5Q= +github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg= +github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o= +github.com/golangci/misspell v0.3.5/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA= +github.com/golangci/revgrep v0.0.0-20210930125155-c22e5001d4f2/go.mod h1:LK+zW4MpyytAWQRz0M4xnzEk50lSvqDQKfx304apFkY= +github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ= github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= -github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= -github.com/google/cel-go v0.9.0/go.mod h1:U7ayypeSkw23szu4GaQTPJGx66c20mx8JklMSxrmI1w= +github.com/google/cel-go v0.10.1/go.mod h1:U7ayypeSkw23szu4GaQTPJGx66c20mx8JklMSxrmI1w= github.com/google/cel-spec v0.6.0/go.mod h1:Nwjgxy5CbjlPrtCWjeDjUyKMl8w41YBYGjsyDdqk0xA= -github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= +github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= +github.com/google/certificate-transparency-go v1.1.1/go.mod h1:FDKqPvSXawb2ecErVRrD+nfy23RCzyl7eqVCEmlT1Zs= github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= +github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0= +github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -660,9 +512,8 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-containerregistry v0.5.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0= -github.com/google/go-containerregistry v0.8.0 h1:mtR24eN6rapCN+shds82qFEIWWmg64NPMuyCNT7/Ogc= -github.com/google/go-containerregistry v0.8.0/go.mod h1:wW5v71NHGnQyb4k+gSshjxidrC7lN33MdWEn+Mz9TsI= +github.com/google/go-containerregistry v0.9.0 h1:5Ths7RjxyFV0huKChQTgY6fLzvHhZMpLTFNja8U0/0w= +github.com/google/go-containerregistry v0.9.0/go.mod h1:9eq4BnSufyT1kHNffX+vSXVonaJ7yaIOulrKZejMxnQ= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= @@ -677,9 +528,11 @@ github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200507031123-427632fa3b1c/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= @@ -688,10 +541,11 @@ github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLe github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/google/trillian v1.3.11/go.mod h1:0tPraVHrSDkA3BO6vKX67zgLXs6SsOAbHEivX+9mPgw= +github.com/google/uuid v0.0.0-20161128191214-064e2069ce9c/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= @@ -700,21 +554,31 @@ github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pf github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= -github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= -github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw= -github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= +github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= +github.com/gordonklaus/ineffassign v0.0.0-20210225214923-2e10b2664254/go.mod h1:M9mZEtGIsR1oDaZagNPNG9iq9n2HrhZ17dsXk73V3Lw= +github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75/go.mod h1:g2644b03hfBX9Ov0ZBDgXXens4rxSxmqFBbhvKv2yVA= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= -github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= -github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= +github.com/gostaticanalysis/analysisutil v0.0.3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= +github.com/gostaticanalysis/analysisutil v0.1.0/go.mod h1:dMhHRU9KTiDcuLGdy87/2gTR8WruwYZrKdRq9m1O6uw= +github.com/gostaticanalysis/analysisutil v0.4.1/go.mod h1:18U/DLpRgIUd459wGxVHE0fRgmo1UgHDcbw7F5idXu0= +github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc= +github.com/gostaticanalysis/comment v1.3.0/go.mod h1:xMicKDx7XRXYdVwY9f9wQpDJVnqWxw9wCauCMKp+IBI= +github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado= +github.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM= +github.com/gostaticanalysis/forcetypeassert v0.0.0-20200621232751-01d4955beaa5/go.mod h1:qZEedyP/sY1lTGV1uJ3VhWZ2mqag3IkWsDHVbplHXak= +github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW0HU0GPE3+5PWN4A= +github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= +github.com/gostaticanalysis/testutil v0.4.0/go.mod h1:bLIoPefWXrRi/ssLFWX1dx7Repi5x3CuviD3dgAZaBU= github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= @@ -722,37 +586,32 @@ github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJr github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.12.1/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= +github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= -github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -762,58 +621,55 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= -github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= -github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= -github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo= +github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.4/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/intel/goresctrl v0.2.0/go.mod h1:+CZdzouYFn5EsxgqAQTEzMfwKwuc0fVdMrT9FCCAVRQ= -github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= -github.com/j-keck/arping v1.0.2/go.mod h1:aJbELhR92bSk7tp79AWM/ftfc90EfEi2bQJrbBFOsPw= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= +github.com/jgautheron/goconst v1.5.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= +github.com/jhump/protoreflect v1.6.1/go.mod h1:RZQ/lnuN+zqeRVpQigTwO6o0AJUkxbnSnpuG7toUTG4= +github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg= github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= -github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= +github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/jmoiron/sqlx v1.3.4/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= +github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= -github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jonboulle/clockwork v0.2.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/josharian/txtarfs v0.0.0-20210218200122-0702f000015a/go.mod h1:izVPOvVRsHiKkeGCT6tYBNWyDVuzj9wAaBb5R9qamfw= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -821,9 +677,11 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/juju/ratelimit v1.0.1/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/karrick/godirwalk v1.15.8/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= +github.com/julz/importas v0.0.0-20210419104244-841f0c0fe66d/go.mod h1:oSFU2R4XK/P7kNBrnL/FEQlDGN1/6WoxXEjSSXO0DV0= +github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw= github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= @@ -832,12 +690,12 @@ github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/errcheck v1.6.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.15.2 h1:3WH+AG7s2+T8o3nrM/8u2rdqUEcQhmga7smjrT41nAw= -github.com/klauspost/compress v1.15.2/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= +github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.15.4 h1:1kn4/7MepF/CHmYub99/nNX8az0IJjfSOU/jbnTVfqQ= +github.com/klauspost/compress v1.15.4/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -850,52 +708,64 @@ github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kulti/thelper v0.4.0/go.mod h1:vMu2Cizjy/grP+jmsvOFDx1kYP6+PD1lqg4Yu5exl2U= +github.com/kunwardeep/paralleltest v1.0.3/go.mod h1:vLydzomDFpk7yu5UX02RmP0H8QfRPOV/oFhWN85Mjb4= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/kyoh86/exportloopref v0.1.8/go.mod h1:1tUcJeiioIs7VWe5gcOObrux3lb66+sBqGZrRkMwPgg= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= +github.com/ldez/gomoddirectives v0.2.2/go.mod h1:cpgBogWITnCfRq2qGoDkKMEVSaarhdBr6g8G04uz6d0= +github.com/ldez/tagliatelle v0.2.0/go.mod h1:8s6WJQwEYHbKZDsp/LjArytKOG8qaMrKQQ3mFukHs88= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/letsencrypt/pkcs11key/v4 v4.0.0/go.mod h1:EFUvBDay26dErnNb70Nd0/VW3tJiIbETBPTl9ATXQag= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.10.5 h1:J+gdV2cUmX7ZqL2B0lFcW0m+egaHC2V3lpO8nWxyYiQ= -github.com/lib/pq v1.10.5/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs= +github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= -github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo= github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= -github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= +github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= +github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/maratori/testpackage v1.0.1/go.mod h1:ddKdw+XG0Phzhx8BFDTKgpWP4i7MpApTE5fXSKAqwDU= github.com/markbates/errx v1.1.0 h1:QDFeR+UP95dO12JgW+tgi2UVfo0V8YBHiUIOaeBPiEI= github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc= github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY= github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI= github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= -github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= -github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= +github.com/matoous/godox v0.0.0-20210227103229-6504466cf951/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= +github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= @@ -908,25 +778,28 @@ github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= -github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= -github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= +github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= +github.com/mbilski/exhaustivestruct v1.2.0/go.mod h1:OeTBVxQWoEmB2J2JCHmXWPJ0aksxSUOUy+nvtVEfzXc= +github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517/go.mod h1:KQ7+USdGKfpPjXk4Ga+5XxQM4Lm4e3gAogrreFAYpOg= +github.com/mgechev/revive v1.1.2/go.mod h1:bnXsMr+ZTH09V5rssEI+jHAZ4z+ZdyhgO/zsy3EhK+0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= -github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= +github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= -github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= github.com/mitchellh/cli v1.1.2/go.mod h1:6iaV0fGdElS6dPBx0EApTxHrcWvmJphyh2n8YBLPPZ4= @@ -947,25 +820,18 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= -github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= +github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= -github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= -github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= github.com/moby/sys/mountinfo v0.5.0 h1:2Ks8/r6lopsxWi9m58nlwjaeSzUX9iiL1vj5qB/9ObI= -github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= -github.com/moby/sys/signal v0.6.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn88Kg8Tg= -github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ= -github.com/moby/sys/symlink v0.2.0/go.mod h1:7uZVF2dqJjG/NsClqul95CqKOBRQyYSNnJ6BMgR/gFs= -github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= -github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A= github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc= github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -975,18 +841,28 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= +github.com/moricho/tparallel v0.2.1/go.mod h1:fXEIZxG2vdfl0ZF8b42f5a78EhjjD5mX8qUplsoSU4k= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= +github.com/mozilla/scribe v0.0.0-20180711195314-fb71baf557c1/go.mod h1:FIczTrinKo8VaLxe6PWTPEXRXDIHz2QAwiaBaP5/4a8= +github.com/mozilla/tls-observatory v0.0.0-20210609171429-7bc42856d2e5/go.mod h1:FUqVoUPHSEdDR0MnFM3Dh8AU0pZHLXUD127SAJGER/s= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-proto-validators v0.0.0-20180403085117-0950a7990007/go.mod h1:m2XC9Qq0AlmmVksL6FktJCdTYyLk7V3fKyp0sl1yWQo= +github.com/mwitkow/go-proto-validators v0.2.0/go.mod h1:ZfA1hW+UH/2ZHOWvQ3HnQaU0DtnpXu850MZiy+YUgcc= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= +github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= +github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nishanths/exhaustive v0.2.3/go.mod h1:bhIX678Nx8inLM9PbpvK1yv6oGtoP8BfaIeMzgBNKvc= +github.com/nishanths/predeclared v0.0.0-20190419143655-18a43bb90ffc/go.mod h1:62PewwiQTlm/7Rj+cxVYqZvDIUc+JjZq6GHAC1fsObQ= +github.com/nishanths/predeclared v0.2.1/go.mod h1:HvkGJcA3naj4lOwnFXFDkFxVtSqQMB9sbB1usJ+xjQE= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= @@ -994,129 +870,103 @@ github.com/ohler55/ojg v1.14.0 h1:DyHomsCwofNswmKj7BLMdx51xnKbXxgIo1rVWCaBcNk= github.com/ohler55/ojg v1.14.0/go.mod h1:3+GH+0PggMKocQtbZCrFifal3yRpHiBT4QUkxFJI6e8= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ= github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= -github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= +github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= -github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/go-digest v1.0.0-rc1.0.20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/image-spec v1.0.2-0.20211117181255-693428a734f5/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec= -github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0= -github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= -github.com/opencontainers/runc v1.1.0/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= -github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= -github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= -github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= -github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= -github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= +github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 h1:+czc/J8SlhPKLOtVLMQc+xDCFBT73ZStMsRhSsUhsSg= +github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198/go.mod h1:j4h1pJW6ZcJTgMZWP3+7RlG3zTaP02aDZ/Qw0sppK7Q= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= +github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= +github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= +github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= +github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU= +github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw= github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc= -github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/polyfloyd/go-errorlint v0.0.0-20210722154253-910bb7978349/go.mod h1:wi9BfjxjF/bwiZ701TzmfKu6UKC357IOAtNr0Td0Lvw= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/poy/onpar v0.0.0-20190519213022-ee068f8ea4d1 h1:oL4IBbcqwhhNWh31bjOX8C/OCy0zs9906d/VUru+bqg= github.com/poy/onpar v0.0.0-20190519213022-ee068f8ea4d1/go.mod h1:nSbFQvMj97ZyhFRSJYtut+msi4sOY6zJDGCdSc+/rZU= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= -github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= -github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_golang v1.12.2 h1:51L9cDoUHVrXx4zWYlcLQIZ+d+VXHgqnYKkIuq4g/34= +github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.28.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.34.0 h1:RBmGO9d/FVjqHT0yUGQwBJhkwKV+wPCn7KGpvfab0uE= github.com/prometheus/common v0.34.0/go.mod h1:gB3sOl7P0TvJabZpLY5uQMpUqRCPPCyRLCZYc7JZTNE= -github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/pseudomuto/protoc-gen-doc v1.3.2/go.mod h1:y5+P6n3iGrbKG+9O04V5ld71in3v/bX88wUwgt+U8EA= +github.com/pseudomuto/protokit v0.2.0/go.mod h1:2PdH30hxVHsup8KpBTOXTBeMVhJZVio3Q8ViKSAXT0Q= +github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI= +github.com/quasilyte/go-ruleguard v0.3.1-0.20210203134552-1b5a410e1cc8/go.mod h1:KsAh3x0e7Fkpgs+Q9pNLS5XpFSvYCEVl5gP9Pp1xp30= +github.com/quasilyte/go-ruleguard v0.3.13/go.mod h1:Ul8wwdqR6kBVOCt2dipDBkE+T6vAV/iixkrKuRTN1oQ= +github.com/quasilyte/go-ruleguard/dsl v0.3.0/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= +github.com/quasilyte/go-ruleguard/dsl v0.3.10/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= +github.com/quasilyte/go-ruleguard/rules v0.0.0-20201231183845-9e62ed36efe1/go.mod h1:7JTjp89EGyU1d6XfBiXihJNG37wB2VRkd125Q1u7Plc= +github.com/quasilyte/go-ruleguard/rules v0.0.0-20210428214800-545e0d2e0bf7/go.mod h1:4cgAphtvu7Ftv7vOT2ZOYhC6CvBxZixcasr8qIOTA50= +github.com/quasilyte/regex/syntax v0.0.0-20200407221936-30656e2c4a95/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= github.com/r3labs/diff/v2 v2.15.1 h1:EOrVqPUzi+njlumoqJwiS/TgGgmZo83619FNDB9xQUg= github.com/r3labs/diff/v2 v2.15.1/go.mod h1:I8noH9Fc2fjSaMxqF3G2lhDdC0b+JXCfyx85tWFM9kc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= @@ -1124,11 +974,12 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.6.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= -github.com/rubenv/sql-migrate v0.0.0-20210614095031-55d5740dbbcc/go.mod h1:HFLT6i9iR4QBOF5rdCyjddC9t59ArqWJV2xx+jwcCMo= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= +github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rubenv/sql-migrate v1.1.1 h1:haR5Hn8hbW9/SpAICrXoZqXnywS7Q5WijwkQENPeNWY= github.com/rubenv/sql-migrate v1.1.1/go.mod h1:/7TZymwxN8VWumcIxw1jjHEcR1djpdkMHQPT4FWdnbQ= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= @@ -1136,25 +987,24 @@ github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3V github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryancurrah/gomodguard v1.2.3/go.mod h1:rYbA/4Tg5c54mV1sv4sQTP5WOPBcoLtnBZ7/TEhXAbg= +github.com/ryanrolds/sqlclosecheck v0.3.0/go.mod h1:1gREqxyTGR3lVtpngyFo3hZAgk0KCtEdgEkHwDbigdA= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= -github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= -github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= -github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= +github.com/sagikazarmark/crypt v0.1.0/go.mod h1:B/mN0msZuINBtQ1zZLEQcegFJJf9vnYIR88KRMEuODE= +github.com/sanposhiho/wastedassign/v2 v2.0.6/go.mod h1:KyZ0MWTwxxBmfwn33zh3k1dmsbF2ud9pAAGfoLfjhtI= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= -github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= +github.com/securego/gosec/v2 v2.9.1/go.mod h1:oDcDLcatOJxkCGaCaq8lua1jTnYf6Sou4wdiJ1n4iHc= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= +github.com/shirou/gopsutil/v3 v3.21.10/go.mod h1:t75NhzCZ/dYyPQjyQmrAYP6c8+LCdFANeBMdLPCNnew= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= -github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -1162,52 +1012,52 @@ github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrf github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sivchari/tenv v1.4.7/go.mod h1:5nF+bITvkebQVanjU6IuMbvIot/7ReNsUV7I5NbprB0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= +github.com/sonatard/noctx v0.0.1/go.mod h1:9D2D/EoULe8Yy2joDHJj7bv3sZoq9AaSb8B4lqBjiZI= +github.com/sourcegraph/go-diff v0.6.1/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= -github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= +github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= +github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= -github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= -github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4= github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= -github.com/spf13/viper v1.10.0 h1:mXH0UwHS4D2HwWZa75im4xIQynLfblmWV7qcWpfv0yk= -github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= -github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= +github.com/spf13/viper v1.9.0/go.mod h1:+i6ajR7OX2XaiBkrcZJFK21htRk7eDeLg7+O6bhUPP4= +github.com/spf13/viper v1.11.0 h1:7OX/1FS6n7jHD1zGrZTM7WtY13ZELRyosK4k93oPr44= +github.com/spf13/viper v1.11.0/go.mod h1:djo0X/bA5+tYVoCn+C7cAYJGcVn/qYLFTG8gdUsX7Zk= +github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= -github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v0.0.0-20170130113145-4d4bfba8f1d1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -1218,39 +1068,39 @@ github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMT github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= -github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= -github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= -github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= +github.com/sylvia7788/contextcheck v1.0.4/go.mod h1:vuPKJMQ7MQ91ZTqfdyreNKwZjyUg6KO+IebVyQDedZQ= +github.com/tdakkota/asciicheck v0.0.0-20200416200610-e657995f937b/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM= +github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= +github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= +github.com/tetafro/godot v1.4.11/go.mod h1:LR3CJpxDVGlYOWn3ZZg1PgNZdTUvzsZWu8xaEohUpn8= +github.com/timakin/bodyclose v0.0.0-20200424151742-cb6215831a94/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= +github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs= +github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20200427203606-3cfed13b9966/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= -github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM= -github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/tomarrell/wrapcheck/v2 v2.4.0/go.mod h1:68bQ/eJg55BROaRTbMjC7vuhL2OgfoG8bLp9ZyoBfyY= +github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4= +github.com/tommy-muehle/go-mnd/v2 v2.4.0/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ultraware/funlen v0.0.3/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= +github.com/ultraware/whitespace v0.0.4/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/uudashr/gocognit v1.0.5/go.mod h1:wgYz0mitoKOTysqxTDMOUXg+Jb5SvtihkfmugIZYpEA= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.30.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus= +github.com/valyala/quicktemplate v1.7.0/go.mod h1:sqKJnoaOF88V07vkO+9FL8fb9uZg/VPSJnLYn+LmLk8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/vbatts/tar-split v0.11.2 h1:Via6XqJr0hceW4wff3QRzD5gAk/tatMw/4ZA7cTlIME= -github.com/vbatts/tar-split v0.11.2/go.mod h1:vV3ZuO2yWSVsz+pfFzDG/upWH1JhjOiEaWq6kXyQ3VI= github.com/vbauerster/mpb/v7 v7.4.1 h1:NhLMWQ3gNg2KJR8oeA9lO8Xvq+eNPmixDmB6JEQOUdA= github.com/vbauerster/mpb/v7 v7.4.1/go.mod h1:Ygg2mV9Vj9sQBWqsK2m2pidcf9H3s6bNKtqd3/M4gBo= -github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= -github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= -github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= -github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= -github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= -github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= -github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= -github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/viki-org/dnscache v0.0.0-20130720023526-c70c1f23c5d8/go.mod h1:dniwbG03GafCjFohMDmz6Zc6oCuiqgH6tGNyXTkHzXE= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/whilp/git-urls v1.0.0 h1:95f6UMWN5FKW71ECsXRUd3FVYiXdrE7aX4NZKcPmIjU= github.com/whilp/git-urls v1.0.0/go.mod h1:J16SAmobsqc3Qcy98brfl5f5+e0clUvg1krgwk/qCfE= -github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= -github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= github.com/xanzy/ssh-agent v0.3.1 h1:AmzO1SSWxw73zxFZPRwaMN1MohDw8UyHnmuxyceTEGo= github.com/xanzy/ssh-agent v0.3.1/go.mod h1:QIE4lCeL7nkC25x+yA3LBIYfwCc1TFziCtG7cBAac6w= @@ -1259,14 +1109,18 @@ github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMc github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= github.com/xlab/treeprint v1.1.0 h1:G/1DjNkPpfZCFt9CSh6b5/nY4VimlbHF3Rh4obvtzDk= github.com/xlab/treeprint v1.1.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= +github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yeya24/promlinter v0.1.0/go.mod h1:rs5vtZzeBHqqMwXqFScncpCF6u06lezhZepno9AB1Oc= +github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -1275,29 +1129,26 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI= -github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE= -github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= -github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= -go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= +go.etcd.io/etcd v0.0.0-20200513171258-e048e166ab9c/go.mod h1:xCI7ZzBfRuGgBXyXO6yfWfDmlWd35khcWpUa4L0xI/k= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= -go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs= go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= +go.etcd.io/etcd/client/v3 v3.5.1/go.mod h1:OnjH4M8OnAotwaB2l9bVgZzRFKru7/ZMoS46OtKyd3Q= go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE= go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc= go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4= -go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= +go.mozilla.org/mozlog v0.0.0-20170222151521-4bb13139d403/go.mod h1:jHoPAGnDrCy6kaI2tAze5Prf0Nr0w/oNkROt2lw3n3o= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -1308,68 +1159,61 @@ go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.28.0/go.mod h1:vEhqr0m4eTc+DWxfsXoXue2GBgV2uUwVznkGIHW/e5w= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= -go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs= go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0/go.mod h1:VpP4/RMn8bv8gNo9uK7/IMY4mtWLELsS+JIP0inH0h4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0/go.mod h1:hO1KLR7jcKaDDKDkvI9dP/FIhpmna5lkqPUQdEjFAM8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.3.0/go.mod h1:keUU7UfnwWTWpJ+FWnyqmogPa82nuU5VUANFq49hlMY= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.3.0/go.mod h1:QNX1aly8ehqqX1LEa6YniTU7VY9I6R3X/oPxhGdTceE= go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= -go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs= go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= -go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.opentelemetry.io/proto/otlp v0.11.0/go.mod h1:QpEjXPrNQzrFDZgoTo49dgHR9RYRSrg3NAKnUGl9YpQ= go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= go.starlark.net v0.0.0-20220328144851-d1966c6b9fcd h1:Uo/x0Ir5vQJ+683GXB9Ug+4fcjsbp7z7Ul8UaZbhsRM= go.starlark.net v0.0.0-20220328144851-d1966c6b9fcd/go.mod h1:t3mmBBPzAVvK0L0n1drDmrQsJ8FoIx4INCqVMTr/Zo0= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= -go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= -golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180501155221-613d6eafa307/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= -golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898 h1:SLP7Q4Di66FONjDJbCYrCRrh97focO6sLogHO7/g8F0= +golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1380,6 +1224,7 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1406,11 +1251,10 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= -golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1421,17 +1265,15 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -1439,6 +1281,7 @@ golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= @@ -1447,7 +1290,6 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -1459,24 +1301,20 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA= -golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 h1:NWy5+hlRbC7HK+PmcXVUmW1IMyFce7to56IUvhUFm7Y= +golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1493,7 +1331,6 @@ golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= @@ -1503,14 +1340,16 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 h1:w8s32wxx3sY+OjLlv9qltkLU5yvJzxjjgiHWLjdIcw4= +golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1518,6 +1357,7 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1525,17 +1365,10 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1545,55 +1378,41 @@ golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1602,7 +1421,6 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1613,19 +1431,16 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210903071746-97244b99971b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210915083310-ed5796bab164/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1634,11 +1449,10 @@ golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc= -golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 h1:EH1Deb8WZJ0xc0WK//leUHXcX9aLE5SymusoTmMZye8= golang.org/x/term v0.0.0-20220411215600-e5f449aeb171/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1658,32 +1472,40 @@ golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220411224347-583f2d630306 h1:+gHMid33q6pen7kv9xvT+JRinntgeXO2AeZVd0AWD3w= golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190307163923-6a08e3108db3/go.mod h1:25r3+/G6/xytQM8iWZKq3Hn0kr0rgFKPUNVEL/dr3z4= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190321232350-e250d351ecad/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190706070813-72ffa07ba3db/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190916130336-e45ffcd953cc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1691,9 +1513,11 @@ golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117220505-0cba7a3a9ee9/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -1702,47 +1526,72 @@ golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200308013534-11ec41452d41/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200414032229-332987a829c3/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200422022333-3d57cf2e726e/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200616133436-c1934b75d054/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200622203043-20e05c1c8ffa/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200624225443-88f3c62a19ff/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200625211823-6506e20df31f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200626171337-aa94e735be7f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200630154851-b2d8b0336632/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200706234117-b22de6825cf7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200812195022-5ae4c3c160a0/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200820010801-b793a1359eac/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200831203904-5a2aa26beb65/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20200916195026-c9a70fc28ce3/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/tools v0.0.0-20201001104356-43ebab892c4c/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/tools v0.0.0-20201002184944-ecd9fd270d5d/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201028025901-8cd080b735b3/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201114224030-61ea331ec02b/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201118003311-bd56c0adb394/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201230224404-63754364767c/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210101214203-2dba1e4ea05c/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210104081019-d8d6ddbec6ee/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= +golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.6-0.20210820212750-d4cc65f0b2ff/go.mod h1:YD9qOF0M9xpSpdWTBbzEl5e/RnCefISl8E5Noe10jFM= +golang.org/x/tools v0.1.6/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= -golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f h1:GGU+dLjvlC3qDwqYgL6UgRmHXhOOgns0bZu2Ty5mm6U= golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df h1:5Pf6pFKu98ODmgnpvkJ3kFUOQGGLIzLIkbzUHp47618= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.10.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= @@ -1769,9 +1618,7 @@ google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6 google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= -google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU= google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= -google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw= google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= @@ -1782,26 +1629,27 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= +google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181107211654-5fc9ac540362/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= @@ -1816,8 +1664,9 @@ google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200626011028-ee7919e894b5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200707001353-8e8330bf89df/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= @@ -1825,11 +1674,12 @@ google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201102152239-715cce707fb0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= @@ -1850,11 +1700,7 @@ google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEc google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= @@ -1870,12 +1716,11 @@ google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220426171045-31bebdecfb46 h1:G1IeWbjrqEq9ChWxEuRPJu6laA67+XgTFHVSAvepr38= -google.golang.org/genproto v0.0.0-20220426171045-31bebdecfb46/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd h1:e0TwkXOdbnH/1x5rc5MZ/VYyiZ4v+RdVfrGMqEwT68I= +google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= @@ -1885,6 +1730,7 @@ google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8 google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.0/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= @@ -1902,12 +1748,11 @@ google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= -google.golang.org/grpc v1.46.0 h1:oCjezcn6g6A75TGoKYBPgKmVBLexhYLM6MebdrPApP8= google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.46.2 h1:u+MLGgVf7vRdjEYZ8wDFhAVNmhkbJ5hmrA1LMWK1CAQ= +google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -1924,31 +1769,28 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= -gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI= -gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4= +gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= @@ -1959,21 +1801,21 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.6/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= -helm.sh/helm/v3 v3.8.2 h1:HDhe2nKek976VLMPZlIgJbNqwcqvHYBp1qy+sXQ4jiY= -helm.sh/helm/v3 v3.8.2/go.mod h1:NxtE2KObf2PrzDl6SIamPFPKyAqWi10iWuvKlQn/Yao= +helm.sh/helm/v3 v3.9.0 h1:qDSWViuF6SzZX5s5AB/NVRGWmdao7T5j4S4ebIkMGag= +helm.sh/helm/v3 v3.9.0/go.mod h1:fzZfyslcPAWwSdkXrXlpKexFeE2Dei8N27FFQWt+PN0= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1981,90 +1823,67 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.23.5 h1:zno3LUiMubxD/V1Zw3ijyKO3wxrhbUF1Ck+VjBvfaoA= -k8s.io/api v0.23.5/go.mod h1:Na4XuKng8PXJ2JsploYYrivXrINeTaycCGcYgF91Xm8= -k8s.io/apiextensions-apiserver v0.23.5/go.mod h1:ntcPWNXS8ZPKN+zTXuzYMeg731CP0heCTl6gYBxLcuQ= -k8s.io/apiextensions-apiserver v0.23.6 h1:v58cQ6Z0/GK1IXYr+oW0fnYl52o9LTY0WgoWvI8uv5Q= -k8s.io/apiextensions-apiserver v0.23.6/go.mod h1:YVh17Mphv183THQJA5spNFp9XfoidFyL3WoDgZxQIZU= -k8s.io/apimachinery v0.23.5 h1:Va7dwhp8wgkUPWsEXk6XglXWU4IKYLKNlv8VkX7SDM0= -k8s.io/apimachinery v0.23.5/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM= -k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= -k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= -k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= -k8s.io/apiserver v0.22.5/go.mod h1:s2WbtgZAkTKt679sYtSudEQrTGWUSQAPe6MupLnlmaQ= -k8s.io/apiserver v0.23.5/go.mod h1:7wvMtGJ42VRxzgVI7jkbKvMbuCbVbgsWFT7RyXiRNTw= -k8s.io/apiserver v0.23.6 h1:p94LiXcsSnpSDIl4cv98liBuFKcaygSCNopFNfMg/Ac= -k8s.io/apiserver v0.23.6/go.mod h1:5PU32F82tfErXPmf7FXhd/UcuLfh97tGepjKUgJ2atg= -k8s.io/cli-runtime v0.23.5 h1:Z7XUpGoJZYZB2uNjQfJjMbyDKyVkoBGye62Ap0sWQHY= -k8s.io/cli-runtime v0.23.5/go.mod h1:oY6QDF2qo9xndSq32tqcmRp2UyXssdGrLfjAVymgbx4= -k8s.io/client-go v0.23.5 h1:zUXHmEuqx0RY4+CsnkOn5l0GU+skkRXKGJrhmE2SLd8= -k8s.io/client-go v0.23.5/go.mod h1:flkeinTO1CirYgzMPRWxUCnV0G4Fbu2vLhYCObnt/r4= -k8s.io/code-generator v0.19.7/go.mod h1:lwEq3YnLYb/7uVXLorOJfxg+cUu2oihFhHZ0n9NIla0= -k8s.io/code-generator v0.23.5/go.mod h1:S0Q1JVA+kSzTI1oUvbKAxZY/DYbA/ZUb4Uknog12ETk= -k8s.io/code-generator v0.23.6/go.mod h1:S0Q1JVA+kSzTI1oUvbKAxZY/DYbA/ZUb4Uknog12ETk= -k8s.io/component-base v0.23.5 h1:8qgP5R6jG1BBSXmRYW+dsmitIrpk8F/fPEvgDenMCCE= -k8s.io/component-base v0.23.5/go.mod h1:c5Nq44KZyt1aLl0IpHX82fhsn84Sb0jjzwjpcA42bY0= -k8s.io/component-helpers v0.23.5/go.mod h1:5riXJgjTIs+ZB8xnf5M2anZ8iQuq37a0B/0BgoPQuSM= -k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM= -k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= -k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= -k8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc= -k8s.io/cri-api v0.23.1/go.mod h1:REJE3PSU0h/LOV1APBrupxrEJqnoxZC8KWzkBUHwrK4= +honnef.co/go/tools v0.2.1/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY= +k8s.io/api v0.24.0/go.mod h1:5Jl90IUrJHUJYEMANRURMiVvJ0g7Ax7r3R1bqO8zx8I= +k8s.io/api v0.24.1-rc.0 h1:qCZcUZ/YVXVVacShyNgyEHSUGxrWwIlDGAU1XpyMhYs= +k8s.io/api v0.24.1-rc.0/go.mod h1:IXhQwEOq8wx1Hmtm/YCNjyQ3XaeIUH0NP5Zsc0aF5Js= +k8s.io/apiextensions-apiserver v0.24.0 h1:JfgFqbA8gKJ/uDT++feAqk9jBIwNnL9YGdQvaI9DLtY= +k8s.io/apiextensions-apiserver v0.24.0/go.mod h1:iuVe4aEpe6827lvO6yWQVxiPSpPoSKVjkq+MIdg84cM= +k8s.io/apimachinery v0.24.0/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= +k8s.io/apimachinery v0.24.1-rc.0 h1:mjcDse2fEbUbiSZYU83NxdLUXlCSprcMlQVgNWn6tGk= +k8s.io/apimachinery v0.24.1-rc.0/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= +k8s.io/apiserver v0.24.0 h1:GR7kGsjOMfilRvlG3Stxv/3uz/ryvJ/aZXc5pqdsNV0= +k8s.io/apiserver v0.24.0/go.mod h1:WFx2yiOMawnogNToVvUYT9nn1jaIkMKj41ZYCVycsBA= +k8s.io/cli-runtime v0.24.0 h1:ot3Qf49T852uEyNApABO1UHHpFIckKK/NqpheZYN2gM= +k8s.io/cli-runtime v0.24.0/go.mod h1:9XxoZDsEkRFUThnwqNviqzljtT/LdHtNWvcNFrAXl0A= +k8s.io/client-go v0.24.0/go.mod h1:VFPQET+cAFpYxh6Bq6f4xyMY80G6jKKktU6G0m00VDw= +k8s.io/client-go v0.24.1-rc.0 h1:FoTsvkV8oz2FTORlpAR6TgRQMcdzGVIGQkjc7VrwZ40= +k8s.io/client-go v0.24.1-rc.0/go.mod h1:6VdmspONsQ+gV2AsYugsRaVSYOEUm3Trd16aQJgJahU= +k8s.io/code-generator v0.24.0/go.mod h1:dpVhs00hTuTdTY6jvVxvTFCk6gSMrtfRydbhZwHI15w= +k8s.io/component-base v0.24.0 h1:h5jieHZQoHrY/lHG+HyrSbJeyfuitheBvqvKwKHVC0g= +k8s.io/component-base v0.24.0/go.mod h1:Dgazgon0i7KYUsS8krG8muGiMVtUZxG037l1MKyXgrA= +k8s.io/component-helpers v0.24.0/go.mod h1:Q2SlLm4h6g6lPTC9GMMfzdywfLSvJT2f1hOnnjaWD8c= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20200428234225-8167cfdcfc14/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20201113003025-83324d819ded/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/gengo v0.0.0-20211129171323-c02415ce4185/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= -k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/klog/v2 v2.60.1 h1:VW25q3bZx9uE3vvdL6M8ezOX79vA2Aq1nEWLqNQclHc= k8s.io/klog/v2 v2.60.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= -k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= -k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= -k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= -k8s.io/kube-openapi v0.0.0-20220401212409-b28bf2818661 h1:nqYOUleKLC/0P1zbU29F5q6aoezM6MOAVz+iyfQbZ5M= +k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42/go.mod h1:Z/45zLw8lUo4wdiUkI+v/ImEGAvu3WatcZl3lPMR4Rk= k8s.io/kube-openapi v0.0.0-20220401212409-b28bf2818661/go.mod h1:daOouuuwd9JXpv1L7Y34iV3yf6nxzipkKMWWlqlvK9M= -k8s.io/kubectl v0.23.5 h1:DmDULqCaF4qstj0Im143XmncvqWtJxHzK8IrW2BzlU0= -k8s.io/kubectl v0.23.5/go.mod h1:lLgw7cVY8xbd7o637vOXPca/w6HC205KsPCRDYRCxwE= -k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= -k8s.io/metrics v0.23.5/go.mod h1:WNAtV2a5BYbmDS8+7jSqYYV6E3efuGTpIwJ8PTD1wgs= -k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/kube-openapi v0.0.0-20220413171646-5e7f5fdc6da6 h1:nBQrWPlrNIiw0BsX6a6MKr1itkm0ZS0Nl97kNLitFfI= +k8s.io/kube-openapi v0.0.0-20220413171646-5e7f5fdc6da6/go.mod h1:daOouuuwd9JXpv1L7Y34iV3yf6nxzipkKMWWlqlvK9M= +k8s.io/kubectl v0.24.0 h1:nA+WtMLVdXUs4wLogGd1mPTAesnLdBpCVgCmz3I7dXo= +k8s.io/kubectl v0.24.0/go.mod h1:pdXkmCyHiRTqjYfyUJiXtbVNURhv0/Q1TyRhy2d5ic0= +k8s.io/metrics v0.24.0/go.mod h1:jrLlFGdKl3X+szubOXPG0Lf2aVxuV3QJcbsgVRAM6fI= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 h1:HNSDgDCrr/6Ly3WEGKZftiE7IY19Vz2GdbOCyI4qqhc= k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +mvdan.cc/gofumpt v0.1.1/go.mod h1:yXG1r1WqZVKWbVRtBWKWX9+CxGYfA51nSomhM0woR48= +mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= +mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= +mvdan.cc/unparam v0.0.0-20210104141923-aac4ce9116a7/go.mod h1:hBpJkZE8H/sb+VRFvw2+rBpHNsTBcvSpk61hr8mzXZE= oras.land/oras-go v1.1.1 h1:gI00ftziRivKXaw1BdMeEoIA4uBgga33iVlOsEwefFs= oras.land/oras-go v1.1.1/go.mod h1:n2TE1ummt9MUyprGhT+Q7kGZUF4kVUpYysPFxeV2IpQ= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.30/go.mod h1:fEO7lRTdivWO2qYVCVG7dEADOMo/MLDCVr8So2g88Uw= -sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 h1:kDi4JBNAsJWfz1aEXhO8Jg87JJaPNLh5tIzYHgStQ9Y= sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY= -sigs.k8s.io/kind v0.12.0 h1:LFynXwQkH1MrWI8pM1FQty0oUwEKjU5EkMaVZaPld8E= -sigs.k8s.io/kind v0.12.0/go.mod h1:EcgDSBVxz8Bvm19fx8xkioFrf9dC30fMJdOTXBSGNoM= -sigs.k8s.io/kustomize/api v0.10.1/go.mod h1:2FigT1QN6xKdcnGS2Ppp1uIWrtWN28Ms8A3OZUZhwr8= +sigs.k8s.io/kind v0.14.0 h1:cNmI3jGBvp7UegEGbC5we8plDtCUmaNRL+bod7JoSCE= +sigs.k8s.io/kind v0.14.0/go.mod h1:UrFRPHG+2a5j0Q7qiR4gtJ4rEyn8TuMQwuOPf+m4oHg= +sigs.k8s.io/kustomize/api v0.11.4/go.mod h1:k+8RsqYbgpkIrJ4p9jcdPqe8DprLxFUUO0yNOq8C+xI= sigs.k8s.io/kustomize/api v0.11.5 h1:vLDp++YAX7iy2y2CVPJNy9pk9CY8XaUKgHkjbVtnWag= sigs.k8s.io/kustomize/api v0.11.5/go.mod h1:2UDpxS6AonWXow2ZbySd4AjUxmdXLeTlvGBC46uSiq8= -sigs.k8s.io/kustomize/cmd/config v0.10.2/go.mod h1:K2aW7nXJ0AaT+VA/eO0/dzFLxmpFcTzudmAgDwPY1HQ= -sigs.k8s.io/kustomize/kustomize/v4 v4.4.1/go.mod h1:qOKJMMz2mBP+vcS7vK+mNz4HBLjaQSWRY22EF6Tb7Io= -sigs.k8s.io/kustomize/kyaml v0.13.0/go.mod h1:FTJxEZ86ScK184NpGSAQcfEqee0nul8oLCK30D47m4E= +sigs.k8s.io/kustomize/cmd/config v0.10.6/go.mod h1:/S4A4nUANUa4bZJ/Edt7ZQTyKOY9WCER0uBS1SW2Rco= +sigs.k8s.io/kustomize/kustomize/v4 v4.5.4/go.mod h1:Zo/Xc5FKD6sHl0lilbrieeGeZHVYCA4BzxeAaLI05Bg= +sigs.k8s.io/kustomize/kyaml v0.13.6/go.mod h1:yHP031rn1QX1lr/Xd934Ri/xdVNG8BE2ECa78Ht/kEg= sigs.k8s.io/kustomize/kyaml v0.13.7 h1:/EZ/nPaLUzeJKF/BuJ4QCuMVJWiEVoI8iftOHY3g3tk= sigs.k8s.io/kustomize/kyaml v0.13.7/go.mod h1:6K+IUOuir3Y7nucPRAjw9yth04KSWBnP5pqUTGwj/qU= -sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y= sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= From 630e7db136b0b02914f9ea94fd4fe8bdbc6dd6d4 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 23 May 2022 13:41:26 +0200 Subject: [PATCH 0866/2916] fix: Fix false positive error about not included rendered helm resource We must take sub directories into account when checking for inclusion of rendered helm charts. --- pkg/deployment/deployment_item.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 783084673..c8a5d92a2 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -171,12 +171,17 @@ func (di *DeploymentItem) renderHelmCharts(k *k8s.K8sCluster, wp *utils.WorkerPo } wp.Submit(func() error { + subDir, err := filepath.Rel(di.renderedDir, filepath.Dir(p)) + if err != nil { + return err + } + chart, err := NewHelmChart(p) if err != nil { return err } - ky, err := di.readKustoimizationYaml() + ky, err := di.readKustoimizationYaml(subDir) if err == nil && ky != nil { resources, _, _ := ky.GetNestedStringList("resources") found := false @@ -301,12 +306,12 @@ func (di *DeploymentItem) checkInclusionForDelete() bool { return di.Inclusion.CheckIncluded(values, di.Config.SkipDeleteIfTags) } -func (di *DeploymentItem) readKustoimizationYaml() (*uo.UnstructuredObject, error) { +func (di *DeploymentItem) readKustoimizationYaml(subDir string) (*uo.UnstructuredObject, error) { if di.dir == nil { return nil, nil } - kustomizeYamlPath := yaml.FixPathExt(filepath.Join(di.renderedDir, "kustomization.yml")) + kustomizeYamlPath := yaml.FixPathExt(filepath.Join(di.renderedDir, subDir, "kustomization.yml")) if !utils.IsFile(kustomizeYamlPath) { return nil, nil } @@ -325,7 +330,7 @@ func (di *DeploymentItem) writeKustomizationYaml(ky *uo.UnstructuredObject) erro } func (di *DeploymentItem) prepareKustomizationYaml() error { - ky, err := di.readKustoimizationYaml() + ky, err := di.readKustoimizationYaml("") if err != nil { return err } From d1904f53b19d84ebf387d13d9fb6feb44f99dca6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 23 May 2022 13:43:01 +0200 Subject: [PATCH 0867/2916] chore: Fix typo in readKustomizationYaml --- pkg/deployment/deployment_item.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index c8a5d92a2..65d303bfd 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -181,7 +181,7 @@ func (di *DeploymentItem) renderHelmCharts(k *k8s.K8sCluster, wp *utils.WorkerPo return err } - ky, err := di.readKustoimizationYaml(subDir) + ky, err := di.readKustomizationYaml(subDir) if err == nil && ky != nil { resources, _, _ := ky.GetNestedStringList("resources") found := false @@ -306,7 +306,7 @@ func (di *DeploymentItem) checkInclusionForDelete() bool { return di.Inclusion.CheckIncluded(values, di.Config.SkipDeleteIfTags) } -func (di *DeploymentItem) readKustoimizationYaml(subDir string) (*uo.UnstructuredObject, error) { +func (di *DeploymentItem) readKustomizationYaml(subDir string) (*uo.UnstructuredObject, error) { if di.dir == nil { return nil, nil } @@ -330,7 +330,7 @@ func (di *DeploymentItem) writeKustomizationYaml(ky *uo.UnstructuredObject) erro } func (di *DeploymentItem) prepareKustomizationYaml() error { - ky, err := di.readKustoimizationYaml("") + ky, err := di.readKustomizationYaml("") if err != nil { return err } From f079878f0a97b510a18f4c2c8a22d5015c03112c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 23 May 2022 16:59:04 +0200 Subject: [PATCH 0868/2916] fix: Get rid of pathType The implementation was broken and it actually doesn't make sense to have it. --- cmd/kluctl/args/cobra_types.go | 21 --------------------- cmd/kluctl/args/misc.go | 2 +- cmd/kluctl/args/project.go | 2 +- cmd/kluctl/commands/cmd_render.go | 2 +- cmd/kluctl/commands/utils.go | 4 ++-- 5 files changed, 5 insertions(+), 26 deletions(-) diff --git a/cmd/kluctl/args/cobra_types.go b/cmd/kluctl/args/cobra_types.go index 7989383a1..c6907ea51 100644 --- a/cmd/kluctl/args/cobra_types.go +++ b/cmd/kluctl/args/cobra_types.go @@ -64,24 +64,3 @@ func (s *existingDirType) Type() string { } func (s *existingDirType) String() string { return string(*s) } - -type pathType string - -func (s *pathType) Set(val string) error { - if val != "-" { - val = utils.ExpandPath(val) - } - if !utils.Exists(val) { - return fmt.Errorf("%s does not exist", val) - } - if !utils.IsDirectory(val) { - return fmt.Errorf("%s exists but is not a directory", val) - } - *s = pathType(val) - return nil -} -func (s *pathType) Type() string { - return "path" -} - -func (s *pathType) String() string { return string(*s) } diff --git a/cmd/kluctl/args/misc.go b/cmd/kluctl/args/misc.go index 86ef05ae5..196f52872 100644 --- a/cmd/kluctl/args/misc.go +++ b/cmd/kluctl/args/misc.go @@ -44,5 +44,5 @@ type OutputFlags struct { } type RenderOutputDirFlags struct { - RenderOutputDir pathType `group:"misc" help:"Specifies the target directory to render the project into. If omitted, a temporary directory is used."` + RenderOutputDir string `group:"misc" help:"Specifies the target directory to render the project into. If omitted, a temporary directory is used."` } diff --git a/cmd/kluctl/args/project.go b/cmd/kluctl/args/project.go index c7479b869..f1b35c3fb 100644 --- a/cmd/kluctl/args/project.go +++ b/cmd/kluctl/args/project.go @@ -12,7 +12,7 @@ type ProjectFlags struct { LocalSealedSecrets existingDirType `group:"project" help:"Local sealed-secrets directory. Overrides the project from .kluctl.yaml" ` FromArchive existingPathType `group:"project" help:"Load project (.kluctl.yaml, cluster, ...) from archive. Given path can either be an archive file or a directory with the extracted contents." exts:"tar.gz,tgz"` FromArchiveMetadata existingFileType `group:"project" help:"Specify where to load metadata (targets, ...) from. If not specified, metadata is assumed to be part of the archive." exts:"yml,yaml"` - OutputMetadata pathType `group:"project" help:"Specify the output path for the project metadata to be written to. When used with the 'archive' command, it will also cause the archive to not include the metadata.yaml file."` + OutputMetadata string `group:"project" help:"Specify the output path for the project metadata to be written to. When used with the 'archive' command, it will also cause the archive to not include the metadata.yaml file."` Cluster string `group:"project" help:"Specify/Override cluster"` Timeout time.Duration `group:"project" help:"Specify timeout for all operations, including loading of the project, all external api calls and waiting for readiness." default:"10m"` diff --git a/cmd/kluctl/commands/cmd_render.go b/cmd/kluctl/commands/cmd_render.go index c22b242af..3aabf8cf0 100644 --- a/cmd/kluctl/commands/cmd_render.go +++ b/cmd/kluctl/commands/cmd_render.go @@ -26,7 +26,7 @@ func (cmd *renderCmd) Run() error { if err != nil { return err } - _ = cmd.RenderOutputDir.Set(p) + cmd.RenderOutputDir = p } ptArgs := projectTargetCommandArgs{ diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 8dab17d0d..388396f30 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -87,7 +87,7 @@ func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, strictTemplates b if err != nil { return err } - err = ioutil.WriteFile(projectFlags.OutputMetadata.String(), b, 0o640) + err = ioutil.WriteFile(projectFlags.OutputMetadata, b, 0o640) if err != nil { return err } @@ -148,7 +148,7 @@ func withProjectTargetCommandContext(ctx context.Context, args projectTargetComm return err } - renderOutputDir := args.renderOutputDirFlags.RenderOutputDir.String() + renderOutputDir := args.renderOutputDirFlags.RenderOutputDir if renderOutputDir == "" { tmpDir, err := ioutil.TempDir(p.TmpDir, "rendered") if err != nil { From f0f94803256d63453627d2b6e42dae8a83671838 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 23 May 2022 17:07:25 +0200 Subject: [PATCH 0869/2916] refactor: Move loading of individual include into own function --- pkg/deployment/deployment_project.go | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go index 6ac7de718..17e5e306c 100644 --- a/pkg/deployment/deployment_project.go +++ b/pkg/deployment/deployment_project.go @@ -162,24 +162,32 @@ func (p *DeploymentProject) loadIncludes(k *k8s.K8sCluster) error { incDir := filepath.Join(p.dir, *inc.Include) - varsCtx := p.VarsCtx.Copy() - err := p.loadVarsList(varsCtx, inc.Vars) + newProject, err := p.loadLocalInclude(k, incDir, inc.Vars) if err != nil { return err } - newProject, err := NewDeploymentProject(p.ctx, k, p.varsLoader, varsCtx, incDir, - p.SealedSecretsDir, p) - if err != nil { - return err - } newProject.parentProjectInclude = inc - p.includes[i] = newProject } return nil } +func (p *DeploymentProject) loadLocalInclude(k *k8s.K8sCluster, incDir string, vars []*types.VarsSource) (*DeploymentProject, error) { + varsCtx := p.VarsCtx.Copy() + err := p.loadVarsList(varsCtx, vars) + if err != nil { + return nil, err + } + + newProject, err := NewDeploymentProject(p.ctx, k, p.varsLoader, varsCtx, incDir, p.SealedSecretsDir, p) + if err != nil { + return nil, err + } + + return newProject, nil +} + func (p *DeploymentProject) getSealedSecretsDir() string { root := p.getRootProject() if root.Config.SealedSecrets == nil || root.Config.SealedSecrets.OutputPattern == nil { From 7b4713fda338d747702f2264a520f88ebb77ebd6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 24 May 2022 10:07:01 +0200 Subject: [PATCH 0870/2916] refactor: Introduce SharedContext for deployment projects and collections --- cmd/kluctl/commands/cmd_delete.go | 4 +-- cmd/kluctl/commands/cmd_deploy.go | 2 +- cmd/kluctl/commands/cmd_diff.go | 2 +- cmd/kluctl/commands/cmd_downscale.go | 2 +- cmd/kluctl/commands/cmd_poke_images.go | 2 +- cmd/kluctl/commands/cmd_prune.go | 4 +-- cmd/kluctl/commands/cmd_render.go | 2 +- cmd/kluctl/commands/cmd_seal.go | 8 +++--- cmd/kluctl/commands/cmd_validate.go | 2 +- cmd/kluctl/commands/completion.go | 2 +- cmd/kluctl/commands/utils.go | 2 +- pkg/deployment/commands/seal.go | 20 +++++++------ pkg/deployment/deployment_collection.go | 35 +++++++++++------------ pkg/deployment/deployment_item.go | 25 +++++++++------- pkg/deployment/deployment_project.go | 38 ++++++++++--------------- pkg/deployment/shared_context.go | 17 +++++++++++ pkg/kluctl_project/target_context.go | 20 +++++++++---- 17 files changed, 104 insertions(+), 83 deletions(-) create mode 100644 pkg/deployment/shared_context.go diff --git a/cmd/kluctl/commands/cmd_delete.go b/cmd/kluctl/commands/cmd_delete.go index 4bb602290..4911d9479 100644 --- a/cmd/kluctl/commands/cmd_delete.go +++ b/cmd/kluctl/commands/cmd_delete.go @@ -55,11 +55,11 @@ func (cmd *deleteCmd) Run() error { cmd2.OverrideDeleteByLabels = deleteByLabels - objects, err := cmd2.Run(ctx.ctx, ctx.targetCtx.K) + objects, err := cmd2.Run(ctx.ctx, ctx.targetCtx.SharedContext.K) if err != nil { return err } - result, err := confirmedDeleteObjects(ctx.targetCtx.K, objects, cmd.DryRun, cmd.Yes) + result, err := confirmedDeleteObjects(ctx.targetCtx.SharedContext.K, objects, cmd.DryRun, cmd.Yes) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_deploy.go b/cmd/kluctl/commands/cmd_deploy.go index 1e514ca9b..a2b6d70a9 100644 --- a/cmd/kluctl/commands/cmd_deploy.go +++ b/cmd/kluctl/commands/cmd_deploy.go @@ -63,7 +63,7 @@ func (cmd *deployCmd) runCmdDeploy(ctx *commandCtx) error { cb = nil } - result, err := cmd2.Run(ctx.ctx, ctx.targetCtx.K, cb) + result, err := cmd2.Run(ctx.ctx, ctx.targetCtx.SharedContext.K, cb) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_diff.go b/cmd/kluctl/commands/cmd_diff.go index 7b2efec67..bc6fcc211 100644 --- a/cmd/kluctl/commands/cmd_diff.go +++ b/cmd/kluctl/commands/cmd_diff.go @@ -43,7 +43,7 @@ func (cmd *diffCmd) Run() error { cmd2.IgnoreTags = cmd.IgnoreTags cmd2.IgnoreLabels = cmd.IgnoreLabels cmd2.IgnoreAnnotations = cmd.IgnoreAnnotations - result, err := cmd2.Run(ctx.ctx, ctx.targetCtx.K) + result, err := cmd2.Run(ctx.ctx, ctx.targetCtx.SharedContext.K) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_downscale.go b/cmd/kluctl/commands/cmd_downscale.go index d3a0ffc10..304b5cb1c 100644 --- a/cmd/kluctl/commands/cmd_downscale.go +++ b/cmd/kluctl/commands/cmd_downscale.go @@ -44,7 +44,7 @@ func (cmd *downscaleCmd) Run() error { cmd2 := commands.NewDownscaleCommand(ctx.targetCtx.DeploymentCollection) - result, err := cmd2.Run(ctx.ctx, ctx.targetCtx.K) + result, err := cmd2.Run(ctx.ctx, ctx.targetCtx.SharedContext.K) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_poke_images.go b/cmd/kluctl/commands/cmd_poke_images.go index c7121e515..67e7c5d0a 100644 --- a/cmd/kluctl/commands/cmd_poke_images.go +++ b/cmd/kluctl/commands/cmd_poke_images.go @@ -44,7 +44,7 @@ func (cmd *pokeImagesCmd) Run() error { cmd2 := commands.NewPokeImagesCommand(ctx.targetCtx.DeploymentCollection) - result, err := cmd2.Run(ctx.ctx, ctx.targetCtx.K) + result, err := cmd2.Run(ctx.ctx, ctx.targetCtx.SharedContext.K) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_prune.go b/cmd/kluctl/commands/cmd_prune.go index 14d4d8790..d9c25852a 100644 --- a/cmd/kluctl/commands/cmd_prune.go +++ b/cmd/kluctl/commands/cmd_prune.go @@ -43,11 +43,11 @@ func (cmd *pruneCmd) Run() error { func (cmd *pruneCmd) runCmdPrune(ctx *commandCtx) error { cmd2 := commands.NewPruneCommand(ctx.targetCtx.DeploymentCollection) - objects, err := cmd2.Run(ctx.ctx, ctx.targetCtx.K) + objects, err := cmd2.Run(ctx.ctx, ctx.targetCtx.SharedContext.K) if err != nil { return err } - result, err := confirmedDeleteObjects(ctx.targetCtx.K, objects, cmd.DryRun, cmd.Yes) + result, err := confirmedDeleteObjects(ctx.targetCtx.SharedContext.K, objects, cmd.DryRun, cmd.Yes) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_render.go b/cmd/kluctl/commands/cmd_render.go index 3aabf8cf0..190760e8d 100644 --- a/cmd/kluctl/commands/cmd_render.go +++ b/cmd/kluctl/commands/cmd_render.go @@ -37,7 +37,7 @@ func (cmd *renderCmd) Run() error { renderOutputDirFlags: cmd.RenderOutputDirFlags, } return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { - status.Info(ctx.ctx, "Rendered into %s", ctx.targetCtx.DeploymentCollection.RenderDir) + status.Info(ctx.ctx, "Rendered into %s", ctx.targetCtx.SharedContext.RenderDir) return nil }) } diff --git a/cmd/kluctl/commands/cmd_seal.go b/cmd/kluctl/commands/cmd_seal.go index 6749b559d..531fb13b4 100644 --- a/cmd/kluctl/commands/cmd_seal.go +++ b/cmd/kluctl/commands/cmd_seal.go @@ -44,7 +44,7 @@ func (cmd *sealCmd) runCmdSealForTarget(ctx context.Context, p *kluctl_project.L // pass forSeal=True so that .sealme files are rendered as well return withProjectTargetCommandContext(ctx, ptArgs, p, func(ctx *commandCtx) error { - err := ctx.targetCtx.DeploymentCollection.RenderDeployments(ctx.targetCtx.K) + err := ctx.targetCtx.DeploymentCollection.RenderDeployments() if err != nil { return doFail(err) } @@ -60,7 +60,7 @@ func (cmd *sealCmd) runCmdSealForTarget(ctx context.Context, p *kluctl_project.L } } if p.Config.SecretsConfig == nil || p.Config.SecretsConfig.SealedSecrets == nil || p.Config.SecretsConfig.SealedSecrets.Bootstrap == nil || *p.Config.SecretsConfig.SealedSecrets.Bootstrap { - err = seal.BootstrapSealedSecrets(ctx.ctx, ctx.targetCtx.K, sealedSecretsNamespace) + err = seal.BootstrapSealedSecrets(ctx.ctx, ctx.targetCtx.SharedContext.K, sealedSecretsNamespace) if err != nil { return doFail(err) } @@ -70,7 +70,7 @@ func (cmd *sealCmd) runCmdSealForTarget(ctx context.Context, p *kluctl_project.L if err != nil { return doFail(err) } - sealer, err := seal.NewSealer(ctx.ctx, ctx.targetCtx.K, sealedSecretsNamespace, sealedSecretsControllerName, clusterConfig.Cluster, cmd.ForceReseal) + sealer, err := seal.NewSealer(ctx.ctx, ctx.targetCtx.SharedContext.K, sealedSecretsNamespace, sealedSecretsControllerName, clusterConfig.Cluster, cmd.ForceReseal) if err != nil { return doFail(err) } @@ -82,7 +82,7 @@ func (cmd *sealCmd) runCmdSealForTarget(ctx context.Context, p *kluctl_project.L outputPattern = *ctx.targetCtx.DeploymentProject.Config.SealedSecrets.OutputPattern } - cmd2 := commands.NewSealCommand(ctx.targetCtx.DeploymentCollection, outputPattern) + cmd2 := commands.NewSealCommand(ctx.targetCtx.DeploymentCollection, outputPattern, ctx.targetCtx.SharedContext.RenderDir, ctx.targetCtx.SharedContext.SealedSecretsDir) err = cmd2.Run(sealer) if err != nil { diff --git a/cmd/kluctl/commands/cmd_validate.go b/cmd/kluctl/commands/cmd_validate.go index 308f1abfb..b34d90a0d 100644 --- a/cmd/kluctl/commands/cmd_validate.go +++ b/cmd/kluctl/commands/cmd_validate.go @@ -39,7 +39,7 @@ func (cmd *validateCmd) Run() error { startTime := time.Now() cmd2 := commands.NewValidateCommand(ctx.ctx, ctx.targetCtx.DeploymentCollection) for true { - result, err := cmd2.Run(ctx.ctx, ctx.targetCtx.K) + result, err := cmd2.Run(ctx.ctx, ctx.targetCtx.SharedContext.K) if err != nil { return err } diff --git a/cmd/kluctl/commands/completion.go b/cmd/kluctl/commands/completion.go index a541ab565..5b94c0964 100644 --- a/cmd/kluctl/commands/completion.go +++ b/cmd/kluctl/commands/completion.go @@ -204,7 +204,7 @@ func buildImagesCompletionFunc(cmdStruct interface{}) func(cmd *cobra.Command, a wg.Add(1) go func() { _ = withProjectTargetCommandContext(ctx, ptArgs, p, func(ctx *commandCtx) error { - err := ctx.targetCtx.DeploymentCollection.Prepare(nil) + err := ctx.targetCtx.DeploymentCollection.Prepare() if err != nil { status.Error(cliCtx, err.Error()) } diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 388396f30..12c89660b 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -172,7 +172,7 @@ func withProjectTargetCommandContext(ctx context.Context, args projectTargetComm } if !args.forSeal && !args.forCompletion { - err = targetCtx.DeploymentCollection.Prepare(targetCtx.K) + err = targetCtx.DeploymentCollection.Prepare() if err != nil { return err } diff --git a/pkg/deployment/commands/seal.go b/pkg/deployment/commands/seal.go index 0397133ca..c8c3e37a5 100644 --- a/pkg/deployment/commands/seal.go +++ b/pkg/deployment/commands/seal.go @@ -11,29 +11,33 @@ import ( ) type SealCommand struct { - c *deployment.DeploymentCollection - outputPattern string + c *deployment.DeploymentCollection + outputPattern string + renderDir string + sealedSecretsDir string } -func NewSealCommand(c *deployment.DeploymentCollection, outputPattern string) *SealCommand { +func NewSealCommand(c *deployment.DeploymentCollection, outputPattern string, renderDir string, sealedSecretsDir string) *SealCommand { return &SealCommand{ - c: c, - outputPattern: outputPattern, + c: c, + outputPattern: outputPattern, + renderDir: renderDir, + sealedSecretsDir: sealedSecretsDir, } } func (cmd *SealCommand) Run(sealer *seal.Sealer) error { - err := filepath.WalkDir(cmd.c.RenderDir, func(p string, d fs.DirEntry, err error) error { + err := filepath.WalkDir(cmd.renderDir, func(p string, d fs.DirEntry, err error) error { if !strings.HasSuffix(p, deployment.SealmeExt) { return nil } - relPath, err := filepath.Rel(cmd.c.RenderDir, p) + relPath, err := filepath.Rel(cmd.renderDir, p) if err != nil { return err } relTargetFile := filepath.Join(filepath.Dir(relPath), cmd.outputPattern, filepath.Base(p)) - targetFile, err := securejoin.SecureJoin(cmd.c.Project.SealedSecretsDir, relTargetFile) + targetFile, err := securejoin.SecureJoin(cmd.sealedSecretsDir, relTargetFile) if err != nil { return err } diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index 03883a86c..d25503c77 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -3,7 +3,6 @@ package deployment import ( "context" "fmt" - "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" @@ -14,25 +13,23 @@ import ( ) type DeploymentCollection struct { - ctx context.Context + ctx SharedContext Project *DeploymentProject Images *Images Inclusion *utils.Inclusion - RenderDir string forSeal bool Deployments []*DeploymentItem mutex sync.Mutex } -func NewDeploymentCollection(ctx context.Context, project *DeploymentProject, images *Images, inclusion *utils.Inclusion, renderDir string, forSeal bool) (*DeploymentCollection, error) { +func NewDeploymentCollection(ctx SharedContext, project *DeploymentProject, images *Images, inclusion *utils.Inclusion, forSeal bool) (*DeploymentCollection, error) { dc := &DeploymentCollection{ ctx: ctx, Project: project, Images: images, Inclusion: inclusion, - RenderDir: renderDir, forSeal: forSeal, } @@ -49,7 +46,7 @@ func (c *DeploymentCollection) createBarrierDummy(project *DeploymentProject) *D tmpDiConfig := &types.DeploymentItemConfig{ Barrier: true, } - di, err := NewDeploymentItem(project, c, tmpDiConfig, nil, 0) + di, err := NewDeploymentItem(c.ctx, project, c, tmpDiConfig, nil, 0) if err != nil { panic(err) } @@ -97,7 +94,7 @@ func (c *DeploymentCollection) collectDeployments(project *DeploymentProject, in } } else { index, dir2 := findDeploymentItemIndex(project, diConfig.Path, indexes) - di, err := NewDeploymentItem(project, c, diConfig, dir2, index) + di, err := NewDeploymentItem(c.ctx, project, c, diConfig, dir2, index) if err != nil { return nil, err } @@ -108,8 +105,8 @@ func (c *DeploymentCollection) collectDeployments(project *DeploymentProject, in return ret, nil } -func (c *DeploymentCollection) RenderDeployments(k *k8s.K8sCluster) error { - s := status.Start(c.ctx, "Rendering templates") +func (c *DeploymentCollection) RenderDeployments() error { + s := status.Start(c.ctx.Ctx, "Rendering templates") defer s.Failed() wp := utils.NewDebuggerAwareWorkerPool(16) @@ -127,11 +124,11 @@ func (c *DeploymentCollection) RenderDeployments(k *k8s.K8sCluster) error { } s.Success() - s = status.Start(c.ctx, "Rendering Helm Charts") + s = status.Start(c.ctx.Ctx, "Rendering Helm Charts") defer s.Failed() for _, d := range c.Deployments { - err := d.renderHelmCharts(k, wp) + err := d.renderHelmCharts(wp) if err != nil { return err } @@ -159,7 +156,7 @@ func (c *DeploymentCollection) resolveSealedSecrets() error { return nil } -func (c *DeploymentCollection) buildKustomizeObjects(k *k8s.K8sCluster) error { +func (c *DeploymentCollection) buildKustomizeObjects() error { var wg sync.WaitGroup var errs []error var mutex sync.Mutex @@ -171,7 +168,7 @@ func (c *DeploymentCollection) buildKustomizeObjects(k *k8s.K8sCluster) error { mutex.Unlock() } - s := status.Start(c.ctx, "Building kustomize objects") + s := status.Start(c.ctx.Ctx, "Building kustomize objects") for _, d_ := range c.Deployments { d := d_ @@ -192,7 +189,7 @@ func (c *DeploymentCollection) buildKustomizeObjects(k *k8s.K8sCluster) error { } s.Success() - s = status.Start(c.ctx, "Postprocessing objects") + s = status.Start(c.ctx.Ctx, "Postprocessing objects") for _, d_ := range c.Deployments { d := d_ @@ -202,7 +199,7 @@ func (c *DeploymentCollection) buildKustomizeObjects(k *k8s.K8sCluster) error { if err != nil { handleError(fmt.Errorf("loading objects failed: %w", err)) } else { - err = d.postprocessCRDs(k) + err = d.postprocessCRDs() if err != nil { handleError(fmt.Errorf("postprocessing CRDs failed: %w", err)) } @@ -220,7 +217,7 @@ func (c *DeploymentCollection) buildKustomizeObjects(k *k8s.K8sCluster) error { _ = sem.Acquire(context.Background(), 1) defer sem.Release(1) - err := d.postprocessObjects(k, c.Images) + err := d.postprocessObjects(c.Images) if err != nil { mutex.Lock() errs = append(errs, fmt.Errorf("postprocessing kustomize objects for %s failed: %w", *d.dir, err)) @@ -259,8 +256,8 @@ func (c *DeploymentCollection) LocalObjectRefs() []k8s2.ObjectRef { return ret } -func (c *DeploymentCollection) Prepare(k *k8s.K8sCluster) error { - err := c.RenderDeployments(k) +func (c *DeploymentCollection) Prepare() error { + err := c.RenderDeployments() if err != nil { return err } @@ -268,7 +265,7 @@ func (c *DeploymentCollection) Prepare(k *k8s.K8sCluster) error { if err != nil { return err } - err = c.buildKustomizeObjects(k) + err = c.buildKustomizeObjects() if err != nil { return err } diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 65d303bfd..b07ff59a9 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -21,6 +21,8 @@ import ( const SealmeExt = ".sealme" type DeploymentItem struct { + ctx SharedContext + Project *DeploymentProject Inclusion *utils.Inclusion Config *types.DeploymentItemConfig @@ -42,8 +44,9 @@ type DeploymentItem struct { renderedYamlPath string } -func NewDeploymentItem(project *DeploymentProject, collection *DeploymentCollection, config *types.DeploymentItemConfig, dir *string, index int) (*DeploymentItem, error) { +func NewDeploymentItem(ctx SharedContext, project *DeploymentProject, collection *DeploymentCollection, config *types.DeploymentItemConfig, dir *string, index int) (*DeploymentItem, error) { di := &DeploymentItem{ + ctx: ctx, Project: project, Inclusion: collection.Inclusion, Config: config, @@ -80,7 +83,7 @@ func NewDeploymentItem(project *DeploymentProject, collection *DeploymentCollect di.relRenderedDir = fmt.Sprintf("%s-%d", di.relRenderedDir, di.index) } - di.renderedDir = filepath.Join(collection.RenderDir, di.relRenderedDir) + di.renderedDir = filepath.Join(collection.ctx.RenderDir, di.relRenderedDir) di.renderedYamlPath = filepath.Join(di.renderedDir, ".rendered.yml") } return di, nil @@ -160,7 +163,7 @@ func (di *DeploymentItem) render(forSeal bool, wp *utils.WorkerPoolWithErrors) e return nil } -func (di *DeploymentItem) renderHelmCharts(k *k8s.K8sCluster, wp *utils.WorkerPoolWithErrors) error { +func (di *DeploymentItem) renderHelmCharts(wp *utils.WorkerPoolWithErrors) error { if di.dir == nil { return nil } @@ -196,7 +199,7 @@ func (di *DeploymentItem) renderHelmCharts(k *k8s.K8sCluster, wp *utils.WorkerPo } } - return chart.Render(di.Project.ctx, k) + return chart.Render(di.ctx.Ctx, di.ctx.K) }) return nil }) @@ -212,7 +215,7 @@ func (di *DeploymentItem) resolveSealedSecrets(subdir string) error { } sealedSecretsDir := di.Project.getSealedSecretsDir() - baseSourcePath := di.Project.SealedSecretsDir + baseSourcePath := di.Project.ctx.SealedSecretsDir renderedDir := filepath.Join(di.renderedDir, subdir) @@ -419,7 +422,7 @@ var crdGV = schema.GroupKind{Group: "apiextensions.k8s.io", Kind: "CustomResourc // postprocessCRDs will update api resources from freshly deployed CRDs // value even if the CRD is not deployed yet. -func (di *DeploymentItem) postprocessCRDs(k *k8s.K8sCluster) error { +func (di *DeploymentItem) postprocessCRDs() error { if di.dir == nil { return nil } @@ -430,7 +433,7 @@ func (di *DeploymentItem) postprocessCRDs(k *k8s.K8sCluster) error { continue } - err := k.Resources.UpdateResourcesFromCRD(o) + err := di.ctx.K.Resources.UpdateResourcesFromCRD(o) if err != nil { return err } @@ -438,7 +441,7 @@ func (di *DeploymentItem) postprocessCRDs(k *k8s.K8sCluster) error { return nil } -func (di *DeploymentItem) postprocessObjects(k *k8s.K8sCluster, images *Images) error { +func (di *DeploymentItem) postprocessObjects(images *Images) error { if di.dir == nil { return nil } @@ -451,8 +454,8 @@ func (di *DeploymentItem) postprocessObjects(k *k8s.K8sCluster, images *Images) commonAnnotations := di.getCommonAnnotations() _ = k8s.UnwrapListItems(o, true, func(o *uo.UnstructuredObject) error { - if k != nil { - k.Resources.FixNamespace(o, "default") + if di.ctx.K != nil { + di.ctx.K.Resources.FixNamespace(o, "default") } // Set common labels/annotations @@ -460,7 +463,7 @@ func (di *DeploymentItem) postprocessObjects(k *k8s.K8sCluster, images *Images) o.SetK8sAnnotations(uo.CopyMergeStrMap(o.GetK8sAnnotations(), commonAnnotations)) // Resolve image placeholders - err := images.ResolvePlaceholders(k, o, di.relRenderedDir, di.Tags.ListKeys()) + err := images.ResolvePlaceholders(di.ctx.K, o, di.relRenderedDir, di.Tags.ListKeys()) if err != nil { errList = append(errList, err) } diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go index 17e5e306c..96985f625 100644 --- a/pkg/deployment/deployment_project.go +++ b/pkg/deployment/deployment_project.go @@ -1,9 +1,7 @@ package deployment import ( - "context" "fmt" - "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" @@ -14,16 +12,12 @@ import ( ) type DeploymentProject struct { - ctx context.Context + ctx SharedContext - varsLoader *vars.VarsLoader - VarsCtx *vars.VarsCtx + VarsCtx *vars.VarsCtx dir string - SealedSecretsDir string - DefaultSealedSecretsOutputPattern string - Config types.DeploymentProjectConfig includes map[int]*DeploymentProject @@ -32,15 +26,13 @@ type DeploymentProject struct { parentProjectInclude *types.DeploymentItemConfig } -func NewDeploymentProject(ctx context.Context, k *k8s.K8sCluster, varsLoader *vars.VarsLoader, varsCtx *vars.VarsCtx, dir string, sealedSecretsDir string, parentProject *DeploymentProject) (*DeploymentProject, error) { +func NewDeploymentProject(ctx SharedContext, varsCtx *vars.VarsCtx, dir string, parentProject *DeploymentProject) (*DeploymentProject, error) { dp := &DeploymentProject{ - ctx: ctx, - varsLoader: varsLoader, - VarsCtx: varsCtx.Copy(), - dir: dir, - SealedSecretsDir: sealedSecretsDir, - parentProject: parentProject, - includes: map[int]*DeploymentProject{}, + ctx: ctx, + VarsCtx: varsCtx.Copy(), + dir: dir, + parentProject: parentProject, + includes: map[int]*DeploymentProject{}, } if !utils.IsDirectory(dir) { @@ -52,7 +44,7 @@ func NewDeploymentProject(ctx context.Context, k *k8s.K8sCluster, varsLoader *va return nil, fmt.Errorf("failed to load deployment config for %s: %w", dir, err) } - err = dp.loadIncludes(k) + err = dp.loadIncludes() if err != nil { return nil, fmt.Errorf("failed to load includes for %s: %w", dir, err) } @@ -61,7 +53,7 @@ func NewDeploymentProject(ctx context.Context, k *k8s.K8sCluster, varsLoader *va } func (p *DeploymentProject) loadVarsList(varsCtx *vars.VarsCtx, varsList []*types.VarsSource) error { - return p.varsLoader.LoadVarsList(varsCtx, varsList, p.getRenderSearchDirs(), "") + return p.ctx.VarsLoader.LoadVarsList(varsCtx, varsList, p.getRenderSearchDirs(), "") } func (p *DeploymentProject) loadConfig() error { @@ -154,7 +146,7 @@ func (p *DeploymentProject) checkDeploymentDirs() error { return nil } -func (p *DeploymentProject) loadIncludes(k *k8s.K8sCluster) error { +func (p *DeploymentProject) loadIncludes() error { for i, inc := range p.Config.Deployments { if inc.Include == nil { continue @@ -162,7 +154,7 @@ func (p *DeploymentProject) loadIncludes(k *k8s.K8sCluster) error { incDir := filepath.Join(p.dir, *inc.Include) - newProject, err := p.loadLocalInclude(k, incDir, inc.Vars) + newProject, err := p.loadLocalInclude(incDir, inc.Vars) if err != nil { return err } @@ -173,14 +165,14 @@ func (p *DeploymentProject) loadIncludes(k *k8s.K8sCluster) error { return nil } -func (p *DeploymentProject) loadLocalInclude(k *k8s.K8sCluster, incDir string, vars []*types.VarsSource) (*DeploymentProject, error) { +func (p *DeploymentProject) loadLocalInclude(incDir string, vars []*types.VarsSource) (*DeploymentProject, error) { varsCtx := p.VarsCtx.Copy() err := p.loadVarsList(varsCtx, vars) if err != nil { return nil, err } - newProject, err := NewDeploymentProject(p.ctx, k, p.varsLoader, varsCtx, incDir, p.SealedSecretsDir, p) + newProject, err := NewDeploymentProject(p.ctx, varsCtx, incDir, p) if err != nil { return nil, err } @@ -191,7 +183,7 @@ func (p *DeploymentProject) loadLocalInclude(k *k8s.K8sCluster, incDir string, v func (p *DeploymentProject) getSealedSecretsDir() string { root := p.getRootProject() if root.Config.SealedSecrets == nil || root.Config.SealedSecrets.OutputPattern == nil { - return root.DefaultSealedSecretsOutputPattern + return p.ctx.DefaultSealedSecretsOutputPattern } return *root.Config.SealedSecrets.OutputPattern } diff --git a/pkg/deployment/shared_context.go b/pkg/deployment/shared_context.go new file mode 100644 index 000000000..9ee8dd233 --- /dev/null +++ b/pkg/deployment/shared_context.go @@ -0,0 +1,17 @@ +package deployment + +import ( + "context" + "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/vars" +) + +type SharedContext struct { + Ctx context.Context + K *k8s.K8sCluster + VarsLoader *vars.VarsLoader + + RenderDir string + SealedSecretsDir string + DefaultSealedSecretsOutputPattern string +} diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index d42151d32..79fccfc7d 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -17,10 +17,11 @@ import ( ) type TargetContext struct { + SharedContext deployment.SharedContext + KluctlProject *LoadedKluctlProject Target *types.Target ClusterContext string - K *k8s.K8sCluster DeploymentProject *deployment.DeploymentProject DeploymentCollection *deployment.DeploymentCollection } @@ -73,23 +74,30 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName s } } - d, err := deployment.NewDeploymentProject(ctx, k, varsLoader, varsCtx, deploymentDir, p.sealedSecretsDir, nil) + dctx := deployment.SharedContext{ + Ctx: ctx, + K: k, + VarsLoader: varsLoader, + RenderDir: renderOutputDir, + SealedSecretsDir: p.sealedSecretsDir, + DefaultSealedSecretsOutputPattern: targetName, + } + + d, err := deployment.NewDeploymentProject(dctx, varsCtx, deploymentDir, nil) if err != nil { return nil, err } - d.DefaultSealedSecretsOutputPattern = targetName - - c, err := deployment.NewDeploymentCollection(ctx, d, images, inclusion, renderOutputDir, forSeal) + c, err := deployment.NewDeploymentCollection(dctx, d, images, inclusion, forSeal) if err != nil { return nil, err } targetCtx := &TargetContext{ + SharedContext: dctx, KluctlProject: p, Target: target, ClusterContext: clusterContext, - K: k, DeploymentProject: d, DeploymentCollection: c, } From a3efdc0e703384adc665918e966f646ac7313d67 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 24 May 2022 10:15:41 +0200 Subject: [PATCH 0871/2916] refactor: Store rootDir, relDir and absDir in DeploymentProject --- pkg/deployment/deployment_collection.go | 2 +- pkg/deployment/deployment_item.go | 20 ++----- pkg/deployment/deployment_project.go | 75 ++++++++++++------------- pkg/kluctl_project/target_context.go | 2 +- 4 files changed, 43 insertions(+), 56 deletions(-) diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index d25503c77..04b5c77a0 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -59,7 +59,7 @@ func findDeploymentItemIndex(project *DeploymentProject, pth *string, indexes ma } var dir2 *string index := 0 - dir := filepath.Join(project.dir, *pth) + dir := filepath.Join(project.absDir, *pth) absDir, err := filepath.Abs(dir) if err != nil { // we pre-checked directories, so this should not happen diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index b07ff59a9..457998d63 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -36,7 +36,6 @@ type DeploymentItem struct { Objects []*uo.UnstructuredObject Tags *utils.OrderedMap - relProjectDir string RelToRootItemDir string RelToProjectItemDir string relRenderedDir string @@ -60,20 +59,13 @@ func NewDeploymentItem(ctx SharedContext, project *DeploymentProject, collection di.Tags = di.Project.getTags() di.Tags.SetMultiple(di.Config.Tags, true) - rootProject := di.Project.getRootProject() - - di.relProjectDir, err = filepath.Rel(rootProject.dir, di.Project.dir) - if err != nil { - return nil, err - } - if di.dir != nil { - di.RelToRootItemDir, err = filepath.Rel(rootProject.dir, *di.dir) + di.RelToRootItemDir, err = filepath.Rel(di.Project.rootDir, *di.dir) if err != nil { return nil, err } - di.RelToProjectItemDir, err = filepath.Rel(di.relProjectDir, di.RelToRootItemDir) + di.RelToProjectItemDir, err = filepath.Rel(di.Project.relDir, di.RelToRootItemDir) if err != nil { return nil, err } @@ -115,8 +107,6 @@ func (di *DeploymentItem) render(forSeal bool, wp *utils.WorkerPoolWithErrors) e return nil } - rootDir := di.Project.getRootProject().dir - err := os.MkdirAll(di.renderedDir, 0o700) if err != nil { return err @@ -157,7 +147,7 @@ func (di *DeploymentItem) render(forSeal bool, wp *utils.WorkerPoolWithErrors) e } wp.Submit(func() error { - return varsCtx.RenderDirectory(rootDir, di.Project.getRenderSearchDirs(), di.relProjectDir, excludePatterns, di.RelToProjectItemDir, di.renderedDir) + return varsCtx.RenderDirectory(di.Project.rootDir, di.Project.getRenderSearchDirs(), di.Project.relDir, excludePatterns, di.RelToProjectItemDir, di.renderedDir) }) return nil @@ -220,7 +210,7 @@ func (di *DeploymentItem) resolveSealedSecrets(subdir string) error { renderedDir := filepath.Join(di.renderedDir, subdir) // ensure we're not leaving the project - _, err := securejoin.SecureJoin(di.Project.getRootProject().dir, subdir) + _, err := securejoin.SecureJoin(di.Project.rootDir, subdir) if err != nil { return err } @@ -251,7 +241,7 @@ func (di *DeploymentItem) resolveSealedSecrets(subdir string) error { } fname := filepath.Base(p) - baseError := fmt.Sprintf("failed to resolve SealedSecret %s", filepath.Clean(filepath.Join(di.Project.dir, resource))) + baseError := fmt.Sprintf("failed to resolve SealedSecret %s", filepath.Clean(filepath.Join(di.Project.absDir, resource))) // ensure we're not leaving the .sealed-secrets dir sourcePath, err := securejoin.SecureJoin(baseSourcePath, filepath.Join(di.relRenderedDir, subdir, relDir, sealedSecretsDir, fname)) diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go index 96985f625..e125d54b9 100644 --- a/pkg/deployment/deployment_project.go +++ b/pkg/deployment/deployment_project.go @@ -2,6 +2,7 @@ package deployment import ( "fmt" + securejoin "github.com/cyphar/filepath-securejoin" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" @@ -16,7 +17,9 @@ type DeploymentProject struct { VarsCtx *vars.VarsCtx - dir string + rootDir string + relDir string + absDir string Config types.DeploymentProjectConfig @@ -26,20 +29,28 @@ type DeploymentProject struct { parentProjectInclude *types.DeploymentItemConfig } -func NewDeploymentProject(ctx SharedContext, varsCtx *vars.VarsCtx, dir string, parentProject *DeploymentProject) (*DeploymentProject, error) { +func NewDeploymentProject(ctx SharedContext, varsCtx *vars.VarsCtx, rootDir string, relDir string, parentProject *DeploymentProject) (*DeploymentProject, error) { dp := &DeploymentProject{ ctx: ctx, VarsCtx: varsCtx.Copy(), - dir: dir, + rootDir: rootDir, + relDir: relDir, parentProject: parentProject, includes: map[int]*DeploymentProject{}, } + dir, err := securejoin.SecureJoin(dp.rootDir, dp.relDir) + if err != nil { + return nil, err + } + if !utils.IsDirectory(dir) { return nil, fmt.Errorf("%s does not exist or is not a directory", dir) } - err := dp.loadConfig() + dp.absDir = dir + + err = dp.loadConfig() if err != nil { return nil, fmt.Errorf("failed to load deployment config for %s: %w", dir, err) } @@ -57,15 +68,15 @@ func (p *DeploymentProject) loadVarsList(varsCtx *vars.VarsCtx, varsList []*type } func (p *DeploymentProject) loadConfig() error { - configPath := filepath.Join(p.dir, "deployment.yml") + configPath := filepath.Join(p.absDir, "deployment.yml") if !yaml.Exists(configPath) { - if yaml.Exists(filepath.Join(p.dir, "kustomization.yml")) { - return fmt.Errorf("deployment.yml not found but folder %s contains a kustomization.yml", p.dir) + if yaml.Exists(filepath.Join(p.absDir, "kustomization.yml")) { + return fmt.Errorf("deployment.yml not found but folder %s contains a kustomization.yml", p.absDir) } - return fmt.Errorf("%s not found", p.dir) + return fmt.Errorf("%s not found", configPath) } - err := p.VarsCtx.RenderYamlFile(yaml.FixNameExt(p.dir, "deployment.yml"), p.getRenderSearchDirs(), &p.Config) + err := p.VarsCtx.RenderYamlFile(yaml.FixNameExt(p.absDir, "deployment.yml"), p.getRenderSearchDirs(), &p.Config) if err != nil { return fmt.Errorf("failed to load deployment.yml: %w", err) } @@ -104,41 +115,28 @@ func (p *DeploymentProject) loadConfig() error { } func (p *DeploymentProject) checkDeploymentDirs() error { - rootProject := p.getRootProject() for _, di := range p.Config.Deployments { - if di.Path == nil && di.Include == nil { + if di.Path == nil { continue } - var pth string - if di.Path != nil { - pth = *di.Path - } else { - pth = *di.Include - } - - diDir := filepath.Join(p.dir, pth) - diDir, err := filepath.Abs(diDir) + diDir, err := securejoin.SecureJoin(p.absDir, *di.Path) if err != nil { return err } - if !strings.HasPrefix(diDir, rootProject.dir) { - return fmt.Errorf("path/include is not part of root deployment project: %s", pth) + if !strings.HasPrefix(diDir, p.rootDir) { + return fmt.Errorf("path/include is not part of the deployment project: %s", *di.Path) } if !utils.Exists(diDir) { - return fmt.Errorf("deployment directory does not exist: %s", pth) + return fmt.Errorf("deployment directory does not exist: %s", *di.Path) } if !utils.IsDirectory(diDir) { - return fmt.Errorf("deployment path is not a directory: %s", pth) + return fmt.Errorf("deployment path is not a directory: %s", *di.Path) } - if di.Path != nil { - pth = yaml.FixPathExt(filepath.Join(diDir, "kustomization.yml")) - } else { - pth = yaml.FixPathExt(filepath.Join(diDir, "deployment.yml")) - } + pth := yaml.FixPathExt(filepath.Join(diDir, "kustomization.yml")) if !utils.IsFile(pth) { return fmt.Errorf("%s not found or not a file", pth) } @@ -152,9 +150,7 @@ func (p *DeploymentProject) loadIncludes() error { continue } - incDir := filepath.Join(p.dir, *inc.Include) - - newProject, err := p.loadLocalInclude(incDir, inc.Vars) + newProject, err := p.loadLocalInclude(p.rootDir, filepath.Join(p.relDir, *inc.Include), inc.Vars) if err != nil { return err } @@ -165,14 +161,14 @@ func (p *DeploymentProject) loadIncludes() error { return nil } -func (p *DeploymentProject) loadLocalInclude(incDir string, vars []*types.VarsSource) (*DeploymentProject, error) { +func (p *DeploymentProject) loadLocalInclude(rootDir string, incDir string, vars []*types.VarsSource) (*DeploymentProject, error) { varsCtx := p.VarsCtx.Copy() err := p.loadVarsList(varsCtx, vars) if err != nil { return nil, err } - newProject, err := NewDeploymentProject(p.ctx, varsCtx, incDir, p) + newProject, err := NewDeploymentProject(p.ctx, varsCtx, rootDir, incDir, p) if err != nil { return nil, err } @@ -181,11 +177,12 @@ func (p *DeploymentProject) loadLocalInclude(incDir string, vars []*types.VarsSo } func (p *DeploymentProject) getSealedSecretsDir() string { - root := p.getRootProject() - if root.Config.SealedSecrets == nil || root.Config.SealedSecrets.OutputPattern == nil { - return p.ctx.DefaultSealedSecretsOutputPattern + for _, x := range p.getParents() { + if x.p.Config.SealedSecrets != nil && x.p.Config.SealedSecrets.OutputPattern != nil { + return *x.p.Config.SealedSecrets.OutputPattern + } } - return *root.Config.SealedSecrets.OutputPattern + return p.ctx.DefaultSealedSecretsOutputPattern } func (p *DeploymentProject) getRootProject() *DeploymentProject { @@ -229,7 +226,7 @@ func (p *DeploymentProject) getChildren(recursive bool, includeSelf bool) []*Dep func (p *DeploymentProject) getRenderSearchDirs() []string { var ret []string for _, d := range p.getParents() { - ret = append(ret, d.p.dir) + ret = append(ret, d.p.absDir) } return ret } diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index 79fccfc7d..c04bdd73b 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -83,7 +83,7 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName s DefaultSealedSecretsOutputPattern: targetName, } - d, err := deployment.NewDeploymentProject(dctx, varsCtx, deploymentDir, nil) + d, err := deployment.NewDeploymentProject(dctx, varsCtx, deploymentDir, ".", nil) if err != nil { return nil, err } From ad0be5ca8b5253f95593e5e834fa1e685ae904f4 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 24 May 2022 10:41:54 +0200 Subject: [PATCH 0872/2916] refactor: Rename getSealedSecretsDir to getRenderedOutputPattern --- pkg/deployment/deployment_item.go | 2 +- pkg/deployment/deployment_project.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 457998d63..8f2afbb25 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -204,7 +204,7 @@ func (di *DeploymentItem) resolveSealedSecrets(subdir string) error { return nil } - sealedSecretsDir := di.Project.getSealedSecretsDir() + sealedSecretsDir := di.Project.getRenderedOutputPattern() baseSourcePath := di.Project.ctx.SealedSecretsDir renderedDir := filepath.Join(di.renderedDir, subdir) diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go index e125d54b9..92a022fac 100644 --- a/pkg/deployment/deployment_project.go +++ b/pkg/deployment/deployment_project.go @@ -176,7 +176,7 @@ func (p *DeploymentProject) loadLocalInclude(rootDir string, incDir string, vars return newProject, nil } -func (p *DeploymentProject) getSealedSecretsDir() string { +func (p *DeploymentProject) getRenderedOutputPattern() string { for _, x := range p.getParents() { if x.p.Config.SealedSecrets != nil && x.p.Config.SealedSecrets.OutputPattern != nil { return *x.p.Config.SealedSecrets.OutputPattern From d158b94fb48cc98c360acd060a8bc0abaa99a399 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 24 May 2022 11:19:49 +0200 Subject: [PATCH 0873/2916] feat: Support including deployment projects from git --- pkg/deployment/deployment_collection.go | 2 +- pkg/deployment/deployment_project.go | 25 ++++++++--- pkg/deployment/shared_context.go | 2 + pkg/git/repo_collection.go | 58 ++++++++++++++++++++++++- pkg/kluctl_project/target_context.go | 1 + pkg/types/deployment.go | 15 ++++++- 6 files changed, 91 insertions(+), 12 deletions(-) diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index 04b5c77a0..698dba2bf 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -79,7 +79,7 @@ func (c *DeploymentCollection) collectDeployments(project *DeploymentProject, in var ret []*DeploymentItem for i, diConfig := range project.Config.Deployments { - if diConfig.Include != nil { + if diConfig.Include != nil || diConfig.Git != nil { includedProject, ok := project.includes[i] if !ok { panic(fmt.Sprintf("Did not find find index %d in project.includes", i)) diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go index 92a022fac..f95deb25d 100644 --- a/pkg/deployment/deployment_project.go +++ b/pkg/deployment/deployment_project.go @@ -146,15 +146,26 @@ func (p *DeploymentProject) checkDeploymentDirs() error { func (p *DeploymentProject) loadIncludes() error { for i, inc := range p.Config.Deployments { - if inc.Include == nil { + var err error + var newProject *DeploymentProject + + if inc.Include != nil { + newProject, err = p.loadLocalInclude(p.rootDir, filepath.Join(p.relDir, *inc.Include), inc.Vars) + if err != nil { + return err + } + } else if inc.Git != nil { + cloneDir, err := p.ctx.GRC.GetClonedDir(inc.Git.Url, inc.Git.Ref, true, true, true) + if err != nil { + return err + } + newProject, err = p.loadLocalInclude(cloneDir, inc.Git.SubDir, inc.Vars) + if err != nil { + return err + } + } else { continue } - - newProject, err := p.loadLocalInclude(p.rootDir, filepath.Join(p.relDir, *inc.Include), inc.Vars) - if err != nil { - return err - } - newProject.parentProjectInclude = inc p.includes[i] = newProject } diff --git a/pkg/deployment/shared_context.go b/pkg/deployment/shared_context.go index 9ee8dd233..59d0ebb07 100644 --- a/pkg/deployment/shared_context.go +++ b/pkg/deployment/shared_context.go @@ -2,6 +2,7 @@ package deployment import ( "context" + "github.com/kluctl/kluctl/v2/pkg/git" "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/vars" ) @@ -9,6 +10,7 @@ import ( type SharedContext struct { Ctx context.Context K *k8s.K8sCluster + GRC *git.MirroredGitRepoCollection VarsLoader *vars.VarsLoader RenderDir string diff --git a/pkg/git/repo_collection.go b/pkg/git/repo_collection.go index 0269bf69f..dd31d890f 100644 --- a/pkg/git/repo_collection.go +++ b/pkg/git/repo_collection.go @@ -5,6 +5,11 @@ import ( "fmt" "github.com/kluctl/kluctl/v2/pkg/git/auth" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" + "github.com/kluctl/kluctl/v2/pkg/utils" + "io/ioutil" + "os" + "path" + "path/filepath" "sync" "time" ) @@ -19,6 +24,7 @@ type MirroredGitRepoCollection struct { type entry struct { mr *MirroredGitRepo + clonedDirs map[string]string updateMutex sync.Mutex } @@ -36,6 +42,10 @@ func (g *MirroredGitRepoCollection) Clear() { defer g.mutex.Unlock() for _, e := range g.repos { + for _, path := range e.clonedDirs { + _ = os.RemoveAll(path) + } + if e.mr.IsLocked() { _ = e.mr.Unlock() } @@ -44,7 +54,7 @@ func (g *MirroredGitRepoCollection) Clear() { g.repos = map[string]*entry{} } -func (g *MirroredGitRepoCollection) GetMirroredGitRepo(url git_url.GitUrl, allowCreate bool, lockRepo bool, update bool) (*MirroredGitRepo, error) { +func (g *MirroredGitRepoCollection) getEntry(url git_url.GitUrl, allowCreate bool, lockRepo bool, update bool) (*entry, error) { e, err := func() (*entry, error) { g.mutex.Lock() defer g.mutex.Unlock() @@ -59,7 +69,8 @@ func (g *MirroredGitRepoCollection) GetMirroredGitRepo(url git_url.GitUrl, allow return nil, err } e = &entry{ - mr: mr, + mr: mr, + clonedDirs: map[string]string{}, } g.repos[url.NormalizedRepoKey()] = e @@ -90,5 +101,48 @@ func (g *MirroredGitRepoCollection) GetMirroredGitRepo(url git_url.GitUrl, allow } } + return e, nil +} + +func (g *MirroredGitRepoCollection) GetMirroredGitRepo(url git_url.GitUrl, allowCreate bool, lockRepo bool, update bool) (*MirroredGitRepo, error) { + e, err := g.getEntry(url, allowCreate, lockRepo, update) + if err != nil { + return nil, err + } return e.mr, nil } + +func (g *MirroredGitRepoCollection) GetClonedDir(url git_url.GitUrl, ref string, allowCreate bool, lockRepo bool, update bool) (string, error) { + e, err := g.getEntry(url, allowCreate, lockRepo, update) + if err != nil { + return "", err + } + + e.updateMutex.Lock() + defer e.updateMutex.Unlock() + + p, ok := e.clonedDirs[ref] + if ok { + return p, nil + } + + tmpDir := filepath.Join(utils.GetTmpBaseDir(), "git-cloned") + err = os.MkdirAll(tmpDir, 0700) + if err != nil { + return "", err + } + + repoName := path.Base(url.Normalize().Path) + p, err = ioutil.TempDir(tmpDir, repoName+"-"+ref+"-") + if err != nil { + return "", err + } + + err = e.mr.CloneProject(ref, p) + if err != nil { + return "", err + } + + e.clonedDirs[ref] = p + return p, nil +} diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index c04bdd73b..c0db4b7e9 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -77,6 +77,7 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName s dctx := deployment.SharedContext{ Ctx: ctx, K: k, + GRC: p.GRC, VarsLoader: varsLoader, RenderDir: renderOutputDir, SealedSecretsDir: p.sealedSecretsDir, diff --git a/pkg/types/deployment.go b/pkg/types/deployment.go index b9db6bb96..6f2ccd6fa 100644 --- a/pkg/types/deployment.go +++ b/pkg/types/deployment.go @@ -8,6 +8,7 @@ import ( type DeploymentItemConfig struct { Path *string `yaml:"path,omitempty"` Include *string `yaml:"include,omitempty"` + Git *GitProject `yaml:"git,omitempty"` Tags []string `yaml:"tags,omitempty"` Barrier bool `yaml:"barrier,omitempty"` WaitReadiness bool `yaml:"waitReadiness,omitempty"` @@ -20,8 +21,18 @@ type DeploymentItemConfig struct { func ValidateDeploymentItemConfig(sl validator.StructLevel) { s := sl.Current().Interface().(DeploymentItemConfig) - if s.Path != nil && s.Include != nil { - sl.ReportError(s, "path", "Path", "path and include can not be set at the same time", "") + cnt := 0 + if s.Path != nil { + cnt += 1 + } + if s.Include != nil { + cnt += 1 + } + if s.Git != nil { + cnt += 1 + } + if cnt > 1 { + sl.ReportError(s, "self", "self", "only one of path, include and git can be set at the same time", "") } if s.Path == nil && s.WaitReadiness { sl.ReportError(s, "waitReadiness", "WaitReadiness", "only kustomize deployments are allowed to have waitReadiness set", "") From 8b2b600504c54f0c054ae1ad3f1823d850ec12e8 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 24 May 2022 11:41:19 +0200 Subject: [PATCH 0874/2916] fix: Introduce Source object in deployment projects and render per source id This fixes issues where multiple included git projects would render into the same directory. --- cmd/kluctl/commands/completion.go | 2 +- pkg/deployment/deployment_item.go | 18 ++++++++--------- pkg/deployment/deployment_project.go | 26 ++++++++++++++----------- pkg/deployment/source.go | 29 ++++++++++++++++++++++++++++ pkg/kluctl_project/target_context.go | 2 +- pkg/utils/fs.go | 4 ++++ 6 files changed, 59 insertions(+), 22 deletions(-) create mode 100644 pkg/deployment/source.go diff --git a/cmd/kluctl/commands/completion.go b/cmd/kluctl/commands/completion.go index 5b94c0964..3c8ae9e37 100644 --- a/cmd/kluctl/commands/completion.go +++ b/cmd/kluctl/commands/completion.go @@ -154,7 +154,7 @@ func buildInclusionCompletionFunc(cmdStruct interface{}, forDirs bool) func(cmd defer mutex.Unlock() for _, di := range ctx.targetCtx.DeploymentCollection.Deployments { tags.Merge(di.Tags) - deploymentItemDirs.Set(di.RelToRootItemDir, true) + deploymentItemDirs.Set(di.RelToSourceItemDir, true) } return nil }) diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 8f2afbb25..f0e89f5ff 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -36,7 +36,7 @@ type DeploymentItem struct { Objects []*uo.UnstructuredObject Tags *utils.OrderedMap - RelToRootItemDir string + RelToSourceItemDir string RelToProjectItemDir string relRenderedDir string renderedDir string @@ -60,22 +60,22 @@ func NewDeploymentItem(ctx SharedContext, project *DeploymentProject, collection di.Tags.SetMultiple(di.Config.Tags, true) if di.dir != nil { - di.RelToRootItemDir, err = filepath.Rel(di.Project.rootDir, *di.dir) + di.RelToSourceItemDir, err = filepath.Rel(di.Project.source.dir, *di.dir) if err != nil { return nil, err } - di.RelToProjectItemDir, err = filepath.Rel(di.Project.relDir, di.RelToRootItemDir) + di.RelToProjectItemDir, err = filepath.Rel(di.Project.relDir, di.RelToSourceItemDir) if err != nil { return nil, err } - di.relRenderedDir = di.RelToRootItemDir + di.relRenderedDir = di.RelToSourceItemDir if di.index != 0 { di.relRenderedDir = fmt.Sprintf("%s-%d", di.relRenderedDir, di.index) } - di.renderedDir = filepath.Join(collection.ctx.RenderDir, di.relRenderedDir) + di.renderedDir = filepath.Join(collection.ctx.RenderDir, di.Project.source.id, di.relRenderedDir) di.renderedYamlPath = filepath.Join(di.renderedDir, ".rendered.yml") } return di, nil @@ -94,7 +94,7 @@ func (di *DeploymentItem) getCommonLabels() map[string]string { func (di *DeploymentItem) getCommonAnnotations() map[string]string { // TODO change it to kluctl.io/deployment_dir a := map[string]string{ - "kluctl.io/kustomize_dir": filepath.ToSlash(di.RelToRootItemDir), + "kluctl.io/kustomize_dir": filepath.ToSlash(di.RelToSourceItemDir), } if di.Config.SkipDeleteIfTags { a["kluctl.io/skip-delete-if-tags"] = "true" @@ -147,7 +147,7 @@ func (di *DeploymentItem) render(forSeal bool, wp *utils.WorkerPoolWithErrors) e } wp.Submit(func() error { - return varsCtx.RenderDirectory(di.Project.rootDir, di.Project.getRenderSearchDirs(), di.Project.relDir, excludePatterns, di.RelToProjectItemDir, di.renderedDir) + return varsCtx.RenderDirectory(di.Project.source.dir, di.Project.getRenderSearchDirs(), di.Project.relDir, excludePatterns, di.RelToProjectItemDir, di.renderedDir) }) return nil @@ -210,7 +210,7 @@ func (di *DeploymentItem) resolveSealedSecrets(subdir string) error { renderedDir := filepath.Join(di.renderedDir, subdir) // ensure we're not leaving the project - _, err := securejoin.SecureJoin(di.Project.rootDir, subdir) + err := utils.CheckSubInDir(di.Project.source.dir, subdir) if err != nil { return err } @@ -271,7 +271,7 @@ func (di *DeploymentItem) buildInclusionEntries() []utils.InclusionEntry { values = append(values, utils.InclusionEntry{Type: "tag", Value: t}) } if di.dir != nil { - dir := filepath.ToSlash(di.RelToRootItemDir) + dir := filepath.ToSlash(di.RelToSourceItemDir) values = append(values, utils.InclusionEntry{Type: "deploymentItemDir", Value: dir}) } return values diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go index f95deb25d..b814ad864 100644 --- a/pkg/deployment/deployment_project.go +++ b/pkg/deployment/deployment_project.go @@ -17,9 +17,9 @@ type DeploymentProject struct { VarsCtx *vars.VarsCtx - rootDir string - relDir string - absDir string + source Source + relDir string + absDir string Config types.DeploymentProjectConfig @@ -29,17 +29,17 @@ type DeploymentProject struct { parentProjectInclude *types.DeploymentItemConfig } -func NewDeploymentProject(ctx SharedContext, varsCtx *vars.VarsCtx, rootDir string, relDir string, parentProject *DeploymentProject) (*DeploymentProject, error) { +func NewDeploymentProject(ctx SharedContext, varsCtx *vars.VarsCtx, source Source, relDir string, parentProject *DeploymentProject) (*DeploymentProject, error) { dp := &DeploymentProject{ ctx: ctx, VarsCtx: varsCtx.Copy(), - rootDir: rootDir, + source: source, relDir: relDir, parentProject: parentProject, includes: map[int]*DeploymentProject{}, } - dir, err := securejoin.SecureJoin(dp.rootDir, dp.relDir) + dir, err := securejoin.SecureJoin(dp.source.dir, dp.relDir) if err != nil { return nil, err } @@ -125,7 +125,7 @@ func (p *DeploymentProject) checkDeploymentDirs() error { return err } - if !strings.HasPrefix(diDir, p.rootDir) { + if !strings.HasPrefix(diDir, p.source.dir) { return fmt.Errorf("path/include is not part of the deployment project: %s", *di.Path) } @@ -150,7 +150,7 @@ func (p *DeploymentProject) loadIncludes() error { var newProject *DeploymentProject if inc.Include != nil { - newProject, err = p.loadLocalInclude(p.rootDir, filepath.Join(p.relDir, *inc.Include), inc.Vars) + newProject, err = p.loadLocalInclude(p.source, filepath.Join(p.relDir, *inc.Include), inc.Vars) if err != nil { return err } @@ -159,7 +159,7 @@ func (p *DeploymentProject) loadIncludes() error { if err != nil { return err } - newProject, err = p.loadLocalInclude(cloneDir, inc.Git.SubDir, inc.Vars) + newProject, err = p.loadLocalInclude(NewSource(cloneDir), inc.Git.SubDir, inc.Vars) if err != nil { return err } @@ -172,14 +172,14 @@ func (p *DeploymentProject) loadIncludes() error { return nil } -func (p *DeploymentProject) loadLocalInclude(rootDir string, incDir string, vars []*types.VarsSource) (*DeploymentProject, error) { +func (p *DeploymentProject) loadLocalInclude(source Source, incDir string, vars []*types.VarsSource) (*DeploymentProject, error) { varsCtx := p.VarsCtx.Copy() err := p.loadVarsList(varsCtx, vars) if err != nil { return nil, err } - newProject, err := NewDeploymentProject(p.ctx, varsCtx, rootDir, incDir, p) + newProject, err := NewDeploymentProject(p.ctx, varsCtx, source, incDir, p) if err != nil { return nil, err } @@ -237,6 +237,10 @@ func (p *DeploymentProject) getChildren(recursive bool, includeSelf bool) []*Dep func (p *DeploymentProject) getRenderSearchDirs() []string { var ret []string for _, d := range p.getParents() { + if d.p.source != p.source { + // only allow searching inside same source project + continue + } ret = append(ret, d.p.absDir) } return ret diff --git a/pkg/deployment/source.go b/pkg/deployment/source.go new file mode 100644 index 000000000..0792ea8b3 --- /dev/null +++ b/pkg/deployment/source.go @@ -0,0 +1,29 @@ +package deployment + +import ( + "fmt" + "sync" +) + +type Source struct { + id string + dir string +} + +func NewSource(dir string) Source { + return Source{ + id: getNextSourceId(), + dir: dir, + } +} + +var nextSourceId int +var nextSourceIdMutex sync.Mutex + +func getNextSourceId() string { + nextSourceIdMutex.Lock() + defer nextSourceIdMutex.Unlock() + id := fmt.Sprintf("%d", nextSourceId) + nextSourceId += 1 + return id +} diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index c0db4b7e9..b8a103e7d 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -84,7 +84,7 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName s DefaultSealedSecretsOutputPattern: targetName, } - d, err := deployment.NewDeploymentProject(dctx, varsCtx, deploymentDir, ".", nil) + d, err := deployment.NewDeploymentProject(dctx, varsCtx, deployment.NewSource(deploymentDir), ".", nil) if err != nil { return nil, err } diff --git a/pkg/utils/fs.go b/pkg/utils/fs.go index ac567cdb8..0b25e1e4b 100644 --- a/pkg/utils/fs.go +++ b/pkg/utils/fs.go @@ -57,6 +57,10 @@ func CheckInDir(root string, path string) error { return nil } +func CheckSubInDir(root string, subDir string) error { + return CheckInDir(root, filepath.Join(root, subDir)) +} + func Touch(path string) error { f, err := os.Create(path) if err != nil { From cef51fae878e40d2d2e74b66e3a57a8d1ce93009 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 24 May 2022 11:47:37 +0200 Subject: [PATCH 0875/2916] feat: Deprecate external projects and warn about future removal --- cmd/kluctl/args/project.go | 8 ++++---- pkg/kluctl_project/project_load.go | 17 +++++++++++++++-- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/cmd/kluctl/args/project.go b/cmd/kluctl/args/project.go index f1b35c3fb..c9f9de378 100644 --- a/cmd/kluctl/args/project.go +++ b/cmd/kluctl/args/project.go @@ -7,13 +7,13 @@ type ProjectFlags struct { ProjectRef string `group:"project" short:"b" help:"Git ref of the kluctl project. Only used when --project-url was given."` ProjectConfig existingFileType `group:"project" short:"c" help:"Location of the .kluctl.yaml config file. Defaults to $PROJECT/.kluctl.yaml" exts:"yml,yaml"` - LocalClusters existingDirType `group:"project" help:"Local clusters directory. Overrides the project from .kluctl.yaml"` - LocalDeployment existingDirType `group:"project" help:"Local deployment directory. Overrides the project from .kluctl.yaml"` - LocalSealedSecrets existingDirType `group:"project" help:"Local sealed-secrets directory. Overrides the project from .kluctl.yaml" ` + LocalClusters existingDirType `group:"project" help:"DEPRECATED. Local clusters directory. Overrides the project from .kluctl.yaml"` + LocalDeployment existingDirType `group:"project" help:"DEPRECATED. Local deployment directory. Overrides the project from .kluctl.yaml"` + LocalSealedSecrets existingDirType `group:"project" help:"DEPRECATED. Local sealed-secrets directory. Overrides the project from .kluctl.yaml" ` FromArchive existingPathType `group:"project" help:"Load project (.kluctl.yaml, cluster, ...) from archive. Given path can either be an archive file or a directory with the extracted contents." exts:"tar.gz,tgz"` FromArchiveMetadata existingFileType `group:"project" help:"Specify where to load metadata (targets, ...) from. If not specified, metadata is assumed to be part of the archive." exts:"yml,yaml"` OutputMetadata string `group:"project" help:"Specify the output path for the project metadata to be written to. When used with the 'archive' command, it will also cause the archive to not include the metadata.yaml file."` - Cluster string `group:"project" help:"Specify/Override cluster"` + Cluster string `group:"project" help:"DEPRECATED. Specify/Override cluster"` Timeout time.Duration `group:"project" help:"Specify timeout for all operations, including loading of the project, all external api calls and waiting for readiness." default:"10m"` GitCacheUpdateInterval time.Duration `group:"project" help:"Specify the time to wait between git cache updates. Defaults to not wait at all and always updating caches."` diff --git a/pkg/kluctl_project/project_load.go b/pkg/kluctl_project/project_load.go index f241a485d..bbab0983b 100644 --- a/pkg/kluctl_project/project_load.go +++ b/pkg/kluctl_project/project_load.go @@ -131,6 +131,11 @@ func (c *LoadedKluctlProject) loadExternalProject(ep *types2.ExternalProject, de } if ep.Project != nil { + c.warnOnce.Do("external-projects", func() { + status.Warning(c.ctx, "External projects are deprecated and support for them will be removed in the future. "+ + "Use Git variable sources as replacement for cluster configs and Git includes as replacement for external deployment projects.") + }) + // pointing to an actual external project, so let's try to clone it return c.loadGitProject(ep.Project, defaultGitSubDir, true) } @@ -192,6 +197,16 @@ func (c *LoadedKluctlProject) loadKluctlProject() error { s := status.Start(c.ctx, "Loading kluctl project") defer s.Failed() + if c.loadArgs.LocalClusters != "" { + status.Warning(c.ctx, "--local-clusters is deprecated and will be removed in an upcoming version. Use variables loaded from git instead.") + } + if c.loadArgs.LocalDeployment != "" { + status.Warning(c.ctx, "--local-deployment is deprecated and will be removed in an upcoming version. Use git includes instead.") + } + if c.loadArgs.LocalSealedSecrets != "" { + status.Warning(c.ctx, "--local-sealed-secrets is deprecated and will be removed in an upcoming version.") + } + deploymentInfo, err := c.loadExternalProject(c.Config.Deployment, "", c.loadArgs.LocalDeployment) if err != nil { return err @@ -202,10 +217,8 @@ func (c *LoadedKluctlProject) loadKluctlProject() error { } var clustersInfos []gitProjectInfo if c.loadArgs.LocalClusters != "" { - status.Warning(c.ctx, "--local-clusters is deprecated and will be removed in an upcoming version. Use variables loaded from git instead.") clustersInfos = append(clustersInfos, c.localProject(c.loadArgs.LocalClusters)) } else if len(c.Config.Clusters.Projects) != 0 { - status.Warning(c.ctx, "'clusters' is deprecated and will be removed in an upcoming version. Use variables loaded from git instead.") for _, ep := range c.Config.Clusters.Projects { info, err := c.loadExternalProject(&ep, "clusters", "") if err != nil { From 2e821a272c149dea34461f1130ed2ac89412dc87 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 24 May 2022 11:54:10 +0200 Subject: [PATCH 0876/2916] fix: Mark --output-archive as required flag --- cmd/kluctl/commands/cmd_archive.go | 2 +- cmd/kluctl/commands/cobra_utils.go | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/cmd/kluctl/commands/cmd_archive.go b/cmd/kluctl/commands/cmd_archive.go index d5c22af07..4e8fe72bc 100644 --- a/cmd/kluctl/commands/cmd_archive.go +++ b/cmd/kluctl/commands/cmd_archive.go @@ -9,7 +9,7 @@ import ( type archiveCmd struct { args.ProjectFlags - OutputArchive string `group:"misc" help:"Path to .tgz to write project to." type:"path"` + OutputArchive string `group:"misc" help:"Path to .tgz to write project to." type:"path" required:"true"` } func (cmd *archiveCmd) Help() string { diff --git a/cmd/kluctl/commands/cobra_utils.go b/cmd/kluctl/commands/cobra_utils.go index 493b15605..b8fae0c44 100644 --- a/cmd/kluctl/commands/cobra_utils.go +++ b/cmd/kluctl/commands/cobra_utils.go @@ -143,6 +143,7 @@ func (c *rootCommand) buildCobraArg(cg *commandAndGroups, f reflect.StructField, help := f.Tag.Get("help") shortFlag := f.Tag.Get("short") defaultValue := f.Tag.Get("default") + required := f.Tag.Get("required") == "true" group := f.Tag.Get("group") if group != "" { @@ -208,6 +209,10 @@ func (c *rootCommand) buildCobraArg(cg *commandAndGroups, f reflect.StructField, return fmt.Errorf("unknown type %s", f.Type.Name()) } + if required { + _ = cg.cmd.MarkPersistentFlagRequired(name) + } + return nil } From ba98984e8c9dd9fd7bfb9d85acfe3cf8c1237702 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 24 May 2022 12:16:53 +0200 Subject: [PATCH 0877/2916] refactor: Use GRC when cloning kluctl project related repos --- pkg/deployment/deployment_project.go | 2 +- pkg/git/repo_collection.go | 19 +++++++---- pkg/kluctl_project/git.go | 49 ---------------------------- pkg/kluctl_project/project_load.go | 49 +++++----------------------- pkg/kluctl_project/targets.go | 4 ++- 5 files changed, 25 insertions(+), 98 deletions(-) diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go index b814ad864..93290a708 100644 --- a/pkg/deployment/deployment_project.go +++ b/pkg/deployment/deployment_project.go @@ -155,7 +155,7 @@ func (p *DeploymentProject) loadIncludes() error { return err } } else if inc.Git != nil { - cloneDir, err := p.ctx.GRC.GetClonedDir(inc.Git.Url, inc.Git.Ref, true, true, true) + cloneDir, _, err := p.ctx.GRC.GetClonedDir(inc.Git.Url, inc.Git.Ref, true, true, true) if err != nil { return err } diff --git a/pkg/git/repo_collection.go b/pkg/git/repo_collection.go index dd31d890f..095b7ffb8 100644 --- a/pkg/git/repo_collection.go +++ b/pkg/git/repo_collection.go @@ -112,10 +112,10 @@ func (g *MirroredGitRepoCollection) GetMirroredGitRepo(url git_url.GitUrl, allow return e.mr, nil } -func (g *MirroredGitRepoCollection) GetClonedDir(url git_url.GitUrl, ref string, allowCreate bool, lockRepo bool, update bool) (string, error) { +func (g *MirroredGitRepoCollection) GetClonedDir(url git_url.GitUrl, ref string, allowCreate bool, lockRepo bool, update bool) (string, GitRepoInfo, error) { e, err := g.getEntry(url, allowCreate, lockRepo, update) if err != nil { - return "", err + return "", GitRepoInfo{}, err } e.updateMutex.Lock() @@ -123,26 +123,31 @@ func (g *MirroredGitRepoCollection) GetClonedDir(url git_url.GitUrl, ref string, p, ok := e.clonedDirs[ref] if ok { - return p, nil + return p, GitRepoInfo{}, nil } tmpDir := filepath.Join(utils.GetTmpBaseDir(), "git-cloned") err = os.MkdirAll(tmpDir, 0700) if err != nil { - return "", err + return "", GitRepoInfo{}, err } repoName := path.Base(url.Normalize().Path) p, err = ioutil.TempDir(tmpDir, repoName+"-"+ref+"-") if err != nil { - return "", err + return "", GitRepoInfo{}, err } err = e.mr.CloneProject(ref, p) if err != nil { - return "", err + return "", GitRepoInfo{}, err + } + + repoInfo, err := GetGitRepoInfo(p) + if err != nil { + return "", GitRepoInfo{}, err } e.clonedDirs[ref] = p - return p, nil + return p, repoInfo, nil } diff --git a/pkg/kluctl_project/git.go b/pkg/kluctl_project/git.go index 3d3a2584a..11c409846 100644 --- a/pkg/kluctl_project/git.go +++ b/pkg/kluctl_project/git.go @@ -2,15 +2,10 @@ package kluctl_project import ( "fmt" - "github.com/kluctl/kluctl/v2/pkg/git" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" types2 "github.com/kluctl/kluctl/v2/pkg/types" - "github.com/kluctl/kluctl/v2/pkg/utils" - "os" - "path/filepath" "reflect" "sort" - "strings" "sync" ) @@ -81,50 +76,6 @@ func (c *LoadedKluctlProject) updateGitCaches() error { return firstError } -func (c *LoadedKluctlProject) cloneGitProject(gitProject *types2.GitProject, targetDir string) (info git.GitRepoInfo, err error) { - err = os.MkdirAll(filepath.Join(c.TmpDir, "git"), 0o700) - if err != nil { - return - } - - mr, err := c.GRC.GetMirroredGitRepo(gitProject.Url, true, true, true) - if err != nil { - return git.GitRepoInfo{}, err - } - - err = mr.CloneProject(gitProject.Ref, targetDir) - if err != nil { - return - } - - info, err = git.GetGitRepoInfo(targetDir) - return -} - -func (c *LoadedKluctlProject) buildCloneDir(u git_url.GitUrl, ref string) (string, error) { - baseDir := c.TmpDir - if c.archiveDir != "" { - baseDir = c.archiveDir - } - baseDir = filepath.Join(baseDir, "git") - - if ref == "" { - ref = "HEAD" - } - ref = strings.ReplaceAll(ref, "/", "-") - ref = strings.ReplaceAll(ref, "\\", "-") - urlPath := filepath.FromSlash(u.Path) - baseName := filepath.Base(urlPath) - urlHash := utils.Sha256String(fmt.Sprintf("%s:%s", u.Host, u.Path))[:16] - cloneDir := filepath.Join(fmt.Sprintf("%s-%s", baseName, urlHash), ref) - cloneDir = filepath.Join(baseDir, cloneDir) - err := utils.CheckInDir(baseDir, cloneDir) - if err != nil { - return "", err - } - return cloneDir, nil -} - func (c *LoadedKluctlProject) addInvolvedRepo(u git_url.GitUrl, refPattern string, refs map[string]string) { repoKey := u.NormalizedRepoKey() irs, _ := c.involvedRepos[repoKey] diff --git a/pkg/kluctl_project/project_load.go b/pkg/kluctl_project/project_load.go index bbab0983b..9a0f231cd 100644 --- a/pkg/kluctl_project/project_load.go +++ b/pkg/kluctl_project/project_load.go @@ -59,51 +59,20 @@ func (c *LoadedKluctlProject) localProject(dir string) gitProjectInfo { } func (c *LoadedKluctlProject) loadGitProject(gitProject *types2.GitProject, defaultSubDir string, doAddInvolvedRepo bool) (ret gitProjectInfo, err error) { - cloneDir, err := c.buildCloneDir(gitProject.Url, gitProject.Ref) + cloneDir, ri, err := c.GRC.GetClonedDir(gitProject.Url, gitProject.Ref, c.loadArgs.AllowGitClone, true, true) if err != nil { return } - if c.archiveDir != "" { - var md types2.GitRepoMetadata - err = yaml.ReadYamlFile(filepath.Join(cloneDir, ".git-metadata.yaml"), &md) - if err != nil { - return - } - ret.url = gitProject.Url - ret.ref = gitProject.Ref - ret.commit = md.Commit - ret.repoRoot = cloneDir - } else { - if !c.loadArgs.AllowGitClone { - err = fmt.Errorf("tried to load an external project from git, which is not allowed") - return - } - - var ri git.GitRepoInfo - ri, err = c.cloneGitProject(gitProject, cloneDir) - if err != nil { - return - } + ret.url = gitProject.Url + ret.ref = ri.CheckedOutRef + ret.commit = ri.CheckedOutCommit + ret.repoRoot = cloneDir - var md types2.GitRepoMetadata - md.Ref = ri.CheckedOutRef - md.Commit = ri.CheckedOutCommit - err = yaml.WriteYamlFile(filepath.Join(cloneDir, ".git-metadata.yaml"), &md) - if err != nil { - return gitProjectInfo{}, err - } - - ret.url = gitProject.Url - ret.ref = ri.CheckedOutRef - ret.commit = ri.CheckedOutCommit - ret.repoRoot = cloneDir - - if doAddInvolvedRepo { - c.addInvolvedRepo(ret.url, ret.ref, map[string]string{ - ret.ref: ret.commit, - }) - } + if doAddInvolvedRepo { + c.addInvolvedRepo(ret.url, ret.ref, map[string]string{ + ret.ref: ret.commit, + }) } subDir := gitProject.SubDir diff --git a/pkg/kluctl_project/targets.go b/pkg/kluctl_project/targets.go index d8a20bcef..b760c01d1 100644 --- a/pkg/kluctl_project/targets.go +++ b/pkg/kluctl_project/targets.go @@ -178,7 +178,7 @@ func (c *LoadedKluctlProject) prepareDynamicTargetsExternal(baseTarget *types.Ta continue } - cloneDir, err := c.buildCloneDir(baseTarget.TargetConfig.Project.Url, refShortName) + cloneDir, _, err := c.GRC.GetClonedDir(baseTarget.TargetConfig.Project.Url, refShortName, false, false, false) if err != nil { return nil, err } @@ -236,7 +236,9 @@ func (c *LoadedKluctlProject) cloneDynamicTargets(dynamicTargets []*dynamicTarge if _, ok := uniqueClones[targetInfo.dir]; ok { continue } + mutex.Lock() uniqueClones[targetInfo.dir] = nil + mutex.Unlock() wp.Submit(func() error { gitProject := *targetInfo.gitProject From 99ed27feed0c6b94c616371f74a6e164be528ddf Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 24 May 2022 13:24:04 +0200 Subject: [PATCH 0878/2916] feat: Get rid of involvedRepos from archive metadata --- pkg/kluctl_project/git.go | 25 ------------------ pkg/kluctl_project/load.go | 3 --- pkg/kluctl_project/project.go | 5 +--- pkg/kluctl_project/project_archive.go | 1 - pkg/kluctl_project/project_load.go | 12 +++------ pkg/kluctl_project/targets.go | 37 +-------------------------- pkg/types/metadata.go | 13 +--------- 7 files changed, 6 insertions(+), 90 deletions(-) diff --git a/pkg/kluctl_project/git.go b/pkg/kluctl_project/git.go index 11c409846..c89e6405c 100644 --- a/pkg/kluctl_project/git.go +++ b/pkg/kluctl_project/git.go @@ -4,8 +4,6 @@ import ( "fmt" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" types2 "github.com/kluctl/kluctl/v2/pkg/types" - "reflect" - "sort" "sync" ) @@ -75,26 +73,3 @@ func (c *LoadedKluctlProject) updateGitCaches() error { waitGroup.Wait() return firstError } - -func (c *LoadedKluctlProject) addInvolvedRepo(u git_url.GitUrl, refPattern string, refs map[string]string) { - repoKey := u.NormalizedRepoKey() - irs, _ := c.involvedRepos[repoKey] - e := &types2.InvolvedRepo{ - RefPattern: refPattern, - Refs: refs, - } - found := false - for _, ir := range irs { - if reflect.DeepEqual(ir, e) { - found = true - break - } - } - if !found { - c.involvedRepos[repoKey] = append(c.involvedRepos[repoKey], *e) - s := c.involvedRepos[repoKey] - sort.SliceStable(s, func(i, j int) bool { - return s[i].RefPattern < s[j].RefPattern - }) - } -} diff --git a/pkg/kluctl_project/load.go b/pkg/kluctl_project/load.go index 801d66f6d..609d15796 100644 --- a/pkg/kluctl_project/load.go +++ b/pkg/kluctl_project/load.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "github.com/kluctl/kluctl/v2/pkg/jinja2" - "github.com/kluctl/kluctl/v2/pkg/types" ) func LoadKluctlProject(ctx context.Context, args LoadKluctlProjectArgs, tmpDir string, j2 *jinja2.Jinja2) (*LoadedKluctlProject, error) { @@ -14,8 +13,6 @@ func LoadKluctlProject(ctx context.Context, args LoadKluctlProjectArgs, tmpDir s TmpDir: tmpDir, J2: j2, GRC: args.GRC, - - involvedRepos: map[string][]types.InvolvedRepo{}, } if args.FromArchive != "" { diff --git a/pkg/kluctl_project/project.go b/pkg/kluctl_project/project.go index bb59ce9b2..a3b197dd7 100644 --- a/pkg/kluctl_project/project.go +++ b/pkg/kluctl_project/project.go @@ -25,8 +25,6 @@ type LoadedKluctlProject struct { ClustersDir string sealedSecretsDir string - involvedRepos map[string][]types2.InvolvedRepo - Config types2.KluctlProject DynamicTargets []*types2.DynamicTarget @@ -38,8 +36,7 @@ type LoadedKluctlProject struct { func (c *LoadedKluctlProject) GetMetadata() *types2.ProjectMetadata { md := &types2.ProjectMetadata{ - InvolvedRepos: c.involvedRepos, - Targets: c.DynamicTargets, + Targets: c.DynamicTargets, } return md } diff --git a/pkg/kluctl_project/project_archive.go b/pkg/kluctl_project/project_archive.go index d9e94f053..dd80a258e 100644 --- a/pkg/kluctl_project/project_archive.go +++ b/pkg/kluctl_project/project_archive.go @@ -57,7 +57,6 @@ func (c *LoadedKluctlProject) loadFromArchive() error { } c.archiveDir = dir - c.involvedRepos = pmd.InvolvedRepos c.DynamicTargets = pmd.Targets err = c.loadKluctlProject() diff --git a/pkg/kluctl_project/project_load.go b/pkg/kluctl_project/project_load.go index 9a0f231cd..fba29c43c 100644 --- a/pkg/kluctl_project/project_load.go +++ b/pkg/kluctl_project/project_load.go @@ -58,7 +58,7 @@ func (c *LoadedKluctlProject) localProject(dir string) gitProjectInfo { } } -func (c *LoadedKluctlProject) loadGitProject(gitProject *types2.GitProject, defaultSubDir string, doAddInvolvedRepo bool) (ret gitProjectInfo, err error) { +func (c *LoadedKluctlProject) loadGitProject(gitProject *types2.GitProject, defaultSubDir string) (ret gitProjectInfo, err error) { cloneDir, ri, err := c.GRC.GetClonedDir(gitProject.Url, gitProject.Ref, c.loadArgs.AllowGitClone, true, true) if err != nil { return @@ -69,12 +69,6 @@ func (c *LoadedKluctlProject) loadGitProject(gitProject *types2.GitProject, defa ret.commit = ri.CheckedOutCommit ret.repoRoot = cloneDir - if doAddInvolvedRepo { - c.addInvolvedRepo(ret.url, ret.ref, map[string]string{ - ret.ref: ret.commit, - }) - } - subDir := gitProject.SubDir if subDir == "" { subDir = defaultSubDir @@ -106,7 +100,7 @@ func (c *LoadedKluctlProject) loadExternalProject(ep *types2.ExternalProject, de }) // pointing to an actual external project, so let's try to clone it - return c.loadGitProject(ep.Project, defaultGitSubDir, true) + return c.loadGitProject(ep.Project, defaultGitSubDir) } // ExternalProject was provided but without an external repo url, so point into the kluctl project. @@ -134,7 +128,7 @@ func (c *LoadedKluctlProject) loadKluctlProject() error { gi, err := c.loadGitProject(&types2.GitProject{ Url: *c.loadArgs.ProjectUrl, Ref: c.loadArgs.ProjectRef, - }, "", true) + }, "") if err != nil { return err } diff --git a/pkg/kluctl_project/targets.go b/pkg/kluctl_project/targets.go index b760c01d1..5a4e0b1f1 100644 --- a/pkg/kluctl_project/targets.go +++ b/pkg/kluctl_project/targets.go @@ -3,7 +3,6 @@ package kluctl_project import ( "fmt" securejoin "github.com/cyphar/filepath-securejoin" - git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" @@ -244,7 +243,7 @@ func (c *LoadedKluctlProject) cloneDynamicTargets(dynamicTargets []*dynamicTarge gitProject := *targetInfo.gitProject gitProject.Ref = *targetInfo.ref - gi, err := c.loadGitProject(&gitProject, "", false) + gi, err := c.loadGitProject(&gitProject, "") mutex.Lock() defer mutex.Unlock() if err != nil { @@ -260,40 +259,6 @@ func (c *LoadedKluctlProject) cloneDynamicTargets(dynamicTargets []*dynamicTarge return err } - refsByUrlAndPattern := make(map[string]map[string]map[string]string) - for _, targetInfo := range dynamicTargets { - if targetInfo.gitProject == nil { - continue - } - o, ok := uniqueClones[targetInfo.dir] - if !ok { - return fmt.Errorf("%s not in uniqueClones. This is probably a bug", targetInfo.dir) - } - err, ok := o.(error) - if ok { - return err - } - info := o.(*gitProjectInfo) - normalizedUrl := info.url.Normalize().String() - if _, ok := refsByUrlAndPattern[normalizedUrl]; !ok { - refsByUrlAndPattern[normalizedUrl] = make(map[string]map[string]string) - } - if _, ok := refsByUrlAndPattern[normalizedUrl][*targetInfo.refPattern]; !ok { - refsByUrlAndPattern[normalizedUrl][*targetInfo.refPattern] = make(map[string]string) - } - refsByUrlAndPattern[normalizedUrl][*targetInfo.refPattern][info.ref] = info.commit - } - - for url, refPatterns := range refsByUrlAndPattern { - for refPattern, refs := range refPatterns { - u, err := git_url.Parse(url) - if err != nil { - return err - } - c.addInvolvedRepo(*u, refPattern, refs) - } - } - return nil } diff --git a/pkg/types/metadata.go b/pkg/types/metadata.go index 0742a92ad..028510789 100644 --- a/pkg/types/metadata.go +++ b/pkg/types/metadata.go @@ -1,18 +1,7 @@ package types -type InvolvedRepo struct { - RefPattern string `yaml:"refPattern"` - Refs map[string]string `yaml:"refs"` -} - type ProjectMetadata struct { - InvolvedRepos map[string][]InvolvedRepo `yaml:"involvedRepos"` - Targets []*DynamicTarget `yaml:"targets"` -} - -type GitRepoMetadata struct { - Ref string `yaml:"ref"` - Commit string `yaml:"commit"` + Targets []*DynamicTarget `yaml:"targets"` } type ArchiveMetadata struct { From aeb26ecdc4088bdd74d31814adf405a143bb8224 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 24 May 2022 14:10:24 +0200 Subject: [PATCH 0879/2916] refactor: Introduce RepoProvider interface --- cmd/kluctl/commands/utils.go | 9 +- pkg/deployment/deployment_project.go | 2 +- pkg/deployment/shared_context.go | 4 +- pkg/git/mirrored_repo.go | 8 +- pkg/git/repo_collection.go | 153 ---------------------- pkg/git/repoprovider/live.go | 184 +++++++++++++++++++++++++++ pkg/git/repoprovider/repoprovider.go | 18 +++ pkg/git/utils.go | 8 +- pkg/kluctl_project/git.go | 2 +- pkg/kluctl_project/load.go | 2 +- pkg/kluctl_project/project.go | 6 +- pkg/kluctl_project/project_load.go | 6 +- pkg/kluctl_project/target_context.go | 4 +- pkg/kluctl_project/targets.go | 18 +-- pkg/vars/vars_loader.go | 21 ++- pkg/vars/vars_loader_test.go | 8 +- 16 files changed, 249 insertions(+), 204 deletions(-) delete mode 100644 pkg/git/repo_collection.go create mode 100644 pkg/git/repoprovider/live.go create mode 100644 pkg/git/repoprovider/repoprovider.go diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 12c89660b..a752a7b85 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -8,6 +8,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/git" "github.com/kluctl/kluctl/v2/pkg/git/auth" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" + "github.com/kluctl/kluctl/v2/pkg/git/repoprovider" "github.com/kluctl/kluctl/v2/pkg/jinja2" "github.com/kluctl/kluctl/v2/pkg/kluctl_project" "github.com/kluctl/kluctl/v2/pkg/registries" @@ -58,8 +59,8 @@ func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, strictTemplates b ctx, cancel := context.WithTimeout(cliCtx, projectFlags.Timeout) defer cancel() - grc := git.NewMirroredGitRepoCollection(ctx, auth.NewDefaultAuthProviders(), projectFlags.GitCacheUpdateInterval) - defer grc.Clear() + rp := repoprovider.NewLiveRepoProvider(ctx, auth.NewDefaultAuthProviders(), projectFlags.GitCacheUpdateInterval) + defer rp.Clear() loadArgs := kluctl_project.LoadKluctlProjectArgs{ RepoRoot: repoRoot, @@ -73,7 +74,7 @@ func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, strictTemplates b FromArchive: projectFlags.FromArchive.String(), FromArchiveMetadata: projectFlags.FromArchiveMetadata.String(), AllowGitClone: projectFlags.FromArchive == "", - GRC: grc, + RP: rp, ClientConfigGetter: clientConfigGetter(forCompletion), } @@ -185,7 +186,7 @@ func withProjectTargetCommandContext(ctx context.Context, args projectTargetComm } // we can assume that all git access is done at this point, so we can clear grc and thus unlock all repos - p.GRC.Clear() + p.RP.Clear() return cb(cmdCtx) } diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go index 93290a708..14b894813 100644 --- a/pkg/deployment/deployment_project.go +++ b/pkg/deployment/deployment_project.go @@ -155,7 +155,7 @@ func (p *DeploymentProject) loadIncludes() error { return err } } else if inc.Git != nil { - cloneDir, _, err := p.ctx.GRC.GetClonedDir(inc.Git.Url, inc.Git.Ref, true, true, true) + cloneDir, _, err := p.ctx.RP.GetClonedDir(inc.Git.Url, inc.Git.Ref) if err != nil { return err } diff --git a/pkg/deployment/shared_context.go b/pkg/deployment/shared_context.go index 59d0ebb07..a659a43e0 100644 --- a/pkg/deployment/shared_context.go +++ b/pkg/deployment/shared_context.go @@ -2,7 +2,7 @@ package deployment import ( "context" - "github.com/kluctl/kluctl/v2/pkg/git" + "github.com/kluctl/kluctl/v2/pkg/git/repoprovider" "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/vars" ) @@ -10,7 +10,7 @@ import ( type SharedContext struct { Ctx context.Context K *k8s.K8sCluster - GRC *git.MirroredGitRepoCollection + RP repoprovider.RepoProvider VarsLoader *vars.VarsLoader RenderDir string diff --git a/pkg/git/mirrored_repo.go b/pkg/git/mirrored_repo.go index 60abfe25e..83fc777e5 100644 --- a/pkg/git/mirrored_repo.go +++ b/pkg/git/mirrored_repo.go @@ -146,19 +146,19 @@ func (g *MirroredGitRepo) RemoteRefHashesMap() (map[string]string, error) { return refs, nil } -func (g *MirroredGitRepo) DefaultRef() *string { +func (g *MirroredGitRepo) DefaultRef() (string, error) { r, err := git.PlainOpen(g.mirrorDir) if err != nil { - return nil + return "", err } ref, err := r.Reference("HEAD", false) if err != nil { - return nil + return "", err } s := ref.Target().String() - return &s + return s, nil } func (g *MirroredGitRepo) buildRepositoryObject() (*git.Repository, error) { diff --git a/pkg/git/repo_collection.go b/pkg/git/repo_collection.go deleted file mode 100644 index 095b7ffb8..000000000 --- a/pkg/git/repo_collection.go +++ /dev/null @@ -1,153 +0,0 @@ -package git - -import ( - "context" - "fmt" - "github.com/kluctl/kluctl/v2/pkg/git/auth" - git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" - "github.com/kluctl/kluctl/v2/pkg/utils" - "io/ioutil" - "os" - "path" - "path/filepath" - "sync" - "time" -) - -type MirroredGitRepoCollection struct { - ctx context.Context - authProviders *auth.GitAuthProviders - updateInterval time.Duration - repos map[string]*entry - mutex sync.Mutex -} - -type entry struct { - mr *MirroredGitRepo - clonedDirs map[string]string - updateMutex sync.Mutex -} - -func NewMirroredGitRepoCollection(ctx context.Context, authProviders *auth.GitAuthProviders, updateInterval time.Duration) *MirroredGitRepoCollection { - return &MirroredGitRepoCollection{ - ctx: ctx, - authProviders: authProviders, - updateInterval: updateInterval, - repos: map[string]*entry{}, - } -} - -func (g *MirroredGitRepoCollection) Clear() { - g.mutex.Lock() - defer g.mutex.Unlock() - - for _, e := range g.repos { - for _, path := range e.clonedDirs { - _ = os.RemoveAll(path) - } - - if e.mr.IsLocked() { - _ = e.mr.Unlock() - } - } - - g.repos = map[string]*entry{} -} - -func (g *MirroredGitRepoCollection) getEntry(url git_url.GitUrl, allowCreate bool, lockRepo bool, update bool) (*entry, error) { - e, err := func() (*entry, error) { - g.mutex.Lock() - defer g.mutex.Unlock() - - e, ok := g.repos[url.NormalizedRepoKey()] - if !ok { - if !allowCreate { - return nil, fmt.Errorf("git repo %s not found", url.NormalizedRepoKey()) - } - mr, err := NewMirroredGitRepo(g.ctx, url) - if err != nil { - return nil, err - } - e = &entry{ - mr: mr, - clonedDirs: map[string]string{}, - } - g.repos[url.NormalizedRepoKey()] = e - - if lockRepo { - err = e.mr.Lock() - if err != nil { - return nil, err - } - } - } - return e, nil - }() - if err != nil { - return nil, err - } - - e.updateMutex.Lock() - defer e.updateMutex.Unlock() - - if update && !e.mr.HasUpdated() { - if time.Now().Sub(e.mr.LastUpdateTime()) <= g.updateInterval { - e.mr.SetUpdated(true) - } else { - err = e.mr.Update(g.authProviders) - if err != nil { - return nil, err - } - } - } - - return e, nil -} - -func (g *MirroredGitRepoCollection) GetMirroredGitRepo(url git_url.GitUrl, allowCreate bool, lockRepo bool, update bool) (*MirroredGitRepo, error) { - e, err := g.getEntry(url, allowCreate, lockRepo, update) - if err != nil { - return nil, err - } - return e.mr, nil -} - -func (g *MirroredGitRepoCollection) GetClonedDir(url git_url.GitUrl, ref string, allowCreate bool, lockRepo bool, update bool) (string, GitRepoInfo, error) { - e, err := g.getEntry(url, allowCreate, lockRepo, update) - if err != nil { - return "", GitRepoInfo{}, err - } - - e.updateMutex.Lock() - defer e.updateMutex.Unlock() - - p, ok := e.clonedDirs[ref] - if ok { - return p, GitRepoInfo{}, nil - } - - tmpDir := filepath.Join(utils.GetTmpBaseDir(), "git-cloned") - err = os.MkdirAll(tmpDir, 0700) - if err != nil { - return "", GitRepoInfo{}, err - } - - repoName := path.Base(url.Normalize().Path) - p, err = ioutil.TempDir(tmpDir, repoName+"-"+ref+"-") - if err != nil { - return "", GitRepoInfo{}, err - } - - err = e.mr.CloneProject(ref, p) - if err != nil { - return "", GitRepoInfo{}, err - } - - repoInfo, err := GetGitRepoInfo(p) - if err != nil { - return "", GitRepoInfo{}, err - } - - e.clonedDirs[ref] = p - return p, repoInfo, nil -} diff --git a/pkg/git/repoprovider/live.go b/pkg/git/repoprovider/live.go new file mode 100644 index 000000000..f1a67b490 --- /dev/null +++ b/pkg/git/repoprovider/live.go @@ -0,0 +1,184 @@ +package repoprovider + +import ( + "context" + "fmt" + "github.com/kluctl/kluctl/v2/pkg/git" + "github.com/kluctl/kluctl/v2/pkg/git/auth" + git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" + "github.com/kluctl/kluctl/v2/pkg/utils" + "io/ioutil" + "os" + "path" + "path/filepath" + "sync" + "time" +) + +type LiveRepoProvider struct { + ctx context.Context + authProviders *auth.GitAuthProviders + updateInterval time.Duration + repos map[string]*entry + mutex sync.Mutex +} + +type entry struct { + mr *git.MirroredGitRepo + clonedDirs map[string]clonedDir + updateMutex sync.Mutex +} + +type clonedDir struct { + dir string + info git.CheckoutInfo +} + +func NewLiveRepoProvider(ctx context.Context, authProviders *auth.GitAuthProviders, updateInterval time.Duration) RepoProvider { + return &LiveRepoProvider{ + ctx: ctx, + authProviders: authProviders, + updateInterval: updateInterval, + repos: map[string]*entry{}, + } +} + +func (rp *LiveRepoProvider) Clear() { + rp.mutex.Lock() + defer rp.mutex.Unlock() + + for _, e := range rp.repos { + for _, cd := range e.clonedDirs { + _ = os.RemoveAll(cd.dir) + } + + if e.mr.IsLocked() { + _ = e.mr.Unlock() + } + } + + rp.repos = map[string]*entry{} +} + +func (rp *LiveRepoProvider) getEntry(url git_url.GitUrl, allowCreate bool, lockRepo bool, update bool) (*entry, error) { + e, err := func() (*entry, error) { + rp.mutex.Lock() + defer rp.mutex.Unlock() + + e, ok := rp.repos[url.NormalizedRepoKey()] + if !ok { + if !allowCreate { + return nil, fmt.Errorf("git repo %s not found", url.NormalizedRepoKey()) + } + mr, err := git.NewMirroredGitRepo(rp.ctx, url) + if err != nil { + return nil, err + } + e = &entry{ + mr: mr, + clonedDirs: map[string]clonedDir{}, + } + rp.repos[url.NormalizedRepoKey()] = e + + if lockRepo { + err = e.mr.Lock() + if err != nil { + return nil, err + } + } + } + return e, nil + }() + if err != nil { + return nil, err + } + + e.updateMutex.Lock() + defer e.updateMutex.Unlock() + + if update && !e.mr.HasUpdated() { + if time.Now().Sub(e.mr.LastUpdateTime()) <= rp.updateInterval { + e.mr.SetUpdated(true) + } else { + err = e.mr.Update(rp.authProviders) + if err != nil { + return nil, err + } + } + } + + return e, nil +} + +func (rp *LiveRepoProvider) GetRepoInfo(url git_url.GitUrl) (RepoInfo, error) { + e, err := rp.getEntry(url, true, true, true) + if err != nil { + return RepoInfo{}, err + } + + remoteRefs, err := e.mr.RemoteRefHashesMap() + if err != nil { + return RepoInfo{}, err + } + + defaultRef, err := e.mr.DefaultRef() + if err != nil { + return RepoInfo{}, err + } + + info := RepoInfo{ + Url: url, + RemoteRefs: remoteRefs, + DefaultRef: defaultRef, + } + + return info, nil +} + +func (rp *LiveRepoProvider) GetClonedDir(url git_url.GitUrl, ref string) (string, git.CheckoutInfo, error) { + e, err := rp.getEntry(url, true, true, true) + if err != nil { + return "", git.CheckoutInfo{}, err + } + + e.updateMutex.Lock() + defer e.updateMutex.Unlock() + + cd, ok := e.clonedDirs[ref] + if ok { + return cd.dir, cd.info, nil + } + + tmpDir := filepath.Join(utils.GetTmpBaseDir(), "git-cloned") + err = os.MkdirAll(tmpDir, 0700) + if err != nil { + return "", git.CheckoutInfo{}, err + } + + repoName := path.Base(url.Normalize().Path) + "-" + if ref == "" { + repoName += "HEAD-" + } else { + repoName += ref + "-" + } + p, err := ioutil.TempDir(tmpDir, repoName) + if err != nil { + return "", git.CheckoutInfo{}, err + } + + err = e.mr.CloneProject(ref, p) + if err != nil { + return "", git.CheckoutInfo{}, err + } + + repoInfo, err := git.GetCheckoutInfo(p) + if err != nil { + return "", git.CheckoutInfo{}, err + } + + e.clonedDirs[ref] = clonedDir{ + dir: p, + info: repoInfo, + } + return p, repoInfo, nil +} diff --git a/pkg/git/repoprovider/repoprovider.go b/pkg/git/repoprovider/repoprovider.go new file mode 100644 index 000000000..77495df96 --- /dev/null +++ b/pkg/git/repoprovider/repoprovider.go @@ -0,0 +1,18 @@ +package repoprovider + +import ( + "github.com/kluctl/kluctl/v2/pkg/git" + git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" +) + +type RepoInfo struct { + Url git_url.GitUrl `yaml:"url"` + RemoteRefs map[string]string `yaml:"remoteRefs"` + DefaultRef string `yaml:"defaultRef"` +} + +type RepoProvider interface { + GetRepoInfo(url git_url.GitUrl) (RepoInfo, error) + GetClonedDir(url git_url.GitUrl, ref string) (string, git.CheckoutInfo, error) + Clear() +} diff --git a/pkg/git/utils.go b/pkg/git/utils.go index d3826b03d..0c0bb72d0 100644 --- a/pkg/git/utils.go +++ b/pkg/git/utils.go @@ -7,12 +7,12 @@ import ( "path/filepath" ) -type GitRepoInfo struct { - CheckedOutRef string - CheckedOutCommit string +type CheckoutInfo struct { + CheckedOutRef string `yaml:"checkedOutRef"` + CheckedOutCommit string `yaml:"checkedOutCommit"` } -func GetGitRepoInfo(path string) (ri GitRepoInfo, err error) { +func GetCheckoutInfo(path string) (ri CheckoutInfo, err error) { r, err := git.PlainOpen(path) if err != nil { return diff --git a/pkg/kluctl_project/git.go b/pkg/kluctl_project/git.go index c89e6405c..31e5678b8 100644 --- a/pkg/kluctl_project/git.go +++ b/pkg/kluctl_project/git.go @@ -25,7 +25,7 @@ func (c *LoadedKluctlProject) updateGitCaches() error { go func() { defer waitGroup.Done() - _, err := c.GRC.GetMirroredGitRepo(u, true, true, true) + _, err := c.RP.GetRepoInfo(u) if err != nil { doError(fmt.Errorf("failed to update git project %v: %v", u.String(), err)) } diff --git a/pkg/kluctl_project/load.go b/pkg/kluctl_project/load.go index 609d15796..8ea01b500 100644 --- a/pkg/kluctl_project/load.go +++ b/pkg/kluctl_project/load.go @@ -12,7 +12,7 @@ func LoadKluctlProject(ctx context.Context, args LoadKluctlProjectArgs, tmpDir s loadArgs: args, TmpDir: tmpDir, J2: j2, - GRC: args.GRC, + RP: args.RP, } if args.FromArchive != "" { diff --git a/pkg/kluctl_project/project.go b/pkg/kluctl_project/project.go index a3b197dd7..d19f515e8 100644 --- a/pkg/kluctl_project/project.go +++ b/pkg/kluctl_project/project.go @@ -3,7 +3,7 @@ package kluctl_project import ( "context" "fmt" - "github.com/kluctl/kluctl/v2/pkg/git" + "github.com/kluctl/kluctl/v2/pkg/git/repoprovider" "github.com/kluctl/kluctl/v2/pkg/jinja2" types2 "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" @@ -28,8 +28,8 @@ type LoadedKluctlProject struct { Config types2.KluctlProject DynamicTargets []*types2.DynamicTarget - J2 *jinja2.Jinja2 - GRC *git.MirroredGitRepoCollection + J2 *jinja2.Jinja2 + RP repoprovider.RepoProvider warnOnce utils.OnceByKey } diff --git a/pkg/kluctl_project/project_load.go b/pkg/kluctl_project/project_load.go index fba29c43c..77cba9795 100644 --- a/pkg/kluctl_project/project_load.go +++ b/pkg/kluctl_project/project_load.go @@ -2,8 +2,8 @@ package kluctl_project import ( "fmt" - "github.com/kluctl/kluctl/v2/pkg/git" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" + "github.com/kluctl/kluctl/v2/pkg/git/repoprovider" "github.com/kluctl/kluctl/v2/pkg/status" types2 "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" @@ -28,7 +28,7 @@ type LoadKluctlProjectArgs struct { FromArchiveMetadata string AllowGitClone bool - GRC *git.MirroredGitRepoCollection + RP repoprovider.RepoProvider ClientConfigGetter func(context *string) (*rest.Config, *api.Config, error) } @@ -59,7 +59,7 @@ func (c *LoadedKluctlProject) localProject(dir string) gitProjectInfo { } func (c *LoadedKluctlProject) loadGitProject(gitProject *types2.GitProject, defaultSubDir string) (ret gitProjectInfo, err error) { - cloneDir, ri, err := c.GRC.GetClonedDir(gitProject.Url, gitProject.Ref, c.loadArgs.AllowGitClone, true, true) + cloneDir, ri, err := c.RP.GetClonedDir(gitProject.Url, gitProject.Ref) if err != nil { return } diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index b8a103e7d..9237562fe 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -65,7 +65,7 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName s s.Success() } - varsLoader := vars.NewVarsLoader(ctx, k, p.GRC, aws.NewClientFactory()) + varsLoader := vars.NewVarsLoader(ctx, k, p.RP, aws.NewClientFactory()) if forSeal { err = p.loadSecrets(target, varsCtx, varsLoader) @@ -77,7 +77,7 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName s dctx := deployment.SharedContext{ Ctx: ctx, K: k, - GRC: p.GRC, + RP: p.RP, VarsLoader: varsLoader, RenderDir: renderOutputDir, SealedSecretsDir: p.sealedSecretsDir, diff --git a/pkg/kluctl_project/targets.go b/pkg/kluctl_project/targets.go index 5a4e0b1f1..828e4ec8c 100644 --- a/pkg/kluctl_project/targets.go +++ b/pkg/kluctl_project/targets.go @@ -133,7 +133,7 @@ func (c *LoadedKluctlProject) prepareDynamicTargetsSimple(baseTarget *types.Targ } func (c *LoadedKluctlProject) prepareDynamicTargetsExternal(baseTarget *types.Target) ([]*dynamicTargetInfo, error) { - mr, err := c.GRC.GetMirroredGitRepo(baseTarget.TargetConfig.Project.Url, true, true, true) + repoInfo, err := c.RP.GetRepoInfo(baseTarget.TargetConfig.Project.Url) if err != nil { return nil, err } @@ -145,21 +145,17 @@ func (c *LoadedKluctlProject) prepareDynamicTargetsExternal(baseTarget *types.Ta targetConfigRef := baseTarget.TargetConfig.Ref refPattern := baseTarget.TargetConfig.RefPattern - defaultBranch := mr.DefaultRef() - if defaultBranch == nil { + defaultBranch := repoInfo.DefaultRef + if defaultBranch == "" { return nil, fmt.Errorf("git project %v seems to have no default branch", baseTarget.TargetConfig.Project.Url.String()) } if baseTarget.TargetConfig.Ref == nil && baseTarget.TargetConfig.RefPattern == nil { // use default branch of repo - targetConfigRef = defaultBranch - } - - refs, err := mr.RemoteRefHashesMap() - if err != nil { - return nil, err + targetConfigRef = &defaultBranch } + refs := repoInfo.RemoteRefs if targetConfigRef != nil { if _, ok := refs[fmt.Sprintf("refs/heads/%s", *targetConfigRef)]; !ok { return nil, fmt.Errorf("git project %s has no ref %s", baseTarget.TargetConfig.Project.Url.String(), *targetConfigRef) @@ -177,7 +173,7 @@ func (c *LoadedKluctlProject) prepareDynamicTargetsExternal(baseTarget *types.Ta continue } - cloneDir, _, err := c.GRC.GetClonedDir(baseTarget.TargetConfig.Project.Url, refShortName, false, false, false) + cloneDir, _, err := c.RP.GetClonedDir(baseTarget.TargetConfig.Project.Url, refShortName) if err != nil { return nil, err } @@ -188,7 +184,7 @@ func (c *LoadedKluctlProject) prepareDynamicTargetsExternal(baseTarget *types.Ta gitProject: baseTarget.TargetConfig.Project, ref: &refShortName, refPattern: refPattern, - defaultBranch: *defaultBranch, + defaultBranch: defaultBranch, }) } return dynamicTargets, nil diff --git a/pkg/vars/vars_loader.go b/pkg/vars/vars_loader.go index 6e1310bee..94a221422 100644 --- a/pkg/vars/vars_loader.go +++ b/pkg/vars/vars_loader.go @@ -4,12 +4,12 @@ import ( "context" "encoding/base64" "fmt" - "github.com/kluctl/kluctl/v2/pkg/git" + securejoin "github.com/cyphar/filepath-securejoin" + "github.com/kluctl/kluctl/v2/pkg/git/repoprovider" "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" - "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/vars/aws" "github.com/kluctl/kluctl/v2/pkg/yaml" @@ -25,17 +25,17 @@ type usernamePassword struct { type VarsLoader struct { ctx context.Context k *k8s.K8sCluster - grc *git.MirroredGitRepoCollection + rp repoprovider.RepoProvider aws aws.AwsClientFactory credentialsCache map[string]usernamePassword } -func NewVarsLoader(ctx context.Context, k *k8s.K8sCluster, grc *git.MirroredGitRepoCollection, aws aws.AwsClientFactory) *VarsLoader { +func NewVarsLoader(ctx context.Context, k *k8s.K8sCluster, rp repoprovider.RepoProvider, aws aws.AwsClientFactory) *VarsLoader { return &VarsLoader{ ctx: ctx, k: k, - grc: grc, + rp: rp, aws: aws, credentialsCache: map[string]usernamePassword{}, } @@ -140,23 +140,22 @@ func (v *VarsLoader) loadAwsSecretsManager(varsCtx *VarsCtx, source *types.VarsS } func (v *VarsLoader) loadGit(varsCtx *VarsCtx, gitFile *types.VarsSourceGit, rootKey string) error { - mr, err := v.grc.GetMirroredGitRepo(gitFile.Url, true, true, true) + clonedDir, _, err := v.rp.GetClonedDir(gitFile.Url, gitFile.Ref) if err != nil { return fmt.Errorf("failed to load vars from git repository %s: %w", gitFile.Url.String(), err) } - tmpDir, err := ioutil.TempDir(utils.GetTmpBaseDir(), "git-vars") + path, err := securejoin.SecureJoin(clonedDir, gitFile.Path) if err != nil { return err } - defer os.RemoveAll(tmpDir) - file, err := mr.ReadFile(gitFile.Ref, gitFile.Path) + f, err := ioutil.ReadFile(path) if err != nil { - return fmt.Errorf("failed to load vars from git repository %s and path %s: %w", gitFile.Url.String(), gitFile.Path, err) + return err } - return v.loadFromString(varsCtx, string(file), rootKey) + return v.loadFromString(varsCtx, string(f), rootKey) } func (v *VarsLoader) loadFromK8sObject(varsCtx *VarsCtx, ref k8s2.ObjectRef, key string, rootKey string, base64Decode bool) error { diff --git a/pkg/vars/vars_loader_test.go b/pkg/vars/vars_loader_test.go index 763b6c1ae..da4f11531 100644 --- a/pkg/vars/vars_loader_test.go +++ b/pkg/vars/vars_loader_test.go @@ -5,9 +5,9 @@ import ( git2 "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" test_utils "github.com/kluctl/kluctl/v2/internal/test-utils" - "github.com/kluctl/kluctl/v2/pkg/git" "github.com/kluctl/kluctl/v2/pkg/git/auth" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" + "github.com/kluctl/kluctl/v2/pkg/git/repoprovider" "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" @@ -37,8 +37,8 @@ func newTestDir(t *testing.T) string { return tmp } -func newGRC(t *testing.T) *git.MirroredGitRepoCollection { - grc := git.NewMirroredGitRepoCollection(context.TODO(), auth.NewDefaultAuthProviders(), 0) +func newRP(t *testing.T) repoprovider.RepoProvider { + grc := repoprovider.NewMirroredGitRepoCollection(context.TODO(), auth.NewDefaultAuthProviders(), 0) t.Cleanup(func() { grc.Clear() }) @@ -50,7 +50,7 @@ func testVarsLoader(t *testing.T, test func(vl *VarsLoader, vc *VarsCtx, aws *aw if err != nil { t.Fatal(err) } - grc := newGRC(t) + grc := newRP(t) fakeAws := aws.NewFakeClientFactory() vl := NewVarsLoader(context.TODO(), k, grc, fakeAws) From 990da6d27843e98609ce5476d19a90ae8c3f6ab5 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 24 May 2022 16:40:00 +0200 Subject: [PATCH 0880/2916] feat: Remove archive support This turned out to be incompatible to git variables loading and git includes. --- cmd/kluctl/args/project.go | 14 ++- cmd/kluctl/commands/cmd_archive.go | 23 ----- cmd/kluctl/commands/root.go | 1 - cmd/kluctl/commands/utils.go | 29 +++--- pkg/kluctl_project/load.go | 29 ++---- pkg/kluctl_project/project.go | 3 +- pkg/kluctl_project/project_archive.go | 127 -------------------------- pkg/kluctl_project/project_load.go | 36 +++----- pkg/types/metadata.go | 5 - pkg/vars/vars_loader_test.go | 2 +- 10 files changed, 45 insertions(+), 224 deletions(-) delete mode 100644 cmd/kluctl/commands/cmd_archive.go delete mode 100644 pkg/kluctl_project/project_archive.go diff --git a/cmd/kluctl/args/project.go b/cmd/kluctl/args/project.go index c9f9de378..2639966fb 100644 --- a/cmd/kluctl/args/project.go +++ b/cmd/kluctl/args/project.go @@ -6,14 +6,12 @@ type ProjectFlags struct { ProjectUrl string `group:"project" short:"p" help:"Git url of the kluctl project. If not specified, the current directory will be used instead of a remote Git project"` ProjectRef string `group:"project" short:"b" help:"Git ref of the kluctl project. Only used when --project-url was given."` - ProjectConfig existingFileType `group:"project" short:"c" help:"Location of the .kluctl.yaml config file. Defaults to $PROJECT/.kluctl.yaml" exts:"yml,yaml"` - LocalClusters existingDirType `group:"project" help:"DEPRECATED. Local clusters directory. Overrides the project from .kluctl.yaml"` - LocalDeployment existingDirType `group:"project" help:"DEPRECATED. Local deployment directory. Overrides the project from .kluctl.yaml"` - LocalSealedSecrets existingDirType `group:"project" help:"DEPRECATED. Local sealed-secrets directory. Overrides the project from .kluctl.yaml" ` - FromArchive existingPathType `group:"project" help:"Load project (.kluctl.yaml, cluster, ...) from archive. Given path can either be an archive file or a directory with the extracted contents." exts:"tar.gz,tgz"` - FromArchiveMetadata existingFileType `group:"project" help:"Specify where to load metadata (targets, ...) from. If not specified, metadata is assumed to be part of the archive." exts:"yml,yaml"` - OutputMetadata string `group:"project" help:"Specify the output path for the project metadata to be written to. When used with the 'archive' command, it will also cause the archive to not include the metadata.yaml file."` - Cluster string `group:"project" help:"DEPRECATED. Specify/Override cluster"` + ProjectConfig existingFileType `group:"project" short:"c" help:"Location of the .kluctl.yaml config file. Defaults to $PROJECT/.kluctl.yaml" exts:"yml,yaml"` + LocalClusters existingDirType `group:"project" help:"DEPRECATED. Local clusters directory. Overrides the project from .kluctl.yaml"` + LocalDeployment existingDirType `group:"project" help:"DEPRECATED. Local deployment directory. Overrides the project from .kluctl.yaml"` + LocalSealedSecrets existingDirType `group:"project" help:"DEPRECATED. Local sealed-secrets directory. Overrides the project from .kluctl.yaml" ` + OutputMetadata string `group:"project" help:"Specify the output path for the project metadata to be written to."` + Cluster string `group:"project" help:"DEPRECATED. Specify/Override cluster"` Timeout time.Duration `group:"project" help:"Specify timeout for all operations, including loading of the project, all external api calls and waiting for readiness." default:"10m"` GitCacheUpdateInterval time.Duration `group:"project" help:"Specify the time to wait between git cache updates. Defaults to not wait at all and always updating caches."` diff --git a/cmd/kluctl/commands/cmd_archive.go b/cmd/kluctl/commands/cmd_archive.go deleted file mode 100644 index 4e8fe72bc..000000000 --- a/cmd/kluctl/commands/cmd_archive.go +++ /dev/null @@ -1,23 +0,0 @@ -package commands - -import ( - "context" - "github.com/kluctl/kluctl/v2/cmd/kluctl/args" - "github.com/kluctl/kluctl/v2/pkg/kluctl_project" -) - -type archiveCmd struct { - args.ProjectFlags - - OutputArchive string `group:"misc" help:"Path to .tgz to write project to." type:"path" required:"true"` -} - -func (cmd *archiveCmd) Help() string { - return `This archive can then be used with '--from-archive'` -} - -func (cmd *archiveCmd) Run() error { - return withKluctlProjectFromArgs(cmd.ProjectFlags, true, false, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { - return p.WriteArchive(cmd.OutputArchive, cmd.ProjectFlags.OutputMetadata == "") - }) -} diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index 9c8e70711..f71cd8595 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -42,7 +42,6 @@ type cli struct { Debug bool `group:"global" help:"Enable debug logging"` NoUpdateCheck bool `group:"global" help:"Disable update check on startup"` - Archive archiveCmd `cmd:"" help:"Write project and all related components into single tgz"` CheckImageUpdates checkImageUpdatesCmd `cmd:"" help:"Render deployment and check if any images have new tags available"` Delete deleteCmd `cmd:"" help:"Delete a target (or parts of it) from the corresponding cluster"` Deploy deployCmd `cmd:"" help:"Deploys a target to the corresponding cluster"` diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index a752a7b85..5ff04f003 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -52,7 +52,7 @@ func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, strictTemplates b } repoRoot, err := git.DetectGitRepositoryRoot(cwd) - if err != nil && projectFlags.FromArchive == "" { + if err != nil { status.Warning(cliCtx, "Failed to detect git project root. This might cause follow-up errors") } @@ -63,25 +63,26 @@ func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, strictTemplates b defer rp.Clear() loadArgs := kluctl_project.LoadKluctlProjectArgs{ - RepoRoot: repoRoot, - ProjectDir: cwd, - ProjectUrl: url, - ProjectRef: projectFlags.ProjectRef, - ProjectConfig: projectFlags.ProjectConfig.String(), - LocalClusters: projectFlags.LocalClusters.String(), - LocalDeployment: projectFlags.LocalDeployment.String(), - LocalSealedSecrets: projectFlags.LocalSealedSecrets.String(), - FromArchive: projectFlags.FromArchive.String(), - FromArchiveMetadata: projectFlags.FromArchiveMetadata.String(), - AllowGitClone: projectFlags.FromArchive == "", - RP: rp, - ClientConfigGetter: clientConfigGetter(forCompletion), + RepoRoot: repoRoot, + ProjectDir: cwd, + ProjectUrl: url, + ProjectRef: projectFlags.ProjectRef, + ProjectConfig: projectFlags.ProjectConfig.String(), + LocalClusters: projectFlags.LocalClusters.String(), + LocalDeployment: projectFlags.LocalDeployment.String(), + LocalSealedSecrets: projectFlags.LocalSealedSecrets.String(), + RP: rp, + ClientConfigGetter: clientConfigGetter(forCompletion), } p, err := kluctl_project.LoadKluctlProject(ctx, loadArgs, tmpDir, j2) + if p != nil && p.RP != nil { + defer p.RP.Clear() + } if err != nil { return err } + if projectFlags.OutputMetadata != "" { md := p.GetMetadata() b, err := yaml.WriteYamlBytes(md) diff --git a/pkg/kluctl_project/load.go b/pkg/kluctl_project/load.go index 8ea01b500..62c4f140d 100644 --- a/pkg/kluctl_project/load.go +++ b/pkg/kluctl_project/load.go @@ -2,11 +2,11 @@ package kluctl_project import ( "context" - "fmt" "github.com/kluctl/kluctl/v2/pkg/jinja2" ) func LoadKluctlProject(ctx context.Context, args LoadKluctlProjectArgs, tmpDir string, j2 *jinja2.Jinja2) (*LoadedKluctlProject, error) { + p := &LoadedKluctlProject{ ctx: ctx, loadArgs: args, @@ -15,24 +15,13 @@ func LoadKluctlProject(ctx context.Context, args LoadKluctlProjectArgs, tmpDir s RP: args.RP, } - if args.FromArchive != "" { - if args.ProjectUrl != nil || args.ProjectRef != "" || args.ProjectConfig != "" || args.LocalClusters != "" || args.LocalDeployment != "" || args.LocalSealedSecrets != "" { - return nil, fmt.Errorf("--from-archive can not be combined with any other project related option") - } - err := p.loadFromArchive() - if err != nil { - return nil, err - } - return p, nil - } else { - err := p.loadKluctlProject() - if err != nil { - return nil, err - } - err = p.loadTargets() - if err != nil { - return nil, err - } - return p, nil + err := p.loadKluctlProject() + if err != nil { + return nil, err + } + err = p.loadTargets() + if err != nil { + return nil, err } + return p, nil } diff --git a/pkg/kluctl_project/project.go b/pkg/kluctl_project/project.go index d19f515e8..ee616243a 100644 --- a/pkg/kluctl_project/project.go +++ b/pkg/kluctl_project/project.go @@ -15,8 +15,7 @@ type LoadedKluctlProject struct { loadArgs LoadKluctlProjectArgs - TmpDir string - archiveDir string + TmpDir string projectRootDir string ProjectDir string diff --git a/pkg/kluctl_project/project_archive.go b/pkg/kluctl_project/project_archive.go deleted file mode 100644 index dd80a258e..000000000 --- a/pkg/kluctl_project/project_archive.go +++ /dev/null @@ -1,127 +0,0 @@ -package kluctl_project - -import ( - "archive/tar" - "compress/gzip" - "fmt" - types2 "github.com/kluctl/kluctl/v2/pkg/types" - "github.com/kluctl/kluctl/v2/pkg/utils" - "github.com/kluctl/kluctl/v2/pkg/yaml" - "os" - "path/filepath" - "strings" - "time" -) - -func (c *LoadedKluctlProject) loadFromArchive() error { - var dir string - if utils.IsFile(c.loadArgs.FromArchive) { - dir = filepath.Join(c.TmpDir, "archive") - err := utils.ExtractTarGzFile(c.loadArgs.FromArchive, dir) - if err != nil { - return fmt.Errorf("failed to extract archive %v: %w", c.loadArgs.FromArchive, err) - } - } else { - dir = c.loadArgs.FromArchive - } - - var pmdPath string - if c.loadArgs.FromArchiveMetadata != "" { - pmdPath = c.loadArgs.FromArchiveMetadata - } else { - pmdPath = yaml.FixPathExt(filepath.Join(dir, "project-metadata.yml")) - } - - var pmd types2.ProjectMetadata - err := yaml.ReadYamlFile(pmdPath, &pmd) - if err != nil { - return err - } - - var amd types2.ArchiveMetadata - err = yaml.ReadYamlFile(yaml.FixPathExt(filepath.Join(dir, "archive-metadata.yml")), &amd) - if err != nil { - return err - } - - c.loadArgs.RepoRoot = filepath.Join(dir, amd.ProjectRootDir) - c.loadArgs.ProjectDir = filepath.Join(dir, amd.ProjectRootDir, amd.ProjectSubDir) - - err = utils.CheckInDir(dir, c.loadArgs.RepoRoot) - if err != nil { - return err - } - err = utils.CheckInDir(dir, c.loadArgs.ProjectDir) - if err != nil { - return err - } - - c.archiveDir = dir - c.DynamicTargets = pmd.Targets - - err = c.loadKluctlProject() - if err != nil { - return err - } - return nil -} - -func (c *LoadedKluctlProject) WriteArchive(archivePath string, embedProjectMetadata bool) error { - f, err := os.Create(archivePath) - if err != nil { - return err - } - defer f.Close() - gz := gzip.NewWriter(f) - defer gz.Close() - tw := tar.NewWriter(gz) - defer tw.Close() - - filter := func(h *tar.Header, size int64) (*tar.Header, error) { - if strings.HasSuffix(strings.ToLower(h.Name), ".git") { - return nil, nil - } - h.Uid = 0 - h.Gid = 0 - h.Uname = "" - h.Gname = "" - h.ModTime = time.Time{} - h.ChangeTime = time.Time{} - h.AccessTime = time.Time{} - return h, nil - } - - if embedProjectMetadata { - err = yaml.WriteYamlToTar(tw, c.GetMetadata(), "project-metadata.yaml") - if err != nil { - return nil - } - } - - amd := types2.ArchiveMetadata{ - ProjectRootDir: "kluctl-project", - } - - amd.ProjectSubDir, err = filepath.Rel(c.projectRootDir, c.ProjectDir) - if err != nil { - return err - } - - err = yaml.WriteYamlToTar(tw, &amd, "archive-metadata.yaml") - if err != nil { - return nil - } - - if err = utils.AddToTar(tw, c.projectRootDir, "kluctl-project", filter); err != nil { - return err - } - gitReposBaseDir := filepath.Join(c.TmpDir, "git") - if utils.Exists(gitReposBaseDir) { - err = utils.AddToTar(tw, gitReposBaseDir, "git", filter) - if err != nil { - return err - } - } - - return nil -} diff --git a/pkg/kluctl_project/project_load.go b/pkg/kluctl_project/project_load.go index 77cba9795..a409b3de7 100644 --- a/pkg/kluctl_project/project_load.go +++ b/pkg/kluctl_project/project_load.go @@ -16,19 +16,16 @@ import ( ) type LoadKluctlProjectArgs struct { - RepoRoot string - ProjectDir string - ProjectUrl *git_url.GitUrl - ProjectRef string - ProjectConfig string - LocalClusters string - LocalDeployment string - LocalSealedSecrets string - FromArchive string - FromArchiveMetadata string - - AllowGitClone bool - RP repoprovider.RepoProvider + RepoRoot string + ProjectDir string + ProjectUrl *git_url.GitUrl + ProjectRef string + ProjectConfig string + LocalClusters string + LocalDeployment string + LocalSealedSecrets string + + RP repoprovider.RepoProvider ClientConfigGetter func(context *string) (*rest.Config, *api.Config, error) } @@ -145,16 +142,9 @@ func (c *LoadedKluctlProject) loadKluctlProject() error { } } - if c.loadArgs.AllowGitClone { - err = os.MkdirAll(filepath.Join(c.TmpDir, "git"), 0o755) - if err != nil { - return err - } - - err = c.updateGitCaches() - if err != nil { - return err - } + err = c.updateGitCaches() + if err != nil { + return err } s := status.Start(c.ctx, "Loading kluctl project") diff --git a/pkg/types/metadata.go b/pkg/types/metadata.go index 028510789..f734de0ad 100644 --- a/pkg/types/metadata.go +++ b/pkg/types/metadata.go @@ -3,8 +3,3 @@ package types type ProjectMetadata struct { Targets []*DynamicTarget `yaml:"targets"` } - -type ArchiveMetadata struct { - ProjectRootDir string `yaml:"projectRootDir"` - ProjectSubDir string `yaml:"projectSubDir"` -} diff --git a/pkg/vars/vars_loader_test.go b/pkg/vars/vars_loader_test.go index da4f11531..03faf654e 100644 --- a/pkg/vars/vars_loader_test.go +++ b/pkg/vars/vars_loader_test.go @@ -38,7 +38,7 @@ func newTestDir(t *testing.T) string { } func newRP(t *testing.T) repoprovider.RepoProvider { - grc := repoprovider.NewMirroredGitRepoCollection(context.TODO(), auth.NewDefaultAuthProviders(), 0) + grc := repoprovider.NewLiveRepoProvider(context.TODO(), auth.NewDefaultAuthProviders(), 0) t.Cleanup(func() { grc.Clear() }) From 501b6289d4283b3791db2aa1181aefc602f6b064 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 24 May 2022 16:40:18 +0200 Subject: [PATCH 0881/2916] fix: Fix locking of cloneDynamicTargets mutx --- pkg/kluctl_project/targets.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/kluctl_project/targets.go b/pkg/kluctl_project/targets.go index 828e4ec8c..827982764 100644 --- a/pkg/kluctl_project/targets.go +++ b/pkg/kluctl_project/targets.go @@ -228,10 +228,11 @@ func (c *LoadedKluctlProject) cloneDynamicTargets(dynamicTargets []*dynamicTarge if targetInfo.gitProject == nil { continue } + mutex.Lock() if _, ok := uniqueClones[targetInfo.dir]; ok { + mutex.Unlock() continue } - mutex.Lock() uniqueClones[targetInfo.dir] = nil mutex.Unlock() From 8e181c3a0b0fe385a18513b45c2707af071f0b68 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 24 May 2022 16:42:22 +0200 Subject: [PATCH 0882/2916] fix: Fix handling of default refs in LiveRepoProvider --- pkg/git/mirrored_repo.go | 4 ++++ pkg/git/repoprovider/live.go | 26 +++++++++++++++++++------- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/pkg/git/mirrored_repo.go b/pkg/git/mirrored_repo.go index 83fc777e5..81525865e 100644 --- a/pkg/git/mirrored_repo.go +++ b/pkg/git/mirrored_repo.go @@ -55,6 +55,10 @@ func NewMirroredGitRepo(ctx context.Context, u git_url.GitUrl) (*MirroredGitRepo return o, nil } +func (g *MirroredGitRepo) Url() git_url.GitUrl { + return g.url +} + func (g *MirroredGitRepo) HasUpdated() bool { return g.hasUpdated } diff --git a/pkg/git/repoprovider/live.go b/pkg/git/repoprovider/live.go index f1a67b490..9d877b6ae 100644 --- a/pkg/git/repoprovider/live.go +++ b/pkg/git/repoprovider/live.go @@ -110,12 +110,7 @@ func (rp *LiveRepoProvider) getEntry(url git_url.GitUrl, allowCreate bool, lockR return e, nil } -func (rp *LiveRepoProvider) GetRepoInfo(url git_url.GitUrl) (RepoInfo, error) { - e, err := rp.getEntry(url, true, true, true) - if err != nil { - return RepoInfo{}, err - } - +func (e *entry) getRepoInfo() (RepoInfo, error) { remoteRefs, err := e.mr.RemoteRefHashesMap() if err != nil { return RepoInfo{}, err @@ -127,7 +122,7 @@ func (rp *LiveRepoProvider) GetRepoInfo(url git_url.GitUrl) (RepoInfo, error) { } info := RepoInfo{ - Url: url, + Url: e.mr.Url(), RemoteRefs: remoteRefs, DefaultRef: defaultRef, } @@ -135,12 +130,29 @@ func (rp *LiveRepoProvider) GetRepoInfo(url git_url.GitUrl) (RepoInfo, error) { return info, nil } +func (rp *LiveRepoProvider) GetRepoInfo(url git_url.GitUrl) (RepoInfo, error) { + e, err := rp.getEntry(url, true, true, true) + if err != nil { + return RepoInfo{}, err + } + + return e.getRepoInfo() +} + func (rp *LiveRepoProvider) GetClonedDir(url git_url.GitUrl, ref string) (string, git.CheckoutInfo, error) { e, err := rp.getEntry(url, true, true, true) if err != nil { return "", git.CheckoutInfo{}, err } + if ref == "" { + ref, err = e.mr.DefaultRef() + if err != nil { + return "", git.CheckoutInfo{}, err + } + ref = path.Base(ref) + } + e.updateMutex.Lock() defer e.updateMutex.Unlock() From c9d9b57954492b4cff5e5d57c35ceed9c8f0bd11 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 27 May 2022 19:16:33 +0200 Subject: [PATCH 0883/2916] feat: Only load cluster and client config when necessary This commit will also remove the cluster name from the secrets hash calculation, which will cause resealing of all secrets. --- cmd/kluctl/commands/cmd_seal.go | 6 +-- pkg/kluctl_project/target_context.go | 60 +++++++++------------------- pkg/kluctl_project/targets.go | 12 +++--- pkg/seal/sealer.go | 19 ++++----- 4 files changed, 35 insertions(+), 62 deletions(-) diff --git a/cmd/kluctl/commands/cmd_seal.go b/cmd/kluctl/commands/cmd_seal.go index 531fb13b4..65fe92997 100644 --- a/cmd/kluctl/commands/cmd_seal.go +++ b/cmd/kluctl/commands/cmd_seal.go @@ -66,11 +66,7 @@ func (cmd *sealCmd) runCmdSealForTarget(ctx context.Context, p *kluctl_project.L } } - clusterConfig, _, err := p.LoadClusterConfig(ctx.targetCtx.Target.Cluster, ctx.targetCtx.Target.Context) - if err != nil { - return doFail(err) - } - sealer, err := seal.NewSealer(ctx.ctx, ctx.targetCtx.SharedContext.K, sealedSecretsNamespace, sealedSecretsControllerName, clusterConfig.Cluster, cmd.ForceReseal) + sealer, err := seal.NewSealer(ctx.ctx, ctx.targetCtx.SharedContext.K, sealedSecretsNamespace, sealedSecretsControllerName, cmd.ForceReseal) if err != nil { return doFail(err) } diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index 9237562fe..2e4ac84bb 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -12,7 +12,6 @@ import ( "github.com/kluctl/kluctl/v2/pkg/vars" "github.com/kluctl/kluctl/v2/pkg/vars/aws" "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd/api" "path/filepath" ) @@ -117,15 +116,21 @@ func (p *LoadedKluctlProject) buildVars(target *types.Target, clusterName *strin contextName = target.Context } - clusterConfig, clientConfig, err := p.LoadClusterConfig(clusterName, contextName) + clientConfig, restConfig, err := p.loadArgs.ClientConfigGetter(contextName) if err != nil { return doError(err) } varsCtx := vars.NewVarsCtx(p.J2) - err = varsCtx.UpdateChildFromStruct("cluster", clusterConfig.Cluster) - if err != nil { - return doError(err) + if clusterName != nil { + clusterConfig, err := p.LoadClusterConfig(*clusterName) + if err != nil { + return doError(err) + } + err = varsCtx.UpdateChildFromStruct("cluster", clusterConfig.Cluster) + if err != nil { + return doError(err) + } } targetVars, err := uo.FromStruct(target) if err != nil { @@ -162,7 +167,7 @@ func (p *LoadedKluctlProject) buildVars(target *types.Target, clusterName *strin varsCtx.UpdateChild("args", allArgs) - return varsCtx, clientConfig, clusterConfig.Cluster.Context, nil + return varsCtx, clientConfig, restConfig.CurrentContext, nil } func (p *LoadedKluctlProject) findSecretsEntry(name string) (*types.SecretSet, error) { @@ -195,45 +200,18 @@ func (p *LoadedKluctlProject) loadSecrets(target *types.Target, varsCtx *vars.Va return nil } -func (p *LoadedKluctlProject) LoadClusterConfig(clusterName *string, contextName *string) (*types.ClusterConfig, *rest.Config, error) { +func (p *LoadedKluctlProject) LoadClusterConfig(clusterName string) (*types.ClusterConfig, error) { var err error var clusterConfig *types.ClusterConfig - if clusterName != nil { - p.warnOnce.Do("cluster-config", func() { - status.Warning(p.ctx, "Cluster configurations have been deprecated and support for them will be removed in a future kluctl release.") - }) - - clusterConfig, err = types.LoadClusterConfig(p.ClustersDir, *clusterName) - if err != nil { - return nil, nil, err - } - } + p.warnOnce.Do("cluster-config", func() { + status.Warning(p.ctx, "Cluster configurations have been deprecated and support for them will be removed in a future kluctl release.") + }) - var clientConfig *rest.Config - if clusterConfig != nil { - clientConfig, _, err = p.loadArgs.ClientConfigGetter(&clusterConfig.Cluster.Context) - if err != nil { - return nil, nil, err - } - } else { - var rawConfig *api.Config - clientConfig, rawConfig, err = p.loadArgs.ClientConfigGetter(contextName) - if err != nil { - return nil, nil, err - } - ctx, ok := rawConfig.Contexts[rawConfig.CurrentContext] - if !ok { - return nil, nil, fmt.Errorf("context %s not found", rawConfig.CurrentContext) - } - clusterConfig = &types.ClusterConfig{ - Cluster: &types.ClusterConfig2{ - Name: ctx.Cluster, - Context: rawConfig.CurrentContext, - Vars: uo.New(), - }, - } + clusterConfig, err = types.LoadClusterConfig(p.ClustersDir, clusterName) + if err != nil { + return nil, err } - return clusterConfig, clientConfig, nil + return clusterConfig, nil } diff --git a/pkg/kluctl_project/targets.go b/pkg/kluctl_project/targets.go index 827982764..7911a2811 100644 --- a/pkg/kluctl_project/targets.go +++ b/pkg/kluctl_project/targets.go @@ -88,11 +88,13 @@ func (c *LoadedKluctlProject) renderTarget(target *types.Target) (*types.Target, return nil, err } - cc, _, err := c.LoadClusterConfig(target.Cluster, target.Context) - if err == nil { - err = varsCtx.UpdateChildFromStruct("cluster", cc.Cluster) - if err != nil { - return nil, err + if target.Cluster != nil { + cc, err := c.LoadClusterConfig(*target.Cluster) + if err == nil { + err = varsCtx.UpdateChildFromStruct("cluster", cc.Cluster) + if err != nil { + return nil, err + } } } diff --git a/pkg/seal/sealer.go b/pkg/seal/sealer.go index 93d1af66f..80309e6a7 100644 --- a/pkg/seal/sealer.go +++ b/pkg/seal/sealer.go @@ -10,7 +10,6 @@ import ( "github.com/bitnami-labs/sealed-secrets/pkg/crypto" "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/status" - "github.com/kluctl/kluctl/v2/pkg/types" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" @@ -28,18 +27,16 @@ const hashAnnotation = "kluctl.io/sealedsecret-hashes" const clusterIdAnnotation = "kluctl.io/sealedsecret-cluster-id" type Sealer struct { - ctx context.Context - clusterConfig *types.ClusterConfig2 - forceReseal bool - cert *rsa.PublicKey - clusterId string + ctx context.Context + forceReseal bool + cert *rsa.PublicKey + clusterId string } -func NewSealer(ctx context.Context, k *k8s.K8sCluster, sealedSecretsNamespace string, sealedSecretsControllerName string, clusterConfig *types.ClusterConfig2, forceReseal bool) (*Sealer, error) { +func NewSealer(ctx context.Context, k *k8s.K8sCluster, sealedSecretsNamespace string, sealedSecretsControllerName string, forceReseal bool) (*Sealer, error) { s := &Sealer{ - ctx: ctx, - clusterConfig: clusterConfig, - forceReseal: forceReseal, + ctx: ctx, + forceReseal: forceReseal, } cert, err := fetchCert(ctx, k, sealedSecretsNamespace, sealedSecretsControllerName) if err != nil { @@ -93,7 +90,7 @@ func (s *Sealer) doHash(key string, secret []byte, secretName string, secretName if secretNamespace == "" { secretNamespace = "*" } - salt := fmt.Sprintf("%s-%s-%s-%s", s.clusterConfig.Name, secretName, secretNamespace, key) + salt := fmt.Sprintf("%s-%s-%s", secretName, secretNamespace, key) if scope != "strict" { salt += "-" + scope } From f686192104dba5dbdaa6e9b495f58333271278ed Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 27 May 2022 19:18:22 +0200 Subject: [PATCH 0884/2916] fix: Reuse sealed secrets gathering code in sealing and resolving phases Before this, the seal command used a different logic to find sealed secrets then the resolving code, causing some issues. --- pkg/deployment/commands/seal.go | 38 ++++--- pkg/deployment/deployment_collection.go | 2 +- pkg/deployment/deployment_item.go | 130 ++++++++++++++++-------- 3 files changed, 107 insertions(+), 63 deletions(-) diff --git a/pkg/deployment/commands/seal.go b/pkg/deployment/commands/seal.go index c8c3e37a5..bd9eccc2e 100644 --- a/pkg/deployment/commands/seal.go +++ b/pkg/deployment/commands/seal.go @@ -2,12 +2,9 @@ package commands import ( "fmt" - securejoin "github.com/cyphar/filepath-securejoin" "github.com/kluctl/kluctl/v2/pkg/deployment" "github.com/kluctl/kluctl/v2/pkg/seal" - "io/fs" "path/filepath" - "strings" ) type SealCommand struct { @@ -27,26 +24,25 @@ func NewSealCommand(c *deployment.DeploymentCollection, outputPattern string, re } func (cmd *SealCommand) Run(sealer *seal.Sealer) error { - err := filepath.WalkDir(cmd.renderDir, func(p string, d fs.DirEntry, err error) error { - if !strings.HasSuffix(p, deployment.SealmeExt) { - return nil - } - - relPath, err := filepath.Rel(cmd.renderDir, p) - if err != nil { - return err - } - relTargetFile := filepath.Join(filepath.Dir(relPath), cmd.outputPattern, filepath.Base(p)) - targetFile, err := securejoin.SecureJoin(cmd.sealedSecretsDir, relTargetFile) + for _, di := range cmd.c.Deployments { + sealedSecrets, err := di.ListSealedSecrets("") if err != nil { return err } - targetFile = targetFile[:len(targetFile)-len(deployment.SealmeExt)] - err = sealer.SealFile(p, targetFile) - if err != nil { - return fmt.Errorf("failed sealing %s: %w", filepath.Base(p), err) + + for _, relPath := range sealedSecrets { + sealmeFile := filepath.Join(di.RenderedDir, relPath+deployment.SealmeExt) + targetFile, err := di.BuildSealedSecretPath(relPath) + if err != nil { + return err + } + + err = sealer.SealFile(sealmeFile, targetFile) + if err != nil { + return fmt.Errorf("failed sealing %s: %w", filepath.Base(relPath), err) + } } - return nil - }) - return err + } + + return nil } diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index 698dba2bf..9b1a99789 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -148,7 +148,7 @@ func (c *DeploymentCollection) resolveSealedSecrets() error { } for _, d := range c.Deployments { - err := d.resolveSealedSecrets("") + err := d.resolveSealedSecrets() if err != nil { return err } diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index f0e89f5ff..b9e36c547 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -2,7 +2,6 @@ package deployment import ( "fmt" - securejoin "github.com/cyphar/filepath-securejoin" "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" @@ -38,8 +37,8 @@ type DeploymentItem struct { RelToSourceItemDir string RelToProjectItemDir string - relRenderedDir string - renderedDir string + RelRenderedDir string + RenderedDir string renderedYamlPath string } @@ -70,13 +69,13 @@ func NewDeploymentItem(ctx SharedContext, project *DeploymentProject, collection return nil, err } - di.relRenderedDir = di.RelToSourceItemDir + di.RelRenderedDir = di.RelToSourceItemDir if di.index != 0 { - di.relRenderedDir = fmt.Sprintf("%s-%d", di.relRenderedDir, di.index) + di.RelRenderedDir = fmt.Sprintf("%s-%d", di.RelRenderedDir, di.index) } - di.renderedDir = filepath.Join(collection.ctx.RenderDir, di.Project.source.id, di.relRenderedDir) - di.renderedYamlPath = filepath.Join(di.renderedDir, ".rendered.yml") + di.RenderedDir = filepath.Join(collection.ctx.RenderDir, di.Project.source.id, di.RelRenderedDir) + di.renderedYamlPath = filepath.Join(di.RenderedDir, ".rendered.yml") } return di, nil } @@ -107,7 +106,7 @@ func (di *DeploymentItem) render(forSeal bool, wp *utils.WorkerPoolWithErrors) e return nil } - err := os.MkdirAll(di.renderedDir, 0o700) + err := os.MkdirAll(di.RenderedDir, 0o700) if err != nil { return err } @@ -147,7 +146,7 @@ func (di *DeploymentItem) render(forSeal bool, wp *utils.WorkerPoolWithErrors) e } wp.Submit(func() error { - return varsCtx.RenderDirectory(di.Project.source.dir, di.Project.getRenderSearchDirs(), di.Project.relDir, excludePatterns, di.RelToProjectItemDir, di.renderedDir) + return varsCtx.RenderDirectory(di.Project.source.dir, di.Project.getRenderSearchDirs(), di.Project.relDir, excludePatterns, di.RelToProjectItemDir, di.RenderedDir) }) return nil @@ -158,13 +157,13 @@ func (di *DeploymentItem) renderHelmCharts(wp *utils.WorkerPoolWithErrors) error return nil } - err := filepath.Walk(di.renderedDir, func(p string, info fs.FileInfo, err error) error { + err := filepath.Walk(di.RenderedDir, func(p string, info fs.FileInfo, err error) error { if !strings.HasSuffix(p, "helm-chart.yml") && !strings.HasSuffix(p, "helm-chart.yaml") { return nil } wp.Submit(func() error { - subDir, err := filepath.Rel(di.renderedDir, filepath.Dir(p)) + subDir, err := filepath.Rel(di.RenderedDir, filepath.Dir(p)) if err != nil { return err } @@ -185,7 +184,7 @@ func (di *DeploymentItem) renderHelmCharts(wp *utils.WorkerPoolWithErrors) error } } if !found { - return fmt.Errorf("%s/kustomization.yaml does not include the rendered helm chart: %s", di.relRenderedDir, chart.GetOutputPath()) + return fmt.Errorf("%s/kustomization.yaml does not include the rendered helm chart: %s", di.RelRenderedDir, chart.GetOutputPath()) } } @@ -199,57 +198,106 @@ func (di *DeploymentItem) renderHelmCharts(wp *utils.WorkerPoolWithErrors) error return nil } -func (di *DeploymentItem) resolveSealedSecrets(subdir string) error { +func (di *DeploymentItem) ListSealedSecrets(subdir string) ([]string, error) { + var ret []string + if di.dir == nil { - return nil + return nil, nil } - sealedSecretsDir := di.Project.getRenderedOutputPattern() - baseSourcePath := di.Project.ctx.SealedSecretsDir - - renderedDir := filepath.Join(di.renderedDir, subdir) + renderedDir := filepath.Join(di.RenderedDir, subdir) // ensure we're not leaving the project err := utils.CheckSubInDir(di.Project.source.dir, subdir) if err != nil { - return err + return nil, err } y, err := uo.FromFile(yaml.FixPathExt(filepath.Join(renderedDir, "kustomization.yml"))) if err != nil { - return err + return nil, err } l, _, err := y.GetNestedStringList("resources") if err != nil { - return err + return nil, err } for _, resource := range l { - p := filepath.Join(renderedDir, resource) - if utils.IsDirectory(p) { - err = di.resolveSealedSecrets(filepath.Join(subdir, resource)) - if err != nil { - return err - } + p := filepath.Clean(filepath.Join(renderedDir, resource)) + + isDir := utils.IsDirectory(p) + isSealedSecret := !utils.Exists(p) && utils.Exists(p+SealmeExt) + if !isDir && !isSealedSecret { continue } - if utils.Exists(p) || !utils.Exists(p+SealmeExt) { - continue + + relPath, err := filepath.Rel(di.RenderedDir, p) + if err != nil { + return nil, err } - relDir, err := filepath.Rel(renderedDir, filepath.Dir(p)) + + relPath = filepath.Clean(relPath) + + // ensure we're not leaving the project + err = utils.CheckSubInDir(di.Project.source.dir, relPath) if err != nil { - return err + return nil, err + } + + if isDir { + ret2, err := di.ListSealedSecrets(relPath) + if err != nil { + return nil, err + } + ret = append(ret, ret2...) + } else { + ret = append(ret, relPath) } - fname := filepath.Base(p) + } + return ret, nil +} - baseError := fmt.Sprintf("failed to resolve SealedSecret %s", filepath.Clean(filepath.Join(di.Project.absDir, resource))) +func (di *DeploymentItem) BuildSealedSecretPath(relPath string) (string, error) { + sealedSecretsDir := di.Project.getRenderedOutputPattern() + baseSourcePath := di.Project.ctx.SealedSecretsDir - // ensure we're not leaving the .sealed-secrets dir - sourcePath, err := securejoin.SecureJoin(baseSourcePath, filepath.Join(di.relRenderedDir, subdir, relDir, sealedSecretsDir, fname)) + relDir := filepath.Dir(relPath) + fname := filepath.Base(relPath) + + // ensure we're not leaving the .sealed-secrets dir + sourcePath := filepath.Join(baseSourcePath, di.RelRenderedDir, relDir, sealedSecretsDir, fname) + err := utils.CheckInDir(baseSourcePath, sourcePath) + if err != nil { + return "", err + } + sourcePath = filepath.Clean(sourcePath) + return sourcePath, nil +} + +func (di *DeploymentItem) resolveSealedSecrets() error { + if di.dir == nil { + return nil + } + + sealedSecrets, err := di.ListSealedSecrets("") + if err != nil { + return err + } + + for _, relPath := range sealedSecrets { + relDir := filepath.Dir(relPath) if err != nil { return err } - sourcePath = filepath.Clean(sourcePath) - targetPath := filepath.Join(renderedDir, relDir, fname) + fname := filepath.Base(relPath) + + baseError := fmt.Sprintf("failed to resolve SealedSecret %s", relPath) + + sourcePath, err := di.BuildSealedSecretPath(relPath) + if err != nil { + return fmt.Errorf("%s: %w", baseError, err) + } + + targetPath := filepath.Join(di.RenderedDir, relDir, fname) if !utils.IsFile(sourcePath) { return fmt.Errorf("%s. %s not found. You might need to seal it first", baseError, sourcePath) } @@ -304,7 +352,7 @@ func (di *DeploymentItem) readKustomizationYaml(subDir string) (*uo.Unstructured return nil, nil } - kustomizeYamlPath := yaml.FixPathExt(filepath.Join(di.renderedDir, subDir, "kustomization.yml")) + kustomizeYamlPath := yaml.FixPathExt(filepath.Join(di.RenderedDir, subDir, "kustomization.yml")) if !utils.IsFile(kustomizeYamlPath) { return nil, nil } @@ -318,7 +366,7 @@ func (di *DeploymentItem) readKustomizationYaml(subDir string) (*uo.Unstructured } func (di *DeploymentItem) writeKustomizationYaml(ky *uo.UnstructuredObject) error { - kustomizeYamlPath := yaml.FixPathExt(filepath.Join(di.renderedDir, "kustomization.yml")) + kustomizeYamlPath := yaml.FixPathExt(filepath.Join(di.RenderedDir, "kustomization.yml")) return yaml.WriteYamlFile(kustomizeYamlPath, ky) } @@ -365,7 +413,7 @@ func (di *DeploymentItem) buildKustomize() error { k := krusty.MakeKustomizer(ko) fsys := filesys.MakeFsOnDisk() - rm, err := k.Run(fsys, di.renderedDir) + rm, err := k.Run(fsys, di.RenderedDir) if err != nil { return err } @@ -453,7 +501,7 @@ func (di *DeploymentItem) postprocessObjects(images *Images) error { o.SetK8sAnnotations(uo.CopyMergeStrMap(o.GetK8sAnnotations(), commonAnnotations)) // Resolve image placeholders - err := images.ResolvePlaceholders(di.ctx.K, o, di.relRenderedDir, di.Tags.ListKeys()) + err := images.ResolvePlaceholders(di.ctx.K, o, di.RelRenderedDir, di.Tags.ListKeys()) if err != nil { errList = append(errList, err) } From eda52d10077003f4ae046c00cc08740327c289e5 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 30 May 2022 08:58:35 +0200 Subject: [PATCH 0885/2916] fix: Fix zombie jinja2 processes We missed calling Wait() on the process. --- pkg/jinja2/jinja2_renderer.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/jinja2/jinja2_renderer.go b/pkg/jinja2/jinja2_renderer.go index f52869396..f29ec2312 100644 --- a/pkg/jinja2/jinja2_renderer.go +++ b/pkg/jinja2/jinja2_renderer.go @@ -13,6 +13,7 @@ import ( "os/exec" "path/filepath" "strings" + "time" ) type pythonJinja2Renderer struct { @@ -75,7 +76,11 @@ func (j *pythonJinja2Renderer) Close() { } if j.cmd != nil { if j.cmd.Process != nil { - _ = j.cmd.Process.Kill() + timer := time.AfterFunc(5*time.Second, func() { + _ = j.cmd.Process.Kill() + }) + _ = j.cmd.Wait() + timer.Stop() } j.cmd = nil } From bef1faf818ef9a7513d4256777151c59862dd8f8 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 30 May 2022 10:58:13 +0200 Subject: [PATCH 0886/2916] fix: Allow to pass nil DeploymentCollection to DeleteCommand --- pkg/deployment/commands/delete.go | 8 +++++++- pkg/utils/inclusion.go | 6 ++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/pkg/deployment/commands/delete.go b/pkg/deployment/commands/delete.go index d794d355e..d4cb32fd8 100644 --- a/pkg/deployment/commands/delete.go +++ b/pkg/deployment/commands/delete.go @@ -6,6 +6,7 @@ import ( utils2 "github.com/kluctl/kluctl/v2/pkg/deployment/utils" "github.com/kluctl/kluctl/v2/pkg/k8s" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/utils" ) type DeleteCommand struct { @@ -31,10 +32,15 @@ func (cmd *DeleteCommand) Run(ctx context.Context, k *k8s.K8sCluster) ([]k8s2.Ob labels = cmd.c.Project.GetCommonLabels() } + var inclusion *utils.Inclusion + if cmd.c != nil { + inclusion = cmd.c.Inclusion + } + err := ru.UpdateRemoteObjects(k, labels, nil) if err != nil { return nil, err } - return utils2.FindObjectsForDelete(k, ru.GetFilteredRemoteObjects(cmd.c.Inclusion), cmd.c.Inclusion.HasType("tags"), nil) + return utils2.FindObjectsForDelete(k, ru.GetFilteredRemoteObjects(inclusion), inclusion.HasType("tags"), nil) } diff --git a/pkg/utils/inclusion.go b/pkg/utils/inclusion.go index 63990cf7c..5300f269f 100644 --- a/pkg/utils/inclusion.go +++ b/pkg/utils/inclusion.go @@ -26,6 +26,9 @@ func (inc *Inclusion) AddExclude(typ string, value string) { } func (inc *Inclusion) HasType(typ string) bool { + if inc == nil { + return false + } for e, _ := range inc.includes { if e.Type == typ { return true @@ -49,6 +52,9 @@ func (inc *Inclusion) checkList(l []InclusionEntry, m map[InclusionEntry]bool) b } func (inc *Inclusion) CheckIncluded(l []InclusionEntry, excludeIfNotIncluded bool) bool { + if inc == nil { + return true + } if len(inc.includes) == 0 && len(inc.excludes) == 0 { return true } From aa47679c2306e732c2aed34d89311e6eedc569cc Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 30 May 2022 11:20:55 +0200 Subject: [PATCH 0887/2916] fix: Remove WaitForOpenapiInitDone as it is not needed anymore Improvements in kustomize resulted in quite some speedup in openapi loading, so this is not needed anymore. --- pkg/deployment/deployment_item.go | 2 -- pkg/k8s/fake_client_factory.go | 3 --- pkg/utils/init_kyaml.go | 30 ------------------------------ 3 files changed, 35 deletions(-) delete mode 100644 pkg/utils/init_kyaml.go diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index b9e36c547..b7ca38e17 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -407,8 +407,6 @@ func (di *DeploymentItem) buildKustomize() error { return err } - utils.WaitForOpenapiInitDone() - ko := krusty.MakeDefaultOptions() k := krusty.MakeKustomizer(ko) diff --git a/pkg/k8s/fake_client_factory.go b/pkg/k8s/fake_client_factory.go index 8af606ad5..8bc1169f2 100644 --- a/pkg/k8s/fake_client_factory.go +++ b/pkg/k8s/fake_client_factory.go @@ -1,7 +1,6 @@ package k8s import ( - "github.com/kluctl/kluctl/v2/pkg/utils" v1 "k8s.io/api/core/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -65,8 +64,6 @@ func NewFakeClientFactory(objects ...runtime.Object) ClientFactory { } func ConvertSchemeToAPIResources(s *runtime.Scheme) []*metav1.APIResourceList { - utils.WaitForOpenapiInitDone() - m := map[schema.GroupVersion][]metav1.APIResource{} for gvk, _ := range s.AllKnownTypes() { diff --git a/pkg/utils/init_kyaml.go b/pkg/utils/init_kyaml.go deleted file mode 100644 index a25bddbb0..000000000 --- a/pkg/utils/init_kyaml.go +++ /dev/null @@ -1,30 +0,0 @@ -package utils - -import ( - "sigs.k8s.io/kustomize/kyaml/openapi" - yaml2 "sigs.k8s.io/kustomize/kyaml/yaml" - "sync" -) - -var openapiInitDoneMutex sync.Mutex -var openapiInitDoneOnce sync.Once - -func WaitForOpenapiInitDone() { - openapiInitDoneOnce.Do(func() { - openapiInitDoneMutex.Lock() - openapiInitDoneMutex.Unlock() - }) -} - -func init() { - openapiInitDoneMutex.Lock() - go func() { - // we do a single call to IsNamespaceScoped to enforce openapi schema initialization - // this is required here to ensure that it is later not done in parallel which would cause race conditions - openapi.IsNamespaceScoped(yaml2.TypeMeta{ - APIVersion: "", - Kind: "ConfigMap", - }) - openapiInitDoneMutex.Unlock() - }() -} From 7270131a4dda803244a8b913c91a8256229c8d42 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 30 May 2022 11:21:40 +0200 Subject: [PATCH 0888/2916] fix: Don't write yamls to disk in buildKustomize And also avoid loading this yaml back into memory. We can simply use the in-memory result of the kustomize run. --- pkg/deployment/deployment_collection.go | 9 ++----- pkg/deployment/deployment_item.go | 31 +++---------------------- 2 files changed, 5 insertions(+), 35 deletions(-) diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index 9b1a99789..be086ebb2 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -195,14 +195,9 @@ func (c *DeploymentCollection) buildKustomizeObjects() error { wg.Add(1) go func() { - err := d.loadObjects() + err := d.postprocessCRDs() if err != nil { - handleError(fmt.Errorf("loading objects failed: %w", err)) - } else { - err = d.postprocessCRDs() - if err != nil { - handleError(fmt.Errorf("postprocessing CRDs failed: %w", err)) - } + handleError(fmt.Errorf("postprocessing CRDs failed: %w", err)) } wg.Done() }() diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index b7ca38e17..7afc98470 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -416,41 +416,16 @@ func (di *DeploymentItem) buildKustomize() error { return err } - var yamls []interface{} + di.Objects = nil for _, r := range rm.Resources() { y, err := r.Map() if err != nil { return err } - yamls = append(yamls, y) + o := uo.FromMap(y) + di.Objects = append(di.Objects, o) } - err = yaml.WriteYamlAllFile(di.renderedYamlPath, yamls) - if err != nil { - return err - } - - return nil -} - -func (di *DeploymentItem) loadObjects() error { - if di.dir == nil { - return nil - } - - objects, err := yaml.ReadYamlAllFile(di.renderedYamlPath) - if err != nil { - return err - } - - di.Objects = []*uo.UnstructuredObject{} - for _, o := range objects { - m, ok := o.(map[string]interface{}) - if !ok { - return fmt.Errorf("object is not a map") - } - di.Objects = append(di.Objects, uo.FromMap(m)) - } return nil } From 6509923813653c7645ad0924849f9a2140ffb7f7 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 30 May 2022 11:42:07 +0200 Subject: [PATCH 0889/2916] fix: Use secureBuildKustomization from Flux kustomize-controller This fixes multiple issues at once, see the comment of the function for details. --- go.mod | 1 + go.sum | 4 +- pkg/deployment/deployment_item.go | 22 +++++------ pkg/utils/kustomize.go | 62 +++++++++++++++++++++++++++++++ 4 files changed, 75 insertions(+), 14 deletions(-) create mode 100644 pkg/utils/kustomize.go diff --git a/go.mod b/go.mod index f9447d54d..e0968119e 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/cyphar/filepath-securejoin v0.2.3 github.com/docker/distribution v2.8.1+incompatible github.com/evanphx/json-patch v5.6.0+incompatible + github.com/fluxcd/pkg/kustomize v0.5.1 github.com/gammazero/workerpool v1.1.2 github.com/go-git/go-git/v5 v5.4.2 github.com/go-playground/validator/v10 v10.11.0 diff --git a/go.sum b/go.sum index 851e2a68e..ba0bc3d06 100644 --- a/go.sum +++ b/go.sum @@ -314,6 +314,8 @@ github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4 github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= +github.com/fluxcd/pkg/kustomize v0.5.1 h1:151Ih34ltxN2z1e2mA5AvQONyE6phc4es57oVK3+plU= +github.com/fluxcd/pkg/kustomize v0.5.1/go.mod h1:58MFITy24bIbGI6cC3JkV/YpFQj648sVvgs0K1kraJw= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= @@ -885,7 +887,7 @@ github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGV github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= +github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 h1:+czc/J8SlhPKLOtVLMQc+xDCFBT73ZStMsRhSsUhsSg= diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 7afc98470..51957e455 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -12,8 +12,6 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "os" "path/filepath" - "sigs.k8s.io/kustomize/api/filesys" - "sigs.k8s.io/kustomize/api/krusty" "strings" ) @@ -35,11 +33,12 @@ type DeploymentItem struct { Objects []*uo.UnstructuredObject Tags *utils.OrderedMap - RelToSourceItemDir string - RelToProjectItemDir string - RelRenderedDir string - RenderedDir string - renderedYamlPath string + RenderedSourceRootDir string + RelToSourceItemDir string + RelToProjectItemDir string + RelRenderedDir string + RenderedDir string + renderedYamlPath string } func NewDeploymentItem(ctx SharedContext, project *DeploymentProject, collection *DeploymentCollection, config *types.DeploymentItemConfig, dir *string, index int) (*DeploymentItem, error) { @@ -74,7 +73,8 @@ func NewDeploymentItem(ctx SharedContext, project *DeploymentProject, collection di.RelRenderedDir = fmt.Sprintf("%s-%d", di.RelRenderedDir, di.index) } - di.RenderedDir = filepath.Join(collection.ctx.RenderDir, di.Project.source.id, di.RelRenderedDir) + di.RenderedSourceRootDir = filepath.Join(collection.ctx.RenderDir, di.Project.source.id) + di.RenderedDir = filepath.Join(di.RenderedSourceRootDir, di.RelRenderedDir) di.renderedYamlPath = filepath.Join(di.RenderedDir, ".rendered.yml") } return di, nil @@ -407,11 +407,7 @@ func (di *DeploymentItem) buildKustomize() error { return err } - ko := krusty.MakeDefaultOptions() - k := krusty.MakeKustomizer(ko) - - fsys := filesys.MakeFsOnDisk() - rm, err := k.Run(fsys, di.RenderedDir) + rm, err := utils.SecureBuildKustomization(di.RenderedSourceRootDir, di.RenderedDir, true) if err != nil { return err } diff --git a/pkg/utils/kustomize.go b/pkg/utils/kustomize.go new file mode 100644 index 000000000..21fbe4fe2 --- /dev/null +++ b/pkg/utils/kustomize.go @@ -0,0 +1,62 @@ +/* +Code in this file is copied from https://github.com/fluxcd/kustomize-controller/blob/main/controllers/kustomization_generator.go +*/ + +package utils + +import ( + "fmt" + securefs "github.com/fluxcd/pkg/kustomize/filesys" + "sigs.k8s.io/kustomize/api/filesys" + "sigs.k8s.io/kustomize/api/krusty" + "sigs.k8s.io/kustomize/api/resmap" + kustypes "sigs.k8s.io/kustomize/api/types" + "sync" +) + +// TODO: remove mutex when kustomize fixes the concurrent map read/write panic +var kustomizeBuildMutex sync.Mutex + +// SecureBuildKustomization wraps krusty.MakeKustomizer with the following settings: +// - secure on-disk FS denying operations outside root +// - load files from outside the kustomization dir path +// (but not outside root) +// - disable plugins except for the builtin ones +func SecureBuildKustomization(root, dirPath string, allowRemoteBases bool) (_ resmap.ResMap, err error) { + var fs filesys.FileSystem + + // Create secure FS for root with or without remote base support + if allowRemoteBases { + fs, err = securefs.MakeFsOnDiskSecureBuild(root) + if err != nil { + return nil, err + } + } else { + fs, err = securefs.MakeFsOnDiskSecure(root) + if err != nil { + return nil, err + } + } + + // Temporary workaround for concurrent map read and map write bug + // https://github.com/kubernetes-sigs/kustomize/issues/3659 + kustomizeBuildMutex.Lock() + defer kustomizeBuildMutex.Unlock() + + // Kustomize tends to panic in unpredicted ways due to (accidental) + // invalid object data; recover when this happens to ensure continuity of + // operations + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("recovered from kustomize build panic: %v", r) + } + }() + + buildOptions := &krusty.Options{ + LoadRestrictions: kustypes.LoadRestrictionsNone, + PluginConfig: kustypes.DisabledPluginConfig(), + } + + k := krusty.MakeKustomizer(buildOptions) + return k.Run(fs, dirPath) +} From 8ef86b647c22473c839f1751cdd7390a707e366b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 30 May 2022 11:44:33 +0200 Subject: [PATCH 0890/2916] ci: Fix branches pattern in Github workflow --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1ad4ff67a..0f3ffed05 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -3,7 +3,7 @@ name: tests on: push: branches: - - '*' + - '**' jobs: build: From 47cb42b0659a107f3cd66d52485bc1dcc3142c34 Mon Sep 17 00:00:00 2001 From: Mathias Gebbe Date: Mon, 30 May 2022 13:35:32 +0200 Subject: [PATCH 0891/2916] Feat/add seald secret vault support (token auth) (#28) * feat: reformat new vars style * feat: modify vault support * fix: Run go mod tidy Co-authored-by: Alexander Block --- go.mod | 28 +++++++++++++ go.sum | 88 +++++++++++++++++++++++++++++++++++++++ pkg/types/vars_source.go | 6 +++ pkg/vars/vars_loader.go | 11 +++++ pkg/vars/vault/secrets.go | 31 ++++++++++++++ 5 files changed, 164 insertions(+) create mode 100644 pkg/vars/vault/secrets.go diff --git a/go.mod b/go.mod index e0968119e..3c936f079 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( github.com/goccy/go-yaml v1.9.5 github.com/golang-jwt/jwt/v4 v4.4.1 github.com/google/go-containerregistry v0.9.0 + github.com/hashicorp/vault/api v1.6.0 github.com/hexops/gotextdiff v1.0.3 github.com/jinzhu/copier v0.3.5 github.com/kevinburke/ssh_config v1.2.0 @@ -74,8 +75,11 @@ require ( github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect github.com/acomagu/bufpipe v1.0.3 // indirect github.com/alessio/shellescape v1.4.1 // indirect + github.com/armon/go-metrics v0.3.10 // indirect + github.com/armon/go-radix v1.0.0 // indirect github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/cenkalti/backoff/v3 v3.0.0 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 // indirect github.com/containerd/containerd v1.6.4 // indirect @@ -105,6 +109,7 @@ require ( github.com/go-playground/universal-translator v0.18.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.0.1 // indirect github.com/google/gnostic v0.6.9 // indirect github.com/google/go-cmp v0.5.8 // indirect @@ -114,7 +119,24 @@ require ( github.com/gorilla/mux v1.8.0 // indirect github.com/gosuri/uitable v0.0.4 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-hclog v1.2.0 // indirect + github.com/hashicorp/go-immutable-radix v1.3.1 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-plugin v1.4.3 // indirect + github.com/hashicorp/go-retryablehttp v0.6.6 // indirect + github.com/hashicorp/go-rootcerts v1.0.2 // indirect + github.com/hashicorp/go-secure-stdlib/mlock v0.1.1 // indirect + github.com/hashicorp/go-secure-stdlib/parseutil v0.1.5 // indirect + github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect + github.com/hashicorp/go-sockaddr v1.0.2 // indirect + github.com/hashicorp/go-uuid v1.0.2 // indirect + github.com/hashicorp/go-version v1.2.1 // indirect + github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect + github.com/hashicorp/vault/sdk v0.5.0 // indirect + github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect github.com/huandu/xstrings v1.3.2 // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect @@ -135,6 +157,7 @@ require ( github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/go-testing-interface v1.0.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect @@ -146,11 +169,13 @@ require ( github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/oklog/run v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.0.1 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect + github.com/pierrec/lz4 v2.5.2+incompatible // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.12.2 // indirect github.com/prometheus/client_model v0.2.0 // indirect @@ -159,6 +184,7 @@ require ( github.com/rivo/uniseg v0.2.0 // indirect github.com/rubenv/sql-migrate v1.1.1 // indirect github.com/russross/blackfriday v1.6.0 // indirect + github.com/ryanuber/go-glob v1.0.0 // indirect github.com/sergi/go-diff v1.2.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect github.com/spf13/afero v1.8.2 // indirect @@ -171,6 +197,7 @@ require ( github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xlab/treeprint v1.1.0 // indirect go.starlark.net v0.0.0-20220328144851-d1966c6b9fcd // indirect + go.uber.org/atomic v1.9.0 // indirect golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 // indirect golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect @@ -181,6 +208,7 @@ require ( google.golang.org/protobuf v1.28.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.66.4 // indirect + gopkg.in/square/go-jose.v2 v2.5.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/apiserver v0.24.0 // indirect diff --git a/go.sum b/go.sum index ba0bc3d06..c0ed01965 100644 --- a/go.sum +++ b/go.sum @@ -93,6 +93,7 @@ github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= @@ -152,7 +153,11 @@ github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.3.9/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-metrics v0.3.10 h1:FR+drcQStOe+32sYyJYyZ7FIdgoGGBnwLl+flodp8Uo= +github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= @@ -188,6 +193,8 @@ github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd h1:rFt+Y/IK1aEZ github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembjv71DPz3uX/V/6MMlSyD9JBQ6kQ= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o= github.com/butuzov/ireturn v0.1.1/go.mod h1:Wh6Zl3IMtTpaIKbmwzqi6olnM9ptYQxxVacMsOEFPoc= +github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c= +github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= @@ -202,6 +209,8 @@ github.com/chavacava/garif v0.0.0-20210405164556-e8a0a408d6af/go.mod h1:Qjyv4H3/ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -299,6 +308,7 @@ github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQL github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.5.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= @@ -310,6 +320,8 @@ github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= @@ -319,6 +331,8 @@ github.com/fluxcd/pkg/kustomize v0.5.1/go.mod h1:58MFITy24bIbGI6cC3JkV/YpFQj648s github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y= +github.com/frankban/quicktest v1.13.0/go.mod h1:qLE0fzW0VuyUAJgPU19zByoIr0HtCHN/r/VLSOOIySU= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -338,6 +352,7 @@ github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49P github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-critic/go-critic v0.6.1/go.mod h1:SdNCfU0yF3UBjtaZGw6586/WocupMOJuiqgom5DsQxM= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= @@ -360,6 +375,7 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-ldap/ldap/v3 v3.1.10/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd8luAK4Q= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= @@ -402,6 +418,8 @@ github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfC github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= +github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4= github.com/go-toolsmith/astcopy v1.0.0/go.mod h1:vrgyG+5Bxrnz4MZWPF+pI4R8h3qKRjjyvV/DSez4WVQ= github.com/go-toolsmith/astequal v1.0.0/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= @@ -476,6 +494,8 @@ github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4= github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8= @@ -600,23 +620,61 @@ github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/ github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= +github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-kms-wrapping/entropy v0.1.0/go.mod h1:d1g9WGtAunDNpek8jUIEJnBlbgKS1N2Q61QkHiZyR1g= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v6UZM= +github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-retryablehttp v0.6.6 h1:HJunrbHTDDbBb/ay4kxa1n+dLmttUlnP3V9oNE4hmsM= +github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-secure-stdlib/base62 v0.1.1/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw= +github.com/hashicorp/go-secure-stdlib/mlock v0.1.1 h1:cCRo8gK7oq6A2L6LICkUZ+/a5rLiRXFMf1Qd4xSwxTc= +github.com/hashicorp/go-secure-stdlib/mlock v0.1.1/go.mod h1:zq93CJChV6L9QTfGKtfBxKqD7BqqXx5O04A/ns2p5+I= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.5 h1:MBgwAFPUbfuI0+tmDU/aeM1MARvdbqWmiieXIalKqDE= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.5/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= +github.com/hashicorp/go-secure-stdlib/password v0.1.1/go.mod h1:9hH302QllNwu1o2TGYtSk8I8kTAN0ca1EHpwhm5Mmzo= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= +github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.1/go.mod h1:l8slYwnJA26yBz+ErHpp2IRCLr0vuOMGBORIz4rRiAs= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= +github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI= github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= @@ -627,6 +685,12 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= +github.com/hashicorp/vault/api v1.6.0 h1:B8UUYod1y1OoiGHq9GtpiqSnGOUEWHaA26AY8RQEDY4= +github.com/hashicorp/vault/api v1.6.0/go.mod h1:h1K70EO2DgnBaTz5IsL6D5ERsNt5Pce93ueVS2+t0Xc= +github.com/hashicorp/vault/sdk v0.5.0 h1:EED7p0OCU3OY5SAqJwSANofY1YKMytm+jDHDQ2EzGVQ= +github.com/hashicorp/vault/sdk v0.5.0/go.mod h1:UJZHlfwj7qUJG8g22CuxUgkdJouFrBNvBHCyx8XAPdo= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= @@ -650,6 +714,8 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/jgautheron/goconst v1.5.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= +github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= +github.com/jhump/protoreflect v1.6.1 h1:4/2yi5LyDPP7nN+Hiird1SAJ6YoxUm13/oxHGRnbPd8= github.com/jhump/protoreflect v1.6.1/go.mod h1:RZQ/lnuN+zqeRVpQigTwO6o0AJUkxbnSnpuG7toUTG4= github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg= @@ -672,6 +738,7 @@ github.com/josharian/txtarfs v0.0.0-20210218200122-0702f000015a/go.mod h1:izVPOv github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -813,6 +880,8 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= @@ -870,6 +939,8 @@ github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/ohler55/ojg v1.14.0 h1:DyHomsCwofNswmKj7BLMdx51xnKbXxgIo1rVWCaBcNk= github.com/ohler55/ojg v1.14.0/go.mod h1:3+GH+0PggMKocQtbZCrFifal3yRpHiBT4QUkxFJI6e8= +github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= @@ -899,6 +970,8 @@ github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6 github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= @@ -911,6 +984,8 @@ github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+v github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw= github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc= +github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI= +github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -931,6 +1006,7 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= @@ -945,6 +1021,7 @@ github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7q github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= @@ -954,6 +1031,7 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= @@ -992,6 +1070,9 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/ryancurrah/gomodguard v1.2.3/go.mod h1:rYbA/4Tg5c54mV1sv4sQTP5WOPBcoLtnBZ7/TEhXAbg= github.com/ryanrolds/sqlclosecheck v0.3.0/go.mod h1:1gREqxyTGR3lVtpngyFo3hZAgk0KCtEdgEkHwDbigdA= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= +github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/sagikazarmark/crypt v0.1.0/go.mod h1:B/mN0msZuINBtQ1zZLEQcegFJJf9vnYIR88KRMEuODE= github.com/sanposhiho/wastedassign/v2 v2.0.6/go.mod h1:KyZ0MWTwxxBmfwn33zh3k1dmsbF2ud9pAAGfoLfjhtI= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= @@ -1085,6 +1166,7 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1 github.com/tomarrell/wrapcheck/v2 v2.4.0/go.mod h1:68bQ/eJg55BROaRTbMjC7vuhL2OgfoG8bLp9ZyoBfyY= github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4= github.com/tommy-muehle/go-mnd/v2 v2.4.0/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ultraware/funlen v0.0.3/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= github.com/ultraware/whitespace v0.0.4/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA= @@ -1178,6 +1260,8 @@ go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= @@ -1254,6 +1338,7 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1750,6 +1835,7 @@ google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= @@ -1793,6 +1879,8 @@ gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= +gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= diff --git a/pkg/types/vars_source.go b/pkg/types/vars_source.go index 84e15f393..006b9d2f4 100644 --- a/pkg/types/vars_source.go +++ b/pkg/types/vars_source.go @@ -37,6 +37,11 @@ type VarsSourceAwsSecretsManager struct { Profile *string `yaml:"profile,omitempty"` } +type VarsSourceVault struct { + Server string `yaml:"server" validate:"required"` + Key string `yaml:"key" validate:"required"` +} + type VarsSource struct { Values *uo.UnstructuredObject `yaml:"values,omitempty"` File *string `yaml:"file,omitempty"` @@ -47,6 +52,7 @@ type VarsSource struct { SystemEnvVars *uo.UnstructuredObject `yaml:"systemEnvVars,omitempty"` Http *VarsSourceHttp `yaml:"http,omitempty"` AwsSecretsManager *VarsSourceAwsSecretsManager `yaml:"awsSecretsManager,omitempty"` + Vault *VarsSourceVault `yaml:"vault,omitempty"` } func ValidateVarsSource(sl validator.StructLevel) { diff --git a/pkg/vars/vars_loader.go b/pkg/vars/vars_loader.go index 94a221422..2af7b09af 100644 --- a/pkg/vars/vars_loader.go +++ b/pkg/vars/vars_loader.go @@ -12,6 +12,7 @@ import ( k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/vars/aws" + "github.com/kluctl/kluctl/v2/pkg/vars/vault" "github.com/kluctl/kluctl/v2/pkg/yaml" "io/ioutil" "os" @@ -81,6 +82,8 @@ func (v *VarsLoader) LoadVars(varsCtx *VarsCtx, sourceIn *types.VarsSource, sear return v.loadHttp(varsCtx, &source, rootKey) } else if source.AwsSecretsManager != nil { return v.loadAwsSecretsManager(varsCtx, &source, rootKey) + } else if source.Vault != nil { + return v.loadVault(varsCtx, &source, rootKey) } return fmt.Errorf("invalid vars source") } @@ -139,6 +142,14 @@ func (v *VarsLoader) loadAwsSecretsManager(varsCtx *VarsCtx, source *types.VarsS return v.loadFromString(varsCtx, secret, rootKey) } +func (v *VarsLoader) loadVault(varsCtx *VarsCtx, source *types.VarsSource, rootKey string) error { + secret, err := vault.GetSecret(source.Vault.Server, source.Vault.Key) + if err != nil { + return err + } + return v.loadFromString(varsCtx, secret, rootKey) +} + func (v *VarsLoader) loadGit(varsCtx *VarsCtx, gitFile *types.VarsSourceGit, rootKey string) error { clonedDir, _, err := v.rp.GetClonedDir(gitFile.Url, gitFile.Ref) if err != nil { diff --git a/pkg/vars/vault/secrets.go b/pkg/vars/vault/secrets.go new file mode 100644 index 000000000..0a05e6579 --- /dev/null +++ b/pkg/vars/vault/secrets.go @@ -0,0 +1,31 @@ +package vault + +import ( + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/hashicorp/vault/api" +) + +var httpClient = &http.Client{ + Timeout: 15 * time.Second, +} + +func GetSecret(server string, key string) (string, error) { + client, err := api.NewClient(&api.Config{Address: server, HttpClient: httpClient}) + if err != nil { + return "", fmt.Errorf("failed to create vault %s client", server) + } + secret, err := client.Logical().Read(key) + if err != nil { + return "", fmt.Errorf("connection to vault %s failed", server) + } + if secret == nil || secret.Data == nil { + return "", fmt.Errorf("the specified vault secret was not found") + } + data, _ := secret.Data["data"].(map[string]interface{}) + jsonData, _ := json.Marshal(data) + return string(jsonData), nil +} From 5d5f316f43424b44692666735f4a48e9e4a1159b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 30 May 2022 15:58:44 +0200 Subject: [PATCH 0892/2916] fix: Move prompts handling into status handler This allows to have prompts while there are still status lines being displayed. Fixes: #37 --- cmd/kluctl/commands/cmd_delete.go | 9 ++-- cmd/kluctl/commands/cmd_deploy.go | 10 ++-- cmd/kluctl/commands/cmd_downscale.go | 4 +- cmd/kluctl/commands/cmd_poke_images.go | 4 +- cmd/kluctl/commands/cmd_prune.go | 2 +- pkg/git/auth/list_auth_provider.go | 2 +- pkg/git/auth/ssh_auth_provider.go | 7 +-- pkg/git/auth/ssh_known_hosts.go | 14 ++--- pkg/status/noop.go | 6 +++ pkg/{utils/prompts.go => status/promts.go} | 61 +++++++--------------- pkg/status/simple_status_handler.go | 27 ++++++++++ pkg/status/status.go | 7 +++ pkg/status/status_handler.go | 22 ++++++++ pkg/utils/term/read_line.go | 48 +++++++++++++++++ pkg/utils/term/term_unix.go | 38 ++++++++++++++ pkg/utils/term/term_unix_bsd.go | 13 +++++ pkg/utils/term/term_unix_other.go | 13 +++++ pkg/utils/term/term_windows.go | 37 +++++++++++++ pkg/vars/vars_loader_http.go | 4 +- 19 files changed, 257 insertions(+), 71 deletions(-) rename pkg/{utils/prompts.go => status/promts.go} (51%) create mode 100644 pkg/utils/term/read_line.go create mode 100644 pkg/utils/term/term_unix.go create mode 100644 pkg/utils/term/term_unix_bsd.go create mode 100644 pkg/utils/term/term_unix_other.go create mode 100644 pkg/utils/term/term_windows.go diff --git a/cmd/kluctl/commands/cmd_delete.go b/cmd/kluctl/commands/cmd_delete.go index 4911d9479..368ccd554 100644 --- a/cmd/kluctl/commands/cmd_delete.go +++ b/cmd/kluctl/commands/cmd_delete.go @@ -1,15 +1,16 @@ package commands import ( + "context" "fmt" "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/deployment" "github.com/kluctl/kluctl/v2/pkg/deployment/commands" "github.com/kluctl/kluctl/v2/pkg/deployment/utils" "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" - utils2 "github.com/kluctl/kluctl/v2/pkg/utils" "os" ) @@ -59,7 +60,7 @@ func (cmd *deleteCmd) Run() error { if err != nil { return err } - result, err := confirmedDeleteObjects(ctx.targetCtx.SharedContext.K, objects, cmd.DryRun, cmd.Yes) + result, err := confirmedDeleteObjects(ctx.ctx, ctx.targetCtx.SharedContext.K, objects, cmd.DryRun, cmd.Yes) if err != nil { return err } @@ -74,14 +75,14 @@ func (cmd *deleteCmd) Run() error { }) } -func confirmedDeleteObjects(k *k8s.K8sCluster, refs []k8s2.ObjectRef, dryRun bool, forceYes bool) (*types.CommandResult, error) { +func confirmedDeleteObjects(ctx context.Context, k *k8s.K8sCluster, refs []k8s2.ObjectRef, dryRun bool, forceYes bool) (*types.CommandResult, error) { if len(refs) != 0 { _, _ = os.Stderr.WriteString("The following objects will be deleted:\n") for _, ref := range refs { _, _ = os.Stderr.WriteString(fmt.Sprintf(" %s\n", ref.String())) } if !forceYes && !dryRun { - if !utils2.AskForConfirmation(fmt.Sprintf("Do you really want to delete %d objects?", len(refs))) { + if !status.AskForConfirmation(ctx, fmt.Sprintf("Do you really want to delete %d objects?", len(refs))) { return nil, fmt.Errorf("aborted") } } diff --git a/cmd/kluctl/commands/cmd_deploy.go b/cmd/kluctl/commands/cmd_deploy.go index a2b6d70a9..8f2d5a8d4 100644 --- a/cmd/kluctl/commands/cmd_deploy.go +++ b/cmd/kluctl/commands/cmd_deploy.go @@ -4,9 +4,8 @@ import ( "fmt" "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/deployment/commands" + "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" - "github.com/kluctl/kluctl/v2/pkg/utils" - "time" ) type deployCmd struct { @@ -78,9 +77,6 @@ func (cmd *deployCmd) runCmdDeploy(ctx *commandCtx) error { } func (cmd *deployCmd) diffResultCb(diffResult *types.CommandResult) error { - // workaround to ensure that progress has been completely written/updated - time.Sleep(130 * time.Millisecond) - err := outputCommandResult(nil, diffResult) if err != nil { return err @@ -89,11 +85,11 @@ func (cmd *deployCmd) diffResultCb(diffResult *types.CommandResult) error { return nil } if len(diffResult.Errors) != 0 { - if !utils.AskForConfirmation("\nThe diff resulted in errors, do you still want to proceed?") { + if !status.AskForConfirmation(cliCtx, "The diff resulted in errors, do you still want to proceed?") { return fmt.Errorf("aborted") } } else { - if !utils.AskForConfirmation("\nThe diff succeeded, do you want to proceed?") { + if !status.AskForConfirmation(cliCtx, "The diff succeeded, do you want to proceed?") { return fmt.Errorf("aborted") } } diff --git a/cmd/kluctl/commands/cmd_downscale.go b/cmd/kluctl/commands/cmd_downscale.go index 304b5cb1c..ac2d0a610 100644 --- a/cmd/kluctl/commands/cmd_downscale.go +++ b/cmd/kluctl/commands/cmd_downscale.go @@ -4,7 +4,7 @@ import ( "fmt" "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/deployment/commands" - "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/status" ) type downscaleCmd struct { @@ -37,7 +37,7 @@ func (cmd *downscaleCmd) Run() error { } return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { if !cmd.Yes && !cmd.DryRun { - if !utils.AskForConfirmation(fmt.Sprintf("Do you really want to downscale on context/cluster %s?", ctx.targetCtx.ClusterContext)) { + if !status.AskForConfirmation(cliCtx, fmt.Sprintf("Do you really want to downscale on context/cluster %s?", ctx.targetCtx.ClusterContext)) { return fmt.Errorf("aborted") } } diff --git a/cmd/kluctl/commands/cmd_poke_images.go b/cmd/kluctl/commands/cmd_poke_images.go index 67e7c5d0a..e08e152c8 100644 --- a/cmd/kluctl/commands/cmd_poke_images.go +++ b/cmd/kluctl/commands/cmd_poke_images.go @@ -4,7 +4,7 @@ import ( "fmt" "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/deployment/commands" - "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/status" ) type pokeImagesCmd struct { @@ -37,7 +37,7 @@ func (cmd *pokeImagesCmd) Run() error { } return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { if !cmd.Yes && !cmd.DryRun { - if !utils.AskForConfirmation(fmt.Sprintf("Do you really want to poke images to the context/cluster %s?", ctx.targetCtx.ClusterContext)) { + if !status.AskForConfirmation(cliCtx, fmt.Sprintf("Do you really want to poke images to the context/cluster %s?", ctx.targetCtx.ClusterContext)) { return fmt.Errorf("aborted") } } diff --git a/cmd/kluctl/commands/cmd_prune.go b/cmd/kluctl/commands/cmd_prune.go index d9c25852a..3610aed88 100644 --- a/cmd/kluctl/commands/cmd_prune.go +++ b/cmd/kluctl/commands/cmd_prune.go @@ -47,7 +47,7 @@ func (cmd *pruneCmd) runCmdPrune(ctx *commandCtx) error { if err != nil { return err } - result, err := confirmedDeleteObjects(ctx.targetCtx.SharedContext.K, objects, cmd.DryRun, cmd.Yes) + result, err := confirmedDeleteObjects(ctx.ctx, ctx.targetCtx.SharedContext.K, objects, cmd.DryRun, cmd.Yes) if err != nil { return err } diff --git a/pkg/git/auth/list_auth_provider.go b/pkg/git/auth/list_auth_provider.go index 241a8d5b2..4faa19b70 100644 --- a/pkg/git/auth/list_auth_provider.go +++ b/pkg/git/auth/list_auth_provider.go @@ -67,7 +67,7 @@ func (a *ListAuthProvider) BuildAuth(ctx context.Context, gitUrl git_url.GitUrl) if err != nil { status.Trace(ctx, "Failed to parse private key: %v", err) } else { - a.HostKeyCallback = buildVerifyHostCallback(e.KnownHosts) + a.HostKeyCallback = buildVerifyHostCallback(ctx, e.KnownHosts) return AuthMethodAndCA{ AuthMethod: a, } diff --git a/pkg/git/auth/ssh_auth_provider.go b/pkg/git/auth/ssh_auth_provider.go index 686e90b47..0ec025aa1 100644 --- a/pkg/git/auth/ssh_auth_provider.go +++ b/pkg/git/auth/ssh_auth_provider.go @@ -43,7 +43,7 @@ func (a *sshDefaultIdentityAndAgent) ClientConfig() (*ssh.ClientConfig, error) { User: a.user, Auth: []ssh.AuthMethod{ssh.PublicKeysCallback(a.Signers)}, } - cc.HostKeyCallback = buildVerifyHostCallback(nil) + cc.HostKeyCallback = buildVerifyHostCallback(a.ctx, nil) return cc, nil } @@ -148,13 +148,14 @@ func (k *deferredPassphraseKey) getPassphrase() ([]byte, error) { return passphrase, nil } - passphraseStr, err := utils.AskForPassword(fmt.Sprintf("Enter passphrase for key '%s'", k.path)) + passphraseStr, err := status.AskForPassword(k.ctx, fmt.Sprintf("Enter passphrase for key '%s'", k.path)) if err != nil { k.a.passphrases[k.path] = nil return nil, err } + passphrase = []byte(passphraseStr) k.a.passphrases[k.path] = passphrase - return []byte(passphraseStr), nil + return passphrase, nil } func (k *deferredPassphraseKey) parse() { diff --git a/pkg/git/auth/ssh_known_hosts.go b/pkg/git/auth/ssh_known_hosts.go index e5d522d6e..ec5df2966 100644 --- a/pkg/git/auth/ssh_known_hosts.go +++ b/pkg/git/auth/ssh_known_hosts.go @@ -1,8 +1,10 @@ package auth import ( + "context" "fmt" "github.com/kluctl/kluctl/v2/pkg/git/auth/goph" + "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/utils" "golang.org/x/crypto/ssh" "net" @@ -14,13 +16,13 @@ import ( var askHostMutex sync.Mutex -func buildVerifyHostCallback(knownHosts []byte) func(hostname string, remote net.Addr, key ssh.PublicKey) error { +func buildVerifyHostCallback(ctx context.Context, knownHosts []byte) func(hostname string, remote net.Addr, key ssh.PublicKey) error { return func(hostname string, remote net.Addr, key ssh.PublicKey) error { - return verifyHost(hostname, remote, key, knownHosts) + return verifyHost(ctx, hostname, remote, key, knownHosts) } } -func verifyHost(host string, remote net.Addr, key ssh.PublicKey, knownHosts []byte) error { +func verifyHost(ctx context.Context, host string, remote net.Addr, key ssh.PublicKey, knownHosts []byte) error { // Ensure only one prompt happens at a time askHostMutex.Lock() defer askHostMutex.Unlock() @@ -85,14 +87,14 @@ func verifyHost(host string, remote net.Addr, key ssh.PublicKey, knownHosts []by return fmt.Errorf("host not found and SSH_KNOWN_HOSTS has been set") } - if !askIsHostTrusted(host, key) { + if !askIsHostTrusted(ctx, host, key) { return fmt.Errorf("aborted") } return goph.AddKnownHost(host, remote, key, "") } -func askIsHostTrusted(host string, key ssh.PublicKey) bool { +func askIsHostTrusted(ctx context.Context, host string, key ssh.PublicKey) bool { prompt := fmt.Sprintf("Unknown Host: %s\nFingerprint: %s\nWould you like to add it? ", host, ssh.FingerprintSHA256(key)) - return utils.AskForConfirmation(prompt) + return status.AskForConfirmation(ctx, prompt) } diff --git a/pkg/status/noop.go b/pkg/status/noop.go index 1f30341a0..24c24ed5c 100644 --- a/pkg/status/noop.go +++ b/pkg/status/noop.go @@ -1,5 +1,7 @@ package status +import "fmt" + type NoopStatusHandler struct { } @@ -34,6 +36,10 @@ func (n NoopStatusHandler) PlainText(text string) { func (n NoopStatusHandler) InfoFallback(message string) { } +func (n NoopStatusHandler) Prompt(password bool, message string) (string, error) { + return "", fmt.Errorf("Prompt not implemented in NoopStatusHandler") +} + var _ StatusHandler = &NoopStatusHandler{} func (n NoopStatusLine) SetTotal(total int) { diff --git a/pkg/utils/prompts.go b/pkg/status/promts.go similarity index 51% rename from pkg/utils/prompts.go rename to pkg/status/promts.go index f95098796..8274e8c40 100644 --- a/pkg/utils/prompts.go +++ b/pkg/status/promts.go @@ -1,98 +1,73 @@ -package utils +package status import ( - "bufio" + "context" "fmt" + "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/mattn/go-isatty" - "golang.org/x/term" "os" "strings" - "syscall" ) -func doWarn(f string, args ...any) { - _, _ = fmt.Fprintf(os.Stderr, f, args...) -} - // AskForConfirmation uses Scanln to parse user input. A user must type in "yes" or "no" and // then press enter. It has fuzzy matching, so "y", "Y", "yes", "YES", and "Yes" all count as // confirmations. If the input is not recognized, it will ask again. The function does not return // until it gets a valid response from the user. Typically, you should use fmt to print out a question // before calling askForConfirmation. E.g. fmt.Println("WARNING: Are you sure? (yes/no)") -func AskForConfirmation(prompt string) bool { +func AskForConfirmation(ctx context.Context, prompt string) bool { if !isatty.IsTerminal(os.Stderr.Fd()) { - doWarn("Not a terminal, suppressed prompt: %s", prompt) + Warning(ctx, "Not a terminal, suppressed prompt: %s", prompt) return false } - _, err := os.Stderr.WriteString(prompt + " (y/N) ") - if err != nil { - panic(err) - } - - var response string - _, err = fmt.Scanln(&response) + response, err := Prompt(ctx, false, prompt+" (y/N) ") if err != nil { return false } okayResponses := []string{"y", "Y", "yes", "Yes", "YES"} nokayResponses := []string{"n", "N", "no", "No", "NO"} - if FindStrInSlice(okayResponses, response) != -1 { + if utils.FindStrInSlice(okayResponses, response) != -1 { return true - } else if FindStrInSlice(nokayResponses, response) != -1 || response == "" { + } else if utils.FindStrInSlice(nokayResponses, response) != -1 || response == "" { return false } else { - fmt.Println("Please type yes or no and then press enter:") - return AskForConfirmation(prompt) + Warning(ctx, "Please type yes or no and then press enter!") + return AskForConfirmation(ctx, prompt) } } -func AskForPassword(prompt string) (string, error) { +func AskForPassword(ctx context.Context, prompt string) (string, error) { if !isatty.IsTerminal(os.Stderr.Fd()) { err := fmt.Errorf("not a terminal, suppressed credentials prompt: %s", prompt) - doWarn(err.Error()) - return "", err - } - - _, err := fmt.Fprintf(os.Stderr, "%s: ", prompt) - if err != nil { + Warning(ctx, err.Error()) return "", err } - bytePassword, err := term.ReadPassword(int(syscall.Stdin)) - _, _ = fmt.Fprintf(os.Stderr, "\n") + password, err := Prompt(ctx, true, fmt.Sprintf("%s: ", prompt)) if err != nil { return "", err } - password := string(bytePassword) return strings.TrimSpace(password), nil } -func AskForCredentials(prompt string) (string, string, error) { +func AskForCredentials(ctx context.Context, prompt string) (string, string, error) { if !isatty.IsTerminal(os.Stderr.Fd()) { err := fmt.Errorf("not a terminal, suppressed credentials prompt: %s", prompt) - doWarn(err.Error()) + Warning(ctx, err.Error()) return "", "", err } - reader := bufio.NewReader(os.Stdin) + Info(ctx, prompt) - _, err := fmt.Fprintf(os.Stderr, prompt+"\n") + username, err := Prompt(ctx, false, "Enter Username: ") if err != nil { return "", "", err } - fmt.Fprint(os.Stderr, "Enter Username: ") - username, err := reader.ReadString('\n') + password, err := Prompt(ctx, true, "Enter Password: ") if err != nil { return "", "", err } - - password, err := AskForPassword("Enter Password") - if err != nil { - return "", "", err - } - return strings.TrimSpace(username), strings.TrimSpace(password), nil } diff --git a/pkg/status/simple_status_handler.go b/pkg/status/simple_status_handler.go index 21a55a6ba..03c2509c8 100644 --- a/pkg/status/simple_status_handler.go +++ b/pkg/status/simple_status_handler.go @@ -1,5 +1,12 @@ package status +import ( + "fmt" + "golang.org/x/term" + "os" + "syscall" +) + type simpleStatusHandler struct { cb func(message string) trace bool @@ -55,6 +62,26 @@ func (s *simpleStatusHandler) InfoFallback(message string) { s.Info(message) } +func (s *simpleStatusHandler) Prompt(password bool, message string) (string, error) { + s.cb(message) + + if password { + bytePassword, err := term.ReadPassword(int(syscall.Stdin)) + _, _ = fmt.Fprintf(os.Stderr, "\n") + if err != nil { + return "", err + } + return string(bytePassword), nil + } else { + var response string + _, err := fmt.Scanln(&response) + if err != nil { + return "", err + } + return response, nil + } +} + func (sl *simpleStatusLine) SetTotal(total int) { } diff --git a/pkg/status/status.go b/pkg/status/status.go index f1f60395c..52faa225a 100644 --- a/pkg/status/status.go +++ b/pkg/status/status.go @@ -48,6 +48,8 @@ type StatusHandler interface { PlainText(text string) InfoFallback(message string) + + Prompt(password bool, message string) (string, error) } type contextKey struct{} @@ -233,3 +235,8 @@ func Error(ctx context.Context, status string, args ...any) { slh := FromContext(ctx) slh.Error(fmt.Sprintf(status, args...)) } + +func Prompt(ctx context.Context, password bool, message string, args ...any) (string, error) { + slh := FromContext(ctx) + return slh.Prompt(password, fmt.Sprintf(message, args...)) +} diff --git a/pkg/status/status_handler.go b/pkg/status/status_handler.go index c0205333a..f5f437acb 100644 --- a/pkg/status/status_handler.go +++ b/pkg/status/status_handler.go @@ -3,13 +3,17 @@ package status import ( "context" "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/term" "github.com/vbauerster/mpb/v7" "github.com/vbauerster/mpb/v7/decor" "io" "math" + "strings" + "syscall" ) type MultiLineStatusHandler struct { + ctx context.Context out io.Writer progress *mpb.Progress stop chan any @@ -119,6 +123,24 @@ func (s *MultiLineStatusHandler) PlainText(text string) { s.Info(text) } +func (s *MultiLineStatusHandler) Prompt(password bool, message string) (string, error) { + o := withColor("yellow", "?") + sl := s.startStatus(1, message, math.MinInt, o) + defer sl.end(o) + + doUpdate := func(ret []byte) { + if password { + sl.Update(message + strings.Repeat("*", len(ret))) + } else { + sl.Update(message + string(ret)) + } + } + + ret, err := term.ReadLineNoEcho(int(syscall.Stdin), doUpdate) + + return string(ret), err +} + func (sl *statusLine) SetTotal(total int) { sl.total = total sl.bar.SetTotal(int64(total), false) diff --git a/pkg/utils/term/read_line.go b/pkg/utils/term/read_line.go new file mode 100644 index 000000000..910b039e7 --- /dev/null +++ b/pkg/utils/term/read_line.go @@ -0,0 +1,48 @@ +package term + +import ( + "io" + "runtime" +) + +func ReadLineNoEcho(fd int, cb func(ret []byte)) ([]byte, error) { + return readLineNoEcho(fd, cb) +} + +func readLine(reader io.Reader, cb func(ret []byte)) ([]byte, error) { + var buf [1]byte + var ret []byte + + for { + n, err := reader.Read(buf[:]) + if n > 0 { + switch buf[0] { + case '', '\b': + if len(ret) > 0 { + ret = ret[:len(ret)-1] + } + cb(ret) + case '\n': + if runtime.GOOS != "windows" { + return ret, nil + } + // otherwise ignore \n + case '\r': + if runtime.GOOS == "windows" { + return ret, nil + } + // otherwise ignore \r + default: + ret = append(ret, buf[0]) + cb(ret) + } + continue + } + if err != nil { + if err == io.EOF && len(ret) > 0 { + return ret, nil + } + return ret, err + } + } +} diff --git a/pkg/utils/term/term_unix.go b/pkg/utils/term/term_unix.go new file mode 100644 index 000000000..399785a6a --- /dev/null +++ b/pkg/utils/term/term_unix.go @@ -0,0 +1,38 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos +// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris zos + +package term + +import ( + "golang.org/x/sys/unix" +) + +// passwordReader is an io.Reader that reads from a specific file descriptor. +type passwordReader int + +func (r passwordReader) Read(buf []byte) (int, error) { + return unix.Read(int(r), buf) +} + +func readLineNoEcho(fd int, cb func(ret []byte)) ([]byte, error) { + termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios) + if err != nil { + return nil, err + } + + newState := *termios + newState.Lflag &^= unix.ECHO | unix.ICANON + newState.Lflag |= unix.ISIG + newState.Iflag |= unix.ICRNL + if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, &newState); err != nil { + return nil, err + } + + defer unix.IoctlSetTermios(fd, ioctlWriteTermios, termios) + + return readLine(passwordReader(fd), cb) +} diff --git a/pkg/utils/term/term_unix_bsd.go b/pkg/utils/term/term_unix_bsd.go new file mode 100644 index 000000000..853b3d698 --- /dev/null +++ b/pkg/utils/term/term_unix_bsd.go @@ -0,0 +1,13 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build darwin || dragonfly || freebsd || netbsd || openbsd +// +build darwin dragonfly freebsd netbsd openbsd + +package term + +import "golang.org/x/sys/unix" + +const ioctlReadTermios = unix.TIOCGETA +const ioctlWriteTermios = unix.TIOCSETA diff --git a/pkg/utils/term/term_unix_other.go b/pkg/utils/term/term_unix_other.go new file mode 100644 index 000000000..1e8955c93 --- /dev/null +++ b/pkg/utils/term/term_unix_other.go @@ -0,0 +1,13 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build aix || linux || solaris || zos +// +build aix linux solaris zos + +package term + +import "golang.org/x/sys/unix" + +const ioctlReadTermios = unix.TCGETS +const ioctlWriteTermios = unix.TCSETS diff --git a/pkg/utils/term/term_windows.go b/pkg/utils/term/term_windows.go new file mode 100644 index 000000000..82ea4fa81 --- /dev/null +++ b/pkg/utils/term/term_windows.go @@ -0,0 +1,37 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package term + +import ( + "os" + + "golang.org/x/sys/windows" +) + +func readLineNoEcho(fd int, cb func(ret []byte)) ([]byte, error) { + var st uint32 + if err := windows.GetConsoleMode(windows.Handle(fd), &st); err != nil { + return nil, err + } + old := st + + st &^= (windows.ENABLE_ECHO_INPUT | windows.ENABLE_LINE_INPUT) + st |= (windows.ENABLE_PROCESSED_OUTPUT | windows.ENABLE_PROCESSED_INPUT) + if err := windows.SetConsoleMode(windows.Handle(fd), st); err != nil { + return nil, err + } + + defer windows.SetConsoleMode(windows.Handle(fd), old) + + var h windows.Handle + p, _ := windows.GetCurrentProcess() + if err := windows.DuplicateHandle(p, windows.Handle(fd), p, &h, 0, false, windows.DUPLICATE_SAME_ACCESS); err != nil { + return nil, err + } + + f := os.NewFile(uintptr(h), "stdin") + defer f.Close() + return readLine(f, cb) +} diff --git a/pkg/vars/vars_loader_http.go b/pkg/vars/vars_loader_http.go index 18cd60695..379e1730e 100644 --- a/pkg/vars/vars_loader_http.go +++ b/pkg/vars/vars_loader_http.go @@ -5,8 +5,8 @@ import ( "fmt" "github.com/Azure/go-ntlmssp" "github.com/docker/distribution/registry/client/auth/challenge" + "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" - "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" "io" @@ -85,7 +85,7 @@ func (v *VarsLoader) loadHttp(varsCtx *VarsCtx, source *types.VarsSource, rootKe credsKey := fmt.Sprintf("%s|%s", source.Http.Url.Host, strings.Join(realms, "+")) creds, ok := v.credentialsCache[credsKey] if !ok { - username, password, err := utils.AskForCredentials(fmt.Sprintf("Please enter credentials for host '%s'", source.Http.Url.Host)) + username, password, err := status.AskForCredentials(v.ctx, fmt.Sprintf("Please enter credentials for host '%s'", source.Http.Url.Host)) if err != nil { return err } From 6f45f60298eefe3a7d7d7c88c59aa460b262b046 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 30 May 2022 15:59:23 +0200 Subject: [PATCH 0893/2916] fix: Flush status lines before printing results --- cmd/kluctl/commands/command_result.go | 7 +++++++ pkg/status/noop.go | 3 +++ pkg/status/simple_status_handler.go | 3 +++ pkg/status/status.go | 6 ++++++ pkg/status/status_handler.go | 24 ++++++++++++++++-------- 5 files changed, 35 insertions(+), 8 deletions(-) diff --git a/cmd/kluctl/commands/command_result.go b/cmd/kluctl/commands/command_result.go index bd2e8cd59..668e21ec2 100644 --- a/cmd/kluctl/commands/command_result.go +++ b/cmd/kluctl/commands/command_result.go @@ -3,6 +3,7 @@ package commands import ( "bytes" "fmt" + "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/utils" @@ -193,18 +194,24 @@ func outputHelper(output []string, cb func(format string) (string, error)) error } func outputCommandResult(output []string, cr *types.CommandResult) error { + status.Flush(cliCtx) + return outputHelper(output, func(format string) (string, error) { return formatCommandResult(cr, format) }) } func outputValidateResult(output []string, vr *types.ValidateResult) error { + status.Flush(cliCtx) + return outputHelper(output, func(format string) (string, error) { return formatValidateResult(vr, format) }) } func outputYamlResult(output []string, result interface{}, multiDoc bool) error { + status.Flush(cliCtx) + if len(output) == 0 { output = []string{"-"} } diff --git a/pkg/status/noop.go b/pkg/status/noop.go index 24c24ed5c..c1ff08a9e 100644 --- a/pkg/status/noop.go +++ b/pkg/status/noop.go @@ -14,6 +14,9 @@ func (n NoopStatusHandler) SetTrace(trace bool) { func (n NoopStatusHandler) Stop() { } +func (n NoopStatusHandler) Flush() { +} + func (n NoopStatusHandler) StartStatus(total int, message string) StatusLine { return &NoopStatusLine{} } diff --git a/pkg/status/simple_status_handler.go b/pkg/status/simple_status_handler.go index 03c2509c8..2f7cc9772 100644 --- a/pkg/status/simple_status_handler.go +++ b/pkg/status/simple_status_handler.go @@ -29,6 +29,9 @@ func (s *simpleStatusHandler) SetTrace(trace bool) { func (s *simpleStatusHandler) Stop() { } +func (s *simpleStatusHandler) Flush() { +} + func (s *simpleStatusHandler) StartStatus(total int, message string) StatusLine { if message != "" { s.Info(message) diff --git a/pkg/status/status.go b/pkg/status/status.go index 52faa225a..0e0514e41 100644 --- a/pkg/status/status.go +++ b/pkg/status/status.go @@ -39,6 +39,7 @@ type StatusLine interface { type StatusHandler interface { SetTrace(trace bool) Stop() + Flush() StartStatus(total int, message string) StatusLine Info(message string) @@ -240,3 +241,8 @@ func Prompt(ctx context.Context, password bool, message string, args ...any) (st slh := FromContext(ctx) return slh.Prompt(password, fmt.Sprintf(message, args...)) } + +func Flush(ctx context.Context) { + slh := FromContext(ctx) + slh.Flush() +} diff --git a/pkg/status/status_handler.go b/pkg/status/status_handler.go index f5f437acb..d385afb76 100644 --- a/pkg/status/status_handler.go +++ b/pkg/status/status_handler.go @@ -16,7 +16,6 @@ type MultiLineStatusHandler struct { ctx context.Context out io.Writer progress *mpb.Progress - stop chan any trace bool } @@ -32,25 +31,34 @@ type statusLine struct { func NewMultiLineStatusHandler(ctx context.Context, out io.Writer, trace bool) *MultiLineStatusHandler { sh := &MultiLineStatusHandler{ + ctx: ctx, out: out, trace: trace, - stop: make(chan any), } - sh.progress = mpb.NewWithContext( - ctx, - mpb.WithWidth(utils.GetTermWidth()), - mpb.WithOutput(out), - mpb.PopCompletedMode(), - ) + sh.start() return sh } +func (s *MultiLineStatusHandler) Flush() { + s.Stop() + s.start() +} + func (s *MultiLineStatusHandler) SetTrace(trace bool) { s.trace = trace } +func (s *MultiLineStatusHandler) start() { + s.progress = mpb.NewWithContext( + s.ctx, + mpb.WithWidth(utils.GetTermWidth()), + mpb.WithOutput(s.out), + mpb.PopCompletedMode(), + ) +} + func (s *MultiLineStatusHandler) Stop() { s.progress.Wait() } From f1869534f24843ab97dde650d202151a59356e41 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 31 May 2022 08:36:37 +0200 Subject: [PATCH 0894/2916] tests: Stop using cluster config in most tests Except for external_projecs_tests as these test only deprecated functionality. --- e2e/deploy_test.go | 13 +++++++---- e2e/external_projects_test.go | 6 ++--- e2e/hooks_test.go | 8 +++---- e2e/inclusion_test.go | 8 +++---- e2e/project.go | 43 +++++++++++++++++++++++------------ 5 files changed, 46 insertions(+), 32 deletions(-) diff --git a/e2e/deploy_test.go b/e2e/deploy_test.go index 64b57dbd4..918559e2f 100644 --- a/e2e/deploy_test.go +++ b/e2e/deploy_test.go @@ -1,18 +1,21 @@ package e2e -import "testing" +import ( + "testing" +) func TestCommandDeploySimple(t *testing.T) { t.Parallel() + + k := defaultKindCluster + p := &testProject{} - p.init(t, "simple") + p.init(t, k, "simple") defer p.cleanup() - k := defaultKindCluster recreateNamespace(t, k, p.projectName) - p.updateKindCluster(k, nil) - p.updateTarget("test", k.Name, nil) + p.updateTarget("test", nil) addConfigMapDeployment(p, "cm", nil, resourceOpts{ name: "cm", diff --git a/e2e/external_projects_test.go b/e2e/external_projects_test.go index 87a972634..11b104d82 100644 --- a/e2e/external_projects_test.go +++ b/e2e/external_projects_test.go @@ -10,7 +10,7 @@ import ( func doTestProject(t *testing.T, namespace string, p *testProject) { k := defaultKindCluster - p.init(t, fmt.Sprintf("project-%s", namespace)) + p.init(t, k, fmt.Sprintf("project-%s", namespace)) defer p.cleanup() recreateNamespace(t, k, namespace) @@ -18,7 +18,7 @@ func doTestProject(t *testing.T, namespace string, p *testProject) { p.updateKindCluster(k, uo.FromMap(map[string]interface{}{ "cluster_var": "cluster_value1", })) - p.updateTarget("test", k.Name, uo.FromMap(map[string]interface{}{ + p.updateTargetDeprecated("test", k.Name, uo.FromMap(map[string]interface{}{ "target_var": "target_value1", })) addBusyboxDeployment(p, "busybox", resourceOpts{name: "busybox", namespace: namespace}) @@ -47,7 +47,7 @@ func doTestProject(t *testing.T, namespace string, p *testProject) { assertNestedFieldEquals(t, o, "cluster_value2", "data", "cluster_var") assertNestedFieldEquals(t, o, "target_value1", "data", "target_var") - p.updateTarget("test", k.Name, uo.FromMap(map[string]interface{}{ + p.updateTargetDeprecated("test", k.Name, uo.FromMap(map[string]interface{}{ "target_var": "target_value2", })) p.KluctlMust("deploy", "--yes", "-t", "test") diff --git a/e2e/hooks_test.go b/e2e/hooks_test.go index 7e6b4557e..161dd8285 100644 --- a/e2e/hooks_test.go +++ b/e2e/hooks_test.go @@ -109,20 +109,18 @@ func assertHookResultNotCMName(t *testing.T, p *testProject, k *KindCluster, sec func prepareHookTestProject(t *testing.T, name string, hook string, hookDeletionPolicy string) (*testProject, *KindCluster) { isDone := false namespace := fmt.Sprintf("hook-%s", name) + k := defaultKindCluster p := &testProject{} - p.init(t, namespace) + p.init(t, k, namespace) defer func() { if !isDone { p.cleanup() } }() - k := defaultKindCluster - recreateNamespace(t, k, namespace) - p.updateKindCluster(k, nil) - p.updateTarget("test", k.Name, nil) + p.updateTarget("test", nil) addHookDeployment(p, "hook", resourceOpts{name: "hook", namespace: namespace}, false, hook, hookDeletionPolicy) addConfigMap(p, "hook", resourceOpts{name: "cm1", namespace: namespace}) diff --git a/e2e/inclusion_test.go b/e2e/inclusion_test.go index c7502a361..55e7be2ff 100644 --- a/e2e/inclusion_test.go +++ b/e2e/inclusion_test.go @@ -10,20 +10,18 @@ import ( func prepareInclusionTestProject(t *testing.T, namespace string, withIncludes bool) (*testProject, *KindCluster) { isDone := false + k := defaultKindCluster p := &testProject{} - p.init(t, namespace) + p.init(t, k, namespace) defer func() { if !isDone { p.cleanup() } }() - k := defaultKindCluster - recreateNamespace(t, k, p.projectName) - p.updateKindCluster(k, nil) - p.updateTarget("test", k.Name, nil) + p.updateTarget("test", nil) addConfigMapDeployment(p, "cm1", nil, resourceOpts{name: "cm1", namespace: p.projectName}) addConfigMapDeployment(p, "cm2", nil, resourceOpts{name: "cm2", namespace: p.projectName}) diff --git a/e2e/project.go b/e2e/project.go index fa5f18e8c..9713265bd 100644 --- a/e2e/project.go +++ b/e2e/project.go @@ -4,7 +4,6 @@ import ( "fmt" "github.com/go-git/go-git/v5" test_utils "github.com/kluctl/kluctl/v2/internal/test-utils" - "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" "os" @@ -29,13 +28,15 @@ type testProject struct { localDeployment *string localSealedSecrets *string + k *KindCluster kubeconfigs []string gitServer *test_utils.GitServer } -func (p *testProject) init(t *testing.T, projectName string) { +func (p *testProject) init(t *testing.T, k *KindCluster, projectName string) { p.t = t + p.k = k p.gitServer = test_utils.NewGitServer(t) p.projectName = projectName @@ -68,6 +69,8 @@ func (p *testProject) init(t *testing.T, projectName string) { p.updateDeploymentYaml(".", func(c *uo.UnstructuredObject) error { return nil }) + + p.kubeconfigs = append(p.kubeconfigs, k.Kubeconfig()) } func (p *testProject) cleanup() { @@ -145,9 +148,6 @@ func (p *testProject) updateCluster(name string, context string, vars *uo.Unstru } func (p *testProject) updateKindCluster(k *KindCluster, vars *uo.UnstructuredObject) { - if utils.FindStrInSlice(p.kubeconfigs, k.Kubeconfig()) == -1 { - p.kubeconfigs = append(p.kubeconfigs, k.Kubeconfig()) - } context, err := k.Kubectl("config", "current-context") if err != nil { p.t.Fatal(err) @@ -156,26 +156,41 @@ func (p *testProject) updateKindCluster(k *KindCluster, vars *uo.UnstructuredObj p.updateCluster(k.Name, context, vars) } -func (p *testProject) updateTarget(name string, cluster string, args *uo.UnstructuredObject) { +func (p *testProject) updateTargetDeprecated(name string, cluster string, args *uo.UnstructuredObject) { + p.updateTarget(name, func(target *uo.UnstructuredObject) { + if args != nil { + target.MergeChild("args", args) + } + // compatibility + _ = target.SetNestedField(cluster, "cluster") + }) +} + +func (p *testProject) updateTarget(name string, cb func(target *uo.UnstructuredObject)) { + if cb == nil { + cb = func(target *uo.UnstructuredObject) {} + } + p.updateKluctlYaml(func(o *uo.UnstructuredObject) error { targets, _, _ := o.GetNestedObjectList("targets") var newTargets []*uo.UnstructuredObject + found := false for _, t := range targets { n, _, _ := t.GetNestedString("name") if n == name { - continue + cb(t) + found = true } newTargets = append(newTargets, t) } - n := uo.FromMap(map[string]interface{}{ - "name": name, - "cluster": cluster, - }) - if args != nil { - n.MergeChild("args", args) + if !found { + n := uo.FromMap(map[string]interface{}{ + "name": name, + }) + cb(n) + newTargets = append(newTargets, n) } - newTargets = append(newTargets, n) _ = o.SetNestedObjectList(newTargets, "targets") return nil }) From 51d767ea5eb20d4e4fa0fa6d891dd37b2c65af9f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 31 May 2022 08:45:31 +0200 Subject: [PATCH 0895/2916] ci: Make KIND_KUBECONFIG more configurable --- .github/workflows/tests.yml | 7 ++++--- hack/start-kind-cluster.sh | 10 +++++----- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0f3ffed05..327211e28 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -246,11 +246,12 @@ jobs: else PORT=10002 fi - KIND_CLUSTER_NAME=$(echo "kluctl-${{ runner.os }}" | awk '{{ print tolower($1)}}') + KIND_CLUSTER_NAME=$(echo "kluctl-${{ runner.os }}" | awk '{{ print tolower($1) }}') + KIND_KUBECONFIG=$(pwd)/kind-kubeconfig echo "KIND_CLUSTER_NAME=$KIND_CLUSTER_NAME" >> $GITHUB_ENV - echo "KIND_KUBECONFIG=$(pwd)/kind-kubeconfig" >> $GITHUB_ENV + echo "KIND_KUBECONFIG=$KIND_KUBECONFIG" >> $GITHUB_ENV - ./hack/start-kind-cluster.sh "$KIND_CLUSTER_NAME" "$DOCKER_IP" "$PORT" + ./hack/start-kind-cluster.sh "$KIND_CLUSTER_NAME" "$DOCKER_IP" "$PORT" "$KIND_KUBECONFIG" - name: Download artifacts uses: actions/download-artifact@v2 - name: Run e2e tests diff --git a/hack/start-kind-cluster.sh b/hack/start-kind-cluster.sh index 3c7e737ee..315d6887a 100755 --- a/hack/start-kind-cluster.sh +++ b/hack/start-kind-cluster.sh @@ -5,8 +5,9 @@ set -e NAME=$1 IP=$2 PORT=$3 +export KUBECONFIG=$4 -cat << EOF > kind-cluster.yml +cat << EOF > kind-cluster-$NAME.yml kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 networking: @@ -15,8 +16,7 @@ networking: EOF rm -f $(pwd)/kind-kubeconfig -export KUBECONFIG=$KIND_KUBECONFIG -kind create cluster --config kind-cluster.yml --name $NAME +kind create cluster --config kind-cluster-$NAME.yml --name $NAME # Rewrite cluster info to point to docker host # This also fully disables TLS verification @@ -24,5 +24,5 @@ kubectl config view -ojson --raw \ | jq ".clusters[0].cluster.\"insecure-skip-tls-verify\"=true" \ | jq "del(.clusters[0].cluster.\"certificate-authority-data\")" \ | jq ".clusters[0].cluster.server=\"https://$IP:$PORT\"" \ -> kind-kubeconfig2 -mv kind-kubeconfig2 kind-kubeconfig +> $KUBECONFIG-tmp +mv $KUBECONFIG-tmp $KUBECONFIG From ac1c0b278e8a6e8f302c08d12705301e4b250ce6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 31 May 2022 08:53:13 +0200 Subject: [PATCH 0896/2916] ci: Start 2 kind clusters in parallel Needed for context tests. --- .github/workflows/tests.yml | 31 +++++++++++++++++++++++-------- e2e/deploy_test.go | 2 +- e2e/external_projects_test.go | 2 +- e2e/hooks_test.go | 2 +- e2e/inclusion_test.go | 2 +- e2e/kind_cluster.go | 32 ++++++++++++++++++++++++++------ e2e/utils.go | 3 ++- 7 files changed, 55 insertions(+), 19 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 327211e28..70690c075 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -240,18 +240,33 @@ jobs: shell: bash run: | if [ "${{ runner.os }}" == "Linux" ]; then - PORT=10000 + PORT1=10000 + PORT2=20000 elif [ "${{ runner.os }}" == "Windows" ]; then - PORT=10001 + PORT1=10001 + PORT2=20001 else - PORT=10002 + PORT1=10002 + PORT2=20002 fi - KIND_CLUSTER_NAME=$(echo "kluctl-${{ runner.os }}" | awk '{{ print tolower($1) }}') - KIND_KUBECONFIG=$(pwd)/kind-kubeconfig - echo "KIND_CLUSTER_NAME=$KIND_CLUSTER_NAME" >> $GITHUB_ENV - echo "KIND_KUBECONFIG=$KIND_KUBECONFIG" >> $GITHUB_ENV + KIND_CLUSTER_NAME_BASE=$(echo "kluctl-${{ runner.os }}" | awk '{{ print tolower($1) }}') - ./hack/start-kind-cluster.sh "$KIND_CLUSTER_NAME" "$DOCKER_IP" "$PORT" "$KIND_KUBECONFIG" + KIND_CLUSTER_NAME1=$KIND_CLUSTER_NAME_BASE-1 + KIND_KUBECONFIG1=$(pwd)/kind-kubeconfig-1 + echo "KIND_CLUSTER_NAME1=$KIND_CLUSTER_NAME1" >> $GITHUB_ENV + echo "KIND_KUBECONFIG1=$KIND_KUBECONFIG1" >> $GITHUB_ENV + ./hack/start-kind-cluster.sh "$KIND_CLUSTER_NAME1" "$DOCKER_IP" "$PORT1" "$KIND_KUBECONFIG1" & + PID1=$! + + KIND_CLUSTER_NAME2=$KIND_CLUSTER_NAME_BASE-2 + KIND_KUBECONFIG2=$(pwd)/kind-kubeconfig-2 + echo "KIND_CLUSTER_NAME2=$KIND_CLUSTER_NAME2" >> $GITHUB_ENV + echo "KIND_KUBECONFIG2=$KIND_KUBECONFIG2" >> $GITHUB_ENV + ./hack/start-kind-cluster.sh "$KIND_CLUSTER_NAME2" "$DOCKER_IP" "$PORT2" "$KIND_KUBECONFIG2" & + PID2=$1 + + wait $PID1 + wait $PID2 - name: Download artifacts uses: actions/download-artifact@v2 - name: Run e2e tests diff --git a/e2e/deploy_test.go b/e2e/deploy_test.go index 918559e2f..f8d451032 100644 --- a/e2e/deploy_test.go +++ b/e2e/deploy_test.go @@ -7,7 +7,7 @@ import ( func TestCommandDeploySimple(t *testing.T) { t.Parallel() - k := defaultKindCluster + k := defaultKindCluster1 p := &testProject{} p.init(t, k, "simple") diff --git a/e2e/external_projects_test.go b/e2e/external_projects_test.go index 11b104d82..eefd91b17 100644 --- a/e2e/external_projects_test.go +++ b/e2e/external_projects_test.go @@ -8,7 +8,7 @@ import ( ) func doTestProject(t *testing.T, namespace string, p *testProject) { - k := defaultKindCluster + k := defaultKindCluster1 p.init(t, k, fmt.Sprintf("project-%s", namespace)) defer p.cleanup() diff --git a/e2e/hooks_test.go b/e2e/hooks_test.go index 161dd8285..5c2af1dae 100644 --- a/e2e/hooks_test.go +++ b/e2e/hooks_test.go @@ -109,7 +109,7 @@ func assertHookResultNotCMName(t *testing.T, p *testProject, k *KindCluster, sec func prepareHookTestProject(t *testing.T, name string, hook string, hookDeletionPolicy string) (*testProject, *KindCluster) { isDone := false namespace := fmt.Sprintf("hook-%s", name) - k := defaultKindCluster + k := defaultKindCluster1 p := &testProject{} p.init(t, k, namespace) defer func() { diff --git a/e2e/inclusion_test.go b/e2e/inclusion_test.go index 55e7be2ff..21a3028eb 100644 --- a/e2e/inclusion_test.go +++ b/e2e/inclusion_test.go @@ -10,7 +10,7 @@ import ( func prepareInclusionTestProject(t *testing.T, namespace string, withIncludes bool) (*testProject, *KindCluster) { isDone := false - k := defaultKindCluster + k := defaultKindCluster1 p := &testProject{} p.init(t, k, namespace) defer func() { diff --git a/e2e/kind_cluster.go b/e2e/kind_cluster.go index f420001a2..1096058ea 100644 --- a/e2e/kind_cluster.go +++ b/e2e/kind_cluster.go @@ -14,6 +14,7 @@ import ( "path/filepath" "sigs.k8s.io/kind/pkg/cluster" kindcmd "sigs.k8s.io/kind/pkg/cmd" + "sync" "testing" "time" ) @@ -148,16 +149,35 @@ func createKindCluster(name string, kubeconfig string) *KindCluster { return k } -func createDefaultKindCluster() *KindCluster { - kindClusterName := os.Getenv("KIND_CLUSTER_NAME") - kindKubeconfig := os.Getenv("KIND_KUBECONFIG") +func createDefaultKindCluster(num int) *KindCluster { + kindClusterName := os.Getenv(fmt.Sprintf("KIND_CLUSTER_NAME%d", num)) + kindKubeconfig := os.Getenv(fmt.Sprintf("KIND_KUBECONFIG%d", num)) if kindClusterName == "" { - kindClusterName = "kluctl-e2e" + kindClusterName = fmt.Sprintf("kluctl-e2e-%d", num) } if kindKubeconfig == "" { - kindKubeconfig = filepath.Join(utils.GetTmpBaseDir(), "kluctl-e2e-kubeconfig.yml") + kindKubeconfig = filepath.Join(utils.GetTmpBaseDir(), fmt.Sprintf("kluctl-e2e-kubeconfig-%d.yml", num)) } return createKindCluster(kindClusterName, kindKubeconfig) } -var defaultKindCluster = createDefaultKindCluster() +func createDefaultKindClusters() (*KindCluster, *KindCluster) { + var k1, k2 *KindCluster + + var wg sync.WaitGroup + wg.Add(2) + go func() { + defer wg.Done() + k1 = createDefaultKindCluster(1) + }() + go func() { + defer wg.Done() + k2 = createDefaultKindCluster(2) + }() + wg.Wait() + return k1, k2 +} + +var ( + defaultKindCluster1, defaultKindCluster2 = createDefaultKindClusters() +) diff --git a/e2e/utils.go b/e2e/utils.go index 97df8657c..79a80696d 100644 --- a/e2e/utils.go +++ b/e2e/utils.go @@ -134,5 +134,6 @@ func runHelper(t *testing.T, cmd *exec.Cmd) (string, string, error) { } func init() { - deleteTestNamespaces(defaultKindCluster) + deleteTestNamespaces(defaultKindCluster1) + deleteTestNamespaces(defaultKindCluster2) } From 064d176408de474e226d55fc0637e6a78042eece Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 31 May 2022 09:49:31 +0200 Subject: [PATCH 0897/2916] tests: Use merged kubeconfig instead of multiple --- e2e/project.go | 51 +++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/e2e/project.go b/e2e/project.go index 9713265bd..c5326febf 100644 --- a/e2e/project.go +++ b/e2e/project.go @@ -3,9 +3,12 @@ package e2e import ( "fmt" "github.com/go-git/go-git/v5" + "github.com/imdario/mergo" test_utils "github.com/kluctl/kluctl/v2/internal/test-utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" + "k8s.io/client-go/tools/clientcmd" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" "os" "os/exec" "path/filepath" @@ -28,15 +31,13 @@ type testProject struct { localDeployment *string localSealedSecrets *string - k *KindCluster - kubeconfigs []string + mergedKubeconfig string gitServer *test_utils.GitServer } func (p *testProject) init(t *testing.T, k *KindCluster, projectName string) { p.t = t - p.k = k p.gitServer = test_utils.NewGitServer(t) p.projectName = projectName @@ -70,7 +71,13 @@ func (p *testProject) init(t *testing.T, k *KindCluster, projectName string) { return nil }) - p.kubeconfigs = append(p.kubeconfigs, k.Kubeconfig()) + tmpFile, err := os.CreateTemp("", projectName+"-kubeconfig-") + if err != nil { + t.Fatal(err) + } + _ = tmpFile.Close() + p.mergedKubeconfig = tmpFile.Name() + p.mergeKubeconfig(k) } func (p *testProject) cleanup() { @@ -78,6 +85,38 @@ func (p *testProject) cleanup() { p.gitServer.Cleanup() p.gitServer = nil } + if p.mergedKubeconfig != "" { + _ = os.Remove(p.mergedKubeconfig) + p.mergedKubeconfig = "" + } +} + +func (p *testProject) mergeKubeconfig(k *KindCluster) { + p.updateMergedKubeconfig(func(config *clientcmdapi.Config) { + nkcfg, err := clientcmd.LoadFromFile(k.kubeconfig) + if err != nil { + p.t.Fatal(err) + } + + err = mergo.Merge(config, nkcfg) + if err != nil { + p.t.Fatal(err) + } + }) +} + +func (p *testProject) updateMergedKubeconfig(cb func(config *clientcmdapi.Config)) { + mkcfg, err := clientcmd.LoadFromFile(p.mergedKubeconfig) + if err != nil { + p.t.Fatal(err) + } + + cb(mkcfg) + + err = clientcmd.WriteToFile(*mkcfg, p.mergedKubeconfig) + if err != nil { + p.t.Fatal(err) + } } func (p *testProject) updateKluctlYaml(update func(o *uo.UnstructuredObject) error) { @@ -377,13 +416,11 @@ func (p *testProject) Kluctl(argsIn ...string) (string, string, error) { args = append(args, "--local-sealed-secrets", *p.localSealedSecrets) } - sep := ":" if runtime.GOOS == "windows" { - sep = ";" args = append(args, "--debug") } env := os.Environ() - env = append(env, fmt.Sprintf("KUBECONFIG=%s", strings.Join(p.kubeconfigs, sep))) + env = append(env, fmt.Sprintf("KUBECONFIG=%s", p.mergedKubeconfig)) p.t.Logf("Runnning kluctl: %s", strings.Join(args, " ")) From aca1058146d3323b33a8e8c2094813ed6f4ecda8 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 31 May 2022 09:57:20 +0200 Subject: [PATCH 0898/2916] tests: Add context tests --- e2e/contexts_test.go | 128 +++++++++++++++++++++++++++++++++++++++++++ e2e/kind_cluster.go | 2 + 2 files changed, 130 insertions(+) create mode 100644 e2e/contexts_test.go diff --git a/e2e/contexts_test.go b/e2e/contexts_test.go new file mode 100644 index 000000000..b55c5ab6d --- /dev/null +++ b/e2e/contexts_test.go @@ -0,0 +1,128 @@ +package e2e + +import ( + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "k8s.io/client-go/tools/clientcmd/api" + "sync" + "testing" +) + +func prepareContextTest(t *testing.T, name string) *testProject { + p := &testProject{} + p.init(t, defaultKindCluster1, name) + p.mergeKubeconfig(defaultKindCluster2) + + var wg sync.WaitGroup + wg.Add(2) + go func() { + defer wg.Done() + recreateNamespace(t, defaultKindCluster1, p.projectName) + }() + go func() { + defer wg.Done() + recreateNamespace(t, defaultKindCluster2, p.projectName) + }() + wg.Wait() + + addConfigMapDeployment(p, "cm", nil, resourceOpts{ + name: "cm", + namespace: p.projectName, + }) + + return p +} + +func TestContextCurrent(t *testing.T) { + t.Parallel() + + p := prepareContextTest(t, "context-current") + defer p.cleanup() + + p.updateTarget("test1", func(target *uo.UnstructuredObject) { + // no context set, assume the current one is used + }) + + p.KluctlMust("deploy", "--yes", "-t", "test1") + assertResourceExists(t, defaultKindCluster1, p.projectName, "ConfigMap/cm") + assertResourceNotExists(t, defaultKindCluster2, p.projectName, "ConfigMap/cm") + + p.updateMergedKubeconfig(func(config *api.Config) { + config.CurrentContext = defaultKindCluster2.Context + }) + + p.KluctlMust("deploy", "--yes", "-t", "test1") + assertResourceExists(t, defaultKindCluster2, p.projectName, "ConfigMap/cm") +} + +func TestContext1(t *testing.T) { + t.Parallel() + + p := prepareContextTest(t, "context-1") + defer p.cleanup() + + p.updateTarget("test1", func(target *uo.UnstructuredObject) { + _ = target.SetNestedField(defaultKindCluster1.Context, "context") + }) + + p.KluctlMust("deploy", "--yes", "-t", "test1") + assertResourceExists(t, defaultKindCluster1, p.projectName, "ConfigMap/cm") + assertResourceNotExists(t, defaultKindCluster2, p.projectName, "ConfigMap/cm") +} + +func TestContext2(t *testing.T) { + t.Parallel() + + p := prepareContextTest(t, "context-2") + defer p.cleanup() + + p.updateTarget("test1", func(target *uo.UnstructuredObject) { + _ = target.SetNestedField(defaultKindCluster2.Context, "context") + }) + + p.KluctlMust("deploy", "--yes", "-t", "test1") + assertResourceExists(t, defaultKindCluster2, p.projectName, "ConfigMap/cm") + assertResourceNotExists(t, defaultKindCluster1, p.projectName, "ConfigMap/cm") +} + +func TestContext1And2(t *testing.T) { + t.Parallel() + + p := prepareContextTest(t, "context-1-and-2") + defer p.cleanup() + + p.updateTarget("test1", func(target *uo.UnstructuredObject) { + _ = target.SetNestedField(defaultKindCluster1.Context, "context") + }) + p.updateTarget("test2", func(target *uo.UnstructuredObject) { + _ = target.SetNestedField(defaultKindCluster2.Context, "context") + }) + + p.KluctlMust("deploy", "--yes", "-t", "test1") + assertResourceExists(t, defaultKindCluster1, p.projectName, "ConfigMap/cm") + assertResourceNotExists(t, defaultKindCluster2, p.projectName, "ConfigMap/cm") + + p.KluctlMust("deploy", "--yes", "-t", "test2") + assertResourceExists(t, defaultKindCluster2, p.projectName, "ConfigMap/cm") +} + +func TestContextSwitch(t *testing.T) { + t.Parallel() + + p := prepareContextTest(t, "context-switch") + defer p.cleanup() + + p.updateTarget("test1", func(target *uo.UnstructuredObject) { + _ = target.SetNestedField(defaultKindCluster1.Context, "context") + }) + + p.KluctlMust("deploy", "--yes", "-t", "test1") + assertResourceExists(t, defaultKindCluster1, p.projectName, "ConfigMap/cm") + assertResourceNotExists(t, defaultKindCluster2, p.projectName, "ConfigMap/cm") + + p.updateTarget("test1", func(target *uo.UnstructuredObject) { + _ = target.SetNestedField(defaultKindCluster2.Context, "context") + }) + + p.KluctlMust("deploy", "--yes", "-t", "test1") + assertResourceExists(t, defaultKindCluster2, p.projectName, "ConfigMap/cm") +} diff --git a/e2e/kind_cluster.go b/e2e/kind_cluster.go index 1096058ea..574b55aaa 100644 --- a/e2e/kind_cluster.go +++ b/e2e/kind_cluster.go @@ -21,6 +21,7 @@ import ( type KindCluster struct { Name string + Context string kubeconfig string config *rest.Config } @@ -30,6 +31,7 @@ func CreateKindCluster(name, kubeconfigPath string) (*KindCluster, error) { c := &KindCluster{ Name: name, + Context: fmt.Sprintf("kind-%s", name), kubeconfig: kubeconfigPath, } From 2fcc67d4e7eba465858d4677f6ded7f9b1854b2a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 31 May 2022 15:00:07 +0200 Subject: [PATCH 0899/2916] fix: Add some tracing to debug test hangs --- cmd/kluctl/commands/cmd_deploy.go | 3 +++ pkg/kluctl_project/load.go | 3 +++ pkg/kluctl_project/targets.go | 3 +++ 3 files changed, 9 insertions(+) diff --git a/cmd/kluctl/commands/cmd_deploy.go b/cmd/kluctl/commands/cmd_deploy.go index 8f2d5a8d4..0e57f6e75 100644 --- a/cmd/kluctl/commands/cmd_deploy.go +++ b/cmd/kluctl/commands/cmd_deploy.go @@ -49,6 +49,9 @@ func (cmd *deployCmd) Run() error { } func (cmd *deployCmd) runCmdDeploy(ctx *commandCtx) error { + status.Trace(ctx.ctx, "enter runCmdDeploy") + defer status.Trace(ctx.ctx, "leave runCmdDeploy") + cmd2 := commands.NewDeployCommand(ctx.targetCtx.DeploymentCollection) cmd2.ForceApply = cmd.ForceApply cmd2.ReplaceOnError = cmd.ReplaceOnError diff --git a/pkg/kluctl_project/load.go b/pkg/kluctl_project/load.go index 62c4f140d..78cde9970 100644 --- a/pkg/kluctl_project/load.go +++ b/pkg/kluctl_project/load.go @@ -3,9 +3,12 @@ package kluctl_project import ( "context" "github.com/kluctl/kluctl/v2/pkg/jinja2" + "github.com/kluctl/kluctl/v2/pkg/status" ) func LoadKluctlProject(ctx context.Context, args LoadKluctlProjectArgs, tmpDir string, j2 *jinja2.Jinja2) (*LoadedKluctlProject, error) { + status.Trace(ctx, "enter LoadKluctlProject") + defer status.Trace(ctx, "leave LoadKluctlProject") p := &LoadedKluctlProject{ ctx: ctx, diff --git a/pkg/kluctl_project/targets.go b/pkg/kluctl_project/targets.go index 7911a2811..acd861f13 100644 --- a/pkg/kluctl_project/targets.go +++ b/pkg/kluctl_project/targets.go @@ -26,6 +26,9 @@ type dynamicTargetInfo struct { } func (c *LoadedKluctlProject) loadTargets() error { + status.Trace(c.ctx, "Loading targets") + defer status.Trace(c.ctx, "Done loading targets") + targetNames := make(map[string]bool) c.DynamicTargets = nil From 167590dbb80baeeb01b46bd5751c8ffd07da9bf3 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 31 May 2022 15:01:07 +0200 Subject: [PATCH 0900/2916] fix: Fix loading of secrets from file The rootKey must be honored. --- pkg/vars/vars_loader.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pkg/vars/vars_loader.go b/pkg/vars/vars_loader.go index 2af7b09af..f6f2dfc76 100644 --- a/pkg/vars/vars_loader.go +++ b/pkg/vars/vars_loader.go @@ -97,12 +97,15 @@ func (v *VarsLoader) mergeVars(varsCtx *VarsCtx, newVars *uo.UnstructuredObject, } func (v *VarsLoader) loadFile(varsCtx *VarsCtx, path string, searchDirs []string, rootKey string) error { - var newVars uo.UnstructuredObject - err := varsCtx.RenderYamlFile(path, searchDirs, &newVars) + newVars := uo.New() + err := varsCtx.RenderYamlFile(path, searchDirs, newVars) if err != nil { return fmt.Errorf("failed to load vars from %s: %w", path, err) } - v.mergeVars(varsCtx, &newVars, rootKey) + if rootKey != "" { + newVars, _, err = newVars.GetNestedObject(rootKey) + } + v.mergeVars(varsCtx, newVars, rootKey) return nil } From 6bc83856316f97c174ef5dbabcf92b2179a268cf Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 31 May 2022 15:01:39 +0200 Subject: [PATCH 0901/2916] tests: Add sealed secrets tests --- e2e/hooks_test.go | 2 +- e2e/project.go | 37 ++- e2e/seal_test.go | 346 +++++++++++++++++++++++++ e2e/test_resources/README.md | 4 + e2e/test_resources/resources.go | 6 + e2e/test_resources/sealed-secrets.yaml | 307 ++++++++++++++++++++++ e2e/utils_resources.go | 20 +- 7 files changed, 706 insertions(+), 16 deletions(-) create mode 100644 e2e/seal_test.go create mode 100644 e2e/test_resources/README.md create mode 100644 e2e/test_resources/resources.go create mode 100644 e2e/test_resources/sealed-secrets.yaml diff --git a/e2e/hooks_test.go b/e2e/hooks_test.go index 5c2af1dae..f1e1f60b1 100644 --- a/e2e/hooks_test.go +++ b/e2e/hooks_test.go @@ -49,7 +49,7 @@ func addConfigMap(p *testProject, dir string, opts resourceOpts) { mergeMetadata(o, opts) o.SetNestedField(map[string]interface{}{}, "data") p.addKustomizeResources(dir, []kustomizeResource{ - {fmt.Sprintf("%s.yml", opts.name), o}, + {fmt.Sprintf("%s.yml", opts.name), "", o}, }) } diff --git a/e2e/project.go b/e2e/project.go index c5326febf..6f73c2197 100644 --- a/e2e/project.go +++ b/e2e/project.go @@ -206,31 +206,39 @@ func (p *testProject) updateTargetDeprecated(name string, cluster string, args * } func (p *testProject) updateTarget(name string, cb func(target *uo.UnstructuredObject)) { + p.updateNamedListItem(uo.KeyPath{"targets"}, name, cb) +} + +func (p *testProject) updateSecretSet(name string, cb func(secretSet *uo.UnstructuredObject)) { + p.updateNamedListItem(uo.KeyPath{"secretsConfig", "secretSets"}, name, cb) +} + +func (p *testProject) updateNamedListItem(path uo.KeyPath, name string, cb func(item *uo.UnstructuredObject)) { if cb == nil { cb = func(target *uo.UnstructuredObject) {} } p.updateKluctlYaml(func(o *uo.UnstructuredObject) error { - targets, _, _ := o.GetNestedObjectList("targets") - var newTargets []*uo.UnstructuredObject + l, _, _ := o.GetNestedObjectList(path...) + var newList []*uo.UnstructuredObject found := false - for _, t := range targets { - n, _, _ := t.GetNestedString("name") + for _, item := range l { + n, _, _ := item.GetNestedString("name") if n == name { - cb(t) + cb(item) found = true } - newTargets = append(newTargets, t) + newList = append(newList, item) } if !found { n := uo.FromMap(map[string]interface{}{ "name": name, }) cb(n) - newTargets = append(newTargets, n) + newList = append(newList, n) } - _ = o.SetNestedObjectList(newTargets, "targets") + _ = o.SetNestedObjectList(newList, path...) return nil }) } @@ -330,8 +338,9 @@ func (p *testProject) convertInterfaceToList(x interface{}) []interface{} { } type kustomizeResource struct { - name string - content interface{} + name string + fileName string + content interface{} } func (p *testProject) addKustomizeResources(dir string, resources []kustomizeResource) { @@ -340,11 +349,15 @@ func (p *testProject) addKustomizeResources(dir string, resources []kustomizeRes for _, r := range resources { l = append(l, r.name) x := p.convertInterfaceToList(r.content) - err := yaml.WriteYamlAllFile(filepath.Join(p.gitServer.LocalRepoDir(p.getDeploymentRepo()), dir, r.name), x) + fileName := r.fileName + if fileName == "" { + fileName = r.name + } + err := yaml.WriteYamlAllFile(filepath.Join(p.gitServer.LocalRepoDir(p.getDeploymentRepo()), dir, fileName), x) if err != nil { return err } - _, err = wt.Add(filepath.Join(dir, r.name)) + _, err = wt.Add(filepath.Join(dir, fileName)) if err != nil { return err } diff --git a/e2e/seal_test.go b/e2e/seal_test.go new file mode 100644 index 000000000..00ad70147 --- /dev/null +++ b/e2e/seal_test.go @@ -0,0 +1,346 @@ +package e2e + +import ( + "encoding/base64" + "fmt" + "github.com/kluctl/kluctl/v2/e2e/test_resources" + "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/stretchr/testify/assert" + "os" + "path/filepath" + "testing" + "time" +) + +func installSealedSecretsOperator(t *testing.T, k *KindCluster) { + tmpFile, _ := os.CreateTemp("", "") + tmpFile.Close() + defer os.Remove(tmpFile.Name()) + + _ = utils.FsCopyFile(test_resources.Yamls, "sealed-secrets.yaml", tmpFile.Name()) + + _, err := k.Kubectl("apply", "-f", tmpFile.Name()) + if err != nil { + panic(err) + } + + waitForReadiness(t, k, "kube-system", "deployment/sealed-secrets-controller", 5*time.Minute) +} + +func deleteSealedSecretsOperator(t *testing.T, k *KindCluster) { + _, _ = defaultKindCluster1.Kubectl("-n", "kube-system", "delete", "deployment", "sealed-secrets-controller", "--wait") + _, _ = defaultKindCluster1.Kubectl("-n", "kube-system", "delete", "secret", "-l", "sealedsecrets.bitnami.com/sealed-secrets-key", "--wait") +} + +func prepareSealTest(t *testing.T, k *KindCluster, namespace string, secrets map[string]string, varsSources []*uo.UnstructuredObject) *testProject { + p := &testProject{} + p.init(t, k, fmt.Sprintf("seal-%s", namespace)) + + recreateNamespace(t, k, namespace) + + addSecretsSet(p, "test", varsSources) + addSecretsSetToTarget(p, "test-target", "test") + + addSecretDeployment(p, "secret-deployment", secrets, true, resourceOpts{name: "secret", namespace: namespace}) + + return p +} + +func addSecretsSet(p *testProject, name string, varsSources []*uo.UnstructuredObject) { + p.updateSecretSet(name, func(secretSet *uo.UnstructuredObject) { + _ = secretSet.SetNestedField(varsSources, "vars") + }) +} + +func addSecretsSetToTarget(p *testProject, targetName string, secretSetName string) { + p.updateTarget(targetName, func(target *uo.UnstructuredObject) { + l, _, _ := target.GetNestedList("sealingConfig", "secretSets") + l = append(l, secretSetName) + _ = target.SetNestedField(l, "sealingConfig", "secretSets") + }) +} + +func assertDecryptedSecrets(t *testing.T, k *KindCluster, namespace string, secretName string, expectedSecrets map[string]string) { + s := k.KubectlYamlMust(t, "-n", namespace, "get", "secret", secretName) + + for key, value := range expectedSecrets { + x, _, _ := s.GetNestedString("data", key) + decoded, _ := base64.StdEncoding.DecodeString(x) + assert.Equal(t, value, string(decoded)) + } +} + +func TestSeal_WithOperator(t *testing.T) { + k := defaultKindCluster1 + namespace := "seal-with-operator" + + deleteSealedSecretsOperator(t, k) + installSealedSecretsOperator(t, k) + + p := prepareSealTest(t, k, namespace, + map[string]string{ + "s1": "{{ secrets.s1 }}", + "s2": "{{ secrets.s2 }}", + }, + []*uo.UnstructuredObject{ + uo.FromMap(map[string]interface{}{ + "values": map[string]interface{}{ + "s1": "v1", + "s2": "v2", + }, + }), + }, + ) + defer p.cleanup() + + p.KluctlMust("seal", "-t", "test-target") + + sealedSecretsDir := p.gitServer.LocalRepoDir(p.getSealedSecretsRepo()) + assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) + + p.KluctlMust("deploy", "--yes", "-t", "test-target") + + waitForReadiness(t, k, namespace, "secret/secret", 1*time.Minute) + assertDecryptedSecrets(t, k, namespace, "secret", map[string]string{ + "s1": "v1", + "s2": "v2", + }) +} + +func TestSeal_WithBootstrap(t *testing.T) { + k := defaultKindCluster2 + namespace := "seal-with-bootstrap" + + deleteSealedSecretsOperator(t, k) + + p := prepareSealTest(t, k, namespace, + map[string]string{ + "s1": "{{ secrets.s1 }}", + "s2": "{{ secrets.s2 }}", + }, + []*uo.UnstructuredObject{ + uo.FromMap(map[string]interface{}{ + "values": map[string]interface{}{ + "s1": "v1", + "s2": "v2", + }, + }), + }, + ) + defer p.cleanup() + + p.KluctlMust("seal", "-t", "test-target") + + sealedSecretsDir := p.gitServer.LocalRepoDir(p.getSealedSecretsRepo()) + assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) + + installSealedSecretsOperator(t, k) + + p.KluctlMust("deploy", "--yes", "-t", "test-target") + + waitForReadiness(t, k, namespace, "secret/secret", 1*time.Minute) + assertDecryptedSecrets(t, k, namespace, "secret", map[string]string{ + "s1": "v1", + "s2": "v2", + }) +} + +func TestSeal_MultipleVarSources(t *testing.T) { + t.Parallel() + + k := defaultKindCluster1 + namespace := "seal-multiple-vs" + + installSealedSecretsOperator(t, k) + + p := prepareSealTest(t, k, namespace, + map[string]string{ + "s1": "{{ secrets.s1 }}", + "s2": "{{ secrets.s2 }}", + }, + []*uo.UnstructuredObject{ + uo.FromMap(map[string]interface{}{ + "values": map[string]interface{}{ + "s1": "v1", + }, + }), + uo.FromMap(map[string]interface{}{ + "values": map[string]interface{}{ + "s2": "v2", + }, + }), + }, + ) + defer p.cleanup() + + p.KluctlMust("seal", "-t", "test-target") + + sealedSecretsDir := p.gitServer.LocalRepoDir(p.getSealedSecretsRepo()) + assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) + + p.KluctlMust("deploy", "--yes", "-t", "test-target") + + waitForReadiness(t, k, namespace, "secret/secret", 1*time.Minute) + assertDecryptedSecrets(t, k, namespace, "secret", map[string]string{ + "s1": "v1", + "s2": "v2", + }) +} + +func TestSeal_MultipleSecretSets(t *testing.T) { + t.Parallel() + + k := defaultKindCluster1 + namespace := "seal-multiple-ss" + + installSealedSecretsOperator(t, k) + + p := prepareSealTest(t, k, namespace, + map[string]string{ + "s1": "{{ secrets.s1 }}", + "s2": "{{ secrets.s2 }}", + }, + []*uo.UnstructuredObject{ + uo.FromMap(map[string]interface{}{ + "values": map[string]interface{}{ + "s1": "v1", + }, + }), + }, + ) + defer p.cleanup() + + addSecretsSet(p, "test2", []*uo.UnstructuredObject{ + uo.FromMap(map[string]interface{}{ + "values": map[string]interface{}{ + "s2": "v2", + }, + }), + }) + addSecretsSetToTarget(p, "test-target", "test2") + + p.KluctlMust("seal", "-t", "test-target") + + sealedSecretsDir := p.gitServer.LocalRepoDir(p.getSealedSecretsRepo()) + assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) + + p.KluctlMust("deploy", "--yes", "-t", "test-target") + + waitForReadiness(t, k, namespace, "secret/secret", 1*time.Minute) + assertDecryptedSecrets(t, k, namespace, "secret", map[string]string{ + "s1": "v1", + "s2": "v2", + }) +} + +func TestSeal_MultipleTargets(t *testing.T) { + k := defaultKindCluster1 + namespace := "seal-multiple-targets" + + installSealedSecretsOperator(t, k) + installSealedSecretsOperator(t, defaultKindCluster2) + + p := prepareSealTest(t, k, namespace, + map[string]string{ + "s1": "{{ secrets.s1 }}", + "s2": "{{ secrets.s2 }}", + }, + []*uo.UnstructuredObject{ + uo.FromMap(map[string]interface{}{ + "values": map[string]interface{}{ + "s1": "v1", + }, + }), + uo.FromMap(map[string]interface{}{ + "values": map[string]interface{}{ + "s2": "v2", + }, + }), + }, + ) + defer p.cleanup() + + addSecretsSet(p, "test2", []*uo.UnstructuredObject{ + uo.FromMap(map[string]interface{}{ + "values": map[string]interface{}{ + "s1": "v3", + "s2": "v4", + }, + }), + }) + addSecretsSetToTarget(p, "test-target2", "test2") + + p.mergeKubeconfig(defaultKindCluster2) + recreateNamespace(t, defaultKindCluster2, namespace) + p.updateTarget("test-target", func(target *uo.UnstructuredObject) { + _ = target.SetNestedField(defaultKindCluster1.Context, "context") + }) + p.updateTarget("test-target2", func(target *uo.UnstructuredObject) { + _ = target.SetNestedField(defaultKindCluster2.Context, "context") + }) + + p.KluctlMust("seal", "-t", "test-target") + p.KluctlMust("seal", "-t", "test-target2") + + sealedSecretsDir := p.gitServer.LocalRepoDir(p.getSealedSecretsRepo()) + assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) + assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target2/secret-secret.yml")) + + p.KluctlMust("deploy", "--yes", "-t", "test-target") + p.KluctlMust("deploy", "--yes", "-t", "test-target2") + + waitForReadiness(t, k, namespace, "secret/secret", 1*time.Minute) + assertDecryptedSecrets(t, k, namespace, "secret", map[string]string{ + "s1": "v1", + "s2": "v2", + }) + waitForReadiness(t, defaultKindCluster2, namespace, "secret/secret", 1*time.Minute) + assertDecryptedSecrets(t, defaultKindCluster2, namespace, "secret", map[string]string{ + "s1": "v3", + "s2": "v4", + }) +} + +func TestSeal_File(t *testing.T) { + k := defaultKindCluster1 + namespace := "seal-file" + + installSealedSecretsOperator(t, k) + + p := prepareSealTest(t, k, namespace, + map[string]string{ + "s1": "{{ secrets.s1 }}", + "s2": "{{ secrets.s2 }}", + }, + []*uo.UnstructuredObject{ + uo.FromMap(map[string]interface{}{ + "file": utils.StrPtr("secret-values.yaml"), + }), + }, + ) + defer p.cleanup() + + p.gitServer.UpdateYaml(p.getKluctlProjectRepo(), "secret-values.yaml", func(o *uo.UnstructuredObject) error { + *o = *uo.FromMap(map[string]interface{}{ + "secrets": map[string]interface{}{ + "s1": "v1", + "s2": "v2", + }, + }) + return nil + }, "") + + p.KluctlMust("seal", "-t", "test-target") + + sealedSecretsDir := p.gitServer.LocalRepoDir(p.getSealedSecretsRepo()) + assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) + + p.KluctlMust("deploy", "--yes", "-t", "test-target") + + waitForReadiness(t, k, namespace, "secret/secret", 1*time.Minute) + assertDecryptedSecrets(t, k, namespace, "secret", map[string]string{ + "s1": "v1", + "s2": "v2", + }) +} diff --git a/e2e/test_resources/README.md b/e2e/test_resources/README.md new file mode 100644 index 000000000..75d21866f --- /dev/null +++ b/e2e/test_resources/README.md @@ -0,0 +1,4 @@ +# sealed-secrets.yaml + +helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets +helm template sealed-secrets-controller sealed-secrets/sealed-secrets -n kube-system --include-crds > sealed-secrets.yaml diff --git a/e2e/test_resources/resources.go b/e2e/test_resources/resources.go new file mode 100644 index 000000000..0ec6d270c --- /dev/null +++ b/e2e/test_resources/resources.go @@ -0,0 +1,6 @@ +package test_resources + +import "embed" + +//go:embed *.yaml +var Yamls embed.FS diff --git a/e2e/test_resources/sealed-secrets.yaml b/e2e/test_resources/sealed-secrets.yaml new file mode 100644 index 000000000..b45788937 --- /dev/null +++ b/e2e/test_resources/sealed-secrets.yaml @@ -0,0 +1,307 @@ +--- +# Source: crds/sealedsecret-crd.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: sealedsecrets.bitnami.com +spec: + group: bitnami.com + names: + kind: SealedSecret + listKind: SealedSecretList + plural: sealedsecrets + singular: sealedsecret + scope: Namespaced + versions: + - name: v1alpha1 + served: true + storage: true + subresources: + status: {} + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + x-kubernetes-preserve-unknown-fields: true + status: + x-kubernetes-preserve-unknown-fields: true + +--- +# Source: sealed-secrets/templates/service-account.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: sealed-secrets-controller + namespace: kube-system + labels: + app.kubernetes.io/name: sealed-secrets + helm.sh/chart: sealed-secrets-2.1.8 + app.kubernetes.io/instance: sealed-secrets-controller + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/version: v0.17.5 +--- +# Source: sealed-secrets/templates/cluster-role.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: secrets-unsealer + labels: + app.kubernetes.io/name: sealed-secrets + helm.sh/chart: sealed-secrets-2.1.8 + app.kubernetes.io/instance: sealed-secrets-controller + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/version: v0.17.5 +rules: + - apiGroups: + - bitnami.com + resources: + - sealedsecrets + verbs: + - get + - list + - watch + - apiGroups: + - bitnami.com + resources: + - sealedsecrets/status + verbs: + - update + - apiGroups: + - "" + resources: + - secrets + verbs: + - get + - list + - create + - update + - delete + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +--- +# Source: sealed-secrets/templates/cluster-role-binding.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: sealed-secrets-controller + labels: + app.kubernetes.io/name: sealed-secrets + helm.sh/chart: sealed-secrets-2.1.8 + app.kubernetes.io/instance: sealed-secrets-controller + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/version: v0.17.5 +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: secrets-unsealer +subjects: + - apiGroup: "" + kind: ServiceAccount + name: sealed-secrets-controller + namespace: kube-system +--- +# Source: sealed-secrets/templates/role.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: sealed-secrets-controller-key-admin + namespace: kube-system + labels: + app.kubernetes.io/name: sealed-secrets + helm.sh/chart: sealed-secrets-2.1.8 + app.kubernetes.io/instance: sealed-secrets-controller + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/version: v0.17.5 +rules: + - apiGroups: + - "" + resourceNames: + - sealed-secrets-key + resources: + - secrets + verbs: + - get + - apiGroups: + - "" + resources: + - secrets + verbs: + - create + - list +--- +# Source: sealed-secrets/templates/role.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: sealed-secrets-controller-service-proxier + namespace: kube-system + labels: + app.kubernetes.io/name: sealed-secrets + helm.sh/chart: sealed-secrets-2.1.8 + app.kubernetes.io/instance: sealed-secrets-controller + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/version: v0.17.5 +rules: + - apiGroups: + - "" + resourceNames: + - sealed-secrets-controller + resources: + - services + verbs: + - get + - apiGroups: + - "" + resourceNames: + - 'http:sealed-secrets-controller:' + - 'http:sealed-secrets-controller:http' + - sealed-secrets-controller + resources: + - services/proxy + verbs: + - create + - get +--- +# Source: sealed-secrets/templates/role-binding.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: sealed-secrets-controller-key-admin + namespace: kube-system + labels: + app.kubernetes.io/name: sealed-secrets + helm.sh/chart: sealed-secrets-2.1.8 + app.kubernetes.io/instance: sealed-secrets-controller + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/version: v0.17.5 +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: sealed-secrets-controller-key-admin +subjects: + - apiGroup: "" + kind: ServiceAccount + name: sealed-secrets-controller + namespace: kube-system +--- +# Source: sealed-secrets/templates/role-binding.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: sealed-secrets-controller-service-proxier + namespace: kube-system + labels: + app.kubernetes.io/name: sealed-secrets + helm.sh/chart: sealed-secrets-2.1.8 + app.kubernetes.io/instance: sealed-secrets-controller + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/version: v0.17.5 +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: sealed-secrets-controller-service-proxier +subjects: +- apiGroup: rbac.authorization.k8s.io + kind: Group + name: system:authenticated +--- +# Source: sealed-secrets/templates/service.yaml +apiVersion: v1 +kind: Service +metadata: + name: sealed-secrets-controller + namespace: kube-system + labels: + app.kubernetes.io/name: sealed-secrets + helm.sh/chart: sealed-secrets-2.1.8 + app.kubernetes.io/instance: sealed-secrets-controller + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/version: v0.17.5 +spec: + type: ClusterIP + ports: + - name: http + port: 8080 + targetPort: http + nodePort: null + selector: + app.kubernetes.io/name: sealed-secrets + app.kubernetes.io/instance: sealed-secrets-controller +--- +# Source: sealed-secrets/templates/deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: sealed-secrets-controller + namespace: kube-system + labels: + app.kubernetes.io/name: sealed-secrets + helm.sh/chart: sealed-secrets-2.1.8 + app.kubernetes.io/instance: sealed-secrets-controller + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/version: v0.17.5 +spec: + selector: + matchLabels: + app.kubernetes.io/name: sealed-secrets + app.kubernetes.io/instance: sealed-secrets-controller + template: + metadata: + labels: + app.kubernetes.io/name: sealed-secrets + app.kubernetes.io/instance: sealed-secrets-controller + spec: + securityContext: + fsGroup: 65534 + serviceAccountName: sealed-secrets-controller + containers: + - name: controller + command: + - controller + args: + - --update-status + - --key-prefix + - "sealed-secrets-key" + image: docker.io/bitnami/sealed-secrets-controller:v0.17.5 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 8080 + name: http + livenessProbe: + failureThreshold: 3 + initialDelaySeconds: 0 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + httpGet: + path: /healthz + port: http + readinessProbe: + failureThreshold: 3 + initialDelaySeconds: 0 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + httpGet: + path: /healthz + port: http + resources: + limits: {} + requests: {} + securityContext: + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1001 + volumeMounts: + - mountPath: /tmp + name: tmp + volumes: + - name: tmp + emptyDir: {} diff --git a/e2e/utils_resources.go b/e2e/utils_resources.go index 255e07476..0dc063e7f 100644 --- a/e2e/utils_resources.go +++ b/e2e/utils_resources.go @@ -2,6 +2,7 @@ package e2e import ( "bytes" + "fmt" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "text/template" ) @@ -56,7 +57,20 @@ func addConfigMapDeployment(p *testProject, dir string, data map[string]string, o.SetNestedField(data, "data") } p.addKustomizeDeployment(dir, []kustomizeResource{ - {"configmap.yml", o}, + {fmt.Sprintf("configmap-%s.yml", opts.name), "", o}, + }, opts.tags) +} + +func addSecretDeployment(p *testProject, dir string, data map[string]string, sealedSecret bool, opts resourceOpts) { + o := uo.New() + o.SetK8sGVKs("", "v1", "Secret") + mergeMetadata(o, opts) + if data != nil { + o.SetNestedField(data, "stringData") + } + fname := fmt.Sprintf("secret-%s.yml", opts.name) + p.addKustomizeDeployment(dir, []kustomizeResource{ + {fname, fname + ".sealme", o}, }, opts.tags) } @@ -71,8 +85,8 @@ func addDeploymentHelper(p *testProject, dir string, o *uo.UnstructuredObject, o mergeMetadata(o, opts) resources := []kustomizeResource{ - {"rbac.yml", rbac}, - {"deploy.yml", o}, + {"rbac.yml", "", rbac}, + {"deploy.yml", "", o}, } p.addKustomizeDeployment(dir, resources, opts.tags) From b1962eb938ebbec9497191f8b2d2483d9d8e4656 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 31 May 2022 15:39:01 +0200 Subject: [PATCH 0902/2916] tests: Ignore NotFound errors when waiting for readiness --- e2e/utils.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/e2e/utils.go b/e2e/utils.go index 79a80696d..f7f3d5f7c 100644 --- a/e2e/utils.go +++ b/e2e/utils.go @@ -32,7 +32,11 @@ func waitForReadiness(t *testing.T, k *KindCluster, namespace string, resource s for time.Now().Sub(startTime) < timeout { y, err := k.KubectlYaml("-n", namespace, "get", resource) if err != nil { - t.Fatal(err) + if ee, ok := err.(*exec.ExitError); !ok || strings.Index(string(ee.Stderr), "NotFound") == -1 { + t.Fatal(err) + } + time.Sleep(1 * time.Second) + continue } v := validation.ValidateObject(nil, y, true) From 9bb46dd7726cc75130ee7ee5349bf8d67170b535 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 31 May 2022 16:08:27 +0200 Subject: [PATCH 0903/2916] tests: Move KindCluster into test-utils package --- e2e/default_clusters.go | 56 ++++++++++++++++++++ e2e/external_projects_test.go | 1 + e2e/hooks_test.go | 15 +++--- e2e/inclusion_test.go | 5 +- e2e/project.go | 6 +-- e2e/seal_test.go | 9 ++-- e2e/utils.go | 18 +++---- {e2e => internal/test-utils}/kind_cluster.go | 47 +--------------- 8 files changed, 84 insertions(+), 73 deletions(-) create mode 100644 e2e/default_clusters.go rename {e2e => internal/test-utils}/kind_cluster.go (75%) diff --git a/e2e/default_clusters.go b/e2e/default_clusters.go new file mode 100644 index 000000000..664cf31cb --- /dev/null +++ b/e2e/default_clusters.go @@ -0,0 +1,56 @@ +package e2e + +import ( + "fmt" + test_utils "github.com/kluctl/kluctl/v2/internal/test-utils" + "github.com/kluctl/kluctl/v2/pkg/utils" + "os" + "path/filepath" + "sync" +) + +func createKindCluster(name string, kubeconfig string) *test_utils.KindCluster { + k, err := test_utils.CreateKindCluster(name, kubeconfig) + if err != nil { + panic(err) + } + return k +} + +func createDefaultKindCluster(num int) *test_utils.KindCluster { + kindClusterName := os.Getenv(fmt.Sprintf("KIND_CLUSTER_NAME%d", num)) + kindKubeconfig := os.Getenv(fmt.Sprintf("KIND_KUBECONFIG%d", num)) + if kindClusterName == "" { + kindClusterName = fmt.Sprintf("kluctl-e2e-%d", num) + } + if kindKubeconfig == "" { + kindKubeconfig = filepath.Join(utils.GetTmpBaseDir(), fmt.Sprintf("kluctl-e2e-kubeconfig-%d.yml", num)) + } + return createKindCluster(kindClusterName, kindKubeconfig) +} + +func createDefaultKindClusters() (*test_utils.KindCluster, *test_utils.KindCluster) { + var k1, k2 *test_utils.KindCluster + + var wg sync.WaitGroup + wg.Add(2) + go func() { + defer wg.Done() + k1 = createDefaultKindCluster(1) + }() + go func() { + defer wg.Done() + k2 = createDefaultKindCluster(2) + }() + wg.Wait() + return k1, k2 +} + +var ( + defaultKindCluster1, defaultKindCluster2 = createDefaultKindClusters() +) + +func init() { + deleteTestNamespaces(defaultKindCluster1) + deleteTestNamespaces(defaultKindCluster2) +} diff --git a/e2e/external_projects_test.go b/e2e/external_projects_test.go index eefd91b17..bb2f2094e 100644 --- a/e2e/external_projects_test.go +++ b/e2e/external_projects_test.go @@ -2,6 +2,7 @@ package e2e import ( "fmt" + "github.com/kluctl/kluctl/v2/internal/test-utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "testing" "time" diff --git a/e2e/hooks_test.go b/e2e/hooks_test.go index f1e1f60b1..18149608c 100644 --- a/e2e/hooks_test.go +++ b/e2e/hooks_test.go @@ -3,6 +3,7 @@ package e2e import ( "encoding/base64" "fmt" + "github.com/kluctl/kluctl/v2/internal/test-utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "testing" ) @@ -53,7 +54,7 @@ func addConfigMap(p *testProject, dir string, opts resourceOpts) { }) } -func getHookResult(t *testing.T, p *testProject, k *KindCluster, secretName string) *uo.UnstructuredObject { +func getHookResult(t *testing.T, p *testProject, k *test_utils.KindCluster, secretName string) *uo.UnstructuredObject { o := k.KubectlYamlMust(t, "-n", p.projectName, "get", "secret", secretName) s, ok, err := o.GetNestedString("data", "result") if err != nil { @@ -73,7 +74,7 @@ func getHookResult(t *testing.T, p *testProject, k *KindCluster, secretName stri return r } -func getHookResultCMNames(t *testing.T, p *testProject, k *KindCluster, second bool) []string { +func getHookResultCMNames(t *testing.T, p *testProject, k *test_utils.KindCluster, second bool) []string { secretName := "hook-result" if second { secretName = "hook-result2" @@ -87,7 +88,7 @@ func getHookResultCMNames(t *testing.T, p *testProject, k *KindCluster, second b return names } -func assertHookResultCMName(t *testing.T, p *testProject, k *KindCluster, second bool, cmName string) { +func assertHookResultCMName(t *testing.T, p *testProject, k *test_utils.KindCluster, second bool, cmName string) { names := getHookResultCMNames(t, p, k, second) for _, x := range names { if x == cmName { @@ -97,7 +98,7 @@ func assertHookResultCMName(t *testing.T, p *testProject, k *KindCluster, second t.Fatalf("%s not found in hook result", cmName) } -func assertHookResultNotCMName(t *testing.T, p *testProject, k *KindCluster, second bool, cmName string) { +func assertHookResultNotCMName(t *testing.T, p *testProject, k *test_utils.KindCluster, second bool, cmName string) { names := getHookResultCMNames(t, p, k, second) for _, x := range names { if x == cmName { @@ -106,7 +107,7 @@ func assertHookResultNotCMName(t *testing.T, p *testProject, k *KindCluster, sec } } -func prepareHookTestProject(t *testing.T, name string, hook string, hookDeletionPolicy string) (*testProject, *KindCluster) { +func prepareHookTestProject(t *testing.T, name string, hook string, hookDeletionPolicy string) (*testProject, *test_utils.KindCluster) { isDone := false namespace := fmt.Sprintf("hook-%s", name) k := defaultKindCluster1 @@ -129,13 +130,13 @@ func prepareHookTestProject(t *testing.T, name string, hook string, hookDeletion return p, k } -func ensureHookExecuted(t *testing.T, p *testProject, k *KindCluster) { +func ensureHookExecuted(t *testing.T, p *testProject, k *test_utils.KindCluster) { _, _ = k.Kubectl("delete", "-n", p.projectName, "secret", "hook-result", "hook-result2") p.KluctlMust("deploy", "--yes", "-t", "test") assertResourceExists(t, k, p.projectName, "ConfigMap/cm1") } -func ensureHookNotExecuted(t *testing.T, p *testProject, k *KindCluster) { +func ensureHookNotExecuted(t *testing.T, p *testProject, k *test_utils.KindCluster) { _, _ = k.Kubectl("delete", "-n", p.projectName, "secret", "hook-result", "hook-result2") p.KluctlMust("deploy", "--yes", "-t", "test") assertResourceNotExists(t, k, p.projectName, "Secret/hook-result") diff --git a/e2e/inclusion_test.go b/e2e/inclusion_test.go index 21a3028eb..0395bff9f 100644 --- a/e2e/inclusion_test.go +++ b/e2e/inclusion_test.go @@ -2,12 +2,13 @@ package e2e import ( "fmt" + "github.com/kluctl/kluctl/v2/internal/test-utils" "path/filepath" "reflect" "testing" ) -func prepareInclusionTestProject(t *testing.T, namespace string, withIncludes bool) (*testProject, *KindCluster) { +func prepareInclusionTestProject(t *testing.T, namespace string, withIncludes bool) (*testProject, *test_utils.KindCluster) { isDone := false k := defaultKindCluster1 @@ -48,7 +49,7 @@ func prepareInclusionTestProject(t *testing.T, namespace string, withIncludes bo return p, k } -func assertExistsHelper(t *testing.T, p *testProject, k *KindCluster, shouldExists map[string]bool, add []string, remove []string) { +func assertExistsHelper(t *testing.T, p *testProject, k *test_utils.KindCluster, shouldExists map[string]bool, add []string, remove []string) { for _, x := range add { shouldExists[x] = true } diff --git a/e2e/project.go b/e2e/project.go index 6f73c2197..3fb63a6fa 100644 --- a/e2e/project.go +++ b/e2e/project.go @@ -36,7 +36,7 @@ type testProject struct { gitServer *test_utils.GitServer } -func (p *testProject) init(t *testing.T, k *KindCluster, projectName string) { +func (p *testProject) init(t *testing.T, k *test_utils.KindCluster, projectName string) { p.t = t p.gitServer = test_utils.NewGitServer(t) p.projectName = projectName @@ -91,7 +91,7 @@ func (p *testProject) cleanup() { } } -func (p *testProject) mergeKubeconfig(k *KindCluster) { +func (p *testProject) mergeKubeconfig(k *test_utils.KindCluster) { p.updateMergedKubeconfig(func(config *clientcmdapi.Config) { nkcfg, err := clientcmd.LoadFromFile(k.kubeconfig) if err != nil { @@ -186,7 +186,7 @@ func (p *testProject) updateCluster(name string, context string, vars *uo.Unstru }, fmt.Sprintf("add/update cluster %s", name)) } -func (p *testProject) updateKindCluster(k *KindCluster, vars *uo.UnstructuredObject) { +func (p *testProject) updateKindCluster(k *test_utils.KindCluster, vars *uo.UnstructuredObject) { context, err := k.Kubectl("config", "current-context") if err != nil { p.t.Fatal(err) diff --git a/e2e/seal_test.go b/e2e/seal_test.go index 00ad70147..b28cc12e7 100644 --- a/e2e/seal_test.go +++ b/e2e/seal_test.go @@ -4,6 +4,7 @@ import ( "encoding/base64" "fmt" "github.com/kluctl/kluctl/v2/e2e/test_resources" + "github.com/kluctl/kluctl/v2/internal/test-utils" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/stretchr/testify/assert" @@ -13,7 +14,7 @@ import ( "time" ) -func installSealedSecretsOperator(t *testing.T, k *KindCluster) { +func installSealedSecretsOperator(t *testing.T, k *test_utils.KindCluster) { tmpFile, _ := os.CreateTemp("", "") tmpFile.Close() defer os.Remove(tmpFile.Name()) @@ -28,12 +29,12 @@ func installSealedSecretsOperator(t *testing.T, k *KindCluster) { waitForReadiness(t, k, "kube-system", "deployment/sealed-secrets-controller", 5*time.Minute) } -func deleteSealedSecretsOperator(t *testing.T, k *KindCluster) { +func deleteSealedSecretsOperator(t *testing.T, k *test_utils.KindCluster) { _, _ = defaultKindCluster1.Kubectl("-n", "kube-system", "delete", "deployment", "sealed-secrets-controller", "--wait") _, _ = defaultKindCluster1.Kubectl("-n", "kube-system", "delete", "secret", "-l", "sealedsecrets.bitnami.com/sealed-secrets-key", "--wait") } -func prepareSealTest(t *testing.T, k *KindCluster, namespace string, secrets map[string]string, varsSources []*uo.UnstructuredObject) *testProject { +func prepareSealTest(t *testing.T, k *test_utils.KindCluster, namespace string, secrets map[string]string, varsSources []*uo.UnstructuredObject) *testProject { p := &testProject{} p.init(t, k, fmt.Sprintf("seal-%s", namespace)) @@ -61,7 +62,7 @@ func addSecretsSetToTarget(p *testProject, targetName string, secretSetName stri }) } -func assertDecryptedSecrets(t *testing.T, k *KindCluster, namespace string, secretName string, expectedSecrets map[string]string) { +func assertDecryptedSecrets(t *testing.T, k *test_utils.KindCluster, namespace string, secretName string, expectedSecrets map[string]string) { s := k.KubectlYamlMust(t, "-n", namespace, "get", "secret", secretName) for key, value := range expectedSecrets { diff --git a/e2e/utils.go b/e2e/utils.go index f7f3d5f7c..274f5946c 100644 --- a/e2e/utils.go +++ b/e2e/utils.go @@ -4,6 +4,7 @@ import ( "bufio" "bytes" "fmt" + "github.com/kluctl/kluctl/v2/internal/test-utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/validation" log "github.com/sirupsen/logrus" @@ -15,17 +16,17 @@ import ( "time" ) -func recreateNamespace(t *testing.T, k *KindCluster, namespace string) { +func recreateNamespace(t *testing.T, k *test_utils.KindCluster, namespace string) { _, _ = k.Kubectl("delete", "ns", namespace) k.KubectlMust(t, "create", "ns", namespace) k.KubectlMust(t, "label", "ns", namespace, "kluctl-e2e=true") } -func deleteTestNamespaces(k *KindCluster) { +func deleteTestNamespaces(k *test_utils.KindCluster) { _, _ = k.Kubectl("delete", "ns", "-l", "kubectl-e2e=true") } -func waitForReadiness(t *testing.T, k *KindCluster, namespace string, resource string, timeout time.Duration) bool { +func waitForReadiness(t *testing.T, k *test_utils.KindCluster, namespace string, resource string, timeout time.Duration) bool { t.Logf("Waiting for readiness: %s/%s", namespace, resource) startTime := time.Now() @@ -59,13 +60,13 @@ func waitForReadiness(t *testing.T, k *KindCluster, namespace string, resource s return false } -func assertReadiness(t *testing.T, k *KindCluster, namespace string, resource string, timeout time.Duration) { +func assertReadiness(t *testing.T, k *test_utils.KindCluster, namespace string, resource string, timeout time.Duration) { if !waitForReadiness(t, k, namespace, resource, timeout) { t.Errorf("%s/%s did not get ready in time", namespace, resource) } } -func assertResourceExists(t *testing.T, k *KindCluster, namespace string, resource string) *uo.UnstructuredObject { +func assertResourceExists(t *testing.T, k *test_utils.KindCluster, namespace string, resource string) *uo.UnstructuredObject { var args []string if namespace != "" { args = append(args, "-n", namespace) @@ -74,7 +75,7 @@ func assertResourceExists(t *testing.T, k *KindCluster, namespace string, resour return k.KubectlYamlMust(t, args...) } -func assertResourceNotExists(t *testing.T, k *KindCluster, namespace string, resource string) { +func assertResourceNotExists(t *testing.T, k *test_utils.KindCluster, namespace string, resource string) { var args []string if namespace != "" { args = append(args, "-n", namespace) @@ -136,8 +137,3 @@ func runHelper(t *testing.T, cmd *exec.Cmd) (string, string, error) { err = cmd.Run() return stdoutBuf.String(), stderrBuf.String(), err } - -func init() { - deleteTestNamespaces(defaultKindCluster1) - deleteTestNamespaces(defaultKindCluster2) -} diff --git a/e2e/kind_cluster.go b/internal/test-utils/kind_cluster.go similarity index 75% rename from e2e/kind_cluster.go rename to internal/test-utils/kind_cluster.go index 574b55aaa..d527f0173 100644 --- a/e2e/kind_cluster.go +++ b/internal/test-utils/kind_cluster.go @@ -1,20 +1,16 @@ -package e2e +package test_utils import ( "fmt" - "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" "github.com/pkg/errors" - log "github.com/sirupsen/logrus" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" "os" "os/exec" - "path/filepath" "sigs.k8s.io/kind/pkg/cluster" kindcmd "sigs.k8s.io/kind/pkg/cmd" - "sync" "testing" "time" ) @@ -142,44 +138,3 @@ func kindCreate(name, kubeconfig string) error { } } } - -func createKindCluster(name string, kubeconfig string) *KindCluster { - k, err := CreateKindCluster(name, kubeconfig) - if err != nil { - log.Fatal(err) - } - return k -} - -func createDefaultKindCluster(num int) *KindCluster { - kindClusterName := os.Getenv(fmt.Sprintf("KIND_CLUSTER_NAME%d", num)) - kindKubeconfig := os.Getenv(fmt.Sprintf("KIND_KUBECONFIG%d", num)) - if kindClusterName == "" { - kindClusterName = fmt.Sprintf("kluctl-e2e-%d", num) - } - if kindKubeconfig == "" { - kindKubeconfig = filepath.Join(utils.GetTmpBaseDir(), fmt.Sprintf("kluctl-e2e-kubeconfig-%d.yml", num)) - } - return createKindCluster(kindClusterName, kindKubeconfig) -} - -func createDefaultKindClusters() (*KindCluster, *KindCluster) { - var k1, k2 *KindCluster - - var wg sync.WaitGroup - wg.Add(2) - go func() { - defer wg.Done() - k1 = createDefaultKindCluster(1) - }() - go func() { - defer wg.Done() - k2 = createDefaultKindCluster(2) - }() - wg.Wait() - return k1, k2 -} - -var ( - defaultKindCluster1, defaultKindCluster2 = createDefaultKindClusters() -) From 9ea226e761915421f31d621ca77ff1279e1a481f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 1 Jun 2022 12:32:05 +0200 Subject: [PATCH 0904/2916] tests: Move starting of kind clusters into go code And allow to specify extra ports to be exposed. --- .github/workflows/tests.yml | 38 +++++------ e2e/default_clusters.go | 68 +++++++++++++------- e2e/external_projects_test.go | 1 - e2e/project.go | 2 +- hack/start-kind-cluster.sh | 28 --------- internal/test-utils/kind_cluster.go | 97 +++++++++++++++++++---------- 6 files changed, 125 insertions(+), 109 deletions(-) delete mode 100755 hack/start-kind-cluster.sh diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 70690c075..b966ab2c8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -240,33 +240,27 @@ jobs: shell: bash run: | if [ "${{ runner.os }}" == "Linux" ]; then - PORT1=10000 - PORT2=20000 + echo "KIND_API_PORT1=10000" >> $GITHUB_ENV + echo "KIND_API_PORT2=20000" >> $GITHUB_ENV + echo "KIND_EXTRA_PORTS_OFFSET1=30000" >> $GITHUB_ENV + echo "KIND_EXTRA_PORTS_OFFSET2=31000" >> $GITHUB_ENV elif [ "${{ runner.os }}" == "Windows" ]; then - PORT1=10001 - PORT2=20001 + echo "KIND_API_PORT1=10001" >> $GITHUB_ENV + echo "KIND_API_PORT2=20001" >> $GITHUB_ENV + echo "KIND_EXTRA_PORTS_OFFSET1=30100" >> $GITHUB_ENV + echo "KIND_EXTRA_PORTS_OFFSET2=31100" >> $GITHUB_ENV else - PORT1=10002 - PORT2=20002 + echo "KIND_API_PORT1=10002" >> $GITHUB_ENV + echo "KIND_API_PORT2=20002" >> $GITHUB_ENV + echo "KIND_EXTRA_PORTS_OFFSET1=30200" >> $GITHUB_ENV + echo "KIND_EXTRA_PORTS_OFFSET2=31200" >> $GITHUB_ENV fi KIND_CLUSTER_NAME_BASE=$(echo "kluctl-${{ runner.os }}" | awk '{{ print tolower($1) }}') - KIND_CLUSTER_NAME1=$KIND_CLUSTER_NAME_BASE-1 - KIND_KUBECONFIG1=$(pwd)/kind-kubeconfig-1 - echo "KIND_CLUSTER_NAME1=$KIND_CLUSTER_NAME1" >> $GITHUB_ENV - echo "KIND_KUBECONFIG1=$KIND_KUBECONFIG1" >> $GITHUB_ENV - ./hack/start-kind-cluster.sh "$KIND_CLUSTER_NAME1" "$DOCKER_IP" "$PORT1" "$KIND_KUBECONFIG1" & - PID1=$! - - KIND_CLUSTER_NAME2=$KIND_CLUSTER_NAME_BASE-2 - KIND_KUBECONFIG2=$(pwd)/kind-kubeconfig-2 - echo "KIND_CLUSTER_NAME2=$KIND_CLUSTER_NAME2" >> $GITHUB_ENV - echo "KIND_KUBECONFIG2=$KIND_KUBECONFIG2" >> $GITHUB_ENV - ./hack/start-kind-cluster.sh "$KIND_CLUSTER_NAME2" "$DOCKER_IP" "$PORT2" "$KIND_KUBECONFIG2" & - PID2=$1 - - wait $PID1 - wait $PID2 + echo "KIND_API_HOST1=$DOCKER_IP" >> $GITHUB_ENV + echo "KIND_API_HOST2=$DOCKER_IP" >> $GITHUB_ENV + echo "KIND_CLUSTER_NAME1=$KIND_CLUSTER_NAME_BASE-1" >> $GITHUB_ENV + echo "KIND_CLUSTER_NAME2=$KIND_CLUSTER_NAME_BASE-2" >> $GITHUB_ENV - name: Download artifacts uses: actions/download-artifact@v2 - name: Run e2e tests diff --git a/e2e/default_clusters.go b/e2e/default_clusters.go index 664cf31cb..466df5b4f 100644 --- a/e2e/default_clusters.go +++ b/e2e/default_clusters.go @@ -6,51 +6,71 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils" "os" "path/filepath" + "strconv" "sync" ) -func createKindCluster(name string, kubeconfig string) *test_utils.KindCluster { - k, err := test_utils.CreateKindCluster(name, kubeconfig) - if err != nil { - panic(err) - } - return k -} - -func createDefaultKindCluster(num int) *test_utils.KindCluster { +func createDefaultKindCluster(num int) (*test_utils.KindCluster, int) { kindClusterName := os.Getenv(fmt.Sprintf("KIND_CLUSTER_NAME%d", num)) + kindApiHost := os.Getenv(fmt.Sprintf("KIND_API_HOST%d", num)) + kindApiPort := os.Getenv(fmt.Sprintf("KIND_API_PORT%d", num)) + kindExtraPortsOffset := os.Getenv(fmt.Sprintf("KIND_EXTRA_PORTS_OFFSET%d", num)) kindKubeconfig := os.Getenv(fmt.Sprintf("KIND_KUBECONFIG%d", num)) if kindClusterName == "" { kindClusterName = fmt.Sprintf("kluctl-e2e-%d", num) } + if kindApiHost == "" { + kindApiHost = "localhost" + } + if kindExtraPortsOffset == "" { + kindExtraPortsOffset = fmt.Sprintf("%d", 30000+num*1000) + } if kindKubeconfig == "" { kindKubeconfig = filepath.Join(utils.GetTmpBaseDir(), fmt.Sprintf("kluctl-e2e-kubeconfig-%d.yml", num)) } - return createKindCluster(kindClusterName, kindKubeconfig) + + var err error + var kindApiPortInt, kindExtraPortsOffsetInt int64 + + if kindApiPort != "" { + kindApiPortInt, err = strconv.ParseInt(kindApiPort, 0, 32) + if err != nil { + panic(err) + } + } + kindExtraPortsOffsetInt, err = strconv.ParseInt(kindExtraPortsOffset, 0, 32) + if err != nil { + panic(err) + } + + vaultPort := int(kindExtraPortsOffsetInt) + 0 + + kindExtraPorts := map[int]int{ + vaultPort: 30000, + } + + k, err := test_utils.CreateKindCluster(kindClusterName, kindApiHost, int(kindApiPortInt), kindExtraPorts, kindKubeconfig) + if err != nil { + panic(err) + } + return k, vaultPort } -func createDefaultKindClusters() (*test_utils.KindCluster, *test_utils.KindCluster) { - var k1, k2 *test_utils.KindCluster +var defaultKindCluster1, defaultKindCluster2 *test_utils.KindCluster +var defaultKindCluster1VaultPort, defaultKindCluster2VaultPort int +func init() { var wg sync.WaitGroup wg.Add(2) go func() { defer wg.Done() - k1 = createDefaultKindCluster(1) + defaultKindCluster1, defaultKindCluster1VaultPort = createDefaultKindCluster(1) + deleteTestNamespaces(defaultKindCluster1) }() go func() { defer wg.Done() - k2 = createDefaultKindCluster(2) + defaultKindCluster2, defaultKindCluster2VaultPort = createDefaultKindCluster(2) + deleteTestNamespaces(defaultKindCluster2) }() wg.Wait() - return k1, k2 -} - -var ( - defaultKindCluster1, defaultKindCluster2 = createDefaultKindClusters() -) - -func init() { - deleteTestNamespaces(defaultKindCluster1) - deleteTestNamespaces(defaultKindCluster2) } diff --git a/e2e/external_projects_test.go b/e2e/external_projects_test.go index bb2f2094e..eefd91b17 100644 --- a/e2e/external_projects_test.go +++ b/e2e/external_projects_test.go @@ -2,7 +2,6 @@ package e2e import ( "fmt" - "github.com/kluctl/kluctl/v2/internal/test-utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "testing" "time" diff --git a/e2e/project.go b/e2e/project.go index 3fb63a6fa..09367ff6c 100644 --- a/e2e/project.go +++ b/e2e/project.go @@ -93,7 +93,7 @@ func (p *testProject) cleanup() { func (p *testProject) mergeKubeconfig(k *test_utils.KindCluster) { p.updateMergedKubeconfig(func(config *clientcmdapi.Config) { - nkcfg, err := clientcmd.LoadFromFile(k.kubeconfig) + nkcfg, err := clientcmd.LoadFromFile(k.Kubeconfig) if err != nil { p.t.Fatal(err) } diff --git a/hack/start-kind-cluster.sh b/hack/start-kind-cluster.sh deleted file mode 100755 index 315d6887a..000000000 --- a/hack/start-kind-cluster.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env bash - -set -e - -NAME=$1 -IP=$2 -PORT=$3 -export KUBECONFIG=$4 - -cat << EOF > kind-cluster-$NAME.yml -kind: Cluster -apiVersion: kind.x-k8s.io/v1alpha4 -networking: - apiServerAddress: "0.0.0.0" - apiServerPort: $PORT -EOF - -rm -f $(pwd)/kind-kubeconfig -kind create cluster --config kind-cluster-$NAME.yml --name $NAME - -# Rewrite cluster info to point to docker host -# This also fully disables TLS verification -kubectl config view -ojson --raw \ - | jq ".clusters[0].cluster.\"insecure-skip-tls-verify\"=true" \ - | jq "del(.clusters[0].cluster.\"certificate-authority-data\")" \ - | jq ".clusters[0].cluster.server=\"https://$IP:$PORT\"" \ -> $KUBECONFIG-tmp -mv $KUBECONFIG-tmp $KUBECONFIG diff --git a/internal/test-utils/kind_cluster.go b/internal/test-utils/kind_cluster.go index d527f0173..eaa7c8dd5 100644 --- a/internal/test-utils/kind_cluster.go +++ b/internal/test-utils/kind_cluster.go @@ -4,11 +4,12 @@ import ( "fmt" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" - "github.com/pkg/errors" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" + "net/url" "os" "os/exec" + "sigs.k8s.io/kind/pkg/apis/config/v1alpha4" "sigs.k8s.io/kind/pkg/cluster" kindcmd "sigs.k8s.io/kind/pkg/cmd" "testing" @@ -18,17 +19,17 @@ import ( type KindCluster struct { Name string Context string - kubeconfig string + Kubeconfig string config *rest.Config } -func CreateKindCluster(name, kubeconfigPath string) (*KindCluster, error) { +func CreateKindCluster(name, apiServerHost string, apiServerPort int, extraPorts map[int]int, kubeconfigPath string) (*KindCluster, error) { provider := cluster.NewProvider(cluster.ProviderWithLogger(kindcmd.NewLogger())) c := &KindCluster{ Name: name, Context: fmt.Sprintf("kind-%s", name), - kubeconfig: kubeconfigPath, + Kubeconfig: kubeconfigPath, } n, err := provider.ListNodes(name) @@ -36,7 +37,7 @@ func CreateKindCluster(name, kubeconfigPath string) (*KindCluster, error) { return nil, err } if len(n) == 0 { - if err := kindCreate(name, kubeconfigPath); err != nil { + if err := kindCreate(name, apiServerHost, apiServerPort, extraPorts, kubeconfigPath); err != nil { return nil, err } } @@ -47,19 +48,14 @@ func CreateKindCluster(name, kubeconfigPath string) (*KindCluster, error) { // Delete removes the cluster from kind. The cluster may not be deleted straight away - this only issues a delete command func (c *KindCluster) Delete() error { provider := cluster.NewProvider(cluster.ProviderWithLogger(kindcmd.NewLogger())) - return provider.Delete(c.Name, c.kubeconfig) -} - -// Kubeconfig returns the path to the cluster kubeconfig -func (c *KindCluster) Kubeconfig() string { - return c.kubeconfig + return provider.Delete(c.Name, c.Kubeconfig) } // RESTConfig returns K8s client config to pass to clientset objects func (c *KindCluster) RESTConfig() *rest.Config { if c.config == nil { var err error - c.config, err = clientcmd.BuildConfigFromFlags("", c.Kubeconfig()) + c.config, err = clientcmd.BuildConfigFromFlags("", c.Kubeconfig) if err != nil { panic(err) } @@ -70,7 +66,7 @@ func (c *KindCluster) RESTConfig() *rest.Config { func (c *KindCluster) Kubectl(args ...string) (string, error) { cmd := exec.Command("kubectl", args...) cmd.Env = os.Environ() - cmd.Env = append(cmd.Env, fmt.Sprintf("KUBECONFIG=%s", c.kubeconfig)) + cmd.Env = append(cmd.Env, fmt.Sprintf("KUBECONFIG=%s", c.Kubeconfig)) stdout, err := cmd.Output() return string(stdout), err @@ -112,29 +108,64 @@ func (c *KindCluster) KubectlYamlMust(t *testing.T, args ...string) *uo.Unstruct } // kindCreate creates the kind cluster. It will retry up to 10 times if cluster creation fails. -func kindCreate(name, kubeconfig string) error { +func kindCreate(name string, apiServerHost string, apiServerPort int, extraPorts map[int]int, kubeconfig string) error { fmt.Printf("🌧️ Creating kind cluster %s...\n", name) provider := cluster.NewProvider(cluster.ProviderWithLogger(kindcmd.NewLogger())) - attempts := 0 - maxAttempts := 10 - for { - err := provider.Create( - name, - cluster.CreateWithNodeImage(""), - cluster.CreateWithRetain(false), - cluster.CreateWithWaitForReady(time.Duration(0)), - cluster.CreateWithKubeconfigPath(kubeconfig), - cluster.CreateWithDisplayUsage(false), - ) - if err == nil { - return nil - } + config := v1alpha4.Cluster{ + Name: name, + Nodes: []v1alpha4.Node{{ + Role: "control-plane", + }}, + Networking: v1alpha4.Networking{ + APIServerAddress: "0.0.0.0", + APIServerPort: int32(apiServerPort), + }, + } + for hostPort, containerPort := range extraPorts { + config.Nodes[0].ExtraPortMappings = append(config.Nodes[0].ExtraPortMappings, v1alpha4.PortMapping{ + ContainerPort: int32(containerPort), + HostPort: int32(hostPort), + ListenAddress: "0.0.0.0", + Protocol: "TCP", + }) + } - fmt.Printf("Error bringing up cluster, will retry (attempt %d): %v", attempts, err) - attempts++ - if attempts >= maxAttempts { - return errors.Wrapf(err, "Error bringing up cluster, exceeded max attempts (%d)", attempts) - } + err := provider.Create( + name, + cluster.CreateWithV1Alpha4Config(&config), + cluster.CreateWithNodeImage(""), + cluster.CreateWithRetain(false), + cluster.CreateWithWaitForReady(time.Duration(0)), + cluster.CreateWithKubeconfigPath(kubeconfig), + cluster.CreateWithDisplayUsage(false), + ) + if err != nil { + return err + } + + kcfg, err := clientcmd.LoadFromFile(kubeconfig) + if err != nil { + return err } + + c := kcfg.Clusters[fmt.Sprintf("kind-%s", name)] + + u, err := url.Parse(c.Server) + if err != nil { + return err + } + + // override api server host and disable TLS verification + // this is needed to make it work with remote docker hosts + c.InsecureSkipTLSVerify = true + c.CertificateAuthorityData = nil + c.Server = fmt.Sprintf("https://%s:%s", apiServerHost, u.Port()) + + err = clientcmd.WriteToFile(*kcfg, kubeconfig) + if err != nil { + return err + } + + return nil } From 548cbef7b529ddbc0a55ba6dbe30016fc0d960ee Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 1 Jun 2022 12:32:42 +0200 Subject: [PATCH 0905/2916] feat: Rename vault vars fields to align with go client lib --- pkg/types/vars_source.go | 4 ++-- pkg/vars/vars_loader.go | 2 +- pkg/vars/vault/secrets.go | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/types/vars_source.go b/pkg/types/vars_source.go index 006b9d2f4..68ba14731 100644 --- a/pkg/types/vars_source.go +++ b/pkg/types/vars_source.go @@ -38,8 +38,8 @@ type VarsSourceAwsSecretsManager struct { } type VarsSourceVault struct { - Server string `yaml:"server" validate:"required"` - Key string `yaml:"key" validate:"required"` + Address string `yaml:"address" validate:"required"` + Path string `yaml:"path" validate:"required"` } type VarsSource struct { diff --git a/pkg/vars/vars_loader.go b/pkg/vars/vars_loader.go index f6f2dfc76..bdb586bd8 100644 --- a/pkg/vars/vars_loader.go +++ b/pkg/vars/vars_loader.go @@ -146,7 +146,7 @@ func (v *VarsLoader) loadAwsSecretsManager(varsCtx *VarsCtx, source *types.VarsS } func (v *VarsLoader) loadVault(varsCtx *VarsCtx, source *types.VarsSource, rootKey string) error { - secret, err := vault.GetSecret(source.Vault.Server, source.Vault.Key) + secret, err := vault.GetSecret(source.Vault.Address, source.Vault.Path) if err != nil { return err } diff --git a/pkg/vars/vault/secrets.go b/pkg/vars/vault/secrets.go index 0a05e6579..49f04618f 100644 --- a/pkg/vars/vault/secrets.go +++ b/pkg/vars/vault/secrets.go @@ -13,14 +13,14 @@ var httpClient = &http.Client{ Timeout: 15 * time.Second, } -func GetSecret(server string, key string) (string, error) { +func GetSecret(server string, path string) (string, error) { client, err := api.NewClient(&api.Config{Address: server, HttpClient: httpClient}) if err != nil { return "", fmt.Errorf("failed to create vault %s client", server) } - secret, err := client.Logical().Read(key) + secret, err := client.Logical().Read(path) if err != nil { - return "", fmt.Errorf("connection to vault %s failed", server) + return "", fmt.Errorf("reading from vault failed: %v", err) } if secret == nil || secret.Data == nil { return "", fmt.Errorf("the specified vault secret was not found") From a3ffe50a8675a62d37afff82682c8aa624fef425 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 31 May 2022 16:11:28 +0200 Subject: [PATCH 0906/2916] tests: Add vault sealing tests --- e2e/project.go | 2 + e2e/seal_test.go | 140 ++++++++++++---- e2e/test_resources/README.md | 7 +- e2e/test_resources/resources.go | 32 +++- e2e/test_resources/vault-values.yaml | 10 ++ e2e/test_resources/vault.yaml | 235 +++++++++++++++++++++++++++ 6 files changed, 396 insertions(+), 30 deletions(-) create mode 100644 e2e/test_resources/vault-values.yaml create mode 100644 e2e/test_resources/vault.yaml diff --git a/e2e/project.go b/e2e/project.go index 09367ff6c..d80c0c3e4 100644 --- a/e2e/project.go +++ b/e2e/project.go @@ -20,6 +20,7 @@ import ( type testProject struct { t *testing.T + extraEnv []string projectName string kluctlProjectExternal bool @@ -433,6 +434,7 @@ func (p *testProject) Kluctl(argsIn ...string) (string, string, error) { args = append(args, "--debug") } env := os.Environ() + env = append(env, p.extraEnv...) env = append(env, fmt.Sprintf("KUBECONFIG=%s", p.mergedKubeconfig)) p.t.Logf("Runnning kluctl: %s", strings.Join(args, " ")) diff --git a/e2e/seal_test.go b/e2e/seal_test.go index b28cc12e7..c04c67361 100644 --- a/e2e/seal_test.go +++ b/e2e/seal_test.go @@ -8,30 +8,51 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/stretchr/testify/assert" - "os" + "io/ioutil" + "net/http" + "net/url" "path/filepath" + "strings" + "sync" "testing" "time" ) -func installSealedSecretsOperator(t *testing.T, k *test_utils.KindCluster) { - tmpFile, _ := os.CreateTemp("", "") - tmpFile.Close() - defer os.Remove(tmpFile.Name()) +func installSealedSecretsOperator(k *test_utils.KindCluster) { + test_resources.ApplyYaml("sealed-secrets.yaml", k) +} - _ = utils.FsCopyFile(test_resources.Yamls, "sealed-secrets.yaml", tmpFile.Name()) +func waitForSealedSecretsOperator(t *testing.T, k *test_utils.KindCluster) { + waitForReadiness(t, k, "kube-system", "deployment/sealed-secrets-controller", 5*time.Minute) +} - _, err := k.Kubectl("apply", "-f", tmpFile.Name()) - if err != nil { - panic(err) - } +func deleteSealedSecretsOperator(k *test_utils.KindCluster) { + _, _ = k.Kubectl("-n", "kube-system", "delete", "deployment", "sealed-secrets-controller", "--wait") + _, _ = k.Kubectl("-n", "kube-system", "delete", "secret", "-l", "sealedsecrets.bitnami.com/sealed-secrets-key", "--wait") +} - waitForReadiness(t, k, "kube-system", "deployment/sealed-secrets-controller", 5*time.Minute) +func installVault(k *test_utils.KindCluster) { + _, _ = k.Kubectl("create", "ns", "vault") + test_resources.ApplyYaml("vault.yaml", k) } -func deleteSealedSecretsOperator(t *testing.T, k *test_utils.KindCluster) { - _, _ = defaultKindCluster1.Kubectl("-n", "kube-system", "delete", "deployment", "sealed-secrets-controller", "--wait") - _, _ = defaultKindCluster1.Kubectl("-n", "kube-system", "delete", "secret", "-l", "sealedsecrets.bitnami.com/sealed-secrets-key", "--wait") +func waitForVault(t *testing.T, k *test_utils.KindCluster) { + waitForReadiness(t, k, "vault", "statefulset/vault", 5*time.Minute) +} + +func init() { + var wg sync.WaitGroup + wg.Add(2) + go func() { + defer wg.Done() + installSealedSecretsOperator(defaultKindCluster1) + installVault(defaultKindCluster1) + }() + go func() { + defer wg.Done() + installSealedSecretsOperator(defaultKindCluster2) + }() + wg.Wait() } func prepareSealTest(t *testing.T, k *test_utils.KindCluster, namespace string, secrets map[string]string, varsSources []*uo.UnstructuredObject) *testProject { @@ -76,8 +97,7 @@ func TestSeal_WithOperator(t *testing.T) { k := defaultKindCluster1 namespace := "seal-with-operator" - deleteSealedSecretsOperator(t, k) - installSealedSecretsOperator(t, k) + waitForSealedSecretsOperator(t, k) p := prepareSealTest(t, k, namespace, map[string]string{ @@ -113,7 +133,10 @@ func TestSeal_WithBootstrap(t *testing.T) { k := defaultKindCluster2 namespace := "seal-with-bootstrap" - deleteSealedSecretsOperator(t, k) + // we still wait for it to be ready before we then delete it + // this way it's pre-pulled and pre-warmed when we later start it + waitForSealedSecretsOperator(t, k) + deleteSealedSecretsOperator(k) p := prepareSealTest(t, k, namespace, map[string]string{ @@ -136,7 +159,8 @@ func TestSeal_WithBootstrap(t *testing.T) { sealedSecretsDir := p.gitServer.LocalRepoDir(p.getSealedSecretsRepo()) assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) - installSealedSecretsOperator(t, k) + installSealedSecretsOperator(k) + waitForSealedSecretsOperator(t, k) p.KluctlMust("deploy", "--yes", "-t", "test-target") @@ -153,7 +177,7 @@ func TestSeal_MultipleVarSources(t *testing.T) { k := defaultKindCluster1 namespace := "seal-multiple-vs" - installSealedSecretsOperator(t, k) + waitForSealedSecretsOperator(t, k) p := prepareSealTest(t, k, namespace, map[string]string{ @@ -195,7 +219,7 @@ func TestSeal_MultipleSecretSets(t *testing.T) { k := defaultKindCluster1 namespace := "seal-multiple-ss" - installSealedSecretsOperator(t, k) + waitForSealedSecretsOperator(t, k) p := prepareSealTest(t, k, namespace, map[string]string{ @@ -239,8 +263,8 @@ func TestSeal_MultipleTargets(t *testing.T) { k := defaultKindCluster1 namespace := "seal-multiple-targets" - installSealedSecretsOperator(t, k) - installSealedSecretsOperator(t, defaultKindCluster2) + waitForSealedSecretsOperator(t, k) + waitForSealedSecretsOperator(t, defaultKindCluster2) p := prepareSealTest(t, k, namespace, map[string]string{ @@ -253,11 +277,6 @@ func TestSeal_MultipleTargets(t *testing.T) { "s1": "v1", }, }), - uo.FromMap(map[string]interface{}{ - "values": map[string]interface{}{ - "s2": "v2", - }, - }), }, ) defer p.cleanup() @@ -304,10 +323,12 @@ func TestSeal_MultipleTargets(t *testing.T) { } func TestSeal_File(t *testing.T) { + t.Parallel() + k := defaultKindCluster1 namespace := "seal-file" - installSealedSecretsOperator(t, k) + waitForSealedSecretsOperator(t, k) p := prepareSealTest(t, k, namespace, map[string]string{ @@ -345,3 +366,66 @@ func TestSeal_File(t *testing.T) { "s2": "v2", }) } + +func TestSeal_Vault(t *testing.T) { + t.Parallel() + + k := defaultKindCluster1 + namespace := "seal-vault" + + waitForSealedSecretsOperator(t, k) + waitForVault(t, k) + + u, err := url.Parse(defaultKindCluster1.RESTConfig().Host) + if err != nil { + t.Fatal(err) + } + + vaultUrl := fmt.Sprintf("http://%s:%d", u.Hostname(), defaultKindCluster1VaultPort) + + req, err := http.NewRequest("POST", fmt.Sprintf("%s/v1/secret/data/secret", vaultUrl), strings.NewReader(`{"data": {"secrets":{"s1":"v1","s2":"v2"}}}`)) + if err != nil { + t.Fatal(err) + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-Vault-Token", "root") + resp, err := http.DefaultClient.Do(req) + if err != nil { + t.Fatal(err) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + body, _ := ioutil.ReadAll(resp.Body) + t.Fatalf("vault response status %d, body=%s", resp.StatusCode, string(body)) + } + + p := prepareSealTest(t, k, namespace, + map[string]string{ + "s1": "{{ secrets.s1 }}", + "s2": "{{ secrets.s2 }}", + }, + []*uo.UnstructuredObject{ + uo.FromMap(map[string]interface{}{ + "vault": map[string]interface{}{ + "address": vaultUrl, + "path": "secret/data/secret", + }, + }), + }, + ) + defer p.cleanup() + + p.extraEnv = append(p.extraEnv, "VAULT_TOKEN=root") + p.KluctlMust("seal", "-t", "test-target") + + sealedSecretsDir := p.gitServer.LocalRepoDir(p.getSealedSecretsRepo()) + assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) + + p.KluctlMust("deploy", "--yes", "-t", "test-target") + + waitForReadiness(t, k, namespace, "secret/secret", 1*time.Minute) + assertDecryptedSecrets(t, k, namespace, "secret", map[string]string{ + "s1": "v1", + "s2": "v2", + }) +} diff --git a/e2e/test_resources/README.md b/e2e/test_resources/README.md index 75d21866f..e3655fad6 100644 --- a/e2e/test_resources/README.md +++ b/e2e/test_resources/README.md @@ -1,4 +1,9 @@ # sealed-secrets.yaml helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets -helm template sealed-secrets-controller sealed-secrets/sealed-secrets -n kube-system --include-crds > sealed-secrets.yaml +helm template sealed-secrets-controller sealed-secrets/sealed-secrets -n kube-system --include-crds --skip-tests > sealed-secrets.yaml + +# vault.yaml + +helm repo add hashicorp https://helm.releases.hashicorp.com +helm template vault hashicorp/vault -n vault -f vault-values.yaml --include-crds --skip-tests > vault.yaml diff --git a/e2e/test_resources/resources.go b/e2e/test_resources/resources.go index 0ec6d270c..2645c7488 100644 --- a/e2e/test_resources/resources.go +++ b/e2e/test_resources/resources.go @@ -1,6 +1,36 @@ package test_resources -import "embed" +import ( + "embed" + test_utils "github.com/kluctl/kluctl/v2/internal/test-utils" + "github.com/kluctl/kluctl/v2/pkg/utils" + "os" +) //go:embed *.yaml var Yamls embed.FS + +func GetYamlTmpFile(name string) string { + tmpFile, err := os.CreateTemp("", "") + if err != nil { + panic(err) + } + tmpFile.Close() + + err = utils.FsCopyFile(Yamls, name, tmpFile.Name()) + if err != nil { + panic(err) + } + + return tmpFile.Name() +} + +func ApplyYaml(name string, k *test_utils.KindCluster) { + tmpFile := GetYamlTmpFile(name) + defer os.Remove(tmpFile) + + _, err := k.Kubectl("apply", "-f", tmpFile) + if err != nil { + panic(err) + } +} \ No newline at end of file diff --git a/e2e/test_resources/vault-values.yaml b/e2e/test_resources/vault-values.yaml new file mode 100644 index 000000000..1934c072a --- /dev/null +++ b/e2e/test_resources/vault-values.yaml @@ -0,0 +1,10 @@ +server: + # Must be RollingUpdate as otherwise it's reported as ready much too early + updateStrategyType: RollingUpdate + dev: + enabled: true + service: + type: NodePort + nodePort: 30000 +injector: + enabled: false diff --git a/e2e/test_resources/vault.yaml b/e2e/test_resources/vault.yaml new file mode 100644 index 000000000..13e65b084 --- /dev/null +++ b/e2e/test_resources/vault.yaml @@ -0,0 +1,235 @@ +--- +# Source: vault/templates/server-serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: vault + namespace: vault + labels: + helm.sh/chart: vault-0.20.1 + app.kubernetes.io/name: vault + app.kubernetes.io/instance: vault + app.kubernetes.io/managed-by: Helm +--- +# Source: vault/templates/server-clusterrolebinding.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: vault-server-binding + labels: + helm.sh/chart: vault-0.20.1 + app.kubernetes.io/name: vault + app.kubernetes.io/instance: vault + app.kubernetes.io/managed-by: Helm +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:auth-delegator +subjects: +- kind: ServiceAccount + name: vault + namespace: vault +--- +# Source: vault/templates/server-headless-service.yaml +# Service for Vault cluster +apiVersion: v1 +kind: Service +metadata: + name: vault-internal + namespace: vault + labels: + helm.sh/chart: vault-0.20.1 + app.kubernetes.io/name: vault + app.kubernetes.io/instance: vault + app.kubernetes.io/managed-by: Helm + annotations: + +spec: + clusterIP: None + publishNotReadyAddresses: true + ports: + - name: "http" + port: 8200 + targetPort: 8200 + - name: https-internal + port: 8201 + targetPort: 8201 + selector: + app.kubernetes.io/name: vault + app.kubernetes.io/instance: vault + component: server +--- +# Source: vault/templates/server-service.yaml +# Service for Vault cluster +apiVersion: v1 +kind: Service +metadata: + name: vault + namespace: vault + labels: + helm.sh/chart: vault-0.20.1 + app.kubernetes.io/name: vault + app.kubernetes.io/instance: vault + app.kubernetes.io/managed-by: Helm + annotations: + +spec: + type: NodePort + externalTrafficPolicy: Cluster + # We want the servers to become available even if they're not ready + # since this DNS is also used for join operations. + publishNotReadyAddresses: true + ports: + - name: http + port: 8200 + targetPort: 8200 + nodePort: 30000 + - name: https-internal + port: 8201 + targetPort: 8201 + selector: + app.kubernetes.io/name: vault + app.kubernetes.io/instance: vault + component: server +--- +# Source: vault/templates/server-statefulset.yaml +# StatefulSet to run the actual vault server cluster. +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: vault + namespace: vault + labels: + app.kubernetes.io/name: vault + app.kubernetes.io/instance: vault + app.kubernetes.io/managed-by: Helm +spec: + serviceName: vault-internal + podManagementPolicy: Parallel + replicas: 1 + updateStrategy: + type: RollingUpdate + selector: + matchLabels: + app.kubernetes.io/name: vault + app.kubernetes.io/instance: vault + component: server + template: + metadata: + labels: + helm.sh/chart: vault-0.20.1 + app.kubernetes.io/name: vault + app.kubernetes.io/instance: vault + component: server + spec: + + + + + terminationGracePeriodSeconds: 10 + serviceAccountName: vault + + securityContext: + runAsNonRoot: true + runAsGroup: 1000 + runAsUser: 100 + fsGroup: 1000 + volumes: + + - name: home + emptyDir: {} + containers: + - name: vault + + image: hashicorp/vault:1.10.3 + imagePullPolicy: IfNotPresent + command: + - "/bin/sh" + - "-ec" + args: + - | + /usr/local/bin/docker-entrypoint.sh vault server -dev + + securityContext: + allowPrivilegeEscalation: false + env: + - name: HOST_IP + valueFrom: + fieldRef: + fieldPath: status.hostIP + - name: POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + - name: VAULT_K8S_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: VAULT_K8S_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: VAULT_ADDR + value: "http://127.0.0.1:8200" + - name: VAULT_API_ADDR + value: "http://$(POD_IP):8200" + - name: SKIP_CHOWN + value: "true" + - name: SKIP_SETCAP + value: "true" + - name: HOSTNAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: VAULT_CLUSTER_ADDR + value: "https://$(HOSTNAME).vault-internal:8201" + - name: HOME + value: "/home/vault" + + - name: VAULT_DEV_ROOT_TOKEN_ID + value: root + - name: VAULT_DEV_LISTEN_ADDRESS + value: "[::]:8200" + + + + volumeMounts: + + + + - name: home + mountPath: /home/vault + ports: + - containerPort: 8200 + name: http + - containerPort: 8201 + name: https-internal + - containerPort: 8202 + name: http-rep + readinessProbe: + # Check status; unsealed vault servers return 0 + # The exit code reflects the seal status: + # 0 - unsealed + # 1 - error + # 2 - sealed + exec: + command: ["/bin/sh", "-ec", "vault status -tls-skip-verify"] + failureThreshold: 2 + initialDelaySeconds: 5 + periodSeconds: 5 + successThreshold: 1 + timeoutSeconds: 3 + lifecycle: + # Vault container doesn't receive SIGTERM from Kubernetes + # and after the grace period ends, Kube sends SIGKILL. This + # causes issues with graceful shutdowns such as deregistering itself + # from Consul (zombie services). + preStop: + exec: + command: [ + "/bin/sh", "-c", + # Adding a sleep here to give the pod eviction a + # chance to propagate, so requests will not be made + # to this pod while it's terminating + "sleep 5 && kill -SIGTERM $(pidof vault)", + ] From e9ef495487ccc3c48d42f46888c77aae756b3c87 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 1 Jun 2022 13:07:31 +0200 Subject: [PATCH 0907/2916] tests: Fully delete all sealed-secrets resources in deleteSealedSecretsOperator --- e2e/seal_test.go | 3 ++- e2e/test_resources/resources.go | 12 +++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/e2e/seal_test.go b/e2e/seal_test.go index c04c67361..d9a6bdce1 100644 --- a/e2e/seal_test.go +++ b/e2e/seal_test.go @@ -27,8 +27,9 @@ func waitForSealedSecretsOperator(t *testing.T, k *test_utils.KindCluster) { } func deleteSealedSecretsOperator(k *test_utils.KindCluster) { - _, _ = k.Kubectl("-n", "kube-system", "delete", "deployment", "sealed-secrets-controller", "--wait") + test_resources.DeleteYaml("sealed-secrets.yaml", k) _, _ = k.Kubectl("-n", "kube-system", "delete", "secret", "-l", "sealedsecrets.bitnami.com/sealed-secrets-key", "--wait") + _, _ = k.Kubectl("-n", "kube-system", "delete", "configmap", "sealed-secrets-key-kluctl-bootstrap", "--wait") } func installVault(k *test_utils.KindCluster) { diff --git a/e2e/test_resources/resources.go b/e2e/test_resources/resources.go index 2645c7488..7644d1f09 100644 --- a/e2e/test_resources/resources.go +++ b/e2e/test_resources/resources.go @@ -33,4 +33,14 @@ func ApplyYaml(name string, k *test_utils.KindCluster) { if err != nil { panic(err) } -} \ No newline at end of file +} + +func DeleteYaml(name string, k *test_utils.KindCluster) { + tmpFile := GetYamlTmpFile(name) + defer os.Remove(tmpFile) + + _, err := k.Kubectl("delete", "-f", tmpFile) + if err != nil { + panic(err) + } +} From 377503949fc7465bff8c6e2c8189173116d5054a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 1 Jun 2022 13:07:41 +0200 Subject: [PATCH 0908/2916] tests: Fix TestSeal_MultipleTargets --- e2e/seal_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/e2e/seal_test.go b/e2e/seal_test.go index d9a6bdce1..28e6b976f 100644 --- a/e2e/seal_test.go +++ b/e2e/seal_test.go @@ -276,6 +276,7 @@ func TestSeal_MultipleTargets(t *testing.T) { uo.FromMap(map[string]interface{}{ "values": map[string]interface{}{ "s1": "v1", + "s2": "v2", }, }), }, From c0f409eabf1368150ac484c43f6cb7189aba17bf Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 1 Jun 2022 13:19:55 +0200 Subject: [PATCH 0909/2916] tests: Add some more debug info to kindCreate logging --- internal/test-utils/kind_cluster.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/test-utils/kind_cluster.go b/internal/test-utils/kind_cluster.go index eaa7c8dd5..791f8753c 100644 --- a/internal/test-utils/kind_cluster.go +++ b/internal/test-utils/kind_cluster.go @@ -110,7 +110,7 @@ func (c *KindCluster) KubectlYamlMust(t *testing.T, args ...string) *uo.Unstruct // kindCreate creates the kind cluster. It will retry up to 10 times if cluster creation fails. func kindCreate(name string, apiServerHost string, apiServerPort int, extraPorts map[int]int, kubeconfig string) error { - fmt.Printf("🌧️ Creating kind cluster %s...\n", name) + fmt.Printf("🌧️ Creating kind cluster %s with apiServerHost=%s, apiServerPort=%d, extraPorts=%v...\n", name, apiServerHost, apiServerPort, extraPorts) provider := cluster.NewProvider(cluster.ProviderWithLogger(kindcmd.NewLogger())) config := v1alpha4.Cluster{ Name: name, From c0a77304296e05a7928269da0e0bf5f6624b181d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 1 Jun 2022 13:58:09 +0200 Subject: [PATCH 0910/2916] tests: Reintroduce retries when creating kind clusters --- .github/workflows/tests.yml | 2 +- internal/test-utils/kind_cluster.go | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b966ab2c8..88c84e18e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -236,7 +236,7 @@ jobs: run: | kubectl version || true kind version || true - - name: Start kind cluster + - name: Prepare kind cluster variables shell: bash run: | if [ "${{ runner.os }}" == "Linux" ]; then diff --git a/internal/test-utils/kind_cluster.go b/internal/test-utils/kind_cluster.go index 791f8753c..99ad404d7 100644 --- a/internal/test-utils/kind_cluster.go +++ b/internal/test-utils/kind_cluster.go @@ -109,7 +109,17 @@ func (c *KindCluster) KubectlYamlMust(t *testing.T, args ...string) *uo.Unstruct // kindCreate creates the kind cluster. It will retry up to 10 times if cluster creation fails. func kindCreate(name string, apiServerHost string, apiServerPort int, extraPorts map[int]int, kubeconfig string) error { + var err error + for i := 0; i < 10; i++ { + err = kindCreate2(name, apiServerHost, apiServerPort, extraPorts, kubeconfig) + if err == nil { + return nil + } + } + return err +} +func kindCreate2(name string, apiServerHost string, apiServerPort int, extraPorts map[int]int, kubeconfig string) error { fmt.Printf("🌧️ Creating kind cluster %s with apiServerHost=%s, apiServerPort=%d, extraPorts=%v...\n", name, apiServerHost, apiServerPort, extraPorts) provider := cluster.NewProvider(cluster.ProviderWithLogger(kindcmd.NewLogger())) config := v1alpha4.Cluster{ From c20d5917abc245164056051317992bb25372139e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 1 Jun 2022 18:54:15 +0200 Subject: [PATCH 0911/2916] ci: Increase inotify limits for docker host --- .github/workflows/tests.yml | 6 +++++- hack/setup-docker-port-forward.sh | 20 -------------------- 2 files changed, 5 insertions(+), 21 deletions(-) delete mode 100755 hack/setup-docker-port-forward.sh diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 88c84e18e..e6a9124ca 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -85,11 +85,15 @@ jobs: - name: Checkout uses: actions/checkout@v2 - name: Install zerotier - if: runner.os == 'Linux' run: | sudo apt update sudo apt install -y gpg jq curl -s https://install.zerotier.com | sudo bash + - name: Setup inotify limits + run: | + # see https://kind.sigs.k8s.io/docs/user/known-issues/#pod-errors-due-to-too-many-open-files + sudo sysctl fs.inotify.max_user_watches=524288 + sudo sysctl fs.inotify.max_user_instances=512 - name: Stop docker run: | # Ensure docker is down and that the test jobs can wait for it to be available again after joining the network. diff --git a/hack/setup-docker-port-forward.sh b/hack/setup-docker-port-forward.sh deleted file mode 100755 index 886ed268f..000000000 --- a/hack/setup-docker-port-forward.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bash - -set -e - -echo "Forwarding ports" - -# pre-create this to avoid races in the background ssh calls -mkdir -p $HOME/.ssh - -# docker -tail -F ssh-log-2375 & -nohup /usr/bin/ssh -i kluctl-ci.pem -o StrictHostKeyChecking=no -L2375:/run/docker.sock -N kluctl-ci@docker.ci.kluctl.io &> ssh-log-2375 & - -while ! curl http://localhost:2375 &> /dev/null; do - echo "Waiting for ports to get available..." - sleep 5 -done - -# keep ports alive -nohup bash -c "while true; do curl http://localhost:2375 &> /dev/null ; sleep 5; done" & From 058f619d71298e46cffe17f5633163e7de6b426e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 7 Jun 2022 09:03:07 +0200 Subject: [PATCH 0912/2916] ci: Exclude "ci" commits from changelog --- .goreleaser.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 029ace461..2461d3dd0 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -61,6 +61,7 @@ changelog: - '^test.*:' - '^chore:' - '^build:' + - '^ci:' - '^refactor:' release: From 937967b840da9ccda9b215cd12ceaeee030edae2 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 7 Jun 2022 09:03:17 +0200 Subject: [PATCH 0913/2916] docs: fix typo in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6cd3ba354..c19f8f86f 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Kluctl does not depend on external operators/controllers and allows to use the s as long as access to the kluctl project and clusters is available. This means, that you can use it from your local machine, from your CI/CD pipelines or any automation platform/system that allows to call custom tools. -Flux support is in alpha statium and available via the [flux-kluctl-controller](https://github.com/kluctl/flux-kluctl-controller). +Flux support is in alpha stadium and available via the [flux-kluctl-controller](https://github.com/kluctl/flux-kluctl-controller). ## Installation From 56fe43b968fe2e13f9cc5b5fee4be94eebe509ac Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 7 Jun 2022 09:29:49 +0200 Subject: [PATCH 0914/2916] refactor: Stop using gammazero WorkerPool --- .../commands/cmd_check_image_updates.go | 16 ++-- pkg/deployment/deployment_collection.go | 47 +++++++---- pkg/deployment/deployment_item.go | 55 ++++++------ pkg/k8s/k8s_cluster.go | 51 +++++------ pkg/kluctl_project/targets.go | 16 ++-- pkg/utils/workerpool.go | 84 ------------------- 6 files changed, 94 insertions(+), 175 deletions(-) delete mode 100644 pkg/utils/workerpool.go diff --git a/cmd/kluctl/commands/cmd_check_image_updates.go b/cmd/kluctl/commands/cmd_check_image_updates.go index 1da6883a3..14be5ede7 100644 --- a/cmd/kluctl/commands/cmd_check_image_updates.go +++ b/cmd/kluctl/commands/cmd_check_image_updates.go @@ -37,11 +37,9 @@ func runCheckImageUpdates(ctx *commandCtx) error { rh := registries.NewRegistryHelper(ctx.ctx) - wg := utils.NewWorkerPoolWithErrors(8) - defer wg.StopWait(false) - imageTags := make(map[string]interface{}) var mutex sync.Mutex + var wg sync.WaitGroup for _, images := range renderedImages { for _, image := range images { @@ -51,7 +49,9 @@ func runCheckImageUpdates(ctx *commandCtx) error { } repo := s[0] if _, ok := imageTags[repo]; !ok { - wg.Submit(func() error { + wg.Add(1) + go func() { + defer wg.Done() tags, err := rh.ListImageTags(repo) mutex.Lock() defer mutex.Unlock() @@ -60,15 +60,11 @@ func runCheckImageUpdates(ctx *commandCtx) error { } else { imageTags[repo] = tags } - return nil - }) + }() } } } - err := wg.StopWait(false) - if err != nil { - return err - } + wg.Wait() prefixPattern := regexp.MustCompile("^([a-zA-Z]+[a-zA-Z-_.]*)") suffixPattern := regexp.MustCompile("([-][a-zA-Z]+[a-zA-Z-_.]*)$") diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index be086ebb2..0a379cffa 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -109,18 +109,26 @@ func (c *DeploymentCollection) RenderDeployments() error { s := status.Start(c.ctx.Ctx, "Rendering templates") defer s.Failed() - wp := utils.NewDebuggerAwareWorkerPool(16) - defer wp.StopWait(false) + var wg sync.WaitGroup + var mutex sync.Mutex + var errors []error for _, d := range c.Deployments { - err := d.render(c.forSeal, wp) - if err != nil { - return err - } + d := d + wg.Add(1) + go func() { + defer wg.Done() + err := d.render(c.forSeal) + if err != nil { + mutex.Lock() + errors = append(errors, err) + mutex.Unlock() + } + }() } - err := wp.StopWait(true) - if err != nil { - return err + wg.Wait() + if len(errors) != 0 { + return utils.NewErrorListOrNil(errors) } s.Success() @@ -128,14 +136,21 @@ func (c *DeploymentCollection) RenderDeployments() error { defer s.Failed() for _, d := range c.Deployments { - err := d.renderHelmCharts(wp) - if err != nil { - return err - } + d := d + wg.Add(1) + go func() { + defer wg.Done() + err := d.renderHelmCharts() + if err != nil { + mutex.Lock() + errors = append(errors, err) + mutex.Unlock() + } + }() } - err = wp.StopWait(false) - if err != nil { - return err + wg.Wait() + if len(errors) != 0 { + return utils.NewErrorListOrNil(errors) } s.Success() diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 51957e455..959830915 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -101,7 +101,7 @@ func (di *DeploymentItem) getCommonAnnotations() map[string]string { return a } -func (di *DeploymentItem) render(forSeal bool, wp *utils.WorkerPoolWithErrors) error { +func (di *DeploymentItem) render(forSeal bool) error { if di.dir == nil { return nil } @@ -145,14 +145,10 @@ func (di *DeploymentItem) render(forSeal bool, wp *utils.WorkerPoolWithErrors) e excludePatterns = append(excludePatterns, "**.sealme") } - wp.Submit(func() error { - return varsCtx.RenderDirectory(di.Project.source.dir, di.Project.getRenderSearchDirs(), di.Project.relDir, excludePatterns, di.RelToProjectItemDir, di.RenderedDir) - }) - - return nil + return varsCtx.RenderDirectory(di.Project.source.dir, di.Project.getRenderSearchDirs(), di.Project.relDir, excludePatterns, di.RelToProjectItemDir, di.RenderedDir) } -func (di *DeploymentItem) renderHelmCharts(wp *utils.WorkerPoolWithErrors) error { +func (di *DeploymentItem) renderHelmCharts() error { if di.dir == nil { return nil } @@ -162,35 +158,32 @@ func (di *DeploymentItem) renderHelmCharts(wp *utils.WorkerPoolWithErrors) error return nil } - wp.Submit(func() error { - subDir, err := filepath.Rel(di.RenderedDir, filepath.Dir(p)) - if err != nil { - return err - } + subDir, err := filepath.Rel(di.RenderedDir, filepath.Dir(p)) + if err != nil { + return err + } - chart, err := NewHelmChart(p) - if err != nil { - return err - } + chart, err := NewHelmChart(p) + if err != nil { + return err + } - ky, err := di.readKustomizationYaml(subDir) - if err == nil && ky != nil { - resources, _, _ := ky.GetNestedStringList("resources") - found := false - for _, r := range resources { - if r == chart.GetOutputPath() { - found = true - break - } - } - if !found { - return fmt.Errorf("%s/kustomization.yaml does not include the rendered helm chart: %s", di.RelRenderedDir, chart.GetOutputPath()) + ky, err := di.readKustomizationYaml(subDir) + if err == nil && ky != nil { + resources, _, _ := ky.GetNestedStringList("resources") + found := false + for _, r := range resources { + if r == chart.GetOutputPath() { + found = true + break } } + if !found { + return fmt.Errorf("%s/kustomization.yaml does not include the rendered helm chart: %s", di.RelRenderedDir, chart.GetOutputPath()) + } + } - return chart.Render(di.ctx.Ctx, di.ctx.K) - }) - return nil + return chart.Render(di.ctx.Ctx, di.ctx.K) }) if err != nil { return err diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index 289e3d53a..32499284f 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -164,12 +164,11 @@ func (k *K8sCluster) ListObjectsMetadata(gvk schema.GroupVersionKind, namespace } func (k *K8sCluster) ListAllObjects(verbs []string, namespace string, labels map[string]string, onlyMetadata bool) ([]*uo.UnstructuredObject, map[schema.GroupVersionKind][]ApiWarning, error) { - wp := utils.NewWorkerPoolWithErrors(8) - defer wp.StopWait(false) - var ret []*uo.UnstructuredObject + var errs []error retApiWarnings := make(map[schema.GroupVersionKind][]ApiWarning) var mutex sync.Mutex + var wg sync.WaitGroup filter := func(ar *v1.APIResource) bool { foundVerb := false @@ -184,7 +183,10 @@ func (k *K8sCluster) ListAllObjects(verbs []string, namespace string, labels map for _, gvk := range k.Resources.GetFilteredPreferredGVKs(filter) { gvk := gvk - wp.Submit(func() error { + wg.Add(1) + go func() { + defer wg.Done() + var l []*uo.UnstructuredObject var apiWarnings []ApiWarning var err error @@ -193,23 +195,24 @@ func (k *K8sCluster) ListAllObjects(verbs []string, namespace string, labels map } else { l, apiWarnings, err = k.ListObjects(gvk, namespace, labels) } - if err != nil && !errors.IsNotFound(err) { - return err - } mutex.Lock() defer mutex.Unlock() + if err != nil && !errors.IsNotFound(err) { + errs = append(errs, err) + return + } ret = append(ret, l...) if len(apiWarnings) != 0 { retApiWarnings[gvk] = apiWarnings } - return nil - }) + }() } + wg.Wait() - err := wp.StopWait(false) - if err != nil { - return nil, retApiWarnings, err + if len(errs) != 0 { + return nil, retApiWarnings, utils.NewErrorListOrNil(errs) } + return ret, retApiWarnings, nil } @@ -228,16 +231,17 @@ func (k *K8sCluster) GetSingleObject(ref k8s.ObjectRef) (*uo.UnstructuredObject, } func (k *K8sCluster) GetObjectsByRefs(refs []k8s.ObjectRef) ([]*uo.UnstructuredObject, map[k8s.ObjectRef][]ApiWarning, error) { - wp := utils.NewWorkerPoolWithErrors(32) - defer wp.StopWait(false) - var ret []*uo.UnstructuredObject + var errs []error retApiWarnings := make(map[k8s.ObjectRef][]ApiWarning) var mutex sync.Mutex + var wg sync.WaitGroup for _, ref_ := range refs { ref := ref_ - wp.Submit(func() error { + wg.Add(1) + go func() { + defer wg.Done() o, apiWarnings, err := k.GetSingleObject(ref) mutex.Lock() defer mutex.Unlock() @@ -245,18 +249,17 @@ func (k *K8sCluster) GetObjectsByRefs(refs []k8s.ObjectRef) ([]*uo.UnstructuredO retApiWarnings[ref] = apiWarnings } if err != nil { - if errors.IsNotFound(err) || meta.IsNoMatchError(err) { - return nil + if !errors.IsNotFound(err) && !meta.IsNoMatchError(err) { + errs = append(errs, err) } - return err + return } ret = append(ret, o) - return nil - }) + }() } - err := wp.StopWait(false) - if err != nil { - return nil, retApiWarnings, err + wg.Wait() + if len(errs) != 0 { + return nil, retApiWarnings, utils.NewErrorListOrNil(errs) } return ret, retApiWarnings, nil diff --git a/pkg/kluctl_project/targets.go b/pkg/kluctl_project/targets.go index acd861f13..795424ad4 100644 --- a/pkg/kluctl_project/targets.go +++ b/pkg/kluctl_project/targets.go @@ -221,11 +221,9 @@ func (c *LoadedKluctlProject) matchRef(s string, pattern string) (bool, string, } func (c *LoadedKluctlProject) cloneDynamicTargets(dynamicTargets []*dynamicTargetInfo) error { - wp := utils.NewDebuggerAwareWorkerPool(8) - defer wp.StopWait(false) - uniqueClones := make(map[string]interface{}) var mutex sync.Mutex + var wg sync.WaitGroup for _, targetInfo_ := range dynamicTargets { targetInfo := targetInfo_ @@ -241,7 +239,9 @@ func (c *LoadedKluctlProject) cloneDynamicTargets(dynamicTargets []*dynamicTarge uniqueClones[targetInfo.dir] = nil mutex.Unlock() - wp.Submit(func() error { + wg.Add(1) + go func() { + defer wg.Done() gitProject := *targetInfo.gitProject gitProject.Ref = *targetInfo.ref @@ -253,13 +253,9 @@ func (c *LoadedKluctlProject) cloneDynamicTargets(dynamicTargets []*dynamicTarge } else { uniqueClones[targetInfo.dir] = &gi } - return nil - }) - } - err := wp.StopWait(false) - if err != nil { - return err + }() } + wg.Wait() return nil } diff --git a/pkg/utils/workerpool.go b/pkg/utils/workerpool.go deleted file mode 100644 index fe71e32b9..000000000 --- a/pkg/utils/workerpool.go +++ /dev/null @@ -1,84 +0,0 @@ -package utils - -import ( - "github.com/gammazero/workerpool" - "os" - "strconv" - "sync" -) - -type WorkerPoolWithErrors struct { - maxWorkers int - pool *workerpool.WorkerPool - errors []error - results []interface{} - mutex sync.Mutex -} - -func NewWorkerPoolWithErrors(maxWorkers int) *WorkerPoolWithErrors { - return &WorkerPoolWithErrors{ - maxWorkers: maxWorkers, - pool: workerpool.New(maxWorkers), - } -} - -func NewDebuggerAwareWorkerPool(maxWorkers int) *WorkerPoolWithErrors { - if IsLaunchedByDebugger() { - ignoreDebuggerStr, _ := os.LookupEnv("KLUCTL_IGNORE_DEBUGGER") - ignoreDebugger, _ := strconv.ParseBool(ignoreDebuggerStr) - if !ignoreDebugger { - maxWorkers = 1 - } - } - return NewWorkerPoolWithErrors(maxWorkers) -} - -func (wp *WorkerPoolWithErrors) Submit(cb func() error) { - wp.SubmitWithResult(func() (interface{}, error) { - err := cb() - return nil, err - }) -} - -func (wp *WorkerPoolWithErrors) SubmitWithResult(cb func() (interface{}, error)) { - wp.pool.Submit(func() { - result, err := cb() - wp.mutex.Lock() - defer wp.mutex.Unlock() - if err != nil { - wp.errors = append(wp.errors, err) - } else if result != nil { - wp.results = append(wp.results, result) - } - }) -} - -func (wp *WorkerPoolWithErrors) StopWait(restart bool) error { - if wp.pool == nil { - return nil - } - wp.pool.StopWait() - if restart { - wp.pool = workerpool.New(wp.maxWorkers) - } else { - wp.pool = nil - } - - wp.mutex.Lock() - defer wp.mutex.Unlock() - - if len(wp.errors) == 0 { - return nil - } - err := NewErrorListOrNil(wp.errors) - wp.errors = nil - return err -} - -func (wp *WorkerPoolWithErrors) Errors() []error { - return wp.errors -} - -func (wp *WorkerPoolWithErrors) Results() []interface{} { - return wp.results -} From a32e7f4e8d584df6f1e6679bed6d93428d6feec6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 7 Jun 2022 10:00:31 +0200 Subject: [PATCH 0915/2916] build: Run go get -u ./... --- go.mod | 94 +++++++++++++++-------------- go.sum | 183 ++++++++++++++++++++++++++++++--------------------------- 2 files changed, 144 insertions(+), 133 deletions(-) diff --git a/go.mod b/go.mod index 3c936f079..b4e6c807c 100644 --- a/go.mod +++ b/go.mod @@ -5,13 +5,12 @@ go 1.18 require ( github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e github.com/Masterminds/semver/v3 v3.1.1 - github.com/aws/aws-sdk-go v1.44.19 - github.com/bitnami-labs/sealed-secrets v0.17.5 + github.com/aws/aws-sdk-go v1.44.28 + github.com/bitnami-labs/sealed-secrets v0.18.0 github.com/cyphar/filepath-securejoin v0.2.3 github.com/docker/distribution v2.8.1+incompatible github.com/evanphx/json-patch v5.6.0+incompatible github.com/fluxcd/pkg/kustomize v0.5.1 - github.com/gammazero/workerpool v1.1.2 github.com/go-git/go-git/v5 v5.4.2 github.com/go-playground/validator/v10 v10.11.0 github.com/gobwas/glob v0.2.3 @@ -20,34 +19,35 @@ require ( github.com/google/go-containerregistry v0.9.0 github.com/hashicorp/vault/api v1.6.0 github.com/hexops/gotextdiff v1.0.3 + github.com/imdario/mergo v0.3.13 github.com/jinzhu/copier v0.3.5 github.com/kevinburke/ssh_config v1.2.0 - github.com/klauspost/compress v1.15.4 + github.com/klauspost/compress v1.15.6 github.com/mattn/go-isatty v0.0.14 github.com/mitchellh/go-ps v1.0.0 - github.com/ohler55/ojg v1.14.0 + github.com/ohler55/ojg v1.14.2 github.com/pkg/errors v0.9.1 github.com/r3labs/diff/v2 v2.15.1 github.com/rogpeppe/go-internal v1.8.1 github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v1.4.0 github.com/spf13/pflag v1.0.5 - github.com/spf13/viper v1.11.0 - github.com/stretchr/testify v1.7.1 - github.com/vbauerster/mpb/v7 v7.4.1 + github.com/spf13/viper v1.12.0 + github.com/stretchr/testify v1.7.2 + github.com/vbauerster/mpb/v7 v7.4.2 github.com/whilp/git-urls v1.0.0 github.com/xanzy/ssh-agent v0.3.1 - golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898 - golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 + golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e + golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a - golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 + golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 golang.org/x/text v0.3.7 - gopkg.in/yaml.v3 v3.0.0 + gopkg.in/yaml.v3 v3.0.1 helm.sh/helm/v3 v3.9.0 - k8s.io/api v0.24.1-rc.0 - k8s.io/apiextensions-apiserver v0.24.0 - k8s.io/apimachinery v0.24.1-rc.0 - k8s.io/client-go v0.24.1-rc.0 + k8s.io/api v0.24.1 + k8s.io/apiextensions-apiserver v0.24.1 + k8s.io/apimachinery v0.24.1 + k8s.io/client-go v0.24.1 k8s.io/klog/v2 v2.60.1 sigs.k8s.io/kind v0.14.0 sigs.k8s.io/kustomize/api v0.11.5 @@ -75,28 +75,27 @@ require ( github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect github.com/acomagu/bufpipe v1.0.3 // indirect github.com/alessio/shellescape v1.4.1 // indirect - github.com/armon/go-metrics v0.3.10 // indirect + github.com/armon/go-metrics v0.4.0 // indirect github.com/armon/go-radix v1.0.0 // indirect github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/cenkalti/backoff/v3 v3.0.0 // indirect + github.com/cenkalti/backoff/v3 v3.2.2 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 // indirect - github.com/containerd/containerd v1.6.4 // indirect + github.com/containerd/containerd v1.6.6 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/docker/cli v20.10.16+incompatible // indirect - github.com/docker/docker v20.10.16+incompatible // indirect + github.com/docker/cli v20.10.17+incompatible // indirect + github.com/docker/docker v20.10.17+incompatible // indirect github.com/docker/docker-credential-helpers v0.6.4 // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/go-units v0.4.0 // indirect - github.com/emicklei/go-restful v2.9.5+incompatible // indirect + github.com/emicklei/go-restful/v3 v3.8.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect github.com/fatih/color v1.13.0 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect - github.com/gammazero/deque v0.1.1 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-git/gcfg v1.5.0 // indirect github.com/go-git/go-billy/v5 v5.3.1 // indirect @@ -121,24 +120,23 @@ require ( github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-hclog v1.2.0 // indirect + github.com/hashicorp/go-hclog v1.2.1 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-plugin v1.4.3 // indirect - github.com/hashicorp/go-retryablehttp v0.6.6 // indirect + github.com/hashicorp/go-plugin v1.4.4 // indirect + github.com/hashicorp/go-retryablehttp v0.7.1 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect - github.com/hashicorp/go-secure-stdlib/mlock v0.1.1 // indirect - github.com/hashicorp/go-secure-stdlib/parseutil v0.1.5 // indirect + github.com/hashicorp/go-secure-stdlib/mlock v0.1.2 // indirect + github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 // indirect github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect github.com/hashicorp/go-sockaddr v1.0.2 // indirect - github.com/hashicorp/go-uuid v1.0.2 // indirect - github.com/hashicorp/go-version v1.2.1 // indirect + github.com/hashicorp/go-uuid v1.0.3 // indirect + github.com/hashicorp/go-version v1.5.0 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/vault/sdk v0.5.0 // indirect - github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect + github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 // indirect github.com/huandu/xstrings v1.3.2 // indirect - github.com/imdario/mergo v0.3.12 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect @@ -157,7 +155,7 @@ require ( github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect - github.com/mitchellh/go-testing-interface v1.0.0 // indirect + github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect @@ -169,13 +167,13 @@ require ( github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/oklog/run v1.0.0 // indirect + github.com/oklog/run v1.1.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.0.1 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect - github.com/pierrec/lz4 v2.5.2+incompatible // indirect + github.com/pierrec/lz4 v2.6.1+incompatible // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.12.2 // indirect github.com/prometheus/client_model v0.2.0 // indirect @@ -190,7 +188,7 @@ require ( github.com/spf13/afero v1.8.2 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/subosito/gotenv v1.2.0 // indirect + github.com/subosito/gotenv v1.4.0 // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect @@ -198,26 +196,26 @@ require ( github.com/xlab/treeprint v1.1.0 // indirect go.starlark.net v0.0.0-20220328144851-d1966c6b9fcd // indirect go.uber.org/atomic v1.9.0 // indirect - golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 // indirect - golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect + golang.org/x/net v0.0.0-20220607020251-c690dde0001d // indirect + golang.org/x/oauth2 v0.0.0-20220524215830-622c5d57e401 // indirect golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd // indirect - google.golang.org/grpc v1.46.2 // indirect + google.golang.org/genproto v0.0.0-20220602131408-e326c6e8e9c8 // indirect + google.golang.org/grpc v1.47.0 // indirect google.golang.org/protobuf v1.28.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/ini.v1 v1.66.4 // indirect - gopkg.in/square/go-jose.v2 v2.5.1 // indirect + gopkg.in/ini.v1 v1.66.6 // indirect + gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/apiserver v0.24.0 // indirect - k8s.io/cli-runtime v0.24.0 // indirect - k8s.io/component-base v0.24.0 // indirect - k8s.io/kube-openapi v0.0.0-20220413171646-5e7f5fdc6da6 // indirect - k8s.io/kubectl v0.24.0 // indirect + k8s.io/apiserver v0.24.1 // indirect + k8s.io/cli-runtime v0.24.1 // indirect + k8s.io/component-base v0.24.1 // indirect + k8s.io/kube-openapi v0.0.0-20220603121420-31174f50af60 // indirect + k8s.io/kubectl v0.24.1 // indirect k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect oras.land/oras-go v1.1.1 // indirect - sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect + sigs.k8s.io/json v0.0.0-20220525155127-227cbc7cc124 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index c0ed01965..739e736a0 100644 --- a/go.sum +++ b/go.sum @@ -116,7 +116,7 @@ github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugX github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/Microsoft/hcsshim v0.9.2 h1:wB06W5aYFfUB3IvootYAY2WnOmIdgPGfqSI6tufQNnY= +github.com/Microsoft/hcsshim v0.9.3 h1:k371PzBuRrz2b+ebGuI2nVgVhgsVX60jMfSw80NECxo= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= @@ -154,8 +154,8 @@ github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hC github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.3.9/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= -github.com/armon/go-metrics v0.3.10 h1:FR+drcQStOe+32sYyJYyZ7FIdgoGGBnwLl+flodp8Uo= -github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-metrics v0.4.0 h1:yCQqn7dwca4ITXb+CbubHmedzaQYHhNhrEXLYUeEe8Q= +github.com/armon/go-metrics v0.4.0/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= @@ -169,8 +169,8 @@ github.com/ashanbrown/makezero v0.0.0-20210520155254-b6261585ddde/go.mod h1:oG9D github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.36.30/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= -github.com/aws/aws-sdk-go v1.44.19 h1:dhI6p4l6kisnA7gBAM8sP5YIk0bZ9HNAj7yrK7kcfdU= -github.com/aws/aws-sdk-go v1.44.19/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.28 h1:h/OAqEqY18wq//v6h4GNPMmCkxuzSDrWuGyrvSiRqf4= +github.com/aws/aws-sdk-go v1.44.28/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -178,8 +178,8 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bitnami-labs/sealed-secrets v0.17.5 h1:v5ENZRSrgog3GnFr8fWfVtrUTPlZlNlbsjaro9mn6YY= -github.com/bitnami-labs/sealed-secrets v0.17.5/go.mod h1:ZgGUqKixr/SRpsG8LVXnuneyZG7DOX/+eCRvXi8SVjo= +github.com/bitnami-labs/sealed-secrets v0.18.0 h1:7LdfPRMyx9nGQW9204JM1F+F+s6xmaSBl8oNujlrCuc= +github.com/bitnami-labs/sealed-secrets v0.18.0/go.mod h1:uV8CUHJQVcDOY9cZ1FdC1rtybC4ath+VGH+/dUQvBu0= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/bkielbasa/cyclop v1.2.0/go.mod h1:qOI0yy6A7dYC4Zgsa72Ppm9kONl0RoIlPbzot9mhmeI= @@ -193,8 +193,9 @@ github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd h1:rFt+Y/IK1aEZ github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembjv71DPz3uX/V/6MMlSyD9JBQ6kQ= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o= github.com/butuzov/ireturn v0.1.1/go.mod h1:Wh6Zl3IMtTpaIKbmwzqi6olnM9ptYQxxVacMsOEFPoc= -github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c= github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= +github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= +github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= @@ -226,8 +227,8 @@ github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= github.com/containerd/cgroups v1.0.3 h1:ADZftAkglvCiD44c77s5YmMqaP2pzVCFZvBmAlBdAP4= -github.com/containerd/containerd v1.6.4 h1:SEDZBp10mhCp+hkO3Njz/YhGrI7ah3edNcUlRdUPOgg= -github.com/containerd/containerd v1.6.4/go.mod h1:oWOqbuJUZmOVafhA0lj2NAXbiO1u7F0K5l1bUgdyo94= +github.com/containerd/containerd v1.6.6 h1:xJNPhbrmz8xAMDNoVjHy9YHtWwEQNS+CDkcIRh7t8Y0= +github.com/containerd/containerd v1.6.6/go.mod h1:ZoP1geJldzCVY3Tonoz7b1IXk8rIX0Nltt5QE4OMNk0= github.com/containerd/stargz-snapshotter/estargz v0.11.4 h1:LjrYUZpyOhiSaU7hHrdR82/RBoxfGWSaC0VeSSMXqnk= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= @@ -264,12 +265,12 @@ github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27N github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/distribution/distribution/v3 v3.0.0-20211118083504-a29a3c99a684 h1:DBZ2sN7CK6dgvHVpQsQj4sRMCbWTmd17l+5SUCjnQSY= -github.com/docker/cli v20.10.16+incompatible h1:aLQ8XowgKpR3/IysPj8qZQJBVQ+Qws61icFuZl6iKYs= -github.com/docker/cli v20.10.16+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v20.10.17+incompatible h1:eO2KS7ZFeov5UJeaDmIs1NFEDRf32PaqRpvoEkKBy5M= +github.com/docker/cli v20.10.17+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v20.10.16+incompatible h1:2Db6ZR/+FUR3hqPMwnogOPHFn405crbpxvWzKovETOQ= -github.com/docker/docker v20.10.16+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v20.10.17+incompatible h1:JYCuMrWaVNophQTOrMMoSwudOVEfcegoZZrleKc1xwE= +github.com/docker/docker v20.10.17+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.6.4 h1:axCks+yV+2MR3/kZhAmy07yC56WZ2Pwu/fKWtKuZB0o= github.com/docker/docker-credential-helpers v0.6.4/go.mod h1:ofX3UI0Gz1TteYBjtgs07O36Pyasyp66D2uKT7H8W1c= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= @@ -286,8 +287,10 @@ github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25Kn github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful/v3 v3.7.5-0.20220308211933-7c971ca4d0fd/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.8.0 h1:eCZ8ulSerjdAiaNpF7GxXIE7ZCMo1moN1qX+S609eVw= +github.com/emicklei/go-restful/v3 v3.8.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= @@ -342,11 +345,6 @@ github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmV github.com/fullstorydev/grpcurl v1.6.0/go.mod h1:ZQ+ayqbKMJNhzLmbpCiurTVlaK2M/3nqZCxaQ2Ze/sM= github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= github.com/fzipp/gocyclo v0.3.1/go.mod h1:DJHO6AUmbdqj2ET4Z9iArSuwWgYDRryYt2wASxc7x3E= -github.com/gammazero/deque v0.1.0/go.mod h1:KQw7vFau1hHuM8xmI9RbgKFbAsQFWmBpqQ2KenFLk6M= -github.com/gammazero/deque v0.1.1 h1:xRVkDuSvDmFuMGf3IquHuRc2jlL0+v/WpFCWaauzwbE= -github.com/gammazero/deque v0.1.1/go.mod h1:KQw7vFau1hHuM8xmI9RbgKFbAsQFWmBpqQ2KenFLk6M= -github.com/gammazero/workerpool v1.1.2 h1:vuioDQbgrz4HoaCi2q1HLlOXdpbap5AET7xu5/qj87g= -github.com/gammazero/workerpool v1.1.2/go.mod h1:UelbXcO0zCIGFcufcirHhq2/xtLXJdQ29qZNlXG9OjQ= github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -630,8 +628,8 @@ github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrj github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= -github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.2.1 h1:YQsLlGDJgwhXFpucSPyVbCBviQtjlHv3jLTlp8YmtEw= +github.com/hashicorp/go-hclog v1.2.1/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= @@ -641,20 +639,24 @@ github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHh github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v6UZM= github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= +github.com/hashicorp/go-plugin v1.4.4 h1:NVdrSdFRt3SkZtNckJ6tog7gbpRrcbOjQi/rgF7JYWQ= +github.com/hashicorp/go-plugin v1.4.4/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= -github.com/hashicorp/go-retryablehttp v0.6.6 h1:HJunrbHTDDbBb/ay4kxa1n+dLmttUlnP3V9oNE4hmsM= github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ= +github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-secure-stdlib/base62 v0.1.1/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw= -github.com/hashicorp/go-secure-stdlib/mlock v0.1.1 h1:cCRo8gK7oq6A2L6LICkUZ+/a5rLiRXFMf1Qd4xSwxTc= github.com/hashicorp/go-secure-stdlib/mlock v0.1.1/go.mod h1:zq93CJChV6L9QTfGKtfBxKqD7BqqXx5O04A/ns2p5+I= +github.com/hashicorp/go-secure-stdlib/mlock v0.1.2 h1:p4AKXPPS24tO8Wc8i1gLvSKdmkiSY5xuju57czJ/IJQ= +github.com/hashicorp/go-secure-stdlib/mlock v0.1.2/go.mod h1:zq93CJChV6L9QTfGKtfBxKqD7BqqXx5O04A/ns2p5+I= github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= -github.com/hashicorp/go-secure-stdlib/parseutil v0.1.5 h1:MBgwAFPUbfuI0+tmDU/aeM1MARvdbqWmiieXIalKqDE= github.com/hashicorp/go-secure-stdlib/parseutil v0.1.5/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 h1:om4Al8Oy7kCm/B86rLCLah4Dt5Aa0Fr5rYBG60OzwHQ= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= github.com/hashicorp/go-secure-stdlib/password v0.1.1/go.mod h1:9hH302QllNwu1o2TGYtSk8I8kTAN0ca1EHpwhm5Mmzo= github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= @@ -666,11 +668,13 @@ github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjG github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI= github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.5.0 h1:O293SZ2Eg+AAYijkVK3jR786Am1bhDEh2GHT0tIVE5E= +github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -689,8 +693,9 @@ github.com/hashicorp/vault/api v1.6.0 h1:B8UUYod1y1OoiGHq9GtpiqSnGOUEWHaA26AY8RQ github.com/hashicorp/vault/api v1.6.0/go.mod h1:h1K70EO2DgnBaTz5IsL6D5ERsNt5Pce93ueVS2+t0Xc= github.com/hashicorp/vault/sdk v0.5.0 h1:EED7p0OCU3OY5SAqJwSANofY1YKMytm+jDHDQ2EzGVQ= github.com/hashicorp/vault/sdk v0.5.0/go.mod h1:UJZHlfwj7qUJG8g22CuxUgkdJouFrBNvBHCyx8XAPdo= -github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 h1:xixZ2bWeofWV68J+x6AzmKuVM/JWCQwkWm6GW/MUR6I= +github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= @@ -705,8 +710,9 @@ github.com/imdario/mergo v0.3.4/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJ github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= +github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= @@ -763,8 +769,8 @@ github.com/kisielk/errcheck v1.6.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.15.4 h1:1kn4/7MepF/CHmYub99/nNX8az0IJjfSOU/jbnTVfqQ= -github.com/klauspost/compress v1.15.4/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= +github.com/klauspost/compress v1.15.6 h1:6D9PcO8QWu0JyaQ2zUMmu16T1T+zjjEpP91guRsvDfY= +github.com/klauspost/compress v1.15.6/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -881,8 +887,9 @@ github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= +github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= @@ -937,10 +944,11 @@ github.com/nishanths/predeclared v0.2.1/go.mod h1:HvkGJcA3naj4lOwnFXFDkFxVtSqQMB github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/ohler55/ojg v1.14.0 h1:DyHomsCwofNswmKj7BLMdx51xnKbXxgIo1rVWCaBcNk= -github.com/ohler55/ojg v1.14.0/go.mod h1:3+GH+0PggMKocQtbZCrFifal3yRpHiBT4QUkxFJI6e8= -github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= +github.com/ohler55/ojg v1.14.2 h1:EHdrwmDrOuTGpj1W2LrT/yKeUOkLMIk1cTYcm42Sjj0= +github.com/ohler55/ojg v1.14.2/go.mod h1:/Y5dGWkekv9ocnUixuETqiL58f+5pAsUfg5P8e7Pa2o= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= +github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= @@ -984,8 +992,9 @@ github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+v github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw= github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc= -github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI= github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= +github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -1131,8 +1140,8 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/spf13/viper v1.9.0/go.mod h1:+i6ajR7OX2XaiBkrcZJFK21htRk7eDeLg7+O6bhUPP4= -github.com/spf13/viper v1.11.0 h1:7OX/1FS6n7jHD1zGrZTM7WtY13ZELRyosK4k93oPr44= -github.com/spf13/viper v1.11.0/go.mod h1:djo0X/bA5+tYVoCn+C7cAYJGcVn/qYLFTG8gdUsX7Zk= +github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= +github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -1147,10 +1156,12 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/subosito/gotenv v1.4.0 h1:yAzM1+SmVcz5R4tXGsNMu1jUl2aOJXoiWUCEwwnGrvs= +github.com/subosito/gotenv v1.4.0/go.mod h1:mZd6rFysKEcUhUHXJk0C/08wAgyDBFuwEYL7vWWGaGo= github.com/sylvia7788/contextcheck v1.0.4/go.mod h1:vuPKJMQ7MQ91ZTqfdyreNKwZjyUg6KO+IebVyQDedZQ= github.com/tdakkota/asciicheck v0.0.0-20200416200610-e657995f937b/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM= github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= @@ -1178,8 +1189,8 @@ github.com/valyala/fasthttp v1.30.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD github.com/valyala/quicktemplate v1.7.0/go.mod h1:sqKJnoaOF88V07vkO+9FL8fb9uZg/VPSJnLYn+LmLk8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/vbatts/tar-split v0.11.2 h1:Via6XqJr0hceW4wff3QRzD5gAk/tatMw/4ZA7cTlIME= -github.com/vbauerster/mpb/v7 v7.4.1 h1:NhLMWQ3gNg2KJR8oeA9lO8Xvq+eNPmixDmB6JEQOUdA= -github.com/vbauerster/mpb/v7 v7.4.1/go.mod h1:Ygg2mV9Vj9sQBWqsK2m2pidcf9H3s6bNKtqd3/M4gBo= +github.com/vbauerster/mpb/v7 v7.4.2 h1:n917F4d8EWdUKc9c81wFkksyG6P6Mg7IETfKCE1Xqng= +github.com/vbauerster/mpb/v7 v7.4.2/go.mod h1:UmOiIUI8aPqWXIps0ciik3RKMdzx7+ooQpq+fBcXwBA= github.com/viki-org/dnscache v0.0.0-20130720023526-c70c1f23c5d8/go.mod h1:dniwbG03GafCjFohMDmz6Zc6oCuiqgH6tGNyXTkHzXE= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= @@ -1298,8 +1309,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898 h1:SLP7Q4Di66FONjDJbCYrCRrh97focO6sLogHO7/g8F0= -golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= +golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1400,8 +1411,8 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 h1:NWy5+hlRbC7HK+PmcXVUmW1IMyFce7to56IUvhUFm7Y= -golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220607020251-c690dde0001d h1:4SFsTMi4UahlKoloni7L4eYzhFRifURQLw+yv0QDCx8= +golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1421,8 +1432,9 @@ golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 h1:OSnWWcOd/CtWQC2cYSBgbTSJv3ciqd8r54ySIW2y3RE= golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220524215830-622c5d57e401 h1:zwrSfklXn0gxyLRX/aR+q6cgHbV/ItVyzbPlbA+dkAw= +golang.org/x/oauth2 v0.0.0-20220524215830-622c5d57e401/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1435,8 +1447,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 h1:w8s32wxx3sY+OjLlv9qltkLU5yvJzxjjgiHWLjdIcw4= -golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8= +golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1536,13 +1548,14 @@ golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 h1:EH1Deb8WZJ0xc0WK//leUHXcX9aLE5SymusoTmMZye8= -golang.org/x/term v0.0.0-20220411215600-e5f449aeb171/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM= +golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1803,8 +1816,8 @@ google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd h1:e0TwkXOdbnH/1x5rc5MZ/VYyiZ4v+RdVfrGMqEwT68I= -google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220602131408-e326c6e8e9c8 h1:qRu95HZ148xXw+XeZ3dvqe85PxH4X8+jIo0iRPKcEnM= +google.golang.org/genproto v0.0.0-20220602131408-e326c6e8e9c8/go.mod h1:yKyY4AMRwFiC8yMMNaMi+RkCnjZJt9LoWuvhXjMs+To= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -1838,9 +1851,9 @@ google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9K google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= -google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.46.2 h1:u+MLGgVf7vRdjEYZ8wDFhAVNmhkbJ5hmrA1LMWK1CAQ= google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8= +google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -1874,13 +1887,14 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4= -gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.66.6 h1:LATuAqN/shcYAOkv3wl2L4rkaKqkcgTBQjOyYDvcPKI= +gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= +gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= @@ -1899,8 +1913,9 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= @@ -1914,25 +1929,22 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.2.1/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY= -k8s.io/api v0.24.0/go.mod h1:5Jl90IUrJHUJYEMANRURMiVvJ0g7Ax7r3R1bqO8zx8I= -k8s.io/api v0.24.1-rc.0 h1:qCZcUZ/YVXVVacShyNgyEHSUGxrWwIlDGAU1XpyMhYs= -k8s.io/api v0.24.1-rc.0/go.mod h1:IXhQwEOq8wx1Hmtm/YCNjyQ3XaeIUH0NP5Zsc0aF5Js= -k8s.io/apiextensions-apiserver v0.24.0 h1:JfgFqbA8gKJ/uDT++feAqk9jBIwNnL9YGdQvaI9DLtY= -k8s.io/apiextensions-apiserver v0.24.0/go.mod h1:iuVe4aEpe6827lvO6yWQVxiPSpPoSKVjkq+MIdg84cM= -k8s.io/apimachinery v0.24.0/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= -k8s.io/apimachinery v0.24.1-rc.0 h1:mjcDse2fEbUbiSZYU83NxdLUXlCSprcMlQVgNWn6tGk= -k8s.io/apimachinery v0.24.1-rc.0/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= -k8s.io/apiserver v0.24.0 h1:GR7kGsjOMfilRvlG3Stxv/3uz/ryvJ/aZXc5pqdsNV0= -k8s.io/apiserver v0.24.0/go.mod h1:WFx2yiOMawnogNToVvUYT9nn1jaIkMKj41ZYCVycsBA= -k8s.io/cli-runtime v0.24.0 h1:ot3Qf49T852uEyNApABO1UHHpFIckKK/NqpheZYN2gM= -k8s.io/cli-runtime v0.24.0/go.mod h1:9XxoZDsEkRFUThnwqNviqzljtT/LdHtNWvcNFrAXl0A= -k8s.io/client-go v0.24.0/go.mod h1:VFPQET+cAFpYxh6Bq6f4xyMY80G6jKKktU6G0m00VDw= -k8s.io/client-go v0.24.1-rc.0 h1:FoTsvkV8oz2FTORlpAR6TgRQMcdzGVIGQkjc7VrwZ40= -k8s.io/client-go v0.24.1-rc.0/go.mod h1:6VdmspONsQ+gV2AsYugsRaVSYOEUm3Trd16aQJgJahU= -k8s.io/code-generator v0.24.0/go.mod h1:dpVhs00hTuTdTY6jvVxvTFCk6gSMrtfRydbhZwHI15w= -k8s.io/component-base v0.24.0 h1:h5jieHZQoHrY/lHG+HyrSbJeyfuitheBvqvKwKHVC0g= -k8s.io/component-base v0.24.0/go.mod h1:Dgazgon0i7KYUsS8krG8muGiMVtUZxG037l1MKyXgrA= -k8s.io/component-helpers v0.24.0/go.mod h1:Q2SlLm4h6g6lPTC9GMMfzdywfLSvJT2f1hOnnjaWD8c= +k8s.io/api v0.24.1 h1:BjCMRDcyEYz03joa3K1+rbshwh1Ay6oB53+iUx2H8UY= +k8s.io/api v0.24.1/go.mod h1:JhoOvNiLXKTPQ60zh2g0ewpA+bnEYf5q44Flhquh4vQ= +k8s.io/apiextensions-apiserver v0.24.1 h1:5yBh9+ueTq/kfnHQZa0MAo6uNcPrtxPMpNQgorBaKS0= +k8s.io/apiextensions-apiserver v0.24.1/go.mod h1:A6MHfaLDGfjOc/We2nM7uewD5Oa/FnEbZ6cD7g2ca4Q= +k8s.io/apimachinery v0.24.1 h1:ShD4aDxTQKN5zNf8K1RQ2u98ELLdIW7jEnlO9uAMX/I= +k8s.io/apimachinery v0.24.1/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= +k8s.io/apiserver v0.24.1 h1:LAA5UpPOeaREEtFAQRUQOI3eE5So/j5J3zeQJjeLdz4= +k8s.io/apiserver v0.24.1/go.mod h1:dQWNMx15S8NqJMp0gpYfssyvhYnkilc1LpExd/dkLh0= +k8s.io/cli-runtime v0.24.1 h1:IW6L8dRBq+pPTzvXcB+m/hOabzbqXy57Bqo4XxmW7DY= +k8s.io/cli-runtime v0.24.1/go.mod h1:14aVvCTqkA7dNXY51N/6hRY3GUjchyWDOwW84qmR3bs= +k8s.io/client-go v0.24.1 h1:w1hNdI9PFrzu3OlovVeTnf4oHDt+FJLd9Ndluvnb42E= +k8s.io/client-go v0.24.1/go.mod h1:f1kIDqcEYmwXS/vTbbhopMUbhKp2JhOeVTfxgaCIlF8= +k8s.io/code-generator v0.24.1/go.mod h1:dpVhs00hTuTdTY6jvVxvTFCk6gSMrtfRydbhZwHI15w= +k8s.io/component-base v0.24.1 h1:APv6W/YmfOWZfo+XJ1mZwep/f7g7Tpwvdbo9CQLDuts= +k8s.io/component-base v0.24.1/go.mod h1:DW5vQGYVCog8WYpNob3PMmmsY8A3L9QZNg4j/dV3s38= +k8s.io/component-helpers v0.24.1/go.mod h1:q5Z1pWV/QfX9ThuNeywxasiwkLw9KsR4Q9TAOdb/Y3s= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/gengo v0.0.0-20211129171323-c02415ce4185/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= @@ -1943,11 +1955,11 @@ k8s.io/klog/v2 v2.60.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42/go.mod h1:Z/45zLw8lUo4wdiUkI+v/ImEGAvu3WatcZl3lPMR4Rk= k8s.io/kube-openapi v0.0.0-20220401212409-b28bf2818661/go.mod h1:daOouuuwd9JXpv1L7Y34iV3yf6nxzipkKMWWlqlvK9M= -k8s.io/kube-openapi v0.0.0-20220413171646-5e7f5fdc6da6 h1:nBQrWPlrNIiw0BsX6a6MKr1itkm0ZS0Nl97kNLitFfI= -k8s.io/kube-openapi v0.0.0-20220413171646-5e7f5fdc6da6/go.mod h1:daOouuuwd9JXpv1L7Y34iV3yf6nxzipkKMWWlqlvK9M= -k8s.io/kubectl v0.24.0 h1:nA+WtMLVdXUs4wLogGd1mPTAesnLdBpCVgCmz3I7dXo= -k8s.io/kubectl v0.24.0/go.mod h1:pdXkmCyHiRTqjYfyUJiXtbVNURhv0/Q1TyRhy2d5ic0= -k8s.io/metrics v0.24.0/go.mod h1:jrLlFGdKl3X+szubOXPG0Lf2aVxuV3QJcbsgVRAM6fI= +k8s.io/kube-openapi v0.0.0-20220603121420-31174f50af60 h1:cE/M8rmDQgibspuSm+X1iW16ByTImtEaapgaHoVSLX4= +k8s.io/kube-openapi v0.0.0-20220603121420-31174f50af60/go.mod h1:ouUzE1U2mEv//HRoBwYLFE5pdqjIebvtX361vtEIlBI= +k8s.io/kubectl v0.24.1 h1:gxcjHrnwntV1c+G/BHWVv4Mtk8CQJ0WTraElLBG+ddk= +k8s.io/kubectl v0.24.1/go.mod h1:NzFqQ50B004fHYWOfhHTrAm4TY6oGF5FAAL13LEaeUI= +k8s.io/metrics v0.24.1/go.mod h1:vMs5xpcOyY9D+/XVwlaw8oUHYCo6JTGBCZfyXOOkAhE= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 h1:HNSDgDCrr/6Ly3WEGKZftiE7IY19Vz2GdbOCyI4qqhc= k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= @@ -1961,8 +1973,9 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.30/go.mod h1:fEO7lRTdivWO2qYVCVG7dEADOMo/MLDCVr8So2g88Uw= -sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 h1:kDi4JBNAsJWfz1aEXhO8Jg87JJaPNLh5tIzYHgStQ9Y= sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY= +sigs.k8s.io/json v0.0.0-20220525155127-227cbc7cc124 h1:2sgAQQcY0dEW2SsQwTXhQV4vO6+rSslYx8K3XmM5hqQ= +sigs.k8s.io/json v0.0.0-20220525155127-227cbc7cc124/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY= sigs.k8s.io/kind v0.14.0 h1:cNmI3jGBvp7UegEGbC5we8plDtCUmaNRL+bod7JoSCE= sigs.k8s.io/kind v0.14.0/go.mod h1:UrFRPHG+2a5j0Q7qiR4gtJ4rEyn8TuMQwuOPf+m4oHg= sigs.k8s.io/kustomize/api v0.11.4/go.mod h1:k+8RsqYbgpkIrJ4p9jcdPqe8DprLxFUUO0yNOq8C+xI= From 7d386057f2571ff337efc7f4a3d307abc83374c0 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 7 Jun 2022 11:17:58 +0200 Subject: [PATCH 0916/2916] ci: Use ubuntu-20.04 in CI --- .github/workflows/tests.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e6a9124ca..e0aaa6f29 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,7 +7,7 @@ on: jobs: build: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - name: Checkout uses: actions/checkout@v2 @@ -78,7 +78,7 @@ jobs: docker-host: if: "!startsWith(github.ref, 'refs/tags/')" - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 needs: - build steps: @@ -137,13 +137,13 @@ jobs: strategy: matrix: include: - - os: ubuntu-18.04 + - os: ubuntu-20.04 binary-suffix: linux-amd64 - os: macos-10.15 binary-suffix: darwin-amd64 - os: windows-2019 binary-suffix: windows-amd64 - os: [ubuntu-18.04, macos-10.15, windows-2019] + os: [ubuntu-20.04, macos-10.15, windows-2019] fail-fast: false needs: - build From 5452ca251b98217bc74e2c675ff89def081863f6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 7 Jun 2022 16:01:46 +0200 Subject: [PATCH 0917/2916] fix: Ignore ComponentStatus when listing all objects This removes the deprecate warning of ComponentStatus --- pkg/k8s/k8s_cluster.go | 6 ------ pkg/k8s/resources.go | 7 +++++++ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index 32499284f..a810baa31 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -25,12 +25,6 @@ import ( "time" ) -var ( - deprecatedResources = map[schema.GroupKind]bool{ - {Group: "extensions", Kind: "Ingress"}: true, - } -) - type K8sCluster struct { ctx context.Context diff --git a/pkg/k8s/resources.go b/pkg/k8s/resources.go index e8a3d843e..a6a5aac81 100644 --- a/pkg/k8s/resources.go +++ b/pkg/k8s/resources.go @@ -17,6 +17,13 @@ import ( "sync" ) +var ( + deprecatedResources = map[schema.GroupKind]bool{ + {Group: "extensions", Kind: "Ingress"}: true, + {Group: "", Kind: "ComponentStatus"}: true, + } +) + type k8sResources struct { ctx context.Context discovery discovery.DiscoveryInterface From eb0a92ffab90656144338d52d477ae7df76e70d0 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 7 Jun 2022 16:44:40 +0200 Subject: [PATCH 0918/2916] fix: Use context name from cluster config --- pkg/kluctl_project/target_context.go | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index 2e4ac84bb..b5ed711fd 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -110,18 +110,14 @@ func (p *LoadedKluctlProject) buildVars(target *types.Target, clusterName *strin return nil, nil, "", err } + varsCtx := vars.NewVarsCtx(p.J2) + var contextName *string if clusterName == nil && target != nil { clusterName = target.Cluster contextName = target.Context } - clientConfig, restConfig, err := p.loadArgs.ClientConfigGetter(contextName) - if err != nil { - return doError(err) - } - - varsCtx := vars.NewVarsCtx(p.J2) if clusterName != nil { clusterConfig, err := p.LoadClusterConfig(*clusterName) if err != nil { @@ -131,7 +127,16 @@ func (p *LoadedKluctlProject) buildVars(target *types.Target, clusterName *strin if err != nil { return doError(err) } + if contextName == nil { + contextName = &clusterConfig.Cluster.Context + } } + + clientConfig, restConfig, err := p.loadArgs.ClientConfigGetter(contextName) + if err != nil { + return doError(err) + } + targetVars, err := uo.FromStruct(target) if err != nil { return doError(err) From 70157973a597cfa5117a2ba74627a6634d809713 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 8 Jun 2022 14:48:28 +0200 Subject: [PATCH 0919/2916] fix: Skip entries with no host when looking for KLUCTL_GIT_XXX variables --- pkg/git/auth/env_auth_provider.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/git/auth/env_auth_provider.go b/pkg/git/auth/env_auth_provider.go index f05516e00..9d4be5eef 100644 --- a/pkg/git/auth/env_auth_provider.go +++ b/pkg/git/auth/env_auth_provider.go @@ -21,6 +21,9 @@ func (a *GitEnvAuthProvider) BuildAuth(ctx context.Context, gitUrl git_url.GitUr Username: m["USERNAME"], Password: m["PASSWORD"], } + if e.Host == "" { + continue + } ssh_key_path, _ := m["SSH_KEY"] if ssh_key_path != "" { From 21d0fe5ed0f1c592c8dbb44a8f3fbf93f20cb254 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 8 Jun 2022 14:49:14 +0200 Subject: [PATCH 0920/2916] fix: More tracing in ListAuthProvider and GitEnvAuthProvider --- pkg/git/auth/env_auth_provider.go | 7 +++++-- pkg/git/auth/list_auth_provider.go | 11 +++++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/pkg/git/auth/env_auth_provider.go b/pkg/git/auth/env_auth_provider.go index 9d4be5eef..2c1483811 100644 --- a/pkg/git/auth/env_auth_provider.go +++ b/pkg/git/auth/env_auth_provider.go @@ -26,11 +26,14 @@ func (a *GitEnvAuthProvider) BuildAuth(ctx context.Context, gitUrl git_url.GitUr } ssh_key_path, _ := m["SSH_KEY"] + + status.Trace(ctx, "GitEnvAuthProvider: adding entry host=%s, pathPrefix=%s, username=%s, ssh_key=%s", e.Host, e.PathPrefix, e.Username, ssh_key_path) + if ssh_key_path != "" { ssh_key_path = utils.ExpandPath(ssh_key_path) b, err := ioutil.ReadFile(ssh_key_path) if err != nil { - status.Trace(ctx, "Failed to read key %s: %v", ssh_key_path, err) + status.Trace(ctx, "GitEnvAuthProvider: failed to read key %s: %v", ssh_key_path, err) } else { e.SshKey = b } @@ -40,7 +43,7 @@ func (a *GitEnvAuthProvider) BuildAuth(ctx context.Context, gitUrl git_url.GitUr ca_bundle_path = utils.ExpandPath(ca_bundle_path) b, err := ioutil.ReadFile(ca_bundle_path) if err != nil { - status.Trace(ctx, "Failed to read ca bundle %s: %v", ca_bundle_path, err) + status.Trace(ctx, "GitEnvAuthProvider: failed to read ca bundle %s: %v", ca_bundle_path, err) } else { e.CABundle = b } diff --git a/pkg/git/auth/list_auth_provider.go b/pkg/git/auth/list_auth_provider.go index 4faa19b70..e1ce56525 100644 --- a/pkg/git/auth/list_auth_provider.go +++ b/pkg/git/auth/list_auth_provider.go @@ -30,7 +30,11 @@ func (a *ListAuthProvider) AddEntry(e AuthEntry) { } func (a *ListAuthProvider) BuildAuth(ctx context.Context, gitUrl git_url.GitUrl) AuthMethodAndCA { + status.Trace(ctx, "ListAuthProvider: BuildAuth for %s", gitUrl.String()) + status.Trace(ctx, "ListAuthProvider: path=%s, username=%s, scheme=%s", gitUrl.Path, gitUrl.User.Username(), gitUrl.Scheme) for _, e := range a.entries { + status.Trace(ctx, "ListAuthProvider: try host=%s, pathPrefix=%s, username=%s", e.Host, e.PathPrefix, e.Username) + if e.Host != "*" && e.Host != gitUrl.Hostname() { continue } @@ -61,11 +65,13 @@ func (a *ListAuthProvider) BuildAuth(ctx context.Context, gitUrl git_url.GitUrl) if gitUrl.IsSsh() { if e.SshKey == nil { + status.Trace(ctx, "ListAuthProvider: empty ssh key is not accepted") continue } + status.Trace(ctx, "ListAuthProvider: using username+sshKey") a, err := ssh.NewPublicKeys(username, e.SshKey, "") if err != nil { - status.Trace(ctx, "Failed to parse private key: %v", err) + status.Trace(ctx, "ListAuthProvider: failed to parse private key: %v", err) } else { a.HostKeyCallback = buildVerifyHostCallback(ctx, e.KnownHosts) return AuthMethodAndCA{ @@ -74,9 +80,10 @@ func (a *ListAuthProvider) BuildAuth(ctx context.Context, gitUrl git_url.GitUrl) } } else { if e.Password == "" { - // empty password is not accepted + status.Trace(ctx, "ListAuthProvider: empty password is not accepted") continue } + status.Trace(ctx, "ListAuthProvider: using username+password") return AuthMethodAndCA{ AuthMethod: &http.BasicAuth{ Username: username, From 0a95b9ac42715ca1d5f63c6fdc82f1668c221cbe Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 8 Jun 2022 15:00:50 +0200 Subject: [PATCH 0921/2916] fix: Ignore leading / when checking for git path prefix --- pkg/git/auth/list_auth_provider.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/git/auth/list_auth_provider.go b/pkg/git/auth/list_auth_provider.go index e1ce56525..6ea7ba938 100644 --- a/pkg/git/auth/list_auth_provider.go +++ b/pkg/git/auth/list_auth_provider.go @@ -38,7 +38,11 @@ func (a *ListAuthProvider) BuildAuth(ctx context.Context, gitUrl git_url.GitUrl) if e.Host != "*" && e.Host != gitUrl.Hostname() { continue } - if !strings.HasPrefix(gitUrl.Path, e.PathPrefix) { + urlPath := gitUrl.Path + if strings.HasPrefix(urlPath, "/") { + urlPath = urlPath[1:] + } + if !strings.HasPrefix(urlPath, e.PathPrefix) { continue } if e.Username == "" { From 560adf2d466d934457424ec8c6bd8c5b59726815 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 10 Jun 2022 10:12:03 +0200 Subject: [PATCH 0922/2916] fix: Don't remove cloned dirs too early --- cmd/kluctl/commands/utils.go | 7 ++---- pkg/git/repoprovider/live.go | 35 ++++++++++++++++++++-------- pkg/git/repoprovider/repoprovider.go | 1 + 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 5ff04f003..4fdc5f1a9 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -76,9 +76,6 @@ func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, strictTemplates b } p, err := kluctl_project.LoadKluctlProject(ctx, loadArgs, tmpDir, j2) - if p != nil && p.RP != nil { - defer p.RP.Clear() - } if err != nil { return err } @@ -186,8 +183,8 @@ func withProjectTargetCommandContext(ctx context.Context, args projectTargetComm images: images, } - // we can assume that all git access is done at this point, so we can clear grc and thus unlock all repos - p.RP.Clear() + // we can assume that all git access is done at this point, so we can unlock all repos + p.RP.UnlockAll() return cb(cmdCtx) } diff --git a/pkg/git/repoprovider/live.go b/pkg/git/repoprovider/live.go index 9d877b6ae..bb0243f9a 100644 --- a/pkg/git/repoprovider/live.go +++ b/pkg/git/repoprovider/live.go @@ -20,7 +20,10 @@ type LiveRepoProvider struct { authProviders *auth.GitAuthProviders updateInterval time.Duration repos map[string]*entry - mutex sync.Mutex + reposMutex sync.Mutex + + cleanupDirs []string + cleeanupDirsMutex sync.Mutex } type entry struct { @@ -43,15 +46,11 @@ func NewLiveRepoProvider(ctx context.Context, authProviders *auth.GitAuthProvide } } -func (rp *LiveRepoProvider) Clear() { - rp.mutex.Lock() - defer rp.mutex.Unlock() +func (rp *LiveRepoProvider) UnlockAll() { + rp.reposMutex.Lock() + defer rp.reposMutex.Unlock() for _, e := range rp.repos { - for _, cd := range e.clonedDirs { - _ = os.RemoveAll(cd.dir) - } - if e.mr.IsLocked() { _ = e.mr.Unlock() } @@ -60,10 +59,22 @@ func (rp *LiveRepoProvider) Clear() { rp.repos = map[string]*entry{} } +func (rp *LiveRepoProvider) Clear() { + rp.UnlockAll() + + rp.cleeanupDirsMutex.Lock() + defer rp.cleeanupDirsMutex.Unlock() + + for _, p := range rp.cleanupDirs { + _ = os.RemoveAll(p) + } + rp.cleanupDirs = nil +} + func (rp *LiveRepoProvider) getEntry(url git_url.GitUrl, allowCreate bool, lockRepo bool, update bool) (*entry, error) { e, err := func() (*entry, error) { - rp.mutex.Lock() - defer rp.mutex.Unlock() + rp.reposMutex.Lock() + defer rp.reposMutex.Unlock() e, ok := rp.repos[url.NormalizedRepoKey()] if !ok { @@ -178,6 +189,10 @@ func (rp *LiveRepoProvider) GetClonedDir(url git_url.GitUrl, ref string) (string return "", git.CheckoutInfo{}, err } + rp.cleeanupDirsMutex.Lock() + rp.cleanupDirs = append(rp.cleanupDirs, p) + rp.cleeanupDirsMutex.Unlock() + err = e.mr.CloneProject(ref, p) if err != nil { return "", git.CheckoutInfo{}, err diff --git a/pkg/git/repoprovider/repoprovider.go b/pkg/git/repoprovider/repoprovider.go index 77495df96..a6427551a 100644 --- a/pkg/git/repoprovider/repoprovider.go +++ b/pkg/git/repoprovider/repoprovider.go @@ -14,5 +14,6 @@ type RepoInfo struct { type RepoProvider interface { GetRepoInfo(url git_url.GitUrl) (RepoInfo, error) GetClonedDir(url git_url.GitUrl, ref string) (string, git.CheckoutInfo, error) + UnlockAll() Clear() } From 11064b7bef932073118db036b27bc49063ea8cef Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 13 Jun 2022 16:47:47 +0200 Subject: [PATCH 0923/2916] feat: Allow to select secrets/configmaps vars sources via labels --- pkg/types/vars_source.go | 18 ++++++++++++--- pkg/vars/vars_loader.go | 38 ++++++++++++++++++++++++------- pkg/vars/vars_loader_test.go | 43 ++++++++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 11 deletions(-) diff --git a/pkg/types/vars_source.go b/pkg/types/vars_source.go index 68ba14731..89ca64760 100644 --- a/pkg/types/vars_source.go +++ b/pkg/types/vars_source.go @@ -15,9 +15,20 @@ type VarsSourceGit struct { } type VarsSourceClusterConfigMapOrSecret struct { - Name string `yaml:"name" validate:"required"` - Namespace string `yaml:"namespace,omitempty"` - Key string `yaml:"key" validate:"required"` + Name string `yaml:"name,omitempty"` + Labels map[string]string `yaml:"labels,omitempty"` + Namespace string `yaml:"namespace" validate:"required"` + Key string `yaml:"key" validate:"required"` +} + +func ValidateVarsSourceClusterConfigMapOrSecret(sl validator.StructLevel) { + s := sl.Current().Interface().(VarsSourceClusterConfigMapOrSecret) + + if s.Name == "" && len(s.Labels) == 0 { + sl.ReportError(s, "self", "self", "either name or labels must be set", "") + } else if s.Name != "" && len(s.Labels) != 0 { + sl.ReportError(s, "self", "self", "only one of name or labels can be set", "") + } } type VarsSourceHttp struct { @@ -74,5 +85,6 @@ func ValidateVarsSource(sl validator.StructLevel) { } func init() { + yaml.Validator.RegisterStructValidation(ValidateVarsSourceClusterConfigMapOrSecret, VarsSourceClusterConfigMapOrSecret{}) yaml.Validator.RegisterStructValidation(ValidateVarsSource, VarsSource{}) } diff --git a/pkg/vars/vars_loader.go b/pkg/vars/vars_loader.go index bdb586bd8..73a0780b1 100644 --- a/pkg/vars/vars_loader.go +++ b/pkg/vars/vars_loader.go @@ -15,6 +15,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/vars/vault" "github.com/kluctl/kluctl/v2/pkg/yaml" "io/ioutil" + "k8s.io/apimachinery/pkg/runtime/schema" "os" ) @@ -71,11 +72,9 @@ func (v *VarsLoader) LoadVars(varsCtx *VarsCtx, sourceIn *types.VarsSource, sear } else if source.Git != nil { return v.loadGit(varsCtx, source.Git, rootKey) } else if source.ClusterConfigMap != nil { - ref := k8s2.NewObjectRef("", "v1", "ConfigMap", source.ClusterConfigMap.Name, source.ClusterConfigMap.Namespace) - return v.loadFromK8sObject(varsCtx, ref, source.ClusterConfigMap.Key, rootKey, false) + return v.loadFromK8sObject(varsCtx, *source.ClusterConfigMap, "ConfigMap", source.ClusterConfigMap.Key, rootKey, false) } else if source.ClusterSecret != nil { - ref := k8s2.NewObjectRef("", "v1", "Secret", source.ClusterSecret.Name, source.ClusterSecret.Namespace) - return v.loadFromK8sObject(varsCtx, ref, source.ClusterSecret.Key, rootKey, true) + return v.loadFromK8sObject(varsCtx, *source.ClusterSecret, "Secret", source.ClusterSecret.Key, rootKey, true) } else if source.SystemEnvVars != nil { return v.loadSystemEnvs(varsCtx, &source, rootKey) } else if source.Http != nil { @@ -172,16 +171,39 @@ func (v *VarsLoader) loadGit(varsCtx *VarsCtx, gitFile *types.VarsSourceGit, roo return v.loadFromString(varsCtx, string(f), rootKey) } -func (v *VarsLoader) loadFromK8sObject(varsCtx *VarsCtx, ref k8s2.ObjectRef, key string, rootKey string, base64Decode bool) error { +func (v *VarsLoader) loadFromK8sObject(varsCtx *VarsCtx, varsSource types.VarsSourceClusterConfigMapOrSecret, kind string, key string, rootKey string, base64Decode bool) error { if v.k == nil { return nil } - o, _, err := v.k.GetSingleObject(ref) - if err != nil { - return err + var err error + var o *uo.UnstructuredObject + + if varsSource.Name != "" { + o, _, err = v.k.GetSingleObject(k8s2.NewObjectRef("", "v1", kind, varsSource.Name, varsSource.Namespace)) + if err != nil { + return err + } + } else { + objs, _, err := v.k.ListObjects(schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: kind, + }, varsSource.Namespace, varsSource.Labels) + if err != nil { + return err + } + if len(objs) == 0 { + return fmt.Errorf("no object found with labels %v", varsSource.Labels) + } + if len(objs) > 1 { + return fmt.Errorf("found more than one objects with labels %v", varsSource.Labels) + } + o = objs[0] } + ref := o.GetK8sRef() + f, found, err := o.GetNestedField("data", key) if err != nil { return err diff --git a/pkg/vars/vars_loader_test.go b/pkg/vars/vars_loader_test.go index 03faf654e..1dbb14018 100644 --- a/pkg/vars/vars_loader_test.go +++ b/pkg/vars/vars_loader_test.go @@ -277,6 +277,49 @@ func TestVarsLoader_ClusterSecret(t *testing.T) { }, &secret) } +func TestVarsLoader_K8sObjectLabels(t *testing.T) { + cm1 := corev1.ConfigMap{ + ObjectMeta: v1.ObjectMeta{Name: "cm1", Namespace: "ns", Labels: map[string]string{"label1": "value1"}}, + Data: map[string]string{ + "vars": `{"test1": {"test2": 42}}`, + }, + } + cm2 := corev1.ConfigMap{ + ObjectMeta: v1.ObjectMeta{Name: "cm2", Namespace: "ns", Labels: map[string]string{"label2": "value2"}}, + Data: map[string]string{ + "vars": `{"test3": {"test4": 43}}`, + }, + } + + testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { + err := vl.LoadVars(vc, &types.VarsSource{ + ClusterConfigMap: &types.VarsSourceClusterConfigMapOrSecret{ + Labels: map[string]string{"label1": "value1"}, + Namespace: "ns", + Key: "vars", + }, + }, nil, "") + assert.NoError(t, err) + + v, _, _ := vc.Vars.GetNestedInt("test1", "test2") + assert.Equal(t, int64(42), v) + }, &cm1, &cm2) + + testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { + err := vl.LoadVars(vc, &types.VarsSource{ + ClusterConfigMap: &types.VarsSourceClusterConfigMapOrSecret{ + Labels: map[string]string{"label2": "value2"}, + Namespace: "ns", + Key: "vars", + }, + }, nil, "") + assert.NoError(t, err) + + v, _, _ := vc.Vars.GetNestedInt("test3", "test4") + assert.Equal(t, int64(43), v) + }, &cm1, &cm2) +} + func TestVarsLoader_SystemEnv(t *testing.T) { t.Setenv("TEST1", "42") t.Setenv("TEST2", "43") From 57a4eb2d4ca93c89f8435d810b5d6efb44339f70 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 14 Jun 2022 14:01:55 +0200 Subject: [PATCH 0924/2916] fix: Use simple status handler in case of --debug --- cmd/kluctl/commands/root.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index f71cd8595..c7854cc4a 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -70,15 +70,16 @@ var flagGroups = []groupInfo{ var cliCtx = context.Background() -func setupStatusHandler() { +func setupStatusHandler(debug bool) { var sh status.StatusHandler - if isatty.IsTerminal(os.Stderr.Fd()) { + if !debug && isatty.IsTerminal(os.Stderr.Fd()) { sh = status.NewMultiLineStatusHandler(cliCtx, os.Stderr, false) } else { sh = status.NewSimpleStatusHandler(func(message string) { _, _ = fmt.Fprintf(os.Stderr, "%s\n", message) }, false) } + sh.SetTrace(debug) cliCtx = status.NewContext(cliCtx, sh) klog.LogToStderr(false) @@ -143,7 +144,7 @@ func (c *cli) checkNewVersion() { } func (c *cli) preRun() error { - status.FromContext(cliCtx).SetTrace(c.Debug) + setupStatusHandler(c.Debug) c.checkNewVersion() return nil } @@ -193,7 +194,6 @@ composed of multiple smaller parts (Helm/Kustomize/...) in a manageable and unif return root.preRun() } - setupStatusHandler() initViper() err = rootCmd.ExecuteContext(cliCtx) From fbdd3856ef9f8f12c464b64d0ba26b1b1d28eedd Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 14 Jun 2022 14:02:07 +0200 Subject: [PATCH 0925/2916] fix: Redirect stderr lines to status handler --- cmd/kluctl/commands/root.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index c7854cc4a..497f18dae 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -27,6 +27,7 @@ import ( "github.com/mattn/go-isatty" "github.com/spf13/cobra" "github.com/spf13/viper" + "io" "k8s.io/klog/v2" "log" "net/http" @@ -85,6 +86,18 @@ func setupStatusHandler(debug bool) { klog.LogToStderr(false) klog.SetOutput(status.NewLineRedirector(sh.Info)) log.SetOutput(status.NewLineRedirector(sh.Info)) + + pr, pw, err := os.Pipe() + if err != nil { + panic(err) + } + + go func() { + x := status.NewLineRedirector(sh.Info) + _, _ = io.Copy(x, pr) + }() + + os.Stderr = pw } type VersionCheckState struct { From a619f0dbea5b1b92ee8bb1e5b587d5faff513439 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 14 Jun 2022 14:03:13 +0200 Subject: [PATCH 0926/2916] fix: Fix check for existing dirs in checkDeploymentDirs This fixes an issue with relatively (e.g. ../../deployment) included sub deployments. --- pkg/deployment/deployment_project.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go index 14b894813..36c005f62 100644 --- a/pkg/deployment/deployment_project.go +++ b/pkg/deployment/deployment_project.go @@ -120,7 +120,7 @@ func (p *DeploymentProject) checkDeploymentDirs() error { continue } - diDir, err := securejoin.SecureJoin(p.absDir, *di.Path) + diDir, err := securejoin.SecureJoin(p.source.dir, filepath.Join(p.relDir, *di.Path)) if err != nil { return err } From a796596b539bd7a465620aa3a1b548b292e7d906 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 15 Jun 2022 10:45:39 +0200 Subject: [PATCH 0927/2916] fix: Determine if stderr is a terminal before overriding os.Stderr --- cmd/kluctl/commands/root.go | 6 ++++-- pkg/status/noop.go | 4 ++++ pkg/status/promts.go | 15 ++++++++++++--- pkg/status/simple_status_handler.go | 16 +++++++++++----- pkg/status/status.go | 2 ++ pkg/status/status_handler.go | 22 ++++++++++++++-------- 6 files changed, 47 insertions(+), 18 deletions(-) diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index 497f18dae..e0329e7ff 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -72,13 +72,15 @@ var flagGroups = []groupInfo{ var cliCtx = context.Background() func setupStatusHandler(debug bool) { + // we must determine isTerminal before we override os.Stderr + isTerminal := isatty.IsTerminal(os.Stderr.Fd()) var sh status.StatusHandler if !debug && isatty.IsTerminal(os.Stderr.Fd()) { - sh = status.NewMultiLineStatusHandler(cliCtx, os.Stderr, false) + sh = status.NewMultiLineStatusHandler(cliCtx, os.Stderr, isTerminal, false) } else { sh = status.NewSimpleStatusHandler(func(message string) { _, _ = fmt.Fprintf(os.Stderr, "%s\n", message) - }, false) + }, isTerminal, false) } sh.SetTrace(debug) cliCtx = status.NewContext(cliCtx, sh) diff --git a/pkg/status/noop.go b/pkg/status/noop.go index c1ff08a9e..53f9f7a51 100644 --- a/pkg/status/noop.go +++ b/pkg/status/noop.go @@ -8,6 +8,10 @@ type NoopStatusHandler struct { type NoopStatusLine struct { } +func (n NoopStatusHandler) IsTerminal() bool { + return false +} + func (n NoopStatusHandler) SetTrace(trace bool) { } diff --git a/pkg/status/promts.go b/pkg/status/promts.go index 8274e8c40..3f8276a10 100644 --- a/pkg/status/promts.go +++ b/pkg/status/promts.go @@ -9,13 +9,22 @@ import ( "strings" ) +func isTerminal(ctx context.Context) bool { + sh := FromContext(ctx) + if sh != nil { + return sh.IsTerminal() + } else { + return isatty.IsTerminal(os.Stderr.Fd()) + } +} + // AskForConfirmation uses Scanln to parse user input. A user must type in "yes" or "no" and // then press enter. It has fuzzy matching, so "y", "Y", "yes", "YES", and "Yes" all count as // confirmations. If the input is not recognized, it will ask again. The function does not return // until it gets a valid response from the user. Typically, you should use fmt to print out a question // before calling askForConfirmation. E.g. fmt.Println("WARNING: Are you sure? (yes/no)") func AskForConfirmation(ctx context.Context, prompt string) bool { - if !isatty.IsTerminal(os.Stderr.Fd()) { + if !isTerminal(ctx) { Warning(ctx, "Not a terminal, suppressed prompt: %s", prompt) return false } @@ -37,7 +46,7 @@ func AskForConfirmation(ctx context.Context, prompt string) bool { } func AskForPassword(ctx context.Context, prompt string) (string, error) { - if !isatty.IsTerminal(os.Stderr.Fd()) { + if !isTerminal(ctx) { err := fmt.Errorf("not a terminal, suppressed credentials prompt: %s", prompt) Warning(ctx, err.Error()) return "", err @@ -52,7 +61,7 @@ func AskForPassword(ctx context.Context, prompt string) (string, error) { } func AskForCredentials(ctx context.Context, prompt string) (string, string, error) { - if !isatty.IsTerminal(os.Stderr.Fd()) { + if !isTerminal(ctx) { err := fmt.Errorf("not a terminal, suppressed credentials prompt: %s", prompt) Warning(ctx, err.Error()) return "", "", err diff --git a/pkg/status/simple_status_handler.go b/pkg/status/simple_status_handler.go index 2f7cc9772..cdddd374c 100644 --- a/pkg/status/simple_status_handler.go +++ b/pkg/status/simple_status_handler.go @@ -8,20 +8,26 @@ import ( ) type simpleStatusHandler struct { - cb func(message string) - trace bool + cb func(message string) + isTerminal bool + trace bool } type simpleStatusLine struct { } -func NewSimpleStatusHandler(cb func(message string), trace bool) StatusHandler { +func NewSimpleStatusHandler(cb func(message string), isTerminal bool, trace bool) StatusHandler { return &simpleStatusHandler{ - cb: cb, - trace: trace, + cb: cb, + isTerminal: isTerminal, + trace: trace, } } +func (s *simpleStatusHandler) IsTerminal() bool { + return s.isTerminal +} + func (s *simpleStatusHandler) SetTrace(trace bool) { s.trace = trace } diff --git a/pkg/status/status.go b/pkg/status/status.go index 0e0514e41..875e03391 100644 --- a/pkg/status/status.go +++ b/pkg/status/status.go @@ -37,6 +37,8 @@ type StatusLine interface { } type StatusHandler interface { + IsTerminal() bool + SetTrace(trace bool) Stop() Flush() diff --git a/pkg/status/status_handler.go b/pkg/status/status_handler.go index d385afb76..ad9491bad 100644 --- a/pkg/status/status_handler.go +++ b/pkg/status/status_handler.go @@ -13,10 +13,11 @@ import ( ) type MultiLineStatusHandler struct { - ctx context.Context - out io.Writer - progress *mpb.Progress - trace bool + ctx context.Context + out io.Writer + isTerminal bool + progress *mpb.Progress + trace bool } type statusLine struct { @@ -29,11 +30,12 @@ type statusLine struct { barOverride string } -func NewMultiLineStatusHandler(ctx context.Context, out io.Writer, trace bool) *MultiLineStatusHandler { +func NewMultiLineStatusHandler(ctx context.Context, out io.Writer, isTerminal bool, trace bool) *MultiLineStatusHandler { sh := &MultiLineStatusHandler{ - ctx: ctx, - out: out, - trace: trace, + ctx: ctx, + out: out, + isTerminal: isTerminal, + trace: trace, } sh.start() @@ -41,6 +43,10 @@ func NewMultiLineStatusHandler(ctx context.Context, out io.Writer, trace bool) * return sh } +func (s *MultiLineStatusHandler) IsTerminal() bool { + return s.isTerminal +} + func (s *MultiLineStatusHandler) Flush() { s.Stop() s.start() From d00efb995c57808f1127d39de77c28818d482574 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 15 Jun 2022 11:06:41 +0200 Subject: [PATCH 0928/2916] tests: No need to deploy a sleeping busybox deployment Deploy a ConfigMap instead --- e2e/external_projects_test.go | 15 +++++++-------- e2e/utils_resources.go | 15 --------------- 2 files changed, 7 insertions(+), 23 deletions(-) diff --git a/e2e/external_projects_test.go b/e2e/external_projects_test.go index eefd91b17..18dbe95ca 100644 --- a/e2e/external_projects_test.go +++ b/e2e/external_projects_test.go @@ -4,7 +4,6 @@ import ( "fmt" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "testing" - "time" ) func doTestProject(t *testing.T, namespace string, p *testProject) { @@ -21,21 +20,21 @@ func doTestProject(t *testing.T, namespace string, p *testProject) { p.updateTargetDeprecated("test", k.Name, uo.FromMap(map[string]interface{}{ "target_var": "target_value1", })) - addBusyboxDeployment(p, "busybox", resourceOpts{name: "busybox", namespace: namespace}) + addConfigMapDeployment(p, "cm1", map[string]string{}, resourceOpts{name: "cm1", namespace: namespace}) p.KluctlMust("deploy", "--yes", "-t", "test") - assertReadiness(t, k, namespace, "Deployment/busybox", time.Minute*5) + assertResourceExists(t, k, namespace, "ConfigMap/cm1") cmData := map[string]string{ "cluster_var": "{{ cluster.cluster_var }}", "target_var": "{{ args.target_var }}", } - assertResourceNotExists(t, k, namespace, "ConfigMap/cm") - addConfigMapDeployment(p, "cm", cmData, resourceOpts{name: "cm", namespace: namespace}) + assertResourceNotExists(t, k, namespace, "ConfigMap/cm2") + addConfigMapDeployment(p, "cm2", cmData, resourceOpts{name: "cm2", namespace: namespace}) p.KluctlMust("deploy", "--yes", "-t", "test") - o := assertResourceExists(t, k, namespace, "ConfigMap/cm") + o := assertResourceExists(t, k, namespace, "ConfigMap/cm2") assertNestedFieldEquals(t, o, "cluster_value1", "data", "cluster_var") assertNestedFieldEquals(t, o, "target_value1", "data", "target_var") @@ -43,7 +42,7 @@ func doTestProject(t *testing.T, namespace string, p *testProject) { "cluster_var": "cluster_value2", })) p.KluctlMust("deploy", "--yes", "-t", "test") - o = assertResourceExists(t, k, namespace, "ConfigMap/cm") + o = assertResourceExists(t, k, namespace, "ConfigMap/cm2") assertNestedFieldEquals(t, o, "cluster_value2", "data", "cluster_var") assertNestedFieldEquals(t, o, "target_value1", "data", "target_var") @@ -51,7 +50,7 @@ func doTestProject(t *testing.T, namespace string, p *testProject) { "target_var": "target_value2", })) p.KluctlMust("deploy", "--yes", "-t", "test") - o = assertResourceExists(t, k, namespace, "ConfigMap/cm") + o = assertResourceExists(t, k, namespace, "ConfigMap/cm2") assertNestedFieldEquals(t, o, "cluster_value2", "data", "cluster_var") assertNestedFieldEquals(t, o, "target_value2", "data", "target_var") } diff --git a/e2e/utils_resources.go b/e2e/utils_resources.go index 0dc063e7f..e6f9e4a20 100644 --- a/e2e/utils_resources.go +++ b/e2e/utils_resources.go @@ -92,17 +92,6 @@ func addDeploymentHelper(p *testProject, dir string, o *uo.UnstructuredObject, o p.addKustomizeDeployment(dir, resources, opts.tags) } -func addDeploymentDeployment(p *testProject, dir string, opts resourceOpts, image string, command []string, args []string) { - o := renderTemplateObjectHelper(deploymentTemplate, map[string]interface{}{ - "name": opts.name, - "namespace": opts.namespace, - "image": image, - }) - o[0].SetNestedField(command, "spec", "template", "spec", "containers", 0, "command") - o[0].SetNestedField(args, "spec", "template", "spec", "containers", 0, "args") - addDeploymentHelper(p, dir, o[0], opts) -} - func addJobDeployment(p *testProject, dir string, opts resourceOpts, image string, command []string, args []string) { o := renderTemplateObjectHelper(jobTemplate, map[string]interface{}{ "name": opts.name, @@ -114,10 +103,6 @@ func addJobDeployment(p *testProject, dir string, opts resourceOpts, image strin addDeploymentHelper(p, dir, o[0], opts) } -func addBusyboxDeployment(p *testProject, dir string, opts resourceOpts) { - addDeploymentDeployment(p, dir, opts, "busybox", []string{"sleep"}, []string{"1000"}) -} - const podRbacTemplate = ` apiVersion: v1 kind: ServiceAccount From 4d1e8b019d833e08f43b74ed78f3e16794841c42 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 16 Jun 2022 11:25:22 +0200 Subject: [PATCH 0929/2916] feat: Remove version field from DeleteObjectItemConfig --- pkg/deployment/utils/apply_utils.go | 2 +- pkg/types/deployment.go | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index 46b246351..eb6cded06 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -403,7 +403,7 @@ func (a *ApplyUtil) WaitReadiness(ref k8s2.ObjectRef, timeout time.Duration) boo func (a *ApplyUtil) applyDeploymentItem(d *deployment.DeploymentItem) { var toDelete []k8s2.ObjectRef for _, x := range d.Config.DeleteObjects { - for _, gvk := range a.k.Resources.GetFilteredGVKs(k8s.BuildGVKFilter(x.Group, x.Version, x.Kind)) { + for _, gvk := range a.k.Resources.GetFilteredGVKs(k8s.BuildGVKFilter(x.Group, nil, x.Kind)) { ref := k8s2.ObjectRef{ GVK: gvk, Name: x.Name, diff --git a/pkg/types/deployment.go b/pkg/types/deployment.go index 6f2ccd6fa..d331457f2 100644 --- a/pkg/types/deployment.go +++ b/pkg/types/deployment.go @@ -41,7 +41,6 @@ func ValidateDeploymentItemConfig(sl validator.StructLevel) { type DeleteObjectItemConfig struct { Group *string `yaml:"group,omitempty"` - Version *string `yaml:"version,omitempty"` Kind *string `yaml:"kind,omitempty"` Name string `yaml:"name" validate:"required"` Namespace string `yaml:"namespace,omitempty"` @@ -49,7 +48,7 @@ type DeleteObjectItemConfig struct { func ValidateDeleteObjectItemConfig(sl validator.StructLevel) { s := sl.Current().Interface().(DeleteObjectItemConfig) - if s.Group == nil && s.Version == nil && s.Kind == nil { + if s.Group == nil && s.Kind == nil { sl.ReportError(s, "self", "self", "at least one of group/version/kind must be set", "") } } From e2092eb29d922403642a8761f198662387874687 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 29 Jun 2022 14:24:50 +0200 Subject: [PATCH 0930/2916] fix: Call setupStatusHandler even for unknown commands --- cmd/kluctl/commands/root.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index e0329e7ff..435c6e67d 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -70,8 +70,11 @@ var flagGroups = []groupInfo{ } var cliCtx = context.Background() +var didSetupStatusHandler bool func setupStatusHandler(debug bool) { + didSetupStatusHandler = true + // we must determine isTerminal before we override os.Stderr isTerminal := isatty.IsTerminal(os.Stderr.Fd()) var sh status.StatusHandler @@ -212,6 +215,9 @@ composed of multiple smaller parts (Helm/Kustomize/...) in a manageable and unif initViper() err = rootCmd.ExecuteContext(cliCtx) + if !didSetupStatusHandler { + setupStatusHandler(false) + } sh := status.FromContext(cliCtx) From 93e9978117bf738ab56b34640856dc1007d41528 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 1 Jul 2022 14:15:40 +0200 Subject: [PATCH 0931/2916] fix: Fix crash in poke-image in case a remote object does not exist yet --- pkg/deployment/commands/poke_images.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/deployment/commands/poke_images.go b/pkg/deployment/commands/poke_images.go index 03ae6dbc3..cde5280b7 100644 --- a/pkg/deployment/commands/poke_images.go +++ b/pkg/deployment/commands/poke_images.go @@ -77,7 +77,12 @@ func (cmd *PokeImagesCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*type go func() { defer wg.Done() au := ad.NewApplyUtil(ctx, nil) - au.ReplaceObject(ref, ru.GetRemoteObject(ref), func(o *uo.UnstructuredObject) (*uo.UnstructuredObject, error) { + remote := ru.GetRemoteObject(ref) + if remote == nil { + dew.AddWarning(ref, fmt.Errorf("remote object not found, skipped image replacement")) + return + } + au.ReplaceObject(ref, remote, func(o *uo.UnstructuredObject) (*uo.UnstructuredObject, error) { return doPokeImage(containers, o) }) }() From ea50eff19dc2c63480f5da6fef3d974ef929e3a2 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 1 Jul 2022 14:31:57 +0200 Subject: [PATCH 0932/2916] fix: Use local object for field manager path conversion This fixes a bug where a key based fieldpath element points to a different index in the local vs remote objects. --- pkg/diff/managed_fields.go | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/pkg/diff/managed_fields.go b/pkg/diff/managed_fields.go index 8dd6ce197..deaf26f4e 100644 --- a/pkg/diff/managed_fields.go +++ b/pkg/diff/managed_fields.go @@ -175,21 +175,28 @@ func ResolveFieldManagerConflicts(local *uo.UnstructuredObject, remote *uo.Unstr return nil, nil, fmt.Errorf("field path '%s' is ambiguous", cause.Field) } - p, found, err := convertToKeyList(remote, mf.pathes[0]) + localKeyPath, found, err := convertToKeyList(local, mf.pathes[0]) if err != nil { return nil, nil, err } if !found { - return nil, nil, fmt.Errorf("field '%s' not found in remote object", cause.Field) + return nil, nil, fmt.Errorf("field '%s' not found in local object", cause.Field) } - localValue, found, err := local.GetNestedField(p...) + + remoteKeyPath, found, err := convertToKeyList(remote, mf.pathes[0]) if err != nil { return nil, nil, err } if !found { - return nil, nil, fmt.Errorf("field '%s' not found in local object", cause.Field) + return nil, nil, fmt.Errorf("field '%s' not found in remote object", cause.Field) } - remoteValue, found, err := remote.GetNestedField(p...) + + localValue, found, err := local.GetNestedField(localKeyPath...) + if !found { + panic(fmt.Sprintf("field '%s' not found in local object...which can't be!", cause.Field)) + } + + remoteValue, found, err := remote.GetNestedField(remoteKeyPath...) if !found { panic(fmt.Sprintf("field '%s' not found in remote object...which can't be!", cause.Field)) } @@ -209,13 +216,16 @@ func ResolveFieldManagerConflicts(local *uo.UnstructuredObject, remote *uo.Unstr break } } - if _, ok := forceApplyFields[p.ToJsonPath()]; ok { + if _, ok := forceApplyFields[localKeyPath.ToJsonPath()]; ok { + overwrite = true + } + if _, ok := forceApplyFields[remoteKeyPath.ToJsonPath()]; ok { overwrite = true } } if !overwrite { - j, err := uo.NewMyJsonPath(p.ToJsonPath()) + j, err := uo.NewMyJsonPath(localKeyPath.ToJsonPath()) if err != nil { return nil, nil, err } From 12c47e8ce6241e5521d4ae02365cd086ea5cee81 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 6 Jul 2022 13:53:57 +0200 Subject: [PATCH 0933/2916] fix: Add original message to conflict error messages --- pkg/diff/managed_fields.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/diff/managed_fields.go b/pkg/diff/managed_fields.go index deaf26f4e..0e89651fe 100644 --- a/pkg/diff/managed_fields.go +++ b/pkg/diff/managed_fields.go @@ -169,10 +169,10 @@ func ResolveFieldManagerConflicts(local *uo.UnstructuredObject, remote *uo.Unstr mf, ok := managersByFields[cause.Field] if !ok { - return nil, nil, fmt.Errorf("could not find matching field for path '%s'", cause.Field) + return nil, nil, fmt.Errorf("%s. Could not find matching field for path '%s'", cause.Message, cause.Field) } if len(mf.pathes) != 1 { - return nil, nil, fmt.Errorf("field path '%s' is ambiguous", cause.Field) + return nil, nil, fmt.Errorf("%s. Field path '%s' is ambiguous", cause.Message, cause.Field) } localKeyPath, found, err := convertToKeyList(local, mf.pathes[0]) @@ -180,7 +180,7 @@ func ResolveFieldManagerConflicts(local *uo.UnstructuredObject, remote *uo.Unstr return nil, nil, err } if !found { - return nil, nil, fmt.Errorf("field '%s' not found in local object", cause.Field) + return nil, nil, fmt.Errorf("%s. Field '%s' not found in local object", cause.Message, cause.Field) } remoteKeyPath, found, err := convertToKeyList(remote, mf.pathes[0]) @@ -188,7 +188,7 @@ func ResolveFieldManagerConflicts(local *uo.UnstructuredObject, remote *uo.Unstr return nil, nil, err } if !found { - return nil, nil, fmt.Errorf("field '%s' not found in remote object", cause.Field) + return nil, nil, fmt.Errorf("%s. Field '%s' not found in remote object", cause.Message, cause.Field) } localValue, found, err := local.GetNestedField(localKeyPath...) From ed7c5000e1339d92b01d2b9006e1bbf88690717e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 6 Jul 2022 14:34:57 +0200 Subject: [PATCH 0934/2916] fix: Stop using goccy/go-yaml and fully rely on yaml.v3 --- go.mod | 4 +--- go.sum | 8 -------- pkg/yaml/validator.go | 26 ++++++++++++++++++++++++- pkg/yaml/yaml.go | 45 ++++++++++++++++++++++++++++--------------- 4 files changed, 56 insertions(+), 27 deletions(-) diff --git a/go.mod b/go.mod index b4e6c807c..ca9a9d8ec 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,6 @@ require ( github.com/go-git/go-git/v5 v5.4.2 github.com/go-playground/validator/v10 v10.11.0 github.com/gobwas/glob v0.2.3 - github.com/goccy/go-yaml v1.9.5 github.com/golang-jwt/jwt/v4 v4.4.1 github.com/google/go-containerregistry v0.9.0 github.com/hashicorp/vault/api v1.6.0 @@ -25,6 +24,7 @@ require ( github.com/klauspost/compress v1.15.6 github.com/mattn/go-isatty v0.0.14 github.com/mitchellh/go-ps v1.0.0 + github.com/mitchellh/reflectwalk v1.0.2 github.com/ohler55/ojg v1.14.2 github.com/pkg/errors v0.9.1 github.com/r3labs/diff/v2 v2.15.1 @@ -158,7 +158,6 @@ require ( github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moby/locker v1.0.1 // indirect github.com/moby/spdystream v0.2.0 // indirect github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect @@ -199,7 +198,6 @@ require ( golang.org/x/net v0.0.0-20220607020251-c690dde0001d // indirect golang.org/x/oauth2 v0.0.0-20220524215830-622c5d57e401 // indirect golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect - golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20220602131408-e326c6e8e9c8 // indirect google.golang.org/grpc v1.47.0 // indirect diff --git a/go.sum b/go.sum index 739e736a0..af4a31531 100644 --- a/go.sum +++ b/go.sum @@ -399,13 +399,10 @@ github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrK github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= -github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= -github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw= github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= @@ -438,8 +435,6 @@ github.com/gobuffalo/packr/v2 v2.8.3 h1:xE1yzvnO56cUC0sTpKR3DIbxZgB54AftTFMhB2XE github.com/gobuffalo/packr/v2 v2.8.3/go.mod h1:0SahksCVcx4IMnigTjiFuyldmTrdTctXsOdiU5KwbKc= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/goccy/go-yaml v1.9.5 h1:Eh/+3uk9kLxG4koCX6lRMAPS1OaMSAi+FJcya0INdB0= -github.com/goccy/go-yaml v1.9.5/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godror/godror v0.24.2/go.mod h1:wZv/9vPiUib6tkoDl+AZ/QLf5YZgMravZ7jxH2eQWAE= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= @@ -796,7 +791,6 @@ github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhR github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/ldez/gomoddirectives v0.2.2/go.mod h1:cpgBogWITnCfRq2qGoDkKMEVSaarhdBr6g8G04uz6d0= github.com/ldez/tagliatelle v0.2.0/go.mod h1:8s6WJQwEYHbKZDsp/LjArytKOG8qaMrKQQ3mFukHs88= -github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/letsencrypt/pkcs11key/v4 v4.0.0/go.mod h1:EFUvBDay26dErnNb70Nd0/VW3tJiIbETBPTl9ATXQag= @@ -1685,8 +1679,6 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df h1:5Pf6pFKu98ODmgnpvkJ3kFUOQGGLIzLIkbzUHp47618= -golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= diff --git a/pkg/yaml/validator.go b/pkg/yaml/validator.go index aa2398605..4c3265da2 100644 --- a/pkg/yaml/validator.go +++ b/pkg/yaml/validator.go @@ -1,7 +1,31 @@ package yaml -import "github.com/go-playground/validator/v10" +import ( + "github.com/go-playground/validator/v10" + "github.com/mitchellh/reflectwalk" + "reflect" +) var ( Validator = validator.New() ) + +type structValidationWalker struct { +} + +func (w *structValidationWalker) Struct(v reflect.Value) error { + v2 := v.Interface() + err := Validator.Struct(v2) + if err != nil { + return err + } + return nil +} + +func (w *structValidationWalker) StructField(reflect.StructField, reflect.Value) error { + return nil +} + +func ValidateStructs(s interface{}) error { + return reflectwalk.Walk(s, &structValidationWalker{}) +} diff --git a/pkg/yaml/yaml.go b/pkg/yaml/yaml.go index 2c24f990a..295ce0a46 100644 --- a/pkg/yaml/yaml.go +++ b/pkg/yaml/yaml.go @@ -6,7 +6,6 @@ import ( "encoding/json" "errors" "fmt" - "github.com/goccy/go-yaml" "github.com/kluctl/kluctl/v2/pkg/utils" "golang.org/x/text/encoding/unicode" "golang.org/x/text/transform" @@ -17,8 +16,32 @@ import ( "strings" ) -func newYamlDecoder(r io.Reader) *yaml.Decoder { - return yaml.NewDecoder(r, yaml.Strict(), yaml.Validator(Validator)) +type Decoder interface { + Decode(v interface{}) error +} + +type decoderWrapper struct { + d *yaml3.Decoder +} + +func (w *decoderWrapper) Decode(v interface{}) error { + err := w.d.Decode(v) + if err != nil { + return err + } + + err = ValidateStructs(v) + if err != nil { + return err + } + + return nil +} + +func newDecoder(r io.Reader, out any) Decoder { + d := yaml3.NewDecoder(r) + d.KnownFields(true) + return &decoderWrapper{d: d} } func newUnicodeReader(r io.Reader) io.Reader { @@ -51,16 +74,9 @@ func ReadYamlBytes(b []byte, o interface{}) error { func ReadYamlStream(r io.Reader, o interface{}) error { r = newUnicodeReader(r) - var err error - if _, ok := o.(*map[string]interface{}); ok { - // much faster - d := yaml3.NewDecoder(r) - err = d.Decode(o) - } else { - // we need proper working strict mode - d := newYamlDecoder(r) - err = d.Decode(o) - } + d := newDecoder(r, o) + + err := d.Decode(o) if err != nil && errors.Is(err, io.EOF) { return nil } @@ -88,8 +104,7 @@ func ReadYamlAllBytes(b []byte) ([]interface{}, error) { func ReadYamlAllStream(r io.Reader) ([]interface{}, error) { r = newUnicodeReader(r) - // yaml.v3 is much faster then go-yaml - d := yaml3.NewDecoder(r) + d := newDecoder(r, nil) var l []interface{} for true { From b3c0ee6b5fd5deac8722cfc8ef1d341fc9e92bdc Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 6 Jul 2022 14:40:33 +0200 Subject: [PATCH 0935/2916] refactor: Move parsing of rendered manifests into own func --- pkg/deployment/helm_chart.go | 39 ++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index b41313276..87e78ddbf 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -299,24 +299,7 @@ func (c *helmChart) doRender(ctx context.Context, k *k8s.K8sCluster) error { return err } - var parsed []*uo.UnstructuredObject - - doParse := func(s string) error { - m, err := yaml.ReadYamlAllString(s) - if err != nil { - return err - } - for _, x := range m { - x2, ok := x.(map[string]interface{}) - if !ok { - return fmt.Errorf("yaml object is not a map") - } - parsed = append(parsed, uo.FromMap(x2)) - } - return nil - } - - err = doParse(rel.Manifest) + parsed, err := c.parseRenderedManifests(rel.Manifest) if err != nil { return err } @@ -326,10 +309,11 @@ func (c *helmChart) doRender(ctx context.Context, k *k8s.K8sCluster) error { if isTestHook(m) { continue } - err = doParse(m.Manifest) + parsedHooks, err := c.parseRenderedManifests(m.Manifest) if err != nil { return err } + parsed = append(parsed, parsedHooks...) } } @@ -360,6 +344,23 @@ func (c *helmChart) doRender(ctx context.Context, k *k8s.K8sCluster) error { return nil } +func (c *helmChart) parseRenderedManifests(s string) ([]*uo.UnstructuredObject, error) { + var parsed []*uo.UnstructuredObject + + m, err := yaml.ReadYamlAllString(s) + if err != nil { + return nil, err + } + for _, x := range m { + x2, ok := x.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("yaml object is not a map") + } + parsed = append(parsed, uo.FromMap(x2)) + } + return parsed, nil +} + func (c *helmChart) SetCredentials(credentials *repo.Entry) { c.credentials = credentials } From 52316f99d0f1e0b683f08e47d1dd93ef1f8e6e0c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 6 Jul 2022 14:57:08 +0200 Subject: [PATCH 0936/2916] fix: Ignore duplicate fields in manifests of rendered Helm charts --- pkg/deployment/helm_chart.go | 7 ++++++- pkg/yaml/yaml.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index 87e78ddbf..a533ba8de 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -347,7 +347,12 @@ func (c *helmChart) doRender(ctx context.Context, k *k8s.K8sCluster) error { func (c *helmChart) parseRenderedManifests(s string) ([]*uo.UnstructuredObject, error) { var parsed []*uo.UnstructuredObject - m, err := yaml.ReadYamlAllString(s) + duplicatesRemoved, err := yaml.RemoveDuplicateFields(strings.NewReader(s)) + if err != nil { + return nil, err + } + + m, err := yaml.ReadYamlAllBytes(duplicatesRemoved) if err != nil { return nil, err } diff --git a/pkg/yaml/yaml.go b/pkg/yaml/yaml.go index 295ce0a46..6d559b530 100644 --- a/pkg/yaml/yaml.go +++ b/pkg/yaml/yaml.go @@ -9,6 +9,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils" "golang.org/x/text/encoding/unicode" "golang.org/x/text/transform" + yaml2 "gopkg.in/yaml.v2" yaml3 "gopkg.in/yaml.v3" "io" "os" @@ -213,6 +214,35 @@ func ConvertYamlToJson(b []byte) ([]byte, error) { return b, nil } +// RemoveDuplicateFields is a helper/hack to remove duplicate fields from yaml maps/structs. The yaml spec explicitly +// forbids duplicate keys, but yaml.v2 ignored those by default, leading to some tools (e.g. Helm) ignoring these. This +// forces us to also ignore/remove them in some cases. We do this by loading the yaml via yaml.v2 and then writing them +// back to a string which can then be parsed by yaml.v3 +// TODO Remove this helper method when https://github.com/go-yaml/yaml/issues/751 is implemented +func RemoveDuplicateFields(r io.Reader) ([]byte, error) { + buf := bytes.NewBuffer(nil) + d := yaml2.NewDecoder(r) + e := yaml2.NewEncoder(buf) + + for { + var o interface{} + err := d.Decode(&o) + if err != nil { + if errors.Is(err, io.EOF) { + break + } + return nil, err + } + if o != nil { + err = e.Encode(o) + if err != nil { + return nil, err + } + } + } + return buf.Bytes(), nil +} + func FixNameExt(dir string, name string) string { p := filepath.Join(dir, name) p = FixPathExt(p) From dbf9994d064955c4534907fd6d8f98a824bd3bf9 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 8 Jul 2022 18:16:01 +0200 Subject: [PATCH 0937/2916] chore: Remove support for metadata listing This is not used anymore and thus can be removed. --- pkg/deployment/utils/remote_objects_utils.go | 2 +- pkg/k8s/client.go | 29 +------------- pkg/k8s/client_factory.go | 8 ---- pkg/k8s/fake_client_factory.go | 6 --- pkg/k8s/k8s_cluster.go | 41 +------------------- 5 files changed, 5 insertions(+), 81 deletions(-) diff --git a/pkg/deployment/utils/remote_objects_utils.go b/pkg/deployment/utils/remote_objects_utils.go index ea2c3339a..442acb1a6 100644 --- a/pkg/deployment/utils/remote_objects_utils.go +++ b/pkg/deployment/utils/remote_objects_utils.go @@ -36,7 +36,7 @@ func (u *RemoteObjectUtils) UpdateRemoteObjects(k *k8s.K8sCluster, labels map[st s := status.Start(u.ctx, "Getting remote objects by commonLabels") defer s.Failed() - allObjects, apiWarnings, err := k.ListAllObjects([]string{"get"}, "", labels, false) + allObjects, apiWarnings, err := k.ListAllObjects([]string{"get"}, "", labels) for gvk, aw := range apiWarnings { u.dew.AddApiWarnings(k8s2.ObjectRef{GVK: gvk}, aw) } diff --git a/pkg/k8s/client.go b/pkg/k8s/client.go index 76f01135e..829240fc5 100644 --- a/pkg/k8s/client.go +++ b/pkg/k8s/client.go @@ -6,7 +6,6 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/dynamic" corev1 "k8s.io/client-go/kubernetes/typed/core/v1" - "k8s.io/client-go/metadata" ) type k8sClients struct { @@ -17,9 +16,8 @@ type k8sClients struct { } type parallelClientEntry struct { - corev1 corev1.CoreV1Interface - dynamicClient dynamic.Interface - metadataClient metadata.Interface + corev1 corev1.CoreV1Interface + dynamicClient dynamic.Interface warnings []ApiWarning } @@ -61,11 +59,6 @@ func newK8sClients(ctx context.Context, clientFactory ClientFactory, count int) return nil, err } - p.metadataClient, err = clientFactory.MetadataClient(p) - if err != nil { - return nil, err - } - k.clientPool <- p } return k, nil @@ -104,16 +97,6 @@ func (k *k8sClients) withDynamicClientForGVR(gvr *schema.GroupVersionResource, n }) } -func (k *k8sClients) withMetadataClientForGVR(gvr *schema.GroupVersionResource, namespace string, cb func(r metadata.ResourceInterface) error) ([]ApiWarning, error) { - return k.withClientFromPool(func(p *parallelClientEntry) error { - if namespace != "" { - return cb(p.metadataClient.Resource(*gvr).Namespace(namespace)) - } else { - return cb(p.metadataClient.Resource(*gvr)) - } - }) -} - func (k *k8sClients) withDynamicClientForGVK(resources *k8sResources, gvk schema.GroupVersionKind, namespace string, cb func(r dynamic.ResourceInterface) error) ([]ApiWarning, error) { gvr, err := resources.GetGVRForGVK(gvk) if err != nil { @@ -121,11 +104,3 @@ func (k *k8sClients) withDynamicClientForGVK(resources *k8sResources, gvk schema } return k.withDynamicClientForGVR(gvr, namespace, cb) } - -func (k *k8sClients) withMetadataClientForGVK(resources *k8sResources, gvk schema.GroupVersionKind, namespace string, cb func(r metadata.ResourceInterface) error) ([]ApiWarning, error) { - gvr, err := resources.GetGVRForGVK(gvk) - if err != nil { - return nil, err - } - return k.withMetadataClientForGVR(gvr, namespace, cb) -} diff --git a/pkg/k8s/client_factory.go b/pkg/k8s/client_factory.go index 0205c475c..6ab49a220 100644 --- a/pkg/k8s/client_factory.go +++ b/pkg/k8s/client_factory.go @@ -6,7 +6,6 @@ import ( "k8s.io/client-go/discovery/cached/disk" "k8s.io/client-go/dynamic" corev1 "k8s.io/client-go/kubernetes/typed/core/v1" - "k8s.io/client-go/metadata" "k8s.io/client-go/rest" "net/http" "net/url" @@ -22,7 +21,6 @@ type ClientFactory interface { DiscoveryClient() (discovery.DiscoveryInterface, error) CoreV1Client(wh rest.WarningHandler) (corev1.CoreV1Interface, error) DynamicClient(wh rest.WarningHandler) (dynamic.Interface, error) - MetadataClient(wh rest.WarningHandler) (metadata.Interface, error) } type realClientFactory struct { @@ -60,12 +58,6 @@ func (r *realClientFactory) DynamicClient(wh rest.WarningHandler) (dynamic.Inter return dynamic.NewForConfigAndClient(config, r.httpClient) } -func (r *realClientFactory) MetadataClient(wh rest.WarningHandler) (metadata.Interface, error) { - config := rest.CopyConfig(r.config) - config.WarningHandler = wh - return metadata.NewForConfigAndClient(config, r.httpClient) -} - func (r *realClientFactory) CloseIdleConnections() { r.httpClient.CloseIdleConnections() } diff --git a/pkg/k8s/fake_client_factory.go b/pkg/k8s/fake_client_factory.go index 8bc1169f2..b01673f27 100644 --- a/pkg/k8s/fake_client_factory.go +++ b/pkg/k8s/fake_client_factory.go @@ -11,8 +11,6 @@ import ( fake_dynamic "k8s.io/client-go/dynamic/fake" "k8s.io/client-go/kubernetes/fake" corev1 "k8s.io/client-go/kubernetes/typed/core/v1" - "k8s.io/client-go/metadata" - metadata_fake "k8s.io/client-go/metadata/fake" "k8s.io/client-go/rest" "sigs.k8s.io/kustomize/kyaml/openapi" "sigs.k8s.io/kustomize/kyaml/yaml" @@ -44,10 +42,6 @@ func (f *fakeClientFactory) DynamicClient(wh rest.WarningHandler) (dynamic.Inter return fake_dynamic.NewSimpleDynamicClient(f.scheme, f.objects...), nil } -func (f *fakeClientFactory) MetadataClient(wh rest.WarningHandler) (metadata.Interface, error) { - return metadata_fake.NewSimpleMetadataClient(f.scheme, f.objects...), nil -} - func NewFakeClientFactory(objects ...runtime.Object) ClientFactory { scheme := runtime.NewScheme() _ = v1.AddToScheme(scheme) diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index a810baa31..8b58788c9 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -2,7 +2,6 @@ package k8s import ( "context" - "encoding/json" "fmt" "github.com/Masterminds/semver/v3" "github.com/kluctl/kluctl/v2/pkg/status" @@ -18,7 +17,6 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/version" "k8s.io/client-go/dynamic" - "k8s.io/client-go/metadata" _ "k8s.io/client-go/plugin/pkg/client/auth" "k8s.io/client-go/rest" "sync" @@ -129,35 +127,7 @@ func (k *K8sCluster) ListObjects(gvk schema.GroupVersionKind, namespace string, return result, apiWarnings, err } -func (k *K8sCluster) ListObjectsMetadata(gvk schema.GroupVersionKind, namespace string, labels map[string]string) ([]*uo.UnstructuredObject, []ApiWarning, error) { - var result []*uo.UnstructuredObject - - apiWarnings, err := k.clients.withMetadataClientForGVK(k.Resources, gvk, namespace, func(r metadata.ResourceInterface) error { - o := v1.ListOptions{ - LabelSelector: k.buildLabelSelector(labels), - } - x, err := r.List(k.ctx, o) - if err != nil { - return err - } - for _, o := range x.Items { - b, err := json.Marshal(o) - if err != nil { - panic(err) - } - u, err := uo.FromString(string(b)) - if err != nil { - panic(err) - } - u.SetK8sGVK(gvk) - result = append(result, u) - } - return nil - }) - return result, apiWarnings, err -} - -func (k *K8sCluster) ListAllObjects(verbs []string, namespace string, labels map[string]string, onlyMetadata bool) ([]*uo.UnstructuredObject, map[schema.GroupVersionKind][]ApiWarning, error) { +func (k *K8sCluster) ListAllObjects(verbs []string, namespace string, labels map[string]string) ([]*uo.UnstructuredObject, map[schema.GroupVersionKind][]ApiWarning, error) { var ret []*uo.UnstructuredObject var errs []error retApiWarnings := make(map[schema.GroupVersionKind][]ApiWarning) @@ -181,14 +151,7 @@ func (k *K8sCluster) ListAllObjects(verbs []string, namespace string, labels map go func() { defer wg.Done() - var l []*uo.UnstructuredObject - var apiWarnings []ApiWarning - var err error - if onlyMetadata { - l, apiWarnings, err = k.ListObjectsMetadata(gvk, namespace, labels) - } else { - l, apiWarnings, err = k.ListObjects(gvk, namespace, labels) - } + l, apiWarnings, err := k.ListObjects(gvk, namespace, labels) mutex.Lock() defer mutex.Unlock() if err != nil && !errors.IsNotFound(err) { From 24336636a1dd23cca2a549652774738a824429a5 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 14 Jul 2022 14:52:04 +0200 Subject: [PATCH 0938/2916] fix: Add rootDir to searchDirs when rendering directories --- pkg/jinja2/jinja2.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/jinja2/jinja2.go b/pkg/jinja2/jinja2.go index 83e176870..1bc115aa7 100644 --- a/pkg/jinja2/jinja2.go +++ b/pkg/jinja2/jinja2.go @@ -214,6 +214,8 @@ func (j *Jinja2) needsRender(path string, excludedPatterns []string) bool { } func (j *Jinja2) RenderDirectory(rootDir string, searchDirs []string, relSourceDir string, excludePatterns []string, subdir string, targetDir string, vars *uo.UnstructuredObject) error { + searchDirs = append([]string{rootDir}, searchDirs...) + walkDir, err := securejoin.SecureJoin(rootDir, filepath.Join(relSourceDir, subdir)) if err != nil { return err From 2a32c584b7809d20ce1cb518eb14c215160f7857 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 14 Jul 2022 15:00:04 +0200 Subject: [PATCH 0939/2916] chore: Upgrade python version to 3.10.5 --- pkg/python/generate/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/python/generate/main.go b/pkg/python/generate/main.go index dc68cc409..9cc24b145 100644 --- a/pkg/python/generate/main.go +++ b/pkg/python/generate/main.go @@ -15,8 +15,8 @@ import ( const ( pythonVersionBase = "3.10" - pythonVersionFull = "3.10.4" - pythonStandaloneVersion = "20220502" + pythonVersionFull = "3.10.5" + pythonStandaloneVersion = "20220630" ) var pythonDists = map[string]string{ From d240624113c42cb2c85a78e0fe9fbe8c4b33df07 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 14 Jul 2022 15:00:14 +0200 Subject: [PATCH 0940/2916] chore: Upgrade jinja2 and click --- pkg/jinja2/python_src/requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/jinja2/python_src/requirements.txt b/pkg/jinja2/python_src/requirements.txt index 7bc8d801c..9ee2b287d 100644 --- a/pkg/jinja2/python_src/requirements.txt +++ b/pkg/jinja2/python_src/requirements.txt @@ -1,4 +1,4 @@ -jinja2===3.0.3 -click==8.0.3 +jinja2===3.1.2 +click==8.1.3 jsonpath-ng==1.5.3 pyyaml==6.0 From 3f7a6357e29afd6cdfa4457126da30bd2cc33ec6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 18 Jul 2022 14:15:27 +0200 Subject: [PATCH 0941/2916] fix: Only hold repo locks while actually pulling/cloning And don't hold locks for the whole lifetime of loaded projects. Instead, get a copy of the refs maps while the lock is held and afterwards use the commit ids for precise checkouts. --- cmd/kluctl/commands/utils.go | 7 +- pkg/deployment/deployment_project.go | 6 +- pkg/deployment/shared_context.go | 4 +- pkg/git/mirrored_repo.go | 8 +- pkg/git/poor_mans_clone.go | 32 +--- pkg/git/repocache/cache.go | 223 +++++++++++++++++++++++++++ pkg/git/repoprovider/live.go | 211 ------------------------- pkg/git/repoprovider/repoprovider.go | 19 --- pkg/kluctl_project/git.go | 2 +- pkg/kluctl_project/project.go | 4 +- pkg/kluctl_project/project_load.go | 11 +- pkg/kluctl_project/targets.go | 11 +- pkg/vars/vars_loader.go | 13 +- pkg/vars/vars_loader_test.go | 5 +- 14 files changed, 271 insertions(+), 285 deletions(-) create mode 100644 pkg/git/repocache/cache.go delete mode 100644 pkg/git/repoprovider/live.go delete mode 100644 pkg/git/repoprovider/repoprovider.go diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 4fdc5f1a9..8459ae908 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -8,7 +8,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/git" "github.com/kluctl/kluctl/v2/pkg/git/auth" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" - "github.com/kluctl/kluctl/v2/pkg/git/repoprovider" + "github.com/kluctl/kluctl/v2/pkg/git/repocache" "github.com/kluctl/kluctl/v2/pkg/jinja2" "github.com/kluctl/kluctl/v2/pkg/kluctl_project" "github.com/kluctl/kluctl/v2/pkg/registries" @@ -59,7 +59,7 @@ func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, strictTemplates b ctx, cancel := context.WithTimeout(cliCtx, projectFlags.Timeout) defer cancel() - rp := repoprovider.NewLiveRepoProvider(ctx, auth.NewDefaultAuthProviders(), projectFlags.GitCacheUpdateInterval) + rp := repocache.NewGitRepoCache(ctx, auth.NewDefaultAuthProviders(), projectFlags.GitCacheUpdateInterval) defer rp.Clear() loadArgs := kluctl_project.LoadKluctlProjectArgs{ @@ -183,9 +183,6 @@ func withProjectTargetCommandContext(ctx context.Context, args projectTargetComm images: images, } - // we can assume that all git access is done at this point, so we can unlock all repos - p.RP.UnlockAll() - return cb(cmdCtx) } diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go index 36c005f62..a11c1e919 100644 --- a/pkg/deployment/deployment_project.go +++ b/pkg/deployment/deployment_project.go @@ -155,7 +155,11 @@ func (p *DeploymentProject) loadIncludes() error { return err } } else if inc.Git != nil { - cloneDir, _, err := p.ctx.RP.GetClonedDir(inc.Git.Url, inc.Git.Ref) + ge, err := p.ctx.RP.GetEntry(inc.Git.Url) + if err != nil { + return err + } + cloneDir, _, err := ge.GetClonedDir(inc.Git.Ref) if err != nil { return err } diff --git a/pkg/deployment/shared_context.go b/pkg/deployment/shared_context.go index a659a43e0..3d4e3ca50 100644 --- a/pkg/deployment/shared_context.go +++ b/pkg/deployment/shared_context.go @@ -2,7 +2,7 @@ package deployment import ( "context" - "github.com/kluctl/kluctl/v2/pkg/git/repoprovider" + "github.com/kluctl/kluctl/v2/pkg/git/repocache" "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/vars" ) @@ -10,7 +10,7 @@ import ( type SharedContext struct { Ctx context.Context K *k8s.K8sCluster - RP repoprovider.RepoProvider + RP *repocache.GitRepoCache VarsLoader *vars.VarsLoader RenderDir string diff --git a/pkg/git/mirrored_repo.go b/pkg/git/mirrored_repo.go index 81525865e..5a48414aa 100644 --- a/pkg/git/mirrored_repo.go +++ b/pkg/git/mirrored_repo.go @@ -343,16 +343,16 @@ func (g *MirroredGitRepo) Update(authProviders *auth2.GitAuthProviders) error { return nil } -func (g *MirroredGitRepo) CloneProject(ref string, targetDir string) error { +func (g *MirroredGitRepo) CloneProjectByCommit(commit string, targetDir string) error { if !g.IsLocked() || !g.hasUpdated { panic("tried to clone from a project that is not locked/updated") } - status.Trace(g.ctx, "Cloning git project: url='%s', ref='%s', target='%s'", g.url.String(), ref, targetDir) + status.Trace(g.ctx, "Cloning git project: url='%s', commit='%s', target='%s'", g.url.String(), commit, targetDir) - err := PoorMansClone(g.mirrorDir, targetDir, ref) + err := PoorMansClone(g.mirrorDir, targetDir, &git.CheckoutOptions{Hash: plumbing.NewHash(commit)}) if err != nil { - return fmt.Errorf("failed to clone %s from %s: %w", ref, g.url.String(), err) + return fmt.Errorf("failed to clone %s from %s: %w", commit, g.url.String(), err) } return nil } diff --git a/pkg/git/poor_mans_clone.go b/pkg/git/poor_mans_clone.go index 851b6ae04..d5216cb49 100644 --- a/pkg/git/poor_mans_clone.go +++ b/pkg/git/poor_mans_clone.go @@ -1,20 +1,17 @@ package git import ( - "fmt" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/config" - "github.com/go-git/go-git/v5/plumbing" "github.com/kluctl/kluctl/v2/pkg/utils" "io/ioutil" "os" "path/filepath" "runtime" - "strings" ) -// PoorMansClone poor mans clone from a local repo, which does not rely on go-git using git-upload-pack -func PoorMansClone(sourceDir string, targetDir string, ref string) error { +// PoorMansCloneCommit poor mans clone from a local repo, which does not rely on go-git using git-upload-pack +func PoorMansClone(sourceDir string, targetDir string, coOptions *git.CheckoutOptions) error { err := os.MkdirAll(targetDir, 0o700) if err != nil { return err @@ -80,29 +77,8 @@ func PoorMansClone(sourceDir string, targetDir string, ref string) error { if err != nil { return err } - var ref2 plumbing.ReferenceName - if ref != "" { - if strings.HasPrefix(ref, "refs/heads") { - ref2 = plumbing.ReferenceName(ref) - } else { - if _, err := r.Reference(plumbing.NewBranchReferenceName(ref), true); err == nil { - ref2 = plumbing.NewBranchReferenceName(ref) - } else if _, err := r.Reference(plumbing.NewTagReferenceName(ref), true); err == nil { - ref2 = plumbing.NewTagReferenceName(ref) - } else { - return fmt.Errorf("ref %s not found", ref) - } - } - } else { - x, err := r.Reference("HEAD", true) - if err != nil { - return err - } - ref2 = x.Name() - } - err = wt.Checkout(&git.CheckoutOptions{ - Branch: ref2, - }) + + err = wt.Checkout(coOptions) if err != nil { return err } diff --git a/pkg/git/repocache/cache.go b/pkg/git/repocache/cache.go new file mode 100644 index 000000000..34d3ebcef --- /dev/null +++ b/pkg/git/repocache/cache.go @@ -0,0 +1,223 @@ +package repocache + +import ( + "context" + "fmt" + "github.com/kluctl/kluctl/v2/pkg/git" + "github.com/kluctl/kluctl/v2/pkg/git/auth" + git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" + "github.com/kluctl/kluctl/v2/pkg/utils" + "io/ioutil" + "os" + "path" + "path/filepath" + "strings" + "sync" + "time" +) + +type GitRepoCache struct { + ctx context.Context + authProviders *auth.GitAuthProviders + updateInterval time.Duration + repos map[string]*CacheEntry + reposMutex sync.Mutex + + cleanupDirs []string + cleeanupDirsMutex sync.Mutex +} + +type CacheEntry struct { + rp *GitRepoCache + mr *git.MirroredGitRepo + defaultRef string + refs map[string]string + + clonedDirs map[string]clonedDir + updateMutex sync.Mutex +} + +type RepoInfo struct { + Url git_url.GitUrl `yaml:"url"` + RemoteRefs map[string]string `yaml:"remoteRefs"` + DefaultRef string `yaml:"defaultRef"` +} + +type clonedDir struct { + dir string + info git.CheckoutInfo +} + +func NewGitRepoCache(ctx context.Context, authProviders *auth.GitAuthProviders, updateInterval time.Duration) *GitRepoCache { + return &GitRepoCache{ + ctx: ctx, + authProviders: authProviders, + updateInterval: updateInterval, + repos: map[string]*CacheEntry{}, + } +} + +func (rp *GitRepoCache) Clear() { + rp.cleeanupDirsMutex.Lock() + defer rp.cleeanupDirsMutex.Unlock() + + for _, p := range rp.cleanupDirs { + _ = os.RemoveAll(p) + } + rp.cleanupDirs = nil +} + +func (rp *GitRepoCache) GetEntry(url git_url.GitUrl) (*CacheEntry, error) { + rp.reposMutex.Lock() + defer rp.reposMutex.Unlock() + + e, ok := rp.repos[url.NormalizedRepoKey()] + if !ok { + mr, err := git.NewMirroredGitRepo(rp.ctx, url) + if err != nil { + return nil, err + } + e = &CacheEntry{ + rp: rp, + mr: mr, + clonedDirs: map[string]clonedDir{}, + } + rp.repos[url.NormalizedRepoKey()] = e + } + err := e.Update() + if err != nil { + return nil, err + } + return e, nil +} + +func (e *CacheEntry) Update() error { + e.updateMutex.Lock() + defer e.updateMutex.Unlock() + + err := e.mr.Lock() + if err != nil { + return err + } + defer e.mr.Unlock() + + if !e.mr.HasUpdated() { + if time.Now().Sub(e.mr.LastUpdateTime()) <= e.rp.updateInterval { + e.mr.SetUpdated(true) + } else { + err := e.mr.Update(e.rp.authProviders) + if err != nil { + return err + } + } + } + + e.refs, err = e.mr.RemoteRefHashesMap() + if err != nil { + return err + } + + e.defaultRef, err = e.mr.DefaultRef() + if err != nil { + return err + } + + return nil +} + +func (e *CacheEntry) GetRepoInfo() RepoInfo { + e.updateMutex.Lock() + defer e.updateMutex.Unlock() + + info := RepoInfo{ + Url: e.mr.Url(), + RemoteRefs: e.refs, + DefaultRef: e.defaultRef, + } + + return info +} + +func (e *CacheEntry) findCommit(ref string) (string, string, error) { + if strings.HasPrefix(ref, "refs/heads") { + c, ok := e.refs[ref] + if !ok { + return "", "", fmt.Errorf("ref %s not found", ref) + } + return ref, c, nil + } else { + ref2 := "refs/heads/" + ref + c, ok := e.refs[ref2] + if ok { + return ref2, c, nil + } + ref2 = "refs/tags/" + ref + c, ok = e.refs[ref2] + if ok { + return ref2, c, nil + } + return "", "", fmt.Errorf("ref %s not found", ref) + } +} + +func (e *CacheEntry) GetClonedDir(ref string) (string, git.CheckoutInfo, error) { + e.updateMutex.Lock() + defer e.updateMutex.Unlock() + + err := e.mr.Lock() + if err != nil { + return "", git.CheckoutInfo{}, err + } + defer e.mr.Unlock() + + if ref == "" { + ref = e.defaultRef + } + + ref2, commit, err := e.findCommit(ref) + if err != nil { + return "", git.CheckoutInfo{}, err + } + + tmpDir := filepath.Join(utils.GetTmpBaseDir(), "git-cloned") + err = os.MkdirAll(tmpDir, 0700) + if err != nil { + return "", git.CheckoutInfo{}, err + } + + url := e.mr.Url() + repoName := path.Base(url.Normalize().Path) + "-" + if ref == "" { + repoName += "HEAD-" + } else { + repoName += ref + "-" + } + repoName = strings.ReplaceAll(repoName, "/", "-") + + p, err := ioutil.TempDir(tmpDir, repoName) + if err != nil { + return "", git.CheckoutInfo{}, err + } + + e.rp.cleeanupDirsMutex.Lock() + e.rp.cleanupDirs = append(e.rp.cleanupDirs, p) + e.rp.cleeanupDirsMutex.Unlock() + + err = e.mr.CloneProjectByCommit(commit, p) + if err != nil { + return "", git.CheckoutInfo{}, err + } + + repoInfo, err := git.GetCheckoutInfo(p) + if err != nil { + return "", git.CheckoutInfo{}, err + } + + repoInfo.CheckedOutRef = ref2 + + e.clonedDirs[ref] = clonedDir{ + dir: p, + info: repoInfo, + } + return p, repoInfo, nil +} diff --git a/pkg/git/repoprovider/live.go b/pkg/git/repoprovider/live.go deleted file mode 100644 index bb0243f9a..000000000 --- a/pkg/git/repoprovider/live.go +++ /dev/null @@ -1,211 +0,0 @@ -package repoprovider - -import ( - "context" - "fmt" - "github.com/kluctl/kluctl/v2/pkg/git" - "github.com/kluctl/kluctl/v2/pkg/git/auth" - git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" - "github.com/kluctl/kluctl/v2/pkg/utils" - "io/ioutil" - "os" - "path" - "path/filepath" - "sync" - "time" -) - -type LiveRepoProvider struct { - ctx context.Context - authProviders *auth.GitAuthProviders - updateInterval time.Duration - repos map[string]*entry - reposMutex sync.Mutex - - cleanupDirs []string - cleeanupDirsMutex sync.Mutex -} - -type entry struct { - mr *git.MirroredGitRepo - clonedDirs map[string]clonedDir - updateMutex sync.Mutex -} - -type clonedDir struct { - dir string - info git.CheckoutInfo -} - -func NewLiveRepoProvider(ctx context.Context, authProviders *auth.GitAuthProviders, updateInterval time.Duration) RepoProvider { - return &LiveRepoProvider{ - ctx: ctx, - authProviders: authProviders, - updateInterval: updateInterval, - repos: map[string]*entry{}, - } -} - -func (rp *LiveRepoProvider) UnlockAll() { - rp.reposMutex.Lock() - defer rp.reposMutex.Unlock() - - for _, e := range rp.repos { - if e.mr.IsLocked() { - _ = e.mr.Unlock() - } - } - - rp.repos = map[string]*entry{} -} - -func (rp *LiveRepoProvider) Clear() { - rp.UnlockAll() - - rp.cleeanupDirsMutex.Lock() - defer rp.cleeanupDirsMutex.Unlock() - - for _, p := range rp.cleanupDirs { - _ = os.RemoveAll(p) - } - rp.cleanupDirs = nil -} - -func (rp *LiveRepoProvider) getEntry(url git_url.GitUrl, allowCreate bool, lockRepo bool, update bool) (*entry, error) { - e, err := func() (*entry, error) { - rp.reposMutex.Lock() - defer rp.reposMutex.Unlock() - - e, ok := rp.repos[url.NormalizedRepoKey()] - if !ok { - if !allowCreate { - return nil, fmt.Errorf("git repo %s not found", url.NormalizedRepoKey()) - } - mr, err := git.NewMirroredGitRepo(rp.ctx, url) - if err != nil { - return nil, err - } - e = &entry{ - mr: mr, - clonedDirs: map[string]clonedDir{}, - } - rp.repos[url.NormalizedRepoKey()] = e - - if lockRepo { - err = e.mr.Lock() - if err != nil { - return nil, err - } - } - } - return e, nil - }() - if err != nil { - return nil, err - } - - e.updateMutex.Lock() - defer e.updateMutex.Unlock() - - if update && !e.mr.HasUpdated() { - if time.Now().Sub(e.mr.LastUpdateTime()) <= rp.updateInterval { - e.mr.SetUpdated(true) - } else { - err = e.mr.Update(rp.authProviders) - if err != nil { - return nil, err - } - } - } - - return e, nil -} - -func (e *entry) getRepoInfo() (RepoInfo, error) { - remoteRefs, err := e.mr.RemoteRefHashesMap() - if err != nil { - return RepoInfo{}, err - } - - defaultRef, err := e.mr.DefaultRef() - if err != nil { - return RepoInfo{}, err - } - - info := RepoInfo{ - Url: e.mr.Url(), - RemoteRefs: remoteRefs, - DefaultRef: defaultRef, - } - - return info, nil -} - -func (rp *LiveRepoProvider) GetRepoInfo(url git_url.GitUrl) (RepoInfo, error) { - e, err := rp.getEntry(url, true, true, true) - if err != nil { - return RepoInfo{}, err - } - - return e.getRepoInfo() -} - -func (rp *LiveRepoProvider) GetClonedDir(url git_url.GitUrl, ref string) (string, git.CheckoutInfo, error) { - e, err := rp.getEntry(url, true, true, true) - if err != nil { - return "", git.CheckoutInfo{}, err - } - - if ref == "" { - ref, err = e.mr.DefaultRef() - if err != nil { - return "", git.CheckoutInfo{}, err - } - ref = path.Base(ref) - } - - e.updateMutex.Lock() - defer e.updateMutex.Unlock() - - cd, ok := e.clonedDirs[ref] - if ok { - return cd.dir, cd.info, nil - } - - tmpDir := filepath.Join(utils.GetTmpBaseDir(), "git-cloned") - err = os.MkdirAll(tmpDir, 0700) - if err != nil { - return "", git.CheckoutInfo{}, err - } - - repoName := path.Base(url.Normalize().Path) + "-" - if ref == "" { - repoName += "HEAD-" - } else { - repoName += ref + "-" - } - p, err := ioutil.TempDir(tmpDir, repoName) - if err != nil { - return "", git.CheckoutInfo{}, err - } - - rp.cleeanupDirsMutex.Lock() - rp.cleanupDirs = append(rp.cleanupDirs, p) - rp.cleeanupDirsMutex.Unlock() - - err = e.mr.CloneProject(ref, p) - if err != nil { - return "", git.CheckoutInfo{}, err - } - - repoInfo, err := git.GetCheckoutInfo(p) - if err != nil { - return "", git.CheckoutInfo{}, err - } - - e.clonedDirs[ref] = clonedDir{ - dir: p, - info: repoInfo, - } - return p, repoInfo, nil -} diff --git a/pkg/git/repoprovider/repoprovider.go b/pkg/git/repoprovider/repoprovider.go deleted file mode 100644 index a6427551a..000000000 --- a/pkg/git/repoprovider/repoprovider.go +++ /dev/null @@ -1,19 +0,0 @@ -package repoprovider - -import ( - "github.com/kluctl/kluctl/v2/pkg/git" - git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" -) - -type RepoInfo struct { - Url git_url.GitUrl `yaml:"url"` - RemoteRefs map[string]string `yaml:"remoteRefs"` - DefaultRef string `yaml:"defaultRef"` -} - -type RepoProvider interface { - GetRepoInfo(url git_url.GitUrl) (RepoInfo, error) - GetClonedDir(url git_url.GitUrl, ref string) (string, git.CheckoutInfo, error) - UnlockAll() - Clear() -} diff --git a/pkg/kluctl_project/git.go b/pkg/kluctl_project/git.go index 31e5678b8..12e7253e5 100644 --- a/pkg/kluctl_project/git.go +++ b/pkg/kluctl_project/git.go @@ -25,7 +25,7 @@ func (c *LoadedKluctlProject) updateGitCaches() error { go func() { defer waitGroup.Done() - _, err := c.RP.GetRepoInfo(u) + _, err := c.RP.GetEntry(u) if err != nil { doError(fmt.Errorf("failed to update git project %v: %v", u.String(), err)) } diff --git a/pkg/kluctl_project/project.go b/pkg/kluctl_project/project.go index ee616243a..da067d70f 100644 --- a/pkg/kluctl_project/project.go +++ b/pkg/kluctl_project/project.go @@ -3,7 +3,7 @@ package kluctl_project import ( "context" "fmt" - "github.com/kluctl/kluctl/v2/pkg/git/repoprovider" + "github.com/kluctl/kluctl/v2/pkg/git/repocache" "github.com/kluctl/kluctl/v2/pkg/jinja2" types2 "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" @@ -28,7 +28,7 @@ type LoadedKluctlProject struct { DynamicTargets []*types2.DynamicTarget J2 *jinja2.Jinja2 - RP repoprovider.RepoProvider + RP *repocache.GitRepoCache warnOnce utils.OnceByKey } diff --git a/pkg/kluctl_project/project_load.go b/pkg/kluctl_project/project_load.go index a409b3de7..3a2d1e730 100644 --- a/pkg/kluctl_project/project_load.go +++ b/pkg/kluctl_project/project_load.go @@ -3,7 +3,7 @@ package kluctl_project import ( "fmt" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" - "github.com/kluctl/kluctl/v2/pkg/git/repoprovider" + "github.com/kluctl/kluctl/v2/pkg/git/repocache" "github.com/kluctl/kluctl/v2/pkg/status" types2 "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" @@ -25,7 +25,7 @@ type LoadKluctlProjectArgs struct { LocalDeployment string LocalSealedSecrets string - RP repoprovider.RepoProvider + RP *repocache.GitRepoCache ClientConfigGetter func(context *string) (*rest.Config, *api.Config, error) } @@ -56,7 +56,12 @@ func (c *LoadedKluctlProject) localProject(dir string) gitProjectInfo { } func (c *LoadedKluctlProject) loadGitProject(gitProject *types2.GitProject, defaultSubDir string) (ret gitProjectInfo, err error) { - cloneDir, ri, err := c.RP.GetClonedDir(gitProject.Url, gitProject.Ref) + ge, err := c.RP.GetEntry(gitProject.Url) + if err != nil { + return + } + + cloneDir, ri, err := ge.GetClonedDir(gitProject.Ref) if err != nil { return } diff --git a/pkg/kluctl_project/targets.go b/pkg/kluctl_project/targets.go index 795424ad4..f51688a38 100644 --- a/pkg/kluctl_project/targets.go +++ b/pkg/kluctl_project/targets.go @@ -138,11 +138,13 @@ func (c *LoadedKluctlProject) prepareDynamicTargetsSimple(baseTarget *types.Targ } func (c *LoadedKluctlProject) prepareDynamicTargetsExternal(baseTarget *types.Target) ([]*dynamicTargetInfo, error) { - repoInfo, err := c.RP.GetRepoInfo(baseTarget.TargetConfig.Project.Url) + ge, err := c.RP.GetEntry(baseTarget.TargetConfig.Project.Url) if err != nil { return nil, err } + repoInfo := ge.GetRepoInfo() + if baseTarget.TargetConfig.Ref != nil && baseTarget.TargetConfig.RefPattern != nil { return nil, fmt.Errorf("'refPattern' and 'ref' can't be specified together") } @@ -178,7 +180,12 @@ func (c *LoadedKluctlProject) prepareDynamicTargetsExternal(baseTarget *types.Ta continue } - cloneDir, _, err := c.RP.GetClonedDir(baseTarget.TargetConfig.Project.Url, refShortName) + ge, err := c.RP.GetEntry(baseTarget.TargetConfig.Project.Url) + if err != nil { + return nil, err + } + + cloneDir, _, err := ge.GetClonedDir(refShortName) if err != nil { return nil, err } diff --git a/pkg/vars/vars_loader.go b/pkg/vars/vars_loader.go index 73a0780b1..022bd895c 100644 --- a/pkg/vars/vars_loader.go +++ b/pkg/vars/vars_loader.go @@ -5,7 +5,7 @@ import ( "encoding/base64" "fmt" securejoin "github.com/cyphar/filepath-securejoin" - "github.com/kluctl/kluctl/v2/pkg/git/repoprovider" + "github.com/kluctl/kluctl/v2/pkg/git/repocache" "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" @@ -27,13 +27,13 @@ type usernamePassword struct { type VarsLoader struct { ctx context.Context k *k8s.K8sCluster - rp repoprovider.RepoProvider + rp *repocache.GitRepoCache aws aws.AwsClientFactory credentialsCache map[string]usernamePassword } -func NewVarsLoader(ctx context.Context, k *k8s.K8sCluster, rp repoprovider.RepoProvider, aws aws.AwsClientFactory) *VarsLoader { +func NewVarsLoader(ctx context.Context, k *k8s.K8sCluster, rp *repocache.GitRepoCache, aws aws.AwsClientFactory) *VarsLoader { return &VarsLoader{ ctx: ctx, k: k, @@ -153,7 +153,12 @@ func (v *VarsLoader) loadVault(varsCtx *VarsCtx, source *types.VarsSource, rootK } func (v *VarsLoader) loadGit(varsCtx *VarsCtx, gitFile *types.VarsSourceGit, rootKey string) error { - clonedDir, _, err := v.rp.GetClonedDir(gitFile.Url, gitFile.Ref) + ge, err := v.rp.GetEntry(gitFile.Url) + if err != nil { + return err + } + + clonedDir, _, err := ge.GetClonedDir(gitFile.Ref) if err != nil { return fmt.Errorf("failed to load vars from git repository %s: %w", gitFile.Url.String(), err) } diff --git a/pkg/vars/vars_loader_test.go b/pkg/vars/vars_loader_test.go index 1dbb14018..94b95ebeb 100644 --- a/pkg/vars/vars_loader_test.go +++ b/pkg/vars/vars_loader_test.go @@ -7,7 +7,6 @@ import ( test_utils "github.com/kluctl/kluctl/v2/internal/test-utils" "github.com/kluctl/kluctl/v2/pkg/git/auth" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" - "github.com/kluctl/kluctl/v2/pkg/git/repoprovider" "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" @@ -37,8 +36,8 @@ func newTestDir(t *testing.T) string { return tmp } -func newRP(t *testing.T) repoprovider.RepoProvider { - grc := repoprovider.NewLiveRepoProvider(context.TODO(), auth.NewDefaultAuthProviders(), 0) +func newRP(t *testing.T) repocache.RepoProvider { + grc := repocache.NewLiveRepoProvider(context.TODO(), auth.NewDefaultAuthProviders(), 0) t.Cleanup(func() { grc.Clear() }) From 8dc84490abecdbb0682be983124247f684843e59 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 18 Jul 2022 15:20:48 +0200 Subject: [PATCH 0942/2916] fix: Fix compilation errors for tests --- pkg/vars/vars_loader_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/vars/vars_loader_test.go b/pkg/vars/vars_loader_test.go index 94b95ebeb..ad7ee5be0 100644 --- a/pkg/vars/vars_loader_test.go +++ b/pkg/vars/vars_loader_test.go @@ -7,6 +7,7 @@ import ( test_utils "github.com/kluctl/kluctl/v2/internal/test-utils" "github.com/kluctl/kluctl/v2/pkg/git/auth" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" + "github.com/kluctl/kluctl/v2/pkg/git/repocache" "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" @@ -36,8 +37,8 @@ func newTestDir(t *testing.T) string { return tmp } -func newRP(t *testing.T) repocache.RepoProvider { - grc := repocache.NewLiveRepoProvider(context.TODO(), auth.NewDefaultAuthProviders(), 0) +func newRP(t *testing.T) *repocache.GitRepoCache { + grc := repocache.NewGitRepoCache(context.TODO(), auth.NewDefaultAuthProviders(), 0) t.Cleanup(func() { grc.Clear() }) From b5bcb45f5ec882b177e0190cf4829f236dff723c Mon Sep 17 00:00:00 2001 From: Christian Neufeld <74351567+CNeufeldCoder@users.noreply.github.com> Date: Wed, 20 Jul 2022 12:14:32 +0200 Subject: [PATCH 0943/2916] fix: Use original stderr file for simple status handler (#44) Co-authored-by: Christian Neufeld --- cmd/kluctl/commands/root.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index 435c6e67d..06a9321e8 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -75,6 +75,8 @@ var didSetupStatusHandler bool func setupStatusHandler(debug bool) { didSetupStatusHandler = true + origStderr := os.Stderr + // we must determine isTerminal before we override os.Stderr isTerminal := isatty.IsTerminal(os.Stderr.Fd()) var sh status.StatusHandler @@ -82,7 +84,7 @@ func setupStatusHandler(debug bool) { sh = status.NewMultiLineStatusHandler(cliCtx, os.Stderr, isTerminal, false) } else { sh = status.NewSimpleStatusHandler(func(message string) { - _, _ = fmt.Fprintf(os.Stderr, "%s\n", message) + _, _ = fmt.Fprintf(origStderr, "%s\n", message) }, isTerminal, false) } sh.SetTrace(debug) From c68720856223536505a5981a14b2fd750efb9808 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 20 Jul 2022 16:06:20 +0200 Subject: [PATCH 0944/2916] tests: Upgrade sealed-secrets operator used in tests --- e2e/test_resources/sealed-secrets.yaml | 38 +++++++++++++------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/e2e/test_resources/sealed-secrets.yaml b/e2e/test_resources/sealed-secrets.yaml index b45788937..8e2262447 100644 --- a/e2e/test_resources/sealed-secrets.yaml +++ b/e2e/test_resources/sealed-secrets.yaml @@ -37,10 +37,10 @@ metadata: namespace: kube-system labels: app.kubernetes.io/name: sealed-secrets - helm.sh/chart: sealed-secrets-2.1.8 + helm.sh/chart: sealed-secrets-2.4.0 app.kubernetes.io/instance: sealed-secrets-controller app.kubernetes.io/managed-by: Helm - app.kubernetes.io/version: v0.17.5 + app.kubernetes.io/version: v0.18.1 --- # Source: sealed-secrets/templates/cluster-role.yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -49,10 +49,10 @@ metadata: name: secrets-unsealer labels: app.kubernetes.io/name: sealed-secrets - helm.sh/chart: sealed-secrets-2.1.8 + helm.sh/chart: sealed-secrets-2.4.0 app.kubernetes.io/instance: sealed-secrets-controller app.kubernetes.io/managed-by: Helm - app.kubernetes.io/version: v0.17.5 + app.kubernetes.io/version: v0.18.1 rules: - apiGroups: - bitnami.com @@ -93,10 +93,10 @@ metadata: name: sealed-secrets-controller labels: app.kubernetes.io/name: sealed-secrets - helm.sh/chart: sealed-secrets-2.1.8 + helm.sh/chart: sealed-secrets-2.4.0 app.kubernetes.io/instance: sealed-secrets-controller app.kubernetes.io/managed-by: Helm - app.kubernetes.io/version: v0.17.5 + app.kubernetes.io/version: v0.18.1 roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole @@ -115,10 +115,10 @@ metadata: namespace: kube-system labels: app.kubernetes.io/name: sealed-secrets - helm.sh/chart: sealed-secrets-2.1.8 + helm.sh/chart: sealed-secrets-2.4.0 app.kubernetes.io/instance: sealed-secrets-controller app.kubernetes.io/managed-by: Helm - app.kubernetes.io/version: v0.17.5 + app.kubernetes.io/version: v0.18.1 rules: - apiGroups: - "" @@ -144,10 +144,10 @@ metadata: namespace: kube-system labels: app.kubernetes.io/name: sealed-secrets - helm.sh/chart: sealed-secrets-2.1.8 + helm.sh/chart: sealed-secrets-2.4.0 app.kubernetes.io/instance: sealed-secrets-controller app.kubernetes.io/managed-by: Helm - app.kubernetes.io/version: v0.17.5 + app.kubernetes.io/version: v0.18.1 rules: - apiGroups: - "" @@ -177,10 +177,10 @@ metadata: namespace: kube-system labels: app.kubernetes.io/name: sealed-secrets - helm.sh/chart: sealed-secrets-2.1.8 + helm.sh/chart: sealed-secrets-2.4.0 app.kubernetes.io/instance: sealed-secrets-controller app.kubernetes.io/managed-by: Helm - app.kubernetes.io/version: v0.17.5 + app.kubernetes.io/version: v0.18.1 roleRef: apiGroup: rbac.authorization.k8s.io kind: Role @@ -199,10 +199,10 @@ metadata: namespace: kube-system labels: app.kubernetes.io/name: sealed-secrets - helm.sh/chart: sealed-secrets-2.1.8 + helm.sh/chart: sealed-secrets-2.4.0 app.kubernetes.io/instance: sealed-secrets-controller app.kubernetes.io/managed-by: Helm - app.kubernetes.io/version: v0.17.5 + app.kubernetes.io/version: v0.18.1 roleRef: apiGroup: rbac.authorization.k8s.io kind: Role @@ -220,10 +220,10 @@ metadata: namespace: kube-system labels: app.kubernetes.io/name: sealed-secrets - helm.sh/chart: sealed-secrets-2.1.8 + helm.sh/chart: sealed-secrets-2.4.0 app.kubernetes.io/instance: sealed-secrets-controller app.kubernetes.io/managed-by: Helm - app.kubernetes.io/version: v0.17.5 + app.kubernetes.io/version: v0.18.1 spec: type: ClusterIP ports: @@ -243,10 +243,10 @@ metadata: namespace: kube-system labels: app.kubernetes.io/name: sealed-secrets - helm.sh/chart: sealed-secrets-2.1.8 + helm.sh/chart: sealed-secrets-2.4.0 app.kubernetes.io/instance: sealed-secrets-controller app.kubernetes.io/managed-by: Helm - app.kubernetes.io/version: v0.17.5 + app.kubernetes.io/version: v0.18.1 spec: selector: matchLabels: @@ -269,7 +269,7 @@ spec: - --update-status - --key-prefix - "sealed-secrets-key" - image: docker.io/bitnami/sealed-secrets-controller:v0.17.5 + image: docker.io/bitnami/sealed-secrets-controller:v0.18.1 imagePullPolicy: IfNotPresent ports: - containerPort: 8080 From 8d605c4d1de642ee72f56903d00b20dce965dba1 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 20 Jul 2022 19:46:14 +0200 Subject: [PATCH 0945/2916] tests: Always use --debug for e2e tests --- e2e/project.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/e2e/project.go b/e2e/project.go index d80c0c3e4..be0bd2823 100644 --- a/e2e/project.go +++ b/e2e/project.go @@ -13,7 +13,6 @@ import ( "os/exec" "path/filepath" "reflect" - "runtime" "strings" "testing" ) @@ -430,9 +429,8 @@ func (p *testProject) Kluctl(argsIn ...string) (string, string, error) { args = append(args, "--local-sealed-secrets", *p.localSealedSecrets) } - if runtime.GOOS == "windows" { - args = append(args, "--debug") - } + args = append(args, "--debug") + env := os.Environ() env = append(env, p.extraEnv...) env = append(env, fmt.Sprintf("KUBECONFIG=%s", p.mergedKubeconfig)) From 16e3b6869c21a14fbeef0c626d5c17338374e0f1 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 20 Jul 2022 19:46:55 +0200 Subject: [PATCH 0946/2916] chore: Write failing yaml to status handler when tracing is enabled --- pkg/deployment/utils/apply_utils.go | 7 +++++++ pkg/status/noop.go | 4 ++++ pkg/status/simple_status_handler.go | 4 ++++ pkg/status/status.go | 6 ++++++ pkg/status/status_handler.go | 4 ++++ 5 files changed, 25 insertions(+) diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index eb6cded06..c241ff6ba 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -13,6 +13,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/validation" + "github.com/kluctl/kluctl/v2/pkg/yaml" "golang.org/x/sync/semaphore" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" @@ -388,6 +389,12 @@ func (a *ApplyUtil) WaitReadiness(ref k8s2.ObjectRef, timeout time.Duration) boo case <-timeoutTimer.C: err := fmt.Errorf("timed out while waiting for readiness of %s", ref.String()) status.Warning(a.ctx, "%s (%ds elapsed)", err.Error(), elapsed) + if status.IsTraceEnabled(a.ctx) { + y, err := yaml.WriteYamlString(o) + if err == nil { + status.Trace(a.ctx, "yaml:\n"+y) + } + } a.HandleError(ref, err) return false case <-a.ctx.Done(): diff --git a/pkg/status/noop.go b/pkg/status/noop.go index 53f9f7a51..b33511b8b 100644 --- a/pkg/status/noop.go +++ b/pkg/status/noop.go @@ -12,6 +12,10 @@ func (n NoopStatusHandler) IsTerminal() bool { return false } +func (n NoopStatusHandler) IsTraceEnabled() bool { + return false +} + func (n NoopStatusHandler) SetTrace(trace bool) { } diff --git a/pkg/status/simple_status_handler.go b/pkg/status/simple_status_handler.go index cdddd374c..1a330c36b 100644 --- a/pkg/status/simple_status_handler.go +++ b/pkg/status/simple_status_handler.go @@ -28,6 +28,10 @@ func (s *simpleStatusHandler) IsTerminal() bool { return s.isTerminal } +func (s *simpleStatusHandler) IsTraceEnabled() bool { + return s.trace +} + func (s *simpleStatusHandler) SetTrace(trace bool) { s.trace = trace } diff --git a/pkg/status/status.go b/pkg/status/status.go index 875e03391..c49f64dfc 100644 --- a/pkg/status/status.go +++ b/pkg/status/status.go @@ -38,6 +38,7 @@ type StatusLine interface { type StatusHandler interface { IsTerminal() bool + IsTraceEnabled() bool SetTrace(trace bool) Stop() @@ -234,6 +235,11 @@ func Trace(ctx context.Context, status string, args ...any) { slh.Trace(fmt.Sprintf(status, args...)) } +func IsTraceEnabled(ctx context.Context) bool { + slh := FromContext(ctx) + return slh.IsTraceEnabled() +} + func Error(ctx context.Context, status string, args ...any) { slh := FromContext(ctx) slh.Error(fmt.Sprintf(status, args...)) diff --git a/pkg/status/status_handler.go b/pkg/status/status_handler.go index ad9491bad..7b5a85a82 100644 --- a/pkg/status/status_handler.go +++ b/pkg/status/status_handler.go @@ -47,6 +47,10 @@ func (s *MultiLineStatusHandler) IsTerminal() bool { return s.isTerminal } +func (s *MultiLineStatusHandler) IsTraceEnabled() bool { + return s.trace +} + func (s *MultiLineStatusHandler) Flush() { s.Stop() s.start() From d61a257271b7eea0ee7aa6829f09d24428245c6e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 22 Jul 2022 12:11:42 +0200 Subject: [PATCH 0947/2916] fix: Fix race condition in ApplyDeploymentsUtil when creating new ApplyUtils --- pkg/deployment/utils/apply_utils.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index c241ff6ba..a14b1528a 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -65,7 +65,8 @@ type ApplyDeploymentsUtil struct { abortSignal atomic.Value - results []*ApplyUtil + resultsMutex sync.Mutex + results []*ApplyUtil } func NewApplyDeploymentsUtil(ctx context.Context, dew *DeploymentErrorsAndWarnings, deployments []*deployment.DeploymentItem, ru *RemoteObjectUtils, k *k8s.K8sCluster, o *ApplyUtilOptions) *ApplyDeploymentsUtil { @@ -82,6 +83,9 @@ func NewApplyDeploymentsUtil(ctx context.Context, dew *DeploymentErrorsAndWarnin } func (ad *ApplyDeploymentsUtil) NewApplyUtil(ctx context.Context, statusCtx *status.StatusContext) *ApplyUtil { + ad.resultsMutex.Lock() + defer ad.resultsMutex.Unlock() + ret := &ApplyUtil{ ctx: ctx, dew: ad.dew, @@ -651,6 +655,9 @@ func (a *ApplyUtil) ReplaceObject(ref k8s2.ObjectRef, firstVersion *uo.Unstructu } func (ad *ApplyDeploymentsUtil) GetAppliedObjects() []*types.RefAndObject { + ad.resultsMutex.Lock() + defer ad.resultsMutex.Unlock() + var ret []*types.RefAndObject for _, a := range ad.results { for _, o := range a.appliedObjects { @@ -672,6 +679,9 @@ func (ad *ApplyDeploymentsUtil) GetAppliedObjectsMap() map[k8s2.ObjectRef]*uo.Un } func (ad *ApplyDeploymentsUtil) GetAppliedHookObjects() []*types.RefAndObject { + ad.resultsMutex.Lock() + defer ad.resultsMutex.Unlock() + var ret []*types.RefAndObject for _, a := range ad.results { for _, o := range a.appliedHookObjects { @@ -685,6 +695,9 @@ func (ad *ApplyDeploymentsUtil) GetAppliedHookObjects() []*types.RefAndObject { } func (ad *ApplyDeploymentsUtil) GetDeletedObjects() []k8s2.ObjectRef { + ad.resultsMutex.Lock() + defer ad.resultsMutex.Unlock() + var ret []k8s2.ObjectRef m := make(map[k8s2.ObjectRef]bool) for _, a := range ad.results { From 7d5c42fbee21cdd8f99b2e15b0d9aad95444a42e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 25 Jul 2022 12:06:25 +0200 Subject: [PATCH 0948/2916] feat: Implement --cpu-profile --- cmd/kluctl/commands/root.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index 06a9321e8..7e3928ac8 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -33,6 +33,7 @@ import ( "net/http" "os" "path/filepath" + "runtime/pprof" "strings" "time" ) @@ -43,6 +44,8 @@ type cli struct { Debug bool `group:"global" help:"Enable debug logging"` NoUpdateCheck bool `group:"global" help:"Disable update check on startup"` + CpuProfile string `group:"global" help:"Enable CPU profiling and write the result to the given path"` + CheckImageUpdates checkImageUpdatesCmd `cmd:"" help:"Render deployment and check if any images have new tags available"` Delete deleteCmd `cmd:"" help:"Delete a target (or parts of it) from the corresponding cluster"` Deploy deployCmd `cmd:"" help:"Deploys a target to the corresponding cluster"` @@ -107,6 +110,23 @@ func setupStatusHandler(debug bool) { os.Stderr = pw } +var cpuProfileFile *os.File + +func setupProfiling(cpuProfile string) error { + var err error + if cpuProfile != "" { + cpuProfileFile, err = os.Create(cpuProfile) + if err != nil { + return fmt.Errorf("failed to create cpu profile file: %w", err) + } + err = pprof.StartCPUProfile(cpuProfileFile) + if err != nil { + return fmt.Errorf("failed to start cpu profiling: %w", err) + } + } + return nil +} + type VersionCheckState struct { LastVersionCheck time.Time `yaml:"lastVersionCheck"` } @@ -164,6 +184,10 @@ func (c *cli) checkNewVersion() { } func (c *cli) preRun() error { + err := setupProfiling(c.CpuProfile) + if err != nil { + return err + } setupStatusHandler(c.Debug) c.checkNewVersion() return nil @@ -221,6 +245,12 @@ composed of multiple smaller parts (Helm/Kustomize/...) in a manageable and unif setupStatusHandler(false) } + if cpuProfileFile != nil { + pprof.StopCPUProfile() + _ = cpuProfileFile.Close() + cpuProfileFile = nil + } + sh := status.FromContext(cliCtx) if err != nil { From 81d01d797f64f203eece52e65435ad85222a0d99 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 22 Jul 2022 15:12:36 +0200 Subject: [PATCH 0949/2916] refactor: Move loading of target-config.yml into own function --- pkg/kluctl_project/targets.go | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/pkg/kluctl_project/targets.go b/pkg/kluctl_project/targets.go index f51688a38..eb09429e5 100644 --- a/pkg/kluctl_project/targets.go +++ b/pkg/kluctl_project/targets.go @@ -9,6 +9,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/vars" "github.com/kluctl/kluctl/v2/pkg/yaml" + "os" "reflect" "regexp" "sort" @@ -267,6 +268,22 @@ func (c *LoadedKluctlProject) cloneDynamicTargets(dynamicTargets []*dynamicTarge return nil } +func (c *LoadedKluctlProject) loadTargetConfigFile(targetInfo *dynamicTargetInfo) ([]byte, error) { + configFile := yaml.FixNameExt(targetInfo.dir, "target-config.yml") + if targetInfo.baseTarget.TargetConfig.File != nil { + configFile = *targetInfo.baseTarget.TargetConfig.File + } + configPath, err := securejoin.SecureJoin(targetInfo.dir, configFile) + if err != nil { + return nil, err + } + if !utils.IsFile(configPath) { + return nil, fmt.Errorf("no target config file with name %s found in target", configFile) + } + + return os.ReadFile(configPath) +} + func (c *LoadedKluctlProject) buildDynamicTarget(targetInfo *dynamicTargetInfo) (*types.Target, error) { var target types.Target err := utils.DeepCopy(&target, targetInfo.baseTarget) @@ -277,20 +294,13 @@ func (c *LoadedKluctlProject) buildDynamicTarget(targetInfo *dynamicTargetInfo) return &target, nil } - configFile := yaml.FixNameExt(targetInfo.dir, "target-config.yml") - if targetInfo.baseTarget.TargetConfig.File != nil { - configFile = *targetInfo.baseTarget.TargetConfig.File - } - configPath, err := securejoin.SecureJoin(targetInfo.dir, configFile) + configFile, err := c.loadTargetConfigFile(targetInfo) if err != nil { return nil, err } - if !utils.IsFile(configPath) { - return nil, fmt.Errorf("no target config file with name %s found in target", configFile) - } var targetConfig types.TargetConfig - err = yaml.ReadYamlFile(configPath, &targetConfig) + err = yaml.ReadYamlBytes(configFile, &targetConfig) if err != nil { return nil, err } From 6ebef19a621e02c6dc2a0d8d6235b46d3ba0057d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 25 Jul 2022 10:33:23 +0200 Subject: [PATCH 0950/2916] chore: Run go mod tidy --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index ca9a9d8ec..54167b996 100644 --- a/go.mod +++ b/go.mod @@ -42,6 +42,7 @@ require ( golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 golang.org/x/text v0.3.7 + gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 helm.sh/helm/v3 v3.9.0 k8s.io/api v0.24.1 @@ -206,7 +207,6 @@ require ( gopkg.in/ini.v1 v1.66.6 // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/apiserver v0.24.1 // indirect k8s.io/cli-runtime v0.24.1 // indirect k8s.io/component-base v0.24.1 // indirect From 10c2380832efb554315b73efca4b635a40ac3537 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 25 Jul 2022 11:35:51 +0200 Subject: [PATCH 0951/2916] fix: Speed up loading of projects with many dynamic targets by avoiding clones So far, kluctl has checked out all branches for all dynamic targets in parallel just to load the target-config.yaml from that branch. This commit changes this to only load the file object from the git tree. --- pkg/git/mirrored_repo.go | 28 +++--------- pkg/git/repocache/cache.go | 23 ++++++++++ pkg/kluctl_project/targets.go | 85 +++++++++++++++++------------------ 3 files changed, 68 insertions(+), 68 deletions(-) diff --git a/pkg/git/mirrored_repo.go b/pkg/git/mirrored_repo.go index 5a48414aa..221113f6f 100644 --- a/pkg/git/mirrored_repo.go +++ b/pkg/git/mirrored_repo.go @@ -357,12 +357,12 @@ func (g *MirroredGitRepo) CloneProjectByCommit(commit string, targetDir string) return nil } -func (g *MirroredGitRepo) ReadFile(ref string, path string) ([]byte, error) { +func (g *MirroredGitRepo) GetGitTreeByCommit(commitHash string) (*object.Tree, error) { if !g.IsLocked() || !g.hasUpdated { panic("tried to read a file from a project that is not locked/updated") } - doError := func(err error) ([]byte, error) { + doError := func(err error) (*object.Tree, error) { return nil, fmt.Errorf("failed to read file from git repostory: %w", err) } @@ -371,16 +371,9 @@ func (g *MirroredGitRepo) ReadFile(ref string, path string) ([]byte, error) { return nil, err } - if ref == "" { - ref = "HEAD" - } + h := plumbing.NewHash(commitHash) - h, err := r.ResolveRevision(plumbing.Revision(ref)) - if err != nil { - return doError(err) - } - - commit, err := object.GetCommit(r.Storer, *h) + commit, err := object.GetCommit(r.Storer, h) if err != nil { return doError(err) } @@ -388,18 +381,7 @@ func (g *MirroredGitRepo) ReadFile(ref string, path string) ([]byte, error) { if err != nil { return doError(err) } - - f, err := tree.File(path) - if err != nil { - return doError(err) - } - reader, err := f.Reader() - if err != nil { - return doError(err) - } - defer reader.Close() - - return ioutil.ReadAll(reader) + return tree, nil } func buildMirrorRepoName(u git_url.GitUrl) string { diff --git a/pkg/git/repocache/cache.go b/pkg/git/repocache/cache.go index 34d3ebcef..3d34e2ec8 100644 --- a/pkg/git/repocache/cache.go +++ b/pkg/git/repocache/cache.go @@ -3,6 +3,7 @@ package repocache import ( "context" "fmt" + "github.com/go-git/go-git/v5/plumbing/object" "github.com/kluctl/kluctl/v2/pkg/git" "github.com/kluctl/kluctl/v2/pkg/git/auth" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" @@ -221,3 +222,25 @@ func (e *CacheEntry) GetClonedDir(ref string) (string, git.CheckoutInfo, error) } return p, repoInfo, nil } + +func (e *CacheEntry) GetGitTree(ref string) (*object.Tree, error) { + e.updateMutex.Lock() + defer e.updateMutex.Unlock() + + err := e.mr.Lock() + if err != nil { + return nil, err + } + defer e.mr.Unlock() + + if ref == "" { + ref = e.defaultRef + } + + _, commit, err := e.findCommit(ref) + if err != nil { + return nil, err + } + + return e.mr.GetGitTreeByCommit(commit) +} diff --git a/pkg/kluctl_project/targets.go b/pkg/kluctl_project/targets.go index eb09429e5..8579a1d7c 100644 --- a/pkg/kluctl_project/targets.go +++ b/pkg/kluctl_project/targets.go @@ -3,23 +3,25 @@ package kluctl_project import ( "fmt" securejoin "github.com/cyphar/filepath-securejoin" + "github.com/go-git/go-git/v5/plumbing/object" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/vars" "github.com/kluctl/kluctl/v2/pkg/yaml" + "io" "os" "reflect" "regexp" "sort" "strings" - "sync" ) type dynamicTargetInfo struct { baseTarget *types.Target dir string + gitTree *object.Tree gitProject *types.GitProject ref *string refPattern *string @@ -42,11 +44,6 @@ func (c *LoadedKluctlProject) loadTargets() error { targetInfos = append(targetInfos, l...) } - err := c.cloneDynamicTargets(targetInfos) - if err != nil { - return err - } - for _, targetInfo := range targetInfos { target, err := c.buildDynamicTarget(targetInfo) if err != nil { @@ -186,14 +183,11 @@ func (c *LoadedKluctlProject) prepareDynamicTargetsExternal(baseTarget *types.Ta return nil, err } - cloneDir, _, err := ge.GetClonedDir(refShortName) - if err != nil { - return nil, err - } + gitTree, err := ge.GetGitTree(refShortName) dynamicTargets = append(dynamicTargets, &dynamicTargetInfo{ baseTarget: baseTarget, - dir: cloneDir, + gitTree: gitTree, gitProject: baseTarget.TargetConfig.Project, ref: &refShortName, refPattern: refPattern, @@ -228,47 +222,40 @@ func (c *LoadedKluctlProject) matchRef(s string, pattern string) (bool, string, } } -func (c *LoadedKluctlProject) cloneDynamicTargets(dynamicTargets []*dynamicTargetInfo) error { - uniqueClones := make(map[string]interface{}) - var mutex sync.Mutex - var wg sync.WaitGroup +func (c *LoadedKluctlProject) loadTargetConfigFileFromGit(targetInfo *dynamicTargetInfo) ([]byte, error) { + existsFunc := func(path string) bool { + e, err := targetInfo.gitTree.FindEntry(path) + if e == nil || err != nil { + return false + } + return true + } - for _, targetInfo_ := range dynamicTargets { - targetInfo := targetInfo_ + var configFile string - if targetInfo.gitProject == nil { - continue - } - mutex.Lock() - if _, ok := uniqueClones[targetInfo.dir]; ok { - mutex.Unlock() - continue + if targetInfo.baseTarget.TargetConfig.File != nil { + configFile = *targetInfo.baseTarget.TargetConfig.File + } else { + configFile = "target-config.yml" + if !existsFunc(configFile) { + configFile = "target-config.yaml" } - uniqueClones[targetInfo.dir] = nil - mutex.Unlock() - - wg.Add(1) - go func() { - defer wg.Done() - gitProject := *targetInfo.gitProject - gitProject.Ref = *targetInfo.ref - - gi, err := c.loadGitProject(&gitProject, "") - mutex.Lock() - defer mutex.Unlock() - if err != nil { - uniqueClones[targetInfo.dir] = err - } else { - uniqueClones[targetInfo.dir] = &gi - } - }() } - wg.Wait() - return nil + f, err := targetInfo.gitTree.File(configFile) + if err != nil { + return nil, fmt.Errorf("failed to load target config: %w", err) + } + r, err := f.Reader() + if err != nil { + return nil, fmt.Errorf("failed to load target config: %w", err) + } + defer r.Close() + + return io.ReadAll(r) } -func (c *LoadedKluctlProject) loadTargetConfigFile(targetInfo *dynamicTargetInfo) ([]byte, error) { +func (c *LoadedKluctlProject) loadTargetConfigFileFromLocal(targetInfo *dynamicTargetInfo) ([]byte, error) { configFile := yaml.FixNameExt(targetInfo.dir, "target-config.yml") if targetInfo.baseTarget.TargetConfig.File != nil { configFile = *targetInfo.baseTarget.TargetConfig.File @@ -284,6 +271,14 @@ func (c *LoadedKluctlProject) loadTargetConfigFile(targetInfo *dynamicTargetInfo return os.ReadFile(configPath) } +func (c *LoadedKluctlProject) loadTargetConfigFile(targetInfo *dynamicTargetInfo) ([]byte, error) { + if targetInfo.gitTree != nil { + return c.loadTargetConfigFileFromGit(targetInfo) + } else { + return c.loadTargetConfigFileFromLocal(targetInfo) + } +} + func (c *LoadedKluctlProject) buildDynamicTarget(targetInfo *dynamicTargetInfo) (*types.Target, error) { var target types.Target err := utils.DeepCopy(&target, targetInfo.baseTarget) From 931274540d593a6dd4534f3cdbcdd21e859a7f3e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 25 Jul 2022 11:36:12 +0200 Subject: [PATCH 0952/2916] tests: Add loadTargetConfigFile tests --- pkg/kluctl_project/targets_test.go | 65 ++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 pkg/kluctl_project/targets_test.go diff --git a/pkg/kluctl_project/targets_test.go b/pkg/kluctl_project/targets_test.go new file mode 100644 index 000000000..852a5f8cd --- /dev/null +++ b/pkg/kluctl_project/targets_test.go @@ -0,0 +1,65 @@ +package kluctl_project + +import ( + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing/object" + "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/stretchr/testify/assert" + "os" + "path/filepath" + "testing" +) + +func TestLoadTargetConfigFileFromLocal(t *testing.T) { + c := LoadedKluctlProject{} + ti := &dynamicTargetInfo{ + baseTarget: &types.Target{ + TargetConfig: &types.ExternalTargetConfig{}, + }, + dir: t.TempDir(), + } + err := os.WriteFile(filepath.Join(ti.dir, "target-config.yml"), []byte("test"), 0600) + assert.NoError(t, err) + + data, err := c.loadTargetConfigFile(ti) + assert.NoError(t, err) + + assert.Equal(t, []byte("test"), data) +} + +func TestLoadTargetConfigFileFromGit(t *testing.T) { + dir := t.TempDir() + r, err := git.PlainInit(dir, false) + assert.NoError(t, err) + + wt, err := r.Worktree() + assert.NoError(t, err) + + err = os.WriteFile(filepath.Join(dir, "target-config.yml"), []byte("test"), 0600) + assert.NoError(t, err) + + _, err = wt.Add("target-config.yml") + assert.NoError(t, err) + + h, err := wt.Commit("test", &git.CommitOptions{}) + assert.NoError(t, err) + + commit, err := object.GetCommit(r.Storer, h) + assert.NoError(t, err) + + gitTree, err := commit.Tree() + assert.NoError(t, err) + + c := LoadedKluctlProject{} + ti := &dynamicTargetInfo{ + baseTarget: &types.Target{ + TargetConfig: &types.ExternalTargetConfig{}, + }, + gitTree: gitTree, + } + + data, err := c.loadTargetConfigFile(ti) + assert.NoError(t, err) + + assert.Equal(t, []byte("test"), data) +} From ecdf1035cf6e981774f5d5929b0e1dfed0f8a1f3 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 25 Jul 2022 14:03:38 +0200 Subject: [PATCH 0953/2916] tests: Add LooseVersion tests for 'v' prefixes --- pkg/utils/versions/looseversion_test.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pkg/utils/versions/looseversion_test.go b/pkg/utils/versions/looseversion_test.go index 5d2bab86f..b25520f8d 100644 --- a/pkg/utils/versions/looseversion_test.go +++ b/pkg/utils/versions/looseversion_test.go @@ -79,3 +79,14 @@ func TestLooseVersionNoNums(t *testing.T) { checkLess(t, "-1", "-2") checkLess(t, "-1.1", "-1.2") } + +func TestLooseVersionVPrefix(t *testing.T) { + checkLess(t, "v1.0", "v1.1") + checkLess(t, "v2.0", "v2.1") + checkLess(t, "v1.0", "v2.0") + checkLess(t, "1.0", "v2.0") + checkLess(t, "v1.0suffix", "v1.0") + checkLess(t, "v1.0-suffix", "v1.0") + checkLess(t, "v1.0suffix1", "v1.0suffix2") + checkLess(t, "v1.0-suffix1", "v1.0-suffix2") +} From 552b22dfebfcd87758716e46d7a8ffbf8eace162 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 25 Jul 2022 14:03:59 +0200 Subject: [PATCH 0954/2916] fix: Also allow 'v' prefixes in semantic versions --- pkg/utils/versions/looseversion.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/utils/versions/looseversion.go b/pkg/utils/versions/looseversion.go index 11101ea03..0c6bbf529 100644 --- a/pkg/utils/versions/looseversion.go +++ b/pkg/utils/versions/looseversion.go @@ -8,7 +8,7 @@ import ( "strings" ) -var looseSemverRegex = regexp.MustCompile(`^(([0-9]+)(\.[0-9]+)*)?(.*)$`) +var looseSemverRegex = regexp.MustCompile(`^v?(([0-9]+)(\.[0-9]+)*)?(.*)$`) var suffixComponentRegex = regexp.MustCompile(`\d+|[a-zA-Z]+|\.`) // Allows to compare ints and strings. Strings are always considered less than ints. From 342b8bc4da85a9d585bf76c3098285bd7671a2cd Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 25 Jul 2022 14:09:56 +0200 Subject: [PATCH 0955/2916] tests: Fix TestLoadTargetConfigFileFromGit test Missed adding a commit author, which was not failing locally due to global git configs. --- pkg/kluctl_project/targets_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/kluctl_project/targets_test.go b/pkg/kluctl_project/targets_test.go index 852a5f8cd..628a69d4b 100644 --- a/pkg/kluctl_project/targets_test.go +++ b/pkg/kluctl_project/targets_test.go @@ -8,6 +8,7 @@ import ( "os" "path/filepath" "testing" + "time" ) func TestLoadTargetConfigFileFromLocal(t *testing.T) { @@ -41,7 +42,7 @@ func TestLoadTargetConfigFileFromGit(t *testing.T) { _, err = wt.Add("target-config.yml") assert.NoError(t, err) - h, err := wt.Commit("test", &git.CommitOptions{}) + h, err := wt.Commit("test", &git.CommitOptions{Author: &object.Signature{Name: "test", Email: "test@test.com", When: time.Now()}}) assert.NoError(t, err) commit, err := object.GetCommit(r.Storer, h) From 0a424702abe2e7b526da8cbdd21f1c6e69ba6747 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 26 Jul 2022 14:26:27 +0200 Subject: [PATCH 0956/2916] fix: Don't rely on yaml marshalling when rendering structs This should speed up rendering of structs as there is no need to marshal+unmarshal structs anymore. --- pkg/jinja2/jinja2.go | 55 ----------- pkg/jinja2/jinja2_renderer.go | 16 ++- pkg/jinja2/render_struct.go | 181 ++++++++++++++++++++++++++++++++++ pkg/kluctl_project/targets.go | 24 ++--- pkg/vars/vars_loader.go | 7 +- 5 files changed, 209 insertions(+), 74 deletions(-) create mode 100644 pkg/jinja2/render_struct.go diff --git a/pkg/jinja2/jinja2.go b/pkg/jinja2/jinja2.go index 1bc115aa7..2d433ec1a 100644 --- a/pkg/jinja2/jinja2.go +++ b/pkg/jinja2/jinja2.go @@ -123,61 +123,6 @@ func (j *Jinja2) RenderFile(template string, searchDirs []string, vars *uo.Unstr return *jobs[0].Result, nil } -func (j *Jinja2) RenderStruct(dst interface{}, src interface{}, vars *uo.UnstructuredObject) error { - m, err := uo.FromStruct(src) - if err != nil { - return err - } - - type pk struct { - parent interface{} - key interface{} - } - - var jobs []*RenderJob - var fields []pk - err = m.NewIterator().IterateLeafs(func(it *uo.ObjectIterator) error { - value := it.Value() - if s, ok := value.(string); ok { - jobs = append(jobs, &RenderJob{Template: s}) - fields = append(fields, pk{ - parent: it.Parent(), - key: it.Key(), - }) - } - return nil - }) - if err != nil { - return err - } - - err = j.RenderStrings(jobs, nil, vars) - if err != nil { - return err - } - - var errors []error - for i, j := range jobs { - if j.Error != nil { - errors = append(errors, j.Error) - } else { - err = uo.SetChild(fields[i].parent, fields[i].key, *j.Result) - if err != nil { - return err - } - } - } - if len(errors) != 0 { - return utils.NewErrorListOrNil(errors) - } - - err = m.ToStruct(dst) - if err != nil { - return err - } - return nil -} - func (j *Jinja2) getGlob(pattern string) (glob.Glob, error) { j.mutex.Lock() defer j.mutex.Unlock() diff --git a/pkg/jinja2/jinja2_renderer.go b/pkg/jinja2/jinja2_renderer.go index f29ec2312..8895f2366 100644 --- a/pkg/jinja2/jinja2_renderer.go +++ b/pkg/jinja2/jinja2_renderer.go @@ -86,9 +86,17 @@ func (j *pythonJinja2Renderer) Close() { } } -func (j *pythonJinja2Renderer) isMaybeTemplate(template string, searchDirs []string, isString bool) (bool, *string) { +func isMaybeTemplateString(template string) bool { + return strings.IndexRune(template, '{') != -1 +} + +func isMaybeTemplateBytes(template []byte) bool { + return bytes.IndexRune(template, '{') != -1 +} + +func isMaybeTemplate(template string, searchDirs []string, isString bool) (bool, *string) { if isString { - if strings.IndexRune(template, '{') == -1 { + if !isMaybeTemplateString(template) { return false, &template } } else { @@ -97,7 +105,7 @@ func (j *pythonJinja2Renderer) isMaybeTemplate(template string, searchDirs []str if err != nil { continue } - if bytes.IndexRune(b, '{') == -1 { + if !isMaybeTemplateBytes(b) { x := string(b) return false, &x } else { @@ -140,7 +148,7 @@ func (j *pythonJinja2Renderer) renderHelper(jobs []*RenderJob, searchDirs []stri jargs.Strict = strict for _, job := range jobs { - if ist, r := j.isMaybeTemplate(job.Template, searchDirs, isString); !ist { + if ist, r := isMaybeTemplate(job.Template, searchDirs, isString); !ist { job.Result = r continue } diff --git a/pkg/jinja2/render_struct.go b/pkg/jinja2/render_struct.go new file mode 100644 index 000000000..e2f06996b --- /dev/null +++ b/pkg/jinja2/render_struct.go @@ -0,0 +1,181 @@ +package jinja2 + +import ( + "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "reflect" +) + +type structStringCollectorEntry struct { + s string + setter func(s string) +} + +type structStringCollector struct { + j *Jinja2 + strings []structStringCollectorEntry +} + +func (w *structStringCollector) extractTemplateString(v reflect.Value) (string, bool) { + if v.IsZero() { + return "", false + } + + t := v.Type() + if t.Kind() == reflect.Interface || t.Kind() == reflect.Pointer { + v = v.Elem() + t = v.Type() + } + if t.Kind() == reflect.String { + s := v.String() + if !isMaybeTemplateString(s) { + return "", false + } + return v.String(), true + } + return "", false +} + +func (w *structStringCollector) addString(s string, setter func(s string)) { + w.strings = append(w.strings, structStringCollectorEntry{ + s: s, + setter: setter, + }) +} + +func (w *structStringCollector) walkStruct(v reflect.Value) error { + t := v.Type() + l := t.NumField() + + for i := 0; i < l; i++ { + f := v.Field(i) + if s, ok := w.extractTemplateString(f); ok { + w.addString(s, func(s string) { + if f.Type().Kind() == reflect.Pointer { + f.Set(reflect.ValueOf(&s)) + } else { + f.Set(reflect.ValueOf(s)) + } + }) + } else { + err := w.walkValue(f) + if err != nil { + return err + } + } + } + return nil +} + +func (w *structStringCollector) walkList(v reflect.Value) error { + l := v.Len() + for i := 0; i < l; i++ { + e := v.Index(i) + if s, ok := w.extractTemplateString(e); ok { + w.addString(s, func(s string) { + if e.Type().Kind() == reflect.Pointer { + e.Set(reflect.ValueOf(&s)) + } else { + e.Set(reflect.ValueOf(s)) + } + }) + } else { + err := w.walkValue(e) + if err != nil { + return err + } + } + } + return nil +} + +func (w *structStringCollector) walkMap(v reflect.Value) error { + it := v.MapRange() + for it.Next() { + ik := it.Key() + iv := it.Value() + + if s, ok := w.extractTemplateString(iv); ok { + if isMaybeTemplateString(s) { + w.addString(s, func(s string) { + if iv.Type().Kind() == reflect.Pointer { + v.SetMapIndex(ik, reflect.ValueOf(&s)) + } else { + v.SetMapIndex(ik, reflect.ValueOf(s)) + } + }) + } + } else { + err := w.walkValue(iv) + if err != nil { + return err + } + } + } + return nil +} + +func (w *structStringCollector) walkValue(v reflect.Value) error { + if v.IsZero() { + return nil + } + + v = reflect.Indirect(v) + t := v.Type() + + switch t.Kind() { + case reflect.Interface, reflect.Pointer: + return w.walkValue(v.Elem()) + case reflect.Slice, reflect.Array: + return w.walkList(v) + case reflect.Struct: + return w.walkStruct(v) + case reflect.Map: + return w.walkMap(v) + } + return nil +} + +func (j *Jinja2) RenderStruct(o interface{}, vars *uo.UnstructuredObject) (bool, bool, error) { + w := &structStringCollector{j: j} + v := reflect.ValueOf(o) + err := w.walkValue(v) + if err != nil { + return false, false, err + } + + var jobs []*RenderJob + + for _, sv := range w.strings { + jobs = append(jobs, &RenderJob{Template: sv.s}) + } + + err = j.RenderStrings(jobs, nil, vars) + if err != nil { + return false, false, err + } + + changed := false + moreTemplates := false + + var errors []error + for i, sv := range w.strings { + job := jobs[i] + if job.Error != nil { + errors = append(errors, job.Error) + } else { + if sv.s != *job.Result { + sv.setter(*job.Result) + changed = true + if isMaybeTemplateString(*job.Result) { + moreTemplates = true + } + } + } + } + if len(errors) != 0 { + return false, false, utils.NewErrorListOrNil(errors) + } + + return changed, moreTemplates, nil +} diff --git a/pkg/kluctl_project/targets.go b/pkg/kluctl_project/targets.go index 8579a1d7c..18ac0153a 100644 --- a/pkg/kluctl_project/targets.go +++ b/pkg/kluctl_project/targets.go @@ -12,7 +12,6 @@ import ( "github.com/kluctl/kluctl/v2/pkg/yaml" "io" "os" - "reflect" "regexp" "sort" "strings" @@ -55,7 +54,7 @@ func (c *LoadedKluctlProject) loadTargets() error { continue } - target, err = c.renderTarget(target) + err = c.renderTarget(target) if err != nil { return err } @@ -76,17 +75,16 @@ func (c *LoadedKluctlProject) loadTargets() error { return nil } -func (c *LoadedKluctlProject) renderTarget(target *types.Target) (*types.Target, error) { +func (c *LoadedKluctlProject) renderTarget(target *types.Target) error { // Try rendering the target multiple times, until all values can be rendered successfully. This allows the target // to reference itself in complex ways. We'll also try loading the cluster vars in each iteration. var errors []error - curTarget := target for i := 0; i < 10; i++ { varsCtx := vars.NewVarsCtx(c.J2) - err := varsCtx.UpdateChildFromStruct("target", curTarget) + err := varsCtx.UpdateChildFromStruct("target", target) if err != nil { - return nil, err + return err } if target.Cluster != nil { @@ -94,22 +92,20 @@ func (c *LoadedKluctlProject) renderTarget(target *types.Target) (*types.Target, if err == nil { err = varsCtx.UpdateChildFromStruct("cluster", cc.Cluster) if err != nil { - return nil, err + return err } } } - var newTarget types.Target - err = c.J2.RenderStruct(&newTarget, curTarget, varsCtx.Vars) - if err == nil && reflect.DeepEqual(curTarget, &newTarget) { - return curTarget, nil + changed, moreTemplates, err := c.J2.RenderStruct(target, varsCtx.Vars) + if err == nil && (!changed || !moreTemplates) { + return nil } - curTarget = &newTarget } if len(errors) != 0 { - return nil, errors[0] + return errors[0] } - return curTarget, nil + return nil } func (c *LoadedKluctlProject) prepareDynamicTargets(baseTarget *types.Target) ([]*dynamicTargetInfo, error) { diff --git a/pkg/vars/vars_loader.go b/pkg/vars/vars_loader.go index 022bd895c..5afb09788 100644 --- a/pkg/vars/vars_loader.go +++ b/pkg/vars/vars_loader.go @@ -10,6 +10,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/vars/aws" "github.com/kluctl/kluctl/v2/pkg/vars/vault" @@ -55,8 +56,12 @@ func (v *VarsLoader) LoadVarsList(varsCtx *VarsCtx, varsList []*types.VarsSource func (v *VarsLoader) LoadVars(varsCtx *VarsCtx, sourceIn *types.VarsSource, searchDirs []string, rootKey string) error { var source types.VarsSource + err := utils.DeepCopy(&source, sourceIn) + if err != nil { + return err + } - err := varsCtx.J2.RenderStruct(&source, sourceIn, varsCtx.Vars) + _, _, err = varsCtx.J2.RenderStruct(&source, varsCtx.Vars) if err != nil { return err } From 2182b5a7d14ee299ea60ad8a8d0c68ea030ec230 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 26 Jul 2022 16:44:43 +0200 Subject: [PATCH 0957/2916] fix: Don't rely on official managed fields parser Instead, parse managed fields based on the already parsed UnstructuredObject. This should speed up parsing of managed fields. --- pkg/deployment/utils/delete_utils.go | 3 +- pkg/diff/fieldpath_utils.go | 54 ++++++++++++++++++++++++++++ pkg/diff/managed_fields.go | 21 ++++++++--- pkg/utils/uo/k8s_fields.go | 6 ++-- 4 files changed, 76 insertions(+), 8 deletions(-) create mode 100644 pkg/diff/fieldpath_utils.go diff --git a/pkg/deployment/utils/delete_utils.go b/pkg/deployment/utils/delete_utils.go index b79f93d5f..dabdf569e 100644 --- a/pkg/deployment/utils/delete_utils.go +++ b/pkg/deployment/utils/delete_utils.go @@ -96,7 +96,8 @@ func filterObjectsForDelete(k *k8s.K8sCluster, objects []*uo.UnstructuredObject, // check if kluctl is managing this object found := false for _, mf := range managedFields { - if mf.Manager == "kluctl" { + mgr, _, _ := mf.GetNestedString("manager") + if mgr == "kluctl" { found = true break } diff --git a/pkg/diff/fieldpath_utils.go b/pkg/diff/fieldpath_utils.go new file mode 100644 index 000000000..c8a2a3b54 --- /dev/null +++ b/pkg/diff/fieldpath_utils.go @@ -0,0 +1,54 @@ +package diff + +import ( + "fmt" + "sigs.k8s.io/structured-merge-diff/v4/fieldpath" +) + +// based on sigs.k8s.io/structured-merge-diff/v4/fieldpath/serialize.go:readIterV1 +func convertManagedFields(m map[string]any) (children *fieldpath.Set, isMember bool, retErr error) { + for key, v := range m { + if key == "." { + isMember = true + continue + } + pe, err := fieldpath.DeserializePathElement(key) + if err == fieldpath.ErrUnknownPathElementType { + // Ignore these-- a future version maybe knows what + // they are. We drop these completely rather than try + // to preserve things we don't understand. + continue + } else if err != nil { + retErr = fmt.Errorf("parsing key as path element: %w", err) + return + } + m2, ok := v.(map[string]any) + if !ok { + retErr = fmt.Errorf("value is not a map") + return + } + grandchildren, childIsMember, err := convertManagedFields(m2) + if err != nil { + retErr = err + return + } + if childIsMember { + if children == nil { + children = &fieldpath.Set{} + } + + children.Members.Insert(pe) + } + if grandchildren != nil { + if children == nil { + children = &fieldpath.Set{} + } + *children.Children.Descend(pe) = *grandchildren + } + } + if children == nil { + isMember = true + } + + return children, isMember, nil +} diff --git a/pkg/diff/managed_fields.go b/pkg/diff/managed_fields.go index 0e89651fe..1a5a36673 100644 --- a/pkg/diff/managed_fields.go +++ b/pkg/diff/managed_fields.go @@ -1,7 +1,6 @@ package diff import ( - "bytes" "fmt" "github.com/kluctl/kluctl/v2/pkg/utils/uo" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -112,11 +111,25 @@ func ResolveFieldManagerConflicts(local *uo.UnstructuredObject, remote *uo.Unstr managersByFields := make(map[string]*managersByField) for _, mf := range managedFields { - fieldSet := fieldpath.NewSet() - err := fieldSet.FromJSON(bytes.NewReader(mf.FieldsV1.Raw)) + fields, ok, err := mf.GetNestedObject("fieldsV1") if err != nil { return nil, nil, err } + if !ok { + continue + } + fieldSet, _, err := convertManagedFields(fields.Object) + if err != nil { + return nil, nil, err + } + + mgr, ok, err := mf.GetNestedString("manager") + if err != nil { + return nil, nil, err + } + if !ok { + return nil, nil, fmt.Errorf("manager field is missing") + } fieldSet.Iterate(func(path fieldpath.Path) { path = path.Copy() @@ -135,7 +148,7 @@ func ResolveFieldManagerConflicts(local *uo.UnstructuredObject, remote *uo.Unstr if !found { m.pathes = append(m.pathes, path) } - m.managers = append(m.managers, mf.Manager) + m.managers = append(m.managers, mgr) }) } diff --git a/pkg/utils/uo/k8s_fields.go b/pkg/utils/uo/k8s_fields.go index 69c295757..2758945b1 100644 --- a/pkg/utils/uo/k8s_fields.go +++ b/pkg/utils/uo/k8s_fields.go @@ -3,7 +3,6 @@ package uo import ( "fmt" "github.com/kluctl/kluctl/v2/pkg/types/k8s" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "reflect" "regexp" @@ -208,8 +207,9 @@ func (uo *UnstructuredObject) GetK8sOwnerReferences() []*UnstructuredObject { return ret } -func (uo *UnstructuredObject) GetK8sManagedFields() []metav1.ManagedFieldsEntry { - return uo.ToUnstructured().GetManagedFields() +func (uo *UnstructuredObject) GetK8sManagedFields() []*UnstructuredObject { + ret, _, _ := uo.GetNestedObjectList("metadata", "managedFields") + return ret } func (uo *UnstructuredObject) GetK8sCreationTime() time.Time { From fd4bd0af59d101e001604ffcf5f7f1a1823de134 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 27 Jul 2022 14:07:23 +0200 Subject: [PATCH 0958/2916] fix: Fix empty status lines --- pkg/deployment/utils/apply_utils.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index a14b1528a..baca9e10f 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -521,6 +521,9 @@ func (a *ApplyUtil) applyDeploymentItem(d *deployment.DeploymentItem) { if a.warningCount != 0 { finalStatus += fmt.Sprintf(" Encountered %d warnings.", a.warningCount) } + if finalStatus == "" { + finalStatus = "Nothing to apply." + } a.sctx.Update(strings.TrimSpace(finalStatus)) @@ -573,6 +576,7 @@ func (a *ApplyDeploymentsUtil) ApplyDeployments() { sctx = status.StartWithOptions(a.ctx, status.WithTotal(-1), status.WithPrefix(*progressName), + status.WithStatus("Initializing"), ) } a2 := a.NewApplyUtil(a.ctx, sctx) From 89b2d3b080b0275372efc8d4a1142acdc9bfe022 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 27 Jul 2022 14:15:56 +0200 Subject: [PATCH 0959/2916] fix: Fix validate command to properly report readiness --- pkg/deployment/commands/validate.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/deployment/commands/validate.go b/pkg/deployment/commands/validate.go index d9a63720d..0647a3b51 100644 --- a/pkg/deployment/commands/validate.go +++ b/pkg/deployment/commands/validate.go @@ -27,6 +27,7 @@ func NewValidateCommand(ctx context.Context, c *deployment.DeploymentCollection) func (cmd *ValidateCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*types.ValidateResult, error) { var result types.ValidateResult + result.Ready = true cmd.dew.Init() @@ -56,6 +57,9 @@ func (cmd *ValidateCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*types. continue } r := validation.ValidateObject(k, remoteObject, true) + if !r.Ready { + result.Ready = false + } result.Errors = append(result.Errors, r.Errors...) result.Warnings = append(result.Warnings, r.Warnings...) result.Results = append(result.Results, r.Results...) From 55530775ede773e6a3a23d15f110d43e7598345c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 27 Jul 2022 14:36:31 +0200 Subject: [PATCH 0960/2916] ci: Upgrade macos images in Github actions --- .github/workflows/tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e0aaa6f29..759044bff 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -139,11 +139,11 @@ jobs: include: - os: ubuntu-20.04 binary-suffix: linux-amd64 - - os: macos-10.15 + - os: macos-11 binary-suffix: darwin-amd64 - os: windows-2019 binary-suffix: windows-amd64 - os: [ubuntu-20.04, macos-10.15, windows-2019] + os: [ubuntu-20.04, macos-11, windows-2019] fail-fast: false needs: - build From 3d87d53383edf7bb344d150e3a340b7fa5614fd6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 28 Jul 2022 13:36:43 +0200 Subject: [PATCH 0961/2916] fix: Properly differentiate between "insecure" and "tlsNoVerify" registries "insecure" means http instead of https. TLS verification is independent from this. This commit properly implements both. --- pkg/registries/registries.go | 89 ++++++++++++++++++++++++++---------- pkg/utils/env.go | 11 +++++ 2 files changed, 77 insertions(+), 23 deletions(-) diff --git a/pkg/registries/registries.go b/pkg/registries/registries.go index 9f2171d83..24f169ccb 100644 --- a/pkg/registries/registries.go +++ b/pkg/registries/registries.go @@ -54,12 +54,13 @@ type RegistryHelper struct { } type AuthEntry struct { - Registry string - Username string - Password string - Auth string - CABundle []byte - Insecure bool + Registry string + Username string + Password string + Auth string + CABundle []byte + Insecure bool + SkipTlsVerify bool } func NewRegistryHelper(ctx context.Context) *RegistryHelper { @@ -86,8 +87,7 @@ func (rh *RegistryHelper) ListImageTags(image string) ([]string, error) { remote.WithContext(context.WithValue(rh.ctx, transportKey, t)), } - e := rh.findAuthEntry(repo.RegistryStr()) - if e != nil && e.Insecure { + if rh.isInsecureRegistry(repo.RegistryStr()) { nameOpts = append(nameOpts, name.Insecure) repo, err = name.NewRepository(image, nameOpts...) } @@ -110,23 +110,63 @@ func (rh *RegistryHelper) AddAuthEntry(e AuthEntry) { rh.authEntries = append(rh.authEntries, e) } -func (rh *RegistryHelper) ParseAuthEntriesFromEnv() error { - defaultTlsVerify := true - if x, ok := os.LookupEnv("KLUCTL_REGISTRY_DEFAULT_TLSVERIFY"); ok { - b, err := strconv.ParseBool(x) - if err != nil { - return fmt.Errorf("failed to parse KLUCTL_REGISTRY_DEFAULT_TLSVERIFY: %w", err) - } - defaultTlsVerify = b +func (rh *RegistryHelper) isDefaultInsecure() bool { + defaultInsecure, err := utils.ParseEnvBool("KLUCTL_REGISTRY_DEFAULT_INSECURE", false) + if err != nil { + status.Warning(rh.ctx, "Failed to parse KLUCTL_REGISTRY_DEFAULT_INSECURE: %w", err) + return false + } + return defaultInsecure +} + +func (rh *RegistryHelper) isDefaultSkipTlsVerify() bool { + defaultTlsVerify, err := utils.ParseEnvBool("KLUCTL_REGISTRY_DEFAULT_TLSVERIFY", true) + if err != nil { + status.Warning(rh.ctx, "Failed to parse KLUCTL_REGISTRY_DEFAULT_TLSVERIFY: %w", err) + return false } + return !defaultTlsVerify +} + +func (rh *RegistryHelper) isInsecureRegistry(registry string) bool { + e := rh.findAuthEntry(registry) + if e == nil { + return rh.isDefaultInsecure() + } + return e.Insecure +} + +func (rh *RegistryHelper) isSkipTlsVerify(registry string) bool { + e := rh.findAuthEntry(registry) + if e == nil { + return rh.isDefaultSkipTlsVerify() + } + return e.SkipTlsVerify +} + +func (rh *RegistryHelper) ParseAuthEntriesFromEnv() error { + defaultInsecure := rh.isDefaultInsecure() + defaultSkipTlsVerify := rh.isDefaultSkipTlsVerify() for _, m := range utils.ParseEnvConfigSets("KLUCTL_REGISTRY") { e := AuthEntry{ - Registry: m["HOST"], - Username: m["USERNAME"], - Password: m["PASSWORD"], - Auth: m["AUTH"], - Insecure: !defaultTlsVerify, + Registry: m["HOST"], + Username: m["USERNAME"], + Password: m["PASSWORD"], + Auth: m["AUTH"], + Insecure: defaultInsecure, + SkipTlsVerify: defaultSkipTlsVerify, + } + if e.Registry == "" { + continue + } + insecureStr, ok := m["INSECURE"] + if ok { + insecure, err := strconv.ParseBool(insecureStr) + if err != nil { + return fmt.Errorf("failed to parse INSECURE from env: %w", err) + } + e.Insecure = insecure } tlsverifyStr, ok := m["TLSVERIFY"] if ok { @@ -134,7 +174,7 @@ func (rh *RegistryHelper) ParseAuthEntriesFromEnv() error { if err != nil { return fmt.Errorf("failed to parse TLSVERIFY from env: %w", err) } - e.Insecure = !tlsverify + e.SkipTlsVerify = !tlsverify } ca_bundle_path := m["CA_BUNDLE"] @@ -178,16 +218,19 @@ func (rh *RegistryHelper) loadCA(registry string) (*x509.CertPool, error) { func (rh *RegistryHelper) buildTransport(registry string) (http.RoundTripper, error) { ret, err := rh.cachedTransports.Get(registry, func() (interface{}, error) { + skipTls := rh.isSkipTlsVerify(registry) + ca, err := rh.loadCA(registry) if err != nil { return nil, err } - if ca == nil { + if ca == nil && !skipTls { return remote.DefaultTransport, nil } t := remote.DefaultTransport.Clone() t.TLSClientConfig.RootCAs = ca + t.TLSClientConfig.InsecureSkipVerify = skipTls return t, nil }) if err != nil { diff --git a/pkg/utils/env.go b/pkg/utils/env.go index 5dbca1e69..66ae280f9 100644 --- a/pkg/utils/env.go +++ b/pkg/utils/env.go @@ -8,6 +8,17 @@ import ( "strings" ) +func ParseEnvBool(name string, def bool) (bool, error) { + if x, ok := os.LookupEnv(name); ok { + b, err := strconv.ParseBool(x) + if err != nil { + return def, err + } + return b, nil + } + return def, nil +} + func ParseEnvConfigSets(prefix string) map[int]map[string]string { ret := make(map[int]map[string]string) From ca65ba160f3d01ce62bc12f8e3e5880a7a5ce054 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 28 Jul 2022 13:37:17 +0200 Subject: [PATCH 0962/2916] fix: Don't use empty credentials when env vars are only used to modify TLSVERIFY --- pkg/registries/registries.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/pkg/registries/registries.go b/pkg/registries/registries.go index 24f169ccb..d6c857aa5 100644 --- a/pkg/registries/registries.go +++ b/pkg/registries/registries.go @@ -247,11 +247,15 @@ func (rh *RegistryHelper) getTransport(req *http.Request) http.RoundTripper { func (rh *RegistryHelper) doResolve(resource authn.Resource) (authn.Authenticator, error) { e := rh.findAuthEntry(resource.RegistryStr()) if e != nil { - return authn.FromConfig(authn.AuthConfig{ - Username: e.Username, - Password: e.Password, - Auth: e.Auth, - }), nil + // only use credentials from entry if present, otherwise fallback to default keychain + // (which will also check ~/.docker/config.json) + if e.Username != "" || e.Auth != "" { + return authn.FromConfig(authn.AuthConfig{ + Username: e.Username, + Password: e.Password, + Auth: e.Auth, + }), nil + } } return authn.DefaultKeychain.Resolve(resource) From e7ec72b8d0891010386c9f09c99cd8bdf130fb59 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 29 Jul 2022 10:16:41 +0200 Subject: [PATCH 0963/2916] fix: Don's use %w when not supported --- pkg/registries/registries.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/registries/registries.go b/pkg/registries/registries.go index d6c857aa5..f51f99913 100644 --- a/pkg/registries/registries.go +++ b/pkg/registries/registries.go @@ -113,7 +113,7 @@ func (rh *RegistryHelper) AddAuthEntry(e AuthEntry) { func (rh *RegistryHelper) isDefaultInsecure() bool { defaultInsecure, err := utils.ParseEnvBool("KLUCTL_REGISTRY_DEFAULT_INSECURE", false) if err != nil { - status.Warning(rh.ctx, "Failed to parse KLUCTL_REGISTRY_DEFAULT_INSECURE: %w", err) + status.Warning(rh.ctx, "Failed to parse KLUCTL_REGISTRY_DEFAULT_INSECURE: %s", err) return false } return defaultInsecure @@ -122,7 +122,7 @@ func (rh *RegistryHelper) isDefaultInsecure() bool { func (rh *RegistryHelper) isDefaultSkipTlsVerify() bool { defaultTlsVerify, err := utils.ParseEnvBool("KLUCTL_REGISTRY_DEFAULT_TLSVERIFY", true) if err != nil { - status.Warning(rh.ctx, "Failed to parse KLUCTL_REGISTRY_DEFAULT_TLSVERIFY: %w", err) + status.Warning(rh.ctx, "Failed to parse KLUCTL_REGISTRY_DEFAULT_TLSVERIFY: %s", err) return false } return !defaultTlsVerify From b7827f423bfc552eb3023c85c540185803829201 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 27 Jul 2022 13:37:40 +0200 Subject: [PATCH 0964/2916] refactor: Allow to access ClientConfig and public keys via AuthMethodAndCA --- pkg/git/auth/auth_provider.go | 4 ++++ pkg/git/auth/list_auth_provider.go | 5 +++++ pkg/git/auth/ssh_auth_provider.go | 9 +++++++++ 3 files changed, 18 insertions(+) diff --git a/pkg/git/auth/auth_provider.go b/pkg/git/auth/auth_provider.go index 2a3f91757..314d2536a 100644 --- a/pkg/git/auth/auth_provider.go +++ b/pkg/git/auth/auth_provider.go @@ -4,11 +4,15 @@ import ( "context" "github.com/go-git/go-git/v5/plumbing/transport" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" + "golang.org/x/crypto/ssh" ) type AuthMethodAndCA struct { AuthMethod transport.AuthMethod CABundle []byte + + PublicKeys func() []ssh.PublicKey + ClientConfig func() (*ssh.ClientConfig, error) } type GitAuthProvider interface { diff --git a/pkg/git/auth/list_auth_provider.go b/pkg/git/auth/list_auth_provider.go index 6ea7ba938..6e92f345e 100644 --- a/pkg/git/auth/list_auth_provider.go +++ b/pkg/git/auth/list_auth_provider.go @@ -6,6 +6,7 @@ import ( "github.com/go-git/go-git/v5/plumbing/transport/ssh" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/status" + ssh2 "golang.org/x/crypto/ssh" "strings" ) @@ -80,6 +81,10 @@ func (a *ListAuthProvider) BuildAuth(ctx context.Context, gitUrl git_url.GitUrl) a.HostKeyCallback = buildVerifyHostCallback(ctx, e.KnownHosts) return AuthMethodAndCA{ AuthMethod: a, + PublicKeys: func() []ssh2.PublicKey { + return []ssh2.PublicKey{a.Signer.PublicKey()} + }, + ClientConfig: a.ClientConfig, } } } else { diff --git a/pkg/git/auth/ssh_auth_provider.go b/pkg/git/auth/ssh_auth_provider.go index 0ec025aa1..d2de0669f 100644 --- a/pkg/git/auth/ssh_auth_provider.go +++ b/pkg/git/auth/ssh_auth_provider.go @@ -121,6 +121,15 @@ func (a *GitSshAuthProvider) BuildAuth(ctx context.Context, gitUrl git_url.GitUr return AuthMethodAndCA{ AuthMethod: auth, + PublicKeys: func() []ssh.PublicKey { + signers, _ := auth.Signers() + var ret []ssh.PublicKey + for _, s := range signers { + ret = append(ret, s.PublicKey()) + } + return ret + }, + ClientConfig: auth.ClientConfig, } } From 804cbb030ac5998b65a2dbec8e76729beb09b91e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 29 Jul 2022 12:25:14 +0200 Subject: [PATCH 0965/2916] feat: Introduce SSH connection pooling for listing of refs This speeds up updating of git repos from SSH when the git repos have no updates. For now, pooling is only used when listing remote refs, which gives enough info to be able to decide if a full fetch is required. --- cmd/kluctl/commands/utils.go | 5 +- pkg/git/list_refs.go | 99 ++++++++++++++++++++ pkg/git/mirrored_repo.go | 42 +++++---- pkg/git/repocache/cache.go | 9 +- pkg/git/ssh-pool/hostport.go | 45 +++++++++ pkg/git/ssh-pool/ssh_pool.go | 177 +++++++++++++++++++++++++++++++++++ 6 files changed, 354 insertions(+), 23 deletions(-) create mode 100644 pkg/git/list_refs.go create mode 100644 pkg/git/ssh-pool/hostport.go create mode 100644 pkg/git/ssh-pool/ssh_pool.go diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 8459ae908..3ab20e9be 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -9,6 +9,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/git/auth" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/git/repocache" + ssh_pool "github.com/kluctl/kluctl/v2/pkg/git/ssh-pool" "github.com/kluctl/kluctl/v2/pkg/jinja2" "github.com/kluctl/kluctl/v2/pkg/kluctl_project" "github.com/kluctl/kluctl/v2/pkg/registries" @@ -59,7 +60,9 @@ func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, strictTemplates b ctx, cancel := context.WithTimeout(cliCtx, projectFlags.Timeout) defer cancel() - rp := repocache.NewGitRepoCache(ctx, auth.NewDefaultAuthProviders(), projectFlags.GitCacheUpdateInterval) + sshPool := &ssh_pool.SshPool{} + + rp := repocache.NewGitRepoCache(ctx, sshPool, auth.NewDefaultAuthProviders(), projectFlags.GitCacheUpdateInterval) defer rp.Clear() loadArgs := kluctl_project.LoadKluctlProjectArgs{ diff --git a/pkg/git/list_refs.go b/pkg/git/list_refs.go new file mode 100644 index 000000000..2bc517fd6 --- /dev/null +++ b/pkg/git/list_refs.go @@ -0,0 +1,99 @@ +package git + +import ( + "bytes" + "fmt" + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/protocol/packp" + auth2 "github.com/kluctl/kluctl/v2/pkg/git/auth" + "github.com/kluctl/kluctl/v2/pkg/status" + "strconv" +) + +// listRemoteRefsFastSsh will reuse existing ssh connections from a pool +func (g *MirroredGitRepo) listRemoteRefsFastSsh(r *git.Repository, auth auth2.AuthMethodAndCA) ([]*plumbing.Reference, error) { + var portInt int64 = -1 + if g.url.Port() != "" { + var err error + portInt, err = strconv.ParseInt(g.url.Port(), 10, 32) + if err != nil { + return nil, err + } + } + + s, err := g.sshPool.GetSession(g.ctx, g.url.Hostname(), int(portInt), auth) + if err != nil { + return nil, err + } + defer s.Close() + + cmd := fmt.Sprintf("git-upload-pack %s", g.url.Path) + + stdout := bytes.NewBuffer(nil) + stderr := bytes.NewBuffer(nil) + stdin := bytes.NewBuffer([]byte("0000\n")) + + s.Session.Stdout = stdout + s.Session.Stderr = stderr + s.Session.Stdin = stdin + + err = s.Session.Run(cmd) + if err != nil { + return nil, fmt.Errorf("git-upload-pack failed: %w\nstderr=%s", err, stderr.String()) + } + + ar := packp.NewAdvRefs() + err = ar.Decode(stdout) + if err != nil { + return nil, err + } + + allRefs, err := ar.AllReferences() + if err != nil { + return nil, err + } + + refs, err := allRefs.IterReferences() + if err != nil { + return nil, err + } + + var resultRefs []*plumbing.Reference + err = refs.ForEach(func(ref *plumbing.Reference) error { + resultRefs = append(resultRefs, ref) + return nil + }) + if err != nil { + return nil, err + } + + return resultRefs, nil +} + +func (g *MirroredGitRepo) listRemoteRefsSlow(r *git.Repository, auth auth2.AuthMethodAndCA) ([]*plumbing.Reference, error) { + remote, err := r.Remote("origin") + if err != nil { + return nil, err + } + + remoteRefs, err := remote.ListContext(g.ctx, &git.ListOptions{ + Auth: auth.AuthMethod, + CABundle: auth.CABundle, + }) + if err != nil { + return nil, err + } + return remoteRefs, nil +} + +func (g *MirroredGitRepo) listRemoteRefs(r *git.Repository, auth auth2.AuthMethodAndCA) ([]*plumbing.Reference, error) { + if g.url.IsSsh() { + refs, err := g.listRemoteRefsFastSsh(r, auth) + if err == nil { + return refs, nil + } + status.Warning(g.ctx, "Fast listing of remote refs failed: %s", err.Error()) + } + return g.listRemoteRefsSlow(r, auth) +} diff --git a/pkg/git/mirrored_repo.go b/pkg/git/mirrored_repo.go index 221113f6f..49e571530 100644 --- a/pkg/git/mirrored_repo.go +++ b/pkg/git/mirrored_repo.go @@ -9,6 +9,8 @@ import ( "github.com/go-git/go-git/v5/plumbing/object" auth2 "github.com/kluctl/kluctl/v2/pkg/git/auth" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" + _ "github.com/kluctl/kluctl/v2/pkg/git/ssh-pool" + ssh_pool "github.com/kluctl/kluctl/v2/pkg/git/ssh-pool" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/rogpeppe/go-internal/lockedfile" @@ -25,6 +27,9 @@ var cacheBaseDir = filepath.Join(utils.GetTmpBaseDir(), "git-cache") type MirroredGitRepo struct { ctx context.Context + sshPool *ssh_pool.SshPool + authProviders *auth2.GitAuthProviders + url git_url.GitUrl mirrorDir string @@ -36,12 +41,14 @@ type MirroredGitRepo struct { mutex sync.Mutex } -func NewMirroredGitRepo(ctx context.Context, u git_url.GitUrl) (*MirroredGitRepo, error) { +func NewMirroredGitRepo(ctx context.Context, u git_url.GitUrl, sshPool *ssh_pool.SshPool, authProviders *auth2.GitAuthProviders) (*MirroredGitRepo, error) { mirrorRepoName := buildMirrorRepoName(u) o := &MirroredGitRepo{ - ctx: ctx, - url: u, - mirrorDir: filepath.Join(cacheBaseDir, mirrorRepoName), + ctx: ctx, + sshPool: sshPool, + authProviders: authProviders, + url: u, + mirrorDir: filepath.Join(cacheBaseDir, mirrorRepoName), } if !utils.IsDirectory(o.mirrorDir) { @@ -185,26 +192,19 @@ func (g *MirroredGitRepo) cleanupMirrorDir() error { return nil } -func (g *MirroredGitRepo) update(s *status.StatusContext, repoDir string, authProviders *auth2.GitAuthProviders) error { +func (g *MirroredGitRepo) update(s *status.StatusContext, repoDir string) error { r, err := git.PlainOpen(repoDir) if err != nil { return err } - auth := authProviders.BuildAuth(g.ctx, g.url) + auth := g.authProviders.BuildAuth(g.ctx, g.url) - remote, err := r.Remote("origin") + remoteRefs, err := g.listRemoteRefs(r, auth) if err != nil { return err } - remoteRefs, err := remote.ListContext(g.ctx, &git.ListOptions{ - Auth: auth.AuthMethod, - CABundle: auth.CABundle, - }) - if err != nil { - return err - } remoteRefsMap := make(map[plumbing.ReferenceName]*plumbing.Reference) for _, reference := range remoteRefs { remoteRefsMap[reference.Name()] = reference @@ -241,6 +241,10 @@ func (g *MirroredGitRepo) update(s *status.StatusContext, repoDir string, authPr } if changed { + remote, err := r.Remote("origin") + if err != nil { + return err + } err = remote.FetchContext(g.ctx, &git.FetchOptions{ Auth: auth.AuthMethod, CABundle: auth.CABundle, @@ -276,10 +280,10 @@ func (g *MirroredGitRepo) update(s *status.StatusContext, repoDir string, authPr return nil } -func (g *MirroredGitRepo) cloneOrUpdate(s *status.StatusContext, authProviders *auth2.GitAuthProviders) error { +func (g *MirroredGitRepo) cloneOrUpdate(s *status.StatusContext) error { initMarker := filepath.Join(g.mirrorDir, ".cache2.init") if utils.IsFile(initMarker) { - return g.update(s, g.mirrorDir, authProviders) + return g.update(s, g.mirrorDir) } err := g.cleanupMirrorDir() if err != nil { @@ -309,7 +313,7 @@ func (g *MirroredGitRepo) cloneOrUpdate(s *status.StatusContext, authProviders * return err } - err = g.update(s, tmpMirrorDir, authProviders) + err = g.update(s, tmpMirrorDir) if err != nil { return err } @@ -331,9 +335,9 @@ func (g *MirroredGitRepo) cloneOrUpdate(s *status.StatusContext, authProviders * return nil } -func (g *MirroredGitRepo) Update(authProviders *auth2.GitAuthProviders) error { +func (g *MirroredGitRepo) Update() error { s := status.Start(g.ctx, "Updating git cache for %s", g.url.String()) - err := g.cloneOrUpdate(s, authProviders) + err := g.cloneOrUpdate(s) if err != nil { s.FailedWithMessage(err.Error()) return err diff --git a/pkg/git/repocache/cache.go b/pkg/git/repocache/cache.go index 3d34e2ec8..76b4aa813 100644 --- a/pkg/git/repocache/cache.go +++ b/pkg/git/repocache/cache.go @@ -7,6 +7,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/git" "github.com/kluctl/kluctl/v2/pkg/git/auth" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" + ssh_pool "github.com/kluctl/kluctl/v2/pkg/git/ssh-pool" "github.com/kluctl/kluctl/v2/pkg/utils" "io/ioutil" "os" @@ -20,6 +21,7 @@ import ( type GitRepoCache struct { ctx context.Context authProviders *auth.GitAuthProviders + sshPool *ssh_pool.SshPool updateInterval time.Duration repos map[string]*CacheEntry reposMutex sync.Mutex @@ -49,9 +51,10 @@ type clonedDir struct { info git.CheckoutInfo } -func NewGitRepoCache(ctx context.Context, authProviders *auth.GitAuthProviders, updateInterval time.Duration) *GitRepoCache { +func NewGitRepoCache(ctx context.Context, sshPool *ssh_pool.SshPool, authProviders *auth.GitAuthProviders, updateInterval time.Duration) *GitRepoCache { return &GitRepoCache{ ctx: ctx, + sshPool: sshPool, authProviders: authProviders, updateInterval: updateInterval, repos: map[string]*CacheEntry{}, @@ -74,7 +77,7 @@ func (rp *GitRepoCache) GetEntry(url git_url.GitUrl) (*CacheEntry, error) { e, ok := rp.repos[url.NormalizedRepoKey()] if !ok { - mr, err := git.NewMirroredGitRepo(rp.ctx, url) + mr, err := git.NewMirroredGitRepo(rp.ctx, url, rp.sshPool, rp.authProviders) if err != nil { return nil, err } @@ -106,7 +109,7 @@ func (e *CacheEntry) Update() error { if time.Now().Sub(e.mr.LastUpdateTime()) <= e.rp.updateInterval { e.mr.SetUpdated(true) } else { - err := e.mr.Update(e.rp.authProviders) + err := e.mr.Update() if err != nil { return err } diff --git a/pkg/git/ssh-pool/hostport.go b/pkg/git/ssh-pool/hostport.go new file mode 100644 index 000000000..4ca5c5cc7 --- /dev/null +++ b/pkg/git/ssh-pool/hostport.go @@ -0,0 +1,45 @@ +package ssh_pool + +import ( + "fmt" + "github.com/go-git/go-git/v5/plumbing/transport/ssh" + "strconv" +) + +func getHostWithPort(host string, port int) string { + if addr, found := doGetHostWithPortFromSSHConfig(host, port); found { + return addr + } + + if port <= 0 { + port = ssh.DefaultPort + } + + return fmt.Sprintf("%s:%d", host, port) +} + +func doGetHostWithPortFromSSHConfig(host string, port int) (addr string, found bool) { + if ssh.DefaultSSHConfig == nil { + return + } + + configHost := ssh.DefaultSSHConfig.Get(host, "Hostname") + if configHost != "" { + host = configHost + found = true + } + + if !found { + return + } + + configPort := ssh.DefaultSSHConfig.Get(host, "Port") + if configPort != "" { + if i, err := strconv.Atoi(configPort); err == nil { + port = i + } + } + + addr = fmt.Sprintf("%s:%d", host, port) + return +} diff --git a/pkg/git/ssh-pool/ssh_pool.go b/pkg/git/ssh-pool/ssh_pool.go new file mode 100644 index 000000000..ccc6d46cd --- /dev/null +++ b/pkg/git/ssh-pool/ssh_pool.go @@ -0,0 +1,177 @@ +package ssh_pool + +import ( + "context" + "crypto/sha256" + "encoding/binary" + "encoding/hex" + "fmt" + "github.com/kluctl/kluctl/v2/pkg/git/auth" + "github.com/kluctl/kluctl/v2/pkg/status" + "golang.org/x/crypto/ssh" + "golang.org/x/net/proxy" + "sync" + "time" +) + +const maxAge = time.Minute * 1 + +type SshPool struct { + pool sync.Map + m sync.Mutex +} + +type poolEntry struct { + hash string + pc *poolClient + m sync.Mutex +} + +type poolClient struct { + time time.Time + sessionCount int + client *ssh.Client +} + +type PooledSession struct { + Session *ssh.Session + pe *poolEntry + pc *poolClient + hash string +} + +func (p *SshPool) GetSession(ctx context.Context, host string, port int, auth auth.AuthMethodAndCA) (*PooledSession, error) { + addr := getHostWithPort(host, port) + + h, err := p.buildHash(addr, auth) + if err != nil { + return nil, err + } + + pe1, _ := p.pool.LoadOrStore(h, &poolEntry{hash: h}) + pe, _ := pe1.(*poolEntry) + + pe.m.Lock() + defer pe.m.Unlock() + + if pe.pc != nil { + if time.Now().Sub(pe.pc.time) > maxAge { + if pe.pc.sessionCount == 0 { + _ = pe.pc.client.Close() + } + pe.pc = nil + } + } + + isNew := false + if pe.pc == nil { + isNew = true + client, err := p.newClient(ctx, addr, auth) + if err != nil { + p.pool.Delete(h) + return nil, err + } + pe.pc = &poolClient{ + time: time.Now(), + client: client, + } + } + + s, err := pe.pc.client.NewSession() + if err != nil { + origErr := err + _ = pe.pc.client.Close() + pe.pc = nil + if isNew { + // don't retry with a fresh connection + p.pool.Delete(h) + return nil, err + } else { + // retry with a fresh connection + client, err := p.newClient(ctx, addr, auth) + if err != nil { + p.pool.Delete(h) + return nil, err + } + status.Trace(ctx, "Successfully retries failed ssh connection. Old error: %s", origErr) + pe.pc = &poolClient{ + time: time.Now(), + client: client, + } + s, err = pe.pc.client.NewSession() + if err != nil { + // something is completely wrong here, forget about the client + p.pool.Delete(h) + return nil, err + } + } + } + + pe.pc.sessionCount += 1 + + ps := &PooledSession{ + hash: h, + pe: pe, + pc: pe.pc, + Session: s, + } + return ps, nil +} + +func (ps *PooledSession) Close() error { + err := ps.Session.Close() + + ps.pe.m.Lock() + defer ps.pe.m.Unlock() + + ps.pc.sessionCount-- + + if ps.pe.pc != ps.pc { + _ = ps.pc.client.Close() + } else if ps.pc.sessionCount == 0 && time.Now().Sub(ps.pc.time) > maxAge { + _ = ps.pc.client.Close() + ps.pe.pc = nil + } + + return err +} + +func (p *SshPool) newClient(ctx context.Context, addr string, auth auth.AuthMethodAndCA) (*ssh.Client, error) { + conn, err := proxy.Dial(ctx, "tcp", addr) + if err != nil { + return nil, err + } + + config, err := auth.ClientConfig() + if err != nil { + return nil, err + } + + c, chans, reqs, err := ssh.NewClientConn(conn, addr, config) + if err != nil { + return nil, err + } + return ssh.NewClient(c, chans, reqs), nil +} + +func (p *SshPool) buildHash(addr string, auth auth.AuthMethodAndCA) (string, error) { + if auth.PublicKeys == nil { + // this should not happen + return "", fmt.Errorf("auth has no PublicKeys") + } + + config, err := auth.ClientConfig() + if err != nil { + return "", err + } + + h := sha256.New() + _ = binary.Write(h, binary.LittleEndian, addr) + _ = binary.Write(h, binary.LittleEndian, config.User) + + for _, pk := range auth.PublicKeys() { + _ = binary.Write(h, binary.LittleEndian, pk.Marshal()) + } + + return hex.EncodeToString(h.Sum(nil)), nil +} From 3a075cfb0473ea001b302c21feb367e713307c03 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 29 Jul 2022 13:31:17 +0200 Subject: [PATCH 0966/2916] tests: Fix tests compilation --- pkg/git/ssh-pool/ssh_pool.go | 1 - pkg/vars/vars_loader_test.go | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/git/ssh-pool/ssh_pool.go b/pkg/git/ssh-pool/ssh_pool.go index ccc6d46cd..bbc84aa7c 100644 --- a/pkg/git/ssh-pool/ssh_pool.go +++ b/pkg/git/ssh-pool/ssh_pool.go @@ -18,7 +18,6 @@ const maxAge = time.Minute * 1 type SshPool struct { pool sync.Map - m sync.Mutex } type poolEntry struct { diff --git a/pkg/vars/vars_loader_test.go b/pkg/vars/vars_loader_test.go index ad7ee5be0..6cac07687 100644 --- a/pkg/vars/vars_loader_test.go +++ b/pkg/vars/vars_loader_test.go @@ -8,6 +8,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/git/auth" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/git/repocache" + ssh_pool "github.com/kluctl/kluctl/v2/pkg/git/ssh-pool" "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" @@ -38,7 +39,7 @@ func newTestDir(t *testing.T) string { } func newRP(t *testing.T) *repocache.GitRepoCache { - grc := repocache.NewGitRepoCache(context.TODO(), auth.NewDefaultAuthProviders(), 0) + grc := repocache.NewGitRepoCache(context.TODO(), &ssh_pool.SshPool{}, auth.NewDefaultAuthProviders(), 0) t.Cleanup(func() { grc.Clear() }) From 1ba1df71d0715a550e1aebf6e00d4b6bfa8943fa Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 1 Aug 2022 09:14:27 +0200 Subject: [PATCH 0967/2916] fix: Don't try to decrypt passphrase protected keys when calculating hash Instead, use the encrypted key hash to calculate the auth entry hash. This prevents passphrase prompts for keys that are already unlocked via the ssh agent. --- pkg/git/auth/auth_provider.go | 2 +- pkg/git/auth/hash.go | 34 ++++++++++++++++++++++++++++++ pkg/git/auth/list_auth_provider.go | 5 ++--- pkg/git/auth/ssh_auth_provider.go | 26 +++++++++++++++++------ pkg/git/mirrored_repo.go | 2 +- pkg/git/ssh-pool/ssh_pool.go | 10 +++++---- 6 files changed, 64 insertions(+), 15 deletions(-) create mode 100644 pkg/git/auth/hash.go diff --git a/pkg/git/auth/auth_provider.go b/pkg/git/auth/auth_provider.go index 314d2536a..0eca67a60 100644 --- a/pkg/git/auth/auth_provider.go +++ b/pkg/git/auth/auth_provider.go @@ -11,7 +11,7 @@ type AuthMethodAndCA struct { AuthMethod transport.AuthMethod CABundle []byte - PublicKeys func() []ssh.PublicKey + Hash func() ([]byte, error) ClientConfig func() (*ssh.ClientConfig, error) } diff --git a/pkg/git/auth/hash.go b/pkg/git/auth/hash.go new file mode 100644 index 000000000..5c5ffdf82 --- /dev/null +++ b/pkg/git/auth/hash.go @@ -0,0 +1,34 @@ +package auth + +import ( + "crypto/sha256" + "encoding/binary" + "golang.org/x/crypto/ssh" +) + +type hashProvider interface { + Hash() ([]byte, error) +} + +func buildHash(s ssh.Signer) ([]byte, error) { + if hp, ok := s.(hashProvider); ok { + return hp.Hash() + } + h := sha256.New() + _ = binary.Write(h, binary.LittleEndian, "pk") + _ = binary.Write(h, binary.LittleEndian, s.PublicKey().Marshal()) + return h.Sum(nil), nil +} + +func buildHashForList(signers []ssh.Signer) ([]byte, error) { + h := sha256.New() + _ = binary.Write(h, binary.LittleEndian, "signers") + for _, s := range signers { + h2, err := buildHash(s) + if err != nil { + return nil, err + } + _ = binary.Write(h, binary.LittleEndian, h2) + } + return h.Sum(nil), nil +} diff --git a/pkg/git/auth/list_auth_provider.go b/pkg/git/auth/list_auth_provider.go index 6e92f345e..532734c1d 100644 --- a/pkg/git/auth/list_auth_provider.go +++ b/pkg/git/auth/list_auth_provider.go @@ -6,7 +6,6 @@ import ( "github.com/go-git/go-git/v5/plumbing/transport/ssh" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/status" - ssh2 "golang.org/x/crypto/ssh" "strings" ) @@ -81,8 +80,8 @@ func (a *ListAuthProvider) BuildAuth(ctx context.Context, gitUrl git_url.GitUrl) a.HostKeyCallback = buildVerifyHostCallback(ctx, e.KnownHosts) return AuthMethodAndCA{ AuthMethod: a, - PublicKeys: func() []ssh2.PublicKey { - return []ssh2.PublicKey{a.Signer.PublicKey()} + Hash: func() ([]byte, error) { + return buildHash(a.Signer) }, ClientConfig: a.ClientConfig, } diff --git a/pkg/git/auth/ssh_auth_provider.go b/pkg/git/auth/ssh_auth_provider.go index d2de0669f..9c347192b 100644 --- a/pkg/git/auth/ssh_auth_provider.go +++ b/pkg/git/auth/ssh_auth_provider.go @@ -2,6 +2,8 @@ package auth import ( "context" + "crypto/sha256" + "encoding/binary" "fmt" "github.com/kevinburke/ssh_config" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" @@ -121,13 +123,12 @@ func (a *GitSshAuthProvider) BuildAuth(ctx context.Context, gitUrl git_url.GitUr return AuthMethodAndCA{ AuthMethod: auth, - PublicKeys: func() []ssh.PublicKey { - signers, _ := auth.Signers() - var ret []ssh.PublicKey - for _, s := range signers { - ret = append(ret, s.PublicKey()) + Hash: func() ([]byte, error) { + signers, err := auth.Signers() + if err != nil { + return nil, err } - return ret + return buildHashForList(signers) }, ClientConfig: auth.ClientConfig, } @@ -211,6 +212,19 @@ func (k *dummyPublicKey) Verify(data []byte, sig *ssh.Signature) error { return fmt.Errorf("this is a dummy key") } +func (k *deferredPassphraseKey) Hash() ([]byte, error) { + pemBytes, err := ioutil.ReadFile(k.path) + if err != nil { + return nil, err + } + + h := sha256.New() + _ = binary.Write(h, binary.LittleEndian, "dpk") + _ = binary.Write(h, binary.LittleEndian, pemBytes) + + return h.Sum(nil), nil +} + func (k *deferredPassphraseKey) PublicKey() ssh.PublicKey { k.mutex.Lock() defer k.mutex.Unlock() diff --git a/pkg/git/mirrored_repo.go b/pkg/git/mirrored_repo.go index 49e571530..3c3a271c1 100644 --- a/pkg/git/mirrored_repo.go +++ b/pkg/git/mirrored_repo.go @@ -222,7 +222,7 @@ func (g *MirroredGitRepo) update(s *status.StatusContext, repoDir string) error }) var toDelete []*plumbing.Reference - changed := false + changed := true for name, ref := range remoteRefsMap { if name.String() != "HEAD" && !strings.HasPrefix(name.String(), "refs/heads/") && !strings.HasPrefix(name.String(), "refs/tags/") { // we only fetch branches and tags diff --git a/pkg/git/ssh-pool/ssh_pool.go b/pkg/git/ssh-pool/ssh_pool.go index bbc84aa7c..fb60c220c 100644 --- a/pkg/git/ssh-pool/ssh_pool.go +++ b/pkg/git/ssh-pool/ssh_pool.go @@ -154,9 +154,9 @@ func (p *SshPool) newClient(ctx context.Context, addr string, auth auth.AuthMeth } func (p *SshPool) buildHash(addr string, auth auth.AuthMethodAndCA) (string, error) { - if auth.PublicKeys == nil { + if auth.Hash == nil { // this should not happen - return "", fmt.Errorf("auth has no PublicKeys") + return "", fmt.Errorf("auth has no Hash") } config, err := auth.ClientConfig() @@ -168,9 +168,11 @@ func (p *SshPool) buildHash(addr string, auth auth.AuthMethodAndCA) (string, err _ = binary.Write(h, binary.LittleEndian, addr) _ = binary.Write(h, binary.LittleEndian, config.User) - for _, pk := range auth.PublicKeys() { - _ = binary.Write(h, binary.LittleEndian, pk.Marshal()) + h2, err := auth.Hash() + if err != nil { + return "", err } + _ = binary.Write(h, binary.LittleEndian, h2) return hex.EncodeToString(h.Sum(nil)), nil } From da21d297560d653fb2d055935bbc7e5ddfd039c1 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 3 Aug 2022 09:19:38 +0200 Subject: [PATCH 0968/2916] chore: Run go get -u ./... --- go.mod | 113 ++++++++++++++++++++--------------------- go.sum | 155 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 212 insertions(+), 56 deletions(-) diff --git a/go.mod b/go.mod index 54167b996..2e5ea6a5c 100644 --- a/go.mod +++ b/go.mod @@ -3,75 +3,76 @@ module github.com/kluctl/kluctl/v2 go 1.18 require ( - github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e + github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e github.com/Masterminds/semver/v3 v3.1.1 - github.com/aws/aws-sdk-go v1.44.28 - github.com/bitnami-labs/sealed-secrets v0.18.0 + github.com/aws/aws-sdk-go v1.44.68 + github.com/bitnami-labs/sealed-secrets v0.18.1 github.com/cyphar/filepath-securejoin v0.2.3 github.com/docker/distribution v2.8.1+incompatible github.com/evanphx/json-patch v5.6.0+incompatible - github.com/fluxcd/pkg/kustomize v0.5.1 + github.com/fluxcd/pkg/kustomize v0.5.3 github.com/go-git/go-git/v5 v5.4.2 github.com/go-playground/validator/v10 v10.11.0 github.com/gobwas/glob v0.2.3 - github.com/golang-jwt/jwt/v4 v4.4.1 - github.com/google/go-containerregistry v0.9.0 - github.com/hashicorp/vault/api v1.6.0 + github.com/golang-jwt/jwt/v4 v4.4.2 + github.com/google/go-containerregistry v0.11.0 + github.com/hashicorp/vault/api v1.7.2 github.com/hexops/gotextdiff v1.0.3 github.com/imdario/mergo v0.3.13 github.com/jinzhu/copier v0.3.5 github.com/kevinburke/ssh_config v1.2.0 - github.com/klauspost/compress v1.15.6 + github.com/klauspost/compress v1.15.9 github.com/mattn/go-isatty v0.0.14 github.com/mitchellh/go-ps v1.0.0 github.com/mitchellh/reflectwalk v1.0.2 - github.com/ohler55/ojg v1.14.2 + github.com/ohler55/ojg v1.14.3 github.com/pkg/errors v0.9.1 github.com/r3labs/diff/v2 v2.15.1 github.com/rogpeppe/go-internal v1.8.1 - github.com/sirupsen/logrus v1.8.1 - github.com/spf13/cobra v1.4.0 + github.com/sirupsen/logrus v1.9.0 + github.com/spf13/cobra v1.5.0 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.12.0 github.com/stretchr/testify v1.7.2 github.com/vbauerster/mpb/v7 v7.4.2 github.com/whilp/git-urls v1.0.0 github.com/xanzy/ssh-agent v0.3.1 - golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e - golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f - golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a - golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 + golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa + golang.org/x/net v0.0.0-20220802222814-0bcc04d9c69b + golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 + golang.org/x/sys v0.0.0-20220731174439-a90be440212d + golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 golang.org/x/text v0.3.7 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 - helm.sh/helm/v3 v3.9.0 - k8s.io/api v0.24.1 - k8s.io/apiextensions-apiserver v0.24.1 - k8s.io/apimachinery v0.24.1 - k8s.io/client-go v0.24.1 - k8s.io/klog/v2 v2.60.1 + helm.sh/helm/v3 v3.9.2 + k8s.io/api v0.24.3 + k8s.io/apiextensions-apiserver v0.24.3 + k8s.io/apimachinery v0.24.3 + k8s.io/client-go v0.24.3 + k8s.io/klog/v2 v2.70.1 sigs.k8s.io/kind v0.14.0 - sigs.k8s.io/kustomize/api v0.11.5 - sigs.k8s.io/kustomize/kyaml v0.13.7 - sigs.k8s.io/structured-merge-diff/v4 v4.2.1 + sigs.k8s.io/kustomize/api v0.12.1 + sigs.k8s.io/kustomize/kyaml v0.13.9 + sigs.k8s.io/structured-merge-diff/v4 v4.2.3 ) require ( - cloud.google.com/go/compute v1.6.1 // indirect + cloud.google.com/go/compute v1.7.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect - github.com/Azure/go-autorest/autorest v0.11.27 // indirect - github.com/Azure/go-autorest/autorest/adal v0.9.20 // indirect + github.com/Azure/go-autorest/autorest v0.11.28 // indirect + github.com/Azure/go-autorest/autorest/adal v0.9.21 // indirect github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect - github.com/BurntSushi/toml v1.1.0 // indirect + github.com/BurntSushi/toml v1.2.0 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/sprig/v3 v3.2.2 // indirect github.com/Masterminds/squirrel v1.5.3 // indirect github.com/Microsoft/go-winio v0.5.2 // indirect - github.com/ProtonMail/go-crypto v0.0.0-20220517143526-88bb52951d5b // indirect + github.com/ProtonMail/go-crypto v0.0.0-20220730123233-d6ffb7692adf // indirect github.com/VividCortex/ewma v1.2.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect github.com/acomagu/bufpipe v1.0.3 // indirect @@ -83,6 +84,7 @@ require ( github.com/cenkalti/backoff/v3 v3.2.2 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 // indirect + github.com/cloudflare/circl v1.2.0 // indirect github.com/containerd/containerd v1.6.6 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/docker/cli v20.10.17+incompatible // indirect @@ -91,7 +93,7 @@ require ( github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/go-units v0.4.0 // indirect - github.com/emicklei/go-restful/v3 v3.8.0 // indirect + github.com/emicklei/go-restful/v3 v3.9.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect @@ -110,7 +112,7 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/golang/snappy v0.0.4 // indirect - github.com/google/btree v1.0.1 // indirect + github.com/google/btree v1.1.2 // indirect github.com/google/gnostic v0.6.9 // indirect github.com/google/go-cmp v0.5.8 // indirect github.com/google/gofuzz v1.2.0 // indirect @@ -121,7 +123,7 @@ require ( github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-hclog v1.2.1 // indirect + github.com/hashicorp/go-hclog v1.2.2 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-plugin v1.4.4 // indirect @@ -132,11 +134,11 @@ require ( github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect github.com/hashicorp/go-sockaddr v1.0.2 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect - github.com/hashicorp/go-version v1.5.0 // indirect + github.com/hashicorp/go-version v1.6.0 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/hashicorp/vault/sdk v0.5.0 // indirect - github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 // indirect + github.com/hashicorp/vault/sdk v0.5.3 // indirect + github.com/hashicorp/yamux v0.1.1 // indirect github.com/huandu/xstrings v1.3.2 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect @@ -171,21 +173,21 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 // indirect github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.0.1 // indirect + github.com/pelletier/go-toml/v2 v2.0.2 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pierrec/lz4 v2.6.1+incompatible // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.12.2 // indirect github.com/prometheus/client_model v0.2.0 // indirect - github.com/prometheus/common v0.34.0 // indirect - github.com/prometheus/procfs v0.7.3 // indirect - github.com/rivo/uniseg v0.2.0 // indirect - github.com/rubenv/sql-migrate v1.1.1 // indirect + github.com/prometheus/common v0.37.0 // indirect + github.com/prometheus/procfs v0.8.0 // indirect + github.com/rivo/uniseg v0.3.1 // indirect + github.com/rubenv/sql-migrate v1.1.2 // indirect github.com/russross/blackfriday v1.6.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect github.com/sergi/go-diff v1.2.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect - github.com/spf13/afero v1.8.2 // indirect + github.com/spf13/afero v1.9.2 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/subosito/gotenv v1.4.0 // indirect @@ -194,26 +196,25 @@ require ( github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xlab/treeprint v1.1.0 // indirect - go.starlark.net v0.0.0-20220328144851-d1966c6b9fcd // indirect + go.starlark.net v0.0.0-20220714194419-4cadf0a12139 // indirect go.uber.org/atomic v1.9.0 // indirect - golang.org/x/net v0.0.0-20220607020251-c690dde0001d // indirect - golang.org/x/oauth2 v0.0.0-20220524215830-622c5d57e401 // indirect - golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect + golang.org/x/oauth2 v0.0.0-20220722155238-128564f6959c // indirect + golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20220602131408-e326c6e8e9c8 // indirect - google.golang.org/grpc v1.47.0 // indirect - google.golang.org/protobuf v1.28.0 // indirect + google.golang.org/genproto v0.0.0-20220802133213-ce4fa296bf78 // indirect + google.golang.org/grpc v1.48.0 // indirect + google.golang.org/protobuf v1.28.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.66.6 // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect - k8s.io/apiserver v0.24.1 // indirect - k8s.io/cli-runtime v0.24.1 // indirect - k8s.io/component-base v0.24.1 // indirect - k8s.io/kube-openapi v0.0.0-20220603121420-31174f50af60 // indirect - k8s.io/kubectl v0.24.1 // indirect - k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect - oras.land/oras-go v1.1.1 // indirect - sigs.k8s.io/json v0.0.0-20220525155127-227cbc7cc124 // indirect + k8s.io/apiserver v0.24.3 // indirect + k8s.io/cli-runtime v0.24.3 // indirect + k8s.io/component-base v0.24.3 // indirect + k8s.io/kube-openapi v0.0.0-20220627174259-011e075b9cb8 // indirect + k8s.io/kubectl v0.24.3 // indirect + k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect + oras.land/oras-go v1.2.0 // indirect + sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index af4a31531..a2dc07990 100644 --- a/go.sum +++ b/go.sum @@ -32,6 +32,7 @@ cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= +cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -44,10 +45,13 @@ cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6m cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= cloud.google.com/go/compute v1.6.1 h1:2sMmt8prCn7DPaG4Pmh0N3Inmc8cT8ae5k1M6VJ9Wqc= cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= +cloud.google.com/go/compute v1.7.0 h1:v/k9Eueb8aAJ0vZuxKMrgm6kPhCLZU9HxFU+AFDs9Uk= +cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/firestore v1.6.0/go.mod h1:afJwI0vaXwAG54kI7A//lP/lSPDkQORQuMkv56TxEPU= +cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -60,6 +64,7 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= contrib.go.opencensus.io/exporter/stackdriver v0.13.4/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Antonboom/errname v0.1.5/go.mod h1:DugbBstvPFQbv/5uLcRRzfrNqKE9tVdVCqWCLp6Cifo= @@ -71,10 +76,14 @@ github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSW github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= github.com/Azure/go-autorest/autorest v0.11.27 h1:F3R3q42aWytozkV8ihzcgMO4OA4cuqr3bNlsEuF6//A= github.com/Azure/go-autorest/autorest v0.11.27/go.mod h1:7l8ybrIdUmGqZMTD0sRtAr8NvbHjfofbf8RSP2q7w7U= +github.com/Azure/go-autorest/autorest v0.11.28 h1:ndAExarwr5Y+GaHE6VCaY1kyS/HwwGGyuimVhWsHOEM= +github.com/Azure/go-autorest/autorest v0.11.28/go.mod h1:MrkzG3Y3AH668QyF9KRk5neJnGgmhQ6krbhR8Q5eMvA= github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= github.com/Azure/go-autorest/autorest/adal v0.9.20 h1:gJ3E98kMpFB1MFqQCvA1yFab8vthOeD4VlFRQULxahg= github.com/Azure/go-autorest/autorest/adal v0.9.20/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= +github.com/Azure/go-autorest/autorest/adal v0.9.21 h1:jjQnVFXPfekaqb8vIsv2G1lxshoW+oGv4MDlhRtnYZk= +github.com/Azure/go-autorest/autorest/adal v0.9.21/go.mod h1:zua7mBUaCc5YnSLKYgGJR/w5ePdMDA6H56upLsHzA9U= github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= @@ -86,11 +95,15 @@ github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUM github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e h1:ZU22z/2YRFLyf/P4ZwUYSdNCWsMEI0VeyrFoI2rAhJQ= github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= +github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e h1:NeAW1fUYUEWhft7pkxDf6WoUvEZJ/uOKsvtpjLnn8MU= +github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0= +github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= @@ -102,10 +115,12 @@ github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy86 github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/sprig v2.15.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= @@ -124,6 +139,8 @@ github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmU github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= github.com/ProtonMail/go-crypto v0.0.0-20220517143526-88bb52951d5b h1:lcbBNuQhppsc7A5gjdHmdlqUqJfgGMylBdGyDs0j7G8= github.com/ProtonMail/go-crypto v0.0.0-20220517143526-88bb52951d5b/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= +github.com/ProtonMail/go-crypto v0.0.0-20220730123233-d6ffb7692adf h1:aFFtnGZ6/2Qlvx80yxA2fFSYDQWTFjtKozQKB36A3/A= +github.com/ProtonMail/go-crypto v0.0.0-20220730123233-d6ffb7692adf/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= @@ -171,6 +188,8 @@ github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpi github.com/aws/aws-sdk-go v1.36.30/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go v1.44.28 h1:h/OAqEqY18wq//v6h4GNPMmCkxuzSDrWuGyrvSiRqf4= github.com/aws/aws-sdk-go v1.44.28/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.68 h1:7zNr5+HLG0TMq+ZcZ8KhT4eT2KyL7v+u7/jANKEIinM= +github.com/aws/aws-sdk-go v1.44.68/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -180,6 +199,8 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitnami-labs/sealed-secrets v0.18.0 h1:7LdfPRMyx9nGQW9204JM1F+F+s6xmaSBl8oNujlrCuc= github.com/bitnami-labs/sealed-secrets v0.18.0/go.mod h1:uV8CUHJQVcDOY9cZ1FdC1rtybC4ath+VGH+/dUQvBu0= +github.com/bitnami-labs/sealed-secrets v0.18.1 h1:xXzi0Z6lArTykRGqCOC2rN3zN648zjQtkCzAVjJj9OY= +github.com/bitnami-labs/sealed-secrets v0.18.1/go.mod h1:pOMGS1imRiIPLm7OpdD/s/OfgQmLkKxTqWruSEiQqCM= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/bkielbasa/cyclop v1.2.0/go.mod h1:qOI0yy6A7dYC4Zgsa72Ppm9kONl0RoIlPbzot9mhmeI= @@ -193,12 +214,15 @@ github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd h1:rFt+Y/IK1aEZ github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembjv71DPz3uX/V/6MMlSyD9JBQ6kQ= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o= github.com/butuzov/ireturn v0.1.1/go.mod h1:Wh6Zl3IMtTpaIKbmwzqi6olnM9ptYQxxVacMsOEFPoc= +github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/bwesterb/go-ristretto v1.2.1/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= @@ -213,6 +237,9 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= +github.com/cloudflare/circl v1.2.0 h1:NheeISPSUcYftKlfrLuOo4T62FkmD4t4jviLfFFYaec= +github.com/cloudflare/circl v1.2.0/go.mod h1:Ch2UgYr6ti2KTtlejELlROl0YIYj7SLjAC8M+INXlMk= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -230,6 +257,7 @@ github.com/containerd/cgroups v1.0.3 h1:ADZftAkglvCiD44c77s5YmMqaP2pzVCFZvBmAlBd github.com/containerd/containerd v1.6.6 h1:xJNPhbrmz8xAMDNoVjHy9YHtWwEQNS+CDkcIRh7t8Y0= github.com/containerd/containerd v1.6.6/go.mod h1:ZoP1geJldzCVY3Tonoz7b1IXk8rIX0Nltt5QE4OMNk0= github.com/containerd/stargz-snapshotter/estargz v0.11.4 h1:LjrYUZpyOhiSaU7hHrdR82/RBoxfGWSaC0VeSSMXqnk= +github.com/containerd/stargz-snapshotter/estargz v0.12.0 h1:idtwRTLjk2erqiYhPWy2L844By8NRFYEwYHcXhoIWPM= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= @@ -247,6 +275,7 @@ github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwc github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= @@ -265,6 +294,7 @@ github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27N github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/distribution/distribution/v3 v3.0.0-20211118083504-a29a3c99a684 h1:DBZ2sN7CK6dgvHVpQsQj4sRMCbWTmd17l+5SUCjnQSY= +github.com/distribution/distribution/v3 v3.0.0-20220526142353-ffbd94cbe269 h1:hbCT8ZPPMqefiAWD2ZKjn7ypokIGViTvBBg/ExLSdCk= github.com/docker/cli v20.10.17+incompatible h1:eO2KS7ZFeov5UJeaDmIs1NFEDRf32PaqRpvoEkKBy5M= github.com/docker/cli v20.10.17+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= @@ -291,6 +321,8 @@ github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT github.com/emicklei/go-restful/v3 v3.7.5-0.20220308211933-7c971ca4d0fd/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emicklei/go-restful/v3 v3.8.0 h1:eCZ8ulSerjdAiaNpF7GxXIE7ZCMo1moN1qX+S609eVw= github.com/emicklei/go-restful/v3 v3.8.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= +github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= @@ -331,6 +363,8 @@ github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= github.com/fluxcd/pkg/kustomize v0.5.1 h1:151Ih34ltxN2z1e2mA5AvQONyE6phc4es57oVK3+plU= github.com/fluxcd/pkg/kustomize v0.5.1/go.mod h1:58MFITy24bIbGI6cC3JkV/YpFQj648sVvgs0K1kraJw= +github.com/fluxcd/pkg/kustomize v0.5.3 h1:WpxNOV/Yklp0p7Qv85VwBegq9fABuLR9qSWaAVa3+yc= +github.com/fluxcd/pkg/kustomize v0.5.3/go.mod h1:zy1FLxkEDADUykCnrXqq6rVN48t3uMhAb3ao+zv0rFE= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= @@ -448,6 +482,8 @@ github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzw github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ= github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs= +github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= @@ -505,6 +541,8 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= +github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/cel-go v0.10.1/go.mod h1:U7ayypeSkw23szu4GaQTPJGx66c20mx8JklMSxrmI1w= github.com/google/cel-spec v0.6.0/go.mod h1:Nwjgxy5CbjlPrtCWjeDjUyKMl8w41YBYGjsyDdqk0xA= github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= @@ -529,6 +567,8 @@ github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-containerregistry v0.9.0 h1:5Ths7RjxyFV0huKChQTgY6fLzvHhZMpLTFNja8U0/0w= github.com/google/go-containerregistry v0.9.0/go.mod h1:9eq4BnSufyT1kHNffX+vSXVonaJ7yaIOulrKZejMxnQ= +github.com/google/go-containerregistry v0.11.0 h1:Xt8x1adcREjFcmDoDK8OdOsjxu90PHkGuwNP8GiHMLM= +github.com/google/go-containerregistry v0.11.0/go.mod h1:BBaYtsHPHA42uEgAvd/NejvAfPSlz281sJWqupjSxfk= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= @@ -563,13 +603,16 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= +github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= +github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -625,6 +668,8 @@ github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39 github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.2.1 h1:YQsLlGDJgwhXFpucSPyVbCBviQtjlHv3jLTlp8YmtEw= github.com/hashicorp/go-hclog v1.2.1/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-hclog v1.2.2 h1:ihRI7YFwcZdiSD7SIenIhHfQH3OuDvWerAUBZbeQS3M= +github.com/hashicorp/go-hclog v1.2.2/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= @@ -670,6 +715,8 @@ github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09 github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.5.0 h1:O293SZ2Eg+AAYijkVK3jR786Am1bhDEh2GHT0tIVE5E= github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -686,11 +733,17 @@ github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/J github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= github.com/hashicorp/vault/api v1.6.0 h1:B8UUYod1y1OoiGHq9GtpiqSnGOUEWHaA26AY8RQEDY4= github.com/hashicorp/vault/api v1.6.0/go.mod h1:h1K70EO2DgnBaTz5IsL6D5ERsNt5Pce93ueVS2+t0Xc= +github.com/hashicorp/vault/api v1.7.2 h1:kawHE7s/4xwrdKbkmwQi0wYaIeUhk5ueek7ljuezCVQ= +github.com/hashicorp/vault/api v1.7.2/go.mod h1:xbfA+1AvxFseDzxxdWaL0uO99n1+tndus4GCrtouy0M= github.com/hashicorp/vault/sdk v0.5.0 h1:EED7p0OCU3OY5SAqJwSANofY1YKMytm+jDHDQ2EzGVQ= github.com/hashicorp/vault/sdk v0.5.0/go.mod h1:UJZHlfwj7qUJG8g22CuxUgkdJouFrBNvBHCyx8XAPdo= +github.com/hashicorp/vault/sdk v0.5.3 h1:PWY8sq/9pRrK9vUIy75qCH2Jd8oeENAgkaa/qbhzFrs= +github.com/hashicorp/vault/sdk v0.5.3/go.mod h1:DoGraE9kKGNcVgPmTuX357Fm6WAx1Okvde8Vp3dPDoU= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 h1:xixZ2bWeofWV68J+x6AzmKuVM/JWCQwkWm6GW/MUR6I= github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= +github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= +github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= @@ -766,6 +819,8 @@ github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8 github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.6 h1:6D9PcO8QWu0JyaQ2zUMmu16T1T+zjjEpP91guRsvDfY= github.com/klauspost/compress v1.15.6/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= +github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= +github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -940,6 +995,8 @@ github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/ohler55/ojg v1.14.2 h1:EHdrwmDrOuTGpj1W2LrT/yKeUOkLMIk1cTYcm42Sjj0= github.com/ohler55/ojg v1.14.2/go.mod h1:/Y5dGWkekv9ocnUixuETqiL58f+5pAsUfg5P8e7Pa2o= +github.com/ohler55/ojg v1.14.3 h1:kaKNsntZ0PuoXPXCY4kjPDbHOLXqokor6Deq/oVxAR0= +github.com/ohler55/ojg v1.14.3/go.mod h1:/Y5dGWkekv9ocnUixuETqiL58f+5pAsUfg5P8e7Pa2o= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= @@ -982,10 +1039,13 @@ github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3v github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU= github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= +github.com/pelletier/go-toml/v2 v2.0.2 h1:+jQXlF3scKIcSEKkdHzXhCTDLPFi5r1wnK6yPS+49Gw= +github.com/pelletier/go-toml/v2 v2.0.2/go.mod h1:MovirKjgVRESsAvNZlAjtFwV867yGuwRkXbG66OzopI= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw= github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc= +github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= @@ -1030,6 +1090,8 @@ github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9 github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.34.0 h1:RBmGO9d/FVjqHT0yUGQwBJhkwKV+wPCn7KGpvfab0uE= github.com/prometheus/common v0.34.0/go.mod h1:gB3sOl7P0TvJabZpLY5uQMpUqRCPPCyRLCZYc7JZTNE= +github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= +github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -1039,6 +1101,8 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= +github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/pseudomuto/protoc-gen-doc v1.3.2/go.mod h1:y5+P6n3iGrbKG+9O04V5ld71in3v/bX88wUwgt+U8EA= github.com/pseudomuto/protokit v0.2.0/go.mod h1:2PdH30hxVHsup8KpBTOXTBeMVhJZVio3Q8ViKSAXT0Q= @@ -1054,6 +1118,8 @@ github.com/r3labs/diff/v2 v2.15.1 h1:EOrVqPUzi+njlumoqJwiS/TgGgmZo83619FNDB9xQUg github.com/r3labs/diff/v2 v2.15.1/go.mod h1:I8noH9Fc2fjSaMxqF3G2lhDdC0b+JXCfyx85tWFM9kc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.3.1 h1:SDPP7SHNl1L7KrEFCSJslJ/DM9DT02Nq2C61XrfHMmk= +github.com/rivo/uniseg v0.3.1/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -1065,6 +1131,8 @@ github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4 github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rubenv/sql-migrate v1.1.1 h1:haR5Hn8hbW9/SpAICrXoZqXnywS7Q5WijwkQENPeNWY= github.com/rubenv/sql-migrate v1.1.1/go.mod h1:/7TZymwxN8VWumcIxw1jjHEcR1djpdkMHQPT4FWdnbQ= +github.com/rubenv/sql-migrate v1.1.2 h1:9M6oj4e//owVVHYrFISmY9LBRw6gzkCNmD9MV36tZeQ= +github.com/rubenv/sql-migrate v1.1.2/go.mod h1:/7TZymwxN8VWumcIxw1jjHEcR1djpdkMHQPT4FWdnbQ= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= @@ -1098,6 +1166,8 @@ github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrf github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sivchari/tenv v1.4.7/go.mod h1:5nF+bITvkebQVanjU6IuMbvIot/7ReNsUV7I5NbprB0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= @@ -1111,6 +1181,8 @@ github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTd github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= +github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw= +github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= @@ -1122,6 +1194,8 @@ github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSW github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= +github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= +github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= @@ -1261,6 +1335,8 @@ go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqe go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= go.starlark.net v0.0.0-20220328144851-d1966c6b9fcd h1:Uo/x0Ir5vQJ+683GXB9Ug+4fcjsbp7z7Ul8UaZbhsRM= go.starlark.net v0.0.0-20220328144851-d1966c6b9fcd/go.mod h1:t3mmBBPzAVvK0L0n1drDmrQsJ8FoIx4INCqVMTr/Zo0= +go.starlark.net v0.0.0-20220714194419-4cadf0a12139 h1:zMemyQYZSyEdPaUFixYICrXf/0Rfnil7+jiQRf5IBZ0= +go.starlark.net v0.0.0-20220714194419-4cadf0a12139/go.mod h1:t3mmBBPzAVvK0L0n1drDmrQsJ8FoIx4INCqVMTr/Zo0= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -1303,8 +1379,11 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1405,8 +1484,11 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220607020251-c690dde0001d h1:4SFsTMi4UahlKoloni7L4eYzhFRifURQLw+yv0QDCx8= golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220802222814-0bcc04d9c69b h1:3ogNYyK4oIQdIKzTu68hQrr4iuVxF3AxKl9Aj/eDrw0= +golang.org/x/net v0.0.0-20220802222814-0bcc04d9c69b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1429,6 +1511,9 @@ golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220524215830-622c5d57e401 h1:zwrSfklXn0gxyLRX/aR+q6cgHbV/ItVyzbPlbA+dkAw= golang.org/x/oauth2 v0.0.0-20220524215830-622c5d57e401/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220722155238-128564f6959c h1:q3gFqPqH7NVofKo3c3yETAP//pPI+G5mvB7qqj1Y5kY= +golang.org/x/oauth2 v0.0.0-20220722155238-128564f6959c/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1443,6 +1528,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8= golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1531,6 +1618,7 @@ golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210915083310-ed5796bab164/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1540,16 +1628,24 @@ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220731174439-a90be440212d h1:Sv5ogFZatcgIMMtBSTTAgMYsicp25MXBubjXNDKwm80= +golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 h1:Q5284mrmYTpACcm+eAKjKJH48BBwSyfJqmmGDTtT8Vc= +golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1569,6 +1665,8 @@ golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220411224347-583f2d630306 h1:+gHMid33q6pen7kv9xvT+JRinntgeXO2AeZVd0AWD3w= golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 h1:ftMN5LMiBFjbzleLqtoBZk7KdJwhuybIU+FckUHgoyQ= +golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1679,6 +1777,8 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -1717,6 +1817,9 @@ google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/S google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= +google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= +google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1775,6 +1878,7 @@ google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= @@ -1808,8 +1912,16 @@ google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/genproto v0.0.0-20220602131408-e326c6e8e9c8 h1:qRu95HZ148xXw+XeZ3dvqe85PxH4X8+jIo0iRPKcEnM= google.golang.org/genproto v0.0.0-20220602131408-e326c6e8e9c8/go.mod h1:yKyY4AMRwFiC8yMMNaMi+RkCnjZJt9LoWuvhXjMs+To= +google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220802133213-ce4fa296bf78 h1:QntLWYqZeuBtJkth3m/6DLznnI0AHJr+AgJXvVh/izw= +google.golang.org/genproto v0.0.0-20220802133213-ce4fa296bf78/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -1843,9 +1955,12 @@ google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9K google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8= google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.48.0 h1:rQOsyJ/8+ufEDJd/Gdsz7HG220Mh9HAhFHRGnIjda0w= +google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -1862,6 +1977,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -1913,6 +2030,8 @@ gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= helm.sh/helm/v3 v3.9.0 h1:qDSWViuF6SzZX5s5AB/NVRGWmdao7T5j4S4ebIkMGag= helm.sh/helm/v3 v3.9.0/go.mod h1:fzZfyslcPAWwSdkXrXlpKexFeE2Dei8N27FFQWt+PN0= +helm.sh/helm/v3 v3.9.2 h1:bx7kdhr5VAhYoWv9bIdT1C6qWR+/7SIoPCwLx22l78g= +helm.sh/helm/v3 v3.9.2/go.mod h1:y/dJc/0Lzcn40jgd85KQXnufhFF7sr4v6L/vYMLRaRM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1923,44 +2042,72 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.2.1/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY= k8s.io/api v0.24.1 h1:BjCMRDcyEYz03joa3K1+rbshwh1Ay6oB53+iUx2H8UY= k8s.io/api v0.24.1/go.mod h1:JhoOvNiLXKTPQ60zh2g0ewpA+bnEYf5q44Flhquh4vQ= +k8s.io/api v0.24.3 h1:tt55QEmKd6L2k5DP6G/ZzdMQKvG5ro4H4teClqm0sTY= +k8s.io/api v0.24.3/go.mod h1:elGR/XSZrS7z7cSZPzVWaycpJuGIw57j9b95/1PdJNI= k8s.io/apiextensions-apiserver v0.24.1 h1:5yBh9+ueTq/kfnHQZa0MAo6uNcPrtxPMpNQgorBaKS0= k8s.io/apiextensions-apiserver v0.24.1/go.mod h1:A6MHfaLDGfjOc/We2nM7uewD5Oa/FnEbZ6cD7g2ca4Q= +k8s.io/apiextensions-apiserver v0.24.3 h1:kyx+Tmro1qEsTUr07ZGQOfvTsF61yn+AxnxytBWq8As= +k8s.io/apiextensions-apiserver v0.24.3/go.mod h1:cL0xkmUefpYM4f6IuOau+6NMFEIh6/7wXe/O4vPVJ8A= k8s.io/apimachinery v0.24.1 h1:ShD4aDxTQKN5zNf8K1RQ2u98ELLdIW7jEnlO9uAMX/I= k8s.io/apimachinery v0.24.1/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= +k8s.io/apimachinery v0.24.3 h1:hrFiNSA2cBZqllakVYyH/VyEh4B581bQRmqATJSeQTg= +k8s.io/apimachinery v0.24.3/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= k8s.io/apiserver v0.24.1 h1:LAA5UpPOeaREEtFAQRUQOI3eE5So/j5J3zeQJjeLdz4= k8s.io/apiserver v0.24.1/go.mod h1:dQWNMx15S8NqJMp0gpYfssyvhYnkilc1LpExd/dkLh0= +k8s.io/apiserver v0.24.3 h1:J8CKjUaZopT0hSgxjzUyp3T1GK78iixxOuFpEC0MI3k= +k8s.io/apiserver v0.24.3/go.mod h1:aXfwtIn4U27B7lYs5f2BKgz6DRbgWy+HJeYReN1jLJ8= k8s.io/cli-runtime v0.24.1 h1:IW6L8dRBq+pPTzvXcB+m/hOabzbqXy57Bqo4XxmW7DY= k8s.io/cli-runtime v0.24.1/go.mod h1:14aVvCTqkA7dNXY51N/6hRY3GUjchyWDOwW84qmR3bs= +k8s.io/cli-runtime v0.24.3 h1:O9YvUHrDSCQUPlsqVmaqDrueqjpJ7IO6Yas9B6xGSoo= +k8s.io/cli-runtime v0.24.3/go.mod h1:In84wauoMOqa7JDvDSXGbf8lTNlr70fOGpYlYfJtSqA= k8s.io/client-go v0.24.1 h1:w1hNdI9PFrzu3OlovVeTnf4oHDt+FJLd9Ndluvnb42E= k8s.io/client-go v0.24.1/go.mod h1:f1kIDqcEYmwXS/vTbbhopMUbhKp2JhOeVTfxgaCIlF8= +k8s.io/client-go v0.24.3 h1:Nl1840+6p4JqkFWEW2LnMKU667BUxw03REfLAVhuKQY= +k8s.io/client-go v0.24.3/go.mod h1:AAovolf5Z9bY1wIg2FZ8LPQlEdKHjLI7ZD4rw920BJw= k8s.io/code-generator v0.24.1/go.mod h1:dpVhs00hTuTdTY6jvVxvTFCk6gSMrtfRydbhZwHI15w= +k8s.io/code-generator v0.24.3/go.mod h1:dpVhs00hTuTdTY6jvVxvTFCk6gSMrtfRydbhZwHI15w= k8s.io/component-base v0.24.1 h1:APv6W/YmfOWZfo+XJ1mZwep/f7g7Tpwvdbo9CQLDuts= k8s.io/component-base v0.24.1/go.mod h1:DW5vQGYVCog8WYpNob3PMmmsY8A3L9QZNg4j/dV3s38= +k8s.io/component-base v0.24.3 h1:u99WjuHYCRJjS1xeLOx72DdRaghuDnuMgueiGMFy1ec= +k8s.io/component-base v0.24.3/go.mod h1:bqom2IWN9Lj+vwAkPNOv2TflsP1PeVDIwIN0lRthxYY= k8s.io/component-helpers v0.24.1/go.mod h1:q5Z1pWV/QfX9ThuNeywxasiwkLw9KsR4Q9TAOdb/Y3s= +k8s.io/component-helpers v0.24.3/go.mod h1:/1WNW8TfBOijQ1ED2uCHb4wtXYWDVNMqUll8h36iNVo= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/gengo v0.0.0-20211129171323-c02415ce4185/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.60.1 h1:VW25q3bZx9uE3vvdL6M8ezOX79vA2Aq1nEWLqNQclHc= k8s.io/klog/v2 v2.60.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.70.1 h1:7aaoSdahviPmR+XkS7FyxlkkXs6tHISSG03RxleQAVQ= +k8s.io/klog/v2 v2.70.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42/go.mod h1:Z/45zLw8lUo4wdiUkI+v/ImEGAvu3WatcZl3lPMR4Rk= k8s.io/kube-openapi v0.0.0-20220401212409-b28bf2818661/go.mod h1:daOouuuwd9JXpv1L7Y34iV3yf6nxzipkKMWWlqlvK9M= k8s.io/kube-openapi v0.0.0-20220603121420-31174f50af60 h1:cE/M8rmDQgibspuSm+X1iW16ByTImtEaapgaHoVSLX4= k8s.io/kube-openapi v0.0.0-20220603121420-31174f50af60/go.mod h1:ouUzE1U2mEv//HRoBwYLFE5pdqjIebvtX361vtEIlBI= +k8s.io/kube-openapi v0.0.0-20220627174259-011e075b9cb8 h1:yEQKdMCjzAOvGeiTwG4hO/hNVNtDOuUFvMUZ0OlaIzs= +k8s.io/kube-openapi v0.0.0-20220627174259-011e075b9cb8/go.mod h1:mbJ+NSUoAhuR14N0S63bPkh8MGVSo3VYSGZtH/mfMe0= k8s.io/kubectl v0.24.1 h1:gxcjHrnwntV1c+G/BHWVv4Mtk8CQJ0WTraElLBG+ddk= k8s.io/kubectl v0.24.1/go.mod h1:NzFqQ50B004fHYWOfhHTrAm4TY6oGF5FAAL13LEaeUI= +k8s.io/kubectl v0.24.3 h1:PqY8ho/S/KuE2/hCC3Iee7X+lOtARYo0LQsNzvV/edE= +k8s.io/kubectl v0.24.3/go.mod h1:PYLcvw96sC1NLbxZEDbdlOEd6/C76VIWjGmWV5QjSk0= k8s.io/metrics v0.24.1/go.mod h1:vMs5xpcOyY9D+/XVwlaw8oUHYCo6JTGBCZfyXOOkAhE= +k8s.io/metrics v0.24.3/go.mod h1:p1M0lhMySWfhISkSd3HEj8xIgrVnJTK3PPhFq2rA3To= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 h1:HNSDgDCrr/6Ly3WEGKZftiE7IY19Vz2GdbOCyI4qqhc= k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed h1:jAne/RjBTyawwAy0utX5eqigAwz/lQhTmy+Hr/Cpue4= +k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= mvdan.cc/gofumpt v0.1.1/go.mod h1:yXG1r1WqZVKWbVRtBWKWX9+CxGYfA51nSomhM0woR48= mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= mvdan.cc/unparam v0.0.0-20210104141923-aac4ce9116a7/go.mod h1:hBpJkZE8H/sb+VRFvw2+rBpHNsTBcvSpk61hr8mzXZE= oras.land/oras-go v1.1.1 h1:gI00ftziRivKXaw1BdMeEoIA4uBgga33iVlOsEwefFs= oras.land/oras-go v1.1.1/go.mod h1:n2TE1ummt9MUyprGhT+Q7kGZUF4kVUpYysPFxeV2IpQ= +oras.land/oras-go v1.2.0 h1:yoKosVIbsPoFMqAIFHTnrmOuafHal+J/r+I5bdbVWu4= +oras.land/oras-go v1.2.0/go.mod h1:pFNs7oHp2dYsYMSS82HaX5l4mpnGO7hbpPN6EWH2ltc= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= @@ -1968,19 +2115,27 @@ sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.30/go.mod h1:fEO7lR sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY= sigs.k8s.io/json v0.0.0-20220525155127-227cbc7cc124 h1:2sgAQQcY0dEW2SsQwTXhQV4vO6+rSslYx8K3XmM5hqQ= sigs.k8s.io/json v0.0.0-20220525155127-227cbc7cc124/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY= +sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= +sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/kind v0.14.0 h1:cNmI3jGBvp7UegEGbC5we8plDtCUmaNRL+bod7JoSCE= sigs.k8s.io/kind v0.14.0/go.mod h1:UrFRPHG+2a5j0Q7qiR4gtJ4rEyn8TuMQwuOPf+m4oHg= sigs.k8s.io/kustomize/api v0.11.4/go.mod h1:k+8RsqYbgpkIrJ4p9jcdPqe8DprLxFUUO0yNOq8C+xI= sigs.k8s.io/kustomize/api v0.11.5 h1:vLDp++YAX7iy2y2CVPJNy9pk9CY8XaUKgHkjbVtnWag= sigs.k8s.io/kustomize/api v0.11.5/go.mod h1:2UDpxS6AonWXow2ZbySd4AjUxmdXLeTlvGBC46uSiq8= +sigs.k8s.io/kustomize/api v0.12.1 h1:7YM7gW3kYBwtKvoY216ZzY+8hM+lV53LUayghNRJ0vM= +sigs.k8s.io/kustomize/api v0.12.1/go.mod h1:y3JUhimkZkR6sbLNwfJHxvo1TCLwuwm14sCYnkH6S1s= sigs.k8s.io/kustomize/cmd/config v0.10.6/go.mod h1:/S4A4nUANUa4bZJ/Edt7ZQTyKOY9WCER0uBS1SW2Rco= sigs.k8s.io/kustomize/kustomize/v4 v4.5.4/go.mod h1:Zo/Xc5FKD6sHl0lilbrieeGeZHVYCA4BzxeAaLI05Bg= sigs.k8s.io/kustomize/kyaml v0.13.6/go.mod h1:yHP031rn1QX1lr/Xd934Ri/xdVNG8BE2ECa78Ht/kEg= sigs.k8s.io/kustomize/kyaml v0.13.7 h1:/EZ/nPaLUzeJKF/BuJ4QCuMVJWiEVoI8iftOHY3g3tk= sigs.k8s.io/kustomize/kyaml v0.13.7/go.mod h1:6K+IUOuir3Y7nucPRAjw9yth04KSWBnP5pqUTGwj/qU= +sigs.k8s.io/kustomize/kyaml v0.13.9 h1:Qz53EAaFFANyNgyOEJbT/yoIHygK40/ZcvU3rgry2Tk= +sigs.k8s.io/kustomize/kyaml v0.13.9/go.mod h1:QsRbD0/KcU+wdk0/L0fIp2KLnohkVzs6fQ85/nOXac4= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y= sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= From 9d20bb4a5ee6a82f021b327e801f12fb2033cc97 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 18 Aug 2022 17:43:15 +0200 Subject: [PATCH 0969/2916] refactor: Pull in python dependencies from kluctl-python-deps project Instead of requiring generation of static python archives. This also removes the need for vendoring and generation. --- .github/workflows/tests.yml | 6 - .gitignore | 2 - .goreleaser.yaml | 1 - Makefile | 17 +- go.mod | 3 +- go.sum | 144 +-------------- pkg/jinja2/embed/python_src.dummy | 0 pkg/jinja2/generate/main.go | 55 ------ pkg/jinja2/jinja2_renderer.go | 2 +- pkg/jinja2/python_src/dummy.go | 1 - pkg/jinja2/python_src/requirements.txt | 4 - pkg/jinja2/source.go | 75 ++++++-- pkg/jinja2/tools.go | 9 - pkg/python/.gitignore | 2 - pkg/python/cmd.go | 24 --- pkg/python/embed.go | 40 ---- pkg/python/embed/python-darwin-amd64.dummy | 0 pkg/python/embed/python-darwin-arm64.dummy | 0 pkg/python/embed/python-linux-amd64.dummy | 0 pkg/python/embed/python-linux-arm64.dummy | 0 pkg/python/embed/python-windows-amd64.dummy | 0 pkg/python/embed_darwin_amd64.go | 6 - pkg/python/embed_darwin_arm64.go | 6 - pkg/python/embed_linux_amd64.go | 6 - pkg/python/embed_linux_arm64.go | 6 - pkg/python/embed_windows_amd64.go | 6 - pkg/python/generate/main.go | 195 -------------------- pkg/python/tools.go | 8 - pkg/utils/embed_util/extract.go | 134 -------------- pkg/utils/embed_util/packer/packer.go | 147 --------------- pkg/utils/tar.go | 190 ------------------- 31 files changed, 73 insertions(+), 1016 deletions(-) delete mode 100644 pkg/jinja2/embed/python_src.dummy delete mode 100644 pkg/jinja2/generate/main.go delete mode 100644 pkg/jinja2/python_src/dummy.go delete mode 100644 pkg/jinja2/python_src/requirements.txt delete mode 100644 pkg/jinja2/tools.go delete mode 100644 pkg/python/.gitignore delete mode 100644 pkg/python/cmd.go delete mode 100644 pkg/python/embed.go delete mode 100644 pkg/python/embed/python-darwin-amd64.dummy delete mode 100644 pkg/python/embed/python-darwin-arm64.dummy delete mode 100644 pkg/python/embed/python-linux-amd64.dummy delete mode 100644 pkg/python/embed/python-linux-arm64.dummy delete mode 100644 pkg/python/embed/python-windows-amd64.dummy delete mode 100644 pkg/python/embed_darwin_amd64.go delete mode 100644 pkg/python/embed_darwin_arm64.go delete mode 100644 pkg/python/embed_linux_amd64.go delete mode 100644 pkg/python/embed_linux_arm64.go delete mode 100644 pkg/python/embed_windows_amd64.go delete mode 100644 pkg/python/generate/main.go delete mode 100644 pkg/python/tools.go delete mode 100644 pkg/utils/embed_util/extract.go delete mode 100644 pkg/utils/embed_util/packer/packer.go delete mode 100644 pkg/utils/tar.go diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 759044bff..509758756 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -28,12 +28,6 @@ jobs: key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- - - name: Go Mod Vendor - run: | - go mod vendor - - name: Go Generate - run: | - go generate ./... - name: Run unit tests run: | go test ./cmd/... ./pkg/... -v diff --git a/.gitignore b/.gitignore index 3158e3eb0..8079ea478 100644 --- a/.gitignore +++ b/.gitignore @@ -7,8 +7,6 @@ .secrets.yml .sealed-secrets -/vendor -/download-python /kluctl /kluctl.exe /e2e.test* diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 2461d3dd0..9570d3b69 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -1,7 +1,6 @@ before: hooks: - go mod tidy - - go generate ./... builds: - <<: &build_defaults binary: kluctl diff --git a/Makefile b/Makefile index 6372eb9d5..631cc08ee 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ WHITE := $(shell tput -Txterm setaf 7) CYAN := $(shell tput -Txterm setaf 6) RESET := $(shell tput -Txterm sgr0) -.PHONY: all test build vendor check-kubectl check-helm check-kind +.PHONY: all test build check-kubectl check-helm check-kind all: help @@ -30,11 +30,11 @@ check-kind: ## Checks if kind is installed kind version ## Build: -build: vendor generate build-go ## Run the complete build pipeline +build: build-go ## Run the complete build pipeline build-go: ## Build your project and put the output binary in ./bin/ mkdir -p ./bin - CGO_ENBALED=0 GO111MODULE=on $(GOCMD) build -mod vendor -o ./bin/$(BINARY_NAME) + CGO_ENBALED=0 GO111MODULE=on $(GOCMD) build -o ./bin/$(BINARY_NAME) clean: ## Remove build related file rm -fr ./bin @@ -42,13 +42,6 @@ clean: ## Remove build related file rm -fr ./reports rm -fr ./download-python -vendor: ## Copy of all packages needed to support builds and tests in the vendor directory - $(GOCMD) mod vendor - -generate: ## Generating Python and Jinja2 support - $(GOCMD) generate ./... - $(GOCMD) generate -tags linux,darwin,windows,amd64,arm64 ./pkg/python - ## Test: test: test-unit test-e2e ## Runs the complete test suite @@ -61,10 +54,10 @@ ifeq ($(EXPORT_RESULT), true) GO111MODULE=off $(GOCMD) get -u github.com/jstemmer/go-junit-report $(eval OUTPUT_OPTIONS = | tee /dev/tty | go-junit-report -set-exit-code > reports/test-unit/junit-report.xml) endif - $(GOTEST) -v -race $(shell go list ./... | grep -v /e2e/ | grep -v /vendor/) $(OUTPUT_OPTIONS) + $(GOTEST) -v -race $(shell go list ./... | grep -v /e2e/) $(OUTPUT_OPTIONS) coverage-unit: ## Run the unit tests of the project and export the coverage - $(GOTEST) -cover -covermode=count -coverprofile=reports/coverage-unit/profile.cov $(shell go list ./... | grep -v /e2e/ | grep -v /vendor/) + $(GOTEST) -cover -covermode=count -coverprofile=reports/coverage-unit/profile.cov $(shell go list ./... | grep -v /e2e/) $(GOCMD) tool cover -func profile.cov ifeq ($(EXPORT_RESULT), true) mkdir -p reports/coverage-unit diff --git a/go.mod b/go.mod index 2e5ea6a5c..4364baad0 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/imdario/mergo v0.3.13 github.com/jinzhu/copier v0.3.5 github.com/kevinburke/ssh_config v1.2.0 - github.com/klauspost/compress v1.15.9 + github.com/kluctl/kluctl-python-deps v0.0.0-20220818145819-a2811c3f89fd github.com/mattn/go-isatty v0.0.14 github.com/mitchellh/go-ps v1.0.0 github.com/mitchellh/reflectwalk v1.0.2 @@ -146,6 +146,7 @@ require ( github.com/jmoiron/sqlx v1.3.5 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.15.9 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/leodido/go-urn v1.2.1 // indirect diff --git a/go.sum b/go.sum index a2dc07990..c55806e56 100644 --- a/go.sum +++ b/go.sum @@ -43,7 +43,6 @@ cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTB cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= -cloud.google.com/go/compute v1.6.1 h1:2sMmt8prCn7DPaG4Pmh0N3Inmc8cT8ae5k1M6VJ9Wqc= cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= cloud.google.com/go/compute v1.7.0 h1:v/k9Eueb8aAJ0vZuxKMrgm6kPhCLZU9HxFU+AFDs9Uk= cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= @@ -74,14 +73,10 @@ github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg6 github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= -github.com/Azure/go-autorest/autorest v0.11.27 h1:F3R3q42aWytozkV8ihzcgMO4OA4cuqr3bNlsEuF6//A= -github.com/Azure/go-autorest/autorest v0.11.27/go.mod h1:7l8ybrIdUmGqZMTD0sRtAr8NvbHjfofbf8RSP2q7w7U= github.com/Azure/go-autorest/autorest v0.11.28 h1:ndAExarwr5Y+GaHE6VCaY1kyS/HwwGGyuimVhWsHOEM= github.com/Azure/go-autorest/autorest v0.11.28/go.mod h1:MrkzG3Y3AH668QyF9KRk5neJnGgmhQ6krbhR8Q5eMvA= github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= -github.com/Azure/go-autorest/autorest/adal v0.9.20 h1:gJ3E98kMpFB1MFqQCvA1yFab8vthOeD4VlFRQULxahg= -github.com/Azure/go-autorest/autorest/adal v0.9.20/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= github.com/Azure/go-autorest/autorest/adal v0.9.21 h1:jjQnVFXPfekaqb8vIsv2G1lxshoW+oGv4MDlhRtnYZk= github.com/Azure/go-autorest/autorest/adal v0.9.21/go.mod h1:zua7mBUaCc5YnSLKYgGJR/w5ePdMDA6H56upLsHzA9U= github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= @@ -93,15 +88,11 @@ github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+Z github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e h1:ZU22z/2YRFLyf/P4ZwUYSdNCWsMEI0VeyrFoI2rAhJQ= -github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e h1:NeAW1fUYUEWhft7pkxDf6WoUvEZJ/uOKsvtpjLnn8MU= github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= -github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0= github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= @@ -115,12 +106,10 @@ github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy86 github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/sprig v2.15.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= -github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= @@ -137,8 +126,6 @@ github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMo github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM= github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= -github.com/ProtonMail/go-crypto v0.0.0-20220517143526-88bb52951d5b h1:lcbBNuQhppsc7A5gjdHmdlqUqJfgGMylBdGyDs0j7G8= -github.com/ProtonMail/go-crypto v0.0.0-20220517143526-88bb52951d5b/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= github.com/ProtonMail/go-crypto v0.0.0-20220730123233-d6ffb7692adf h1:aFFtnGZ6/2Qlvx80yxA2fFSYDQWTFjtKozQKB36A3/A= github.com/ProtonMail/go-crypto v0.0.0-20220730123233-d6ffb7692adf/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= @@ -170,7 +157,6 @@ github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-metrics v0.3.9/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-metrics v0.4.0 h1:yCQqn7dwca4ITXb+CbubHmedzaQYHhNhrEXLYUeEe8Q= github.com/armon/go-metrics v0.4.0/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= @@ -186,8 +172,6 @@ github.com/ashanbrown/makezero v0.0.0-20210520155254-b6261585ddde/go.mod h1:oG9D github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.36.30/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= -github.com/aws/aws-sdk-go v1.44.28 h1:h/OAqEqY18wq//v6h4GNPMmCkxuzSDrWuGyrvSiRqf4= -github.com/aws/aws-sdk-go v1.44.28/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aws/aws-sdk-go v1.44.68 h1:7zNr5+HLG0TMq+ZcZ8KhT4eT2KyL7v+u7/jANKEIinM= github.com/aws/aws-sdk-go v1.44.68/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= @@ -197,8 +181,6 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bitnami-labs/sealed-secrets v0.18.0 h1:7LdfPRMyx9nGQW9204JM1F+F+s6xmaSBl8oNujlrCuc= -github.com/bitnami-labs/sealed-secrets v0.18.0/go.mod h1:uV8CUHJQVcDOY9cZ1FdC1rtybC4ath+VGH+/dUQvBu0= github.com/bitnami-labs/sealed-secrets v0.18.1 h1:xXzi0Z6lArTykRGqCOC2rN3zN648zjQtkCzAVjJj9OY= github.com/bitnami-labs/sealed-secrets v0.18.1/go.mod h1:pOMGS1imRiIPLm7OpdD/s/OfgQmLkKxTqWruSEiQqCM= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= @@ -216,13 +198,11 @@ github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXe github.com/butuzov/ireturn v0.1.1/go.mod h1:Wh6Zl3IMtTpaIKbmwzqi6olnM9ptYQxxVacMsOEFPoc= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/bwesterb/go-ristretto v1.2.1/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= -github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= @@ -256,7 +236,6 @@ github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u9 github.com/containerd/cgroups v1.0.3 h1:ADZftAkglvCiD44c77s5YmMqaP2pzVCFZvBmAlBdAP4= github.com/containerd/containerd v1.6.6 h1:xJNPhbrmz8xAMDNoVjHy9YHtWwEQNS+CDkcIRh7t8Y0= github.com/containerd/containerd v1.6.6/go.mod h1:ZoP1geJldzCVY3Tonoz7b1IXk8rIX0Nltt5QE4OMNk0= -github.com/containerd/stargz-snapshotter/estargz v0.11.4 h1:LjrYUZpyOhiSaU7hHrdR82/RBoxfGWSaC0VeSSMXqnk= github.com/containerd/stargz-snapshotter/estargz v0.12.0 h1:idtwRTLjk2erqiYhPWy2L844By8NRFYEwYHcXhoIWPM= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= @@ -293,7 +272,6 @@ github.com/denis-tingajkin/go-header v0.4.2/go.mod h1:eLRHAVXzE5atsKAnNRDB90WHCF github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/distribution/distribution/v3 v3.0.0-20211118083504-a29a3c99a684 h1:DBZ2sN7CK6dgvHVpQsQj4sRMCbWTmd17l+5SUCjnQSY= github.com/distribution/distribution/v3 v3.0.0-20220526142353-ffbd94cbe269 h1:hbCT8ZPPMqefiAWD2ZKjn7ypokIGViTvBBg/ExLSdCk= github.com/docker/cli v20.10.17+incompatible h1:eO2KS7ZFeov5UJeaDmIs1NFEDRf32PaqRpvoEkKBy5M= github.com/docker/cli v20.10.17+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= @@ -318,9 +296,6 @@ github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7fo github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful/v3 v3.7.5-0.20220308211933-7c971ca4d0fd/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/emicklei/go-restful/v3 v3.8.0 h1:eCZ8ulSerjdAiaNpF7GxXIE7ZCMo1moN1qX+S609eVw= -github.com/emicklei/go-restful/v3 v3.8.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= @@ -343,7 +318,6 @@ github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQL github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch/v5 v5.5.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= @@ -356,20 +330,15 @@ github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGE github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= -github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= -github.com/fluxcd/pkg/kustomize v0.5.1 h1:151Ih34ltxN2z1e2mA5AvQONyE6phc4es57oVK3+plU= -github.com/fluxcd/pkg/kustomize v0.5.1/go.mod h1:58MFITy24bIbGI6cC3JkV/YpFQj648sVvgs0K1kraJw= github.com/fluxcd/pkg/kustomize v0.5.3 h1:WpxNOV/Yklp0p7Qv85VwBegq9fABuLR9qSWaAVa3+yc= github.com/fluxcd/pkg/kustomize v0.5.3/go.mod h1:zy1FLxkEDADUykCnrXqq6rVN48t3uMhAb3ao+zv0rFE= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y= -github.com/frankban/quicktest v1.13.0/go.mod h1:qLE0fzW0VuyUAJgPU19zByoIr0HtCHN/r/VLSOOIySU= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -384,7 +353,6 @@ github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49P github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= -github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-critic/go-critic v0.6.1/go.mod h1:SdNCfU0yF3UBjtaZGw6586/WocupMOJuiqgom5DsQxM= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= @@ -407,7 +375,6 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= -github.com/go-ldap/ldap/v3 v3.1.10/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd8luAK4Q= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= @@ -448,7 +415,6 @@ github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= -github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4= github.com/go-toolsmith/astcopy v1.0.0/go.mod h1:vrgyG+5Bxrnz4MZWPF+pI4R8h3qKRjjyvV/DSez4WVQ= github.com/go-toolsmith/astequal v1.0.0/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= @@ -480,8 +446,6 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= -github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ= -github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs= github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= @@ -539,7 +503,6 @@ github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA// github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= @@ -565,8 +528,6 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-containerregistry v0.9.0 h1:5Ths7RjxyFV0huKChQTgY6fLzvHhZMpLTFNja8U0/0w= -github.com/google/go-containerregistry v0.9.0/go.mod h1:9eq4BnSufyT1kHNffX+vSXVonaJ7yaIOulrKZejMxnQ= github.com/google/go-containerregistry v0.11.0 h1:Xt8x1adcREjFcmDoDK8OdOsjxu90PHkGuwNP8GiHMLM= github.com/google/go-containerregistry v0.11.0/go.mod h1:BBaYtsHPHA42uEgAvd/NejvAfPSlz281sJWqupjSxfk= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -664,57 +625,40 @@ github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9n github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v1.2.1 h1:YQsLlGDJgwhXFpucSPyVbCBviQtjlHv3jLTlp8YmtEw= -github.com/hashicorp/go-hclog v1.2.1/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-hclog v1.2.2 h1:ihRI7YFwcZdiSD7SIenIhHfQH3OuDvWerAUBZbeQS3M= github.com/hashicorp/go-hclog v1.2.2/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-kms-wrapping/entropy v0.1.0/go.mod h1:d1g9WGtAunDNpek8jUIEJnBlbgKS1N2Q61QkHiZyR1g= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= github.com/hashicorp/go-plugin v1.4.4 h1:NVdrSdFRt3SkZtNckJ6tog7gbpRrcbOjQi/rgF7JYWQ= github.com/hashicorp/go-plugin v1.4.4/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= -github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ= github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= -github.com/hashicorp/go-secure-stdlib/base62 v0.1.1/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw= -github.com/hashicorp/go-secure-stdlib/mlock v0.1.1/go.mod h1:zq93CJChV6L9QTfGKtfBxKqD7BqqXx5O04A/ns2p5+I= github.com/hashicorp/go-secure-stdlib/mlock v0.1.2 h1:p4AKXPPS24tO8Wc8i1gLvSKdmkiSY5xuju57czJ/IJQ= github.com/hashicorp/go-secure-stdlib/mlock v0.1.2/go.mod h1:zq93CJChV6L9QTfGKtfBxKqD7BqqXx5O04A/ns2p5+I= -github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= -github.com/hashicorp/go-secure-stdlib/parseutil v0.1.5/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 h1:om4Al8Oy7kCm/B86rLCLah4Dt5Aa0Fr5rYBG60OzwHQ= github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= -github.com/hashicorp/go-secure-stdlib/password v0.1.1/go.mod h1:9hH302QllNwu1o2TGYtSk8I8kTAN0ca1EHpwhm5Mmzo= github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= -github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.1/go.mod h1:l8slYwnJA26yBz+ErHpp2IRCLr0vuOMGBORIz4rRiAs= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.5.0 h1:O293SZ2Eg+AAYijkVK3jR786Am1bhDEh2GHT0tIVE5E= -github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= @@ -731,17 +675,10 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= -github.com/hashicorp/vault/api v1.6.0 h1:B8UUYod1y1OoiGHq9GtpiqSnGOUEWHaA26AY8RQEDY4= -github.com/hashicorp/vault/api v1.6.0/go.mod h1:h1K70EO2DgnBaTz5IsL6D5ERsNt5Pce93ueVS2+t0Xc= github.com/hashicorp/vault/api v1.7.2 h1:kawHE7s/4xwrdKbkmwQi0wYaIeUhk5ueek7ljuezCVQ= github.com/hashicorp/vault/api v1.7.2/go.mod h1:xbfA+1AvxFseDzxxdWaL0uO99n1+tndus4GCrtouy0M= -github.com/hashicorp/vault/sdk v0.5.0 h1:EED7p0OCU3OY5SAqJwSANofY1YKMytm+jDHDQ2EzGVQ= -github.com/hashicorp/vault/sdk v0.5.0/go.mod h1:UJZHlfwj7qUJG8g22CuxUgkdJouFrBNvBHCyx8XAPdo= github.com/hashicorp/vault/sdk v0.5.3 h1:PWY8sq/9pRrK9vUIy75qCH2Jd8oeENAgkaa/qbhzFrs= github.com/hashicorp/vault/sdk v0.5.3/go.mod h1:DoGraE9kKGNcVgPmTuX357Fm6WAx1Okvde8Vp3dPDoU= -github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 h1:xixZ2bWeofWV68J+x6AzmKuVM/JWCQwkWm6GW/MUR6I= -github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= @@ -768,7 +705,6 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/jgautheron/goconst v1.5.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= -github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= github.com/jhump/protoreflect v1.6.1 h1:4/2yi5LyDPP7nN+Hiird1SAJ6YoxUm13/oxHGRnbPd8= github.com/jhump/protoreflect v1.6.1/go.mod h1:RZQ/lnuN+zqeRVpQigTwO6o0AJUkxbnSnpuG7toUTG4= github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= @@ -817,10 +753,10 @@ github.com/kisielk/errcheck v1.6.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.15.6 h1:6D9PcO8QWu0JyaQ2zUMmu16T1T+zjjEpP91guRsvDfY= -github.com/klauspost/compress v1.15.6/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= +github.com/kluctl/kluctl-python-deps v0.0.0-20220818145819-a2811c3f89fd h1:CTUYp06sH7d38kfHY+tysa9jhQuo1C9ssSlVR1hFuDA= +github.com/kluctl/kluctl-python-deps v0.0.0-20220818145819-a2811c3f89fd/go.mod h1:w4cTz1av5JFX9bMTB7Vm2qtmm5+eU+ncvB1/oyjVwOw= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -935,7 +871,6 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= -github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= @@ -993,11 +928,8 @@ github.com/nishanths/predeclared v0.2.1/go.mod h1:HvkGJcA3naj4lOwnFXFDkFxVtSqQMB github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/ohler55/ojg v1.14.2 h1:EHdrwmDrOuTGpj1W2LrT/yKeUOkLMIk1cTYcm42Sjj0= -github.com/ohler55/ojg v1.14.2/go.mod h1:/Y5dGWkekv9ocnUixuETqiL58f+5pAsUfg5P8e7Pa2o= github.com/ohler55/ojg v1.14.3 h1:kaKNsntZ0PuoXPXCY4kjPDbHOLXqokor6Deq/oVxAR0= github.com/ohler55/ojg v1.14.3/go.mod h1:/Y5dGWkekv9ocnUixuETqiL58f+5pAsUfg5P8e7Pa2o= -github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= @@ -1013,6 +945,7 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108 github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo/v2 v2.1.4 h1:GNapqRSid3zijZ9H77KrgVG4/8KqiyRsxcSxe+7ApXY= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= @@ -1037,16 +970,12 @@ github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCko github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU= -github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= github.com/pelletier/go-toml/v2 v2.0.2 h1:+jQXlF3scKIcSEKkdHzXhCTDLPFi5r1wnK6yPS+49Gw= github.com/pelletier/go-toml/v2 v2.0.2/go.mod h1:MovirKjgVRESsAvNZlAjtFwV867yGuwRkXbG66OzopI= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw= -github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= -github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -1088,8 +1017,6 @@ github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8b github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.34.0 h1:RBmGO9d/FVjqHT0yUGQwBJhkwKV+wPCn7KGpvfab0uE= -github.com/prometheus/common v0.34.0/go.mod h1:gB3sOl7P0TvJabZpLY5uQMpUqRCPPCyRLCZYc7JZTNE= github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= @@ -1099,7 +1026,6 @@ github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDa github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= @@ -1116,7 +1042,6 @@ github.com/quasilyte/go-ruleguard/rules v0.0.0-20210428214800-545e0d2e0bf7/go.mo github.com/quasilyte/regex/syntax v0.0.0-20200407221936-30656e2c4a95/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= github.com/r3labs/diff/v2 v2.15.1 h1:EOrVqPUzi+njlumoqJwiS/TgGgmZo83619FNDB9xQUg= github.com/r3labs/diff/v2 v2.15.1/go.mod h1:I8noH9Fc2fjSaMxqF3G2lhDdC0b+JXCfyx85tWFM9kc= -github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.3.1 h1:SDPP7SHNl1L7KrEFCSJslJ/DM9DT02Nq2C61XrfHMmk= github.com/rivo/uniseg v0.3.1/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= @@ -1129,8 +1054,6 @@ github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6po github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= -github.com/rubenv/sql-migrate v1.1.1 h1:haR5Hn8hbW9/SpAICrXoZqXnywS7Q5WijwkQENPeNWY= -github.com/rubenv/sql-migrate v1.1.1/go.mod h1:/7TZymwxN8VWumcIxw1jjHEcR1djpdkMHQPT4FWdnbQ= github.com/rubenv/sql-migrate v1.1.2 h1:9M6oj4e//owVVHYrFISmY9LBRw6gzkCNmD9MV36tZeQ= github.com/rubenv/sql-migrate v1.1.2/go.mod h1:/7TZymwxN8VWumcIxw1jjHEcR1djpdkMHQPT4FWdnbQ= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= @@ -1164,7 +1087,6 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= @@ -1179,8 +1101,6 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= -github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= -github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw= github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= @@ -1192,7 +1112,6 @@ github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3 github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= -github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= @@ -1224,7 +1143,6 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= @@ -1333,8 +1251,6 @@ go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4 go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= -go.starlark.net v0.0.0-20220328144851-d1966c6b9fcd h1:Uo/x0Ir5vQJ+683GXB9Ug+4fcjsbp7z7Ul8UaZbhsRM= -go.starlark.net v0.0.0-20220328144851-d1966c6b9fcd/go.mod h1:t3mmBBPzAVvK0L0n1drDmrQsJ8FoIx4INCqVMTr/Zo0= go.starlark.net v0.0.0-20220714194419-4cadf0a12139 h1:zMemyQYZSyEdPaUFixYICrXf/0Rfnil7+jiQRf5IBZ0= go.starlark.net v0.0.0-20220714194419-4cadf0a12139/go.mod h1:t3mmBBPzAVvK0L0n1drDmrQsJ8FoIx4INCqVMTr/Zo0= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -1380,8 +1296,6 @@ golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= -golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1422,7 +1336,6 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= -golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1485,7 +1398,6 @@ golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220607020251-c690dde0001d h1:4SFsTMi4UahlKoloni7L4eYzhFRifURQLw+yv0QDCx8= golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220802222814-0bcc04d9c69b h1:3ogNYyK4oIQdIKzTu68hQrr4iuVxF3AxKl9Aj/eDrw0= golang.org/x/net v0.0.0-20220802222814-0bcc04d9c69b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= @@ -1509,8 +1421,6 @@ golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220524215830-622c5d57e401 h1:zwrSfklXn0gxyLRX/aR+q6cgHbV/ItVyzbPlbA+dkAw= -golang.org/x/oauth2 v0.0.0-20220524215830-622c5d57e401/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= golang.org/x/oauth2 v0.0.0-20220722155238-128564f6959c h1:q3gFqPqH7NVofKo3c3yETAP//pPI+G5mvB7qqj1Y5kY= golang.org/x/oauth2 v0.0.0-20220722155238-128564f6959c/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= @@ -1526,7 +1436,6 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8= golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1633,7 +1542,6 @@ golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1642,8 +1550,6 @@ golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM= -golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 h1:Q5284mrmYTpACcm+eAKjKJH48BBwSyfJqmmGDTtT8Vc= golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1663,8 +1569,6 @@ golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220411224347-583f2d630306 h1:+gHMid33q6pen7kv9xvT+JRinntgeXO2AeZVd0AWD3w= -golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 h1:ftMN5LMiBFjbzleLqtoBZk7KdJwhuybIU+FckUHgoyQ= golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1916,8 +1820,6 @@ google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220602131408-e326c6e8e9c8 h1:qRu95HZ148xXw+XeZ3dvqe85PxH4X8+jIo0iRPKcEnM= -google.golang.org/genproto v0.0.0-20220602131408-e326c6e8e9c8/go.mod h1:yKyY4AMRwFiC8yMMNaMi+RkCnjZJt9LoWuvhXjMs+To= google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= google.golang.org/genproto v0.0.0-20220802133213-ce4fa296bf78 h1:QntLWYqZeuBtJkth3m/6DLznnI0AHJr+AgJXvVh/izw= @@ -1952,12 +1854,10 @@ google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8= google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.48.0 h1:rQOsyJ/8+ufEDJd/Gdsz7HG220Mh9HAhFHRGnIjda0w= google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= @@ -1975,7 +1875,6 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= @@ -2001,7 +1900,6 @@ gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= @@ -2028,8 +1926,6 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= -helm.sh/helm/v3 v3.9.0 h1:qDSWViuF6SzZX5s5AB/NVRGWmdao7T5j4S4ebIkMGag= -helm.sh/helm/v3 v3.9.0/go.mod h1:fzZfyslcPAWwSdkXrXlpKexFeE2Dei8N27FFQWt+PN0= helm.sh/helm/v3 v3.9.2 h1:bx7kdhr5VAhYoWv9bIdT1C6qWR+/7SIoPCwLx22l78g= helm.sh/helm/v3 v3.9.2/go.mod h1:y/dJc/0Lzcn40jgd85KQXnufhFF7sr4v6L/vYMLRaRM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -2040,63 +1936,38 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.2.1/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY= -k8s.io/api v0.24.1 h1:BjCMRDcyEYz03joa3K1+rbshwh1Ay6oB53+iUx2H8UY= -k8s.io/api v0.24.1/go.mod h1:JhoOvNiLXKTPQ60zh2g0ewpA+bnEYf5q44Flhquh4vQ= k8s.io/api v0.24.3 h1:tt55QEmKd6L2k5DP6G/ZzdMQKvG5ro4H4teClqm0sTY= k8s.io/api v0.24.3/go.mod h1:elGR/XSZrS7z7cSZPzVWaycpJuGIw57j9b95/1PdJNI= -k8s.io/apiextensions-apiserver v0.24.1 h1:5yBh9+ueTq/kfnHQZa0MAo6uNcPrtxPMpNQgorBaKS0= -k8s.io/apiextensions-apiserver v0.24.1/go.mod h1:A6MHfaLDGfjOc/We2nM7uewD5Oa/FnEbZ6cD7g2ca4Q= k8s.io/apiextensions-apiserver v0.24.3 h1:kyx+Tmro1qEsTUr07ZGQOfvTsF61yn+AxnxytBWq8As= k8s.io/apiextensions-apiserver v0.24.3/go.mod h1:cL0xkmUefpYM4f6IuOau+6NMFEIh6/7wXe/O4vPVJ8A= -k8s.io/apimachinery v0.24.1 h1:ShD4aDxTQKN5zNf8K1RQ2u98ELLdIW7jEnlO9uAMX/I= -k8s.io/apimachinery v0.24.1/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= k8s.io/apimachinery v0.24.3 h1:hrFiNSA2cBZqllakVYyH/VyEh4B581bQRmqATJSeQTg= k8s.io/apimachinery v0.24.3/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= -k8s.io/apiserver v0.24.1 h1:LAA5UpPOeaREEtFAQRUQOI3eE5So/j5J3zeQJjeLdz4= -k8s.io/apiserver v0.24.1/go.mod h1:dQWNMx15S8NqJMp0gpYfssyvhYnkilc1LpExd/dkLh0= k8s.io/apiserver v0.24.3 h1:J8CKjUaZopT0hSgxjzUyp3T1GK78iixxOuFpEC0MI3k= k8s.io/apiserver v0.24.3/go.mod h1:aXfwtIn4U27B7lYs5f2BKgz6DRbgWy+HJeYReN1jLJ8= -k8s.io/cli-runtime v0.24.1 h1:IW6L8dRBq+pPTzvXcB+m/hOabzbqXy57Bqo4XxmW7DY= -k8s.io/cli-runtime v0.24.1/go.mod h1:14aVvCTqkA7dNXY51N/6hRY3GUjchyWDOwW84qmR3bs= k8s.io/cli-runtime v0.24.3 h1:O9YvUHrDSCQUPlsqVmaqDrueqjpJ7IO6Yas9B6xGSoo= k8s.io/cli-runtime v0.24.3/go.mod h1:In84wauoMOqa7JDvDSXGbf8lTNlr70fOGpYlYfJtSqA= -k8s.io/client-go v0.24.1 h1:w1hNdI9PFrzu3OlovVeTnf4oHDt+FJLd9Ndluvnb42E= -k8s.io/client-go v0.24.1/go.mod h1:f1kIDqcEYmwXS/vTbbhopMUbhKp2JhOeVTfxgaCIlF8= k8s.io/client-go v0.24.3 h1:Nl1840+6p4JqkFWEW2LnMKU667BUxw03REfLAVhuKQY= k8s.io/client-go v0.24.3/go.mod h1:AAovolf5Z9bY1wIg2FZ8LPQlEdKHjLI7ZD4rw920BJw= -k8s.io/code-generator v0.24.1/go.mod h1:dpVhs00hTuTdTY6jvVxvTFCk6gSMrtfRydbhZwHI15w= k8s.io/code-generator v0.24.3/go.mod h1:dpVhs00hTuTdTY6jvVxvTFCk6gSMrtfRydbhZwHI15w= -k8s.io/component-base v0.24.1 h1:APv6W/YmfOWZfo+XJ1mZwep/f7g7Tpwvdbo9CQLDuts= -k8s.io/component-base v0.24.1/go.mod h1:DW5vQGYVCog8WYpNob3PMmmsY8A3L9QZNg4j/dV3s38= k8s.io/component-base v0.24.3 h1:u99WjuHYCRJjS1xeLOx72DdRaghuDnuMgueiGMFy1ec= k8s.io/component-base v0.24.3/go.mod h1:bqom2IWN9Lj+vwAkPNOv2TflsP1PeVDIwIN0lRthxYY= -k8s.io/component-helpers v0.24.1/go.mod h1:q5Z1pWV/QfX9ThuNeywxasiwkLw9KsR4Q9TAOdb/Y3s= k8s.io/component-helpers v0.24.3/go.mod h1:/1WNW8TfBOijQ1ED2uCHb4wtXYWDVNMqUll8h36iNVo= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/gengo v0.0.0-20211129171323-c02415ce4185/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.60.1 h1:VW25q3bZx9uE3vvdL6M8ezOX79vA2Aq1nEWLqNQclHc= k8s.io/klog/v2 v2.60.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/klog/v2 v2.70.1 h1:7aaoSdahviPmR+XkS7FyxlkkXs6tHISSG03RxleQAVQ= k8s.io/klog/v2 v2.70.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42/go.mod h1:Z/45zLw8lUo4wdiUkI+v/ImEGAvu3WatcZl3lPMR4Rk= -k8s.io/kube-openapi v0.0.0-20220401212409-b28bf2818661/go.mod h1:daOouuuwd9JXpv1L7Y34iV3yf6nxzipkKMWWlqlvK9M= -k8s.io/kube-openapi v0.0.0-20220603121420-31174f50af60 h1:cE/M8rmDQgibspuSm+X1iW16ByTImtEaapgaHoVSLX4= -k8s.io/kube-openapi v0.0.0-20220603121420-31174f50af60/go.mod h1:ouUzE1U2mEv//HRoBwYLFE5pdqjIebvtX361vtEIlBI= k8s.io/kube-openapi v0.0.0-20220627174259-011e075b9cb8 h1:yEQKdMCjzAOvGeiTwG4hO/hNVNtDOuUFvMUZ0OlaIzs= k8s.io/kube-openapi v0.0.0-20220627174259-011e075b9cb8/go.mod h1:mbJ+NSUoAhuR14N0S63bPkh8MGVSo3VYSGZtH/mfMe0= -k8s.io/kubectl v0.24.1 h1:gxcjHrnwntV1c+G/BHWVv4Mtk8CQJ0WTraElLBG+ddk= -k8s.io/kubectl v0.24.1/go.mod h1:NzFqQ50B004fHYWOfhHTrAm4TY6oGF5FAAL13LEaeUI= k8s.io/kubectl v0.24.3 h1:PqY8ho/S/KuE2/hCC3Iee7X+lOtARYo0LQsNzvV/edE= k8s.io/kubectl v0.24.3/go.mod h1:PYLcvw96sC1NLbxZEDbdlOEd6/C76VIWjGmWV5QjSk0= -k8s.io/metrics v0.24.1/go.mod h1:vMs5xpcOyY9D+/XVwlaw8oUHYCo6JTGBCZfyXOOkAhE= k8s.io/metrics v0.24.3/go.mod h1:p1M0lhMySWfhISkSd3HEj8xIgrVnJTK3PPhFq2rA3To= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 h1:HNSDgDCrr/6Ly3WEGKZftiE7IY19Vz2GdbOCyI4qqhc= k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed h1:jAne/RjBTyawwAy0utX5eqigAwz/lQhTmy+Hr/Cpue4= k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= @@ -2104,8 +1975,6 @@ mvdan.cc/gofumpt v0.1.1/go.mod h1:yXG1r1WqZVKWbVRtBWKWX9+CxGYfA51nSomhM0woR48= mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= mvdan.cc/unparam v0.0.0-20210104141923-aac4ce9116a7/go.mod h1:hBpJkZE8H/sb+VRFvw2+rBpHNsTBcvSpk61hr8mzXZE= -oras.land/oras-go v1.1.1 h1:gI00ftziRivKXaw1BdMeEoIA4uBgga33iVlOsEwefFs= -oras.land/oras-go v1.1.1/go.mod h1:n2TE1ummt9MUyprGhT+Q7kGZUF4kVUpYysPFxeV2IpQ= oras.land/oras-go v1.2.0 h1:yoKosVIbsPoFMqAIFHTnrmOuafHal+J/r+I5bdbVWu4= oras.land/oras-go v1.2.0/go.mod h1:pFNs7oHp2dYsYMSS82HaX5l4mpnGO7hbpPN6EWH2ltc= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= @@ -2113,26 +1982,19 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.30/go.mod h1:fEO7lRTdivWO2qYVCVG7dEADOMo/MLDCVr8So2g88Uw= sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY= -sigs.k8s.io/json v0.0.0-20220525155127-227cbc7cc124 h1:2sgAQQcY0dEW2SsQwTXhQV4vO6+rSslYx8K3XmM5hqQ= -sigs.k8s.io/json v0.0.0-20220525155127-227cbc7cc124/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/kind v0.14.0 h1:cNmI3jGBvp7UegEGbC5we8plDtCUmaNRL+bod7JoSCE= sigs.k8s.io/kind v0.14.0/go.mod h1:UrFRPHG+2a5j0Q7qiR4gtJ4rEyn8TuMQwuOPf+m4oHg= sigs.k8s.io/kustomize/api v0.11.4/go.mod h1:k+8RsqYbgpkIrJ4p9jcdPqe8DprLxFUUO0yNOq8C+xI= -sigs.k8s.io/kustomize/api v0.11.5 h1:vLDp++YAX7iy2y2CVPJNy9pk9CY8XaUKgHkjbVtnWag= -sigs.k8s.io/kustomize/api v0.11.5/go.mod h1:2UDpxS6AonWXow2ZbySd4AjUxmdXLeTlvGBC46uSiq8= sigs.k8s.io/kustomize/api v0.12.1 h1:7YM7gW3kYBwtKvoY216ZzY+8hM+lV53LUayghNRJ0vM= sigs.k8s.io/kustomize/api v0.12.1/go.mod h1:y3JUhimkZkR6sbLNwfJHxvo1TCLwuwm14sCYnkH6S1s= sigs.k8s.io/kustomize/cmd/config v0.10.6/go.mod h1:/S4A4nUANUa4bZJ/Edt7ZQTyKOY9WCER0uBS1SW2Rco= sigs.k8s.io/kustomize/kustomize/v4 v4.5.4/go.mod h1:Zo/Xc5FKD6sHl0lilbrieeGeZHVYCA4BzxeAaLI05Bg= sigs.k8s.io/kustomize/kyaml v0.13.6/go.mod h1:yHP031rn1QX1lr/Xd934Ri/xdVNG8BE2ECa78Ht/kEg= -sigs.k8s.io/kustomize/kyaml v0.13.7 h1:/EZ/nPaLUzeJKF/BuJ4QCuMVJWiEVoI8iftOHY3g3tk= -sigs.k8s.io/kustomize/kyaml v0.13.7/go.mod h1:6K+IUOuir3Y7nucPRAjw9yth04KSWBnP5pqUTGwj/qU= sigs.k8s.io/kustomize/kyaml v0.13.9 h1:Qz53EAaFFANyNgyOEJbT/yoIHygK40/ZcvU3rgry2Tk= sigs.k8s.io/kustomize/kyaml v0.13.9/go.mod h1:QsRbD0/KcU+wdk0/L0fIp2KLnohkVzs6fQ85/nOXac4= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y= sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= diff --git a/pkg/jinja2/embed/python_src.dummy b/pkg/jinja2/embed/python_src.dummy deleted file mode 100644 index e69de29bb..000000000 diff --git a/pkg/jinja2/generate/main.go b/pkg/jinja2/generate/main.go deleted file mode 100644 index 9da3d4f4e..000000000 --- a/pkg/jinja2/generate/main.go +++ /dev/null @@ -1,55 +0,0 @@ -package main - -import ( - "github.com/kluctl/kluctl/v2/pkg/utils/embed_util/packer" - "os" - "os/exec" - "path/filepath" - "strings" -) - -func main() { - pipWheel() -} - -func pipWheel() { - _ = os.RemoveAll("python_src/wheel") - _ = os.MkdirAll("python_src/wheel", 0o700) - - cmd := exec.Command("pip3", "wheel", "-r", "../requirements.txt") - cmd.Dir = "python_src/wheel" - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - err := cmd.Run() - if err != nil { - panic(err) - } - - wheels, err := os.ReadDir("python_src/wheel") - if err != nil { - panic(err) - } - for _, w := range wheels { - if !strings.HasSuffix(w.Name(), ".whl") { - continue - } - - cmd = exec.Command("unzip", w.Name()) - cmd.Dir = "python_src/wheel" - - err = cmd.Run() - if err != nil { - panic(err) - } - - err = os.Remove(filepath.Join("python_src/wheel", w.Name())) - if err != nil { - panic(err) - } - } - - err = packer.Pack("embed/python_src.tar.gz", "python_src", "*") - if err != nil { - panic(err) - } -} diff --git a/pkg/jinja2/jinja2_renderer.go b/pkg/jinja2/jinja2_renderer.go index 8895f2366..e5b64ef73 100644 --- a/pkg/jinja2/jinja2_renderer.go +++ b/pkg/jinja2/jinja2_renderer.go @@ -5,7 +5,7 @@ import ( "bytes" "encoding/json" "fmt" - "github.com/kluctl/kluctl/v2/pkg/python" + "github.com/kluctl/kluctl-python-deps/pkg/python" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "io" "io/ioutil" diff --git a/pkg/jinja2/python_src/dummy.go b/pkg/jinja2/python_src/dummy.go deleted file mode 100644 index 60d276519..000000000 --- a/pkg/jinja2/python_src/dummy.go +++ /dev/null @@ -1 +0,0 @@ -package python_src diff --git a/pkg/jinja2/python_src/requirements.txt b/pkg/jinja2/python_src/requirements.txt deleted file mode 100644 index 9ee2b287d..000000000 --- a/pkg/jinja2/python_src/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -jinja2===3.1.2 -click==8.1.3 -jsonpath-ng==1.5.3 -pyyaml==6.0 diff --git a/pkg/jinja2/source.go b/pkg/jinja2/source.go index d9ab49bb4..b80377261 100644 --- a/pkg/jinja2/source.go +++ b/pkg/jinja2/source.go @@ -1,16 +1,22 @@ package jinja2 import ( + "crypto/sha256" "embed" + "encoding/binary" + "encoding/hex" + "fmt" "github.com/kluctl/kluctl/v2/pkg/utils" - "github.com/kluctl/kluctl/v2/pkg/utils/embed_util" + "github.com/rogpeppe/go-internal/lockedfile" + "io/fs" + "os" "path/filepath" ) -//go:generate go run ./generate +//go:embed python_src +var _pythonSrc embed.FS +var pythonSrc, _ = fs.Sub(_pythonSrc, "python_src") -//go:embed embed/python_src.* -var pythonSrc embed.FS var pythonSrcExtracted string func init() { @@ -22,23 +28,66 @@ func init() { } func extractSource() (string, error) { - tgz, err := pythonSrc.Open("embed/python_src.tar.gz") + hash := calcEmbeddedHash(pythonSrc) + + targetPath := filepath.Join(utils.GetTmpBaseDir(), fmt.Sprintf("jinja2-%s", hash[:16])) + + lock, err := lockedfile.Create(targetPath + ".lock") if err != nil { return "", err } - defer tgz.Close() + defer lock.Close() - fileList, err := pythonSrc.Open("embed/python_src.tar.gz.files") + err = fs.WalkDir(pythonSrc, ".", func(path string, d fs.DirEntry, err error) error { + if d == nil || d.IsDir() { + return nil + } + data, err := fs.ReadFile(pythonSrc, path) + if err != nil { + return err + } + targetPath2 := filepath.Join(targetPath, path) + err = os.MkdirAll(filepath.Dir(targetPath2), 0o755) + if err != nil { + return err + } + + err = os.WriteFile(targetPath2+".tmp", data, 0o644) + if err != nil { + return err + } + err = os.Rename(targetPath2+".tmp", targetPath2) + if err != nil { + return err + } + return nil + }) if err != nil { return "", err } - defer fileList.Close() - libPath := filepath.Join(utils.GetTmpBaseDir(), "jinja2-src") - libPath, err = embed_util.ExtractTarToTmp(tgz, fileList, libPath) + return targetPath, nil +} + +func calcEmbeddedHash(fs1 fs.FS) string { + h := sha256.New() + err := fs.WalkDir(fs1, ".", func(path string, d fs.DirEntry, err error) error { + _ = binary.Write(h, binary.LittleEndian, path) + if d.IsDir() { + _ = binary.Write(h, binary.LittleEndian, "dir") + } else { + _ = binary.Write(h, binary.LittleEndian, "regular") + data, err := fs.ReadFile(fs1, path) + if err != nil { + panic(err) + } + _ = binary.Write(h, binary.LittleEndian, len(data)) + _ = binary.Write(h, binary.LittleEndian, data) + } + return nil + }) if err != nil { - return "", err + panic(err) } - - return libPath, nil + return hex.EncodeToString(h.Sum(nil)) } diff --git a/pkg/jinja2/tools.go b/pkg/jinja2/tools.go deleted file mode 100644 index 839cd9103..000000000 --- a/pkg/jinja2/tools.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build tools -// +build tools - -package tools - -import ( - _ "github.com/kluctl/kluctl/v2/pkg/jinja2/generate" - _ "github.com/kluctl/kluctl/v2/pkg/jinja2/python_src" -) diff --git a/pkg/python/.gitignore b/pkg/python/.gitignore deleted file mode 100644 index 01ec3b178..000000000 --- a/pkg/python/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.tar.gz -*.files diff --git a/pkg/python/cmd.go b/pkg/python/cmd.go deleted file mode 100644 index eea32c554..000000000 --- a/pkg/python/cmd.go +++ /dev/null @@ -1,24 +0,0 @@ -package python - -import ( - "fmt" - "os" - "os/exec" - "path/filepath" - "runtime" -) - -func PythonCmd(args []string) *exec.Cmd { - var exePath string - if runtime.GOOS == "windows" { - exePath = filepath.Join(embeddedPythonPath, "python.exe") - } else { - exePath = filepath.Join(embeddedPythonPath, "bin/python3") - } - - cmd := exec.Command(exePath, args...) - cmd.Env = os.Environ() - cmd.Env = append(cmd.Env, fmt.Sprintf("PYTHONHOME=%s", embeddedPythonPath)) - - return cmd -} diff --git a/pkg/python/embed.go b/pkg/python/embed.go deleted file mode 100644 index 500e68ed8..000000000 --- a/pkg/python/embed.go +++ /dev/null @@ -1,40 +0,0 @@ -package python - -import ( - "fmt" - "github.com/kluctl/kluctl/v2/pkg/utils" - "github.com/kluctl/kluctl/v2/pkg/utils/embed_util" - "path/filepath" - "runtime" -) - -//go:generate go run ./generate - -var embeddedPythonPath string - -func init() { - embeddedPythonPath = decompressPython() -} - -func decompressPython() string { - tarName := fmt.Sprintf("embed/python-%s-%s.tar.gz", runtime.GOOS, runtime.GOARCH) - tgz, err := pythonLib.Open(tarName) - if err != nil { - panic(err) - } - defer tgz.Close() - - fileList, err := pythonLib.Open(tarName + ".files") - if err != nil { - panic(err) - } - defer fileList.Close() - - path := filepath.Join(utils.GetTmpBaseDir(), fmt.Sprintf("python-%s", runtime.GOOS)) - path, err = embed_util.ExtractTarToTmp(tgz, fileList, path) - if err != nil { - panic(err) - } - - return path -} diff --git a/pkg/python/embed/python-darwin-amd64.dummy b/pkg/python/embed/python-darwin-amd64.dummy deleted file mode 100644 index e69de29bb..000000000 diff --git a/pkg/python/embed/python-darwin-arm64.dummy b/pkg/python/embed/python-darwin-arm64.dummy deleted file mode 100644 index e69de29bb..000000000 diff --git a/pkg/python/embed/python-linux-amd64.dummy b/pkg/python/embed/python-linux-amd64.dummy deleted file mode 100644 index e69de29bb..000000000 diff --git a/pkg/python/embed/python-linux-arm64.dummy b/pkg/python/embed/python-linux-arm64.dummy deleted file mode 100644 index e69de29bb..000000000 diff --git a/pkg/python/embed/python-windows-amd64.dummy b/pkg/python/embed/python-windows-amd64.dummy deleted file mode 100644 index e69de29bb..000000000 diff --git a/pkg/python/embed_darwin_amd64.go b/pkg/python/embed_darwin_amd64.go deleted file mode 100644 index 110c5af57..000000000 --- a/pkg/python/embed_darwin_amd64.go +++ /dev/null @@ -1,6 +0,0 @@ -package python - -import "embed" - -//go:embed embed/python-darwin-amd64.* -var pythonLib embed.FS diff --git a/pkg/python/embed_darwin_arm64.go b/pkg/python/embed_darwin_arm64.go deleted file mode 100644 index f14b56a5f..000000000 --- a/pkg/python/embed_darwin_arm64.go +++ /dev/null @@ -1,6 +0,0 @@ -package python - -import "embed" - -//go:embed embed/python-darwin-arm64.* -var pythonLib embed.FS diff --git a/pkg/python/embed_linux_amd64.go b/pkg/python/embed_linux_amd64.go deleted file mode 100644 index 99838aee9..000000000 --- a/pkg/python/embed_linux_amd64.go +++ /dev/null @@ -1,6 +0,0 @@ -package python - -import "embed" - -//go:embed embed/python-linux-amd64.* -var pythonLib embed.FS diff --git a/pkg/python/embed_linux_arm64.go b/pkg/python/embed_linux_arm64.go deleted file mode 100644 index 158c5087e..000000000 --- a/pkg/python/embed_linux_arm64.go +++ /dev/null @@ -1,6 +0,0 @@ -package python - -import "embed" - -//go:embed embed/python-linux-arm64.* -var pythonLib embed.FS diff --git a/pkg/python/embed_windows_amd64.go b/pkg/python/embed_windows_amd64.go deleted file mode 100644 index 65e5e2c9c..000000000 --- a/pkg/python/embed_windows_amd64.go +++ /dev/null @@ -1,6 +0,0 @@ -package python - -import "embed" - -//go:embed embed/python-windows-amd64.* -var pythonLib embed.FS diff --git a/pkg/python/generate/main.go b/pkg/python/generate/main.go deleted file mode 100644 index 9cc24b145..000000000 --- a/pkg/python/generate/main.go +++ /dev/null @@ -1,195 +0,0 @@ -package main - -import ( - "fmt" - "github.com/klauspost/compress/zstd" - "github.com/kluctl/kluctl/v2/pkg/utils" - "github.com/kluctl/kluctl/v2/pkg/utils/embed_util/packer" - log "github.com/sirupsen/logrus" - "io/ioutil" - "net/http" - "os" - "path/filepath" - "sync" -) - -const ( - pythonVersionBase = "3.10" - pythonVersionFull = "3.10.5" - pythonStandaloneVersion = "20220630" -) - -var pythonDists = map[string]string{ - "linux": "unknown-linux-gnu-lto-full", - "darwin": "apple-darwin-lto-full", - "windows": "pc-windows-msvc-shared-pgo-full", -} - -var archMapping = map[string]string{ - "amd64": "x86_64", - "386": "i686", - "arm64": "aarch64", -} - -var removeLibs = []string{ - "site-packages", - "venv", - "ensurepip", - "idlelib", - "distutils", - "pydoc_data", - "asyncio", - "email", - "tkinter", - "lib2to3", - "xml", - "multiprocessing", - "unittest", -} - -var downloadLock sync.Mutex - -func main() { - var wg sync.WaitGroup - - nixPatterns := []string{ - "bin", - "lib/*.so*", - "lib/*.dylib", - "lib/python3.*", - } - winPatterns := []string{ - "Lib", - "DLLs", - "*.dll", - "*.exe", - } - type job struct { - os string - arch string - packSubDir string - out string - patterns []string - } - jobs := []job{ - {"linux", "amd64", "python/install", "embed/python-linux-amd64.tar.gz", nixPatterns}, - {"linux", "arm64", "python/install", "embed/python-linux-arm64.tar.gz", nixPatterns}, - {"darwin", "amd64", "python/install", "embed/python-darwin-amd64.tar.gz", nixPatterns}, - {"darwin", "arm64", "python/install", "embed/python-darwin-arm64.tar.gz", nixPatterns}, - {"windows", "amd64", "python/install", "embed/python-windows-amd64.tar.gz", winPatterns}, - } - for _, j := range jobs { - j := j - wg.Add(1) - go func() { - downloadAndPack(j.os, j.arch, j.packSubDir, j.out, j.patterns) - wg.Done() - }() - } - wg.Wait() -} - -func downloadAndPack(osName string, arch string, packSubdir string, out string, patterns []string) { - dist, ok := pythonDists[osName] - if !ok { - log.Panicf("no dist for %s", osName) - } - - downloadPath := download(osName, arch, dist) - archiveBytes, _ := ioutil.ReadFile(downloadPath) - hash := utils.Sha256Bytes(archiveBytes) - - extractPath := downloadPath + ".extracted" - if utils.Exists(filepath.Join(extractPath, hash)) { - log.Infof("skipping extract of %s", extractPath) - pack(out, filepath.Join(extractPath, packSubdir), patterns) - return - } - - _ = os.RemoveAll(extractPath) - extract(downloadPath, extractPath) - - for _, lib := range removeLibs { - _ = os.RemoveAll(filepath.Join(extractPath, "python", "install", "lib", fmt.Sprintf("python%s", pythonVersionBase), lib)) - _ = os.RemoveAll(filepath.Join(extractPath, "python", "install", "Lib", lib)) - } - - _ = utils.Touch(filepath.Join(extractPath, hash)) - - pack(out, filepath.Join(extractPath, packSubdir), patterns) -} - -func pack(out string, dir string, patterns []string) { - err := packer.Pack(out, dir, patterns...) - if err != nil { - log.Panic(err) - } -} - -func download(osName, arch, dist string) string { - downloadLock.Lock() - defer downloadLock.Unlock() - - pythonArch, ok := archMapping[arch] - if !ok { - log.Errorf("arch %s not supported", arch) - os.Exit(1) - } - fname := fmt.Sprintf("cpython-%s+%s-%s-%s.tar.zst", pythonVersionFull, pythonStandaloneVersion, pythonArch, dist) - downloadPath := filepath.Join(utils.GetTmpBaseDir(), fname) - downloadUrl := fmt.Sprintf("https://github.com/indygreg/python-build-standalone/releases/download/%s/%s", pythonStandaloneVersion, fname) - - if _, err := os.Stat(downloadPath); err == nil { - log.Infof("skipping download of %s", downloadUrl) - return downloadPath - } - - log.Infof("downloading %s", downloadUrl) - - r, err := http.Get(downloadUrl) - if err != nil { - log.Errorf("download failed: %v", err) - os.Exit(1) - } - if r.StatusCode == http.StatusNotFound { - log.Errorf("404 not found") - os.Exit(1) - } - defer r.Body.Close() - - fileData, err := ioutil.ReadAll(r.Body) - - err = ioutil.WriteFile(downloadPath, fileData, 0o640) - if err != nil { - log.Errorf("writing file failed: %v", err) - os.Remove(downloadPath) - os.Exit(1) - } - - return downloadPath -} - -func extract(archivePath string, targetPath string) string { - f, err := os.Open(archivePath) - if err != nil { - log.Errorf("opening file failed: %v", err) - os.Exit(1) - } - defer f.Close() - - z, err := zstd.NewReader(f) - if err != nil { - log.Errorf("decompression failed: %v", err) - os.Exit(1) - } - defer z.Close() - - log.Infof("decompressing %s", archivePath) - err = utils.ExtractTarStream(z, targetPath) - if err != nil { - log.Errorf("decompression failed: %v", err) - os.Exit(1) - } - - return targetPath -} diff --git a/pkg/python/tools.go b/pkg/python/tools.go deleted file mode 100644 index 48d6d8a70..000000000 --- a/pkg/python/tools.go +++ /dev/null @@ -1,8 +0,0 @@ -//go:build tools -// +build tools - -package tools - -import ( - _ "github.com/kluctl/kluctl/v2/pkg/python/generate" -) diff --git a/pkg/utils/embed_util/extract.go b/pkg/utils/embed_util/extract.go deleted file mode 100644 index e3c5dc0d4..000000000 --- a/pkg/utils/embed_util/extract.go +++ /dev/null @@ -1,134 +0,0 @@ -package embed_util - -import ( - "fmt" - "github.com/kluctl/kluctl/v2/pkg/utils" - "github.com/rogpeppe/go-internal/lockedfile" - "io" - "io/fs" - "io/ioutil" - "os" - "path/filepath" - "strconv" - "strings" -) - -func ExtractTarToTmp(r io.Reader, fileListR io.Reader, targetPrefix string) (string, error) { - fileList, err := ioutil.ReadAll(fileListR) - if err != nil { - return "", err - } - - targetPath := fmt.Sprintf("%s-%s", targetPrefix, utils.Sha256Bytes(fileList)[:16]) - - fl, err := lockedfile.Create(targetPath + ".lock") - if err != nil { - return "", err - } - defer fl.Close() - - needsExtract, expectedTarGzHash, err := checkExtractNeeded(targetPath, string(fileList)) - if err != nil { - return "", err - } - if !needsExtract { - return targetPath, nil - } - - err = os.RemoveAll(targetPath) - if err != nil && !os.IsNotExist(err) { - return "", err - } - - err = os.MkdirAll(targetPath, 0o700) - if err != nil { - return "", err - } - - err = utils.ExtractTarGzStream(r, targetPath) - if err != nil { - return "", err - } - - err = ioutil.WriteFile(filepath.Join(targetPath, ".tar-gz-hash"), []byte(expectedTarGzHash), 0o600) - if err != nil { - return "", err - } - - return targetPath, nil -} - -func checkExtractNeeded(targetPath string, fileListStr string) (bool, string, error) { - expectedHash, tarFilesMap, err := ReadFileList(fileListStr) - if err != nil { - return false, "", err - } - - if !utils.Exists(targetPath) { - return true, expectedHash, nil - } - - existingHash, err := ioutil.ReadFile(filepath.Join(targetPath, ".tar-gz-hash")) - if err != nil { - return true, expectedHash, nil - } - - if strings.TrimSpace(expectedHash) != strings.TrimSpace(string(existingHash)) { - return true, expectedHash, nil - } - - existingFiles, err := BuildFileList(targetPath) - if err != nil { - return false, "", err - } - - for fname, size := range tarFilesMap { - if s, ok := existingFiles[fname]; !ok || s != size { - return true, expectedHash, nil - } - } - return false, expectedHash, nil -} - -func ReadFileList(fileListStr string) (string, map[string]int64, error) { - fileList := strings.Split(fileListStr, "\n") - expectedHash := fileList[0] - fileList = fileList[1:] - - tarFilesMap := make(map[string]int64) - for _, l := range fileList { - s := strings.SplitN(l, ":", 2) - fname := strings.TrimSpace(s[0]) - sh := strings.SplitN(strings.TrimSpace(s[1]), " ", 2) - size, err := strconv.ParseInt(strings.TrimSpace(sh[0]), 10, 64) - if err != nil { - return expectedHash, tarFilesMap, err - } - tarFilesMap[fname] = size - } - return expectedHash, tarFilesMap, nil -} - -func BuildFileList(targetPath string) (map[string]int64, error) { - existingFiles := make(map[string]int64) - err := filepath.Walk(targetPath, func(path string, info fs.FileInfo, err error) error { - if !info.Mode().IsRegular() && info.Mode().Type() != fs.ModeSymlink && info.Mode().Type() != fs.ModeDir { - return nil - } - relPath, err := filepath.Rel(targetPath, path) - if err != nil { - return err - } - relPath = filepath.ToSlash(relPath) - if info.IsDir() { - existingFiles[relPath] = 0 - } else { - existingFiles[relPath] = info.Size() - } - return nil - }) - if err != nil { - return nil, err - } - return existingFiles, nil -} diff --git a/pkg/utils/embed_util/packer/packer.go b/pkg/utils/embed_util/packer/packer.go deleted file mode 100644 index fd6fdd077..000000000 --- a/pkg/utils/embed_util/packer/packer.go +++ /dev/null @@ -1,147 +0,0 @@ -package packer - -import ( - "archive/tar" - "bytes" - "compress/gzip" - "crypto/sha256" - "encoding/hex" - "fmt" - "github.com/gobwas/glob" - "github.com/kluctl/kluctl/v2/pkg/utils" - "github.com/kluctl/kluctl/v2/pkg/utils/embed_util" - log "github.com/sirupsen/logrus" - "io/fs" - "io/ioutil" - "path/filepath" - "reflect" - "strings" -) - -func Pack(out string, dir string, patterns ...string) error { - fileList, err := findFiles(dir, patterns) - if err != nil { - return err - } - - if utils.Exists(out) && utils.Exists(out+".files") { - existingFileListStr, err := ioutil.ReadFile(out + ".files") - if err == nil { - _, existingFileList, err := embed_util.ReadFileList(string(existingFileListStr)) - if err == nil { - if reflect.DeepEqual(existingFileList, fileList) { - log.Infof("Skipping packing of %s", out) - return nil - } - } - } - } - - log.Infof("writing tar %s with %d files", out, len(fileList)) - tgz, err := writeTar(dir, fileList) - if err != nil { - return err - } - - hash := sha256.Sum256(tgz) - err = ioutil.WriteFile(out, tgz, 0o600) - if err != nil { - return err - } - - var fileList2 []string - for f, l := range fileList { - fileList2 = append(fileList2, fmt.Sprintf("%s: %d", f, l)) - } - - fileListStr := strings.Join(fileList2, "\n") - fileListStr = hex.EncodeToString(hash[:]) + "\n" + fileListStr - err = ioutil.WriteFile(out+".files", []byte(fileListStr), 0o600) - return err -} - -func findFiles(dir string, patterns []string) (map[string]int64, error) { - var globs []glob.Glob - for _, p := range patterns { - globs = append(globs, glob.MustCompile(p, '/')) - } - - var rootNames []string - err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { - rel, err := filepath.Rel(dir, path) - if err != nil { - return err - } - match := false - for _, p := range globs { - if p.Match(filepath.ToSlash(rel)) { - match = true - break - } - } - if match { - rootNames = append(rootNames, rel) - } - return nil - }) - if err != nil { - return nil, err - } - - excludes := []glob.Glob{ - glob.MustCompile("__pycache__"), - glob.MustCompile("**/__pycache__"), - glob.MustCompile("**.a"), - glob.MustCompile("**.pdb"), - glob.MustCompile("**.pyc"), - } - - fileList := make(map[string]int64) - for _, d := range rootNames { - err = filepath.Walk(filepath.Join(dir, d), func(path string, info fs.FileInfo, err error) error { - if !info.Mode().IsRegular() && info.Mode().Type() != fs.ModeSymlink { - return nil - } - - relPath, err := filepath.Rel(dir, path) - if err != nil { - return err - } - for _, e := range excludes { - if e.Match(relPath) { - return nil - } - } - fileList[relPath] = info.Size() - return nil - }) - if err != nil { - return nil, err - } - } - return fileList, err -} - -func writeTar(dir string, fileList map[string]int64) ([]byte, error) { - b := bytes.NewBuffer(nil) - gz := gzip.NewWriter(b) - t := tar.NewWriter(gz) - - for f, _ := range fileList { - err := utils.AddToTar(t, filepath.Join(dir, f), f, nil) - if err != nil { - return nil, err - } - } - - err := t.Close() - if err != nil { - return nil, err - } - err = gz.Close() - if err != nil { - return nil, err - } - - return b.Bytes(), nil -} diff --git a/pkg/utils/tar.go b/pkg/utils/tar.go deleted file mode 100644 index 0425dc6e6..000000000 --- a/pkg/utils/tar.go +++ /dev/null @@ -1,190 +0,0 @@ -package utils - -import ( - "archive/tar" - "compress/gzip" - "fmt" - securejoin "github.com/cyphar/filepath-securejoin" - "io" - "io/fs" - "io/ioutil" - "os" - "path/filepath" -) - -func ExtractTarGzFile(tarGzPath string, targetPath string) error { - f, err := os.Open(tarGzPath) - if err != nil { - return fmt.Errorf("archive %v could not be opened: %w", tarGzPath, err) - } - defer f.Close() - - err = ExtractTarGzStream(f, targetPath) - if err != nil { - return fmt.Errorf("archive %v could not be extracted: %w", tarGzPath, err) - } - return nil -} - -func ExtractTarGzStream(r io.Reader, targetPath string) error { - gz, err := gzip.NewReader(r) - if err != nil { - return err - } - defer gz.Close() - - return ExtractTarStream(gz, targetPath) -} - -func ExtractTarStream(r io.Reader, targetPath string) error { - tarReader := tar.NewReader(r) - for true { - header, err := tarReader.Next() - if err == io.EOF { - break - } - - if err != nil { - return fmt.Errorf("ExtractTarStream: Next() failed: %w", err) - } - - header.Name = filepath.FromSlash(header.Name) - - p, err := securejoin.SecureJoin(targetPath, header.Name) - if err != nil { - return err - } - err = os.MkdirAll(filepath.Dir(p), 0755) - if err != nil { - return err - } - - switch header.Typeflag { - case tar.TypeDir: - if err := os.MkdirAll(p, 0755); err != nil { - return fmt.Errorf("ExtractTarStream: Mkdir() failed: %w", err) - } - case tar.TypeReg: - outFile, err := os.Create(p) - if err != nil { - return fmt.Errorf("ExtractTarStream: Create() failed: %w", err) - } - _, err = io.Copy(outFile, tarReader) - _ = outFile.Close() - if err != nil { - return fmt.Errorf("ExtractTarStream: Copy() failed: %w", err) - } - err = os.Chmod(p, header.FileInfo().Mode()) - if err != nil { - return fmt.Errorf("ExtractTarStream: Chmod() failed: %w", err) - } - case tar.TypeSymlink: - if err := os.Symlink(header.Linkname, p); err != nil { - return fmt.Errorf("ExtractTarStream: Symlink() failed: %w", err) - } - default: - return fmt.Errorf("ExtractTarStream: uknown type %v in %v", header.Typeflag, header.Name) - } - } - return nil -} - -func AddToTar(tw *tar.Writer, pth string, name string, filter func(h *tar.Header, size int64) (*tar.Header, error)) error { - fi, err := os.Lstat(pth) - if err != nil { - return err - } - - var linkName string - if fi.Mode().Type() == fs.ModeSymlink { - x, err := os.Readlink(pth) - if err != nil { - return err - } - linkName = x - } - - h, err := tar.FileInfoHeader(fi, linkName) - if err != nil { - return err - } - h.Name = filepath.ToSlash(name) - - if filter != nil { - s := fi.Size() - if fi.IsDir() { - s = 0 - } - h, err = filter(h, s) - if err != nil { - return err - } - if h == nil { - return nil - } - } - - err = tw.WriteHeader(h) - if err != nil { - return err - } - - if fi.Mode().Type() == fs.ModeSymlink { - return nil - } - - if fi.Mode().IsDir() { - des, err := os.ReadDir(pth) - if err != nil { - return err - } - for _, d := range des { - err = AddToTar(tw, filepath.Join(pth, d.Name()), filepath.Join(name, d.Name()), filter) - if err != nil { - return err - } - } - return nil - } else if fi.Mode().IsRegular() { - f, err := os.Open(pth) - if err != nil { - return err - } - defer f.Close() - _, err = io.Copy(tw, f) - if err != nil { - return err - } - return nil - } else { - return fmt.Errorf("unsupported file type/mode %s", fi.Mode().String()) - } -} - -func HashTarEntry(dir string, name string) (string, error) { - p := filepath.Join(dir, filepath.FromSlash(name)) - st, err := os.Lstat(p) - if err != nil { - return "", err - } - var hashData []byte - if st.Mode().Type() == fs.ModeDir { - hashData = []byte(filepath.ToSlash(name)) - } else if st.Mode().Type() == fs.ModeSymlink { - l, err := os.Readlink(p) - if err != nil { - return "", err - } - hashData = []byte(l) - } else if st.Mode().IsRegular() { - var err error - hashData, err = ioutil.ReadFile(p) - if err != nil { - return "", err - } - } else { - return "", fmt.Errorf("unknown type %s", st.Mode().Type()) - } - hashStr := Sha256Bytes(hashData) - return hashStr, nil -} From 7904b73ca9ef92d1bf941fbb11627ea365c17012 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 18 Aug 2022 17:46:24 +0200 Subject: [PATCH 0970/2916] chore: Upgrade to go 1.19 --- .github/workflows/release.yml | 2 +- .github/workflows/tests.yml | 2 +- go.mod | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 11588e3a3..9c7982a03 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,7 +23,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v2 with: - go-version: 1.18.1 + go-version: 1.19 - name: Set up Python uses: actions/setup-python@v2 with: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 509758756..26e089c85 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -15,7 +15,7 @@ jobs: fetch-depth: 0 - uses: actions/setup-go@v2 with: - go-version: '1.18.1' + go-version: '1.19' - name: Set up Python uses: actions/setup-python@v2 with: diff --git a/go.mod b/go.mod index 4364baad0..deb6c1c5e 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/kluctl/kluctl/v2 -go 1.18 +go 1.19 require ( github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e From 6524f91adee683f02a01e536e9c18a6957523b77 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 19 Aug 2022 08:59:15 +0200 Subject: [PATCH 0971/2916] chore: Run go get -u ./... --- go.mod | 74 +++++++++--------- go.sum | 239 ++++++++++++++++++++------------------------------------- 2 files changed, 119 insertions(+), 194 deletions(-) diff --git a/go.mod b/go.mod index deb6c1c5e..df2bf20cd 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.19 require ( github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e github.com/Masterminds/semver/v3 v3.1.1 - github.com/aws/aws-sdk-go v1.44.68 + github.com/aws/aws-sdk-go v1.44.80 github.com/bitnami-labs/sealed-secrets v0.18.1 github.com/cyphar/filepath-securejoin v0.2.3 github.com/docker/distribution v2.8.1+incompatible @@ -21,11 +21,11 @@ require ( github.com/imdario/mergo v0.3.13 github.com/jinzhu/copier v0.3.5 github.com/kevinburke/ssh_config v1.2.0 - github.com/kluctl/kluctl-python-deps v0.0.0-20220818145819-a2811c3f89fd - github.com/mattn/go-isatty v0.0.14 + github.com/kluctl/kluctl-python-deps v0.0.0-20220819065415-624185ed2c0a + github.com/mattn/go-isatty v0.0.16 github.com/mitchellh/go-ps v1.0.0 github.com/mitchellh/reflectwalk v1.0.2 - github.com/ohler55/ojg v1.14.3 + github.com/ohler55/ojg v1.14.4 github.com/pkg/errors v0.9.1 github.com/r3labs/diff/v2 v2.15.1 github.com/rogpeppe/go-internal v1.8.1 @@ -33,23 +33,23 @@ require ( github.com/spf13/cobra v1.5.0 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.12.0 - github.com/stretchr/testify v1.7.2 + github.com/stretchr/testify v1.8.0 github.com/vbauerster/mpb/v7 v7.4.2 github.com/whilp/git-urls v1.0.0 github.com/xanzy/ssh-agent v0.3.1 - golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa - golang.org/x/net v0.0.0-20220802222814-0bcc04d9c69b - golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 - golang.org/x/sys v0.0.0-20220731174439-a90be440212d + golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8 + golang.org/x/net v0.0.0-20220812174116-3211cb980234 + golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde + golang.org/x/sys v0.0.0-20220818161305-2296e01440c6 golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 golang.org/x/text v0.3.7 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 - helm.sh/helm/v3 v3.9.2 - k8s.io/api v0.24.3 - k8s.io/apiextensions-apiserver v0.24.3 - k8s.io/apimachinery v0.24.3 - k8s.io/client-go v0.24.3 + helm.sh/helm/v3 v3.9.3 + k8s.io/api v0.24.4 + k8s.io/apiextensions-apiserver v0.24.4 + k8s.io/apimachinery v0.24.4 + k8s.io/client-go v0.24.4 k8s.io/klog/v2 v2.70.1 sigs.k8s.io/kind v0.14.0 sigs.k8s.io/kustomize/api v0.12.1 @@ -58,7 +58,7 @@ require ( ) require ( - cloud.google.com/go/compute v1.7.0 // indirect + cloud.google.com/go/compute v1.9.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest v0.11.28 // indirect @@ -72,7 +72,7 @@ require ( github.com/Masterminds/sprig/v3 v3.2.2 // indirect github.com/Masterminds/squirrel v1.5.3 // indirect github.com/Microsoft/go-winio v0.5.2 // indirect - github.com/ProtonMail/go-crypto v0.0.0-20220730123233-d6ffb7692adf // indirect + github.com/ProtonMail/go-crypto v0.0.0-20220812175011-7fcef0dbe794 // indirect github.com/VividCortex/ewma v1.2.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect github.com/acomagu/bufpipe v1.0.3 // indirect @@ -85,7 +85,7 @@ require ( github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 // indirect github.com/cloudflare/circl v1.2.0 // indirect - github.com/containerd/containerd v1.6.6 // indirect + github.com/containerd/containerd v1.6.8 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/docker/cli v20.10.17+incompatible // indirect github.com/docker/docker v20.10.17+incompatible // indirect @@ -106,7 +106,7 @@ require ( github.com/go-logr/logr v1.2.3 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.20.0 // indirect - github.com/go-openapi/swag v0.21.1 // indirect + github.com/go-openapi/swag v0.22.3 // indirect github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect @@ -126,11 +126,11 @@ require ( github.com/hashicorp/go-hclog v1.2.2 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-plugin v1.4.4 // indirect + github.com/hashicorp/go-plugin v1.4.5 // indirect github.com/hashicorp/go-retryablehttp v0.7.1 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect github.com/hashicorp/go-secure-stdlib/mlock v0.1.2 // indirect - github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 // indirect + github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 // indirect github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect github.com/hashicorp/go-sockaddr v1.0.2 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect @@ -140,7 +140,7 @@ require ( github.com/hashicorp/vault/sdk v0.5.3 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/huandu/xstrings v1.3.2 // indirect - github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jmoiron/sqlx v1.3.5 // indirect @@ -154,7 +154,7 @@ require ( github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/magiconair/properties v1.8.6 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect @@ -164,7 +164,7 @@ require ( github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moby/locker v1.0.1 // indirect github.com/moby/spdystream v0.2.0 // indirect - github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect + github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect @@ -174,15 +174,15 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 // indirect github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.0.3 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pierrec/lz4 v2.6.1+incompatible // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.12.2 // indirect + github.com/prometheus/client_golang v1.13.0 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect - github.com/rivo/uniseg v0.3.1 // indirect + github.com/rivo/uniseg v0.3.4 // indirect github.com/rubenv/sql-migrate v1.1.2 // indirect github.com/russross/blackfriday v1.6.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect @@ -197,24 +197,24 @@ require ( github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xlab/treeprint v1.1.0 // indirect - go.starlark.net v0.0.0-20220714194419-4cadf0a12139 // indirect - go.uber.org/atomic v1.9.0 // indirect - golang.org/x/oauth2 v0.0.0-20220722155238-128564f6959c // indirect + go.starlark.net v0.0.0-20220817180228-f738f5508c12 // indirect + go.uber.org/atomic v1.10.0 // indirect + golang.org/x/oauth2 v0.0.0-20220808172628-8227340efae7 // indirect golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20220802133213-ce4fa296bf78 // indirect + google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1 // indirect google.golang.org/grpc v1.48.0 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/ini.v1 v1.66.6 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect - k8s.io/apiserver v0.24.3 // indirect - k8s.io/cli-runtime v0.24.3 // indirect - k8s.io/component-base v0.24.3 // indirect - k8s.io/kube-openapi v0.0.0-20220627174259-011e075b9cb8 // indirect - k8s.io/kubectl v0.24.3 // indirect - k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect + k8s.io/apiserver v0.24.4 // indirect + k8s.io/cli-runtime v0.24.4 // indirect + k8s.io/component-base v0.24.4 // indirect + k8s.io/kube-openapi v0.0.0-20220803164354-a70c9af30aea // indirect + k8s.io/kubectl v0.24.4 // indirect + k8s.io/utils v0.0.0-20220812165043-ad590609e2e5 // indirect oras.land/oras-go v1.2.0 // indirect sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect sigs.k8s.io/yaml v1.3.0 // indirect diff --git a/go.sum b/go.sum index c55806e56..ed9d54a25 100644 --- a/go.sum +++ b/go.sum @@ -28,29 +28,18 @@ cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSU cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= -cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= -cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= -cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= -cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= -cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= -cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= -cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= -cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= -cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= -cloud.google.com/go/compute v1.7.0 h1:v/k9Eueb8aAJ0vZuxKMrgm6kPhCLZU9HxFU+AFDs9Uk= -cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= +cloud.google.com/go/compute v1.9.0 h1:ED/FP4xv8GJw63v556/ASNc1CeeLUO2Bs8nzaHchkHg= +cloud.google.com/go/compute v1.9.0/go.mod h1:lWv1h/zUWTm/LozzfTJhBSkd6ShQq8la8VeeuOEGxfY= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/firestore v1.6.0/go.mod h1:afJwI0vaXwAG54kI7A//lP/lSPDkQORQuMkv56TxEPU= -cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -63,7 +52,6 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= -cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= contrib.go.opencensus.io/exporter/stackdriver v0.13.4/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Antonboom/errname v0.1.5/go.mod h1:DugbBstvPFQbv/5uLcRRzfrNqKE9tVdVCqWCLp6Cifo= @@ -120,14 +108,14 @@ github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugX github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/Microsoft/hcsshim v0.9.3 h1:k371PzBuRrz2b+ebGuI2nVgVhgsVX60jMfSw80NECxo= +github.com/Microsoft/hcsshim v0.9.4 h1:mnUj0ivWy6UzbB1uLFqKR6F+ZyiDc7j4iGgHTpO+5+I= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM= github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= -github.com/ProtonMail/go-crypto v0.0.0-20220730123233-d6ffb7692adf h1:aFFtnGZ6/2Qlvx80yxA2fFSYDQWTFjtKozQKB36A3/A= -github.com/ProtonMail/go-crypto v0.0.0-20220730123233-d6ffb7692adf/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8= +github.com/ProtonMail/go-crypto v0.0.0-20220812175011-7fcef0dbe794 h1:efPD6snIrIBAfmZhcm7GQ72VHlzsQ/3OrghnnGEpJBM= +github.com/ProtonMail/go-crypto v0.0.0-20220812175011-7fcef0dbe794/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= @@ -172,8 +160,8 @@ github.com/ashanbrown/makezero v0.0.0-20210520155254-b6261585ddde/go.mod h1:oG9D github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.36.30/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= -github.com/aws/aws-sdk-go v1.44.68 h1:7zNr5+HLG0TMq+ZcZ8KhT4eT2KyL7v+u7/jANKEIinM= -github.com/aws/aws-sdk-go v1.44.68/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.80 h1:jEXGecSgPdvM5KnyDsSgFhZSm7WwaTp4h544Im4SfhI= +github.com/aws/aws-sdk-go v1.44.80/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -225,7 +213,6 @@ github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnht github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= @@ -234,8 +221,8 @@ github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= github.com/containerd/cgroups v1.0.3 h1:ADZftAkglvCiD44c77s5YmMqaP2pzVCFZvBmAlBdAP4= -github.com/containerd/containerd v1.6.6 h1:xJNPhbrmz8xAMDNoVjHy9YHtWwEQNS+CDkcIRh7t8Y0= -github.com/containerd/containerd v1.6.6/go.mod h1:ZoP1geJldzCVY3Tonoz7b1IXk8rIX0Nltt5QE4OMNk0= +github.com/containerd/containerd v1.6.8 h1:h4dOFDwzHmqFEP754PgfgTeVXFnLiRc6kiqC7tplDJs= +github.com/containerd/containerd v1.6.8/go.mod h1:By6p5KqPK0/7/CgO/A6t/Gz+CUYUu2zf1hUaaymVXB0= github.com/containerd/stargz-snapshotter/estargz v0.12.0 h1:idtwRTLjk2erqiYhPWy2L844By8NRFYEwYHcXhoIWPM= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= @@ -308,7 +295,6 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= -github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.0.14/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= @@ -396,8 +382,8 @@ github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZ github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU= -github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= @@ -525,7 +511,6 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-containerregistry v0.11.0 h1:Xt8x1adcREjFcmDoDK8OdOsjxu90PHkGuwNP8GiHMLM= @@ -564,16 +549,10 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= -github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= -github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= -github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= -github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= -github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -635,8 +614,8 @@ github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHh github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-plugin v1.4.4 h1:NVdrSdFRt3SkZtNckJ6tog7gbpRrcbOjQi/rgF7JYWQ= -github.com/hashicorp/go-plugin v1.4.4/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= +github.com/hashicorp/go-plugin v1.4.5 h1:oTE/oQR4eghggRg8VY7PAz3dr++VwDNBGCcOfIvHpBo= +github.com/hashicorp/go-plugin v1.4.5/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ= github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= @@ -645,8 +624,8 @@ github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5O github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-secure-stdlib/mlock v0.1.2 h1:p4AKXPPS24tO8Wc8i1gLvSKdmkiSY5xuju57czJ/IJQ= github.com/hashicorp/go-secure-stdlib/mlock v0.1.2/go.mod h1:zq93CJChV6L9QTfGKtfBxKqD7BqqXx5O04A/ns2p5+I= -github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 h1:om4Al8Oy7kCm/B86rLCLah4Dt5Aa0Fr5rYBG60OzwHQ= -github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 h1:UpiO20jno/eV1eVZcxqWnUohyKRe1g8FPV/xH1s/2qs= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= @@ -698,8 +677,9 @@ github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= -github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= +github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -755,8 +735,8 @@ github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8 github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= -github.com/kluctl/kluctl-python-deps v0.0.0-20220818145819-a2811c3f89fd h1:CTUYp06sH7d38kfHY+tysa9jhQuo1C9ssSlVR1hFuDA= -github.com/kluctl/kluctl-python-deps v0.0.0-20220818145819-a2811c3f89fd/go.mod h1:w4cTz1av5JFX9bMTB7Vm2qtmm5+eU+ncvB1/oyjVwOw= +github.com/kluctl/kluctl-python-deps v0.0.0-20220819065415-624185ed2c0a h1:2W9TlV8b6sqjIKhbp9bgKjusdF3HgQucFNmwGNb1N+Y= +github.com/kluctl/kluctl-python-deps v0.0.0-20220819065415-624185ed2c0a/go.mod h1:w4cTz1av5JFX9bMTB7Vm2qtmm5+eU+ncvB1/oyjVwOw= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -826,16 +806,18 @@ github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= @@ -894,8 +876,9 @@ github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQ github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/moby/sys/mountinfo v0.5.0 h1:2Ks8/r6lopsxWi9m58nlwjaeSzUX9iiL1vj5qB/9ObI= -github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc= github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= +github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae h1:O4SWKdcHVCvYqyDV+9CJA1fcDN2L11Bule0iFy3YlAI= +github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -928,8 +911,8 @@ github.com/nishanths/predeclared v0.2.1/go.mod h1:HvkGJcA3naj4lOwnFXFDkFxVtSqQMB github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/ohler55/ojg v1.14.3 h1:kaKNsntZ0PuoXPXCY4kjPDbHOLXqokor6Deq/oVxAR0= -github.com/ohler55/ojg v1.14.3/go.mod h1:/Y5dGWkekv9ocnUixuETqiL58f+5pAsUfg5P8e7Pa2o= +github.com/ohler55/ojg v1.14.4 h1:L2ds8AlB5t/QbqSfhRwvagJzQ7pgmdrefMIypQs0Xik= +github.com/ohler55/ojg v1.14.4/go.mod h1:7Ghirupn8NC8hSSDpI0gcjorPxj+vSVIONDWfliHR1k= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= @@ -970,8 +953,8 @@ github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCko github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.0.2 h1:+jQXlF3scKIcSEKkdHzXhCTDLPFi5r1wnK6yPS+49Gw= -github.com/pelletier/go-toml/v2 v2.0.2/go.mod h1:MovirKjgVRESsAvNZlAjtFwV867yGuwRkXbG66OzopI= +github.com/pelletier/go-toml/v2 v2.0.3 h1:h9JoA60e1dVEOpp0PFwJSmt1Htu057NUq9/bUwaO61s= +github.com/pelletier/go-toml/v2 v2.0.3/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw= @@ -1002,8 +985,8 @@ github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3O github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_golang v1.12.2 h1:51L9cDoUHVrXx4zWYlcLQIZ+d+VXHgqnYKkIuq4g/34= -github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_golang v1.13.0 h1:b71QUfeo5M8gq2+evJdTPfZhYMAU0uKPkyPJ7TPsloU= +github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -1043,8 +1026,8 @@ github.com/quasilyte/regex/syntax v0.0.0-20200407221936-30656e2c4a95/go.mod h1:r github.com/r3labs/diff/v2 v2.15.1 h1:EOrVqPUzi+njlumoqJwiS/TgGgmZo83619FNDB9xQUg= github.com/r3labs/diff/v2 v2.15.1/go.mod h1:I8noH9Fc2fjSaMxqF3G2lhDdC0b+JXCfyx85tWFM9kc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.3.1 h1:SDPP7SHNl1L7KrEFCSJslJ/DM9DT02Nq2C61XrfHMmk= -github.com/rivo/uniseg v0.3.1/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rivo/uniseg v0.3.4 h1:3Z3Eu6FGHZWSfNKJTOUiPatWwfc7DzJRU04jFUqJODw= +github.com/rivo/uniseg v0.3.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -1133,8 +1116,9 @@ github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRk github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v0.0.0-20170130113145-4d4bfba8f1d1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -1143,8 +1127,10 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.4.0 h1:yAzM1+SmVcz5R4tXGsNMu1jUl2aOJXoiWUCEwwnGrvs= github.com/subosito/gotenv v1.4.0/go.mod h1:mZd6rFysKEcUhUHXJk0C/08wAgyDBFuwEYL7vWWGaGo= @@ -1251,14 +1237,14 @@ go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4 go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= -go.starlark.net v0.0.0-20220714194419-4cadf0a12139 h1:zMemyQYZSyEdPaUFixYICrXf/0Rfnil7+jiQRf5IBZ0= -go.starlark.net v0.0.0-20220714194419-4cadf0a12139/go.mod h1:t3mmBBPzAVvK0L0n1drDmrQsJ8FoIx4INCqVMTr/Zo0= +go.starlark.net v0.0.0-20220817180228-f738f5508c12 h1:xOBJXWGEDwU5xSDxH6macxO11Us0AH2fTa9rmsbbF7g= +go.starlark.net v0.0.0-20220817180228-f738f5508c12/go.mod h1:VZcBMdr3cT3PnBoWunTabuSEXwVAH+ZJ5zxfs3AdASk= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= +go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= @@ -1296,8 +1282,9 @@ golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8 h1:GIAS/yBem/gq2MUqgNIzUHW7cJMmx3TGZOrnyYaNQ6c= +golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1395,12 +1382,8 @@ golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220802222814-0bcc04d9c69b h1:3ogNYyK4oIQdIKzTu68hQrr4iuVxF3AxKl9Aj/eDrw0= -golang.org/x/net v0.0.0-20220802222814-0bcc04d9c69b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20220812174116-3211cb980234 h1:RDqmgfe7SvlMWoqC3xwQ2blLO3fcWcxMa3eBLRdRW7E= +golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1419,11 +1402,8 @@ golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= -golang.org/x/oauth2 v0.0.0-20220722155238-128564f6959c h1:q3gFqPqH7NVofKo3c3yETAP//pPI+G5mvB7qqj1Y5kY= -golang.org/x/oauth2 v0.0.0-20220722155238-128564f6959c/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20220808172628-8227340efae7 h1:dtndE8FcEta75/4kHF3AbpuWzV6f1LjnLrM4pe2SZrw= +golang.org/x/oauth2 v0.0.0-20220808172628-8227340efae7/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1436,9 +1416,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde h1:ejfdSekXMDxDLbRrJMwUk6KnSLZ2McaUCVcIKM+N6jc= +golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1523,30 +1502,23 @@ golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210915083310-ed5796bab164/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220731174439-a90be440212d h1:Sv5ogFZatcgIMMtBSTTAgMYsicp25MXBubjXNDKwm80= -golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220818161305-2296e01440c6 h1:Sx/u41w+OwrInGdEckYmEuU5gHoGSL4QbDz3S9s6j4U= +golang.org/x/sys v0.0.0-20220818161305-2296e01440c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1680,9 +1652,6 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -1711,19 +1680,7 @@ google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtuk google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= -google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= -google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= -google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= -google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= -google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= -google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= -google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= -google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= -google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= -google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= -google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1782,7 +1739,6 @@ google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= @@ -1797,33 +1753,9 @@ google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwy google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= -google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220802133213-ce4fa296bf78 h1:QntLWYqZeuBtJkth3m/6DLznnI0AHJr+AgJXvVh/izw= -google.golang.org/genproto v0.0.0-20220802133213-ce4fa296bf78/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc= +google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1 h1:C2UVWqrgLYKrT5nh5oU6hLRm1AeEklCK5eloQA1NtFY= +google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -1853,12 +1785,6 @@ google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQ google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= -google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.48.0 h1:rQOsyJ/8+ufEDJd/Gdsz7HG220Mh9HAhFHRGnIjda0w= google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= @@ -1875,7 +1801,6 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= @@ -1895,8 +1820,8 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.66.6 h1:LATuAqN/shcYAOkv3wl2L4rkaKqkcgTBQjOyYDvcPKI= -gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= @@ -1926,8 +1851,8 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= -helm.sh/helm/v3 v3.9.2 h1:bx7kdhr5VAhYoWv9bIdT1C6qWR+/7SIoPCwLx22l78g= -helm.sh/helm/v3 v3.9.2/go.mod h1:y/dJc/0Lzcn40jgd85KQXnufhFF7sr4v6L/vYMLRaRM= +helm.sh/helm/v3 v3.9.3 h1:etd4Qc45/bnIkBofZIRwrAzYuG3bNWR1EdMN4fsfzoE= +helm.sh/helm/v3 v3.9.3/go.mod h1:3eaWAIqzvlRSD06gR9MMwmp2KBKwlu9av1/1BZpjeWY= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1936,22 +1861,22 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.2.1/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY= -k8s.io/api v0.24.3 h1:tt55QEmKd6L2k5DP6G/ZzdMQKvG5ro4H4teClqm0sTY= -k8s.io/api v0.24.3/go.mod h1:elGR/XSZrS7z7cSZPzVWaycpJuGIw57j9b95/1PdJNI= -k8s.io/apiextensions-apiserver v0.24.3 h1:kyx+Tmro1qEsTUr07ZGQOfvTsF61yn+AxnxytBWq8As= -k8s.io/apiextensions-apiserver v0.24.3/go.mod h1:cL0xkmUefpYM4f6IuOau+6NMFEIh6/7wXe/O4vPVJ8A= -k8s.io/apimachinery v0.24.3 h1:hrFiNSA2cBZqllakVYyH/VyEh4B581bQRmqATJSeQTg= -k8s.io/apimachinery v0.24.3/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= -k8s.io/apiserver v0.24.3 h1:J8CKjUaZopT0hSgxjzUyp3T1GK78iixxOuFpEC0MI3k= -k8s.io/apiserver v0.24.3/go.mod h1:aXfwtIn4U27B7lYs5f2BKgz6DRbgWy+HJeYReN1jLJ8= -k8s.io/cli-runtime v0.24.3 h1:O9YvUHrDSCQUPlsqVmaqDrueqjpJ7IO6Yas9B6xGSoo= -k8s.io/cli-runtime v0.24.3/go.mod h1:In84wauoMOqa7JDvDSXGbf8lTNlr70fOGpYlYfJtSqA= -k8s.io/client-go v0.24.3 h1:Nl1840+6p4JqkFWEW2LnMKU667BUxw03REfLAVhuKQY= -k8s.io/client-go v0.24.3/go.mod h1:AAovolf5Z9bY1wIg2FZ8LPQlEdKHjLI7ZD4rw920BJw= -k8s.io/code-generator v0.24.3/go.mod h1:dpVhs00hTuTdTY6jvVxvTFCk6gSMrtfRydbhZwHI15w= -k8s.io/component-base v0.24.3 h1:u99WjuHYCRJjS1xeLOx72DdRaghuDnuMgueiGMFy1ec= -k8s.io/component-base v0.24.3/go.mod h1:bqom2IWN9Lj+vwAkPNOv2TflsP1PeVDIwIN0lRthxYY= -k8s.io/component-helpers v0.24.3/go.mod h1:/1WNW8TfBOijQ1ED2uCHb4wtXYWDVNMqUll8h36iNVo= +k8s.io/api v0.24.4 h1:I5Y645gJ8zWKawyr78lVfDQkZrAViSbeRXsPZWTxmXk= +k8s.io/api v0.24.4/go.mod h1:42pVfA0NRxrtJhZQOvRSyZcJihzAdU59WBtTjYcB0/M= +k8s.io/apiextensions-apiserver v0.24.4 h1:w53Pm4zu8fCt9WfiRgS2YI6LE6I4NJ5aUi78GElD3K8= +k8s.io/apiextensions-apiserver v0.24.4/go.mod h1:iDK+Xb4jsPNnRGj5jU/WqqjLvt8363M7cKixKe1C9+U= +k8s.io/apimachinery v0.24.4 h1:S0Ur3J/PbivTcL43EdSdPhqCqKla2NIuneNwZcTDeGQ= +k8s.io/apimachinery v0.24.4/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= +k8s.io/apiserver v0.24.4 h1:ei+OunC83pVEiagBeZhTnRZvkclHgpzs/rrm7aSBDYs= +k8s.io/apiserver v0.24.4/go.mod h1:mAuC3pZVc0IDXLx7lUHoisBOtBa1SobfLW/CI3klXQE= +k8s.io/cli-runtime v0.24.4 h1:YCSf0dZp+pYXVR/8aZQ6MEBSiicv8rLyVsGBEbRnwfY= +k8s.io/cli-runtime v0.24.4/go.mod h1:RF+cSLYXkPV3WyvPrX2qeRLEUJY38INWx6jLKVLFCxM= +k8s.io/client-go v0.24.4 h1:hIAIJZIPyaw46AkxwyR0FRfM/pRxpUNTd3ysYu9vyRg= +k8s.io/client-go v0.24.4/go.mod h1:+AxlPWw/H6f+EJhRSjIeALaJT4tbeB/8g9BNvXGPd0Y= +k8s.io/code-generator v0.24.4/go.mod h1:dpVhs00hTuTdTY6jvVxvTFCk6gSMrtfRydbhZwHI15w= +k8s.io/component-base v0.24.4 h1:WEGRp06GBYVwxp5JdiRaJ1zkdOhrqucxRv/8IrABLG0= +k8s.io/component-base v0.24.4/go.mod h1:sWxkgcMfbYHadw0OJ0N+vIscd14/nqSIM2veCdg843o= +k8s.io/component-helpers v0.24.4/go.mod h1:xAHlOKU8rAjLgXWJEsueWLR1LDMThbaPf2YvgKpSyQ8= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/gengo v0.0.0-20211129171323-c02415ce4185/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= @@ -1962,15 +1887,15 @@ k8s.io/klog/v2 v2.70.1 h1:7aaoSdahviPmR+XkS7FyxlkkXs6tHISSG03RxleQAVQ= k8s.io/klog/v2 v2.70.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42/go.mod h1:Z/45zLw8lUo4wdiUkI+v/ImEGAvu3WatcZl3lPMR4Rk= -k8s.io/kube-openapi v0.0.0-20220627174259-011e075b9cb8 h1:yEQKdMCjzAOvGeiTwG4hO/hNVNtDOuUFvMUZ0OlaIzs= -k8s.io/kube-openapi v0.0.0-20220627174259-011e075b9cb8/go.mod h1:mbJ+NSUoAhuR14N0S63bPkh8MGVSo3VYSGZtH/mfMe0= -k8s.io/kubectl v0.24.3 h1:PqY8ho/S/KuE2/hCC3Iee7X+lOtARYo0LQsNzvV/edE= -k8s.io/kubectl v0.24.3/go.mod h1:PYLcvw96sC1NLbxZEDbdlOEd6/C76VIWjGmWV5QjSk0= -k8s.io/metrics v0.24.3/go.mod h1:p1M0lhMySWfhISkSd3HEj8xIgrVnJTK3PPhFq2rA3To= +k8s.io/kube-openapi v0.0.0-20220803164354-a70c9af30aea h1:3QOH5+2fGsY8e1qf+GIFpg+zw/JGNrgyZRQR7/m6uWg= +k8s.io/kube-openapi v0.0.0-20220803164354-a70c9af30aea/go.mod h1:C/N6wCaBHeBHkHUesQOQy2/MZqGgMAFPqGsGQLdbZBU= +k8s.io/kubectl v0.24.4 h1:fPEBkAV3/cu3BQVIUCXNngCCY62AlZ+2rkRVHcmJPn0= +k8s.io/kubectl v0.24.4/go.mod h1:AVyJzxUwA5UMGTDyKGL6nd6RRW36FbmAdtIDMhrZtW0= +k8s.io/metrics v0.24.4/go.mod h1:7D8Xm3DGZoJaiCS8+QA2EzdMuDlq0Y8SiOPUB/1BaGU= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed h1:jAne/RjBTyawwAy0utX5eqigAwz/lQhTmy+Hr/Cpue4= -k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20220812165043-ad590609e2e5 h1:XmRqFcQlCy/lKRZ39j+RVpokYNroHPqV3mcBRfnhT5o= +k8s.io/utils v0.0.0-20220812165043-ad590609e2e5/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= mvdan.cc/gofumpt v0.1.1/go.mod h1:yXG1r1WqZVKWbVRtBWKWX9+CxGYfA51nSomhM0woR48= mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= From 09406c6ff1e06f903adae86586b8bfc3481d7587 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 19 Aug 2022 10:20:16 +0200 Subject: [PATCH 0972/2916] fix: Use distinct tmp dir per user This avoids conflicts when used in parallel and also avoids any possible security issues in regard to temporary files. --- pkg/utils/utils.go | 55 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 4bcd0bf70..f704bf0f6 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -3,24 +3,71 @@ package utils import ( "crypto/sha256" "encoding/hex" + "fmt" "github.com/jinzhu/copier" + "github.com/kluctl/kluctl-python-deps/pkg/utils" + "io/fs" "os" + "os/user" "path/filepath" "strconv" "sync" ) var createTmpBaseDirOnce sync.Once +var tmpBaseDir string func GetTmpBaseDir() string { - dir := filepath.Join(os.TempDir(), "kluctl-workdir") createTmpBaseDirOnce.Do(func() { - err := os.MkdirAll(dir, 0o700) + createTmpBaseDir() + }) + return tmpBaseDir +} + +func createTmpBaseDir() { + dir := filepath.Join(os.TempDir(), "kluctl-workdir") + + ensureDir(dir, 0o777, true) + + // every user gets its own tmp dir + u, err := user.Current() + if err != nil { + panic(err) + } + + dir = filepath.Join(dir, u.Uid) + ensureDir(dir, 0o700, true) + + tmpBaseDir = dir +} + +func ensureDir(path string, perm fs.FileMode, allowCreate bool) { + if utils.Exists(path) { + st, err := os.Lstat(path) if err != nil { panic(err) } - }) - return dir + if !st.IsDir() { + panic(fmt.Sprintf("%s is not a directory", path)) + } + if st.Mode().Perm() != perm { + err = os.Chmod(path, perm) + if err != nil { + panic(err) + } + } + } else if !allowCreate { + panic(fmt.Sprintf("failed to ensure directory %s", path)) + } else { + err := os.Mkdir(path, perm) + if err != nil { + if os.IsExist(err) { + ensureDir(path, perm, false) + } else { + panic(err) + } + } + } } func Sha256String(data string) string { From ecb5e10341a1125723ebdd00d206312922fee021 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 29 Aug 2022 12:11:40 +0200 Subject: [PATCH 0973/2916] fix: Properly marshal objects with yaml tags when passing vars to jinja2 --- pkg/jinja2/jinja2_renderer.go | 3 ++- pkg/yaml/yaml.go | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/pkg/jinja2/jinja2_renderer.go b/pkg/jinja2/jinja2_renderer.go index e5b64ef73..687669507 100644 --- a/pkg/jinja2/jinja2_renderer.go +++ b/pkg/jinja2/jinja2_renderer.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/kluctl/kluctl-python-deps/pkg/python" "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/yaml" "io" "io/ioutil" "os" @@ -130,7 +131,7 @@ type jinja2Result struct { } func (j *pythonJinja2Renderer) renderHelper(jobs []*RenderJob, searchDirs []string, vars *uo.UnstructuredObject, isString bool, strict bool) error { - varsStr, err := json.Marshal(vars.Object) + varsStr, err := yaml.WriteJsonString(vars) if err != nil { return err } diff --git a/pkg/yaml/yaml.go b/pkg/yaml/yaml.go index 6d559b530..89610c94a 100644 --- a/pkg/yaml/yaml.go +++ b/pkg/yaml/yaml.go @@ -201,6 +201,19 @@ func WriteYamlToTar(tw *tar.Writer, o interface{}, name string) error { return nil } +func WriteJsonString(o interface{}) (string, error) { + x, err := WriteYamlBytes(o) + if err != nil { + return "", err + } + + x, err = ConvertYamlToJson(x) + if err != nil { + return "", err + } + return string(x), nil +} + func ConvertYamlToJson(b []byte) ([]byte, error) { var x interface{} err := ReadYamlBytes(b, &x) From d60d77dd11e36195f16ebb97b4295e1f64a73527 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 29 Aug 2022 12:12:03 +0200 Subject: [PATCH 0974/2916] fix: Only check for replicas if replicas != 0 --- pkg/validation/validation.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pkg/validation/validation.go b/pkg/validation/validation.go index 6e631470f..7a3ecb5d2 100644 --- a/pkg/validation/validation.go +++ b/pkg/validation/validation.go @@ -281,10 +281,13 @@ func ValidateObject(k *k8s.K8sCluster, o *uo.UnstructuredObject, notReadyIsError } } case schema.GroupKind{Group: "apps", Kind: "Deployment"}: - readyReplicas := getStatusFieldInt("readyReplicas", reactNotReady, true, 0) - replicas := getStatusFieldInt("replicas", reactNotReady, true, 0) - if readyReplicas < replicas { - addNotReady(fmt.Sprintf("readyReplicas (%d) is less then replicas (%d)", readyReplicas, replicas)) + specReplicas, ok, _ := o.GetNestedInt("spec", "replicas") + if ok && specReplicas != 0 { + readyReplicas := getStatusFieldInt("readyReplicas", reactNotReady, true, 0) + replicas := getStatusFieldInt("replicas", reactNotReady, true, 0) + if readyReplicas < replicas { + addNotReady(fmt.Sprintf("readyReplicas (%d) is less then replicas (%d)", readyReplicas, replicas)) + } } case schema.GroupKind{Group: "", Kind: "PersistentVolumeClaim"}: phase := getStatusFieldStr("phase", reactNotReady, true, "") From 215585b3528929a0ce55059c1c7ca3b71ea9ea61 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 29 Aug 2022 14:41:07 +0200 Subject: [PATCH 0975/2916] refactor: Export ListRemoteRefs helper functions --- pkg/git/list_refs.go | 43 +++++++++++++++++++++++----------------- pkg/git/mirrored_repo.go | 16 ++++++++------- 2 files changed, 34 insertions(+), 25 deletions(-) diff --git a/pkg/git/list_refs.go b/pkg/git/list_refs.go index 2bc517fd6..d529c952c 100644 --- a/pkg/git/list_refs.go +++ b/pkg/git/list_refs.go @@ -2,33 +2,38 @@ package git import ( "bytes" + "context" "fmt" "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/config" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/protocol/packp" + "github.com/go-git/go-git/v5/storage/memory" auth2 "github.com/kluctl/kluctl/v2/pkg/git/auth" + git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" + ssh_pool "github.com/kluctl/kluctl/v2/pkg/git/ssh-pool" "github.com/kluctl/kluctl/v2/pkg/status" "strconv" ) -// listRemoteRefsFastSsh will reuse existing ssh connections from a pool -func (g *MirroredGitRepo) listRemoteRefsFastSsh(r *git.Repository, auth auth2.AuthMethodAndCA) ([]*plumbing.Reference, error) { - var portInt int64 = -1 - if g.url.Port() != "" { +// ListRemoteRefsFastSsh will reuse existing ssh connections from a pool +func ListRemoteRefsFastSsh(ctx context.Context, url git_url.GitUrl, sshPool *ssh_pool.SshPool, auth auth2.AuthMethodAndCA) ([]*plumbing.Reference, error) { + var portInt int64 = 22 + if url.Port() != "" { var err error - portInt, err = strconv.ParseInt(g.url.Port(), 10, 32) + portInt, err = strconv.ParseInt(url.Port(), 10, 32) if err != nil { return nil, err } } - s, err := g.sshPool.GetSession(g.ctx, g.url.Hostname(), int(portInt), auth) + s, err := sshPool.GetSession(ctx, url.Hostname(), int(portInt), auth) if err != nil { return nil, err } defer s.Close() - cmd := fmt.Sprintf("git-upload-pack %s", g.url.Path) + cmd := fmt.Sprintf("git-upload-pack %s", url.Path) stdout := bytes.NewBuffer(nil) stderr := bytes.NewBuffer(nil) @@ -71,13 +76,15 @@ func (g *MirroredGitRepo) listRemoteRefsFastSsh(r *git.Repository, auth auth2.Au return resultRefs, nil } -func (g *MirroredGitRepo) listRemoteRefsSlow(r *git.Repository, auth auth2.AuthMethodAndCA) ([]*plumbing.Reference, error) { - remote, err := r.Remote("origin") - if err != nil { - return nil, err - } +func ListRemoteRefsSlow(ctx context.Context, url git_url.GitUrl, auth auth2.AuthMethodAndCA) ([]*plumbing.Reference, error) { + storage := memory.NewStorage() + remote := git.NewRemote(storage, &config.RemoteConfig{ + Name: "origin", + URLs: []string{url.String()}, + Fetch: defaultFetch, + }) - remoteRefs, err := remote.ListContext(g.ctx, &git.ListOptions{ + remoteRefs, err := remote.ListContext(ctx, &git.ListOptions{ Auth: auth.AuthMethod, CABundle: auth.CABundle, }) @@ -87,13 +94,13 @@ func (g *MirroredGitRepo) listRemoteRefsSlow(r *git.Repository, auth auth2.AuthM return remoteRefs, nil } -func (g *MirroredGitRepo) listRemoteRefs(r *git.Repository, auth auth2.AuthMethodAndCA) ([]*plumbing.Reference, error) { - if g.url.IsSsh() { - refs, err := g.listRemoteRefsFastSsh(r, auth) +func ListRemoteRefs(ctx context.Context, url git_url.GitUrl, sshPool *ssh_pool.SshPool, auth auth2.AuthMethodAndCA) ([]*plumbing.Reference, error) { + if url.IsSsh() { + refs, err := ListRemoteRefsFastSsh(ctx, url, sshPool, auth) if err == nil { return refs, nil } - status.Warning(g.ctx, "Fast listing of remote refs failed: %s", err.Error()) + status.Warning(ctx, "Fast listing of remote refs failed: %s", err.Error()) } - return g.listRemoteRefsSlow(r, auth) + return ListRemoteRefsSlow(ctx, url, auth) } diff --git a/pkg/git/mirrored_repo.go b/pkg/git/mirrored_repo.go index 3c3a271c1..00eb928c6 100644 --- a/pkg/git/mirrored_repo.go +++ b/pkg/git/mirrored_repo.go @@ -24,6 +24,11 @@ import ( var cacheBaseDir = filepath.Join(utils.GetTmpBaseDir(), "git-cache") +var defaultFetch = []config.RefSpec{ + "+refs/heads/*:refs/heads/*", + "+refs/tags/*:refs/tags/*", +} + type MirroredGitRepo struct { ctx context.Context @@ -200,7 +205,7 @@ func (g *MirroredGitRepo) update(s *status.StatusContext, repoDir string) error auth := g.authProviders.BuildAuth(g.ctx, g.url) - remoteRefs, err := g.listRemoteRefs(r, auth) + remoteRefs, err := ListRemoteRefs(g.ctx, g.url, g.sshPool, auth) if err != nil { return err } @@ -302,12 +307,9 @@ func (g *MirroredGitRepo) cloneOrUpdate(s *status.StatusContext) error { } _, err = repo.CreateRemote(&config.RemoteConfig{ - Name: "origin", - URLs: []string{g.url.String()}, - Fetch: []config.RefSpec{ - "+refs/heads/*:refs/heads/*", - "+refs/tags/*:refs/tags/*", - }, + Name: "origin", + URLs: []string{g.url.String()}, + Fetch: defaultFetch, }) if err != nil { return err From 317da3aeac29300e5b14543a13f24efc3b043528 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 1 Sep 2022 15:18:02 +0200 Subject: [PATCH 0976/2916] refactor: Allow to trim/lstrip blocks in Jinja2 --- pkg/jinja2/jinja2.go | 19 ++++++++++++++++++- pkg/jinja2/jinja2_renderer.go | 8 ++++++++ pkg/jinja2/python_src/jinja2_renderer.py | 6 +++++- pkg/jinja2/python_src/main.py | 4 ++-- 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/pkg/jinja2/jinja2.go b/pkg/jinja2/jinja2.go index 2d433ec1a..62df03c34 100644 --- a/pkg/jinja2/jinja2.go +++ b/pkg/jinja2/jinja2.go @@ -35,11 +35,25 @@ type Jinja2Error struct { error string } +type Jinja2Opt func(j2 *pythonJinja2Renderer) + +func WithTrimBlocks(trimBlocks bool) Jinja2Opt { + return func(j2 *pythonJinja2Renderer) { + j2.trimBlocks = trimBlocks + } +} + +func WithLStripBlocks(lstripBlocks bool) Jinja2Opt { + return func(j2 *pythonJinja2Renderer) { + j2.lstripBlocks = lstripBlocks + } +} + func (m *Jinja2Error) Error() string { return m.error } -func NewJinja2() (*Jinja2, error) { +func NewJinja2(opts ...Jinja2Opt) (*Jinja2, error) { var wg sync.WaitGroup var mutex sync.Mutex var err error @@ -61,6 +75,9 @@ func NewJinja2() (*Jinja2, error) { err = err2 return } + for _, o := range opts { + o(pj) + } j.pj <- pj }() } diff --git a/pkg/jinja2/jinja2_renderer.go b/pkg/jinja2/jinja2_renderer.go index 687669507..bcbd36edb 100644 --- a/pkg/jinja2/jinja2_renderer.go +++ b/pkg/jinja2/jinja2_renderer.go @@ -23,6 +23,9 @@ type pythonJinja2Renderer struct { stdout io.ReadCloser stdoutReader *bufio.Reader + + trimBlocks bool + lstripBlocks bool } func newPythonJinja2Renderer() (*pythonJinja2Renderer, error) { @@ -123,6 +126,9 @@ type jinja2Args struct { SearchDirs []string `json:"searchDirs"` Vars string `json:"vars"` Strict bool `json:"strict"` + + TrimBlocks bool `json:"trimBlocks"` + LStripBlocks bool `json:"lstripBlocks"` } type jinja2Result struct { @@ -147,6 +153,8 @@ func (j *pythonJinja2Renderer) renderHelper(jobs []*RenderJob, searchDirs []stri jargs.Vars = string(varsStr) jargs.SearchDirs = searchDirs jargs.Strict = strict + jargs.TrimBlocks = j.trimBlocks + jargs.LStripBlocks = j.lstripBlocks for _, job := range jobs { if ist, r := isMaybeTemplate(job.Template, searchDirs, isString); !ist { diff --git a/pkg/jinja2/python_src/jinja2_renderer.py b/pkg/jinja2/python_src/jinja2_renderer.py index 52e868b30..fbc1a1364 100644 --- a/pkg/jinja2/python_src/jinja2_renderer.py +++ b/pkg/jinja2/python_src/jinja2_renderer.py @@ -27,6 +27,9 @@ def _return_self(self, other): __pow__ = __rpow__ = _return_self class Jinja2Renderer: + def __init__(self, trim_blocks=False, lstrip_blocks=False): + self.trim_blocks = trim_blocks + self.lstrip_blocks = lstrip_blocks def get_image_wrapper(self, image, latest_version=None): if latest_version is None: @@ -74,7 +77,8 @@ def build_env(self, vars_str, search_dirs, strict): environment = KluctlJinja2Environment(loader=FileSystemLoader(search_dirs), undefined=StrictUndefined if strict else NullUndefined, cache_size=10000, - auto_reload=False) + auto_reload=False, + trim_blocks=self.trim_blocks, lstrip_blocks=self.lstrip_blocks) merge_dict(environment.globals, vars, clone=False) add_jinja2_filters(environment) diff --git a/pkg/jinja2/python_src/main.py b/pkg/jinja2/python_src/main.py index 4f35ec9b7..15916d2c3 100644 --- a/pkg/jinja2/python_src/main.py +++ b/pkg/jinja2/python_src/main.py @@ -5,14 +5,14 @@ def main(): - r = Jinja2Renderer() - while True: args = sys.stdin.readline() if not args: break args = json.loads(args) + r = Jinja2Renderer(args.get("trimBlocks", False), args.get("lstripBlocks", False)) + if args["cmd"] == "render-strings": result = r.RenderStrings(args["templates"], args["searchDirs"] or [], args["vars"], args["strict"]) elif args["cmd"] == "render-files": From a0713fbde6841fb692b953189c44de8a7503a726 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 1 Sep 2022 16:26:00 +0200 Subject: [PATCH 0977/2916] chore: Run go get -u ./... --- go.mod | 58 ++++++++++++++++++++++++++-------------------------- go.sum | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 29 deletions(-) diff --git a/go.mod b/go.mod index df2bf20cd..eb85f2f1c 100644 --- a/go.mod +++ b/go.mod @@ -5,12 +5,12 @@ go 1.19 require ( github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e github.com/Masterminds/semver/v3 v3.1.1 - github.com/aws/aws-sdk-go v1.44.80 - github.com/bitnami-labs/sealed-secrets v0.18.1 + github.com/aws/aws-sdk-go v1.44.89 + github.com/bitnami-labs/sealed-secrets v0.18.2 github.com/cyphar/filepath-securejoin v0.2.3 github.com/docker/distribution v2.8.1+incompatible github.com/evanphx/json-patch v5.6.0+incompatible - github.com/fluxcd/pkg/kustomize v0.5.3 + github.com/fluxcd/pkg/kustomize v0.7.0 github.com/go-git/go-git/v5 v5.4.2 github.com/go-playground/validator/v10 v10.11.0 github.com/gobwas/glob v0.2.3 @@ -21,35 +21,35 @@ require ( github.com/imdario/mergo v0.3.13 github.com/jinzhu/copier v0.3.5 github.com/kevinburke/ssh_config v1.2.0 - github.com/kluctl/kluctl-python-deps v0.0.0-20220819065415-624185ed2c0a + github.com/kluctl/kluctl-python-deps v0.0.0-20220819072433-e8107cc5ae41 github.com/mattn/go-isatty v0.0.16 github.com/mitchellh/go-ps v1.0.0 github.com/mitchellh/reflectwalk v1.0.2 github.com/ohler55/ojg v1.14.4 github.com/pkg/errors v0.9.1 github.com/r3labs/diff/v2 v2.15.1 - github.com/rogpeppe/go-internal v1.8.1 + github.com/rogpeppe/go-internal v1.9.0 github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.5.0 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.12.0 github.com/stretchr/testify v1.8.0 - github.com/vbauerster/mpb/v7 v7.4.2 + github.com/vbauerster/mpb/v7 v7.5.2 github.com/whilp/git-urls v1.0.0 - github.com/xanzy/ssh-agent v0.3.1 - golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8 - golang.org/x/net v0.0.0-20220812174116-3211cb980234 + github.com/xanzy/ssh-agent v0.3.2 + golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 + golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde - golang.org/x/sys v0.0.0-20220818161305-2296e01440c6 + golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 golang.org/x/text v0.3.7 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 - helm.sh/helm/v3 v3.9.3 - k8s.io/api v0.24.4 - k8s.io/apiextensions-apiserver v0.24.4 - k8s.io/apimachinery v0.24.4 - k8s.io/client-go v0.24.4 + helm.sh/helm/v3 v3.9.4 + k8s.io/api v0.25.0 + k8s.io/apiextensions-apiserver v0.25.0 + k8s.io/apimachinery v0.25.0 + k8s.io/client-go v0.25.0 k8s.io/klog/v2 v2.70.1 sigs.k8s.io/kind v0.14.0 sigs.k8s.io/kustomize/api v0.12.1 @@ -72,7 +72,7 @@ require ( github.com/Masterminds/sprig/v3 v3.2.2 // indirect github.com/Masterminds/squirrel v1.5.3 // indirect github.com/Microsoft/go-winio v0.5.2 // indirect - github.com/ProtonMail/go-crypto v0.0.0-20220812175011-7fcef0dbe794 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895 // indirect github.com/VividCortex/ewma v1.2.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect github.com/acomagu/bufpipe v1.0.3 // indirect @@ -83,7 +83,7 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v3 v3.2.2 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect - github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 // indirect + github.com/chai2010/gettext-go v1.0.2 // indirect github.com/cloudflare/circl v1.2.0 // indirect github.com/containerd/containerd v1.6.8 // indirect github.com/davecgh/go-spew v1.1.1 // indirect @@ -92,7 +92,7 @@ require ( github.com/docker/docker-credential-helpers v0.6.4 // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-metrics v0.0.1 // indirect - github.com/docker/go-units v0.4.0 // indirect + github.com/docker/go-units v0.5.0 // indirect github.com/emicklei/go-restful/v3 v3.9.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect @@ -123,7 +123,7 @@ require ( github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-hclog v1.2.2 // indirect + github.com/hashicorp/go-hclog v1.3.0 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-plugin v1.4.5 // indirect @@ -174,7 +174,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 // indirect github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.0.3 // indirect + github.com/pelletier/go-toml/v2 v2.0.5 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pierrec/lz4 v2.6.1+incompatible // indirect github.com/pmezard/go-difflib v1.0.0 // indirect @@ -191,7 +191,7 @@ require ( github.com/spf13/afero v1.9.2 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/subosito/gotenv v1.4.0 // indirect + github.com/subosito/gotenv v1.4.1 // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect @@ -199,22 +199,22 @@ require ( github.com/xlab/treeprint v1.1.0 // indirect go.starlark.net v0.0.0-20220817180228-f738f5508c12 // indirect go.uber.org/atomic v1.10.0 // indirect - golang.org/x/oauth2 v0.0.0-20220808172628-8227340efae7 // indirect + golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094 // indirect golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1 // indirect - google.golang.org/grpc v1.48.0 // indirect + google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf // indirect + google.golang.org/grpc v1.49.0 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect - k8s.io/apiserver v0.24.4 // indirect - k8s.io/cli-runtime v0.24.4 // indirect - k8s.io/component-base v0.24.4 // indirect + k8s.io/apiserver v0.25.0 // indirect + k8s.io/cli-runtime v0.25.0 // indirect + k8s.io/component-base v0.25.0 // indirect k8s.io/kube-openapi v0.0.0-20220803164354-a70c9af30aea // indirect - k8s.io/kubectl v0.24.4 // indirect - k8s.io/utils v0.0.0-20220812165043-ad590609e2e5 // indirect + k8s.io/kubectl v0.25.0 // indirect + k8s.io/utils v0.0.0-20220823124924-e9cbc92d1a73 // indirect oras.land/oras-go v1.2.0 // indirect sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect sigs.k8s.io/yaml v1.3.0 // indirect diff --git a/go.sum b/go.sum index ed9d54a25..301f77c4e 100644 --- a/go.sum +++ b/go.sum @@ -94,10 +94,12 @@ github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy86 github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/sprig v2.15.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= @@ -116,6 +118,8 @@ github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmU github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= github.com/ProtonMail/go-crypto v0.0.0-20220812175011-7fcef0dbe794 h1:efPD6snIrIBAfmZhcm7GQ72VHlzsQ/3OrghnnGEpJBM= github.com/ProtonMail/go-crypto v0.0.0-20220812175011-7fcef0dbe794/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8= +github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895 h1:NsReiLpErIPzRrnogAXYwSoU7txA977LjDGrbkewJbg= +github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= @@ -162,6 +166,8 @@ github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpi github.com/aws/aws-sdk-go v1.36.30/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go v1.44.80 h1:jEXGecSgPdvM5KnyDsSgFhZSm7WwaTp4h544Im4SfhI= github.com/aws/aws-sdk-go v1.44.80/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.89 h1:Xf5Pp9GsNSMRinAuWNiQd0vusXXb3IgYbNlxldhWS2Q= +github.com/aws/aws-sdk-go v1.44.89/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -171,6 +177,8 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitnami-labs/sealed-secrets v0.18.1 h1:xXzi0Z6lArTykRGqCOC2rN3zN648zjQtkCzAVjJj9OY= github.com/bitnami-labs/sealed-secrets v0.18.1/go.mod h1:pOMGS1imRiIPLm7OpdD/s/OfgQmLkKxTqWruSEiQqCM= +github.com/bitnami-labs/sealed-secrets v0.18.2 h1:CfZD0JmhAPOQ7YqxrxZ90/+BjqTheWztBOYploqO6nc= +github.com/bitnami-labs/sealed-secrets v0.18.2/go.mod h1:wlhaZ+SI5aJkpq9CyyP0pVLJEj4LPXLzLHb2TnID/Rc= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/bkielbasa/cyclop v1.2.0/go.mod h1:qOI0yy6A7dYC4Zgsa72Ppm9kONl0RoIlPbzot9mhmeI= @@ -191,12 +199,15 @@ github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4r github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 h1:7aWHqerlJ41y6FOsEUvknqgXnGmJyJSbjhAWq5pO4F8= github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= +github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= +github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= github.com/charithe/durationcheck v0.0.9/go.mod h1:SSbRIBVfMjCi/kEB6K65XEA83D6prSM8ap1UCpNKtgg= github.com/chavacava/garif v0.0.0-20210405164556-e8a0a408d6af/go.mod h1:Qjyv4H3//PWVzTeCezG2b9IRn6myJxJSr4TD/xo6ojU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -275,6 +286,8 @@ github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQ github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= @@ -322,6 +335,8 @@ github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= github.com/fluxcd/pkg/kustomize v0.5.3 h1:WpxNOV/Yklp0p7Qv85VwBegq9fABuLR9qSWaAVa3+yc= github.com/fluxcd/pkg/kustomize v0.5.3/go.mod h1:zy1FLxkEDADUykCnrXqq6rVN48t3uMhAb3ao+zv0rFE= +github.com/fluxcd/pkg/kustomize v0.7.0 h1:604rlpRZTWaOfzDZ1W93aHaFh9kn8/UMX/wzsjwIUQY= +github.com/fluxcd/pkg/kustomize v0.7.0/go.mod h1:zJY3Z0+SX+zs+/A1F6fCT0JvUce265XnrpTtHnujXPo= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= @@ -606,6 +621,8 @@ github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrj github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.2.2 h1:ihRI7YFwcZdiSD7SIenIhHfQH3OuDvWerAUBZbeQS3M= github.com/hashicorp/go-hclog v1.2.2/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-hclog v1.3.0 h1:G0ACM8Z2WilWgPv3Vdzwm3V0BQu/kSmrkVtpe1fy9do= +github.com/hashicorp/go-hclog v1.3.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= @@ -737,6 +754,8 @@ github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQan github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/kluctl/kluctl-python-deps v0.0.0-20220819065415-624185ed2c0a h1:2W9TlV8b6sqjIKhbp9bgKjusdF3HgQucFNmwGNb1N+Y= github.com/kluctl/kluctl-python-deps v0.0.0-20220819065415-624185ed2c0a/go.mod h1:w4cTz1av5JFX9bMTB7Vm2qtmm5+eU+ncvB1/oyjVwOw= +github.com/kluctl/kluctl-python-deps v0.0.0-20220819072433-e8107cc5ae41 h1:sMQz3y4XtYS4YjAil3B8pxuhjPEObt0xMPMwmPZC9O0= +github.com/kluctl/kluctl-python-deps v0.0.0-20220819072433-e8107cc5ae41/go.mod h1:w4cTz1av5JFX9bMTB7Vm2qtmm5+eU+ncvB1/oyjVwOw= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -934,6 +953,7 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= +github.com/onsi/gomega v1.20.0 h1:8W0cWlwFkflGPLltQvLRB7ZVD5HuP6ng320w2IS245Q= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 h1:+czc/J8SlhPKLOtVLMQc+xDCFBT73ZStMsRhSsUhsSg= @@ -955,6 +975,8 @@ github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3v github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.0.3 h1:h9JoA60e1dVEOpp0PFwJSmt1Htu057NUq9/bUwaO61s= github.com/pelletier/go-toml/v2 v2.0.3/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= +github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= +github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw= @@ -1036,6 +1058,8 @@ github.com/rogpeppe/go-internal v1.6.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rubenv/sql-migrate v1.1.2 h1:9M6oj4e//owVVHYrFISmY9LBRw6gzkCNmD9MV36tZeQ= github.com/rubenv/sql-migrate v1.1.2/go.mod h1:/7TZymwxN8VWumcIxw1jjHEcR1djpdkMHQPT4FWdnbQ= @@ -1134,6 +1158,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.4.0 h1:yAzM1+SmVcz5R4tXGsNMu1jUl2aOJXoiWUCEwwnGrvs= github.com/subosito/gotenv v1.4.0/go.mod h1:mZd6rFysKEcUhUHXJk0C/08wAgyDBFuwEYL7vWWGaGo= +github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= +github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/sylvia7788/contextcheck v1.0.4/go.mod h1:vuPKJMQ7MQ91ZTqfdyreNKwZjyUg6KO+IebVyQDedZQ= github.com/tdakkota/asciicheck v0.0.0-20200416200610-e657995f937b/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM= github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= @@ -1163,6 +1189,8 @@ github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7Fw github.com/vbatts/tar-split v0.11.2 h1:Via6XqJr0hceW4wff3QRzD5gAk/tatMw/4ZA7cTlIME= github.com/vbauerster/mpb/v7 v7.4.2 h1:n917F4d8EWdUKc9c81wFkksyG6P6Mg7IETfKCE1Xqng= github.com/vbauerster/mpb/v7 v7.4.2/go.mod h1:UmOiIUI8aPqWXIps0ciik3RKMdzx7+ooQpq+fBcXwBA= +github.com/vbauerster/mpb/v7 v7.5.2 h1:Ph3JvpBcoIwzIG1QwbUq97KQifrTRbKcMXN9rN5BYAs= +github.com/vbauerster/mpb/v7 v7.5.2/go.mod h1:UmOiIUI8aPqWXIps0ciik3RKMdzx7+ooQpq+fBcXwBA= github.com/viki-org/dnscache v0.0.0-20130720023526-c70c1f23c5d8/go.mod h1:dniwbG03GafCjFohMDmz6Zc6oCuiqgH6tGNyXTkHzXE= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= @@ -1171,6 +1199,8 @@ github.com/whilp/git-urls v1.0.0/go.mod h1:J16SAmobsqc3Qcy98brfl5f5+e0clUvg1krgw github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= github.com/xanzy/ssh-agent v0.3.1 h1:AmzO1SSWxw73zxFZPRwaMN1MohDw8UyHnmuxyceTEGo= github.com/xanzy/ssh-agent v0.3.1/go.mod h1:QIE4lCeL7nkC25x+yA3LBIYfwCc1TFziCtG7cBAac6w= +github.com/xanzy/ssh-agent v0.3.2 h1:eKj4SX2Fe7mui28ZgnFW5fmTz1EIr7ugo5s6wDxdHBM= +github.com/xanzy/ssh-agent v0.3.2/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= @@ -1282,9 +1312,12 @@ golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8 h1:GIAS/yBem/gq2MUqgNIzUHW7cJMmx3TGZOrnyYaNQ6c= golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1384,6 +1417,8 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220812174116-3211cb980234 h1:RDqmgfe7SvlMWoqC3xwQ2blLO3fcWcxMa3eBLRdRW7E= golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b h1:ZmngSVLe/wycRns9MKikG9OWIEjGcGAkacif7oYQaUY= +golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1404,6 +1439,8 @@ golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220808172628-8227340efae7 h1:dtndE8FcEta75/4kHF3AbpuWzV6f1LjnLrM4pe2SZrw= golang.org/x/oauth2 v0.0.0-20220808172628-8227340efae7/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094 h1:2o1E+E8TpNLklK9nHiPiK1uzIYrIHt+cQx3ynCwq9V8= +golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1519,6 +1556,8 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220818161305-2296e01440c6 h1:Sx/u41w+OwrInGdEckYmEuU5gHoGSL4QbDz3S9s6j4U= golang.org/x/sys v0.0.0-20220818161305-2296e01440c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 h1:v6hYoSR9T5oet+pMXwUWkbiVqx/63mlHjefrHmxwfeY= +golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1756,6 +1795,8 @@ google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEc google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1 h1:C2UVWqrgLYKrT5nh5oU6hLRm1AeEklCK5eloQA1NtFY= google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf h1:Q5xNKbTSFwkuaaGaR7CMcXEM5sy19KYdUU8iF8/iRC0= +google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -1787,6 +1828,8 @@ google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.48.0 h1:rQOsyJ/8+ufEDJd/Gdsz7HG220Mh9HAhFHRGnIjda0w= google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw= +google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -1853,6 +1896,8 @@ gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= helm.sh/helm/v3 v3.9.3 h1:etd4Qc45/bnIkBofZIRwrAzYuG3bNWR1EdMN4fsfzoE= helm.sh/helm/v3 v3.9.3/go.mod h1:3eaWAIqzvlRSD06gR9MMwmp2KBKwlu9av1/1BZpjeWY= +helm.sh/helm/v3 v3.9.4 h1:TCI1QhJUeLVOdccfdw+vnSEO3Td6gNqibptB04QtExY= +helm.sh/helm/v3 v3.9.4/go.mod h1:3eaWAIqzvlRSD06gR9MMwmp2KBKwlu9av1/1BZpjeWY= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1863,23 +1908,38 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.2.1/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY= k8s.io/api v0.24.4 h1:I5Y645gJ8zWKawyr78lVfDQkZrAViSbeRXsPZWTxmXk= k8s.io/api v0.24.4/go.mod h1:42pVfA0NRxrtJhZQOvRSyZcJihzAdU59WBtTjYcB0/M= +k8s.io/api v0.25.0 h1:H+Q4ma2U/ww0iGB78ijZx6DRByPz6/733jIuFpX70e0= +k8s.io/api v0.25.0/go.mod h1:ttceV1GyV1i1rnmvzT3BST08N6nGt+dudGrquzVQWPk= k8s.io/apiextensions-apiserver v0.24.4 h1:w53Pm4zu8fCt9WfiRgS2YI6LE6I4NJ5aUi78GElD3K8= k8s.io/apiextensions-apiserver v0.24.4/go.mod h1:iDK+Xb4jsPNnRGj5jU/WqqjLvt8363M7cKixKe1C9+U= +k8s.io/apiextensions-apiserver v0.25.0 h1:CJ9zlyXAbq0FIW8CD7HHyozCMBpDSiH7EdrSTCZcZFY= +k8s.io/apiextensions-apiserver v0.25.0/go.mod h1:3pAjZiN4zw7R8aZC5gR0y3/vCkGlAjCazcg1me8iB/E= k8s.io/apimachinery v0.24.4 h1:S0Ur3J/PbivTcL43EdSdPhqCqKla2NIuneNwZcTDeGQ= k8s.io/apimachinery v0.24.4/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= +k8s.io/apimachinery v0.25.0 h1:MlP0r6+3XbkUG2itd6vp3oxbtdQLQI94fD5gCS+gnoU= +k8s.io/apimachinery v0.25.0/go.mod h1:qMx9eAk0sZQGsXGu86fab8tZdffHbwUfsvzqKn4mfB0= k8s.io/apiserver v0.24.4 h1:ei+OunC83pVEiagBeZhTnRZvkclHgpzs/rrm7aSBDYs= k8s.io/apiserver v0.24.4/go.mod h1:mAuC3pZVc0IDXLx7lUHoisBOtBa1SobfLW/CI3klXQE= +k8s.io/apiserver v0.25.0 h1:8kl2ifbNffD440MyvHtPaIz1mw4mGKVgWqM0nL+oyu4= +k8s.io/apiserver v0.25.0/go.mod h1:BKwsE+PTC+aZK+6OJQDPr0v6uS91/HWxX7evElAH6xo= k8s.io/cli-runtime v0.24.4 h1:YCSf0dZp+pYXVR/8aZQ6MEBSiicv8rLyVsGBEbRnwfY= k8s.io/cli-runtime v0.24.4/go.mod h1:RF+cSLYXkPV3WyvPrX2qeRLEUJY38INWx6jLKVLFCxM= +k8s.io/cli-runtime v0.25.0 h1:XBnTc2Fi+w818jcJGzhiJKQuXl8479sZ4FhtV5hVJ1Q= +k8s.io/cli-runtime v0.25.0/go.mod h1:bHOI5ZZInRHhbq12OdUiYZQN8ml8aKZLwQgt9QlLINw= k8s.io/client-go v0.24.4 h1:hIAIJZIPyaw46AkxwyR0FRfM/pRxpUNTd3ysYu9vyRg= k8s.io/client-go v0.24.4/go.mod h1:+AxlPWw/H6f+EJhRSjIeALaJT4tbeB/8g9BNvXGPd0Y= +k8s.io/client-go v0.25.0 h1:CVWIaCETLMBNiTUta3d5nzRbXvY5Hy9Dpl+VvREpu5E= +k8s.io/client-go v0.25.0/go.mod h1:lxykvypVfKilxhTklov0wz1FoaUZ8X4EwbhS6rpRfN8= k8s.io/code-generator v0.24.4/go.mod h1:dpVhs00hTuTdTY6jvVxvTFCk6gSMrtfRydbhZwHI15w= k8s.io/component-base v0.24.4 h1:WEGRp06GBYVwxp5JdiRaJ1zkdOhrqucxRv/8IrABLG0= k8s.io/component-base v0.24.4/go.mod h1:sWxkgcMfbYHadw0OJ0N+vIscd14/nqSIM2veCdg843o= +k8s.io/component-base v0.25.0 h1:haVKlLkPCFZhkcqB6WCvpVxftrg6+FK5x1ZuaIDaQ5Y= +k8s.io/component-base v0.25.0/go.mod h1:F2Sumv9CnbBlqrpdf7rKZTmmd2meJq0HizeyY/yAFxk= k8s.io/component-helpers v0.24.4/go.mod h1:xAHlOKU8rAjLgXWJEsueWLR1LDMThbaPf2YvgKpSyQ8= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/gengo v0.0.0-20211129171323-c02415ce4185/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.60.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= @@ -1891,11 +1951,15 @@ k8s.io/kube-openapi v0.0.0-20220803164354-a70c9af30aea h1:3QOH5+2fGsY8e1qf+GIFpg k8s.io/kube-openapi v0.0.0-20220803164354-a70c9af30aea/go.mod h1:C/N6wCaBHeBHkHUesQOQy2/MZqGgMAFPqGsGQLdbZBU= k8s.io/kubectl v0.24.4 h1:fPEBkAV3/cu3BQVIUCXNngCCY62AlZ+2rkRVHcmJPn0= k8s.io/kubectl v0.24.4/go.mod h1:AVyJzxUwA5UMGTDyKGL6nd6RRW36FbmAdtIDMhrZtW0= +k8s.io/kubectl v0.25.0 h1:/Wn1cFqo8ik3iee1EvpxYre3bkWsGLXzLQI6uCCAkQc= +k8s.io/kubectl v0.25.0/go.mod h1:n16ULWsOl2jmQpzt2o7Dud1t4o0+Y186ICb4O+GwKAU= k8s.io/metrics v0.24.4/go.mod h1:7D8Xm3DGZoJaiCS8+QA2EzdMuDlq0Y8SiOPUB/1BaGU= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20220812165043-ad590609e2e5 h1:XmRqFcQlCy/lKRZ39j+RVpokYNroHPqV3mcBRfnhT5o= k8s.io/utils v0.0.0-20220812165043-ad590609e2e5/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20220823124924-e9cbc92d1a73 h1:H9TCJUUx+2VA0ZiD9lvtaX8fthFsMoD+Izn93E/hm8U= +k8s.io/utils v0.0.0-20220823124924-e9cbc92d1a73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= mvdan.cc/gofumpt v0.1.1/go.mod h1:yXG1r1WqZVKWbVRtBWKWX9+CxGYfA51nSomhM0woR48= mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= From d0e639c4ec6f6882d018b86d2ca501d61110bedf Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 5 Sep 2022 11:26:28 +0200 Subject: [PATCH 0978/2916] fix: Fix crash when secrets file has no "secrets" root --- pkg/vars/vars_loader.go | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/pkg/vars/vars_loader.go b/pkg/vars/vars_loader.go index 5afb09788..49ffe3e9b 100644 --- a/pkg/vars/vars_loader.go +++ b/pkg/vars/vars_loader.go @@ -108,6 +108,12 @@ func (v *VarsLoader) loadFile(varsCtx *VarsCtx, path string, searchDirs []string } if rootKey != "" { newVars, _, err = newVars.GetNestedObject(rootKey) + if err != nil { + return err + } + if newVars == nil { + return fmt.Errorf("vars from %s have no '%s' root", path, rootKey) + } } v.mergeVars(varsCtx, newVars, rootKey) return nil @@ -146,7 +152,7 @@ func (v *VarsLoader) loadAwsSecretsManager(varsCtx *VarsCtx, source *types.VarsS if err != nil { return err } - return v.loadFromString(varsCtx, secret, rootKey) + return v.loadFromString(varsCtx, secret, "awsSecretsManager", rootKey) } func (v *VarsLoader) loadVault(varsCtx *VarsCtx, source *types.VarsSource, rootKey string) error { @@ -154,7 +160,7 @@ func (v *VarsLoader) loadVault(varsCtx *VarsCtx, source *types.VarsSource, rootK if err != nil { return err } - return v.loadFromString(varsCtx, secret, rootKey) + return v.loadFromString(varsCtx, secret, "vault", rootKey) } func (v *VarsLoader) loadGit(varsCtx *VarsCtx, gitFile *types.VarsSourceGit, rootKey string) error { @@ -178,7 +184,7 @@ func (v *VarsLoader) loadGit(varsCtx *VarsCtx, gitFile *types.VarsSourceGit, roo return err } - return v.loadFromString(varsCtx, string(f), rootKey) + return v.loadFromString(varsCtx, string(f), "git", rootKey) } func (v *VarsLoader) loadFromK8sObject(varsCtx *VarsCtx, varsSource types.VarsSourceClusterConfigMapOrSecret, kind string, key string, rootKey string, base64Decode bool) error { @@ -237,14 +243,14 @@ func (v *VarsLoader) loadFromK8sObject(varsCtx *VarsCtx, varsSource types.VarsSo } } - err = v.loadFromString(varsCtx, value, rootKey) + err = v.loadFromString(varsCtx, value, "k8s", rootKey) if err != nil { return fmt.Errorf("failed to load vars from kubernetes object %s and key %s: %w", ref.String(), key, err) } return nil } -func (v *VarsLoader) loadFromString(varsCtx *VarsCtx, s string, rootKey string) error { +func (v *VarsLoader) loadFromString(varsCtx *VarsCtx, s string, secretType string, rootKey string) error { newVars := uo.New() err := v.renderYamlString(varsCtx, s, newVars) if err != nil { @@ -253,6 +259,12 @@ func (v *VarsLoader) loadFromString(varsCtx *VarsCtx, s string, rootKey string) if rootKey != "" { newVars, _, err = newVars.GetNestedObject(rootKey) + if err != nil { + return err + } + if newVars == nil { + return fmt.Errorf("%s secret has no '%s' root", secretType, rootKey) + } } v.mergeVars(varsCtx, newVars, rootKey) From 9bf311f63a29d16825b96a2f7d1b518398159ec5 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 5 Sep 2022 11:32:39 +0200 Subject: [PATCH 0979/2916] chore: Run go mod tidy --- go.sum | 227 --------------------------------------------------------- 1 file changed, 227 deletions(-) diff --git a/go.sum b/go.sum index 301f77c4e..d09bfb382 100644 --- a/go.sum +++ b/go.sum @@ -60,10 +60,8 @@ github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOEl github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= github.com/Azure/go-autorest/autorest v0.11.28 h1:ndAExarwr5Y+GaHE6VCaY1kyS/HwwGGyuimVhWsHOEM= github.com/Azure/go-autorest/autorest v0.11.28/go.mod h1:MrkzG3Y3AH668QyF9KRk5neJnGgmhQ6krbhR8Q5eMvA= -github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= github.com/Azure/go-autorest/autorest/adal v0.9.21 h1:jjQnVFXPfekaqb8vIsv2G1lxshoW+oGv4MDlhRtnYZk= github.com/Azure/go-autorest/autorest/adal v0.9.21/go.mod h1:zua7mBUaCc5YnSLKYgGJR/w5ePdMDA6H56upLsHzA9U= @@ -87,19 +85,16 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= -github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/sprig v2.15.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= -github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= @@ -107,21 +102,14 @@ github.com/Masterminds/squirrel v1.5.3 h1:YPpoceAcxuzIljlr5iWpNKaql7hLeG1KLSrhvd github.com/Masterminds/squirrel v1.5.3/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= -github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/hcsshim v0.9.4 h1:mnUj0ivWy6UzbB1uLFqKR6F+ZyiDc7j4iGgHTpO+5+I= -github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM= github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= -github.com/ProtonMail/go-crypto v0.0.0-20220812175011-7fcef0dbe794 h1:efPD6snIrIBAfmZhcm7GQ72VHlzsQ/3OrghnnGEpJBM= -github.com/ProtonMail/go-crypto v0.0.0-20220812175011-7fcef0dbe794/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8= github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895 h1:NsReiLpErIPzRrnogAXYwSoU7txA977LjDGrbkewJbg= github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8= -github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= @@ -144,7 +132,6 @@ github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210826220005-b48c857c3a0e/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= @@ -156,7 +143,6 @@ github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/ashanbrown/forbidigo v1.2.0/go.mod h1:vVW7PEdqEFqapJe95xHkTfB1+XvZXBFg8t0sG2FIxmI= @@ -164,25 +150,17 @@ github.com/ashanbrown/makezero v0.0.0-20210520155254-b6261585ddde/go.mod h1:oG9D github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.36.30/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= -github.com/aws/aws-sdk-go v1.44.80 h1:jEXGecSgPdvM5KnyDsSgFhZSm7WwaTp4h544Im4SfhI= -github.com/aws/aws-sdk-go v1.44.80/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aws/aws-sdk-go v1.44.89 h1:Xf5Pp9GsNSMRinAuWNiQd0vusXXb3IgYbNlxldhWS2Q= github.com/aws/aws-sdk-go v1.44.89/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= -github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= -github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bitnami-labs/sealed-secrets v0.18.1 h1:xXzi0Z6lArTykRGqCOC2rN3zN648zjQtkCzAVjJj9OY= -github.com/bitnami-labs/sealed-secrets v0.18.1/go.mod h1:pOMGS1imRiIPLm7OpdD/s/OfgQmLkKxTqWruSEiQqCM= github.com/bitnami-labs/sealed-secrets v0.18.2 h1:CfZD0JmhAPOQ7YqxrxZ90/+BjqTheWztBOYploqO6nc= github.com/bitnami-labs/sealed-secrets v0.18.2/go.mod h1:wlhaZ+SI5aJkpq9CyyP0pVLJEj4LPXLzLHb2TnID/Rc= -github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/bkielbasa/cyclop v1.2.0/go.mod h1:qOI0yy6A7dYC4Zgsa72Ppm9kONl0RoIlPbzot9mhmeI= -github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/blizzy78/varnamelen v0.3.0/go.mod h1:hbwRdBvoBqxk34XyQ6HA0UH3G0/1TKuv5AC4eaBT0Ec= github.com/bombsimon/wsl/v3 v3.3.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc= github.com/breml/bidichk v0.1.1/go.mod h1:zbfeitpevDUGI7V91Uzzuwrn4Vls8MoBMrwtt78jmso= @@ -197,15 +175,10 @@ github.com/bwesterb/go-ristretto v1.2.1/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7N github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= -github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 h1:7aWHqerlJ41y6FOsEUvknqgXnGmJyJSbjhAWq5pO4F8= -github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= github.com/charithe/durationcheck v0.0.9/go.mod h1:SSbRIBVfMjCi/kEB6K65XEA83D6prSM8ap1UCpNKtgg= @@ -222,28 +195,17 @@ github.com/cloudflare/circl v1.2.0/go.mod h1:Ch2UgYr6ti2KTtlejELlROl0YIYj7SLjAC8 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= -github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= -github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= -github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= github.com/containerd/cgroups v1.0.3 h1:ADZftAkglvCiD44c77s5YmMqaP2pzVCFZvBmAlBdAP4= github.com/containerd/containerd v1.6.8 h1:h4dOFDwzHmqFEP754PgfgTeVXFnLiRc6kiqC7tplDJs= github.com/containerd/containerd v1.6.8/go.mod h1:By6p5KqPK0/7/CgO/A6t/Gz+CUYUu2zf1hUaaymVXB0= github.com/containerd/stargz-snapshotter/estargz v0.12.0 h1:idtwRTLjk2erqiYhPWy2L844By8NRFYEwYHcXhoIWPM= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= -github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190620071333-e64a0ec8b42a/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= @@ -265,11 +227,9 @@ github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= github.com/denis-tingajkin/go-header v0.4.2/go.mod h1:eLRHAVXzE5atsKAnNRDB90WHCFFnBUn4RN0nRcs1LJA= github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/distribution/distribution/v3 v3.0.0-20220526142353-ffbd94cbe269 h1:hbCT8ZPPMqefiAWD2ZKjn7ypokIGViTvBBg/ExLSdCk= github.com/docker/cli v20.10.17+incompatible h1:eO2KS7ZFeov5UJeaDmIs1NFEDRf32PaqRpvoEkKBy5M= github.com/docker/cli v20.10.17+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= @@ -284,8 +244,6 @@ github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5Xh github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= -github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= -github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= @@ -293,9 +251,6 @@ github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3 github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= -github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= @@ -308,21 +263,16 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= -github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.0.14/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/esimonov/ifshort v1.0.3/go.mod h1:yZqNJUrNn20K8Q9n2CrjTKYyVEmX209Hgu+M1LBpeZE= github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY= -github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= -github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4= github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc= -github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= @@ -331,15 +281,10 @@ github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYF github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= -github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= -github.com/fluxcd/pkg/kustomize v0.5.3 h1:WpxNOV/Yklp0p7Qv85VwBegq9fABuLR9qSWaAVa3+yc= -github.com/fluxcd/pkg/kustomize v0.5.3/go.mod h1:zy1FLxkEDADUykCnrXqq6rVN48t3uMhAb3ao+zv0rFE= github.com/fluxcd/pkg/kustomize v0.7.0 h1:604rlpRZTWaOfzDZ1W93aHaFh9kn8/UMX/wzsjwIUQY= github.com/fluxcd/pkg/kustomize v0.7.0/go.mod h1:zJY3Z0+SX+zs+/A1F6fCT0JvUce265XnrpTtHnujXPo= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= -github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -347,15 +292,11 @@ github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5 github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fullstorydev/grpcurl v1.6.0/go.mod h1:ZQ+ayqbKMJNhzLmbpCiurTVlaK2M/3nqZCxaQ2Ze/sM= -github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= github.com/fzipp/gocyclo v0.3.1/go.mod h1:DJHO6AUmbdqj2ET4Z9iArSuwWgYDRryYt2wASxc7x3E= -github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= -github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-critic/go-critic v0.6.1/go.mod h1:SdNCfU0yF3UBjtaZGw6586/WocupMOJuiqgom5DsQxM= -github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= @@ -381,22 +322,17 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/zapr v1.2.0/go.mod h1:Qa4Bsj2Vb+FAVeAKsLD8RLQ+YRJB8YDmOAKxaBQf7Ro= github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= -github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= @@ -451,14 +387,11 @@ github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQA github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -500,18 +433,13 @@ github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZ github.com/golangci/misspell v0.3.5/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA= github.com/golangci/revgrep v0.0.0-20210930125155-c22e5001d4f2/go.mod h1:LK+zW4MpyytAWQRz0M4xnzEk50lSvqDQKfx304apFkY= github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ= -github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/cel-go v0.10.1/go.mod h1:U7ayypeSkw23szu4GaQTPJGx66c20mx8JklMSxrmI1w= -github.com/google/cel-spec v0.6.0/go.mod h1:Nwjgxy5CbjlPrtCWjeDjUyKMl8w41YBYGjsyDdqk0xA= github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= github.com/google/certificate-transparency-go v1.1.1/go.mod h1:FDKqPvSXawb2ecErVRrD+nfy23RCzyl7eqVCEmlT1Zs= -github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0= github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -531,7 +459,6 @@ github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/go-containerregistry v0.11.0 h1:Xt8x1adcREjFcmDoDK8OdOsjxu90PHkGuwNP8GiHMLM= github.com/google/go-containerregistry v0.11.0/go.mod h1:BBaYtsHPHA42uEgAvd/NejvAfPSlz281sJWqupjSxfk= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -567,7 +494,6 @@ github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= -github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -594,15 +520,11 @@ github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod github.com/gostaticanalysis/testutil v0.4.0/go.mod h1:bLIoPefWXrRi/ssLFWX1dx7Repi5x3CuviD3dgAZaBU= github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= -github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.12.1/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= @@ -619,8 +541,6 @@ github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9n github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v1.2.2 h1:ihRI7YFwcZdiSD7SIenIhHfQH3OuDvWerAUBZbeQS3M= -github.com/hashicorp/go-hclog v1.2.2/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-hclog v1.3.0 h1:G0ACM8Z2WilWgPv3Vdzwm3V0BQu/kSmrkVtpe1fy9do= github.com/hashicorp/go-hclog v1.3.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= @@ -688,7 +608,6 @@ github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.4/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= @@ -718,7 +637,6 @@ github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.2.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= -github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/josharian/txtarfs v0.0.0-20210218200122-0702f000015a/go.mod h1:izVPOvVRsHiKkeGCT6tYBNWyDVuzj9wAaBb5R9qamfw= @@ -752,8 +670,6 @@ github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8 github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= -github.com/kluctl/kluctl-python-deps v0.0.0-20220819065415-624185ed2c0a h1:2W9TlV8b6sqjIKhbp9bgKjusdF3HgQucFNmwGNb1N+Y= -github.com/kluctl/kluctl-python-deps v0.0.0-20220819065415-624185ed2c0a/go.mod h1:w4cTz1av5JFX9bMTB7Vm2qtmm5+eU+ncvB1/oyjVwOw= github.com/kluctl/kluctl-python-deps v0.0.0-20220819072433-e8107cc5ae41 h1:sMQz3y4XtYS4YjAil3B8pxuhjPEObt0xMPMwmPZC9O0= github.com/kluctl/kluctl-python-deps v0.0.0-20220819072433-e8107cc5ae41/go.mod h1:w4cTz1av5JFX9bMTB7Vm2qtmm5+eU+ncvB1/oyjVwOw= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -794,18 +710,14 @@ github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs= github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= -github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= -github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/maratori/testpackage v1.0.1/go.mod h1:ddKdw+XG0Phzhx8BFDTKgpWP4i7MpApTE5fXSKAqwDU= @@ -841,7 +753,6 @@ github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mN github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= @@ -895,7 +806,6 @@ github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQ github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/moby/sys/mountinfo v0.5.0 h1:2Ks8/r6lopsxWi9m58nlwjaeSzUX9iiL1vj5qB/9ObI= -github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae h1:O4SWKdcHVCvYqyDV+9CJA1fcDN2L11Bule0iFy3YlAI= github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -913,14 +823,12 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mozilla/scribe v0.0.0-20180711195314-fb71baf557c1/go.mod h1:FIczTrinKo8VaLxe6PWTPEXRXDIHz2QAwiaBaP5/4a8= github.com/mozilla/tls-observatory v0.0.0-20210609171429-7bc42856d2e5/go.mod h1:FUqVoUPHSEdDR0MnFM3Dh8AU0pZHLXUD127SAJGER/s= -github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-proto-validators v0.0.0-20180403085117-0950a7990007/go.mod h1:m2XC9Qq0AlmmVksL6FktJCdTYyLk7V3fKyp0sl1yWQo= github.com/mwitkow/go-proto-validators v0.2.0/go.mod h1:ZfA1hW+UH/2ZHOWvQ3HnQaU0DtnpXu850MZiy+YUgcc= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= @@ -928,31 +836,24 @@ github.com/nishanths/exhaustive v0.2.3/go.mod h1:bhIX678Nx8inLM9PbpvK1yv6oGtoP8B github.com/nishanths/predeclared v0.0.0-20190419143655-18a43bb90ffc/go.mod h1:62PewwiQTlm/7Rj+cxVYqZvDIUc+JjZq6GHAC1fsObQ= github.com/nishanths/predeclared v0.2.1/go.mod h1:HvkGJcA3naj4lOwnFXFDkFxVtSqQMB9sbB1usJ+xjQE= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/ohler55/ojg v1.14.4 h1:L2ds8AlB5t/QbqSfhRwvagJzQ7pgmdrefMIypQs0Xik= github.com/ohler55/ojg v1.14.4/go.mod h1:7Ghirupn8NC8hSSDpI0gcjorPxj+vSVIONDWfliHR1k= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ= -github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo/v2 v2.1.4 h1:GNapqRSid3zijZ9H77KrgVG4/8KqiyRsxcSxe+7ApXY= -github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= github.com/onsi/gomega v1.20.0 h1:8W0cWlwFkflGPLltQvLRB7ZVD5HuP6ng320w2IS245Q= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= @@ -973,8 +874,6 @@ github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCko github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.0.3 h1:h9JoA60e1dVEOpp0PFwJSmt1Htu057NUq9/bUwaO61s= -github.com/pelletier/go-toml/v2 v2.0.3/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= @@ -998,9 +897,7 @@ github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndr github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/poy/onpar v0.0.0-20190519213022-ee068f8ea4d1 h1:oL4IBbcqwhhNWh31bjOX8C/OCy0zs9906d/VUru+bqg= github.com/poy/onpar v0.0.0-20190519213022-ee068f8ea4d1/go.mod h1:nSbFQvMj97ZyhFRSJYtut+msi4sOY6zJDGCdSc+/rZU= -github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= @@ -1014,8 +911,6 @@ github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1: github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= @@ -1025,7 +920,6 @@ github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+ github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= @@ -1034,7 +928,6 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/pseudomuto/protoc-gen-doc v1.3.2/go.mod h1:y5+P6n3iGrbKG+9O04V5ld71in3v/bX88wUwgt+U8EA= github.com/pseudomuto/protokit v0.2.0/go.mod h1:2PdH30hxVHsup8KpBTOXTBeMVhJZVio3Q8ViKSAXT0Q= github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI= @@ -1056,8 +949,6 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.6.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= -github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= -github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= @@ -1101,7 +992,6 @@ github.com/sivchari/tenv v1.4.7/go.mod h1:5nF+bITvkebQVanjU6IuMbvIot/7ReNsUV7I5N github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/sonatard/noctx v0.0.1/go.mod h1:9D2D/EoULe8Yy2joDHJj7bv3sZoq9AaSb8B4lqBjiZI= github.com/sourcegraph/go-diff v0.6.1/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -1117,7 +1007,6 @@ github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= @@ -1125,13 +1014,11 @@ github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJ github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/spf13/viper v1.9.0/go.mod h1:+i6ajR7OX2XaiBkrcZJFK21htRk7eDeLg7+O6bhUPP4= github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= @@ -1140,7 +1027,6 @@ github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRk github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v0.0.0-20170130113145-4d4bfba8f1d1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -1156,8 +1042,6 @@ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1F github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/subosito/gotenv v1.4.0 h1:yAzM1+SmVcz5R4tXGsNMu1jUl2aOJXoiWUCEwwnGrvs= -github.com/subosito/gotenv v1.4.0/go.mod h1:mZd6rFysKEcUhUHXJk0C/08wAgyDBFuwEYL7vWWGaGo= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/sylvia7788/contextcheck v1.0.4/go.mod h1:vuPKJMQ7MQ91ZTqfdyreNKwZjyUg6KO+IebVyQDedZQ= @@ -1171,7 +1055,6 @@ github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcy github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20200427203606-3cfed13b9966/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tomarrell/wrapcheck/v2 v2.4.0/go.mod h1:68bQ/eJg55BROaRTbMjC7vuhL2OgfoG8bLp9ZyoBfyY= github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4= github.com/tommy-muehle/go-mnd/v2 v2.4.0/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= @@ -1187,8 +1070,6 @@ github.com/valyala/fasthttp v1.30.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD github.com/valyala/quicktemplate v1.7.0/go.mod h1:sqKJnoaOF88V07vkO+9FL8fb9uZg/VPSJnLYn+LmLk8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/vbatts/tar-split v0.11.2 h1:Via6XqJr0hceW4wff3QRzD5gAk/tatMw/4ZA7cTlIME= -github.com/vbauerster/mpb/v7 v7.4.2 h1:n917F4d8EWdUKc9c81wFkksyG6P6Mg7IETfKCE1Xqng= -github.com/vbauerster/mpb/v7 v7.4.2/go.mod h1:UmOiIUI8aPqWXIps0ciik3RKMdzx7+ooQpq+fBcXwBA= github.com/vbauerster/mpb/v7 v7.5.2 h1:Ph3JvpBcoIwzIG1QwbUq97KQifrTRbKcMXN9rN5BYAs= github.com/vbauerster/mpb/v7 v7.5.2/go.mod h1:UmOiIUI8aPqWXIps0ciik3RKMdzx7+ooQpq+fBcXwBA= github.com/viki-org/dnscache v0.0.0-20130720023526-c70c1f23c5d8/go.mod h1:dniwbG03GafCjFohMDmz6Zc6oCuiqgH6tGNyXTkHzXE= @@ -1197,8 +1078,6 @@ github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6Ac github.com/whilp/git-urls v1.0.0 h1:95f6UMWN5FKW71ECsXRUd3FVYiXdrE7aX4NZKcPmIjU= github.com/whilp/git-urls v1.0.0/go.mod h1:J16SAmobsqc3Qcy98brfl5f5+e0clUvg1krgwk/qCfE= github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= -github.com/xanzy/ssh-agent v0.3.1 h1:AmzO1SSWxw73zxFZPRwaMN1MohDw8UyHnmuxyceTEGo= -github.com/xanzy/ssh-agent v0.3.1/go.mod h1:QIE4lCeL7nkC25x+yA3LBIYfwCc1TFziCtG7cBAac6w= github.com/xanzy/ssh-agent v0.3.2 h1:eKj4SX2Fe7mui28ZgnFW5fmTz1EIr7ugo5s6wDxdHBM= github.com/xanzy/ssh-agent v0.3.2/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= @@ -1209,7 +1088,6 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1: github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= github.com/xlab/treeprint v1.1.0 h1:G/1DjNkPpfZCFt9CSh6b5/nY4VimlbHF3Rh4obvtzDk= github.com/xlab/treeprint v1.1.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= @@ -1224,27 +1102,17 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI= github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= -go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.etcd.io/etcd v0.0.0-20200513171258-e048e166ab9c/go.mod h1:xCI7ZzBfRuGgBXyXO6yfWfDmlWd35khcWpUa4L0xI/k= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= -go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= -go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= -go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= -go.etcd.io/etcd/client/v3 v3.5.1/go.mod h1:OnjH4M8OnAotwaB2l9bVgZzRFKru7/ZMoS46OtKyd3Q= -go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE= -go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc= -go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4= go.mozilla.org/mozlog v0.0.0-20170222151521-4bb13139d403/go.mod h1:jHoPAGnDrCy6kaI2tAze5Prf0Nr0w/oNkROt2lw3n3o= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= @@ -1254,19 +1122,7 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= -go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= -go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= -go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= -go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= -go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= -go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= -go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= -go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= go.starlark.net v0.0.0-20220817180228-f738f5508c12 h1:xOBJXWGEDwU5xSDxH6macxO11Us0AH2fTa9rmsbbF7g= go.starlark.net v0.0.0-20220817180228-f738f5508c12/go.mod h1:VZcBMdr3cT3PnBoWunTabuSEXwVAH+ZJ5zxfs3AdASk= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -1275,7 +1131,6 @@ go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= @@ -1284,7 +1139,6 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9E go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= golang.org/x/crypto v0.0.0-20180501155221-613d6eafa307/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -1300,22 +1154,17 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8 h1:GIAS/yBem/gq2MUqgNIzUHW7cJMmx3TGZOrnyYaNQ6c= -golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1355,7 +1204,6 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= -golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1375,7 +1223,6 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -1397,7 +1244,6 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -1410,13 +1256,9 @@ golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220812174116-3211cb980234 h1:RDqmgfe7SvlMWoqC3xwQ2blLO3fcWcxMa3eBLRdRW7E= -golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b h1:ZmngSVLe/wycRns9MKikG9OWIEjGcGAkacif7oYQaUY= golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1435,10 +1277,7 @@ golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220808172628-8227340efae7 h1:dtndE8FcEta75/4kHF3AbpuWzV6f1LjnLrM4pe2SZrw= -golang.org/x/oauth2 v0.0.0-20220808172628-8227340efae7/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094 h1:2o1E+E8TpNLklK9nHiPiK1uzIYrIHt+cQx3ynCwq9V8= golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1480,7 +1319,6 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1502,13 +1340,11 @@ golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1538,24 +1374,19 @@ golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210915083310-ed5796bab164/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220818161305-2296e01440c6 h1:Sx/u41w+OwrInGdEckYmEuU5gHoGSL4QbDz3S9s6j4U= -golang.org/x/sys v0.0.0-20220818161305-2296e01440c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 h1:v6hYoSR9T5oet+pMXwUWkbiVqx/63mlHjefrHmxwfeY= golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= @@ -1578,8 +1409,6 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 h1:ftMN5LMiBFjbzleLqtoBZk7KdJwhuybIU+FckUHgoyQ= golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1641,7 +1470,6 @@ golang.org/x/tools v0.0.0-20200414032229-332987a829c3/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200422022333-3d57cf2e726e/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -1686,7 +1514,6 @@ golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= -golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1766,8 +1593,6 @@ google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201102152239-715cce707fb0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= @@ -1791,10 +1616,7 @@ google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKr google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1 h1:C2UVWqrgLYKrT5nh5oU6hLRm1AeEklCK5eloQA1NtFY= -google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf h1:Q5xNKbTSFwkuaaGaR7CMcXEM5sy19KYdUU8iF8/iRC0= google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= @@ -1826,8 +1648,6 @@ google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQ google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.48.0 h1:rQOsyJ/8+ufEDJd/Gdsz7HG220Mh9HAhFHRGnIjda0w= -google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw= google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= @@ -1860,17 +1680,13 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= @@ -1893,9 +1709,6 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= -gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= -helm.sh/helm/v3 v3.9.3 h1:etd4Qc45/bnIkBofZIRwrAzYuG3bNWR1EdMN4fsfzoE= -helm.sh/helm/v3 v3.9.3/go.mod h1:3eaWAIqzvlRSD06gR9MMwmp2KBKwlu9av1/1BZpjeWY= helm.sh/helm/v3 v3.9.4 h1:TCI1QhJUeLVOdccfdw+vnSEO3Td6gNqibptB04QtExY= helm.sh/helm/v3 v3.9.4/go.mod h1:3eaWAIqzvlRSD06gR9MMwmp2KBKwlu9av1/1BZpjeWY= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1906,58 +1719,27 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.2.1/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY= -k8s.io/api v0.24.4 h1:I5Y645gJ8zWKawyr78lVfDQkZrAViSbeRXsPZWTxmXk= -k8s.io/api v0.24.4/go.mod h1:42pVfA0NRxrtJhZQOvRSyZcJihzAdU59WBtTjYcB0/M= k8s.io/api v0.25.0 h1:H+Q4ma2U/ww0iGB78ijZx6DRByPz6/733jIuFpX70e0= k8s.io/api v0.25.0/go.mod h1:ttceV1GyV1i1rnmvzT3BST08N6nGt+dudGrquzVQWPk= -k8s.io/apiextensions-apiserver v0.24.4 h1:w53Pm4zu8fCt9WfiRgS2YI6LE6I4NJ5aUi78GElD3K8= -k8s.io/apiextensions-apiserver v0.24.4/go.mod h1:iDK+Xb4jsPNnRGj5jU/WqqjLvt8363M7cKixKe1C9+U= k8s.io/apiextensions-apiserver v0.25.0 h1:CJ9zlyXAbq0FIW8CD7HHyozCMBpDSiH7EdrSTCZcZFY= k8s.io/apiextensions-apiserver v0.25.0/go.mod h1:3pAjZiN4zw7R8aZC5gR0y3/vCkGlAjCazcg1me8iB/E= -k8s.io/apimachinery v0.24.4 h1:S0Ur3J/PbivTcL43EdSdPhqCqKla2NIuneNwZcTDeGQ= -k8s.io/apimachinery v0.24.4/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= k8s.io/apimachinery v0.25.0 h1:MlP0r6+3XbkUG2itd6vp3oxbtdQLQI94fD5gCS+gnoU= k8s.io/apimachinery v0.25.0/go.mod h1:qMx9eAk0sZQGsXGu86fab8tZdffHbwUfsvzqKn4mfB0= -k8s.io/apiserver v0.24.4 h1:ei+OunC83pVEiagBeZhTnRZvkclHgpzs/rrm7aSBDYs= -k8s.io/apiserver v0.24.4/go.mod h1:mAuC3pZVc0IDXLx7lUHoisBOtBa1SobfLW/CI3klXQE= k8s.io/apiserver v0.25.0 h1:8kl2ifbNffD440MyvHtPaIz1mw4mGKVgWqM0nL+oyu4= k8s.io/apiserver v0.25.0/go.mod h1:BKwsE+PTC+aZK+6OJQDPr0v6uS91/HWxX7evElAH6xo= -k8s.io/cli-runtime v0.24.4 h1:YCSf0dZp+pYXVR/8aZQ6MEBSiicv8rLyVsGBEbRnwfY= -k8s.io/cli-runtime v0.24.4/go.mod h1:RF+cSLYXkPV3WyvPrX2qeRLEUJY38INWx6jLKVLFCxM= k8s.io/cli-runtime v0.25.0 h1:XBnTc2Fi+w818jcJGzhiJKQuXl8479sZ4FhtV5hVJ1Q= k8s.io/cli-runtime v0.25.0/go.mod h1:bHOI5ZZInRHhbq12OdUiYZQN8ml8aKZLwQgt9QlLINw= -k8s.io/client-go v0.24.4 h1:hIAIJZIPyaw46AkxwyR0FRfM/pRxpUNTd3ysYu9vyRg= -k8s.io/client-go v0.24.4/go.mod h1:+AxlPWw/H6f+EJhRSjIeALaJT4tbeB/8g9BNvXGPd0Y= k8s.io/client-go v0.25.0 h1:CVWIaCETLMBNiTUta3d5nzRbXvY5Hy9Dpl+VvREpu5E= k8s.io/client-go v0.25.0/go.mod h1:lxykvypVfKilxhTklov0wz1FoaUZ8X4EwbhS6rpRfN8= -k8s.io/code-generator v0.24.4/go.mod h1:dpVhs00hTuTdTY6jvVxvTFCk6gSMrtfRydbhZwHI15w= -k8s.io/component-base v0.24.4 h1:WEGRp06GBYVwxp5JdiRaJ1zkdOhrqucxRv/8IrABLG0= -k8s.io/component-base v0.24.4/go.mod h1:sWxkgcMfbYHadw0OJ0N+vIscd14/nqSIM2veCdg843o= k8s.io/component-base v0.25.0 h1:haVKlLkPCFZhkcqB6WCvpVxftrg6+FK5x1ZuaIDaQ5Y= k8s.io/component-base v0.25.0/go.mod h1:F2Sumv9CnbBlqrpdf7rKZTmmd2meJq0HizeyY/yAFxk= -k8s.io/component-helpers v0.24.4/go.mod h1:xAHlOKU8rAjLgXWJEsueWLR1LDMThbaPf2YvgKpSyQ8= -k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/gengo v0.0.0-20211129171323-c02415ce4185/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.60.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/klog/v2 v2.70.1 h1:7aaoSdahviPmR+XkS7FyxlkkXs6tHISSG03RxleQAVQ= k8s.io/klog/v2 v2.70.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= -k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42/go.mod h1:Z/45zLw8lUo4wdiUkI+v/ImEGAvu3WatcZl3lPMR4Rk= k8s.io/kube-openapi v0.0.0-20220803164354-a70c9af30aea h1:3QOH5+2fGsY8e1qf+GIFpg+zw/JGNrgyZRQR7/m6uWg= k8s.io/kube-openapi v0.0.0-20220803164354-a70c9af30aea/go.mod h1:C/N6wCaBHeBHkHUesQOQy2/MZqGgMAFPqGsGQLdbZBU= -k8s.io/kubectl v0.24.4 h1:fPEBkAV3/cu3BQVIUCXNngCCY62AlZ+2rkRVHcmJPn0= -k8s.io/kubectl v0.24.4/go.mod h1:AVyJzxUwA5UMGTDyKGL6nd6RRW36FbmAdtIDMhrZtW0= k8s.io/kubectl v0.25.0 h1:/Wn1cFqo8ik3iee1EvpxYre3bkWsGLXzLQI6uCCAkQc= k8s.io/kubectl v0.25.0/go.mod h1:n16ULWsOl2jmQpzt2o7Dud1t4o0+Y186ICb4O+GwKAU= -k8s.io/metrics v0.24.4/go.mod h1:7D8Xm3DGZoJaiCS8+QA2EzdMuDlq0Y8SiOPUB/1BaGU= -k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20220812165043-ad590609e2e5 h1:XmRqFcQlCy/lKRZ39j+RVpokYNroHPqV3mcBRfnhT5o= -k8s.io/utils v0.0.0-20220812165043-ad590609e2e5/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20220823124924-e9cbc92d1a73 h1:H9TCJUUx+2VA0ZiD9lvtaX8fthFsMoD+Izn93E/hm8U= k8s.io/utils v0.0.0-20220823124924-e9cbc92d1a73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= mvdan.cc/gofumpt v0.1.1/go.mod h1:yXG1r1WqZVKWbVRtBWKWX9+CxGYfA51nSomhM0woR48= @@ -1969,25 +1751,16 @@ oras.land/oras-go v1.2.0/go.mod h1:pFNs7oHp2dYsYMSS82HaX5l4mpnGO7hbpPN6EWH2ltc= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.30/go.mod h1:fEO7lRTdivWO2qYVCVG7dEADOMo/MLDCVr8So2g88Uw= -sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/kind v0.14.0 h1:cNmI3jGBvp7UegEGbC5we8plDtCUmaNRL+bod7JoSCE= sigs.k8s.io/kind v0.14.0/go.mod h1:UrFRPHG+2a5j0Q7qiR4gtJ4rEyn8TuMQwuOPf+m4oHg= -sigs.k8s.io/kustomize/api v0.11.4/go.mod h1:k+8RsqYbgpkIrJ4p9jcdPqe8DprLxFUUO0yNOq8C+xI= sigs.k8s.io/kustomize/api v0.12.1 h1:7YM7gW3kYBwtKvoY216ZzY+8hM+lV53LUayghNRJ0vM= sigs.k8s.io/kustomize/api v0.12.1/go.mod h1:y3JUhimkZkR6sbLNwfJHxvo1TCLwuwm14sCYnkH6S1s= -sigs.k8s.io/kustomize/cmd/config v0.10.6/go.mod h1:/S4A4nUANUa4bZJ/Edt7ZQTyKOY9WCER0uBS1SW2Rco= -sigs.k8s.io/kustomize/kustomize/v4 v4.5.4/go.mod h1:Zo/Xc5FKD6sHl0lilbrieeGeZHVYCA4BzxeAaLI05Bg= -sigs.k8s.io/kustomize/kyaml v0.13.6/go.mod h1:yHP031rn1QX1lr/Xd934Ri/xdVNG8BE2ECa78Ht/kEg= sigs.k8s.io/kustomize/kyaml v0.13.9 h1:Qz53EAaFFANyNgyOEJbT/yoIHygK40/ZcvU3rgry2Tk= sigs.k8s.io/kustomize/kyaml v0.13.9/go.mod h1:QsRbD0/KcU+wdk0/L0fIp2KLnohkVzs6fQ85/nOXac4= -sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= From fc0b6af6e72d705f219538b10e90392458dfd657 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 7 Sep 2022 10:53:05 +0200 Subject: [PATCH 0980/2916] fix: Prefer fixed-images passed via command line over images configured anywhere else --- cmd/kluctl/commands/utils.go | 4 +--- pkg/deployment/images.go | 7 +++++++ pkg/kluctl_project/target_context.go | 4 +--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 3ab20e9be..ed0257f38 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -136,9 +136,7 @@ func withProjectTargetCommandContext(ctx context.Context, args projectTargetComm if err != nil { return err } - for _, fi := range fixedImages { - images.AddFixedImage(fi) - } + images.PrependFixedImages(fixedImages) inclusion, err := args.inclusionFlags.ParseInclusionFromArgs() if err != nil { diff --git a/pkg/deployment/images.go b/pkg/deployment/images.go index f7ef098e7..8dc9c5f77 100644 --- a/pkg/deployment/images.go +++ b/pkg/deployment/images.go @@ -41,6 +41,13 @@ func (images *Images) AddFixedImage(fi types.FixedImage) { images.fixedImages = append(images.fixedImages, fi) } +func (images *Images) PrependFixedImages(fis []types.FixedImage) { + var newFixedImages []types.FixedImage + newFixedImages = append(newFixedImages, fis...) + newFixedImages = append(newFixedImages, images.fixedImages...) + images.fixedImages = newFixedImages +} + func (images *Images) SeenImages(simple bool) []types.FixedImage { var ret []types.FixedImage for _, fi := range images.seenImages { diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index b5ed711fd..47d187a36 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -39,9 +39,7 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName s } target = t.Target - for _, fi := range target.Images { - images.AddFixedImage(fi) - } + images.PrependFixedImages(target.Images) } varsCtx, clientConfig, clusterContext, err := p.buildVars(target, clusterName, args, forSeal) From 29ad3cfb633613fb36a299b4d444f69162824f5b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 8 Sep 2022 13:24:39 +0200 Subject: [PATCH 0981/2916] fix: Don't write addresses into known_hosts to avoid issues with IPV6 parsing --- pkg/git/auth/goph/hosts.go | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/pkg/git/auth/goph/hosts.go b/pkg/git/auth/goph/hosts.go index e2d9bec1e..10b8cdeb3 100644 --- a/pkg/git/auth/goph/hosts.go +++ b/pkg/git/auth/goph/hosts.go @@ -96,14 +96,7 @@ func AddKnownHost(host string, remote net.Addr, key ssh.PublicKey, knownFile str defer f.Close() - remoteNormalized := knownhosts.Normalize(remote.String()) - hostNormalized := knownhosts.Normalize(host) - addresses := []string{remoteNormalized} - - if hostNormalized != remoteNormalized { - addresses = append(addresses, hostNormalized) - } - + addresses := []string{host} _, err = f.WriteString(knownhosts.Line(addresses, key) + "\n") return err From 2889a9f2af1660edcc84c5dbb501dff0eb325ae3 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 8 Sep 2022 22:29:09 +0200 Subject: [PATCH 0982/2916] fix: Stop using mpb and instead rely on uilive --- go.mod | 4 +- go.sum | 8 +- pkg/status/multiline/multiline.go | 133 ++++++++++++++++++++++++++++++ pkg/status/multiline/spinner.go | 13 +++ pkg/status/status_handler.go | 102 ++++++++++++----------- pkg/status/utils.go | 2 +- 6 files changed, 203 insertions(+), 59 deletions(-) create mode 100644 pkg/status/multiline/multiline.go create mode 100644 pkg/status/multiline/spinner.go diff --git a/go.mod b/go.mod index eb85f2f1c..a17436d62 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/gobwas/glob v0.2.3 github.com/golang-jwt/jwt/v4 v4.4.2 github.com/google/go-containerregistry v0.11.0 + github.com/gosuri/uilive v0.0.4 github.com/hashicorp/vault/api v1.7.2 github.com/hexops/gotextdiff v1.0.3 github.com/imdario/mergo v0.3.13 @@ -34,7 +35,6 @@ require ( github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.12.0 github.com/stretchr/testify v1.8.0 - github.com/vbauerster/mpb/v7 v7.5.2 github.com/whilp/git-urls v1.0.0 github.com/xanzy/ssh-agent v0.3.2 golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 @@ -73,8 +73,6 @@ require ( github.com/Masterminds/squirrel v1.5.3 // indirect github.com/Microsoft/go-winio v0.5.2 // indirect github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895 // indirect - github.com/VividCortex/ewma v1.2.0 // indirect - github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect github.com/acomagu/bufpipe v1.0.3 // indirect github.com/alessio/shellescape v1.4.1 // indirect github.com/armon/go-metrics v0.4.0 // indirect diff --git a/go.sum b/go.sum index d09bfb382..a4812274e 100644 --- a/go.sum +++ b/go.sum @@ -112,10 +112,6 @@ github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895 h1:NsReiLpErI github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= -github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= -github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= -github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= -github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -518,6 +514,8 @@ github.com/gostaticanalysis/forcetypeassert v0.0.0-20200621232751-01d4955beaa5/g github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW0HU0GPE3+5PWN4A= github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= github.com/gostaticanalysis/testutil v0.4.0/go.mod h1:bLIoPefWXrRi/ssLFWX1dx7Repi5x3CuviD3dgAZaBU= +github.com/gosuri/uilive v0.0.4 h1:hUEBpQDj8D8jXgtCdBu7sWsy5sbW/5GhuO8KBwJ2jyY= +github.com/gosuri/uilive v0.0.4/go.mod h1:V/epo5LjjlDE5RJUcqx8dbw+zc93y5Ya3yg8tfZ74VI= github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= @@ -1070,8 +1068,6 @@ github.com/valyala/fasthttp v1.30.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD github.com/valyala/quicktemplate v1.7.0/go.mod h1:sqKJnoaOF88V07vkO+9FL8fb9uZg/VPSJnLYn+LmLk8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/vbatts/tar-split v0.11.2 h1:Via6XqJr0hceW4wff3QRzD5gAk/tatMw/4ZA7cTlIME= -github.com/vbauerster/mpb/v7 v7.5.2 h1:Ph3JvpBcoIwzIG1QwbUq97KQifrTRbKcMXN9rN5BYAs= -github.com/vbauerster/mpb/v7 v7.5.2/go.mod h1:UmOiIUI8aPqWXIps0ciik3RKMdzx7+ooQpq+fBcXwBA= github.com/viki-org/dnscache v0.0.0-20130720023526-c70c1f23c5d8/go.mod h1:dniwbG03GafCjFohMDmz6Zc6oCuiqgH6tGNyXTkHzXE= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= diff --git a/pkg/status/multiline/multiline.go b/pkg/status/multiline/multiline.go new file mode 100644 index 000000000..f2a67889e --- /dev/null +++ b/pkg/status/multiline/multiline.go @@ -0,0 +1,133 @@ +package multiline + +import ( + "bytes" + "fmt" + "github.com/gosuri/uilive" + "io" + "sync" + "time" +) + +type MultiLinePrinter struct { + topLines []*Line + lines []*Line + + uil *uilive.Writer + lastWritten []byte + + ticker *time.Ticker + tdone chan bool + + mutex sync.Mutex +} + +type LineFunc func() string + +type Line struct { + ml *MultiLinePrinter + s LineFunc +} + +func (ml *MultiLinePrinter) Start(w io.Writer) { + ml.uil = uilive.New() + ml.uil.Out = w + ml.uil.Start() + + ml.ticker = time.NewTicker(100 * time.Millisecond) + ml.tdone = make(chan bool) + + go ml.loop() +} + +func (ml *MultiLinePrinter) Stop() { + ml.Flush() + ml.tdone <- true + <-ml.tdone + ml.uil.Stop() +} + +func (ml *MultiLinePrinter) loop() { + for { + select { + case <-ml.ticker.C: + if ml.ticker != nil { + ml.Flush() + } + case <-ml.tdone: + ml.mutex.Lock() + ml.ticker.Stop() + ml.ticker = nil + ml.mutex.Unlock() + close(ml.tdone) + return + } + } +} + +func (ml *MultiLinePrinter) Flush() { + ml.mutex.Lock() + defer ml.mutex.Unlock() + + hadTopLines := false + if len(ml.topLines) != 0 { + hadTopLines = true + topBuf := bytes.NewBuffer(nil) + for _, l := range ml.topLines { + _, _ = fmt.Fprintf(topBuf, "%s\n", l.s()) + } + + _, _ = ml.uil.Bypass().Write(topBuf.Bytes()) + ml.topLines = nil + } + + linesBuf := bytes.NewBuffer(nil) + for _, l := range ml.lines { + _, _ = fmt.Fprintf(linesBuf, "%s\n", l.s()) + } + newBytes := linesBuf.Bytes() + if hadTopLines || !bytes.Equal(newBytes, ml.lastWritten) { + _, _ = ml.uil.Write(newBytes) + ml.lastWritten = newBytes + } +} + +func (ml *MultiLinePrinter) NewTopLine(s LineFunc) { + ml.mutex.Lock() + defer ml.mutex.Unlock() + + l := &Line{ + ml: ml, + s: s, + } + ml.topLines = append(ml.topLines, l) +} + +func (ml *MultiLinePrinter) NewLine(s LineFunc) *Line { + ml.mutex.Lock() + defer ml.mutex.Unlock() + + l := &Line{ + ml: ml, + s: s, + } + ml.lines = append(ml.lines, l) + + return l +} + +func (l *Line) Remove(pushToTop bool) { + l.ml.mutex.Lock() + defer l.ml.mutex.Unlock() + + for i, l2 := range l.ml.lines { + if l2 == l { + l.ml.lines = append(l.ml.lines[:i], l.ml.lines[i+1:]...) + + if pushToTop { + l.ml.topLines = append(l.ml.topLines, l) + } + break + } + } +} diff --git a/pkg/status/multiline/spinner.go b/pkg/status/multiline/spinner.go new file mode 100644 index 000000000..b686973ab --- /dev/null +++ b/pkg/status/multiline/spinner.go @@ -0,0 +1,13 @@ +package multiline + +import ( + "time" +) + +var spinner = []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"} + +func Spinner() string { + factor := int64(100) + frame := int((time.Now().UnixMilli() / factor) % int64(len(spinner))) + return spinner[frame] +} diff --git a/pkg/status/status_handler.go b/pkg/status/status_handler.go index 7b5a85a82..34f2bd717 100644 --- a/pkg/status/status_handler.go +++ b/pkg/status/status_handler.go @@ -2,13 +2,12 @@ package status import ( "context" - "github.com/kluctl/kluctl/v2/pkg/utils" + "fmt" + "github.com/kluctl/kluctl/v2/pkg/status/multiline" "github.com/kluctl/kluctl/v2/pkg/utils/term" - "github.com/vbauerster/mpb/v7" - "github.com/vbauerster/mpb/v7/decor" "io" - "math" "strings" + "sync" "syscall" ) @@ -16,18 +15,22 @@ type MultiLineStatusHandler struct { ctx context.Context out io.Writer isTerminal bool - progress *mpb.Progress trace bool + + ml *multiline.MultiLinePrinter } type statusLine struct { - slh *MultiLineStatusHandler + slh *MultiLineStatusHandler + l *multiline.Line + + current int total int - bar *mpb.Bar - filler mpb.BarFiller message string barOverride string + + mutex sync.Mutex } func NewMultiLineStatusHandler(ctx context.Context, out io.Writer, isTerminal bool, trace bool) *MultiLineStatusHandler { @@ -52,8 +55,7 @@ func (s *MultiLineStatusHandler) IsTraceEnabled() bool { } func (s *MultiLineStatusHandler) Flush() { - s.Stop() - s.start() + s.ml.Flush() } func (s *MultiLineStatusHandler) SetTrace(trace bool) { @@ -61,60 +63,43 @@ func (s *MultiLineStatusHandler) SetTrace(trace bool) { } func (s *MultiLineStatusHandler) start() { - s.progress = mpb.NewWithContext( - s.ctx, - mpb.WithWidth(utils.GetTermWidth()), - mpb.WithOutput(s.out), - mpb.PopCompletedMode(), - ) + s.ml = &multiline.MultiLinePrinter{} + s.ml.Start(s.out) } func (s *MultiLineStatusHandler) Stop() { - s.progress.Wait() + s.ml.Stop() } func (s *MultiLineStatusHandler) StartStatus(total int, message string) StatusLine { - return s.startStatus(total, message, 0, "") + return s.startStatus(total, message, "") } -func (s *MultiLineStatusHandler) startStatus(total int, message string, priority int, barOverride string) *statusLine { +func (s *MultiLineStatusHandler) startStatus(total int, message string, barOverride string) *statusLine { sl := &statusLine{ slh: s, total: total, message: message, barOverride: barOverride, } - sl.filler = mpb.SpinnerStyle().PositionLeft().Build() - opts := []mpb.BarOption{ - mpb.BarWidth(1), - mpb.AppendDecorators(decor.Any(sl.DecorMessage, decor.WCSyncWidthR)), - } - if priority != 0 { - opts = append(opts, mpb.BarPriority(priority)) - } - - sl.bar = s.progress.Add(int64(total), sl, opts...) + sl.l = sl.slh.ml.NewLine(sl.lineFunc) return sl } -func (sl *statusLine) DecorMessage(s decor.Statistics) string { - return sl.message -} - -func (sl *statusLine) Fill(w io.Writer, reqWidth int, stat decor.Statistics) { - if sl.barOverride != "" { - _, _ = io.WriteString(w, sl.barOverride) - return +func (s *MultiLineStatusHandler) printLine(message string, barOverride string, doFlush bool) { + s.ml.NewTopLine(func() string { + return fmt.Sprintf("%s %s", barOverride, message) + }) + if doFlush { + s.Flush() } - - sl.filler.Fill(w, reqWidth, stat) } func (s *MultiLineStatusHandler) Info(message string) { o := withColor("green", "ⓘ") - s.startStatus(1, message, math.MinInt, o).end(o) + s.printLine(message, o, true) } func (s *MultiLineStatusHandler) InfoFallback(message string) { @@ -123,12 +108,12 @@ func (s *MultiLineStatusHandler) InfoFallback(message string) { func (s *MultiLineStatusHandler) Warning(message string) { o := withColor("yellow", "⚠") - s.startStatus(1, message, math.MinInt, o).end(o) + s.printLine(message, o, true) } func (s *MultiLineStatusHandler) Error(message string) { o := withColor("red", "✗") - s.startStatus(1, message, math.MinInt, o).end(o) + s.printLine(message, o, true) } func (s *MultiLineStatusHandler) Trace(message string) { @@ -143,7 +128,7 @@ func (s *MultiLineStatusHandler) PlainText(text string) { func (s *MultiLineStatusHandler) Prompt(password bool, message string) (string, error) { o := withColor("yellow", "?") - sl := s.startStatus(1, message, math.MinInt, o) + sl := s.startStatus(1, message, o) defer sl.end(o) doUpdate := func(ret []byte) { @@ -159,28 +144,47 @@ func (s *MultiLineStatusHandler) Prompt(password bool, message string) (string, return string(ret), err } +func (sl *statusLine) lineFunc() string { + sl.mutex.Lock() + defer sl.mutex.Unlock() + + if sl.barOverride != "" { + return fmt.Sprintf("%s %s", sl.barOverride, sl.message) + } + + s := multiline.Spinner() + return fmt.Sprintf("%s %s", s, sl.message) +} + func (sl *statusLine) SetTotal(total int) { + sl.mutex.Lock() + defer sl.mutex.Unlock() sl.total = total - sl.bar.SetTotal(int64(total), false) - sl.bar.EnableTriggerComplete() } func (sl *statusLine) Increment() { - sl.bar.Increment() + sl.mutex.Lock() + defer sl.mutex.Unlock() + if sl.current < sl.total { + sl.current++ + } } func (sl *statusLine) Update(message string) { + sl.mutex.Lock() + defer sl.mutex.Unlock() sl.message = message } func (sl *statusLine) end(barOverride string) { sl.barOverride = barOverride - // make sure that the bar es rendered on top so that it can be properly popped - sl.bar.SetPriority(math.MinInt) - sl.bar.SetCurrent(int64(sl.total)) + sl.current = sl.total + sl.l.Remove(true) } func (sl *statusLine) End(result EndResult) { + sl.mutex.Lock() + defer sl.mutex.Unlock() switch result { case EndSuccess: sl.end(withColor("green", "✓")) diff --git a/pkg/status/utils.go b/pkg/status/utils.go index 0a2edfc82..21555d432 100644 --- a/pkg/status/utils.go +++ b/pkg/status/utils.go @@ -31,7 +31,7 @@ type replaceRReader struct { func (r *replaceRReader) Read(p []byte) (int, error) { written := 0 - for true { + for written < len(p) { b, err := r.reader.ReadByte() if err != nil { if err == io.EOF { From 1189a4598dbe2bd3e40ee74ed586109e63595b0e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 7 Sep 2022 15:23:38 +0200 Subject: [PATCH 0983/2916] feat: Switch to go-jinja2 library --- cmd/kluctl/commands/utils.go | 6 +- go.mod | 5 +- go.sum | 5 +- pkg/deployment/deployment_item.go | 9 +- pkg/deployment/deployment_project.go | 3 +- pkg/jinja2/.gitignore | 2 - pkg/jinja2/jinja2.go | 255 ----------------------- pkg/jinja2/jinja2_renderer.go | 213 ------------------- pkg/jinja2/python_src/.gitignore | 4 - pkg/jinja2/python_src/.python-version | 1 - pkg/jinja2/python_src/dict_utils.py | 64 ------ pkg/jinja2/python_src/jinja2_renderer.py | 122 ----------- pkg/jinja2/python_src/jinja2_utils.py | 140 ------------- pkg/jinja2/python_src/jsonpath_utils.py | 100 --------- pkg/jinja2/python_src/main.py | 34 --- pkg/jinja2/python_src/yaml_utils.py | 51 ----- pkg/jinja2/render_struct.go | 181 ---------------- pkg/jinja2/source.go | 93 --------- pkg/kluctl_jinja2/embed.go | 8 + pkg/kluctl_jinja2/ext/__init__.py | 3 + pkg/kluctl_jinja2/ext/images_ext.py | 53 +++++ pkg/kluctl_jinja2/jinja2.go | 23 ++ pkg/kluctl_project/load.go | 2 +- pkg/kluctl_project/project.go | 2 +- pkg/kluctl_project/targets.go | 4 +- pkg/utils/uo/uo.go | 16 ++ pkg/utils/utils.go | 3 +- pkg/vars/vars.go | 37 +++- pkg/vars/vars_loader.go | 10 +- pkg/vars/vars_test.go | 5 +- 30 files changed, 168 insertions(+), 1286 deletions(-) delete mode 100644 pkg/jinja2/.gitignore delete mode 100644 pkg/jinja2/jinja2.go delete mode 100644 pkg/jinja2/jinja2_renderer.go delete mode 100644 pkg/jinja2/python_src/.gitignore delete mode 100644 pkg/jinja2/python_src/.python-version delete mode 100644 pkg/jinja2/python_src/dict_utils.py delete mode 100644 pkg/jinja2/python_src/jinja2_renderer.py delete mode 100644 pkg/jinja2/python_src/jinja2_utils.py delete mode 100644 pkg/jinja2/python_src/jsonpath_utils.py delete mode 100644 pkg/jinja2/python_src/main.py delete mode 100644 pkg/jinja2/python_src/yaml_utils.py delete mode 100644 pkg/jinja2/render_struct.go delete mode 100644 pkg/jinja2/source.go create mode 100644 pkg/kluctl_jinja2/embed.go create mode 100644 pkg/kluctl_jinja2/ext/__init__.py create mode 100644 pkg/kluctl_jinja2/ext/images_ext.py create mode 100644 pkg/kluctl_jinja2/jinja2.go diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index ed0257f38..9b50739d4 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -10,7 +10,7 @@ import ( git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/git/repocache" ssh_pool "github.com/kluctl/kluctl/v2/pkg/git/ssh-pool" - "github.com/kluctl/kluctl/v2/pkg/jinja2" + "github.com/kluctl/kluctl/v2/pkg/kluctl_jinja2" "github.com/kluctl/kluctl/v2/pkg/kluctl_project" "github.com/kluctl/kluctl/v2/pkg/registries" "github.com/kluctl/kluctl/v2/pkg/status" @@ -39,14 +39,12 @@ func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, strictTemplates b } defer os.RemoveAll(tmpDir) - j2, err := jinja2.NewJinja2() + j2, err := kluctl_jinja2.NewKluctlJinja2(strictTemplates) if err != nil { return err } defer j2.Close() - j2.SetStrict(strictTemplates) - cwd, err := os.Getwd() if err != nil { return err diff --git a/go.mod b/go.mod index a17436d62..93aaf886c 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/fluxcd/pkg/kustomize v0.7.0 github.com/go-git/go-git/v5 v5.4.2 github.com/go-playground/validator/v10 v10.11.0 - github.com/gobwas/glob v0.2.3 + github.com/gobwas/glob v0.2.3 // indirect github.com/golang-jwt/jwt/v4 v4.4.2 github.com/google/go-containerregistry v0.11.0 github.com/gosuri/uilive v0.0.4 @@ -22,7 +22,8 @@ require ( github.com/imdario/mergo v0.3.13 github.com/jinzhu/copier v0.3.5 github.com/kevinburke/ssh_config v1.2.0 - github.com/kluctl/kluctl-python-deps v0.0.0-20220819072433-e8107cc5ae41 + github.com/kluctl/go-embed-python v0.0.0-3.10.6-20220906-2 + github.com/kluctl/go-jinja2 v0.0.0-20220908201753-f33cd6224f2b github.com/mattn/go-isatty v0.0.16 github.com/mitchellh/go-ps v1.0.0 github.com/mitchellh/reflectwalk v1.0.2 diff --git a/go.sum b/go.sum index a4812274e..d4cf7fb99 100644 --- a/go.sum +++ b/go.sum @@ -668,8 +668,8 @@ github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8 github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= -github.com/kluctl/kluctl-python-deps v0.0.0-20220819072433-e8107cc5ae41 h1:sMQz3y4XtYS4YjAil3B8pxuhjPEObt0xMPMwmPZC9O0= -github.com/kluctl/kluctl-python-deps v0.0.0-20220819072433-e8107cc5ae41/go.mod h1:w4cTz1av5JFX9bMTB7Vm2qtmm5+eU+ncvB1/oyjVwOw= +github.com/kluctl/go-embed-python v0.0.0-3.10.6-20220906-2 h1:zjJAVyZiWJhufcafhykvXNar1vqDp/ukQk0hFuCJ9zw= +github.com/kluctl/go-embed-python v0.0.0-3.10.6-20220906-2/go.mod h1:uekQDAlvKAYJWuDxxP1SGn1vTPUfoBwMS+aB6kR+v+o= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -1380,7 +1380,6 @@ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 h1:v6hYoSR9T5oet+pMXwUWkbiVqx/63mlHjefrHmxwfeY= diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 959830915..e97bb48d2 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -128,7 +128,7 @@ func (di *DeploymentItem) render(forSeal bool) error { } if yaml.Exists(filepath.Join(p, "helm-chart.yml")) { // never try to render helm charts - ep := filepath.Join(di.RelToProjectItemDir, relDir, "charts/**") + ep := filepath.Join(relDir, "charts/**") ep = filepath.ToSlash(ep) excludePatterns = append(excludePatterns, ep) return filepath.SkipDir @@ -145,7 +145,12 @@ func (di *DeploymentItem) render(forSeal bool) error { excludePatterns = append(excludePatterns, "**.sealme") } - return varsCtx.RenderDirectory(di.Project.source.dir, di.Project.getRenderSearchDirs(), di.Project.relDir, excludePatterns, di.RelToProjectItemDir, di.RenderedDir) + return varsCtx.RenderDirectory( + filepath.Join(di.Project.source.dir, di.RelToSourceItemDir), + di.RenderedDir, + excludePatterns, + di.Project.getRenderSearchDirs(), + ) } func (di *DeploymentItem) renderHelmCharts() error { diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go index a11c1e919..ecd599c31 100644 --- a/pkg/deployment/deployment_project.go +++ b/pkg/deployment/deployment_project.go @@ -75,8 +75,9 @@ func (p *DeploymentProject) loadConfig() error { } return fmt.Errorf("%s not found", configPath) } + configPath = yaml.FixPathExt(configPath) - err := p.VarsCtx.RenderYamlFile(yaml.FixNameExt(p.absDir, "deployment.yml"), p.getRenderSearchDirs(), &p.Config) + err := p.VarsCtx.RenderYamlFile(configPath, p.getRenderSearchDirs(), &p.Config) if err != nil { return fmt.Errorf("failed to load deployment.yml: %w", err) } diff --git a/pkg/jinja2/.gitignore b/pkg/jinja2/.gitignore deleted file mode 100644 index c23b5c5b7..000000000 --- a/pkg/jinja2/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.tar.gz -*.files \ No newline at end of file diff --git a/pkg/jinja2/jinja2.go b/pkg/jinja2/jinja2.go deleted file mode 100644 index 62df03c34..000000000 --- a/pkg/jinja2/jinja2.go +++ /dev/null @@ -1,255 +0,0 @@ -package jinja2 - -import ( - "fmt" - securejoin "github.com/cyphar/filepath-securejoin" - "github.com/gobwas/glob" - "github.com/kluctl/kluctl/v2/pkg/utils" - "github.com/kluctl/kluctl/v2/pkg/utils/uo" - "io/fs" - "io/ioutil" - "os" - "path/filepath" - "strings" - "sync" -) - -var paralellism = 4 - -type Jinja2 struct { - pj chan *pythonJinja2Renderer - strict bool - globCache map[string]interface{} - mutex sync.Mutex -} - -type RenderJob struct { - Template string - Result *string - Error error - - target string -} - -type Jinja2Error struct { - error string -} - -type Jinja2Opt func(j2 *pythonJinja2Renderer) - -func WithTrimBlocks(trimBlocks bool) Jinja2Opt { - return func(j2 *pythonJinja2Renderer) { - j2.trimBlocks = trimBlocks - } -} - -func WithLStripBlocks(lstripBlocks bool) Jinja2Opt { - return func(j2 *pythonJinja2Renderer) { - j2.lstripBlocks = lstripBlocks - } -} - -func (m *Jinja2Error) Error() string { - return m.error -} - -func NewJinja2(opts ...Jinja2Opt) (*Jinja2, error) { - var wg sync.WaitGroup - var mutex sync.Mutex - var err error - - j := &Jinja2{ - pj: make(chan *pythonJinja2Renderer, paralellism), - strict: true, - globCache: map[string]interface{}{}, - } - - for i := 0; i < paralellism; i++ { - wg.Add(1) - go func() { - defer wg.Done() - pj, err2 := newPythonJinja2Renderer() - if err2 != nil { - mutex.Lock() - defer mutex.Unlock() - err = err2 - return - } - for _, o := range opts { - o(pj) - } - j.pj <- pj - }() - } - wg.Wait() - if err != nil { - return nil, err - } - - return j, nil -} - -func (j *Jinja2) Close() { - for i := 0; i < paralellism; i++ { - pj := <-j.pj - pj.Close() - } -} - -func (j *Jinja2) SetStrict(strict bool) { - j.strict = strict -} - -func (j *Jinja2) RenderStrings(jobs []*RenderJob, searchDirs []string, vars *uo.UnstructuredObject) error { - pj := <-j.pj - defer func() { j.pj <- pj }() - return pj.renderHelper(jobs, searchDirs, vars, true, j.strict) -} - -func (j *Jinja2) RenderFiles(jobs []*RenderJob, searchDirs []string, vars *uo.UnstructuredObject) error { - pj := <-j.pj - defer func() { j.pj <- pj }() - return pj.renderHelper(jobs, searchDirs, vars, false, j.strict) -} - -func (j *Jinja2) RenderString(template string, searchDirs []string, vars *uo.UnstructuredObject) (string, error) { - jobs := []*RenderJob{{ - Template: template, - }} - err := j.RenderStrings(jobs, searchDirs, vars) - if err != nil { - return "", err - } - if jobs[0].Error != nil { - return "", jobs[0].Error - } - return *jobs[0].Result, nil -} - -func (j *Jinja2) RenderFile(template string, searchDirs []string, vars *uo.UnstructuredObject) (string, error) { - jobs := []*RenderJob{{ - Template: template, - }} - err := j.RenderFiles(jobs, searchDirs, vars) - if err != nil { - return "", err - } - if jobs[0].Error != nil { - return "", jobs[0].Error - } - return *jobs[0].Result, nil -} - -func (j *Jinja2) getGlob(pattern string) (glob.Glob, error) { - j.mutex.Lock() - defer j.mutex.Unlock() - - g, ok := j.globCache[pattern] - if ok { - if g2, ok := g.(glob.Glob); ok { - return g2, nil - } else { - return nil, g2.(error) - } - } - g, err := glob.Compile(pattern, '/') - if err != nil { - j.globCache[pattern] = err - return nil, err - } - j.globCache[pattern] = g - return g.(glob.Glob), nil -} -func (j *Jinja2) needsRender(path string, excludedPatterns []string) bool { - path = filepath.ToSlash(path) - - for _, p := range excludedPatterns { - g, err := j.getGlob(p) - if err != nil { - return false - } - if g.Match(path) { - return false - } - } - return true -} - -func (j *Jinja2) RenderDirectory(rootDir string, searchDirs []string, relSourceDir string, excludePatterns []string, subdir string, targetDir string, vars *uo.UnstructuredObject) error { - searchDirs = append([]string{rootDir}, searchDirs...) - - walkDir, err := securejoin.SecureJoin(rootDir, filepath.Join(relSourceDir, subdir)) - if err != nil { - return err - } - - var jobs []*RenderJob - - err = filepath.WalkDir(walkDir, func(p string, d fs.DirEntry, err error) error { - relPath, err := filepath.Rel(walkDir, p) - if err != nil { - return err - } - if d.IsDir() { - err = os.MkdirAll(filepath.Join(targetDir, relPath), 0o700) - if err != nil { - return err - } - return nil - } - - sourcePath := filepath.Clean(filepath.Join(subdir, relPath)) - targetPath := filepath.Join(targetDir, relPath) - - if !j.needsRender(sourcePath, excludePatterns) { - return utils.CopyFile(p, targetPath) - } - - found := false - for _, searchDir := range searchDirs { - if utils.Exists(filepath.Join(searchDir, sourcePath)) { - sourcePath = filepath.Clean(filepath.Join(searchDir, sourcePath)) - sourcePath, err = filepath.Rel(rootDir, sourcePath) - if err != nil { - return err - } - found = true - } - } - if !found { - return fmt.Errorf("did not find %s in searchDirs", relPath) - } - - // jinja2 templates are using / even on Windows - sourcePath = strings.ReplaceAll(sourcePath, "\\", "/") - - job := &RenderJob{ - Template: sourcePath, - target: targetPath, - } - jobs = append(jobs, job) - return nil - }) - if err != nil { - return err - } - - err = j.RenderFiles(jobs, searchDirs, vars) - if err != nil { - return err - } - - var errors []error - for _, job := range jobs { - if job.Error != nil { - errors = append(errors, job.Error) - continue - } - - err = ioutil.WriteFile(job.target, []byte(*job.Result), 0o600) - if err != nil { - return err - } - } - return utils.NewErrorListOrNil(errors) -} diff --git a/pkg/jinja2/jinja2_renderer.go b/pkg/jinja2/jinja2_renderer.go deleted file mode 100644 index bcbd36edb..000000000 --- a/pkg/jinja2/jinja2_renderer.go +++ /dev/null @@ -1,213 +0,0 @@ -package jinja2 - -import ( - "bufio" - "bytes" - "encoding/json" - "fmt" - "github.com/kluctl/kluctl-python-deps/pkg/python" - "github.com/kluctl/kluctl/v2/pkg/utils/uo" - "github.com/kluctl/kluctl/v2/pkg/yaml" - "io" - "io/ioutil" - "os" - "os/exec" - "path/filepath" - "strings" - "time" -) - -type pythonJinja2Renderer struct { - cmd *exec.Cmd - stdin io.WriteCloser - stdout io.ReadCloser - - stdoutReader *bufio.Reader - - trimBlocks bool - lstripBlocks bool -} - -func newPythonJinja2Renderer() (*pythonJinja2Renderer, error) { - isOk := false - j := &pythonJinja2Renderer{} - defer func() { - if !isOk { - j.Close() - } - }() - - args := []string{filepath.Join(pythonSrcExtracted, "main.py")} - j.cmd = python.PythonCmd(args) - j.cmd.Stderr = os.Stderr - j.cmd.Env = append(j.cmd.Env, fmt.Sprintf("PYTHONPATH=%s/wheel", pythonSrcExtracted)) - - stdout, err := j.cmd.StdoutPipe() - if err != nil { - return nil, err - } - j.stdout = stdout - - stdin, err := j.cmd.StdinPipe() - if err != nil { - return nil, err - } - j.stdin = stdin - - err = j.cmd.Start() - if err != nil { - return nil, err - } - - j.stdoutReader = bufio.NewReader(j.stdout) - - isOk = true - - return j, nil -} - -func (j *pythonJinja2Renderer) Close() { - if j.stdin != nil { - args := jinja2Args{Cmd: "exit"} - _ = json.NewEncoder(j.stdin).Encode(args) - - _ = j.stdin.Close() - j.stdin = nil - } - if j.stdout != nil { - _ = j.stdout.Close() - j.stdout = nil - } - if j.cmd != nil { - if j.cmd.Process != nil { - timer := time.AfterFunc(5*time.Second, func() { - _ = j.cmd.Process.Kill() - }) - _ = j.cmd.Wait() - timer.Stop() - } - j.cmd = nil - } -} - -func isMaybeTemplateString(template string) bool { - return strings.IndexRune(template, '{') != -1 -} - -func isMaybeTemplateBytes(template []byte) bool { - return bytes.IndexRune(template, '{') != -1 -} - -func isMaybeTemplate(template string, searchDirs []string, isString bool) (bool, *string) { - if isString { - if !isMaybeTemplateString(template) { - return false, &template - } - } else { - for _, s := range searchDirs { - b, err := ioutil.ReadFile(filepath.Join(s, template)) - if err != nil { - continue - } - if !isMaybeTemplateBytes(b) { - x := string(b) - return false, &x - } else { - return true, nil - } - } - } - return true, nil -} - -type jinja2Args struct { - Cmd string `json:"cmd"` - Templates []string `json:"templates"` - SearchDirs []string `json:"searchDirs"` - Vars string `json:"vars"` - Strict bool `json:"strict"` - - TrimBlocks bool `json:"trimBlocks"` - LStripBlocks bool `json:"lstripBlocks"` -} - -type jinja2Result struct { - Result *string `json:"result,omitempty"` - Error *string `json:"error,omitempty"` -} - -func (j *pythonJinja2Renderer) renderHelper(jobs []*RenderJob, searchDirs []string, vars *uo.UnstructuredObject, isString bool, strict bool) error { - varsStr, err := yaml.WriteJsonString(vars) - if err != nil { - return err - } - - var processedJobs []*RenderJob - - var jargs jinja2Args - if isString { - jargs.Cmd = "render-strings" - } else { - jargs.Cmd = "render-files" - } - jargs.Vars = string(varsStr) - jargs.SearchDirs = searchDirs - jargs.Strict = strict - jargs.TrimBlocks = j.trimBlocks - jargs.LStripBlocks = j.lstripBlocks - - for _, job := range jobs { - if ist, r := isMaybeTemplate(job.Template, searchDirs, isString); !ist { - job.Result = r - continue - } - processedJobs = append(processedJobs, job) - jargs.Templates = append(jargs.Templates, job.Template) - } - if len(processedJobs) == 0 { - return nil - } - - b, err := json.Marshal(jargs) - if err != nil { - j.Close() - return err - } - b = append(b, '\n') - - _, err = j.stdin.Write(b) - if err != nil { - j.Close() - return err - } - - line := bytes.NewBuffer(nil) - for true { - l, p, err := j.stdoutReader.ReadLine() - if err != nil { - return err - } - line.Write(l) - if !p { - break - } - } - var result []jinja2Result - err = json.Unmarshal(line.Bytes(), &result) - if err != nil { - return err - } - - for i, item := range result { - if item.Result != nil { - processedJobs[i].Result = item.Result - } else { - if item.Error == nil { - return fmt.Errorf("missing result and error from item at index %d", i) - } - processedJobs[i].Error = &Jinja2Error{*item.Error} - } - } - - return nil -} diff --git a/pkg/jinja2/python_src/.gitignore b/pkg/jinja2/python_src/.gitignore deleted file mode 100644 index c4095b70b..000000000 --- a/pkg/jinja2/python_src/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -__pycache__ - -/venv -/wheel diff --git a/pkg/jinja2/python_src/.python-version b/pkg/jinja2/python_src/.python-version deleted file mode 100644 index 30291cba2..000000000 --- a/pkg/jinja2/python_src/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.10.0 diff --git a/pkg/jinja2/python_src/dict_utils.py b/pkg/jinja2/python_src/dict_utils.py deleted file mode 100644 index f076faeea..000000000 --- a/pkg/jinja2/python_src/dict_utils.py +++ /dev/null @@ -1,64 +0,0 @@ -from jsonpath_utils import parse_json_path - - -def copy_primitive_value(v): - if isinstance(v, dict): - return copy_dict(v) - if is_iterable(v, False): - return [copy_primitive_value(x) for x in v] - return v - -def copy_dict(a): - ret = {} - for k, v in a.items(): - ret[k] = copy_primitive_value(v) - return ret - -def merge_dict(a, b, clone=True): - if clone: - a = copy_dict(a) - if a is None: - a = {} - if b is None: - b = {} - for key in b: - if key in a: - if isinstance(a[key], dict) and isinstance(b[key], dict): - merge_dict(a[key], b[key], clone=False) - else: - a[key] = b[key] - else: - a[key] = b[key] - return a - - -def get_dict_value(y, path, default=None): - p = parse_json_path(path) - f = p.find(y) - if len(f) > 1: - raise Exception("Only simple jsonpath supported in get_dict_value") - if len(f) == 0: - return default - return f[0].value - -def is_iterable(obj, str_and_bytes=True): - if isinstance(obj, list): - return True - if isinstance(obj, tuple): - return True - if isinstance(obj, dict): - return True - if isinstance(obj, str): - return str_and_bytes - if isinstance(obj, bytes): - return str_and_bytes - if isinstance(obj, int) or isinstance(obj, bool): - return False - if isinstance(obj, type): - return False - try: - iter(obj) - except Exception: - return False - else: - return True diff --git a/pkg/jinja2/python_src/jinja2_renderer.py b/pkg/jinja2/python_src/jinja2_renderer.py deleted file mode 100644 index fbc1a1364..000000000 --- a/pkg/jinja2/python_src/jinja2_renderer.py +++ /dev/null @@ -1,122 +0,0 @@ -import base64 -import json - -from jinja2 import StrictUndefined, FileSystemLoader, ChainableUndefined - -from dict_utils import merge_dict -from jinja2_utils import KluctlJinja2Environment, add_jinja2_filters, extract_template_error - - -begin_placeholder = "XXXXXbegin_get_image_" -end_placeholder = "_end_get_imageXXXXX" - - -class NullUndefined(ChainableUndefined): - def _return_self(self, other): - return self - - __add__ = __radd__ = __sub__ = __rsub__ = _return_self - __mul__ = __rmul__ = __div__ = __rdiv__ = _return_self - __truediv__ = __rtruediv__ = _return_self - __floordiv__ = __rfloordiv__ = _return_self - __mod__ = __rmod__ = _return_self - __pos__ = __neg__ = _return_self - __call__ = __getitem__ = _return_self - __lt__ = __le__ = __gt__ = __ge__ = _return_self - __int__ = __float__ = __complex__ = _return_self - __pow__ = __rpow__ = _return_self - -class Jinja2Renderer: - def __init__(self, trim_blocks=False, lstrip_blocks=False): - self.trim_blocks = trim_blocks - self.lstrip_blocks = lstrip_blocks - - def get_image_wrapper(self, image, latest_version=None): - if latest_version is None: - latest_version = "semver()" - placeholder = { - "image": image, - "latestVersion": str(latest_version), - } - j = json.dumps(placeholder) - j = base64.b64encode(j.encode("utf8")).decode("utf8") - j = begin_placeholder + j + end_placeholder - return j - - def build_images_vars(self): - def semver(allow_no_nums=False): - return "semver(allow_no_nums=%s)" % allow_no_nums - def prefix(s, suffix=None): - if suffix is None: - return "prefix(\"%s\")" % s - else: - return "prefix(\"%s\", suffix=\"%s\")" % (s, suffix) - def number(): - return "number()" - def regex(r): - return "regex(\"%s\")" % r - - vars = { - 'images': { - 'get_image': self.get_image_wrapper, - }, - 'version': { - 'semver': semver, - 'prefix': prefix, - 'number': number, - 'regex': regex, - }, - } - return vars - - def build_env(self, vars_str, search_dirs, strict): - vars = json.loads(vars_str) - image_vars = self.build_images_vars() - merge_dict(vars, image_vars, clone=False) - - environment = KluctlJinja2Environment(loader=FileSystemLoader(search_dirs), - undefined=StrictUndefined if strict else NullUndefined, - cache_size=10000, - auto_reload=False, - trim_blocks=self.trim_blocks, lstrip_blocks=self.lstrip_blocks) - merge_dict(environment.globals, vars, clone=False) - - add_jinja2_filters(environment) - return environment - - def render_helper(self, templates, search_dirs, vars, is_string, strict): - env = self.build_env(vars, search_dirs, strict) - - result = [] - - for i, t in enumerate(templates): - try: - if is_string: - t = env.from_string(t) - else: - t = env.get_template(t) - result.append({ - "result": t.render() - }) - except Exception as e: - result.append({ - "error": extract_template_error(e), - }) - - return result - - def RenderStrings(self, templates, search_dirs, vars, strict): - try: - return self.render_helper(templates, search_dirs, vars, True, strict) - except Exception as e: - return [{ - "error": str(e) - }] * len(templates) - - def RenderFiles(self, templates, search_dirs, vars, strict): - try: - return self.render_helper(templates, search_dirs, vars, False, strict) - except Exception as e: - return [{ - "error": str(e) - }] * len(templates) diff --git a/pkg/jinja2/python_src/jinja2_utils.py b/pkg/jinja2/python_src/jinja2_utils.py deleted file mode 100644 index 373afa280..000000000 --- a/pkg/jinja2/python_src/jinja2_utils.py +++ /dev/null @@ -1,140 +0,0 @@ -import base64 -import hashlib -import io -import json -import logging -import os -import sys -import traceback - -import jinja2 -from jinja2 import Environment, TemplateNotFound, TemplateError -from jinja2.runtime import Context - -from dict_utils import merge_dict, get_dict_value -from yaml_utils import yaml_dump, yaml_load - -logger = logging.getLogger(__name__) - -# This Jinja2 environment allows to load templates relative to the parent template. This means that for example -# '{% include "file.yml" %}' will try to include the template from a ./file.yml -class KluctlJinja2Environment(Environment): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.add_extension("jinja2.ext.loopcontrols") - - """Override join_path() to enable relative template paths.""" - """See https://stackoverflow.com/a/3655911/7132642""" - def join_path(self, template, parent): - p = os.path.join(os.path.dirname(parent), template) - p = os.path.normpath(p) - return p.replace('\\', '/') - -def b64encode(string): - return base64.b64encode(string.encode()).decode() - -def b64decode(string): - return base64.b64decode(string.encode()).decode() - -def to_yaml(obj): - return yaml_dump(obj) - -def to_json(obj): - return json.dumps(obj) - -def from_yaml(s): - return yaml_load(s) - -@jinja2.pass_context -def render(ctx, string): - t = ctx.environment.from_string(string) - return t.render(ctx.parent) - -def sha256(s): - if not isinstance(s, bytes): - s = s.encode("utf-8") - return hashlib.sha256(s).hexdigest() - -@jinja2.pass_context -def load_template(ctx, path, **kwargs): - dir = os.path.dirname(ctx.name) - full_path = os.path.join(dir, path) - try: - t = ctx.environment.get_template(full_path.replace('\\', '/')) - except TemplateNotFound: - t = ctx.environment.get_template(path.replace('\\', '/')) - vars = merge_dict(ctx.parent, kwargs) - return t.render(vars) - -class VarNotFoundException(Exception): - pass - -@jinja2.pass_context -def get_var(ctx, path, default): - if not isinstance(path, list): - path = [path] - for p in path: - r = get_dict_value(ctx.parent, p, VarNotFoundException()) - if isinstance(r, VarNotFoundException): - continue - return r - return default - -def update_dict(a, b): - merge_dict(a, b, False) - return "" - -def raise_helper(msg): - raise TemplateError(msg) - -def debug_print(msg): - logger.info("debug_print: %s" % str(msg)) - return "" - -@jinja2.pass_context -def load_sha256(ctx: Context, path, digest_len=None): - if "__calc_sha256__" in ctx: - return "__self_sha256__" - rendered = load_template(ctx, path, __calc_sha256__=True) - hash = hashlib.sha256(rendered.encode("utf-8")).hexdigest() - if digest_len is not None: - hash = hash[:digest_len] - return hash - - -def add_jinja2_filters(jinja2_env): - jinja2_env.filters['b64encode'] = b64encode - jinja2_env.filters['b64decode'] = b64decode - jinja2_env.filters['to_yaml'] = to_yaml - jinja2_env.filters['to_json'] = to_json - jinja2_env.filters['from_yaml'] = from_yaml - jinja2_env.filters['render'] = render - jinja2_env.filters['sha256'] = sha256 - jinja2_env.globals['load_template'] = load_template - jinja2_env.globals['get_var'] = get_var - jinja2_env.globals['merge_dict'] = merge_dict - jinja2_env.globals['update_dict'] = update_dict - jinja2_env.globals['raise'] = raise_helper - jinja2_env.globals['debug_print'] = debug_print - jinja2_env.globals['load_sha256'] = load_sha256 - -def extract_template_error(e): - try: - raise e - except TemplateNotFound as e2: - return "template %s not found" % str(e2) - except: - etype, value, tb = sys.exc_info() - extracted_tb = traceback.extract_tb(tb) - found_template = None - for i, s in reversed(list(enumerate(extracted_tb))): - if not s.filename.endswith(".py"): - found_template = i - break - f = io.StringIO() - if found_template is not None: - traceback.print_list([extracted_tb[found_template]], file=f) - print("%s: %s" % (type(e).__name__, str(e)), file=f) - else: - traceback.print_exception(etype, value, tb, file=f) - return f.getvalue() diff --git a/pkg/jinja2/python_src/jsonpath_utils.py b/pkg/jinja2/python_src/jsonpath_utils.py deleted file mode 100644 index 4d7f8d494..000000000 --- a/pkg/jinja2/python_src/jsonpath_utils.py +++ /dev/null @@ -1,100 +0,0 @@ -# Add better wildcard support to jsonpath lib -import fnmatch -import logging -import os - -import ply -from jsonpath_ng import auto_id_field, jsonpath, JSONPath -from jsonpath_ng.exceptions import JsonPathParserError -from jsonpath_ng.ext.parser import ExtentedJsonPathParser -from jsonpath_ng.parser import IteratorToTokenStream - - -logger = logging.getLogger(__name__) - -def ext_reified_fields(self, datum): - result = [] - for field in self.fields: - if "*" not in field: - result.append(field) - continue - try: - fields = [f for f in datum.value.keys() if fnmatch.fnmatch(f, field)] - if auto_id_field is not None: - fields.append(auto_id_field) - result += fields - except AttributeError: - pass - return tuple(result) -jsonpath.Fields.reified_fields = ext_reified_fields - -# This class pre-creates the yacc parser to speed up things -class MyJsonPathParser(ExtentedJsonPathParser): - ''' - Dummy doc-string to avoid exceptions - ''' - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - start_symbol = 'jsonpath' - - output_directory = os.path.dirname(__file__) - try: - module_name = os.path.splitext(os.path.split(__file__)[1])[0] - except: - module_name = __name__ - - parsing_table_module = '_'.join([module_name, start_symbol, 'parsetab']) - self.parser = ply.yacc.yacc(module=self, - debug=self.debug, - tabmodule=parsing_table_module, - outputdir=output_directory, - write_tables=0, - start=start_symbol, - errorlog=logger) - - def parse_token_stream(self, token_iterator, start_symbol='jsonpath'): - assert start_symbol == "jsonpath" - return self.parser.parse(lexer = IteratorToTokenStream(token_iterator)) - - -def convert_list_to_json_path(p): - p2 = "" - for x in p: - if isinstance(x, str): - if x.isalnum(): - if p2 != "": - p2 += "." - p2 += "%s" % x - else: - if p2 == "": - p2 = "$" - if '"' in x: - p2 = "%s['%s']" % (p2, x) - else: - p2 = '%s["%s"]' % (p2, x) - else: - if p2 == "": - p2 = "$" - p2 = "%s[%d]" % (p2, x) - return p2 - -json_path_cache = {} -json_path_parser = MyJsonPathParser() - -def parse_json_path(p) -> JSONPath: - if isinstance(p, list) or isinstance(p, tuple): - p = convert_list_to_json_path(p) - - if p in json_path_cache: - return json_path_cache[p] - - try: - pp = json_path_parser.parse(p) - except JsonPathParserError as e: - raise Exception("Invalid json path '%s'. Error=%s" % (p, str(e))) - - pp = json_path_cache.setdefault(p, pp) - - return pp diff --git a/pkg/jinja2/python_src/main.py b/pkg/jinja2/python_src/main.py deleted file mode 100644 index 15916d2c3..000000000 --- a/pkg/jinja2/python_src/main.py +++ /dev/null @@ -1,34 +0,0 @@ -import json -import sys - -from jinja2_renderer import Jinja2Renderer - - -def main(): - while True: - args = sys.stdin.readline() - if not args: - break - args = json.loads(args) - - r = Jinja2Renderer(args.get("trimBlocks", False), args.get("lstripBlocks", False)) - - if args["cmd"] == "render-strings": - result = r.RenderStrings(args["templates"], args["searchDirs"] or [], args["vars"], args["strict"]) - elif args["cmd"] == "render-files": - result = r.RenderFiles(args["templates"], args["searchDirs"] or [], args["vars"], args["strict"]) - elif args["cmd"] == "exit": - break - else: - raise Exception("invalid cmd") - - result = json.dumps(result) - - sys.stdout.write(result + "\n") - sys.stdout.flush() - -if __name__ == "__main__": - try: - main() - except KeyboardInterrupt as e: - pass diff --git a/pkg/jinja2/python_src/yaml_utils.py b/pkg/jinja2/python_src/yaml_utils.py deleted file mode 100644 index ce9d8ec8d..000000000 --- a/pkg/jinja2/python_src/yaml_utils.py +++ /dev/null @@ -1,51 +0,0 @@ -import yaml - -from yaml import SafeLoader as SafeLoader, SafeDumper as SafeDumper - -def construct_value(load, node): - if not isinstance(node, yaml.ScalarNode): - raise yaml.constructor.ConstructorError( - "while constructing a value", - node.start_mark, - "expected a scalar, but found %s" % node.id, node.start_mark - ) - yield str(node.value) - - -# See https://github.com/yaml/pyyaml/issues/89 -SafeLoader.add_constructor(u'tag:yaml.org,2002:value', construct_value) - - -def multiline_str_representer(dumper, data): - if len(data.splitlines()) > 1: # check for multiline string - return dumper.represent_scalar('tag:yaml.org,2002:str', data, style='|') - return dumper.represent_scalar('tag:yaml.org,2002:str', data) - -class MultilineStrDumper(SafeDumper): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.add_representer(str, multiline_str_representer) - -def yaml_load(s): - return yaml.load(s, Loader=SafeLoader) - -def yaml_load_all(s): - return list(yaml.load_all(s, Loader=SafeLoader)) - -def yaml_load_file(path, all=False): - with open(path) as f: - if all: - y = yaml_load_all(f) - else: - y = yaml_load(f) - return y - -def yaml_dump(y, stream=None): - return yaml.dump(y, stream=stream, Dumper=MultilineStrDumper, sort_keys=False) - -def yaml_dump_all(y, stream=None): - return yaml.dump_all(y, stream=stream, Dumper=MultilineStrDumper, sort_keys=False) - -def yaml_save_file(y, path): - with open(path, mode='w') as f: - yaml_dump(y, f) diff --git a/pkg/jinja2/render_struct.go b/pkg/jinja2/render_struct.go deleted file mode 100644 index e2f06996b..000000000 --- a/pkg/jinja2/render_struct.go +++ /dev/null @@ -1,181 +0,0 @@ -package jinja2 - -import ( - "github.com/kluctl/kluctl/v2/pkg/utils" - "github.com/kluctl/kluctl/v2/pkg/utils/uo" - "reflect" -) - -type structStringCollectorEntry struct { - s string - setter func(s string) -} - -type structStringCollector struct { - j *Jinja2 - strings []structStringCollectorEntry -} - -func (w *structStringCollector) extractTemplateString(v reflect.Value) (string, bool) { - if v.IsZero() { - return "", false - } - - t := v.Type() - if t.Kind() == reflect.Interface || t.Kind() == reflect.Pointer { - v = v.Elem() - t = v.Type() - } - if t.Kind() == reflect.String { - s := v.String() - if !isMaybeTemplateString(s) { - return "", false - } - return v.String(), true - } - return "", false -} - -func (w *structStringCollector) addString(s string, setter func(s string)) { - w.strings = append(w.strings, structStringCollectorEntry{ - s: s, - setter: setter, - }) -} - -func (w *structStringCollector) walkStruct(v reflect.Value) error { - t := v.Type() - l := t.NumField() - - for i := 0; i < l; i++ { - f := v.Field(i) - if s, ok := w.extractTemplateString(f); ok { - w.addString(s, func(s string) { - if f.Type().Kind() == reflect.Pointer { - f.Set(reflect.ValueOf(&s)) - } else { - f.Set(reflect.ValueOf(s)) - } - }) - } else { - err := w.walkValue(f) - if err != nil { - return err - } - } - } - return nil -} - -func (w *structStringCollector) walkList(v reflect.Value) error { - l := v.Len() - for i := 0; i < l; i++ { - e := v.Index(i) - if s, ok := w.extractTemplateString(e); ok { - w.addString(s, func(s string) { - if e.Type().Kind() == reflect.Pointer { - e.Set(reflect.ValueOf(&s)) - } else { - e.Set(reflect.ValueOf(s)) - } - }) - } else { - err := w.walkValue(e) - if err != nil { - return err - } - } - } - return nil -} - -func (w *structStringCollector) walkMap(v reflect.Value) error { - it := v.MapRange() - for it.Next() { - ik := it.Key() - iv := it.Value() - - if s, ok := w.extractTemplateString(iv); ok { - if isMaybeTemplateString(s) { - w.addString(s, func(s string) { - if iv.Type().Kind() == reflect.Pointer { - v.SetMapIndex(ik, reflect.ValueOf(&s)) - } else { - v.SetMapIndex(ik, reflect.ValueOf(s)) - } - }) - } - } else { - err := w.walkValue(iv) - if err != nil { - return err - } - } - } - return nil -} - -func (w *structStringCollector) walkValue(v reflect.Value) error { - if v.IsZero() { - return nil - } - - v = reflect.Indirect(v) - t := v.Type() - - switch t.Kind() { - case reflect.Interface, reflect.Pointer: - return w.walkValue(v.Elem()) - case reflect.Slice, reflect.Array: - return w.walkList(v) - case reflect.Struct: - return w.walkStruct(v) - case reflect.Map: - return w.walkMap(v) - } - return nil -} - -func (j *Jinja2) RenderStruct(o interface{}, vars *uo.UnstructuredObject) (bool, bool, error) { - w := &structStringCollector{j: j} - v := reflect.ValueOf(o) - err := w.walkValue(v) - if err != nil { - return false, false, err - } - - var jobs []*RenderJob - - for _, sv := range w.strings { - jobs = append(jobs, &RenderJob{Template: sv.s}) - } - - err = j.RenderStrings(jobs, nil, vars) - if err != nil { - return false, false, err - } - - changed := false - moreTemplates := false - - var errors []error - for i, sv := range w.strings { - job := jobs[i] - if job.Error != nil { - errors = append(errors, job.Error) - } else { - if sv.s != *job.Result { - sv.setter(*job.Result) - changed = true - if isMaybeTemplateString(*job.Result) { - moreTemplates = true - } - } - } - } - if len(errors) != 0 { - return false, false, utils.NewErrorListOrNil(errors) - } - - return changed, moreTemplates, nil -} diff --git a/pkg/jinja2/source.go b/pkg/jinja2/source.go deleted file mode 100644 index b80377261..000000000 --- a/pkg/jinja2/source.go +++ /dev/null @@ -1,93 +0,0 @@ -package jinja2 - -import ( - "crypto/sha256" - "embed" - "encoding/binary" - "encoding/hex" - "fmt" - "github.com/kluctl/kluctl/v2/pkg/utils" - "github.com/rogpeppe/go-internal/lockedfile" - "io/fs" - "os" - "path/filepath" -) - -//go:embed python_src -var _pythonSrc embed.FS -var pythonSrc, _ = fs.Sub(_pythonSrc, "python_src") - -var pythonSrcExtracted string - -func init() { - srcDir, err := extractSource() - if err != nil { - panic(err) - } - pythonSrcExtracted = srcDir -} - -func extractSource() (string, error) { - hash := calcEmbeddedHash(pythonSrc) - - targetPath := filepath.Join(utils.GetTmpBaseDir(), fmt.Sprintf("jinja2-%s", hash[:16])) - - lock, err := lockedfile.Create(targetPath + ".lock") - if err != nil { - return "", err - } - defer lock.Close() - - err = fs.WalkDir(pythonSrc, ".", func(path string, d fs.DirEntry, err error) error { - if d == nil || d.IsDir() { - return nil - } - data, err := fs.ReadFile(pythonSrc, path) - if err != nil { - return err - } - targetPath2 := filepath.Join(targetPath, path) - err = os.MkdirAll(filepath.Dir(targetPath2), 0o755) - if err != nil { - return err - } - - err = os.WriteFile(targetPath2+".tmp", data, 0o644) - if err != nil { - return err - } - err = os.Rename(targetPath2+".tmp", targetPath2) - if err != nil { - return err - } - return nil - }) - if err != nil { - return "", err - } - - return targetPath, nil -} - -func calcEmbeddedHash(fs1 fs.FS) string { - h := sha256.New() - err := fs.WalkDir(fs1, ".", func(path string, d fs.DirEntry, err error) error { - _ = binary.Write(h, binary.LittleEndian, path) - if d.IsDir() { - _ = binary.Write(h, binary.LittleEndian, "dir") - } else { - _ = binary.Write(h, binary.LittleEndian, "regular") - data, err := fs.ReadFile(fs1, path) - if err != nil { - panic(err) - } - _ = binary.Write(h, binary.LittleEndian, len(data)) - _ = binary.Write(h, binary.LittleEndian, data) - } - return nil - }) - if err != nil { - panic(err) - } - return hex.EncodeToString(h.Sum(nil)) -} diff --git a/pkg/kluctl_jinja2/embed.go b/pkg/kluctl_jinja2/embed.go new file mode 100644 index 000000000..a948f8133 --- /dev/null +++ b/pkg/kluctl_jinja2/embed.go @@ -0,0 +1,8 @@ +package kluctl_jinja2 + +import ( + "embed" +) + +//go:embed all:ext +var ExtSource embed.FS diff --git a/pkg/kluctl_jinja2/ext/__init__.py b/pkg/kluctl_jinja2/ext/__init__.py new file mode 100644 index 000000000..4e1189132 --- /dev/null +++ b/pkg/kluctl_jinja2/ext/__init__.py @@ -0,0 +1,3 @@ +from .images_ext import ImagesExtension + +images = ImagesExtension diff --git a/pkg/kluctl_jinja2/ext/images_ext.py b/pkg/kluctl_jinja2/ext/images_ext.py new file mode 100644 index 000000000..4c363b194 --- /dev/null +++ b/pkg/kluctl_jinja2/ext/images_ext.py @@ -0,0 +1,53 @@ +import base64 +import json + +from jinja2.ext import Extension + +begin_placeholder = "XXXXXbegin_get_image_" +end_placeholder = "_end_get_imageXXXXX" + +class ImagesExtension(Extension): + def __init__(self, environment): + super().__init__(environment) + environment.globals.update(self.build_images_vars()) + + def get_image_wrapper(self, image, latest_version=None): + if latest_version is None: + latest_version = "semver()" + placeholder = { + "image": image, + "latestVersion": str(latest_version), + } + j = json.dumps(placeholder) + j = base64.b64encode(j.encode("utf8")).decode("utf8") + j = begin_placeholder + j + end_placeholder + return j + + def build_images_vars(self): + def semver(allow_no_nums=False): + return "semver(allow_no_nums=%s)" % allow_no_nums + + def prefix(s, suffix=None): + if suffix is None: + return "prefix(\"%s\")" % s + else: + return "prefix(\"%s\", suffix=\"%s\")" % (s, suffix) + + def number(): + return "number()" + + def regex(r): + return "regex(\"%s\")" % r + + vars = { + 'images': { + 'get_image': self.get_image_wrapper, + }, + 'version': { + 'semver': semver, + 'prefix': prefix, + 'number': number, + 'regex': regex, + }, + } + return vars diff --git a/pkg/kluctl_jinja2/jinja2.go b/pkg/kluctl_jinja2/jinja2.go new file mode 100644 index 000000000..13b04154d --- /dev/null +++ b/pkg/kluctl_jinja2/jinja2.go @@ -0,0 +1,23 @@ +package kluctl_jinja2 + +import ( + "github.com/kluctl/go-embed-python/embed_util" + x "github.com/kluctl/go-jinja2" +) + +const parallelism = 4 + +func NewKluctlJinja2(strict bool) (*x.Jinja2, error) { + extSrc, err := embed_util.NewEmbeddedFiles(ExtSource, "kluctl-ext") + if err != nil { + return nil, err + } + + return x.NewJinja2("kluctl", + parallelism, + x.WithStrict(strict), + x.WithExtension("jinja2.ext.loopcontrols"), + x.WithExtension("go_jinja2.ext.kluctl"), + x.WithExtension("ext.images_ext.ImagesExtension"), + x.WithPythonPath(extSrc.GetExtractedPath())) +} diff --git a/pkg/kluctl_project/load.go b/pkg/kluctl_project/load.go index 78cde9970..d5d8ec704 100644 --- a/pkg/kluctl_project/load.go +++ b/pkg/kluctl_project/load.go @@ -2,7 +2,7 @@ package kluctl_project import ( "context" - "github.com/kluctl/kluctl/v2/pkg/jinja2" + "github.com/kluctl/go-jinja2" "github.com/kluctl/kluctl/v2/pkg/status" ) diff --git a/pkg/kluctl_project/project.go b/pkg/kluctl_project/project.go index da067d70f..e0495d225 100644 --- a/pkg/kluctl_project/project.go +++ b/pkg/kluctl_project/project.go @@ -3,8 +3,8 @@ package kluctl_project import ( "context" "fmt" + "github.com/kluctl/go-jinja2" "github.com/kluctl/kluctl/v2/pkg/git/repocache" - "github.com/kluctl/kluctl/v2/pkg/jinja2" types2 "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" "regexp" diff --git a/pkg/kluctl_project/targets.go b/pkg/kluctl_project/targets.go index 18ac0153a..17f3f784c 100644 --- a/pkg/kluctl_project/targets.go +++ b/pkg/kluctl_project/targets.go @@ -97,8 +97,8 @@ func (c *LoadedKluctlProject) renderTarget(target *types.Target) error { } } - changed, moreTemplates, err := c.J2.RenderStruct(target, varsCtx.Vars) - if err == nil && (!changed || !moreTemplates) { + changed, err := varsCtx.RenderStruct(target) + if err == nil && !changed { return nil } } diff --git a/pkg/utils/uo/uo.go b/pkg/utils/uo/uo.go index 0356a98ac..8630e1a15 100644 --- a/pkg/utils/uo/uo.go +++ b/pkg/utils/uo/uo.go @@ -63,6 +63,22 @@ func (uo *UnstructuredObject) ToStruct(out interface{}) error { return yaml.ReadYamlBytes(b, out) } +// ToMap will ensure that only plain go values are returned, meaning that all internal structs are converted +// to maps +func (uo *UnstructuredObject) ToMap() (map[string]any, error) { + b, err := yaml.WriteYamlBytes(uo.Object) + if err != nil { + return nil, err + } + + var ret map[string]any + err = yaml.ReadYamlBytes(b, &ret) + if err != nil { + return nil, err + } + return ret, nil +} + func FromString(s string) (*UnstructuredObject, error) { o := New() err := yaml.ReadYamlString(s, &o.Object) diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index f704bf0f6..ae5ad7ca1 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -5,7 +5,6 @@ import ( "encoding/hex" "fmt" "github.com/jinzhu/copier" - "github.com/kluctl/kluctl-python-deps/pkg/utils" "io/fs" "os" "os/user" @@ -42,7 +41,7 @@ func createTmpBaseDir() { } func ensureDir(path string, perm fs.FileMode, allowCreate bool) { - if utils.Exists(path) { + if Exists(path) { st, err := os.Lstat(path) if err != nil { panic(err) diff --git a/pkg/vars/vars.go b/pkg/vars/vars.go index c29d4ba18..ecb960224 100644 --- a/pkg/vars/vars.go +++ b/pkg/vars/vars.go @@ -1,7 +1,7 @@ package vars import ( - "github.com/kluctl/kluctl/v2/pkg/jinja2" + "github.com/kluctl/go-jinja2" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" ) @@ -44,8 +44,33 @@ func (vc *VarsCtx) UpdateChildFromStruct(child string, o interface{}) error { return nil } +func (vc *VarsCtx) RenderString(t string) (string, error) { + globals, err := vc.Vars.ToMap() + if err != nil { + return "", err + } + return vc.J2.RenderString(t, + jinja2.WithGlobals(globals), + ) +} + +func (vc *VarsCtx) RenderStruct(o interface{}) (bool, error) { + globals, err := vc.Vars.ToMap() + if err != nil { + return false, err + } + return vc.J2.RenderStruct(o, jinja2.WithGlobals(globals)) +} + func (vc *VarsCtx) RenderYamlFile(p string, searchDirs []string, out interface{}) error { - ret, err := vc.J2.RenderFile(p, searchDirs, vc.Vars) + globals, err := vc.Vars.ToMap() + if err != nil { + return err + } + ret, err := vc.J2.RenderFile(p, + jinja2.WithSearchDirs(searchDirs), + jinja2.WithGlobals(globals), + ) if err != nil { return err } @@ -58,6 +83,10 @@ func (vc *VarsCtx) RenderYamlFile(p string, searchDirs []string, out interface{} return nil } -func (vc *VarsCtx) RenderDirectory(rootDir string, searchDirs []string, relSourceDir string, excludePatterns []string, subdir string, targetDir string) error { - return vc.J2.RenderDirectory(rootDir, searchDirs, relSourceDir, excludePatterns, subdir, targetDir, vc.Vars) +func (vc *VarsCtx) RenderDirectory(sourceDir string, targetDir string, excludePatterns []string, searchDirs []string) error { + globals, err := vc.Vars.ToMap() + if err != nil { + return err + } + return vc.J2.RenderDirectory(sourceDir, targetDir, excludePatterns, jinja2.WithGlobals(globals), jinja2.WithSearchDirs(searchDirs)) } diff --git a/pkg/vars/vars_loader.go b/pkg/vars/vars_loader.go index 49ffe3e9b..338e1b446 100644 --- a/pkg/vars/vars_loader.go +++ b/pkg/vars/vars_loader.go @@ -5,6 +5,7 @@ import ( "encoding/base64" "fmt" securejoin "github.com/cyphar/filepath-securejoin" + "github.com/kluctl/go-jinja2" "github.com/kluctl/kluctl/v2/pkg/git/repocache" "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/status" @@ -61,7 +62,12 @@ func (v *VarsLoader) LoadVars(varsCtx *VarsCtx, sourceIn *types.VarsSource, sear return err } - _, _, err = varsCtx.J2.RenderStruct(&source, varsCtx.Vars) + globals, err := varsCtx.Vars.ToMap() + if err != nil { + return err + } + + _, err = varsCtx.J2.RenderStruct(&source, jinja2.WithGlobals(globals)) if err != nil { return err } @@ -272,7 +278,7 @@ func (v *VarsLoader) loadFromString(varsCtx *VarsCtx, s string, secretType strin } func (v *VarsLoader) renderYamlString(varsCtx *VarsCtx, s string, out interface{}) error { - ret, err := varsCtx.J2.RenderString(s, nil, varsCtx.Vars) + ret, err := varsCtx.RenderString(s) if err != nil { return err } diff --git a/pkg/vars/vars_test.go b/pkg/vars/vars_test.go index 66e7bb092..46c822e8b 100644 --- a/pkg/vars/vars_test.go +++ b/pkg/vars/vars_test.go @@ -1,14 +1,15 @@ package vars import ( - "github.com/kluctl/kluctl/v2/pkg/jinja2" + "github.com/kluctl/go-jinja2" + "github.com/kluctl/kluctl/v2/pkg/kluctl_jinja2" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/stretchr/testify/assert" "testing" ) func newJinja2Must(t *testing.T) *jinja2.Jinja2 { - j2, err := jinja2.NewJinja2() + j2, err := kluctl_jinja2.NewKluctlJinja2(true) if err != nil { t.Fatal(err) } From 6b9961a6d49d61f88ed57d670ddb53be405f4ea1 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 8 Sep 2022 23:31:52 +0200 Subject: [PATCH 0984/2916] fix: Run go mod tidy --- go.sum | 2 ++ 1 file changed, 2 insertions(+) diff --git a/go.sum b/go.sum index d4cf7fb99..09884bf43 100644 --- a/go.sum +++ b/go.sum @@ -670,6 +670,8 @@ github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQan github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/kluctl/go-embed-python v0.0.0-3.10.6-20220906-2 h1:zjJAVyZiWJhufcafhykvXNar1vqDp/ukQk0hFuCJ9zw= github.com/kluctl/go-embed-python v0.0.0-3.10.6-20220906-2/go.mod h1:uekQDAlvKAYJWuDxxP1SGn1vTPUfoBwMS+aB6kR+v+o= +github.com/kluctl/go-jinja2 v0.0.0-20220908201753-f33cd6224f2b h1:6IGYwbtc6VR5nyUckJvgtnIlrg7dzNUbkHtVvTfPVkY= +github.com/kluctl/go-jinja2 v0.0.0-20220908201753-f33cd6224f2b/go.mod h1:8deI9sssmQf1CiCnyRJoBgfIw4Hh3+WsHmmO9+xIIO0= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= From 890e75da7d4ea2a42b9947f8a3d353c8e54b3e77 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 9 Sep 2022 09:26:41 +0200 Subject: [PATCH 0985/2916] ci: Remove python from Github workflows --- .github/workflows/release.yml | 4 ---- .github/workflows/tests.yml | 4 ---- 2 files changed, 8 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9c7982a03..bc3074b45 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,10 +24,6 @@ jobs: uses: actions/setup-go@v2 with: go-version: 1.19 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: 3.10.2 - name: Setup QEMU uses: docker/setup-qemu-action@v1 - name: Setup Docker Buildx diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 26e089c85..fad114c1a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,10 +16,6 @@ jobs: - uses: actions/setup-go@v2 with: go-version: '1.19' - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: 3.10.2 - uses: actions/cache@v2 with: path: | From 21c7a3d95ef4b919c11c117ef5eb31c442355c3a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 9 Sep 2022 09:52:02 +0200 Subject: [PATCH 0986/2916] fix: Add deployment item dir to search dir --- pkg/deployment/deployment_item.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index e97bb48d2..a5709038c 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -145,11 +145,15 @@ func (di *DeploymentItem) render(forSeal bool) error { excludePatterns = append(excludePatterns, "**.sealme") } + searchDirs := di.Project.getRenderSearchDirs() + // also add deployment item dir to search dirs + searchDirs = append([]string{*di.dir}, searchDirs...) + return varsCtx.RenderDirectory( filepath.Join(di.Project.source.dir, di.RelToSourceItemDir), di.RenderedDir, excludePatterns, - di.Project.getRenderSearchDirs(), + searchDirs, ) } From bdba594f019420270977de4e48fdbb214f108beb Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 9 Sep 2022 09:52:12 +0200 Subject: [PATCH 0987/2916] fix: Fix deadloc in status handler --- pkg/status/status_handler.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/status/status_handler.go b/pkg/status/status_handler.go index 34f2bd717..3f10a4763 100644 --- a/pkg/status/status_handler.go +++ b/pkg/status/status_handler.go @@ -177,14 +177,15 @@ func (sl *statusLine) Update(message string) { } func (sl *statusLine) end(barOverride string) { + sl.mutex.Lock() sl.barOverride = barOverride sl.current = sl.total + sl.mutex.Unlock() + sl.l.Remove(true) } func (sl *statusLine) End(result EndResult) { - sl.mutex.Lock() - defer sl.mutex.Unlock() switch result { case EndSuccess: sl.end(withColor("green", "✓")) From 9a386611f2c6520cdcb795a2d6565b576822f0b7 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 9 Sep 2022 10:59:16 +0200 Subject: [PATCH 0988/2916] feat: Remove Helm from docker image --- Dockerfile | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index cd4912bac..1585cdfb3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,11 +4,6 @@ RUN apk add --no-cache ca-certificates curl ARG ARCH=linux-amd64 -ENV HELM_VERSION=v3.8.2 -RUN wget -O helm.tar.gz https://get.helm.sh/helm-$HELM_VERSION-$ARCH.tar.gz && \ - tar xzf helm.tar.gz && \ - mv $ARCH/helm / - # We must use a glibc based distro due to embedded python not supporting musl libc for aarch64 FROM debian:bullseye-slim COPY --from=builder /helm /usr/bin From ff96e257f53eef03c17fe9e5b10ea4fcfd05b0aa Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 13 Sep 2022 15:34:56 +0200 Subject: [PATCH 0989/2916] chore: Add /vendor to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 8079ea478..276ce77b0 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,5 @@ /e2e.test* /bin /reports +/vendor dist/ From 90d639b6e6d525ae8b01d8e21497428a6f901b9b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 14 Sep 2022 11:41:39 +0200 Subject: [PATCH 0990/2916] fix: Don't use user.Current() on Linux/Mac as it requires CGO/$USER --- pkg/utils/utils.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index ae5ad7ca1..b28b992eb 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -9,6 +9,7 @@ import ( "os" "os/user" "path/filepath" + "runtime" "strconv" "sync" ) @@ -29,12 +30,17 @@ func createTmpBaseDir() { ensureDir(dir, 0o777, true) // every user gets its own tmp dir - u, err := user.Current() - if err != nil { - panic(err) + if runtime.GOOS == "windows" { + u, err := user.Current() + if err != nil { + panic(err) + } + dir = filepath.Join(dir, u.Uid) + } else { + uid := os.Getuid() + dir = filepath.Join(dir, fmt.Sprintf("%d", uid)) } - dir = filepath.Join(dir, u.Uid) ensureDir(dir, 0o700, true) tmpBaseDir = dir From 7bb2a79621c55039a6507fbbb488da9d5d9fd43e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 19 Sep 2022 16:41:57 +0200 Subject: [PATCH 0991/2916] fix: Also treat --force-replace-on-error as --replace-on-error --- pkg/deployment/utils/apply_utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index baca9e10f..9b3ce4101 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -208,7 +208,7 @@ func (a *ApplyUtil) retryApplyForceReplace(x *uo.UnstructuredObject, hook bool, func (a *ApplyUtil) retryApplyWithReplace(x *uo.UnstructuredObject, hook bool, remoteObject *uo.UnstructuredObject, applyError error) { ref := x.GetK8sRef() - if !a.o.ReplaceOnError || remoteObject == nil { + if (!a.o.ReplaceOnError && !a.o.ForceReplaceOnError) || remoteObject == nil { a.HandleError(ref, applyError) return } From 80df7d116430ed9fbe258bd1d138ccdf0c1e2a28 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 19 Sep 2022 16:42:15 +0200 Subject: [PATCH 0992/2916] fix: Upgrade go-jinja2 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 93aaf886c..6faf03396 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/jinzhu/copier v0.3.5 github.com/kevinburke/ssh_config v1.2.0 github.com/kluctl/go-embed-python v0.0.0-3.10.6-20220906-2 - github.com/kluctl/go-jinja2 v0.0.0-20220908201753-f33cd6224f2b + github.com/kluctl/go-jinja2 v0.0.0-20220915092153-31a9e083bdf2 github.com/mattn/go-isatty v0.0.16 github.com/mitchellh/go-ps v1.0.0 github.com/mitchellh/reflectwalk v1.0.2 diff --git a/go.sum b/go.sum index 09884bf43..9c5246c17 100644 --- a/go.sum +++ b/go.sum @@ -670,8 +670,8 @@ github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQan github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/kluctl/go-embed-python v0.0.0-3.10.6-20220906-2 h1:zjJAVyZiWJhufcafhykvXNar1vqDp/ukQk0hFuCJ9zw= github.com/kluctl/go-embed-python v0.0.0-3.10.6-20220906-2/go.mod h1:uekQDAlvKAYJWuDxxP1SGn1vTPUfoBwMS+aB6kR+v+o= -github.com/kluctl/go-jinja2 v0.0.0-20220908201753-f33cd6224f2b h1:6IGYwbtc6VR5nyUckJvgtnIlrg7dzNUbkHtVvTfPVkY= -github.com/kluctl/go-jinja2 v0.0.0-20220908201753-f33cd6224f2b/go.mod h1:8deI9sssmQf1CiCnyRJoBgfIw4Hh3+WsHmmO9+xIIO0= +github.com/kluctl/go-jinja2 v0.0.0-20220915092153-31a9e083bdf2 h1:qjiBSePbrBSWaJhvymlcrM3TW/PhbFoXvOfgKuQsDr0= +github.com/kluctl/go-jinja2 v0.0.0-20220915092153-31a9e083bdf2/go.mod h1:8deI9sssmQf1CiCnyRJoBgfIw4Hh3+WsHmmO9+xIIO0= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= From 5f283c9ec542003255616be7e966916c18d2311d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 20 Sep 2022 21:26:03 +0200 Subject: [PATCH 0993/2916] feat: Remove dynamicArgs pattern support --- pkg/kluctl_project/project.go | 14 -------------- pkg/types/kluctl_project.go | 3 +-- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/pkg/kluctl_project/project.go b/pkg/kluctl_project/project.go index e0495d225..31e833e3a 100644 --- a/pkg/kluctl_project/project.go +++ b/pkg/kluctl_project/project.go @@ -7,7 +7,6 @@ import ( "github.com/kluctl/kluctl/v2/pkg/git/repocache" types2 "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" - "regexp" ) type LoadedKluctlProject struct { @@ -70,18 +69,5 @@ func (c *LoadedKluctlProject) CheckDynamicArg(target *types2.Target, argName str return fmt.Errorf("dynamic argument %s is not allowed for target", argName) } - argPattern := ".*" - if dynArg.Pattern != nil { - argPattern = *dynArg.Pattern - } - argPattern = fmt.Sprintf("^%s$", argPattern) - - m, err := regexp.MatchString(argPattern, argValue) - if err != nil { - return err - } - if !m { - return fmt.Errorf("dynamic argument %s does not match required pattern '%s", argName, argPattern) - } return nil } diff --git a/pkg/types/kluctl_project.go b/pkg/types/kluctl_project.go index 9ecb1fcdd..f5fb852ad 100644 --- a/pkg/types/kluctl_project.go +++ b/pkg/types/kluctl_project.go @@ -7,8 +7,7 @@ import ( ) type DynamicArg struct { - Name string `yaml:"name" validate:"required"` - Pattern *string `yaml:"pattern,omitempty"` + Name string `yaml:"name" validate:"required"` } type ExternalTargetConfig struct { From 834c11f258fedcdc6ce232240c5d67b81f6c196e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 20 Sep 2022 23:11:06 +0200 Subject: [PATCH 0994/2916] fix: Don't treat final error as formatted string --- cmd/kluctl/commands/root.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index 7e3928ac8..f7eea355e 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -254,7 +254,7 @@ composed of multiple smaller parts (Helm/Kustomize/...) in a manageable and unif sh := status.FromContext(cliCtx) if err != nil { - status.Error(cliCtx, err.Error()) + status.Error(cliCtx, "%s", err.Error()) sh.Stop() os.Exit(1) } From e6c028ca3bcda3996df6409d5c1e37543a1f251a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 20 Sep 2022 23:11:32 +0200 Subject: [PATCH 0995/2916] fix: Fix diff after downscale --- pkg/deployment/commands/downscale.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pkg/deployment/commands/downscale.go b/pkg/deployment/commands/downscale.go index 82e89b2cc..184f08029 100644 --- a/pkg/deployment/commands/downscale.go +++ b/pkg/deployment/commands/downscale.go @@ -6,7 +6,6 @@ import ( utils2 "github.com/kluctl/kluctl/v2/pkg/deployment/utils" "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/types" - k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "sync" ) @@ -34,8 +33,6 @@ func (cmd *DownscaleCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*types ad := utils2.NewApplyDeploymentsUtil(ctx, dew, cmd.c.Deployments, ru, k, &utils2.ApplyUtilOptions{}) - appliedObjects := make(map[k8s2.ObjectRef]*uo.UnstructuredObject) - for _, d := range cmd.c.Deployments { if !d.CheckInclusionForDeploy() { continue @@ -62,7 +59,7 @@ func (cmd *DownscaleCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*types } wg.Wait() - du := utils2.NewDiffUtil(dew, cmd.c.Deployments, ru, appliedObjects) + du := utils2.NewDiffUtil(dew, cmd.c.Deployments, ru, ad.GetAppliedObjectsMap()) du.Diff() return &types.CommandResult{ From 0c9fedec1fd44efce4a9ec08d24f85fc17660bfd Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 20 Sep 2022 23:12:13 +0200 Subject: [PATCH 0996/2916] feat: Treat -a values as yaml --- pkg/deployment/external_args.go | 11 ++++++++--- pkg/kluctl_project/target_context.go | 6 +++++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/pkg/deployment/external_args.go b/pkg/deployment/external_args.go index e3c6600ed..53e4a292d 100644 --- a/pkg/deployment/external_args.go +++ b/pkg/deployment/external_args.go @@ -28,16 +28,21 @@ func ParseArgs(argsList []string) (map[string]string, error) { return args, nil } -func ConvertArgsToVars(args map[string]string) *uo.UnstructuredObject { +func ConvertArgsToVars(args map[string]string) (*uo.UnstructuredObject, error) { vars := uo.New() for n, v := range args { var p []interface{} for _, x := range strings.Split(n, ".") { p = append(p, x) } - _ = vars.SetNestedField(v, p...) + var j any + err := yaml.ReadYamlString(v, &j) + if err != nil { + return nil, err + } + _ = vars.SetNestedField(j, p...) } - return vars + return vars, nil } func CheckRequiredDeployArgs(dir string, varsCtx *vars.VarsCtx, deployArgs *uo.UnstructuredObject) error { diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index 47d187a36..d8624f8ba 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -151,7 +151,11 @@ func (p *LoadedKluctlProject) buildVars(target *types.Target, clusterName *strin } } } - allArgs.Merge(deployment.ConvertArgsToVars(args)) + convertedArgs, err := deployment.ConvertArgsToVars(args) + if err != nil { + return doError(err) + } + allArgs.Merge(convertedArgs) if target != nil { if target.Args != nil { allArgs.Merge(target.Args) From ad4535223b7b722b195bec6ab66feff32680b8b9 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 20 Sep 2022 23:12:48 +0200 Subject: [PATCH 0997/2916] feat: Allow children of dynamic args to be set via -a --- pkg/kluctl_project/project.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/kluctl_project/project.go b/pkg/kluctl_project/project.go index 31e833e3a..65dc137da 100644 --- a/pkg/kluctl_project/project.go +++ b/pkg/kluctl_project/project.go @@ -7,6 +7,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/git/repocache" types2 "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" + "strings" ) type LoadedKluctlProject struct { @@ -60,7 +61,7 @@ func (c *LoadedKluctlProject) FindDynamicTarget(name string) (*types2.DynamicTar func (c *LoadedKluctlProject) CheckDynamicArg(target *types2.Target, argName string, argValue string) error { var dynArg *types2.DynamicArg for _, x := range target.DynamicArgs { - if x.Name == argName { + if x.Name == argName || strings.HasPrefix(argName, x.Name+".") { dynArg = &x break } From 42e5a451dd52a10c9a017c3896bd82d0e75d4b93 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 20 Sep 2022 23:13:53 +0200 Subject: [PATCH 0998/2916] feat: Allow nested defaults for args --- pkg/deployment/external_args.go | 20 ++++++++++++++------ pkg/kluctl_project/target_context.go | 2 +- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/pkg/deployment/external_args.go b/pkg/deployment/external_args.go index 53e4a292d..955b74b27 100644 --- a/pkg/deployment/external_args.go +++ b/pkg/deployment/external_args.go @@ -45,7 +45,7 @@ func ConvertArgsToVars(args map[string]string) (*uo.UnstructuredObject, error) { return vars, nil } -func CheckRequiredDeployArgs(dir string, varsCtx *vars.VarsCtx, deployArgs *uo.UnstructuredObject) error { +func LoadDeploymentArgs(dir string, varsCtx *vars.VarsCtx, deployArgs *uo.UnstructuredObject) error { // First try to load the config without templating to avoid getting errors while rendering because required // args were not set. Otherwise we won't be able to iterator through the 'args' array in the deployment.yml // when the rendering error is actually args related. @@ -69,6 +69,19 @@ func CheckRequiredDeployArgs(dir string, varsCtx *vars.VarsCtx, deployArgs *uo.U return nil } + // load defaults + defaults := uo.New() + for _, a := range conf.Args { + if a.Default != nil { + a2 := uo.FromMap(map[string]interface{}{ + a.Name: a.Default, + }) + defaults.Merge(a2) + } + } + defaults.Merge(deployArgs) + *deployArgs = *defaults + err = checkRequiredArgs(conf.Args, deployArgs) if err != nil { return err @@ -86,11 +99,6 @@ func checkRequiredArgs(argsDef []*types.DeploymentArg, args *uo.UnstructuredObje if !found { if a.Default == nil { return fmt.Errorf("required argument %s not set", a.Name) - } else { - err := args.SetNestedField(a.Default, p...) - if err != nil { - return err - } } } } diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index d8624f8ba..023bf089a 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -167,7 +167,7 @@ func (p *LoadedKluctlProject) buildVars(target *types.Target, clusterName *strin } } - err = deployment.CheckRequiredDeployArgs(p.DeploymentDir, varsCtx, allArgs) + err = deployment.LoadDeploymentArgs(p.DeploymentDir, varsCtx, allArgs) if err != nil { return doError(err) } From 97bc348f027b1739bb99cb69df56e2efcb02f72a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 20 Sep 2022 23:14:26 +0200 Subject: [PATCH 0999/2916] feat: Implement kluctl.io/delete annotation --- pkg/deployment/utils/apply_utils.go | 16 +++++++++++++--- pkg/deployment/utils/hooks_util.go | 4 ++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index 9b3ce4101..58b32270f 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -412,7 +412,7 @@ func (a *ApplyUtil) WaitReadiness(ref k8s2.ObjectRef, timeout time.Duration) boo } func (a *ApplyUtil) applyDeploymentItem(d *deployment.DeploymentItem) { - var toDelete []k8s2.ObjectRef + toDelete := map[k8s2.ObjectRef]bool{} for _, x := range d.Config.DeleteObjects { for _, gvk := range a.k.Resources.GetFilteredGVKs(k8s.BuildGVKFilter(x.Group, nil, x.Kind)) { ref := k8s2.ObjectRef{ @@ -420,7 +420,12 @@ func (a *ApplyUtil) applyDeploymentItem(d *deployment.DeploymentItem) { Name: x.Name, Namespace: x.Namespace, } - toDelete = append(toDelete, ref) + toDelete[ref] = true + } + } + for _, x := range d.Objects { + if utils.ParseBoolOrFalse(x.GetK8sAnnotation("kluctl.io/delete")) { + toDelete[x.GetK8sRef()] = true } } @@ -438,6 +443,9 @@ func (a *ApplyUtil) applyDeploymentItem(d *deployment.DeploymentItem) { if h.GetHook(o) != nil { continue } + if _, ok := toDelete[o.GetK8sRef()]; ok { + continue + } applyObjects = append(applyObjects, o) } @@ -462,10 +470,12 @@ func (a *ApplyUtil) applyDeploymentItem(d *deployment.DeploymentItem) { if len(toDelete) != 0 { a.sctx.InfoFallback("Deleting %d objects", len(toDelete)) - for i, ref := range toDelete { + i := 0 + for ref := range toDelete { a.sctx.Update(fmt.Sprintf("Deleting object %s (%d of %d)", ref.String(), i+1, len(toDelete))) a.DeleteObject(ref, false) a.sctx.Increment() + i++ } } diff --git a/pkg/deployment/utils/hooks_util.go b/pkg/deployment/utils/hooks_util.go index b811962c7..9acbb6178 100644 --- a/pkg/deployment/utils/hooks_util.go +++ b/pkg/deployment/utils/hooks_util.go @@ -156,6 +156,10 @@ func (u *HooksUtil) GetHook(o *uo.UnstructuredObject) *hook { return ret } + if utils.ParseBoolOrFalse(o.GetK8sAnnotation("kluctl.io/delete")) { + return nil + } + hooks := getSet("kluctl.io/hook") for h := range hooks { if utils.FindStrInSlice(supportedKluctlHooks, h) == -1 { From b844b865c5db9634dd00e85e0ffada40f7bcdb3e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 20 Sep 2022 23:41:10 +0200 Subject: [PATCH 1000/2916] fix: Don'tr treat string array arguments as CSV --- cmd/kluctl/commands/cobra_utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/kluctl/commands/cobra_utils.go b/cmd/kluctl/commands/cobra_utils.go index b8fae0c44..1d2fa005a 100644 --- a/cmd/kluctl/commands/cobra_utils.go +++ b/cmd/kluctl/commands/cobra_utils.go @@ -171,7 +171,7 @@ func (c *rootCommand) buildCobraArg(cg *commandAndGroups, f reflect.StructField, if defaultValue != "" { return fmt.Errorf("default not supported for slices") } - cg.cmd.PersistentFlags().StringSliceVarP(v2.(*[]string), name, shortFlag, nil, help) + cg.cmd.PersistentFlags().StringArrayVarP(v2.(*[]string), name, shortFlag, nil, help) case *bool: parsedDefault := false if defaultValue != "" { From e96b775ee87bf305b53adfd01ecc31be7b4783a0 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 22 Sep 2022 08:56:03 +0200 Subject: [PATCH 1001/2916] refactor: Pass UnstructedObject into NewTargetContext for vars Instead of map[string]string. This allows arbitrary yaml values to be passed. --- cmd/kluctl/commands/utils.go | 6 +++++- pkg/kluctl_project/project.go | 2 +- pkg/kluctl_project/target_context.go | 15 ++++++--------- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 9b50739d4..6a6efa30f 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -145,6 +145,10 @@ func withProjectTargetCommandContext(ctx context.Context, args projectTargetComm if err != nil { return err } + optionArgs2, err := deployment.ConvertArgsToVars(optionArgs) + if err != nil { + return err + } renderOutputDir := args.renderOutputDirFlags.RenderOutputDir if renderOutputDir == "" { @@ -163,7 +167,7 @@ func withProjectTargetCommandContext(ctx context.Context, args projectTargetComm targetCtx, err := p.NewTargetContext(ctx, args.targetFlags.Target, clusterName, args.dryRunArgs == nil || args.dryRunArgs.DryRun || args.forCompletion, - optionArgs, args.forSeal, images, inclusion, + optionArgs2, args.forSeal, images, inclusion, renderOutputDir) if err != nil { return err diff --git a/pkg/kluctl_project/project.go b/pkg/kluctl_project/project.go index 65dc137da..bca9ab1a3 100644 --- a/pkg/kluctl_project/project.go +++ b/pkg/kluctl_project/project.go @@ -58,7 +58,7 @@ func (c *LoadedKluctlProject) FindDynamicTarget(name string) (*types2.DynamicTar return nil, fmt.Errorf("target %s not existent in kluctl project config", name) } -func (c *LoadedKluctlProject) CheckDynamicArg(target *types2.Target, argName string, argValue string) error { +func (c *LoadedKluctlProject) CheckDynamicArg(target *types2.Target, argName string, argValue any) error { var dynArg *types2.DynamicArg for _, x := range target.DynamicArgs { if x.Name == argName || strings.HasPrefix(argName, x.Name+".") { diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index 023bf089a..37e87f56f 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -25,7 +25,7 @@ type TargetContext struct { DeploymentCollection *deployment.DeploymentCollection } -func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName string, clusterName *string, dryRun bool, args map[string]string, forSeal bool, images *deployment.Images, inclusion *utils.Inclusion, renderOutputDir string) (*TargetContext, error) { +func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName string, clusterName *string, dryRun bool, externalArgs *uo.UnstructuredObject, forSeal bool, images *deployment.Images, inclusion *utils.Inclusion, renderOutputDir string) (*TargetContext, error) { deploymentDir, err := filepath.Abs(p.DeploymentDir) if err != nil { return nil, err @@ -42,7 +42,7 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName s images.PrependFixedImages(target.Images) } - varsCtx, clientConfig, clusterContext, err := p.buildVars(target, clusterName, args, forSeal) + varsCtx, clientConfig, clusterContext, err := p.buildVars(target, clusterName, externalArgs, forSeal) if err != nil { return nil, err } @@ -103,7 +103,7 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName s return targetCtx, nil } -func (p *LoadedKluctlProject) buildVars(target *types.Target, clusterName *string, args map[string]string, forSeal bool) (*vars.VarsCtx, *rest.Config, string, error) { +func (p *LoadedKluctlProject) buildVars(target *types.Target, clusterName *string, externalArgs *uo.UnstructuredObject, forSeal bool) (*vars.VarsCtx, *rest.Config, string, error) { doError := func(err error) (*vars.VarsCtx, *rest.Config, string, error) { return nil, nil, "", err } @@ -144,18 +144,15 @@ func (p *LoadedKluctlProject) buildVars(target *types.Target, clusterName *strin allArgs := uo.New() if target != nil { - for argName, argValue := range args { + for argName, argValue := range externalArgs.Object { err = p.CheckDynamicArg(target, argName, argValue) if err != nil { return doError(err) } } } - convertedArgs, err := deployment.ConvertArgsToVars(args) - if err != nil { - return doError(err) - } - allArgs.Merge(convertedArgs) + + allArgs.Merge(externalArgs) if target != nil { if target.Args != nil { allArgs.Merge(target.Args) From 0ec1e7c47c1e1ff78f93121a068eb2fceb9d15a3 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 22 Sep 2022 08:59:17 +0200 Subject: [PATCH 1002/2916] feat: Enable time extension for jinja2 templating --- go.mod | 2 +- go.sum | 4 ++-- pkg/kluctl_jinja2/jinja2.go | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 6faf03396..c1423545f 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/jinzhu/copier v0.3.5 github.com/kevinburke/ssh_config v1.2.0 github.com/kluctl/go-embed-python v0.0.0-3.10.6-20220906-2 - github.com/kluctl/go-jinja2 v0.0.0-20220915092153-31a9e083bdf2 + github.com/kluctl/go-jinja2 v0.0.0-20220922065727-cf67f5b7f26d github.com/mattn/go-isatty v0.0.16 github.com/mitchellh/go-ps v1.0.0 github.com/mitchellh/reflectwalk v1.0.2 diff --git a/go.sum b/go.sum index 9c5246c17..1c0792b44 100644 --- a/go.sum +++ b/go.sum @@ -670,8 +670,8 @@ github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQan github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/kluctl/go-embed-python v0.0.0-3.10.6-20220906-2 h1:zjJAVyZiWJhufcafhykvXNar1vqDp/ukQk0hFuCJ9zw= github.com/kluctl/go-embed-python v0.0.0-3.10.6-20220906-2/go.mod h1:uekQDAlvKAYJWuDxxP1SGn1vTPUfoBwMS+aB6kR+v+o= -github.com/kluctl/go-jinja2 v0.0.0-20220915092153-31a9e083bdf2 h1:qjiBSePbrBSWaJhvymlcrM3TW/PhbFoXvOfgKuQsDr0= -github.com/kluctl/go-jinja2 v0.0.0-20220915092153-31a9e083bdf2/go.mod h1:8deI9sssmQf1CiCnyRJoBgfIw4Hh3+WsHmmO9+xIIO0= +github.com/kluctl/go-jinja2 v0.0.0-20220922065727-cf67f5b7f26d h1:pfyMHiDIYskxhJFlh1u1Xi3qHKGzg8Vna5hsRm7Cz0I= +github.com/kluctl/go-jinja2 v0.0.0-20220922065727-cf67f5b7f26d/go.mod h1:8deI9sssmQf1CiCnyRJoBgfIw4Hh3+WsHmmO9+xIIO0= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= diff --git a/pkg/kluctl_jinja2/jinja2.go b/pkg/kluctl_jinja2/jinja2.go index 13b04154d..8f81ebd5e 100644 --- a/pkg/kluctl_jinja2/jinja2.go +++ b/pkg/kluctl_jinja2/jinja2.go @@ -18,6 +18,7 @@ func NewKluctlJinja2(strict bool) (*x.Jinja2, error) { x.WithStrict(strict), x.WithExtension("jinja2.ext.loopcontrols"), x.WithExtension("go_jinja2.ext.kluctl"), + x.WithExtension("go_jinja2.ext.time"), x.WithExtension("ext.images_ext.ImagesExtension"), x.WithPythonPath(extSrc.GetExtractedPath())) } From 931a04fd5bf276974bf4e2bc47f7b21f6ffb49dd Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 22 Sep 2022 09:03:22 +0200 Subject: [PATCH 1003/2916] chore: Run go get -u ./... --- go.mod | 70 ++++++++++++++++++++++++++-------------------------- go.sum | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 35 deletions(-) diff --git a/go.mod b/go.mod index c1423545f..b36010d39 100644 --- a/go.mod +++ b/go.mod @@ -5,19 +5,19 @@ go 1.19 require ( github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e github.com/Masterminds/semver/v3 v3.1.1 - github.com/aws/aws-sdk-go v1.44.89 + github.com/aws/aws-sdk-go v1.44.103 github.com/bitnami-labs/sealed-secrets v0.18.2 github.com/cyphar/filepath-securejoin v0.2.3 github.com/docker/distribution v2.8.1+incompatible github.com/evanphx/json-patch v5.6.0+incompatible github.com/fluxcd/pkg/kustomize v0.7.0 github.com/go-git/go-git/v5 v5.4.2 - github.com/go-playground/validator/v10 v10.11.0 + github.com/go-playground/validator/v10 v10.11.1 github.com/gobwas/glob v0.2.3 // indirect github.com/golang-jwt/jwt/v4 v4.4.2 github.com/google/go-containerregistry v0.11.0 github.com/gosuri/uilive v0.0.4 - github.com/hashicorp/vault/api v1.7.2 + github.com/hashicorp/vault/api v1.8.0 github.com/hexops/gotextdiff v1.0.3 github.com/imdario/mergo v0.3.13 github.com/jinzhu/copier v0.3.5 @@ -34,32 +34,32 @@ require ( github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.5.0 github.com/spf13/pflag v1.0.5 - github.com/spf13/viper v1.12.0 + github.com/spf13/viper v1.13.0 github.com/stretchr/testify v1.8.0 github.com/whilp/git-urls v1.0.0 github.com/xanzy/ssh-agent v0.3.2 - golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 - golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b - golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde - golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 - golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 + golang.org/x/crypto v0.0.0-20220919173607-35f4265a4bc0 + golang.org/x/net v0.0.0-20220921203646-d300de134e69 + golang.org/x/sync v0.0.0-20220907140024-f12130a52804 + golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 + golang.org/x/term v0.0.0-20220919170432-7a66f970e087 golang.org/x/text v0.3.7 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 - helm.sh/helm/v3 v3.9.4 - k8s.io/api v0.25.0 - k8s.io/apiextensions-apiserver v0.25.0 - k8s.io/apimachinery v0.25.0 - k8s.io/client-go v0.25.0 - k8s.io/klog/v2 v2.70.1 - sigs.k8s.io/kind v0.14.0 + helm.sh/helm/v3 v3.10.0 + k8s.io/api v0.25.2 + k8s.io/apiextensions-apiserver v0.25.2 + k8s.io/apimachinery v0.25.2 + k8s.io/client-go v0.25.2 + k8s.io/klog/v2 v2.80.1 + sigs.k8s.io/kind v0.15.0 sigs.k8s.io/kustomize/api v0.12.1 sigs.k8s.io/kustomize/kyaml v0.13.9 sigs.k8s.io/structured-merge-diff/v4 v4.2.3 ) require ( - cloud.google.com/go/compute v1.9.0 // indirect + cloud.google.com/go/compute v1.10.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest v0.11.28 // indirect @@ -76,7 +76,7 @@ require ( github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895 // indirect github.com/acomagu/bufpipe v1.0.3 // indirect github.com/alessio/shellescape v1.4.1 // indirect - github.com/armon/go-metrics v0.4.0 // indirect + github.com/armon/go-metrics v0.4.1 // indirect github.com/armon/go-radix v1.0.0 // indirect github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect github.com/beorn7/perks v1.0.1 // indirect @@ -86,8 +86,8 @@ require ( github.com/cloudflare/circl v1.2.0 // indirect github.com/containerd/containerd v1.6.8 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/docker/cli v20.10.17+incompatible // indirect - github.com/docker/docker v20.10.17+incompatible // indirect + github.com/docker/cli v20.10.18+incompatible // indirect + github.com/docker/docker v20.10.18+incompatible // indirect github.com/docker/docker-credential-helpers v0.6.4 // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-metrics v0.0.1 // indirect @@ -113,7 +113,7 @@ require ( github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.1.2 // indirect github.com/google/gnostic v0.6.9 // indirect - github.com/google/go-cmp v0.5.8 // indirect + github.com/google/go-cmp v0.5.9 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.3.0 // indirect @@ -122,7 +122,7 @@ require ( github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-hclog v1.3.0 // indirect + github.com/hashicorp/go-hclog v1.3.1 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-plugin v1.4.5 // indirect @@ -136,7 +136,7 @@ require ( github.com/hashicorp/go-version v1.6.0 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/hashicorp/vault/sdk v0.5.3 // indirect + github.com/hashicorp/vault/sdk v0.6.0 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/huandu/xstrings v1.3.2 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect @@ -145,16 +145,16 @@ require ( github.com/jmoiron/sqlx v1.3.5 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.15.9 // indirect + github.com/klauspost/compress v1.15.10 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/leodido/go-urn v1.2.1 // indirect - github.com/lib/pq v1.10.6 // indirect + github.com/lib/pq v1.10.7 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/magiconair/properties v1.8.6 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-runewidth v0.0.13 // indirect + github.com/mattn/go-runewidth v0.0.14 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect @@ -181,8 +181,8 @@ require ( github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect - github.com/rivo/uniseg v0.3.4 // indirect - github.com/rubenv/sql-migrate v1.1.2 // indirect + github.com/rivo/uniseg v0.4.2 // indirect + github.com/rubenv/sql-migrate v1.2.0 // indirect github.com/russross/blackfriday v1.6.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect github.com/sergi/go-diff v1.2.0 // indirect @@ -198,21 +198,21 @@ require ( github.com/xlab/treeprint v1.1.0 // indirect go.starlark.net v0.0.0-20220817180228-f738f5508c12 // indirect go.uber.org/atomic v1.10.0 // indirect - golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094 // indirect - golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect + golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 // indirect + golang.org/x/time v0.0.0-20220920022843-2ce7c2934d45 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf // indirect + google.golang.org/genproto v0.0.0-20220921223823-23cae91e6737 // indirect google.golang.org/grpc v1.49.0 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect - k8s.io/apiserver v0.25.0 // indirect - k8s.io/cli-runtime v0.25.0 // indirect - k8s.io/component-base v0.25.0 // indirect + k8s.io/apiserver v0.25.2 // indirect + k8s.io/cli-runtime v0.25.2 // indirect + k8s.io/component-base v0.25.2 // indirect k8s.io/kube-openapi v0.0.0-20220803164354-a70c9af30aea // indirect - k8s.io/kubectl v0.25.0 // indirect + k8s.io/kubectl v0.25.2 // indirect k8s.io/utils v0.0.0-20220823124924-e9cbc92d1a73 // indirect oras.land/oras-go v1.2.0 // indirect sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect diff --git a/go.sum b/go.sum index 1c0792b44..5851700e6 100644 --- a/go.sum +++ b/go.sum @@ -36,6 +36,8 @@ cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4g cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/compute v1.9.0 h1:ED/FP4xv8GJw63v556/ASNc1CeeLUO2Bs8nzaHchkHg= cloud.google.com/go/compute v1.9.0/go.mod h1:lWv1h/zUWTm/LozzfTJhBSkd6ShQq8la8VeeuOEGxfY= +cloud.google.com/go/compute v1.10.0 h1:aoLIYaA1fX3ywihqpBk2APQKOo20nXsp1GEZQbx5Jk4= +cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= @@ -91,11 +93,14 @@ github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy86 github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/sprig v2.15.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/Masterminds/sprig/v3 v3.2.0/go.mod h1:tWhwTbUTndesPNeF0C900vKoq283u6zp4APT9vaF3SI= github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= github.com/Masterminds/squirrel v1.5.3 h1:YPpoceAcxuzIljlr5iWpNKaql7hLeG1KLSrhvdHpkZc= @@ -134,6 +139,8 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5 github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.4.0 h1:yCQqn7dwca4ITXb+CbubHmedzaQYHhNhrEXLYUeEe8Q= github.com/armon/go-metrics v0.4.0/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= +github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= +github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= @@ -148,6 +155,8 @@ github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpi github.com/aws/aws-sdk-go v1.36.30/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go v1.44.89 h1:Xf5Pp9GsNSMRinAuWNiQd0vusXXb3IgYbNlxldhWS2Q= github.com/aws/aws-sdk-go v1.44.89/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.103 h1:tbhBHKgiZSIUkG8FcHy3wYKpPVvp65Wn7ZiX0B8phpY= +github.com/aws/aws-sdk-go v1.44.103/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -171,6 +180,7 @@ github.com/bwesterb/go-ristretto v1.2.1/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7N github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= @@ -229,10 +239,14 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm github.com/distribution/distribution/v3 v3.0.0-20220526142353-ffbd94cbe269 h1:hbCT8ZPPMqefiAWD2ZKjn7ypokIGViTvBBg/ExLSdCk= github.com/docker/cli v20.10.17+incompatible h1:eO2KS7ZFeov5UJeaDmIs1NFEDRf32PaqRpvoEkKBy5M= github.com/docker/cli v20.10.17+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v20.10.18+incompatible h1:f/GQLsVpo10VvToRay2IraVA1wHz9KktZyjev3SIVDU= +github.com/docker/cli v20.10.18+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v20.10.17+incompatible h1:JYCuMrWaVNophQTOrMMoSwudOVEfcegoZZrleKc1xwE= github.com/docker/docker v20.10.17+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v20.10.18+incompatible h1:SN84VYXTBNGn92T/QwIRPlum9zfemfitN7pbsp26WSc= +github.com/docker/docker v20.10.18+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.6.4 h1:axCks+yV+2MR3/kZhAmy07yC56WZ2Pwu/fKWtKuZB0o= github.com/docker/docker-credential-helpers v0.6.4/go.mod h1:ofX3UI0Gz1TteYBjtgs07O36Pyasyp66D2uKT7H8W1c= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= @@ -339,6 +353,8 @@ github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/j github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw= github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= +github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= +github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= @@ -452,6 +468,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-containerregistry v0.11.0 h1:Xt8x1adcREjFcmDoDK8OdOsjxu90PHkGuwNP8GiHMLM= github.com/google/go-containerregistry v0.11.0/go.mod h1:BBaYtsHPHA42uEgAvd/NejvAfPSlz281sJWqupjSxfk= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -541,6 +559,8 @@ github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrj github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.3.0 h1:G0ACM8Z2WilWgPv3Vdzwm3V0BQu/kSmrkVtpe1fy9do= github.com/hashicorp/go-hclog v1.3.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-hclog v1.3.1 h1:vDwF1DFNZhntP4DAjuTpOw3uEgMUpXh1pB5fW9DqHpo= +github.com/hashicorp/go-hclog v1.3.1/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= @@ -591,8 +611,12 @@ github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/J github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= github.com/hashicorp/vault/api v1.7.2 h1:kawHE7s/4xwrdKbkmwQi0wYaIeUhk5ueek7ljuezCVQ= github.com/hashicorp/vault/api v1.7.2/go.mod h1:xbfA+1AvxFseDzxxdWaL0uO99n1+tndus4GCrtouy0M= +github.com/hashicorp/vault/api v1.8.0 h1:7765sW1XBt+qf4XKIYE4ebY9qc/yi9V2/egzGSUNMZU= +github.com/hashicorp/vault/api v1.8.0/go.mod h1:uJrw6D3y9Rv7hhmS17JQC50jbPDAZdjZoTtrCCxxs7E= github.com/hashicorp/vault/sdk v0.5.3 h1:PWY8sq/9pRrK9vUIy75qCH2Jd8oeENAgkaa/qbhzFrs= github.com/hashicorp/vault/sdk v0.5.3/go.mod h1:DoGraE9kKGNcVgPmTuX357Fm6WAx1Okvde8Vp3dPDoU= +github.com/hashicorp/vault/sdk v0.6.0 h1:6Z+In5DXHiUfZvIZdMx7e2loL1PPyDjA4bVh9ZTIAhs= +github.com/hashicorp/vault/sdk v0.6.0/go.mod h1:+DRpzoXIdMvKc88R4qxr+edwy/RvH5QK8itmxLiDHLc= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= @@ -668,6 +692,8 @@ github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8 github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= +github.com/klauspost/compress v1.15.10 h1:Ai8UzuomSCDw90e1qNMtb15msBXsNpH6gzkkENQNcJo= +github.com/klauspost/compress v1.15.10/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= github.com/kluctl/go-embed-python v0.0.0-3.10.6-20220906-2 h1:zjJAVyZiWJhufcafhykvXNar1vqDp/ukQk0hFuCJ9zw= github.com/kluctl/go-embed-python v0.0.0-3.10.6-20220906-2/go.mod h1:uekQDAlvKAYJWuDxxP1SGn1vTPUfoBwMS+aB6kR+v+o= github.com/kluctl/go-jinja2 v0.0.0-20220922065727-cf67f5b7f26d h1:pfyMHiDIYskxhJFlh1u1Xi3qHKGzg8Vna5hsRm7Cz0I= @@ -708,6 +734,8 @@ github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs= github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= +github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= @@ -756,10 +784,13 @@ github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= @@ -775,6 +806,7 @@ github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WT github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= github.com/mitchellh/cli v1.1.2/go.mod h1:6iaV0fGdElS6dPBx0EApTxHrcWvmJphyh2n8YBLPPZ4= +github.com/mitchellh/cli v1.1.4/go.mod h1:vTLESy5mRhKOs9KDp0/RATawxP1UqBmdrpVRMnpcvKQ= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= @@ -855,6 +887,7 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.20.0 h1:8W0cWlwFkflGPLltQvLRB7ZVD5HuP6ng320w2IS245Q= +github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 h1:+czc/J8SlhPKLOtVLMQc+xDCFBT73ZStMsRhSsUhsSg= @@ -943,6 +976,8 @@ github.com/r3labs/diff/v2 v2.15.1/go.mod h1:I8noH9Fc2fjSaMxqF3G2lhDdC0b+JXCfyx85 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.3.4 h1:3Z3Eu6FGHZWSfNKJTOUiPatWwfc7DzJRU04jFUqJODw= github.com/rivo/uniseg v0.3.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8= +github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -954,6 +989,8 @@ github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/f github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rubenv/sql-migrate v1.1.2 h1:9M6oj4e//owVVHYrFISmY9LBRw6gzkCNmD9MV36tZeQ= github.com/rubenv/sql-migrate v1.1.2/go.mod h1:/7TZymwxN8VWumcIxw1jjHEcR1djpdkMHQPT4FWdnbQ= +github.com/rubenv/sql-migrate v1.2.0 h1:fOXMPLMd41sK7Tg75SXDec15k3zg5WNV6SjuDRiNfcU= +github.com/rubenv/sql-migrate v1.2.0/go.mod h1:Z5uVnq7vrIrPmHbVFfR4YLHRZquxeHpckCnRq0P/K9Y= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= @@ -1023,6 +1060,8 @@ github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH github.com/spf13/viper v1.9.0/go.mod h1:+i6ajR7OX2XaiBkrcZJFK21htRk7eDeLg7+O6bhUPP4= github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= +github.com/spf13/viper v1.13.0 h1:BWSJ/M+f+3nmdz9bxB+bWX28kkALN2ok11D0rSo8EJU= +github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw= github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -1165,6 +1204,8 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220919173607-35f4265a4bc0 h1:a5Yg6ylndHHYJqIPrdq0AhvR6KTvDTAvgBtaidhEevY= +golang.org/x/crypto v0.0.0-20220919173607-35f4265a4bc0/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1259,6 +1300,8 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b h1:ZmngSVLe/wycRns9MKikG9OWIEjGcGAkacif7oYQaUY= golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20220921203646-d300de134e69 h1:hUJpGDpnfwdJW8iNypFjmSY0sCBEL+spFTZ2eO+Sfps= +golang.org/x/net v0.0.0-20220921203646-d300de134e69/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1278,6 +1321,8 @@ golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094 h1:2o1E+E8TpNLklK9nHiPiK1uzIYrIHt+cQx3ynCwq9V8= golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 h1:lxqLZaMad/dJHMFZH0NiNpiEZI/nhgWhe4wgzpE+MuA= +golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1292,6 +1337,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde h1:ejfdSekXMDxDLbRrJMwUk6KnSLZ2McaUCVcIKM+N6jc= golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220907140024-f12130a52804 h1:0SH2R3f1b1VmIMG7BXbEZCBUu2dKmHschSmjqGUrW8A= +golang.org/x/sync v0.0.0-20220907140024-f12130a52804/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1386,11 +1433,15 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 h1:v6hYoSR9T5oet+pMXwUWkbiVqx/63mlHjefrHmxwfeY= golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 h1:h+EGohizhe9XlX18rfpa8k8RAc5XyaeamM+0VHRd4lc= +golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 h1:Q5284mrmYTpACcm+eAKjKJH48BBwSyfJqmmGDTtT8Vc= golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20220919170432-7a66f970e087 h1:tPwmk4vmvVCMdr98VgL4JH+qZxPL8fqlUOHnyOM8N3w= +golang.org/x/term v0.0.0-20220919170432-7a66f970e087/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1408,6 +1459,8 @@ golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 h1:ftMN5LMiBFjbzleLqtoBZk7KdJwhuybIU+FckUHgoyQ= golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220920022843-2ce7c2934d45 h1:yuLAip3bfURHClMG9VBdzPrQvCWjWiWUTBGV+/fCbUs= +golang.org/x/time v0.0.0-20220920022843-2ce7c2934d45/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1616,6 +1669,8 @@ google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEc google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf h1:Q5xNKbTSFwkuaaGaR7CMcXEM5sy19KYdUU8iF8/iRC0= google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220921223823-23cae91e6737 h1:K1zaaMdYBXRyX+cwFnxj7M6zwDyumLQMZ5xqwGvjreQ= +google.golang.org/genproto v0.0.0-20220921223823-23cae91e6737/go.mod h1:2r/26NEF3bFmT3eC3aZreahSal0C3Shl8Gi6vyDYqOQ= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -1708,6 +1763,8 @@ gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= helm.sh/helm/v3 v3.9.4 h1:TCI1QhJUeLVOdccfdw+vnSEO3Td6gNqibptB04QtExY= helm.sh/helm/v3 v3.9.4/go.mod h1:3eaWAIqzvlRSD06gR9MMwmp2KBKwlu9av1/1BZpjeWY= +helm.sh/helm/v3 v3.10.0 h1:y/MYONZ/bsld9kHwqgBX2uPggnUr5hahpjwt9/jrHlI= +helm.sh/helm/v3 v3.10.0/go.mod h1:paPw0hO5KVfrCMbi1M8+P8xdfBri3IiJiVKATZsFR94= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1718,25 +1775,44 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.2.1/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY= k8s.io/api v0.25.0 h1:H+Q4ma2U/ww0iGB78ijZx6DRByPz6/733jIuFpX70e0= k8s.io/api v0.25.0/go.mod h1:ttceV1GyV1i1rnmvzT3BST08N6nGt+dudGrquzVQWPk= +k8s.io/api v0.25.2 h1:v6G8RyFcwf0HR5jQGIAYlvtRNrxMJQG1xJzaSeVnIS8= +k8s.io/api v0.25.2/go.mod h1:qP1Rn4sCVFwx/xIhe+we2cwBLTXNcheRyYXwajonhy0= k8s.io/apiextensions-apiserver v0.25.0 h1:CJ9zlyXAbq0FIW8CD7HHyozCMBpDSiH7EdrSTCZcZFY= k8s.io/apiextensions-apiserver v0.25.0/go.mod h1:3pAjZiN4zw7R8aZC5gR0y3/vCkGlAjCazcg1me8iB/E= +k8s.io/apiextensions-apiserver v0.25.2 h1:8uOQX17RE7XL02ngtnh3TgifY7EhekpK+/piwzQNnBo= +k8s.io/apiextensions-apiserver v0.25.2/go.mod h1:iRwwRDlWPfaHhuBfQ0WMa5skdQfrE18QXJaJvIDLvE8= k8s.io/apimachinery v0.25.0 h1:MlP0r6+3XbkUG2itd6vp3oxbtdQLQI94fD5gCS+gnoU= k8s.io/apimachinery v0.25.0/go.mod h1:qMx9eAk0sZQGsXGu86fab8tZdffHbwUfsvzqKn4mfB0= +k8s.io/apimachinery v0.25.2 h1:WbxfAjCx+AeN8Ilp9joWnyJ6xu9OMeS/fsfjK/5zaQs= +k8s.io/apimachinery v0.25.2/go.mod h1:hqqA1X0bsgsxI6dXsJ4HnNTBOmJNxyPp8dw3u2fSHwA= k8s.io/apiserver v0.25.0 h1:8kl2ifbNffD440MyvHtPaIz1mw4mGKVgWqM0nL+oyu4= k8s.io/apiserver v0.25.0/go.mod h1:BKwsE+PTC+aZK+6OJQDPr0v6uS91/HWxX7evElAH6xo= +k8s.io/apiserver v0.25.2 h1:YePimobk187IMIdnmsMxsfIbC5p4eX3WSOrS9x6FEYw= +k8s.io/apiserver v0.25.2/go.mod h1:30r7xyQTREWCkG2uSjgjhQcKVvAAlqoD+YyrqR6Cn+I= k8s.io/cli-runtime v0.25.0 h1:XBnTc2Fi+w818jcJGzhiJKQuXl8479sZ4FhtV5hVJ1Q= k8s.io/cli-runtime v0.25.0/go.mod h1:bHOI5ZZInRHhbq12OdUiYZQN8ml8aKZLwQgt9QlLINw= +k8s.io/cli-runtime v0.25.2 h1:XOx+SKRjBpYMLY/J292BHTkmyDffl/qOx3YSuFZkTuc= +k8s.io/cli-runtime v0.25.2/go.mod h1:OQx3+/0st6x5YpkkJQlEWLC73V0wHsOFMC1/roxV8Oc= k8s.io/client-go v0.25.0 h1:CVWIaCETLMBNiTUta3d5nzRbXvY5Hy9Dpl+VvREpu5E= k8s.io/client-go v0.25.0/go.mod h1:lxykvypVfKilxhTklov0wz1FoaUZ8X4EwbhS6rpRfN8= +k8s.io/client-go v0.25.2 h1:SUPp9p5CwM0yXGQrwYurw9LWz+YtMwhWd0GqOsSiefo= +k8s.io/client-go v0.25.2/go.mod h1:i7cNU7N+yGQmJkewcRD2+Vuj4iz7b30kI8OcL3horQ4= k8s.io/component-base v0.25.0 h1:haVKlLkPCFZhkcqB6WCvpVxftrg6+FK5x1ZuaIDaQ5Y= k8s.io/component-base v0.25.0/go.mod h1:F2Sumv9CnbBlqrpdf7rKZTmmd2meJq0HizeyY/yAFxk= +k8s.io/component-base v0.25.2 h1:Nve/ZyHLUBHz1rqwkjXm/Re6IniNa5k7KgzxZpTfSQY= +k8s.io/component-base v0.25.2/go.mod h1:90W21YMr+Yjg7MX+DohmZLzjsBtaxQDDwaX4YxDkl60= +k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.70.1 h1:7aaoSdahviPmR+XkS7FyxlkkXs6tHISSG03RxleQAVQ= k8s.io/klog/v2 v2.70.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= +k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20220803164354-a70c9af30aea h1:3QOH5+2fGsY8e1qf+GIFpg+zw/JGNrgyZRQR7/m6uWg= k8s.io/kube-openapi v0.0.0-20220803164354-a70c9af30aea/go.mod h1:C/N6wCaBHeBHkHUesQOQy2/MZqGgMAFPqGsGQLdbZBU= k8s.io/kubectl v0.25.0 h1:/Wn1cFqo8ik3iee1EvpxYre3bkWsGLXzLQI6uCCAkQc= k8s.io/kubectl v0.25.0/go.mod h1:n16ULWsOl2jmQpzt2o7Dud1t4o0+Y186ICb4O+GwKAU= +k8s.io/kubectl v0.25.2 h1:2993lTeVimxKSWx/7z2PiJxUILygRa3tmC4QhFaeioA= +k8s.io/kubectl v0.25.2/go.mod h1:eoBGJtKUj7x38KXelz+dqVtbtbKwCqyKzJWmBHU0prg= k8s.io/utils v0.0.0-20220823124924-e9cbc92d1a73 h1:H9TCJUUx+2VA0ZiD9lvtaX8fthFsMoD+Izn93E/hm8U= k8s.io/utils v0.0.0-20220823124924-e9cbc92d1a73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= mvdan.cc/gofumpt v0.1.1/go.mod h1:yXG1r1WqZVKWbVRtBWKWX9+CxGYfA51nSomhM0woR48= @@ -1752,6 +1828,8 @@ sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/kind v0.14.0 h1:cNmI3jGBvp7UegEGbC5we8plDtCUmaNRL+bod7JoSCE= sigs.k8s.io/kind v0.14.0/go.mod h1:UrFRPHG+2a5j0Q7qiR4gtJ4rEyn8TuMQwuOPf+m4oHg= +sigs.k8s.io/kind v0.15.0 h1:Fskj234L4hjQlsScCgeYvCBIRt06cjLzc7+kbr1u8Tg= +sigs.k8s.io/kind v0.15.0/go.mod h1:cKTqagdRyUQmihhBOd+7p43DpOPRn9rHsUC08K1Jbsk= sigs.k8s.io/kustomize/api v0.12.1 h1:7YM7gW3kYBwtKvoY216ZzY+8hM+lV53LUayghNRJ0vM= sigs.k8s.io/kustomize/api v0.12.1/go.mod h1:y3JUhimkZkR6sbLNwfJHxvo1TCLwuwm14sCYnkH6S1s= sigs.k8s.io/kustomize/kyaml v0.13.9 h1:Qz53EAaFFANyNgyOEJbT/yoIHygK40/ZcvU3rgry2Tk= From 3704872db5d2c0ebd33dd717b6039fc0ff9180ed Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 22 Sep 2022 09:10:24 +0200 Subject: [PATCH 1004/2916] feat: Remove downscale command --- .github/ISSUE_TEMPLATE/FEATURE.yml | 1 - cmd/kluctl/commands/cmd_downscale.go | 60 ----------------- cmd/kluctl/commands/root.go | 1 - pkg/deployment/commands/downscale.go | 73 -------------------- pkg/deployment/utils/downscale_utils.go | 88 ------------------------- 5 files changed, 223 deletions(-) delete mode 100644 cmd/kluctl/commands/cmd_downscale.go delete mode 100644 pkg/deployment/commands/downscale.go delete mode 100644 pkg/deployment/utils/downscale_utils.go diff --git a/.github/ISSUE_TEMPLATE/FEATURE.yml b/.github/ISSUE_TEMPLATE/FEATURE.yml index 4bdf4b33c..44c5f6875 100644 --- a/.github/ISSUE_TEMPLATE/FEATURE.yml +++ b/.github/ISSUE_TEMPLATE/FEATURE.yml @@ -21,7 +21,6 @@ body: - label: "delete" - label: "deploy" - label: "diff" - - label: "downscale" - label: "helm-pull" - label: "helm-update" - label: "list-images" diff --git a/cmd/kluctl/commands/cmd_downscale.go b/cmd/kluctl/commands/cmd_downscale.go deleted file mode 100644 index ac2d0a610..000000000 --- a/cmd/kluctl/commands/cmd_downscale.go +++ /dev/null @@ -1,60 +0,0 @@ -package commands - -import ( - "fmt" - "github.com/kluctl/kluctl/v2/cmd/kluctl/args" - "github.com/kluctl/kluctl/v2/pkg/deployment/commands" - "github.com/kluctl/kluctl/v2/pkg/status" -) - -type downscaleCmd struct { - args.ProjectFlags - args.TargetFlags - args.ArgsFlags - args.ImageFlags - args.InclusionFlags - args.YesFlags - args.DryRunFlags - args.OutputFormatFlags - args.RenderOutputDirFlags -} - -func (cmd *downscaleCmd) Help() string { - return `This command will downscale all Deployments, StatefulSets and CronJobs. -It is also possible to influence the behaviour with the help of annotations, as described in -the documentation.` -} - -func (cmd *downscaleCmd) Run() error { - ptArgs := projectTargetCommandArgs{ - projectFlags: cmd.ProjectFlags, - targetFlags: cmd.TargetFlags, - argsFlags: cmd.ArgsFlags, - imageFlags: cmd.ImageFlags, - inclusionFlags: cmd.InclusionFlags, - dryRunArgs: &cmd.DryRunFlags, - renderOutputDirFlags: cmd.RenderOutputDirFlags, - } - return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { - if !cmd.Yes && !cmd.DryRun { - if !status.AskForConfirmation(cliCtx, fmt.Sprintf("Do you really want to downscale on context/cluster %s?", ctx.targetCtx.ClusterContext)) { - return fmt.Errorf("aborted") - } - } - - cmd2 := commands.NewDownscaleCommand(ctx.targetCtx.DeploymentCollection) - - result, err := cmd2.Run(ctx.ctx, ctx.targetCtx.SharedContext.K) - if err != nil { - return err - } - err = outputCommandResult(cmd.OutputFormat, result) - if err != nil { - return err - } - if len(result.Errors) != 0 { - return fmt.Errorf("command failed") - } - return nil - }) -} diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index f7eea355e..8c0f31a30 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -50,7 +50,6 @@ type cli struct { Delete deleteCmd `cmd:"" help:"Delete a target (or parts of it) from the corresponding cluster"` Deploy deployCmd `cmd:"" help:"Deploys a target to the corresponding cluster"` Diff diffCmd `cmd:"" help:"Perform a diff between the locally rendered target and the already deployed target"` - Downscale downscaleCmd `cmd:"" help:"Downscale all deployments"` HelmPull helmPullCmd `cmd:"" help:"Recursively searches for 'helm-chart.yaml' files and pulls the specified Helm charts"` HelmUpdate helmUpdateCmd `cmd:"" help:"Recursively searches for 'helm-chart.yaml' files and checks for new available versions"` ListImages listImagesCmd `cmd:"" help:"Renders the target and outputs all images used via 'images.get_image(...)"` diff --git a/pkg/deployment/commands/downscale.go b/pkg/deployment/commands/downscale.go deleted file mode 100644 index 184f08029..000000000 --- a/pkg/deployment/commands/downscale.go +++ /dev/null @@ -1,73 +0,0 @@ -package commands - -import ( - "context" - "github.com/kluctl/kluctl/v2/pkg/deployment" - utils2 "github.com/kluctl/kluctl/v2/pkg/deployment/utils" - "github.com/kluctl/kluctl/v2/pkg/k8s" - "github.com/kluctl/kluctl/v2/pkg/types" - "github.com/kluctl/kluctl/v2/pkg/utils/uo" - "sync" -) - -type DownscaleCommand struct { - c *deployment.DeploymentCollection -} - -func NewDownscaleCommand(c *deployment.DeploymentCollection) *DownscaleCommand { - return &DownscaleCommand{ - c: c, - } -} - -func (cmd *DownscaleCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*types.CommandResult, error) { - var wg sync.WaitGroup - - dew := utils2.NewDeploymentErrorsAndWarnings() - - ru := utils2.NewRemoteObjectsUtil(ctx, dew) - err := ru.UpdateRemoteObjects(k, cmd.c.Project.GetCommonLabels(), cmd.c.LocalObjectRefs()) - if err != nil { - return nil, err - } - - ad := utils2.NewApplyDeploymentsUtil(ctx, dew, cmd.c.Deployments, ru, k, &utils2.ApplyUtilOptions{}) - - for _, d := range cmd.c.Deployments { - if !d.CheckInclusionForDeploy() { - continue - } - au := ad.NewApplyUtil(ctx, nil) - for _, o := range d.Objects { - o := o - ref := o.GetK8sRef() - wg.Add(1) - if utils2.IsDownscaleDelete(o) { - go func() { - defer wg.Done() - au.DeleteObject(ref, false) - }() - } else { - go func() { - defer wg.Done() - au.ReplaceObject(ref, ru.GetRemoteObject(ref), func(remote *uo.UnstructuredObject) (*uo.UnstructuredObject, error) { - return utils2.DownscaleObject(remote, o) - }) - }() - } - } - } - wg.Wait() - - du := utils2.NewDiffUtil(dew, cmd.c.Deployments, ru, ad.GetAppliedObjectsMap()) - du.Diff() - - return &types.CommandResult{ - NewObjects: du.NewObjects, - ChangedObjects: du.ChangedObjects, - DeletedObjects: ad.GetDeletedObjects(), - Errors: dew.GetErrorsList(), - Warnings: dew.GetWarningsList(), - SeenImages: cmd.c.Images.SeenImages(false), - }, nil -} diff --git a/pkg/deployment/utils/downscale_utils.go b/pkg/deployment/utils/downscale_utils.go deleted file mode 100644 index aed46dd92..000000000 --- a/pkg/deployment/utils/downscale_utils.go +++ /dev/null @@ -1,88 +0,0 @@ -package utils - -import ( - "fmt" - jsonpatch "github.com/evanphx/json-patch" - "github.com/kluctl/kluctl/v2/pkg/utils/uo" - "github.com/kluctl/kluctl/v2/pkg/yaml" - "k8s.io/apimachinery/pkg/runtime/schema" - "regexp" - "strconv" -) - -var ( - downscaleAnnotationPatchRegex = regexp.MustCompile(`^kluctl.io/downscale-patch(-\d*)?$`) - downscaleAnnotationDelete = "kluctl.io/downscale-delete" - downscaleAnnotationIgnore = "kluctl.io/downscale-ignore" -) - -func IsDownscaleDelete(o *uo.UnstructuredObject) bool { - a, _ := o.GetK8sAnnotations()[downscaleAnnotationDelete] - b, _ := strconv.ParseBool(a) - return b -} - -func isDownscaleIgnore(o *uo.UnstructuredObject) bool { - a, _ := o.GetK8sAnnotations()[downscaleAnnotationIgnore] - b, _ := strconv.ParseBool(a) - return b -} - -func DownscaleObject(remote *uo.UnstructuredObject, local *uo.UnstructuredObject) (*uo.UnstructuredObject, error) { - ret := remote - if isDownscaleIgnore(local) { - return ret, nil - } - var patch jsonpatch.Patch - for _, v := range local.GetK8sAnnotationsWithRegex(downscaleAnnotationPatchRegex) { - j, err := yaml.ConvertYamlToJson([]byte(v)) - if err != nil { - return nil, fmt.Errorf("invalid jsonpatch json/yaml: %w", err) - } - p, err := jsonpatch.DecodePatch(j) - if err != nil { - return nil, fmt.Errorf("invalid jsonpatch: %w", err) - } - patch = append(patch, p...) - } - - if len(patch) != 0 { - j, err := yaml.WriteYamlBytes(ret.Object) - if err != nil { - return nil, err - } - j, err = yaml.ConvertYamlToJson(j) - if err != nil { - return nil, err - } - j, err = patch.Apply(j) - if err != nil { - return nil, err - } - ret = &uo.UnstructuredObject{} - err = yaml.ReadYamlBytes(j, &ret.Object) - if err != nil { - return nil, err - } - } - - ref := remote.GetK8sRef() - switch ref.GVK.GroupKind() { - case schema.GroupKind{Group: "apps", Kind: "Deployment"}: - fallthrough - case schema.GroupKind{Group: "apps", Kind: "StatefulSet"}: - ret = ret.Clone() - err := ret.SetNestedField(0, "spec", "replicas") - if err != nil { - return nil, err - } - case schema.GroupKind{Group: "batch", Kind: "CronJob"}: - ret = ret.Clone() - err := ret.SetNestedField(true, "spec", "suspend") - if err != nil { - return nil, err - } - } - - return ret, nil -} From 2ee45fb78ec1a5dca3fd7cd62e8ee79be7455642 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 22 Sep 2022 10:35:36 +0200 Subject: [PATCH 1005/2916] feat: Add --offline-kubernetes flag to render command --- cmd/kluctl/commands/cmd_render.go | 2 ++ cmd/kluctl/commands/utils.go | 8 +++++--- pkg/kluctl_project/target_context.go | 27 ++++++++++++++++++++------- pkg/vars/vars_loader.go | 2 +- 4 files changed, 28 insertions(+), 11 deletions(-) diff --git a/cmd/kluctl/commands/cmd_render.go b/cmd/kluctl/commands/cmd_render.go index 190760e8d..2b45c9f9b 100644 --- a/cmd/kluctl/commands/cmd_render.go +++ b/cmd/kluctl/commands/cmd_render.go @@ -13,6 +13,7 @@ type renderCmd struct { args.ArgsFlags args.ImageFlags args.RenderOutputDirFlags + OfflineKubernetes bool `group:"misc" help:"Run render in offline mode, meaning that it will not try to connect the target cluster"` } func (cmd *renderCmd) Help() string { @@ -35,6 +36,7 @@ func (cmd *renderCmd) Run() error { argsFlags: cmd.ArgsFlags, imageFlags: cmd.ImageFlags, renderOutputDirFlags: cmd.RenderOutputDirFlags, + offlineKubernetes: cmd.OfflineKubernetes, } return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { status.Info(ctx.ctx, "Rendered into %s", ctx.targetCtx.SharedContext.RenderDir) diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 6a6efa30f..968077a02 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -104,8 +104,9 @@ type projectTargetCommandArgs struct { dryRunArgs *args.DryRunFlags renderOutputDirFlags args.RenderOutputDirFlags - forSeal bool - forCompletion bool + forSeal bool + forCompletion bool + offlineKubernetes bool } type commandCtx struct { @@ -165,7 +166,8 @@ func withProjectTargetCommandContext(ctx context.Context, args projectTargetComm clusterName = &args.projectFlags.Cluster } - targetCtx, err := p.NewTargetContext(ctx, args.targetFlags.Target, clusterName, + targetCtx, err := p.NewTargetContext(ctx, + args.targetFlags.Target, clusterName, args.offlineKubernetes, args.dryRunArgs == nil || args.dryRunArgs.DryRun || args.forCompletion, optionArgs2, args.forSeal, images, inclusion, renderOutputDir) diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index 37e87f56f..f93c9e8ed 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -12,6 +12,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/vars" "github.com/kluctl/kluctl/v2/pkg/vars/aws" "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd/api" "path/filepath" ) @@ -25,7 +26,7 @@ type TargetContext struct { DeploymentCollection *deployment.DeploymentCollection } -func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName string, clusterName *string, dryRun bool, externalArgs *uo.UnstructuredObject, forSeal bool, images *deployment.Images, inclusion *utils.Inclusion, renderOutputDir string) (*TargetContext, error) { +func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName string, clusterName *string, offlineK8s bool, dryRun bool, externalArgs *uo.UnstructuredObject, forSeal bool, images *deployment.Images, inclusion *utils.Inclusion, renderOutputDir string) (*TargetContext, error) { deploymentDir, err := filepath.Abs(p.DeploymentDir) if err != nil { return nil, err @@ -42,7 +43,7 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName s images.PrependFixedImages(target.Images) } - varsCtx, clientConfig, clusterContext, err := p.buildVars(target, clusterName, externalArgs, forSeal) + varsCtx, clientConfig, clusterContext, err := p.buildVars(target, clusterName, offlineK8s, externalArgs, forSeal) if err != nil { return nil, err } @@ -103,7 +104,7 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName s return targetCtx, nil } -func (p *LoadedKluctlProject) buildVars(target *types.Target, clusterName *string, externalArgs *uo.UnstructuredObject, forSeal bool) (*vars.VarsCtx, *rest.Config, string, error) { +func (p *LoadedKluctlProject) buildVars(target *types.Target, clusterName *string, offlineK8s bool, externalArgs *uo.UnstructuredObject, forSeal bool) (*vars.VarsCtx, *rest.Config, string, error) { doError := func(err error) (*vars.VarsCtx, *rest.Config, string, error) { return nil, nil, "", err } @@ -130,9 +131,17 @@ func (p *LoadedKluctlProject) buildVars(target *types.Target, clusterName *strin } } - clientConfig, restConfig, err := p.loadArgs.ClientConfigGetter(contextName) - if err != nil { - return doError(err) + var err error + var clientConfig *rest.Config + if !offlineK8s { + var restConfig *api.Config + clientConfig, restConfig, err = p.loadArgs.ClientConfigGetter(contextName) + if err != nil { + return doError(err) + } + if contextName == nil { + contextName = &restConfig.CurrentContext + } } targetVars, err := uo.FromStruct(target) @@ -171,7 +180,11 @@ func (p *LoadedKluctlProject) buildVars(target *types.Target, clusterName *strin varsCtx.UpdateChild("args", allArgs) - return varsCtx, clientConfig, restConfig.CurrentContext, nil + var contextName2 string + if contextName != nil { + contextName2 = *contextName + } + return varsCtx, clientConfig, contextName2, nil } func (p *LoadedKluctlProject) findSecretsEntry(name string) (*types.SecretSet, error) { diff --git a/pkg/vars/vars_loader.go b/pkg/vars/vars_loader.go index 338e1b446..a479ec791 100644 --- a/pkg/vars/vars_loader.go +++ b/pkg/vars/vars_loader.go @@ -195,7 +195,7 @@ func (v *VarsLoader) loadGit(varsCtx *VarsCtx, gitFile *types.VarsSourceGit, roo func (v *VarsLoader) loadFromK8sObject(varsCtx *VarsCtx, varsSource types.VarsSourceClusterConfigMapOrSecret, kind string, key string, rootKey string, base64Decode bool) error { if v.k == nil { - return nil + return fmt.Errorf("loading vars from cluster is disabled") } var err error From ce61477328ea2e1ce0076a9675d7dbfa00877451 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 22 Sep 2022 10:35:49 +0200 Subject: [PATCH 1006/2916] feat: Add --print-all flag to render command --- cmd/kluctl/commands/cmd_render.go | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/cmd/kluctl/commands/cmd_render.go b/cmd/kluctl/commands/cmd_render.go index 2b45c9f9b..52327efdd 100644 --- a/cmd/kluctl/commands/cmd_render.go +++ b/cmd/kluctl/commands/cmd_render.go @@ -4,7 +4,9 @@ import ( "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/yaml" "io/ioutil" + "os" ) type renderCmd struct { @@ -13,7 +15,9 @@ type renderCmd struct { args.ArgsFlags args.ImageFlags args.RenderOutputDirFlags + OfflineKubernetes bool `group:"misc" help:"Run render in offline mode, meaning that it will not try to connect the target cluster"` + PrintAll bool `group:"misc" help:"Write all rendered manifests to stdout"` } func (cmd *renderCmd) Help() string { @@ -22,12 +26,14 @@ a temporary directory or a specified directory.` } func (cmd *renderCmd) Run() error { + isTmp := false if cmd.RenderOutputDir == "" { p, err := ioutil.TempDir(utils.GetTmpBaseDir(), "rendered-") if err != nil { return err } cmd.RenderOutputDir = p + isTmp = true } ptArgs := projectTargetCommandArgs{ @@ -39,7 +45,21 @@ func (cmd *renderCmd) Run() error { offlineKubernetes: cmd.OfflineKubernetes, } return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { - status.Info(ctx.ctx, "Rendered into %s", ctx.targetCtx.SharedContext.RenderDir) + if cmd.PrintAll { + var all []any + for _, d := range ctx.targetCtx.DeploymentCollection.Deployments { + for _, o := range d.Objects { + all = append(all, o) + } + } + if isTmp { + defer os.RemoveAll(cmd.RenderOutputDir) + } + status.Flush(ctx.ctx) + return yaml.WriteYamlAllStream(os.Stdout, all) + } else { + status.Info(ctx.ctx, "Rendered into %s", ctx.targetCtx.SharedContext.RenderDir) + } return nil }) } From ac91c0a5cdb90c40d0b674cea21e96b342e1e4d1 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 22 Sep 2022 10:47:30 +0200 Subject: [PATCH 1007/2916] feat: Remove cluster-id from sealed secrets This is required to make offline sealing possible. --- pkg/seal/sealer.go | 53 +--------------------------------------------- 1 file changed, 1 insertion(+), 52 deletions(-) diff --git a/pkg/seal/sealer.go b/pkg/seal/sealer.go index 80309e6a7..57e02491b 100644 --- a/pkg/seal/sealer.go +++ b/pkg/seal/sealer.go @@ -10,12 +10,10 @@ import ( "github.com/bitnami-labs/sealed-secrets/pkg/crypto" "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/status" - k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" "golang.org/x/crypto/scrypt" - "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime/schema" "os" "path/filepath" @@ -24,13 +22,11 @@ import ( ) const hashAnnotation = "kluctl.io/sealedsecret-hashes" -const clusterIdAnnotation = "kluctl.io/sealedsecret-cluster-id" type Sealer struct { ctx context.Context forceReseal bool cert *rsa.PublicKey - clusterId string } func NewSealer(ctx context.Context, k *k8s.K8sCluster, sealedSecretsNamespace string, sealedSecretsControllerName string, forceReseal bool) (*Sealer, error) { @@ -38,54 +34,16 @@ func NewSealer(ctx context.Context, k *k8s.K8sCluster, sealedSecretsNamespace st ctx: ctx, forceReseal: forceReseal, } + cert, err := fetchCert(ctx, k, sealedSecretsNamespace, sealedSecretsControllerName) if err != nil { return nil, err } s.cert = cert - clusterId, err := getClusterId(k) - if err != nil { - return nil, err - } - - s.clusterId = clusterId - return s, nil } -// We treat the hashed kube-root-ca.crt as cluster id for now. We also accept that it might change when keys -// get rotated. -func getClusterId(k *k8s.K8sCluster) (string, error) { - o, _, err := k.GetSingleObject(k8s2.ObjectRef{ - GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, - Name: "kube-root-ca.crt", - Namespace: "kube-system", - }) - if err != nil && !errors.IsNotFound(err) { - return "", fmt.Errorf("failed to retrieve kube-root-ca.crt: %w", err) - } - - var kubeRootCA string - if o != nil { - x, ok, err := o.GetNestedString("data", "ca.crt") - if err != nil { - return "", fmt.Errorf("failed to retrieve kube-root-ca.crt: %w", err) - } - if !ok { - return "", fmt.Errorf("failed to retrieve kube-root-ca.crt: ca.crt key is missing") - } - kubeRootCA = x - } else { - // fall-back to CA from kubeconfig - ca := k.GetCA() - if ca != nil { - kubeRootCA = string(ca) - } - } - return utils.Sha256String(kubeRootCA), nil -} - func (s *Sealer) doHash(key string, secret []byte, secretName string, secretNamespace string, scope string) string { if secretNamespace == "" { secretNamespace = "*" @@ -170,7 +128,6 @@ func (s *Sealer) SealFile(p string, targetFile string) error { var existingContent *uo.UnstructuredObject var existingHashes *uo.UnstructuredObject - var existingClusterId string if utils.Exists(targetFile) { existingContent, err = uo.FromFile(targetFile) @@ -178,10 +135,6 @@ func (s *Sealer) SealFile(p string, targetFile string) error { if a != nil { existingHashes, _ = uo.FromString(*a) } - a = existingContent.GetK8sAnnotation(clusterIdAnnotation) - if a != nil { - existingClusterId = *a - } } if existingHashes == nil { existingHashes = uo.New() @@ -243,9 +196,6 @@ func (s *Sealer) SealFile(p string, targetFile string) error { if s.forceReseal { resealAll = true status.Info(s.ctx, "Forcing reseal of secrets in %s", secretName) - } else if existingClusterId != s.clusterId { - resealAll = true - status.Info(s.ctx, "Target cluster for secret %s has changed, forcing reseal", secretName) } for k, v := range secrets { @@ -284,7 +234,6 @@ func (s *Sealer) SealFile(p string, targetFile string) error { return err } result.SetK8sAnnotation(hashAnnotation, resultSecretHashesStr) - result.SetK8sAnnotation(clusterIdAnnotation, s.clusterId) if reflect.DeepEqual(existingContent, result) { status.Info(s.ctx, "Skipped %s as it did not change", baseName) From 3f97ff132d744c11820b9ebb272fa3b491641c82 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 22 Sep 2022 10:54:04 +0200 Subject: [PATCH 1008/2916] refactor: Move fetching of certs outside of NewSealer --- cmd/kluctl/commands/cmd_seal.go | 7 ++++++- pkg/seal/fetch_cert.go | 2 +- pkg/seal/sealer.go | 10 ++-------- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/cmd/kluctl/commands/cmd_seal.go b/cmd/kluctl/commands/cmd_seal.go index 65fe92997..1cf922d40 100644 --- a/cmd/kluctl/commands/cmd_seal.go +++ b/cmd/kluctl/commands/cmd_seal.go @@ -66,7 +66,12 @@ func (cmd *sealCmd) runCmdSealForTarget(ctx context.Context, p *kluctl_project.L } } - sealer, err := seal.NewSealer(ctx.ctx, ctx.targetCtx.SharedContext.K, sealedSecretsNamespace, sealedSecretsControllerName, cmd.ForceReseal) + cert, err := seal.FetchCert(ctx.ctx, ctx.targetCtx.SharedContext.K, sealedSecretsNamespace, sealedSecretsControllerName) + if err != nil { + return doFail(err) + } + + sealer, err := seal.NewSealer(ctx.ctx, cert, cmd.ForceReseal) if err != nil { return doFail(err) } diff --git a/pkg/seal/fetch_cert.go b/pkg/seal/fetch_cert.go index 95b1c154a..5679079a0 100644 --- a/pkg/seal/fetch_cert.go +++ b/pkg/seal/fetch_cert.go @@ -13,7 +13,7 @@ import ( "k8s.io/client-go/util/cert" ) -func fetchCert(ctx context.Context, k *k8s.K8sCluster, namespace string, controllerName string) (*rsa.PublicKey, error) { +func FetchCert(ctx context.Context, k *k8s.K8sCluster, namespace string, controllerName string) (*rsa.PublicKey, error) { certData, err := openCertFromController(k, namespace, controllerName) if err != nil { if controllerName == "sealed-secrets-controller" { diff --git a/pkg/seal/sealer.go b/pkg/seal/sealer.go index 57e02491b..d6c31391e 100644 --- a/pkg/seal/sealer.go +++ b/pkg/seal/sealer.go @@ -8,7 +8,6 @@ import ( "encoding/hex" "fmt" "github.com/bitnami-labs/sealed-secrets/pkg/crypto" - "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" @@ -29,18 +28,13 @@ type Sealer struct { cert *rsa.PublicKey } -func NewSealer(ctx context.Context, k *k8s.K8sCluster, sealedSecretsNamespace string, sealedSecretsControllerName string, forceReseal bool) (*Sealer, error) { +func NewSealer(ctx context.Context, cert *rsa.PublicKey, forceReseal bool) (*Sealer, error) { s := &Sealer{ ctx: ctx, forceReseal: forceReseal, + cert: cert, } - cert, err := fetchCert(ctx, k, sealedSecretsNamespace, sealedSecretsControllerName) - if err != nil { - return nil, err - } - s.cert = cert - return s, nil } From 667ecaeee97d2097b1fa1700a6253eba0cf7c064 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 22 Sep 2022 11:03:09 +0200 Subject: [PATCH 1009/2916] refactor: Simplify runCmdSealForTarget --- cmd/kluctl/commands/cmd_seal.go | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/cmd/kluctl/commands/cmd_seal.go b/cmd/kluctl/commands/cmd_seal.go index 1cf922d40..7aaaa489c 100644 --- a/cmd/kluctl/commands/cmd_seal.go +++ b/cmd/kluctl/commands/cmd_seal.go @@ -8,6 +8,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/kluctl_project" "github.com/kluctl/kluctl/v2/pkg/seal" "github.com/kluctl/kluctl/v2/pkg/status" + "github.com/kluctl/kluctl/v2/pkg/types" ) type sealCmd struct { @@ -49,17 +50,24 @@ func (cmd *sealCmd) runCmdSealForTarget(ctx context.Context, p *kluctl_project.L return doFail(err) } + secretsConfig := p.Config.SecretsConfig + var sealedSecretsConfig *types.GlobalSealedSecretsConfig + if secretsConfig != nil { + sealedSecretsConfig = secretsConfig.SealedSecrets + } + sealedSecretsNamespace := "kube-system" sealedSecretsControllerName := "sealed-secrets-controller" - if p.Config.SecretsConfig != nil && p.Config.SecretsConfig.SealedSecrets != nil { - if p.Config.SecretsConfig.SealedSecrets.Namespace != nil { - sealedSecretsNamespace = *p.Config.SecretsConfig.SealedSecrets.Namespace + if sealedSecretsConfig != nil { + if sealedSecretsConfig.Namespace != nil { + sealedSecretsNamespace = *sealedSecretsConfig.Namespace } - if p.Config.SecretsConfig.SealedSecrets.ControllerName != nil { - sealedSecretsControllerName = *p.Config.SecretsConfig.SealedSecrets.ControllerName + if sealedSecretsConfig.ControllerName != nil { + sealedSecretsControllerName = *sealedSecretsConfig.ControllerName } } - if p.Config.SecretsConfig == nil || p.Config.SecretsConfig.SealedSecrets == nil || p.Config.SecretsConfig.SealedSecrets.Bootstrap == nil || *p.Config.SecretsConfig.SealedSecrets.Bootstrap { + + if sealedSecretsConfig == nil || sealedSecretsConfig.Bootstrap == nil || *sealedSecretsConfig.Bootstrap { err = seal.BootstrapSealedSecrets(ctx.ctx, ctx.targetCtx.SharedContext.K, sealedSecretsNamespace) if err != nil { return doFail(err) From 1bf1c1ab31aed5f28af85c3982e7c4fa05c2a600 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 22 Sep 2022 11:25:40 +0200 Subject: [PATCH 1010/2916] feat: Allow to specify certFile via target SealingConfig --- cmd/kluctl/commands/cmd_seal.go | 85 +++++++++++++++++++++++---------- pkg/seal/fetch_cert.go | 4 +- pkg/types/kluctl_project.go | 1 + 3 files changed, 63 insertions(+), 27 deletions(-) diff --git a/cmd/kluctl/commands/cmd_seal.go b/cmd/kluctl/commands/cmd_seal.go index 7aaaa489c..61ab95183 100644 --- a/cmd/kluctl/commands/cmd_seal.go +++ b/cmd/kluctl/commands/cmd_seal.go @@ -2,13 +2,16 @@ package commands import ( "context" + "crypto/rsa" "fmt" + securejoin "github.com/cyphar/filepath-securejoin" "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/deployment/commands" "github.com/kluctl/kluctl/v2/pkg/kluctl_project" "github.com/kluctl/kluctl/v2/pkg/seal" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" + "os" ) type sealCmd struct { @@ -50,31 +53,7 @@ func (cmd *sealCmd) runCmdSealForTarget(ctx context.Context, p *kluctl_project.L return doFail(err) } - secretsConfig := p.Config.SecretsConfig - var sealedSecretsConfig *types.GlobalSealedSecretsConfig - if secretsConfig != nil { - sealedSecretsConfig = secretsConfig.SealedSecrets - } - - sealedSecretsNamespace := "kube-system" - sealedSecretsControllerName := "sealed-secrets-controller" - if sealedSecretsConfig != nil { - if sealedSecretsConfig.Namespace != nil { - sealedSecretsNamespace = *sealedSecretsConfig.Namespace - } - if sealedSecretsConfig.ControllerName != nil { - sealedSecretsControllerName = *sealedSecretsConfig.ControllerName - } - } - - if sealedSecretsConfig == nil || sealedSecretsConfig.Bootstrap == nil || *sealedSecretsConfig.Bootstrap { - err = seal.BootstrapSealedSecrets(ctx.ctx, ctx.targetCtx.SharedContext.K, sealedSecretsNamespace) - if err != nil { - return doFail(err) - } - } - - cert, err := seal.FetchCert(ctx.ctx, ctx.targetCtx.SharedContext.K, sealedSecretsNamespace, sealedSecretsControllerName) + cert, err := cmd.loadCert(ctx) if err != nil { return doFail(err) } @@ -102,6 +81,62 @@ func (cmd *sealCmd) runCmdSealForTarget(ctx context.Context, p *kluctl_project.L }) } +func (cmd *sealCmd) loadCert(ctx *commandCtx) (*rsa.PublicKey, error) { + sealingConfig := ctx.targetCtx.Target.SealingConfig + + var certFile string + + if sealingConfig != nil && sealingConfig.CertFile != nil { + path, err := securejoin.SecureJoin(ctx.targetCtx.KluctlProject.ProjectDir, *sealingConfig.CertFile) + if err != nil { + return nil, err + } + certFile = path + } + + if certFile != "" { + d, err := os.ReadFile(certFile) + if err != nil { + return nil, err + } + cert, err := seal.ParseCert(d) + if err != nil { + return nil, err + } + return cert, nil + } else { + secretsConfig := ctx.targetCtx.KluctlProject.Config.SecretsConfig + var sealedSecretsConfig *types.GlobalSealedSecretsConfig + if secretsConfig != nil { + sealedSecretsConfig = secretsConfig.SealedSecrets + } + + sealedSecretsNamespace := "kube-system" + sealedSecretsControllerName := "sealed-secrets-controller" + if sealedSecretsConfig != nil { + if sealedSecretsConfig.Namespace != nil { + sealedSecretsNamespace = *sealedSecretsConfig.Namespace + } + if sealedSecretsConfig.ControllerName != nil { + sealedSecretsControllerName = *sealedSecretsConfig.ControllerName + } + } + + if sealedSecretsConfig == nil || sealedSecretsConfig.Bootstrap == nil || *sealedSecretsConfig.Bootstrap { + err := seal.BootstrapSealedSecrets(ctx.ctx, ctx.targetCtx.SharedContext.K, sealedSecretsNamespace) + if err != nil { + return nil, err + } + } + + cert, err := seal.FetchCert(ctx.ctx, ctx.targetCtx.SharedContext.K, sealedSecretsNamespace, sealedSecretsControllerName) + if err != nil { + return nil, err + } + return cert, nil + } +} + func (cmd *sealCmd) Run() error { return withKluctlProjectFromArgs(cmd.ProjectFlags, true, false, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { hadError := false diff --git a/pkg/seal/fetch_cert.go b/pkg/seal/fetch_cert.go index 5679079a0..900dc0369 100644 --- a/pkg/seal/fetch_cert.go +++ b/pkg/seal/fetch_cert.go @@ -34,7 +34,7 @@ func FetchCert(ctx context.Context, k *k8s.K8sCluster, namespace string, control } } - return parseKey(certData) + return ParseCert(certData) } func openCertFromBootstrap(k *k8s.K8sCluster, namespace string) ([]byte, error) { @@ -92,7 +92,7 @@ func getServicePortName(k *k8s.K8sCluster, namespace, serviceName string) (strin return n, nil } -func parseKey(data []byte) (*rsa.PublicKey, error) { +func ParseCert(data []byte) (*rsa.PublicKey, error) { certs, err := cert.ParseCertsPEM(data) if err != nil { return nil, err diff --git a/pkg/types/kluctl_project.go b/pkg/types/kluctl_project.go index f5fb852ad..3f7efdf59 100644 --- a/pkg/types/kluctl_project.go +++ b/pkg/types/kluctl_project.go @@ -25,6 +25,7 @@ type SealingConfig struct { DynamicSealing *bool `yaml:"dynamicSealing,omitempty"` Args *uo.UnstructuredObject `yaml:"args,omitempty"` SecretSets []string `yaml:"secretSets,omitempty"` + CertFile *string `yaml:"certFile,omitempty"` } type Target struct { From 864a0d694b84ce73af47801e82b01ad1e727501e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 22 Sep 2022 11:26:07 +0200 Subject: [PATCH 1011/2916] feat: Allow to override certFile via command line --- cmd/kluctl/commands/cmd_seal.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/cmd/kluctl/commands/cmd_seal.go b/cmd/kluctl/commands/cmd_seal.go index 61ab95183..83ebbe68a 100644 --- a/cmd/kluctl/commands/cmd_seal.go +++ b/cmd/kluctl/commands/cmd_seal.go @@ -18,7 +18,8 @@ type sealCmd struct { args.ProjectFlags args.TargetFlags - ForceReseal bool `group:"misc" help:"Lets kluctl ignore secret hashes found in already sealed secrets and thus forces resealing of those."` + ForceReseal bool `group:"misc" help:"Lets kluctl ignore secret hashes found in already sealed secrets and thus forces resealing of those."` + CertFile string `group:"misc" help:"Use the given certificate for sealing instead of requesting it from the sealed-secrets controller"` } func (cmd *sealCmd) Help() string { @@ -94,6 +95,13 @@ func (cmd *sealCmd) loadCert(ctx *commandCtx) (*rsa.PublicKey, error) { certFile = path } + if cmd.CertFile != "" { + if certFile != "" { + status.Info(ctx.ctx, "Overriding certFile from target with certFile argument") + } + certFile = cmd.CertFile + } + if certFile != "" { d, err := os.ReadFile(certFile) if err != nil { From b25579cc82baf29c015bc63ef6e975298b3fec3e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 22 Sep 2022 11:26:29 +0200 Subject: [PATCH 1012/2916] feat: Add --offline-kubernetes flag to seal command --- cmd/kluctl/commands/cmd_seal.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/cmd/kluctl/commands/cmd_seal.go b/cmd/kluctl/commands/cmd_seal.go index 83ebbe68a..6b3d9e9ff 100644 --- a/cmd/kluctl/commands/cmd_seal.go +++ b/cmd/kluctl/commands/cmd_seal.go @@ -18,8 +18,9 @@ type sealCmd struct { args.ProjectFlags args.TargetFlags - ForceReseal bool `group:"misc" help:"Lets kluctl ignore secret hashes found in already sealed secrets and thus forces resealing of those."` - CertFile string `group:"misc" help:"Use the given certificate for sealing instead of requesting it from the sealed-secrets controller"` + ForceReseal bool `group:"misc" help:"Lets kluctl ignore secret hashes found in already sealed secrets and thus forces resealing of those."` + CertFile string `group:"misc" help:"Use the given certificate for sealing instead of requesting it from the sealed-secrets controller"` + OfflineKubernetes bool `group:"misc" help:"Run seal in offline mode, meaning that it will not try to connect the target cluster"` } func (cmd *sealCmd) Help() string { @@ -41,9 +42,10 @@ func (cmd *sealCmd) runCmdSealForTarget(ctx context.Context, p *kluctl_project.L } ptArgs := projectTargetCommandArgs{ - projectFlags: cmd.ProjectFlags, - targetFlags: cmd.TargetFlags, - forSeal: true, + projectFlags: cmd.ProjectFlags, + targetFlags: cmd.TargetFlags, + forSeal: true, + offlineKubernetes: cmd.OfflineKubernetes, } ptArgs.targetFlags.Target = targetName @@ -113,6 +115,10 @@ func (cmd *sealCmd) loadCert(ctx *commandCtx) (*rsa.PublicKey, error) { } return cert, nil } else { + if ctx.targetCtx.SharedContext.K == nil { + return nil, fmt.Errorf("must specify certFile when sealing in offline mode") + } + secretsConfig := ctx.targetCtx.KluctlProject.Config.SecretsConfig var sealedSecretsConfig *types.GlobalSealedSecretsConfig if secretsConfig != nil { From 74ae3f7bb04ab18a9deb185911a79357e7f32750 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 22 Sep 2022 11:43:27 +0200 Subject: [PATCH 1013/2916] feat: Write certificate hash to sealed secret and re-seal when it changes --- cmd/kluctl/commands/cmd_seal.go | 4 ++-- pkg/seal/fetch_cert.go | 13 ++++--------- pkg/seal/sealer.go | 34 ++++++++++++++++++++++++++++++--- 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/cmd/kluctl/commands/cmd_seal.go b/cmd/kluctl/commands/cmd_seal.go index 6b3d9e9ff..636444179 100644 --- a/cmd/kluctl/commands/cmd_seal.go +++ b/cmd/kluctl/commands/cmd_seal.go @@ -2,7 +2,7 @@ package commands import ( "context" - "crypto/rsa" + "crypto/x509" "fmt" securejoin "github.com/cyphar/filepath-securejoin" "github.com/kluctl/kluctl/v2/cmd/kluctl/args" @@ -84,7 +84,7 @@ func (cmd *sealCmd) runCmdSealForTarget(ctx context.Context, p *kluctl_project.L }) } -func (cmd *sealCmd) loadCert(ctx *commandCtx) (*rsa.PublicKey, error) { +func (cmd *sealCmd) loadCert(ctx *commandCtx) (*x509.Certificate, error) { sealingConfig := ctx.targetCtx.Target.SealingConfig var certFile string diff --git a/pkg/seal/fetch_cert.go b/pkg/seal/fetch_cert.go index 900dc0369..07fd5e7c0 100644 --- a/pkg/seal/fetch_cert.go +++ b/pkg/seal/fetch_cert.go @@ -2,7 +2,7 @@ package seal import ( "context" - "crypto/rsa" + "crypto/x509" "errors" "fmt" "github.com/kluctl/kluctl/v2/pkg/k8s" @@ -13,7 +13,7 @@ import ( "k8s.io/client-go/util/cert" ) -func FetchCert(ctx context.Context, k *k8s.K8sCluster, namespace string, controllerName string) (*rsa.PublicKey, error) { +func FetchCert(ctx context.Context, k *k8s.K8sCluster, namespace string, controllerName string) (*x509.Certificate, error) { certData, err := openCertFromController(k, namespace, controllerName) if err != nil { if controllerName == "sealed-secrets-controller" { @@ -92,7 +92,7 @@ func getServicePortName(k *k8s.K8sCluster, namespace, serviceName string) (strin return n, nil } -func ParseCert(data []byte) (*rsa.PublicKey, error) { +func ParseCert(data []byte) (*x509.Certificate, error) { certs, err := cert.ParseCertsPEM(data) if err != nil { return nil, err @@ -103,10 +103,5 @@ func ParseCert(data []byte) (*rsa.PublicKey, error) { return nil, errors.New("failed to read any certificates") } - cert, ok := certs[0].PublicKey.(*rsa.PublicKey) - if !ok { - return nil, fmt.Errorf("fxpected RSA public key but found %v", certs[0].PublicKey) - } - - return cert, nil + return certs[0], nil } diff --git a/pkg/seal/sealer.go b/pkg/seal/sealer.go index d6c31391e..268009cc6 100644 --- a/pkg/seal/sealer.go +++ b/pkg/seal/sealer.go @@ -4,6 +4,8 @@ import ( "context" "crypto/rand" "crypto/rsa" + "crypto/sha256" + "crypto/x509" "encoding/base64" "encoding/hex" "fmt" @@ -21,20 +23,36 @@ import ( ) const hashAnnotation = "kluctl.io/sealedsecret-hashes" +const certHashAnnotation = "kluctl.io/sealedsecret-cert-hash" type Sealer struct { ctx context.Context forceReseal bool - cert *rsa.PublicKey + cert *x509.Certificate + pubKey *rsa.PublicKey + certHash string } -func NewSealer(ctx context.Context, cert *rsa.PublicKey, forceReseal bool) (*Sealer, error) { +func NewSealer(ctx context.Context, cert *x509.Certificate, forceReseal bool) (*Sealer, error) { s := &Sealer{ ctx: ctx, forceReseal: forceReseal, cert: cert, } + pk, ok := cert.PublicKey.(*rsa.PublicKey) + if !ok { + return nil, fmt.Errorf("expected RSA public key but found %v", cert.PublicKey) + } + s.pubKey = pk + + pkBytes, err := x509.MarshalPKIXPublicKey(cert.PublicKey) + if err != nil { + return nil, err + } + h := sha256.Sum256(pkBytes) + s.certHash = hex.EncodeToString(h[:]) + return s, nil } @@ -69,7 +87,8 @@ func encryptionLabel(namespace string, name string, scope string) []byte { } func (s *Sealer) encryptSecret(secret []byte, secretName string, secretNamespace string, scope string) (string, error) { - b, err := crypto.HybridEncrypt(rand.Reader, s.cert, secret, encryptionLabel(secretNamespace, secretName, scope)) + // todo + b, err := crypto.HybridEncrypt(rand.Reader, s.pubKey, secret, encryptionLabel(secretNamespace, secretName, scope)) if err != nil { return "", err } @@ -122,6 +141,7 @@ func (s *Sealer) SealFile(p string, targetFile string) error { var existingContent *uo.UnstructuredObject var existingHashes *uo.UnstructuredObject + var existingCertHash string if utils.Exists(targetFile) { existingContent, err = uo.FromFile(targetFile) @@ -129,6 +149,10 @@ func (s *Sealer) SealFile(p string, targetFile string) error { if a != nil { existingHashes, _ = uo.FromString(*a) } + a = existingContent.GetK8sAnnotation(certHashAnnotation) + if a != nil { + existingCertHash = *a + } } if existingHashes == nil { existingHashes = uo.New() @@ -190,6 +214,9 @@ func (s *Sealer) SealFile(p string, targetFile string) error { if s.forceReseal { resealAll = true status.Info(s.ctx, "Forcing reseal of secrets in %s", secretName) + } else if existingCertHash != s.certHash { + resealAll = true + status.Info(s.ctx, "Cert for secret %s has changed, forcing reseal", secretName) } for k, v := range secrets { @@ -228,6 +255,7 @@ func (s *Sealer) SealFile(p string, targetFile string) error { return err } result.SetK8sAnnotation(hashAnnotation, resultSecretHashesStr) + result.SetK8sAnnotation(certHashAnnotation, s.certHash) if reflect.DeepEqual(existingContent, result) { status.Info(s.ctx, "Skipped %s as it did not change", baseName) From 738b2c834d3f768fd45203383369d6f2b3001791 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 22 Sep 2022 11:49:20 +0200 Subject: [PATCH 1014/2916] chore: Run go mod tidy --- go.mod | 2 +- go.sum | 80 ++-------------------------------------------------------- 2 files changed, 3 insertions(+), 79 deletions(-) diff --git a/go.mod b/go.mod index b36010d39..2832f1384 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/bitnami-labs/sealed-secrets v0.18.2 github.com/cyphar/filepath-securejoin v0.2.3 github.com/docker/distribution v2.8.1+incompatible - github.com/evanphx/json-patch v5.6.0+incompatible + github.com/evanphx/json-patch v5.6.0+incompatible // indirect github.com/fluxcd/pkg/kustomize v0.7.0 github.com/go-git/go-git/v5 v5.4.2 github.com/go-playground/validator/v10 v10.11.1 diff --git a/go.sum b/go.sum index 5851700e6..b2e3c6035 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,6 @@ cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvf cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute v1.9.0 h1:ED/FP4xv8GJw63v556/ASNc1CeeLUO2Bs8nzaHchkHg= -cloud.google.com/go/compute v1.9.0/go.mod h1:lWv1h/zUWTm/LozzfTJhBSkd6ShQq8la8VeeuOEGxfY= cloud.google.com/go/compute v1.10.0 h1:aoLIYaA1fX3ywihqpBk2APQKOo20nXsp1GEZQbx5Jk4= cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= @@ -93,12 +91,10 @@ github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy86 github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/sprig v2.15.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= -github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= github.com/Masterminds/sprig/v3 v3.2.0/go.mod h1:tWhwTbUTndesPNeF0C900vKoq283u6zp4APT9vaF3SI= github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= @@ -137,8 +133,6 @@ github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-metrics v0.4.0 h1:yCQqn7dwca4ITXb+CbubHmedzaQYHhNhrEXLYUeEe8Q= -github.com/armon/go-metrics v0.4.0/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= @@ -153,8 +147,6 @@ github.com/ashanbrown/makezero v0.0.0-20210520155254-b6261585ddde/go.mod h1:oG9D github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.36.30/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= -github.com/aws/aws-sdk-go v1.44.89 h1:Xf5Pp9GsNSMRinAuWNiQd0vusXXb3IgYbNlxldhWS2Q= -github.com/aws/aws-sdk-go v1.44.89/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aws/aws-sdk-go v1.44.103 h1:tbhBHKgiZSIUkG8FcHy3wYKpPVvp65Wn7ZiX0B8phpY= github.com/aws/aws-sdk-go v1.44.103/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -180,7 +172,6 @@ github.com/bwesterb/go-ristretto v1.2.1/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7N github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= @@ -237,14 +228,10 @@ github.com/denis-tingajkin/go-header v0.4.2/go.mod h1:eLRHAVXzE5atsKAnNRDB90WHCF github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/distribution/distribution/v3 v3.0.0-20220526142353-ffbd94cbe269 h1:hbCT8ZPPMqefiAWD2ZKjn7ypokIGViTvBBg/ExLSdCk= -github.com/docker/cli v20.10.17+incompatible h1:eO2KS7ZFeov5UJeaDmIs1NFEDRf32PaqRpvoEkKBy5M= -github.com/docker/cli v20.10.17+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v20.10.18+incompatible h1:f/GQLsVpo10VvToRay2IraVA1wHz9KktZyjev3SIVDU= github.com/docker/cli v20.10.18+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v20.10.17+incompatible h1:JYCuMrWaVNophQTOrMMoSwudOVEfcegoZZrleKc1xwE= -github.com/docker/docker v20.10.17+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v20.10.18+incompatible h1:SN84VYXTBNGn92T/QwIRPlum9zfemfitN7pbsp26WSc= github.com/docker/docker v20.10.18+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.6.4 h1:axCks+yV+2MR3/kZhAmy07yC56WZ2Pwu/fKWtKuZB0o= @@ -351,8 +338,6 @@ github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= -github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw= -github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= @@ -466,8 +451,6 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-containerregistry v0.11.0 h1:Xt8x1adcREjFcmDoDK8OdOsjxu90PHkGuwNP8GiHMLM= @@ -557,8 +540,6 @@ github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9n github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v1.3.0 h1:G0ACM8Z2WilWgPv3Vdzwm3V0BQu/kSmrkVtpe1fy9do= -github.com/hashicorp/go-hclog v1.3.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-hclog v1.3.1 h1:vDwF1DFNZhntP4DAjuTpOw3uEgMUpXh1pB5fW9DqHpo= github.com/hashicorp/go-hclog v1.3.1/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= @@ -609,12 +590,8 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= -github.com/hashicorp/vault/api v1.7.2 h1:kawHE7s/4xwrdKbkmwQi0wYaIeUhk5ueek7ljuezCVQ= -github.com/hashicorp/vault/api v1.7.2/go.mod h1:xbfA+1AvxFseDzxxdWaL0uO99n1+tndus4GCrtouy0M= github.com/hashicorp/vault/api v1.8.0 h1:7765sW1XBt+qf4XKIYE4ebY9qc/yi9V2/egzGSUNMZU= github.com/hashicorp/vault/api v1.8.0/go.mod h1:uJrw6D3y9Rv7hhmS17JQC50jbPDAZdjZoTtrCCxxs7E= -github.com/hashicorp/vault/sdk v0.5.3 h1:PWY8sq/9pRrK9vUIy75qCH2Jd8oeENAgkaa/qbhzFrs= -github.com/hashicorp/vault/sdk v0.5.3/go.mod h1:DoGraE9kKGNcVgPmTuX357Fm6WAx1Okvde8Vp3dPDoU= github.com/hashicorp/vault/sdk v0.6.0 h1:6Z+In5DXHiUfZvIZdMx7e2loL1PPyDjA4bVh9ZTIAhs= github.com/hashicorp/vault/sdk v0.6.0/go.mod h1:+DRpzoXIdMvKc88R4qxr+edwy/RvH5QK8itmxLiDHLc= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= @@ -690,8 +667,6 @@ github.com/kisielk/errcheck v1.6.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= -github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/klauspost/compress v1.15.10 h1:Ai8UzuomSCDw90e1qNMtb15msBXsNpH6gzkkENQNcJo= github.com/klauspost/compress v1.15.10/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= github.com/kluctl/go-embed-python v0.0.0-3.10.6-20220906-2 h1:zjJAVyZiWJhufcafhykvXNar1vqDp/ukQk0hFuCJ9zw= @@ -732,8 +707,6 @@ github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs= -github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= @@ -782,14 +755,12 @@ github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= -github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.14 h1:qZgc/Rwetq+MtyE18WhzjokPD93dNqLGNT3QJuLvBGw= github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -805,7 +776,6 @@ github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WT github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= -github.com/mitchellh/cli v1.1.2/go.mod h1:6iaV0fGdElS6dPBx0EApTxHrcWvmJphyh2n8YBLPPZ4= github.com/mitchellh/cli v1.1.4/go.mod h1:vTLESy5mRhKOs9KDp0/RATawxP1UqBmdrpVRMnpcvKQ= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= @@ -882,11 +852,10 @@ github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo/v2 v2.1.4 h1:GNapqRSid3zijZ9H77KrgVG4/8KqiyRsxcSxe+7ApXY= +github.com/onsi/ginkgo/v2 v2.1.6 h1:Fx2POJZfKRQcM1pH49qSZiYeu319wji004qX+GDovrU= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/onsi/gomega v1.20.0 h1:8W0cWlwFkflGPLltQvLRB7ZVD5HuP6ng320w2IS245Q= github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= @@ -974,8 +943,6 @@ github.com/quasilyte/regex/syntax v0.0.0-20200407221936-30656e2c4a95/go.mod h1:r github.com/r3labs/diff/v2 v2.15.1 h1:EOrVqPUzi+njlumoqJwiS/TgGgmZo83619FNDB9xQUg= github.com/r3labs/diff/v2 v2.15.1/go.mod h1:I8noH9Fc2fjSaMxqF3G2lhDdC0b+JXCfyx85tWFM9kc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.3.4 h1:3Z3Eu6FGHZWSfNKJTOUiPatWwfc7DzJRU04jFUqJODw= -github.com/rivo/uniseg v0.3.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8= github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= @@ -987,8 +954,6 @@ github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6po github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= -github.com/rubenv/sql-migrate v1.1.2 h1:9M6oj4e//owVVHYrFISmY9LBRw6gzkCNmD9MV36tZeQ= -github.com/rubenv/sql-migrate v1.1.2/go.mod h1:/7TZymwxN8VWumcIxw1jjHEcR1djpdkMHQPT4FWdnbQ= github.com/rubenv/sql-migrate v1.2.0 h1:fOXMPLMd41sK7Tg75SXDec15k3zg5WNV6SjuDRiNfcU= github.com/rubenv/sql-migrate v1.2.0/go.mod h1:Z5uVnq7vrIrPmHbVFfR4YLHRZquxeHpckCnRq0P/K9Y= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= @@ -1058,8 +1023,6 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/spf13/viper v1.9.0/go.mod h1:+i6ajR7OX2XaiBkrcZJFK21htRk7eDeLg7+O6bhUPP4= -github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= -github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= github.com/spf13/viper v1.13.0 h1:BWSJ/M+f+3nmdz9bxB+bWX28kkALN2ok11D0rSo8EJU= github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw= github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= @@ -1202,8 +1165,6 @@ golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= -golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220919173607-35f4265a4bc0 h1:a5Yg6ylndHHYJqIPrdq0AhvR6KTvDTAvgBtaidhEevY= golang.org/x/crypto v0.0.0-20220919173607-35f4265a4bc0/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1298,8 +1259,6 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b h1:ZmngSVLe/wycRns9MKikG9OWIEjGcGAkacif7oYQaUY= -golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20220921203646-d300de134e69 h1:hUJpGDpnfwdJW8iNypFjmSY0sCBEL+spFTZ2eO+Sfps= golang.org/x/net v0.0.0-20220921203646-d300de134e69/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1319,8 +1278,6 @@ golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094 h1:2o1E+E8TpNLklK9nHiPiK1uzIYrIHt+cQx3ynCwq9V8= -golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 h1:lxqLZaMad/dJHMFZH0NiNpiEZI/nhgWhe4wgzpE+MuA= golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1335,8 +1292,6 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde h1:ejfdSekXMDxDLbRrJMwUk6KnSLZ2McaUCVcIKM+N6jc= -golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220907140024-f12130a52804 h1:0SH2R3f1b1VmIMG7BXbEZCBUu2dKmHschSmjqGUrW8A= golang.org/x/sync v0.0.0-20220907140024-f12130a52804/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1431,15 +1386,11 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 h1:v6hYoSR9T5oet+pMXwUWkbiVqx/63mlHjefrHmxwfeY= -golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 h1:h+EGohizhe9XlX18rfpa8k8RAc5XyaeamM+0VHRd4lc= golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 h1:Q5284mrmYTpACcm+eAKjKJH48BBwSyfJqmmGDTtT8Vc= -golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220919170432-7a66f970e087 h1:tPwmk4vmvVCMdr98VgL4JH+qZxPL8fqlUOHnyOM8N3w= golang.org/x/term v0.0.0-20220919170432-7a66f970e087/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1457,8 +1408,6 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 h1:ftMN5LMiBFjbzleLqtoBZk7KdJwhuybIU+FckUHgoyQ= -golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220920022843-2ce7c2934d45 h1:yuLAip3bfURHClMG9VBdzPrQvCWjWiWUTBGV+/fCbUs= golang.org/x/time v0.0.0-20220920022843-2ce7c2934d45/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1667,8 +1616,6 @@ google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwy google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf h1:Q5xNKbTSFwkuaaGaR7CMcXEM5sy19KYdUU8iF8/iRC0= -google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= google.golang.org/genproto v0.0.0-20220921223823-23cae91e6737 h1:K1zaaMdYBXRyX+cwFnxj7M6zwDyumLQMZ5xqwGvjreQ= google.golang.org/genproto v0.0.0-20220921223823-23cae91e6737/go.mod h1:2r/26NEF3bFmT3eC3aZreahSal0C3Shl8Gi6vyDYqOQ= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= @@ -1761,8 +1708,6 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= -helm.sh/helm/v3 v3.9.4 h1:TCI1QhJUeLVOdccfdw+vnSEO3Td6gNqibptB04QtExY= -helm.sh/helm/v3 v3.9.4/go.mod h1:3eaWAIqzvlRSD06gR9MMwmp2KBKwlu9av1/1BZpjeWY= helm.sh/helm/v3 v3.10.0 h1:y/MYONZ/bsld9kHwqgBX2uPggnUr5hahpjwt9/jrHlI= helm.sh/helm/v3 v3.10.0/go.mod h1:paPw0hO5KVfrCMbi1M8+P8xdfBri3IiJiVKATZsFR94= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1773,44 +1718,25 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.2.1/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY= -k8s.io/api v0.25.0 h1:H+Q4ma2U/ww0iGB78ijZx6DRByPz6/733jIuFpX70e0= -k8s.io/api v0.25.0/go.mod h1:ttceV1GyV1i1rnmvzT3BST08N6nGt+dudGrquzVQWPk= k8s.io/api v0.25.2 h1:v6G8RyFcwf0HR5jQGIAYlvtRNrxMJQG1xJzaSeVnIS8= k8s.io/api v0.25.2/go.mod h1:qP1Rn4sCVFwx/xIhe+we2cwBLTXNcheRyYXwajonhy0= -k8s.io/apiextensions-apiserver v0.25.0 h1:CJ9zlyXAbq0FIW8CD7HHyozCMBpDSiH7EdrSTCZcZFY= -k8s.io/apiextensions-apiserver v0.25.0/go.mod h1:3pAjZiN4zw7R8aZC5gR0y3/vCkGlAjCazcg1me8iB/E= k8s.io/apiextensions-apiserver v0.25.2 h1:8uOQX17RE7XL02ngtnh3TgifY7EhekpK+/piwzQNnBo= k8s.io/apiextensions-apiserver v0.25.2/go.mod h1:iRwwRDlWPfaHhuBfQ0WMa5skdQfrE18QXJaJvIDLvE8= -k8s.io/apimachinery v0.25.0 h1:MlP0r6+3XbkUG2itd6vp3oxbtdQLQI94fD5gCS+gnoU= -k8s.io/apimachinery v0.25.0/go.mod h1:qMx9eAk0sZQGsXGu86fab8tZdffHbwUfsvzqKn4mfB0= k8s.io/apimachinery v0.25.2 h1:WbxfAjCx+AeN8Ilp9joWnyJ6xu9OMeS/fsfjK/5zaQs= k8s.io/apimachinery v0.25.2/go.mod h1:hqqA1X0bsgsxI6dXsJ4HnNTBOmJNxyPp8dw3u2fSHwA= -k8s.io/apiserver v0.25.0 h1:8kl2ifbNffD440MyvHtPaIz1mw4mGKVgWqM0nL+oyu4= -k8s.io/apiserver v0.25.0/go.mod h1:BKwsE+PTC+aZK+6OJQDPr0v6uS91/HWxX7evElAH6xo= k8s.io/apiserver v0.25.2 h1:YePimobk187IMIdnmsMxsfIbC5p4eX3WSOrS9x6FEYw= k8s.io/apiserver v0.25.2/go.mod h1:30r7xyQTREWCkG2uSjgjhQcKVvAAlqoD+YyrqR6Cn+I= -k8s.io/cli-runtime v0.25.0 h1:XBnTc2Fi+w818jcJGzhiJKQuXl8479sZ4FhtV5hVJ1Q= -k8s.io/cli-runtime v0.25.0/go.mod h1:bHOI5ZZInRHhbq12OdUiYZQN8ml8aKZLwQgt9QlLINw= k8s.io/cli-runtime v0.25.2 h1:XOx+SKRjBpYMLY/J292BHTkmyDffl/qOx3YSuFZkTuc= k8s.io/cli-runtime v0.25.2/go.mod h1:OQx3+/0st6x5YpkkJQlEWLC73V0wHsOFMC1/roxV8Oc= -k8s.io/client-go v0.25.0 h1:CVWIaCETLMBNiTUta3d5nzRbXvY5Hy9Dpl+VvREpu5E= -k8s.io/client-go v0.25.0/go.mod h1:lxykvypVfKilxhTklov0wz1FoaUZ8X4EwbhS6rpRfN8= k8s.io/client-go v0.25.2 h1:SUPp9p5CwM0yXGQrwYurw9LWz+YtMwhWd0GqOsSiefo= k8s.io/client-go v0.25.2/go.mod h1:i7cNU7N+yGQmJkewcRD2+Vuj4iz7b30kI8OcL3horQ4= -k8s.io/component-base v0.25.0 h1:haVKlLkPCFZhkcqB6WCvpVxftrg6+FK5x1ZuaIDaQ5Y= -k8s.io/component-base v0.25.0/go.mod h1:F2Sumv9CnbBlqrpdf7rKZTmmd2meJq0HizeyY/yAFxk= k8s.io/component-base v0.25.2 h1:Nve/ZyHLUBHz1rqwkjXm/Re6IniNa5k7KgzxZpTfSQY= k8s.io/component-base v0.25.2/go.mod h1:90W21YMr+Yjg7MX+DohmZLzjsBtaxQDDwaX4YxDkl60= -k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/klog/v2 v2.70.1 h1:7aaoSdahviPmR+XkS7FyxlkkXs6tHISSG03RxleQAVQ= -k8s.io/klog/v2 v2.70.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20220803164354-a70c9af30aea h1:3QOH5+2fGsY8e1qf+GIFpg+zw/JGNrgyZRQR7/m6uWg= k8s.io/kube-openapi v0.0.0-20220803164354-a70c9af30aea/go.mod h1:C/N6wCaBHeBHkHUesQOQy2/MZqGgMAFPqGsGQLdbZBU= -k8s.io/kubectl v0.25.0 h1:/Wn1cFqo8ik3iee1EvpxYre3bkWsGLXzLQI6uCCAkQc= -k8s.io/kubectl v0.25.0/go.mod h1:n16ULWsOl2jmQpzt2o7Dud1t4o0+Y186ICb4O+GwKAU= k8s.io/kubectl v0.25.2 h1:2993lTeVimxKSWx/7z2PiJxUILygRa3tmC4QhFaeioA= k8s.io/kubectl v0.25.2/go.mod h1:eoBGJtKUj7x38KXelz+dqVtbtbKwCqyKzJWmBHU0prg= k8s.io/utils v0.0.0-20220823124924-e9cbc92d1a73 h1:H9TCJUUx+2VA0ZiD9lvtaX8fthFsMoD+Izn93E/hm8U= @@ -1826,8 +1752,6 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/kind v0.14.0 h1:cNmI3jGBvp7UegEGbC5we8plDtCUmaNRL+bod7JoSCE= -sigs.k8s.io/kind v0.14.0/go.mod h1:UrFRPHG+2a5j0Q7qiR4gtJ4rEyn8TuMQwuOPf+m4oHg= sigs.k8s.io/kind v0.15.0 h1:Fskj234L4hjQlsScCgeYvCBIRt06cjLzc7+kbr1u8Tg= sigs.k8s.io/kind v0.15.0/go.mod h1:cKTqagdRyUQmihhBOd+7p43DpOPRn9rHsUC08K1Jbsk= sigs.k8s.io/kustomize/api v0.12.1 h1:7YM7gW3kYBwtKvoY216ZzY+8hM+lV53LUayghNRJ0vM= From 26ebb324ca8f9bfaf1fa1b9f78066d9d2ab82aa8 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 22 Sep 2022 12:25:17 +0200 Subject: [PATCH 1015/2916] fix: Upgrade go-jinja2 to fix relative includes of ../ files --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 2832f1384..9aa4aa26d 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/jinzhu/copier v0.3.5 github.com/kevinburke/ssh_config v1.2.0 github.com/kluctl/go-embed-python v0.0.0-3.10.6-20220906-2 - github.com/kluctl/go-jinja2 v0.0.0-20220922065727-cf67f5b7f26d + github.com/kluctl/go-jinja2 v0.0.0-20220922102415-424914b4142b github.com/mattn/go-isatty v0.0.16 github.com/mitchellh/go-ps v1.0.0 github.com/mitchellh/reflectwalk v1.0.2 diff --git a/go.sum b/go.sum index b2e3c6035..40c565407 100644 --- a/go.sum +++ b/go.sum @@ -671,8 +671,8 @@ github.com/klauspost/compress v1.15.10 h1:Ai8UzuomSCDw90e1qNMtb15msBXsNpH6gzkkEN github.com/klauspost/compress v1.15.10/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= github.com/kluctl/go-embed-python v0.0.0-3.10.6-20220906-2 h1:zjJAVyZiWJhufcafhykvXNar1vqDp/ukQk0hFuCJ9zw= github.com/kluctl/go-embed-python v0.0.0-3.10.6-20220906-2/go.mod h1:uekQDAlvKAYJWuDxxP1SGn1vTPUfoBwMS+aB6kR+v+o= -github.com/kluctl/go-jinja2 v0.0.0-20220922065727-cf67f5b7f26d h1:pfyMHiDIYskxhJFlh1u1Xi3qHKGzg8Vna5hsRm7Cz0I= -github.com/kluctl/go-jinja2 v0.0.0-20220922065727-cf67f5b7f26d/go.mod h1:8deI9sssmQf1CiCnyRJoBgfIw4Hh3+WsHmmO9+xIIO0= +github.com/kluctl/go-jinja2 v0.0.0-20220922102415-424914b4142b h1:4fBKK0PZ8e/DC4GqM6RrUSSE2J6SlVTKNPb8awYxb5g= +github.com/kluctl/go-jinja2 v0.0.0-20220922102415-424914b4142b/go.mod h1:8deI9sssmQf1CiCnyRJoBgfIw4Hh3+WsHmmO9+xIIO0= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= From 52ca9acce842256386fccce6817fa1e3186723ed Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 22 Sep 2022 13:43:21 +0200 Subject: [PATCH 1016/2916] chore: Remove archive command from issue template --- .github/ISSUE_TEMPLATE/FEATURE.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/FEATURE.yml b/.github/ISSUE_TEMPLATE/FEATURE.yml index 44c5f6875..79ca1bca3 100644 --- a/.github/ISSUE_TEMPLATE/FEATURE.yml +++ b/.github/ISSUE_TEMPLATE/FEATURE.yml @@ -16,7 +16,6 @@ body: label: Command description: Which command will be affected by the feature request? (If you want to propose a new command, just leave all unchecked) options: - - label: "archive" - label: "check-image-updates" - label: "delete" - label: "deploy" From 8606733482c694fd5f3d706130d877ed9e93cc30 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 22 Sep 2022 14:03:50 +0200 Subject: [PATCH 1017/2916] fix: Enable colored output in windows --- cmd/kluctl/commands/root.go | 12 ++++++--- pkg/status/status_handler.go | 49 ++++++++++++++++++++++++------------ pkg/status/utils.go | 13 ---------- 3 files changed, 41 insertions(+), 33 deletions(-) diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index 8c0f31a30..3fabd1561 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -24,6 +24,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils/versions" "github.com/kluctl/kluctl/v2/pkg/version" "github.com/kluctl/kluctl/v2/pkg/yaml" + "github.com/mattn/go-colorable" "github.com/mattn/go-isatty" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -43,6 +44,7 @@ const latestReleaseUrl = "https://api.github.com/repos/kluctl/kluctl/releases/la type cli struct { Debug bool `group:"global" help:"Enable debug logging"` NoUpdateCheck bool `group:"global" help:"Disable update check on startup"` + NoColor bool `group:"global" help:"Disable colored output"` CpuProfile string `group:"global" help:"Enable CPU profiling and write the result to the given path"` @@ -74,7 +76,7 @@ var flagGroups = []groupInfo{ var cliCtx = context.Background() var didSetupStatusHandler bool -func setupStatusHandler(debug bool) { +func setupStatusHandler(debug bool, noColor bool) { didSetupStatusHandler = true origStderr := os.Stderr @@ -83,7 +85,7 @@ func setupStatusHandler(debug bool) { isTerminal := isatty.IsTerminal(os.Stderr.Fd()) var sh status.StatusHandler if !debug && isatty.IsTerminal(os.Stderr.Fd()) { - sh = status.NewMultiLineStatusHandler(cliCtx, os.Stderr, isTerminal, false) + sh = status.NewMultiLineStatusHandler(cliCtx, os.Stderr, isTerminal, !noColor, false) } else { sh = status.NewSimpleStatusHandler(func(message string) { _, _ = fmt.Fprintf(origStderr, "%s\n", message) @@ -187,7 +189,7 @@ func (c *cli) preRun() error { if err != nil { return err } - setupStatusHandler(c.Debug) + setupStatusHandler(c.Debug, c.NoColor) c.checkNewVersion() return nil } @@ -214,6 +216,8 @@ func initViper() { } func Execute() { + colorable.EnableColorsStdout(nil) + root := cli{} rootCmd, err := buildRootCobraCmd(&root, "kluctl", "Deploy and manage complex deployments on Kubernetes", @@ -241,7 +245,7 @@ composed of multiple smaller parts (Helm/Kustomize/...) in a manageable and unif err = rootCmd.ExecuteContext(cliCtx) if !didSetupStatusHandler { - setupStatusHandler(false) + setupStatusHandler(false, true) } if cpuProfileFile != nil { diff --git a/pkg/status/status_handler.go b/pkg/status/status_handler.go index 3f10a4763..a55075de4 100644 --- a/pkg/status/status_handler.go +++ b/pkg/status/status_handler.go @@ -12,10 +12,11 @@ import ( ) type MultiLineStatusHandler struct { - ctx context.Context - out io.Writer - isTerminal bool - trace bool + ctx context.Context + out io.Writer + isTerminal bool + enableColor bool + trace bool ml *multiline.MultiLinePrinter } @@ -33,12 +34,13 @@ type statusLine struct { mutex sync.Mutex } -func NewMultiLineStatusHandler(ctx context.Context, out io.Writer, isTerminal bool, trace bool) *MultiLineStatusHandler { +func NewMultiLineStatusHandler(ctx context.Context, out io.Writer, isTerminal bool, enableColor bool, trace bool) *MultiLineStatusHandler { sh := &MultiLineStatusHandler{ - ctx: ctx, - out: out, - isTerminal: isTerminal, - trace: trace, + ctx: ctx, + out: out, + isTerminal: isTerminal, + enableColor: enableColor, + trace: trace, } sh.start() @@ -88,6 +90,21 @@ func (s *MultiLineStatusHandler) startStatus(total int, message string, barOverr return sl } +func (s *MultiLineStatusHandler) withColor(c string, txt string) string { + if !s.isTerminal || !s.enableColor { + return txt + } + switch c { + case "red": + c = "\x1b[31m" + case "green": + c = "\x1b[32m" + case "yellow": + c = "\x1b[33m" + } + return fmt.Sprintf("%s%s\x1b[0m", c, txt) +} + func (s *MultiLineStatusHandler) printLine(message string, barOverride string, doFlush bool) { s.ml.NewTopLine(func() string { return fmt.Sprintf("%s %s", barOverride, message) @@ -98,7 +115,7 @@ func (s *MultiLineStatusHandler) printLine(message string, barOverride string, d } func (s *MultiLineStatusHandler) Info(message string) { - o := withColor("green", "ⓘ") + o := s.withColor("green", "ⓘ") s.printLine(message, o, true) } @@ -107,12 +124,12 @@ func (s *MultiLineStatusHandler) InfoFallback(message string) { } func (s *MultiLineStatusHandler) Warning(message string) { - o := withColor("yellow", "⚠") + o := s.withColor("yellow", "⚠") s.printLine(message, o, true) } func (s *MultiLineStatusHandler) Error(message string) { - o := withColor("red", "✗") + o := s.withColor("red", "✗") s.printLine(message, o, true) } @@ -127,7 +144,7 @@ func (s *MultiLineStatusHandler) PlainText(text string) { } func (s *MultiLineStatusHandler) Prompt(password bool, message string) (string, error) { - o := withColor("yellow", "?") + o := s.withColor("yellow", "?") sl := s.startStatus(1, message, o) defer sl.end(o) @@ -188,10 +205,10 @@ func (sl *statusLine) end(barOverride string) { func (sl *statusLine) End(result EndResult) { switch result { case EndSuccess: - sl.end(withColor("green", "✓")) + sl.end(sl.slh.withColor("green", "✓")) case EndWarning: - sl.end(withColor("yellow", "⚠")) + sl.end(sl.slh.withColor("yellow", "⚠")) case EndError: - sl.end(withColor("red", "✗")) + sl.end(sl.slh.withColor("red", "✗")) } } diff --git a/pkg/status/utils.go b/pkg/status/utils.go index 21555d432..cc5002217 100644 --- a/pkg/status/utils.go +++ b/pkg/status/utils.go @@ -2,7 +2,6 @@ package status import ( "bufio" - "fmt" "io" ) @@ -64,15 +63,3 @@ func (r *replaceRReader) Read(p []byte) (int, error) { } return written, nil } - -func withColor(c string, s string) string { - switch c { - case "red": - c = "\x1b[31m" - case "green": - c = "\x1b[32m" - case "yellow": - c = "\x1b[33m" - } - return fmt.Sprintf("%s%s\x1b[0m", c, s) -} From 7bb7747095af82c18d8a1ded6c4a9b8d08a0e9e9 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 22 Sep 2022 21:17:57 +0200 Subject: [PATCH 1018/2916] chore: Run go fmt ./... --- cmd/kluctl/commands/root.go | 2 +- main.go | 2 +- pkg/utils/kustomize.go | 8 ++++---- pkg/utils/python_scanner/scanner.go | 1 - 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index 3fabd1561..d98200898 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -5,7 +5,7 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, diff --git a/main.go b/main.go index fb256f4c4..f6ca27150 100644 --- a/main.go +++ b/main.go @@ -5,7 +5,7 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, diff --git a/pkg/utils/kustomize.go b/pkg/utils/kustomize.go index 21fbe4fe2..44a92258a 100644 --- a/pkg/utils/kustomize.go +++ b/pkg/utils/kustomize.go @@ -18,10 +18,10 @@ import ( var kustomizeBuildMutex sync.Mutex // SecureBuildKustomization wraps krusty.MakeKustomizer with the following settings: -// - secure on-disk FS denying operations outside root -// - load files from outside the kustomization dir path -// (but not outside root) -// - disable plugins except for the builtin ones +// - secure on-disk FS denying operations outside root +// - load files from outside the kustomization dir path +// (but not outside root) +// - disable plugins except for the builtin ones func SecureBuildKustomization(root, dirPath string, allowRemoteBases bool) (_ resmap.ResMap, err error) { var fs filesys.FileSystem diff --git a/pkg/utils/python_scanner/scanner.go b/pkg/utils/python_scanner/scanner.go index c6f385484..c64b1e506 100644 --- a/pkg/utils/python_scanner/scanner.go +++ b/pkg/utils/python_scanner/scanner.go @@ -60,7 +60,6 @@ func (pos Position) String() string { // // Use GoTokens to configure the Scanner such that it accepts all Go // literal tokens including Go identifiers. Comments will be skipped. -// const ( ScanIdents = 1 << -Ident ScanInts = 1 << -Int From 0682af3ac4b6553d3a63ae92b142fff223f6b24f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 26 Sep 2022 12:26:43 +0200 Subject: [PATCH 1019/2916] feat: Pass k8s client into Helm install commands This is a preparation to allow lookup in Helm charts. It will however not work before https://github.com/helm/helm/pull/9426 or a comparable solution is merged. --- pkg/deployment/helm_chart.go | 10 ++++++---- pkg/k8s/client_factory.go | 6 +++++- pkg/k8s/fake_client_factory.go | 4 ++++ pkg/k8s/k8s_cluster.go | 29 +++++++++++++++++++++++++++++ 4 files changed, 44 insertions(+), 5 deletions(-) diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index a533ba8de..2d930c4b0 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -90,13 +90,15 @@ func (c *helmChart) GetFullOutputPath() (string, error) { return securejoin.SecureJoin(dir, c.GetOutputPath()) } -func (c *helmChart) buildHelmConfig() (*action.Configuration, error) { +func (c *helmChart) buildHelmConfig(k *k8s.K8sCluster) (*action.Configuration, error) { rc, err := registry.NewClient() if err != nil { return nil, err } + return &action.Configuration{ - RegistryClient: rc, + RESTClientGetter: k, + RegistryClient: rc, }, nil } @@ -114,7 +116,7 @@ func (c *helmChart) Pull(ctx context.Context) error { targetDir := filepath.Join(filepath.Dir(c.configFile), "charts") _ = os.RemoveAll(chartDir) - cfg, err := c.buildHelmConfig() + cfg, err := c.buildHelmConfig(nil) if err != nil { return err } @@ -232,7 +234,7 @@ func (c *helmChart) doRender(ctx context.Context, k *k8s.K8sCluster) error { return err } } - cfg, err := c.buildHelmConfig() + cfg, err := c.buildHelmConfig(k) if err != nil { return err } diff --git a/pkg/k8s/client_factory.go b/pkg/k8s/client_factory.go index 6ab49a220..8480a2540 100644 --- a/pkg/k8s/client_factory.go +++ b/pkg/k8s/client_factory.go @@ -14,6 +14,7 @@ import ( ) type ClientFactory interface { + RESTConfig() *rest.Config GetCA() []byte CloseIdleConnections() @@ -28,12 +29,15 @@ type realClientFactory struct { httpClient *http.Client } +func (r *realClientFactory) RESTConfig() *rest.Config { + return r.config +} + func (r *realClientFactory) GetCA() []byte { return r.config.CAData } func (r *realClientFactory) DiscoveryClient() (discovery.DiscoveryInterface, error) { - apiHost, err := url.Parse(r.config.Host) if err != nil { return nil, err diff --git a/pkg/k8s/fake_client_factory.go b/pkg/k8s/fake_client_factory.go index b01673f27..cee966194 100644 --- a/pkg/k8s/fake_client_factory.go +++ b/pkg/k8s/fake_client_factory.go @@ -23,6 +23,10 @@ type fakeClientFactory struct { scheme *runtime.Scheme } +func (f *fakeClientFactory) RESTConfig() *rest.Config { + return nil +} + func (f *fakeClientFactory) GetCA() []byte { return []byte{} } diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index 8b58788c9..de92465ac 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -16,9 +16,11 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/version" + "k8s.io/client-go/discovery" "k8s.io/client-go/dynamic" _ "k8s.io/client-go/plugin/pkg/client/auth" "k8s.io/client-go/rest" + "k8s.io/client-go/restmapper" "sync" "time" ) @@ -449,3 +451,30 @@ func (k *K8sCluster) ProxyGet(scheme, namespace, name, port, path string, params } return ret.Stream(k.ctx) } + +func (k *K8sCluster) ToRESTConfig() (*rest.Config, error) { + return k.clientFactory.RESTConfig(), nil +} + +func (k *K8sCluster) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) { + d, err := k.clientFactory.DiscoveryClient() + if err != nil { + return nil, err + } + cd, ok := d.(discovery.CachedDiscoveryInterface) + if !ok { + return nil, fmt.Errorf("not a CachedDiscoveryInterface") + } + return cd, nil +} + +func (k *K8sCluster) ToRESTMapper() (meta.RESTMapper, error) { + discoveryClient, err := k.ToDiscoveryClient() + if err != nil { + return nil, err + } + + mapper := restmapper.NewDeferredDiscoveryRESTMapper(discoveryClient) + expander := restmapper.NewShortcutExpander(mapper, discoveryClient) + return expander, nil +} From 7e2b6b690403df4e386588c0b83e58735fd68a63 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 26 Sep 2022 13:10:45 +0200 Subject: [PATCH 1020/2916] fix: Add new-line between diff tables --- cmd/kluctl/commands/command_result.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmd/kluctl/commands/command_result.go b/cmd/kluctl/commands/command_result.go index 668e21ec2..b5702f1c7 100644 --- a/cmd/kluctl/commands/command_result.go +++ b/cmd/kluctl/commands/command_result.go @@ -38,7 +38,10 @@ func formatCommandResultText(cr *types.CommandResult) string { prettyObjectRefs(buf, refs) buf.WriteString("\n") - for _, co := range cr.ChangedObjects { + for i, co := range cr.ChangedObjects { + if i != 0 { + buf.WriteString("\n") + } prettyChanges(buf, co.Ref, co.Changes) } } From b717b2bb6d7bcc710b7ed8abde75191b80dcda20 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 26 Sep 2022 14:00:08 +0200 Subject: [PATCH 1021/2916] fix: Use custom multiline implementation uilive had issues on windows and causes partially cleared lines. --- go.mod | 9 +- go.sum | 10 +-- pkg/status/multiline/clear_lines.go | 4 + pkg/status/multiline/clear_lines_posix.go | 15 ++++ pkg/status/multiline/clear_lines_windows.go | 94 +++++++++++++++++++++ pkg/status/multiline/multiline.go | 55 +++++++----- 6 files changed, 154 insertions(+), 33 deletions(-) create mode 100644 pkg/status/multiline/clear_lines.go create mode 100644 pkg/status/multiline/clear_lines_posix.go create mode 100644 pkg/status/multiline/clear_lines_windows.go diff --git a/go.mod b/go.mod index 9aa4aa26d..8b701789b 100644 --- a/go.mod +++ b/go.mod @@ -5,18 +5,17 @@ go 1.19 require ( github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e github.com/Masterminds/semver/v3 v3.1.1 + github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d github.com/aws/aws-sdk-go v1.44.103 github.com/bitnami-labs/sealed-secrets v0.18.2 github.com/cyphar/filepath-securejoin v0.2.3 github.com/docker/distribution v2.8.1+incompatible - github.com/evanphx/json-patch v5.6.0+incompatible // indirect github.com/fluxcd/pkg/kustomize v0.7.0 github.com/go-git/go-git/v5 v5.4.2 github.com/go-playground/validator/v10 v10.11.1 github.com/gobwas/glob v0.2.3 // indirect github.com/golang-jwt/jwt/v4 v4.4.2 github.com/google/go-containerregistry v0.11.0 - github.com/gosuri/uilive v0.0.4 github.com/hashicorp/vault/api v1.8.0 github.com/hexops/gotextdiff v1.0.3 github.com/imdario/mergo v0.3.13 @@ -24,7 +23,9 @@ require ( github.com/kevinburke/ssh_config v1.2.0 github.com/kluctl/go-embed-python v0.0.0-3.10.6-20220906-2 github.com/kluctl/go-jinja2 v0.0.0-20220922102415-424914b4142b + github.com/mattn/go-colorable v0.1.13 github.com/mattn/go-isatty v0.0.16 + github.com/mattn/go-runewidth v0.0.9 github.com/mitchellh/go-ps v1.0.0 github.com/mitchellh/reflectwalk v1.0.2 github.com/ohler55/ojg v1.14.4 @@ -94,6 +95,7 @@ require ( github.com/docker/go-units v0.5.0 // indirect github.com/emicklei/go-restful/v3 v3.9.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect + github.com/evanphx/json-patch v5.6.0+incompatible // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect github.com/fatih/color v1.13.0 // indirect @@ -153,8 +155,6 @@ require ( github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/magiconair/properties v1.8.6 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-runewidth v0.0.14 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect @@ -181,7 +181,6 @@ require ( github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect - github.com/rivo/uniseg v0.4.2 // indirect github.com/rubenv/sql-migrate v1.2.0 // indirect github.com/russross/blackfriday v1.6.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect diff --git a/go.sum b/go.sum index 40c565407..4c724677b 100644 --- a/go.sum +++ b/go.sum @@ -113,6 +113,8 @@ github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895 h1:NsReiLpErI github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= +github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= +github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -515,8 +517,6 @@ github.com/gostaticanalysis/forcetypeassert v0.0.0-20200621232751-01d4955beaa5/g github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW0HU0GPE3+5PWN4A= github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= github.com/gostaticanalysis/testutil v0.4.0/go.mod h1:bLIoPefWXrRi/ssLFWX1dx7Repi5x3CuviD3dgAZaBU= -github.com/gosuri/uilive v0.0.4 h1:hUEBpQDj8D8jXgtCdBu7sWsy5sbW/5GhuO8KBwJ2jyY= -github.com/gosuri/uilive v0.0.4/go.mod h1:V/epo5LjjlDE5RJUcqx8dbw+zc93y5Ya3yg8tfZ74VI= github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= @@ -754,9 +754,8 @@ github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mN github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= -github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= @@ -942,9 +941,6 @@ github.com/quasilyte/go-ruleguard/rules v0.0.0-20210428214800-545e0d2e0bf7/go.mo github.com/quasilyte/regex/syntax v0.0.0-20200407221936-30656e2c4a95/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= github.com/r3labs/diff/v2 v2.15.1 h1:EOrVqPUzi+njlumoqJwiS/TgGgmZo83619FNDB9xQUg= github.com/r3labs/diff/v2 v2.15.1/go.mod h1:I8noH9Fc2fjSaMxqF3G2lhDdC0b+JXCfyx85tWFM9kc= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8= -github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= diff --git a/pkg/status/multiline/clear_lines.go b/pkg/status/multiline/clear_lines.go new file mode 100644 index 000000000..0b0a41c7b --- /dev/null +++ b/pkg/status/multiline/clear_lines.go @@ -0,0 +1,4 @@ +package multiline + +// ESC is the ASCII code for escape character +const ESC = 27 diff --git a/pkg/status/multiline/clear_lines_posix.go b/pkg/status/multiline/clear_lines_posix.go new file mode 100644 index 000000000..0742f106d --- /dev/null +++ b/pkg/status/multiline/clear_lines_posix.go @@ -0,0 +1,15 @@ +//go:build !windows + +package multiline + +import ( + "fmt" + "strings" +) + +// clear the line and move the cursor up +var clear = fmt.Sprintf("%c[%dA%c[2K", ESC, 1, ESC) + +func (ml *MultiLinePrinter) clearLines(lines int) { + _, _ = fmt.Fprint(ml.w, strings.Repeat(clear, lines)) +} diff --git a/pkg/status/multiline/clear_lines_windows.go b/pkg/status/multiline/clear_lines_windows.go new file mode 100644 index 000000000..b58b42fa2 --- /dev/null +++ b/pkg/status/multiline/clear_lines_windows.go @@ -0,0 +1,94 @@ +//go:build windows + +package multiline + +import ( + "fmt" + "github.com/mattn/go-isatty" + "io" + "strings" + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +var kernel32 = syscall.NewLazyDLL("kernel32.dll") + +var ( + procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") + procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition") + procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW") +) + +// clear the line and move the cursor up +var clear = fmt.Sprintf("%c[%dA%c[2K\r", ESC, 0, ESC) + +type short int16 +type dword uint32 +type word uint16 + +type coord struct { + x short + y short +} + +type smallRect struct { + left short + top short + right short + bottom short +} + +type consoleScreenBufferInfo struct { + size coord + cursorPosition coord + attributes word + window smallRect + maximumWindowSize coord +} + +// FdWriter is a writer with a file descriptor. +type FdWriter interface { + io.Writer + Fd() uintptr +} + +func (ml *MultiLinePrinter) clearLines(lines int) { + f, ok := ml.w.(FdWriter) + if ok && !isatty.IsTerminal(f.Fd()) { + ok = false + } + if !ok { + _, _ = fmt.Fprint(ml.w, strings.Repeat(clear, lines)) + return + } + fd := f.Fd() + var info windows.ConsoleScreenBufferInfo + if err := windows.GetConsoleScreenBufferInfo(windows.Handle(fd), &info); err != nil { + return + } + + info.CursorPosition.Y -= int16(lines) + if info.CursorPosition.Y < 0 { + info.CursorPosition.Y = 0 + } + _, _, _ = procSetConsoleCursorPosition.Call( + uintptr(fd), + uintptr(uint32(uint16(info.CursorPosition.Y))<<16|uint32(uint16(info.CursorPosition.X))), + ) + + // clear the lines + cursor := &windows.Coord{ + X: info.Window.Left, + Y: info.CursorPosition.Y, + } + count := uint32(info.Size.X) * uint32(lines) + _, _, _ = procFillConsoleOutputCharacter.Call( + uintptr(fd), + uintptr(' '), + uintptr(count), + *(*uintptr)(unsafe.Pointer(cursor)), + uintptr(unsafe.Pointer(new(uint32))), + ) +} diff --git a/pkg/status/multiline/multiline.go b/pkg/status/multiline/multiline.go index f2a67889e..9ecce1d7c 100644 --- a/pkg/status/multiline/multiline.go +++ b/pkg/status/multiline/multiline.go @@ -1,9 +1,10 @@ package multiline import ( - "bytes" "fmt" - "github.com/gosuri/uilive" + "github.com/acarl005/stripansi" + "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/mattn/go-runewidth" "io" "sync" "time" @@ -13,8 +14,8 @@ type MultiLinePrinter struct { topLines []*Line lines []*Line - uil *uilive.Writer - lastWritten []byte + w io.Writer + prevLines []string ticker *time.Ticker tdone chan bool @@ -30,11 +31,9 @@ type Line struct { } func (ml *MultiLinePrinter) Start(w io.Writer) { - ml.uil = uilive.New() - ml.uil.Out = w - ml.uil.Start() + ml.w = w - ml.ticker = time.NewTicker(100 * time.Millisecond) + ml.ticker = time.NewTicker(1 * time.Millisecond) ml.tdone = make(chan bool) go ml.loop() @@ -44,7 +43,6 @@ func (ml *MultiLinePrinter) Stop() { ml.Flush() ml.tdone <- true <-ml.tdone - ml.uil.Stop() } func (ml *MultiLinePrinter) loop() { @@ -69,27 +67,42 @@ func (ml *MultiLinePrinter) Flush() { ml.mutex.Lock() defer ml.mutex.Unlock() - hadTopLines := false + tw := utils.GetTermWidth() + + // Count the number of lines that need to be cleared. We need to take wrapping into account as well + prevTotalLines := 0 + for _, line := range ml.prevLines { + prevTotalLines += ml.countConsoleLines(line, tw) + } + if prevTotalLines > 0 { + ml.clearLines(prevTotalLines) + } + if len(ml.topLines) != 0 { - hadTopLines = true - topBuf := bytes.NewBuffer(nil) for _, l := range ml.topLines { - _, _ = fmt.Fprintf(topBuf, "%s\n", l.s()) + _, _ = fmt.Fprintf(ml.w, "%s\n", l.s()) } - - _, _ = ml.uil.Bypass().Write(topBuf.Bytes()) ml.topLines = nil } - linesBuf := bytes.NewBuffer(nil) + ml.prevLines = nil for _, l := range ml.lines { - _, _ = fmt.Fprintf(linesBuf, "%s\n", l.s()) + s := l.s() + ml.prevLines = append(ml.prevLines, s) + _, _ = fmt.Fprintf(ml.w, "%s\n", s) } - newBytes := linesBuf.Bytes() - if hadTopLines || !bytes.Equal(newBytes, ml.lastWritten) { - _, _ = ml.uil.Write(newBytes) - ml.lastWritten = newBytes +} + +func (ml *MultiLinePrinter) countConsoleLines(s string, tw int) int { + s = stripansi.Strip(s) + w := runewidth.StringWidth(s) + cnt := 1 + for w > tw { + cnt++ + s = s[tw:] + w = runewidth.StringWidth(s) } + return cnt } func (ml *MultiLinePrinter) NewTopLine(s LineFunc) { From e190eec384f337ae26b7e4014f6778cc0206c723 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 26 Sep 2022 14:35:23 +0200 Subject: [PATCH 1022/2916] fix: Use custom GetSize/GetWidth implementation --- cmd/kluctl/commands/cobra_utils.go | 3 ++- pkg/status/multiline/multiline.go | 4 ++-- pkg/utils/prettytable.go | 20 ++------------------ pkg/utils/term/term_size.go | 22 ++++++++++++++++++++++ pkg/utils/term/term_size_unix.go | 14 ++++++++++++++ pkg/utils/term/term_size_windows.go | 21 +++++++++++++++++++++ 6 files changed, 63 insertions(+), 21 deletions(-) create mode 100644 pkg/utils/term/term_size.go create mode 100644 pkg/utils/term/term_size_unix.go create mode 100644 pkg/utils/term/term_size_windows.go diff --git a/cmd/kluctl/commands/cobra_utils.go b/cmd/kluctl/commands/cobra_utils.go index 1d2fa005a..3f8bc6316 100644 --- a/cmd/kluctl/commands/cobra_utils.go +++ b/cmd/kluctl/commands/cobra_utils.go @@ -3,6 +3,7 @@ package commands import ( "fmt" "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/term" "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/spf13/viper" @@ -252,7 +253,7 @@ func copyViperValuesToCobraFlags(flags *pflag.FlagSet) error { func (c *rootCommand) helpFunc(cg *commandAndGroups) { cmd := cg.cmd - termWidth := utils.GetTermWidth() + termWidth := term.GetWidth() h := "Usage: " if cmd.Runnable() { diff --git a/pkg/status/multiline/multiline.go b/pkg/status/multiline/multiline.go index 9ecce1d7c..09bcd5b40 100644 --- a/pkg/status/multiline/multiline.go +++ b/pkg/status/multiline/multiline.go @@ -3,7 +3,7 @@ package multiline import ( "fmt" "github.com/acarl005/stripansi" - "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/term" "github.com/mattn/go-runewidth" "io" "sync" @@ -67,7 +67,7 @@ func (ml *MultiLinePrinter) Flush() { ml.mutex.Lock() defer ml.mutex.Unlock() - tw := utils.GetTermWidth() + tw := term.GetWidth() // Count the number of lines that need to be cleared. We need to take wrapping into account as well prevTotalLines := 0 diff --git a/pkg/utils/prettytable.go b/pkg/utils/prettytable.go index cb9858530..88db41614 100644 --- a/pkg/utils/prettytable.go +++ b/pkg/utils/prettytable.go @@ -2,10 +2,8 @@ package utils import ( "bytes" - "golang.org/x/crypto/ssh/terminal" - "os" + "github.com/kluctl/kluctl/v2/pkg/utils/term" "sort" - "strconv" "strings" ) @@ -25,20 +23,6 @@ func (t *PrettyTable) SortRows(col int) { }) } -func GetTermWidth() int { - if c, ok := os.LookupEnv("COLUMNS"); ok { - tw, err := strconv.ParseInt(c, 10, 32) - if err == nil { - return int(tw) - } - } - tw, _, err := terminal.GetSize(0) - if err != nil { - return 80 - } - return tw -} - func (t *PrettyTable) Render(limitWidths []int) string { cols := len(t.rows[0]) @@ -82,7 +66,7 @@ func (t *PrettyTable) Render(limitWidths []int) string { } if len(limitWidths) < cols { - tw := GetTermWidth() + tw := term.GetWidth() // last column should use all remaining space tw = tw - widthSum - (cols-1)*3 - 4 if tw <= 0 { diff --git a/pkg/utils/term/term_size.go b/pkg/utils/term/term_size.go new file mode 100644 index 000000000..706f09180 --- /dev/null +++ b/pkg/utils/term/term_size.go @@ -0,0 +1,22 @@ +package term + +import ( + "os" + "strconv" +) + +var origStdout = os.Stdout + +func GetWidth() int { + if c, ok := os.LookupEnv("COLUMNS"); ok { + tw, err := strconv.ParseInt(c, 10, 32) + if err == nil { + return int(tw) + } + } + w, _, err := GetSize(int(origStdout.Fd())) + if err != nil { + return 80 + } + return w +} diff --git a/pkg/utils/term/term_size_unix.go b/pkg/utils/term/term_size_unix.go new file mode 100644 index 000000000..f283e307c --- /dev/null +++ b/pkg/utils/term/term_size_unix.go @@ -0,0 +1,14 @@ +//go:build !windows + +package term + +import "golang.org/x/sys/unix" + +// GetSize returns the dimensions of the given terminal. +func GetSize(fd int) (width, height int, err error) { + ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ) + if err != nil { + return -1, -1, err + } + return int(ws.Col), int(ws.Row), nil +} diff --git a/pkg/utils/term/term_size_windows.go b/pkg/utils/term/term_size_windows.go new file mode 100644 index 000000000..51a9d1310 --- /dev/null +++ b/pkg/utils/term/term_size_windows.go @@ -0,0 +1,21 @@ +//go:build windows + +package term + +import ( + "golang.org/x/sys/windows" +) + +// GetSize returns the visible dimensions of the given terminal. +// +// These dimensions don't include any scrollback buffer height. +func GetSize(fd int) (width, height int, err error) { + var info windows.ConsoleScreenBufferInfo + if err := windows.GetConsoleScreenBufferInfo(windows.Handle(fd), &info); err != nil { + return 0, 0, err + } + // terminal.GetSize from crypto/ssh adds "+ 1" to both width and height: + // https://go.googlesource.com/crypto/+/refs/heads/release-branch.go1.14/ssh/terminal/util_windows.go#75 + // but looks like this is a root cause of issue #66, so removing both "+ 1" have fixed it. + return int(info.Window.Right - info.Window.Left), int(info.Window.Bottom - info.Window.Top), nil +} From d1e5da6d4c74f7a1a08a2161aa091a72d663b2ac Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 26 Sep 2022 18:43:27 +0200 Subject: [PATCH 1023/2916] chore: Run go get -u ./... --- go.mod | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/go.mod b/go.mod index 8b701789b..81af6f3a8 100644 --- a/go.mod +++ b/go.mod @@ -6,11 +6,11 @@ require ( github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e github.com/Masterminds/semver/v3 v3.1.1 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d - github.com/aws/aws-sdk-go v1.44.103 - github.com/bitnami-labs/sealed-secrets v0.18.2 + github.com/aws/aws-sdk-go v1.44.105 + github.com/bitnami-labs/sealed-secrets v0.18.5 github.com/cyphar/filepath-securejoin v0.2.3 github.com/docker/distribution v2.8.1+incompatible - github.com/fluxcd/pkg/kustomize v0.7.0 + github.com/fluxcd/pkg/kustomize v0.8.0 github.com/go-git/go-git/v5 v5.4.2 github.com/go-playground/validator/v10 v10.11.1 github.com/gobwas/glob v0.2.3 // indirect @@ -25,7 +25,7 @@ require ( github.com/kluctl/go-jinja2 v0.0.0-20220922102415-424914b4142b github.com/mattn/go-colorable v0.1.13 github.com/mattn/go-isatty v0.0.16 - github.com/mattn/go-runewidth v0.0.9 + github.com/mattn/go-runewidth v0.0.14 github.com/mitchellh/go-ps v1.0.0 github.com/mitchellh/reflectwalk v1.0.2 github.com/ohler55/ojg v1.14.4 @@ -39,9 +39,9 @@ require ( github.com/stretchr/testify v1.8.0 github.com/whilp/git-urls v1.0.0 github.com/xanzy/ssh-agent v0.3.2 - golang.org/x/crypto v0.0.0-20220919173607-35f4265a4bc0 - golang.org/x/net v0.0.0-20220921203646-d300de134e69 - golang.org/x/sync v0.0.0-20220907140024-f12130a52804 + golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be + golang.org/x/net v0.0.0-20220923203811-8be639271d50 + golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7 golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 golang.org/x/term v0.0.0-20220919170432-7a66f970e087 golang.org/x/text v0.3.7 @@ -53,7 +53,7 @@ require ( k8s.io/apimachinery v0.25.2 k8s.io/client-go v0.25.2 k8s.io/klog/v2 v2.80.1 - sigs.k8s.io/kind v0.15.0 + sigs.k8s.io/kind v0.16.0 sigs.k8s.io/kustomize/api v0.12.1 sigs.k8s.io/kustomize/kyaml v0.13.9 sigs.k8s.io/structured-merge-diff/v4 v4.2.3 @@ -73,7 +73,7 @@ require ( github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/sprig/v3 v3.2.2 // indirect github.com/Masterminds/squirrel v1.5.3 // indirect - github.com/Microsoft/go-winio v0.5.2 // indirect + github.com/Microsoft/go-winio v0.6.0 // indirect github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895 // indirect github.com/acomagu/bufpipe v1.0.3 // indirect github.com/alessio/shellescape v1.4.1 // indirect @@ -89,7 +89,7 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/docker/cli v20.10.18+incompatible // indirect github.com/docker/docker v20.10.18+incompatible // indirect - github.com/docker/docker-credential-helpers v0.6.4 // indirect + github.com/docker/docker-credential-helpers v0.7.0 // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/go-units v0.5.0 // indirect @@ -147,7 +147,7 @@ require ( github.com/jmoiron/sqlx v1.3.5 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.15.10 // indirect + github.com/klauspost/compress v1.15.11 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/leodido/go-urn v1.2.1 // indirect @@ -155,7 +155,7 @@ require ( github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/magiconair/properties v1.8.6 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect @@ -171,7 +171,7 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/oklog/run v1.1.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 // indirect + github.com/opencontainers/image-spec v1.1.0-rc1 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.0.5 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect @@ -181,6 +181,7 @@ require ( github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect + github.com/rivo/uniseg v0.4.2 // indirect github.com/rubenv/sql-migrate v1.2.0 // indirect github.com/russross/blackfriday v1.6.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect @@ -195,12 +196,14 @@ require ( github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xlab/treeprint v1.1.0 // indirect - go.starlark.net v0.0.0-20220817180228-f738f5508c12 // indirect + go.starlark.net v0.0.0-20220926145019-14b050677505 // indirect go.uber.org/atomic v1.10.0 // indirect + golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 // indirect - golang.org/x/time v0.0.0-20220920022843-2ce7c2934d45 // indirect + golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect + golang.org/x/tools v0.1.12 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20220921223823-23cae91e6737 // indirect + google.golang.org/genproto v0.0.0-20220923205249-dd2d53f1fffc // indirect google.golang.org/grpc v1.49.0 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect @@ -212,7 +215,7 @@ require ( k8s.io/component-base v0.25.2 // indirect k8s.io/kube-openapi v0.0.0-20220803164354-a70c9af30aea // indirect k8s.io/kubectl v0.25.2 // indirect - k8s.io/utils v0.0.0-20220823124924-e9cbc92d1a73 // indirect + k8s.io/utils v0.0.0-20220922133306-665eaaec4324 // indirect oras.land/oras-go v1.2.0 // indirect sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect sigs.k8s.io/yaml v1.3.0 // indirect From c6281fa167a3afdea9c8f906b8ac79ecaab7c9e4 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 26 Sep 2022 18:48:20 +0200 Subject: [PATCH 1024/2916] chore: Run go mod tidy --- go.sum | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/go.sum b/go.sum index 4c724677b..46fc203bd 100644 --- a/go.sum +++ b/go.sum @@ -105,6 +105,8 @@ github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jB github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= +github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= github.com/Microsoft/hcsshim v0.9.4 h1:mnUj0ivWy6UzbB1uLFqKR6F+ZyiDc7j4iGgHTpO+5+I= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM= @@ -151,6 +153,8 @@ github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpi github.com/aws/aws-sdk-go v1.36.30/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go v1.44.103 h1:tbhBHKgiZSIUkG8FcHy3wYKpPVvp65Wn7ZiX0B8phpY= github.com/aws/aws-sdk-go v1.44.103/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.105 h1:UUwoD1PRKIj3ltrDUYTDQj5fOTK3XsnqolLpRTMmSEM= +github.com/aws/aws-sdk-go v1.44.105/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -158,6 +162,8 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitnami-labs/sealed-secrets v0.18.2 h1:CfZD0JmhAPOQ7YqxrxZ90/+BjqTheWztBOYploqO6nc= github.com/bitnami-labs/sealed-secrets v0.18.2/go.mod h1:wlhaZ+SI5aJkpq9CyyP0pVLJEj4LPXLzLHb2TnID/Rc= +github.com/bitnami-labs/sealed-secrets v0.18.5 h1:PT/lxfgWnP8l8ybpvTsHuJltY4A35LI8if19Ww9FbcU= +github.com/bitnami-labs/sealed-secrets v0.18.5/go.mod h1:nzCyKhzDRbNySRUZXR6tvui4s95LbuaNpBoGRvV9Nk8= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/bkielbasa/cyclop v1.2.0/go.mod h1:qOI0yy6A7dYC4Zgsa72Ppm9kONl0RoIlPbzot9mhmeI= github.com/blizzy78/varnamelen v0.3.0/go.mod h1:hbwRdBvoBqxk34XyQ6HA0UH3G0/1TKuv5AC4eaBT0Ec= @@ -174,6 +180,7 @@ github.com/bwesterb/go-ristretto v1.2.1/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7N github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= @@ -238,6 +245,8 @@ github.com/docker/docker v20.10.18+incompatible h1:SN84VYXTBNGn92T/QwIRPlum9zfem github.com/docker/docker v20.10.18+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.6.4 h1:axCks+yV+2MR3/kZhAmy07yC56WZ2Pwu/fKWtKuZB0o= github.com/docker/docker-credential-helpers v0.6.4/go.mod h1:ofX3UI0Gz1TteYBjtgs07O36Pyasyp66D2uKT7H8W1c= +github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= +github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= @@ -283,6 +292,8 @@ github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8S github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= github.com/fluxcd/pkg/kustomize v0.7.0 h1:604rlpRZTWaOfzDZ1W93aHaFh9kn8/UMX/wzsjwIUQY= github.com/fluxcd/pkg/kustomize v0.7.0/go.mod h1:zJY3Z0+SX+zs+/A1F6fCT0JvUce265XnrpTtHnujXPo= +github.com/fluxcd/pkg/kustomize v0.8.0 h1:8AdEvp6y38ISZzoi0H82Si5zkmLXClbeX10W7HevB00= +github.com/fluxcd/pkg/kustomize v0.8.0/go.mod h1:zGtCZF6V3hMWcf46SqrQc10fS9yUlKzi2UcFUeabDAE= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -620,6 +631,7 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/jgautheron/goconst v1.5.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= +github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= github.com/jhump/protoreflect v1.6.1 h1:4/2yi5LyDPP7nN+Hiird1SAJ6YoxUm13/oxHGRnbPd8= github.com/jhump/protoreflect v1.6.1/go.mod h1:RZQ/lnuN+zqeRVpQigTwO6o0AJUkxbnSnpuG7toUTG4= github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= @@ -669,6 +681,8 @@ github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8 github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.10 h1:Ai8UzuomSCDw90e1qNMtb15msBXsNpH6gzkkENQNcJo= github.com/klauspost/compress v1.15.10/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= +github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c= +github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= github.com/kluctl/go-embed-python v0.0.0-3.10.6-20220906-2 h1:zjJAVyZiWJhufcafhykvXNar1vqDp/ukQk0hFuCJ9zw= github.com/kluctl/go-embed-python v0.0.0-3.10.6-20220906-2/go.mod h1:uekQDAlvKAYJWuDxxP1SGn1vTPUfoBwMS+aB6kR+v+o= github.com/kluctl/go-jinja2 v0.0.0-20220922102415-424914b4142b h1:4fBKK0PZ8e/DC4GqM6RrUSSE2J6SlVTKNPb8awYxb5g= @@ -729,6 +743,7 @@ github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2 github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/matoous/godox v0.0.0-20210227103229-6504466cf951/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= +github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= @@ -756,6 +771,8 @@ github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= @@ -765,6 +782,8 @@ github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpe github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/matttproud/golang_protobuf_extensions v1.0.2 h1:hAHbPm5IJGijwng3PWk09JkG9WeqChjprR5s9bBZ+OM= +github.com/matttproud/golang_protobuf_extensions v1.0.2/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/mbilski/exhaustivestruct v1.2.0/go.mod h1:OeTBVxQWoEmB2J2JCHmXWPJ0aksxSUOUy+nvtVEfzXc= github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517/go.mod h1:KQ7+USdGKfpPjXk4Ga+5XxQM4Lm4e3gAogrreFAYpOg= github.com/mgechev/revive v1.1.2/go.mod h1:bnXsMr+ZTH09V5rssEI+jHAZ4z+ZdyhgO/zsy3EhK+0= @@ -860,6 +879,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 h1:+czc/J8SlhPKLOtVLMQc+xDCFBT73ZStMsRhSsUhsSg= github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198/go.mod h1:j4h1pJW6ZcJTgMZWP3+7RlG3zTaP02aDZ/Qw0sppK7Q= +github.com/opencontainers/image-spec v1.1.0-rc1 h1:lfG+OTa7V8PD3PKvkocSG9KAcA9MANqJn53m31Fvwkc= +github.com/opencontainers/image-spec v1.1.0-rc1/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= @@ -941,6 +962,9 @@ github.com/quasilyte/go-ruleguard/rules v0.0.0-20210428214800-545e0d2e0bf7/go.mo github.com/quasilyte/regex/syntax v0.0.0-20200407221936-30656e2c4a95/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= github.com/r3labs/diff/v2 v2.15.1 h1:EOrVqPUzi+njlumoqJwiS/TgGgmZo83619FNDB9xQUg= github.com/r3labs/diff/v2 v2.15.1/go.mod h1:I8noH9Fc2fjSaMxqF3G2lhDdC0b+JXCfyx85tWFM9kc= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8= +github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -1121,6 +1145,8 @@ go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.starlark.net v0.0.0-20220817180228-f738f5508c12 h1:xOBJXWGEDwU5xSDxH6macxO11Us0AH2fTa9rmsbbF7g= go.starlark.net v0.0.0-20220817180228-f738f5508c12/go.mod h1:VZcBMdr3cT3PnBoWunTabuSEXwVAH+ZJ5zxfs3AdASk= +go.starlark.net v0.0.0-20220926145019-14b050677505 h1:W0MibAL5BiEenQR+F/EF/a4HJhgLngHVvm6jbtUW0PM= +go.starlark.net v0.0.0-20220926145019-14b050677505/go.mod h1:qsNirHv+Awo5xHuNyQ/0niov6kDxdBs+bqpVMBCW77k= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -1163,6 +1189,8 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220919173607-35f4265a4bc0 h1:a5Yg6ylndHHYJqIPrdq0AhvR6KTvDTAvgBtaidhEevY= golang.org/x/crypto v0.0.0-20220919173607-35f4265a4bc0/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be h1:fmw3UbQh+nxngCAHrDCCztao/kbYFnWjoqop8dHx05A= +golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1200,6 +1228,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1257,6 +1287,8 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220921203646-d300de134e69 h1:hUJpGDpnfwdJW8iNypFjmSY0sCBEL+spFTZ2eO+Sfps= golang.org/x/net v0.0.0-20220921203646-d300de134e69/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20220923203811-8be639271d50 h1:vKyz8L3zkd+xrMeIaBsQ/MNVPVFSffdaU3ZyYlBGFnI= +golang.org/x/net v0.0.0-20220923203811-8be639271d50/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1290,6 +1322,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220907140024-f12130a52804 h1:0SH2R3f1b1VmIMG7BXbEZCBUu2dKmHschSmjqGUrW8A= golang.org/x/sync v0.0.0-20220907140024-f12130a52804/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7 h1:ZrnxWX62AgTKOSagEqxvb3ffipvEDX2pl7E1TdqLqIc= +golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1387,6 +1421,7 @@ golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220919170432-7a66f970e087 h1:tPwmk4vmvVCMdr98VgL4JH+qZxPL8fqlUOHnyOM8N3w= golang.org/x/term v0.0.0-20220919170432-7a66f970e087/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1406,6 +1441,8 @@ golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220920022843-2ce7c2934d45 h1:yuLAip3bfURHClMG9VBdzPrQvCWjWiWUTBGV+/fCbUs= golang.org/x/time v0.0.0-20220920022843-2ce7c2934d45/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220922220347-f3bd1da661af h1:Yx9k8YCG3dvF87UAn2tu2HQLf2dt/eR1bXxpLMWeH+Y= +golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1509,6 +1546,8 @@ golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1614,6 +1653,8 @@ google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEc google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220921223823-23cae91e6737 h1:K1zaaMdYBXRyX+cwFnxj7M6zwDyumLQMZ5xqwGvjreQ= google.golang.org/genproto v0.0.0-20220921223823-23cae91e6737/go.mod h1:2r/26NEF3bFmT3eC3aZreahSal0C3Shl8Gi6vyDYqOQ= +google.golang.org/genproto v0.0.0-20220923205249-dd2d53f1fffc h1:saaNe2+SBQxandnzcD/qB1JEBQ2Pqew+KlFLLdA/XcM= +google.golang.org/genproto v0.0.0-20220923205249-dd2d53f1fffc/go.mod h1:yEEpwVWKMZZzo81NwRgyEJnA2fQvpXAYPVisv8EgDVs= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -1728,6 +1769,7 @@ k8s.io/client-go v0.25.2 h1:SUPp9p5CwM0yXGQrwYurw9LWz+YtMwhWd0GqOsSiefo= k8s.io/client-go v0.25.2/go.mod h1:i7cNU7N+yGQmJkewcRD2+Vuj4iz7b30kI8OcL3horQ4= k8s.io/component-base v0.25.2 h1:Nve/ZyHLUBHz1rqwkjXm/Re6IniNa5k7KgzxZpTfSQY= k8s.io/component-base v0.25.2/go.mod h1:90W21YMr+Yjg7MX+DohmZLzjsBtaxQDDwaX4YxDkl60= +k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= @@ -1737,6 +1779,8 @@ k8s.io/kubectl v0.25.2 h1:2993lTeVimxKSWx/7z2PiJxUILygRa3tmC4QhFaeioA= k8s.io/kubectl v0.25.2/go.mod h1:eoBGJtKUj7x38KXelz+dqVtbtbKwCqyKzJWmBHU0prg= k8s.io/utils v0.0.0-20220823124924-e9cbc92d1a73 h1:H9TCJUUx+2VA0ZiD9lvtaX8fthFsMoD+Izn93E/hm8U= k8s.io/utils v0.0.0-20220823124924-e9cbc92d1a73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20220922133306-665eaaec4324 h1:i+xdFemcSNuJvIfBlaYuXgRondKxK4z4prVPKzEaelI= +k8s.io/utils v0.0.0-20220922133306-665eaaec4324/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= mvdan.cc/gofumpt v0.1.1/go.mod h1:yXG1r1WqZVKWbVRtBWKWX9+CxGYfA51nSomhM0woR48= mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= @@ -1750,6 +1794,8 @@ sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/kind v0.15.0 h1:Fskj234L4hjQlsScCgeYvCBIRt06cjLzc7+kbr1u8Tg= sigs.k8s.io/kind v0.15.0/go.mod h1:cKTqagdRyUQmihhBOd+7p43DpOPRn9rHsUC08K1Jbsk= +sigs.k8s.io/kind v0.16.0 h1:GFXyyxtPnHFKqXr3ZG8/X0+0K9sl69lejStlPn2WQyM= +sigs.k8s.io/kind v0.16.0/go.mod h1:cKTqagdRyUQmihhBOd+7p43DpOPRn9rHsUC08K1Jbsk= sigs.k8s.io/kustomize/api v0.12.1 h1:7YM7gW3kYBwtKvoY216ZzY+8hM+lV53LUayghNRJ0vM= sigs.k8s.io/kustomize/api v0.12.1/go.mod h1:y3JUhimkZkR6sbLNwfJHxvo1TCLwuwm14sCYnkH6S1s= sigs.k8s.io/kustomize/kyaml v0.13.9 h1:Qz53EAaFFANyNgyOEJbT/yoIHygK40/ZcvU3rgry2Tk= From 4ea1d40bb7dd5909ecb0b73011cafdfa73bd76f8 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 27 Sep 2022 10:19:43 +0200 Subject: [PATCH 1025/2916] fix: Use 100ms for multiline updates --- pkg/status/multiline/multiline.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/status/multiline/multiline.go b/pkg/status/multiline/multiline.go index 09bcd5b40..399942828 100644 --- a/pkg/status/multiline/multiline.go +++ b/pkg/status/multiline/multiline.go @@ -33,7 +33,7 @@ type Line struct { func (ml *MultiLinePrinter) Start(w io.Writer) { ml.w = w - ml.ticker = time.NewTicker(1 * time.Millisecond) + ml.ticker = time.NewTicker(100 * time.Millisecond) ml.tdone = make(chan bool) go ml.loop() From d4b7cfd01b2b49d8501c1b3e855d35796a7d8113 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 27 Sep 2022 11:33:25 +0200 Subject: [PATCH 1026/2916] refactor: Generalize deprecation handling --- pkg/kluctl_project/project.go | 3 --- pkg/kluctl_project/project_load.go | 12 +++++------ pkg/kluctl_project/target_context.go | 6 ++---- pkg/status/status.go | 32 ++++++++++++++++++++++++---- pkg/vars/vars_loader.go | 2 +- 5 files changed, 36 insertions(+), 19 deletions(-) diff --git a/pkg/kluctl_project/project.go b/pkg/kluctl_project/project.go index bca9ab1a3..ebb5b2e26 100644 --- a/pkg/kluctl_project/project.go +++ b/pkg/kluctl_project/project.go @@ -6,7 +6,6 @@ import ( "github.com/kluctl/go-jinja2" "github.com/kluctl/kluctl/v2/pkg/git/repocache" types2 "github.com/kluctl/kluctl/v2/pkg/types" - "github.com/kluctl/kluctl/v2/pkg/utils" "strings" ) @@ -29,8 +28,6 @@ type LoadedKluctlProject struct { J2 *jinja2.Jinja2 RP *repocache.GitRepoCache - - warnOnce utils.OnceByKey } func (c *LoadedKluctlProject) GetMetadata() *types2.ProjectMetadata { diff --git a/pkg/kluctl_project/project_load.go b/pkg/kluctl_project/project_load.go index 3a2d1e730..738c3219c 100644 --- a/pkg/kluctl_project/project_load.go +++ b/pkg/kluctl_project/project_load.go @@ -96,10 +96,8 @@ func (c *LoadedKluctlProject) loadExternalProject(ep *types2.ExternalProject, de } if ep.Project != nil { - c.warnOnce.Do("external-projects", func() { - status.Warning(c.ctx, "External projects are deprecated and support for them will be removed in the future. "+ - "Use Git variable sources as replacement for cluster configs and Git includes as replacement for external deployment projects.") - }) + status.Deprecation(c.ctx, "external-projects", "External projects are deprecated and support for them will be removed in the future. "+ + "Use Git variable sources as replacement for cluster configs and Git includes as replacement for external deployment projects.") // pointing to an actual external project, so let's try to clone it return c.loadGitProject(ep.Project, defaultGitSubDir) @@ -156,13 +154,13 @@ func (c *LoadedKluctlProject) loadKluctlProject() error { defer s.Failed() if c.loadArgs.LocalClusters != "" { - status.Warning(c.ctx, "--local-clusters is deprecated and will be removed in an upcoming version. Use variables loaded from git instead.") + status.Deprecation(c.ctx, "--local-clusters", "--local-clusters is deprecated and will be removed in an upcoming version. Use variables loaded from git instead.") } if c.loadArgs.LocalDeployment != "" { - status.Warning(c.ctx, "--local-deployment is deprecated and will be removed in an upcoming version. Use git includes instead.") + status.Deprecation(c.ctx, "--local-deployment", "--local-deployment is deprecated and will be removed in an upcoming version. Use git includes instead.") } if c.loadArgs.LocalSealedSecrets != "" { - status.Warning(c.ctx, "--local-sealed-secrets is deprecated and will be removed in an upcoming version.") + status.Deprecation(c.ctx, "--local-sealed-secrets", "--local-sealed-secrets is deprecated and will be removed in an upcoming version.") } deploymentInfo, err := c.loadExternalProject(c.Config.Deployment, "", c.loadArgs.LocalDeployment) diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index f93c9e8ed..b2c9ad978 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -205,7 +205,7 @@ func (p *LoadedKluctlProject) loadSecrets(target *types.Target, varsCtx *vars.Va return err } if len(secretEntry.Sources) != 0 { - status.Warning(p.ctx, "'sources' in secretSets is deprecated, use 'vars' instead") + status.Deprecation(p.ctx, "secrets-sets-sources", "'sources' in secretSets is deprecated, use 'vars' instead") err = varsLoader.LoadVarsList(varsCtx, secretEntry.Sources, searchDirs, "secrets") } else { err = varsLoader.LoadVarsList(varsCtx, secretEntry.Vars, searchDirs, "secrets") @@ -221,9 +221,7 @@ func (p *LoadedKluctlProject) LoadClusterConfig(clusterName string) (*types.Clus var err error var clusterConfig *types.ClusterConfig - p.warnOnce.Do("cluster-config", func() { - status.Warning(p.ctx, "Cluster configurations have been deprecated and support for them will be removed in a future kluctl release.") - }) + status.Deprecation(p.ctx, "cluster-config", "Cluster configurations have been deprecated and support for them will be removed in a future kluctl release.") clusterConfig, err = types.LoadClusterConfig(p.ClustersDir, clusterName) if err != nil { diff --git a/pkg/status/status.go b/pkg/status/status.go index c49f64dfc..8c8f158c3 100644 --- a/pkg/status/status.go +++ b/pkg/status/status.go @@ -3,6 +3,7 @@ package status import ( "context" "fmt" + "github.com/kluctl/kluctl/v2/pkg/utils" ) // StatusContext is used to report user-facing status/progress @@ -57,17 +58,33 @@ type StatusHandler interface { } type contextKey struct{} +type contextValue struct { + slh StatusHandler + deprecation utils.OnceByKey +} + +var noopContextValue = contextValue{ + slh: &NoopStatusHandler{}, +} func NewContext(ctx context.Context, slh StatusHandler) context.Context { - return context.WithValue(ctx, contextKey{}, slh) + return context.WithValue(ctx, contextKey{}, &contextValue{ + slh: slh, + }) } -func FromContext(ctx context.Context) StatusHandler { +func getContextValue(ctx context.Context) *contextValue { v := ctx.Value(contextKey{}) if v == nil { - return &NoopStatusHandler{} + return &noopContextValue } - return v.(StatusHandler) + cv := v.(*contextValue) + return cv +} + +func FromContext(ctx context.Context) StatusHandler { + v := getContextValue(ctx) + return v.slh } type Option func(s *StatusContext) @@ -250,6 +267,13 @@ func Prompt(ctx context.Context, password bool, message string, args ...any) (st return slh.Prompt(password, fmt.Sprintf(message, args...)) } +func Deprecation(ctx context.Context, key string, message string) { + cv := getContextValue(ctx) + cv.deprecation.Do(key, func() { + cv.slh.Warning(message) + }) +} + func Flush(ctx context.Context) { slh := FromContext(ctx) slh.Flush() diff --git a/pkg/vars/vars_loader.go b/pkg/vars/vars_loader.go index a479ec791..84986f0a1 100644 --- a/pkg/vars/vars_loader.go +++ b/pkg/vars/vars_loader.go @@ -76,7 +76,7 @@ func (v *VarsLoader) LoadVars(varsCtx *VarsCtx, sourceIn *types.VarsSource, sear v.mergeVars(varsCtx, source.Values, rootKey) return nil } else if source.Path != nil { - status.Warning(v.ctx, "'path' is deprecated as vars source, use 'file' instead") + status.Deprecation(v.ctx, "vars-path", "'path' is deprecated as vars source, use 'file' instead") return v.loadFile(varsCtx, *source.Path, searchDirs, rootKey) } else if source.File != nil { return v.loadFile(varsCtx, *source.File, searchDirs, rootKey) From 1952338e127d2f06be04983a1ae214c969565b9d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 27 Sep 2022 11:33:56 +0200 Subject: [PATCH 1027/2916] feat: Upgrade go-jinja2 to add .templateignore support Also deprecate templateExcludes. --- go.mod | 2 +- go.sum | 517 +----------------------------- pkg/deployment/deployment_item.go | 4 + 3 files changed, 8 insertions(+), 515 deletions(-) diff --git a/go.mod b/go.mod index 81af6f3a8..cc78c4587 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/jinzhu/copier v0.3.5 github.com/kevinburke/ssh_config v1.2.0 github.com/kluctl/go-embed-python v0.0.0-3.10.6-20220906-2 - github.com/kluctl/go-jinja2 v0.0.0-20220922102415-424914b4142b + github.com/kluctl/go-jinja2 v0.0.0-20220927093149-38ad307371ca github.com/mattn/go-colorable v0.1.13 github.com/mattn/go-isatty v0.0.16 github.com/mattn/go-runewidth v0.0.14 diff --git a/go.sum b/go.sum index 46fc203bd..1b9d4d57c 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -4d63.com/gochecknoglobals v0.1.0/go.mod h1:wfdC5ZjKSPr7CybKEcgJhUOgeAQW1+7WcyK8OvUilfo= -bitbucket.org/creachadair/shell v0.0.6/go.mod h1:8Qqi/cYk7vPnsOePHroKXDJYmb5x7ENhtiFtfZq8K+M= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -14,7 +12,6 @@ cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6 cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.60.0/go.mod h1:yw2G51M9IfRboUH61Us8GqCeF1PzPblB823Mn2q2eAU= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= @@ -23,11 +20,6 @@ cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPT cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= -cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= -cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= -cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= -cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= -cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -39,23 +31,17 @@ cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOt cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= -cloud.google.com/go/firestore v1.6.0/go.mod h1:afJwI0vaXwAG54kI7A//lP/lSPDkQORQuMkv56TxEPU= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/pubsub v1.5.0/go.mod h1:ZEwJccE3z93Z2HWvstpri00jOg7oO4UZDtKhwDwqF0w= -cloud.google.com/go/spanner v1.7.0/go.mod h1:sd3K2gZ9Fd0vMPLXzeCrF6fq4i63Q7aTLW/lBIfBkIk= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= -contrib.go.opencensus.io/exporter/stackdriver v0.13.4/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Antonboom/errname v0.1.5/go.mod h1:DugbBstvPFQbv/5uLcRRzfrNqKE9tVdVCqWCLp6Cifo= -github.com/Antonboom/nilnil v0.1.0/go.mod h1:PhHLvRPSghY5Y7mX4TW+BHZQYo1A8flE5H20D3IPZBo= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= @@ -77,25 +63,19 @@ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBp github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e h1:NeAW1fUYUEWhft7pkxDf6WoUvEZJ/uOKsvtpjLnn8MU= github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0= github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= -github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Masterminds/sprig v2.15.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= -github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= github.com/Masterminds/sprig/v3 v3.2.0/go.mod h1:tWhwTbUTndesPNeF0C900vKoq283u6zp4APT9vaF3SI= github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= @@ -103,18 +83,15 @@ github.com/Masterminds/squirrel v1.5.3 h1:YPpoceAcxuzIljlr5iWpNKaql7hLeG1KLSrhvd github.com/Masterminds/squirrel v1.5.3/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= -github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= github.com/Microsoft/hcsshim v0.9.4 h1:mnUj0ivWy6UzbB1uLFqKR6F+ZyiDc7j4iGgHTpO+5+I= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM= github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895 h1:NsReiLpErIPzRrnogAXYwSoU7txA977LjDGrbkewJbg= github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= -github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= @@ -126,16 +103,10 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= -github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE= -github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= -github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= -github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= @@ -146,13 +117,6 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/ashanbrown/forbidigo v1.2.0/go.mod h1:vVW7PEdqEFqapJe95xHkTfB1+XvZXBFg8t0sG2FIxmI= -github.com/ashanbrown/makezero v0.0.0-20210520155254-b6261585ddde/go.mod h1:oG9Dnez7/ESBqc4EdrdNlryeo7d0KcW1ftXHm7nU/UU= -github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.36.30/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= -github.com/aws/aws-sdk-go v1.44.103 h1:tbhBHKgiZSIUkG8FcHy3wYKpPVvp65Wn7ZiX0B8phpY= -github.com/aws/aws-sdk-go v1.44.103/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aws/aws-sdk-go v1.44.105 h1:UUwoD1PRKIj3ltrDUYTDQj5fOTK3XsnqolLpRTMmSEM= github.com/aws/aws-sdk-go v1.44.105/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -160,35 +124,25 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bitnami-labs/sealed-secrets v0.18.2 h1:CfZD0JmhAPOQ7YqxrxZ90/+BjqTheWztBOYploqO6nc= -github.com/bitnami-labs/sealed-secrets v0.18.2/go.mod h1:wlhaZ+SI5aJkpq9CyyP0pVLJEj4LPXLzLHb2TnID/Rc= github.com/bitnami-labs/sealed-secrets v0.18.5 h1:PT/lxfgWnP8l8ybpvTsHuJltY4A35LI8if19Ww9FbcU= github.com/bitnami-labs/sealed-secrets v0.18.5/go.mod h1:nzCyKhzDRbNySRUZXR6tvui4s95LbuaNpBoGRvV9Nk8= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= -github.com/bkielbasa/cyclop v1.2.0/go.mod h1:qOI0yy6A7dYC4Zgsa72Ppm9kONl0RoIlPbzot9mhmeI= -github.com/blizzy78/varnamelen v0.3.0/go.mod h1:hbwRdBvoBqxk34XyQ6HA0UH3G0/1TKuv5AC4eaBT0Ec= -github.com/bombsimon/wsl/v3 v3.3.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc= -github.com/breml/bidichk v0.1.1/go.mod h1:zbfeitpevDUGI7V91Uzzuwrn4Vls8MoBMrwtt78jmso= github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd h1:rFt+Y/IK1aEZkEHchZRSq9OQbsSzIT/OrI8YFFmRIng= github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembjv71DPz3uX/V/6MMlSyD9JBQ6kQ= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o= -github.com/butuzov/ireturn v0.1.1/go.mod h1:Wh6Zl3IMtTpaIKbmwzqi6olnM9ptYQxxVacMsOEFPoc= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/bwesterb/go-ristretto v1.2.1/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= -github.com/charithe/durationcheck v0.0.9/go.mod h1:SSbRIBVfMjCi/kEB6K65XEA83D6prSM8ap1UCpNKtgg= -github.com/chavacava/garif v0.0.0-20210405164556-e8a0a408d6af/go.mod h1:Qjyv4H3//PWVzTeCezG2b9IRn6myJxJSr4TD/xo6ojU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -202,40 +156,24 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/containerd/cgroups v1.0.3 h1:ADZftAkglvCiD44c77s5YmMqaP2pzVCFZvBmAlBdAP4= github.com/containerd/containerd v1.6.8 h1:h4dOFDwzHmqFEP754PgfgTeVXFnLiRc6kiqC7tplDJs= github.com/containerd/containerd v1.6.8/go.mod h1:By6p5KqPK0/7/CgO/A6t/Gz+CUYUu2zf1hUaaymVXB0= github.com/containerd/stargz-snapshotter/estargz v0.12.0 h1:idtwRTLjk2erqiYhPWy2L844By8NRFYEwYHcXhoIWPM= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190620071333-e64a0ec8b42a/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI= github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= -github.com/daixiang0/gci v0.2.9/go.mod h1:+4dZ7TISfSmqfAGv59ePaHfNzgGtIkHAhhdKggP1JAc= -github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg= -github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/denis-tingajkin/go-header v0.4.2/go.mod h1:eLRHAVXzE5atsKAnNRDB90WHCFFnBUn4RN0nRcs1LJA= github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/distribution/distribution/v3 v3.0.0-20220526142353-ffbd94cbe269 h1:hbCT8ZPPMqefiAWD2ZKjn7ypokIGViTvBBg/ExLSdCk= github.com/docker/cli v20.10.18+incompatible h1:f/GQLsVpo10VvToRay2IraVA1wHz9KktZyjev3SIVDU= github.com/docker/cli v20.10.18+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= @@ -243,8 +181,6 @@ github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6 github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v20.10.18+incompatible h1:SN84VYXTBNGn92T/QwIRPlum9zfemfitN7pbsp26WSc= github.com/docker/docker v20.10.18+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker-credential-helpers v0.6.4 h1:axCks+yV+2MR3/kZhAmy07yC56WZ2Pwu/fKWtKuZB0o= -github.com/docker/docker-credential-helpers v0.6.4/go.mod h1:ofX3UI0Gz1TteYBjtgs07O36Pyasyp66D2uKT7H8W1c= github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= @@ -256,8 +192,6 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4 github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= @@ -271,10 +205,7 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= -github.com/envoyproxy/protoc-gen-validate v0.0.14/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/esimonov/ifshort v1.0.3/go.mod h1:yZqNJUrNn20K8Q9n2CrjTKYyVEmX209Hgu+M1LBpeZE= -github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= @@ -282,31 +213,21 @@ github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2Vvl github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4= github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= -github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= -github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= -github.com/fluxcd/pkg/kustomize v0.7.0 h1:604rlpRZTWaOfzDZ1W93aHaFh9kn8/UMX/wzsjwIUQY= -github.com/fluxcd/pkg/kustomize v0.7.0/go.mod h1:zJY3Z0+SX+zs+/A1F6fCT0JvUce265XnrpTtHnujXPo= github.com/fluxcd/pkg/kustomize v0.8.0 h1:8AdEvp6y38ISZzoi0H82Si5zkmLXClbeX10W7HevB00= github.com/fluxcd/pkg/kustomize v0.8.0/go.mod h1:zGtCZF6V3hMWcf46SqrQc10fS9yUlKzi2UcFUeabDAE= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= -github.com/fullstorydev/grpcurl v1.6.0/go.mod h1:ZQ+ayqbKMJNhzLmbpCiurTVlaK2M/3nqZCxaQ2Ze/sM= -github.com/fzipp/gocyclo v0.3.1/go.mod h1:DJHO6AUmbdqj2ET4Z9iArSuwWgYDRryYt2wASxc7x3E= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= -github.com/go-critic/go-critic v0.6.1/go.mod h1:SdNCfU0yF3UBjtaZGw6586/WocupMOJuiqgom5DsQxM= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= @@ -331,12 +252,9 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= -github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= @@ -353,27 +271,12 @@ github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/j github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= -github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= -github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= -github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4= -github.com/go-toolsmith/astcopy v1.0.0/go.mod h1:vrgyG+5Bxrnz4MZWPF+pI4R8h3qKRjjyvV/DSez4WVQ= -github.com/go-toolsmith/astequal v1.0.0/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= -github.com/go-toolsmith/astequal v1.0.1/go.mod h1:4oGA3EZXTVItV/ipGiOx7NWkY5veFfcsOJVS2YxltLw= -github.com/go-toolsmith/astfmt v1.0.0/go.mod h1:cnWmsOAuq4jJY6Ct5YWlVLmcmLMn1JUPuQIHCY7CJDw= -github.com/go-toolsmith/astinfo v0.0.0-20180906194353-9809ff7efb21/go.mod h1:dDStQCHtmZpYOmjRP/8gHHnCCch3Zz3oEgCdZVdtweU= -github.com/go-toolsmith/astp v1.0.0/go.mod h1:RSyrtpVlfTFGDYRbrjyWP1pYu//tSFcvdYrA8meBmLI= -github.com/go-toolsmith/pkgload v1.0.0/go.mod h1:5eFArkbO80v7Z0kdngIxsRXRMTaX4Ilcwuh3clNrQJc= -github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= -github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU= -github.com/go-toolsmith/typep v1.0.2/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU= -github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= github.com/gobuffalo/logger v1.0.6 h1:nnZNpxYo0zx+Aj9RfMPBm+x9zAU2OayFh/xrAWi34HU= github.com/gobuffalo/logger v1.0.6/go.mod h1:J31TBEHR1QLV2683OXTAItYIg8pv2JMHnF/quuAbMjs= github.com/gobuffalo/packd v1.0.1 h1:U2wXfRr4E9DH8IdsDLlRFwTZTK7hLfq9qT/QHXGVe/0= @@ -384,11 +287,7 @@ github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godror/godror v0.24.2/go.mod h1:wZv/9vPiUib6tkoDl+AZ/QLf5YZgMravZ7jxH2eQWAE= -github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= @@ -397,7 +296,6 @@ github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQA github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -410,8 +308,6 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -430,26 +326,13 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4= -github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= -github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8= -github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU= -github.com/golangci/golangci-lint v1.43.0/go.mod h1:VIFlUqidx5ggxDfQagdvd9E67UjMXtTHBkBQ7sHoC5Q= -github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg= -github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o= -github.com/golangci/misspell v0.3.5/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA= -github.com/golangci/revgrep v0.0.0-20210930125155-c22e5001d4f2/go.mod h1:LK+zW4MpyytAWQRz0M4xnzEk50lSvqDQKfx304apFkY= -github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ= github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= -github.com/google/certificate-transparency-go v1.1.1/go.mod h1:FDKqPvSXawb2ecErVRrD+nfy23RCzyl7eqVCEmlT1Zs= github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0= github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -463,7 +346,6 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-containerregistry v0.11.0 h1:Xt8x1adcREjFcmDoDK8OdOsjxu90PHkGuwNP8GiHMLM= @@ -474,74 +356,40 @@ github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200507031123-427632fa3b1c/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= -github.com/google/trillian v1.3.11/go.mod h1:0tPraVHrSDkA3BO6vKX67zgLXs6SsOAbHEivX+9mPgw= -github.com/google/uuid v0.0.0-20161128191214-064e2069ce9c/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= -github.com/gordonklaus/ineffassign v0.0.0-20210225214923-2e10b2664254/go.mod h1:M9mZEtGIsR1oDaZagNPNG9iq9n2HrhZ17dsXk73V3Lw= -github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75/go.mod h1:g2644b03hfBX9Ov0ZBDgXXens4rxSxmqFBbhvKv2yVA= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= -github.com/gostaticanalysis/analysisutil v0.0.3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= -github.com/gostaticanalysis/analysisutil v0.1.0/go.mod h1:dMhHRU9KTiDcuLGdy87/2gTR8WruwYZrKdRq9m1O6uw= -github.com/gostaticanalysis/analysisutil v0.4.1/go.mod h1:18U/DLpRgIUd459wGxVHE0fRgmo1UgHDcbw7F5idXu0= -github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc= -github.com/gostaticanalysis/comment v1.3.0/go.mod h1:xMicKDx7XRXYdVwY9f9wQpDJVnqWxw9wCauCMKp+IBI= -github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado= -github.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM= -github.com/gostaticanalysis/forcetypeassert v0.0.0-20200621232751-01d4955beaa5/go.mod h1:qZEedyP/sY1lTGV1uJ3VhWZ2mqag3IkWsDHVbplHXak= -github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW0HU0GPE3+5PWN4A= -github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= -github.com/gostaticanalysis/testutil v0.4.0/go.mod h1:bLIoPefWXrRi/ssLFWX1dx7Repi5x3CuviD3dgAZaBU= github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.12.1/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -550,7 +398,6 @@ github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.3.1 h1:vDwF1DFNZhntP4DAjuTpOw3uEgMUpXh1pB5fW9DqHpo= github.com/hashicorp/go-hclog v1.3.1/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= @@ -558,7 +405,6 @@ github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJ github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-plugin v1.4.5 h1:oTE/oQR4eghggRg8VY7PAz3dr++VwDNBGCcOfIvHpBo= @@ -584,7 +430,6 @@ github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= @@ -596,11 +441,8 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= github.com/hashicorp/vault/api v1.8.0 h1:7765sW1XBt+qf4XKIYE4ebY9qc/yi9V2/egzGSUNMZU= github.com/hashicorp/vault/api v1.8.0/go.mod h1:uJrw6D3y9Rv7hhmS17JQC50jbPDAZdjZoTtrCCxxs7E= github.com/hashicorp/vault/sdk v0.6.0 h1:6Z+In5DXHiUfZvIZdMx7e2loL1PPyDjA4bVh9ZTIAhs= @@ -609,16 +451,11 @@ github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo= -github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.4/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= @@ -630,27 +467,17 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= -github.com/jgautheron/goconst v1.5.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= -github.com/jhump/protoreflect v1.6.1 h1:4/2yi5LyDPP7nN+Hiird1SAJ6YoxUm13/oxHGRnbPd8= -github.com/jhump/protoreflect v1.6.1/go.mod h1:RZQ/lnuN+zqeRVpQigTwO6o0AJUkxbnSnpuG7toUTG4= -github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg= github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= -github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/jonboulle/clockwork v0.2.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/josharian/txtarfs v0.0.0-20210218200122-0702f000015a/go.mod h1:izVPOvVRsHiKkeGCT6tYBNWyDVuzj9wAaBb5R9qamfw= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -662,33 +489,22 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/juju/ratelimit v1.0.1/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/julz/importas v0.0.0-20210419104244-841f0c0fe66d/go.mod h1:oSFU2R4XK/P7kNBrnL/FEQlDGN1/6WoxXEjSSXO0DV0= -github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw= github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/errcheck v1.6.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= -github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.15.10 h1:Ai8UzuomSCDw90e1qNMtb15msBXsNpH6gzkkENQNcJo= -github.com/klauspost/compress v1.15.10/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c= github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= github.com/kluctl/go-embed-python v0.0.0-3.10.6-20220906-2 h1:zjJAVyZiWJhufcafhykvXNar1vqDp/ukQk0hFuCJ9zw= github.com/kluctl/go-embed-python v0.0.0-3.10.6-20220906-2/go.mod h1:uekQDAlvKAYJWuDxxP1SGn1vTPUfoBwMS+aB6kR+v+o= -github.com/kluctl/go-jinja2 v0.0.0-20220922102415-424914b4142b h1:4fBKK0PZ8e/DC4GqM6RrUSSE2J6SlVTKNPb8awYxb5g= -github.com/kluctl/go-jinja2 v0.0.0-20220922102415-424914b4142b/go.mod h1:8deI9sssmQf1CiCnyRJoBgfIw4Hh3+WsHmmO9+xIIO0= +github.com/kluctl/go-jinja2 v0.0.0-20220927093149-38ad307371ca h1:OSl16ha01V8QHu/yKmsfKHx8r6ts/O3xHuT5SaaJ/1I= +github.com/kluctl/go-jinja2 v0.0.0-20220927093149-38ad307371ca/go.mod h1:kMEDb2dWTbpe6CT+XuGVPcnY1ph9IfHf4b43KN80qas= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kortschak/utter v1.0.1/go.mod h1:vSmSjbyrlKjjsL71193LmzBOKgwePk9DH6uFaWHIInc= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= @@ -702,32 +518,18 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kulti/thelper v0.4.0/go.mod h1:vMu2Cizjy/grP+jmsvOFDx1kYP6+PD1lqg4Yu5exl2U= -github.com/kunwardeep/paralleltest v1.0.3/go.mod h1:vLydzomDFpk7yu5UX02RmP0H8QfRPOV/oFhWN85Mjb4= -github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/kyoh86/exportloopref v0.1.8/go.mod h1:1tUcJeiioIs7VWe5gcOObrux3lb66+sBqGZrRkMwPgg= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= -github.com/ldez/gomoddirectives v0.2.2/go.mod h1:cpgBogWITnCfRq2qGoDkKMEVSaarhdBr6g8G04uz6d0= -github.com/ldez/tagliatelle v0.2.0/go.mod h1:8s6WJQwEYHbKZDsp/LjArytKOG8qaMrKQQ3mFukHs88= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= -github.com/letsencrypt/pkcs11key/v4 v4.0.0/go.mod h1:EFUvBDay26dErnNb70Nd0/VW3tJiIbETBPTl9ATXQag= -github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= -github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= -github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= @@ -735,65 +537,37 @@ github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/maratori/testpackage v1.0.1/go.mod h1:ddKdw+XG0Phzhx8BFDTKgpWP4i7MpApTE5fXSKAqwDU= github.com/markbates/errx v1.1.0 h1:QDFeR+UP95dO12JgW+tgi2UVfo0V8YBHiUIOaeBPiEI= github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc= github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY= github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI= github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= -github.com/matoous/godox v0.0.0-20210227103229-6504466cf951/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= -github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= -github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= -github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI= -github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.14 h1:qZgc/Rwetq+MtyE18WhzjokPD93dNqLGNT3QJuLvBGw= github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= -github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/matttproud/golang_protobuf_extensions v1.0.2 h1:hAHbPm5IJGijwng3PWk09JkG9WeqChjprR5s9bBZ+OM= github.com/matttproud/golang_protobuf_extensions v1.0.2/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/mbilski/exhaustivestruct v1.2.0/go.mod h1:OeTBVxQWoEmB2J2JCHmXWPJ0aksxSUOUy+nvtVEfzXc= -github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517/go.mod h1:KQ7+USdGKfpPjXk4Ga+5XxQM4Lm4e3gAogrreFAYpOg= -github.com/mgechev/revive v1.1.2/go.mod h1:bnXsMr+ZTH09V5rssEI+jHAZ4z+ZdyhgO/zsy3EhK+0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= -github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= -github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= -github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= github.com/mitchellh/cli v1.1.4/go.mod h1:vTLESy5mRhKOs9KDp0/RATawxP1UqBmdrpVRMnpcvKQ= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= @@ -814,11 +588,9 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= @@ -835,63 +607,30 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= -github.com/moricho/tparallel v0.2.1/go.mod h1:fXEIZxG2vdfl0ZF8b42f5a78EhjjD5mX8qUplsoSU4k= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/mozilla/scribe v0.0.0-20180711195314-fb71baf557c1/go.mod h1:FIczTrinKo8VaLxe6PWTPEXRXDIHz2QAwiaBaP5/4a8= -github.com/mozilla/tls-observatory v0.0.0-20210609171429-7bc42856d2e5/go.mod h1:FUqVoUPHSEdDR0MnFM3Dh8AU0pZHLXUD127SAJGER/s= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-proto-validators v0.0.0-20180403085117-0950a7990007/go.mod h1:m2XC9Qq0AlmmVksL6FktJCdTYyLk7V3fKyp0sl1yWQo= -github.com/mwitkow/go-proto-validators v0.2.0/go.mod h1:ZfA1hW+UH/2ZHOWvQ3HnQaU0DtnpXu850MZiy+YUgcc= -github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= -github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nishanths/exhaustive v0.2.3/go.mod h1:bhIX678Nx8inLM9PbpvK1yv6oGtoP8BfaIeMzgBNKvc= -github.com/nishanths/predeclared v0.0.0-20190419143655-18a43bb90ffc/go.mod h1:62PewwiQTlm/7Rj+cxVYqZvDIUc+JjZq6GHAC1fsObQ= -github.com/nishanths/predeclared v0.2.1/go.mod h1:HvkGJcA3naj4lOwnFXFDkFxVtSqQMB9sbB1usJ+xjQE= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/ohler55/ojg v1.14.4 h1:L2ds8AlB5t/QbqSfhRwvagJzQ7pgmdrefMIypQs0Xik= github.com/ohler55/ojg v1.14.4/go.mod h1:7Ghirupn8NC8hSSDpI0gcjorPxj+vSVIONDWfliHR1k= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= -github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo/v2 v2.1.6 h1:Fx2POJZfKRQcM1pH49qSZiYeu319wji004qX+GDovrU= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q= +github.com/onsi/gomega v1.20.2 h1:8uQq0zMgLEfa0vRrrBgaJF2gyW9Da9BmfGV+OyUzfkY= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 h1:+czc/J8SlhPKLOtVLMQc+xDCFBT73ZStMsRhSsUhsSg= -github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198/go.mod h1:j4h1pJW6ZcJTgMZWP3+7RlG3zTaP02aDZ/Qw0sppK7Q= github.com/opencontainers/image-spec v1.1.0-rc1 h1:lfG+OTa7V8PD3PKvkocSG9KAcA9MANqJn53m31Fvwkc= github.com/opencontainers/image-spec v1.1.0-rc1/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= -github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= -github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= -github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= -github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= @@ -900,7 +639,6 @@ github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwb github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= @@ -911,12 +649,9 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= -github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/polyfloyd/go-errorlint v0.0.0-20210722154253-910bb7978349/go.mod h1:wi9BfjxjF/bwiZ701TzmfKu6UKC357IOAtNr0Td0Lvw= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/poy/onpar v0.0.0-20190519213022-ee068f8ea4d1 h1:oL4IBbcqwhhNWh31bjOX8C/OCy0zs9906d/VUru+bqg= github.com/poy/onpar v0.0.0-20190519213022-ee068f8ea4d1/go.mod h1:nSbFQvMj97ZyhFRSJYtut+msi4sOY6zJDGCdSc+/rZU= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -950,57 +685,34 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= -github.com/pseudomuto/protoc-gen-doc v1.3.2/go.mod h1:y5+P6n3iGrbKG+9O04V5ld71in3v/bX88wUwgt+U8EA= -github.com/pseudomuto/protokit v0.2.0/go.mod h1:2PdH30hxVHsup8KpBTOXTBeMVhJZVio3Q8ViKSAXT0Q= -github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI= -github.com/quasilyte/go-ruleguard v0.3.1-0.20210203134552-1b5a410e1cc8/go.mod h1:KsAh3x0e7Fkpgs+Q9pNLS5XpFSvYCEVl5gP9Pp1xp30= -github.com/quasilyte/go-ruleguard v0.3.13/go.mod h1:Ul8wwdqR6kBVOCt2dipDBkE+T6vAV/iixkrKuRTN1oQ= -github.com/quasilyte/go-ruleguard/dsl v0.3.0/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= -github.com/quasilyte/go-ruleguard/dsl v0.3.10/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= -github.com/quasilyte/go-ruleguard/rules v0.0.0-20201231183845-9e62ed36efe1/go.mod h1:7JTjp89EGyU1d6XfBiXihJNG37wB2VRkd125Q1u7Plc= -github.com/quasilyte/go-ruleguard/rules v0.0.0-20210428214800-545e0d2e0bf7/go.mod h1:4cgAphtvu7Ftv7vOT2ZOYhC6CvBxZixcasr8qIOTA50= -github.com/quasilyte/regex/syntax v0.0.0-20200407221936-30656e2c4a95/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= github.com/r3labs/diff/v2 v2.15.1 h1:EOrVqPUzi+njlumoqJwiS/TgGgmZo83619FNDB9xQUg= github.com/r3labs/diff/v2 v2.15.1/go.mod h1:I8noH9Fc2fjSaMxqF3G2lhDdC0b+JXCfyx85tWFM9kc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8= github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.6.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rubenv/sql-migrate v1.2.0 h1:fOXMPLMd41sK7Tg75SXDec15k3zg5WNV6SjuDRiNfcU= github.com/rubenv/sql-migrate v1.2.0/go.mod h1:Z5uVnq7vrIrPmHbVFfR4YLHRZquxeHpckCnRq0P/K9Y= -github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryancurrah/gomodguard v1.2.3/go.mod h1:rYbA/4Tg5c54mV1sv4sQTP5WOPBcoLtnBZ7/TEhXAbg= -github.com/ryanrolds/sqlclosecheck v0.3.0/go.mod h1:1gREqxyTGR3lVtpngyFo3hZAgk0KCtEdgEkHwDbigdA= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= -github.com/sagikazarmark/crypt v0.1.0/go.mod h1:B/mN0msZuINBtQ1zZLEQcegFJJf9vnYIR88KRMEuODE= -github.com/sanposhiho/wastedassign/v2 v2.0.6/go.mod h1:KyZ0MWTwxxBmfwn33zh3k1dmsbF2ud9pAAGfoLfjhtI= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/securego/gosec/v2 v2.9.1/go.mod h1:oDcDLcatOJxkCGaCaq8lua1jTnYf6Sou4wdiJ1n4iHc= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= -github.com/shirou/gopsutil/v3 v3.21.10/go.mod h1:t75NhzCZ/dYyPQjyQmrAYP6c8+LCdFANeBMdLPCNnew= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= -github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= @@ -1010,49 +722,32 @@ github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/sivchari/tenv v1.4.7/go.mod h1:5nF+bITvkebQVanjU6IuMbvIot/7ReNsUV7I5NbprB0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/sonatard/noctx v0.0.1/go.mod h1:9D2D/EoULe8Yy2joDHJj7bv3sZoq9AaSb8B4lqBjiZI= -github.com/sourcegraph/go-diff v0.6.1/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw= github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= -github.com/spf13/viper v1.9.0/go.mod h1:+i6ajR7OX2XaiBkrcZJFK21htRk7eDeLg7+O6bhUPP4= github.com/spf13/viper v1.13.0 h1:BWSJ/M+f+3nmdz9bxB+bWX28kkALN2ok11D0rSo8EJU= github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw= -github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/testify v0.0.0-20170130113145-4d4bfba8f1d1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -1066,33 +761,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/sylvia7788/contextcheck v1.0.4/go.mod h1:vuPKJMQ7MQ91ZTqfdyreNKwZjyUg6KO+IebVyQDedZQ= -github.com/tdakkota/asciicheck v0.0.0-20200416200610-e657995f937b/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM= -github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= -github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= -github.com/tetafro/godot v1.4.11/go.mod h1:LR3CJpxDVGlYOWn3ZZg1PgNZdTUvzsZWu8xaEohUpn8= -github.com/timakin/bodyclose v0.0.0-20200424151742-cb6215831a94/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= -github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs= -github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8= -github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tmc/grpc-websocket-proxy v0.0.0-20200427203606-3cfed13b9966/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tomarrell/wrapcheck/v2 v2.4.0/go.mod h1:68bQ/eJg55BROaRTbMjC7vuhL2OgfoG8bLp9ZyoBfyY= -github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4= -github.com/tommy-muehle/go-mnd/v2 v2.4.0/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= -github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/ultraware/funlen v0.0.3/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= -github.com/ultraware/whitespace v0.0.4/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA= -github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/uudashr/gocognit v1.0.5/go.mod h1:wgYz0mitoKOTysqxTDMOUXg+Jb5SvtihkfmugIZYpEA= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.30.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus= -github.com/valyala/quicktemplate v1.7.0/go.mod h1:sqKJnoaOF88V07vkO+9FL8fb9uZg/VPSJnLYn+LmLk8= -github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/vbatts/tar-split v0.11.2 h1:Via6XqJr0hceW4wff3QRzD5gAk/tatMw/4ZA7cTlIME= -github.com/viki-org/dnscache v0.0.0-20130720023526-c70c1f23c5d8/go.mod h1:dniwbG03GafCjFohMDmz6Zc6oCuiqgH6tGNyXTkHzXE= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/whilp/git-urls v1.0.0 h1:95f6UMWN5FKW71ECsXRUd3FVYiXdrE7aX4NZKcPmIjU= @@ -1107,15 +777,8 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v1.1.0 h1:G/1DjNkPpfZCFt9CSh6b5/nY4VimlbHF3Rh4obvtzDk= github.com/xlab/treeprint v1.1.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= -github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -github.com/yeya24/promlinter v0.1.0/go.mod h1:rs5vtZzeBHqqMwXqFScncpCF6u06lezhZepno9AB1Oc= -github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= -github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= -github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -1127,13 +790,9 @@ github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMzt github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= -go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= -go.etcd.io/etcd v0.0.0-20200513171258-e048e166ab9c/go.mod h1:xCI7ZzBfRuGgBXyXO6yfWfDmlWd35khcWpUa4L0xI/k= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= -go.mozilla.org/mozlog v0.0.0-20170222151521-4bb13139d403/go.mod h1:jHoPAGnDrCy6kaI2tAze5Prf0Nr0w/oNkROt2lw3n3o= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -1143,52 +802,33 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.starlark.net v0.0.0-20220817180228-f738f5508c12 h1:xOBJXWGEDwU5xSDxH6macxO11Us0AH2fTa9rmsbbF7g= -go.starlark.net v0.0.0-20220817180228-f738f5508c12/go.mod h1:VZcBMdr3cT3PnBoWunTabuSEXwVAH+ZJ5zxfs3AdASk= go.starlark.net v0.0.0-20220926145019-14b050677505 h1:W0MibAL5BiEenQR+F/EF/a4HJhgLngHVvm6jbtUW0PM= go.starlark.net v0.0.0-20220926145019-14b050677505/go.mod h1:qsNirHv+Awo5xHuNyQ/0niov6kDxdBs+bqpVMBCW77k= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= -go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -golang.org/x/crypto v0.0.0-20180501155221-613d6eafa307/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220919173607-35f4265a4bc0 h1:a5Yg6ylndHHYJqIPrdq0AhvR6KTvDTAvgBtaidhEevY= -golang.org/x/crypto v0.0.0-20220919173607-35f4265a4bc0/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be h1:fmw3UbQh+nxngCAHrDCCztao/kbYFnWjoqop8dHx05A= golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1201,7 +841,6 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1227,16 +866,13 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -1248,9 +884,6 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -1258,11 +891,9 @@ golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= @@ -1277,16 +908,11 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220921203646-d300de134e69 h1:hUJpGDpnfwdJW8iNypFjmSY0sCBEL+spFTZ2eO+Sfps= -golang.org/x/net v0.0.0-20220921203646-d300de134e69/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20220923203811-8be639271d50 h1:vKyz8L3zkd+xrMeIaBsQ/MNVPVFSffdaU3ZyYlBGFnI= golang.org/x/net v0.0.0-20220923203811-8be639271d50/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1302,9 +928,6 @@ golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 h1:lxqLZaMad/dJHMFZH0NiNpiEZI/nhgWhe4wgzpE+MuA= golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= @@ -1312,7 +935,6 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1320,20 +942,14 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220907140024-f12130a52804 h1:0SH2R3f1b1VmIMG7BXbEZCBUu2dKmHschSmjqGUrW8A= -golang.org/x/sync v0.0.0-20220907140024-f12130a52804/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7 h1:ZrnxWX62AgTKOSagEqxvb3ffipvEDX2pl7E1TdqLqIc= golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1343,30 +959,22 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1379,7 +987,6 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1394,21 +1001,14 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210915083310-ed5796bab164/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1418,7 +1018,6 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 h1:h+EGohizhe9XlX18rfpa8k8RAc5XyaeamM+0VHRd4lc= golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1434,29 +1033,17 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220920022843-2ce7c2934d45 h1:yuLAip3bfURHClMG9VBdzPrQvCWjWiWUTBGV+/fCbUs= -golang.org/x/time v0.0.0-20220920022843-2ce7c2934d45/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220922220347-f3bd1da661af h1:Yx9k8YCG3dvF87UAn2tu2HQLf2dt/eR1bXxpLMWeH+Y= golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190307163923-6a08e3108db3/go.mod h1:25r3+/G6/xytQM8iWZKq3Hn0kr0rgFKPUNVEL/dr3z4= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190321232350-e250d351ecad/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -1466,26 +1053,17 @@ golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190916130336-e45ffcd953cc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117220505-0cba7a3a9ee9/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -1495,56 +1073,24 @@ golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200414032229-332987a829c3/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200422022333-3d57cf2e726e/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200622203043-20e05c1c8ffa/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200624225443-88f3c62a19ff/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200625211823-6506e20df31f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200626171337-aa94e735be7f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200630154851-b2d8b0336632/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200706234117-b22de6825cf7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200812195022-5ae4c3c160a0/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200820010801-b793a1359eac/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200831203904-5a2aa26beb65/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201001104356-43ebab892c4c/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= -golang.org/x/tools v0.0.0-20201002184944-ecd9fd270d5d/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= -golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201028025901-8cd080b735b3/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201114224030-61ea331ec02b/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201118003311-bd56c0adb394/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201230224404-63754364767c/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210101214203-2dba1e4ea05c/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210104081019-d8d6ddbec6ee/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= -golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.6/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= @@ -1556,7 +1102,6 @@ google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEt google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.10.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= @@ -1575,24 +1120,15 @@ google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjR google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= -google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= -google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= -google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= -google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= -google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= -google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20181107211654-5fc9ac540362/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -1600,7 +1136,6 @@ google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= @@ -1614,15 +1149,12 @@ google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200626011028-ee7919e894b5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200707001353-8e8330bf89df/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= @@ -1638,36 +1170,19 @@ google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= -google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220921223823-23cae91e6737 h1:K1zaaMdYBXRyX+cwFnxj7M6zwDyumLQMZ5xqwGvjreQ= -google.golang.org/genproto v0.0.0-20220921223823-23cae91e6737/go.mod h1:2r/26NEF3bFmT3eC3aZreahSal0C3Shl8Gi6vyDYqOQ= google.golang.org/genproto v0.0.0-20220923205249-dd2d53f1fffc h1:saaNe2+SBQxandnzcD/qB1JEBQ2Pqew+KlFLLdA/XcM= google.golang.org/genproto v0.0.0-20220923205249-dd2d53f1fffc/go.mod h1:yEEpwVWKMZZzo81NwRgyEJnA2fQvpXAYPVisv8EgDVs= -google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.0/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= @@ -1678,15 +1193,10 @@ google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA5 google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw= google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1709,30 +1219,21 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= -gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.6/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= @@ -1754,7 +1255,6 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.2.1/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY= k8s.io/api v0.25.2 h1:v6G8RyFcwf0HR5jQGIAYlvtRNrxMJQG1xJzaSeVnIS8= k8s.io/api v0.25.2/go.mod h1:qP1Rn4sCVFwx/xIhe+we2cwBLTXNcheRyYXwajonhy0= k8s.io/apiextensions-apiserver v0.25.2 h1:8uOQX17RE7XL02ngtnh3TgifY7EhekpK+/piwzQNnBo= @@ -1769,22 +1269,14 @@ k8s.io/client-go v0.25.2 h1:SUPp9p5CwM0yXGQrwYurw9LWz+YtMwhWd0GqOsSiefo= k8s.io/client-go v0.25.2/go.mod h1:i7cNU7N+yGQmJkewcRD2+Vuj4iz7b30kI8OcL3horQ4= k8s.io/component-base v0.25.2 h1:Nve/ZyHLUBHz1rqwkjXm/Re6IniNa5k7KgzxZpTfSQY= k8s.io/component-base v0.25.2/go.mod h1:90W21YMr+Yjg7MX+DohmZLzjsBtaxQDDwaX4YxDkl60= -k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= -k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20220803164354-a70c9af30aea h1:3QOH5+2fGsY8e1qf+GIFpg+zw/JGNrgyZRQR7/m6uWg= k8s.io/kube-openapi v0.0.0-20220803164354-a70c9af30aea/go.mod h1:C/N6wCaBHeBHkHUesQOQy2/MZqGgMAFPqGsGQLdbZBU= k8s.io/kubectl v0.25.2 h1:2993lTeVimxKSWx/7z2PiJxUILygRa3tmC4QhFaeioA= k8s.io/kubectl v0.25.2/go.mod h1:eoBGJtKUj7x38KXelz+dqVtbtbKwCqyKzJWmBHU0prg= -k8s.io/utils v0.0.0-20220823124924-e9cbc92d1a73 h1:H9TCJUUx+2VA0ZiD9lvtaX8fthFsMoD+Izn93E/hm8U= -k8s.io/utils v0.0.0-20220823124924-e9cbc92d1a73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20220922133306-665eaaec4324 h1:i+xdFemcSNuJvIfBlaYuXgRondKxK4z4prVPKzEaelI= k8s.io/utils v0.0.0-20220922133306-665eaaec4324/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -mvdan.cc/gofumpt v0.1.1/go.mod h1:yXG1r1WqZVKWbVRtBWKWX9+CxGYfA51nSomhM0woR48= -mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= -mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= -mvdan.cc/unparam v0.0.0-20210104141923-aac4ce9116a7/go.mod h1:hBpJkZE8H/sb+VRFvw2+rBpHNsTBcvSpk61hr8mzXZE= oras.land/oras-go v1.2.0 h1:yoKosVIbsPoFMqAIFHTnrmOuafHal+J/r+I5bdbVWu4= oras.land/oras-go v1.2.0/go.mod h1:pFNs7oHp2dYsYMSS82HaX5l4mpnGO7hbpPN6EWH2ltc= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= @@ -1792,8 +1284,6 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/kind v0.15.0 h1:Fskj234L4hjQlsScCgeYvCBIRt06cjLzc7+kbr1u8Tg= -sigs.k8s.io/kind v0.15.0/go.mod h1:cKTqagdRyUQmihhBOd+7p43DpOPRn9rHsUC08K1Jbsk= sigs.k8s.io/kind v0.16.0 h1:GFXyyxtPnHFKqXr3ZG8/X0+0K9sl69lejStlPn2WQyM= sigs.k8s.io/kind v0.16.0/go.mod h1:cKTqagdRyUQmihhBOd+7p43DpOPRn9rHsUC08K1Jbsk= sigs.k8s.io/kustomize/api v0.12.1 h1:7YM7gW3kYBwtKvoY216ZzY+8hM+lV53LUayghNRJ0vM= @@ -1802,6 +1292,5 @@ sigs.k8s.io/kustomize/kyaml v0.13.9 h1:Qz53EAaFFANyNgyOEJbT/yoIHygK40/ZcvU3rgry2 sigs.k8s.io/kustomize/kyaml v0.13.9/go.mod h1:QsRbD0/KcU+wdk0/L0fIp2KLnohkVzs6fQ85/nOXac4= sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= -sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index a5709038c..e0c30c28e 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -3,6 +3,7 @@ package deployment import ( "fmt" "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" @@ -119,6 +120,9 @@ func (di *DeploymentItem) render(forSeal bool) error { } var excludePatterns []string + if len(di.Project.Config.TemplateExcludes) != 0 { + status.Deprecation(di.ctx.Ctx, "template-excludes", "'templateExcludes' are deprecated, use .templateignore files instead.") + } excludePatterns = append(excludePatterns, di.Project.Config.TemplateExcludes...) err = filepath.WalkDir(*di.dir, func(p string, d fs.DirEntry, err error) error { if d.IsDir() { From 2ee42097e3045124cd96e98b5f46aa070c2ddedd Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 27 Sep 2022 14:05:58 +0200 Subject: [PATCH 1028/2916] fix: Remove traces of Helm installation from Dockerfile --- Dockerfile | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index 1585cdfb3..d312e8c6f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,4 @@ -FROM alpine:3.15 as builder - -RUN apk add --no-cache ca-certificates curl - -ARG ARCH=linux-amd64 - # We must use a glibc based distro due to embedded python not supporting musl libc for aarch64 FROM debian:bullseye-slim -COPY --from=builder /helm /usr/bin COPY kluctl /usr/bin/ ENTRYPOINT ["/usr/bin/kluctl"] From f9d6b8a2f36f5004a525c6ff31bc77c5e152114d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 27 Sep 2022 16:08:36 +0200 Subject: [PATCH 1029/2916] docs: Enhance documentation for `-a` --- cmd/kluctl/args/project.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/kluctl/args/project.go b/cmd/kluctl/args/project.go index 2639966fb..d2c933721 100644 --- a/cmd/kluctl/args/project.go +++ b/cmd/kluctl/args/project.go @@ -18,7 +18,7 @@ type ProjectFlags struct { } type ArgsFlags struct { - Arg []string `group:"project" short:"a" help:"Template argument in the form name=value"` + Arg []string `group:"project" short:"a" help:"Passes a template argument in the form of name=value. Nested args can be set with the '-a my.nested.arg=value' syntax. Values are interpreted as yaml values, meaning that 'true' and 'false' will lead to boolean values and numbers will be treated as numbers. Use quotes if you want these to be treated as strings."` } type TargetFlags struct { From 545bff744d53bcb0e2da15de57c3ed750a5ead2a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 27 Sep 2022 17:19:51 +0200 Subject: [PATCH 1030/2916] fix: Skip waiting on hooks when --no-wait was specified --- pkg/deployment/utils/hooks_util.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/deployment/utils/hooks_util.go b/pkg/deployment/utils/hooks_util.go index 9acbb6178..6ebcd928f 100644 --- a/pkg/deployment/utils/hooks_util.go +++ b/pkg/deployment/utils/hooks_util.go @@ -107,7 +107,7 @@ func (u *HooksUtil) RunHooks(hooks []*hook) { if u.a.HadError(ref) { continue } - if !h.wait { + if !h.wait || u.a.o.NoWait { continue } waitResults[ref] = u.a.WaitReadiness(ref, h.timeout) From 92163822f91267ee78f3c6b5ff209418e8426b8c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 29 Sep 2022 08:52:26 +0200 Subject: [PATCH 1031/2916] fix: Fix diffs pretending that an object could be applied to a non-existing ns We should only simulate creation of objects into new NSs if we know that the NS is actually created. --- pkg/deployment/utils/apply_utils.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index 58b32270f..bf1043c91 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -297,11 +297,15 @@ func (a *ApplyUtil) ApplyObject(x *uo.UnstructuredObject, replaced bool, hook bo x = x.Clone() x.SetK8sName(fmt.Sprintf("%s-%s", ref.Name, utils.RandomString(8))) } else if a.o.DryRun && remoteNamespace == nil && ref.Namespace != "" { - // The namespace does not exist, so let's pretend we deploy it to the default namespace with a dummy name - usesDummyName = true - x = x.Clone() - x.SetK8sName(fmt.Sprintf("%s-%s", ref.Name, utils.RandomString(8))) - x.SetK8sNamespace("default") + nsRef := k8s2.NewObjectRef("", "v1", "Namespace", ref.Namespace, "") + if _, ok := a.appliedObjects[nsRef]; ok { + // The namespace does not really exist, but would have been created if dryRun would be false. + // So let's pretend we deploy it to the default namespace with a dummy name + usesDummyName = true + x = x.Clone() + x.SetK8sName(fmt.Sprintf("%s-%s", ref.Name, utils.RandomString(8))) + x.SetK8sNamespace("default") + } } options := k8s.PatchOptions{ From fbfbdc564e7a5d4f1a868607d7e461e0c0ef1f32 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 29 Sep 2022 08:59:30 +0200 Subject: [PATCH 1032/2916] fix: Add ca-certificates to docker container This is required to make https git repos work. --- Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Dockerfile b/Dockerfile index d312e8c6f..b22c03a9a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,7 @@ # We must use a glibc based distro due to embedded python not supporting musl libc for aarch64 FROM debian:bullseye-slim + +RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates && rm -rf /var/lib/apt/lists/* + COPY kluctl /usr/bin/ ENTRYPOINT ["/usr/bin/kluctl"] From d6732bbe13653a50c70147c344968e27cd701fec Mon Sep 17 00:00:00 2001 From: Marko Petrovic <22802784+gitbluf@users.noreply.github.com> Date: Thu, 29 Sep 2022 09:21:22 +0200 Subject: [PATCH 1033/2916] feat: Add flux-kluctl-controller subcommands (#46) * Add: subcommands: flux-reconcile flux-suspend flux-resume * Add: print out revision * refactor: Add NewClientFactoryFromDefaultConfig helper method This method allows to create a client factory for the default kubeconfig. * refactor: Implement PatchObjectWithJsonPatch * Cherry pick suggested commits * Refactor: reconcile,resume,suspend Update: k8s_cluster.go helper fn * Remove: pkg/flux => helper functions moved to k8s * Add: helper functions: GetObjectSource GetObjectStatus WaitForReady * Refactor: flux-reconcile,suspend,resume * Add: Flux VerifyFlag function * Add: e2e test for flux commands * Update: test yaml resources, README * chore: Run go mod tidy * refactor: Add real "flux" sub-command instead of using flux-xxx commands * fix: Fix crash when adding nested sub-commands * fix: Adjust status output of flux sub-commands to be in the same style as other commands * tests: Fix flux tests to use correct sub-commands * fix: Fix patch type in PatchObject * Refactor: Move Flux unique functions to flux_cmd as helper functions Add Flux test asserts of actions Co-authored-by: Alexander Block --- .gitignore | 1 + cmd/kluctl/args/flux.go | 34 + cmd/kluctl/args/helm_credentials.go | 3 +- cmd/kluctl/commands/cmd_flux.go | 70 + cmd/kluctl/commands/cmd_flux_reconcile.go | 108 + cmd/kluctl/commands/cmd_flux_resume.go | 48 + cmd/kluctl/commands/cmd_flux_suspend.go | 47 + cmd/kluctl/commands/cobra_utils.go | 9 +- cmd/kluctl/commands/root.go | 19 +- e2e/flux_test.go | 65 + e2e/hooks_test.go | 5 +- e2e/project.go | 13 +- e2e/test_resources/README.md | 21 +- e2e/test_resources/flux-source-crd.yaml | 2623 +++++++++++++++++++++ e2e/test_resources/kluctl-crds.yaml | 1592 +++++++++++++ e2e/test_resources/kluctl-deployment.yaml | 30 + e2e/test_resources/resources.go | 3 +- e2e/utils.go | 9 +- pkg/k8s/client_factory.go | 17 + pkg/k8s/k8s_cluster.go | 40 +- 20 files changed, 4719 insertions(+), 38 deletions(-) create mode 100644 cmd/kluctl/args/flux.go create mode 100644 cmd/kluctl/commands/cmd_flux.go create mode 100644 cmd/kluctl/commands/cmd_flux_reconcile.go create mode 100644 cmd/kluctl/commands/cmd_flux_resume.go create mode 100644 cmd/kluctl/commands/cmd_flux_suspend.go create mode 100644 e2e/flux_test.go create mode 100644 e2e/test_resources/flux-source-crd.yaml create mode 100644 e2e/test_resources/kluctl-crds.yaml create mode 100644 e2e/test_resources/kluctl-deployment.yaml diff --git a/.gitignore b/.gitignore index 276ce77b0..8160eea20 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ /reports /vendor dist/ +.vscode \ No newline at end of file diff --git a/cmd/kluctl/args/flux.go b/cmd/kluctl/args/flux.go new file mode 100644 index 000000000..a1307a16a --- /dev/null +++ b/cmd/kluctl/args/flux.go @@ -0,0 +1,34 @@ +package args + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" +) + +type KluctlDeploymentFlags struct { + KluctlDeployment string `group:"flux" short:"k" help:"Name of the KluctlDeployment to interact with"` + Namespace string `group:"flux" short:"n" help:"Namespace where KluctlDeployment is located"` + WithSource bool `group:"flux" help:"--with-source will annotate Source object as well, triggering pulling"` + NoWait bool `group:"flux" help:"Don't wait for objects readiness'"` +} + +var KluctlDeploymentGVK = schema.GroupVersionKind{ + Group: "flux.kluctl.io", + Version: "v1alpha1", + Kind: "KluctlDeployment", +} + +var GitRepositoryGVK = schema.GroupVersionKind{ + Group: "source.toolkit.fluxcd.io", + Version: "v1beta2", + Kind: "GitRepository", +} + +func (cmd *KluctlDeploymentFlags) VerifyFlags() bool { + if cmd.KluctlDeployment == "" { + return false + } + if cmd.Namespace == "" { + cmd.Namespace = "default" + } + return true +} diff --git a/cmd/kluctl/args/helm_credentials.go b/cmd/kluctl/args/helm_credentials.go index 23db78c6d..47cd25feb 100644 --- a/cmd/kluctl/args/helm_credentials.go +++ b/cmd/kluctl/args/helm_credentials.go @@ -1,9 +1,10 @@ package args import ( + "strings" + "helm.sh/helm/v3/pkg/cli" "helm.sh/helm/v3/pkg/repo" - "strings" ) type HelmCredentials struct { diff --git a/cmd/kluctl/commands/cmd_flux.go b/cmd/kluctl/commands/cmd_flux.go new file mode 100644 index 000000000..2b68aa514 --- /dev/null +++ b/cmd/kluctl/commands/cmd_flux.go @@ -0,0 +1,70 @@ +package commands + +import ( + "context" + "fmt" + "time" + + kube "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/status" + "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" +) + +type fluxCmd struct { + Reconcile fluxReconcileCmd `cmd:"" help:"Reconcile KluctlDeployment"` + Suspend fluxSuspendCmd `cmd:"" help:"Suspend KluctlDeployment"` + Resume fluxResumeCmd `cmd:"" help:"Resume KluctlDeployment"` +} + +func WaitForReady(k *kube.K8sCluster, ref k8s.ObjectRef) (bool, error) { + o, _, err := k.GetSingleObject(ref) + if o == nil { + return false, err + } + retry := 0 + finalStatus := true + var errorMsg error + for s, err := GetObjectStatus(o); s != true; { + if retry >= 5 || err != nil { + finalStatus = false + errorMsg = err + break + } + retry++ + time.Sleep(8 * time.Second) + } + return finalStatus, errorMsg +} + +func GetObjectStatus(o *uo.UnstructuredObject) (bool, error) { + conditions, ok, err := o.GetNestedField("status", "conditions") + if !ok { + return false, err + } + for _, v := range conditions.([]interface{}) { + if (v.(map[string]interface{})["type"]) == "Ready" { + return true, nil + } + } + + return false, err +} + +func GetObjectSource(o *uo.UnstructuredObject, name string, namespace string, ctx context.Context) (string, string, error) { + status.Trace(ctx, "fetching %s in %s", name, namespace) + + sourceName, ok, err := o.GetNestedField("spec", "sourceRef", "name") + if !ok { + return "", "", err + } + + sourceNamespace, ok, err := o.GetNestedField("spec", "sourceRef", "namespace") + if !ok { + return "", "", err + } + + return fmt.Sprintf("%v", sourceName), + fmt.Sprintf("%v", sourceNamespace), + err +} diff --git a/cmd/kluctl/commands/cmd_flux_reconcile.go b/cmd/kluctl/commands/cmd_flux_reconcile.go new file mode 100644 index 000000000..a6ed024e2 --- /dev/null +++ b/cmd/kluctl/commands/cmd_flux_reconcile.go @@ -0,0 +1,108 @@ +package commands + +import ( + "context" + "fmt" + "os" + "time" + + "github.com/kluctl/kluctl/v2/cmd/kluctl/args" + "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/status" + k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" +) + +type fluxReconcileCmd struct { + args.KluctlDeploymentFlags +} + +func (cmd *fluxReconcileCmd) Run() error { + var ( + sourceNamespace string + sourceName string + ) + ns := cmd.KluctlDeploymentFlags.Namespace + kd := cmd.KluctlDeploymentFlags.KluctlDeployment + source := cmd.KluctlDeploymentFlags.WithSource + noWait := cmd.KluctlDeploymentFlags.NoWait + timestamp := time.Now().Format(time.RFC3339) + + cf, err := k8s.NewClientFactoryFromDefaultConfig(nil) + if err != nil { + return err + } + k, err := k8s.NewK8sCluster(context.TODO(), cf, false) + if err != nil { + return err + } + + ref := k8s2.ObjectRef{GVK: args.KluctlDeploymentGVK, Name: kd, Namespace: ns} + patch := []k8s.JsonPatch{{ + Op: "replace", + Path: "/metadata/annotations", + Value: map[string]string{ + "fluxcd.io/reconcileAt": timestamp, + }, + }} + + if source { + if !cmd.VerifyFlags() { + fmt.Println("KluctlDeployment name flag not provided..") + os.Exit(1) + } + o, _, err := k.GetSingleObject(ref) + if o == nil { + return err + } + + sourceName, sourceNamespace, err = GetObjectSource(o, kd, ns, context.TODO()) + if err != nil { + return err + } + ref2 := k8s2.ObjectRef{GVK: args.GitRepositoryGVK, Name: sourceName, Namespace: sourceNamespace} + + s := status.Start(cliCtx, "Annotating Source %s in %s namespace", sourceName, sourceNamespace) + defer s.Failed() + + _, _, err = k.PatchObjectWithJsonPatch(ref2, patch, k8s.PatchOptions{}) + if err != nil { + return err + } + s.Success() + + s = status.Start(cliCtx, "Waiting for Source %s to finish reconciliation", sourceName) + + if !noWait { + ready, err := WaitForReady(k, ref2) + if !ready { + s.FailedWithMessage("Failed while waiting for Source %s to get ready..", sourceName) + return err + } + } + + s.Success() + } + + s := status.Start(cliCtx, "Annotating KluctlDeployment %s in %s namespace", kd, ns) + defer s.Failed() + + _, _, err = k.PatchObjectWithJsonPatch(ref, patch, k8s.PatchOptions{}) + if err != nil { + return err + } + s.Success() + + s = status.Start(cliCtx, "Waiting for KluctlDeployment %s in %s namespace to finish reconciliation", kd, ns) + + if !noWait { + ready, err := WaitForReady(k, ref) + if !ready { + s.FailedWithMessage("Failed while waiting for KluctlDeployment %s to get ready..", kd) + return err + } + } + + s.Success() + + return err +} diff --git a/cmd/kluctl/commands/cmd_flux_resume.go b/cmd/kluctl/commands/cmd_flux_resume.go new file mode 100644 index 000000000..8b7aaafc8 --- /dev/null +++ b/cmd/kluctl/commands/cmd_flux_resume.go @@ -0,0 +1,48 @@ +package commands + +import ( + "context" + + "github.com/kluctl/kluctl/v2/cmd/kluctl/args" + "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/status" + k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" +) + +type fluxResumeCmd struct { + args.KluctlDeploymentFlags +} + +// TODO add reconciliation after resume +func (cmd *fluxResumeCmd) Run() error { + ns := cmd.KluctlDeploymentFlags.Namespace + kd := cmd.KluctlDeploymentFlags.KluctlDeployment + + cf, err := k8s.NewClientFactoryFromDefaultConfig(nil) + if err != nil { + return err + } + k, err := k8s.NewK8sCluster(context.TODO(), cf, false) + if err != nil { + return err + } + + ref := k8s2.ObjectRef{GVK: args.KluctlDeploymentGVK, Name: kd, Namespace: ns} + + patch := []k8s.JsonPatch{{ + Op: "replace", + Path: "/spec/suspend", + Value: false, + }} + + s := status.Start(cliCtx, "Resuming KluctlDeployment %s in %s namespace", kd, ns) + defer s.Failed() + + _, _, err = k.PatchObjectWithJsonPatch(ref, patch, k8s.PatchOptions{}) + if err != nil { + return err + } + s.Success() + + return err +} diff --git a/cmd/kluctl/commands/cmd_flux_suspend.go b/cmd/kluctl/commands/cmd_flux_suspend.go new file mode 100644 index 000000000..6e5422fb6 --- /dev/null +++ b/cmd/kluctl/commands/cmd_flux_suspend.go @@ -0,0 +1,47 @@ +package commands + +import ( + "context" + + "github.com/kluctl/kluctl/v2/cmd/kluctl/args" + "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/status" + k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" +) + +type fluxSuspendCmd struct { + args.KluctlDeploymentFlags +} + +func (cmd *fluxSuspendCmd) Run() error { + ns := cmd.KluctlDeploymentFlags.Namespace + kd := cmd.KluctlDeploymentFlags.KluctlDeployment + + cf, err := k8s.NewClientFactoryFromDefaultConfig(nil) + if err != nil { + return err + } + k, err := k8s.NewK8sCluster(context.TODO(), cf, false) + if err != nil { + return err + } + + ref := k8s2.ObjectRef{GVK: args.KluctlDeploymentGVK, Name: kd, Namespace: ns} + patch := []k8s.JsonPatch{{ + Op: "replace", + Path: "/spec/suspend", + Value: true, + }} + + s := status.Start(cliCtx, "Suspending KluctlDeployment %s in %s namespace", kd, ns) + defer s.Failed() + + _, _, err = k.PatchObjectWithJsonPatch(ref, patch, k8s.PatchOptions{}) + if err != nil { + return err + } + + s.Success() + + return err +} diff --git a/cmd/kluctl/commands/cobra_utils.go b/cmd/kluctl/commands/cobra_utils.go index 3f8bc6316..d495f58f6 100644 --- a/cmd/kluctl/commands/cobra_utils.go +++ b/cmd/kluctl/commands/cobra_utils.go @@ -66,11 +66,10 @@ func (c *rootCommand) buildCobraCmd(parent *commandAndGroups, cmdStruct interfac } runP, ok := cmdStruct.(runProvider) - if !ok { - panic(fmt.Sprintf("%s does not implement runProvider", name)) - } - cg.cmd.RunE = func(cmd *cobra.Command, args []string) error { - return runP.Run() + if ok { + cg.cmd.RunE = func(cmd *cobra.Command, args []string) error { + return runP.Run() + } } err := c.buildCobraSubCommands(cg, cmdStruct) diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index d98200898..e85358e86 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -18,6 +18,15 @@ package commands import ( "context" "fmt" + "io" + "log" + "net/http" + "os" + "path/filepath" + "runtime/pprof" + "strings" + "time" + "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" @@ -28,15 +37,7 @@ import ( "github.com/mattn/go-isatty" "github.com/spf13/cobra" "github.com/spf13/viper" - "io" "k8s.io/klog/v2" - "log" - "net/http" - "os" - "path/filepath" - "runtime/pprof" - "strings" - "time" ) const latestReleaseUrl = "https://api.github.com/repos/kluctl/kluctl/releases/latest" @@ -61,6 +62,7 @@ type cli struct { Render renderCmd `cmd:"" help:"Renders all resources and configuration files"` Seal sealCmd `cmd:"" help:"Seal secrets based on target's sealingConfig"` Validate validateCmd `cmd:"" help:"Validates the already deployed deployment"` + Flux fluxCmd `cmd:"" help:"Flux sub-commands"` Version versionCmd `cmd:"" help:"Print kluctl version"` } @@ -71,6 +73,7 @@ var flagGroups = []groupInfo{ {group: "images", title: "Image arguments:", description: "Control fixed images and update behaviour."}, {group: "inclusion", title: "Inclusion/Exclusion arguments:", description: "Control inclusion/exclusion."}, {group: "misc", title: "Misc arguments:", description: "Command specific arguments."}, + {group: "flux", title: "Flux arguments:", description: "EXPERIMENTAL: Subcommands for interaction with flux-kluctl-controller"}, } var cliCtx = context.Background() diff --git a/e2e/flux_test.go b/e2e/flux_test.go new file mode 100644 index 000000000..56bde84b3 --- /dev/null +++ b/e2e/flux_test.go @@ -0,0 +1,65 @@ +package e2e + +import ( + "fmt" + "testing" + + "github.com/kluctl/kluctl/v2/e2e/test_resources" + test_utils "github.com/kluctl/kluctl/v2/internal/test-utils" + "github.com/stretchr/testify/assert" +) + +func TestFluxCommands(t *testing.T) { + t.Parallel() + + k := defaultKindCluster1 + + p := &testProject{} + p.init(t, k, "simple") + + defer p.cleanup() + + test_resources.ApplyYaml("flux-source-crd.yaml", k) + test_resources.ApplyYaml("kluctl-crds.yaml", k) + test_resources.ApplyYaml("kluctl-deployment.yaml", k) + + assertResourceExists(t, k, "default", "kluctldeployment/microservices-demo-test") + + p.KluctlMust("flux", "suspend", "--namespace", "default", "--kluctl-deployment", "microservices-demo-test") + suspend := getKluctlSuspendField(t, k) + assert.Equal(t, true, suspend, "Field status.suspend is not false") + + p.KluctlMust("flux", "resume", "--namespace", "default", "--kluctl-deployment", "microservices-demo-test", "--no-wait") + resume := getKluctlSuspendField(t, k) + assert.Equal(t, false, resume, "Field status.suspend is not true") + + p.KluctlMust("flux", "reconcile", "--namespace", "default", "--kluctl-deployment", "microservices-demo-test", "--with-source", "--no-wait") + annotation := getKluctlAnnotations(t, k) + assert.Len(t, annotation, 1, "Annotation not present") +} + +func getKluctlSuspendField(t *testing.T, k *test_utils.KindCluster) interface{} { + o := k.KubectlYamlMust(t, "-n", "default", "get", "kluctldeployment", "microservices-demo-test") + result, ok, err := o.GetNestedField("spec", "suspend") + fmt.Println(result) + if err != nil { + t.Fatal(err) + } + if !ok { + t.Fatalf("result not found") + } + return result +} + +func getKluctlAnnotations(t *testing.T, k *test_utils.KindCluster) interface{} { + o := k.KubectlYamlMust(t, "-n", "default", "get", "kluctldeployment", "microservices-demo-test") + result, ok, err := o.GetNestedField("metadata", "annotations") + if err != nil { + t.Fatal(err) + } + if !ok { + t.Fatalf("result not found") + } + fmt.Println(result) + return result +} diff --git a/e2e/hooks_test.go b/e2e/hooks_test.go index 18149608c..d105b9a45 100644 --- a/e2e/hooks_test.go +++ b/e2e/hooks_test.go @@ -3,9 +3,10 @@ package e2e import ( "encoding/base64" "fmt" - "github.com/kluctl/kluctl/v2/internal/test-utils" - "github.com/kluctl/kluctl/v2/pkg/utils/uo" "testing" + + test_utils "github.com/kluctl/kluctl/v2/internal/test-utils" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" ) const hookScript = ` diff --git a/e2e/project.go b/e2e/project.go index be0bd2823..2b67e294a 100644 --- a/e2e/project.go +++ b/e2e/project.go @@ -2,6 +2,13 @@ package e2e import ( "fmt" + "os" + "os/exec" + "path/filepath" + "reflect" + "strings" + "testing" + "github.com/go-git/go-git/v5" "github.com/imdario/mergo" test_utils "github.com/kluctl/kluctl/v2/internal/test-utils" @@ -9,12 +16,6 @@ import ( "github.com/kluctl/kluctl/v2/pkg/yaml" "k8s.io/client-go/tools/clientcmd" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" - "os" - "os/exec" - "path/filepath" - "reflect" - "strings" - "testing" ) type testProject struct { diff --git a/e2e/test_resources/README.md b/e2e/test_resources/README.md index e3655fad6..a95af0258 100644 --- a/e2e/test_resources/README.md +++ b/e2e/test_resources/README.md @@ -1,9 +1,26 @@ # sealed-secrets.yaml - +```bash helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets helm template sealed-secrets-controller sealed-secrets/sealed-secrets -n kube-system --include-crds --skip-tests > sealed-secrets.yaml +``` # vault.yaml - +```bash helm repo add hashicorp https://helm.releases.hashicorp.com helm template vault hashicorp/vault -n vault -f vault-values.yaml --include-crds --skip-tests > vault.yaml +``` + +# kluctl-crds.yaml +Fetches flux-kluctl-controller crd definitions +```bash +kustomize build https://github.com/kluctl/flux-kluctl-controller/config/crd > kluctl-crds.yaml +``` + +# kluctl-deployment.yaml +This is a simple KluctlDeployment that is not really valid. It points to a non-existing GitRepository. This object is only used to test `kluctl flux` subcommands (which only sets annotations and updates field 'suspend'). + +# flux-source-crd.yaml +Fetches flux-source-controller crd definitions +```bash +kustomize build https://github.com/fluxcd/source-controller/config/crd > flux-source-crd.yaml +``` \ No newline at end of file diff --git a/e2e/test_resources/flux-source-crd.yaml b/e2e/test_resources/flux-source-crd.yaml new file mode 100644 index 000000000..4eda189ac --- /dev/null +++ b/e2e/test_resources/flux-source-crd.yaml @@ -0,0 +1,2623 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + name: buckets.source.toolkit.fluxcd.io +spec: + group: source.toolkit.fluxcd.io + names: + kind: Bucket + listKind: BucketList + plural: buckets + singular: bucket + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.endpoint + name: Endpoint + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].message + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: Bucket is the Schema for the buckets API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: BucketSpec defines the desired state of an S3 compatible + bucket + properties: + accessFrom: + description: AccessFrom defines an Access Control List for allowing + cross-namespace references to this object. + properties: + namespaceSelectors: + description: NamespaceSelectors is the list of namespace selectors + to which this ACL applies. Items in this list are evaluated + using a logical OR operation. + items: + description: NamespaceSelector selects the namespaces to which + this ACL applies. An empty map of MatchLabels matches all + namespaces in a cluster. + properties: + matchLabels: + additionalProperties: + type: string + description: MatchLabels is a map of {key,value} pairs. + A single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field is + "key", the operator is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + type: array + required: + - namespaceSelectors + type: object + bucketName: + description: The bucket name. + type: string + endpoint: + description: The bucket endpoint address. + type: string + ignore: + description: Ignore overrides the set of excluded patterns in the + .sourceignore format (which is the same as .gitignore). If not provided, + a default will be used, consult the documentation for your version + to find out what those are. + type: string + insecure: + description: Insecure allows connecting to a non-TLS S3 HTTP endpoint. + type: boolean + interval: + description: The interval at which to check for bucket updates. + type: string + provider: + default: generic + description: The S3 compatible storage provider name, default ('generic'). + enum: + - generic + - aws + - gcp + type: string + region: + description: The bucket region. + type: string + secretRef: + description: The name of the secret containing authentication credentials + for the Bucket. + properties: + name: + description: Name of the referent. + type: string + required: + - name + type: object + suspend: + description: This flag tells the controller to suspend the reconciliation + of this source. + type: boolean + timeout: + default: 60s + description: The timeout for download operations, defaults to 60s. + type: string + required: + - bucketName + - endpoint + - interval + type: object + status: + default: + observedGeneration: -1 + description: BucketStatus defines the observed state of a bucket + properties: + artifact: + description: Artifact represents the output of the last successful + Bucket sync. + properties: + checksum: + description: Checksum is the SHA256 checksum of the artifact. + type: string + lastUpdateTime: + description: LastUpdateTime is the timestamp corresponding to + the last update of this artifact. + format: date-time + type: string + path: + description: Path is the relative file path of this artifact. + type: string + revision: + description: Revision is a human readable identifier traceable + in the origin source system. It can be a Git commit SHA, Git + tag, a Helm index timestamp, a Helm chart version, etc. + type: string + url: + description: URL is the HTTP address of this artifact. + type: string + required: + - path + - url + type: object + conditions: + description: Conditions holds the conditions for the Bucket. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n \ttype FooStatus struct{ \t // Represents the observations + of a foo's current state. \t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\" \t // + +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map + \t // +listMapKey=type \t Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields + \t}" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + lastHandledReconcileAt: + description: LastHandledReconcileAt holds the value of the most recent + reconcile request value, so a change of the annotation value can + be detected. + type: string + observedGeneration: + description: ObservedGeneration is the last observed generation. + format: int64 + type: integer + url: + description: URL is the download link for the artifact output of the + last Bucket sync. + type: string + type: object + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.endpoint + name: Endpoint + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].message + name: Status + type: string + name: v1beta2 + schema: + openAPIV3Schema: + description: Bucket is the Schema for the buckets API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: BucketSpec specifies the required configuration to produce + an Artifact for an object storage bucket. + properties: + accessFrom: + description: 'AccessFrom specifies an Access Control List for allowing + cross-namespace references to this object. NOTE: Not implemented, + provisional as of https://github.com/fluxcd/flux2/pull/2092' + properties: + namespaceSelectors: + description: NamespaceSelectors is the list of namespace selectors + to which this ACL applies. Items in this list are evaluated + using a logical OR operation. + items: + description: NamespaceSelector selects the namespaces to which + this ACL applies. An empty map of MatchLabels matches all + namespaces in a cluster. + properties: + matchLabels: + additionalProperties: + type: string + description: MatchLabels is a map of {key,value} pairs. + A single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field is + "key", the operator is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + type: array + required: + - namespaceSelectors + type: object + bucketName: + description: BucketName is the name of the object storage bucket. + type: string + endpoint: + description: Endpoint is the object storage address the BucketName + is located at. + type: string + ignore: + description: Ignore overrides the set of excluded patterns in the + .sourceignore format (which is the same as .gitignore). If not provided, + a default will be used, consult the documentation for your version + to find out what those are. + type: string + insecure: + description: Insecure allows connecting to a non-TLS HTTP Endpoint. + type: boolean + interval: + description: Interval at which to check the Endpoint for updates. + type: string + provider: + default: generic + description: Provider of the object storage bucket. Defaults to 'generic', + which expects an S3 (API) compatible object storage. + enum: + - generic + - aws + - gcp + - azure + type: string + region: + description: Region of the Endpoint where the BucketName is located + in. + type: string + secretRef: + description: SecretRef specifies the Secret containing authentication + credentials for the Bucket. + properties: + name: + description: Name of the referent. + type: string + required: + - name + type: object + suspend: + description: Suspend tells the controller to suspend the reconciliation + of this Bucket. + type: boolean + timeout: + default: 60s + description: Timeout for fetch operations, defaults to 60s. + type: string + required: + - bucketName + - endpoint + - interval + type: object + status: + default: + observedGeneration: -1 + description: BucketStatus records the observed state of a Bucket. + properties: + artifact: + description: Artifact represents the last successful Bucket reconciliation. + properties: + checksum: + description: Checksum is the SHA256 checksum of the Artifact file. + type: string + lastUpdateTime: + description: LastUpdateTime is the timestamp corresponding to + the last update of the Artifact. + format: date-time + type: string + metadata: + additionalProperties: + type: string + description: Metadata holds upstream information such as OCI annotations. + type: object + path: + description: Path is the relative file path of the Artifact. It + can be used to locate the file in the root of the Artifact storage + on the local file system of the controller managing the Source. + type: string + revision: + description: Revision is a human-readable identifier traceable + in the origin source system. It can be a Git commit SHA, Git + tag, a Helm chart version, etc. + type: string + size: + description: Size is the number of bytes in the file. + format: int64 + type: integer + url: + description: URL is the HTTP address of the Artifact as exposed + by the controller managing the Source. It can be used to retrieve + the Artifact for consumption, e.g. by another controller applying + the Artifact contents. + type: string + required: + - path + - url + type: object + conditions: + description: Conditions holds the conditions for the Bucket. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n \ttype FooStatus struct{ \t // Represents the observations + of a foo's current state. \t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\" \t // + +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map + \t // +listMapKey=type \t Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields + \t}" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + lastHandledReconcileAt: + description: LastHandledReconcileAt holds the value of the most recent + reconcile request value, so a change of the annotation value can + be detected. + type: string + observedGeneration: + description: ObservedGeneration is the last observed generation of + the Bucket object. + format: int64 + type: integer + url: + description: URL is the dynamic fetch link for the latest Artifact. + It is provided on a "best effort" basis, and using the precise BucketStatus.Artifact + data is recommended. + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + name: gitrepositories.source.toolkit.fluxcd.io +spec: + group: source.toolkit.fluxcd.io + names: + kind: GitRepository + listKind: GitRepositoryList + plural: gitrepositories + shortNames: + - gitrepo + singular: gitrepository + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.url + name: URL + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].message + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: GitRepository is the Schema for the gitrepositories API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: GitRepositorySpec defines the desired state of a Git repository. + properties: + accessFrom: + description: AccessFrom defines an Access Control List for allowing + cross-namespace references to this object. + properties: + namespaceSelectors: + description: NamespaceSelectors is the list of namespace selectors + to which this ACL applies. Items in this list are evaluated + using a logical OR operation. + items: + description: NamespaceSelector selects the namespaces to which + this ACL applies. An empty map of MatchLabels matches all + namespaces in a cluster. + properties: + matchLabels: + additionalProperties: + type: string + description: MatchLabels is a map of {key,value} pairs. + A single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field is + "key", the operator is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + type: array + required: + - namespaceSelectors + type: object + gitImplementation: + default: go-git + description: Determines which git client library to use. Defaults + to go-git, valid values are ('go-git', 'libgit2'). + enum: + - go-git + - libgit2 + type: string + ignore: + description: Ignore overrides the set of excluded patterns in the + .sourceignore format (which is the same as .gitignore). If not provided, + a default will be used, consult the documentation for your version + to find out what those are. + type: string + include: + description: Extra git repositories to map into the repository + items: + description: GitRepositoryInclude defines a source with a from and + to path. + properties: + fromPath: + description: The path to copy contents from, defaults to the + root directory. + type: string + repository: + description: Reference to a GitRepository to include. + properties: + name: + description: Name of the referent. + type: string + required: + - name + type: object + toPath: + description: The path to copy contents to, defaults to the name + of the source ref. + type: string + required: + - repository + type: object + type: array + interval: + description: The interval at which to check for repository updates. + type: string + recurseSubmodules: + description: When enabled, after the clone is created, initializes + all submodules within, using their default settings. This option + is available only when using the 'go-git' GitImplementation. + type: boolean + ref: + description: The Git reference to checkout and monitor for changes, + defaults to master branch. + properties: + branch: + description: The Git branch to checkout, defaults to master. + type: string + commit: + description: The Git commit SHA to checkout, if specified Tag + filters will be ignored. + type: string + semver: + description: The Git tag semver expression, takes precedence over + Tag. + type: string + tag: + description: The Git tag to checkout, takes precedence over Branch. + type: string + type: object + secretRef: + description: The secret name containing the Git credentials. For HTTPS + repositories the secret must contain username and password fields. + For SSH repositories the secret must contain identity and known_hosts + fields. + properties: + name: + description: Name of the referent. + type: string + required: + - name + type: object + suspend: + description: This flag tells the controller to suspend the reconciliation + of this source. + type: boolean + timeout: + default: 60s + description: The timeout for remote Git operations like cloning, defaults + to 60s. + type: string + url: + description: The repository URL, can be a HTTP/S or SSH address. + pattern: ^(http|https|ssh)://.*$ + type: string + verify: + description: Verify OpenPGP signature for the Git commit HEAD points + to. + properties: + mode: + description: Mode describes what git object should be verified, + currently ('head'). + enum: + - head + type: string + secretRef: + description: The secret name containing the public keys of all + trusted Git authors. + properties: + name: + description: Name of the referent. + type: string + required: + - name + type: object + required: + - mode + type: object + required: + - interval + - url + type: object + status: + default: + observedGeneration: -1 + description: GitRepositoryStatus defines the observed state of a Git repository. + properties: + artifact: + description: Artifact represents the output of the last successful + repository sync. + properties: + checksum: + description: Checksum is the SHA256 checksum of the artifact. + type: string + lastUpdateTime: + description: LastUpdateTime is the timestamp corresponding to + the last update of this artifact. + format: date-time + type: string + path: + description: Path is the relative file path of this artifact. + type: string + revision: + description: Revision is a human readable identifier traceable + in the origin source system. It can be a Git commit SHA, Git + tag, a Helm index timestamp, a Helm chart version, etc. + type: string + url: + description: URL is the HTTP address of this artifact. + type: string + required: + - path + - url + type: object + conditions: + description: Conditions holds the conditions for the GitRepository. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n \ttype FooStatus struct{ \t // Represents the observations + of a foo's current state. \t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\" \t // + +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map + \t // +listMapKey=type \t Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields + \t}" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + includedArtifacts: + description: IncludedArtifacts represents the included artifacts from + the last successful repository sync. + items: + description: Artifact represents the output of a source synchronisation. + properties: + checksum: + description: Checksum is the SHA256 checksum of the artifact. + type: string + lastUpdateTime: + description: LastUpdateTime is the timestamp corresponding to + the last update of this artifact. + format: date-time + type: string + path: + description: Path is the relative file path of this artifact. + type: string + revision: + description: Revision is a human readable identifier traceable + in the origin source system. It can be a Git commit SHA, Git + tag, a Helm index timestamp, a Helm chart version, etc. + type: string + url: + description: URL is the HTTP address of this artifact. + type: string + required: + - path + - url + type: object + type: array + lastHandledReconcileAt: + description: LastHandledReconcileAt holds the value of the most recent + reconcile request value, so a change of the annotation value can + be detected. + type: string + observedGeneration: + description: ObservedGeneration is the last observed generation. + format: int64 + type: integer + url: + description: URL is the download link for the artifact output of the + last repository sync. + type: string + type: object + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.url + name: URL + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].message + name: Status + type: string + name: v1beta2 + schema: + openAPIV3Schema: + description: GitRepository is the Schema for the gitrepositories API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: GitRepositorySpec specifies the required configuration to + produce an Artifact for a Git repository. + properties: + accessFrom: + description: 'AccessFrom specifies an Access Control List for allowing + cross-namespace references to this object. NOTE: Not implemented, + provisional as of https://github.com/fluxcd/flux2/pull/2092' + properties: + namespaceSelectors: + description: NamespaceSelectors is the list of namespace selectors + to which this ACL applies. Items in this list are evaluated + using a logical OR operation. + items: + description: NamespaceSelector selects the namespaces to which + this ACL applies. An empty map of MatchLabels matches all + namespaces in a cluster. + properties: + matchLabels: + additionalProperties: + type: string + description: MatchLabels is a map of {key,value} pairs. + A single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field is + "key", the operator is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + type: array + required: + - namespaceSelectors + type: object + gitImplementation: + default: go-git + description: GitImplementation specifies which Git client library + implementation to use. Defaults to 'go-git', valid values are ('go-git', + 'libgit2'). + enum: + - go-git + - libgit2 + type: string + ignore: + description: Ignore overrides the set of excluded patterns in the + .sourceignore format (which is the same as .gitignore). If not provided, + a default will be used, consult the documentation for your version + to find out what those are. + type: string + include: + description: Include specifies a list of GitRepository resources which + Artifacts should be included in the Artifact produced for this GitRepository. + items: + description: GitRepositoryInclude specifies a local reference to + a GitRepository which Artifact (sub-)contents must be included, + and where they should be placed. + properties: + fromPath: + description: FromPath specifies the path to copy contents from, + defaults to the root of the Artifact. + type: string + repository: + description: GitRepositoryRef specifies the GitRepository which + Artifact contents must be included. + properties: + name: + description: Name of the referent. + type: string + required: + - name + type: object + toPath: + description: ToPath specifies the path to copy contents to, + defaults to the name of the GitRepositoryRef. + type: string + required: + - repository + type: object + type: array + interval: + description: Interval at which to check the GitRepository for updates. + type: string + recurseSubmodules: + description: RecurseSubmodules enables the initialization of all submodules + within the GitRepository as cloned from the URL, using their default + settings. This option is available only when using the 'go-git' + GitImplementation. + type: boolean + ref: + description: Reference specifies the Git reference to resolve and + monitor for changes, defaults to the 'master' branch. + properties: + branch: + description: "Branch to check out, defaults to 'master' if no + other field is defined. \n When GitRepositorySpec.GitImplementation + is set to 'go-git', a shallow clone of the specified branch + is performed." + type: string + commit: + description: "Commit SHA to check out, takes precedence over all + reference fields. \n When GitRepositorySpec.GitImplementation + is set to 'go-git', this can be combined with Branch to shallow + clone the branch, in which the commit is expected to exist." + type: string + semver: + description: SemVer tag expression to check out, takes precedence + over Tag. + type: string + tag: + description: Tag to check out, takes precedence over Branch. + type: string + type: object + secretRef: + description: SecretRef specifies the Secret containing authentication + credentials for the GitRepository. For HTTPS repositories the Secret + must contain 'username' and 'password' fields. For SSH repositories + the Secret must contain 'identity' and 'known_hosts' fields. + properties: + name: + description: Name of the referent. + type: string + required: + - name + type: object + suspend: + description: Suspend tells the controller to suspend the reconciliation + of this GitRepository. + type: boolean + timeout: + default: 60s + description: Timeout for Git operations like cloning, defaults to + 60s. + type: string + url: + description: URL specifies the Git repository URL, it can be an HTTP/S + or SSH address. + pattern: ^(http|https|ssh)://.*$ + type: string + verify: + description: Verification specifies the configuration to verify the + Git commit signature(s). + properties: + mode: + description: Mode specifies what Git object should be verified, + currently ('head'). + enum: + - head + type: string + secretRef: + description: SecretRef specifies the Secret containing the public + keys of trusted Git authors. + properties: + name: + description: Name of the referent. + type: string + required: + - name + type: object + required: + - mode + type: object + required: + - interval + - url + type: object + status: + default: + observedGeneration: -1 + description: GitRepositoryStatus records the observed state of a Git repository. + properties: + artifact: + description: Artifact represents the last successful GitRepository + reconciliation. + properties: + checksum: + description: Checksum is the SHA256 checksum of the Artifact file. + type: string + lastUpdateTime: + description: LastUpdateTime is the timestamp corresponding to + the last update of the Artifact. + format: date-time + type: string + metadata: + additionalProperties: + type: string + description: Metadata holds upstream information such as OCI annotations. + type: object + path: + description: Path is the relative file path of the Artifact. It + can be used to locate the file in the root of the Artifact storage + on the local file system of the controller managing the Source. + type: string + revision: + description: Revision is a human-readable identifier traceable + in the origin source system. It can be a Git commit SHA, Git + tag, a Helm chart version, etc. + type: string + size: + description: Size is the number of bytes in the file. + format: int64 + type: integer + url: + description: URL is the HTTP address of the Artifact as exposed + by the controller managing the Source. It can be used to retrieve + the Artifact for consumption, e.g. by another controller applying + the Artifact contents. + type: string + required: + - path + - url + type: object + conditions: + description: Conditions holds the conditions for the GitRepository. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n \ttype FooStatus struct{ \t // Represents the observations + of a foo's current state. \t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\" \t // + +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map + \t // +listMapKey=type \t Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields + \t}" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + contentConfigChecksum: + description: 'ContentConfigChecksum is a checksum of all the configurations + related to the content of the source artifact: - .spec.ignore - + .spec.recurseSubmodules - .spec.included and the checksum of the + included artifacts observed in .status.observedGeneration version + of the object. This can be used to determine if the content of the + included repository has changed. It has the format of `:`, + for example: `sha256:`.' + type: string + includedArtifacts: + description: IncludedArtifacts contains a list of the last successfully + included Artifacts as instructed by GitRepositorySpec.Include. + items: + description: Artifact represents the output of a Source reconciliation. + properties: + checksum: + description: Checksum is the SHA256 checksum of the Artifact + file. + type: string + lastUpdateTime: + description: LastUpdateTime is the timestamp corresponding to + the last update of the Artifact. + format: date-time + type: string + metadata: + additionalProperties: + type: string + description: Metadata holds upstream information such as OCI + annotations. + type: object + path: + description: Path is the relative file path of the Artifact. + It can be used to locate the file in the root of the Artifact + storage on the local file system of the controller managing + the Source. + type: string + revision: + description: Revision is a human-readable identifier traceable + in the origin source system. It can be a Git commit SHA, Git + tag, a Helm chart version, etc. + type: string + size: + description: Size is the number of bytes in the file. + format: int64 + type: integer + url: + description: URL is the HTTP address of the Artifact as exposed + by the controller managing the Source. It can be used to retrieve + the Artifact for consumption, e.g. by another controller applying + the Artifact contents. + type: string + required: + - path + - url + type: object + type: array + lastHandledReconcileAt: + description: LastHandledReconcileAt holds the value of the most recent + reconcile request value, so a change of the annotation value can + be detected. + type: string + observedGeneration: + description: ObservedGeneration is the last observed generation of + the GitRepository object. + format: int64 + type: integer + url: + description: URL is the dynamic fetch link for the latest Artifact. + It is provided on a "best effort" basis, and using the precise GitRepositoryStatus.Artifact + data is recommended. + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + name: helmcharts.source.toolkit.fluxcd.io +spec: + group: source.toolkit.fluxcd.io + names: + kind: HelmChart + listKind: HelmChartList + plural: helmcharts + shortNames: + - hc + singular: helmchart + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.chart + name: Chart + type: string + - jsonPath: .spec.version + name: Version + type: string + - jsonPath: .spec.sourceRef.kind + name: Source Kind + type: string + - jsonPath: .spec.sourceRef.name + name: Source Name + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].message + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: HelmChart is the Schema for the helmcharts API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: HelmChartSpec defines the desired state of a Helm chart. + properties: + accessFrom: + description: AccessFrom defines an Access Control List for allowing + cross-namespace references to this object. + properties: + namespaceSelectors: + description: NamespaceSelectors is the list of namespace selectors + to which this ACL applies. Items in this list are evaluated + using a logical OR operation. + items: + description: NamespaceSelector selects the namespaces to which + this ACL applies. An empty map of MatchLabels matches all + namespaces in a cluster. + properties: + matchLabels: + additionalProperties: + type: string + description: MatchLabels is a map of {key,value} pairs. + A single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field is + "key", the operator is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + type: array + required: + - namespaceSelectors + type: object + chart: + description: The name or path the Helm chart is available at in the + SourceRef. + type: string + interval: + description: The interval at which to check the Source for updates. + type: string + reconcileStrategy: + default: ChartVersion + description: Determines what enables the creation of a new artifact. + Valid values are ('ChartVersion', 'Revision'). See the documentation + of the values for an explanation on their behavior. Defaults to + ChartVersion when omitted. + enum: + - ChartVersion + - Revision + type: string + sourceRef: + description: The reference to the Source the chart is available at. + properties: + apiVersion: + description: APIVersion of the referent. + type: string + kind: + description: Kind of the referent, valid values are ('HelmRepository', + 'GitRepository', 'Bucket'). + enum: + - HelmRepository + - GitRepository + - Bucket + type: string + name: + description: Name of the referent. + type: string + required: + - kind + - name + type: object + suspend: + description: This flag tells the controller to suspend the reconciliation + of this source. + type: boolean + valuesFile: + description: Alternative values file to use as the default chart values, + expected to be a relative path in the SourceRef. Deprecated in favor + of ValuesFiles, for backwards compatibility the file defined here + is merged before the ValuesFiles items. Ignored when omitted. + type: string + valuesFiles: + description: Alternative list of values files to use as the chart + values (values.yaml is not included by default), expected to be + a relative path in the SourceRef. Values files are merged in the + order of this list with the last file overriding the first. Ignored + when omitted. + items: + type: string + type: array + version: + default: '*' + description: The chart version semver expression, ignored for charts + from GitRepository and Bucket sources. Defaults to latest when omitted. + type: string + required: + - chart + - interval + - sourceRef + type: object + status: + default: + observedGeneration: -1 + description: HelmChartStatus defines the observed state of the HelmChart. + properties: + artifact: + description: Artifact represents the output of the last successful + chart sync. + properties: + checksum: + description: Checksum is the SHA256 checksum of the artifact. + type: string + lastUpdateTime: + description: LastUpdateTime is the timestamp corresponding to + the last update of this artifact. + format: date-time + type: string + path: + description: Path is the relative file path of this artifact. + type: string + revision: + description: Revision is a human readable identifier traceable + in the origin source system. It can be a Git commit SHA, Git + tag, a Helm index timestamp, a Helm chart version, etc. + type: string + url: + description: URL is the HTTP address of this artifact. + type: string + required: + - path + - url + type: object + conditions: + description: Conditions holds the conditions for the HelmChart. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n \ttype FooStatus struct{ \t // Represents the observations + of a foo's current state. \t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\" \t // + +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map + \t // +listMapKey=type \t Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields + \t}" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + lastHandledReconcileAt: + description: LastHandledReconcileAt holds the value of the most recent + reconcile request value, so a change of the annotation value can + be detected. + type: string + observedGeneration: + description: ObservedGeneration is the last observed generation. + format: int64 + type: integer + url: + description: URL is the download link for the last chart pulled. + type: string + type: object + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.chart + name: Chart + type: string + - jsonPath: .spec.version + name: Version + type: string + - jsonPath: .spec.sourceRef.kind + name: Source Kind + type: string + - jsonPath: .spec.sourceRef.name + name: Source Name + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].message + name: Status + type: string + name: v1beta2 + schema: + openAPIV3Schema: + description: HelmChart is the Schema for the helmcharts API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: HelmChartSpec specifies the desired state of a Helm chart. + properties: + accessFrom: + description: 'AccessFrom specifies an Access Control List for allowing + cross-namespace references to this object. NOTE: Not implemented, + provisional as of https://github.com/fluxcd/flux2/pull/2092' + properties: + namespaceSelectors: + description: NamespaceSelectors is the list of namespace selectors + to which this ACL applies. Items in this list are evaluated + using a logical OR operation. + items: + description: NamespaceSelector selects the namespaces to which + this ACL applies. An empty map of MatchLabels matches all + namespaces in a cluster. + properties: + matchLabels: + additionalProperties: + type: string + description: MatchLabels is a map of {key,value} pairs. + A single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field is + "key", the operator is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + type: array + required: + - namespaceSelectors + type: object + chart: + description: Chart is the name or path the Helm chart is available + at in the SourceRef. + type: string + interval: + description: Interval is the interval at which to check the Source + for updates. + type: string + reconcileStrategy: + default: ChartVersion + description: ReconcileStrategy determines what enables the creation + of a new artifact. Valid values are ('ChartVersion', 'Revision'). + See the documentation of the values for an explanation on their + behavior. Defaults to ChartVersion when omitted. + enum: + - ChartVersion + - Revision + type: string + sourceRef: + description: SourceRef is the reference to the Source the chart is + available at. + properties: + apiVersion: + description: APIVersion of the referent. + type: string + kind: + description: Kind of the referent, valid values are ('HelmRepository', + 'GitRepository', 'Bucket'). + enum: + - HelmRepository + - GitRepository + - Bucket + type: string + name: + description: Name of the referent. + type: string + required: + - kind + - name + type: object + suspend: + description: Suspend tells the controller to suspend the reconciliation + of this source. + type: boolean + valuesFile: + description: ValuesFile is an alternative values file to use as the + default chart values, expected to be a relative path in the SourceRef. + Deprecated in favor of ValuesFiles, for backwards compatibility + the file specified here is merged before the ValuesFiles items. + Ignored when omitted. + type: string + valuesFiles: + description: ValuesFiles is an alternative list of values files to + use as the chart values (values.yaml is not included by default), + expected to be a relative path in the SourceRef. Values files are + merged in the order of this list with the last file overriding the + first. Ignored when omitted. + items: + type: string + type: array + version: + default: '*' + description: Version is the chart version semver expression, ignored + for charts from GitRepository and Bucket sources. Defaults to latest + when omitted. + type: string + required: + - chart + - interval + - sourceRef + type: object + status: + default: + observedGeneration: -1 + description: HelmChartStatus records the observed state of the HelmChart. + properties: + artifact: + description: Artifact represents the output of the last successful + reconciliation. + properties: + checksum: + description: Checksum is the SHA256 checksum of the Artifact file. + type: string + lastUpdateTime: + description: LastUpdateTime is the timestamp corresponding to + the last update of the Artifact. + format: date-time + type: string + metadata: + additionalProperties: + type: string + description: Metadata holds upstream information such as OCI annotations. + type: object + path: + description: Path is the relative file path of the Artifact. It + can be used to locate the file in the root of the Artifact storage + on the local file system of the controller managing the Source. + type: string + revision: + description: Revision is a human-readable identifier traceable + in the origin source system. It can be a Git commit SHA, Git + tag, a Helm chart version, etc. + type: string + size: + description: Size is the number of bytes in the file. + format: int64 + type: integer + url: + description: URL is the HTTP address of the Artifact as exposed + by the controller managing the Source. It can be used to retrieve + the Artifact for consumption, e.g. by another controller applying + the Artifact contents. + type: string + required: + - path + - url + type: object + conditions: + description: Conditions holds the conditions for the HelmChart. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n \ttype FooStatus struct{ \t // Represents the observations + of a foo's current state. \t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\" \t // + +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map + \t // +listMapKey=type \t Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields + \t}" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + lastHandledReconcileAt: + description: LastHandledReconcileAt holds the value of the most recent + reconcile request value, so a change of the annotation value can + be detected. + type: string + observedChartName: + description: ObservedChartName is the last observed chart name as + specified by the resolved chart reference. + type: string + observedGeneration: + description: ObservedGeneration is the last observed generation of + the HelmChart object. + format: int64 + type: integer + observedSourceArtifactRevision: + description: ObservedSourceArtifactRevision is the last observed Artifact.Revision + of the HelmChartSpec.SourceRef. + type: string + url: + description: URL is the dynamic fetch link for the latest Artifact. + It is provided on a "best effort" basis, and using the precise BucketStatus.Artifact + data is recommended. + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + name: helmrepositories.source.toolkit.fluxcd.io +spec: + group: source.toolkit.fluxcd.io + names: + kind: HelmRepository + listKind: HelmRepositoryList + plural: helmrepositories + shortNames: + - helmrepo + singular: helmrepository + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.url + name: URL + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].message + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: HelmRepository is the Schema for the helmrepositories API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: HelmRepositorySpec defines the reference to a Helm repository. + properties: + accessFrom: + description: AccessFrom defines an Access Control List for allowing + cross-namespace references to this object. + properties: + namespaceSelectors: + description: NamespaceSelectors is the list of namespace selectors + to which this ACL applies. Items in this list are evaluated + using a logical OR operation. + items: + description: NamespaceSelector selects the namespaces to which + this ACL applies. An empty map of MatchLabels matches all + namespaces in a cluster. + properties: + matchLabels: + additionalProperties: + type: string + description: MatchLabels is a map of {key,value} pairs. + A single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field is + "key", the operator is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + type: array + required: + - namespaceSelectors + type: object + interval: + description: The interval at which to check the upstream for updates. + type: string + passCredentials: + description: PassCredentials allows the credentials from the SecretRef + to be passed on to a host that does not match the host as defined + in URL. This may be required if the host of the advertised chart + URLs in the index differ from the defined URL. Enabling this should + be done with caution, as it can potentially result in credentials + getting stolen in a MITM-attack. + type: boolean + secretRef: + description: The name of the secret containing authentication credentials + for the Helm repository. For HTTP/S basic auth the secret must contain + username and password fields. For TLS the secret must contain a + certFile and keyFile, and/or caCert fields. + properties: + name: + description: Name of the referent. + type: string + required: + - name + type: object + suspend: + description: This flag tells the controller to suspend the reconciliation + of this source. + type: boolean + timeout: + default: 60s + description: The timeout of index downloading, defaults to 60s. + type: string + url: + description: The Helm repository URL, a valid URL contains at least + a protocol and host. + type: string + required: + - interval + - url + type: object + status: + default: + observedGeneration: -1 + description: HelmRepositoryStatus defines the observed state of the HelmRepository. + properties: + artifact: + description: Artifact represents the output of the last successful + repository sync. + properties: + checksum: + description: Checksum is the SHA256 checksum of the artifact. + type: string + lastUpdateTime: + description: LastUpdateTime is the timestamp corresponding to + the last update of this artifact. + format: date-time + type: string + path: + description: Path is the relative file path of this artifact. + type: string + revision: + description: Revision is a human readable identifier traceable + in the origin source system. It can be a Git commit SHA, Git + tag, a Helm index timestamp, a Helm chart version, etc. + type: string + url: + description: URL is the HTTP address of this artifact. + type: string + required: + - path + - url + type: object + conditions: + description: Conditions holds the conditions for the HelmRepository. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n \ttype FooStatus struct{ \t // Represents the observations + of a foo's current state. \t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\" \t // + +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map + \t // +listMapKey=type \t Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields + \t}" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + lastHandledReconcileAt: + description: LastHandledReconcileAt holds the value of the most recent + reconcile request value, so a change of the annotation value can + be detected. + type: string + observedGeneration: + description: ObservedGeneration is the last observed generation. + format: int64 + type: integer + url: + description: URL is the download link for the last index fetched. + type: string + type: object + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.url + name: URL + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].message + name: Status + type: string + name: v1beta2 + schema: + openAPIV3Schema: + description: HelmRepository is the Schema for the helmrepositories API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: HelmRepositorySpec specifies the required configuration to + produce an Artifact for a Helm repository index YAML. + properties: + accessFrom: + description: 'AccessFrom specifies an Access Control List for allowing + cross-namespace references to this object. NOTE: Not implemented, + provisional as of https://github.com/fluxcd/flux2/pull/2092' + properties: + namespaceSelectors: + description: NamespaceSelectors is the list of namespace selectors + to which this ACL applies. Items in this list are evaluated + using a logical OR operation. + items: + description: NamespaceSelector selects the namespaces to which + this ACL applies. An empty map of MatchLabels matches all + namespaces in a cluster. + properties: + matchLabels: + additionalProperties: + type: string + description: MatchLabels is a map of {key,value} pairs. + A single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field is + "key", the operator is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + type: array + required: + - namespaceSelectors + type: object + interval: + description: Interval at which to check the URL for updates. + type: string + passCredentials: + description: PassCredentials allows the credentials from the SecretRef + to be passed on to a host that does not match the host as defined + in URL. This may be required if the host of the advertised chart + URLs in the index differ from the defined URL. Enabling this should + be done with caution, as it can potentially result in credentials + getting stolen in a MITM-attack. + type: boolean + provider: + default: generic + description: Provider used for authentication, can be 'aws', 'azure', + 'gcp' or 'generic'. This field is optional, and only taken into + account if the .spec.type field is set to 'oci'. When not specified, + defaults to 'generic'. + enum: + - generic + - aws + - azure + - gcp + type: string + secretRef: + description: SecretRef specifies the Secret containing authentication + credentials for the HelmRepository. For HTTP/S basic auth the secret + must contain 'username' and 'password' fields. For TLS the secret + must contain a 'certFile' and 'keyFile', and/or 'caCert' fields. + properties: + name: + description: Name of the referent. + type: string + required: + - name + type: object + suspend: + description: Suspend tells the controller to suspend the reconciliation + of this HelmRepository. + type: boolean + timeout: + default: 60s + description: Timeout is used for the index fetch operation for an + HTTPS helm repository, and for remote OCI Repository operations + like pulling for an OCI helm repository. Its default value is 60s. + type: string + type: + description: Type of the HelmRepository. When this field is set to "oci", + the URL field value must be prefixed with "oci://". + enum: + - default + - oci + type: string + url: + description: URL of the Helm repository, a valid URL contains at least + a protocol and host. + type: string + required: + - interval + - url + type: object + status: + default: + observedGeneration: -1 + description: HelmRepositoryStatus records the observed state of the HelmRepository. + properties: + artifact: + description: Artifact represents the last successful HelmRepository + reconciliation. + properties: + checksum: + description: Checksum is the SHA256 checksum of the Artifact file. + type: string + lastUpdateTime: + description: LastUpdateTime is the timestamp corresponding to + the last update of the Artifact. + format: date-time + type: string + metadata: + additionalProperties: + type: string + description: Metadata holds upstream information such as OCI annotations. + type: object + path: + description: Path is the relative file path of the Artifact. It + can be used to locate the file in the root of the Artifact storage + on the local file system of the controller managing the Source. + type: string + revision: + description: Revision is a human-readable identifier traceable + in the origin source system. It can be a Git commit SHA, Git + tag, a Helm chart version, etc. + type: string + size: + description: Size is the number of bytes in the file. + format: int64 + type: integer + url: + description: URL is the HTTP address of the Artifact as exposed + by the controller managing the Source. It can be used to retrieve + the Artifact for consumption, e.g. by another controller applying + the Artifact contents. + type: string + required: + - path + - url + type: object + conditions: + description: Conditions holds the conditions for the HelmRepository. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n \ttype FooStatus struct{ \t // Represents the observations + of a foo's current state. \t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\" \t // + +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map + \t // +listMapKey=type \t Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields + \t}" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + lastHandledReconcileAt: + description: LastHandledReconcileAt holds the value of the most recent + reconcile request value, so a change of the annotation value can + be detected. + type: string + observedGeneration: + description: ObservedGeneration is the last observed generation of + the HelmRepository object. + format: int64 + type: integer + url: + description: URL is the dynamic fetch link for the latest Artifact. + It is provided on a "best effort" basis, and using the precise HelmRepositoryStatus.Artifact + data is recommended. + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + name: ocirepositories.source.toolkit.fluxcd.io +spec: + group: source.toolkit.fluxcd.io + names: + kind: OCIRepository + listKind: OCIRepositoryList + plural: ocirepositories + shortNames: + - ocirepo + singular: ocirepository + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.url + name: URL + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].message + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta2 + schema: + openAPIV3Schema: + description: OCIRepository is the Schema for the ocirepositories API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: OCIRepositorySpec defines the desired state of OCIRepository + properties: + certSecretRef: + description: "CertSecretRef can be given the name of a secret containing + either or both of \n - a PEM-encoded client certificate (`certFile`) + and private key (`keyFile`); - a PEM-encoded CA certificate (`caFile`) + \n and whichever are supplied, will be used for connecting to the + \ registry. The client cert and key are useful if you are authenticating + with a certificate; the CA cert is useful if you are using a self-signed + server certificate." + properties: + name: + description: Name of the referent. + type: string + required: + - name + type: object + ignore: + description: Ignore overrides the set of excluded patterns in the + .sourceignore format (which is the same as .gitignore). If not provided, + a default will be used, consult the documentation for your version + to find out what those are. + type: string + insecure: + description: Insecure allows connecting to a non-TLS HTTP container + registry. + type: boolean + interval: + description: The interval at which to check for image updates. + type: string + layerSelector: + description: LayerSelector specifies which layer should be extracted + from the OCI artifact. When not specified, the first layer found + in the artifact is selected. + properties: + mediaType: + description: MediaType specifies the OCI media type of the layer + which should be extracted from the OCI Artifact. The first layer + matching this type is selected. + type: string + type: object + provider: + default: generic + description: The provider used for authentication, can be 'aws', 'azure', + 'gcp' or 'generic'. When not specified, defaults to 'generic'. + enum: + - generic + - aws + - azure + - gcp + type: string + ref: + description: The OCI reference to pull and monitor for changes, defaults + to the latest tag. + properties: + digest: + description: Digest is the image digest to pull, takes precedence + over SemVer. The value should be in the format 'sha256:'. + type: string + semver: + description: SemVer is the range of tags to pull selecting the + latest within the range, takes precedence over Tag. + type: string + tag: + description: Tag is the image tag to pull, defaults to latest. + type: string + type: object + secretRef: + description: SecretRef contains the secret name containing the registry + login credentials to resolve image metadata. The secret must be + of type kubernetes.io/dockerconfigjson. + properties: + name: + description: Name of the referent. + type: string + required: + - name + type: object + serviceAccountName: + description: 'ServiceAccountName is the name of the Kubernetes ServiceAccount + used to authenticate the image pull if the service account has attached + pull secrets. For more information: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#add-imagepullsecrets-to-a-service-account' + type: string + suspend: + description: This flag tells the controller to suspend the reconciliation + of this source. + type: boolean + timeout: + default: 60s + description: The timeout for remote OCI Repository operations like + pulling, defaults to 60s. + type: string + url: + description: URL is a reference to an OCI artifact repository hosted + on a remote container registry. + pattern: ^oci://.*$ + type: string + required: + - interval + - url + type: object + status: + default: + observedGeneration: -1 + description: OCIRepositoryStatus defines the observed state of OCIRepository + properties: + artifact: + description: Artifact represents the output of the last successful + OCI Repository sync. + properties: + checksum: + description: Checksum is the SHA256 checksum of the Artifact file. + type: string + lastUpdateTime: + description: LastUpdateTime is the timestamp corresponding to + the last update of the Artifact. + format: date-time + type: string + metadata: + additionalProperties: + type: string + description: Metadata holds upstream information such as OCI annotations. + type: object + path: + description: Path is the relative file path of the Artifact. It + can be used to locate the file in the root of the Artifact storage + on the local file system of the controller managing the Source. + type: string + revision: + description: Revision is a human-readable identifier traceable + in the origin source system. It can be a Git commit SHA, Git + tag, a Helm chart version, etc. + type: string + size: + description: Size is the number of bytes in the file. + format: int64 + type: integer + url: + description: URL is the HTTP address of the Artifact as exposed + by the controller managing the Source. It can be used to retrieve + the Artifact for consumption, e.g. by another controller applying + the Artifact contents. + type: string + required: + - path + - url + type: object + conditions: + description: Conditions holds the conditions for the OCIRepository. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n \ttype FooStatus struct{ \t // Represents the observations + of a foo's current state. \t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\" \t // + +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map + \t // +listMapKey=type \t Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields + \t}" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + lastHandledReconcileAt: + description: LastHandledReconcileAt holds the value of the most recent + reconcile request value, so a change of the annotation value can + be detected. + type: string + observedGeneration: + description: ObservedGeneration is the last observed generation. + format: int64 + type: integer + url: + description: URL is the download link for the artifact output of the + last OCI Repository sync. + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/e2e/test_resources/kluctl-crds.yaml b/e2e/test_resources/kluctl-crds.yaml new file mode 100644 index 000000000..d16c0d9ea --- /dev/null +++ b/e2e/test_resources/kluctl-crds.yaml @@ -0,0 +1,1592 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + name: kluctldeployments.flux.kluctl.io +spec: + group: flux.kluctl.io + names: + kind: KluctlDeployment + listKind: KluctlDeploymentList + plural: kluctldeployments + singular: kluctldeployment + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.lastDeployResult.time + name: Deployed + type: date + - jsonPath: .status.lastPruneResult.time + name: Pruned + type: date + - jsonPath: .status.lastValidateResult.time + name: Validated + type: date + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].message + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: KluctlDeployment is the Schema for the kluctldeployments API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: KluctlDeploymentSpec defines the desired state of KluctlDeployment + properties: + abortOnError: + default: false + description: ForceReplaceOnError instructs kluctl to abort deployments + immediately when something fails. Equivalent to using '--abort-on-error' + when calling kluctl. + type: boolean + args: + additionalProperties: + type: string + description: Args specifies dynamic target args. Only arguments defined + by 'dynamicArgs' of the target are allowed. + type: object + dependsOn: + description: DependsOn may contain a meta.NamespacedObjectReference + slice with references to resources that must be ready before this + kluctl project can be deployed. + items: + description: NamespacedObjectReference contains enough information + to locate the referenced Kubernetes resource object in any namespace. + properties: + name: + description: Name of the referent. + type: string + namespace: + description: Namespace of the referent, when not specified it + acts as LocalObjectReference. + type: string + required: + - name + type: object + type: array + deployInterval: + description: DeployInterval specifies the interval at which to deploy + the KluctlDeployment. This is independent of the 'Interval' value, + which only causes deployments if some deployment objects have changed. + type: string + deployMode: + default: full-deploy + description: DeployMode specifies what deploy mode should be used + enum: + - full-deploy + - poke-images + type: string + dryRun: + default: false + description: DryRun instructs kluctl to run everything in dry-run + mode. Equivalent to using '--dry-run' when calling kluctl. + type: boolean + excludeDeploymentDirs: + description: ExcludeDeploymentDirs instructs kluctl to exclude deployments + with the given dir. Equivalent to using '--exclude-deployment-dir' + when calling kluctl. + items: + type: string + type: array + excludeTags: + description: ExcludeTags instructs kluctl to exclude deployments with + given tags. Equivalent to using '--exclude-tag' when calling kluctl. + items: + type: string + type: array + forceApply: + default: false + description: ForceApply instructs kluctl to force-apply in case of + SSA conflicts. Equivalent to using '--force-apply' when calling + kluctl. + type: boolean + forceReplaceOnError: + default: false + description: ForceReplaceOnError instructs kluctl to force-replace + resources in case a normal replace fails. Equivalent to using '--force-replace-on-error' + when calling kluctl. + type: boolean + images: + description: Images contains a list of fixed image overrides. Equivalent + to using '--fixed-images-file' when calling kluctl. + items: + properties: + container: + type: string + deployTags: + items: + type: string + type: array + deployedImage: + type: string + deployment: + type: string + deploymentDir: + type: string + image: + type: string + namespace: + type: string + object: + description: ObjectRef contains the information necessary to + locate a resource within a cluster. + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + required: + - group + - kind + - name + - namespace + - version + type: object + registryImage: + type: string + resultImage: + type: string + versionFilter: + type: string + required: + - image + - resultImage + type: object + type: array + includeDeploymentDirs: + description: IncludeDeploymentDirs instructs kluctl to only include + deployments with the given dir. Equivalent to using '--include-deployment-dir' + when calling kluctl. + items: + type: string + type: array + includeTags: + description: IncludeTags instructs kluctl to only include deployments + with given tags. Equivalent to using '--include-tag' when calling + kluctl. + items: + type: string + type: array + interval: + description: The interval at which to reconcile the KluctlDeployment. + type: string + kubeConfig: + description: The KubeConfig for deploying to the target cluster. Specifies + the kubeconfig to be used when invoking kluctl. Contexts in this + kubeconfig must match the context found in the kluctl target. As + an alternative, RenameContexts can be used to fix non-matching context + names. + properties: + secretRef: + description: SecretRef holds the name of a secret that contains + a key with the kubeconfig file as the value. If no key is set, + the key will default to 'value'. The secret must be in the same + namespace as the Kustomization. It is recommended that the kubeconfig + is self-contained, and the secret is regularly updated if credentials + such as a cloud-access-token expire. Cloud specific `cmd-path` + auth helpers will not function without adding binaries and credentials + to the Pod that is responsible for reconciling the KluctlDeployment. + properties: + key: + description: Key in the Secret, when not specified an implementation-specific + default key is used. + type: string + name: + description: Name of the Secret. + type: string + required: + - name + type: object + type: object + noWait: + default: false + description: NoWait instructs kluctl to not wait for any resources + to become ready, including hooks. Equivalent to using '--no-wait' + when calling kluctl. + type: boolean + path: + description: Path to the directory containing the .kluctl.yaml file, + or the Defaults to 'None', which translates to the root path of + the SourceRef. + type: string + prune: + default: false + description: Prune enables pruning after deploying. + type: boolean + registrySecrets: + description: RegistrySecrets is a list of secret references to be + used for image registry authentication. The secrets must either + have ".dockerconfigjson" included or "registry", "username" and + "password". Additionally, "caFile" and "insecure" can be specified. + items: + description: LocalObjectReference contains enough information to + locate the referenced Kubernetes resource object. + properties: + name: + description: Name of the referent. + type: string + required: + - name + type: object + type: array + renameContexts: + description: RenameContexts specifies a list of context rename operations. + This is useful when the kluctl target's context does not match with + the contexts found in the kubeconfig while deploying. This is the + case when using kubeconfigs generated from service accounts, in + which case the context name is always "default". + items: + description: RenameContext specifies a single rename of a context + properties: + newContext: + description: NewContext is the new name of the context + type: string + oldContext: + description: OldContext is the name of the context to be renamed + type: string + required: + - newContext + - oldContext + type: object + type: array + replaceOnError: + default: false + description: ReplaceOnError instructs kluctl to replace resources + on error. Equivalent to using '--replace-on-error' when calling + kluctl. + type: boolean + retryInterval: + description: The interval at which to retry a previously failed reconciliation. + When not specified, the controller uses the KluctlDeploymentSpec.Interval + value to retry failures. + type: string + serviceAccountName: + description: The name of the Kubernetes service account to use while + deploying. If not specified, the default service account is used. + type: string + sourceRef: + description: Reference of the source where the kluctl project is. + The authentication secrets from the source are also used to authenticate + dependent git repositories which are cloned while deploying the + kluctl project. + properties: + apiVersion: + description: API version of the referent. + type: string + kind: + description: Kind of the referent. + enum: + - GitRepository + - Bucket + type: string + name: + description: Name of the referent. + type: string + namespace: + description: Namespace of the referent, defaults to the namespace + of the Kubernetes resource object that contains the reference. + type: string + required: + - kind + - name + type: object + suspend: + description: This flag tells the controller to suspend subsequent + kluctl executions, it does not apply to already started executions. + Defaults to false. + type: boolean + target: + description: Target specifies the kluctl target to deploy + maxLength: 63 + minLength: 1 + type: string + timeout: + description: Timeout for all operations. Defaults to 'Interval' duration. + type: string + updateImages: + default: false + description: UpdateImages instructs kluctl to update dynamic images. + Equivalent to using '-u' when calling kluctl. + type: boolean + validateInterval: + default: 5m + description: ValidateInterval specifies the interval at which to validate + the KluctlDeployment. Validation is performed the same way as with + 'kluctl validate -t '. Defaults to 1m. + type: string + required: + - interval + - sourceRef + - target + type: object + status: + description: KluctlDeploymentStatus defines the observed state of KluctlDeployment + properties: + commonLabels: + additionalProperties: + type: string + description: CommonLabels are the commonLabels found in the deployment + project when the last deployment was done. This is used to perform + cleanup/deletion in case the KluctlDeployment project is deleted + type: object + conditions: + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: + \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type + \ // +patchStrategy=merge // +listType=map // +listMapKey=type + \ Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` + \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + lastAttemptedRevision: + description: LastAttemptedRevision is the revision of the last reconciliation + attempt. + type: string + lastDeployResult: + description: LastDeployResult is the result of the last deploy command + properties: + error: + type: string + objectsHash: + description: ObjectsHash is the hash of all rendered objects + type: string + result: + properties: + changedObjects: + items: + description: ObjectRef contains the information necessary + to locate a resource within a cluster. + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + required: + - group + - kind + - name + - namespace + - version + type: object + type: array + deletedObjects: + items: + description: ObjectRef contains the information necessary + to locate a resource within a cluster. + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + required: + - group + - kind + - name + - namespace + - version + type: object + type: array + errors: + items: + properties: + error: + type: string + ref: + description: ObjectRef contains the information necessary + to locate a resource within a cluster. + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + required: + - group + - kind + - name + - namespace + - version + type: object + required: + - error + - ref + type: object + type: array + hookObjects: + items: + description: ObjectRef contains the information necessary + to locate a resource within a cluster. + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + required: + - group + - kind + - name + - namespace + - version + type: object + type: array + newObjects: + items: + description: ObjectRef contains the information necessary + to locate a resource within a cluster. + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + required: + - group + - kind + - name + - namespace + - version + type: object + type: array + orphanObjects: + items: + description: ObjectRef contains the information necessary + to locate a resource within a cluster. + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + required: + - group + - kind + - name + - namespace + - version + type: object + type: array + seenImages: + items: + properties: + container: + type: string + deployTags: + items: + type: string + type: array + deployedImage: + type: string + deployment: + type: string + deploymentDir: + type: string + image: + type: string + namespace: + type: string + object: + description: ObjectRef contains the information necessary + to locate a resource within a cluster. + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + required: + - group + - kind + - name + - namespace + - version + type: object + registryImage: + type: string + resultImage: + type: string + versionFilter: + type: string + required: + - image + - resultImage + type: object + type: array + warnings: + items: + properties: + error: + type: string + ref: + description: ObjectRef contains the information necessary + to locate a resource within a cluster. + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + required: + - group + - kind + - name + - namespace + - version + type: object + required: + - error + - ref + type: object + type: array + type: object + revision: + description: Revision is the source revision. Please note that + kluctl projects have dependent git repositories which are not + considered in the source revision + type: string + targetName: + description: TargetName is the name of the target + type: string + time: + description: AttemptedAt is the time when the attempt was performed + format: date-time + type: string + required: + - targetName + - time + type: object + lastHandledReconcileAt: + description: LastHandledReconcileAt holds the value of the most recent + reconcile request value, so a change of the annotation value can + be detected. + type: string + lastPruneResult: + description: LastDeployResult is the result of the last prune command + properties: + error: + type: string + objectsHash: + description: ObjectsHash is the hash of all rendered objects + type: string + result: + properties: + changedObjects: + items: + description: ObjectRef contains the information necessary + to locate a resource within a cluster. + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + required: + - group + - kind + - name + - namespace + - version + type: object + type: array + deletedObjects: + items: + description: ObjectRef contains the information necessary + to locate a resource within a cluster. + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + required: + - group + - kind + - name + - namespace + - version + type: object + type: array + errors: + items: + properties: + error: + type: string + ref: + description: ObjectRef contains the information necessary + to locate a resource within a cluster. + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + required: + - group + - kind + - name + - namespace + - version + type: object + required: + - error + - ref + type: object + type: array + hookObjects: + items: + description: ObjectRef contains the information necessary + to locate a resource within a cluster. + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + required: + - group + - kind + - name + - namespace + - version + type: object + type: array + newObjects: + items: + description: ObjectRef contains the information necessary + to locate a resource within a cluster. + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + required: + - group + - kind + - name + - namespace + - version + type: object + type: array + orphanObjects: + items: + description: ObjectRef contains the information necessary + to locate a resource within a cluster. + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + required: + - group + - kind + - name + - namespace + - version + type: object + type: array + seenImages: + items: + properties: + container: + type: string + deployTags: + items: + type: string + type: array + deployedImage: + type: string + deployment: + type: string + deploymentDir: + type: string + image: + type: string + namespace: + type: string + object: + description: ObjectRef contains the information necessary + to locate a resource within a cluster. + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + required: + - group + - kind + - name + - namespace + - version + type: object + registryImage: + type: string + resultImage: + type: string + versionFilter: + type: string + required: + - image + - resultImage + type: object + type: array + warnings: + items: + properties: + error: + type: string + ref: + description: ObjectRef contains the information necessary + to locate a resource within a cluster. + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + required: + - group + - kind + - name + - namespace + - version + type: object + required: + - error + - ref + type: object + type: array + type: object + revision: + description: Revision is the source revision. Please note that + kluctl projects have dependent git repositories which are not + considered in the source revision + type: string + targetName: + description: TargetName is the name of the target + type: string + time: + description: AttemptedAt is the time when the attempt was performed + format: date-time + type: string + required: + - targetName + - time + type: object + lastValidateResult: + description: LastValidateResult is the result of the last validate + command + properties: + error: + type: string + objectsHash: + description: ObjectsHash is the hash of all rendered objects + type: string + result: + properties: + errors: + items: + properties: + error: + type: string + ref: + description: ObjectRef contains the information necessary + to locate a resource within a cluster. + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + required: + - group + - kind + - name + - namespace + - version + type: object + required: + - error + - ref + type: object + type: array + ready: + type: boolean + results: + items: + properties: + annotation: + type: string + message: + type: string + ref: + description: ObjectRef contains the information necessary + to locate a resource within a cluster. + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + required: + - group + - kind + - name + - namespace + - version + type: object + required: + - annotation + - message + - ref + type: object + type: array + warnings: + items: + properties: + error: + type: string + ref: + description: ObjectRef contains the information necessary + to locate a resource within a cluster. + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + required: + - group + - kind + - name + - namespace + - version + type: object + required: + - error + - ref + type: object + type: array + required: + - ready + type: object + revision: + description: Revision is the source revision. Please note that + kluctl projects have dependent git repositories which are not + considered in the source revision + type: string + targetName: + description: TargetName is the name of the target + type: string + time: + description: AttemptedAt is the time when the attempt was performed + format: date-time + type: string + required: + - error + - targetName + - time + type: object + observedGeneration: + description: ObservedGeneration is the last reconciled generation. + format: int64 + type: integer + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + name: kluctlmultideployments.flux.kluctl.io +spec: + group: flux.kluctl.io + names: + kind: KluctlMultiDeployment + listKind: KluctlMultiDeploymentList + plural: kluctlmultideployments + singular: kluctlmultideployment + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.targetPattern + name: Pattern + type: string + - jsonPath: .status.targetCount + name: Targets + type: integer + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].message + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: KluctlMultiDeployment is the Schema for the kluctlmultideployments + API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: KluctlMultiDeploymentSpec defines the desired state of KluctlMultiDeployment + properties: + dependsOn: + description: DependsOn may contain a meta.NamespacedObjectReference + slice with references to resources that must be ready before this + kluctl project can be deployed. + items: + description: NamespacedObjectReference contains enough information + to locate the referenced Kubernetes resource object in any namespace. + properties: + name: + description: Name of the referent. + type: string + namespace: + description: Namespace of the referent, when not specified it + acts as LocalObjectReference. + type: string + required: + - name + type: object + type: array + interval: + description: The interval at which to reconcile the KluctlDeployment. + type: string + path: + description: Path to the directory containing the .kluctl.yaml file, + or the Defaults to 'None', which translates to the root path of + the SourceRef. + type: string + retryInterval: + description: The interval at which to retry a previously failed reconciliation. + When not specified, the controller uses the KluctlDeploymentSpec.Interval + value to retry failures. + type: string + sourceRef: + description: Reference of the source where the kluctl project is. + The authentication secrets from the source are also used to authenticate + dependent git repositories which are cloned while deploying the + kluctl project. + properties: + apiVersion: + description: API version of the referent. + type: string + kind: + description: Kind of the referent. + enum: + - GitRepository + - Bucket + type: string + name: + description: Name of the referent. + type: string + namespace: + description: Namespace of the referent, defaults to the namespace + of the Kubernetes resource object that contains the reference. + type: string + required: + - kind + - name + type: object + suspend: + description: This flag tells the controller to suspend subsequent + kluctl executions, it does not apply to already started executions. + Defaults to false. + type: boolean + targetPattern: + description: TargetPattern is the regex pattern used to match targets + type: string + template: + description: Template is the object template used to create KluctlDeploymet + objects + properties: + spec: + description: Spec is the KluctlDeployment spec to be used as a + template + properties: + abortOnError: + default: false + description: ForceReplaceOnError instructs kluctl to abort + deployments immediately when something fails. Equivalent + to using '--abort-on-error' when calling kluctl. + type: boolean + args: + additionalProperties: + type: string + description: Args specifies dynamic target args. Only arguments + defined by 'dynamicArgs' of the target are allowed. + type: object + deployInterval: + description: DeployInterval specifies the interval at which + to deploy the KluctlDeployment. This is independent of the + 'Interval' value, which only causes deployments if some + deployment objects have changed. + type: string + deployMode: + default: full-deploy + description: DeployMode specifies what deploy mode should + be used + enum: + - full-deploy + - poke-images + type: string + dryRun: + default: false + description: DryRun instructs kluctl to run everything in + dry-run mode. Equivalent to using '--dry-run' when calling + kluctl. + type: boolean + excludeDeploymentDirs: + description: ExcludeDeploymentDirs instructs kluctl to exclude + deployments with the given dir. Equivalent to using '--exclude-deployment-dir' + when calling kluctl. + items: + type: string + type: array + excludeTags: + description: ExcludeTags instructs kluctl to exclude deployments + with given tags. Equivalent to using '--exclude-tag' when + calling kluctl. + items: + type: string + type: array + forceApply: + default: false + description: ForceApply instructs kluctl to force-apply in + case of SSA conflicts. Equivalent to using '--force-apply' + when calling kluctl. + type: boolean + forceReplaceOnError: + default: false + description: ForceReplaceOnError instructs kluctl to force-replace + resources in case a normal replace fails. Equivalent to + using '--force-replace-on-error' when calling kluctl. + type: boolean + images: + description: Images contains a list of fixed image overrides. + Equivalent to using '--fixed-images-file' when calling kluctl. + items: + properties: + container: + type: string + deployTags: + items: + type: string + type: array + deployedImage: + type: string + deployment: + type: string + deploymentDir: + type: string + image: + type: string + namespace: + type: string + object: + description: ObjectRef contains the information necessary + to locate a resource within a cluster. + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + required: + - group + - kind + - name + - namespace + - version + type: object + registryImage: + type: string + resultImage: + type: string + versionFilter: + type: string + required: + - image + - resultImage + type: object + type: array + includeDeploymentDirs: + description: IncludeDeploymentDirs instructs kluctl to only + include deployments with the given dir. Equivalent to using + '--include-deployment-dir' when calling kluctl. + items: + type: string + type: array + includeTags: + description: IncludeTags instructs kluctl to only include + deployments with given tags. Equivalent to using '--include-tag' + when calling kluctl. + items: + type: string + type: array + interval: + description: The interval at which to reconcile the KluctlDeployment. + type: string + kubeConfig: + description: The KubeConfig for deploying to the target cluster. + Specifies the kubeconfig to be used when invoking kluctl. + Contexts in this kubeconfig must match the context found + in the kluctl target. As an alternative, RenameContexts + can be used to fix non-matching context names. + properties: + secretRef: + description: SecretRef holds the name of a secret that + contains a key with the kubeconfig file as the value. + If no key is set, the key will default to 'value'. The + secret must be in the same namespace as the Kustomization. + It is recommended that the kubeconfig is self-contained, + and the secret is regularly updated if credentials such + as a cloud-access-token expire. Cloud specific `cmd-path` + auth helpers will not function without adding binaries + and credentials to the Pod that is responsible for reconciling + the KluctlDeployment. + properties: + key: + description: Key in the Secret, when not specified + an implementation-specific default key is used. + type: string + name: + description: Name of the Secret. + type: string + required: + - name + type: object + type: object + noWait: + default: false + description: NoWait instructs kluctl to not wait for any resources + to become ready, including hooks. Equivalent to using '--no-wait' + when calling kluctl. + type: boolean + prune: + default: false + description: Prune enables pruning after deploying. + type: boolean + registrySecrets: + description: RegistrySecrets is a list of secret references + to be used for image registry authentication. The secrets + must either have ".dockerconfigjson" included or "registry", + "username" and "password". Additionally, "caFile" and "insecure" + can be specified. + items: + description: LocalObjectReference contains enough information + to locate the referenced Kubernetes resource object. + properties: + name: + description: Name of the referent. + type: string + required: + - name + type: object + type: array + renameContexts: + description: RenameContexts specifies a list of context rename + operations. This is useful when the kluctl target's context + does not match with the contexts found in the kubeconfig + while deploying. This is the case when using kubeconfigs + generated from service accounts, in which case the context + name is always "default". + items: + description: RenameContext specifies a single rename of + a context + properties: + newContext: + description: NewContext is the new name of the context + type: string + oldContext: + description: OldContext is the name of the context to + be renamed + type: string + required: + - newContext + - oldContext + type: object + type: array + replaceOnError: + default: false + description: ReplaceOnError instructs kluctl to replace resources + on error. Equivalent to using '--replace-on-error' when + calling kluctl. + type: boolean + retryInterval: + description: The interval at which to retry a previously failed + reconciliation. When not specified, the controller uses + the KluctlDeploymentSpec.Interval value to retry failures. + type: string + serviceAccountName: + description: The name of the Kubernetes service account to + use while deploying. If not specified, the default service + account is used. + type: string + timeout: + description: Timeout for all operations. Defaults to 'Interval' + duration. + type: string + updateImages: + default: false + description: UpdateImages instructs kluctl to update dynamic + images. Equivalent to using '-u' when calling kluctl. + type: boolean + validateInterval: + default: 5m + description: ValidateInterval specifies the interval at which + to validate the KluctlDeployment. Validation is performed + the same way as with 'kluctl validate -t '. Defaults + to 1m. + type: string + required: + - interval + type: object + required: + - spec + type: object + timeout: + description: Timeout for all operations. Defaults to 'Interval' duration. + type: string + required: + - interval + - sourceRef + - targetPattern + - template + type: object + status: + description: KluctlMultiDeploymentStatus defines the observed state of + KluctlMultiDeployment + properties: + conditions: + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: + \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type + \ // +patchStrategy=merge // +listType=map // +listMapKey=type + \ Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` + \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + lastAttemptedRevision: + description: LastAttemptedRevision is the revision of the last reconciliation + attempt. + type: string + lastHandledReconcileAt: + description: LastHandledReconcileAt holds the value of the most recent + reconcile request value, so a change of the annotation value can + be detected. + type: string + observedGeneration: + description: ObservedGeneration is the last reconciled generation. + format: int64 + type: integer + targetCount: + description: TargetCount is the number of targets detected + type: integer + targets: + description: Targets is the list of detected targets + items: + description: KluctlMultiDeploymentTargetStatus describes the status + of a single target + properties: + kluctlDeploymentName: + description: KluctlDeploymentName is the name of the generated + KluctlDeployment object + type: string + name: + description: Name is the name of the detected target + type: string + required: + - kluctlDeploymentName + - name + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/e2e/test_resources/kluctl-deployment.yaml b/e2e/test_resources/kluctl-deployment.yaml new file mode 100644 index 000000000..e10aeede8 --- /dev/null +++ b/e2e/test_resources/kluctl-deployment.yaml @@ -0,0 +1,30 @@ +--- +apiVersion: flux.kluctl.io/v1alpha1 +kind: KluctlDeployment +metadata: + name: microservices-demo-test + namespace: default +spec: + interval: 10m + path: ./simple + prune: true + renameContexts: + - newContext: kind-kind + oldContext: default + sourceRef: + kind: GitRepository + name: microservices-demo + namespace: default + target: simple + timeout: 2m +--- +apiVersion: source.toolkit.fluxcd.io/v1beta1 +kind: GitRepository +metadata: + name: microservices-demo + namespace: default +spec: + interval: 5m + ref: + branch: main + url: https://github.com/gitbluf/kluctl-examples.git diff --git a/e2e/test_resources/resources.go b/e2e/test_resources/resources.go index 7644d1f09..24d724f66 100644 --- a/e2e/test_resources/resources.go +++ b/e2e/test_resources/resources.go @@ -2,9 +2,10 @@ package test_resources import ( "embed" + "os" + test_utils "github.com/kluctl/kluctl/v2/internal/test-utils" "github.com/kluctl/kluctl/v2/pkg/utils" - "os" ) //go:embed *.yaml diff --git a/e2e/utils.go b/e2e/utils.go index 274f5946c..9798a9de1 100644 --- a/e2e/utils.go +++ b/e2e/utils.go @@ -4,16 +4,17 @@ import ( "bufio" "bytes" "fmt" - "github.com/kluctl/kluctl/v2/internal/test-utils" - "github.com/kluctl/kluctl/v2/pkg/utils/uo" - "github.com/kluctl/kluctl/v2/pkg/validation" - log "github.com/sirupsen/logrus" "io" "os/exec" "reflect" "strings" "testing" "time" + + test_utils "github.com/kluctl/kluctl/v2/internal/test-utils" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/validation" + log "github.com/sirupsen/logrus" ) func recreateNamespace(t *testing.T, k *test_utils.KindCluster, namespace string) { diff --git a/pkg/k8s/client_factory.go b/pkg/k8s/client_factory.go index 8480a2540..1c9e451df 100644 --- a/pkg/k8s/client_factory.go +++ b/pkg/k8s/client_factory.go @@ -7,6 +7,7 @@ import ( "k8s.io/client-go/dynamic" corev1 "k8s.io/client-go/kubernetes/typed/core/v1" "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" "net/http" "net/url" "path/filepath" @@ -81,3 +82,19 @@ func NewClientFactory(configIn *rest.Config) (ClientFactory, error) { httpClient: httpClient, }, nil } + +func NewClientFactoryFromDefaultConfig(context *string) (ClientFactory, error) { + configOverrides := &clientcmd.ConfigOverrides{} + if context != nil { + configOverrides.CurrentContext = *context + } + + config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( + clientcmd.NewDefaultClientConfigLoadingRules(), + configOverrides).ClientConfig() + if err != nil { + return nil, err + } + + return NewClientFactory(config) +} diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index de92465ac..69bd95feb 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -2,14 +2,19 @@ package k8s import ( "context" + "encoding/json" "fmt" + "io" + "sync" + "time" + "github.com/Masterminds/semver/v3" + "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" - "io" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -378,14 +383,8 @@ type PatchOptions struct { ForceApply bool } -func (k *K8sCluster) PatchObject(o *uo.UnstructuredObject, options PatchOptions) (*uo.UnstructuredObject, []ApiWarning, error) { +func (k *K8sCluster) doPatch(ref k8s.ObjectRef, data []byte, patchType types.PatchType, options PatchOptions) (*uo.UnstructuredObject, []ApiWarning, error) { dryRun := k.DryRun || options.ForceDryRun - ref := o.GetK8sRef() - - data, err := yaml.WriteYamlBytes(o) - if err != nil { - return nil, nil, err - } po := v1.PatchOptions{ FieldManager: "kluctl", @@ -401,7 +400,7 @@ func (k *K8sCluster) PatchObject(o *uo.UnstructuredObject, options PatchOptions) var result *uo.UnstructuredObject apiWarnings, err := k.clients.withDynamicClientForGVK(k.Resources, ref.GVK, ref.Namespace, func(r dynamic.ResourceInterface) error { - x, err := r.Patch(k.ctx, ref.Name, types.ApplyPatchType, data, po) + x, err := r.Patch(k.ctx, ref.Name, patchType, data, po) if err != nil { return fmt.Errorf("failed to patch %s: %w", ref.String(), err) } @@ -411,6 +410,29 @@ func (k *K8sCluster) PatchObject(o *uo.UnstructuredObject, options PatchOptions) return result, apiWarnings, err } +func (k *K8sCluster) PatchObject(o *uo.UnstructuredObject, options PatchOptions) (*uo.UnstructuredObject, []ApiWarning, error) { + data, err := yaml.WriteYamlBytes(o) + if err != nil { + return nil, nil, err + } + + return k.doPatch(o.GetK8sRef(), data, types.ApplyPatchType, options) +} + +type JsonPatch struct { + Op string `json:"op"` + Path string `json:"path"` + Value any `json:"value"` +} + +func (k *K8sCluster) PatchObjectWithJsonPatch(ref k8s.ObjectRef, patch interface{}, options PatchOptions) (*uo.UnstructuredObject, []ApiWarning, error) { + data, err := json.Marshal(patch) + if err != nil { + return nil, nil, err + } + return k.doPatch(ref, data, types.JSONPatchType, options) +} + type UpdateOptions struct { ForceDryRun bool } From 0e2503532226dda8bba670d639c05f6268f8d8b6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 29 Sep 2022 09:33:14 +0200 Subject: [PATCH 1034/2916] fix: Remove redundant imports (#52) --- pkg/k8s/k8s_cluster.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index 69bd95feb..f8e8a07a7 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -26,8 +26,6 @@ import ( _ "k8s.io/client-go/plugin/pkg/client/auth" "k8s.io/client-go/rest" "k8s.io/client-go/restmapper" - "sync" - "time" ) type K8sCluster struct { From 58aa90b428d7c871e17142a8c29456263979b590 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 29 Sep 2022 11:33:05 +0200 Subject: [PATCH 1035/2916] chore: Prepare Makefile for installation/setup of envtest --- .gitignore | 1 + Makefile | 62 +++++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index 8160eea20..d7ad8b4f4 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ /kluctl.exe /e2e.test* /bin +/build /reports /vendor dist/ diff --git a/Makefile b/Makefile index 631cc08ee..7d7c482d2 100644 --- a/Makefile +++ b/Makefile @@ -8,27 +8,27 @@ TEST_BINARY_NAME=kluctl-e2e REQUIRED_ENV_VARS=GOOS GOARCH EXPORT_RESULT?=false +# If gobin not set, create one on ./build and add to path. +ifeq (,$(shell go env GOBIN)) +GOBIN=$(BUILD_DIR)/gobin +else +GOBIN=$(shell go env GOBIN) +endif +export PATH:=$(GOBIN):${PATH} + +# Architecture to use envtest with +ENVTEST_ARCH ?= amd64 + GREEN := $(shell tput -Txterm setaf 2) YELLOW := $(shell tput -Txterm setaf 3) WHITE := $(shell tput -Txterm setaf 7) CYAN := $(shell tput -Txterm setaf 6) RESET := $(shell tput -Txterm sgr0) -.PHONY: all test build check-kubectl check-helm check-kind +.PHONY: all test build all: help -## Check: - -check-kubectl: ## Checks if kubectl is installed - kubectl version --client=true - -check-helm: ## Checks if helm is installed - helm version - -check-kind: ## Checks if kind is installed - kind version - ## Build: build: build-go ## Run the complete build pipeline @@ -42,11 +42,28 @@ clean: ## Remove build related file rm -fr ./reports rm -fr ./download-python +## Envtest setup +# Repository root based on Git metadata +REPOSITORY_ROOT := $(shell git rev-parse --show-toplevel) +BUILD_DIR := $(REPOSITORY_ROOT)/build +ENVTEST = $(GOBIN)/setup-envtest +.PHONY: envtest +setup-envtest: ## Download envtest-setup locally if necessary. + $(call go-install-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest@latest) + +# Download the envtest binaries to testbin +ENVTEST_ASSETS_DIR=$(BUILD_DIR)/testbin +ENVTEST_KUBERNETES_VERSION?=latest +install-envtest: setup-envtest + mkdir -p ${ENVTEST_ASSETS_DIR} + $(ENVTEST) use $(ENVTEST_KUBERNETES_VERSION) --arch=$(ENVTEST_ARCH) --bin-dir=$(ENVTEST_ASSETS_DIR) + ## Test: test: test-unit test-e2e ## Runs the complete test suite -test-e2e: check-kubectl check-helm check-kind ## Runs the end to end tests - CGO_ENBALED=0 GO111MODULE=on $(GOCMD) test -o ./bin/$(TEST_BINARY_NAME) ./e2e +KUBEBUILDER_ASSETS?="$(shell $(ENVTEST) --arch=$(ENVTEST_ARCH) use -i $(ENVTEST_KUBERNETES_VERSION) --bin-dir=$(ENVTEST_ASSETS_DIR) -p path)" +test-e2e: install-envtest ## Runs the end to end tests + KUBEBUILDER_ASSETS=$(KUBEBUILDER_ASSETS) CGO_ENBALED=0 GO111MODULE=on $(GOCMD) test -o ./bin/$(TEST_BINARY_NAME) ./e2e test-unit: ## Run the unit tests of the project ifeq ($(EXPORT_RESULT), true) @@ -98,4 +115,19 @@ help: ## Show this help. @awk 'BEGIN {FS = ":.*?## "} { \ if (/^[0-9a-zA-Z_-]+:.*?##.*$$/) {printf " ${YELLOW}%-20s${GREEN}%s${RESET}\n", $$1, $$2} \ else if (/^## .*$$/) {printf " ${CYAN}%s${RESET}\n", substr($$1,4)} \ - }' $(MAKEFILE_LIST) \ No newline at end of file + }' $(MAKEFILE_LIST) + +## Tools +# go-install-tool will 'go install' any package $2 and install it to $1. +PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST)))) +define go-install-tool +@[ -f $(1) ] || { \ +set -e ;\ +TMP_DIR=$$(mktemp -d) ;\ +cd $$TMP_DIR ;\ +go mod init tmp ;\ +echo "Downloading $(2)" ;\ +GOBIN=$(GOBIN) go install $(2) ;\ +rm -rf $$TMP_DIR ;\ +} +endef From f19dad3dbaa993a14047a37bf9c41c81b4674ebf Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 29 Sep 2022 11:35:42 +0200 Subject: [PATCH 1036/2916] tests: Switch to envtest based clusters --- e2e/contexts_test.go | 14 +- e2e/default_clusters.go | 67 ++------- e2e/deploy_test.go | 2 +- e2e/external_projects_test.go | 6 +- e2e/flux_test.go | 4 +- e2e/hooks_test.go | 20 +-- e2e/inclusion_test.go | 6 +- e2e/project.go | 15 +- e2e/seal_test.go | 24 ++-- e2e/test_resources/resources.go | 8 +- e2e/utils.go | 27 ++-- go.mod | 4 +- go.sum | 16 +-- internal/test-utils/envtest_cluster.go | 100 ++++++++++++++ internal/test-utils/kind_cluster.go | 181 ------------------------- 15 files changed, 173 insertions(+), 321 deletions(-) create mode 100644 internal/test-utils/envtest_cluster.go delete mode 100644 internal/test-utils/kind_cluster.go diff --git a/e2e/contexts_test.go b/e2e/contexts_test.go index b55c5ab6d..34d036a1e 100644 --- a/e2e/contexts_test.go +++ b/e2e/contexts_test.go @@ -3,7 +3,6 @@ package e2e import ( "github.com/kluctl/kluctl/v2/pkg/utils/uo" "k8s.io/client-go/tools/clientcmd/api" - "sync" "testing" ) @@ -12,17 +11,8 @@ func prepareContextTest(t *testing.T, name string) *testProject { p.init(t, defaultKindCluster1, name) p.mergeKubeconfig(defaultKindCluster2) - var wg sync.WaitGroup - wg.Add(2) - go func() { - defer wg.Done() - recreateNamespace(t, defaultKindCluster1, p.projectName) - }() - go func() { - defer wg.Done() - recreateNamespace(t, defaultKindCluster2, p.projectName) - }() - wg.Wait() + createNamespace(t, defaultKindCluster1, p.projectName) + createNamespace(t, defaultKindCluster2, p.projectName) addConfigMapDeployment(p, "cm", nil, resourceOpts{ name: "cm", diff --git a/e2e/default_clusters.go b/e2e/default_clusters.go index 466df5b4f..c042571cc 100644 --- a/e2e/default_clusters.go +++ b/e2e/default_clusters.go @@ -1,62 +1,11 @@ package e2e import ( - "fmt" test_utils "github.com/kluctl/kluctl/v2/internal/test-utils" - "github.com/kluctl/kluctl/v2/pkg/utils" - "os" - "path/filepath" - "strconv" "sync" ) -func createDefaultKindCluster(num int) (*test_utils.KindCluster, int) { - kindClusterName := os.Getenv(fmt.Sprintf("KIND_CLUSTER_NAME%d", num)) - kindApiHost := os.Getenv(fmt.Sprintf("KIND_API_HOST%d", num)) - kindApiPort := os.Getenv(fmt.Sprintf("KIND_API_PORT%d", num)) - kindExtraPortsOffset := os.Getenv(fmt.Sprintf("KIND_EXTRA_PORTS_OFFSET%d", num)) - kindKubeconfig := os.Getenv(fmt.Sprintf("KIND_KUBECONFIG%d", num)) - if kindClusterName == "" { - kindClusterName = fmt.Sprintf("kluctl-e2e-%d", num) - } - if kindApiHost == "" { - kindApiHost = "localhost" - } - if kindExtraPortsOffset == "" { - kindExtraPortsOffset = fmt.Sprintf("%d", 30000+num*1000) - } - if kindKubeconfig == "" { - kindKubeconfig = filepath.Join(utils.GetTmpBaseDir(), fmt.Sprintf("kluctl-e2e-kubeconfig-%d.yml", num)) - } - - var err error - var kindApiPortInt, kindExtraPortsOffsetInt int64 - - if kindApiPort != "" { - kindApiPortInt, err = strconv.ParseInt(kindApiPort, 0, 32) - if err != nil { - panic(err) - } - } - kindExtraPortsOffsetInt, err = strconv.ParseInt(kindExtraPortsOffset, 0, 32) - if err != nil { - panic(err) - } - - vaultPort := int(kindExtraPortsOffsetInt) + 0 - - kindExtraPorts := map[int]int{ - vaultPort: 30000, - } - - k, err := test_utils.CreateKindCluster(kindClusterName, kindApiHost, int(kindApiPortInt), kindExtraPorts, kindKubeconfig) - if err != nil { - panic(err) - } - return k, vaultPort -} - -var defaultKindCluster1, defaultKindCluster2 *test_utils.KindCluster +var defaultKindCluster1, defaultKindCluster2 *test_utils.EnvTestCluster var defaultKindCluster1VaultPort, defaultKindCluster2VaultPort int func init() { @@ -64,13 +13,19 @@ func init() { wg.Add(2) go func() { defer wg.Done() - defaultKindCluster1, defaultKindCluster1VaultPort = createDefaultKindCluster(1) - deleteTestNamespaces(defaultKindCluster1) + var err error + defaultKindCluster1, err = test_utils.CreateEnvTestCluster("cluster1") + if err != nil { + panic(err) + } }() go func() { defer wg.Done() - defaultKindCluster2, defaultKindCluster2VaultPort = createDefaultKindCluster(2) - deleteTestNamespaces(defaultKindCluster2) + var err error + defaultKindCluster2, err = test_utils.CreateEnvTestCluster("cluster2") + if err != nil { + panic(err) + } }() wg.Wait() } diff --git a/e2e/deploy_test.go b/e2e/deploy_test.go index f8d451032..97044e7eb 100644 --- a/e2e/deploy_test.go +++ b/e2e/deploy_test.go @@ -13,7 +13,7 @@ func TestCommandDeploySimple(t *testing.T) { p.init(t, k, "simple") defer p.cleanup() - recreateNamespace(t, k, p.projectName) + createNamespace(t, k, p.projectName) p.updateTarget("test", nil) diff --git a/e2e/external_projects_test.go b/e2e/external_projects_test.go index 18dbe95ca..f2a4dd9db 100644 --- a/e2e/external_projects_test.go +++ b/e2e/external_projects_test.go @@ -12,12 +12,12 @@ func doTestProject(t *testing.T, namespace string, p *testProject) { p.init(t, k, fmt.Sprintf("project-%s", namespace)) defer p.cleanup() - recreateNamespace(t, k, namespace) + createNamespace(t, k, namespace) p.updateKindCluster(k, uo.FromMap(map[string]interface{}{ "cluster_var": "cluster_value1", })) - p.updateTargetDeprecated("test", k.Name, uo.FromMap(map[string]interface{}{ + p.updateTargetDeprecated("test", k.Context, uo.FromMap(map[string]interface{}{ "target_var": "target_value1", })) addConfigMapDeployment(p, "cm1", map[string]string{}, resourceOpts{name: "cm1", namespace: namespace}) @@ -46,7 +46,7 @@ func doTestProject(t *testing.T, namespace string, p *testProject) { assertNestedFieldEquals(t, o, "cluster_value2", "data", "cluster_var") assertNestedFieldEquals(t, o, "target_value1", "data", "target_var") - p.updateTargetDeprecated("test", k.Name, uo.FromMap(map[string]interface{}{ + p.updateTargetDeprecated("test", k.Context, uo.FromMap(map[string]interface{}{ "target_var": "target_value2", })) p.KluctlMust("deploy", "--yes", "-t", "test") diff --git a/e2e/flux_test.go b/e2e/flux_test.go index 56bde84b3..d11f62790 100644 --- a/e2e/flux_test.go +++ b/e2e/flux_test.go @@ -38,7 +38,7 @@ func TestFluxCommands(t *testing.T) { assert.Len(t, annotation, 1, "Annotation not present") } -func getKluctlSuspendField(t *testing.T, k *test_utils.KindCluster) interface{} { +func getKluctlSuspendField(t *testing.T, k *test_utils.EnvTestCluster) interface{} { o := k.KubectlYamlMust(t, "-n", "default", "get", "kluctldeployment", "microservices-demo-test") result, ok, err := o.GetNestedField("spec", "suspend") fmt.Println(result) @@ -51,7 +51,7 @@ func getKluctlSuspendField(t *testing.T, k *test_utils.KindCluster) interface{} return result } -func getKluctlAnnotations(t *testing.T, k *test_utils.KindCluster) interface{} { +func getKluctlAnnotations(t *testing.T, k *test_utils.EnvTestCluster) interface{} { o := k.KubectlYamlMust(t, "-n", "default", "get", "kluctldeployment", "microservices-demo-test") result, ok, err := o.GetNestedField("metadata", "annotations") if err != nil { diff --git a/e2e/hooks_test.go b/e2e/hooks_test.go index d105b9a45..4c9cf2930 100644 --- a/e2e/hooks_test.go +++ b/e2e/hooks_test.go @@ -55,7 +55,7 @@ func addConfigMap(p *testProject, dir string, opts resourceOpts) { }) } -func getHookResult(t *testing.T, p *testProject, k *test_utils.KindCluster, secretName string) *uo.UnstructuredObject { +func getHookResult(t *testing.T, p *testProject, k *test_utils.EnvTestCluster, secretName string) *uo.UnstructuredObject { o := k.KubectlYamlMust(t, "-n", p.projectName, "get", "secret", secretName) s, ok, err := o.GetNestedString("data", "result") if err != nil { @@ -75,7 +75,7 @@ func getHookResult(t *testing.T, p *testProject, k *test_utils.KindCluster, secr return r } -func getHookResultCMNames(t *testing.T, p *testProject, k *test_utils.KindCluster, second bool) []string { +func getHookResultCMNames(t *testing.T, p *testProject, k *test_utils.EnvTestCluster, second bool) []string { secretName := "hook-result" if second { secretName = "hook-result2" @@ -89,7 +89,7 @@ func getHookResultCMNames(t *testing.T, p *testProject, k *test_utils.KindCluste return names } -func assertHookResultCMName(t *testing.T, p *testProject, k *test_utils.KindCluster, second bool, cmName string) { +func assertHookResultCMName(t *testing.T, p *testProject, k *test_utils.EnvTestCluster, second bool, cmName string) { names := getHookResultCMNames(t, p, k, second) for _, x := range names { if x == cmName { @@ -99,7 +99,7 @@ func assertHookResultCMName(t *testing.T, p *testProject, k *test_utils.KindClus t.Fatalf("%s not found in hook result", cmName) } -func assertHookResultNotCMName(t *testing.T, p *testProject, k *test_utils.KindCluster, second bool, cmName string) { +func assertHookResultNotCMName(t *testing.T, p *testProject, k *test_utils.EnvTestCluster, second bool, cmName string) { names := getHookResultCMNames(t, p, k, second) for _, x := range names { if x == cmName { @@ -108,7 +108,7 @@ func assertHookResultNotCMName(t *testing.T, p *testProject, k *test_utils.KindC } } -func prepareHookTestProject(t *testing.T, name string, hook string, hookDeletionPolicy string) (*testProject, *test_utils.KindCluster) { +func prepareHookTestProject(t *testing.T, name string, hook string, hookDeletionPolicy string) (*testProject, *test_utils.EnvTestCluster) { isDone := false namespace := fmt.Sprintf("hook-%s", name) k := defaultKindCluster1 @@ -120,7 +120,7 @@ func prepareHookTestProject(t *testing.T, name string, hook string, hookDeletion } }() - recreateNamespace(t, k, namespace) + createNamespace(t, k, namespace) p.updateTarget("test", nil) @@ -131,14 +131,14 @@ func prepareHookTestProject(t *testing.T, name string, hook string, hookDeletion return p, k } -func ensureHookExecuted(t *testing.T, p *testProject, k *test_utils.KindCluster) { - _, _ = k.Kubectl("delete", "-n", p.projectName, "secret", "hook-result", "hook-result2") +func ensureHookExecuted(t *testing.T, p *testProject, k *test_utils.EnvTestCluster) { + _, _, _ = k.Kubectl("delete", "-n", p.projectName, "secret", "hook-result", "hook-result2") p.KluctlMust("deploy", "--yes", "-t", "test") assertResourceExists(t, k, p.projectName, "ConfigMap/cm1") } -func ensureHookNotExecuted(t *testing.T, p *testProject, k *test_utils.KindCluster) { - _, _ = k.Kubectl("delete", "-n", p.projectName, "secret", "hook-result", "hook-result2") +func ensureHookNotExecuted(t *testing.T, p *testProject, k *test_utils.EnvTestCluster) { + _, _, _ = k.Kubectl("delete", "-n", p.projectName, "secret", "hook-result", "hook-result2") p.KluctlMust("deploy", "--yes", "-t", "test") assertResourceNotExists(t, k, p.projectName, "Secret/hook-result") } diff --git a/e2e/inclusion_test.go b/e2e/inclusion_test.go index 0395bff9f..247073d64 100644 --- a/e2e/inclusion_test.go +++ b/e2e/inclusion_test.go @@ -8,7 +8,7 @@ import ( "testing" ) -func prepareInclusionTestProject(t *testing.T, namespace string, withIncludes bool) (*testProject, *test_utils.KindCluster) { +func prepareInclusionTestProject(t *testing.T, namespace string, withIncludes bool) (*testProject, *test_utils.EnvTestCluster) { isDone := false k := defaultKindCluster1 @@ -20,7 +20,7 @@ func prepareInclusionTestProject(t *testing.T, namespace string, withIncludes bo } }() - recreateNamespace(t, k, p.projectName) + createNamespace(t, k, p.projectName) p.updateTarget("test", nil) @@ -49,7 +49,7 @@ func prepareInclusionTestProject(t *testing.T, namespace string, withIncludes bo return p, k } -func assertExistsHelper(t *testing.T, p *testProject, k *test_utils.KindCluster, shouldExists map[string]bool, add []string, remove []string) { +func assertExistsHelper(t *testing.T, p *testProject, k *test_utils.EnvTestCluster, shouldExists map[string]bool, add []string, remove []string) { for _, x := range add { shouldExists[x] = true } diff --git a/e2e/project.go b/e2e/project.go index 2b67e294a..f540b6b3d 100644 --- a/e2e/project.go +++ b/e2e/project.go @@ -37,7 +37,7 @@ type testProject struct { gitServer *test_utils.GitServer } -func (p *testProject) init(t *testing.T, k *test_utils.KindCluster, projectName string) { +func (p *testProject) init(t *testing.T, k *test_utils.EnvTestCluster, projectName string) { p.t = t p.gitServer = test_utils.NewGitServer(t) p.projectName = projectName @@ -92,9 +92,9 @@ func (p *testProject) cleanup() { } } -func (p *testProject) mergeKubeconfig(k *test_utils.KindCluster) { +func (p *testProject) mergeKubeconfig(k *test_utils.EnvTestCluster) { p.updateMergedKubeconfig(func(config *clientcmdapi.Config) { - nkcfg, err := clientcmd.LoadFromFile(k.Kubeconfig) + nkcfg, err := clientcmd.Load(k.Kubeconfig) if err != nil { p.t.Fatal(err) } @@ -187,13 +187,10 @@ func (p *testProject) updateCluster(name string, context string, vars *uo.Unstru }, fmt.Sprintf("add/update cluster %s", name)) } -func (p *testProject) updateKindCluster(k *test_utils.KindCluster, vars *uo.UnstructuredObject) { - context, err := k.Kubectl("config", "current-context") - if err != nil { - p.t.Fatal(err) - } +func (p *testProject) updateKindCluster(k *test_utils.EnvTestCluster, vars *uo.UnstructuredObject) { + context := k.KubectlMust(p.t, "config", "current-context") context = strings.TrimSpace(context) - p.updateCluster(k.Name, context, vars) + p.updateCluster(k.Context, context, vars) } func (p *testProject) updateTargetDeprecated(name string, cluster string, args *uo.UnstructuredObject) { diff --git a/e2e/seal_test.go b/e2e/seal_test.go index 28e6b976f..da18bb655 100644 --- a/e2e/seal_test.go +++ b/e2e/seal_test.go @@ -18,26 +18,26 @@ import ( "time" ) -func installSealedSecretsOperator(k *test_utils.KindCluster) { +func installSealedSecretsOperator(k *test_utils.EnvTestCluster) { test_resources.ApplyYaml("sealed-secrets.yaml", k) } -func waitForSealedSecretsOperator(t *testing.T, k *test_utils.KindCluster) { +func waitForSealedSecretsOperator(t *testing.T, k *test_utils.EnvTestCluster) { waitForReadiness(t, k, "kube-system", "deployment/sealed-secrets-controller", 5*time.Minute) } -func deleteSealedSecretsOperator(k *test_utils.KindCluster) { +func deleteSealedSecretsOperator(k *test_utils.EnvTestCluster) { test_resources.DeleteYaml("sealed-secrets.yaml", k) - _, _ = k.Kubectl("-n", "kube-system", "delete", "secret", "-l", "sealedsecrets.bitnami.com/sealed-secrets-key", "--wait") - _, _ = k.Kubectl("-n", "kube-system", "delete", "configmap", "sealed-secrets-key-kluctl-bootstrap", "--wait") + _, _, _ = k.Kubectl("-n", "kube-system", "delete", "secret", "-l", "sealedsecrets.bitnami.com/sealed-secrets-key", "--wait") + _, _, _ = k.Kubectl("-n", "kube-system", "delete", "configmap", "sealed-secrets-key-kluctl-bootstrap", "--wait") } -func installVault(k *test_utils.KindCluster) { - _, _ = k.Kubectl("create", "ns", "vault") +func installVault(k *test_utils.EnvTestCluster) { + _, _, _ = k.Kubectl("create", "ns", "vault") test_resources.ApplyYaml("vault.yaml", k) } -func waitForVault(t *testing.T, k *test_utils.KindCluster) { +func waitForVault(t *testing.T, k *test_utils.EnvTestCluster) { waitForReadiness(t, k, "vault", "statefulset/vault", 5*time.Minute) } @@ -56,11 +56,11 @@ func init() { wg.Wait() } -func prepareSealTest(t *testing.T, k *test_utils.KindCluster, namespace string, secrets map[string]string, varsSources []*uo.UnstructuredObject) *testProject { +func prepareSealTest(t *testing.T, k *test_utils.EnvTestCluster, namespace string, secrets map[string]string, varsSources []*uo.UnstructuredObject) *testProject { p := &testProject{} p.init(t, k, fmt.Sprintf("seal-%s", namespace)) - recreateNamespace(t, k, namespace) + createNamespace(t, k, namespace) addSecretsSet(p, "test", varsSources) addSecretsSetToTarget(p, "test-target", "test") @@ -84,7 +84,7 @@ func addSecretsSetToTarget(p *testProject, targetName string, secretSetName stri }) } -func assertDecryptedSecrets(t *testing.T, k *test_utils.KindCluster, namespace string, secretName string, expectedSecrets map[string]string) { +func assertDecryptedSecrets(t *testing.T, k *test_utils.EnvTestCluster, namespace string, secretName string, expectedSecrets map[string]string) { s := k.KubectlYamlMust(t, "-n", namespace, "get", "secret", secretName) for key, value := range expectedSecrets { @@ -294,7 +294,7 @@ func TestSeal_MultipleTargets(t *testing.T) { addSecretsSetToTarget(p, "test-target2", "test2") p.mergeKubeconfig(defaultKindCluster2) - recreateNamespace(t, defaultKindCluster2, namespace) + createNamespace(t, defaultKindCluster2, namespace) p.updateTarget("test-target", func(target *uo.UnstructuredObject) { _ = target.SetNestedField(defaultKindCluster1.Context, "context") }) diff --git a/e2e/test_resources/resources.go b/e2e/test_resources/resources.go index 24d724f66..90454201d 100644 --- a/e2e/test_resources/resources.go +++ b/e2e/test_resources/resources.go @@ -26,21 +26,21 @@ func GetYamlTmpFile(name string) string { return tmpFile.Name() } -func ApplyYaml(name string, k *test_utils.KindCluster) { +func ApplyYaml(name string, k *test_utils.EnvTestCluster) { tmpFile := GetYamlTmpFile(name) defer os.Remove(tmpFile) - _, err := k.Kubectl("apply", "-f", tmpFile) + _, _, err := k.Kubectl("apply", "-f", tmpFile) if err != nil { panic(err) } } -func DeleteYaml(name string, k *test_utils.KindCluster) { +func DeleteYaml(name string, k *test_utils.EnvTestCluster) { tmpFile := GetYamlTmpFile(name) defer os.Remove(tmpFile) - _, err := k.Kubectl("delete", "-f", tmpFile) + _, _, err := k.Kubectl("delete", "-f", tmpFile) if err != nil { panic(err) } diff --git a/e2e/utils.go b/e2e/utils.go index 9798a9de1..eaf69dea2 100644 --- a/e2e/utils.go +++ b/e2e/utils.go @@ -17,24 +17,19 @@ import ( log "github.com/sirupsen/logrus" ) -func recreateNamespace(t *testing.T, k *test_utils.KindCluster, namespace string) { - _, _ = k.Kubectl("delete", "ns", namespace) +func createNamespace(t *testing.T, k *test_utils.EnvTestCluster, namespace string) { k.KubectlMust(t, "create", "ns", namespace) k.KubectlMust(t, "label", "ns", namespace, "kluctl-e2e=true") } -func deleteTestNamespaces(k *test_utils.KindCluster) { - _, _ = k.Kubectl("delete", "ns", "-l", "kubectl-e2e=true") -} - -func waitForReadiness(t *testing.T, k *test_utils.KindCluster, namespace string, resource string, timeout time.Duration) bool { +func waitForReadiness(t *testing.T, k *test_utils.EnvTestCluster, namespace string, resource string, timeout time.Duration) bool { t.Logf("Waiting for readiness: %s/%s", namespace, resource) startTime := time.Now() for time.Now().Sub(startTime) < timeout { - y, err := k.KubectlYaml("-n", namespace, "get", resource) + y, stderr, err := k.KubectlYaml("-n", namespace, "get", resource) if err != nil { - if ee, ok := err.(*exec.ExitError); !ok || strings.Index(string(ee.Stderr), "NotFound") == -1 { + if strings.Index(stderr, "NotFound") == -1 { t.Fatal(err) } time.Sleep(1 * time.Second) @@ -61,13 +56,13 @@ func waitForReadiness(t *testing.T, k *test_utils.KindCluster, namespace string, return false } -func assertReadiness(t *testing.T, k *test_utils.KindCluster, namespace string, resource string, timeout time.Duration) { +func assertReadiness(t *testing.T, k *test_utils.EnvTestCluster, namespace string, resource string, timeout time.Duration) { if !waitForReadiness(t, k, namespace, resource, timeout) { t.Errorf("%s/%s did not get ready in time", namespace, resource) } } -func assertResourceExists(t *testing.T, k *test_utils.KindCluster, namespace string, resource string) *uo.UnstructuredObject { +func assertResourceExists(t *testing.T, k *test_utils.EnvTestCluster, namespace string, resource string) *uo.UnstructuredObject { var args []string if namespace != "" { args = append(args, "-n", namespace) @@ -76,21 +71,17 @@ func assertResourceExists(t *testing.T, k *test_utils.KindCluster, namespace str return k.KubectlYamlMust(t, args...) } -func assertResourceNotExists(t *testing.T, k *test_utils.KindCluster, namespace string, resource string) { +func assertResourceNotExists(t *testing.T, k *test_utils.EnvTestCluster, namespace string, resource string) { var args []string if namespace != "" { args = append(args, "-n", namespace) } args = append(args, "get", resource) - _, err := k.KubectlYaml(args...) + _, stderr, err := k.KubectlYaml(args...) if err == nil { t.Fatalf("'kubectl get' for %s should not have succeeded", resource) } else { - ee, ok := err.(*exec.ExitError) - if !ok { - t.Fatal(err) - } - if strings.Index(string(ee.Stderr), "(NotFound)") == -1 { + if strings.Index(stderr, "(NotFound)") == -1 { t.Fatal(err) } } diff --git a/go.mod b/go.mod index cc78c4587..5a7fa1316 100644 --- a/go.mod +++ b/go.mod @@ -53,12 +53,13 @@ require ( k8s.io/apimachinery v0.25.2 k8s.io/client-go v0.25.2 k8s.io/klog/v2 v2.80.1 - sigs.k8s.io/kind v0.16.0 sigs.k8s.io/kustomize/api v0.12.1 sigs.k8s.io/kustomize/kyaml v0.13.9 sigs.k8s.io/structured-merge-diff/v4 v4.2.3 ) +require sigs.k8s.io/controller-runtime v0.13.0 + require ( cloud.google.com/go/compute v1.10.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect @@ -76,7 +77,6 @@ require ( github.com/Microsoft/go-winio v0.6.0 // indirect github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895 // indirect github.com/acomagu/bufpipe v1.0.3 // indirect - github.com/alessio/shellescape v1.4.1 // indirect github.com/armon/go-metrics v0.4.1 // indirect github.com/armon/go-radix v1.0.0 // indirect github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect diff --git a/go.sum b/go.sum index 1b9d4d57c..c5651ad7a 100644 --- a/go.sum +++ b/go.sum @@ -63,7 +63,6 @@ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBp github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e h1:NeAW1fUYUEWhft7pkxDf6WoUvEZJ/uOKsvtpjLnn8MU= github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0= github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= @@ -101,8 +100,6 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= -github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= @@ -163,7 +160,6 @@ github.com/containerd/stargz-snapshotter/estargz v0.12.0 h1:idtwRTLjk2erqiYhPWy2 github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= @@ -255,6 +251,7 @@ github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KE github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= @@ -616,6 +613,7 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/ohler55/ojg v1.14.4 h1:L2ds8AlB5t/QbqSfhRwvagJzQ7pgmdrefMIypQs0Xik= github.com/ohler55/ojg v1.14.4/go.mod h1:7Ghirupn8NC8hSSDpI0gcjorPxj+vSVIONDWfliHR1k= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= @@ -632,7 +630,6 @@ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FI github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= @@ -732,7 +729,6 @@ github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= -github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= @@ -807,8 +803,10 @@ go.starlark.net v0.0.0-20220926145019-14b050677505/go.mod h1:qsNirHv+Awo5xHuNyQ/ go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -1098,6 +1096,7 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -1227,6 +1226,7 @@ gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -1282,10 +1282,10 @@ oras.land/oras-go v1.2.0/go.mod h1:pFNs7oHp2dYsYMSS82HaX5l4mpnGO7hbpPN6EWH2ltc= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/controller-runtime v0.13.0 h1:iqa5RNciy7ADWnIc8QxCbOX5FEKVR3uxVxKHRMc2WIQ= +sigs.k8s.io/controller-runtime v0.13.0/go.mod h1:Zbz+el8Yg31jubvAEyglRZGdLAjplZl+PgtYNI6WNTI= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/kind v0.16.0 h1:GFXyyxtPnHFKqXr3ZG8/X0+0K9sl69lejStlPn2WQyM= -sigs.k8s.io/kind v0.16.0/go.mod h1:cKTqagdRyUQmihhBOd+7p43DpOPRn9rHsUC08K1Jbsk= sigs.k8s.io/kustomize/api v0.12.1 h1:7YM7gW3kYBwtKvoY216ZzY+8hM+lV53LUayghNRJ0vM= sigs.k8s.io/kustomize/api v0.12.1/go.mod h1:y3JUhimkZkR6sbLNwfJHxvo1TCLwuwm14sCYnkH6S1s= sigs.k8s.io/kustomize/kyaml v0.13.9 h1:Qz53EAaFFANyNgyOEJbT/yoIHygK40/ZcvU3rgry2Tk= diff --git a/internal/test-utils/envtest_cluster.go b/internal/test-utils/envtest_cluster.go new file mode 100644 index 000000000..1151e87a7 --- /dev/null +++ b/internal/test-utils/envtest_cluster.go @@ -0,0 +1,100 @@ +package test_utils + +import ( + "bytes" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/yaml" + "io" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/envtest" + "testing" +) + +type EnvTestCluster struct { + env envtest.Environment + + user *envtest.AuthenticatedUser + Kubeconfig []byte + Context string + config *rest.Config +} + +func CreateEnvTestCluster(context string) (*EnvTestCluster, error) { + k := &EnvTestCluster{ + Context: context, + } + + _, err := k.env.Start() + if err != nil { + return nil, err + } + + isOk := false + defer func() { + if !isOk { + _ = k.env.Stop() + } + }() + + user, err := k.env.AddUser(envtest.User{Name: "default", Groups: []string{"system:masters"}}, &rest.Config{}) + if err != nil { + return nil, err + } + k.user = user + + kcfg, err := user.KubeConfig() + if err != nil { + return nil, err + } + + kcfg = bytes.ReplaceAll(kcfg, []byte("envtest"), []byte(context)) + + k.Kubeconfig = kcfg + + isOk = true + return k, nil +} + +// RESTConfig returns K8s client config to pass to clientset objects +func (c *EnvTestCluster) RESTConfig() *rest.Config { + return c.config +} + +func (c *EnvTestCluster) Kubectl(args ...string) (string, string, error) { + kctl, err := c.user.Kubectl() + if err != nil { + return "", "", err + } + + stdout1, stderr1, err := kctl.Run(args...) + stdout, _ := io.ReadAll(stdout1) + stderr, _ := io.ReadAll(stderr1) + return string(stdout), string(stderr), err +} + +func (c *EnvTestCluster) KubectlMust(t *testing.T, args ...string) string { + stdout, stderr, err := c.Kubectl(args...) + if err != nil { + t.Fatalf("%v, stderr=%s\n", err, stderr) + } + return stdout +} + +func (c *EnvTestCluster) KubectlYaml(args ...string) (*uo.UnstructuredObject, string, error) { + args = append(args, "-oyaml") + stdout, stderr, err := c.Kubectl(args...) + if err != nil { + return nil, stderr, err + } + ret := uo.New() + err = yaml.ReadYamlString(stdout, ret) + return ret, stderr, err +} + +func (c *EnvTestCluster) KubectlYamlMust(t *testing.T, args ...string) *uo.UnstructuredObject { + o, stderr, err := c.KubectlYaml(args...) + if err != nil { + t.Fatalf("%v, stderr=%s\n", err, stderr) + } + return o +} diff --git a/internal/test-utils/kind_cluster.go b/internal/test-utils/kind_cluster.go deleted file mode 100644 index 99ad404d7..000000000 --- a/internal/test-utils/kind_cluster.go +++ /dev/null @@ -1,181 +0,0 @@ -package test_utils - -import ( - "fmt" - "github.com/kluctl/kluctl/v2/pkg/utils/uo" - "github.com/kluctl/kluctl/v2/pkg/yaml" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" - "net/url" - "os" - "os/exec" - "sigs.k8s.io/kind/pkg/apis/config/v1alpha4" - "sigs.k8s.io/kind/pkg/cluster" - kindcmd "sigs.k8s.io/kind/pkg/cmd" - "testing" - "time" -) - -type KindCluster struct { - Name string - Context string - Kubeconfig string - config *rest.Config -} - -func CreateKindCluster(name, apiServerHost string, apiServerPort int, extraPorts map[int]int, kubeconfigPath string) (*KindCluster, error) { - provider := cluster.NewProvider(cluster.ProviderWithLogger(kindcmd.NewLogger())) - - c := &KindCluster{ - Name: name, - Context: fmt.Sprintf("kind-%s", name), - Kubeconfig: kubeconfigPath, - } - - n, err := provider.ListNodes(name) - if err != nil { - return nil, err - } - if len(n) == 0 { - if err := kindCreate(name, apiServerHost, apiServerPort, extraPorts, kubeconfigPath); err != nil { - return nil, err - } - } - - return c, nil -} - -// Delete removes the cluster from kind. The cluster may not be deleted straight away - this only issues a delete command -func (c *KindCluster) Delete() error { - provider := cluster.NewProvider(cluster.ProviderWithLogger(kindcmd.NewLogger())) - return provider.Delete(c.Name, c.Kubeconfig) -} - -// RESTConfig returns K8s client config to pass to clientset objects -func (c *KindCluster) RESTConfig() *rest.Config { - if c.config == nil { - var err error - c.config, err = clientcmd.BuildConfigFromFlags("", c.Kubeconfig) - if err != nil { - panic(err) - } - } - return c.config -} - -func (c *KindCluster) Kubectl(args ...string) (string, error) { - cmd := exec.Command("kubectl", args...) - cmd.Env = os.Environ() - cmd.Env = append(cmd.Env, fmt.Sprintf("KUBECONFIG=%s", c.Kubeconfig)) - - stdout, err := cmd.Output() - return string(stdout), err -} - -func (c *KindCluster) KubectlMust(t *testing.T, args ...string) string { - stdout, err := c.Kubectl(args...) - if err != nil { - if e, ok := err.(*exec.ExitError); ok { - t.Fatalf("%v, stderr=%s\n", err, string(e.Stderr)) - } else { - t.Fatal(err) - } - } - return stdout -} - -func (c *KindCluster) KubectlYaml(args ...string) (*uo.UnstructuredObject, error) { - args = append(args, "-oyaml") - stdout, err := c.Kubectl(args...) - if err != nil { - return nil, err - } - ret := uo.New() - err = yaml.ReadYamlString(stdout, ret) - return ret, err -} - -func (c *KindCluster) KubectlYamlMust(t *testing.T, args ...string) *uo.UnstructuredObject { - o, err := c.KubectlYaml(args...) - if err != nil { - if e, ok := err.(*exec.ExitError); ok { - t.Fatalf("%v, stderr=%s\n", err, string(e.Stderr)) - } else { - t.Fatal(err) - } - } - return o -} - -// kindCreate creates the kind cluster. It will retry up to 10 times if cluster creation fails. -func kindCreate(name string, apiServerHost string, apiServerPort int, extraPorts map[int]int, kubeconfig string) error { - var err error - for i := 0; i < 10; i++ { - err = kindCreate2(name, apiServerHost, apiServerPort, extraPorts, kubeconfig) - if err == nil { - return nil - } - } - return err -} - -func kindCreate2(name string, apiServerHost string, apiServerPort int, extraPorts map[int]int, kubeconfig string) error { - fmt.Printf("🌧️ Creating kind cluster %s with apiServerHost=%s, apiServerPort=%d, extraPorts=%v...\n", name, apiServerHost, apiServerPort, extraPorts) - provider := cluster.NewProvider(cluster.ProviderWithLogger(kindcmd.NewLogger())) - config := v1alpha4.Cluster{ - Name: name, - Nodes: []v1alpha4.Node{{ - Role: "control-plane", - }}, - Networking: v1alpha4.Networking{ - APIServerAddress: "0.0.0.0", - APIServerPort: int32(apiServerPort), - }, - } - for hostPort, containerPort := range extraPorts { - config.Nodes[0].ExtraPortMappings = append(config.Nodes[0].ExtraPortMappings, v1alpha4.PortMapping{ - ContainerPort: int32(containerPort), - HostPort: int32(hostPort), - ListenAddress: "0.0.0.0", - Protocol: "TCP", - }) - } - - err := provider.Create( - name, - cluster.CreateWithV1Alpha4Config(&config), - cluster.CreateWithNodeImage(""), - cluster.CreateWithRetain(false), - cluster.CreateWithWaitForReady(time.Duration(0)), - cluster.CreateWithKubeconfigPath(kubeconfig), - cluster.CreateWithDisplayUsage(false), - ) - if err != nil { - return err - } - - kcfg, err := clientcmd.LoadFromFile(kubeconfig) - if err != nil { - return err - } - - c := kcfg.Clusters[fmt.Sprintf("kind-%s", name)] - - u, err := url.Parse(c.Server) - if err != nil { - return err - } - - // override api server host and disable TLS verification - // this is needed to make it work with remote docker hosts - c.InsecureSkipTLSVerify = true - c.CertificateAuthorityData = nil - c.Server = fmt.Sprintf("https://%s:%s", apiServerHost, u.Port()) - - err = clientcmd.WriteToFile(*kcfg, kubeconfig) - if err != nil { - return err - } - - return nil -} From 80952981c34216cd441586ec3ee6b8efa07e4555 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 29 Sep 2022 12:04:57 +0200 Subject: [PATCH 1037/2916] tests: Rename defaultKindClusterX to defaultClusterX --- e2e/contexts_test.go | 48 +++++++++++++++++------------------ e2e/default_clusters.go | 6 ++--- e2e/deploy_test.go | 2 +- e2e/external_projects_test.go | 6 ++--- e2e/flux_test.go | 2 +- e2e/hooks_test.go | 2 +- e2e/inclusion_test.go | 2 +- e2e/project.go | 2 +- e2e/seal_test.go | 36 +++++++++++++------------- 9 files changed, 53 insertions(+), 53 deletions(-) diff --git a/e2e/contexts_test.go b/e2e/contexts_test.go index 34d036a1e..5600f3689 100644 --- a/e2e/contexts_test.go +++ b/e2e/contexts_test.go @@ -8,11 +8,11 @@ import ( func prepareContextTest(t *testing.T, name string) *testProject { p := &testProject{} - p.init(t, defaultKindCluster1, name) - p.mergeKubeconfig(defaultKindCluster2) + p.init(t, defaultCluster1, name) + p.mergeKubeconfig(defaultCluster2) - createNamespace(t, defaultKindCluster1, p.projectName) - createNamespace(t, defaultKindCluster2, p.projectName) + createNamespace(t, defaultCluster1, p.projectName) + createNamespace(t, defaultCluster2, p.projectName) addConfigMapDeployment(p, "cm", nil, resourceOpts{ name: "cm", @@ -33,15 +33,15 @@ func TestContextCurrent(t *testing.T) { }) p.KluctlMust("deploy", "--yes", "-t", "test1") - assertResourceExists(t, defaultKindCluster1, p.projectName, "ConfigMap/cm") - assertResourceNotExists(t, defaultKindCluster2, p.projectName, "ConfigMap/cm") + assertResourceExists(t, defaultCluster1, p.projectName, "ConfigMap/cm") + assertResourceNotExists(t, defaultCluster2, p.projectName, "ConfigMap/cm") p.updateMergedKubeconfig(func(config *api.Config) { - config.CurrentContext = defaultKindCluster2.Context + config.CurrentContext = defaultCluster2.Context }) p.KluctlMust("deploy", "--yes", "-t", "test1") - assertResourceExists(t, defaultKindCluster2, p.projectName, "ConfigMap/cm") + assertResourceExists(t, defaultCluster2, p.projectName, "ConfigMap/cm") } func TestContext1(t *testing.T) { @@ -51,12 +51,12 @@ func TestContext1(t *testing.T) { defer p.cleanup() p.updateTarget("test1", func(target *uo.UnstructuredObject) { - _ = target.SetNestedField(defaultKindCluster1.Context, "context") + _ = target.SetNestedField(defaultCluster1.Context, "context") }) p.KluctlMust("deploy", "--yes", "-t", "test1") - assertResourceExists(t, defaultKindCluster1, p.projectName, "ConfigMap/cm") - assertResourceNotExists(t, defaultKindCluster2, p.projectName, "ConfigMap/cm") + assertResourceExists(t, defaultCluster1, p.projectName, "ConfigMap/cm") + assertResourceNotExists(t, defaultCluster2, p.projectName, "ConfigMap/cm") } func TestContext2(t *testing.T) { @@ -66,12 +66,12 @@ func TestContext2(t *testing.T) { defer p.cleanup() p.updateTarget("test1", func(target *uo.UnstructuredObject) { - _ = target.SetNestedField(defaultKindCluster2.Context, "context") + _ = target.SetNestedField(defaultCluster2.Context, "context") }) p.KluctlMust("deploy", "--yes", "-t", "test1") - assertResourceExists(t, defaultKindCluster2, p.projectName, "ConfigMap/cm") - assertResourceNotExists(t, defaultKindCluster1, p.projectName, "ConfigMap/cm") + assertResourceExists(t, defaultCluster2, p.projectName, "ConfigMap/cm") + assertResourceNotExists(t, defaultCluster1, p.projectName, "ConfigMap/cm") } func TestContext1And2(t *testing.T) { @@ -81,18 +81,18 @@ func TestContext1And2(t *testing.T) { defer p.cleanup() p.updateTarget("test1", func(target *uo.UnstructuredObject) { - _ = target.SetNestedField(defaultKindCluster1.Context, "context") + _ = target.SetNestedField(defaultCluster1.Context, "context") }) p.updateTarget("test2", func(target *uo.UnstructuredObject) { - _ = target.SetNestedField(defaultKindCluster2.Context, "context") + _ = target.SetNestedField(defaultCluster2.Context, "context") }) p.KluctlMust("deploy", "--yes", "-t", "test1") - assertResourceExists(t, defaultKindCluster1, p.projectName, "ConfigMap/cm") - assertResourceNotExists(t, defaultKindCluster2, p.projectName, "ConfigMap/cm") + assertResourceExists(t, defaultCluster1, p.projectName, "ConfigMap/cm") + assertResourceNotExists(t, defaultCluster2, p.projectName, "ConfigMap/cm") p.KluctlMust("deploy", "--yes", "-t", "test2") - assertResourceExists(t, defaultKindCluster2, p.projectName, "ConfigMap/cm") + assertResourceExists(t, defaultCluster2, p.projectName, "ConfigMap/cm") } func TestContextSwitch(t *testing.T) { @@ -102,17 +102,17 @@ func TestContextSwitch(t *testing.T) { defer p.cleanup() p.updateTarget("test1", func(target *uo.UnstructuredObject) { - _ = target.SetNestedField(defaultKindCluster1.Context, "context") + _ = target.SetNestedField(defaultCluster1.Context, "context") }) p.KluctlMust("deploy", "--yes", "-t", "test1") - assertResourceExists(t, defaultKindCluster1, p.projectName, "ConfigMap/cm") - assertResourceNotExists(t, defaultKindCluster2, p.projectName, "ConfigMap/cm") + assertResourceExists(t, defaultCluster1, p.projectName, "ConfigMap/cm") + assertResourceNotExists(t, defaultCluster2, p.projectName, "ConfigMap/cm") p.updateTarget("test1", func(target *uo.UnstructuredObject) { - _ = target.SetNestedField(defaultKindCluster2.Context, "context") + _ = target.SetNestedField(defaultCluster2.Context, "context") }) p.KluctlMust("deploy", "--yes", "-t", "test1") - assertResourceExists(t, defaultKindCluster2, p.projectName, "ConfigMap/cm") + assertResourceExists(t, defaultCluster2, p.projectName, "ConfigMap/cm") } diff --git a/e2e/default_clusters.go b/e2e/default_clusters.go index c042571cc..1d632c7b4 100644 --- a/e2e/default_clusters.go +++ b/e2e/default_clusters.go @@ -5,7 +5,7 @@ import ( "sync" ) -var defaultKindCluster1, defaultKindCluster2 *test_utils.EnvTestCluster +var defaultCluster1, defaultCluster2 *test_utils.EnvTestCluster var defaultKindCluster1VaultPort, defaultKindCluster2VaultPort int func init() { @@ -14,7 +14,7 @@ func init() { go func() { defer wg.Done() var err error - defaultKindCluster1, err = test_utils.CreateEnvTestCluster("cluster1") + defaultCluster1, err = test_utils.CreateEnvTestCluster("cluster1") if err != nil { panic(err) } @@ -22,7 +22,7 @@ func init() { go func() { defer wg.Done() var err error - defaultKindCluster2, err = test_utils.CreateEnvTestCluster("cluster2") + defaultCluster2, err = test_utils.CreateEnvTestCluster("cluster2") if err != nil { panic(err) } diff --git a/e2e/deploy_test.go b/e2e/deploy_test.go index 97044e7eb..9e54ebcf8 100644 --- a/e2e/deploy_test.go +++ b/e2e/deploy_test.go @@ -7,7 +7,7 @@ import ( func TestCommandDeploySimple(t *testing.T) { t.Parallel() - k := defaultKindCluster1 + k := defaultCluster1 p := &testProject{} p.init(t, k, "simple") diff --git a/e2e/external_projects_test.go b/e2e/external_projects_test.go index f2a4dd9db..94e6aebc6 100644 --- a/e2e/external_projects_test.go +++ b/e2e/external_projects_test.go @@ -7,14 +7,14 @@ import ( ) func doTestProject(t *testing.T, namespace string, p *testProject) { - k := defaultKindCluster1 + k := defaultCluster1 p.init(t, k, fmt.Sprintf("project-%s", namespace)) defer p.cleanup() createNamespace(t, k, namespace) - p.updateKindCluster(k, uo.FromMap(map[string]interface{}{ + p.updateEnvTestCluster(k, uo.FromMap(map[string]interface{}{ "cluster_var": "cluster_value1", })) p.updateTargetDeprecated("test", k.Context, uo.FromMap(map[string]interface{}{ @@ -38,7 +38,7 @@ func doTestProject(t *testing.T, namespace string, p *testProject) { assertNestedFieldEquals(t, o, "cluster_value1", "data", "cluster_var") assertNestedFieldEquals(t, o, "target_value1", "data", "target_var") - p.updateKindCluster(k, uo.FromMap(map[string]interface{}{ + p.updateEnvTestCluster(k, uo.FromMap(map[string]interface{}{ "cluster_var": "cluster_value2", })) p.KluctlMust("deploy", "--yes", "-t", "test") diff --git a/e2e/flux_test.go b/e2e/flux_test.go index d11f62790..1b604dccb 100644 --- a/e2e/flux_test.go +++ b/e2e/flux_test.go @@ -12,7 +12,7 @@ import ( func TestFluxCommands(t *testing.T) { t.Parallel() - k := defaultKindCluster1 + k := defaultCluster1 p := &testProject{} p.init(t, k, "simple") diff --git a/e2e/hooks_test.go b/e2e/hooks_test.go index 4c9cf2930..395640e7b 100644 --- a/e2e/hooks_test.go +++ b/e2e/hooks_test.go @@ -111,7 +111,7 @@ func assertHookResultNotCMName(t *testing.T, p *testProject, k *test_utils.EnvTe func prepareHookTestProject(t *testing.T, name string, hook string, hookDeletionPolicy string) (*testProject, *test_utils.EnvTestCluster) { isDone := false namespace := fmt.Sprintf("hook-%s", name) - k := defaultKindCluster1 + k := defaultCluster1 p := &testProject{} p.init(t, k, namespace) defer func() { diff --git a/e2e/inclusion_test.go b/e2e/inclusion_test.go index 247073d64..243584b03 100644 --- a/e2e/inclusion_test.go +++ b/e2e/inclusion_test.go @@ -11,7 +11,7 @@ import ( func prepareInclusionTestProject(t *testing.T, namespace string, withIncludes bool) (*testProject, *test_utils.EnvTestCluster) { isDone := false - k := defaultKindCluster1 + k := defaultCluster1 p := &testProject{} p.init(t, k, namespace) defer func() { diff --git a/e2e/project.go b/e2e/project.go index f540b6b3d..39996fa5b 100644 --- a/e2e/project.go +++ b/e2e/project.go @@ -187,7 +187,7 @@ func (p *testProject) updateCluster(name string, context string, vars *uo.Unstru }, fmt.Sprintf("add/update cluster %s", name)) } -func (p *testProject) updateKindCluster(k *test_utils.EnvTestCluster, vars *uo.UnstructuredObject) { +func (p *testProject) updateEnvTestCluster(k *test_utils.EnvTestCluster, vars *uo.UnstructuredObject) { context := k.KubectlMust(p.t, "config", "current-context") context = strings.TrimSpace(context) p.updateCluster(k.Context, context, vars) diff --git a/e2e/seal_test.go b/e2e/seal_test.go index da18bb655..f74a7544a 100644 --- a/e2e/seal_test.go +++ b/e2e/seal_test.go @@ -46,12 +46,12 @@ func init() { wg.Add(2) go func() { defer wg.Done() - installSealedSecretsOperator(defaultKindCluster1) - installVault(defaultKindCluster1) + installSealedSecretsOperator(defaultCluster1) + installVault(defaultCluster1) }() go func() { defer wg.Done() - installSealedSecretsOperator(defaultKindCluster2) + installSealedSecretsOperator(defaultCluster2) }() wg.Wait() } @@ -95,7 +95,7 @@ func assertDecryptedSecrets(t *testing.T, k *test_utils.EnvTestCluster, namespac } func TestSeal_WithOperator(t *testing.T) { - k := defaultKindCluster1 + k := defaultCluster1 namespace := "seal-with-operator" waitForSealedSecretsOperator(t, k) @@ -131,7 +131,7 @@ func TestSeal_WithOperator(t *testing.T) { } func TestSeal_WithBootstrap(t *testing.T) { - k := defaultKindCluster2 + k := defaultCluster2 namespace := "seal-with-bootstrap" // we still wait for it to be ready before we then delete it @@ -175,7 +175,7 @@ func TestSeal_WithBootstrap(t *testing.T) { func TestSeal_MultipleVarSources(t *testing.T) { t.Parallel() - k := defaultKindCluster1 + k := defaultCluster1 namespace := "seal-multiple-vs" waitForSealedSecretsOperator(t, k) @@ -217,7 +217,7 @@ func TestSeal_MultipleVarSources(t *testing.T) { func TestSeal_MultipleSecretSets(t *testing.T) { t.Parallel() - k := defaultKindCluster1 + k := defaultCluster1 namespace := "seal-multiple-ss" waitForSealedSecretsOperator(t, k) @@ -261,11 +261,11 @@ func TestSeal_MultipleSecretSets(t *testing.T) { } func TestSeal_MultipleTargets(t *testing.T) { - k := defaultKindCluster1 + k := defaultCluster1 namespace := "seal-multiple-targets" waitForSealedSecretsOperator(t, k) - waitForSealedSecretsOperator(t, defaultKindCluster2) + waitForSealedSecretsOperator(t, defaultCluster2) p := prepareSealTest(t, k, namespace, map[string]string{ @@ -293,13 +293,13 @@ func TestSeal_MultipleTargets(t *testing.T) { }) addSecretsSetToTarget(p, "test-target2", "test2") - p.mergeKubeconfig(defaultKindCluster2) - createNamespace(t, defaultKindCluster2, namespace) + p.mergeKubeconfig(defaultCluster2) + createNamespace(t, defaultCluster2, namespace) p.updateTarget("test-target", func(target *uo.UnstructuredObject) { - _ = target.SetNestedField(defaultKindCluster1.Context, "context") + _ = target.SetNestedField(defaultCluster1.Context, "context") }) p.updateTarget("test-target2", func(target *uo.UnstructuredObject) { - _ = target.SetNestedField(defaultKindCluster2.Context, "context") + _ = target.SetNestedField(defaultCluster2.Context, "context") }) p.KluctlMust("seal", "-t", "test-target") @@ -317,8 +317,8 @@ func TestSeal_MultipleTargets(t *testing.T) { "s1": "v1", "s2": "v2", }) - waitForReadiness(t, defaultKindCluster2, namespace, "secret/secret", 1*time.Minute) - assertDecryptedSecrets(t, defaultKindCluster2, namespace, "secret", map[string]string{ + waitForReadiness(t, defaultCluster2, namespace, "secret/secret", 1*time.Minute) + assertDecryptedSecrets(t, defaultCluster2, namespace, "secret", map[string]string{ "s1": "v3", "s2": "v4", }) @@ -327,7 +327,7 @@ func TestSeal_MultipleTargets(t *testing.T) { func TestSeal_File(t *testing.T) { t.Parallel() - k := defaultKindCluster1 + k := defaultCluster1 namespace := "seal-file" waitForSealedSecretsOperator(t, k) @@ -372,13 +372,13 @@ func TestSeal_File(t *testing.T) { func TestSeal_Vault(t *testing.T) { t.Parallel() - k := defaultKindCluster1 + k := defaultCluster1 namespace := "seal-vault" waitForSealedSecretsOperator(t, k) waitForVault(t, k) - u, err := url.Parse(defaultKindCluster1.RESTConfig().Host) + u, err := url.Parse(defaultCluster1.RESTConfig().Host) if err != nil { t.Fatal(err) } From dfc44a928a70d693b336663700d8c4f88b4eff78 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 29 Sep 2022 12:16:24 +0200 Subject: [PATCH 1038/2916] tests: Enforce use of modified kubeconfig in Kubectl calls --- internal/test-utils/envtest_cluster.go | 30 +++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/internal/test-utils/envtest_cluster.go b/internal/test-utils/envtest_cluster.go index 1151e87a7..eba311716 100644 --- a/internal/test-utils/envtest_cluster.go +++ b/internal/test-utils/envtest_cluster.go @@ -2,10 +2,13 @@ package test_utils import ( "bytes" + "fmt" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" "io" "k8s.io/client-go/rest" + "os" + "os/exec" "sigs.k8s.io/controller-runtime/pkg/envtest" "testing" ) @@ -46,7 +49,7 @@ func CreateEnvTestCluster(context string) (*EnvTestCluster, error) { if err != nil { return nil, err } - + kcfg = bytes.ReplaceAll(kcfg, []byte("envtest"), []byte(context)) k.Kubeconfig = kcfg @@ -61,14 +64,31 @@ func (c *EnvTestCluster) RESTConfig() *rest.Config { } func (c *EnvTestCluster) Kubectl(args ...string) (string, string, error) { - kctl, err := c.user.Kubectl() + tmp, err := os.CreateTemp("", "") if err != nil { return "", "", err } + defer func() { + tmp.Close() + os.Remove(tmp.Name()) + }() + + _, err = tmp.Write(c.Kubeconfig) + if err != nil { + return "", "", err + } + + stdoutBuffer := &bytes.Buffer{} + stderrBuffer := &bytes.Buffer{} + allArgs := append([]string{fmt.Sprintf("--kubeconfig=%s", tmp.Name())}, args...) + + cmd := exec.Command(c.env.ControlPlane.KubectlPath, allArgs...) + cmd.Stdout = stdoutBuffer + cmd.Stderr = stderrBuffer - stdout1, stderr1, err := kctl.Run(args...) - stdout, _ := io.ReadAll(stdout1) - stderr, _ := io.ReadAll(stderr1) + err = cmd.Run() + stdout, _ := io.ReadAll(stdoutBuffer) + stderr, _ := io.ReadAll(stderrBuffer) return string(stdout), string(stderr), err } From 15ba31bab821aef6ae4b359d8312b70108c24934 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 29 Sep 2022 23:35:44 +0200 Subject: [PATCH 1039/2916] tests: Implement webhook callbacks in EnvTestCluster --- internal/test-utils/envtest_cluster.go | 46 +++++--- .../test-utils/envtest_cluster_callback.go | 109 ++++++++++++++++++ 2 files changed, 141 insertions(+), 14 deletions(-) create mode 100644 internal/test-utils/envtest_cluster_callback.go diff --git a/internal/test-utils/envtest_cluster.go b/internal/test-utils/envtest_cluster.go index eba311716..7683f8079 100644 --- a/internal/test-utils/envtest_cluster.go +++ b/internal/test-utils/envtest_cluster.go @@ -2,6 +2,7 @@ package test_utils import ( "bytes" + "context" "fmt" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" @@ -10,52 +11,69 @@ import ( "os" "os/exec" "sigs.k8s.io/controller-runtime/pkg/envtest" + "sigs.k8s.io/controller-runtime/pkg/webhook" "testing" ) type EnvTestCluster struct { - env envtest.Environment + env envtest.Environment + started bool user *envtest.AuthenticatedUser Kubeconfig []byte Context string config *rest.Config + + callbackServer webhook.Server + callbackServerStop context.CancelFunc } -func CreateEnvTestCluster(context string) (*EnvTestCluster, error) { +func CreateEnvTestCluster(context string) *EnvTestCluster { k := &EnvTestCluster{ Context: context, } + return k +} +func (k *EnvTestCluster) Start() error { _, err := k.env.Start() if err != nil { - return nil, err + return err } + k.started = true - isOk := false - defer func() { - if !isOk { - _ = k.env.Stop() - } - }() + err = k.startCallbackServer() + if err != nil { + return err + } user, err := k.env.AddUser(envtest.User{Name: "default", Groups: []string{"system:masters"}}, &rest.Config{}) if err != nil { - return nil, err + return err } k.user = user kcfg, err := user.KubeConfig() if err != nil { - return nil, err + return err } - kcfg = bytes.ReplaceAll(kcfg, []byte("envtest"), []byte(context)) + kcfg = bytes.ReplaceAll(kcfg, []byte("envtest"), []byte(k.Context)) k.Kubeconfig = kcfg - isOk = true - return k, nil + return nil +} + +func (c *EnvTestCluster) Stop() { + if c.started { + _ = c.env.Stop() + c.started = false + } + if c.callbackServerStop != nil { + c.callbackServerStop() + c.callbackServerStop = nil + } } // RESTConfig returns K8s client config to pass to clientset objects diff --git a/internal/test-utils/envtest_cluster_callback.go b/internal/test-utils/envtest_cluster_callback.go new file mode 100644 index 000000000..2ec870b3c --- /dev/null +++ b/internal/test-utils/envtest_cluster_callback.go @@ -0,0 +1,109 @@ +package test_utils + +import ( + "context" + "fmt" + "github.com/go-logr/logr" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + admissionv1 "k8s.io/api/admissionregistration/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "net/http" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +type CallbackHandler func(o *uo.UnstructuredObject) + +func (k *EnvTestCluster) buildServeCallback(gvr schema.GroupVersionResource, cb CallbackHandler) http.Handler { + wh := &webhook.Admission{ + Handler: admission.HandlerFunc(func(ctx context.Context, request admission.Request) admission.Response { + k.serveCallback(gvr, request, cb) + return admission.Allowed("") + }), + } + _ = wh.InjectLogger(logr.New(log.NullLogSink{})) + return wh +} + +func (k *EnvTestCluster) serveCallback(gvr schema.GroupVersionResource, request admission.Request, cb CallbackHandler) { + o, err := uo.FromString(string(request.Object.Raw)) + if err != nil { + panic(err) + } + + cb(o) +} + +func (k *EnvTestCluster) startCallbackServer() error { + k.callbackServer.Host = k.env.WebhookInstallOptions.LocalServingHost + k.callbackServer.Port = k.env.WebhookInstallOptions.LocalServingPort + k.callbackServer.CertDir = k.env.WebhookInstallOptions.LocalServingCertDir + + ctx, cancel := context.WithCancel(context.Background()) + k.callbackServerStop = cancel + + go func() { + _ = k.callbackServer.Start(ctx) + }() + + return nil +} + +func (k *EnvTestCluster) AddWebhookCallback(gvr schema.GroupVersionResource, isNamespaced bool, cb CallbackHandler) { + scope := admissionv1.ClusterScope + if isNamespaced { + scope = admissionv1.NamespacedScope + } + + failedTypeV1 := admissionv1.Fail + equivalentTypeV1 := admissionv1.Equivalent + noSideEffectsV1 := admissionv1.SideEffectClassNone + timeout := int32(30) + + group := gvr.Group + if gvr.Group == "" { + group = "none" + } + name := fmt.Sprintf("%s-%s-%s-callback", group, gvr.Version, gvr.Resource) + path := "/" + name + + k.env.WebhookInstallOptions.ValidatingWebhooks = append(k.env.WebhookInstallOptions.ValidatingWebhooks, &admissionv1.ValidatingWebhookConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + TypeMeta: metav1.TypeMeta{ + Kind: "ValidatingWebhookConfiguration", + APIVersion: "admissionregistration.k8s.io/v1", + }, + Webhooks: []admissionv1.ValidatingWebhook{ + { + Name: fmt.Sprintf("%s.callback.kubectl.io", name), + Rules: []admissionv1.RuleWithOperations{ + { + Operations: []admissionv1.OperationType{"CREATE", "UPDATE"}, + Rule: admissionv1.Rule{ + APIGroups: []string{gvr.Group}, + APIVersions: []string{gvr.Version}, + Resources: []string{gvr.Resource}, + Scope: &scope, + }, + }, + }, + FailurePolicy: &failedTypeV1, + MatchPolicy: &equivalentTypeV1, + SideEffects: &noSideEffectsV1, + ClientConfig: admissionv1.WebhookClientConfig{ + Service: &admissionv1.ServiceReference{ + Path: &name, + }, + }, + AdmissionReviewVersions: []string{"v1"}, + TimeoutSeconds: &timeout, + }, + }, + }) + + k.callbackServer.Register(path, k.buildServeCallback(gvr, cb)) +} From c3daf546e27dc3aff0a2dee6a85ebe04572bb4cc Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 29 Sep 2022 23:36:08 +0200 Subject: [PATCH 1040/2916] tests: Simplify hook tests by using webhook callbacks --- e2e/default_clusters.go | 9 +- e2e/hooks_test.go | 268 +++++++++++++++------------------------- 2 files changed, 107 insertions(+), 170 deletions(-) diff --git a/e2e/default_clusters.go b/e2e/default_clusters.go index 1d632c7b4..b65f26b6d 100644 --- a/e2e/default_clusters.go +++ b/e2e/default_clusters.go @@ -5,7 +5,8 @@ import ( "sync" ) -var defaultCluster1, defaultCluster2 *test_utils.EnvTestCluster +var defaultCluster1 = test_utils.CreateEnvTestCluster("cluster1") +var defaultCluster2 = test_utils.CreateEnvTestCluster("cluster2") var defaultKindCluster1VaultPort, defaultKindCluster2VaultPort int func init() { @@ -13,16 +14,14 @@ func init() { wg.Add(2) go func() { defer wg.Done() - var err error - defaultCluster1, err = test_utils.CreateEnvTestCluster("cluster1") + err := defaultCluster1.Start() if err != nil { panic(err) } }() go func() { defer wg.Done() - var err error - defaultCluster2, err = test_utils.CreateEnvTestCluster("cluster2") + err := defaultCluster2.Start() if err != nil { panic(err) } diff --git a/e2e/hooks_test.go b/e2e/hooks_test.go index 395640e7b..f860553ed 100644 --- a/e2e/hooks_test.go +++ b/e2e/hooks_test.go @@ -1,27 +1,58 @@ package e2e import ( - "encoding/base64" "fmt" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" + "k8s.io/apimachinery/pkg/runtime/schema" "testing" test_utils "github.com/kluctl/kluctl/v2/internal/test-utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" ) -const hookScript = ` -kubectl get configmap -oyaml > /tmp/result.yml -cat /tmp/result.yml -if ! kubectl get secret {{ .name }}-result; then - name="{{ .name }}-result" -else - name="{{ .name }}-result2" -fi -kubectl create secret generic $name --from-file=result=/tmp/result.yml -kubectl delete configmap cm2 || true -` - -func addHookDeployment(p *testProject, dir string, opts resourceOpts, isHelm bool, hook string, hookDeletionPolicy string) { +type HookTestSuite struct { + suite.Suite + + k *test_utils.EnvTestCluster + + seenConfigMaps []string +} + +func TestHooks(t *testing.T) { + suite.Run(t, &HookTestSuite{}) +} + +func (s *HookTestSuite) SetupSuite() { + s.clearSeenConfigmaps() + + s.k = test_utils.CreateEnvTestCluster("cluster1") + s.k.AddWebhookCallback(schema.GroupVersionResource{ + Version: "v1", Resource: "configmaps", + }, true, s.handleConfigmap) + err := s.k.Start() + if err != nil { + s.T().Fatal(err) + } +} + +func (s *HookTestSuite) TearDownSuite() { + s.k.Stop() +} + +func (s *HookTestSuite) SetupTest() { + s.clearSeenConfigmaps() +} + +func (s *HookTestSuite) handleConfigmap(o *uo.UnstructuredObject) { + s.seenConfigMaps = append(s.seenConfigMaps, o.GetK8sName()) +} + +func (s *HookTestSuite) clearSeenConfigmaps() { + s.seenConfigMaps = nil +} + +func (s *HookTestSuite) addHookConfigMap(p *testProject, dir string, opts resourceOpts, isHelm bool, hook string, hookDeletionPolicy string) { annotations := make(map[string]string) if isHelm { annotations["helm.sh/hook"] = hook @@ -35,17 +66,12 @@ func addHookDeployment(p *testProject, dir string, opts resourceOpts, isHelm boo annotations["kluctl.io/hook-deletion-policy"] = hookDeletionPolicy } - script := renderTemplateHelper(hookScript, map[string]interface{}{ - "name": opts.name, - "namespace": opts.namespace, - }) - opts.annotations = uo.CopyMergeStrMap(opts.annotations, annotations) - addJobDeployment(p, dir, opts, "bitnami/kubectl:1.21", []string{"sh"}, []string{"-c", script}) + s.addConfigMap(p, dir, opts) } -func addConfigMap(p *testProject, dir string, opts resourceOpts) { +func (s *HookTestSuite) addConfigMap(p *testProject, dir string, opts resourceOpts) { o := uo.New() o.SetK8sGVKs("", "v1", "ConfigMap") mergeMetadata(o, opts) @@ -55,203 +81,115 @@ func addConfigMap(p *testProject, dir string, opts resourceOpts) { }) } -func getHookResult(t *testing.T, p *testProject, k *test_utils.EnvTestCluster, secretName string) *uo.UnstructuredObject { - o := k.KubectlYamlMust(t, "-n", p.projectName, "get", "secret", secretName) - s, ok, err := o.GetNestedString("data", "result") - if err != nil { - t.Fatal(err) - } - if !ok { - t.Fatalf("result not found") - } - b, err := base64.StdEncoding.DecodeString(s) - if err != nil { - t.Fatal(err) - } - r, err := uo.FromString(string(b)) - if err != nil { - t.Fatal(err) - } - return r -} - -func getHookResultCMNames(t *testing.T, p *testProject, k *test_utils.EnvTestCluster, second bool) []string { - secretName := "hook-result" - if second { - secretName = "hook-result2" - } - o := getHookResult(t, p, k, secretName) - items, _, _ := o.GetNestedObjectList("items") - var names []string - for _, x := range items { - names = append(names, x.GetK8sName()) - } - return names -} - -func assertHookResultCMName(t *testing.T, p *testProject, k *test_utils.EnvTestCluster, second bool, cmName string) { - names := getHookResultCMNames(t, p, k, second) - for _, x := range names { - if x == cmName { - return - } - } - t.Fatalf("%s not found in hook result", cmName) -} - -func assertHookResultNotCMName(t *testing.T, p *testProject, k *test_utils.EnvTestCluster, second bool, cmName string) { - names := getHookResultCMNames(t, p, k, second) - for _, x := range names { - if x == cmName { - t.Fatalf("%s found in hook result", cmName) - } - } -} - -func prepareHookTestProject(t *testing.T, name string, hook string, hookDeletionPolicy string) (*testProject, *test_utils.EnvTestCluster) { +func (s *HookTestSuite) prepareHookTestProject(name string, hook string, hookDeletionPolicy string) *testProject { isDone := false namespace := fmt.Sprintf("hook-%s", name) - k := defaultCluster1 p := &testProject{} - p.init(t, k, namespace) + p.init(s.T(), s.k, namespace) defer func() { if !isDone { p.cleanup() } }() - createNamespace(t, k, namespace) + createNamespace(s.T(), s.k, namespace) p.updateTarget("test", nil) - addHookDeployment(p, "hook", resourceOpts{name: "hook", namespace: namespace}, false, hook, hookDeletionPolicy) - addConfigMap(p, "hook", resourceOpts{name: "cm1", namespace: namespace}) + p.addKustomizeDeployment("hook", nil, nil) + + s.addConfigMap(p, "hook", resourceOpts{name: "cm1", namespace: namespace}) + s.addHookConfigMap(p, "hook", resourceOpts{name: "hook1", namespace: namespace}, false, hook, hookDeletionPolicy) isDone = true - return p, k + return p } -func ensureHookExecuted(t *testing.T, p *testProject, k *test_utils.EnvTestCluster) { - _, _, _ = k.Kubectl("delete", "-n", p.projectName, "secret", "hook-result", "hook-result2") +func (s *HookTestSuite) ensureHookExecuted(p *testProject, expectedCms ...string) { + s.clearSeenConfigmaps() p.KluctlMust("deploy", "--yes", "-t", "test") - assertResourceExists(t, k, p.projectName, "ConfigMap/cm1") + assert.Equal(s.T(), expectedCms, s.seenConfigMaps) } -func ensureHookNotExecuted(t *testing.T, p *testProject, k *test_utils.EnvTestCluster) { - _, _, _ = k.Kubectl("delete", "-n", p.projectName, "secret", "hook-result", "hook-result2") +func (s *HookTestSuite) ensureHookNotExecuted(p *testProject) { + _, _, _ = s.k.Kubectl("delete", "-n", p.projectName, "configmaps", "cm1") p.KluctlMust("deploy", "--yes", "-t", "test") - assertResourceNotExists(t, k, p.projectName, "Secret/hook-result") + assertResourceNotExists(s.T(), s.k, p.projectName, "ConfigMap/cm1") } -func TestHooksPreDeployInitial(t *testing.T) { - t.Parallel() - p, k := prepareHookTestProject(t, "pre-deploy-initial", "pre-deploy-initial", "") +func (s *HookTestSuite) TestHooksPreDeployInitial() { + p := s.prepareHookTestProject("pre-deploy-initial", "pre-deploy-initial", "") defer p.cleanup() - ensureHookExecuted(t, p, k) - assertHookResultNotCMName(t, p, k, false, "cm1") - ensureHookNotExecuted(t, p, k) + s.ensureHookExecuted(p, "hook1", "cm1") + s.ensureHookExecuted(p, "cm1") } -func TestHooksPostDeployInitial(t *testing.T) { - t.Parallel() - p, k := prepareHookTestProject(t, "post-deploy-initial", "post-deploy-initial", "") +func (s *HookTestSuite) TestHooksPostDeployInitial() { + p := s.prepareHookTestProject("post-deploy-initial", "post-deploy-initial", "") defer p.cleanup() - ensureHookExecuted(t, p, k) - assertHookResultCMName(t, p, k, false, "cm1") - ensureHookNotExecuted(t, p, k) + s.ensureHookExecuted(p, "cm1", "hook1") + s.ensureHookExecuted(p, "cm1") } -func TestHooksPreDeployUpgrade(t *testing.T) { - t.Parallel() - p, k := prepareHookTestProject(t, "pre-deploy-upgrade", "pre-deploy-upgrade", "") +func (s *HookTestSuite) TestHooksPreDeployUpgrade() { + p := s.prepareHookTestProject("pre-deploy-upgrade", "pre-deploy-upgrade", "") defer p.cleanup() - addConfigMap(p, "hook", resourceOpts{name: "cm2", namespace: p.projectName}) - ensureHookNotExecuted(t, p, k) - k.KubectlMust(t, "delete", "-n", p.projectName, "configmap", "cm1") - ensureHookExecuted(t, p, k) - assertHookResultNotCMName(t, p, k, false, "cm1") - ensureHookExecuted(t, p, k) - assertHookResultCMName(t, p, k, false, "cm1") + s.ensureHookExecuted(p, "cm1") + s.ensureHookExecuted(p, "hook1", "cm1") + s.ensureHookExecuted(p, "hook1", "cm1") } -func TestHooksPostDeployUpgrade(t *testing.T) { - t.Parallel() - p, k := prepareHookTestProject(t, "post-deploy-upgrade", "post-deploy-upgrade", "") +func (s *HookTestSuite) TestHooksPostDeployUpgrade() { + p := s.prepareHookTestProject("post-deploy-upgrade", "post-deploy-upgrade", "") defer p.cleanup() - addConfigMap(p, "hook", resourceOpts{name: "cm2", namespace: p.projectName}) - ensureHookNotExecuted(t, p, k) - k.KubectlMust(t, "delete", "-n", p.projectName, "configmap", "cm1") - ensureHookExecuted(t, p, k) - assertHookResultCMName(t, p, k, false, "cm1") + s.ensureHookExecuted(p, "cm1") + s.ensureHookExecuted(p, "cm1", "hook1") + s.ensureHookExecuted(p, "cm1", "hook1") } -func doTestHooksPreDeploy(t *testing.T, name string, hooks string) { - p, k := prepareHookTestProject(t, name, hooks, "") +func (s *HookTestSuite) doTestHooksPreDeploy(name string, hooks string) { + p := s.prepareHookTestProject(name, hooks, "") defer p.cleanup() - addConfigMap(p, "hook", resourceOpts{name: "cm2", namespace: p.projectName}) - ensureHookExecuted(t, p, k) - k.KubectlMust(t, "delete", "-n", p.projectName, "configmap", "cm1") - ensureHookExecuted(t, p, k) - assertHookResultNotCMName(t, p, k, false, "cm1") - ensureHookExecuted(t, p, k) - assertHookResultCMName(t, p, k, false, "cm1") + s.ensureHookExecuted(p, "hook1", "cm1") + s.ensureHookExecuted(p, "hook1", "cm1") } -func doTestHooksPostDeploy(t *testing.T, name string, hooks string) { - p, k := prepareHookTestProject(t, name, hooks, "") +func (s *HookTestSuite) doTestHooksPostDeploy(name string, hooks string) { + p := s.prepareHookTestProject(name, hooks, "") defer p.cleanup() - addConfigMap(p, "hook", resourceOpts{name: "cm2", namespace: p.projectName}) - ensureHookExecuted(t, p, k) - k.KubectlMust(t, "delete", "-n", p.projectName, "configmap", "cm1") - ensureHookExecuted(t, p, k) - assertHookResultCMName(t, p, k, false, "cm1") + s.ensureHookExecuted(p, "cm1", "hook1") + s.ensureHookExecuted(p, "cm1", "hook1") } -func doTestHooksPrePostDeploy(t *testing.T, name string, hooks string) { - p, k := prepareHookTestProject(t, name, hooks, "") +func (s *HookTestSuite) doTestHooksPrePostDeploy(name string, hooks string) { + p := s.prepareHookTestProject(name, hooks, "") defer p.cleanup() - addConfigMap(p, "hook", resourceOpts{name: "cm2", namespace: p.projectName}) - ensureHookExecuted(t, p, k) - assertHookResultNotCMName(t, p, k, false, "cm1") - assertHookResultNotCMName(t, p, k, false, "cm2") - assertHookResultCMName(t, p, k, true, "cm1") - assertHookResultCMName(t, p, k, true, "cm2") - - ensureHookExecuted(t, p, k) - assertHookResultCMName(t, p, k, false, "cm1") - assertHookResultNotCMName(t, p, k, false, "cm2") - assertHookResultCMName(t, p, k, true, "cm1") - assertHookResultCMName(t, p, k, true, "cm2") + s.ensureHookExecuted(p, "hook1", "cm1", "hook1") + s.ensureHookExecuted(p, "hook1", "cm1", "hook1") } -func TestHooksPreDeploy(t *testing.T) { - t.Parallel() - doTestHooksPreDeploy(t, "pre-deploy", "pre-deploy") +func (s *HookTestSuite) TestHooksPreDeploy() { + s.doTestHooksPreDeploy("pre-deploy", "pre-deploy") } -func TestHooksPreDeploy2(t *testing.T) { - t.Parallel() +func (s *HookTestSuite) TestHooksPreDeploy2() { // same as pre-deploy - doTestHooksPreDeploy(t, "pre-deploy2", "pre-deploy-initial,pre-deploy-upgrade") + s.doTestHooksPreDeploy("pre-deploy2", "pre-deploy-initial,pre-deploy-upgrade") } -func TestHooksPostDeploy(t *testing.T) { - t.Parallel() - doTestHooksPostDeploy(t, "post-deploy", "post-deploy") +func (s *HookTestSuite) TestHooksPostDeploy() { + s.doTestHooksPostDeploy("post-deploy", "post-deploy") } -func TestHooksPostDeploy2(t *testing.T) { - t.Parallel() +func (s *HookTestSuite) TestHooksPostDeploy2() { // same as post-deploy - doTestHooksPostDeploy(t, "post-deploy2", "post-deploy-initial,post-deploy-upgrade") + s.doTestHooksPostDeploy("post-deploy2", "post-deploy-initial,post-deploy-upgrade") } -func TestHooksPrePostDeploy(t *testing.T) { - t.Parallel() - doTestHooksPrePostDeploy(t, "pre-post-deploy", "pre-deploy,post-deploy") +func (s *HookTestSuite) TestHooksPrePostDeploy() { + s.doTestHooksPrePostDeploy("pre-post-deploy", "pre-deploy,post-deploy") } -func TestHooksPrePostDeploy2(t *testing.T) { - t.Parallel() - doTestHooksPrePostDeploy(t, "pre-post-deploy2", "pre-deploy-initial,pre-deploy-upgrade,post-deploy-initial,post-deploy-upgrade") +func (s *HookTestSuite) TestHooksPrePostDeploy2() { + s.doTestHooksPrePostDeploy("pre-post-deploy2", "pre-deploy-initial,pre-deploy-upgrade,post-deploy-initial,post-deploy-upgrade") } From 3cab5788e1ce77caa7c5523166ba617700169628 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 29 Sep 2022 23:36:48 +0200 Subject: [PATCH 1041/2916] fix: Use background instead of forground deletion --- pkg/k8s/k8s_cluster.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index f8e8a07a7..1656e080d 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -236,7 +236,7 @@ type DeleteOptions struct { func (k *K8sCluster) DeleteSingleObject(ref k8s.ObjectRef, options DeleteOptions) ([]ApiWarning, error) { dryRun := k.DryRun || options.ForceDryRun - pp := v1.DeletePropagationForeground + pp := v1.DeletePropagationBackground o := v1.DeleteOptions{ PropagationPolicy: &pp, } From c320b15d4314514af78da85ff1da81ce3579d40e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 30 Sep 2022 15:13:48 +0200 Subject: [PATCH 1042/2916] tests: Simplify sealing tests and remove the need to install a real sealed-secrets controller --- e2e/default_clusters.go | 1 - e2e/project.go | 13 +- e2e/seal_test.go | 377 ++++++++++++++----------- e2e/test_resources/README.md | 6 - e2e/test_resources/vault-values.yaml | 10 - e2e/test_resources/vault.yaml | 235 --------------- e2e/utils_resources.go | 95 ------- go.mod | 7 +- go.sum | 2 + internal/test-utils/envtest_cluster.go | 8 +- pkg/k8s/k8s_cluster.go | 42 ++- pkg/seal/fetch_cert.go | 11 + pkg/seal/sealer.go | 10 +- 13 files changed, 294 insertions(+), 523 deletions(-) delete mode 100644 e2e/test_resources/vault-values.yaml delete mode 100644 e2e/test_resources/vault.yaml diff --git a/e2e/default_clusters.go b/e2e/default_clusters.go index b65f26b6d..3d0f10fd8 100644 --- a/e2e/default_clusters.go +++ b/e2e/default_clusters.go @@ -7,7 +7,6 @@ import ( var defaultCluster1 = test_utils.CreateEnvTestCluster("cluster1") var defaultCluster2 = test_utils.CreateEnvTestCluster("cluster2") -var defaultKindCluster1VaultPort, defaultKindCluster2VaultPort int func init() { var wg sync.WaitGroup diff --git a/e2e/project.go b/e2e/project.go index 39996fa5b..e106c795a 100644 --- a/e2e/project.go +++ b/e2e/project.go @@ -2,13 +2,6 @@ package e2e import ( "fmt" - "os" - "os/exec" - "path/filepath" - "reflect" - "strings" - "testing" - "github.com/go-git/go-git/v5" "github.com/imdario/mergo" test_utils "github.com/kluctl/kluctl/v2/internal/test-utils" @@ -16,6 +9,12 @@ import ( "github.com/kluctl/kluctl/v2/pkg/yaml" "k8s.io/client-go/tools/clientcmd" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" + "os" + "os/exec" + "path/filepath" + "reflect" + "strings" + "testing" ) type testProject struct { diff --git a/e2e/seal_test.go b/e2e/seal_test.go index f74a7544a..531fef2c1 100644 --- a/e2e/seal_test.go +++ b/e2e/seal_test.go @@ -1,82 +1,154 @@ package e2e import ( - "encoding/base64" + "crypto/x509" + "encoding/pem" "fmt" + "github.com/bitnami-labs/sealed-secrets/pkg/crypto" "github.com/kluctl/kluctl/v2/e2e/test_resources" "github.com/kluctl/kluctl/v2/internal/test-utils" + "github.com/kluctl/kluctl/v2/pkg/seal" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/yaml" "github.com/stretchr/testify/assert" - "io/ioutil" + "github.com/stretchr/testify/suite" + certUtil "k8s.io/client-go/util/cert" + "k8s.io/client-go/util/keyutil" + "net" "net/http" - "net/url" + "net/http/httptest" "path/filepath" - "strings" - "sync" "testing" "time" ) -func installSealedSecretsOperator(k *test_utils.EnvTestCluster) { - test_resources.ApplyYaml("sealed-secrets.yaml", k) +type SealedSecretsTestSuite struct { + suite.Suite + + k *test_utils.EnvTestCluster + k2 *test_utils.EnvTestCluster + + certServer1 *certServer + certServer2 *certServer } -func waitForSealedSecretsOperator(t *testing.T, k *test_utils.EnvTestCluster) { - waitForReadiness(t, k, "kube-system", "deployment/sealed-secrets-controller", 5*time.Minute) +type certServer struct { + server http.Server + url string + certHash string } -func deleteSealedSecretsOperator(k *test_utils.EnvTestCluster) { - test_resources.DeleteYaml("sealed-secrets.yaml", k) - _, _, _ = k.Kubectl("-n", "kube-system", "delete", "secret", "-l", "sealedsecrets.bitnami.com/sealed-secrets-key", "--wait") - _, _, _ = k.Kubectl("-n", "kube-system", "delete", "configmap", "sealed-secrets-key-kluctl-bootstrap", "--wait") +func TestSealedSecrets(t *testing.T) { + suite.Run(t, &SealedSecretsTestSuite{}) } -func installVault(k *test_utils.EnvTestCluster) { - _, _, _ = k.Kubectl("create", "ns", "vault") - test_resources.ApplyYaml("vault.yaml", k) +func (s *SealedSecretsTestSuite) SetupSuite() { + s.k = defaultCluster1 + s.k2 = defaultCluster2 + + test_resources.ApplyYaml("sealed-secrets.yaml", s.k) + test_resources.ApplyYaml("sealed-secrets.yaml", s.k2) + + var err error + s.certServer1, err = s.startCertServer() + if err != nil { + s.T().Fatal(err) + } + s.certServer2, err = s.startCertServer() + if err != nil { + s.T().Fatal(err) + } } -func waitForVault(t *testing.T, k *test_utils.EnvTestCluster) { - waitForReadiness(t, k, "vault", "statefulset/vault", 5*time.Minute) +func (s *SealedSecretsTestSuite) TearDownSuite() { + s.k = nil + s.k2 = nil + + if s.certServer1 != nil { + s.certServer1.server.Close() + } + if s.certServer2 != nil { + s.certServer2.server.Close() + } + s.certServer1 = nil + s.certServer2 = nil } -func init() { - var wg sync.WaitGroup - wg.Add(2) - go func() { - defer wg.Done() - installSealedSecretsOperator(defaultCluster1) - installVault(defaultCluster1) - }() +func (s *SealedSecretsTestSuite) startCertServer() (*certServer, error) { + key, cert, err := crypto.GeneratePrivateKeyAndCert(2048, 10*365*24*time.Hour, "tests.kluctl.io") + if err != nil { + return nil, err + } + + certbytes := []byte{} + certbytes = append(certbytes, pem.EncodeToMemory(&pem.Block{Type: certUtil.CertificateBlockType, Bytes: cert.Raw})...) + keybytes := pem.EncodeToMemory(&pem.Block{Type: keyutil.RSAPrivateKeyBlockType, Bytes: x509.MarshalPKCS1PrivateKey(key)}) + _ = keybytes + l, err := net.Listen("tcp", "") + if err != nil { + return nil, err + } + + mux := http.NewServeMux() + mux.Handle("/v1/cert.pem", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/x-pem-file") + _, _ = w.Write(certbytes) + })) + + certUrl := fmt.Sprintf("http://localhost:%d", l.Addr().(*net.TCPAddr).Port) + + var cs certServer + cs.url = certUrl + cs.server.Handler = mux + cs.certHash, err = seal.HashPublicKey(cert) + if err != nil { + return nil, err + } + go func() { - defer wg.Done() - installSealedSecretsOperator(defaultCluster2) + _ = cs.server.Serve(l) }() - wg.Wait() + + return &cs, nil +} + +func (s *SealedSecretsTestSuite) addProxyVars(p *testProject) { + f := func(idx int, k *test_utils.EnvTestCluster, cs *certServer) { + p.extraEnv = append(p.extraEnv, fmt.Sprintf("KLUCTL_K8S_SERVICE_PROXY_%d_API_HOST=%s", idx, k.RESTConfig().Host)) + p.extraEnv = append(p.extraEnv, fmt.Sprintf("KLUCTL_K8S_SERVICE_PROXY_%d_SERVICE_NAMESPACE=%s", idx, "kube-system")) + p.extraEnv = append(p.extraEnv, fmt.Sprintf("KLUCTL_K8S_SERVICE_PROXY_%d_SERVICE_NAME=%s", idx, "sealed-secrets-controller")) + p.extraEnv = append(p.extraEnv, fmt.Sprintf("KLUCTL_K8S_SERVICE_PROXY_%d_SERVICE_PORT=%s", idx, "http")) + p.extraEnv = append(p.extraEnv, fmt.Sprintf("KLUCTL_K8S_SERVICE_PROXY_%d_LOCAL_URL=%s", idx, cs.url)) + } + f(0, s.k, s.certServer1) + f(1, s.k2, s.certServer2) } -func prepareSealTest(t *testing.T, k *test_utils.EnvTestCluster, namespace string, secrets map[string]string, varsSources []*uo.UnstructuredObject) *testProject { +func (s *SealedSecretsTestSuite) prepareSealTest(k *test_utils.EnvTestCluster, namespace string, secrets map[string]string, varsSources []*uo.UnstructuredObject, proxy bool) *testProject { p := &testProject{} - p.init(t, k, fmt.Sprintf("seal-%s", namespace)) + p.init(s.T(), k, fmt.Sprintf("seal-%s", namespace)) + if proxy { + s.addProxyVars(p) + } - createNamespace(t, k, namespace) + createNamespace(s.T(), k, namespace) - addSecretsSet(p, "test", varsSources) - addSecretsSetToTarget(p, "test-target", "test") + s.addSecretsSet(p, "test", varsSources) + s.addSecretsSetToTarget(p, "test-target", "test") addSecretDeployment(p, "secret-deployment", secrets, true, resourceOpts{name: "secret", namespace: namespace}) return p } -func addSecretsSet(p *testProject, name string, varsSources []*uo.UnstructuredObject) { +func (s *SealedSecretsTestSuite) addSecretsSet(p *testProject, name string, varsSources []*uo.UnstructuredObject) { p.updateSecretSet(name, func(secretSet *uo.UnstructuredObject) { _ = secretSet.SetNestedField(varsSources, "vars") }) } -func addSecretsSetToTarget(p *testProject, targetName string, secretSetName string) { +func (s *SealedSecretsTestSuite) addSecretsSetToTarget(p *testProject, targetName string, secretSetName string) { p.updateTarget(targetName, func(target *uo.UnstructuredObject) { l, _, _ := target.GetNestedList("sealingConfig", "secretSets") l = append(l, secretSetName) @@ -84,23 +156,37 @@ func addSecretsSetToTarget(p *testProject, targetName string, secretSetName stri }) } -func assertDecryptedSecrets(t *testing.T, k *test_utils.EnvTestCluster, namespace string, secretName string, expectedSecrets map[string]string) { - s := k.KubectlYamlMust(t, "-n", namespace, "get", "secret", secretName) +func (s *SealedSecretsTestSuite) assertSealedSecret(k *test_utils.EnvTestCluster, namespace string, secretName string, expectedCertHash string, expectedSecrets map[string]string) { + y := k.KubectlYamlMust(s.T(), "-n", namespace, "get", "sealedsecret", secretName) + + h1 := y.GetK8sAnnotation("kluctl.io/sealedsecret-cert-hash") + if h1 == nil { + s.T().Fatal("kluctl.io/sealedsecret-cert-hash annotation not found") + } + s.Assertions.Equal(expectedCertHash, *h1) + + hashesStr := y.GetK8sAnnotation("kluctl.io/sealedsecret-hashes") + if hashesStr == nil { + s.T().Fatal("kluctl.io/sealedsecret-hashes annotation not found") + } + hashes, err := uo.FromString(*hashesStr) + if err != nil { + s.T().Fatal(err) + } - for key, value := range expectedSecrets { - x, _, _ := s.GetNestedString("data", key) - decoded, _ := base64.StdEncoding.DecodeString(x) - assert.Equal(t, value, string(decoded)) + expectedHashes := map[string]any{} + for k, v := range expectedSecrets { + expectedHashes[k] = seal.HashSecret(k, []byte(v), secretName, namespace, "strict") } + + s.Assertions.Equal(expectedHashes, hashes.Object) } -func TestSeal_WithOperator(t *testing.T) { - k := defaultCluster1 +func (s *SealedSecretsTestSuite) TestSeal_WithOperator() { + k := s.k namespace := "seal-with-operator" - waitForSealedSecretsOperator(t, k) - - p := prepareSealTest(t, k, namespace, + p := s.prepareSealTest(k, namespace, map[string]string{ "s1": "{{ secrets.s1 }}", "s2": "{{ secrets.s2 }}", @@ -112,34 +198,29 @@ func TestSeal_WithOperator(t *testing.T) { "s2": "v2", }, }), - }, - ) + }, true) defer p.cleanup() p.KluctlMust("seal", "-t", "test-target") sealedSecretsDir := p.gitServer.LocalRepoDir(p.getSealedSecretsRepo()) - assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) + assert.FileExists(s.T(), filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) p.KluctlMust("deploy", "--yes", "-t", "test-target") - - waitForReadiness(t, k, namespace, "secret/secret", 1*time.Minute) - assertDecryptedSecrets(t, k, namespace, "secret", map[string]string{ + s.assertSealedSecret(k, namespace, "secret", s.certServer1.certHash, map[string]string{ "s1": "v1", "s2": "v2", }) } -func TestSeal_WithBootstrap(t *testing.T) { - k := defaultCluster2 +func (s *SealedSecretsTestSuite) TestSeal_WithBootstrap() { + k := s.k namespace := "seal-with-bootstrap" - // we still wait for it to be ready before we then delete it - // this way it's pre-pulled and pre-warmed when we later start it - waitForSealedSecretsOperator(t, k) - deleteSealedSecretsOperator(k) + // deleting the crd causes kluctl to not recognize the operator, so it will do a bootstrap + k.KubectlMust(s.T(), "delete", "crd", "sealedsecrets.bitnami.com") - p := prepareSealTest(t, k, namespace, + p := s.prepareSealTest(k, namespace, map[string]string{ "s1": "{{ secrets.s1 }}", "s2": "{{ secrets.s2 }}", @@ -151,36 +232,40 @@ func TestSeal_WithBootstrap(t *testing.T) { "s2": "v2", }, }), - }, - ) + }, false) + defer p.cleanup() p.KluctlMust("seal", "-t", "test-target") sealedSecretsDir := p.gitServer.LocalRepoDir(p.getSealedSecretsRepo()) - assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) + assert.FileExists(s.T(), filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) - installSealedSecretsOperator(k) - waitForSealedSecretsOperator(t, k) + test_resources.ApplyYaml("sealed-secrets.yaml", k) p.KluctlMust("deploy", "--yes", "-t", "test-target") - waitForReadiness(t, k, namespace, "secret/secret", 1*time.Minute) - assertDecryptedSecrets(t, k, namespace, "secret", map[string]string{ + pkCm := k.KubectlYamlMust(s.T(), "-n", "kube-system", "get", "cm", "sealed-secrets-key-kluctl-bootstrap") + certBytes, ok, _ := pkCm.GetNestedString("data", "tls.crt") + s.Assertions.True(ok) + + cert, err := seal.ParseCert([]byte(certBytes)) + s.Assertions.NoError(err) + + certHash, err := seal.HashPublicKey(cert) + s.Assertions.NoError(err) + + s.assertSealedSecret(k, namespace, "secret", certHash, map[string]string{ "s1": "v1", "s2": "v2", }) } -func TestSeal_MultipleVarSources(t *testing.T) { - t.Parallel() - - k := defaultCluster1 +func (s *SealedSecretsTestSuite) TestSeal_MultipleVarSources() { + k := s.k namespace := "seal-multiple-vs" - waitForSealedSecretsOperator(t, k) - - p := prepareSealTest(t, k, namespace, + p := s.prepareSealTest(k, namespace, map[string]string{ "s1": "{{ secrets.s1 }}", "s2": "{{ secrets.s2 }}", @@ -196,33 +281,27 @@ func TestSeal_MultipleVarSources(t *testing.T) { "s2": "v2", }, }), - }, - ) + }, true) defer p.cleanup() p.KluctlMust("seal", "-t", "test-target") sealedSecretsDir := p.gitServer.LocalRepoDir(p.getSealedSecretsRepo()) - assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) + assert.FileExists(s.T(), filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) p.KluctlMust("deploy", "--yes", "-t", "test-target") - waitForReadiness(t, k, namespace, "secret/secret", 1*time.Minute) - assertDecryptedSecrets(t, k, namespace, "secret", map[string]string{ + s.assertSealedSecret(k, namespace, "secret", s.certServer1.certHash, map[string]string{ "s1": "v1", "s2": "v2", }) } -func TestSeal_MultipleSecretSets(t *testing.T) { - t.Parallel() - - k := defaultCluster1 +func (s *SealedSecretsTestSuite) TestSeal_MultipleSecretSets() { + k := s.k namespace := "seal-multiple-ss" - waitForSealedSecretsOperator(t, k) - - p := prepareSealTest(t, k, namespace, + p := s.prepareSealTest(k, namespace, map[string]string{ "s1": "{{ secrets.s1 }}", "s2": "{{ secrets.s2 }}", @@ -233,41 +312,37 @@ func TestSeal_MultipleSecretSets(t *testing.T) { "s1": "v1", }, }), - }, - ) + }, true) + defer p.cleanup() - addSecretsSet(p, "test2", []*uo.UnstructuredObject{ + s.addSecretsSet(p, "test2", []*uo.UnstructuredObject{ uo.FromMap(map[string]interface{}{ "values": map[string]interface{}{ "s2": "v2", }, }), }) - addSecretsSetToTarget(p, "test-target", "test2") + s.addSecretsSetToTarget(p, "test-target", "test2") p.KluctlMust("seal", "-t", "test-target") sealedSecretsDir := p.gitServer.LocalRepoDir(p.getSealedSecretsRepo()) - assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) + assert.FileExists(s.T(), filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) p.KluctlMust("deploy", "--yes", "-t", "test-target") - waitForReadiness(t, k, namespace, "secret/secret", 1*time.Minute) - assertDecryptedSecrets(t, k, namespace, "secret", map[string]string{ + s.assertSealedSecret(k, namespace, "secret", s.certServer1.certHash, map[string]string{ "s1": "v1", "s2": "v2", }) } -func TestSeal_MultipleTargets(t *testing.T) { - k := defaultCluster1 +func (s *SealedSecretsTestSuite) TestSeal_MultipleTargets() { + k := s.k namespace := "seal-multiple-targets" - waitForSealedSecretsOperator(t, k) - waitForSealedSecretsOperator(t, defaultCluster2) - - p := prepareSealTest(t, k, namespace, + p := s.prepareSealTest(k, namespace, map[string]string{ "s1": "{{ secrets.s1 }}", "s2": "{{ secrets.s2 }}", @@ -279,11 +354,10 @@ func TestSeal_MultipleTargets(t *testing.T) { "s2": "v2", }, }), - }, - ) + }, true) defer p.cleanup() - addSecretsSet(p, "test2", []*uo.UnstructuredObject{ + s.addSecretsSet(p, "test2", []*uo.UnstructuredObject{ uo.FromMap(map[string]interface{}{ "values": map[string]interface{}{ "s1": "v3", @@ -291,48 +365,42 @@ func TestSeal_MultipleTargets(t *testing.T) { }, }), }) - addSecretsSetToTarget(p, "test-target2", "test2") + s.addSecretsSetToTarget(p, "test-target2", "test2") - p.mergeKubeconfig(defaultCluster2) - createNamespace(t, defaultCluster2, namespace) + p.mergeKubeconfig(s.k2) + createNamespace(s.T(), s.k2, namespace) p.updateTarget("test-target", func(target *uo.UnstructuredObject) { - _ = target.SetNestedField(defaultCluster1.Context, "context") + _ = target.SetNestedField(s.k.Context, "context") }) p.updateTarget("test-target2", func(target *uo.UnstructuredObject) { - _ = target.SetNestedField(defaultCluster2.Context, "context") + _ = target.SetNestedField(s.k2.Context, "context") }) p.KluctlMust("seal", "-t", "test-target") p.KluctlMust("seal", "-t", "test-target2") sealedSecretsDir := p.gitServer.LocalRepoDir(p.getSealedSecretsRepo()) - assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) - assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target2/secret-secret.yml")) + assert.FileExists(s.T(), filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) + assert.FileExists(s.T(), filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target2/secret-secret.yml")) p.KluctlMust("deploy", "--yes", "-t", "test-target") p.KluctlMust("deploy", "--yes", "-t", "test-target2") - waitForReadiness(t, k, namespace, "secret/secret", 1*time.Minute) - assertDecryptedSecrets(t, k, namespace, "secret", map[string]string{ + s.assertSealedSecret(k, namespace, "secret", s.certServer1.certHash, map[string]string{ "s1": "v1", "s2": "v2", }) - waitForReadiness(t, defaultCluster2, namespace, "secret/secret", 1*time.Minute) - assertDecryptedSecrets(t, defaultCluster2, namespace, "secret", map[string]string{ + s.assertSealedSecret(s.k2, namespace, "secret", s.certServer2.certHash, map[string]string{ "s1": "v3", "s2": "v4", }) } -func TestSeal_File(t *testing.T) { - t.Parallel() - - k := defaultCluster1 +func (s *SealedSecretsTestSuite) TestSeal_File() { + k := s.k namespace := "seal-file" - waitForSealedSecretsOperator(t, k) - - p := prepareSealTest(t, k, namespace, + p := s.prepareSealTest(k, namespace, map[string]string{ "s1": "{{ secrets.s1 }}", "s2": "{{ secrets.s2 }}", @@ -341,8 +409,7 @@ func TestSeal_File(t *testing.T) { uo.FromMap(map[string]interface{}{ "file": utils.StrPtr("secret-values.yaml"), }), - }, - ) + }, true) defer p.cleanup() p.gitServer.UpdateYaml(p.getKluctlProjectRepo(), "secret-values.yaml", func(o *uo.UnstructuredObject) error { @@ -358,50 +425,44 @@ func TestSeal_File(t *testing.T) { p.KluctlMust("seal", "-t", "test-target") sealedSecretsDir := p.gitServer.LocalRepoDir(p.getSealedSecretsRepo()) - assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) + assert.FileExists(s.T(), filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) p.KluctlMust("deploy", "--yes", "-t", "test-target") - waitForReadiness(t, k, namespace, "secret/secret", 1*time.Minute) - assertDecryptedSecrets(t, k, namespace, "secret", map[string]string{ + s.assertSealedSecret(k, namespace, "secret", s.certServer1.certHash, map[string]string{ "s1": "v1", "s2": "v2", }) } -func TestSeal_Vault(t *testing.T) { - t.Parallel() - - k := defaultCluster1 +func (s *SealedSecretsTestSuite) TestSeal_Vault() { + k := s.k namespace := "seal-vault" - waitForSealedSecretsOperator(t, k) - waitForVault(t, k) - - u, err := url.Parse(defaultCluster1.RESTConfig().Host) - if err != nil { - t.Fatal(err) - } - - vaultUrl := fmt.Sprintf("http://%s:%d", u.Hostname(), defaultKindCluster1VaultPort) + server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + if request.URL.Path != "/v1/secret/data/secret" { + http.NotFound(writer, request) + return + } + o := uo.FromMap(map[string]interface{}{ + "data": map[string]interface{}{ + "data": map[string]interface{}{ + "secrets": map[string]interface{}{ + "s1": "v1", + "s2": "v2", + }, + }, + }, + }) + s, _ := yaml.WriteJsonString(o) + writer.Header().Set("Content-Type", "application/json") + _, _ = writer.Write([]byte(s)) + })) + defer server.Close() - req, err := http.NewRequest("POST", fmt.Sprintf("%s/v1/secret/data/secret", vaultUrl), strings.NewReader(`{"data": {"secrets":{"s1":"v1","s2":"v2"}}}`)) - if err != nil { - t.Fatal(err) - } - req.Header.Set("Content-Type", "application/json") - req.Header.Set("X-Vault-Token", "root") - resp, err := http.DefaultClient.Do(req) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - body, _ := ioutil.ReadAll(resp.Body) - t.Fatalf("vault response status %d, body=%s", resp.StatusCode, string(body)) - } + vaultUrl := server.URL - p := prepareSealTest(t, k, namespace, + p := s.prepareSealTest(k, namespace, map[string]string{ "s1": "{{ secrets.s1 }}", "s2": "{{ secrets.s2 }}", @@ -413,20 +474,18 @@ func TestSeal_Vault(t *testing.T) { "path": "secret/data/secret", }, }), - }, - ) + }, true) defer p.cleanup() p.extraEnv = append(p.extraEnv, "VAULT_TOKEN=root") p.KluctlMust("seal", "-t", "test-target") sealedSecretsDir := p.gitServer.LocalRepoDir(p.getSealedSecretsRepo()) - assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) + assert.FileExists(s.T(), filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) p.KluctlMust("deploy", "--yes", "-t", "test-target") - waitForReadiness(t, k, namespace, "secret/secret", 1*time.Minute) - assertDecryptedSecrets(t, k, namespace, "secret", map[string]string{ + s.assertSealedSecret(k, namespace, "secret", s.certServer1.certHash, map[string]string{ "s1": "v1", "s2": "v2", }) diff --git a/e2e/test_resources/README.md b/e2e/test_resources/README.md index a95af0258..742fd70c2 100644 --- a/e2e/test_resources/README.md +++ b/e2e/test_resources/README.md @@ -4,12 +4,6 @@ helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets helm template sealed-secrets-controller sealed-secrets/sealed-secrets -n kube-system --include-crds --skip-tests > sealed-secrets.yaml ``` -# vault.yaml -```bash -helm repo add hashicorp https://helm.releases.hashicorp.com -helm template vault hashicorp/vault -n vault -f vault-values.yaml --include-crds --skip-tests > vault.yaml -``` - # kluctl-crds.yaml Fetches flux-kluctl-controller crd definitions ```bash diff --git a/e2e/test_resources/vault-values.yaml b/e2e/test_resources/vault-values.yaml deleted file mode 100644 index 1934c072a..000000000 --- a/e2e/test_resources/vault-values.yaml +++ /dev/null @@ -1,10 +0,0 @@ -server: - # Must be RollingUpdate as otherwise it's reported as ready much too early - updateStrategyType: RollingUpdate - dev: - enabled: true - service: - type: NodePort - nodePort: 30000 -injector: - enabled: false diff --git a/e2e/test_resources/vault.yaml b/e2e/test_resources/vault.yaml deleted file mode 100644 index 13e65b084..000000000 --- a/e2e/test_resources/vault.yaml +++ /dev/null @@ -1,235 +0,0 @@ ---- -# Source: vault/templates/server-serviceaccount.yaml -apiVersion: v1 -kind: ServiceAccount -metadata: - name: vault - namespace: vault - labels: - helm.sh/chart: vault-0.20.1 - app.kubernetes.io/name: vault - app.kubernetes.io/instance: vault - app.kubernetes.io/managed-by: Helm ---- -# Source: vault/templates/server-clusterrolebinding.yaml -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: vault-server-binding - labels: - helm.sh/chart: vault-0.20.1 - app.kubernetes.io/name: vault - app.kubernetes.io/instance: vault - app.kubernetes.io/managed-by: Helm -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: system:auth-delegator -subjects: -- kind: ServiceAccount - name: vault - namespace: vault ---- -# Source: vault/templates/server-headless-service.yaml -# Service for Vault cluster -apiVersion: v1 -kind: Service -metadata: - name: vault-internal - namespace: vault - labels: - helm.sh/chart: vault-0.20.1 - app.kubernetes.io/name: vault - app.kubernetes.io/instance: vault - app.kubernetes.io/managed-by: Helm - annotations: - -spec: - clusterIP: None - publishNotReadyAddresses: true - ports: - - name: "http" - port: 8200 - targetPort: 8200 - - name: https-internal - port: 8201 - targetPort: 8201 - selector: - app.kubernetes.io/name: vault - app.kubernetes.io/instance: vault - component: server ---- -# Source: vault/templates/server-service.yaml -# Service for Vault cluster -apiVersion: v1 -kind: Service -metadata: - name: vault - namespace: vault - labels: - helm.sh/chart: vault-0.20.1 - app.kubernetes.io/name: vault - app.kubernetes.io/instance: vault - app.kubernetes.io/managed-by: Helm - annotations: - -spec: - type: NodePort - externalTrafficPolicy: Cluster - # We want the servers to become available even if they're not ready - # since this DNS is also used for join operations. - publishNotReadyAddresses: true - ports: - - name: http - port: 8200 - targetPort: 8200 - nodePort: 30000 - - name: https-internal - port: 8201 - targetPort: 8201 - selector: - app.kubernetes.io/name: vault - app.kubernetes.io/instance: vault - component: server ---- -# Source: vault/templates/server-statefulset.yaml -# StatefulSet to run the actual vault server cluster. -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: vault - namespace: vault - labels: - app.kubernetes.io/name: vault - app.kubernetes.io/instance: vault - app.kubernetes.io/managed-by: Helm -spec: - serviceName: vault-internal - podManagementPolicy: Parallel - replicas: 1 - updateStrategy: - type: RollingUpdate - selector: - matchLabels: - app.kubernetes.io/name: vault - app.kubernetes.io/instance: vault - component: server - template: - metadata: - labels: - helm.sh/chart: vault-0.20.1 - app.kubernetes.io/name: vault - app.kubernetes.io/instance: vault - component: server - spec: - - - - - terminationGracePeriodSeconds: 10 - serviceAccountName: vault - - securityContext: - runAsNonRoot: true - runAsGroup: 1000 - runAsUser: 100 - fsGroup: 1000 - volumes: - - - name: home - emptyDir: {} - containers: - - name: vault - - image: hashicorp/vault:1.10.3 - imagePullPolicy: IfNotPresent - command: - - "/bin/sh" - - "-ec" - args: - - | - /usr/local/bin/docker-entrypoint.sh vault server -dev - - securityContext: - allowPrivilegeEscalation: false - env: - - name: HOST_IP - valueFrom: - fieldRef: - fieldPath: status.hostIP - - name: POD_IP - valueFrom: - fieldRef: - fieldPath: status.podIP - - name: VAULT_K8S_POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: VAULT_K8S_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: VAULT_ADDR - value: "http://127.0.0.1:8200" - - name: VAULT_API_ADDR - value: "http://$(POD_IP):8200" - - name: SKIP_CHOWN - value: "true" - - name: SKIP_SETCAP - value: "true" - - name: HOSTNAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: VAULT_CLUSTER_ADDR - value: "https://$(HOSTNAME).vault-internal:8201" - - name: HOME - value: "/home/vault" - - - name: VAULT_DEV_ROOT_TOKEN_ID - value: root - - name: VAULT_DEV_LISTEN_ADDRESS - value: "[::]:8200" - - - - volumeMounts: - - - - - name: home - mountPath: /home/vault - ports: - - containerPort: 8200 - name: http - - containerPort: 8201 - name: https-internal - - containerPort: 8202 - name: http-rep - readinessProbe: - # Check status; unsealed vault servers return 0 - # The exit code reflects the seal status: - # 0 - unsealed - # 1 - error - # 2 - sealed - exec: - command: ["/bin/sh", "-ec", "vault status -tls-skip-verify"] - failureThreshold: 2 - initialDelaySeconds: 5 - periodSeconds: 5 - successThreshold: 1 - timeoutSeconds: 3 - lifecycle: - # Vault container doesn't receive SIGTERM from Kubernetes - # and after the grace period ends, Kube sends SIGKILL. This - # causes issues with graceful shutdowns such as deregistering itself - # from Consul (zombie services). - preStop: - exec: - command: [ - "/bin/sh", "-c", - # Adding a sleep here to give the pod eviction a - # chance to propagate, so requests will not be made - # to this pod while it's terminating - "sleep 5 && kill -SIGTERM $(pidof vault)", - ] diff --git a/e2e/utils_resources.go b/e2e/utils_resources.go index e6f9e4a20..38ebbde01 100644 --- a/e2e/utils_resources.go +++ b/e2e/utils_resources.go @@ -73,98 +73,3 @@ func addSecretDeployment(p *testProject, dir string, data map[string]string, sea {fname, fname + ".sealme", o}, }, opts.tags) } - -func addDeploymentHelper(p *testProject, dir string, o *uo.UnstructuredObject, opts resourceOpts) { - rbac := renderTemplateObjectHelper(podRbacTemplate, map[string]interface{}{ - "name": o.GetK8sName(), - "namespace": o.GetK8sNamespace(), - }) - for _, x := range rbac { - mergeMetadata(x, opts) - } - mergeMetadata(o, opts) - - resources := []kustomizeResource{ - {"rbac.yml", "", rbac}, - {"deploy.yml", "", o}, - } - - p.addKustomizeDeployment(dir, resources, opts.tags) -} - -func addJobDeployment(p *testProject, dir string, opts resourceOpts, image string, command []string, args []string) { - o := renderTemplateObjectHelper(jobTemplate, map[string]interface{}{ - "name": opts.name, - "namespace": opts.namespace, - "image": image, - }) - o[0].SetNestedField(command, "spec", "template", "spec", "containers", 0, "command") - o[0].SetNestedField(args, "spec", "template", "spec", "containers", 0, "args") - addDeploymentHelper(p, dir, o[0], opts) -} - -const podRbacTemplate = ` -apiVersion: v1 -kind: ServiceAccount -metadata: - name: {{ .name }} - namespace: {{ .namespace }} ---- -kind: RoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: "{{ .name }}" - namespace: "{{ .namespace }}" -subjects: -- kind: ServiceAccount - name: "{{ .name }}" - namespace: "{{ .namespace }}" -roleRef: - kind: ClusterRole - name: "cluster-admin" - apiGroup: rbac.authorization.k8s.io -` - -const deploymentTemplate = ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ .name }} - namespace: {{ .namespace }} - labels: - app.kubernetes.io/name: {{ .name }} -spec: - replicas: 1 - selector: - matchLabels: - app.kubernetes.io/name: {{ .name }} - template: - metadata: - labels: - app.kubernetes.io/name: {{ .name }} - spec: - terminationGracePeriodSeconds: 0 - serviceAccountName: {{ .name }} - containers: - - image: {{ .image }} - imagePullPolicy: IfNotPresent - name: container -` - -const jobTemplate = ` -apiVersion: batch/v1 -kind: Job -metadata: - name: {{ .name }} - namespace: {{ .namespace }} -spec: - template: - spec: - terminationGracePeriodSeconds: 0 - restartPolicy: OnFailure - serviceAccountName: {{ .name }} - containers: - - image: {{ .image }} - imagePullPolicy: IfNotPresent - name: container -` diff --git a/go.mod b/go.mod index 5a7fa1316..57771f841 100644 --- a/go.mod +++ b/go.mod @@ -58,7 +58,10 @@ require ( sigs.k8s.io/structured-merge-diff/v4 v4.2.3 ) -require sigs.k8s.io/controller-runtime v0.13.0 +require ( + github.com/go-logr/logr v1.2.3 + sigs.k8s.io/controller-runtime v0.13.0 +) require ( cloud.google.com/go/compute v1.10.0 // indirect @@ -104,7 +107,6 @@ require ( github.com/go-git/gcfg v1.5.0 // indirect github.com/go-git/go-billy/v5 v5.3.1 // indirect github.com/go-gorp/gorp/v3 v3.0.2 // indirect - github.com/go-logr/logr v1.2.3 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.20.0 // indirect github.com/go-openapi/swag v0.22.3 // indirect @@ -202,6 +204,7 @@ require ( golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 // indirect golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect golang.org/x/tools v0.1.12 // indirect + gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20220923205249-dd2d53f1fffc // indirect google.golang.org/grpc v1.49.0 // indirect diff --git a/go.sum b/go.sum index c5651ad7a..4249e8b49 100644 --- a/go.sum +++ b/go.sum @@ -202,6 +202,7 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= @@ -1097,6 +1098,7 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY= +gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= diff --git a/internal/test-utils/envtest_cluster.go b/internal/test-utils/envtest_cluster.go index 7683f8079..5bfd6100a 100644 --- a/internal/test-utils/envtest_cluster.go +++ b/internal/test-utils/envtest_cluster.go @@ -53,6 +53,8 @@ func (k *EnvTestCluster) Start() error { } k.user = user + k.config = user.Config() + kcfg, err := user.KubeConfig() if err != nil { return err @@ -113,7 +115,11 @@ func (c *EnvTestCluster) Kubectl(args ...string) (string, string, error) { func (c *EnvTestCluster) KubectlMust(t *testing.T, args ...string) string { stdout, stderr, err := c.Kubectl(args...) if err != nil { - t.Fatalf("%v, stderr=%s\n", err, stderr) + if t != nil { + t.Fatalf("%v, stderr=%s\n", err, stderr) + } else { + panic(fmt.Sprintf("%v, stderr=%s\n", err, stderr)) + } } return stdout } diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index 1656e080d..751664902 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -5,6 +5,8 @@ import ( "encoding/json" "fmt" "io" + "net/http" + "strings" "sync" "time" @@ -460,9 +462,47 @@ func (k *K8sCluster) UpdateObject(o *uo.UnstructuredObject, options UpdateOption return result, apiWarnings, err } +// envtestProxyGet checks the environment variables KLUCTL_K8S_SERVICE_PROXY_XXX to enable testing of proxy requests with envtest +func (k *K8sCluster) envtestProxyGet(scheme, namespace, name, port, path string, params map[string]string) (io.ReadCloser, error) { + for _, m := range utils.ParseEnvConfigSets("KLUCTL_K8S_SERVICE_PROXY") { + envApiHost := strings.TrimSuffix(m["API_HOST"], "/") + envNamespace := m["SERVICE_NAMESPACE"] + envName := m["SERVICE_NAME"] + envPort := m["SERVICE_PORT"] + envUrl := m["LOCAL_URL"] + + apiHost := strings.TrimSuffix(k.clientFactory.RESTConfig().Host, "/") + + if envApiHost != apiHost || envNamespace != namespace || envName != name || port != envPort { + continue + } + + status.Trace(k.ctx, "envtestProxyGet apiHost=%s, ns=%s, name=%s, port=%s, path=%s, envUrl=%s", apiHost, namespace, name, port, path, envUrl) + + envUrl = fmt.Sprintf("%s%s", envUrl, path) + resp, err := http.Get(envUrl) + if err != nil { + return nil, err + } + if resp.StatusCode < 200 || resp.StatusCode > 299 { + return nil, fmt.Errorf("http get returned status %d: %s", resp.StatusCode, resp.Status) + } + return resp.Body, nil + } + return nil, nil +} + func (k *K8sCluster) ProxyGet(scheme, namespace, name, port, path string, params map[string]string) (io.ReadCloser, error) { + stream, err := k.envtestProxyGet(scheme, namespace, name, port, path, params) + if err != nil { + return nil, err + } + if stream != nil { + return stream, nil + } + var ret rest.ResponseWrapper - _, err := k.clients.withClientFromPool(func(p *parallelClientEntry) error { + _, err = k.clients.withClientFromPool(func(p *parallelClientEntry) error { ret = p.corev1.Services(namespace).ProxyGet(scheme, name, port, path, params) return nil }) diff --git a/pkg/seal/fetch_cert.go b/pkg/seal/fetch_cert.go index 07fd5e7c0..5c1c28963 100644 --- a/pkg/seal/fetch_cert.go +++ b/pkg/seal/fetch_cert.go @@ -2,7 +2,9 @@ package seal import ( "context" + "crypto/sha256" "crypto/x509" + "encoding/hex" "errors" "fmt" "github.com/kluctl/kluctl/v2/pkg/k8s" @@ -105,3 +107,12 @@ func ParseCert(data []byte) (*x509.Certificate, error) { return certs[0], nil } + +func HashPublicKey(cert *x509.Certificate) (string, error) { + pkBytes, err := x509.MarshalPKIXPublicKey(cert.PublicKey) + if err != nil { + return "", err + } + h := sha256.Sum256(pkBytes) + return hex.EncodeToString(h[:]), nil +} diff --git a/pkg/seal/sealer.go b/pkg/seal/sealer.go index 268009cc6..e821f2e99 100644 --- a/pkg/seal/sealer.go +++ b/pkg/seal/sealer.go @@ -4,7 +4,6 @@ import ( "context" "crypto/rand" "crypto/rsa" - "crypto/sha256" "crypto/x509" "encoding/base64" "encoding/hex" @@ -46,17 +45,16 @@ func NewSealer(ctx context.Context, cert *x509.Certificate, forceReseal bool) (* } s.pubKey = pk - pkBytes, err := x509.MarshalPKIXPublicKey(cert.PublicKey) + var err error + s.certHash, err = HashPublicKey(cert) if err != nil { return nil, err } - h := sha256.Sum256(pkBytes) - s.certHash = hex.EncodeToString(h[:]) return s, nil } -func (s *Sealer) doHash(key string, secret []byte, secretName string, secretNamespace string, scope string) string { +func HashSecret(key string, secret []byte, secretName string, secretNamespace string, scope string) string { if secretNamespace == "" { secretNamespace = "*" } @@ -220,7 +218,7 @@ func (s *Sealer) SealFile(p string, targetFile string) error { } for k, v := range secrets { - hash := s.doHash(k, v, secretName, secretNamespace, *scope) + hash := HashSecret(k, v, secretName, secretNamespace, *scope) existingHash, _, _ := existingHashes.GetNestedString(k) doEncrypt := resealAll From 7d1f28804e43dacd0f8b5dbd13c2cc87ffb18b06 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 30 Sep 2022 15:20:44 +0200 Subject: [PATCH 1043/2916] chore: Fix exclusion of e2e tests from "make test-unit" --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 7d7c482d2..f4f6b06d0 100644 --- a/Makefile +++ b/Makefile @@ -71,7 +71,7 @@ ifeq ($(EXPORT_RESULT), true) GO111MODULE=off $(GOCMD) get -u github.com/jstemmer/go-junit-report $(eval OUTPUT_OPTIONS = | tee /dev/tty | go-junit-report -set-exit-code > reports/test-unit/junit-report.xml) endif - $(GOTEST) -v -race $(shell go list ./... | grep -v /e2e/) $(OUTPUT_OPTIONS) + $(GOTEST) -v -race $(shell go list ./... | grep -v 'v2/e2e') $(OUTPUT_OPTIONS) coverage-unit: ## Run the unit tests of the project and export the coverage $(GOTEST) -cover -covermode=count -coverprofile=reports/coverage-unit/profile.cov $(shell go list ./... | grep -v /e2e/) From b042dd909492ef42c6212afe9b9b9c6d08b457d8 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 30 Sep 2022 16:28:41 +0200 Subject: [PATCH 1044/2916] chore: Enable verbose e2e tests --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index f4f6b06d0..82f7d3504 100644 --- a/Makefile +++ b/Makefile @@ -63,7 +63,7 @@ test: test-unit test-e2e ## Runs the complete test suite KUBEBUILDER_ASSETS?="$(shell $(ENVTEST) --arch=$(ENVTEST_ARCH) use -i $(ENVTEST_KUBERNETES_VERSION) --bin-dir=$(ENVTEST_ASSETS_DIR) -p path)" test-e2e: install-envtest ## Runs the end to end tests - KUBEBUILDER_ASSETS=$(KUBEBUILDER_ASSETS) CGO_ENBALED=0 GO111MODULE=on $(GOCMD) test -o ./bin/$(TEST_BINARY_NAME) ./e2e + KUBEBUILDER_ASSETS=$(KUBEBUILDER_ASSETS) CGO_ENBALED=0 GO111MODULE=on $(GOCMD) test -o ./bin/$(TEST_BINARY_NAME) ./e2e -test.v test-unit: ## Run the unit tests of the project ifeq ($(EXPORT_RESULT), true) From 6fa34a316782b4509c72999a911c1a8dd2c9e11a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 30 Sep 2022 17:27:11 +0200 Subject: [PATCH 1045/2916] tests: Paralellize a few tests --- e2e/hooks_test.go | 1 + e2e/seal_test.go | 1 + 2 files changed, 2 insertions(+) diff --git a/e2e/hooks_test.go b/e2e/hooks_test.go index f860553ed..347ef2cf8 100644 --- a/e2e/hooks_test.go +++ b/e2e/hooks_test.go @@ -20,6 +20,7 @@ type HookTestSuite struct { } func TestHooks(t *testing.T) { + t.Parallel() suite.Run(t, &HookTestSuite{}) } diff --git a/e2e/seal_test.go b/e2e/seal_test.go index 531fef2c1..a06c03156 100644 --- a/e2e/seal_test.go +++ b/e2e/seal_test.go @@ -40,6 +40,7 @@ type certServer struct { } func TestSealedSecrets(t *testing.T) { + t.Parallel() suite.Run(t, &SealedSecretsTestSuite{}) } From 44982e64a4a974aa27b3ff5f5250043c7f992521 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 30 Sep 2022 17:27:14 +0200 Subject: [PATCH 1046/2916] chore: Download kubebuilder-tools for windows from kubebuilder-tools-releases-windows repo --- Makefile | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 82f7d3504..446ffbcf9 100644 --- a/Makefile +++ b/Makefile @@ -52,11 +52,21 @@ setup-envtest: ## Download envtest-setup locally if necessary. $(call go-install-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest@latest) # Download the envtest binaries to testbin +ENVTEST_KUBERNETES_VERSION?=1.25.0 ENVTEST_ASSETS_DIR=$(BUILD_DIR)/testbin -ENVTEST_KUBERNETES_VERSION?=latest + +ifeq ($(OS),Windows_NT) +ENVTEST_ASSETS_DIR_WINDOWS=$(ENVTEST_ASSETS_DIR)/k8s/$(ENVTEST_KUBERNETES_VERSION)-windows-amd64 +install-envtest: setup-envtest $(ENVTEST_ASSETS_DIR_WINDOWS)/etcd.exe +$(ENVTEST_ASSETS_DIR_WINDOWS)/etcd.exe: + mkdir -p $(ENVTEST_ASSETS_DIR_WINDOWS) + curl -o $(ENVTEST_ASSETS_DIR_WINDOWS)/kubebuilder-tools.tar.gz "https://raw.githubusercontent.com/kluctl/kubebuilder-tools-releases-windows/main/releases/kubebuilder-tools-$(ENVTEST_KUBERNETES_VERSION)_windows_amd64.tar.gz" + cd $(ENVTEST_ASSETS_DIR_WINDOWS) && tar xzf kubebuilder-tools.tar.gz && mv kubebuilder/bin/* . + rm $(ENVTEST_ASSETS_DIR_WINDOWS)/kubebuilder-tools.tar.gz +else install-envtest: setup-envtest - mkdir -p ${ENVTEST_ASSETS_DIR} $(ENVTEST) use $(ENVTEST_KUBERNETES_VERSION) --arch=$(ENVTEST_ARCH) --bin-dir=$(ENVTEST_ASSETS_DIR) +endif ## Test: test: test-unit test-e2e ## Runs the complete test suite From 53bd717555dc314bb0307056433d7c0e05f583fa Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 30 Sep 2022 15:25:55 +0200 Subject: [PATCH 1047/2916] ci: Stop using zerotier in CI and instead rely on envtest based tests (no docker) --- .github/workflows/tests.yml | 199 ++++------------------------- Makefile | 3 + hack/zerotier-create-network.sh | 29 ----- hack/zerotier-delete-network.sh | 16 --- hack/zerotier-join-network.sh | 28 ---- hack/zerotier-setup-docker-host.sh | 29 ----- 6 files changed, 28 insertions(+), 276 deletions(-) delete mode 100755 hack/zerotier-create-network.sh delete mode 100755 hack/zerotier-delete-network.sh delete mode 100755 hack/zerotier-join-network.sh delete mode 100755 hack/zerotier-setup-docker-host.sh diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index fad114c1a..b26c3e618 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -21,9 +21,9 @@ jobs: path: | ~/go/pkg/mod ~/.cache/go-build - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + key: build-go-${{ runner.os }}-${{ hashFiles('**/go.sum') }} restore-keys: | - ${{ runner.os }}-go- + build-go-${{ runner.os }}- - name: Run unit tests run: | go test ./cmd/... ./pkg/... -v @@ -66,64 +66,7 @@ jobs: e2e.test-darwin-amd64 e2e.test-windows-amd64.exe - docker-host: - if: "!startsWith(github.ref, 'refs/tags/')" - runs-on: ubuntu-20.04 - needs: - - build - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Install zerotier - run: | - sudo apt update - sudo apt install -y gpg jq - curl -s https://install.zerotier.com | sudo bash - - name: Setup inotify limits - run: | - # see https://kind.sigs.k8s.io/docs/user/known-issues/#pod-errors-due-to-too-many-open-files - sudo sysctl fs.inotify.max_user_watches=524288 - sudo sysctl fs.inotify.max_user_instances=512 - - name: Stop docker - run: | - # Ensure docker is down and that the test jobs can wait for it to be available again after joining the network. - # Otherwise they might join and then docker restarts - sudo systemctl stop docker - - name: Create network - run: | - export CI_ZEROTIER_API_KEY=${{ secrets.CI_ZEROTIER_API_KEY }} - ./hack/zerotier-create-network.sh $GITHUB_RUN_ID - - name: Join network - run: | - export CI_ZEROTIER_API_KEY=${{ secrets.CI_ZEROTIER_API_KEY }} - ./hack/zerotier-join-network.sh $GITHUB_RUN_ID docker - - name: Restart ssh - run: | - sudo systemctl restart ssh - - name: Restart docker - run: | - sudo sed -i 's|-H fd://|-H fd:// -H tcp://0.0.0.0:2375|g' /lib/systemd/system/docker.service - sudo systemctl daemon-reload - sudo systemctl start docker - - name: Wait for other jobs to finish - run: | - export GITHUB_TOKEN="${{ secrets.GITHUB_TOKEN }}" - while true; do - JOBS=$(gh api /repos/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID/jobs | jq '.jobs[] | select(.name | startswith("tests "))') - NON_COMPLETED=$(echo $JOBS | jq '. | select(.status != "completed")') - if [ "$NON_COMPLETED" == "" ]; then - break - fi - sleep 5 - done - - name: Delete network - if: always() - run: | - export CI_ZEROTIER_API_KEY=${{ secrets.CI_ZEROTIER_API_KEY }} - ./hack/zerotier-delete-network.sh $GITHUB_RUN_ID - tests: - if: "!startsWith(github.ref, 'refs/tags/')" strategy: matrix: include: @@ -141,125 +84,33 @@ jobs: steps: - name: Checkout uses: actions/checkout@v2 - - name: Install zerotier (linux) - if: runner.os == 'Linux' - run: | - sudo apt update - sudo apt install -y gpg jq - curl -s https://install.zerotier.com | sudo bash - - name: Install zerotier (macOS) - if: runner.os == 'macOS' - run: | - brew install zerotier-one - - name: Install zerotier (windows) - if: runner.os == 'Windows' - shell: bash - run: | - choco install zerotier-one - echo "#!/usr/bin/env bash" > /usr/bin/zerotier-cli - echo '/c/ProgramData/ZeroTier/One/zerotier-one_x64.exe -q "$@"' >> /usr/bin/zerotier-cli - chmod +x /usr/bin/zerotier-cli - - choco install netcat - echo /c/ProgramData/chocolatey/bin >> $GITHUB_PATH - - name: Join network - shell: bash - run: | - export CI_ZEROTIER_API_KEY=${{ secrets.CI_ZEROTIER_API_KEY }} - ./hack/zerotier-join-network.sh $GITHUB_RUN_ID ${{ matrix.binary-suffix }} - - name: Determine DOCKER_HOST - shell: bash - run: | - export CI_ZEROTIER_API_KEY=${{ secrets.CI_ZEROTIER_API_KEY }} - ./hack/zerotier-setup-docker-host.sh $GITHUB_RUN_ID - - name: Setup TOOLS envs - shell: bash - run: | - if [ "${{ runner.os }}" != "Windows" ]; then - echo "SUDO=sudo" >> $GITHUB_ENV - fi - - TOOLS_EXE= - TOOLS_TARGET_DIR=$GITHUB_WORKSPACE/bin - mkdir $TOOLS_TARGET_DIR - - if [ "${{ runner.os }}" == "macOS" ]; then - TOOLS_OS=darwin - elif [ "${{ runner.os }}" == "Windows" ]; then - TOOLS_OS=windows - TOOLS_EXE=.exe - else - TOOLS_OS=linux - fi - echo "TOOLS_EXE=$TOOLS_EXE" >> $GITHUB_ENV - echo "TOOLS_OS=$TOOLS_OS" >> $GITHUB_ENV - echo "TOOLS_TARGET_DIR=$TOOLS_TARGET_DIR" >> $GITHUB_ENV - echo "$TOOLS_TARGET_DIR" >> $GITHUB_PATH - - name: "[Windows] Install openssh" - if: runner.os == 'Windows' - shell: bash - run: | - choco install openssh - - name: Provide required tools versions - shell: bash - run: | - echo "KUBECTL_VERSION=1.21.5" >> $GITHUB_ENV - echo "KIND_VERSION=0.11.1" >> $GITHUB_ENV - echo "DOCKER_VERSION=20.10.9" >> $GITHUB_ENV - - name: Download required tools - shell: bash - run: | - curl -L -o kubectl$TOOLS_EXE https://storage.googleapis.com/kubernetes-release/release/v$KUBECTL_VERSION/bin/${TOOLS_OS}/amd64/kubectl$TOOLS_EXE && \ - $SUDO mv kubectl$TOOLS_EXE "$TOOLS_TARGET_DIR/" - curl -L -o kind$TOOLS_EXE https://github.com/kubernetes-sigs/kind/releases/download/v${KIND_VERSION}/kind-${TOOLS_OS}-amd64 && \ - $SUDO mv kind$TOOLS_EXE "$TOOLS_TARGET_DIR/" - if [ "${{ runner.os }}" == "macOS" ]; then - curl -L -o docker.tar.gz https://download.docker.com/mac/static/stable/x86_64/docker-$DOCKER_VERSION.tgz - tar xzf docker.tar.gz - $SUDO mv docker/docker "$TOOLS_TARGET_DIR/" - rm -rf docker - elif [ "${{ runner.os }}" == "Windows" ]; then - curl -L -o docker.zip https://download.docker.com/win/static/stable/x86_64/docker-$DOCKER_VERSION.zip - unzip docker.zip - mv docker/docker.exe "$TOOLS_TARGET_DIR/" - rm -rf docker - fi - $SUDO chmod -R +x "$TOOLS_TARGET_DIR/" - - name: Test required tools + - uses: actions/setup-go@v2 + with: + go-version: '1.19' + - uses: actions/cache@v2 + with: + path: | + ~/go/pkg/mod + ~/.cache/go-build + key: tests-go-${{ runner.os }}-${{ hashFiles('**/go.sum') }} + restore-keys: | + tests-go-${{ runner.os }}- + - name: Download artifacts + uses: actions/download-artifact@v2 + - name: setup-envtest shell: bash run: | - kubectl version || true - kind version || true - - name: Prepare kind cluster variables + make install-envtest + - name: Run e2e tests shell: bash run: | - if [ "${{ runner.os }}" == "Linux" ]; then - echo "KIND_API_PORT1=10000" >> $GITHUB_ENV - echo "KIND_API_PORT2=20000" >> $GITHUB_ENV - echo "KIND_EXTRA_PORTS_OFFSET1=30000" >> $GITHUB_ENV - echo "KIND_EXTRA_PORTS_OFFSET2=31000" >> $GITHUB_ENV - elif [ "${{ runner.os }}" == "Windows" ]; then - echo "KIND_API_PORT1=10001" >> $GITHUB_ENV - echo "KIND_API_PORT2=20001" >> $GITHUB_ENV - echo "KIND_EXTRA_PORTS_OFFSET1=30100" >> $GITHUB_ENV - echo "KIND_EXTRA_PORTS_OFFSET2=31100" >> $GITHUB_ENV - else - echo "KIND_API_PORT1=10002" >> $GITHUB_ENV - echo "KIND_API_PORT2=20002" >> $GITHUB_ENV - echo "KIND_EXTRA_PORTS_OFFSET1=30200" >> $GITHUB_ENV - echo "KIND_EXTRA_PORTS_OFFSET2=31200" >> $GITHUB_ENV + EXE= + if [ "${{ runner.os }}" == "Windows" ]; then + EXE=.exe fi - KIND_CLUSTER_NAME_BASE=$(echo "kluctl-${{ runner.os }}" | awk '{{ print tolower($1) }}') - echo "KIND_API_HOST1=$DOCKER_IP" >> $GITHUB_ENV - echo "KIND_API_HOST2=$DOCKER_IP" >> $GITHUB_ENV - echo "KIND_CLUSTER_NAME1=$KIND_CLUSTER_NAME_BASE-1" >> $GITHUB_ENV - echo "KIND_CLUSTER_NAME2=$KIND_CLUSTER_NAME_BASE-2" >> $GITHUB_ENV - - name: Download artifacts - uses: actions/download-artifact@v2 - - name: Run e2e tests - shell: bash - run: | chmod +x ./binaries/* - export KLUCTL_EXE=./binaries/kluctl-${{ matrix.binary-suffix }}$TOOLS_EXE - ./binaries/e2e.test-${{ matrix.binary-suffix }}$TOOLS_EXE -test.v + mv ./binaries/e2e.test-${{ matrix.binary-suffix }}$EXE ./e2e.test$EXE + + export KLUCTL_EXE=./binaries/kluctl-${{ matrix.binary-suffix }}$EXE + make test-e2e-pre-built diff --git a/Makefile b/Makefile index 446ffbcf9..d8a176b7f 100644 --- a/Makefile +++ b/Makefile @@ -75,6 +75,9 @@ KUBEBUILDER_ASSETS?="$(shell $(ENVTEST) --arch=$(ENVTEST_ARCH) use -i $(ENVTEST_ test-e2e: install-envtest ## Runs the end to end tests KUBEBUILDER_ASSETS=$(KUBEBUILDER_ASSETS) CGO_ENBALED=0 GO111MODULE=on $(GOCMD) test -o ./bin/$(TEST_BINARY_NAME) ./e2e -test.v +test-e2e-pre-built: install-envtest + KUBEBUILDER_ASSETS=$(KUBEBUILDER_ASSETS) ./e2e.test -test.v + test-unit: ## Run the unit tests of the project ifeq ($(EXPORT_RESULT), true) mkdir -p reports/test-unit diff --git a/hack/zerotier-create-network.sh b/hack/zerotier-create-network.sh deleted file mode 100755 index 000e77af9..000000000 --- a/hack/zerotier-create-network.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash - -set -e - -NETWORK_NAME=$1 - -NETWORK=$(curl -H"Authorization: bearer $CI_ZEROTIER_API_KEY" -XPOST -d"{}" https://my.zerotier.com/api/v1/network) -NETWORK_ID=$(echo $NETWORK | jq '.config.id' -r) - -echo "Configuring network" -cat << EOF | curl -H"Authorization: bearer $CI_ZEROTIER_API_KEY" -XPOST -d@- https://my.zerotier.com/api/v1/network/$NETWORK_ID -{ - "config": { - "private": false, - "name": "$NETWORK_NAME", - "ipAssignmentPools": [ - { - "ipRangeStart":"10.147.17.1", - "ipRangeEnd":"10.147.17.254" - } - ], - "routes": [ - { - "target":"10.147.17.0/24" - } - ] - } -} -EOF diff --git a/hack/zerotier-delete-network.sh b/hack/zerotier-delete-network.sh deleted file mode 100755 index a8c22a7f7..000000000 --- a/hack/zerotier-delete-network.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash - -set -e - -NETWORK_NAME=$1 - -echo "Searching network " -NETWORKS=$(curl -H"Authorization: bearer $CI_ZEROTIER_API_KEY" https://my.zerotier.com/api/v1/network) -NETWORK_ID=$(echo $NETWORKS | jq ".[] | select(.config.name == \"$NETWORK_NAME\") | .config.id" -r) - -if [ "$NETWORK_ID" = "" ]; then - exit 0 -fi - -echo "Deleting network" -curl -H"Authorization: bearer $CI_ZEROTIER_API_KEY" -XDELETE https://my.zerotier.com/api/v1/network/$NETWORK_ID diff --git a/hack/zerotier-join-network.sh b/hack/zerotier-join-network.sh deleted file mode 100755 index 3f2094e41..000000000 --- a/hack/zerotier-join-network.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env bash - -set -e - -NETWORK_NAME=$1 -MEMBER_NAME=$2 - -while true; do - echo "Searching network" - NETWORKS=$(curl -H"Authorization: bearer $CI_ZEROTIER_API_KEY" https://my.zerotier.com/api/v1/network) - NETWORK_ID=$(echo $NETWORKS | jq ".[] | select(.config.name == \"$NETWORK_NAME\") | .config.id" -r) - if [ "$NETWORK_ID" != "" ]; then - break - fi - sleep 5 -done - -SUDO= -if which sudo &> /dev/null; then - SUDO=sudo -fi - -echo "Joining network" -$SUDO zerotier-cli join $NETWORK_ID -MEMBER_ID=$($SUDO zerotier-cli status | awk '{print $3}') - -echo "Renaming member $MEMBER_ID" -curl -H"Authorization: bearer $CI_ZEROTIER_API_KEY" -XPOST -d"{\"name\": \"$MEMBER_NAME\"}" https://my.zerotier.com/api/v1/network/$NETWORK_ID/member/$MEMBER_ID diff --git a/hack/zerotier-setup-docker-host.sh b/hack/zerotier-setup-docker-host.sh deleted file mode 100755 index 4c06dbfc1..000000000 --- a/hack/zerotier-setup-docker-host.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash - -set -e - -NETWORK_NAME=$1 - -echo "Searching network " -NETWORKS=$(curl -H"Authorization: bearer $CI_ZEROTIER_API_KEY" https://my.zerotier.com/api/v1/network) -NETWORK_ID=$(echo $NETWORKS | jq ".[] | select(.config.name == \"$NETWORK_NAME\") | .config.id" -r) - -echo "Waiting for IP to be assigned to docker member" -while true; do - MEMBERS=$(curl -H"Authorization: bearer $CI_ZEROTIER_API_KEY" https://my.zerotier.com/api/v1/network/$NETWORK_ID/member) - IP=$(echo $MEMBERS | jq ".[] | select(.name == \"docker\") | .config.ipAssignments[0]" -r) - if [ "$IP" != "null" ]; then - break - fi - sleep 5 - echo "Still waiting..." -done - -echo "DOCKER_IP=$IP" >> $GITHUB_ENV -echo "DOCKER_HOST=tcp://$IP:2375" >> $GITHUB_ENV - -echo "Waiting for docker to become available" -while ! nc -z $IP 2375; do - sleep 5 - echo "Still waiting..." -done From 8538ad7cbe99a5a19a7cf5a16603e2e47d292f47 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 30 Sep 2022 22:39:59 +0200 Subject: [PATCH 1048/2916] ci: Run tests workflow for pull requests --- .github/workflows/tests.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b26c3e618..9f00b5a06 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -2,8 +2,9 @@ name: tests on: push: + pull_request: branches: - - '**' + - master jobs: build: From c6a093e55018cb8a4a955955f37b923bba2eaab2 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 11 Oct 2022 09:54:56 +0200 Subject: [PATCH 1049/2916] fix: Upgrade go-jinja2 to fix zombie processes --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 57771f841..d00ca0ba4 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/jinzhu/copier v0.3.5 github.com/kevinburke/ssh_config v1.2.0 github.com/kluctl/go-embed-python v0.0.0-3.10.6-20220906-2 - github.com/kluctl/go-jinja2 v0.0.0-20220927093149-38ad307371ca + github.com/kluctl/go-jinja2 v0.0.0-20221011075324-ee187dbffa85 github.com/mattn/go-colorable v0.1.13 github.com/mattn/go-isatty v0.0.16 github.com/mattn/go-runewidth v0.0.14 diff --git a/go.sum b/go.sum index 4249e8b49..df2ed7fd8 100644 --- a/go.sum +++ b/go.sum @@ -500,8 +500,8 @@ github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= github.com/kluctl/go-embed-python v0.0.0-3.10.6-20220906-2 h1:zjJAVyZiWJhufcafhykvXNar1vqDp/ukQk0hFuCJ9zw= github.com/kluctl/go-embed-python v0.0.0-3.10.6-20220906-2/go.mod h1:uekQDAlvKAYJWuDxxP1SGn1vTPUfoBwMS+aB6kR+v+o= -github.com/kluctl/go-jinja2 v0.0.0-20220927093149-38ad307371ca h1:OSl16ha01V8QHu/yKmsfKHx8r6ts/O3xHuT5SaaJ/1I= -github.com/kluctl/go-jinja2 v0.0.0-20220927093149-38ad307371ca/go.mod h1:kMEDb2dWTbpe6CT+XuGVPcnY1ph9IfHf4b43KN80qas= +github.com/kluctl/go-jinja2 v0.0.0-20221011075324-ee187dbffa85 h1:T2Xhn0xx4MYwFBzc2ADMMJbO2SwH5lfL6r3txHCPYYE= +github.com/kluctl/go-jinja2 v0.0.0-20221011075324-ee187dbffa85/go.mod h1:kMEDb2dWTbpe6CT+XuGVPcnY1ph9IfHf4b43KN80qas= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kortschak/utter v1.0.1/go.mod h1:vSmSjbyrlKjjsL71193LmzBOKgwePk9DH6uFaWHIInc= From 6fe7abcabffea0520a737ea49d3fe8548c895d91 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 17 Oct 2022 09:27:27 +0200 Subject: [PATCH 1050/2916] fix: Skip postprocessCRDs in case --offline-kubernetes was used Fixes #58 --- pkg/deployment/deployment_item.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index e0c30c28e..588a08fca 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -439,6 +439,9 @@ func (di *DeploymentItem) postprocessCRDs() error { if di.dir == nil { return nil } + if di.ctx.K == nil { + return nil + } for _, o := range di.Objects { gvk := o.GetK8sGVK() From 6b4cf92cc8847fc75c1d0df236e47ce8019a1373 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 17 Oct 2022 14:42:41 +0200 Subject: [PATCH 1051/2916] feat: Allow to pass --offline-kuberntes to list-images --- cmd/kluctl/commands/cmd_list_images.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmd/kluctl/commands/cmd_list_images.go b/cmd/kluctl/commands/cmd_list_images.go index cfd9ef56f..4ec7d3543 100644 --- a/cmd/kluctl/commands/cmd_list_images.go +++ b/cmd/kluctl/commands/cmd_list_images.go @@ -14,14 +14,15 @@ type listImagesCmd struct { args.OutputFlags args.RenderOutputDirFlags - Simple bool `group:"misc" help:"Output a simplified version of the images list"` + OfflineKubernetes bool `group:"misc" help:"Run list-images in offline mode, meaning that it will not try to connect the target cluster"` + Simple bool `group:"misc" help:"Output a simplified version of the images list"` } func (cmd *listImagesCmd) Help() string { return `The result is a compatible with yaml files expected by --fixed-images-file. If fixed images ('-f/--fixed-image') are provided, these are also taken into account, -as described in for the deploy command.` +as described in the deploy command.` } func (cmd *listImagesCmd) Run() error { @@ -32,6 +33,7 @@ func (cmd *listImagesCmd) Run() error { imageFlags: cmd.ImageFlags, inclusionFlags: cmd.InclusionFlags, renderOutputDirFlags: cmd.RenderOutputDirFlags, + offlineKubernetes: cmd.OfflineKubernetes, } return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { result := types.FixedImagesConfig{ From 63c11e52dad6d3bc23a77f67605ccfc672afd461 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 17 Oct 2022 14:56:28 +0200 Subject: [PATCH 1052/2916] feat: Allow loading args from files, e.g. -amy_arg=@file.yaml --- cmd/kluctl/args/project.go | 2 +- cmd/kluctl/commands/utils.go | 2 +- pkg/deployment/external_args.go | 13 ++++++++++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/cmd/kluctl/args/project.go b/cmd/kluctl/args/project.go index d2c933721..b9e839fc7 100644 --- a/cmd/kluctl/args/project.go +++ b/cmd/kluctl/args/project.go @@ -18,7 +18,7 @@ type ProjectFlags struct { } type ArgsFlags struct { - Arg []string `group:"project" short:"a" help:"Passes a template argument in the form of name=value. Nested args can be set with the '-a my.nested.arg=value' syntax. Values are interpreted as yaml values, meaning that 'true' and 'false' will lead to boolean values and numbers will be treated as numbers. Use quotes if you want these to be treated as strings."` + Arg []string `group:"project" short:"a" help:"Passes a template argument in the form of name=value. Nested args can be set with the '-a my.nested.arg=value' syntax. Values are interpreted as yaml values, meaning that 'true' and 'false' will lead to boolean values and numbers will be treated as numbers. Use quotes if you want these to be treated as strings. If the value starts with @, it is treated as a file, meaning that the contents of the file will be loaded and treated as yaml."` } type TargetFlags struct { diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 968077a02..5b9373376 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -146,7 +146,7 @@ func withProjectTargetCommandContext(ctx context.Context, args projectTargetComm if err != nil { return err } - optionArgs2, err := deployment.ConvertArgsToVars(optionArgs) + optionArgs2, err := deployment.ConvertArgsToVars(optionArgs, true) if err != nil { return err } diff --git a/pkg/deployment/external_args.go b/pkg/deployment/external_args.go index 955b74b27..952dfa063 100644 --- a/pkg/deployment/external_args.go +++ b/pkg/deployment/external_args.go @@ -6,6 +6,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/vars" "github.com/kluctl/kluctl/v2/pkg/yaml" + "os" "path/filepath" "regexp" "strings" @@ -28,9 +29,19 @@ func ParseArgs(argsList []string) (map[string]string, error) { return args, nil } -func ConvertArgsToVars(args map[string]string) (*uo.UnstructuredObject, error) { +func ConvertArgsToVars(args map[string]string, allowLoadFromFiles bool) (*uo.UnstructuredObject, error) { vars := uo.New() for n, v := range args { + if allowLoadFromFiles && strings.HasPrefix(v, "@") { + b, err := os.ReadFile(v[1:]) + if err != nil { + return nil, err + } + v = string(b) + } else if strings.HasPrefix(v, "\\@") { + v = v[1:] + } + var p []interface{} for _, x := range strings.Split(n, ".") { p = append(p, x) From 08b3c3df11fe4126c3beccbcaafb4b41f62151a5 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 17 Oct 2022 15:06:14 +0200 Subject: [PATCH 1053/2916] feat: Implement --args-from-file to load args from a yaml file --- cmd/kluctl/args/project.go | 3 ++- cmd/kluctl/commands/utils.go | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/cmd/kluctl/args/project.go b/cmd/kluctl/args/project.go index b9e839fc7..6dfbc8bf0 100644 --- a/cmd/kluctl/args/project.go +++ b/cmd/kluctl/args/project.go @@ -18,7 +18,8 @@ type ProjectFlags struct { } type ArgsFlags struct { - Arg []string `group:"project" short:"a" help:"Passes a template argument in the form of name=value. Nested args can be set with the '-a my.nested.arg=value' syntax. Values are interpreted as yaml values, meaning that 'true' and 'false' will lead to boolean values and numbers will be treated as numbers. Use quotes if you want these to be treated as strings. If the value starts with @, it is treated as a file, meaning that the contents of the file will be loaded and treated as yaml."` + Arg []string `group:"project" short:"a" help:"Passes a template argument in the form of name=value. Nested args can be set with the '-a my.nested.arg=value' syntax. Values are interpreted as yaml values, meaning that 'true' and 'false' will lead to boolean values and numbers will be treated as numbers. Use quotes if you want these to be treated as strings. If the value starts with @, it is treated as a file, meaning that the contents of the file will be loaded and treated as yaml."` + ArgsFromFile []string `group:"project" help:"Loads a yaml file and makes it available as arguments, meaning that they will be available thought the global 'args' variable."` } type TargetFlags struct { diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 5b9373376..eb0c9a1ad 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -15,6 +15,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/registries" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" "io/ioutil" "k8s.io/client-go/rest" @@ -150,6 +151,13 @@ func withProjectTargetCommandContext(ctx context.Context, args projectTargetComm if err != nil { return err } + for _, a := range args.argsFlags.ArgsFromFile { + optionArgs3, err := uo.FromFile(a) + if err != nil { + return err + } + optionArgs2.Merge(optionArgs3) + } renderOutputDir := args.renderOutputDirFlags.RenderOutputDir if renderOutputDir == "" { From 4bad414e42a3b4bfd616c477ece47b97405dedf4 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 17 Oct 2022 15:51:33 +0200 Subject: [PATCH 1054/2916] tests: Add tests for -a and --args-from-file --- e2e/args_test.go | 125 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 e2e/args_test.go diff --git a/e2e/args_test.go b/e2e/args_test.go new file mode 100644 index 000000000..b606df688 --- /dev/null +++ b/e2e/args_test.go @@ -0,0 +1,125 @@ +package e2e + +import ( + "fmt" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "os" + "testing" +) + +func TestArgs(t *testing.T) { + t.Parallel() + + k := defaultCluster1 + + p := &testProject{} + p.init(t, k, "args") + defer p.cleanup() + + createNamespace(t, k, p.projectName) + + p.updateTarget("test", func(target *uo.UnstructuredObject) { + _ = target.SetNestedField([]any{ + map[string]any{ + "name": "a", + }, + map[string]any{ + "name": "b", + }, + map[string]any{ + "name": "c", + }, + map[string]any{ + "name": "d", + }, + }, "dynamicArgs") + }) + p.updateDeploymentYaml(".", func(o *uo.UnstructuredObject) error { + _ = o.SetNestedField([]any{ + map[string]any{ + "name": "a", + }, + map[string]any{ + "name": "b", + "default": "default", + }, + map[string]any{ + "name": "d", + "default": map[string]any{ + "nested": "default", + }, + }, + }, "args") + return nil + }) + + addConfigMapDeployment(p, "cm", map[string]string{ + "a": `{{ args.a | default("na") }}`, + "b": `{{ args.b | default("na") }}`, + "c": `{{ args.c | default("na") }}`, + "d": "{{ args.d | to_json }}", + }, resourceOpts{ + name: "cm", + namespace: p.projectName, + }) + + p.KluctlMust("deploy", "--yes", "-t", "test", "-aa=a") + cm := k.KubectlYamlMust(t, "-n", p.projectName, "get", "cm/cm") + assertNestedFieldEquals(t, cm, "a", "data", "a") + assertNestedFieldEquals(t, cm, "default", "data", "b") + assertNestedFieldEquals(t, cm, "na", "data", "c") + assertNestedFieldEquals(t, cm, `{"nested": "default"}`, "data", "d") + + p.KluctlMust("deploy", "--yes", "-t", "test", "-aa=a", "-ab=b") + cm = k.KubectlYamlMust(t, "-n", p.projectName, "get", "cm/cm") + assertNestedFieldEquals(t, cm, "a", "data", "a") + assertNestedFieldEquals(t, cm, "b", "data", "b") + assertNestedFieldEquals(t, cm, "na", "data", "c") + assertNestedFieldEquals(t, cm, `{"nested": "default"}`, "data", "d") + + p.KluctlMust("deploy", "--yes", "-t", "test", "-aa=a", "-ab=b", "-ac=c") + cm = k.KubectlYamlMust(t, "-n", p.projectName, "get", "cm/cm") + assertNestedFieldEquals(t, cm, "a", "data", "a") + assertNestedFieldEquals(t, cm, "b", "data", "b") + assertNestedFieldEquals(t, cm, "c", "data", "c") + assertNestedFieldEquals(t, cm, `{"nested": "default"}`, "data", "d") + + p.KluctlMust("deploy", "--yes", "-t", "test", "-aa=a", "-ab=b", "-ac=c", "-ad.nested=d") + cm = k.KubectlYamlMust(t, "-n", p.projectName, "get", "cm/cm") + assertNestedFieldEquals(t, cm, `{"nested": "d"}`, "data", "d") + + p.KluctlMust("deploy", "--yes", "-t", "test", "-aa=a", "-ab=b", "-ac=c", `-ad={"nested": "d2"}`) + cm = k.KubectlYamlMust(t, "-n", p.projectName, "get", "cm/cm") + assertNestedFieldEquals(t, cm, `{"nested": "d2"}`, "data", "d") + + tmpFile, err := os.CreateTemp("", "") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpFile.Name()) + _, _ = tmpFile.WriteString(` +nested: + nested2: d3 +`) + + p.KluctlMust("deploy", "--yes", "-t", "test", "-aa=a", "-ab=b", "-ac=c", fmt.Sprintf(`-ad=@%s`, tmpFile.Name())) + cm = k.KubectlYamlMust(t, "-n", p.projectName, "get", "cm/cm") + assertNestedFieldEquals(t, cm, `{"nested": {"nested2": "d3"}}`, "data", "d") + + _ = tmpFile.Truncate(0) + _, _ = tmpFile.Seek(0, 0) + _, _ = tmpFile.WriteString(` +a: a2 +c: c2 +d: + nested: + nested2: d4 +`) + + p.KluctlMust("deploy", "--yes", "-t", "test", fmt.Sprintf(`--args-from-file=%s`, tmpFile.Name())) + cm = k.KubectlYamlMust(t, "-n", p.projectName, "get", "cm/cm") + assertNestedFieldEquals(t, cm, "a2", "data", "a") + assertNestedFieldEquals(t, cm, "default", "data", "b") + assertNestedFieldEquals(t, cm, "c2", "data", "c") + assertNestedFieldEquals(t, cm, `{"nested": {"nested2": "d4"}}`, "data", "d") +} From 7393163edbc2f2af1592105f23f25701934cf27c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 17 Oct 2022 16:02:44 +0200 Subject: [PATCH 1055/2916] tests: Add some log outputs to debug hook tests flakiness --- e2e/hooks_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/e2e/hooks_test.go b/e2e/hooks_test.go index 347ef2cf8..86bbf45fa 100644 --- a/e2e/hooks_test.go +++ b/e2e/hooks_test.go @@ -46,10 +46,12 @@ func (s *HookTestSuite) SetupTest() { } func (s *HookTestSuite) handleConfigmap(o *uo.UnstructuredObject) { + s.T().Logf("handleConfigmap: %s", o.GetK8sName()) s.seenConfigMaps = append(s.seenConfigMaps, o.GetK8sName()) } func (s *HookTestSuite) clearSeenConfigmaps() { + s.T().Logf("clearSeenConfigmaps: %v", s.seenConfigMaps) s.seenConfigMaps = nil } From 43a4f58cc4f6645ebb938f243b41c15135e8b4b8 Mon Sep 17 00:00:00 2001 From: Aljoscha Poertner Date: Mon, 17 Oct 2022 16:15:18 +0200 Subject: [PATCH 1056/2916] chore: move check list in PR template to comment Signed-off-by: Aljoscha Poertner --- .github/pull_request_template.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index dc3dd31cb..205acfcdf 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -14,6 +14,7 @@ Please delete options that are not relevant. - [ ] This change requires a documentation update - [ ] This change requires a new example + From 909f06b35a77444cef4b693cebfbfd8b0dabd806 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 17 Oct 2022 16:59:24 +0200 Subject: [PATCH 1057/2916] fix: Treat strings as raw strings (no escape sequences handles) in version patterns This avoids unnecessary warnings being printed in case of invalid escape sequences in regex strings. The parser should actually NOT try to parse these escape sequences as it would lead to invalid regex strings in case a valid escape sequence (e.g. \n) is provided. --- pkg/utils/python_scanner/README.md | 2 +- pkg/utils/python_scanner/scanner.go | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/utils/python_scanner/README.md b/pkg/utils/python_scanner/README.md index 3ae1e4c85..645cb1299 100644 --- a/pkg/utils/python_scanner/README.md +++ b/pkg/utils/python_scanner/README.md @@ -1,2 +1,2 @@ -This package is a copy of golangs text/scanner package, with the difference that it interprets ' the same way as ", so +This package is a copy of golangs text/scanner package, with the difference that it interprets ' and ` the same way as ", so that it's a little bit more like python. \ No newline at end of file diff --git a/pkg/utils/python_scanner/scanner.go b/pkg/utils/python_scanner/scanner.go index c64b1e506..006f9107b 100644 --- a/pkg/utils/python_scanner/scanner.go +++ b/pkg/utils/python_scanner/scanner.go @@ -593,9 +593,9 @@ func (s *Scanner) scanString(quote rune) (n int) { return } -func (s *Scanner) scanRawString() { +func (s *Scanner) scanRawString(quote rune) { ch := s.next() // read character after '`' - for ch != '`' { + for ch != quote { if ch < 0 { s.error("literal not terminated") return @@ -697,14 +697,14 @@ redo: break case '"': if s.Mode&ScanStrings != 0 { - s.scanString('"') + s.scanRawString('"') tok = String } ch = s.next() case '\'': // This is the difference to golang's text/scanner package, we handle ' as a string and not as a char if s.Mode&ScanStrings != 0 { - s.scanString('\'') + s.scanRawString('\'') tok = String } ch = s.next() @@ -726,7 +726,7 @@ redo: } case '`': if s.Mode&ScanRawStrings != 0 { - s.scanRawString() + s.scanRawString('`') tok = RawString } ch = s.next() From 20da8b095a6bad2df0ad3b248a492d7d0bb8aa7c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 17 Oct 2022 17:34:35 +0200 Subject: [PATCH 1058/2916] feat: Upgrade go-jinja2 to add slugify filter --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index d00ca0ba4..6b01fa205 100644 --- a/go.mod +++ b/go.mod @@ -21,8 +21,8 @@ require ( github.com/imdario/mergo v0.3.13 github.com/jinzhu/copier v0.3.5 github.com/kevinburke/ssh_config v1.2.0 - github.com/kluctl/go-embed-python v0.0.0-3.10.6-20220906-2 - github.com/kluctl/go-jinja2 v0.0.0-20221011075324-ee187dbffa85 + github.com/kluctl/go-embed-python v0.0.0-3.10.7-20221017-1 + github.com/kluctl/go-jinja2 v0.0.0-20221017153334-80addb432305 github.com/mattn/go-colorable v0.1.13 github.com/mattn/go-isatty v0.0.16 github.com/mattn/go-runewidth v0.0.14 @@ -40,9 +40,9 @@ require ( github.com/whilp/git-urls v1.0.0 github.com/xanzy/ssh-agent v0.3.2 golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be - golang.org/x/net v0.0.0-20220923203811-8be639271d50 + golang.org/x/net v0.0.0-20221014081412-f15817d10f9b golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7 - golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 + golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 golang.org/x/term v0.0.0-20220919170432-7a66f970e087 golang.org/x/text v0.3.7 gopkg.in/yaml.v2 v2.4.0 diff --git a/go.sum b/go.sum index df2ed7fd8..5d9e2e5ba 100644 --- a/go.sum +++ b/go.sum @@ -498,10 +498,10 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c= github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= -github.com/kluctl/go-embed-python v0.0.0-3.10.6-20220906-2 h1:zjJAVyZiWJhufcafhykvXNar1vqDp/ukQk0hFuCJ9zw= -github.com/kluctl/go-embed-python v0.0.0-3.10.6-20220906-2/go.mod h1:uekQDAlvKAYJWuDxxP1SGn1vTPUfoBwMS+aB6kR+v+o= -github.com/kluctl/go-jinja2 v0.0.0-20221011075324-ee187dbffa85 h1:T2Xhn0xx4MYwFBzc2ADMMJbO2SwH5lfL6r3txHCPYYE= -github.com/kluctl/go-jinja2 v0.0.0-20221011075324-ee187dbffa85/go.mod h1:kMEDb2dWTbpe6CT+XuGVPcnY1ph9IfHf4b43KN80qas= +github.com/kluctl/go-embed-python v0.0.0-3.10.7-20221017-1 h1:4XlPysiwaex+j7+r6pPA12WyuBM9Sy6gSoeKYTqxU9M= +github.com/kluctl/go-embed-python v0.0.0-3.10.7-20221017-1/go.mod h1:fuMI+yFFgrViXsRl+NIWQeuPDTvJtOk2pe0HoeujT54= +github.com/kluctl/go-jinja2 v0.0.0-20221017153334-80addb432305 h1:95WNlnGuRfLXgWoeBTDV0DhHwX26hUfnIzgiYTitsLk= +github.com/kluctl/go-jinja2 v0.0.0-20221017153334-80addb432305/go.mod h1:2G09vDjMA8VSWJT7Kox9EQi8l7DCkGnyBNcd7eomM1s= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kortschak/utter v1.0.1/go.mod h1:vSmSjbyrlKjjsL71193LmzBOKgwePk9DH6uFaWHIInc= @@ -912,8 +912,8 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220923203811-8be639271d50 h1:vKyz8L3zkd+xrMeIaBsQ/MNVPVFSffdaU3ZyYlBGFnI= -golang.org/x/net v0.0.0-20220923203811-8be639271d50/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221014081412-f15817d10f9b h1:tvrvnPFcdzp294diPnrdZZZ8XUt2Tyj7svb7X52iDuU= +golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1015,8 +1015,8 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 h1:h+EGohizhe9XlX18rfpa8k8RAc5XyaeamM+0VHRd4lc= -golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 h1:OK7RB6t2WQX54srQQYSXMW8dF5C6/8+oA/s5QBmmto4= +golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= From 8a66a63f0264acf88e79a9968aafd3596af778aa Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 17 Oct 2022 17:36:44 +0200 Subject: [PATCH 1059/2916] chore: Run go get -u ./... --- go.mod | 59 +++++++++++++++++++++++++-------------------------- go.sum | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 29 deletions(-) diff --git a/go.mod b/go.mod index 6b01fa205..fbccf67c5 100644 --- a/go.mod +++ b/go.mod @@ -6,8 +6,8 @@ require ( github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e github.com/Masterminds/semver/v3 v3.1.1 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d - github.com/aws/aws-sdk-go v1.44.105 - github.com/bitnami-labs/sealed-secrets v0.18.5 + github.com/aws/aws-sdk-go v1.44.116 + github.com/bitnami-labs/sealed-secrets v0.19.1 github.com/cyphar/filepath-securejoin v0.2.3 github.com/docker/distribution v2.8.1+incompatible github.com/fluxcd/pkg/kustomize v0.8.0 @@ -16,7 +16,7 @@ require ( github.com/gobwas/glob v0.2.3 // indirect github.com/golang-jwt/jwt/v4 v4.4.2 github.com/google/go-containerregistry v0.11.0 - github.com/hashicorp/vault/api v1.8.0 + github.com/hashicorp/vault/api v1.8.1 github.com/hexops/gotextdiff v1.0.3 github.com/imdario/mergo v0.3.13 github.com/jinzhu/copier v0.3.5 @@ -28,30 +28,30 @@ require ( github.com/mattn/go-runewidth v0.0.14 github.com/mitchellh/go-ps v1.0.0 github.com/mitchellh/reflectwalk v1.0.2 - github.com/ohler55/ojg v1.14.4 + github.com/ohler55/ojg v1.14.5 github.com/pkg/errors v0.9.1 github.com/r3labs/diff/v2 v2.15.1 github.com/rogpeppe/go-internal v1.9.0 github.com/sirupsen/logrus v1.9.0 - github.com/spf13/cobra v1.5.0 + github.com/spf13/cobra v1.6.0 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.13.0 github.com/stretchr/testify v1.8.0 github.com/whilp/git-urls v1.0.0 github.com/xanzy/ssh-agent v0.3.2 - golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be + golang.org/x/crypto v0.0.0-20221012134737-56aed061732a golang.org/x/net v0.0.0-20221014081412-f15817d10f9b - golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7 + golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 golang.org/x/term v0.0.0-20220919170432-7a66f970e087 - golang.org/x/text v0.3.7 + golang.org/x/text v0.3.8 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 - helm.sh/helm/v3 v3.10.0 - k8s.io/api v0.25.2 - k8s.io/apiextensions-apiserver v0.25.2 - k8s.io/apimachinery v0.25.2 - k8s.io/client-go v0.25.2 + helm.sh/helm/v3 v3.10.1 + k8s.io/api v0.25.3 + k8s.io/apiextensions-apiserver v0.25.3 + k8s.io/apimachinery v0.25.3 + k8s.io/client-go v0.25.3 k8s.io/klog/v2 v2.80.1 sigs.k8s.io/kustomize/api v0.12.1 sigs.k8s.io/kustomize/kyaml v0.13.9 @@ -78,7 +78,7 @@ require ( github.com/Masterminds/sprig/v3 v3.2.2 // indirect github.com/Masterminds/squirrel v1.5.3 // indirect github.com/Microsoft/go-winio v0.6.0 // indirect - github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20220930113650-c6815a8c17ad // indirect github.com/acomagu/bufpipe v1.0.3 // indirect github.com/armon/go-metrics v0.4.1 // indirect github.com/armon/go-radix v1.0.0 // indirect @@ -90,8 +90,8 @@ require ( github.com/cloudflare/circl v1.2.0 // indirect github.com/containerd/containerd v1.6.8 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/docker/cli v20.10.18+incompatible // indirect - github.com/docker/docker v20.10.18+incompatible // indirect + github.com/docker/cli v20.10.19+incompatible // indirect + github.com/docker/docker v20.10.19+incompatible // indirect github.com/docker/docker-credential-helpers v0.7.0 // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-metrics v0.0.1 // indirect @@ -102,7 +102,7 @@ require ( github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect github.com/fatih/color v1.13.0 // indirect - github.com/fsnotify/fsnotify v1.5.4 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-git/gcfg v1.5.0 // indirect github.com/go-git/go-billy/v5 v5.3.1 // indirect @@ -173,7 +173,7 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/oklog/run v1.1.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.0-rc1 // indirect + github.com/opencontainers/image-spec v1.1.0-rc2 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.0.5 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect @@ -198,28 +198,29 @@ require ( github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xlab/treeprint v1.1.0 // indirect - go.starlark.net v0.0.0-20220926145019-14b050677505 // indirect + go.etcd.io/etcd/api/v3 v3.5.5 // indirect + go.starlark.net v0.0.0-20221010140840-6bf6f0955179 // indirect go.uber.org/atomic v1.10.0 // indirect golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect - golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 // indirect + golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect golang.org/x/tools v0.1.12 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20220923205249-dd2d53f1fffc // indirect - google.golang.org/grpc v1.49.0 // indirect + google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a // indirect + google.golang.org/grpc v1.50.1 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect - k8s.io/apiserver v0.25.2 // indirect - k8s.io/cli-runtime v0.25.2 // indirect - k8s.io/component-base v0.25.2 // indirect - k8s.io/kube-openapi v0.0.0-20220803164354-a70c9af30aea // indirect - k8s.io/kubectl v0.25.2 // indirect - k8s.io/utils v0.0.0-20220922133306-665eaaec4324 // indirect - oras.land/oras-go v1.2.0 // indirect + k8s.io/apiserver v0.25.3 // indirect + k8s.io/cli-runtime v0.25.3 // indirect + k8s.io/component-base v0.25.3 // indirect + k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect + k8s.io/kubectl v0.25.3 // indirect + k8s.io/utils v0.0.0-20221012122500-cfd413dd9e85 // indirect + oras.land/oras-go v1.2.1 // indirect sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index 5d9e2e5ba..cc6a47da0 100644 --- a/go.sum +++ b/go.sum @@ -90,6 +90,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895 h1:NsReiLpErIPzRrnogAXYwSoU7txA977LjDGrbkewJbg= github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8= +github.com/ProtonMail/go-crypto v0.0.0-20220930113650-c6815a8c17ad h1:QeeqI2zxxgZVe11UrYFXXx6gVxPVF40ygekjBzEg4XY= +github.com/ProtonMail/go-crypto v0.0.0-20220930113650-c6815a8c17ad/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= @@ -116,6 +118,8 @@ github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go v1.44.105 h1:UUwoD1PRKIj3ltrDUYTDQj5fOTK3XsnqolLpRTMmSEM= github.com/aws/aws-sdk-go v1.44.105/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.116 h1:NpLIhcvLWXJZAEwvPj3TDHeqp7DleK6ZUVYyW01WNHY= +github.com/aws/aws-sdk-go v1.44.116/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -123,6 +127,8 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitnami-labs/sealed-secrets v0.18.5 h1:PT/lxfgWnP8l8ybpvTsHuJltY4A35LI8if19Ww9FbcU= github.com/bitnami-labs/sealed-secrets v0.18.5/go.mod h1:nzCyKhzDRbNySRUZXR6tvui4s95LbuaNpBoGRvV9Nk8= +github.com/bitnami-labs/sealed-secrets v0.19.1 h1:ZNLcVtTXRf7VkyNzyhe9omlwNYI0OHDteTbIHsQI7Ug= +github.com/bitnami-labs/sealed-secrets v0.19.1/go.mod h1:5UcsiOdOoviJUtXY1GNSeFa8zezbBY+j9pQAA2RtKyw= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= @@ -134,6 +140,7 @@ github.com/bwesterb/go-ristretto v1.2.1/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7N github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= @@ -153,6 +160,7 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/containerd/cgroups v1.0.3 h1:ADZftAkglvCiD44c77s5YmMqaP2pzVCFZvBmAlBdAP4= github.com/containerd/containerd v1.6.8 h1:h4dOFDwzHmqFEP754PgfgTeVXFnLiRc6kiqC7tplDJs= github.com/containerd/containerd v1.6.8/go.mod h1:By6p5KqPK0/7/CgO/A6t/Gz+CUYUu2zf1hUaaymVXB0= @@ -173,10 +181,14 @@ github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27N github.com/distribution/distribution/v3 v3.0.0-20220526142353-ffbd94cbe269 h1:hbCT8ZPPMqefiAWD2ZKjn7ypokIGViTvBBg/ExLSdCk= github.com/docker/cli v20.10.18+incompatible h1:f/GQLsVpo10VvToRay2IraVA1wHz9KktZyjev3SIVDU= github.com/docker/cli v20.10.18+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v20.10.19+incompatible h1:VKVBUb0KY/bx0FUCrCiNCL8wqgy8VxQli1dtNTn38AE= +github.com/docker/cli v20.10.19+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v20.10.18+incompatible h1:SN84VYXTBNGn92T/QwIRPlum9zfemfitN7pbsp26WSc= github.com/docker/docker v20.10.18+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v20.10.19+incompatible h1:lzEmjivyNHFHMNAFLXORMBXyGIhw/UP4DvJwvyKYq64= +github.com/docker/docker v20.10.19+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= @@ -201,6 +213,7 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= @@ -222,6 +235,8 @@ github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= @@ -443,6 +458,8 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/vault/api v1.8.0 h1:7765sW1XBt+qf4XKIYE4ebY9qc/yi9V2/egzGSUNMZU= github.com/hashicorp/vault/api v1.8.0/go.mod h1:uJrw6D3y9Rv7hhmS17JQC50jbPDAZdjZoTtrCCxxs7E= +github.com/hashicorp/vault/api v1.8.1 h1:bMieWIe6dAlqAAPReZO/8zYtXaWUg/21umwqGZpEjCI= +github.com/hashicorp/vault/api v1.8.1/go.mod h1:uJrw6D3y9Rv7hhmS17JQC50jbPDAZdjZoTtrCCxxs7E= github.com/hashicorp/vault/sdk v0.6.0 h1:6Z+In5DXHiUfZvIZdMx7e2loL1PPyDjA4bVh9ZTIAhs= github.com/hashicorp/vault/sdk v0.6.0/go.mod h1:+DRpzoXIdMvKc88R4qxr+edwy/RvH5QK8itmxLiDHLc= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= @@ -617,6 +634,8 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/ohler55/ojg v1.14.4 h1:L2ds8AlB5t/QbqSfhRwvagJzQ7pgmdrefMIypQs0Xik= github.com/ohler55/ojg v1.14.4/go.mod h1:7Ghirupn8NC8hSSDpI0gcjorPxj+vSVIONDWfliHR1k= +github.com/ohler55/ojg v1.14.5 h1:xCX2oyh/ZaoesbLH6fwVHStSJpk4o4eJs8ttXutzdg0= +github.com/ohler55/ojg v1.14.5/go.mod h1:7Ghirupn8NC8hSSDpI0gcjorPxj+vSVIONDWfliHR1k= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= @@ -627,6 +646,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc1 h1:lfG+OTa7V8PD3PKvkocSG9KAcA9MANqJn53m31Fvwkc= github.com/opencontainers/image-spec v1.1.0-rc1/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= +github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034= +github.com/opencontainers/image-spec v1.1.0-rc2/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -732,6 +753,8 @@ github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155 github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= +github.com/spf13/cobra v1.6.0 h1:42a0n6jwCot1pUmomAp4T7DeMD+20LFv4Q54pxLf2LI= +github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -788,6 +811,8 @@ github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1 github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/api/v3 v3.5.5 h1:BX4JIbQ7hl7+jL+g+2j5UAr0o1bctCm6/Ct+ArBGkf0= +go.etcd.io/etcd/api/v3 v3.5.5/go.mod h1:KFtNaxGDw4Yx/BA4iPPwevUTAuqcsPxzyX8PHydchN8= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -801,6 +826,8 @@ go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.starlark.net v0.0.0-20220926145019-14b050677505 h1:W0MibAL5BiEenQR+F/EF/a4HJhgLngHVvm6jbtUW0PM= go.starlark.net v0.0.0-20220926145019-14b050677505/go.mod h1:qsNirHv+Awo5xHuNyQ/0niov6kDxdBs+bqpVMBCW77k= +go.starlark.net v0.0.0-20221010140840-6bf6f0955179 h1:Mc5MkF55Iasgq23vSYpL6/l7EJXtlNjzw+8hbMQ/ShY= +go.starlark.net v0.0.0-20221010140840-6bf6f0955179/go.mod h1:kIVgS18CjmEC3PqMd5kaJSGEifyV/CeB9x506ZJ1Vbk= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= @@ -830,6 +857,8 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be h1:fmw3UbQh+nxngCAHrDCCztao/kbYFnWjoqop8dHx05A= golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20221012134737-56aed061732a h1:NmSIgad6KjE6VvHciPZuNRTKxGhlPfD6OA87W/PLkqg= +golang.org/x/crypto v0.0.0-20221012134737-56aed061732a/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -930,6 +959,8 @@ golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 h1:lxqLZaMad/dJHMFZH0NiNpiEZI/nhgWhe4wgzpE+MuA= golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 h1:nt+Q6cXKz4MosCSpnbMtqiQ8Oz0pxTef2B4Vca2lvfk= +golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -943,6 +974,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7 h1:ZrnxWX62AgTKOSagEqxvb3ffipvEDX2pl7E1TdqLqIc= golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 h1:cu5kTvlzcw1Q5S9f5ip1/cpiB4nXvw1XYzFPGgzLUOY= +golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1015,6 +1048,7 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 h1:OK7RB6t2WQX54srQQYSXMW8dF5C6/8+oA/s5QBmmto4= golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1032,6 +1066,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1175,6 +1211,8 @@ google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxH google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220923205249-dd2d53f1fffc h1:saaNe2+SBQxandnzcD/qB1JEBQ2Pqew+KlFLLdA/XcM= google.golang.org/genproto v0.0.0-20220923205249-dd2d53f1fffc/go.mod h1:yEEpwVWKMZZzo81NwRgyEJnA2fQvpXAYPVisv8EgDVs= +google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a h1:GH6UPn3ixhWcKDhpnEC55S75cerLPdpp3hrhfKYjZgw= +google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1196,8 +1234,11 @@ google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw= google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.50.1 h1:DS/BukOZWp8s6p4Dt/tOaJaTQyPyOoCcrjroHuCeLzY= +google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1250,6 +1291,8 @@ gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= helm.sh/helm/v3 v3.10.0 h1:y/MYONZ/bsld9kHwqgBX2uPggnUr5hahpjwt9/jrHlI= helm.sh/helm/v3 v3.10.0/go.mod h1:paPw0hO5KVfrCMbi1M8+P8xdfBri3IiJiVKATZsFR94= +helm.sh/helm/v3 v3.10.1 h1:uTnNlYx8QcTSNA4ZJ50Llwife4CSohUY4ehumyVf2QE= +helm.sh/helm/v3 v3.10.1/go.mod h1:CXOcs02AYvrlPMWARNYNRgf2rNP7gLJQsi/Ubd4EDrI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1259,28 +1302,51 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.25.2 h1:v6G8RyFcwf0HR5jQGIAYlvtRNrxMJQG1xJzaSeVnIS8= k8s.io/api v0.25.2/go.mod h1:qP1Rn4sCVFwx/xIhe+we2cwBLTXNcheRyYXwajonhy0= +k8s.io/api v0.25.3 h1:Q1v5UFfYe87vi5H7NU0p4RXC26PPMT8KOpr1TLQbCMQ= +k8s.io/api v0.25.3/go.mod h1:o42gKscFrEVjHdQnyRenACrMtbuJsVdP+WVjqejfzmI= k8s.io/apiextensions-apiserver v0.25.2 h1:8uOQX17RE7XL02ngtnh3TgifY7EhekpK+/piwzQNnBo= k8s.io/apiextensions-apiserver v0.25.2/go.mod h1:iRwwRDlWPfaHhuBfQ0WMa5skdQfrE18QXJaJvIDLvE8= +k8s.io/apiextensions-apiserver v0.25.3 h1:bfI4KS31w2f9WM1KLGwnwuVlW3RSRPuIsfNF/3HzR0k= +k8s.io/apiextensions-apiserver v0.25.3/go.mod h1:ZJqwpCkxIx9itilmZek7JgfUAM0dnTsA48I4krPqRmo= k8s.io/apimachinery v0.25.2 h1:WbxfAjCx+AeN8Ilp9joWnyJ6xu9OMeS/fsfjK/5zaQs= k8s.io/apimachinery v0.25.2/go.mod h1:hqqA1X0bsgsxI6dXsJ4HnNTBOmJNxyPp8dw3u2fSHwA= +k8s.io/apimachinery v0.25.3 h1:7o9ium4uyUOM76t6aunP0nZuex7gDf8VGwkR5RcJnQc= +k8s.io/apimachinery v0.25.3/go.mod h1:jaF9C/iPNM1FuLl7Zuy5b9v+n35HGSh6AQ4HYRkCqwo= k8s.io/apiserver v0.25.2 h1:YePimobk187IMIdnmsMxsfIbC5p4eX3WSOrS9x6FEYw= k8s.io/apiserver v0.25.2/go.mod h1:30r7xyQTREWCkG2uSjgjhQcKVvAAlqoD+YyrqR6Cn+I= +k8s.io/apiserver v0.25.3 h1:m7+xGuG5+KYAnEsqaFtDyWMkmMMEOFYlu+NlWv5qSBI= +k8s.io/apiserver v0.25.3/go.mod h1:9bT47iM2fzRuhICJpM/RcQR9sqDDfZ7Yw60h0p3JW08= k8s.io/cli-runtime v0.25.2 h1:XOx+SKRjBpYMLY/J292BHTkmyDffl/qOx3YSuFZkTuc= k8s.io/cli-runtime v0.25.2/go.mod h1:OQx3+/0st6x5YpkkJQlEWLC73V0wHsOFMC1/roxV8Oc= +k8s.io/cli-runtime v0.25.3 h1:Zs7P7l7db/5J+KDePOVtDlArAa9pZXaDinGWGZl0aM8= +k8s.io/cli-runtime v0.25.3/go.mod h1:InHHsjkyW5hQsILJGpGjeruiDZT/R0OkROQgD6GzxO4= k8s.io/client-go v0.25.2 h1:SUPp9p5CwM0yXGQrwYurw9LWz+YtMwhWd0GqOsSiefo= k8s.io/client-go v0.25.2/go.mod h1:i7cNU7N+yGQmJkewcRD2+Vuj4iz7b30kI8OcL3horQ4= +k8s.io/client-go v0.25.3 h1:oB4Dyl8d6UbfDHD8Bv8evKylzs3BXzzufLiO27xuPs0= +k8s.io/client-go v0.25.3/go.mod h1:t39LPczAIMwycjcXkVc+CB+PZV69jQuNx4um5ORDjQA= k8s.io/component-base v0.25.2 h1:Nve/ZyHLUBHz1rqwkjXm/Re6IniNa5k7KgzxZpTfSQY= k8s.io/component-base v0.25.2/go.mod h1:90W21YMr+Yjg7MX+DohmZLzjsBtaxQDDwaX4YxDkl60= +k8s.io/component-base v0.25.3 h1:UrsxciGdrCY03ULT1h/S/gXFCOPnLhUVwSyx+hM/zq4= +k8s.io/component-base v0.25.3/go.mod h1:WYoS8L+IlTZgU7rhAl5Ctpw0WdMxDfCC5dkxcEFa/TI= +k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20220803164354-a70c9af30aea h1:3QOH5+2fGsY8e1qf+GIFpg+zw/JGNrgyZRQR7/m6uWg= k8s.io/kube-openapi v0.0.0-20220803164354-a70c9af30aea/go.mod h1:C/N6wCaBHeBHkHUesQOQy2/MZqGgMAFPqGsGQLdbZBU= +k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= +k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= k8s.io/kubectl v0.25.2 h1:2993lTeVimxKSWx/7z2PiJxUILygRa3tmC4QhFaeioA= k8s.io/kubectl v0.25.2/go.mod h1:eoBGJtKUj7x38KXelz+dqVtbtbKwCqyKzJWmBHU0prg= +k8s.io/kubectl v0.25.3 h1:HnWJziEtmsm4JaJiKT33kG0kadx68MXxUE8UEbXnN4U= +k8s.io/kubectl v0.25.3/go.mod h1:glU7PiVj/R6Ud4A9FJdTcJjyzOtCJyc0eO7Mrbh3jlI= k8s.io/utils v0.0.0-20220922133306-665eaaec4324 h1:i+xdFemcSNuJvIfBlaYuXgRondKxK4z4prVPKzEaelI= k8s.io/utils v0.0.0-20220922133306-665eaaec4324/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20221012122500-cfd413dd9e85 h1:cTdVh7LYu82xeClmfzGtgyspNh6UxpwLWGi8R4sspNo= +k8s.io/utils v0.0.0-20221012122500-cfd413dd9e85/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= oras.land/oras-go v1.2.0 h1:yoKosVIbsPoFMqAIFHTnrmOuafHal+J/r+I5bdbVWu4= oras.land/oras-go v1.2.0/go.mod h1:pFNs7oHp2dYsYMSS82HaX5l4mpnGO7hbpPN6EWH2ltc= +oras.land/oras-go v1.2.1 h1:/VcGS8FUy3eEXLl/1vC4QypLHwrfSmgW7ygsoklqKK8= +oras.land/oras-go v1.2.1/go.mod h1:3N11Z5E3c4ZzOjroCl1RtAdB4yNAYl7A27j2SVf913A= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= From 8db57a5531abd06e04c553ef9be348d443b55737 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 17 Oct 2022 21:42:14 +0200 Subject: [PATCH 1060/2916] feat: Remove deprecated external projects and cluster configs --- cmd/kluctl/args/project.go | 11 +- cmd/kluctl/commands/cmd_seal.go | 3 - cmd/kluctl/commands/completion.go | 36 ------ cmd/kluctl/commands/utils.go | 22 +--- e2e/external_projects_test.go | 77 ----------- e2e/project.go | 109 +--------------- e2e/seal_test.go | 14 +- pkg/kluctl_project/git.go | 27 +--- pkg/kluctl_project/project.go | 2 - pkg/kluctl_project/project_load.go | 187 ++------------------------- pkg/kluctl_project/target_context.go | 46 +------ pkg/kluctl_project/targets.go | 10 -- pkg/types/cluster_config.go | 83 ------------ pkg/types/git_project.go | 15 --- pkg/types/kluctl_project.go | 8 +- 15 files changed, 37 insertions(+), 613 deletions(-) delete mode 100644 e2e/external_projects_test.go delete mode 100644 pkg/types/cluster_config.go diff --git a/cmd/kluctl/args/project.go b/cmd/kluctl/args/project.go index 6dfbc8bf0..d142ece1a 100644 --- a/cmd/kluctl/args/project.go +++ b/cmd/kluctl/args/project.go @@ -3,15 +3,8 @@ package args import "time" type ProjectFlags struct { - ProjectUrl string `group:"project" short:"p" help:"Git url of the kluctl project. If not specified, the current directory will be used instead of a remote Git project"` - ProjectRef string `group:"project" short:"b" help:"Git ref of the kluctl project. Only used when --project-url was given."` - - ProjectConfig existingFileType `group:"project" short:"c" help:"Location of the .kluctl.yaml config file. Defaults to $PROJECT/.kluctl.yaml" exts:"yml,yaml"` - LocalClusters existingDirType `group:"project" help:"DEPRECATED. Local clusters directory. Overrides the project from .kluctl.yaml"` - LocalDeployment existingDirType `group:"project" help:"DEPRECATED. Local deployment directory. Overrides the project from .kluctl.yaml"` - LocalSealedSecrets existingDirType `group:"project" help:"DEPRECATED. Local sealed-secrets directory. Overrides the project from .kluctl.yaml" ` - OutputMetadata string `group:"project" help:"Specify the output path for the project metadata to be written to."` - Cluster string `group:"project" help:"DEPRECATED. Specify/Override cluster"` + ProjectConfig existingFileType `group:"project" short:"c" help:"Location of the .kluctl.yaml config file. Defaults to $PROJECT/.kluctl.yaml" exts:"yml,yaml"` + OutputMetadata string `group:"project" help:"Specify the output path for the project metadata to be written to."` Timeout time.Duration `group:"project" help:"Specify timeout for all operations, including loading of the project, all external api calls and waiting for readiness." default:"10m"` GitCacheUpdateInterval time.Duration `group:"project" help:"Specify the time to wait between git cache updates. Defaults to not wait at all and always updating caches."` diff --git a/cmd/kluctl/commands/cmd_seal.go b/cmd/kluctl/commands/cmd_seal.go index 636444179..e2388b317 100644 --- a/cmd/kluctl/commands/cmd_seal.go +++ b/cmd/kluctl/commands/cmd_seal.go @@ -161,9 +161,6 @@ func (cmd *sealCmd) Run() error { if cmd.Target != "" && cmd.Target != target.Target.Name { continue } - if cmd.Cluster != "" && target.Target.Cluster != nil && cmd.Cluster != *target.Target.Cluster { - continue - } if target.Target.SealingConfig == nil { status.Info(ctx, "Target %s has no sealingConfig", target.Target.Name) continue diff --git a/cmd/kluctl/commands/completion.go b/cmd/kluctl/commands/completion.go index 3c8ae9e37..7830d90e4 100644 --- a/cmd/kluctl/commands/completion.go +++ b/cmd/kluctl/commands/completion.go @@ -5,12 +5,8 @@ import ( "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/kluctl_project" "github.com/kluctl/kluctl/v2/pkg/status" - "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" - "github.com/kluctl/kluctl/v2/pkg/yaml" "github.com/spf13/cobra" - "os" - "path/filepath" "reflect" "strings" "sync" @@ -24,10 +20,6 @@ func RegisterFlagCompletionFuncs(cmdStruct interface{}, ccmd *cobra.Command) err inclusionFlags := v.FieldByName("InclusionFlags") imageFlags := v.FieldByName("ImageFlags") - if projectFlags.IsValid() { - _ = ccmd.RegisterFlagCompletionFunc("cluster", buildClusterCompletionFunc(projectFlags.Addr().Interface().(*args.ProjectFlags))) - } - if projectFlags.IsValid() && targetFlags.IsValid() { _ = ccmd.RegisterFlagCompletionFunc("target", buildTargetCompletionFunc(projectFlags.Addr().Interface().(*args.ProjectFlags))) } @@ -56,34 +48,6 @@ func withProjectForCompletion(projectArgs *args.ProjectFlags, cb func(ctx contex }) } -func buildClusterCompletionFunc(projectArgs *args.ProjectFlags) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - var ret []string - err := withProjectForCompletion(projectArgs, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { - dents, err := os.ReadDir(p.ClustersDir) - if err != nil { - return err - } - for _, de := range dents { - var config types.ClusterConfig - err = yaml.ReadYamlFile(filepath.Join(p.ClustersDir, de.Name()), &config) - if err != nil { - continue - } - if config.Cluster.Name != "" { - ret = append(ret, config.Cluster.Name) - } - } - return nil - }) - if err != nil { - status.Error(cliCtx, err.Error()) - return nil, cobra.ShellCompDirectiveError - } - return ret, cobra.ShellCompDirectiveDefault - } -} - func buildTargetCompletionFunc(projectArgs *args.ProjectFlags) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { var ret []string diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index eb0c9a1ad..84b5f9162 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -7,7 +7,6 @@ import ( "github.com/kluctl/kluctl/v2/pkg/deployment" "github.com/kluctl/kluctl/v2/pkg/git" "github.com/kluctl/kluctl/v2/pkg/git/auth" - git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/git/repocache" ssh_pool "github.com/kluctl/kluctl/v2/pkg/git/ssh-pool" "github.com/kluctl/kluctl/v2/pkg/kluctl_jinja2" @@ -25,15 +24,6 @@ import ( ) func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, strictTemplates bool, forCompletion bool, cb func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error) error { - var url *git_url.GitUrl - if projectFlags.ProjectUrl != "" { - var err error - url, err = git_url.Parse(projectFlags.ProjectUrl) - if err != nil { - return err - } - } - tmpDir, err := ioutil.TempDir(utils.GetTmpBaseDir(), "project-") if err != nil { return fmt.Errorf("creating temporary project directory failed: %w", err) @@ -67,12 +57,7 @@ func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, strictTemplates b loadArgs := kluctl_project.LoadKluctlProjectArgs{ RepoRoot: repoRoot, ProjectDir: cwd, - ProjectUrl: url, - ProjectRef: projectFlags.ProjectRef, ProjectConfig: projectFlags.ProjectConfig.String(), - LocalClusters: projectFlags.LocalClusters.String(), - LocalDeployment: projectFlags.LocalDeployment.String(), - LocalSealedSecrets: projectFlags.LocalSealedSecrets.String(), RP: rp, ClientConfigGetter: clientConfigGetter(forCompletion), } @@ -169,13 +154,8 @@ func withProjectTargetCommandContext(ctx context.Context, args projectTargetComm renderOutputDir = tmpDir } - var clusterName *string - if args.projectFlags.Cluster != "" { - clusterName = &args.projectFlags.Cluster - } - targetCtx, err := p.NewTargetContext(ctx, - args.targetFlags.Target, clusterName, args.offlineKubernetes, + args.targetFlags.Target, args.offlineKubernetes, args.dryRunArgs == nil || args.dryRunArgs.DryRun || args.forCompletion, optionArgs2, args.forSeal, images, inclusion, renderOutputDir) diff --git a/e2e/external_projects_test.go b/e2e/external_projects_test.go deleted file mode 100644 index 94e6aebc6..000000000 --- a/e2e/external_projects_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package e2e - -import ( - "fmt" - "github.com/kluctl/kluctl/v2/pkg/utils/uo" - "testing" -) - -func doTestProject(t *testing.T, namespace string, p *testProject) { - k := defaultCluster1 - - p.init(t, k, fmt.Sprintf("project-%s", namespace)) - defer p.cleanup() - - createNamespace(t, k, namespace) - - p.updateEnvTestCluster(k, uo.FromMap(map[string]interface{}{ - "cluster_var": "cluster_value1", - })) - p.updateTargetDeprecated("test", k.Context, uo.FromMap(map[string]interface{}{ - "target_var": "target_value1", - })) - addConfigMapDeployment(p, "cm1", map[string]string{}, resourceOpts{name: "cm1", namespace: namespace}) - - p.KluctlMust("deploy", "--yes", "-t", "test") - assertResourceExists(t, k, namespace, "ConfigMap/cm1") - - cmData := map[string]string{ - "cluster_var": "{{ cluster.cluster_var }}", - "target_var": "{{ args.target_var }}", - } - - assertResourceNotExists(t, k, namespace, "ConfigMap/cm2") - addConfigMapDeployment(p, "cm2", cmData, resourceOpts{name: "cm2", namespace: namespace}) - p.KluctlMust("deploy", "--yes", "-t", "test") - - o := assertResourceExists(t, k, namespace, "ConfigMap/cm2") - assertNestedFieldEquals(t, o, "cluster_value1", "data", "cluster_var") - assertNestedFieldEquals(t, o, "target_value1", "data", "target_var") - - p.updateEnvTestCluster(k, uo.FromMap(map[string]interface{}{ - "cluster_var": "cluster_value2", - })) - p.KluctlMust("deploy", "--yes", "-t", "test") - o = assertResourceExists(t, k, namespace, "ConfigMap/cm2") - assertNestedFieldEquals(t, o, "cluster_value2", "data", "cluster_var") - assertNestedFieldEquals(t, o, "target_value1", "data", "target_var") - - p.updateTargetDeprecated("test", k.Context, uo.FromMap(map[string]interface{}{ - "target_var": "target_value2", - })) - p.KluctlMust("deploy", "--yes", "-t", "test") - o = assertResourceExists(t, k, namespace, "ConfigMap/cm2") - assertNestedFieldEquals(t, o, "cluster_value2", "data", "cluster_var") - assertNestedFieldEquals(t, o, "target_value2", "data", "target_var") -} - -func TestExternalProjects(t *testing.T) { - testCases := []struct { - name string - p testProject - }{ - {name: "external-kluctl-project", p: testProject{kluctlProjectExternal: true}}, - {name: "external-clusters-project", p: testProject{clustersExternal: true}}, - {name: "external-deployment-project", p: testProject{deploymentExternal: true}}, - {name: "external-sealed-secrets-project", p: testProject{sealedSecretsExternal: true}}, - {name: "external-all-projects", p: testProject{kluctlProjectExternal: true, clustersExternal: true, deploymentExternal: true, sealedSecretsExternal: true}}, - } - - for _, tc := range testCases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - doTestProject(t, tc.name, &tc.p) - }) - } -} diff --git a/e2e/project.go b/e2e/project.go index e106c795a..3249dd8c5 100644 --- a/e2e/project.go +++ b/e2e/project.go @@ -22,15 +22,6 @@ type testProject struct { extraEnv []string projectName string - kluctlProjectExternal bool - clustersExternal bool - deploymentExternal bool - sealedSecretsExternal bool - - localClusters *string - localDeployment *string - localSealedSecrets *string - mergedKubeconfig string gitServer *test_utils.GitServer @@ -42,29 +33,8 @@ func (p *testProject) init(t *testing.T, k *test_utils.EnvTestCluster, projectNa p.projectName = projectName p.gitServer.GitInit(p.getKluctlProjectRepo()) - if p.clustersExternal { - p.gitServer.GitInit(p.getClustersRepo()) - } - if p.deploymentExternal { - p.gitServer.GitInit(p.getDeploymentRepo()) - } - if p.sealedSecretsExternal { - p.gitServer.GitInit(p.getSealedSecretsRepo()) - } - - _ = os.MkdirAll(filepath.Join(p.gitServer.LocalRepoDir(p.getClustersRepo()), "clusters"), 0o700) - _ = os.MkdirAll(filepath.Join(p.gitServer.LocalRepoDir(p.getSealedSecretsRepo()), ".sealed-secrets"), 0o700) p.updateKluctlYaml(func(o *uo.UnstructuredObject) error { - if p.clustersExternal { - o.SetNestedField(p.gitServer.LocalGitUrl(p.getClustersRepo()), "clusters", "project") - } - if p.deploymentExternal { - o.SetNestedField(p.gitServer.LocalGitUrl(p.getDeploymentRepo()), "deployment", "project") - } - if p.sealedSecretsExternal { - o.SetNestedField(p.gitServer.LocalGitUrl(p.getSealedSecretsRepo()), "sealedSecrets", "project") - } return nil }) p.updateDeploymentYaml(".", func(c *uo.UnstructuredObject) error { @@ -124,7 +94,7 @@ func (p *testProject) updateKluctlYaml(update func(o *uo.UnstructuredObject) err } func (p *testProject) updateDeploymentYaml(dir string, update func(o *uo.UnstructuredObject) error) { - p.gitServer.UpdateYaml(p.getDeploymentRepo(), filepath.Join(dir, "deployment.yml"), func(o *uo.UnstructuredObject) error { + p.gitServer.UpdateYaml(p.getKluctlProjectRepo(), filepath.Join(dir, "deployment.yml"), func(o *uo.UnstructuredObject) error { if dir == "." { o.SetNestedField(p.projectName, "commonLabels", "project_name") } @@ -133,7 +103,7 @@ func (p *testProject) updateDeploymentYaml(dir string, update func(o *uo.Unstruc } func (p *testProject) getDeploymentYaml(dir string) *uo.UnstructuredObject { - o, err := uo.FromFile(filepath.Join(p.gitServer.LocalRepoDir(p.getDeploymentRepo()), dir, "deployment.yml")) + o, err := uo.FromFile(filepath.Join(p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()), dir, "deployment.yml")) if err != nil { p.t.Fatal(err) } @@ -165,43 +135,14 @@ func (p *testProject) listDeploymentItemPathes(dir string, fullPath bool) []stri } func (p *testProject) updateKustomizeDeployment(dir string, update func(o *uo.UnstructuredObject, wt *git.Worktree) error) { - wt := p.gitServer.GetWorktree(p.getDeploymentRepo()) + wt := p.gitServer.GetWorktree(p.getKluctlProjectRepo()) pth := filepath.Join(dir, "kustomization.yml") - p.gitServer.UpdateYaml(p.getDeploymentRepo(), pth, func(o *uo.UnstructuredObject) error { + p.gitServer.UpdateYaml(p.getKluctlProjectRepo(), pth, func(o *uo.UnstructuredObject) error { return update(o, wt) }, fmt.Sprintf("Update kustomization.yml for %s", dir)) } -func (p *testProject) updateCluster(name string, context string, vars *uo.UnstructuredObject) { - pth := filepath.Join("clusters", fmt.Sprintf("%s.yml", name)) - p.gitServer.UpdateYaml(p.getClustersRepo(), pth, func(o *uo.UnstructuredObject) error { - o.Clear() - o.SetNestedField(name, "cluster", "name") - o.SetNestedField(context, "cluster", "context") - if vars != nil { - o.MergeChild("cluster", vars) - } - return nil - }, fmt.Sprintf("add/update cluster %s", name)) -} - -func (p *testProject) updateEnvTestCluster(k *test_utils.EnvTestCluster, vars *uo.UnstructuredObject) { - context := k.KubectlMust(p.t, "config", "current-context") - context = strings.TrimSpace(context) - p.updateCluster(k.Context, context, vars) -} - -func (p *testProject) updateTargetDeprecated(name string, cluster string, args *uo.UnstructuredObject) { - p.updateTarget(name, func(target *uo.UnstructuredObject) { - if args != nil { - target.MergeChild("args", args) - } - // compatibility - _ = target.SetNestedField(cluster, "cluster") - }) -} - func (p *testProject) updateTarget(name string, cb func(target *uo.UnstructuredObject)) { p.updateNamedListItem(uo.KeyPath{"targets"}, name, cb) } @@ -286,7 +227,7 @@ func (p *testProject) addKustomizeDeployment(dir string, resources []kustomizeRe p.addDeploymentIncludes(deploymentDir) } - absKustomizeDir := filepath.Join(p.gitServer.LocalRepoDir(p.getDeploymentRepo()), dir) + absKustomizeDir := filepath.Join(p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()), dir) err := os.MkdirAll(absKustomizeDir, 0o700) if err != nil { @@ -350,7 +291,7 @@ func (p *testProject) addKustomizeResources(dir string, resources []kustomizeRes if fileName == "" { fileName = r.name } - err := yaml.WriteYamlAllFile(filepath.Join(p.gitServer.LocalRepoDir(p.getDeploymentRepo()), dir, fileName), x) + err := yaml.WriteYamlAllFile(filepath.Join(p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()), dir, fileName), x) if err != nil { return err } @@ -383,48 +324,12 @@ func (p *testProject) getKluctlProjectRepo() string { return "kluctl-project" } -func (p *testProject) getClustersRepo() string { - if p.clustersExternal { - return "external-clusters" - } - return p.getKluctlProjectRepo() -} - -func (p *testProject) getDeploymentRepo() string { - if p.deploymentExternal { - return "external-deployment" - } - return p.getKluctlProjectRepo() -} - -func (p *testProject) getSealedSecretsRepo() string { - if p.sealedSecretsExternal { - return "external-sealed-secrets" - } - return p.getKluctlProjectRepo() -} - func (p *testProject) Kluctl(argsIn ...string) (string, string, error) { var args []string args = append(args, argsIn...) args = append(args, "--no-update-check") - cwd := "" - if p.kluctlProjectExternal { - args = append(args, "--project-url", p.gitServer.LocalGitUrl(p.getKluctlProjectRepo())) - } else { - cwd = p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()) - } - - if p.localClusters != nil { - args = append(args, "--local-clusters", *p.localClusters) - } - if p.localDeployment != nil { - args = append(args, "--local-deployment", *p.localDeployment) - } - if p.localSealedSecrets != nil { - args = append(args, "--local-sealed-secrets", *p.localSealedSecrets) - } + cwd := p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()) args = append(args, "--debug") diff --git a/e2e/seal_test.go b/e2e/seal_test.go index a06c03156..14219dd66 100644 --- a/e2e/seal_test.go +++ b/e2e/seal_test.go @@ -204,7 +204,7 @@ func (s *SealedSecretsTestSuite) TestSeal_WithOperator() { p.KluctlMust("seal", "-t", "test-target") - sealedSecretsDir := p.gitServer.LocalRepoDir(p.getSealedSecretsRepo()) + sealedSecretsDir := p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()) assert.FileExists(s.T(), filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) p.KluctlMust("deploy", "--yes", "-t", "test-target") @@ -239,7 +239,7 @@ func (s *SealedSecretsTestSuite) TestSeal_WithBootstrap() { p.KluctlMust("seal", "-t", "test-target") - sealedSecretsDir := p.gitServer.LocalRepoDir(p.getSealedSecretsRepo()) + sealedSecretsDir := p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()) assert.FileExists(s.T(), filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) test_resources.ApplyYaml("sealed-secrets.yaml", k) @@ -287,7 +287,7 @@ func (s *SealedSecretsTestSuite) TestSeal_MultipleVarSources() { p.KluctlMust("seal", "-t", "test-target") - sealedSecretsDir := p.gitServer.LocalRepoDir(p.getSealedSecretsRepo()) + sealedSecretsDir := p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()) assert.FileExists(s.T(), filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) p.KluctlMust("deploy", "--yes", "-t", "test-target") @@ -328,7 +328,7 @@ func (s *SealedSecretsTestSuite) TestSeal_MultipleSecretSets() { p.KluctlMust("seal", "-t", "test-target") - sealedSecretsDir := p.gitServer.LocalRepoDir(p.getSealedSecretsRepo()) + sealedSecretsDir := p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()) assert.FileExists(s.T(), filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) p.KluctlMust("deploy", "--yes", "-t", "test-target") @@ -380,7 +380,7 @@ func (s *SealedSecretsTestSuite) TestSeal_MultipleTargets() { p.KluctlMust("seal", "-t", "test-target") p.KluctlMust("seal", "-t", "test-target2") - sealedSecretsDir := p.gitServer.LocalRepoDir(p.getSealedSecretsRepo()) + sealedSecretsDir := p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()) assert.FileExists(s.T(), filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) assert.FileExists(s.T(), filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target2/secret-secret.yml")) @@ -425,7 +425,7 @@ func (s *SealedSecretsTestSuite) TestSeal_File() { p.KluctlMust("seal", "-t", "test-target") - sealedSecretsDir := p.gitServer.LocalRepoDir(p.getSealedSecretsRepo()) + sealedSecretsDir := p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()) assert.FileExists(s.T(), filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) p.KluctlMust("deploy", "--yes", "-t", "test-target") @@ -481,7 +481,7 @@ func (s *SealedSecretsTestSuite) TestSeal_Vault() { p.extraEnv = append(p.extraEnv, "VAULT_TOKEN=root") p.KluctlMust("seal", "-t", "test-target") - sealedSecretsDir := p.gitServer.LocalRepoDir(p.getSealedSecretsRepo()) + sealedSecretsDir := p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()) assert.FileExists(s.T(), filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) p.KluctlMust("deploy", "--yes", "-t", "test-target") diff --git a/pkg/kluctl_project/git.go b/pkg/kluctl_project/git.go index 12e7253e5..393836704 100644 --- a/pkg/kluctl_project/git.go +++ b/pkg/kluctl_project/git.go @@ -3,7 +3,6 @@ package kluctl_project import ( "fmt" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" - types2 "github.com/kluctl/kluctl/v2/pkg/types" "sync" ) @@ -33,37 +32,13 @@ func (c *LoadedKluctlProject) updateGitCaches() error { return nil } - doUpdateExternalProject := func(p *types2.ExternalProject) error { - if p == nil || p.Project == nil { - return nil - } - return doUpdateGitProject(p.Project.Url) - } - - err := doUpdateExternalProject(c.Config.Deployment) - if err != nil { - waitGroup.Wait() - return err - } - err = doUpdateExternalProject(c.Config.SealedSecrets) - if err != nil { - waitGroup.Wait() - return err - } - for _, ep := range c.Config.Clusters.Projects { - err = doUpdateExternalProject(&ep) - if err != nil { - waitGroup.Wait() - return err - } - } for _, target := range c.Config.Targets { if target.TargetConfig == nil || target.TargetConfig.Project == nil { continue } - err = doUpdateGitProject(target.TargetConfig.Project.Url) + err := doUpdateGitProject(target.TargetConfig.Project.Url) if err != nil { waitGroup.Wait() return err diff --git a/pkg/kluctl_project/project.go b/pkg/kluctl_project/project.go index ebb5b2e26..0f89a9eb1 100644 --- a/pkg/kluctl_project/project.go +++ b/pkg/kluctl_project/project.go @@ -19,8 +19,6 @@ type LoadedKluctlProject struct { projectRootDir string ProjectDir string - DeploymentDir string - ClustersDir string sealedSecretsDir string Config types2.KluctlProject diff --git a/pkg/kluctl_project/project_load.go b/pkg/kluctl_project/project_load.go index 738c3219c..29ea9e871 100644 --- a/pkg/kluctl_project/project_load.go +++ b/pkg/kluctl_project/project_load.go @@ -1,43 +1,25 @@ package kluctl_project import ( - "fmt" - git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/git/repocache" "github.com/kluctl/kluctl/v2/pkg/status" - types2 "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/yaml" - "io/ioutil" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd/api" - "os" "path/filepath" ) type LoadKluctlProjectArgs struct { - RepoRoot string - ProjectDir string - ProjectUrl *git_url.GitUrl - ProjectRef string - ProjectConfig string - LocalClusters string - LocalDeployment string - LocalSealedSecrets string + RepoRoot string + ProjectDir string + ProjectConfig string RP *repocache.GitRepoCache ClientConfigGetter func(context *string) (*rest.Config, *api.Config, error) } -type gitProjectInfo struct { - url git_url.GitUrl - ref string - commit string - repoRoot string - dir string -} - func (c *LoadedKluctlProject) getConfigPath() string { configPath := c.loadArgs.ProjectConfig if configPath == "" { @@ -49,91 +31,14 @@ func (c *LoadedKluctlProject) getConfigPath() string { return configPath } -func (c *LoadedKluctlProject) localProject(dir string) gitProjectInfo { - return gitProjectInfo{ - dir: dir, - } -} - -func (c *LoadedKluctlProject) loadGitProject(gitProject *types2.GitProject, defaultSubDir string) (ret gitProjectInfo, err error) { - ge, err := c.RP.GetEntry(gitProject.Url) - if err != nil { - return - } - - cloneDir, ri, err := ge.GetClonedDir(gitProject.Ref) - if err != nil { - return - } - - ret.url = gitProject.Url - ret.ref = ri.CheckedOutRef - ret.commit = ri.CheckedOutCommit - ret.repoRoot = cloneDir - - subDir := gitProject.SubDir - if subDir == "" { - subDir = defaultSubDir - } - ret.dir = filepath.Join(ret.repoRoot, subDir) - err = utils.CheckInDir(ret.repoRoot, ret.dir) - if err != nil { - return - } - - return -} - -func (c *LoadedKluctlProject) loadExternalProject(ep *types2.ExternalProject, defaultGitSubDir string, localDir string) (gitProjectInfo, error) { - if localDir != "" { - return c.localProject(localDir), nil - } - - if ep == nil { - // no ExternalProject provided, so we point into the kluctl project + defaultGitSubDir - p := filepath.Join(c.ProjectDir, defaultGitSubDir) - return c.localProject(p), nil - } - - if ep.Project != nil { - status.Deprecation(c.ctx, "external-projects", "External projects are deprecated and support for them will be removed in the future. "+ - "Use Git variable sources as replacement for cluster configs and Git includes as replacement for external deployment projects.") - - // pointing to an actual external project, so let's try to clone it - return c.loadGitProject(ep.Project, defaultGitSubDir) - } - - // ExternalProject was provided but without an external repo url, so point into the kluctl project. - // We also allow to leave the kluctl project dir but limit it to the git project - - p := filepath.Join(c.ProjectDir, *ep.Path) - err := utils.CheckInDir(c.projectRootDir, p) - if err != nil { - return gitProjectInfo{}, fmt.Errorf("path '%s' is not inside git project root '%s': %w", p, c.projectRootDir, err) - } - return c.localProject(p), nil -} - func (c *LoadedKluctlProject) loadKluctlProject() error { var err error - if c.loadArgs.ProjectUrl == nil { - c.projectRootDir = c.loadArgs.RepoRoot - c.ProjectDir = c.loadArgs.ProjectDir - err = utils.CheckInDir(c.projectRootDir, c.ProjectDir) - if err != nil { - return err - } - } else { - gi, err := c.loadGitProject(&types2.GitProject{ - Url: *c.loadArgs.ProjectUrl, - Ref: c.loadArgs.ProjectRef, - }, "") - if err != nil { - return err - } - c.projectRootDir = gi.repoRoot - c.ProjectDir = gi.dir + c.projectRootDir = c.loadArgs.RepoRoot + c.ProjectDir = c.loadArgs.ProjectDir + err = utils.CheckInDir(c.projectRootDir, c.ProjectDir) + if err != nil { + return err } configPath := c.getConfigPath() @@ -145,89 +50,17 @@ func (c *LoadedKluctlProject) loadKluctlProject() error { } } - err = c.updateGitCaches() - if err != nil { - return err - } - s := status.Start(c.ctx, "Loading kluctl project") defer s.Failed() - if c.loadArgs.LocalClusters != "" { - status.Deprecation(c.ctx, "--local-clusters", "--local-clusters is deprecated and will be removed in an upcoming version. Use variables loaded from git instead.") - } - if c.loadArgs.LocalDeployment != "" { - status.Deprecation(c.ctx, "--local-deployment", "--local-deployment is deprecated and will be removed in an upcoming version. Use git includes instead.") - } - if c.loadArgs.LocalSealedSecrets != "" { - status.Deprecation(c.ctx, "--local-sealed-secrets", "--local-sealed-secrets is deprecated and will be removed in an upcoming version.") - } - - deploymentInfo, err := c.loadExternalProject(c.Config.Deployment, "", c.loadArgs.LocalDeployment) - if err != nil { - return err - } - sealedSecretsInfo, err := c.loadExternalProject(c.Config.SealedSecrets, ".sealed-secrets", c.loadArgs.LocalSealedSecrets) - if err != nil { - return err - } - var clustersInfos []gitProjectInfo - if c.loadArgs.LocalClusters != "" { - clustersInfos = append(clustersInfos, c.localProject(c.loadArgs.LocalClusters)) - } else if len(c.Config.Clusters.Projects) != 0 { - for _, ep := range c.Config.Clusters.Projects { - info, err := c.loadExternalProject(&ep, "clusters", "") - if err != nil { - return err - } - clustersInfos = append(clustersInfos, info) - } - } else { - ci, err := c.loadExternalProject(nil, "clusters", "") - if err != nil { - return err - } - clustersInfos = append(clustersInfos, ci) - } - - mergedClustersDir := filepath.Join(c.TmpDir, "merged-clusters") - err = c.mergeClustersDirs(mergedClustersDir, clustersInfos) + err = c.updateGitCaches() if err != nil { return err } - c.DeploymentDir = deploymentInfo.dir - c.ClustersDir = mergedClustersDir - c.sealedSecretsDir = sealedSecretsInfo.dir + c.sealedSecretsDir = filepath.Join(c.ProjectDir, ".sealed-secrets") s.Success() return nil } - -func (c *LoadedKluctlProject) mergeClustersDirs(mergedClustersDir string, clustersInfos []gitProjectInfo) error { - err := os.MkdirAll(mergedClustersDir, 0o700) - if err != nil { - return err - } - - for _, ci := range clustersInfos { - if !utils.IsDirectory(ci.dir) { - continue - } - files, err := ioutil.ReadDir(ci.dir) - if err != nil { - return err - } - for _, fi := range files { - p := filepath.Join(ci.dir, fi.Name()) - if utils.IsFile(p) { - err = utils.CopyFile(p, filepath.Join(mergedClustersDir, fi.Name())) - if err != nil { - return err - } - } - } - } - return nil -} diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index b2c9ad978..3015d4345 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -26,8 +26,8 @@ type TargetContext struct { DeploymentCollection *deployment.DeploymentCollection } -func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName string, clusterName *string, offlineK8s bool, dryRun bool, externalArgs *uo.UnstructuredObject, forSeal bool, images *deployment.Images, inclusion *utils.Inclusion, renderOutputDir string) (*TargetContext, error) { - deploymentDir, err := filepath.Abs(p.DeploymentDir) +func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName string, offlineK8s bool, dryRun bool, externalArgs *uo.UnstructuredObject, forSeal bool, images *deployment.Images, inclusion *utils.Inclusion, renderOutputDir string) (*TargetContext, error) { + deploymentDir, err := filepath.Abs(p.ProjectDir) if err != nil { return nil, err } @@ -43,7 +43,7 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName s images.PrependFixedImages(target.Images) } - varsCtx, clientConfig, clusterContext, err := p.buildVars(target, clusterName, offlineK8s, externalArgs, forSeal) + varsCtx, clientConfig, clusterContext, err := p.buildVars(target, offlineK8s, externalArgs, forSeal) if err != nil { return nil, err } @@ -104,32 +104,14 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName s return targetCtx, nil } -func (p *LoadedKluctlProject) buildVars(target *types.Target, clusterName *string, offlineK8s bool, externalArgs *uo.UnstructuredObject, forSeal bool) (*vars.VarsCtx, *rest.Config, string, error) { +func (p *LoadedKluctlProject) buildVars(target *types.Target, offlineK8s bool, externalArgs *uo.UnstructuredObject, forSeal bool) (*vars.VarsCtx, *rest.Config, string, error) { doError := func(err error) (*vars.VarsCtx, *rest.Config, string, error) { return nil, nil, "", err } varsCtx := vars.NewVarsCtx(p.J2) - var contextName *string - if clusterName == nil && target != nil { - clusterName = target.Cluster - contextName = target.Context - } - - if clusterName != nil { - clusterConfig, err := p.LoadClusterConfig(*clusterName) - if err != nil { - return doError(err) - } - err = varsCtx.UpdateChildFromStruct("cluster", clusterConfig.Cluster) - if err != nil { - return doError(err) - } - if contextName == nil { - contextName = &clusterConfig.Cluster.Context - } - } + contextName := target.Context var err error var clientConfig *rest.Config @@ -173,7 +155,7 @@ func (p *LoadedKluctlProject) buildVars(target *types.Target, clusterName *strin } } - err = deployment.LoadDeploymentArgs(p.DeploymentDir, varsCtx, allArgs) + err = deployment.LoadDeploymentArgs(p.ProjectDir, varsCtx, allArgs) if err != nil { return doError(err) } @@ -197,7 +179,7 @@ func (p *LoadedKluctlProject) findSecretsEntry(name string) (*types.SecretSet, e } func (p *LoadedKluctlProject) loadSecrets(target *types.Target, varsCtx *vars.VarsCtx, varsLoader *vars.VarsLoader) error { - searchDirs := []string{p.DeploymentDir} + searchDirs := []string{p.ProjectDir} for _, secretSetName := range target.SealingConfig.SecretSets { secretEntry, err := p.findSecretsEntry(secretSetName) @@ -216,17 +198,3 @@ func (p *LoadedKluctlProject) loadSecrets(target *types.Target, varsCtx *vars.Va } return nil } - -func (p *LoadedKluctlProject) LoadClusterConfig(clusterName string) (*types.ClusterConfig, error) { - var err error - var clusterConfig *types.ClusterConfig - - status.Deprecation(p.ctx, "cluster-config", "Cluster configurations have been deprecated and support for them will be removed in a future kluctl release.") - - clusterConfig, err = types.LoadClusterConfig(p.ClustersDir, clusterName) - if err != nil { - return nil, err - } - - return clusterConfig, nil -} diff --git a/pkg/kluctl_project/targets.go b/pkg/kluctl_project/targets.go index 17f3f784c..3be7f1d60 100644 --- a/pkg/kluctl_project/targets.go +++ b/pkg/kluctl_project/targets.go @@ -87,16 +87,6 @@ func (c *LoadedKluctlProject) renderTarget(target *types.Target) error { return err } - if target.Cluster != nil { - cc, err := c.LoadClusterConfig(*target.Cluster) - if err == nil { - err = varsCtx.UpdateChildFromStruct("cluster", cc.Cluster) - if err != nil { - return err - } - } - } - changed, err := varsCtx.RenderStruct(target) if err == nil && !changed { return nil diff --git a/pkg/types/cluster_config.go b/pkg/types/cluster_config.go deleted file mode 100644 index fe8ef11b2..000000000 --- a/pkg/types/cluster_config.go +++ /dev/null @@ -1,83 +0,0 @@ -package types - -import ( - "fmt" - "github.com/kluctl/kluctl/v2/pkg/utils" - "github.com/kluctl/kluctl/v2/pkg/utils/uo" - "github.com/kluctl/kluctl/v2/pkg/yaml" - "path/filepath" -) - -type ClusterConfig2 struct { - Name string `yaml:"name" validate:"required"` - Context string `yaml:"context" validate:"required"` - Vars *uo.UnstructuredObject `yaml:"vars,omitempty"` -} - -type ClusterConfig struct { - Cluster *ClusterConfig2 `yaml:"cluster"` -} - -// TODO remove custom unmarshaller when https://github.com/goccy/go-yaml/pull/220 gets released -func (cc *ClusterConfig2) UnmarshalYAML(unmarshal func(interface{}) error) error { - var u uo.UnstructuredObject - err := unmarshal(&u) - if err != nil { - return err - } - - name, ok, err := u.GetNestedString("name") - if !ok { - return fmt.Errorf("name is missing (or not a string) in cluster config") - } - context, ok, err := u.GetNestedString("context") - if !ok { - return fmt.Errorf("context is missing (or not a string) in cluster config") - } - - cc.Name = name - cc.Context = context - - _ = u.RemoveNestedField("name") - _ = u.RemoveNestedField("context") - - cc.Vars = &u - return nil -} - -func (cc *ClusterConfig2) MarshalYAML() (interface{}, error) { - o := uo.FromMap(map[string]interface{}{ - "name": cc.Name, - "context": cc.Context, - }) - o.Merge(cc.Vars) - return o, nil -} - -func LoadClusterConfig(clusterDir string, clusterName string) (*ClusterConfig, error) { - if clusterName == "" { - return nil, fmt.Errorf("cluster name must be specified") - } - - p := yaml.FixPathExt(filepath.Join(clusterDir, fmt.Sprintf("%s.yml", clusterName))) - if !utils.IsFile(p) { - return nil, fmt.Errorf("cluster config for %s not found", clusterName) - } - - err := utils.CheckInDir(clusterDir, p) - if err != nil { - return nil, fmt.Errorf("cluster config for %s is not in cluster dir", clusterName) - } - - var config ClusterConfig - err = yaml.ReadYamlFile(p, &config) - if err != nil { - return nil, err - } - - if config.Cluster.Name != clusterName { - return nil, fmt.Errorf("cluster name in config (%s) does not match requested cluster name %s", config.Cluster.Name, clusterName) - } - - return &config, nil -} diff --git a/pkg/types/git_project.go b/pkg/types/git_project.go index efa7c6dc8..dd8c7bae6 100644 --- a/pkg/types/git_project.go +++ b/pkg/types/git_project.go @@ -43,21 +43,6 @@ func ValidateExternalProject(sl validator.StructLevel) { } } -type ExternalProjects struct { - Projects []ExternalProject -} - -func (gp *ExternalProjects) UnmarshalYAML(unmarshal func(interface{}) error) error { - singleProject := ExternalProject{} - if err := unmarshal(&singleProject); err == nil { - // it's a single project - gp.Projects = []ExternalProject{singleProject} - return nil - } - // try as array - return unmarshal(&gp.Projects) -} - func init() { yaml.Validator.RegisterStructValidation(ValidateGitProject, GitProject{}) yaml.Validator.RegisterStructValidation(ValidateExternalProject, ExternalProject{}) diff --git a/pkg/types/kluctl_project.go b/pkg/types/kluctl_project.go index 3f7efdf59..9fddb42e8 100644 --- a/pkg/types/kluctl_project.go +++ b/pkg/types/kluctl_project.go @@ -30,7 +30,6 @@ type SealingConfig struct { type Target struct { Name string `yaml:"name" validate:"required"` - Cluster *string `yaml:"cluster,omitempty"` Context *string `yaml:"context,omitempty"` Args *uo.UnstructuredObject `yaml:"args,omitempty"` DynamicArgs []DynamicArg `yaml:"dynamicArgs,omitempty"` @@ -71,11 +70,8 @@ type SecretsConfig struct { } type KluctlProject struct { - Deployment *ExternalProject `yaml:"deployment,omitempty"` - SealedSecrets *ExternalProject `yaml:"sealedSecrets,omitempty"` - Clusters ExternalProjects `yaml:"clusters,omitempty"` - Targets []*Target `yaml:"targets,omitempty"` - SecretsConfig *SecretsConfig `yaml:"secretsConfig,omitempty"` + Targets []*Target `yaml:"targets,omitempty"` + SecretsConfig *SecretsConfig `yaml:"secretsConfig,omitempty"` } func init() { From 0cb6433818d30f18602688fa15aa3cfe40a7af99 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 17 Oct 2022 22:06:24 +0200 Subject: [PATCH 1061/2916] chore: Run go mod tidy --- go.sum | 60 ---------------------------------------------------------- 1 file changed, 60 deletions(-) diff --git a/go.sum b/go.sum index cc6a47da0..379bb254c 100644 --- a/go.sum +++ b/go.sum @@ -88,8 +88,6 @@ github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2B github.com/Microsoft/hcsshim v0.9.4 h1:mnUj0ivWy6UzbB1uLFqKR6F+ZyiDc7j4iGgHTpO+5+I= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= -github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895 h1:NsReiLpErIPzRrnogAXYwSoU7txA977LjDGrbkewJbg= -github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8= github.com/ProtonMail/go-crypto v0.0.0-20220930113650-c6815a8c17ad h1:QeeqI2zxxgZVe11UrYFXXx6gVxPVF40ygekjBzEg4XY= github.com/ProtonMail/go-crypto v0.0.0-20220930113650-c6815a8c17ad/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= @@ -116,8 +114,6 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/aws/aws-sdk-go v1.44.105 h1:UUwoD1PRKIj3ltrDUYTDQj5fOTK3XsnqolLpRTMmSEM= -github.com/aws/aws-sdk-go v1.44.105/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aws/aws-sdk-go v1.44.116 h1:NpLIhcvLWXJZAEwvPj3TDHeqp7DleK6ZUVYyW01WNHY= github.com/aws/aws-sdk-go v1.44.116/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -125,8 +121,6 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bitnami-labs/sealed-secrets v0.18.5 h1:PT/lxfgWnP8l8ybpvTsHuJltY4A35LI8if19Ww9FbcU= -github.com/bitnami-labs/sealed-secrets v0.18.5/go.mod h1:nzCyKhzDRbNySRUZXR6tvui4s95LbuaNpBoGRvV9Nk8= github.com/bitnami-labs/sealed-secrets v0.19.1 h1:ZNLcVtTXRf7VkyNzyhe9omlwNYI0OHDteTbIHsQI7Ug= github.com/bitnami-labs/sealed-secrets v0.19.1/go.mod h1:5UcsiOdOoviJUtXY1GNSeFa8zezbBY+j9pQAA2RtKyw= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= @@ -140,7 +134,6 @@ github.com/bwesterb/go-ristretto v1.2.1/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7N github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= @@ -179,14 +172,10 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/distribution/distribution/v3 v3.0.0-20220526142353-ffbd94cbe269 h1:hbCT8ZPPMqefiAWD2ZKjn7ypokIGViTvBBg/ExLSdCk= -github.com/docker/cli v20.10.18+incompatible h1:f/GQLsVpo10VvToRay2IraVA1wHz9KktZyjev3SIVDU= -github.com/docker/cli v20.10.18+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v20.10.19+incompatible h1:VKVBUb0KY/bx0FUCrCiNCL8wqgy8VxQli1dtNTn38AE= github.com/docker/cli v20.10.19+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v20.10.18+incompatible h1:SN84VYXTBNGn92T/QwIRPlum9zfemfitN7pbsp26WSc= -github.com/docker/docker v20.10.18+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v20.10.19+incompatible h1:lzEmjivyNHFHMNAFLXORMBXyGIhw/UP4DvJwvyKYq64= github.com/docker/docker v20.10.19+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= @@ -233,8 +222,6 @@ github.com/fluxcd/pkg/kustomize v0.8.0/go.mod h1:zGtCZF6V3hMWcf46SqrQc10fS9yUlKz github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= -github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -456,8 +443,6 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hashicorp/vault/api v1.8.0 h1:7765sW1XBt+qf4XKIYE4ebY9qc/yi9V2/egzGSUNMZU= -github.com/hashicorp/vault/api v1.8.0/go.mod h1:uJrw6D3y9Rv7hhmS17JQC50jbPDAZdjZoTtrCCxxs7E= github.com/hashicorp/vault/api v1.8.1 h1:bMieWIe6dAlqAAPReZO/8zYtXaWUg/21umwqGZpEjCI= github.com/hashicorp/vault/api v1.8.1/go.mod h1:uJrw6D3y9Rv7hhmS17JQC50jbPDAZdjZoTtrCCxxs7E= github.com/hashicorp/vault/sdk v0.6.0 h1:6Z+In5DXHiUfZvIZdMx7e2loL1PPyDjA4bVh9ZTIAhs= @@ -632,8 +617,6 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= -github.com/ohler55/ojg v1.14.4 h1:L2ds8AlB5t/QbqSfhRwvagJzQ7pgmdrefMIypQs0Xik= -github.com/ohler55/ojg v1.14.4/go.mod h1:7Ghirupn8NC8hSSDpI0gcjorPxj+vSVIONDWfliHR1k= github.com/ohler55/ojg v1.14.5 h1:xCX2oyh/ZaoesbLH6fwVHStSJpk4o4eJs8ttXutzdg0= github.com/ohler55/ojg v1.14.5/go.mod h1:7Ghirupn8NC8hSSDpI0gcjorPxj+vSVIONDWfliHR1k= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= @@ -644,8 +627,6 @@ github.com/onsi/ginkgo/v2 v2.1.6 h1:Fx2POJZfKRQcM1pH49qSZiYeu319wji004qX+GDovrU= github.com/onsi/gomega v1.20.2 h1:8uQq0zMgLEfa0vRrrBgaJF2gyW9Da9BmfGV+OyUzfkY= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0-rc1 h1:lfG+OTa7V8PD3PKvkocSG9KAcA9MANqJn53m31Fvwkc= -github.com/opencontainers/image-spec v1.1.0-rc1/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034= github.com/opencontainers/image-spec v1.1.0-rc2/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -751,8 +732,6 @@ github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= -github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= -github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= github.com/spf13/cobra v1.6.0 h1:42a0n6jwCot1pUmomAp4T7DeMD+20LFv4Q54pxLf2LI= github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= @@ -824,8 +803,6 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.starlark.net v0.0.0-20220926145019-14b050677505 h1:W0MibAL5BiEenQR+F/EF/a4HJhgLngHVvm6jbtUW0PM= -go.starlark.net v0.0.0-20220926145019-14b050677505/go.mod h1:qsNirHv+Awo5xHuNyQ/0niov6kDxdBs+bqpVMBCW77k= go.starlark.net v0.0.0-20221010140840-6bf6f0955179 h1:Mc5MkF55Iasgq23vSYpL6/l7EJXtlNjzw+8hbMQ/ShY= go.starlark.net v0.0.0-20221010140840-6bf6f0955179/go.mod h1:kIVgS18CjmEC3PqMd5kaJSGEifyV/CeB9x506ZJ1Vbk= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= @@ -855,8 +832,6 @@ golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be h1:fmw3UbQh+nxngCAHrDCCztao/kbYFnWjoqop8dHx05A= -golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20221012134737-56aed061732a h1:NmSIgad6KjE6VvHciPZuNRTKxGhlPfD6OA87W/PLkqg= golang.org/x/crypto v0.0.0-20221012134737-56aed061732a/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -957,8 +932,6 @@ golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 h1:lxqLZaMad/dJHMFZH0NiNpiEZI/nhgWhe4wgzpE+MuA= -golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 h1:nt+Q6cXKz4MosCSpnbMtqiQ8Oz0pxTef2B4Vca2lvfk= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -972,8 +945,6 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7 h1:ZrnxWX62AgTKOSagEqxvb3ffipvEDX2pl7E1TdqLqIc= -golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 h1:cu5kTvlzcw1Q5S9f5ip1/cpiB4nXvw1XYzFPGgzLUOY= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1044,7 +1015,6 @@ golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1064,7 +1034,6 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= @@ -1209,8 +1178,6 @@ google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220923205249-dd2d53f1fffc h1:saaNe2+SBQxandnzcD/qB1JEBQ2Pqew+KlFLLdA/XcM= -google.golang.org/genproto v0.0.0-20220923205249-dd2d53f1fffc/go.mod h1:yEEpwVWKMZZzo81NwRgyEJnA2fQvpXAYPVisv8EgDVs= google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a h1:GH6UPn3ixhWcKDhpnEC55S75cerLPdpp3hrhfKYjZgw= google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -1235,8 +1202,6 @@ google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= -google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw= -google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.1 h1:DS/BukOZWp8s6p4Dt/tOaJaTQyPyOoCcrjroHuCeLzY= google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -1289,8 +1254,6 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= -helm.sh/helm/v3 v3.10.0 h1:y/MYONZ/bsld9kHwqgBX2uPggnUr5hahpjwt9/jrHlI= -helm.sh/helm/v3 v3.10.0/go.mod h1:paPw0hO5KVfrCMbi1M8+P8xdfBri3IiJiVKATZsFR94= helm.sh/helm/v3 v3.10.1 h1:uTnNlYx8QcTSNA4ZJ50Llwife4CSohUY4ehumyVf2QE= helm.sh/helm/v3 v3.10.1/go.mod h1:CXOcs02AYvrlPMWARNYNRgf2rNP7gLJQsi/Ubd4EDrI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1300,51 +1263,28 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.25.2 h1:v6G8RyFcwf0HR5jQGIAYlvtRNrxMJQG1xJzaSeVnIS8= -k8s.io/api v0.25.2/go.mod h1:qP1Rn4sCVFwx/xIhe+we2cwBLTXNcheRyYXwajonhy0= k8s.io/api v0.25.3 h1:Q1v5UFfYe87vi5H7NU0p4RXC26PPMT8KOpr1TLQbCMQ= k8s.io/api v0.25.3/go.mod h1:o42gKscFrEVjHdQnyRenACrMtbuJsVdP+WVjqejfzmI= -k8s.io/apiextensions-apiserver v0.25.2 h1:8uOQX17RE7XL02ngtnh3TgifY7EhekpK+/piwzQNnBo= -k8s.io/apiextensions-apiserver v0.25.2/go.mod h1:iRwwRDlWPfaHhuBfQ0WMa5skdQfrE18QXJaJvIDLvE8= k8s.io/apiextensions-apiserver v0.25.3 h1:bfI4KS31w2f9WM1KLGwnwuVlW3RSRPuIsfNF/3HzR0k= k8s.io/apiextensions-apiserver v0.25.3/go.mod h1:ZJqwpCkxIx9itilmZek7JgfUAM0dnTsA48I4krPqRmo= -k8s.io/apimachinery v0.25.2 h1:WbxfAjCx+AeN8Ilp9joWnyJ6xu9OMeS/fsfjK/5zaQs= -k8s.io/apimachinery v0.25.2/go.mod h1:hqqA1X0bsgsxI6dXsJ4HnNTBOmJNxyPp8dw3u2fSHwA= k8s.io/apimachinery v0.25.3 h1:7o9ium4uyUOM76t6aunP0nZuex7gDf8VGwkR5RcJnQc= k8s.io/apimachinery v0.25.3/go.mod h1:jaF9C/iPNM1FuLl7Zuy5b9v+n35HGSh6AQ4HYRkCqwo= -k8s.io/apiserver v0.25.2 h1:YePimobk187IMIdnmsMxsfIbC5p4eX3WSOrS9x6FEYw= -k8s.io/apiserver v0.25.2/go.mod h1:30r7xyQTREWCkG2uSjgjhQcKVvAAlqoD+YyrqR6Cn+I= k8s.io/apiserver v0.25.3 h1:m7+xGuG5+KYAnEsqaFtDyWMkmMMEOFYlu+NlWv5qSBI= k8s.io/apiserver v0.25.3/go.mod h1:9bT47iM2fzRuhICJpM/RcQR9sqDDfZ7Yw60h0p3JW08= -k8s.io/cli-runtime v0.25.2 h1:XOx+SKRjBpYMLY/J292BHTkmyDffl/qOx3YSuFZkTuc= -k8s.io/cli-runtime v0.25.2/go.mod h1:OQx3+/0st6x5YpkkJQlEWLC73V0wHsOFMC1/roxV8Oc= k8s.io/cli-runtime v0.25.3 h1:Zs7P7l7db/5J+KDePOVtDlArAa9pZXaDinGWGZl0aM8= k8s.io/cli-runtime v0.25.3/go.mod h1:InHHsjkyW5hQsILJGpGjeruiDZT/R0OkROQgD6GzxO4= -k8s.io/client-go v0.25.2 h1:SUPp9p5CwM0yXGQrwYurw9LWz+YtMwhWd0GqOsSiefo= -k8s.io/client-go v0.25.2/go.mod h1:i7cNU7N+yGQmJkewcRD2+Vuj4iz7b30kI8OcL3horQ4= k8s.io/client-go v0.25.3 h1:oB4Dyl8d6UbfDHD8Bv8evKylzs3BXzzufLiO27xuPs0= k8s.io/client-go v0.25.3/go.mod h1:t39LPczAIMwycjcXkVc+CB+PZV69jQuNx4um5ORDjQA= -k8s.io/component-base v0.25.2 h1:Nve/ZyHLUBHz1rqwkjXm/Re6IniNa5k7KgzxZpTfSQY= -k8s.io/component-base v0.25.2/go.mod h1:90W21YMr+Yjg7MX+DohmZLzjsBtaxQDDwaX4YxDkl60= k8s.io/component-base v0.25.3 h1:UrsxciGdrCY03ULT1h/S/gXFCOPnLhUVwSyx+hM/zq4= k8s.io/component-base v0.25.3/go.mod h1:WYoS8L+IlTZgU7rhAl5Ctpw0WdMxDfCC5dkxcEFa/TI= -k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20220803164354-a70c9af30aea h1:3QOH5+2fGsY8e1qf+GIFpg+zw/JGNrgyZRQR7/m6uWg= -k8s.io/kube-openapi v0.0.0-20220803164354-a70c9af30aea/go.mod h1:C/N6wCaBHeBHkHUesQOQy2/MZqGgMAFPqGsGQLdbZBU= k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= -k8s.io/kubectl v0.25.2 h1:2993lTeVimxKSWx/7z2PiJxUILygRa3tmC4QhFaeioA= -k8s.io/kubectl v0.25.2/go.mod h1:eoBGJtKUj7x38KXelz+dqVtbtbKwCqyKzJWmBHU0prg= k8s.io/kubectl v0.25.3 h1:HnWJziEtmsm4JaJiKT33kG0kadx68MXxUE8UEbXnN4U= k8s.io/kubectl v0.25.3/go.mod h1:glU7PiVj/R6Ud4A9FJdTcJjyzOtCJyc0eO7Mrbh3jlI= -k8s.io/utils v0.0.0-20220922133306-665eaaec4324 h1:i+xdFemcSNuJvIfBlaYuXgRondKxK4z4prVPKzEaelI= -k8s.io/utils v0.0.0-20220922133306-665eaaec4324/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= k8s.io/utils v0.0.0-20221012122500-cfd413dd9e85 h1:cTdVh7LYu82xeClmfzGtgyspNh6UxpwLWGi8R4sspNo= k8s.io/utils v0.0.0-20221012122500-cfd413dd9e85/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -oras.land/oras-go v1.2.0 h1:yoKosVIbsPoFMqAIFHTnrmOuafHal+J/r+I5bdbVWu4= -oras.land/oras-go v1.2.0/go.mod h1:pFNs7oHp2dYsYMSS82HaX5l4mpnGO7hbpPN6EWH2ltc= oras.land/oras-go v1.2.1 h1:/VcGS8FUy3eEXLl/1vC4QypLHwrfSmgW7ygsoklqKK8= oras.land/oras-go v1.2.1/go.mod h1:3N11Z5E3c4ZzOjroCl1RtAdB4yNAYl7A27j2SVf913A= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= From 5c2cb4fa9505d450b682ee34a3cba7918bfa4670 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 17 Oct 2022 22:18:33 +0200 Subject: [PATCH 1062/2916] refactor: Remove some unused code --- e2e/utils_resources.go | 21 --------------------- pkg/utils/debugger.go | 26 -------------------------- 2 files changed, 47 deletions(-) delete mode 100644 pkg/utils/debugger.go diff --git a/e2e/utils_resources.go b/e2e/utils_resources.go index 38ebbde01..4bf65af62 100644 --- a/e2e/utils_resources.go +++ b/e2e/utils_resources.go @@ -1,10 +1,8 @@ package e2e import ( - "bytes" "fmt" "github.com/kluctl/kluctl/v2/pkg/utils/uo" - "text/template" ) type resourceOpts struct { @@ -30,25 +28,6 @@ func mergeMetadata(o *uo.UnstructuredObject, opts resourceOpts) { } } -func renderTemplateHelper(tmpl string, m map[string]interface{}) string { - t := template.Must(template.New("").Parse(tmpl)) - r := bytes.NewBuffer(nil) - err := t.Execute(r, m) - if err != nil { - panic(err) - } - return r.String() -} - -func renderTemplateObjectHelper(tmpl string, m map[string]interface{}) []*uo.UnstructuredObject { - s := renderTemplateHelper(tmpl, m) - ret, err := uo.FromStringMulti(s) - if err != nil { - panic(err) - } - return ret -} - func addConfigMapDeployment(p *testProject, dir string, data map[string]string, opts resourceOpts) { o := uo.New() o.SetK8sGVKs("", "v1", "ConfigMap") diff --git a/pkg/utils/debugger.go b/pkg/utils/debugger.go deleted file mode 100644 index d16910726..000000000 --- a/pkg/utils/debugger.go +++ /dev/null @@ -1,26 +0,0 @@ -package utils - -import ( - "os" - - "github.com/mitchellh/go-ps" -) - -func IsLaunchedByDebugger() bool { - pid := os.Getppid() - - // We loop in case there were intermediary processes like the gopls language server. - for pid != 0 { - switch p, err := ps.FindProcess(pid); { - case p == nil: - return false - case err != nil: - return false - case p.Executable() == "dlv": - return true - default: - pid = p.PPid() - } - } - return false -} From 96e1a086fa86380d6ee95cca4cbc8817a2483b4e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 17 Oct 2022 22:19:23 +0200 Subject: [PATCH 1063/2916] refactor: Stop using deprecated ioutil funcs --- cmd/kluctl/commands/utils.go | 7 +++---- internal/test-utils/git_server.go | 3 +-- pkg/deployment/deployment_item.go | 5 ++--- pkg/deployment/helm_chart.go | 3 +-- pkg/git/auth/env_auth_provider.go | 6 +++--- pkg/git/auth/ssh_auth_provider.go | 7 +++---- pkg/git/mirrored_repo.go | 11 +++++------ pkg/git/poor_mans_clone.go | 3 +-- pkg/git/repocache/cache.go | 3 +-- pkg/registries/registries.go | 10 +++++----- pkg/seal/fetch_cert.go | 4 ++-- pkg/vars/vars_loader.go | 3 +-- pkg/vars/vars_loader_http.go | 3 +-- pkg/vars/vars_loader_test.go | 18 +++++++++--------- 14 files changed, 38 insertions(+), 48 deletions(-) diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 84b5f9162..0b3fe41d8 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -16,7 +16,6 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" - "io/ioutil" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd/api" @@ -24,7 +23,7 @@ import ( ) func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, strictTemplates bool, forCompletion bool, cb func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error) error { - tmpDir, err := ioutil.TempDir(utils.GetTmpBaseDir(), "project-") + tmpDir, err := os.MkdirTemp(utils.GetTmpBaseDir(), "project-") if err != nil { return fmt.Errorf("creating temporary project directory failed: %w", err) } @@ -73,7 +72,7 @@ func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, strictTemplates b if err != nil { return err } - err = ioutil.WriteFile(projectFlags.OutputMetadata, b, 0o640) + err = os.WriteFile(projectFlags.OutputMetadata, b, 0o640) if err != nil { return err } @@ -146,7 +145,7 @@ func withProjectTargetCommandContext(ctx context.Context, args projectTargetComm renderOutputDir := args.renderOutputDirFlags.RenderOutputDir if renderOutputDir == "" { - tmpDir, err := ioutil.TempDir(p.TmpDir, "rendered") + tmpDir, err := os.MkdirTemp(p.TmpDir, "rendered") if err != nil { return err } diff --git a/internal/test-utils/git_server.go b/internal/test-utils/git_server.go index 29727fdeb..45a969474 100644 --- a/internal/test-utils/git_server.go +++ b/internal/test-utils/git_server.go @@ -8,7 +8,6 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" - "io/ioutil" "log" "net" "net/http" @@ -33,7 +32,7 @@ func NewGitServer(t *testing.T) *GitServer { t: t, } - baseDir, err := ioutil.TempDir(os.TempDir(), "kluctl-tests-") + baseDir, err := os.MkdirTemp(os.TempDir(), "kluctl-tests-") if err != nil { p.t.Fatal(err) } diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 588a08fca..7f944e92f 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -9,7 +9,6 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" "io/fs" - "io/ioutil" "k8s.io/apimachinery/pkg/runtime/schema" "os" "path/filepath" @@ -307,11 +306,11 @@ func (di *DeploymentItem) resolveSealedSecrets() error { if !utils.IsFile(sourcePath) { return fmt.Errorf("%s. %s not found. You might need to seal it first", baseError, sourcePath) } - b, err := ioutil.ReadFile(sourcePath) + b, err := os.ReadFile(sourcePath) if err != nil { return fmt.Errorf("failed to read source secret file %s: %w", sourcePath, err) } - err = ioutil.WriteFile(targetPath, b, 0o600) + err = os.WriteFile(targetPath, b, 0o600) if err != nil { return fmt.Errorf("failed to write target secret file %s: %w", targetPath, err) } diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index 2d930c4b0..b7930f0f8 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -22,7 +22,6 @@ import ( "helm.sh/helm/v3/pkg/registry" "helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/repo" - "io/ioutil" "k8s.io/apimachinery/pkg/runtime/schema" "os" "path/filepath" @@ -339,7 +338,7 @@ func (c *helmChart) doRender(ctx context.Context, k *k8s.K8sCluster) error { return err } - err = ioutil.WriteFile(outputPath, rendered, 0o600) + err = os.WriteFile(outputPath, rendered, 0o600) if err != nil { return err } diff --git a/pkg/git/auth/env_auth_provider.go b/pkg/git/auth/env_auth_provider.go index 2c1483811..7f692b661 100644 --- a/pkg/git/auth/env_auth_provider.go +++ b/pkg/git/auth/env_auth_provider.go @@ -5,7 +5,7 @@ import ( git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/utils" - "io/ioutil" + "os" ) type GitEnvAuthProvider struct { @@ -31,7 +31,7 @@ func (a *GitEnvAuthProvider) BuildAuth(ctx context.Context, gitUrl git_url.GitUr if ssh_key_path != "" { ssh_key_path = utils.ExpandPath(ssh_key_path) - b, err := ioutil.ReadFile(ssh_key_path) + b, err := os.ReadFile(ssh_key_path) if err != nil { status.Trace(ctx, "GitEnvAuthProvider: failed to read key %s: %v", ssh_key_path, err) } else { @@ -41,7 +41,7 @@ func (a *GitEnvAuthProvider) BuildAuth(ctx context.Context, gitUrl git_url.GitUr ca_bundle_path := m["CA_BUNDLE"] if ca_bundle_path != "" { ca_bundle_path = utils.ExpandPath(ca_bundle_path) - b, err := ioutil.ReadFile(ca_bundle_path) + b, err := os.ReadFile(ca_bundle_path) if err != nil { status.Trace(ctx, "GitEnvAuthProvider: failed to read ca bundle %s: %v", ca_bundle_path, err) } else { diff --git a/pkg/git/auth/ssh_auth_provider.go b/pkg/git/auth/ssh_auth_provider.go index 9c347192b..c1be1b032 100644 --- a/pkg/git/auth/ssh_auth_provider.go +++ b/pkg/git/auth/ssh_auth_provider.go @@ -12,7 +12,6 @@ import ( sshagent "github.com/xanzy/ssh-agent" "golang.org/x/crypto/ssh" "io" - "io/ioutil" "os" "os/user" "path/filepath" @@ -176,7 +175,7 @@ func (k *deferredPassphraseKey) parse() { return } - pemBytes, err := ioutil.ReadFile(k.path) + pemBytes, err := os.ReadFile(k.path) if err != nil { k.err = err status.Warning(k.ctx, "Failed to parse key %s: %v", k.path, err) @@ -213,7 +212,7 @@ func (k *dummyPublicKey) Verify(data []byte, sig *ssh.Signature) error { } func (k *deferredPassphraseKey) Hash() ([]byte, error) { - pemBytes, err := ioutil.ReadFile(k.path) + pemBytes, err := os.ReadFile(k.path) if err != nil { return nil, err } @@ -252,7 +251,7 @@ func (k *deferredPassphraseKey) Sign(rand io.Reader, data []byte) (*ssh.Signatur } func (a *GitSshAuthProvider) readKey(ctx context.Context, path string) (ssh.Signer, error) { - pemBytes, err := ioutil.ReadFile(path) + pemBytes, err := os.ReadFile(path) if err != nil { return nil, err } else { diff --git a/pkg/git/mirrored_repo.go b/pkg/git/mirrored_repo.go index 00eb928c6..c3a08ff7d 100644 --- a/pkg/git/mirrored_repo.go +++ b/pkg/git/mirrored_repo.go @@ -14,7 +14,6 @@ import ( "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/rogpeppe/go-internal/lockedfile" - "io/ioutil" "os" "path/filepath" "strings" @@ -120,7 +119,7 @@ func (g *MirroredGitRepo) IsLocked() bool { } func (g *MirroredGitRepo) LastUpdateTime() time.Time { - s, err := ioutil.ReadFile(filepath.Join(g.mirrorDir, ".update-time")) + s, err := os.ReadFile(filepath.Join(g.mirrorDir, ".update-time")) if err != nil { return time.Time{} } @@ -183,7 +182,7 @@ func (g *MirroredGitRepo) buildRepositoryObject() (*git.Repository, error) { func (g *MirroredGitRepo) cleanupMirrorDir() error { if utils.IsDirectory(g.mirrorDir) { - files, err := ioutil.ReadDir(g.mirrorDir) + files, err := os.ReadDir(g.mirrorDir) if err != nil { return err } @@ -280,7 +279,7 @@ func (g *MirroredGitRepo) update(s *status.StatusContext, repoDir string) error } } - _ = ioutil.WriteFile(filepath.Join(g.mirrorDir, ".update-time"), []byte(time.Now().Format(time.RFC3339Nano)), 0644) + _ = os.WriteFile(filepath.Join(g.mirrorDir, ".update-time"), []byte(time.Now().Format(time.RFC3339Nano)), 0644) return nil } @@ -295,7 +294,7 @@ func (g *MirroredGitRepo) cloneOrUpdate(s *status.StatusContext) error { return err } - tmpMirrorDir, err := ioutil.TempDir(utils.GetTmpBaseDir(), "mirror-") + tmpMirrorDir, err := os.MkdirTemp(utils.GetTmpBaseDir(), "mirror-") if err != nil { return err } @@ -320,7 +319,7 @@ func (g *MirroredGitRepo) cloneOrUpdate(s *status.StatusContext) error { return err } - files, err := ioutil.ReadDir(tmpMirrorDir) + files, err := os.ReadDir(tmpMirrorDir) if err != nil { return err } diff --git a/pkg/git/poor_mans_clone.go b/pkg/git/poor_mans_clone.go index d5216cb49..f5f306924 100644 --- a/pkg/git/poor_mans_clone.go +++ b/pkg/git/poor_mans_clone.go @@ -4,7 +4,6 @@ import ( "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/config" "github.com/kluctl/kluctl/v2/pkg/utils" - "io/ioutil" "os" "path/filepath" "runtime" @@ -63,7 +62,7 @@ func PoorMansClone(sourceDir string, targetDir string, coOptions *git.CheckoutOp if err != nil { return err } - err = ioutil.WriteFile(filepath.Join(targetDir, ".git", "config"), b, 0o600) + err = os.WriteFile(filepath.Join(targetDir, ".git", "config"), b, 0o600) if err != nil { return err } diff --git a/pkg/git/repocache/cache.go b/pkg/git/repocache/cache.go index 76b4aa813..0f9dfcb90 100644 --- a/pkg/git/repocache/cache.go +++ b/pkg/git/repocache/cache.go @@ -9,7 +9,6 @@ import ( git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" ssh_pool "github.com/kluctl/kluctl/v2/pkg/git/ssh-pool" "github.com/kluctl/kluctl/v2/pkg/utils" - "io/ioutil" "os" "path" "path/filepath" @@ -198,7 +197,7 @@ func (e *CacheEntry) GetClonedDir(ref string) (string, git.CheckoutInfo, error) } repoName = strings.ReplaceAll(repoName, "/", "-") - p, err := ioutil.TempDir(tmpDir, repoName) + p, err := os.MkdirTemp(tmpDir, repoName) if err != nil { return "", git.CheckoutInfo{}, err } diff --git a/pkg/registries/registries.go b/pkg/registries/registries.go index f51f99913..731f3cf27 100644 --- a/pkg/registries/registries.go +++ b/pkg/registries/registries.go @@ -15,7 +15,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" - "io/ioutil" + "io" "net/http" "net/http/httputil" "net/url" @@ -180,7 +180,7 @@ func (rh *RegistryHelper) ParseAuthEntriesFromEnv() error { ca_bundle_path := m["CA_BUNDLE"] if ca_bundle_path != "" { ca_bundle_path = utils.ExpandPath(ca_bundle_path) - b, err := ioutil.ReadFile(ca_bundle_path) + b, err := os.ReadFile(ca_bundle_path) if err != nil { return fmt.Errorf("failed to read ca bundle %s: %w", ca_bundle_path, err) } else { @@ -319,7 +319,7 @@ func (rh *RegistryHelper) readCachedResponse(key string) []byte { return nil } - b, err := ioutil.ReadFile(cachePath) + b, err := os.ReadFile(cachePath) if err != nil { return nil } @@ -327,7 +327,7 @@ func (rh *RegistryHelper) readCachedResponse(key string) []byte { res, err := http.ReadResponse(bufio.NewReader(bytes.NewReader(b)), nil) if strings.HasPrefix(res.Header.Get("Content-Type"), "application/json") { - jb, err := ioutil.ReadAll(res.Body) + jb, err := io.ReadAll(res.Body) if err != nil { return nil } @@ -349,7 +349,7 @@ func (rh *RegistryHelper) writeCachedResponse(key string, data []byte) { } } - err := ioutil.WriteFile(cachePath+".tmp", data, 0o600) + err := os.WriteFile(cachePath+".tmp", data, 0o600) if err != nil { status.Warning(rh.ctx, "writeCachedResponse failed: %v", err) return diff --git a/pkg/seal/fetch_cert.go b/pkg/seal/fetch_cert.go index 5c1c28963..6916a1845 100644 --- a/pkg/seal/fetch_cert.go +++ b/pkg/seal/fetch_cert.go @@ -10,7 +10,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/status" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" - "io/ioutil" + "io" v12 "k8s.io/api/core/v1" "k8s.io/client-go/util/cert" ) @@ -68,7 +68,7 @@ func openCertFromController(k *k8s.K8sCluster, namespace, name string) ([]byte, } defer r.Close() - cert, err := ioutil.ReadAll(r) + cert, err := io.ReadAll(r) if err != nil { return nil, err } diff --git a/pkg/vars/vars_loader.go b/pkg/vars/vars_loader.go index 84986f0a1..6a1953644 100644 --- a/pkg/vars/vars_loader.go +++ b/pkg/vars/vars_loader.go @@ -16,7 +16,6 @@ import ( "github.com/kluctl/kluctl/v2/pkg/vars/aws" "github.com/kluctl/kluctl/v2/pkg/vars/vault" "github.com/kluctl/kluctl/v2/pkg/yaml" - "io/ioutil" "k8s.io/apimachinery/pkg/runtime/schema" "os" ) @@ -185,7 +184,7 @@ func (v *VarsLoader) loadGit(varsCtx *VarsCtx, gitFile *types.VarsSourceGit, roo return err } - f, err := ioutil.ReadFile(path) + f, err := os.ReadFile(path) if err != nil { return err } diff --git a/pkg/vars/vars_loader_http.go b/pkg/vars/vars_loader_http.go index 379e1730e..2f70cd260 100644 --- a/pkg/vars/vars_loader_http.go +++ b/pkg/vars/vars_loader_http.go @@ -10,7 +10,6 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" "io" - "io/ioutil" "net/http" "strings" ) @@ -53,7 +52,7 @@ func (v *VarsLoader) doHttp(httpSource *types.VarsSourceHttp, username string, p } defer resp.Body.Close() - respBody, err := ioutil.ReadAll(resp.Body) + respBody, err := io.ReadAll(resp.Body) if err != nil { return nil, "", err } diff --git a/pkg/vars/vars_loader_test.go b/pkg/vars/vars_loader_test.go index 6cac07687..95c8ca022 100644 --- a/pkg/vars/vars_loader_test.go +++ b/pkg/vars/vars_loader_test.go @@ -15,7 +15,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/vars/aws" "github.com/stretchr/testify/assert" - "io/ioutil" + "io" corev1 "k8s.io/api/core/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -28,7 +28,7 @@ import ( ) func newTestDir(t *testing.T) string { - tmp, err := ioutil.TempDir("", "") + tmp, err := os.MkdirTemp("", "") if err != nil { t.Fatal(err) } @@ -74,7 +74,7 @@ func TestVarsLoader_Values(t *testing.T) { func TestVarsLoader_File(t *testing.T) { d := newTestDir(t) - _ = ioutil.WriteFile(filepath.Join(d, "test.yaml"), []byte(`{"test1": {"test2": 42}}`), 0o600) + _ = os.WriteFile(filepath.Join(d, "test.yaml"), []byte(`{"test1": {"test2": 42}}`), 0o600) testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { err := vl.LoadVars(vc, &types.VarsSource{ @@ -89,8 +89,8 @@ func TestVarsLoader_File(t *testing.T) { func TestVarsLoader_FileWithLoad(t *testing.T) { d := newTestDir(t) - _ = ioutil.WriteFile(filepath.Join(d, "test.yaml"), []byte(`{"test1": {"test2": {{ load_template("test2.txt") }}}}`), 0o600) - _ = ioutil.WriteFile(filepath.Join(d, "test2.txt"), []byte(`42`), 0o600) + _ = os.WriteFile(filepath.Join(d, "test.yaml"), []byte(`{"test1": {"test2": {{ load_template("test2.txt") }}}}`), 0o600) + _ = os.WriteFile(filepath.Join(d, "test2.txt"), []byte(`42`), 0o600) testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { err := vl.LoadVars(vc, &types.VarsSource{ @@ -106,8 +106,8 @@ func TestVarsLoader_FileWithLoad(t *testing.T) { func TestVarsLoader_FileWithLoadSubDir(t *testing.T) { d := newTestDir(t) _ = os.Mkdir(filepath.Join(d, "subdir"), 0o700) - _ = ioutil.WriteFile(filepath.Join(d, "test.yaml"), []byte(`{"test1": {"test2": {{ load_template("test2.txt") }}}}`), 0o600) - _ = ioutil.WriteFile(filepath.Join(d, "subdir/test2.txt"), []byte(`42`), 0o600) + _ = os.WriteFile(filepath.Join(d, "test.yaml"), []byte(`{"test1": {"test2": {{ load_template("test2.txt") }}}}`), 0o600) + _ = os.WriteFile(filepath.Join(d, "subdir/test2.txt"), []byte(`42`), 0o600) testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { err := vl.LoadVars(vc, &types.VarsSource{ @@ -122,7 +122,7 @@ func TestVarsLoader_FileWithLoadSubDir(t *testing.T) { func TestVarsLoader_FileWithLoadNotExists(t *testing.T) { d := newTestDir(t) - _ = ioutil.WriteFile(filepath.Join(d, "test.yaml"), []byte(`{"test1": {"test2": {{load_template("test3.txt")}}}}`), 0o600) + _ = os.WriteFile(filepath.Join(d, "test.yaml"), []byte(`{"test1": {"test2": {{load_template("test3.txt")}}}}`), 0o600) testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { err := vl.LoadVars(vc, &types.VarsSource{ @@ -385,7 +385,7 @@ func TestVarsLoader_Http_POST(t *testing.T) { w.WriteHeader(542) return } - body, _ := ioutil.ReadAll(r.Body) + body, _ := io.ReadAll(r.Body) if string(body) != "body" { w.WriteHeader(543) return From a4d30666a09d2ccfa81c05303f99bdec7ef05664 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 17 Oct 2022 22:19:37 +0200 Subject: [PATCH 1064/2916] refactor: Remove unused WithPriority --- pkg/status/status.go | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/pkg/status/status.go b/pkg/status/status.go index 8c8f158c3..5c8675bdd 100644 --- a/pkg/status/status.go +++ b/pkg/status/status.go @@ -14,11 +14,10 @@ type StatusContext struct { finished bool failed bool - prefix string - startMessage string - startPriority int - startTotal int - disableLogs bool + prefix string + startMessage string + startTotal int + disableLogs bool } type EndResult int @@ -101,12 +100,6 @@ func WithStatus(message string, args ...any) Option { } } -func WithPriority(p int) Option { - return func(s *StatusContext) { - s.startPriority = p - } -} - func WithTotal(t int) Option { return func(s *StatusContext) { s.startTotal = t From b34c45e0f69eb83f114ce4c62fffa5135601887f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 17 Oct 2022 22:25:13 +0200 Subject: [PATCH 1065/2916] fix: Fix mixed value and pointer receivers --- pkg/git/git-url/url.go | 2 +- pkg/types/url.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/git/git-url/url.go b/pkg/git/git-url/url.go index 452df12ee..ff3ae35bd 100644 --- a/pkg/git/git-url/url.go +++ b/pkg/git/git-url/url.go @@ -33,7 +33,7 @@ func (u *GitUrl) UnmarshalYAML(unmarshal func(interface{}) error) error { return err } -func (u GitUrl) MarshalYAML() (interface{}, error) { +func (u *GitUrl) MarshalYAML() (interface{}, error) { return u.String(), nil } diff --git a/pkg/types/url.go b/pkg/types/url.go index 703f8dfba..400328b3a 100644 --- a/pkg/types/url.go +++ b/pkg/types/url.go @@ -22,6 +22,6 @@ func (u *YamlUrl) UnmarshalYAML(unmarshal func(interface{}) error) error { return err } -func (u YamlUrl) MarshalYAML() (interface{}, error) { +func (u *YamlUrl) MarshalYAML() (interface{}, error) { return u.String(), nil } From 73ec171bc0138a2821d217bbfbbcb1ff446595c7 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 18 Oct 2022 08:51:13 +0200 Subject: [PATCH 1066/2916] feat: Remove --output-metadata flag --- cmd/kluctl/args/project.go | 3 +-- cmd/kluctl/commands/utils.go | 12 ------------ pkg/kluctl_project/project.go | 7 ------- pkg/types/metadata.go | 5 ----- 4 files changed, 1 insertion(+), 26 deletions(-) delete mode 100644 pkg/types/metadata.go diff --git a/cmd/kluctl/args/project.go b/cmd/kluctl/args/project.go index d142ece1a..a5a29038e 100644 --- a/cmd/kluctl/args/project.go +++ b/cmd/kluctl/args/project.go @@ -3,8 +3,7 @@ package args import "time" type ProjectFlags struct { - ProjectConfig existingFileType `group:"project" short:"c" help:"Location of the .kluctl.yaml config file. Defaults to $PROJECT/.kluctl.yaml" exts:"yml,yaml"` - OutputMetadata string `group:"project" help:"Specify the output path for the project metadata to be written to."` + ProjectConfig existingFileType `group:"project" short:"c" help:"Location of the .kluctl.yaml config file. Defaults to $PROJECT/.kluctl.yaml" exts:"yml,yaml"` Timeout time.Duration `group:"project" help:"Specify timeout for all operations, including loading of the project, all external api calls and waiting for readiness." default:"10m"` GitCacheUpdateInterval time.Duration `group:"project" help:"Specify the time to wait between git cache updates. Defaults to not wait at all and always updating caches."` diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 0b3fe41d8..209052620 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -15,7 +15,6 @@ import ( "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" - "github.com/kluctl/kluctl/v2/pkg/yaml" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd/api" @@ -66,17 +65,6 @@ func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, strictTemplates b return err } - if projectFlags.OutputMetadata != "" { - md := p.GetMetadata() - b, err := yaml.WriteYamlBytes(md) - if err != nil { - return err - } - err = os.WriteFile(projectFlags.OutputMetadata, b, 0o640) - if err != nil { - return err - } - } return cb(ctx, p) } diff --git a/pkg/kluctl_project/project.go b/pkg/kluctl_project/project.go index 0f89a9eb1..29d27077f 100644 --- a/pkg/kluctl_project/project.go +++ b/pkg/kluctl_project/project.go @@ -28,13 +28,6 @@ type LoadedKluctlProject struct { RP *repocache.GitRepoCache } -func (c *LoadedKluctlProject) GetMetadata() *types2.ProjectMetadata { - md := &types2.ProjectMetadata{ - Targets: c.DynamicTargets, - } - return md -} - func (c *LoadedKluctlProject) FindBaseTarget(name string) (*types2.Target, error) { for _, target := range c.Config.Targets { if target.Name == name { diff --git a/pkg/types/metadata.go b/pkg/types/metadata.go deleted file mode 100644 index f734de0ad..000000000 --- a/pkg/types/metadata.go +++ /dev/null @@ -1,5 +0,0 @@ -package types - -type ProjectMetadata struct { - Targets []*DynamicTarget `yaml:"targets"` -} From f464d4999bb42eed2aec9dba91492a8a493cba13 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 18 Oct 2022 11:50:00 +0200 Subject: [PATCH 1067/2916] Revert "fix: Fix mixed value and pointer receivers" This reverts commit b34c45e0f69eb83f114ce4c62fffa5135601887f. --- pkg/git/git-url/url.go | 2 +- pkg/types/url.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/git/git-url/url.go b/pkg/git/git-url/url.go index ff3ae35bd..452df12ee 100644 --- a/pkg/git/git-url/url.go +++ b/pkg/git/git-url/url.go @@ -33,7 +33,7 @@ func (u *GitUrl) UnmarshalYAML(unmarshal func(interface{}) error) error { return err } -func (u *GitUrl) MarshalYAML() (interface{}, error) { +func (u GitUrl) MarshalYAML() (interface{}, error) { return u.String(), nil } diff --git a/pkg/types/url.go b/pkg/types/url.go index 400328b3a..703f8dfba 100644 --- a/pkg/types/url.go +++ b/pkg/types/url.go @@ -22,6 +22,6 @@ func (u *YamlUrl) UnmarshalYAML(unmarshal func(interface{}) error) error { return err } -func (u *YamlUrl) MarshalYAML() (interface{}, error) { +func (u YamlUrl) MarshalYAML() (interface{}, error) { return u.String(), nil } From 6a8160b488e5276514481d4167557335b5e6c32d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 18 Oct 2022 12:04:34 +0200 Subject: [PATCH 1068/2916] refactor: Stop using GetGitTree and instead do a plain clone again Speed improvement was not worth it and it made implementing overrides harder. --- pkg/git/repocache/cache.go | 23 ------------- pkg/kluctl_project/targets.go | 53 ++++-------------------------- pkg/kluctl_project/targets_test.go | 42 +---------------------- 3 files changed, 7 insertions(+), 111 deletions(-) diff --git a/pkg/git/repocache/cache.go b/pkg/git/repocache/cache.go index 0f9dfcb90..e7f6dd11b 100644 --- a/pkg/git/repocache/cache.go +++ b/pkg/git/repocache/cache.go @@ -3,7 +3,6 @@ package repocache import ( "context" "fmt" - "github.com/go-git/go-git/v5/plumbing/object" "github.com/kluctl/kluctl/v2/pkg/git" "github.com/kluctl/kluctl/v2/pkg/git/auth" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" @@ -224,25 +223,3 @@ func (e *CacheEntry) GetClonedDir(ref string) (string, git.CheckoutInfo, error) } return p, repoInfo, nil } - -func (e *CacheEntry) GetGitTree(ref string) (*object.Tree, error) { - e.updateMutex.Lock() - defer e.updateMutex.Unlock() - - err := e.mr.Lock() - if err != nil { - return nil, err - } - defer e.mr.Unlock() - - if ref == "" { - ref = e.defaultRef - } - - _, commit, err := e.findCommit(ref) - if err != nil { - return nil, err - } - - return e.mr.GetGitTreeByCommit(commit) -} diff --git a/pkg/kluctl_project/targets.go b/pkg/kluctl_project/targets.go index 3be7f1d60..0600dae27 100644 --- a/pkg/kluctl_project/targets.go +++ b/pkg/kluctl_project/targets.go @@ -3,14 +3,12 @@ package kluctl_project import ( "fmt" securejoin "github.com/cyphar/filepath-securejoin" - "github.com/go-git/go-git/v5/plumbing/object" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/vars" "github.com/kluctl/kluctl/v2/pkg/yaml" - "io" "os" "regexp" "sort" @@ -20,7 +18,6 @@ import ( type dynamicTargetInfo struct { baseTarget *types.Target dir string - gitTree *object.Tree gitProject *types.GitProject ref *string refPattern *string @@ -169,11 +166,14 @@ func (c *LoadedKluctlProject) prepareDynamicTargetsExternal(baseTarget *types.Ta return nil, err } - gitTree, err := ge.GetGitTree(refShortName) + dir, _, err := ge.GetClonedDir(refShortName) + if err != nil { + return nil, err + } dynamicTargets = append(dynamicTargets, &dynamicTargetInfo{ baseTarget: baseTarget, - gitTree: gitTree, + dir: dir, gitProject: baseTarget.TargetConfig.Project, ref: &refShortName, refPattern: refPattern, @@ -208,40 +208,7 @@ func (c *LoadedKluctlProject) matchRef(s string, pattern string) (bool, string, } } -func (c *LoadedKluctlProject) loadTargetConfigFileFromGit(targetInfo *dynamicTargetInfo) ([]byte, error) { - existsFunc := func(path string) bool { - e, err := targetInfo.gitTree.FindEntry(path) - if e == nil || err != nil { - return false - } - return true - } - - var configFile string - - if targetInfo.baseTarget.TargetConfig.File != nil { - configFile = *targetInfo.baseTarget.TargetConfig.File - } else { - configFile = "target-config.yml" - if !existsFunc(configFile) { - configFile = "target-config.yaml" - } - } - - f, err := targetInfo.gitTree.File(configFile) - if err != nil { - return nil, fmt.Errorf("failed to load target config: %w", err) - } - r, err := f.Reader() - if err != nil { - return nil, fmt.Errorf("failed to load target config: %w", err) - } - defer r.Close() - - return io.ReadAll(r) -} - -func (c *LoadedKluctlProject) loadTargetConfigFileFromLocal(targetInfo *dynamicTargetInfo) ([]byte, error) { +func (c *LoadedKluctlProject) loadTargetConfigFile(targetInfo *dynamicTargetInfo) ([]byte, error) { configFile := yaml.FixNameExt(targetInfo.dir, "target-config.yml") if targetInfo.baseTarget.TargetConfig.File != nil { configFile = *targetInfo.baseTarget.TargetConfig.File @@ -257,14 +224,6 @@ func (c *LoadedKluctlProject) loadTargetConfigFileFromLocal(targetInfo *dynamicT return os.ReadFile(configPath) } -func (c *LoadedKluctlProject) loadTargetConfigFile(targetInfo *dynamicTargetInfo) ([]byte, error) { - if targetInfo.gitTree != nil { - return c.loadTargetConfigFileFromGit(targetInfo) - } else { - return c.loadTargetConfigFileFromLocal(targetInfo) - } -} - func (c *LoadedKluctlProject) buildDynamicTarget(targetInfo *dynamicTargetInfo) (*types.Target, error) { var target types.Target err := utils.DeepCopy(&target, targetInfo.baseTarget) diff --git a/pkg/kluctl_project/targets_test.go b/pkg/kluctl_project/targets_test.go index 628a69d4b..8f1cf30b1 100644 --- a/pkg/kluctl_project/targets_test.go +++ b/pkg/kluctl_project/targets_test.go @@ -1,17 +1,14 @@ package kluctl_project import ( - "github.com/go-git/go-git/v5" - "github.com/go-git/go-git/v5/plumbing/object" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/stretchr/testify/assert" "os" "path/filepath" "testing" - "time" ) -func TestLoadTargetConfigFileFromLocal(t *testing.T) { +func TestLoadTargetConfigFile(t *testing.T) { c := LoadedKluctlProject{} ti := &dynamicTargetInfo{ baseTarget: &types.Target{ @@ -27,40 +24,3 @@ func TestLoadTargetConfigFileFromLocal(t *testing.T) { assert.Equal(t, []byte("test"), data) } - -func TestLoadTargetConfigFileFromGit(t *testing.T) { - dir := t.TempDir() - r, err := git.PlainInit(dir, false) - assert.NoError(t, err) - - wt, err := r.Worktree() - assert.NoError(t, err) - - err = os.WriteFile(filepath.Join(dir, "target-config.yml"), []byte("test"), 0600) - assert.NoError(t, err) - - _, err = wt.Add("target-config.yml") - assert.NoError(t, err) - - h, err := wt.Commit("test", &git.CommitOptions{Author: &object.Signature{Name: "test", Email: "test@test.com", When: time.Now()}}) - assert.NoError(t, err) - - commit, err := object.GetCommit(r.Storer, h) - assert.NoError(t, err) - - gitTree, err := commit.Tree() - assert.NoError(t, err) - - c := LoadedKluctlProject{} - ti := &dynamicTargetInfo{ - baseTarget: &types.Target{ - TargetConfig: &types.ExternalTargetConfig{}, - }, - gitTree: gitTree, - } - - data, err := c.loadTargetConfigFile(ti) - assert.NoError(t, err) - - assert.Equal(t, []byte("test"), data) -} From 530b0268b6aaf6c70009c9af66a02192eb383c96 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 18 Oct 2022 12:05:55 +0200 Subject: [PATCH 1069/2916] chore: Implement status.WarningOnce --- pkg/status/status.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/pkg/status/status.go b/pkg/status/status.go index 5c8675bdd..50dedcdd2 100644 --- a/pkg/status/status.go +++ b/pkg/status/status.go @@ -58,8 +58,9 @@ type StatusHandler interface { type contextKey struct{} type contextValue struct { - slh StatusHandler - deprecation utils.OnceByKey + slh StatusHandler + warningOnce utils.OnceByKey + deprecationOnce utils.OnceByKey } var noopContextValue = contextValue{ @@ -240,6 +241,13 @@ func Warning(ctx context.Context, status string, args ...any) { slh.Warning(fmt.Sprintf(status, args...)) } +func WarningOnce(ctx context.Context, key string, status string, args ...any) { + cv := getContextValue(ctx) + cv.deprecationOnce.Do(key, func() { + Warning(ctx, status, args...) + }) +} + func Trace(ctx context.Context, status string, args ...any) { slh := FromContext(ctx) slh.Trace(fmt.Sprintf(status, args...)) @@ -262,7 +270,7 @@ func Prompt(ctx context.Context, password bool, message string, args ...any) (st func Deprecation(ctx context.Context, key string, message string) { cv := getContextValue(ctx) - cv.deprecation.Do(key, func() { + cv.deprecationOnce.Do(key, func() { cv.slh.Warning(message) }) } From ccade82cd5e48386f79296f1bc3f8b8ec92f4a06 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 18 Oct 2022 12:06:26 +0200 Subject: [PATCH 1070/2916] feat: Implement --local-git-override to allow local testing of external git repos --- cmd/kluctl/args/project.go | 1 + cmd/kluctl/commands/utils.go | 37 ++++++++++++++++++++++++++++++- pkg/git/repocache/cache.go | 43 +++++++++++++++++++++++++++++++----- pkg/vars/vars_loader_test.go | 2 +- 4 files changed, 75 insertions(+), 8 deletions(-) diff --git a/cmd/kluctl/args/project.go b/cmd/kluctl/args/project.go index a5a29038e..8b49faa53 100644 --- a/cmd/kluctl/args/project.go +++ b/cmd/kluctl/args/project.go @@ -7,6 +7,7 @@ type ProjectFlags struct { Timeout time.Duration `group:"project" help:"Specify timeout for all operations, including loading of the project, all external api calls and waiting for readiness." default:"10m"` GitCacheUpdateInterval time.Duration `group:"project" help:"Specify the time to wait between git cache updates. Defaults to not wait at all and always updating caches."` + LocalGitOverride []string `group:"project" help:"Specify a local git override in the form of 'github.com:my-org/my-repo=/local/path/to/override'. This will cause kluctl to not use git to clone for the specified repository but instead use the local directory. This is useful in case you need to test out changes in external git repositories without pushing them. To only override a single branch of the repo, use 'github.com:my-org/my-repo:my-branch=/local/path/to/override'"` } type ArgsFlags struct { diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 209052620..7a49bf371 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -7,6 +7,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/deployment" "github.com/kluctl/kluctl/v2/pkg/git" "github.com/kluctl/kluctl/v2/pkg/git/auth" + git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/git/repocache" ssh_pool "github.com/kluctl/kluctl/v2/pkg/git/ssh-pool" "github.com/kluctl/kluctl/v2/pkg/kluctl_jinja2" @@ -19,6 +20,7 @@ import ( "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd/api" "os" + "strings" ) func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, strictTemplates bool, forCompletion bool, cb func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error) error { @@ -49,7 +51,16 @@ func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, strictTemplates b sshPool := &ssh_pool.SshPool{} - rp := repocache.NewGitRepoCache(ctx, sshPool, auth.NewDefaultAuthProviders(), projectFlags.GitCacheUpdateInterval) + var repoOverrides []repocache.RepoOverride + for _, x := range projectFlags.LocalGitOverride { + ro, err := parseRepoOverride(x) + if err != nil { + return err + } + repoOverrides = append(repoOverrides, ro) + } + + rp := repocache.NewGitRepoCache(ctx, sshPool, auth.NewDefaultAuthProviders(), repoOverrides, projectFlags.GitCacheUpdateInterval) defer rp.Clear() loadArgs := kluctl_project.LoadKluctlProjectArgs{ @@ -192,3 +203,27 @@ func clientConfigGetter(forCompletion bool) func(context *string) (*rest.Config, return restConfig, &rawConfig, nil } } + +func parseRepoOverride(s string) (ret repocache.RepoOverride, err error) { + sp := strings.SplitN(s, "=", 2) + if len(sp) != 2 { + return repocache.RepoOverride{}, fmt.Errorf("invalid --local-git-override %s", s) + } + + sp2 := strings.Split(sp[0], ":") + if len(sp2) < 2 || len(sp2) > 3 { + return repocache.RepoOverride{}, fmt.Errorf("invalid --local-git-override %s", s) + } + + u, err := git_url.Parse(fmt.Sprintf("%s:%s", sp2[0], sp2[1])) + if err != nil { + return repocache.RepoOverride{}, fmt.Errorf("invalid --local-git-override %s: %w", s, err) + } + + ret.RepoKey = u.NormalizedRepoKey() + if len(sp2) == 3 { + ret.Ref = sp2[2] + } + ret.Override = sp[1] + return +} diff --git a/pkg/git/repocache/cache.go b/pkg/git/repocache/cache.go index e7f6dd11b..59e89ef6f 100644 --- a/pkg/git/repocache/cache.go +++ b/pkg/git/repocache/cache.go @@ -7,6 +7,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/git/auth" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" ssh_pool "github.com/kluctl/kluctl/v2/pkg/git/ssh-pool" + "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/utils" "os" "path" @@ -21,8 +22,11 @@ type GitRepoCache struct { authProviders *auth.GitAuthProviders sshPool *ssh_pool.SshPool updateInterval time.Duration - repos map[string]*CacheEntry - reposMutex sync.Mutex + + repos map[string]*CacheEntry + reposMutex sync.Mutex + + repoOverrides []RepoOverride cleanupDirs []string cleeanupDirsMutex sync.Mutex @@ -44,18 +48,25 @@ type RepoInfo struct { DefaultRef string `yaml:"defaultRef"` } +type RepoOverride struct { + RepoKey string + Ref string + Override string +} + type clonedDir struct { dir string info git.CheckoutInfo } -func NewGitRepoCache(ctx context.Context, sshPool *ssh_pool.SshPool, authProviders *auth.GitAuthProviders, updateInterval time.Duration) *GitRepoCache { +func NewGitRepoCache(ctx context.Context, sshPool *ssh_pool.SshPool, authProviders *auth.GitAuthProviders, repoOverrides []RepoOverride, updateInterval time.Duration) *GitRepoCache { return &GitRepoCache{ ctx: ctx, sshPool: sshPool, authProviders: authProviders, updateInterval: updateInterval, repos: map[string]*CacheEntry{}, + repoOverrides: repoOverrides, } } @@ -205,9 +216,29 @@ func (e *CacheEntry) GetClonedDir(ref string) (string, git.CheckoutInfo, error) e.rp.cleanupDirs = append(e.rp.cleanupDirs, p) e.rp.cleeanupDirsMutex.Unlock() - err = e.mr.CloneProjectByCommit(commit, p) - if err != nil { - return "", git.CheckoutInfo{}, err + var foundRo *RepoOverride + for _, ro := range e.rp.repoOverrides { + u := e.mr.Url() + if ro.RepoKey == u.NormalizedRepoKey() { + if ro.Ref == "" || strings.HasSuffix(ref2, "/"+ro.Ref) { + foundRo = &ro + break + } + } + } + + if foundRo != nil { + u := e.mr.Url() + status.WarningOnce(e.rp.ctx, fmt.Sprintf("git-override-%s|%s", foundRo.RepoKey, foundRo.Ref), "Overriding git repo %s with local directory %s", u.String(), foundRo.Override) + err = utils.CopyDir(foundRo.Override, p) + if err != nil { + return "", git.CheckoutInfo{}, err + } + } else { + err = e.mr.CloneProjectByCommit(commit, p) + if err != nil { + return "", git.CheckoutInfo{}, err + } } repoInfo, err := git.GetCheckoutInfo(p) diff --git a/pkg/vars/vars_loader_test.go b/pkg/vars/vars_loader_test.go index 95c8ca022..872b6a6c3 100644 --- a/pkg/vars/vars_loader_test.go +++ b/pkg/vars/vars_loader_test.go @@ -39,7 +39,7 @@ func newTestDir(t *testing.T) string { } func newRP(t *testing.T) *repocache.GitRepoCache { - grc := repocache.NewGitRepoCache(context.TODO(), &ssh_pool.SshPool{}, auth.NewDefaultAuthProviders(), 0) + grc := repocache.NewGitRepoCache(context.TODO(), &ssh_pool.SshPool{}, auth.NewDefaultAuthProviders(), nil, 0) t.Cleanup(func() { grc.Clear() }) From 2733b1b825e7dfbdf7c4b766ff7e97b542506e5e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 18 Oct 2022 13:36:27 +0200 Subject: [PATCH 1071/2916] feat: Remove deprecated "sources" from secretSets --- pkg/types/kluctl_project.go | 20 ++------------------ pkg/vars/vars_loader.go | 4 ---- 2 files changed, 2 insertions(+), 22 deletions(-) diff --git a/pkg/types/kluctl_project.go b/pkg/types/kluctl_project.go index 9fddb42e8..b1e7e5f68 100644 --- a/pkg/types/kluctl_project.go +++ b/pkg/types/kluctl_project.go @@ -1,9 +1,7 @@ package types import ( - "github.com/go-playground/validator/v10" "github.com/kluctl/kluctl/v2/pkg/utils/uo" - "github.com/kluctl/kluctl/v2/pkg/yaml" ) type DynamicArg struct { @@ -44,18 +42,8 @@ type DynamicTarget struct { } type SecretSet struct { - Name string `yaml:"name" validate:"required"` - // TODO deprecated, use vars instead - Sources []*VarsSource `yaml:"sources,omitempty"` - Vars []*VarsSource `yaml:"vars,omitempty"` -} - -func ValidateSecretSet(sl validator.StructLevel) { - s := sl.Current().Interface().(SecretSet) - - if len(s.Sources) != 0 && len(s.Vars) != 0 { - sl.ReportError(s, "vars", "vars", "sources and vars can't be set at the same time", "") - } + Name string `yaml:"name" validate:"required"` + Vars []*VarsSource `yaml:"vars,omitempty"` } type GlobalSealedSecretsConfig struct { @@ -73,7 +61,3 @@ type KluctlProject struct { Targets []*Target `yaml:"targets,omitempty"` SecretsConfig *SecretsConfig `yaml:"secretsConfig,omitempty"` } - -func init() { - yaml.Validator.RegisterStructValidation(ValidateSecretSet, SecretSet{}) -} diff --git a/pkg/vars/vars_loader.go b/pkg/vars/vars_loader.go index 6a1953644..53f004f8f 100644 --- a/pkg/vars/vars_loader.go +++ b/pkg/vars/vars_loader.go @@ -8,7 +8,6 @@ import ( "github.com/kluctl/go-jinja2" "github.com/kluctl/kluctl/v2/pkg/git/repocache" "github.com/kluctl/kluctl/v2/pkg/k8s" - "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/utils" @@ -74,9 +73,6 @@ func (v *VarsLoader) LoadVars(varsCtx *VarsCtx, sourceIn *types.VarsSource, sear if source.Values != nil { v.mergeVars(varsCtx, source.Values, rootKey) return nil - } else if source.Path != nil { - status.Deprecation(v.ctx, "vars-path", "'path' is deprecated as vars source, use 'file' instead") - return v.loadFile(varsCtx, *source.Path, searchDirs, rootKey) } else if source.File != nil { return v.loadFile(varsCtx, *source.File, searchDirs, rootKey) } else if source.Git != nil { From 5a853eba8e95d493026f330c321e8581227d4915 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 18 Oct 2022 13:36:47 +0200 Subject: [PATCH 1072/2916] feat: Remove deprecated "path" from "vars" --- pkg/kluctl_project/target_context.go | 7 +------ pkg/types/vars_source.go | 1 - 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index 3015d4345..60ea9db07 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -186,12 +186,7 @@ func (p *LoadedKluctlProject) loadSecrets(target *types.Target, varsCtx *vars.Va if err != nil { return err } - if len(secretEntry.Sources) != 0 { - status.Deprecation(p.ctx, "secrets-sets-sources", "'sources' in secretSets is deprecated, use 'vars' instead") - err = varsLoader.LoadVarsList(varsCtx, secretEntry.Sources, searchDirs, "secrets") - } else { - err = varsLoader.LoadVarsList(varsCtx, secretEntry.Vars, searchDirs, "secrets") - } + err = varsLoader.LoadVarsList(varsCtx, secretEntry.Vars, searchDirs, "secrets") if err != nil { return err } diff --git a/pkg/types/vars_source.go b/pkg/types/vars_source.go index 89ca64760..af8a235cc 100644 --- a/pkg/types/vars_source.go +++ b/pkg/types/vars_source.go @@ -56,7 +56,6 @@ type VarsSourceVault struct { type VarsSource struct { Values *uo.UnstructuredObject `yaml:"values,omitempty"` File *string `yaml:"file,omitempty"` - Path *string `yaml:"path,omitempty"` Git *VarsSourceGit `yaml:"git,omitempty"` ClusterConfigMap *VarsSourceClusterConfigMapOrSecret `yaml:"clusterConfigMap,omitempty"` ClusterSecret *VarsSourceClusterConfigMapOrSecret `yaml:"clusterSecret,omitempty"` From 48cf6dba60ef11a051cf35c2c1bc54932302c868 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 18 Oct 2022 16:18:20 +0200 Subject: [PATCH 1073/2916] feat: Deprecate dynamicTargets configuration per target --- pkg/kluctl_project/project.go | 16 ---------------- pkg/kluctl_project/target_context.go | 9 --------- pkg/kluctl_project/targets.go | 16 ++++------------ 3 files changed, 4 insertions(+), 37 deletions(-) diff --git a/pkg/kluctl_project/project.go b/pkg/kluctl_project/project.go index 29d27077f..7d36b0872 100644 --- a/pkg/kluctl_project/project.go +++ b/pkg/kluctl_project/project.go @@ -6,7 +6,6 @@ import ( "github.com/kluctl/go-jinja2" "github.com/kluctl/kluctl/v2/pkg/git/repocache" types2 "github.com/kluctl/kluctl/v2/pkg/types" - "strings" ) type LoadedKluctlProject struct { @@ -45,18 +44,3 @@ func (c *LoadedKluctlProject) FindDynamicTarget(name string) (*types2.DynamicTar } return nil, fmt.Errorf("target %s not existent in kluctl project config", name) } - -func (c *LoadedKluctlProject) CheckDynamicArg(target *types2.Target, argName string, argValue any) error { - var dynArg *types2.DynamicArg - for _, x := range target.DynamicArgs { - if x.Name == argName || strings.HasPrefix(argName, x.Name+".") { - dynArg = &x - break - } - } - if dynArg == nil { - return fmt.Errorf("dynamic argument %s is not allowed for target", argName) - } - - return nil -} diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index 60ea9db07..6b91e5fd0 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -134,15 +134,6 @@ func (p *LoadedKluctlProject) buildVars(target *types.Target, offlineK8s bool, e allArgs := uo.New() - if target != nil { - for argName, argValue := range externalArgs.Object { - err = p.CheckDynamicArg(target, argName, argValue) - if err != nil { - return doError(err) - } - } - } - allArgs.Merge(externalArgs) if target != nil { if target.Args != nil { diff --git a/pkg/kluctl_project/targets.go b/pkg/kluctl_project/targets.go index 0600dae27..baa2aa52f 100644 --- a/pkg/kluctl_project/targets.go +++ b/pkg/kluctl_project/targets.go @@ -6,7 +6,6 @@ import ( "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" - "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/vars" "github.com/kluctl/kluctl/v2/pkg/yaml" "os" @@ -245,19 +244,12 @@ func (c *LoadedKluctlProject) buildDynamicTarget(targetInfo *dynamicTargetInfo) return nil, err } + if len(target.DynamicArgs) != 0 { + status.Deprecation(c.ctx, "dynamic-args", "dynamicArgs are deprecated and ignored. The field will be removed in a future kluctl release.") + } + // check and merge args if targetConfig.Args != nil { - err = targetConfig.Args.NewIterator().IterateLeafs(func(it *uo.ObjectIterator) error { - strValue := fmt.Sprintf("%v", it.Value()) - err := c.CheckDynamicArg(&target, it.KeyPath().ToJsonPath(), strValue) - if err != nil { - return err - } - return nil - }) - if err != nil { - return nil, err - } target.Args.Merge(targetConfig.Args) } // We prepend the dynamic images to ensure they get higher priority later From 107155bcfc3bc881bee7759df0ff023fd0396884 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 18 Oct 2022 16:19:25 +0200 Subject: [PATCH 1074/2916] feat: Allow to run without targets (through the use of a no-name target) --- pkg/kluctl_project/target_context.go | 56 ++++++++++++++++------------ 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index 6b91e5fd0..741cccbfe 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -39,11 +39,19 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName s return nil, err } target = t.Target + } else { + target = &types.Target{} + } + + images.PrependFixedImages(target.Images) - images.PrependFixedImages(target.Images) + clientConfig, clusterContext, err := p.loadK8sConfig(target, offlineK8s) + if err != nil { + return nil, err } + target.Context = &clusterContext - varsCtx, clientConfig, clusterContext, err := p.buildVars(target, offlineK8s, externalArgs, forSeal) + varsCtx, err := p.buildVars(target, externalArgs, forSeal) if err != nil { return nil, err } @@ -79,7 +87,7 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName s VarsLoader: varsLoader, RenderDir: renderOutputDir, SealedSecretsDir: p.sealedSecretsDir, - DefaultSealedSecretsOutputPattern: targetName, + DefaultSealedSecretsOutputPattern: target.Name, } d, err := deployment.NewDeploymentProject(dctx, varsCtx, deployment.NewSource(deploymentDir), ".", nil) @@ -104,31 +112,35 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName s return targetCtx, nil } -func (p *LoadedKluctlProject) buildVars(target *types.Target, offlineK8s bool, externalArgs *uo.UnstructuredObject, forSeal bool) (*vars.VarsCtx, *rest.Config, string, error) { - doError := func(err error) (*vars.VarsCtx, *rest.Config, string, error) { - return nil, nil, "", err +func (p *LoadedKluctlProject) loadK8sConfig(target *types.Target, offlineK8s bool) (*rest.Config, string, error) { + if offlineK8s { + return nil, "", nil } - varsCtx := vars.NewVarsCtx(p.J2) - contextName := target.Context var err error var clientConfig *rest.Config - if !offlineK8s { - var restConfig *api.Config - clientConfig, restConfig, err = p.loadArgs.ClientConfigGetter(contextName) - if err != nil { - return doError(err) - } - if contextName == nil { - contextName = &restConfig.CurrentContext - } + var restConfig *api.Config + clientConfig, restConfig, err = p.loadArgs.ClientConfigGetter(contextName) + if err != nil { + return nil, "", err } + if contextName == nil { + contextName = &restConfig.CurrentContext + } + if contextName != nil { + return clientConfig, *contextName, nil + } + return clientConfig, "", nil +} + +func (p *LoadedKluctlProject) buildVars(target *types.Target, externalArgs *uo.UnstructuredObject, forSeal bool) (*vars.VarsCtx, error) { + varsCtx := vars.NewVarsCtx(p.J2) targetVars, err := uo.FromStruct(target) if err != nil { - return doError(err) + return nil, err } varsCtx.UpdateChild("target", targetVars) @@ -148,16 +160,12 @@ func (p *LoadedKluctlProject) buildVars(target *types.Target, offlineK8s bool, e err = deployment.LoadDeploymentArgs(p.ProjectDir, varsCtx, allArgs) if err != nil { - return doError(err) + return nil, err } varsCtx.UpdateChild("args", allArgs) - var contextName2 string - if contextName != nil { - contextName2 = *contextName - } - return varsCtx, clientConfig, contextName2, nil + return varsCtx, nil } func (p *LoadedKluctlProject) findSecretsEntry(name string) (*types.SecretSet, error) { From 09e34f7d7b33e0b76c9b3b375ea549ecee77d043 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 18 Oct 2022 16:23:24 +0200 Subject: [PATCH 1075/2916] refactor: Pass target context params via struct --- cmd/kluctl/commands/utils.go | 17 ++++++++++----- pkg/kluctl_project/target_context.go | 31 +++++++++++++++++++--------- 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 7a49bf371..5e41a4633 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -152,11 +152,18 @@ func withProjectTargetCommandContext(ctx context.Context, args projectTargetComm renderOutputDir = tmpDir } - targetCtx, err := p.NewTargetContext(ctx, - args.targetFlags.Target, args.offlineKubernetes, - args.dryRunArgs == nil || args.dryRunArgs.DryRun || args.forCompletion, - optionArgs2, args.forSeal, images, inclusion, - renderOutputDir) + targetParams := kluctl_project.TargetContextParams{ + TargetName: args.targetFlags.Target, + OfflineK8s: args.offlineKubernetes, + DryRun: args.dryRunArgs == nil || args.dryRunArgs.DryRun || args.forCompletion, + ExternalArgs: optionArgs2, + ForSeal: args.forSeal, + Images: images, + Inclusion: inclusion, + RenderOutputDir: renderOutputDir, + } + + targetCtx, err := p.NewTargetContext(ctx, targetParams) if err != nil { return err } diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index 741cccbfe..41faba801 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -26,15 +26,26 @@ type TargetContext struct { DeploymentCollection *deployment.DeploymentCollection } -func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName string, offlineK8s bool, dryRun bool, externalArgs *uo.UnstructuredObject, forSeal bool, images *deployment.Images, inclusion *utils.Inclusion, renderOutputDir string) (*TargetContext, error) { +type TargetContextParams struct { + TargetName string + OfflineK8s bool + DryRun bool + ExternalArgs *uo.UnstructuredObject + ForSeal bool + Images *deployment.Images + Inclusion *utils.Inclusion + RenderOutputDir string +} + +func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, params TargetContextParams) (*TargetContext, error) { deploymentDir, err := filepath.Abs(p.ProjectDir) if err != nil { return nil, err } var target *types.Target - if targetName != "" { - t, err := p.FindDynamicTarget(targetName) + if params.TargetName != "" { + t, err := p.FindDynamicTarget(params.TargetName) if err != nil { return nil, err } @@ -43,15 +54,15 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName s target = &types.Target{} } - images.PrependFixedImages(target.Images) + params.Images.PrependFixedImages(target.Images) - clientConfig, clusterContext, err := p.loadK8sConfig(target, offlineK8s) + clientConfig, clusterContext, err := p.loadK8sConfig(target, params.OfflineK8s) if err != nil { return nil, err } target.Context = &clusterContext - varsCtx, err := p.buildVars(target, externalArgs, forSeal) + varsCtx, err := p.buildVars(target, params.ExternalArgs, params.ForSeal) if err != nil { return nil, err } @@ -63,7 +74,7 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName s if err != nil { return nil, err } - k, err = k8s.NewK8sCluster(ctx, clientFactory, dryRun) + k, err = k8s.NewK8sCluster(ctx, clientFactory, params.DryRun) if err != nil { s.Failed() return nil, err @@ -73,7 +84,7 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName s varsLoader := vars.NewVarsLoader(ctx, k, p.RP, aws.NewClientFactory()) - if forSeal { + if params.ForSeal { err = p.loadSecrets(target, varsCtx, varsLoader) if err != nil { return nil, err @@ -85,7 +96,7 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName s K: k, RP: p.RP, VarsLoader: varsLoader, - RenderDir: renderOutputDir, + RenderDir: params.RenderOutputDir, SealedSecretsDir: p.sealedSecretsDir, DefaultSealedSecretsOutputPattern: target.Name, } @@ -95,7 +106,7 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, targetName s return nil, err } - c, err := deployment.NewDeploymentCollection(dctx, d, images, inclusion, forSeal) + c, err := deployment.NewDeploymentCollection(dctx, d, params.Images, params.Inclusion, params.ForSeal) if err != nil { return nil, err } From c7d8dfbe647cd67015a8734a764b1a40ece32f80 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 18 Oct 2022 16:39:21 +0200 Subject: [PATCH 1076/2916] feat: Allow to override target name via -T --- cmd/kluctl/args/project.go | 4 +++- cmd/kluctl/commands/utils.go | 18 ++++++++++-------- pkg/kluctl_project/target_context.go | 26 +++++++++++++++++--------- 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/cmd/kluctl/args/project.go b/cmd/kluctl/args/project.go index 8b49faa53..f60f27482 100644 --- a/cmd/kluctl/args/project.go +++ b/cmd/kluctl/args/project.go @@ -16,5 +16,7 @@ type ArgsFlags struct { } type TargetFlags struct { - Target string `group:"project" short:"t" help:"Target name to run command for. Target must exist in .kluctl.yaml."` + Target string `group:"project" short:"t" help:"Target name to run command for. Target must exist in .kluctl.yaml."` + TargetNameOverride string `group:"project" short:"T" help:"Overrides the target name. If -t is used at the same time, then the target will be looked up based on -t and then renamed to the value of -T. If no target is specified via -t, then the no-name target is renamed to the value of -T."` + Context string `group:"project" help:"Overrides the context name specified in the target. If the selected target does not specify a context or the no-name target is used, --context will override the currently active context."` } diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 5e41a4633..cd67132ad 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -153,14 +153,16 @@ func withProjectTargetCommandContext(ctx context.Context, args projectTargetComm } targetParams := kluctl_project.TargetContextParams{ - TargetName: args.targetFlags.Target, - OfflineK8s: args.offlineKubernetes, - DryRun: args.dryRunArgs == nil || args.dryRunArgs.DryRun || args.forCompletion, - ExternalArgs: optionArgs2, - ForSeal: args.forSeal, - Images: images, - Inclusion: inclusion, - RenderOutputDir: renderOutputDir, + TargetName: args.targetFlags.Target, + TargetNameOverride: args.targetFlags.TargetNameOverride, + ContextOverride: args.targetFlags.Context, + OfflineK8s: args.offlineKubernetes, + DryRun: args.dryRunArgs == nil || args.dryRunArgs.DryRun || args.forCompletion, + ExternalArgs: optionArgs2, + ForSeal: args.forSeal, + Images: images, + Inclusion: inclusion, + RenderOutputDir: renderOutputDir, } targetCtx, err := p.NewTargetContext(ctx, targetParams) diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index 41faba801..246762e92 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -27,14 +27,16 @@ type TargetContext struct { } type TargetContextParams struct { - TargetName string - OfflineK8s bool - DryRun bool - ExternalArgs *uo.UnstructuredObject - ForSeal bool - Images *deployment.Images - Inclusion *utils.Inclusion - RenderOutputDir string + TargetName string + TargetNameOverride string + ContextOverride string + OfflineK8s bool + DryRun bool + ExternalArgs *uo.UnstructuredObject + ForSeal bool + Images *deployment.Images + Inclusion *utils.Inclusion + RenderOutputDir string } func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, params TargetContextParams) (*TargetContext, error) { @@ -49,10 +51,16 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, params Targe if err != nil { return nil, err } - target = t.Target + target = &*t.Target } else { target = &types.Target{} } + if params.TargetNameOverride != "" { + target.Name = params.TargetNameOverride + } + if params.ContextOverride != "" { + target.Context = ¶ms.ContextOverride + } params.Images.PrependFixedImages(target.Images) From a1cf0f34af14b32e51610f84667dde2dc8ff8ae2 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 18 Oct 2022 16:44:37 +0200 Subject: [PATCH 1077/2916] tests: Add test for context override --- e2e/contexts_test.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/e2e/contexts_test.go b/e2e/contexts_test.go index 5600f3689..6d9d10c7a 100644 --- a/e2e/contexts_test.go +++ b/e2e/contexts_test.go @@ -116,3 +116,21 @@ func TestContextSwitch(t *testing.T) { p.KluctlMust("deploy", "--yes", "-t", "test1") assertResourceExists(t, defaultCluster2, p.projectName, "ConfigMap/cm") } + +func TestContextOverride(t *testing.T) { + t.Parallel() + + p := prepareContextTest(t, "context-override") + defer p.cleanup() + + p.updateTarget("test1", func(target *uo.UnstructuredObject) { + _ = target.SetNestedField(defaultCluster1.Context, "context") + }) + + p.KluctlMust("deploy", "--yes", "-t", "test1") + assertResourceExists(t, defaultCluster1, p.projectName, "ConfigMap/cm") + assertResourceNotExists(t, defaultCluster2, p.projectName, "ConfigMap/cm") + + p.KluctlMust("deploy", "--yes", "-t", "test1", "--context", defaultCluster2.Context) + assertResourceExists(t, defaultCluster2, p.projectName, "ConfigMap/cm") +} From ae1fcd5f4c82deb64ab88ef21b24dcba7526b8dc Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 18 Oct 2022 16:53:18 +0200 Subject: [PATCH 1078/2916] tests: Add test for no-name targets --- e2e/no_target_test.go | 54 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 e2e/no_target_test.go diff --git a/e2e/no_target_test.go b/e2e/no_target_test.go new file mode 100644 index 000000000..80f419298 --- /dev/null +++ b/e2e/no_target_test.go @@ -0,0 +1,54 @@ +package e2e + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func prepareNoTargetTest(t *testing.T, name string) *testProject { + p := &testProject{} + p.init(t, defaultCluster1, name) + p.mergeKubeconfig(defaultCluster2) + + createNamespace(t, defaultCluster1, p.projectName) + createNamespace(t, defaultCluster2, p.projectName) + + addConfigMapDeployment(p, "cm", map[string]string{ + "targetName": `{{ target.name }}`, + "targetContext": `{{ target.context }}`, + }, resourceOpts{ + name: "cm", + namespace: p.projectName, + }) + + return p +} + +func TestNoTarget(t *testing.T) { + t.Parallel() + + p := prepareNoTargetTest(t, "no-target") + defer p.cleanup() + + p.KluctlMust("deploy", "--yes") + cm := assertResourceExists(t, defaultCluster1, p.projectName, "ConfigMap/cm") + assertResourceNotExists(t, defaultCluster2, p.projectName, "ConfigMap/cm") + assert.Equal(t, map[string]any{ + "targetName": "", + "targetContext": defaultCluster1.Context, + }, cm.Object["data"]) + + p.KluctlMust("deploy", "--yes", "-T", "override-name") + cm = assertResourceExists(t, defaultCluster1, p.projectName, "ConfigMap/cm") + assert.Equal(t, map[string]any{ + "targetName": "override-name", + "targetContext": defaultCluster1.Context, + }, cm.Object["data"]) + + p.KluctlMust("deploy", "--yes", "-T", "override-name", "--context", defaultCluster2.Context) + cm = assertResourceExists(t, defaultCluster2, p.projectName, "ConfigMap/cm") + assert.Equal(t, map[string]any{ + "targetName": "override-name", + "targetContext": defaultCluster2.Context, + }, cm.Object["data"]) +} From 929fec104964e9599907ac221be5f23230b9a999 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 19 Oct 2022 09:18:00 +0200 Subject: [PATCH 1079/2916] feat: Implement kluctl.io/validate-ignore annotation This allows to ignore an object while validation is running. --- pkg/validation/validation.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/validation/validation.go b/pkg/validation/validation.go index 7a3ecb5d2..7a1f1083c 100644 --- a/pkg/validation/validation.go +++ b/pkg/validation/validation.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime/schema" @@ -50,6 +51,10 @@ func ValidateObject(k *k8s.K8sCluster, o *uo.UnstructuredObject, notReadyIsError // We assume all is good in case no validation is performed ret.Ready = true + if utils.ParseBoolOrFalse(o.GetK8sAnnotation("kluctl.io/validate-ignore")) { + return + } + defer func() { if r := recover(); r != nil { if _, ok := r.(*validationFailed); ok { From e9cbef252118b2304274af29e6176ba1c09524b2 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 21 Oct 2022 17:56:27 +0200 Subject: [PATCH 1080/2916] ci: Fix wrong pull_request branch --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9f00b5a06..012806f23 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -4,7 +4,7 @@ on: push: pull_request: branches: - - master + - main jobs: build: From ae017d389cb50009ac80f6588d8467c51496e9b9 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 18 Oct 2022 22:33:41 +0200 Subject: [PATCH 1081/2916] docs: Copy docs from www-kluctl.io project --- docs/_index.md | 93 ++++++ docs/concepts.md | 54 ++++ docs/get-started.md | 119 +++++++ docs/history.md | 25 ++ docs/installation.md | 64 ++++ docs/philosophy.md | 36 +++ docs/reference/_index.md | 8 + docs/reference/commands/_index.md | 14 + docs/reference/commands/common-arguments.md | 106 ++++++ docs/reference/commands/delete.md | 46 +++ docs/reference/commands/deploy.md | 78 +++++ docs/reference/commands/diff.md | 50 +++ .../commands/environment-variables.md | 23 ++ docs/reference/commands/helm-pull.md | 24 ++ docs/reference/commands/helm-update.md | 45 +++ docs/reference/commands/list-images.md | 39 +++ docs/reference/commands/list-targets.md | 28 ++ docs/reference/commands/poke-images.md | 41 +++ docs/reference/commands/prune.md | 39 +++ docs/reference/commands/render.md | 37 +++ docs/reference/commands/seal.md | 42 +++ docs/reference/commands/validate.md | 39 +++ docs/reference/deployments/_index.md | 62 ++++ .../deployments/annotations/_index.md | 7 + .../deployments/annotations/all-resources.md | 91 ++++++ .../deployments/annotations/hooks.md | 23 ++ .../deployments/annotations/kustomization.md | 33 ++ .../deployments/annotations/validation.md | 16 + docs/reference/deployments/deployment-yml.md | 306 ++++++++++++++++++ docs/reference/deployments/helm.md | 174 ++++++++++ docs/reference/deployments/hooks.md | 48 +++ docs/reference/deployments/images.md | 130 ++++++++ docs/reference/deployments/kustomize.md | 17 + docs/reference/deployments/readiness.md | 15 + docs/reference/deployments/tags.md | 83 +++++ docs/reference/kluctl-project/_index.md | 60 ++++ .../kluctl-project/secrets-config/_index.md | 73 +++++ .../kluctl-project/targets/_index.md | 103 ++++++ .../kluctl-project/targets/dynamic-targets.md | 110 +++++++ docs/reference/sealed-secrets.md | 152 +++++++++ docs/reference/templating/_index.md | 53 +++ docs/reference/templating/filters.md | 80 +++++ docs/reference/templating/functions.md | 65 ++++ .../templating/predefined-variables.md | 27 ++ docs/reference/templating/variable-sources.md | 215 ++++++++++++ 45 files changed, 2993 insertions(+) create mode 100644 docs/_index.md create mode 100644 docs/concepts.md create mode 100644 docs/get-started.md create mode 100644 docs/history.md create mode 100644 docs/installation.md create mode 100644 docs/philosophy.md create mode 100644 docs/reference/_index.md create mode 100644 docs/reference/commands/_index.md create mode 100644 docs/reference/commands/common-arguments.md create mode 100644 docs/reference/commands/delete.md create mode 100644 docs/reference/commands/deploy.md create mode 100644 docs/reference/commands/diff.md create mode 100644 docs/reference/commands/environment-variables.md create mode 100644 docs/reference/commands/helm-pull.md create mode 100644 docs/reference/commands/helm-update.md create mode 100644 docs/reference/commands/list-images.md create mode 100644 docs/reference/commands/list-targets.md create mode 100644 docs/reference/commands/poke-images.md create mode 100644 docs/reference/commands/prune.md create mode 100644 docs/reference/commands/render.md create mode 100644 docs/reference/commands/seal.md create mode 100644 docs/reference/commands/validate.md create mode 100644 docs/reference/deployments/_index.md create mode 100644 docs/reference/deployments/annotations/_index.md create mode 100644 docs/reference/deployments/annotations/all-resources.md create mode 100644 docs/reference/deployments/annotations/hooks.md create mode 100644 docs/reference/deployments/annotations/kustomization.md create mode 100644 docs/reference/deployments/annotations/validation.md create mode 100644 docs/reference/deployments/deployment-yml.md create mode 100644 docs/reference/deployments/helm.md create mode 100644 docs/reference/deployments/hooks.md create mode 100644 docs/reference/deployments/images.md create mode 100644 docs/reference/deployments/kustomize.md create mode 100644 docs/reference/deployments/readiness.md create mode 100644 docs/reference/deployments/tags.md create mode 100644 docs/reference/kluctl-project/_index.md create mode 100644 docs/reference/kluctl-project/secrets-config/_index.md create mode 100644 docs/reference/kluctl-project/targets/_index.md create mode 100644 docs/reference/kluctl-project/targets/dynamic-targets.md create mode 100644 docs/reference/sealed-secrets.md create mode 100644 docs/reference/templating/_index.md create mode 100644 docs/reference/templating/filters.md create mode 100644 docs/reference/templating/functions.md create mode 100644 docs/reference/templating/predefined-variables.md create mode 100644 docs/reference/templating/variable-sources.md diff --git a/docs/_index.md b/docs/_index.md new file mode 100644 index 000000000..c6a4cdaa4 --- /dev/null +++ b/docs/_index.md @@ -0,0 +1,93 @@ +--- +title: "Kluctl Documentation" +linkTitle: "Docs" +description: "The missing glue to put together large Kubernetes deployments." +taxonomyCloud: [] +weight: 20 +menu: + main: + weight: 20 +--- + +Kluctl is the missing glue that puts together your (and any third-party) deployments into one large declarative +Kubernetes deployment, while making it fully manageable (deploy, diff, prune, delete, ...) via one unified command +line interface. + +Kluctl tries to be as flexible as possible, while remaining as simple as possible. It reuses established +tools (e.g. Kustomize and Helm), making it possible to re-use a large set of available third-party deployments. + +Kluctl is centered around "targets", which can be a cluster or a specific environment (e.g. test, dev, prod, ...) on one +or multiple clusters. Targets can be deployed, diffed, pruned, deleted, and so on. The idea is to have the same set of +operations for every target, no matter how simple or complex the deployment and/or target is. + +Kluctl does not depend on external operators/controllers and allows to use the same deployment wherever you want, +as long as access to the kluctl project and clusters is available. This means, that you can use it from your +local machine, from your CI/CD pipelines or any automation platform/system that allows to call custom tools. + +Flux support is in alpha state and available via the [flux-kluctl-controller](https://github.com/kluctl/flux-kluctl-controller). + +## Kluctl in Short + + + +| | | +| --- | --- | +| 💪 Kluctl handles all your deployments | You can manage all your deployments with Kluctl, including infrastructure related and your applications. | +| 🪶 Complex or simple, all the same | You can manage complex and simple deployments with Kluctl. Simple deployments are lightweight while complex deployment are easily manageable. | +| 🤖 Native git support | Kluctl has native Git support integrated, meaning that it can easily deploy remote Kluctl projects or externalize parts (e.g. configuration) of your Kluctl project. | +| 🪐 Multiple environments | Deploy the same deployment to multiple environments (dev, test, prod, ...), with flexible differences in configuration. | +| 🌌 Multiple clusters | Manage multiple target clusters (in multiple clouds or bare-metal if you want). | +| 🔩 Configuration and Templating | Kluctl allows to use templating in nearly all places, making it easy to have dynamic configuration. | +| ⎈ Helm and Kustomize | The Helm and Kustomize integrations allow you to reuse plenty of third-party charts and kustomizations. | +| 🔍 See what's different | Always know what the state of your deployments is by being able to run diffs on the whole deployment. | +| 🔎 See what happened | Always know what you actually changed after performing a deployment. | +| 💥 Know what went wrong | Kluctl will show you what part of your deployment failed and why. | +| 👐 Live and let live | Kluctl tries to not interfere with any other tools or operators. This is possible due to it's use of server-side-apply. | +| 🧹 Keep it clean | Keep your clusters clean by issuing regular prune calls. | +| 🔐 Encrypted Secrets | Manage encrypted secrets for multiple target environments and clusters. | + +## What can I do with Kluctl? + +Kluctl allows you to define a Kluctl project, which in turn defines Kluctl +deployments and sub-deployments. Each Kluctl deployment defines Kustomize deployments. + +A Kluctl project also defines targets, which represent your target environments +and/or clusters. + +The Kluctl CLI then allows to deploy, diff, prune, delete, ... your deployments. + +## Where do I start? + +{{% alert title="Get started with Kluctl!" %}} +Following this [guide]({{< ref "docs/get-started" >}}) will just take a couple of minutes to complete: +After installing `kluctl`, you can either check out the [example]({{< ref "docs/guides/examples" >}}) or [tutorials]({{< ref "docs/guides/tutorials" >}}). +{{% /alert %}} + + diff --git a/docs/concepts.md b/docs/concepts.md new file mode 100644 index 000000000..c410e32e1 --- /dev/null +++ b/docs/concepts.md @@ -0,0 +1,54 @@ +--- +title: Core Concepts +description: Core Concepts of Kluctl. +weight: 10 +--- + +These are some core concepts in Kluctl. + +## Kluctl project +The kluctl project defines targets, secret sources and external git projects. +It is defined via the [.kluctl.yaml]({{< ref "docs/reference/kluctl-project" >}}) configuration file. + +The kluctl project can also optionally define where the deployment project and clusters configs are located (external +git projects). + +## Targets +A target defines a target cluster and a set of deployment arguments. Multiple targets can use the same cluster. Targets +allow implementing multi-cluster, multi-environment, multi-customer, ... deployments. + +## Deployments +A [deployment]({{< ref "docs/reference/deployments" >}}) defines which Kustomize deployments and which sub-deployments +to deploy. It also controls the order of deployments. + +Deployments may be configured through deployment arguments, which are typically provided via the targets but might also +be provided through the CLI. + +## Variables +[Variables]({{< ref "docs/reference/templating" >}}) are the main source of configuration. They are either loaded yaml +files or directly defined inside deployments. Each variables file that is loaded has access to all the variables which +were defined before, allowing complex composition of configuration. + +After being loaded, variables are usable through the templating engine at all nearly all places. + +## Templating +All configuration files (including .kluctl.yaml and deployment.yaml) and all Kubernetes manifests involved are processed +through a templating engine. +The [templating engine]({{< ref "docs/reference/templating" >}}) allows simple variable substitution and also complex +control structures (if/else, for loops, ...). + +## Secrets +Secrets are loaded from [external sources]({{< ref "docs/reference/kluctl-project" >}}) and are only available +while [sealing]({{< ref "docs/reference/sealed-secrets" >}}). After the sealing process, only the public-key encrypted +sealed secrets are available. + +## Sealed Secrets +[Sealed Secrets]({{< ref "docs/reference/sealed-secrets" >}}) are based on +[Bitnami's sealed-secrets controller](https://github.com/bitnami-labs/sealed-secrets). Kluctl offers integration of +sealed secrets through the `seal` command. Kluctl allows managing multiple sets of sealed secrets for multiple targets. + +## Unified CLI +The CLI of kluctl is designed to be unified/consistent as much as possible. Most commands are centered around targets +and thus require you to specify the target name (via `-t `). If you remember how one command works, it's easy +to figure out how the others work. Output from all targets based commands is also unified, allowing you to easily see +what will and what did happen. diff --git a/docs/get-started.md b/docs/get-started.md new file mode 100644 index 000000000..d8868313b --- /dev/null +++ b/docs/get-started.md @@ -0,0 +1,119 @@ +--- +title: "Get Started with Kluctl" +linkTitle: "Get Started" +description: "Get Started with Kluctl." +weight: 20 +--- + +This tutorial shows you how to bootstrap Flux to a Kubernetes cluster and deploy a sample application in a GitOps manner. + +## Before you begin + +A few things must be prepared before you actually begin. + +### Get a Kubernetes cluster + +The first step is of course: You need a kubernetes cluster. It doesn't really matter where this cluster is hosted, if +it's a local (e.g. [kind](https://kind.sigs.k8s.io/docs/user/quick-start/)) cluster, managed cluster, or a self-hosted +cluster, kops or kubespray based, AWS, GCE, Azure, ... and so on. kluctl +is completely independent of how Kubernetes is deployed and where it is hosted. + +There is however a minimum Kubernetes version that must be met: 1.20.0. This is due to the heavy use of server-side apply +which was not stable enough in older versions of Kubernetes. + +### Prepare your kubeconfig + +Your local kubeconfig should be configured to have access to the target Kubernetes cluster via a dedicated context. The context +name should match with the name that you want to use for the cluster from now on. Let's assume the name is `test.example.com`, +then you'd have to ensure that the kubeconfig context `test.example.com` is correctly pointing and authorized for this +cluster. + +See [Configure Access to Multiple Clusters](https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/) for documentation +on how to manage multiple clusters with a single kubeconfig. Depending on the Kubernets provisioning/deployment tooling +you used, you might also be able to directly export the context into your local kubeconfig. For example, +[kops](https://github.com/kubernetes/kops/blob/master/docs/cli/kops_export.md) is able to export and merge the kubeconfig +for a given cluster. + +## Objectives + +- Checkout one of the example Kluctl projects +- Deploy to your local cluster +- Change something and re-deploy + +## Install Kluctl + +The `kluctl` command-line interface (CLI) is required to perform deployments. + +To install the CLI with Homebrew run: + +```sh +brew install kluctl/tap/kluctl +``` + +For other installation methods, see the [install documentation]({{< ref "docs/installation" >}}). + +## Clone the kluctl examples + +Clone the example project found at https://github.com/kluctl/kluctl-examples + +```sh +git clone https://github.com/kluctl/kluctl-examples.git +``` + +## Choose one of the examples + +You can choose whatever example you like from the clones repository. We will however continue this guide by referring +to the `simple-helm` example found in that repository. Change the current directory: + +```sh +cd kluctl-examples/simple-helm +``` + +## Create your local cluster + +Create a local cluster with [kind](https://kind.sigs.k8s.io): + +```sh +kind create cluster +``` + +This will update your kubeconfig to contain a context with the name `kind-kind`. By default, all examples will use +the currently active context. + +## Deploy the example + +Now run the following command to deploy the example: + +```sh +kluctl deploy -t simple-helm +``` + +Kluctl will perform a diff first and then ask for your confirmation to deploy it. In this case, you should only see +some objects being newly deployed. + +```sh +kubectl -nsimple-helm get pod +``` + +## Change something and re-deploy + +Now change something inside the deployment project. You could for example add `replicaCount: 2` to `deployment/nginx/helm-values.yml`. +After you have saved your changes, run the deploy command again: + +```sh +kluctl deploy -t simple-helm +``` + +This time it should show your modifications in the diff. Confirm that you want to perform the deployment and then verify +it: + +```sh +kubectl -nsimple-helm get pod +``` + +You should need 2 instances of the nginx POD running now. + +## Where to continue? + +Continue by reading through the [tutorials]({{< ref "docs/guides/tutorials" >}}) and by consulting +the [reference documentation]({{< ref "reference" >}}). diff --git a/docs/history.md b/docs/history.md new file mode 100644 index 000000000..a1baceb2a --- /dev/null +++ b/docs/history.md @@ -0,0 +1,25 @@ +--- +title: "History" +linkTitle: "History" +weight: 40 +description: "The history of kluctl." +--- + +Kluctl was created after multiple incarnations of complex multi-environment (e.g. dev, test, prod) deployments, including everything +from monitoring, persistency and the actual custom services. The philosophy of these deployments was always +"what belongs together, should be put together", meaning that only as much Git repositories were involved as necessary. + +The problems to solve turned out to be always the same: +* Dozens of Helm Charts, kustomize deployments and standalone Kubernetes manifests needed to be orchestrated in a way + that they work together (services need to connect to the correct databases, and so on) +* (Encrypted) Secrets needed to be managed and orchestrated for multiple environments and clusters +* Updates of components was always risky and required keeping track of what actually changed since the last deployment +* Available tools (Helm, Kustomize) were not suitable to solve this on its own in an easy/natural way +* A lot of bash scripting was required to put things together + +When this got more and more complex, and the bash scripts started to become a mess (as "simple" Bash scripts always tend to become), +kluctl was started from scratch. It now tries to solve the mentioned problems and provide a useful set of features (commands) +in a sane and unified way. + +The first versions of kluctl were written in Python, hence the use of Jinja2 templating in kluctl. With version 2.0.0, +kluctl was rewritten in Go. diff --git a/docs/installation.md b/docs/installation.md new file mode 100644 index 000000000..94ae4641a --- /dev/null +++ b/docs/installation.md @@ -0,0 +1,64 @@ +--- +title: "Installation" +linkTitle: "Installation" +weight: 20 +description: "Installing kluctl." +--- + +## Install kluctl + +The kluctl CLI is available as a binary executable for all major platforms, +the binaries can be downloaded form GitHub +[releases page](https://github.com/kluctl/kluctl/releases). + +{{% tabs %}} +{{% tab "Homebrew" %}} + +With [Homebrew](https://brew.sh) for macOS and Linux: + +```sh +brew install kluctl/tap/kluctl +``` + +{{% /tab %}} +{{% tab "bash" %}} + +With [Bash](https://www.gnu.org/software/bash/) for macOS and Linux: + +```sh +curl -s https://kluctl.io/install.sh | bash +``` + +{{% /tab %}} + + +{{% /tabs %}} + + + +## Container images + +A container image with `kluctl` is available on GitHub: + +* `ghcr.io/kluctl/kluctl:` diff --git a/docs/philosophy.md b/docs/philosophy.md new file mode 100644 index 000000000..580b9f397 --- /dev/null +++ b/docs/philosophy.md @@ -0,0 +1,36 @@ +--- +title: "Philosophy" +linkTitle: "Philosophy" +weight: 30 +description: "The philosophy behind kluctl." +--- + +Kluctl tries to follow a few basic ideas and a philosophy. Project and deployments structure, as well as all commands +are centered on these. + +## Be practical +Everything found in kluctl is based on years of experience in daily business, from the perspective of a DevOps Engineer. +Kluctl prefers practicability when possible, trying to make the daily life of a DevOps Engineer as comfortable as possible. + +## Consistent CLI +Commands try to be as consistent as possible, making it easy to remember how they are used. For example, a `diff` is used the same way as a `deploy`. This applies to all sizes and complexities of projects. A simple/single-application deployment is used the same way as a complex one, so that it is easy to switch between projects. + +## Mostly declarative +Kluctl tries to be declarative whenever possible, but loosens this in some cases to stay practical. +For example, hooks, barriers and waitReadiness allows you to control order of deployments in a way that a pure declarative approach would not allow. + +## Predictable and traceable +Always know what will happen (`diff` or `--dry-run`) and always know what happened (output changes done by a command). +There is nothing worse than not knowing what's going to happen when you deploy the current state to prod. Not knowing what happened is on the same level. + +## Live and let live +Kluctl tries to not interfere with any other tools or operators. It achieves this by honoring managed fields in an intelligent way. +Kluctl will never force-apply anything without being told so, it will also always inform you about fields that you lost ownership of. + +## CLI/Client first +Kluctl is centered around a unified command line interface and will always prioritize this. This +guarantees that the DevOps Engineer never looses control, even if automation and/or GitOps style operators are being used. + +## No scripting +Kluctl tries its best to remove the need for scripts (e.g. Bash) around deployments. It tries to remove the need +for external orchestration of deployment order and/or dependencies. diff --git a/docs/reference/_index.md b/docs/reference/_index.md new file mode 100644 index 000000000..06d1ad3b4 --- /dev/null +++ b/docs/reference/_index.md @@ -0,0 +1,8 @@ +--- +title: "Reference" +linkTitle: "Reference" +description: > + Description of configuration files and commands +weight: 110 +--- + diff --git a/docs/reference/commands/_index.md b/docs/reference/commands/_index.md new file mode 100644 index 000000000..438c870aa --- /dev/null +++ b/docs/reference/commands/_index.md @@ -0,0 +1,14 @@ +--- +title: "Commands" +linkTitle: "Commands" +weight: 10 +description: > + Description of available commands. +--- + +kluctl offers a unified command line interface that allows to standardize all your deployments. Every project, +no matter how different it is from other projects, is managed the same way. + +You can always call `kluctl --help` or `kluctl --help` for a help prompt. + +Individual commands are documented in sub-sections. diff --git a/docs/reference/commands/common-arguments.md b/docs/reference/commands/common-arguments.md new file mode 100644 index 000000000..429d78235 --- /dev/null +++ b/docs/reference/commands/common-arguments.md @@ -0,0 +1,106 @@ +--- +title: "Common Arguments" +linkTitle: "Common Arguments" +weight: 1 +description: > + Common arguments +--- + +A few sets of arguments are common between multiple commands. These arguments are still part of the command itself and +must be placed *after* the command name. + +## Global arguments + +These arguments are available for all commands. + + +``` +Global arguments: + --cpu-profile string Enable CPU profiling and write the result to the given path + --debug Enable debug logging + --no-color Disable colored output + --no-update-check Disable update check on startup + +``` + + +## Project arguments + +These arguments are available for all commands that are based on a Kluctl project. +They control where and how to load the kluctl project and deployment project. + + +``` +Project arguments: + Define where and how to load the kluctl project and its components from. + + -a, --arg stringArray Template argument in the form name=value + --cluster string DEPRECATED. Specify/Override cluster + --git-cache-update-interval duration Specify the time to wait between git cache updates. Defaults to not + wait at all and always updating caches. + --local-clusters existingdir DEPRECATED. Local clusters directory. Overrides the project from + .kluctl.yaml + --local-deployment existingdir DEPRECATED. Local deployment directory. Overrides the project from + .kluctl.yaml + --local-sealed-secrets existingdir DEPRECATED. Local sealed-secrets directory. Overrides the project + from .kluctl.yaml + --output-metadata string Specify the output path for the project metadata to be written to. + -c, --project-config existingfile Location of the .kluctl.yaml config file. Defaults to + $PROJECT/.kluctl.yaml + -b, --project-ref string Git ref of the kluctl project. Only used when --project-url was given. + -p, --project-url string Git url of the kluctl project. If not specified, the current + directory will be used instead of a remote Git project + -t, --target string Target name to run command for. Target must exist in .kluctl.yaml. + --timeout duration Specify timeout for all operations, including loading of the project, + all external api calls and waiting for readiness. (default 10m0s) + +``` + + +## Image arguments + +These arguments are available on some target based commands. +They control image versions requested by `images.get_image(...)` [calls]({{< ref "docs/reference/deployments/images#imagesget_image" >}}). + + +``` +Image arguments: + Control fixed images and update behaviour. + + -F, --fixed-image stringArray Pin an image to a given version. Expects + '--fixed-image=image<:namespace:deployment:container>=result' + --fixed-images-file existingfile Use .yaml file to pin image versions. See output of list-images + sub-command or read the documentation for details about the output format + --offline-images Omit contacting image registries and do not query for latest image tags. + -u, --update-images This causes kluctl to prefer the latest image found in registries, based + on the 'latest_image' filters provided to 'images.get_image(...)' calls. + Use this flag if you want to update to the latest versions/tags of all + images. '-u' takes precedence over '--fixed-image/--fixed-images-file', + meaning that the latest images are used even if an older image is given + via fixed images. + +``` + + +## Inclusion/Exclusion arguments + +These arguments are available for some target based commands. +They control inclusion/exclusion based on tags and deployment item pathes. + + +``` +Inclusion/Exclusion arguments: + Control inclusion/exclusion. + + --exclude-deployment-dir stringArray Exclude deployment dir. The path must be relative to the root + deployment project. Exclusion has precedence over inclusion, same as + in --exclude-tag + -E, --exclude-tag stringArray Exclude deployments with given tag. Exclusion has precedence over + inclusion, meaning that explicitly excluded deployments will always + be excluded even if an inclusion rule would match the same deployment. + --include-deployment-dir stringArray Include deployment dir. The path must be relative to the root + deployment project. + -I, --include-tag stringArray Include deployments with given tag. + +``` + diff --git a/docs/reference/commands/delete.md b/docs/reference/commands/delete.md new file mode 100644 index 000000000..d2753f4b4 --- /dev/null +++ b/docs/reference/commands/delete.md @@ -0,0 +1,46 @@ +--- +title: "delete" +linkTitle: "delete" +weight: 10 +description: > + delete command +--- + +## Command + +Usage: kluctl delete [flags] + +Delete a target (or parts of it) from the corresponding cluster +Objects are located based on 'commonLabels', configured in 'deployment.yaml' + +WARNING: This command will also delete objects which are not part of your deployment +project (anymore). It really only decides based on the 'deleteByLabel' labels and does NOT +take the local target/state into account! + + + +## Arguments +The following sets of arguments are available: +1. [project arguments]({{< ref "./common-arguments#project-arguments" >}}) +1. [image arguments]({{< ref "./common-arguments#image-arguments" >}}) +1. [inclusion/exclusion arguments]({{< ref "./common-arguments#inclusionexclusion-arguments" >}}) + +In addition, the following arguments are available: + +``` +Misc arguments: + Command specific arguments. + + -l, --delete-by-label stringArray Override the labels used to find objects for deletion. + --dry-run Performs all kubernetes API calls in dry-run mode. + -o, --output-format stringArray Specify output format and target file, in the format 'format=path'. Format + can either be 'text' or 'yaml'. Can be specified multiple times. The actual + format for yaml is currently not documented and subject to change. + --render-output-dir string Specifies the target directory to render the project into. If omitted, a + temporary directory is used. + -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. + +``` + + +They have the same meaning as described in [deploy](#deploy). diff --git a/docs/reference/commands/deploy.md b/docs/reference/commands/deploy.md new file mode 100644 index 000000000..2114ac54c --- /dev/null +++ b/docs/reference/commands/deploy.md @@ -0,0 +1,78 @@ +--- +title: "deploy" +linkTitle: "deploy" +weight: 10 +description: > + deploy command +--- + +## Command + +Usage: kluctl deploy [flags] + +Deploys a target to the corresponding cluster +This command will also output a diff between the initial state and the state after +deployment. The format of this diff is the same as for the 'diff' command. +It will also output a list of prunable objects (without actually deleting them). + + + +## Arguments +The following sets of arguments are available: +1. [project arguments]({{< ref "./common-arguments#project-arguments" >}}) +1. [image arguments]({{< ref "./common-arguments#image-arguments" >}}) +1. [inclusion/exclusion arguments]({{< ref "./common-arguments#inclusionexclusion-arguments" >}}) + +In addition, the following arguments are available: + +``` +Misc arguments: + Command specific arguments. + + --abort-on-error Abort deploying when an error occurs instead of trying the remaining deployments + --dry-run Performs all kubernetes API calls in dry-run mode. + --force-apply Force conflict resolution when applying. See documentation for details + --force-replace-on-error Same as --replace-on-error, but also try to delete and re-create objects. See + documentation for more details. + --no-wait Don't wait for objects readiness' + -o, --output-format stringArray Specify output format and target file, in the format 'format=path'. Format + can either be 'text' or 'yaml'. Can be specified multiple times. The actual + format for yaml is currently not documented and subject to change. + --readiness-timeout duration Maximum time to wait for object readiness. The timeout is meant per-object. + Timeouts are in the duration format (1s, 1m, 1h, ...). If not specified, a + default timeout of 5m is used. (default 5m0s) + --render-output-dir string Specifies the target directory to render the project into. If omitted, a + temporary directory is used. + --replace-on-error When patching an object fails, try to replace it. See documentation for more + details. + -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. + +``` + + +### --force-apply +kluctl implements deployments via [server-side apply](https://kubernetes.io/reference/using-api/server-side-apply/) +and a custom automatic conflict resolution algorithm. This algurithm is an automatic implementation of the +"[Don't overwrite value, give up management claim](https://kubernetes.io/reference/using-api/server-side-apply/#conflicts)" +method. It should work in most cases, but might still fail. In case of such failure, you can use `--force-apply` to +use the "Overwrite value, become sole manager" strategy instead. + +Please note that this is a risky operation which might overwrite fields which were initially managed by kluctl but were +then overtaken by other managers (e.g. by operators). Always use this option with caution and perform a dry-run +before to ensure nothing unexpected gets overwritten. + +### --replace-on-error +In some situations, patching Kubernetes objects might fail for different reasons. In such cases, you can try +`--replace-on-error` to instruct kluctl to retry with an update operation. + +Please note that this will cause all fields to be overwritten, even if owned by other field managers. + +### --force-replace-on-error +This flag will cause the same replacement attempt on failure as with `--replace-on-error`. In addition, it will fallback +to a delete+recreate operation in case the replace also fails. + +Please note that this is a potentially risky operation, especially when an object carries some kind of important state. + +### --abort-on-error +kluctl does not abort a command when an individual object fails can not be updated. It collects all errors and warnings +and outputs them instead. This option modifies the behaviour to immediately abort the command. diff --git a/docs/reference/commands/diff.md b/docs/reference/commands/diff.md new file mode 100644 index 000000000..3af235dd6 --- /dev/null +++ b/docs/reference/commands/diff.md @@ -0,0 +1,50 @@ +--- +title: "diff" +linkTitle: "diff" +weight: 10 +description: > + diff command +--- + +## Command + +Usage: kluctl diff [flags] + +Perform a diff between the locally rendered target and the already deployed target +The output is by default in human readable form (a table combined with unified diffs). +The output can also be changed to output a yaml file. Please note however that the format +is currently not documented and prone to changes. +After the diff is performed, the command will also search for prunable objects and list them. + + + +## Arguments +The following sets of arguments are available: +1. [project arguments]({{< ref "./common-arguments#project-arguments" >}}) +1. [image arguments]({{< ref "./common-arguments#image-arguments" >}}) +1. [inclusion/exclusion arguments]({{< ref "./common-arguments#inclusionexclusion-arguments" >}}) + +In addition, the following arguments are available: + +``` +Misc arguments: + Command specific arguments. + + --force-apply Force conflict resolution when applying. See documentation for details + --force-replace-on-error Same as --replace-on-error, but also try to delete and re-create objects. See + documentation for more details. + --ignore-annotations Ignores changes in annotations when diffing + --ignore-labels Ignores changes in labels when diffing + --ignore-tags Ignores changes in tags when diffing + -o, --output-format stringArray Specify output format and target file, in the format 'format=path'. Format can + either be 'text' or 'yaml'. Can be specified multiple times. The actual format + for yaml is currently not documented and subject to change. + --render-output-dir string Specifies the target directory to render the project into. If omitted, a + temporary directory is used. + --replace-on-error When patching an object fails, try to replace it. See documentation for more + details. + +``` + + +`--force-apply` and `--replace-on-error` have the same meaning as in [deploy](#deploy). diff --git a/docs/reference/commands/environment-variables.md b/docs/reference/commands/environment-variables.md new file mode 100644 index 000000000..15b7f003c --- /dev/null +++ b/docs/reference/commands/environment-variables.md @@ -0,0 +1,23 @@ +--- +title: "Environment Variables" +linkTitle: "Environment Variables" +weight: 2 +description: > + Controlling Kluctl via environment variables +--- + +In addition to arguments, Kluctl can be controlled via a set of environment variables. + +## Environment variables as arguments +All options/arguments accepted by kluctl can also be specified via environment variables. The name of the environment +variables always start with `KLUCTL_` and end with the option/argument in uppercase and dashes replaced with +underscores. As an example, `--project-url=my-project` can also be specified with the environment variable +`KLUCTL_PROJECT_URL=my-project`. + +## Additional environment variables +A few additional environment variables are supported which do not belong to an option/argument. These are: + +1. `KLUCTL_REGISTRY__HOST`, `KLUCTL_REGISTRY__USERNAME`, and so on. See [registries]({{< ref "docs/reference/deployments/images#supported-image-registries-and-authentication" >}}) for details. +2. `KLUCTL_SSH_DISABLE_STRICT_HOST_KEY_CHECKING`. Disable ssh host key checking when accessing git repositories. +3. `KLUCTL_NO_THREADS`. Do not use multithreading while performing work. This is only useful for debugging purposes. +4. `KLUCTL_IGNORE_DEBUGGER`. Pretend that there is no debugger attached when automatically deciding if multi-threading should be enabled or not. diff --git a/docs/reference/commands/helm-pull.md b/docs/reference/commands/helm-pull.md new file mode 100644 index 000000000..576324be4 --- /dev/null +++ b/docs/reference/commands/helm-pull.md @@ -0,0 +1,24 @@ +--- +title: "helm-pull" +linkTitle: "helm-pull" +weight: 10 +description: > + helm-pull command +--- + +## Command + +Usage: kluctl helm-pull [flags] + +Recursively searches for 'helm-chart.yaml' files and pulls the specified Helm charts +The Helm charts are stored under the sub-directory 'charts/' next to the +'helm-chart.yaml'. These Helm charts are meant to be added to version control so that +pulling is only needed when really required (e.g. when the chart version changes). + + + +See [helm-integration]({{< ref "docs/reference/deployments/helm">}}) for more details. + +## Arguments +The following sets of arguments are available: +1. [project arguments]({{< ref "./common-arguments#project-arguments" >}}) (except `-a`) diff --git a/docs/reference/commands/helm-update.md b/docs/reference/commands/helm-update.md new file mode 100644 index 000000000..a9e71f641 --- /dev/null +++ b/docs/reference/commands/helm-update.md @@ -0,0 +1,45 @@ +--- +title: "helm-update" +linkTitle: "helm-update" +weight: 10 +description: > + helm-update command +--- + +## Command + +Usage: kluctl helm-update [flags] + +Recursively searches for 'helm-chart.yaml' files and checks for new available versions +Optionally performs the actual upgrade and/or add a commit to version control. + + + +## Arguments +The following sets of arguments are available: +1. [project arguments]({{< ref "./common-arguments#project-arguments" >}}) (except `-a`) + +In addition, the following arguments are available: + +``` +Misc arguments: + Command specific arguments. + + --commit Create a git commit for every updated chart + --insecure-skip-tls-verify stringArray Controls skipping of TLS verification. Must be in the form + --insecure-skip-tls-verify=, where + must match the id specified in the helm-chart.yaml. + --key-file stringArray Specify client certificate to use for Helm Repository + authentication. Must be in the form + --key-file=:, where must match + the id specified in the helm-chart.yaml. + --password stringArray Specify password to use for Helm Repository authentication. Must be + in the form --password=:, where + must match the id specified in the helm-chart.yaml. + --upgrade Write new versions into helm-chart.yaml and perform helm-pull afterwards + --username stringArray Specify username to use for Helm Repository authentication. Must be + in the form --username=:, where + must match the id specified in the helm-chart.yaml. + +``` + \ No newline at end of file diff --git a/docs/reference/commands/list-images.md b/docs/reference/commands/list-images.md new file mode 100644 index 000000000..cf56b6af1 --- /dev/null +++ b/docs/reference/commands/list-images.md @@ -0,0 +1,39 @@ +--- +title: "list-images" +linkTitle: "list-images" +weight: 10 +description: > + list-images command +--- + +## Command + +Usage: kluctl list-images [flags] + +Renders the target and outputs all images used via 'images.get_image(...) +The result is a compatible with yaml files expected by --fixed-images-file. + +If fixed images ('-f/--fixed-image') are provided, these are also taken into account, +as described in for the deploy command. + + + +## Arguments +The following sets of arguments are available: +1. [project arguments]({{< ref "./common-arguments#project-arguments" >}}) +1. [image arguments]({{< ref "./common-arguments#image-arguments" >}}) +1. [inclusion/exclusion arguments]({{< ref "./common-arguments#inclusionexclusion-arguments" >}}) + +In addition, the following arguments are available: + +``` +Misc arguments: + Command specific arguments. + + -o, --output stringArray Specify output target file. Can be specified multiple times + --render-output-dir string Specifies the target directory to render the project into. If omitted, a + temporary directory is used. + --simple Output a simplified version of the images list + +``` + diff --git a/docs/reference/commands/list-targets.md b/docs/reference/commands/list-targets.md new file mode 100644 index 000000000..3ef135116 --- /dev/null +++ b/docs/reference/commands/list-targets.md @@ -0,0 +1,28 @@ +--- +title: "list-targets" +linkTitle: "list-targets" +weight: 10 +description: > + list-targets command +--- + +## Command + +Usage: kluctl list-targets [flags] + +Outputs a yaml list with all target, including dynamic targets +Outputs a yaml list with all target, including dynamic targets + + + +## Arguments +The following arguments are available: + +``` +Misc arguments: + Command specific arguments. + + -o, --output stringArray Specify output target file. Can be specified multiple times + +``` + \ No newline at end of file diff --git a/docs/reference/commands/poke-images.md b/docs/reference/commands/poke-images.md new file mode 100644 index 000000000..2a0aa64f2 --- /dev/null +++ b/docs/reference/commands/poke-images.md @@ -0,0 +1,41 @@ +--- +title: "poke-images" +linkTitle: "poke-images" +weight: 10 +description: > + poke-images command +--- + +## Command + +Usage: kluctl poke-images [flags] + +Replace all images in target +This command will fully render the target and then only replace images instead of fully +deploying the target. Only images used in combination with 'images.get_image(...)' are +replaced + + + +## Arguments +The following sets of arguments are available: +1. [project arguments]({{< ref "./common-arguments#project-arguments" >}}) +1. [image arguments]({{< ref "./common-arguments#image-arguments" >}}) +1. [inclusion/exclusion arguments]({{< ref "./common-arguments#inclusionexclusion-arguments" >}}) + +In addition, the following arguments are available: + +``` +Misc arguments: + Command specific arguments. + + --dry-run Performs all kubernetes API calls in dry-run mode. + -o, --output-format stringArray Specify output format and target file, in the format 'format=path'. Format can + either be 'text' or 'yaml'. Can be specified multiple times. The actual format + for yaml is currently not documented and subject to change. + --render-output-dir string Specifies the target directory to render the project into. If omitted, a + temporary directory is used. + -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. + +``` + \ No newline at end of file diff --git a/docs/reference/commands/prune.md b/docs/reference/commands/prune.md new file mode 100644 index 000000000..f019404ac --- /dev/null +++ b/docs/reference/commands/prune.md @@ -0,0 +1,39 @@ +--- +title: "prune" +linkTitle: "prune" +weight: 10 +description: > + prune command +--- + +## Command + +Usage: kluctl prune [flags] + +Searches the target cluster for prunable objects and deletes them + + +## Arguments +The following sets of arguments are available: +1. [project arguments]({{< ref "./common-arguments#project-arguments" >}}) +1. [image arguments]({{< ref "./common-arguments#image-arguments" >}}) +1. [inclusion/exclusion arguments]({{< ref "./common-arguments#inclusionexclusion-arguments" >}}) + +In addition, the following arguments are available: + +``` +Misc arguments: + Command specific arguments. + + --dry-run Performs all kubernetes API calls in dry-run mode. + -o, --output-format stringArray Specify output format and target file, in the format 'format=path'. Format can + either be 'text' or 'yaml'. Can be specified multiple times. The actual format + for yaml is currently not documented and subject to change. + --render-output-dir string Specifies the target directory to render the project into. If omitted, a + temporary directory is used. + -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. + +``` + + +They have the same meaning as described in [deploy](#deploy). diff --git a/docs/reference/commands/render.md b/docs/reference/commands/render.md new file mode 100644 index 000000000..f4869433b --- /dev/null +++ b/docs/reference/commands/render.md @@ -0,0 +1,37 @@ +--- +title: "render" +linkTitle: "render" +weight: 10 +description: > + render command +--- + +## Command + +Usage: kluctl render [flags] + +Renders all resources and configuration files +Renders all resources and configuration files and stores the result in either +a temporary directory or a specified directory. + + + +## Arguments +The following sets of arguments are available: +1. [project arguments]({{< ref "./common-arguments#project-arguments" >}}) +1. [image arguments]({{< ref "./common-arguments#image-arguments" >}}) + +In addition, the following arguments are available: + +``` +Misc arguments: + Command specific arguments. + + --offline-kubernetes Run render in offline mode, meaning that it will not try to connect the target + cluster + --print-all Write all rendered manifests to stdout + --render-output-dir string Specifies the target directory to render the project into. If omitted, a + temporary directory is used. + +``` + \ No newline at end of file diff --git a/docs/reference/commands/seal.md b/docs/reference/commands/seal.md new file mode 100644 index 000000000..e87b720eb --- /dev/null +++ b/docs/reference/commands/seal.md @@ -0,0 +1,42 @@ +--- +title: "seal" +linkTitle: "seal" +weight: 10 +description: > + seal command +--- + +## Command + +Usage: kluctl seal [flags] + +Seal secrets based on target's sealingConfig +Loads all secrets from the specified secrets sets from the target's sealingConfig and +then renders the target, including all files with the '.sealme' extension. Then runs +kubeseal on each '.sealme' file and stores secrets in the directory specified by +'--local-sealed-secrets', using the outputPattern from your deployment project. + +If no '--target' is specified, sealing is performed for all targets. + + + +See [sealed-secrets]({{< ref "docs/reference/sealed-secrets">}}) for more details. + +## Arguments +The following sets of arguments are available: +1. [project arguments]({{< ref "./common-arguments#project-arguments" >}}) (except `-a`) + +In addition, the following arguments are available: + +``` +Misc arguments: + Command specific arguments. + + --cert-file string Use the given certificate for sealing instead of requesting it from the + sealed-secrets controller + --force-reseal Lets kluctl ignore secret hashes found in already sealed secrets and thus forces + resealing of those. + --offline-kubernetes Run seal in offline mode, meaning that it will not try to connect the target cluster + +``` + diff --git a/docs/reference/commands/validate.md b/docs/reference/commands/validate.md new file mode 100644 index 000000000..d27b9f59d --- /dev/null +++ b/docs/reference/commands/validate.md @@ -0,0 +1,39 @@ +--- +title: "validate" +linkTitle: "validate" +weight: 10 +description: > + validate command +--- + +## Command + +Usage: kluctl validate [flags] + +Validates the already deployed deployment +This means that all objects are retrieved from the cluster and checked for readiness. + +TODO: This needs to be better documented! + + + +## Arguments +The following sets of arguments are available: +1. [project arguments]({{< ref "./common-arguments#project-arguments" >}}) +1. [image arguments]({{< ref "./common-arguments#image-arguments" >}}) + +In addition, the following arguments are available: + +``` +Misc arguments: + Command specific arguments. + + -o, --output stringArray Specify output target file. Can be specified multiple times + --render-output-dir string Specifies the target directory to render the project into. If omitted, a + temporary directory is used. + --sleep duration Sleep duration between validation attempts (default 5s) + --wait duration Wait for the given amount of time until the deployment validates + --warnings-as-errors Consider warnings as failures + +``` + diff --git a/docs/reference/deployments/_index.md b/docs/reference/deployments/_index.md new file mode 100644 index 000000000..de97255a3 --- /dev/null +++ b/docs/reference/deployments/_index.md @@ -0,0 +1,62 @@ +--- +title: "Deployments" +linkTitle: "Deployments" +weight: 2 +description: > + Deployments and sub-deployments. +--- + +A deployment project is collection of deployment items and sub-deployments. Deployment items are usually +[Kustomize]({{< ref "./kustomize" >}}) deployments, but can also integrate [Helm Charts]({{< ref "./helm" >}}). + +## Basic structure + +The following visualization shows the basic structure of a deployment project. The entry point of every deployment +project is the `deployment.yaml` file, which then includes further sub-deployments and kustomize deployments. It also +provides some additional configuration required for multiple kluctl features to work as expected. + +As can be seen, sub-deployments can include other sub-deployments, allowing you to structure the deployment project +as you need. + +Each level in this structure recursively adds [tags]({{< ref "./tags" >}}) to each deployed resources, allowing you to control +precisely what is deployed in the future. + +Some visualized files/directories have links attached, follow them to get more information. + +
+-- project-dir/
+   |-- }}">deployment.yaml
+   |-- .gitignore
+   |-- kustomize-deployment1/
+   |   |-- kustomization.yaml
+   |   `-- resource.yaml
+   |-- sub-deployment/
+   |   |-- deployment.yaml
+   |   |-- kustomize-deployment2/
+   |   |   |-- kustomization.yaml
+   |   |   |-- resource1.yaml
+   |   |   `-- ...
+   |   |-- kustomize-deployment3/
+   |   |   |-- kustomization.yaml
+   |   |   |-- resource1.yaml
+   |   |   |-- resource2.yaml
+   |   |   |-- patch1.yaml
+   |   |   `-- ...
+   |   |-- }}">kustomize-with-helm-deployment/
+   |   |   |-- charts/
+   |   |   |   `-- ...
+   |   |   |-- kustomization.yaml
+   |   |   |-- helm-chart.yaml
+   |   |   `-- helm-values.yaml
+   |   `-- subsub-deployment/
+   |       |-- deployment.yaml
+   |       |-- ... kustomize deployments
+   |       `-- ... subsubsub deployments
+   `-- sub-deployment/
+       `-- ...
+
+ +## Order of deployments +Deployments are done in parallel, meaning that there are usually no order guarantees. The only way to somehow control +order, is by placing [barriers]({{< ref "./deployment-yml#barriers" >}}) between kustomize deployments. +You should however not overuse barriers, as they negatively impact the speed of kluctl. diff --git a/docs/reference/deployments/annotations/_index.md b/docs/reference/deployments/annotations/_index.md new file mode 100644 index 000000000..a4187a3e9 --- /dev/null +++ b/docs/reference/deployments/annotations/_index.md @@ -0,0 +1,7 @@ +--- +title: "Annotations" +linkTitle: "Annotations" +weight: 10 +description: > + Annotations usable in Kubernetes resources. +--- diff --git a/docs/reference/deployments/annotations/all-resources.md b/docs/reference/deployments/annotations/all-resources.md new file mode 100644 index 000000000..c77c2604f --- /dev/null +++ b/docs/reference/deployments/annotations/all-resources.md @@ -0,0 +1,91 @@ +--- +title: "All resources" +linkTitle: "All resources" +weight: 1 +description: > + Annotations on all resources +--- + +The following annotations control the behavior of the `deploy` and related commands. + +## Control deploy behavior + +The following annotations control deploy behavior, especially in regard to conflict resolution. + +### kluctl.io/delete +If set to "true", the resource will be deleted at deployment time. Kluctl will not emit an error in case the resource +does not exist. A resource with this annotation does not have to be complete/valid as it is never sent to the Kubernetes +api server. + +### kluctl.io/force-apply +If set to "true", the whole resource will be force-applied, meaning that all fields will be overwritten in case of +field manager conflicts. + +### kluctl.io/force-apply-field +Specifies a [JSON Path](https://goessner.net/articles/JsonPath/) for fields that should be force-applied. Matching +fields will be overwritten in case of field manager conflicts. + +If more than one field needs to be specified, add `-xxx` to the annotation key, where `xxx` is an arbitrary number. + +## Control deletion/pruning + +The following annotations control how delete/prune is behaving. + +### kluctl.io/skip-delete +If set to "true", the annotated resource will not be deleted when [delete]({{< ref "docs/reference/commands/delete" >}}) or +[prune]({{< ref "docs/reference/commands/prune" >}}) is called. + +### kluctl.io/skip-delete-if-tags +If set to "true", the annotated resource will not be deleted when [delete]({{< ref "docs/reference/commands/delete" >}}) or +[prune]({{< ref "docs/reference/commands/prune" >}}) is called and inclusion/exclusion tags are used at the same time. + +This tag is especially useful and required on resources that would otherwise cause cascaded deletions of resources that +do not match the specified inclusion/exclusion tags. Namespaces are the most prominent example of such resources, as +they most likely don't match exclusion tags, but cascaded deletion would still cause deletion of the excluded resources. + +## Control diff behavior + +The following annotations control how diffs are performed. + +### kluctl.io/diff-name +This annotation will override the name of the object when looking for the in-cluster version of an object used for +diffs. This is useful when you are forced to use new names for the same objects whenever the content changes, e.g. +for all kinds of immutable resource types. + +Example (filename job.yaml): +```yaml +apiVersion: batch/v1 +kind: Job +metadata: + name: myjob-{{ load_sha256("job.yaml", 6) }} + annotations: + kluctl.io/diff-name: myjob +spec: + template: + spec: + containers: + - name: hello + image: busybox + command: ["sh", "-c", "echo hello"] + restartPolicy: Never +``` + +Without the `kluctl.io/diff-name` annotation, any change to the `job.yaml` would be treated as a new object in resulting +diffs from various commands. This is due to the inclusion of the file hash in the job name. This would make it very hard +to figure out what exactly changed in an object. + +With the `kluctl.io/diff-name` annotation, kluctl will pick an existing job from the cluster with the same diff-name +and use it for the diff, making it a lot easier to analyze changes. If multiple objects match, the one with the youngest +`creationTimestamp` is chosen. + +Please note that this will not cause old objects (with the same diff-name) to be prunes. You still have to regularely +prune the deployment. + +### kluctl.io/ignore-diff +If set to "true", the whole resource will be ignored while calculating diffs. + +### kluctl.io/ignore-diff-field +Specifies a [JSON Path](https://goessner.net/articles/JsonPath/) for fields that should be ignored while calculating +diffs. + +If more than one field needs to be specified, add `-xxx` to the annotation key, where `xxx` is an arbitrary number. diff --git a/docs/reference/deployments/annotations/hooks.md b/docs/reference/deployments/annotations/hooks.md new file mode 100644 index 000000000..c937cc95e --- /dev/null +++ b/docs/reference/deployments/annotations/hooks.md @@ -0,0 +1,23 @@ +--- +title: "Hooks" +linkTitle: "Hooks" +weight: 2 +description: > + Annotations on hooks +--- +The following annotations control hook execution + +See [hooks]({{< ref "docs/reference/deployments/hooks" >}}) for more details. + +### kluctl.io/hook +Declares a resource to be a hook, which is deployed/executed as described in [hooks]({{< ref "docs/reference/deployments/hooks" >}}). The value of the +annotation determines when the hook is deployed/executed. + +### kluctl.io/hook-weight +Specifies a weight for the hook, used to determine deployment/execution order. + +### kluctl.io/hook-delete-policy +Defines when to delete the hook resource. + +### kluctl.io/hook-wait +Defines whether kluctl should wait for hook-completion. diff --git a/docs/reference/deployments/annotations/kustomization.md b/docs/reference/deployments/annotations/kustomization.md new file mode 100644 index 000000000..8c2f98e0d --- /dev/null +++ b/docs/reference/deployments/annotations/kustomization.md @@ -0,0 +1,33 @@ +--- +title: "Kustomize" +linkTitle: "Kustomize" +weight: 4 +description: > + Annotations on the kustomization.yaml resource +--- + +Even though the `kustomization.yaml` from Kustomize deployments are not really Kubernetes resources (as they are not +really deployed), they have the same structure as Kubernetes resources. This also means that the `kustomization.yaml` +can define metadata and annotations. Through these annotations, additional behavior on the deployment can be controlled. + +Example: +```yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +metadata: + annotations: + kluctl.io/barrier: "true" + kluctl.io/wait-readiness: "true" + +resources: + - deployment.yaml +``` + +### kluctl.io/barrier +If set to `true`, kluctl will wait for all previous objects to be applied (but not necessarily ready). This has the +same effect as [barrier]({{< ref "docs/reference/deployments#barriers" >}}) from deployment projects. + +### kluctl.io/wait-readiness +If set to `true`, kluctl will wait for readiness of all objects from this kustomization project. Readiness is defined +the same as in [hook readiness]({{< ref "docs/reference/deployments/readiness" >}}). diff --git a/docs/reference/deployments/annotations/validation.md b/docs/reference/deployments/annotations/validation.md new file mode 100644 index 000000000..df528c164 --- /dev/null +++ b/docs/reference/deployments/annotations/validation.md @@ -0,0 +1,16 @@ +--- +title: "Validation" +linkTitle: "Validation" +weight: 3 +description: > + Annotations to control validation +--- + +The following annotations influence the [validate]({{< ref "docs/reference/commands/validate" >}}) command. + +### validate-result.kluctl.io/xxx +If this annotation is found on a resource that is checked while validation, the key and the value of the annotation +are added to the validation result, which is then returned by the validate command. + +The annotation key is dynamic, meaning that all annotations that begin with `validate-result.kluctl.io/` are taken +into account. diff --git a/docs/reference/deployments/deployment-yml.md b/docs/reference/deployments/deployment-yml.md new file mode 100644 index 000000000..bb22bac01 --- /dev/null +++ b/docs/reference/deployments/deployment-yml.md @@ -0,0 +1,306 @@ +--- +title: "deployment.yaml" +linkTitle: "deployment.yaml" +weight: 1 +description: > + Structure of deployment.yaml. +--- + +The `deployment.yaml` file is the entrypoint for the deployment project. Included sub-deployments also provide a +`deployment.yaml` file with the same structure as the initial one. + +An example `deployment.yaml` looks like this: +```yaml +sealedSecrets: + outputPattern: "{{ cluster.name }}/{{ args.environment }}" + +deployments: +- path: nginx +- path: my-app +- include: monitoring + +commonLabels: + my.prefix/target: "{{ target.name }}" + my.prefix/deployment-project: my-deployment-project + +args: +- name: environment +``` + +The following sub-chapters describe the available fields in the `deployment.yaml` + +## sealedSecrets +`sealedSecrets` configures how sealed secrets are stored while sealing and located while rendering. +See [Sealed Secrets]({{< ref "docs/reference/sealed-secrets#outputpattern-and-location-of-stored-sealed-secrets" >}}) +for details. + +## deployments + +`deployments` is a list of deployment items. Multiple deployment types are supported, which is documented further down. +Individual deployments are performed in parallel, unless a [barrier](#barriers) is encountered which causes kluctl to +wait for all previous deployments to finish. + +### Kustomize deployments + +Specifies a [kustomize](https://kustomize.io/) deployment. +Please see [Kustomize integration]({{< ref "./kustomize" >}}) for more details. + +Example: +```yaml +deployments: +- path: path/to/deployment1 +- path: path/to/deployment2 + waitReadiness: true +``` + +The `path` must point to a directory relative to the directory containing the `deployment.yaml`. Only directories +that are part of the kluctl project are allowed. The directory must contain a valid `kustomization.yaml`. + +`waitReadiness` is optional and if set to `true` instructs kluctl to wait for readiness of each individual object +of the kustomize deployment. Readiness is defined in [readiness]({{< ref "./readiness" >}}). + +### Includes + +Specifies a sub-deployment project to be included. The included sub-deployment project will inherit many properties +of the parent project, e.g. tags, commonLabels and so on. + +Example: +```yaml +deployments: +- include: path/to/sub-deployment +``` + +The `path` must point to a directory relative to the directory containing the `deployment.yaml`. Only directories +that are part of the kluctl project are allowed. The directory must contain a valid `deployment.yaml`. + +### Git includes + +Specifies an external git project to be included. The project is included the same way with regular includes, except +that the included project can not use/load templates from the parent project. An included project might also include +further git projects. + +Simple example: +```yaml +deployments: +- git: git@github.com/example/example.git +``` + +This will clone the git repository at `git@github.com/example/example.git`, checkout the default branch and include it +into the current project. + +Advanced Example: +```yaml +deployments: +- git: + url: git@github.com/example/example.git + ref: my-branch + subDir: some/sub/dir +``` + +The url specifies the Git url to be cloned and checked out. `ref` is optional and specifies the branch or tag to be used. +If `ref` is omitted, the default branch will be checked out. `subDir` is optional and specifies the sub directory inside +the git repository to include. + +### Barriers +Causes kluctl to wait until all previous kustomize deployments have been applied. This is useful when +upcoming deployments need the current or previous deployments to be finished beforehand. Previous deployments also +include all sub-deployments from included deployments. + +Example: +```yaml +deployments: +- path: kustomizeDeployment1 +- path: kustomizeDeployment2 +- include: subDeployment1 +- barrier: true +# At this point, it's ensured that kustomizeDeployment1, kustomizeDeployment2 and all sub-deployments from +# subDeployment1 are fully deployed. +- path: kustomizeDeployment3 +``` + +## deployments common properties +All entries in `deployments` can have the following common properties: + +### vars (deployment item) +A list of variable sets to be loaded into the templating context, which is then available in all [deployment items](#deployments) +and [sub-deployments](#includes). + +See [templating]({{< ref "docs/reference/templating#vars-from-deploymentyaml" >}}) for more details. + +Example: +```yaml +deployments: +- path: kustomizeDeployment1 + vars: + - file: vars1.yaml + - values: + var1: value1 +- path: kustomizeDeployment2 +# all sub-deployments of this include will have the given variables available in their Jinj2 context. +- include: subDeployment1 + vars: + - file: vars2.yaml +``` + +### tags (deployment item) +A list of tags the deployment should have. See [tags]({{< ref "./tags" >}}) for more details. For includes, this means that all +sub-deployments will get these tags applied to. If not specified, the default tags logic as described in [tags]({{< ref "./tags" >}}) +is applied. + +Example: + +```yaml +deployments: +- path: kustomizeDeployment1 + tags: + - tag1 + - tag2 +- path: kustomizeDeployment2 + tags: + - tag3 +# all sub-deployments of this include will get tag4 applied +- include: subDeployment1 + tags: + - tag4 +``` + +### alwaysDeploy +Forces a deployment to be included everytime, ignoring inclusion/exclusion sets from the command line. +See [Deploying with tag inclusion/exclusion]({{< ref "./tags#deploying-with-tag-inclusionexclusion" >}}) for details. + +```yaml +deployments: +- path: kustomizeDeployment1 + alwaysDeploy: true +- path: kustomizeDeployment2 +``` + +### skipDeleteIfTags +Forces exclusion of a deployment whenever inclusion/exclusion tags are specified via command line. +See [Deleting with tag inclusion/exclusion]({{< ref "./tags#deploying-with-tag-inclusionexclusion" >}}) for details. + +```yaml +deployments: +- path: kustomizeDeployment1 + skipDeleteIfTags: true +- path: kustomizeDeployment2 +``` + +## vars (deployment project) +A list of variable sets to be loaded into the templating context, which is then available in all [deployment items](#deployments) +and [sub-deployments](#includes). + +See [templating]({{< ref "docs/reference/templating#vars-from-deploymentyaml" >}}) for more details. + +## commonLabels +A dictionary of [labels](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/) and values to be +added to all resources deployed by any of the kustomize deployments in this deployment project. + +This feature is mainly meant to make it possible to identify all objects in a kubernetes cluster that were once deployed +through a specific deployment project. + +Consider the following example `deployment.yaml`: +```yaml +deployments: + - path: nginx + - include: sub-deployment1 + +commonLabels: + my.prefix/target: {{ target.name }} + my.prefix/deployment-name: my-deployment-project-name + my.prefix/label-1: value-1 + my.prefix/label-2: value-2 +``` + +Every resource deployed by the kustomize deployment `nginx` will now get the two provided labels attached. All included +sub-deployment projects (e.g. `sub-deployment1`) will also recursively inherit these labels and pass them to further +down. + +In case an included sub-deployment project also contains `commonLabels`, both dictionaries of common labels are merged +inside the included sub-deployment project. In case of conflicts, the included common labels override the inherited. + +The root deployment's `commonLabels` is also used to identify objects to be deleted when performing `kluctl delete` +or `kluctl prune` operations + +Please note that these `commonLabels` are not related to `commonLabels` supported in `kustomization.yaml` files. It was +decided to not rely on this feature but instead attach labels manually to resources right before sending them to +kubernetes. This is due to an [implementation detail](https://github.com/kubernetes-sigs/kustomize/issues/1009) in +kustomize which causes `commonLabels` to also be applied to label selectors, which makes otherwise editable resources +read-only when it comes to `commonLabels`. + +## overrideNamespace +A string that is used as the default namespace for all kustomize deployments which don't have a `namespace` set in their +`kustomization.yaml`. + +## tags (deployment project) +A list of common tags which are applied to all kustomize deployments and sub-deployment includes. + +See [tags]({{< ref "./tags" >}}) for more details. + +## args +A list of arguments that can or must be passed to most kluctl operations. Each of these arguments is then available +in templating via the global `args` object. Only the root `deployment.yaml` can contain such argument definitions. + +An example looks like this: +```yaml +deployments: + - path: nginx + +args: + - name: environment + - name: enable_debug + default: false + - name: complex_arg + default: + my: + nested1: arg1 + nested2: arg2 +``` + +These arguments can then be used in templating, e.g. by using `{{ args.environment }}`. + +When calling kluctl, most of the commands will then require you to specify at least `-a environment=xxx` and optionally +`-a enable_debug=true` + +The following sub chapters describe the fields for argument entries. + +### name +The name of the argument. + +### default +If specified, the argument becomes optional and will use the given value as default when not specified. + +The default value can be an arbitrary yaml value, meaning that it can also be a nested dictionary. In that case, passing +args in nested form will only set the nested value. With the above example of `complex_arg`, running: + +``` +kluctl deploy -t my-target -a my.nested1=override` +``` + +will only modify the value below `my.nested1` and keep the value of `my.nested2`. + +## ignoreForDiff + +A list of objects and fields to ignore while performing diffs. Consider the following example: + +```yaml +deployments: + - ... + +ignoreForDiff: + - group: apps + kind: Deployment + namespace: my-namespace + name: my-deployment + fieldPath: spec.replicas +``` + +This will remove the `spec.replicas` field from every resource that matches the object. +`group`, `kind`, `namespace` and `name` can be omitted, which results in all objects matching. `fieldPath` must be a +valid [JSON Path](https://goessner.net/articles/JsonPath/). `fieldPath` may also be a list of JSON paths. + +The JSON Path implementation used in kluctl has extended support for wildcards in field +names, allowing you to also specify paths like `metadata.labels.my-prefix-*`. + +As an alternative, [annotations]({{< ref "./annotations/all-resources#control-diff-behavior" >}}) can be used to control +diff behavior of individual resources. diff --git a/docs/reference/deployments/helm.md b/docs/reference/deployments/helm.md new file mode 100644 index 000000000..0bc1733d9 --- /dev/null +++ b/docs/reference/deployments/helm.md @@ -0,0 +1,174 @@ +--- +title: "Helm Integration" +linkTitle: "Helm Integration" +weight: 3 +description: > + How Helm is integrated into Kluctl. +--- + +kluctl offers a simple-to-use Helm integration, which allows you to reuse many common third-party Helm Charts. + +The integration is split into 2 parts/steps/layers. The first is the management and pulling of the Helm Charts, while +the second part handles configuration/customization and deployment of the chart. + +Pulled Helm Charts are meant to be added to version control to ensure proper speed and consistency. + +## How it works + +Helm charts are not directly deployed via Helm. Instead, kluctl renders the Helm Chart into a single file and then +hands over the rendered yaml to [kustomize](https://kustomize.io/). Rendering is done in combination with a provided +`helm-values.yaml`, which contains the necessary values to configure the Helm Chart. + +The resulting rendered yaml is then referred by your `kustomization.yaml`, from which point on the +[kustomize integration]({{< ref "docs/reference/sealed-secrets#outputpattern-and-location-of-stored-sealed-secrets" >}}) +takes over. This means, that you can perform all desired customization (patches, namespace override, ...) as if you +provided your own resources via yaml files. + +### Helm hooks + +[Helm Hooks](https://helm.sh/docs/topics/charts_hooks/) are implemented by mapping them +to [kluctl hooks]({{< ref "./hooks" >}}), based on the following mapping table: + +| Helm hook | kluctl hook | +|---------------|---------------------| +| pre-install | pre-deploy-initial | +| post-install | post-deploy-initial | +| pre-delete | Not supported | +| post-delete | Not supported | +| pre-upgrade | pre-deploy-upgrade | +| post-upgrade | post-deploy-upgrade | +| pre-rollback | Not supported | +| post-rollback | Not supported | +| test | Not supported | + +Please note that this is a best effort approach and not 100% compatible to how Helm would run hooks. + +## helm-chart.yaml + +The `helm-chart.yaml` defines where to get the chart from, which version should be pulled, the rendered output file name, +and a few more Helm options. After this file is added to your project, you need to invoke the `helm-pull` command +to pull the Helm Chart into your local project. It is advised to put the pulled Helm Chart into version control, so +that deployments will always be based on the exact same Chart (Helm does not guarantee this when pulling). + +Example `helm-chart.yaml`: + +```yaml +helmChart: + repo: https://charts.bitnami.com/bitnami + chartName: redis + chartVersion: 12.1.1 + skipUpdate: false + releaseName: redis-cache + namespace: "{{ my.jinja2.var }}" + output: helm-rendered.yaml # this is optional +``` + +When running the `helm-pull` command, it will search for all `helm-chart.yaml` files in your project and then pull the +chart from the specified repository with the specified version. The pull chart will then be located in the sub-directory +`charts` below the same directory as the `helm-chart.yaml` + +The same filename that was specified in `output` must then be referred in a `kustomization.yaml` as a normal local +resource. If `output` is omitted, the default value `helm-rendered.yaml` is used and must also be referenced in +`kustomization.yaml`. + +`helmChart` inside `helm-chart.yaml` supports the following fields: + +### repo +The url to the Helm repository where the Helm Chart is located. You can use hub.helm.sh to search for repositories and +charts and then use the repos found there. + +oci based repositories are also supported, for example: +```yaml +helmChart: + repo: oci://r.myreg.io/mycharts/pepper + chartName: pepper + chartVersion: 1.2.3 + releaseName: pepper + namespace: pepper +``` + +### chartName +The name of the chart that can be found in the repository. + +### chartVersion +The version of the chart. + +### skipUpdate +Skip this Helm Chart when the [helm-update]({{< ref "docs/reference/commands/helm-update" >}}) command is called. +If omitted, defaults to `false`. + +### releaseName +The name of the Helm Release. + +### namespace +The namespace that this Helm Chart is going to be deployed to. Please note that this should match the namespace +that you're actually deploying the kustomize deployment to. This means, that either `namespace` in `kustomization.yaml` +or `overrideNamespace` in `deployment.yaml` should match the namespace given here. The namespace should also be existing +already at the point in time when the kustomize deployment is deployed. + +### output +This is the file name into which the Helm Chart is rendered into. Your `kustomization.yaml` should include this same +file. The file should not be existing in your project, as it is created on-the-fly while deploying. + +### skipCRDs +If set to `true`, kluctl will pass `--skip-crds` to Helm when rendering the deployment. If set to `false` (which is +the default), kluctl will pass `--include-crds` to Helm. + +## helm-values.yaml +This file should be present when you need to pass custom Helm Value to Helm while rendering the deployment. Please +read the documentation of the used Helm Charts for details on what is supported. + +## Updates to helm-charts +In case a Helm Chart needs to be updated, you can either do this manually by replacing the [chartVersion](#chartversion) +value in `helm-chart.yaml` and the calling the [helm-pull]({{< ref "docs/reference/commands/helm-pull" >}}) command or by simply invoking +[helm-update]({{< ref "docs/reference/commands/helm-update" >}}) with `--upgrade` and/or `--commit` being set. + +## Private Chart Repositories +It is also possible to use private chart repositories. There are currently two options to provide Helm Repository +credentials to Kluctl. + +### Use `helm repo add --username xxx --password xxx` before +Kluctl will try to find known repositories that are managed by the Helm CLI and then try to reuse the credentials of +these. The repositories are identified by the URL of the repository, so it doesn't matter what name you used when you +added the repository to Helm. The same method can be used for client certificate based authentication (`--key-file` +in `helm repo add`). + +### Use the --username/--password arguments in `kluctl helm-pull` +See the [helm-pull command]({{< ref "docs/reference/commands/helm-pull" >}}). You can control repository credentials +via `--username`, `--password` and `--key-file`. Each argument must be in the form `credentialsId:value`, where +the `credentialsId` must match the id specified in the `helm-chart.yaml`. Example: + +```yaml +helmChart: + repo: https://raw.githubusercontent.com/example/private-helm-repo/main/ + credentialsId: private-helm-repo + chartName: my-chart + chartVersion: 1.2.3 + releaseName: my-chart + namespace: default +``` + +When credentialsId is specified, Kluctl will require you to specify `--username=private-helm-repo:my-username` and +`--password=private-helm-repo:my-password`. You can also specify a client-side certificate instead via +`--key-file=private-helm-repo:/path/to/cert`. + +Multiple Helm Charts can use the same `credentialsId`. + +Environment variables can also be used instead of arguments. See +[Environment Variables]({{< ref "docs/reference/commands/environment-variables" >}}) for details. + +## Templating + +Both `helm-chart.yaml` and `helm-values.yaml` are rendered by the [templating engine]({{< ref "docs/reference/templating" >}}) before they +are actually used. This means, that you can use all available Jinja2 variables at that point, which can for example be +seen in the above `helm-chart.yaml` example for the namespace. + +There is however one exception that leads to a small limitation. When `helm-pull` reads the `helm-chart.yaml`, it does +NOT render the file via the templating engine. This is because it can not know how to properly render the template as it +does have no information about targets (there are no `-t` arguments set) at that point. + +This exception leads to the limitation that the `helm-chart.yaml` MUST be valid yaml even in case it is not rendered +via the templating engine. This makes using control statements (if/for/...) impossible in this file. It also makes it +a requirement to use quotes around values that contain templates (e.g. the namespace in the above example). + +`helm-values.yaml` is not subject to these limitations as it is only interpreted while deploying. diff --git a/docs/reference/deployments/hooks.md b/docs/reference/deployments/hooks.md new file mode 100644 index 000000000..db83a5a5b --- /dev/null +++ b/docs/reference/deployments/hooks.md @@ -0,0 +1,48 @@ +--- +title: "Hooks" +linkTitle: "Hooks" +weight: 4 +description: > + Kluctl hooks. +--- + +Kluctl supports hooks in a similar fashion as known from Helm Charts. Hooks are executed/deployed before and/or after the +actual deployment of a kustomize deployment. + +To mark a resource as a hook, add the `kluctl.io/hook` annotation to a resource. The value of the annotation must be +a comma separated list of hook names. Possible value are described in the next chapter. + +## Hook types + +| Hook Type | Description | +|---|---| +| pre-deploy-initial | Executed right before the initial deployment is performed. | +| post-deploy-initial | Executed right after the initial deployment is performed. | +| pre-deploy-upgrade | Executed right before a non-initial deployment is performed. | +| post-deploy-upgrade | Executed right after a non-initial deployment is performed. | +| pre-deploy | Executed right before any (initial and non-initial) deployment is performed.| +| post-deploy | Executed right after any (initial and non-initial) deployment is performed. | + +A deployment is considered to be an "initial" deployment if none of the resources related to the current kustomize +deployment are found on the cluster at the time of deployment. + +If you need to execute hooks for every deployment, independent of its "initial" state, use +`pre-deploy-initial,pre-deploy` to indicate that it should be executed all the time. + +## Hook deletion + +Hook resources are by default deleted right before creation (if they already existed before). This behavior can be +changed by setting the `kluctl.io/hook-delete-policy` to a comma separated list of the following values: + +| Policy | Description | +|---|---| +| before-hook-creation | The default behavior, which means that the hook resource is deleted right before (re-)creation. | +| hook-succeeded | Delete the hook resource directly after it got "ready" | +| hook-failed | Delete the hook resource when it failed to get "ready" | + +## Hook readiness + +After each deployment/execution of the hooks that belong to a deployment stage (before/after deployment), kluctl +waits for the hook resources to become "ready". Readiness is defined [here]({{< ref "./readiness" >}}). + +It is possible to disable waiting for hook readiness by setting the annotation `kluctl.io/hook-wait` to "false". diff --git a/docs/reference/deployments/images.md b/docs/reference/deployments/images.md new file mode 100644 index 000000000..3c419381c --- /dev/null +++ b/docs/reference/deployments/images.md @@ -0,0 +1,130 @@ +--- +title: "Container Images" +linkTitle: "Container Images" +weight: 3 +description: > + Dynamic configuration of container images. +--- + +There are usually 2 different scenarios where Container Images need to be specified: +1. When deploying third party applications like nginx, redis, ... (e.g. via the [Helm integration]({{< ref "./helm" >}})).
+ * In this case, image versions/tags rarely change, and if they do, this is an explicit change to the deployment. +1. When deploying your own applications.
+ * In this case, image versions/tags might change very rapidly, sometimes multiple times per hour. It would be too much + effort and overhead when this would be managed explicitly via your deployment. Even with Jinja2 templating, this + would be hard to maintain. + +kluctl offers a better solution for the second case. + +## Dynamic versions/tags + +kluctl is able to ask the used container registry for a list of tags/versions available for an image. It then can +sort the list of images via a configurable order and then use the latest image for your deployment. + +It however only does this when the involved resource (e.g. a `Deployment` or `StatefulSet`) is not yet deployed. In case +it is already deployed, the already deployed image will be reused to avoid undesired re-deployment/re-starting of +otherwise unchanged resources. + +## images.get_image() + +This is solved via a templating function that is available in all templates/resources. The function is part of the global +`images` object and expects the following arguments: + +`images.get_image(image, latest_version)` + +* image + * The image location, excluding the tag. Please see [supported image registries](#supported-image-registries-and-authentication) to + understand which registries are supported.` +* latest_version + * Configures how tags/versions are sorted and thus how the latest image is determined. Can be: + * `version.semver()`
+ Filters and sorts by loose semantic versioning. Versions must start with a number. It allows unlimited + `.` inside the version. It treats versions with a suffix as less then versions without a suffix + (e.g. 1.0-rc1 < 1.0). Two versions which only differ by suffix are sorted semantically. + * `version.prefix(prefix)`
+ Only allows tags with the given prefix and then applies the same logic as images.semver() to whatever + follows right after the prefix. You can override the handling of the right part by providing `suffix=xxx`, + while `xxx` is another version filter, e.g. `version.prefix("master-", suffix=version.number()) + * `version.number()`
+ Only allows plain numbers as version numbers sorts them accordingly. + * `version.regex(regex)`
+ Only allows versions/tags that match the given regex. Sorting is done the same way as in version.semver(), + except that versions do not necessarily need to start with a number. + +The mentioned version filters must be specified as strings. For example, + +`images.get_version("my-image", "prefix('master-', suffix=number())")`. + +If no version_filter is specified, then it defaults to `"semver()"`. + +Example deployment: + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment +spec: + template: + spec: + containers: + - name: c1 + image: "{{ images.get_image('registry.gitlab.com/my-group/my-project') }}" +``` + +## Always using the latest images +If you want to use the latest image no matter if an older version is already deployed, use the `-u` flag to your +`deploy`, `diff` or `list-images` commands. + +You can restrict updating to individual images by using `-I/--include-tag`. This is useful when using CI/CD for example, +where you often want to perform a deployment that only updates a single application/service from your large deployment. + +## Fixed images via CLI +The described `images.get_image` logic however leads to a loosely defined state on your target cluster/environment. This +might be fine in a CI/CD environment, but might be undesired when deploying to production. In that case, it might be +desirable to explicitly define which versions need to be deployed. + +To achieve this, you can use the `-F FIXED_IMAGE` [argument]({{< ref "docs/reference/commands/common-arguments#image-arguments" >}}). +`FIXED_IMAGE` must be in the form of `-F image<:namespace:deployment:container>=result`. For example, to pin the image +`registry.gitlab.com/my-group/my-project` to the tag `1.1.2` you'd have to specify +`-F registry.gitlab.com/my-group/my-project=registry.gitlab.com/my-group/my-project:1.1.2`. + +## Fixed images via a yaml file + +As an alternative to specifying each fixed image via CLI (`--fixed-images-file=`), you can also specify a single +yaml file via CLI which then contains a list of entries that define image/deployment -> imageResult mappings. + +An example fixed-images files looks like this: + +```yaml +images: + - image: registry.gitlab.com/my-group/my-project + resultImage: registry.gitlab.com/my-group/my-project:1.1.0 + - image: registry.gitlab.com/my-group/my-project2 + resultImage: registry.gitlab.com/my-group/my-project2:2.0.0 + - deployment: StatefulSet/my-sts + resultImage: registry.gitlab.com/my-group/my-project3:1.0.0 +``` + +You can also take an existing deployment and export the already deployed image versions into a fixed-images file by +using the `list-images` command. It will produce a compatible fixed-images file based on the calls to +`images.get_image` if a deployment would be performed with the given arguments. The result of that call is quite +expressive, as it contains all the information gathered while images were collected. Use `--simple` to only return +a list with image -> resultImage mappings. + +## Supported image registries and authentication +All [v2 API](https://docs.docker.com/registry/spec/api/) based image registries are supported, including the Docker Hub, +Gitlab, and many more. Private registries will need credentials to be setup correctly. This can be done by locally +logging in via `docker login ` or by specifying the following environment variables: + +Simply set the following environment variables to pass credentials to you private repository: +1. KLUCTL_REGISTRY_HOST=registry.example.com +2. KLUCTL_REGISTRY_USERNAME=username +3. KLUCTL_REGISTRY_PASSWORD=password + +You can also pass credentials for more registries by adding an index to the environment variables, +e.g. "KLUCTL_REGISTRY_1_HOST=registry.gitlab.com" + +In case your registry uses self-signed TLS certificates, it is currently required to disable TLS verification for these. +You can do this via `KLUCTL_REGISTRY_TLSVERIFY=1`/`KLUCTL_REGISTRY__TLSVERIFY=1` for the corresponding +`KLUCTL_REGISTRY_HOST`/`KLUCTL_REGISTRY__HOST` or by globally disabling it via `KLUCTL_REGISTRY_DEFAULT_TLSVERIFY=1`. diff --git a/docs/reference/deployments/kustomize.md b/docs/reference/deployments/kustomize.md new file mode 100644 index 000000000..d2099397c --- /dev/null +++ b/docs/reference/deployments/kustomize.md @@ -0,0 +1,17 @@ +--- +title: "Kustomize Integration" +linkTitle: "Kustomize Integration" +weight: 2 +description: > + How Kustomize is integrated into Kluctl +--- + +kluctl uses [kustomize](https://kustomize.io/) to render final resources. This means, that the finest/lowest +level in kluctl is represented with kustomize deployments. These kustomize deployments can then perform further +customization, e.g. patching and more. You can also use kustomize to easily generate ConfigMaps or secrets from files. + +Generally, everything is possible via `kustomization.yaml`, is thus possible in kluctl. + +We advise to read the kustomize +[reference](https://kubectl.docs.kubernetes.io/references/kustomize/). You can also look into the official kustomize +[example](https://github.com/kubernetes-sigs/kustomize/tree/master/examples). diff --git a/docs/reference/deployments/readiness.md b/docs/reference/deployments/readiness.md new file mode 100644 index 000000000..4712fc172 --- /dev/null +++ b/docs/reference/deployments/readiness.md @@ -0,0 +1,15 @@ +--- +title: "Readiness" +linkTitle: "Readiness" +weight: 5 +description: + Definition of readiness. +--- + +There are multiple places where kluctl can wait for "readiness" of resources, e.g. for hooks or when `waitReadiness` is +specified on a deployment item. Readiness depends on the resource kind, e.g. for a Job, kluctl would wait until it +finishes successfully. + +After each deployment/execution of the hooks that belong to a deployment stage (before/after deployment), kluctl +waits for the hook resources to become "ready". Readiness depends on the resource kind, e.g. for a Job, kluctl would +wait until it finishes successfully. diff --git a/docs/reference/deployments/tags.md b/docs/reference/deployments/tags.md new file mode 100644 index 000000000..bf2a00d86 --- /dev/null +++ b/docs/reference/deployments/tags.md @@ -0,0 +1,83 @@ +--- +title: "Tags" +linkTitle: "Tags" +weight: 6 +--- + +Every kustomize deployment has a set of tags assigned to it. These tags are defined in multiple places, which is +documented in [deployment.yaml]({{< ref "./deployment-yml" >}}). Look for the `tags` field, which is available in multiple places per +deployment project. + +Tags are useful when only one or more specific kustomize deployments need to be deployed or deleted. + +## Default tags + +[deployment items]({{< ref "./deployment-yml#deployments" >}}) in deployment projects can have an optional list of tags assigned. + +If this list is completely omitted, one single entry is added by default. This single entry equals to the last element +of the `path` in the `deployments` entry. + +Consider the following example: + +```yaml +deployments: + - path: nginx + - path: some/subdir +``` + +In this example, two kustomize deployments are defined. The first would get the tag `nginx` while the second +would get the tag `subdir`. + +In most cases this heuristic is enough to get proper tags with which you can work. It might however lead to strange +or even conflicting tags (e.g. `subdir` is really a bad tag), in which case you'd have to explicitly set tags. + +## Tag inheritance + +Deployment projects and deployments items inherit the tags of their parents. For example, if a deployment project +has a [tags]({{< ref "./deployment-yml#tags-deployment-project" >}}) property defined, all `deployments` entries would +inherit all these tags. Also, the sub-deployment projects included via deployment items of type +[include]({{< ref "./deployment-yml#includes" >}}) inherit the tags of the deployment project. These included sub-deployments also +inherit the [tags]({{< ref "./deployment-yml#tags-deployment-item" >}}) specified by the deployment item itself. + +Consider the following example `deployment.yaml`: + +```yaml +deployments: + - include: sub-deployment1 + tags: + - tag1 + - tag2 + - include: sub-deployment2 + tags: + - tag3 + - tag4 + - include: subdir/subsub +``` + +Any kustomize deployment found in `sub-deployment1` would now inherit `tag1` and `tag2`. If `sub-deployment1` performs +any further includes, these would also inherit these two tags. Inheriting is additive and recursive. + +The last sub-deployment project in the example is subject to the same default-tags logic as described +in [Default tags](#default-tags), meaning that it will get the default tag `subsub`. + +## Deploying with tag inclusion/exclusion + +Special care needs to be taken when trying to deploy only a specific part of your deployment which requires some base +resources to be deployed as well. + +Imagine a large deployment is able to deploy 10 applications, but you only want to deploy one of them. When using tags +to achieve this, there might be some base resources (e.g. Namespaces) which are needed no matter if everything or just +this single application is deployed. In that case, you'd need to set [alwaysDeploy]({{< ref "./deployment-yml#deployments" >}}) +to `true`. + +## Deleting with tag inclusion/exclusion + +Also, in most cases, even more special care has to be taken for the same types of resources as decribed before. + +Imagine a kustomize deployment being responsible for namespaces deployments. If you now want to delete everything except +deployments that have the `persistency` tag assigned, the exclusion logic would NOT exclude deletion of the namespace. +This would ultimately lead to everything being deleted, and the exclusion tag having no effect. + +In such a case, you'd need to set [skipDeleteIfTags]({{< ref "./deployment-yml#skipdeleteiftags" >}}) to `true` as well. + +In most cases, setting `alwaysDeploy` to `true` also requires setting `skipDeleteIfTags` to `true`. diff --git a/docs/reference/kluctl-project/_index.md b/docs/reference/kluctl-project/_index.md new file mode 100644 index 000000000..32d6bd66b --- /dev/null +++ b/docs/reference/kluctl-project/_index.md @@ -0,0 +1,60 @@ +--- +title: "Kluctl project (.kluctl.yaml)" +linkTitle: ".kluctl.yaml" +weight: 1 +description: > + Kluctl project configuration, found in the .kluctl.yaml file. +--- + +The `.kluctl.yaml` is the central configuration and entry point for your deployments. It defines where the actual +[deployment project]({{< ref "docs/reference/deployments" >}}) is located, +where [sealed secrets]({{< ref "docs/reference/sealed-secrets" >}}) and unencrypted secrets are localed and which targets are available to +invoke [commands]({{< ref "docs/reference/commands" >}}) on. + +## Example + +An example .kluctl.yaml looks like this: + +```yaml +targets: + # test cluster, dev env + - name: dev + context: test.example.com + args: + environment_name: dev + sealingConfig: + secretSets: + - non-prod + # test cluster, test env + - name: test + context: test.example.com + args: + environment_name: test + sealingConfig: + secretSets: + - non-prod + # prod cluster, prod env + - name: prod + context: prod.example.com + args: + environment_name: prod + sealingConfig: + secretSets: + - prod + +# This is only required if you actually need sealed secrets +secretsConfig: + secretSets: + - name: prod + vars: + # This file should not be part of version control! + - file: .secrets-prod.yaml + - name: non-prod + vars: + # This file should not be part of version control! + - file: .secrets-non-prod.yaml +``` + +## Allowed fields + +Please check the sub-sections of this section to see which fields are allowed at the root level of `.kluctl.yaml`. diff --git a/docs/reference/kluctl-project/secrets-config/_index.md b/docs/reference/kluctl-project/secrets-config/_index.md new file mode 100644 index 000000000..4aa9394f1 --- /dev/null +++ b/docs/reference/kluctl-project/secrets-config/_index.md @@ -0,0 +1,73 @@ +--- +title: "secretsConfig" +linkTitle: "secretsConfig" +weight: 4 +description: > + Optional, defines where to load secrets from. +--- + +This configures how secrets are retrieved while sealing. It is basically a list of named secret sets which can be +referenced from targets. + +It has the following form: +```yaml +... +secretsConfig: + secretSets: + - name: + vars: + - ... + sealedSecrets: ... +... +``` + +## secretSets + +Each `secretSets` entry has the following fields. + +### name +This field specifies the name of the secret set. The name can be used in targets to refer to this secret set. + +### vars +A list of variables sources. Check the documentation of +[variables sources]({{< ref "docs/reference/templating/variable-sources" >}}) for details. + +Each variables source must have a root dictionary with the name `secrets` and all the actual secret values +below that dictionary. Every other root key will be ignored. + +Example variables file: + +```yaml +secrets: + secret: value1 + nested: + secret: value2 + list: + - a + - b +... +``` + +## sealedSecrets +This field specifies the configuration for sealing. It has the following form: + +```yaml +... +secretsConfig: + secretSets: ... + sealedSecrets: + bootstrap: true + namespace: kube-system + controllerName: sealed-secrets-controller +... +``` + +### bootstrap +Controls whether kluctl should bootstrap the initial private key in case the controller is not yet installed on +the target cluster. Defaults to `true`. + +### namespace +Specifies the namespace where the sealed-secrets controller is installed. Defaults to "kube-system". + +### controllerName +Specifies the name of the sealed-secrets controller. Defaults to "sealed-secrets-controller". diff --git a/docs/reference/kluctl-project/targets/_index.md b/docs/reference/kluctl-project/targets/_index.md new file mode 100644 index 000000000..63951d5f0 --- /dev/null +++ b/docs/reference/kluctl-project/targets/_index.md @@ -0,0 +1,103 @@ +--- +title: "targets" +linkTitle: "targets" +weight: 4 +description: > + Required, defines targets for this kluctl project. +--- + +Specifies a list of targets for which commands can be invoked. A target puts together environment/target specific +configuration and the target cluster. Multiple targets can exist which target the same cluster but with differing +configuration (via `args`). Target entries also specifies which secrets to use while [sealing]({{< ref "docs/reference/sealed-secrets" >}}). + +Each value found in the target definition is rendered with a simple Jinja2 context that only contains the target itself. +The rendering process is retried 10 times until it finally succeeds, allowing you to reference +the target itself in complex ways. This is especially useful when using [dynamic targets]({{< ref "./dynamic-targets" >}}). + +Target entries have the following form: +```yaml +targets: +... + - name: + context: + args: + arg1: + arg2: + ... + dynamicArgs: + - name: + ... + images: + - image: my-image + resultImage: my-image:1.2.3 + sealingConfig: + secretSets: + - +... +``` + +The following fields are allowed per target: + +## name +This field specifies the name of the target. The name must be unique. It is referred in all commands via the +[-t]({{< ref "docs/reference/commands/common-arguments" >}}) option. + +## context +This field specifies the kubectl context of the target cluster. The context must exist in the currently active kubeconfig. +If this field is omitted, Kluctl will always use the currently active context. + +## args +This fields specifies a map of arguments to be passed to the deployment project when it is rendered. Allowed argument names +are configured via [deployment args]({{< ref "docs/reference/deployments/deployment-yml#args" >}}). + +The arguments specified in the [dynamic target config]({{< ref "docs/reference/kluctl-project/targets/dynamic-targets#args" >}}) +have higher priority. + +## dynamicArgs +This field specifies a list of CLI arguments that can be passed to kluctl when performing any commands on the target. These +arguments are passed with `-a arg_name=arg_value` when for example calling `kluctl deploy -t target_name`. + +Each entry has the following fields: + +## images +This field specifies a list of fixed images to be used by [`images.get_image(...)`]({{< ref "docs/reference/deployments/images#imagesget_image" >}}). +The format is identical to the [fixed images file](https://kluctl.io/docs/reference/deployments/images/#fixed-images-via-a-yaml-file). + +The fixed images specified in the [dynamic target config]({{< ref "docs/reference/kluctl-project/targets/dynamic-targets#images" >}}) +have higher priority. + +### name +The name of the argument. + +## sealingConfig +This field configures how sealing is performed when the [seal command] ({{< ref "docs/reference/commands/seal" >}}) is invoked for this target. +It has the following form: + +```yaml +targets: +... +- name: + ... + sealingConfig: + args: + arg1: + certFile: + dynamicSealing: + secretSets: + - +``` + +### args +This field allows adding extra arguments to the target args. These are only used while sealing and may override +arguments which are already configured for the target. + +### certFile +Optional path to a local (inside your project) public certificate used for sealing. Such a certificate can be fetched +from the sealed-secrets controller using `kubeseal --fetch-cert`. + +### dynamicSealing +This field specifies weather sealing should happen per [dynamic target]({{< ref "./dynamic-targets" >}}) or only once. This +field is optional and defaults to `true`. + +### secretSets +This field specifies a list of secret set names, which all must exist in the [secretsConfig]({{< ref "../secrets-config" >}}). diff --git a/docs/reference/kluctl-project/targets/dynamic-targets.md b/docs/reference/kluctl-project/targets/dynamic-targets.md new file mode 100644 index 000000000..f15ed7506 --- /dev/null +++ b/docs/reference/kluctl-project/targets/dynamic-targets.md @@ -0,0 +1,110 @@ +--- +title: "Dynamic Targets" +linkTitle: "Dynamic Targets" +weight: 1 +description: > + Dynamically defined targets. +--- + +Targets can also be "dynamic", meaning that additional configuration can be sourced from another git repository. +This can be based on a single target repository and branch, or on a target repository and branch/ref pattern, resulting +in multiple dynamic targets being created from one target definition. + +Please note that a single entry in `target` might end up with multiple dynamic targets, meaning that the name must be +made unique between these dynamic targets. This can be achieved by using templating in the `name` field. As an example, +`{{ target.targetConfig.ref }}` can be used to set the target name to the branch name of the dynamic target. + +Dynamic targets have the following form: +```yaml +targets: +... + - name: + context: + args: ... + arg1: + arg2: + ... + targetConfig: + project: + url: + ref: + refPattern: + file: + sealingConfig: + dynamicSealing: + secretSets: + - +... +``` + +All fields known from normal targets are allowed. In addition, the targetConfig with following fields is available. + +## targetConfig + +The presence of this field causes the target to become a dynamic target. +It specifies where to look for dynamic targets and their addional configuration. It has the following form: + +```yaml +... +targets: +... +- name: + ... + targetConfig: + project: + url: + ref: + refPattern: +... +``` + +### project.url +This field specifies the git clone url of the target configuration project. + +### ref +This field specifies the branch or tag to use. If this field is specified, using `refPattern` is forbidden. +This will result in one single dynamic target. + +### refPattern +This field specifies a regex pattern to use when looking for candidate branches and tags. If this is specified, +using `ref` is forbidden. This will result in multiple dynamic targets. Each dynamic target will have `ref` set to +the actual branch name it belong to. This allows using of `{{ target.targetConfig.ref }}` in all other target fields. + +### file +This field specifies the config file name to read externalized target config from. + +## Format of the target config +The target config file referenced in `targetConfig` must be of the following format: + +```yaml +args: + arg1: value1 + arg2: value2 +images: + - image: registry.gitlab.com/my-group/my-project + resultImage: registry.gitlab.com/my-group/my-project:1.1.0 +``` + +### args +An optional map of arguments, in the same format as in the normal [target args]({{< ref "docs/reference/kluctl-project/targets/#args" >}}). + +The arguments specified here have higher priority. + +### images +An optional list of fixed images, in the same format as in the normal [target images]({{< ref "docs/reference/kluctl-project/targets/#images" >}}) + +## Simple dynamic targets + +A simplified form of dynamic targets is to store target config inside the same directory/project as the `.kluctl.yaml`. +This can be done by omitting `project`, `ref` and `refPattern` from `targetConfig` and only specify `file`. + +## A note on sealing + +When sealing dynamic targets, it is very likely that it is not known yet which dynamic targets will actually exist in +the future. This requires some special care when sealing secrets for these targets. Sealed secrets are usually namespace +scoped, which might need to be changed to cluster-wide scoping so that the same sealed secret can be deployed into +multiple targets (assuming you deploy to different namespaces for each target). When you do this, watch out to not +compromise security, e.g. by sealing production level secrets with a cluster-wide scope! + +It is also very likely required to set `target.sealingConfig.dynamicSealing` to `false`, so that sealing is only performed +once and not for all dynamic targets. \ No newline at end of file diff --git a/docs/reference/sealed-secrets.md b/docs/reference/sealed-secrets.md new file mode 100644 index 000000000..fd9bf6fb3 --- /dev/null +++ b/docs/reference/sealed-secrets.md @@ -0,0 +1,152 @@ +--- +title: "Sealed Secrets" +linkTitle: "Sealed Secrets" +weight: 2 +description: > + Sealed Secrets integration +--- + +kluctl has an integration for [sealed secrets](https://github.com/bitnami-labs/sealed-secrets), allowing you to +securely store secrets for multiple target clusters and/or environments inside version control. + +The integration consists of two parts: +1. Sealing of secrets +1. Automatically choosing and deploying the correct sealed secrets for a target + +## Requirements + +The Sealed Secrets integration relies on the [sealed-secrets operator](https://github.com/bitnami-labs/sealed-secrets) +being installed. Installing the operator is the responsibility of you (or whoever is managing/operating the cluster). + +Kluctl can however perform sealing of secrets without an existing sealed-secrets operator installation. This is solved +by automatically pre-provisioning a key onto the cluster that is compatible with the operator or by providing the +public certificate via `certFile` in the targets [sealingConfig]({{< ref "docs/reference/kluctl-project/targets#certfile" >}}). + +## Sealing of .sealme files + +Sealing is done via the [seal command]({{< ref "docs/reference/commands/seal" >}}). It must be done before the actual +deployment is performed. + +The `seal` command recursively searches for files that end with `.sealme`, renders them with the +[templating engine]({{< ref "docs/reference/templating" >}}) engine. The rendered secret resource is then +converted/encrypted into a sealed secret. + +The `.sealme` files itself have to be [Kubernetes Secrets](https://kubernetes.io/docs/concepts/configuration/secret/), +but without any actual secret data inside. The secret data is referenced via templating variables and is expected to be +provided only at the time of sealing. This means, that the sensitive secret data must only be in clear text while sealing. +Afterwards the sealed secrets can be added to version control. + +Example file (the name could be for example `db-secrets.yaml.sealme`): +```yaml +kind: Secret +apiVersion: v1 +metadata: + name: db-secrets + namespace: {{ my.namespace.variable }} +stringData: + DB_URL: {{ secrets.database.url }} + DB_USERNAME: {{ secrets.database.username }} + DB_PASSWORD: {{ secrets.database.password }} +``` + +While sealing, the full templating context (same as in [templating]({{< ref "docs/reference/templating" >}})) is available. +Additionally, the global `secrets` object/variable is available which contains the sensitive secrets. + +## Secret Sources + +Secrets are only loaded while sealing. Available secret sets and sources are configured via +[.kluctl.yaml]({{< ref "docs/reference/kluctl-project/secrets-config" >}}). The secrets used per target are configured via the +[secrets config]({{< ref "docs/reference/kluctl-project/targets#secretsets" >}}) of the targets. + +## Using sealed secrets + +After sealing a secret, it can be used inside kustomize deployments. While deploying, kluctl will look for resources +included from `kustomization.yaml` which are not existent but for which a file with a `.sealme` extension exists. If such +a file is found, the appropriate sealed secrets is located based on the +[outputPattern](#outputpattern-and-location-of-stored-sealed-secrets). + +An example `kustomization.yaml`: +```yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: +# please note that we do not specify the .sealme suffix here +- db-secrets.yaml +- my-deployments.yaml +``` + +## outputPattern and location of stored sealed secrets + +It is possible to override the output pattern in the root [deployment project]({{< ref "docs/reference/deployments" >}}). +The output pattern must be a template string that is rendered with the full +[templating context]({{< ref "docs/reference/templating" >}}) available for the deployment.yaml. + +When manually specifying the outputPattern, ensure that it works well with multiple clusters and targets. You can +for example use the `{{ target.name }}` and `{{ cluster.name }}` inside the outputPattern. + +```yaml +# deployment.yaml in root directory +sealedSecrets: + outputPattern: "{{ cluster.name }}/{{ target.name }}" +``` + +The default outputPattern is simply `{{ target.name }}`, which should work well in most cases. + +The final storage location for the sealed secret is: + +`///` + +with: +* `base_dir`: The base directory for sealed secrets, which defaults to to the subdirectory `.sealed-secrets` in the kluctl project root + diretory. +* `rendered_output_pattern`: The rendered outputPattern as described above. +* `relative_sealme_file_dir`: The relative path from the deployment root directory. +* `file_name`: The filename of the sealed secret, excluding the `.sealme` extension. + +## Content Hashes and re-sealing +Sealed secrets are stored together with hashes of all individual secret entries. These hashes are then used to avoid +unnecessary re-sealing in future [seal]({{< ref "docs/reference/commands/seal" >}}) invocations. If you want to force re-sealing, use the +[--force-reseal]({{< ref "docs/reference/commands/seal" >}}) option. + +Hashing of secrets is done with bcrypt and the cluster id as salt. The cluster id is currently defined as the sha256 hash +of the cluster CA certificate. This will cause re-sealing of all secrets in case a cluster is set up from scratch +(which causes key from the sealed secrets operator to get wiped as well). + +## Clusters and namespaces +Sealed secrets are usually only decryptable by one cluster, simply because each cluster has its own set of randomly +generated public/private key pairs. This means, that a secret that was sealed for your production cluster can't be +unsealed on your test cluster. + +In addition, sealed secrets can be bound to a single namespace, making them undecryptable for any other namespace. +To limit a sealed secret to a namespace, simply fill the `metadata.namespace` field of the input secret (which is in +the `.sealme` file). This way, the sealed secret can only be deployed to a single namespace. + +You can also use [Scopes](https://github.com/bitnami-labs/sealed-secrets#scopes) to lift/limit restrictions. + +## Using reflectors/replicators +In case a sealed secrets needs to be deployed to more than one namespace, some form of replication must be used. You'd +then seal the secret for a single namespace and use a reflection/replication controller to reflect the unsealed secret +into one or multiple other namespaces. Example controllers that can accomplish this are the +[Mittwald kubernetes-reflector](https://github.com/mittwald/kubernetes-replicator) and the +[Emberstack Kubernetes Reflector](https://github.com/emberstack/kubernetes-reflector). + +Consider the following example (using the Mittwald replicator): +```yaml +kind: Secret +apiVersion: v1 +metadata: + name: db-secrets + namespace: {{ my.namespace.variable }} + annotations: + replicator.v1.mittwald.de/replicate-to: '{{ my.namespace.variable }}-.*' +stringData: + DB_URL: {{ secrets.database.url }} + DB_USERNAME: {{ secrets.database.username }} + DB_PASSWORD: {{ secrets.database.password }} +``` + +The above example would cause automatic replication into every namespace that matches the replicate-to pattern. + +Please watch out for security implications. In the above example, everyone who has the right to create a namespace that +matches the pattern will get access to the secret. diff --git a/docs/reference/templating/_index.md b/docs/reference/templating/_index.md new file mode 100644 index 000000000..ddef4c163 --- /dev/null +++ b/docs/reference/templating/_index.md @@ -0,0 +1,53 @@ +--- +title: "Templating" +linkTitle: "Templating" +weight: 2 +description: > + Templating Engine. +--- + +kluctl uses a Jinja2 Templating engine to pre-process/render every involved configuration file and resource before +actually interpreting it. Only files that are explicitly excluded via [.templateignore files](#templateignore) +are not rendered via Jinja2. + +Generally, everything that is possible with Jinja2 is possible in kluctl configuration/resources. Please +read into the [Jinja2 documentation](https://jinja.palletsprojects.com/en/3.0.x/templates/) to understand what exactly +is possible and how to use it. + +## .templateignore +In some cases it is required to exclude specific files from templating, for example when the contents conflict with +the used template engine (e.g. Go templates conflict with Jinja2 and cause errors). In such cases, you can place +a `.templateignore` beside the excluded files or into a parent folder of it. The contents/format of the `.templateignore` +file is the same as you would use in a `.gitignore` file. + +## Includes and imports +Standard Jinja2 [includes](https://jinja.palletsprojects.com/en/2.11.x/templates/#include) and +[imports](https://jinja.palletsprojects.com/en/2.11.x/templates/#import) can be used in all templates. + +The path given to include/import is searched in the directory of the root template and all it's parent directories up +until the project root. Please note that the search path is not altered in included templates, meaning that it will +always search in the same directories even if an include happens inside a file that was included as well. + +To include/import a file relative to the currently rendered file (which is not necessarily the root template), prefix +the path with `./`, e.g. use `{% include "./my-relative-file.j2" %}"`. + +## Macros + +[Jinja2 macros](https://jinja.palletsprojects.com/en/2.11.x/templates/#macros) are fully supported. When writing +macros that produce yaml resources, you must use the `---` yaml separator in case you want to produce multiple resources +in one go. + +## Why no Go Templating + +kluctl started as a python project and was then migrated to be a Go project. In the python world, Jinja2 is the obvious +choice when it comes to templating. In the Go world, of course Go Templates would be the first choice. + +When the migration to Go was performed, it was a conscious and opinionated decision to stick with Jinja2 templating. +The reason is that I (@codablock) believe that Go Templates are hard to read and write and at the same time quite limited +in their features (without extensive work). It never felt natural to write Go Templates. + +This "feeling" was confirmed by multiple users of kluctl when it started and users described as "relieving" to not +be forced to use Go Templates. + +The above is my personal experience and opinion. I'm still quite open for contributions in regard to Go Templating +support, as long as Jinja2 support is kept. diff --git a/docs/reference/templating/filters.md b/docs/reference/templating/filters.md new file mode 100644 index 000000000..cebf0275c --- /dev/null +++ b/docs/reference/templating/filters.md @@ -0,0 +1,80 @@ +--- +title: "Filters" +linkTitle: "Filters" +weight: 3 +description: > + Available filters. +--- + +In addition to the [builtin Jinja2 filters](https://jinja.palletsprojects.com/en/2.11.x/templates/#list-of-builtin-filters), +kluctl provides a few additional filters: + +### b64encode +Encodes the input value as base64. Example: `{{ "test" | b64encode }}` will result in `dGVzdA==`. + +### b64decode +Decodes an input base64 encoded string. Example `{{ my.source.var | b64decode }}`. + +### from_yaml +Parses a yaml string and returns an object. Please note that json is valid yaml, meaning that you can also use this +filter to parse json. + +### to_yaml +Converts a variable/object into its yaml representation. Please note that in most cases the resulting string will not +be properly indented, which will require you to also use the `indent` filter. Example: + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: my-config +data: + config.yaml: | + {{ my_config | to_yaml | indent(4) }} +``` + +### to_json +Same as `to_yaml`, but with json as output. Please note that json is always valid yaml, meaning that you can also use +`to_json` in yaml files. Consider the following example: + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment +spec: + template: + spec: + containers: + - name: c1 + image: my-image + env: {{ my_list_of_env_entries | to_json }} +``` + +This would render json into a yaml file, which is still a valid yaml file. Compare this to how this would have to be +solved with `to_yaml`: + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment +spec: + template: + spec: + containers: + - name: c1 + image: my-image + env: + {{ my_list_of_env_entries | to_yaml | indent(10) }} +``` + +The required indention filter is the part that makes this error-prone and hard to maintain. Consider using `to_json` +whenever you can. + +### render +Renders the input string with the current Jinja2 context. Example: +``` +{% set a="{{ my_var }}" %} +{{ a | render }} +``` \ No newline at end of file diff --git a/docs/reference/templating/functions.md b/docs/reference/templating/functions.md new file mode 100644 index 000000000..e24b4108e --- /dev/null +++ b/docs/reference/templating/functions.md @@ -0,0 +1,65 @@ +--- +title: "Functions" +linkTitle: "Functions" +weight: 4 +description: > + Available functions. +--- + +In addition to the provided +[builtin global functions](https://jinja.palletsprojects.com/en/2.11.x/templates/#list-of-global-functions), +kluctl also provides a few global functions: + +### load_template(file) +Loads the given file into memory, renders it with the current Jinja2 context and then returns it as a string. Example: +``` +{% set a=load_template('file.yaml') %} +{{ a }} +``` + +`load_template` uses the same path searching rules as described in [includes/imports]({{< ref "docs/reference/templating#includes-and-imports" >}}). + +### load_sha256(file, digest_len) +Loads the given file into memory, renders it and calculates the sha256 hash of the result. + +The filename given to `load_sha256` is treated the same as in `load_template`. Recursive loading/calculating of hashes +is allowed and is solved by replacing `load_sha256` invocations with currently loaded templates with dummy strings. +This also allows to calculate the hash of the currently rendered template, for example: + +``` +apiVersion: v1 +kind: ConfigMap +metadata: + name: my-config-{{ load_sha256("configmap.yaml") }} +data: +``` + +`digest_len` is an optional parameter that allows to limit the length of the returned hex digest. + +### get_var(field_path, default) +Convenience method to navigate through the current context variables via a +[JSON Path](https://goessner.net/articles/JsonPath/). Let's assume you currently have these variables defined +(e.g. via [vars]({{< ref "docs/reference/deployments/deployment-yml#vars-deployment-project" >}})): +```yaml +my: + deep: + var: value +``` +Then `{{ get_var('my.deep.var', 'my-default') }}` would return `value`. +When any of the elements inside the field path are non-existent, the given default value is returned instead. + +The `field_path` parameter can also be a list of pathes, which are then tried one after the another, returning the first +result that gives a value that is not None. For example, `{{ get_var(['non.existing.var', my.deep.var'], 'my-default') }}` +would also return `value`. + +### merge_dict(d1, d2) +Clones d1 and then recursively merges d2 into it and returns the result. Values inside d2 will override values in d1. + +### update_dict(d1, d2) +Same as `merge_dict`, but merging is performed in-place into d1. + +### raise(msg) +Raises a python exception with the given message. This causes the current command to abort. + +### debug_print(msg) +Prints a line to stderr. \ No newline at end of file diff --git a/docs/reference/templating/predefined-variables.md b/docs/reference/templating/predefined-variables.md new file mode 100644 index 000000000..a5aabcee1 --- /dev/null +++ b/docs/reference/templating/predefined-variables.md @@ -0,0 +1,27 @@ +--- +title: "Predefined Variables" +linkTitle: "Predefined Variables" +weight: 1 +description: > + Available predefined variables. +--- + +There are multiple variables available which are pre-defined by kluctl. These are: + +### args +This is a dictionary of arguments given via command line. It contains every argument defined in +[deployment args]({{< ref "docs/reference/deployments/deployment-yml#args" >}}). + +### target +This is the target definition of the currently processed target. It contains all values found in the +[target definition]({{< ref "docs/reference/kluctl-project/targets" >}}), for example `target.name`. + +### images +This global object provides the dynamic images features described in [images]({{< ref "docs/reference/deployments/images" >}}). + +### version +This global object defines latest version filters for `images.get_image(...)`. See [images]({{< ref "docs/reference/deployments/images" >}}) for details. + +### secrets +This global object is only available while [sealing]({{< ref "docs/reference/sealed-secrets" >}}) and contains the loaded +secrets defined via the currently sealed target. diff --git a/docs/reference/templating/variable-sources.md b/docs/reference/templating/variable-sources.md new file mode 100644 index 000000000..34ea6bf09 --- /dev/null +++ b/docs/reference/templating/variable-sources.md @@ -0,0 +1,215 @@ +--- +title: "Variable Sources" +linkTitle: "Variable Sources" +weight: 2 +description: > + Available variable sources. +--- + +There are multiple places in deployment projects (deployment.yaml) where additional variables can be loaded into +future Jinja2 contexts. + +The first place where vars can be specified is the deployment root, as documented [here]({{< ref "docs/reference/deployments/deployment-yml#vars-deployment-project" >}}). +These vars are visible for all deployments inside the deployment project, including sub-deployments from includes. + +The second place to specify variables is in the deployment items, as documented [here]({{< ref "docs/reference/deployments/deployment-yml#vars-deployment-item" >}}). + +The variables loaded for each entry in `vars` are not available inside the `deployment.yaml` file itself. +However, each entry in `vars` can use all variables defined before that specific entry is processed. Consider the +following example. + +```yaml +vars: +- file: vars1.yaml +- file: vars2.yaml +``` + +`vars2.yaml` can now use variables that are defined in `vars1.yaml`. At all times, variables defined by +parents of the current sub-deployment project can be used in the current vars file. + +Different types of vars entries are possible: + +### file +This loads variables from a yaml file. Assume the following yaml file with the name `vars1.yaml`: +```yaml +my_vars: + a: 1 + b: "b" + c: + - l1 + - l2 +``` + +This file can be loaded via: + +```yaml +vars: + - file: vars1.yaml +``` + +After which all included deployments and sub-deployments can use the jinja2 variables from `vars1.yaml`. + +### values +An inline definition of variables. Example: + +```yaml +vars: + - values: + a: 1 + b: c +``` + +These variables can then be used in all deployments and sub-deployments. + +### git +This loads variables from a git repository. Example: + +```yaml +vars: + - git: + url: ssh://git@github.com/example/repo.git + ref: my-branch + path: path/to/vars.yaml +``` + +### clusterConfigMap +Loads a configmap from the target's cluster and loads the specified key's value as a yaml file into the jinja2 variables +context. + +Assume the following configmap to be deployed to the target cluster: +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: my-vars + namespace: my-namespace +data: + vars: | + a: 1 + b: "b" + c: + - l1 + - l2 +``` + +This configmap can be loaded via: + +```yaml +vars: + - clusterConfigMap: + name: my-vars + namespace: my-namespace + key: vars +``` + +It assumes that the configmap is already deployed before the kluctl deployment happens. This might for example be +useful to store meta information about the cluster itself and then make it available to kluctl deployments. + +### clusterSecret +Same as clusterConfigMap, but for secrets. + +### http +The http variables source allows to load variables from an arbitrary HTTP resource by performing a GET (or any other +configured HTTP method) on the URL. Example: + +```yaml +vars: + - http: + url: https://example.com/path/to/my/vars +``` + +The above source will load a variables file from the given URL. The file is expected to be in yaml or json format. + +The following additional properties are supported for http sources: + +##### method +Specifies the HTTP method to be used when requesting the given resource. Defaults to `GET`. + +##### body +The body to send along with the request. If not specified, nothing is sent. + +#### headers +A map of key/values pairs representing the header entries to be added to the request. If not specified, nothing is added. + +##### jsonPath +Can be used to select a nested element from the yaml/json document returned by the HTTP request. This is useful in case +some REST api is used which does not directly return the variables file. Example: + +```yaml +vars: + - http: + url: https://example.com/path/to/my/vars + jsonPath: $[0].data +``` + +The above example would successfully use the following json document as variables source: + +```json +[{"data": {"vars": {"var1": "value1"}}}] +``` + +#### Authentication + +Kluctl currently supports BASIC and NTLM authentication. It will prompt for credentials when needed. + +### awsSecretsManager +[AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) integration. Loads a variables YAML from an AWS Secrets +Manager secret. The secret can either be specified via an ARN or via a secretName and region combination. An AWS +config profile can also be specified (which must exist while sealing). + +The secrets stored in AWS Secrets manager must contain a valid yaml or json file. + +Example using an ARN: +```yaml +vars: + - awsSecretsManager: + secretName: arn:aws:secretsmanager:eu-central-1:12345678:secret:secret-name-XYZ + profile: my-prod-profile +``` + +Example using a secret name and region: +```yaml +vars: + - awsSecretsManager: + secretName: secret-name + region: eu-central-1 + profile: my-prod-profile +``` + +The advantage of the latter is that the auto-generated suffix in the ARN (which might not be known at the time of +writing the configuration) doesn't have to be specified. + +### vault + +[Vault by HashiCorp](https://www.vaultproject.io/) with [Tokens](https://www.vaultproject.io/docs/concepts/tokens) +authentication integration. The address and the path to the secret can be configured. +The implementation was tested with KV Secrets Engine. + +Example using vault: +```yaml +vars: + - vault: + address: http://localhost:8200 + path: secret/data/simple +``` + +Before deploying or sealing please make sure that you have access to vault. You can do this for example by setting +the environment variable `VAULT_TOKEN`. + +### systemEnvVars +Load variables from environment variables. Children of `systemEnvVars` can be arbitrary yaml, e.g. dictionaries or lists. +The leaf values are used to get a value from the system environment. + +Example: +```yaml +vars: +- systemEnvVars: + var1: ENV_VAR_NAME1 + someDict: + var2: ENV_VAR_NAME2 + someList: + - var3: ENV_VAR_NAME3 +``` + +The above example will make 3 variables available: `var1`, `someDict.var2` and +`someList[0].var3`, each having the values of the environment variables specified by the leaf values. From 16aacf901b31e10cc5bc594f6ae93eb47fc11122 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 18 Oct 2022 22:36:16 +0200 Subject: [PATCH 1082/2916] docs: Remove mentions of external projects --- docs/concepts.md | 5 +---- docs/reference/commands/environment-variables.md | 4 ++-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/docs/concepts.md b/docs/concepts.md index c410e32e1..583b8ccbe 100644 --- a/docs/concepts.md +++ b/docs/concepts.md @@ -7,12 +7,9 @@ weight: 10 These are some core concepts in Kluctl. ## Kluctl project -The kluctl project defines targets, secret sources and external git projects. +The kluctl project defines targets and secret sources. It is defined via the [.kluctl.yaml]({{< ref "docs/reference/kluctl-project" >}}) configuration file. -The kluctl project can also optionally define where the deployment project and clusters configs are located (external -git projects). - ## Targets A target defines a target cluster and a set of deployment arguments. Multiple targets can use the same cluster. Targets allow implementing multi-cluster, multi-environment, multi-customer, ... deployments. diff --git a/docs/reference/commands/environment-variables.md b/docs/reference/commands/environment-variables.md index 15b7f003c..be7ade381 100644 --- a/docs/reference/commands/environment-variables.md +++ b/docs/reference/commands/environment-variables.md @@ -11,8 +11,8 @@ In addition to arguments, Kluctl can be controlled via a set of environment vari ## Environment variables as arguments All options/arguments accepted by kluctl can also be specified via environment variables. The name of the environment variables always start with `KLUCTL_` and end with the option/argument in uppercase and dashes replaced with -underscores. As an example, `--project-url=my-project` can also be specified with the environment variable -`KLUCTL_PROJECT_URL=my-project`. +underscores. As an example, `--dry-run` can also be specified with the environment variable +`KLUCTL_DRY_RUN=true`. ## Additional environment variables A few additional environment variables are supported which do not belong to an option/argument. These are: From 89f36d5ebdde0c53a08f38587009f77acd6cc94a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 18 Oct 2022 22:36:48 +0200 Subject: [PATCH 1083/2916] docs: Update documentation about environment variables --- docs/reference/commands/environment-variables.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/reference/commands/environment-variables.md b/docs/reference/commands/environment-variables.md index be7ade381..9bc60def5 100644 --- a/docs/reference/commands/environment-variables.md +++ b/docs/reference/commands/environment-variables.md @@ -18,6 +18,5 @@ underscores. As an example, `--dry-run` can also be specified with the environme A few additional environment variables are supported which do not belong to an option/argument. These are: 1. `KLUCTL_REGISTRY__HOST`, `KLUCTL_REGISTRY__USERNAME`, and so on. See [registries]({{< ref "docs/reference/deployments/images#supported-image-registries-and-authentication" >}}) for details. -2. `KLUCTL_SSH_DISABLE_STRICT_HOST_KEY_CHECKING`. Disable ssh host key checking when accessing git repositories. -3. `KLUCTL_NO_THREADS`. Do not use multithreading while performing work. This is only useful for debugging purposes. -4. `KLUCTL_IGNORE_DEBUGGER`. Pretend that there is no debugger attached when automatically deciding if multi-threading should be enabled or not. +2. `KLUCTL_GIT__HOST`, `KLUCTL_GIT__USERNAME`, and so on. +3. `KLUCTL_SSH_DISABLE_STRICT_HOST_KEY_CHECKING`. Disable ssh host key checking when accessing git repositories. From c7f4e47a8fcb1ca24917e706c286a072779807cf Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 18 Oct 2022 22:42:53 +0200 Subject: [PATCH 1084/2916] docs: Document slugify filter --- docs/reference/templating/filters.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/reference/templating/filters.md b/docs/reference/templating/filters.md index cebf0275c..0f556d1f3 100644 --- a/docs/reference/templating/filters.md +++ b/docs/reference/templating/filters.md @@ -77,4 +77,7 @@ Renders the input string with the current Jinja2 context. Example: ``` {% set a="{{ my_var }}" %} {{ a | render }} -``` \ No newline at end of file +``` + +### slugify +Slugify a string based on [python-slugify](https://github.com/un33k/python-slugify). From 89d16e797790a05060002709290aed9b840e4af4 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 18 Oct 2022 22:57:24 +0200 Subject: [PATCH 1085/2916] docs: Add documentation for time template functions --- docs/reference/templating/functions.md | 31 +++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/docs/reference/templating/functions.md b/docs/reference/templating/functions.md index e24b4108e..50c474ff9 100644 --- a/docs/reference/templating/functions.md +++ b/docs/reference/templating/functions.md @@ -62,4 +62,33 @@ Same as `merge_dict`, but merging is performed in-place into d1. Raises a python exception with the given message. This causes the current command to abort. ### debug_print(msg) -Prints a line to stderr. \ No newline at end of file +Prints a line to stderr. + +### time.now() +Returns the current time. The returned object has the following members: + +| member | description | +|-------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| t.as_timezone(tz) | Converts and returns the time `t` in the given timezone. Example:
`{{ time.now().as_timezone("Europe/Berlin") }}` | +| t.weekday() | Returns the time's weekday. 0 means Monday and 6 means Sunday. | +| t.hour() | Returns the time's hour from 0-23. | +| t.minute() | Returns the time's minute from 0-59. | +| t.second() | Returns the time's second from 0-59. | +| t.nanosecond() | Returns the time's nanosecond from 0-999999999. | +| t + delta | Adds a delta to `t`. Example: `{{ time.now() + time.second * 10 }}` | +| t - delta | Subtracts a delta from `t`. Example: `{{ time.now() - time.second * 10 }}` | +| t1 < t2
t1 >= t2
... | Time objects can be compared to other time objects. Example:
`{% if time.now() < time.parse_iso("2022-10-01T10:00") %}...{% endif %}`
All logical operators are supported. | + +### time.utcnow() +Returns the current time in UTC. +The object has the same members as described in [time.now()](#timenow). + +### time.parse_iso(iso_time_str) +Parse the given string and return a time object. The string must be in ISO time. +The object has the same members as described in [time.now()](#timenow). + +### time.second, time.minute, time.hour +Represents a time delta to be used with `t + delta` and `t - delta`. Example +``` +{{ time.now() + time.minute * 10 }} +``` From 638248c9ea1e6db2390b56cab832bc8fc53c951b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 18 Oct 2022 23:11:05 +0200 Subject: [PATCH 1086/2916] docs: Rename all _index.md files to README.md --- docs/{_index.md => README.md} | 0 docs/reference/{_index.md => README.md} | 0 docs/reference/commands/{_index.md => README.md} | 0 docs/reference/deployments/{_index.md => README.md} | 0 docs/reference/deployments/annotations/{_index.md => README.md} | 0 docs/reference/kluctl-project/{_index.md => README.md} | 0 .../kluctl-project/secrets-config/{_index.md => README.md} | 0 docs/reference/kluctl-project/targets/{_index.md => README.md} | 0 docs/reference/templating/{_index.md => README.md} | 0 9 files changed, 0 insertions(+), 0 deletions(-) rename docs/{_index.md => README.md} (100%) rename docs/reference/{_index.md => README.md} (100%) rename docs/reference/commands/{_index.md => README.md} (100%) rename docs/reference/deployments/{_index.md => README.md} (100%) rename docs/reference/deployments/annotations/{_index.md => README.md} (100%) rename docs/reference/kluctl-project/{_index.md => README.md} (100%) rename docs/reference/kluctl-project/secrets-config/{_index.md => README.md} (100%) rename docs/reference/kluctl-project/targets/{_index.md => README.md} (100%) rename docs/reference/templating/{_index.md => README.md} (100%) diff --git a/docs/_index.md b/docs/README.md similarity index 100% rename from docs/_index.md rename to docs/README.md diff --git a/docs/reference/_index.md b/docs/reference/README.md similarity index 100% rename from docs/reference/_index.md rename to docs/reference/README.md diff --git a/docs/reference/commands/_index.md b/docs/reference/commands/README.md similarity index 100% rename from docs/reference/commands/_index.md rename to docs/reference/commands/README.md diff --git a/docs/reference/deployments/_index.md b/docs/reference/deployments/README.md similarity index 100% rename from docs/reference/deployments/_index.md rename to docs/reference/deployments/README.md diff --git a/docs/reference/deployments/annotations/_index.md b/docs/reference/deployments/annotations/README.md similarity index 100% rename from docs/reference/deployments/annotations/_index.md rename to docs/reference/deployments/annotations/README.md diff --git a/docs/reference/kluctl-project/_index.md b/docs/reference/kluctl-project/README.md similarity index 100% rename from docs/reference/kluctl-project/_index.md rename to docs/reference/kluctl-project/README.md diff --git a/docs/reference/kluctl-project/secrets-config/_index.md b/docs/reference/kluctl-project/secrets-config/README.md similarity index 100% rename from docs/reference/kluctl-project/secrets-config/_index.md rename to docs/reference/kluctl-project/secrets-config/README.md diff --git a/docs/reference/kluctl-project/targets/_index.md b/docs/reference/kluctl-project/targets/README.md similarity index 100% rename from docs/reference/kluctl-project/targets/_index.md rename to docs/reference/kluctl-project/targets/README.md diff --git a/docs/reference/templating/_index.md b/docs/reference/templating/README.md similarity index 100% rename from docs/reference/templating/_index.md rename to docs/reference/templating/README.md From d7b9b1c2e4cb54f05185ae501ed8ed8ddbee986a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 19 Oct 2022 09:15:26 +0200 Subject: [PATCH 1087/2916] docs: Document kluctl.io/validate-ignore --- docs/reference/deployments/annotations/validation.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/reference/deployments/annotations/validation.md b/docs/reference/deployments/annotations/validation.md index df528c164..f291a837c 100644 --- a/docs/reference/deployments/annotations/validation.md +++ b/docs/reference/deployments/annotations/validation.md @@ -14,3 +14,6 @@ are added to the validation result, which is then returned by the validate comma The annotation key is dynamic, meaning that all annotations that begin with `validate-result.kluctl.io/` are taken into account. + +### kluctl.io/validate-ignore +If this annotation is set to `true`, the object will be ignored while `kluctl validate` is run. \ No newline at end of file From 91ab5bf74666efa988947aa40c99e8e9d6e4fa3e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 21 Oct 2022 15:04:59 +0200 Subject: [PATCH 1088/2916] docs: Prepare docs to be syncable to www-kluctl.io --- README.md | 24 ++++--- docs/README.md | 62 +++---------------- docs/concepts.md | 5 ++ docs/get-started.md | 28 ++++----- docs/history.md | 5 ++ docs/installation.md | 41 ++++++++++-- docs/philosophy.md | 5 ++ docs/reference/README.md | 9 +++ docs/reference/commands/README.md | 22 +++++++ docs/reference/commands/common-arguments.md | 5 ++ docs/reference/commands/delete.md | 3 + docs/reference/commands/deploy.md | 3 + docs/reference/commands/diff.md | 3 + .../commands/environment-variables.md | 3 + docs/reference/commands/helm-pull.md | 3 + docs/reference/commands/helm-update.md | 3 + docs/reference/commands/list-images.md | 3 + docs/reference/commands/list-targets.md | 3 + docs/reference/commands/poke-images.md | 3 + docs/reference/commands/prune.md | 3 + docs/reference/commands/render.md | 3 + docs/reference/commands/seal.md | 3 + docs/reference/commands/validate.md | 3 + docs/reference/deployments/README.md | 14 +++++ .../deployments/annotations/README.md | 10 +++ .../deployments/annotations/all-resources.md | 5 ++ .../deployments/annotations/hooks.md | 6 ++ .../deployments/annotations/kustomization.md | 5 ++ .../deployments/annotations/validation.md | 5 ++ docs/reference/deployments/deployment-yml.md | 5 ++ docs/reference/deployments/helm.md | 5 ++ docs/reference/deployments/hooks.md | 5 ++ docs/reference/deployments/images.md | 5 ++ docs/reference/deployments/kustomize.md | 5 ++ docs/reference/deployments/readiness.md | 5 ++ docs/reference/deployments/tags.md | 5 ++ docs/reference/kluctl-project/README.md | 10 ++- .../kluctl-project/secrets-config/README.md | 5 ++ .../kluctl-project/targets/README.md | 5 ++ .../kluctl-project/targets/dynamic-targets.md | 5 ++ docs/reference/sealed-secrets.md | 7 ++- docs/reference/templating/README.md | 12 ++++ docs/reference/templating/filters.md | 5 ++ docs/reference/templating/functions.md | 5 ++ .../templating/predefined-variables.md | 5 ++ docs/reference/templating/variable-sources.md | 5 ++ install/README.md | 40 +----------- 47 files changed, 308 insertions(+), 121 deletions(-) diff --git a/README.md b/README.md index c19f8f86f..899b40f2e 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,6 @@ kluctl - - Kluctl is the missing glue that puts together your (and any third-party) deployments into one large declarative Kubernetes deployment, while making it fully manageable (deploy, diff, prune, delete, ...) via one unified command line interface. @@ -25,17 +23,29 @@ local machine, from your CI/CD pipelines or any automation platform/system that Flux support is in alpha stadium and available via the [flux-kluctl-controller](https://github.com/kluctl/flux-kluctl-controller). -## Installation +## What can I do with Kluctl? + +Kluctl allows you to define a Kluctl project, which in turn defines Kluctl +deployments and sub-deployments. Each Kluctl deployment defines Kustomize deployments. + +A Kluctl project also defines targets, which represent your target environments +and/or clusters. + +The Kluctl CLI then allows to deploy, diff, prune, delete, ... your deployments. -See [installation](./install). +## Where do I start? + +Installation instructions can be found [here](./docs/installation.md). For a getting started guide, continue +[here](./docs/get-started.md). ## Documentation -Documentation can be found here: https://kluctl.io/docs +Documentation, news and blog posts can be found on https://kluctl.io. -## Kluctl in Short +The underlying documentation is synced from this repo (look into ./docs) to the website whenever something is merged +into main. - +## Kluctl in Short | | | | --- | --- | diff --git a/docs/README.md b/docs/README.md index c6a4cdaa4..f9311fca4 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,3 +1,5 @@ + -Kluctl is the missing glue that puts together your (and any third-party) deployments into one large declarative -Kubernetes deployment, while making it fully manageable (deploy, diff, prune, delete, ...) via one unified command -line interface. - -Kluctl tries to be as flexible as possible, while remaining as simple as possible. It reuses established -tools (e.g. Kustomize and Helm), making it possible to re-use a large set of available third-party deployments. - -Kluctl is centered around "targets", which can be a cluster or a specific environment (e.g. test, dev, prod, ...) on one -or multiple clusters. Targets can be deployed, diffed, pruned, deleted, and so on. The idea is to have the same set of -operations for every target, no matter how simple or complex the deployment and/or target is. - -Kluctl does not depend on external operators/controllers and allows to use the same deployment wherever you want, -as long as access to the kluctl project and clusters is available. This means, that you can use it from your -local machine, from your CI/CD pipelines or any automation platform/system that allows to call custom tools. - -Flux support is in alpha state and available via the [flux-kluctl-controller](https://github.com/kluctl/flux-kluctl-controller). - -## Kluctl in Short - - - -| | | -| --- | --- | -| 💪 Kluctl handles all your deployments | You can manage all your deployments with Kluctl, including infrastructure related and your applications. | -| 🪶 Complex or simple, all the same | You can manage complex and simple deployments with Kluctl. Simple deployments are lightweight while complex deployment are easily manageable. | -| 🤖 Native git support | Kluctl has native Git support integrated, meaning that it can easily deploy remote Kluctl projects or externalize parts (e.g. configuration) of your Kluctl project. | -| 🪐 Multiple environments | Deploy the same deployment to multiple environments (dev, test, prod, ...), with flexible differences in configuration. | -| 🌌 Multiple clusters | Manage multiple target clusters (in multiple clouds or bare-metal if you want). | -| 🔩 Configuration and Templating | Kluctl allows to use templating in nearly all places, making it easy to have dynamic configuration. | -| ⎈ Helm and Kustomize | The Helm and Kustomize integrations allow you to reuse plenty of third-party charts and kustomizations. | -| 🔍 See what's different | Always know what the state of your deployments is by being able to run diffs on the whole deployment. | -| 🔎 See what happened | Always know what you actually changed after performing a deployment. | -| 💥 Know what went wrong | Kluctl will show you what part of your deployment failed and why. | -| 👐 Live and let live | Kluctl tries to not interfere with any other tools or operators. This is possible due to it's use of server-side-apply. | -| 🧹 Keep it clean | Keep your clusters clean by issuing regular prune calls. | -| 🔐 Encrypted Secrets | Manage encrypted secrets for multiple target environments and clusters. | - -## What can I do with Kluctl? - -Kluctl allows you to define a Kluctl project, which in turn defines Kluctl -deployments and sub-deployments. Each Kluctl deployment defines Kustomize deployments. - -A Kluctl project also defines targets, which represent your target environments -and/or clusters. - -The Kluctl CLI then allows to deploy, diff, prune, delete, ... your deployments. - -## Where do I start? +# Table of Contents -{{% alert title="Get started with Kluctl!" %}} -Following this [guide]({{< ref "docs/get-started" >}}) will just take a couple of minutes to complete: -After installing `kluctl`, you can either check out the [example]({{< ref "docs/guides/examples" >}}) or [tutorials]({{< ref "docs/guides/tutorials" >}}). -{{% /alert %}} +1. [Get Started](./get-started.md) +2. [Core Concepts](./concepts.md) +3. [Installation](./installation) +4. [Philosophy](./philosophy.md) +5. [History](./history.md) +6. [Reference](./reference) + +# Core Concepts These are some core concepts in Kluctl. diff --git a/docs/get-started.md b/docs/get-started.md index d8868313b..711235908 100644 --- a/docs/get-started.md +++ b/docs/get-started.md @@ -1,11 +1,16 @@ + + +# Get Started -This tutorial shows you how to bootstrap Flux to a Kubernetes cluster and deploy a sample application in a GitOps manner. +This tutorial shows you how to start using kluctl. ## Before you begin @@ -15,7 +20,7 @@ A few things must be prepared before you actually begin. The first step is of course: You need a kubernetes cluster. It doesn't really matter where this cluster is hosted, if it's a local (e.g. [kind](https://kind.sigs.k8s.io/docs/user/quick-start/)) cluster, managed cluster, or a self-hosted -cluster, kops or kubespray based, AWS, GCE, Azure, ... and so on. kluctl +cluster, kops or kubespray based, AWS, GCE, Azure, ... and so on. Kluctl is completely independent of how Kubernetes is deployed and where it is hosted. There is however a minimum Kubernetes version that must be met: 1.20.0. This is due to the heavy use of server-side apply @@ -29,7 +34,7 @@ then you'd have to ensure that the kubeconfig context `test.example.com` is corr cluster. See [Configure Access to Multiple Clusters](https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/) for documentation -on how to manage multiple clusters with a single kubeconfig. Depending on the Kubernets provisioning/deployment tooling +on how to manage multiple clusters with a single kubeconfig. Depending on the Kubernetes provisioning/deployment tooling you used, you might also be able to directly export the context into your local kubeconfig. For example, [kops](https://github.com/kubernetes/kops/blob/master/docs/cli/kops_export.md) is able to export and merge the kubeconfig for a given cluster. @@ -42,15 +47,8 @@ for a given cluster. ## Install Kluctl -The `kluctl` command-line interface (CLI) is required to perform deployments. - -To install the CLI with Homebrew run: - -```sh -brew install kluctl/tap/kluctl -``` - -For other installation methods, see the [install documentation]({{< ref "docs/installation" >}}). +The `kluctl` command-line interface (CLI) is required to perform deployments. Read the [installation instructions](./installation.md) +to figure out how to install it. ## Clone the kluctl examples @@ -62,7 +60,7 @@ git clone https://github.com/kluctl/kluctl-examples.git ## Choose one of the examples -You can choose whatever example you like from the clones repository. We will however continue this guide by referring +You can choose whatever example you like from the cloned repository. We will however continue this guide by referring to the `simple-helm` example found in that repository. Change the current directory: ```sh @@ -115,5 +113,5 @@ You should need 2 instances of the nginx POD running now. ## Where to continue? -Continue by reading through the [tutorials]({{< ref "docs/guides/tutorials" >}}) and by consulting -the [reference documentation]({{< ref "reference" >}}). +Continue by reading through the [tutorials](https://kluctl.io/docs/guides/tutorials/) and by consulting +the [reference documentation](./reference). diff --git a/docs/history.md b/docs/history.md index a1baceb2a..f2f6fad8a 100644 --- a/docs/history.md +++ b/docs/history.md @@ -1,9 +1,14 @@ + + +# History Kluctl was created after multiple incarnations of complex multi-environment (e.g. dev, test, prod) deployments, including everything from monitoring, persistency and the actual custom services. The philosophy of these deployments was always diff --git a/docs/installation.md b/docs/installation.md index 94ae4641a..3bf922ec7 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -1,18 +1,22 @@ + -## Install kluctl +# Installation + +## Binaries The kluctl CLI is available as a binary executable for all major platforms, the binaries can be downloaded form GitHub [releases page](https://github.com/kluctl/kluctl/releases). -{{% tabs %}} -{{% tab "Homebrew" %}} +## Installation with Homebrew With [Homebrew](https://brew.sh) for macOS and Linux: @@ -20,8 +24,7 @@ With [Homebrew](https://brew.sh) for macOS and Linux: brew install kluctl/tap/kluctl ``` -{{% /tab %}} -{{% tab "bash" %}} +## Installation with Bash With [Bash](https://www.gnu.org/software/bash/) for macOS and Linux: @@ -29,7 +32,33 @@ With [Bash](https://www.gnu.org/software/bash/) for macOS and Linux: curl -s https://kluctl.io/install.sh | bash ``` -{{% /tab %}} +The install script does the following: +* attempts to detect your OS +* downloads and unpacks the release tar file in a temporary directory +* copies the kluctl binary to `/usr/local/bin` +* removes the temporary directory + +## Build from source + +Clone the repository: + +```bash +git clone https://github.com/kluctl/kluctl +cd kluctl +``` + +Build the `kluctl` binary (requires go >= 1.19): + +```bash +make build +``` + +Run the binary: + +```bash +./bin/kluctl -h +``` + + +# Philosophy Kluctl tries to follow a few basic ideas and a philosophy. Project and deployments structure, as well as all commands are centered on these. diff --git a/docs/reference/README.md b/docs/reference/README.md index 06d1ad3b4..98ee9a446 100644 --- a/docs/reference/README.md +++ b/docs/reference/README.md @@ -1,3 +1,5 @@ + + +# Table of Contents +1. [.kluctl.yaml](./kluctl-project) +2. [Deployments](./deployments) +3. [Sealed Secrets](./sealed-secrets.md) +4. [Kluctl Commands](./commands) diff --git a/docs/reference/commands/README.md b/docs/reference/commands/README.md index 438c870aa..1fe1a1923 100644 --- a/docs/reference/commands/README.md +++ b/docs/reference/commands/README.md @@ -1,3 +1,5 @@ + + +# Commands kluctl offers a unified command line interface that allows to standardize all your deployments. Every project, no matter how different it is from other projects, is managed the same way. @@ -12,3 +17,20 @@ no matter how different it is from other projects, is managed the same way. You can always call `kluctl --help` or `kluctl --help` for a help prompt. Individual commands are documented in sub-sections. + +## Table of Contents + +1. [Common Arguments](./common-arguments.md) +2. [Environment Variables](./environment-variables.md) +3. [delete](./delete.md) +4. [deploy](./deploy.md) +5. [diff](./diff.md) +6. [helm-pull](./helm-pull.md) +7. [helm-update](./helm-update.md) +8. [list-images](./list-images.md) +9. [list-targets](./list-targets.md) +10. [poke-images](./poke-images.md) +11. [prune](./prune.md) +12. [render](./render.md) +13. [seal](./seal.md) +14. [validate](./validate.md) diff --git a/docs/reference/commands/common-arguments.md b/docs/reference/commands/common-arguments.md index 429d78235..9cfe45558 100644 --- a/docs/reference/commands/common-arguments.md +++ b/docs/reference/commands/common-arguments.md @@ -1,3 +1,5 @@ + + +# Common Arguments A few sets of arguments are common between multiple commands. These arguments are still part of the command itself and must be placed *after* the command name. diff --git a/docs/reference/commands/delete.md b/docs/reference/commands/delete.md index d2753f4b4..70483166f 100644 --- a/docs/reference/commands/delete.md +++ b/docs/reference/commands/delete.md @@ -1,3 +1,5 @@ + ## Command diff --git a/docs/reference/commands/deploy.md b/docs/reference/commands/deploy.md index 2114ac54c..7ecfc1b24 100644 --- a/docs/reference/commands/deploy.md +++ b/docs/reference/commands/deploy.md @@ -1,3 +1,5 @@ + ## Command diff --git a/docs/reference/commands/diff.md b/docs/reference/commands/diff.md index 3af235dd6..c9d4546f4 100644 --- a/docs/reference/commands/diff.md +++ b/docs/reference/commands/diff.md @@ -1,3 +1,5 @@ + ## Command diff --git a/docs/reference/commands/environment-variables.md b/docs/reference/commands/environment-variables.md index 9bc60def5..6db995487 100644 --- a/docs/reference/commands/environment-variables.md +++ b/docs/reference/commands/environment-variables.md @@ -1,3 +1,5 @@ + In addition to arguments, Kluctl can be controlled via a set of environment variables. diff --git a/docs/reference/commands/helm-pull.md b/docs/reference/commands/helm-pull.md index 576324be4..80fb685a3 100644 --- a/docs/reference/commands/helm-pull.md +++ b/docs/reference/commands/helm-pull.md @@ -1,3 +1,5 @@ + ## Command diff --git a/docs/reference/commands/helm-update.md b/docs/reference/commands/helm-update.md index a9e71f641..e1a526ae8 100644 --- a/docs/reference/commands/helm-update.md +++ b/docs/reference/commands/helm-update.md @@ -1,3 +1,5 @@ + ## Command diff --git a/docs/reference/commands/list-images.md b/docs/reference/commands/list-images.md index cf56b6af1..ce0f2aaba 100644 --- a/docs/reference/commands/list-images.md +++ b/docs/reference/commands/list-images.md @@ -1,3 +1,5 @@ + ## Command diff --git a/docs/reference/commands/list-targets.md b/docs/reference/commands/list-targets.md index 3ef135116..f99d5b2e0 100644 --- a/docs/reference/commands/list-targets.md +++ b/docs/reference/commands/list-targets.md @@ -1,3 +1,5 @@ + ## Command diff --git a/docs/reference/commands/poke-images.md b/docs/reference/commands/poke-images.md index 2a0aa64f2..585ae6d47 100644 --- a/docs/reference/commands/poke-images.md +++ b/docs/reference/commands/poke-images.md @@ -1,3 +1,5 @@ + ## Command diff --git a/docs/reference/commands/prune.md b/docs/reference/commands/prune.md index f019404ac..a895db0e8 100644 --- a/docs/reference/commands/prune.md +++ b/docs/reference/commands/prune.md @@ -1,3 +1,5 @@ + ## Command diff --git a/docs/reference/commands/render.md b/docs/reference/commands/render.md index f4869433b..c2b626415 100644 --- a/docs/reference/commands/render.md +++ b/docs/reference/commands/render.md @@ -1,3 +1,5 @@ + ## Command diff --git a/docs/reference/commands/seal.md b/docs/reference/commands/seal.md index e87b720eb..ee99cba1c 100644 --- a/docs/reference/commands/seal.md +++ b/docs/reference/commands/seal.md @@ -1,3 +1,5 @@ + ## Command diff --git a/docs/reference/commands/validate.md b/docs/reference/commands/validate.md index d27b9f59d..5ff5a0a6e 100644 --- a/docs/reference/commands/validate.md +++ b/docs/reference/commands/validate.md @@ -1,3 +1,5 @@ + ## Command diff --git a/docs/reference/deployments/README.md b/docs/reference/deployments/README.md index de97255a3..08c0d07d9 100644 --- a/docs/reference/deployments/README.md +++ b/docs/reference/deployments/README.md @@ -1,3 +1,5 @@ + + +# Table of Contents + +1. [deployment.yaml](./deployment-yml.md) +2. [Kustomize Integration](./kustomize.md) +3. [Container Images](./images.md) +4. [Helm Integration](./helm.md) +5. [Hooks](./hooks.md) +6. [Readiness](./readiness.md) +7. [Tags](./tags.md) +8. [Annotations](./annotations) A deployment project is collection of deployment items and sub-deployments. Deployment items are usually [Kustomize]({{< ref "./kustomize" >}}) deployments, but can also integrate [Helm Charts]({{< ref "./helm" >}}). diff --git a/docs/reference/deployments/annotations/README.md b/docs/reference/deployments/annotations/README.md index a4187a3e9..398001649 100644 --- a/docs/reference/deployments/annotations/README.md +++ b/docs/reference/deployments/annotations/README.md @@ -1,3 +1,5 @@ + + +# Table of Contents + +1. [All Resources](./all-resources.md) +2. [Hooks](./hooks.md) +3. [Validation](./validation.md) +4. [Kustomize](./kustomization.md) diff --git a/docs/reference/deployments/annotations/all-resources.md b/docs/reference/deployments/annotations/all-resources.md index c77c2604f..5f2ee3327 100644 --- a/docs/reference/deployments/annotations/all-resources.md +++ b/docs/reference/deployments/annotations/all-resources.md @@ -1,3 +1,5 @@ + + +# All resources The following annotations control the behavior of the `deploy` and related commands. diff --git a/docs/reference/deployments/annotations/hooks.md b/docs/reference/deployments/annotations/hooks.md index c937cc95e..ca946554e 100644 --- a/docs/reference/deployments/annotations/hooks.md +++ b/docs/reference/deployments/annotations/hooks.md @@ -1,3 +1,5 @@ + + +# Hooks + The following annotations control hook execution See [hooks]({{< ref "docs/reference/deployments/hooks" >}}) for more details. diff --git a/docs/reference/deployments/annotations/kustomization.md b/docs/reference/deployments/annotations/kustomization.md index 8c2f98e0d..dfdcbf661 100644 --- a/docs/reference/deployments/annotations/kustomization.md +++ b/docs/reference/deployments/annotations/kustomization.md @@ -1,3 +1,5 @@ + + +# Kustomize Even though the `kustomization.yaml` from Kustomize deployments are not really Kubernetes resources (as they are not really deployed), they have the same structure as Kubernetes resources. This also means that the `kustomization.yaml` diff --git a/docs/reference/deployments/annotations/validation.md b/docs/reference/deployments/annotations/validation.md index f291a837c..d4720d2e4 100644 --- a/docs/reference/deployments/annotations/validation.md +++ b/docs/reference/deployments/annotations/validation.md @@ -1,3 +1,5 @@ + + +# Validation The following annotations influence the [validate]({{< ref "docs/reference/commands/validate" >}}) command. diff --git a/docs/reference/deployments/deployment-yml.md b/docs/reference/deployments/deployment-yml.md index bb22bac01..ab36ffc00 100644 --- a/docs/reference/deployments/deployment-yml.md +++ b/docs/reference/deployments/deployment-yml.md @@ -1,3 +1,5 @@ + + +# Deployments The `deployment.yaml` file is the entrypoint for the deployment project. Included sub-deployments also provide a `deployment.yaml` file with the same structure as the initial one. diff --git a/docs/reference/deployments/helm.md b/docs/reference/deployments/helm.md index 0bc1733d9..2d8dab7b4 100644 --- a/docs/reference/deployments/helm.md +++ b/docs/reference/deployments/helm.md @@ -1,3 +1,5 @@ + + +# Helm Integration kluctl offers a simple-to-use Helm integration, which allows you to reuse many common third-party Helm Charts. diff --git a/docs/reference/deployments/hooks.md b/docs/reference/deployments/hooks.md index db83a5a5b..9764f0051 100644 --- a/docs/reference/deployments/hooks.md +++ b/docs/reference/deployments/hooks.md @@ -1,3 +1,5 @@ + + +# Hooks Kluctl supports hooks in a similar fashion as known from Helm Charts. Hooks are executed/deployed before and/or after the actual deployment of a kustomize deployment. diff --git a/docs/reference/deployments/images.md b/docs/reference/deployments/images.md index 3c419381c..6f2a7e3cb 100644 --- a/docs/reference/deployments/images.md +++ b/docs/reference/deployments/images.md @@ -1,3 +1,5 @@ + + +# Container Images There are usually 2 different scenarios where Container Images need to be specified: 1. When deploying third party applications like nginx, redis, ... (e.g. via the [Helm integration]({{< ref "./helm" >}})).
diff --git a/docs/reference/deployments/kustomize.md b/docs/reference/deployments/kustomize.md index d2099397c..bcf694096 100644 --- a/docs/reference/deployments/kustomize.md +++ b/docs/reference/deployments/kustomize.md @@ -1,3 +1,5 @@ + + +# Kustomize Integration kluctl uses [kustomize](https://kustomize.io/) to render final resources. This means, that the finest/lowest level in kluctl is represented with kustomize deployments. These kustomize deployments can then perform further diff --git a/docs/reference/deployments/readiness.md b/docs/reference/deployments/readiness.md index 4712fc172..d571f969f 100644 --- a/docs/reference/deployments/readiness.md +++ b/docs/reference/deployments/readiness.md @@ -1,3 +1,5 @@ + + +# Readiness There are multiple places where kluctl can wait for "readiness" of resources, e.g. for hooks or when `waitReadiness` is specified on a deployment item. Readiness depends on the resource kind, e.g. for a Job, kluctl would wait until it diff --git a/docs/reference/deployments/tags.md b/docs/reference/deployments/tags.md index bf2a00d86..9501d0845 100644 --- a/docs/reference/deployments/tags.md +++ b/docs/reference/deployments/tags.md @@ -1,8 +1,13 @@ + + +# Tags Every kustomize deployment has a set of tags assigned to it. These tags are defined in multiple places, which is documented in [deployment.yaml]({{< ref "./deployment-yml" >}}). Look for the `tags` field, which is available in multiple places per diff --git a/docs/reference/kluctl-project/README.md b/docs/reference/kluctl-project/README.md index 32d6bd66b..eedd06aaa 100644 --- a/docs/reference/kluctl-project/README.md +++ b/docs/reference/kluctl-project/README.md @@ -1,3 +1,5 @@ + + +# Kluctl project The `.kluctl.yaml` is the central configuration and entry point for your deployments. It defines where the actual [deployment project]({{< ref "docs/reference/deployments" >}}) is located, @@ -57,4 +62,7 @@ secretsConfig: ## Allowed fields -Please check the sub-sections of this section to see which fields are allowed at the root level of `.kluctl.yaml`. +Please check the following sub-sections of this section to see which fields are allowed at the root level of `.kluctl.yaml`. + +1. [targets](./targets) +2. [secretsConfig](./secrets-config) diff --git a/docs/reference/kluctl-project/secrets-config/README.md b/docs/reference/kluctl-project/secrets-config/README.md index 4aa9394f1..41663bdb2 100644 --- a/docs/reference/kluctl-project/secrets-config/README.md +++ b/docs/reference/kluctl-project/secrets-config/README.md @@ -1,3 +1,5 @@ + + +# secretsConfig This configures how secrets are retrieved while sealing. It is basically a list of named secret sets which can be referenced from targets. diff --git a/docs/reference/kluctl-project/targets/README.md b/docs/reference/kluctl-project/targets/README.md index 63951d5f0..9349cc476 100644 --- a/docs/reference/kluctl-project/targets/README.md +++ b/docs/reference/kluctl-project/targets/README.md @@ -1,3 +1,5 @@ + + +# targets Specifies a list of targets for which commands can be invoked. A target puts together environment/target specific configuration and the target cluster. Multiple targets can exist which target the same cluster but with differing diff --git a/docs/reference/kluctl-project/targets/dynamic-targets.md b/docs/reference/kluctl-project/targets/dynamic-targets.md index f15ed7506..158262cdc 100644 --- a/docs/reference/kluctl-project/targets/dynamic-targets.md +++ b/docs/reference/kluctl-project/targets/dynamic-targets.md @@ -1,3 +1,5 @@ + + +# Dynamic Targets Targets can also be "dynamic", meaning that additional configuration can be sourced from another git repository. This can be based on a single target repository and branch, or on a target repository and branch/ref pattern, resulting diff --git a/docs/reference/sealed-secrets.md b/docs/reference/sealed-secrets.md index fd9bf6fb3..8c7ebca61 100644 --- a/docs/reference/sealed-secrets.md +++ b/docs/reference/sealed-secrets.md @@ -1,3 +1,5 @@ + + +# Sealed Secrets kluctl has an integration for [sealed secrets](https://github.com/bitnami-labs/sealed-secrets), allowing you to securely store secrets for multiple target clusters and/or environments inside version control. The integration consists of two parts: 1. Sealing of secrets -1. Automatically choosing and deploying the correct sealed secrets for a target +2. Automatically choosing and deploying the correct sealed secrets for a target ## Requirements diff --git a/docs/reference/templating/README.md b/docs/reference/templating/README.md index ddef4c163..fc2a26c3b 100644 --- a/docs/reference/templating/README.md +++ b/docs/reference/templating/README.md @@ -1,3 +1,5 @@ + + +# Table of Contents + +1. [Predefined Variables](./predefined-variables.md) +2. [Variable Sources](./variable-sources.md) +3. [Filters](./filters.md) +4. [Functions](./functions.md) + +# Templating kluctl uses a Jinja2 Templating engine to pre-process/render every involved configuration file and resource before actually interpreting it. Only files that are explicitly excluded via [.templateignore files](#templateignore) diff --git a/docs/reference/templating/filters.md b/docs/reference/templating/filters.md index 0f556d1f3..6495f57cd 100644 --- a/docs/reference/templating/filters.md +++ b/docs/reference/templating/filters.md @@ -1,3 +1,5 @@ + + +# Filters In addition to the [builtin Jinja2 filters](https://jinja.palletsprojects.com/en/2.11.x/templates/#list-of-builtin-filters), kluctl provides a few additional filters: diff --git a/docs/reference/templating/functions.md b/docs/reference/templating/functions.md index 50c474ff9..ad4894474 100644 --- a/docs/reference/templating/functions.md +++ b/docs/reference/templating/functions.md @@ -1,3 +1,5 @@ + + +# Functions In addition to the provided [builtin global functions](https://jinja.palletsprojects.com/en/2.11.x/templates/#list-of-global-functions), diff --git a/docs/reference/templating/predefined-variables.md b/docs/reference/templating/predefined-variables.md index a5aabcee1..75c962937 100644 --- a/docs/reference/templating/predefined-variables.md +++ b/docs/reference/templating/predefined-variables.md @@ -1,3 +1,5 @@ + + +# Predefined Variables There are multiple variables available which are pre-defined by kluctl. These are: diff --git a/docs/reference/templating/variable-sources.md b/docs/reference/templating/variable-sources.md index 34ea6bf09..368e15eb6 100644 --- a/docs/reference/templating/variable-sources.md +++ b/docs/reference/templating/variable-sources.md @@ -1,3 +1,5 @@ + + +# Variable Sources There are multiple places in deployment projects (deployment.yaml) where additional variables can be loaded into future Jinja2 contexts. diff --git a/install/README.md b/install/README.md index a02fc9390..12aca8ecb 100644 --- a/install/README.md +++ b/install/README.md @@ -1,41 +1,3 @@ # kluctl Installation -Binaries for macOS and Linux AMD64 are available for download on the -[release page](https://github.com/kluctl/kluctl/releases). - -To install the latest release run: - -```bash -curl -s https://raw.githubusercontent.com/kluctl/kluctl/main/install/kluctl.sh | bash -``` - -The install script does the following: -* attempts to detect your OS -* downloads and unpacks the release tar file in a temporary directory -* copies the kluctl binary to `/usr/local/bin` -* removes the temporary directory - -## Alternative installation methods - -See https://kluctl.io/docs/installation for alternative installation methods. - -## Build from source - -Clone the repository: - -```bash -git clone https://github.com/kluctl/kluctl -cd kluctl -``` - -Build the `kluctl` binary (requires go >= 1.18 and python >= 3.10): - -```bash -make build -``` - -Run the binary: - -```bash -./bin/kluctl -h -``` +Read [installation](../docs/installation.md) for instructions. From ef2c8979a06cea33cc039131f9b9cd9d89274fd1 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 21 Oct 2022 15:49:18 +0200 Subject: [PATCH 1089/2916] ci: Use make in CI build --- .github/workflows/tests.yml | 48 +++++++++++++------------------------ Makefile | 17 ++++++++----- 2 files changed, 28 insertions(+), 37 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 012806f23..6caae2381 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -30,42 +30,28 @@ jobs: go test ./cmd/... ./pkg/... -v - name: Build kluctl (linux) run: | - export CGO_ENABLED=0 - export GOARCH=amd64 - export GOOS=linux - go build - go test -c ./e2e - mv kluctl kluctl-linux-amd64 - mv e2e.test e2e.test-linux-amd64 + make build GOARCH=amd64 GOOS=linux + make test-e2e-build GOARCH=amd64 GOOS=linux + mv ./bin/kluctl ./bin/kluctl-linux-amd64 + mv ./bin/kluctl-e2e ./bin/kluctl-e2e-linux-amd64 - name: Build kluctl (darwin) run: | - export CGO_ENABLED=0 - export GOARCH=amd64 - export GOOS=darwin - go build - go test -c ./e2e - mv kluctl kluctl-darwin-amd64 - mv e2e.test e2e.test-darwin-amd64 + make build GOARCH=amd64 GOOS=darwin + make test-e2e-build GOARCH=amd64 GOOS=darwin + mv ./bin/kluctl ./bin/kluctl-darwin-amd64 + mv ./bin/kluctl-e2e ./bin/kluctl-e2e-darwin-amd64 - name: Build kluctl (windows) run: | - export CGO_ENABLED=0 - export GOARCH=amd64 - export GOOS=windows - go build - go test -c ./e2e - mv kluctl.exe kluctl-windows-amd64.exe - mv e2e.test.exe e2e.test-windows-amd64.exe + make build GOARCH=amd64 GOOS=windows + make test-e2e-build GOARCH=amd64 GOOS=windows + mv ./bin/kluctl.exe ./bin/kluctl-windows-amd64.exe + mv ./bin/kluctl-e2e.exe ./bin/kluctl-e2e-windows-amd64.exe - name: Upload binaries uses: actions/upload-artifact@v2 with: - name: binaries + name: bin path: | - kluctl-linux-amd64 - kluctl-darwin-amd64 - kluctl-windows-amd64.exe - e2e.test-linux-amd64 - e2e.test-darwin-amd64 - e2e.test-windows-amd64.exe + ./bin tests: strategy: @@ -110,8 +96,8 @@ jobs: EXE=.exe fi - chmod +x ./binaries/* - mv ./binaries/e2e.test-${{ matrix.binary-suffix }}$EXE ./e2e.test$EXE + chmod +x ./bin/* + mv ./bin/kluctl-${{ matrix.binary-suffix }}$EXE ./kluctl$EXE + mv ./bin/kluctl-e2e-${{ matrix.binary-suffix }}$EXE ./kluctl-e2e$EXE - export KLUCTL_EXE=./binaries/kluctl-${{ matrix.binary-suffix }}$EXE make test-e2e-pre-built diff --git a/Makefile b/Makefile index d8a176b7f..b20f9f51e 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,14 @@ # Based on the work of Thomas Poignant (thomaspoignant) # https://gist.github.com/thomaspoignant/5b72d579bd5f311904d973652180c705 +EXE= +ifeq ($(GOOS), windows) +EXE=.exe +endif GOCMD=go GOTEST=$(GOCMD) test GOVET=$(GOCMD) vet -BINARY_NAME=kluctl -TEST_BINARY_NAME=kluctl-e2e -REQUIRED_ENV_VARS=GOOS GOARCH +BINARY_NAME=kluctl$(EXE) +TEST_BINARY_NAME=kluctl-e2e$(EXE) EXPORT_RESULT?=false # If gobin not set, create one on ./build and add to path. @@ -72,11 +75,13 @@ endif test: test-unit test-e2e ## Runs the complete test suite KUBEBUILDER_ASSETS?="$(shell $(ENVTEST) --arch=$(ENVTEST_ARCH) use -i $(ENVTEST_KUBERNETES_VERSION) --bin-dir=$(ENVTEST_ASSETS_DIR) -p path)" -test-e2e: install-envtest ## Runs the end to end tests - KUBEBUILDER_ASSETS=$(KUBEBUILDER_ASSETS) CGO_ENBALED=0 GO111MODULE=on $(GOCMD) test -o ./bin/$(TEST_BINARY_NAME) ./e2e -test.v +test-e2e: test-e2e-build test-e2e-pre-built ## Runs the end to end tests + +test-e2e-build: ## Builds the end to end tests + CGO_ENBALED=0 GO111MODULE=on $(GOCMD) test -c ./e2e -o ./bin/$(TEST_BINARY_NAME) test-e2e-pre-built: install-envtest - KUBEBUILDER_ASSETS=$(KUBEBUILDER_ASSETS) ./e2e.test -test.v + KUBEBUILDER_ASSETS=$(KUBEBUILDER_ASSETS) ./bin/$(TEST_BINARY_NAME) -test.v test-unit: ## Run the unit tests of the project ifeq ($(EXPORT_RESULT), true) From 3e4e5ff475c03c757c76dd30cc6b25b4d58deae2 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 21 Oct 2022 15:50:36 +0200 Subject: [PATCH 1090/2916] chore: Add replace-commands-help script and enforce up-to-date commands help --- .github/workflows/tests.yml | 9 ++ Makefile | 2 + internal/replace-commands-help/main.go | 172 +++++++++++++++++++++++++ 3 files changed, 183 insertions(+) create mode 100644 internal/replace-commands-help/main.go diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6caae2381..1b3f73653 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -46,6 +46,15 @@ jobs: make test-e2e-build GOARCH=amd64 GOOS=windows mv ./bin/kluctl.exe ./bin/kluctl-windows-amd64.exe mv ./bin/kluctl-e2e.exe ./bin/kluctl-e2e-windows-amd64.exe + - name: Verify commands help is up-to-date + run: | + make replace-commands-help + if [ ! -z "$(git status --porcelain)" ]; then + echo "replace-commands-help must be invoked and the result committed" + git status + git diff + exit 1 + fi - name: Upload binaries uses: actions/upload-artifact@v2 with: diff --git a/Makefile b/Makefile index b20f9f51e..6c4304f17 100644 --- a/Makefile +++ b/Makefile @@ -113,6 +113,8 @@ else endif docker run --rm -v $(shell pwd):/app -w /app golangci/golangci-lint:latest-alpine golangci-lint run $(OUTPUT_OPTIONS) +replace-commands-help: ## Replace commands help in docs + $(GOCMD) run ./internal/replace-commands-help --docs-dir ./docs/reference/commands ## Release: version: ## Write next version into version file diff --git a/internal/replace-commands-help/main.go b/internal/replace-commands-help/main.go new file mode 100644 index 000000000..6d4e781fb --- /dev/null +++ b/internal/replace-commands-help/main.go @@ -0,0 +1,172 @@ +package main + +import ( + "flag" + "fmt" + "github.com/kluctl/kluctl/v2/cmd/kluctl/commands" + "io/fs" + "io/ioutil" + "log" + "os" + "os/exec" + "path/filepath" + "reflect" + "regexp" + "strings" + "syscall" +) + +var docsDir = flag.String("docs-dir", "", "Path to documentation") + +type section struct { + start int + end int + command string + section string + code bool +} + +func main() { + _ = syscall.Setenv("COLUMNS", "120") + + if os.Getenv("CALL_KLUCTL") == "true" { + kluctlMain() + return + } + + flag.Parse() + + filepath.WalkDir(*docsDir, func(path string, d fs.DirEntry, err error) error { + if !strings.HasSuffix(path, ".md") { + return nil + } + processFile(path) + return nil + }) +} + +func kluctlMain() { + commands.Execute() +} + +func processFile(path string) { + text, err := ioutil.ReadFile(path) + if err != nil { + log.Fatal(err) + } + lines := strings.Split(string(text), "\n") + + var newLines []string + pos := 0 + for true { + s := findNextSection(lines, pos) + if s == nil { + newLines = append(newLines, lines[pos:]...) + break + } + + newLines = append(newLines, lines[pos:s.start+1]...) + + s2 := getHelpSection(s.command, s.section) + + if s.code { + newLines = append(newLines, "```") + } + newLines = append(newLines, s2...) + if s.code { + newLines = append(newLines, "```") + } + + newLines = append(newLines, lines[s.end]) + pos = s.end + 1 + } + + if !reflect.DeepEqual(lines, newLines) { + err = ioutil.WriteFile(path, []byte(strings.Join(newLines, "\n")), 0o600) + if err != nil { + log.Fatal(err) + } + } +} + +var beginPattern = regexp.MustCompile(``) +var endPattern = regexp.MustCompile(``) + +func findNextSection(lines []string, start int) *section { + + for i := start; i < len(lines); i++ { + m := beginPattern.FindSubmatch([]byte(lines[i])) + if m == nil { + continue + } + + var s section + s.start = i + s.command = string(m[1]) + s.section = string(m[2]) + s.code = string(m[3]) == "true" + + for j := i + 1; j < len(lines); j++ { + m = endPattern.FindSubmatch([]byte(lines[j])) + if m == nil { + continue + } + s.end = j + return &s + } + } + return nil +} + +func countIndent(str string) int { + for i := 0; i < len(str); i++ { + if str[i] != ' ' { + return i + } + } + return 0 +} + +func getHelpSection(command string, section string) []string { + log.Printf("Getting section '%s' from command '%s'", section, command) + + exe, err := os.Executable() + if err != nil { + log.Fatal(err) + } + + helpCmd := exec.Command(exe, command, "--help") + helpCmd.Env = os.Environ() + helpCmd.Env = append(helpCmd.Env, "CALL_KLUCTL=true") + + out, err := helpCmd.CombinedOutput() + if err != nil { + log.Fatal(err) + } + + lines := strings.Split(string(out), "\n") + + sectionStart := -1 + for i := 0; i < len(lines); i++ { + indent := countIndent(lines[i]) + if strings.HasPrefix(lines[i][indent:], fmt.Sprintf("%s:", section)) { + sectionStart = i + break + } + } + if sectionStart == -1 { + log.Fatalf("Section %s not found in command %s", section, command) + } + + var ret []string + ret = append(ret, lines[sectionStart]) + for i := sectionStart + 1; i < len(lines); i++ { + indent := countIndent(lines[i]) + if len(lines[i]) != 0 && indent == 0 && lines[i][len(lines[i])-1] == ':' { + // new section has started + break + } + ret = append(ret, lines[i]) + } + return ret +} From 27a85b75aa266951b47d6d10cbbda7650ad3329e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 21 Oct 2022 16:02:58 +0200 Subject: [PATCH 1091/2916] tests: Implement a hack that removes the need for kluctl to be pre-build for e2e tests --- .github/workflows/tests.yml | 13 +++---------- e2e/call_kluctl_hack.go | 19 +++++++++++++++++++ e2e/default_clusters.go | 4 ++++ e2e/project.go | 24 +++++++----------------- 4 files changed, 33 insertions(+), 27 deletions(-) create mode 100644 e2e/call_kluctl_hack.go diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1b3f73653..20f223be9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -28,23 +28,17 @@ jobs: - name: Run unit tests run: | go test ./cmd/... ./pkg/... -v - - name: Build kluctl (linux) + - name: Build kluctl-e2e (linux) run: | - make build GOARCH=amd64 GOOS=linux make test-e2e-build GOARCH=amd64 GOOS=linux - mv ./bin/kluctl ./bin/kluctl-linux-amd64 mv ./bin/kluctl-e2e ./bin/kluctl-e2e-linux-amd64 - - name: Build kluctl (darwin) + - name: Build kluctl-e2e (darwin) run: | - make build GOARCH=amd64 GOOS=darwin make test-e2e-build GOARCH=amd64 GOOS=darwin - mv ./bin/kluctl ./bin/kluctl-darwin-amd64 mv ./bin/kluctl-e2e ./bin/kluctl-e2e-darwin-amd64 - - name: Build kluctl (windows) + - name: Build kluctl-e2e (windows) run: | - make build GOARCH=amd64 GOOS=windows make test-e2e-build GOARCH=amd64 GOOS=windows - mv ./bin/kluctl.exe ./bin/kluctl-windows-amd64.exe mv ./bin/kluctl-e2e.exe ./bin/kluctl-e2e-windows-amd64.exe - name: Verify commands help is up-to-date run: | @@ -106,7 +100,6 @@ jobs: fi chmod +x ./bin/* - mv ./bin/kluctl-${{ matrix.binary-suffix }}$EXE ./kluctl$EXE mv ./bin/kluctl-e2e-${{ matrix.binary-suffix }}$EXE ./kluctl-e2e$EXE make test-e2e-pre-built diff --git a/e2e/call_kluctl_hack.go b/e2e/call_kluctl_hack.go new file mode 100644 index 000000000..6da895464 --- /dev/null +++ b/e2e/call_kluctl_hack.go @@ -0,0 +1,19 @@ +package e2e + +import ( + "github.com/kluctl/kluctl/v2/cmd/kluctl/commands" + "os" +) + +func isCallKluctlHack() bool { + return os.Getenv("CALL_KLUCTL") == "true" +} + +func init() { + // We use the Golang's initializing mechanism to run kluctl even though the test executable was invoked + // This is clearly a hack, but it avoids the requirement to have a kluctl executable pre-built + if isCallKluctlHack() { + commands.Execute() + os.Exit(0) + } +} diff --git a/e2e/default_clusters.go b/e2e/default_clusters.go index 3d0f10fd8..ed28ccbbb 100644 --- a/e2e/default_clusters.go +++ b/e2e/default_clusters.go @@ -9,6 +9,10 @@ var defaultCluster1 = test_utils.CreateEnvTestCluster("cluster1") var defaultCluster2 = test_utils.CreateEnvTestCluster("cluster2") func init() { + if isCallKluctlHack() { + return + } + var wg sync.WaitGroup wg.Add(2) go func() { diff --git a/e2e/project.go b/e2e/project.go index 3249dd8c5..b6f74c66f 100644 --- a/e2e/project.go +++ b/e2e/project.go @@ -337,27 +337,17 @@ func (p *testProject) Kluctl(argsIn ...string) (string, string, error) { env = append(env, p.extraEnv...) env = append(env, fmt.Sprintf("KUBECONFIG=%s", p.mergedKubeconfig)) + // this will cause the init() function from call_kluctl_hack.go to invoke the kluctl root command and then exit + env = append(env, "CALL_KLUCTL=true") + p.t.Logf("Runnning kluctl: %s", strings.Join(args, " ")) - kluctlExe := os.Getenv("KLUCTL_EXE") - if kluctlExe == "" { - curDir, _ := os.Getwd() - for i, p := range env { - x := strings.SplitN(p, "=", 2) - if x[0] == "PATH" { - env[i] = fmt.Sprintf("PATH=%s%c%s%c%s", curDir, os.PathListSeparator, filepath.Join(curDir, ".."), os.PathListSeparator, x[1]) - } - } - kluctlExe = "kluctl" - } else { - p, err := filepath.Abs(kluctlExe) - if err != nil { - return "", "", err - } - kluctlExe = p + testExe, err := os.Executable() + if err != nil { + panic(err) } - cmd := exec.Command(kluctlExe, args...) + cmd := exec.Command(testExe, args...) cmd.Dir = cwd cmd.Env = env From 2be5c441af959e2d125ea197d449dfa5db2a42d5 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 21 Oct 2022 16:13:47 +0200 Subject: [PATCH 1092/2916] tests: Build e2e tests with -race --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 6c4304f17..ffa103c7d 100644 --- a/Makefile +++ b/Makefile @@ -78,7 +78,7 @@ KUBEBUILDER_ASSETS?="$(shell $(ENVTEST) --arch=$(ENVTEST_ARCH) use -i $(ENVTEST_ test-e2e: test-e2e-build test-e2e-pre-built ## Runs the end to end tests test-e2e-build: ## Builds the end to end tests - CGO_ENBALED=0 GO111MODULE=on $(GOCMD) test -c ./e2e -o ./bin/$(TEST_BINARY_NAME) + CGO_ENBALED=0 GO111MODULE=on $(GOCMD) test -race -c ./e2e -o ./bin/$(TEST_BINARY_NAME) test-e2e-pre-built: install-envtest KUBEBUILDER_ASSETS=$(KUBEBUILDER_ASSETS) ./bin/$(TEST_BINARY_NAME) -test.v From d0433bafd0b64146e9ac21822c8d24731c1615e5 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 21 Oct 2022 16:15:22 +0200 Subject: [PATCH 1093/2916] ci: Fix path to ./bin/kluctl-e2e --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 20f223be9..6e24c9004 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -100,6 +100,6 @@ jobs: fi chmod +x ./bin/* - mv ./bin/kluctl-e2e-${{ matrix.binary-suffix }}$EXE ./kluctl-e2e$EXE + mv ./bin/kluctl-e2e-${{ matrix.binary-suffix }}$EXE ./bin/kluctl-e2e$EXE make test-e2e-pre-built From ce112fb489be7cadefda4844335bbbeefa2f865b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 21 Oct 2022 16:30:15 +0200 Subject: [PATCH 1094/2916] chore: Remove CGO_ENABLED=0 We need CGO to enable -race --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index ffa103c7d..0431f4133 100644 --- a/Makefile +++ b/Makefile @@ -37,7 +37,7 @@ build: build-go ## Run the complete build pipeline build-go: ## Build your project and put the output binary in ./bin/ mkdir -p ./bin - CGO_ENBALED=0 GO111MODULE=on $(GOCMD) build -o ./bin/$(BINARY_NAME) + GO111MODULE=on $(GOCMD) build -o ./bin/$(BINARY_NAME) clean: ## Remove build related file rm -fr ./bin @@ -78,7 +78,7 @@ KUBEBUILDER_ASSETS?="$(shell $(ENVTEST) --arch=$(ENVTEST_ARCH) use -i $(ENVTEST_ test-e2e: test-e2e-build test-e2e-pre-built ## Runs the end to end tests test-e2e-build: ## Builds the end to end tests - CGO_ENBALED=0 GO111MODULE=on $(GOCMD) test -race -c ./e2e -o ./bin/$(TEST_BINARY_NAME) + GO111MODULE=on $(GOCMD) test -race -c ./e2e -o ./bin/$(TEST_BINARY_NAME) test-e2e-pre-built: install-envtest KUBEBUILDER_ASSETS=$(KUBEBUILDER_ASSETS) ./bin/$(TEST_BINARY_NAME) -test.v From 1fcfd5317490f6c9b49aaae8cedef690bcee889c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 21 Oct 2022 16:31:52 +0200 Subject: [PATCH 1095/2916] chore: Remove GO111MODULE=on as it is the default now --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 0431f4133..5cd44b6c1 100644 --- a/Makefile +++ b/Makefile @@ -37,7 +37,7 @@ build: build-go ## Run the complete build pipeline build-go: ## Build your project and put the output binary in ./bin/ mkdir -p ./bin - GO111MODULE=on $(GOCMD) build -o ./bin/$(BINARY_NAME) + $(GOCMD) build -o ./bin/$(BINARY_NAME) clean: ## Remove build related file rm -fr ./bin @@ -78,7 +78,7 @@ KUBEBUILDER_ASSETS?="$(shell $(ENVTEST) --arch=$(ENVTEST_ARCH) use -i $(ENVTEST_ test-e2e: test-e2e-build test-e2e-pre-built ## Runs the end to end tests test-e2e-build: ## Builds the end to end tests - GO111MODULE=on $(GOCMD) test -race -c ./e2e -o ./bin/$(TEST_BINARY_NAME) + $(GOCMD) test -race -c ./e2e -o ./bin/$(TEST_BINARY_NAME) test-e2e-pre-built: install-envtest KUBEBUILDER_ASSETS=$(KUBEBUILDER_ASSETS) ./bin/$(TEST_BINARY_NAME) -test.v From fdd1e72d8d3bc7c66a178f9ac8c12294622ce358 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 21 Oct 2022 17:02:38 +0200 Subject: [PATCH 1096/2916] ci: Only run race detection on linux --- Makefile | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 5cd44b6c1..92f691a82 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,12 @@ EXE= ifeq ($(GOOS), windows) EXE=.exe endif + +RACE= +ifeq ($(GOOS), linux) +RACE=-race +endif + GOCMD=go GOTEST=$(GOCMD) test GOVET=$(GOCMD) vet @@ -78,7 +84,7 @@ KUBEBUILDER_ASSETS?="$(shell $(ENVTEST) --arch=$(ENVTEST_ARCH) use -i $(ENVTEST_ test-e2e: test-e2e-build test-e2e-pre-built ## Runs the end to end tests test-e2e-build: ## Builds the end to end tests - $(GOCMD) test -race -c ./e2e -o ./bin/$(TEST_BINARY_NAME) + $(GOCMD) test $(RACE) -c ./e2e -o ./bin/$(TEST_BINARY_NAME) test-e2e-pre-built: install-envtest KUBEBUILDER_ASSETS=$(KUBEBUILDER_ASSETS) ./bin/$(TEST_BINARY_NAME) -test.v @@ -89,7 +95,7 @@ ifeq ($(EXPORT_RESULT), true) GO111MODULE=off $(GOCMD) get -u github.com/jstemmer/go-junit-report $(eval OUTPUT_OPTIONS = | tee /dev/tty | go-junit-report -set-exit-code > reports/test-unit/junit-report.xml) endif - $(GOTEST) -v -race $(shell go list ./... | grep -v 'v2/e2e') $(OUTPUT_OPTIONS) + $(GOTEST) -v $(RACE) $(shell go list ./... | grep -v 'v2/e2e') $(OUTPUT_OPTIONS) coverage-unit: ## Run the unit tests of the project and export the coverage $(GOTEST) -cover -covermode=count -coverprofile=reports/coverage-unit/profile.cov $(shell go list ./... | grep -v /e2e/) From 6b649574432a23781ec3b2b9d8f0039be047795b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 21 Oct 2022 17:09:24 +0200 Subject: [PATCH 1097/2916] docs: Replace hugo refs with simple MD links --- docs/concepts.md | 14 ++++++------ docs/installation.md | 4 +--- docs/reference/commands/common-arguments.md | 2 +- docs/reference/commands/delete.md | 6 ++--- docs/reference/commands/deploy.md | 6 ++--- docs/reference/commands/diff.md | 6 ++--- .../commands/environment-variables.md | 2 +- docs/reference/commands/helm-pull.md | 4 ++-- docs/reference/commands/helm-update.md | 2 +- docs/reference/commands/list-images.md | 6 ++--- docs/reference/commands/poke-images.md | 6 ++--- docs/reference/commands/prune.md | 6 ++--- docs/reference/commands/render.md | 4 ++-- docs/reference/commands/seal.md | 4 ++-- docs/reference/commands/validate.md | 4 ++-- docs/reference/deployments/README.md | 10 ++++----- .../deployments/annotations/all-resources.md | 8 +++---- .../deployments/annotations/hooks.md | 4 ++-- .../deployments/annotations/kustomization.md | 4 ++-- .../deployments/annotations/validation.md | 2 +- docs/reference/deployments/deployment-yml.md | 22 +++++++++---------- docs/reference/deployments/helm.md | 16 +++++++------- docs/reference/deployments/hooks.md | 2 +- docs/reference/deployments/images.md | 4 ++-- docs/reference/deployments/tags.md | 14 ++++++------ docs/reference/kluctl-project/README.md | 6 ++--- .../kluctl-project/secrets-config/README.md | 2 +- .../kluctl-project/targets/README.md | 20 ++++++++--------- .../kluctl-project/targets/dynamic-targets.md | 4 ++-- docs/reference/sealed-secrets.md | 20 ++++++++--------- docs/reference/templating/functions.md | 4 ++-- .../templating/predefined-variables.md | 10 ++++----- docs/reference/templating/variable-sources.md | 4 ++-- 33 files changed, 115 insertions(+), 117 deletions(-) diff --git a/docs/concepts.md b/docs/concepts.md index b1ff352a0..946f69c0f 100644 --- a/docs/concepts.md +++ b/docs/concepts.md @@ -13,21 +13,21 @@ These are some core concepts in Kluctl. ## Kluctl project The kluctl project defines targets and secret sources. -It is defined via the [.kluctl.yaml]({{< ref "docs/reference/kluctl-project" >}}) configuration file. +It is defined via the [.kluctl.yaml](./reference/kluctl-project) configuration file. ## Targets A target defines a target cluster and a set of deployment arguments. Multiple targets can use the same cluster. Targets allow implementing multi-cluster, multi-environment, multi-customer, ... deployments. ## Deployments -A [deployment]({{< ref "docs/reference/deployments" >}}) defines which Kustomize deployments and which sub-deployments +A [deployment](./reference/deployments) defines which Kustomize deployments and which sub-deployments to deploy. It also controls the order of deployments. Deployments may be configured through deployment arguments, which are typically provided via the targets but might also be provided through the CLI. ## Variables -[Variables]({{< ref "docs/reference/templating" >}}) are the main source of configuration. They are either loaded yaml +[Variables](./reference/templating) are the main source of configuration. They are either loaded yaml files or directly defined inside deployments. Each variables file that is loaded has access to all the variables which were defined before, allowing complex composition of configuration. @@ -36,16 +36,16 @@ After being loaded, variables are usable through the templating engine at all ne ## Templating All configuration files (including .kluctl.yaml and deployment.yaml) and all Kubernetes manifests involved are processed through a templating engine. -The [templating engine]({{< ref "docs/reference/templating" >}}) allows simple variable substitution and also complex +The [templating engine](./reference/templating) allows simple variable substitution and also complex control structures (if/else, for loops, ...). ## Secrets -Secrets are loaded from [external sources]({{< ref "docs/reference/kluctl-project" >}}) and are only available -while [sealing]({{< ref "docs/reference/sealed-secrets" >}}). After the sealing process, only the public-key encrypted +Secrets are loaded from [external sources](./reference/kluctl-project) and are only available +while [sealing](./reference/sealed-secrets). After the sealing process, only the public-key encrypted sealed secrets are available. ## Sealed Secrets -[Sealed Secrets]({{< ref "docs/reference/sealed-secrets" >}}) are based on +[Sealed Secrets](./reference/sealed-secrets) are based on [Bitnami's sealed-secrets controller](https://github.com/bitnami-labs/sealed-secrets). Kluctl offers integration of sealed secrets through the `seal` command. Kluctl allows managing multiple sets of sealed secrets for multiple targets. diff --git a/docs/installation.md b/docs/installation.md index 3bf922ec7..bd42145bf 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -61,7 +61,7 @@ Run the binary: -{{% /tabs %}} ``` diff --git a/docs/reference/commands/delete.md b/docs/reference/commands/delete.md index 70483166f..33e8e2acd 100644 --- a/docs/reference/commands/delete.md +++ b/docs/reference/commands/delete.md @@ -24,9 +24,9 @@ take the local target/state into account! ## Arguments The following sets of arguments are available: -1. [project arguments]({{< ref "./common-arguments#project-arguments" >}}) -1. [image arguments]({{< ref "./common-arguments#image-arguments" >}}) -1. [inclusion/exclusion arguments]({{< ref "./common-arguments#inclusionexclusion-arguments" >}}) +1. [project arguments](./common-arguments#project-arguments) +1. [image arguments](./common-arguments#image-arguments) +1. [inclusion/exclusion arguments](./common-arguments#inclusionexclusion-arguments) In addition, the following arguments are available: diff --git a/docs/reference/commands/deploy.md b/docs/reference/commands/deploy.md index 7ecfc1b24..65e472674 100644 --- a/docs/reference/commands/deploy.md +++ b/docs/reference/commands/deploy.md @@ -22,9 +22,9 @@ It will also output a list of prunable objects (without actually deleting them). ## Arguments The following sets of arguments are available: -1. [project arguments]({{< ref "./common-arguments#project-arguments" >}}) -1. [image arguments]({{< ref "./common-arguments#image-arguments" >}}) -1. [inclusion/exclusion arguments]({{< ref "./common-arguments#inclusionexclusion-arguments" >}}) +1. [project arguments](./common-arguments#project-arguments) +1. [image arguments](./common-arguments#image-arguments) +1. [inclusion/exclusion arguments](./common-arguments#inclusionexclusion-arguments) In addition, the following arguments are available: diff --git a/docs/reference/commands/diff.md b/docs/reference/commands/diff.md index c9d4546f4..d54d6091b 100644 --- a/docs/reference/commands/diff.md +++ b/docs/reference/commands/diff.md @@ -23,9 +23,9 @@ After the diff is performed, the command will also search for prunable objects a ## Arguments The following sets of arguments are available: -1. [project arguments]({{< ref "./common-arguments#project-arguments" >}}) -1. [image arguments]({{< ref "./common-arguments#image-arguments" >}}) -1. [inclusion/exclusion arguments]({{< ref "./common-arguments#inclusionexclusion-arguments" >}}) +1. [project arguments](./common-arguments#project-arguments) +1. [image arguments](./common-arguments#image-arguments) +1. [inclusion/exclusion arguments](./common-arguments#inclusionexclusion-arguments) In addition, the following arguments are available: diff --git a/docs/reference/commands/environment-variables.md b/docs/reference/commands/environment-variables.md index 6db995487..2247be7cf 100644 --- a/docs/reference/commands/environment-variables.md +++ b/docs/reference/commands/environment-variables.md @@ -20,6 +20,6 @@ underscores. As an example, `--dry-run` can also be specified with the environme ## Additional environment variables A few additional environment variables are supported which do not belong to an option/argument. These are: -1. `KLUCTL_REGISTRY__HOST`, `KLUCTL_REGISTRY__USERNAME`, and so on. See [registries]({{< ref "docs/reference/deployments/images#supported-image-registries-and-authentication" >}}) for details. +1. `KLUCTL_REGISTRY__HOST`, `KLUCTL_REGISTRY__USERNAME`, and so on. See [registries](../deployments/images#supported-image-registries-and-authentication) for details. 2. `KLUCTL_GIT__HOST`, `KLUCTL_GIT__USERNAME`, and so on. 3. `KLUCTL_SSH_DISABLE_STRICT_HOST_KEY_CHECKING`. Disable ssh host key checking when accessing git repositories. diff --git a/docs/reference/commands/helm-pull.md b/docs/reference/commands/helm-pull.md index 80fb685a3..68d3871b4 100644 --- a/docs/reference/commands/helm-pull.md +++ b/docs/reference/commands/helm-pull.md @@ -20,8 +20,8 @@ pulling is only needed when really required (e.g. when the chart version changes -See [helm-integration]({{< ref "docs/reference/deployments/helm">}}) for more details. +See [helm-integration](../deployments/helm) for more details. ## Arguments The following sets of arguments are available: -1. [project arguments]({{< ref "./common-arguments#project-arguments" >}}) (except `-a`) +1. [project arguments](./common-arguments#project-arguments) (except `-a`) diff --git a/docs/reference/commands/helm-update.md b/docs/reference/commands/helm-update.md index e1a526ae8..d719c76a0 100644 --- a/docs/reference/commands/helm-update.md +++ b/docs/reference/commands/helm-update.md @@ -20,7 +20,7 @@ Optionally performs the actual upgrade and/or add a commit to version control. ## Arguments The following sets of arguments are available: -1. [project arguments]({{< ref "./common-arguments#project-arguments" >}}) (except `-a`) +1. [project arguments](./common-arguments#project-arguments) (except `-a`) In addition, the following arguments are available: diff --git a/docs/reference/commands/list-images.md b/docs/reference/commands/list-images.md index ce0f2aaba..5d59e6949 100644 --- a/docs/reference/commands/list-images.md +++ b/docs/reference/commands/list-images.md @@ -23,9 +23,9 @@ as described in for the deploy command. ## Arguments The following sets of arguments are available: -1. [project arguments]({{< ref "./common-arguments#project-arguments" >}}) -1. [image arguments]({{< ref "./common-arguments#image-arguments" >}}) -1. [inclusion/exclusion arguments]({{< ref "./common-arguments#inclusionexclusion-arguments" >}}) +1. [project arguments](./common-arguments#project-arguments) +1. [image arguments](./common-arguments#image-arguments) +1. [inclusion/exclusion arguments](./common-arguments#inclusionexclusion-arguments) In addition, the following arguments are available: diff --git a/docs/reference/commands/poke-images.md b/docs/reference/commands/poke-images.md index 585ae6d47..b5279c256 100644 --- a/docs/reference/commands/poke-images.md +++ b/docs/reference/commands/poke-images.md @@ -22,9 +22,9 @@ replaced ## Arguments The following sets of arguments are available: -1. [project arguments]({{< ref "./common-arguments#project-arguments" >}}) -1. [image arguments]({{< ref "./common-arguments#image-arguments" >}}) -1. [inclusion/exclusion arguments]({{< ref "./common-arguments#inclusionexclusion-arguments" >}}) +1. [project arguments](./common-arguments#project-arguments) +1. [image arguments](./common-arguments#image-arguments) +1. [inclusion/exclusion arguments](./common-arguments#inclusionexclusion-arguments) In addition, the following arguments are available: diff --git a/docs/reference/commands/prune.md b/docs/reference/commands/prune.md index a895db0e8..86161c3ea 100644 --- a/docs/reference/commands/prune.md +++ b/docs/reference/commands/prune.md @@ -18,9 +18,9 @@ Searches the target cluster for prunable objects and deletes them ## Arguments The following sets of arguments are available: -1. [project arguments]({{< ref "./common-arguments#project-arguments" >}}) -1. [image arguments]({{< ref "./common-arguments#image-arguments" >}}) -1. [inclusion/exclusion arguments]({{< ref "./common-arguments#inclusionexclusion-arguments" >}}) +1. [project arguments](./common-arguments#project-arguments) +1. [image arguments](./common-arguments#image-arguments) +1. [inclusion/exclusion arguments](./common-arguments#inclusionexclusion-arguments) In addition, the following arguments are available: diff --git a/docs/reference/commands/render.md b/docs/reference/commands/render.md index c2b626415..05a48e59c 100644 --- a/docs/reference/commands/render.md +++ b/docs/reference/commands/render.md @@ -21,8 +21,8 @@ a temporary directory or a specified directory. ## Arguments The following sets of arguments are available: -1. [project arguments]({{< ref "./common-arguments#project-arguments" >}}) -1. [image arguments]({{< ref "./common-arguments#image-arguments" >}}) +1. [project arguments](./common-arguments#project-arguments) +1. [image arguments](./common-arguments#image-arguments) In addition, the following arguments are available: diff --git a/docs/reference/commands/seal.md b/docs/reference/commands/seal.md index ee99cba1c..88edc02c7 100644 --- a/docs/reference/commands/seal.md +++ b/docs/reference/commands/seal.md @@ -23,11 +23,11 @@ If no '--target' is specified, sealing is performed for all targets. -See [sealed-secrets]({{< ref "docs/reference/sealed-secrets">}}) for more details. +See [sealed-secrets](../sealed-secrets) for more details. ## Arguments The following sets of arguments are available: -1. [project arguments]({{< ref "./common-arguments#project-arguments" >}}) (except `-a`) +1. [project arguments](./common-arguments#project-arguments) (except `-a`) In addition, the following arguments are available: diff --git a/docs/reference/commands/validate.md b/docs/reference/commands/validate.md index 5ff5a0a6e..d904c98fb 100644 --- a/docs/reference/commands/validate.md +++ b/docs/reference/commands/validate.md @@ -22,8 +22,8 @@ TODO: This needs to be better documented! ## Arguments The following sets of arguments are available: -1. [project arguments]({{< ref "./common-arguments#project-arguments" >}}) -1. [image arguments]({{< ref "./common-arguments#image-arguments" >}}) +1. [project arguments](./common-arguments#project-arguments) +1. [image arguments](./common-arguments#image-arguments) In addition, the following arguments are available: diff --git a/docs/reference/deployments/README.md b/docs/reference/deployments/README.md index 08c0d07d9..d4e211174 100644 --- a/docs/reference/deployments/README.md +++ b/docs/reference/deployments/README.md @@ -21,7 +21,7 @@ description: > 8. [Annotations](./annotations) A deployment project is collection of deployment items and sub-deployments. Deployment items are usually -[Kustomize]({{< ref "./kustomize" >}}) deployments, but can also integrate [Helm Charts]({{< ref "./helm" >}}). +[Kustomize](./kustomize.md) deployments, but can also integrate [Helm Charts](./helm.md). ## Basic structure @@ -32,14 +32,14 @@ provides some additional configuration required for multiple kluctl features to As can be seen, sub-deployments can include other sub-deployments, allowing you to structure the deployment project as you need. -Each level in this structure recursively adds [tags]({{< ref "./tags" >}}) to each deployed resources, allowing you to control +Each level in this structure recursively adds [tags](./tags.md) to each deployed resources, allowing you to control precisely what is deployed in the future. Some visualized files/directories have links attached, follow them to get more information.
 -- project-dir/
-   |-- }}">deployment.yaml
+   |-- deployment.yaml
    |-- .gitignore
    |-- kustomize-deployment1/
    |   |-- kustomization.yaml
@@ -56,7 +56,7 @@ Some visualized files/directories have links attached, follow them to get more i
    |   |   |-- resource2.yaml
    |   |   |-- patch1.yaml
    |   |   `-- ...
-   |   |-- }}">kustomize-with-helm-deployment/
+   |   |-- kustomize-with-helm-deployment/
    |   |   |-- charts/
    |   |   |   `-- ...
    |   |   |-- kustomization.yaml
@@ -72,5 +72,5 @@ Some visualized files/directories have links attached, follow them to get more i
 
 ## Order of deployments
 Deployments are done in parallel, meaning that there are usually no order guarantees. The only way to somehow control
-order, is by placing [barriers]({{< ref "./deployment-yml#barriers" >}}) between kustomize deployments.
+order, is by placing [barriers](./deployment-yml.md#barriers) between kustomize deployments.
 You should however not overuse barriers, as they negatively impact the speed of kluctl.
diff --git a/docs/reference/deployments/annotations/all-resources.md b/docs/reference/deployments/annotations/all-resources.md
index 5f2ee3327..e1aa989d6 100644
--- a/docs/reference/deployments/annotations/all-resources.md
+++ b/docs/reference/deployments/annotations/all-resources.md
@@ -37,12 +37,12 @@ If more than one field needs to be specified, add `-xxx` to the annotation key,
 The following annotations control how delete/prune is behaving.
 
 ### kluctl.io/skip-delete
-If set to "true", the annotated resource will not be deleted when [delete]({{< ref "docs/reference/commands/delete" >}}) or
-[prune]({{< ref "docs/reference/commands/prune" >}}) is called.
+If set to "true", the annotated resource will not be deleted when [delete](../../commands/delete) or
+[prune](../../commands/prune) is called.
 
 ### kluctl.io/skip-delete-if-tags
-If set to "true", the annotated resource will not be deleted when [delete]({{< ref "docs/reference/commands/delete" >}}) or
-[prune]({{< ref "docs/reference/commands/prune" >}}) is called and inclusion/exclusion tags are used at the same time.
+If set to "true", the annotated resource will not be deleted when [delete](../../commands/delete) or
+[prune](../../commands/prune) is called and inclusion/exclusion tags are used at the same time.
 
 This tag is especially useful and required on resources that would otherwise cause cascaded deletions of resources that
 do not match the specified inclusion/exclusion tags. Namespaces are the most prominent example of such resources, as
diff --git a/docs/reference/deployments/annotations/hooks.md b/docs/reference/deployments/annotations/hooks.md
index ca946554e..1b033bf9b 100644
--- a/docs/reference/deployments/annotations/hooks.md
+++ b/docs/reference/deployments/annotations/hooks.md
@@ -13,10 +13,10 @@ description: >
 
 The following annotations control hook execution
 
-See [hooks]({{< ref "docs/reference/deployments/hooks" >}}) for more details.
+See [hooks](../../deployments/hooks) for more details.
 
 ### kluctl.io/hook
-Declares a resource to be a hook, which is deployed/executed as described in [hooks]({{< ref "docs/reference/deployments/hooks" >}}). The value of the
+Declares a resource to be a hook, which is deployed/executed as described in [hooks](../../deployments/hooks). The value of the
 annotation determines when the hook is deployed/executed.
 
 ### kluctl.io/hook-weight
diff --git a/docs/reference/deployments/annotations/kustomization.md b/docs/reference/deployments/annotations/kustomization.md
index dfdcbf661..f0312940c 100644
--- a/docs/reference/deployments/annotations/kustomization.md
+++ b/docs/reference/deployments/annotations/kustomization.md
@@ -31,8 +31,8 @@ resources:
 
 ### kluctl.io/barrier
 If set to `true`, kluctl will wait for all previous objects to be applied (but not necessarily ready). This has the
-same effect as [barrier]({{< ref "docs/reference/deployments#barriers" >}}) from deployment projects.
+same effect as [barrier](../../deployments/deployment-yml.md#barriers) from deployment projects.
 
 ### kluctl.io/wait-readiness
 If set to `true`, kluctl will wait for readiness of all objects from this kustomization project. Readiness is defined
-the same as in [hook readiness]({{< ref "docs/reference/deployments/readiness" >}}).
+the same as in [hook readiness](../../deployments/readiness).
diff --git a/docs/reference/deployments/annotations/validation.md b/docs/reference/deployments/annotations/validation.md
index d4720d2e4..a36fcf2e4 100644
--- a/docs/reference/deployments/annotations/validation.md
+++ b/docs/reference/deployments/annotations/validation.md
@@ -11,7 +11,7 @@ description: >
 
 # Validation
 
-The following annotations influence the [validate]({{< ref "docs/reference/commands/validate" >}}) command.
+The following annotations influence the [validate](../../commands/validate) command.
 
 ### validate-result.kluctl.io/xxx
 If this annotation is found on a resource that is checked while validation, the key and the value of the annotation
diff --git a/docs/reference/deployments/deployment-yml.md b/docs/reference/deployments/deployment-yml.md
index ab36ffc00..0ca5cb75b 100644
--- a/docs/reference/deployments/deployment-yml.md
+++ b/docs/reference/deployments/deployment-yml.md
@@ -36,7 +36,7 @@ The following sub-chapters describe the available fields in the `deployment.yaml
 
 ## sealedSecrets
 `sealedSecrets` configures how sealed secrets are stored while sealing and located while rendering.
-See [Sealed Secrets]({{< ref "docs/reference/sealed-secrets#outputpattern-and-location-of-stored-sealed-secrets" >}})
+See [Sealed Secrets](../sealed-secrets#outputpattern-and-location-of-stored-sealed-secrets)
 for details.
 
 ## deployments
@@ -48,7 +48,7 @@ wait for all previous deployments to finish.
 ### Kustomize deployments
 
 Specifies a [kustomize](https://kustomize.io/) deployment.
-Please see [Kustomize integration]({{< ref "./kustomize" >}}) for more details.
+Please see [Kustomize integration](./kustomize.md) for more details.
 
 Example:
 ```yaml
@@ -62,7 +62,7 @@ The `path` must point to a directory relative to the directory containing the `d
 that are part of the kluctl project are allowed. The directory must contain a valid `kustomization.yaml`.
 
 `waitReadiness` is optional and if set to `true` instructs kluctl to wait for readiness of each individual object
-of the kustomize deployment. Readiness is defined in [readiness]({{< ref "./readiness" >}}).
+of the kustomize deployment. Readiness is defined in [readiness](./readiness.md).
 
 ### Includes
 
@@ -130,7 +130,7 @@ All entries in `deployments` can have the following common properties:
 A list of variable sets to be loaded into the templating context, which is then available in all [deployment items](#deployments)
 and [sub-deployments](#includes).
 
-See [templating]({{< ref "docs/reference/templating#vars-from-deploymentyaml" >}}) for more details.
+See [templating](../templating/variable-sources.md) for more details.
 
 Example:
 ```yaml
@@ -148,8 +148,8 @@ deployments:
 ```
 
 ### tags (deployment item)
-A list of tags the deployment should have. See [tags]({{< ref "./tags" >}}) for more details. For includes, this means that all
-sub-deployments will get these tags applied to. If not specified, the default tags logic as described in [tags]({{< ref "./tags" >}})
+A list of tags the deployment should have. See [tags](./tags.md) for more details. For includes, this means that all
+sub-deployments will get these tags applied to. If not specified, the default tags logic as described in [tags](./tags.md)
 is applied.
 
 Example:
@@ -171,7 +171,7 @@ deployments:
 
 ### alwaysDeploy
 Forces a deployment to be included everytime, ignoring inclusion/exclusion sets from the command line.
-See [Deploying with tag inclusion/exclusion]({{< ref "./tags#deploying-with-tag-inclusionexclusion" >}}) for details.
+See [Deploying with tag inclusion/exclusion](./tags.md#deploying-with-tag-inclusionexclusion) for details.
 
 ```yaml
 deployments:
@@ -182,7 +182,7 @@ deployments:
 
 ### skipDeleteIfTags
 Forces exclusion of a deployment whenever inclusion/exclusion tags are specified via command line.
-See [Deleting with tag inclusion/exclusion]({{< ref "./tags#deploying-with-tag-inclusionexclusion" >}}) for details.
+See [Deleting with tag inclusion/exclusion](./tags.md#deploying-with-tag-inclusionexclusion) for details.
 
 ```yaml
 deployments:
@@ -195,7 +195,7 @@ deployments:
 A list of variable sets to be loaded into the templating context, which is then available in all [deployment items](#deployments)
 and [sub-deployments](#includes).
 
-See [templating]({{< ref "docs/reference/templating#vars-from-deploymentyaml" >}}) for more details.
+See [templating](../templating/variable-sources.md) for more details.
 
 ## commonLabels
 A dictionary of [labels](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/) and values to be
@@ -240,7 +240,7 @@ A string that is used as the default namespace for all kustomize deployments whi
 ## tags (deployment project)
 A list of common tags which are applied to all kustomize deployments and sub-deployment includes.
 
-See [tags]({{< ref "./tags" >}}) for more details.
+See [tags](./tags.md) for more details.
 
 ## args
 A list of arguments that can or must be passed to most kluctl operations. Each of these arguments is then available
@@ -307,5 +307,5 @@ valid [JSON Path](https://goessner.net/articles/JsonPath/). `fieldPath` may also
 The JSON Path implementation used in kluctl has extended support for wildcards in field
 names, allowing you to also specify paths like `metadata.labels.my-prefix-*`.
 
-As an alternative, [annotations]({{< ref "./annotations/all-resources#control-diff-behavior" >}}) can be used to control
+As an alternative, [annotations](./annotations/all-resources#control-diff-behavior) can be used to control
 diff behavior of individual resources.
diff --git a/docs/reference/deployments/helm.md b/docs/reference/deployments/helm.md
index 2d8dab7b4..aab1fdbf0 100644
--- a/docs/reference/deployments/helm.md
+++ b/docs/reference/deployments/helm.md
@@ -25,14 +25,14 @@ hands over the rendered yaml to [kustomize](https://kustomize.io/). Rendering is
 `helm-values.yaml`, which contains the necessary values to configure the Helm Chart.
 
 The resulting rendered yaml is then referred by your `kustomization.yaml`, from which point on the
-[kustomize integration]({{< ref "docs/reference/sealed-secrets#outputpattern-and-location-of-stored-sealed-secrets" >}}) 
+[kustomize integration](../sealed-secrets#outputpattern-and-location-of-stored-sealed-secrets) 
 takes over. This means, that you can perform all desired customization (patches, namespace override, ...) as if you
 provided your own resources via yaml files.
 
 ### Helm hooks
 
 [Helm Hooks](https://helm.sh/docs/topics/charts_hooks/) are implemented by mapping them 
-to [kluctl hooks]({{< ref "./hooks" >}}), based on the following mapping table:
+to [kluctl hooks](./hooks.md), based on the following mapping table:
 
 | Helm hook     | kluctl hook         |
 |---------------|---------------------|
@@ -99,7 +99,7 @@ The name of the chart that can be found in the repository.
 The version of the chart.
 
 ### skipUpdate
-Skip this Helm Chart when the [helm-update]({{< ref "docs/reference/commands/helm-update" >}}) command is called.
+Skip this Helm Chart when the [helm-update](../commands/helm-update) command is called.
 If omitted, defaults to `false`.
 
 ### releaseName
@@ -125,8 +125,8 @@ read the documentation of the used Helm Charts for details on what is supported.
 
 ## Updates to helm-charts
 In case a Helm Chart needs to be updated, you can either do this manually by replacing the [chartVersion](#chartversion)
-value in `helm-chart.yaml` and the calling the [helm-pull]({{< ref "docs/reference/commands/helm-pull" >}}) command or by simply invoking
-[helm-update]({{< ref "docs/reference/commands/helm-update" >}}) with `--upgrade` and/or `--commit` being set.
+value in `helm-chart.yaml` and the calling the [helm-pull](../commands/helm-pull) command or by simply invoking
+[helm-update](../commands/helm-update) with `--upgrade` and/or `--commit` being set.
 
 ## Private Chart Repositories
 It is also possible to use private chart repositories. There are currently two options to provide Helm Repository
@@ -139,7 +139,7 @@ added the repository to Helm. The same method can be used for client certificate
 in `helm repo add`).
 
 ### Use the --username/--password arguments in `kluctl helm-pull`
-See the [helm-pull command]({{< ref "docs/reference/commands/helm-pull" >}}). You can control repository credentials
+See the [helm-pull command](../commands/helm-pull). You can control repository credentials
 via `--username`, `--password` and `--key-file`. Each argument must be in the form `credentialsId:value`, where
 the `credentialsId` must match the id specified in the `helm-chart.yaml`. Example:
 
@@ -160,11 +160,11 @@ When credentialsId is specified, Kluctl will require you to specify `--username=
 Multiple Helm Charts can use the same `credentialsId`.
 
 Environment variables can also be used instead of arguments. See
-[Environment Variables]({{< ref "docs/reference/commands/environment-variables" >}}) for details.
+[Environment Variables](../commands/environment-variables) for details.
 
 ## Templating
 
-Both `helm-chart.yaml` and `helm-values.yaml` are rendered by the [templating engine]({{< ref "docs/reference/templating" >}}) before they
+Both `helm-chart.yaml` and `helm-values.yaml` are rendered by the [templating engine](../templating) before they
 are actually used. This means, that you can use all available Jinja2 variables at that point, which can for example be
 seen in the above `helm-chart.yaml` example for the namespace.
 
diff --git a/docs/reference/deployments/hooks.md b/docs/reference/deployments/hooks.md
index 9764f0051..b9f5879c2 100644
--- a/docs/reference/deployments/hooks.md
+++ b/docs/reference/deployments/hooks.md
@@ -48,6 +48,6 @@ changed by setting the `kluctl.io/hook-delete-policy` to a comma separated list
 ## Hook readiness
 
 After each deployment/execution of the hooks that belong to a deployment stage (before/after deployment), kluctl
-waits for the hook resources to become "ready". Readiness is defined [here]({{< ref "./readiness" >}}).
+waits for the hook resources to become "ready". Readiness is defined [here](./readiness.md).
 
 It is possible to disable waiting for hook readiness by setting the annotation `kluctl.io/hook-wait` to "false".
diff --git a/docs/reference/deployments/images.md b/docs/reference/deployments/images.md
index 6f2a7e3cb..3b88af2bb 100644
--- a/docs/reference/deployments/images.md
+++ b/docs/reference/deployments/images.md
@@ -12,7 +12,7 @@ description: >
 # Container Images
 
 There are usually 2 different scenarios where Container Images need to be specified:
-1. When deploying third party applications like nginx, redis, ... (e.g. via the [Helm integration]({{< ref "./helm" >}})). 
+1. When deploying third party applications like nginx, redis, ... (e.g. via the [Helm integration](./helm.md)).
* In this case, image versions/tags rarely change, and if they do, this is an explicit change to the deployment. 1. When deploying your own applications.
* In this case, image versions/tags might change very rapidly, sometimes multiple times per hour. It would be too much @@ -89,7 +89,7 @@ The described `images.get_image` logic however leads to a loosely defined state might be fine in a CI/CD environment, but might be undesired when deploying to production. In that case, it might be desirable to explicitly define which versions need to be deployed. -To achieve this, you can use the `-F FIXED_IMAGE` [argument]({{< ref "docs/reference/commands/common-arguments#image-arguments" >}}). +To achieve this, you can use the `-F FIXED_IMAGE` [argument](../commands/common-arguments#image-arguments). `FIXED_IMAGE` must be in the form of `-F image<:namespace:deployment:container>=result`. For example, to pin the image `registry.gitlab.com/my-group/my-project` to the tag `1.1.2` you'd have to specify `-F registry.gitlab.com/my-group/my-project=registry.gitlab.com/my-group/my-project:1.1.2`. diff --git a/docs/reference/deployments/tags.md b/docs/reference/deployments/tags.md index 9501d0845..6bbfe18f4 100644 --- a/docs/reference/deployments/tags.md +++ b/docs/reference/deployments/tags.md @@ -10,14 +10,14 @@ weight: 6 # Tags Every kustomize deployment has a set of tags assigned to it. These tags are defined in multiple places, which is -documented in [deployment.yaml]({{< ref "./deployment-yml" >}}). Look for the `tags` field, which is available in multiple places per +documented in [deployment.yaml](./deployment-yml.md). Look for the `tags` field, which is available in multiple places per deployment project. Tags are useful when only one or more specific kustomize deployments need to be deployed or deleted. ## Default tags -[deployment items]({{< ref "./deployment-yml#deployments" >}}) in deployment projects can have an optional list of tags assigned. +[deployment items](./deployment-yml.md#deployments) in deployment projects can have an optional list of tags assigned. If this list is completely omitted, one single entry is added by default. This single entry equals to the last element of the `path` in the `deployments` entry. @@ -39,10 +39,10 @@ or even conflicting tags (e.g. `subdir` is really a bad tag), in which case you' ## Tag inheritance Deployment projects and deployments items inherit the tags of their parents. For example, if a deployment project -has a [tags]({{< ref "./deployment-yml#tags-deployment-project" >}}) property defined, all `deployments` entries would +has a [tags](./deployment-yml.md#tags-deployment-project) property defined, all `deployments` entries would inherit all these tags. Also, the sub-deployment projects included via deployment items of type -[include]({{< ref "./deployment-yml#includes" >}}) inherit the tags of the deployment project. These included sub-deployments also -inherit the [tags]({{< ref "./deployment-yml#tags-deployment-item" >}}) specified by the deployment item itself. +[include](./deployment-yml.md#includes) inherit the tags of the deployment project. These included sub-deployments also +inherit the [tags](./deployment-yml.md#tags-deployment-item) specified by the deployment item itself. Consider the following example `deployment.yaml`: @@ -72,7 +72,7 @@ resources to be deployed as well. Imagine a large deployment is able to deploy 10 applications, but you only want to deploy one of them. When using tags to achieve this, there might be some base resources (e.g. Namespaces) which are needed no matter if everything or just -this single application is deployed. In that case, you'd need to set [alwaysDeploy]({{< ref "./deployment-yml#deployments" >}}) +this single application is deployed. In that case, you'd need to set [alwaysDeploy](./deployment-yml.md#deployments) to `true`. ## Deleting with tag inclusion/exclusion @@ -83,6 +83,6 @@ Imagine a kustomize deployment being responsible for namespaces deployments. If deployments that have the `persistency` tag assigned, the exclusion logic would NOT exclude deletion of the namespace. This would ultimately lead to everything being deleted, and the exclusion tag having no effect. -In such a case, you'd need to set [skipDeleteIfTags]({{< ref "./deployment-yml#skipdeleteiftags" >}}) to `true` as well. +In such a case, you'd need to set [skipDeleteIfTags](./deployment-yml.md#skipdeleteiftags) to `true` as well. In most cases, setting `alwaysDeploy` to `true` also requires setting `skipDeleteIfTags` to `true`. diff --git a/docs/reference/kluctl-project/README.md b/docs/reference/kluctl-project/README.md index eedd06aaa..556e1313f 100644 --- a/docs/reference/kluctl-project/README.md +++ b/docs/reference/kluctl-project/README.md @@ -12,9 +12,9 @@ description: > # Kluctl project The `.kluctl.yaml` is the central configuration and entry point for your deployments. It defines where the actual -[deployment project]({{< ref "docs/reference/deployments" >}}) is located, -where [sealed secrets]({{< ref "docs/reference/sealed-secrets" >}}) and unencrypted secrets are localed and which targets are available to -invoke [commands]({{< ref "docs/reference/commands" >}}) on. +[deployment project](../deployments) is located, +where [sealed secrets](../sealed-secrets) and unencrypted secrets are localed and which targets are available to +invoke [commands](../commands) on. ## Example diff --git a/docs/reference/kluctl-project/secrets-config/README.md b/docs/reference/kluctl-project/secrets-config/README.md index 41663bdb2..81207967a 100644 --- a/docs/reference/kluctl-project/secrets-config/README.md +++ b/docs/reference/kluctl-project/secrets-config/README.md @@ -35,7 +35,7 @@ This field specifies the name of the secret set. The name can be used in targets ### vars A list of variables sources. Check the documentation of -[variables sources]({{< ref "docs/reference/templating/variable-sources" >}}) for details. +[variables sources](../../templating/variable-sources) for details. Each variables source must have a root dictionary with the name `secrets` and all the actual secret values below that dictionary. Every other root key will be ignored. diff --git a/docs/reference/kluctl-project/targets/README.md b/docs/reference/kluctl-project/targets/README.md index 9349cc476..ce1a19f3c 100644 --- a/docs/reference/kluctl-project/targets/README.md +++ b/docs/reference/kluctl-project/targets/README.md @@ -13,11 +13,11 @@ description: > Specifies a list of targets for which commands can be invoked. A target puts together environment/target specific configuration and the target cluster. Multiple targets can exist which target the same cluster but with differing -configuration (via `args`). Target entries also specifies which secrets to use while [sealing]({{< ref "docs/reference/sealed-secrets" >}}). +configuration (via `args`). Target entries also specifies which secrets to use while [sealing](../../sealed-secrets). Each value found in the target definition is rendered with a simple Jinja2 context that only contains the target itself. The rendering process is retried 10 times until it finally succeeds, allowing you to reference -the target itself in complex ways. This is especially useful when using [dynamic targets]({{< ref "./dynamic-targets" >}}). +the target itself in complex ways. This is especially useful when using [dynamic targets](./dynamic-targets.md). Target entries have the following form: ```yaml @@ -45,7 +45,7 @@ The following fields are allowed per target: ## name This field specifies the name of the target. The name must be unique. It is referred in all commands via the -[-t]({{< ref "docs/reference/commands/common-arguments" >}}) option. +[-t](../../commands/common-arguments) option. ## context This field specifies the kubectl context of the target cluster. The context must exist in the currently active kubeconfig. @@ -53,9 +53,9 @@ If this field is omitted, Kluctl will always use the currently active context. ## args This fields specifies a map of arguments to be passed to the deployment project when it is rendered. Allowed argument names -are configured via [deployment args]({{< ref "docs/reference/deployments/deployment-yml#args" >}}). +are configured via [deployment args](../../deployments/deployment-yml#args). -The arguments specified in the [dynamic target config]({{< ref "docs/reference/kluctl-project/targets/dynamic-targets#args" >}}) +The arguments specified in the [dynamic target config](../../kluctl-project/targets/dynamic-targets#args) have higher priority. ## dynamicArgs @@ -65,17 +65,17 @@ arguments are passed with `-a arg_name=arg_value` when for example calling `kluc Each entry has the following fields: ## images -This field specifies a list of fixed images to be used by [`images.get_image(...)`]({{< ref "docs/reference/deployments/images#imagesget_image" >}}). +This field specifies a list of fixed images to be used by [`images.get_image(...)`](../../deployments/images#imagesget_image). The format is identical to the [fixed images file](https://kluctl.io/docs/reference/deployments/images/#fixed-images-via-a-yaml-file). -The fixed images specified in the [dynamic target config]({{< ref "docs/reference/kluctl-project/targets/dynamic-targets#images" >}}) +The fixed images specified in the [dynamic target config](../../kluctl-project/targets/dynamic-targets#images) have higher priority. ### name The name of the argument. ## sealingConfig -This field configures how sealing is performed when the [seal command] ({{< ref "docs/reference/commands/seal" >}}) is invoked for this target. +This field configures how sealing is performed when the [seal command](../../commands/seal) is invoked for this target. It has the following form: ```yaml @@ -101,8 +101,8 @@ Optional path to a local (inside your project) public certificate used for seali from the sealed-secrets controller using `kubeseal --fetch-cert`. ### dynamicSealing -This field specifies weather sealing should happen per [dynamic target]({{< ref "./dynamic-targets" >}}) or only once. This +This field specifies weather sealing should happen per [dynamic target](./dynamic-targets.md) or only once. This field is optional and defaults to `true`. ### secretSets -This field specifies a list of secret set names, which all must exist in the [secretsConfig]({{< ref "../secrets-config" >}}). +This field specifies a list of secret set names, which all must exist in the [secretsConfig](../secrets-config). diff --git a/docs/reference/kluctl-project/targets/dynamic-targets.md b/docs/reference/kluctl-project/targets/dynamic-targets.md index 158262cdc..32fa6a1af 100644 --- a/docs/reference/kluctl-project/targets/dynamic-targets.md +++ b/docs/reference/kluctl-project/targets/dynamic-targets.md @@ -91,12 +91,12 @@ images: ``` ### args -An optional map of arguments, in the same format as in the normal [target args]({{< ref "docs/reference/kluctl-project/targets/#args" >}}). +An optional map of arguments, in the same format as in the normal [target args](../../kluctl-project/targets#args). The arguments specified here have higher priority. ### images -An optional list of fixed images, in the same format as in the normal [target images]({{< ref "docs/reference/kluctl-project/targets/#images" >}}) +An optional list of fixed images, in the same format as in the normal [target images](../../kluctl-project/targets#images) ## Simple dynamic targets diff --git a/docs/reference/sealed-secrets.md b/docs/reference/sealed-secrets.md index 8c7ebca61..81dd38c84 100644 --- a/docs/reference/sealed-secrets.md +++ b/docs/reference/sealed-secrets.md @@ -25,15 +25,15 @@ being installed. Installing the operator is the responsibility of you (or whoeve Kluctl can however perform sealing of secrets without an existing sealed-secrets operator installation. This is solved by automatically pre-provisioning a key onto the cluster that is compatible with the operator or by providing the -public certificate via `certFile` in the targets [sealingConfig]({{< ref "docs/reference/kluctl-project/targets#certfile" >}}). +public certificate via `certFile` in the targets [sealingConfig](./kluctl-project/targets#certfile). ## Sealing of .sealme files -Sealing is done via the [seal command]({{< ref "docs/reference/commands/seal" >}}). It must be done before the actual +Sealing is done via the [seal command](./commands/seal). It must be done before the actual deployment is performed. The `seal` command recursively searches for files that end with `.sealme`, renders them with the -[templating engine]({{< ref "docs/reference/templating" >}}) engine. The rendered secret resource is then +[templating engine](./templating) engine. The rendered secret resource is then converted/encrypted into a sealed secret. The `.sealme` files itself have to be [Kubernetes Secrets](https://kubernetes.io/docs/concepts/configuration/secret/), @@ -54,14 +54,14 @@ stringData: DB_PASSWORD: {{ secrets.database.password }} ``` -While sealing, the full templating context (same as in [templating]({{< ref "docs/reference/templating" >}})) is available. +While sealing, the full templating context (same as in [templating](./templating)) is available. Additionally, the global `secrets` object/variable is available which contains the sensitive secrets. ## Secret Sources Secrets are only loaded while sealing. Available secret sets and sources are configured via -[.kluctl.yaml]({{< ref "docs/reference/kluctl-project/secrets-config" >}}). The secrets used per target are configured via the -[secrets config]({{< ref "docs/reference/kluctl-project/targets#secretsets" >}}) of the targets. +[.kluctl.yaml](./kluctl-project/secrets-config). The secrets used per target are configured via the +[secrets config](./kluctl-project/targets#secretsets) of the targets. ## Using sealed secrets @@ -83,9 +83,9 @@ resources: ## outputPattern and location of stored sealed secrets -It is possible to override the output pattern in the root [deployment project]({{< ref "docs/reference/deployments" >}}). +It is possible to override the output pattern in the root [deployment project](./deployments). The output pattern must be a template string that is rendered with the full -[templating context]({{< ref "docs/reference/templating" >}}) available for the deployment.yaml. +[templating context](./templating) available for the deployment.yaml. When manually specifying the outputPattern, ensure that it works well with multiple clusters and targets. You can for example use the `{{ target.name }}` and `{{ cluster.name }}` inside the outputPattern. @@ -111,8 +111,8 @@ with: ## Content Hashes and re-sealing Sealed secrets are stored together with hashes of all individual secret entries. These hashes are then used to avoid -unnecessary re-sealing in future [seal]({{< ref "docs/reference/commands/seal" >}}) invocations. If you want to force re-sealing, use the -[--force-reseal]({{< ref "docs/reference/commands/seal" >}}) option. +unnecessary re-sealing in future [seal](./commands/seal) invocations. If you want to force re-sealing, use the +[--force-reseal](./commands/seal) option. Hashing of secrets is done with bcrypt and the cluster id as salt. The cluster id is currently defined as the sha256 hash of the cluster CA certificate. This will cause re-sealing of all secrets in case a cluster is set up from scratch diff --git a/docs/reference/templating/functions.md b/docs/reference/templating/functions.md index ad4894474..db0eac48e 100644 --- a/docs/reference/templating/functions.md +++ b/docs/reference/templating/functions.md @@ -22,7 +22,7 @@ Loads the given file into memory, renders it with the current Jinja2 context and {{ a }} ``` -`load_template` uses the same path searching rules as described in [includes/imports]({{< ref "docs/reference/templating#includes-and-imports" >}}). +`load_template` uses the same path searching rules as described in [includes/imports](../templating#includes-and-imports). ### load_sha256(file, digest_len) Loads the given file into memory, renders it and calculates the sha256 hash of the result. @@ -44,7 +44,7 @@ data: ### get_var(field_path, default) Convenience method to navigate through the current context variables via a [JSON Path](https://goessner.net/articles/JsonPath/). Let's assume you currently have these variables defined -(e.g. via [vars]({{< ref "docs/reference/deployments/deployment-yml#vars-deployment-project" >}})): +(e.g. via [vars](../deployments/deployment-yml#vars-deployment-project)): ```yaml my: deep: diff --git a/docs/reference/templating/predefined-variables.md b/docs/reference/templating/predefined-variables.md index 75c962937..5c4e88645 100644 --- a/docs/reference/templating/predefined-variables.md +++ b/docs/reference/templating/predefined-variables.md @@ -15,18 +15,18 @@ There are multiple variables available which are pre-defined by kluctl. These ar ### args This is a dictionary of arguments given via command line. It contains every argument defined in -[deployment args]({{< ref "docs/reference/deployments/deployment-yml#args" >}}). +[deployment args](../deployments/deployment-yml#args). ### target This is the target definition of the currently processed target. It contains all values found in the -[target definition]({{< ref "docs/reference/kluctl-project/targets" >}}), for example `target.name`. +[target definition](../kluctl-project/targets), for example `target.name`. ### images -This global object provides the dynamic images features described in [images]({{< ref "docs/reference/deployments/images" >}}). +This global object provides the dynamic images features described in [images](../deployments/images). ### version -This global object defines latest version filters for `images.get_image(...)`. See [images]({{< ref "docs/reference/deployments/images" >}}) for details. +This global object defines latest version filters for `images.get_image(...)`. See [images](../deployments/images) for details. ### secrets -This global object is only available while [sealing]({{< ref "docs/reference/sealed-secrets" >}}) and contains the loaded +This global object is only available while [sealing](../sealed-secrets) and contains the loaded secrets defined via the currently sealed target. diff --git a/docs/reference/templating/variable-sources.md b/docs/reference/templating/variable-sources.md index 368e15eb6..c81465380 100644 --- a/docs/reference/templating/variable-sources.md +++ b/docs/reference/templating/variable-sources.md @@ -14,10 +14,10 @@ description: > There are multiple places in deployment projects (deployment.yaml) where additional variables can be loaded into future Jinja2 contexts. -The first place where vars can be specified is the deployment root, as documented [here]({{< ref "docs/reference/deployments/deployment-yml#vars-deployment-project" >}}). +The first place where vars can be specified is the deployment root, as documented [here](../deployments/deployment-yml#vars-deployment-project). These vars are visible for all deployments inside the deployment project, including sub-deployments from includes. -The second place to specify variables is in the deployment items, as documented [here]({{< ref "docs/reference/deployments/deployment-yml#vars-deployment-item" >}}). +The second place to specify variables is in the deployment items, as documented [here](../deployments/deployment-yml#vars-deployment-item). The variables loaded for each entry in `vars` are not available inside the `deployment.yaml` file itself. However, each entry in `vars` can use all variables defined before that specific entry is processed. Consider the From ceb13154a42e3ab04eef960ee096a734ab0b2a80 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 21 Oct 2022 17:10:06 +0200 Subject: [PATCH 1098/2916] docs: Run make replace-commands-help --- docs/reference/commands/common-arguments.md | 35 ++++++++++++++------- docs/reference/commands/list-images.md | 4 ++- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/docs/reference/commands/common-arguments.md b/docs/reference/commands/common-arguments.md index fdaa1c016..235b823e1 100644 --- a/docs/reference/commands/common-arguments.md +++ b/docs/reference/commands/common-arguments.md @@ -39,23 +39,34 @@ They control where and how to load the kluctl project and deployment project. Project arguments: Define where and how to load the kluctl project and its components from. - -a, --arg stringArray Template argument in the form name=value - --cluster string DEPRECATED. Specify/Override cluster + -a, --arg stringArray Passes a template argument in the form of name=value. Nested args can + be set with the '-a my.nested.arg=value' syntax. Values are + interpreted as yaml values, meaning that 'true' and 'false' will lead + to boolean values and numbers will be treated as numbers. Use quotes + if you want these to be treated as strings. If the value starts with + @, it is treated as a file, meaning that the contents of the file + will be loaded and treated as yaml. + --args-from-file stringArray Loads a yaml file and makes it available as arguments, meaning that + they will be available thought the global 'args' variable. + --context string Overrides the context name specified in the target. If the selected + target does not specify a context or the no-name target is used, + --context will override the currently active context. --git-cache-update-interval duration Specify the time to wait between git cache updates. Defaults to not wait at all and always updating caches. - --local-clusters existingdir DEPRECATED. Local clusters directory. Overrides the project from - .kluctl.yaml - --local-deployment existingdir DEPRECATED. Local deployment directory. Overrides the project from - .kluctl.yaml - --local-sealed-secrets existingdir DEPRECATED. Local sealed-secrets directory. Overrides the project - from .kluctl.yaml - --output-metadata string Specify the output path for the project metadata to be written to. + --local-git-override stringArray Specify a local git override in the form of + 'github.com:my-org/my-repo=/local/path/to/override'. This will cause + kluctl to not use git to clone for the specified repository but + instead use the local directory. This is useful in case you need to + test out changes in external git repositories without pushing them. + To only override a single branch of the repo, use + 'github.com:my-org/my-repo:my-branch=/local/path/to/override' -c, --project-config existingfile Location of the .kluctl.yaml config file. Defaults to $PROJECT/.kluctl.yaml - -b, --project-ref string Git ref of the kluctl project. Only used when --project-url was given. - -p, --project-url string Git url of the kluctl project. If not specified, the current - directory will be used instead of a remote Git project -t, --target string Target name to run command for. Target must exist in .kluctl.yaml. + -T, --target-name-override string Overrides the target name. If -t is used at the same time, then the + target will be looked up based on -t and then renamed to the + value of -T. If no target is specified via -t, then the no-name + target is renamed to the value of -T. --timeout duration Specify timeout for all operations, including loading of the project, all external api calls and waiting for readiness. (default 10m0s) diff --git a/docs/reference/commands/list-images.md b/docs/reference/commands/list-images.md index 5d59e6949..e9deee305 100644 --- a/docs/reference/commands/list-images.md +++ b/docs/reference/commands/list-images.md @@ -17,7 +17,7 @@ Renders the target and outputs all images used via 'images.get_image(...) The result is a compatible with yaml files expected by --fixed-images-file. If fixed images ('-f/--fixed-image') are provided, these are also taken into account, -as described in for the deploy command. +as described in the deploy command. @@ -33,6 +33,8 @@ In addition, the following arguments are available: Misc arguments: Command specific arguments. + --offline-kubernetes Run list-images in offline mode, meaning that it will not try to connect the + target cluster -o, --output stringArray Specify output target file. Can be specified multiple times --render-output-dir string Specifies the target directory to render the project into. If omitted, a temporary directory is used. From 545cd85e93765756729b1736b945298cafe0813b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 21 Oct 2022 17:43:35 +0200 Subject: [PATCH 1099/2916] ci: Run markdown-link-check on markdown files --- .github/workflows/check-links-cron.yml | 19 +++++++++++++++++++ .github/workflows/tests.yml | 11 +++++++++++ Makefile | 3 +++ 3 files changed, 33 insertions(+) create mode 100644 .github/workflows/check-links-cron.yml diff --git a/.github/workflows/check-links-cron.yml b/.github/workflows/check-links-cron.yml new file mode 100644 index 000000000..117b11289 --- /dev/null +++ b/.github/workflows/check-links-cron.yml @@ -0,0 +1,19 @@ +name: Check Markdown links + +on: + push: + branches: + - main + schedule: + # Run everyday at 9:00 AM (See https://pubs.opengroup.org/onlinepubs/9699919799/utilities/crontab.html#tag_20_25_07) + - cron: "0 9 * * *" + +jobs: + markdown-link-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - uses: gaurav-nelson/github-action-markdown-link-check@v1 + with: + folder-path: 'docs, install' + file-path: './README.md' diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6e24c9004..4ce1e0de4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,6 +7,17 @@ on: - main jobs: + pre-checks: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Check links on changed files + run: | + make markdown-link-check + build: runs-on: ubuntu-20.04 steps: diff --git a/Makefile b/Makefile index 92f691a82..6dee972ce 100644 --- a/Makefile +++ b/Makefile @@ -122,6 +122,9 @@ endif replace-commands-help: ## Replace commands help in docs $(GOCMD) run ./internal/replace-commands-help --docs-dir ./docs/reference/commands +markdown-link-check: ## Check markdown files for dead links + find . -name '*.md' | xargs docker run -v ${PWD}:/tmp:ro --rm -i -w /tmp ghcr.io/tcort/markdown-link-check:stable + ## Release: version: ## Write next version into version file $(GOCMD) install github.com/bvieira/sv4git/v2/cmd/git-sv@v2.7.0 From 4b1a44511d1c4938f82bebdb467ba94b8a4e56e4 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 21 Oct 2022 18:05:08 +0200 Subject: [PATCH 1100/2916] ci: Move replace-commands-help into docs-check job --- .github/workflows/tests.yml | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4ce1e0de4..3dd57cb2a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,16 +7,36 @@ on: - main jobs: - pre-checks: + docs-checks: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v2 with: fetch-depth: 0 + - uses: actions/setup-go@v2 + with: + go-version: '1.19' + - uses: actions/cache@v2 + with: + path: | + ~/go/pkg/mod + ~/.cache/go-build + key: docs-check-go-${{ runner.os }}-${{ hashFiles('**/go.sum') }} + restore-keys: | + docs-check-go-${{ runner.os }}- - name: Check links on changed files run: | make markdown-link-check + - name: Verify commands help is up-to-date + run: | + make replace-commands-help + if [ ! -z "$(git status --porcelain)" ]; then + echo "replace-commands-help must be invoked and the result committed" + git status + git diff + exit 1 + fi build: runs-on: ubuntu-20.04 @@ -51,15 +71,6 @@ jobs: run: | make test-e2e-build GOARCH=amd64 GOOS=windows mv ./bin/kluctl-e2e.exe ./bin/kluctl-e2e-windows-amd64.exe - - name: Verify commands help is up-to-date - run: | - make replace-commands-help - if [ ! -z "$(git status --porcelain)" ]; then - echo "replace-commands-help must be invoked and the result committed" - git status - git diff - exit 1 - fi - name: Upload binaries uses: actions/upload-artifact@v2 with: From c2c2e81ac7c1cd4dd9baa7acef2309c899640fd4 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 21 Oct 2022 17:57:00 +0200 Subject: [PATCH 1101/2916] docs: Fix dead links --- docs/README.md | 2 +- docs/concepts.md | 4 ++-- docs/reference/commands/common-arguments.md | 2 +- docs/reference/commands/delete.md | 8 ++++---- docs/reference/commands/deploy.md | 10 +++++----- docs/reference/commands/diff.md | 8 ++++---- docs/reference/commands/environment-variables.md | 2 +- docs/reference/commands/helm-pull.md | 4 ++-- docs/reference/commands/helm-update.md | 2 +- docs/reference/commands/list-images.md | 6 +++--- docs/reference/commands/poke-images.md | 6 +++--- docs/reference/commands/prune.md | 8 ++++---- docs/reference/commands/render.md | 4 ++-- docs/reference/commands/seal.md | 4 ++-- docs/reference/commands/validate.md | 4 ++-- .../deployments/annotations/all-resources.md | 8 ++++---- docs/reference/deployments/annotations/hooks.md | 4 ++-- .../deployments/annotations/kustomization.md | 2 +- .../deployments/annotations/validation.md | 2 +- docs/reference/deployments/deployment-yml.md | 4 ++-- docs/reference/deployments/helm.md | 12 ++++++------ docs/reference/deployments/images.md | 2 +- docs/reference/kluctl-project/README.md | 2 +- .../kluctl-project/secrets-config/README.md | 2 +- docs/reference/kluctl-project/targets/README.md | 16 ++++++++-------- docs/reference/sealed-secrets.md | 6 +++--- docs/reference/templating/functions.md | 2 +- .../reference/templating/predefined-variables.md | 8 ++++---- docs/reference/templating/variable-sources.md | 4 ++-- 29 files changed, 74 insertions(+), 74 deletions(-) diff --git a/docs/README.md b/docs/README.md index f9311fca4..1ccac0e55 100644 --- a/docs/README.md +++ b/docs/README.md @@ -16,7 +16,7 @@ menu: 1. [Get Started](./get-started.md) 2. [Core Concepts](./concepts.md) -3. [Installation](./installation) +3. [Installation](./installation.md) 4. [Philosophy](./philosophy.md) 5. [History](./history.md) 6. [Reference](./reference) diff --git a/docs/concepts.md b/docs/concepts.md index 946f69c0f..2967356fe 100644 --- a/docs/concepts.md +++ b/docs/concepts.md @@ -41,11 +41,11 @@ control structures (if/else, for loops, ...). ## Secrets Secrets are loaded from [external sources](./reference/kluctl-project) and are only available -while [sealing](./reference/sealed-secrets). After the sealing process, only the public-key encrypted +while [sealing](./reference/sealed-secrets.md). After the sealing process, only the public-key encrypted sealed secrets are available. ## Sealed Secrets -[Sealed Secrets](./reference/sealed-secrets) are based on +[Sealed Secrets](./reference/sealed-secrets.md) are based on [Bitnami's sealed-secrets controller](https://github.com/bitnami-labs/sealed-secrets). Kluctl offers integration of sealed secrets through the `seal` command. Kluctl allows managing multiple sets of sealed secrets for multiple targets. diff --git a/docs/reference/commands/common-arguments.md b/docs/reference/commands/common-arguments.md index 235b823e1..aedafc415 100644 --- a/docs/reference/commands/common-arguments.md +++ b/docs/reference/commands/common-arguments.md @@ -76,7 +76,7 @@ Project arguments: ## Image arguments These arguments are available on some target based commands. -They control image versions requested by `images.get_image(...)` [calls](../deployments/images#imagesget_image). +They control image versions requested by `images.get_image(...)` [calls](../deployments/images.md#imagesget_image). ``` diff --git a/docs/reference/commands/delete.md b/docs/reference/commands/delete.md index 33e8e2acd..7dd91db7c 100644 --- a/docs/reference/commands/delete.md +++ b/docs/reference/commands/delete.md @@ -24,9 +24,9 @@ take the local target/state into account! ## Arguments The following sets of arguments are available: -1. [project arguments](./common-arguments#project-arguments) -1. [image arguments](./common-arguments#image-arguments) -1. [inclusion/exclusion arguments](./common-arguments#inclusionexclusion-arguments) +1. [project arguments](./common-arguments.md#project-arguments) +1. [image arguments](./common-arguments.md#image-arguments) +1. [inclusion/exclusion arguments](./common-arguments.md#inclusionexclusion-arguments) In addition, the following arguments are available: @@ -46,4 +46,4 @@ Misc arguments: ``` -They have the same meaning as described in [deploy](#deploy). +They have the same meaning as described in [deploy](./deploy.md). diff --git a/docs/reference/commands/deploy.md b/docs/reference/commands/deploy.md index 65e472674..73ce93477 100644 --- a/docs/reference/commands/deploy.md +++ b/docs/reference/commands/deploy.md @@ -22,9 +22,9 @@ It will also output a list of prunable objects (without actually deleting them). ## Arguments The following sets of arguments are available: -1. [project arguments](./common-arguments#project-arguments) -1. [image arguments](./common-arguments#image-arguments) -1. [inclusion/exclusion arguments](./common-arguments#inclusionexclusion-arguments) +1. [project arguments](./common-arguments.md#project-arguments) +1. [image arguments](./common-arguments.md#image-arguments) +1. [inclusion/exclusion arguments](./common-arguments.md#inclusionexclusion-arguments) In addition, the following arguments are available: @@ -54,9 +54,9 @@ Misc arguments: ### --force-apply -kluctl implements deployments via [server-side apply](https://kubernetes.io/reference/using-api/server-side-apply/) +kluctl implements deployments via [server-side apply](https://kubernetes.io/docs/reference/using-api/server-side-apply/) and a custom automatic conflict resolution algorithm. This algurithm is an automatic implementation of the -"[Don't overwrite value, give up management claim](https://kubernetes.io/reference/using-api/server-side-apply/#conflicts)" +"[Don't overwrite value, give up management claim](https://kubernetes.io/docs/reference/using-api/server-side-apply/#conflicts)" method. It should work in most cases, but might still fail. In case of such failure, you can use `--force-apply` to use the "Overwrite value, become sole manager" strategy instead. diff --git a/docs/reference/commands/diff.md b/docs/reference/commands/diff.md index d54d6091b..320833839 100644 --- a/docs/reference/commands/diff.md +++ b/docs/reference/commands/diff.md @@ -23,9 +23,9 @@ After the diff is performed, the command will also search for prunable objects a ## Arguments The following sets of arguments are available: -1. [project arguments](./common-arguments#project-arguments) -1. [image arguments](./common-arguments#image-arguments) -1. [inclusion/exclusion arguments](./common-arguments#inclusionexclusion-arguments) +1. [project arguments](./common-arguments.md#project-arguments) +1. [image arguments](./common-arguments.md#image-arguments) +1. [inclusion/exclusion arguments](./common-arguments.md#inclusionexclusion-arguments) In addition, the following arguments are available: @@ -50,4 +50,4 @@ Misc arguments: ``` -`--force-apply` and `--replace-on-error` have the same meaning as in [deploy](#deploy). +`--force-apply` and `--replace-on-error` have the same meaning as in [deploy](./deploy.md). diff --git a/docs/reference/commands/environment-variables.md b/docs/reference/commands/environment-variables.md index 2247be7cf..62f775ed6 100644 --- a/docs/reference/commands/environment-variables.md +++ b/docs/reference/commands/environment-variables.md @@ -20,6 +20,6 @@ underscores. As an example, `--dry-run` can also be specified with the environme ## Additional environment variables A few additional environment variables are supported which do not belong to an option/argument. These are: -1. `KLUCTL_REGISTRY__HOST`, `KLUCTL_REGISTRY__USERNAME`, and so on. See [registries](../deployments/images#supported-image-registries-and-authentication) for details. +1. `KLUCTL_REGISTRY__HOST`, `KLUCTL_REGISTRY__USERNAME`, and so on. See [registries](../deployments/images.md#supported-image-registries-and-authentication) for details. 2. `KLUCTL_GIT__HOST`, `KLUCTL_GIT__USERNAME`, and so on. 3. `KLUCTL_SSH_DISABLE_STRICT_HOST_KEY_CHECKING`. Disable ssh host key checking when accessing git repositories. diff --git a/docs/reference/commands/helm-pull.md b/docs/reference/commands/helm-pull.md index 68d3871b4..8cc7ee552 100644 --- a/docs/reference/commands/helm-pull.md +++ b/docs/reference/commands/helm-pull.md @@ -20,8 +20,8 @@ pulling is only needed when really required (e.g. when the chart version changes -See [helm-integration](../deployments/helm) for more details. +See [helm-integration](../deployments/helm.md) for more details. ## Arguments The following sets of arguments are available: -1. [project arguments](./common-arguments#project-arguments) (except `-a`) +1. [project arguments](./common-arguments.md#project-arguments) (except `-a`) diff --git a/docs/reference/commands/helm-update.md b/docs/reference/commands/helm-update.md index d719c76a0..f9e679928 100644 --- a/docs/reference/commands/helm-update.md +++ b/docs/reference/commands/helm-update.md @@ -20,7 +20,7 @@ Optionally performs the actual upgrade and/or add a commit to version control. ## Arguments The following sets of arguments are available: -1. [project arguments](./common-arguments#project-arguments) (except `-a`) +1. [project arguments](./common-arguments.md#project-arguments) (except `-a`) In addition, the following arguments are available: diff --git a/docs/reference/commands/list-images.md b/docs/reference/commands/list-images.md index e9deee305..e4f6f7e2f 100644 --- a/docs/reference/commands/list-images.md +++ b/docs/reference/commands/list-images.md @@ -23,9 +23,9 @@ as described in the deploy command. ## Arguments The following sets of arguments are available: -1. [project arguments](./common-arguments#project-arguments) -1. [image arguments](./common-arguments#image-arguments) -1. [inclusion/exclusion arguments](./common-arguments#inclusionexclusion-arguments) +1. [project arguments](./common-arguments.md#project-arguments) +1. [image arguments](./common-arguments.md#image-arguments) +1. [inclusion/exclusion arguments](./common-arguments.md#inclusionexclusion-arguments) In addition, the following arguments are available: diff --git a/docs/reference/commands/poke-images.md b/docs/reference/commands/poke-images.md index b5279c256..8c3ba5ca8 100644 --- a/docs/reference/commands/poke-images.md +++ b/docs/reference/commands/poke-images.md @@ -22,9 +22,9 @@ replaced ## Arguments The following sets of arguments are available: -1. [project arguments](./common-arguments#project-arguments) -1. [image arguments](./common-arguments#image-arguments) -1. [inclusion/exclusion arguments](./common-arguments#inclusionexclusion-arguments) +1. [project arguments](./common-arguments.md#project-arguments) +1. [image arguments](./common-arguments.md#image-arguments) +1. [inclusion/exclusion arguments](./common-arguments.md#inclusionexclusion-arguments) In addition, the following arguments are available: diff --git a/docs/reference/commands/prune.md b/docs/reference/commands/prune.md index 86161c3ea..c33800f8c 100644 --- a/docs/reference/commands/prune.md +++ b/docs/reference/commands/prune.md @@ -18,9 +18,9 @@ Searches the target cluster for prunable objects and deletes them ## Arguments The following sets of arguments are available: -1. [project arguments](./common-arguments#project-arguments) -1. [image arguments](./common-arguments#image-arguments) -1. [inclusion/exclusion arguments](./common-arguments#inclusionexclusion-arguments) +1. [project arguments](./common-arguments.md#project-arguments) +1. [image arguments](./common-arguments.md#image-arguments) +1. [inclusion/exclusion arguments](./common-arguments.md#inclusionexclusion-arguments) In addition, the following arguments are available: @@ -39,4 +39,4 @@ Misc arguments: ``` -They have the same meaning as described in [deploy](#deploy). +They have the same meaning as described in [deploy](./prune.md). diff --git a/docs/reference/commands/render.md b/docs/reference/commands/render.md index 05a48e59c..2fb25364c 100644 --- a/docs/reference/commands/render.md +++ b/docs/reference/commands/render.md @@ -21,8 +21,8 @@ a temporary directory or a specified directory. ## Arguments The following sets of arguments are available: -1. [project arguments](./common-arguments#project-arguments) -1. [image arguments](./common-arguments#image-arguments) +1. [project arguments](./common-arguments.md#project-arguments) +1. [image arguments](./common-arguments.md#image-arguments) In addition, the following arguments are available: diff --git a/docs/reference/commands/seal.md b/docs/reference/commands/seal.md index 88edc02c7..4ec5fe457 100644 --- a/docs/reference/commands/seal.md +++ b/docs/reference/commands/seal.md @@ -23,11 +23,11 @@ If no '--target' is specified, sealing is performed for all targets. -See [sealed-secrets](../sealed-secrets) for more details. +See [sealed-secrets](../sealed-secrets.md) for more details. ## Arguments The following sets of arguments are available: -1. [project arguments](./common-arguments#project-arguments) (except `-a`) +1. [project arguments](./common-arguments.md#project-arguments) (except `-a`) In addition, the following arguments are available: diff --git a/docs/reference/commands/validate.md b/docs/reference/commands/validate.md index d904c98fb..987592774 100644 --- a/docs/reference/commands/validate.md +++ b/docs/reference/commands/validate.md @@ -22,8 +22,8 @@ TODO: This needs to be better documented! ## Arguments The following sets of arguments are available: -1. [project arguments](./common-arguments#project-arguments) -1. [image arguments](./common-arguments#image-arguments) +1. [project arguments](./common-arguments.md#project-arguments) +1. [image arguments](./common-arguments.md#image-arguments) In addition, the following arguments are available: diff --git a/docs/reference/deployments/annotations/all-resources.md b/docs/reference/deployments/annotations/all-resources.md index e1aa989d6..aa517a46c 100644 --- a/docs/reference/deployments/annotations/all-resources.md +++ b/docs/reference/deployments/annotations/all-resources.md @@ -37,12 +37,12 @@ If more than one field needs to be specified, add `-xxx` to the annotation key, The following annotations control how delete/prune is behaving. ### kluctl.io/skip-delete -If set to "true", the annotated resource will not be deleted when [delete](../../commands/delete) or -[prune](../../commands/prune) is called. +If set to "true", the annotated resource will not be deleted when [delete](../../commands/delete.md) or +[prune](../../commands/prune.md) is called. ### kluctl.io/skip-delete-if-tags -If set to "true", the annotated resource will not be deleted when [delete](../../commands/delete) or -[prune](../../commands/prune) is called and inclusion/exclusion tags are used at the same time. +If set to "true", the annotated resource will not be deleted when [delete](../../commands/delete.md) or +[prune](../../commands/prune.md) is called and inclusion/exclusion tags are used at the same time. This tag is especially useful and required on resources that would otherwise cause cascaded deletions of resources that do not match the specified inclusion/exclusion tags. Namespaces are the most prominent example of such resources, as diff --git a/docs/reference/deployments/annotations/hooks.md b/docs/reference/deployments/annotations/hooks.md index 1b033bf9b..bcc81b6e1 100644 --- a/docs/reference/deployments/annotations/hooks.md +++ b/docs/reference/deployments/annotations/hooks.md @@ -13,10 +13,10 @@ description: > The following annotations control hook execution -See [hooks](../../deployments/hooks) for more details. +See [hooks](../../deployments/hooks.md) for more details. ### kluctl.io/hook -Declares a resource to be a hook, which is deployed/executed as described in [hooks](../../deployments/hooks). The value of the +Declares a resource to be a hook, which is deployed/executed as described in [hooks](../../deployments/hooks.md). The value of the annotation determines when the hook is deployed/executed. ### kluctl.io/hook-weight diff --git a/docs/reference/deployments/annotations/kustomization.md b/docs/reference/deployments/annotations/kustomization.md index f0312940c..6a740fc18 100644 --- a/docs/reference/deployments/annotations/kustomization.md +++ b/docs/reference/deployments/annotations/kustomization.md @@ -35,4 +35,4 @@ same effect as [barrier](../../deployments/deployment-yml.md#barriers) from depl ### kluctl.io/wait-readiness If set to `true`, kluctl will wait for readiness of all objects from this kustomization project. Readiness is defined -the same as in [hook readiness](../../deployments/readiness). +the same as in [hook readiness](../../deployments/readiness.md). diff --git a/docs/reference/deployments/annotations/validation.md b/docs/reference/deployments/annotations/validation.md index a36fcf2e4..a6c35372c 100644 --- a/docs/reference/deployments/annotations/validation.md +++ b/docs/reference/deployments/annotations/validation.md @@ -11,7 +11,7 @@ description: > # Validation -The following annotations influence the [validate](../../commands/validate) command. +The following annotations influence the [validate](../../commands/validate.md) command. ### validate-result.kluctl.io/xxx If this annotation is found on a resource that is checked while validation, the key and the value of the annotation diff --git a/docs/reference/deployments/deployment-yml.md b/docs/reference/deployments/deployment-yml.md index 0ca5cb75b..722a81e2a 100644 --- a/docs/reference/deployments/deployment-yml.md +++ b/docs/reference/deployments/deployment-yml.md @@ -36,7 +36,7 @@ The following sub-chapters describe the available fields in the `deployment.yaml ## sealedSecrets `sealedSecrets` configures how sealed secrets are stored while sealing and located while rendering. -See [Sealed Secrets](../sealed-secrets#outputpattern-and-location-of-stored-sealed-secrets) +See [Sealed Secrets](../sealed-secrets.md#outputpattern-and-location-of-stored-sealed-secrets) for details. ## deployments @@ -307,5 +307,5 @@ valid [JSON Path](https://goessner.net/articles/JsonPath/). `fieldPath` may also The JSON Path implementation used in kluctl has extended support for wildcards in field names, allowing you to also specify paths like `metadata.labels.my-prefix-*`. -As an alternative, [annotations](./annotations/all-resources#control-diff-behavior) can be used to control +As an alternative, [annotations](./annotations/all-resources.md#control-diff-behavior) can be used to control diff behavior of individual resources. diff --git a/docs/reference/deployments/helm.md b/docs/reference/deployments/helm.md index aab1fdbf0..75016f9e8 100644 --- a/docs/reference/deployments/helm.md +++ b/docs/reference/deployments/helm.md @@ -25,7 +25,7 @@ hands over the rendered yaml to [kustomize](https://kustomize.io/). Rendering is `helm-values.yaml`, which contains the necessary values to configure the Helm Chart. The resulting rendered yaml is then referred by your `kustomization.yaml`, from which point on the -[kustomize integration](../sealed-secrets#outputpattern-and-location-of-stored-sealed-secrets) +[kustomize integration](../sealed-secrets.md#outputpattern-and-location-of-stored-sealed-secrets) takes over. This means, that you can perform all desired customization (patches, namespace override, ...) as if you provided your own resources via yaml files. @@ -99,7 +99,7 @@ The name of the chart that can be found in the repository. The version of the chart. ### skipUpdate -Skip this Helm Chart when the [helm-update](../commands/helm-update) command is called. +Skip this Helm Chart when the [helm-update](../commands/helm-update.md) command is called. If omitted, defaults to `false`. ### releaseName @@ -125,8 +125,8 @@ read the documentation of the used Helm Charts for details on what is supported. ## Updates to helm-charts In case a Helm Chart needs to be updated, you can either do this manually by replacing the [chartVersion](#chartversion) -value in `helm-chart.yaml` and the calling the [helm-pull](../commands/helm-pull) command or by simply invoking -[helm-update](../commands/helm-update) with `--upgrade` and/or `--commit` being set. +value in `helm-chart.yaml` and the calling the [helm-pull](../commands/helm-pull.md) command or by simply invoking +[helm-update](../commands/helm-update.md) with `--upgrade` and/or `--commit` being set. ## Private Chart Repositories It is also possible to use private chart repositories. There are currently two options to provide Helm Repository @@ -139,7 +139,7 @@ added the repository to Helm. The same method can be used for client certificate in `helm repo add`). ### Use the --username/--password arguments in `kluctl helm-pull` -See the [helm-pull command](../commands/helm-pull). You can control repository credentials +See the [helm-pull command](../commands/helm-pull.md). You can control repository credentials via `--username`, `--password` and `--key-file`. Each argument must be in the form `credentialsId:value`, where the `credentialsId` must match the id specified in the `helm-chart.yaml`. Example: @@ -160,7 +160,7 @@ When credentialsId is specified, Kluctl will require you to specify `--username= Multiple Helm Charts can use the same `credentialsId`. Environment variables can also be used instead of arguments. See -[Environment Variables](../commands/environment-variables) for details. +[Environment Variables](../commands/environment-variables.md) for details. ## Templating diff --git a/docs/reference/deployments/images.md b/docs/reference/deployments/images.md index 3b88af2bb..b6270cf7d 100644 --- a/docs/reference/deployments/images.md +++ b/docs/reference/deployments/images.md @@ -89,7 +89,7 @@ The described `images.get_image` logic however leads to a loosely defined state might be fine in a CI/CD environment, but might be undesired when deploying to production. In that case, it might be desirable to explicitly define which versions need to be deployed. -To achieve this, you can use the `-F FIXED_IMAGE` [argument](../commands/common-arguments#image-arguments). +To achieve this, you can use the `-F FIXED_IMAGE` [argument](../commands/common-arguments.md#image-arguments). `FIXED_IMAGE` must be in the form of `-F image<:namespace:deployment:container>=result`. For example, to pin the image `registry.gitlab.com/my-group/my-project` to the tag `1.1.2` you'd have to specify `-F registry.gitlab.com/my-group/my-project=registry.gitlab.com/my-group/my-project:1.1.2`. diff --git a/docs/reference/kluctl-project/README.md b/docs/reference/kluctl-project/README.md index 556e1313f..472a6f80d 100644 --- a/docs/reference/kluctl-project/README.md +++ b/docs/reference/kluctl-project/README.md @@ -13,7 +13,7 @@ description: > The `.kluctl.yaml` is the central configuration and entry point for your deployments. It defines where the actual [deployment project](../deployments) is located, -where [sealed secrets](../sealed-secrets) and unencrypted secrets are localed and which targets are available to +where [sealed secrets](../sealed-secrets.md) and unencrypted secrets are localed and which targets are available to invoke [commands](../commands) on. ## Example diff --git a/docs/reference/kluctl-project/secrets-config/README.md b/docs/reference/kluctl-project/secrets-config/README.md index 81207967a..8e1387af2 100644 --- a/docs/reference/kluctl-project/secrets-config/README.md +++ b/docs/reference/kluctl-project/secrets-config/README.md @@ -35,7 +35,7 @@ This field specifies the name of the secret set. The name can be used in targets ### vars A list of variables sources. Check the documentation of -[variables sources](../../templating/variable-sources) for details. +[variables sources](../../templating/variable-sources.md) for details. Each variables source must have a root dictionary with the name `secrets` and all the actual secret values below that dictionary. Every other root key will be ignored. diff --git a/docs/reference/kluctl-project/targets/README.md b/docs/reference/kluctl-project/targets/README.md index ce1a19f3c..46eba7293 100644 --- a/docs/reference/kluctl-project/targets/README.md +++ b/docs/reference/kluctl-project/targets/README.md @@ -13,7 +13,7 @@ description: > Specifies a list of targets for which commands can be invoked. A target puts together environment/target specific configuration and the target cluster. Multiple targets can exist which target the same cluster but with differing -configuration (via `args`). Target entries also specifies which secrets to use while [sealing](../../sealed-secrets). +configuration (via `args`). Target entries also specifies which secrets to use while [sealing](../../sealed-secrets.md). Each value found in the target definition is rendered with a simple Jinja2 context that only contains the target itself. The rendering process is retried 10 times until it finally succeeds, allowing you to reference @@ -45,7 +45,7 @@ The following fields are allowed per target: ## name This field specifies the name of the target. The name must be unique. It is referred in all commands via the -[-t](../../commands/common-arguments) option. +[-t](../../commands/common-arguments.md) option. ## context This field specifies the kubectl context of the target cluster. The context must exist in the currently active kubeconfig. @@ -53,9 +53,9 @@ If this field is omitted, Kluctl will always use the currently active context. ## args This fields specifies a map of arguments to be passed to the deployment project when it is rendered. Allowed argument names -are configured via [deployment args](../../deployments/deployment-yml#args). +are configured via [deployment args](../../deployments/deployment-yml.md#args). -The arguments specified in the [dynamic target config](../../kluctl-project/targets/dynamic-targets#args) +The arguments specified in the [dynamic target config](../../kluctl-project/targets/dynamic-targets.md#args) have higher priority. ## dynamicArgs @@ -65,17 +65,17 @@ arguments are passed with `-a arg_name=arg_value` when for example calling `kluc Each entry has the following fields: ## images -This field specifies a list of fixed images to be used by [`images.get_image(...)`](../../deployments/images#imagesget_image). -The format is identical to the [fixed images file](https://kluctl.io/docs/reference/deployments/images/#fixed-images-via-a-yaml-file). +This field specifies a list of fixed images to be used by [`images.get_image(...)`](../../deployments/images.md#imagesget_image). +The format is identical to the [fixed images file](../../deployments/images.md#fixed-images-via-a-yaml-file). -The fixed images specified in the [dynamic target config](../../kluctl-project/targets/dynamic-targets#images) +The fixed images specified in the [dynamic target config](../../kluctl-project/targets/dynamic-targets.md#images) have higher priority. ### name The name of the argument. ## sealingConfig -This field configures how sealing is performed when the [seal command](../../commands/seal) is invoked for this target. +This field configures how sealing is performed when the [seal command](../../commands/seal.md) is invoked for this target. It has the following form: ```yaml diff --git a/docs/reference/sealed-secrets.md b/docs/reference/sealed-secrets.md index 81dd38c84..cfd40a2d1 100644 --- a/docs/reference/sealed-secrets.md +++ b/docs/reference/sealed-secrets.md @@ -29,7 +29,7 @@ public certificate via `certFile` in the targets [sealingConfig](./kluctl-projec ## Sealing of .sealme files -Sealing is done via the [seal command](./commands/seal). It must be done before the actual +Sealing is done via the [seal command](./commands/seal.md). It must be done before the actual deployment is performed. The `seal` command recursively searches for files that end with `.sealme`, renders them with the @@ -111,8 +111,8 @@ with: ## Content Hashes and re-sealing Sealed secrets are stored together with hashes of all individual secret entries. These hashes are then used to avoid -unnecessary re-sealing in future [seal](./commands/seal) invocations. If you want to force re-sealing, use the -[--force-reseal](./commands/seal) option. +unnecessary re-sealing in future [seal](./commands/seal.md) invocations. If you want to force re-sealing, use the +[--force-reseal](./commands/seal.md) option. Hashing of secrets is done with bcrypt and the cluster id as salt. The cluster id is currently defined as the sha256 hash of the cluster CA certificate. This will cause re-sealing of all secrets in case a cluster is set up from scratch diff --git a/docs/reference/templating/functions.md b/docs/reference/templating/functions.md index db0eac48e..0ce224c56 100644 --- a/docs/reference/templating/functions.md +++ b/docs/reference/templating/functions.md @@ -44,7 +44,7 @@ data: ### get_var(field_path, default) Convenience method to navigate through the current context variables via a [JSON Path](https://goessner.net/articles/JsonPath/). Let's assume you currently have these variables defined -(e.g. via [vars](../deployments/deployment-yml#vars-deployment-project)): +(e.g. via [vars](../deployments/deployment-yml.md#vars-deployment-project)): ```yaml my: deep: diff --git a/docs/reference/templating/predefined-variables.md b/docs/reference/templating/predefined-variables.md index 5c4e88645..951226f57 100644 --- a/docs/reference/templating/predefined-variables.md +++ b/docs/reference/templating/predefined-variables.md @@ -15,18 +15,18 @@ There are multiple variables available which are pre-defined by kluctl. These ar ### args This is a dictionary of arguments given via command line. It contains every argument defined in -[deployment args](../deployments/deployment-yml#args). +[deployment args](../deployments/deployment-yml.md#args). ### target This is the target definition of the currently processed target. It contains all values found in the [target definition](../kluctl-project/targets), for example `target.name`. ### images -This global object provides the dynamic images features described in [images](../deployments/images). +This global object provides the dynamic images features described in [images](../deployments/images.md). ### version -This global object defines latest version filters for `images.get_image(...)`. See [images](../deployments/images) for details. +This global object defines latest version filters for `images.get_image(...)`. See [images](../deployments/images.md) for details. ### secrets -This global object is only available while [sealing](../sealed-secrets) and contains the loaded +This global object is only available while [sealing](../sealed-secrets.md) and contains the loaded secrets defined via the currently sealed target. diff --git a/docs/reference/templating/variable-sources.md b/docs/reference/templating/variable-sources.md index c81465380..f2391f2a6 100644 --- a/docs/reference/templating/variable-sources.md +++ b/docs/reference/templating/variable-sources.md @@ -14,10 +14,10 @@ description: > There are multiple places in deployment projects (deployment.yaml) where additional variables can be loaded into future Jinja2 contexts. -The first place where vars can be specified is the deployment root, as documented [here](../deployments/deployment-yml#vars-deployment-project). +The first place where vars can be specified is the deployment root, as documented [here](../deployments/deployment-yml.md#vars-deployment-project). These vars are visible for all deployments inside the deployment project, including sub-deployments from includes. -The second place to specify variables is in the deployment items, as documented [here](../deployments/deployment-yml#vars-deployment-item). +The second place to specify variables is in the deployment items, as documented [here](../deployments/deployment-yml.md#vars-deployment-item). The variables loaded for each entry in `vars` are not available inside the `deployment.yaml` file itself. However, each entry in `vars` can use all variables defined before that specific entry is processed. Consider the From bda3a5dd9433263ef53dcd7889eb4a2932e957ab Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 21 Oct 2022 19:45:46 +0200 Subject: [PATCH 1102/2916] docs: Fix issues related to hugo sync --- docs/get-started.md | 2 +- docs/reference/README.md | 2 +- docs/reference/deployments/README.md | 4 ++-- docs/reference/deployments/annotations/README.md | 2 +- docs/reference/deployments/deployment-yml.md | 4 ++-- docs/reference/kluctl-project/README.md | 2 +- docs/reference/templating/README.md | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/get-started.md b/docs/get-started.md index 711235908..e1cc96cce 100644 --- a/docs/get-started.md +++ b/docs/get-started.md @@ -1,7 +1,7 @@ -# Table of Contents +## Table of Contents 1. [.kluctl.yaml](./kluctl-project) 2. [Deployments](./deployments) diff --git a/docs/reference/deployments/README.md b/docs/reference/deployments/README.md index d4e211174..81af0afa7 100644 --- a/docs/reference/deployments/README.md +++ b/docs/reference/deployments/README.md @@ -9,9 +9,9 @@ description: > --- --> -# Table of Contents +## Table of Contents -1. [deployment.yaml](./deployment-yml.md) +1. [Deployments](./deployment-yml.md) 2. [Kustomize Integration](./kustomize.md) 3. [Container Images](./images.md) 4. [Helm Integration](./helm.md) diff --git a/docs/reference/deployments/annotations/README.md b/docs/reference/deployments/annotations/README.md index 398001649..3c38ab58b 100644 --- a/docs/reference/deployments/annotations/README.md +++ b/docs/reference/deployments/annotations/README.md @@ -9,7 +9,7 @@ description: > --- --> -# Table of Contents +## Table of Contents 1. [All Resources](./all-resources.md) 2. [Hooks](./hooks.md) diff --git a/docs/reference/deployments/deployment-yml.md b/docs/reference/deployments/deployment-yml.md index 722a81e2a..1d5914919 100644 --- a/docs/reference/deployments/deployment-yml.md +++ b/docs/reference/deployments/deployment-yml.md @@ -1,8 +1,8 @@ -# Table of Contents +## Table of Contents 1. [Predefined Variables](./predefined-variables.md) 2. [Variable Sources](./variable-sources.md) From 5f1589719a4929321ea8d0ef1cc0374ac7ade735 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 21 Oct 2022 22:28:41 +0200 Subject: [PATCH 1103/2916] docs: Remove .md from raw-html links --- docs/reference/deployments/README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/reference/deployments/README.md b/docs/reference/deployments/README.md index 81af0afa7..ede5bdaa1 100644 --- a/docs/reference/deployments/README.md +++ b/docs/reference/deployments/README.md @@ -37,9 +37,10 @@ precisely what is deployed in the future. Some visualized files/directories have links attached, follow them to get more information. +
 -- project-dir/
-   |-- deployment.yaml
+   |-- deployment.yaml
    |-- .gitignore
    |-- kustomize-deployment1/
    |   |-- kustomization.yaml
@@ -56,7 +57,7 @@ Some visualized files/directories have links attached, follow them to get more i
    |   |   |-- resource2.yaml
    |   |   |-- patch1.yaml
    |   |   `-- ...
-   |   |-- kustomize-with-helm-deployment/
+   |   |-- kustomize-with-helm-deployment/
    |   |   |-- charts/
    |   |   |   `-- ...
    |   |   |-- kustomization.yaml
@@ -69,6 +70,7 @@ Some visualized files/directories have links attached, follow them to get more i
    `-- sub-deployment/
        `-- ...
 
+ ## Order of deployments Deployments are done in parallel, meaning that there are usually no order guarantees. The only way to somehow control From bf052f3986131f642e7bd4cbbced2fcc210e67c9 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 24 Oct 2022 11:56:50 +0200 Subject: [PATCH 1104/2916] feat: Allow to specify default values for systemEnvVars --- docs/reference/templating/variable-sources.md | 12 +++++++++++ pkg/vars/vars_loader.go | 20 ++++++++++++++++--- pkg/vars/vars_loader_test.go | 8 ++++++++ 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/docs/reference/templating/variable-sources.md b/docs/reference/templating/variable-sources.md index f2391f2a6..b2ab642c9 100644 --- a/docs/reference/templating/variable-sources.md +++ b/docs/reference/templating/variable-sources.md @@ -218,3 +218,15 @@ vars: The above example will make 3 variables available: `var1`, `someDict.var2` and `someList[0].var3`, each having the values of the environment variables specified by the leaf values. + +All specified environment variables must be set before calling kluctl unless a default value is set. Default values +can be set by using the `ENV_VAR_NAME:default-value` form. + +Example: +```yaml +vars: +- systemEnvVars: + var1: ENV_VAR_NAME4:defaultValue +``` + +The above example will set the variable `var1` to `defaultValue` in case ENV_VAR_NAME4 is not set. diff --git a/pkg/vars/vars_loader.go b/pkg/vars/vars_loader.go index 53f004f8f..1f37ae405 100644 --- a/pkg/vars/vars_loader.go +++ b/pkg/vars/vars_loader.go @@ -17,6 +17,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/yaml" "k8s.io/apimachinery/pkg/runtime/schema" "os" + "strings" ) type usernamePassword struct { @@ -127,11 +128,24 @@ func (v *VarsLoader) loadSystemEnvs(varsCtx *VarsCtx, source *types.VarsSource, if !ok { return fmt.Errorf("value at %s is not a string", it.KeyPath().ToJsonPath()) } - envValue, ok := os.LookupEnv(envName) - if !ok { + var defaultValue string + hasDefaultValue := false + if strings.IndexRune(envName, ':') != -1 { + s := strings.SplitN(envName, ":", 2) + envName = s[0] + defaultValue = s[1] + hasDefaultValue = true + } + envValueStr := "" + if v, ok := os.LookupEnv(envName); ok { + envValueStr = v + } else if hasDefaultValue { + envValueStr = defaultValue + } else { return fmt.Errorf("environment variable %s not found for %s", envName, it.KeyPath().ToJsonPath()) } - err := newVars.SetNestedField(envValue, it.KeyPath()...) + + err := newVars.SetNestedField(envValueStr, it.KeyPath()...) if err != nil { return fmt.Errorf("failed to set value for %s: %w", it.KeyPath().ToJsonPath(), err) } diff --git a/pkg/vars/vars_loader_test.go b/pkg/vars/vars_loader_test.go index 872b6a6c3..2845892a4 100644 --- a/pkg/vars/vars_loader_test.go +++ b/pkg/vars/vars_loader_test.go @@ -334,6 +334,8 @@ func TestVarsLoader_SystemEnv(t *testing.T) { "test3": map[string]interface{}{ "test4": "TEST4", }, + "test5": "TEST5:def", + "test6": "TEST1:def", }), }, nil, "") assert.NoError(t, err) @@ -346,6 +348,12 @@ func TestVarsLoader_SystemEnv(t *testing.T) { v, _, _ = vc.Vars.GetNestedString("test3", "test4") assert.Equal(t, "44", v) + + v, _, _ = vc.Vars.GetNestedString("test5") + assert.Equal(t, "def", v) + + v, _, _ = vc.Vars.GetNestedString("test6") + assert.Equal(t, "42", v) }) testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { From 89f88cecdddaa6666b4a9ffbf86e0987543c0b91 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 24 Oct 2022 12:07:29 +0200 Subject: [PATCH 1105/2916] feat: Treat systemEnvVars as yaml instead of plain string This is a potentially breakting change, as it will cause integers and booleans to not be treated as strings anymore. --- docs/reference/templating/variable-sources.md | 14 ++++++++++++++ pkg/vars/vars_loader.go | 8 +++++++- pkg/vars/vars_loader_test.go | 18 +++++++++--------- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/docs/reference/templating/variable-sources.md b/docs/reference/templating/variable-sources.md index b2ab642c9..2941e532b 100644 --- a/docs/reference/templating/variable-sources.md +++ b/docs/reference/templating/variable-sources.md @@ -230,3 +230,17 @@ vars: ``` The above example will set the variable `var1` to `defaultValue` in case ENV_VAR_NAME4 is not set. + +All values retrieved from environment variables (or specified as default values) will be treated as YAML, meaning that +integers and booleans will be treated as integers/booleans. If you want to enforce strings, encapsulate the values in +quotes. + +Example: +```yaml +vars: +- systemEnvVars: + var1: ENV_VAR_NAME5:'true' +``` + +The above example will treat `true` as a string instead of a boolean. When the environment variable is set outside +kluctl, it should also contain the quotes. Please note that your shell might require escaping to properly pass quotes. diff --git a/pkg/vars/vars_loader.go b/pkg/vars/vars_loader.go index 1f37ae405..176c59d4e 100644 --- a/pkg/vars/vars_loader.go +++ b/pkg/vars/vars_loader.go @@ -145,7 +145,13 @@ func (v *VarsLoader) loadSystemEnvs(varsCtx *VarsCtx, source *types.VarsSource, return fmt.Errorf("environment variable %s not found for %s", envName, it.KeyPath().ToJsonPath()) } - err := newVars.SetNestedField(envValueStr, it.KeyPath()...) + var envValue any + err := yaml.ReadYamlString(envValueStr, &envValue) + if err != nil { + return fmt.Errorf("failed to parse env value '%s': %w", envValueStr, err) + } + + err = newVars.SetNestedField(envValue, it.KeyPath()...) if err != nil { return fmt.Errorf("failed to set value for %s: %w", it.KeyPath().ToJsonPath(), err) } diff --git a/pkg/vars/vars_loader_test.go b/pkg/vars/vars_loader_test.go index 2845892a4..65027e369 100644 --- a/pkg/vars/vars_loader_test.go +++ b/pkg/vars/vars_loader_test.go @@ -323,7 +323,7 @@ func TestVarsLoader_K8sObjectLabels(t *testing.T) { func TestVarsLoader_SystemEnv(t *testing.T) { t.Setenv("TEST1", "42") - t.Setenv("TEST2", "43") + t.Setenv("TEST2", "'43'") t.Setenv("TEST4", "44") testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { @@ -340,20 +340,20 @@ func TestVarsLoader_SystemEnv(t *testing.T) { }, nil, "") assert.NoError(t, err) - v, _, _ := vc.Vars.GetNestedString("test1") - assert.Equal(t, "42", v) + v, _, _ := vc.Vars.GetNestedField("test1") + assert.Equal(t, 42, v) - v, _, _ = vc.Vars.GetNestedString("test2") + v, _, _ = vc.Vars.GetNestedField("test2") assert.Equal(t, "43", v) - v, _, _ = vc.Vars.GetNestedString("test3", "test4") - assert.Equal(t, "44", v) + v, _, _ = vc.Vars.GetNestedField("test3", "test4") + assert.Equal(t, 44, v) - v, _, _ = vc.Vars.GetNestedString("test5") + v, _, _ = vc.Vars.GetNestedField("test5") assert.Equal(t, "def", v) - v, _, _ = vc.Vars.GetNestedString("test6") - assert.Equal(t, "42", v) + v, _, _ = vc.Vars.GetNestedField("test6") + assert.Equal(t, 42, v) }) testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { From 0bf1753d4a0e284e56a1a7e9cc7cd0b9bc26394d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 24 Oct 2022 14:44:43 +0200 Subject: [PATCH 1106/2916] feat: Allow to pass CLI arguments multiple times via environment variables --- cmd/kluctl/commands/cobra_utils.go | 31 +++++++-- cmd/kluctl/commands/root.go | 4 -- .../commands/environment-variables.md | 3 + pkg/utils/env.go | 67 ++++++++++++++----- pkg/utils/env_test.go | 32 +++++++++ 5 files changed, 112 insertions(+), 25 deletions(-) create mode 100644 pkg/utils/env_test.go diff --git a/cmd/kluctl/commands/cobra_utils.go b/cmd/kluctl/commands/cobra_utils.go index d495f58f6..af5644631 100644 --- a/cmd/kluctl/commands/cobra_utils.go +++ b/cmd/kluctl/commands/cobra_utils.go @@ -234,13 +234,36 @@ func copyViperValuesToCobraFlags(flags *pflag.FlagSet) error { return } v := viper.Get(flag.Name) + + var a []string if v != nil { - s, ok := v.(string) - if !ok { - retErr = append(retErr, fmt.Errorf("viper flag %s is not a string", flag.Name)) + if x, ok := v.(string); ok { + a = []string{x} + } else if x, ok := v.([]any); ok { + for _, y := range x { + s, ok := y.(string) + if !ok { + retErr = append(retErr, fmt.Errorf("viper flag %s has unexpected type", flag.Name)) + return + } + a = append(a, s) + } + } else { + retErr = append(retErr, fmt.Errorf("viper flag %s has unexpected type", flag.Name)) return } - err := flag.Value.Set(s) + } + + envName := strings.ReplaceAll(flag.Name, "-", "_") + envName = strings.ToUpper(envName) + envName = fmt.Sprintf("KLUCTL_%s", envName) + + for _, v := range utils.ParseEnvConfigList(envName) { + a = append(a, v) + } + + for _, x := range a { + err := flag.Value.Set(x) if err != nil { retErr = append(retErr, err) } diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index e85358e86..d864584f0 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -212,10 +212,6 @@ func initViper() { os.Exit(1) } } - - viper.SetEnvPrefix("kluctl") - viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) - viper.AutomaticEnv() } func Execute() { diff --git a/docs/reference/commands/environment-variables.md b/docs/reference/commands/environment-variables.md index 62f775ed6..a76dcea9b 100644 --- a/docs/reference/commands/environment-variables.md +++ b/docs/reference/commands/environment-variables.md @@ -17,6 +17,9 @@ variables always start with `KLUCTL_` and end with the option/argument in upperc underscores. As an example, `--dry-run` can also be specified with the environment variable `KLUCTL_DRY_RUN=true`. +If an argument needs to be specified multiple times through environment variables, indexed can be appended to the +names of the environment variables, e.g. `KLUCTL_ARG_0=name1=value1` and `KLUCTL_ARG_1=name2=value2`. + ## Additional environment variables A few additional environment variables are supported which do not belong to an option/argument. These are: diff --git a/pkg/utils/env.go b/pkg/utils/env.go index 66ae280f9..b78f7dd39 100644 --- a/pkg/utils/env.go +++ b/pkg/utils/env.go @@ -19,11 +19,25 @@ func ParseEnvBool(name string, def bool) (bool, error) { return def, nil } -func ParseEnvConfigSets(prefix string) map[int]map[string]string { +func parseEnv(prefix string, withIndex bool, withSuffix bool) map[int]map[string]string { ret := make(map[int]map[string]string) - r := regexp.MustCompile(fmt.Sprintf(`^%s_(\d+)_(.*)$`, prefix)) - r2 := regexp.MustCompile(fmt.Sprintf(`^%s_(.*)$`, prefix)) + rs := prefix + curGroup := 1 + indexGroup := -1 + suffixGroup := -1 + if withIndex { + rs += `(_\d+)?` + indexGroup = curGroup + curGroup++ + } + if withSuffix { + rs += `_(.*)` + suffixGroup = curGroup + curGroup++ + } + rs = fmt.Sprintf("^%s$", rs) + r := regexp.MustCompile(rs) for _, e := range os.Environ() { eq := strings.Index(e, "=") @@ -34,26 +48,45 @@ func ParseEnvConfigSets(prefix string) map[int]map[string]string { v := e[eq+1:] idx := -1 - key := "" + suffix := "" m := r.FindStringSubmatch(n) - if m != nil { - x, _ := strconv.ParseInt(m[1], 10, 32) - idx = int(x) - key = m[2] - } else { - m = r2.FindStringSubmatch(n) - if m != nil { - key = m[1] - } + if m == nil { + continue } - if key != "" { - if _, ok := ret[idx]; !ok { - ret[idx] = make(map[string]string) + if withIndex { + idxStr := m[indexGroup] + if idxStr != "" { + idxStr = idxStr[1:] // remove leading _ + x, err := strconv.ParseInt(idxStr, 10, 32) + if err != nil { + continue + } + idx = int(x) } - ret[idx][key] = v } + if withSuffix { + suffix = m[suffixGroup] + } + + if _, ok := ret[idx]; !ok { + ret[idx] = map[string]string{} + } + ret[idx][suffix] = v + } + return ret +} + +func ParseEnvConfigSets(prefix string) map[int]map[string]string { + return parseEnv(prefix, true, true) +} + +func ParseEnvConfigList(prefix string) map[int]string { + ret := make(map[int]string) + + for idx, m := range parseEnv(prefix, true, false) { + ret[idx] = m[""] } return ret } diff --git a/pkg/utils/env_test.go b/pkg/utils/env_test.go new file mode 100644 index 000000000..2da463533 --- /dev/null +++ b/pkg/utils/env_test.go @@ -0,0 +1,32 @@ +package utils + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestParseEnvConfigSets_Prefixes(t *testing.T) { + t.Setenv("PREFIX_A", "a") + t.Setenv("PREFIX_B", "b") + t.Setenv("PREFIX2_C", "c") + assert.Equal(t, map[int]map[string]string{}, ParseEnvConfigSets("DUMMY")) + assert.Equal(t, map[int]map[string]string{-1: {"A": "a", "B": "b"}}, ParseEnvConfigSets("PREFIX")) + assert.Equal(t, map[int]map[string]string{-1: {"C": "c"}}, ParseEnvConfigSets("PREFIX2")) +} + +func TestParseEnvConfigSets_Indexes(t *testing.T) { + t.Setenv("PREFIX_A", "a") + t.Setenv("PREFIX_0_A", "a0") + t.Setenv("PREFIX_0_B", "b0") + t.Setenv("PREFIX_1_A", "a1") + assert.Equal(t, map[int]map[string]string{-1: {"A": "a"}, 0: {"A": "a0", "B": "b0"}, 1: {"A": "a1"}}, ParseEnvConfigSets("PREFIX")) +} + +func TestParseEnvConfigList(t *testing.T) { + t.Setenv("PREFIX", "a") + assert.Equal(t, map[int]string{-1: "a"}, ParseEnvConfigList("PREFIX")) + + t.Setenv("PREFIX_0", "b") + t.Setenv("PREFIX_1", "c") + assert.Equal(t, map[int]string{-1: "a", 0: "b", 1: "c"}, ParseEnvConfigList("PREFIX")) +} From 076093234143e97cfabdfdb70df32c62acfb8a2d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 24 Oct 2022 15:08:13 +0200 Subject: [PATCH 1107/2916] tests: Test passing multiple args via environment variables --- e2e/args_test.go | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/e2e/args_test.go b/e2e/args_test.go index b606df688..df588a8a6 100644 --- a/e2e/args_test.go +++ b/e2e/args_test.go @@ -123,3 +123,41 @@ d: assertNestedFieldEquals(t, cm, "c2", "data", "c") assertNestedFieldEquals(t, cm, `{"nested": {"nested2": "d4"}}`, "data", "d") } + +func TestArgsFromEnv(t *testing.T) { + t.Setenv("KLUCTL_ARG", "a=a") + t.Setenv("KLUCTL_ARG_1", "b=b") + t.Setenv("KLUCTL_ARG_2", `c={"nested":{"nested2":"c"}}`) + t.Setenv("KLUCTL_ARG_3", "d=true") + t.Setenv("KLUCTL_ARG_4", "e='true'") + + k := defaultCluster1 + + p := &testProject{} + p.init(t, k, "args-from-envs") + defer p.cleanup() + + createNamespace(t, k, p.projectName) + + p.updateTarget("test", func(target *uo.UnstructuredObject) { + }) + + addConfigMapDeployment(p, "cm", map[string]string{ + "a": `{{ args.a }}`, + "b": `{{ args.b }}`, + "c": `{{ args.c | to_json }}`, + "d": `{{ args.d }}`, + "e": `{{ args.e }}`, + }, resourceOpts{ + name: "cm", + namespace: p.projectName, + }) + + p.KluctlMust("deploy", "--yes", "-t", "test") + cm := k.KubectlYamlMust(t, "-n", p.projectName, "get", "cm/cm") + assertNestedFieldEquals(t, cm, "a", "data", "a") + assertNestedFieldEquals(t, cm, "b", "data", "b") + assertNestedFieldEquals(t, cm, `{"nested": {"nested2": "c"}}`, "data", "c") + assertNestedFieldEquals(t, cm, "True", "data", "d") + assertNestedFieldEquals(t, cm, "true", "data", "e") +} From f3a727c47955e4b7f35bf022b75741845a5738dc Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 24 Oct 2022 15:08:31 +0200 Subject: [PATCH 1108/2916] fix: Print arg value that was invalid --- pkg/deployment/external_args.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/deployment/external_args.go b/pkg/deployment/external_args.go index 952dfa063..365342560 100644 --- a/pkg/deployment/external_args.go +++ b/pkg/deployment/external_args.go @@ -18,7 +18,7 @@ func ParseArgs(argsList []string) (map[string]string, error) { args := make(map[string]string) for _, arg := range argsList { if !argPattern.MatchString(arg) { - return nil, fmt.Errorf("invalid --arg argument. Must be --arg=some_var_name=value") + return nil, fmt.Errorf("invalid --arg argument. Must be --arg=some_var_name=value, not '%s'", arg) } s := strings.SplitN(arg, "=", 2) From a739b97ee79905ca84d10c3e5bef386f4df50a84 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 24 Oct 2022 15:11:01 +0200 Subject: [PATCH 1109/2916] docs: Remove mentions of dynamicArgs --- docs/reference/kluctl-project/targets/README.md | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/docs/reference/kluctl-project/targets/README.md b/docs/reference/kluctl-project/targets/README.md index 46eba7293..a9d0de7a5 100644 --- a/docs/reference/kluctl-project/targets/README.md +++ b/docs/reference/kluctl-project/targets/README.md @@ -29,9 +29,6 @@ targets: arg1: arg2: ... - dynamicArgs: - - name: - ... images: - image: my-image resultImage: my-image:1.2.3 @@ -58,12 +55,6 @@ are configured via [deployment args](../../deployments/deployment-yml.md#args). The arguments specified in the [dynamic target config](../../kluctl-project/targets/dynamic-targets.md#args) have higher priority. -## dynamicArgs -This field specifies a list of CLI arguments that can be passed to kluctl when performing any commands on the target. These -arguments are passed with `-a arg_name=arg_value` when for example calling `kluctl deploy -t target_name`. - -Each entry has the following fields: - ## images This field specifies a list of fixed images to be used by [`images.get_image(...)`](../../deployments/images.md#imagesget_image). The format is identical to the [fixed images file](../../deployments/images.md#fixed-images-via-a-yaml-file). @@ -71,9 +62,6 @@ The format is identical to the [fixed images file](../../deployments/images.md#f The fixed images specified in the [dynamic target config](../../kluctl-project/targets/dynamic-targets.md#images) have higher priority. -### name -The name of the argument. - ## sealingConfig This field configures how sealing is performed when the [seal command](../../commands/seal.md) is invoked for this target. It has the following form: From 9a1703f6c1aed0e2a80f6c72526df27edfaca0e3 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 24 Oct 2022 15:11:16 +0200 Subject: [PATCH 1110/2916] tests: Remove dynamicArgs from TestArgs --- e2e/args_test.go | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/e2e/args_test.go b/e2e/args_test.go index df588a8a6..840203e04 100644 --- a/e2e/args_test.go +++ b/e2e/args_test.go @@ -19,20 +19,6 @@ func TestArgs(t *testing.T) { createNamespace(t, k, p.projectName) p.updateTarget("test", func(target *uo.UnstructuredObject) { - _ = target.SetNestedField([]any{ - map[string]any{ - "name": "a", - }, - map[string]any{ - "name": "b", - }, - map[string]any{ - "name": "c", - }, - map[string]any{ - "name": "d", - }, - }, "dynamicArgs") }) p.updateDeploymentYaml(".", func(o *uo.UnstructuredObject) error { _ = o.SetNestedField([]any{ From d35c8c08165b45d3cd1e6e2e96b3cad8e0da70e4 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 25 Oct 2022 09:24:34 +0200 Subject: [PATCH 1111/2916] ci: Only run tests workflow on main branch and PRs --- .github/workflows/tests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3dd57cb2a..8e060e2ca 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -2,6 +2,8 @@ name: tests on: push: + branches: + - main pull_request: branches: - main From da90721b96ef3c1f1d9029382532657b63ed86e2 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 25 Oct 2022 09:54:00 +0200 Subject: [PATCH 1112/2916] ci: Stop depending on pre-built test binaries and build on the target os runner --- .github/workflows/tests.yml | 48 +++++++++++-------------------------- 1 file changed, 14 insertions(+), 34 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8e060e2ca..faa1a4ee5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -40,7 +40,7 @@ jobs: exit 1 fi - build: + cross-compile: runs-on: ubuntu-20.04 steps: - name: Checkout @@ -55,30 +55,18 @@ jobs: path: | ~/go/pkg/mod ~/.cache/go-build - key: build-go-${{ runner.os }}-${{ hashFiles('**/go.sum') }} + key: cross-compile-go-${{ runner.os }}-${{ hashFiles('**/go.sum') }} restore-keys: | - build-go-${{ runner.os }}- - - name: Run unit tests - run: | - go test ./cmd/... ./pkg/... -v - - name: Build kluctl-e2e (linux) + cross-compile-go-${{ runner.os }}- + - name: Build kluctl (linux) run: | - make test-e2e-build GOARCH=amd64 GOOS=linux - mv ./bin/kluctl-e2e ./bin/kluctl-e2e-linux-amd64 - - name: Build kluctl-e2e (darwin) + make build GOARCH=amd64 GOOS=linux + - name: Build kluctl (darwin) run: | - make test-e2e-build GOARCH=amd64 GOOS=darwin - mv ./bin/kluctl-e2e ./bin/kluctl-e2e-darwin-amd64 - - name: Build kluctl-e2e (windows) + make build GOARCH=amd64 GOOS=darwin + - name: Build kluctl (windows) run: | - make test-e2e-build GOARCH=amd64 GOOS=windows - mv ./bin/kluctl-e2e.exe ./bin/kluctl-e2e-windows-amd64.exe - - name: Upload binaries - uses: actions/upload-artifact@v2 - with: - name: bin - path: | - ./bin + make build GOARCH=amd64 GOOS=windows tests: strategy: @@ -92,8 +80,6 @@ jobs: binary-suffix: windows-amd64 os: [ubuntu-20.04, macos-11, windows-2019] fail-fast: false - needs: - - build runs-on: ${{ matrix.os }} steps: - name: Checkout @@ -109,21 +95,15 @@ jobs: key: tests-go-${{ runner.os }}-${{ hashFiles('**/go.sum') }} restore-keys: | tests-go-${{ runner.os }}- - - name: Download artifacts - uses: actions/download-artifact@v2 - name: setup-envtest shell: bash run: | make install-envtest + - name: Run unit tests + shell: bash + run: | + make test-unit - name: Run e2e tests shell: bash run: | - EXE= - if [ "${{ runner.os }}" == "Windows" ]; then - EXE=.exe - fi - - chmod +x ./bin/* - mv ./bin/kluctl-e2e-${{ matrix.binary-suffix }}$EXE ./bin/kluctl-e2e$EXE - - make test-e2e-pre-built + make test-e2e From 9664ff02b8f3ffd3f8badb95a210b5f693f5e22b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 25 Oct 2022 11:01:50 +0200 Subject: [PATCH 1113/2916] chore: Setup GOOS/GOARCH properly in Makefile --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index 6dee972ce..dcae8fd0d 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,8 @@ # Based on the work of Thomas Poignant (thomaspoignant) # https://gist.github.com/thomaspoignant/5b72d579bd5f311904d973652180c705 +GOOS=$(shell go env GOOS) +GOARCH=$(shell go env GOARCH) + EXE= ifeq ($(GOOS), windows) EXE=.exe From d9f3483b473fcec8a84abd07d38e7b5a9baf7f41 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 25 Oct 2022 10:55:06 +0200 Subject: [PATCH 1114/2916] tests: Refactor webhook handler --- e2e/hooks_test.go | 7 ++++--- internal/test-utils/envtest_cluster_callback.go | 10 ++++------ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/e2e/hooks_test.go b/e2e/hooks_test.go index 86bbf45fa..1d6555f3a 100644 --- a/e2e/hooks_test.go +++ b/e2e/hooks_test.go @@ -5,6 +5,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" "testing" test_utils "github.com/kluctl/kluctl/v2/internal/test-utils" @@ -45,9 +46,9 @@ func (s *HookTestSuite) SetupTest() { s.clearSeenConfigmaps() } -func (s *HookTestSuite) handleConfigmap(o *uo.UnstructuredObject) { - s.T().Logf("handleConfigmap: %s", o.GetK8sName()) - s.seenConfigMaps = append(s.seenConfigMaps, o.GetK8sName()) +func (s *HookTestSuite) handleConfigmap(request admission.Request) { + s.T().Logf("handleConfigmap: %s %s/%s", request.Operation, request.Namespace, request.Name) + s.seenConfigMaps = append(s.seenConfigMaps, request.Name) } func (s *HookTestSuite) clearSeenConfigmaps() { diff --git a/internal/test-utils/envtest_cluster_callback.go b/internal/test-utils/envtest_cluster_callback.go index 2ec870b3c..31a6dc8f4 100644 --- a/internal/test-utils/envtest_cluster_callback.go +++ b/internal/test-utils/envtest_cluster_callback.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "github.com/go-logr/logr" - "github.com/kluctl/kluctl/v2/pkg/utils/uo" admissionv1 "k8s.io/api/admissionregistration/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" @@ -14,7 +13,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" ) -type CallbackHandler func(o *uo.UnstructuredObject) +type CallbackHandler func(request admission.Request) func (k *EnvTestCluster) buildServeCallback(gvr schema.GroupVersionResource, cb CallbackHandler) http.Handler { wh := &webhook.Admission{ @@ -28,12 +27,11 @@ func (k *EnvTestCluster) buildServeCallback(gvr schema.GroupVersionResource, cb } func (k *EnvTestCluster) serveCallback(gvr schema.GroupVersionResource, request admission.Request, cb CallbackHandler) { - o, err := uo.FromString(string(request.Object.Raw)) - if err != nil { - panic(err) + if request.Resource.Group != gvr.Group || request.Resource.Version != gvr.Version || request.Resource.Resource != gvr.Resource { + return } - cb(o) + cb(request) } func (k *EnvTestCluster) startCallbackServer() error { From 4cf053b8f4085deab16f2e80c30c922d7f6112e0 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 25 Oct 2022 11:12:31 +0200 Subject: [PATCH 1115/2916] tests: Add some more info to webhook logging --- e2e/hooks_test.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/e2e/hooks_test.go b/e2e/hooks_test.go index 1d6555f3a..ef4e44fba 100644 --- a/e2e/hooks_test.go +++ b/e2e/hooks_test.go @@ -47,7 +47,21 @@ func (s *HookTestSuite) SetupTest() { } func (s *HookTestSuite) handleConfigmap(request admission.Request) { - s.T().Logf("handleConfigmap: %s %s/%s", request.Operation, request.Namespace, request.Name) + x, err := uo.FromString(string(request.Object.Raw)) + if err != nil { + s.T().Fatal(err) + } + generation, _, err := x.GetNestedInt("metadata", "generation") + if err != nil { + s.T().Fatal(err) + } + uid, _, err := x.GetNestedString("metadata", "uid") + if err != nil { + s.T().Fatal(err) + } + + s.T().Logf("handleConfigmap: op=%s, name=%s/%s, generation=%d, uid=%s", request.Operation, request.Namespace, request.Name, generation, uid) + s.seenConfigMaps = append(s.seenConfigMaps, request.Name) } From 73df58aeed572e6332404fefb9775d24e251846f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 24 Oct 2022 16:06:59 +0200 Subject: [PATCH 1116/2916] tests: Add DynamicClient to EnvTestCluster --- internal/test-utils/envtest_cluster.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/internal/test-utils/envtest_cluster.go b/internal/test-utils/envtest_cluster.go index 5bfd6100a..12891067b 100644 --- a/internal/test-utils/envtest_cluster.go +++ b/internal/test-utils/envtest_cluster.go @@ -7,7 +7,9 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" "io" + "k8s.io/client-go/dynamic" "k8s.io/client-go/rest" + "net/http" "os" "os/exec" "sigs.k8s.io/controller-runtime/pkg/envtest" @@ -24,6 +26,9 @@ type EnvTestCluster struct { Context string config *rest.Config + HttpClient *http.Client + DynamicClient dynamic.Interface + callbackServer webhook.Server callbackServerStop context.CancelFunc } @@ -64,6 +69,18 @@ func (k *EnvTestCluster) Start() error { k.Kubeconfig = kcfg + client, err := rest.HTTPClientFor(k.config) + if err != nil { + return err + } + k.HttpClient = client + + dynamicClient, err := dynamic.NewForConfigAndClient(k.config, k.HttpClient) + if err != nil { + return err + } + k.DynamicClient = dynamicClient + return nil } From 1a779a9a81e58866680eaa7fd4cac9136768e502 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 24 Oct 2022 16:07:43 +0200 Subject: [PATCH 1117/2916] tests: Let flux tests use DynamicClient --- e2e/flux_test.go | 36 ++++++++++++++++++----- e2e/test_resources/kluctl-deployment.yaml | 11 +++++-- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/e2e/flux_test.go b/e2e/flux_test.go index 1b604dccb..269562405 100644 --- a/e2e/flux_test.go +++ b/e2e/flux_test.go @@ -1,7 +1,11 @@ package e2e import ( + "context" "fmt" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" "testing" "github.com/kluctl/kluctl/v2/e2e/test_resources" @@ -9,13 +13,30 @@ import ( "github.com/stretchr/testify/assert" ) +var kluctlDeploymentGVR = schema.GroupVersionResource{ + Group: "flux.kluctl.io", + Version: "v1alpha1", + Resource: "kluctldeployments", +} + +func getKluctlDeploymentObject(t *testing.T, k *test_utils.EnvTestCluster) *uo.UnstructuredObject { + kd, err := k.DynamicClient.Resource(kluctlDeploymentGVR).Namespace("flux-test").Get(context.Background(), "microservices-demo-test", v1.GetOptions{}) + if err != nil { + t.Fatal(err) + } + if kd == nil { + t.Fatal("kluctldeployment not found") + } + return uo.FromUnstructured(kd) +} + func TestFluxCommands(t *testing.T) { t.Parallel() k := defaultCluster1 p := &testProject{} - p.init(t, k, "simple") + p.init(t, k, "flux-test") defer p.cleanup() @@ -23,23 +44,24 @@ func TestFluxCommands(t *testing.T) { test_resources.ApplyYaml("kluctl-crds.yaml", k) test_resources.ApplyYaml("kluctl-deployment.yaml", k) - assertResourceExists(t, k, "default", "kluctldeployment/microservices-demo-test") + // assert that it was created + _ = getKluctlDeploymentObject(t, k) - p.KluctlMust("flux", "suspend", "--namespace", "default", "--kluctl-deployment", "microservices-demo-test") + p.KluctlMust("flux", "suspend", "--namespace", "flux-test", "--kluctl-deployment", "microservices-demo-test") suspend := getKluctlSuspendField(t, k) assert.Equal(t, true, suspend, "Field status.suspend is not false") - p.KluctlMust("flux", "resume", "--namespace", "default", "--kluctl-deployment", "microservices-demo-test", "--no-wait") + p.KluctlMust("flux", "resume", "--namespace", "flux-test", "--kluctl-deployment", "microservices-demo-test", "--no-wait") resume := getKluctlSuspendField(t, k) assert.Equal(t, false, resume, "Field status.suspend is not true") - p.KluctlMust("flux", "reconcile", "--namespace", "default", "--kluctl-deployment", "microservices-demo-test", "--with-source", "--no-wait") + p.KluctlMust("flux", "reconcile", "--namespace", "flux-test", "--kluctl-deployment", "microservices-demo-test", "--with-source", "--no-wait") annotation := getKluctlAnnotations(t, k) assert.Len(t, annotation, 1, "Annotation not present") } func getKluctlSuspendField(t *testing.T, k *test_utils.EnvTestCluster) interface{} { - o := k.KubectlYamlMust(t, "-n", "default", "get", "kluctldeployment", "microservices-demo-test") + o := getKluctlDeploymentObject(t, k) result, ok, err := o.GetNestedField("spec", "suspend") fmt.Println(result) if err != nil { @@ -52,7 +74,7 @@ func getKluctlSuspendField(t *testing.T, k *test_utils.EnvTestCluster) interface } func getKluctlAnnotations(t *testing.T, k *test_utils.EnvTestCluster) interface{} { - o := k.KubectlYamlMust(t, "-n", "default", "get", "kluctldeployment", "microservices-demo-test") + o := getKluctlDeploymentObject(t, k) result, ok, err := o.GetNestedField("metadata", "annotations") if err != nil { t.Fatal(err) diff --git a/e2e/test_resources/kluctl-deployment.yaml b/e2e/test_resources/kluctl-deployment.yaml index e10aeede8..eae1bec80 100644 --- a/e2e/test_resources/kluctl-deployment.yaml +++ b/e2e/test_resources/kluctl-deployment.yaml @@ -1,9 +1,14 @@ --- +apiVersion: v1 +kind: Namespace +metadata: + name: flux-test +--- apiVersion: flux.kluctl.io/v1alpha1 kind: KluctlDeployment metadata: name: microservices-demo-test - namespace: default + namespace: flux-test spec: interval: 10m path: ./simple @@ -14,7 +19,7 @@ spec: sourceRef: kind: GitRepository name: microservices-demo - namespace: default + namespace: flux-test target: simple timeout: 2m --- @@ -22,7 +27,7 @@ apiVersion: source.toolkit.fluxcd.io/v1beta1 kind: GitRepository metadata: name: microservices-demo - namespace: default + namespace: flux-test spec: interval: 5m ref: From 0f96267fc13780fb5f875fe0cc8661667900d5f8 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 24 Oct 2022 16:08:53 +0200 Subject: [PATCH 1118/2916] tests: Remove waitReadiness and waitForReadiness --- e2e/utils.go | 49 ++----------------------------------------------- 1 file changed, 2 insertions(+), 47 deletions(-) diff --git a/e2e/utils.go b/e2e/utils.go index eaf69dea2..e4f75094a 100644 --- a/e2e/utils.go +++ b/e2e/utils.go @@ -3,18 +3,13 @@ package e2e import ( "bufio" "bytes" - "fmt" + test_utils "github.com/kluctl/kluctl/v2/internal/test-utils" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" "io" "os/exec" "reflect" "strings" "testing" - "time" - - test_utils "github.com/kluctl/kluctl/v2/internal/test-utils" - "github.com/kluctl/kluctl/v2/pkg/utils/uo" - "github.com/kluctl/kluctl/v2/pkg/validation" - log "github.com/sirupsen/logrus" ) func createNamespace(t *testing.T, k *test_utils.EnvTestCluster, namespace string) { @@ -22,46 +17,6 @@ func createNamespace(t *testing.T, k *test_utils.EnvTestCluster, namespace strin k.KubectlMust(t, "label", "ns", namespace, "kluctl-e2e=true") } -func waitForReadiness(t *testing.T, k *test_utils.EnvTestCluster, namespace string, resource string, timeout time.Duration) bool { - t.Logf("Waiting for readiness: %s/%s", namespace, resource) - - startTime := time.Now() - for time.Now().Sub(startTime) < timeout { - y, stderr, err := k.KubectlYaml("-n", namespace, "get", resource) - if err != nil { - if strings.Index(stderr, "NotFound") == -1 { - t.Fatal(err) - } - time.Sleep(1 * time.Second) - continue - } - - v := validation.ValidateObject(nil, y, true) - if v.Ready { - return true - } - - if log.IsLevelEnabled(log.DebugLevel) { - errTxt := "" - for _, e := range v.Errors { - if errTxt != "" { - errTxt += "\n" - } - errTxt += fmt.Sprintf("%s: %s", e.Ref.String(), e.Error) - } - log.Debugf("validation failed for %s/%s. errors:\n%s", namespace, resource, errTxt) - } - time.Sleep(1 * time.Second) - } - return false -} - -func assertReadiness(t *testing.T, k *test_utils.EnvTestCluster, namespace string, resource string, timeout time.Duration) { - if !waitForReadiness(t, k, namespace, resource, timeout) { - t.Errorf("%s/%s did not get ready in time", namespace, resource) - } -} - func assertResourceExists(t *testing.T, k *test_utils.EnvTestCluster, namespace string, resource string) *uo.UnstructuredObject { var args []string if namespace != "" { From da3134335991590f26bfcad82e1e361d8690ca97 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 24 Oct 2022 16:18:17 +0200 Subject: [PATCH 1119/2916] tests: Use DynamicClient instead of kubectl to check for configmaps --- e2e/contexts_test.go | 32 +++++++++--------- e2e/deploy_test.go | 6 ++-- e2e/hooks_test.go | 2 +- e2e/no_target_test.go | 8 ++--- e2e/utils.go | 45 ++++++++++++++------------ internal/test-utils/envtest_cluster.go | 26 +++++++++++++++ 6 files changed, 74 insertions(+), 45 deletions(-) diff --git a/e2e/contexts_test.go b/e2e/contexts_test.go index 6d9d10c7a..7de30a13f 100644 --- a/e2e/contexts_test.go +++ b/e2e/contexts_test.go @@ -33,15 +33,15 @@ func TestContextCurrent(t *testing.T) { }) p.KluctlMust("deploy", "--yes", "-t", "test1") - assertResourceExists(t, defaultCluster1, p.projectName, "ConfigMap/cm") - assertResourceNotExists(t, defaultCluster2, p.projectName, "ConfigMap/cm") + assertConfigMapExists(t, defaultCluster1, p.projectName, "cm") + assertConfigMapNotExists(t, defaultCluster2, p.projectName, "cm") p.updateMergedKubeconfig(func(config *api.Config) { config.CurrentContext = defaultCluster2.Context }) p.KluctlMust("deploy", "--yes", "-t", "test1") - assertResourceExists(t, defaultCluster2, p.projectName, "ConfigMap/cm") + assertConfigMapExists(t, defaultCluster2, p.projectName, "cm") } func TestContext1(t *testing.T) { @@ -55,8 +55,8 @@ func TestContext1(t *testing.T) { }) p.KluctlMust("deploy", "--yes", "-t", "test1") - assertResourceExists(t, defaultCluster1, p.projectName, "ConfigMap/cm") - assertResourceNotExists(t, defaultCluster2, p.projectName, "ConfigMap/cm") + assertConfigMapExists(t, defaultCluster1, p.projectName, "cm") + assertConfigMapNotExists(t, defaultCluster2, p.projectName, "cm") } func TestContext2(t *testing.T) { @@ -70,8 +70,8 @@ func TestContext2(t *testing.T) { }) p.KluctlMust("deploy", "--yes", "-t", "test1") - assertResourceExists(t, defaultCluster2, p.projectName, "ConfigMap/cm") - assertResourceNotExists(t, defaultCluster1, p.projectName, "ConfigMap/cm") + assertConfigMapExists(t, defaultCluster2, p.projectName, "cm") + assertConfigMapNotExists(t, defaultCluster1, p.projectName, "cm") } func TestContext1And2(t *testing.T) { @@ -88,11 +88,11 @@ func TestContext1And2(t *testing.T) { }) p.KluctlMust("deploy", "--yes", "-t", "test1") - assertResourceExists(t, defaultCluster1, p.projectName, "ConfigMap/cm") - assertResourceNotExists(t, defaultCluster2, p.projectName, "ConfigMap/cm") + assertConfigMapExists(t, defaultCluster1, p.projectName, "cm") + assertConfigMapNotExists(t, defaultCluster2, p.projectName, "cm") p.KluctlMust("deploy", "--yes", "-t", "test2") - assertResourceExists(t, defaultCluster2, p.projectName, "ConfigMap/cm") + assertConfigMapExists(t, defaultCluster2, p.projectName, "cm") } func TestContextSwitch(t *testing.T) { @@ -106,15 +106,15 @@ func TestContextSwitch(t *testing.T) { }) p.KluctlMust("deploy", "--yes", "-t", "test1") - assertResourceExists(t, defaultCluster1, p.projectName, "ConfigMap/cm") - assertResourceNotExists(t, defaultCluster2, p.projectName, "ConfigMap/cm") + assertConfigMapExists(t, defaultCluster1, p.projectName, "cm") + assertConfigMapNotExists(t, defaultCluster2, p.projectName, "cm") p.updateTarget("test1", func(target *uo.UnstructuredObject) { _ = target.SetNestedField(defaultCluster2.Context, "context") }) p.KluctlMust("deploy", "--yes", "-t", "test1") - assertResourceExists(t, defaultCluster2, p.projectName, "ConfigMap/cm") + assertConfigMapExists(t, defaultCluster2, p.projectName, "cm") } func TestContextOverride(t *testing.T) { @@ -128,9 +128,9 @@ func TestContextOverride(t *testing.T) { }) p.KluctlMust("deploy", "--yes", "-t", "test1") - assertResourceExists(t, defaultCluster1, p.projectName, "ConfigMap/cm") - assertResourceNotExists(t, defaultCluster2, p.projectName, "ConfigMap/cm") + assertConfigMapExists(t, defaultCluster1, p.projectName, "cm") + assertConfigMapNotExists(t, defaultCluster2, p.projectName, "cm") p.KluctlMust("deploy", "--yes", "-t", "test1", "--context", defaultCluster2.Context) - assertResourceExists(t, defaultCluster2, p.projectName, "ConfigMap/cm") + assertConfigMapExists(t, defaultCluster2, p.projectName, "cm") } diff --git a/e2e/deploy_test.go b/e2e/deploy_test.go index 9e54ebcf8..c6b0493da 100644 --- a/e2e/deploy_test.go +++ b/e2e/deploy_test.go @@ -22,14 +22,14 @@ func TestCommandDeploySimple(t *testing.T) { namespace: p.projectName, }) p.KluctlMust("deploy", "--yes", "-t", "test") - assertResourceExists(t, k, p.projectName, "ConfigMap/cm") + assertConfigMapExists(t, k, p.projectName, "cm") addConfigMapDeployment(p, "cm2", nil, resourceOpts{ name: "cm2", namespace: p.projectName, }) p.KluctlMust("deploy", "--yes", "-t", "test", "--dry-run") - assertResourceNotExists(t, k, p.projectName, "ConfigMap/cm2") + assertConfigMapNotExists(t, k, p.projectName, "cm2") p.KluctlMust("deploy", "--yes", "-t", "test") - assertResourceExists(t, k, p.projectName, "ConfigMap/cm2") + assertConfigMapExists(t, k, p.projectName, "cm2") } diff --git a/e2e/hooks_test.go b/e2e/hooks_test.go index ef4e44fba..0385eb418 100644 --- a/e2e/hooks_test.go +++ b/e2e/hooks_test.go @@ -132,7 +132,7 @@ func (s *HookTestSuite) ensureHookExecuted(p *testProject, expectedCms ...string func (s *HookTestSuite) ensureHookNotExecuted(p *testProject) { _, _, _ = s.k.Kubectl("delete", "-n", p.projectName, "configmaps", "cm1") p.KluctlMust("deploy", "--yes", "-t", "test") - assertResourceNotExists(s.T(), s.k, p.projectName, "ConfigMap/cm1") + assertConfigMapNotExists(s.T(), s.k, p.projectName, "cm1") } func (s *HookTestSuite) TestHooksPreDeployInitial() { diff --git a/e2e/no_target_test.go b/e2e/no_target_test.go index 80f419298..31351c3eb 100644 --- a/e2e/no_target_test.go +++ b/e2e/no_target_test.go @@ -31,22 +31,22 @@ func TestNoTarget(t *testing.T) { defer p.cleanup() p.KluctlMust("deploy", "--yes") - cm := assertResourceExists(t, defaultCluster1, p.projectName, "ConfigMap/cm") - assertResourceNotExists(t, defaultCluster2, p.projectName, "ConfigMap/cm") + cm := assertConfigMapExists(t, defaultCluster1, p.projectName, "cm") + assertConfigMapNotExists(t, defaultCluster2, p.projectName, "cm") assert.Equal(t, map[string]any{ "targetName": "", "targetContext": defaultCluster1.Context, }, cm.Object["data"]) p.KluctlMust("deploy", "--yes", "-T", "override-name") - cm = assertResourceExists(t, defaultCluster1, p.projectName, "ConfigMap/cm") + cm = assertConfigMapExists(t, defaultCluster1, p.projectName, "cm") assert.Equal(t, map[string]any{ "targetName": "override-name", "targetContext": defaultCluster1.Context, }, cm.Object["data"]) p.KluctlMust("deploy", "--yes", "-T", "override-name", "--context", defaultCluster2.Context) - cm = assertResourceExists(t, defaultCluster2, p.projectName, "ConfigMap/cm") + cm = assertConfigMapExists(t, defaultCluster2, p.projectName, "cm") assert.Equal(t, map[string]any{ "targetName": "override-name", "targetContext": defaultCluster2.Context, diff --git a/e2e/utils.go b/e2e/utils.go index e4f75094a..7e7ac0d26 100644 --- a/e2e/utils.go +++ b/e2e/utils.go @@ -3,42 +3,45 @@ package e2e import ( "bufio" "bytes" + "context" test_utils "github.com/kluctl/kluctl/v2/internal/test-utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "io" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "os/exec" "reflect" - "strings" "testing" ) func createNamespace(t *testing.T, k *test_utils.EnvTestCluster, namespace string) { - k.KubectlMust(t, "create", "ns", namespace) - k.KubectlMust(t, "label", "ns", namespace, "kluctl-e2e=true") -} + var ns unstructured.Unstructured + ns.SetName(namespace) + + _, err := k.DynamicClient.Resource(v1.SchemeGroupVersion.WithResource("namespaces")).Create(context.Background(), &ns, metav1.CreateOptions{}) -func assertResourceExists(t *testing.T, k *test_utils.EnvTestCluster, namespace string, resource string) *uo.UnstructuredObject { - var args []string - if namespace != "" { - args = append(args, "-n", namespace) + if err != nil { + t.Fatal(err) } - args = append(args, "get", resource) - return k.KubectlYamlMust(t, args...) } -func assertResourceNotExists(t *testing.T, k *test_utils.EnvTestCluster, namespace string, resource string) { - var args []string - if namespace != "" { - args = append(args, "-n", namespace) +func assertConfigMapExists(t *testing.T, k *test_utils.EnvTestCluster, namespace string, name string) *uo.UnstructuredObject { + x, err := k.Get(v1.SchemeGroupVersion.WithResource("configmaps"), namespace, name) + if err != nil { + t.Fatalf("unexpected error '%v' while getting ConfigMap %s/%s", err, namespace, name) } - args = append(args, "get", resource) - _, stderr, err := k.KubectlYaml(args...) + return x +} + +func assertConfigMapNotExists(t *testing.T, k *test_utils.EnvTestCluster, namespace string, name string) { + _, err := k.Get(v1.SchemeGroupVersion.WithResource("configmaps"), namespace, name) if err == nil { - t.Fatalf("'kubectl get' for %s should not have succeeded", resource) - } else { - if strings.Index(stderr, "(NotFound)") == -1 { - t.Fatal(err) - } + t.Fatalf("expected %s/%s to not exist", namespace, name) + } + if !errors.IsNotFound(err) { + t.Fatalf("unexpected error '%v' for %s/%s, expected a NotFound error", err, namespace, name) } } diff --git a/internal/test-utils/envtest_cluster.go b/internal/test-utils/envtest_cluster.go index 12891067b..33fa9a51a 100644 --- a/internal/test-utils/envtest_cluster.go +++ b/internal/test-utils/envtest_cluster.go @@ -7,6 +7,8 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" "io" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/dynamic" "k8s.io/client-go/rest" "net/http" @@ -100,6 +102,30 @@ func (c *EnvTestCluster) RESTConfig() *rest.Config { return c.config } +func (c *EnvTestCluster) Get(gvr schema.GroupVersionResource, namespace string, name string) (*uo.UnstructuredObject, error) { + x, err := c.DynamicClient.Resource(gvr).Namespace(namespace).Get(context.Background(), name, metav1.GetOptions{}) + if err != nil { + return nil, err + } + return uo.FromUnstructured(x), nil +} + +func (c *EnvTestCluster) MustGet(t *testing.T, gvr schema.GroupVersionResource, namespace string, name string) *uo.UnstructuredObject { + x, err := c.Get(gvr, namespace, name) + if err != nil { + t.Fatalf("error while getting %s/%s/%s: %s", gvr.String(), namespace, name, err.Error()) + } + return x +} + +func (c *EnvTestCluster) MustGetCoreV1(t *testing.T, resource string, namespace string, name string) *uo.UnstructuredObject { + return c.MustGet(t, schema.GroupVersionResource{ + Group: "", + Version: "v1", + Resource: resource, + }, namespace, name) +} + func (c *EnvTestCluster) Kubectl(args ...string) (string, string, error) { tmp, err := os.CreateTemp("", "") if err != nil { From 54edaaa93d29e8e6cbb96bf2498bbc276b39c76b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 24 Oct 2022 17:06:33 +0200 Subject: [PATCH 1120/2916] tests: Stop using kubectl in ApplyYamls --- e2e/test_resources/resources.go | 68 +++++++++++++++++++++++++---- pkg/deployment/commands/validate.go | 2 +- pkg/deployment/utils/apply_utils.go | 2 +- pkg/validation/validation.go | 6 ++- 4 files changed, 67 insertions(+), 11 deletions(-) diff --git a/e2e/test_resources/resources.go b/e2e/test_resources/resources.go index 90454201d..2e53339ec 100644 --- a/e2e/test_resources/resources.go +++ b/e2e/test_resources/resources.go @@ -1,8 +1,17 @@ package test_resources import ( + "context" "embed" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/validation" + "github.com/kluctl/kluctl/v2/pkg/yaml" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" "os" + "strings" + "time" test_utils "github.com/kluctl/kluctl/v2/internal/test-utils" "github.com/kluctl/kluctl/v2/pkg/utils" @@ -26,22 +35,65 @@ func GetYamlTmpFile(name string) string { return tmpFile.Name() } +// poor mans resource mapper :) +func guessGVR(gvk schema.GroupVersionKind) schema.GroupVersionResource { + gvr := schema.GroupVersionResource{ + Group: gvk.Group, + Version: gvk.Version, + } + if strings.HasSuffix(gvk.Kind, "y") { + gvr.Resource = strings.ToLower(gvk.Kind)[:len(gvk.Kind)-1] + "ies" + } else { + gvr.Resource = strings.ToLower(gvk.Kind) + "s" + } + return gvr +} + func ApplyYaml(name string, k *test_utils.EnvTestCluster) { tmpFile := GetYamlTmpFile(name) defer os.Remove(tmpFile) - _, _, err := k.Kubectl("apply", "-f", tmpFile) + docs, err := yaml.ReadYamlAllFile(tmpFile) if err != nil { panic(err) } -} -func DeleteYaml(name string, k *test_utils.EnvTestCluster) { - tmpFile := GetYamlTmpFile(name) - defer os.Remove(tmpFile) + for _, doc := range docs { + m, ok := doc.(map[string]any) + if !ok { + panic("not a map!") + } + x := uo.FromMap(m) - _, _, err := k.Kubectl("delete", "-f", tmpFile) - if err != nil { - panic(err) + data, err := yaml.WriteYamlBytes(x) + if err != nil { + panic(err) + } + + gvr := guessGVR(x.GetK8sGVK()) + _, err = k.DynamicClient.Resource(gvr). + Namespace(x.GetK8sNamespace()). + Patch(context.Background(), x.GetK8sName(), types.ApplyPatchType, data, metav1.PatchOptions{ + FieldManager: "e2e-tests", + }) + if err != nil { + panic(err) + } + + // wait for CRDs to get accepted + if x.GetK8sGVK().Kind == "CustomResourceDefinition" { + for true { + u, err := k.DynamicClient.Resource(gvr).Get(context.Background(), x.GetK8sName(), metav1.GetOptions{}) + if err != nil { + panic(err) + } + vr := validation.ValidateObject(nil, uo.FromUnstructured(u), true, true) + if vr.Ready { + break + } else { + time.Sleep(time.Millisecond * 100) + } + } + } } } diff --git a/pkg/deployment/commands/validate.go b/pkg/deployment/commands/validate.go index 0647a3b51..1b4f20e89 100644 --- a/pkg/deployment/commands/validate.go +++ b/pkg/deployment/commands/validate.go @@ -56,7 +56,7 @@ func (cmd *ValidateCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*types. result.Errors = append(result.Errors, types.DeploymentError{Ref: ref, Error: "object not found"}) continue } - r := validation.ValidateObject(k, remoteObject, true) + r := validation.ValidateObject(k, remoteObject, true, false) if !r.Ready { result.Ready = false } diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index bf1043c91..440b83e76 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -363,7 +363,7 @@ func (a *ApplyUtil) WaitReadiness(ref k8s2.ObjectRef, timeout time.Duration) boo a.HandleError(ref, err) return false } - v := validation.ValidateObject(a.k, o, false) + v := validation.ValidateObject(a.k, o, false, false) if v.Ready { if didLog { a.sctx.InfoFallback("Finished waiting for %s (%ds elapsed)", ref.String(), elapsed) diff --git a/pkg/validation/validation.go b/pkg/validation/validation.go index 7a1f1083c..36cf3445e 100644 --- a/pkg/validation/validation.go +++ b/pkg/validation/validation.go @@ -45,7 +45,7 @@ const ( reactNotReady ) -func ValidateObject(k *k8s.K8sCluster, o *uo.UnstructuredObject, notReadyIsError bool) (ret types.ValidateResult) { +func ValidateObject(k *k8s.K8sCluster, o *uo.UnstructuredObject, notReadyIsError bool, forceStatusRequired bool) (ret types.ValidateResult) { ref := o.GetK8sRef() // We assume all is good in case no validation is performed @@ -118,6 +118,10 @@ func ValidateObject(k *k8s.K8sCluster, o *uo.UnstructuredObject, notReadyIsError status, _, _ := o.GetNestedObject("status") if status == nil { + if forceStatusRequired { + addNotReady("no status available yet") + return + } if k == nil { // can't really say anything... return From 9befb241e5cec495988e0fe868583c48d724af56 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 25 Oct 2022 11:31:05 +0200 Subject: [PATCH 1121/2916] tests: Stop using kubectl in inclusion_test.go --- e2e/inclusion_test.go | 8 +++++--- internal/test-utils/envtest_cluster.go | 27 ++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/e2e/inclusion_test.go b/e2e/inclusion_test.go index 243584b03..99e1beaab 100644 --- a/e2e/inclusion_test.go +++ b/e2e/inclusion_test.go @@ -1,8 +1,8 @@ package e2e import ( - "fmt" "github.com/kluctl/kluctl/v2/internal/test-utils" + corev1 "k8s.io/api/core/v1" "path/filepath" "reflect" "testing" @@ -58,8 +58,10 @@ func assertExistsHelper(t *testing.T, p *testProject, k *test_utils.EnvTestClust delete(shouldExists, x) } } - exists := k.KubectlYamlMust(t, "-n", p.projectName, "get", "configmaps", "-l", fmt.Sprintf("project_name=%s", p.projectName)) - items, _, _ := exists.GetNestedObjectList("items") + items, err := k.List(corev1.SchemeGroupVersion.WithResource("configmaps"), p.projectName, map[string]string{"project_name": p.projectName}) + if err != nil { + t.Fatal(err) + } found := make(map[string]bool) for _, x := range items { found[x.GetK8sName()] = true diff --git a/internal/test-utils/envtest_cluster.go b/internal/test-utils/envtest_cluster.go index 33fa9a51a..fbf531283 100644 --- a/internal/test-utils/envtest_cluster.go +++ b/internal/test-utils/envtest_cluster.go @@ -126,6 +126,33 @@ func (c *EnvTestCluster) MustGetCoreV1(t *testing.T, resource string, namespace }, namespace, name) } +func (c *EnvTestCluster) List(gvr schema.GroupVersionResource, namespace string, labels map[string]string) ([]*uo.UnstructuredObject, error) { + labelSelector := "" + if len(labels) != 0 { + for k, v := range labels { + if labelSelector != "" { + labelSelector += "," + } + labelSelector += fmt.Sprintf("%s=%s", k, v) + } + } + + l, err := c.DynamicClient.Resource(gvr). + Namespace(namespace). + List(context.Background(), metav1.ListOptions{ + LabelSelector: labelSelector, + }) + if err != nil { + return nil, err + } + var ret []*uo.UnstructuredObject + for _, x := range l.Items { + x := x + ret = append(ret, uo.FromUnstructured(&x)) + } + return ret, nil +} + func (c *EnvTestCluster) Kubectl(args ...string) (string, string, error) { tmp, err := os.CreateTemp("", "") if err != nil { From abe958b7e83f351bb49449d0fd9353c5a278b6df Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 25 Oct 2022 11:31:25 +0200 Subject: [PATCH 1122/2916] tests: Stop using kubectl in args_test.go --- e2e/args_test.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/e2e/args_test.go b/e2e/args_test.go index 840203e04..026f3bee0 100644 --- a/e2e/args_test.go +++ b/e2e/args_test.go @@ -50,32 +50,32 @@ func TestArgs(t *testing.T) { }) p.KluctlMust("deploy", "--yes", "-t", "test", "-aa=a") - cm := k.KubectlYamlMust(t, "-n", p.projectName, "get", "cm/cm") + cm := k.MustGetCoreV1(t, "configmaps", p.projectName, "cm") assertNestedFieldEquals(t, cm, "a", "data", "a") assertNestedFieldEquals(t, cm, "default", "data", "b") assertNestedFieldEquals(t, cm, "na", "data", "c") assertNestedFieldEquals(t, cm, `{"nested": "default"}`, "data", "d") p.KluctlMust("deploy", "--yes", "-t", "test", "-aa=a", "-ab=b") - cm = k.KubectlYamlMust(t, "-n", p.projectName, "get", "cm/cm") + cm = k.MustGetCoreV1(t, "configmaps", p.projectName, "cm") assertNestedFieldEquals(t, cm, "a", "data", "a") assertNestedFieldEquals(t, cm, "b", "data", "b") assertNestedFieldEquals(t, cm, "na", "data", "c") assertNestedFieldEquals(t, cm, `{"nested": "default"}`, "data", "d") p.KluctlMust("deploy", "--yes", "-t", "test", "-aa=a", "-ab=b", "-ac=c") - cm = k.KubectlYamlMust(t, "-n", p.projectName, "get", "cm/cm") + cm = k.MustGetCoreV1(t, "configmaps", p.projectName, "cm") assertNestedFieldEquals(t, cm, "a", "data", "a") assertNestedFieldEquals(t, cm, "b", "data", "b") assertNestedFieldEquals(t, cm, "c", "data", "c") assertNestedFieldEquals(t, cm, `{"nested": "default"}`, "data", "d") p.KluctlMust("deploy", "--yes", "-t", "test", "-aa=a", "-ab=b", "-ac=c", "-ad.nested=d") - cm = k.KubectlYamlMust(t, "-n", p.projectName, "get", "cm/cm") + cm = k.MustGetCoreV1(t, "configmaps", p.projectName, "cm") assertNestedFieldEquals(t, cm, `{"nested": "d"}`, "data", "d") p.KluctlMust("deploy", "--yes", "-t", "test", "-aa=a", "-ab=b", "-ac=c", `-ad={"nested": "d2"}`) - cm = k.KubectlYamlMust(t, "-n", p.projectName, "get", "cm/cm") + cm = k.MustGetCoreV1(t, "configmaps", p.projectName, "cm") assertNestedFieldEquals(t, cm, `{"nested": "d2"}`, "data", "d") tmpFile, err := os.CreateTemp("", "") @@ -89,7 +89,7 @@ nested: `) p.KluctlMust("deploy", "--yes", "-t", "test", "-aa=a", "-ab=b", "-ac=c", fmt.Sprintf(`-ad=@%s`, tmpFile.Name())) - cm = k.KubectlYamlMust(t, "-n", p.projectName, "get", "cm/cm") + cm = k.MustGetCoreV1(t, "configmaps", p.projectName, "cm") assertNestedFieldEquals(t, cm, `{"nested": {"nested2": "d3"}}`, "data", "d") _ = tmpFile.Truncate(0) @@ -103,7 +103,7 @@ d: `) p.KluctlMust("deploy", "--yes", "-t", "test", fmt.Sprintf(`--args-from-file=%s`, tmpFile.Name())) - cm = k.KubectlYamlMust(t, "-n", p.projectName, "get", "cm/cm") + cm = k.MustGetCoreV1(t, "configmaps", p.projectName, "cm") assertNestedFieldEquals(t, cm, "a2", "data", "a") assertNestedFieldEquals(t, cm, "default", "data", "b") assertNestedFieldEquals(t, cm, "c2", "data", "c") @@ -140,7 +140,7 @@ func TestArgsFromEnv(t *testing.T) { }) p.KluctlMust("deploy", "--yes", "-t", "test") - cm := k.KubectlYamlMust(t, "-n", p.projectName, "get", "cm/cm") + cm := k.MustGetCoreV1(t, "configmaps", p.projectName, "cm") assertNestedFieldEquals(t, cm, "a", "data", "a") assertNestedFieldEquals(t, cm, "b", "data", "b") assertNestedFieldEquals(t, cm, `{"nested": {"nested2": "c"}}`, "data", "c") From e6b33ce8ad9ccc6f4aa5d44d91f2c51617bd95fb Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 25 Oct 2022 11:31:38 +0200 Subject: [PATCH 1123/2916] tests: Stop using kubectl in hooks_test.go --- e2e/hooks_test.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/e2e/hooks_test.go b/e2e/hooks_test.go index 0385eb418..1d16eb736 100644 --- a/e2e/hooks_test.go +++ b/e2e/hooks_test.go @@ -1,9 +1,12 @@ package e2e import ( + "context" "fmt" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" "testing" @@ -130,7 +133,9 @@ func (s *HookTestSuite) ensureHookExecuted(p *testProject, expectedCms ...string } func (s *HookTestSuite) ensureHookNotExecuted(p *testProject) { - _, _, _ = s.k.Kubectl("delete", "-n", p.projectName, "configmaps", "cm1") + _ = s.k.DynamicClient.Resource(corev1.SchemeGroupVersion.WithResource("configmaps")). + Namespace(p.projectName). + Delete(context.Background(), "cm1", metav1.DeleteOptions{}) p.KluctlMust("deploy", "--yes", "-t", "test") assertConfigMapNotExists(s.T(), s.k, p.projectName, "cm1") } From e805b7f8645c066c234952cfc1227268641e13bc Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 25 Oct 2022 11:31:48 +0200 Subject: [PATCH 1124/2916] tests: Stop using kubectl in seal_test.go --- e2e/seal_test.go | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/e2e/seal_test.go b/e2e/seal_test.go index 14219dd66..808735fa8 100644 --- a/e2e/seal_test.go +++ b/e2e/seal_test.go @@ -1,6 +1,7 @@ package e2e import ( + "context" "crypto/x509" "encoding/pem" "fmt" @@ -13,6 +14,9 @@ import ( "github.com/kluctl/kluctl/v2/pkg/yaml" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" certUtil "k8s.io/client-go/util/cert" "k8s.io/client-go/util/keyutil" "net" @@ -158,7 +162,12 @@ func (s *SealedSecretsTestSuite) addSecretsSetToTarget(p *testProject, targetNam } func (s *SealedSecretsTestSuite) assertSealedSecret(k *test_utils.EnvTestCluster, namespace string, secretName string, expectedCertHash string, expectedSecrets map[string]string) { - y := k.KubectlYamlMust(s.T(), "-n", namespace, "get", "sealedsecret", secretName) + + y := k.MustGet(s.T(), schema.GroupVersionResource{ + Group: "bitnami.com", + Version: "v1alpha1", + Resource: "sealedsecrets", + }, namespace, secretName) h1 := y.GetK8sAnnotation("kluctl.io/sealedsecret-cert-hash") if h1 == nil { @@ -219,7 +228,8 @@ func (s *SealedSecretsTestSuite) TestSeal_WithBootstrap() { namespace := "seal-with-bootstrap" // deleting the crd causes kluctl to not recognize the operator, so it will do a bootstrap - k.KubectlMust(s.T(), "delete", "crd", "sealedsecrets.bitnami.com") + _ = k.DynamicClient.Resource(apiextensionsv1.SchemeGroupVersion.WithResource("customresourcedefinitions")). + Delete(context.Background(), "sealedsecrets.bitnami.com", metav1.DeleteOptions{}) p := s.prepareSealTest(k, namespace, map[string]string{ @@ -246,7 +256,7 @@ func (s *SealedSecretsTestSuite) TestSeal_WithBootstrap() { p.KluctlMust("deploy", "--yes", "-t", "test-target") - pkCm := k.KubectlYamlMust(s.T(), "-n", "kube-system", "get", "cm", "sealed-secrets-key-kluctl-bootstrap") + pkCm := k.MustGetCoreV1(s.T(), "configmaps", "kube-system", "sealed-secrets-key-kluctl-bootstrap") certBytes, ok, _ := pkCm.GetNestedString("data", "tls.crt") s.Assertions.True(ok) From ba963705c677c3ad0cfd7c48a9477041a098dd8d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 25 Oct 2022 11:39:06 +0200 Subject: [PATCH 1125/2916] tests: Paralellize ApplyYaml --- e2e/flux_test.go | 14 ++++- e2e/seal_test.go | 14 ++++- e2e/test_resources/resources.go | 100 +++++++++++++++++++++++--------- 3 files changed, 95 insertions(+), 33 deletions(-) diff --git a/e2e/flux_test.go b/e2e/flux_test.go index 269562405..33f30dbde 100644 --- a/e2e/flux_test.go +++ b/e2e/flux_test.go @@ -6,6 +6,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils/uo" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" + "sync" "testing" "github.com/kluctl/kluctl/v2/e2e/test_resources" @@ -40,8 +41,17 @@ func TestFluxCommands(t *testing.T) { defer p.cleanup() - test_resources.ApplyYaml("flux-source-crd.yaml", k) - test_resources.ApplyYaml("kluctl-crds.yaml", k) + var wg sync.WaitGroup + wg.Add(2) + go func() { + test_resources.ApplyYaml("flux-source-crd.yaml", k) + wg.Done() + }() + go func() { + test_resources.ApplyYaml("kluctl-crds.yaml", k) + wg.Done() + }() + wg.Wait() test_resources.ApplyYaml("kluctl-deployment.yaml", k) // assert that it was created diff --git a/e2e/seal_test.go b/e2e/seal_test.go index 808735fa8..36cc27099 100644 --- a/e2e/seal_test.go +++ b/e2e/seal_test.go @@ -23,6 +23,7 @@ import ( "net/http" "net/http/httptest" "path/filepath" + "sync" "testing" "time" ) @@ -52,8 +53,17 @@ func (s *SealedSecretsTestSuite) SetupSuite() { s.k = defaultCluster1 s.k2 = defaultCluster2 - test_resources.ApplyYaml("sealed-secrets.yaml", s.k) - test_resources.ApplyYaml("sealed-secrets.yaml", s.k2) + var wg sync.WaitGroup + wg.Add(2) + go func() { + test_resources.ApplyYaml("sealed-secrets.yaml", s.k) + wg.Done() + }() + go func() { + test_resources.ApplyYaml("sealed-secrets.yaml", s.k2) + wg.Done() + }() + wg.Wait() var err error s.certServer1, err = s.startCertServer() diff --git a/e2e/test_resources/resources.go b/e2e/test_resources/resources.go index 2e53339ec..f374d2ee0 100644 --- a/e2e/test_resources/resources.go +++ b/e2e/test_resources/resources.go @@ -3,6 +3,8 @@ package test_resources import ( "context" "embed" + test_utils "github.com/kluctl/kluctl/v2/internal/test-utils" + "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/validation" "github.com/kluctl/kluctl/v2/pkg/yaml" @@ -10,11 +12,10 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" "os" + "sort" "strings" + "sync" "time" - - test_utils "github.com/kluctl/kluctl/v2/internal/test-utils" - "github.com/kluctl/kluctl/v2/pkg/utils" ) //go:embed *.yaml @@ -49,6 +50,32 @@ func guessGVR(gvk schema.GroupVersionKind) schema.GroupVersionResource { return gvr } +func prio(x *uo.UnstructuredObject) int { + switch x.GetK8sGVK().Kind { + case "Namespace": + return 100 + case "CustomResourceDefinition": + return 100 + default: + return 0 + } +} + +func waitReadiness(k *test_utils.EnvTestCluster, x *uo.UnstructuredObject) { + for true { + u, err := k.DynamicClient.Resource(guessGVR(x.GetK8sGVK())).Get(context.Background(), x.GetK8sName(), metav1.GetOptions{}) + if err != nil { + panic(err) + } + vr := validation.ValidateObject(nil, uo.FromUnstructured(u), true, true) + if vr.Ready { + break + } else { + time.Sleep(time.Millisecond * 100) + } + } +} + func ApplyYaml(name string, k *test_utils.EnvTestCluster) { tmpFile := GetYamlTmpFile(name) defer os.Remove(tmpFile) @@ -58,42 +85,57 @@ func ApplyYaml(name string, k *test_utils.EnvTestCluster) { panic(err) } + var objects []*uo.UnstructuredObject for _, doc := range docs { m, ok := doc.(map[string]any) if !ok { panic("not a map!") } x := uo.FromMap(m) + objects = append(objects, x) + } - data, err := yaml.WriteYamlBytes(x) - if err != nil { - panic(err) - } + sort.SliceStable(objects, func(i, j int) bool { + return prio(objects[i]) > prio(objects[j]) + }) - gvr := guessGVR(x.GetK8sGVK()) - _, err = k.DynamicClient.Resource(gvr). - Namespace(x.GetK8sNamespace()). - Patch(context.Background(), x.GetK8sName(), types.ApplyPatchType, data, metav1.PatchOptions{ - FieldManager: "e2e-tests", - }) - if err != nil { - panic(err) + var wg sync.WaitGroup + prevPrio := prio(objects[0]) + for _, x := range objects { + x := x + + p := prio(x) + if p != prevPrio { + wg.Wait() } + prevPrio = p + + wg.Add(1) + go func() { + defer wg.Done() - // wait for CRDs to get accepted - if x.GetK8sGVK().Kind == "CustomResourceDefinition" { - for true { - u, err := k.DynamicClient.Resource(gvr).Get(context.Background(), x.GetK8sName(), metav1.GetOptions{}) - if err != nil { - panic(err) - } - vr := validation.ValidateObject(nil, uo.FromUnstructured(u), true, true) - if vr.Ready { - break - } else { - time.Sleep(time.Millisecond * 100) - } + data, err := yaml.WriteYamlBytes(x) + if err != nil { + panic(err) } - } + + gvr := guessGVR(x.GetK8sGVK()) + _, err = k.DynamicClient.Resource(gvr). + Namespace(x.GetK8sNamespace()). + Patch(context.Background(), x.GetK8sName(), types.ApplyPatchType, data, metav1.PatchOptions{ + FieldManager: "e2e-tests", + }) + if err != nil { + panic(err) + } + + // wait for CRDs to get accepted + if x.GetK8sGVK().Kind == "CustomResourceDefinition" { + waitReadiness(k, x) + // add some safety net...for some reason the envtest api server still fails if not waiting + time.Sleep(200 * time.Millisecond) + } + }() } + wg.Wait() } From e86e1bae403a9ea17b3ada0b0dc52dd63eff6d7e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 25 Oct 2022 12:06:04 +0200 Subject: [PATCH 1126/2916] tests: Disable rate limitting --- internal/test-utils/envtest_cluster.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/test-utils/envtest_cluster.go b/internal/test-utils/envtest_cluster.go index fbf531283..dd03a5c59 100644 --- a/internal/test-utils/envtest_cluster.go +++ b/internal/test-utils/envtest_cluster.go @@ -11,6 +11,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/dynamic" "k8s.io/client-go/rest" + "k8s.io/client-go/util/flowcontrol" "net/http" "os" "os/exec" @@ -61,6 +62,7 @@ func (k *EnvTestCluster) Start() error { k.user = user k.config = user.Config() + k.config.RateLimiter = flowcontrol.NewFakeAlwaysRateLimiter() kcfg, err := user.KubeConfig() if err != nil { From f55474521d423b220ebf0f341a8937c6b6048911 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 25 Oct 2022 12:06:39 +0200 Subject: [PATCH 1127/2916] tests: Remove Kubectl help funcs --- internal/test-utils/envtest_cluster.go | 64 -------------------------- 1 file changed, 64 deletions(-) diff --git a/internal/test-utils/envtest_cluster.go b/internal/test-utils/envtest_cluster.go index dd03a5c59..f113e87ff 100644 --- a/internal/test-utils/envtest_cluster.go +++ b/internal/test-utils/envtest_cluster.go @@ -5,16 +5,12 @@ import ( "context" "fmt" "github.com/kluctl/kluctl/v2/pkg/utils/uo" - "github.com/kluctl/kluctl/v2/pkg/yaml" - "io" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/dynamic" "k8s.io/client-go/rest" "k8s.io/client-go/util/flowcontrol" "net/http" - "os" - "os/exec" "sigs.k8s.io/controller-runtime/pkg/envtest" "sigs.k8s.io/controller-runtime/pkg/webhook" "testing" @@ -154,63 +150,3 @@ func (c *EnvTestCluster) List(gvr schema.GroupVersionResource, namespace string, } return ret, nil } - -func (c *EnvTestCluster) Kubectl(args ...string) (string, string, error) { - tmp, err := os.CreateTemp("", "") - if err != nil { - return "", "", err - } - defer func() { - tmp.Close() - os.Remove(tmp.Name()) - }() - - _, err = tmp.Write(c.Kubeconfig) - if err != nil { - return "", "", err - } - - stdoutBuffer := &bytes.Buffer{} - stderrBuffer := &bytes.Buffer{} - allArgs := append([]string{fmt.Sprintf("--kubeconfig=%s", tmp.Name())}, args...) - - cmd := exec.Command(c.env.ControlPlane.KubectlPath, allArgs...) - cmd.Stdout = stdoutBuffer - cmd.Stderr = stderrBuffer - - err = cmd.Run() - stdout, _ := io.ReadAll(stdoutBuffer) - stderr, _ := io.ReadAll(stderrBuffer) - return string(stdout), string(stderr), err -} - -func (c *EnvTestCluster) KubectlMust(t *testing.T, args ...string) string { - stdout, stderr, err := c.Kubectl(args...) - if err != nil { - if t != nil { - t.Fatalf("%v, stderr=%s\n", err, stderr) - } else { - panic(fmt.Sprintf("%v, stderr=%s\n", err, stderr)) - } - } - return stdout -} - -func (c *EnvTestCluster) KubectlYaml(args ...string) (*uo.UnstructuredObject, string, error) { - args = append(args, "-oyaml") - stdout, stderr, err := c.Kubectl(args...) - if err != nil { - return nil, stderr, err - } - ret := uo.New() - err = yaml.ReadYamlString(stdout, ret) - return ret, stderr, err -} - -func (c *EnvTestCluster) KubectlYamlMust(t *testing.T, args ...string) *uo.UnstructuredObject { - o, stderr, err := c.KubectlYaml(args...) - if err != nil { - t.Fatalf("%v, stderr=%s\n", err, stderr) - } - return o -} From c659f7846174b5121dd174471224e7ce5e296c99 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 25 Oct 2022 13:49:42 +0200 Subject: [PATCH 1128/2916] tests: Make hook tests parallel again --- e2e/default_clusters.go | 4 + e2e/hooks_test.go | 209 +++++++++--------- internal/test-utils/envtest_cluster.go | 4 + .../test-utils/envtest_cluster_callback.go | 44 +++- 4 files changed, 153 insertions(+), 108 deletions(-) diff --git a/e2e/default_clusters.go b/e2e/default_clusters.go index ed28ccbbb..ca75f2003 100644 --- a/e2e/default_clusters.go +++ b/e2e/default_clusters.go @@ -2,6 +2,7 @@ package e2e import ( test_utils "github.com/kluctl/kluctl/v2/internal/test-utils" + "k8s.io/apimachinery/pkg/runtime/schema" "sync" ) @@ -24,6 +25,9 @@ func init() { }() go func() { defer wg.Done() + defaultCluster2.InitWebhookCallback(schema.GroupVersionResource{ + Version: "v1", Resource: "configmaps", + }, true) err := defaultCluster2.Start() if err != nil { panic(err) diff --git a/e2e/hooks_test.go b/e2e/hooks_test.go index 1d16eb736..29a54f239 100644 --- a/e2e/hooks_test.go +++ b/e2e/hooks_test.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/suite" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" @@ -15,65 +14,56 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils/uo" ) -type HookTestSuite struct { - suite.Suite - +type hooksTestContext struct { + t *testing.T k *test_utils.EnvTestCluster + p *testProject + seenConfigMaps []string -} -func TestHooks(t *testing.T) { - t.Parallel() - suite.Run(t, &HookTestSuite{}) + whh *test_utils.CallbackHandlerEntry } -func (s *HookTestSuite) SetupSuite() { - s.clearSeenConfigmaps() - - s.k = test_utils.CreateEnvTestCluster("cluster1") - s.k.AddWebhookCallback(schema.GroupVersionResource{ +func (s *hooksTestContext) setupWebhook() { + s.whh = s.k.AddWebhookHandler(schema.GroupVersionResource{ Version: "v1", Resource: "configmaps", - }, true, s.handleConfigmap) - err := s.k.Start() - if err != nil { - s.T().Fatal(err) - } + }, s.handleConfigmap) } -func (s *HookTestSuite) TearDownSuite() { - s.k.Stop() +func (s *hooksTestContext) removeWebhook() { + s.k.RemoveWebhookHandler(s.whh) } -func (s *HookTestSuite) SetupTest() { - s.clearSeenConfigmaps() -} +func (s *hooksTestContext) handleConfigmap(request admission.Request) { + if s.p.projectName != request.Namespace { + return + } -func (s *HookTestSuite) handleConfigmap(request admission.Request) { x, err := uo.FromString(string(request.Object.Raw)) if err != nil { - s.T().Fatal(err) + s.t.Fatal(err) } generation, _, err := x.GetNestedInt("metadata", "generation") if err != nil { - s.T().Fatal(err) + s.t.Fatal(err) } uid, _, err := x.GetNestedString("metadata", "uid") if err != nil { - s.T().Fatal(err) + s.t.Fatal(err) } - s.T().Logf("handleConfigmap: op=%s, name=%s/%s, generation=%d, uid=%s", request.Operation, request.Namespace, request.Name, generation, uid) + s.t.Logf("handleConfigmap: op=%s, name=%s/%s, generation=%d, uid=%s", request.Operation, request.Namespace, request.Name, generation, uid) s.seenConfigMaps = append(s.seenConfigMaps, request.Name) } -func (s *HookTestSuite) clearSeenConfigmaps() { - s.T().Logf("clearSeenConfigmaps: %v", s.seenConfigMaps) +func (s *hooksTestContext) clearSeenConfigmaps() { + s.t.Logf("clearSeenConfigmaps: %v", s.seenConfigMaps) s.seenConfigMaps = nil } -func (s *HookTestSuite) addHookConfigMap(p *testProject, dir string, opts resourceOpts, isHelm bool, hook string, hookDeletionPolicy string) { +func (s *hooksTestContext) addHookConfigMap(dir string, opts resourceOpts, isHelm bool, hook string, hookDeletionPolicy string) { annotations := make(map[string]string) if isHelm { annotations["helm.sh/hook"] = hook @@ -89,130 +79,137 @@ func (s *HookTestSuite) addHookConfigMap(p *testProject, dir string, opts resour opts.annotations = uo.CopyMergeStrMap(opts.annotations, annotations) - s.addConfigMap(p, dir, opts) + s.addConfigMap(dir, opts) } -func (s *HookTestSuite) addConfigMap(p *testProject, dir string, opts resourceOpts) { +func (s *hooksTestContext) addConfigMap(dir string, opts resourceOpts) { o := uo.New() o.SetK8sGVKs("", "v1", "ConfigMap") mergeMetadata(o, opts) o.SetNestedField(map[string]interface{}{}, "data") - p.addKustomizeResources(dir, []kustomizeResource{ + s.p.addKustomizeResources(dir, []kustomizeResource{ {fmt.Sprintf("%s.yml", opts.name), "", o}, }) } -func (s *HookTestSuite) prepareHookTestProject(name string, hook string, hookDeletionPolicy string) *testProject { - isDone := false +func prepareHookTestProject(t *testing.T, name string, hook string, hookDeletionPolicy string) *hooksTestContext { + s := &hooksTestContext{ + t: t, + k: defaultCluster2, // use cluster2 as it has webhooks setup + p: &testProject{}, + } + s.setupWebhook() + namespace := fmt.Sprintf("hook-%s", name) - p := &testProject{} - p.init(s.T(), s.k, namespace) - defer func() { - if !isDone { - p.cleanup() - } - }() - createNamespace(s.T(), s.k, namespace) + s.p.init(t, s.k, namespace) + t.Cleanup(func() { + s.removeWebhook() + s.p.cleanup() + }) + + createNamespace(s.t, s.k, namespace) - p.updateTarget("test", nil) + s.p.updateTarget("test", nil) - p.addKustomizeDeployment("hook", nil, nil) + s.p.addKustomizeDeployment("hook", nil, nil) - s.addConfigMap(p, "hook", resourceOpts{name: "cm1", namespace: namespace}) - s.addHookConfigMap(p, "hook", resourceOpts{name: "hook1", namespace: namespace}, false, hook, hookDeletionPolicy) + s.addConfigMap("hook", resourceOpts{name: "cm1", namespace: namespace}) + s.addHookConfigMap("hook", resourceOpts{name: "hook1", namespace: namespace}, false, hook, hookDeletionPolicy) - isDone = true - return p + return s } -func (s *HookTestSuite) ensureHookExecuted(p *testProject, expectedCms ...string) { +func (s *hooksTestContext) ensureHookExecuted(expectedCms ...string) { s.clearSeenConfigmaps() - p.KluctlMust("deploy", "--yes", "-t", "test") - assert.Equal(s.T(), expectedCms, s.seenConfigMaps) + s.p.KluctlMust("deploy", "--yes", "-t", "test") + assert.Equal(s.t, expectedCms, s.seenConfigMaps) } -func (s *HookTestSuite) ensureHookNotExecuted(p *testProject) { +func (s *hooksTestContext) ensureHookNotExecuted() { _ = s.k.DynamicClient.Resource(corev1.SchemeGroupVersion.WithResource("configmaps")). - Namespace(p.projectName). + Namespace(s.p.projectName). Delete(context.Background(), "cm1", metav1.DeleteOptions{}) - p.KluctlMust("deploy", "--yes", "-t", "test") - assertConfigMapNotExists(s.T(), s.k, p.projectName, "cm1") + s.p.KluctlMust("deploy", "--yes", "-t", "test") + assertConfigMapNotExists(s.t, s.k, s.p.projectName, "cm1") } -func (s *HookTestSuite) TestHooksPreDeployInitial() { - p := s.prepareHookTestProject("pre-deploy-initial", "pre-deploy-initial", "") - defer p.cleanup() - s.ensureHookExecuted(p, "hook1", "cm1") - s.ensureHookExecuted(p, "cm1") +func TestHooksPreDeployInitial(t *testing.T) { + t.Parallel() + s := prepareHookTestProject(t, "pre-deploy-initial", "pre-deploy-initial", "") + s.ensureHookExecuted("hook1", "cm1") + s.ensureHookExecuted("cm1") } -func (s *HookTestSuite) TestHooksPostDeployInitial() { - p := s.prepareHookTestProject("post-deploy-initial", "post-deploy-initial", "") - defer p.cleanup() - s.ensureHookExecuted(p, "cm1", "hook1") - s.ensureHookExecuted(p, "cm1") +func TestHooksPostDeployInitial(t *testing.T) { + t.Parallel() + s := prepareHookTestProject(t, "post-deploy-initial", "post-deploy-initial", "") + s.ensureHookExecuted("cm1", "hook1") + s.ensureHookExecuted("cm1") } -func (s *HookTestSuite) TestHooksPreDeployUpgrade() { - p := s.prepareHookTestProject("pre-deploy-upgrade", "pre-deploy-upgrade", "") - defer p.cleanup() - s.ensureHookExecuted(p, "cm1") - s.ensureHookExecuted(p, "hook1", "cm1") - s.ensureHookExecuted(p, "hook1", "cm1") +func TestHooksPreDeployUpgrade(t *testing.T) { + t.Parallel() + s := prepareHookTestProject(t, "pre-deploy-upgrade", "pre-deploy-upgrade", "") + s.ensureHookExecuted("cm1") + s.ensureHookExecuted("hook1", "cm1") + s.ensureHookExecuted("hook1", "cm1") } -func (s *HookTestSuite) TestHooksPostDeployUpgrade() { - p := s.prepareHookTestProject("post-deploy-upgrade", "post-deploy-upgrade", "") - defer p.cleanup() - s.ensureHookExecuted(p, "cm1") - s.ensureHookExecuted(p, "cm1", "hook1") - s.ensureHookExecuted(p, "cm1", "hook1") +func TestHooksPostDeployUpgrade(t *testing.T) { + t.Parallel() + s := prepareHookTestProject(t, "post-deploy-upgrade", "post-deploy-upgrade", "") + s.ensureHookExecuted("cm1") + s.ensureHookExecuted("cm1", "hook1") + s.ensureHookExecuted("cm1", "hook1") } -func (s *HookTestSuite) doTestHooksPreDeploy(name string, hooks string) { - p := s.prepareHookTestProject(name, hooks, "") - defer p.cleanup() - s.ensureHookExecuted(p, "hook1", "cm1") - s.ensureHookExecuted(p, "hook1", "cm1") +func doTestHooksPreDeploy(t *testing.T, name string, hooks string) { + s := prepareHookTestProject(t, name, hooks, "") + s.ensureHookExecuted("hook1", "cm1") + s.ensureHookExecuted("hook1", "cm1") } -func (s *HookTestSuite) doTestHooksPostDeploy(name string, hooks string) { - p := s.prepareHookTestProject(name, hooks, "") - defer p.cleanup() - s.ensureHookExecuted(p, "cm1", "hook1") - s.ensureHookExecuted(p, "cm1", "hook1") +func doTestHooksPostDeploy(t *testing.T, name string, hooks string) { + s := prepareHookTestProject(t, name, hooks, "") + s.ensureHookExecuted("cm1", "hook1") + s.ensureHookExecuted("cm1", "hook1") } -func (s *HookTestSuite) doTestHooksPrePostDeploy(name string, hooks string) { - p := s.prepareHookTestProject(name, hooks, "") - defer p.cleanup() - s.ensureHookExecuted(p, "hook1", "cm1", "hook1") - s.ensureHookExecuted(p, "hook1", "cm1", "hook1") +func doTestHooksPrePostDeploy(t *testing.T, name string, hooks string) { + s := prepareHookTestProject(t, name, hooks, "") + s.ensureHookExecuted("hook1", "cm1", "hook1") + s.ensureHookExecuted("hook1", "cm1", "hook1") } -func (s *HookTestSuite) TestHooksPreDeploy() { - s.doTestHooksPreDeploy("pre-deploy", "pre-deploy") +func TestHooksPreDeploy(t *testing.T) { + t.Parallel() + doTestHooksPreDeploy(t, "pre-deploy", "pre-deploy") } -func (s *HookTestSuite) TestHooksPreDeploy2() { +func TestHooksPreDeploy2(t *testing.T) { + t.Parallel() // same as pre-deploy - s.doTestHooksPreDeploy("pre-deploy2", "pre-deploy-initial,pre-deploy-upgrade") + doTestHooksPreDeploy(t, "pre-deploy2", "pre-deploy-initial,pre-deploy-upgrade") } -func (s *HookTestSuite) TestHooksPostDeploy() { - s.doTestHooksPostDeploy("post-deploy", "post-deploy") +func TestHooksPostDeploy(t *testing.T) { + t.Parallel() + doTestHooksPostDeploy(t, "post-deploy", "post-deploy") } -func (s *HookTestSuite) TestHooksPostDeploy2() { +func TestHooksPostDeploy2(t *testing.T) { + t.Parallel() // same as post-deploy - s.doTestHooksPostDeploy("post-deploy2", "post-deploy-initial,post-deploy-upgrade") + doTestHooksPostDeploy(t, "post-deploy2", "post-deploy-initial,post-deploy-upgrade") } -func (s *HookTestSuite) TestHooksPrePostDeploy() { - s.doTestHooksPrePostDeploy("pre-post-deploy", "pre-deploy,post-deploy") +func TestHooksPrePostDeploy(t *testing.T) { + t.Parallel() + doTestHooksPrePostDeploy(t, "pre-post-deploy", "pre-deploy,post-deploy") } -func (s *HookTestSuite) TestHooksPrePostDeploy2() { - s.doTestHooksPrePostDeploy("pre-post-deploy2", "pre-deploy-initial,pre-deploy-upgrade,post-deploy-initial,post-deploy-upgrade") +func TestHooksPrePostDeploy2(t *testing.T) { + t.Parallel() + doTestHooksPrePostDeploy(t, "pre-post-deploy2", "pre-deploy-initial,pre-deploy-upgrade,post-deploy-initial,post-deploy-upgrade") } diff --git a/internal/test-utils/envtest_cluster.go b/internal/test-utils/envtest_cluster.go index f113e87ff..c32b82665 100644 --- a/internal/test-utils/envtest_cluster.go +++ b/internal/test-utils/envtest_cluster.go @@ -13,6 +13,7 @@ import ( "net/http" "sigs.k8s.io/controller-runtime/pkg/envtest" "sigs.k8s.io/controller-runtime/pkg/webhook" + "sync" "testing" ) @@ -30,6 +31,9 @@ type EnvTestCluster struct { callbackServer webhook.Server callbackServerStop context.CancelFunc + + webhookHandlers []*CallbackHandlerEntry + webhookHandlersMutex sync.Mutex } func CreateEnvTestCluster(context string) *EnvTestCluster { diff --git a/internal/test-utils/envtest_cluster_callback.go b/internal/test-utils/envtest_cluster_callback.go index 31a6dc8f4..54b95fc02 100644 --- a/internal/test-utils/envtest_cluster_callback.go +++ b/internal/test-utils/envtest_cluster_callback.go @@ -14,6 +14,10 @@ import ( ) type CallbackHandler func(request admission.Request) +type CallbackHandlerEntry struct { + GVR schema.GroupVersionResource + Callback CallbackHandler +} func (k *EnvTestCluster) buildServeCallback(gvr schema.GroupVersionResource, cb CallbackHandler) http.Handler { wh := &webhook.Admission{ @@ -49,7 +53,7 @@ func (k *EnvTestCluster) startCallbackServer() error { return nil } -func (k *EnvTestCluster) AddWebhookCallback(gvr schema.GroupVersionResource, isNamespaced bool, cb CallbackHandler) { +func (k *EnvTestCluster) InitWebhookCallback(gvr schema.GroupVersionResource, isNamespaced bool) { scope := admissionv1.ClusterScope if isNamespaced { scope = admissionv1.NamespacedScope @@ -103,5 +107,41 @@ func (k *EnvTestCluster) AddWebhookCallback(gvr schema.GroupVersionResource, isN }, }) - k.callbackServer.Register(path, k.buildServeCallback(gvr, cb)) + k.callbackServer.Register(path, k.buildServeCallback(gvr, k.handleWebhook)) +} + +func (k *EnvTestCluster) handleWebhook(request admission.Request) { + k.webhookHandlersMutex.Lock() + defer k.webhookHandlersMutex.Unlock() + + for _, e := range k.webhookHandlers { + if e.GVR.Group == request.Resource.Group && e.GVR.Version == request.Resource.Version && e.GVR.Resource == request.Resource.Resource { + e.Callback(request) + } + } +} + +func (k *EnvTestCluster) AddWebhookHandler(gvr schema.GroupVersionResource, cb CallbackHandler) *CallbackHandlerEntry { + k.webhookHandlersMutex.Lock() + defer k.webhookHandlersMutex.Unlock() + + entry := &CallbackHandlerEntry{ + GVR: gvr, + Callback: cb, + } + k.webhookHandlers = append(k.webhookHandlers, entry) + + return entry +} + +func (k *EnvTestCluster) RemoveWebhookHandler(e *CallbackHandlerEntry) { + k.webhookHandlersMutex.Lock() + defer k.webhookHandlersMutex.Unlock() + old := k.webhookHandlers + k.webhookHandlers = make([]*CallbackHandlerEntry, 0, len(k.webhookHandlers)) + for _, e2 := range old { + if e != e2 { + k.webhookHandlers = append(k.webhookHandlers, e2) + } + } } From 984cd77f3667ef15c7fbd29c45f19a56c13e6845 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 25 Oct 2022 14:05:32 +0200 Subject: [PATCH 1129/2916] tests: Paralellize sealed secrets tests --- e2e/default_clusters.go | 3 + e2e/seal_test.go | 220 +++++++++++++++++----------------------- 2 files changed, 94 insertions(+), 129 deletions(-) diff --git a/e2e/default_clusters.go b/e2e/default_clusters.go index ca75f2003..3ef14c5c8 100644 --- a/e2e/default_clusters.go +++ b/e2e/default_clusters.go @@ -1,6 +1,7 @@ package e2e import ( + "github.com/kluctl/kluctl/v2/e2e/test_resources" test_utils "github.com/kluctl/kluctl/v2/internal/test-utils" "k8s.io/apimachinery/pkg/runtime/schema" "sync" @@ -22,6 +23,7 @@ func init() { if err != nil { panic(err) } + test_resources.ApplyYaml("sealed-secrets.yaml", defaultCluster1) }() go func() { defer wg.Done() @@ -32,6 +34,7 @@ func init() { if err != nil { panic(err) } + test_resources.ApplyYaml("sealed-secrets.yaml", defaultCluster2) }() wg.Wait() } diff --git a/e2e/seal_test.go b/e2e/seal_test.go index 36cc27099..8f1b42dfe 100644 --- a/e2e/seal_test.go +++ b/e2e/seal_test.go @@ -13,7 +13,6 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/suite" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" @@ -23,74 +22,32 @@ import ( "net/http" "net/http/httptest" "path/filepath" - "sync" "testing" "time" ) -type SealedSecretsTestSuite struct { - suite.Suite - - k *test_utils.EnvTestCluster - k2 *test_utils.EnvTestCluster - - certServer1 *certServer - certServer2 *certServer -} - type certServer struct { server http.Server url string certHash string } -func TestSealedSecrets(t *testing.T) { - t.Parallel() - suite.Run(t, &SealedSecretsTestSuite{}) -} - -func (s *SealedSecretsTestSuite) SetupSuite() { - s.k = defaultCluster1 - s.k2 = defaultCluster2 - - var wg sync.WaitGroup - wg.Add(2) - go func() { - test_resources.ApplyYaml("sealed-secrets.yaml", s.k) - wg.Done() - }() - go func() { - test_resources.ApplyYaml("sealed-secrets.yaml", s.k2) - wg.Done() - }() - wg.Wait() +var certServer1 *certServer +var certServer2 *certServer +func init() { var err error - s.certServer1, err = s.startCertServer() + certServer1, err = startCertServer() if err != nil { - s.T().Fatal(err) + panic(err) } - s.certServer2, err = s.startCertServer() + certServer2, err = startCertServer() if err != nil { - s.T().Fatal(err) - } -} - -func (s *SealedSecretsTestSuite) TearDownSuite() { - s.k = nil - s.k2 = nil - - if s.certServer1 != nil { - s.certServer1.server.Close() - } - if s.certServer2 != nil { - s.certServer2.server.Close() + panic(err) } - s.certServer1 = nil - s.certServer2 = nil } -func (s *SealedSecretsTestSuite) startCertServer() (*certServer, error) { +func startCertServer() (*certServer, error) { key, cert, err := crypto.GeneratePrivateKeyAndCert(2048, 10*365*24*time.Hour, "tests.kluctl.io") if err != nil { return nil, err @@ -128,7 +85,7 @@ func (s *SealedSecretsTestSuite) startCertServer() (*certServer, error) { return &cs, nil } -func (s *SealedSecretsTestSuite) addProxyVars(p *testProject) { +func addProxyVars(p *testProject) { f := func(idx int, k *test_utils.EnvTestCluster, cs *certServer) { p.extraEnv = append(p.extraEnv, fmt.Sprintf("KLUCTL_K8S_SERVICE_PROXY_%d_API_HOST=%s", idx, k.RESTConfig().Host)) p.extraEnv = append(p.extraEnv, fmt.Sprintf("KLUCTL_K8S_SERVICE_PROXY_%d_SERVICE_NAMESPACE=%s", idx, "kube-system")) @@ -136,34 +93,37 @@ func (s *SealedSecretsTestSuite) addProxyVars(p *testProject) { p.extraEnv = append(p.extraEnv, fmt.Sprintf("KLUCTL_K8S_SERVICE_PROXY_%d_SERVICE_PORT=%s", idx, "http")) p.extraEnv = append(p.extraEnv, fmt.Sprintf("KLUCTL_K8S_SERVICE_PROXY_%d_LOCAL_URL=%s", idx, cs.url)) } - f(0, s.k, s.certServer1) - f(1, s.k2, s.certServer2) + f(0, defaultCluster1, certServer1) + f(1, defaultCluster2, certServer2) } -func (s *SealedSecretsTestSuite) prepareSealTest(k *test_utils.EnvTestCluster, namespace string, secrets map[string]string, varsSources []*uo.UnstructuredObject, proxy bool) *testProject { +func prepareSealTest(t *testing.T, k *test_utils.EnvTestCluster, namespace string, secrets map[string]string, varsSources []*uo.UnstructuredObject, proxy bool) *testProject { p := &testProject{} - p.init(s.T(), k, fmt.Sprintf("seal-%s", namespace)) + p.init(t, k, fmt.Sprintf("seal-%s", namespace)) + t.Cleanup(func() { + p.cleanup() + }) if proxy { - s.addProxyVars(p) + addProxyVars(p) } - createNamespace(s.T(), k, namespace) + createNamespace(t, k, namespace) - s.addSecretsSet(p, "test", varsSources) - s.addSecretsSetToTarget(p, "test-target", "test") + addSecretsSet(p, "test", varsSources) + addSecretsSetToTarget(p, "test-target", "test") addSecretDeployment(p, "secret-deployment", secrets, true, resourceOpts{name: "secret", namespace: namespace}) return p } -func (s *SealedSecretsTestSuite) addSecretsSet(p *testProject, name string, varsSources []*uo.UnstructuredObject) { +func addSecretsSet(p *testProject, name string, varsSources []*uo.UnstructuredObject) { p.updateSecretSet(name, func(secretSet *uo.UnstructuredObject) { _ = secretSet.SetNestedField(varsSources, "vars") }) } -func (s *SealedSecretsTestSuite) addSecretsSetToTarget(p *testProject, targetName string, secretSetName string) { +func addSecretsSetToTarget(p *testProject, targetName string, secretSetName string) { p.updateTarget(targetName, func(target *uo.UnstructuredObject) { l, _, _ := target.GetNestedList("sealingConfig", "secretSets") l = append(l, secretSetName) @@ -171,9 +131,8 @@ func (s *SealedSecretsTestSuite) addSecretsSetToTarget(p *testProject, targetNam }) } -func (s *SealedSecretsTestSuite) assertSealedSecret(k *test_utils.EnvTestCluster, namespace string, secretName string, expectedCertHash string, expectedSecrets map[string]string) { - - y := k.MustGet(s.T(), schema.GroupVersionResource{ +func assertSealedSecret(t *testing.T, k *test_utils.EnvTestCluster, namespace string, secretName string, expectedCertHash string, expectedSecrets map[string]string) { + y := k.MustGet(t, schema.GroupVersionResource{ Group: "bitnami.com", Version: "v1alpha1", Resource: "sealedsecrets", @@ -181,17 +140,17 @@ func (s *SealedSecretsTestSuite) assertSealedSecret(k *test_utils.EnvTestCluster h1 := y.GetK8sAnnotation("kluctl.io/sealedsecret-cert-hash") if h1 == nil { - s.T().Fatal("kluctl.io/sealedsecret-cert-hash annotation not found") + t.Fatal("kluctl.io/sealedsecret-cert-hash annotation not found") } - s.Assertions.Equal(expectedCertHash, *h1) + assert.Equal(t, expectedCertHash, *h1) hashesStr := y.GetK8sAnnotation("kluctl.io/sealedsecret-hashes") if hashesStr == nil { - s.T().Fatal("kluctl.io/sealedsecret-hashes annotation not found") + t.Fatal("kluctl.io/sealedsecret-hashes annotation not found") } hashes, err := uo.FromString(*hashesStr) if err != nil { - s.T().Fatal(err) + t.Fatal(err) } expectedHashes := map[string]any{} @@ -199,14 +158,15 @@ func (s *SealedSecretsTestSuite) assertSealedSecret(k *test_utils.EnvTestCluster expectedHashes[k] = seal.HashSecret(k, []byte(v), secretName, namespace, "strict") } - s.Assertions.Equal(expectedHashes, hashes.Object) + assert.Equal(t, expectedHashes, hashes.Object) } -func (s *SealedSecretsTestSuite) TestSeal_WithOperator() { - k := s.k +func TestSeal_WithOperator(t *testing.T) { + t.Parallel() + k := defaultCluster1 namespace := "seal-with-operator" - p := s.prepareSealTest(k, namespace, + p := prepareSealTest(t, k, namespace, map[string]string{ "s1": "{{ secrets.s1 }}", "s2": "{{ secrets.s2 }}", @@ -219,29 +179,30 @@ func (s *SealedSecretsTestSuite) TestSeal_WithOperator() { }, }), }, true) - defer p.cleanup() p.KluctlMust("seal", "-t", "test-target") sealedSecretsDir := p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()) - assert.FileExists(s.T(), filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) + assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) p.KluctlMust("deploy", "--yes", "-t", "test-target") - s.assertSealedSecret(k, namespace, "secret", s.certServer1.certHash, map[string]string{ + assertSealedSecret(t, k, namespace, "secret", certServer1.certHash, map[string]string{ "s1": "v1", "s2": "v2", }) } -func (s *SealedSecretsTestSuite) TestSeal_WithBootstrap() { - k := s.k +func TestSeal_WithBootstrap(t *testing.T) { + // this test must NOT run in parallel + + k := defaultCluster1 namespace := "seal-with-bootstrap" // deleting the crd causes kluctl to not recognize the operator, so it will do a bootstrap _ = k.DynamicClient.Resource(apiextensionsv1.SchemeGroupVersion.WithResource("customresourcedefinitions")). Delete(context.Background(), "sealedsecrets.bitnami.com", metav1.DeleteOptions{}) - p := s.prepareSealTest(k, namespace, + p := prepareSealTest(t, k, namespace, map[string]string{ "s1": "{{ secrets.s1 }}", "s2": "{{ secrets.s2 }}", @@ -255,38 +216,38 @@ func (s *SealedSecretsTestSuite) TestSeal_WithBootstrap() { }), }, false) - defer p.cleanup() - p.KluctlMust("seal", "-t", "test-target") sealedSecretsDir := p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()) - assert.FileExists(s.T(), filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) + assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) test_resources.ApplyYaml("sealed-secrets.yaml", k) p.KluctlMust("deploy", "--yes", "-t", "test-target") - pkCm := k.MustGetCoreV1(s.T(), "configmaps", "kube-system", "sealed-secrets-key-kluctl-bootstrap") + pkCm := k.MustGetCoreV1(t, "configmaps", "kube-system", "sealed-secrets-key-kluctl-bootstrap") certBytes, ok, _ := pkCm.GetNestedString("data", "tls.crt") - s.Assertions.True(ok) + assert.True(t, ok) cert, err := seal.ParseCert([]byte(certBytes)) - s.Assertions.NoError(err) + assert.NoError(t, err) certHash, err := seal.HashPublicKey(cert) - s.Assertions.NoError(err) + assert.NoError(t, err) - s.assertSealedSecret(k, namespace, "secret", certHash, map[string]string{ + assertSealedSecret(t, k, namespace, "secret", certHash, map[string]string{ "s1": "v1", "s2": "v2", }) } -func (s *SealedSecretsTestSuite) TestSeal_MultipleVarSources() { - k := s.k +func TestSeal_MultipleVarSources(t *testing.T) { + t.Parallel() + + k := defaultCluster1 namespace := "seal-multiple-vs" - p := s.prepareSealTest(k, namespace, + p := prepareSealTest(t, k, namespace, map[string]string{ "s1": "{{ secrets.s1 }}", "s2": "{{ secrets.s2 }}", @@ -303,26 +264,27 @@ func (s *SealedSecretsTestSuite) TestSeal_MultipleVarSources() { }, }), }, true) - defer p.cleanup() p.KluctlMust("seal", "-t", "test-target") sealedSecretsDir := p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()) - assert.FileExists(s.T(), filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) + assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) p.KluctlMust("deploy", "--yes", "-t", "test-target") - s.assertSealedSecret(k, namespace, "secret", s.certServer1.certHash, map[string]string{ + assertSealedSecret(t, k, namespace, "secret", certServer1.certHash, map[string]string{ "s1": "v1", "s2": "v2", }) } -func (s *SealedSecretsTestSuite) TestSeal_MultipleSecretSets() { - k := s.k +func TestSeal_MultipleSecretSets(t *testing.T) { + t.Parallel() + + k := defaultCluster1 namespace := "seal-multiple-ss" - p := s.prepareSealTest(k, namespace, + p := prepareSealTest(t, k, namespace, map[string]string{ "s1": "{{ secrets.s1 }}", "s2": "{{ secrets.s2 }}", @@ -335,35 +297,35 @@ func (s *SealedSecretsTestSuite) TestSeal_MultipleSecretSets() { }), }, true) - defer p.cleanup() - - s.addSecretsSet(p, "test2", []*uo.UnstructuredObject{ + addSecretsSet(p, "test2", []*uo.UnstructuredObject{ uo.FromMap(map[string]interface{}{ "values": map[string]interface{}{ "s2": "v2", }, }), }) - s.addSecretsSetToTarget(p, "test-target", "test2") + addSecretsSetToTarget(p, "test-target", "test2") p.KluctlMust("seal", "-t", "test-target") sealedSecretsDir := p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()) - assert.FileExists(s.T(), filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) + assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) p.KluctlMust("deploy", "--yes", "-t", "test-target") - s.assertSealedSecret(k, namespace, "secret", s.certServer1.certHash, map[string]string{ + assertSealedSecret(t, k, namespace, "secret", certServer1.certHash, map[string]string{ "s1": "v1", "s2": "v2", }) } -func (s *SealedSecretsTestSuite) TestSeal_MultipleTargets() { - k := s.k +func TestSeal_MultipleTargets(t *testing.T) { + t.Parallel() + + k := defaultCluster1 namespace := "seal-multiple-targets" - p := s.prepareSealTest(k, namespace, + p := prepareSealTest(t, k, namespace, map[string]string{ "s1": "{{ secrets.s1 }}", "s2": "{{ secrets.s2 }}", @@ -376,9 +338,8 @@ func (s *SealedSecretsTestSuite) TestSeal_MultipleTargets() { }, }), }, true) - defer p.cleanup() - s.addSecretsSet(p, "test2", []*uo.UnstructuredObject{ + addSecretsSet(p, "test2", []*uo.UnstructuredObject{ uo.FromMap(map[string]interface{}{ "values": map[string]interface{}{ "s1": "v3", @@ -386,42 +347,44 @@ func (s *SealedSecretsTestSuite) TestSeal_MultipleTargets() { }, }), }) - s.addSecretsSetToTarget(p, "test-target2", "test2") + addSecretsSetToTarget(p, "test-target2", "test2") - p.mergeKubeconfig(s.k2) - createNamespace(s.T(), s.k2, namespace) + p.mergeKubeconfig(defaultCluster2) + createNamespace(t, defaultCluster2, namespace) p.updateTarget("test-target", func(target *uo.UnstructuredObject) { - _ = target.SetNestedField(s.k.Context, "context") + _ = target.SetNestedField(defaultCluster1.Context, "context") }) p.updateTarget("test-target2", func(target *uo.UnstructuredObject) { - _ = target.SetNestedField(s.k2.Context, "context") + _ = target.SetNestedField(defaultCluster2.Context, "context") }) p.KluctlMust("seal", "-t", "test-target") p.KluctlMust("seal", "-t", "test-target2") sealedSecretsDir := p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()) - assert.FileExists(s.T(), filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) - assert.FileExists(s.T(), filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target2/secret-secret.yml")) + assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) + assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target2/secret-secret.yml")) p.KluctlMust("deploy", "--yes", "-t", "test-target") p.KluctlMust("deploy", "--yes", "-t", "test-target2") - s.assertSealedSecret(k, namespace, "secret", s.certServer1.certHash, map[string]string{ + assertSealedSecret(t, defaultCluster1, namespace, "secret", certServer1.certHash, map[string]string{ "s1": "v1", "s2": "v2", }) - s.assertSealedSecret(s.k2, namespace, "secret", s.certServer2.certHash, map[string]string{ + assertSealedSecret(t, defaultCluster2, namespace, "secret", certServer2.certHash, map[string]string{ "s1": "v3", "s2": "v4", }) } -func (s *SealedSecretsTestSuite) TestSeal_File() { - k := s.k +func TestSeal_File(t *testing.T) { + t.Parallel() + + k := defaultCluster1 namespace := "seal-file" - p := s.prepareSealTest(k, namespace, + p := prepareSealTest(t, k, namespace, map[string]string{ "s1": "{{ secrets.s1 }}", "s2": "{{ secrets.s2 }}", @@ -431,7 +394,6 @@ func (s *SealedSecretsTestSuite) TestSeal_File() { "file": utils.StrPtr("secret-values.yaml"), }), }, true) - defer p.cleanup() p.gitServer.UpdateYaml(p.getKluctlProjectRepo(), "secret-values.yaml", func(o *uo.UnstructuredObject) error { *o = *uo.FromMap(map[string]interface{}{ @@ -446,18 +408,20 @@ func (s *SealedSecretsTestSuite) TestSeal_File() { p.KluctlMust("seal", "-t", "test-target") sealedSecretsDir := p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()) - assert.FileExists(s.T(), filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) + assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) p.KluctlMust("deploy", "--yes", "-t", "test-target") - s.assertSealedSecret(k, namespace, "secret", s.certServer1.certHash, map[string]string{ + assertSealedSecret(t, k, namespace, "secret", certServer1.certHash, map[string]string{ "s1": "v1", "s2": "v2", }) } -func (s *SealedSecretsTestSuite) TestSeal_Vault() { - k := s.k +func TestSeal_Vault(t *testing.T) { + t.Parallel() + + k := defaultCluster1 namespace := "seal-vault" server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { @@ -479,11 +443,10 @@ func (s *SealedSecretsTestSuite) TestSeal_Vault() { writer.Header().Set("Content-Type", "application/json") _, _ = writer.Write([]byte(s)) })) - defer server.Close() vaultUrl := server.URL - p := s.prepareSealTest(k, namespace, + p := prepareSealTest(t, k, namespace, map[string]string{ "s1": "{{ secrets.s1 }}", "s2": "{{ secrets.s2 }}", @@ -496,17 +459,16 @@ func (s *SealedSecretsTestSuite) TestSeal_Vault() { }, }), }, true) - defer p.cleanup() p.extraEnv = append(p.extraEnv, "VAULT_TOKEN=root") p.KluctlMust("seal", "-t", "test-target") sealedSecretsDir := p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()) - assert.FileExists(s.T(), filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) + assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) p.KluctlMust("deploy", "--yes", "-t", "test-target") - s.assertSealedSecret(k, namespace, "secret", s.certServer1.certHash, map[string]string{ + assertSealedSecret(t, k, namespace, "secret", certServer1.certHash, map[string]string{ "s1": "v1", "s2": "v2", }) From 519ec7ebfa91b996db3721d878b55f90581af75c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 25 Oct 2022 14:09:10 +0200 Subject: [PATCH 1130/2916] chore: Run go mod tidy --- go.mod | 1 - go.sum | 2 -- 2 files changed, 3 deletions(-) diff --git a/go.mod b/go.mod index fbccf67c5..729920c5c 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,6 @@ require ( github.com/mattn/go-colorable v0.1.13 github.com/mattn/go-isatty v0.0.16 github.com/mattn/go-runewidth v0.0.14 - github.com/mitchellh/go-ps v1.0.0 github.com/mitchellh/reflectwalk v1.0.2 github.com/ohler55/ojg v1.14.5 github.com/pkg/errors v0.9.1 diff --git a/go.sum b/go.sum index 379bb254c..e00cb9714 100644 --- a/go.sum +++ b/go.sum @@ -575,8 +575,6 @@ github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HK github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= -github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= From ca84085fcf8a657343e446e8c1206cece120eda4 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 25 Oct 2022 14:26:37 +0200 Subject: [PATCH 1131/2916] fix: Fix check for helm-chart.yaml The check did previously not verify that a / was present before the filename. --- pkg/deployment/deployment_item.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 7f944e92f..873a478c1 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -160,13 +160,19 @@ func (di *DeploymentItem) render(forSeal bool) error { ) } +func (di *DeploymentItem) isHelmChartYaml(p string) bool { + _, file := filepath.Split(p) + file = strings.ToLower(file) + return file == "helm-chart.yml" || file == "helm-chart.yaml" +} + func (di *DeploymentItem) renderHelmCharts() error { if di.dir == nil { return nil } err := filepath.Walk(di.RenderedDir, func(p string, info fs.FileInfo, err error) error { - if !strings.HasSuffix(p, "helm-chart.yml") && !strings.HasSuffix(p, "helm-chart.yaml") { + if !di.isHelmChartYaml(p) { return nil } From 72b777b9084b3f1d96532601b84b55244be49c2b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 25 Oct 2022 15:39:22 +0200 Subject: [PATCH 1132/2916] feat: Allow to add deployment items without a kustomization.yaml kluctl will then generate a simple kustomization.yaml. --- docs/reference/deployments/deployment-yml.md | 16 +++- pkg/deployment/deployment_item.go | 86 ++++++++++++++++++-- pkg/deployment/deployment_project.go | 5 -- 3 files changed, 96 insertions(+), 11 deletions(-) diff --git a/docs/reference/deployments/deployment-yml.md b/docs/reference/deployments/deployment-yml.md index 1d5914919..e1aac4061 100644 --- a/docs/reference/deployments/deployment-yml.md +++ b/docs/reference/deployments/deployment-yml.md @@ -45,9 +45,23 @@ for details. Individual deployments are performed in parallel, unless a [barrier](#barriers) is encountered which causes kluctl to wait for all previous deployments to finish. +### Simple deployments + +Simple deployments are specified via `path` and are expected to be directories with Kubernetes manifests inside. +Kluctl will internally generate a kustomization.yaml from these manifests and treat the deployment item the same way +as it would treat a [Kustomize deployment](#kustomize-deployments). + +Example: +```yaml +deployments: +- path: path/to/manifests +``` + ### Kustomize deployments -Specifies a [kustomize](https://kustomize.io/) deployment. +When the deployment item directory specified via `path` contains a `kustomization.yaml`, Kluctl will use this file +instead of generating one. + Please see [Kustomize integration](./kustomize.md) for more details. Example: diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 873a478c1..0c53716e9 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -11,6 +11,7 @@ import ( "io/fs" "k8s.io/apimachinery/pkg/runtime/schema" "os" + "path" "path/filepath" "strings" ) @@ -166,6 +167,12 @@ func (di *DeploymentItem) isHelmChartYaml(p string) bool { return file == "helm-chart.yml" || file == "helm-chart.yaml" } +func (di *DeploymentItem) isHelmValuesYaml(p string) bool { + _, file := filepath.Split(p) + file = strings.ToLower(file) + return file == "helm-values.yml" || file == "helm-values.yaml" +} + func (di *DeploymentItem) renderHelmCharts() error { if di.dir == nil { return nil @@ -224,15 +231,23 @@ func (di *DeploymentItem) ListSealedSecrets(subdir string) ([]string, error) { return nil, err } - y, err := uo.FromFile(yaml.FixPathExt(filepath.Join(renderedDir, "kustomization.yml"))) + y, err := di.readKustomizationYaml(subdir) if err != nil { return nil, err } - l, _, err := y.GetNestedStringList("resources") + if y == nil { + y, err = di.generateKustomizationYaml(subdir) + if err != nil { + return nil, err + } + } + + resources, _, err := y.GetNestedStringList("resources") if err != nil { return nil, err } - for _, resource := range l { + + for _, resource := range resources { p := filepath.Clean(filepath.Join(renderedDir, resource)) isDir := utils.IsDirectory(p) @@ -376,6 +391,60 @@ func (di *DeploymentItem) readKustomizationYaml(subDir string) (*uo.Unstructured return ky, err } +func (di *DeploymentItem) generateKustomizationYaml(subDir string) (*uo.UnstructuredObject, error) { + kustomizeYamlPath := yaml.FixPathExt(filepath.Join(di.RenderedDir, subDir, "kustomization.yml")) + if utils.IsFile(kustomizeYamlPath) { + return nil, nil + } + + des, err := os.ReadDir(filepath.Join(di.RenderedDir, subDir)) + if err != nil { + return nil, err + } + + var list []any + m := map[string]bool{} + + for _, de := range des { + if de.IsDir() { + continue + } + + lname := strings.ToLower(de.Name()) + resourcePath := "" + + if di.isHelmValuesYaml(de.Name()) { + continue + } else if di.isHelmChartYaml(de.Name()) { + c, err := NewHelmChart(filepath.Join(di.RenderedDir, subDir, de.Name())) + if err != nil { + return nil, err + } + if !utils.IsFile(filepath.Join(di.RenderedDir, subDir, c.GetOutputPath())) { + resourcePath = c.GetOutputPath() + } + } else if strings.HasSuffix(lname, ".yml") || strings.HasSuffix(lname, ".yaml") { + resourcePath = de.Name() + } else if strings.HasSuffix(lname, ".yml"+SealmeExt) || strings.HasSuffix(lname, ".yaml"+SealmeExt) { + resourcePath = de.Name()[:len(de.Name())-len(SealmeExt)] + } + + if resourcePath != "" { + resourcePath = filepath.ToSlash(resourcePath) + resourcePath = path.Clean(resourcePath) + if _, ok := m[resourcePath]; !ok { + m[resourcePath] = true + list = append(list, resourcePath) + } + } + } + + generated := uo.New() + _ = generated.SetNestedField(list, "resources") + + return generated, nil +} + func (di *DeploymentItem) writeKustomizationYaml(ky *uo.UnstructuredObject) error { kustomizeYamlPath := yaml.FixPathExt(filepath.Join(di.RenderedDir, "kustomization.yml")) return yaml.WriteYamlFile(kustomizeYamlPath, ky) @@ -387,7 +456,14 @@ func (di *DeploymentItem) prepareKustomizationYaml() error { return err } if ky == nil { - return nil + ky, err = di.generateKustomizationYaml("") + if err != nil { + return err + } + err = di.writeKustomizationYaml(ky) + if err != nil { + return err + } } overrideNamespace := di.Project.getOverrideNamespace() @@ -404,7 +480,7 @@ func (di *DeploymentItem) prepareKustomizationYaml() error { di.Barrier = utils.ParseBoolOrFalse(ky.GetK8sAnnotation("kluctl.io/barrier")) di.WaitReadiness = utils.ParseBoolOrFalse(ky.GetK8sAnnotation("kluctl.io/wait-readiness")) - // Save modified kustomize.yml + // Save modified kustomization.yml return di.writeKustomizationYaml(ky) } diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go index ecd599c31..04a06dac7 100644 --- a/pkg/deployment/deployment_project.go +++ b/pkg/deployment/deployment_project.go @@ -136,11 +136,6 @@ func (p *DeploymentProject) checkDeploymentDirs() error { if !utils.IsDirectory(diDir) { return fmt.Errorf("deployment path is not a directory: %s", *di.Path) } - - pth := yaml.FixPathExt(filepath.Join(diDir, "kustomization.yml")) - if !utils.IsFile(pth) { - return fmt.Errorf("%s not found or not a file", pth) - } } return nil } From 3f86f9e0578d0fa22c6e4cc5c14c16b33253fc71 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 25 Oct 2022 15:50:22 +0200 Subject: [PATCH 1133/2916] tests: Cleanup testProject as part of testing cleanup --- e2e/args_test.go | 2 -- e2e/contexts_test.go | 6 ------ e2e/deploy_test.go | 1 - e2e/flux_test.go | 2 -- e2e/hooks_test.go | 1 - e2e/inclusion_test.go | 14 -------------- e2e/no_target_test.go | 1 - e2e/project.go | 14 +++----------- e2e/seal_test.go | 4 +--- 9 files changed, 4 insertions(+), 41 deletions(-) diff --git a/e2e/args_test.go b/e2e/args_test.go index 026f3bee0..527a75ce3 100644 --- a/e2e/args_test.go +++ b/e2e/args_test.go @@ -14,7 +14,6 @@ func TestArgs(t *testing.T) { p := &testProject{} p.init(t, k, "args") - defer p.cleanup() createNamespace(t, k, p.projectName) @@ -121,7 +120,6 @@ func TestArgsFromEnv(t *testing.T) { p := &testProject{} p.init(t, k, "args-from-envs") - defer p.cleanup() createNamespace(t, k, p.projectName) diff --git a/e2e/contexts_test.go b/e2e/contexts_test.go index 7de30a13f..29fa1d133 100644 --- a/e2e/contexts_test.go +++ b/e2e/contexts_test.go @@ -26,7 +26,6 @@ func TestContextCurrent(t *testing.T) { t.Parallel() p := prepareContextTest(t, "context-current") - defer p.cleanup() p.updateTarget("test1", func(target *uo.UnstructuredObject) { // no context set, assume the current one is used @@ -48,7 +47,6 @@ func TestContext1(t *testing.T) { t.Parallel() p := prepareContextTest(t, "context-1") - defer p.cleanup() p.updateTarget("test1", func(target *uo.UnstructuredObject) { _ = target.SetNestedField(defaultCluster1.Context, "context") @@ -63,7 +61,6 @@ func TestContext2(t *testing.T) { t.Parallel() p := prepareContextTest(t, "context-2") - defer p.cleanup() p.updateTarget("test1", func(target *uo.UnstructuredObject) { _ = target.SetNestedField(defaultCluster2.Context, "context") @@ -78,7 +75,6 @@ func TestContext1And2(t *testing.T) { t.Parallel() p := prepareContextTest(t, "context-1-and-2") - defer p.cleanup() p.updateTarget("test1", func(target *uo.UnstructuredObject) { _ = target.SetNestedField(defaultCluster1.Context, "context") @@ -99,7 +95,6 @@ func TestContextSwitch(t *testing.T) { t.Parallel() p := prepareContextTest(t, "context-switch") - defer p.cleanup() p.updateTarget("test1", func(target *uo.UnstructuredObject) { _ = target.SetNestedField(defaultCluster1.Context, "context") @@ -121,7 +116,6 @@ func TestContextOverride(t *testing.T) { t.Parallel() p := prepareContextTest(t, "context-override") - defer p.cleanup() p.updateTarget("test1", func(target *uo.UnstructuredObject) { _ = target.SetNestedField(defaultCluster1.Context, "context") diff --git a/e2e/deploy_test.go b/e2e/deploy_test.go index c6b0493da..6c3029ea8 100644 --- a/e2e/deploy_test.go +++ b/e2e/deploy_test.go @@ -11,7 +11,6 @@ func TestCommandDeploySimple(t *testing.T) { p := &testProject{} p.init(t, k, "simple") - defer p.cleanup() createNamespace(t, k, p.projectName) diff --git a/e2e/flux_test.go b/e2e/flux_test.go index 33f30dbde..b7b4bcb53 100644 --- a/e2e/flux_test.go +++ b/e2e/flux_test.go @@ -39,8 +39,6 @@ func TestFluxCommands(t *testing.T) { p := &testProject{} p.init(t, k, "flux-test") - defer p.cleanup() - var wg sync.WaitGroup wg.Add(2) go func() { diff --git a/e2e/hooks_test.go b/e2e/hooks_test.go index 29a54f239..af1c2221c 100644 --- a/e2e/hooks_test.go +++ b/e2e/hooks_test.go @@ -105,7 +105,6 @@ func prepareHookTestProject(t *testing.T, name string, hook string, hookDeletion s.p.init(t, s.k, namespace) t.Cleanup(func() { s.removeWebhook() - s.p.cleanup() }) createNamespace(s.t, s.k, namespace) diff --git a/e2e/inclusion_test.go b/e2e/inclusion_test.go index 99e1beaab..edba11b1a 100644 --- a/e2e/inclusion_test.go +++ b/e2e/inclusion_test.go @@ -9,16 +9,9 @@ import ( ) func prepareInclusionTestProject(t *testing.T, namespace string, withIncludes bool) (*testProject, *test_utils.EnvTestCluster) { - isDone := false - k := defaultCluster1 p := &testProject{} p.init(t, k, namespace) - defer func() { - if !isDone { - p.cleanup() - } - }() createNamespace(t, k, p.projectName) @@ -45,7 +38,6 @@ func prepareInclusionTestProject(t *testing.T, namespace string, withIncludes bo addConfigMapDeployment(p, "include3/icm5", nil, resourceOpts{name: "icm5", namespace: p.projectName, tags: []string{"itag5", "itag6"}}) } - isDone = true return p, k } @@ -74,7 +66,6 @@ func assertExistsHelper(t *testing.T, p *testProject, k *test_utils.EnvTestClust func TestInclusionTags(t *testing.T) { t.Parallel() p, k := prepareInclusionTestProject(t, "inclusion-tags", false) - defer p.cleanup() shouldExists := make(map[string]bool) doAssertExists := func(add ...string) { @@ -109,7 +100,6 @@ func TestInclusionTags(t *testing.T) { func TestExclusionTags(t *testing.T) { t.Parallel() p, k := prepareInclusionTestProject(t, "inclusion-exclusion", false) - defer p.cleanup() shouldExists := make(map[string]bool) doAssertExists := func(add ...string) { @@ -134,7 +124,6 @@ func TestExclusionTags(t *testing.T) { func TestInclusionIncludeDirs(t *testing.T) { t.Parallel() p, k := prepareInclusionTestProject(t, "inclusion-dirs", true) - defer p.cleanup() shouldExists := make(map[string]bool) doAssertExists := func(add ...string) { @@ -156,7 +145,6 @@ func TestInclusionIncludeDirs(t *testing.T) { func TestInclusionDeploymentDirs(t *testing.T) { t.Parallel() p, k := prepareInclusionTestProject(t, "inclusion-kustomize-dirs", true) - defer p.cleanup() shouldExists := make(map[string]bool) doAssertExists := func(add ...string) { @@ -184,7 +172,6 @@ func TestInclusionDeploymentDirs(t *testing.T) { func TestInclusionPrune(t *testing.T) { t.Parallel() p, k := prepareInclusionTestProject(t, "inclusion-prune", false) - defer p.cleanup() shouldExists := make(map[string]bool) doAssertExists := func(add []string, remove []string) { @@ -218,7 +205,6 @@ func TestInclusionPrune(t *testing.T) { func TestInclusionDelete(t *testing.T) { t.Parallel() p, k := prepareInclusionTestProject(t, "inclusion-delete", false) - defer p.cleanup() shouldExists := make(map[string]bool) doAssertExists := func(add []string, remove []string) { diff --git a/e2e/no_target_test.go b/e2e/no_target_test.go index 31351c3eb..7c2f74f61 100644 --- a/e2e/no_target_test.go +++ b/e2e/no_target_test.go @@ -28,7 +28,6 @@ func TestNoTarget(t *testing.T) { t.Parallel() p := prepareNoTargetTest(t, "no-target") - defer p.cleanup() p.KluctlMust("deploy", "--yes") cm := assertConfigMapExists(t, defaultCluster1, p.projectName, "cm") diff --git a/e2e/project.go b/e2e/project.go index b6f74c66f..83e3a6ecc 100644 --- a/e2e/project.go +++ b/e2e/project.go @@ -47,20 +47,12 @@ func (p *testProject) init(t *testing.T, k *test_utils.EnvTestCluster, projectNa } _ = tmpFile.Close() p.mergedKubeconfig = tmpFile.Name() + t.Cleanup(func() { + os.Remove(p.mergedKubeconfig) + }) p.mergeKubeconfig(k) } -func (p *testProject) cleanup() { - if p.gitServer != nil { - p.gitServer.Cleanup() - p.gitServer = nil - } - if p.mergedKubeconfig != "" { - _ = os.Remove(p.mergedKubeconfig) - p.mergedKubeconfig = "" - } -} - func (p *testProject) mergeKubeconfig(k *test_utils.EnvTestCluster) { p.updateMergedKubeconfig(func(config *clientcmdapi.Config) { nkcfg, err := clientcmd.Load(k.Kubeconfig) diff --git a/e2e/seal_test.go b/e2e/seal_test.go index 8f1b42dfe..ab7fb7fbe 100644 --- a/e2e/seal_test.go +++ b/e2e/seal_test.go @@ -100,9 +100,7 @@ func addProxyVars(p *testProject) { func prepareSealTest(t *testing.T, k *test_utils.EnvTestCluster, namespace string, secrets map[string]string, varsSources []*uo.UnstructuredObject, proxy bool) *testProject { p := &testProject{} p.init(t, k, fmt.Sprintf("seal-%s", namespace)) - t.Cleanup(func() { - p.cleanup() - }) + if proxy { addProxyVars(p) } From 105be5a7ab561616888f2503e5cc46183d415c92 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 25 Oct 2022 16:09:45 +0200 Subject: [PATCH 1134/2916] tests: Add test for generated kustomizations --- e2e/deploy_test.go | 34 ------------- e2e/deployment_items_test.go | 83 +++++++++++++++++++++++++++++++ e2e/project.go | 12 +++-- e2e/seal_test.go | 4 +- e2e/utils_resources.go | 31 ++++++++---- internal/test-utils/git_server.go | 8 +++ 6 files changed, 124 insertions(+), 48 deletions(-) delete mode 100644 e2e/deploy_test.go create mode 100644 e2e/deployment_items_test.go diff --git a/e2e/deploy_test.go b/e2e/deploy_test.go deleted file mode 100644 index 6c3029ea8..000000000 --- a/e2e/deploy_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package e2e - -import ( - "testing" -) - -func TestCommandDeploySimple(t *testing.T) { - t.Parallel() - - k := defaultCluster1 - - p := &testProject{} - p.init(t, k, "simple") - - createNamespace(t, k, p.projectName) - - p.updateTarget("test", nil) - - addConfigMapDeployment(p, "cm", nil, resourceOpts{ - name: "cm", - namespace: p.projectName, - }) - p.KluctlMust("deploy", "--yes", "-t", "test") - assertConfigMapExists(t, k, p.projectName, "cm") - - addConfigMapDeployment(p, "cm2", nil, resourceOpts{ - name: "cm2", - namespace: p.projectName, - }) - p.KluctlMust("deploy", "--yes", "-t", "test", "--dry-run") - assertConfigMapNotExists(t, k, p.projectName, "cm2") - p.KluctlMust("deploy", "--yes", "-t", "test") - assertConfigMapExists(t, k, p.projectName, "cm2") -} diff --git a/e2e/deployment_items_test.go b/e2e/deployment_items_test.go new file mode 100644 index 000000000..c1a8ffe8e --- /dev/null +++ b/e2e/deployment_items_test.go @@ -0,0 +1,83 @@ +package e2e + +import ( + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "testing" +) + +func TestKustomize(t *testing.T) { + t.Parallel() + + k := defaultCluster1 + + p := &testProject{} + p.init(t, k, "di-kustomize") + + createNamespace(t, k, p.projectName) + + p.updateTarget("test", nil) + + addConfigMapDeployment(p, "cm", nil, resourceOpts{ + name: "cm", + namespace: p.projectName, + }) + p.KluctlMust("deploy", "--yes", "-t", "test") + assertConfigMapExists(t, k, p.projectName, "cm") + + addConfigMapDeployment(p, "cm2", nil, resourceOpts{ + name: "cm2", + namespace: p.projectName, + }) + p.KluctlMust("deploy", "--yes", "-t", "test", "--dry-run") + assertConfigMapNotExists(t, k, p.projectName, "cm2") + p.KluctlMust("deploy", "--yes", "-t", "test") + assertConfigMapExists(t, k, p.projectName, "cm2") +} + +func TestGeneratedKustomize(t *testing.T) { + t.Parallel() + + k := defaultCluster1 + + p := &testProject{} + p.init(t, k, "di-generated-kustomize") + + createNamespace(t, k, p.projectName) + + p.updateTarget("test", nil) + + p.updateDeploymentYaml("", func(o *uo.UnstructuredObject) error { + _ = o.SetNestedField([]any{ + map[string]any{ + "path": "generated-kustomize", + }, + }, "deployments") + return nil + }) + p.updateYaml("generated-kustomize/cm1.yaml", func(o *uo.UnstructuredObject) error { + *o = *createConfigMapObject(nil, resourceOpts{ + name: "cm1", + namespace: p.projectName, + }) + return nil + }, "") + p.updateYaml("generated-kustomize/cm2.yaml", func(o *uo.UnstructuredObject) error { + *o = *createConfigMapObject(nil, resourceOpts{ + name: "cm2", + namespace: p.projectName, + }) + return nil + }, "") + p.updateYaml("generated-kustomize/cm3._yaml", func(o *uo.UnstructuredObject) error { + *o = *createConfigMapObject(nil, resourceOpts{ + name: "cm3", + namespace: p.projectName, + }) + return nil + }, "") + + p.KluctlMust("deploy", "--yes", "-t", "test") + assertConfigMapExists(t, k, p.projectName, "cm1") + assertConfigMapExists(t, k, p.projectName, "cm2") + assertConfigMapNotExists(t, k, p.projectName, "cm3") +} diff --git a/e2e/project.go b/e2e/project.go index 83e3a6ecc..f68025aef 100644 --- a/e2e/project.go +++ b/e2e/project.go @@ -82,11 +82,11 @@ func (p *testProject) updateMergedKubeconfig(cb func(config *clientcmdapi.Config } func (p *testProject) updateKluctlYaml(update func(o *uo.UnstructuredObject) error) { - p.gitServer.UpdateYaml(p.getKluctlProjectRepo(), ".kluctl.yml", update, "") + p.updateYaml(".kluctl.yml", update, "") } func (p *testProject) updateDeploymentYaml(dir string, update func(o *uo.UnstructuredObject) error) { - p.gitServer.UpdateYaml(p.getKluctlProjectRepo(), filepath.Join(dir, "deployment.yml"), func(o *uo.UnstructuredObject) error { + p.updateYaml(filepath.Join(dir, "deployment.yml"), func(o *uo.UnstructuredObject) error { if dir == "." { o.SetNestedField(p.projectName, "commonLabels", "project_name") } @@ -94,6 +94,12 @@ func (p *testProject) updateDeploymentYaml(dir string, update func(o *uo.Unstruc }, "") } +func (p *testProject) updateYaml(path string, update func(o *uo.UnstructuredObject) error, message string) { + p.gitServer.UpdateYaml(p.getKluctlProjectRepo(), path, func(o *uo.UnstructuredObject) error { + return update(o) + }, message) +} + func (p *testProject) getDeploymentYaml(dir string) *uo.UnstructuredObject { o, err := uo.FromFile(filepath.Join(p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()), dir, "deployment.yml")) if err != nil { @@ -130,7 +136,7 @@ func (p *testProject) updateKustomizeDeployment(dir string, update func(o *uo.Un wt := p.gitServer.GetWorktree(p.getKluctlProjectRepo()) pth := filepath.Join(dir, "kustomization.yml") - p.gitServer.UpdateYaml(p.getKluctlProjectRepo(), pth, func(o *uo.UnstructuredObject) error { + p.updateYaml(pth, func(o *uo.UnstructuredObject) error { return update(o, wt) }, fmt.Sprintf("Update kustomization.yml for %s", dir)) } diff --git a/e2e/seal_test.go b/e2e/seal_test.go index ab7fb7fbe..7ac2ef26a 100644 --- a/e2e/seal_test.go +++ b/e2e/seal_test.go @@ -110,7 +110,7 @@ func prepareSealTest(t *testing.T, k *test_utils.EnvTestCluster, namespace strin addSecretsSet(p, "test", varsSources) addSecretsSetToTarget(p, "test-target", "test") - addSecretDeployment(p, "secret-deployment", secrets, true, resourceOpts{name: "secret", namespace: namespace}) + addSecretDeployment(p, "secret-deployment", secrets, resourceOpts{name: "secret", namespace: namespace}) return p } @@ -393,7 +393,7 @@ func TestSeal_File(t *testing.T) { }), }, true) - p.gitServer.UpdateYaml(p.getKluctlProjectRepo(), "secret-values.yaml", func(o *uo.UnstructuredObject) error { + p.updateYaml("secret-values.yaml", func(o *uo.UnstructuredObject) error { *o = *uo.FromMap(map[string]interface{}{ "secrets": map[string]interface{}{ "s1": "v1", diff --git a/e2e/utils_resources.go b/e2e/utils_resources.go index 4bf65af62..073b246c5 100644 --- a/e2e/utils_resources.go +++ b/e2e/utils_resources.go @@ -28,25 +28,38 @@ func mergeMetadata(o *uo.UnstructuredObject, opts resourceOpts) { } } -func addConfigMapDeployment(p *testProject, dir string, data map[string]string, opts resourceOpts) { +func createCoreV1Object(kind string, opts resourceOpts) *uo.UnstructuredObject { o := uo.New() - o.SetK8sGVKs("", "v1", "ConfigMap") + o.SetK8sGVKs("", "v1", kind) mergeMetadata(o, opts) + return o +} + +func createConfigMapObject(data map[string]string, opts resourceOpts) *uo.UnstructuredObject { + o := createCoreV1Object("ConfigMap", opts) if data != nil { o.SetNestedField(data, "data") } - p.addKustomizeDeployment(dir, []kustomizeResource{ - {fmt.Sprintf("configmap-%s.yml", opts.name), "", o}, - }, opts.tags) + return o } -func addSecretDeployment(p *testProject, dir string, data map[string]string, sealedSecret bool, opts resourceOpts) { - o := uo.New() - o.SetK8sGVKs("", "v1", "Secret") - mergeMetadata(o, opts) +func createSecretObject(data map[string]string, opts resourceOpts) *uo.UnstructuredObject { + o := createCoreV1Object("ConfigMap", opts) if data != nil { o.SetNestedField(data, "stringData") } + return o +} + +func addConfigMapDeployment(p *testProject, dir string, data map[string]string, opts resourceOpts) { + o := createConfigMapObject(data, opts) + p.addKustomizeDeployment(dir, []kustomizeResource{ + {fmt.Sprintf("configmap-%s.yml", opts.name), "", o}, + }, opts.tags) +} + +func addSecretDeployment(p *testProject, dir string, data map[string]string, opts resourceOpts) { + o := createSecretObject(data, opts) fname := fmt.Sprintf("secret-%s.yml", opts.name) p.addKustomizeDeployment(dir, []kustomizeResource{ {fname, fname + ".sealme", o}, diff --git a/internal/test-utils/git_server.go b/internal/test-utils/git_server.go index 45a969474..4ed9f70da 100644 --- a/internal/test-utils/git_server.go +++ b/internal/test-utils/git_server.go @@ -150,6 +150,14 @@ func (p *GitServer) CommitFiles(repo string, add []string, all bool, message str func (p *GitServer) CommitYaml(repo string, pth string, message string, y *uo.UnstructuredObject) { fullPath := filepath.Join(p.LocalRepoDir(repo), pth) + dir, _ := filepath.Split(fullPath) + if dir != "" { + err := os.MkdirAll(dir, 0o700) + if err != nil { + panic(err) + } + } + err := yaml.WriteYamlFile(fullPath, y) if err != nil { p.t.Fatal(err) From 0323bf04f825be439053022c7300cc8fbdcbba41 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 25 Oct 2022 16:20:36 +0200 Subject: [PATCH 1135/2916] tests: Add test for sealing of multiple secrets --- e2e/seal_test.go | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/e2e/seal_test.go b/e2e/seal_test.go index 7ac2ef26a..801c5b63b 100644 --- a/e2e/seal_test.go +++ b/e2e/seal_test.go @@ -376,6 +376,47 @@ func TestSeal_MultipleTargets(t *testing.T) { }) } +func TestSeal_MultipleSecrets(t *testing.T) { + t.Parallel() + + k := defaultCluster1 + namespace := "seal-multiple-secrets" + + secret1 := map[string]string{ + "s1": "{{ secrets.s1 }}", + } + secret2 := map[string]string{ + "s2": "{{ secrets.s2 }}", + } + + p := prepareSealTest(t, k, namespace, + secret1, + []*uo.UnstructuredObject{ + uo.FromMap(map[string]interface{}{ + "values": map[string]interface{}{ + "s1": "v1", + "s2": "v2", + }, + }), + }, true) + addSecretDeployment(p, "secret-deployment2", secret2, resourceOpts{name: "secret2", namespace: namespace}) + + p.KluctlMust("seal", "-t", "test-target") + + sealedSecretsDir := p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()) + assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) + assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment2/test-target/secret-secret2.yml")) + + p.KluctlMust("deploy", "--yes", "-t", "test-target") + + assertSealedSecret(t, k, namespace, "secret", certServer1.certHash, map[string]string{ + "s1": "v1", + }) + assertSealedSecret(t, k, namespace, "secret2", certServer1.certHash, map[string]string{ + "s2": "v2", + }) +} + func TestSeal_File(t *testing.T) { t.Parallel() From 340bd4b67c645a9fc7ec941a568528e6f2b7b085 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 25 Oct 2022 16:33:46 +0200 Subject: [PATCH 1136/2916] test: Add test for sealing of multiple secrets via a single .sealme file --- e2e/seal_test.go | 46 +++++++++++++++++++++++++++++++ internal/test-utils/git_server.go | 26 +++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/e2e/seal_test.go b/e2e/seal_test.go index 801c5b63b..9f5e00490 100644 --- a/e2e/seal_test.go +++ b/e2e/seal_test.go @@ -417,6 +417,52 @@ func TestSeal_MultipleSecrets(t *testing.T) { }) } +func TestSeal_MultipleSecretsInOneFile(t *testing.T) { + t.Parallel() + + k := defaultCluster1 + namespace := "seal-multiple-secrets2" + + secret1 := map[string]string{ + "s1": "{{ secrets.s1 }}", + } + secret2 := map[string]string{ + "s2": "{{ secrets.s2 }}", + } + secret2Text, _ := yaml.WriteYamlString(createSecretObject(secret2, resourceOpts{name: "secret2", namespace: namespace})) + + p := prepareSealTest(t, k, namespace, + secret1, + []*uo.UnstructuredObject{ + uo.FromMap(map[string]interface{}{ + "values": map[string]interface{}{ + "s1": "v1", + "s2": "v2", + }, + }), + }, true) + + p.gitServer.UpdateFile(p.getKluctlProjectRepo(), "secret-deployment/secret-secret.yml.sealme", func(f string) (string, error) { + f += "---\n" + f += secret2Text + return f, nil + }, "") + + p.KluctlMust("seal", "-t", "test-target") + + sealedSecretsDir := p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()) + assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) + + p.KluctlMust("deploy", "--yes", "-t", "test-target") + + assertSealedSecret(t, k, namespace, "secret", certServer1.certHash, map[string]string{ + "s1": "v1", + }) + assertSealedSecret(t, k, namespace, "secret2", certServer1.certHash, map[string]string{ + "s2": "v2", + }) +} + func TestSeal_File(t *testing.T) { t.Parallel() diff --git a/internal/test-utils/git_server.go b/internal/test-utils/git_server.go index 4ed9f70da..070e4c98e 100644 --- a/internal/test-utils/git_server.go +++ b/internal/test-utils/git_server.go @@ -168,6 +168,32 @@ func (p *GitServer) CommitYaml(repo string, pth string, message string, y *uo.Un p.CommitFiles(repo, []string{pth}, false, message) } +func (p *GitServer) UpdateFile(repo string, pth string, update func(f string) (string, error), message string) { + fullPath := filepath.Join(p.LocalRepoDir(repo), pth) + f := "" + if utils.Exists(fullPath) { + b, err := os.ReadFile(fullPath) + if err != nil { + p.t.Fatal(err) + } + f = string(b) + } + + newF, err := update(f) + if err != nil { + p.t.Fatal(err) + } + + if f == newF { + return + } + err = os.WriteFile(fullPath, []byte(newF), 0o600) + if err != nil { + p.t.Fatal(err) + } + p.CommitFiles(repo, []string{pth}, false, message) +} + func (p *GitServer) UpdateYaml(repo string, pth string, update func(o *uo.UnstructuredObject) error, message string) { fullPath := filepath.Join(p.LocalRepoDir(repo), pth) From b6b5956472d451baf36d84e7af70dbe99db341b7 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 25 Oct 2022 17:11:08 +0200 Subject: [PATCH 1137/2916] feat: Allow to have multiple secrets in a single .sealme file --- pkg/seal/sealer.go | 160 +++++++++++++++++++++++++++++---------------- pkg/utils/uo/uo.go | 12 ++++ 2 files changed, 115 insertions(+), 57 deletions(-) diff --git a/pkg/seal/sealer.go b/pkg/seal/sealer.go index e821f2e99..3055768cf 100644 --- a/pkg/seal/sealer.go +++ b/pkg/seal/sealer.go @@ -17,7 +17,6 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "os" "path/filepath" - "reflect" "strconv" ) @@ -93,18 +92,88 @@ func (s *Sealer) encryptSecret(secret []byte, secretName string, secretNamespace return base64.StdEncoding.EncodeToString(b), nil } +type sealedSecret struct { + content *uo.UnstructuredObject + hashes *uo.UnstructuredObject + certHash string +} + +func buildSecretRef(o *uo.UnstructuredObject) string { + return fmt.Sprintf("%s/%s", o.GetK8sNamespace(), o.GetK8sName()) +} + +func (s *Sealer) loadExistingSealedSecrets(p string) (map[string]*sealedSecret, error) { + ret := map[string]*sealedSecret{} + + if !utils.Exists(p) { + return ret, nil + } + + list, err := uo.FromFileMulti(p) + if err != nil { + return nil, err + } + if len(list) != 1 { + err = nil + } + + for _, x := range list { + var ss sealedSecret + + ss.content = x + + a := x.GetK8sAnnotation(hashAnnotation) + if a != nil { + ss.hashes, _ = uo.FromString(*a) + } + a = x.GetK8sAnnotation(certHashAnnotation) + if a != nil { + ss.certHash = *a + } + if ss.hashes == nil { + ss.hashes = uo.New() + } + + ret[buildSecretRef(x)] = &ss + } + return ret, nil +} + func (s *Sealer) SealFile(p string, targetFile string) error { - baseName := filepath.Base(targetFile) err := os.MkdirAll(filepath.Dir(targetFile), 0o700) if err != nil { return err } - o, err := uo.FromFile(p) + existingSealedSecrets, err := s.loadExistingSealedSecrets(targetFile) + if err != nil { + return err + } + + secrets, err := uo.FromFileMulti(p) + if err != nil { + return err + } + + var result []any + + for _, o := range secrets { + existing, _ := existingSealedSecrets[buildSecretRef(o)] + newSealedSecret, err := s.sealSecret(o, existing) + if err != nil { + return err + } + result = append(result, newSealedSecret.content) + } + + err = yaml.WriteYamlAllFile(targetFile, result) if err != nil { return err } + return nil +} +func (s *Sealer) sealSecret(o *uo.UnstructuredObject, existing *sealedSecret) (*sealedSecret, error) { secretName := o.ToUnstructured().GetName() secretNamespace := o.ToUnstructured().GetNamespace() if secretNamespace == "" { @@ -112,7 +181,7 @@ func (s *Sealer) SealFile(p string, targetFile string) error { } secretType, ok, err := o.GetNestedString("type") if err != nil { - return err + return nil, err } if !ok { secretType = "Opaque" @@ -137,53 +206,34 @@ func (s *Sealer) SealFile(p string, targetFile string) error { scope = &x } - var existingContent *uo.UnstructuredObject - var existingHashes *uo.UnstructuredObject - var existingCertHash string - - if utils.Exists(targetFile) { - existingContent, err = uo.FromFile(targetFile) - a := existingContent.GetK8sAnnotation(hashAnnotation) - if a != nil { - existingHashes, _ = uo.FromString(*a) - } - a = existingContent.GetK8sAnnotation(certHashAnnotation) - if a != nil { - existingCertHash = *a - } - } - if existingHashes == nil { - existingHashes = uo.New() - } - secrets := make(map[string][]byte) data, ok, err := o.GetNestedObject("data") if err != nil { - return err + return nil, err } if ok { for k, v := range data.Object { s, ok := v.(string) if !ok { - return fmt.Errorf("%s is not a string", k) + return nil, fmt.Errorf("%s is not a string", k) } secrets[k], err = base64.StdEncoding.DecodeString(s) if err != nil { - return fmt.Errorf("failed to decode base64 string for secret %s and key %s", secretName, k) + return nil, fmt.Errorf("failed to decode base64 string for secret %s and key %s", secretName, k) } } } stringData, ok, err := o.GetNestedObject("stringData") if err != nil { - return err + return nil, err } if ok { for k, v := range stringData.Object { s, ok := v.(string) if !ok { - return fmt.Errorf("%s is not a string", k) + return nil, fmt.Errorf("%s is not a string", k) } secrets[k] = []byte(s) } @@ -191,47 +241,52 @@ func (s *Sealer) SealFile(p string, targetFile string) error { resultSecretHashes := make(map[string]string) - result := uo.New() - result.SetK8sGVK(schema.GroupVersionKind{Group: "bitnami.com", Version: "v1alpha1", Kind: "SealedSecret"}) - result.SetK8sName(secretName) - result.SetK8sNamespace(secretNamespace) - result.SetK8sAnnotation("sealedsecrets.bitnami.com/scope", *scope) + var result sealedSecret + result.content = uo.New() + result.content.SetK8sGVK(schema.GroupVersionKind{Group: "bitnami.com", Version: "v1alpha1", Kind: "SealedSecret"}) + result.content.SetK8sName(secretName) + result.content.SetK8sNamespace(secretNamespace) + result.content.SetK8sAnnotation("sealedsecrets.bitnami.com/scope", *scope) if *scope == "namespace-wide" { - result.SetK8sAnnotation("sealedsecrets.bitnami.com/namespace-wide", "true") + result.content.SetK8sAnnotation("sealedsecrets.bitnami.com/namespace-wide", "true") } if *scope == "cluster-wide" { - result.SetK8sAnnotation("sealedsecrets.bitnami.com/cluster-wide", "true") + result.content.SetK8sAnnotation("sealedsecrets.bitnami.com/cluster-wide", "true") } - _ = result.SetNestedField(secretType, "spec", "template", "type") + _ = result.content.SetNestedField(secretType, "spec", "template", "type") metadata, ok, _ := o.GetNestedObject("metadata") if ok { - result.SetNestedField(metadata.Object, "spec", "template", "metadata") + result.content.SetNestedField(metadata.Object, "spec", "template", "metadata") } resealAll := false if s.forceReseal { resealAll = true status.Info(s.ctx, "Forcing reseal of secrets in %s", secretName) - } else if existingCertHash != s.certHash { + } else if existing == nil || existing.certHash != s.certHash { resealAll = true status.Info(s.ctx, "Cert for secret %s has changed, forcing reseal", secretName) } for k, v := range secrets { hash := HashSecret(k, v, secretName, secretNamespace, *scope) - existingHash, _, _ := existingHashes.GetNestedString(k) - doEncrypt := resealAll + var existingHash string + if existing != nil { + existingHash, _, _ = existing.hashes.GetNestedString(k) + } + + doEncrypt := existing == nil || resealAll if !doEncrypt && hash != existingHash { status.Info(s.ctx, "Secret %s and key %s has changed, resealing", secretName, k) doEncrypt = true } if !doEncrypt { - e, ok, _ := existingContent.GetNestedString("spec", "encryptedData", k) + e, ok, _ := existing.content.GetNestedString("spec", "encryptedData", k) if ok { status.Trace(s.ctx, "Secret %s and key %s is unchanged", secretName, k) - result.SetNestedField(e, "spec", "encryptedData", k) + result.content.SetNestedField(e, "spec", "encryptedData", k) resultSecretHashes[k] = hash continue } else { @@ -242,27 +297,18 @@ func (s *Sealer) SealFile(p string, targetFile string) error { e, err := s.encryptSecret(v, secretName, secretNamespace, *scope) if err != nil { - return fmt.Errorf("failed to encrypt secret %s with key %s", secretName, k) + return nil, fmt.Errorf("failed to encrypt secret %s with key %s", secretName, k) } - result.SetNestedField(e, "spec", "encryptedData", k) + result.content.SetNestedField(e, "spec", "encryptedData", k) resultSecretHashes[k] = hash } resultSecretHashesStr, err := yaml.WriteYamlString(resultSecretHashes) if err != nil { - return err - } - result.SetK8sAnnotation(hashAnnotation, resultSecretHashesStr) - result.SetK8sAnnotation(certHashAnnotation, s.certHash) - - if reflect.DeepEqual(existingContent, result) { - status.Info(s.ctx, "Skipped %s as it did not change", baseName) - return nil + return nil, err } + result.content.SetK8sAnnotation(hashAnnotation, resultSecretHashesStr) + result.content.SetK8sAnnotation(certHashAnnotation, s.certHash) - err = yaml.WriteYamlFile(targetFile, result) - if err != nil { - return err - } - return nil + return &result, nil } diff --git a/pkg/utils/uo/uo.go b/pkg/utils/uo/uo.go index 8630e1a15..d5190d1ae 100644 --- a/pkg/utils/uo/uo.go +++ b/pkg/utils/uo/uo.go @@ -110,6 +110,18 @@ func FromStringMulti(s string) ([]*UnstructuredObject, error) { if err != nil { return nil, err } + return fromAnyList(ifs) +} + +func FromFileMulti(p string) ([]*UnstructuredObject, error) { + ifs, err := yaml.ReadYamlAllFile(p) + if err != nil { + return nil, err + } + return fromAnyList(ifs) +} + +func fromAnyList(ifs []any) ([]*UnstructuredObject, error) { var ret []*UnstructuredObject for _, i := range ifs { m, ok := i.(map[string]interface{}) From eb2d5d6861bb69cd005011fcf224e7506c10132d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 25 Oct 2022 17:23:04 +0200 Subject: [PATCH 1138/2916] chore: Run go get -u ./... --- go.mod | 42 +++++++++++++-------------- go.sum | 91 +++++++++++++++++++++++++++++----------------------------- 2 files changed, 67 insertions(+), 66 deletions(-) diff --git a/go.mod b/go.mod index 729920c5c..b422ea1b5 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e github.com/Masterminds/semver/v3 v3.1.1 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d - github.com/aws/aws-sdk-go v1.44.116 + github.com/aws/aws-sdk-go v1.44.122 github.com/bitnami-labs/sealed-secrets v0.19.1 github.com/cyphar/filepath-securejoin v0.2.3 github.com/docker/distribution v2.8.1+incompatible @@ -15,7 +15,7 @@ require ( github.com/go-playground/validator/v10 v10.11.1 github.com/gobwas/glob v0.2.3 // indirect github.com/golang-jwt/jwt/v4 v4.4.2 - github.com/google/go-containerregistry v0.11.0 + github.com/google/go-containerregistry v0.12.0 github.com/hashicorp/vault/api v1.8.1 github.com/hexops/gotextdiff v1.0.3 github.com/imdario/mergo v0.3.13 @@ -32,18 +32,18 @@ require ( github.com/r3labs/diff/v2 v2.15.1 github.com/rogpeppe/go-internal v1.9.0 github.com/sirupsen/logrus v1.9.0 - github.com/spf13/cobra v1.6.0 + github.com/spf13/cobra v1.6.1 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.13.0 github.com/stretchr/testify v1.8.0 github.com/whilp/git-urls v1.0.0 github.com/xanzy/ssh-agent v0.3.2 - golang.org/x/crypto v0.0.0-20221012134737-56aed061732a - golang.org/x/net v0.0.0-20221014081412-f15817d10f9b - golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 - golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 - golang.org/x/term v0.0.0-20220919170432-7a66f970e087 - golang.org/x/text v0.3.8 + golang.org/x/crypto v0.1.0 + golang.org/x/net v0.1.0 + golang.org/x/sync v0.1.0 + golang.org/x/sys v0.1.0 + golang.org/x/term v0.1.0 + golang.org/x/text v0.4.0 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 helm.sh/helm/v3 v3.10.1 @@ -71,7 +71,7 @@ require ( github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect - github.com/BurntSushi/toml v1.2.0 // indirect + github.com/BurntSushi/toml v1.2.1 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/sprig/v3 v3.2.2 // indirect @@ -87,10 +87,10 @@ require ( github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect github.com/cloudflare/circl v1.2.0 // indirect - github.com/containerd/containerd v1.6.8 // indirect + github.com/containerd/containerd v1.6.9 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/docker/cli v20.10.19+incompatible // indirect - github.com/docker/docker v20.10.19+incompatible // indirect + github.com/docker/cli v20.10.20+incompatible // indirect + github.com/docker/docker v20.10.20+incompatible // indirect github.com/docker/docker-credential-helpers v0.7.0 // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-metrics v0.0.1 // indirect @@ -105,7 +105,7 @@ require ( github.com/go-errors/errors v1.4.2 // indirect github.com/go-git/gcfg v1.5.0 // indirect github.com/go-git/go-billy/v5 v5.3.1 // indirect - github.com/go-gorp/gorp/v3 v3.0.2 // indirect + github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.20.0 // indirect github.com/go-openapi/swag v0.22.3 // indirect @@ -179,7 +179,7 @@ require ( github.com/pierrec/lz4 v2.6.1+incompatible // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.13.0 // indirect - github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect github.com/rivo/uniseg v0.4.2 // indirect @@ -198,15 +198,15 @@ require ( github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xlab/treeprint v1.1.0 // indirect go.etcd.io/etcd/api/v3 v3.5.5 // indirect - go.starlark.net v0.0.0-20221010140840-6bf6f0955179 // indirect + go.starlark.net v0.0.0-20221020143700-22309ac47eac // indirect go.uber.org/atomic v1.10.0 // indirect - golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect - golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect - golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect - golang.org/x/tools v0.1.12 // indirect + golang.org/x/mod v0.6.0 // indirect + golang.org/x/oauth2 v0.1.0 // indirect + golang.org/x/time v0.1.0 // indirect + golang.org/x/tools v0.2.0 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a // indirect + google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71 // indirect google.golang.org/grpc v1.50.1 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index e00cb9714..7e295f7f2 100644 --- a/go.sum +++ b/go.sum @@ -63,8 +63,8 @@ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBp github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e h1:NeAW1fUYUEWhft7pkxDf6WoUvEZJ/uOKsvtpjLnn8MU= github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0= -github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= +github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= @@ -114,8 +114,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/aws/aws-sdk-go v1.44.116 h1:NpLIhcvLWXJZAEwvPj3TDHeqp7DleK6ZUVYyW01WNHY= -github.com/aws/aws-sdk-go v1.44.116/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.122 h1:p6mw01WBaNpbdP2xrisz5tIkcNwzj/HysobNoaAHjgo= +github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -155,9 +155,9 @@ github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnht github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/containerd/cgroups v1.0.3 h1:ADZftAkglvCiD44c77s5YmMqaP2pzVCFZvBmAlBdAP4= -github.com/containerd/containerd v1.6.8 h1:h4dOFDwzHmqFEP754PgfgTeVXFnLiRc6kiqC7tplDJs= -github.com/containerd/containerd v1.6.8/go.mod h1:By6p5KqPK0/7/CgO/A6t/Gz+CUYUu2zf1hUaaymVXB0= -github.com/containerd/stargz-snapshotter/estargz v0.12.0 h1:idtwRTLjk2erqiYhPWy2L844By8NRFYEwYHcXhoIWPM= +github.com/containerd/containerd v1.6.9 h1:IN/r8DUes/B5lEGTNfIiUkfZBtIQJGx2ai703dV6lRA= +github.com/containerd/containerd v1.6.9/go.mod h1:XVicUvkxOrftE2Q1YWUXgZwkkAxwQYNOFzYWvfVfEfQ= +github.com/containerd/stargz-snapshotter/estargz v0.12.1 h1:+7nYmHJb0tEkcRaAW+MHqoKaJYZmkikupxCqVtmPuY0= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= @@ -172,12 +172,12 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/distribution/distribution/v3 v3.0.0-20220526142353-ffbd94cbe269 h1:hbCT8ZPPMqefiAWD2ZKjn7ypokIGViTvBBg/ExLSdCk= -github.com/docker/cli v20.10.19+incompatible h1:VKVBUb0KY/bx0FUCrCiNCL8wqgy8VxQli1dtNTn38AE= -github.com/docker/cli v20.10.19+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v20.10.20+incompatible h1:lWQbHSHUFs7KraSN2jOJK7zbMS2jNCHI4mt4xUFUVQ4= +github.com/docker/cli v20.10.20+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v20.10.19+incompatible h1:lzEmjivyNHFHMNAFLXORMBXyGIhw/UP4DvJwvyKYq64= -github.com/docker/docker v20.10.19+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v20.10.20+incompatible h1:kH9tx6XO+359d+iAkumyKDc5Q1kOwPuAUaeri48nD6E= +github.com/docker/docker v20.10.20+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= @@ -241,8 +241,9 @@ github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gorp/gorp/v3 v3.0.2 h1:ULqJXIekoqMx29FI5ekXXFoH1dT2Vc8UhnRzBg+Emz4= github.com/go-gorp/gorp/v3 v3.0.2/go.mod h1:BJ3q1ejpV8cVALtcXvXaXyTOlMmJhWDxTmncaR6rwBY= +github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= +github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= @@ -348,8 +349,8 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-containerregistry v0.11.0 h1:Xt8x1adcREjFcmDoDK8OdOsjxu90PHkGuwNP8GiHMLM= -github.com/google/go-containerregistry v0.11.0/go.mod h1:BBaYtsHPHA42uEgAvd/NejvAfPSlz281sJWqupjSxfk= +github.com/google/go-containerregistry v0.12.0 h1:nidOEtFYlgPCRqxCKj/4c/js940HVWplCWc5ftdfdUA= +github.com/google/go-containerregistry v0.12.0/go.mod h1:sdIK+oHQO7B93xI8UweYdl887YhuIwg9vz8BSLH3+8k= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -512,8 +513,8 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -561,8 +562,8 @@ github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWV github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/mattn/go-sqlite3 v1.14.14 h1:qZgc/Rwetq+MtyE18WhzjokPD93dNqLGNT3QJuLvBGw= github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2 h1:hAHbPm5IJGijwng3PWk09JkG9WeqChjprR5s9bBZ+OM= github.com/matttproud/golang_protobuf_extensions v1.0.2/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= @@ -650,8 +651,8 @@ github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qR github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/poy/onpar v0.0.0-20190519213022-ee068f8ea4d1 h1:oL4IBbcqwhhNWh31bjOX8C/OCy0zs9906d/VUru+bqg= github.com/poy/onpar v0.0.0-20190519213022-ee068f8ea4d1/go.mod h1:nSbFQvMj97ZyhFRSJYtut+msi4sOY6zJDGCdSc+/rZU= +github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= @@ -664,8 +665,9 @@ github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= @@ -730,8 +732,8 @@ github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= -github.com/spf13/cobra v1.6.0 h1:42a0n6jwCot1pUmomAp4T7DeMD+20LFv4Q54pxLf2LI= -github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= +github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -785,7 +787,6 @@ github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI= github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= -github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/api/v3 v3.5.5 h1:BX4JIbQ7hl7+jL+g+2j5UAr0o1bctCm6/Ct+ArBGkf0= @@ -801,8 +802,8 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.starlark.net v0.0.0-20221010140840-6bf6f0955179 h1:Mc5MkF55Iasgq23vSYpL6/l7EJXtlNjzw+8hbMQ/ShY= -go.starlark.net v0.0.0-20221010140840-6bf6f0955179/go.mod h1:kIVgS18CjmEC3PqMd5kaJSGEifyV/CeB9x506ZJ1Vbk= +go.starlark.net v0.0.0-20221020143700-22309ac47eac h1:gBO5Qfcw5V9404yzsu2FEIsxK/u2mBNTNogK0uIoVhk= +go.starlark.net v0.0.0-20221020143700-22309ac47eac/go.mod h1:kIVgS18CjmEC3PqMd5kaJSGEifyV/CeB9x506ZJ1Vbk= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= @@ -830,8 +831,8 @@ golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20221012134737-56aed061732a h1:NmSIgad6KjE6VvHciPZuNRTKxGhlPfD6OA87W/PLkqg= -golang.org/x/crypto v0.0.0-20221012134737-56aed061732a/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -867,8 +868,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I= +golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -914,8 +915,8 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20221014081412-f15817d10f9b h1:tvrvnPFcdzp294diPnrdZZZ8XUt2Tyj7svb7X52iDuU= -golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -930,8 +931,8 @@ golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 h1:nt+Q6cXKz4MosCSpnbMtqiQ8Oz0pxTef2B4Vca2lvfk= -golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.1.0 h1:isLCZuhj4v+tYv7eskaN4v/TM+A1begWWgyVJDdl1+Y= +golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -943,8 +944,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 h1:cu5kTvlzcw1Q5S9f5ip1/cpiB4nXvw1XYzFPGgzLUOY= -golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1017,13 +1018,13 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 h1:OK7RB6t2WQX54srQQYSXMW8dF5C6/8+oA/s5QBmmto4= -golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.0.0-20220919170432-7a66f970e087 h1:tPwmk4vmvVCMdr98VgL4JH+qZxPL8fqlUOHnyOM8N3w= -golang.org/x/term v0.0.0-20220919170432-7a66f970e087/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1033,13 +1034,13 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220922220347-f3bd1da661af h1:Yx9k8YCG3dvF87UAn2tu2HQLf2dt/eR1bXxpLMWeH+Y= -golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA= +golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -1094,8 +1095,8 @@ golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= -golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE= +golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1176,8 +1177,8 @@ google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a h1:GH6UPn3ixhWcKDhpnEC55S75cerLPdpp3hrhfKYjZgw= -google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= +google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71 h1:GEgb2jF5zxsFJpJfg9RoDDWm7tiwc/DDSTE2BtLUkXU= +google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= From c023d41b61703ff608744e2983a67efc8069cd31 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 25 Oct 2022 17:36:33 +0200 Subject: [PATCH 1139/2916] fix: Fix compilation of buildTransport after updating dependencies --- pkg/registries/registries.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/registries/registries.go b/pkg/registries/registries.go index 731f3cf27..a280438bf 100644 --- a/pkg/registries/registries.go +++ b/pkg/registries/registries.go @@ -228,7 +228,12 @@ func (rh *RegistryHelper) buildTransport(registry string) (http.RoundTripper, er return remote.DefaultTransport, nil } - t := remote.DefaultTransport.Clone() + httpTransport, ok := remote.DefaultTransport.(*http.Transport) + if !ok { + return nil, fmt.Errorf("remote.DefaultTransport is not a http.Transport anymore. Please report this to https://github.com/kluctl/kluctl") + } + t := httpTransport.Clone() + t.TLSClientConfig.RootCAs = ca t.TLSClientConfig.InsecureSkipVerify = skipTls return t, nil From 5aad1eccc36976d96310d466a20137c4b5753ab8 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 25 Oct 2022 22:49:53 +0200 Subject: [PATCH 1140/2916] feat: Also print orphan objects when showing diff before deploy --- pkg/deployment/commands/deploy.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/deployment/commands/deploy.go b/pkg/deployment/commands/deploy.go index 0fd1823ca..c82a6a303 100644 --- a/pkg/deployment/commands/deploy.go +++ b/pkg/deployment/commands/deploy.go @@ -53,17 +53,19 @@ func (cmd *DeployCommand) Run(ctx context.Context, k *k8s.K8sCluster, diffResult du := utils2.NewDiffUtil(dew, cmd.c.Deployments, ru, au.GetAppliedObjectsMap()) du.Diff() + orphanObjects, err := FindOrphanObjects(k, ru, cmd.c) diffResult := &types.CommandResult{ NewObjects: du.NewObjects, ChangedObjects: du.ChangedObjects, DeletedObjects: au.GetDeletedObjects(), HookObjects: au.GetAppliedHookObjects(), + OrphanObjects: orphanObjects, Errors: dew.GetErrorsList(), Warnings: dew.GetWarningsList(), SeenImages: cmd.c.Images.SeenImages(false), } - err := diffResultCb(diffResult) + err = diffResultCb(diffResult) if err != nil { return nil, err } From a0ae4c51ea0fcf246f9bdbab071abe27a6b4bae8 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 27 Oct 2022 10:33:29 +0200 Subject: [PATCH 1141/2916] fix: Treat "MY_ENV_VAR:" as empty string instead of nil --- pkg/vars/vars_loader.go | 4 ++++ pkg/vars/vars_loader_test.go | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/pkg/vars/vars_loader.go b/pkg/vars/vars_loader.go index 176c59d4e..81e62d746 100644 --- a/pkg/vars/vars_loader.go +++ b/pkg/vars/vars_loader.go @@ -141,6 +141,10 @@ func (v *VarsLoader) loadSystemEnvs(varsCtx *VarsCtx, source *types.VarsSource, envValueStr = v } else if hasDefaultValue { envValueStr = defaultValue + if envValueStr == "" { + // treat empty default string as literal empty string instead of treating it as nil + envValueStr = `""` + } } else { return fmt.Errorf("environment variable %s not found for %s", envName, it.KeyPath().ToJsonPath()) } diff --git a/pkg/vars/vars_loader_test.go b/pkg/vars/vars_loader_test.go index 65027e369..bf4e3085b 100644 --- a/pkg/vars/vars_loader_test.go +++ b/pkg/vars/vars_loader_test.go @@ -336,6 +336,8 @@ func TestVarsLoader_SystemEnv(t *testing.T) { }, "test5": "TEST5:def", "test6": "TEST1:def", + "test7": "TEST5:''", + "test8": "TEST5:", }), }, nil, "") assert.NoError(t, err) @@ -354,6 +356,12 @@ func TestVarsLoader_SystemEnv(t *testing.T) { v, _, _ = vc.Vars.GetNestedField("test6") assert.Equal(t, 42, v) + + v, _, _ = vc.Vars.GetNestedField("test7") + assert.Equal(t, "", v) + + v, _, _ = vc.Vars.GetNestedField("test8") + assert.Equal(t, "", v) }) testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { From 951dcccf17ab043620ba8cc8f16da64c86fdda31 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 27 Oct 2022 10:34:06 +0200 Subject: [PATCH 1142/2916] fix: Allow to mix arguments from environment and CLI --- cmd/kluctl/commands/cobra_utils.go | 16 ++++++++++--- e2e/args_test.go | 37 ++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/cmd/kluctl/commands/cobra_utils.go b/cmd/kluctl/commands/cobra_utils.go index af5644631..5982f04bf 100644 --- a/cmd/kluctl/commands/cobra_utils.go +++ b/cmd/kluctl/commands/cobra_utils.go @@ -230,7 +230,8 @@ func copyViperValuesToCobraCmd(cmd *cobra.Command) error { func copyViperValuesToCobraFlags(flags *pflag.FlagSet) error { var retErr []error flags.VisitAll(func(flag *pflag.Flag) { - if flag.Changed { + sliceValue, _ := flag.Value.(pflag.SliceValue) + if flag.Changed && sliceValue == nil { return } v := viper.Get(flag.Name) @@ -262,11 +263,20 @@ func copyViperValuesToCobraFlags(flags *pflag.FlagSet) error { a = append(a, v) } - for _, x := range a { - err := flag.Value.Set(x) + if sliceValue != nil { + // we must ensure that values passed via CLI are at the end of the slice + a = append(a, sliceValue.GetSlice()...) + err := sliceValue.Replace(a) if err != nil { retErr = append(retErr, err) } + } else { + for _, x := range a { + err := flag.Value.Set(x) + if err != nil { + retErr = append(retErr, err) + } + } } }) return utils.NewErrorListOrNil(retErr) diff --git a/e2e/args_test.go b/e2e/args_test.go index 527a75ce3..41c28870b 100644 --- a/e2e/args_test.go +++ b/e2e/args_test.go @@ -145,3 +145,40 @@ func TestArgsFromEnv(t *testing.T) { assertNestedFieldEquals(t, cm, "True", "data", "d") assertNestedFieldEquals(t, cm, "true", "data", "e") } + +func TestArgsFromEnvAndCli(t *testing.T) { + t.Setenv("KLUCTL_ARG_1", "a=a") + t.Setenv("KLUCTL_ARG_2", "c=c") + + k := defaultCluster1 + + p := &testProject{} + p.init(t, k, "args-from-envs-and-cli") + + createNamespace(t, k, p.projectName) + + p.updateTarget("test", func(target *uo.UnstructuredObject) { + }) + + addConfigMapDeployment(p, "cm", map[string]string{ + "a": `{{ args.a }}`, + "b": `{{ args.b }}`, + "c": `{{ args.c }}`, + }, resourceOpts{ + name: "cm", + namespace: p.projectName, + }) + + p.KluctlMust("deploy", "--yes", "-t", "test", "-a", "b=b") + cm := k.MustGetCoreV1(t, "configmaps", p.projectName, "cm") + assertNestedFieldEquals(t, cm, "a", "data", "a") + assertNestedFieldEquals(t, cm, "b", "data", "b") + assertNestedFieldEquals(t, cm, "c", "data", "c") + + // make sure the CLI overrides values from env + p.KluctlMust("deploy", "--yes", "-t", "test", "-a", "b=b", "-a", "c=c2") + cm = k.MustGetCoreV1(t, "configmaps", p.projectName, "cm") + assertNestedFieldEquals(t, cm, "a", "data", "a") + assertNestedFieldEquals(t, cm, "b", "data", "b") + assertNestedFieldEquals(t, cm, "c2", "data", "c") +} From 199955ca1d21b4ca9f6e5fca6f2d3c130099cf39 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 28 Oct 2022 12:13:48 +0200 Subject: [PATCH 1143/2916] docs: Add COC, CONTRIBUTING and DEVELOPMENT docs --- CODE_OF_CONDUCT.md | 3 +++ CONTRIBUTING.md | 59 ++++++++++++++++++++++++++++++++++++++++++++++ DEVELOPMENT.md | 50 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+) create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 DEVELOPMENT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..5f90c194f --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,3 @@ +## Code of Conduct + +Kluctl follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..bc4ff3e98 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,59 @@ +# Contributing + +As all Open Source projects, Kluctl lives from contributions. If you consider contributing, we are welcoming you to do so. + +## Communication + +Contributing to Open Source projects also means that you are communicating with other members of the community. Please +follow the [Code of Conduct](./CODE_OF_CONDUCT.md) whenever you communicate. + +## Creating issues / Reporting bugs + +Probably the easiest way to contribute is to create issues on Github. An issue could for example be a bug report or +a feature request. It's also fine to create an issue that requests some change, for example to documentation. Issues +can also be used as reminders/TODOs that something needs to be done in the future. + +The project is still in early stage when it comes to project management, so please bare with us if processes are still +a bit "loose". + +One thing we'd like to ask for is to first search for issues that might already represent what you plan to report. +In many cases it turns out that other people stumbled across the same thing already. If not, then you're "lucky" to +report first :) + +It might also be useful to ask inside the #kluctl channel of the [CNCF Slack](https://slack.cncf.io) if you are unsure +about your issue. + +## Finding issues to work on + +Check the [open issues](https://github.com/kluctl/kluctl/issues) and see if you can find something that you believe you +could help with. + +## Contributing/Modifying documentation + +The next level of contribution is modifying documentation. Fork the repository and start working on the changes locally. +When done, commit the changes, push them and create a pull request. If your pull request fixes an issue, add the proper +`Fixes: #xxx` line so that the issue can be auto-closed. + +Please note that we follow [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) when committing. + +## Contributing/Modifying source code + +To fix a bug or add a feature, follow the same procedure as described in the previous chapter. Read the +[DEVELOPMENT](./DEVELOPMENT.md) guidelines on how to build and run Kluctl from source. + +Additionally, you should ensure that your changes don't break anything. You can do this by running the +[test suite](./DEVELOPMENT.md#how-to-run-the-test-suite) and by testing your changes manually. Adding new tests for +fixed bugs and/or new features is also nice to see. Reviewers will also point out when tests would be of value. + +However, don't worry if you feel overwhelmed (e.g. with tests). You can also create a `[WIP]` pull request and ask for +help. + +Please note that we follow [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) when committing. + +## Conventional commits + +As mentioned before, we follow [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) when committing. +These commits are then used to create release notes and properly bump version numbers. + +We might reconsider this approach in the future, especially when we re-design the +[release process](./DEVELOPMENT.md#releasing-process). diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 000000000..015f2a904 --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,50 @@ +# Development + +## Installing required dependencies + +There are a number of dependencies required to be able to compile, run and test Kluctl: + +- [Install Go](https://golang.org/doc/install) + +In addition to the above, the following dependencies are also used by some of the `make` targets: + +- `goreleaser` (latest) +- `setup-envtest` (latest) + +If any of the above dependencies are not present on your system, the first invocation of a `make` target that requires them will install them. + +## How to run the test suite + +Prerequisites: +* Go >= 1.19 + +You can run the test suite by simply doing + +```sh +make test +``` + +Tests are separated between unit tests (found in-tree next to the tested code) and e2e tests (found below `./e2e`). +Running `make test` will run all these tests. To only run unit tests, run `make test-unit`. To only run e2e tests, run +`make test-e2e`. + +e2e tests rely on kubebuilders `envtest` package and thus also require `setup-envtest` and dependent binaries +(e.g. kube-apiserver) to be available as well. The Makefile will take care of downloading these if required. + +## How to build Kluctl + +Simply run `make build`, which will build the kluctl binary any put into `./bin/`. To use, either directly invoke it +with the relative or absolute path or update your `PATH` environment variable to point to the bin directory. + +## Contributions + +See [CONTRIBUTING](./CONTRIBUTING.md) for details. + +## Releasing process + +The release process is currently partly manual and partly automated. A maintainer has to create a version tag and +manually push it to Github. A Github workflow will then react to this tag by running `goreleaser`, which will then +create a draft release. The maintainer then has to manually update the pre-generated release notes and publish the +release. + +This process is going to be modified in the future to contain more automation and more collaboration friendly processes. From bac93151c38802a7919c18c8bc01f811f7bd8b45 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 28 Oct 2022 12:14:04 +0200 Subject: [PATCH 1144/2916] docs: Update README.md with links to new docs --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 899b40f2e..3d6380bc7 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,11 @@ Documentation, news and blog posts can be found on https://kluctl.io. The underlying documentation is synced from this repo (look into ./docs) to the website whenever something is merged into main. +## Development and contributions + +Please read [DEVELOPMENT](./DEVELOPMENT.md) and [CONTRIBUTIONS](./CONTRIBUTING.md) for details on how the Kluctl project +handles these matters. + ## Kluctl in Short | | | From 0f165a912f2f7d46f3fd53e5b3f5c6a87392a07d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 28 Oct 2022 12:14:15 +0200 Subject: [PATCH 1145/2916] docs: Add community section in README.md --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 3d6380bc7..51c58facb 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,15 @@ The Kluctl CLI then allows to deploy, diff, prune, delete, ... your deployments. Installation instructions can be found [here](./docs/installation.md). For a getting started guide, continue [here](./docs/get-started.md). +## Community + +Check the [community page](https://kluctl.io/community/) for details about the Kluctl community. + +In short: We use [Github Issues](https://github.com/kluctl/kluctl/issues) and +[Github Discussions](https://github.com/kluctl/kluctl/discussions) to track and discuss Kluctl related development. +You can also join the #kluctl channel inside the [CNCF Slack](https://slack.cncf.io) to get in contact with other +community members and contributors/developers. + ## Documentation Documentation, news and blog posts can be found on https://kluctl.io. From c5790b8df0571c05eca983d92acac0a74c1f4ab2 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 28 Oct 2022 12:23:45 +0200 Subject: [PATCH 1146/2916] docs: Add MAINTAINERS.md --- MAINTAINERS.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 MAINTAINERS.md diff --git a/MAINTAINERS.md b/MAINTAINERS.md new file mode 100644 index 000000000..cd30797d4 --- /dev/null +++ b/MAINTAINERS.md @@ -0,0 +1,11 @@ +# Maintainers + +The maintainers are generally available in Slack at +https://cloud-native.slack.com in #klcutl +(obtain an invitation at https://slack.cncf.io/). + +Currently, the maintainers of Kluctl are: + +- Alexander Block (github: @codablock, slack: codablock) +- Aljoscha Poertner (github: @AljoschaP, slack: aljoshare) +- Matthias Gebbe (github: @matzegebbe) From 652bfb0b94d1f033c74e0095f46502615fa0c38c Mon Sep 17 00:00:00 2001 From: Mathias Gebbe Date: Tue, 1 Nov 2022 23:14:10 +0100 Subject: [PATCH 1147/2916] Update MAINTAINERS.md fix firstname, added slack for Mathias Gebbe --- MAINTAINERS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS.md b/MAINTAINERS.md index cd30797d4..715056cf7 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -8,4 +8,4 @@ Currently, the maintainers of Kluctl are: - Alexander Block (github: @codablock, slack: codablock) - Aljoscha Poertner (github: @AljoschaP, slack: aljoshare) -- Matthias Gebbe (github: @matzegebbe) +- Mathias Gebbe (github: @matzegebbe, slack: matzeihnsein) From 2e285bb4848d9c51334fcc847252937249f09e3e Mon Sep 17 00:00:00 2001 From: Prateek Mishra Date: Fri, 4 Nov 2022 22:43:36 +0530 Subject: [PATCH 1148/2916] typo fix --- docs/get-started.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/get-started.md b/docs/get-started.md index e1cc96cce..5393963e9 100644 --- a/docs/get-started.md +++ b/docs/get-started.md @@ -90,7 +90,7 @@ Kluctl will perform a diff first and then ask for your confirmation to deploy it some objects being newly deployed. ```sh -kubectl -nsimple-helm get pod +kubectl -n simple-helm get pod ``` ## Change something and re-deploy @@ -106,7 +106,7 @@ This time it should show your modifications in the diff. Confirm that you want t it: ```sh -kubectl -nsimple-helm get pod +kubectl -n simple-helm get pod ``` You should need 2 instances of the nginx POD running now. From 896ffb1344bd8052eb85ec393fc0158ae11bca0e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 8 Nov 2022 08:43:10 +0100 Subject: [PATCH 1149/2916] fix: Also take initContainers into account in poke-images --- pkg/deployment/commands/poke_images.go | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/pkg/deployment/commands/poke_images.go b/pkg/deployment/commands/poke_images.go index cde5280b7..a06bf48a2 100644 --- a/pkg/deployment/commands/poke_images.go +++ b/pkg/deployment/commands/poke_images.go @@ -54,14 +54,19 @@ func (cmd *PokeImagesCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*type containersAndImages[*fi.Object] = append(containersAndImages[*fi.Object], fi) } - doPokeImage := func(images []types.FixedImage, o *uo.UnstructuredObject) (*uo.UnstructuredObject, error) { - containers, _, _ := o.GetNestedObjectList("spec", "template", "spec", "containers") + var fieldPathes []uo.KeyPath + fieldPathes = append(fieldPathes, uo.KeyPath{"spec", "template", "spec", "containers"}) + fieldPathes = append(fieldPathes, uo.KeyPath{"spec", "template", "spec", "initContainers"}) + doPokeImage := func(images []types.FixedImage, o *uo.UnstructuredObject) (*uo.UnstructuredObject, error) { for _, image := range images { - for _, c := range containers { - containerName, _, _ := c.GetNestedString("name") - if image.Container != nil && containerName == *image.Container { - c.SetNestedField(image.ResultImage, "image") + for _, jsp := range fieldPathes { + containers, _, _ := o.GetNestedObjectList(jsp...) + for _, c := range containers { + containerName, _, _ := c.GetNestedString("name") + if image.Container != nil && containerName == *image.Container { + _ = c.SetNestedField(image.ResultImage, "image") + } } } } From 4bb1f6717620a1350fce21de9d1f08171b189142 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 8 Nov 2022 10:08:07 +0100 Subject: [PATCH 1150/2916] docs: Remove redundant section from readiness.md That same section is already inside hooks.md --- docs/reference/deployments/readiness.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/reference/deployments/readiness.md b/docs/reference/deployments/readiness.md index d571f969f..ec8020bb0 100644 --- a/docs/reference/deployments/readiness.md +++ b/docs/reference/deployments/readiness.md @@ -14,7 +14,3 @@ description: There are multiple places where kluctl can wait for "readiness" of resources, e.g. for hooks or when `waitReadiness` is specified on a deployment item. Readiness depends on the resource kind, e.g. for a Job, kluctl would wait until it finishes successfully. - -After each deployment/execution of the hooks that belong to a deployment stage (before/after deployment), kluctl -waits for the hook resources to become "ready". Readiness depends on the resource kind, e.g. for a Job, kluctl would -wait until it finishes successfully. From 194582a39c5e96ea52dd7ef0e26c5ebb93290a49 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 10 Nov 2022 13:17:45 +0100 Subject: [PATCH 1151/2916] refactor: Move writing of kustomization.yaml into buildKustomize --- pkg/deployment/deployment_item.go | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 0c53716e9..490cec5f9 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -450,19 +450,19 @@ func (di *DeploymentItem) writeKustomizationYaml(ky *uo.UnstructuredObject) erro return yaml.WriteYamlFile(kustomizeYamlPath, ky) } -func (di *DeploymentItem) prepareKustomizationYaml() error { +func (di *DeploymentItem) prepareKustomizationYaml() (*uo.UnstructuredObject, error) { ky, err := di.readKustomizationYaml("") if err != nil { - return err + return nil, err } if ky == nil { ky, err = di.generateKustomizationYaml("") if err != nil { - return err + return nil, err } err = di.writeKustomizationYaml(ky) if err != nil { - return err + return nil, err } } @@ -470,7 +470,7 @@ func (di *DeploymentItem) prepareKustomizationYaml() error { if overrideNamespace != nil { _, ok, err := ky.GetNestedString("namespace") if err != nil { - return err + return nil, err } if !ok { ky.SetNestedField(*overrideNamespace, "namespace") @@ -480,8 +480,7 @@ func (di *DeploymentItem) prepareKustomizationYaml() error { di.Barrier = utils.ParseBoolOrFalse(ky.GetK8sAnnotation("kluctl.io/barrier")) di.WaitReadiness = utils.ParseBoolOrFalse(ky.GetK8sAnnotation("kluctl.io/wait-readiness")) - // Save modified kustomization.yml - return di.writeKustomizationYaml(ky) + return ky, nil } func (di *DeploymentItem) buildKustomize() error { @@ -489,7 +488,13 @@ func (di *DeploymentItem) buildKustomize() error { return nil } - err := di.prepareKustomizationYaml() + ky, err := di.prepareKustomizationYaml() + if err != nil { + return err + } + + // Save modified kustomization.yml + err = di.writeKustomizationYaml(ky) if err != nil { return err } From d4d381855a1d0299c400c26759ea7815785fb314 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 10 Nov 2022 11:01:47 +0100 Subject: [PATCH 1152/2916] refacor: Reuse loadFile for git vars --- pkg/vars/vars.go | 20 ++++++++++++++------ pkg/vars/vars_loader.go | 15 ++------------- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/pkg/vars/vars.go b/pkg/vars/vars.go index ecb960224..cc3599471 100644 --- a/pkg/vars/vars.go +++ b/pkg/vars/vars.go @@ -44,12 +44,13 @@ func (vc *VarsCtx) UpdateChildFromStruct(child string, o interface{}) error { return nil } -func (vc *VarsCtx) RenderString(t string) (string, error) { +func (vc *VarsCtx) RenderString(t string, searchDirs []string) (string, error) { globals, err := vc.Vars.ToMap() if err != nil { return "", err } return vc.J2.RenderString(t, + jinja2.WithSearchDirs(searchDirs), jinja2.WithGlobals(globals), ) } @@ -62,24 +63,31 @@ func (vc *VarsCtx) RenderStruct(o interface{}) (bool, error) { return vc.J2.RenderStruct(o, jinja2.WithGlobals(globals)) } -func (vc *VarsCtx) RenderYamlFile(p string, searchDirs []string, out interface{}) error { +func (vc *VarsCtx) RenderFile(p string, searchDirs []string) (string, error) { globals, err := vc.Vars.ToMap() if err != nil { - return err + return "", err } ret, err := vc.J2.RenderFile(p, jinja2.WithSearchDirs(searchDirs), jinja2.WithGlobals(globals), ) if err != nil { - return err + return "", err } - err = yaml.ReadYamlString(ret, out) + return ret, nil +} + +func (vc *VarsCtx) RenderYamlFile(p string, searchDirs []string, out interface{}) error { + rendered, err := vc.RenderFile(p, searchDirs) + if err != nil { + return err + } + err = yaml.ReadYamlString(rendered, out) if err != nil { return err } - return nil } diff --git a/pkg/vars/vars_loader.go b/pkg/vars/vars_loader.go index 81e62d746..b0ae45db4 100644 --- a/pkg/vars/vars_loader.go +++ b/pkg/vars/vars_loader.go @@ -4,7 +4,6 @@ import ( "context" "encoding/base64" "fmt" - securejoin "github.com/cyphar/filepath-securejoin" "github.com/kluctl/go-jinja2" "github.com/kluctl/kluctl/v2/pkg/git/repocache" "github.com/kluctl/kluctl/v2/pkg/k8s" @@ -199,17 +198,7 @@ func (v *VarsLoader) loadGit(varsCtx *VarsCtx, gitFile *types.VarsSourceGit, roo return fmt.Errorf("failed to load vars from git repository %s: %w", gitFile.Url.String(), err) } - path, err := securejoin.SecureJoin(clonedDir, gitFile.Path) - if err != nil { - return err - } - - f, err := os.ReadFile(path) - if err != nil { - return err - } - - return v.loadFromString(varsCtx, string(f), "git", rootKey) + return v.loadFile(varsCtx, gitFile.Path, []string{clonedDir}, rootKey) } func (v *VarsLoader) loadFromK8sObject(varsCtx *VarsCtx, varsSource types.VarsSourceClusterConfigMapOrSecret, kind string, key string, rootKey string, base64Decode bool) error { @@ -297,7 +286,7 @@ func (v *VarsLoader) loadFromString(varsCtx *VarsCtx, s string, secretType strin } func (v *VarsLoader) renderYamlString(varsCtx *VarsCtx, s string, out interface{}) error { - ret, err := varsCtx.RenderString(s) + ret, err := varsCtx.RenderString(s, nil) if err != nil { return err } From 54a3485d2b1ce96a2cb2a4cc37435b0fa86b0330 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 10 Nov 2022 15:30:17 +0100 Subject: [PATCH 1153/2916] feat: Implement sops support for vars files --- go.mod | 18 +++ go.sum | 185 ++++++++++++++++++++++ pkg/vars/sops_test_resources/.sops.yaml | 3 + pkg/vars/sops_test_resources/README.md | 4 + pkg/vars/sops_test_resources/embed.go | 6 + pkg/vars/sops_test_resources/test-key.txt | 3 + pkg/vars/sops_test_resources/test.yaml | 22 +++ pkg/vars/vars_loader.go | 22 ++- pkg/vars/vars_loader_test.go | 22 ++- pkg/yaml/yaml.go | 4 + 10 files changed, 287 insertions(+), 2 deletions(-) create mode 100644 pkg/vars/sops_test_resources/.sops.yaml create mode 100644 pkg/vars/sops_test_resources/README.md create mode 100644 pkg/vars/sops_test_resources/embed.go create mode 100644 pkg/vars/sops_test_resources/test-key.txt create mode 100644 pkg/vars/sops_test_resources/test.yaml diff --git a/go.mod b/go.mod index b422ea1b5..4bcfb6282 100644 --- a/go.mod +++ b/go.mod @@ -59,16 +59,23 @@ require ( require ( github.com/go-logr/logr v1.2.3 + go.mozilla.org/sops/v3 v3.7.3 sigs.k8s.io/controller-runtime v0.13.0 ) require ( cloud.google.com/go/compute v1.10.0 // indirect + filippo.io/age v1.0.0 // indirect + github.com/Azure/azure-sdk-for-go v63.3.0+incompatible // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest v0.11.28 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.21 // indirect + github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 // indirect + github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 // indirect github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect + github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect + github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/BurntSushi/toml v1.2.1 // indirect @@ -83,12 +90,14 @@ require ( github.com/armon/go-radix v1.0.0 // indirect github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/blang/semver v3.5.1+incompatible // indirect github.com/cenkalti/backoff/v3 v3.2.2 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect github.com/cloudflare/circl v1.2.0 // indirect github.com/containerd/containerd v1.6.9 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dimchansky/utfbom v1.1.1 // indirect github.com/docker/cli v20.10.20+incompatible // indirect github.com/docker/docker v20.10.20+incompatible // indirect github.com/docker/docker-credential-helpers v0.7.0 // indirect @@ -112,6 +121,7 @@ require ( github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.1.2 // indirect @@ -120,8 +130,11 @@ require ( github.com/google/gofuzz v1.2.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.3.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.1.0 // indirect + github.com/googleapis/gax-go/v2 v2.4.0 // indirect github.com/gorilla/mux v1.8.0 // indirect github.com/gosuri/uitable v0.0.4 // indirect + github.com/goware/prefixer v0.0.0-20160118172347-395022866408 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect @@ -141,6 +154,7 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/vault/sdk v0.6.0 // indirect github.com/hashicorp/yamux v0.1.1 // indirect + github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef // indirect github.com/huandu/xstrings v1.3.2 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect @@ -198,6 +212,8 @@ require ( github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xlab/treeprint v1.1.0 // indirect go.etcd.io/etcd/api/v3 v3.5.5 // indirect + go.mozilla.org/gopgagent v0.0.0-20170926210634-4d7ea76ff71a // indirect + go.opencensus.io v0.23.0 // indirect go.starlark.net v0.0.0-20221020143700-22309ac47eac // indirect go.uber.org/atomic v1.10.0 // indirect golang.org/x/mod v0.6.0 // indirect @@ -205,6 +221,7 @@ require ( golang.org/x/time v0.1.0 // indirect golang.org/x/tools v0.2.0 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect + google.golang.org/api v0.96.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71 // indirect google.golang.org/grpc v1.50.1 // indirect @@ -212,6 +229,7 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect + gopkg.in/urfave/cli.v1 v1.20.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect k8s.io/apiserver v0.25.3 // indirect k8s.io/cli-runtime v0.25.3 // indirect diff --git a/go.sum b/go.sum index 7e295f7f2..11bfa90e8 100644 --- a/go.sum +++ b/go.sum @@ -20,17 +20,34 @@ cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPT cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= +cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= +cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= +cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= +cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= +cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= +cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= cloud.google.com/go/compute v1.10.0 h1:aoLIYaA1fX3ywihqpBk2APQKOo20nXsp1GEZQbx5Jk4= cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -41,21 +58,35 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +filippo.io/age v1.0.0 h1:V6q14n0mqYU3qKFkZ6oOaF9oXneOviS3ubXsSVBRSzc= +filippo.io/age v1.0.0/go.mod h1:PaX+Si/Sd5G8LgfCwldsSba3H1DDQZhIhFGkhbHaBq8= +github.com/Azure/azure-sdk-for-go v63.3.0+incompatible h1:INepVujzUrmArRZjDLHbtER+FkvCoEwyRCXGqOlmDII= +github.com/Azure/azure-sdk-for-go v63.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.11.24/go.mod h1:G6kyRlFnTuSbEYkQGawPfsCswgme4iYf6rfSKUDzbCc= github.com/Azure/go-autorest/autorest v0.11.28 h1:ndAExarwr5Y+GaHE6VCaY1kyS/HwwGGyuimVhWsHOEM= github.com/Azure/go-autorest/autorest v0.11.28/go.mod h1:MrkzG3Y3AH668QyF9KRk5neJnGgmhQ6krbhR8Q5eMvA= github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= github.com/Azure/go-autorest/autorest/adal v0.9.21 h1:jjQnVFXPfekaqb8vIsv2G1lxshoW+oGv4MDlhRtnYZk= github.com/Azure/go-autorest/autorest/adal v0.9.21/go.mod h1:zua7mBUaCc5YnSLKYgGJR/w5ePdMDA6H56upLsHzA9U= +github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 h1:P6bYXFoao05z5uhOQzbC3Qd8JqF3jUoocoTeIxkp2cA= +github.com/Azure/go-autorest/autorest/azure/auth v0.5.11/go.mod h1:84w/uV8E37feW2NCJ08uT9VBfjfUHpgLVnG2InYD6cg= +github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 h1:0W/yGmFdTIT77fvdlGZ0LMISoLHFJ7Tx4U0yeB+uFs4= +github.com/Azure/go-autorest/autorest/azure/cli v0.4.5/go.mod h1:ADQAXrkgm7acgWVUNamOgh8YNrv4p27l3Wc55oVfpzg= github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw= github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU= +github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk= +github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= +github.com/Azure/go-autorest/autorest/validation v0.3.1 h1:AgyqjAd94fwNAoTjl/WQXg4VvFeRFpO+UhNyRXqF1ac= +github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E= github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= @@ -86,6 +117,7 @@ github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= github.com/Microsoft/hcsshim v0.9.4 h1:mnUj0ivWy6UzbB1uLFqKR6F+ZyiDc7j4iGgHTpO+5+I= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= github.com/ProtonMail/go-crypto v0.0.0-20220930113650-c6815a8c17ad h1:QeeqI2zxxgZVe11UrYFXXx6gVxPVF40ygekjBzEg4XY= @@ -124,6 +156,8 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB github.com/bitnami-labs/sealed-secrets v0.19.1 h1:ZNLcVtTXRf7VkyNzyhe9omlwNYI0OHDteTbIHsQI7Ug= github.com/bitnami-labs/sealed-secrets v0.19.1/go.mod h1:5UcsiOdOoviJUtXY1GNSeFa8zezbBY+j9pQAA2RtKyw= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= +github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd h1:rFt+Y/IK1aEZkEHchZRSq9OQbsSzIT/OrI8YFFmRIng= @@ -131,6 +165,7 @@ github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembj github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/bwesterb/go-ristretto v1.2.1/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -152,11 +187,16 @@ github.com/cloudflare/circl v1.2.0/go.mod h1:Ch2UgYr6ti2KTtlejELlROl0YIYj7SLjAC8 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/containerd/cgroups v1.0.3 h1:ADZftAkglvCiD44c77s5YmMqaP2pzVCFZvBmAlBdAP4= github.com/containerd/containerd v1.6.9 h1:IN/r8DUes/B5lEGTNfIiUkfZBtIQJGx2ai703dV6lRA= github.com/containerd/containerd v1.6.9/go.mod h1:XVicUvkxOrftE2Q1YWUXgZwkkAxwQYNOFzYWvfVfEfQ= +github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= github.com/containerd/stargz-snapshotter/estargz v0.12.1 h1:+7nYmHJb0tEkcRaAW+MHqoKaJYZmkikupxCqVtmPuY0= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= @@ -171,6 +211,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= +github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= github.com/distribution/distribution/v3 v3.0.0-20220526142353-ffbd94cbe269 h1:hbCT8ZPPMqefiAWD2ZKjn7ypokIGViTvBBg/ExLSdCk= github.com/docker/cli v20.10.20+incompatible h1:lWQbHSHUFs7KraSN2jOJK7zbMS2jNCHI4mt4xUFUVQ4= github.com/docker/cli v20.10.20+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= @@ -203,6 +245,7 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= @@ -301,6 +344,7 @@ github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4er github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -309,6 +353,7 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -327,6 +372,7 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= @@ -347,6 +393,9 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-containerregistry v0.12.0 h1:nidOEtFYlgPCRqxCKj/4c/js940HVWplCWc5ftdfdUA= @@ -357,6 +406,7 @@ github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -369,6 +419,9 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= @@ -376,8 +429,18 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= +github.com/googleapis/enterprise-certificate-proxy v0.1.0 h1:zO8WHNx/MYiAKJ3d5spxZXZE6KHmIQGQcAzwUzV7qQw= +github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= +github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= +github.com/googleapis/gax-go/v2 v2.4.0 h1:dS9eYAjhrE2RjmzYw2XAPvcXfmcQLtFEQWn0CR82awk= +github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= +github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= @@ -386,6 +449,8 @@ github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB7 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= +github.com/goware/prefixer v0.0.0-20160118172347-395022866408 h1:Y9iQJfEqnN3/Nce9cOegemcy/9Ai5k3huT6E80F3zaw= +github.com/goware/prefixer v0.0.0-20160118172347-395022866408/go.mod h1:PE1ycukgRPJ7bJ9a1fdfQ9j8i/cEcRAoLZzbxYpNB/s= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= @@ -452,6 +517,8 @@ github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= +github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef h1:A9HsByNhogrvm9cWb28sjiS3i7tcKCkflWFEkHfuAgM= +github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= @@ -628,6 +695,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034= github.com/opencontainers/image-spec v1.1.0-rc2/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= +github.com/opencontainers/runc v1.1.2 h1:2VSZwLx5k/BfsBxMMipG/LYUnmqOD/BPkIVgQUcTlLw= +github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -793,6 +862,10 @@ go.etcd.io/etcd/api/v3 v3.5.5 h1:BX4JIbQ7hl7+jL+g+2j5UAr0o1bctCm6/Ct+ArBGkf0= go.etcd.io/etcd/api/v3 v3.5.5/go.mod h1:KFtNaxGDw4Yx/BA4iPPwevUTAuqcsPxzyX8PHydchN8= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= +go.mozilla.org/gopgagent v0.0.0-20170926210634-4d7ea76ff71a h1:N7VD+PwpJME2ZfQT8+ejxwA4Ow10IkGbU0MGf94ll8k= +go.mozilla.org/gopgagent v0.0.0-20170926210634-4d7ea76ff71a/go.mod h1:YDKUvO0b//78PaaEro6CAPH6NqohCmL2Cwju5XI2HoE= +go.mozilla.org/sops/v3 v3.7.3 h1:CYx02LnWTATWv6NqWJIt4JCKVKSnGV+MsRiDpvwWQhg= +go.mozilla.org/sops/v3 v3.7.3/go.mod h1:AutdccISG5Nt/faUigaKPU9aGmhyZuCyUiSx5YCa1O8= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -910,11 +983,18 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -930,7 +1010,15 @@ golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.1.0 h1:isLCZuhj4v+tYv7eskaN4v/TM+A1begWWgyVJDdl1+Y= golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -944,6 +1032,7 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1003,19 +1092,34 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= @@ -1093,7 +1197,11 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE= golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= @@ -1101,6 +1209,10 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f h1:uF6paiQQebLeSXkrTqHqz0MXhXXS1KgF41eUdBNvxK0= +golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY= gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= @@ -1125,6 +1237,26 @@ google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjR google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= +google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= +google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= +google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= +google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= +google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= +google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= +google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= +google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= +google.golang.org/api v0.96.0 h1:F60cuQPJq7K7FzsxMYHAUJSiXh2oKctHxBMbDygxhfM= +google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1174,9 +1306,47 @@ google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= +google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71 h1:GEgb2jF5zxsFJpJfg9RoDDWm7tiwc/DDSTE2BtLUkXU= google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -1198,11 +1368,22 @@ google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA5 google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= +google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.50.1 h1:DS/BukOZWp8s6p4Dt/tOaJaTQyPyOoCcrjroHuCeLzY= google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1216,6 +1397,7 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= @@ -1234,6 +1416,8 @@ gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0= +gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -1251,6 +1435,7 @@ gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= helm.sh/helm/v3 v3.10.1 h1:uTnNlYx8QcTSNA4ZJ50Llwife4CSohUY4ehumyVf2QE= diff --git a/pkg/vars/sops_test_resources/.sops.yaml b/pkg/vars/sops_test_resources/.sops.yaml new file mode 100644 index 000000000..9071c5ce4 --- /dev/null +++ b/pkg/vars/sops_test_resources/.sops.yaml @@ -0,0 +1,3 @@ +creation_rules: + - path_regex: test.yaml + age: age1q69g6x9jcz7lgnrgdxemystmhec4e8cxlzz45x0tt6t7dddp2ppsnkdxe7 diff --git a/pkg/vars/sops_test_resources/README.md b/pkg/vars/sops_test_resources/README.md new file mode 100644 index 000000000..d9d669b06 --- /dev/null +++ b/pkg/vars/sops_test_resources/README.md @@ -0,0 +1,4 @@ +To edit the test.yaml file, run: +```sh +SOPS_AGE_KEY_FILE=$(pwd)/test-key.txt sops test.yaml +``` diff --git a/pkg/vars/sops_test_resources/embed.go b/pkg/vars/sops_test_resources/embed.go new file mode 100644 index 000000000..de606a4eb --- /dev/null +++ b/pkg/vars/sops_test_resources/embed.go @@ -0,0 +1,6 @@ +package sops_test_resources + +import "embed" + +//go:embed all:* +var TestResources embed.FS diff --git a/pkg/vars/sops_test_resources/test-key.txt b/pkg/vars/sops_test_resources/test-key.txt new file mode 100644 index 000000000..3f4a2f064 --- /dev/null +++ b/pkg/vars/sops_test_resources/test-key.txt @@ -0,0 +1,3 @@ +# created: 2022-11-10T11:37:19+01:00 +# public key: age1q69g6x9jcz7lgnrgdxemystmhec4e8cxlzz45x0tt6t7dddp2ppsnkdxe7 +AGE-SECRET-KEY-1SKGMW8JCGJ5U0UZZ0WC0583NZTRZ58F02A2KXKUULLNRM07XZ9SQ228TEW diff --git a/pkg/vars/sops_test_resources/test.yaml b/pkg/vars/sops_test_resources/test.yaml new file mode 100644 index 000000000..c3a287063 --- /dev/null +++ b/pkg/vars/sops_test_resources/test.yaml @@ -0,0 +1,22 @@ +test1: + test2: ENC[AES256_GCM,data:t/0=,iv:O061yXXLVkr36SYhF5fHHVdq+uzIbuGWMo1exsR0XK0=,tag:x/adK5sLVqdm233w/oWWiw==,type:int] +sops: + kms: [] + gcp_kms: [] + azure_kv: [] + hc_vault: [] + age: + - recipient: age1q69g6x9jcz7lgnrgdxemystmhec4e8cxlzz45x0tt6t7dddp2ppsnkdxe7 + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBPUzZtaTl5YlhaOWRoRG9j + Vnc5UlpTUHZVUkcwbmgzM2lmTjJob3lQaURrCi9MSmNsZ3ltUkV4dkZScEJ0cks4 + cmlIRUgvWDBnQVpDdTRudXFMNlc3TFEKLS0tIGJHZEowODRNaittT3htZ29CSFJn + bjFkMEg4cCttZXcwdURNdnVaZ2EyUlEKTaJsay+v0TXNr9NIxFyvTXrcHgSV63Si + YiVg5/ovqVUd26lPvsQwuS9iu5BQaZAzA5uZdw4Ain+SQM9iIf8zTQ== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2022-11-10T11:17:45Z" + mac: ENC[AES256_GCM,data:mr9GReeEuGOobT0DwL2VBPuapT1Hc6tkhG0djYBybHKIpYaRzqz1RLdxeMHlsIMar6tbKHqRXC4euwXpzArUEhdJXzWMnBbiimibDauvH/Zf5Yf4LSYM0OyCttOfxYKejwtnBJ7Lm4C0udciPcCGKDBpql+yTX9U/vOqPQt0/vA=,iv:2jpr2QLN/zyv1QBXsVQpduDh16i2q/Sr4xTF+ojwXq0=,tag:CCl/Il59RnYe2HuoDJycZg==,type:str] + pgp: [] + unencrypted_suffix: _unencrypted + version: 3.7.3 diff --git a/pkg/vars/vars_loader.go b/pkg/vars/vars_loader.go index b0ae45db4..0dd5e66eb 100644 --- a/pkg/vars/vars_loader.go +++ b/pkg/vars/vars_loader.go @@ -14,6 +14,9 @@ import ( "github.com/kluctl/kluctl/v2/pkg/vars/aws" "github.com/kluctl/kluctl/v2/pkg/vars/vault" "github.com/kluctl/kluctl/v2/pkg/yaml" + "go.mozilla.org/sops/v3" + "go.mozilla.org/sops/v3/cmd/sops/formats" + "go.mozilla.org/sops/v3/decrypt" "k8s.io/apimachinery/pkg/runtime/schema" "os" "strings" @@ -102,8 +105,25 @@ func (v *VarsLoader) mergeVars(varsCtx *VarsCtx, newVars *uo.UnstructuredObject, } func (v *VarsLoader) loadFile(varsCtx *VarsCtx, path string, searchDirs []string, rootKey string) error { + rendered, err := varsCtx.RenderFile(path, searchDirs) + if err != nil { + return fmt.Errorf("failed to render vars file %s: %w", path, err) + } + + if yaml.IsMaybeSopsFile([]byte(rendered)) { + decrypted, err := decrypt.DataWithFormat([]byte(rendered), formats.FormatForPath(path)) + if err != nil && err != sops.MetadataNotFound { + return fmt.Errorf("failed to decrypt vars file %s: %w", path, err) + } else if err == nil { + rendered = string(decrypted) + } + } + newVars := uo.New() - err := varsCtx.RenderYamlFile(path, searchDirs, newVars) + err = yaml.ReadYamlString(rendered, newVars) + if err != nil { + return err + } if err != nil { return fmt.Errorf("failed to load vars from %s: %w", path, err) } diff --git a/pkg/vars/vars_loader_test.go b/pkg/vars/vars_loader_test.go index bf4e3085b..c7e758bdf 100644 --- a/pkg/vars/vars_loader_test.go +++ b/pkg/vars/vars_loader_test.go @@ -15,6 +15,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/vars/aws" "github.com/stretchr/testify/assert" + "go.mozilla.org/sops/v3/age" "io" corev1 "k8s.io/api/core/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -87,6 +88,25 @@ func TestVarsLoader_File(t *testing.T) { }) } +func TestVarsLoader_SopsFile(t *testing.T) { + d := newTestDir(t) + f, _ := sops_test_resources.TestResources.ReadFile("test.yaml") + key, _ := sops_test_resources.TestResources.ReadFile("test-key.txt") + _ = os.WriteFile(filepath.Join(d, "test.yaml"), f, 0o600) + + t.Setenv(age.SopsAgeKeyEnv, string(key)) + + testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { + err := vl.LoadVars(vc, &types.VarsSource{ + File: utils.StrPtr("test.yaml"), + }, []string{d}, "") + assert.NoError(t, err) + + v, _, _ := vc.Vars.GetNestedInt("test1", "test2") + assert.Equal(t, int64(42), v) + }) +} + func TestVarsLoader_FileWithLoad(t *testing.T) { d := newTestDir(t) _ = os.WriteFile(filepath.Join(d, "test.yaml"), []byte(`{"test1": {"test2": {{ load_template("test2.txt") }}}}`), 0o600) @@ -128,7 +148,7 @@ func TestVarsLoader_FileWithLoadNotExists(t *testing.T) { err := vl.LoadVars(vc, &types.VarsSource{ File: utils.StrPtr("test.yaml"), }, []string{d}, "") - assert.EqualError(t, err, "failed to load vars from test.yaml: template test3.txt not found") + assert.EqualError(t, err, "failed to render vars file test.yaml: template test3.txt not found") }) } diff --git a/pkg/yaml/yaml.go b/pkg/yaml/yaml.go index 89610c94a..0dde23fad 100644 --- a/pkg/yaml/yaml.go +++ b/pkg/yaml/yaml.go @@ -285,3 +285,7 @@ func Exists(p string) bool { p = FixPathExt(p) return utils.Exists(p) } + +func IsMaybeSopsFile(s []byte) bool { + return bytes.Index(s, []byte("sops")) != -1 +} From 86c8f83dfd244009b142b1187f8153f3f0cbd081 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 10 Nov 2022 15:21:12 +0100 Subject: [PATCH 1154/2916] feat: Support sops in kustomize resources --- pkg/deployment/deployment_item.go | 39 +++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 490cec5f9..d42afe526 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -8,6 +8,9 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" + "go.mozilla.org/sops/v3" + "go.mozilla.org/sops/v3/cmd/sops/formats" + "go.mozilla.org/sops/v3/decrypt" "io/fs" "k8s.io/apimachinery/pkg/runtime/schema" "os" @@ -483,6 +486,31 @@ func (di *DeploymentItem) prepareKustomizationYaml() (*uo.UnstructuredObject, er return ky, nil } +func (di *DeploymentItem) decryptSopsFile(p string) error { + file, err := os.ReadFile(p) + if err != nil { + return fmt.Errorf("failed to read file %s: %w", p, err) + } + + if !yaml.IsMaybeSopsFile(file) { + return nil + } + + decrypted, err := decrypt.DataWithFormat(file, formats.FormatForPath(p)) + if err == sops.MetadataNotFound { + // not encrypted, so bail out + return nil + } else if err != nil { + return fmt.Errorf("failed to decrypt file %s: %w", p, err) + } + + err = os.WriteFile(p, decrypted, 0o600) + if err != nil { + return fmt.Errorf("failed to save decrypted file %s: %w", p, err) + } + return nil +} + func (di *DeploymentItem) buildKustomize() error { if di.dir == nil { return nil @@ -499,6 +527,17 @@ func (di *DeploymentItem) buildKustomize() error { return err } + resources, _, _ := ky.GetNestedStringList("resources") + for _, r := range resources { + p := filepath.Join(di.RenderedDir, r) + if utils.IsFile(p) { + err = di.decryptSopsFile(p) + if err != nil { + return err + } + } + } + rm, err := utils.SecureBuildKustomization(di.RenderedSourceRootDir, di.RenderedDir, true) if err != nil { return err From 3e0a1b0af658891f0417714db1dc01f93802ac92 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 10 Nov 2022 16:09:10 +0100 Subject: [PATCH 1155/2916] tests: Add sops tests --- e2e/project.go | 22 +++-- e2e/sops_test.go | 83 +++++++++++++++++++ pkg/vars/sops_test_resources/.sops.yaml | 3 +- pkg/vars/sops_test_resources/README.md | 1 + .../sops_test_resources/test-configmap.yaml | 26 ++++++ 5 files changed, 125 insertions(+), 10 deletions(-) create mode 100644 e2e/sops_test.go create mode 100644 pkg/vars/sops_test_resources/test-configmap.yaml diff --git a/e2e/project.go b/e2e/project.go index f68025aef..2e137e841 100644 --- a/e2e/project.go +++ b/e2e/project.go @@ -100,6 +100,10 @@ func (p *testProject) updateYaml(path string, update func(o *uo.UnstructuredObje }, message) } +func (p *testProject) updateFile(path string, update func(f string) (string, error), message string) { + p.gitServer.UpdateFile(p.getKluctlProjectRepo(), path, update, message) +} + func (p *testProject) getDeploymentYaml(dir string) *uo.UnstructuredObject { o, err := uo.FromFile(filepath.Join(p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()), dir, "deployment.yml")) if err != nil { @@ -284,18 +288,20 @@ func (p *testProject) addKustomizeResources(dir string, resources []kustomizeRes l, _, _ := o.GetNestedList("resources") for _, r := range resources { l = append(l, r.name) - x := p.convertInterfaceToList(r.content) fileName := r.fileName if fileName == "" { fileName = r.name } - err := yaml.WriteYamlAllFile(filepath.Join(p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()), dir, fileName), x) - if err != nil { - return err - } - _, err = wt.Add(filepath.Join(dir, fileName)) - if err != nil { - return err + if r.content != nil { + x := p.convertInterfaceToList(r.content) + err := yaml.WriteYamlAllFile(filepath.Join(p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()), dir, fileName), x) + if err != nil { + return err + } + _, err = wt.Add(filepath.Join(dir, fileName)) + if err != nil { + return err + } } } o.SetNestedField(l, "resources") diff --git a/e2e/sops_test.go b/e2e/sops_test.go new file mode 100644 index 000000000..59e352b82 --- /dev/null +++ b/e2e/sops_test.go @@ -0,0 +1,83 @@ +package e2e + +import ( + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/vars/sops_test_resources" + "go.mozilla.org/sops/v3/age" + "testing" +) + +func TestSopsVars(t *testing.T) { + key, _ := sops_test_resources.TestResources.ReadFile("test-key.txt") + t.Setenv(age.SopsAgeKeyEnv, string(key)) + + k := defaultCluster1 + + p := &testProject{} + p.init(t, k, "sops-vars") + + createNamespace(t, k, p.projectName) + + p.updateTarget("test", nil) + + addConfigMapDeployment(p, "cm", map[string]string{ + "v1": "{{ test1.test2 }}", + }, resourceOpts{ + name: "cm", + namespace: p.projectName, + }) + p.updateDeploymentYaml("", func(o *uo.UnstructuredObject) error { + _ = o.SetNestedField([]map[string]any{ + { + "file": "encrypted-vars.yaml", + }, + }, "vars") + return nil + }) + + p.updateFile("encrypted-vars.yaml", func(f string) (string, error) { + b, _ := sops_test_resources.TestResources.ReadFile("test.yaml") + return string(b), nil + }, "") + + p.KluctlMust("deploy", "--yes", "-t", "test") + + cm := assertConfigMapExists(t, k, p.projectName, "cm") + assertNestedFieldEquals(t, cm, map[string]any{ + "v1": "42", + }, "data") +} + +func TestSopsResources(t *testing.T) { + key, _ := sops_test_resources.TestResources.ReadFile("test-key.txt") + t.Setenv(age.SopsAgeKeyEnv, string(key)) + + k := defaultCluster1 + + p := &testProject{} + p.init(t, k, "sops-resources") + + createNamespace(t, k, p.projectName) + + p.updateTarget("test", nil) + p.updateDeploymentYaml("", func(o *uo.UnstructuredObject) error { + _ = o.SetNestedField(p.projectName, "overrideNamespace") + return nil + }) + + p.addKustomizeDeployment("cm", []kustomizeResource{ + {name: "encrypted-cm.yaml"}, + }, nil) + + p.updateFile("cm/encrypted-cm.yaml", func(f string) (string, error) { + b, _ := sops_test_resources.TestResources.ReadFile("test-configmap.yaml") + return string(b), nil + }, "") + + p.KluctlMust("deploy", "--yes", "-t", "test") + + cm := assertConfigMapExists(t, k, p.projectName, "encrypted-cm") + assertNestedFieldEquals(t, cm, map[string]any{ + "a": "b", + }, "data") +} diff --git a/pkg/vars/sops_test_resources/.sops.yaml b/pkg/vars/sops_test_resources/.sops.yaml index 9071c5ce4..73232ad66 100644 --- a/pkg/vars/sops_test_resources/.sops.yaml +++ b/pkg/vars/sops_test_resources/.sops.yaml @@ -1,3 +1,2 @@ creation_rules: - - path_regex: test.yaml - age: age1q69g6x9jcz7lgnrgdxemystmhec4e8cxlzz45x0tt6t7dddp2ppsnkdxe7 + - age: age1q69g6x9jcz7lgnrgdxemystmhec4e8cxlzz45x0tt6t7dddp2ppsnkdxe7 diff --git a/pkg/vars/sops_test_resources/README.md b/pkg/vars/sops_test_resources/README.md index d9d669b06..f25a10769 100644 --- a/pkg/vars/sops_test_resources/README.md +++ b/pkg/vars/sops_test_resources/README.md @@ -1,4 +1,5 @@ To edit the test.yaml file, run: ```sh SOPS_AGE_KEY_FILE=$(pwd)/test-key.txt sops test.yaml +SOPS_AGE_KEY_FILE=$(pwd)/test-key.txt sops test-configmap.yaml ``` diff --git a/pkg/vars/sops_test_resources/test-configmap.yaml b/pkg/vars/sops_test_resources/test-configmap.yaml new file mode 100644 index 000000000..412303b43 --- /dev/null +++ b/pkg/vars/sops_test_resources/test-configmap.yaml @@ -0,0 +1,26 @@ +apiVersion: ENC[AES256_GCM,data:P+0=,iv:BI/ZR9fS+Pt26Ert1Bm+CgEPba/rN149lhBvMJdrC3M=,tag:/vMKi5bqzS7RiYlHV6ES0w==,type:str] +kind: ENC[AES256_GCM,data:AdJ37WNulWCn,iv:HcLZZEUbzDw0Y/eXaJ/1Lzr/lJ1wexXvq0sf+WRTgAw=,tag:3SpRc9DHXcdINxsBZAB4aw==,type:str] +metadata: + name: ENC[AES256_GCM,data:JzAbIoxlaC/W+2ng,iv:Wiv9cFlmOJov5HL8nWwDNUg+GAsq2NRvyWM9OK5SNCA=,tag:/N+sO//QpnGbcxfq0OxDlg==,type:str] +data: + a: ENC[AES256_GCM,data:Gg==,iv:8cfbT7VXyWdQxWWAB84+RhdEfQ/QYCXEtdQBVtL4dGg=,tag:+EYsouHQU5jXyAMTnLAJHg==,type:str] +sops: + kms: [] + gcp_kms: [] + azure_kv: [] + hc_vault: [] + age: + - recipient: age1q69g6x9jcz7lgnrgdxemystmhec4e8cxlzz45x0tt6t7dddp2ppsnkdxe7 + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSArZ05WY2hiNFdOcnJyYThr + QXZTTXFGVWJtYzcvakRBbzFpRnFwWGVPVjNnCkYyUW9qMEx2K25NbFVYY3d6NWZ3 + YnU4ZGpnamYwWGxrb2ZTandVQXNPR2sKLS0tIGZ2cnZZNXRKRnI2TVdVV1Z0eUsx + V1A2MXp1V3Z3VHUzVzlQc1dISkpmeVUKRNruIW7lGYGZ2zP2bJ/zrfB5ezktDPSU + I21uQv0OMYxCBBxVGCwqGm2LEG9jJZR+8yV7QTQqH4x/G3/V+zFFEA== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2022-11-10T14:53:10Z" + mac: ENC[AES256_GCM,data:Qxl1mPN5nGaH9/K+RGuItcHou7tbPrd1p37067nKoxPPF2a2o7kJV6FfJsB6tUYWaEHZMMseE2Gl5mJ1H8m6mmZBjx+DiBBoGmAh719PJ7kffDYFB0trk2Ysh1TQubvy7lv8pPCo7ruVhfoz9/oUTyoFquD+70f/xFa3nH0HPM8=,iv:5NnE8CfRy4Vz95CxQW5omkTsj+i6LDWzuAWIfzF5qOg=,tag:+dyFsFfsJj+dojMiDRIb7w==,type:str] + pgp: [] + unencrypted_suffix: _unencrypted + version: 3.7.3 From 05fae7cad7d96c8c7660ae84c80841af5e813e23 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 10 Nov 2022 21:57:17 +0100 Subject: [PATCH 1156/2916] tests: Fix tests compilation --- pkg/vars/vars_loader_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/vars/vars_loader_test.go b/pkg/vars/vars_loader_test.go index c7e758bdf..95db5f20f 100644 --- a/pkg/vars/vars_loader_test.go +++ b/pkg/vars/vars_loader_test.go @@ -14,6 +14,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/vars/aws" + "github.com/kluctl/kluctl/v2/pkg/vars/sops_test_resources" "github.com/stretchr/testify/assert" "go.mozilla.org/sops/v3/age" "io" From fb17e29216ed48632e724062f1d1f58004e5ebfe Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 10 Nov 2022 21:58:58 +0100 Subject: [PATCH 1157/2916] docs: Add docs for SOPS support --- docs/reference/deployments/README.md | 9 ++-- docs/reference/deployments/helm.md | 2 +- docs/reference/deployments/hooks.md | 2 +- docs/reference/deployments/readiness.md | 2 +- docs/reference/deployments/sops.md | 70 +++++++++++++++++++++++++ docs/reference/deployments/tags.md | 2 +- 6 files changed, 79 insertions(+), 8 deletions(-) create mode 100644 docs/reference/deployments/sops.md diff --git a/docs/reference/deployments/README.md b/docs/reference/deployments/README.md index ede5bdaa1..b54534e4f 100644 --- a/docs/reference/deployments/README.md +++ b/docs/reference/deployments/README.md @@ -15,10 +15,11 @@ description: > 2. [Kustomize Integration](./kustomize.md) 3. [Container Images](./images.md) 4. [Helm Integration](./helm.md) -5. [Hooks](./hooks.md) -6. [Readiness](./readiness.md) -7. [Tags](./tags.md) -8. [Annotations](./annotations) +5. [SOPS Integration](./sops.md) +6. [Hooks](./hooks.md) +7. [Readiness](./readiness.md) +8. [Tags](./tags.md) +9. [Annotations](./annotations) A deployment project is collection of deployment items and sub-deployments. Deployment items are usually [Kustomize](./kustomize.md) deployments, but can also integrate [Helm Charts](./helm.md). diff --git a/docs/reference/deployments/helm.md b/docs/reference/deployments/helm.md index 75016f9e8..c5ac21956 100644 --- a/docs/reference/deployments/helm.md +++ b/docs/reference/deployments/helm.md @@ -3,7 +3,7 @@ --- title: "Helm Integration" linkTitle: "Helm Integration" -weight: 3 +weight: 4 description: > How Helm is integrated into Kluctl. --- diff --git a/docs/reference/deployments/hooks.md b/docs/reference/deployments/hooks.md index b9f5879c2..34567de06 100644 --- a/docs/reference/deployments/hooks.md +++ b/docs/reference/deployments/hooks.md @@ -3,7 +3,7 @@ --- title: "Hooks" linkTitle: "Hooks" -weight: 4 +weight: 6 description: > Kluctl hooks. --- diff --git a/docs/reference/deployments/readiness.md b/docs/reference/deployments/readiness.md index ec8020bb0..ff3197659 100644 --- a/docs/reference/deployments/readiness.md +++ b/docs/reference/deployments/readiness.md @@ -3,7 +3,7 @@ --- title: "Readiness" linkTitle: "Readiness" -weight: 5 +weight: 7 description: Definition of readiness. --- diff --git a/docs/reference/deployments/sops.md b/docs/reference/deployments/sops.md new file mode 100644 index 000000000..1b29dcf0e --- /dev/null +++ b/docs/reference/deployments/sops.md @@ -0,0 +1,70 @@ + + +# SOPS Integration + +Kluctl integrates natively with [SOPS](https://github.com/mozilla/sops). Kluctl is able to decrypt all resources +referenced by [Kustomize](./kustomize.md) deployment items (including [simple deployments](./deployment-yml.md#simple-deployments)). +In addition, Kluctl will also decrypt all variable sources of the types [file](../templating/variable-sources.md#file) +and [git](../templating/variable-sources.md#git). + +Kluctl assumes that you have setup sops as usual so that it knows how to decrypt these files. + +## Only encrypting Secrets's data + +To only encrypt the `data` and `stringData` fields of Kubernetes secrets, use a `.sops.yaml` configuration file that +`encrypted_regex` to filter encrypted fields: + +``` +creation_rules: + - path_regex: .*.yaml + encrypted_regex: ^(data|stringData)$ +``` + +## Combining templating and SOPS + +As an alternative, you can split secret values and the resulting Kubernetes resources into two different places and then +use templating to use the secret values wherever needed. Example: + +Write the following content into `secrets/my-secrets.yaml`: + +```yaml +secrets: + mySecret: secret-value +``` + +And encrypt it with SOPS: + +```shell +$ sops -e -i secrets/my-secrets.yaml +``` + +Add this [variables source](../templating/variable-sources.md) to one of your [deployments](./deployment-yml.md): + +```yaml +vars: + - file: secrets/my-secrets.yaml + +deployments: +- ... +``` + +Then, in one of your deployment items define the following `Secret`: + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: my-secret + namespace: default +stringData: + secret: "{{ secrets.mySecret }}" +``` diff --git a/docs/reference/deployments/tags.md b/docs/reference/deployments/tags.md index 6bbfe18f4..b1aa08afe 100644 --- a/docs/reference/deployments/tags.md +++ b/docs/reference/deployments/tags.md @@ -3,7 +3,7 @@ --- title: "Tags" linkTitle: "Tags" -weight: 6 +weight: 8 --- --> From 85e0bdc658b1fb0b812ff0c839760d45810135d2 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 10 Nov 2022 22:12:13 +0100 Subject: [PATCH 1158/2916] docs: Remove all references to sealed secrets in docs --- docs/concepts.md | 12 +- docs/reference/README.md | 3 +- docs/reference/commands/README.md | 3 +- docs/reference/commands/seal.md | 45 ----- docs/reference/deployments/deployment-yml.md | 8 - docs/reference/deployments/helm.md | 2 +- docs/reference/kluctl-project/README.md | 28 +--- .../kluctl-project/secrets-config/README.md | 78 --------- .../kluctl-project/targets/README.md | 38 +---- .../kluctl-project/targets/dynamic-targets.md | 15 -- docs/reference/sealed-secrets.md | 157 ------------------ .../templating/predefined-variables.md | 4 - docs/reference/templating/variable-sources.md | 6 +- 13 files changed, 10 insertions(+), 389 deletions(-) delete mode 100644 docs/reference/commands/seal.md delete mode 100644 docs/reference/kluctl-project/secrets-config/README.md delete mode 100644 docs/reference/sealed-secrets.md diff --git a/docs/concepts.md b/docs/concepts.md index 2967356fe..ef99583dd 100644 --- a/docs/concepts.md +++ b/docs/concepts.md @@ -12,7 +12,7 @@ weight: 10 These are some core concepts in Kluctl. ## Kluctl project -The kluctl project defines targets and secret sources. +The kluctl project defines targets. It is defined via the [.kluctl.yaml](./reference/kluctl-project) configuration file. ## Targets @@ -39,16 +39,6 @@ through a templating engine. The [templating engine](./reference/templating) allows simple variable substitution and also complex control structures (if/else, for loops, ...). -## Secrets -Secrets are loaded from [external sources](./reference/kluctl-project) and are only available -while [sealing](./reference/sealed-secrets.md). After the sealing process, only the public-key encrypted -sealed secrets are available. - -## Sealed Secrets -[Sealed Secrets](./reference/sealed-secrets.md) are based on -[Bitnami's sealed-secrets controller](https://github.com/bitnami-labs/sealed-secrets). Kluctl offers integration of -sealed secrets through the `seal` command. Kluctl allows managing multiple sets of sealed secrets for multiple targets. - ## Unified CLI The CLI of kluctl is designed to be unified/consistent as much as possible. Most commands are centered around targets and thus require you to specify the target name (via `-t `). If you remember how one command works, it's easy diff --git a/docs/reference/README.md b/docs/reference/README.md index aec1d2c6b..19695290e 100644 --- a/docs/reference/README.md +++ b/docs/reference/README.md @@ -13,5 +13,4 @@ weight: 110 1. [.kluctl.yaml](./kluctl-project) 2. [Deployments](./deployments) -3. [Sealed Secrets](./sealed-secrets.md) -4. [Kluctl Commands](./commands) +3. [Kluctl Commands](./commands) diff --git a/docs/reference/commands/README.md b/docs/reference/commands/README.md index 1fe1a1923..8a3683520 100644 --- a/docs/reference/commands/README.md +++ b/docs/reference/commands/README.md @@ -32,5 +32,4 @@ Individual commands are documented in sub-sections. 10. [poke-images](./poke-images.md) 11. [prune](./prune.md) 12. [render](./render.md) -13. [seal](./seal.md) -14. [validate](./validate.md) +13. [validate](./validate.md) diff --git a/docs/reference/commands/seal.md b/docs/reference/commands/seal.md deleted file mode 100644 index 4ec5fe457..000000000 --- a/docs/reference/commands/seal.md +++ /dev/null @@ -1,45 +0,0 @@ - - -## Command - -Usage: kluctl seal [flags] - -Seal secrets based on target's sealingConfig -Loads all secrets from the specified secrets sets from the target's sealingConfig and -then renders the target, including all files with the '.sealme' extension. Then runs -kubeseal on each '.sealme' file and stores secrets in the directory specified by -'--local-sealed-secrets', using the outputPattern from your deployment project. - -If no '--target' is specified, sealing is performed for all targets. - - - -See [sealed-secrets](../sealed-secrets.md) for more details. - -## Arguments -The following sets of arguments are available: -1. [project arguments](./common-arguments.md#project-arguments) (except `-a`) - -In addition, the following arguments are available: - -``` -Misc arguments: - Command specific arguments. - - --cert-file string Use the given certificate for sealing instead of requesting it from the - sealed-secrets controller - --force-reseal Lets kluctl ignore secret hashes found in already sealed secrets and thus forces - resealing of those. - --offline-kubernetes Run seal in offline mode, meaning that it will not try to connect the target cluster - -``` - diff --git a/docs/reference/deployments/deployment-yml.md b/docs/reference/deployments/deployment-yml.md index e1aac4061..7a0be122b 100644 --- a/docs/reference/deployments/deployment-yml.md +++ b/docs/reference/deployments/deployment-yml.md @@ -16,9 +16,6 @@ The `deployment.yaml` file is the entrypoint for the deployment project. Include An example `deployment.yaml` looks like this: ```yaml -sealedSecrets: - outputPattern: "{{ cluster.name }}/{{ args.environment }}" - deployments: - path: nginx - path: my-app @@ -34,11 +31,6 @@ args: The following sub-chapters describe the available fields in the `deployment.yaml` -## sealedSecrets -`sealedSecrets` configures how sealed secrets are stored while sealing and located while rendering. -See [Sealed Secrets](../sealed-secrets.md#outputpattern-and-location-of-stored-sealed-secrets) -for details. - ## deployments `deployments` is a list of deployment items. Multiple deployment types are supported, which is documented further down. diff --git a/docs/reference/deployments/helm.md b/docs/reference/deployments/helm.md index 75016f9e8..f0aaf8403 100644 --- a/docs/reference/deployments/helm.md +++ b/docs/reference/deployments/helm.md @@ -25,7 +25,7 @@ hands over the rendered yaml to [kustomize](https://kustomize.io/). Rendering is `helm-values.yaml`, which contains the necessary values to configure the Helm Chart. The resulting rendered yaml is then referred by your `kustomization.yaml`, from which point on the -[kustomize integration](../sealed-secrets.md#outputpattern-and-location-of-stored-sealed-secrets) +[kustomize integration](./kustomize.md) takes over. This means, that you can perform all desired customization (patches, namespace override, ...) as if you provided your own resources via yaml files. diff --git a/docs/reference/kluctl-project/README.md b/docs/reference/kluctl-project/README.md index 367e23778..f442ee3fb 100644 --- a/docs/reference/kluctl-project/README.md +++ b/docs/reference/kluctl-project/README.md @@ -11,10 +11,8 @@ description: > # Kluctl project -The `.kluctl.yaml` is the central configuration and entry point for your deployments. It defines where the actual -[deployment project](../deployments) is located, -where [sealed secrets](../sealed-secrets.md) and unencrypted secrets are localed and which targets are available to -invoke [commands](../commands) on. +The `.kluctl.yaml` is the central configuration and entry point for your deployments. It defines which targets are +available to invoke [commands](../commands) on. ## Example @@ -27,37 +25,16 @@ targets: context: test.example.com args: environment_name: dev - sealingConfig: - secretSets: - - non-prod # test cluster, test env - name: test context: test.example.com args: environment_name: test - sealingConfig: - secretSets: - - non-prod # prod cluster, prod env - name: prod context: prod.example.com args: environment_name: prod - sealingConfig: - secretSets: - - prod - -# This is only required if you actually need sealed secrets -secretsConfig: - secretSets: - - name: prod - vars: - # This file should not be part of version control! - - file: .secrets-prod.yaml - - name: non-prod - vars: - # This file should not be part of version control! - - file: .secrets-non-prod.yaml ``` ## Allowed fields @@ -65,4 +42,3 @@ secretsConfig: Please check the following sub-sections of this section to see which fields are allowed at the root level of `.kluctl.yaml`. 1. [targets](./targets) -2. [secretsConfig](./secrets-config) diff --git a/docs/reference/kluctl-project/secrets-config/README.md b/docs/reference/kluctl-project/secrets-config/README.md deleted file mode 100644 index 8e1387af2..000000000 --- a/docs/reference/kluctl-project/secrets-config/README.md +++ /dev/null @@ -1,78 +0,0 @@ - - -# secretsConfig - -This configures how secrets are retrieved while sealing. It is basically a list of named secret sets which can be -referenced from targets. - -It has the following form: -```yaml -... -secretsConfig: - secretSets: - - name: - vars: - - ... - sealedSecrets: ... -... -``` - -## secretSets - -Each `secretSets` entry has the following fields. - -### name -This field specifies the name of the secret set. The name can be used in targets to refer to this secret set. - -### vars -A list of variables sources. Check the documentation of -[variables sources](../../templating/variable-sources.md) for details. - -Each variables source must have a root dictionary with the name `secrets` and all the actual secret values -below that dictionary. Every other root key will be ignored. - -Example variables file: - -```yaml -secrets: - secret: value1 - nested: - secret: value2 - list: - - a - - b -... -``` - -## sealedSecrets -This field specifies the configuration for sealing. It has the following form: - -```yaml -... -secretsConfig: - secretSets: ... - sealedSecrets: - bootstrap: true - namespace: kube-system - controllerName: sealed-secrets-controller -... -``` - -### bootstrap -Controls whether kluctl should bootstrap the initial private key in case the controller is not yet installed on -the target cluster. Defaults to `true`. - -### namespace -Specifies the namespace where the sealed-secrets controller is installed. Defaults to "kube-system". - -### controllerName -Specifies the name of the sealed-secrets controller. Defaults to "sealed-secrets-controller". diff --git a/docs/reference/kluctl-project/targets/README.md b/docs/reference/kluctl-project/targets/README.md index a9d0de7a5..74673d86d 100644 --- a/docs/reference/kluctl-project/targets/README.md +++ b/docs/reference/kluctl-project/targets/README.md @@ -13,7 +13,7 @@ description: > Specifies a list of targets for which commands can be invoked. A target puts together environment/target specific configuration and the target cluster. Multiple targets can exist which target the same cluster but with differing -configuration (via `args`). Target entries also specifies which secrets to use while [sealing](../../sealed-secrets.md). +configuration (via `args`). Each value found in the target definition is rendered with a simple Jinja2 context that only contains the target itself. The rendering process is retried 10 times until it finally succeeds, allowing you to reference @@ -32,9 +32,6 @@ targets: images: - image: my-image resultImage: my-image:1.2.3 - sealingConfig: - secretSets: - - ... ``` @@ -61,36 +58,3 @@ The format is identical to the [fixed images file](../../deployments/images.md#f The fixed images specified in the [dynamic target config](../../kluctl-project/targets/dynamic-targets.md#images) have higher priority. - -## sealingConfig -This field configures how sealing is performed when the [seal command](../../commands/seal.md) is invoked for this target. -It has the following form: - -```yaml -targets: -... -- name: - ... - sealingConfig: - args: - arg1: - certFile: - dynamicSealing: - secretSets: - - -``` - -### args -This field allows adding extra arguments to the target args. These are only used while sealing and may override -arguments which are already configured for the target. - -### certFile -Optional path to a local (inside your project) public certificate used for sealing. Such a certificate can be fetched -from the sealed-secrets controller using `kubeseal --fetch-cert`. - -### dynamicSealing -This field specifies weather sealing should happen per [dynamic target](./dynamic-targets.md) or only once. This -field is optional and defaults to `true`. - -### secretSets -This field specifies a list of secret set names, which all must exist in the [secretsConfig](../secrets-config). diff --git a/docs/reference/kluctl-project/targets/dynamic-targets.md b/docs/reference/kluctl-project/targets/dynamic-targets.md index 32fa6a1af..6b18cace4 100644 --- a/docs/reference/kluctl-project/targets/dynamic-targets.md +++ b/docs/reference/kluctl-project/targets/dynamic-targets.md @@ -35,10 +35,6 @@ targets: ref: refPattern: file: - sealingConfig: - dynamicSealing: - secretSets: - - ... ``` @@ -102,14 +98,3 @@ An optional list of fixed images, in the same format as in the normal [target im A simplified form of dynamic targets is to store target config inside the same directory/project as the `.kluctl.yaml`. This can be done by omitting `project`, `ref` and `refPattern` from `targetConfig` and only specify `file`. - -## A note on sealing - -When sealing dynamic targets, it is very likely that it is not known yet which dynamic targets will actually exist in -the future. This requires some special care when sealing secrets for these targets. Sealed secrets are usually namespace -scoped, which might need to be changed to cluster-wide scoping so that the same sealed secret can be deployed into -multiple targets (assuming you deploy to different namespaces for each target). When you do this, watch out to not -compromise security, e.g. by sealing production level secrets with a cluster-wide scope! - -It is also very likely required to set `target.sealingConfig.dynamicSealing` to `false`, so that sealing is only performed -once and not for all dynamic targets. \ No newline at end of file diff --git a/docs/reference/sealed-secrets.md b/docs/reference/sealed-secrets.md deleted file mode 100644 index cfd40a2d1..000000000 --- a/docs/reference/sealed-secrets.md +++ /dev/null @@ -1,157 +0,0 @@ - - -# Sealed Secrets - -kluctl has an integration for [sealed secrets](https://github.com/bitnami-labs/sealed-secrets), allowing you to -securely store secrets for multiple target clusters and/or environments inside version control. - -The integration consists of two parts: -1. Sealing of secrets -2. Automatically choosing and deploying the correct sealed secrets for a target - -## Requirements - -The Sealed Secrets integration relies on the [sealed-secrets operator](https://github.com/bitnami-labs/sealed-secrets) -being installed. Installing the operator is the responsibility of you (or whoever is managing/operating the cluster). - -Kluctl can however perform sealing of secrets without an existing sealed-secrets operator installation. This is solved -by automatically pre-provisioning a key onto the cluster that is compatible with the operator or by providing the -public certificate via `certFile` in the targets [sealingConfig](./kluctl-project/targets#certfile). - -## Sealing of .sealme files - -Sealing is done via the [seal command](./commands/seal.md). It must be done before the actual -deployment is performed. - -The `seal` command recursively searches for files that end with `.sealme`, renders them with the -[templating engine](./templating) engine. The rendered secret resource is then -converted/encrypted into a sealed secret. - -The `.sealme` files itself have to be [Kubernetes Secrets](https://kubernetes.io/docs/concepts/configuration/secret/), -but without any actual secret data inside. The secret data is referenced via templating variables and is expected to be -provided only at the time of sealing. This means, that the sensitive secret data must only be in clear text while sealing. -Afterwards the sealed secrets can be added to version control. - -Example file (the name could be for example `db-secrets.yaml.sealme`): -```yaml -kind: Secret -apiVersion: v1 -metadata: - name: db-secrets - namespace: {{ my.namespace.variable }} -stringData: - DB_URL: {{ secrets.database.url }} - DB_USERNAME: {{ secrets.database.username }} - DB_PASSWORD: {{ secrets.database.password }} -``` - -While sealing, the full templating context (same as in [templating](./templating)) is available. -Additionally, the global `secrets` object/variable is available which contains the sensitive secrets. - -## Secret Sources - -Secrets are only loaded while sealing. Available secret sets and sources are configured via -[.kluctl.yaml](./kluctl-project/secrets-config). The secrets used per target are configured via the -[secrets config](./kluctl-project/targets#secretsets) of the targets. - -## Using sealed secrets - -After sealing a secret, it can be used inside kustomize deployments. While deploying, kluctl will look for resources -included from `kustomization.yaml` which are not existent but for which a file with a `.sealme` extension exists. If such -a file is found, the appropriate sealed secrets is located based on the -[outputPattern](#outputpattern-and-location-of-stored-sealed-secrets). - -An example `kustomization.yaml`: -```yaml -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization - -resources: -# please note that we do not specify the .sealme suffix here -- db-secrets.yaml -- my-deployments.yaml -``` - -## outputPattern and location of stored sealed secrets - -It is possible to override the output pattern in the root [deployment project](./deployments). -The output pattern must be a template string that is rendered with the full -[templating context](./templating) available for the deployment.yaml. - -When manually specifying the outputPattern, ensure that it works well with multiple clusters and targets. You can -for example use the `{{ target.name }}` and `{{ cluster.name }}` inside the outputPattern. - -```yaml -# deployment.yaml in root directory -sealedSecrets: - outputPattern: "{{ cluster.name }}/{{ target.name }}" -``` - -The default outputPattern is simply `{{ target.name }}`, which should work well in most cases. - -The final storage location for the sealed secret is: - -`///` - -with: -* `base_dir`: The base directory for sealed secrets, which defaults to to the subdirectory `.sealed-secrets` in the kluctl project root - diretory. -* `rendered_output_pattern`: The rendered outputPattern as described above. -* `relative_sealme_file_dir`: The relative path from the deployment root directory. -* `file_name`: The filename of the sealed secret, excluding the `.sealme` extension. - -## Content Hashes and re-sealing -Sealed secrets are stored together with hashes of all individual secret entries. These hashes are then used to avoid -unnecessary re-sealing in future [seal](./commands/seal.md) invocations. If you want to force re-sealing, use the -[--force-reseal](./commands/seal.md) option. - -Hashing of secrets is done with bcrypt and the cluster id as salt. The cluster id is currently defined as the sha256 hash -of the cluster CA certificate. This will cause re-sealing of all secrets in case a cluster is set up from scratch -(which causes key from the sealed secrets operator to get wiped as well). - -## Clusters and namespaces -Sealed secrets are usually only decryptable by one cluster, simply because each cluster has its own set of randomly -generated public/private key pairs. This means, that a secret that was sealed for your production cluster can't be -unsealed on your test cluster. - -In addition, sealed secrets can be bound to a single namespace, making them undecryptable for any other namespace. -To limit a sealed secret to a namespace, simply fill the `metadata.namespace` field of the input secret (which is in -the `.sealme` file). This way, the sealed secret can only be deployed to a single namespace. - -You can also use [Scopes](https://github.com/bitnami-labs/sealed-secrets#scopes) to lift/limit restrictions. - -## Using reflectors/replicators -In case a sealed secrets needs to be deployed to more than one namespace, some form of replication must be used. You'd -then seal the secret for a single namespace and use a reflection/replication controller to reflect the unsealed secret -into one or multiple other namespaces. Example controllers that can accomplish this are the -[Mittwald kubernetes-reflector](https://github.com/mittwald/kubernetes-replicator) and the -[Emberstack Kubernetes Reflector](https://github.com/emberstack/kubernetes-reflector). - -Consider the following example (using the Mittwald replicator): -```yaml -kind: Secret -apiVersion: v1 -metadata: - name: db-secrets - namespace: {{ my.namespace.variable }} - annotations: - replicator.v1.mittwald.de/replicate-to: '{{ my.namespace.variable }}-.*' -stringData: - DB_URL: {{ secrets.database.url }} - DB_USERNAME: {{ secrets.database.username }} - DB_PASSWORD: {{ secrets.database.password }} -``` - -The above example would cause automatic replication into every namespace that matches the replicate-to pattern. - -Please watch out for security implications. In the above example, everyone who has the right to create a namespace that -matches the pattern will get access to the secret. diff --git a/docs/reference/templating/predefined-variables.md b/docs/reference/templating/predefined-variables.md index 951226f57..a547e1d86 100644 --- a/docs/reference/templating/predefined-variables.md +++ b/docs/reference/templating/predefined-variables.md @@ -26,7 +26,3 @@ This global object provides the dynamic images features described in [images](.. ### version This global object defines latest version filters for `images.get_image(...)`. See [images](../deployments/images.md) for details. - -### secrets -This global object is only available while [sealing](../sealed-secrets.md) and contains the loaded -secrets defined via the currently sealed target. diff --git a/docs/reference/templating/variable-sources.md b/docs/reference/templating/variable-sources.md index 2941e532b..ad6ad30c2 100644 --- a/docs/reference/templating/variable-sources.md +++ b/docs/reference/templating/variable-sources.md @@ -159,8 +159,8 @@ Kluctl currently supports BASIC and NTLM authentication. It will prompt for cred ### awsSecretsManager [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) integration. Loads a variables YAML from an AWS Secrets -Manager secret. The secret can either be specified via an ARN or via a secretName and region combination. An AWS -config profile can also be specified (which must exist while sealing). +Manager secret. The secret can either be specified via an ARN or via a secretName and region combination. An existing AWS +config profile can also be specified. The secrets stored in AWS Secrets manager must contain a valid yaml or json file. @@ -198,7 +198,7 @@ vars: path: secret/data/simple ``` -Before deploying or sealing please make sure that you have access to vault. You can do this for example by setting +Before deploying please make sure that you have access to vault. You can do this for example by setting the environment variable `VAULT_TOKEN`. ### systemEnvVars From 3e885a1e4e566d49e21baac6a4650cb8c7af8afe Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 10 Nov 2022 22:17:15 +0100 Subject: [PATCH 1159/2916] feat: Deprecate the SealedSecrets integration --- pkg/kluctl_project/project_load.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pkg/kluctl_project/project_load.go b/pkg/kluctl_project/project_load.go index 29ea9e871..c4a2b20b9 100644 --- a/pkg/kluctl_project/project_load.go +++ b/pkg/kluctl_project/project_load.go @@ -60,6 +60,19 @@ func (c *LoadedKluctlProject) loadKluctlProject() error { c.sealedSecretsDir = filepath.Join(c.ProjectDir, ".sealed-secrets") + sealedSecretsUsed := false + if c.Config.SecretsConfig != nil { + sealedSecretsUsed = true + } + for _, t := range c.Config.Targets { + if t.SealingConfig != nil { + sealedSecretsUsed = true + } + } + if sealedSecretsUsed { + status.Deprecation(c.ctx, "sealed-secrets", "The SealedSecrets integration is deprecated and will be completely removed in an upcoming version. Please switch to using the SOPS integration instead.") + } + s.Success() return nil From 86a7a4cb9e91d622f047f74466b1ae864e503ed9 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 11 Nov 2022 13:37:48 +0100 Subject: [PATCH 1160/2916] fix: Upgrade github.com/xanzy/ssh-agent to current master to fix crash See https://github.com/xanzy/ssh-agent/pull/11 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b422ea1b5..ba6357db2 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,7 @@ require ( github.com/spf13/viper v1.13.0 github.com/stretchr/testify v1.8.0 github.com/whilp/git-urls v1.0.0 - github.com/xanzy/ssh-agent v0.3.2 + github.com/xanzy/ssh-agent v0.3.3-0.20220920102508-0fa644ba07f4 golang.org/x/crypto v0.1.0 golang.org/x/net v0.1.0 golang.org/x/sync v0.1.0 diff --git a/go.sum b/go.sum index 7e295f7f2..7698848d5 100644 --- a/go.sum +++ b/go.sum @@ -767,8 +767,8 @@ github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6Ac github.com/whilp/git-urls v1.0.0 h1:95f6UMWN5FKW71ECsXRUd3FVYiXdrE7aX4NZKcPmIjU= github.com/whilp/git-urls v1.0.0/go.mod h1:J16SAmobsqc3Qcy98brfl5f5+e0clUvg1krgwk/qCfE= github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= -github.com/xanzy/ssh-agent v0.3.2 h1:eKj4SX2Fe7mui28ZgnFW5fmTz1EIr7ugo5s6wDxdHBM= -github.com/xanzy/ssh-agent v0.3.2/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +github.com/xanzy/ssh-agent v0.3.3-0.20220920102508-0fa644ba07f4 h1:3kHD8uZqiYes9JHdd3FzuyUbG10g9Bp9EOfqkSHIhg4= +github.com/xanzy/ssh-agent v0.3.3-0.20220920102508-0fa644ba07f4/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= From 12a50d0c29ec28444864c86fbeef0996f59fb79e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 11 Nov 2022 13:57:59 +0100 Subject: [PATCH 1161/2916] fix: Fix clearing of multiline status lines --- pkg/status/multiline/multiline.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pkg/status/multiline/multiline.go b/pkg/status/multiline/multiline.go index 399942828..8d1153b9e 100644 --- a/pkg/status/multiline/multiline.go +++ b/pkg/status/multiline/multiline.go @@ -6,6 +6,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils/term" "github.com/mattn/go-runewidth" "io" + "strings" "sync" "time" ) @@ -72,7 +73,9 @@ func (ml *MultiLinePrinter) Flush() { // Count the number of lines that need to be cleared. We need to take wrapping into account as well prevTotalLines := 0 for _, line := range ml.prevLines { - prevTotalLines += ml.countConsoleLines(line, tw) + for _, sl := range strings.Split(line, "\n") { + prevTotalLines += ml.countWrappedLines(sl, tw) + } } if prevTotalLines > 0 { ml.clearLines(prevTotalLines) @@ -93,7 +96,7 @@ func (ml *MultiLinePrinter) Flush() { } } -func (ml *MultiLinePrinter) countConsoleLines(s string, tw int) int { +func (ml *MultiLinePrinter) countWrappedLines(s string, tw int) int { s = stripansi.Strip(s) w := runewidth.StringWidth(s) cnt := 1 From bb421eb47ad24448b2ccc17c17093b2936e1238c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 11 Nov 2022 14:08:31 +0100 Subject: [PATCH 1162/2916] ci: Add dependabot support --- .github/dependabot.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..4143be270 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +version: 2 + +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + - package-ecosystem: "gomod" + directory: "/" + schedule: + interval: "daily" From 4ae9a5ee63cc9cc733a3276cbc27dc9dc2032ce2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Nov 2022 13:25:28 +0000 Subject: [PATCH 1163/2916] chore(deps): Bump actions/setup-go from 2 to 3 Bumps [actions/setup-go](https://github.com/actions/setup-go) from 2 to 3. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/setup-go dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/release.yml | 2 +- .github/workflows/tests.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bc3074b45..5d1e4aed2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,7 +21,7 @@ jobs: - name: Fetch all tags run: git fetch --force --tags - name: Set up Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v3 with: go-version: 1.19 - name: Setup QEMU diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index faa1a4ee5..771c00cc2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,7 +16,7 @@ jobs: uses: actions/checkout@v2 with: fetch-depth: 0 - - uses: actions/setup-go@v2 + - uses: actions/setup-go@v3 with: go-version: '1.19' - uses: actions/cache@v2 @@ -47,7 +47,7 @@ jobs: uses: actions/checkout@v2 with: fetch-depth: 0 - - uses: actions/setup-go@v2 + - uses: actions/setup-go@v3 with: go-version: '1.19' - uses: actions/cache@v2 @@ -84,7 +84,7 @@ jobs: steps: - name: Checkout uses: actions/checkout@v2 - - uses: actions/setup-go@v2 + - uses: actions/setup-go@v3 with: go-version: '1.19' - uses: actions/cache@v2 From 73eca1f83f627d466679d272d75260e5b5e3cdeb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Nov 2022 13:25:33 +0000 Subject: [PATCH 1164/2916] chore(deps): Bump docker/setup-qemu-action from 1 to 2 Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 1 to 2. - [Release notes](https://github.com/docker/setup-qemu-action/releases) - [Commits](https://github.com/docker/setup-qemu-action/compare/v1...v2) --- updated-dependencies: - dependency-name: docker/setup-qemu-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bc3074b45..af7579d5e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,7 +25,7 @@ jobs: with: go-version: 1.19 - name: Setup QEMU - uses: docker/setup-qemu-action@v1 + uses: docker/setup-qemu-action@v2 - name: Setup Docker Buildx id: buildx uses: docker/setup-buildx-action@v1 From 39273d96af5e36c328b1539425b4e0ac82033cef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Nov 2022 13:25:42 +0000 Subject: [PATCH 1165/2916] chore(deps): Bump docker/login-action from 1 to 2 Bumps [docker/login-action](https://github.com/docker/login-action) from 1 to 2. - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/v1...v2) --- updated-dependencies: - dependency-name: docker/login-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bc3074b45..9727d90dd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -32,7 +32,7 @@ jobs: - name: Setup Syft uses: anchore/sbom-action/download-syft@v0 - name: Login to GitHub Container Registry - uses: docker/login-action@v1 + uses: docker/login-action@v2 with: registry: ghcr.io username: kluctlbot From 0694d18a7b3fbb17f8f62b028fa378576335e15e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Nov 2022 13:25:47 +0000 Subject: [PATCH 1166/2916] chore(deps): Bump actions/cache from 2 to 3 Bumps [actions/cache](https://github.com/actions/cache) from 2 to 3. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/cache dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/release.yml | 2 +- .github/workflows/tests.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bc3074b45..70dff0a0c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -37,7 +37,7 @@ jobs: registry: ghcr.io username: kluctlbot password: ${{ secrets.GHCR_TOKEN }} - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: | ~/go/pkg/mod diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index faa1a4ee5..0a019bae9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/setup-go@v2 with: go-version: '1.19' - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: | ~/go/pkg/mod @@ -50,7 +50,7 @@ jobs: - uses: actions/setup-go@v2 with: go-version: '1.19' - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: | ~/go/pkg/mod @@ -87,7 +87,7 @@ jobs: - uses: actions/setup-go@v2 with: go-version: '1.19' - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: | ~/go/pkg/mod From 0f2c257b591789ab8d843718862fc9b48544e638 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Nov 2022 13:25:56 +0000 Subject: [PATCH 1167/2916] chore(deps): Bump docker/setup-buildx-action from 1 to 2 Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 1 to 2. - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/v1...v2) --- updated-dependencies: - dependency-name: docker/setup-buildx-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bc3074b45..f23bc8f45 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,7 +28,7 @@ jobs: uses: docker/setup-qemu-action@v1 - name: Setup Docker Buildx id: buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v2 - name: Setup Syft uses: anchore/sbom-action/download-syft@v0 - name: Login to GitHub Container Registry From 305e3fd2e4372a5c2ffbe25669ff12de886a0436 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Nov 2022 13:27:48 +0000 Subject: [PATCH 1168/2916] chore(deps): Bump github.com/google/go-containerregistry Bumps [github.com/google/go-containerregistry](https://github.com/google/go-containerregistry) from 0.12.0 to 0.12.1. - [Release notes](https://github.com/google/go-containerregistry/releases) - [Changelog](https://github.com/google/go-containerregistry/blob/main/.goreleaser.yml) - [Commits](https://github.com/google/go-containerregistry/compare/v0.12.0...v0.12.1) --- updated-dependencies: - dependency-name: github.com/google/go-containerregistry dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ebdc3b552..1f37560d5 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/go-playground/validator/v10 v10.11.1 github.com/gobwas/glob v0.2.3 // indirect github.com/golang-jwt/jwt/v4 v4.4.2 - github.com/google/go-containerregistry v0.12.0 + github.com/google/go-containerregistry v0.12.1 github.com/hashicorp/vault/api v1.8.1 github.com/hexops/gotextdiff v1.0.3 github.com/imdario/mergo v0.3.13 diff --git a/go.sum b/go.sum index 9437e853f..176a61dc2 100644 --- a/go.sum +++ b/go.sum @@ -398,8 +398,8 @@ github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8 github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-containerregistry v0.12.0 h1:nidOEtFYlgPCRqxCKj/4c/js940HVWplCWc5ftdfdUA= -github.com/google/go-containerregistry v0.12.0/go.mod h1:sdIK+oHQO7B93xI8UweYdl887YhuIwg9vz8BSLH3+8k= +github.com/google/go-containerregistry v0.12.1 h1:W1mzdNUTx4Zla4JaixCRLhORcR7G6KxE5hHl5fkPsp8= +github.com/google/go-containerregistry v0.12.1/go.mod h1:sdIK+oHQO7B93xI8UweYdl887YhuIwg9vz8BSLH3+8k= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= From 4c21f14d7257186b9c8163c837731db338e87393 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Nov 2022 13:27:52 +0000 Subject: [PATCH 1169/2916] chore(deps): Bump golang.org/x/sys from 0.1.0 to 0.2.0 Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.1.0 to 0.2.0. - [Release notes](https://github.com/golang/sys/releases) - [Commits](https://github.com/golang/sys/compare/v0.1.0...v0.2.0) --- updated-dependencies: - dependency-name: golang.org/x/sys dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ebdc3b552..af0f017b4 100644 --- a/go.mod +++ b/go.mod @@ -41,7 +41,7 @@ require ( golang.org/x/crypto v0.1.0 golang.org/x/net v0.1.0 golang.org/x/sync v0.1.0 - golang.org/x/sys v0.1.0 + golang.org/x/sys v0.2.0 golang.org/x/term v0.1.0 golang.org/x/text v0.4.0 gopkg.in/yaml.v2 v2.4.0 diff --git a/go.sum b/go.sum index 9437e853f..ae46d1cbe 100644 --- a/go.sum +++ b/go.sum @@ -1122,8 +1122,8 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= From 59393c0b195409643682e5056ecad3c90292e697 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Nov 2022 13:27:57 +0000 Subject: [PATCH 1170/2916] chore(deps): Bump github.com/spf13/viper from 1.13.0 to 1.14.0 Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.13.0 to 1.14.0. - [Release notes](https://github.com/spf13/viper/releases) - [Commits](https://github.com/spf13/viper/compare/v1.13.0...v1.14.0) --- updated-dependencies: - dependency-name: github.com/spf13/viper dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 13 +++-- go.sum | 176 ++++++--------------------------------------------------- 2 files changed, 25 insertions(+), 164 deletions(-) diff --git a/go.mod b/go.mod index ebdc3b552..dba0c8c73 100644 --- a/go.mod +++ b/go.mod @@ -34,8 +34,8 @@ require ( github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.6.1 github.com/spf13/pflag v1.0.5 - github.com/spf13/viper v1.13.0 - github.com/stretchr/testify v1.8.0 + github.com/spf13/viper v1.14.0 + github.com/stretchr/testify v1.8.1 github.com/whilp/git-urls v1.0.0 github.com/xanzy/ssh-agent v0.3.3-0.20220920102508-0fa644ba07f4 golang.org/x/crypto v0.1.0 @@ -64,7 +64,8 @@ require ( ) require ( - cloud.google.com/go/compute v1.10.0 // indirect + cloud.google.com/go/compute v1.12.1 // indirect + cloud.google.com/go/compute/metadata v0.2.1 // indirect filippo.io/age v1.0.0 // indirect github.com/Azure/azure-sdk-for-go v63.3.0+incompatible // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect @@ -130,8 +131,8 @@ require ( github.com/google/gofuzz v1.2.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.3.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.1.0 // indirect - github.com/googleapis/gax-go/v2 v2.4.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect + github.com/googleapis/gax-go/v2 v2.6.0 // indirect github.com/gorilla/mux v1.8.0 // indirect github.com/gosuri/uitable v0.0.4 // indirect github.com/goware/prefixer v0.0.0-20160118172347-395022866408 // indirect @@ -221,7 +222,7 @@ require ( golang.org/x/time v0.1.0 // indirect golang.org/x/tools v0.2.0 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect - google.golang.org/api v0.96.0 // indirect + google.golang.org/api v0.102.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71 // indirect google.golang.org/grpc v1.50.1 // indirect diff --git a/go.sum b/go.sum index 9437e853f..5ef390e2b 100644 --- a/go.sum +++ b/go.sum @@ -20,34 +20,19 @@ cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPT cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= -cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= -cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= -cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= -cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= -cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= -cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= -cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= -cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= -cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= -cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= -cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= -cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= -cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= -cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= -cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= -cloud.google.com/go/compute v1.10.0 h1:aoLIYaA1fX3ywihqpBk2APQKOo20nXsp1GEZQbx5Jk4= -cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= +cloud.google.com/go/compute v1.12.1 h1:gKVJMEyqV5c/UnpzjjQbo3Rjvvqpr9B1DFSbJC4OXr0= +cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= +cloud.google.com/go/compute/metadata v0.2.1 h1:efOwf5ymceDhK6PKMnnrTHP4pppY5L22mle96M1yP48= +cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= -cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -58,7 +43,6 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= -cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= filippo.io/age v1.0.0 h1:V6q14n0mqYU3qKFkZ6oOaF9oXneOviS3ubXsSVBRSzc= filippo.io/age v1.0.0/go.mod h1:PaX+Si/Sd5G8LgfCwldsSba3H1DDQZhIhFGkhbHaBq8= @@ -187,12 +171,8 @@ github.com/cloudflare/circl v1.2.0/go.mod h1:Ch2UgYr6ti2KTtlejELlROl0YIYj7SLjAC8 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/containerd/cgroups v1.0.3 h1:ADZftAkglvCiD44c77s5YmMqaP2pzVCFZvBmAlBdAP4= github.com/containerd/containerd v1.6.9 h1:IN/r8DUes/B5lEGTNfIiUkfZBtIQJGx2ai703dV6lRA= github.com/containerd/containerd v1.6.9/go.mod h1:XVicUvkxOrftE2Q1YWUXgZwkkAxwQYNOFzYWvfVfEfQ= @@ -245,7 +225,6 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= -github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= @@ -353,7 +332,6 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -372,7 +350,6 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= @@ -393,9 +370,6 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-containerregistry v0.12.0 h1:nidOEtFYlgPCRqxCKj/4c/js940HVWplCWc5ftdfdUA= @@ -406,7 +380,6 @@ github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -419,9 +392,6 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= @@ -429,18 +399,12 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= -github.com/googleapis/enterprise-certificate-proxy v0.1.0 h1:zO8WHNx/MYiAKJ3d5spxZXZE6KHmIQGQcAzwUzV7qQw= -github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= +github.com/googleapis/enterprise-certificate-proxy v0.2.0 h1:y8Yozv7SZtlU//QXbezB6QkpuE6jMD2/gfzk4AftXjs= +github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= -github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= -github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= -github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= -github.com/googleapis/gax-go/v2 v2.4.0 h1:dS9eYAjhrE2RjmzYw2XAPvcXfmcQLtFEQWn0CR82awk= -github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= -github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= +github.com/googleapis/gax-go/v2 v2.6.0 h1:SXk3ABtQYDT/OH8jAyvEOQ58mgawq5C4o/4/89qN2ZU= +github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= @@ -809,13 +773,14 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= -github.com/spf13/viper v1.13.0 h1:BWSJ/M+f+3nmdz9bxB+bWX28kkALN2ok11D0rSo8EJU= -github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw= +github.com/spf13/viper v1.14.0 h1:Rg7d3Lo706X9tHsJMUjdiwMpHB7W8WnSVOssIY+JElU= +github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -824,8 +789,9 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= @@ -880,8 +846,8 @@ go.starlark.net v0.0.0-20221020143700-22309ac47eac/go.mod h1:kIVgS18CjmEC3PqMd5k go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -983,18 +949,11 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1010,15 +969,7 @@ golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= -golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.1.0 h1:isLCZuhj4v+tYv7eskaN4v/TM+A1begWWgyVJDdl1+Y= golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1032,7 +983,6 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1092,34 +1042,19 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= @@ -1197,11 +1132,7 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE= golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= @@ -1209,10 +1140,7 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f h1:uF6paiQQebLeSXkrTqHqz0MXhXXS1KgF41eUdBNvxK0= -golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY= gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= @@ -1237,26 +1165,8 @@ google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjR google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= -google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= -google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= -google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= -google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= -google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= -google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= -google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= -google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= -google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= -google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= -google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= -google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= -google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= -google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= -google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= -google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= -google.golang.org/api v0.96.0 h1:F60cuQPJq7K7FzsxMYHAUJSiXh2oKctHxBMbDygxhfM= -google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.102.0 h1:JxJl2qQ85fRMPNvlZY/enexbxpCjLwGhZUtgfGeQ51I= +google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1306,47 +1216,9 @@ google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= -google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= -google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71 h1:GEgb2jF5zxsFJpJfg9RoDDWm7tiwc/DDSTE2BtLUkXU= google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -1368,22 +1240,11 @@ google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA5 google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= -google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= -google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.50.1 h1:DS/BukOZWp8s6p4Dt/tOaJaTQyPyOoCcrjroHuCeLzY= google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1397,7 +1258,6 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= From ec0e194db10f0ac5c73e39aaf26e9fa779657005 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Nov 2022 13:28:15 +0000 Subject: [PATCH 1171/2916] chore(deps): Bump helm.sh/helm/v3 from 3.10.1 to 3.10.2 Bumps [helm.sh/helm/v3](https://github.com/helm/helm) from 3.10.1 to 3.10.2. - [Release notes](https://github.com/helm/helm/releases) - [Commits](https://github.com/helm/helm/compare/v3.10.1...v3.10.2) --- updated-dependencies: - dependency-name: helm.sh/helm/v3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ebdc3b552..d79225173 100644 --- a/go.mod +++ b/go.mod @@ -46,7 +46,7 @@ require ( golang.org/x/text v0.4.0 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 - helm.sh/helm/v3 v3.10.1 + helm.sh/helm/v3 v3.10.2 k8s.io/api v0.25.3 k8s.io/apiextensions-apiserver v0.25.3 k8s.io/apimachinery v0.25.3 diff --git a/go.sum b/go.sum index 9437e853f..489310b1e 100644 --- a/go.sum +++ b/go.sum @@ -1438,8 +1438,8 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= -helm.sh/helm/v3 v3.10.1 h1:uTnNlYx8QcTSNA4ZJ50Llwife4CSohUY4ehumyVf2QE= -helm.sh/helm/v3 v3.10.1/go.mod h1:CXOcs02AYvrlPMWARNYNRgf2rNP7gLJQsi/Ubd4EDrI= +helm.sh/helm/v3 v3.10.2 h1:2PmN9NgmqTn5pswfL5Kh2LxOKjkmh0hxKLe6/J0yUY4= +helm.sh/helm/v3 v3.10.2/go.mod h1:CXOcs02AYvrlPMWARNYNRgf2rNP7gLJQsi/Ubd4EDrI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From bedc8a32cae08b487ba468875b0d18c8870dd09a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 11 Nov 2022 14:33:08 +0100 Subject: [PATCH 1172/2916] feat: Move args definitions into .kluctl.yaml and deprecate args in deployment.yaml --- e2e/args_test.go | 54 ++++++++++++++++++---------- pkg/deployment/external_args.go | 18 +++++++--- pkg/kluctl_project/target_context.go | 11 +++++- pkg/types/deployment.go | 5 --- pkg/types/kluctl_project.go | 10 ++++-- 5 files changed, 67 insertions(+), 31 deletions(-) diff --git a/e2e/args_test.go b/e2e/args_test.go index 41c28870b..f20bc88b9 100644 --- a/e2e/args_test.go +++ b/e2e/args_test.go @@ -7,7 +7,7 @@ import ( "testing" ) -func TestArgs(t *testing.T) { +func testArgs(t *testing.T, deprecated bool) { t.Parallel() k := defaultCluster1 @@ -19,24 +19,34 @@ func TestArgs(t *testing.T) { p.updateTarget("test", func(target *uo.UnstructuredObject) { }) - p.updateDeploymentYaml(".", func(o *uo.UnstructuredObject) error { - _ = o.SetNestedField([]any{ - map[string]any{ - "name": "a", - }, - map[string]any{ - "name": "b", - "default": "default", - }, - map[string]any{ - "name": "d", - "default": map[string]any{ - "nested": "default", - }, + + args := []any{ + map[string]any{ + "name": "a", + }, + map[string]any{ + "name": "b", + "default": "default", + }, + map[string]any{ + "name": "d", + "default": map[string]any{ + "nested": "default", }, - }, "args") - return nil - }) + }, + } + + if deprecated { + p.updateDeploymentYaml(".", func(o *uo.UnstructuredObject) error { + _ = o.SetNestedField(args, "args") + return nil + }) + } else { + p.updateKluctlYaml(func(o *uo.UnstructuredObject) error { + _ = o.SetNestedField(args, "args") + return nil + }) + } addConfigMapDeployment(p, "cm", map[string]string{ "a": `{{ args.a | default("na") }}`, @@ -109,6 +119,14 @@ d: assertNestedFieldEquals(t, cm, `{"nested": {"nested2": "d4"}}`, "data", "d") } +func TestDeprecatedArgs(t *testing.T) { + testArgs(t, true) +} + +func TestArgs(t *testing.T) { + testArgs(t, false) +} + func TestArgsFromEnv(t *testing.T) { t.Setenv("KLUCTL_ARG", "a=a") t.Setenv("KLUCTL_ARG_1", "b=b") diff --git a/pkg/deployment/external_args.go b/pkg/deployment/external_args.go index 365342560..53e6eaf70 100644 --- a/pkg/deployment/external_args.go +++ b/pkg/deployment/external_args.go @@ -1,7 +1,9 @@ package deployment import ( + "context" "fmt" + "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/vars" @@ -56,7 +58,7 @@ func ConvertArgsToVars(args map[string]string, allowLoadFromFiles bool) (*uo.Uns return vars, nil } -func LoadDeploymentArgs(dir string, varsCtx *vars.VarsCtx, deployArgs *uo.UnstructuredObject) error { +func LoadDeprecatedDeploymentArgs(ctx context.Context, dir string, varsCtx *vars.VarsCtx, deployArgs *uo.UnstructuredObject) (bool, error) { // First try to load the config without templating to avoid getting errors while rendering because required // args were not set. Otherwise we won't be able to iterator through the 'args' array in the deployment.yml // when the rendering error is actually args related. @@ -72,17 +74,23 @@ func LoadDeploymentArgs(dir string, varsCtx *vars.VarsCtx, deployArgs *uo.Unstru varsCtx2.UpdateChild("args", deployArgs) err = varsCtx2.RenderYamlFile(yaml.FixNameExt(dir, "deployment.yml"), []string{dir}, &conf) if err != nil { - return err + return false, err } } if len(conf.Args) == 0 { - return nil + return false, nil } + status.Deprecation(ctx, "deployment-args", "'args' in deployment.yaml is deprecated, please use 'args' from .kluctl.yaml instead.") + + return true, LoadDefaultArgs(conf.Args, deployArgs) +} + +func LoadDefaultArgs(args []*types.DeploymentArg, deployArgs *uo.UnstructuredObject) error { // load defaults defaults := uo.New() - for _, a := range conf.Args { + for _, a := range args { if a.Default != nil { a2 := uo.FromMap(map[string]interface{}{ a.Name: a.Default, @@ -93,7 +101,7 @@ func LoadDeploymentArgs(dir string, varsCtx *vars.VarsCtx, deployArgs *uo.Unstru defaults.Merge(deployArgs) *deployArgs = *defaults - err = checkRequiredArgs(conf.Args, deployArgs) + err := checkRequiredArgs(args, deployArgs) if err != nil { return err } diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index 246762e92..3a47c0642 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -177,7 +177,16 @@ func (p *LoadedKluctlProject) buildVars(target *types.Target, externalArgs *uo.U } } - err = deployment.LoadDeploymentArgs(p.ProjectDir, varsCtx, allArgs) + deprecatedArgs, err := deployment.LoadDeprecatedDeploymentArgs(p.ctx, p.ProjectDir, varsCtx, allArgs) + if err != nil { + return nil, err + } + + if deprecatedArgs && len(p.Config.Args) != 0 { + return nil, fmt.Errorf("mixing deprecated 'args' from deployment.yaml and .kluctl.yaml is not allowed") + } + + err = deployment.LoadDefaultArgs(p.Config.Args, allArgs) if err != nil { return nil, err } diff --git a/pkg/types/deployment.go b/pkg/types/deployment.go index d331457f2..eee074fda 100644 --- a/pkg/types/deployment.go +++ b/pkg/types/deployment.go @@ -53,11 +53,6 @@ func ValidateDeleteObjectItemConfig(sl validator.StructLevel) { } } -type DeploymentArg struct { - Name string `yaml:"name" validate:"required"` - Default interface{} `yaml:"default,omitempty"` -} - type SealedSecretsConfig struct { OutputPattern *string `yaml:"outputPattern,omitempty"` } diff --git a/pkg/types/kluctl_project.go b/pkg/types/kluctl_project.go index b1e7e5f68..0266fa1a0 100644 --- a/pkg/types/kluctl_project.go +++ b/pkg/types/kluctl_project.go @@ -41,6 +41,11 @@ type DynamicTarget struct { BaseTargetName string `yaml:"baseTargetName"` } +type DeploymentArg struct { + Name string `yaml:"name" validate:"required"` + Default interface{} `yaml:"default,omitempty"` +} + type SecretSet struct { Name string `yaml:"name" validate:"required"` Vars []*VarsSource `yaml:"vars,omitempty"` @@ -58,6 +63,7 @@ type SecretsConfig struct { } type KluctlProject struct { - Targets []*Target `yaml:"targets,omitempty"` - SecretsConfig *SecretsConfig `yaml:"secretsConfig,omitempty"` + Targets []*Target `yaml:"targets,omitempty"` + Args []*DeploymentArg `yaml:"args,omitempty"` + SecretsConfig *SecretsConfig `yaml:"secretsConfig,omitempty"` } From 941f90e93a8398fc9f4b0acddce2c80e7cf1c455 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 11 Nov 2022 15:01:46 +0100 Subject: [PATCH 1173/2916] feat: Allow to run kluctl on simple kustomize deployments --- pkg/deployment/deployment_project.go | 22 +++++++++++++++++++--- pkg/deployment/external_args.go | 4 ++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go index 04a06dac7..a0bb80952 100644 --- a/pkg/deployment/deployment_project.go +++ b/pkg/deployment/deployment_project.go @@ -70,10 +70,15 @@ func (p *DeploymentProject) loadVarsList(varsCtx *vars.VarsCtx, varsList []*type func (p *DeploymentProject) loadConfig() error { configPath := filepath.Join(p.absDir, "deployment.yml") if !yaml.Exists(configPath) { - if yaml.Exists(filepath.Join(p.absDir, "kustomization.yml")) { + if p.parentProject == nil { + // the root project is allowed to not have a deployment.yaml + // in that case, it is treated as a simple deployment with a single deployment item pointing to "." + return p.generateSingleKustomizeProject() + } else if yaml.Exists(filepath.Join(p.absDir, "kustomization.yml")) { return fmt.Errorf("deployment.yml not found but folder %s contains a kustomization.yml", p.absDir) + } else { + return fmt.Errorf("%s not found", configPath) } - return fmt.Errorf("%s not found", configPath) } configPath = yaml.FixPathExt(configPath) @@ -82,7 +87,18 @@ func (p *DeploymentProject) loadConfig() error { return fmt.Errorf("failed to load deployment.yml: %w", err) } - err = p.loadVarsList(p.VarsCtx, p.Config.Vars) + return p.processConfig() +} + +func (p *DeploymentProject) generateSingleKustomizeProject() error { + p.Config.Deployments = append(p.Config.Deployments, &types.DeploymentItemConfig{ + Path: utils.StrPtr("."), + }) + return nil +} + +func (p *DeploymentProject) processConfig() error { + err := p.loadVarsList(p.VarsCtx, p.Config.Vars) if err != nil { return fmt.Errorf("failed to load deployment.yml vars: %w", err) } diff --git a/pkg/deployment/external_args.go b/pkg/deployment/external_args.go index 53e6eaf70..c7f0aefcc 100644 --- a/pkg/deployment/external_args.go +++ b/pkg/deployment/external_args.go @@ -63,6 +63,10 @@ func LoadDeprecatedDeploymentArgs(ctx context.Context, dir string, varsCtx *vars // args were not set. Otherwise we won't be able to iterator through the 'args' array in the deployment.yml // when the rendering error is actually args related. + if !yaml.Exists(filepath.Join(dir, "deployment.yml")) { + return false, nil + } + var conf types.DeploymentProjectConfig err := yaml.ReadYamlFile(yaml.FixPathExt(filepath.Join(dir, "deployment.yml")), &conf) From 8c5c0e732b3455bf85e8c0186ac2386c081e4658 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 11 Nov 2022 15:19:13 +0100 Subject: [PATCH 1174/2916] refactor: Remove mutex that is not really needed in RemoteObjectUtils --- pkg/deployment/utils/remote_objects_utils.go | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/pkg/deployment/utils/remote_objects_utils.go b/pkg/deployment/utils/remote_objects_utils.go index 442acb1a6..e61cff4f6 100644 --- a/pkg/deployment/utils/remote_objects_utils.go +++ b/pkg/deployment/utils/remote_objects_utils.go @@ -8,7 +8,6 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "k8s.io/apimachinery/pkg/runtime/schema" - "sync" ) type RemoteObjectUtils struct { @@ -16,7 +15,6 @@ type RemoteObjectUtils struct { dew *DeploymentErrorsAndWarnings remoteObjects map[k8s2.ObjectRef]*uo.UnstructuredObject remoteNamespaces map[string]*uo.UnstructuredObject - mutex sync.Mutex } func NewRemoteObjectsUtil(ctx context.Context, dew *DeploymentErrorsAndWarnings) *RemoteObjectUtils { @@ -44,7 +42,6 @@ func (u *RemoteObjectUtils) UpdateRemoteObjects(k *k8s.K8sCluster, labels map[st return err } - u.mutex.Lock() for _, o := range allObjects { u.remoteObjects[o.GetK8sRef()] = o } @@ -59,7 +56,6 @@ func (u *RemoteObjectUtils) UpdateRemoteObjects(k *k8s.K8sCluster, labels map[st } } } - u.mutex.Unlock() if len(notFoundRefsList) != 0 { s.UpdateAndInfoFallback("Getting %d additional remote objects", len(notFoundRefsList)) @@ -70,11 +66,9 @@ func (u *RemoteObjectUtils) UpdateRemoteObjects(k *k8s.K8sCluster, labels map[st if err != nil { return err } - u.mutex.Lock() for _, o := range r { u.remoteObjects[o.GetK8sRef()] = o } - u.mutex.Unlock() } s.UpdateAndInfoFallback("Getting namespaces") @@ -96,31 +90,22 @@ func (u *RemoteObjectUtils) UpdateRemoteObjects(k *k8s.K8sCluster, labels map[st } func (u *RemoteObjectUtils) GetRemoteObject(ref k8s2.ObjectRef) *uo.UnstructuredObject { - u.mutex.Lock() - defer u.mutex.Unlock() o, _ := u.remoteObjects[ref] return o } func (u *RemoteObjectUtils) GetRemoteNamespace(name string) *uo.UnstructuredObject { - u.mutex.Lock() - defer u.mutex.Unlock() o, _ := u.remoteNamespaces[name] return o } func (u *RemoteObjectUtils) ForgetRemoteObject(ref k8s2.ObjectRef) { - u.mutex.Lock() - defer u.mutex.Unlock() delete(u.remoteObjects, ref) } func (u *RemoteObjectUtils) GetFilteredRemoteObjects(inclusion *utils.Inclusion) []*uo.UnstructuredObject { var ret []*uo.UnstructuredObject - u.mutex.Lock() - defer u.mutex.Unlock() - for _, o := range u.remoteObjects { iv := u.getInclusionEntries(o) if inclusion.CheckIncluded(iv, false) { From d6aee5b1d63dcd75370c009b9023e862c589f1d5 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 11 Nov 2022 15:21:27 +0100 Subject: [PATCH 1175/2916] feat: Allow deployments without commonLabels But forbid deletes. --- e2e/no_target_test.go | 26 +++++++++++++++++--- pkg/deployment/commands/delete.go | 5 ++++ pkg/deployment/deployment_project.go | 4 --- pkg/deployment/utils/remote_objects_utils.go | 21 ++++++++-------- 4 files changed, 38 insertions(+), 18 deletions(-) diff --git a/e2e/no_target_test.go b/e2e/no_target_test.go index 7c2f74f61..7e3083cee 100644 --- a/e2e/no_target_test.go +++ b/e2e/no_target_test.go @@ -2,10 +2,12 @@ package e2e import ( "github.com/stretchr/testify/assert" + "os" + "path/filepath" "testing" ) -func prepareNoTargetTest(t *testing.T, name string) *testProject { +func prepareNoTargetTest(t *testing.T, name string, withDeploymentYaml bool) *testProject { p := &testProject{} p.init(t, defaultCluster1, name) p.mergeKubeconfig(defaultCluster2) @@ -13,7 +15,7 @@ func prepareNoTargetTest(t *testing.T, name string) *testProject { createNamespace(t, defaultCluster1, p.projectName) createNamespace(t, defaultCluster2, p.projectName) - addConfigMapDeployment(p, "cm", map[string]string{ + cm := createConfigMapObject(map[string]string{ "targetName": `{{ target.name }}`, "targetContext": `{{ target.context }}`, }, resourceOpts{ @@ -21,13 +23,21 @@ func prepareNoTargetTest(t *testing.T, name string) *testProject { namespace: p.projectName, }) + if withDeploymentYaml { + p.addKustomizeDeployment("cm", []kustomizeResource{{name: "cm.yaml", content: cm}}, nil) + } else { + p.addKustomizeResources("", []kustomizeResource{{name: "cm.yaml", content: cm}}) + err := os.Remove(filepath.Join(p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()), "deployment.yml")) + assert.NoError(t, err) + } + return p } -func TestNoTarget(t *testing.T) { +func testNoTarget(t *testing.T, withDeploymentYaml bool) { t.Parallel() - p := prepareNoTargetTest(t, "no-target") + p := prepareNoTargetTest(t, "no-target", withDeploymentYaml) p.KluctlMust("deploy", "--yes") cm := assertConfigMapExists(t, defaultCluster1, p.projectName, "cm") @@ -51,3 +61,11 @@ func TestNoTarget(t *testing.T) { "targetContext": defaultCluster2.Context, }, cm.Object["data"]) } + +func TestNoTarget(t *testing.T) { + testNoTarget(t, true) +} + +func TestNoTargetNoDeployment(t *testing.T) { + testNoTarget(t, false) +} diff --git a/pkg/deployment/commands/delete.go b/pkg/deployment/commands/delete.go index d4cb32fd8..e32ae759f 100644 --- a/pkg/deployment/commands/delete.go +++ b/pkg/deployment/commands/delete.go @@ -2,6 +2,7 @@ package commands import ( "context" + "fmt" "github.com/kluctl/kluctl/v2/pkg/deployment" utils2 "github.com/kluctl/kluctl/v2/pkg/deployment/utils" "github.com/kluctl/kluctl/v2/pkg/k8s" @@ -32,6 +33,10 @@ func (cmd *DeleteCommand) Run(ctx context.Context, k *k8s.K8sCluster) ([]k8s2.Ob labels = cmd.c.Project.GetCommonLabels() } + if len(labels) == 0 { + return nil, fmt.Errorf("deletion without using commonLabels in the root deployment.yaml is not allowed") + } + var inclusion *utils.Inclusion if cmd.c != nil { inclusion = cmd.c.Inclusion diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go index a0bb80952..9f3909ab7 100644 --- a/pkg/deployment/deployment_project.go +++ b/pkg/deployment/deployment_project.go @@ -121,10 +121,6 @@ func (p *DeploymentProject) processConfig() error { return err } - if len(p.GetCommonLabels()) == 0 { - return fmt.Errorf("no commonLabels in root deployment. This is not allowed") - } - if len(p.Config.Args) != 0 && p.parentProject != nil { return fmt.Errorf("only the root deployment.yml can define args") } diff --git a/pkg/deployment/utils/remote_objects_utils.go b/pkg/deployment/utils/remote_objects_utils.go index e61cff4f6..520a786a0 100644 --- a/pkg/deployment/utils/remote_objects_utils.go +++ b/pkg/deployment/utils/remote_objects_utils.go @@ -34,16 +34,17 @@ func (u *RemoteObjectUtils) UpdateRemoteObjects(k *k8s.K8sCluster, labels map[st s := status.Start(u.ctx, "Getting remote objects by commonLabels") defer s.Failed() - allObjects, apiWarnings, err := k.ListAllObjects([]string{"get"}, "", labels) - for gvk, aw := range apiWarnings { - u.dew.AddApiWarnings(k8s2.ObjectRef{GVK: gvk}, aw) - } - if err != nil { - return err - } - - for _, o := range allObjects { - u.remoteObjects[o.GetK8sRef()] = o + if len(labels) != 0 { + allObjects, apiWarnings, err := k.ListAllObjects([]string{"get"}, "", labels) + for gvk, aw := range apiWarnings { + u.dew.AddApiWarnings(k8s2.ObjectRef{GVK: gvk}, aw) + } + if err != nil { + return err + } + for _, o := range allObjects { + u.remoteObjects[o.GetK8sRef()] = o + } } notFoundRefsMap := make(map[k8s2.ObjectRef]bool) From 6d34cf7b25b1056a44ab20e2162852a796f6efd6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 11 Nov 2022 15:40:29 +0100 Subject: [PATCH 1176/2916] fix: Ignore .git folders by default --- pkg/deployment/deployment_item.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index d42afe526..69cdb586f 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -123,6 +123,8 @@ func (di *DeploymentItem) render(forSeal bool) error { } var excludePatterns []string + excludePatterns = append(excludePatterns, "**/.git") + if len(di.Project.Config.TemplateExcludes) != 0 { status.Deprecation(di.ctx.Ctx, "template-excludes", "'templateExcludes' are deprecated, use .templateignore files instead.") } From 12595b79fa4e66101706245bc752e133bd7e17b8 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 11 Nov 2022 16:09:47 +0100 Subject: [PATCH 1177/2916] tests: Automate namespace uses in tests --- e2e/args_test.go | 38 ++++++++++----------- e2e/contexts_test.go | 54 ++++++++++++++--------------- e2e/deployment_items_test.go | 30 ++++++++-------- e2e/flux_test.go | 2 +- e2e/hooks_test.go | 16 ++++----- e2e/inclusion_test.go | 44 ++++++++++++------------ e2e/no_target_test.go | 20 +++++------ e2e/project.go | 20 ++++++----- e2e/seal_test.go | 66 ++++++++++++++++-------------------- e2e/sops_test.go | 16 ++++----- 10 files changed, 150 insertions(+), 156 deletions(-) diff --git a/e2e/args_test.go b/e2e/args_test.go index f20bc88b9..05e38fe97 100644 --- a/e2e/args_test.go +++ b/e2e/args_test.go @@ -13,9 +13,9 @@ func testArgs(t *testing.T, deprecated bool) { k := defaultCluster1 p := &testProject{} - p.init(t, k, "args") + p.init(t, k) - createNamespace(t, k, p.projectName) + createNamespace(t, k, p.testSlug()) p.updateTarget("test", func(target *uo.UnstructuredObject) { }) @@ -55,36 +55,36 @@ func testArgs(t *testing.T, deprecated bool) { "d": "{{ args.d | to_json }}", }, resourceOpts{ name: "cm", - namespace: p.projectName, + namespace: p.testSlug(), }) p.KluctlMust("deploy", "--yes", "-t", "test", "-aa=a") - cm := k.MustGetCoreV1(t, "configmaps", p.projectName, "cm") + cm := k.MustGetCoreV1(t, "configmaps", p.testSlug(), "cm") assertNestedFieldEquals(t, cm, "a", "data", "a") assertNestedFieldEquals(t, cm, "default", "data", "b") assertNestedFieldEquals(t, cm, "na", "data", "c") assertNestedFieldEquals(t, cm, `{"nested": "default"}`, "data", "d") p.KluctlMust("deploy", "--yes", "-t", "test", "-aa=a", "-ab=b") - cm = k.MustGetCoreV1(t, "configmaps", p.projectName, "cm") + cm = k.MustGetCoreV1(t, "configmaps", p.testSlug(), "cm") assertNestedFieldEquals(t, cm, "a", "data", "a") assertNestedFieldEquals(t, cm, "b", "data", "b") assertNestedFieldEquals(t, cm, "na", "data", "c") assertNestedFieldEquals(t, cm, `{"nested": "default"}`, "data", "d") p.KluctlMust("deploy", "--yes", "-t", "test", "-aa=a", "-ab=b", "-ac=c") - cm = k.MustGetCoreV1(t, "configmaps", p.projectName, "cm") + cm = k.MustGetCoreV1(t, "configmaps", p.testSlug(), "cm") assertNestedFieldEquals(t, cm, "a", "data", "a") assertNestedFieldEquals(t, cm, "b", "data", "b") assertNestedFieldEquals(t, cm, "c", "data", "c") assertNestedFieldEquals(t, cm, `{"nested": "default"}`, "data", "d") p.KluctlMust("deploy", "--yes", "-t", "test", "-aa=a", "-ab=b", "-ac=c", "-ad.nested=d") - cm = k.MustGetCoreV1(t, "configmaps", p.projectName, "cm") + cm = k.MustGetCoreV1(t, "configmaps", p.testSlug(), "cm") assertNestedFieldEquals(t, cm, `{"nested": "d"}`, "data", "d") p.KluctlMust("deploy", "--yes", "-t", "test", "-aa=a", "-ab=b", "-ac=c", `-ad={"nested": "d2"}`) - cm = k.MustGetCoreV1(t, "configmaps", p.projectName, "cm") + cm = k.MustGetCoreV1(t, "configmaps", p.testSlug(), "cm") assertNestedFieldEquals(t, cm, `{"nested": "d2"}`, "data", "d") tmpFile, err := os.CreateTemp("", "") @@ -98,7 +98,7 @@ nested: `) p.KluctlMust("deploy", "--yes", "-t", "test", "-aa=a", "-ab=b", "-ac=c", fmt.Sprintf(`-ad=@%s`, tmpFile.Name())) - cm = k.MustGetCoreV1(t, "configmaps", p.projectName, "cm") + cm = k.MustGetCoreV1(t, "configmaps", p.testSlug(), "cm") assertNestedFieldEquals(t, cm, `{"nested": {"nested2": "d3"}}`, "data", "d") _ = tmpFile.Truncate(0) @@ -112,7 +112,7 @@ d: `) p.KluctlMust("deploy", "--yes", "-t", "test", fmt.Sprintf(`--args-from-file=%s`, tmpFile.Name())) - cm = k.MustGetCoreV1(t, "configmaps", p.projectName, "cm") + cm = k.MustGetCoreV1(t, "configmaps", p.testSlug(), "cm") assertNestedFieldEquals(t, cm, "a2", "data", "a") assertNestedFieldEquals(t, cm, "default", "data", "b") assertNestedFieldEquals(t, cm, "c2", "data", "c") @@ -137,9 +137,9 @@ func TestArgsFromEnv(t *testing.T) { k := defaultCluster1 p := &testProject{} - p.init(t, k, "args-from-envs") + p.init(t, k) - createNamespace(t, k, p.projectName) + createNamespace(t, k, p.testSlug()) p.updateTarget("test", func(target *uo.UnstructuredObject) { }) @@ -152,11 +152,11 @@ func TestArgsFromEnv(t *testing.T) { "e": `{{ args.e }}`, }, resourceOpts{ name: "cm", - namespace: p.projectName, + namespace: p.testSlug(), }) p.KluctlMust("deploy", "--yes", "-t", "test") - cm := k.MustGetCoreV1(t, "configmaps", p.projectName, "cm") + cm := k.MustGetCoreV1(t, "configmaps", p.testSlug(), "cm") assertNestedFieldEquals(t, cm, "a", "data", "a") assertNestedFieldEquals(t, cm, "b", "data", "b") assertNestedFieldEquals(t, cm, `{"nested": {"nested2": "c"}}`, "data", "c") @@ -171,9 +171,9 @@ func TestArgsFromEnvAndCli(t *testing.T) { k := defaultCluster1 p := &testProject{} - p.init(t, k, "args-from-envs-and-cli") + p.init(t, k) - createNamespace(t, k, p.projectName) + createNamespace(t, k, p.testSlug()) p.updateTarget("test", func(target *uo.UnstructuredObject) { }) @@ -184,18 +184,18 @@ func TestArgsFromEnvAndCli(t *testing.T) { "c": `{{ args.c }}`, }, resourceOpts{ name: "cm", - namespace: p.projectName, + namespace: p.testSlug(), }) p.KluctlMust("deploy", "--yes", "-t", "test", "-a", "b=b") - cm := k.MustGetCoreV1(t, "configmaps", p.projectName, "cm") + cm := k.MustGetCoreV1(t, "configmaps", p.testSlug(), "cm") assertNestedFieldEquals(t, cm, "a", "data", "a") assertNestedFieldEquals(t, cm, "b", "data", "b") assertNestedFieldEquals(t, cm, "c", "data", "c") // make sure the CLI overrides values from env p.KluctlMust("deploy", "--yes", "-t", "test", "-a", "b=b", "-a", "c=c2") - cm = k.MustGetCoreV1(t, "configmaps", p.projectName, "cm") + cm = k.MustGetCoreV1(t, "configmaps", p.testSlug(), "cm") assertNestedFieldEquals(t, cm, "a", "data", "a") assertNestedFieldEquals(t, cm, "b", "data", "b") assertNestedFieldEquals(t, cm, "c2", "data", "c") diff --git a/e2e/contexts_test.go b/e2e/contexts_test.go index 29fa1d133..a6e2a0898 100644 --- a/e2e/contexts_test.go +++ b/e2e/contexts_test.go @@ -6,17 +6,17 @@ import ( "testing" ) -func prepareContextTest(t *testing.T, name string) *testProject { +func prepareContextTest(t *testing.T) *testProject { p := &testProject{} - p.init(t, defaultCluster1, name) + p.init(t, defaultCluster1) p.mergeKubeconfig(defaultCluster2) - createNamespace(t, defaultCluster1, p.projectName) - createNamespace(t, defaultCluster2, p.projectName) + createNamespace(t, defaultCluster1, p.testSlug()) + createNamespace(t, defaultCluster2, p.testSlug()) addConfigMapDeployment(p, "cm", nil, resourceOpts{ name: "cm", - namespace: p.projectName, + namespace: p.testSlug(), }) return p @@ -25,56 +25,56 @@ func prepareContextTest(t *testing.T, name string) *testProject { func TestContextCurrent(t *testing.T) { t.Parallel() - p := prepareContextTest(t, "context-current") + p := prepareContextTest(t) p.updateTarget("test1", func(target *uo.UnstructuredObject) { // no context set, assume the current one is used }) p.KluctlMust("deploy", "--yes", "-t", "test1") - assertConfigMapExists(t, defaultCluster1, p.projectName, "cm") - assertConfigMapNotExists(t, defaultCluster2, p.projectName, "cm") + assertConfigMapExists(t, defaultCluster1, p.testSlug(), "cm") + assertConfigMapNotExists(t, defaultCluster2, p.testSlug(), "cm") p.updateMergedKubeconfig(func(config *api.Config) { config.CurrentContext = defaultCluster2.Context }) p.KluctlMust("deploy", "--yes", "-t", "test1") - assertConfigMapExists(t, defaultCluster2, p.projectName, "cm") + assertConfigMapExists(t, defaultCluster2, p.testSlug(), "cm") } func TestContext1(t *testing.T) { t.Parallel() - p := prepareContextTest(t, "context-1") + p := prepareContextTest(t) p.updateTarget("test1", func(target *uo.UnstructuredObject) { _ = target.SetNestedField(defaultCluster1.Context, "context") }) p.KluctlMust("deploy", "--yes", "-t", "test1") - assertConfigMapExists(t, defaultCluster1, p.projectName, "cm") - assertConfigMapNotExists(t, defaultCluster2, p.projectName, "cm") + assertConfigMapExists(t, defaultCluster1, p.testSlug(), "cm") + assertConfigMapNotExists(t, defaultCluster2, p.testSlug(), "cm") } func TestContext2(t *testing.T) { t.Parallel() - p := prepareContextTest(t, "context-2") + p := prepareContextTest(t) p.updateTarget("test1", func(target *uo.UnstructuredObject) { _ = target.SetNestedField(defaultCluster2.Context, "context") }) p.KluctlMust("deploy", "--yes", "-t", "test1") - assertConfigMapExists(t, defaultCluster2, p.projectName, "cm") - assertConfigMapNotExists(t, defaultCluster1, p.projectName, "cm") + assertConfigMapExists(t, defaultCluster2, p.testSlug(), "cm") + assertConfigMapNotExists(t, defaultCluster1, p.testSlug(), "cm") } func TestContext1And2(t *testing.T) { t.Parallel() - p := prepareContextTest(t, "context-1-and-2") + p := prepareContextTest(t) p.updateTarget("test1", func(target *uo.UnstructuredObject) { _ = target.SetNestedField(defaultCluster1.Context, "context") @@ -84,47 +84,47 @@ func TestContext1And2(t *testing.T) { }) p.KluctlMust("deploy", "--yes", "-t", "test1") - assertConfigMapExists(t, defaultCluster1, p.projectName, "cm") - assertConfigMapNotExists(t, defaultCluster2, p.projectName, "cm") + assertConfigMapExists(t, defaultCluster1, p.testSlug(), "cm") + assertConfigMapNotExists(t, defaultCluster2, p.testSlug(), "cm") p.KluctlMust("deploy", "--yes", "-t", "test2") - assertConfigMapExists(t, defaultCluster2, p.projectName, "cm") + assertConfigMapExists(t, defaultCluster2, p.testSlug(), "cm") } func TestContextSwitch(t *testing.T) { t.Parallel() - p := prepareContextTest(t, "context-switch") + p := prepareContextTest(t) p.updateTarget("test1", func(target *uo.UnstructuredObject) { _ = target.SetNestedField(defaultCluster1.Context, "context") }) p.KluctlMust("deploy", "--yes", "-t", "test1") - assertConfigMapExists(t, defaultCluster1, p.projectName, "cm") - assertConfigMapNotExists(t, defaultCluster2, p.projectName, "cm") + assertConfigMapExists(t, defaultCluster1, p.testSlug(), "cm") + assertConfigMapNotExists(t, defaultCluster2, p.testSlug(), "cm") p.updateTarget("test1", func(target *uo.UnstructuredObject) { _ = target.SetNestedField(defaultCluster2.Context, "context") }) p.KluctlMust("deploy", "--yes", "-t", "test1") - assertConfigMapExists(t, defaultCluster2, p.projectName, "cm") + assertConfigMapExists(t, defaultCluster2, p.testSlug(), "cm") } func TestContextOverride(t *testing.T) { t.Parallel() - p := prepareContextTest(t, "context-override") + p := prepareContextTest(t) p.updateTarget("test1", func(target *uo.UnstructuredObject) { _ = target.SetNestedField(defaultCluster1.Context, "context") }) p.KluctlMust("deploy", "--yes", "-t", "test1") - assertConfigMapExists(t, defaultCluster1, p.projectName, "cm") - assertConfigMapNotExists(t, defaultCluster2, p.projectName, "cm") + assertConfigMapExists(t, defaultCluster1, p.testSlug(), "cm") + assertConfigMapNotExists(t, defaultCluster2, p.testSlug(), "cm") p.KluctlMust("deploy", "--yes", "-t", "test1", "--context", defaultCluster2.Context) - assertConfigMapExists(t, defaultCluster2, p.projectName, "cm") + assertConfigMapExists(t, defaultCluster2, p.testSlug(), "cm") } diff --git a/e2e/deployment_items_test.go b/e2e/deployment_items_test.go index c1a8ffe8e..a9fa3c5b6 100644 --- a/e2e/deployment_items_test.go +++ b/e2e/deployment_items_test.go @@ -11,27 +11,27 @@ func TestKustomize(t *testing.T) { k := defaultCluster1 p := &testProject{} - p.init(t, k, "di-kustomize") + p.init(t, k) - createNamespace(t, k, p.projectName) + createNamespace(t, k, p.testSlug()) p.updateTarget("test", nil) addConfigMapDeployment(p, "cm", nil, resourceOpts{ name: "cm", - namespace: p.projectName, + namespace: p.testSlug(), }) p.KluctlMust("deploy", "--yes", "-t", "test") - assertConfigMapExists(t, k, p.projectName, "cm") + assertConfigMapExists(t, k, p.testSlug(), "cm") addConfigMapDeployment(p, "cm2", nil, resourceOpts{ name: "cm2", - namespace: p.projectName, + namespace: p.testSlug(), }) p.KluctlMust("deploy", "--yes", "-t", "test", "--dry-run") - assertConfigMapNotExists(t, k, p.projectName, "cm2") + assertConfigMapNotExists(t, k, p.testSlug(), "cm2") p.KluctlMust("deploy", "--yes", "-t", "test") - assertConfigMapExists(t, k, p.projectName, "cm2") + assertConfigMapExists(t, k, p.testSlug(), "cm2") } func TestGeneratedKustomize(t *testing.T) { @@ -40,9 +40,9 @@ func TestGeneratedKustomize(t *testing.T) { k := defaultCluster1 p := &testProject{} - p.init(t, k, "di-generated-kustomize") + p.init(t, k) - createNamespace(t, k, p.projectName) + createNamespace(t, k, p.testSlug()) p.updateTarget("test", nil) @@ -57,27 +57,27 @@ func TestGeneratedKustomize(t *testing.T) { p.updateYaml("generated-kustomize/cm1.yaml", func(o *uo.UnstructuredObject) error { *o = *createConfigMapObject(nil, resourceOpts{ name: "cm1", - namespace: p.projectName, + namespace: p.testSlug(), }) return nil }, "") p.updateYaml("generated-kustomize/cm2.yaml", func(o *uo.UnstructuredObject) error { *o = *createConfigMapObject(nil, resourceOpts{ name: "cm2", - namespace: p.projectName, + namespace: p.testSlug(), }) return nil }, "") p.updateYaml("generated-kustomize/cm3._yaml", func(o *uo.UnstructuredObject) error { *o = *createConfigMapObject(nil, resourceOpts{ name: "cm3", - namespace: p.projectName, + namespace: p.testSlug(), }) return nil }, "") p.KluctlMust("deploy", "--yes", "-t", "test") - assertConfigMapExists(t, k, p.projectName, "cm1") - assertConfigMapExists(t, k, p.projectName, "cm2") - assertConfigMapNotExists(t, k, p.projectName, "cm3") + assertConfigMapExists(t, k, p.testSlug(), "cm1") + assertConfigMapExists(t, k, p.testSlug(), "cm2") + assertConfigMapNotExists(t, k, p.testSlug(), "cm3") } diff --git a/e2e/flux_test.go b/e2e/flux_test.go index b7b4bcb53..b796e792d 100644 --- a/e2e/flux_test.go +++ b/e2e/flux_test.go @@ -37,7 +37,7 @@ func TestFluxCommands(t *testing.T) { k := defaultCluster1 p := &testProject{} - p.init(t, k, "flux-test") + p.init(t, k) var wg sync.WaitGroup wg.Add(2) diff --git a/e2e/hooks_test.go b/e2e/hooks_test.go index af1c2221c..863ba6af3 100644 --- a/e2e/hooks_test.go +++ b/e2e/hooks_test.go @@ -36,7 +36,7 @@ func (s *hooksTestContext) removeWebhook() { } func (s *hooksTestContext) handleConfigmap(request admission.Request) { - if s.p.projectName != request.Namespace { + if s.p.testSlug() != request.Namespace { return } @@ -100,21 +100,19 @@ func prepareHookTestProject(t *testing.T, name string, hook string, hookDeletion } s.setupWebhook() - namespace := fmt.Sprintf("hook-%s", name) - - s.p.init(t, s.k, namespace) + s.p.init(t, s.k) t.Cleanup(func() { s.removeWebhook() }) - createNamespace(s.t, s.k, namespace) + createNamespace(s.t, s.k, s.p.testSlug()) s.p.updateTarget("test", nil) s.p.addKustomizeDeployment("hook", nil, nil) - s.addConfigMap("hook", resourceOpts{name: "cm1", namespace: namespace}) - s.addHookConfigMap("hook", resourceOpts{name: "hook1", namespace: namespace}, false, hook, hookDeletionPolicy) + s.addConfigMap("hook", resourceOpts{name: "cm1", namespace: s.p.testSlug()}) + s.addHookConfigMap("hook", resourceOpts{name: "hook1", namespace: s.p.testSlug()}, false, hook, hookDeletionPolicy) return s } @@ -127,10 +125,10 @@ func (s *hooksTestContext) ensureHookExecuted(expectedCms ...string) { func (s *hooksTestContext) ensureHookNotExecuted() { _ = s.k.DynamicClient.Resource(corev1.SchemeGroupVersion.WithResource("configmaps")). - Namespace(s.p.projectName). + Namespace(s.p.testSlug()). Delete(context.Background(), "cm1", metav1.DeleteOptions{}) s.p.KluctlMust("deploy", "--yes", "-t", "test") - assertConfigMapNotExists(s.t, s.k, s.p.projectName, "cm1") + assertConfigMapNotExists(s.t, s.k, s.p.testSlug(), "cm1") } func TestHooksPreDeployInitial(t *testing.T) { diff --git a/e2e/inclusion_test.go b/e2e/inclusion_test.go index edba11b1a..a50564854 100644 --- a/e2e/inclusion_test.go +++ b/e2e/inclusion_test.go @@ -8,34 +8,34 @@ import ( "testing" ) -func prepareInclusionTestProject(t *testing.T, namespace string, withIncludes bool) (*testProject, *test_utils.EnvTestCluster) { +func prepareInclusionTestProject(t *testing.T, withIncludes bool) (*testProject, *test_utils.EnvTestCluster) { k := defaultCluster1 p := &testProject{} - p.init(t, k, namespace) + p.init(t, k) - createNamespace(t, k, p.projectName) + createNamespace(t, k, p.testSlug()) p.updateTarget("test", nil) - addConfigMapDeployment(p, "cm1", nil, resourceOpts{name: "cm1", namespace: p.projectName}) - addConfigMapDeployment(p, "cm2", nil, resourceOpts{name: "cm2", namespace: p.projectName}) - addConfigMapDeployment(p, "cm3", nil, resourceOpts{name: "cm3", namespace: p.projectName, tags: []string{"tag1", "tag2"}}) - addConfigMapDeployment(p, "cm4", nil, resourceOpts{name: "cm4", namespace: p.projectName, tags: []string{"tag1", "tag3"}}) - addConfigMapDeployment(p, "cm5", nil, resourceOpts{name: "cm5", namespace: p.projectName, tags: []string{"tag1", "tag4"}}) - addConfigMapDeployment(p, "cm6", nil, resourceOpts{name: "cm6", namespace: p.projectName, tags: []string{"tag1", "tag5"}}) - addConfigMapDeployment(p, "cm7", nil, resourceOpts{name: "cm7", namespace: p.projectName, tags: []string{"tag1", "tag6"}}) + addConfigMapDeployment(p, "cm1", nil, resourceOpts{name: "cm1", namespace: p.testSlug()}) + addConfigMapDeployment(p, "cm2", nil, resourceOpts{name: "cm2", namespace: p.testSlug()}) + addConfigMapDeployment(p, "cm3", nil, resourceOpts{name: "cm3", namespace: p.testSlug(), tags: []string{"tag1", "tag2"}}) + addConfigMapDeployment(p, "cm4", nil, resourceOpts{name: "cm4", namespace: p.testSlug(), tags: []string{"tag1", "tag3"}}) + addConfigMapDeployment(p, "cm5", nil, resourceOpts{name: "cm5", namespace: p.testSlug(), tags: []string{"tag1", "tag4"}}) + addConfigMapDeployment(p, "cm6", nil, resourceOpts{name: "cm6", namespace: p.testSlug(), tags: []string{"tag1", "tag5"}}) + addConfigMapDeployment(p, "cm7", nil, resourceOpts{name: "cm7", namespace: p.testSlug(), tags: []string{"tag1", "tag6"}}) if withIncludes { p.addDeploymentInclude(".", "include1", nil) - addConfigMapDeployment(p, "include1/icm1", nil, resourceOpts{name: "icm1", namespace: p.projectName, tags: []string{"itag1", "itag2"}}) + addConfigMapDeployment(p, "include1/icm1", nil, resourceOpts{name: "icm1", namespace: p.testSlug(), tags: []string{"itag1", "itag2"}}) p.addDeploymentInclude(".", "include2", nil) - addConfigMapDeployment(p, "include2/icm2", nil, resourceOpts{name: "icm2", namespace: p.projectName}) - addConfigMapDeployment(p, "include2/icm3", nil, resourceOpts{name: "icm3", namespace: p.projectName, tags: []string{"itag3", "itag4"}}) + addConfigMapDeployment(p, "include2/icm2", nil, resourceOpts{name: "icm2", namespace: p.testSlug()}) + addConfigMapDeployment(p, "include2/icm3", nil, resourceOpts{name: "icm3", namespace: p.testSlug(), tags: []string{"itag3", "itag4"}}) p.addDeploymentInclude(".", "include3", []string{"itag5"}) - addConfigMapDeployment(p, "include3/icm4", nil, resourceOpts{name: "icm4", namespace: p.projectName}) - addConfigMapDeployment(p, "include3/icm5", nil, resourceOpts{name: "icm5", namespace: p.projectName, tags: []string{"itag5", "itag6"}}) + addConfigMapDeployment(p, "include3/icm4", nil, resourceOpts{name: "icm4", namespace: p.testSlug()}) + addConfigMapDeployment(p, "include3/icm5", nil, resourceOpts{name: "icm5", namespace: p.testSlug(), tags: []string{"itag5", "itag6"}}) } return p, k @@ -50,7 +50,7 @@ func assertExistsHelper(t *testing.T, p *testProject, k *test_utils.EnvTestClust delete(shouldExists, x) } } - items, err := k.List(corev1.SchemeGroupVersion.WithResource("configmaps"), p.projectName, map[string]string{"project_name": p.projectName}) + items, err := k.List(corev1.SchemeGroupVersion.WithResource("configmaps"), p.testSlug(), map[string]string{"project_name": p.testSlug()}) if err != nil { t.Fatal(err) } @@ -65,7 +65,7 @@ func assertExistsHelper(t *testing.T, p *testProject, k *test_utils.EnvTestClust func TestInclusionTags(t *testing.T) { t.Parallel() - p, k := prepareInclusionTestProject(t, "inclusion-tags", false) + p, k := prepareInclusionTestProject(t, false) shouldExists := make(map[string]bool) doAssertExists := func(add ...string) { @@ -99,7 +99,7 @@ func TestInclusionTags(t *testing.T) { func TestExclusionTags(t *testing.T) { t.Parallel() - p, k := prepareInclusionTestProject(t, "inclusion-exclusion", false) + p, k := prepareInclusionTestProject(t, false) shouldExists := make(map[string]bool) doAssertExists := func(add ...string) { @@ -123,7 +123,7 @@ func TestExclusionTags(t *testing.T) { func TestInclusionIncludeDirs(t *testing.T) { t.Parallel() - p, k := prepareInclusionTestProject(t, "inclusion-dirs", true) + p, k := prepareInclusionTestProject(t, true) shouldExists := make(map[string]bool) doAssertExists := func(add ...string) { @@ -144,7 +144,7 @@ func TestInclusionIncludeDirs(t *testing.T) { func TestInclusionDeploymentDirs(t *testing.T) { t.Parallel() - p, k := prepareInclusionTestProject(t, "inclusion-kustomize-dirs", true) + p, k := prepareInclusionTestProject(t, true) shouldExists := make(map[string]bool) doAssertExists := func(add ...string) { @@ -171,7 +171,7 @@ func TestInclusionDeploymentDirs(t *testing.T) { func TestInclusionPrune(t *testing.T) { t.Parallel() - p, k := prepareInclusionTestProject(t, "inclusion-prune", false) + p, k := prepareInclusionTestProject(t, false) shouldExists := make(map[string]bool) doAssertExists := func(add []string, remove []string) { @@ -204,7 +204,7 @@ func TestInclusionPrune(t *testing.T) { func TestInclusionDelete(t *testing.T) { t.Parallel() - p, k := prepareInclusionTestProject(t, "inclusion-delete", false) + p, k := prepareInclusionTestProject(t, false) shouldExists := make(map[string]bool) doAssertExists := func(add []string, remove []string) { diff --git a/e2e/no_target_test.go b/e2e/no_target_test.go index 7e3083cee..01cdfe738 100644 --- a/e2e/no_target_test.go +++ b/e2e/no_target_test.go @@ -7,20 +7,20 @@ import ( "testing" ) -func prepareNoTargetTest(t *testing.T, name string, withDeploymentYaml bool) *testProject { +func prepareNoTargetTest(t *testing.T, withDeploymentYaml bool) *testProject { p := &testProject{} - p.init(t, defaultCluster1, name) + p.init(t, defaultCluster1) p.mergeKubeconfig(defaultCluster2) - createNamespace(t, defaultCluster1, p.projectName) - createNamespace(t, defaultCluster2, p.projectName) + createNamespace(t, defaultCluster1, p.testSlug()) + createNamespace(t, defaultCluster2, p.testSlug()) cm := createConfigMapObject(map[string]string{ "targetName": `{{ target.name }}`, "targetContext": `{{ target.context }}`, }, resourceOpts{ name: "cm", - namespace: p.projectName, + namespace: p.testSlug(), }) if withDeploymentYaml { @@ -37,25 +37,25 @@ func prepareNoTargetTest(t *testing.T, name string, withDeploymentYaml bool) *te func testNoTarget(t *testing.T, withDeploymentYaml bool) { t.Parallel() - p := prepareNoTargetTest(t, "no-target", withDeploymentYaml) + p := prepareNoTargetTest(t, withDeploymentYaml) p.KluctlMust("deploy", "--yes") - cm := assertConfigMapExists(t, defaultCluster1, p.projectName, "cm") - assertConfigMapNotExists(t, defaultCluster2, p.projectName, "cm") + cm := assertConfigMapExists(t, defaultCluster1, p.testSlug(), "cm") + assertConfigMapNotExists(t, defaultCluster2, p.testSlug(), "cm") assert.Equal(t, map[string]any{ "targetName": "", "targetContext": defaultCluster1.Context, }, cm.Object["data"]) p.KluctlMust("deploy", "--yes", "-T", "override-name") - cm = assertConfigMapExists(t, defaultCluster1, p.projectName, "cm") + cm = assertConfigMapExists(t, defaultCluster1, p.testSlug(), "cm") assert.Equal(t, map[string]any{ "targetName": "override-name", "targetContext": defaultCluster1.Context, }, cm.Object["data"]) p.KluctlMust("deploy", "--yes", "-T", "override-name", "--context", defaultCluster2.Context) - cm = assertConfigMapExists(t, defaultCluster2, p.projectName, "cm") + cm = assertConfigMapExists(t, defaultCluster2, p.testSlug(), "cm") assert.Equal(t, map[string]any{ "targetName": "override-name", "targetContext": defaultCluster2.Context, diff --git a/e2e/project.go b/e2e/project.go index 2e137e841..41fd915eb 100644 --- a/e2e/project.go +++ b/e2e/project.go @@ -3,6 +3,7 @@ package e2e import ( "fmt" "github.com/go-git/go-git/v5" + "github.com/huandu/xstrings" "github.com/imdario/mergo" test_utils "github.com/kluctl/kluctl/v2/internal/test-utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" @@ -18,20 +19,17 @@ import ( ) type testProject struct { - t *testing.T - extraEnv []string - projectName string + t *testing.T + extraEnv []string mergedKubeconfig string gitServer *test_utils.GitServer } -func (p *testProject) init(t *testing.T, k *test_utils.EnvTestCluster, projectName string) { +func (p *testProject) init(t *testing.T, k *test_utils.EnvTestCluster) { p.t = t p.gitServer = test_utils.NewGitServer(t) - p.projectName = projectName - p.gitServer.GitInit(p.getKluctlProjectRepo()) p.updateKluctlYaml(func(o *uo.UnstructuredObject) error { @@ -41,7 +39,7 @@ func (p *testProject) init(t *testing.T, k *test_utils.EnvTestCluster, projectNa return nil }) - tmpFile, err := os.CreateTemp("", projectName+"-kubeconfig-") + tmpFile, err := os.CreateTemp("", p.testSlug()+"-kubeconfig-") if err != nil { t.Fatal(err) } @@ -53,6 +51,12 @@ func (p *testProject) init(t *testing.T, k *test_utils.EnvTestCluster, projectNa p.mergeKubeconfig(k) } +func (p *testProject) testSlug() string { + n := p.t.Name() + n = xstrings.ToKebabCase(n) + return n +} + func (p *testProject) mergeKubeconfig(k *test_utils.EnvTestCluster) { p.updateMergedKubeconfig(func(config *clientcmdapi.Config) { nkcfg, err := clientcmd.Load(k.Kubeconfig) @@ -88,7 +92,7 @@ func (p *testProject) updateKluctlYaml(update func(o *uo.UnstructuredObject) err func (p *testProject) updateDeploymentYaml(dir string, update func(o *uo.UnstructuredObject) error) { p.updateYaml(filepath.Join(dir, "deployment.yml"), func(o *uo.UnstructuredObject) error { if dir == "." { - o.SetNestedField(p.projectName, "commonLabels", "project_name") + o.SetNestedField(p.testSlug(), "commonLabels", "project_name") } return update(o) }, "") diff --git a/e2e/seal_test.go b/e2e/seal_test.go index 9f5e00490..c5b9268bf 100644 --- a/e2e/seal_test.go +++ b/e2e/seal_test.go @@ -97,20 +97,20 @@ func addProxyVars(p *testProject) { f(1, defaultCluster2, certServer2) } -func prepareSealTest(t *testing.T, k *test_utils.EnvTestCluster, namespace string, secrets map[string]string, varsSources []*uo.UnstructuredObject, proxy bool) *testProject { +func prepareSealTest(t *testing.T, k *test_utils.EnvTestCluster, secrets map[string]string, varsSources []*uo.UnstructuredObject, proxy bool) *testProject { p := &testProject{} - p.init(t, k, fmt.Sprintf("seal-%s", namespace)) + p.init(t, k) if proxy { addProxyVars(p) } - createNamespace(t, k, namespace) + createNamespace(t, k, p.testSlug()) addSecretsSet(p, "test", varsSources) addSecretsSetToTarget(p, "test-target", "test") - addSecretDeployment(p, "secret-deployment", secrets, resourceOpts{name: "secret", namespace: namespace}) + addSecretDeployment(p, "secret-deployment", secrets, resourceOpts{name: "secret", namespace: p.testSlug()}) return p } @@ -162,9 +162,8 @@ func assertSealedSecret(t *testing.T, k *test_utils.EnvTestCluster, namespace st func TestSeal_WithOperator(t *testing.T) { t.Parallel() k := defaultCluster1 - namespace := "seal-with-operator" - p := prepareSealTest(t, k, namespace, + p := prepareSealTest(t, k, map[string]string{ "s1": "{{ secrets.s1 }}", "s2": "{{ secrets.s2 }}", @@ -184,7 +183,7 @@ func TestSeal_WithOperator(t *testing.T) { assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) p.KluctlMust("deploy", "--yes", "-t", "test-target") - assertSealedSecret(t, k, namespace, "secret", certServer1.certHash, map[string]string{ + assertSealedSecret(t, k, p.testSlug(), "secret", certServer1.certHash, map[string]string{ "s1": "v1", "s2": "v2", }) @@ -194,13 +193,12 @@ func TestSeal_WithBootstrap(t *testing.T) { // this test must NOT run in parallel k := defaultCluster1 - namespace := "seal-with-bootstrap" // deleting the crd causes kluctl to not recognize the operator, so it will do a bootstrap _ = k.DynamicClient.Resource(apiextensionsv1.SchemeGroupVersion.WithResource("customresourcedefinitions")). Delete(context.Background(), "sealedsecrets.bitnami.com", metav1.DeleteOptions{}) - p := prepareSealTest(t, k, namespace, + p := prepareSealTest(t, k, map[string]string{ "s1": "{{ secrets.s1 }}", "s2": "{{ secrets.s2 }}", @@ -233,7 +231,7 @@ func TestSeal_WithBootstrap(t *testing.T) { certHash, err := seal.HashPublicKey(cert) assert.NoError(t, err) - assertSealedSecret(t, k, namespace, "secret", certHash, map[string]string{ + assertSealedSecret(t, k, p.testSlug(), "secret", certHash, map[string]string{ "s1": "v1", "s2": "v2", }) @@ -243,9 +241,8 @@ func TestSeal_MultipleVarSources(t *testing.T) { t.Parallel() k := defaultCluster1 - namespace := "seal-multiple-vs" - p := prepareSealTest(t, k, namespace, + p := prepareSealTest(t, k, map[string]string{ "s1": "{{ secrets.s1 }}", "s2": "{{ secrets.s2 }}", @@ -270,7 +267,7 @@ func TestSeal_MultipleVarSources(t *testing.T) { p.KluctlMust("deploy", "--yes", "-t", "test-target") - assertSealedSecret(t, k, namespace, "secret", certServer1.certHash, map[string]string{ + assertSealedSecret(t, k, p.testSlug(), "secret", certServer1.certHash, map[string]string{ "s1": "v1", "s2": "v2", }) @@ -280,9 +277,8 @@ func TestSeal_MultipleSecretSets(t *testing.T) { t.Parallel() k := defaultCluster1 - namespace := "seal-multiple-ss" - p := prepareSealTest(t, k, namespace, + p := prepareSealTest(t, k, map[string]string{ "s1": "{{ secrets.s1 }}", "s2": "{{ secrets.s2 }}", @@ -311,7 +307,7 @@ func TestSeal_MultipleSecretSets(t *testing.T) { p.KluctlMust("deploy", "--yes", "-t", "test-target") - assertSealedSecret(t, k, namespace, "secret", certServer1.certHash, map[string]string{ + assertSealedSecret(t, k, p.testSlug(), "secret", certServer1.certHash, map[string]string{ "s1": "v1", "s2": "v2", }) @@ -321,9 +317,8 @@ func TestSeal_MultipleTargets(t *testing.T) { t.Parallel() k := defaultCluster1 - namespace := "seal-multiple-targets" - p := prepareSealTest(t, k, namespace, + p := prepareSealTest(t, k, map[string]string{ "s1": "{{ secrets.s1 }}", "s2": "{{ secrets.s2 }}", @@ -348,7 +343,7 @@ func TestSeal_MultipleTargets(t *testing.T) { addSecretsSetToTarget(p, "test-target2", "test2") p.mergeKubeconfig(defaultCluster2) - createNamespace(t, defaultCluster2, namespace) + createNamespace(t, defaultCluster2, p.testSlug()) p.updateTarget("test-target", func(target *uo.UnstructuredObject) { _ = target.SetNestedField(defaultCluster1.Context, "context") }) @@ -366,11 +361,11 @@ func TestSeal_MultipleTargets(t *testing.T) { p.KluctlMust("deploy", "--yes", "-t", "test-target") p.KluctlMust("deploy", "--yes", "-t", "test-target2") - assertSealedSecret(t, defaultCluster1, namespace, "secret", certServer1.certHash, map[string]string{ + assertSealedSecret(t, defaultCluster1, p.testSlug(), "secret", certServer1.certHash, map[string]string{ "s1": "v1", "s2": "v2", }) - assertSealedSecret(t, defaultCluster2, namespace, "secret", certServer2.certHash, map[string]string{ + assertSealedSecret(t, defaultCluster2, p.testSlug(), "secret", certServer2.certHash, map[string]string{ "s1": "v3", "s2": "v4", }) @@ -380,7 +375,6 @@ func TestSeal_MultipleSecrets(t *testing.T) { t.Parallel() k := defaultCluster1 - namespace := "seal-multiple-secrets" secret1 := map[string]string{ "s1": "{{ secrets.s1 }}", @@ -389,7 +383,7 @@ func TestSeal_MultipleSecrets(t *testing.T) { "s2": "{{ secrets.s2 }}", } - p := prepareSealTest(t, k, namespace, + p := prepareSealTest(t, k, secret1, []*uo.UnstructuredObject{ uo.FromMap(map[string]interface{}{ @@ -399,7 +393,7 @@ func TestSeal_MultipleSecrets(t *testing.T) { }, }), }, true) - addSecretDeployment(p, "secret-deployment2", secret2, resourceOpts{name: "secret2", namespace: namespace}) + addSecretDeployment(p, "secret-deployment2", secret2, resourceOpts{name: "secret2", namespace: p.testSlug()}) p.KluctlMust("seal", "-t", "test-target") @@ -409,10 +403,10 @@ func TestSeal_MultipleSecrets(t *testing.T) { p.KluctlMust("deploy", "--yes", "-t", "test-target") - assertSealedSecret(t, k, namespace, "secret", certServer1.certHash, map[string]string{ + assertSealedSecret(t, k, p.testSlug(), "secret", certServer1.certHash, map[string]string{ "s1": "v1", }) - assertSealedSecret(t, k, namespace, "secret2", certServer1.certHash, map[string]string{ + assertSealedSecret(t, k, p.testSlug(), "secret2", certServer1.certHash, map[string]string{ "s2": "v2", }) } @@ -421,7 +415,6 @@ func TestSeal_MultipleSecretsInOneFile(t *testing.T) { t.Parallel() k := defaultCluster1 - namespace := "seal-multiple-secrets2" secret1 := map[string]string{ "s1": "{{ secrets.s1 }}", @@ -429,9 +422,8 @@ func TestSeal_MultipleSecretsInOneFile(t *testing.T) { secret2 := map[string]string{ "s2": "{{ secrets.s2 }}", } - secret2Text, _ := yaml.WriteYamlString(createSecretObject(secret2, resourceOpts{name: "secret2", namespace: namespace})) - p := prepareSealTest(t, k, namespace, + p := prepareSealTest(t, k, secret1, []*uo.UnstructuredObject{ uo.FromMap(map[string]interface{}{ @@ -442,6 +434,8 @@ func TestSeal_MultipleSecretsInOneFile(t *testing.T) { }), }, true) + secret2Text, _ := yaml.WriteYamlString(createSecretObject(secret2, resourceOpts{name: "secret2", namespace: p.testSlug()})) + p.gitServer.UpdateFile(p.getKluctlProjectRepo(), "secret-deployment/secret-secret.yml.sealme", func(f string) (string, error) { f += "---\n" f += secret2Text @@ -455,10 +449,10 @@ func TestSeal_MultipleSecretsInOneFile(t *testing.T) { p.KluctlMust("deploy", "--yes", "-t", "test-target") - assertSealedSecret(t, k, namespace, "secret", certServer1.certHash, map[string]string{ + assertSealedSecret(t, k, p.testSlug(), "secret", certServer1.certHash, map[string]string{ "s1": "v1", }) - assertSealedSecret(t, k, namespace, "secret2", certServer1.certHash, map[string]string{ + assertSealedSecret(t, k, p.testSlug(), "secret2", certServer1.certHash, map[string]string{ "s2": "v2", }) } @@ -467,9 +461,8 @@ func TestSeal_File(t *testing.T) { t.Parallel() k := defaultCluster1 - namespace := "seal-file" - p := prepareSealTest(t, k, namespace, + p := prepareSealTest(t, k, map[string]string{ "s1": "{{ secrets.s1 }}", "s2": "{{ secrets.s2 }}", @@ -497,7 +490,7 @@ func TestSeal_File(t *testing.T) { p.KluctlMust("deploy", "--yes", "-t", "test-target") - assertSealedSecret(t, k, namespace, "secret", certServer1.certHash, map[string]string{ + assertSealedSecret(t, k, p.testSlug(), "secret", certServer1.certHash, map[string]string{ "s1": "v1", "s2": "v2", }) @@ -507,7 +500,6 @@ func TestSeal_Vault(t *testing.T) { t.Parallel() k := defaultCluster1 - namespace := "seal-vault" server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { if request.URL.Path != "/v1/secret/data/secret" { @@ -531,7 +523,7 @@ func TestSeal_Vault(t *testing.T) { vaultUrl := server.URL - p := prepareSealTest(t, k, namespace, + p := prepareSealTest(t, k, map[string]string{ "s1": "{{ secrets.s1 }}", "s2": "{{ secrets.s2 }}", @@ -553,7 +545,7 @@ func TestSeal_Vault(t *testing.T) { p.KluctlMust("deploy", "--yes", "-t", "test-target") - assertSealedSecret(t, k, namespace, "secret", certServer1.certHash, map[string]string{ + assertSealedSecret(t, k, p.testSlug(), "secret", certServer1.certHash, map[string]string{ "s1": "v1", "s2": "v2", }) diff --git a/e2e/sops_test.go b/e2e/sops_test.go index 59e352b82..ee9af770a 100644 --- a/e2e/sops_test.go +++ b/e2e/sops_test.go @@ -14,9 +14,9 @@ func TestSopsVars(t *testing.T) { k := defaultCluster1 p := &testProject{} - p.init(t, k, "sops-vars") + p.init(t, k) - createNamespace(t, k, p.projectName) + createNamespace(t, k, p.testSlug()) p.updateTarget("test", nil) @@ -24,7 +24,7 @@ func TestSopsVars(t *testing.T) { "v1": "{{ test1.test2 }}", }, resourceOpts{ name: "cm", - namespace: p.projectName, + namespace: p.testSlug(), }) p.updateDeploymentYaml("", func(o *uo.UnstructuredObject) error { _ = o.SetNestedField([]map[string]any{ @@ -42,7 +42,7 @@ func TestSopsVars(t *testing.T) { p.KluctlMust("deploy", "--yes", "-t", "test") - cm := assertConfigMapExists(t, k, p.projectName, "cm") + cm := assertConfigMapExists(t, k, p.testSlug(), "cm") assertNestedFieldEquals(t, cm, map[string]any{ "v1": "42", }, "data") @@ -55,13 +55,13 @@ func TestSopsResources(t *testing.T) { k := defaultCluster1 p := &testProject{} - p.init(t, k, "sops-resources") + p.init(t, k) - createNamespace(t, k, p.projectName) + createNamespace(t, k, p.testSlug()) p.updateTarget("test", nil) p.updateDeploymentYaml("", func(o *uo.UnstructuredObject) error { - _ = o.SetNestedField(p.projectName, "overrideNamespace") + _ = o.SetNestedField(p.testSlug(), "overrideNamespace") return nil }) @@ -76,7 +76,7 @@ func TestSopsResources(t *testing.T) { p.KluctlMust("deploy", "--yes", "-t", "test") - cm := assertConfigMapExists(t, k, p.projectName, "encrypted-cm") + cm := assertConfigMapExists(t, k, p.testSlug(), "encrypted-cm") assertNestedFieldEquals(t, cm, map[string]any{ "a": "b", }, "data") From 8aeab4110760b3e4dc0ef8069770b997f8894d4d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 11 Nov 2022 16:31:04 +0100 Subject: [PATCH 1178/2916] docs: Add docs about plain Kustomize deployments --- docs/get-started.md | 16 +++++++++++++++- docs/reference/deployments/README.md | 7 +++++++ docs/reference/kluctl-project/README.md | 5 +++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/docs/get-started.md b/docs/get-started.md index 5393963e9..c92642802 100644 --- a/docs/get-started.md +++ b/docs/get-started.md @@ -50,8 +50,22 @@ for a given cluster. The `kluctl` command-line interface (CLI) is required to perform deployments. Read the [installation instructions](./installation.md) to figure out how to install it. -## Clone the kluctl examples +## Use Kluctl with a plain Kustomize deployment +The simplest way to test out Kluctl is to use an existing Kustomize deployment and just test out the CLI. For example, +try it with the [podtato-head project](https://github.com/podtato-head/podtato-head): + +```shell +$ git clone https://github.com/podtato-head/podtato-head.git +$ cd podtato-head/delivery/kustomize/base +$ kluctl deploy +``` + +Then try to modify something inside the Kustomize deployment and retry the `kluctl deploy` call. + +## Try out the Kluctl examples + +For more advanced examples, check out the Kluctl example projects. Clone the example project found at https://github.com/kluctl/kluctl-examples ```sh diff --git a/docs/reference/deployments/README.md b/docs/reference/deployments/README.md index b54534e4f..19145cca4 100644 --- a/docs/reference/deployments/README.md +++ b/docs/reference/deployments/README.md @@ -77,3 +77,10 @@ Some visualized files/directories have links attached, follow them to get more i Deployments are done in parallel, meaning that there are usually no order guarantees. The only way to somehow control order, is by placing [barriers](./deployment-yml.md#barriers) between kustomize deployments. You should however not overuse barriers, as they negatively impact the speed of kluctl. + +## Plain Kustomize + +It's also possible to use Kluctl on plain Kustomize deployments. Simply run `kluctl deploy` from inside the +folder of your `kustomization.yaml`. If you also don't have a `.kluctl.yaml`, you can also work without targets. + +Please note that pruning and deletion is not supported in this mode. \ No newline at end of file diff --git a/docs/reference/kluctl-project/README.md b/docs/reference/kluctl-project/README.md index f442ee3fb..ba14d1b65 100644 --- a/docs/reference/kluctl-project/README.md +++ b/docs/reference/kluctl-project/README.md @@ -42,3 +42,8 @@ targets: Please check the following sub-sections of this section to see which fields are allowed at the root level of `.kluctl.yaml`. 1. [targets](./targets) + +## Using Kluctl without .kluctl.yaml + +It's possible to use Kluctl without any `.kluctl.yaml`. In that case, all commands must be used without specifying the +target. From 551d76ad66fb0846f7b0fec6ef6535a2a4b0a5fa Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sat, 12 Nov 2022 22:01:46 +0100 Subject: [PATCH 1179/2916] docs: Mention SOPS in variable-sources.md --- docs/reference/templating/variable-sources.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/reference/templating/variable-sources.md b/docs/reference/templating/variable-sources.md index ad6ad30c2..2f4a4137f 100644 --- a/docs/reference/templating/variable-sources.md +++ b/docs/reference/templating/variable-sources.md @@ -54,6 +54,9 @@ vars: After which all included deployments and sub-deployments can use the jinja2 variables from `vars1.yaml`. +Kluctl also supports variable files encrypted with [SOPS](https://github.com/mozilla/sops). See the +[sops integration](../deployments/sops.md) integration for more details. + ### values An inline definition of variables. Example: @@ -77,6 +80,9 @@ vars: path: path/to/vars.yaml ``` +Kluctl also supports variable files encrypted with [SOPS](https://github.com/mozilla/sops). See the +[sops integration](../deployments/sops.md) integration for more details. + ### clusterConfigMap Loads a configmap from the target's cluster and loads the specified key's value as a yaml file into the jinja2 variables context. From f33d4185f5c445253709f405c7bc5e7ce3b085a5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Nov 2022 22:10:08 +0000 Subject: [PATCH 1180/2916] chore(deps): Bump goreleaser/goreleaser-action from 2 to 3 Bumps [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action) from 2 to 3. - [Release notes](https://github.com/goreleaser/goreleaser-action/releases) - [Commits](https://github.com/goreleaser/goreleaser-action/compare/v2...v3) --- updated-dependencies: - dependency-name: goreleaser/goreleaser-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b5e3dd1f8..dd4ea8163 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -46,7 +46,7 @@ jobs: restore-keys: | ${{ runner.os }}-goreleaser- - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v2 + uses: goreleaser/goreleaser-action@v3 with: distribution: goreleaser version: latest From 4ec6a5e533c831b9c7ed91c13d445a6cf5d9c822 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Nov 2022 22:12:48 +0000 Subject: [PATCH 1181/2916] chore(deps): Bump k8s.io/client-go from 0.25.3 to 0.25.4 Bumps [k8s.io/client-go](https://github.com/kubernetes/client-go) from 0.25.3 to 0.25.4. - [Release notes](https://github.com/kubernetes/client-go/releases) - [Changelog](https://github.com/kubernetes/client-go/blob/master/CHANGELOG.md) - [Commits](https://github.com/kubernetes/client-go/compare/v0.25.3...v0.25.4) --- updated-dependencies: - dependency-name: k8s.io/client-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++---- go.sum | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index cd6924cbe..114f968a3 100644 --- a/go.mod +++ b/go.mod @@ -47,10 +47,10 @@ require ( gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 helm.sh/helm/v3 v3.10.2 - k8s.io/api v0.25.3 + k8s.io/api v0.25.4 k8s.io/apiextensions-apiserver v0.25.3 - k8s.io/apimachinery v0.25.3 - k8s.io/client-go v0.25.3 + k8s.io/apimachinery v0.25.4 + k8s.io/client-go v0.25.4 k8s.io/klog/v2 v2.80.1 sigs.k8s.io/kustomize/api v0.12.1 sigs.k8s.io/kustomize/kyaml v0.13.9 @@ -59,6 +59,7 @@ require ( require ( github.com/go-logr/logr v1.2.3 + github.com/huandu/xstrings v1.3.2 go.mozilla.org/sops/v3 v3.7.3 sigs.k8s.io/controller-runtime v0.13.0 ) @@ -156,7 +157,6 @@ require ( github.com/hashicorp/vault/sdk v0.6.0 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef // indirect - github.com/huandu/xstrings v1.3.2 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect diff --git a/go.sum b/go.sum index 83a6e4635..3d9ef5775 100644 --- a/go.sum +++ b/go.sum @@ -1307,18 +1307,18 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.25.3 h1:Q1v5UFfYe87vi5H7NU0p4RXC26PPMT8KOpr1TLQbCMQ= -k8s.io/api v0.25.3/go.mod h1:o42gKscFrEVjHdQnyRenACrMtbuJsVdP+WVjqejfzmI= +k8s.io/api v0.25.4 h1:3YO8J4RtmG7elEgaWMb4HgmpS2CfY1QlaOz9nwB+ZSs= +k8s.io/api v0.25.4/go.mod h1:IG2+RzyPQLllQxnhzD8KQNEu4c4YvyDTpSMztf4A0OQ= k8s.io/apiextensions-apiserver v0.25.3 h1:bfI4KS31w2f9WM1KLGwnwuVlW3RSRPuIsfNF/3HzR0k= k8s.io/apiextensions-apiserver v0.25.3/go.mod h1:ZJqwpCkxIx9itilmZek7JgfUAM0dnTsA48I4krPqRmo= -k8s.io/apimachinery v0.25.3 h1:7o9ium4uyUOM76t6aunP0nZuex7gDf8VGwkR5RcJnQc= -k8s.io/apimachinery v0.25.3/go.mod h1:jaF9C/iPNM1FuLl7Zuy5b9v+n35HGSh6AQ4HYRkCqwo= +k8s.io/apimachinery v0.25.4 h1:CtXsuaitMESSu339tfhVXhQrPET+EiWnIY1rcurKnAc= +k8s.io/apimachinery v0.25.4/go.mod h1:jaF9C/iPNM1FuLl7Zuy5b9v+n35HGSh6AQ4HYRkCqwo= k8s.io/apiserver v0.25.3 h1:m7+xGuG5+KYAnEsqaFtDyWMkmMMEOFYlu+NlWv5qSBI= k8s.io/apiserver v0.25.3/go.mod h1:9bT47iM2fzRuhICJpM/RcQR9sqDDfZ7Yw60h0p3JW08= k8s.io/cli-runtime v0.25.3 h1:Zs7P7l7db/5J+KDePOVtDlArAa9pZXaDinGWGZl0aM8= k8s.io/cli-runtime v0.25.3/go.mod h1:InHHsjkyW5hQsILJGpGjeruiDZT/R0OkROQgD6GzxO4= -k8s.io/client-go v0.25.3 h1:oB4Dyl8d6UbfDHD8Bv8evKylzs3BXzzufLiO27xuPs0= -k8s.io/client-go v0.25.3/go.mod h1:t39LPczAIMwycjcXkVc+CB+PZV69jQuNx4um5ORDjQA= +k8s.io/client-go v0.25.4 h1:3RNRDffAkNU56M/a7gUfXaEzdhZlYhoW8dgViGy5fn8= +k8s.io/client-go v0.25.4/go.mod h1:8trHCAC83XKY0wsBIpbirZU4NTUpbuhc2JnI7OruGZw= k8s.io/component-base v0.25.3 h1:UrsxciGdrCY03ULT1h/S/gXFCOPnLhUVwSyx+hM/zq4= k8s.io/component-base v0.25.3/go.mod h1:WYoS8L+IlTZgU7rhAl5Ctpw0WdMxDfCC5dkxcEFa/TI= k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= From b07d6489a20d2086ce682299a2e9b01a5746af47 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Nov 2022 22:13:03 +0000 Subject: [PATCH 1182/2916] chore(deps): Bump sigs.k8s.io/controller-runtime from 0.13.0 to 0.13.1 Bumps [sigs.k8s.io/controller-runtime](https://github.com/kubernetes-sigs/controller-runtime) from 0.13.0 to 0.13.1. - [Release notes](https://github.com/kubernetes-sigs/controller-runtime/releases) - [Changelog](https://github.com/kubernetes-sigs/controller-runtime/blob/master/RELEASE.md) - [Commits](https://github.com/kubernetes-sigs/controller-runtime/compare/v0.13.0...v0.13.1) --- updated-dependencies: - dependency-name: sigs.k8s.io/controller-runtime dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index cd6924cbe..fd9a08fd4 100644 --- a/go.mod +++ b/go.mod @@ -59,8 +59,9 @@ require ( require ( github.com/go-logr/logr v1.2.3 + github.com/huandu/xstrings v1.3.2 go.mozilla.org/sops/v3 v3.7.3 - sigs.k8s.io/controller-runtime v0.13.0 + sigs.k8s.io/controller-runtime v0.13.1 ) require ( @@ -156,7 +157,6 @@ require ( github.com/hashicorp/vault/sdk v0.6.0 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef // indirect - github.com/huandu/xstrings v1.3.2 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect diff --git a/go.sum b/go.sum index 83a6e4635..bb045f8fb 100644 --- a/go.sum +++ b/go.sum @@ -1334,8 +1334,8 @@ oras.land/oras-go v1.2.1/go.mod h1:3N11Z5E3c4ZzOjroCl1RtAdB4yNAYl7A27j2SVf913A= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/controller-runtime v0.13.0 h1:iqa5RNciy7ADWnIc8QxCbOX5FEKVR3uxVxKHRMc2WIQ= -sigs.k8s.io/controller-runtime v0.13.0/go.mod h1:Zbz+el8Yg31jubvAEyglRZGdLAjplZl+PgtYNI6WNTI= +sigs.k8s.io/controller-runtime v0.13.1 h1:tUsRCSJVM1QQOOeViGeX3GMT3dQF1eePPw6sEE3xSlg= +sigs.k8s.io/controller-runtime v0.13.1/go.mod h1:Zbz+el8Yg31jubvAEyglRZGdLAjplZl+PgtYNI6WNTI= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/kustomize/api v0.12.1 h1:7YM7gW3kYBwtKvoY216ZzY+8hM+lV53LUayghNRJ0vM= From 7b3a7f341e86cab645fac9b76e2c7f777e5ecfa2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Nov 2022 22:13:19 +0000 Subject: [PATCH 1183/2916] chore(deps): Bump golang.org/x/net from 0.1.0 to 0.2.0 Bumps [golang.org/x/net](https://github.com/golang/net) from 0.1.0 to 0.2.0. - [Release notes](https://github.com/golang/net/releases) - [Commits](https://github.com/golang/net/compare/v0.1.0...v0.2.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 6 +++--- go.sum | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index cd6924cbe..cc815544c 100644 --- a/go.mod +++ b/go.mod @@ -39,10 +39,10 @@ require ( github.com/whilp/git-urls v1.0.0 github.com/xanzy/ssh-agent v0.3.3-0.20220920102508-0fa644ba07f4 golang.org/x/crypto v0.1.0 - golang.org/x/net v0.1.0 + golang.org/x/net v0.2.0 golang.org/x/sync v0.1.0 golang.org/x/sys v0.2.0 - golang.org/x/term v0.1.0 + golang.org/x/term v0.2.0 golang.org/x/text v0.4.0 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 @@ -59,6 +59,7 @@ require ( require ( github.com/go-logr/logr v1.2.3 + github.com/huandu/xstrings v1.3.2 go.mozilla.org/sops/v3 v3.7.3 sigs.k8s.io/controller-runtime v0.13.0 ) @@ -156,7 +157,6 @@ require ( github.com/hashicorp/vault/sdk v0.6.0 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef // indirect - github.com/huandu/xstrings v1.3.2 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect diff --git a/go.sum b/go.sum index 83a6e4635..cac032fe8 100644 --- a/go.sum +++ b/go.sum @@ -954,8 +954,8 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1062,8 +1062,8 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= -golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From 04d450f6a13fa32a5c9c8ef93e988c7b04b0f7b5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Nov 2022 22:13:59 +0000 Subject: [PATCH 1184/2916] chore(deps): Bump github.com/hashicorp/vault/api from 1.8.1 to 1.8.2 Bumps [github.com/hashicorp/vault/api](https://github.com/hashicorp/vault) from 1.8.1 to 1.8.2. - [Release notes](https://github.com/hashicorp/vault/releases) - [Changelog](https://github.com/hashicorp/vault/blob/main/CHANGELOG.md) - [Commits](https://github.com/hashicorp/vault/compare/v1.8.1...v1.8.2) --- updated-dependencies: - dependency-name: github.com/hashicorp/vault/api dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index cd6924cbe..7553e9e47 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/gobwas/glob v0.2.3 // indirect github.com/golang-jwt/jwt/v4 v4.4.2 github.com/google/go-containerregistry v0.12.1 - github.com/hashicorp/vault/api v1.8.1 + github.com/hashicorp/vault/api v1.8.2 github.com/hexops/gotextdiff v1.0.3 github.com/imdario/mergo v0.3.13 github.com/jinzhu/copier v0.3.5 @@ -59,6 +59,7 @@ require ( require ( github.com/go-logr/logr v1.2.3 + github.com/huandu/xstrings v1.3.2 go.mozilla.org/sops/v3 v3.7.3 sigs.k8s.io/controller-runtime v0.13.0 ) @@ -156,7 +157,6 @@ require ( github.com/hashicorp/vault/sdk v0.6.0 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef // indirect - github.com/huandu/xstrings v1.3.2 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect diff --git a/go.sum b/go.sum index 83a6e4635..473fff58b 100644 --- a/go.sum +++ b/go.sum @@ -473,8 +473,8 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hashicorp/vault/api v1.8.1 h1:bMieWIe6dAlqAAPReZO/8zYtXaWUg/21umwqGZpEjCI= -github.com/hashicorp/vault/api v1.8.1/go.mod h1:uJrw6D3y9Rv7hhmS17JQC50jbPDAZdjZoTtrCCxxs7E= +github.com/hashicorp/vault/api v1.8.2 h1:C7OL9YtOtwQbTKI9ogB0A1wffRbCN+rH/LLCHO3d8HM= +github.com/hashicorp/vault/api v1.8.2/go.mod h1:ML8aYzBIhY5m1MD1B2Q0JV89cC85YVH4t5kBaZiyVaE= github.com/hashicorp/vault/sdk v0.6.0 h1:6Z+In5DXHiUfZvIZdMx7e2loL1PPyDjA4bVh9ZTIAhs= github.com/hashicorp/vault/sdk v0.6.0/go.mod h1:+DRpzoXIdMvKc88R4qxr+edwy/RvH5QK8itmxLiDHLc= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= From 3758a64805fbdbbd4d382df7b3604103bba75541 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 14 Nov 2022 14:37:41 +0100 Subject: [PATCH 1185/2916] feat: Remove --local-deployment from helm-pull/helm-update --- cmd/kluctl/commands/cmd_helm_pull.go | 17 +++++++++++------ cmd/kluctl/commands/cmd_helm_update.go | 19 ++++++++++--------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/cmd/kluctl/commands/cmd_helm_pull.go b/cmd/kluctl/commands/cmd_helm_pull.go index 90d8c9fc8..74adaf8cf 100644 --- a/cmd/kluctl/commands/cmd_helm_pull.go +++ b/cmd/kluctl/commands/cmd_helm_pull.go @@ -4,15 +4,15 @@ import ( "fmt" "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/deployment" + git2 "github.com/kluctl/kluctl/v2/pkg/git" "github.com/kluctl/kluctl/v2/pkg/status" "io/fs" + "os" "path/filepath" ) type helmPullCmd struct { args.HelmCredentials - - LocalDeployment string `group:"project" help:"Local deployment directory. Defaults to current directory"` } func (cmd *helmPullCmd) Help() string { @@ -22,12 +22,17 @@ pulling is only needed when really required (e.g. when the chart version changes } func (cmd *helmPullCmd) Run() error { - rootPath := "." - if cmd.LocalDeployment != "" { - rootPath = cmd.LocalDeployment + cwd, err := os.Getwd() + if err != nil { + return err + } + + gitRootPath, err := git2.DetectGitRepositoryRoot(cwd) + if err != nil { + return err } - err := filepath.WalkDir(rootPath, func(p string, d fs.DirEntry, err error) error { + err = filepath.WalkDir(cwd, func(p string, d fs.DirEntry, err error) error { fname := filepath.Base(p) if fname == "helm-chart.yml" || fname == "helm-chart.yaml" { s := status.Start(cliCtx, "Pulling for %s", p) diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go index 55c30b8e8..9210c9e38 100644 --- a/cmd/kluctl/commands/cmd_helm_update.go +++ b/cmd/kluctl/commands/cmd_helm_update.go @@ -8,15 +8,15 @@ import ( git2 "github.com/kluctl/kluctl/v2/pkg/git" "github.com/kluctl/kluctl/v2/pkg/status" "io/fs" + "os" "path/filepath" ) type helmUpdateCmd struct { args.HelmCredentials - LocalDeployment string `group:"project" help:"Local deployment directory. Defaults to current directory"` - Upgrade bool `group:"misc" help:"Write new versions into helm-chart.yaml and perform helm-pull afterwards"` - Commit bool `group:"misc" help:"Create a git commit for every updated chart"` + Upgrade bool `group:"misc" help:"Write new versions into helm-chart.yaml and perform helm-pull afterwards"` + Commit bool `group:"misc" help:"Create a git commit for every updated chart"` } func (cmd *helmUpdateCmd) Help() string { @@ -24,16 +24,17 @@ func (cmd *helmUpdateCmd) Help() string { } func (cmd *helmUpdateCmd) Run() error { - rootPath := "." - if cmd.LocalDeployment != "" { - rootPath = cmd.LocalDeployment + cwd, err := os.Getwd() + if err != nil { + return err } - gitRootPath, err := git2.DetectGitRepositoryRoot(rootPath) + + gitRootPath, err := git2.DetectGitRepositoryRoot(cwd) if err != nil { return err } - err = filepath.WalkDir(rootPath, func(p string, d fs.DirEntry, err error) error { + err = filepath.WalkDir(cwd, func(p string, d fs.DirEntry, err error) error { fname := filepath.Base(p) if fname == "helm-chart.yml" || fname == "helm-chart.yaml" { statusPrefix := filepath.Base(filepath.Dir(p)) @@ -134,7 +135,7 @@ func (cmd *helmUpdateCmd) Run() error { return err } for p := range gitFiles { - absPath, err := filepath.Abs(filepath.Join(rootPath, p)) + absPath, err := filepath.Abs(filepath.Join(cwd, p)) if err != nil { return err } From 87978f3772aa7c0810b61acc77a88c2f8dd6936a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 14 Nov 2022 14:38:13 +0100 Subject: [PATCH 1186/2916] feat: Better status reporting for helm-pull/helm-update --- cmd/kluctl/commands/cmd_helm_pull.go | 21 +++++++++++++++++---- cmd/kluctl/commands/cmd_helm_update.go | 6 +++++- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/cmd/kluctl/commands/cmd_helm_pull.go b/cmd/kluctl/commands/cmd_helm_pull.go index 74adaf8cf..dc6f251de 100644 --- a/cmd/kluctl/commands/cmd_helm_pull.go +++ b/cmd/kluctl/commands/cmd_helm_pull.go @@ -35,16 +35,29 @@ func (cmd *helmPullCmd) Run() error { err = filepath.WalkDir(cwd, func(p string, d fs.DirEntry, err error) error { fname := filepath.Base(p) if fname == "helm-chart.yml" || fname == "helm-chart.yaml" { - s := status.Start(cliCtx, "Pulling for %s", p) + statusPrefix, err := filepath.Rel(gitRootPath, filepath.Dir(p)) + if err != nil { + return err + } + + s := status.Start(cliCtx, "%s: Pulling Chart %s", statusPrefix) chart, err := deployment.NewHelmChart(p) if err != nil { - s.FailedWithMessage(err.Error()) + s.FailedWithMessage("%s: %s", statusPrefix, err.Error()) return err } + chartName, err := chart.GetChartName() + if err != nil { + s.FailedWithMessage("%s: %s", statusPrefix, err.Error()) + return err + } + + s.Update("%s: Pulling Chart %s with version %s", statusPrefix, chartName, *chart.Config.ChartVersion) + creds := cmd.HelmCredentials.FindCredentials(*chart.Config.Repo, chart.Config.CredentialsId) if chart.Config.CredentialsId != nil && creds == nil { - err := fmt.Errorf("no credentials provided for %s", p) + err := fmt.Errorf("%s: no credentials provided", statusPrefix) s.FailedWithMessage(err.Error()) return err } @@ -52,7 +65,7 @@ func (cmd *helmPullCmd) Run() error { err = chart.Pull(cliCtx) if err != nil { - s.FailedWithMessage(err.Error()) + s.FailedWithMessage("%s: %s", statusPrefix, err.Error()) return err } s.Success() diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go index 9210c9e38..0f629a099 100644 --- a/cmd/kluctl/commands/cmd_helm_update.go +++ b/cmd/kluctl/commands/cmd_helm_update.go @@ -37,7 +37,11 @@ func (cmd *helmUpdateCmd) Run() error { err = filepath.WalkDir(cwd, func(p string, d fs.DirEntry, err error) error { fname := filepath.Base(p) if fname == "helm-chart.yml" || fname == "helm-chart.yaml" { - statusPrefix := filepath.Base(filepath.Dir(p)) + statusPrefix, err := filepath.Rel(gitRootPath, filepath.Dir(p)) + if err != nil { + return err + } + s := status.Start(cliCtx, "%s: Checking for updates", statusPrefix) defer s.Failed() From b7ef0e513bce388523e2bb2b0f120559487fb92c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 14 Nov 2022 14:48:30 +0100 Subject: [PATCH 1187/2916] feat: Paralellize helm-pull --- cmd/kluctl/commands/cmd_helm_pull.go | 99 +++++++++++++++++----------- pkg/utils/limited_routine.go | 36 ++++++++++ 2 files changed, 98 insertions(+), 37 deletions(-) create mode 100644 pkg/utils/limited_routine.go diff --git a/cmd/kluctl/commands/cmd_helm_pull.go b/cmd/kluctl/commands/cmd_helm_pull.go index dc6f251de..87d7cb732 100644 --- a/cmd/kluctl/commands/cmd_helm_pull.go +++ b/cmd/kluctl/commands/cmd_helm_pull.go @@ -2,13 +2,17 @@ package commands import ( "fmt" + "github.com/hashicorp/go-multierror" "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/deployment" git2 "github.com/kluctl/kluctl/v2/pkg/git" "github.com/kluctl/kluctl/v2/pkg/status" + "github.com/kluctl/kluctl/v2/pkg/utils" + "golang.org/x/sync/semaphore" "io/fs" "os" "path/filepath" + "sync" ) type helmPullCmd struct { @@ -32,50 +36,71 @@ func (cmd *helmPullCmd) Run() error { return err } + var errs *multierror.Error + var wg sync.WaitGroup + var mutex sync.Mutex + sem := semaphore.NewWeighted(8) + err = filepath.WalkDir(cwd, func(p string, d fs.DirEntry, err error) error { fname := filepath.Base(p) - if fname == "helm-chart.yml" || fname == "helm-chart.yaml" { - statusPrefix, err := filepath.Rel(gitRootPath, filepath.Dir(p)) - if err != nil { - return err - } - - s := status.Start(cliCtx, "%s: Pulling Chart %s", statusPrefix) - chart, err := deployment.NewHelmChart(p) - if err != nil { - s.FailedWithMessage("%s: %s", statusPrefix, err.Error()) - return err - } - - chartName, err := chart.GetChartName() - if err != nil { - s.FailedWithMessage("%s: %s", statusPrefix, err.Error()) - return err - } - - s.Update("%s: Pulling Chart %s with version %s", statusPrefix, chartName, *chart.Config.ChartVersion) - - creds := cmd.HelmCredentials.FindCredentials(*chart.Config.Repo, chart.Config.CredentialsId) - if chart.Config.CredentialsId != nil && creds == nil { - err := fmt.Errorf("%s: no credentials provided", statusPrefix) - s.FailedWithMessage(err.Error()) - return err - } - chart.SetCredentials(creds) - - err = chart.Pull(cliCtx) - if err != nil { - s.FailedWithMessage("%s: %s", statusPrefix, err.Error()) - return err - } - s.Success() + if fname != "helm-chart.yml" && fname != "helm-chart.yaml" { + return nil } + + wg.Add(1) + utils.GoLimitedMultiError(cliCtx, sem, &errs, &mutex, func() error { + defer wg.Done() + return doPull(gitRootPath, p, cmd.HelmCredentials) + }) + return nil }) - + wg.Wait() if err != nil { + errs = multierror.Append(errs, err) + } + + if errs.ErrorOrNil() != nil { return fmt.Errorf("command failed") } - return err + return nil +} + +func doPull(gitRootPath string, p string, helmCredentials args.HelmCredentials) error { + statusPrefix, err := filepath.Rel(gitRootPath, filepath.Dir(p)) + if err != nil { + return err + } + + s := status.Start(cliCtx, "%s: Pulling Chart %s", statusPrefix) + doError := func(err error) error { + s.FailedWithMessage("%s: %s", statusPrefix, err.Error()) + return err + } + + chart, err := deployment.NewHelmChart(p) + if err != nil { + return doError(err) + } + + chartName, err := chart.GetChartName() + if err != nil { + return doError(err) + } + + s.Update("%s: Pulling Chart %s with version %s", statusPrefix, chartName, *chart.Config.ChartVersion) + + creds := helmCredentials.FindCredentials(*chart.Config.Repo, chart.Config.CredentialsId) + if chart.Config.CredentialsId != nil && creds == nil { + return doError(fmt.Errorf("no credentials provided")) + } + chart.SetCredentials(creds) + + err = chart.Pull(cliCtx) + if err != nil { + return doError(err) + } + s.Success() + return nil } diff --git a/pkg/utils/limited_routine.go b/pkg/utils/limited_routine.go new file mode 100644 index 000000000..7653bfc26 --- /dev/null +++ b/pkg/utils/limited_routine.go @@ -0,0 +1,36 @@ +package utils + +import ( + "context" + "github.com/hashicorp/go-multierror" + "golang.org/x/sync/semaphore" + "sync" +) + +func GoLimited(ctx context.Context, sem *semaphore.Weighted, fn func(), errCb func(err error)) { + go func() { + err := sem.Acquire(ctx, 1) + if err != nil { + if errCb != nil { + errCb(err) + } + return + } + defer sem.Release(1) + fn() + }() +} + +func GoLimitedMultiError(ctx context.Context, sem *semaphore.Weighted, merr **multierror.Error, mutex *sync.Mutex, fn func() error) { + errCb := func(err error) { + mutex.Lock() + defer mutex.Unlock() + *merr = multierror.Append(*merr, err) + } + GoLimited(ctx, sem, func() { + err := fn() + if err != nil { + errCb(err) + } + }, errCb) +} From c2ee8f1931849752ef11a76f6c52c33c840fd91f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 14 Nov 2022 14:49:37 +0100 Subject: [PATCH 1188/2916] refactor: Eaerly bailout --- cmd/kluctl/commands/cmd_helm_update.go | 177 +++++++++++++------------ 1 file changed, 89 insertions(+), 88 deletions(-) diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go index 0f629a099..42e71ac6d 100644 --- a/cmd/kluctl/commands/cmd_helm_update.go +++ b/cmd/kluctl/commands/cmd_helm_update.go @@ -36,129 +36,130 @@ func (cmd *helmUpdateCmd) Run() error { err = filepath.WalkDir(cwd, func(p string, d fs.DirEntry, err error) error { fname := filepath.Base(p) - if fname == "helm-chart.yml" || fname == "helm-chart.yaml" { - statusPrefix, err := filepath.Rel(gitRootPath, filepath.Dir(p)) + if fname != "helm-chart.yml" && fname != "helm-chart.yaml" { + return nil + } + statusPrefix, err := filepath.Rel(gitRootPath, filepath.Dir(p)) + if err != nil { + return err + } + + s := status.Start(cliCtx, "%s: Checking for updates", statusPrefix) + defer s.Failed() + + chart, err := deployment.NewHelmChart(p) + if err != nil { + s.Update("%s: Error while loading helm-chart.yaml: %v", statusPrefix, err) + return err + } + + creds := cmd.HelmCredentials.FindCredentials(*chart.Config.Repo, chart.Config.CredentialsId) + if chart.Config.CredentialsId != nil && creds == nil { + err := fmt.Errorf("%s: No credentials provided", statusPrefix) + s.FailedWithMessage(err.Error()) + return err + } + chart.SetCredentials(creds) + + newVersion, updated, err := chart.CheckUpdate() + if err != nil { + return err + } + if !updated { + s.Update("%s: Version %s is already up-to-date.", statusPrefix, *chart.Config.ChartVersion) + s.Success() + return nil + } + msg := fmt.Sprintf("%s: Chart has new version %s available. Old version is %s.", statusPrefix, newVersion, *chart.Config.ChartVersion) + if chart.Config.SkipUpdate { + msg += " skipUpdate is set to true." + } + s.Update(msg) + + if !cmd.Upgrade { + s.Success() + } else { + if chart.Config.SkipUpdate { + s.Update("%s: NOT upgrading chart as skipUpdate was set to true", statusPrefix) + s.Success() + return nil + } + + oldVersion := *chart.Config.ChartVersion + chart.Config.ChartVersion = &newVersion + err = chart.Save() if err != nil { return err } - s := status.Start(cliCtx, "%s: Checking for updates", statusPrefix) - defer s.Failed() + chartsDir := filepath.Join(filepath.Dir(p), "charts") - chart, err := deployment.NewHelmChart(p) + // we need to list all files contained inside the charts dir BEFORE doing the pull, so that we later + // know what got deleted + gitFiles := make(map[string]bool) + gitFiles[p] = true + err = filepath.WalkDir(chartsDir, func(p string, d fs.DirEntry, err error) error { + if !d.IsDir() { + gitFiles[p] = true + } + return nil + }) if err != nil { - s.Update("%s: Error while loading helm-chart.yaml: %v", statusPrefix, err) return err } - creds := cmd.HelmCredentials.FindCredentials(*chart.Config.Repo, chart.Config.CredentialsId) - if chart.Config.CredentialsId != nil && creds == nil { - err := fmt.Errorf("%s: No credentials provided", statusPrefix) - s.FailedWithMessage(err.Error()) - return err - } - chart.SetCredentials(creds) + s.Update("%s: Pulling new version", statusPrefix) + defer s.Failed() - newVersion, updated, err := chart.CheckUpdate() + err = chart.Pull(cliCtx) if err != nil { return err } - if !updated { - s.Update("%s: Version %s is already up-to-date.", statusPrefix, *chart.Config.ChartVersion) - s.Success() - return nil - } - msg := fmt.Sprintf("%s: Chart has new version %s available. Old version is %s.", statusPrefix, newVersion, *chart.Config.ChartVersion) - if chart.Config.SkipUpdate { - msg += " skipUpdate is set to true." - } - s.Update(msg) - if !cmd.Upgrade { - s.Success() - } else { - if chart.Config.SkipUpdate { - s.Update("%s: NOT upgrading chart as skipUpdate was set to true", statusPrefix) - s.Success() - return nil - } - - oldVersion := *chart.Config.ChartVersion - chart.Config.ChartVersion = &newVersion - err = chart.Save() - if err != nil { - return err + // and now list all files again to catch all new files + err = filepath.WalkDir(chartsDir, func(p string, d fs.DirEntry, err error) error { + if !d.IsDir() { + gitFiles[p] = true } + return nil + }) + if err != nil { + return err + } - chartsDir := filepath.Join(filepath.Dir(p), "charts") - - // we need to list all files contained inside the charts dir BEFORE doing the pull, so that we later - // know what got deleted - gitFiles := make(map[string]bool) - gitFiles[p] = true - err = filepath.WalkDir(chartsDir, func(p string, d fs.DirEntry, err error) error { - if !d.IsDir() { - gitFiles[p] = true - } - return nil - }) - if err != nil { - return err - } + if cmd.Commit { + commitMsg := fmt.Sprintf("Updated helm chart %s from %s to %s", filepath.Dir(p), oldVersion, newVersion) - s.Update("%s: Pulling new version", statusPrefix) - defer s.Failed() + s.Update(fmt.Sprintf("%s: Updating chart from %s to %s", statusPrefix, oldVersion, newVersion)) - err = chart.Pull(cliCtx) + r, err := git.PlainOpen(gitRootPath) if err != nil { return err } - - // and now list all files again to catch all new files - err = filepath.WalkDir(chartsDir, func(p string, d fs.DirEntry, err error) error { - if !d.IsDir() { - gitFiles[p] = true - } - return nil - }) + wt, err := r.Worktree() if err != nil { return err } - - if cmd.Commit { - commitMsg := fmt.Sprintf("Updated helm chart %s from %s to %s", filepath.Dir(p), oldVersion, newVersion) - - s.Update(fmt.Sprintf("%s: Updating chart from %s to %s", statusPrefix, oldVersion, newVersion)) - - r, err := git.PlainOpen(gitRootPath) + for p := range gitFiles { + absPath, err := filepath.Abs(filepath.Join(cwd, p)) if err != nil { return err } - wt, err := r.Worktree() + relToGit, err := filepath.Rel(gitRootPath, absPath) if err != nil { return err } - for p := range gitFiles { - absPath, err := filepath.Abs(filepath.Join(cwd, p)) - if err != nil { - return err - } - relToGit, err := filepath.Rel(gitRootPath, absPath) - if err != nil { - return err - } - _, err = wt.Add(relToGit) - if err != nil { - return err - } - } - _, err = wt.Commit(commitMsg, &git.CommitOptions{}) + _, err = wt.Add(relToGit) if err != nil { return err } } - s.Success() + _, err = wt.Commit(commitMsg, &git.CommitOptions{}) + if err != nil { + return err + } } + s.Success() } return nil }) From 9e7bcf7c4869a29bde28f399fdffa601c5177301 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 14 Nov 2022 15:34:49 +0100 Subject: [PATCH 1189/2916] feat: Paralellize helm-update --- cmd/kluctl/commands/cmd_helm_pull.go | 13 +- cmd/kluctl/commands/cmd_helm_update.go | 296 ++++++++++++++++--------- pkg/deployment/helm_chart.go | 44 ++-- 3 files changed, 221 insertions(+), 132 deletions(-) diff --git a/cmd/kluctl/commands/cmd_helm_pull.go b/cmd/kluctl/commands/cmd_helm_pull.go index 87d7cb732..e4c510993 100644 --- a/cmd/kluctl/commands/cmd_helm_pull.go +++ b/cmd/kluctl/commands/cmd_helm_pull.go @@ -50,7 +50,14 @@ func (cmd *helmPullCmd) Run() error { wg.Add(1) utils.GoLimitedMultiError(cliCtx, sem, &errs, &mutex, func() error { defer wg.Done() - return doPull(gitRootPath, p, cmd.HelmCredentials) + s := status.Start(cliCtx, "%s: Pulling Chart") + defer s.Failed() + err := doPull(gitRootPath, p, cmd.HelmCredentials, s) + if err != nil { + return err + } + s.Success() + return nil }) return nil @@ -67,13 +74,12 @@ func (cmd *helmPullCmd) Run() error { return nil } -func doPull(gitRootPath string, p string, helmCredentials args.HelmCredentials) error { +func doPull(gitRootPath string, p string, helmCredentials args.HelmCredentials, s *status.StatusContext) error { statusPrefix, err := filepath.Rel(gitRootPath, filepath.Dir(p)) if err != nil { return err } - s := status.Start(cliCtx, "%s: Pulling Chart %s", statusPrefix) doError := func(err error) error { s.FailedWithMessage("%s: %s", statusPrefix, err.Error()) return err @@ -101,6 +107,5 @@ func doPull(gitRootPath string, p string, helmCredentials args.HelmCredentials) if err != nil { return doError(err) } - s.Success() return nil } diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go index 42e71ac6d..d35bdb496 100644 --- a/cmd/kluctl/commands/cmd_helm_update.go +++ b/cmd/kluctl/commands/cmd_helm_update.go @@ -3,13 +3,17 @@ package commands import ( "fmt" "github.com/go-git/go-git/v5" + "github.com/hashicorp/go-multierror" "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/deployment" git2 "github.com/kluctl/kluctl/v2/pkg/git" "github.com/kluctl/kluctl/v2/pkg/status" + "github.com/kluctl/kluctl/v2/pkg/utils" + "golang.org/x/sync/semaphore" "io/fs" "os" "path/filepath" + "sync" ) type helmUpdateCmd struct { @@ -34,134 +38,214 @@ func (cmd *helmUpdateCmd) Run() error { return err } + var errs *multierror.Error + var wg sync.WaitGroup + var mutex sync.Mutex + sem := semaphore.NewWeighted(8) + + type updatedChart struct { + chart *deployment.HelmChart + newVersion string + oldVersion string + pullSuccess bool + } + var updatedCharts []*updatedChart + err = filepath.WalkDir(cwd, func(p string, d fs.DirEntry, err error) error { fname := filepath.Base(p) if fname != "helm-chart.yml" && fname != "helm-chart.yaml" { return nil } - statusPrefix, err := filepath.Rel(gitRootPath, filepath.Dir(p)) - if err != nil { - return err - } - s := status.Start(cliCtx, "%s: Checking for updates", statusPrefix) - defer s.Failed() + wg.Add(1) + utils.GoLimitedMultiError(cliCtx, sem, &errs, &mutex, func() error { + defer wg.Done() - chart, err := deployment.NewHelmChart(p) - if err != nil { - s.Update("%s: Error while loading helm-chart.yaml: %v", statusPrefix, err) - return err - } + chart, newVersion, updated, err := cmd.doCheckUpdate(gitRootPath, p) + if err != nil { + return err + } - creds := cmd.HelmCredentials.FindCredentials(*chart.Config.Repo, chart.Config.CredentialsId) - if chart.Config.CredentialsId != nil && creds == nil { - err := fmt.Errorf("%s: No credentials provided", statusPrefix) - s.FailedWithMessage(err.Error()) - return err - } - chart.SetCredentials(creds) + mutex.Lock() + defer mutex.Unlock() - newVersion, updated, err := chart.CheckUpdate() - if err != nil { - return err - } - if !updated { - s.Update("%s: Version %s is already up-to-date.", statusPrefix, *chart.Config.ChartVersion) - s.Success() + if updated { + updatedCharts = append(updatedCharts, &updatedChart{ + chart: chart, + newVersion: newVersion, + oldVersion: *chart.Config.ChartVersion, + }) + } return nil - } + }) + return nil + }) + wg.Wait() + if err != nil { + errs = multierror.Append(errs, err) + return errs.ErrorOrNil() + } + + if !cmd.Upgrade { + return errs.ErrorOrNil() + } + + for _, uc := range updatedCharts { + uc := uc + + wg.Add(1) + utils.GoLimitedMultiError(cliCtx, sem, &errs, &mutex, func() error { + defer wg.Done() + + err := cmd.pullAndCommitChart(gitRootPath, uc.chart, uc.oldVersion, uc.newVersion, &mutex) + if err != nil { + return err + } + return nil + }) + } + wg.Wait() + + if !cmd.Commit { + return errs.ErrorOrNil() + } + + return errs.ErrorOrNil() +} + +func (cmd *helmUpdateCmd) doCheckUpdate(gitRootPath string, p string) (*deployment.HelmChart, string, bool, error) { + statusPrefix, err := filepath.Rel(gitRootPath, filepath.Dir(p)) + if err != nil { + return nil, "", false, err + } + + s := status.Start(cliCtx, "%s: Checking for updates", statusPrefix) + doError := func(err error) (*deployment.HelmChart, string, bool, error) { + s.FailedWithMessage("%s: %s", statusPrefix, err.Error()) + return nil, "", false, err + } + + chart, err := deployment.NewHelmChart(p) + if err != nil { + return doError(err) + } + + creds := cmd.HelmCredentials.FindCredentials(*chart.Config.Repo, chart.Config.CredentialsId) + if chart.Config.CredentialsId != nil && creds == nil { + return doError(fmt.Errorf("no credentials provided")) + } + chart.SetCredentials(creds) + + newVersion, updated, err := chart.CheckUpdate() + if err != nil { + return doError(err) + } + if !updated { + s.Update("%s: Version %s is already up-to-date.", statusPrefix, *chart.Config.ChartVersion) + } else { msg := fmt.Sprintf("%s: Chart has new version %s available. Old version is %s.", statusPrefix, newVersion, *chart.Config.ChartVersion) if chart.Config.SkipUpdate { msg += " skipUpdate is set to true." } s.Update(msg) + } + s.Success() - if !cmd.Upgrade { - s.Success() - } else { - if chart.Config.SkipUpdate { - s.Update("%s: NOT upgrading chart as skipUpdate was set to true", statusPrefix) - s.Success() - return nil - } - - oldVersion := *chart.Config.ChartVersion - chart.Config.ChartVersion = &newVersion - err = chart.Save() - if err != nil { - return err - } + return chart, newVersion, updated, nil +} - chartsDir := filepath.Join(filepath.Dir(p), "charts") - - // we need to list all files contained inside the charts dir BEFORE doing the pull, so that we later - // know what got deleted - gitFiles := make(map[string]bool) - gitFiles[p] = true - err = filepath.WalkDir(chartsDir, func(p string, d fs.DirEntry, err error) error { - if !d.IsDir() { - gitFiles[p] = true - } - return nil - }) - if err != nil { - return err - } +func (cmd *helmUpdateCmd) pullAndCommitChart(gitRootPath string, chart *deployment.HelmChart, oldVersion string, newVersion string, mutex *sync.Mutex) error { + statusPrefix, err := filepath.Rel(gitRootPath, filepath.Dir(chart.ConfigFile)) + if err != nil { + return err + } - s.Update("%s: Pulling new version", statusPrefix) - defer s.Failed() + s := status.Start(cliCtx, "%s: Pulling Chart", statusPrefix) + defer s.Failed() - err = chart.Pull(cliCtx) - if err != nil { - return err - } + chart.Config.ChartVersion = &newVersion + err = chart.Save() + if err != nil { + return err + } - // and now list all files again to catch all new files - err = filepath.WalkDir(chartsDir, func(p string, d fs.DirEntry, err error) error { - if !d.IsDir() { - gitFiles[p] = true - } - return nil - }) - if err != nil { - return err - } + chartsDir, err := chart.GetChartDir() + if err != nil { + return err + } - if cmd.Commit { - commitMsg := fmt.Sprintf("Updated helm chart %s from %s to %s", filepath.Dir(p), oldVersion, newVersion) - - s.Update(fmt.Sprintf("%s: Updating chart from %s to %s", statusPrefix, oldVersion, newVersion)) - - r, err := git.PlainOpen(gitRootPath) - if err != nil { - return err - } - wt, err := r.Worktree() - if err != nil { - return err - } - for p := range gitFiles { - absPath, err := filepath.Abs(filepath.Join(cwd, p)) - if err != nil { - return err - } - relToGit, err := filepath.Rel(gitRootPath, absPath) - if err != nil { - return err - } - _, err = wt.Add(relToGit) - if err != nil { - return err - } - } - _, err = wt.Commit(commitMsg, &git.CommitOptions{}) - if err != nil { - return err - } - } - s.Success() + // we need to list all files contained inside the charts dir BEFORE doing the pull, so that we later + // know what got deleted + oldFiles := map[string]bool{} + err = filepath.WalkDir(chartsDir, func(p string, d fs.DirEntry, err error) error { + if d.IsDir() { + return nil + } + relToGit, err := filepath.Rel(gitRootPath, p) + if err != nil { + return err } + oldFiles[relToGit] = true return nil }) - return err + if err != nil { + return err + } + + err = doPull(gitRootPath, chart.ConfigFile, cmd.HelmCredentials, s) + if err != nil { + return err + } + + var toAdd []string + relToGit, err := filepath.Rel(gitRootPath, chart.ConfigFile) + if err != nil { + return err + } + toAdd = append(toAdd, relToGit) + + relToGit, err = filepath.Rel(gitRootPath, chartsDir) + if err != nil { + return err + } + toAdd = append(toAdd, relToGit) + + // figure out what got deleted + for p, _ := range oldFiles { + if !utils.IsFile(filepath.Join(gitRootPath, p)) { + toAdd = append(toAdd, p) + } + } + + s.Update("%s: Committing chart", statusPrefix) + + mutex.Lock() + defer mutex.Unlock() + + r, err := git.PlainOpen(gitRootPath) + if err != nil { + return err + } + wt, err := r.Worktree() + if err != nil { + return err + } + + for _, p := range toAdd { + _, err = wt.Add(p) + if err != nil { + return err + } + } + + commitMsg := fmt.Sprintf("Updated helm chart %s from %s to %s", statusPrefix, oldVersion, newVersion) + _, err = wt.Commit(commitMsg, &git.CommitOptions{}) + if err != nil { + return err + } + + s.Update("%s: Committed helm chart with version %s", statusPrefix, newVersion) + s.Success() + + return nil } diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index b7930f0f8..f5b8fcdc7 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -30,27 +30,27 @@ import ( "strings" ) -type helmChart struct { - configFile string +type HelmChart struct { + ConfigFile string Config *types.HelmChartConfig credentials *repo.Entry } -func NewHelmChart(configFile string) (*helmChart, error) { +func NewHelmChart(configFile string) (*HelmChart, error) { var config types.HelmChartConfig err := yaml.ReadYamlFile(configFile, &config) if err != nil { return nil, err } - hc := &helmChart{ - configFile: configFile, + hc := &HelmChart{ + ConfigFile: configFile, Config: &config, } return hc, nil } -func (c *helmChart) GetChartName() (string, error) { +func (c *HelmChart) GetChartName() (string, error) { if c.Config.Repo != nil && registry.IsOCI(*c.Config.Repo) { s := strings.Split(*c.Config.Repo, "/") chartName := s[len(s)-1] @@ -65,18 +65,18 @@ func (c *helmChart) GetChartName() (string, error) { return *c.Config.ChartName, nil } -func (c *helmChart) GetChartDir() (string, error) { +func (c *HelmChart) GetChartDir() (string, error) { chartName, err := c.GetChartName() if err != nil { return "", err } - dir := filepath.Dir(c.configFile) + dir := filepath.Dir(c.ConfigFile) targetDir := filepath.Join(dir, "charts") return securejoin.SecureJoin(targetDir, chartName) } -func (c *helmChart) GetOutputPath() string { +func (c *HelmChart) GetOutputPath() string { output := "helm-rendered.yaml" if c.Config.Output != nil { output = *c.Config.Output @@ -84,12 +84,12 @@ func (c *helmChart) GetOutputPath() string { return output } -func (c *helmChart) GetFullOutputPath() (string, error) { - dir := filepath.Dir(c.configFile) +func (c *HelmChart) GetFullOutputPath() (string, error) { + dir := filepath.Dir(c.ConfigFile) return securejoin.SecureJoin(dir, c.GetOutputPath()) } -func (c *helmChart) buildHelmConfig(k *k8s.K8sCluster) (*action.Configuration, error) { +func (c *HelmChart) buildHelmConfig(k *k8s.K8sCluster) (*action.Configuration, error) { rc, err := registry.NewClient() if err != nil { return nil, err @@ -101,7 +101,7 @@ func (c *helmChart) buildHelmConfig(k *k8s.K8sCluster) (*action.Configuration, e }, nil } -func (c *helmChart) Pull(ctx context.Context) error { +func (c *HelmChart) Pull(ctx context.Context) error { chartName, err := c.GetChartName() if err != nil { return err @@ -112,7 +112,7 @@ func (c *helmChart) Pull(ctx context.Context) error { return err } - targetDir := filepath.Join(filepath.Dir(c.configFile), "charts") + targetDir := filepath.Join(filepath.Dir(c.ConfigFile), "charts") _ = os.RemoveAll(chartDir) cfg, err := c.buildHelmConfig(nil) @@ -154,7 +154,7 @@ func (c *helmChart) Pull(ctx context.Context) error { return nil } -func (c *helmChart) CheckUpdate() (string, bool, error) { +func (c *HelmChart) CheckUpdate() (string, bool, error) { if c.Config.Repo != nil && registry.IsOCI(*c.Config.Repo) { return "", false, nil } @@ -203,7 +203,7 @@ func (c *helmChart) CheckUpdate() (string, bool, error) { return latestVersion, updated, nil } -func (c *helmChart) Render(ctx context.Context, k *k8s.K8sCluster) error { +func (c *HelmChart) Render(ctx context.Context, k *k8s.K8sCluster) error { chartName, err := c.GetChartName() if err != nil { return err @@ -215,7 +215,7 @@ func (c *helmChart) Render(ctx context.Context, k *k8s.K8sCluster) error { return nil } -func (c *helmChart) doRender(ctx context.Context, k *k8s.K8sCluster) error { +func (c *HelmChart) doRender(ctx context.Context, k *k8s.K8sCluster) error { chartDir, err := c.GetChartDir() if err != nil { return err @@ -224,7 +224,7 @@ func (c *helmChart) doRender(ctx context.Context, k *k8s.K8sCluster) error { if err != nil { return err } - valuesPath := yaml.FixPathExt(filepath.Join(filepath.Dir(c.configFile), "helm-values.yml")) + valuesPath := yaml.FixPathExt(filepath.Join(filepath.Dir(c.ConfigFile), "helm-values.yml")) var gvs []schema.GroupVersion if k != nil { @@ -345,7 +345,7 @@ func (c *helmChart) doRender(ctx context.Context, k *k8s.K8sCluster) error { return nil } -func (c *helmChart) parseRenderedManifests(s string) ([]*uo.UnstructuredObject, error) { +func (c *HelmChart) parseRenderedManifests(s string) ([]*uo.UnstructuredObject, error) { var parsed []*uo.UnstructuredObject duplicatesRemoved, err := yaml.RemoveDuplicateFields(strings.NewReader(s)) @@ -367,7 +367,7 @@ func (c *helmChart) parseRenderedManifests(s string) ([]*uo.UnstructuredObject, return parsed, nil } -func (c *helmChart) SetCredentials(credentials *repo.Entry) { +func (c *HelmChart) SetCredentials(credentials *repo.Entry) { c.credentials = credentials } @@ -388,6 +388,6 @@ func isTestHook(h *release.Hook) bool { return false } -func (c *helmChart) Save() error { - return yaml.WriteYamlFile(c.configFile, c.Config) +func (c *HelmChart) Save() error { + return yaml.WriteYamlFile(c.ConfigFile, c.Config) } From 41271471fb25026fe1120997c1202437636c9b0f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 14 Nov 2022 23:54:33 +0100 Subject: [PATCH 1190/2916] feat: Allow to use Helm without pre-pulling --- pkg/deployment/helm_chart.go | 129 +++++++++++++++++++++++++++++++++-- 1 file changed, 123 insertions(+), 6 deletions(-) diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index f5b8fcdc7..f259e6a31 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -2,6 +2,8 @@ package deployment import ( "context" + "crypto/sha256" + "encoding/hex" "fmt" securejoin "github.com/cyphar/filepath-securejoin" "github.com/kluctl/kluctl/v2/pkg/k8s" @@ -12,6 +14,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils/versions" "github.com/kluctl/kluctl/v2/pkg/yaml" "github.com/pkg/errors" + "github.com/rogpeppe/go-internal/lockedfile" "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart/loader" @@ -28,6 +31,7 @@ import ( "regexp" "sort" "strings" + "time" ) type HelmChart struct { @@ -101,19 +105,96 @@ func (c *HelmChart) buildHelmConfig(k *k8s.K8sCluster) (*action.Configuration, e }, nil } +func (c *HelmChart) checkNeedsPull(chartDir string, isTmp bool) (bool, bool, string, error) { + if !utils.IsDirectory(chartDir) { + return true, false, "", nil + } + + chartYamlPath := yaml.FixPathExt(filepath.Join(chartDir, "Chart.yaml")) + st, err := os.Stat(chartYamlPath) + if err != nil { + if os.IsNotExist(err) { + return true, false, "", nil + } + return false, false, "", err + } + if isTmp && time.Now().Sub(st.ModTime()) >= time.Hour*24 { + // MacOS will delete tmp files after 3 days, so lets be safe and re-pull every day + return true, false, "", nil + } + + chartYaml, err := uo.FromFile(chartYamlPath) + if err != nil { + return false, false, "", err + } + + version, _, _ := chartYaml.GetNestedString("version") + if version != *c.Config.ChartVersion { + return true, true, version, nil + } + return false, false, "", nil +} + func (c *HelmChart) Pull(ctx context.Context) error { - chartName, err := c.GetChartName() + chartDir, err := c.GetChartDir() if err != nil { return err } + return c.doPull(ctx, chartDir) +} - chartDir, err := c.GetChartDir() +func (c *HelmChart) pullTmpChart(ctx context.Context) (string, error) { + chartName, err := c.GetChartName() + if err != nil { + return "", err + } + + hash := sha256.New() + _, _ = fmt.Fprintf(hash, "%s\n", *c.Config.Repo) + _, _ = fmt.Fprintf(hash, "%s\n", chartName) + _, _ = fmt.Fprintf(hash, "%s\n", *c.Config.ChartVersion) + h := hex.EncodeToString(hash.Sum(nil)) + tmpDir := filepath.Join(utils.GetTmpBaseDir(), "helm-charts") + _ = os.MkdirAll(tmpDir, 0o700) + tmpDir = filepath.Join(tmpDir, fmt.Sprintf("%s-%s", chartName, h)) + + lockFile := tmpDir + ".lock" + lock, err := lockedfile.Create(lockFile) + if err != nil { + return "", err + } + defer lock.Close() + + needsPull, _, _, err := c.checkNeedsPull(tmpDir, true) + if err != nil { + return "", err + } + if !needsPull { + return tmpDir, nil + } + err = c.doPull(ctx, tmpDir) + if err != nil { + return "", err + } + return tmpDir, nil +} + +func (c *HelmChart) doPull(ctx context.Context, chartDir string) error { + chartName, err := c.GetChartName() if err != nil { return err } - targetDir := filepath.Join(filepath.Dir(c.ConfigFile), "charts") _ = os.RemoveAll(chartDir) + _ = os.MkdirAll(filepath.Dir(chartDir), 0o700) + + tmpDir, err := os.MkdirTemp(utils.GetTmpBaseDir(), "helm-pull-") + if err != nil { + return err + } + defer os.RemoveAll(tmpDir) + + tmpChartDir := filepath.Join(tmpDir, chartName) cfg, err := c.buildHelmConfig(nil) if err != nil { @@ -122,7 +203,7 @@ func (c *HelmChart) Pull(ctx context.Context) error { a := action.NewPullWithOpts(action.WithConfig(cfg)) a.Settings = cli.New() a.Untar = true - a.DestDir = targetDir + a.DestDir = tmpDir a.Version = *c.Config.ChartVersion if c.credentials != nil { @@ -142,15 +223,22 @@ func (c *HelmChart) Pull(ctx context.Context) error { a.RepoURL = *c.Config.Repo out, err = a.Run(chartName) } + if err != nil { + return err + } + // a bug in the Pull command causes this directory to be created by accident - _ = os.RemoveAll(chartDir + fmt.Sprintf("-%s.tar.gz", a.Version)) - _ = os.RemoveAll(chartDir + fmt.Sprintf("-%s.tgz", a.Version)) + _ = os.RemoveAll(tmpChartDir + fmt.Sprintf("-%s.tar.gz", a.Version)) + _ = os.RemoveAll(tmpChartDir + fmt.Sprintf("-%s.tgz", a.Version)) if out != "" { status.PlainText(ctx, out) } + + err = os.Rename(tmpChartDir, chartDir) if err != nil { return err } + return nil } @@ -216,10 +304,39 @@ func (c *HelmChart) Render(ctx context.Context, k *k8s.K8sCluster) error { } func (c *HelmChart) doRender(ctx context.Context, k *k8s.K8sCluster) error { + chartName, err := c.GetChartName() + if err != nil { + return err + } + chartDir, err := c.GetChartDir() if err != nil { return err } + + needsPull, versionChanged, prePulledVersion, err := c.checkNeedsPull(chartDir, false) + if err != nil { + return err + } + if needsPull { + if versionChanged { + return fmt.Errorf("pre-pulled Helm Chart %s need to be pulled (call 'kluctl helm-pull'). "+ + "Desired version is %s while pre-pulled version is %s", chartName, *c.Config.ChartVersion, prePulledVersion) + } else { + status.Warning(ctx, "Warning, need to pull Helm Chart %s with version %s. "+ + "Please consider pre-pulling it with 'kluctl helm-pull'", chartName, *c.Config.ChartVersion) + } + + s := status.Start(ctx, "Pulling Helm Chart %s with version %s", chartName, *c.Config.ChartVersion) + defer s.Failed() + + chartDir, err = c.pullTmpChart(ctx) + if err != nil { + return err + } + s.Success() + } + outputPath, err := c.GetFullOutputPath() if err != nil { return err From 5cfde34fd9dc29fa7412a54221aadbe56fbd3c4f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Nov 2022 10:27:16 +0100 Subject: [PATCH 1191/2916] fix: Don't swallow errors when using simple status handler --- pkg/status/status.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/status/status.go b/pkg/status/status.go index 50dedcdd2..2bf1ec85a 100644 --- a/pkg/status/status.go +++ b/pkg/status/status.go @@ -201,7 +201,7 @@ func (s *StatusContext) FailedWithMessage(msg string, args ...any) { if s.finished { return } - s.Update(msg, args...) + s.UpdateAndInfoFallback(msg, args...) s.Failed() } From 752653b6fc74f23e9f05bab94df2dfbaca872687 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Nov 2022 10:27:50 +0100 Subject: [PATCH 1192/2916] fix: Show prefix in helmPullCmd.Run() --- cmd/kluctl/commands/cmd_helm_pull.go | 16 ++++++++-------- cmd/kluctl/commands/cmd_helm_update.go | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cmd/kluctl/commands/cmd_helm_pull.go b/cmd/kluctl/commands/cmd_helm_pull.go index e4c510993..c4f9ff8f4 100644 --- a/cmd/kluctl/commands/cmd_helm_pull.go +++ b/cmd/kluctl/commands/cmd_helm_pull.go @@ -47,12 +47,17 @@ func (cmd *helmPullCmd) Run() error { return nil } + statusPrefix, err := filepath.Rel(gitRootPath, filepath.Dir(p)) + if err != nil { + return err + } + wg.Add(1) utils.GoLimitedMultiError(cliCtx, sem, &errs, &mutex, func() error { defer wg.Done() - s := status.Start(cliCtx, "%s: Pulling Chart") + s := status.Start(cliCtx, "%s: Pulling Chart", statusPrefix) defer s.Failed() - err := doPull(gitRootPath, p, cmd.HelmCredentials, s) + err := doPull(statusPrefix, p, cmd.HelmCredentials, s) if err != nil { return err } @@ -74,12 +79,7 @@ func (cmd *helmPullCmd) Run() error { return nil } -func doPull(gitRootPath string, p string, helmCredentials args.HelmCredentials, s *status.StatusContext) error { - statusPrefix, err := filepath.Rel(gitRootPath, filepath.Dir(p)) - if err != nil { - return err - } - +func doPull(statusPrefix string, p string, helmCredentials args.HelmCredentials, s *status.StatusContext) error { doError := func(err error) error { s.FailedWithMessage("%s: %s", statusPrefix, err.Error()) return err diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go index d35bdb496..1d5f696a8 100644 --- a/cmd/kluctl/commands/cmd_helm_update.go +++ b/cmd/kluctl/commands/cmd_helm_update.go @@ -192,7 +192,7 @@ func (cmd *helmUpdateCmd) pullAndCommitChart(gitRootPath string, chart *deployme return err } - err = doPull(gitRootPath, chart.ConfigFile, cmd.HelmCredentials, s) + err = doPull(statusPrefix, chart.ConfigFile, cmd.HelmCredentials, s) if err != nil { return err } From 03f9a74ac736c5e099e4759703db655eddb16a21 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Nov 2022 14:34:24 +0100 Subject: [PATCH 1193/2916] tests: Fix incomplete stdout/stderr reading from sub-processes --- e2e/utils.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/e2e/utils.go b/e2e/utils.go index 7e7ac0d26..f8a20f9ad 100644 --- a/e2e/utils.go +++ b/e2e/utils.go @@ -13,6 +13,7 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "os/exec" "reflect" + "sync" "testing" ) @@ -69,7 +70,9 @@ func runHelper(t *testing.T, cmd *exec.Cmd) (string, string, error) { return "", "", err } + var wg sync.WaitGroup stdReader := func(testLogPrefix string, buf io.StringWriter, pipe io.Reader) { + defer wg.Done() scanner := bufio.NewScanner(pipe) for scanner.Scan() { l := scanner.Text() @@ -81,9 +84,15 @@ func runHelper(t *testing.T, cmd *exec.Cmd) (string, string, error) { stdoutBuf := bytes.NewBuffer(nil) stderrBuf := bytes.NewBuffer(nil) + wg.Add(2) go stdReader("stdout: ", stdoutBuf, stdoutPipe) go stdReader("stderr: ", stderrBuf, stderrPipe) - err = cmd.Run() + err = cmd.Start() + if err != nil { + return "", "", err + } + wg.Wait() + err = cmd.Wait() return stdoutBuf.String(), stderrBuf.String(), err } From 91689d19493c4a987a7e987682839ac697ecb061 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Nov 2022 14:34:54 +0100 Subject: [PATCH 1194/2916] fix: Also print warnings/errors with simple status handler --- cmd/kluctl/commands/cmd_helm_update.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go index 1d5f696a8..6c1ed737a 100644 --- a/cmd/kluctl/commands/cmd_helm_update.go +++ b/cmd/kluctl/commands/cmd_helm_update.go @@ -141,13 +141,13 @@ func (cmd *helmUpdateCmd) doCheckUpdate(gitRootPath string, p string) (*deployme return doError(err) } if !updated { - s.Update("%s: Version %s is already up-to-date.", statusPrefix, *chart.Config.ChartVersion) + s.UpdateAndInfoFallback("%s: Version %s is already up-to-date.", statusPrefix, *chart.Config.ChartVersion) } else { msg := fmt.Sprintf("%s: Chart has new version %s available. Old version is %s.", statusPrefix, newVersion, *chart.Config.ChartVersion) if chart.Config.SkipUpdate { msg += " skipUpdate is set to true." } - s.Update(msg) + s.UpdateAndInfoFallback(msg) } s.Success() From 3fa462a8683dc69def02319afd3ce7362a58b549 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Nov 2022 14:35:14 +0100 Subject: [PATCH 1195/2916] fix: Properly skip chart updates if skipUpdate is true --- cmd/kluctl/commands/cmd_helm_update.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go index 6c1ed737a..ae560adab 100644 --- a/cmd/kluctl/commands/cmd_helm_update.go +++ b/cmd/kluctl/commands/cmd_helm_update.go @@ -69,7 +69,7 @@ func (cmd *helmUpdateCmd) Run() error { mutex.Lock() defer mutex.Unlock() - if updated { + if !chart.Config.SkipUpdate && updated { updatedCharts = append(updatedCharts, &updatedChart{ chart: chart, newVersion: newVersion, From 4ab482756681ad79b0427ab8423397a45062fb60 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Nov 2022 14:35:37 +0100 Subject: [PATCH 1196/2916] fix: Use temporary cache when downloading Helm index files --- pkg/deployment/helm_chart.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index f259e6a31..a7cf18b55 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -265,6 +265,12 @@ func (c *HelmChart) CheckUpdate() (string, bool, error) { return "", false, err } + r.CachePath, err = os.MkdirTemp(utils.GetTmpBaseDir(), "helm-check-update-") + if err != nil { + return "", false, err + } + defer os.RemoveAll(r.CachePath) + indexFile, err := r.DownloadIndexFile() if err != nil { return "", false, err From f698dd97accb8470227a0effa4950b2351251d9e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Nov 2022 14:35:47 +0100 Subject: [PATCH 1197/2916] tests: Implement Helm tests --- e2e/helm_test.go | 395 +++++++++++++++++++ e2e/test-helm-chart/.helmignore | 23 ++ e2e/test-helm-chart/Chart.yaml | 6 + e2e/test-helm-chart/resources.go | 8 + e2e/test-helm-chart/templates/_helpers.tpl | 62 +++ e2e/test-helm-chart/templates/configmap.yaml | 10 + e2e/test-helm-chart/values.yaml | 3 + internal/test-utils/git_server.go | 7 +- 8 files changed, 513 insertions(+), 1 deletion(-) create mode 100644 e2e/helm_test.go create mode 100644 e2e/test-helm-chart/.helmignore create mode 100644 e2e/test-helm-chart/Chart.yaml create mode 100644 e2e/test-helm-chart/resources.go create mode 100644 e2e/test-helm-chart/templates/_helpers.tpl create mode 100644 e2e/test-helm-chart/templates/configmap.yaml create mode 100644 e2e/test-helm-chart/values.yaml diff --git a/e2e/helm_test.go b/e2e/helm_test.go new file mode 100644 index 000000000..a0a73552a --- /dev/null +++ b/e2e/helm_test.go @@ -0,0 +1,395 @@ +package e2e + +import ( + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing/object" + test_resources "github.com/kluctl/kluctl/v2/e2e/test-helm-chart" + "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/yaml" + "github.com/stretchr/testify/assert" + "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/cli" + "helm.sh/helm/v3/pkg/cli/values" + "helm.sh/helm/v3/pkg/getter" + "helm.sh/helm/v3/pkg/repo" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "sort" + "testing" +) + +func createHelmPackage(t *testing.T, name string, version string) string { + tmpDir := t.TempDir() + err := utils.FsCopyDir(test_resources.HelmChartFS, ".", tmpDir) + if err != nil { + t.Fatal(err) + } + + c, err := uo.FromFile(filepath.Join(tmpDir, "Chart.yaml")) + if err != nil { + t.Fatal(err) + } + + _ = c.SetNestedField(name, "name") + _ = c.SetNestedField(version, "version") + + err = yaml.WriteYamlFile(filepath.Join(tmpDir, "Chart.yaml"), c) + if err != nil { + t.Fatal(err) + } + + settings := cli.New() + client := action.NewPackage() + client.Destination = tmpDir + valueOpts := &values.Options{} + p := getter.All(settings) + vals, err := valueOpts.MergeValues(p) + retName, err := client.Run(tmpDir, vals) + if err != nil { + t.Fatal(err) + } + + return retName +} + +type repoChart struct { + chartName string + version string +} + +func createHelmRepo(t *testing.T, charts []repoChart) string { + tmpDir := t.TempDir() + + for _, c := range charts { + tgz := createHelmPackage(t, c.chartName, c.version) + _ = utils.CopyFile(tgz, filepath.Join(tmpDir, filepath.Base(tgz))) + } + + s := httptest.NewServer(http.FileServer(http.FS(os.DirFS(tmpDir)))) + t.Cleanup(s.Close) + + i, err := repo.IndexDirectory(tmpDir, s.URL) + if err != nil { + t.Fatal(err) + } + + i.SortEntries() + err = i.WriteFile(filepath.Join(tmpDir, "index.yaml"), 0644) + if err != nil { + t.Fatal(err) + } + + return s.URL +} + +func addHelmDeployment(p *testProject, dir string, repoUrl string, chartName, version string, releaseName string, namespace string, values map[string]any) { + + p.addKustomizeDeployment(dir, []kustomizeResource{ + {name: "helm-rendered.yaml"}, + }, nil) + + p.updateYaml(filepath.Join(dir, "helm-chart.yaml"), func(o *uo.UnstructuredObject) error { + *o = *uo.FromMap(map[string]interface{}{ + "helmChart": map[string]any{ + "repo": repoUrl, + "chartName": chartName, + "chartVersion": version, + "releaseName": releaseName, + "namespace": namespace, + }, + }) + return nil + }, "") + + if values != nil { + p.updateYaml(filepath.Join(dir, "helm-values.yaml"), func(o *uo.UnstructuredObject) error { + *o = *uo.FromMap(values) + return nil + }, "") + } +} + +func TestHelmNoPrePull(t *testing.T) { + t.Parallel() + + k := defaultCluster1 + + p := &testProject{} + p.init(t, k) + + createNamespace(t, k, p.testSlug()) + + repoUrl := createHelmRepo(p.t, []repoChart{ + {chartName: "test-chart1", version: "0.1.0"}, + }) + + p.updateTarget("test", nil) + addHelmDeployment(p, "helm1", repoUrl, "test-chart1", "0.1.0", "test-helm1", p.testSlug(), nil) + p.KluctlMust("deploy", "--yes", "-t", "test") + assertConfigMapExists(t, k, p.testSlug(), "test-helm1-test-chart1") +} + +func TestHelmPrePull(t *testing.T) { + t.Parallel() + + k := defaultCluster1 + + p := &testProject{} + p.init(t, k) + + createNamespace(t, k, p.testSlug()) + + repoUrl := createHelmRepo(p.t, []repoChart{ + {chartName: "test-chart1", version: "0.1.0"}, + {chartName: "test-chart2", version: "0.1.0"}, + }) + + p.updateTarget("test", nil) + addHelmDeployment(p, "helm1", repoUrl, "test-chart1", "0.1.0", "test-helm1", p.testSlug(), nil) + + p.KluctlMust("helm-pull") + assert.FileExists(t, filepath.Join(p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()), "helm1/charts/test-chart1/Chart.yaml")) + p.KluctlMust("deploy", "--yes", "-t", "test") + assertConfigMapExists(t, k, p.testSlug(), "test-helm1-test-chart1") + + addHelmDeployment(p, "helm2", repoUrl, "test-chart2", "0.1.0", "test-helm2", p.testSlug(), nil) + + p.KluctlMust("helm-pull") + assert.FileExists(t, filepath.Join(p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()), "helm2/charts/test-chart2/Chart.yaml")) + p.KluctlMust("deploy", "--yes", "-t", "test") + assertConfigMapExists(t, k, p.testSlug(), "test-helm2-test-chart2") +} + +func TestHelmManualUpgrade(t *testing.T) { + t.Parallel() + + k := defaultCluster1 + + p := &testProject{} + p.init(t, k) + + createNamespace(t, k, p.testSlug()) + + repoUrl := createHelmRepo(p.t, []repoChart{ + {chartName: "test-chart1", version: "0.1.0"}, + {chartName: "test-chart1", version: "0.2.0"}, + }) + + p.updateTarget("test", nil) + addHelmDeployment(p, "helm1", repoUrl, "test-chart1", "0.1.0", "test-helm1", p.testSlug(), nil) + + p.KluctlMust("helm-pull") + assert.FileExists(t, filepath.Join(p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()), "helm1/charts/test-chart1/Chart.yaml")) + p.KluctlMust("deploy", "--yes", "-t", "test") + cm := assertConfigMapExists(t, k, p.testSlug(), "test-helm1-test-chart1") + v, _, _ := cm.GetNestedString("data", "version") + assert.Equal(t, "0.1.0", v) + + p.updateYaml("helm1/helm-chart.yaml", func(o *uo.UnstructuredObject) error { + _ = o.SetNestedField("0.2.0", "helmChart", "chartVersion") + return nil + }, "") + + p.KluctlMust("helm-pull") + p.KluctlMust("deploy", "--yes", "-t", "test") + cm = assertConfigMapExists(t, k, p.testSlug(), "test-helm1-test-chart1") + v, _, _ = cm.GetNestedString("data", "version") + assert.Equal(t, "0.2.0", v) +} + +func testHelmUpdate(t *testing.T, upgrade bool, commit bool) { + t.Parallel() + + k := defaultCluster1 + + p := &testProject{} + p.init(t, k) + + createNamespace(t, k, p.testSlug()) + + repoUrl := createHelmRepo(p.t, []repoChart{ + {chartName: "test-chart1", version: "0.1.0"}, + {chartName: "test-chart1", version: "0.2.0"}, + {chartName: "test-chart2", version: "0.1.0"}, + {chartName: "test-chart2", version: "0.3.0"}, + }) + + p.updateTarget("test", nil) + addHelmDeployment(p, "helm1", repoUrl, "test-chart1", "0.1.0", "test-helm1", p.testSlug(), nil) + addHelmDeployment(p, "helm2", repoUrl, "test-chart2", "0.1.0", "test-helm2", p.testSlug(), nil) + addHelmDeployment(p, "helm3", repoUrl, "test-chart1", "0.1.0", "test-helm3", p.testSlug(), nil) + + p.updateYaml("helm3/helm-chart.yaml", func(o *uo.UnstructuredObject) error { + _ = o.SetNestedField(true, "helmChart", "skipUpdate") + return nil + }, "") + + p.KluctlMust("helm-pull") + assert.FileExists(t, filepath.Join(p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()), "helm1/charts/test-chart1/Chart.yaml")) + assert.FileExists(t, filepath.Join(p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()), "helm2/charts/test-chart2/Chart.yaml")) + assert.FileExists(t, filepath.Join(p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()), "helm3/charts/test-chart1/Chart.yaml")) + + args := []string{"helm-update"} + if upgrade { + args = append(args, "--upgrade") + } + if commit { + args = append(args, "--commit") + } + + _, stderr := p.KluctlMust(args...) + assert.Contains(t, stderr, "helm1: Chart has new version 0.2.0 available.") + assert.Contains(t, stderr, "helm2: Chart has new version 0.3.0 available.") + assert.Contains(t, stderr, "helm3: Chart has new version 0.2.0 available. Old version is 0.1.0. skipUpdate is set to true.") + + c1, err := uo.FromFile(filepath.Join(p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()), "helm1/charts/test-chart1/Chart.yaml")) + assert.NoError(t, err) + c2, err := uo.FromFile(filepath.Join(p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()), "helm2/charts/test-chart2/Chart.yaml")) + assert.NoError(t, err) + c3, err := uo.FromFile(filepath.Join(p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()), "helm3/charts/test-chart1/Chart.yaml")) + assert.NoError(t, err) + + v1, _, _ := c1.GetNestedString("version") + v2, _, _ := c2.GetNestedString("version") + v3, _, _ := c3.GetNestedString("version") + if upgrade { + assert.Equal(t, "0.2.0", v1) + assert.Equal(t, "0.3.0", v2) + assert.Equal(t, "0.1.0", v3) + } else { + assert.Equal(t, "0.1.0", v1) + assert.Equal(t, "0.1.0", v2) + assert.Equal(t, "0.1.0", v3) + } + + if commit { + r := p.gitServer.GetGitRepo(p.getKluctlProjectRepo()) + + commits, err := r.Log(&git.LogOptions{}) + assert.NoError(t, err) + var commitList []object.Commit + err = commits.ForEach(func(commit *object.Commit) error { + commitList = append(commitList, *commit) + return nil + }) + assert.NoError(t, err) + + commitList = commitList[0:2] + sort.Slice(commitList, func(i, j int) bool { + return commitList[i].Message < commitList[j].Message + }) + + assert.Equal(t, "Updated helm chart helm1 from 0.1.0 to 0.2.0", commitList[0].Message) + assert.Equal(t, "Updated helm chart helm2 from 0.1.0 to 0.3.0", commitList[1].Message) + } +} + +func TestHelmUpdate(t *testing.T) { + testHelmUpdate(t, false, false) +} + +func TestHelmUpdateAndUpgrade(t *testing.T) { + testHelmUpdate(t, true, false) +} + +func TestHelmUpdateAndUpgradeAndCommit(t *testing.T) { + testHelmUpdate(t, true, true) +} + +func TestHelmValues(t *testing.T) { + t.Parallel() + + k := defaultCluster1 + + p := &testProject{} + p.init(t, k) + + createNamespace(t, k, p.testSlug()) + + repoUrl := createHelmRepo(p.t, []repoChart{ + {chartName: "test-chart1", version: "0.1.0"}, + {chartName: "test-chart2", version: "0.1.0"}, + }) + + values1 := map[string]any{ + "data": map[string]any{ + "a": "x1", + "b": "y1", + }, + } + values2 := map[string]any{ + "data": map[string]any{ + "a": "x2", + "b": "y2", + }, + } + values3 := map[string]any{ + "data": map[string]any{ + "a": "{{ args.a }}", + "b": "{{ args.b }}", + }, + } + + p.updateTarget("test", nil) + addHelmDeployment(p, "helm1", repoUrl, "test-chart1", "0.1.0", "test-helm1", p.testSlug(), values1) + addHelmDeployment(p, "helm2", repoUrl, "test-chart2", "0.1.0", "test-helm2", p.testSlug(), values2) + addHelmDeployment(p, "helm3", repoUrl, "test-chart1", "0.1.0", "test-helm3", p.testSlug(), values3) + + p.KluctlMust("helm-pull") + p.KluctlMust("deploy", "--yes", "-t", "test", "-aa=a", "-ab=b") + + cm1 := assertConfigMapExists(t, k, p.testSlug(), "test-helm1-test-chart1") + cm2 := assertConfigMapExists(t, k, p.testSlug(), "test-helm2-test-chart2") + cm3 := assertConfigMapExists(t, k, p.testSlug(), "test-helm3-test-chart1") + + assert.Equal(t, map[string]any{ + "a": "x1", + "b": "y1", + "version": "0.1.0", + }, cm1.Object["data"]) + assert.Equal(t, map[string]any{ + "a": "x2", + "b": "y2", + "version": "0.1.0", + }, cm2.Object["data"]) + assert.Equal(t, map[string]any{ + "a": "a", + "b": "b", + "version": "0.1.0", + }, cm3.Object["data"]) +} + +func TestHelmTemplateChartYaml(t *testing.T) { + t.Parallel() + + k := defaultCluster1 + + p := &testProject{} + p.init(t, k) + + createNamespace(t, k, p.testSlug()) + createNamespace(t, k, p.testSlug()+"-a") + createNamespace(t, k, p.testSlug()+"-b") + + repoUrl := createHelmRepo(p.t, []repoChart{ + {chartName: "test-chart1", version: "0.1.0"}, + {chartName: "test-chart2", version: "0.1.0"}, + }) + + p.updateTarget("test", nil) + addHelmDeployment(p, "helm1", repoUrl, "test-chart1", "0.1.0", "test-helm-{{ args.a }}", p.testSlug(), nil) + addHelmDeployment(p, "helm2", repoUrl, "test-chart2", "0.1.0", "test-helm-{{ args.b }}", p.testSlug(), nil) + addHelmDeployment(p, "helm3", repoUrl, "test-chart1", "0.1.0", "test-helm-ns", p.testSlug()+"-{{ args.a }}", nil) + addHelmDeployment(p, "helm4", repoUrl, "test-chart1", "0.1.0", "test-helm-ns", p.testSlug()+"-{{ args.b }}", nil) + + p.KluctlMust("helm-pull") + p.KluctlMust("deploy", "--yes", "-t", "test", "-aa=a", "-ab=b") + + assertConfigMapExists(t, k, p.testSlug(), "test-helm-a-test-chart1") + assertConfigMapExists(t, k, p.testSlug(), "test-helm-b-test-chart2") + assertConfigMapExists(t, k, p.testSlug()+"-a", "test-helm-ns-test-chart1") + assertConfigMapExists(t, k, p.testSlug()+"-b", "test-helm-ns-test-chart1") +} diff --git a/e2e/test-helm-chart/.helmignore b/e2e/test-helm-chart/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/e2e/test-helm-chart/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/e2e/test-helm-chart/Chart.yaml b/e2e/test-helm-chart/Chart.yaml new file mode 100644 index 000000000..0f3baa0c1 --- /dev/null +++ b/e2e/test-helm-chart/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: test-helm-chart +description: A Helm chart for Kubernetes +type: application +version: 0.1.0 +appVersion: "0.1.0" diff --git a/e2e/test-helm-chart/resources.go b/e2e/test-helm-chart/resources.go new file mode 100644 index 000000000..2ad97a675 --- /dev/null +++ b/e2e/test-helm-chart/resources.go @@ -0,0 +1,8 @@ +package test_resources + +import ( + "embed" +) + +//go:embed all:* +var HelmChartFS embed.FS diff --git a/e2e/test-helm-chart/templates/_helpers.tpl b/e2e/test-helm-chart/templates/_helpers.tpl new file mode 100644 index 000000000..bcbf57e17 --- /dev/null +++ b/e2e/test-helm-chart/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "test-helm-chart.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "test-helm-chart.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "test-helm-chart.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "test-helm-chart.labels" -}} +helm.sh/chart: {{ include "test-helm-chart.chart" . }} +{{ include "test-helm-chart.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "test-helm-chart.selectorLabels" -}} +app.kubernetes.io/name: {{ include "test-helm-chart.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "test-helm-chart.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "test-helm-chart.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/e2e/test-helm-chart/templates/configmap.yaml b/e2e/test-helm-chart/templates/configmap.yaml new file mode 100644 index 000000000..fe63edb28 --- /dev/null +++ b/e2e/test-helm-chart/templates/configmap.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "test-helm-chart.fullname" . }} + labels: + {{- include "test-helm-chart.labels" . | nindent 4 }} +data: + a: {{ .Values.data.a }} + b: {{ .Values.data.b }} + version: {{ .Chart.Version }} diff --git a/e2e/test-helm-chart/values.yaml b/e2e/test-helm-chart/values.yaml new file mode 100644 index 000000000..9d4b13a77 --- /dev/null +++ b/e2e/test-helm-chart/values.yaml @@ -0,0 +1,3 @@ +data: + a: v1 + b: v2 diff --git a/internal/test-utils/git_server.go b/internal/test-utils/git_server.go index 070e4c98e..f539dac91 100644 --- a/internal/test-utils/git_server.go +++ b/internal/test-utils/git_server.go @@ -243,11 +243,16 @@ func (p *GitServer) LocalRepoDir(repo string) string { return filepath.Join(p.baseDir, repo) } -func (p *GitServer) GetWorktree(repo string) *git.Worktree { +func (p *GitServer) GetGitRepo(repo string) *git.Repository { r, err := git.PlainOpen(p.LocalRepoDir(repo)) if err != nil { p.t.Fatal(err) } + return r +} + +func (p *GitServer) GetWorktree(repo string) *git.Worktree { + r := p.GetGitRepo(repo) wt, err := r.Worktree() if err != nil { p.t.Fatal(err) From bae9fe76d8e558911af350c2eaa96fa9e37af5bf Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Nov 2022 14:44:32 +0100 Subject: [PATCH 1198/2916] docs: Update helm.md to mention that pre-pulling is optional --- docs/reference/deployments/helm.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/reference/deployments/helm.md b/docs/reference/deployments/helm.md index 11794ea7d..bc49ddf0d 100644 --- a/docs/reference/deployments/helm.md +++ b/docs/reference/deployments/helm.md @@ -16,11 +16,17 @@ kluctl offers a simple-to-use Helm integration, which allows you to reuse many c The integration is split into 2 parts/steps/layers. The first is the management and pulling of the Helm Charts, while the second part handles configuration/customization and deployment of the chart. -Pulled Helm Charts are meant to be added to version control to ensure proper speed and consistency. +It is recommended to pre-pull Helm Charts with [`kluctl helm-pull`](../commands/helm-pull.md), which will store the +pulled charts near the `helm-chart.yaml` file (inside the `charts` sub-directory). It is however also possible (but not +recommended) to skip the pre-pulling phase and let kluctl pull Charts on-demand. + +When pre-pulling Helm Charts, you can also add the resulting Chart contents into version control. This is actually +recommended as it ensures that the deployment will always behave the same. It also allows pull-request based reviews +on third-party Helm Charts. ## How it works -Helm charts are not directly deployed via Helm. Instead, kluctl renders the Helm Chart into a single file and then +Helm charts are not directly installed via Helm. Instead, kluctl renders the Helm Chart into a single file and then hands over the rendered yaml to [kustomize](https://kustomize.io/). Rendering is done in combination with a provided `helm-values.yaml`, which contains the necessary values to configure the Helm Chart. @@ -82,7 +88,7 @@ resource. If `output` is omitted, the default value `helm-rendered.yaml` is used The url to the Helm repository where the Helm Chart is located. You can use hub.helm.sh to search for repositories and charts and then use the repos found there. -oci based repositories are also supported, for example: +OCI based repositories are also supported, for example: ```yaml helmChart: repo: oci://r.myreg.io/mycharts/pepper From 90bf499d8b81dba8ac89787f356336835d27fd3c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Nov 2022 16:14:35 +0100 Subject: [PATCH 1199/2916] fix: Fix false-positive cache hits when scheme changes --- pkg/registries/registries.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/registries/registries.go b/pkg/registries/registries.go index a280438bf..48556c1e8 100644 --- a/pkg/registries/registries.go +++ b/pkg/registries/registries.go @@ -367,7 +367,7 @@ func (rh *RegistryHelper) writeCachedResponse(key string, data []byte) { } func (rh *RegistryHelper) RoundTripCached(req *http.Request, extraKey string, onNew func(res *http.Response) error) (*http.Response, error) { - key := fmt.Sprintf("%s\n%s\n%s\n", req.Host, req.URL.Path, extraKey) + key := fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n", req.URL.Scheme, req.URL.Host, req.Host, req.URL.Path, extraKey) key = utils.Sha256String(key) isNew := false From 3447518f585d24e7b2e409fe67840ae883c4121e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Nov 2022 16:14:51 +0100 Subject: [PATCH 1200/2916] feat: Implement helm-update for OCI charts --- cmd/kluctl/commands/cmd_helm_update.go | 2 +- pkg/deployment/helm_chart.go | 29 ++++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go index ae560adab..4dca7ca47 100644 --- a/cmd/kluctl/commands/cmd_helm_update.go +++ b/cmd/kluctl/commands/cmd_helm_update.go @@ -136,7 +136,7 @@ func (cmd *helmUpdateCmd) doCheckUpdate(gitRootPath string, p string) (*deployme } chart.SetCredentials(creds) - newVersion, updated, err := chart.CheckUpdate() + newVersion, updated, err := chart.CheckUpdate(cliCtx) if err != nil { return doError(err) } diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index a7cf18b55..fb3b53926 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -7,6 +7,7 @@ import ( "fmt" securejoin "github.com/cyphar/filepath-securejoin" "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/registries" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" @@ -242,10 +243,34 @@ func (c *HelmChart) doPull(ctx context.Context, chartDir string) error { return nil } -func (c *HelmChart) CheckUpdate() (string, bool, error) { +func (c *HelmChart) CheckUpdate(ctx context.Context) (string, bool, error) { if c.Config.Repo != nil && registry.IsOCI(*c.Config.Repo) { - return "", false, nil + return c.checkUpdateOciRepo(ctx) } + return c.checkUpdateHelmRepo() +} + +func (c *HelmChart) checkUpdateOciRepo(ctx context.Context) (string, bool, error) { + rh := registries.NewRegistryHelper(ctx) + + imageName := strings.TrimPrefix(*c.Config.Repo, "oci://") + tags, err := rh.ListImageTags(imageName) + if err != nil { + return "", false, err + } + + var ls versions.LooseVersionSlice + for _, x := range tags { + ls = append(ls, versions.LooseVersion(x)) + } + sort.Stable(ls) + latestVersion := string(ls[len(ls)-1]) + + updated := latestVersion != *c.Config.ChartVersion + return latestVersion, updated, nil +} + +func (c *HelmChart) checkUpdateHelmRepo() (string, bool, error) { chartName, err := c.GetChartName() if err != nil { return "", false, err From 2a3b86b91d1e05e016d1cc6545ac547e879f8239 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Nov 2022 16:15:00 +0100 Subject: [PATCH 1201/2916] tests: Test OCI charts --- e2e/helm_test.go | 118 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 102 insertions(+), 16 deletions(-) diff --git a/e2e/helm_test.go b/e2e/helm_test.go index a0a73552a..e4195f7f7 100644 --- a/e2e/helm_test.go +++ b/e2e/helm_test.go @@ -3,6 +3,7 @@ package e2e import ( "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing/object" + "github.com/google/go-containerregistry/pkg/registry" test_resources "github.com/kluctl/kluctl/v2/e2e/test-helm-chart" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" @@ -12,12 +13,16 @@ import ( "helm.sh/helm/v3/pkg/cli" "helm.sh/helm/v3/pkg/cli/values" "helm.sh/helm/v3/pkg/getter" + "helm.sh/helm/v3/pkg/pusher" + registry2 "helm.sh/helm/v3/pkg/registry" "helm.sh/helm/v3/pkg/repo" + "helm.sh/helm/v3/pkg/uploader" "net/http" "net/http/httptest" "os" "path/filepath" "sort" + "strings" "testing" ) @@ -85,7 +90,50 @@ func createHelmRepo(t *testing.T, charts []repoChart) string { return s.URL } +func createOciRepo(t *testing.T, charts []repoChart) string { + tmpDir := t.TempDir() + + ociRegistry := registry.New() + ociServer := httptest.NewServer(ociRegistry) + + t.Cleanup(ociServer.Close) + + ociUrl := strings.ReplaceAll(ociServer.URL, "http://", "oci://") + + var out strings.Builder + settings := cli.New() + c := uploader.ChartUploader{ + Out: &out, + Pushers: pusher.All(settings), + Options: []pusher.Option{}, + } + + for _, chart := range charts { + tgz := createHelmPackage(t, chart.chartName, chart.version) + _ = utils.CopyFile(tgz, filepath.Join(tmpDir, filepath.Base(tgz))) + + err := c.UploadTo(tgz, ociUrl) + if err != nil { + t.Fatal(err) + } + } + + return ociUrl +} + +func createHelmOrOciRepo(t *testing.T, charts []repoChart, oci bool) string { + if oci { + return createOciRepo(t, charts) + } else { + return createHelmRepo(t, charts) + } +} + func addHelmDeployment(p *testProject, dir string, repoUrl string, chartName, version string, releaseName string, namespace string, values map[string]any) { + if registry2.IsOCI(repoUrl) { + repoUrl += "/" + chartName + chartName = "" + } p.addKustomizeDeployment(dir, []kustomizeResource{ {name: "helm-rendered.yaml"}, @@ -95,12 +143,14 @@ func addHelmDeployment(p *testProject, dir string, repoUrl string, chartName, ve *o = *uo.FromMap(map[string]interface{}{ "helmChart": map[string]any{ "repo": repoUrl, - "chartName": chartName, "chartVersion": version, "releaseName": releaseName, "namespace": namespace, }, }) + if chartName != "" { + _ = o.SetNestedField(chartName, "helmChart", "chartName") + } return nil }, "") @@ -112,7 +162,7 @@ func addHelmDeployment(p *testProject, dir string, repoUrl string, chartName, ve } } -func TestHelmNoPrePull(t *testing.T) { +func testHelmNoPrePull(t *testing.T, oci bool) { t.Parallel() k := defaultCluster1 @@ -122,9 +172,9 @@ func TestHelmNoPrePull(t *testing.T) { createNamespace(t, k, p.testSlug()) - repoUrl := createHelmRepo(p.t, []repoChart{ + repoUrl := createHelmOrOciRepo(p.t, []repoChart{ {chartName: "test-chart1", version: "0.1.0"}, - }) + }, oci) p.updateTarget("test", nil) addHelmDeployment(p, "helm1", repoUrl, "test-chart1", "0.1.0", "test-helm1", p.testSlug(), nil) @@ -132,7 +182,15 @@ func TestHelmNoPrePull(t *testing.T) { assertConfigMapExists(t, k, p.testSlug(), "test-helm1-test-chart1") } -func TestHelmPrePull(t *testing.T) { +func TestHelmNoPrePull(t *testing.T) { + testHelmNoPrePull(t, false) +} + +func TestHelmNoPrePullOci(t *testing.T) { + testHelmNoPrePull(t, true) +} + +func testHelmPrePull(t *testing.T, oci bool) { t.Parallel() k := defaultCluster1 @@ -142,10 +200,10 @@ func TestHelmPrePull(t *testing.T) { createNamespace(t, k, p.testSlug()) - repoUrl := createHelmRepo(p.t, []repoChart{ + repoUrl := createHelmOrOciRepo(p.t, []repoChart{ {chartName: "test-chart1", version: "0.1.0"}, {chartName: "test-chart2", version: "0.1.0"}, - }) + }, oci) p.updateTarget("test", nil) addHelmDeployment(p, "helm1", repoUrl, "test-chart1", "0.1.0", "test-helm1", p.testSlug(), nil) @@ -163,7 +221,15 @@ func TestHelmPrePull(t *testing.T) { assertConfigMapExists(t, k, p.testSlug(), "test-helm2-test-chart2") } -func TestHelmManualUpgrade(t *testing.T) { +func TestHelmPrePull(t *testing.T) { + testHelmPrePull(t, false) +} + +func TestHelmPrePullOci(t *testing.T) { + testHelmPrePull(t, true) +} + +func testHelmManualUpgrade(t *testing.T, oci bool) { t.Parallel() k := defaultCluster1 @@ -173,10 +239,10 @@ func TestHelmManualUpgrade(t *testing.T) { createNamespace(t, k, p.testSlug()) - repoUrl := createHelmRepo(p.t, []repoChart{ + repoUrl := createHelmOrOciRepo(p.t, []repoChart{ {chartName: "test-chart1", version: "0.1.0"}, {chartName: "test-chart1", version: "0.2.0"}, - }) + }, oci) p.updateTarget("test", nil) addHelmDeployment(p, "helm1", repoUrl, "test-chart1", "0.1.0", "test-helm1", p.testSlug(), nil) @@ -200,7 +266,15 @@ func TestHelmManualUpgrade(t *testing.T) { assert.Equal(t, "0.2.0", v) } -func testHelmUpdate(t *testing.T, upgrade bool, commit bool) { +func TestHelmManualUpgrade(t *testing.T) { + testHelmManualUpgrade(t, false) +} + +func TestHelmManualUpgradeOci(t *testing.T) { + testHelmManualUpgrade(t, true) +} + +func testHelmUpdate(t *testing.T, oci bool, upgrade bool, commit bool) { t.Parallel() k := defaultCluster1 @@ -210,12 +284,12 @@ func testHelmUpdate(t *testing.T, upgrade bool, commit bool) { createNamespace(t, k, p.testSlug()) - repoUrl := createHelmRepo(p.t, []repoChart{ + repoUrl := createHelmOrOciRepo(p.t, []repoChart{ {chartName: "test-chart1", version: "0.1.0"}, {chartName: "test-chart1", version: "0.2.0"}, {chartName: "test-chart2", version: "0.1.0"}, {chartName: "test-chart2", version: "0.3.0"}, - }) + }, oci) p.updateTarget("test", nil) addHelmDeployment(p, "helm1", repoUrl, "test-chart1", "0.1.0", "test-helm1", p.testSlug(), nil) @@ -288,15 +362,27 @@ func testHelmUpdate(t *testing.T, upgrade bool, commit bool) { } func TestHelmUpdate(t *testing.T) { - testHelmUpdate(t, false, false) + testHelmUpdate(t, false, false, false) +} + +func TestHelmUpdateOci(t *testing.T) { + testHelmUpdate(t, true, false, false) } func TestHelmUpdateAndUpgrade(t *testing.T) { - testHelmUpdate(t, true, false) + testHelmUpdate(t, false, true, false) +} + +func TestHelmUpdateAndUpgradeOci(t *testing.T) { + testHelmUpdate(t, true, true, false) } func TestHelmUpdateAndUpgradeAndCommit(t *testing.T) { - testHelmUpdate(t, true, true) + testHelmUpdate(t, false, true, true) +} + +func TestHelmUpdateAndUpgradeAndCommitOci(t *testing.T) { + testHelmUpdate(t, true, true, true) } func TestHelmValues(t *testing.T) { From ea71790483532b9665fa3a81de24d25a8abb556d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Nov 2022 16:24:43 +0100 Subject: [PATCH 1202/2916] feat: Add --interactive/-i mode for helm-update --- cmd/kluctl/commands/cmd_helm_update.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go index 4dca7ca47..638d4792c 100644 --- a/cmd/kluctl/commands/cmd_helm_update.go +++ b/cmd/kluctl/commands/cmd_helm_update.go @@ -21,6 +21,8 @@ type helmUpdateCmd struct { Upgrade bool `group:"misc" help:"Write new versions into helm-chart.yaml and perform helm-pull afterwards"` Commit bool `group:"misc" help:"Create a git commit for every updated chart"` + + Interactive bool `group:"misc" short:"i" help:"Ask for every Helm Chart if it should be upgraded."` } func (cmd *helmUpdateCmd) Help() string { @@ -44,6 +46,7 @@ func (cmd *helmUpdateCmd) Run() error { sem := semaphore.NewWeighted(8) type updatedChart struct { + path string chart *deployment.HelmChart newVersion string oldVersion string @@ -71,6 +74,7 @@ func (cmd *helmUpdateCmd) Run() error { if !chart.Config.SkipUpdate && updated { updatedCharts = append(updatedCharts, &updatedChart{ + path: p, chart: chart, newVersion: newVersion, oldVersion: *chart.Config.ChartVersion, @@ -90,6 +94,10 @@ func (cmd *helmUpdateCmd) Run() error { return errs.ErrorOrNil() } + if cmd.Interactive { + sem = semaphore.NewWeighted(1) + } + for _, uc := range updatedCharts { uc := uc @@ -97,6 +105,14 @@ func (cmd *helmUpdateCmd) Run() error { utils.GoLimitedMultiError(cliCtx, sem, &errs, &mutex, func() error { defer wg.Done() + if cmd.Interactive { + statusPrefix, _ := filepath.Rel(gitRootPath, filepath.Dir(uc.path)) + chartName, _ := uc.chart.GetChartName() + if !status.AskForConfirmation(cliCtx, fmt.Sprintf("%s: Do you want to upgrade Chart %s from version %s to %s?", statusPrefix, chartName, uc.oldVersion, uc.newVersion)) { + return nil + } + } + err := cmd.pullAndCommitChart(gitRootPath, uc.chart, uc.oldVersion, uc.newVersion, &mutex) if err != nil { return err From 73e65fff1cbafefafdd5bb77a601fd9005d0fd18 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Nov 2022 16:43:21 +0100 Subject: [PATCH 1203/2916] refactor: Put rendering of Helm charts into own function --- pkg/deployment/deployment_collection.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index 0a379cffa..d8449cd77 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -131,10 +131,17 @@ func (c *DeploymentCollection) RenderDeployments() error { return utils.NewErrorListOrNil(errors) } s.Success() + return nil +} - s = status.Start(c.ctx.Ctx, "Rendering Helm Charts") +func (c *DeploymentCollection) renderHelmCharts() error { + s := status.Start(c.ctx.Ctx, "Rendering Helm Charts") defer s.Failed() + var wg sync.WaitGroup + var mutex sync.Mutex + var errors []error + for _, d := range c.Deployments { d := d wg.Add(1) @@ -271,6 +278,10 @@ func (c *DeploymentCollection) Prepare() error { if err != nil { return err } + err = c.renderHelmCharts() + if err != nil { + return err + } err = c.resolveSealedSecrets() if err != nil { return err From f34daed0c744b93f310f8101641ca1fd5555de11 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Nov 2022 16:47:09 +0100 Subject: [PATCH 1204/2916] feat: Allow to pass Helm credentials in all relevant commands --- cmd/kluctl/args/helm_credentials.go | 17 +++++---- cmd/kluctl/commands/cmd_delete.go | 2 + cmd/kluctl/commands/cmd_deploy.go | 2 + cmd/kluctl/commands/cmd_diff.go | 2 + cmd/kluctl/commands/cmd_helm_pull.go | 8 +--- cmd/kluctl/commands/cmd_helm_update.go | 6 +-- cmd/kluctl/commands/cmd_list_images.go | 2 + cmd/kluctl/commands/cmd_poke_images.go | 2 + cmd/kluctl/commands/cmd_prune.go | 2 + cmd/kluctl/commands/cmd_render.go | 2 + cmd/kluctl/commands/cmd_seal.go | 2 + cmd/kluctl/commands/cmd_validate.go | 2 + cmd/kluctl/commands/utils.go | 2 + pkg/deployment/deployment_item.go | 2 + pkg/deployment/helm_chart.go | 51 ++++++++++++++++++-------- pkg/deployment/shared_context.go | 9 +++-- pkg/kluctl_project/target_context.go | 2 + 17 files changed, 77 insertions(+), 38 deletions(-) diff --git a/cmd/kluctl/args/helm_credentials.go b/cmd/kluctl/args/helm_credentials.go index 47cd25feb..451ff7ef2 100644 --- a/cmd/kluctl/args/helm_credentials.go +++ b/cmd/kluctl/args/helm_credentials.go @@ -8,10 +8,10 @@ import ( ) type HelmCredentials struct { - Username []string `group:"misc" help:"Specify username to use for Helm Repository authentication. Must be in the form --username=:, where must match the id specified in the helm-chart.yaml."` - Password []string `group:"misc" help:"Specify password to use for Helm Repository authentication. Must be in the form --password=:, where must match the id specified in the helm-chart.yaml."` - KeyFile []string `group:"misc" help:"Specify client certificate to use for Helm Repository authentication. Must be in the form --key-file=:, where must match the id specified in the helm-chart.yaml."` - InsecureSkipTlsVerify []string `group:"misc" help:"Controls skipping of TLS verification. Must be in the form --insecure-skip-tls-verify=, where must match the id specified in the helm-chart.yaml."` + HelmUsername []string `group:"misc" help:"Specify username to use for Helm Repository authentication. Must be in the form --helm-username=:, where must match the id specified in the helm-chart.yaml."` + HelmPassword []string `group:"misc" help:"Specify password to use for Helm Repository authentication. Must be in the form --helm-password=:, where must match the id specified in the helm-chart.yaml."` + HelmKeyFile []string `group:"misc" help:"Specify client certificate to use for Helm Repository authentication. Must be in the form --helm-key-file=:, where must match the id specified in the helm-chart.yaml."` + HelmInsecureSkipTlsVerify []string `group:"misc" help:"Controls skipping of TLS verification. Must be in the form --helm-insecure-skip-tls-verify=, where must match the id specified in the helm-chart.yaml."` } func (c *HelmCredentials) FindCredentials(repoUrl string, credentialsId *string) *repo.Entry { @@ -28,28 +28,29 @@ func (c *HelmCredentials) FindCredentials(repoUrl string, credentialsId *string) } var e repo.Entry - for _, x := range c.Username { + for _, x := range c.HelmUsername { if v, ok := splitIdAndValue(x); ok { e.Username = v } } - for _, x := range c.Password { + for _, x := range c.HelmPassword { if v, ok := splitIdAndValue(x); ok { e.Password = v } } - for _, x := range c.KeyFile { + for _, x := range c.HelmKeyFile { if v, ok := splitIdAndValue(x); ok { e.KeyFile = v } } - for _, x := range c.InsecureSkipTlsVerify { + for _, x := range c.HelmInsecureSkipTlsVerify { if x == *credentialsId { e.InsecureSkipTLSverify = true } } if e != (repo.Entry{}) { + e.URL = repoUrl return &e } } diff --git a/cmd/kluctl/commands/cmd_delete.go b/cmd/kluctl/commands/cmd_delete.go index 368ccd554..ecfc97814 100644 --- a/cmd/kluctl/commands/cmd_delete.go +++ b/cmd/kluctl/commands/cmd_delete.go @@ -20,6 +20,7 @@ type deleteCmd struct { args.ArgsFlags args.ImageFlags args.InclusionFlags + args.HelmCredentials args.YesFlags args.DryRunFlags args.OutputFormatFlags @@ -43,6 +44,7 @@ func (cmd *deleteCmd) Run() error { argsFlags: cmd.ArgsFlags, imageFlags: cmd.ImageFlags, inclusionFlags: cmd.InclusionFlags, + helmCredentials: cmd.HelmCredentials, dryRunArgs: &cmd.DryRunFlags, renderOutputDirFlags: cmd.RenderOutputDirFlags, } diff --git a/cmd/kluctl/commands/cmd_deploy.go b/cmd/kluctl/commands/cmd_deploy.go index 0e57f6e75..9b62a7aa8 100644 --- a/cmd/kluctl/commands/cmd_deploy.go +++ b/cmd/kluctl/commands/cmd_deploy.go @@ -14,6 +14,7 @@ type deployCmd struct { args.ArgsFlags args.ImageFlags args.InclusionFlags + args.HelmCredentials args.YesFlags args.DryRunFlags args.ForceApplyFlags @@ -40,6 +41,7 @@ func (cmd *deployCmd) Run() error { argsFlags: cmd.ArgsFlags, imageFlags: cmd.ImageFlags, inclusionFlags: cmd.InclusionFlags, + helmCredentials: cmd.HelmCredentials, dryRunArgs: &cmd.DryRunFlags, renderOutputDirFlags: cmd.RenderOutputDirFlags, } diff --git a/cmd/kluctl/commands/cmd_diff.go b/cmd/kluctl/commands/cmd_diff.go index bc6fcc211..d18becfe5 100644 --- a/cmd/kluctl/commands/cmd_diff.go +++ b/cmd/kluctl/commands/cmd_diff.go @@ -12,6 +12,7 @@ type diffCmd struct { args.ArgsFlags args.InclusionFlags args.ImageFlags + args.HelmCredentials args.ForceApplyFlags args.ReplaceOnErrorFlags args.IgnoreFlags @@ -33,6 +34,7 @@ func (cmd *diffCmd) Run() error { argsFlags: cmd.ArgsFlags, imageFlags: cmd.ImageFlags, inclusionFlags: cmd.InclusionFlags, + helmCredentials: cmd.HelmCredentials, renderOutputDirFlags: cmd.RenderOutputDirFlags, } return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { diff --git a/cmd/kluctl/commands/cmd_helm_pull.go b/cmd/kluctl/commands/cmd_helm_pull.go index c4f9ff8f4..42e5da324 100644 --- a/cmd/kluctl/commands/cmd_helm_pull.go +++ b/cmd/kluctl/commands/cmd_helm_pull.go @@ -90,6 +90,8 @@ func doPull(statusPrefix string, p string, helmCredentials args.HelmCredentials, return doError(err) } + chart.SetCredentials(&helmCredentials) + chartName, err := chart.GetChartName() if err != nil { return doError(err) @@ -97,12 +99,6 @@ func doPull(statusPrefix string, p string, helmCredentials args.HelmCredentials, s.Update("%s: Pulling Chart %s with version %s", statusPrefix, chartName, *chart.Config.ChartVersion) - creds := helmCredentials.FindCredentials(*chart.Config.Repo, chart.Config.CredentialsId) - if chart.Config.CredentialsId != nil && creds == nil { - return doError(fmt.Errorf("no credentials provided")) - } - chart.SetCredentials(creds) - err = chart.Pull(cliCtx) if err != nil { return doError(err) diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go index 638d4792c..10c8a412c 100644 --- a/cmd/kluctl/commands/cmd_helm_update.go +++ b/cmd/kluctl/commands/cmd_helm_update.go @@ -146,11 +146,7 @@ func (cmd *helmUpdateCmd) doCheckUpdate(gitRootPath string, p string) (*deployme return doError(err) } - creds := cmd.HelmCredentials.FindCredentials(*chart.Config.Repo, chart.Config.CredentialsId) - if chart.Config.CredentialsId != nil && creds == nil { - return doError(fmt.Errorf("no credentials provided")) - } - chart.SetCredentials(creds) + chart.SetCredentials(&cmd.HelmCredentials) newVersion, updated, err := chart.CheckUpdate(cliCtx) if err != nil { diff --git a/cmd/kluctl/commands/cmd_list_images.go b/cmd/kluctl/commands/cmd_list_images.go index 4ec7d3543..371310ed1 100644 --- a/cmd/kluctl/commands/cmd_list_images.go +++ b/cmd/kluctl/commands/cmd_list_images.go @@ -11,6 +11,7 @@ type listImagesCmd struct { args.ArgsFlags args.ImageFlags args.InclusionFlags + args.HelmCredentials args.OutputFlags args.RenderOutputDirFlags @@ -32,6 +33,7 @@ func (cmd *listImagesCmd) Run() error { argsFlags: cmd.ArgsFlags, imageFlags: cmd.ImageFlags, inclusionFlags: cmd.InclusionFlags, + helmCredentials: cmd.HelmCredentials, renderOutputDirFlags: cmd.RenderOutputDirFlags, offlineKubernetes: cmd.OfflineKubernetes, } diff --git a/cmd/kluctl/commands/cmd_poke_images.go b/cmd/kluctl/commands/cmd_poke_images.go index e08e152c8..b28c2f369 100644 --- a/cmd/kluctl/commands/cmd_poke_images.go +++ b/cmd/kluctl/commands/cmd_poke_images.go @@ -13,6 +13,7 @@ type pokeImagesCmd struct { args.ArgsFlags args.ImageFlags args.InclusionFlags + args.HelmCredentials args.YesFlags args.DryRunFlags args.OutputFormatFlags @@ -32,6 +33,7 @@ func (cmd *pokeImagesCmd) Run() error { argsFlags: cmd.ArgsFlags, imageFlags: cmd.ImageFlags, inclusionFlags: cmd.InclusionFlags, + helmCredentials: cmd.HelmCredentials, dryRunArgs: &cmd.DryRunFlags, renderOutputDirFlags: cmd.RenderOutputDirFlags, } diff --git a/cmd/kluctl/commands/cmd_prune.go b/cmd/kluctl/commands/cmd_prune.go index 3610aed88..a9197b22f 100644 --- a/cmd/kluctl/commands/cmd_prune.go +++ b/cmd/kluctl/commands/cmd_prune.go @@ -12,6 +12,7 @@ type pruneCmd struct { args.ArgsFlags args.ImageFlags args.InclusionFlags + args.HelmCredentials args.YesFlags args.DryRunFlags args.OutputFormatFlags @@ -33,6 +34,7 @@ func (cmd *pruneCmd) Run() error { argsFlags: cmd.ArgsFlags, imageFlags: cmd.ImageFlags, inclusionFlags: cmd.InclusionFlags, + helmCredentials: cmd.HelmCredentials, dryRunArgs: &cmd.DryRunFlags, renderOutputDirFlags: cmd.RenderOutputDirFlags, } diff --git a/cmd/kluctl/commands/cmd_render.go b/cmd/kluctl/commands/cmd_render.go index 52327efdd..f3fcf8b5c 100644 --- a/cmd/kluctl/commands/cmd_render.go +++ b/cmd/kluctl/commands/cmd_render.go @@ -14,6 +14,7 @@ type renderCmd struct { args.TargetFlags args.ArgsFlags args.ImageFlags + args.HelmCredentials args.RenderOutputDirFlags OfflineKubernetes bool `group:"misc" help:"Run render in offline mode, meaning that it will not try to connect the target cluster"` @@ -41,6 +42,7 @@ func (cmd *renderCmd) Run() error { targetFlags: cmd.TargetFlags, argsFlags: cmd.ArgsFlags, imageFlags: cmd.ImageFlags, + helmCredentials: cmd.HelmCredentials, renderOutputDirFlags: cmd.RenderOutputDirFlags, offlineKubernetes: cmd.OfflineKubernetes, } diff --git a/cmd/kluctl/commands/cmd_seal.go b/cmd/kluctl/commands/cmd_seal.go index e2388b317..e1b2dce24 100644 --- a/cmd/kluctl/commands/cmd_seal.go +++ b/cmd/kluctl/commands/cmd_seal.go @@ -17,6 +17,7 @@ import ( type sealCmd struct { args.ProjectFlags args.TargetFlags + args.HelmCredentials ForceReseal bool `group:"misc" help:"Lets kluctl ignore secret hashes found in already sealed secrets and thus forces resealing of those."` CertFile string `group:"misc" help:"Use the given certificate for sealing instead of requesting it from the sealed-secrets controller"` @@ -44,6 +45,7 @@ func (cmd *sealCmd) runCmdSealForTarget(ctx context.Context, p *kluctl_project.L ptArgs := projectTargetCommandArgs{ projectFlags: cmd.ProjectFlags, targetFlags: cmd.TargetFlags, + helmCredentials: cmd.HelmCredentials, forSeal: true, offlineKubernetes: cmd.OfflineKubernetes, } diff --git a/cmd/kluctl/commands/cmd_validate.go b/cmd/kluctl/commands/cmd_validate.go index b34d90a0d..4558cd76f 100644 --- a/cmd/kluctl/commands/cmd_validate.go +++ b/cmd/kluctl/commands/cmd_validate.go @@ -13,6 +13,7 @@ type validateCmd struct { args.TargetFlags args.ArgsFlags args.InclusionFlags + args.HelmCredentials args.OutputFlags args.RenderOutputDirFlags @@ -33,6 +34,7 @@ func (cmd *validateCmd) Run() error { targetFlags: cmd.TargetFlags, argsFlags: cmd.ArgsFlags, inclusionFlags: cmd.InclusionFlags, + helmCredentials: cmd.HelmCredentials, renderOutputDirFlags: cmd.RenderOutputDirFlags, } return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index cd67132ad..eab25b232 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -85,6 +85,7 @@ type projectTargetCommandArgs struct { argsFlags args.ArgsFlags imageFlags args.ImageFlags inclusionFlags args.InclusionFlags + helmCredentials args.HelmCredentials dryRunArgs *args.DryRunFlags renderOutputDirFlags args.RenderOutputDirFlags @@ -162,6 +163,7 @@ func withProjectTargetCommandContext(ctx context.Context, args projectTargetComm ForSeal: args.forSeal, Images: images, Inclusion: inclusion, + HelmCredentials: &args.helmCredentials, RenderOutputDir: renderOutputDir, } diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 69cdb586f..084035ba4 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -198,6 +198,8 @@ func (di *DeploymentItem) renderHelmCharts() error { return err } + chart.SetCredentials(di.ctx.HelmCredentials) + ky, err := di.readKustomizationYaml(subDir) if err == nil && ky != nil { resources, _, _ := ky.GetNestedStringList("resources") diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index fb3b53926..214a1ed1c 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -35,10 +35,15 @@ import ( "time" ) +type HelmCredentialsProvider interface { + FindCredentials(repoUrl string, credentialsId *string) *repo.Entry +} + type HelmChart struct { - ConfigFile string - Config *types.HelmChartConfig - credentials *repo.Entry + ConfigFile string + Config *types.HelmChartConfig + + credentials HelmCredentialsProvider } func NewHelmChart(configFile string) (*HelmChart, error) { @@ -207,14 +212,21 @@ func (c *HelmChart) doPull(ctx context.Context, chartDir string) error { a.DestDir = tmpDir a.Version = *c.Config.ChartVersion - if c.credentials != nil { - a.Username = c.credentials.Username - a.Password = c.credentials.Password - a.CertFile = c.credentials.CertFile - a.CaFile = c.credentials.CAFile - a.KeyFile = c.credentials.KeyFile - a.InsecureSkipTLSverify = c.credentials.InsecureSkipTLSverify - a.PassCredentialsAll = c.credentials.PassCredentialsAll + if c.Config.CredentialsId != nil { + if c.credentials == nil { + return fmt.Errorf("no credentials provider") + } + creds := c.credentials.FindCredentials(*c.Config.Repo, c.Config.CredentialsId) + if creds == nil { + return fmt.Errorf("no credentials provided for Chart %s", chartName) + } + a.Username = creds.Username + a.Password = creds.Password + a.CertFile = creds.CertFile + a.CaFile = creds.CAFile + a.KeyFile = creds.KeyFile + a.InsecureSkipTLSverify = creds.InsecureSkipTLSverify + a.PassCredentialsAll = creds.PassCredentialsAll } var out string @@ -278,8 +290,17 @@ func (c *HelmChart) checkUpdateHelmRepo() (string, bool, error) { var latestVersion string settings := cli.New() - e := c.credentials - if e == nil { + + var e *repo.Entry + if c.Config.CredentialsId != nil { + if c.credentials == nil { + return "", false, fmt.Errorf("no credentials provider") + } + e = c.credentials.FindCredentials(*c.Config.Repo, c.Config.CredentialsId) + if e == nil { + return "", false, fmt.Errorf("no credentials provided for Chart %s", chartName) + } + } else { e = &repo.Entry{ URL: *c.Config.Repo, } @@ -515,8 +536,8 @@ func (c *HelmChart) parseRenderedManifests(s string) ([]*uo.UnstructuredObject, return parsed, nil } -func (c *HelmChart) SetCredentials(credentials *repo.Entry) { - c.credentials = credentials +func (c *HelmChart) SetCredentials(p HelmCredentialsProvider) { + c.credentials = p } func checkIfInstallable(ch *chart.Chart) error { diff --git a/pkg/deployment/shared_context.go b/pkg/deployment/shared_context.go index 3d4e3ca50..4851cbcd9 100644 --- a/pkg/deployment/shared_context.go +++ b/pkg/deployment/shared_context.go @@ -8,10 +8,11 @@ import ( ) type SharedContext struct { - Ctx context.Context - K *k8s.K8sCluster - RP *repocache.GitRepoCache - VarsLoader *vars.VarsLoader + Ctx context.Context + K *k8s.K8sCluster + RP *repocache.GitRepoCache + VarsLoader *vars.VarsLoader + HelmCredentials HelmCredentialsProvider RenderDir string SealedSecretsDir string diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index 3a47c0642..9af4f6c1c 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -36,6 +36,7 @@ type TargetContextParams struct { ForSeal bool Images *deployment.Images Inclusion *utils.Inclusion + HelmCredentials deployment.HelmCredentialsProvider RenderOutputDir string } @@ -104,6 +105,7 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, params Targe K: k, RP: p.RP, VarsLoader: varsLoader, + HelmCredentials: params.HelmCredentials, RenderDir: params.RenderOutputDir, SealedSecretsDir: p.sealedSecretsDir, DefaultSealedSecretsOutputPattern: target.Name, From 155fc8e94670a9942abda9c8de52c034ec06a424 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Nov 2022 16:25:44 +0100 Subject: [PATCH 1205/2916] docs: Run replace-commands-help --- docs/reference/commands/delete.md | 33 ++++++++++++---- docs/reference/commands/deploy.md | 54 ++++++++++++++++++-------- docs/reference/commands/diff.md | 43 +++++++++++++------- docs/reference/commands/helm-update.md | 34 +++++++++------- docs/reference/commands/list-images.md | 27 ++++++++++--- docs/reference/commands/poke-images.md | 31 +++++++++++---- docs/reference/commands/prune.md | 31 +++++++++++---- docs/reference/commands/render.md | 25 +++++++++--- docs/reference/commands/validate.md | 27 ++++++++++--- 9 files changed, 221 insertions(+), 84 deletions(-) diff --git a/docs/reference/commands/delete.md b/docs/reference/commands/delete.md index 7dd91db7c..264e4c553 100644 --- a/docs/reference/commands/delete.md +++ b/docs/reference/commands/delete.md @@ -34,14 +34,31 @@ In addition, the following arguments are available: Misc arguments: Command specific arguments. - -l, --delete-by-label stringArray Override the labels used to find objects for deletion. - --dry-run Performs all kubernetes API calls in dry-run mode. - -o, --output-format stringArray Specify output format and target file, in the format 'format=path'. Format - can either be 'text' or 'yaml'. Can be specified multiple times. The actual - format for yaml is currently not documented and subject to change. - --render-output-dir string Specifies the target directory to render the project into. If omitted, a - temporary directory is used. - -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. + -l, --delete-by-label stringArray Override the labels used to find objects for deletion. + --dry-run Performs all kubernetes API calls in dry-run mode. + --helm-insecure-skip-tls-verify stringArray Controls skipping of TLS verification. Must be in the form + --helm-insecure-skip-tls-verify=, where + must match the id specified in the helm-chart.yaml. + --helm-key-file stringArray Specify client certificate to use for Helm Repository + authentication. Must be in the form + --helm-key-file=:, where + must match the id specified in the helm-chart.yaml. + --helm-password stringArray Specify password to use for Helm Repository authentication. + Must be in the form + --helm-password=:, where + must match the id specified in the helm-chart.yaml. + --helm-username stringArray Specify username to use for Helm Repository authentication. + Must be in the form + --helm-username=:, where + must match the id specified in the helm-chart.yaml. + -o, --output-format stringArray Specify output format and target file, in the format + 'format=path'. Format can either be 'text' or 'yaml'. Can be + specified multiple times. The actual format for yaml is + currently not documented and subject to change. + --render-output-dir string Specifies the target directory to render the project into. If + omitted, a temporary directory is used. + -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you + would answer 'yes'. ``` diff --git a/docs/reference/commands/deploy.md b/docs/reference/commands/deploy.md index 73ce93477..044715d7b 100644 --- a/docs/reference/commands/deploy.md +++ b/docs/reference/commands/deploy.md @@ -32,23 +32,43 @@ In addition, the following arguments are available: Misc arguments: Command specific arguments. - --abort-on-error Abort deploying when an error occurs instead of trying the remaining deployments - --dry-run Performs all kubernetes API calls in dry-run mode. - --force-apply Force conflict resolution when applying. See documentation for details - --force-replace-on-error Same as --replace-on-error, but also try to delete and re-create objects. See - documentation for more details. - --no-wait Don't wait for objects readiness' - -o, --output-format stringArray Specify output format and target file, in the format 'format=path'. Format - can either be 'text' or 'yaml'. Can be specified multiple times. The actual - format for yaml is currently not documented and subject to change. - --readiness-timeout duration Maximum time to wait for object readiness. The timeout is meant per-object. - Timeouts are in the duration format (1s, 1m, 1h, ...). If not specified, a - default timeout of 5m is used. (default 5m0s) - --render-output-dir string Specifies the target directory to render the project into. If omitted, a - temporary directory is used. - --replace-on-error When patching an object fails, try to replace it. See documentation for more - details. - -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. + --abort-on-error Abort deploying when an error occurs instead of trying the + remaining deployments + --dry-run Performs all kubernetes API calls in dry-run mode. + --force-apply Force conflict resolution when applying. See documentation for + details + --force-replace-on-error Same as --replace-on-error, but also try to delete and + re-create objects. See documentation for more details. + --helm-insecure-skip-tls-verify stringArray Controls skipping of TLS verification. Must be in the form + --helm-insecure-skip-tls-verify=, where + must match the id specified in the helm-chart.yaml. + --helm-key-file stringArray Specify client certificate to use for Helm Repository + authentication. Must be in the form + --helm-key-file=:, where + must match the id specified in the helm-chart.yaml. + --helm-password stringArray Specify password to use for Helm Repository authentication. + Must be in the form + --helm-password=:, where + must match the id specified in the helm-chart.yaml. + --helm-username stringArray Specify username to use for Helm Repository authentication. + Must be in the form + --helm-username=:, where + must match the id specified in the helm-chart.yaml. + --no-wait Don't wait for objects readiness' + -o, --output-format stringArray Specify output format and target file, in the format + 'format=path'. Format can either be 'text' or 'yaml'. Can be + specified multiple times. The actual format for yaml is + currently not documented and subject to change. + --readiness-timeout duration Maximum time to wait for object readiness. The timeout is + meant per-object. Timeouts are in the duration format (1s, 1m, + 1h, ...). If not specified, a default timeout of 5m is used. + (default 5m0s) + --render-output-dir string Specifies the target directory to render the project into. If + omitted, a temporary directory is used. + --replace-on-error When patching an object fails, try to replace it. See + documentation for more details. + -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you + would answer 'yes'. ``` diff --git a/docs/reference/commands/diff.md b/docs/reference/commands/diff.md index 320833839..bb047a7c4 100644 --- a/docs/reference/commands/diff.md +++ b/docs/reference/commands/diff.md @@ -33,19 +33,36 @@ In addition, the following arguments are available: Misc arguments: Command specific arguments. - --force-apply Force conflict resolution when applying. See documentation for details - --force-replace-on-error Same as --replace-on-error, but also try to delete and re-create objects. See - documentation for more details. - --ignore-annotations Ignores changes in annotations when diffing - --ignore-labels Ignores changes in labels when diffing - --ignore-tags Ignores changes in tags when diffing - -o, --output-format stringArray Specify output format and target file, in the format 'format=path'. Format can - either be 'text' or 'yaml'. Can be specified multiple times. The actual format - for yaml is currently not documented and subject to change. - --render-output-dir string Specifies the target directory to render the project into. If omitted, a - temporary directory is used. - --replace-on-error When patching an object fails, try to replace it. See documentation for more - details. + --force-apply Force conflict resolution when applying. See documentation for + details + --force-replace-on-error Same as --replace-on-error, but also try to delete and + re-create objects. See documentation for more details. + --helm-insecure-skip-tls-verify stringArray Controls skipping of TLS verification. Must be in the form + --helm-insecure-skip-tls-verify=, where + must match the id specified in the helm-chart.yaml. + --helm-key-file stringArray Specify client certificate to use for Helm Repository + authentication. Must be in the form + --helm-key-file=:, where + must match the id specified in the helm-chart.yaml. + --helm-password stringArray Specify password to use for Helm Repository authentication. + Must be in the form + --helm-password=:, where + must match the id specified in the helm-chart.yaml. + --helm-username stringArray Specify username to use for Helm Repository authentication. + Must be in the form + --helm-username=:, where + must match the id specified in the helm-chart.yaml. + --ignore-annotations Ignores changes in annotations when diffing + --ignore-labels Ignores changes in labels when diffing + --ignore-tags Ignores changes in tags when diffing + -o, --output-format stringArray Specify output format and target file, in the format + 'format=path'. Format can either be 'text' or 'yaml'. Can be + specified multiple times. The actual format for yaml is + currently not documented and subject to change. + --render-output-dir string Specifies the target directory to render the project into. If + omitted, a temporary directory is used. + --replace-on-error When patching an object fails, try to replace it. See + documentation for more details. ``` diff --git a/docs/reference/commands/helm-update.md b/docs/reference/commands/helm-update.md index f9e679928..0e671b5aa 100644 --- a/docs/reference/commands/helm-update.md +++ b/docs/reference/commands/helm-update.md @@ -28,21 +28,25 @@ In addition, the following arguments are available: Misc arguments: Command specific arguments. - --commit Create a git commit for every updated chart - --insecure-skip-tls-verify stringArray Controls skipping of TLS verification. Must be in the form - --insecure-skip-tls-verify=, where - must match the id specified in the helm-chart.yaml. - --key-file stringArray Specify client certificate to use for Helm Repository - authentication. Must be in the form - --key-file=:, where must match - the id specified in the helm-chart.yaml. - --password stringArray Specify password to use for Helm Repository authentication. Must be - in the form --password=:, where - must match the id specified in the helm-chart.yaml. - --upgrade Write new versions into helm-chart.yaml and perform helm-pull afterwards - --username stringArray Specify username to use for Helm Repository authentication. Must be - in the form --username=:, where - must match the id specified in the helm-chart.yaml. + --commit Create a git commit for every updated chart + --helm-insecure-skip-tls-verify stringArray Controls skipping of TLS verification. Must be in the form + --helm-insecure-skip-tls-verify=, where + must match the id specified in the helm-chart.yaml. + --helm-key-file stringArray Specify client certificate to use for Helm Repository + authentication. Must be in the form + --helm-key-file=:, where + must match the id specified in the helm-chart.yaml. + --helm-password stringArray Specify password to use for Helm Repository authentication. + Must be in the form + --helm-password=:, where + must match the id specified in the helm-chart.yaml. + --helm-username stringArray Specify username to use for Helm Repository authentication. + Must be in the form + --helm-username=:, where + must match the id specified in the helm-chart.yaml. + -i, --interactive Ask for every Helm Chart if it should be upgraded. + --upgrade Write new versions into helm-chart.yaml and perform helm-pull + afterwards ``` \ No newline at end of file diff --git a/docs/reference/commands/list-images.md b/docs/reference/commands/list-images.md index e4f6f7e2f..e90fe6624 100644 --- a/docs/reference/commands/list-images.md +++ b/docs/reference/commands/list-images.md @@ -33,12 +33,27 @@ In addition, the following arguments are available: Misc arguments: Command specific arguments. - --offline-kubernetes Run list-images in offline mode, meaning that it will not try to connect the - target cluster - -o, --output stringArray Specify output target file. Can be specified multiple times - --render-output-dir string Specifies the target directory to render the project into. If omitted, a - temporary directory is used. - --simple Output a simplified version of the images list + --helm-insecure-skip-tls-verify stringArray Controls skipping of TLS verification. Must be in the form + --helm-insecure-skip-tls-verify=, where + must match the id specified in the helm-chart.yaml. + --helm-key-file stringArray Specify client certificate to use for Helm Repository + authentication. Must be in the form + --helm-key-file=:, where + must match the id specified in the helm-chart.yaml. + --helm-password stringArray Specify password to use for Helm Repository authentication. + Must be in the form + --helm-password=:, where + must match the id specified in the helm-chart.yaml. + --helm-username stringArray Specify username to use for Helm Repository authentication. + Must be in the form + --helm-username=:, where + must match the id specified in the helm-chart.yaml. + --offline-kubernetes Run list-images in offline mode, meaning that it will not try + to connect the target cluster + -o, --output stringArray Specify output target file. Can be specified multiple times + --render-output-dir string Specifies the target directory to render the project into. If + omitted, a temporary directory is used. + --simple Output a simplified version of the images list ``` diff --git a/docs/reference/commands/poke-images.md b/docs/reference/commands/poke-images.md index 8c3ba5ca8..4d16e9589 100644 --- a/docs/reference/commands/poke-images.md +++ b/docs/reference/commands/poke-images.md @@ -32,13 +32,30 @@ In addition, the following arguments are available: Misc arguments: Command specific arguments. - --dry-run Performs all kubernetes API calls in dry-run mode. - -o, --output-format stringArray Specify output format and target file, in the format 'format=path'. Format can - either be 'text' or 'yaml'. Can be specified multiple times. The actual format - for yaml is currently not documented and subject to change. - --render-output-dir string Specifies the target directory to render the project into. If omitted, a - temporary directory is used. - -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. + --dry-run Performs all kubernetes API calls in dry-run mode. + --helm-insecure-skip-tls-verify stringArray Controls skipping of TLS verification. Must be in the form + --helm-insecure-skip-tls-verify=, where + must match the id specified in the helm-chart.yaml. + --helm-key-file stringArray Specify client certificate to use for Helm Repository + authentication. Must be in the form + --helm-key-file=:, where + must match the id specified in the helm-chart.yaml. + --helm-password stringArray Specify password to use for Helm Repository authentication. + Must be in the form + --helm-password=:, where + must match the id specified in the helm-chart.yaml. + --helm-username stringArray Specify username to use for Helm Repository authentication. + Must be in the form + --helm-username=:, where + must match the id specified in the helm-chart.yaml. + -o, --output-format stringArray Specify output format and target file, in the format + 'format=path'. Format can either be 'text' or 'yaml'. Can be + specified multiple times. The actual format for yaml is + currently not documented and subject to change. + --render-output-dir string Specifies the target directory to render the project into. If + omitted, a temporary directory is used. + -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you + would answer 'yes'. ``` \ No newline at end of file diff --git a/docs/reference/commands/prune.md b/docs/reference/commands/prune.md index c33800f8c..bef1d7c4b 100644 --- a/docs/reference/commands/prune.md +++ b/docs/reference/commands/prune.md @@ -28,13 +28,30 @@ In addition, the following arguments are available: Misc arguments: Command specific arguments. - --dry-run Performs all kubernetes API calls in dry-run mode. - -o, --output-format stringArray Specify output format and target file, in the format 'format=path'. Format can - either be 'text' or 'yaml'. Can be specified multiple times. The actual format - for yaml is currently not documented and subject to change. - --render-output-dir string Specifies the target directory to render the project into. If omitted, a - temporary directory is used. - -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. + --dry-run Performs all kubernetes API calls in dry-run mode. + --helm-insecure-skip-tls-verify stringArray Controls skipping of TLS verification. Must be in the form + --helm-insecure-skip-tls-verify=, where + must match the id specified in the helm-chart.yaml. + --helm-key-file stringArray Specify client certificate to use for Helm Repository + authentication. Must be in the form + --helm-key-file=:, where + must match the id specified in the helm-chart.yaml. + --helm-password stringArray Specify password to use for Helm Repository authentication. + Must be in the form + --helm-password=:, where + must match the id specified in the helm-chart.yaml. + --helm-username stringArray Specify username to use for Helm Repository authentication. + Must be in the form + --helm-username=:, where + must match the id specified in the helm-chart.yaml. + -o, --output-format stringArray Specify output format and target file, in the format + 'format=path'. Format can either be 'text' or 'yaml'. Can be + specified multiple times. The actual format for yaml is + currently not documented and subject to change. + --render-output-dir string Specifies the target directory to render the project into. If + omitted, a temporary directory is used. + -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you + would answer 'yes'. ``` diff --git a/docs/reference/commands/render.md b/docs/reference/commands/render.md index 2fb25364c..f27882a06 100644 --- a/docs/reference/commands/render.md +++ b/docs/reference/commands/render.md @@ -30,11 +30,26 @@ In addition, the following arguments are available: Misc arguments: Command specific arguments. - --offline-kubernetes Run render in offline mode, meaning that it will not try to connect the target - cluster - --print-all Write all rendered manifests to stdout - --render-output-dir string Specifies the target directory to render the project into. If omitted, a - temporary directory is used. + --helm-insecure-skip-tls-verify stringArray Controls skipping of TLS verification. Must be in the form + --helm-insecure-skip-tls-verify=, where + must match the id specified in the helm-chart.yaml. + --helm-key-file stringArray Specify client certificate to use for Helm Repository + authentication. Must be in the form + --helm-key-file=:, where + must match the id specified in the helm-chart.yaml. + --helm-password stringArray Specify password to use for Helm Repository authentication. + Must be in the form + --helm-password=:, where + must match the id specified in the helm-chart.yaml. + --helm-username stringArray Specify username to use for Helm Repository authentication. + Must be in the form + --helm-username=:, where + must match the id specified in the helm-chart.yaml. + --offline-kubernetes Run render in offline mode, meaning that it will not try to + connect the target cluster + --print-all Write all rendered manifests to stdout + --render-output-dir string Specifies the target directory to render the project into. If + omitted, a temporary directory is used. ``` \ No newline at end of file diff --git a/docs/reference/commands/validate.md b/docs/reference/commands/validate.md index 987592774..77a0abb98 100644 --- a/docs/reference/commands/validate.md +++ b/docs/reference/commands/validate.md @@ -31,12 +31,27 @@ In addition, the following arguments are available: Misc arguments: Command specific arguments. - -o, --output stringArray Specify output target file. Can be specified multiple times - --render-output-dir string Specifies the target directory to render the project into. If omitted, a - temporary directory is used. - --sleep duration Sleep duration between validation attempts (default 5s) - --wait duration Wait for the given amount of time until the deployment validates - --warnings-as-errors Consider warnings as failures + --helm-insecure-skip-tls-verify stringArray Controls skipping of TLS verification. Must be in the form + --helm-insecure-skip-tls-verify=, where + must match the id specified in the helm-chart.yaml. + --helm-key-file stringArray Specify client certificate to use for Helm Repository + authentication. Must be in the form + --helm-key-file=:, where + must match the id specified in the helm-chart.yaml. + --helm-password stringArray Specify password to use for Helm Repository authentication. + Must be in the form + --helm-password=:, where + must match the id specified in the helm-chart.yaml. + --helm-username stringArray Specify username to use for Helm Repository authentication. + Must be in the form + --helm-username=:, where + must match the id specified in the helm-chart.yaml. + -o, --output stringArray Specify output target file. Can be specified multiple times + --render-output-dir string Specifies the target directory to render the project into. If + omitted, a temporary directory is used. + --sleep duration Sleep duration between validation attempts (default 5s) + --wait duration Wait for the given amount of time until the deployment validates + --warnings-as-errors Consider warnings as failures ``` From bdff6953602d92e53a1ca890de9f64d878e5c809 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Nov 2022 21:58:28 +0100 Subject: [PATCH 1206/2916] fix: Fix panic in case of empty env vars --- pkg/utils/env.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/utils/env.go b/pkg/utils/env.go index b78f7dd39..a4b2845aa 100644 --- a/pkg/utils/env.go +++ b/pkg/utils/env.go @@ -42,7 +42,7 @@ func parseEnv(prefix string, withIndex bool, withSuffix bool) map[int]map[string for _, e := range os.Environ() { eq := strings.Index(e, "=") if eq == -1 { - panic(fmt.Sprintf("unexpected env var %s", e)) + continue } n := e[:eq] v := e[eq+1:] From b07ae7bb6f13e1e5d123ff432ba1a0235870acd4 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Nov 2022 21:59:09 +0100 Subject: [PATCH 1207/2916] tests: Use fresh base tmp dir for each test --- e2e/project.go | 1 + pkg/utils/utils.go | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/e2e/project.go b/e2e/project.go index 41fd915eb..02e0b0135 100644 --- a/e2e/project.go +++ b/e2e/project.go @@ -347,6 +347,7 @@ func (p *testProject) Kluctl(argsIn ...string) (string, string, error) { // this will cause the init() function from call_kluctl_hack.go to invoke the kluctl root command and then exit env = append(env, "CALL_KLUCTL=true") + env = append(env, fmt.Sprintf("KLUCTL_BASE_TMP_DIR=%s", p.t.TempDir())) p.t.Logf("Runnning kluctl: %s", strings.Join(args, " ")) diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index b28b992eb..8796755a9 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -25,6 +25,12 @@ func GetTmpBaseDir() string { } func createTmpBaseDir() { + envTmpDir := os.Getenv("KLUCTL_BASE_TMP_DIR") + if envTmpDir != "" { + tmpBaseDir = envTmpDir + return + } + dir := filepath.Join(os.TempDir(), "kluctl-workdir") ensureDir(dir, 0o777, true) From d6e2d34ecadd10ff352af59f4c5852610c31e2d6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Nov 2022 21:59:28 +0100 Subject: [PATCH 1208/2916] tests: Fix testSlug() for sub tests --- e2e/project.go | 1 + 1 file changed, 1 insertion(+) diff --git a/e2e/project.go b/e2e/project.go index 02e0b0135..bcf57d051 100644 --- a/e2e/project.go +++ b/e2e/project.go @@ -54,6 +54,7 @@ func (p *testProject) init(t *testing.T, k *test_utils.EnvTestCluster) { func (p *testProject) testSlug() string { n := p.t.Name() n = xstrings.ToKebabCase(n) + n = strings.ReplaceAll(n, "/", "-") return n } From 90b3762ebcd44e19dc7ada9eda7554709d5dd177 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Nov 2022 22:09:46 +0100 Subject: [PATCH 1209/2916] refactor: Pre-calculate chartName and chartDir --- cmd/kluctl/commands/cmd_helm_pull.go | 7 +- cmd/kluctl/commands/cmd_helm_update.go | 13 +--- pkg/deployment/helm_chart.go | 103 ++++++++++--------------- 3 files changed, 45 insertions(+), 78 deletions(-) diff --git a/cmd/kluctl/commands/cmd_helm_pull.go b/cmd/kluctl/commands/cmd_helm_pull.go index 42e5da324..bc55d6865 100644 --- a/cmd/kluctl/commands/cmd_helm_pull.go +++ b/cmd/kluctl/commands/cmd_helm_pull.go @@ -92,12 +92,7 @@ func doPull(statusPrefix string, p string, helmCredentials args.HelmCredentials, chart.SetCredentials(&helmCredentials) - chartName, err := chart.GetChartName() - if err != nil { - return doError(err) - } - - s.Update("%s: Pulling Chart %s with version %s", statusPrefix, chartName, *chart.Config.ChartVersion) + s.Update("%s: Pulling Chart %s with version %s", statusPrefix, chart.GetChartName(), *chart.Config.ChartVersion) err = chart.Pull(cliCtx) if err != nil { diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go index 10c8a412c..30e4a5626 100644 --- a/cmd/kluctl/commands/cmd_helm_update.go +++ b/cmd/kluctl/commands/cmd_helm_update.go @@ -107,8 +107,8 @@ func (cmd *helmUpdateCmd) Run() error { if cmd.Interactive { statusPrefix, _ := filepath.Rel(gitRootPath, filepath.Dir(uc.path)) - chartName, _ := uc.chart.GetChartName() - if !status.AskForConfirmation(cliCtx, fmt.Sprintf("%s: Do you want to upgrade Chart %s from version %s to %s?", statusPrefix, chartName, uc.oldVersion, uc.newVersion)) { + if !status.AskForConfirmation(cliCtx, fmt.Sprintf("%s: Do you want to upgrade Chart %s from version %s to %s?", + statusPrefix, uc.chart.GetChartName(), uc.oldVersion, uc.newVersion)) { return nil } } @@ -181,15 +181,10 @@ func (cmd *helmUpdateCmd) pullAndCommitChart(gitRootPath string, chart *deployme return err } - chartsDir, err := chart.GetChartDir() - if err != nil { - return err - } - // we need to list all files contained inside the charts dir BEFORE doing the pull, so that we later // know what got deleted oldFiles := map[string]bool{} - err = filepath.WalkDir(chartsDir, func(p string, d fs.DirEntry, err error) error { + err = filepath.WalkDir(chart.GetChartDir(), func(p string, d fs.DirEntry, err error) error { if d.IsDir() { return nil } @@ -216,7 +211,7 @@ func (cmd *helmUpdateCmd) pullAndCommitChart(gitRootPath string, chart *deployme } toAdd = append(toAdd, relToGit) - relToGit, err = filepath.Rel(gitRootPath, chartsDir) + relToGit, err = filepath.Rel(gitRootPath, chart.GetChartDir()) if err != nil { return err } diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index 214a1ed1c..cf9a62766 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -44,6 +44,9 @@ type HelmChart struct { Config *types.HelmChartConfig credentials HelmCredentialsProvider + + chartName string + chartDir string } func NewHelmChart(configFile string) (*HelmChart, error) { @@ -57,33 +60,37 @@ func NewHelmChart(configFile string) (*HelmChart, error) { ConfigFile: configFile, Config: &config, } - return hc, nil -} -func (c *HelmChart) GetChartName() (string, error) { - if c.Config.Repo != nil && registry.IsOCI(*c.Config.Repo) { - s := strings.Split(*c.Config.Repo, "/") + if hc.Config.Repo != nil && registry.IsOCI(*hc.Config.Repo) { + s := strings.Split(*hc.Config.Repo, "/") chartName := s[len(s)-1] if m, _ := regexp.MatchString(`[a-zA-Z_-]+`, chartName); !m { - return "", fmt.Errorf("invalid oci chart url: %s", *c.Config.Repo) + return nil, fmt.Errorf("invalid oci chart url: %s", *hc.Config.Repo) } - return chartName, nil - } - if c.Config.ChartName == nil { - return "", fmt.Errorf("chartName is missing in helm-chart.yml") + hc.chartName = chartName + } else if hc.Config.ChartName == nil { + return nil, fmt.Errorf("chartName is missing in helm-chart.yml") + } else { + hc.chartName = *hc.Config.ChartName } - return *c.Config.ChartName, nil -} -func (c *HelmChart) GetChartDir() (string, error) { - chartName, err := c.GetChartName() + dir := filepath.Dir(configFile) + chartDir := filepath.Join(dir, "charts") + chartDir, err = securejoin.SecureJoin(chartDir, hc.chartName) if err != nil { - return "", err + return nil, err } + hc.chartDir = chartDir - dir := filepath.Dir(c.ConfigFile) - targetDir := filepath.Join(dir, "charts") - return securejoin.SecureJoin(targetDir, chartName) + return hc, nil +} + +func (c *HelmChart) GetChartName() string { + return c.chartName +} + +func (c *HelmChart) GetChartDir() string { + return c.chartDir } func (c *HelmChart) GetOutputPath() string { @@ -142,27 +149,18 @@ func (c *HelmChart) checkNeedsPull(chartDir string, isTmp bool) (bool, bool, str } func (c *HelmChart) Pull(ctx context.Context) error { - chartDir, err := c.GetChartDir() - if err != nil { - return err - } - return c.doPull(ctx, chartDir) + return c.doPull(ctx, c.chartDir) } func (c *HelmChart) pullTmpChart(ctx context.Context) (string, error) { - chartName, err := c.GetChartName() - if err != nil { - return "", err - } - hash := sha256.New() _, _ = fmt.Fprintf(hash, "%s\n", *c.Config.Repo) - _, _ = fmt.Fprintf(hash, "%s\n", chartName) + _, _ = fmt.Fprintf(hash, "%s\n", c.chartName) _, _ = fmt.Fprintf(hash, "%s\n", *c.Config.ChartVersion) h := hex.EncodeToString(hash.Sum(nil)) tmpDir := filepath.Join(utils.GetTmpBaseDir(), "helm-charts") _ = os.MkdirAll(tmpDir, 0o700) - tmpDir = filepath.Join(tmpDir, fmt.Sprintf("%s-%s", chartName, h)) + tmpDir = filepath.Join(tmpDir, fmt.Sprintf("%s-%s", c.chartName, h)) lockFile := tmpDir + ".lock" lock, err := lockedfile.Create(lockFile) @@ -186,11 +184,6 @@ func (c *HelmChart) pullTmpChart(ctx context.Context) (string, error) { } func (c *HelmChart) doPull(ctx context.Context, chartDir string) error { - chartName, err := c.GetChartName() - if err != nil { - return err - } - _ = os.RemoveAll(chartDir) _ = os.MkdirAll(filepath.Dir(chartDir), 0o700) @@ -200,7 +193,7 @@ func (c *HelmChart) doPull(ctx context.Context, chartDir string) error { } defer os.RemoveAll(tmpDir) - tmpChartDir := filepath.Join(tmpDir, chartName) + tmpChartDir := filepath.Join(tmpDir, c.chartName) cfg, err := c.buildHelmConfig(nil) if err != nil { @@ -218,7 +211,7 @@ func (c *HelmChart) doPull(ctx context.Context, chartDir string) error { } creds := c.credentials.FindCredentials(*c.Config.Repo, c.Config.CredentialsId) if creds == nil { - return fmt.Errorf("no credentials provided for Chart %s", chartName) + return fmt.Errorf("no credentials provided for Chart %s", c.chartName) } a.Username = creds.Username a.Password = creds.Password @@ -234,7 +227,7 @@ func (c *HelmChart) doPull(ctx context.Context, chartDir string) error { out, err = a.Run(*c.Config.Repo) } else { a.RepoURL = *c.Config.Repo - out, err = a.Run(chartName) + out, err = a.Run(c.chartName) } if err != nil { return err @@ -283,10 +276,6 @@ func (c *HelmChart) checkUpdateOciRepo(ctx context.Context) (string, bool, error } func (c *HelmChart) checkUpdateHelmRepo() (string, bool, error) { - chartName, err := c.GetChartName() - if err != nil { - return "", false, err - } var latestVersion string settings := cli.New() @@ -298,7 +287,7 @@ func (c *HelmChart) checkUpdateHelmRepo() (string, bool, error) { } e = c.credentials.FindCredentials(*c.Config.Repo, c.Config.CredentialsId) if e == nil { - return "", false, fmt.Errorf("no credentials provided for Chart %s", chartName) + return "", false, fmt.Errorf("no credentials provided for Chart %s", c.chartName) } } else { e = &repo.Entry{ @@ -327,9 +316,9 @@ func (c *HelmChart) checkUpdateHelmRepo() (string, bool, error) { return "", false, err } - indexEntry, ok := index.Entries[chartName] + indexEntry, ok := index.Entries[c.chartName] if !ok || len(indexEntry) == 0 { - return "", false, fmt.Errorf("helm chart %s not found in repo index", chartName) + return "", false, fmt.Errorf("helm chart %s not found in repo index", c.chartName) } var ls versions.LooseVersionSlice @@ -344,27 +333,15 @@ func (c *HelmChart) checkUpdateHelmRepo() (string, bool, error) { } func (c *HelmChart) Render(ctx context.Context, k *k8s.K8sCluster) error { - chartName, err := c.GetChartName() + err := c.doRender(ctx, k) if err != nil { - return err - } - err = c.doRender(ctx, k) - if err != nil { - return fmt.Errorf("rendering helm chart %s for release %s has failed: %w", chartName, c.Config.ReleaseName, err) + return fmt.Errorf("rendering helm chart %s for release %s has failed: %w", c.chartName, c.Config.ReleaseName, err) } return nil } func (c *HelmChart) doRender(ctx context.Context, k *k8s.K8sCluster) error { - chartName, err := c.GetChartName() - if err != nil { - return err - } - - chartDir, err := c.GetChartDir() - if err != nil { - return err - } + chartDir := c.chartDir needsPull, versionChanged, prePulledVersion, err := c.checkNeedsPull(chartDir, false) if err != nil { @@ -373,13 +350,13 @@ func (c *HelmChart) doRender(ctx context.Context, k *k8s.K8sCluster) error { if needsPull { if versionChanged { return fmt.Errorf("pre-pulled Helm Chart %s need to be pulled (call 'kluctl helm-pull'). "+ - "Desired version is %s while pre-pulled version is %s", chartName, *c.Config.ChartVersion, prePulledVersion) + "Desired version is %s while pre-pulled version is %s", c.chartName, *c.Config.ChartVersion, prePulledVersion) } else { status.Warning(ctx, "Warning, need to pull Helm Chart %s with version %s. "+ - "Please consider pre-pulling it with 'kluctl helm-pull'", chartName, *c.Config.ChartVersion) + "Please consider pre-pulling it with 'kluctl helm-pull'", c.chartName, *c.Config.ChartVersion) } - s := status.Start(ctx, "Pulling Helm Chart %s with version %s", chartName, *c.Config.ChartVersion) + s := status.Start(ctx, "Pulling Helm Chart %s with version %s", c.chartName, *c.Config.ChartVersion) defer s.Failed() chartDir, err = c.pullTmpChart(ctx) From 9f9865f81ec8d1b73f35830643caa556169bc5eb Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Nov 2022 22:17:49 +0100 Subject: [PATCH 1210/2916] fix: Disable OCI credentials for now --- pkg/deployment/helm_chart.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index cf9a62766..1b53132c0 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -206,6 +206,10 @@ func (c *HelmChart) doPull(ctx context.Context, chartDir string) error { a.Version = *c.Config.ChartVersion if c.Config.CredentialsId != nil { + if registry.IsOCI(*c.Config.Repo) { + return fmt.Errorf("OCI charts can currently only be authenticated via registry login and not via cli arguments") + } + if c.credentials == nil { return fmt.Errorf("no credentials provider") } From 78dde16ff1d7508707a6da106a7466067cb0f871 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Nov 2022 22:37:55 +0100 Subject: [PATCH 1211/2916] tests: Add tests for Helm credentials --- e2e/helm_test.go | 189 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 133 insertions(+), 56 deletions(-) diff --git a/e2e/helm_test.go b/e2e/helm_test.go index e4195f7f7..780ea9c2b 100644 --- a/e2e/helm_test.go +++ b/e2e/helm_test.go @@ -19,6 +19,7 @@ import ( "helm.sh/helm/v3/pkg/uploader" "net/http" "net/http/httptest" + "net/url" "os" "path/filepath" "sort" @@ -65,7 +66,7 @@ type repoChart struct { version string } -func createHelmRepo(t *testing.T, charts []repoChart) string { +func createHelmRepo(t *testing.T, charts []repoChart, password string) string { tmpDir := t.TempDir() for _, c := range charts { @@ -73,7 +74,18 @@ func createHelmRepo(t *testing.T, charts []repoChart) string { _ = utils.CopyFile(tgz, filepath.Join(tmpDir, filepath.Base(tgz))) } - s := httptest.NewServer(http.FileServer(http.FS(os.DirFS(tmpDir)))) + fs := http.FileServer(http.FS(os.DirFS(tmpDir))) + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if password != "" { + _, p, ok := r.BasicAuth() + if !ok || p != password { + http.Error(w, "Auth header was incorrect", http.StatusUnauthorized) + return + } + } + fs.ServeHTTP(w, r) + })) + t.Cleanup(s.Close) i, err := repo.IndexDirectory(tmpDir, s.URL) @@ -90,15 +102,30 @@ func createHelmRepo(t *testing.T, charts []repoChart) string { return s.URL } -func createOciRepo(t *testing.T, charts []repoChart) string { +func createOciRepo(t *testing.T, charts []repoChart, password string) string { tmpDir := t.TempDir() ociRegistry := registry.New() - ociServer := httptest.NewServer(ociRegistry) + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if password != "" { + _, p, ok := r.BasicAuth() + if !ok { + w.Header().Add("WWW-Authenticate", "Basic") + http.Error(w, "Auth header was incorrect", http.StatusUnauthorized) + return + } + if !ok || p != password { + http.Error(w, "Auth header was incorrect", http.StatusUnauthorized) + return + } + } + ociRegistry.ServeHTTP(w, r) + })) - t.Cleanup(ociServer.Close) + t.Cleanup(s.Close) - ociUrl := strings.ReplaceAll(ociServer.URL, "http://", "oci://") + ociUrl := strings.ReplaceAll(s.URL, "http://", "oci://") + ociUrl2, _ := url.Parse(ociUrl) var out strings.Builder settings := cli.New() @@ -108,6 +135,20 @@ func createOciRepo(t *testing.T, charts []repoChart) string { Options: []pusher.Option{}, } + var registryClient *registry2.Client + if password != "" { + var err error + registryClient, err = registry2.NewClient() + if err != nil { + t.Fatal(err) + } + err = registryClient.Login(ociUrl2.Host, registry2.LoginOptBasicAuth("test-user", password), registry2.LoginOptInsecure(true)) + if err != nil { + t.Fatal(err) + } + c.Options = append(c.Options, pusher.WithRegistryClient(registryClient)) + } + for _, chart := range charts { tgz := createHelmPackage(t, chart.chartName, chart.version) _ = utils.CopyFile(tgz, filepath.Join(tmpDir, filepath.Base(tgz))) @@ -118,14 +159,18 @@ func createOciRepo(t *testing.T, charts []repoChart) string { } } + if registryClient != nil { + registryClient.Logout(ociUrl2.Host) + } + return ociUrl } -func createHelmOrOciRepo(t *testing.T, charts []repoChart, oci bool) string { +func createHelmOrOciRepo(t *testing.T, charts []repoChart, oci bool, password string) string { if oci { - return createOciRepo(t, charts) + return createOciRepo(t, charts, password) } else { - return createHelmRepo(t, charts) + return createHelmRepo(t, charts, password) } } @@ -162,35 +207,16 @@ func addHelmDeployment(p *testProject, dir string, repoUrl string, chartName, ve } } -func testHelmNoPrePull(t *testing.T, oci bool) { - t.Parallel() - - k := defaultCluster1 - - p := &testProject{} - p.init(t, k) - - createNamespace(t, k, p.testSlug()) - - repoUrl := createHelmOrOciRepo(p.t, []repoChart{ - {chartName: "test-chart1", version: "0.1.0"}, - }, oci) - - p.updateTarget("test", nil) - addHelmDeployment(p, "helm1", repoUrl, "test-chart1", "0.1.0", "test-helm1", p.testSlug(), nil) - p.KluctlMust("deploy", "--yes", "-t", "test") - assertConfigMapExists(t, k, p.testSlug(), "test-helm1-test-chart1") +type testCase struct { + name string + oci bool + testAuth bool + credsId string + extraArgs []string + expectedError string } -func TestHelmNoPrePull(t *testing.T) { - testHelmNoPrePull(t, false) -} - -func TestHelmNoPrePullOci(t *testing.T) { - testHelmNoPrePull(t, true) -} - -func testHelmPrePull(t *testing.T, oci bool) { +func testHelmPull(t *testing.T, tc testCase, prePull bool) { t.Parallel() k := defaultCluster1 @@ -200,33 +226,84 @@ func testHelmPrePull(t *testing.T, oci bool) { createNamespace(t, k, p.testSlug()) + password := "" + if tc.testAuth { + password = "secret-password" + } + repoUrl := createHelmOrOciRepo(p.t, []repoChart{ {chartName: "test-chart1", version: "0.1.0"}, - {chartName: "test-chart2", version: "0.1.0"}, - }, oci) + }, tc.oci, password) p.updateTarget("test", nil) addHelmDeployment(p, "helm1", repoUrl, "test-chart1", "0.1.0", "test-helm1", p.testSlug(), nil) - p.KluctlMust("helm-pull") - assert.FileExists(t, filepath.Join(p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()), "helm1/charts/test-chart1/Chart.yaml")) - p.KluctlMust("deploy", "--yes", "-t", "test") - assertConfigMapExists(t, k, p.testSlug(), "test-helm1-test-chart1") + if tc.testAuth { + if tc.credsId != "" { + p.updateYaml("helm1/helm-chart.yaml", func(o *uo.UnstructuredObject) error { + _ = o.SetNestedField(tc.credsId, "helmChart", "credentialsId") + return nil + }, "") + } + } - addHelmDeployment(p, "helm2", repoUrl, "test-chart2", "0.1.0", "test-helm2", p.testSlug(), nil) + if prePull { + args := []string{"helm-pull"} + args = append(args, tc.extraArgs...) + + _, stderr, err := p.Kluctl(args...) + if tc.expectedError != "" { + assert.Error(t, err) + assert.Contains(t, stderr, tc.expectedError) + return + } else { + assert.NoError(t, err) + assert.FileExists(t, filepath.Join(p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()), "helm1/charts/test-chart1/Chart.yaml")) + } + } - p.KluctlMust("helm-pull") - assert.FileExists(t, filepath.Join(p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()), "helm2/charts/test-chart2/Chart.yaml")) - p.KluctlMust("deploy", "--yes", "-t", "test") - assertConfigMapExists(t, k, p.testSlug(), "test-helm2-test-chart2") + args := []string{"deploy", "--yes", "-t", "test"} + args = append(args, tc.extraArgs...) + _, stderr, err := p.Kluctl(args...) + prePullWarning := "Warning, need to pull Helm Chart test-chart1 with version 0.1.0." + if prePull { + assert.NotContains(t, stderr, prePullWarning) + } else { + assert.Contains(t, stderr, prePullWarning) + } + if tc.expectedError != "" { + assert.Error(t, err) + assert.Contains(t, stderr, tc.expectedError) + } else { + assert.NoError(t, err) + assertConfigMapExists(t, k, p.testSlug(), "test-helm1-test-chart1") + } } -func TestHelmPrePull(t *testing.T) { - testHelmPrePull(t, false) -} +func TestHelmPull(t *testing.T) { + tests := []testCase{ + {name: "helm-no-creds"}, + {name: "helm-creds-missing", oci: false, testAuth: true, credsId: "test-creds", + expectedError: "no credentials provided for Chart test-chart1"}, + {name: "helm-creds-invalid", oci: false, testAuth: true, credsId: "test-creds", + extraArgs: []string{"--helm-username=test-creds:user", "--helm-password=test-creds:invalid"}, + expectedError: "401 Unauthorized"}, + {name: "helm-creds-valid", oci: false, testAuth: true, credsId: "test-creds", + extraArgs: []string{"--helm-username=test-creds:user", "--helm-password=test-creds:secret-password"}}, + {name: "oci", oci: true}, + {name: "oci-creds-fail", oci: true, testAuth: true, credsId: "test-creds", + extraArgs: []string{"--helm-username=test-creds:user", "--helm-password=test-creds:secret-password"}, + expectedError: "OCI charts can currently only be authenticated via registry login and not via cli arguments"}, + } -func TestHelmPrePullOci(t *testing.T) { - testHelmPrePull(t, true) + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + testHelmPull(t, tc, false) + }) + t.Run(tc.name+"-prepull", func(t *testing.T) { + testHelmPull(t, tc, true) + }) + } } func testHelmManualUpgrade(t *testing.T, oci bool) { @@ -242,7 +319,7 @@ func testHelmManualUpgrade(t *testing.T, oci bool) { repoUrl := createHelmOrOciRepo(p.t, []repoChart{ {chartName: "test-chart1", version: "0.1.0"}, {chartName: "test-chart1", version: "0.2.0"}, - }, oci) + }, oci, "") p.updateTarget("test", nil) addHelmDeployment(p, "helm1", repoUrl, "test-chart1", "0.1.0", "test-helm1", p.testSlug(), nil) @@ -289,7 +366,7 @@ func testHelmUpdate(t *testing.T, oci bool, upgrade bool, commit bool) { {chartName: "test-chart1", version: "0.2.0"}, {chartName: "test-chart2", version: "0.1.0"}, {chartName: "test-chart2", version: "0.3.0"}, - }, oci) + }, oci, "") p.updateTarget("test", nil) addHelmDeployment(p, "helm1", repoUrl, "test-chart1", "0.1.0", "test-helm1", p.testSlug(), nil) @@ -398,7 +475,7 @@ func TestHelmValues(t *testing.T) { repoUrl := createHelmRepo(p.t, []repoChart{ {chartName: "test-chart1", version: "0.1.0"}, {chartName: "test-chart2", version: "0.1.0"}, - }) + }, "") values1 := map[string]any{ "data": map[string]any{ @@ -463,7 +540,7 @@ func TestHelmTemplateChartYaml(t *testing.T) { repoUrl := createHelmRepo(p.t, []repoChart{ {chartName: "test-chart1", version: "0.1.0"}, {chartName: "test-chart2", version: "0.1.0"}, - }) + }, "") p.updateTarget("test", nil) addHelmDeployment(p, "helm1", repoUrl, "test-chart1", "0.1.0", "test-helm-{{ args.a }}", p.testSlug(), nil) From 46eea9002af77fdd539d9c2470fdd354214102ae Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Nov 2022 22:38:25 +0100 Subject: [PATCH 1212/2916] chore: Run go mod tidy --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 9c9e527f2..8110b5859 100644 --- a/go.mod +++ b/go.mod @@ -59,6 +59,7 @@ require ( require ( github.com/go-logr/logr v1.2.3 + github.com/hashicorp/go-multierror v1.1.1 github.com/huandu/xstrings v1.3.2 go.mozilla.org/sops/v3 v3.7.3 sigs.k8s.io/controller-runtime v0.13.1 @@ -142,7 +143,6 @@ require ( github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-hclog v1.3.1 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-plugin v1.4.5 // indirect github.com/hashicorp/go-retryablehttp v0.7.1 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect From a7f87d0444a56d51c45cd3c7c65250be7eef30c6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Nov 2022 23:29:11 +0100 Subject: [PATCH 1213/2916] ci: Run cross-compile in own workflow and only on main --- .github/workflows/cross-compile.yaml | 36 ++++++++++++++++++++++++++++ .github/workflows/tests.yml | 28 ---------------------- 2 files changed, 36 insertions(+), 28 deletions(-) create mode 100644 .github/workflows/cross-compile.yaml diff --git a/.github/workflows/cross-compile.yaml b/.github/workflows/cross-compile.yaml new file mode 100644 index 000000000..556c864d7 --- /dev/null +++ b/.github/workflows/cross-compile.yaml @@ -0,0 +1,36 @@ +name: cross-compile + +on: + push: + branches: + - main + +jobs: + cross-compile: + runs-on: ubuntu-20.04 + if: github.event_name != 'pull_request' + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - uses: actions/setup-go@v3 + with: + go-version: '1.19' + - uses: actions/cache@v3 + with: + path: | + ~/go/pkg/mod + ~/.cache/go-build + key: cross-compile-go-${{ runner.os }}-${{ hashFiles('**/go.sum') }} + restore-keys: | + cross-compile-go-${{ runner.os }}- + - name: Build kluctl (linux) + run: | + make build GOARCH=amd64 GOOS=linux + - name: Build kluctl (darwin) + run: | + make build GOARCH=amd64 GOOS=darwin + - name: Build kluctl (windows) + run: | + make build GOARCH=amd64 GOOS=windows diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2731beb35..0b99db18d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -40,34 +40,6 @@ jobs: exit 1 fi - cross-compile: - runs-on: ubuntu-20.04 - steps: - - name: Checkout - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - uses: actions/setup-go@v3 - with: - go-version: '1.19' - - uses: actions/cache@v3 - with: - path: | - ~/go/pkg/mod - ~/.cache/go-build - key: cross-compile-go-${{ runner.os }}-${{ hashFiles('**/go.sum') }} - restore-keys: | - cross-compile-go-${{ runner.os }}- - - name: Build kluctl (linux) - run: | - make build GOARCH=amd64 GOOS=linux - - name: Build kluctl (darwin) - run: | - make build GOARCH=amd64 GOOS=darwin - - name: Build kluctl (windows) - run: | - make build GOARCH=amd64 GOOS=windows - tests: strategy: matrix: From c974123e5d3ec3c2c47008cd90663fdd1cc7c79b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Nov 2022 23:33:52 +0100 Subject: [PATCH 1214/2916] ci: In pull requests, only run e2e tests for linux --- .github/workflows/tests.yml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0b99db18d..08e319e0b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -45,11 +45,11 @@ jobs: matrix: include: - os: ubuntu-20.04 - binary-suffix: linux-amd64 + run_on_pull_requests: true - os: macos-11 - binary-suffix: darwin-amd64 + run_on_pull_requests: false - os: windows-2019 - binary-suffix: windows-amd64 + run_on_pull_requests: false os: [ubuntu-20.04, macos-11, windows-2019] fail-fast: false runs-on: ${{ matrix.os }} @@ -67,15 +67,17 @@ jobs: key: tests-go-${{ runner.os }}-${{ hashFiles('**/go.sum') }} restore-keys: | tests-go-${{ runner.os }}- - - name: setup-envtest - shell: bash - run: | - make install-envtest - name: Run unit tests shell: bash run: | make test-unit + - name: setup-envtest + if: matrix.run_on_pull_requests || github.event_name != 'pull_request' + shell: bash + run: | + make install-envtest - name: Run e2e tests + if: matrix.run_on_pull_requests || github.event_name != 'pull_request' shell: bash run: | make test-e2e From 6c898b13ad0cbafad02f3d282709c9f732f42f9f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 15 Nov 2022 23:41:24 +0100 Subject: [PATCH 1215/2916] ci: Wipe cache --- .github/workflows/tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 08e319e0b..92f166bb7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -26,7 +26,7 @@ jobs: ~/.cache/go-build key: docs-check-go-${{ runner.os }}-${{ hashFiles('**/go.sum') }} restore-keys: | - docs-check-go-${{ runner.os }}- + docs1-check-go-${{ runner.os }}- - name: Check links on changed files run: | make markdown-link-check @@ -64,9 +64,9 @@ jobs: path: | ~/go/pkg/mod ~/.cache/go-build - key: tests-go-${{ runner.os }}-${{ hashFiles('**/go.sum') }} + key: tests1-go-${{ runner.os }}-${{ hashFiles('**/go.sum') }} restore-keys: | - tests-go-${{ runner.os }}- + tests1-go-${{ runner.os }}- - name: Run unit tests shell: bash run: | From 603e0716fafee9254e35bfda1e77e98c643419e5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Nov 2022 23:02:37 +0000 Subject: [PATCH 1216/2916] chore(deps): Bump golang.org/x/crypto from 0.1.0 to 0.2.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.1.0 to 0.2.0. - [Release notes](https://github.com/golang/crypto/releases) - [Commits](https://github.com/golang/crypto/compare/v0.1.0...v0.2.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8110b5859..30a4da448 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( github.com/stretchr/testify v1.8.1 github.com/whilp/git-urls v1.0.0 github.com/xanzy/ssh-agent v0.3.3-0.20220920102508-0fa644ba07f4 - golang.org/x/crypto v0.1.0 + golang.org/x/crypto v0.2.0 golang.org/x/net v0.2.0 golang.org/x/sync v0.1.0 golang.org/x/sys v0.2.0 diff --git a/go.sum b/go.sum index c7ca5c92f..28eb9796f 100644 --- a/go.sum +++ b/go.sum @@ -870,8 +870,8 @@ golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.2.0 h1:BRXPfhNivWL5Yq0BGQ39a2sW6t44aODpfxkWjYdzewE= +golang.org/x/crypto v0.2.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= From 7028b084e1dd01049a7cc368657e7ab2436bf836 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 16 Nov 2022 20:38:32 +0100 Subject: [PATCH 1217/2916] fix: Fix "invalid cross-device link" errors when pulling charts inside docker --- pkg/deployment/helm_chart.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index 1b53132c0..0d9b4d4cb 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -184,10 +184,14 @@ func (c *HelmChart) pullTmpChart(ctx context.Context) (string, error) { } func (c *HelmChart) doPull(ctx context.Context, chartDir string) error { + baseDir := filepath.Dir(chartDir) + _ = os.RemoveAll(chartDir) - _ = os.MkdirAll(filepath.Dir(chartDir), 0o700) + _ = os.MkdirAll(baseDir, 0o700) - tmpDir, err := os.MkdirTemp(utils.GetTmpBaseDir(), "helm-pull-") + // need to use the same filesystem/volume that we later os.Rename the final pull chart to, as otherwise + // the rename operation will lead to errors + tmpDir, err := os.MkdirTemp(baseDir, c.chartName+"-pull-") if err != nil { return err } From f811edec55f506a07d1c158b8594a55c06f74b6d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 16 Nov 2022 21:00:21 +0100 Subject: [PATCH 1218/2916] docs: Fix reference to long removed deleteByLabels --- docs/reference/commands/delete.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/commands/delete.md b/docs/reference/commands/delete.md index 264e4c553..e67295aab 100644 --- a/docs/reference/commands/delete.md +++ b/docs/reference/commands/delete.md @@ -17,7 +17,7 @@ Delete a target (or parts of it) from the corresponding cluster Objects are located based on 'commonLabels', configured in 'deployment.yaml' WARNING: This command will also delete objects which are not part of your deployment -project (anymore). It really only decides based on the 'deleteByLabel' labels and does NOT +project (anymore). It really only decides based on the 'commonLabels' labels and does NOT take the local target/state into account! From 90d5b0f5a86d37e20356e1ee28f2480f46453cd1 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 16 Nov 2022 21:04:28 +0100 Subject: [PATCH 1219/2916] docs: Move args documentation into .kluctl.yaml documentation --- docs/reference/commands/delete.md | 2 +- docs/reference/deployments/deployment-yml.md | 45 ------------------ docs/reference/kluctl-project/README.md | 50 +++++++++++++++++++- 3 files changed, 49 insertions(+), 48 deletions(-) diff --git a/docs/reference/commands/delete.md b/docs/reference/commands/delete.md index e67295aab..264e4c553 100644 --- a/docs/reference/commands/delete.md +++ b/docs/reference/commands/delete.md @@ -17,7 +17,7 @@ Delete a target (or parts of it) from the corresponding cluster Objects are located based on 'commonLabels', configured in 'deployment.yaml' WARNING: This command will also delete objects which are not part of your deployment -project (anymore). It really only decides based on the 'commonLabels' labels and does NOT +project (anymore). It really only decides based on the 'deleteByLabel' labels and does NOT take the local target/state into account! diff --git a/docs/reference/deployments/deployment-yml.md b/docs/reference/deployments/deployment-yml.md index 7a0be122b..a8ff6252f 100644 --- a/docs/reference/deployments/deployment-yml.md +++ b/docs/reference/deployments/deployment-yml.md @@ -24,9 +24,6 @@ deployments: commonLabels: my.prefix/target: "{{ target.name }}" my.prefix/deployment-project: my-deployment-project - -args: -- name: environment ``` The following sub-chapters describe the available fields in the `deployment.yaml` @@ -248,48 +245,6 @@ A list of common tags which are applied to all kustomize deployments and sub-dep See [tags](./tags.md) for more details. -## args -A list of arguments that can or must be passed to most kluctl operations. Each of these arguments is then available -in templating via the global `args` object. Only the root `deployment.yaml` can contain such argument definitions. - -An example looks like this: -```yaml -deployments: - - path: nginx - -args: - - name: environment - - name: enable_debug - default: false - - name: complex_arg - default: - my: - nested1: arg1 - nested2: arg2 -``` - -These arguments can then be used in templating, e.g. by using `{{ args.environment }}`. - -When calling kluctl, most of the commands will then require you to specify at least `-a environment=xxx` and optionally -`-a enable_debug=true` - -The following sub chapters describe the fields for argument entries. - -### name -The name of the argument. - -### default -If specified, the argument becomes optional and will use the given value as default when not specified. - -The default value can be an arbitrary yaml value, meaning that it can also be a nested dictionary. In that case, passing -args in nested form will only set the nested value. With the above example of `complex_arg`, running: - -``` -kluctl deploy -t my-target -a my.nested1=override` -``` - -will only modify the value below `my.nested1` and keep the value of `my.nested2`. - ## ignoreForDiff A list of objects and fields to ignore while performing diffs. Consider the following example: diff --git a/docs/reference/kluctl-project/README.md b/docs/reference/kluctl-project/README.md index ba14d1b65..e48f33f47 100644 --- a/docs/reference/kluctl-project/README.md +++ b/docs/reference/kluctl-project/README.md @@ -35,13 +35,59 @@ targets: context: prod.example.com args: environment_name: prod + +args: + - name: environment ``` ## Allowed fields -Please check the following sub-sections of this section to see which fields are allowed at the root level of `.kluctl.yaml`. +### targets + +Please check the [targets](./targets) sub-section for details. + +### args + +A list of arguments that can or must be passed to most kluctl operations. Each of these arguments is then available +in templating via the global `args` object. + +An example looks like this: +```yaml +targets: +... + +args: + - name: environment + - name: enable_debug + default: false + - name: complex_arg + default: + my: + nested1: arg1 + nested2: arg2 +``` + +These arguments can then be used in templating, e.g. by using `{{ args.environment }}`. + +When calling kluctl, most of the commands will then require you to specify at least `-a environment=xxx` and optionally +`-a enable_debug=true` + +The following sub chapters describe the fields for argument entries. + +#### name +The name of the argument. + +#### default +If specified, the argument becomes optional and will use the given value as default when not specified. + +The default value can be an arbitrary yaml value, meaning that it can also be a nested dictionary. In that case, passing +args in nested form will only set the nested value. With the above example of `complex_arg`, running: + +``` +kluctl deploy -t my-target -a my.nested1=override` +``` -1. [targets](./targets) +will only modify the value below `my.nested1` and keep the value of `my.nested2`. ## Using Kluctl without .kluctl.yaml From 1c8c8abd2247d69096b361b39729c481de617476 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Nov 2022 22:16:53 +0000 Subject: [PATCH 1220/2916] chore(deps): Bump github.com/aws/aws-sdk-go from 1.44.122 to 1.44.139 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.122 to 1.44.139. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.122...v1.44.139) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 30a4da448..39c3fbede 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e github.com/Masterminds/semver/v3 v3.1.1 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d - github.com/aws/aws-sdk-go v1.44.122 + github.com/aws/aws-sdk-go v1.44.139 github.com/bitnami-labs/sealed-secrets v0.19.1 github.com/cyphar/filepath-securejoin v0.2.3 github.com/docker/distribution v2.8.1+incompatible diff --git a/go.sum b/go.sum index 28eb9796f..97ed5720e 100644 --- a/go.sum +++ b/go.sum @@ -130,8 +130,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/aws/aws-sdk-go v1.44.122 h1:p6mw01WBaNpbdP2xrisz5tIkcNwzj/HysobNoaAHjgo= -github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.139 h1:Mj/OZBy9RTbzJ8pfgK6rOL8xgUEAIn8pfIN6qWFtpAk= +github.com/aws/aws-sdk-go v1.44.139/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -819,6 +819,7 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI= github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= @@ -907,6 +908,7 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I= golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -954,6 +956,8 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -983,6 +987,7 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1054,14 +1059,18 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1134,6 +1143,7 @@ golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE= golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 4025358e28d4650f43516896bcfed9e472735bdc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Nov 2022 22:17:31 +0000 Subject: [PATCH 1221/2916] chore(deps): Bump golang.org/x/crypto from 0.2.0 to 0.3.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.2.0 to 0.3.0. - [Release notes](https://github.com/golang/crypto/releases) - [Commits](https://github.com/golang/crypto/compare/v0.2.0...v0.3.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 30a4da448..db5547ad2 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( github.com/stretchr/testify v1.8.1 github.com/whilp/git-urls v1.0.0 github.com/xanzy/ssh-agent v0.3.3-0.20220920102508-0fa644ba07f4 - golang.org/x/crypto v0.2.0 + golang.org/x/crypto v0.3.0 golang.org/x/net v0.2.0 golang.org/x/sync v0.1.0 golang.org/x/sys v0.2.0 diff --git a/go.sum b/go.sum index 28eb9796f..842a149d9 100644 --- a/go.sum +++ b/go.sum @@ -870,8 +870,8 @@ golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.2.0 h1:BRXPfhNivWL5Yq0BGQ39a2sW6t44aODpfxkWjYdzewE= -golang.org/x/crypto v0.2.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A= +golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= From 1b0bf313ec8b7cbae5b2bf8fea9528b302ce10f6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Nov 2022 22:17:37 +0000 Subject: [PATCH 1222/2916] chore(deps): Bump github.com/huandu/xstrings from 1.3.2 to 1.3.3 Bumps [github.com/huandu/xstrings](https://github.com/huandu/xstrings) from 1.3.2 to 1.3.3. - [Release notes](https://github.com/huandu/xstrings/releases) - [Commits](https://github.com/huandu/xstrings/compare/v1.3.2...v1.3.3) --- updated-dependencies: - dependency-name: github.com/huandu/xstrings dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 30a4da448..2d60b5875 100644 --- a/go.mod +++ b/go.mod @@ -60,7 +60,7 @@ require ( require ( github.com/go-logr/logr v1.2.3 github.com/hashicorp/go-multierror v1.1.1 - github.com/huandu/xstrings v1.3.2 + github.com/huandu/xstrings v1.3.3 go.mozilla.org/sops/v3 v3.7.3 sigs.k8s.io/controller-runtime v0.13.1 ) diff --git a/go.sum b/go.sum index 28eb9796f..a8fae1877 100644 --- a/go.sum +++ b/go.sum @@ -484,8 +484,9 @@ github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSo github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef h1:A9HsByNhogrvm9cWb28sjiS3i7tcKCkflWFEkHfuAgM= github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= +github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= From 47ac7e4a41d63fed3832d2f676550287fb5cbd98 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 17 Nov 2022 11:48:42 +0100 Subject: [PATCH 1223/2916] docs: Make kustomize-deployment2 a simple-deployment --- docs/reference/deployments/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/reference/deployments/README.md b/docs/reference/deployments/README.md index 19145cca4..36c4f42d7 100644 --- a/docs/reference/deployments/README.md +++ b/docs/reference/deployments/README.md @@ -49,7 +49,6 @@ Some visualized files/directories have links attached, follow them to get more i |-- sub-deployment/ | |-- deployment.yaml | |-- kustomize-deployment2/ - | | |-- kustomization.yaml | | |-- resource1.yaml | | `-- ... | |-- kustomize-deployment3/ From 84ca198a97957d6dd58110facd807042cf6308df Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Nov 2022 22:10:48 +0000 Subject: [PATCH 1224/2916] chore(deps): Bump github.com/aws/aws-sdk-go from 1.44.139 to 1.44.140 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.139 to 1.44.140. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.139...v1.44.140) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c1e9d2c67..a8ff4d321 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e github.com/Masterminds/semver/v3 v3.1.1 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d - github.com/aws/aws-sdk-go v1.44.139 + github.com/aws/aws-sdk-go v1.44.140 github.com/bitnami-labs/sealed-secrets v0.19.1 github.com/cyphar/filepath-securejoin v0.2.3 github.com/docker/distribution v2.8.1+incompatible diff --git a/go.sum b/go.sum index b9fdb39d8..b5bd674c7 100644 --- a/go.sum +++ b/go.sum @@ -130,8 +130,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/aws/aws-sdk-go v1.44.139 h1:Mj/OZBy9RTbzJ8pfgK6rOL8xgUEAIn8pfIN6qWFtpAk= -github.com/aws/aws-sdk-go v1.44.139/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.140 h1:6MxVSiAORc6AG+oh6401TEgWHb1ZzFL8y6+eBLoJtdU= +github.com/aws/aws-sdk-go v1.44.140/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= From 0579d250447bfafa0e31a4136351561e60515da2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 18 Nov 2022 22:53:40 +0000 Subject: [PATCH 1225/2916] chore(deps): Bump github.com/bitnami-labs/sealed-secrets Bumps [github.com/bitnami-labs/sealed-secrets](https://github.com/bitnami-labs/sealed-secrets) from 0.19.1 to 0.19.2. - [Release notes](https://github.com/bitnami-labs/sealed-secrets/releases) - [Changelog](https://github.com/bitnami-labs/sealed-secrets/blob/main/RELEASE-NOTES.md) - [Commits](https://github.com/bitnami-labs/sealed-secrets/compare/v0.19.1...v0.19.2) --- updated-dependencies: - dependency-name: github.com/bitnami-labs/sealed-secrets dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index a8ff4d321..8f2d91554 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/Masterminds/semver/v3 v3.1.1 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d github.com/aws/aws-sdk-go v1.44.140 - github.com/bitnami-labs/sealed-secrets v0.19.1 + github.com/bitnami-labs/sealed-secrets v0.19.2 github.com/cyphar/filepath-securejoin v0.2.3 github.com/docker/distribution v2.8.1+incompatible github.com/fluxcd/pkg/kustomize v0.8.0 @@ -193,7 +193,7 @@ require ( github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pierrec/lz4 v2.6.1+incompatible // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.13.0 // indirect + github.com/prometheus/client_golang v1.14.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect diff --git a/go.sum b/go.sum index b5bd674c7..77ae9e7db 100644 --- a/go.sum +++ b/go.sum @@ -137,8 +137,8 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bitnami-labs/sealed-secrets v0.19.1 h1:ZNLcVtTXRf7VkyNzyhe9omlwNYI0OHDteTbIHsQI7Ug= -github.com/bitnami-labs/sealed-secrets v0.19.1/go.mod h1:5UcsiOdOoviJUtXY1GNSeFa8zezbBY+j9pQAA2RtKyw= +github.com/bitnami-labs/sealed-secrets v0.19.2 h1:0NpJCAhfDTkZ2u3PJaRWSynxlg55ssHxPQNn8T5JNPU= +github.com/bitnami-labs/sealed-secrets v0.19.2/go.mod h1:KjTgHdD22gpjERjT1rA8yi4P4EkccMhRR2JW6Ctfu6g= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= @@ -654,8 +654,8 @@ github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo/v2 v2.1.6 h1:Fx2POJZfKRQcM1pH49qSZiYeu319wji004qX+GDovrU= -github.com/onsi/gomega v1.20.2 h1:8uQq0zMgLEfa0vRrrBgaJF2gyW9Da9BmfGV+OyUzfkY= +github.com/onsi/ginkgo/v2 v2.5.0 h1:TRtrvv2vdQqzkwrQ1ke6vtXf7IK34RBUJafIy1wMwls= +github.com/onsi/gomega v1.24.1 h1:KORJXNNTzJXzu4ScJWssJfJMnJ+2QJqhoQSRwNlze9E= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034= @@ -694,8 +694,8 @@ github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3O github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_golang v1.13.0 h1:b71QUfeo5M8gq2+evJdTPfZhYMAU0uKPkyPJ7TPsloU= -github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ= +github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= +github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= From 87c683f52efa37b10c2b573162ffc6c10cff0a44 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 19 Nov 2022 19:27:14 +0000 Subject: [PATCH 1226/2916] chore(deps): Bump github.com/aws/aws-sdk-go from 1.44.140 to 1.44.142 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.140 to 1.44.142. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.140...v1.44.142) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8f2d91554..9b323e838 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e github.com/Masterminds/semver/v3 v3.1.1 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d - github.com/aws/aws-sdk-go v1.44.140 + github.com/aws/aws-sdk-go v1.44.142 github.com/bitnami-labs/sealed-secrets v0.19.2 github.com/cyphar/filepath-securejoin v0.2.3 github.com/docker/distribution v2.8.1+incompatible diff --git a/go.sum b/go.sum index 77ae9e7db..2b2ef6427 100644 --- a/go.sum +++ b/go.sum @@ -130,8 +130,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/aws/aws-sdk-go v1.44.140 h1:6MxVSiAORc6AG+oh6401TEgWHb1ZzFL8y6+eBLoJtdU= -github.com/aws/aws-sdk-go v1.44.140/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.142 h1:KZ1/FDwCSft1DuNllFaBtWpcG0CW2NgQjvOrE1TdlXE= +github.com/aws/aws-sdk-go v1.44.142/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= From cc88ac875c01265a183e5b0e2e8a07d7287cbbe4 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 21 Nov 2022 13:29:38 +0100 Subject: [PATCH 1227/2916] fix: Fix race condition due to use of GoLimitedMultiError --- cmd/kluctl/commands/cmd_helm_pull.go | 4 +--- cmd/kluctl/commands/cmd_helm_update.go | 10 ++-------- pkg/utils/limited_routine.go | 12 +++++++++--- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/cmd/kluctl/commands/cmd_helm_pull.go b/cmd/kluctl/commands/cmd_helm_pull.go index bc55d6865..af62259f1 100644 --- a/cmd/kluctl/commands/cmd_helm_pull.go +++ b/cmd/kluctl/commands/cmd_helm_pull.go @@ -52,9 +52,7 @@ func (cmd *helmPullCmd) Run() error { return err } - wg.Add(1) - utils.GoLimitedMultiError(cliCtx, sem, &errs, &mutex, func() error { - defer wg.Done() + utils.GoLimitedMultiError(cliCtx, sem, &errs, &mutex, &wg, func() error { s := status.Start(cliCtx, "%s: Pulling Chart", statusPrefix) defer s.Failed() err := doPull(statusPrefix, p, cmd.HelmCredentials, s) diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go index 30e4a5626..7d6fc14f0 100644 --- a/cmd/kluctl/commands/cmd_helm_update.go +++ b/cmd/kluctl/commands/cmd_helm_update.go @@ -60,10 +60,7 @@ func (cmd *helmUpdateCmd) Run() error { return nil } - wg.Add(1) - utils.GoLimitedMultiError(cliCtx, sem, &errs, &mutex, func() error { - defer wg.Done() - + utils.GoLimitedMultiError(cliCtx, sem, &errs, &mutex, &wg, func() error { chart, newVersion, updated, err := cmd.doCheckUpdate(gitRootPath, p) if err != nil { return err @@ -101,10 +98,7 @@ func (cmd *helmUpdateCmd) Run() error { for _, uc := range updatedCharts { uc := uc - wg.Add(1) - utils.GoLimitedMultiError(cliCtx, sem, &errs, &mutex, func() error { - defer wg.Done() - + utils.GoLimitedMultiError(cliCtx, sem, &errs, &mutex, &wg, func() error { if cmd.Interactive { statusPrefix, _ := filepath.Rel(gitRootPath, filepath.Dir(uc.path)) if !status.AskForConfirmation(cliCtx, fmt.Sprintf("%s: Do you want to upgrade Chart %s from version %s to %s?", diff --git a/pkg/utils/limited_routine.go b/pkg/utils/limited_routine.go index 7653bfc26..f38a1bdde 100644 --- a/pkg/utils/limited_routine.go +++ b/pkg/utils/limited_routine.go @@ -7,8 +7,14 @@ import ( "sync" ) -func GoLimited(ctx context.Context, sem *semaphore.Weighted, fn func(), errCb func(err error)) { +func GoLimited(ctx context.Context, sem *semaphore.Weighted, wg *sync.WaitGroup, fn func(), errCb func(err error)) { + if wg != nil { + wg.Add(1) + } go func() { + if wg != nil { + defer wg.Done() + } err := sem.Acquire(ctx, 1) if err != nil { if errCb != nil { @@ -21,13 +27,13 @@ func GoLimited(ctx context.Context, sem *semaphore.Weighted, fn func(), errCb fu }() } -func GoLimitedMultiError(ctx context.Context, sem *semaphore.Weighted, merr **multierror.Error, mutex *sync.Mutex, fn func() error) { +func GoLimitedMultiError(ctx context.Context, sem *semaphore.Weighted, merr **multierror.Error, mutex *sync.Mutex, wg *sync.WaitGroup, fn func() error) { errCb := func(err error) { mutex.Lock() defer mutex.Unlock() *merr = multierror.Append(*merr, err) } - GoLimited(ctx, sem, func() { + GoLimited(ctx, sem, wg, func() { err := fn() if err != nil { errCb(err) From e2c9361f34a2e8d4661fb2ffd00c82907bba796c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Nov 2022 22:48:37 +0000 Subject: [PATCH 1228/2916] chore(deps): Bump github.com/aws/aws-sdk-go from 1.44.142 to 1.44.144 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.142 to 1.44.144. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.142...v1.44.144) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9b323e838..57851c7ab 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e github.com/Masterminds/semver/v3 v3.1.1 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d - github.com/aws/aws-sdk-go v1.44.142 + github.com/aws/aws-sdk-go v1.44.144 github.com/bitnami-labs/sealed-secrets v0.19.2 github.com/cyphar/filepath-securejoin v0.2.3 github.com/docker/distribution v2.8.1+incompatible diff --git a/go.sum b/go.sum index 2b2ef6427..aa745abe8 100644 --- a/go.sum +++ b/go.sum @@ -130,8 +130,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/aws/aws-sdk-go v1.44.142 h1:KZ1/FDwCSft1DuNllFaBtWpcG0CW2NgQjvOrE1TdlXE= -github.com/aws/aws-sdk-go v1.44.142/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.144 h1:mMWdnYL8HZsobrQe1mwvQ18Xt8UbOVhWgipjuma5Mkg= +github.com/aws/aws-sdk-go v1.44.144/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= From ebba705357714fefeeeba390ccb012865924e17e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Nov 2022 22:07:33 +0000 Subject: [PATCH 1229/2916] chore(deps): Bump github.com/aws/aws-sdk-go from 1.44.144 to 1.44.145 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.144 to 1.44.145. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.144...v1.44.145) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 57851c7ab..7108d7e82 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e github.com/Masterminds/semver/v3 v3.1.1 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d - github.com/aws/aws-sdk-go v1.44.144 + github.com/aws/aws-sdk-go v1.44.145 github.com/bitnami-labs/sealed-secrets v0.19.2 github.com/cyphar/filepath-securejoin v0.2.3 github.com/docker/distribution v2.8.1+incompatible diff --git a/go.sum b/go.sum index aa745abe8..89ad58cea 100644 --- a/go.sum +++ b/go.sum @@ -130,8 +130,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/aws/aws-sdk-go v1.44.144 h1:mMWdnYL8HZsobrQe1mwvQ18Xt8UbOVhWgipjuma5Mkg= -github.com/aws/aws-sdk-go v1.44.144/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.145 h1:KMVRrIyjBsNz3xGPuHIRnhIuKlb5h3Ii5e5jbi3cgnc= +github.com/aws/aws-sdk-go v1.44.145/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= From d5ddbfa41f102882146b297a02ad5c26926aad2e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 25 Nov 2022 10:53:45 +0100 Subject: [PATCH 1230/2916] feat: Allow to pass SOPS decrypter from outside This is the preparation for SOPS support in the flux-kluctl-controller --- pkg/deployment/deployment_item.go | 31 +-------------- pkg/deployment/shared_context.go | 2 + pkg/kluctl_project/load.go | 16 +++++--- pkg/kluctl_project/project.go | 6 ++- pkg/kluctl_project/project_load.go | 4 +- pkg/kluctl_project/target_context.go | 3 +- pkg/sops/sops_decryptor.go | 21 +++++++++++ pkg/sops/utils.go | 56 ++++++++++++++++++++++++++++ pkg/vars/vars_loader.go | 27 +++++++------- pkg/vars/vars_loader_test.go | 3 +- pkg/yaml/yaml.go | 4 -- 11 files changed, 116 insertions(+), 57 deletions(-) create mode 100644 pkg/sops/sops_decryptor.go create mode 100644 pkg/sops/utils.go diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 084035ba4..d6d1ad573 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -3,14 +3,12 @@ package deployment import ( "fmt" "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/sops" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" - "go.mozilla.org/sops/v3" - "go.mozilla.org/sops/v3/cmd/sops/formats" - "go.mozilla.org/sops/v3/decrypt" "io/fs" "k8s.io/apimachinery/pkg/runtime/schema" "os" @@ -490,31 +488,6 @@ func (di *DeploymentItem) prepareKustomizationYaml() (*uo.UnstructuredObject, er return ky, nil } -func (di *DeploymentItem) decryptSopsFile(p string) error { - file, err := os.ReadFile(p) - if err != nil { - return fmt.Errorf("failed to read file %s: %w", p, err) - } - - if !yaml.IsMaybeSopsFile(file) { - return nil - } - - decrypted, err := decrypt.DataWithFormat(file, formats.FormatForPath(p)) - if err == sops.MetadataNotFound { - // not encrypted, so bail out - return nil - } else if err != nil { - return fmt.Errorf("failed to decrypt file %s: %w", p, err) - } - - err = os.WriteFile(p, decrypted, 0o600) - if err != nil { - return fmt.Errorf("failed to save decrypted file %s: %w", p, err) - } - return nil -} - func (di *DeploymentItem) buildKustomize() error { if di.dir == nil { return nil @@ -535,7 +508,7 @@ func (di *DeploymentItem) buildKustomize() error { for _, r := range resources { p := filepath.Join(di.RenderedDir, r) if utils.IsFile(p) { - err = di.decryptSopsFile(p) + err = sops.MaybeDecryptFile(di.ctx.SopsDecrypter, p) if err != nil { return err } diff --git a/pkg/deployment/shared_context.go b/pkg/deployment/shared_context.go index 4851cbcd9..f5334c195 100644 --- a/pkg/deployment/shared_context.go +++ b/pkg/deployment/shared_context.go @@ -4,6 +4,7 @@ import ( "context" "github.com/kluctl/kluctl/v2/pkg/git/repocache" "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/sops" "github.com/kluctl/kluctl/v2/pkg/vars" ) @@ -11,6 +12,7 @@ type SharedContext struct { Ctx context.Context K *k8s.K8sCluster RP *repocache.GitRepoCache + SopsDecrypter sops.SopsDecrypter VarsLoader *vars.VarsLoader HelmCredentials HelmCredentialsProvider diff --git a/pkg/kluctl_project/load.go b/pkg/kluctl_project/load.go index d5d8ec704..d477429b8 100644 --- a/pkg/kluctl_project/load.go +++ b/pkg/kluctl_project/load.go @@ -3,6 +3,7 @@ package kluctl_project import ( "context" "github.com/kluctl/go-jinja2" + "github.com/kluctl/kluctl/v2/pkg/sops" "github.com/kluctl/kluctl/v2/pkg/status" ) @@ -11,11 +12,16 @@ func LoadKluctlProject(ctx context.Context, args LoadKluctlProjectArgs, tmpDir s defer status.Trace(ctx, "leave LoadKluctlProject") p := &LoadedKluctlProject{ - ctx: ctx, - loadArgs: args, - TmpDir: tmpDir, - J2: j2, - RP: args.RP, + ctx: ctx, + loadArgs: args, + TmpDir: tmpDir, + J2: j2, + RP: args.RP, + SopsDecrypter: args.SopsDecrypter, + } + + if p.SopsDecrypter == nil { + p.SopsDecrypter = &sops.LocalSopsDecrypter{} } err := p.loadKluctlProject() diff --git a/pkg/kluctl_project/project.go b/pkg/kluctl_project/project.go index 7d36b0872..1701abfc2 100644 --- a/pkg/kluctl_project/project.go +++ b/pkg/kluctl_project/project.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/kluctl/go-jinja2" "github.com/kluctl/kluctl/v2/pkg/git/repocache" + "github.com/kluctl/kluctl/v2/pkg/sops" types2 "github.com/kluctl/kluctl/v2/pkg/types" ) @@ -23,8 +24,9 @@ type LoadedKluctlProject struct { Config types2.KluctlProject DynamicTargets []*types2.DynamicTarget - J2 *jinja2.Jinja2 - RP *repocache.GitRepoCache + J2 *jinja2.Jinja2 + RP *repocache.GitRepoCache + SopsDecrypter sops.SopsDecrypter } func (c *LoadedKluctlProject) FindBaseTarget(name string) (*types2.Target, error) { diff --git a/pkg/kluctl_project/project_load.go b/pkg/kluctl_project/project_load.go index c4a2b20b9..dfe0cbe2d 100644 --- a/pkg/kluctl_project/project_load.go +++ b/pkg/kluctl_project/project_load.go @@ -2,6 +2,7 @@ package kluctl_project import ( "github.com/kluctl/kluctl/v2/pkg/git/repocache" + "github.com/kluctl/kluctl/v2/pkg/sops" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/yaml" @@ -15,7 +16,8 @@ type LoadKluctlProjectArgs struct { ProjectDir string ProjectConfig string - RP *repocache.GitRepoCache + SopsDecrypter sops.SopsDecrypter + RP *repocache.GitRepoCache ClientConfigGetter func(context *string) (*rest.Config, *api.Config, error) } diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index 9af4f6c1c..952c44d85 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -91,7 +91,7 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, params Targe s.Success() } - varsLoader := vars.NewVarsLoader(ctx, k, p.RP, aws.NewClientFactory()) + varsLoader := vars.NewVarsLoader(ctx, k, p.SopsDecrypter, p.RP, aws.NewClientFactory()) if params.ForSeal { err = p.loadSecrets(target, varsCtx, varsLoader) @@ -104,6 +104,7 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, params Targe Ctx: ctx, K: k, RP: p.RP, + SopsDecrypter: p.SopsDecrypter, VarsLoader: varsLoader, HelmCredentials: params.HelmCredentials, RenderDir: params.RenderOutputDir, diff --git a/pkg/sops/sops_decryptor.go b/pkg/sops/sops_decryptor.go new file mode 100644 index 000000000..474d9872d --- /dev/null +++ b/pkg/sops/sops_decryptor.go @@ -0,0 +1,21 @@ +package sops + +import ( + "fmt" + "go.mozilla.org/sops/v3/cmd/sops/formats" + "go.mozilla.org/sops/v3/decrypt" +) + +type SopsDecrypter interface { + SopsDecryptWithFormat(data []byte, inputFormat, outputFormat formats.Format) ([]byte, error) +} + +type LocalSopsDecrypter struct { +} + +func (_ LocalSopsDecrypter) SopsDecryptWithFormat(data []byte, inputFormat, outputFormat formats.Format) ([]byte, error) { + if inputFormat != outputFormat { + return nil, fmt.Errorf("inputFormat and outputFormat must be equal") + } + return decrypt.DataWithFormat(data, inputFormat) +} diff --git a/pkg/sops/utils.go b/pkg/sops/utils.go new file mode 100644 index 000000000..39a1d85b2 --- /dev/null +++ b/pkg/sops/utils.go @@ -0,0 +1,56 @@ +package sops + +import ( + "bytes" + "errors" + "fmt" + "go.mozilla.org/sops/v3" + "go.mozilla.org/sops/v3/cmd/sops/formats" + "os" +) + +func IsMaybeSopsFile(s []byte) bool { + return bytes.Index(s, []byte("sops")) != -1 +} + +func MaybeDecrypt(decrypter SopsDecrypter, encrypted []byte, inputFormat, outputFormat formats.Format) ([]byte, bool, error) { + if decrypter == nil { + return encrypted, false, nil + } + + if !IsMaybeSopsFile(encrypted) { + return encrypted, false, nil + } + + d, err := decrypter.SopsDecryptWithFormat(encrypted, inputFormat, outputFormat) + if err != nil { + if errors.Is(err, sops.MetadataNotFound) { + return encrypted, false, nil + } + return nil, false, err + } + return d, true, nil +} + +func MaybeDecryptFile(decrypter SopsDecrypter, path string) error { + format := formats.FormatForPath(path) + + file, err := os.ReadFile(path) + if err != nil { + return fmt.Errorf("failed to read file %s: %w", path, err) + } + + decrypted, encrypted, err := MaybeDecrypt(decrypter, file, format, format) + if err != nil { + return fmt.Errorf("failed to decrypt file %s: %w", path, err) + } + if !encrypted { + return nil + } + + err = os.WriteFile(path, decrypted, 0o600) + if err != nil { + return fmt.Errorf("failed to save decrypted file %s: %w", path, err) + } + return nil +} diff --git a/pkg/vars/vars_loader.go b/pkg/vars/vars_loader.go index 0dd5e66eb..226a5d5e3 100644 --- a/pkg/vars/vars_loader.go +++ b/pkg/vars/vars_loader.go @@ -7,6 +7,7 @@ import ( "github.com/kluctl/go-jinja2" "github.com/kluctl/kluctl/v2/pkg/git/repocache" "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/sops" "github.com/kluctl/kluctl/v2/pkg/types" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/utils" @@ -14,9 +15,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/vars/aws" "github.com/kluctl/kluctl/v2/pkg/vars/vault" "github.com/kluctl/kluctl/v2/pkg/yaml" - "go.mozilla.org/sops/v3" "go.mozilla.org/sops/v3/cmd/sops/formats" - "go.mozilla.org/sops/v3/decrypt" "k8s.io/apimachinery/pkg/runtime/schema" "os" "strings" @@ -28,18 +27,20 @@ type usernamePassword struct { } type VarsLoader struct { - ctx context.Context - k *k8s.K8sCluster - rp *repocache.GitRepoCache - aws aws.AwsClientFactory + ctx context.Context + k *k8s.K8sCluster + sops sops.SopsDecrypter + rp *repocache.GitRepoCache + aws aws.AwsClientFactory credentialsCache map[string]usernamePassword } -func NewVarsLoader(ctx context.Context, k *k8s.K8sCluster, rp *repocache.GitRepoCache, aws aws.AwsClientFactory) *VarsLoader { +func NewVarsLoader(ctx context.Context, k *k8s.K8sCluster, sops sops.SopsDecrypter, rp *repocache.GitRepoCache, aws aws.AwsClientFactory) *VarsLoader { return &VarsLoader{ ctx: ctx, k: k, + sops: sops, rp: rp, aws: aws, credentialsCache: map[string]usernamePassword{}, @@ -110,14 +111,12 @@ func (v *VarsLoader) loadFile(varsCtx *VarsCtx, path string, searchDirs []string return fmt.Errorf("failed to render vars file %s: %w", path, err) } - if yaml.IsMaybeSopsFile([]byte(rendered)) { - decrypted, err := decrypt.DataWithFormat([]byte(rendered), formats.FormatForPath(path)) - if err != nil && err != sops.MetadataNotFound { - return fmt.Errorf("failed to decrypt vars file %s: %w", path, err) - } else if err == nil { - rendered = string(decrypted) - } + format := formats.FormatForPath(path) + decrypted, _, err := sops.MaybeDecrypt(v.sops, []byte(rendered), format, format) + if err != nil { + return fmt.Errorf("failed to decrypt vars file %s: %w", path, err) } + rendered = string(decrypted) newVars := uo.New() err = yaml.ReadYamlString(rendered, newVars) diff --git a/pkg/vars/vars_loader_test.go b/pkg/vars/vars_loader_test.go index 95db5f20f..95fff9e9a 100644 --- a/pkg/vars/vars_loader_test.go +++ b/pkg/vars/vars_loader_test.go @@ -10,6 +10,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/git/repocache" ssh_pool "github.com/kluctl/kluctl/v2/pkg/git/ssh-pool" "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/sops" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" @@ -56,7 +57,7 @@ func testVarsLoader(t *testing.T, test func(vl *VarsLoader, vc *VarsCtx, aws *aw grc := newRP(t) fakeAws := aws.NewFakeClientFactory() - vl := NewVarsLoader(context.TODO(), k, grc, fakeAws) + vl := NewVarsLoader(context.TODO(), k, &sops.LocalSopsDecrypter{}, grc, fakeAws) vc := NewVarsCtx(newJinja2Must(t)) test(vl, vc, fakeAws) diff --git a/pkg/yaml/yaml.go b/pkg/yaml/yaml.go index 0dde23fad..89610c94a 100644 --- a/pkg/yaml/yaml.go +++ b/pkg/yaml/yaml.go @@ -285,7 +285,3 @@ func Exists(p string) bool { p = FixPathExt(p) return utils.Exists(p) } - -func IsMaybeSopsFile(s []byte) bool { - return bytes.Index(s, []byte("sops")) != -1 -} From 9b8f27efec2a535500466c9e2d71174d76ab855a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 25 Nov 2022 15:58:30 +0100 Subject: [PATCH 1231/2916] fix: Fix simulation of dryRun creation into fresh namespaces The ApplyUtil only tracks applied objects of the current deployment item but not objects that were previously applied by other instances. This causes namespaces to be considered "non-existent" when not created in the same item. This is fixed by globally tracking created namespaces. --- pkg/deployment/utils/apply_utils.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index 440b83e76..2bee3e50d 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -46,7 +46,8 @@ type ApplyUtil struct { deletedHookObjects map[k8s2.ObjectRef]bool mutex sync.Mutex - abortSignal *atomic.Value + abortSignal *atomic.Value + allNamespaces *sync.Map ru *RemoteObjectUtils k *k8s.K8sCluster @@ -65,6 +66,11 @@ type ApplyDeploymentsUtil struct { abortSignal atomic.Value + // Used to track all created namespaces + // All ApplyUtil instances write to this in parallel and we ignore that order might be unstable + // This is only used to simulate dryRun apply into new namespaces + allNamespaces sync.Map + resultsMutex sync.Mutex results []*ApplyUtil } @@ -94,6 +100,7 @@ func (ad *ApplyDeploymentsUtil) NewApplyUtil(ctx context.Context, statusCtx *sta deletedObjects: map[k8s2.ObjectRef]bool{}, deletedHookObjects: map[k8s2.ObjectRef]bool{}, abortSignal: &ad.abortSignal, + allNamespaces: &ad.allNamespaces, ru: ad.ru, k: ad.k, o: ad.o, @@ -297,8 +304,7 @@ func (a *ApplyUtil) ApplyObject(x *uo.UnstructuredObject, replaced bool, hook bo x = x.Clone() x.SetK8sName(fmt.Sprintf("%s-%s", ref.Name, utils.RandomString(8))) } else if a.o.DryRun && remoteNamespace == nil && ref.Namespace != "" { - nsRef := k8s2.NewObjectRef("", "v1", "Namespace", ref.Namespace, "") - if _, ok := a.appliedObjects[nsRef]; ok { + if _, ok := a.allNamespaces.Load(ref.Namespace); ok { // The namespace does not really exist, but would have been created if dryRun would be false. // So let's pretend we deploy it to the default namespace with a dummy name usesDummyName = true @@ -318,6 +324,9 @@ func (a *ApplyUtil) ApplyObject(x *uo.UnstructuredObject, replaced bool, hook bo _ = r.ReplaceValues(tmpName, ref.Name) r.SetK8sNamespace(ref.Namespace) } + if r != nil && ref.GVK.GroupKind().String() == "Namespace" { + a.allNamespaces.Store(ref.Name, r) + } a.handleApiWarnings(ref, apiWarnings) if err == nil { a.handleResult(r, hook) From 121881e88e6c1a1b31a4e16ebe93803fa0d612f9 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 28 Nov 2022 10:38:46 +0100 Subject: [PATCH 1232/2916] fix: Make check for overwritten fields managers more strict Otherwise field managers that for example contain "kluctl" somewhere in-between are overwritten as well. Noticed this while working on the flux-kluctl-controller. --- pkg/diff/managed_fields.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/diff/managed_fields.go b/pkg/diff/managed_fields.go index 1a5a36673..9fa5d1d69 100644 --- a/pkg/diff/managed_fields.go +++ b/pkg/diff/managed_fields.go @@ -18,11 +18,11 @@ type LostOwnership struct { var forceApplyFieldAnnotationRegex = regexp.MustCompile(`^kluctl.io/force-apply-field(-\d*)?$`) var overwriteAllowedManagers = []*regexp.Regexp{ - regexp.MustCompile("kluctl"), - regexp.MustCompile("kubectl"), - regexp.MustCompile("kubectl-.*"), - regexp.MustCompile("rancher"), - regexp.MustCompile("k9s"), + regexp.MustCompile("^kluctl$"), + regexp.MustCompile("^kubectl$"), + regexp.MustCompile("^kubectl-.*$"), + regexp.MustCompile("^rancher$"), + regexp.MustCompile("^k9s$"), } func checkListItemMatch(o interface{}, pathElement fieldpath.PathElement, index int) (bool, error) { From d7aac60ee0aede00b325cb2bdf4a9cc462e41d19 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 28 Nov 2022 15:59:02 +0100 Subject: [PATCH 1233/2916] tests: Move test-utils package below e2e --- e2e/default_clusters.go | 2 +- e2e/flux_test.go | 2 +- e2e/hooks_test.go | 2 +- e2e/inclusion_test.go | 2 +- e2e/project.go | 2 +- e2e/seal_test.go | 2 +- {internal => e2e}/test-utils/envtest_cluster.go | 0 {internal => e2e}/test-utils/envtest_cluster_callback.go | 0 {internal => e2e}/test-utils/git_server.go | 0 e2e/test_resources/resources.go | 2 +- e2e/utils.go | 2 +- pkg/vars/vars_loader_test.go | 2 +- 12 files changed, 9 insertions(+), 9 deletions(-) rename {internal => e2e}/test-utils/envtest_cluster.go (100%) rename {internal => e2e}/test-utils/envtest_cluster_callback.go (100%) rename {internal => e2e}/test-utils/git_server.go (100%) diff --git a/e2e/default_clusters.go b/e2e/default_clusters.go index 3ef14c5c8..3baff99ef 100644 --- a/e2e/default_clusters.go +++ b/e2e/default_clusters.go @@ -1,8 +1,8 @@ package e2e import ( + "github.com/kluctl/kluctl/v2/e2e/test-utils" "github.com/kluctl/kluctl/v2/e2e/test_resources" - test_utils "github.com/kluctl/kluctl/v2/internal/test-utils" "k8s.io/apimachinery/pkg/runtime/schema" "sync" ) diff --git a/e2e/flux_test.go b/e2e/flux_test.go index b796e792d..5cd90eec7 100644 --- a/e2e/flux_test.go +++ b/e2e/flux_test.go @@ -3,6 +3,7 @@ package e2e import ( "context" "fmt" + "github.com/kluctl/kluctl/v2/e2e/test-utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" @@ -10,7 +11,6 @@ import ( "testing" "github.com/kluctl/kluctl/v2/e2e/test_resources" - test_utils "github.com/kluctl/kluctl/v2/internal/test-utils" "github.com/stretchr/testify/assert" ) diff --git a/e2e/hooks_test.go b/e2e/hooks_test.go index 863ba6af3..663c8fb22 100644 --- a/e2e/hooks_test.go +++ b/e2e/hooks_test.go @@ -3,6 +3,7 @@ package e2e import ( "context" "fmt" + "github.com/kluctl/kluctl/v2/e2e/test-utils" "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -10,7 +11,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" "testing" - test_utils "github.com/kluctl/kluctl/v2/internal/test-utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" ) diff --git a/e2e/inclusion_test.go b/e2e/inclusion_test.go index a50564854..84e61ad8e 100644 --- a/e2e/inclusion_test.go +++ b/e2e/inclusion_test.go @@ -1,7 +1,7 @@ package e2e import ( - "github.com/kluctl/kluctl/v2/internal/test-utils" + "github.com/kluctl/kluctl/v2/e2e/test-utils" corev1 "k8s.io/api/core/v1" "path/filepath" "reflect" diff --git a/e2e/project.go b/e2e/project.go index bcf57d051..1443b4901 100644 --- a/e2e/project.go +++ b/e2e/project.go @@ -5,7 +5,7 @@ import ( "github.com/go-git/go-git/v5" "github.com/huandu/xstrings" "github.com/imdario/mergo" - test_utils "github.com/kluctl/kluctl/v2/internal/test-utils" + "github.com/kluctl/kluctl/v2/e2e/test-utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" "k8s.io/client-go/tools/clientcmd" diff --git a/e2e/seal_test.go b/e2e/seal_test.go index c5b9268bf..c172f060b 100644 --- a/e2e/seal_test.go +++ b/e2e/seal_test.go @@ -6,8 +6,8 @@ import ( "encoding/pem" "fmt" "github.com/bitnami-labs/sealed-secrets/pkg/crypto" + "github.com/kluctl/kluctl/v2/e2e/test-utils" "github.com/kluctl/kluctl/v2/e2e/test_resources" - "github.com/kluctl/kluctl/v2/internal/test-utils" "github.com/kluctl/kluctl/v2/pkg/seal" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" diff --git a/internal/test-utils/envtest_cluster.go b/e2e/test-utils/envtest_cluster.go similarity index 100% rename from internal/test-utils/envtest_cluster.go rename to e2e/test-utils/envtest_cluster.go diff --git a/internal/test-utils/envtest_cluster_callback.go b/e2e/test-utils/envtest_cluster_callback.go similarity index 100% rename from internal/test-utils/envtest_cluster_callback.go rename to e2e/test-utils/envtest_cluster_callback.go diff --git a/internal/test-utils/git_server.go b/e2e/test-utils/git_server.go similarity index 100% rename from internal/test-utils/git_server.go rename to e2e/test-utils/git_server.go diff --git a/e2e/test_resources/resources.go b/e2e/test_resources/resources.go index f374d2ee0..fa1754bff 100644 --- a/e2e/test_resources/resources.go +++ b/e2e/test_resources/resources.go @@ -3,7 +3,7 @@ package test_resources import ( "context" "embed" - test_utils "github.com/kluctl/kluctl/v2/internal/test-utils" + "github.com/kluctl/kluctl/v2/e2e/test-utils" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/validation" diff --git a/e2e/utils.go b/e2e/utils.go index f8a20f9ad..bd8eb8e63 100644 --- a/e2e/utils.go +++ b/e2e/utils.go @@ -4,7 +4,7 @@ import ( "bufio" "bytes" "context" - test_utils "github.com/kluctl/kluctl/v2/internal/test-utils" + "github.com/kluctl/kluctl/v2/e2e/test-utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "io" v1 "k8s.io/api/core/v1" diff --git a/pkg/vars/vars_loader_test.go b/pkg/vars/vars_loader_test.go index 95fff9e9a..9b46900c8 100644 --- a/pkg/vars/vars_loader_test.go +++ b/pkg/vars/vars_loader_test.go @@ -4,7 +4,7 @@ import ( "context" git2 "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" - test_utils "github.com/kluctl/kluctl/v2/internal/test-utils" + "github.com/kluctl/kluctl/v2/e2e/test-utils" "github.com/kluctl/kluctl/v2/pkg/git/auth" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/git/repocache" From 95f6242dceb3360aaf9edac964097267702082bb Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 28 Nov 2022 16:25:55 +0100 Subject: [PATCH 1234/2916] tests: Move Helm Repo utils into test-utils --- e2e/helm_test.go | 194 +++--------------------------- e2e/test-utils/helm_repo_utils.go | 163 +++++++++++++++++++++++++ 2 files changed, 183 insertions(+), 174 deletions(-) create mode 100644 e2e/test-utils/helm_repo_utils.go diff --git a/e2e/helm_test.go b/e2e/helm_test.go index 780ea9c2b..e5d5b9952 100644 --- a/e2e/helm_test.go +++ b/e2e/helm_test.go @@ -3,174 +3,20 @@ package e2e import ( "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing/object" - "github.com/google/go-containerregistry/pkg/registry" - test_resources "github.com/kluctl/kluctl/v2/e2e/test-helm-chart" - "github.com/kluctl/kluctl/v2/pkg/utils" + test_utils "github.com/kluctl/kluctl/v2/e2e/test-utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" - "github.com/kluctl/kluctl/v2/pkg/yaml" "github.com/stretchr/testify/assert" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/cli/values" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/pusher" registry2 "helm.sh/helm/v3/pkg/registry" - "helm.sh/helm/v3/pkg/repo" - "helm.sh/helm/v3/pkg/uploader" - "net/http" - "net/http/httptest" - "net/url" - "os" "path/filepath" "sort" - "strings" "testing" ) -func createHelmPackage(t *testing.T, name string, version string) string { - tmpDir := t.TempDir() - err := utils.FsCopyDir(test_resources.HelmChartFS, ".", tmpDir) - if err != nil { - t.Fatal(err) - } - - c, err := uo.FromFile(filepath.Join(tmpDir, "Chart.yaml")) - if err != nil { - t.Fatal(err) - } - - _ = c.SetNestedField(name, "name") - _ = c.SetNestedField(version, "version") - - err = yaml.WriteYamlFile(filepath.Join(tmpDir, "Chart.yaml"), c) - if err != nil { - t.Fatal(err) - } - - settings := cli.New() - client := action.NewPackage() - client.Destination = tmpDir - valueOpts := &values.Options{} - p := getter.All(settings) - vals, err := valueOpts.MergeValues(p) - retName, err := client.Run(tmpDir, vals) - if err != nil { - t.Fatal(err) - } - - return retName -} - -type repoChart struct { - chartName string - version string -} - -func createHelmRepo(t *testing.T, charts []repoChart, password string) string { - tmpDir := t.TempDir() - - for _, c := range charts { - tgz := createHelmPackage(t, c.chartName, c.version) - _ = utils.CopyFile(tgz, filepath.Join(tmpDir, filepath.Base(tgz))) - } - - fs := http.FileServer(http.FS(os.DirFS(tmpDir))) - s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if password != "" { - _, p, ok := r.BasicAuth() - if !ok || p != password { - http.Error(w, "Auth header was incorrect", http.StatusUnauthorized) - return - } - } - fs.ServeHTTP(w, r) - })) - - t.Cleanup(s.Close) - - i, err := repo.IndexDirectory(tmpDir, s.URL) - if err != nil { - t.Fatal(err) - } - - i.SortEntries() - err = i.WriteFile(filepath.Join(tmpDir, "index.yaml"), 0644) - if err != nil { - t.Fatal(err) - } - - return s.URL -} - -func createOciRepo(t *testing.T, charts []repoChart, password string) string { - tmpDir := t.TempDir() - - ociRegistry := registry.New() - s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if password != "" { - _, p, ok := r.BasicAuth() - if !ok { - w.Header().Add("WWW-Authenticate", "Basic") - http.Error(w, "Auth header was incorrect", http.StatusUnauthorized) - return - } - if !ok || p != password { - http.Error(w, "Auth header was incorrect", http.StatusUnauthorized) - return - } - } - ociRegistry.ServeHTTP(w, r) - })) - - t.Cleanup(s.Close) - - ociUrl := strings.ReplaceAll(s.URL, "http://", "oci://") - ociUrl2, _ := url.Parse(ociUrl) - - var out strings.Builder - settings := cli.New() - c := uploader.ChartUploader{ - Out: &out, - Pushers: pusher.All(settings), - Options: []pusher.Option{}, - } - - var registryClient *registry2.Client - if password != "" { - var err error - registryClient, err = registry2.NewClient() - if err != nil { - t.Fatal(err) - } - err = registryClient.Login(ociUrl2.Host, registry2.LoginOptBasicAuth("test-user", password), registry2.LoginOptInsecure(true)) - if err != nil { - t.Fatal(err) - } - c.Options = append(c.Options, pusher.WithRegistryClient(registryClient)) - } - - for _, chart := range charts { - tgz := createHelmPackage(t, chart.chartName, chart.version) - _ = utils.CopyFile(tgz, filepath.Join(tmpDir, filepath.Base(tgz))) - - err := c.UploadTo(tgz, ociUrl) - if err != nil { - t.Fatal(err) - } - } - - if registryClient != nil { - registryClient.Logout(ociUrl2.Host) - } - - return ociUrl -} - -func createHelmOrOciRepo(t *testing.T, charts []repoChart, oci bool, password string) string { +func createHelmOrOciRepo(t *testing.T, charts []test_utils.RepoChart, oci bool, password string) string { if oci { - return createOciRepo(t, charts, password) + return test_utils.CreateOciRepo(t, charts, password) } else { - return createHelmRepo(t, charts, password) + return test_utils.CreateHelmRepo(t, charts, password) } } @@ -231,8 +77,8 @@ func testHelmPull(t *testing.T, tc testCase, prePull bool) { password = "secret-password" } - repoUrl := createHelmOrOciRepo(p.t, []repoChart{ - {chartName: "test-chart1", version: "0.1.0"}, + repoUrl := createHelmOrOciRepo(p.t, []test_utils.RepoChart{ + {ChartName: "test-chart1", Version: "0.1.0"}, }, tc.oci, password) p.updateTarget("test", nil) @@ -316,9 +162,9 @@ func testHelmManualUpgrade(t *testing.T, oci bool) { createNamespace(t, k, p.testSlug()) - repoUrl := createHelmOrOciRepo(p.t, []repoChart{ - {chartName: "test-chart1", version: "0.1.0"}, - {chartName: "test-chart1", version: "0.2.0"}, + repoUrl := createHelmOrOciRepo(p.t, []test_utils.RepoChart{ + {ChartName: "test-chart1", Version: "0.1.0"}, + {ChartName: "test-chart1", Version: "0.2.0"}, }, oci, "") p.updateTarget("test", nil) @@ -361,11 +207,11 @@ func testHelmUpdate(t *testing.T, oci bool, upgrade bool, commit bool) { createNamespace(t, k, p.testSlug()) - repoUrl := createHelmOrOciRepo(p.t, []repoChart{ - {chartName: "test-chart1", version: "0.1.0"}, - {chartName: "test-chart1", version: "0.2.0"}, - {chartName: "test-chart2", version: "0.1.0"}, - {chartName: "test-chart2", version: "0.3.0"}, + repoUrl := createHelmOrOciRepo(p.t, []test_utils.RepoChart{ + {ChartName: "test-chart1", Version: "0.1.0"}, + {ChartName: "test-chart1", Version: "0.2.0"}, + {ChartName: "test-chart2", Version: "0.1.0"}, + {ChartName: "test-chart2", Version: "0.3.0"}, }, oci, "") p.updateTarget("test", nil) @@ -472,9 +318,9 @@ func TestHelmValues(t *testing.T) { createNamespace(t, k, p.testSlug()) - repoUrl := createHelmRepo(p.t, []repoChart{ - {chartName: "test-chart1", version: "0.1.0"}, - {chartName: "test-chart2", version: "0.1.0"}, + repoUrl := test_utils.CreateHelmRepo(p.t, []test_utils.RepoChart{ + {ChartName: "test-chart1", Version: "0.1.0"}, + {ChartName: "test-chart2", Version: "0.1.0"}, }, "") values1 := map[string]any{ @@ -537,9 +383,9 @@ func TestHelmTemplateChartYaml(t *testing.T) { createNamespace(t, k, p.testSlug()+"-a") createNamespace(t, k, p.testSlug()+"-b") - repoUrl := createHelmRepo(p.t, []repoChart{ - {chartName: "test-chart1", version: "0.1.0"}, - {chartName: "test-chart2", version: "0.1.0"}, + repoUrl := test_utils.CreateHelmRepo(p.t, []test_utils.RepoChart{ + {ChartName: "test-chart1", Version: "0.1.0"}, + {ChartName: "test-chart2", Version: "0.1.0"}, }, "") p.updateTarget("test", nil) diff --git a/e2e/test-utils/helm_repo_utils.go b/e2e/test-utils/helm_repo_utils.go new file mode 100644 index 000000000..afe54a276 --- /dev/null +++ b/e2e/test-utils/helm_repo_utils.go @@ -0,0 +1,163 @@ +package test_utils + +import ( + "github.com/google/go-containerregistry/pkg/registry" + test_resources "github.com/kluctl/kluctl/v2/e2e/test-helm-chart" + "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/yaml" + "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/cli" + "helm.sh/helm/v3/pkg/cli/values" + "helm.sh/helm/v3/pkg/getter" + "helm.sh/helm/v3/pkg/pusher" + registry2 "helm.sh/helm/v3/pkg/registry" + "helm.sh/helm/v3/pkg/repo" + "helm.sh/helm/v3/pkg/uploader" + "net/http" + "net/http/httptest" + "net/url" + "os" + "path/filepath" + "strings" + "testing" +) + +func createHelmPackage(t *testing.T, name string, version string) string { + tmpDir := t.TempDir() + err := utils.FsCopyDir(test_resources.HelmChartFS, ".", tmpDir) + if err != nil { + t.Fatal(err) + } + + c, err := uo.FromFile(filepath.Join(tmpDir, "Chart.yaml")) + if err != nil { + t.Fatal(err) + } + + _ = c.SetNestedField(name, "name") + _ = c.SetNestedField(version, "version") + + err = yaml.WriteYamlFile(filepath.Join(tmpDir, "Chart.yaml"), c) + if err != nil { + t.Fatal(err) + } + + settings := cli.New() + client := action.NewPackage() + client.Destination = tmpDir + valueOpts := &values.Options{} + p := getter.All(settings) + vals, err := valueOpts.MergeValues(p) + retName, err := client.Run(tmpDir, vals) + if err != nil { + t.Fatal(err) + } + + return retName +} + +type RepoChart struct { + ChartName string + Version string +} + +func CreateHelmRepo(t *testing.T, charts []RepoChart, password string) string { + tmpDir := t.TempDir() + + for _, c := range charts { + tgz := createHelmPackage(t, c.ChartName, c.Version) + _ = utils.CopyFile(tgz, filepath.Join(tmpDir, filepath.Base(tgz))) + } + + fs := http.FileServer(http.FS(os.DirFS(tmpDir))) + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if password != "" { + _, p, ok := r.BasicAuth() + if !ok || p != password { + http.Error(w, "Auth header was incorrect", http.StatusUnauthorized) + return + } + } + fs.ServeHTTP(w, r) + })) + + t.Cleanup(s.Close) + + i, err := repo.IndexDirectory(tmpDir, s.URL) + if err != nil { + t.Fatal(err) + } + + i.SortEntries() + err = i.WriteFile(filepath.Join(tmpDir, "index.yaml"), 0644) + if err != nil { + t.Fatal(err) + } + + return s.URL +} + +func CreateOciRepo(t *testing.T, charts []RepoChart, password string) string { + tmpDir := t.TempDir() + + ociRegistry := registry.New() + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if password != "" { + _, p, ok := r.BasicAuth() + if !ok { + w.Header().Add("WWW-Authenticate", "Basic") + http.Error(w, "Auth header was incorrect", http.StatusUnauthorized) + return + } + if !ok || p != password { + http.Error(w, "Auth header was incorrect", http.StatusUnauthorized) + return + } + } + ociRegistry.ServeHTTP(w, r) + })) + + t.Cleanup(s.Close) + + ociUrl := strings.ReplaceAll(s.URL, "http://", "oci://") + ociUrl2, _ := url.Parse(ociUrl) + + var out strings.Builder + settings := cli.New() + c := uploader.ChartUploader{ + Out: &out, + Pushers: pusher.All(settings), + Options: []pusher.Option{}, + } + + var registryClient *registry2.Client + if password != "" { + var err error + registryClient, err = registry2.NewClient() + if err != nil { + t.Fatal(err) + } + err = registryClient.Login(ociUrl2.Host, registry2.LoginOptBasicAuth("test-user", password), registry2.LoginOptInsecure(true)) + if err != nil { + t.Fatal(err) + } + c.Options = append(c.Options, pusher.WithRegistryClient(registryClient)) + } + + for _, chart := range charts { + tgz := createHelmPackage(t, chart.ChartName, chart.Version) + _ = utils.CopyFile(tgz, filepath.Join(tmpDir, filepath.Base(tgz))) + + err := c.UploadTo(tgz, ociUrl) + if err != nil { + t.Fatal(err) + } + } + + if registryClient != nil { + registryClient.Logout(ociUrl2.Host) + } + + return ociUrl +} From 8e531507d95cfd7ca922c6057b4a7ed65818a0c3 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 28 Nov 2022 16:33:48 +0100 Subject: [PATCH 1235/2916] tests: Also pass username to test Helm repos --- e2e/helm_test.go | 24 +++++++++++++----------- e2e/test-utils/helm_repo_utils.go | 16 ++++++++-------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/e2e/helm_test.go b/e2e/helm_test.go index e5d5b9952..8b9adf1c5 100644 --- a/e2e/helm_test.go +++ b/e2e/helm_test.go @@ -12,11 +12,11 @@ import ( "testing" ) -func createHelmOrOciRepo(t *testing.T, charts []test_utils.RepoChart, oci bool, password string) string { +func createHelmOrOciRepo(t *testing.T, charts []test_utils.RepoChart, oci bool, user string, password string) string { if oci { - return test_utils.CreateOciRepo(t, charts, password) + return test_utils.CreateOciRepo(t, charts, user, password) } else { - return test_utils.CreateHelmRepo(t, charts, password) + return test_utils.CreateHelmRepo(t, charts, user, password) } } @@ -72,14 +72,16 @@ func testHelmPull(t *testing.T, tc testCase, prePull bool) { createNamespace(t, k, p.testSlug()) + user := "" password := "" if tc.testAuth { + user = "test-user" password = "secret-password" } repoUrl := createHelmOrOciRepo(p.t, []test_utils.RepoChart{ {ChartName: "test-chart1", Version: "0.1.0"}, - }, tc.oci, password) + }, tc.oci, user, password) p.updateTarget("test", nil) addHelmDeployment(p, "helm1", repoUrl, "test-chart1", "0.1.0", "test-helm1", p.testSlug(), nil) @@ -132,13 +134,13 @@ func TestHelmPull(t *testing.T) { {name: "helm-creds-missing", oci: false, testAuth: true, credsId: "test-creds", expectedError: "no credentials provided for Chart test-chart1"}, {name: "helm-creds-invalid", oci: false, testAuth: true, credsId: "test-creds", - extraArgs: []string{"--helm-username=test-creds:user", "--helm-password=test-creds:invalid"}, + extraArgs: []string{"--helm-username=test-creds:test-user", "--helm-password=test-creds:invalid"}, expectedError: "401 Unauthorized"}, {name: "helm-creds-valid", oci: false, testAuth: true, credsId: "test-creds", - extraArgs: []string{"--helm-username=test-creds:user", "--helm-password=test-creds:secret-password"}}, + extraArgs: []string{"--helm-username=test-creds:test-user", "--helm-password=test-creds:secret-password"}}, {name: "oci", oci: true}, {name: "oci-creds-fail", oci: true, testAuth: true, credsId: "test-creds", - extraArgs: []string{"--helm-username=test-creds:user", "--helm-password=test-creds:secret-password"}, + extraArgs: []string{"--helm-username=test-creds:test-user", "--helm-password=test-creds:secret-password"}, expectedError: "OCI charts can currently only be authenticated via registry login and not via cli arguments"}, } @@ -165,7 +167,7 @@ func testHelmManualUpgrade(t *testing.T, oci bool) { repoUrl := createHelmOrOciRepo(p.t, []test_utils.RepoChart{ {ChartName: "test-chart1", Version: "0.1.0"}, {ChartName: "test-chart1", Version: "0.2.0"}, - }, oci, "") + }, oci, "", "") p.updateTarget("test", nil) addHelmDeployment(p, "helm1", repoUrl, "test-chart1", "0.1.0", "test-helm1", p.testSlug(), nil) @@ -212,7 +214,7 @@ func testHelmUpdate(t *testing.T, oci bool, upgrade bool, commit bool) { {ChartName: "test-chart1", Version: "0.2.0"}, {ChartName: "test-chart2", Version: "0.1.0"}, {ChartName: "test-chart2", Version: "0.3.0"}, - }, oci, "") + }, oci, "", "") p.updateTarget("test", nil) addHelmDeployment(p, "helm1", repoUrl, "test-chart1", "0.1.0", "test-helm1", p.testSlug(), nil) @@ -321,7 +323,7 @@ func TestHelmValues(t *testing.T) { repoUrl := test_utils.CreateHelmRepo(p.t, []test_utils.RepoChart{ {ChartName: "test-chart1", Version: "0.1.0"}, {ChartName: "test-chart2", Version: "0.1.0"}, - }, "") + }, "", "") values1 := map[string]any{ "data": map[string]any{ @@ -386,7 +388,7 @@ func TestHelmTemplateChartYaml(t *testing.T) { repoUrl := test_utils.CreateHelmRepo(p.t, []test_utils.RepoChart{ {ChartName: "test-chart1", Version: "0.1.0"}, {ChartName: "test-chart2", Version: "0.1.0"}, - }, "") + }, "", "") p.updateTarget("test", nil) addHelmDeployment(p, "helm1", repoUrl, "test-chart1", "0.1.0", "test-helm-{{ args.a }}", p.testSlug(), nil) diff --git a/e2e/test-utils/helm_repo_utils.go b/e2e/test-utils/helm_repo_utils.go index afe54a276..631b2a677 100644 --- a/e2e/test-utils/helm_repo_utils.go +++ b/e2e/test-utils/helm_repo_utils.go @@ -62,7 +62,7 @@ type RepoChart struct { Version string } -func CreateHelmRepo(t *testing.T, charts []RepoChart, password string) string { +func CreateHelmRepo(t *testing.T, charts []RepoChart, username string, password string) string { tmpDir := t.TempDir() for _, c := range charts { @@ -73,8 +73,8 @@ func CreateHelmRepo(t *testing.T, charts []RepoChart, password string) string { fs := http.FileServer(http.FS(os.DirFS(tmpDir))) s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if password != "" { - _, p, ok := r.BasicAuth() - if !ok || p != password { + u, p, ok := r.BasicAuth() + if !ok || u != username || p != password { http.Error(w, "Auth header was incorrect", http.StatusUnauthorized) return } @@ -98,19 +98,19 @@ func CreateHelmRepo(t *testing.T, charts []RepoChart, password string) string { return s.URL } -func CreateOciRepo(t *testing.T, charts []RepoChart, password string) string { +func CreateOciRepo(t *testing.T, charts []RepoChart, username string, password string) string { tmpDir := t.TempDir() ociRegistry := registry.New() s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if password != "" { - _, p, ok := r.BasicAuth() + if username != "" || password != "" { + u, p, ok := r.BasicAuth() if !ok { w.Header().Add("WWW-Authenticate", "Basic") http.Error(w, "Auth header was incorrect", http.StatusUnauthorized) return } - if !ok || p != password { + if u != username || p != password { http.Error(w, "Auth header was incorrect", http.StatusUnauthorized) return } @@ -138,7 +138,7 @@ func CreateOciRepo(t *testing.T, charts []RepoChart, password string) string { if err != nil { t.Fatal(err) } - err = registryClient.Login(ociUrl2.Host, registry2.LoginOptBasicAuth("test-user", password), registry2.LoginOptInsecure(true)) + err = registryClient.Login(ociUrl2.Host, registry2.LoginOptBasicAuth(username, password), registry2.LoginOptInsecure(true)) if err != nil { t.Fatal(err) } From 203ceed998e0d3a62fb235130d13790e31c450b8 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 28 Nov 2022 16:48:16 +0100 Subject: [PATCH 1236/2916] tests: Get rid of redundant name in hooks tests --- e2e/hooks_test.go | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/e2e/hooks_test.go b/e2e/hooks_test.go index 663c8fb22..c94827e4d 100644 --- a/e2e/hooks_test.go +++ b/e2e/hooks_test.go @@ -92,7 +92,7 @@ func (s *hooksTestContext) addConfigMap(dir string, opts resourceOpts) { }) } -func prepareHookTestProject(t *testing.T, name string, hook string, hookDeletionPolicy string) *hooksTestContext { +func prepareHookTestProject(t *testing.T, hook string, hookDeletionPolicy string) *hooksTestContext { s := &hooksTestContext{ t: t, k: defaultCluster2, // use cluster2 as it has webhooks setup @@ -133,21 +133,21 @@ func (s *hooksTestContext) ensureHookNotExecuted() { func TestHooksPreDeployInitial(t *testing.T) { t.Parallel() - s := prepareHookTestProject(t, "pre-deploy-initial", "pre-deploy-initial", "") + s := prepareHookTestProject(t, "pre-deploy-initial", "") s.ensureHookExecuted("hook1", "cm1") s.ensureHookExecuted("cm1") } func TestHooksPostDeployInitial(t *testing.T) { t.Parallel() - s := prepareHookTestProject(t, "post-deploy-initial", "post-deploy-initial", "") + s := prepareHookTestProject(t, "post-deploy-initial", "") s.ensureHookExecuted("cm1", "hook1") s.ensureHookExecuted("cm1") } func TestHooksPreDeployUpgrade(t *testing.T) { t.Parallel() - s := prepareHookTestProject(t, "pre-deploy-upgrade", "pre-deploy-upgrade", "") + s := prepareHookTestProject(t, "pre-deploy-upgrade", "") s.ensureHookExecuted("cm1") s.ensureHookExecuted("hook1", "cm1") s.ensureHookExecuted("hook1", "cm1") @@ -155,58 +155,58 @@ func TestHooksPreDeployUpgrade(t *testing.T) { func TestHooksPostDeployUpgrade(t *testing.T) { t.Parallel() - s := prepareHookTestProject(t, "post-deploy-upgrade", "post-deploy-upgrade", "") + s := prepareHookTestProject(t, "post-deploy-upgrade", "") s.ensureHookExecuted("cm1") s.ensureHookExecuted("cm1", "hook1") s.ensureHookExecuted("cm1", "hook1") } -func doTestHooksPreDeploy(t *testing.T, name string, hooks string) { - s := prepareHookTestProject(t, name, hooks, "") +func doTestHooksPreDeploy(t *testing.T, hooks string) { + s := prepareHookTestProject(t, hooks, "") s.ensureHookExecuted("hook1", "cm1") s.ensureHookExecuted("hook1", "cm1") } -func doTestHooksPostDeploy(t *testing.T, name string, hooks string) { - s := prepareHookTestProject(t, name, hooks, "") +func doTestHooksPostDeploy(t *testing.T, hooks string) { + s := prepareHookTestProject(t, hooks, "") s.ensureHookExecuted("cm1", "hook1") s.ensureHookExecuted("cm1", "hook1") } -func doTestHooksPrePostDeploy(t *testing.T, name string, hooks string) { - s := prepareHookTestProject(t, name, hooks, "") +func doTestHooksPrePostDeploy(t *testing.T, hooks string) { + s := prepareHookTestProject(t, hooks, "") s.ensureHookExecuted("hook1", "cm1", "hook1") s.ensureHookExecuted("hook1", "cm1", "hook1") } func TestHooksPreDeploy(t *testing.T) { t.Parallel() - doTestHooksPreDeploy(t, "pre-deploy", "pre-deploy") + doTestHooksPreDeploy(t, "pre-deploy") } func TestHooksPreDeploy2(t *testing.T) { t.Parallel() // same as pre-deploy - doTestHooksPreDeploy(t, "pre-deploy2", "pre-deploy-initial,pre-deploy-upgrade") + doTestHooksPreDeploy(t, "pre-deploy-initial,pre-deploy-upgrade") } func TestHooksPostDeploy(t *testing.T) { t.Parallel() - doTestHooksPostDeploy(t, "post-deploy", "post-deploy") + doTestHooksPostDeploy(t, "post-deploy") } func TestHooksPostDeploy2(t *testing.T) { t.Parallel() // same as post-deploy - doTestHooksPostDeploy(t, "post-deploy2", "post-deploy-initial,post-deploy-upgrade") + doTestHooksPostDeploy(t, "post-deploy-initial,post-deploy-upgrade") } func TestHooksPrePostDeploy(t *testing.T) { t.Parallel() - doTestHooksPrePostDeploy(t, "pre-post-deploy", "pre-deploy,post-deploy") + doTestHooksPrePostDeploy(t, "pre-deploy,post-deploy") } func TestHooksPrePostDeploy2(t *testing.T) { t.Parallel() - doTestHooksPrePostDeploy(t, "pre-post-deploy2", "pre-deploy-initial,pre-deploy-upgrade,post-deploy-initial,post-deploy-upgrade") + doTestHooksPrePostDeploy(t, "pre-deploy-initial,pre-deploy-upgrade,post-deploy-initial,post-deploy-upgrade") } From 30e646f1020a66c43dd613975825852330c7d1b3 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 28 Nov 2022 16:50:44 +0100 Subject: [PATCH 1237/2916] tests: Implement and use NewTestProject --- e2e/args_test.go | 9 ++---- e2e/contexts_test.go | 5 ++- e2e/deployment_items_test.go | 6 ++-- e2e/flux_test.go | 3 +- e2e/helm_test.go | 17 ++++------ e2e/hooks_test.go | 5 ++- e2e/inclusion_test.go | 7 ++-- e2e/no_target_test.go | 5 ++- e2e/project.go | 62 ++++++++++++++++++++---------------- e2e/seal_test.go | 11 +++---- e2e/sops_test.go | 6 ++-- e2e/utils_resources.go | 4 +-- 12 files changed, 64 insertions(+), 76 deletions(-) diff --git a/e2e/args_test.go b/e2e/args_test.go index 05e38fe97..17e772568 100644 --- a/e2e/args_test.go +++ b/e2e/args_test.go @@ -12,8 +12,7 @@ func testArgs(t *testing.T, deprecated bool) { k := defaultCluster1 - p := &testProject{} - p.init(t, k) + p := NewTestProject(t, k) createNamespace(t, k, p.testSlug()) @@ -136,8 +135,7 @@ func TestArgsFromEnv(t *testing.T) { k := defaultCluster1 - p := &testProject{} - p.init(t, k) + p := NewTestProject(t, k) createNamespace(t, k, p.testSlug()) @@ -170,8 +168,7 @@ func TestArgsFromEnvAndCli(t *testing.T) { k := defaultCluster1 - p := &testProject{} - p.init(t, k) + p := NewTestProject(t, k) createNamespace(t, k, p.testSlug()) diff --git a/e2e/contexts_test.go b/e2e/contexts_test.go index a6e2a0898..55296bac2 100644 --- a/e2e/contexts_test.go +++ b/e2e/contexts_test.go @@ -6,9 +6,8 @@ import ( "testing" ) -func prepareContextTest(t *testing.T) *testProject { - p := &testProject{} - p.init(t, defaultCluster1) +func prepareContextTest(t *testing.T) *TestProject { + p := NewTestProject(t, defaultCluster1) p.mergeKubeconfig(defaultCluster2) createNamespace(t, defaultCluster1, p.testSlug()) diff --git a/e2e/deployment_items_test.go b/e2e/deployment_items_test.go index a9fa3c5b6..23d4c0bad 100644 --- a/e2e/deployment_items_test.go +++ b/e2e/deployment_items_test.go @@ -10,8 +10,7 @@ func TestKustomize(t *testing.T) { k := defaultCluster1 - p := &testProject{} - p.init(t, k) + p := NewTestProject(t, k) createNamespace(t, k, p.testSlug()) @@ -39,8 +38,7 @@ func TestGeneratedKustomize(t *testing.T) { k := defaultCluster1 - p := &testProject{} - p.init(t, k) + p := NewTestProject(t, k) createNamespace(t, k, p.testSlug()) diff --git a/e2e/flux_test.go b/e2e/flux_test.go index 5cd90eec7..b9f49d496 100644 --- a/e2e/flux_test.go +++ b/e2e/flux_test.go @@ -36,8 +36,7 @@ func TestFluxCommands(t *testing.T) { k := defaultCluster1 - p := &testProject{} - p.init(t, k) + p := NewTestProject(t, k) var wg sync.WaitGroup wg.Add(2) diff --git a/e2e/helm_test.go b/e2e/helm_test.go index 8b9adf1c5..cb4a588f5 100644 --- a/e2e/helm_test.go +++ b/e2e/helm_test.go @@ -20,7 +20,7 @@ func createHelmOrOciRepo(t *testing.T, charts []test_utils.RepoChart, oci bool, } } -func addHelmDeployment(p *testProject, dir string, repoUrl string, chartName, version string, releaseName string, namespace string, values map[string]any) { +func addHelmDeployment(p *TestProject, dir string, repoUrl string, chartName, version string, releaseName string, namespace string, values map[string]any) { if registry2.IsOCI(repoUrl) { repoUrl += "/" + chartName chartName = "" @@ -67,8 +67,7 @@ func testHelmPull(t *testing.T, tc testCase, prePull bool) { k := defaultCluster1 - p := &testProject{} - p.init(t, k) + p := NewTestProject(t, k) createNamespace(t, k, p.testSlug()) @@ -159,8 +158,7 @@ func testHelmManualUpgrade(t *testing.T, oci bool) { k := defaultCluster1 - p := &testProject{} - p.init(t, k) + p := NewTestProject(t, k) createNamespace(t, k, p.testSlug()) @@ -204,8 +202,7 @@ func testHelmUpdate(t *testing.T, oci bool, upgrade bool, commit bool) { k := defaultCluster1 - p := &testProject{} - p.init(t, k) + p := NewTestProject(t, k) createNamespace(t, k, p.testSlug()) @@ -315,8 +312,7 @@ func TestHelmValues(t *testing.T) { k := defaultCluster1 - p := &testProject{} - p.init(t, k) + p := NewTestProject(t, k) createNamespace(t, k, p.testSlug()) @@ -378,8 +374,7 @@ func TestHelmTemplateChartYaml(t *testing.T) { k := defaultCluster1 - p := &testProject{} - p.init(t, k) + p := NewTestProject(t, k) createNamespace(t, k, p.testSlug()) createNamespace(t, k, p.testSlug()+"-a") diff --git a/e2e/hooks_test.go b/e2e/hooks_test.go index c94827e4d..54df6986e 100644 --- a/e2e/hooks_test.go +++ b/e2e/hooks_test.go @@ -18,7 +18,7 @@ type hooksTestContext struct { t *testing.T k *test_utils.EnvTestCluster - p *testProject + p *TestProject seenConfigMaps []string @@ -96,11 +96,10 @@ func prepareHookTestProject(t *testing.T, hook string, hookDeletionPolicy string s := &hooksTestContext{ t: t, k: defaultCluster2, // use cluster2 as it has webhooks setup - p: &testProject{}, } s.setupWebhook() - s.p.init(t, s.k) + s.p = NewTestProject(t, s.k) t.Cleanup(func() { s.removeWebhook() }) diff --git a/e2e/inclusion_test.go b/e2e/inclusion_test.go index 84e61ad8e..1a212cc4d 100644 --- a/e2e/inclusion_test.go +++ b/e2e/inclusion_test.go @@ -8,10 +8,9 @@ import ( "testing" ) -func prepareInclusionTestProject(t *testing.T, withIncludes bool) (*testProject, *test_utils.EnvTestCluster) { +func prepareInclusionTestProject(t *testing.T, withIncludes bool) (*TestProject, *test_utils.EnvTestCluster) { k := defaultCluster1 - p := &testProject{} - p.init(t, k) + p := NewTestProject(t, k) createNamespace(t, k, p.testSlug()) @@ -41,7 +40,7 @@ func prepareInclusionTestProject(t *testing.T, withIncludes bool) (*testProject, return p, k } -func assertExistsHelper(t *testing.T, p *testProject, k *test_utils.EnvTestCluster, shouldExists map[string]bool, add []string, remove []string) { +func assertExistsHelper(t *testing.T, p *TestProject, k *test_utils.EnvTestCluster, shouldExists map[string]bool, add []string, remove []string) { for _, x := range add { shouldExists[x] = true } diff --git a/e2e/no_target_test.go b/e2e/no_target_test.go index 01cdfe738..433df873d 100644 --- a/e2e/no_target_test.go +++ b/e2e/no_target_test.go @@ -7,9 +7,8 @@ import ( "testing" ) -func prepareNoTargetTest(t *testing.T, withDeploymentYaml bool) *testProject { - p := &testProject{} - p.init(t, defaultCluster1) +func prepareNoTargetTest(t *testing.T, withDeploymentYaml bool) *TestProject { + p := NewTestProject(t, defaultCluster1) p.mergeKubeconfig(defaultCluster2) createNamespace(t, defaultCluster1, p.testSlug()) diff --git a/e2e/project.go b/e2e/project.go index 1443b4901..afbb0fc0c 100644 --- a/e2e/project.go +++ b/e2e/project.go @@ -18,7 +18,7 @@ import ( "testing" ) -type testProject struct { +type TestProject struct { t *testing.T extraEnv []string @@ -27,8 +27,11 @@ type testProject struct { gitServer *test_utils.GitServer } -func (p *testProject) init(t *testing.T, k *test_utils.EnvTestCluster) { - p.t = t +func NewTestProject(t *testing.T, k *test_utils.EnvTestCluster) *TestProject { + p := &TestProject{ + t: t, + } + p.gitServer = test_utils.NewGitServer(t) p.gitServer.GitInit(p.getKluctlProjectRepo()) @@ -48,17 +51,20 @@ func (p *testProject) init(t *testing.T, k *test_utils.EnvTestCluster) { t.Cleanup(func() { os.Remove(p.mergedKubeconfig) }) - p.mergeKubeconfig(k) + if k != nil { + p.mergeKubeconfig(k) + } + return p } -func (p *testProject) testSlug() string { +func (p *TestProject) testSlug() string { n := p.t.Name() n = xstrings.ToKebabCase(n) n = strings.ReplaceAll(n, "/", "-") return n } -func (p *testProject) mergeKubeconfig(k *test_utils.EnvTestCluster) { +func (p *TestProject) mergeKubeconfig(k *test_utils.EnvTestCluster) { p.updateMergedKubeconfig(func(config *clientcmdapi.Config) { nkcfg, err := clientcmd.Load(k.Kubeconfig) if err != nil { @@ -72,7 +78,7 @@ func (p *testProject) mergeKubeconfig(k *test_utils.EnvTestCluster) { }) } -func (p *testProject) updateMergedKubeconfig(cb func(config *clientcmdapi.Config)) { +func (p *TestProject) updateMergedKubeconfig(cb func(config *clientcmdapi.Config)) { mkcfg, err := clientcmd.LoadFromFile(p.mergedKubeconfig) if err != nil { p.t.Fatal(err) @@ -86,11 +92,11 @@ func (p *testProject) updateMergedKubeconfig(cb func(config *clientcmdapi.Config } } -func (p *testProject) updateKluctlYaml(update func(o *uo.UnstructuredObject) error) { +func (p *TestProject) updateKluctlYaml(update func(o *uo.UnstructuredObject) error) { p.updateYaml(".kluctl.yml", update, "") } -func (p *testProject) updateDeploymentYaml(dir string, update func(o *uo.UnstructuredObject) error) { +func (p *TestProject) updateDeploymentYaml(dir string, update func(o *uo.UnstructuredObject) error) { p.updateYaml(filepath.Join(dir, "deployment.yml"), func(o *uo.UnstructuredObject) error { if dir == "." { o.SetNestedField(p.testSlug(), "commonLabels", "project_name") @@ -99,17 +105,17 @@ func (p *testProject) updateDeploymentYaml(dir string, update func(o *uo.Unstruc }, "") } -func (p *testProject) updateYaml(path string, update func(o *uo.UnstructuredObject) error, message string) { +func (p *TestProject) updateYaml(path string, update func(o *uo.UnstructuredObject) error, message string) { p.gitServer.UpdateYaml(p.getKluctlProjectRepo(), path, func(o *uo.UnstructuredObject) error { return update(o) }, message) } -func (p *testProject) updateFile(path string, update func(f string) (string, error), message string) { +func (p *TestProject) updateFile(path string, update func(f string) (string, error), message string) { p.gitServer.UpdateFile(p.getKluctlProjectRepo(), path, update, message) } -func (p *testProject) getDeploymentYaml(dir string) *uo.UnstructuredObject { +func (p *TestProject) getDeploymentYaml(dir string) *uo.UnstructuredObject { o, err := uo.FromFile(filepath.Join(p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()), dir, "deployment.yml")) if err != nil { p.t.Fatal(err) @@ -117,7 +123,7 @@ func (p *testProject) getDeploymentYaml(dir string) *uo.UnstructuredObject { return o } -func (p *testProject) listDeploymentItemPathes(dir string, fullPath bool) []string { +func (p *TestProject) listDeploymentItemPathes(dir string, fullPath bool) []string { var ret []string o := p.getDeploymentYaml(dir) l, _, err := o.GetNestedObjectList("deployments") @@ -141,7 +147,7 @@ func (p *testProject) listDeploymentItemPathes(dir string, fullPath bool) []stri return ret } -func (p *testProject) updateKustomizeDeployment(dir string, update func(o *uo.UnstructuredObject, wt *git.Worktree) error) { +func (p *TestProject) updateKustomizeDeployment(dir string, update func(o *uo.UnstructuredObject, wt *git.Worktree) error) { wt := p.gitServer.GetWorktree(p.getKluctlProjectRepo()) pth := filepath.Join(dir, "kustomization.yml") @@ -150,15 +156,15 @@ func (p *testProject) updateKustomizeDeployment(dir string, update func(o *uo.Un }, fmt.Sprintf("Update kustomization.yml for %s", dir)) } -func (p *testProject) updateTarget(name string, cb func(target *uo.UnstructuredObject)) { +func (p *TestProject) updateTarget(name string, cb func(target *uo.UnstructuredObject)) { p.updateNamedListItem(uo.KeyPath{"targets"}, name, cb) } -func (p *testProject) updateSecretSet(name string, cb func(secretSet *uo.UnstructuredObject)) { +func (p *TestProject) updateSecretSet(name string, cb func(secretSet *uo.UnstructuredObject)) { p.updateNamedListItem(uo.KeyPath{"secretsConfig", "secretSets"}, name, cb) } -func (p *testProject) updateNamedListItem(path uo.KeyPath, name string, cb func(item *uo.UnstructuredObject)) { +func (p *TestProject) updateNamedListItem(path uo.KeyPath, name string, cb func(item *uo.UnstructuredObject)) { if cb == nil { cb = func(target *uo.UnstructuredObject) {} } @@ -188,7 +194,7 @@ func (p *testProject) updateNamedListItem(path uo.KeyPath, name string, cb func( }) } -func (p *testProject) updateDeploymentItems(dir string, update func(items []*uo.UnstructuredObject) []*uo.UnstructuredObject) { +func (p *TestProject) updateDeploymentItems(dir string, update func(items []*uo.UnstructuredObject) []*uo.UnstructuredObject) { p.updateDeploymentYaml(dir, func(o *uo.UnstructuredObject) error { items, _, _ := o.GetNestedObjectList("deployments") items = update(items) @@ -196,7 +202,7 @@ func (p *testProject) updateDeploymentItems(dir string, update func(items []*uo. }) } -func (p *testProject) addDeploymentItem(dir string, item *uo.UnstructuredObject) { +func (p *TestProject) addDeploymentItem(dir string, item *uo.UnstructuredObject) { p.updateDeploymentItems(dir, func(items []*uo.UnstructuredObject) []*uo.UnstructuredObject { for _, x := range items { if reflect.DeepEqual(x, item) { @@ -208,7 +214,7 @@ func (p *testProject) addDeploymentItem(dir string, item *uo.UnstructuredObject) }) } -func (p *testProject) addDeploymentInclude(dir string, includePath string, tags []string) { +func (p *TestProject) addDeploymentInclude(dir string, includePath string, tags []string) { n := uo.FromMap(map[string]interface{}{ "include": includePath, }) @@ -218,7 +224,7 @@ func (p *testProject) addDeploymentInclude(dir string, includePath string, tags p.addDeploymentItem(dir, n) } -func (p *testProject) addDeploymentIncludes(dir string) { +func (p *TestProject) addDeploymentIncludes(dir string) { var pp []string for _, x := range strings.Split(dir, "/") { if x != "." { @@ -228,7 +234,7 @@ func (p *testProject) addDeploymentIncludes(dir string) { } } -func (p *testProject) addKustomizeDeployment(dir string, resources []kustomizeResource, tags []string) { +func (p *TestProject) addKustomizeDeployment(dir string, resources []kustomizeResource, tags []string) { deploymentDir := filepath.Dir(dir) if deploymentDir != "" { p.addDeploymentIncludes(deploymentDir) @@ -262,7 +268,7 @@ func (p *testProject) addKustomizeDeployment(dir string, resources []kustomizeRe }) } -func (p *testProject) convertInterfaceToList(x interface{}) []interface{} { +func (p *TestProject) convertInterfaceToList(x interface{}) []interface{} { var ret []interface{} if l, ok := x.([]interface{}); ok { return l @@ -288,7 +294,7 @@ type kustomizeResource struct { content interface{} } -func (p *testProject) addKustomizeResources(dir string, resources []kustomizeResource) { +func (p *TestProject) addKustomizeResources(dir string, resources []kustomizeResource) { p.updateKustomizeDeployment(dir, func(o *uo.UnstructuredObject, wt *git.Worktree) error { l, _, _ := o.GetNestedList("resources") for _, r := range resources { @@ -314,7 +320,7 @@ func (p *testProject) addKustomizeResources(dir string, resources []kustomizeRes }) } -func (p *testProject) deleteKustomizeDeployment(dir string) { +func (p *TestProject) deleteKustomizeDeployment(dir string) { deploymentDir := filepath.Dir(dir) p.updateDeploymentItems(deploymentDir, func(items []*uo.UnstructuredObject) []*uo.UnstructuredObject { var newItems []*uo.UnstructuredObject @@ -329,11 +335,11 @@ func (p *testProject) deleteKustomizeDeployment(dir string) { }) } -func (p *testProject) getKluctlProjectRepo() string { +func (p *TestProject) getKluctlProjectRepo() string { return "kluctl-project" } -func (p *testProject) Kluctl(argsIn ...string) (string, string, error) { +func (p *TestProject) Kluctl(argsIn ...string) (string, string, error) { var args []string args = append(args, argsIn...) args = append(args, "--no-update-check") @@ -365,7 +371,7 @@ func (p *testProject) Kluctl(argsIn ...string) (string, string, error) { return stdout, stderr, err } -func (p *testProject) KluctlMust(argsIn ...string) (string, string) { +func (p *TestProject) KluctlMust(argsIn ...string) (string, string) { stdout, stderr, err := p.Kluctl(argsIn...) if err != nil { p.t.Logf(stderr) diff --git a/e2e/seal_test.go b/e2e/seal_test.go index c172f060b..bf64c78a8 100644 --- a/e2e/seal_test.go +++ b/e2e/seal_test.go @@ -85,7 +85,7 @@ func startCertServer() (*certServer, error) { return &cs, nil } -func addProxyVars(p *testProject) { +func addProxyVars(p *TestProject) { f := func(idx int, k *test_utils.EnvTestCluster, cs *certServer) { p.extraEnv = append(p.extraEnv, fmt.Sprintf("KLUCTL_K8S_SERVICE_PROXY_%d_API_HOST=%s", idx, k.RESTConfig().Host)) p.extraEnv = append(p.extraEnv, fmt.Sprintf("KLUCTL_K8S_SERVICE_PROXY_%d_SERVICE_NAMESPACE=%s", idx, "kube-system")) @@ -97,9 +97,8 @@ func addProxyVars(p *testProject) { f(1, defaultCluster2, certServer2) } -func prepareSealTest(t *testing.T, k *test_utils.EnvTestCluster, secrets map[string]string, varsSources []*uo.UnstructuredObject, proxy bool) *testProject { - p := &testProject{} - p.init(t, k) +func prepareSealTest(t *testing.T, k *test_utils.EnvTestCluster, secrets map[string]string, varsSources []*uo.UnstructuredObject, proxy bool) *TestProject { + p := NewTestProject(t, k) if proxy { addProxyVars(p) @@ -115,13 +114,13 @@ func prepareSealTest(t *testing.T, k *test_utils.EnvTestCluster, secrets map[str return p } -func addSecretsSet(p *testProject, name string, varsSources []*uo.UnstructuredObject) { +func addSecretsSet(p *TestProject, name string, varsSources []*uo.UnstructuredObject) { p.updateSecretSet(name, func(secretSet *uo.UnstructuredObject) { _ = secretSet.SetNestedField(varsSources, "vars") }) } -func addSecretsSetToTarget(p *testProject, targetName string, secretSetName string) { +func addSecretsSetToTarget(p *TestProject, targetName string, secretSetName string) { p.updateTarget(targetName, func(target *uo.UnstructuredObject) { l, _, _ := target.GetNestedList("sealingConfig", "secretSets") l = append(l, secretSetName) diff --git a/e2e/sops_test.go b/e2e/sops_test.go index ee9af770a..c7e3f127f 100644 --- a/e2e/sops_test.go +++ b/e2e/sops_test.go @@ -13,8 +13,7 @@ func TestSopsVars(t *testing.T) { k := defaultCluster1 - p := &testProject{} - p.init(t, k) + p := NewTestProject(t, k) createNamespace(t, k, p.testSlug()) @@ -54,8 +53,7 @@ func TestSopsResources(t *testing.T) { k := defaultCluster1 - p := &testProject{} - p.init(t, k) + p := NewTestProject(t, k) createNamespace(t, k, p.testSlug()) diff --git a/e2e/utils_resources.go b/e2e/utils_resources.go index 073b246c5..9c8f03fae 100644 --- a/e2e/utils_resources.go +++ b/e2e/utils_resources.go @@ -51,14 +51,14 @@ func createSecretObject(data map[string]string, opts resourceOpts) *uo.Unstructu return o } -func addConfigMapDeployment(p *testProject, dir string, data map[string]string, opts resourceOpts) { +func addConfigMapDeployment(p *TestProject, dir string, data map[string]string, opts resourceOpts) { o := createConfigMapObject(data, opts) p.addKustomizeDeployment(dir, []kustomizeResource{ {fmt.Sprintf("configmap-%s.yml", opts.name), "", o}, }, opts.tags) } -func addSecretDeployment(p *testProject, dir string, data map[string]string, opts resourceOpts) { +func addSecretDeployment(p *TestProject, dir string, data map[string]string, opts resourceOpts) { o := createSecretObject(data, opts) fname := fmt.Sprintf("secret-%s.yml", opts.name) p.addKustomizeDeployment(dir, []kustomizeResource{ From 82b5844237f3c39a05680f341d78700712415bda Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 28 Nov 2022 16:51:51 +0100 Subject: [PATCH 1238/2916] tests: Update flux-kluctl-controller CRDs --- e2e/test_resources/kluctl-crds.yaml | 1221 +++------------------------ 1 file changed, 98 insertions(+), 1123 deletions(-) diff --git a/e2e/test_resources/kluctl-crds.yaml b/e2e/test_resources/kluctl-crds.yaml index d16c0d9ea..83b9cffcc 100644 --- a/e2e/test_resources/kluctl-crds.yaml +++ b/e2e/test_resources/kluctl-crds.yaml @@ -51,7 +51,6 @@ spec: metadata: type: object spec: - description: KluctlDeploymentSpec defines the desired state of KluctlDeployment properties: abortOnError: default: false @@ -60,34 +59,42 @@ spec: when calling kluctl. type: boolean args: - additionalProperties: - type: string - description: Args specifies dynamic target args. Only arguments defined - by 'dynamicArgs' of the target are allowed. + description: Args specifies dynamic target args. + type: object + x-kubernetes-preserve-unknown-fields: true + context: + description: If specified, overrides the context to be used. This + will effectively make kluctl ignore the context specified in the + target. + type: string + decryption: + description: Decrypt Kubernetes secrets before applying them on the + cluster. + properties: + provider: + description: Provider is the name of the decryption engine. + enum: + - sops + type: string + secretRef: + description: The secret name containing the private OpenPGP keys + used for decryption. + properties: + name: + description: Name of the referent. + type: string + required: + - name + type: object + required: + - provider type: object - dependsOn: - description: DependsOn may contain a meta.NamespacedObjectReference - slice with references to resources that must be ready before this - kluctl project can be deployed. - items: - description: NamespacedObjectReference contains enough information - to locate the referenced Kubernetes resource object in any namespace. - properties: - name: - description: Name of the referent. - type: string - namespace: - description: Namespace of the referent, when not specified it - acts as LocalObjectReference. - type: string - required: - - name - type: object - type: array deployInterval: description: DeployInterval specifies the interval at which to deploy - the KluctlDeployment. This is independent of the 'Interval' value, - which only causes deployments if some deployment objects have changed. + the KluctlDeployment. It defaults to the Interval value, meaning + that it will re-deploy on every reconciliation. If you set DeployInterval + to a different value, + pattern: ^(([0-9]+(\.[0-9]+)?(ms|s|m|h))+)|never$ type: string deployMode: default: full-deploy @@ -96,6 +103,14 @@ spec: - full-deploy - poke-images type: string + deployOnChanges: + default: true + description: DeployOnChanges will cause a re-deployment whenever the + rendered resources change in the deployment. This check is performed + on every reconciliation. This means that a deployment will be triggered + even before the DeployInterval has passed in case something has + changed in the rendered resources. + type: boolean dryRun: default: false description: DryRun instructs kluctl to run everything in dry-run @@ -195,13 +210,16 @@ spec: type: array interval: description: The interval at which to reconcile the KluctlDeployment. + By default, the controller will re-deploy and validate the deployment + on each reconciliation. To override this behavior, change the DeployInterval + and/or ValidateInterval values. + pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ type: string kubeConfig: description: The KubeConfig for deploying to the target cluster. Specifies the kubeconfig to be used when invoking kluctl. Contexts in this kubeconfig must match the context found in the kluctl target. As - an alternative, RenameContexts can be used to fix non-matching context - names. + an alternative, specify the context to be used via 'context' properties: secretRef: description: SecretRef holds the name of a secret that contains @@ -283,8 +301,9 @@ spec: type: boolean retryInterval: description: The interval at which to retry a previously failed reconciliation. - When not specified, the controller uses the KluctlDeploymentSpec.Interval - value to retry failures. + When not specified, the controller uses the Interval value to retry + failures. + pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ type: string serviceAccountName: description: The name of the Kubernetes service account to use while @@ -297,20 +316,18 @@ spec: kluctl project. properties: apiVersion: - description: API version of the referent. + description: API version of the referent, if not specified the + Kubernetes preferred version will be used. type: string kind: description: Kind of the referent. - enum: - - GitRepository - - Bucket type: string name: description: Name of the referent. type: string namespace: - description: Namespace of the referent, defaults to the namespace - of the Kubernetes resource object that contains the reference. + description: Namespace of the referent, when not specified it + acts as LocalObjectReference. type: string required: - kind @@ -322,28 +339,43 @@ spec: Defaults to false. type: boolean target: - description: Target specifies the kluctl target to deploy + description: Target specifies the kluctl target to deploy. If not + specified, an empty target is used that has no name and no context. + Use 'TargetName' and 'Context' to specify the name and context in + that case. + maxLength: 63 + minLength: 1 + type: string + targetNameOverride: + description: TargetNameOverride sets or overrides the target name. + This is especially useful when deployment without a target. maxLength: 63 minLength: 1 type: string timeout: description: Timeout for all operations. Defaults to 'Interval' duration. + pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ type: string updateImages: default: false description: UpdateImages instructs kluctl to update dynamic images. Equivalent to using '-u' when calling kluctl. type: boolean + validate: + default: true + description: Validate enables validation after deploying + type: boolean validateInterval: - default: 5m description: ValidateInterval specifies the interval at which to validate the KluctlDeployment. Validation is performed the same way as with - 'kluctl validate -t '. Defaults to 1m. + 'kluctl validate -t '. Defaults to the same value as specified + in Interval. Validate is also performed whenever a deployment is + performed, independent of the value of ValidateInterval + pattern: ^(([0-9]+(\.[0-9]+)?(ms|s|m|h))+)|never$ type: string required: - interval - sourceRef - - target type: object status: description: KluctlDeploymentStatus defines the observed state of KluctlDeployment @@ -360,13 +392,14 @@ spec: description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, - type FooStatus struct{ // Represents the observations of a - foo's current state. // Known .status.conditions.type are: - \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type - \ // +patchStrategy=merge // +listType=map // +listMapKey=type - \ Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` - \n // other fields }" + \n \ttype FooStatus struct{ \t // Represents the observations + of a foo's current state. \t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\" \t // + +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map + \t // +listMapKey=type \t Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields + \t}" properties: lastTransitionTime: description: lastTransitionTime is the last time the condition @@ -436,253 +469,26 @@ spec: objectsHash: description: ObjectsHash is the hash of all rendered objects type: string - result: - properties: - changedObjects: - items: - description: ObjectRef contains the information necessary - to locate a resource within a cluster. - properties: - group: - type: string - kind: - type: string - name: - type: string - namespace: - type: string - version: - type: string - required: - - group - - kind - - name - - namespace - - version - type: object - type: array - deletedObjects: - items: - description: ObjectRef contains the information necessary - to locate a resource within a cluster. - properties: - group: - type: string - kind: - type: string - name: - type: string - namespace: - type: string - version: - type: string - required: - - group - - kind - - name - - namespace - - version - type: object - type: array - errors: - items: - properties: - error: - type: string - ref: - description: ObjectRef contains the information necessary - to locate a resource within a cluster. - properties: - group: - type: string - kind: - type: string - name: - type: string - namespace: - type: string - version: - type: string - required: - - group - - kind - - name - - namespace - - version - type: object - required: - - error - - ref - type: object - type: array - hookObjects: - items: - description: ObjectRef contains the information necessary - to locate a resource within a cluster. - properties: - group: - type: string - kind: - type: string - name: - type: string - namespace: - type: string - version: - type: string - required: - - group - - kind - - name - - namespace - - version - type: object - type: array - newObjects: - items: - description: ObjectRef contains the information necessary - to locate a resource within a cluster. - properties: - group: - type: string - kind: - type: string - name: - type: string - namespace: - type: string - version: - type: string - required: - - group - - kind - - name - - namespace - - version - type: object - type: array - orphanObjects: - items: - description: ObjectRef contains the information necessary - to locate a resource within a cluster. - properties: - group: - type: string - kind: - type: string - name: - type: string - namespace: - type: string - version: - type: string - required: - - group - - kind - - name - - namespace - - version - type: object - type: array - seenImages: - items: - properties: - container: - type: string - deployTags: - items: - type: string - type: array - deployedImage: - type: string - deployment: - type: string - deploymentDir: - type: string - image: - type: string - namespace: - type: string - object: - description: ObjectRef contains the information necessary - to locate a resource within a cluster. - properties: - group: - type: string - kind: - type: string - name: - type: string - namespace: - type: string - version: - type: string - required: - - group - - kind - - name - - namespace - - version - type: object - registryImage: - type: string - resultImage: - type: string - versionFilter: - type: string - required: - - image - - resultImage - type: object - type: array - warnings: - items: - properties: - error: - type: string - ref: - description: ObjectRef contains the information necessary - to locate a resource within a cluster. - properties: - group: - type: string - kind: - type: string - name: - type: string - namespace: - type: string - version: - type: string - required: - - group - - kind - - name - - namespace - - version - type: object - required: - - error - - ref - type: object - type: array - type: object + rawResult: + type: string revision: description: Revision is the source revision. Please note that kluctl projects have dependent git repositories which are not considered in the source revision type: string - targetName: - description: TargetName is the name of the target + target: + type: string + targetNameOverride: type: string time: description: AttemptedAt is the time when the attempt was performed format: date-time type: string required: - - targetName - time type: object + lastHandledDeployAt: + type: string lastHandledReconcileAt: description: LastHandledReconcileAt holds the value of the most recent reconcile request value, so a change of the annotation value can @@ -696,251 +502,22 @@ spec: objectsHash: description: ObjectsHash is the hash of all rendered objects type: string - result: - properties: - changedObjects: - items: - description: ObjectRef contains the information necessary - to locate a resource within a cluster. - properties: - group: - type: string - kind: - type: string - name: - type: string - namespace: - type: string - version: - type: string - required: - - group - - kind - - name - - namespace - - version - type: object - type: array - deletedObjects: - items: - description: ObjectRef contains the information necessary - to locate a resource within a cluster. - properties: - group: - type: string - kind: - type: string - name: - type: string - namespace: - type: string - version: - type: string - required: - - group - - kind - - name - - namespace - - version - type: object - type: array - errors: - items: - properties: - error: - type: string - ref: - description: ObjectRef contains the information necessary - to locate a resource within a cluster. - properties: - group: - type: string - kind: - type: string - name: - type: string - namespace: - type: string - version: - type: string - required: - - group - - kind - - name - - namespace - - version - type: object - required: - - error - - ref - type: object - type: array - hookObjects: - items: - description: ObjectRef contains the information necessary - to locate a resource within a cluster. - properties: - group: - type: string - kind: - type: string - name: - type: string - namespace: - type: string - version: - type: string - required: - - group - - kind - - name - - namespace - - version - type: object - type: array - newObjects: - items: - description: ObjectRef contains the information necessary - to locate a resource within a cluster. - properties: - group: - type: string - kind: - type: string - name: - type: string - namespace: - type: string - version: - type: string - required: - - group - - kind - - name - - namespace - - version - type: object - type: array - orphanObjects: - items: - description: ObjectRef contains the information necessary - to locate a resource within a cluster. - properties: - group: - type: string - kind: - type: string - name: - type: string - namespace: - type: string - version: - type: string - required: - - group - - kind - - name - - namespace - - version - type: object - type: array - seenImages: - items: - properties: - container: - type: string - deployTags: - items: - type: string - type: array - deployedImage: - type: string - deployment: - type: string - deploymentDir: - type: string - image: - type: string - namespace: - type: string - object: - description: ObjectRef contains the information necessary - to locate a resource within a cluster. - properties: - group: - type: string - kind: - type: string - name: - type: string - namespace: - type: string - version: - type: string - required: - - group - - kind - - name - - namespace - - version - type: object - registryImage: - type: string - resultImage: - type: string - versionFilter: - type: string - required: - - image - - resultImage - type: object - type: array - warnings: - items: - properties: - error: - type: string - ref: - description: ObjectRef contains the information necessary - to locate a resource within a cluster. - properties: - group: - type: string - kind: - type: string - name: - type: string - namespace: - type: string - version: - type: string - required: - - group - - kind - - name - - namespace - - version - type: object - required: - - error - - ref - type: object - type: array - type: object + rawResult: + type: string revision: description: Revision is the source revision. Please note that kluctl projects have dependent git repositories which are not considered in the source revision type: string - targetName: - description: TargetName is the name of the target + target: + type: string + targetNameOverride: type: string time: description: AttemptedAt is the time when the attempt was performed format: date-time type: string required: - - targetName - time type: object lastValidateResult: @@ -952,632 +529,30 @@ spec: objectsHash: description: ObjectsHash is the hash of all rendered objects type: string - result: - properties: - errors: - items: - properties: - error: - type: string - ref: - description: ObjectRef contains the information necessary - to locate a resource within a cluster. - properties: - group: - type: string - kind: - type: string - name: - type: string - namespace: - type: string - version: - type: string - required: - - group - - kind - - name - - namespace - - version - type: object - required: - - error - - ref - type: object - type: array - ready: - type: boolean - results: - items: - properties: - annotation: - type: string - message: - type: string - ref: - description: ObjectRef contains the information necessary - to locate a resource within a cluster. - properties: - group: - type: string - kind: - type: string - name: - type: string - namespace: - type: string - version: - type: string - required: - - group - - kind - - name - - namespace - - version - type: object - required: - - annotation - - message - - ref - type: object - type: array - warnings: - items: - properties: - error: - type: string - ref: - description: ObjectRef contains the information necessary - to locate a resource within a cluster. - properties: - group: - type: string - kind: - type: string - name: - type: string - namespace: - type: string - version: - type: string - required: - - group - - kind - - name - - namespace - - version - type: object - required: - - error - - ref - type: object - type: array - required: - - ready - type: object + rawResult: + type: string revision: description: Revision is the source revision. Please note that kluctl projects have dependent git repositories which are not considered in the source revision type: string - targetName: - description: TargetName is the name of the target + target: + type: string + targetNameOverride: type: string time: description: AttemptedAt is the time when the attempt was performed format: date-time type: string required: - - error - - targetName - time type: object observedGeneration: description: ObservedGeneration is the last reconciled generation. format: int64 type: integer - type: object - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.7.0 - creationTimestamp: null - name: kluctlmultideployments.flux.kluctl.io -spec: - group: flux.kluctl.io - names: - kind: KluctlMultiDeployment - listKind: KluctlMultiDeploymentList - plural: kluctlmultideployments - singular: kluctlmultideployment - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .spec.targetPattern - name: Pattern - type: string - - jsonPath: .status.targetCount - name: Targets - type: integer - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha1 - schema: - openAPIV3Schema: - description: KluctlMultiDeployment is the Schema for the kluctlmultideployments - API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: KluctlMultiDeploymentSpec defines the desired state of KluctlMultiDeployment - properties: - dependsOn: - description: DependsOn may contain a meta.NamespacedObjectReference - slice with references to resources that must be ready before this - kluctl project can be deployed. - items: - description: NamespacedObjectReference contains enough information - to locate the referenced Kubernetes resource object in any namespace. - properties: - name: - description: Name of the referent. - type: string - namespace: - description: Namespace of the referent, when not specified it - acts as LocalObjectReference. - type: string - required: - - name - type: object - type: array - interval: - description: The interval at which to reconcile the KluctlDeployment. - type: string - path: - description: Path to the directory containing the .kluctl.yaml file, - or the Defaults to 'None', which translates to the root path of - the SourceRef. - type: string - retryInterval: - description: The interval at which to retry a previously failed reconciliation. - When not specified, the controller uses the KluctlDeploymentSpec.Interval - value to retry failures. - type: string - sourceRef: - description: Reference of the source where the kluctl project is. - The authentication secrets from the source are also used to authenticate - dependent git repositories which are cloned while deploying the - kluctl project. - properties: - apiVersion: - description: API version of the referent. - type: string - kind: - description: Kind of the referent. - enum: - - GitRepository - - Bucket - type: string - name: - description: Name of the referent. - type: string - namespace: - description: Namespace of the referent, defaults to the namespace - of the Kubernetes resource object that contains the reference. - type: string - required: - - kind - - name - type: object - suspend: - description: This flag tells the controller to suspend subsequent - kluctl executions, it does not apply to already started executions. - Defaults to false. - type: boolean - targetPattern: - description: TargetPattern is the regex pattern used to match targets + rawTarget: type: string - template: - description: Template is the object template used to create KluctlDeploymet - objects - properties: - spec: - description: Spec is the KluctlDeployment spec to be used as a - template - properties: - abortOnError: - default: false - description: ForceReplaceOnError instructs kluctl to abort - deployments immediately when something fails. Equivalent - to using '--abort-on-error' when calling kluctl. - type: boolean - args: - additionalProperties: - type: string - description: Args specifies dynamic target args. Only arguments - defined by 'dynamicArgs' of the target are allowed. - type: object - deployInterval: - description: DeployInterval specifies the interval at which - to deploy the KluctlDeployment. This is independent of the - 'Interval' value, which only causes deployments if some - deployment objects have changed. - type: string - deployMode: - default: full-deploy - description: DeployMode specifies what deploy mode should - be used - enum: - - full-deploy - - poke-images - type: string - dryRun: - default: false - description: DryRun instructs kluctl to run everything in - dry-run mode. Equivalent to using '--dry-run' when calling - kluctl. - type: boolean - excludeDeploymentDirs: - description: ExcludeDeploymentDirs instructs kluctl to exclude - deployments with the given dir. Equivalent to using '--exclude-deployment-dir' - when calling kluctl. - items: - type: string - type: array - excludeTags: - description: ExcludeTags instructs kluctl to exclude deployments - with given tags. Equivalent to using '--exclude-tag' when - calling kluctl. - items: - type: string - type: array - forceApply: - default: false - description: ForceApply instructs kluctl to force-apply in - case of SSA conflicts. Equivalent to using '--force-apply' - when calling kluctl. - type: boolean - forceReplaceOnError: - default: false - description: ForceReplaceOnError instructs kluctl to force-replace - resources in case a normal replace fails. Equivalent to - using '--force-replace-on-error' when calling kluctl. - type: boolean - images: - description: Images contains a list of fixed image overrides. - Equivalent to using '--fixed-images-file' when calling kluctl. - items: - properties: - container: - type: string - deployTags: - items: - type: string - type: array - deployedImage: - type: string - deployment: - type: string - deploymentDir: - type: string - image: - type: string - namespace: - type: string - object: - description: ObjectRef contains the information necessary - to locate a resource within a cluster. - properties: - group: - type: string - kind: - type: string - name: - type: string - namespace: - type: string - version: - type: string - required: - - group - - kind - - name - - namespace - - version - type: object - registryImage: - type: string - resultImage: - type: string - versionFilter: - type: string - required: - - image - - resultImage - type: object - type: array - includeDeploymentDirs: - description: IncludeDeploymentDirs instructs kluctl to only - include deployments with the given dir. Equivalent to using - '--include-deployment-dir' when calling kluctl. - items: - type: string - type: array - includeTags: - description: IncludeTags instructs kluctl to only include - deployments with given tags. Equivalent to using '--include-tag' - when calling kluctl. - items: - type: string - type: array - interval: - description: The interval at which to reconcile the KluctlDeployment. - type: string - kubeConfig: - description: The KubeConfig for deploying to the target cluster. - Specifies the kubeconfig to be used when invoking kluctl. - Contexts in this kubeconfig must match the context found - in the kluctl target. As an alternative, RenameContexts - can be used to fix non-matching context names. - properties: - secretRef: - description: SecretRef holds the name of a secret that - contains a key with the kubeconfig file as the value. - If no key is set, the key will default to 'value'. The - secret must be in the same namespace as the Kustomization. - It is recommended that the kubeconfig is self-contained, - and the secret is regularly updated if credentials such - as a cloud-access-token expire. Cloud specific `cmd-path` - auth helpers will not function without adding binaries - and credentials to the Pod that is responsible for reconciling - the KluctlDeployment. - properties: - key: - description: Key in the Secret, when not specified - an implementation-specific default key is used. - type: string - name: - description: Name of the Secret. - type: string - required: - - name - type: object - type: object - noWait: - default: false - description: NoWait instructs kluctl to not wait for any resources - to become ready, including hooks. Equivalent to using '--no-wait' - when calling kluctl. - type: boolean - prune: - default: false - description: Prune enables pruning after deploying. - type: boolean - registrySecrets: - description: RegistrySecrets is a list of secret references - to be used for image registry authentication. The secrets - must either have ".dockerconfigjson" included or "registry", - "username" and "password". Additionally, "caFile" and "insecure" - can be specified. - items: - description: LocalObjectReference contains enough information - to locate the referenced Kubernetes resource object. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - type: array - renameContexts: - description: RenameContexts specifies a list of context rename - operations. This is useful when the kluctl target's context - does not match with the contexts found in the kubeconfig - while deploying. This is the case when using kubeconfigs - generated from service accounts, in which case the context - name is always "default". - items: - description: RenameContext specifies a single rename of - a context - properties: - newContext: - description: NewContext is the new name of the context - type: string - oldContext: - description: OldContext is the name of the context to - be renamed - type: string - required: - - newContext - - oldContext - type: object - type: array - replaceOnError: - default: false - description: ReplaceOnError instructs kluctl to replace resources - on error. Equivalent to using '--replace-on-error' when - calling kluctl. - type: boolean - retryInterval: - description: The interval at which to retry a previously failed - reconciliation. When not specified, the controller uses - the KluctlDeploymentSpec.Interval value to retry failures. - type: string - serviceAccountName: - description: The name of the Kubernetes service account to - use while deploying. If not specified, the default service - account is used. - type: string - timeout: - description: Timeout for all operations. Defaults to 'Interval' - duration. - type: string - updateImages: - default: false - description: UpdateImages instructs kluctl to update dynamic - images. Equivalent to using '-u' when calling kluctl. - type: boolean - validateInterval: - default: 5m - description: ValidateInterval specifies the interval at which - to validate the KluctlDeployment. Validation is performed - the same way as with 'kluctl validate -t '. Defaults - to 1m. - type: string - required: - - interval - type: object - required: - - spec - type: object - timeout: - description: Timeout for all operations. Defaults to 'Interval' duration. - type: string - required: - - interval - - sourceRef - - targetPattern - - template - type: object - status: - description: KluctlMultiDeploymentStatus defines the observed state of - KluctlMultiDeployment - properties: - conditions: - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - type FooStatus struct{ // Represents the observations of a - foo's current state. // Known .status.conditions.type are: - \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type - \ // +patchStrategy=merge // +listType=map // +listMapKey=type - \ Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` - \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - lastAttemptedRevision: - description: LastAttemptedRevision is the revision of the last reconciliation - attempt. - type: string - lastHandledReconcileAt: - description: LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value can - be detected. - type: string - observedGeneration: - description: ObservedGeneration is the last reconciled generation. - format: int64 - type: integer - targetCount: - description: TargetCount is the number of targets detected - type: integer - targets: - description: Targets is the list of detected targets - items: - description: KluctlMultiDeploymentTargetStatus describes the status - of a single target - properties: - kluctlDeploymentName: - description: KluctlDeploymentName is the name of the generated - KluctlDeployment object - type: string - name: - description: Name is the name of the detected target - type: string - required: - - kluctlDeploymentName - - name - type: object - type: array type: object type: object served: true From c5454557f845548588cbd005839bdc8020ca211a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 28 Nov 2022 16:54:53 +0100 Subject: [PATCH 1239/2916] tests: Make multiple functions of TestProject public --- e2e/args_test.go | 42 +++++++++--------- e2e/contexts_test.go | 58 ++++++++++++------------ e2e/deployment_items_test.go | 38 ++++++++-------- e2e/helm_test.go | 80 ++++++++++++++++----------------- e2e/hooks_test.go | 18 ++++---- e2e/inclusion_test.go | 50 ++++++++++----------- e2e/no_target_test.go | 20 ++++----- e2e/project.go | 86 ++++++++++++++++++------------------ e2e/seal_test.go | 44 +++++++++--------- e2e/sops_test.go | 26 +++++------ e2e/utils_resources.go | 4 +- 11 files changed, 233 insertions(+), 233 deletions(-) diff --git a/e2e/args_test.go b/e2e/args_test.go index 17e772568..32443a740 100644 --- a/e2e/args_test.go +++ b/e2e/args_test.go @@ -14,9 +14,9 @@ func testArgs(t *testing.T, deprecated bool) { p := NewTestProject(t, k) - createNamespace(t, k, p.testSlug()) + createNamespace(t, k, p.TestSlug()) - p.updateTarget("test", func(target *uo.UnstructuredObject) { + p.UpdateTarget("test", func(target *uo.UnstructuredObject) { }) args := []any{ @@ -36,12 +36,12 @@ func testArgs(t *testing.T, deprecated bool) { } if deprecated { - p.updateDeploymentYaml(".", func(o *uo.UnstructuredObject) error { + p.UpdateDeploymentYaml(".", func(o *uo.UnstructuredObject) error { _ = o.SetNestedField(args, "args") return nil }) } else { - p.updateKluctlYaml(func(o *uo.UnstructuredObject) error { + p.UpdateKluctlYaml(func(o *uo.UnstructuredObject) error { _ = o.SetNestedField(args, "args") return nil }) @@ -54,36 +54,36 @@ func testArgs(t *testing.T, deprecated bool) { "d": "{{ args.d | to_json }}", }, resourceOpts{ name: "cm", - namespace: p.testSlug(), + namespace: p.TestSlug(), }) p.KluctlMust("deploy", "--yes", "-t", "test", "-aa=a") - cm := k.MustGetCoreV1(t, "configmaps", p.testSlug(), "cm") + cm := k.MustGetCoreV1(t, "configmaps", p.TestSlug(), "cm") assertNestedFieldEquals(t, cm, "a", "data", "a") assertNestedFieldEquals(t, cm, "default", "data", "b") assertNestedFieldEquals(t, cm, "na", "data", "c") assertNestedFieldEquals(t, cm, `{"nested": "default"}`, "data", "d") p.KluctlMust("deploy", "--yes", "-t", "test", "-aa=a", "-ab=b") - cm = k.MustGetCoreV1(t, "configmaps", p.testSlug(), "cm") + cm = k.MustGetCoreV1(t, "configmaps", p.TestSlug(), "cm") assertNestedFieldEquals(t, cm, "a", "data", "a") assertNestedFieldEquals(t, cm, "b", "data", "b") assertNestedFieldEquals(t, cm, "na", "data", "c") assertNestedFieldEquals(t, cm, `{"nested": "default"}`, "data", "d") p.KluctlMust("deploy", "--yes", "-t", "test", "-aa=a", "-ab=b", "-ac=c") - cm = k.MustGetCoreV1(t, "configmaps", p.testSlug(), "cm") + cm = k.MustGetCoreV1(t, "configmaps", p.TestSlug(), "cm") assertNestedFieldEquals(t, cm, "a", "data", "a") assertNestedFieldEquals(t, cm, "b", "data", "b") assertNestedFieldEquals(t, cm, "c", "data", "c") assertNestedFieldEquals(t, cm, `{"nested": "default"}`, "data", "d") p.KluctlMust("deploy", "--yes", "-t", "test", "-aa=a", "-ab=b", "-ac=c", "-ad.nested=d") - cm = k.MustGetCoreV1(t, "configmaps", p.testSlug(), "cm") + cm = k.MustGetCoreV1(t, "configmaps", p.TestSlug(), "cm") assertNestedFieldEquals(t, cm, `{"nested": "d"}`, "data", "d") p.KluctlMust("deploy", "--yes", "-t", "test", "-aa=a", "-ab=b", "-ac=c", `-ad={"nested": "d2"}`) - cm = k.MustGetCoreV1(t, "configmaps", p.testSlug(), "cm") + cm = k.MustGetCoreV1(t, "configmaps", p.TestSlug(), "cm") assertNestedFieldEquals(t, cm, `{"nested": "d2"}`, "data", "d") tmpFile, err := os.CreateTemp("", "") @@ -97,7 +97,7 @@ nested: `) p.KluctlMust("deploy", "--yes", "-t", "test", "-aa=a", "-ab=b", "-ac=c", fmt.Sprintf(`-ad=@%s`, tmpFile.Name())) - cm = k.MustGetCoreV1(t, "configmaps", p.testSlug(), "cm") + cm = k.MustGetCoreV1(t, "configmaps", p.TestSlug(), "cm") assertNestedFieldEquals(t, cm, `{"nested": {"nested2": "d3"}}`, "data", "d") _ = tmpFile.Truncate(0) @@ -111,7 +111,7 @@ d: `) p.KluctlMust("deploy", "--yes", "-t", "test", fmt.Sprintf(`--args-from-file=%s`, tmpFile.Name())) - cm = k.MustGetCoreV1(t, "configmaps", p.testSlug(), "cm") + cm = k.MustGetCoreV1(t, "configmaps", p.TestSlug(), "cm") assertNestedFieldEquals(t, cm, "a2", "data", "a") assertNestedFieldEquals(t, cm, "default", "data", "b") assertNestedFieldEquals(t, cm, "c2", "data", "c") @@ -137,9 +137,9 @@ func TestArgsFromEnv(t *testing.T) { p := NewTestProject(t, k) - createNamespace(t, k, p.testSlug()) + createNamespace(t, k, p.TestSlug()) - p.updateTarget("test", func(target *uo.UnstructuredObject) { + p.UpdateTarget("test", func(target *uo.UnstructuredObject) { }) addConfigMapDeployment(p, "cm", map[string]string{ @@ -150,11 +150,11 @@ func TestArgsFromEnv(t *testing.T) { "e": `{{ args.e }}`, }, resourceOpts{ name: "cm", - namespace: p.testSlug(), + namespace: p.TestSlug(), }) p.KluctlMust("deploy", "--yes", "-t", "test") - cm := k.MustGetCoreV1(t, "configmaps", p.testSlug(), "cm") + cm := k.MustGetCoreV1(t, "configmaps", p.TestSlug(), "cm") assertNestedFieldEquals(t, cm, "a", "data", "a") assertNestedFieldEquals(t, cm, "b", "data", "b") assertNestedFieldEquals(t, cm, `{"nested": {"nested2": "c"}}`, "data", "c") @@ -170,9 +170,9 @@ func TestArgsFromEnvAndCli(t *testing.T) { p := NewTestProject(t, k) - createNamespace(t, k, p.testSlug()) + createNamespace(t, k, p.TestSlug()) - p.updateTarget("test", func(target *uo.UnstructuredObject) { + p.UpdateTarget("test", func(target *uo.UnstructuredObject) { }) addConfigMapDeployment(p, "cm", map[string]string{ @@ -181,18 +181,18 @@ func TestArgsFromEnvAndCli(t *testing.T) { "c": `{{ args.c }}`, }, resourceOpts{ name: "cm", - namespace: p.testSlug(), + namespace: p.TestSlug(), }) p.KluctlMust("deploy", "--yes", "-t", "test", "-a", "b=b") - cm := k.MustGetCoreV1(t, "configmaps", p.testSlug(), "cm") + cm := k.MustGetCoreV1(t, "configmaps", p.TestSlug(), "cm") assertNestedFieldEquals(t, cm, "a", "data", "a") assertNestedFieldEquals(t, cm, "b", "data", "b") assertNestedFieldEquals(t, cm, "c", "data", "c") // make sure the CLI overrides values from env p.KluctlMust("deploy", "--yes", "-t", "test", "-a", "b=b", "-a", "c=c2") - cm = k.MustGetCoreV1(t, "configmaps", p.testSlug(), "cm") + cm = k.MustGetCoreV1(t, "configmaps", p.TestSlug(), "cm") assertNestedFieldEquals(t, cm, "a", "data", "a") assertNestedFieldEquals(t, cm, "b", "data", "b") assertNestedFieldEquals(t, cm, "c2", "data", "c") diff --git a/e2e/contexts_test.go b/e2e/contexts_test.go index 55296bac2..1f3c5ee45 100644 --- a/e2e/contexts_test.go +++ b/e2e/contexts_test.go @@ -8,14 +8,14 @@ import ( func prepareContextTest(t *testing.T) *TestProject { p := NewTestProject(t, defaultCluster1) - p.mergeKubeconfig(defaultCluster2) + p.MergeKubeconfig(defaultCluster2) - createNamespace(t, defaultCluster1, p.testSlug()) - createNamespace(t, defaultCluster2, p.testSlug()) + createNamespace(t, defaultCluster1, p.TestSlug()) + createNamespace(t, defaultCluster2, p.TestSlug()) addConfigMapDeployment(p, "cm", nil, resourceOpts{ name: "cm", - namespace: p.testSlug(), + namespace: p.TestSlug(), }) return p @@ -26,20 +26,20 @@ func TestContextCurrent(t *testing.T) { p := prepareContextTest(t) - p.updateTarget("test1", func(target *uo.UnstructuredObject) { + p.UpdateTarget("test1", func(target *uo.UnstructuredObject) { // no context set, assume the current one is used }) p.KluctlMust("deploy", "--yes", "-t", "test1") - assertConfigMapExists(t, defaultCluster1, p.testSlug(), "cm") - assertConfigMapNotExists(t, defaultCluster2, p.testSlug(), "cm") + assertConfigMapExists(t, defaultCluster1, p.TestSlug(), "cm") + assertConfigMapNotExists(t, defaultCluster2, p.TestSlug(), "cm") - p.updateMergedKubeconfig(func(config *api.Config) { + p.UpdateMergedKubeconfig(func(config *api.Config) { config.CurrentContext = defaultCluster2.Context }) p.KluctlMust("deploy", "--yes", "-t", "test1") - assertConfigMapExists(t, defaultCluster2, p.testSlug(), "cm") + assertConfigMapExists(t, defaultCluster2, p.TestSlug(), "cm") } func TestContext1(t *testing.T) { @@ -47,13 +47,13 @@ func TestContext1(t *testing.T) { p := prepareContextTest(t) - p.updateTarget("test1", func(target *uo.UnstructuredObject) { + p.UpdateTarget("test1", func(target *uo.UnstructuredObject) { _ = target.SetNestedField(defaultCluster1.Context, "context") }) p.KluctlMust("deploy", "--yes", "-t", "test1") - assertConfigMapExists(t, defaultCluster1, p.testSlug(), "cm") - assertConfigMapNotExists(t, defaultCluster2, p.testSlug(), "cm") + assertConfigMapExists(t, defaultCluster1, p.TestSlug(), "cm") + assertConfigMapNotExists(t, defaultCluster2, p.TestSlug(), "cm") } func TestContext2(t *testing.T) { @@ -61,13 +61,13 @@ func TestContext2(t *testing.T) { p := prepareContextTest(t) - p.updateTarget("test1", func(target *uo.UnstructuredObject) { + p.UpdateTarget("test1", func(target *uo.UnstructuredObject) { _ = target.SetNestedField(defaultCluster2.Context, "context") }) p.KluctlMust("deploy", "--yes", "-t", "test1") - assertConfigMapExists(t, defaultCluster2, p.testSlug(), "cm") - assertConfigMapNotExists(t, defaultCluster1, p.testSlug(), "cm") + assertConfigMapExists(t, defaultCluster2, p.TestSlug(), "cm") + assertConfigMapNotExists(t, defaultCluster1, p.TestSlug(), "cm") } func TestContext1And2(t *testing.T) { @@ -75,19 +75,19 @@ func TestContext1And2(t *testing.T) { p := prepareContextTest(t) - p.updateTarget("test1", func(target *uo.UnstructuredObject) { + p.UpdateTarget("test1", func(target *uo.UnstructuredObject) { _ = target.SetNestedField(defaultCluster1.Context, "context") }) - p.updateTarget("test2", func(target *uo.UnstructuredObject) { + p.UpdateTarget("test2", func(target *uo.UnstructuredObject) { _ = target.SetNestedField(defaultCluster2.Context, "context") }) p.KluctlMust("deploy", "--yes", "-t", "test1") - assertConfigMapExists(t, defaultCluster1, p.testSlug(), "cm") - assertConfigMapNotExists(t, defaultCluster2, p.testSlug(), "cm") + assertConfigMapExists(t, defaultCluster1, p.TestSlug(), "cm") + assertConfigMapNotExists(t, defaultCluster2, p.TestSlug(), "cm") p.KluctlMust("deploy", "--yes", "-t", "test2") - assertConfigMapExists(t, defaultCluster2, p.testSlug(), "cm") + assertConfigMapExists(t, defaultCluster2, p.TestSlug(), "cm") } func TestContextSwitch(t *testing.T) { @@ -95,20 +95,20 @@ func TestContextSwitch(t *testing.T) { p := prepareContextTest(t) - p.updateTarget("test1", func(target *uo.UnstructuredObject) { + p.UpdateTarget("test1", func(target *uo.UnstructuredObject) { _ = target.SetNestedField(defaultCluster1.Context, "context") }) p.KluctlMust("deploy", "--yes", "-t", "test1") - assertConfigMapExists(t, defaultCluster1, p.testSlug(), "cm") - assertConfigMapNotExists(t, defaultCluster2, p.testSlug(), "cm") + assertConfigMapExists(t, defaultCluster1, p.TestSlug(), "cm") + assertConfigMapNotExists(t, defaultCluster2, p.TestSlug(), "cm") - p.updateTarget("test1", func(target *uo.UnstructuredObject) { + p.UpdateTarget("test1", func(target *uo.UnstructuredObject) { _ = target.SetNestedField(defaultCluster2.Context, "context") }) p.KluctlMust("deploy", "--yes", "-t", "test1") - assertConfigMapExists(t, defaultCluster2, p.testSlug(), "cm") + assertConfigMapExists(t, defaultCluster2, p.TestSlug(), "cm") } func TestContextOverride(t *testing.T) { @@ -116,14 +116,14 @@ func TestContextOverride(t *testing.T) { p := prepareContextTest(t) - p.updateTarget("test1", func(target *uo.UnstructuredObject) { + p.UpdateTarget("test1", func(target *uo.UnstructuredObject) { _ = target.SetNestedField(defaultCluster1.Context, "context") }) p.KluctlMust("deploy", "--yes", "-t", "test1") - assertConfigMapExists(t, defaultCluster1, p.testSlug(), "cm") - assertConfigMapNotExists(t, defaultCluster2, p.testSlug(), "cm") + assertConfigMapExists(t, defaultCluster1, p.TestSlug(), "cm") + assertConfigMapNotExists(t, defaultCluster2, p.TestSlug(), "cm") p.KluctlMust("deploy", "--yes", "-t", "test1", "--context", defaultCluster2.Context) - assertConfigMapExists(t, defaultCluster2, p.testSlug(), "cm") + assertConfigMapExists(t, defaultCluster2, p.TestSlug(), "cm") } diff --git a/e2e/deployment_items_test.go b/e2e/deployment_items_test.go index 23d4c0bad..122204df4 100644 --- a/e2e/deployment_items_test.go +++ b/e2e/deployment_items_test.go @@ -12,25 +12,25 @@ func TestKustomize(t *testing.T) { p := NewTestProject(t, k) - createNamespace(t, k, p.testSlug()) + createNamespace(t, k, p.TestSlug()) - p.updateTarget("test", nil) + p.UpdateTarget("test", nil) addConfigMapDeployment(p, "cm", nil, resourceOpts{ name: "cm", - namespace: p.testSlug(), + namespace: p.TestSlug(), }) p.KluctlMust("deploy", "--yes", "-t", "test") - assertConfigMapExists(t, k, p.testSlug(), "cm") + assertConfigMapExists(t, k, p.TestSlug(), "cm") addConfigMapDeployment(p, "cm2", nil, resourceOpts{ name: "cm2", - namespace: p.testSlug(), + namespace: p.TestSlug(), }) p.KluctlMust("deploy", "--yes", "-t", "test", "--dry-run") - assertConfigMapNotExists(t, k, p.testSlug(), "cm2") + assertConfigMapNotExists(t, k, p.TestSlug(), "cm2") p.KluctlMust("deploy", "--yes", "-t", "test") - assertConfigMapExists(t, k, p.testSlug(), "cm2") + assertConfigMapExists(t, k, p.TestSlug(), "cm2") } func TestGeneratedKustomize(t *testing.T) { @@ -40,11 +40,11 @@ func TestGeneratedKustomize(t *testing.T) { p := NewTestProject(t, k) - createNamespace(t, k, p.testSlug()) + createNamespace(t, k, p.TestSlug()) - p.updateTarget("test", nil) + p.UpdateTarget("test", nil) - p.updateDeploymentYaml("", func(o *uo.UnstructuredObject) error { + p.UpdateDeploymentYaml("", func(o *uo.UnstructuredObject) error { _ = o.SetNestedField([]any{ map[string]any{ "path": "generated-kustomize", @@ -52,30 +52,30 @@ func TestGeneratedKustomize(t *testing.T) { }, "deployments") return nil }) - p.updateYaml("generated-kustomize/cm1.yaml", func(o *uo.UnstructuredObject) error { + p.UpdateYaml("generated-kustomize/cm1.yaml", func(o *uo.UnstructuredObject) error { *o = *createConfigMapObject(nil, resourceOpts{ name: "cm1", - namespace: p.testSlug(), + namespace: p.TestSlug(), }) return nil }, "") - p.updateYaml("generated-kustomize/cm2.yaml", func(o *uo.UnstructuredObject) error { + p.UpdateYaml("generated-kustomize/cm2.yaml", func(o *uo.UnstructuredObject) error { *o = *createConfigMapObject(nil, resourceOpts{ name: "cm2", - namespace: p.testSlug(), + namespace: p.TestSlug(), }) return nil }, "") - p.updateYaml("generated-kustomize/cm3._yaml", func(o *uo.UnstructuredObject) error { + p.UpdateYaml("generated-kustomize/cm3._yaml", func(o *uo.UnstructuredObject) error { *o = *createConfigMapObject(nil, resourceOpts{ name: "cm3", - namespace: p.testSlug(), + namespace: p.TestSlug(), }) return nil }, "") p.KluctlMust("deploy", "--yes", "-t", "test") - assertConfigMapExists(t, k, p.testSlug(), "cm1") - assertConfigMapExists(t, k, p.testSlug(), "cm2") - assertConfigMapNotExists(t, k, p.testSlug(), "cm3") + assertConfigMapExists(t, k, p.TestSlug(), "cm1") + assertConfigMapExists(t, k, p.TestSlug(), "cm2") + assertConfigMapNotExists(t, k, p.TestSlug(), "cm3") } diff --git a/e2e/helm_test.go b/e2e/helm_test.go index cb4a588f5..f973570be 100644 --- a/e2e/helm_test.go +++ b/e2e/helm_test.go @@ -26,11 +26,11 @@ func addHelmDeployment(p *TestProject, dir string, repoUrl string, chartName, ve chartName = "" } - p.addKustomizeDeployment(dir, []kustomizeResource{ + p.AddKustomizeDeployment(dir, []kustomizeResource{ {name: "helm-rendered.yaml"}, }, nil) - p.updateYaml(filepath.Join(dir, "helm-chart.yaml"), func(o *uo.UnstructuredObject) error { + p.UpdateYaml(filepath.Join(dir, "helm-chart.yaml"), func(o *uo.UnstructuredObject) error { *o = *uo.FromMap(map[string]interface{}{ "helmChart": map[string]any{ "repo": repoUrl, @@ -46,7 +46,7 @@ func addHelmDeployment(p *TestProject, dir string, repoUrl string, chartName, ve }, "") if values != nil { - p.updateYaml(filepath.Join(dir, "helm-values.yaml"), func(o *uo.UnstructuredObject) error { + p.UpdateYaml(filepath.Join(dir, "helm-values.yaml"), func(o *uo.UnstructuredObject) error { *o = *uo.FromMap(values) return nil }, "") @@ -69,7 +69,7 @@ func testHelmPull(t *testing.T, tc testCase, prePull bool) { p := NewTestProject(t, k) - createNamespace(t, k, p.testSlug()) + createNamespace(t, k, p.TestSlug()) user := "" password := "" @@ -82,12 +82,12 @@ func testHelmPull(t *testing.T, tc testCase, prePull bool) { {ChartName: "test-chart1", Version: "0.1.0"}, }, tc.oci, user, password) - p.updateTarget("test", nil) - addHelmDeployment(p, "helm1", repoUrl, "test-chart1", "0.1.0", "test-helm1", p.testSlug(), nil) + p.UpdateTarget("test", nil) + addHelmDeployment(p, "helm1", repoUrl, "test-chart1", "0.1.0", "test-helm1", p.TestSlug(), nil) if tc.testAuth { if tc.credsId != "" { - p.updateYaml("helm1/helm-chart.yaml", func(o *uo.UnstructuredObject) error { + p.UpdateYaml("helm1/helm-chart.yaml", func(o *uo.UnstructuredObject) error { _ = o.SetNestedField(tc.credsId, "helmChart", "credentialsId") return nil }, "") @@ -123,7 +123,7 @@ func testHelmPull(t *testing.T, tc testCase, prePull bool) { assert.Contains(t, stderr, tc.expectedError) } else { assert.NoError(t, err) - assertConfigMapExists(t, k, p.testSlug(), "test-helm1-test-chart1") + assertConfigMapExists(t, k, p.TestSlug(), "test-helm1-test-chart1") } } @@ -160,31 +160,31 @@ func testHelmManualUpgrade(t *testing.T, oci bool) { p := NewTestProject(t, k) - createNamespace(t, k, p.testSlug()) + createNamespace(t, k, p.TestSlug()) repoUrl := createHelmOrOciRepo(p.t, []test_utils.RepoChart{ {ChartName: "test-chart1", Version: "0.1.0"}, {ChartName: "test-chart1", Version: "0.2.0"}, }, oci, "", "") - p.updateTarget("test", nil) - addHelmDeployment(p, "helm1", repoUrl, "test-chart1", "0.1.0", "test-helm1", p.testSlug(), nil) + p.UpdateTarget("test", nil) + addHelmDeployment(p, "helm1", repoUrl, "test-chart1", "0.1.0", "test-helm1", p.TestSlug(), nil) p.KluctlMust("helm-pull") assert.FileExists(t, filepath.Join(p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()), "helm1/charts/test-chart1/Chart.yaml")) p.KluctlMust("deploy", "--yes", "-t", "test") - cm := assertConfigMapExists(t, k, p.testSlug(), "test-helm1-test-chart1") + cm := assertConfigMapExists(t, k, p.TestSlug(), "test-helm1-test-chart1") v, _, _ := cm.GetNestedString("data", "version") assert.Equal(t, "0.1.0", v) - p.updateYaml("helm1/helm-chart.yaml", func(o *uo.UnstructuredObject) error { + p.UpdateYaml("helm1/helm-chart.yaml", func(o *uo.UnstructuredObject) error { _ = o.SetNestedField("0.2.0", "helmChart", "chartVersion") return nil }, "") p.KluctlMust("helm-pull") p.KluctlMust("deploy", "--yes", "-t", "test") - cm = assertConfigMapExists(t, k, p.testSlug(), "test-helm1-test-chart1") + cm = assertConfigMapExists(t, k, p.TestSlug(), "test-helm1-test-chart1") v, _, _ = cm.GetNestedString("data", "version") assert.Equal(t, "0.2.0", v) } @@ -204,7 +204,7 @@ func testHelmUpdate(t *testing.T, oci bool, upgrade bool, commit bool) { p := NewTestProject(t, k) - createNamespace(t, k, p.testSlug()) + createNamespace(t, k, p.TestSlug()) repoUrl := createHelmOrOciRepo(p.t, []test_utils.RepoChart{ {ChartName: "test-chart1", Version: "0.1.0"}, @@ -213,12 +213,12 @@ func testHelmUpdate(t *testing.T, oci bool, upgrade bool, commit bool) { {ChartName: "test-chart2", Version: "0.3.0"}, }, oci, "", "") - p.updateTarget("test", nil) - addHelmDeployment(p, "helm1", repoUrl, "test-chart1", "0.1.0", "test-helm1", p.testSlug(), nil) - addHelmDeployment(p, "helm2", repoUrl, "test-chart2", "0.1.0", "test-helm2", p.testSlug(), nil) - addHelmDeployment(p, "helm3", repoUrl, "test-chart1", "0.1.0", "test-helm3", p.testSlug(), nil) + p.UpdateTarget("test", nil) + addHelmDeployment(p, "helm1", repoUrl, "test-chart1", "0.1.0", "test-helm1", p.TestSlug(), nil) + addHelmDeployment(p, "helm2", repoUrl, "test-chart2", "0.1.0", "test-helm2", p.TestSlug(), nil) + addHelmDeployment(p, "helm3", repoUrl, "test-chart1", "0.1.0", "test-helm3", p.TestSlug(), nil) - p.updateYaml("helm3/helm-chart.yaml", func(o *uo.UnstructuredObject) error { + p.UpdateYaml("helm3/helm-chart.yaml", func(o *uo.UnstructuredObject) error { _ = o.SetNestedField(true, "helmChart", "skipUpdate") return nil }, "") @@ -314,7 +314,7 @@ func TestHelmValues(t *testing.T) { p := NewTestProject(t, k) - createNamespace(t, k, p.testSlug()) + createNamespace(t, k, p.TestSlug()) repoUrl := test_utils.CreateHelmRepo(p.t, []test_utils.RepoChart{ {ChartName: "test-chart1", Version: "0.1.0"}, @@ -340,17 +340,17 @@ func TestHelmValues(t *testing.T) { }, } - p.updateTarget("test", nil) - addHelmDeployment(p, "helm1", repoUrl, "test-chart1", "0.1.0", "test-helm1", p.testSlug(), values1) - addHelmDeployment(p, "helm2", repoUrl, "test-chart2", "0.1.0", "test-helm2", p.testSlug(), values2) - addHelmDeployment(p, "helm3", repoUrl, "test-chart1", "0.1.0", "test-helm3", p.testSlug(), values3) + p.UpdateTarget("test", nil) + addHelmDeployment(p, "helm1", repoUrl, "test-chart1", "0.1.0", "test-helm1", p.TestSlug(), values1) + addHelmDeployment(p, "helm2", repoUrl, "test-chart2", "0.1.0", "test-helm2", p.TestSlug(), values2) + addHelmDeployment(p, "helm3", repoUrl, "test-chart1", "0.1.0", "test-helm3", p.TestSlug(), values3) p.KluctlMust("helm-pull") p.KluctlMust("deploy", "--yes", "-t", "test", "-aa=a", "-ab=b") - cm1 := assertConfigMapExists(t, k, p.testSlug(), "test-helm1-test-chart1") - cm2 := assertConfigMapExists(t, k, p.testSlug(), "test-helm2-test-chart2") - cm3 := assertConfigMapExists(t, k, p.testSlug(), "test-helm3-test-chart1") + cm1 := assertConfigMapExists(t, k, p.TestSlug(), "test-helm1-test-chart1") + cm2 := assertConfigMapExists(t, k, p.TestSlug(), "test-helm2-test-chart2") + cm3 := assertConfigMapExists(t, k, p.TestSlug(), "test-helm3-test-chart1") assert.Equal(t, map[string]any{ "a": "x1", @@ -376,26 +376,26 @@ func TestHelmTemplateChartYaml(t *testing.T) { p := NewTestProject(t, k) - createNamespace(t, k, p.testSlug()) - createNamespace(t, k, p.testSlug()+"-a") - createNamespace(t, k, p.testSlug()+"-b") + createNamespace(t, k, p.TestSlug()) + createNamespace(t, k, p.TestSlug()+"-a") + createNamespace(t, k, p.TestSlug()+"-b") repoUrl := test_utils.CreateHelmRepo(p.t, []test_utils.RepoChart{ {ChartName: "test-chart1", Version: "0.1.0"}, {ChartName: "test-chart2", Version: "0.1.0"}, }, "", "") - p.updateTarget("test", nil) - addHelmDeployment(p, "helm1", repoUrl, "test-chart1", "0.1.0", "test-helm-{{ args.a }}", p.testSlug(), nil) - addHelmDeployment(p, "helm2", repoUrl, "test-chart2", "0.1.0", "test-helm-{{ args.b }}", p.testSlug(), nil) - addHelmDeployment(p, "helm3", repoUrl, "test-chart1", "0.1.0", "test-helm-ns", p.testSlug()+"-{{ args.a }}", nil) - addHelmDeployment(p, "helm4", repoUrl, "test-chart1", "0.1.0", "test-helm-ns", p.testSlug()+"-{{ args.b }}", nil) + p.UpdateTarget("test", nil) + addHelmDeployment(p, "helm1", repoUrl, "test-chart1", "0.1.0", "test-helm-{{ args.a }}", p.TestSlug(), nil) + addHelmDeployment(p, "helm2", repoUrl, "test-chart2", "0.1.0", "test-helm-{{ args.b }}", p.TestSlug(), nil) + addHelmDeployment(p, "helm3", repoUrl, "test-chart1", "0.1.0", "test-helm-ns", p.TestSlug()+"-{{ args.a }}", nil) + addHelmDeployment(p, "helm4", repoUrl, "test-chart1", "0.1.0", "test-helm-ns", p.TestSlug()+"-{{ args.b }}", nil) p.KluctlMust("helm-pull") p.KluctlMust("deploy", "--yes", "-t", "test", "-aa=a", "-ab=b") - assertConfigMapExists(t, k, p.testSlug(), "test-helm-a-test-chart1") - assertConfigMapExists(t, k, p.testSlug(), "test-helm-b-test-chart2") - assertConfigMapExists(t, k, p.testSlug()+"-a", "test-helm-ns-test-chart1") - assertConfigMapExists(t, k, p.testSlug()+"-b", "test-helm-ns-test-chart1") + assertConfigMapExists(t, k, p.TestSlug(), "test-helm-a-test-chart1") + assertConfigMapExists(t, k, p.TestSlug(), "test-helm-b-test-chart2") + assertConfigMapExists(t, k, p.TestSlug()+"-a", "test-helm-ns-test-chart1") + assertConfigMapExists(t, k, p.TestSlug()+"-b", "test-helm-ns-test-chart1") } diff --git a/e2e/hooks_test.go b/e2e/hooks_test.go index 54df6986e..e9e4ae433 100644 --- a/e2e/hooks_test.go +++ b/e2e/hooks_test.go @@ -36,7 +36,7 @@ func (s *hooksTestContext) removeWebhook() { } func (s *hooksTestContext) handleConfigmap(request admission.Request) { - if s.p.testSlug() != request.Namespace { + if s.p.TestSlug() != request.Namespace { return } @@ -87,7 +87,7 @@ func (s *hooksTestContext) addConfigMap(dir string, opts resourceOpts) { o.SetK8sGVKs("", "v1", "ConfigMap") mergeMetadata(o, opts) o.SetNestedField(map[string]interface{}{}, "data") - s.p.addKustomizeResources(dir, []kustomizeResource{ + s.p.AddKustomizeResources(dir, []kustomizeResource{ {fmt.Sprintf("%s.yml", opts.name), "", o}, }) } @@ -104,14 +104,14 @@ func prepareHookTestProject(t *testing.T, hook string, hookDeletionPolicy string s.removeWebhook() }) - createNamespace(s.t, s.k, s.p.testSlug()) + createNamespace(s.t, s.k, s.p.TestSlug()) - s.p.updateTarget("test", nil) + s.p.UpdateTarget("test", nil) - s.p.addKustomizeDeployment("hook", nil, nil) + s.p.AddKustomizeDeployment("hook", nil, nil) - s.addConfigMap("hook", resourceOpts{name: "cm1", namespace: s.p.testSlug()}) - s.addHookConfigMap("hook", resourceOpts{name: "hook1", namespace: s.p.testSlug()}, false, hook, hookDeletionPolicy) + s.addConfigMap("hook", resourceOpts{name: "cm1", namespace: s.p.TestSlug()}) + s.addHookConfigMap("hook", resourceOpts{name: "hook1", namespace: s.p.TestSlug()}, false, hook, hookDeletionPolicy) return s } @@ -124,10 +124,10 @@ func (s *hooksTestContext) ensureHookExecuted(expectedCms ...string) { func (s *hooksTestContext) ensureHookNotExecuted() { _ = s.k.DynamicClient.Resource(corev1.SchemeGroupVersion.WithResource("configmaps")). - Namespace(s.p.testSlug()). + Namespace(s.p.TestSlug()). Delete(context.Background(), "cm1", metav1.DeleteOptions{}) s.p.KluctlMust("deploy", "--yes", "-t", "test") - assertConfigMapNotExists(s.t, s.k, s.p.testSlug(), "cm1") + assertConfigMapNotExists(s.t, s.k, s.p.TestSlug(), "cm1") } func TestHooksPreDeployInitial(t *testing.T) { diff --git a/e2e/inclusion_test.go b/e2e/inclusion_test.go index 1a212cc4d..f8e196651 100644 --- a/e2e/inclusion_test.go +++ b/e2e/inclusion_test.go @@ -12,29 +12,29 @@ func prepareInclusionTestProject(t *testing.T, withIncludes bool) (*TestProject, k := defaultCluster1 p := NewTestProject(t, k) - createNamespace(t, k, p.testSlug()) + createNamespace(t, k, p.TestSlug()) - p.updateTarget("test", nil) + p.UpdateTarget("test", nil) - addConfigMapDeployment(p, "cm1", nil, resourceOpts{name: "cm1", namespace: p.testSlug()}) - addConfigMapDeployment(p, "cm2", nil, resourceOpts{name: "cm2", namespace: p.testSlug()}) - addConfigMapDeployment(p, "cm3", nil, resourceOpts{name: "cm3", namespace: p.testSlug(), tags: []string{"tag1", "tag2"}}) - addConfigMapDeployment(p, "cm4", nil, resourceOpts{name: "cm4", namespace: p.testSlug(), tags: []string{"tag1", "tag3"}}) - addConfigMapDeployment(p, "cm5", nil, resourceOpts{name: "cm5", namespace: p.testSlug(), tags: []string{"tag1", "tag4"}}) - addConfigMapDeployment(p, "cm6", nil, resourceOpts{name: "cm6", namespace: p.testSlug(), tags: []string{"tag1", "tag5"}}) - addConfigMapDeployment(p, "cm7", nil, resourceOpts{name: "cm7", namespace: p.testSlug(), tags: []string{"tag1", "tag6"}}) + addConfigMapDeployment(p, "cm1", nil, resourceOpts{name: "cm1", namespace: p.TestSlug()}) + addConfigMapDeployment(p, "cm2", nil, resourceOpts{name: "cm2", namespace: p.TestSlug()}) + addConfigMapDeployment(p, "cm3", nil, resourceOpts{name: "cm3", namespace: p.TestSlug(), tags: []string{"tag1", "tag2"}}) + addConfigMapDeployment(p, "cm4", nil, resourceOpts{name: "cm4", namespace: p.TestSlug(), tags: []string{"tag1", "tag3"}}) + addConfigMapDeployment(p, "cm5", nil, resourceOpts{name: "cm5", namespace: p.TestSlug(), tags: []string{"tag1", "tag4"}}) + addConfigMapDeployment(p, "cm6", nil, resourceOpts{name: "cm6", namespace: p.TestSlug(), tags: []string{"tag1", "tag5"}}) + addConfigMapDeployment(p, "cm7", nil, resourceOpts{name: "cm7", namespace: p.TestSlug(), tags: []string{"tag1", "tag6"}}) if withIncludes { - p.addDeploymentInclude(".", "include1", nil) - addConfigMapDeployment(p, "include1/icm1", nil, resourceOpts{name: "icm1", namespace: p.testSlug(), tags: []string{"itag1", "itag2"}}) + p.AddDeploymentInclude(".", "include1", nil) + addConfigMapDeployment(p, "include1/icm1", nil, resourceOpts{name: "icm1", namespace: p.TestSlug(), tags: []string{"itag1", "itag2"}}) - p.addDeploymentInclude(".", "include2", nil) - addConfigMapDeployment(p, "include2/icm2", nil, resourceOpts{name: "icm2", namespace: p.testSlug()}) - addConfigMapDeployment(p, "include2/icm3", nil, resourceOpts{name: "icm3", namespace: p.testSlug(), tags: []string{"itag3", "itag4"}}) + p.AddDeploymentInclude(".", "include2", nil) + addConfigMapDeployment(p, "include2/icm2", nil, resourceOpts{name: "icm2", namespace: p.TestSlug()}) + addConfigMapDeployment(p, "include2/icm3", nil, resourceOpts{name: "icm3", namespace: p.TestSlug(), tags: []string{"itag3", "itag4"}}) - p.addDeploymentInclude(".", "include3", []string{"itag5"}) - addConfigMapDeployment(p, "include3/icm4", nil, resourceOpts{name: "icm4", namespace: p.testSlug()}) - addConfigMapDeployment(p, "include3/icm5", nil, resourceOpts{name: "icm5", namespace: p.testSlug(), tags: []string{"itag5", "itag6"}}) + p.AddDeploymentInclude(".", "include3", []string{"itag5"}) + addConfigMapDeployment(p, "include3/icm4", nil, resourceOpts{name: "icm4", namespace: p.TestSlug()}) + addConfigMapDeployment(p, "include3/icm5", nil, resourceOpts{name: "icm5", namespace: p.TestSlug(), tags: []string{"itag5", "itag6"}}) } return p, k @@ -49,7 +49,7 @@ func assertExistsHelper(t *testing.T, p *TestProject, k *test_utils.EnvTestClust delete(shouldExists, x) } } - items, err := k.List(corev1.SchemeGroupVersion.WithResource("configmaps"), p.testSlug(), map[string]string{"project_name": p.testSlug()}) + items, err := k.List(corev1.SchemeGroupVersion.WithResource("configmaps"), p.TestSlug(), map[string]string{"project_name": p.TestSlug()}) if err != nil { t.Fatal(err) } @@ -160,7 +160,7 @@ func TestInclusionDeploymentDirs(t *testing.T) { p.KluctlMust("deploy", "--yes", "-t", "test", "--exclude-deployment-dir", "include3/icm5") var a []string - for _, x := range p.listDeploymentItemPathes(".", false) { + for _, x := range p.ListDeploymentItemPathes(".", false) { if x != "icm5" { a = append(a, filepath.Base(x)) } @@ -180,20 +180,20 @@ func TestInclusionPrune(t *testing.T) { doAssertExists(nil, nil) p.KluctlMust("deploy", "--yes", "-t", "test") - doAssertExists(p.listDeploymentItemPathes(".", false), nil) + doAssertExists(p.ListDeploymentItemPathes(".", false), nil) - p.deleteKustomizeDeployment("cm1") + p.DeleteKustomizeDeployment("cm1") p.KluctlMust("prune", "--yes", "-t", "test", "-I", "non-existent-tag") doAssertExists(nil, nil) p.KluctlMust("prune", "--yes", "-t", "test", "-I", "cm1") doAssertExists(nil, []string{"cm1"}) - p.deleteKustomizeDeployment("cm2") + p.DeleteKustomizeDeployment("cm2") p.KluctlMust("prune", "--yes", "-t", "test", "-E", "cm2") doAssertExists(nil, nil) - p.deleteKustomizeDeployment("cm3") + p.DeleteKustomizeDeployment("cm3") p.KluctlMust("prune", "--yes", "-t", "test", "--exclude-deployment-dir", "cm3") doAssertExists(nil, []string{"cm2"}) @@ -211,7 +211,7 @@ func TestInclusionDelete(t *testing.T) { } p.KluctlMust("deploy", "--yes", "-t", "test") - doAssertExists(p.listDeploymentItemPathes(".", false), nil) + doAssertExists(p.ListDeploymentItemPathes(".", false), nil) p.KluctlMust("delete", "--yes", "-t", "test", "-I", "non-existent-tag") doAssertExists(nil, nil) @@ -221,7 +221,7 @@ func TestInclusionDelete(t *testing.T) { p.KluctlMust("delete", "--yes", "-t", "test", "-E", "cm2") var a []string - for _, x := range p.listDeploymentItemPathes(".", false) { + for _, x := range p.ListDeploymentItemPathes(".", false) { if x != "cm1" && x != "cm2" { a = append(a, x) } diff --git a/e2e/no_target_test.go b/e2e/no_target_test.go index 433df873d..c3866d521 100644 --- a/e2e/no_target_test.go +++ b/e2e/no_target_test.go @@ -9,23 +9,23 @@ import ( func prepareNoTargetTest(t *testing.T, withDeploymentYaml bool) *TestProject { p := NewTestProject(t, defaultCluster1) - p.mergeKubeconfig(defaultCluster2) + p.MergeKubeconfig(defaultCluster2) - createNamespace(t, defaultCluster1, p.testSlug()) - createNamespace(t, defaultCluster2, p.testSlug()) + createNamespace(t, defaultCluster1, p.TestSlug()) + createNamespace(t, defaultCluster2, p.TestSlug()) cm := createConfigMapObject(map[string]string{ "targetName": `{{ target.name }}`, "targetContext": `{{ target.context }}`, }, resourceOpts{ name: "cm", - namespace: p.testSlug(), + namespace: p.TestSlug(), }) if withDeploymentYaml { - p.addKustomizeDeployment("cm", []kustomizeResource{{name: "cm.yaml", content: cm}}, nil) + p.AddKustomizeDeployment("cm", []kustomizeResource{{name: "cm.yaml", content: cm}}, nil) } else { - p.addKustomizeResources("", []kustomizeResource{{name: "cm.yaml", content: cm}}) + p.AddKustomizeResources("", []kustomizeResource{{name: "cm.yaml", content: cm}}) err := os.Remove(filepath.Join(p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()), "deployment.yml")) assert.NoError(t, err) } @@ -39,22 +39,22 @@ func testNoTarget(t *testing.T, withDeploymentYaml bool) { p := prepareNoTargetTest(t, withDeploymentYaml) p.KluctlMust("deploy", "--yes") - cm := assertConfigMapExists(t, defaultCluster1, p.testSlug(), "cm") - assertConfigMapNotExists(t, defaultCluster2, p.testSlug(), "cm") + cm := assertConfigMapExists(t, defaultCluster1, p.TestSlug(), "cm") + assertConfigMapNotExists(t, defaultCluster2, p.TestSlug(), "cm") assert.Equal(t, map[string]any{ "targetName": "", "targetContext": defaultCluster1.Context, }, cm.Object["data"]) p.KluctlMust("deploy", "--yes", "-T", "override-name") - cm = assertConfigMapExists(t, defaultCluster1, p.testSlug(), "cm") + cm = assertConfigMapExists(t, defaultCluster1, p.TestSlug(), "cm") assert.Equal(t, map[string]any{ "targetName": "override-name", "targetContext": defaultCluster1.Context, }, cm.Object["data"]) p.KluctlMust("deploy", "--yes", "-T", "override-name", "--context", defaultCluster2.Context) - cm = assertConfigMapExists(t, defaultCluster2, p.testSlug(), "cm") + cm = assertConfigMapExists(t, defaultCluster2, p.TestSlug(), "cm") assert.Equal(t, map[string]any{ "targetName": "override-name", "targetContext": defaultCluster2.Context, diff --git a/e2e/project.go b/e2e/project.go index afbb0fc0c..762506d57 100644 --- a/e2e/project.go +++ b/e2e/project.go @@ -35,14 +35,14 @@ func NewTestProject(t *testing.T, k *test_utils.EnvTestCluster) *TestProject { p.gitServer = test_utils.NewGitServer(t) p.gitServer.GitInit(p.getKluctlProjectRepo()) - p.updateKluctlYaml(func(o *uo.UnstructuredObject) error { + p.UpdateKluctlYaml(func(o *uo.UnstructuredObject) error { return nil }) - p.updateDeploymentYaml(".", func(c *uo.UnstructuredObject) error { + p.UpdateDeploymentYaml(".", func(c *uo.UnstructuredObject) error { return nil }) - tmpFile, err := os.CreateTemp("", p.testSlug()+"-kubeconfig-") + tmpFile, err := os.CreateTemp("", p.TestSlug()+"-kubeconfig-") if err != nil { t.Fatal(err) } @@ -52,20 +52,20 @@ func NewTestProject(t *testing.T, k *test_utils.EnvTestCluster) *TestProject { os.Remove(p.mergedKubeconfig) }) if k != nil { - p.mergeKubeconfig(k) + p.MergeKubeconfig(k) } return p } -func (p *TestProject) testSlug() string { +func (p *TestProject) TestSlug() string { n := p.t.Name() n = xstrings.ToKebabCase(n) n = strings.ReplaceAll(n, "/", "-") return n } -func (p *TestProject) mergeKubeconfig(k *test_utils.EnvTestCluster) { - p.updateMergedKubeconfig(func(config *clientcmdapi.Config) { +func (p *TestProject) MergeKubeconfig(k *test_utils.EnvTestCluster) { + p.UpdateMergedKubeconfig(func(config *clientcmdapi.Config) { nkcfg, err := clientcmd.Load(k.Kubeconfig) if err != nil { p.t.Fatal(err) @@ -78,7 +78,7 @@ func (p *TestProject) mergeKubeconfig(k *test_utils.EnvTestCluster) { }) } -func (p *TestProject) updateMergedKubeconfig(cb func(config *clientcmdapi.Config)) { +func (p *TestProject) UpdateMergedKubeconfig(cb func(config *clientcmdapi.Config)) { mkcfg, err := clientcmd.LoadFromFile(p.mergedKubeconfig) if err != nil { p.t.Fatal(err) @@ -92,30 +92,30 @@ func (p *TestProject) updateMergedKubeconfig(cb func(config *clientcmdapi.Config } } -func (p *TestProject) updateKluctlYaml(update func(o *uo.UnstructuredObject) error) { - p.updateYaml(".kluctl.yml", update, "") +func (p *TestProject) UpdateKluctlYaml(update func(o *uo.UnstructuredObject) error) { + p.UpdateYaml(".kluctl.yml", update, "") } -func (p *TestProject) updateDeploymentYaml(dir string, update func(o *uo.UnstructuredObject) error) { - p.updateYaml(filepath.Join(dir, "deployment.yml"), func(o *uo.UnstructuredObject) error { +func (p *TestProject) UpdateDeploymentYaml(dir string, update func(o *uo.UnstructuredObject) error) { + p.UpdateYaml(filepath.Join(dir, "deployment.yml"), func(o *uo.UnstructuredObject) error { if dir == "." { - o.SetNestedField(p.testSlug(), "commonLabels", "project_name") + o.SetNestedField(p.TestSlug(), "commonLabels", "project_name") } return update(o) }, "") } -func (p *TestProject) updateYaml(path string, update func(o *uo.UnstructuredObject) error, message string) { +func (p *TestProject) UpdateYaml(path string, update func(o *uo.UnstructuredObject) error, message string) { p.gitServer.UpdateYaml(p.getKluctlProjectRepo(), path, func(o *uo.UnstructuredObject) error { return update(o) }, message) } -func (p *TestProject) updateFile(path string, update func(f string) (string, error), message string) { +func (p *TestProject) UpdateFile(path string, update func(f string) (string, error), message string) { p.gitServer.UpdateFile(p.getKluctlProjectRepo(), path, update, message) } -func (p *TestProject) getDeploymentYaml(dir string) *uo.UnstructuredObject { +func (p *TestProject) GetDeploymentYaml(dir string) *uo.UnstructuredObject { o, err := uo.FromFile(filepath.Join(p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()), dir, "deployment.yml")) if err != nil { p.t.Fatal(err) @@ -123,9 +123,9 @@ func (p *TestProject) getDeploymentYaml(dir string) *uo.UnstructuredObject { return o } -func (p *TestProject) listDeploymentItemPathes(dir string, fullPath bool) []string { +func (p *TestProject) ListDeploymentItemPathes(dir string, fullPath bool) []string { var ret []string - o := p.getDeploymentYaml(dir) + o := p.GetDeploymentYaml(dir) l, _, err := o.GetNestedObjectList("deployments") if err != nil { p.t.Fatal(err) @@ -141,35 +141,35 @@ func (p *TestProject) listDeploymentItemPathes(dir string, fullPath bool) []stri } pth, ok, _ = x.GetNestedString("include") if ok { - ret = append(ret, p.listDeploymentItemPathes(filepath.Join(dir, pth), fullPath)...) + ret = append(ret, p.ListDeploymentItemPathes(filepath.Join(dir, pth), fullPath)...) } } return ret } -func (p *TestProject) updateKustomizeDeployment(dir string, update func(o *uo.UnstructuredObject, wt *git.Worktree) error) { +func (p *TestProject) UpdateKustomizeDeployment(dir string, update func(o *uo.UnstructuredObject, wt *git.Worktree) error) { wt := p.gitServer.GetWorktree(p.getKluctlProjectRepo()) pth := filepath.Join(dir, "kustomization.yml") - p.updateYaml(pth, func(o *uo.UnstructuredObject) error { + p.UpdateYaml(pth, func(o *uo.UnstructuredObject) error { return update(o, wt) }, fmt.Sprintf("Update kustomization.yml for %s", dir)) } -func (p *TestProject) updateTarget(name string, cb func(target *uo.UnstructuredObject)) { - p.updateNamedListItem(uo.KeyPath{"targets"}, name, cb) +func (p *TestProject) UpdateTarget(name string, cb func(target *uo.UnstructuredObject)) { + p.UpdateNamedListItem(uo.KeyPath{"targets"}, name, cb) } func (p *TestProject) updateSecretSet(name string, cb func(secretSet *uo.UnstructuredObject)) { - p.updateNamedListItem(uo.KeyPath{"secretsConfig", "secretSets"}, name, cb) + p.UpdateNamedListItem(uo.KeyPath{"secretsConfig", "secretSets"}, name, cb) } -func (p *TestProject) updateNamedListItem(path uo.KeyPath, name string, cb func(item *uo.UnstructuredObject)) { +func (p *TestProject) UpdateNamedListItem(path uo.KeyPath, name string, cb func(item *uo.UnstructuredObject)) { if cb == nil { cb = func(target *uo.UnstructuredObject) {} } - p.updateKluctlYaml(func(o *uo.UnstructuredObject) error { + p.UpdateKluctlYaml(func(o *uo.UnstructuredObject) error { l, _, _ := o.GetNestedObjectList(path...) var newList []*uo.UnstructuredObject found := false @@ -194,16 +194,16 @@ func (p *TestProject) updateNamedListItem(path uo.KeyPath, name string, cb func( }) } -func (p *TestProject) updateDeploymentItems(dir string, update func(items []*uo.UnstructuredObject) []*uo.UnstructuredObject) { - p.updateDeploymentYaml(dir, func(o *uo.UnstructuredObject) error { +func (p *TestProject) UpdateDeploymentItems(dir string, update func(items []*uo.UnstructuredObject) []*uo.UnstructuredObject) { + p.UpdateDeploymentYaml(dir, func(o *uo.UnstructuredObject) error { items, _, _ := o.GetNestedObjectList("deployments") items = update(items) return o.SetNestedField(items, "deployments") }) } -func (p *TestProject) addDeploymentItem(dir string, item *uo.UnstructuredObject) { - p.updateDeploymentItems(dir, func(items []*uo.UnstructuredObject) []*uo.UnstructuredObject { +func (p *TestProject) AddDeploymentItem(dir string, item *uo.UnstructuredObject) { + p.UpdateDeploymentItems(dir, func(items []*uo.UnstructuredObject) []*uo.UnstructuredObject { for _, x := range items { if reflect.DeepEqual(x, item) { return items @@ -214,30 +214,30 @@ func (p *TestProject) addDeploymentItem(dir string, item *uo.UnstructuredObject) }) } -func (p *TestProject) addDeploymentInclude(dir string, includePath string, tags []string) { +func (p *TestProject) AddDeploymentInclude(dir string, includePath string, tags []string) { n := uo.FromMap(map[string]interface{}{ "include": includePath, }) if len(tags) != 0 { n.SetNestedField(tags, "tags") } - p.addDeploymentItem(dir, n) + p.AddDeploymentItem(dir, n) } -func (p *TestProject) addDeploymentIncludes(dir string) { +func (p *TestProject) AddDeploymentIncludes(dir string) { var pp []string for _, x := range strings.Split(dir, "/") { if x != "." { - p.addDeploymentInclude(filepath.Join(pp...), x, nil) + p.AddDeploymentInclude(filepath.Join(pp...), x, nil) } pp = append(pp, x) } } -func (p *TestProject) addKustomizeDeployment(dir string, resources []kustomizeResource, tags []string) { +func (p *TestProject) AddKustomizeDeployment(dir string, resources []kustomizeResource, tags []string) { deploymentDir := filepath.Dir(dir) if deploymentDir != "" { - p.addDeploymentIncludes(deploymentDir) + p.AddDeploymentIncludes(deploymentDir) } absKustomizeDir := filepath.Join(p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()), dir) @@ -247,14 +247,14 @@ func (p *TestProject) addKustomizeDeployment(dir string, resources []kustomizeRe p.t.Fatal(err) } - p.updateKustomizeDeployment(dir, func(o *uo.UnstructuredObject, wt *git.Worktree) error { + p.UpdateKustomizeDeployment(dir, func(o *uo.UnstructuredObject, wt *git.Worktree) error { o.SetNestedField("kustomize.config.k8s.io/v1beta1", "apiVersion") o.SetNestedField("Kustomization", "kind") return nil }) - p.addKustomizeResources(dir, resources) - p.updateDeploymentYaml(deploymentDir, func(o *uo.UnstructuredObject) error { + p.AddKustomizeResources(dir, resources) + p.UpdateDeploymentYaml(deploymentDir, func(o *uo.UnstructuredObject) error { d, _, _ := o.GetNestedObjectList("deployments") n := uo.FromMap(map[string]interface{}{ "path": filepath.Base(dir), @@ -294,8 +294,8 @@ type kustomizeResource struct { content interface{} } -func (p *TestProject) addKustomizeResources(dir string, resources []kustomizeResource) { - p.updateKustomizeDeployment(dir, func(o *uo.UnstructuredObject, wt *git.Worktree) error { +func (p *TestProject) AddKustomizeResources(dir string, resources []kustomizeResource) { + p.UpdateKustomizeDeployment(dir, func(o *uo.UnstructuredObject, wt *git.Worktree) error { l, _, _ := o.GetNestedList("resources") for _, r := range resources { l = append(l, r.name) @@ -320,9 +320,9 @@ func (p *TestProject) addKustomizeResources(dir string, resources []kustomizeRes }) } -func (p *TestProject) deleteKustomizeDeployment(dir string) { +func (p *TestProject) DeleteKustomizeDeployment(dir string) { deploymentDir := filepath.Dir(dir) - p.updateDeploymentItems(deploymentDir, func(items []*uo.UnstructuredObject) []*uo.UnstructuredObject { + p.UpdateDeploymentItems(deploymentDir, func(items []*uo.UnstructuredObject) []*uo.UnstructuredObject { var newItems []*uo.UnstructuredObject for _, item := range items { pth, _, _ := item.GetNestedString("path") diff --git a/e2e/seal_test.go b/e2e/seal_test.go index bf64c78a8..441281150 100644 --- a/e2e/seal_test.go +++ b/e2e/seal_test.go @@ -104,12 +104,12 @@ func prepareSealTest(t *testing.T, k *test_utils.EnvTestCluster, secrets map[str addProxyVars(p) } - createNamespace(t, k, p.testSlug()) + createNamespace(t, k, p.TestSlug()) addSecretsSet(p, "test", varsSources) addSecretsSetToTarget(p, "test-target", "test") - addSecretDeployment(p, "secret-deployment", secrets, resourceOpts{name: "secret", namespace: p.testSlug()}) + addSecretDeployment(p, "secret-deployment", secrets, resourceOpts{name: "secret", namespace: p.TestSlug()}) return p } @@ -121,7 +121,7 @@ func addSecretsSet(p *TestProject, name string, varsSources []*uo.UnstructuredOb } func addSecretsSetToTarget(p *TestProject, targetName string, secretSetName string) { - p.updateTarget(targetName, func(target *uo.UnstructuredObject) { + p.UpdateTarget(targetName, func(target *uo.UnstructuredObject) { l, _, _ := target.GetNestedList("sealingConfig", "secretSets") l = append(l, secretSetName) _ = target.SetNestedField(l, "sealingConfig", "secretSets") @@ -182,7 +182,7 @@ func TestSeal_WithOperator(t *testing.T) { assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) p.KluctlMust("deploy", "--yes", "-t", "test-target") - assertSealedSecret(t, k, p.testSlug(), "secret", certServer1.certHash, map[string]string{ + assertSealedSecret(t, k, p.TestSlug(), "secret", certServer1.certHash, map[string]string{ "s1": "v1", "s2": "v2", }) @@ -230,7 +230,7 @@ func TestSeal_WithBootstrap(t *testing.T) { certHash, err := seal.HashPublicKey(cert) assert.NoError(t, err) - assertSealedSecret(t, k, p.testSlug(), "secret", certHash, map[string]string{ + assertSealedSecret(t, k, p.TestSlug(), "secret", certHash, map[string]string{ "s1": "v1", "s2": "v2", }) @@ -266,7 +266,7 @@ func TestSeal_MultipleVarSources(t *testing.T) { p.KluctlMust("deploy", "--yes", "-t", "test-target") - assertSealedSecret(t, k, p.testSlug(), "secret", certServer1.certHash, map[string]string{ + assertSealedSecret(t, k, p.TestSlug(), "secret", certServer1.certHash, map[string]string{ "s1": "v1", "s2": "v2", }) @@ -306,7 +306,7 @@ func TestSeal_MultipleSecretSets(t *testing.T) { p.KluctlMust("deploy", "--yes", "-t", "test-target") - assertSealedSecret(t, k, p.testSlug(), "secret", certServer1.certHash, map[string]string{ + assertSealedSecret(t, k, p.TestSlug(), "secret", certServer1.certHash, map[string]string{ "s1": "v1", "s2": "v2", }) @@ -341,12 +341,12 @@ func TestSeal_MultipleTargets(t *testing.T) { }) addSecretsSetToTarget(p, "test-target2", "test2") - p.mergeKubeconfig(defaultCluster2) - createNamespace(t, defaultCluster2, p.testSlug()) - p.updateTarget("test-target", func(target *uo.UnstructuredObject) { + p.MergeKubeconfig(defaultCluster2) + createNamespace(t, defaultCluster2, p.TestSlug()) + p.UpdateTarget("test-target", func(target *uo.UnstructuredObject) { _ = target.SetNestedField(defaultCluster1.Context, "context") }) - p.updateTarget("test-target2", func(target *uo.UnstructuredObject) { + p.UpdateTarget("test-target2", func(target *uo.UnstructuredObject) { _ = target.SetNestedField(defaultCluster2.Context, "context") }) @@ -360,11 +360,11 @@ func TestSeal_MultipleTargets(t *testing.T) { p.KluctlMust("deploy", "--yes", "-t", "test-target") p.KluctlMust("deploy", "--yes", "-t", "test-target2") - assertSealedSecret(t, defaultCluster1, p.testSlug(), "secret", certServer1.certHash, map[string]string{ + assertSealedSecret(t, defaultCluster1, p.TestSlug(), "secret", certServer1.certHash, map[string]string{ "s1": "v1", "s2": "v2", }) - assertSealedSecret(t, defaultCluster2, p.testSlug(), "secret", certServer2.certHash, map[string]string{ + assertSealedSecret(t, defaultCluster2, p.TestSlug(), "secret", certServer2.certHash, map[string]string{ "s1": "v3", "s2": "v4", }) @@ -392,7 +392,7 @@ func TestSeal_MultipleSecrets(t *testing.T) { }, }), }, true) - addSecretDeployment(p, "secret-deployment2", secret2, resourceOpts{name: "secret2", namespace: p.testSlug()}) + addSecretDeployment(p, "secret-deployment2", secret2, resourceOpts{name: "secret2", namespace: p.TestSlug()}) p.KluctlMust("seal", "-t", "test-target") @@ -402,10 +402,10 @@ func TestSeal_MultipleSecrets(t *testing.T) { p.KluctlMust("deploy", "--yes", "-t", "test-target") - assertSealedSecret(t, k, p.testSlug(), "secret", certServer1.certHash, map[string]string{ + assertSealedSecret(t, k, p.TestSlug(), "secret", certServer1.certHash, map[string]string{ "s1": "v1", }) - assertSealedSecret(t, k, p.testSlug(), "secret2", certServer1.certHash, map[string]string{ + assertSealedSecret(t, k, p.TestSlug(), "secret2", certServer1.certHash, map[string]string{ "s2": "v2", }) } @@ -433,7 +433,7 @@ func TestSeal_MultipleSecretsInOneFile(t *testing.T) { }), }, true) - secret2Text, _ := yaml.WriteYamlString(createSecretObject(secret2, resourceOpts{name: "secret2", namespace: p.testSlug()})) + secret2Text, _ := yaml.WriteYamlString(createSecretObject(secret2, resourceOpts{name: "secret2", namespace: p.TestSlug()})) p.gitServer.UpdateFile(p.getKluctlProjectRepo(), "secret-deployment/secret-secret.yml.sealme", func(f string) (string, error) { f += "---\n" @@ -448,10 +448,10 @@ func TestSeal_MultipleSecretsInOneFile(t *testing.T) { p.KluctlMust("deploy", "--yes", "-t", "test-target") - assertSealedSecret(t, k, p.testSlug(), "secret", certServer1.certHash, map[string]string{ + assertSealedSecret(t, k, p.TestSlug(), "secret", certServer1.certHash, map[string]string{ "s1": "v1", }) - assertSealedSecret(t, k, p.testSlug(), "secret2", certServer1.certHash, map[string]string{ + assertSealedSecret(t, k, p.TestSlug(), "secret2", certServer1.certHash, map[string]string{ "s2": "v2", }) } @@ -472,7 +472,7 @@ func TestSeal_File(t *testing.T) { }), }, true) - p.updateYaml("secret-values.yaml", func(o *uo.UnstructuredObject) error { + p.UpdateYaml("secret-values.yaml", func(o *uo.UnstructuredObject) error { *o = *uo.FromMap(map[string]interface{}{ "secrets": map[string]interface{}{ "s1": "v1", @@ -489,7 +489,7 @@ func TestSeal_File(t *testing.T) { p.KluctlMust("deploy", "--yes", "-t", "test-target") - assertSealedSecret(t, k, p.testSlug(), "secret", certServer1.certHash, map[string]string{ + assertSealedSecret(t, k, p.TestSlug(), "secret", certServer1.certHash, map[string]string{ "s1": "v1", "s2": "v2", }) @@ -544,7 +544,7 @@ func TestSeal_Vault(t *testing.T) { p.KluctlMust("deploy", "--yes", "-t", "test-target") - assertSealedSecret(t, k, p.testSlug(), "secret", certServer1.certHash, map[string]string{ + assertSealedSecret(t, k, p.TestSlug(), "secret", certServer1.certHash, map[string]string{ "s1": "v1", "s2": "v2", }) diff --git a/e2e/sops_test.go b/e2e/sops_test.go index c7e3f127f..0e4e6bbf4 100644 --- a/e2e/sops_test.go +++ b/e2e/sops_test.go @@ -15,17 +15,17 @@ func TestSopsVars(t *testing.T) { p := NewTestProject(t, k) - createNamespace(t, k, p.testSlug()) + createNamespace(t, k, p.TestSlug()) - p.updateTarget("test", nil) + p.UpdateTarget("test", nil) addConfigMapDeployment(p, "cm", map[string]string{ "v1": "{{ test1.test2 }}", }, resourceOpts{ name: "cm", - namespace: p.testSlug(), + namespace: p.TestSlug(), }) - p.updateDeploymentYaml("", func(o *uo.UnstructuredObject) error { + p.UpdateDeploymentYaml("", func(o *uo.UnstructuredObject) error { _ = o.SetNestedField([]map[string]any{ { "file": "encrypted-vars.yaml", @@ -34,14 +34,14 @@ func TestSopsVars(t *testing.T) { return nil }) - p.updateFile("encrypted-vars.yaml", func(f string) (string, error) { + p.UpdateFile("encrypted-vars.yaml", func(f string) (string, error) { b, _ := sops_test_resources.TestResources.ReadFile("test.yaml") return string(b), nil }, "") p.KluctlMust("deploy", "--yes", "-t", "test") - cm := assertConfigMapExists(t, k, p.testSlug(), "cm") + cm := assertConfigMapExists(t, k, p.TestSlug(), "cm") assertNestedFieldEquals(t, cm, map[string]any{ "v1": "42", }, "data") @@ -55,26 +55,26 @@ func TestSopsResources(t *testing.T) { p := NewTestProject(t, k) - createNamespace(t, k, p.testSlug()) + createNamespace(t, k, p.TestSlug()) - p.updateTarget("test", nil) - p.updateDeploymentYaml("", func(o *uo.UnstructuredObject) error { - _ = o.SetNestedField(p.testSlug(), "overrideNamespace") + p.UpdateTarget("test", nil) + p.UpdateDeploymentYaml("", func(o *uo.UnstructuredObject) error { + _ = o.SetNestedField(p.TestSlug(), "overrideNamespace") return nil }) - p.addKustomizeDeployment("cm", []kustomizeResource{ + p.AddKustomizeDeployment("cm", []kustomizeResource{ {name: "encrypted-cm.yaml"}, }, nil) - p.updateFile("cm/encrypted-cm.yaml", func(f string) (string, error) { + p.UpdateFile("cm/encrypted-cm.yaml", func(f string) (string, error) { b, _ := sops_test_resources.TestResources.ReadFile("test-configmap.yaml") return string(b), nil }, "") p.KluctlMust("deploy", "--yes", "-t", "test") - cm := assertConfigMapExists(t, k, p.testSlug(), "encrypted-cm") + cm := assertConfigMapExists(t, k, p.TestSlug(), "encrypted-cm") assertNestedFieldEquals(t, cm, map[string]any{ "a": "b", }, "data") diff --git a/e2e/utils_resources.go b/e2e/utils_resources.go index 9c8f03fae..4df587ec1 100644 --- a/e2e/utils_resources.go +++ b/e2e/utils_resources.go @@ -53,7 +53,7 @@ func createSecretObject(data map[string]string, opts resourceOpts) *uo.Unstructu func addConfigMapDeployment(p *TestProject, dir string, data map[string]string, opts resourceOpts) { o := createConfigMapObject(data, opts) - p.addKustomizeDeployment(dir, []kustomizeResource{ + p.AddKustomizeDeployment(dir, []kustomizeResource{ {fmt.Sprintf("configmap-%s.yml", opts.name), "", o}, }, opts.tags) } @@ -61,7 +61,7 @@ func addConfigMapDeployment(p *TestProject, dir string, data map[string]string, func addSecretDeployment(p *TestProject, dir string, data map[string]string, opts resourceOpts) { o := createSecretObject(data, opts) fname := fmt.Sprintf("secret-%s.yml", opts.name) - p.addKustomizeDeployment(dir, []kustomizeResource{ + p.AddKustomizeDeployment(dir, []kustomizeResource{ {fname, fname + ".sealme", o}, }, opts.tags) } From 65bfbf428501ecd375d7d697b9690e10223f841c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 28 Nov 2022 16:55:55 +0100 Subject: [PATCH 1240/2916] tests: KustomizeResource public --- e2e/helm_test.go | 4 ++-- e2e/hooks_test.go | 2 +- e2e/no_target_test.go | 4 ++-- e2e/project.go | 22 +++++++++++----------- e2e/sops_test.go | 4 ++-- e2e/utils_resources.go | 4 ++-- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/e2e/helm_test.go b/e2e/helm_test.go index f973570be..786ae8487 100644 --- a/e2e/helm_test.go +++ b/e2e/helm_test.go @@ -26,8 +26,8 @@ func addHelmDeployment(p *TestProject, dir string, repoUrl string, chartName, ve chartName = "" } - p.AddKustomizeDeployment(dir, []kustomizeResource{ - {name: "helm-rendered.yaml"}, + p.AddKustomizeDeployment(dir, []KustomizeResource{ + {Name: "helm-rendered.yaml"}, }, nil) p.UpdateYaml(filepath.Join(dir, "helm-chart.yaml"), func(o *uo.UnstructuredObject) error { diff --git a/e2e/hooks_test.go b/e2e/hooks_test.go index e9e4ae433..2d57815bd 100644 --- a/e2e/hooks_test.go +++ b/e2e/hooks_test.go @@ -87,7 +87,7 @@ func (s *hooksTestContext) addConfigMap(dir string, opts resourceOpts) { o.SetK8sGVKs("", "v1", "ConfigMap") mergeMetadata(o, opts) o.SetNestedField(map[string]interface{}{}, "data") - s.p.AddKustomizeResources(dir, []kustomizeResource{ + s.p.AddKustomizeResources(dir, []KustomizeResource{ {fmt.Sprintf("%s.yml", opts.name), "", o}, }) } diff --git a/e2e/no_target_test.go b/e2e/no_target_test.go index c3866d521..367041be0 100644 --- a/e2e/no_target_test.go +++ b/e2e/no_target_test.go @@ -23,9 +23,9 @@ func prepareNoTargetTest(t *testing.T, withDeploymentYaml bool) *TestProject { }) if withDeploymentYaml { - p.AddKustomizeDeployment("cm", []kustomizeResource{{name: "cm.yaml", content: cm}}, nil) + p.AddKustomizeDeployment("cm", []KustomizeResource{{Name: "cm.yaml", Content: cm}}, nil) } else { - p.AddKustomizeResources("", []kustomizeResource{{name: "cm.yaml", content: cm}}) + p.AddKustomizeResources("", []KustomizeResource{{Name: "cm.yaml", Content: cm}}) err := os.Remove(filepath.Join(p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()), "deployment.yml")) assert.NoError(t, err) } diff --git a/e2e/project.go b/e2e/project.go index 762506d57..25ee4be57 100644 --- a/e2e/project.go +++ b/e2e/project.go @@ -234,7 +234,7 @@ func (p *TestProject) AddDeploymentIncludes(dir string) { } } -func (p *TestProject) AddKustomizeDeployment(dir string, resources []kustomizeResource, tags []string) { +func (p *TestProject) AddKustomizeDeployment(dir string, resources []KustomizeResource, tags []string) { deploymentDir := filepath.Dir(dir) if deploymentDir != "" { p.AddDeploymentIncludes(deploymentDir) @@ -288,23 +288,23 @@ func (p *TestProject) convertInterfaceToList(x interface{}) []interface{} { return []interface{}{x} } -type kustomizeResource struct { - name string - fileName string - content interface{} +type KustomizeResource struct { + Name string + FileName string + Content interface{} } -func (p *TestProject) AddKustomizeResources(dir string, resources []kustomizeResource) { +func (p *TestProject) AddKustomizeResources(dir string, resources []KustomizeResource) { p.UpdateKustomizeDeployment(dir, func(o *uo.UnstructuredObject, wt *git.Worktree) error { l, _, _ := o.GetNestedList("resources") for _, r := range resources { - l = append(l, r.name) - fileName := r.fileName + l = append(l, r.Name) + fileName := r.FileName if fileName == "" { - fileName = r.name + fileName = r.Name } - if r.content != nil { - x := p.convertInterfaceToList(r.content) + if r.Content != nil { + x := p.convertInterfaceToList(r.Content) err := yaml.WriteYamlAllFile(filepath.Join(p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()), dir, fileName), x) if err != nil { return err diff --git a/e2e/sops_test.go b/e2e/sops_test.go index 0e4e6bbf4..726c29b1b 100644 --- a/e2e/sops_test.go +++ b/e2e/sops_test.go @@ -63,8 +63,8 @@ func TestSopsResources(t *testing.T) { return nil }) - p.AddKustomizeDeployment("cm", []kustomizeResource{ - {name: "encrypted-cm.yaml"}, + p.AddKustomizeDeployment("cm", []KustomizeResource{ + {Name: "encrypted-cm.yaml"}, }, nil) p.UpdateFile("cm/encrypted-cm.yaml", func(f string) (string, error) { diff --git a/e2e/utils_resources.go b/e2e/utils_resources.go index 4df587ec1..83a5df3e8 100644 --- a/e2e/utils_resources.go +++ b/e2e/utils_resources.go @@ -53,7 +53,7 @@ func createSecretObject(data map[string]string, opts resourceOpts) *uo.Unstructu func addConfigMapDeployment(p *TestProject, dir string, data map[string]string, opts resourceOpts) { o := createConfigMapObject(data, opts) - p.AddKustomizeDeployment(dir, []kustomizeResource{ + p.AddKustomizeDeployment(dir, []KustomizeResource{ {fmt.Sprintf("configmap-%s.yml", opts.name), "", o}, }, opts.tags) } @@ -61,7 +61,7 @@ func addConfigMapDeployment(p *TestProject, dir string, data map[string]string, func addSecretDeployment(p *TestProject, dir string, data map[string]string, opts resourceOpts) { o := createSecretObject(data, opts) fname := fmt.Sprintf("secret-%s.yml", opts.name) - p.AddKustomizeDeployment(dir, []kustomizeResource{ + p.AddKustomizeDeployment(dir, []KustomizeResource{ {fname, fname + ".sealme", o}, }, opts.tags) } From d12955dd49a41904390aa3d0c916732ac7148f39 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 28 Nov 2022 17:05:32 +0100 Subject: [PATCH 1241/2916] tests: Simplify TestProject to only support one git repo --- e2e/helm_test.go | 18 +++++++++--------- e2e/no_target_test.go | 2 +- e2e/project.go | 28 ++++++++++++++++++---------- e2e/seal_test.go | 20 ++++++++++---------- e2e/test-utils/git_server.go | 2 +- pkg/vars/vars_loader_test.go | 4 ++-- 6 files changed, 41 insertions(+), 33 deletions(-) diff --git a/e2e/helm_test.go b/e2e/helm_test.go index 786ae8487..0110c3689 100644 --- a/e2e/helm_test.go +++ b/e2e/helm_test.go @@ -105,7 +105,7 @@ func testHelmPull(t *testing.T, tc testCase, prePull bool) { return } else { assert.NoError(t, err) - assert.FileExists(t, filepath.Join(p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()), "helm1/charts/test-chart1/Chart.yaml")) + assert.FileExists(t, filepath.Join(p.LocalRepoDir(), "helm1/charts/test-chart1/Chart.yaml")) } } @@ -171,7 +171,7 @@ func testHelmManualUpgrade(t *testing.T, oci bool) { addHelmDeployment(p, "helm1", repoUrl, "test-chart1", "0.1.0", "test-helm1", p.TestSlug(), nil) p.KluctlMust("helm-pull") - assert.FileExists(t, filepath.Join(p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()), "helm1/charts/test-chart1/Chart.yaml")) + assert.FileExists(t, filepath.Join(p.LocalRepoDir(), "helm1/charts/test-chart1/Chart.yaml")) p.KluctlMust("deploy", "--yes", "-t", "test") cm := assertConfigMapExists(t, k, p.TestSlug(), "test-helm1-test-chart1") v, _, _ := cm.GetNestedString("data", "version") @@ -224,9 +224,9 @@ func testHelmUpdate(t *testing.T, oci bool, upgrade bool, commit bool) { }, "") p.KluctlMust("helm-pull") - assert.FileExists(t, filepath.Join(p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()), "helm1/charts/test-chart1/Chart.yaml")) - assert.FileExists(t, filepath.Join(p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()), "helm2/charts/test-chart2/Chart.yaml")) - assert.FileExists(t, filepath.Join(p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()), "helm3/charts/test-chart1/Chart.yaml")) + assert.FileExists(t, filepath.Join(p.LocalRepoDir(), "helm1/charts/test-chart1/Chart.yaml")) + assert.FileExists(t, filepath.Join(p.LocalRepoDir(), "helm2/charts/test-chart2/Chart.yaml")) + assert.FileExists(t, filepath.Join(p.LocalRepoDir(), "helm3/charts/test-chart1/Chart.yaml")) args := []string{"helm-update"} if upgrade { @@ -241,11 +241,11 @@ func testHelmUpdate(t *testing.T, oci bool, upgrade bool, commit bool) { assert.Contains(t, stderr, "helm2: Chart has new version 0.3.0 available.") assert.Contains(t, stderr, "helm3: Chart has new version 0.2.0 available. Old version is 0.1.0. skipUpdate is set to true.") - c1, err := uo.FromFile(filepath.Join(p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()), "helm1/charts/test-chart1/Chart.yaml")) + c1, err := uo.FromFile(filepath.Join(p.LocalRepoDir(), "helm1/charts/test-chart1/Chart.yaml")) assert.NoError(t, err) - c2, err := uo.FromFile(filepath.Join(p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()), "helm2/charts/test-chart2/Chart.yaml")) + c2, err := uo.FromFile(filepath.Join(p.LocalRepoDir(), "helm2/charts/test-chart2/Chart.yaml")) assert.NoError(t, err) - c3, err := uo.FromFile(filepath.Join(p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()), "helm3/charts/test-chart1/Chart.yaml")) + c3, err := uo.FromFile(filepath.Join(p.LocalRepoDir(), "helm3/charts/test-chart1/Chart.yaml")) assert.NoError(t, err) v1, _, _ := c1.GetNestedString("version") @@ -262,7 +262,7 @@ func testHelmUpdate(t *testing.T, oci bool, upgrade bool, commit bool) { } if commit { - r := p.gitServer.GetGitRepo(p.getKluctlProjectRepo()) + r := p.GetGitRepo() commits, err := r.Log(&git.LogOptions{}) assert.NoError(t, err) diff --git a/e2e/no_target_test.go b/e2e/no_target_test.go index 367041be0..5d92a5c6c 100644 --- a/e2e/no_target_test.go +++ b/e2e/no_target_test.go @@ -26,7 +26,7 @@ func prepareNoTargetTest(t *testing.T, withDeploymentYaml bool) *TestProject { p.AddKustomizeDeployment("cm", []KustomizeResource{{Name: "cm.yaml", Content: cm}}, nil) } else { p.AddKustomizeResources("", []KustomizeResource{{Name: "cm.yaml", Content: cm}}) - err := os.Remove(filepath.Join(p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()), "deployment.yml")) + err := os.Remove(filepath.Join(p.LocalRepoDir(), "deployment.yml")) assert.NoError(t, err) } diff --git a/e2e/project.go b/e2e/project.go index 25ee4be57..4e1a4a14d 100644 --- a/e2e/project.go +++ b/e2e/project.go @@ -33,7 +33,7 @@ func NewTestProject(t *testing.T, k *test_utils.EnvTestCluster) *TestProject { } p.gitServer = test_utils.NewGitServer(t) - p.gitServer.GitInit(p.getKluctlProjectRepo()) + p.gitServer.GitInit("kluctl-project") p.UpdateKluctlYaml(func(o *uo.UnstructuredObject) error { return nil @@ -106,17 +106,17 @@ func (p *TestProject) UpdateDeploymentYaml(dir string, update func(o *uo.Unstruc } func (p *TestProject) UpdateYaml(path string, update func(o *uo.UnstructuredObject) error, message string) { - p.gitServer.UpdateYaml(p.getKluctlProjectRepo(), path, func(o *uo.UnstructuredObject) error { + p.gitServer.UpdateYaml("kluctl-project", path, func(o *uo.UnstructuredObject) error { return update(o) }, message) } func (p *TestProject) UpdateFile(path string, update func(f string) (string, error), message string) { - p.gitServer.UpdateFile(p.getKluctlProjectRepo(), path, update, message) + p.gitServer.UpdateFile("kluctl-project", path, update, message) } func (p *TestProject) GetDeploymentYaml(dir string) *uo.UnstructuredObject { - o, err := uo.FromFile(filepath.Join(p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()), dir, "deployment.yml")) + o, err := uo.FromFile(filepath.Join(p.LocalRepoDir(), dir, "deployment.yml")) if err != nil { p.t.Fatal(err) } @@ -148,7 +148,7 @@ func (p *TestProject) ListDeploymentItemPathes(dir string, fullPath bool) []stri } func (p *TestProject) UpdateKustomizeDeployment(dir string, update func(o *uo.UnstructuredObject, wt *git.Worktree) error) { - wt := p.gitServer.GetWorktree(p.getKluctlProjectRepo()) + wt := p.gitServer.GetWorktree("kluctl-project") pth := filepath.Join(dir, "kustomization.yml") p.UpdateYaml(pth, func(o *uo.UnstructuredObject) error { @@ -240,7 +240,7 @@ func (p *TestProject) AddKustomizeDeployment(dir string, resources []KustomizeRe p.AddDeploymentIncludes(deploymentDir) } - absKustomizeDir := filepath.Join(p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()), dir) + absKustomizeDir := filepath.Join(p.LocalRepoDir(), dir) err := os.MkdirAll(absKustomizeDir, 0o700) if err != nil { @@ -305,7 +305,7 @@ func (p *TestProject) AddKustomizeResources(dir string, resources []KustomizeRes } if r.Content != nil { x := p.convertInterfaceToList(r.Content) - err := yaml.WriteYamlAllFile(filepath.Join(p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()), dir, fileName), x) + err := yaml.WriteYamlAllFile(filepath.Join(p.LocalRepoDir(), dir, fileName), x) if err != nil { return err } @@ -335,8 +335,16 @@ func (p *TestProject) DeleteKustomizeDeployment(dir string) { }) } -func (p *TestProject) getKluctlProjectRepo() string { - return "kluctl-project" +func (p *TestProject) GitUrl() string { + return p.gitServer.GitUrl("kluctl-project") +} + +func (p *TestProject) LocalRepoDir() string { + return p.gitServer.LocalRepoDir("kluctl-project") +} + +func (p *TestProject) GetGitRepo() *git.Repository { + return p.gitServer.GetGitRepo("kluctl-project") } func (p *TestProject) Kluctl(argsIn ...string) (string, string, error) { @@ -344,7 +352,7 @@ func (p *TestProject) Kluctl(argsIn ...string) (string, string, error) { args = append(args, argsIn...) args = append(args, "--no-update-check") - cwd := p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()) + cwd := p.LocalRepoDir() args = append(args, "--debug") diff --git a/e2e/seal_test.go b/e2e/seal_test.go index 441281150..d80be37b3 100644 --- a/e2e/seal_test.go +++ b/e2e/seal_test.go @@ -178,7 +178,7 @@ func TestSeal_WithOperator(t *testing.T) { p.KluctlMust("seal", "-t", "test-target") - sealedSecretsDir := p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()) + sealedSecretsDir := p.LocalRepoDir() assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) p.KluctlMust("deploy", "--yes", "-t", "test-target") @@ -213,7 +213,7 @@ func TestSeal_WithBootstrap(t *testing.T) { p.KluctlMust("seal", "-t", "test-target") - sealedSecretsDir := p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()) + sealedSecretsDir := p.LocalRepoDir() assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) test_resources.ApplyYaml("sealed-secrets.yaml", k) @@ -261,7 +261,7 @@ func TestSeal_MultipleVarSources(t *testing.T) { p.KluctlMust("seal", "-t", "test-target") - sealedSecretsDir := p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()) + sealedSecretsDir := p.LocalRepoDir() assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) p.KluctlMust("deploy", "--yes", "-t", "test-target") @@ -301,7 +301,7 @@ func TestSeal_MultipleSecretSets(t *testing.T) { p.KluctlMust("seal", "-t", "test-target") - sealedSecretsDir := p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()) + sealedSecretsDir := p.LocalRepoDir() assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) p.KluctlMust("deploy", "--yes", "-t", "test-target") @@ -353,7 +353,7 @@ func TestSeal_MultipleTargets(t *testing.T) { p.KluctlMust("seal", "-t", "test-target") p.KluctlMust("seal", "-t", "test-target2") - sealedSecretsDir := p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()) + sealedSecretsDir := p.LocalRepoDir() assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target2/secret-secret.yml")) @@ -396,7 +396,7 @@ func TestSeal_MultipleSecrets(t *testing.T) { p.KluctlMust("seal", "-t", "test-target") - sealedSecretsDir := p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()) + sealedSecretsDir := p.LocalRepoDir() assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment2/test-target/secret-secret2.yml")) @@ -435,7 +435,7 @@ func TestSeal_MultipleSecretsInOneFile(t *testing.T) { secret2Text, _ := yaml.WriteYamlString(createSecretObject(secret2, resourceOpts{name: "secret2", namespace: p.TestSlug()})) - p.gitServer.UpdateFile(p.getKluctlProjectRepo(), "secret-deployment/secret-secret.yml.sealme", func(f string) (string, error) { + p.UpdateFile("secret-deployment/secret-secret.yml.sealme", func(f string) (string, error) { f += "---\n" f += secret2Text return f, nil @@ -443,7 +443,7 @@ func TestSeal_MultipleSecretsInOneFile(t *testing.T) { p.KluctlMust("seal", "-t", "test-target") - sealedSecretsDir := p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()) + sealedSecretsDir := p.LocalRepoDir() assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) p.KluctlMust("deploy", "--yes", "-t", "test-target") @@ -484,7 +484,7 @@ func TestSeal_File(t *testing.T) { p.KluctlMust("seal", "-t", "test-target") - sealedSecretsDir := p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()) + sealedSecretsDir := p.LocalRepoDir() assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) p.KluctlMust("deploy", "--yes", "-t", "test-target") @@ -539,7 +539,7 @@ func TestSeal_Vault(t *testing.T) { p.extraEnv = append(p.extraEnv, "VAULT_TOKEN=root") p.KluctlMust("seal", "-t", "test-target") - sealedSecretsDir := p.gitServer.LocalRepoDir(p.getKluctlProjectRepo()) + sealedSecretsDir := p.LocalRepoDir() assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) p.KluctlMust("deploy", "--yes", "-t", "test-target") diff --git a/e2e/test-utils/git_server.go b/e2e/test-utils/git_server.go index f539dac91..6e45a3031 100644 --- a/e2e/test-utils/git_server.go +++ b/e2e/test-utils/git_server.go @@ -235,7 +235,7 @@ func (p *GitServer) convertInterfaceToList(x interface{}) []interface{} { return []interface{}{x} } -func (p *GitServer) LocalGitUrl(repo string) string { +func (p *GitServer) GitUrl(repo string) string { return fmt.Sprintf("http://localhost:%d/%s/.git", p.gitServerPort, repo) } diff --git a/pkg/vars/vars_loader_test.go b/pkg/vars/vars_loader_test.go index 9b46900c8..fc0d510e0 100644 --- a/pkg/vars/vars_loader_test.go +++ b/pkg/vars/vars_loader_test.go @@ -163,7 +163,7 @@ func TestVarsLoader_Git(t *testing.T) { }, "") testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { - url, _ := git_url.Parse(gs.LocalGitUrl("repo")) + url, _ := git_url.Parse(gs.GitUrl("repo")) err := vl.LoadVars(vc, &types.VarsSource{ Git: &types.VarsSourceGit{ Url: *url, @@ -199,7 +199,7 @@ func TestVarsLoader_GitBranch(t *testing.T) { assert.NoError(t, err) testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { - url, _ := git_url.Parse(gs.LocalGitUrl("repo")) + url, _ := git_url.Parse(gs.GitUrl("repo")) err = vl.LoadVars(vc, &types.VarsSource{ Git: &types.VarsSourceGit{ Url: *url, From ef3c11d54670b529415dfc3ccb60e66e5ab6489d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 28 Nov 2022 17:07:19 +0100 Subject: [PATCH 1242/2916] tests: Implement AddExtraEnv helper --- e2e/project.go | 4 ++++ e2e/seal_test.go | 12 ++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/e2e/project.go b/e2e/project.go index 4e1a4a14d..8b4cc5364 100644 --- a/e2e/project.go +++ b/e2e/project.go @@ -92,6 +92,10 @@ func (p *TestProject) UpdateMergedKubeconfig(cb func(config *clientcmdapi.Config } } +func (p *TestProject) AddExtraEnv(e string) { + p.extraEnv = append(p.extraEnv, e) +} + func (p *TestProject) UpdateKluctlYaml(update func(o *uo.UnstructuredObject) error) { p.UpdateYaml(".kluctl.yml", update, "") } diff --git a/e2e/seal_test.go b/e2e/seal_test.go index d80be37b3..ab4b87d4c 100644 --- a/e2e/seal_test.go +++ b/e2e/seal_test.go @@ -87,11 +87,11 @@ func startCertServer() (*certServer, error) { func addProxyVars(p *TestProject) { f := func(idx int, k *test_utils.EnvTestCluster, cs *certServer) { - p.extraEnv = append(p.extraEnv, fmt.Sprintf("KLUCTL_K8S_SERVICE_PROXY_%d_API_HOST=%s", idx, k.RESTConfig().Host)) - p.extraEnv = append(p.extraEnv, fmt.Sprintf("KLUCTL_K8S_SERVICE_PROXY_%d_SERVICE_NAMESPACE=%s", idx, "kube-system")) - p.extraEnv = append(p.extraEnv, fmt.Sprintf("KLUCTL_K8S_SERVICE_PROXY_%d_SERVICE_NAME=%s", idx, "sealed-secrets-controller")) - p.extraEnv = append(p.extraEnv, fmt.Sprintf("KLUCTL_K8S_SERVICE_PROXY_%d_SERVICE_PORT=%s", idx, "http")) - p.extraEnv = append(p.extraEnv, fmt.Sprintf("KLUCTL_K8S_SERVICE_PROXY_%d_LOCAL_URL=%s", idx, cs.url)) + p.AddExtraEnv(fmt.Sprintf("KLUCTL_K8S_SERVICE_PROXY_%d_API_HOST=%s", idx, k.RESTConfig().Host)) + p.AddExtraEnv(fmt.Sprintf("KLUCTL_K8S_SERVICE_PROXY_%d_SERVICE_NAMESPACE=%s", idx, "kube-system")) + p.AddExtraEnv(fmt.Sprintf("KLUCTL_K8S_SERVICE_PROXY_%d_SERVICE_NAME=%s", idx, "sealed-secrets-controller")) + p.AddExtraEnv(fmt.Sprintf("KLUCTL_K8S_SERVICE_PROXY_%d_SERVICE_PORT=%s", idx, "http")) + p.AddExtraEnv(fmt.Sprintf("KLUCTL_K8S_SERVICE_PROXY_%d_LOCAL_URL=%s", idx, cs.url)) } f(0, defaultCluster1, certServer1) f(1, defaultCluster2, certServer2) @@ -536,7 +536,7 @@ func TestSeal_Vault(t *testing.T) { }), }, true) - p.extraEnv = append(p.extraEnv, "VAULT_TOKEN=root") + p.AddExtraEnv("VAULT_TOKEN=root") p.KluctlMust("seal", "-t", "test-target") sealedSecretsDir := p.LocalRepoDir() From fdeee82cafe249a6e25120631b8d0794a945de05 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 28 Nov 2022 17:11:53 +0100 Subject: [PATCH 1243/2916] tests: Move TestProject into test-utils package --- e2e/args_test.go | 7 ++--- e2e/contexts_test.go | 5 ++-- e2e/deployment_items_test.go | 5 ++-- e2e/flux_test.go | 2 +- e2e/helm_test.go | 24 ++++++++--------- e2e/hooks_test.go | 6 ++--- e2e/inclusion_test.go | 6 ++--- e2e/no_target_test.go | 9 ++++--- e2e/seal_test.go | 12 ++++----- e2e/sops_test.go | 7 ++--- e2e/{ => test-utils}/project.go | 13 +++++---- e2e/test-utils/run_helper.go | 48 +++++++++++++++++++++++++++++++++ e2e/utils.go | 43 ----------------------------- e2e/utils_resources.go | 9 ++++--- 14 files changed, 103 insertions(+), 93 deletions(-) rename e2e/{ => test-utils}/project.go (96%) create mode 100644 e2e/test-utils/run_helper.go diff --git a/e2e/args_test.go b/e2e/args_test.go index 32443a740..2cfebb50e 100644 --- a/e2e/args_test.go +++ b/e2e/args_test.go @@ -2,6 +2,7 @@ package e2e import ( "fmt" + "github.com/kluctl/kluctl/v2/e2e/test-utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "os" "testing" @@ -12,7 +13,7 @@ func testArgs(t *testing.T, deprecated bool) { k := defaultCluster1 - p := NewTestProject(t, k) + p := test_utils.NewTestProject(t, k) createNamespace(t, k, p.TestSlug()) @@ -135,7 +136,7 @@ func TestArgsFromEnv(t *testing.T) { k := defaultCluster1 - p := NewTestProject(t, k) + p := test_utils.NewTestProject(t, k) createNamespace(t, k, p.TestSlug()) @@ -168,7 +169,7 @@ func TestArgsFromEnvAndCli(t *testing.T) { k := defaultCluster1 - p := NewTestProject(t, k) + p := test_utils.NewTestProject(t, k) createNamespace(t, k, p.TestSlug()) diff --git a/e2e/contexts_test.go b/e2e/contexts_test.go index 1f3c5ee45..ef7a203b0 100644 --- a/e2e/contexts_test.go +++ b/e2e/contexts_test.go @@ -1,13 +1,14 @@ package e2e import ( + "github.com/kluctl/kluctl/v2/e2e/test-utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "k8s.io/client-go/tools/clientcmd/api" "testing" ) -func prepareContextTest(t *testing.T) *TestProject { - p := NewTestProject(t, defaultCluster1) +func prepareContextTest(t *testing.T) *test_utils.TestProject { + p := test_utils.NewTestProject(t, defaultCluster1) p.MergeKubeconfig(defaultCluster2) createNamespace(t, defaultCluster1, p.TestSlug()) diff --git a/e2e/deployment_items_test.go b/e2e/deployment_items_test.go index 122204df4..2a346b8fe 100644 --- a/e2e/deployment_items_test.go +++ b/e2e/deployment_items_test.go @@ -1,6 +1,7 @@ package e2e import ( + "github.com/kluctl/kluctl/v2/e2e/test-utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "testing" ) @@ -10,7 +11,7 @@ func TestKustomize(t *testing.T) { k := defaultCluster1 - p := NewTestProject(t, k) + p := test_utils.NewTestProject(t, k) createNamespace(t, k, p.TestSlug()) @@ -38,7 +39,7 @@ func TestGeneratedKustomize(t *testing.T) { k := defaultCluster1 - p := NewTestProject(t, k) + p := test_utils.NewTestProject(t, k) createNamespace(t, k, p.TestSlug()) diff --git a/e2e/flux_test.go b/e2e/flux_test.go index b9f49d496..c8e1979b2 100644 --- a/e2e/flux_test.go +++ b/e2e/flux_test.go @@ -36,7 +36,7 @@ func TestFluxCommands(t *testing.T) { k := defaultCluster1 - p := NewTestProject(t, k) + p := test_utils.NewTestProject(t, k) var wg sync.WaitGroup wg.Add(2) diff --git a/e2e/helm_test.go b/e2e/helm_test.go index 0110c3689..4efa1fc14 100644 --- a/e2e/helm_test.go +++ b/e2e/helm_test.go @@ -20,13 +20,13 @@ func createHelmOrOciRepo(t *testing.T, charts []test_utils.RepoChart, oci bool, } } -func addHelmDeployment(p *TestProject, dir string, repoUrl string, chartName, version string, releaseName string, namespace string, values map[string]any) { +func addHelmDeployment(p *test_utils.TestProject, dir string, repoUrl string, chartName, version string, releaseName string, namespace string, values map[string]any) { if registry2.IsOCI(repoUrl) { repoUrl += "/" + chartName chartName = "" } - p.AddKustomizeDeployment(dir, []KustomizeResource{ + p.AddKustomizeDeployment(dir, []test_utils.KustomizeResource{ {Name: "helm-rendered.yaml"}, }, nil) @@ -67,7 +67,7 @@ func testHelmPull(t *testing.T, tc testCase, prePull bool) { k := defaultCluster1 - p := NewTestProject(t, k) + p := test_utils.NewTestProject(t, k) createNamespace(t, k, p.TestSlug()) @@ -78,7 +78,7 @@ func testHelmPull(t *testing.T, tc testCase, prePull bool) { password = "secret-password" } - repoUrl := createHelmOrOciRepo(p.t, []test_utils.RepoChart{ + repoUrl := createHelmOrOciRepo(t, []test_utils.RepoChart{ {ChartName: "test-chart1", Version: "0.1.0"}, }, tc.oci, user, password) @@ -158,11 +158,11 @@ func testHelmManualUpgrade(t *testing.T, oci bool) { k := defaultCluster1 - p := NewTestProject(t, k) + p := test_utils.NewTestProject(t, k) createNamespace(t, k, p.TestSlug()) - repoUrl := createHelmOrOciRepo(p.t, []test_utils.RepoChart{ + repoUrl := createHelmOrOciRepo(t, []test_utils.RepoChart{ {ChartName: "test-chart1", Version: "0.1.0"}, {ChartName: "test-chart1", Version: "0.2.0"}, }, oci, "", "") @@ -202,11 +202,11 @@ func testHelmUpdate(t *testing.T, oci bool, upgrade bool, commit bool) { k := defaultCluster1 - p := NewTestProject(t, k) + p := test_utils.NewTestProject(t, k) createNamespace(t, k, p.TestSlug()) - repoUrl := createHelmOrOciRepo(p.t, []test_utils.RepoChart{ + repoUrl := createHelmOrOciRepo(t, []test_utils.RepoChart{ {ChartName: "test-chart1", Version: "0.1.0"}, {ChartName: "test-chart1", Version: "0.2.0"}, {ChartName: "test-chart2", Version: "0.1.0"}, @@ -312,11 +312,11 @@ func TestHelmValues(t *testing.T) { k := defaultCluster1 - p := NewTestProject(t, k) + p := test_utils.NewTestProject(t, k) createNamespace(t, k, p.TestSlug()) - repoUrl := test_utils.CreateHelmRepo(p.t, []test_utils.RepoChart{ + repoUrl := test_utils.CreateHelmRepo(t, []test_utils.RepoChart{ {ChartName: "test-chart1", Version: "0.1.0"}, {ChartName: "test-chart2", Version: "0.1.0"}, }, "", "") @@ -374,13 +374,13 @@ func TestHelmTemplateChartYaml(t *testing.T) { k := defaultCluster1 - p := NewTestProject(t, k) + p := test_utils.NewTestProject(t, k) createNamespace(t, k, p.TestSlug()) createNamespace(t, k, p.TestSlug()+"-a") createNamespace(t, k, p.TestSlug()+"-b") - repoUrl := test_utils.CreateHelmRepo(p.t, []test_utils.RepoChart{ + repoUrl := test_utils.CreateHelmRepo(t, []test_utils.RepoChart{ {ChartName: "test-chart1", Version: "0.1.0"}, {ChartName: "test-chart2", Version: "0.1.0"}, }, "", "") diff --git a/e2e/hooks_test.go b/e2e/hooks_test.go index 2d57815bd..766c43e9c 100644 --- a/e2e/hooks_test.go +++ b/e2e/hooks_test.go @@ -18,7 +18,7 @@ type hooksTestContext struct { t *testing.T k *test_utils.EnvTestCluster - p *TestProject + p *test_utils.TestProject seenConfigMaps []string @@ -87,7 +87,7 @@ func (s *hooksTestContext) addConfigMap(dir string, opts resourceOpts) { o.SetK8sGVKs("", "v1", "ConfigMap") mergeMetadata(o, opts) o.SetNestedField(map[string]interface{}{}, "data") - s.p.AddKustomizeResources(dir, []KustomizeResource{ + s.p.AddKustomizeResources(dir, []test_utils.KustomizeResource{ {fmt.Sprintf("%s.yml", opts.name), "", o}, }) } @@ -99,7 +99,7 @@ func prepareHookTestProject(t *testing.T, hook string, hookDeletionPolicy string } s.setupWebhook() - s.p = NewTestProject(t, s.k) + s.p = test_utils.NewTestProject(t, s.k) t.Cleanup(func() { s.removeWebhook() }) diff --git a/e2e/inclusion_test.go b/e2e/inclusion_test.go index f8e196651..08e6f5881 100644 --- a/e2e/inclusion_test.go +++ b/e2e/inclusion_test.go @@ -8,9 +8,9 @@ import ( "testing" ) -func prepareInclusionTestProject(t *testing.T, withIncludes bool) (*TestProject, *test_utils.EnvTestCluster) { +func prepareInclusionTestProject(t *testing.T, withIncludes bool) (*test_utils.TestProject, *test_utils.EnvTestCluster) { k := defaultCluster1 - p := NewTestProject(t, k) + p := test_utils.NewTestProject(t, k) createNamespace(t, k, p.TestSlug()) @@ -40,7 +40,7 @@ func prepareInclusionTestProject(t *testing.T, withIncludes bool) (*TestProject, return p, k } -func assertExistsHelper(t *testing.T, p *TestProject, k *test_utils.EnvTestCluster, shouldExists map[string]bool, add []string, remove []string) { +func assertExistsHelper(t *testing.T, p *test_utils.TestProject, k *test_utils.EnvTestCluster, shouldExists map[string]bool, add []string, remove []string) { for _, x := range add { shouldExists[x] = true } diff --git a/e2e/no_target_test.go b/e2e/no_target_test.go index 5d92a5c6c..caa7d217c 100644 --- a/e2e/no_target_test.go +++ b/e2e/no_target_test.go @@ -1,14 +1,15 @@ package e2e import ( + "github.com/kluctl/kluctl/v2/e2e/test-utils" "github.com/stretchr/testify/assert" "os" "path/filepath" "testing" ) -func prepareNoTargetTest(t *testing.T, withDeploymentYaml bool) *TestProject { - p := NewTestProject(t, defaultCluster1) +func prepareNoTargetTest(t *testing.T, withDeploymentYaml bool) *test_utils.TestProject { + p := test_utils.NewTestProject(t, defaultCluster1) p.MergeKubeconfig(defaultCluster2) createNamespace(t, defaultCluster1, p.TestSlug()) @@ -23,9 +24,9 @@ func prepareNoTargetTest(t *testing.T, withDeploymentYaml bool) *TestProject { }) if withDeploymentYaml { - p.AddKustomizeDeployment("cm", []KustomizeResource{{Name: "cm.yaml", Content: cm}}, nil) + p.AddKustomizeDeployment("cm", []test_utils.KustomizeResource{{Name: "cm.yaml", Content: cm}}, nil) } else { - p.AddKustomizeResources("", []KustomizeResource{{Name: "cm.yaml", Content: cm}}) + p.AddKustomizeResources("", []test_utils.KustomizeResource{{Name: "cm.yaml", Content: cm}}) err := os.Remove(filepath.Join(p.LocalRepoDir(), "deployment.yml")) assert.NoError(t, err) } diff --git a/e2e/seal_test.go b/e2e/seal_test.go index ab4b87d4c..ecbf0b69f 100644 --- a/e2e/seal_test.go +++ b/e2e/seal_test.go @@ -85,7 +85,7 @@ func startCertServer() (*certServer, error) { return &cs, nil } -func addProxyVars(p *TestProject) { +func addProxyVars(p *test_utils.TestProject) { f := func(idx int, k *test_utils.EnvTestCluster, cs *certServer) { p.AddExtraEnv(fmt.Sprintf("KLUCTL_K8S_SERVICE_PROXY_%d_API_HOST=%s", idx, k.RESTConfig().Host)) p.AddExtraEnv(fmt.Sprintf("KLUCTL_K8S_SERVICE_PROXY_%d_SERVICE_NAMESPACE=%s", idx, "kube-system")) @@ -97,8 +97,8 @@ func addProxyVars(p *TestProject) { f(1, defaultCluster2, certServer2) } -func prepareSealTest(t *testing.T, k *test_utils.EnvTestCluster, secrets map[string]string, varsSources []*uo.UnstructuredObject, proxy bool) *TestProject { - p := NewTestProject(t, k) +func prepareSealTest(t *testing.T, k *test_utils.EnvTestCluster, secrets map[string]string, varsSources []*uo.UnstructuredObject, proxy bool) *test_utils.TestProject { + p := test_utils.NewTestProject(t, k) if proxy { addProxyVars(p) @@ -114,13 +114,13 @@ func prepareSealTest(t *testing.T, k *test_utils.EnvTestCluster, secrets map[str return p } -func addSecretsSet(p *TestProject, name string, varsSources []*uo.UnstructuredObject) { - p.updateSecretSet(name, func(secretSet *uo.UnstructuredObject) { +func addSecretsSet(p *test_utils.TestProject, name string, varsSources []*uo.UnstructuredObject) { + p.UpdateSecretSet(name, func(secretSet *uo.UnstructuredObject) { _ = secretSet.SetNestedField(varsSources, "vars") }) } -func addSecretsSetToTarget(p *TestProject, targetName string, secretSetName string) { +func addSecretsSetToTarget(p *test_utils.TestProject, targetName string, secretSetName string) { p.UpdateTarget(targetName, func(target *uo.UnstructuredObject) { l, _, _ := target.GetNestedList("sealingConfig", "secretSets") l = append(l, secretSetName) diff --git a/e2e/sops_test.go b/e2e/sops_test.go index 726c29b1b..22014d9a0 100644 --- a/e2e/sops_test.go +++ b/e2e/sops_test.go @@ -1,6 +1,7 @@ package e2e import ( + "github.com/kluctl/kluctl/v2/e2e/test-utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/vars/sops_test_resources" "go.mozilla.org/sops/v3/age" @@ -13,7 +14,7 @@ func TestSopsVars(t *testing.T) { k := defaultCluster1 - p := NewTestProject(t, k) + p := test_utils.NewTestProject(t, k) createNamespace(t, k, p.TestSlug()) @@ -53,7 +54,7 @@ func TestSopsResources(t *testing.T) { k := defaultCluster1 - p := NewTestProject(t, k) + p := test_utils.NewTestProject(t, k) createNamespace(t, k, p.TestSlug()) @@ -63,7 +64,7 @@ func TestSopsResources(t *testing.T) { return nil }) - p.AddKustomizeDeployment("cm", []KustomizeResource{ + p.AddKustomizeDeployment("cm", []test_utils.KustomizeResource{ {Name: "encrypted-cm.yaml"}, }, nil) diff --git a/e2e/project.go b/e2e/test-utils/project.go similarity index 96% rename from e2e/project.go rename to e2e/test-utils/project.go index 8b4cc5364..fdf05fc6b 100644 --- a/e2e/project.go +++ b/e2e/test-utils/project.go @@ -1,11 +1,10 @@ -package e2e +package test_utils import ( "fmt" "github.com/go-git/go-git/v5" "github.com/huandu/xstrings" "github.com/imdario/mergo" - "github.com/kluctl/kluctl/v2/e2e/test-utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" "k8s.io/client-go/tools/clientcmd" @@ -24,15 +23,15 @@ type TestProject struct { mergedKubeconfig string - gitServer *test_utils.GitServer + gitServer *GitServer } -func NewTestProject(t *testing.T, k *test_utils.EnvTestCluster) *TestProject { +func NewTestProject(t *testing.T, k *EnvTestCluster) *TestProject { p := &TestProject{ t: t, } - p.gitServer = test_utils.NewGitServer(t) + p.gitServer = NewGitServer(t) p.gitServer.GitInit("kluctl-project") p.UpdateKluctlYaml(func(o *uo.UnstructuredObject) error { @@ -64,7 +63,7 @@ func (p *TestProject) TestSlug() string { return n } -func (p *TestProject) MergeKubeconfig(k *test_utils.EnvTestCluster) { +func (p *TestProject) MergeKubeconfig(k *EnvTestCluster) { p.UpdateMergedKubeconfig(func(config *clientcmdapi.Config) { nkcfg, err := clientcmd.Load(k.Kubeconfig) if err != nil { @@ -164,7 +163,7 @@ func (p *TestProject) UpdateTarget(name string, cb func(target *uo.UnstructuredO p.UpdateNamedListItem(uo.KeyPath{"targets"}, name, cb) } -func (p *TestProject) updateSecretSet(name string, cb func(secretSet *uo.UnstructuredObject)) { +func (p *TestProject) UpdateSecretSet(name string, cb func(secretSet *uo.UnstructuredObject)) { p.UpdateNamedListItem(uo.KeyPath{"secretsConfig", "secretSets"}, name, cb) } diff --git a/e2e/test-utils/run_helper.go b/e2e/test-utils/run_helper.go new file mode 100644 index 000000000..face1f426 --- /dev/null +++ b/e2e/test-utils/run_helper.go @@ -0,0 +1,48 @@ +package test_utils + +import ( + "bufio" + "bytes" + "io" + "os/exec" + "sync" + "testing" +) + +func runHelper(t *testing.T, cmd *exec.Cmd) (string, string, error) { + stdoutPipe, err := cmd.StdoutPipe() + if err != nil { + return "", "", err + } + stderrPipe, err := cmd.StderrPipe() + if err != nil { + _ = stdoutPipe.Close() + return "", "", err + } + + var wg sync.WaitGroup + stdReader := func(testLogPrefix string, buf io.StringWriter, pipe io.Reader) { + defer wg.Done() + scanner := bufio.NewScanner(pipe) + for scanner.Scan() { + l := scanner.Text() + t.Log(testLogPrefix + l) + _, _ = buf.WriteString(l + "\n") + } + } + + stdoutBuf := bytes.NewBuffer(nil) + stderrBuf := bytes.NewBuffer(nil) + + wg.Add(2) + go stdReader("stdout: ", stdoutBuf, stdoutPipe) + go stdReader("stderr: ", stderrBuf, stderrPipe) + + err = cmd.Start() + if err != nil { + return "", "", err + } + wg.Wait() + err = cmd.Wait() + return stdoutBuf.String(), stderrBuf.String(), err +} diff --git a/e2e/utils.go b/e2e/utils.go index bd8eb8e63..5d4a234a0 100644 --- a/e2e/utils.go +++ b/e2e/utils.go @@ -1,19 +1,14 @@ package e2e import ( - "bufio" - "bytes" "context" "github.com/kluctl/kluctl/v2/e2e/test-utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" - "io" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "os/exec" "reflect" - "sync" "testing" ) @@ -58,41 +53,3 @@ func assertNestedFieldEquals(t *testing.T, o *uo.UnstructuredObject, expected in t.Fatalf("%v != %v", v, expected) } } - -func runHelper(t *testing.T, cmd *exec.Cmd) (string, string, error) { - stdoutPipe, err := cmd.StdoutPipe() - if err != nil { - return "", "", err - } - stderrPipe, err := cmd.StderrPipe() - if err != nil { - _ = stdoutPipe.Close() - return "", "", err - } - - var wg sync.WaitGroup - stdReader := func(testLogPrefix string, buf io.StringWriter, pipe io.Reader) { - defer wg.Done() - scanner := bufio.NewScanner(pipe) - for scanner.Scan() { - l := scanner.Text() - t.Log(testLogPrefix + l) - _, _ = buf.WriteString(l + "\n") - } - } - - stdoutBuf := bytes.NewBuffer(nil) - stderrBuf := bytes.NewBuffer(nil) - - wg.Add(2) - go stdReader("stdout: ", stdoutBuf, stdoutPipe) - go stdReader("stderr: ", stderrBuf, stderrPipe) - - err = cmd.Start() - if err != nil { - return "", "", err - } - wg.Wait() - err = cmd.Wait() - return stdoutBuf.String(), stderrBuf.String(), err -} diff --git a/e2e/utils_resources.go b/e2e/utils_resources.go index 83a5df3e8..6d645d980 100644 --- a/e2e/utils_resources.go +++ b/e2e/utils_resources.go @@ -2,6 +2,7 @@ package e2e import ( "fmt" + "github.com/kluctl/kluctl/v2/e2e/test-utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" ) @@ -51,17 +52,17 @@ func createSecretObject(data map[string]string, opts resourceOpts) *uo.Unstructu return o } -func addConfigMapDeployment(p *TestProject, dir string, data map[string]string, opts resourceOpts) { +func addConfigMapDeployment(p *test_utils.TestProject, dir string, data map[string]string, opts resourceOpts) { o := createConfigMapObject(data, opts) - p.AddKustomizeDeployment(dir, []KustomizeResource{ + p.AddKustomizeDeployment(dir, []test_utils.KustomizeResource{ {fmt.Sprintf("configmap-%s.yml", opts.name), "", o}, }, opts.tags) } -func addSecretDeployment(p *TestProject, dir string, data map[string]string, opts resourceOpts) { +func addSecretDeployment(p *test_utils.TestProject, dir string, data map[string]string, opts resourceOpts) { o := createSecretObject(data, opts) fname := fmt.Sprintf("secret-%s.yml", opts.name) - p.AddKustomizeDeployment(dir, []KustomizeResource{ + p.AddKustomizeDeployment(dir, []test_utils.KustomizeResource{ {fname, fname + ".sealme", o}, }, opts.tags) } From 04bb23d138d6d4b7833f699a377ba9766172b421 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 28 Nov 2022 20:58:13 +0100 Subject: [PATCH 1244/2916] tests: Move test-helm-chart into test-utils --- e2e/test-utils/helm_repo_utils.go | 2 +- e2e/{ => test-utils}/test-helm-chart/.helmignore | 0 e2e/{ => test-utils}/test-helm-chart/Chart.yaml | 0 e2e/{ => test-utils}/test-helm-chart/resources.go | 0 e2e/{ => test-utils}/test-helm-chart/templates/_helpers.tpl | 0 e2e/{ => test-utils}/test-helm-chart/templates/configmap.yaml | 0 e2e/{ => test-utils}/test-helm-chart/values.yaml | 0 7 files changed, 1 insertion(+), 1 deletion(-) rename e2e/{ => test-utils}/test-helm-chart/.helmignore (100%) rename e2e/{ => test-utils}/test-helm-chart/Chart.yaml (100%) rename e2e/{ => test-utils}/test-helm-chart/resources.go (100%) rename e2e/{ => test-utils}/test-helm-chart/templates/_helpers.tpl (100%) rename e2e/{ => test-utils}/test-helm-chart/templates/configmap.yaml (100%) rename e2e/{ => test-utils}/test-helm-chart/values.yaml (100%) diff --git a/e2e/test-utils/helm_repo_utils.go b/e2e/test-utils/helm_repo_utils.go index 631b2a677..fc5f422f2 100644 --- a/e2e/test-utils/helm_repo_utils.go +++ b/e2e/test-utils/helm_repo_utils.go @@ -2,7 +2,7 @@ package test_utils import ( "github.com/google/go-containerregistry/pkg/registry" - test_resources "github.com/kluctl/kluctl/v2/e2e/test-helm-chart" + "github.com/kluctl/kluctl/v2/e2e/test-utils/test-helm-chart" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" diff --git a/e2e/test-helm-chart/.helmignore b/e2e/test-utils/test-helm-chart/.helmignore similarity index 100% rename from e2e/test-helm-chart/.helmignore rename to e2e/test-utils/test-helm-chart/.helmignore diff --git a/e2e/test-helm-chart/Chart.yaml b/e2e/test-utils/test-helm-chart/Chart.yaml similarity index 100% rename from e2e/test-helm-chart/Chart.yaml rename to e2e/test-utils/test-helm-chart/Chart.yaml diff --git a/e2e/test-helm-chart/resources.go b/e2e/test-utils/test-helm-chart/resources.go similarity index 100% rename from e2e/test-helm-chart/resources.go rename to e2e/test-utils/test-helm-chart/resources.go diff --git a/e2e/test-helm-chart/templates/_helpers.tpl b/e2e/test-utils/test-helm-chart/templates/_helpers.tpl similarity index 100% rename from e2e/test-helm-chart/templates/_helpers.tpl rename to e2e/test-utils/test-helm-chart/templates/_helpers.tpl diff --git a/e2e/test-helm-chart/templates/configmap.yaml b/e2e/test-utils/test-helm-chart/templates/configmap.yaml similarity index 100% rename from e2e/test-helm-chart/templates/configmap.yaml rename to e2e/test-utils/test-helm-chart/templates/configmap.yaml diff --git a/e2e/test-helm-chart/values.yaml b/e2e/test-utils/test-helm-chart/values.yaml similarity index 100% rename from e2e/test-helm-chart/values.yaml rename to e2e/test-utils/test-helm-chart/values.yaml From 02181c5e80252fd1679f1f0dd9ebf5e3f0028083 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 28 Nov 2022 23:04:58 +0100 Subject: [PATCH 1245/2916] tests: Move addHelmDeployment into TestProject --- e2e/helm_test.go | 58 ++++++++------------------------------- e2e/test-utils/project.go | 34 +++++++++++++++++++++++ 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/e2e/helm_test.go b/e2e/helm_test.go index 4efa1fc14..58b15625d 100644 --- a/e2e/helm_test.go +++ b/e2e/helm_test.go @@ -6,7 +6,6 @@ import ( test_utils "github.com/kluctl/kluctl/v2/e2e/test-utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/stretchr/testify/assert" - registry2 "helm.sh/helm/v3/pkg/registry" "path/filepath" "sort" "testing" @@ -20,39 +19,6 @@ func createHelmOrOciRepo(t *testing.T, charts []test_utils.RepoChart, oci bool, } } -func addHelmDeployment(p *test_utils.TestProject, dir string, repoUrl string, chartName, version string, releaseName string, namespace string, values map[string]any) { - if registry2.IsOCI(repoUrl) { - repoUrl += "/" + chartName - chartName = "" - } - - p.AddKustomizeDeployment(dir, []test_utils.KustomizeResource{ - {Name: "helm-rendered.yaml"}, - }, nil) - - p.UpdateYaml(filepath.Join(dir, "helm-chart.yaml"), func(o *uo.UnstructuredObject) error { - *o = *uo.FromMap(map[string]interface{}{ - "helmChart": map[string]any{ - "repo": repoUrl, - "chartVersion": version, - "releaseName": releaseName, - "namespace": namespace, - }, - }) - if chartName != "" { - _ = o.SetNestedField(chartName, "helmChart", "chartName") - } - return nil - }, "") - - if values != nil { - p.UpdateYaml(filepath.Join(dir, "helm-values.yaml"), func(o *uo.UnstructuredObject) error { - *o = *uo.FromMap(values) - return nil - }, "") - } -} - type testCase struct { name string oci bool @@ -83,7 +49,7 @@ func testHelmPull(t *testing.T, tc testCase, prePull bool) { }, tc.oci, user, password) p.UpdateTarget("test", nil) - addHelmDeployment(p, "helm1", repoUrl, "test-chart1", "0.1.0", "test-helm1", p.TestSlug(), nil) + p.AddHelmDeployment("helm1", repoUrl, "test-chart1", "0.1.0", "test-helm1", p.TestSlug(), nil) if tc.testAuth { if tc.credsId != "" { @@ -168,7 +134,7 @@ func testHelmManualUpgrade(t *testing.T, oci bool) { }, oci, "", "") p.UpdateTarget("test", nil) - addHelmDeployment(p, "helm1", repoUrl, "test-chart1", "0.1.0", "test-helm1", p.TestSlug(), nil) + p.AddHelmDeployment("helm1", repoUrl, "test-chart1", "0.1.0", "test-helm1", p.TestSlug(), nil) p.KluctlMust("helm-pull") assert.FileExists(t, filepath.Join(p.LocalRepoDir(), "helm1/charts/test-chart1/Chart.yaml")) @@ -214,9 +180,9 @@ func testHelmUpdate(t *testing.T, oci bool, upgrade bool, commit bool) { }, oci, "", "") p.UpdateTarget("test", nil) - addHelmDeployment(p, "helm1", repoUrl, "test-chart1", "0.1.0", "test-helm1", p.TestSlug(), nil) - addHelmDeployment(p, "helm2", repoUrl, "test-chart2", "0.1.0", "test-helm2", p.TestSlug(), nil) - addHelmDeployment(p, "helm3", repoUrl, "test-chart1", "0.1.0", "test-helm3", p.TestSlug(), nil) + p.AddHelmDeployment("helm1", repoUrl, "test-chart1", "0.1.0", "test-helm1", p.TestSlug(), nil) + p.AddHelmDeployment("helm2", repoUrl, "test-chart2", "0.1.0", "test-helm2", p.TestSlug(), nil) + p.AddHelmDeployment("helm3", repoUrl, "test-chart1", "0.1.0", "test-helm3", p.TestSlug(), nil) p.UpdateYaml("helm3/helm-chart.yaml", func(o *uo.UnstructuredObject) error { _ = o.SetNestedField(true, "helmChart", "skipUpdate") @@ -341,9 +307,9 @@ func TestHelmValues(t *testing.T) { } p.UpdateTarget("test", nil) - addHelmDeployment(p, "helm1", repoUrl, "test-chart1", "0.1.0", "test-helm1", p.TestSlug(), values1) - addHelmDeployment(p, "helm2", repoUrl, "test-chart2", "0.1.0", "test-helm2", p.TestSlug(), values2) - addHelmDeployment(p, "helm3", repoUrl, "test-chart1", "0.1.0", "test-helm3", p.TestSlug(), values3) + p.AddHelmDeployment("helm1", repoUrl, "test-chart1", "0.1.0", "test-helm1", p.TestSlug(), values1) + p.AddHelmDeployment("helm2", repoUrl, "test-chart2", "0.1.0", "test-helm2", p.TestSlug(), values2) + p.AddHelmDeployment("helm3", repoUrl, "test-chart1", "0.1.0", "test-helm3", p.TestSlug(), values3) p.KluctlMust("helm-pull") p.KluctlMust("deploy", "--yes", "-t", "test", "-aa=a", "-ab=b") @@ -386,10 +352,10 @@ func TestHelmTemplateChartYaml(t *testing.T) { }, "", "") p.UpdateTarget("test", nil) - addHelmDeployment(p, "helm1", repoUrl, "test-chart1", "0.1.0", "test-helm-{{ args.a }}", p.TestSlug(), nil) - addHelmDeployment(p, "helm2", repoUrl, "test-chart2", "0.1.0", "test-helm-{{ args.b }}", p.TestSlug(), nil) - addHelmDeployment(p, "helm3", repoUrl, "test-chart1", "0.1.0", "test-helm-ns", p.TestSlug()+"-{{ args.a }}", nil) - addHelmDeployment(p, "helm4", repoUrl, "test-chart1", "0.1.0", "test-helm-ns", p.TestSlug()+"-{{ args.b }}", nil) + p.AddHelmDeployment("helm1", repoUrl, "test-chart1", "0.1.0", "test-helm-{{ args.a }}", p.TestSlug(), nil) + p.AddHelmDeployment("helm2", repoUrl, "test-chart2", "0.1.0", "test-helm-{{ args.b }}", p.TestSlug(), nil) + p.AddHelmDeployment("helm3", repoUrl, "test-chart1", "0.1.0", "test-helm-ns", p.TestSlug()+"-{{ args.a }}", nil) + p.AddHelmDeployment("helm4", repoUrl, "test-chart1", "0.1.0", "test-helm-ns", p.TestSlug()+"-{{ args.b }}", nil) p.KluctlMust("helm-pull") p.KluctlMust("deploy", "--yes", "-t", "test", "-aa=a", "-ab=b") diff --git a/e2e/test-utils/project.go b/e2e/test-utils/project.go index fdf05fc6b..51183ffef 100644 --- a/e2e/test-utils/project.go +++ b/e2e/test-utils/project.go @@ -7,6 +7,7 @@ import ( "github.com/imdario/mergo" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" + registry2 "helm.sh/helm/v3/pkg/registry" "k8s.io/client-go/tools/clientcmd" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" "os" @@ -271,6 +272,39 @@ func (p *TestProject) AddKustomizeDeployment(dir string, resources []KustomizeRe }) } +func (p *TestProject) AddHelmDeployment(dir string, repoUrl string, chartName, version string, releaseName string, namespace string, values map[string]any) { + if registry2.IsOCI(repoUrl) { + repoUrl += "/" + chartName + chartName = "" + } + + p.AddKustomizeDeployment(dir, []KustomizeResource{ + {Name: "helm-rendered.yaml"}, + }, nil) + + p.UpdateYaml(filepath.Join(dir, "helm-chart.yaml"), func(o *uo.UnstructuredObject) error { + *o = *uo.FromMap(map[string]interface{}{ + "helmChart": map[string]any{ + "repo": repoUrl, + "chartVersion": version, + "releaseName": releaseName, + "namespace": namespace, + }, + }) + if chartName != "" { + _ = o.SetNestedField(chartName, "helmChart", "chartName") + } + return nil + }, "") + + if values != nil { + p.UpdateYaml(filepath.Join(dir, "helm-values.yaml"), func(o *uo.UnstructuredObject) error { + *o = *uo.FromMap(values) + return nil + }, "") + } +} + func (p *TestProject) convertInterfaceToList(x interface{}) []interface{} { var ret []interface{} if l, ok := x.([]interface{}); ok { From 22d0278238d0751061a30b349f2b79999bd44a96 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Nov 2022 22:13:00 +0000 Subject: [PATCH 1246/2916] chore(deps): Bump github.com/aws/aws-sdk-go from 1.44.145 to 1.44.146 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.145 to 1.44.146. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.145...v1.44.146) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 7108d7e82..7f5badd32 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e github.com/Masterminds/semver/v3 v3.1.1 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d - github.com/aws/aws-sdk-go v1.44.145 + github.com/aws/aws-sdk-go v1.44.146 github.com/bitnami-labs/sealed-secrets v0.19.2 github.com/cyphar/filepath-securejoin v0.2.3 github.com/docker/distribution v2.8.1+incompatible diff --git a/go.sum b/go.sum index 89ad58cea..b83e8c3c7 100644 --- a/go.sum +++ b/go.sum @@ -130,8 +130,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/aws/aws-sdk-go v1.44.145 h1:KMVRrIyjBsNz3xGPuHIRnhIuKlb5h3Ii5e5jbi3cgnc= -github.com/aws/aws-sdk-go v1.44.145/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.146 h1:7YdGgPxDPRJu/yYffzZp/H7yHzQ6AqmuNFZPYraaN8I= +github.com/aws/aws-sdk-go v1.44.146/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= From 64abf6c5824c515c17b4594db3782c9b34797014 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Nov 2022 22:13:40 +0000 Subject: [PATCH 1247/2916] chore(deps): Bump github.com/Masterminds/semver/v3 from 3.1.1 to 3.2.0 Bumps [github.com/Masterminds/semver/v3](https://github.com/Masterminds/semver) from 3.1.1 to 3.2.0. - [Release notes](https://github.com/Masterminds/semver/releases) - [Changelog](https://github.com/Masterminds/semver/blob/master/CHANGELOG.md) - [Commits](https://github.com/Masterminds/semver/compare/v3.1.1...v3.2.0) --- updated-dependencies: - dependency-name: github.com/Masterminds/semver/v3 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 7108d7e82..202de4dd6 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.19 require ( github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e - github.com/Masterminds/semver/v3 v3.1.1 + github.com/Masterminds/semver/v3 v3.2.0 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d github.com/aws/aws-sdk-go v1.44.145 github.com/bitnami-labs/sealed-secrets v0.19.2 diff --git a/go.sum b/go.sum index 89ad58cea..c186d2345 100644 --- a/go.sum +++ b/go.sum @@ -88,8 +88,9 @@ github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6 github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= +github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Masterminds/sprig/v3 v3.2.0/go.mod h1:tWhwTbUTndesPNeF0C900vKoq283u6zp4APT9vaF3SI= github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= From 81a8c7424eb7f2f37c19fe09a1dae0f1e6b2b9a0 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 28 Nov 2022 23:07:37 +0100 Subject: [PATCH 1248/2916] fix: Also consider helm credentials when no credentialsId is provided --- e2e/helm_test.go | 2 +- pkg/deployment/helm_chart.go | 21 +++++++++++---------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/e2e/helm_test.go b/e2e/helm_test.go index 58b15625d..94386b8f8 100644 --- a/e2e/helm_test.go +++ b/e2e/helm_test.go @@ -97,7 +97,7 @@ func TestHelmPull(t *testing.T) { tests := []testCase{ {name: "helm-no-creds"}, {name: "helm-creds-missing", oci: false, testAuth: true, credsId: "test-creds", - expectedError: "no credentials provided for Chart test-chart1"}, + expectedError: "401 Unauthorized"}, {name: "helm-creds-invalid", oci: false, testAuth: true, credsId: "test-creds", extraArgs: []string{"--helm-username=test-creds:test-user", "--helm-password=test-creds:invalid"}, expectedError: "401 Unauthorized"}, diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index 0d9b4d4cb..dda52347a 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -213,21 +213,22 @@ func (c *HelmChart) doPull(ctx context.Context, chartDir string) error { if registry.IsOCI(*c.Config.Repo) { return fmt.Errorf("OCI charts can currently only be authenticated via registry login and not via cli arguments") } - if c.credentials == nil { return fmt.Errorf("no credentials provider") } + } + + if c.credentials != nil { creds := c.credentials.FindCredentials(*c.Config.Repo, c.Config.CredentialsId) - if creds == nil { - return fmt.Errorf("no credentials provided for Chart %s", c.chartName) + if creds != nil { + a.Username = creds.Username + a.Password = creds.Password + a.CertFile = creds.CertFile + a.CaFile = creds.CAFile + a.KeyFile = creds.KeyFile + a.InsecureSkipTLSverify = creds.InsecureSkipTLSverify + a.PassCredentialsAll = creds.PassCredentialsAll } - a.Username = creds.Username - a.Password = creds.Password - a.CertFile = creds.CertFile - a.CaFile = creds.CAFile - a.KeyFile = creds.KeyFile - a.InsecureSkipTLSverify = creds.InsecureSkipTLSverify - a.PassCredentialsAll = creds.PassCredentialsAll } var out string From 511efdc4b08295230e067d0073d0b145be7fa070 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Nov 2022 22:07:31 +0000 Subject: [PATCH 1249/2916] chore(deps): Bump github.com/golang-jwt/jwt/v4 from 4.4.2 to 4.4.3 Bumps [github.com/golang-jwt/jwt/v4](https://github.com/golang-jwt/jwt) from 4.4.2 to 4.4.3. - [Release notes](https://github.com/golang-jwt/jwt/releases) - [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md) - [Commits](https://github.com/golang-jwt/jwt/compare/v4.4.2...v4.4.3) --- updated-dependencies: - dependency-name: github.com/golang-jwt/jwt/v4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index aa0533f25..3561dec7e 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/go-git/go-git/v5 v5.4.2 github.com/go-playground/validator/v10 v10.11.1 github.com/gobwas/glob v0.2.3 // indirect - github.com/golang-jwt/jwt/v4 v4.4.2 + github.com/golang-jwt/jwt/v4 v4.4.3 github.com/google/go-containerregistry v0.12.1 github.com/hashicorp/vault/api v1.8.2 github.com/hexops/gotextdiff v1.0.3 diff --git a/go.sum b/go.sum index 3acee807f..e633e02b6 100644 --- a/go.sum +++ b/go.sum @@ -316,8 +316,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= -github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs= -github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU= +github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= From fe837cbe6a7c8f73ef29c30b1082d1217c605b2f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 29 Nov 2022 20:12:56 +0100 Subject: [PATCH 1250/2916] refactor: Remove status handler dependency from git pkg --- pkg/git/mirrored_repo.go | 17 +++++------------ pkg/git/repocache/cache.go | 5 +++++ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/pkg/git/mirrored_repo.go b/pkg/git/mirrored_repo.go index c3a08ff7d..14262b6fd 100644 --- a/pkg/git/mirrored_repo.go +++ b/pkg/git/mirrored_repo.go @@ -11,7 +11,6 @@ import ( git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" _ "github.com/kluctl/kluctl/v2/pkg/git/ssh-pool" ssh_pool "github.com/kluctl/kluctl/v2/pkg/git/ssh-pool" - "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/rogpeppe/go-internal/lockedfile" "os" @@ -105,7 +104,6 @@ func (g *MirroredGitRepo) Unlock() error { err := g.fileLock.Close() if err != nil { - status.Warning(g.ctx, "Unlock of %s failed: %v", g.fileLockPath, err) return err } g.fileLock = nil @@ -196,7 +194,7 @@ func (g *MirroredGitRepo) cleanupMirrorDir() error { return nil } -func (g *MirroredGitRepo) update(s *status.StatusContext, repoDir string) error { +func (g *MirroredGitRepo) update(repoDir string) error { r, err := git.PlainOpen(repoDir) if err != nil { return err @@ -284,10 +282,10 @@ func (g *MirroredGitRepo) update(s *status.StatusContext, repoDir string) error return nil } -func (g *MirroredGitRepo) cloneOrUpdate(s *status.StatusContext) error { +func (g *MirroredGitRepo) cloneOrUpdate() error { initMarker := filepath.Join(g.mirrorDir, ".cache2.init") if utils.IsFile(initMarker) { - return g.update(s, g.mirrorDir) + return g.update(g.mirrorDir) } err := g.cleanupMirrorDir() if err != nil { @@ -314,7 +312,7 @@ func (g *MirroredGitRepo) cloneOrUpdate(s *status.StatusContext) error { return err } - err = g.update(s, tmpMirrorDir) + err = g.update(tmpMirrorDir) if err != nil { return err } @@ -337,14 +335,11 @@ func (g *MirroredGitRepo) cloneOrUpdate(s *status.StatusContext) error { } func (g *MirroredGitRepo) Update() error { - s := status.Start(g.ctx, "Updating git cache for %s", g.url.String()) - err := g.cloneOrUpdate(s) + err := g.cloneOrUpdate() if err != nil { - s.FailedWithMessage(err.Error()) return err } g.hasUpdated = true - s.Success() return nil } @@ -353,8 +348,6 @@ func (g *MirroredGitRepo) CloneProjectByCommit(commit string, targetDir string) panic("tried to clone from a project that is not locked/updated") } - status.Trace(g.ctx, "Cloning git project: url='%s', commit='%s', target='%s'", g.url.String(), commit, targetDir) - err := PoorMansClone(g.mirrorDir, targetDir, &git.CheckoutOptions{Hash: plumbing.NewHash(commit)}) if err != nil { return fmt.Errorf("failed to clone %s from %s: %w", commit, g.url.String(), err) diff --git a/pkg/git/repocache/cache.go b/pkg/git/repocache/cache.go index 59e89ef6f..aa0cb0c91 100644 --- a/pkg/git/repocache/cache.go +++ b/pkg/git/repocache/cache.go @@ -118,10 +118,15 @@ func (e *CacheEntry) Update() error { if time.Now().Sub(e.mr.LastUpdateTime()) <= e.rp.updateInterval { e.mr.SetUpdated(true) } else { + url := e.mr.Url() + s := status.Start(e.rp.ctx, "Updating git cache for %s", url.String()) + defer s.Failed() err := e.mr.Update() if err != nil { + s.FailedWithMessage(err.Error()) return err } + s.Success() } } From baf324c82cf9a93437c8a2591442fd049a6ea7e8 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 30 Nov 2022 13:02:28 +0100 Subject: [PATCH 1251/2916] refactor: Get rid of status handler dependency in git package --- cmd/kluctl/commands/utils.go | 11 +++++++- pkg/git/auth/auth_provider.go | 13 ++++++--- pkg/git/auth/env_auth_provider.go | 10 ++++--- pkg/git/auth/git_credentials_file.go | 15 ++++++----- pkg/git/auth/list_auth_provider.go | 32 +++++++++++----------- pkg/git/auth/ssh_auth_provider.go | 38 +++++++++++++------------- pkg/git/auth/ssh_known_hosts.go | 15 +++++------ pkg/git/list_refs.go | 2 -- pkg/git/messages/message_callbacks.go | 39 +++++++++++++++++++++++++++ pkg/git/ssh-pool/ssh_pool.go | 4 +-- pkg/vars/vars_loader_test.go | 2 +- 11 files changed, 118 insertions(+), 63 deletions(-) create mode 100644 pkg/git/messages/message_callbacks.go diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index eab25b232..0a53757ad 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -8,6 +8,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/git" "github.com/kluctl/kluctl/v2/pkg/git/auth" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" + "github.com/kluctl/kluctl/v2/pkg/git/messages" "github.com/kluctl/kluctl/v2/pkg/git/repocache" ssh_pool "github.com/kluctl/kluctl/v2/pkg/git/ssh-pool" "github.com/kluctl/kluctl/v2/pkg/kluctl_jinja2" @@ -60,7 +61,15 @@ func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, strictTemplates b repoOverrides = append(repoOverrides, ro) } - rp := repocache.NewGitRepoCache(ctx, sshPool, auth.NewDefaultAuthProviders(), repoOverrides, projectFlags.GitCacheUpdateInterval) + messageCallbacks := &messages.MessageCallbacks{ + WarningFn: func(s string) { status.Warning(ctx, s) }, + TraceFn: func(s string) { status.Trace(ctx, s) }, + AskForPasswordFn: func(s string) (string, error) { return status.AskForPassword(ctx, s) }, + AskForConfirmationFn: func(s string) bool { return status.AskForConfirmation(ctx, s) }, + } + gitAuth := auth.NewDefaultAuthProviders(messageCallbacks) + + rp := repocache.NewGitRepoCache(ctx, sshPool, gitAuth, repoOverrides, projectFlags.GitCacheUpdateInterval) defer rp.Clear() loadArgs := kluctl_project.LoadKluctlProjectArgs{ diff --git a/pkg/git/auth/auth_provider.go b/pkg/git/auth/auth_provider.go index 0eca67a60..0d77d2cf6 100644 --- a/pkg/git/auth/auth_provider.go +++ b/pkg/git/auth/auth_provider.go @@ -4,6 +4,7 @@ import ( "context" "github.com/go-git/go-git/v5/plumbing/transport" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" + "github.com/kluctl/kluctl/v2/pkg/git/messages" "golang.org/x/crypto/ssh" ) @@ -41,10 +42,14 @@ func (a *GitAuthProviders) BuildAuth(ctx context.Context, gitUrl git_url.GitUrl) return AuthMethodAndCA{} } -func NewDefaultAuthProviders() *GitAuthProviders { +func NewDefaultAuthProviders(messageCallbacks *messages.MessageCallbacks) *GitAuthProviders { + if messageCallbacks == nil { + messageCallbacks = &messages.MessageCallbacks{} + } + a := &GitAuthProviders{} - a.RegisterAuthProvider(&GitEnvAuthProvider{}, true) - a.RegisterAuthProvider(&GitCredentialsFileAuthProvider{}, true) - a.RegisterAuthProvider(&GitSshAuthProvider{}, true) + a.RegisterAuthProvider(&GitEnvAuthProvider{MessageCallbacks: *messageCallbacks}, true) + a.RegisterAuthProvider(&GitCredentialsFileAuthProvider{MessageCallbacks: *messageCallbacks}, true) + a.RegisterAuthProvider(&GitSshAuthProvider{MessageCallbacks: *messageCallbacks}, true) return a } diff --git a/pkg/git/auth/env_auth_provider.go b/pkg/git/auth/env_auth_provider.go index 7f692b661..76a4a3c8d 100644 --- a/pkg/git/auth/env_auth_provider.go +++ b/pkg/git/auth/env_auth_provider.go @@ -2,13 +2,15 @@ package auth import ( "context" + "fmt" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" - "github.com/kluctl/kluctl/v2/pkg/status" + "github.com/kluctl/kluctl/v2/pkg/git/messages" "github.com/kluctl/kluctl/v2/pkg/utils" "os" ) type GitEnvAuthProvider struct { + MessageCallbacks messages.MessageCallbacks } func (a *GitEnvAuthProvider) BuildAuth(ctx context.Context, gitUrl git_url.GitUrl) AuthMethodAndCA { @@ -27,13 +29,13 @@ func (a *GitEnvAuthProvider) BuildAuth(ctx context.Context, gitUrl git_url.GitUr ssh_key_path, _ := m["SSH_KEY"] - status.Trace(ctx, "GitEnvAuthProvider: adding entry host=%s, pathPrefix=%s, username=%s, ssh_key=%s", e.Host, e.PathPrefix, e.Username, ssh_key_path) + a.MessageCallbacks.Trace(fmt.Sprintf("GitEnvAuthProvider: adding entry host=%s, pathPrefix=%s, username=%s, ssh_key=%s", e.Host, e.PathPrefix, e.Username, ssh_key_path)) if ssh_key_path != "" { ssh_key_path = utils.ExpandPath(ssh_key_path) b, err := os.ReadFile(ssh_key_path) if err != nil { - status.Trace(ctx, "GitEnvAuthProvider: failed to read key %s: %v", ssh_key_path, err) + a.MessageCallbacks.Trace(fmt.Sprintf("GitEnvAuthProvider: failed to read key %s: %v", ssh_key_path, err)) } else { e.SshKey = b } @@ -43,7 +45,7 @@ func (a *GitEnvAuthProvider) BuildAuth(ctx context.Context, gitUrl git_url.GitUr ca_bundle_path = utils.ExpandPath(ca_bundle_path) b, err := os.ReadFile(ca_bundle_path) if err != nil { - status.Trace(ctx, "GitEnvAuthProvider: failed to read ca bundle %s: %v", ca_bundle_path, err) + a.MessageCallbacks.Trace(fmt.Sprintf("GitEnvAuthProvider: failed to read ca bundle %s: %v", ca_bundle_path, err)) } else { e.CABundle = b } diff --git a/pkg/git/auth/git_credentials_file.go b/pkg/git/auth/git_credentials_file.go index 7ee6775c3..5d3ef3cfd 100644 --- a/pkg/git/auth/git_credentials_file.go +++ b/pkg/git/auth/git_credentials_file.go @@ -5,14 +5,15 @@ import ( "context" "github.com/go-git/go-git/v5/plumbing/transport/http" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" - "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/git/messages" giturls "github.com/whilp/git-urls" "os" "path/filepath" ) type GitCredentialsFileAuthProvider struct { + MessageCallbacks messages.MessageCallbacks } func (a *GitCredentialsFileAuthProvider) BuildAuth(ctx context.Context, gitUrl git_url.GitUrl) AuthMethodAndCA { @@ -22,21 +23,21 @@ func (a *GitCredentialsFileAuthProvider) BuildAuth(ctx context.Context, gitUrl g home, err := os.UserHomeDir() if err != nil { - status.Warning(ctx, "Could not determine home directory: %v", err) + a.MessageCallbacks.Warning("Could not determine home directory: %v", err) return AuthMethodAndCA{} } - auth := a.tryBuildAuth(ctx, gitUrl, filepath.Join(home, ".git-credentials")) + auth := a.tryBuildAuth(gitUrl, filepath.Join(home, ".git-credentials")) if auth != nil { return *auth } if xdgHome, ok := os.LookupEnv("XDG_CONFIG_HOME"); ok && xdgHome != "" { - auth = a.tryBuildAuth(ctx, gitUrl, filepath.Join(xdgHome, ".git-credentials")) + auth = a.tryBuildAuth(gitUrl, filepath.Join(xdgHome, ".git-credentials")) if auth != nil { return *auth } } else { - auth = a.tryBuildAuth(ctx, gitUrl, filepath.Join(home, ".config/.git-credentials")) + auth = a.tryBuildAuth(gitUrl, filepath.Join(home, ".config/.git-credentials")) if auth != nil { return *auth } @@ -44,14 +45,14 @@ func (a *GitCredentialsFileAuthProvider) BuildAuth(ctx context.Context, gitUrl g return AuthMethodAndCA{} } -func (a *GitCredentialsFileAuthProvider) tryBuildAuth(ctx context.Context, gitUrl git_url.GitUrl, gitCredentialsPath string) *AuthMethodAndCA { +func (a *GitCredentialsFileAuthProvider) tryBuildAuth(gitUrl git_url.GitUrl, gitCredentialsPath string) *AuthMethodAndCA { if !utils.IsFile(gitCredentialsPath) { return nil } f, err := os.Open(gitCredentialsPath) if err != nil { - status.Warning(ctx, "Failed to open %s: %v", gitCredentialsPath, err) + a.MessageCallbacks.Warning("Failed to open %s: %v", gitCredentialsPath, err) return nil } defer f.Close() diff --git a/pkg/git/auth/list_auth_provider.go b/pkg/git/auth/list_auth_provider.go index 532734c1d..e11cb176c 100644 --- a/pkg/git/auth/list_auth_provider.go +++ b/pkg/git/auth/list_auth_provider.go @@ -4,12 +4,14 @@ import ( "context" "github.com/go-git/go-git/v5/plumbing/transport/http" "github.com/go-git/go-git/v5/plumbing/transport/ssh" - git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" - "github.com/kluctl/kluctl/v2/pkg/status" + "github.com/kluctl/kluctl/v2/pkg/git/git-url" + "github.com/kluctl/kluctl/v2/pkg/git/messages" "strings" ) type ListAuthProvider struct { + MessageCallbacks messages.MessageCallbacks + entries []AuthEntry } @@ -30,10 +32,10 @@ func (a *ListAuthProvider) AddEntry(e AuthEntry) { } func (a *ListAuthProvider) BuildAuth(ctx context.Context, gitUrl git_url.GitUrl) AuthMethodAndCA { - status.Trace(ctx, "ListAuthProvider: BuildAuth for %s", gitUrl.String()) - status.Trace(ctx, "ListAuthProvider: path=%s, username=%s, scheme=%s", gitUrl.Path, gitUrl.User.Username(), gitUrl.Scheme) + a.MessageCallbacks.Trace("ListAuthProvider: BuildAuth for %s", gitUrl.String()) + a.MessageCallbacks.Trace("ListAuthProvider: path=%s, username=%s, scheme=%s", gitUrl.Path, gitUrl.User.Username(), gitUrl.Scheme) for _, e := range a.entries { - status.Trace(ctx, "ListAuthProvider: try host=%s, pathPrefix=%s, username=%s", e.Host, e.PathPrefix, e.Username) + a.MessageCallbacks.Trace("ListAuthProvider: try host=%s, pathPrefix=%s, username=%s", e.Host, e.PathPrefix, e.Username) if e.Host != "*" && e.Host != gitUrl.Hostname() { continue @@ -69,29 +71,29 @@ func (a *ListAuthProvider) BuildAuth(ctx context.Context, gitUrl git_url.GitUrl) if gitUrl.IsSsh() { if e.SshKey == nil { - status.Trace(ctx, "ListAuthProvider: empty ssh key is not accepted") + a.MessageCallbacks.Trace("ListAuthProvider: empty ssh key is not accepted") continue } - status.Trace(ctx, "ListAuthProvider: using username+sshKey") - a, err := ssh.NewPublicKeys(username, e.SshKey, "") + a.MessageCallbacks.Trace("ListAuthProvider: using username+sshKey") + pk, err := ssh.NewPublicKeys(username, e.SshKey, "") if err != nil { - status.Trace(ctx, "ListAuthProvider: failed to parse private key: %v", err) + a.MessageCallbacks.Trace("ListAuthProvider: failed to parse private key: %v", err) } else { - a.HostKeyCallback = buildVerifyHostCallback(ctx, e.KnownHosts) + pk.HostKeyCallback = buildVerifyHostCallback(a.MessageCallbacks, e.KnownHosts) return AuthMethodAndCA{ - AuthMethod: a, + AuthMethod: pk, Hash: func() ([]byte, error) { - return buildHash(a.Signer) + return buildHash(pk.Signer) }, - ClientConfig: a.ClientConfig, + ClientConfig: pk.ClientConfig, } } } else { if e.Password == "" { - status.Trace(ctx, "ListAuthProvider: empty password is not accepted") + a.MessageCallbacks.Trace("ListAuthProvider: empty password is not accepted") continue } - status.Trace(ctx, "ListAuthProvider: using username+password") + a.MessageCallbacks.Trace("ListAuthProvider: using username+password") return AuthMethodAndCA{ AuthMethod: &http.BasicAuth{ Username: username, diff --git a/pkg/git/auth/ssh_auth_provider.go b/pkg/git/auth/ssh_auth_provider.go index c1be1b032..1098ef791 100644 --- a/pkg/git/auth/ssh_auth_provider.go +++ b/pkg/git/auth/ssh_auth_provider.go @@ -7,7 +7,7 @@ import ( "fmt" "github.com/kevinburke/ssh_config" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" - "github.com/kluctl/kluctl/v2/pkg/status" + "github.com/kluctl/kluctl/v2/pkg/git/messages" "github.com/kluctl/kluctl/v2/pkg/utils" sshagent "github.com/xanzy/ssh-agent" "golang.org/x/crypto/ssh" @@ -19,6 +19,8 @@ import ( ) type GitSshAuthProvider struct { + MessageCallbacks messages.MessageCallbacks + passphrases map[string][]byte passphrasesMutex sync.Mutex } @@ -44,7 +46,7 @@ func (a *sshDefaultIdentityAndAgent) ClientConfig() (*ssh.ClientConfig, error) { User: a.user, Auth: []ssh.AuthMethod{ssh.PublicKeysCallback(a.Signers)}, } - cc.HostKeyCallback = buildVerifyHostCallback(a.ctx, nil) + cc.HostKeyCallback = buildVerifyHostCallback(a.authProvider.MessageCallbacks, nil) return cc, nil } @@ -53,49 +55,49 @@ func (a *sshDefaultIdentityAndAgent) Signers() ([]ssh.Signer, error) { } func (a *sshDefaultIdentityAndAgent) addDefaultIdentity(gitUrl git_url.GitUrl) { - status.Trace(a.ctx, "trying to add default identity") + a.authProvider.MessageCallbacks.Trace("trying to add default identity") u, err := user.Current() if err != nil { - status.Trace(a.ctx, "No current user: %v", err) + a.authProvider.MessageCallbacks.Trace("No current user: %v", err) } else { path := filepath.Join(u.HomeDir, ".ssh", "id_rsa") signer, err := a.authProvider.readKey(a.ctx, path) if err != nil && !os.IsNotExist(err) { - status.Warning(a.ctx, "Failed to read default identity file for url %s: %v", gitUrl.String(), err) + a.authProvider.MessageCallbacks.Warning("Failed to read default identity file for url %s: %v", gitUrl.String(), err) } else if signer != nil { - status.Trace(a.ctx, "...added '%s' as default identity", path) + a.authProvider.MessageCallbacks.Trace("...added '%s' as default identity", path) a.signers = append(a.signers, signer) } } } func (a *sshDefaultIdentityAndAgent) addConfigIdentities(gitUrl git_url.GitUrl) { - status.Trace(a.ctx, "trying to add identities from ssh config") + a.authProvider.MessageCallbacks.Trace("trying to add identities from ssh config") for _, id := range ssh_config.GetAll(gitUrl.Hostname(), "IdentityFile") { expanded := utils.ExpandPath(id) - status.Trace(a.ctx, "...trying '%s' (expanded: '%s')", id, expanded) + a.authProvider.MessageCallbacks.Trace("...trying '%s' (expanded: '%s')", id, expanded) signer, err := a.authProvider.readKey(a.ctx, expanded) if err != nil && !os.IsNotExist(err) { - status.Warning(a.ctx, "Failed to read key %s for url %s: %v", id, gitUrl.String(), err) + a.authProvider.MessageCallbacks.Warning("Failed to read key %s for url %s: %v", id, gitUrl.String(), err) } else if err == nil { - status.Trace(a.ctx, "...added '%s' from ssh config", expanded) + a.authProvider.MessageCallbacks.Trace("...added '%s' from ssh config", expanded) a.signers = append(a.signers, signer) } } } func (a *sshDefaultIdentityAndAgent) addAgentIdentities(gitUrl git_url.GitUrl) { - status.Trace(a.ctx, "trying to add agent keys") + a.authProvider.MessageCallbacks.Trace("trying to add agent keys") agent, _, err := sshagent.New() if err != nil { - status.Warning(a.ctx, "Failed to connect to ssh agent for url %s: %v", gitUrl.String(), err) + a.authProvider.MessageCallbacks.Warning("Failed to connect to ssh agent for url %s: %v", gitUrl.String(), err) } else { signers, err := agent.Signers() if err != nil { - status.Warning(a.ctx, "Failed to get signers from ssh agent for url %s: %v", gitUrl.String(), err) + a.authProvider.MessageCallbacks.Warning("Failed to get signers from ssh agent for url %s: %v", gitUrl.String(), err) return } - status.Trace(a.ctx, "...added %d agent keys", len(signers)) + a.authProvider.MessageCallbacks.Trace("...added %d agent keys", len(signers)) a.signers = append(a.signers, signers...) } } @@ -157,7 +159,7 @@ func (k *deferredPassphraseKey) getPassphrase() ([]byte, error) { return passphrase, nil } - passphraseStr, err := status.AskForPassword(k.ctx, fmt.Sprintf("Enter passphrase for key '%s'", k.path)) + passphraseStr, err := k.a.MessageCallbacks.AskForPassword(fmt.Sprintf("Enter passphrase for key '%s'", k.path)) if err != nil { k.a.passphrases[k.path] = nil return nil, err @@ -171,21 +173,21 @@ func (k *deferredPassphraseKey) parse() { passphrase, err := k.getPassphrase() if err != nil { k.err = err - status.Warning(k.ctx, "Failed to parse key %s: %v", k.path, err) + k.a.MessageCallbacks.Warning("Failed to parse key %s: %v", k.path, err) return } pemBytes, err := os.ReadFile(k.path) if err != nil { k.err = err - status.Warning(k.ctx, "Failed to parse key %s: %v", k.path, err) + k.a.MessageCallbacks.Warning("Failed to parse key %s: %v", k.path, err) return } s, err := ssh.ParsePrivateKeyWithPassphrase(pemBytes, passphrase) if err != nil { k.err = err - status.Warning(k.ctx, "Failed to parse key %s: %v", k.path, err) + k.a.MessageCallbacks.Warning("Failed to parse key %s: %v", k.path, err) return } k.parsed = s diff --git a/pkg/git/auth/ssh_known_hosts.go b/pkg/git/auth/ssh_known_hosts.go index ec5df2966..0d0e9b713 100644 --- a/pkg/git/auth/ssh_known_hosts.go +++ b/pkg/git/auth/ssh_known_hosts.go @@ -1,10 +1,9 @@ package auth import ( - "context" "fmt" "github.com/kluctl/kluctl/v2/pkg/git/auth/goph" - "github.com/kluctl/kluctl/v2/pkg/status" + "github.com/kluctl/kluctl/v2/pkg/git/messages" "github.com/kluctl/kluctl/v2/pkg/utils" "golang.org/x/crypto/ssh" "net" @@ -16,13 +15,13 @@ import ( var askHostMutex sync.Mutex -func buildVerifyHostCallback(ctx context.Context, knownHosts []byte) func(hostname string, remote net.Addr, key ssh.PublicKey) error { +func buildVerifyHostCallback(messageCallbacks messages.MessageCallbacks, knownHosts []byte) func(hostname string, remote net.Addr, key ssh.PublicKey) error { return func(hostname string, remote net.Addr, key ssh.PublicKey) error { - return verifyHost(ctx, hostname, remote, key, knownHosts) + return verifyHost(messageCallbacks, hostname, remote, key, knownHosts) } } -func verifyHost(ctx context.Context, host string, remote net.Addr, key ssh.PublicKey, knownHosts []byte) error { +func verifyHost(messageCallbacks messages.MessageCallbacks, host string, remote net.Addr, key ssh.PublicKey, knownHosts []byte) error { // Ensure only one prompt happens at a time askHostMutex.Lock() defer askHostMutex.Unlock() @@ -87,14 +86,14 @@ func verifyHost(ctx context.Context, host string, remote net.Addr, key ssh.Publi return fmt.Errorf("host not found and SSH_KNOWN_HOSTS has been set") } - if !askIsHostTrusted(ctx, host, key) { + if !askIsHostTrusted(messageCallbacks, host, key) { return fmt.Errorf("aborted") } return goph.AddKnownHost(host, remote, key, "") } -func askIsHostTrusted(ctx context.Context, host string, key ssh.PublicKey) bool { +func askIsHostTrusted(messageCallbacks messages.MessageCallbacks, host string, key ssh.PublicKey) bool { prompt := fmt.Sprintf("Unknown Host: %s\nFingerprint: %s\nWould you like to add it? ", host, ssh.FingerprintSHA256(key)) - return status.AskForConfirmation(ctx, prompt) + return messageCallbacks.AskForConfirmation(prompt) } diff --git a/pkg/git/list_refs.go b/pkg/git/list_refs.go index d529c952c..735234d6b 100644 --- a/pkg/git/list_refs.go +++ b/pkg/git/list_refs.go @@ -12,7 +12,6 @@ import ( auth2 "github.com/kluctl/kluctl/v2/pkg/git/auth" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" ssh_pool "github.com/kluctl/kluctl/v2/pkg/git/ssh-pool" - "github.com/kluctl/kluctl/v2/pkg/status" "strconv" ) @@ -100,7 +99,6 @@ func ListRemoteRefs(ctx context.Context, url git_url.GitUrl, sshPool *ssh_pool.S if err == nil { return refs, nil } - status.Warning(ctx, "Fast listing of remote refs failed: %s", err.Error()) } return ListRemoteRefsSlow(ctx, url, auth) } diff --git a/pkg/git/messages/message_callbacks.go b/pkg/git/messages/message_callbacks.go new file mode 100644 index 000000000..e9d8715c4 --- /dev/null +++ b/pkg/git/messages/message_callbacks.go @@ -0,0 +1,39 @@ +package messages + +import "fmt" + +type MessageCallbacks struct { + WarningFn func(s string) + TraceFn func(s string) + AskForPasswordFn func(prompt string) (string, error) + AskForConfirmationFn func(prompt string) bool +} + +func (l MessageCallbacks) Warning(s string, args ...any) { + if l.WarningFn != nil { + l.WarningFn(fmt.Sprintf(s, args...)) + } +} + +func (l MessageCallbacks) Trace(s string, args ...any) { + if l.TraceFn != nil { + l.TraceFn(fmt.Sprintf(s, args...)) + } +} + +func (l MessageCallbacks) AskForPassword(prompt string) (string, error) { + if l.AskForPasswordFn != nil { + return l.AskForPasswordFn(prompt) + } + err := fmt.Errorf("AskForPasswordFn not provided, skipping prompt: %s", prompt) + l.Warning(err.Error()) + return "", err +} + +func (l MessageCallbacks) AskForConfirmation(prompt string) bool { + if l.AskForConfirmationFn != nil { + return l.AskForConfirmationFn(prompt) + } + l.Warning("Not a terminal, suppressed prompt: %s", prompt) + return false +} diff --git a/pkg/git/ssh-pool/ssh_pool.go b/pkg/git/ssh-pool/ssh_pool.go index fb60c220c..7b33a4f1c 100644 --- a/pkg/git/ssh-pool/ssh_pool.go +++ b/pkg/git/ssh-pool/ssh_pool.go @@ -7,7 +7,6 @@ import ( "encoding/hex" "fmt" "github.com/kluctl/kluctl/v2/pkg/git/auth" - "github.com/kluctl/kluctl/v2/pkg/status" "golang.org/x/crypto/ssh" "golang.org/x/net/proxy" "sync" @@ -78,7 +77,6 @@ func (p *SshPool) GetSession(ctx context.Context, host string, port int, auth au s, err := pe.pc.client.NewSession() if err != nil { - origErr := err _ = pe.pc.client.Close() pe.pc = nil if isNew { @@ -92,7 +90,7 @@ func (p *SshPool) GetSession(ctx context.Context, host string, port int, auth au p.pool.Delete(h) return nil, err } - status.Trace(ctx, "Successfully retries failed ssh connection. Old error: %s", origErr) + pe.pc = &poolClient{ time: time.Now(), client: client, diff --git a/pkg/vars/vars_loader_test.go b/pkg/vars/vars_loader_test.go index fc0d510e0..719315987 100644 --- a/pkg/vars/vars_loader_test.go +++ b/pkg/vars/vars_loader_test.go @@ -42,7 +42,7 @@ func newTestDir(t *testing.T) string { } func newRP(t *testing.T) *repocache.GitRepoCache { - grc := repocache.NewGitRepoCache(context.TODO(), &ssh_pool.SshPool{}, auth.NewDefaultAuthProviders(), nil, 0) + grc := repocache.NewGitRepoCache(context.TODO(), &ssh_pool.SshPool{}, auth.NewDefaultAuthProviders(nil), nil, 0) t.Cleanup(func() { grc.Clear() }) From 68f2c00951300c9c8695358345785d7e24a5c791 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 30 Nov 2022 13:03:02 +0100 Subject: [PATCH 1252/2916] refactor: Make git env prefix dynamic --- cmd/kluctl/commands/utils.go | 2 +- pkg/git/auth/auth_provider.go | 4 ++-- pkg/git/auth/env_auth_provider.go | 4 +++- pkg/vars/vars_loader_test.go | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 0a53757ad..e3c827be0 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -67,7 +67,7 @@ func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, strictTemplates b AskForPasswordFn: func(s string) (string, error) { return status.AskForPassword(ctx, s) }, AskForConfirmationFn: func(s string) bool { return status.AskForConfirmation(ctx, s) }, } - gitAuth := auth.NewDefaultAuthProviders(messageCallbacks) + gitAuth := auth.NewDefaultAuthProviders("KLUCTL_GIT", messageCallbacks) rp := repocache.NewGitRepoCache(ctx, sshPool, gitAuth, repoOverrides, projectFlags.GitCacheUpdateInterval) defer rp.Clear() diff --git a/pkg/git/auth/auth_provider.go b/pkg/git/auth/auth_provider.go index 0d77d2cf6..19b2d418e 100644 --- a/pkg/git/auth/auth_provider.go +++ b/pkg/git/auth/auth_provider.go @@ -42,13 +42,13 @@ func (a *GitAuthProviders) BuildAuth(ctx context.Context, gitUrl git_url.GitUrl) return AuthMethodAndCA{} } -func NewDefaultAuthProviders(messageCallbacks *messages.MessageCallbacks) *GitAuthProviders { +func NewDefaultAuthProviders(envPrefix string, messageCallbacks *messages.MessageCallbacks) *GitAuthProviders { if messageCallbacks == nil { messageCallbacks = &messages.MessageCallbacks{} } a := &GitAuthProviders{} - a.RegisterAuthProvider(&GitEnvAuthProvider{MessageCallbacks: *messageCallbacks}, true) + a.RegisterAuthProvider(&GitEnvAuthProvider{MessageCallbacks: *messageCallbacks, Prefix: envPrefix}, true) a.RegisterAuthProvider(&GitCredentialsFileAuthProvider{MessageCallbacks: *messageCallbacks}, true) a.RegisterAuthProvider(&GitSshAuthProvider{MessageCallbacks: *messageCallbacks}, true) return a diff --git a/pkg/git/auth/env_auth_provider.go b/pkg/git/auth/env_auth_provider.go index 76a4a3c8d..7dfa737fb 100644 --- a/pkg/git/auth/env_auth_provider.go +++ b/pkg/git/auth/env_auth_provider.go @@ -11,12 +11,14 @@ import ( type GitEnvAuthProvider struct { MessageCallbacks messages.MessageCallbacks + + Prefix string } func (a *GitEnvAuthProvider) BuildAuth(ctx context.Context, gitUrl git_url.GitUrl) AuthMethodAndCA { var la ListAuthProvider - for _, m := range utils.ParseEnvConfigSets("KLUCTL_GIT") { + for _, m := range utils.ParseEnvConfigSets(a.Prefix) { e := AuthEntry{ Host: m["HOST"], PathPrefix: m["PATH_PREFIX"], diff --git a/pkg/vars/vars_loader_test.go b/pkg/vars/vars_loader_test.go index 719315987..30d6ce755 100644 --- a/pkg/vars/vars_loader_test.go +++ b/pkg/vars/vars_loader_test.go @@ -42,7 +42,7 @@ func newTestDir(t *testing.T) string { } func newRP(t *testing.T) *repocache.GitRepoCache { - grc := repocache.NewGitRepoCache(context.TODO(), &ssh_pool.SshPool{}, auth.NewDefaultAuthProviders(nil), nil, 0) + grc := repocache.NewGitRepoCache(context.TODO(), &ssh_pool.SshPool{}, auth.NewDefaultAuthProviders("KLUCTL_GIT", nil), nil, 0) t.Cleanup(func() { grc.Clear() }) From 18083d897aeba5ba76c08485f78419a53dea5ee1 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 30 Nov 2022 13:04:21 +0100 Subject: [PATCH 1253/2916] refactor: Get rid of pkg/utils dependency from git package --- pkg/git/auth/env_auth_provider.go | 4 +-- pkg/git/auth/git_credentials_file.go | 4 +-- pkg/git/auth/ssh_auth_provider.go | 3 +-- pkg/git/auth/utils.go | 13 ++++++++++ pkg/git/mirrored_repo.go | 37 ++++++++++++++++++---------- pkg/git/repocache/cache.go | 2 +- pkg/git/utils.go | 5 ++-- 7 files changed, 46 insertions(+), 22 deletions(-) create mode 100644 pkg/git/auth/utils.go diff --git a/pkg/git/auth/env_auth_provider.go b/pkg/git/auth/env_auth_provider.go index 7dfa737fb..d88c54e89 100644 --- a/pkg/git/auth/env_auth_provider.go +++ b/pkg/git/auth/env_auth_provider.go @@ -34,7 +34,7 @@ func (a *GitEnvAuthProvider) BuildAuth(ctx context.Context, gitUrl git_url.GitUr a.MessageCallbacks.Trace(fmt.Sprintf("GitEnvAuthProvider: adding entry host=%s, pathPrefix=%s, username=%s, ssh_key=%s", e.Host, e.PathPrefix, e.Username, ssh_key_path)) if ssh_key_path != "" { - ssh_key_path = utils.ExpandPath(ssh_key_path) + ssh_key_path = expandHomeDir(ssh_key_path) b, err := os.ReadFile(ssh_key_path) if err != nil { a.MessageCallbacks.Trace(fmt.Sprintf("GitEnvAuthProvider: failed to read key %s: %v", ssh_key_path, err)) @@ -44,7 +44,7 @@ func (a *GitEnvAuthProvider) BuildAuth(ctx context.Context, gitUrl git_url.GitUr } ca_bundle_path := m["CA_BUNDLE"] if ca_bundle_path != "" { - ca_bundle_path = utils.ExpandPath(ca_bundle_path) + ca_bundle_path = expandHomeDir(ca_bundle_path) b, err := os.ReadFile(ca_bundle_path) if err != nil { a.MessageCallbacks.Trace(fmt.Sprintf("GitEnvAuthProvider: failed to read ca bundle %s: %v", ca_bundle_path, err)) diff --git a/pkg/git/auth/git_credentials_file.go b/pkg/git/auth/git_credentials_file.go index 5d3ef3cfd..68989ca85 100644 --- a/pkg/git/auth/git_credentials_file.go +++ b/pkg/git/auth/git_credentials_file.go @@ -5,7 +5,6 @@ import ( "context" "github.com/go-git/go-git/v5/plumbing/transport/http" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" - "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/git/messages" giturls "github.com/whilp/git-urls" "os" @@ -46,7 +45,8 @@ func (a *GitCredentialsFileAuthProvider) BuildAuth(ctx context.Context, gitUrl g } func (a *GitCredentialsFileAuthProvider) tryBuildAuth(gitUrl git_url.GitUrl, gitCredentialsPath string) *AuthMethodAndCA { - if !utils.IsFile(gitCredentialsPath) { + st, err := os.Stat(gitCredentialsPath) + if err != nil || !st.Mode().IsDir() { return nil } diff --git a/pkg/git/auth/ssh_auth_provider.go b/pkg/git/auth/ssh_auth_provider.go index 1098ef791..2e95cb864 100644 --- a/pkg/git/auth/ssh_auth_provider.go +++ b/pkg/git/auth/ssh_auth_provider.go @@ -8,7 +8,6 @@ import ( "github.com/kevinburke/ssh_config" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/git/messages" - "github.com/kluctl/kluctl/v2/pkg/utils" sshagent "github.com/xanzy/ssh-agent" "golang.org/x/crypto/ssh" "io" @@ -74,7 +73,7 @@ func (a *sshDefaultIdentityAndAgent) addDefaultIdentity(gitUrl git_url.GitUrl) { func (a *sshDefaultIdentityAndAgent) addConfigIdentities(gitUrl git_url.GitUrl) { a.authProvider.MessageCallbacks.Trace("trying to add identities from ssh config") for _, id := range ssh_config.GetAll(gitUrl.Hostname(), "IdentityFile") { - expanded := utils.ExpandPath(id) + expanded := expandHomeDir(id) a.authProvider.MessageCallbacks.Trace("...trying '%s' (expanded: '%s')", id, expanded) signer, err := a.authProvider.readKey(a.ctx, expanded) if err != nil && !os.IsNotExist(err) { diff --git a/pkg/git/auth/utils.go b/pkg/git/auth/utils.go new file mode 100644 index 000000000..ff0c9e834 --- /dev/null +++ b/pkg/git/auth/utils.go @@ -0,0 +1,13 @@ +package auth + +import ( + "k8s.io/client-go/util/homedir" + "strings" +) + +func expandHomeDir(p string) string { + if strings.HasPrefix(p, "~/") { + p = homedir.HomeDir() + p[1:] + } + return p +} diff --git a/pkg/git/mirrored_repo.go b/pkg/git/mirrored_repo.go index 14262b6fd..10e2e805c 100644 --- a/pkg/git/mirrored_repo.go +++ b/pkg/git/mirrored_repo.go @@ -2,6 +2,8 @@ package git import ( "context" + "crypto/sha256" + "encoding/hex" "fmt" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/config" @@ -11,7 +13,6 @@ import ( git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" _ "github.com/kluctl/kluctl/v2/pkg/git/ssh-pool" ssh_pool "github.com/kluctl/kluctl/v2/pkg/git/ssh-pool" - "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/rogpeppe/go-internal/lockedfile" "os" "path/filepath" @@ -20,8 +21,6 @@ import ( "time" ) -var cacheBaseDir = filepath.Join(utils.GetTmpBaseDir(), "git-cache") - var defaultFetch = []config.RefSpec{ "+refs/heads/*:refs/heads/*", "+refs/tags/*:refs/tags/*", @@ -30,6 +29,7 @@ var defaultFetch = []config.RefSpec{ type MirroredGitRepo struct { ctx context.Context + baseDir string sshPool *ssh_pool.SshPool authProviders *auth2.GitAuthProviders @@ -44,21 +44,25 @@ type MirroredGitRepo struct { mutex sync.Mutex } -func NewMirroredGitRepo(ctx context.Context, u git_url.GitUrl, sshPool *ssh_pool.SshPool, authProviders *auth2.GitAuthProviders) (*MirroredGitRepo, error) { +func NewMirroredGitRepo(ctx context.Context, u git_url.GitUrl, baseDir string, sshPool *ssh_pool.SshPool, authProviders *auth2.GitAuthProviders) (*MirroredGitRepo, error) { mirrorRepoName := buildMirrorRepoName(u) o := &MirroredGitRepo{ ctx: ctx, + baseDir: baseDir, sshPool: sshPool, authProviders: authProviders, url: u, - mirrorDir: filepath.Join(cacheBaseDir, mirrorRepoName), + mirrorDir: filepath.Join(baseDir, mirrorRepoName), } - if !utils.IsDirectory(o.mirrorDir) { - err := os.MkdirAll(o.mirrorDir, 0o700) + st, err := os.Stat(o.mirrorDir) + if err != nil { + err = os.MkdirAll(o.mirrorDir, 0o700) if err != nil { return nil, fmt.Errorf("failed to create mirror repo for %v: %w", u.String(), err) } + } else if !st.IsDir() { + return nil, fmt.Errorf("%s is not a directory", o.mirrorDir) } o.fileLockPath = filepath.Join(o.mirrorDir, ".cache.lock") @@ -179,7 +183,8 @@ func (g *MirroredGitRepo) buildRepositoryObject() (*git.Repository, error) { } func (g *MirroredGitRepo) cleanupMirrorDir() error { - if utils.IsDirectory(g.mirrorDir) { + st, err := os.Stat(g.mirrorDir) + if err == nil && st.IsDir() { files, err := os.ReadDir(g.mirrorDir) if err != nil { return err @@ -284,15 +289,16 @@ func (g *MirroredGitRepo) update(repoDir string) error { func (g *MirroredGitRepo) cloneOrUpdate() error { initMarker := filepath.Join(g.mirrorDir, ".cache2.init") - if utils.IsFile(initMarker) { + st, err := os.Stat(initMarker) + if err == nil && st.Mode().IsRegular() { return g.update(g.mirrorDir) } - err := g.cleanupMirrorDir() + err = g.cleanupMirrorDir() if err != nil { return err } - tmpMirrorDir, err := os.MkdirTemp(utils.GetTmpBaseDir(), "mirror-") + tmpMirrorDir, err := os.MkdirTemp(g.baseDir, "tmp-mirror-") if err != nil { return err } @@ -327,10 +333,11 @@ func (g *MirroredGitRepo) cloneOrUpdate() error { return err } } - err = utils.Touch(initMarker) + f, err := os.Create(initMarker) if err != nil { return err } + defer f.Close() return nil } @@ -383,12 +390,16 @@ func (g *MirroredGitRepo) GetGitTreeByCommit(commitHash string) (*object.Tree, e } func buildMirrorRepoName(u git_url.GitUrl) string { + h := sha256.New() + h.Write([]byte(u.String())) + h2 := hex.EncodeToString(h.Sum(nil)) + r := filepath.Base(u.Path) r = strings.ReplaceAll(r, "/", "-") r = strings.ReplaceAll(r, "\\", "-") if strings.HasSuffix(r, ".git") { r = r[:len(r)-len(".git")] } - r += "-" + utils.Sha256String(u.String())[:6] + r += "-" + h2[:6] return r } diff --git a/pkg/git/repocache/cache.go b/pkg/git/repocache/cache.go index aa0cb0c91..1aecfa179 100644 --- a/pkg/git/repocache/cache.go +++ b/pkg/git/repocache/cache.go @@ -86,7 +86,7 @@ func (rp *GitRepoCache) GetEntry(url git_url.GitUrl) (*CacheEntry, error) { e, ok := rp.repos[url.NormalizedRepoKey()] if !ok { - mr, err := git.NewMirroredGitRepo(rp.ctx, url, rp.sshPool, rp.authProviders) + mr, err := git.NewMirroredGitRepo(rp.ctx, url, filepath.Join(utils.GetTmpBaseDir(), "git-cache"), rp.sshPool, rp.authProviders) if err != nil { return nil, err } diff --git a/pkg/git/utils.go b/pkg/git/utils.go index 0c0bb72d0..f007fa3ee 100644 --- a/pkg/git/utils.go +++ b/pkg/git/utils.go @@ -3,7 +3,7 @@ package git import ( "fmt" "github.com/go-git/go-git/v5" - "github.com/kluctl/kluctl/v2/pkg/utils" + "os" "path/filepath" ) @@ -32,7 +32,8 @@ func DetectGitRepositoryRoot(path string) (string, error) { return "", err } for true { - if utils.Exists(filepath.Join(path, ".git")) { + st, err := os.Stat(filepath.Join(path, ".git")) + if err == nil && st.IsDir() { break } old := path From 580352ec507d2972c9b03c84bc7a5761511a4601 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 30 Nov 2022 13:13:58 +0100 Subject: [PATCH 1254/2916] refactor: Remove custom Copy functions from utils and use otiai10/copy instead --- e2e/test-utils/helm_repo_utils.go | 20 ++++++-- e2e/test_resources/resources.go | 7 ++- go.mod | 1 + go.sum | 7 +++ pkg/git/poor_mans_clone.go | 14 +++--- pkg/git/repocache/cache.go | 3 +- pkg/utils/fs.go | 79 ------------------------------- 7 files changed, 37 insertions(+), 94 deletions(-) diff --git a/e2e/test-utils/helm_repo_utils.go b/e2e/test-utils/helm_repo_utils.go index fc5f422f2..aabe7835b 100644 --- a/e2e/test-utils/helm_repo_utils.go +++ b/e2e/test-utils/helm_repo_utils.go @@ -3,9 +3,9 @@ package test_utils import ( "github.com/google/go-containerregistry/pkg/registry" "github.com/kluctl/kluctl/v2/e2e/test-utils/test-helm-chart" - "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" + cp "github.com/otiai10/copy" "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/cli" "helm.sh/helm/v3/pkg/cli/values" @@ -14,6 +14,7 @@ import ( registry2 "helm.sh/helm/v3/pkg/registry" "helm.sh/helm/v3/pkg/repo" "helm.sh/helm/v3/pkg/uploader" + "io/fs" "net/http" "net/http/httptest" "net/url" @@ -25,7 +26,18 @@ import ( func createHelmPackage(t *testing.T, name string, version string) string { tmpDir := t.TempDir() - err := utils.FsCopyDir(test_resources.HelmChartFS, ".", tmpDir) + + err := fs.WalkDir(test_resources.HelmChartFS, ".", func(path string, d fs.DirEntry, err error) error { + if d.IsDir() { + return os.MkdirAll(filepath.Join(tmpDir, path), 0o700) + } else { + b, err := test_resources.HelmChartFS.ReadFile(path) + if err != nil { + return err + } + return os.WriteFile(filepath.Join(tmpDir, path), b, 0o600) + } + }) if err != nil { t.Fatal(err) } @@ -67,7 +79,7 @@ func CreateHelmRepo(t *testing.T, charts []RepoChart, username string, password for _, c := range charts { tgz := createHelmPackage(t, c.ChartName, c.Version) - _ = utils.CopyFile(tgz, filepath.Join(tmpDir, filepath.Base(tgz))) + _ = cp.Copy(tgz, filepath.Join(tmpDir, filepath.Base(tgz))) } fs := http.FileServer(http.FS(os.DirFS(tmpDir))) @@ -147,7 +159,7 @@ func CreateOciRepo(t *testing.T, charts []RepoChart, username string, password s for _, chart := range charts { tgz := createHelmPackage(t, chart.ChartName, chart.Version) - _ = utils.CopyFile(tgz, filepath.Join(tmpDir, filepath.Base(tgz))) + _ = cp.Copy(tgz, filepath.Join(tmpDir, filepath.Base(tgz))) err := c.UploadTo(tgz, ociUrl) if err != nil { diff --git a/e2e/test_resources/resources.go b/e2e/test_resources/resources.go index fa1754bff..23dad66c9 100644 --- a/e2e/test_resources/resources.go +++ b/e2e/test_resources/resources.go @@ -4,7 +4,6 @@ import ( "context" "embed" "github.com/kluctl/kluctl/v2/e2e/test-utils" - "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/validation" "github.com/kluctl/kluctl/v2/pkg/yaml" @@ -28,7 +27,11 @@ func GetYamlTmpFile(name string) string { } tmpFile.Close() - err = utils.FsCopyFile(Yamls, name, tmpFile.Name()) + b, err := Yamls.ReadFile(name) + if err != nil { + panic(err) + } + err = os.WriteFile(tmpFile.Name(), b, 0o600) if err != nil { panic(err) } diff --git a/go.mod b/go.mod index aa0533f25..ae00085ce 100644 --- a/go.mod +++ b/go.mod @@ -61,6 +61,7 @@ require ( github.com/go-logr/logr v1.2.3 github.com/hashicorp/go-multierror v1.1.1 github.com/huandu/xstrings v1.3.3 + github.com/otiai10/copy v1.9.0 go.mozilla.org/sops/v3 v3.7.3 sigs.k8s.io/controller-runtime v0.13.1 ) diff --git a/go.sum b/go.sum index 3acee807f..2a56447bd 100644 --- a/go.sum +++ b/go.sum @@ -663,6 +663,13 @@ github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7X github.com/opencontainers/image-spec v1.1.0-rc2/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= github.com/opencontainers/runc v1.1.2 h1:2VSZwLx5k/BfsBxMMipG/LYUnmqOD/BPkIVgQUcTlLw= github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= +github.com/otiai10/copy v1.9.0 h1:7KFNiCgZ91Ru4qW4CWPf/7jqtxLagGRmIxWldPP9VY4= +github.com/otiai10/copy v1.9.0/go.mod h1:hsfX19wcn0UWIHUQ3/4fHuehhk2UyArQ9dVFAn3FczI= +github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= +github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= +github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= +github.com/otiai10/mint v1.4.0 h1:umwcf7gbpEwf7WFzqmWwSv0CzbeMsae2u9ZvpP8j2q4= +github.com/otiai10/mint v1.4.0/go.mod h1:gifjb2MYOoULtKLqUAEILUG/9KONW6f7YsJ6vQLTlFI= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= diff --git a/pkg/git/poor_mans_clone.go b/pkg/git/poor_mans_clone.go index f5f306924..975112564 100644 --- a/pkg/git/poor_mans_clone.go +++ b/pkg/git/poor_mans_clone.go @@ -3,7 +3,7 @@ package git import ( "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/config" - "github.com/kluctl/kluctl/v2/pkg/utils" + cp "github.com/otiai10/copy" "os" "path/filepath" "runtime" @@ -32,15 +32,13 @@ func PoorMansClone(sourceDir string, targetDir string, coOptions *git.CheckoutOp if de.Name() == "objects" { err = os.Symlink(s, d) if err != nil && runtime.GOOS == "windows" { - // windows 10 does not support symlinks as unprivileged users... - err = utils.CopyDir(s, d) + // Windows 10 does not support symlinks as unprivileged users, so we revert to deep copying + err = cp.Copy(s, d, cp.Options{OnSymlink: func(src string) cp.SymlinkAction { + return cp.Deep + }}) } } else { - if de.IsDir() { - err = utils.CopyDir(s, d) - } else { - err = utils.CopyFile(s, d) - } + err = cp.Copy(s, d) } if err != nil { return err diff --git a/pkg/git/repocache/cache.go b/pkg/git/repocache/cache.go index 1aecfa179..88d20f53a 100644 --- a/pkg/git/repocache/cache.go +++ b/pkg/git/repocache/cache.go @@ -9,6 +9,7 @@ import ( ssh_pool "github.com/kluctl/kluctl/v2/pkg/git/ssh-pool" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/utils" + cp "github.com/otiai10/copy" "os" "path" "path/filepath" @@ -235,7 +236,7 @@ func (e *CacheEntry) GetClonedDir(ref string) (string, git.CheckoutInfo, error) if foundRo != nil { u := e.mr.Url() status.WarningOnce(e.rp.ctx, fmt.Sprintf("git-override-%s|%s", foundRo.RepoKey, foundRo.Ref), "Overriding git repo %s with local directory %s", u.String(), foundRo.Override) - err = utils.CopyDir(foundRo.Override, p) + err = cp.Copy(foundRo.Override, p) if err != nil { return "", git.CheckoutInfo{}, err } diff --git a/pkg/utils/fs.go b/pkg/utils/fs.go index 0b25e1e4b..1c6909e00 100644 --- a/pkg/utils/fs.go +++ b/pkg/utils/fs.go @@ -2,11 +2,8 @@ package utils import ( "fmt" - "io" - "io/fs" "k8s.io/client-go/util/homedir" "os" - "path" "path/filepath" "strings" ) @@ -69,82 +66,6 @@ func Touch(path string) error { return f.Close() } -func CopyFile(src string, dst string) error { - if !IsFile(src) { - return fmt.Errorf("%s is not a regular file", src) - } - f, err := os.Open(src) - if err != nil { - return err - } - defer f.Close() - return CopyFileStream(f, dst) -} - -func FsCopyFile(srcFs fs.FS, src, dst string) error { - src = filepath.ToSlash(src) - source, err := srcFs.Open(src) - if err != nil { - return err - } - defer source.Close() - - sourceFileStat, err := source.Stat() - if err != nil { - return err - } - - if !sourceFileStat.Mode().IsRegular() { - return fmt.Errorf("%s is not a regular file", src) - } - - return CopyFileStream(source, dst) -} - -func CopyFileStream(src io.Reader, dst string) error { - destination, err := os.Create(dst) - if err != nil { - return err - } - defer destination.Close() - - _, err = io.Copy(destination, src) - return err -} - -func FsCopyDir(srcFs fs.FS, src string, dst string) error { - var err error - var fds []fs.DirEntry - - src = filepath.ToSlash(src) - - if fds, err = fs.ReadDir(srcFs, src); err != nil { - return err - } - if err = os.MkdirAll(dst, 0o700); err != nil { - return err - } - for _, fd := range fds { - srcfp := path.Join(src, fd.Name()) - dstfp := filepath.Join(dst, fd.Name()) - - if fd.IsDir() { - if err = FsCopyDir(srcFs, srcfp, dstfp); err != nil { - return err - } - } else { - if err = FsCopyFile(srcFs, srcfp, dstfp); err != nil { - return err - } - } - } - return nil -} - -func CopyDir(src string, dst string) error { - return FsCopyDir(os.DirFS(src), ".", dst) -} - func ExpandPath(p string) string { if strings.HasPrefix(p, "~/") { p = homedir.HomeDir() + p[1:] From a152db00b4cd887d624c444c843d4940ebe329cb Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 30 Nov 2022 13:15:56 +0100 Subject: [PATCH 1255/2916] refactor: Move repocache package out of git package --- cmd/kluctl/commands/utils.go | 2 +- pkg/deployment/shared_context.go | 2 +- pkg/kluctl_project/project.go | 2 +- pkg/kluctl_project/project_load.go | 2 +- pkg/{git => }/repocache/cache.go | 0 pkg/vars/vars_loader.go | 2 +- pkg/vars/vars_loader_test.go | 2 +- 7 files changed, 6 insertions(+), 6 deletions(-) rename pkg/{git => }/repocache/cache.go (100%) diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index e3c827be0..221f97ac0 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -9,11 +9,11 @@ import ( "github.com/kluctl/kluctl/v2/pkg/git/auth" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/git/messages" - "github.com/kluctl/kluctl/v2/pkg/git/repocache" ssh_pool "github.com/kluctl/kluctl/v2/pkg/git/ssh-pool" "github.com/kluctl/kluctl/v2/pkg/kluctl_jinja2" "github.com/kluctl/kluctl/v2/pkg/kluctl_project" "github.com/kluctl/kluctl/v2/pkg/registries" + "github.com/kluctl/kluctl/v2/pkg/repocache" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" diff --git a/pkg/deployment/shared_context.go b/pkg/deployment/shared_context.go index f5334c195..be1492050 100644 --- a/pkg/deployment/shared_context.go +++ b/pkg/deployment/shared_context.go @@ -2,8 +2,8 @@ package deployment import ( "context" - "github.com/kluctl/kluctl/v2/pkg/git/repocache" "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/repocache" "github.com/kluctl/kluctl/v2/pkg/sops" "github.com/kluctl/kluctl/v2/pkg/vars" ) diff --git a/pkg/kluctl_project/project.go b/pkg/kluctl_project/project.go index 1701abfc2..0df49e01c 100644 --- a/pkg/kluctl_project/project.go +++ b/pkg/kluctl_project/project.go @@ -4,7 +4,7 @@ import ( "context" "fmt" "github.com/kluctl/go-jinja2" - "github.com/kluctl/kluctl/v2/pkg/git/repocache" + "github.com/kluctl/kluctl/v2/pkg/repocache" "github.com/kluctl/kluctl/v2/pkg/sops" types2 "github.com/kluctl/kluctl/v2/pkg/types" ) diff --git a/pkg/kluctl_project/project_load.go b/pkg/kluctl_project/project_load.go index dfe0cbe2d..4c075fd23 100644 --- a/pkg/kluctl_project/project_load.go +++ b/pkg/kluctl_project/project_load.go @@ -1,7 +1,7 @@ package kluctl_project import ( - "github.com/kluctl/kluctl/v2/pkg/git/repocache" + "github.com/kluctl/kluctl/v2/pkg/repocache" "github.com/kluctl/kluctl/v2/pkg/sops" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/utils" diff --git a/pkg/git/repocache/cache.go b/pkg/repocache/cache.go similarity index 100% rename from pkg/git/repocache/cache.go rename to pkg/repocache/cache.go diff --git a/pkg/vars/vars_loader.go b/pkg/vars/vars_loader.go index 226a5d5e3..6ead52755 100644 --- a/pkg/vars/vars_loader.go +++ b/pkg/vars/vars_loader.go @@ -5,8 +5,8 @@ import ( "encoding/base64" "fmt" "github.com/kluctl/go-jinja2" - "github.com/kluctl/kluctl/v2/pkg/git/repocache" "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/repocache" "github.com/kluctl/kluctl/v2/pkg/sops" "github.com/kluctl/kluctl/v2/pkg/types" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" diff --git a/pkg/vars/vars_loader_test.go b/pkg/vars/vars_loader_test.go index 30d6ce755..be4b18fa8 100644 --- a/pkg/vars/vars_loader_test.go +++ b/pkg/vars/vars_loader_test.go @@ -7,9 +7,9 @@ import ( "github.com/kluctl/kluctl/v2/e2e/test-utils" "github.com/kluctl/kluctl/v2/pkg/git/auth" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" - "github.com/kluctl/kluctl/v2/pkg/git/repocache" ssh_pool "github.com/kluctl/kluctl/v2/pkg/git/ssh-pool" "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/repocache" "github.com/kluctl/kluctl/v2/pkg/sops" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" From 1635e682aa09d22019e0bc5e440e9aa95ae62181 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 30 Nov 2022 13:17:14 +0100 Subject: [PATCH 1256/2916] refactor: Rename GitServer to TestGitServer --- e2e/test-utils/project.go | 4 +-- .../{git_server.go => test_git_server.go} | 30 +++++++++---------- pkg/vars/vars_loader_test.go | 4 +-- 3 files changed, 19 insertions(+), 19 deletions(-) rename e2e/test-utils/{git_server.go => test_git_server.go} (80%) diff --git a/e2e/test-utils/project.go b/e2e/test-utils/project.go index 51183ffef..d9a5a4bd3 100644 --- a/e2e/test-utils/project.go +++ b/e2e/test-utils/project.go @@ -24,7 +24,7 @@ type TestProject struct { mergedKubeconfig string - gitServer *GitServer + gitServer *TestGitServer } func NewTestProject(t *testing.T, k *EnvTestCluster) *TestProject { @@ -32,7 +32,7 @@ func NewTestProject(t *testing.T, k *EnvTestCluster) *TestProject { t: t, } - p.gitServer = NewGitServer(t) + p.gitServer = NewTestGitServer(t) p.gitServer.GitInit("kluctl-project") p.UpdateKluctlYaml(func(o *uo.UnstructuredObject) error { diff --git a/e2e/test-utils/git_server.go b/e2e/test-utils/test_git_server.go similarity index 80% rename from e2e/test-utils/git_server.go rename to e2e/test-utils/test_git_server.go index 6e45a3031..dd163fafb 100644 --- a/e2e/test-utils/git_server.go +++ b/e2e/test-utils/test_git_server.go @@ -17,7 +17,7 @@ import ( "testing" ) -type GitServer struct { +type TestGitServer struct { t *testing.T baseDir string @@ -27,8 +27,8 @@ type GitServer struct { gitServerPort int } -func NewGitServer(t *testing.T) *GitServer { - p := &GitServer{ +func NewTestGitServer(t *testing.T) *TestGitServer { + p := &TestGitServer{ t: t, } @@ -47,7 +47,7 @@ func NewGitServer(t *testing.T) *GitServer { return p } -func (p *GitServer) initGitServer() { +func (p *TestGitServer) initGitServer() { p.gitServer = http_server.New(p.baseDir) p.gitHttpServer = &http.Server{ @@ -67,7 +67,7 @@ func (p *GitServer) initGitServer() { }() } -func (p *GitServer) Cleanup() { +func (p *TestGitServer) Cleanup() { if p.gitHttpServer != nil { _ = p.gitHttpServer.Shutdown(context.Background()) p.gitHttpServer = nil @@ -81,7 +81,7 @@ func (p *GitServer) Cleanup() { p.baseDir = "" } -func (p *GitServer) GitInit(repo string) { +func (p *TestGitServer) GitInit(repo string) { dir := p.LocalRepoDir(repo) err := os.MkdirAll(dir, 0o700) @@ -124,7 +124,7 @@ func (p *GitServer) GitInit(repo string) { } } -func (p *GitServer) CommitFiles(repo string, add []string, all bool, message string) { +func (p *TestGitServer) CommitFiles(repo string, add []string, all bool, message string) { r, err := git.PlainOpen(p.LocalRepoDir(repo)) if err != nil { p.t.Fatal(err) @@ -147,7 +147,7 @@ func (p *GitServer) CommitFiles(repo string, add []string, all bool, message str } } -func (p *GitServer) CommitYaml(repo string, pth string, message string, y *uo.UnstructuredObject) { +func (p *TestGitServer) CommitYaml(repo string, pth string, message string, y *uo.UnstructuredObject) { fullPath := filepath.Join(p.LocalRepoDir(repo), pth) dir, _ := filepath.Split(fullPath) @@ -168,7 +168,7 @@ func (p *GitServer) CommitYaml(repo string, pth string, message string, y *uo.Un p.CommitFiles(repo, []string{pth}, false, message) } -func (p *GitServer) UpdateFile(repo string, pth string, update func(f string) (string, error), message string) { +func (p *TestGitServer) UpdateFile(repo string, pth string, update func(f string) (string, error), message string) { fullPath := filepath.Join(p.LocalRepoDir(repo), pth) f := "" if utils.Exists(fullPath) { @@ -194,7 +194,7 @@ func (p *GitServer) UpdateFile(repo string, pth string, update func(f string) (s p.CommitFiles(repo, []string{pth}, false, message) } -func (p *GitServer) UpdateYaml(repo string, pth string, update func(o *uo.UnstructuredObject) error, message string) { +func (p *TestGitServer) UpdateYaml(repo string, pth string, update func(o *uo.UnstructuredObject) error, message string) { fullPath := filepath.Join(p.LocalRepoDir(repo), pth) o := uo.New() @@ -215,7 +215,7 @@ func (p *GitServer) UpdateYaml(repo string, pth string, update func(o *uo.Unstru p.CommitYaml(repo, pth, message, o) } -func (p *GitServer) convertInterfaceToList(x interface{}) []interface{} { +func (p *TestGitServer) convertInterfaceToList(x interface{}) []interface{} { var ret []interface{} if l, ok := x.([]interface{}); ok { return l @@ -235,15 +235,15 @@ func (p *GitServer) convertInterfaceToList(x interface{}) []interface{} { return []interface{}{x} } -func (p *GitServer) GitUrl(repo string) string { +func (p *TestGitServer) GitUrl(repo string) string { return fmt.Sprintf("http://localhost:%d/%s/.git", p.gitServerPort, repo) } -func (p *GitServer) LocalRepoDir(repo string) string { +func (p *TestGitServer) LocalRepoDir(repo string) string { return filepath.Join(p.baseDir, repo) } -func (p *GitServer) GetGitRepo(repo string) *git.Repository { +func (p *TestGitServer) GetGitRepo(repo string) *git.Repository { r, err := git.PlainOpen(p.LocalRepoDir(repo)) if err != nil { p.t.Fatal(err) @@ -251,7 +251,7 @@ func (p *GitServer) GetGitRepo(repo string) *git.Repository { return r } -func (p *GitServer) GetWorktree(repo string) *git.Worktree { +func (p *TestGitServer) GetWorktree(repo string) *git.Worktree { r := p.GetGitRepo(repo) wt, err := r.Worktree() if err != nil { diff --git a/pkg/vars/vars_loader_test.go b/pkg/vars/vars_loader_test.go index be4b18fa8..729970b94 100644 --- a/pkg/vars/vars_loader_test.go +++ b/pkg/vars/vars_loader_test.go @@ -155,7 +155,7 @@ func TestVarsLoader_FileWithLoadNotExists(t *testing.T) { } func TestVarsLoader_Git(t *testing.T) { - gs := test_utils.NewGitServer(t) + gs := test_utils.NewTestGitServer(t) gs.GitInit("repo") gs.UpdateYaml("repo", "test.yaml", func(o *uo.UnstructuredObject) error { *o = *uo.FromStringMust(`{"test1": {"test2": 42}}`) @@ -178,7 +178,7 @@ func TestVarsLoader_Git(t *testing.T) { } func TestVarsLoader_GitBranch(t *testing.T) { - gs := test_utils.NewGitServer(t) + gs := test_utils.NewTestGitServer(t) gs.GitInit("repo") wt := gs.GetWorktree("repo") From 1ed0c22ef21d87491d79d5c2c37e1adcd3463d5d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 30 Nov 2022 13:17:57 +0100 Subject: [PATCH 1257/2916] refactor: Move TestGitServer into git package --- e2e/test-utils/project.go | 5 +++-- {e2e/test-utils => pkg/git}/test_git_server.go | 2 +- pkg/vars/vars_loader_test.go | 6 +++--- 3 files changed, 7 insertions(+), 6 deletions(-) rename {e2e/test-utils => pkg/git}/test_git_server.go (99%) diff --git a/e2e/test-utils/project.go b/e2e/test-utils/project.go index d9a5a4bd3..b07fb529d 100644 --- a/e2e/test-utils/project.go +++ b/e2e/test-utils/project.go @@ -5,6 +5,7 @@ import ( "github.com/go-git/go-git/v5" "github.com/huandu/xstrings" "github.com/imdario/mergo" + git2 "github.com/kluctl/kluctl/v2/pkg/git" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" registry2 "helm.sh/helm/v3/pkg/registry" @@ -24,7 +25,7 @@ type TestProject struct { mergedKubeconfig string - gitServer *TestGitServer + gitServer *git2.TestGitServer } func NewTestProject(t *testing.T, k *EnvTestCluster) *TestProject { @@ -32,7 +33,7 @@ func NewTestProject(t *testing.T, k *EnvTestCluster) *TestProject { t: t, } - p.gitServer = NewTestGitServer(t) + p.gitServer = git2.NewTestGitServer(t) p.gitServer.GitInit("kluctl-project") p.UpdateKluctlYaml(func(o *uo.UnstructuredObject) error { diff --git a/e2e/test-utils/test_git_server.go b/pkg/git/test_git_server.go similarity index 99% rename from e2e/test-utils/test_git_server.go rename to pkg/git/test_git_server.go index dd163fafb..2e7805c7c 100644 --- a/e2e/test-utils/test_git_server.go +++ b/pkg/git/test_git_server.go @@ -1,4 +1,4 @@ -package test_utils +package git import ( "context" diff --git a/pkg/vars/vars_loader_test.go b/pkg/vars/vars_loader_test.go index 729970b94..18e342465 100644 --- a/pkg/vars/vars_loader_test.go +++ b/pkg/vars/vars_loader_test.go @@ -4,7 +4,7 @@ import ( "context" git2 "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" - "github.com/kluctl/kluctl/v2/e2e/test-utils" + "github.com/kluctl/kluctl/v2/pkg/git" "github.com/kluctl/kluctl/v2/pkg/git/auth" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" ssh_pool "github.com/kluctl/kluctl/v2/pkg/git/ssh-pool" @@ -155,7 +155,7 @@ func TestVarsLoader_FileWithLoadNotExists(t *testing.T) { } func TestVarsLoader_Git(t *testing.T) { - gs := test_utils.NewTestGitServer(t) + gs := git.NewTestGitServer(t) gs.GitInit("repo") gs.UpdateYaml("repo", "test.yaml", func(o *uo.UnstructuredObject) error { *o = *uo.FromStringMust(`{"test1": {"test2": 42}}`) @@ -178,7 +178,7 @@ func TestVarsLoader_Git(t *testing.T) { } func TestVarsLoader_GitBranch(t *testing.T) { - gs := test_utils.NewTestGitServer(t) + gs := git.NewTestGitServer(t) gs.GitInit("repo") wt := gs.GetWorktree("repo") From 4585fe5f9790a4f5dbe0511abf62af158e9bb731 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 30 Nov 2022 15:30:04 +0100 Subject: [PATCH 1258/2916] refactor: Get rid of utils dependency in TestGitServer --- pkg/git/test_git_server.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/git/test_git_server.go b/pkg/git/test_git_server.go index 2e7805c7c..5a1cf29b4 100644 --- a/pkg/git/test_git_server.go +++ b/pkg/git/test_git_server.go @@ -5,7 +5,6 @@ import ( "fmt" "github.com/go-git/go-git/v5" http_server "github.com/kluctl/kluctl/v2/pkg/git/http-server" - "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" "log" @@ -110,10 +109,11 @@ func (p *TestGitServer) GitInit(repo string) { if err != nil { p.t.Fatal(err) } - err = utils.Touch(filepath.Join(dir, ".dummy")) + f, err := os.Create(filepath.Join(dir, ".dummy")) if err != nil { p.t.Fatal(err) } + _ = f.Close() _, err = wt.Add(".dummy") if err != nil { p.t.Fatal(err) @@ -171,7 +171,7 @@ func (p *TestGitServer) CommitYaml(repo string, pth string, message string, y *u func (p *TestGitServer) UpdateFile(repo string, pth string, update func(f string) (string, error), message string) { fullPath := filepath.Join(p.LocalRepoDir(repo), pth) f := "" - if utils.Exists(fullPath) { + if _, err := os.Stat(fullPath); err == nil { b, err := os.ReadFile(fullPath) if err != nil { p.t.Fatal(err) @@ -198,7 +198,7 @@ func (p *TestGitServer) UpdateYaml(repo string, pth string, update func(o *uo.Un fullPath := filepath.Join(p.LocalRepoDir(repo), pth) o := uo.New() - if utils.Exists(fullPath) { + if _, err := os.Stat(fullPath); err == nil { err := yaml.ReadYamlFile(fullPath, o) if err != nil { p.t.Fatal(err) From 0c450715d1c2c1363408113771862d7824a00808 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 30 Nov 2022 15:27:31 +0100 Subject: [PATCH 1259/2916] refactor: Get rid of uo dependency in TestGitServer --- pkg/git/test_git_server.go | 54 ++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/pkg/git/test_git_server.go b/pkg/git/test_git_server.go index 5a1cf29b4..fcbd28883 100644 --- a/pkg/git/test_git_server.go +++ b/pkg/git/test_git_server.go @@ -4,9 +4,9 @@ import ( "context" "fmt" "github.com/go-git/go-git/v5" + "github.com/jinzhu/copier" http_server "github.com/kluctl/kluctl/v2/pkg/git/http-server" - "github.com/kluctl/kluctl/v2/pkg/utils/uo" - "github.com/kluctl/kluctl/v2/pkg/yaml" + "gopkg.in/yaml.v3" "log" "net" "net/http" @@ -147,7 +147,7 @@ func (p *TestGitServer) CommitFiles(repo string, add []string, all bool, message } } -func (p *TestGitServer) CommitYaml(repo string, pth string, message string, y *uo.UnstructuredObject) { +func (p *TestGitServer) CommitYaml(repo string, pth string, message string, o map[string]any) { fullPath := filepath.Join(p.LocalRepoDir(repo), pth) dir, _ := filepath.Split(fullPath) @@ -158,7 +158,11 @@ func (p *TestGitServer) CommitYaml(repo string, pth string, message string, y *u } } - err := yaml.WriteYamlFile(fullPath, y) + b, err := yaml.Marshal(o) + if err != nil { + p.t.Fatal(err) + } + err = os.WriteFile(fullPath, b, 0o600) if err != nil { p.t.Fatal(err) } @@ -194,18 +198,30 @@ func (p *TestGitServer) UpdateFile(repo string, pth string, update func(f string p.CommitFiles(repo, []string{pth}, false, message) } -func (p *TestGitServer) UpdateYaml(repo string, pth string, update func(o *uo.UnstructuredObject) error, message string) { +func (p *TestGitServer) UpdateYaml(repo string, pth string, update func(o *map[string]any) error, message string) { fullPath := filepath.Join(p.LocalRepoDir(repo), pth) - o := uo.New() + var o map[string]any if _, err := os.Stat(fullPath); err == nil { - err := yaml.ReadYamlFile(fullPath, o) + b, err := os.ReadFile(fullPath) + if err != nil { + p.t.Fatal(err) + } + err = yaml.Unmarshal(b, &o) if err != nil { p.t.Fatal(err) } + } else { + o = map[string]any{} } - orig := o.Clone() - err := update(o) + + var orig map[string]any + err := copier.CopyWithOption(&orig, &o, copier.Option{DeepCopy: true}) + if err != nil { + p.t.Fatal(err) + } + + err = update(&o) if err != nil { p.t.Fatal(err) } @@ -215,26 +231,6 @@ func (p *TestGitServer) UpdateYaml(repo string, pth string, update func(o *uo.Un p.CommitYaml(repo, pth, message, o) } -func (p *TestGitServer) convertInterfaceToList(x interface{}) []interface{} { - var ret []interface{} - if l, ok := x.([]interface{}); ok { - return l - } - if l, ok := x.([]*uo.UnstructuredObject); ok { - for _, y := range l { - ret = append(ret, y) - } - return ret - } - if l, ok := x.([]map[string]interface{}); ok { - for _, y := range l { - ret = append(ret, y) - } - return ret - } - return []interface{}{x} -} - func (p *TestGitServer) GitUrl(repo string) string { return fmt.Sprintf("http://localhost:%d/%s/.git", p.gitServerPort, repo) } From 667937f7995f666fd360ef612067608cf01e1e55 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 30 Nov 2022 22:04:29 +0100 Subject: [PATCH 1260/2916] refactor: Remove dependency to UnstructuredObject in git package --- e2e/test-utils/project.go | 11 +++++++++-- pkg/git/test_git_server.go | 4 ++-- pkg/vars/vars_loader_test.go | 12 ++++++++---- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/e2e/test-utils/project.go b/e2e/test-utils/project.go index b07fb529d..f52e77e80 100644 --- a/e2e/test-utils/project.go +++ b/e2e/test-utils/project.go @@ -5,6 +5,7 @@ import ( "github.com/go-git/go-git/v5" "github.com/huandu/xstrings" "github.com/imdario/mergo" + "github.com/jinzhu/copier" git2 "github.com/kluctl/kluctl/v2/pkg/git" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" @@ -111,8 +112,14 @@ func (p *TestProject) UpdateDeploymentYaml(dir string, update func(o *uo.Unstruc } func (p *TestProject) UpdateYaml(path string, update func(o *uo.UnstructuredObject) error, message string) { - p.gitServer.UpdateYaml("kluctl-project", path, func(o *uo.UnstructuredObject) error { - return update(o) + p.gitServer.UpdateYaml("kluctl-project", path, func(o map[string]any) error { + u := uo.FromMap(o) + err := update(u) + if err != nil { + return err + } + _ = copier.CopyWithOption(&o, &u.Object, copier.Option{DeepCopy: true}) + return nil }, message) } diff --git a/pkg/git/test_git_server.go b/pkg/git/test_git_server.go index fcbd28883..e7661ab9e 100644 --- a/pkg/git/test_git_server.go +++ b/pkg/git/test_git_server.go @@ -198,7 +198,7 @@ func (p *TestGitServer) UpdateFile(repo string, pth string, update func(f string p.CommitFiles(repo, []string{pth}, false, message) } -func (p *TestGitServer) UpdateYaml(repo string, pth string, update func(o *map[string]any) error, message string) { +func (p *TestGitServer) UpdateYaml(repo string, pth string, update func(o map[string]any) error, message string) { fullPath := filepath.Join(p.LocalRepoDir(repo), pth) var o map[string]any @@ -221,7 +221,7 @@ func (p *TestGitServer) UpdateYaml(repo string, pth string, update func(o *map[s p.t.Fatal(err) } - err = update(&o) + err = update(o) if err != nil { p.t.Fatal(err) } diff --git a/pkg/vars/vars_loader_test.go b/pkg/vars/vars_loader_test.go index 18e342465..ecf2e006e 100644 --- a/pkg/vars/vars_loader_test.go +++ b/pkg/vars/vars_loader_test.go @@ -157,8 +157,10 @@ func TestVarsLoader_FileWithLoadNotExists(t *testing.T) { func TestVarsLoader_Git(t *testing.T) { gs := git.NewTestGitServer(t) gs.GitInit("repo") - gs.UpdateYaml("repo", "test.yaml", func(o *uo.UnstructuredObject) error { - *o = *uo.FromStringMust(`{"test1": {"test2": 42}}`) + gs.UpdateYaml("repo", "test.yaml", func(o map[string]any) error { + o["test1"] = map[string]any{ + "test2": 42, + } return nil }, "") @@ -188,8 +190,10 @@ func TestVarsLoader_GitBranch(t *testing.T) { }) assert.NoError(t, err) - gs.UpdateYaml("repo", "test.yaml", func(o *uo.UnstructuredObject) error { - *o = *uo.FromStringMust(`{"test1": {"test2": 42}}`) + gs.UpdateYaml("repo", "test.yaml", func(o map[string]any) error { + o["test1"] = map[string]any{ + "test2": 42, + } return nil }, "") From 2853468ed9c5a93e1e3ac23f602f2ad40b30f21e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 30 Nov 2022 22:09:52 +0100 Subject: [PATCH 1261/2916] refactor: Remove dependency to utils in git package --- pkg/git/auth/ssh_known_hosts.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/git/auth/ssh_known_hosts.go b/pkg/git/auth/ssh_known_hosts.go index 0d0e9b713..9e8017040 100644 --- a/pkg/git/auth/ssh_known_hosts.go +++ b/pkg/git/auth/ssh_known_hosts.go @@ -4,7 +4,6 @@ import ( "fmt" "github.com/kluctl/kluctl/v2/pkg/git/auth/goph" "github.com/kluctl/kluctl/v2/pkg/git/messages" - "github.com/kluctl/kluctl/v2/pkg/utils" "golang.org/x/crypto/ssh" "net" "os" @@ -47,7 +46,7 @@ func verifyHost(messageCallbacks messages.MessageCallbacks, host string, remote } f := filepath.Join(home, ".ssh", "known_hosts") - if !utils.Exists(filepath.Dir(f)) { + if _, err := os.Stat(filepath.Dir(f)); err != nil { err = os.MkdirAll(filepath.Dir(f), 0o700) if err != nil { return err @@ -58,7 +57,7 @@ func verifyHost(messageCallbacks messages.MessageCallbacks, host string, remote allowAdd = true } } else { - tmpFile, err := os.CreateTemp(utils.GetTmpBaseDir(), "known_hosts-") + tmpFile, err := os.CreateTemp("", "known_hosts-") if err != nil { return err } From 2ceb76ee809ebffc4b24f713d6db95095aafb994 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Dec 2022 22:09:20 +0000 Subject: [PATCH 1262/2916] chore(deps): Bump github.com/huandu/xstrings from 1.3.3 to 1.4.0 Bumps [github.com/huandu/xstrings](https://github.com/huandu/xstrings) from 1.3.3 to 1.4.0. - [Release notes](https://github.com/huandu/xstrings/releases) - [Commits](https://github.com/huandu/xstrings/compare/v1.3.3...v1.4.0) --- updated-dependencies: - dependency-name: github.com/huandu/xstrings dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ae00085ce..5e425d6db 100644 --- a/go.mod +++ b/go.mod @@ -60,7 +60,7 @@ require ( require ( github.com/go-logr/logr v1.2.3 github.com/hashicorp/go-multierror v1.1.1 - github.com/huandu/xstrings v1.3.3 + github.com/huandu/xstrings v1.4.0 github.com/otiai10/copy v1.9.0 go.mozilla.org/sops/v3 v3.7.3 sigs.k8s.io/controller-runtime v0.13.1 diff --git a/go.sum b/go.sum index 2a56447bd..6216b20a0 100644 --- a/go.sum +++ b/go.sum @@ -486,8 +486,8 @@ github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef h1:A9HsByNhogrvm9cWb github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= -github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= +github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= From d72f1746c7d0082a26668a8fdfaf37d854bb9095 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Dec 2022 22:08:44 +0000 Subject: [PATCH 1263/2916] chore(deps): Bump github.com/aws/aws-sdk-go from 1.44.146 to 1.44.154 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.146 to 1.44.154. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.146...v1.44.154) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ae00085ce..981c37ff5 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e github.com/Masterminds/semver/v3 v3.2.0 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d - github.com/aws/aws-sdk-go v1.44.146 + github.com/aws/aws-sdk-go v1.44.154 github.com/bitnami-labs/sealed-secrets v0.19.2 github.com/cyphar/filepath-securejoin v0.2.3 github.com/docker/distribution v2.8.1+incompatible diff --git a/go.sum b/go.sum index 2a56447bd..561253b46 100644 --- a/go.sum +++ b/go.sum @@ -131,8 +131,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/aws/aws-sdk-go v1.44.146 h1:7YdGgPxDPRJu/yYffzZp/H7yHzQ6AqmuNFZPYraaN8I= -github.com/aws/aws-sdk-go v1.44.146/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.154 h1:2TaEC8JIM/t7Fu+FBmdOBK5jFUAVL07tbpfJsLuVo3Q= +github.com/aws/aws-sdk-go v1.44.154/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= From 58ca81510ee22a60f152fae2fd3c722e362ecca6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 7 Dec 2022 11:16:47 +0100 Subject: [PATCH 1264/2916] refactor: Use to github.com/fluxcd/pkg/kustomize This removes the need to use a copy of SecureBuildKustomization inside Kluctl. --- go.mod | 24 ++++++------ go.sum | 44 ++++++++++++---------- pkg/deployment/deployment_item.go | 3 +- pkg/utils/kustomize.go | 62 ------------------------------- 4 files changed, 39 insertions(+), 94 deletions(-) delete mode 100644 pkg/utils/kustomize.go diff --git a/go.mod b/go.mod index 3cef08b7c..8870ed9e9 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/bitnami-labs/sealed-secrets v0.19.2 github.com/cyphar/filepath-securejoin v0.2.3 github.com/docker/distribution v2.8.1+incompatible - github.com/fluxcd/pkg/kustomize v0.8.0 + github.com/fluxcd/pkg/kustomize v0.11.0 github.com/go-git/go-git/v5 v5.4.2 github.com/go-playground/validator/v10 v10.11.1 github.com/gobwas/glob v0.2.3 // indirect @@ -48,11 +48,11 @@ require ( gopkg.in/yaml.v3 v3.0.1 helm.sh/helm/v3 v3.10.2 k8s.io/api v0.25.4 - k8s.io/apiextensions-apiserver v0.25.3 + k8s.io/apiextensions-apiserver v0.25.4 k8s.io/apimachinery v0.25.4 k8s.io/client-go v0.25.4 k8s.io/klog/v2 v2.80.1 - sigs.k8s.io/kustomize/api v0.12.1 + sigs.k8s.io/kustomize/api v0.12.1 // indirect sigs.k8s.io/kustomize/kyaml v0.13.9 sigs.k8s.io/structured-merge-diff/v4 v4.2.3 ) @@ -108,12 +108,14 @@ require ( github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/emicklei/go-restful/v3 v3.9.0 // indirect + github.com/drone/envsubst v1.0.3 // indirect + github.com/emicklei/go-restful/v3 v3.10.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/evanphx/json-patch v5.6.0+incompatible // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect github.com/fatih/color v1.13.0 // indirect + github.com/fluxcd/pkg/apis/kustomize v0.7.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-git/gcfg v1.5.0 // indirect @@ -216,11 +218,11 @@ require ( go.etcd.io/etcd/api/v3 v3.5.5 // indirect go.mozilla.org/gopgagent v0.0.0-20170926210634-4d7ea76ff71a // indirect go.opencensus.io v0.23.0 // indirect - go.starlark.net v0.0.0-20221020143700-22309ac47eac // indirect + go.starlark.net v0.0.0-20221028183056-acb66ad56dd2 // indirect go.uber.org/atomic v1.10.0 // indirect golang.org/x/mod v0.6.0 // indirect - golang.org/x/oauth2 v0.1.0 // indirect - golang.org/x/time v0.1.0 // indirect + golang.org/x/oauth2 v0.2.0 // indirect + golang.org/x/time v0.2.0 // indirect golang.org/x/tools v0.2.0 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect google.golang.org/api v0.102.0 // indirect @@ -233,12 +235,12 @@ require ( gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/urfave/cli.v1 v1.20.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect - k8s.io/apiserver v0.25.3 // indirect + k8s.io/apiserver v0.25.4 // indirect k8s.io/cli-runtime v0.25.3 // indirect - k8s.io/component-base v0.25.3 // indirect - k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect + k8s.io/component-base v0.25.4 // indirect + k8s.io/kube-openapi v0.0.0-20221110221610-a28e98eb7c70 // indirect k8s.io/kubectl v0.25.3 // indirect - k8s.io/utils v0.0.0-20221012122500-cfd413dd9e85 // indirect + k8s.io/utils v0.0.0-20221108210102-8e77b1f39fe2 // indirect oras.land/oras-go v1.2.1 // indirect sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect sigs.k8s.io/yaml v1.3.0 // indirect diff --git a/go.sum b/go.sum index f91f3c8d1..cbe4b45be 100644 --- a/go.sum +++ b/go.sum @@ -212,9 +212,11 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4 github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g= +github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= -github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= -github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.10.0 h1:X4gma4HM7hFm6WMeAsTfqA0GOfdNoCzBIkHGoRLGXuM= +github.com/emicklei/go-restful/v3 v3.10.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= @@ -240,8 +242,10 @@ github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYF github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= -github.com/fluxcd/pkg/kustomize v0.8.0 h1:8AdEvp6y38ISZzoi0H82Si5zkmLXClbeX10W7HevB00= -github.com/fluxcd/pkg/kustomize v0.8.0/go.mod h1:zGtCZF6V3hMWcf46SqrQc10fS9yUlKzi2UcFUeabDAE= +github.com/fluxcd/pkg/apis/kustomize v0.7.0 h1:X2htBmJ91nGYv4d93gin665MFWKNGiNwUiZ08/Zz0hY= +github.com/fluxcd/pkg/apis/kustomize v0.7.0/go.mod h1:Mu+KdktsEKWA4l/33CZdY5lB4hz51mqfcLzBZSwAqVg= +github.com/fluxcd/pkg/kustomize v0.11.0 h1:zseS9LRUuzhP/7KamccmsOgYpJAdhqtsf+2wN/CHF3I= +github.com/fluxcd/pkg/kustomize v0.11.0/go.mod h1:awHID4OKe2/WAfTFg4u0fURXZPUkrIslSZNSPX9MEFQ= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -851,8 +855,8 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.starlark.net v0.0.0-20221020143700-22309ac47eac h1:gBO5Qfcw5V9404yzsu2FEIsxK/u2mBNTNogK0uIoVhk= -go.starlark.net v0.0.0-20221020143700-22309ac47eac/go.mod h1:kIVgS18CjmEC3PqMd5kaJSGEifyV/CeB9x506ZJ1Vbk= +go.starlark.net v0.0.0-20221028183056-acb66ad56dd2 h1:5/KzhcSqd4UgY51l17r7C5g/JiE6DRw1Vq7VJfQHuMc= +go.starlark.net v0.0.0-20221028183056-acb66ad56dd2/go.mod h1:kIVgS18CjmEC3PqMd5kaJSGEifyV/CeB9x506ZJ1Vbk= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= @@ -983,8 +987,8 @@ golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.1.0 h1:isLCZuhj4v+tYv7eskaN4v/TM+A1begWWgyVJDdl1+Y= -golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A= +golang.org/x/oauth2 v0.2.0 h1:GtQkldQ9m7yvzCL1V+LrYow3Khe0eJH0w7RbX/VbaIU= +golang.org/x/oauth2 v0.2.0/go.mod h1:Cwn6afJ8jrQwYMxQDTpISoXmXW9I6qF6vDeuuoX3Ibs= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1096,8 +1100,8 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA= -golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.2.0 h1:52I/1L54xyEQAYdtcSuxtiT84KGYTBGXwayxmIpNJhE= +golang.org/x/time v0.2.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -1328,26 +1332,26 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.25.4 h1:3YO8J4RtmG7elEgaWMb4HgmpS2CfY1QlaOz9nwB+ZSs= k8s.io/api v0.25.4/go.mod h1:IG2+RzyPQLllQxnhzD8KQNEu4c4YvyDTpSMztf4A0OQ= -k8s.io/apiextensions-apiserver v0.25.3 h1:bfI4KS31w2f9WM1KLGwnwuVlW3RSRPuIsfNF/3HzR0k= -k8s.io/apiextensions-apiserver v0.25.3/go.mod h1:ZJqwpCkxIx9itilmZek7JgfUAM0dnTsA48I4krPqRmo= +k8s.io/apiextensions-apiserver v0.25.4 h1:7hu9pF+xikxQuQZ7/30z/qxIPZc2J1lFElPtr7f+B6U= +k8s.io/apiextensions-apiserver v0.25.4/go.mod h1:bkSGki5YBoZWdn5pWtNIdGvDrrsRWlmnvl9a+tAw5vQ= k8s.io/apimachinery v0.25.4 h1:CtXsuaitMESSu339tfhVXhQrPET+EiWnIY1rcurKnAc= k8s.io/apimachinery v0.25.4/go.mod h1:jaF9C/iPNM1FuLl7Zuy5b9v+n35HGSh6AQ4HYRkCqwo= -k8s.io/apiserver v0.25.3 h1:m7+xGuG5+KYAnEsqaFtDyWMkmMMEOFYlu+NlWv5qSBI= -k8s.io/apiserver v0.25.3/go.mod h1:9bT47iM2fzRuhICJpM/RcQR9sqDDfZ7Yw60h0p3JW08= +k8s.io/apiserver v0.25.4 h1:/3TwZcgLqX7wUxq7TtXOUqXeBTwXIblVMQdhR5XZ7yo= +k8s.io/apiserver v0.25.4/go.mod h1:rPcm567XxjOnnd7jedDUnGJGmDGAo+cT6H7QHAN+xV0= k8s.io/cli-runtime v0.25.3 h1:Zs7P7l7db/5J+KDePOVtDlArAa9pZXaDinGWGZl0aM8= k8s.io/cli-runtime v0.25.3/go.mod h1:InHHsjkyW5hQsILJGpGjeruiDZT/R0OkROQgD6GzxO4= k8s.io/client-go v0.25.4 h1:3RNRDffAkNU56M/a7gUfXaEzdhZlYhoW8dgViGy5fn8= k8s.io/client-go v0.25.4/go.mod h1:8trHCAC83XKY0wsBIpbirZU4NTUpbuhc2JnI7OruGZw= -k8s.io/component-base v0.25.3 h1:UrsxciGdrCY03ULT1h/S/gXFCOPnLhUVwSyx+hM/zq4= -k8s.io/component-base v0.25.3/go.mod h1:WYoS8L+IlTZgU7rhAl5Ctpw0WdMxDfCC5dkxcEFa/TI= +k8s.io/component-base v0.25.4 h1:n1bjg9Yt+G1C0WnIDJmg2fo6wbEU1UGMRiQSjmj7hNQ= +k8s.io/component-base v0.25.4/go.mod h1:nnZJU8OP13PJEm6/p5V2ztgX2oyteIaAGKGMYb2L2cY= k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= -k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= +k8s.io/kube-openapi v0.0.0-20221110221610-a28e98eb7c70 h1:zfqQc1V6/ZgGpvrOVvr62OjiqQX4lZjfznK34NQwkqw= +k8s.io/kube-openapi v0.0.0-20221110221610-a28e98eb7c70/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= k8s.io/kubectl v0.25.3 h1:HnWJziEtmsm4JaJiKT33kG0kadx68MXxUE8UEbXnN4U= k8s.io/kubectl v0.25.3/go.mod h1:glU7PiVj/R6Ud4A9FJdTcJjyzOtCJyc0eO7Mrbh3jlI= -k8s.io/utils v0.0.0-20221012122500-cfd413dd9e85 h1:cTdVh7LYu82xeClmfzGtgyspNh6UxpwLWGi8R4sspNo= -k8s.io/utils v0.0.0-20221012122500-cfd413dd9e85/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20221108210102-8e77b1f39fe2 h1:GfD9OzL11kvZN5iArC6oTS7RTj7oJOIfnislxYlqTj8= +k8s.io/utils v0.0.0-20221108210102-8e77b1f39fe2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= oras.land/oras-go v1.2.1 h1:/VcGS8FUy3eEXLl/1vC4QypLHwrfSmgW7ygsoklqKK8= oras.land/oras-go v1.2.1/go.mod h1:3N11Z5E3c4ZzOjroCl1RtAdB4yNAYl7A27j2SVf913A= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index d6d1ad573..94d23ddd9 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -2,6 +2,7 @@ package deployment import ( "fmt" + "github.com/fluxcd/pkg/kustomize" "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/sops" "github.com/kluctl/kluctl/v2/pkg/status" @@ -515,7 +516,7 @@ func (di *DeploymentItem) buildKustomize() error { } } - rm, err := utils.SecureBuildKustomization(di.RenderedSourceRootDir, di.RenderedDir, true) + rm, err := kustomize.SecureBuild(di.RenderedSourceRootDir, di.RenderedDir, true) if err != nil { return err } diff --git a/pkg/utils/kustomize.go b/pkg/utils/kustomize.go deleted file mode 100644 index 44a92258a..000000000 --- a/pkg/utils/kustomize.go +++ /dev/null @@ -1,62 +0,0 @@ -/* -Code in this file is copied from https://github.com/fluxcd/kustomize-controller/blob/main/controllers/kustomization_generator.go -*/ - -package utils - -import ( - "fmt" - securefs "github.com/fluxcd/pkg/kustomize/filesys" - "sigs.k8s.io/kustomize/api/filesys" - "sigs.k8s.io/kustomize/api/krusty" - "sigs.k8s.io/kustomize/api/resmap" - kustypes "sigs.k8s.io/kustomize/api/types" - "sync" -) - -// TODO: remove mutex when kustomize fixes the concurrent map read/write panic -var kustomizeBuildMutex sync.Mutex - -// SecureBuildKustomization wraps krusty.MakeKustomizer with the following settings: -// - secure on-disk FS denying operations outside root -// - load files from outside the kustomization dir path -// (but not outside root) -// - disable plugins except for the builtin ones -func SecureBuildKustomization(root, dirPath string, allowRemoteBases bool) (_ resmap.ResMap, err error) { - var fs filesys.FileSystem - - // Create secure FS for root with or without remote base support - if allowRemoteBases { - fs, err = securefs.MakeFsOnDiskSecureBuild(root) - if err != nil { - return nil, err - } - } else { - fs, err = securefs.MakeFsOnDiskSecure(root) - if err != nil { - return nil, err - } - } - - // Temporary workaround for concurrent map read and map write bug - // https://github.com/kubernetes-sigs/kustomize/issues/3659 - kustomizeBuildMutex.Lock() - defer kustomizeBuildMutex.Unlock() - - // Kustomize tends to panic in unpredicted ways due to (accidental) - // invalid object data; recover when this happens to ensure continuity of - // operations - defer func() { - if r := recover(); r != nil { - err = fmt.Errorf("recovered from kustomize build panic: %v", r) - } - }() - - buildOptions := &krusty.Options{ - LoadRestrictions: kustypes.LoadRestrictionsNone, - PluginConfig: kustypes.DisabledPluginConfig(), - } - - k := krusty.MakeKustomizer(buildOptions) - return k.Run(fs, dirPath) -} From c9972318ac1a66b7e7430140f7554fb22abf6716 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 7 Dec 2022 11:42:25 +0100 Subject: [PATCH 1265/2916] fix: Use locked files to write/read registry cache files --- pkg/registries/registries.go | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/pkg/registries/registries.go b/pkg/registries/registries.go index 48556c1e8..81b1156e8 100644 --- a/pkg/registries/registries.go +++ b/pkg/registries/registries.go @@ -15,6 +15,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/rogpeppe/go-internal/lockedfile" "io" "net/http" "net/http/httputil" @@ -324,7 +325,13 @@ func (rh *RegistryHelper) readCachedResponse(key string) []byte { return nil } - b, err := os.ReadFile(cachePath) + f, err := lockedfile.OpenFile(cachePath, os.O_RDONLY, 0) + if err != nil { + status.Warning(rh.ctx, "readCachedResponse failed: %v", err) + return nil + } + b, err := io.ReadAll(f) + _ = f.Close() if err != nil { return nil } @@ -346,20 +353,23 @@ func (rh *RegistryHelper) readCachedResponse(key string) []byte { func (rh *RegistryHelper) writeCachedResponse(key string, data []byte) { cachePath := rh.getCachePath(key) - if !utils.Exists(filepath.Dir(cachePath)) { - err := os.MkdirAll(filepath.Dir(cachePath), 0o700) + cacheDir := filepath.Dir(cachePath) + if !utils.Exists(cacheDir) { + err := os.MkdirAll(cacheDir, 0o700) if err != nil { status.Warning(rh.ctx, "writeCachedResponse failed: %v", err) return } } - err := os.WriteFile(cachePath+".tmp", data, 0o600) + f, err := lockedfile.OpenFile(cachePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600) if err != nil { status.Warning(rh.ctx, "writeCachedResponse failed: %v", err) return } - err = os.Rename(cachePath+".tmp", cachePath) + defer f.Close() + + _, err = f.Write(data) if err != nil { status.Warning(rh.ctx, "writeCachedResponse failed: %v", err) return From 215ab6df1ace4bad8c4241773ac84c0717e86a98 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 7 Dec 2022 14:04:12 +0100 Subject: [PATCH 1266/2916] feat: Support SOPS in helm-files.yaml --- e2e/sops_test.go | 34 +++++++++++++++++++ pkg/deployment/deployment_item.go | 2 +- pkg/deployment/helm_chart.go | 14 +++++--- pkg/sops/utils.go | 18 ++++++++-- pkg/vars/sops_test_resources/README.md | 1 + pkg/vars/sops_test_resources/helm-values.yaml | 23 +++++++++++++ 6 files changed, 85 insertions(+), 7 deletions(-) create mode 100644 pkg/vars/sops_test_resources/helm-values.yaml diff --git a/e2e/sops_test.go b/e2e/sops_test.go index 22014d9a0..6b4a1a1a2 100644 --- a/e2e/sops_test.go +++ b/e2e/sops_test.go @@ -4,6 +4,7 @@ import ( "github.com/kluctl/kluctl/v2/e2e/test-utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/vars/sops_test_resources" + "github.com/stretchr/testify/assert" "go.mozilla.org/sops/v3/age" "testing" ) @@ -80,3 +81,36 @@ func TestSopsResources(t *testing.T) { "a": "b", }, "data") } + +func TestSopsHelmValues(t *testing.T) { + key, _ := sops_test_resources.TestResources.ReadFile("test-key.txt") + t.Setenv(age.SopsAgeKeyEnv, string(key)) + + k := defaultCluster1 + + p := test_utils.NewTestProject(t, k) + + createNamespace(t, k, p.TestSlug()) + + repoUrl := test_utils.CreateHelmRepo(t, []test_utils.RepoChart{ + {ChartName: "test-chart1", Version: "0.1.0"}, + }, "", "") + + valuesBytes, err := sops_test_resources.TestResources.ReadFile("helm-values.yaml") + assert.NoError(t, err) + values1, err := uo.FromString(string(valuesBytes)) + assert.NoError(t, err) + + p.UpdateTarget("test", nil) + p.AddHelmDeployment("helm1", repoUrl, "test-chart1", "0.1.0", "test-helm1", p.TestSlug(), values1.Object) + + p.KluctlMust("deploy", "--yes", "-t", "test") + + cm1 := assertConfigMapExists(t, k, p.TestSlug(), "test-helm1-test-chart1") + + assert.Equal(t, map[string]any{ + "a": "secret1", + "b": "secret2", + "version": "0.1.0", + }, cm1.Object["data"]) +} diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 94d23ddd9..f6caec641 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -214,7 +214,7 @@ func (di *DeploymentItem) renderHelmCharts() error { } } - return chart.Render(di.ctx.Ctx, di.ctx.K) + return chart.Render(di.ctx.Ctx, di.ctx.K, di.ctx.SopsDecrypter) }) if err != nil { return err diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index dda52347a..584fe3a72 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -8,6 +8,7 @@ import ( securejoin "github.com/cyphar/filepath-securejoin" "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/registries" + "github.com/kluctl/kluctl/v2/pkg/sops" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" @@ -341,15 +342,15 @@ func (c *HelmChart) checkUpdateHelmRepo() (string, bool, error) { return latestVersion, updated, nil } -func (c *HelmChart) Render(ctx context.Context, k *k8s.K8sCluster) error { - err := c.doRender(ctx, k) +func (c *HelmChart) Render(ctx context.Context, k *k8s.K8sCluster, sopsDecrypter sops.SopsDecrypter) error { + err := c.doRender(ctx, k, sopsDecrypter) if err != nil { return fmt.Errorf("rendering helm chart %s for release %s has failed: %w", c.chartName, c.Config.ReleaseName, err) } return nil } -func (c *HelmChart) doRender(ctx context.Context, k *k8s.K8sCluster) error { +func (c *HelmChart) doRender(ctx context.Context, k *k8s.K8sCluster, sopsDecrypter sops.SopsDecrypter) error { chartDir := c.chartDir needsPull, versionChanged, prePulledVersion, err := c.checkNeedsPull(chartDir, false) @@ -397,7 +398,12 @@ func (c *HelmChart) doRender(ctx context.Context, k *k8s.K8sCluster) error { valueOpts := values.Options{} if utils.Exists(valuesPath) { - valueOpts.ValueFiles = append(valueOpts.ValueFiles, valuesPath) + tmpValues, err := sops.MaybeDecryptFileToTmp(sopsDecrypter, valuesPath) + if err != nil { + return err + } + defer os.Remove(tmpValues) + valueOpts.ValueFiles = append(valueOpts.ValueFiles, tmpValues) } var kubeVersion *chartutil.KubeVersion diff --git a/pkg/sops/utils.go b/pkg/sops/utils.go index 39a1d85b2..7a1250d5e 100644 --- a/pkg/sops/utils.go +++ b/pkg/sops/utils.go @@ -4,6 +4,7 @@ import ( "bytes" "errors" "fmt" + "github.com/kluctl/kluctl/v2/pkg/utils" "go.mozilla.org/sops/v3" "go.mozilla.org/sops/v3/cmd/sops/formats" "os" @@ -33,6 +34,10 @@ func MaybeDecrypt(decrypter SopsDecrypter, encrypted []byte, inputFormat, output } func MaybeDecryptFile(decrypter SopsDecrypter, path string) error { + return MaybeDecryptFileTo(decrypter, path, path) +} + +func MaybeDecryptFileTo(decrypter SopsDecrypter, path string, to string) error { format := formats.FormatForPath(path) file, err := os.ReadFile(path) @@ -44,13 +49,22 @@ func MaybeDecryptFile(decrypter SopsDecrypter, path string) error { if err != nil { return fmt.Errorf("failed to decrypt file %s: %w", path, err) } - if !encrypted { + if !encrypted && path == to { return nil } - err = os.WriteFile(path, decrypted, 0o600) + err = os.WriteFile(to, decrypted, 0o600) if err != nil { return fmt.Errorf("failed to save decrypted file %s: %w", path, err) } return nil } + +func MaybeDecryptFileToTmp(decrypter SopsDecrypter, path string) (string, error) { + tmp, err := os.CreateTemp(utils.GetTmpBaseDir(), "sops-decrypt-") + if err != nil { + return "", err + } + _ = tmp.Close() + return tmp.Name(), MaybeDecryptFileTo(decrypter, path, tmp.Name()) +} diff --git a/pkg/vars/sops_test_resources/README.md b/pkg/vars/sops_test_resources/README.md index f25a10769..6a5cfbd52 100644 --- a/pkg/vars/sops_test_resources/README.md +++ b/pkg/vars/sops_test_resources/README.md @@ -2,4 +2,5 @@ To edit the test.yaml file, run: ```sh SOPS_AGE_KEY_FILE=$(pwd)/test-key.txt sops test.yaml SOPS_AGE_KEY_FILE=$(pwd)/test-key.txt sops test-configmap.yaml +SOPS_AGE_KEY_FILE=$(pwd)/test-key.txt sops helm-values.yaml ``` diff --git a/pkg/vars/sops_test_resources/helm-values.yaml b/pkg/vars/sops_test_resources/helm-values.yaml new file mode 100644 index 000000000..06aca28b4 --- /dev/null +++ b/pkg/vars/sops_test_resources/helm-values.yaml @@ -0,0 +1,23 @@ +data: + a: ENC[AES256_GCM,data:72uviUAptw==,iv:jRc24qIQwkbhGAp2zj6qiaoiunMFYiQdJEW8tC6nCOw=,tag:LA4hUT++fqgT5ZgKTM6LFA==,type:str] + b: ENC[AES256_GCM,data:M7AlzSVTxg==,iv:j2yFEUIc5OwWmhVFzljZDmPEII/0jzr42nBiplXZG1Y=,tag:1w91ofjr1nOF8rU7gH8ZGQ==,type:str] +sops: + kms: [] + gcp_kms: [] + azure_kv: [] + hc_vault: [] + age: + - recipient: age1q69g6x9jcz7lgnrgdxemystmhec4e8cxlzz45x0tt6t7dddp2ppsnkdxe7 + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBSRzZhejBCdmdYdHc5dEYw + UGduYk1neGlYeXNQenRSYmQrV0FpTkcwRTNFCk1panJJTmsyejdzNGtkTFJhWVVn + bXhkaU0xZlAwbFo2UG5QdkpnajlZZzQKLS0tIHRzRkRtWEhzL1R1R2VoM0NFMk5s + NzlGRU0zK29IV3FuSWxIS2ZXb1FFMEEKQKXp5xfIRQ94uQ0Z6QkNYrnqF6LjW/XT + HihRLtBxL4Yuj/3bhImk+3PqCh8fdCjPmN13NURUxeW9q6fWwZWekA== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2022-12-07T12:53:07Z" + mac: ENC[AES256_GCM,data:tQFvNENKLBCmIO5De4QVj3GDpXq2haYpK7dlrvcYZ9S0Aw5l92HB3MHi3RYtxDH6ZqC1HbwKGEcFmINRUAA98+g6htJ3XV+yfjuL09v0W3Kosf6FbkEpKvfrbD+Z2nGrQ6+IdGAH/TPICK1D2MtJ7Q9DHd+98STxmcZ4npyiu8g=,iv:bteoE24FA+h+BnbRKoH9FbAT3qk3p8Mh6oqUBGg7NRU=,tag:Al6hWq7fuI8eXOddoCwdNg==,type:str] + pgp: [] + unencrypted_suffix: _unencrypted + version: 3.7.3 From 97e622973dbc12759ec60d36bf96296054d5b543 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 7 Dec 2022 14:33:11 +0100 Subject: [PATCH 1267/2916] feat: Implement annotations to mark objects/fields to be ignored on conflict --- .../deployments/annotations/all-resources.md | 10 ++++ pkg/diff/managed_fields.go | 52 +++++++++++++------ 2 files changed, 47 insertions(+), 15 deletions(-) diff --git a/docs/reference/deployments/annotations/all-resources.md b/docs/reference/deployments/annotations/all-resources.md index aa517a46c..9340ebdbf 100644 --- a/docs/reference/deployments/annotations/all-resources.md +++ b/docs/reference/deployments/annotations/all-resources.md @@ -32,6 +32,16 @@ fields will be overwritten in case of field manager conflicts. If more than one field needs to be specified, add `-xxx` to the annotation key, where `xxx` is an arbitrary number. +### kluctl.io/ignore-conflicts +If set to "true", the whole all fields of the object are going to be ignored when conflicts arise. +This effectively disables the warnings that are shown when field ownership is lost. + +### kluctl.io/ignore-conflicts-field +Specifies a [JSON Path](https://goessner.net/articles/JsonPath/) for fields that should be ignored when conflicts arise. +This effectively disables the warnings that are shown when field ownership is lost. + +If more than one field needs to be specified, add `-xxx` to the annotation key, where `xxx` is an arbitrary number. + ## Control deletion/pruning The following annotations control how delete/prune is behaving. diff --git a/pkg/diff/managed_fields.go b/pkg/diff/managed_fields.go index 9fa5d1d69..e2b4e6797 100644 --- a/pkg/diff/managed_fields.go +++ b/pkg/diff/managed_fields.go @@ -2,6 +2,7 @@ package diff import ( "fmt" + "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "reflect" @@ -17,6 +18,7 @@ type LostOwnership struct { } var forceApplyFieldAnnotationRegex = regexp.MustCompile(`^kluctl.io/force-apply-field(-\d*)?$`) +var ignoreConflictsFieldAnnotationRegex = regexp.MustCompile(`^kluctl.io/ignore-conflicts-field(-\d*)?$`) var overwriteAllowedManagers = []*regexp.Regexp{ regexp.MustCompile("^kluctl$"), regexp.MustCompile("^kubectl$"), @@ -98,6 +100,24 @@ func convertToKeyList(remote *uo.UnstructuredObject, path fieldpath.Path) (uo.Ke return ret, true, nil } +func collectFields(o *uo.UnstructuredObject, regex *regexp.Regexp) (map[string]bool, error) { + result := map[string]bool{} + for _, v := range o.GetK8sAnnotationsWithRegex(regex) { + j, err := uo.NewMyJsonPath(v) + if err != nil { + return nil, err + } + fields, err := j.ListMatchingFields(o) + if err != nil { + return nil, err + } + for _, f := range fields { + result[f.ToJsonPath()] = true + } + } + return result, nil +} + func ResolveFieldManagerConflicts(local *uo.UnstructuredObject, remote *uo.UnstructuredObject, conflictStatus metav1.Status) (*uo.UnstructuredObject, []LostOwnership, error) { managedFields := remote.GetK8sManagedFields() @@ -154,24 +174,19 @@ func ResolveFieldManagerConflicts(local *uo.UnstructuredObject, remote *uo.Unstr ret := local.Clone() - forceApplyAll := false + forceApplyAll := utils.ParseBoolOrFalse(local.GetK8sAnnotation("kluctl.io/force-apply")) + ignoreConflictsAll := utils.ParseBoolOrFalse(local.GetK8sAnnotation("kluctl.io/ignore-conflicts")) if x := local.GetK8sAnnotation("kluctl.io/force-apply"); x != nil { forceApplyAll, _ = strconv.ParseBool(*x) } - forceApplyFields := make(map[string]bool) - for _, v := range local.GetK8sAnnotationsWithRegex(forceApplyFieldAnnotationRegex) { - j, err := uo.NewMyJsonPath(v) - if err != nil { - return nil, nil, err - } - fields, err := j.ListMatchingFields(ret) - if err != nil { - return nil, nil, err - } - for _, f := range fields { - forceApplyFields[f.ToJsonPath()] = true - } + forceApplyFields, err := collectFields(ret, forceApplyFieldAnnotationRegex) + if err != nil { + return nil, nil, err + } + ignoreConflictFields, err := collectFields(ret, ignoreConflictsFieldAnnotationRegex) + if err != nil { + return nil, nil, err } var lostOwnership []LostOwnership @@ -215,6 +230,7 @@ func ResolveFieldManagerConflicts(local *uo.UnstructuredObject, remote *uo.Unstr } overwrite := true + ignoreConflict := ignoreConflictsAll if !forceApplyAll { for _, mfn := range mf.managers { found := false @@ -236,6 +252,12 @@ func ResolveFieldManagerConflicts(local *uo.UnstructuredObject, remote *uo.Unstr overwrite = true } } + if _, ok := ignoreConflictFields[localKeyPath.ToJsonPath()]; ok { + ignoreConflict = true + } + if _, ok := ignoreConflictFields[remoteKeyPath.ToJsonPath()]; ok { + ignoreConflict = true + } if !overwrite { j, err := uo.NewMyJsonPath(localKeyPath.ToJsonPath()) @@ -247,7 +269,7 @@ func ResolveFieldManagerConflicts(local *uo.UnstructuredObject, remote *uo.Unstr return nil, nil, err } - if !reflect.DeepEqual(localValue, remoteValue) { + if !reflect.DeepEqual(localValue, remoteValue) && !ignoreConflict { lostOwnership = append(lostOwnership, LostOwnership{ Field: cause.Field, Message: cause.Message, From 794c94fc3eff185efcd4957c76a9e6172c64eb2e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 7 Dec 2022 15:50:14 +0100 Subject: [PATCH 1268/2916] tests: Implement tests for conflict resolution --- pkg/diff/managed_fields_test.go | 192 ++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 pkg/diff/managed_fields_test.go diff --git a/pkg/diff/managed_fields_test.go b/pkg/diff/managed_fields_test.go new file mode 100644 index 000000000..3c6fcd96e --- /dev/null +++ b/pkg/diff/managed_fields_test.go @@ -0,0 +1,192 @@ +package diff + +import ( + "fmt" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/structured-merge-diff/v4/fieldpath" + "testing" +) + +func TestResolveFieldManagerConflicts(t *testing.T) { + type testCase struct { + name string + remote *uo.UnstructuredObject + local *uo.UnstructuredObject + status metav1.Status + result *uo.UnstructuredObject + lost []LostOwnership + anns map[string]string + } + + type fieldInfo struct { + name string + value string + manager string + } + + buildConfigMap := func(fields ...fieldInfo) *uo.UnstructuredObject { + o := uo.FromMap(map[string]interface{}{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": map[string]any{ + "name": "name", + "namespace": "namespace", + }, + "data": map[string]any{}, + }) + pathesByManagers := map[string][]fieldpath.Path{} + for _, fi := range fields { + _ = o.SetNestedField(fi.value, "data", fi.name) + if fi.manager != "" { + pathesByManagers[fi.manager] = append(pathesByManagers[fi.manager], fieldpath.MakePathOrDie("data", fi.name)) + } + } + var managedFields []any + for manager, pbm := range pathesByManagers { + fs := fieldpath.NewSet(pbm...) + json, _ := fs.ToJSON() + fsY, _ := uo.FromString(string(json)) + managedFields = append(managedFields, map[string]interface{}{ + "apiVersion": "v1", + "fieldsType": "FieldsV1", + "manager": manager, + "operation": "Apply", + "time": "dummy", + "fieldsV1": fsY.Object, + }) + } + _ = o.SetNestedField(managedFields, "metadata", "managedFields") + return o + } + + buildConflicts := func(fields ...string) metav1.Status { + var s metav1.Status + s.Details = &metav1.StatusDetails{} + for _, f := range fields { + s.Details.Causes = append(s.Details.Causes, metav1.StatusCause{ + Type: metav1.CauseTypeFieldManagerConflict, + Field: fmt.Sprintf(".data.%s", f), + }) + } + return s + } + + buildLost := func(fields ...string) []LostOwnership { + var l []LostOwnership + for _, f := range fields { + l = append(l, LostOwnership{ + Field: fmt.Sprintf(".data.%s", f), + Message: "", + }) + } + return l + } + + buildAnnotations := func(kvs ...string) map[string]string { + ret := map[string]string{} + for i := 0; i < len(kvs); i += 2 { + ret[kvs[i]] = kvs[i+1] + } + return ret + } + + tests := []testCase{ + { + name: "overwrite-kubectl", + remote: buildConfigMap(fieldInfo{"d1", "v1", "kubectl"}), + local: buildConfigMap(fieldInfo{"d1", "x", "m1"}), + status: buildConflicts("d1"), + result: buildConfigMap(fieldInfo{"d1", "x", "m1"}), + }, + { + name: "lost-field", + remote: buildConfigMap(fieldInfo{"d1", "v1", "c1"}, fieldInfo{"d2", "v2", "m1"}), + local: buildConfigMap(fieldInfo{"d1", "x", "m1"}, fieldInfo{"d2", "x", "m1"}), + status: buildConflicts("d1"), + result: buildConfigMap(fieldInfo{"d2", "x", "m1"}), + lost: buildLost("d1"), + // also test non-matching fields here + anns: buildAnnotations("kluctl.io/force-apply-field", "data.d3"), + }, + { + name: "force-apply-object", + remote: buildConfigMap(fieldInfo{"d1", "v1", "c1"}, fieldInfo{"d2", "v2", "m1"}, fieldInfo{"d3", "v3", "c1"}), + local: buildConfigMap(fieldInfo{"d1", "x", "m1"}, fieldInfo{"d2", "x", "m1"}, fieldInfo{"d3", "x", "m1"}), + status: buildConflicts("d1", "d3"), + result: buildConfigMap(fieldInfo{"d1", "x", "m1"}, fieldInfo{"d2", "x", "m1"}, fieldInfo{"d3", "x", "m1"}), + lost: buildLost(), + anns: buildAnnotations("kluctl.io/force-apply", "true"), + }, + { + name: "force-apply-field", + remote: buildConfigMap(fieldInfo{"d1", "v1", "c1"}, fieldInfo{"d2", "v2", "m1"}, fieldInfo{"d3", "v3", "c1"}), + local: buildConfigMap(fieldInfo{"d1", "x", "m1"}, fieldInfo{"d2", "x", "m1"}, fieldInfo{"d3", "x", "m1"}), + status: buildConflicts("d1", "d3"), + result: buildConfigMap(fieldInfo{"d1", "x", "m1"}, fieldInfo{"d2", "x", "m1"}), + lost: buildLost("d3"), + anns: buildAnnotations("kluctl.io/force-apply-field", "data.d1"), + }, + { + name: "force-apply-field-xxx", + remote: buildConfigMap(fieldInfo{"d1", "v1", "c1"}, fieldInfo{"d2", "v2", "m1"}, fieldInfo{"d3", "v3", "c1"}), + local: buildConfigMap(fieldInfo{"d1", "x", "m1"}, fieldInfo{"d2", "x", "m1"}, fieldInfo{"d3", "x", "m1"}), + status: buildConflicts("d1", "d3"), + result: buildConfigMap(fieldInfo{"d2", "x", "m1"}, fieldInfo{"d3", "x", "m1"}), + lost: buildLost("d1"), + anns: buildAnnotations("kluctl.io/force-apply-field-123", "data.d3"), + }, + { + name: "ignore-conflicts", + remote: buildConfigMap(fieldInfo{"d1", "v1", "c1"}, fieldInfo{"d2", "v2", "m1"}, fieldInfo{"d3", "v3", "c1"}), + local: buildConfigMap(fieldInfo{"d1", "x", "m1"}, fieldInfo{"d2", "x", "m1"}, fieldInfo{"d3", "x", "m1"}), + status: buildConflicts("d1", "d3"), + result: buildConfigMap(fieldInfo{"d2", "x", "m1"}), + lost: buildLost(), + anns: buildAnnotations("kluctl.io/ignore-conflicts", "true"), + }, + { + name: "ignore-conflicts-field", + remote: buildConfigMap(fieldInfo{"d1", "v1", "c1"}, fieldInfo{"d2", "v2", "m1"}, fieldInfo{"d3", "v3", "c1"}), + local: buildConfigMap(fieldInfo{"d1", "x", "m1"}, fieldInfo{"d2", "x", "m1"}, fieldInfo{"d3", "x", "m1"}), + status: buildConflicts("d1", "d3"), + result: buildConfigMap(fieldInfo{"d2", "x", "m1"}), + lost: buildLost("d3"), + anns: buildAnnotations("kluctl.io/ignore-conflicts-field", "data.d1"), + }, + { + name: "ignore-conflicts-field-xxx", + remote: buildConfigMap(fieldInfo{"d1", "v1", "c1"}, fieldInfo{"d2", "v2", "m1"}, fieldInfo{"d3", "v3", "c1"}), + local: buildConfigMap(fieldInfo{"d1", "x", "m1"}, fieldInfo{"d2", "x", "m1"}, fieldInfo{"d3", "x", "m1"}), + status: buildConflicts("d1", "d3"), + result: buildConfigMap(fieldInfo{"d2", "x", "m1"}), + lost: buildLost("d1"), + anns: buildAnnotations("kluctl.io/ignore-conflicts-field-123", "data.d3"), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + if tc.local != nil { + _ = tc.local.RemoveNestedField("metadata", "managedFields") + } + if tc.result != nil { + _ = tc.result.RemoveNestedField("metadata", "managedFields") + } + for k, v := range tc.anns { + if tc.local != nil { + tc.local.SetK8sAnnotation(k, v) + } + if tc.result != nil { + tc.result.SetK8sAnnotation(k, v) + } + } + + r, l, err := ResolveFieldManagerConflicts(tc.local, tc.remote, tc.status) + assert.NoError(t, err) + assert.Equal(t, r, tc.result) + assert.Equal(t, l, tc.lost) + }) + } +} From aa7b47c81cdaedd6cf40353158e9d71a2aee3d9f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 7 Dec 2022 17:12:33 +0100 Subject: [PATCH 1269/2916] fix: Fix potential nil pointer access in pullAndCommitChart --- cmd/kluctl/commands/cmd_helm_update.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go index 7d6fc14f0..e831c591f 100644 --- a/cmd/kluctl/commands/cmd_helm_update.go +++ b/cmd/kluctl/commands/cmd_helm_update.go @@ -179,7 +179,7 @@ func (cmd *helmUpdateCmd) pullAndCommitChart(gitRootPath string, chart *deployme // know what got deleted oldFiles := map[string]bool{} err = filepath.WalkDir(chart.GetChartDir(), func(p string, d fs.DirEntry, err error) error { - if d.IsDir() { + if d == nil || d.IsDir() { return nil } relToGit, err := filepath.Rel(gitRootPath, p) From 967ec16f3fb3ddf9cbe1a726620ed64ece8fc935 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 7 Dec 2022 17:15:16 +0100 Subject: [PATCH 1270/2916] feat: Use github.com/Masterminds/semver for Helm version sorting --- pkg/deployment/helm_chart.go | 47 +++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index 584fe3a72..5308e9431 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -5,6 +5,7 @@ import ( "crypto/sha256" "encoding/hex" "fmt" + "github.com/Masterminds/semver/v3" securejoin "github.com/cyphar/filepath-securejoin" "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/registries" @@ -13,7 +14,6 @@ import ( "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" - "github.com/kluctl/kluctl/v2/pkg/utils/versions" "github.com/kluctl/kluctl/v2/pkg/yaml" "github.com/pkg/errors" "github.com/rogpeppe/go-internal/lockedfile" @@ -57,6 +57,11 @@ func NewHelmChart(configFile string) (*HelmChart, error) { return nil, err } + _, err = semver.NewVersion(*config.ChartVersion) + if err != nil { + return nil, fmt.Errorf("invalid chart version '%s': %w", *config.ChartVersion, err) + } + hc := &HelmChart{ ConfigFile: configFile, Config: &config, @@ -274,20 +279,16 @@ func (c *HelmChart) checkUpdateOciRepo(ctx context.Context) (string, bool, error return "", false, err } - var ls versions.LooseVersionSlice - for _, x := range tags { - ls = append(ls, versions.LooseVersion(x)) + latestVersion, err := c.findLatestVersion(tags) + if err != nil { + return "", false, err } - sort.Stable(ls) - latestVersion := string(ls[len(ls)-1]) updated := latestVersion != *c.Config.ChartVersion return latestVersion, updated, nil } func (c *HelmChart) checkUpdateHelmRepo() (string, bool, error) { - var latestVersion string - settings := cli.New() var e *repo.Entry @@ -331,17 +332,39 @@ func (c *HelmChart) checkUpdateHelmRepo() (string, bool, error) { return "", false, fmt.Errorf("helm chart %s not found in repo index", c.chartName) } - var ls versions.LooseVersionSlice + versions := make([]string, 0, indexEntry.Len()) for _, x := range indexEntry { - ls = append(ls, versions.LooseVersion(x.Version)) + versions = append(versions, x.Version) + } + + latestVersion, err := c.findLatestVersion(versions) + if err != nil { + return "", false, err } - sort.Stable(ls) - latestVersion = string(ls[len(ls)-1]) updated := latestVersion != *c.Config.ChartVersion return latestVersion, updated, nil } +func (c *HelmChart) findLatestVersion(inputVersions []string) (string, error) { + var versions semver.Collection + for _, x := range inputVersions { + v, err := semver.NewVersion(x) + if err != nil { + continue + } + + versions = append(versions, v) + } + if len(versions) == 0 { + return "", fmt.Errorf("no version found") + } + + sort.Stable(versions) + latestVersion := versions[len(versions)-1].Original() + return latestVersion, nil +} + func (c *HelmChart) Render(ctx context.Context, k *k8s.K8sCluster, sopsDecrypter sops.SopsDecrypter) error { err := c.doRender(ctx, k, sopsDecrypter) if err != nil { From 5e84b0f426e00f4ced5c40d5b8145c9a6510dce2 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 7 Dec 2022 17:15:33 +0100 Subject: [PATCH 1271/2916] feat: Implement updateConstraints for helm-chart.yaml --- docs/reference/deployments/helm.md | 11 +++++- e2e/helm_test.go | 63 ++++++++++++++++++++++++++++++ e2e/test-utils/project.go | 8 +++- pkg/deployment/helm_chart.go | 23 ++++++++++- pkg/types/helm_chart.go | 19 ++++----- 5 files changed, 111 insertions(+), 13 deletions(-) diff --git a/docs/reference/deployments/helm.md b/docs/reference/deployments/helm.md index bc49ddf0d..ee2c4ec21 100644 --- a/docs/reference/deployments/helm.md +++ b/docs/reference/deployments/helm.md @@ -68,6 +68,7 @@ helmChart: repo: https://charts.bitnami.com/bitnami chartName: redis chartVersion: 12.1.1 + updateConstraints: ~12.1.0 skipUpdate: false releaseName: redis-cache namespace: "{{ my.jinja2.var }}" @@ -102,7 +103,15 @@ helmChart: The name of the chart that can be found in the repository. ### chartVersion -The version of the chart. +The version of the chart. Must be a valid semantic version. + +### updateConstraints +Specifies version constraints to be used when running [helm-update](../commands/helm-update.md). See +[Checking Version Constraints](https://github.com/Masterminds/semver#checking-version-constraints) for details on the +supported syntax. + +If omitted, Kluctl will filter out pre-releases by default. Use a `updateConstraings` like `~1.2.3-0` to enable +pre-releases. ### skipUpdate Skip this Helm Chart when the [helm-update](../commands/helm-update.md) command is called. diff --git a/e2e/helm_test.go b/e2e/helm_test.go index 94386b8f8..4ac15022b 100644 --- a/e2e/helm_test.go +++ b/e2e/helm_test.go @@ -273,6 +273,69 @@ func TestHelmUpdateAndUpgradeAndCommitOci(t *testing.T) { testHelmUpdate(t, true, true, true) } +func testHelmUpdateConstraints(t *testing.T, oci bool) { + t.Parallel() + + k := defaultCluster1 + + p := test_utils.NewTestProject(t, k) + + createNamespace(t, k, p.TestSlug()) + + repoUrl := createHelmOrOciRepo(t, []test_utils.RepoChart{ + {ChartName: "test-chart1", Version: "0.1.0"}, + {ChartName: "test-chart1", Version: "0.1.1"}, + {ChartName: "test-chart1", Version: "0.2.0"}, + {ChartName: "test-chart1", Version: "1.1.0"}, + {ChartName: "test-chart1", Version: "1.1.1"}, + {ChartName: "test-chart1", Version: "1.2.1"}, + }, oci, "", "") + + p.UpdateTarget("test", nil) + p.AddHelmDeployment("helm1", repoUrl, "test-chart1", "0.1.0", "test-helm1", p.TestSlug(), nil) + p.AddHelmDeployment("helm2", repoUrl, "test-chart1", "0.1.0", "test-helm2", p.TestSlug(), nil) + p.AddHelmDeployment("helm3", repoUrl, "test-chart1", "0.1.0", "test-helm3", p.TestSlug(), nil) + + p.UpdateYaml("helm1/helm-chart.yaml", func(o *uo.UnstructuredObject) error { + _ = o.SetNestedField("~0.1.0", "helmChart", "updateConstraints") + return nil + }, "") + p.UpdateYaml("helm2/helm-chart.yaml", func(o *uo.UnstructuredObject) error { + _ = o.SetNestedField("~0.2.0", "helmChart", "updateConstraints") + return nil + }, "") + p.UpdateYaml("helm3/helm-chart.yaml", func(o *uo.UnstructuredObject) error { + _ = o.SetNestedField("~1.2.0", "helmChart", "updateConstraints") + return nil + }, "") + + args := []string{"helm-update", "--upgrade"} + + _, stderr := p.KluctlMust(args...) + assert.Contains(t, stderr, "helm1: Chart has new version 0.1.1 available.") + assert.Contains(t, stderr, "helm2: Chart has new version 0.2.0 available.") + assert.Contains(t, stderr, "helm3: Chart has new version 1.2.1 available.") + + c1 := p.GetYaml("helm1/helm-chart.yaml") + c2 := p.GetYaml("helm2/helm-chart.yaml") + c3 := p.GetYaml("helm3/helm-chart.yaml") + + v1, _, _ := c1.GetNestedString("helmChart", "chartVersion") + v2, _, _ := c2.GetNestedString("helmChart", "chartVersion") + v3, _, _ := c3.GetNestedString("helmChart", "chartVersion") + assert.Equal(t, "0.1.1", v1) + assert.Equal(t, "0.2.0", v2) + assert.Equal(t, "1.2.1", v3) +} + +func TestHelmUpdateConstraints(t *testing.T) { + testHelmUpdateConstraints(t, false) +} + +func TestHelmUpdateConstraintsOci(t *testing.T) { + testHelmUpdateConstraints(t, true) +} + func TestHelmValues(t *testing.T) { t.Parallel() diff --git a/e2e/test-utils/project.go b/e2e/test-utils/project.go index f52e77e80..ffc225c62 100644 --- a/e2e/test-utils/project.go +++ b/e2e/test-utils/project.go @@ -127,14 +127,18 @@ func (p *TestProject) UpdateFile(path string, update func(f string) (string, err p.gitServer.UpdateFile("kluctl-project", path, update, message) } -func (p *TestProject) GetDeploymentYaml(dir string) *uo.UnstructuredObject { - o, err := uo.FromFile(filepath.Join(p.LocalRepoDir(), dir, "deployment.yml")) +func (p *TestProject) GetYaml(path string) *uo.UnstructuredObject { + o, err := uo.FromFile(filepath.Join(p.LocalRepoDir(), path)) if err != nil { p.t.Fatal(err) } return o } +func (p *TestProject) GetDeploymentYaml(dir string) *uo.UnstructuredObject { + return p.GetYaml(filepath.Join(dir, "deployment.yml")) +} + func (p *TestProject) ListDeploymentItemPathes(dir string, fullPath bool) []string { var ret []string o := p.GetDeploymentYaml(dir) diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index 5308e9431..bd5f27fdf 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -347,6 +347,15 @@ func (c *HelmChart) checkUpdateHelmRepo() (string, bool, error) { } func (c *HelmChart) findLatestVersion(inputVersions []string) (string, error) { + var err error + var updateConstraints *semver.Constraints + if c.Config.UpdateConstraints != nil { + updateConstraints, err = semver.NewConstraint(*c.Config.UpdateConstraints) + if err != nil { + return "", fmt.Errorf("invalid update constraints '%s': %w", *c.Config.UpdateConstraints, err) + } + } + var versions semver.Collection for _, x := range inputVersions { v, err := semver.NewVersion(x) @@ -354,10 +363,22 @@ func (c *HelmChart) findLatestVersion(inputVersions []string) (string, error) { continue } + if updateConstraints == nil { + if v.Prerelease() != "" { + // we don't allow pre-releases by default. To allow pre-releases, use 1.0.0-0 as constraint + continue + } + } else if !updateConstraints.Check(v) { + continue + } versions = append(versions, v) } if len(versions) == 0 { - return "", fmt.Errorf("no version found") + if c.Config.UpdateConstraints == nil { + return "", fmt.Errorf("no version found") + } else { + return "", fmt.Errorf("no version found that satisfies constraints '%s'", *c.Config.UpdateConstraints) + } } sort.Stable(versions) diff --git a/pkg/types/helm_chart.go b/pkg/types/helm_chart.go index d5e9e4445..7cf645597 100644 --- a/pkg/types/helm_chart.go +++ b/pkg/types/helm_chart.go @@ -1,15 +1,16 @@ package types type HelmChartConfig2 struct { - Repo *string `yaml:"repo" validate:"required"` - CredentialsId *string `yaml:"credentialsId,omitempty"` - ChartName *string `yaml:"chartName,omitempty"` - ChartVersion *string `yaml:"chartVersion" validate:"required"` - ReleaseName string `yaml:"releaseName" validate:"required"` - Namespace *string `yaml:"namespace,omitempty"` - Output *string `yaml:"output,omitempty"` - SkipCRDs bool `yaml:"skipCRDs,omitempty"` - SkipUpdate bool `yaml:"skipUpdate,omitempty"` + Repo *string `yaml:"repo" validate:"required"` + CredentialsId *string `yaml:"credentialsId,omitempty"` + ChartName *string `yaml:"chartName,omitempty"` + ChartVersion *string `yaml:"chartVersion" validate:"required"` + UpdateConstraints *string `yaml:"updateConstraints"` + ReleaseName string `yaml:"releaseName" validate:"required"` + Namespace *string `yaml:"namespace,omitempty"` + Output *string `yaml:"output,omitempty"` + SkipCRDs bool `yaml:"skipCRDs,omitempty"` + SkipUpdate bool `yaml:"skipUpdate,omitempty"` } type HelmChartConfig struct { From 64f602eb37ff9e2b42ab6410c724319dd5539359 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 7 Dec 2022 22:14:05 +0000 Subject: [PATCH 1272/2916] chore(deps): Bump golang.org/x/net from 0.2.0 to 0.4.0 Bumps [golang.org/x/net](https://github.com/golang/net) from 0.2.0 to 0.4.0. - [Release notes](https://github.com/golang/net/releases) - [Commits](https://github.com/golang/net/compare/v0.2.0...v0.4.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++---- go.sum | 15 ++++++++------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index b3ba13049..df37fc236 100644 --- a/go.mod +++ b/go.mod @@ -39,11 +39,11 @@ require ( github.com/whilp/git-urls v1.0.0 github.com/xanzy/ssh-agent v0.3.3-0.20220920102508-0fa644ba07f4 golang.org/x/crypto v0.3.0 - golang.org/x/net v0.2.0 + golang.org/x/net v0.4.0 golang.org/x/sync v0.1.0 - golang.org/x/sys v0.2.0 - golang.org/x/term v0.2.0 - golang.org/x/text v0.4.0 + golang.org/x/sys v0.3.0 + golang.org/x/term v0.3.0 + golang.org/x/text v0.5.0 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 helm.sh/helm/v3 v3.10.2 diff --git a/go.sum b/go.sum index 83e4ab2bc..6a0a94efe 100644 --- a/go.sum +++ b/go.sum @@ -971,8 +971,8 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU= +golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1078,14 +1078,14 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1095,8 +1095,9 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From 17c8fd9074f27c02bb4464ea88f48ad1e42f6389 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 7 Dec 2022 23:18:05 +0100 Subject: [PATCH 1273/2916] chore: Add more info when pulling charts --- cmd/kluctl/commands/cmd_helm_pull.go | 2 +- cmd/kluctl/commands/cmd_helm_update.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/kluctl/commands/cmd_helm_pull.go b/cmd/kluctl/commands/cmd_helm_pull.go index af62259f1..fba329f84 100644 --- a/cmd/kluctl/commands/cmd_helm_pull.go +++ b/cmd/kluctl/commands/cmd_helm_pull.go @@ -90,7 +90,7 @@ func doPull(statusPrefix string, p string, helmCredentials args.HelmCredentials, chart.SetCredentials(&helmCredentials) - s.Update("%s: Pulling Chart %s with version %s", statusPrefix, chart.GetChartName(), *chart.Config.ChartVersion) + s.UpdateAndInfoFallback("%s: Pulling Chart %s with version %s", statusPrefix, chart.GetChartName(), *chart.Config.ChartVersion) err = chart.Pull(cliCtx) if err != nil { diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go index e831c591f..47edfeae9 100644 --- a/cmd/kluctl/commands/cmd_helm_update.go +++ b/cmd/kluctl/commands/cmd_helm_update.go @@ -218,7 +218,7 @@ func (cmd *helmUpdateCmd) pullAndCommitChart(gitRootPath string, chart *deployme } } - s.Update("%s: Committing chart", statusPrefix) + s.UpdateAndInfoFallback("%s: Committing chart", statusPrefix) mutex.Lock() defer mutex.Unlock() @@ -235,14 +235,14 @@ func (cmd *helmUpdateCmd) pullAndCommitChart(gitRootPath string, chart *deployme for _, p := range toAdd { _, err = wt.Add(p) if err != nil { - return err + return fmt.Errorf("failed to add %s to git index: %w", p, err) } } commitMsg := fmt.Sprintf("Updated helm chart %s from %s to %s", statusPrefix, oldVersion, newVersion) _, err = wt.Commit(commitMsg, &git.CommitOptions{}) if err != nil { - return err + return fmt.Errorf("failed to commit: %w", err) } s.Update("%s: Committed helm chart with version %s", statusPrefix, newVersion) From 18e686a7a594889fe45a75dfd94f3c149a71270b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 8 Dec 2022 09:51:38 +0100 Subject: [PATCH 1274/2916] fix: Fix sporadically occuring "no such file or directly" errors See the inline comment for details. --- cmd/kluctl/commands/cmd_helm_update.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go index 47edfeae9..2b9fad76b 100644 --- a/cmd/kluctl/commands/cmd_helm_update.go +++ b/cmd/kluctl/commands/cmd_helm_update.go @@ -11,9 +11,11 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils" "golang.org/x/sync/semaphore" "io/fs" + "math/rand" "os" "path/filepath" "sync" + "time" ) type helmUpdateCmd struct { @@ -233,7 +235,19 @@ func (cmd *helmUpdateCmd) pullAndCommitChart(gitRootPath string, chart *deployme } for _, p := range toAdd { - _, err = wt.Add(p) + // we have to retry a few times as Add() might fail with "no such file or directly" + // This is because it internally tries to get the git status, which fails if files are added/deleted in + // parallel by another goroutine (we're pulling in parallel). We're guarding the repo via the mutex from above + // so this is actually safe. + for i := 0; i < 10; i++ { + _, err = wt.Add(p) + if err == nil || !os.IsNotExist(err) { + break + } + // let's have some randomness in waiting time to ensure we don't run into the same problem again and again + s := time.Duration(rand.Intn(10) + 10) + time.Sleep(s * time.Millisecond) + } if err != nil { return fmt.Errorf("failed to add %s to git index: %w", p, err) } From 006eba70d60e5bddc948fed6905f1b94bf822de8 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 8 Dec 2022 09:58:00 +0100 Subject: [PATCH 1275/2916] refactor: Move --offline-kubernetes flag into OfflineKubernetesFlags --- cmd/kluctl/args/misc.go | 4 ++++ cmd/kluctl/commands/cmd_list_images.go | 4 ++-- cmd/kluctl/commands/cmd_render.go | 4 ++-- cmd/kluctl/commands/cmd_seal.go | 6 +++--- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/cmd/kluctl/args/misc.go b/cmd/kluctl/args/misc.go index 196f52872..7b2540c12 100644 --- a/cmd/kluctl/args/misc.go +++ b/cmd/kluctl/args/misc.go @@ -8,6 +8,10 @@ type YesFlags struct { Yes bool `group:"misc" short:"y" help:"Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'."` } +type OfflineKubernetesFlags struct { + OfflineKubernetes bool `group:"misc" help:"Run command in offline mode, meaning that it will not try to connect the target cluster"` +} + type DryRunFlags struct { DryRun bool `group:"misc" help:"Performs all kubernetes API calls in dry-run mode."` } diff --git a/cmd/kluctl/commands/cmd_list_images.go b/cmd/kluctl/commands/cmd_list_images.go index 371310ed1..477a757c2 100644 --- a/cmd/kluctl/commands/cmd_list_images.go +++ b/cmd/kluctl/commands/cmd_list_images.go @@ -14,9 +14,9 @@ type listImagesCmd struct { args.HelmCredentials args.OutputFlags args.RenderOutputDirFlags + args.OfflineKubernetesFlags - OfflineKubernetes bool `group:"misc" help:"Run list-images in offline mode, meaning that it will not try to connect the target cluster"` - Simple bool `group:"misc" help:"Output a simplified version of the images list"` + Simple bool `group:"misc" help:"Output a simplified version of the images list"` } func (cmd *listImagesCmd) Help() string { diff --git a/cmd/kluctl/commands/cmd_render.go b/cmd/kluctl/commands/cmd_render.go index f3fcf8b5c..9a82dc4ce 100644 --- a/cmd/kluctl/commands/cmd_render.go +++ b/cmd/kluctl/commands/cmd_render.go @@ -16,9 +16,9 @@ type renderCmd struct { args.ImageFlags args.HelmCredentials args.RenderOutputDirFlags + args.OfflineKubernetesFlags - OfflineKubernetes bool `group:"misc" help:"Run render in offline mode, meaning that it will not try to connect the target cluster"` - PrintAll bool `group:"misc" help:"Write all rendered manifests to stdout"` + PrintAll bool `group:"misc" help:"Write all rendered manifests to stdout"` } func (cmd *renderCmd) Help() string { diff --git a/cmd/kluctl/commands/cmd_seal.go b/cmd/kluctl/commands/cmd_seal.go index e1b2dce24..679d5697d 100644 --- a/cmd/kluctl/commands/cmd_seal.go +++ b/cmd/kluctl/commands/cmd_seal.go @@ -18,10 +18,10 @@ type sealCmd struct { args.ProjectFlags args.TargetFlags args.HelmCredentials + args.OfflineKubernetesFlags - ForceReseal bool `group:"misc" help:"Lets kluctl ignore secret hashes found in already sealed secrets and thus forces resealing of those."` - CertFile string `group:"misc" help:"Use the given certificate for sealing instead of requesting it from the sealed-secrets controller"` - OfflineKubernetes bool `group:"misc" help:"Run seal in offline mode, meaning that it will not try to connect the target cluster"` + ForceReseal bool `group:"misc" help:"Lets kluctl ignore secret hashes found in already sealed secrets and thus forces resealing of those."` + CertFile string `group:"misc" help:"Use the given certificate for sealing instead of requesting it from the sealed-secrets controller"` } func (cmd *sealCmd) Help() string { From df99219670320b1510f4fac0b275438d71e55f75 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Dec 2022 09:33:27 +0000 Subject: [PATCH 1276/2916] chore(deps): Bump golang.org/x/crypto from 0.3.0 to 0.4.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.3.0 to 0.4.0. - [Release notes](https://github.com/golang/crypto/releases) - [Commits](https://github.com/golang/crypto/compare/v0.3.0...v0.4.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index df37fc236..ab79f6f8b 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( github.com/stretchr/testify v1.8.1 github.com/whilp/git-urls v1.0.0 github.com/xanzy/ssh-agent v0.3.3-0.20220920102508-0fa644ba07f4 - golang.org/x/crypto v0.3.0 + golang.org/x/crypto v0.4.0 golang.org/x/net v0.4.0 golang.org/x/sync v0.1.0 golang.org/x/sys v0.3.0 diff --git a/go.sum b/go.sum index 6a0a94efe..4b69beba1 100644 --- a/go.sum +++ b/go.sum @@ -884,8 +884,8 @@ golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A= -golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8= +golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= From 17ba8b1c31d283de49701e7d8da979f6323e1183 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 8 Dec 2022 10:07:39 +0100 Subject: [PATCH 1277/2916] feat: Implement --kubernetes-version to be used with --offline-kubernetes --- cmd/kluctl/args/misc.go | 3 ++- cmd/kluctl/commands/cmd_list_images.go | 1 + cmd/kluctl/commands/cmd_render.go | 1 + cmd/kluctl/commands/cmd_seal.go | 1 + cmd/kluctl/commands/utils.go | 2 ++ pkg/deployment/deployment_item.go | 2 +- pkg/deployment/helm_chart.go | 12 +++++++++--- pkg/deployment/shared_context.go | 1 + pkg/kluctl_project/target_context.go | 2 ++ 9 files changed, 20 insertions(+), 5 deletions(-) diff --git a/cmd/kluctl/args/misc.go b/cmd/kluctl/args/misc.go index 7b2540c12..52b7b6f4c 100644 --- a/cmd/kluctl/args/misc.go +++ b/cmd/kluctl/args/misc.go @@ -9,7 +9,8 @@ type YesFlags struct { } type OfflineKubernetesFlags struct { - OfflineKubernetes bool `group:"misc" help:"Run command in offline mode, meaning that it will not try to connect the target cluster"` + OfflineKubernetes bool `group:"misc" help:"Run command in offline mode, meaning that it will not try to connect the target cluster"` + KubernetesVersion string `group:"misc" help:"Specify the Kubernetes version that will be assumed. This will also override the kubeVersion used when rendering Helm Charts."` } type DryRunFlags struct { diff --git a/cmd/kluctl/commands/cmd_list_images.go b/cmd/kluctl/commands/cmd_list_images.go index 477a757c2..748d2805e 100644 --- a/cmd/kluctl/commands/cmd_list_images.go +++ b/cmd/kluctl/commands/cmd_list_images.go @@ -36,6 +36,7 @@ func (cmd *listImagesCmd) Run() error { helmCredentials: cmd.HelmCredentials, renderOutputDirFlags: cmd.RenderOutputDirFlags, offlineKubernetes: cmd.OfflineKubernetes, + kubernetesVersion: cmd.KubernetesVersion, } return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { result := types.FixedImagesConfig{ diff --git a/cmd/kluctl/commands/cmd_render.go b/cmd/kluctl/commands/cmd_render.go index 9a82dc4ce..d7c5ff244 100644 --- a/cmd/kluctl/commands/cmd_render.go +++ b/cmd/kluctl/commands/cmd_render.go @@ -45,6 +45,7 @@ func (cmd *renderCmd) Run() error { helmCredentials: cmd.HelmCredentials, renderOutputDirFlags: cmd.RenderOutputDirFlags, offlineKubernetes: cmd.OfflineKubernetes, + kubernetesVersion: cmd.KubernetesVersion, } return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { if cmd.PrintAll { diff --git a/cmd/kluctl/commands/cmd_seal.go b/cmd/kluctl/commands/cmd_seal.go index 679d5697d..fc31ec63f 100644 --- a/cmd/kluctl/commands/cmd_seal.go +++ b/cmd/kluctl/commands/cmd_seal.go @@ -48,6 +48,7 @@ func (cmd *sealCmd) runCmdSealForTarget(ctx context.Context, p *kluctl_project.L helmCredentials: cmd.HelmCredentials, forSeal: true, offlineKubernetes: cmd.OfflineKubernetes, + kubernetesVersion: cmd.KubernetesVersion, } ptArgs.targetFlags.Target = targetName diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 221f97ac0..5715f93e4 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -101,6 +101,7 @@ type projectTargetCommandArgs struct { forSeal bool forCompletion bool offlineKubernetes bool + kubernetesVersion string } type commandCtx struct { @@ -167,6 +168,7 @@ func withProjectTargetCommandContext(ctx context.Context, args projectTargetComm TargetNameOverride: args.targetFlags.TargetNameOverride, ContextOverride: args.targetFlags.Context, OfflineK8s: args.offlineKubernetes, + K8sVersion: args.kubernetesVersion, DryRun: args.dryRunArgs == nil || args.dryRunArgs.DryRun || args.forCompletion, ExternalArgs: optionArgs2, ForSeal: args.forSeal, diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index f6caec641..bbd9665b6 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -214,7 +214,7 @@ func (di *DeploymentItem) renderHelmCharts() error { } } - return chart.Render(di.ctx.Ctx, di.ctx.K, di.ctx.SopsDecrypter) + return chart.Render(di.ctx.Ctx, di.ctx.K, di.ctx.K8sVersion, di.ctx.SopsDecrypter) }) if err != nil { return err diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index bd5f27fdf..d21426da2 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -386,15 +386,15 @@ func (c *HelmChart) findLatestVersion(inputVersions []string) (string, error) { return latestVersion, nil } -func (c *HelmChart) Render(ctx context.Context, k *k8s.K8sCluster, sopsDecrypter sops.SopsDecrypter) error { - err := c.doRender(ctx, k, sopsDecrypter) +func (c *HelmChart) Render(ctx context.Context, k *k8s.K8sCluster, k8sVersion string, sopsDecrypter sops.SopsDecrypter) error { + err := c.doRender(ctx, k, k8sVersion, sopsDecrypter) if err != nil { return fmt.Errorf("rendering helm chart %s for release %s has failed: %w", c.chartName, c.Config.ReleaseName, err) } return nil } -func (c *HelmChart) doRender(ctx context.Context, k *k8s.K8sCluster, sopsDecrypter sops.SopsDecrypter) error { +func (c *HelmChart) doRender(ctx context.Context, k *k8s.K8sCluster, k8sVersion string, sopsDecrypter sops.SopsDecrypter) error { chartDir := c.chartDir needsPull, versionChanged, prePulledVersion, err := c.checkNeedsPull(chartDir, false) @@ -457,6 +457,12 @@ func (c *HelmChart) doRender(ctx context.Context, k *k8s.K8sCluster, sopsDecrypt return err } } + if k8sVersion != "" { + kubeVersion, err = chartutil.ParseKubeVersion(k8sVersion) + if err != nil { + return err + } + } namespace := "default" if c.Config.Namespace != nil { diff --git a/pkg/deployment/shared_context.go b/pkg/deployment/shared_context.go index be1492050..7fce849ce 100644 --- a/pkg/deployment/shared_context.go +++ b/pkg/deployment/shared_context.go @@ -11,6 +11,7 @@ import ( type SharedContext struct { Ctx context.Context K *k8s.K8sCluster + K8sVersion string RP *repocache.GitRepoCache SopsDecrypter sops.SopsDecrypter VarsLoader *vars.VarsLoader diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index 952c44d85..980585382 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -31,6 +31,7 @@ type TargetContextParams struct { TargetNameOverride string ContextOverride string OfflineK8s bool + K8sVersion string DryRun bool ExternalArgs *uo.UnstructuredObject ForSeal bool @@ -103,6 +104,7 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, params Targe dctx := deployment.SharedContext{ Ctx: ctx, K: k, + K8sVersion: params.K8sVersion, RP: p.RP, SopsDecrypter: p.SopsDecrypter, VarsLoader: varsLoader, From da1647e2b7122ba148d27661e32cd4b1f7d34a1b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 8 Dec 2022 10:20:07 +0100 Subject: [PATCH 1278/2916] tests: Add tests for --kubernetes-version --- e2e/helm_test.go | 58 ++++++++++++++++--- e2e/sops_test.go | 7 ++- e2e/test-utils/envtest_cluster.go | 12 ++++ .../test-helm-chart/templates/configmap.yaml | 1 + 4 files changed, 66 insertions(+), 12 deletions(-) diff --git a/e2e/helm_test.go b/e2e/helm_test.go index 4ac15022b..cedfa3a60 100644 --- a/e2e/helm_test.go +++ b/e2e/helm_test.go @@ -382,19 +382,22 @@ func TestHelmValues(t *testing.T) { cm3 := assertConfigMapExists(t, k, p.TestSlug(), "test-helm3-test-chart1") assert.Equal(t, map[string]any{ - "a": "x1", - "b": "y1", - "version": "0.1.0", + "a": "x1", + "b": "y1", + "version": "0.1.0", + "kubeVersion": k.ServerVersion.String(), }, cm1.Object["data"]) assert.Equal(t, map[string]any{ - "a": "x2", - "b": "y2", - "version": "0.1.0", + "a": "x2", + "b": "y2", + "version": "0.1.0", + "kubeVersion": k.ServerVersion.String(), }, cm2.Object["data"]) assert.Equal(t, map[string]any{ - "a": "a", - "b": "b", - "version": "0.1.0", + "a": "a", + "b": "b", + "version": "0.1.0", + "kubeVersion": k.ServerVersion.String(), }, cm3.Object["data"]) } @@ -428,3 +431,40 @@ func TestHelmTemplateChartYaml(t *testing.T) { assertConfigMapExists(t, k, p.TestSlug()+"-a", "test-helm-ns-test-chart1") assertConfigMapExists(t, k, p.TestSlug()+"-b", "test-helm-ns-test-chart1") } + +func TestHelmRenderOfflineKubernetes(t *testing.T) { + t.Parallel() + + k := defaultCluster1 + + p := test_utils.NewTestProject(t, k) + + createNamespace(t, k, p.TestSlug()) + + repoUrl := test_utils.CreateHelmRepo(t, []test_utils.RepoChart{ + {ChartName: "test-chart1", Version: "0.1.0"}, + }, "", "") + + p.UpdateTarget("test", nil) + p.AddHelmDeployment("helm1", repoUrl, "test-chart1", "0.1.0", "test-helm1", p.TestSlug(), nil) + + stdout, _ := p.KluctlMust("render", "--print-all", "--offline-kubernetes", "-t", "test") + cm1 := uo.FromStringMust(stdout) + + assert.Equal(t, map[string]any{ + "a": "v1", + "b": "v2", + "version": "0.1.0", + "kubeVersion": "v1.20.0", + }, cm1.Object["data"]) + + stdout, _ = p.KluctlMust("render", "--print-all", "--offline-kubernetes", "--kubernetes-version", "1.22.1", "-t", "test") + cm1 = uo.FromStringMust(stdout) + + assert.Equal(t, map[string]any{ + "a": "v1", + "b": "v2", + "version": "0.1.0", + "kubeVersion": "v1.22.1", + }, cm1.Object["data"]) +} diff --git a/e2e/sops_test.go b/e2e/sops_test.go index 6b4a1a1a2..da65ece12 100644 --- a/e2e/sops_test.go +++ b/e2e/sops_test.go @@ -109,8 +109,9 @@ func TestSopsHelmValues(t *testing.T) { cm1 := assertConfigMapExists(t, k, p.TestSlug(), "test-helm1-test-chart1") assert.Equal(t, map[string]any{ - "a": "secret1", - "b": "secret2", - "version": "0.1.0", + "a": "secret1", + "b": "secret2", + "version": "0.1.0", + "kubeVersion": k.ServerVersion.String(), }, cm1.Object["data"]) } diff --git a/e2e/test-utils/envtest_cluster.go b/e2e/test-utils/envtest_cluster.go index c32b82665..df6893c7d 100644 --- a/e2e/test-utils/envtest_cluster.go +++ b/e2e/test-utils/envtest_cluster.go @@ -7,6 +7,8 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils/uo" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/version" + "k8s.io/client-go/discovery" "k8s.io/client-go/dynamic" "k8s.io/client-go/rest" "k8s.io/client-go/util/flowcontrol" @@ -28,6 +30,7 @@ type EnvTestCluster struct { HttpClient *http.Client DynamicClient dynamic.Interface + ServerVersion *version.Info callbackServer webhook.Server callbackServerStop context.CancelFunc @@ -85,6 +88,15 @@ func (k *EnvTestCluster) Start() error { } k.DynamicClient = dynamicClient + discoveryClient, err := discovery.NewDiscoveryClientForConfigAndClient(k.config, client) + if err != nil { + return err + } + k.ServerVersion, err = discoveryClient.ServerVersion() + if err != nil { + return err + } + return nil } diff --git a/e2e/test-utils/test-helm-chart/templates/configmap.yaml b/e2e/test-utils/test-helm-chart/templates/configmap.yaml index fe63edb28..25adf10ac 100644 --- a/e2e/test-utils/test-helm-chart/templates/configmap.yaml +++ b/e2e/test-utils/test-helm-chart/templates/configmap.yaml @@ -8,3 +8,4 @@ data: a: {{ .Values.data.a }} b: {{ .Values.data.b }} version: {{ .Chart.Version }} + kubeVersion: {{ .Capabilities.KubeVersion }} From f25e9812e4538b92470de55d0814a0a86265b45e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 8 Dec 2022 10:34:46 +0100 Subject: [PATCH 1279/2916] docs: Run make replace-commands-help --- docs/reference/commands/list-images.md | 6 ++++-- docs/reference/commands/render.md | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/reference/commands/list-images.md b/docs/reference/commands/list-images.md index e90fe6624..125b79770 100644 --- a/docs/reference/commands/list-images.md +++ b/docs/reference/commands/list-images.md @@ -48,8 +48,10 @@ Misc arguments: Must be in the form --helm-username=:, where must match the id specified in the helm-chart.yaml. - --offline-kubernetes Run list-images in offline mode, meaning that it will not try - to connect the target cluster + --kubernetes-version string Specify the Kubernetes version that will be assumed. This will + also override the kubeVersion used when rendering Helm Charts. + --offline-kubernetes Run command in offline mode, meaning that it will not try to + connect the target cluster -o, --output stringArray Specify output target file. Can be specified multiple times --render-output-dir string Specifies the target directory to render the project into. If omitted, a temporary directory is used. diff --git a/docs/reference/commands/render.md b/docs/reference/commands/render.md index f27882a06..3556e40c0 100644 --- a/docs/reference/commands/render.md +++ b/docs/reference/commands/render.md @@ -45,7 +45,9 @@ Misc arguments: Must be in the form --helm-username=:, where must match the id specified in the helm-chart.yaml. - --offline-kubernetes Run render in offline mode, meaning that it will not try to + --kubernetes-version string Specify the Kubernetes version that will be assumed. This will + also override the kubeVersion used when rendering Helm Charts. + --offline-kubernetes Run command in offline mode, meaning that it will not try to connect the target cluster --print-all Write all rendered manifests to stdout --render-output-dir string Specifies the target directory to render the project into. If From 5a5112f9957acd247467111c31c2041fe6dbd272 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 9 Dec 2022 10:34:43 +0100 Subject: [PATCH 1280/2916] refactor: Don't use global cliCtx anymore and pass ctx around where needed --- .../commands/cmd_check_image_updates.go | 17 +++---- cmd/kluctl/commands/cmd_delete.go | 12 ++--- cmd/kluctl/commands/cmd_deploy.go | 31 +++++++------ cmd/kluctl/commands/cmd_diff.go | 11 ++--- cmd/kluctl/commands/cmd_flux_reconcile.go | 10 ++--- cmd/kluctl/commands/cmd_flux_resume.go | 4 +- cmd/kluctl/commands/cmd_flux_suspend.go | 4 +- cmd/kluctl/commands/cmd_helm_pull.go | 13 +++--- cmd/kluctl/commands/cmd_helm_update.go | 25 ++++++----- cmd/kluctl/commands/cmd_list_images.go | 9 ++-- cmd/kluctl/commands/cmd_list_targets.go | 6 +-- cmd/kluctl/commands/cmd_poke_images.go | 13 +++--- cmd/kluctl/commands/cmd_prune.go | 17 +++---- cmd/kluctl/commands/cmd_render.go | 11 ++--- cmd/kluctl/commands/cmd_seal.go | 34 +++++++------- cmd/kluctl/commands/cmd_validate.go | 11 ++--- cmd/kluctl/commands/cmd_version.go | 3 +- cmd/kluctl/commands/cobra_utils.go | 5 ++- cmd/kluctl/commands/command_result.go | 13 +++--- cmd/kluctl/commands/completion.go | 44 ++++++++++--------- cmd/kluctl/commands/root.go | 8 +++- cmd/kluctl/commands/utils.go | 12 ++--- 22 files changed, 167 insertions(+), 146 deletions(-) diff --git a/cmd/kluctl/commands/cmd_check_image_updates.go b/cmd/kluctl/commands/cmd_check_image_updates.go index 14be5ede7..a21df32c3 100644 --- a/cmd/kluctl/commands/cmd_check_image_updates.go +++ b/cmd/kluctl/commands/cmd_check_image_updates.go @@ -1,6 +1,7 @@ package commands import ( + "context" "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/registries" "github.com/kluctl/kluctl/v2/pkg/status" @@ -22,20 +23,20 @@ func (cmd *checkImageUpdatesCmd) Help() string { return `This is based on a best effort approach and might give many false-positives.` } -func (cmd *checkImageUpdatesCmd) Run() error { +func (cmd *checkImageUpdatesCmd) Run(ctx context.Context) error { ptArgs := projectTargetCommandArgs{ projectFlags: cmd.ProjectFlags, targetFlags: cmd.TargetFlags, } - return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { - return runCheckImageUpdates(ctx) + return withProjectCommandContext(ctx, ptArgs, func(cmdCtx *commandCtx) error { + return runCheckImageUpdates(cmdCtx) }) } -func runCheckImageUpdates(ctx *commandCtx) error { - renderedImages := ctx.targetCtx.DeploymentCollection.FindRenderedImages() +func runCheckImageUpdates(cmdCtx *commandCtx) error { + renderedImages := cmdCtx.targetCtx.DeploymentCollection.FindRenderedImages() - rh := registries.NewRegistryHelper(ctx.ctx) + rh := registries.NewRegistryHelper(cmdCtx.ctx) imageTags := make(map[string]interface{}) var mutex sync.Mutex @@ -76,7 +77,7 @@ func runCheckImageUpdates(ctx *commandCtx) error { for _, image := range images { s := strings.SplitN(image, ":", 2) if len(s) == 1 { - status.Warning(ctx.ctx, "%s: Ignoring image %s as it doesn't specify a tag", ref.String(), image) + status.Warning(cmdCtx.ctx, "%s: Ignoring image %s as it doesn't specify a tag", ref.String(), image) continue } repo := s[0] @@ -84,7 +85,7 @@ func runCheckImageUpdates(ctx *commandCtx) error { repoTags, _ := imageTags[repo].([]string) err, _ := imageTags[repo].(error) if err != nil { - status.Warning(ctx.ctx, "%s: Failed to list tags for %s. %v", ref.String(), repo, err) + status.Warning(cmdCtx.ctx, "%s: Failed to list tags for %s. %v", ref.String(), repo, err) continue } diff --git a/cmd/kluctl/commands/cmd_delete.go b/cmd/kluctl/commands/cmd_delete.go index ecfc97814..48a09b2c7 100644 --- a/cmd/kluctl/commands/cmd_delete.go +++ b/cmd/kluctl/commands/cmd_delete.go @@ -37,7 +37,7 @@ project (anymore). It really only decides based on the 'deleteByLabel' labels an take the local target/state into account!` } -func (cmd *deleteCmd) Run() error { +func (cmd *deleteCmd) Run(ctx context.Context) error { ptArgs := projectTargetCommandArgs{ projectFlags: cmd.ProjectFlags, targetFlags: cmd.TargetFlags, @@ -48,8 +48,8 @@ func (cmd *deleteCmd) Run() error { dryRunArgs: &cmd.DryRunFlags, renderOutputDirFlags: cmd.RenderOutputDirFlags, } - return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { - cmd2 := commands.NewDeleteCommand(ctx.targetCtx.DeploymentCollection) + return withProjectCommandContext(ctx, ptArgs, func(cmdCtx *commandCtx) error { + cmd2 := commands.NewDeleteCommand(cmdCtx.targetCtx.DeploymentCollection) deleteByLabels, err := deployment.ParseArgs(cmd.DeleteByLabel) if err != nil { @@ -58,15 +58,15 @@ func (cmd *deleteCmd) Run() error { cmd2.OverrideDeleteByLabels = deleteByLabels - objects, err := cmd2.Run(ctx.ctx, ctx.targetCtx.SharedContext.K) + objects, err := cmd2.Run(cmdCtx.ctx, cmdCtx.targetCtx.SharedContext.K) if err != nil { return err } - result, err := confirmedDeleteObjects(ctx.ctx, ctx.targetCtx.SharedContext.K, objects, cmd.DryRun, cmd.Yes) + result, err := confirmedDeleteObjects(cmdCtx.ctx, cmdCtx.targetCtx.SharedContext.K, objects, cmd.DryRun, cmd.Yes) if err != nil { return err } - err = outputCommandResult(cmd.OutputFormat, result) + err = outputCommandResult(ctx, cmd.OutputFormat, result) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_deploy.go b/cmd/kluctl/commands/cmd_deploy.go index 9b62a7aa8..f819935db 100644 --- a/cmd/kluctl/commands/cmd_deploy.go +++ b/cmd/kluctl/commands/cmd_deploy.go @@ -1,6 +1,7 @@ package commands import ( + "context" "fmt" "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/deployment/commands" @@ -34,7 +35,7 @@ It will also output a list of prunable objects (without actually deleting them). ` } -func (cmd *deployCmd) Run() error { +func (cmd *deployCmd) Run(ctx context.Context) error { ptArgs := projectTargetCommandArgs{ projectFlags: cmd.ProjectFlags, targetFlags: cmd.TargetFlags, @@ -45,16 +46,16 @@ func (cmd *deployCmd) Run() error { dryRunArgs: &cmd.DryRunFlags, renderOutputDirFlags: cmd.RenderOutputDirFlags, } - return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { - return cmd.runCmdDeploy(ctx) + return withProjectCommandContext(ctx, ptArgs, func(cmdCtx *commandCtx) error { + return cmd.runCmdDeploy(cmdCtx) }) } -func (cmd *deployCmd) runCmdDeploy(ctx *commandCtx) error { - status.Trace(ctx.ctx, "enter runCmdDeploy") - defer status.Trace(ctx.ctx, "leave runCmdDeploy") +func (cmd *deployCmd) runCmdDeploy(cmdCtx *commandCtx) error { + status.Trace(cmdCtx.ctx, "enter runCmdDeploy") + defer status.Trace(cmdCtx.ctx, "leave runCmdDeploy") - cmd2 := commands.NewDeployCommand(ctx.targetCtx.DeploymentCollection) + cmd2 := commands.NewDeployCommand(cmdCtx.targetCtx.DeploymentCollection) cmd2.ForceApply = cmd.ForceApply cmd2.ReplaceOnError = cmd.ReplaceOnError cmd2.ForceReplaceOnError = cmd.ForceReplaceOnError @@ -62,16 +63,18 @@ func (cmd *deployCmd) runCmdDeploy(ctx *commandCtx) error { cmd2.ReadinessTimeout = cmd.ReadinessTimeout cmd2.NoWait = cmd.NoWait - cb := cmd.diffResultCb + cb := func(diffResult *types.CommandResult) error { + return cmd.diffResultCb(cmdCtx.ctx, diffResult) + } if cmd.Yes || cmd.DryRun { cb = nil } - result, err := cmd2.Run(ctx.ctx, ctx.targetCtx.SharedContext.K, cb) + result, err := cmd2.Run(cmdCtx.ctx, cmdCtx.targetCtx.SharedContext.K, cb) if err != nil { return err } - err = outputCommandResult(cmd.OutputFormat, result) + err = outputCommandResult(cmdCtx.ctx, cmd.OutputFormat, result) if err != nil { return err } @@ -81,8 +84,8 @@ func (cmd *deployCmd) runCmdDeploy(ctx *commandCtx) error { return nil } -func (cmd *deployCmd) diffResultCb(diffResult *types.CommandResult) error { - err := outputCommandResult(nil, diffResult) +func (cmd *deployCmd) diffResultCb(ctx context.Context, diffResult *types.CommandResult) error { + err := outputCommandResult(ctx, nil, diffResult) if err != nil { return err } @@ -90,11 +93,11 @@ func (cmd *deployCmd) diffResultCb(diffResult *types.CommandResult) error { return nil } if len(diffResult.Errors) != 0 { - if !status.AskForConfirmation(cliCtx, "The diff resulted in errors, do you still want to proceed?") { + if !status.AskForConfirmation(ctx, "The diff resulted in errors, do you still want to proceed?") { return fmt.Errorf("aborted") } } else { - if !status.AskForConfirmation(cliCtx, "The diff succeeded, do you want to proceed?") { + if !status.AskForConfirmation(ctx, "The diff succeeded, do you want to proceed?") { return fmt.Errorf("aborted") } } diff --git a/cmd/kluctl/commands/cmd_diff.go b/cmd/kluctl/commands/cmd_diff.go index d18becfe5..bb8fcce19 100644 --- a/cmd/kluctl/commands/cmd_diff.go +++ b/cmd/kluctl/commands/cmd_diff.go @@ -1,6 +1,7 @@ package commands import ( + "context" "fmt" "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/deployment/commands" @@ -27,7 +28,7 @@ is currently not documented and prone to changes. After the diff is performed, the command will also search for prunable objects and list them.` } -func (cmd *diffCmd) Run() error { +func (cmd *diffCmd) Run(ctx context.Context) error { ptArgs := projectTargetCommandArgs{ projectFlags: cmd.ProjectFlags, targetFlags: cmd.TargetFlags, @@ -37,19 +38,19 @@ func (cmd *diffCmd) Run() error { helmCredentials: cmd.HelmCredentials, renderOutputDirFlags: cmd.RenderOutputDirFlags, } - return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { - cmd2 := commands.NewDiffCommand(ctx.targetCtx.DeploymentCollection) + return withProjectCommandContext(ctx, ptArgs, func(cmdCtx *commandCtx) error { + cmd2 := commands.NewDiffCommand(cmdCtx.targetCtx.DeploymentCollection) cmd2.ForceApply = cmd.ForceApply cmd2.ReplaceOnError = cmd.ReplaceOnError cmd2.ForceReplaceOnError = cmd.ForceReplaceOnError cmd2.IgnoreTags = cmd.IgnoreTags cmd2.IgnoreLabels = cmd.IgnoreLabels cmd2.IgnoreAnnotations = cmd.IgnoreAnnotations - result, err := cmd2.Run(ctx.ctx, ctx.targetCtx.SharedContext.K) + result, err := cmd2.Run(cmdCtx.ctx, cmdCtx.targetCtx.SharedContext.K) if err != nil { return err } - err = outputCommandResult(cmd.OutputFormat, result) + err = outputCommandResult(ctx, cmd.OutputFormat, result) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_flux_reconcile.go b/cmd/kluctl/commands/cmd_flux_reconcile.go index a6ed024e2..8e4668024 100644 --- a/cmd/kluctl/commands/cmd_flux_reconcile.go +++ b/cmd/kluctl/commands/cmd_flux_reconcile.go @@ -16,7 +16,7 @@ type fluxReconcileCmd struct { args.KluctlDeploymentFlags } -func (cmd *fluxReconcileCmd) Run() error { +func (cmd *fluxReconcileCmd) Run(ctx context.Context) error { var ( sourceNamespace string sourceName string @@ -61,7 +61,7 @@ func (cmd *fluxReconcileCmd) Run() error { } ref2 := k8s2.ObjectRef{GVK: args.GitRepositoryGVK, Name: sourceName, Namespace: sourceNamespace} - s := status.Start(cliCtx, "Annotating Source %s in %s namespace", sourceName, sourceNamespace) + s := status.Start(ctx, "Annotating Source %s in %s namespace", sourceName, sourceNamespace) defer s.Failed() _, _, err = k.PatchObjectWithJsonPatch(ref2, patch, k8s.PatchOptions{}) @@ -70,7 +70,7 @@ func (cmd *fluxReconcileCmd) Run() error { } s.Success() - s = status.Start(cliCtx, "Waiting for Source %s to finish reconciliation", sourceName) + s = status.Start(ctx, "Waiting for Source %s to finish reconciliation", sourceName) if !noWait { ready, err := WaitForReady(k, ref2) @@ -83,7 +83,7 @@ func (cmd *fluxReconcileCmd) Run() error { s.Success() } - s := status.Start(cliCtx, "Annotating KluctlDeployment %s in %s namespace", kd, ns) + s := status.Start(ctx, "Annotating KluctlDeployment %s in %s namespace", kd, ns) defer s.Failed() _, _, err = k.PatchObjectWithJsonPatch(ref, patch, k8s.PatchOptions{}) @@ -92,7 +92,7 @@ func (cmd *fluxReconcileCmd) Run() error { } s.Success() - s = status.Start(cliCtx, "Waiting for KluctlDeployment %s in %s namespace to finish reconciliation", kd, ns) + s = status.Start(ctx, "Waiting for KluctlDeployment %s in %s namespace to finish reconciliation", kd, ns) if !noWait { ready, err := WaitForReady(k, ref) diff --git a/cmd/kluctl/commands/cmd_flux_resume.go b/cmd/kluctl/commands/cmd_flux_resume.go index 8b7aaafc8..3b90f1219 100644 --- a/cmd/kluctl/commands/cmd_flux_resume.go +++ b/cmd/kluctl/commands/cmd_flux_resume.go @@ -14,7 +14,7 @@ type fluxResumeCmd struct { } // TODO add reconciliation after resume -func (cmd *fluxResumeCmd) Run() error { +func (cmd *fluxResumeCmd) Run(ctx context.Context) error { ns := cmd.KluctlDeploymentFlags.Namespace kd := cmd.KluctlDeploymentFlags.KluctlDeployment @@ -35,7 +35,7 @@ func (cmd *fluxResumeCmd) Run() error { Value: false, }} - s := status.Start(cliCtx, "Resuming KluctlDeployment %s in %s namespace", kd, ns) + s := status.Start(ctx, "Resuming KluctlDeployment %s in %s namespace", kd, ns) defer s.Failed() _, _, err = k.PatchObjectWithJsonPatch(ref, patch, k8s.PatchOptions{}) diff --git a/cmd/kluctl/commands/cmd_flux_suspend.go b/cmd/kluctl/commands/cmd_flux_suspend.go index 6e5422fb6..d06212dbe 100644 --- a/cmd/kluctl/commands/cmd_flux_suspend.go +++ b/cmd/kluctl/commands/cmd_flux_suspend.go @@ -13,7 +13,7 @@ type fluxSuspendCmd struct { args.KluctlDeploymentFlags } -func (cmd *fluxSuspendCmd) Run() error { +func (cmd *fluxSuspendCmd) Run(ctx context.Context) error { ns := cmd.KluctlDeploymentFlags.Namespace kd := cmd.KluctlDeploymentFlags.KluctlDeployment @@ -33,7 +33,7 @@ func (cmd *fluxSuspendCmd) Run() error { Value: true, }} - s := status.Start(cliCtx, "Suspending KluctlDeployment %s in %s namespace", kd, ns) + s := status.Start(ctx, "Suspending KluctlDeployment %s in %s namespace", kd, ns) defer s.Failed() _, _, err = k.PatchObjectWithJsonPatch(ref, patch, k8s.PatchOptions{}) diff --git a/cmd/kluctl/commands/cmd_helm_pull.go b/cmd/kluctl/commands/cmd_helm_pull.go index fba329f84..4ee57ec2e 100644 --- a/cmd/kluctl/commands/cmd_helm_pull.go +++ b/cmd/kluctl/commands/cmd_helm_pull.go @@ -1,6 +1,7 @@ package commands import ( + "context" "fmt" "github.com/hashicorp/go-multierror" "github.com/kluctl/kluctl/v2/cmd/kluctl/args" @@ -25,7 +26,7 @@ func (cmd *helmPullCmd) Help() string { pulling is only needed when really required (e.g. when the chart version changes).` } -func (cmd *helmPullCmd) Run() error { +func (cmd *helmPullCmd) Run(ctx context.Context) error { cwd, err := os.Getwd() if err != nil { return err @@ -52,10 +53,10 @@ func (cmd *helmPullCmd) Run() error { return err } - utils.GoLimitedMultiError(cliCtx, sem, &errs, &mutex, &wg, func() error { - s := status.Start(cliCtx, "%s: Pulling Chart", statusPrefix) + utils.GoLimitedMultiError(ctx, sem, &errs, &mutex, &wg, func() error { + s := status.Start(ctx, "%s: Pulling Chart", statusPrefix) defer s.Failed() - err := doPull(statusPrefix, p, cmd.HelmCredentials, s) + err := doPull(ctx, statusPrefix, p, cmd.HelmCredentials, s) if err != nil { return err } @@ -77,7 +78,7 @@ func (cmd *helmPullCmd) Run() error { return nil } -func doPull(statusPrefix string, p string, helmCredentials args.HelmCredentials, s *status.StatusContext) error { +func doPull(ctx context.Context, statusPrefix string, p string, helmCredentials args.HelmCredentials, s *status.StatusContext) error { doError := func(err error) error { s.FailedWithMessage("%s: %s", statusPrefix, err.Error()) return err @@ -92,7 +93,7 @@ func doPull(statusPrefix string, p string, helmCredentials args.HelmCredentials, s.UpdateAndInfoFallback("%s: Pulling Chart %s with version %s", statusPrefix, chart.GetChartName(), *chart.Config.ChartVersion) - err = chart.Pull(cliCtx) + err = chart.Pull(ctx) if err != nil { return doError(err) } diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go index 2b9fad76b..beecb5d0e 100644 --- a/cmd/kluctl/commands/cmd_helm_update.go +++ b/cmd/kluctl/commands/cmd_helm_update.go @@ -1,6 +1,7 @@ package commands import ( + "context" "fmt" "github.com/go-git/go-git/v5" "github.com/hashicorp/go-multierror" @@ -31,7 +32,7 @@ func (cmd *helmUpdateCmd) Help() string { return `Optionally performs the actual upgrade and/or add a commit to version control.` } -func (cmd *helmUpdateCmd) Run() error { +func (cmd *helmUpdateCmd) Run(ctx context.Context) error { cwd, err := os.Getwd() if err != nil { return err @@ -62,8 +63,8 @@ func (cmd *helmUpdateCmd) Run() error { return nil } - utils.GoLimitedMultiError(cliCtx, sem, &errs, &mutex, &wg, func() error { - chart, newVersion, updated, err := cmd.doCheckUpdate(gitRootPath, p) + utils.GoLimitedMultiError(ctx, sem, &errs, &mutex, &wg, func() error { + chart, newVersion, updated, err := cmd.doCheckUpdate(ctx, gitRootPath, p) if err != nil { return err } @@ -100,16 +101,16 @@ func (cmd *helmUpdateCmd) Run() error { for _, uc := range updatedCharts { uc := uc - utils.GoLimitedMultiError(cliCtx, sem, &errs, &mutex, &wg, func() error { + utils.GoLimitedMultiError(ctx, sem, &errs, &mutex, &wg, func() error { if cmd.Interactive { statusPrefix, _ := filepath.Rel(gitRootPath, filepath.Dir(uc.path)) - if !status.AskForConfirmation(cliCtx, fmt.Sprintf("%s: Do you want to upgrade Chart %s from version %s to %s?", + if !status.AskForConfirmation(ctx, fmt.Sprintf("%s: Do you want to upgrade Chart %s from version %s to %s?", statusPrefix, uc.chart.GetChartName(), uc.oldVersion, uc.newVersion)) { return nil } } - err := cmd.pullAndCommitChart(gitRootPath, uc.chart, uc.oldVersion, uc.newVersion, &mutex) + err := cmd.pullAndCommitChart(ctx, gitRootPath, uc.chart, uc.oldVersion, uc.newVersion, &mutex) if err != nil { return err } @@ -125,13 +126,13 @@ func (cmd *helmUpdateCmd) Run() error { return errs.ErrorOrNil() } -func (cmd *helmUpdateCmd) doCheckUpdate(gitRootPath string, p string) (*deployment.HelmChart, string, bool, error) { +func (cmd *helmUpdateCmd) doCheckUpdate(ctx context.Context, gitRootPath string, p string) (*deployment.HelmChart, string, bool, error) { statusPrefix, err := filepath.Rel(gitRootPath, filepath.Dir(p)) if err != nil { return nil, "", false, err } - s := status.Start(cliCtx, "%s: Checking for updates", statusPrefix) + s := status.Start(ctx, "%s: Checking for updates", statusPrefix) doError := func(err error) (*deployment.HelmChart, string, bool, error) { s.FailedWithMessage("%s: %s", statusPrefix, err.Error()) return nil, "", false, err @@ -144,7 +145,7 @@ func (cmd *helmUpdateCmd) doCheckUpdate(gitRootPath string, p string) (*deployme chart.SetCredentials(&cmd.HelmCredentials) - newVersion, updated, err := chart.CheckUpdate(cliCtx) + newVersion, updated, err := chart.CheckUpdate(ctx) if err != nil { return doError(err) } @@ -162,13 +163,13 @@ func (cmd *helmUpdateCmd) doCheckUpdate(gitRootPath string, p string) (*deployme return chart, newVersion, updated, nil } -func (cmd *helmUpdateCmd) pullAndCommitChart(gitRootPath string, chart *deployment.HelmChart, oldVersion string, newVersion string, mutex *sync.Mutex) error { +func (cmd *helmUpdateCmd) pullAndCommitChart(ctx context.Context, gitRootPath string, chart *deployment.HelmChart, oldVersion string, newVersion string, mutex *sync.Mutex) error { statusPrefix, err := filepath.Rel(gitRootPath, filepath.Dir(chart.ConfigFile)) if err != nil { return err } - s := status.Start(cliCtx, "%s: Pulling Chart", statusPrefix) + s := status.Start(ctx, "%s: Pulling Chart", statusPrefix) defer s.Failed() chart.Config.ChartVersion = &newVersion @@ -195,7 +196,7 @@ func (cmd *helmUpdateCmd) pullAndCommitChart(gitRootPath string, chart *deployme return err } - err = doPull(statusPrefix, chart.ConfigFile, cmd.HelmCredentials, s) + err = doPull(ctx, statusPrefix, chart.ConfigFile, cmd.HelmCredentials, s) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_list_images.go b/cmd/kluctl/commands/cmd_list_images.go index 748d2805e..7e1f78e83 100644 --- a/cmd/kluctl/commands/cmd_list_images.go +++ b/cmd/kluctl/commands/cmd_list_images.go @@ -1,6 +1,7 @@ package commands import ( + "context" "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/types" ) @@ -26,7 +27,7 @@ If fixed images ('-f/--fixed-image') are provided, these are also taken into acc as described in the deploy command.` } -func (cmd *listImagesCmd) Run() error { +func (cmd *listImagesCmd) Run(ctx context.Context) error { ptArgs := projectTargetCommandArgs{ projectFlags: cmd.ProjectFlags, targetFlags: cmd.TargetFlags, @@ -38,10 +39,10 @@ func (cmd *listImagesCmd) Run() error { offlineKubernetes: cmd.OfflineKubernetes, kubernetesVersion: cmd.KubernetesVersion, } - return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { + return withProjectCommandContext(ctx, ptArgs, func(cmdCtx *commandCtx) error { result := types.FixedImagesConfig{ - Images: ctx.images.SeenImages(cmd.Simple), + Images: cmdCtx.images.SeenImages(cmd.Simple), } - return outputYamlResult(cmd.Output, result, false) + return outputYamlResult(ctx, cmd.Output, result, false) }) } diff --git a/cmd/kluctl/commands/cmd_list_targets.go b/cmd/kluctl/commands/cmd_list_targets.go index f8a6b5626..91eacf231 100644 --- a/cmd/kluctl/commands/cmd_list_targets.go +++ b/cmd/kluctl/commands/cmd_list_targets.go @@ -16,12 +16,12 @@ func (cmd *listTargetsCmd) Help() string { return `Outputs a yaml list with all target, including dynamic targets` } -func (cmd *listTargetsCmd) Run() error { - return withKluctlProjectFromArgs(cmd.ProjectFlags, true, false, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { +func (cmd *listTargetsCmd) Run(ctx context.Context) error { + return withKluctlProjectFromArgs(ctx, cmd.ProjectFlags, true, false, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { var result []*types.Target for _, t := range p.DynamicTargets { result = append(result, t.Target) } - return outputYamlResult(cmd.Output, result, false) + return outputYamlResult(ctx, cmd.Output, result, false) }) } diff --git a/cmd/kluctl/commands/cmd_poke_images.go b/cmd/kluctl/commands/cmd_poke_images.go index b28c2f369..7492acc74 100644 --- a/cmd/kluctl/commands/cmd_poke_images.go +++ b/cmd/kluctl/commands/cmd_poke_images.go @@ -1,6 +1,7 @@ package commands import ( + "context" "fmt" "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/deployment/commands" @@ -26,7 +27,7 @@ deploying the target. Only images used in combination with 'images.get_image(... replaced` } -func (cmd *pokeImagesCmd) Run() error { +func (cmd *pokeImagesCmd) Run(ctx context.Context) error { ptArgs := projectTargetCommandArgs{ projectFlags: cmd.ProjectFlags, targetFlags: cmd.TargetFlags, @@ -37,20 +38,20 @@ func (cmd *pokeImagesCmd) Run() error { dryRunArgs: &cmd.DryRunFlags, renderOutputDirFlags: cmd.RenderOutputDirFlags, } - return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { + return withProjectCommandContext(ctx, ptArgs, func(cmdCtx *commandCtx) error { if !cmd.Yes && !cmd.DryRun { - if !status.AskForConfirmation(cliCtx, fmt.Sprintf("Do you really want to poke images to the context/cluster %s?", ctx.targetCtx.ClusterContext)) { + if !status.AskForConfirmation(ctx, fmt.Sprintf("Do you really want to poke images to the context/cluster %s?", cmdCtx.targetCtx.ClusterContext)) { return fmt.Errorf("aborted") } } - cmd2 := commands.NewPokeImagesCommand(ctx.targetCtx.DeploymentCollection) + cmd2 := commands.NewPokeImagesCommand(cmdCtx.targetCtx.DeploymentCollection) - result, err := cmd2.Run(ctx.ctx, ctx.targetCtx.SharedContext.K) + result, err := cmd2.Run(ctx, cmdCtx.targetCtx.SharedContext.K) if err != nil { return err } - err = outputCommandResult(cmd.OutputFormat, result) + err = outputCommandResult(ctx, cmd.OutputFormat, result) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_prune.go b/cmd/kluctl/commands/cmd_prune.go index a9197b22f..13516a1b4 100644 --- a/cmd/kluctl/commands/cmd_prune.go +++ b/cmd/kluctl/commands/cmd_prune.go @@ -1,6 +1,7 @@ package commands import ( + "context" "fmt" "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/deployment/commands" @@ -27,7 +28,7 @@ func (cmd *pruneCmd) Help() string { 3. Remove all objects from the list of 1. that are part of the list in 2.` } -func (cmd *pruneCmd) Run() error { +func (cmd *pruneCmd) Run(ctx context.Context) error { ptArgs := projectTargetCommandArgs{ projectFlags: cmd.ProjectFlags, targetFlags: cmd.TargetFlags, @@ -38,22 +39,22 @@ func (cmd *pruneCmd) Run() error { dryRunArgs: &cmd.DryRunFlags, renderOutputDirFlags: cmd.RenderOutputDirFlags, } - return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { - return cmd.runCmdPrune(ctx) + return withProjectCommandContext(ctx, ptArgs, func(cmdCtx *commandCtx) error { + return cmd.runCmdPrune(cmdCtx) }) } -func (cmd *pruneCmd) runCmdPrune(ctx *commandCtx) error { - cmd2 := commands.NewPruneCommand(ctx.targetCtx.DeploymentCollection) - objects, err := cmd2.Run(ctx.ctx, ctx.targetCtx.SharedContext.K) +func (cmd *pruneCmd) runCmdPrune(cmdCtx *commandCtx) error { + cmd2 := commands.NewPruneCommand(cmdCtx.targetCtx.DeploymentCollection) + objects, err := cmd2.Run(cmdCtx.ctx, cmdCtx.targetCtx.SharedContext.K) if err != nil { return err } - result, err := confirmedDeleteObjects(ctx.ctx, ctx.targetCtx.SharedContext.K, objects, cmd.DryRun, cmd.Yes) + result, err := confirmedDeleteObjects(cmdCtx.ctx, cmdCtx.targetCtx.SharedContext.K, objects, cmd.DryRun, cmd.Yes) if err != nil { return err } - err = outputCommandResult(cmd.OutputFormat, result) + err = outputCommandResult(cmdCtx.ctx, cmd.OutputFormat, result) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_render.go b/cmd/kluctl/commands/cmd_render.go index d7c5ff244..16c0bc2c1 100644 --- a/cmd/kluctl/commands/cmd_render.go +++ b/cmd/kluctl/commands/cmd_render.go @@ -1,6 +1,7 @@ package commands import ( + "context" "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/utils" @@ -26,7 +27,7 @@ func (cmd *renderCmd) Help() string { a temporary directory or a specified directory.` } -func (cmd *renderCmd) Run() error { +func (cmd *renderCmd) Run(ctx context.Context) error { isTmp := false if cmd.RenderOutputDir == "" { p, err := ioutil.TempDir(utils.GetTmpBaseDir(), "rendered-") @@ -47,10 +48,10 @@ func (cmd *renderCmd) Run() error { offlineKubernetes: cmd.OfflineKubernetes, kubernetesVersion: cmd.KubernetesVersion, } - return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { + return withProjectCommandContext(ctx, ptArgs, func(cmdCtx *commandCtx) error { if cmd.PrintAll { var all []any - for _, d := range ctx.targetCtx.DeploymentCollection.Deployments { + for _, d := range cmdCtx.targetCtx.DeploymentCollection.Deployments { for _, o := range d.Objects { all = append(all, o) } @@ -58,10 +59,10 @@ func (cmd *renderCmd) Run() error { if isTmp { defer os.RemoveAll(cmd.RenderOutputDir) } - status.Flush(ctx.ctx) + status.Flush(cmdCtx.ctx) return yaml.WriteYamlAllStream(os.Stdout, all) } else { - status.Info(ctx.ctx, "Rendered into %s", ctx.targetCtx.SharedContext.RenderDir) + status.Info(cmdCtx.ctx, "Rendered into %s", cmdCtx.targetCtx.SharedContext.RenderDir) } return nil }) diff --git a/cmd/kluctl/commands/cmd_seal.go b/cmd/kluctl/commands/cmd_seal.go index fc31ec63f..18fd57647 100644 --- a/cmd/kluctl/commands/cmd_seal.go +++ b/cmd/kluctl/commands/cmd_seal.go @@ -53,30 +53,30 @@ func (cmd *sealCmd) runCmdSealForTarget(ctx context.Context, p *kluctl_project.L ptArgs.targetFlags.Target = targetName // pass forSeal=True so that .sealme files are rendered as well - return withProjectTargetCommandContext(ctx, ptArgs, p, func(ctx *commandCtx) error { - err := ctx.targetCtx.DeploymentCollection.RenderDeployments() + return withProjectTargetCommandContext(ctx, ptArgs, p, func(cmdCtx *commandCtx) error { + err := cmdCtx.targetCtx.DeploymentCollection.RenderDeployments() if err != nil { return doFail(err) } - cert, err := cmd.loadCert(ctx) + cert, err := cmd.loadCert(cmdCtx) if err != nil { return doFail(err) } - sealer, err := seal.NewSealer(ctx.ctx, cert, cmd.ForceReseal) + sealer, err := seal.NewSealer(cmdCtx.ctx, cert, cmd.ForceReseal) if err != nil { return doFail(err) } outputPattern := targetName - if ctx.targetCtx.DeploymentProject.Config.SealedSecrets != nil && ctx.targetCtx.DeploymentProject.Config.SealedSecrets.OutputPattern != nil { + if cmdCtx.targetCtx.DeploymentProject.Config.SealedSecrets != nil && cmdCtx.targetCtx.DeploymentProject.Config.SealedSecrets.OutputPattern != nil { // the outputPattern is rendered already at this point, meaning that for example // '{{ cluster.name }}/{{ target.name }}' will already be rendered to 'my-cluster/my-target' - outputPattern = *ctx.targetCtx.DeploymentProject.Config.SealedSecrets.OutputPattern + outputPattern = *cmdCtx.targetCtx.DeploymentProject.Config.SealedSecrets.OutputPattern } - cmd2 := commands.NewSealCommand(ctx.targetCtx.DeploymentCollection, outputPattern, ctx.targetCtx.SharedContext.RenderDir, ctx.targetCtx.SharedContext.SealedSecretsDir) + cmd2 := commands.NewSealCommand(cmdCtx.targetCtx.DeploymentCollection, outputPattern, cmdCtx.targetCtx.SharedContext.RenderDir, cmdCtx.targetCtx.SharedContext.SealedSecretsDir) err = cmd2.Run(sealer) if err != nil { @@ -87,13 +87,13 @@ func (cmd *sealCmd) runCmdSealForTarget(ctx context.Context, p *kluctl_project.L }) } -func (cmd *sealCmd) loadCert(ctx *commandCtx) (*x509.Certificate, error) { - sealingConfig := ctx.targetCtx.Target.SealingConfig +func (cmd *sealCmd) loadCert(cmdCtx *commandCtx) (*x509.Certificate, error) { + sealingConfig := cmdCtx.targetCtx.Target.SealingConfig var certFile string if sealingConfig != nil && sealingConfig.CertFile != nil { - path, err := securejoin.SecureJoin(ctx.targetCtx.KluctlProject.ProjectDir, *sealingConfig.CertFile) + path, err := securejoin.SecureJoin(cmdCtx.targetCtx.KluctlProject.ProjectDir, *sealingConfig.CertFile) if err != nil { return nil, err } @@ -102,7 +102,7 @@ func (cmd *sealCmd) loadCert(ctx *commandCtx) (*x509.Certificate, error) { if cmd.CertFile != "" { if certFile != "" { - status.Info(ctx.ctx, "Overriding certFile from target with certFile argument") + status.Info(cmdCtx.ctx, "Overriding certFile from target with certFile argument") } certFile = cmd.CertFile } @@ -118,11 +118,11 @@ func (cmd *sealCmd) loadCert(ctx *commandCtx) (*x509.Certificate, error) { } return cert, nil } else { - if ctx.targetCtx.SharedContext.K == nil { + if cmdCtx.targetCtx.SharedContext.K == nil { return nil, fmt.Errorf("must specify certFile when sealing in offline mode") } - secretsConfig := ctx.targetCtx.KluctlProject.Config.SecretsConfig + secretsConfig := cmdCtx.targetCtx.KluctlProject.Config.SecretsConfig var sealedSecretsConfig *types.GlobalSealedSecretsConfig if secretsConfig != nil { sealedSecretsConfig = secretsConfig.SealedSecrets @@ -140,13 +140,13 @@ func (cmd *sealCmd) loadCert(ctx *commandCtx) (*x509.Certificate, error) { } if sealedSecretsConfig == nil || sealedSecretsConfig.Bootstrap == nil || *sealedSecretsConfig.Bootstrap { - err := seal.BootstrapSealedSecrets(ctx.ctx, ctx.targetCtx.SharedContext.K, sealedSecretsNamespace) + err := seal.BootstrapSealedSecrets(cmdCtx.ctx, cmdCtx.targetCtx.SharedContext.K, sealedSecretsNamespace) if err != nil { return nil, err } } - cert, err := seal.FetchCert(ctx.ctx, ctx.targetCtx.SharedContext.K, sealedSecretsNamespace, sealedSecretsControllerName) + cert, err := seal.FetchCert(cmdCtx.ctx, cmdCtx.targetCtx.SharedContext.K, sealedSecretsNamespace, sealedSecretsControllerName) if err != nil { return nil, err } @@ -154,8 +154,8 @@ func (cmd *sealCmd) loadCert(ctx *commandCtx) (*x509.Certificate, error) { } } -func (cmd *sealCmd) Run() error { - return withKluctlProjectFromArgs(cmd.ProjectFlags, true, false, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { +func (cmd *sealCmd) Run(ctx context.Context) error { + return withKluctlProjectFromArgs(ctx, cmd.ProjectFlags, true, false, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { hadError := false baseTargets := make(map[string]bool) diff --git a/cmd/kluctl/commands/cmd_validate.go b/cmd/kluctl/commands/cmd_validate.go index 4558cd76f..101af905f 100644 --- a/cmd/kluctl/commands/cmd_validate.go +++ b/cmd/kluctl/commands/cmd_validate.go @@ -1,6 +1,7 @@ package commands import ( + "context" "fmt" "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/deployment/commands" @@ -28,7 +29,7 @@ func (cmd *validateCmd) Help() string { TODO: This needs to be better documented!` } -func (cmd *validateCmd) Run() error { +func (cmd *validateCmd) Run(ctx context.Context) error { ptArgs := projectTargetCommandArgs{ projectFlags: cmd.ProjectFlags, targetFlags: cmd.TargetFlags, @@ -37,17 +38,17 @@ func (cmd *validateCmd) Run() error { helmCredentials: cmd.HelmCredentials, renderOutputDirFlags: cmd.RenderOutputDirFlags, } - return withProjectCommandContext(ptArgs, func(ctx *commandCtx) error { + return withProjectCommandContext(ctx, ptArgs, func(cmdCtx *commandCtx) error { startTime := time.Now() - cmd2 := commands.NewValidateCommand(ctx.ctx, ctx.targetCtx.DeploymentCollection) + cmd2 := commands.NewValidateCommand(cmdCtx.ctx, cmdCtx.targetCtx.DeploymentCollection) for true { - result, err := cmd2.Run(ctx.ctx, ctx.targetCtx.SharedContext.K) + result, err := cmd2.Run(cmdCtx.ctx, cmdCtx.targetCtx.SharedContext.K) if err != nil { return err } failed := len(result.Errors) != 0 || (cmd.WarningsAsErrors && len(result.Warnings) != 0) - err = outputValidateResult(cmd.Output, result) + err = outputValidateResult(ctx, cmd.Output, result) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_version.go b/cmd/kluctl/commands/cmd_version.go index 98da1b398..b7683a784 100644 --- a/cmd/kluctl/commands/cmd_version.go +++ b/cmd/kluctl/commands/cmd_version.go @@ -1,6 +1,7 @@ package commands import ( + "context" "github.com/kluctl/kluctl/v2/pkg/version" "os" ) @@ -8,7 +9,7 @@ import ( type versionCmd struct { } -func (cmd *versionCmd) Run() error { +func (cmd *versionCmd) Run(ctx context.Context) error { _, err := os.Stdout.WriteString(version.GetVersion() + "\n") return err } diff --git a/cmd/kluctl/commands/cobra_utils.go b/cmd/kluctl/commands/cobra_utils.go index 5982f04bf..5ebf48f10 100644 --- a/cmd/kluctl/commands/cobra_utils.go +++ b/cmd/kluctl/commands/cobra_utils.go @@ -1,6 +1,7 @@ package commands import ( + "context" "fmt" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/term" @@ -20,7 +21,7 @@ type helpProvider interface { } type runProvider interface { - Run() error + Run(ctx context.Context) error } type rootCommand struct { @@ -68,7 +69,7 @@ func (c *rootCommand) buildCobraCmd(parent *commandAndGroups, cmdStruct interfac runP, ok := cmdStruct.(runProvider) if ok { cg.cmd.RunE = func(cmd *cobra.Command, args []string) error { - return runP.Run() + return runP.Run(cmd.Context()) } } diff --git a/cmd/kluctl/commands/command_result.go b/cmd/kluctl/commands/command_result.go index b5702f1c7..cdf7f0753 100644 --- a/cmd/kluctl/commands/command_result.go +++ b/cmd/kluctl/commands/command_result.go @@ -2,6 +2,7 @@ package commands import ( "bytes" + "context" "fmt" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" @@ -196,24 +197,24 @@ func outputHelper(output []string, cb func(format string) (string, error)) error return nil } -func outputCommandResult(output []string, cr *types.CommandResult) error { - status.Flush(cliCtx) +func outputCommandResult(ctx context.Context, output []string, cr *types.CommandResult) error { + status.Flush(ctx) return outputHelper(output, func(format string) (string, error) { return formatCommandResult(cr, format) }) } -func outputValidateResult(output []string, vr *types.ValidateResult) error { - status.Flush(cliCtx) +func outputValidateResult(ctx context.Context, output []string, vr *types.ValidateResult) error { + status.Flush(ctx) return outputHelper(output, func(format string) (string, error) { return formatValidateResult(vr, format) }) } -func outputYamlResult(output []string, result interface{}, multiDoc bool) error { - status.Flush(cliCtx) +func outputYamlResult(ctx context.Context, output []string, result interface{}, multiDoc bool) error { + status.Flush(ctx) if len(output) == 0 { output = []string{"-"} diff --git a/cmd/kluctl/commands/completion.go b/cmd/kluctl/commands/completion.go index 7830d90e4..32ffab84f 100644 --- a/cmd/kluctl/commands/completion.go +++ b/cmd/kluctl/commands/completion.go @@ -20,13 +20,15 @@ func RegisterFlagCompletionFuncs(cmdStruct interface{}, ccmd *cobra.Command) err inclusionFlags := v.FieldByName("InclusionFlags") imageFlags := v.FieldByName("ImageFlags") + ctx := context.Background() + if projectFlags.IsValid() && targetFlags.IsValid() { - _ = ccmd.RegisterFlagCompletionFunc("target", buildTargetCompletionFunc(projectFlags.Addr().Interface().(*args.ProjectFlags))) + _ = ccmd.RegisterFlagCompletionFunc("target", buildTargetCompletionFunc(ctx, projectFlags.Addr().Interface().(*args.ProjectFlags))) } if projectFlags.IsValid() && inclusionFlags.IsValid() { - tagsFunc := buildInclusionCompletionFunc(cmdStruct, false) - dirsFunc := buildInclusionCompletionFunc(cmdStruct, true) + tagsFunc := buildInclusionCompletionFunc(ctx, cmdStruct, false) + dirsFunc := buildInclusionCompletionFunc(ctx, cmdStruct, true) _ = ccmd.RegisterFlagCompletionFunc("include-tag", tagsFunc) _ = ccmd.RegisterFlagCompletionFunc("exclude-tag", tagsFunc) _ = ccmd.RegisterFlagCompletionFunc("include-deployment-dir", dirsFunc) @@ -34,31 +36,31 @@ func RegisterFlagCompletionFuncs(cmdStruct interface{}, ccmd *cobra.Command) err } if imageFlags.IsValid() { - _ = ccmd.RegisterFlagCompletionFunc("fixed-image", buildImagesCompletionFunc(cmdStruct)) + _ = ccmd.RegisterFlagCompletionFunc("fixed-image", buildImagesCompletionFunc(ctx, cmdStruct)) } return nil } -func withProjectForCompletion(projectArgs *args.ProjectFlags, cb func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error) error { +func withProjectForCompletion(ctx context.Context, projectArgs *args.ProjectFlags, cb func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error) error { // let's not update git caches too often projectArgs.GitCacheUpdateInterval = time.Second * 60 - return withKluctlProjectFromArgs(*projectArgs, false, true, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { + return withKluctlProjectFromArgs(ctx, *projectArgs, false, true, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { return cb(ctx, p) }) } -func buildTargetCompletionFunc(projectArgs *args.ProjectFlags) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { +func buildTargetCompletionFunc(ctx context.Context, projectArgs *args.ProjectFlags) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { var ret []string - err := withProjectForCompletion(projectArgs, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { + err := withProjectForCompletion(ctx, projectArgs, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { for _, t := range p.DynamicTargets { ret = append(ret, t.Target.Name) } return nil }) if err != nil { - status.Error(cliCtx, err.Error()) + status.Error(ctx, err.Error()) return nil, cobra.ShellCompDirectiveError } return ret, cobra.ShellCompDirectiveDefault @@ -89,7 +91,7 @@ func buildAutocompleteProjectTargetCommandArgs(cmdStruct interface{}) projectTar return ptArgs } -func buildInclusionCompletionFunc(cmdStruct interface{}, forDirs bool) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { +func buildInclusionCompletionFunc(ctx context.Context, cmdStruct interface{}, forDirs bool) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return func(cmd *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) { ptArgs := buildAutocompleteProjectTargetCommandArgs(cmdStruct) @@ -97,7 +99,7 @@ func buildInclusionCompletionFunc(cmdStruct interface{}, forDirs bool) func(cmd var deploymentItemDirs utils.OrderedMap var mutex sync.Mutex - err := withProjectForCompletion(&ptArgs.projectFlags, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { + err := withProjectForCompletion(ctx, &ptArgs.projectFlags, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { var targets []string if ptArgs.targetFlags.Target == "" { for _, t := range p.DynamicTargets { @@ -113,10 +115,10 @@ func buildInclusionCompletionFunc(cmdStruct interface{}, forDirs bool) func(cmd ptArgs.targetFlags.Target = t wg.Add(1) go func() { - _ = withProjectTargetCommandContext(ctx, ptArgs, p, func(ctx *commandCtx) error { + _ = withProjectTargetCommandContext(ctx, ptArgs, p, func(cmdCtx *commandCtx) error { mutex.Lock() defer mutex.Unlock() - for _, di := range ctx.targetCtx.DeploymentCollection.Deployments { + for _, di := range cmdCtx.targetCtx.DeploymentCollection.Deployments { tags.Merge(di.Tags) deploymentItemDirs.Set(di.RelToSourceItemDir, true) } @@ -129,7 +131,7 @@ func buildInclusionCompletionFunc(cmdStruct interface{}, forDirs bool) func(cmd return nil }) if err != nil { - status.Error(cliCtx, err.Error()) + status.Error(ctx, err.Error()) return nil, cobra.ShellCompDirectiveError } if forDirs { @@ -140,7 +142,7 @@ func buildInclusionCompletionFunc(cmdStruct interface{}, forDirs bool) func(cmd } } -func buildImagesCompletionFunc(cmdStruct interface{}) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { +func buildImagesCompletionFunc(ctx context.Context, cmdStruct interface{}) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return func(cmd *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) { ptArgs := buildAutocompleteProjectTargetCommandArgs(cmdStruct) @@ -151,7 +153,7 @@ func buildImagesCompletionFunc(cmdStruct interface{}) func(cmd *cobra.Command, a var images utils.OrderedMap var mutex sync.Mutex - err := withProjectForCompletion(&ptArgs.projectFlags, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { + err := withProjectForCompletion(ctx, &ptArgs.projectFlags, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { var targets []string if ptArgs.targetFlags.Target == "" { for _, t := range p.DynamicTargets { @@ -167,15 +169,15 @@ func buildImagesCompletionFunc(cmdStruct interface{}) func(cmd *cobra.Command, a ptArgs.targetFlags.Target = t wg.Add(1) go func() { - _ = withProjectTargetCommandContext(ctx, ptArgs, p, func(ctx *commandCtx) error { - err := ctx.targetCtx.DeploymentCollection.Prepare() + _ = withProjectTargetCommandContext(ctx, ptArgs, p, func(cmdCtx *commandCtx) error { + err := cmdCtx.targetCtx.DeploymentCollection.Prepare() if err != nil { - status.Error(cliCtx, err.Error()) + status.Error(ctx, err.Error()) } mutex.Lock() defer mutex.Unlock() - for _, si := range ctx.images.SeenImages(false) { + for _, si := range cmdCtx.images.SeenImages(false) { str := si.Image if si.Namespace != nil { str += ":" + *si.Namespace @@ -197,7 +199,7 @@ func buildImagesCompletionFunc(cmdStruct interface{}) func(cmd *cobra.Command, a return nil }) if err != nil { - status.Error(cliCtx, err.Error()) + status.Error(ctx, err.Error()) return nil, cobra.ShellCompDirectiveError } return images.ListKeys(), cobra.ShellCompDirectiveNoSpace diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index d864584f0..203e06405 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -197,7 +197,7 @@ func (c *cli) preRun() error { return nil } -func (c *cli) Run() error { +func (c *cli) Run(ctx context.Context) error { return nil } @@ -237,7 +237,11 @@ composed of multiple smaller parts (Helm/Kustomize/...) in a manageable and unif if err != nil { return err } - return root.preRun() + err = root.preRun() + for c := cmd; c != nil; c = c.Parent() { + c.SetContext(cliCtx) + } + return err } initViper() diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 5715f93e4..f3184db23 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -24,7 +24,7 @@ import ( "strings" ) -func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, strictTemplates bool, forCompletion bool, cb func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error) error { +func withKluctlProjectFromArgs(ctx context.Context, projectFlags args.ProjectFlags, strictTemplates bool, forCompletion bool, cb func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error) error { tmpDir, err := os.MkdirTemp(utils.GetTmpBaseDir(), "project-") if err != nil { return fmt.Errorf("creating temporary project directory failed: %w", err) @@ -44,10 +44,10 @@ func withKluctlProjectFromArgs(projectFlags args.ProjectFlags, strictTemplates b repoRoot, err := git.DetectGitRepositoryRoot(cwd) if err != nil { - status.Warning(cliCtx, "Failed to detect git project root. This might cause follow-up errors") + status.Warning(ctx, "Failed to detect git project root. This might cause follow-up errors") } - ctx, cancel := context.WithTimeout(cliCtx, projectFlags.Timeout) + ctx, cancel := context.WithTimeout(ctx, projectFlags.Timeout) defer cancel() sshPool := &ssh_pool.SshPool{} @@ -110,13 +110,13 @@ type commandCtx struct { images *deployment.Images } -func withProjectCommandContext(args projectTargetCommandArgs, cb func(ctx *commandCtx) error) error { - return withKluctlProjectFromArgs(args.projectFlags, true, false, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { +func withProjectCommandContext(ctx context.Context, args projectTargetCommandArgs, cb func(cmdCtx *commandCtx) error) error { + return withKluctlProjectFromArgs(ctx, args.projectFlags, true, false, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { return withProjectTargetCommandContext(ctx, args, p, cb) }) } -func withProjectTargetCommandContext(ctx context.Context, args projectTargetCommandArgs, p *kluctl_project.LoadedKluctlProject, cb func(ctx *commandCtx) error) error { +func withProjectTargetCommandContext(ctx context.Context, args projectTargetCommandArgs, p *kluctl_project.LoadedKluctlProject, cb func(cmdCtx *commandCtx) error) error { rh := registries.NewRegistryHelper(ctx) err := rh.ParseAuthEntriesFromEnv() if err != nil { From 4834099b8eecc52384568c0fb66c67543252d064 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 9 Dec 2022 10:50:38 +0100 Subject: [PATCH 1281/2916] chore: Reformat replace-commands-help/main.go --- internal/replace-commands-help/main.go | 282 ++++++++++++------------- 1 file changed, 141 insertions(+), 141 deletions(-) diff --git a/internal/replace-commands-help/main.go b/internal/replace-commands-help/main.go index 6d4e781fb..a29bc8764 100644 --- a/internal/replace-commands-help/main.go +++ b/internal/replace-commands-help/main.go @@ -1,92 +1,92 @@ package main import ( - "flag" - "fmt" - "github.com/kluctl/kluctl/v2/cmd/kluctl/commands" - "io/fs" - "io/ioutil" - "log" - "os" - "os/exec" - "path/filepath" - "reflect" - "regexp" - "strings" - "syscall" + "flag" + "fmt" + "github.com/kluctl/kluctl/v2/cmd/kluctl/commands" + "io/fs" + "io/ioutil" + "log" + "os" + "os/exec" + "path/filepath" + "reflect" + "regexp" + "strings" + "syscall" ) var docsDir = flag.String("docs-dir", "", "Path to documentation") type section struct { - start int - end int - command string - section string - code bool + start int + end int + command string + section string + code bool } func main() { - _ = syscall.Setenv("COLUMNS", "120") - - if os.Getenv("CALL_KLUCTL") == "true" { - kluctlMain() - return - } - - flag.Parse() - - filepath.WalkDir(*docsDir, func(path string, d fs.DirEntry, err error) error { - if !strings.HasSuffix(path, ".md") { - return nil - } - processFile(path) - return nil - }) + _ = syscall.Setenv("COLUMNS", "120") + + if os.Getenv("CALL_KLUCTL") == "true" { + kluctlMain() + return + } + + flag.Parse() + + filepath.WalkDir(*docsDir, func(path string, d fs.DirEntry, err error) error { + if !strings.HasSuffix(path, ".md") { + return nil + } + processFile(path) + return nil + }) } func kluctlMain() { - commands.Execute() + commands.Execute() } func processFile(path string) { - text, err := ioutil.ReadFile(path) - if err != nil { - log.Fatal(err) - } - lines := strings.Split(string(text), "\n") - - var newLines []string - pos := 0 - for true { - s := findNextSection(lines, pos) - if s == nil { - newLines = append(newLines, lines[pos:]...) - break - } - - newLines = append(newLines, lines[pos:s.start+1]...) - - s2 := getHelpSection(s.command, s.section) - - if s.code { - newLines = append(newLines, "```") - } - newLines = append(newLines, s2...) - if s.code { - newLines = append(newLines, "```") - } - - newLines = append(newLines, lines[s.end]) - pos = s.end + 1 - } - - if !reflect.DeepEqual(lines, newLines) { - err = ioutil.WriteFile(path, []byte(strings.Join(newLines, "\n")), 0o600) - if err != nil { - log.Fatal(err) - } - } + text, err := ioutil.ReadFile(path) + if err != nil { + log.Fatal(err) + } + lines := strings.Split(string(text), "\n") + + var newLines []string + pos := 0 + for true { + s := findNextSection(lines, pos) + if s == nil { + newLines = append(newLines, lines[pos:]...) + break + } + + newLines = append(newLines, lines[pos:s.start+1]...) + + s2 := getHelpSection(s.command, s.section) + + if s.code { + newLines = append(newLines, "```") + } + newLines = append(newLines, s2...) + if s.code { + newLines = append(newLines, "```") + } + + newLines = append(newLines, lines[s.end]) + pos = s.end + 1 + } + + if !reflect.DeepEqual(lines, newLines) { + err = ioutil.WriteFile(path, []byte(strings.Join(newLines, "\n")), 0o600) + if err != nil { + log.Fatal(err) + } + } } var beginPattern = regexp.MustCompile(``) @@ -94,79 +94,79 @@ var endPattern = regexp.MustCompile(``) func findNextSection(lines []string, start int) *section { - for i := start; i < len(lines); i++ { - m := beginPattern.FindSubmatch([]byte(lines[i])) - if m == nil { - continue - } - - var s section - s.start = i - s.command = string(m[1]) - s.section = string(m[2]) - s.code = string(m[3]) == "true" - - for j := i + 1; j < len(lines); j++ { - m = endPattern.FindSubmatch([]byte(lines[j])) - if m == nil { - continue - } - s.end = j - return &s - } - } - return nil + for i := start; i < len(lines); i++ { + m := beginPattern.FindSubmatch([]byte(lines[i])) + if m == nil { + continue + } + + var s section + s.start = i + s.command = string(m[1]) + s.section = string(m[2]) + s.code = string(m[3]) == "true" + + for j := i + 1; j < len(lines); j++ { + m = endPattern.FindSubmatch([]byte(lines[j])) + if m == nil { + continue + } + s.end = j + return &s + } + } + return nil } func countIndent(str string) int { - for i := 0; i < len(str); i++ { - if str[i] != ' ' { - return i - } - } - return 0 + for i := 0; i < len(str); i++ { + if str[i] != ' ' { + return i + } + } + return 0 } func getHelpSection(command string, section string) []string { - log.Printf("Getting section '%s' from command '%s'", section, command) - - exe, err := os.Executable() - if err != nil { - log.Fatal(err) - } - - helpCmd := exec.Command(exe, command, "--help") - helpCmd.Env = os.Environ() - helpCmd.Env = append(helpCmd.Env, "CALL_KLUCTL=true") - - out, err := helpCmd.CombinedOutput() - if err != nil { - log.Fatal(err) - } - - lines := strings.Split(string(out), "\n") - - sectionStart := -1 - for i := 0; i < len(lines); i++ { - indent := countIndent(lines[i]) - if strings.HasPrefix(lines[i][indent:], fmt.Sprintf("%s:", section)) { - sectionStart = i - break - } - } - if sectionStart == -1 { - log.Fatalf("Section %s not found in command %s", section, command) - } - - var ret []string - ret = append(ret, lines[sectionStart]) - for i := sectionStart + 1; i < len(lines); i++ { - indent := countIndent(lines[i]) - if len(lines[i]) != 0 && indent == 0 && lines[i][len(lines[i])-1] == ':' { - // new section has started - break - } - ret = append(ret, lines[i]) - } - return ret + log.Printf("Getting section '%s' from command '%s'", section, command) + + exe, err := os.Executable() + if err != nil { + log.Fatal(err) + } + + helpCmd := exec.Command(exe, command, "--help") + helpCmd.Env = os.Environ() + helpCmd.Env = append(helpCmd.Env, "CALL_KLUCTL=true") + + out, err := helpCmd.CombinedOutput() + if err != nil { + log.Fatal(err) + } + + lines := strings.Split(string(out), "\n") + + sectionStart := -1 + for i := 0; i < len(lines); i++ { + indent := countIndent(lines[i]) + if strings.HasPrefix(lines[i][indent:], fmt.Sprintf("%s:", section)) { + sectionStart = i + break + } + } + if sectionStart == -1 { + log.Fatalf("Section %s not found in command %s", section, command) + } + + var ret []string + ret = append(ret, lines[sectionStart]) + for i := sectionStart + 1; i < len(lines); i++ { + indent := countIndent(lines[i]) + if len(lines[i]) != 0 && indent == 0 && lines[i][len(lines[i])-1] == ':' { + // new section has started + break + } + ret = append(ret, lines[i]) + } + return ret } From 77d5c6ee5d8d13ed008de5e73c2345e97fe14ec8 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 9 Dec 2022 10:51:55 +0100 Subject: [PATCH 1282/2916] refactor: Split Execute() into into Main() and Execute() --- cmd/kluctl/commands/root.go | 152 +++++++++++++++---------- e2e/call_kluctl_hack.go | 2 +- internal/replace-commands-help/main.go | 2 +- main.go | 2 +- 4 files changed, 94 insertions(+), 64 deletions(-) diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index 203e06405..0af94bdfa 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -42,12 +42,16 @@ import ( const latestReleaseUrl = "https://api.github.com/repos/kluctl/kluctl/releases/latest" -type cli struct { +type GlobalFlags struct { Debug bool `group:"global" help:"Enable debug logging"` NoUpdateCheck bool `group:"global" help:"Disable update check on startup"` NoColor bool `group:"global" help:"Disable colored output"` CpuProfile string `group:"global" help:"Enable CPU profiling and write the result to the given path"` +} + +type cli struct { + GlobalFlags CheckImageUpdates checkImageUpdatesCmd `cmd:"" help:"Render deployment and check if any images have new tags available"` Delete deleteCmd `cmd:"" help:"Delete a target (or parts of it) from the corresponding cluster"` @@ -76,30 +80,35 @@ var flagGroups = []groupInfo{ {group: "flux", title: "Flux arguments:", description: "EXPERIMENTAL: Subcommands for interaction with flux-kluctl-controller"}, } -var cliCtx = context.Background() -var didSetupStatusHandler bool - -func setupStatusHandler(debug bool, noColor bool) { - didSetupStatusHandler = true - - origStderr := os.Stderr +var origStderr = os.Stderr +func initStatusHandler(ctx context.Context, debug bool, noColor bool) context.Context { // we must determine isTerminal before we override os.Stderr - isTerminal := isatty.IsTerminal(os.Stderr.Fd()) + isTerminal := isatty.IsTerminal(origStderr.Fd()) var sh status.StatusHandler - if !debug && isatty.IsTerminal(os.Stderr.Fd()) { - sh = status.NewMultiLineStatusHandler(cliCtx, os.Stderr, isTerminal, !noColor, false) + if !debug && isatty.IsTerminal(origStderr.Fd()) { + sh = status.NewMultiLineStatusHandler(ctx, origStderr, isTerminal, !noColor, false) } else { sh = status.NewSimpleStatusHandler(func(message string) { _, _ = fmt.Fprintf(origStderr, "%s\n", message) }, isTerminal, false) } sh.SetTrace(debug) - cliCtx = status.NewContext(cliCtx, sh) + ctx = status.NewContext(ctx, sh) + return ctx +} + +func redirectLogsAndStderr(ctxGetter func() context.Context) { + f := func(line string) { + status.Info(ctxGetter(), line) + } + + lr1 := status.NewLineRedirector(f) + lr2 := status.NewLineRedirector(f) klog.LogToStderr(false) - klog.SetOutput(status.NewLineRedirector(sh.Info)) - log.SetOutput(status.NewLineRedirector(sh.Info)) + klog.SetOutput(lr1) + log.SetOutput(lr2) pr, pw, err := os.Pipe() if err != nil { @@ -107,7 +116,7 @@ func setupStatusHandler(debug bool, noColor bool) { } go func() { - x := status.NewLineRedirector(sh.Info) + x := status.NewLineRedirector(f) _, _ = io.Copy(x, pr) }() @@ -135,10 +144,7 @@ type VersionCheckState struct { LastVersionCheck time.Time `yaml:"lastVersionCheck"` } -func (c *cli) checkNewVersion() { - if c.NoUpdateCheck { - return - } +func checkNewVersion(ctx context.Context) { if version.GetVersion() == "0.0.0" { return } @@ -155,7 +161,7 @@ func (c *cli) checkNewVersion() { versionCheckState.LastVersionCheck = time.Now() _ = yaml.WriteYamlFile(versionCheckPath, &versionCheckState) - s := status.Start(cliCtx, "Checking for new kluctl version") + s := status.Start(ctx, "Checking for new kluctl version") defer s.Failed() r, err := http.Get(latestReleaseUrl) @@ -187,36 +193,70 @@ func (c *cli) checkNewVersion() { s.Success() } -func (c *cli) preRun() error { - err := setupProfiling(c.CpuProfile) - if err != nil { - return err - } - setupStatusHandler(c.Debug, c.NoColor) - c.checkNewVersion() - return nil -} - func (c *cli) Run(ctx context.Context) error { return nil } -func initViper() { +func initViper(ctx context.Context) { viper.SetConfigName("config") viper.SetConfigType("yaml") viper.AddConfigPath("/etc/kluctl/") viper.AddConfigPath("$HOME/.kluctl") if err := viper.ReadInConfig(); err != nil { if _, ok := err.(viper.ConfigFileNotFoundError); !ok { - status.Error(cliCtx, err.Error()) + status.Error(ctx, err.Error()) os.Exit(1) } } } -func Execute() { +func Main() { colorable.EnableColorsStdout(nil) + ctx := context.Background() + + ctx = initStatusHandler(ctx, false, true) + redirectLogsAndStderr(func() context.Context { + // ctx might be replaced later in preRun() of Execute() + return ctx + }) + + initViper(ctx) + err := Execute(ctx, os.Args[1:], func(ctxIn context.Context, cmd *cobra.Command, flags GlobalFlags) (context.Context, error) { + err := copyViperValuesToCobraCmd(cmd) + if err != nil { + return ctx, err + } + err = setupProfiling(flags.CpuProfile) + if err != nil { + return ctx, err + } + oldSh := status.FromContext(ctxIn) + if oldSh != nil { + oldSh.Stop() + } + ctx = initStatusHandler(ctxIn, flags.Debug, flags.NoColor) + if !flags.NoUpdateCheck { + checkNewVersion(ctx) + } + return ctx, nil + }) + + if cpuProfileFile != nil { + pprof.StopCPUProfile() + _ = cpuProfileFile.Close() + cpuProfileFile = nil + } + + sh := status.FromContext(ctx) + sh.Stop() + + if err != nil { + os.Exit(1) + } +} + +func Execute(ctx context.Context, args []string, preRun func(ctx context.Context, rootCmd *cobra.Command, flags GlobalFlags) (context.Context, error)) error { root := cli{} rootCmd, err := buildRootCobraCmd(&root, "kluctl", "Deploy and manage complex deployments on Kubernetes", @@ -225,44 +265,34 @@ composed of multiple smaller parts (Helm/Kustomize/...) in a manageable and unif flagGroups) if err != nil { - panic(err) + return err } + rootCmd.SetContext(ctx) + rootCmd.SetArgs(args) rootCmd.Version = version.GetVersion() rootCmd.SilenceUsage = true rootCmd.SilenceErrors = true rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error { - err := copyViperValuesToCobraCmd(cmd) - if err != nil { - return err - } - err = root.preRun() - for c := cmd; c != nil; c = c.Parent() { - c.SetContext(cliCtx) + if preRun != nil { + ctx, err = preRun(ctx, cmd, root.GlobalFlags) + if ctx != nil { + for c := cmd; c != nil; c = c.Parent() { + c.SetContext(ctx) + } + } + if err != nil { + return err + } } - return err - } - - initViper() - - err = rootCmd.ExecuteContext(cliCtx) - if !didSetupStatusHandler { - setupStatusHandler(false, true) + return nil } - if cpuProfileFile != nil { - pprof.StopCPUProfile() - _ = cpuProfileFile.Close() - cpuProfileFile = nil - } - - sh := status.FromContext(cliCtx) - + err = rootCmd.Execute() if err != nil { - status.Error(cliCtx, "%s", err.Error()) - sh.Stop() - os.Exit(1) + status.Error(ctx, err.Error()) + return err } - sh.Stop() + return nil } diff --git a/e2e/call_kluctl_hack.go b/e2e/call_kluctl_hack.go index 6da895464..fa5268d30 100644 --- a/e2e/call_kluctl_hack.go +++ b/e2e/call_kluctl_hack.go @@ -13,7 +13,7 @@ func init() { // We use the Golang's initializing mechanism to run kluctl even though the test executable was invoked // This is clearly a hack, but it avoids the requirement to have a kluctl executable pre-built if isCallKluctlHack() { - commands.Execute() + commands.Main() os.Exit(0) } } diff --git a/internal/replace-commands-help/main.go b/internal/replace-commands-help/main.go index a29bc8764..2f47f1c08 100644 --- a/internal/replace-commands-help/main.go +++ b/internal/replace-commands-help/main.go @@ -46,7 +46,7 @@ func main() { } func kluctlMain() { - commands.Execute() + commands.Main() } func processFile(path string) { diff --git a/main.go b/main.go index f6ca27150..d7b055670 100644 --- a/main.go +++ b/main.go @@ -24,5 +24,5 @@ var version = "0.0.0" func main() { version2.SetVersion(version) - commands.Execute() + commands.Main() } From 779a97a1df9877cf7e8fe0090d9d40d1150bb674 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 9 Dec 2022 11:26:54 +0100 Subject: [PATCH 1283/2916] refactor: Allow to override tmp base dir via context --- cmd/kluctl/commands/cmd_flux_reconcile.go | 2 +- cmd/kluctl/commands/cmd_flux_resume.go | 2 +- cmd/kluctl/commands/cmd_flux_suspend.go | 2 +- cmd/kluctl/commands/cmd_render.go | 2 +- cmd/kluctl/commands/root.go | 2 +- cmd/kluctl/commands/utils.go | 2 +- pkg/deployment/helm_chart.go | 10 +-- pkg/k8s/client_factory.go | 11 ++- pkg/kluctl_project/target_context.go | 2 +- pkg/registries/registries.go | 2 +- pkg/repocache/cache.go | 4 +- pkg/sops/utils.go | 5 +- pkg/utils/tmpdir.go | 98 +++++++++++++++++++++++ pkg/utils/utils.go | 74 ----------------- 14 files changed, 123 insertions(+), 95 deletions(-) create mode 100644 pkg/utils/tmpdir.go diff --git a/cmd/kluctl/commands/cmd_flux_reconcile.go b/cmd/kluctl/commands/cmd_flux_reconcile.go index 8e4668024..91e345112 100644 --- a/cmd/kluctl/commands/cmd_flux_reconcile.go +++ b/cmd/kluctl/commands/cmd_flux_reconcile.go @@ -27,7 +27,7 @@ func (cmd *fluxReconcileCmd) Run(ctx context.Context) error { noWait := cmd.KluctlDeploymentFlags.NoWait timestamp := time.Now().Format(time.RFC3339) - cf, err := k8s.NewClientFactoryFromDefaultConfig(nil) + cf, err := k8s.NewClientFactoryFromDefaultConfig(ctx, nil) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_flux_resume.go b/cmd/kluctl/commands/cmd_flux_resume.go index 3b90f1219..d2fbc7d69 100644 --- a/cmd/kluctl/commands/cmd_flux_resume.go +++ b/cmd/kluctl/commands/cmd_flux_resume.go @@ -18,7 +18,7 @@ func (cmd *fluxResumeCmd) Run(ctx context.Context) error { ns := cmd.KluctlDeploymentFlags.Namespace kd := cmd.KluctlDeploymentFlags.KluctlDeployment - cf, err := k8s.NewClientFactoryFromDefaultConfig(nil) + cf, err := k8s.NewClientFactoryFromDefaultConfig(ctx, nil) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_flux_suspend.go b/cmd/kluctl/commands/cmd_flux_suspend.go index d06212dbe..dc90d9c2b 100644 --- a/cmd/kluctl/commands/cmd_flux_suspend.go +++ b/cmd/kluctl/commands/cmd_flux_suspend.go @@ -17,7 +17,7 @@ func (cmd *fluxSuspendCmd) Run(ctx context.Context) error { ns := cmd.KluctlDeploymentFlags.Namespace kd := cmd.KluctlDeploymentFlags.KluctlDeployment - cf, err := k8s.NewClientFactoryFromDefaultConfig(nil) + cf, err := k8s.NewClientFactoryFromDefaultConfig(ctx, nil) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_render.go b/cmd/kluctl/commands/cmd_render.go index 16c0bc2c1..27851b332 100644 --- a/cmd/kluctl/commands/cmd_render.go +++ b/cmd/kluctl/commands/cmd_render.go @@ -30,7 +30,7 @@ a temporary directory or a specified directory.` func (cmd *renderCmd) Run(ctx context.Context) error { isTmp := false if cmd.RenderOutputDir == "" { - p, err := ioutil.TempDir(utils.GetTmpBaseDir(), "rendered-") + p, err := ioutil.TempDir(utils.GetTmpBaseDir(ctx), "rendered-") if err != nil { return err } diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index 0af94bdfa..2c6d696f1 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -149,7 +149,7 @@ func checkNewVersion(ctx context.Context) { return } - versionCheckPath := filepath.Join(utils.GetTmpBaseDir(), "version_check.yaml") + versionCheckPath := filepath.Join(utils.GetTmpBaseDir(ctx), "version_check.yaml") var versionCheckState VersionCheckState err := yaml.ReadYamlFile(versionCheckPath, &versionCheckState) if err == nil { diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index f3184db23..bfe63cb17 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -25,7 +25,7 @@ import ( ) func withKluctlProjectFromArgs(ctx context.Context, projectFlags args.ProjectFlags, strictTemplates bool, forCompletion bool, cb func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error) error { - tmpDir, err := os.MkdirTemp(utils.GetTmpBaseDir(), "project-") + tmpDir, err := os.MkdirTemp(utils.GetTmpBaseDir(ctx), "project-") if err != nil { return fmt.Errorf("creating temporary project directory failed: %w", err) } diff --git a/pkg/deployment/helm_chart.go b/pkg/deployment/helm_chart.go index d21426da2..cf1cb825c 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/deployment/helm_chart.go @@ -164,7 +164,7 @@ func (c *HelmChart) pullTmpChart(ctx context.Context) (string, error) { _, _ = fmt.Fprintf(hash, "%s\n", c.chartName) _, _ = fmt.Fprintf(hash, "%s\n", *c.Config.ChartVersion) h := hex.EncodeToString(hash.Sum(nil)) - tmpDir := filepath.Join(utils.GetTmpBaseDir(), "helm-charts") + tmpDir := filepath.Join(utils.GetTmpBaseDir(ctx), "helm-charts") _ = os.MkdirAll(tmpDir, 0o700) tmpDir = filepath.Join(tmpDir, fmt.Sprintf("%s-%s", c.chartName, h)) @@ -267,7 +267,7 @@ func (c *HelmChart) CheckUpdate(ctx context.Context) (string, bool, error) { if c.Config.Repo != nil && registry.IsOCI(*c.Config.Repo) { return c.checkUpdateOciRepo(ctx) } - return c.checkUpdateHelmRepo() + return c.checkUpdateHelmRepo(ctx) } func (c *HelmChart) checkUpdateOciRepo(ctx context.Context) (string, bool, error) { @@ -288,7 +288,7 @@ func (c *HelmChart) checkUpdateOciRepo(ctx context.Context) (string, bool, error return latestVersion, updated, nil } -func (c *HelmChart) checkUpdateHelmRepo() (string, bool, error) { +func (c *HelmChart) checkUpdateHelmRepo(ctx context.Context) (string, bool, error) { settings := cli.New() var e *repo.Entry @@ -311,7 +311,7 @@ func (c *HelmChart) checkUpdateHelmRepo() (string, bool, error) { return "", false, err } - r.CachePath, err = os.MkdirTemp(utils.GetTmpBaseDir(), "helm-check-update-") + r.CachePath, err = os.MkdirTemp(utils.GetTmpBaseDir(ctx), "helm-check-update-") if err != nil { return "", false, err } @@ -442,7 +442,7 @@ func (c *HelmChart) doRender(ctx context.Context, k *k8s.K8sCluster, k8sVersion valueOpts := values.Options{} if utils.Exists(valuesPath) { - tmpValues, err := sops.MaybeDecryptFileToTmp(sopsDecrypter, valuesPath) + tmpValues, err := sops.MaybeDecryptFileToTmp(ctx, sopsDecrypter, valuesPath) if err != nil { return err } diff --git a/pkg/k8s/client_factory.go b/pkg/k8s/client_factory.go index 1c9e451df..f5e1f4b7a 100644 --- a/pkg/k8s/client_factory.go +++ b/pkg/k8s/client_factory.go @@ -1,6 +1,7 @@ package k8s import ( + "context" "github.com/kluctl/kluctl/v2/pkg/utils" "k8s.io/client-go/discovery" "k8s.io/client-go/discovery/cached/disk" @@ -26,6 +27,7 @@ type ClientFactory interface { } type realClientFactory struct { + ctx context.Context config *rest.Config httpClient *http.Client } @@ -43,7 +45,7 @@ func (r *realClientFactory) DiscoveryClient() (discovery.DiscoveryInterface, err if err != nil { return nil, err } - discoveryCacheDir := filepath.Join(utils.GetTmpBaseDir(), "kube-cache/discovery", apiHost.Hostname()) + discoveryCacheDir := filepath.Join(utils.GetTmpBaseDir(r.ctx), "kube-cache/discovery", apiHost.Hostname()) discovery2, err := disk.NewCachedDiscoveryClientForConfig(dynamic.ConfigFor(r.config), discoveryCacheDir, "", time.Hour*24) if err != nil { return nil, err @@ -67,7 +69,7 @@ func (r *realClientFactory) CloseIdleConnections() { r.httpClient.CloseIdleConnections() } -func NewClientFactory(configIn *rest.Config) (ClientFactory, error) { +func NewClientFactory(ctx context.Context, configIn *rest.Config) (ClientFactory, error) { restConfig := rest.CopyConfig(configIn) restConfig.QPS = 10 restConfig.Burst = 20 @@ -78,12 +80,13 @@ func NewClientFactory(configIn *rest.Config) (ClientFactory, error) { } return &realClientFactory{ + ctx: ctx, config: restConfig, httpClient: httpClient, }, nil } -func NewClientFactoryFromDefaultConfig(context *string) (ClientFactory, error) { +func NewClientFactoryFromDefaultConfig(ctx context.Context, context *string) (ClientFactory, error) { configOverrides := &clientcmd.ConfigOverrides{} if context != nil { configOverrides.CurrentContext = *context @@ -96,5 +99,5 @@ func NewClientFactoryFromDefaultConfig(context *string) (ClientFactory, error) { return nil, err } - return NewClientFactory(config) + return NewClientFactory(ctx, config) } diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index 980585382..bacf2d7d5 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -80,7 +80,7 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, params Targe var k *k8s.K8sCluster if clientConfig != nil { s := status.Start(ctx, fmt.Sprintf("Initializing k8s client")) - clientFactory, err := k8s.NewClientFactory(clientConfig) + clientFactory, err := k8s.NewClientFactory(ctx, clientConfig) if err != nil { return nil, err } diff --git a/pkg/registries/registries.go b/pkg/registries/registries.go index 81b1156e8..ffda2e3f8 100644 --- a/pkg/registries/registries.go +++ b/pkg/registries/registries.go @@ -284,7 +284,7 @@ func (rh *RegistryHelper) realmFromRequest(req *http.Request) string { } func (rh *RegistryHelper) getCachePath(key string) string { - return filepath.Join(utils.GetTmpBaseDir(), "registries-cache", key[0:2], key[2:4], key) + return filepath.Join(utils.GetTmpBaseDir(rh.ctx), "registries-cache", key[0:2], key[2:4], key) } func (rh *RegistryHelper) checkInvalidToken(resBody []byte) bool { diff --git a/pkg/repocache/cache.go b/pkg/repocache/cache.go index 88d20f53a..9d9e70b32 100644 --- a/pkg/repocache/cache.go +++ b/pkg/repocache/cache.go @@ -87,7 +87,7 @@ func (rp *GitRepoCache) GetEntry(url git_url.GitUrl) (*CacheEntry, error) { e, ok := rp.repos[url.NormalizedRepoKey()] if !ok { - mr, err := git.NewMirroredGitRepo(rp.ctx, url, filepath.Join(utils.GetTmpBaseDir(), "git-cache"), rp.sshPool, rp.authProviders) + mr, err := git.NewMirroredGitRepo(rp.ctx, url, filepath.Join(utils.GetTmpBaseDir(rp.ctx), "git-cache"), rp.sshPool, rp.authProviders) if err != nil { return nil, err } @@ -198,7 +198,7 @@ func (e *CacheEntry) GetClonedDir(ref string) (string, git.CheckoutInfo, error) return "", git.CheckoutInfo{}, err } - tmpDir := filepath.Join(utils.GetTmpBaseDir(), "git-cloned") + tmpDir := filepath.Join(utils.GetTmpBaseDir(e.rp.ctx), "git-cloned") err = os.MkdirAll(tmpDir, 0700) if err != nil { return "", git.CheckoutInfo{}, err diff --git a/pkg/sops/utils.go b/pkg/sops/utils.go index 7a1250d5e..5b05e20ca 100644 --- a/pkg/sops/utils.go +++ b/pkg/sops/utils.go @@ -2,6 +2,7 @@ package sops import ( "bytes" + "context" "errors" "fmt" "github.com/kluctl/kluctl/v2/pkg/utils" @@ -60,8 +61,8 @@ func MaybeDecryptFileTo(decrypter SopsDecrypter, path string, to string) error { return nil } -func MaybeDecryptFileToTmp(decrypter SopsDecrypter, path string) (string, error) { - tmp, err := os.CreateTemp(utils.GetTmpBaseDir(), "sops-decrypt-") +func MaybeDecryptFileToTmp(ctx context.Context, decrypter SopsDecrypter, path string) (string, error) { + tmp, err := os.CreateTemp(utils.GetTmpBaseDir(ctx), "sops-decrypt-") if err != nil { return "", err } diff --git a/pkg/utils/tmpdir.go b/pkg/utils/tmpdir.go new file mode 100644 index 000000000..76d684f8a --- /dev/null +++ b/pkg/utils/tmpdir.go @@ -0,0 +1,98 @@ +package utils + +import ( + "context" + "fmt" + "io/fs" + "os" + "os/user" + "path/filepath" + "runtime" + "sync" +) + +type tmpBaseDirKey int +type tmpBaseDirValue struct { + dir string + initOnce sync.Once +} + +var tmpBaseDirKeyInst tmpBaseDirKey +var tmpBaseDirValueDefault = &tmpBaseDirValue{ + dir: getDefaultTmpBaseDir(), +} + +func WithTmpBaseDir(ctx context.Context, tmpBaseDir string) context.Context { + return context.WithValue(ctx, tmpBaseDirKeyInst, &tmpBaseDirValue{ + dir: tmpBaseDir, + }) +} + +func GetTmpBaseDir(ctx context.Context) string { + v := ctx.Value(tmpBaseDirKeyInst) + if v == nil { + v = tmpBaseDirValueDefault + } + v2 := v.(*tmpBaseDirValue) + v2.initOnce.Do(func() { + v2.dir = createTmpBaseDir(v2.dir) + }) + return v2.dir +} + +func getDefaultTmpBaseDir() string { + dir := os.Getenv("KLUCTL_BASE_TMP_DIR") + if dir != "" { + return dir + } + + return filepath.Join(os.TempDir(), "kluctl-workdir") +} + +func createTmpBaseDir(dir string) string { + ensureDir(dir, 0o777, true) + + // every user gets its own tmp dir + if runtime.GOOS == "windows" { + u, err := user.Current() + if err != nil { + panic(err) + } + dir = filepath.Join(dir, u.Uid) + } else { + uid := os.Getuid() + dir = filepath.Join(dir, fmt.Sprintf("%d", uid)) + } + + ensureDir(dir, 0o700, true) + return dir +} + +func ensureDir(path string, perm fs.FileMode, allowCreate bool) { + if Exists(path) { + st, err := os.Lstat(path) + if err != nil { + panic(err) + } + if !st.IsDir() { + panic(fmt.Sprintf("%s is not a directory", path)) + } + if st.Mode().Perm() != perm { + err = os.Chmod(path, perm) + if err != nil { + panic(err) + } + } + } else if !allowCreate { + panic(fmt.Sprintf("failed to ensure directory %s", path)) + } else { + err := os.Mkdir(path, perm) + if err != nil { + if os.IsExist(err) { + ensureDir(path, perm, false) + } else { + panic(err) + } + } + } +} diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 8796755a9..97b9d781d 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -3,84 +3,10 @@ package utils import ( "crypto/sha256" "encoding/hex" - "fmt" "github.com/jinzhu/copier" - "io/fs" - "os" - "os/user" - "path/filepath" - "runtime" "strconv" - "sync" ) -var createTmpBaseDirOnce sync.Once -var tmpBaseDir string - -func GetTmpBaseDir() string { - createTmpBaseDirOnce.Do(func() { - createTmpBaseDir() - }) - return tmpBaseDir -} - -func createTmpBaseDir() { - envTmpDir := os.Getenv("KLUCTL_BASE_TMP_DIR") - if envTmpDir != "" { - tmpBaseDir = envTmpDir - return - } - - dir := filepath.Join(os.TempDir(), "kluctl-workdir") - - ensureDir(dir, 0o777, true) - - // every user gets its own tmp dir - if runtime.GOOS == "windows" { - u, err := user.Current() - if err != nil { - panic(err) - } - dir = filepath.Join(dir, u.Uid) - } else { - uid := os.Getuid() - dir = filepath.Join(dir, fmt.Sprintf("%d", uid)) - } - - ensureDir(dir, 0o700, true) - - tmpBaseDir = dir -} - -func ensureDir(path string, perm fs.FileMode, allowCreate bool) { - if Exists(path) { - st, err := os.Lstat(path) - if err != nil { - panic(err) - } - if !st.IsDir() { - panic(fmt.Sprintf("%s is not a directory", path)) - } - if st.Mode().Perm() != perm { - err = os.Chmod(path, perm) - if err != nil { - panic(err) - } - } - } else if !allowCreate { - panic(fmt.Sprintf("failed to ensure directory %s", path)) - } else { - err := os.Mkdir(path, perm) - if err != nil { - if os.IsExist(err) { - ensureDir(path, perm, false) - } else { - panic(err) - } - } - } -} - func Sha256String(data string) string { return Sha256Bytes([]byte(data)) } From 8849b1f19d5f5ce394db03280eca87f5d5b9b45d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 9 Dec 2022 11:55:53 +0100 Subject: [PATCH 1284/2916] tests: Use global KUBECONFIG instead of per-project mergedKubeconfig --- e2e/args_test.go | 6 ++-- e2e/contexts_test.go | 10 ++---- e2e/default_clusters.go | 63 ++++++++++++++++++++++++++++++++++++ e2e/deployment_items_test.go | 4 +-- e2e/flux_test.go | 2 +- e2e/helm_test.go | 14 ++++---- e2e/hooks_test.go | 6 ++-- e2e/inclusion_test.go | 2 +- e2e/no_target_test.go | 3 +- e2e/seal_test.go | 3 +- e2e/sops_test.go | 6 ++-- e2e/test-utils/project.go | 48 +-------------------------- 12 files changed, 89 insertions(+), 78 deletions(-) diff --git a/e2e/args_test.go b/e2e/args_test.go index 2cfebb50e..b52578096 100644 --- a/e2e/args_test.go +++ b/e2e/args_test.go @@ -13,7 +13,7 @@ func testArgs(t *testing.T, deprecated bool) { k := defaultCluster1 - p := test_utils.NewTestProject(t, k) + p := test_utils.NewTestProject(t) createNamespace(t, k, p.TestSlug()) @@ -136,7 +136,7 @@ func TestArgsFromEnv(t *testing.T) { k := defaultCluster1 - p := test_utils.NewTestProject(t, k) + p := test_utils.NewTestProject(t) createNamespace(t, k, p.TestSlug()) @@ -169,7 +169,7 @@ func TestArgsFromEnvAndCli(t *testing.T) { k := defaultCluster1 - p := test_utils.NewTestProject(t, k) + p := test_utils.NewTestProject(t) createNamespace(t, k, p.TestSlug()) diff --git a/e2e/contexts_test.go b/e2e/contexts_test.go index ef7a203b0..e5c6a73a9 100644 --- a/e2e/contexts_test.go +++ b/e2e/contexts_test.go @@ -3,13 +3,11 @@ package e2e import ( "github.com/kluctl/kluctl/v2/e2e/test-utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" - "k8s.io/client-go/tools/clientcmd/api" "testing" ) func prepareContextTest(t *testing.T) *test_utils.TestProject { - p := test_utils.NewTestProject(t, defaultCluster1) - p.MergeKubeconfig(defaultCluster2) + p := test_utils.NewTestProject(t) createNamespace(t, defaultCluster1, p.TestSlug()) createNamespace(t, defaultCluster2, p.TestSlug()) @@ -23,8 +21,6 @@ func prepareContextTest(t *testing.T) *test_utils.TestProject { } func TestContextCurrent(t *testing.T) { - t.Parallel() - p := prepareContextTest(t) p.UpdateTarget("test1", func(target *uo.UnstructuredObject) { @@ -35,9 +31,7 @@ func TestContextCurrent(t *testing.T) { assertConfigMapExists(t, defaultCluster1, p.TestSlug(), "cm") assertConfigMapNotExists(t, defaultCluster2, p.TestSlug(), "cm") - p.UpdateMergedKubeconfig(func(config *api.Config) { - config.CurrentContext = defaultCluster2.Context - }) + setMergedKubeconfigContext(t, defaultCluster2.Context) p.KluctlMust("deploy", "--yes", "-t", "test1") assertConfigMapExists(t, defaultCluster2, p.TestSlug(), "cm") diff --git a/e2e/default_clusters.go b/e2e/default_clusters.go index 3baff99ef..0d5479d27 100644 --- a/e2e/default_clusters.go +++ b/e2e/default_clusters.go @@ -1,14 +1,21 @@ package e2e +import "C" import ( + "github.com/imdario/mergo" "github.com/kluctl/kluctl/v2/e2e/test-utils" "github.com/kluctl/kluctl/v2/e2e/test_resources" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/tools/clientcmd" + "os" + "runtime" "sync" + "testing" ) var defaultCluster1 = test_utils.CreateEnvTestCluster("cluster1") var defaultCluster2 = test_utils.CreateEnvTestCluster("cluster2") +var mergedKubeconfig string func init() { if isCallKluctlHack() { @@ -37,4 +44,60 @@ func init() { test_resources.ApplyYaml("sealed-secrets.yaml", defaultCluster2) }() wg.Wait() + + tmpKubeconfig, err := os.CreateTemp("", "") + if err != nil { + panic(err) + } + _ = tmpKubeconfig.Close() + runtime.SetFinalizer(defaultCluster1, func(_ *test_utils.EnvTestCluster) { + _ = os.Remove(tmpKubeconfig.Name()) + }) + + mergeKubeconfig(tmpKubeconfig.Name(), defaultCluster1.Kubeconfig) + mergeKubeconfig(tmpKubeconfig.Name(), defaultCluster2.Kubeconfig) + + mergedKubeconfig = tmpKubeconfig.Name() + _ = os.Setenv("KUBECONFIG", mergedKubeconfig) +} + +func mergeKubeconfig(path string, kubeconfig []byte) { + mkcfg, err := clientcmd.LoadFromFile(path) + if err != nil { + panic(err) + } + + nkcfg, err := clientcmd.Load(kubeconfig) + if err != nil { + panic(err) + } + + err = mergo.Merge(mkcfg, nkcfg) + if err != nil { + panic(err) + } + + err = clientcmd.WriteToFile(*mkcfg, path) + if err != nil { + panic(err) + } +} + +func setMergedKubeconfigContext(t *testing.T, newContext string) { + tmpKubeconfig, err := os.CreateTemp(t.TempDir(), "kubeconfig-") + if err != nil { + t.Fatal(err) + } + _ = tmpKubeconfig.Close() + + kcfg, err := clientcmd.LoadFromFile(mergedKubeconfig) + if err != nil { + t.Fatal(err) + } + kcfg.CurrentContext = newContext + err = clientcmd.WriteToFile(*kcfg, tmpKubeconfig.Name()) + if err != nil { + t.Fatal(err) + } + t.Setenv("KUBECONFIG", tmpKubeconfig.Name()) } diff --git a/e2e/deployment_items_test.go b/e2e/deployment_items_test.go index 2a346b8fe..71e93a34a 100644 --- a/e2e/deployment_items_test.go +++ b/e2e/deployment_items_test.go @@ -11,7 +11,7 @@ func TestKustomize(t *testing.T) { k := defaultCluster1 - p := test_utils.NewTestProject(t, k) + p := test_utils.NewTestProject(t) createNamespace(t, k, p.TestSlug()) @@ -39,7 +39,7 @@ func TestGeneratedKustomize(t *testing.T) { k := defaultCluster1 - p := test_utils.NewTestProject(t, k) + p := test_utils.NewTestProject(t) createNamespace(t, k, p.TestSlug()) diff --git a/e2e/flux_test.go b/e2e/flux_test.go index c8e1979b2..8854487b7 100644 --- a/e2e/flux_test.go +++ b/e2e/flux_test.go @@ -36,7 +36,7 @@ func TestFluxCommands(t *testing.T) { k := defaultCluster1 - p := test_utils.NewTestProject(t, k) + p := test_utils.NewTestProject(t) var wg sync.WaitGroup wg.Add(2) diff --git a/e2e/helm_test.go b/e2e/helm_test.go index cedfa3a60..3fe9dad20 100644 --- a/e2e/helm_test.go +++ b/e2e/helm_test.go @@ -33,7 +33,7 @@ func testHelmPull(t *testing.T, tc testCase, prePull bool) { k := defaultCluster1 - p := test_utils.NewTestProject(t, k) + p := test_utils.NewTestProject(t) createNamespace(t, k, p.TestSlug()) @@ -124,7 +124,7 @@ func testHelmManualUpgrade(t *testing.T, oci bool) { k := defaultCluster1 - p := test_utils.NewTestProject(t, k) + p := test_utils.NewTestProject(t) createNamespace(t, k, p.TestSlug()) @@ -168,7 +168,7 @@ func testHelmUpdate(t *testing.T, oci bool, upgrade bool, commit bool) { k := defaultCluster1 - p := test_utils.NewTestProject(t, k) + p := test_utils.NewTestProject(t) createNamespace(t, k, p.TestSlug()) @@ -278,7 +278,7 @@ func testHelmUpdateConstraints(t *testing.T, oci bool) { k := defaultCluster1 - p := test_utils.NewTestProject(t, k) + p := test_utils.NewTestProject(t) createNamespace(t, k, p.TestSlug()) @@ -341,7 +341,7 @@ func TestHelmValues(t *testing.T) { k := defaultCluster1 - p := test_utils.NewTestProject(t, k) + p := test_utils.NewTestProject(t) createNamespace(t, k, p.TestSlug()) @@ -406,7 +406,7 @@ func TestHelmTemplateChartYaml(t *testing.T) { k := defaultCluster1 - p := test_utils.NewTestProject(t, k) + p := test_utils.NewTestProject(t) createNamespace(t, k, p.TestSlug()) createNamespace(t, k, p.TestSlug()+"-a") @@ -437,7 +437,7 @@ func TestHelmRenderOfflineKubernetes(t *testing.T) { k := defaultCluster1 - p := test_utils.NewTestProject(t, k) + p := test_utils.NewTestProject(t) createNamespace(t, k, p.TestSlug()) diff --git a/e2e/hooks_test.go b/e2e/hooks_test.go index 766c43e9c..e32f1c55f 100644 --- a/e2e/hooks_test.go +++ b/e2e/hooks_test.go @@ -99,14 +99,16 @@ func prepareHookTestProject(t *testing.T, hook string, hookDeletionPolicy string } s.setupWebhook() - s.p = test_utils.NewTestProject(t, s.k) + s.p = test_utils.NewTestProject(t) t.Cleanup(func() { s.removeWebhook() }) createNamespace(s.t, s.k, s.p.TestSlug()) - s.p.UpdateTarget("test", nil) + s.p.UpdateTarget("test", func(target *uo.UnstructuredObject) { + _ = target.SetNestedField(s.k.Context, "context") + }) s.p.AddKustomizeDeployment("hook", nil, nil) diff --git a/e2e/inclusion_test.go b/e2e/inclusion_test.go index 08e6f5881..84b427c97 100644 --- a/e2e/inclusion_test.go +++ b/e2e/inclusion_test.go @@ -10,7 +10,7 @@ import ( func prepareInclusionTestProject(t *testing.T, withIncludes bool) (*test_utils.TestProject, *test_utils.EnvTestCluster) { k := defaultCluster1 - p := test_utils.NewTestProject(t, k) + p := test_utils.NewTestProject(t) createNamespace(t, k, p.TestSlug()) diff --git a/e2e/no_target_test.go b/e2e/no_target_test.go index caa7d217c..1451d10d8 100644 --- a/e2e/no_target_test.go +++ b/e2e/no_target_test.go @@ -9,8 +9,7 @@ import ( ) func prepareNoTargetTest(t *testing.T, withDeploymentYaml bool) *test_utils.TestProject { - p := test_utils.NewTestProject(t, defaultCluster1) - p.MergeKubeconfig(defaultCluster2) + p := test_utils.NewTestProject(t) createNamespace(t, defaultCluster1, p.TestSlug()) createNamespace(t, defaultCluster2, p.TestSlug()) diff --git a/e2e/seal_test.go b/e2e/seal_test.go index ecbf0b69f..49c060feb 100644 --- a/e2e/seal_test.go +++ b/e2e/seal_test.go @@ -98,7 +98,7 @@ func addProxyVars(p *test_utils.TestProject) { } func prepareSealTest(t *testing.T, k *test_utils.EnvTestCluster, secrets map[string]string, varsSources []*uo.UnstructuredObject, proxy bool) *test_utils.TestProject { - p := test_utils.NewTestProject(t, k) + p := test_utils.NewTestProject(t) if proxy { addProxyVars(p) @@ -341,7 +341,6 @@ func TestSeal_MultipleTargets(t *testing.T) { }) addSecretsSetToTarget(p, "test-target2", "test2") - p.MergeKubeconfig(defaultCluster2) createNamespace(t, defaultCluster2, p.TestSlug()) p.UpdateTarget("test-target", func(target *uo.UnstructuredObject) { _ = target.SetNestedField(defaultCluster1.Context, "context") diff --git a/e2e/sops_test.go b/e2e/sops_test.go index da65ece12..598885272 100644 --- a/e2e/sops_test.go +++ b/e2e/sops_test.go @@ -15,7 +15,7 @@ func TestSopsVars(t *testing.T) { k := defaultCluster1 - p := test_utils.NewTestProject(t, k) + p := test_utils.NewTestProject(t) createNamespace(t, k, p.TestSlug()) @@ -55,7 +55,7 @@ func TestSopsResources(t *testing.T) { k := defaultCluster1 - p := test_utils.NewTestProject(t, k) + p := test_utils.NewTestProject(t) createNamespace(t, k, p.TestSlug()) @@ -88,7 +88,7 @@ func TestSopsHelmValues(t *testing.T) { k := defaultCluster1 - p := test_utils.NewTestProject(t, k) + p := test_utils.NewTestProject(t) createNamespace(t, k, p.TestSlug()) diff --git a/e2e/test-utils/project.go b/e2e/test-utils/project.go index ffc225c62..e3951f616 100644 --- a/e2e/test-utils/project.go +++ b/e2e/test-utils/project.go @@ -4,14 +4,11 @@ import ( "fmt" "github.com/go-git/go-git/v5" "github.com/huandu/xstrings" - "github.com/imdario/mergo" "github.com/jinzhu/copier" git2 "github.com/kluctl/kluctl/v2/pkg/git" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" registry2 "helm.sh/helm/v3/pkg/registry" - "k8s.io/client-go/tools/clientcmd" - clientcmdapi "k8s.io/client-go/tools/clientcmd/api" "os" "os/exec" "path/filepath" @@ -24,12 +21,10 @@ type TestProject struct { t *testing.T extraEnv []string - mergedKubeconfig string - gitServer *git2.TestGitServer } -func NewTestProject(t *testing.T, k *EnvTestCluster) *TestProject { +func NewTestProject(t *testing.T) *TestProject { p := &TestProject{ t: t, } @@ -43,19 +38,6 @@ func NewTestProject(t *testing.T, k *EnvTestCluster) *TestProject { p.UpdateDeploymentYaml(".", func(c *uo.UnstructuredObject) error { return nil }) - - tmpFile, err := os.CreateTemp("", p.TestSlug()+"-kubeconfig-") - if err != nil { - t.Fatal(err) - } - _ = tmpFile.Close() - p.mergedKubeconfig = tmpFile.Name() - t.Cleanup(func() { - os.Remove(p.mergedKubeconfig) - }) - if k != nil { - p.MergeKubeconfig(k) - } return p } @@ -66,34 +48,6 @@ func (p *TestProject) TestSlug() string { return n } -func (p *TestProject) MergeKubeconfig(k *EnvTestCluster) { - p.UpdateMergedKubeconfig(func(config *clientcmdapi.Config) { - nkcfg, err := clientcmd.Load(k.Kubeconfig) - if err != nil { - p.t.Fatal(err) - } - - err = mergo.Merge(config, nkcfg) - if err != nil { - p.t.Fatal(err) - } - }) -} - -func (p *TestProject) UpdateMergedKubeconfig(cb func(config *clientcmdapi.Config)) { - mkcfg, err := clientcmd.LoadFromFile(p.mergedKubeconfig) - if err != nil { - p.t.Fatal(err) - } - - cb(mkcfg) - - err = clientcmd.WriteToFile(*mkcfg, p.mergedKubeconfig) - if err != nil { - p.t.Fatal(err) - } -} - func (p *TestProject) AddExtraEnv(e string) { p.extraEnv = append(p.extraEnv, e) } From e427b01612a6b1c02ca7a134117c2b457e300633 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 9 Dec 2022 13:23:23 +0100 Subject: [PATCH 1285/2916] refactor: Allow to override stdout/stderr in commands --- .../commands/cmd_check_image_updates.go | 3 +- cmd/kluctl/commands/cmd_delete.go | 5 +- cmd/kluctl/commands/cmd_render.go | 2 +- cmd/kluctl/commands/cmd_validate.go | 3 +- cmd/kluctl/commands/cmd_version.go | 3 +- cmd/kluctl/commands/command_result.go | 15 +++--- cmd/kluctl/commands/override_std_streams.go | 53 +++++++++++++++++++ 7 files changed, 67 insertions(+), 17 deletions(-) create mode 100644 cmd/kluctl/commands/override_std_streams.go diff --git a/cmd/kluctl/commands/cmd_check_image_updates.go b/cmd/kluctl/commands/cmd_check_image_updates.go index a21df32c3..64471dd3b 100644 --- a/cmd/kluctl/commands/cmd_check_image_updates.go +++ b/cmd/kluctl/commands/cmd_check_image_updates.go @@ -7,7 +7,6 @@ import ( "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/versions" - "os" "regexp" "sort" "strings" @@ -130,6 +129,6 @@ func runCheckImageUpdates(cmdCtx *commandCtx) error { } table.SortRows(1) - _, _ = os.Stdout.WriteString(table.Render([]int{60})) + _, _ = getStdout(cmdCtx.ctx).WriteString(table.Render([]int{60})) return nil } diff --git a/cmd/kluctl/commands/cmd_delete.go b/cmd/kluctl/commands/cmd_delete.go index 48a09b2c7..b034c6c4e 100644 --- a/cmd/kluctl/commands/cmd_delete.go +++ b/cmd/kluctl/commands/cmd_delete.go @@ -11,7 +11,6 @@ import ( "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" - "os" ) type deleteCmd struct { @@ -79,9 +78,9 @@ func (cmd *deleteCmd) Run(ctx context.Context) error { func confirmedDeleteObjects(ctx context.Context, k *k8s.K8sCluster, refs []k8s2.ObjectRef, dryRun bool, forceYes bool) (*types.CommandResult, error) { if len(refs) != 0 { - _, _ = os.Stderr.WriteString("The following objects will be deleted:\n") + _, _ = getStderr(ctx).WriteString("The following objects will be deleted:\n") for _, ref := range refs { - _, _ = os.Stderr.WriteString(fmt.Sprintf(" %s\n", ref.String())) + _, _ = getStderr(ctx).WriteString(fmt.Sprintf(" %s\n", ref.String())) } if !forceYes && !dryRun { if !status.AskForConfirmation(ctx, fmt.Sprintf("Do you really want to delete %d objects?", len(refs))) { diff --git a/cmd/kluctl/commands/cmd_render.go b/cmd/kluctl/commands/cmd_render.go index 27851b332..9c4152129 100644 --- a/cmd/kluctl/commands/cmd_render.go +++ b/cmd/kluctl/commands/cmd_render.go @@ -60,7 +60,7 @@ func (cmd *renderCmd) Run(ctx context.Context) error { defer os.RemoveAll(cmd.RenderOutputDir) } status.Flush(cmdCtx.ctx) - return yaml.WriteYamlAllStream(os.Stdout, all) + return yaml.WriteYamlAllStream(getStdout(ctx), all) } else { status.Info(cmdCtx.ctx, "Rendered into %s", cmdCtx.targetCtx.SharedContext.RenderDir) } diff --git a/cmd/kluctl/commands/cmd_validate.go b/cmd/kluctl/commands/cmd_validate.go index 101af905f..26b43da2f 100644 --- a/cmd/kluctl/commands/cmd_validate.go +++ b/cmd/kluctl/commands/cmd_validate.go @@ -5,7 +5,6 @@ import ( "fmt" "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/deployment/commands" - "os" "time" ) @@ -54,7 +53,7 @@ func (cmd *validateCmd) Run(ctx context.Context) error { } if !failed { - _, _ = os.Stderr.WriteString("Validation succeeded\n") + _, _ = getStderr(ctx).WriteString("Validation succeeded\n") return nil } diff --git a/cmd/kluctl/commands/cmd_version.go b/cmd/kluctl/commands/cmd_version.go index b7683a784..c28a5b37e 100644 --- a/cmd/kluctl/commands/cmd_version.go +++ b/cmd/kluctl/commands/cmd_version.go @@ -3,13 +3,12 @@ package commands import ( "context" "github.com/kluctl/kluctl/v2/pkg/version" - "os" ) type versionCmd struct { } func (cmd *versionCmd) Run(ctx context.Context) error { - _, err := os.Stdout.WriteString(version.GetVersion() + "\n") + _, err := getStdout(ctx).WriteString(version.GetVersion() + "\n") return err } diff --git a/cmd/kluctl/commands/command_result.go b/cmd/kluctl/commands/command_result.go index cdf7f0753..f9dd12408 100644 --- a/cmd/kluctl/commands/command_result.go +++ b/cmd/kluctl/commands/command_result.go @@ -173,7 +173,7 @@ func formatValidateResult(vr *types.ValidateResult, format string) (string, erro } } -func outputHelper(output []string, cb func(format string) (string, error)) error { +func outputHelper(ctx context.Context, output []string, cb func(format string) (string, error)) error { if len(output) == 0 { output = []string{"text"} } @@ -189,7 +189,7 @@ func outputHelper(output []string, cb func(format string) (string, error)) error return err } - err = outputResult(path, r) + err = outputResult(ctx, path, r) if err != nil { return err } @@ -200,7 +200,7 @@ func outputHelper(output []string, cb func(format string) (string, error)) error func outputCommandResult(ctx context.Context, output []string, cr *types.CommandResult) error { status.Flush(ctx) - return outputHelper(output, func(format string) (string, error) { + return outputHelper(ctx, output, func(format string) (string, error) { return formatCommandResult(cr, format) }) } @@ -208,7 +208,7 @@ func outputCommandResult(ctx context.Context, output []string, cr *types.Command func outputValidateResult(ctx context.Context, output []string, vr *types.ValidateResult) error { status.Flush(ctx) - return outputHelper(output, func(format string) (string, error) { + return outputHelper(ctx, output, func(format string) (string, error) { return formatValidateResult(vr, format) }) } @@ -238,7 +238,7 @@ func outputYamlResult(ctx context.Context, output []string, result interface{}, s = x } for _, path := range output { - err := outputResult(&path, s) + err := outputResult(ctx, &path, s) if err != nil { return err } @@ -246,8 +246,9 @@ func outputYamlResult(ctx context.Context, output []string, result interface{}, return nil } -func outputResult(f *string, result string) error { - w := os.Stdout +func outputResult(ctx context.Context, f *string, result string) error { + var w io.Writer + w = getStdout(ctx) if f != nil && *f != "-" { f, err := os.Create(*f) if err != nil { diff --git a/cmd/kluctl/commands/override_std_streams.go b/cmd/kluctl/commands/override_std_streams.go new file mode 100644 index 000000000..ab2573f72 --- /dev/null +++ b/cmd/kluctl/commands/override_std_streams.go @@ -0,0 +1,53 @@ +package commands + +import ( + "context" + "io" + "os" +) + +type overrideStdStreamsKey int +type overrideStdStreamsValue struct { + stdout io.Writer + stderr io.Writer +} + +var overrideStdStreamsKeyInst overrideStdStreamsKey + +func WithStdStreams(ctx context.Context, stdout io.Writer, stderr io.Writer) context.Context { + return context.WithValue(ctx, overrideStdStreamsKeyInst, &overrideStdStreamsValue{ + stdout: stdout, + stderr: stderr, + }) +} + +func getStdStreams(ctx context.Context) (io.Writer, io.Writer) { + v := ctx.Value(overrideStdStreamsKeyInst) + if v == nil { + return os.Stdout, os.Stderr + } + v2 := v.(*overrideStdStreamsValue) + return v2.stdout, v2.stderr +} + +type stringWriter struct { + w io.Writer +} + +func (w *stringWriter) WriteString(s string) (n int, err error) { + return w.w.Write([]byte(s)) +} + +func (w *stringWriter) Write(b []byte) (n int, err error) { + return w.w.Write(b) +} + +func getStdout(ctx context.Context) *stringWriter { + stdout, _ := getStdStreams(ctx) + return &stringWriter{stdout} +} + +func getStderr(ctx context.Context) *stringWriter { + _, stderr := getStdStreams(ctx) + return &stringWriter{stderr} +} From d496a45ccdb50b7ff884a11d28d365aa3d16e49c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 9 Dec 2022 13:46:52 +0100 Subject: [PATCH 1286/2916] feat: Implement --project-dir argument --- cmd/kluctl/args/project.go | 22 +++++++++++++++++++++- cmd/kluctl/commands/cmd_helm_pull.go | 8 ++++---- cmd/kluctl/commands/cmd_helm_update.go | 7 ++++--- cmd/kluctl/commands/utils.go | 6 +++--- 4 files changed, 32 insertions(+), 11 deletions(-) diff --git a/cmd/kluctl/args/project.go b/cmd/kluctl/args/project.go index f60f27482..eff3ec1f0 100644 --- a/cmd/kluctl/args/project.go +++ b/cmd/kluctl/args/project.go @@ -1,8 +1,28 @@ package args -import "time" +import ( + "os" + "time" +) + +type ProjectDir struct { + ProjectDir existingDirType `group:"project" help:"Specify the project directory. Defaults to the current working directory."` +} + +func (a ProjectDir) GetProjectDir() (string, error) { + if a.ProjectDir != "" { + return a.ProjectDir.String(), nil + } + cwd, err := os.Getwd() + if err != nil { + return "", err + } + return cwd, nil +} type ProjectFlags struct { + ProjectDir + ProjectConfig existingFileType `group:"project" short:"c" help:"Location of the .kluctl.yaml config file. Defaults to $PROJECT/.kluctl.yaml" exts:"yml,yaml"` Timeout time.Duration `group:"project" help:"Specify timeout for all operations, including loading of the project, all external api calls and waiting for readiness." default:"10m"` diff --git a/cmd/kluctl/commands/cmd_helm_pull.go b/cmd/kluctl/commands/cmd_helm_pull.go index 4ee57ec2e..ab2d7bf66 100644 --- a/cmd/kluctl/commands/cmd_helm_pull.go +++ b/cmd/kluctl/commands/cmd_helm_pull.go @@ -11,12 +11,12 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils" "golang.org/x/sync/semaphore" "io/fs" - "os" "path/filepath" "sync" ) type helmPullCmd struct { + args.ProjectDir args.HelmCredentials } @@ -27,12 +27,12 @@ pulling is only needed when really required (e.g. when the chart version changes } func (cmd *helmPullCmd) Run(ctx context.Context) error { - cwd, err := os.Getwd() + projectDir, err := cmd.ProjectDir.GetProjectDir() if err != nil { return err } - gitRootPath, err := git2.DetectGitRepositoryRoot(cwd) + gitRootPath, err := git2.DetectGitRepositoryRoot(projectDir) if err != nil { return err } @@ -42,7 +42,7 @@ func (cmd *helmPullCmd) Run(ctx context.Context) error { var mutex sync.Mutex sem := semaphore.NewWeighted(8) - err = filepath.WalkDir(cwd, func(p string, d fs.DirEntry, err error) error { + err = filepath.WalkDir(projectDir, func(p string, d fs.DirEntry, err error) error { fname := filepath.Base(p) if fname != "helm-chart.yml" && fname != "helm-chart.yaml" { return nil diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go index beecb5d0e..225caec92 100644 --- a/cmd/kluctl/commands/cmd_helm_update.go +++ b/cmd/kluctl/commands/cmd_helm_update.go @@ -20,6 +20,7 @@ import ( ) type helmUpdateCmd struct { + args.ProjectDir args.HelmCredentials Upgrade bool `group:"misc" help:"Write new versions into helm-chart.yaml and perform helm-pull afterwards"` @@ -33,12 +34,12 @@ func (cmd *helmUpdateCmd) Help() string { } func (cmd *helmUpdateCmd) Run(ctx context.Context) error { - cwd, err := os.Getwd() + projectDir, err := cmd.ProjectDir.GetProjectDir() if err != nil { return err } - gitRootPath, err := git2.DetectGitRepositoryRoot(cwd) + gitRootPath, err := git2.DetectGitRepositoryRoot(projectDir) if err != nil { return err } @@ -57,7 +58,7 @@ func (cmd *helmUpdateCmd) Run(ctx context.Context) error { } var updatedCharts []*updatedChart - err = filepath.WalkDir(cwd, func(p string, d fs.DirEntry, err error) error { + err = filepath.WalkDir(projectDir, func(p string, d fs.DirEntry, err error) error { fname := filepath.Base(p) if fname != "helm-chart.yml" && fname != "helm-chart.yaml" { return nil diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index bfe63cb17..a209d2829 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -37,12 +37,12 @@ func withKluctlProjectFromArgs(ctx context.Context, projectFlags args.ProjectFla } defer j2.Close() - cwd, err := os.Getwd() + projectDir, err := projectFlags.ProjectDir.GetProjectDir() if err != nil { return err } - repoRoot, err := git.DetectGitRepositoryRoot(cwd) + repoRoot, err := git.DetectGitRepositoryRoot(projectDir) if err != nil { status.Warning(ctx, "Failed to detect git project root. This might cause follow-up errors") } @@ -74,7 +74,7 @@ func withKluctlProjectFromArgs(ctx context.Context, projectFlags args.ProjectFla loadArgs := kluctl_project.LoadKluctlProjectArgs{ RepoRoot: repoRoot, - ProjectDir: cwd, + ProjectDir: projectDir, ProjectConfig: projectFlags.ProjectConfig.String(), RP: rp, ClientConfigGetter: clientConfigGetter(forCompletion), From 986c5d62a62549a9b5e64c172db42c895c8bb894 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 9 Dec 2022 13:47:17 +0100 Subject: [PATCH 1287/2916] fix: Don't allow term.GetWidth() to return 0 --- pkg/utils/term/term_size.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/utils/term/term_size.go b/pkg/utils/term/term_size.go index 706f09180..0c210fd72 100644 --- a/pkg/utils/term/term_size.go +++ b/pkg/utils/term/term_size.go @@ -15,7 +15,7 @@ func GetWidth() int { } } w, _, err := GetSize(int(origStdout.Fd())) - if err != nil { + if err != nil || w == 0 { return 80 } return w From a71636b61c5280498da3bf4bb25b6383bf62f813 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 9 Dec 2022 14:42:19 +0100 Subject: [PATCH 1288/2916] tests: Execute kluctl via commands.Execute() instead of spawning a process --- e2e/args_test.go | 20 +++++------ e2e/flux_test.go | 2 +- e2e/seal_test.go | 2 +- e2e/sops_test.go | 24 ++++++++----- e2e/test-utils/project.go | 73 +++++++++++++++++++++++++++++++++++---- 5 files changed, 93 insertions(+), 28 deletions(-) diff --git a/e2e/args_test.go b/e2e/args_test.go index b52578096..d754bf3f6 100644 --- a/e2e/args_test.go +++ b/e2e/args_test.go @@ -128,15 +128,14 @@ func TestArgs(t *testing.T) { } func TestArgsFromEnv(t *testing.T) { - t.Setenv("KLUCTL_ARG", "a=a") - t.Setenv("KLUCTL_ARG_1", "b=b") - t.Setenv("KLUCTL_ARG_2", `c={"nested":{"nested2":"c"}}`) - t.Setenv("KLUCTL_ARG_3", "d=true") - t.Setenv("KLUCTL_ARG_4", "e='true'") - k := defaultCluster1 - p := test_utils.NewTestProject(t) + p := test_utils.NewTestProject(t, test_utils.WithUseProcess(true)) + p.AddExtraEnv("KLUCTL_ARG=a=a") + p.AddExtraEnv("KLUCTL_ARG_1=b=b") + p.AddExtraEnv(`KLUCTL_ARG_2=c={"nested":{"nested2":"c"}}`) + p.AddExtraEnv("KLUCTL_ARG_3=d=true") + p.AddExtraEnv("KLUCTL_ARG_4=e='true'") createNamespace(t, k, p.TestSlug()) @@ -164,12 +163,11 @@ func TestArgsFromEnv(t *testing.T) { } func TestArgsFromEnvAndCli(t *testing.T) { - t.Setenv("KLUCTL_ARG_1", "a=a") - t.Setenv("KLUCTL_ARG_2", "c=c") - k := defaultCluster1 - p := test_utils.NewTestProject(t) + p := test_utils.NewTestProject(t, test_utils.WithUseProcess(true)) + p.AddExtraEnv("KLUCTL_ARG_1=a=a") + p.AddExtraEnv("KLUCTL_ARG_2=c=c") createNamespace(t, k, p.TestSlug()) diff --git a/e2e/flux_test.go b/e2e/flux_test.go index 8854487b7..5967270c4 100644 --- a/e2e/flux_test.go +++ b/e2e/flux_test.go @@ -36,7 +36,7 @@ func TestFluxCommands(t *testing.T) { k := defaultCluster1 - p := test_utils.NewTestProject(t) + p := test_utils.NewTestProject(t, test_utils.WithUseProcess(true)) var wg sync.WaitGroup wg.Add(2) diff --git a/e2e/seal_test.go b/e2e/seal_test.go index 49c060feb..2308ec103 100644 --- a/e2e/seal_test.go +++ b/e2e/seal_test.go @@ -98,7 +98,7 @@ func addProxyVars(p *test_utils.TestProject) { } func prepareSealTest(t *testing.T, k *test_utils.EnvTestCluster, secrets map[string]string, varsSources []*uo.UnstructuredObject, proxy bool) *test_utils.TestProject { - p := test_utils.NewTestProject(t) + p := test_utils.NewTestProject(t, test_utils.WithUseProcess(true)) if proxy { addProxyVars(p) diff --git a/e2e/sops_test.go b/e2e/sops_test.go index 598885272..2ea1d9ac7 100644 --- a/e2e/sops_test.go +++ b/e2e/sops_test.go @@ -1,6 +1,7 @@ package e2e import ( + "fmt" "github.com/kluctl/kluctl/v2/e2e/test-utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/vars/sops_test_resources" @@ -9,13 +10,18 @@ import ( "testing" ) -func TestSopsVars(t *testing.T) { +func setSopsKey(p *test_utils.TestProject) { key, _ := sops_test_resources.TestResources.ReadFile("test-key.txt") - t.Setenv(age.SopsAgeKeyEnv, string(key)) + p.AddExtraEnv(fmt.Sprintf("%s=%s", age.SopsAgeKeyEnv, string(key))) +} + +func TestSopsVars(t *testing.T) { + t.Parallel() k := defaultCluster1 - p := test_utils.NewTestProject(t) + p := test_utils.NewTestProject(t, test_utils.WithUseProcess(true)) + setSopsKey(p) createNamespace(t, k, p.TestSlug()) @@ -50,12 +56,12 @@ func TestSopsVars(t *testing.T) { } func TestSopsResources(t *testing.T) { - key, _ := sops_test_resources.TestResources.ReadFile("test-key.txt") - t.Setenv(age.SopsAgeKeyEnv, string(key)) + t.Parallel() k := defaultCluster1 - p := test_utils.NewTestProject(t) + p := test_utils.NewTestProject(t, test_utils.WithUseProcess(true)) + setSopsKey(p) createNamespace(t, k, p.TestSlug()) @@ -83,12 +89,12 @@ func TestSopsResources(t *testing.T) { } func TestSopsHelmValues(t *testing.T) { - key, _ := sops_test_resources.TestResources.ReadFile("test-key.txt") - t.Setenv(age.SopsAgeKeyEnv, string(key)) + t.Parallel() k := defaultCluster1 - p := test_utils.NewTestProject(t) + p := test_utils.NewTestProject(t, test_utils.WithUseProcess(true)) + setSopsKey(p) createNamespace(t, k, p.TestSlug()) diff --git a/e2e/test-utils/project.go b/e2e/test-utils/project.go index e3951f616..0d3b79415 100644 --- a/e2e/test-utils/project.go +++ b/e2e/test-utils/project.go @@ -1,11 +1,16 @@ package test_utils import ( + "bytes" + "context" "fmt" "github.com/go-git/go-git/v5" "github.com/huandu/xstrings" "github.com/jinzhu/copier" + "github.com/kluctl/kluctl/v2/cmd/kluctl/commands" git2 "github.com/kluctl/kluctl/v2/pkg/git" + "github.com/kluctl/kluctl/v2/pkg/status" + "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" registry2 "helm.sh/helm/v3/pkg/registry" @@ -18,17 +23,31 @@ import ( ) type TestProject struct { - t *testing.T - extraEnv []string + t *testing.T + + extraEnv []string + useProcess bool gitServer *git2.TestGitServer } -func NewTestProject(t *testing.T) *TestProject { +type TestProjectOption func(p *TestProject) + +func WithUseProcess(useProcess bool) TestProjectOption { + return func(p *TestProject) { + p.useProcess = useProcess + } +} + +func NewTestProject(t *testing.T, opts ...TestProjectOption) *TestProject { p := &TestProject{ t: t, } + for _, o := range opts { + o(p) + } + p.gitServer = git2.NewTestGitServer(t) p.gitServer.GitInit("kluctl-project") @@ -350,7 +369,7 @@ func (p *TestProject) GetGitRepo() *git.Repository { return p.gitServer.GetGitRepo("kluctl-project") } -func (p *TestProject) Kluctl(argsIn ...string) (string, string, error) { +func (p *TestProject) KluctlProcess(argsIn ...string) (string, string, error) { var args []string args = append(args, argsIn...) args = append(args, "--no-update-check") @@ -361,7 +380,6 @@ func (p *TestProject) Kluctl(argsIn ...string) (string, string, error) { env := os.Environ() env = append(env, p.extraEnv...) - env = append(env, fmt.Sprintf("KUBECONFIG=%s", p.mergedKubeconfig)) // this will cause the init() function from call_kluctl_hack.go to invoke the kluctl root command and then exit env = append(env, "CALL_KLUCTL=true") @@ -382,10 +400,53 @@ func (p *TestProject) Kluctl(argsIn ...string) (string, string, error) { return stdout, stderr, err } +func (p *TestProject) KluctlProcessMust(argsIn ...string) (string, string) { + stdout, stderr, err := p.KluctlProcess(argsIn...) + if err != nil { + p.t.Logf(stderr) + p.t.Fatal(fmt.Errorf("kluctl failed: %w", err)) + } + return stdout, stderr +} + +func (p *TestProject) KluctlExecute(argsIn ...string) (string, string, error) { + if len(p.extraEnv) != 0 { + p.t.Fatal("extraEnv is only supported in KluctlProcess(...)") + } + + var args []string + args = append(args, "--project-dir", p.LocalRepoDir()) + args = append(args, argsIn...) + + p.t.Logf("Runnning kluctl: %s", strings.Join(args, " ")) + + stdout := bytes.NewBuffer(nil) + stderr := bytes.NewBuffer(nil) + + ctx := context.Background() + ctx = utils.WithTmpBaseDir(ctx, p.t.TempDir()) + ctx = commands.WithStdStreams(ctx, stdout, stderr) + sh := status.NewSimpleStatusHandler(func(message string) { + p.t.Log(message) + stderr.WriteString(message + "\n") + }, false, true) + defer sh.Stop() + ctx = status.NewContext(ctx, sh) + err := commands.Execute(ctx, args, nil) + return stdout.String(), stderr.String(), err +} + +func (p *TestProject) Kluctl(argsIn ...string) (string, string, error) { + if p.useProcess { + return p.KluctlProcess(argsIn...) + } else { + return p.KluctlExecute(argsIn...) + } +} + func (p *TestProject) KluctlMust(argsIn ...string) (string, string) { stdout, stderr, err := p.Kluctl(argsIn...) if err != nil { - p.t.Logf(stderr) p.t.Fatal(fmt.Errorf("kluctl failed: %w", err)) } return stdout, stderr From 687d718794b1f7008f1117dd56b9e97c6b5e88c3 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 9 Dec 2022 15:52:50 +0100 Subject: [PATCH 1289/2916] tests: Fix potential crash in webhook handler --- e2e/hooks_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/e2e/hooks_test.go b/e2e/hooks_test.go index e32f1c55f..122dc018a 100644 --- a/e2e/hooks_test.go +++ b/e2e/hooks_test.go @@ -96,10 +96,9 @@ func prepareHookTestProject(t *testing.T, hook string, hookDeletionPolicy string s := &hooksTestContext{ t: t, k: defaultCluster2, // use cluster2 as it has webhooks setup + p: test_utils.NewTestProject(t), } s.setupWebhook() - - s.p = test_utils.NewTestProject(t) t.Cleanup(func() { s.removeWebhook() }) From 8328ad819370c05b3c5bdaaae0cfea2ce5c37920 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 9 Dec 2022 15:53:25 +0100 Subject: [PATCH 1290/2916] tests: Ignore config map updates when they don't belong to the current run --- e2e/hooks_test.go | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/e2e/hooks_test.go b/e2e/hooks_test.go index 122dc018a..504559c23 100644 --- a/e2e/hooks_test.go +++ b/e2e/hooks_test.go @@ -1,14 +1,12 @@ package e2e import ( - "context" "fmt" "github.com/kluctl/kluctl/v2/e2e/test-utils" "github.com/stretchr/testify/assert" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + "sync" "testing" "github.com/kluctl/kluctl/v2/pkg/utils/uo" @@ -20,7 +18,9 @@ type hooksTestContext struct { p *test_utils.TestProject + m sync.Mutex seenConfigMaps []string + runCount int whh *test_utils.CallbackHandlerEntry } @@ -39,6 +39,8 @@ func (s *hooksTestContext) handleConfigmap(request admission.Request) { if s.p.TestSlug() != request.Namespace { return } + s.m.Lock() + defer s.m.Unlock() x, err := uo.FromString(string(request.Object.Raw)) if err != nil { @@ -52,6 +54,10 @@ func (s *hooksTestContext) handleConfigmap(request admission.Request) { if err != nil { s.t.Fatal(err) } + runCount := x.GetK8sLabel("tests.kluctl.io/runCount") + if runCount == nil || *runCount != fmt.Sprintf("%d", s.runCount) { + return + } s.t.Logf("handleConfigmap: op=%s, name=%s/%s, generation=%d, uid=%s", request.Operation, request.Namespace, request.Name, generation, uid) @@ -59,6 +65,8 @@ func (s *hooksTestContext) handleConfigmap(request admission.Request) { } func (s *hooksTestContext) clearSeenConfigmaps() { + s.m.Lock() + defer s.m.Unlock() s.t.Logf("clearSeenConfigmaps: %v", s.seenConfigMaps) s.seenConfigMaps = nil } @@ -117,20 +125,22 @@ func prepareHookTestProject(t *testing.T, hook string, hookDeletionPolicy string return s } +func (s *hooksTestContext) incRunCount() { + s.runCount++ + s.t.Logf("incRunCount: %d", s.runCount) + s.p.UpdateDeploymentYaml(".", func(o *uo.UnstructuredObject) error { + _ = o.SetNestedField(fmt.Sprintf("%d", s.runCount), "commonLabels", "tests.kluctl.io/runCount") + return nil + }) +} + func (s *hooksTestContext) ensureHookExecuted(expectedCms ...string) { s.clearSeenConfigmaps() + s.incRunCount() s.p.KluctlMust("deploy", "--yes", "-t", "test") assert.Equal(s.t, expectedCms, s.seenConfigMaps) } -func (s *hooksTestContext) ensureHookNotExecuted() { - _ = s.k.DynamicClient.Resource(corev1.SchemeGroupVersion.WithResource("configmaps")). - Namespace(s.p.TestSlug()). - Delete(context.Background(), "cm1", metav1.DeleteOptions{}) - s.p.KluctlMust("deploy", "--yes", "-t", "test") - assertConfigMapNotExists(s.t, s.k, s.p.TestSlug(), "cm1") -} - func TestHooksPreDeployInitial(t *testing.T) { t.Parallel() s := prepareHookTestProject(t, "pre-deploy-initial", "") From b3a72803d3653eed220fb74f585e2a8a71a0897b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 9 Dec 2022 16:03:13 +0100 Subject: [PATCH 1291/2916] docs: Run make replace-commands-help --- docs/reference/commands/common-arguments.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/reference/commands/common-arguments.md b/docs/reference/commands/common-arguments.md index aedafc415..0f3150151 100644 --- a/docs/reference/commands/common-arguments.md +++ b/docs/reference/commands/common-arguments.md @@ -62,6 +62,7 @@ Project arguments: 'github.com:my-org/my-repo:my-branch=/local/path/to/override' -c, --project-config existingfile Location of the .kluctl.yaml config file. Defaults to $PROJECT/.kluctl.yaml + --project-dir existingdir Specify the project directory. Defaults to the current working directory. -t, --target string Target name to run command for. Target must exist in .kluctl.yaml. -T, --target-name-override string Overrides the target name. If -t is used at the same time, then the target will be looked up based on -t and then renamed to the From f704069fcab9070242670027936a152dc85326d4 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 9 Dec 2022 16:14:28 +0100 Subject: [PATCH 1292/2916] refactor: More uses og GetK8sAnnotation/GetK8sLabel and ParseBoolOrFalse --- pkg/deployment/deployment_item.go | 8 ++++++-- pkg/deployment/utils/delete_utils.go | 9 +++------ pkg/diff/normalize.go | 5 ++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index bbd9665b6..94f005e5c 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -578,8 +578,12 @@ func (di *DeploymentItem) postprocessObjects(images *Images) error { } // Set common labels/annotations - o.SetK8sLabels(uo.CopyMergeStrMap(o.GetK8sLabels(), commonLabels)) - o.SetK8sAnnotations(uo.CopyMergeStrMap(o.GetK8sAnnotations(), commonAnnotations)) + for n, v := range commonLabels { + o.SetK8sLabel(n, v) + } + for n, v := range commonAnnotations { + o.SetK8sAnnotation(n, v) + } // Resolve image placeholders err := images.ResolvePlaceholders(di.ctx.K, o, di.RelRenderedDir, di.Tags.ListKeys()) diff --git a/pkg/deployment/utils/delete_utils.go b/pkg/deployment/utils/delete_utils.go index dabdf569e..f4cec36cc 100644 --- a/pkg/deployment/utils/delete_utils.go +++ b/pkg/deployment/utils/delete_utils.go @@ -5,11 +5,11 @@ import ( "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/types" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "golang.org/x/sync/semaphore" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" - "strconv" "sync" ) @@ -73,13 +73,11 @@ func filterObjectsForDelete(k *k8s.K8sCluster, objects []*uo.UnstructuredObject, continue } - annotations := o.GetK8sAnnotations() ownerRefs := o.GetK8sOwnerReferences() managedFields := o.GetK8sManagedFields() // exclude when explicitly requested - skipDelete, err := strconv.ParseBool(annotations["kluctl.io/skip-delete"]) - if err == nil && skipDelete { + if utils.ParseBoolOrFalse(o.GetK8sAnnotation("kluctl.io/skip-delete")) { continue } @@ -114,8 +112,7 @@ func filterObjectsForDelete(k *k8s.K8sCluster, objects []*uo.UnstructuredObject, // exclude resources which have the 'kluctl.io/skip-delete-if-tags' annotation set if inclusionHasTags { - skipDeleteIfTags, err := strconv.ParseBool(annotations["kluctl.io/skip-delete-if-tags"]) - if err == nil && skipDeleteIfTags { + if utils.ParseBoolOrFalse(o.GetK8sAnnotation("kluctl.io/skip-delete-if-tags")) { continue } } diff --git a/pkg/diff/normalize.go b/pkg/diff/normalize.go index dd69cf073..5123a347f 100644 --- a/pkg/diff/normalize.go +++ b/pkg/diff/normalize.go @@ -3,9 +3,9 @@ package diff import ( "fmt" "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "regexp" - "strconv" "strings" ) @@ -168,8 +168,7 @@ func NormalizeObject(o_ *uo.UnstructuredObject, ignoreForDiffs []*types.IgnoreFo } } - ignoreAll, _ := strconv.ParseBool(localObject.GetK8sAnnotations()["kluctl.io/ignore-diff"]) - if ignoreAll { + if utils.ParseBoolOrFalse(localObject.GetK8sAnnotation("kluctl.io/ignore-diff")) { // Return empty object so that diffs will always be empty return &uo.UnstructuredObject{Object: map[string]interface{}{}} } From e4da1f800f5869eacfd463c99f3224dea9732e6b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 9 Dec 2022 16:20:56 +0100 Subject: [PATCH 1293/2916] feat: Add compatibility code for helm.sh/resource-policy annotation --- pkg/deployment/utils/delete_utils.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkg/deployment/utils/delete_utils.go b/pkg/deployment/utils/delete_utils.go index f4cec36cc..babaa5a4b 100644 --- a/pkg/deployment/utils/delete_utils.go +++ b/pkg/deployment/utils/delete_utils.go @@ -81,6 +81,14 @@ func filterObjectsForDelete(k *k8s.K8sCluster, objects []*uo.UnstructuredObject, continue } + helmResourcePolicy := o.GetK8sAnnotation("helm.sh/resource-policy") + if helmResourcePolicy != nil && *helmResourcePolicy == "keep" { + // warning, this is currently not how Helm handles it. Helm will only respect annotations set in the Chart + // itself, while Kluctl will also respect it when manually set on the live resource + // See: https://github.com/helm/helm/issues/8132 + continue + } + // exclude objects which are owned by some other object if len(ownerRefs) != 0 { continue From 0cc442d698975b728946edd7d5e45564bf77f5b7 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 9 Dec 2022 16:23:47 +0100 Subject: [PATCH 1294/2916] tests: Fix race condition --- e2e/test-utils/project.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/e2e/test-utils/project.go b/e2e/test-utils/project.go index 0d3b79415..9a87be0f1 100644 --- a/e2e/test-utils/project.go +++ b/e2e/test-utils/project.go @@ -19,6 +19,7 @@ import ( "path/filepath" "reflect" "strings" + "sync" "testing" ) @@ -420,6 +421,7 @@ func (p *TestProject) KluctlExecute(argsIn ...string) (string, string, error) { p.t.Logf("Runnning kluctl: %s", strings.Join(args, " ")) + var m sync.Mutex stdout := bytes.NewBuffer(nil) stderr := bytes.NewBuffer(nil) @@ -427,6 +429,8 @@ func (p *TestProject) KluctlExecute(argsIn ...string) (string, string, error) { ctx = utils.WithTmpBaseDir(ctx, p.t.TempDir()) ctx = commands.WithStdStreams(ctx, stdout, stderr) sh := status.NewSimpleStatusHandler(func(message string) { + m.Lock() + defer m.Unlock() p.t.Log(message) stderr.WriteString(message + "\n") }, false, true) From 7cf5d1ac4df9f772d8aa538abbc747f5e593878f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 9 Dec 2022 17:06:57 +0100 Subject: [PATCH 1295/2916] feat: Respect inclusion flags while collecting deployments --- docs/reference/deployments/deployment-yml.md | 2 ++ pkg/deployment/commands/poke_images.go | 3 --- pkg/deployment/commands/validate.go | 3 --- pkg/deployment/deployment_collection.go | 13 +++++++++---- pkg/deployment/deployment_item.go | 9 ++------- pkg/deployment/utils/apply_utils.go | 5 ----- pkg/deployment/utils/diff_utils.go | 4 ---- 7 files changed, 13 insertions(+), 26 deletions(-) diff --git a/docs/reference/deployments/deployment-yml.md b/docs/reference/deployments/deployment-yml.md index a8ff6252f..73e0fb3a5 100644 --- a/docs/reference/deployments/deployment-yml.md +++ b/docs/reference/deployments/deployment-yml.md @@ -183,6 +183,8 @@ deployments: - path: kustomizeDeployment2 ``` +Please note that `alwaysDeploy` will also cause [kluctl render](../commands/render.md) to always render the resources. + ### skipDeleteIfTags Forces exclusion of a deployment whenever inclusion/exclusion tags are specified via command line. See [Deleting with tag inclusion/exclusion](./tags.md#deploying-with-tag-inclusionexclusion) for details. diff --git a/pkg/deployment/commands/poke_images.go b/pkg/deployment/commands/poke_images.go index a06bf48a2..3bcaaef36 100644 --- a/pkg/deployment/commands/poke_images.go +++ b/pkg/deployment/commands/poke_images.go @@ -35,9 +35,6 @@ func (cmd *PokeImagesCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*type allObjects := make(map[k8s2.ObjectRef]*uo.UnstructuredObject) for _, d := range cmd.c.Deployments { - if !d.CheckInclusionForDeploy() { - continue - } for _, o := range d.Objects { allObjects[o.GetK8sRef()] = o } diff --git a/pkg/deployment/commands/validate.go b/pkg/deployment/commands/validate.go index 1b4f20e89..a5781f855 100644 --- a/pkg/deployment/commands/validate.go +++ b/pkg/deployment/commands/validate.go @@ -38,9 +38,6 @@ func (cmd *ValidateCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*types. ad := utils2.NewApplyDeploymentsUtil(ctx, cmd.dew, cmd.c.Deployments, cmd.ru, k, &utils2.ApplyUtilOptions{}) for _, d := range cmd.c.Deployments { - if !d.CheckInclusionForDeploy() { - continue - } au := ad.NewApplyUtil(ctx, nil) h := utils2.NewHooksUtil(au) for _, o := range d.Objects { diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index d8449cd77..c37a9fc9d 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -34,11 +34,16 @@ func NewDeploymentCollection(ctx SharedContext, project *DeploymentProject, imag } indexes := make(map[string]int) - deployments, err := dc.collectDeployments(project, indexes) + deployments, err := dc.collectAllDeployments(project, indexes) if err != nil { return nil, err } - dc.Deployments = deployments + dc.Deployments = make([]*DeploymentItem, 0, len(deployments)) + for _, d := range deployments { + if d.CheckInclusionForDeploy() { + dc.Deployments = append(dc.Deployments, d) + } + } return dc, nil } @@ -75,7 +80,7 @@ func findDeploymentItemIndex(project *DeploymentProject, pth *string, indexes ma return index, dir2 } -func (c *DeploymentCollection) collectDeployments(project *DeploymentProject, indexes map[string]int) ([]*DeploymentItem, error) { +func (c *DeploymentCollection) collectAllDeployments(project *DeploymentProject, indexes map[string]int) ([]*DeploymentItem, error) { var ret []*DeploymentItem for i, diConfig := range project.Config.Deployments { @@ -84,7 +89,7 @@ func (c *DeploymentCollection) collectDeployments(project *DeploymentProject, in if !ok { panic(fmt.Sprintf("Did not find find index %d in project.includes", i)) } - ret2, err := c.collectDeployments(includedProject, indexes) + ret2, err := c.collectAllDeployments(includedProject, indexes) if err != nil { return nil, err } diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index bbd9665b6..6776bb7db 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -367,16 +367,11 @@ func (di *DeploymentItem) CheckInclusionForDeploy() bool { if di.Config.AlwaysDeploy { return true } - values := di.buildInclusionEntries() - return di.Inclusion.CheckIncluded(values, false) -} - -func (di *DeploymentItem) checkInclusionForDelete() bool { - if di.Inclusion == nil { + if di.Config.Barrier { return true } values := di.buildInclusionEntries() - return di.Inclusion.CheckIncluded(values, di.Config.SkipDeleteIfTags) + return di.Inclusion.CheckIncluded(values, false) } func (di *DeploymentItem) readKustomizationYaml(subDir string) (*uo.UnstructuredObject, error) { diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index 2bee3e50d..87767ad19 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -475,11 +475,6 @@ func (a *ApplyUtil) applyDeploymentItem(d *deployment.DeploymentItem) { // +1 to ensure that we don't prematurely complete the bar (which would happen as we don't count for waiting) total := len(applyObjects) + len(preHooks) + len(postHooks) + 1 a.sctx.SetTotal(total) - if !d.CheckInclusionForDeploy() { - a.sctx.UpdateAndInfoFallback("Skipped") - a.sctx.Warning() - return - } if len(toDelete) != 0 { a.sctx.InfoFallback("Deleting %d objects", len(toDelete)) diff --git a/pkg/deployment/utils/diff_utils.go b/pkg/deployment/utils/diff_utils.go index 3690ecdd8..77b1e31ad 100644 --- a/pkg/deployment/utils/diff_utils.go +++ b/pkg/deployment/utils/diff_utils.go @@ -42,10 +42,6 @@ func (u *diffUtil) Diff() { u.calcRemoteObjectsForDiff() for _, d := range u.deployments { - if !d.CheckInclusionForDeploy() { - continue - } - ignoreForDiffs := d.Project.GetIgnoreForDiffs(u.IgnoreTags, u.IgnoreLabels, u.IgnoreAnnotations) for _, o := range d.Objects { o := o From 553d8ea76cd019446421ae18274d7873d4e5829e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 9 Dec 2022 17:07:38 +0100 Subject: [PATCH 1296/2916] feat: Allow inclusion flags on render command --- cmd/kluctl/commands/cmd_render.go | 2 ++ docs/reference/commands/render.md | 1 + 2 files changed, 3 insertions(+) diff --git a/cmd/kluctl/commands/cmd_render.go b/cmd/kluctl/commands/cmd_render.go index 9c4152129..f8e252804 100644 --- a/cmd/kluctl/commands/cmd_render.go +++ b/cmd/kluctl/commands/cmd_render.go @@ -15,6 +15,7 @@ type renderCmd struct { args.TargetFlags args.ArgsFlags args.ImageFlags + args.InclusionFlags args.HelmCredentials args.RenderOutputDirFlags args.OfflineKubernetesFlags @@ -43,6 +44,7 @@ func (cmd *renderCmd) Run(ctx context.Context) error { targetFlags: cmd.TargetFlags, argsFlags: cmd.ArgsFlags, imageFlags: cmd.ImageFlags, + inclusionFlags: cmd.InclusionFlags, helmCredentials: cmd.HelmCredentials, renderOutputDirFlags: cmd.RenderOutputDirFlags, offlineKubernetes: cmd.OfflineKubernetes, diff --git a/docs/reference/commands/render.md b/docs/reference/commands/render.md index 3556e40c0..5681e0f57 100644 --- a/docs/reference/commands/render.md +++ b/docs/reference/commands/render.md @@ -23,6 +23,7 @@ a temporary directory or a specified directory. The following sets of arguments are available: 1. [project arguments](./common-arguments.md#project-arguments) 1. [image arguments](./common-arguments.md#image-arguments) +1. [inclusion/exclusion arguments](./common-arguments.md#inclusionexclusion-arguments) In addition, the following arguments are available: From 0275419164233600a55c940017695b872eb5313d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 9 Dec 2022 17:57:37 +0100 Subject: [PATCH 1297/2916] feat: Allow to ignore missing variable sources --- docs/reference/templating/variable-sources.md | 5 + pkg/types/vars_source.go | 5 + pkg/vars/aws/fake_clientfactory.go | 3 +- pkg/vars/vars_loader.go | 66 ++++++--- pkg/vars/vars_loader_http.go | 11 +- pkg/vars/vars_loader_test.go | 132 +++++++++++++++++- pkg/vars/vault/secrets.go | 11 +- 7 files changed, 204 insertions(+), 29 deletions(-) diff --git a/docs/reference/templating/variable-sources.md b/docs/reference/templating/variable-sources.md index 2f4a4137f..43c03e071 100644 --- a/docs/reference/templating/variable-sources.md +++ b/docs/reference/templating/variable-sources.md @@ -27,11 +27,16 @@ following example. vars: - file: vars1.yaml - file: vars2.yaml +- file: optional-vars.yaml + ignoreMissing: true ``` `vars2.yaml` can now use variables that are defined in `vars1.yaml`. At all times, variables defined by parents of the current sub-deployment project can be used in the current vars file. +Each variable source can have the optional field `ignoreMissing` set to `true`, causing Kluctl to ignore if the source +can not be found. + Different types of vars entries are possible: ### file diff --git a/pkg/types/vars_source.go b/pkg/types/vars_source.go index af8a235cc..c32578469 100644 --- a/pkg/types/vars_source.go +++ b/pkg/types/vars_source.go @@ -54,6 +54,8 @@ type VarsSourceVault struct { } type VarsSource struct { + IgnoreMissing *bool `yaml:"ignoreMissing,omitempty"` + Values *uo.UnstructuredObject `yaml:"values,omitempty"` File *string `yaml:"file,omitempty"` Git *VarsSourceGit `yaml:"git,omitempty"` @@ -71,6 +73,9 @@ func ValidateVarsSource(sl validator.StructLevel) { count := 0 v := reflect.ValueOf(s) for i := 0; i < v.NumField(); i++ { + if v.Type().Field(i).Name == "IgnoreMissing" { + continue + } if !v.Field(i).IsNil() { count += 1 } diff --git a/pkg/vars/aws/fake_clientfactory.go b/pkg/vars/aws/fake_clientfactory.go index 219dc1259..d06d68fd5 100644 --- a/pkg/vars/aws/fake_clientfactory.go +++ b/pkg/vars/aws/fake_clientfactory.go @@ -2,6 +2,7 @@ package aws import ( "fmt" + "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/secretsmanager" "github.com/aws/aws-sdk-go/service/secretsmanager/secretsmanageriface" ) @@ -27,7 +28,7 @@ func (f *FakeAwsClientFactory) GetSecretValue(in *secretsmanager.GetSecretValueI }, nil } - return nil, fmt.Errorf("secret %s not found", *in.SecretId) + return nil, awserr.New(secretsmanager.ErrCodeResourceNotFoundException, fmt.Sprintf("secret %s not found", *in.SecretId), nil) } func (f *FakeAwsClientFactory) SecretsManagerClient(profile *string, region *string) (secretsmanageriface.SecretsManagerAPI, error) { diff --git a/pkg/vars/vars_loader.go b/pkg/vars/vars_loader.go index 6ead52755..40e0bea86 100644 --- a/pkg/vars/vars_loader.go +++ b/pkg/vars/vars_loader.go @@ -3,7 +3,10 @@ package vars import ( "context" "encoding/base64" + errors2 "errors" "fmt" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/secretsmanager" "github.com/kluctl/go-jinja2" "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/repocache" @@ -16,6 +19,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/vars/vault" "github.com/kluctl/kluctl/v2/pkg/yaml" "go.mozilla.org/sops/v3/cmd/sops/formats" + "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime/schema" "os" "strings" @@ -74,25 +78,30 @@ func (v *VarsLoader) LoadVars(varsCtx *VarsCtx, sourceIn *types.VarsSource, sear return err } + ignoreMissing := false + if source.IgnoreMissing != nil { + ignoreMissing = *source.IgnoreMissing + } + if source.Values != nil { v.mergeVars(varsCtx, source.Values, rootKey) return nil } else if source.File != nil { - return v.loadFile(varsCtx, *source.File, searchDirs, rootKey) + return v.loadFile(varsCtx, *source.File, ignoreMissing, searchDirs, rootKey) } else if source.Git != nil { - return v.loadGit(varsCtx, source.Git, rootKey) + return v.loadGit(varsCtx, source.Git, ignoreMissing, rootKey) } else if source.ClusterConfigMap != nil { - return v.loadFromK8sObject(varsCtx, *source.ClusterConfigMap, "ConfigMap", source.ClusterConfigMap.Key, rootKey, false) + return v.loadFromK8sObject(varsCtx, *source.ClusterConfigMap, "ConfigMap", source.ClusterConfigMap.Key, rootKey, ignoreMissing, false) } else if source.ClusterSecret != nil { - return v.loadFromK8sObject(varsCtx, *source.ClusterSecret, "Secret", source.ClusterSecret.Key, rootKey, true) + return v.loadFromK8sObject(varsCtx, *source.ClusterSecret, "Secret", source.ClusterSecret.Key, rootKey, ignoreMissing, true) } else if source.SystemEnvVars != nil { - return v.loadSystemEnvs(varsCtx, &source, rootKey) + return v.loadSystemEnvs(varsCtx, &source, ignoreMissing, rootKey) } else if source.Http != nil { - return v.loadHttp(varsCtx, &source, rootKey) + return v.loadHttp(varsCtx, &source, ignoreMissing, rootKey) } else if source.AwsSecretsManager != nil { - return v.loadAwsSecretsManager(varsCtx, &source, rootKey) + return v.loadAwsSecretsManager(varsCtx, &source, ignoreMissing, rootKey) } else if source.Vault != nil { - return v.loadVault(varsCtx, &source, rootKey) + return v.loadVault(varsCtx, &source, ignoreMissing, rootKey) } return fmt.Errorf("invalid vars source") } @@ -105,7 +114,11 @@ func (v *VarsLoader) mergeVars(varsCtx *VarsCtx, newVars *uo.UnstructuredObject, } } -func (v *VarsLoader) loadFile(varsCtx *VarsCtx, path string, searchDirs []string, rootKey string) error { +func (v *VarsLoader) loadFile(varsCtx *VarsCtx, path string, ignoreMissing bool, searchDirs []string, rootKey string) error { + if ignoreMissing && !utils.Exists(path) { + return nil + } + rendered, err := varsCtx.RenderFile(path, searchDirs) if err != nil { return fmt.Errorf("failed to render vars file %s: %w", path, err) @@ -139,7 +152,7 @@ func (v *VarsLoader) loadFile(varsCtx *VarsCtx, path string, searchDirs []string return nil } -func (v *VarsLoader) loadSystemEnvs(varsCtx *VarsCtx, source *types.VarsSource, rootKey string) error { +func (v *VarsLoader) loadSystemEnvs(varsCtx *VarsCtx, source *types.VarsSource, ignoreMissing bool, rootKey string) error { newVars := uo.New() err := source.SystemEnvVars.NewIterator().IterateLeafs(func(it *uo.ObjectIterator) error { envName, ok := it.Value().(string) @@ -164,6 +177,9 @@ func (v *VarsLoader) loadSystemEnvs(varsCtx *VarsCtx, source *types.VarsSource, envValueStr = `""` } } else { + if ignoreMissing { + return nil + } return fmt.Errorf("environment variable %s not found for %s", envName, it.KeyPath().ToJsonPath()) } @@ -186,27 +202,39 @@ func (v *VarsLoader) loadSystemEnvs(varsCtx *VarsCtx, source *types.VarsSource, return nil } -func (v *VarsLoader) loadAwsSecretsManager(varsCtx *VarsCtx, source *types.VarsSource, rootKey string) error { +func (v *VarsLoader) loadAwsSecretsManager(varsCtx *VarsCtx, source *types.VarsSource, ignoreMissing bool, rootKey string) error { if v.aws == nil { return fmt.Errorf("no AWS client factory provided") } secret, err := aws.GetAwsSecretsManagerSecret(v.aws, source.AwsSecretsManager.Profile, source.AwsSecretsManager.Region, source.AwsSecretsManager.SecretName) if err != nil { + var aerr awserr.Error + if errors2.As(err, &aerr) { + if ignoreMissing && aerr.Code() == secretsmanager.ErrCodeResourceNotFoundException { + return nil + } + } return err } return v.loadFromString(varsCtx, secret, "awsSecretsManager", rootKey) } -func (v *VarsLoader) loadVault(varsCtx *VarsCtx, source *types.VarsSource, rootKey string) error { +func (v *VarsLoader) loadVault(varsCtx *VarsCtx, source *types.VarsSource, ignoreMissing bool, rootKey string) error { secret, err := vault.GetSecret(source.Vault.Address, source.Vault.Path) if err != nil { return err } - return v.loadFromString(varsCtx, secret, "vault", rootKey) + if secret == nil { + if ignoreMissing { + return nil + } + return fmt.Errorf("the specified vault secret was not found") + } + return v.loadFromString(varsCtx, *secret, "vault", rootKey) } -func (v *VarsLoader) loadGit(varsCtx *VarsCtx, gitFile *types.VarsSourceGit, rootKey string) error { +func (v *VarsLoader) loadGit(varsCtx *VarsCtx, gitFile *types.VarsSourceGit, ignoreMissing bool, rootKey string) error { ge, err := v.rp.GetEntry(gitFile.Url) if err != nil { return err @@ -217,10 +245,10 @@ func (v *VarsLoader) loadGit(varsCtx *VarsCtx, gitFile *types.VarsSourceGit, roo return fmt.Errorf("failed to load vars from git repository %s: %w", gitFile.Url.String(), err) } - return v.loadFile(varsCtx, gitFile.Path, []string{clonedDir}, rootKey) + return v.loadFile(varsCtx, gitFile.Path, ignoreMissing, []string{clonedDir}, rootKey) } -func (v *VarsLoader) loadFromK8sObject(varsCtx *VarsCtx, varsSource types.VarsSourceClusterConfigMapOrSecret, kind string, key string, rootKey string, base64Decode bool) error { +func (v *VarsLoader) loadFromK8sObject(varsCtx *VarsCtx, varsSource types.VarsSourceClusterConfigMapOrSecret, kind string, key string, rootKey string, ignoreMissing bool, base64Decode bool) error { if v.k == nil { return fmt.Errorf("loading vars from cluster is disabled") } @@ -231,6 +259,9 @@ func (v *VarsLoader) loadFromK8sObject(varsCtx *VarsCtx, varsSource types.VarsSo if varsSource.Name != "" { o, _, err = v.k.GetSingleObject(k8s2.NewObjectRef("", "v1", kind, varsSource.Name, varsSource.Namespace)) if err != nil { + if ignoreMissing && errors.IsNotFound(err) { + return nil + } return err } } else { @@ -243,6 +274,9 @@ func (v *VarsLoader) loadFromK8sObject(varsCtx *VarsCtx, varsSource types.VarsSo return err } if len(objs) == 0 { + if ignoreMissing { + return nil + } return fmt.Errorf("no object found with labels %v", varsSource.Labels) } if len(objs) > 1 { diff --git a/pkg/vars/vars_loader_http.go b/pkg/vars/vars_loader_http.go index 2f70cd260..7de4ca0a2 100644 --- a/pkg/vars/vars_loader_http.go +++ b/pkg/vars/vars_loader_http.go @@ -14,7 +14,7 @@ import ( "strings" ) -func (v *VarsLoader) doHttp(httpSource *types.VarsSourceHttp, username string, password string) (*http.Response, string, error) { +func (v *VarsLoader) doHttp(httpSource *types.VarsSourceHttp, ignoreMissing bool, username string, password string) (*http.Response, string, error) { client := &http.Client{ Transport: ntlmssp.Negotiator{ RoundTripper: &http.Transport{ @@ -64,8 +64,8 @@ func (v *VarsLoader) doHttp(httpSource *types.VarsSourceHttp, username string, p return resp, string(respBody), nil } -func (v *VarsLoader) loadHttp(varsCtx *VarsCtx, source *types.VarsSource, rootKey string) error { - resp, respBody, err := v.doHttp(source.Http, "", "") +func (v *VarsLoader) loadHttp(varsCtx *VarsCtx, source *types.VarsSource, ignoreMissing bool, rootKey string) error { + resp, respBody, err := v.doHttp(source.Http, ignoreMissing, "", "") if err != nil && resp != nil && resp.StatusCode == http.StatusUnauthorized { chgs := challenge.ResponseChallenges(resp) if len(chgs) == 0 { @@ -95,11 +95,14 @@ func (v *VarsLoader) loadHttp(varsCtx *VarsCtx, source *types.VarsSource, rootKe v.credentialsCache[credsKey] = creds } - resp, respBody, err = v.doHttp(source.Http, creds.username, creds.password) + resp, respBody, err = v.doHttp(source.Http, ignoreMissing, creds.username, creds.password) if err != nil { return err } } else if err != nil { + if ignoreMissing && resp != nil && resp.StatusCode == http.StatusNotFound { + return nil + } return err } diff --git a/pkg/vars/vars_loader_test.go b/pkg/vars/vars_loader_test.go index ecf2e006e..b4d4a0f38 100644 --- a/pkg/vars/vars_loader_test.go +++ b/pkg/vars/vars_loader_test.go @@ -27,6 +27,7 @@ import ( "net/url" "os" "path/filepath" + "strings" "testing" ) @@ -88,6 +89,15 @@ func TestVarsLoader_File(t *testing.T) { v, _, _ := vc.Vars.GetNestedInt("test1", "test2") assert.Equal(t, int64(42), v) }) + + testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { + b := true + err := vl.LoadVars(vc, &types.VarsSource{ + IgnoreMissing: &b, + File: utils.StrPtr("test-missing.yaml"), + }, []string{d}, "") + assert.NoError(t, err) + }) } func TestVarsLoader_SopsFile(t *testing.T) { @@ -177,6 +187,19 @@ func TestVarsLoader_Git(t *testing.T) { v, _, _ := vc.Vars.GetNestedInt("test1", "test2") assert.Equal(t, int64(42), v) }) + + testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { + url, _ := git_url.Parse(gs.GitUrl("repo")) + b := true + err := vl.LoadVars(vc, &types.VarsSource{ + IgnoreMissing: &b, + Git: &types.VarsSourceGit{ + Url: *url, + Path: "test-missing.yaml", + }, + }, nil, "") + assert.NoError(t, err) + }) } func TestVarsLoader_GitBranch(t *testing.T) { @@ -259,6 +282,19 @@ func TestVarsLoader_ClusterConfigMap(t *testing.T) { }, nil, "") assert.EqualError(t, err, "key vars1 not found in ns/ConfigMap/cm on cluster") }, &cm) + + testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { + b := true + err := vl.LoadVars(vc, &types.VarsSource{ + IgnoreMissing: &b, + ClusterConfigMap: &types.VarsSourceClusterConfigMapOrSecret{ + Name: "cm-missing", + Namespace: "ns", + Key: "vars", + }, + }, nil, "") + assert.NoError(t, err) + }, &cm) } func TestVarsLoader_ClusterSecret(t *testing.T) { @@ -302,6 +338,19 @@ func TestVarsLoader_ClusterSecret(t *testing.T) { }, nil, "") assert.EqualError(t, err, "key vars1 not found in ns/Secret/s on cluster") }, &secret) + + testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { + b := true + err := vl.LoadVars(vc, &types.VarsSource{ + IgnoreMissing: &b, + ClusterSecret: &types.VarsSourceClusterConfigMapOrSecret{ + Name: "s-missing", + Namespace: "ns", + Key: "vars", + }, + }, nil, "") + assert.NoError(t, err) + }, &secret) } func TestVarsLoader_K8sObjectLabels(t *testing.T) { @@ -345,6 +394,19 @@ func TestVarsLoader_K8sObjectLabels(t *testing.T) { v, _, _ := vc.Vars.GetNestedInt("test3", "test4") assert.Equal(t, int64(43), v) }, &cm1, &cm2) + + testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { + b := true + err := vl.LoadVars(vc, &types.VarsSource{ + IgnoreMissing: &b, + ClusterConfigMap: &types.VarsSourceClusterConfigMapOrSecret{ + Labels: map[string]string{"label2": "value-missing"}, + Namespace: "ns", + Key: "vars", + }, + }, nil, "") + assert.NoError(t, err) + }, &cm1, &cm2) } func TestVarsLoader_SystemEnv(t *testing.T) { @@ -398,17 +460,38 @@ func TestVarsLoader_SystemEnv(t *testing.T) { }, nil, "") assert.EqualError(t, err, "environment variable TEST5 not found for test5") }) + + testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { + b := true + err := vl.LoadVars(vc, &types.VarsSource{ + IgnoreMissing: &b, + SystemEnvVars: uo.FromMap(map[string]interface{}{ + "test1": "TEST-MISSING", + }), + }, nil, "") + assert.NoError(t, err) + + _, ok, _ := vc.Vars.GetNestedField("test1") + assert.False(t, ok) + }) } func TestVarsLoader_Http_GET(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - _, _ = w.Write([]byte(`{"test1": {"test2": 42}}`)) + if strings.HasSuffix(r.URL.Path, "/ok") { + _, _ = w.Write([]byte(`{"test1": {"test2": 42}}`)) + } else if strings.HasSuffix(r.URL.Path, "/error") { + http.Error(w, "error", http.StatusInternalServerError) + } else { + http.NotFound(w, r) + } })) defer ts.Close() - u, _ := url.Parse(ts.URL) - testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { + u, _ := url.Parse(ts.URL) + u.Path += "/ok" + err := vl.LoadVars(vc, &types.VarsSource{ Http: &types.VarsSourceHttp{ Url: types.YamlUrl{URL: *u}, @@ -419,6 +502,33 @@ func TestVarsLoader_Http_GET(t *testing.T) { v, _, _ := vc.Vars.GetNestedInt("test1", "test2") assert.Equal(t, int64(42), v) }) + + testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { + b := true + u, _ := url.Parse(ts.URL) + u.Path += "/missing" + err := vl.LoadVars(vc, &types.VarsSource{ + IgnoreMissing: &b, + Http: &types.VarsSourceHttp{ + Url: types.YamlUrl{URL: *u}, + }, + }, nil, "") + assert.NoError(t, err) + }) + + testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { + b := true + u, _ := url.Parse(ts.URL) + u.Path += "/error" + err := vl.LoadVars(vc, &types.VarsSource{ + IgnoreMissing: &b, + Http: &types.VarsSourceHttp{ + Url: types.YamlUrl{URL: *u}, + }, + }, nil, "") + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed with status code 500") + }) } func TestVarsLoader_Http_POST(t *testing.T) { @@ -543,4 +653,20 @@ func TestVarsLoader_AwsSecretsManager(t *testing.T) { v, _, _ := vc.Vars.GetNestedInt("test1", "test2") assert.Equal(t, int64(42), v) }) + + testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { + aws.Secrets = map[string]string{ + "secret": `{"test1": {"test2": 42}}`, + } + + b := true + err := vl.LoadVars(vc, &types.VarsSource{ + IgnoreMissing: &b, + AwsSecretsManager: &types.VarsSourceAwsSecretsManager{ + SecretName: "missing", + Region: utils.StrPtr("eu-central1"), + }, + }, nil, "") + assert.NoError(t, err) + }) } diff --git a/pkg/vars/vault/secrets.go b/pkg/vars/vault/secrets.go index 49f04618f..7d4fb9012 100644 --- a/pkg/vars/vault/secrets.go +++ b/pkg/vars/vault/secrets.go @@ -13,19 +13,20 @@ var httpClient = &http.Client{ Timeout: 15 * time.Second, } -func GetSecret(server string, path string) (string, error) { +func GetSecret(server string, path string) (*string, error) { client, err := api.NewClient(&api.Config{Address: server, HttpClient: httpClient}) if err != nil { - return "", fmt.Errorf("failed to create vault %s client", server) + return nil, fmt.Errorf("failed to create vault %s client", server) } secret, err := client.Logical().Read(path) if err != nil { - return "", fmt.Errorf("reading from vault failed: %v", err) + return nil, fmt.Errorf("reading from vault failed: %v", err) } if secret == nil || secret.Data == nil { - return "", fmt.Errorf("the specified vault secret was not found") + return nil, nil } data, _ := secret.Data["data"].(map[string]interface{}) jsonData, _ := json.Marshal(data) - return string(jsonData), nil + ret := string(jsonData) + return &ret, nil } From 63182574f360610bceebaa86cd0dad9a303f3b65 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Dec 2022 22:08:14 +0000 Subject: [PATCH 1298/2916] chore(deps): Bump github.com/aws/aws-sdk-go from 1.44.154 to 1.44.157 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.154 to 1.44.157. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.154...v1.44.157) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ab79f6f8b..957530805 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e github.com/Masterminds/semver/v3 v3.2.0 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d - github.com/aws/aws-sdk-go v1.44.154 + github.com/aws/aws-sdk-go v1.44.157 github.com/bitnami-labs/sealed-secrets v0.19.2 github.com/cyphar/filepath-securejoin v0.2.3 github.com/docker/distribution v2.8.1+incompatible diff --git a/go.sum b/go.sum index 4b69beba1..c627a02b2 100644 --- a/go.sum +++ b/go.sum @@ -131,8 +131,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/aws/aws-sdk-go v1.44.154 h1:2TaEC8JIM/t7Fu+FBmdOBK5jFUAVL07tbpfJsLuVo3Q= -github.com/aws/aws-sdk-go v1.44.154/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.157 h1:JVBPpEWC8+yA7CbfAuTl/ZFFlHS3yoqWFqxFyTCISwg= +github.com/aws/aws-sdk-go v1.44.157/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= From aae82d0e28b2eaad9028aeef489d59eccd5b8d58 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Dec 2022 22:08:33 +0000 Subject: [PATCH 1299/2916] chore(deps): Bump k8s.io/api from 0.25.4 to 0.26.0 Bumps [k8s.io/api](https://github.com/kubernetes/api) from 0.25.4 to 0.26.0. - [Release notes](https://github.com/kubernetes/api/releases) - [Commits](https://github.com/kubernetes/api/compare/v0.25.4...v0.26.0) --- updated-dependencies: - dependency-name: k8s.io/api dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index ab79f6f8b..79fcb8c5e 100644 --- a/go.mod +++ b/go.mod @@ -47,9 +47,9 @@ require ( gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 helm.sh/helm/v3 v3.10.2 - k8s.io/api v0.25.4 + k8s.io/api v0.26.0 k8s.io/apiextensions-apiserver v0.25.4 - k8s.io/apimachinery v0.25.4 + k8s.io/apimachinery v0.26.0 k8s.io/client-go v0.25.4 k8s.io/klog/v2 v2.80.1 sigs.k8s.io/kustomize/api v0.12.1 // indirect diff --git a/go.sum b/go.sum index 4b69beba1..9b23f2e91 100644 --- a/go.sum +++ b/go.sum @@ -1331,12 +1331,12 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.25.4 h1:3YO8J4RtmG7elEgaWMb4HgmpS2CfY1QlaOz9nwB+ZSs= -k8s.io/api v0.25.4/go.mod h1:IG2+RzyPQLllQxnhzD8KQNEu4c4YvyDTpSMztf4A0OQ= +k8s.io/api v0.26.0 h1:IpPlZnxBpV1xl7TGk/X6lFtpgjgntCg8PJ+qrPHAC7I= +k8s.io/api v0.26.0/go.mod h1:k6HDTaIFC8yn1i6pSClSqIwLABIcLV9l5Q4EcngKnQg= k8s.io/apiextensions-apiserver v0.25.4 h1:7hu9pF+xikxQuQZ7/30z/qxIPZc2J1lFElPtr7f+B6U= k8s.io/apiextensions-apiserver v0.25.4/go.mod h1:bkSGki5YBoZWdn5pWtNIdGvDrrsRWlmnvl9a+tAw5vQ= -k8s.io/apimachinery v0.25.4 h1:CtXsuaitMESSu339tfhVXhQrPET+EiWnIY1rcurKnAc= -k8s.io/apimachinery v0.25.4/go.mod h1:jaF9C/iPNM1FuLl7Zuy5b9v+n35HGSh6AQ4HYRkCqwo= +k8s.io/apimachinery v0.26.0 h1:1feANjElT7MvPqp0JT6F3Ss6TWDwmcjLypwoPpEf7zg= +k8s.io/apimachinery v0.26.0/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74= k8s.io/apiserver v0.25.4 h1:/3TwZcgLqX7wUxq7TtXOUqXeBTwXIblVMQdhR5XZ7yo= k8s.io/apiserver v0.25.4/go.mod h1:rPcm567XxjOnnd7jedDUnGJGmDGAo+cT6H7QHAN+xV0= k8s.io/cli-runtime v0.25.3 h1:Zs7P7l7db/5J+KDePOVtDlArAa9pZXaDinGWGZl0aM8= From 096659c65d305a78f3ddbc2e956f4907eba642c2 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sat, 10 Dec 2022 00:50:36 +0100 Subject: [PATCH 1300/2916] tests: Actually collect and return stdout --- e2e/test-utils/project.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/e2e/test-utils/project.go b/e2e/test-utils/project.go index 9a87be0f1..441d8250c 100644 --- a/e2e/test-utils/project.go +++ b/e2e/test-utils/project.go @@ -422,22 +422,28 @@ func (p *TestProject) KluctlExecute(argsIn ...string) (string, string, error) { p.t.Logf("Runnning kluctl: %s", strings.Join(args, " ")) var m sync.Mutex - stdout := bytes.NewBuffer(nil) - stderr := bytes.NewBuffer(nil) + stdoutBuf := bytes.NewBuffer(nil) + stdout := status.NewLineRedirector(func(line string) { + m.Lock() + defer m.Unlock() + p.t.Log(line) + stdoutBuf.WriteString(line + "\n") + }) + stderrBuf := bytes.NewBuffer(nil) ctx := context.Background() ctx = utils.WithTmpBaseDir(ctx, p.t.TempDir()) - ctx = commands.WithStdStreams(ctx, stdout, stderr) + ctx = commands.WithStdStreams(ctx, stdout, stderrBuf) sh := status.NewSimpleStatusHandler(func(message string) { m.Lock() defer m.Unlock() p.t.Log(message) - stderr.WriteString(message + "\n") + stderrBuf.WriteString(message + "\n") }, false, true) defer sh.Stop() ctx = status.NewContext(ctx, sh) err := commands.Execute(ctx, args, nil) - return stdout.String(), stderr.String(), err + return stdoutBuf.String(), stderrBuf.String(), err } func (p *TestProject) Kluctl(argsIn ...string) (string, string, error) { From 9f7d5cc8937f46e420340e48aeabbdae357820ff Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sat, 10 Dec 2022 00:51:17 +0100 Subject: [PATCH 1301/2916] feat: Don't force-replace objects marked with kluctl.io/skip-delete --- e2e/skip_delete_test.go | 132 +++++++++++++++++++++++++++ e2e/utils.go | 10 ++ pkg/deployment/utils/apply_utils.go | 16 +++- pkg/deployment/utils/delete_utils.go | 26 ++++-- 4 files changed, 171 insertions(+), 13 deletions(-) create mode 100644 e2e/skip_delete_test.go diff --git a/e2e/skip_delete_test.go b/e2e/skip_delete_test.go new file mode 100644 index 000000000..ba7802e2e --- /dev/null +++ b/e2e/skip_delete_test.go @@ -0,0 +1,132 @@ +package e2e + +import ( + test_utils "github.com/kluctl/kluctl/v2/e2e/test-utils" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/stretchr/testify/assert" + "strings" + "testing" +) + +func TestSkipDelete(t *testing.T) { + t.Parallel() + + k := defaultCluster1 + + p := test_utils.NewTestProject(t) + + createNamespace(t, k, p.TestSlug()) + + p.UpdateTarget("test", nil) + + addConfigMapDeployment(p, "cm1", map[string]string{}, resourceOpts{ + name: "cm1", + namespace: p.TestSlug(), + }) + addConfigMapDeployment(p, "cm2", map[string]string{}, resourceOpts{ + name: "cm2", + namespace: p.TestSlug(), + }) + addConfigMapDeployment(p, "cm3", map[string]string{}, resourceOpts{ + name: "cm3", + namespace: p.TestSlug(), + annotations: map[string]string{ + "kluctl.io/skip-delete": "true", + }, + }) + addConfigMapDeployment(p, "cm4", map[string]string{}, resourceOpts{ + name: "cm4", + namespace: p.TestSlug(), + annotations: map[string]string{ + "helm.sh/resource-policy": "keep", + }, + }) + + p.KluctlMust("deploy", "--yes", "-t", "test") + assertConfigMapExists(t, k, p.TestSlug(), "cm1") + cm2 := assertConfigMapExists(t, k, p.TestSlug(), "cm2") + assertConfigMapExists(t, k, p.TestSlug(), "cm3") + assertConfigMapExists(t, k, p.TestSlug(), "cm4") + + cm2.SetK8sAnnotation("kluctl.io/skip-delete", "true") + updateObject(t, k, cm2) + + p.KluctlMust("delete", "--yes", "-t", "test") + assertConfigMapNotExists(t, k, p.TestSlug(), "cm1") + cm2 = assertConfigMapExists(t, k, p.TestSlug(), "cm2") + assertConfigMapExists(t, k, p.TestSlug(), "cm3") + assertConfigMapExists(t, k, p.TestSlug(), "cm4") + + p.KluctlMust("deploy", "--yes", "-t", "test") + cm1 := assertConfigMapExists(t, k, p.TestSlug(), "cm1") + cm1.SetK8sAnnotation("kluctl.io/skip-delete", "true") + cm2.SetK8sAnnotation("kluctl.io/skip-delete", "false") + updateObject(t, k, cm1) + updateObject(t, k, cm2) + p.DeleteKustomizeDeployment("cm1") + p.DeleteKustomizeDeployment("cm2") + p.DeleteKustomizeDeployment("cm3") + p.KluctlMust("prune", "--yes", "-t", "test") + cm1 = assertConfigMapExists(t, k, p.TestSlug(), "cm1") + assertConfigMapNotExists(t, k, p.TestSlug(), "cm2") + assertConfigMapExists(t, k, p.TestSlug(), "cm3") + assertConfigMapExists(t, k, p.TestSlug(), "cm4") +} + +func TestForceReplaceSkipDelete(t *testing.T) { + t.Parallel() + + k := defaultCluster1 + + p := test_utils.NewTestProject(t) + + createNamespace(t, k, p.TestSlug()) + + p.UpdateTarget("test", nil) + + addConfigMapDeployment(p, "cm1", map[string]string{ + "k1": "v1", + }, resourceOpts{ + name: "cm1", + namespace: p.TestSlug(), + annotations: map[string]string{ + "kluctl.io/skip-delete": "true", + }, + }) + + p.KluctlMust("deploy", "--yes", "-t", "test") + cm1 := assertConfigMapExists(t, k, p.TestSlug(), "cm1") + assert.Equal(t, map[string]any{ + "k1": "v1", + }, cm1.Object["data"]) + + p.UpdateYaml("cm1/configmap-cm1.yml", func(o *uo.UnstructuredObject) error { + _ = o.SetNestedField("v2", "data", "k1") + return nil + }, "") + + p.KluctlMust("deploy", "--yes", "-t", "test") + cm1 = assertConfigMapExists(t, k, p.TestSlug(), "cm1") + assert.Equal(t, map[string]any{ + "k1": "v2", + }, cm1.Object["data"]) + + invalidLabel := "invalid_label_" + strings.Repeat("x", 63) + p.UpdateYaml("cm1/configmap-cm1.yml", func(o *uo.UnstructuredObject) error { + o.SetK8sLabel(invalidLabel, "invalid_label") + return nil + }, "") + stdout, _, err := p.Kluctl("deploy", "--yes", "-t", "test") + assert.Error(t, err) + assert.Contains(t, stdout, "invalid: metadata.labels") + // make sure it did not try to replace it + assertConfigMapExists(t, k, p.TestSlug(), "cm1") + + stdout, stderr, err := p.Kluctl("deploy", "--yes", "-t", "test", "--force-replace-on-error") + assert.Error(t, err) + assert.Contains(t, stdout, "retrying with replace instead of patch") + assert.Contains(t, stderr, "skipped forced replace") + assert.Contains(t, stdout, "invalid: metadata.labels") + // make sure it did not try to replace it + assertConfigMapExists(t, k, p.TestSlug(), "cm1") +} diff --git a/e2e/utils.go b/e2e/utils.go index 5d4a234a0..1e3b6c61b 100644 --- a/e2e/utils.go +++ b/e2e/utils.go @@ -4,10 +4,12 @@ import ( "context" "github.com/kluctl/kluctl/v2/e2e/test-utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/stretchr/testify/assert" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" "reflect" "testing" ) @@ -53,3 +55,11 @@ func assertNestedFieldEquals(t *testing.T, o *uo.UnstructuredObject, expected in t.Fatalf("%v != %v", v, expected) } } + +func updateObject(t *testing.T, k *test_utils.EnvTestCluster, o *uo.UnstructuredObject) { + _, err := k.DynamicClient.Resource(schema.GroupVersionResource{ + Version: "v1", + Resource: "configmaps", + }).Namespace(o.GetK8sNamespace()).Update(context.Background(), o.ToUnstructured(), metav1.UpdateOptions{}) + assert.NoError(t, err) +} diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index 2bee3e50d..78bc51836 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -180,7 +180,7 @@ func (a *ApplyUtil) DeleteObject(ref k8s2.ObjectRef, hook bool) bool { return true } -func (a *ApplyUtil) retryApplyForceReplace(x *uo.UnstructuredObject, hook bool, applyError error) { +func (a *ApplyUtil) retryApplyForceReplace(x *uo.UnstructuredObject, hook bool, remoteObject *uo.UnstructuredObject, applyError error) { ref := x.GetK8sRef() if !a.o.ForceReplaceOnError { @@ -188,6 +188,16 @@ func (a *ApplyUtil) retryApplyForceReplace(x *uo.UnstructuredObject, hook bool, return } + skipDelete := isSkipDelete(x) + if remoteObject != nil { + skipDelete = skipDelete || isSkipDelete(remoteObject) + } + if skipDelete { + status.Warning(a.ctx, "skipped forced replace of %s", ref.String()) + a.HandleError(ref, applyError) + return + } + warn := fmt.Errorf("patching %s failed, retrying by deleting and re-applying", ref.String()) a.HandleWarning(ref, warn) status.Warning(a.ctx, warn.Error()) @@ -235,7 +245,7 @@ func (a *ApplyUtil) retryApplyWithReplace(x *uo.UnstructuredObject, hook bool, r r, apiWarnings, err := a.k.UpdateObject(x, o) a.handleApiWarnings(ref, apiWarnings) if err != nil { - a.retryApplyForceReplace(x, hook, err) + a.retryApplyForceReplace(x, hook, remoteObject, err) return } a.handleResult(r, hook) @@ -334,8 +344,6 @@ func (a *ApplyUtil) ApplyObject(x *uo.UnstructuredObject, replaced bool, hook bo a.HandleError(ref, err) } else if errors.IsConflict(err) { a.retryApplyWithConflicts(x, hook, remoteObject, err) - } else if errors.IsInternalError(err) { - a.HandleError(ref, err) } else { a.retryApplyWithReplace(x, hook, remoteObject, err) } diff --git a/pkg/deployment/utils/delete_utils.go b/pkg/deployment/utils/delete_utils.go index babaa5a4b..d79f5d229 100644 --- a/pkg/deployment/utils/delete_utils.go +++ b/pkg/deployment/utils/delete_utils.go @@ -47,6 +47,22 @@ func objectRefForExclusion(k *k8s.K8sCluster, ref k8s2.ObjectRef) k8s2.ObjectRef return ref } +func isSkipDelete(o *uo.UnstructuredObject) bool { + if utils.ParseBoolOrFalse(o.GetK8sAnnotation("kluctl.io/skip-delete")) { + return true + } + + helmResourcePolicy := o.GetK8sAnnotation("helm.sh/resource-policy") + if helmResourcePolicy != nil && *helmResourcePolicy == "keep" { + // warning, this is currently not how Helm handles it. Helm will only respect annotations set in the Chart + // itself, while Kluctl will also respect it when manually set on the live resource + // See: https://github.com/helm/helm/issues/8132 + return true + } + + return false +} + func filterObjectsForDelete(k *k8s.K8sCluster, objects []*uo.UnstructuredObject, apiFilter []string, inclusionHasTags bool, excludedObjects map[k8s2.ObjectRef]bool) ([]*uo.UnstructuredObject, error) { filterFunc := func(ar *v1.APIResource) bool { if len(apiFilter) == 0 { @@ -77,15 +93,7 @@ func filterObjectsForDelete(k *k8s.K8sCluster, objects []*uo.UnstructuredObject, managedFields := o.GetK8sManagedFields() // exclude when explicitly requested - if utils.ParseBoolOrFalse(o.GetK8sAnnotation("kluctl.io/skip-delete")) { - continue - } - - helmResourcePolicy := o.GetK8sAnnotation("helm.sh/resource-policy") - if helmResourcePolicy != nil && *helmResourcePolicy == "keep" { - // warning, this is currently not how Helm handles it. Helm will only respect annotations set in the Chart - // itself, while Kluctl will also respect it when manually set on the live resource - // See: https://github.com/helm/helm/issues/8132 + if isSkipDelete(o) { continue } From 5f0338566fb26048e198b9c99acd388c95994fde Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sat, 10 Dec 2022 01:01:54 +0100 Subject: [PATCH 1302/2916] chore: Upgrade go-git to v5.5.0 --- go.mod | 6 ++-- go.sum | 51 +++++++++++++++--------------- pkg/git/auth/list_auth_provider.go | 12 +++++-- 3 files changed, 39 insertions(+), 30 deletions(-) diff --git a/go.mod b/go.mod index ab79f6f8b..e3b62184a 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/cyphar/filepath-securejoin v0.2.3 github.com/docker/distribution v2.8.1+incompatible github.com/fluxcd/pkg/kustomize v0.11.0 - github.com/go-git/go-git/v5 v5.4.2 + github.com/go-git/go-git/v5 v5.5.0 github.com/go-playground/validator/v10 v10.11.1 github.com/gobwas/glob v0.2.3 // indirect github.com/golang-jwt/jwt/v4 v4.4.3 @@ -88,7 +88,7 @@ require ( github.com/Masterminds/sprig/v3 v3.2.2 // indirect github.com/Masterminds/squirrel v1.5.3 // indirect github.com/Microsoft/go-winio v0.6.0 // indirect - github.com/ProtonMail/go-crypto v0.0.0-20220930113650-c6815a8c17ad // indirect + github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 // indirect github.com/acomagu/bufpipe v1.0.3 // indirect github.com/armon/go-metrics v0.4.1 // indirect github.com/armon/go-radix v1.0.0 // indirect @@ -195,6 +195,7 @@ require ( github.com/pelletier/go-toml/v2 v2.0.5 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pierrec/lz4 v2.6.1+incompatible // indirect + github.com/pjbgf/sha1cd v0.2.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.14.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect @@ -206,6 +207,7 @@ require ( github.com/ryanuber/go-glob v1.0.0 // indirect github.com/sergi/go-diff v1.2.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect + github.com/skeema/knownhosts v1.1.0 // indirect github.com/spf13/afero v1.9.2 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect diff --git a/go.sum b/go.sum index 4b69beba1..7eb91241c 100644 --- a/go.sum +++ b/go.sum @@ -96,17 +96,14 @@ github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmy github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= github.com/Masterminds/squirrel v1.5.3 h1:YPpoceAcxuzIljlr5iWpNKaql7hLeG1KLSrhvdHpkZc= github.com/Masterminds/squirrel v1.5.3/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= -github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= -github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= github.com/Microsoft/hcsshim v0.9.4 h1:mnUj0ivWy6UzbB1uLFqKR6F+ZyiDc7j4iGgHTpO+5+I= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= -github.com/ProtonMail/go-crypto v0.0.0-20220930113650-c6815a8c17ad h1:QeeqI2zxxgZVe11UrYFXXx6gVxPVF40ygekjBzEg4XY= -github.com/ProtonMail/go-crypto v0.0.0-20220930113650-c6815a8c17ad/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8= +github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 h1:ra2OtmuW0AE5csawV4YXMNGNQQXvLRps3z2Z59OPO+I= +github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= @@ -117,8 +114,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= -github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -217,7 +214,6 @@ github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9 github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= github.com/emicklei/go-restful/v3 v3.10.0 h1:X4gma4HM7hFm6WMeAsTfqA0GOfdNoCzBIkHGoRLGXuM= github.com/emicklei/go-restful/v3 v3.10.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -246,25 +242,23 @@ github.com/fluxcd/pkg/apis/kustomize v0.7.0 h1:X2htBmJ91nGYv4d93gin665MFWKNGiNwU github.com/fluxcd/pkg/apis/kustomize v0.7.0/go.mod h1:Mu+KdktsEKWA4l/33CZdY5lB4hz51mqfcLzBZSwAqVg= github.com/fluxcd/pkg/kustomize v0.11.0 h1:zseS9LRUuzhP/7KamccmsOgYpJAdhqtsf+2wN/CHF3I= github.com/fluxcd/pkg/kustomize v0.11.0/go.mod h1:awHID4OKe2/WAfTFg4u0fURXZPUkrIslSZNSPX9MEFQ= -github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= -github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= +github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= -github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34= github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= -github.com/go-git/go-git-fixtures/v4 v4.2.1 h1:n9gGL1Ct/yIw+nfsfr8s4+sbhT+Ncu2SubfXjIWgci8= -github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0= -github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4= -github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc= +github.com/go-git/go-git-fixtures/v4 v4.3.1 h1:y5z6dd3qi8Hl+stezc8p3JxDkoTRqMAlKnXHuzrfjTQ= +github.com/go-git/go-git-fixtures/v4 v4.3.1/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo= +github.com/go-git/go-git/v5 v5.5.0 h1:StO/ASRvk1Pp74tr7XQ0pQwKlCFignzzTF/NLKdQzUE= +github.com/go-git/go-git/v5 v5.5.0/go.mod h1:g456XI30HAdt7GQtIf8JR6GDAdULGaR4KtfFtQa0uTg= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -495,7 +489,6 @@ github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= @@ -531,7 +524,6 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw= github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= -github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -687,6 +679,8 @@ github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pjbgf/sha1cd v0.2.0 h1:gIsJVwjbRviE4gydidGztxH1IlJQoYBcCrwG4Dz8wvM= +github.com/pjbgf/sha1cd v0.2.0/go.mod h1:HOK9QrgzdHpbc2Kzip0Q1yi3M2MFGPADtR6HjG65m5M= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -761,13 +755,14 @@ github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5g github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/skeema/knownhosts v1.1.0 h1:Wvr9V0MxhjRbl3f9nMnKnFfiWTJmtECJ9Njkea3ysW0= +github.com/skeema/knownhosts v1.1.0/go.mod h1:sKFq3RD6/TKZkSWn8boUbDC7Qkgcv+8XXijpFO6roag= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -814,7 +809,7 @@ github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaU github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/whilp/git-urls v1.0.0 h1:95f6UMWN5FKW71ECsXRUd3FVYiXdrE7aX4NZKcPmIjU= github.com/whilp/git-urls v1.0.0/go.mod h1:J16SAmobsqc3Qcy98brfl5f5+e0clUvg1krgwk/qCfE= -github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= +github.com/xanzy/ssh-agent v0.3.2/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xanzy/ssh-agent v0.3.3-0.20220920102508-0fa644ba07f4 h1:3kHD8uZqiYes9JHdd3FzuyUbG10g9Bp9EOfqkSHIhg4= github.com/xanzy/ssh-agent v0.3.3-0.20220920102508-0fa644ba07f4/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= @@ -866,7 +861,6 @@ go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -876,14 +870,16 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8= golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -962,7 +958,6 @@ golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= -golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -970,7 +965,9 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU= golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1018,7 +1015,6 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1053,12 +1049,10 @@ golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1075,15 +1069,20 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/pkg/git/auth/list_auth_provider.go b/pkg/git/auth/list_auth_provider.go index e11cb176c..44e75c477 100644 --- a/pkg/git/auth/list_auth_provider.go +++ b/pkg/git/auth/list_auth_provider.go @@ -6,6 +6,7 @@ import ( "github.com/go-git/go-git/v5/plumbing/transport/ssh" "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/git/messages" + ssh2 "golang.org/x/crypto/ssh" "strings" ) @@ -79,13 +80,20 @@ func (a *ListAuthProvider) BuildAuth(ctx context.Context, gitUrl git_url.GitUrl) if err != nil { a.MessageCallbacks.Trace("ListAuthProvider: failed to parse private key: %v", err) } else { - pk.HostKeyCallback = buildVerifyHostCallback(a.MessageCallbacks, e.KnownHosts) + hostKeyCallback := buildVerifyHostCallback(a.MessageCallbacks, e.KnownHosts) return AuthMethodAndCA{ AuthMethod: pk, Hash: func() ([]byte, error) { return buildHash(pk.Signer) }, - ClientConfig: pk.ClientConfig, + ClientConfig: func() (*ssh2.ClientConfig, error) { + ccfg, err := pk.ClientConfig() + if err != nil { + return nil, err + } + ccfg.HostKeyCallback = hostKeyCallback + return ccfg, nil + }, } } } else { From c9aa3d1628b8687165e20c26e4a642428fe5aa02 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sat, 10 Dec 2022 01:07:59 +0100 Subject: [PATCH 1303/2916] tests: Fix race condition --- e2e/test-utils/project.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/e2e/test-utils/project.go b/e2e/test-utils/project.go index 441d8250c..b97497f98 100644 --- a/e2e/test-utils/project.go +++ b/e2e/test-utils/project.go @@ -440,9 +440,16 @@ func (p *TestProject) KluctlExecute(argsIn ...string) (string, string, error) { p.t.Log(message) stderrBuf.WriteString(message + "\n") }, false, true) - defer sh.Stop() + defer func() { + if sh != nil { + sh.Stop() + } + }() ctx = status.NewContext(ctx, sh) err := commands.Execute(ctx, args, nil) + sh.Stop() + sh = nil + _ = stdout.Close() return stdoutBuf.String(), stderrBuf.String(), err } From 0e712d3c1fa009abf8fc9f3c3fb130c6cb727115 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sat, 10 Dec 2022 01:21:30 +0100 Subject: [PATCH 1304/2916] fix: Wait for lineRedirector to finish when calling Close() --- pkg/status/utils.go | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/pkg/status/utils.go b/pkg/status/utils.go index cc5002217..4930ddf2f 100644 --- a/pkg/status/utils.go +++ b/pkg/status/utils.go @@ -5,8 +5,23 @@ import ( "io" ) +type lineRedirector struct { + io.WriteCloser + done chan bool +} + +func (lr *lineRedirector) Close() error { + err := lr.WriteCloser.Close() + if err != nil { + return err + } + <-lr.done + return nil +} + func NewLineRedirector(cb func(line string)) io.WriteCloser { r, w := io.Pipe() + done := make(chan bool, 1) go func() { br := bufio.NewReader(r) @@ -18,9 +33,10 @@ func NewLineRedirector(cb func(line string)) io.WriteCloser { } cb(msg) } + done <- true }() - return w + return &lineRedirector{w, done} } type replaceRReader struct { From 56eb1e32c5403f6155dc2c806950388d76c19f54 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sat, 10 Dec 2022 01:29:25 +0100 Subject: [PATCH 1305/2916] fix: Upgrade github.com/pjbgf/sha1cd to 0.2.3 to fix !cgo compilation --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 49328dda2..484ebe921 100644 --- a/go.mod +++ b/go.mod @@ -195,7 +195,7 @@ require ( github.com/pelletier/go-toml/v2 v2.0.5 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pierrec/lz4 v2.6.1+incompatible // indirect - github.com/pjbgf/sha1cd v0.2.0 // indirect + github.com/pjbgf/sha1cd v0.2.3 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.14.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect diff --git a/go.sum b/go.sum index 07e98a652..5ce647007 100644 --- a/go.sum +++ b/go.sum @@ -679,8 +679,9 @@ github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pjbgf/sha1cd v0.2.0 h1:gIsJVwjbRviE4gydidGztxH1IlJQoYBcCrwG4Dz8wvM= github.com/pjbgf/sha1cd v0.2.0/go.mod h1:HOK9QrgzdHpbc2Kzip0Q1yi3M2MFGPADtR6HjG65m5M= +github.com/pjbgf/sha1cd v0.2.3 h1:uKQP/7QOzNtKYH7UTohZLcjF5/55EnTw0jO/Ru4jZwI= +github.com/pjbgf/sha1cd v0.2.3/go.mod h1:HOK9QrgzdHpbc2Kzip0Q1yi3M2MFGPADtR6HjG65m5M= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= From c534527a165594e11acaef426f8a387c39e278bf Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sat, 10 Dec 2022 21:57:26 +0100 Subject: [PATCH 1306/2916] fix: Fix potential crash in readCachedResponse --- pkg/registries/registries.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/registries/registries.go b/pkg/registries/registries.go index ffda2e3f8..b372b06b6 100644 --- a/pkg/registries/registries.go +++ b/pkg/registries/registries.go @@ -337,6 +337,10 @@ func (rh *RegistryHelper) readCachedResponse(key string) []byte { } res, err := http.ReadResponse(bufio.NewReader(bytes.NewReader(b)), nil) + if err != nil { + status.Warning(rh.ctx, "readCachedResponse failed: %v", err) + return nil + } if strings.HasPrefix(res.Header.Get("Content-Type"), "application/json") { jb, err := io.ReadAll(res.Body) From 85888a3370ba4a05e32dd37a0113dbfd3fa29cea Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 12 Dec 2022 09:23:06 +0100 Subject: [PATCH 1307/2916] refactor: Move HelmChart into own package --- cmd/kluctl/commands/cmd_helm_pull.go | 4 ++-- cmd/kluctl/commands/cmd_helm_update.go | 12 ++++++------ pkg/deployment/deployment_item.go | 5 +++-- pkg/deployment/shared_context.go | 3 ++- pkg/{deployment => helm}/helm_chart.go | 2 +- pkg/kluctl_project/target_context.go | 3 ++- 6 files changed, 16 insertions(+), 13 deletions(-) rename pkg/{deployment => helm}/helm_chart.go (99%) diff --git a/cmd/kluctl/commands/cmd_helm_pull.go b/cmd/kluctl/commands/cmd_helm_pull.go index ab2d7bf66..1dd11609c 100644 --- a/cmd/kluctl/commands/cmd_helm_pull.go +++ b/cmd/kluctl/commands/cmd_helm_pull.go @@ -5,8 +5,8 @@ import ( "fmt" "github.com/hashicorp/go-multierror" "github.com/kluctl/kluctl/v2/cmd/kluctl/args" - "github.com/kluctl/kluctl/v2/pkg/deployment" git2 "github.com/kluctl/kluctl/v2/pkg/git" + "github.com/kluctl/kluctl/v2/pkg/helm" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/utils" "golang.org/x/sync/semaphore" @@ -84,7 +84,7 @@ func doPull(ctx context.Context, statusPrefix string, p string, helmCredentials return err } - chart, err := deployment.NewHelmChart(p) + chart, err := helm.NewHelmChart(p) if err != nil { return doError(err) } diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go index 225caec92..daafebf19 100644 --- a/cmd/kluctl/commands/cmd_helm_update.go +++ b/cmd/kluctl/commands/cmd_helm_update.go @@ -6,8 +6,8 @@ import ( "github.com/go-git/go-git/v5" "github.com/hashicorp/go-multierror" "github.com/kluctl/kluctl/v2/cmd/kluctl/args" - "github.com/kluctl/kluctl/v2/pkg/deployment" git2 "github.com/kluctl/kluctl/v2/pkg/git" + "github.com/kluctl/kluctl/v2/pkg/helm" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/utils" "golang.org/x/sync/semaphore" @@ -51,7 +51,7 @@ func (cmd *helmUpdateCmd) Run(ctx context.Context) error { type updatedChart struct { path string - chart *deployment.HelmChart + chart *helm.HelmChart newVersion string oldVersion string pullSuccess bool @@ -127,19 +127,19 @@ func (cmd *helmUpdateCmd) Run(ctx context.Context) error { return errs.ErrorOrNil() } -func (cmd *helmUpdateCmd) doCheckUpdate(ctx context.Context, gitRootPath string, p string) (*deployment.HelmChart, string, bool, error) { +func (cmd *helmUpdateCmd) doCheckUpdate(ctx context.Context, gitRootPath string, p string) (*helm.HelmChart, string, bool, error) { statusPrefix, err := filepath.Rel(gitRootPath, filepath.Dir(p)) if err != nil { return nil, "", false, err } s := status.Start(ctx, "%s: Checking for updates", statusPrefix) - doError := func(err error) (*deployment.HelmChart, string, bool, error) { + doError := func(err error) (*helm.HelmChart, string, bool, error) { s.FailedWithMessage("%s: %s", statusPrefix, err.Error()) return nil, "", false, err } - chart, err := deployment.NewHelmChart(p) + chart, err := helm.NewHelmChart(p) if err != nil { return doError(err) } @@ -164,7 +164,7 @@ func (cmd *helmUpdateCmd) doCheckUpdate(ctx context.Context, gitRootPath string, return chart, newVersion, updated, nil } -func (cmd *helmUpdateCmd) pullAndCommitChart(ctx context.Context, gitRootPath string, chart *deployment.HelmChart, oldVersion string, newVersion string, mutex *sync.Mutex) error { +func (cmd *helmUpdateCmd) pullAndCommitChart(ctx context.Context, gitRootPath string, chart *helm.HelmChart, oldVersion string, newVersion string, mutex *sync.Mutex) error { statusPrefix, err := filepath.Rel(gitRootPath, filepath.Dir(chart.ConfigFile)) if err != nil { return err diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 9051825d0..b63c7d704 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -3,6 +3,7 @@ package deployment import ( "fmt" "github.com/fluxcd/pkg/kustomize" + "github.com/kluctl/kluctl/v2/pkg/helm" "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/sops" "github.com/kluctl/kluctl/v2/pkg/status" @@ -192,7 +193,7 @@ func (di *DeploymentItem) renderHelmCharts() error { return err } - chart, err := NewHelmChart(p) + chart, err := helm.NewHelmChart(p) if err != nil { return err } @@ -417,7 +418,7 @@ func (di *DeploymentItem) generateKustomizationYaml(subDir string) (*uo.Unstruct if di.isHelmValuesYaml(de.Name()) { continue } else if di.isHelmChartYaml(de.Name()) { - c, err := NewHelmChart(filepath.Join(di.RenderedDir, subDir, de.Name())) + c, err := helm.NewHelmChart(filepath.Join(di.RenderedDir, subDir, de.Name())) if err != nil { return nil, err } diff --git a/pkg/deployment/shared_context.go b/pkg/deployment/shared_context.go index 7fce849ce..dec1c4571 100644 --- a/pkg/deployment/shared_context.go +++ b/pkg/deployment/shared_context.go @@ -2,6 +2,7 @@ package deployment import ( "context" + "github.com/kluctl/kluctl/v2/pkg/helm" "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/repocache" "github.com/kluctl/kluctl/v2/pkg/sops" @@ -15,7 +16,7 @@ type SharedContext struct { RP *repocache.GitRepoCache SopsDecrypter sops.SopsDecrypter VarsLoader *vars.VarsLoader - HelmCredentials HelmCredentialsProvider + HelmCredentials helm.HelmCredentialsProvider RenderDir string SealedSecretsDir string diff --git a/pkg/deployment/helm_chart.go b/pkg/helm/helm_chart.go similarity index 99% rename from pkg/deployment/helm_chart.go rename to pkg/helm/helm_chart.go index cf1cb825c..5c79c616e 100644 --- a/pkg/deployment/helm_chart.go +++ b/pkg/helm/helm_chart.go @@ -1,4 +1,4 @@ -package deployment +package helm import ( "context" diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index bacf2d7d5..f07436014 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "github.com/kluctl/kluctl/v2/pkg/deployment" + "github.com/kluctl/kluctl/v2/pkg/helm" "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" @@ -37,7 +38,7 @@ type TargetContextParams struct { ForSeal bool Images *deployment.Images Inclusion *utils.Inclusion - HelmCredentials deployment.HelmCredentialsProvider + HelmCredentials helm.HelmCredentialsProvider RenderOutputDir string } From cc8b1ebf9149d0d6e17242b696b095354ed8681a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 12 Dec 2022 09:28:50 +0100 Subject: [PATCH 1308/2916] fix: Add omitempty to UpdateConstraints --- pkg/types/helm_chart.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/types/helm_chart.go b/pkg/types/helm_chart.go index 7cf645597..9ebc00118 100644 --- a/pkg/types/helm_chart.go +++ b/pkg/types/helm_chart.go @@ -5,7 +5,7 @@ type HelmChartConfig2 struct { CredentialsId *string `yaml:"credentialsId,omitempty"` ChartName *string `yaml:"chartName,omitempty"` ChartVersion *string `yaml:"chartVersion" validate:"required"` - UpdateConstraints *string `yaml:"updateConstraints"` + UpdateConstraints *string `yaml:"updateConstraints,omitempty"` ReleaseName string `yaml:"releaseName" validate:"required"` Namespace *string `yaml:"namespace,omitempty"` Output *string `yaml:"output,omitempty"` From ca06a858884a9bee5deeb004d202d07a68bf89f2 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 12 Dec 2022 09:36:30 +0100 Subject: [PATCH 1309/2916] feat: Store pre-pulled helm charts in central .helm-charts dir --- cmd/kluctl/commands/cmd_helm_pull.go | 97 ++++++--- cmd/kluctl/commands/cmd_helm_update.go | 275 +++++++++++++++++-------- pkg/deployment/deployment_item.go | 4 +- pkg/deployment/shared_context.go | 1 + pkg/helm/helm_chart.go | 125 ++++++++--- pkg/kluctl_project/project.go | 1 + pkg/kluctl_project/project_load.go | 1 + pkg/kluctl_project/target_context.go | 1 + 8 files changed, 356 insertions(+), 149 deletions(-) diff --git a/cmd/kluctl/commands/cmd_helm_pull.go b/cmd/kluctl/commands/cmd_helm_pull.go index 1dd11609c..cb9ac562a 100644 --- a/cmd/kluctl/commands/cmd_helm_pull.go +++ b/cmd/kluctl/commands/cmd_helm_pull.go @@ -5,12 +5,13 @@ import ( "fmt" "github.com/hashicorp/go-multierror" "github.com/kluctl/kluctl/v2/cmd/kluctl/args" - git2 "github.com/kluctl/kluctl/v2/pkg/git" "github.com/kluctl/kluctl/v2/pkg/helm" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/yaml" "golang.org/x/sync/semaphore" "io/fs" + "os" "path/filepath" "sync" ) @@ -32,15 +33,14 @@ func (cmd *helmPullCmd) Run(ctx context.Context) error { return err } - gitRootPath, err := git2.DetectGitRepositoryRoot(projectDir) - if err != nil { - return err + if !yaml.Exists(filepath.Join(projectDir, ".kluctl.yaml")) { + return fmt.Errorf("helm-pull can only be used on the root of a Kluctl project that must have a .kluctl.yaml file") } - var errs *multierror.Error - var wg sync.WaitGroup - var mutex sync.Mutex - sem := semaphore.NewWeighted(8) + baseChartsDir := filepath.Join(projectDir, ".helm-charts") + + chartsToPull := map[string]*helm.HelmChart{} + cleanupDirs := map[string][]string{} err = filepath.WalkDir(projectDir, func(p string, d fs.DirEntry, err error) error { fname := filepath.Base(p) @@ -48,24 +48,46 @@ func (cmd *helmPullCmd) Run(ctx context.Context) error { return nil } - statusPrefix, err := filepath.Rel(gitRootPath, filepath.Dir(p)) + chart, err := helm.NewHelmChart(baseChartsDir, p) if err != nil { return err } + chart.SetCredentials(&cmd.HelmCredentials) + + cleanupDir := chart.GetChartDir(false) + cleanupDirs[cleanupDir] = append(cleanupDirs[cleanupDir], *chart.Config.ChartVersion) + + if _, ok := chartsToPull[chart.GetChartDir(true)]; ok { + return nil + } + + chartsToPull[chart.GetChartDir(true)] = chart + return nil + }) + + var errs *multierror.Error + var wg sync.WaitGroup + var mutex sync.Mutex + sem := semaphore.NewWeighted(8) + + for _, chart := range chartsToPull { + chart := chart + statusPrefix := chart.GetChartName() + utils.GoLimitedMultiError(ctx, sem, &errs, &mutex, &wg, func() error { - s := status.Start(ctx, "%s: Pulling Chart", statusPrefix) + s := status.Start(ctx, "%s: Pulling Chart with version %s", statusPrefix, *chart.Config.ChartVersion) defer s.Failed() - err := doPull(ctx, statusPrefix, p, cmd.HelmCredentials, s) + + err := chart.Pull(ctx) if err != nil { + s.FailedWithMessage("%s: %s", statusPrefix, err.Error()) return err } s.Success() return nil }) - - return nil - }) + } wg.Wait() if err != nil { errs = multierror.Append(errs, err) @@ -75,27 +97,34 @@ func (cmd *helmPullCmd) Run(ctx context.Context) error { return fmt.Errorf("command failed") } - return nil -} - -func doPull(ctx context.Context, statusPrefix string, p string, helmCredentials args.HelmCredentials, s *status.StatusContext) error { - doError := func(err error) error { - s.FailedWithMessage("%s: %s", statusPrefix, err.Error()) - return err - } - - chart, err := helm.NewHelmChart(p) - if err != nil { - return doError(err) + for dir, versions := range cleanupDirs { + des, err := os.ReadDir(dir) + if err != nil { + return err + } + for _, de := range des { + if !de.IsDir() { + continue + } + found := false + for _, v := range versions { + if v == de.Name() { + found = true + break + } + } + if !found { + chartName := filepath.Base(dir) + s := status.Start(ctx, "%s: Removing unused version %s", chartName, de.Name()) + err = os.RemoveAll(filepath.Join(dir, de.Name())) + if err != nil { + s.Failed() + return err + } + s.Success() + } + } } - chart.SetCredentials(&helmCredentials) - - s.UpdateAndInfoFallback("%s: Pulling Chart %s with version %s", statusPrefix, chart.GetChartName(), *chart.Config.ChartVersion) - - err = chart.Pull(ctx) - if err != nil { - return doError(err) - } return nil } diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go index daafebf19..98dd66f4c 100644 --- a/cmd/kluctl/commands/cmd_helm_update.go +++ b/cmd/kluctl/commands/cmd_helm_update.go @@ -10,6 +10,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/helm" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/yaml" "golang.org/x/sync/semaphore" "io/fs" "math/rand" @@ -33,30 +34,34 @@ func (cmd *helmUpdateCmd) Help() string { return `Optionally performs the actual upgrade and/or add a commit to version control.` } +type updatedChart struct { + charts []*helm.HelmChart + newVersion string +} + func (cmd *helmUpdateCmd) Run(ctx context.Context) error { projectDir, err := cmd.ProjectDir.GetProjectDir() if err != nil { return err } + if !yaml.Exists(filepath.Join(projectDir, ".kluctl.yaml")) { + return fmt.Errorf("helm-pull can only be used on the root of a Kluctl project that must have a .kluctl.yaml file") + } + gitRootPath, err := git2.DetectGitRepositoryRoot(projectDir) if err != nil { return err } + baseChartsDir := filepath.Join(projectDir, ".helm-charts") + var errs *multierror.Error var wg sync.WaitGroup var mutex sync.Mutex sem := semaphore.NewWeighted(8) - type updatedChart struct { - path string - chart *helm.HelmChart - newVersion string - oldVersion string - pullSuccess bool - } - var updatedCharts []*updatedChart + chartsToCheck := map[string]*updatedChart{} err = filepath.WalkDir(projectDir, func(p string, d fs.DirEntry, err error) error { fname := filepath.Base(p) @@ -64,8 +69,31 @@ func (cmd *helmUpdateCmd) Run(ctx context.Context) error { return nil } + chart, err := helm.NewHelmChart(baseChartsDir, p) + if err != nil { + return err + } + + chart.SetCredentials(&cmd.HelmCredentials) + + key := buildKey(chart, chart.Config.UpdateConstraints, nil) + if uc2, ok := chartsToCheck[key]; ok { + uc2.charts = append(uc2.charts, chart) + } else { + chartsToCheck[key] = &updatedChart{ + charts: []*helm.HelmChart{chart}, + } + } + return nil + }) + + toKeep := map[string]bool{} + chartsToPull := map[string]*updatedChart{} + + for _, uc := range chartsToCheck { + uc := uc utils.GoLimitedMultiError(ctx, sem, &errs, &mutex, &wg, func() error { - chart, newVersion, updated, err := cmd.doCheckUpdate(ctx, gitRootPath, p) + newVersion, err := cmd.doQueryLatestVersion(ctx, uc.charts[0]) if err != nil { return err } @@ -73,22 +101,43 @@ func (cmd *helmUpdateCmd) Run(ctx context.Context) error { mutex.Lock() defer mutex.Unlock() - if !chart.Config.SkipUpdate && updated { - updatedCharts = append(updatedCharts, &updatedChart{ - path: p, - chart: chart, + key := buildKey(uc.charts[0], &newVersion, nil) + toKeep[key] = true + + uc2, ok := chartsToPull[key] + if !ok { + uc2 = &updatedChart{ newVersion: newVersion, - oldVersion: *chart.Config.ChartVersion, - }) + } + chartsToPull[key] = uc2 + } + + for _, chart := range uc.charts { + updated := newVersion != *chart.Config.ChartVersion + if updated && chart.Config.SkipUpdate { + status.Info(ctx, "%s: Skipping update to version %s", chart.GetChartName(), newVersion) + updated = false + } + if !updated { + toKeep[buildKey(chart, chart.Config.ChartVersion, nil)] = true + continue + } + if len(uc2.charts) == 0 { + status.Info(ctx, "%s: Chart has new version %s", chart.GetChartName(), newVersion) + } + uc2.charts = append(uc2.charts, chart) + } + + if len(uc2.charts) == 0 { + delete(chartsToPull, key) } + return nil }) - return nil - }) + } wg.Wait() - if err != nil { - errs = multierror.Append(errs, err) - return errs.ErrorOrNil() + if errs.ErrorOrNil() != nil { + return errs } if !cmd.Upgrade { @@ -99,19 +148,19 @@ func (cmd *helmUpdateCmd) Run(ctx context.Context) error { sem = semaphore.NewWeighted(1) } - for _, uc := range updatedCharts { + for _, uc := range chartsToPull { uc := uc utils.GoLimitedMultiError(ctx, sem, &errs, &mutex, &wg, func() error { if cmd.Interactive { - statusPrefix, _ := filepath.Rel(gitRootPath, filepath.Dir(uc.path)) - if !status.AskForConfirmation(ctx, fmt.Sprintf("%s: Do you want to upgrade Chart %s from version %s to %s?", - statusPrefix, uc.chart.GetChartName(), uc.oldVersion, uc.newVersion)) { + statusPrefix := uc.charts[0].GetChartName() + if !status.AskForConfirmation(ctx, fmt.Sprintf("%s: Do you want to upgrade Chart %s to version %s?", + statusPrefix, uc.charts[0].GetChartName(), uc.newVersion)) { return nil } } - err := cmd.pullAndCommitChart(ctx, gitRootPath, uc.chart, uc.oldVersion, uc.newVersion, &mutex) + err := cmd.pullAndCommitCharts(ctx, gitRootPath, uc, toKeep, &mutex) if err != nil { return err } @@ -127,99 +176,155 @@ func (cmd *helmUpdateCmd) Run(ctx context.Context) error { return errs.ErrorOrNil() } -func (cmd *helmUpdateCmd) doCheckUpdate(ctx context.Context, gitRootPath string, p string) (*helm.HelmChart, string, bool, error) { - statusPrefix, err := filepath.Rel(gitRootPath, filepath.Dir(p)) - if err != nil { - return nil, "", false, err +func buildKey(chart *helm.HelmChart, v1 *string, v2 *string) string { + key := chart.GetChartDir(false) + if v1 != nil { + key += " / " + *v1 } - - s := status.Start(ctx, "%s: Checking for updates", statusPrefix) - doError := func(err error) (*helm.HelmChart, string, bool, error) { - s.FailedWithMessage("%s: %s", statusPrefix, err.Error()) - return nil, "", false, err + if v2 != nil { + key += " / " + *v2 } + return key +} - chart, err := helm.NewHelmChart(p) - if err != nil { - return doError(err) +func (cmd *helmUpdateCmd) doQueryLatestVersion(ctx context.Context, chart *helm.HelmChart) (string, error) { + statusPrefix := chart.GetChartName() + + statusText := fmt.Sprintf("%s: Querying latest version", statusPrefix) + if chart.Config.UpdateConstraints != nil { + statusText = fmt.Sprintf("%s: Querying latest version with constraints '%s'", statusPrefix, *chart.Config.UpdateConstraints) } - chart.SetCredentials(&cmd.HelmCredentials) + s := status.Start(ctx, statusText) + defer s.Failed() - newVersion, updated, err := chart.CheckUpdate(ctx) + doError := func(err error) (string, error) { + s.FailedWithMessage("%s: %s", statusPrefix, err.Error()) + return "", err + } + + latestVersion, err := chart.QueryLatestVersion(ctx) if err != nil { return doError(err) } - if !updated { - s.UpdateAndInfoFallback("%s: Version %s is already up-to-date.", statusPrefix, *chart.Config.ChartVersion) - } else { - msg := fmt.Sprintf("%s: Chart has new version %s available. Old version is %s.", statusPrefix, newVersion, *chart.Config.ChartVersion) - if chart.Config.SkipUpdate { - msg += " skipUpdate is set to true." - } - s.UpdateAndInfoFallback(msg) - } s.Success() - return chart, newVersion, updated, nil + return latestVersion, nil } -func (cmd *helmUpdateCmd) pullAndCommitChart(ctx context.Context, gitRootPath string, chart *helm.HelmChart, oldVersion string, newVersion string, mutex *sync.Mutex) error { - statusPrefix, err := filepath.Rel(gitRootPath, filepath.Dir(chart.ConfigFile)) - if err != nil { - return err +func (cmd *helmUpdateCmd) collectFiles(root string, dir string, m map[string]os.FileInfo) error { + err := filepath.WalkDir(dir, func(p string, d fs.DirEntry, err error) error { + if d == nil || d.IsDir() { + return nil + } + relToGit, err := filepath.Rel(root, p) + if err != nil { + return err + } + if _, ok := m[relToGit]; ok { + return nil + } + st, err := d.Info() + if err != nil { + return err + } + m[relToGit] = st + return nil + }) + if os.IsNotExist(err) { + err = nil } + return err +} - s := status.Start(ctx, "%s: Pulling Chart", statusPrefix) +func (cmd *helmUpdateCmd) pullAndCommitCharts(ctx context.Context, gitRootPath string, uc *updatedChart, toKeep map[string]bool, mutex *sync.Mutex) error { + statusPrefix := uc.charts[0].GetChartName() + + s := status.Start(ctx, "%s: Pulling Chart with version %s", statusPrefix, uc.newVersion) defer s.Failed() - chart.Config.ChartVersion = &newVersion - err = chart.Save() - if err != nil { + doError := func(err error) error { + s.FailedWithMessage("%s: %s", statusPrefix, err.Error()) return err } + var toAdd []string + var oldVersions []string + var oldChartDirs []string + // we need to list all files contained inside the charts dir BEFORE doing the pull, so that we later // know what got deleted - oldFiles := map[string]bool{} - err = filepath.WalkDir(chart.GetChartDir(), func(p string, d fs.DirEntry, err error) error { - if d == nil || d.IsDir() { - return nil + files := map[string]os.FileInfo{} + + for _, chart := range uc.charts { + oldChartDirs = append(oldChartDirs, chart.GetChartDir(true)) + oldVersions = append(oldVersions, *chart.Config.ChartVersion) + + chart.Config.ChartVersion = &uc.newVersion + err := chart.Save() + if err != nil { + return doError(err) } - relToGit, err := filepath.Rel(gitRootPath, p) + + // add helm-chart.yaml + relToGit, err := filepath.Rel(gitRootPath, chart.ConfigFile) if err != nil { - return err + return doError(err) + } + toAdd = append(toAdd, relToGit) + + err = cmd.collectFiles(gitRootPath, chart.GetDeprecatedChartDir(), files) + if err != nil { + return doError(err) } - oldFiles[relToGit] = true - return nil - }) - if err != nil { - return err } - err = doPull(ctx, statusPrefix, chart.ConfigFile, cmd.HelmCredentials, s) + err := cmd.collectFiles(gitRootPath, uc.charts[0].GetChartDir(true), files) if err != nil { - return err + return doError(err) } - var toAdd []string - relToGit, err := filepath.Rel(gitRootPath, chart.ConfigFile) + err = uc.charts[0].Pull(ctx) if err != nil { - return err + return doError(err) } - toAdd = append(toAdd, relToGit) - relToGit, err = filepath.Rel(gitRootPath, chart.GetChartDir()) + relToGit, err := filepath.Rel(gitRootPath, uc.charts[0].GetChartDir(true)) if err != nil { - return err + return doError(err) } toAdd = append(toAdd, relToGit) + // delete old versions + for i, chart := range uc.charts { + deleteOldVersion := !toKeep[buildKey(chart, &oldVersions[i], nil)] + if deleteOldVersion { + err = os.RemoveAll(oldChartDirs[i]) + if err != nil { + return doError(err) + } + } + + err = os.RemoveAll(chart.GetDeprecatedChartDir()) + if err != nil { + return doError(err) + } + } + // figure out what got deleted - for p, _ := range oldFiles { - if !utils.IsFile(filepath.Join(gitRootPath, p)) { - toAdd = append(toAdd, p) + for p, oldSt := range files { + st, err := os.Lstat(filepath.Join(gitRootPath, p)) + if err != nil { + if os.IsNotExist(err) { + toAdd = append(toAdd, p) + continue + } + return doError(err) + } + if st.Mode() == oldSt.Mode() && st.ModTime() == oldSt.ModTime() && st.Size() == oldSt.Size() { + continue } + toAdd = append(toAdd, p) } s.UpdateAndInfoFallback("%s: Committing chart", statusPrefix) @@ -229,11 +334,11 @@ func (cmd *helmUpdateCmd) pullAndCommitChart(ctx context.Context, gitRootPath st r, err := git.PlainOpen(gitRootPath) if err != nil { - return err + return doError(err) } wt, err := r.Worktree() if err != nil { - return err + return doError(err) } for _, p := range toAdd { @@ -251,17 +356,17 @@ func (cmd *helmUpdateCmd) pullAndCommitChart(ctx context.Context, gitRootPath st time.Sleep(s * time.Millisecond) } if err != nil { - return fmt.Errorf("failed to add %s to git index: %w", p, err) + return doError(fmt.Errorf("failed to add %s to git index: %w", p, err)) } } - commitMsg := fmt.Sprintf("Updated helm chart %s from %s to %s", statusPrefix, oldVersion, newVersion) + commitMsg := fmt.Sprintf("Updated helm chart %s to version %s", statusPrefix, uc.newVersion) _, err = wt.Commit(commitMsg, &git.CommitOptions{}) if err != nil { - return fmt.Errorf("failed to commit: %w", err) + return doError(fmt.Errorf("failed to commit: %w", err)) } - s.Update("%s: Committed helm chart with version %s", statusPrefix, newVersion) + s.Update("%s: Committed helm chart with version %s", statusPrefix, uc.newVersion) s.Success() return nil diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index b63c7d704..72688db84 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -193,7 +193,7 @@ func (di *DeploymentItem) renderHelmCharts() error { return err } - chart, err := helm.NewHelmChart(p) + chart, err := helm.NewHelmChart(di.ctx.HelmChartsDir, p) if err != nil { return err } @@ -418,7 +418,7 @@ func (di *DeploymentItem) generateKustomizationYaml(subDir string) (*uo.Unstruct if di.isHelmValuesYaml(de.Name()) { continue } else if di.isHelmChartYaml(de.Name()) { - c, err := helm.NewHelmChart(filepath.Join(di.RenderedDir, subDir, de.Name())) + c, err := helm.NewHelmChart(di.ctx.HelmChartsDir, filepath.Join(di.RenderedDir, subDir, de.Name())) if err != nil { return nil, err } diff --git a/pkg/deployment/shared_context.go b/pkg/deployment/shared_context.go index dec1c4571..49133fecb 100644 --- a/pkg/deployment/shared_context.go +++ b/pkg/deployment/shared_context.go @@ -21,4 +21,5 @@ type SharedContext struct { RenderDir string SealedSecretsDir string DefaultSealedSecretsOutputPattern string + HelmChartsDir string } diff --git a/pkg/helm/helm_chart.go b/pkg/helm/helm_chart.go index 5c79c616e..e6dfab130 100644 --- a/pkg/helm/helm_chart.go +++ b/pkg/helm/helm_chart.go @@ -28,6 +28,7 @@ import ( "helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/repo" "k8s.io/apimachinery/pkg/runtime/schema" + "net/url" "os" "path/filepath" "regexp" @@ -46,11 +47,14 @@ type HelmChart struct { credentials HelmCredentialsProvider - chartName string - chartDir string + chartName string + chartDir string + deprecatedChartDir string + + versions []string } -func NewHelmChart(configFile string) (*HelmChart, error) { +func NewHelmChart(baseChartsDir string, configFile string) (*HelmChart, error) { var config types.HelmChartConfig err := yaml.ReadYamlFile(configFile, &config) if err != nil { @@ -86,19 +90,73 @@ func NewHelmChart(configFile string) (*HelmChart, error) { if err != nil { return nil, err } - hc.chartDir = chartDir + hc.deprecatedChartDir = chartDir + + hc.chartDir, err = hc.buildChartDir(baseChartsDir) + if err != nil { + return nil, err + } return hc, nil } +func (c *HelmChart) buildChartDir(baseChartsDir string) (string, error) { + u, err := url.Parse(*c.Config.Repo) + if err != nil { + return "", err + } + + scheme := "" + port := "" + switch { + case registry.IsOCI(*c.Config.Repo): + scheme = "oci" + case u.Scheme == "http": + scheme = "http" + if u.Port() != "80" { + port = u.Port() + } + case u.Scheme == "https": + scheme = "https" + if u.Port() != "443" { + port = u.Port() + } + default: + return "", fmt.Errorf("unsupported scheme in %s", u.String()) + } + if port != "" { + scheme += "_" + port + } + + dir := filepath.Join( + baseChartsDir, + fmt.Sprintf("%s_%s", scheme, strings.ToLower(u.Hostname())), + filepath.FromSlash(strings.ToLower(u.Path)), + c.chartName, + ) + err = utils.CheckInDir(baseChartsDir, dir) + if err != nil { + return "", err + } + + return dir, nil +} + func (c *HelmChart) GetChartName() string { return c.chartName } -func (c *HelmChart) GetChartDir() string { +func (c *HelmChart) GetChartDir(withVersion bool) string { + if withVersion { + return filepath.Join(c.chartDir, *c.Config.ChartVersion) + } return c.chartDir } +func (c *HelmChart) GetDeprecatedChartDir() string { + return c.deprecatedChartDir +} + func (c *HelmChart) GetOutputPath() string { output := "helm-rendered.yaml" if c.Config.Output != nil { @@ -155,7 +213,14 @@ func (c *HelmChart) checkNeedsPull(chartDir string, isTmp bool) (bool, bool, str } func (c *HelmChart) Pull(ctx context.Context) error { - return c.doPull(ctx, c.chartDir) + if utils.Exists(c.GetDeprecatedChartDir()) { + err := os.RemoveAll(c.GetDeprecatedChartDir()) + if err != nil { + return err + } + } + + return c.doPull(ctx, c.GetChartDir(true)) } func (c *HelmChart) pullTmpChart(ctx context.Context) (string, error) { @@ -165,7 +230,7 @@ func (c *HelmChart) pullTmpChart(ctx context.Context) (string, error) { _, _ = fmt.Fprintf(hash, "%s\n", *c.Config.ChartVersion) h := hex.EncodeToString(hash.Sum(nil)) tmpDir := filepath.Join(utils.GetTmpBaseDir(ctx), "helm-charts") - _ = os.MkdirAll(tmpDir, 0o700) + _ = os.MkdirAll(tmpDir, 0o755) tmpDir = filepath.Join(tmpDir, fmt.Sprintf("%s-%s", c.chartName, h)) lockFile := tmpDir + ".lock" @@ -193,7 +258,7 @@ func (c *HelmChart) doPull(ctx context.Context, chartDir string) error { baseDir := filepath.Dir(chartDir) _ = os.RemoveAll(chartDir) - _ = os.MkdirAll(baseDir, 0o700) + _ = os.MkdirAll(baseDir, 0o755) // need to use the same filesystem/volume that we later os.Rename the final pull chart to, as otherwise // the rename operation will lead to errors @@ -263,42 +328,41 @@ func (c *HelmChart) doPull(ctx context.Context, chartDir string) error { return nil } -func (c *HelmChart) CheckUpdate(ctx context.Context) (string, bool, error) { +func (c *HelmChart) QueryLatestVersion(ctx context.Context) (string, error) { if c.Config.Repo != nil && registry.IsOCI(*c.Config.Repo) { - return c.checkUpdateOciRepo(ctx) + return c.queryLatestVersionOci(ctx) } - return c.checkUpdateHelmRepo(ctx) + return c.queryLatestVersionHelmRepo(ctx) } -func (c *HelmChart) checkUpdateOciRepo(ctx context.Context) (string, bool, error) { +func (c *HelmChart) queryLatestVersionOci(ctx context.Context) (string, error) { rh := registries.NewRegistryHelper(ctx) imageName := strings.TrimPrefix(*c.Config.Repo, "oci://") tags, err := rh.ListImageTags(imageName) if err != nil { - return "", false, err + return "", err } latestVersion, err := c.findLatestVersion(tags) if err != nil { - return "", false, err + return "", err } - updated := latestVersion != *c.Config.ChartVersion - return latestVersion, updated, nil + return latestVersion, nil } -func (c *HelmChart) checkUpdateHelmRepo(ctx context.Context) (string, bool, error) { +func (c *HelmChart) queryLatestVersionHelmRepo(ctx context.Context) (string, error) { settings := cli.New() var e *repo.Entry if c.Config.CredentialsId != nil { if c.credentials == nil { - return "", false, fmt.Errorf("no credentials provider") + return "", fmt.Errorf("no credentials provider") } e = c.credentials.FindCredentials(*c.Config.Repo, c.Config.CredentialsId) if e == nil { - return "", false, fmt.Errorf("no credentials provided for Chart %s", c.chartName) + return "", fmt.Errorf("no credentials provided for Chart %s", c.chartName) } } else { e = &repo.Entry{ @@ -308,28 +372,28 @@ func (c *HelmChart) checkUpdateHelmRepo(ctx context.Context) (string, bool, erro r, err := repo.NewChartRepository(e, getter.All(settings)) if err != nil { - return "", false, err + return "", err } r.CachePath, err = os.MkdirTemp(utils.GetTmpBaseDir(ctx), "helm-check-update-") if err != nil { - return "", false, err + return "", err } defer os.RemoveAll(r.CachePath) indexFile, err := r.DownloadIndexFile() if err != nil { - return "", false, err + return "", err } index, err := repo.LoadIndexFile(indexFile) if err != nil { - return "", false, err + return "", err } indexEntry, ok := index.Entries[c.chartName] if !ok || len(indexEntry) == 0 { - return "", false, fmt.Errorf("helm chart %s not found in repo index", c.chartName) + return "", fmt.Errorf("helm chart %s not found in repo index", c.chartName) } versions := make([]string, 0, indexEntry.Len()) @@ -339,11 +403,10 @@ func (c *HelmChart) checkUpdateHelmRepo(ctx context.Context) (string, bool, erro latestVersion, err := c.findLatestVersion(versions) if err != nil { - return "", false, err + return "", err } - updated := latestVersion != *c.Config.ChartVersion - return latestVersion, updated, nil + return latestVersion, nil } func (c *HelmChart) findLatestVersion(inputVersions []string) (string, error) { @@ -387,6 +450,12 @@ func (c *HelmChart) findLatestVersion(inputVersions []string) (string, error) { } func (c *HelmChart) Render(ctx context.Context, k *k8s.K8sCluster, k8sVersion string, sopsDecrypter sops.SopsDecrypter) error { + deprecatedChartDir := c.GetDeprecatedChartDir() + if utils.IsDirectory(deprecatedChartDir) { + status.Deprecation(ctx, "helm-charts-dir", "Your project has pre-pulled charts located beside the helm-chart.yaml, which is deprecated. "+ + "Please run 'kluctl helm-pull' on your project and ensure that the deprecated charts are removed! Future versions of kluctl will ignore these locations.") + } + err := c.doRender(ctx, k, k8sVersion, sopsDecrypter) if err != nil { return fmt.Errorf("rendering helm chart %s for release %s has failed: %w", c.chartName, c.Config.ReleaseName, err) @@ -395,7 +464,7 @@ func (c *HelmChart) Render(ctx context.Context, k *k8s.K8sCluster, k8sVersion st } func (c *HelmChart) doRender(ctx context.Context, k *k8s.K8sCluster, k8sVersion string, sopsDecrypter sops.SopsDecrypter) error { - chartDir := c.chartDir + chartDir := c.deprecatedChartDir needsPull, versionChanged, prePulledVersion, err := c.checkNeedsPull(chartDir, false) if err != nil { diff --git a/pkg/kluctl_project/project.go b/pkg/kluctl_project/project.go index 0df49e01c..d30936121 100644 --- a/pkg/kluctl_project/project.go +++ b/pkg/kluctl_project/project.go @@ -20,6 +20,7 @@ type LoadedKluctlProject struct { ProjectDir string sealedSecretsDir string + helmChartsDir string Config types2.KluctlProject DynamicTargets []*types2.DynamicTarget diff --git a/pkg/kluctl_project/project_load.go b/pkg/kluctl_project/project_load.go index 4c075fd23..b809bdd68 100644 --- a/pkg/kluctl_project/project_load.go +++ b/pkg/kluctl_project/project_load.go @@ -61,6 +61,7 @@ func (c *LoadedKluctlProject) loadKluctlProject() error { } c.sealedSecretsDir = filepath.Join(c.ProjectDir, ".sealed-secrets") + c.helmChartsDir = filepath.Join(c.ProjectDir, ".helm-charts") sealedSecretsUsed := false if c.Config.SecretsConfig != nil { diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index f07436014..9ee3c53ec 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -113,6 +113,7 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, params Targe RenderDir: params.RenderOutputDir, SealedSecretsDir: p.sealedSecretsDir, DefaultSealedSecretsOutputPattern: target.Name, + HelmChartsDir: p.helmChartsDir, } d, err := deployment.NewDeploymentProject(dctx, varsCtx, deployment.NewSource(deploymentDir), ".", nil) From 85a993bad818ba841ebabadc335e1415608a052a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 12 Dec 2022 11:18:03 +0100 Subject: [PATCH 1310/2916] refactor: Don't use pointers for Repo/ChartVersion/ChartName --- cmd/kluctl/commands/cmd_helm_pull.go | 4 +- cmd/kluctl/commands/cmd_helm_update.go | 8 ++-- pkg/helm/helm_chart.go | 54 +++++++++++++------------- pkg/types/helm_chart.go | 6 +-- 4 files changed, 36 insertions(+), 36 deletions(-) diff --git a/cmd/kluctl/commands/cmd_helm_pull.go b/cmd/kluctl/commands/cmd_helm_pull.go index cb9ac562a..18f53c464 100644 --- a/cmd/kluctl/commands/cmd_helm_pull.go +++ b/cmd/kluctl/commands/cmd_helm_pull.go @@ -56,7 +56,7 @@ func (cmd *helmPullCmd) Run(ctx context.Context) error { chart.SetCredentials(&cmd.HelmCredentials) cleanupDir := chart.GetChartDir(false) - cleanupDirs[cleanupDir] = append(cleanupDirs[cleanupDir], *chart.Config.ChartVersion) + cleanupDirs[cleanupDir] = append(cleanupDirs[cleanupDir], chart.Config.ChartVersion) if _, ok := chartsToPull[chart.GetChartDir(true)]; ok { return nil @@ -76,7 +76,7 @@ func (cmd *helmPullCmd) Run(ctx context.Context) error { statusPrefix := chart.GetChartName() utils.GoLimitedMultiError(ctx, sem, &errs, &mutex, &wg, func() error { - s := status.Start(ctx, "%s: Pulling Chart with version %s", statusPrefix, *chart.Config.ChartVersion) + s := status.Start(ctx, "%s: Pulling Chart with version %s", statusPrefix, chart.Config.ChartVersion) defer s.Failed() err := chart.Pull(ctx) diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go index 98dd66f4c..6d02499c4 100644 --- a/cmd/kluctl/commands/cmd_helm_update.go +++ b/cmd/kluctl/commands/cmd_helm_update.go @@ -113,13 +113,13 @@ func (cmd *helmUpdateCmd) Run(ctx context.Context) error { } for _, chart := range uc.charts { - updated := newVersion != *chart.Config.ChartVersion + updated := newVersion != chart.Config.ChartVersion if updated && chart.Config.SkipUpdate { status.Info(ctx, "%s: Skipping update to version %s", chart.GetChartName(), newVersion) updated = false } if !updated { - toKeep[buildKey(chart, chart.Config.ChartVersion, nil)] = true + toKeep[buildKey(chart, &chart.Config.ChartVersion, nil)] = true continue } if len(uc2.charts) == 0 { @@ -258,9 +258,9 @@ func (cmd *helmUpdateCmd) pullAndCommitCharts(ctx context.Context, gitRootPath s for _, chart := range uc.charts { oldChartDirs = append(oldChartDirs, chart.GetChartDir(true)) - oldVersions = append(oldVersions, *chart.Config.ChartVersion) + oldVersions = append(oldVersions, chart.Config.ChartVersion) - chart.Config.ChartVersion = &uc.newVersion + chart.Config.ChartVersion = uc.newVersion err := chart.Save() if err != nil { return doError(err) diff --git a/pkg/helm/helm_chart.go b/pkg/helm/helm_chart.go index e6dfab130..6c1f8e4dd 100644 --- a/pkg/helm/helm_chart.go +++ b/pkg/helm/helm_chart.go @@ -61,9 +61,9 @@ func NewHelmChart(baseChartsDir string, configFile string) (*HelmChart, error) { return nil, err } - _, err = semver.NewVersion(*config.ChartVersion) + _, err = semver.NewVersion(config.ChartVersion) if err != nil { - return nil, fmt.Errorf("invalid chart version '%s': %w", *config.ChartVersion, err) + return nil, fmt.Errorf("invalid chart version '%s': %w", config.ChartVersion, err) } hc := &HelmChart{ @@ -71,17 +71,17 @@ func NewHelmChart(baseChartsDir string, configFile string) (*HelmChart, error) { Config: &config, } - if hc.Config.Repo != nil && registry.IsOCI(*hc.Config.Repo) { - s := strings.Split(*hc.Config.Repo, "/") + if registry.IsOCI(hc.Config.Repo) { + s := strings.Split(hc.Config.Repo, "/") chartName := s[len(s)-1] if m, _ := regexp.MatchString(`[a-zA-Z_-]+`, chartName); !m { - return nil, fmt.Errorf("invalid oci chart url: %s", *hc.Config.Repo) + return nil, fmt.Errorf("invalid oci chart url: %s", hc.Config.Repo) } hc.chartName = chartName - } else if hc.Config.ChartName == nil { + } else if hc.Config.ChartName == "" { return nil, fmt.Errorf("chartName is missing in helm-chart.yml") } else { - hc.chartName = *hc.Config.ChartName + hc.chartName = hc.Config.ChartName } dir := filepath.Dir(configFile) @@ -101,7 +101,7 @@ func NewHelmChart(baseChartsDir string, configFile string) (*HelmChart, error) { } func (c *HelmChart) buildChartDir(baseChartsDir string) (string, error) { - u, err := url.Parse(*c.Config.Repo) + u, err := url.Parse(c.Config.Repo) if err != nil { return "", err } @@ -109,7 +109,7 @@ func (c *HelmChart) buildChartDir(baseChartsDir string) (string, error) { scheme := "" port := "" switch { - case registry.IsOCI(*c.Config.Repo): + case registry.IsOCI(c.Config.Repo): scheme = "oci" case u.Scheme == "http": scheme = "http" @@ -148,7 +148,7 @@ func (c *HelmChart) GetChartName() string { func (c *HelmChart) GetChartDir(withVersion bool) string { if withVersion { - return filepath.Join(c.chartDir, *c.Config.ChartVersion) + return filepath.Join(c.chartDir, c.Config.ChartVersion) } return c.chartDir } @@ -206,7 +206,7 @@ func (c *HelmChart) checkNeedsPull(chartDir string, isTmp bool) (bool, bool, str } version, _, _ := chartYaml.GetNestedString("version") - if version != *c.Config.ChartVersion { + if version != c.Config.ChartVersion { return true, true, version, nil } return false, false, "", nil @@ -225,9 +225,9 @@ func (c *HelmChart) Pull(ctx context.Context) error { func (c *HelmChart) pullTmpChart(ctx context.Context) (string, error) { hash := sha256.New() - _, _ = fmt.Fprintf(hash, "%s\n", *c.Config.Repo) + _, _ = fmt.Fprintf(hash, "%s\n", c.Config.Repo) _, _ = fmt.Fprintf(hash, "%s\n", c.chartName) - _, _ = fmt.Fprintf(hash, "%s\n", *c.Config.ChartVersion) + _, _ = fmt.Fprintf(hash, "%s\n", c.Config.ChartVersion) h := hex.EncodeToString(hash.Sum(nil)) tmpDir := filepath.Join(utils.GetTmpBaseDir(ctx), "helm-charts") _ = os.MkdirAll(tmpDir, 0o755) @@ -278,10 +278,10 @@ func (c *HelmChart) doPull(ctx context.Context, chartDir string) error { a.Settings = cli.New() a.Untar = true a.DestDir = tmpDir - a.Version = *c.Config.ChartVersion + a.Version = c.Config.ChartVersion if c.Config.CredentialsId != nil { - if registry.IsOCI(*c.Config.Repo) { + if registry.IsOCI(c.Config.Repo) { return fmt.Errorf("OCI charts can currently only be authenticated via registry login and not via cli arguments") } if c.credentials == nil { @@ -290,7 +290,7 @@ func (c *HelmChart) doPull(ctx context.Context, chartDir string) error { } if c.credentials != nil { - creds := c.credentials.FindCredentials(*c.Config.Repo, c.Config.CredentialsId) + creds := c.credentials.FindCredentials(c.Config.Repo, c.Config.CredentialsId) if creds != nil { a.Username = creds.Username a.Password = creds.Password @@ -303,10 +303,10 @@ func (c *HelmChart) doPull(ctx context.Context, chartDir string) error { } var out string - if registry.IsOCI(*c.Config.Repo) { - out, err = a.Run(*c.Config.Repo) + if registry.IsOCI(c.Config.Repo) { + out, err = a.Run(c.Config.Repo) } else { - a.RepoURL = *c.Config.Repo + a.RepoURL = c.Config.Repo out, err = a.Run(c.chartName) } if err != nil { @@ -329,7 +329,7 @@ func (c *HelmChart) doPull(ctx context.Context, chartDir string) error { } func (c *HelmChart) QueryLatestVersion(ctx context.Context) (string, error) { - if c.Config.Repo != nil && registry.IsOCI(*c.Config.Repo) { + if registry.IsOCI(c.Config.Repo) { return c.queryLatestVersionOci(ctx) } return c.queryLatestVersionHelmRepo(ctx) @@ -338,7 +338,7 @@ func (c *HelmChart) QueryLatestVersion(ctx context.Context) (string, error) { func (c *HelmChart) queryLatestVersionOci(ctx context.Context) (string, error) { rh := registries.NewRegistryHelper(ctx) - imageName := strings.TrimPrefix(*c.Config.Repo, "oci://") + imageName := strings.TrimPrefix(c.Config.Repo, "oci://") tags, err := rh.ListImageTags(imageName) if err != nil { return "", err @@ -360,13 +360,13 @@ func (c *HelmChart) queryLatestVersionHelmRepo(ctx context.Context) (string, err if c.credentials == nil { return "", fmt.Errorf("no credentials provider") } - e = c.credentials.FindCredentials(*c.Config.Repo, c.Config.CredentialsId) + e = c.credentials.FindCredentials(c.Config.Repo, c.Config.CredentialsId) if e == nil { return "", fmt.Errorf("no credentials provided for Chart %s", c.chartName) } } else { e = &repo.Entry{ - URL: *c.Config.Repo, + URL: c.Config.Repo, } } @@ -473,13 +473,13 @@ func (c *HelmChart) doRender(ctx context.Context, k *k8s.K8sCluster, k8sVersion if needsPull { if versionChanged { return fmt.Errorf("pre-pulled Helm Chart %s need to be pulled (call 'kluctl helm-pull'). "+ - "Desired version is %s while pre-pulled version is %s", c.chartName, *c.Config.ChartVersion, prePulledVersion) + "Desired version is %s while pre-pulled version is %s", c.chartName, c.Config.ChartVersion, prePulledVersion) } else { status.Warning(ctx, "Warning, need to pull Helm Chart %s with version %s. "+ - "Please consider pre-pulling it with 'kluctl helm-pull'", c.chartName, *c.Config.ChartVersion) + "Please consider pre-pulling it with 'kluctl helm-pull'", c.chartName, c.Config.ChartVersion) } - s := status.Start(ctx, "Pulling Helm Chart %s with version %s", c.chartName, *c.Config.ChartVersion) + s := status.Start(ctx, "Pulling Helm Chart %s with version %s", c.chartName, c.Config.ChartVersion) defer s.Failed() chartDir, err = c.pullTmpChart(ctx) @@ -572,7 +572,7 @@ func (c *HelmChart) doRender(ctx context.Context, k *k8s.K8sCluster, k8sVersion } if chartRequested.Metadata.Deprecated { - status.Warning(ctx, "Chart %s is deprecated", *c.Config.ChartName) + status.Warning(ctx, "Chart %s is deprecated", c.Config.ChartName) } rel, err := client.Run(chartRequested, vals) diff --git a/pkg/types/helm_chart.go b/pkg/types/helm_chart.go index 9ebc00118..11498426b 100644 --- a/pkg/types/helm_chart.go +++ b/pkg/types/helm_chart.go @@ -1,10 +1,10 @@ package types type HelmChartConfig2 struct { - Repo *string `yaml:"repo" validate:"required"` + Repo string `yaml:"repo" validate:"required"` CredentialsId *string `yaml:"credentialsId,omitempty"` - ChartName *string `yaml:"chartName,omitempty"` - ChartVersion *string `yaml:"chartVersion" validate:"required"` + ChartName string `yaml:"chartName,omitempty"` + ChartVersion string `yaml:"chartVersion" validate:"required"` UpdateConstraints *string `yaml:"updateConstraints,omitempty"` ReleaseName string `yaml:"releaseName" validate:"required"` Namespace *string `yaml:"namespace,omitempty"` From 979de607b9af5caf0ff3d04379f1470a38f66ecd Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 12 Dec 2022 16:22:17 +0100 Subject: [PATCH 1311/2916] feat: Reimplement helm support to allow better control of pre-pulled charts --- cmd/kluctl/commands/cmd_helm_pull.go | 145 +++--- cmd/kluctl/commands/cmd_helm_update.go | 408 +++++++-------- e2e/helm_test.go | 101 ++-- pkg/deployment/deployment_item.go | 12 +- pkg/helm/chart.go | 409 +++++++++++++++ pkg/helm/helm_chart.go | 673 ------------------------- pkg/helm/helm_release.go | 313 ++++++++++++ pkg/helm/pulled_chart.go | 65 +++ pkg/helm/utils.go | 19 + 9 files changed, 1159 insertions(+), 986 deletions(-) create mode 100644 pkg/helm/chart.go delete mode 100644 pkg/helm/helm_chart.go create mode 100644 pkg/helm/helm_release.go create mode 100644 pkg/helm/pulled_chart.go create mode 100644 pkg/helm/utils.go diff --git a/cmd/kluctl/commands/cmd_helm_pull.go b/cmd/kluctl/commands/cmd_helm_pull.go index 18f53c464..ee8fc429d 100644 --- a/cmd/kluctl/commands/cmd_helm_pull.go +++ b/cmd/kluctl/commands/cmd_helm_pull.go @@ -39,92 +39,121 @@ func (cmd *helmPullCmd) Run(ctx context.Context) error { baseChartsDir := filepath.Join(projectDir, ".helm-charts") - chartsToPull := map[string]*helm.HelmChart{} - cleanupDirs := map[string][]string{} - - err = filepath.WalkDir(projectDir, func(p string, d fs.DirEntry, err error) error { - fname := filepath.Base(p) - if fname != "helm-chart.yml" && fname != "helm-chart.yaml" { - return nil - } - - chart, err := helm.NewHelmChart(baseChartsDir, p) - if err != nil { - return err - } - - chart.SetCredentials(&cmd.HelmCredentials) - - cleanupDir := chart.GetChartDir(false) - cleanupDirs[cleanupDir] = append(cleanupDirs[cleanupDir], chart.Config.ChartVersion) + releases, charts, err := loadHelmReleases(projectDir, baseChartsDir, &cmd.HelmCredentials) + if err != nil { + return err + } - if _, ok := chartsToPull[chart.GetChartDir(true)]; ok { - return nil + for _, hr := range releases { + if utils.Exists(hr.GetDeprecatedChartDir()) { + rel, err := filepath.Rel(projectDir, hr.GetDeprecatedChartDir()) + if err != nil { + return err + } + status.Info(ctx, "Removing deprecated charts dir %s", rel) + err = os.RemoveAll(hr.GetDeprecatedChartDir()) + if err != nil { + return err + } } - - chartsToPull[chart.GetChartDir(true)] = chart - return nil - }) + } var errs *multierror.Error var wg sync.WaitGroup var mutex sync.Mutex sem := semaphore.NewWeighted(8) - for _, chart := range chartsToPull { + for _, chart := range charts { chart := chart statusPrefix := chart.GetChartName() - utils.GoLimitedMultiError(ctx, sem, &errs, &mutex, &wg, func() error { - s := status.Start(ctx, "%s: Pulling Chart with version %s", statusPrefix, chart.Config.ChartVersion) - defer s.Failed() - - err := chart.Pull(ctx) - if err != nil { - s.FailedWithMessage("%s: %s", statusPrefix, err.Error()) - return err + versionsToPull := map[string]bool{} + for _, hr := range releases { + if hr.Chart == chart { + versionsToPull[hr.Config.ChartVersion] = true } - s.Success() - return nil - }) - } - wg.Wait() - if err != nil { - errs = multierror.Append(errs, err) - } - - if errs.ErrorOrNil() != nil { - return fmt.Errorf("command failed") - } + } - for dir, versions := range cleanupDirs { - des, err := os.ReadDir(dir) + cleanupDir, err := chart.BuildPulledChartDir(baseChartsDir, "") if err != nil { return err } + des, err := os.ReadDir(cleanupDir) + if err != nil && !os.IsNotExist(err) { + return err + } for _, de := range des { if !de.IsDir() { continue } - found := false - for _, v := range versions { - if v == de.Name() { - found = true - break + if _, ok := versionsToPull[de.Name()]; !ok { + status.Info(ctx, "Removing unused Chart with version %s", de.Name()) + err = os.RemoveAll(filepath.Join(cleanupDir, de.Name())) + if err != nil { + return err } } - if !found { - chartName := filepath.Base(dir) - s := status.Start(ctx, "%s: Removing unused version %s", chartName, de.Name()) - err = os.RemoveAll(filepath.Join(dir, de.Name())) + } + + for version, _ := range versionsToPull { + version := version + utils.GoLimitedMultiError(ctx, sem, &errs, &mutex, &wg, func() error { + s := status.Start(ctx, "%s: Pulling Chart with version %s", statusPrefix, version) + defer s.Failed() + + _, err := chart.PullInProject(ctx, baseChartsDir, version) if err != nil { - s.Failed() + s.FailedWithMessage("%s: %s", statusPrefix, err.Error()) return err } + s.Success() - } + return nil + }) } } + wg.Wait() + if err != nil { + errs = multierror.Append(errs, err) + } + + if errs.ErrorOrNil() != nil { + return fmt.Errorf("command failed") + } return nil } + +func loadHelmReleases(projectDir string, baseChartsDir string, credentialsProvider helm.HelmCredentialsProvider) ([]*helm.Release, []*helm.Chart, error) { + var releases []*helm.Release + chartsMap := make(map[string]*helm.Chart) + err := filepath.WalkDir(projectDir, func(p string, d fs.DirEntry, err error) error { + fname := filepath.Base(p) + if fname != "helm-chart.yml" && fname != "helm-chart.yaml" { + return nil + } + + hr, err := helm.NewRelease(p, baseChartsDir, credentialsProvider) + if err != nil { + return err + } + + releases = append(releases, hr) + chart := hr.Chart + key := fmt.Sprintf("%s / %s", chart.GetRepo(), chart.GetChartName()) + if x, ok := chartsMap[key]; !ok { + chartsMap[key] = chart + } else { + hr.Chart = x + } + return nil + }) + if err != nil { + return nil, nil, err + } + charts := make([]*helm.Chart, 0, len(chartsMap)) + for _, chart := range chartsMap { + charts = append(charts, chart) + } + return releases, charts, nil +} diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go index 6d02499c4..d40b274a6 100644 --- a/cmd/kluctl/commands/cmd_helm_update.go +++ b/cmd/kluctl/commands/cmd_helm_update.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing/format/index" "github.com/hashicorp/go-multierror" "github.com/kluctl/kluctl/v2/cmd/kluctl/args" git2 "github.com/kluctl/kluctl/v2/pkg/git" @@ -13,11 +14,9 @@ import ( "github.com/kluctl/kluctl/v2/pkg/yaml" "golang.org/x/sync/semaphore" "io/fs" - "math/rand" "os" "path/filepath" "sync" - "time" ) type helmUpdateCmd struct { @@ -34,11 +33,6 @@ func (cmd *helmUpdateCmd) Help() string { return `Optionally performs the actual upgrade and/or add a commit to version control.` } -type updatedChart struct { - charts []*helm.HelmChart - newVersion string -} - func (cmd *helmUpdateCmd) Run(ctx context.Context) error { projectDir, err := cmd.ProjectDir.GetProjectDir() if err != nil { @@ -54,6 +48,26 @@ func (cmd *helmUpdateCmd) Run(ctx context.Context) error { return err } + if cmd.Commit { + g, err := git.PlainOpen(gitRootPath) + if err != nil { + return err + } + wt, err := g.Worktree() + if err != nil { + return err + } + gitStatus, err := wt.Status() + if err != nil { + return err + } + for _, s := range gitStatus { + if (s.Staging != git.Untracked && s.Staging != git.Unmodified) || (s.Worktree != git.Untracked && s.Worktree != git.Unmodified) { + return fmt.Errorf("--commit can only be used when the git worktree is unmodified") + } + } + } + baseChartsDir := filepath.Join(projectDir, ".helm-charts") var errs *multierror.Error @@ -61,158 +75,151 @@ func (cmd *helmUpdateCmd) Run(ctx context.Context) error { var mutex sync.Mutex sem := semaphore.NewWeighted(8) - chartsToCheck := map[string]*updatedChart{} - - err = filepath.WalkDir(projectDir, func(p string, d fs.DirEntry, err error) error { - fname := filepath.Base(p) - if fname != "helm-chart.yml" && fname != "helm-chart.yaml" { - return nil - } - - chart, err := helm.NewHelmChart(baseChartsDir, p) - if err != nil { - return err - } - - chart.SetCredentials(&cmd.HelmCredentials) + releases, charts, err := loadHelmReleases(projectDir, baseChartsDir, &cmd.HelmCredentials) + if err != nil { + return err + } - key := buildKey(chart, chart.Config.UpdateConstraints, nil) - if uc2, ok := chartsToCheck[key]; ok { - uc2.charts = append(uc2.charts, chart) - } else { - chartsToCheck[key] = &updatedChart{ - charts: []*helm.HelmChart{chart}, + for _, hr := range releases { + if utils.Exists(hr.GetDeprecatedChartDir()) { + relDir, err := filepath.Rel(projectDir, filepath.Dir(hr.ConfigFile)) + if err != nil { + return err } + status.Error(ctx, "%s: Project is using a pre-pulled Helm Chart that is next to the helm-chart.yaml, which is deprecated. "+ + "Updating is only possible after removing these. Use 'kluctl helm-pull' to remove all deprecated chart folders.", relDir) + return fmt.Errorf("detected deprecated chart folder") } - return nil - }) - - toKeep := map[string]bool{} - chartsToPull := map[string]*updatedChart{} + } - for _, uc := range chartsToCheck { - uc := uc + for _, chart := range charts { + chart := chart utils.GoLimitedMultiError(ctx, sem, &errs, &mutex, &wg, func() error { - newVersion, err := cmd.doQueryLatestVersion(ctx, uc.charts[0]) + s := status.Start(ctx, "%s: Querying versions", chart.GetChartName()) + defer s.Failed() + err := chart.QueryVersions(ctx) if err != nil { + s.FailedWithMessage("%s: %s", chart.GetChartName(), err.Error()) return err } + s.Success() + return nil + }) + } + wg.Wait() + if errs.ErrorOrNil() != nil { + return errs + } - mutex.Lock() - defer mutex.Unlock() - - key := buildKey(uc.charts[0], &newVersion, nil) - toKeep[key] = true + for _, chart := range charts { + chart := chart - uc2, ok := chartsToPull[key] - if !ok { - uc2 = &updatedChart{ - newVersion: newVersion, - } - chartsToPull[key] = uc2 + versionsToPull := map[string]bool{} + for _, hr := range releases { + if hr.Chart != chart { + continue } + versionsToPull[hr.Config.ChartVersion] = true + } - for _, chart := range uc.charts { - updated := newVersion != chart.Config.ChartVersion - if updated && chart.Config.SkipUpdate { - status.Info(ctx, "%s: Skipping update to version %s", chart.GetChartName(), newVersion) - updated = false - } - if !updated { - toKeep[buildKey(chart, &chart.Config.ChartVersion, nil)] = true - continue - } - if len(uc2.charts) == 0 { - status.Info(ctx, "%s: Chart has new version %s", chart.GetChartName(), newVersion) + for version, _ := range versionsToPull { + version := version + utils.GoLimitedMultiError(ctx, sem, &errs, &mutex, &wg, func() error { + s := status.Start(ctx, "%s: Downloading Chart with version %s into cache", chart.GetChartName(), version) + defer s.Failed() + _, err := chart.PullCached(ctx, version) + if err != nil { + s.FailedWithMessage("%s: %s", chart.GetChartName(), err.Error()) + return err } - uc2.charts = append(uc2.charts, chart) - } - - if len(uc2.charts) == 0 { - delete(chartsToPull, key) - } - - return nil - }) + s.Success() + return nil + }) + } } wg.Wait() if errs.ErrorOrNil() != nil { return errs } - if !cmd.Upgrade { - return errs.ErrorOrNil() + versionUseCounts := map[string]map[string]int{} + for _, hr := range releases { + key := fmt.Sprintf("%s / %s", hr.Chart.GetRepo(), hr.Chart.GetChartName()) + if _, ok := versionUseCounts[key]; !ok { + versionUseCounts[key] = map[string]int{} + } + versionUseCounts[key][hr.Config.ChartVersion]++ } - if cmd.Interactive { - sem = semaphore.NewWeighted(1) - } + for _, hr := range releases { + relDir, err := filepath.Rel(projectDir, filepath.Dir(hr.ConfigFile)) + if err != nil { + return err + } - for _, uc := range chartsToPull { - uc := uc + latestVersion, err := hr.Chart.GetLatestVersion(hr.Config.UpdateConstraints) + if err != nil { + return err + } + if hr.Config.ChartVersion == latestVersion { + continue + } - utils.GoLimitedMultiError(ctx, sem, &errs, &mutex, &wg, func() error { - if cmd.Interactive { - statusPrefix := uc.charts[0].GetChartName() - if !status.AskForConfirmation(ctx, fmt.Sprintf("%s: Do you want to upgrade Chart %s to version %s?", - statusPrefix, uc.charts[0].GetChartName(), uc.newVersion)) { - return nil - } - } + if hr.Config.SkipUpdate { + status.Info(ctx, "%s: Skipped update to version %s", relDir, latestVersion) + continue + } - err := cmd.pullAndCommitCharts(ctx, gitRootPath, uc, toKeep, &mutex) - if err != nil { - return err - } - return nil - }) - } - wg.Wait() + status.Info(ctx, "%s: Chart has new version %s available", relDir, latestVersion) - if !cmd.Commit { - return errs.ErrorOrNil() - } + if !cmd.Upgrade { + continue + } - return errs.ErrorOrNil() -} + if cmd.Interactive { + if !status.AskForConfirmation(ctx, fmt.Sprintf("%s: Do you want to upgrade Chart %s to version %s?", + relDir, hr.Chart.GetChartName(), latestVersion)) { + continue + } + } -func buildKey(chart *helm.HelmChart, v1 *string, v2 *string) string { - key := chart.GetChartDir(false) - if v1 != nil { - key += " / " + *v1 - } - if v2 != nil { - key += " / " + *v2 - } - return key -} + oldVersion := hr.Config.ChartVersion + hr.Config.ChartVersion = latestVersion + err = hr.Save() + if err != nil { + return err + } -func (cmd *helmUpdateCmd) doQueryLatestVersion(ctx context.Context, chart *helm.HelmChart) (string, error) { - statusPrefix := chart.GetChartName() + status.Info(ctx, "%s: Updated Chart version to %s", relDir, latestVersion) - statusText := fmt.Sprintf("%s: Querying latest version", statusPrefix) - if chart.Config.UpdateConstraints != nil { - statusText = fmt.Sprintf("%s: Querying latest version with constraints '%s'", statusPrefix, *chart.Config.UpdateConstraints) - } + if !cmd.Commit { + continue + } - s := status.Start(ctx, statusText) - defer s.Failed() + key := fmt.Sprintf("%s / %s", hr.Chart.GetRepo(), hr.Chart.GetChartName()) + uv := versionUseCounts[key] + uv[oldVersion]-- + uv[latestVersion]++ - doError := func(err error) (string, error) { - s.FailedWithMessage("%s: %s", statusPrefix, err.Error()) - return "", err - } + pullChart := false + deleteChart := false + if uv[latestVersion] == 1 { + pullChart = true + } + if uv[oldVersion] == 0 { + deleteChart = true + } - latestVersion, err := chart.QueryLatestVersion(ctx) - if err != nil { - return doError(err) + err = cmd.pullAndCommitCharts(ctx, projectDir, baseChartsDir, gitRootPath, hr, oldVersion, latestVersion, pullChart, deleteChart) + if err != nil { + return err + } } - s.Success() - return latestVersion, nil + return errs.ErrorOrNil() } -func (cmd *helmUpdateCmd) collectFiles(root string, dir string, m map[string]os.FileInfo) error { +func (cmd *helmUpdateCmd) collectFiles(root string, dir string, m map[string]bool) error { err := filepath.WalkDir(dir, func(p string, d fs.DirEntry, err error) error { if d == nil || d.IsDir() { return nil @@ -224,11 +231,7 @@ func (cmd *helmUpdateCmd) collectFiles(root string, dir string, m map[string]os. if _, ok := m[relToGit]; ok { return nil } - st, err := d.Info() - if err != nil { - return err - } - m[relToGit] = st + m[relToGit] = true return nil }) if os.IsNotExist(err) { @@ -237,136 +240,109 @@ func (cmd *helmUpdateCmd) collectFiles(root string, dir string, m map[string]os. return err } -func (cmd *helmUpdateCmd) pullAndCommitCharts(ctx context.Context, gitRootPath string, uc *updatedChart, toKeep map[string]bool, mutex *sync.Mutex) error { - statusPrefix := uc.charts[0].GetChartName() +func (cmd *helmUpdateCmd) pullAndCommitCharts(ctx context.Context, projectDir string, baseChartsDir string, gitRootPath string, hr *helm.Release, oldVersion string, newVersion string, pullChart bool, deleteChart bool) error { + relDir, err := filepath.Rel(projectDir, filepath.Dir(hr.ConfigFile)) + if err != nil { + return err + } - s := status.Start(ctx, "%s: Pulling Chart with version %s", statusPrefix, uc.newVersion) + s := status.Start(ctx, "%s: Committing Chart with version %s", relDir, newVersion) defer s.Failed() doError := func(err error) error { - s.FailedWithMessage("%s: %s", statusPrefix, err.Error()) + s.FailedWithMessage("%s: %s", relDir, err.Error()) return err } - var toAdd []string - var oldVersions []string - var oldChartDirs []string - - // we need to list all files contained inside the charts dir BEFORE doing the pull, so that we later - // know what got deleted - files := map[string]os.FileInfo{} - - for _, chart := range uc.charts { - oldChartDirs = append(oldChartDirs, chart.GetChartDir(true)) - oldVersions = append(oldVersions, chart.Config.ChartVersion) - - chart.Config.ChartVersion = uc.newVersion - err := chart.Save() - if err != nil { - return doError(err) - } - - // add helm-chart.yaml - relToGit, err := filepath.Rel(gitRootPath, chart.ConfigFile) - if err != nil { - return doError(err) - } - toAdd = append(toAdd, relToGit) - - err = cmd.collectFiles(gitRootPath, chart.GetDeprecatedChartDir(), files) - if err != nil { - return doError(err) - } + r, err := git.PlainOpen(gitRootPath) + if err != nil { + return doError(err) } - - err := cmd.collectFiles(gitRootPath, uc.charts[0].GetChartDir(true), files) + wt, err := r.Worktree() if err != nil { return doError(err) } - err = uc.charts[0].Pull(ctx) + // add helm-chart.yaml + relToGit, err := filepath.Rel(gitRootPath, hr.ConfigFile) if err != nil { return doError(err) } - - relToGit, err := filepath.Rel(gitRootPath, uc.charts[0].GetChartDir(true)) + _, err = wt.Add(relToGit) if err != nil { return doError(err) } - toAdd = append(toAdd, relToGit) - // delete old versions - for i, chart := range uc.charts { - deleteOldVersion := !toKeep[buildKey(chart, &oldVersions[i], nil)] - if deleteOldVersion { - err = os.RemoveAll(oldChartDirs[i]) - if err != nil { - return doError(err) - } + if deleteChart { + chartDir, err := hr.Chart.BuildPulledChartDir(baseChartsDir, oldVersion) + if err != nil { + return doError(err) } - - err = os.RemoveAll(chart.GetDeprecatedChartDir()) + relChartDir, err := filepath.Rel(gitRootPath, chartDir) + if err != nil { + return doError(err) + } + _, err = wt.Remove(relChartDir) + if err != nil && err != index.ErrEntryNotFound { + return doError(err) + } + err = os.RemoveAll(chartDir) if err != nil { return doError(err) } } - // figure out what got deleted - for p, oldSt := range files { - st, err := os.Lstat(filepath.Join(gitRootPath, p)) + if pullChart { + chartDir, err := hr.Chart.BuildPulledChartDir(baseChartsDir, newVersion) if err != nil { - if os.IsNotExist(err) { - toAdd = append(toAdd, p) - continue - } return doError(err) } - if st.Mode() == oldSt.Mode() && st.ModTime() == oldSt.ModTime() && st.Size() == oldSt.Size() { - continue + relChartDir, err := filepath.Rel(gitRootPath, chartDir) + if err != nil { + return doError(err) } - toAdd = append(toAdd, p) - } - s.UpdateAndInfoFallback("%s: Committing chart", statusPrefix) + // we need to list all files contained inside the charts dir BEFORE doing the pull, so that we later + // know what got deleted + files := map[string]bool{} + err = cmd.collectFiles(gitRootPath, chartDir, files) + if err != nil { + return doError(err) + } - mutex.Lock() - defer mutex.Unlock() + _, err = hr.Chart.PullInProject(ctx, baseChartsDir, newVersion) + if err != nil { + return doError(err) + } - r, err := git.PlainOpen(gitRootPath) - if err != nil { - return doError(err) - } - wt, err := r.Worktree() - if err != nil { - return doError(err) - } + _, err = wt.Add(relChartDir) + if err != nil { + return doError(err) + } - for _, p := range toAdd { - // we have to retry a few times as Add() might fail with "no such file or directly" - // This is because it internally tries to get the git status, which fails if files are added/deleted in - // parallel by another goroutine (we're pulling in parallel). We're guarding the repo via the mutex from above - // so this is actually safe. - for i := 0; i < 10; i++ { - _, err = wt.Add(p) - if err == nil || !os.IsNotExist(err) { - break + // figure out what got deleted + for p := range files { + _, err := os.Lstat(filepath.Join(gitRootPath, p)) + if err != nil { + if os.IsNotExist(err) { + _, err = wt.Remove(p) + if err != nil { + return doError(err) + } + } else { + return doError(err) + } } - // let's have some randomness in waiting time to ensure we don't run into the same problem again and again - s := time.Duration(rand.Intn(10) + 10) - time.Sleep(s * time.Millisecond) - } - if err != nil { - return doError(fmt.Errorf("failed to add %s to git index: %w", p, err)) } } - commitMsg := fmt.Sprintf("Updated helm chart %s to version %s", statusPrefix, uc.newVersion) + commitMsg := fmt.Sprintf("Updated helm chart %s to version %s", relToGit, newVersion) _, err = wt.Commit(commitMsg, &git.CommitOptions{}) if err != nil { return doError(fmt.Errorf("failed to commit: %w", err)) } - s.Update("%s: Committed helm chart with version %s", statusPrefix, uc.newVersion) + s.Update("%s: Committed helm chart with version %s", relToGit, newVersion) s.Success() return nil diff --git a/e2e/helm_test.go b/e2e/helm_test.go index 3fe9dad20..682163cca 100644 --- a/e2e/helm_test.go +++ b/e2e/helm_test.go @@ -1,11 +1,14 @@ package e2e import ( + "fmt" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing/object" test_utils "github.com/kluctl/kluctl/v2/e2e/test-utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/stretchr/testify/assert" + "net/url" + "os" "path/filepath" "sort" "testing" @@ -71,18 +74,18 @@ func testHelmPull(t *testing.T, tc testCase, prePull bool) { return } else { assert.NoError(t, err) - assert.FileExists(t, filepath.Join(p.LocalRepoDir(), "helm1/charts/test-chart1/Chart.yaml")) + assert.FileExists(t, getChartFile(t, p, repoUrl, "test-chart1", "0.1.0")) } } args := []string{"deploy", "--yes", "-t", "test"} args = append(args, tc.extraArgs...) _, stderr, err := p.Kluctl(args...) - prePullWarning := "Warning, need to pull Helm Chart test-chart1 with version 0.1.0." + pullMessage := "Pulling Helm Chart test-chart1 with version 0.1.0" if prePull { - assert.NotContains(t, stderr, prePullWarning) + assert.NotContains(t, stderr, pullMessage) } else { - assert.Contains(t, stderr, prePullWarning) + assert.Contains(t, stderr, pullMessage) } if tc.expectedError != "" { assert.Error(t, err) @@ -137,7 +140,7 @@ func testHelmManualUpgrade(t *testing.T, oci bool) { p.AddHelmDeployment("helm1", repoUrl, "test-chart1", "0.1.0", "test-helm1", p.TestSlug(), nil) p.KluctlMust("helm-pull") - assert.FileExists(t, filepath.Join(p.LocalRepoDir(), "helm1/charts/test-chart1/Chart.yaml")) + assert.FileExists(t, getChartFile(t, p, repoUrl, "test-chart1", "0.1.0")) p.KluctlMust("deploy", "--yes", "-t", "test") cm := assertConfigMapExists(t, k, p.TestSlug(), "test-helm1-test-chart1") v, _, _ := cm.GetNestedString("data", "version") @@ -149,6 +152,8 @@ func testHelmManualUpgrade(t *testing.T, oci bool) { }, "") p.KluctlMust("helm-pull") + assert.NoFileExists(t, getChartFile(t, p, repoUrl, "test-chart1", "0.1.0")) + assert.FileExists(t, getChartFile(t, p, repoUrl, "test-chart1", "0.2.0")) p.KluctlMust("deploy", "--yes", "-t", "test") cm = assertConfigMapExists(t, k, p.TestSlug(), "test-helm1-test-chart1") v, _, _ = cm.GetNestedString("data", "version") @@ -190,9 +195,8 @@ func testHelmUpdate(t *testing.T, oci bool, upgrade bool, commit bool) { }, "") p.KluctlMust("helm-pull") - assert.FileExists(t, filepath.Join(p.LocalRepoDir(), "helm1/charts/test-chart1/Chart.yaml")) - assert.FileExists(t, filepath.Join(p.LocalRepoDir(), "helm2/charts/test-chart2/Chart.yaml")) - assert.FileExists(t, filepath.Join(p.LocalRepoDir(), "helm3/charts/test-chart1/Chart.yaml")) + assert.FileExists(t, getChartFile(t, p, repoUrl, "test-chart1", "0.1.0")) + assert.FileExists(t, getChartFile(t, p, repoUrl, "test-chart2", "0.1.0")) args := []string{"helm-update"} if upgrade { @@ -203,28 +207,28 @@ func testHelmUpdate(t *testing.T, oci bool, upgrade bool, commit bool) { } _, stderr := p.KluctlMust(args...) - assert.Contains(t, stderr, "helm1: Chart has new version 0.2.0 available.") - assert.Contains(t, stderr, "helm2: Chart has new version 0.3.0 available.") - assert.Contains(t, stderr, "helm3: Chart has new version 0.2.0 available. Old version is 0.1.0. skipUpdate is set to true.") + assert.Contains(t, stderr, "helm1: Chart has new version 0.2.0 available") + assert.Contains(t, stderr, "helm2: Chart has new version 0.3.0 available") + assert.Contains(t, stderr, "helm3: Skipped update to version 0.2.0") - c1, err := uo.FromFile(filepath.Join(p.LocalRepoDir(), "helm1/charts/test-chart1/Chart.yaml")) - assert.NoError(t, err) - c2, err := uo.FromFile(filepath.Join(p.LocalRepoDir(), "helm2/charts/test-chart2/Chart.yaml")) - assert.NoError(t, err) - c3, err := uo.FromFile(filepath.Join(p.LocalRepoDir(), "helm3/charts/test-chart1/Chart.yaml")) - assert.NoError(t, err) - - v1, _, _ := c1.GetNestedString("version") - v2, _, _ := c2.GetNestedString("version") - v3, _, _ := c3.GetNestedString("version") if upgrade { - assert.Equal(t, "0.2.0", v1) - assert.Equal(t, "0.3.0", v2) - assert.Equal(t, "0.1.0", v3) + assert.Contains(t, stderr, "helm1: Updated Chart version to 0.2.0") + assert.Contains(t, stderr, "helm2: Updated Chart version to 0.3.0") + } + if commit { + assert.Contains(t, stderr, "helm1: Committing Chart with version 0.2.0") + assert.Contains(t, stderr, "helm2: Committing Chart with version 0.3.0") + } + + pulledVersions1 := listChartVersions(t, p, repoUrl, "test-chart1") + pulledVersions2 := listChartVersions(t, p, repoUrl, "test-chart2") + + if commit { + assert.Equal(t, []string{"0.1.0", "0.2.0"}, pulledVersions1) + assert.Equal(t, []string{"0.3.0"}, pulledVersions2) } else { - assert.Equal(t, "0.1.0", v1) - assert.Equal(t, "0.1.0", v2) - assert.Equal(t, "0.1.0", v3) + assert.Equal(t, []string{"0.1.0"}, pulledVersions1) + assert.Equal(t, []string{"0.1.0"}, pulledVersions2) } if commit { @@ -244,8 +248,8 @@ func testHelmUpdate(t *testing.T, oci bool, upgrade bool, commit bool) { return commitList[i].Message < commitList[j].Message }) - assert.Equal(t, "Updated helm chart helm1 from 0.1.0 to 0.2.0", commitList[0].Message) - assert.Equal(t, "Updated helm chart helm2 from 0.1.0 to 0.3.0", commitList[1].Message) + assert.Equal(t, "Updated helm chart helm1/helm-chart.yaml to version 0.2.0", commitList[0].Message) + assert.Equal(t, "Updated helm chart helm2/helm-chart.yaml to version 0.3.0", commitList[1].Message) } } @@ -312,9 +316,9 @@ func testHelmUpdateConstraints(t *testing.T, oci bool) { args := []string{"helm-update", "--upgrade"} _, stderr := p.KluctlMust(args...) - assert.Contains(t, stderr, "helm1: Chart has new version 0.1.1 available.") - assert.Contains(t, stderr, "helm2: Chart has new version 0.2.0 available.") - assert.Contains(t, stderr, "helm3: Chart has new version 1.2.1 available.") + assert.Contains(t, stderr, "helm1: Chart has new version 0.1.1 available") + assert.Contains(t, stderr, "helm2: Chart has new version 0.2.0 available") + assert.Contains(t, stderr, "helm3: Chart has new version 1.2.1 available") c1 := p.GetYaml("helm1/helm-chart.yaml") c2 := p.GetYaml("helm2/helm-chart.yaml") @@ -468,3 +472,36 @@ func TestHelmRenderOfflineKubernetes(t *testing.T) { "kubeVersion": "v1.22.1", }, cm1.Object["data"]) } + +func getChartDir(t *testing.T, p *test_utils.TestProject, url2 string, chartName string, chartVersion string) string { + u, err := url.Parse(url2) + if err != nil { + t.Fatal(err) + } + var dir string + if u.Scheme == "oci" { + dir = filepath.Join(p.LocalRepoDir(), ".helm-charts", fmt.Sprintf("%s_%s", u.Scheme, u.Hostname()), chartName) + } else { + dir = filepath.Join(p.LocalRepoDir(), ".helm-charts", fmt.Sprintf("%s_%s_%s", u.Scheme, u.Port(), u.Hostname()), chartName) + } + if chartVersion != "" { + dir = filepath.Join(dir, chartVersion) + } + return dir +} + +func getChartFile(t *testing.T, p *test_utils.TestProject, url2 string, chartName string, chartVersion string) string { + return filepath.Join(getChartDir(t, p, url2, chartName, chartVersion), "Chart.yaml") +} + +func listChartVersions(t *testing.T, p *test_utils.TestProject, url2 string, chartName string) []string { + des, err := os.ReadDir(getChartDir(t, p, url2, chartName, "")) + assert.NoError(t, err) + + var versions []string + for _, de := range des { + versions = append(versions, de.Name()) + } + sort.Strings(versions) + return versions +} diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 72688db84..05df656bd 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -193,29 +193,27 @@ func (di *DeploymentItem) renderHelmCharts() error { return err } - chart, err := helm.NewHelmChart(di.ctx.HelmChartsDir, p) + hr, err := helm.NewRelease(p, di.ctx.HelmChartsDir, di.ctx.HelmCredentials) if err != nil { return err } - chart.SetCredentials(di.ctx.HelmCredentials) - ky, err := di.readKustomizationYaml(subDir) if err == nil && ky != nil { resources, _, _ := ky.GetNestedStringList("resources") found := false for _, r := range resources { - if r == chart.GetOutputPath() { + if r == hr.GetOutputPath() { found = true break } } if !found { - return fmt.Errorf("%s/kustomization.yaml does not include the rendered helm chart: %s", di.RelRenderedDir, chart.GetOutputPath()) + return fmt.Errorf("%s/kustomization.yaml does not include the rendered helm chart: %s", di.RelRenderedDir, hr.GetOutputPath()) } } - return chart.Render(di.ctx.Ctx, di.ctx.K, di.ctx.K8sVersion, di.ctx.SopsDecrypter) + return hr.Render(di.ctx.Ctx, di.ctx.K, di.ctx.K8sVersion, di.ctx.SopsDecrypter) }) if err != nil { return err @@ -418,7 +416,7 @@ func (di *DeploymentItem) generateKustomizationYaml(subDir string) (*uo.Unstruct if di.isHelmValuesYaml(de.Name()) { continue } else if di.isHelmChartYaml(de.Name()) { - c, err := helm.NewHelmChart(di.ctx.HelmChartsDir, filepath.Join(di.RenderedDir, subDir, de.Name())) + c, err := helm.NewRelease(filepath.Join(di.RenderedDir, subDir, de.Name()), di.ctx.HelmChartsDir, nil) if err != nil { return nil, err } diff --git a/pkg/helm/chart.go b/pkg/helm/chart.go new file mode 100644 index 000000000..c5c29e54b --- /dev/null +++ b/pkg/helm/chart.go @@ -0,0 +1,409 @@ +package helm + +import ( + "context" + "fmt" + "github.com/Masterminds/semver/v3" + "github.com/kluctl/kluctl/v2/pkg/registries" + "github.com/kluctl/kluctl/v2/pkg/status" + "github.com/kluctl/kluctl/v2/pkg/utils" + cp "github.com/otiai10/copy" + "github.com/rogpeppe/go-internal/lockedfile" + "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/cli" + "helm.sh/helm/v3/pkg/getter" + "helm.sh/helm/v3/pkg/registry" + "helm.sh/helm/v3/pkg/repo" + "net/url" + "os" + "path/filepath" + "regexp" + "sort" + "strings" +) + +type HelmCredentialsProvider interface { + FindCredentials(repoUrl string, credentialsId *string) *repo.Entry +} + +type Chart struct { + repo string + chartName string + + credentials HelmCredentialsProvider + credentialsId string + + versions []string +} + +func NewChart(repo string, chartName string, credentialsProvider HelmCredentialsProvider, credentialsId string) (*Chart, error) { + hc := &Chart{ + repo: repo, + credentials: credentialsProvider, + credentialsId: credentialsId, + } + + if repo == "" { + return nil, fmt.Errorf("repo is missing") + } + + if registry.IsOCI(repo) { + if chartName != "" { + return nil, fmt.Errorf("chartName can't be specified when using OCI repos") + } + s := strings.Split(repo, "/") + chartName := s[len(s)-1] + if m, _ := regexp.MatchString(`[a-zA-Z_-]+`, chartName); !m { + return nil, fmt.Errorf("invalid oci chart url: %s", repo) + } + hc.chartName = chartName + } else if chartName == "" { + return nil, fmt.Errorf("chartName is missing") + } else { + hc.chartName = chartName + } + + return hc, nil +} + +func (c *Chart) GetRepo() string { + return c.repo +} + +func (c *Chart) BuildPulledChartDir(baseDir string, version string) (string, error) { + u, err := url.Parse(c.repo) + if err != nil { + return "", err + } + + scheme := "" + port := "" + switch { + case u.Scheme == "oci": + scheme = "oci" + case u.Scheme == "http": + scheme = "http" + if u.Port() != "80" { + port = u.Port() + } + case u.Scheme == "https": + scheme = "https" + if u.Port() != "443" { + port = u.Port() + } + default: + return "", fmt.Errorf("unsupported scheme in %s", u.String()) + } + if port != "" { + scheme += "_" + port + } + + dir := filepath.Join( + baseDir, + fmt.Sprintf("%s_%s", scheme, strings.ToLower(u.Hostname())), + filepath.FromSlash(strings.ToLower(u.Path)), + ) + if u.Scheme != "oci" { + dir = filepath.Join(dir, c.chartName) + } + if version != "" { + dir = filepath.Join(dir, version) + } + err = utils.CheckInDir(baseDir, dir) + if err != nil { + return "", err + } + + return dir, nil +} + +func (c *Chart) GetChartName() string { + return c.chartName +} + +func (c *Chart) PullToTmp(ctx context.Context, version string) (*PulledChart, error) { + tmpPullDir, err := os.MkdirTemp(utils.GetTmpBaseDir(ctx), c.chartName+"-pull-") + if err != nil { + return nil, err + } + defer os.RemoveAll(tmpPullDir) + + cfg, err := buildHelmConfig(nil) + if err != nil { + return nil, err + } + a := action.NewPullWithOpts(action.WithConfig(cfg)) + a.Settings = cli.New() + a.Untar = true + a.DestDir = tmpPullDir + a.Version = version + + if c.credentialsId != "" { + if registry.IsOCI(c.repo) { + return nil, fmt.Errorf("OCI charts can currently only be authenticated via registry login and not via cli arguments") + } + if c.credentials == nil { + return nil, fmt.Errorf("no credentials provider") + } + } + + if c.credentials != nil { + p := &c.credentialsId + if c.credentialsId == "" { + p = nil + } + creds := c.credentials.FindCredentials(c.repo, p) + if creds != nil { + a.Username = creds.Username + a.Password = creds.Password + a.CertFile = creds.CertFile + a.CaFile = creds.CAFile + a.KeyFile = creds.KeyFile + a.InsecureSkipTLSverify = creds.InsecureSkipTLSverify + a.PassCredentialsAll = creds.PassCredentialsAll + } + } + + var out string + if registry.IsOCI(c.repo) { + out, err = a.Run(c.repo) + } else { + a.RepoURL = c.repo + out, err = a.Run(c.chartName) + } + if out != "" { + status.PlainText(ctx, out) + } + if err != nil { + return nil, err + } + + chartDir, err := os.MkdirTemp(utils.GetTmpBaseDir(ctx), c.chartName+"-pulled-") + if err != nil { + return nil, err + } + + // move chart + des, err := os.ReadDir(filepath.Join(tmpPullDir, c.chartName)) + if err != nil { + return nil, err + } + for _, de := range des { + err = os.Rename(filepath.Join(tmpPullDir, c.chartName, de.Name()), filepath.Join(chartDir, de.Name())) + if err != nil { + return nil, err + } + } + + return NewPulledChart(c, version, chartDir, true), nil +} + +func (c *Chart) Pull(ctx context.Context, pc *PulledChart) error { + newPulled, err := c.PullToTmp(ctx, pc.version) + if err != nil { + return err + } + defer os.RemoveAll(newPulled.dir) + + err = os.RemoveAll(pc.dir) + if err != nil { + return err + } + + _ = os.MkdirAll(filepath.Dir(pc.dir), 0o755) + + err = cp.Copy(newPulled.dir, pc.dir) + if err != nil { + return err + } + + return nil +} + +func (c *Chart) doPullCached(ctx context.Context, version string) (*PulledChart, *lockedfile.File, error) { + baseDir := filepath.Join(utils.GetTmpBaseDir(ctx), "helm-charts") + cacheDir, err := c.BuildPulledChartDir(baseDir, version) + _ = os.MkdirAll(cacheDir, 0o755) + + lock, err := lockedfile.Create(cacheDir + ".lock") + if err != nil { + return nil, nil, err + } + + cached := NewPulledChart(c, version, cacheDir, true) + needsPull, _, _, err := cached.CheckNeedsPull() + if err != nil { + _ = lock.Close() + return nil, nil, err + } + if !needsPull { + return cached, lock, nil + } + + err = c.Pull(ctx, cached) + if err != nil { + _ = lock.Close() + return nil, nil, err + } + + return cached, lock, nil +} + +func (c *Chart) PullCached(ctx context.Context, version string) (*PulledChart, error) { + pc, lock, err := c.doPullCached(ctx, version) + if err != nil { + return nil, err + } + _ = lock.Close() + return pc, nil +} + +func (c *Chart) PullInProject(ctx context.Context, baseDir string, version string) (*PulledChart, error) { + cachePc, lock, err := c.doPullCached(ctx, version) + if err != nil { + return nil, err + } + defer lock.Close() + + pc, err := c.GetPulledChart(baseDir, version) + if err != nil { + return nil, err + } + + err = os.RemoveAll(pc.dir) + if err != nil { + return nil, err + } + + err = cp.Copy(cachePc.dir, pc.dir) + if err != nil { + return nil, err + } + + return pc, nil +} + +func (c *Chart) GetPulledChart(baseDir string, version string) (*PulledChart, error) { + chartDir, err := c.BuildPulledChartDir(baseDir, version) + if err != nil { + return nil, err + } + return NewPulledChart(c, version, chartDir, false), nil +} + +func (c *Chart) QueryVersions(ctx context.Context) error { + if registry.IsOCI(c.repo) { + return c.queryVersionsOci(ctx) + } + return c.queryVersionsHelmRepo(ctx) +} + +func (c *Chart) queryVersionsOci(ctx context.Context) error { + rh := registries.NewRegistryHelper(ctx) + + imageName := strings.TrimPrefix(c.repo, "oci://") + tags, err := rh.ListImageTags(imageName) + if err != nil { + return err + } + + c.versions = tags + + return nil +} + +func (c *Chart) queryVersionsHelmRepo(ctx context.Context) error { + settings := cli.New() + + var e *repo.Entry + if c.credentialsId != "" { + if c.credentials == nil { + return fmt.Errorf("no credentials provider") + } + e = c.credentials.FindCredentials(c.repo, &c.credentialsId) + if e == nil { + return fmt.Errorf("no credentials provided for Chart %s", c.chartName) + } + } else { + e = &repo.Entry{ + URL: c.repo, + } + } + + r, err := repo.NewChartRepository(e, getter.All(settings)) + if err != nil { + return err + } + + r.CachePath, err = os.MkdirTemp(utils.GetTmpBaseDir(ctx), "helm-check-update-") + if err != nil { + return err + } + defer os.RemoveAll(r.CachePath) + + indexFile, err := r.DownloadIndexFile() + if err != nil { + return err + } + + index, err := repo.LoadIndexFile(indexFile) + if err != nil { + return err + } + + indexEntry, ok := index.Entries[c.chartName] + if !ok || len(indexEntry) == 0 { + return fmt.Errorf("helm chart %s not found in Repo index", c.chartName) + } + + c.versions = make([]string, 0, indexEntry.Len()) + for _, x := range indexEntry { + c.versions = append(c.versions, x.Version) + } + + return nil +} + +func (c *Chart) GetLatestVersion(constraints *string) (string, error) { + if len(c.versions) == 0 { + return "", fmt.Errorf("no versions found or queried") + } + + var err error + var updateConstraints *semver.Constraints + if constraints != nil { + updateConstraints, err = semver.NewConstraint(*constraints) + if err != nil { + return "", fmt.Errorf("invalid constraints '%s': %w", *constraints, err) + } + } + + var versions semver.Collection + for _, x := range c.versions { + v, err := semver.NewVersion(x) + if err != nil { + continue + } + + if updateConstraints == nil { + if v.Prerelease() != "" { + // we don't allow pre-releases by default. To allow pre-releases, use 1.0.0-0 as constraint + continue + } + } else if !updateConstraints.Check(v) { + continue + } + versions = append(versions, v) + } + if len(versions) == 0 { + if constraints == nil { + return "", fmt.Errorf("no version found") + } else { + return "", fmt.Errorf("no version found that satisfies constraints '%s'", *constraints) + } + } + + sort.Stable(versions) + latestVersion := versions[len(versions)-1].Original() + return latestVersion, nil +} diff --git a/pkg/helm/helm_chart.go b/pkg/helm/helm_chart.go deleted file mode 100644 index 6c1f8e4dd..000000000 --- a/pkg/helm/helm_chart.go +++ /dev/null @@ -1,673 +0,0 @@ -package helm - -import ( - "context" - "crypto/sha256" - "encoding/hex" - "fmt" - "github.com/Masterminds/semver/v3" - securejoin "github.com/cyphar/filepath-securejoin" - "github.com/kluctl/kluctl/v2/pkg/k8s" - "github.com/kluctl/kluctl/v2/pkg/registries" - "github.com/kluctl/kluctl/v2/pkg/sops" - "github.com/kluctl/kluctl/v2/pkg/status" - "github.com/kluctl/kluctl/v2/pkg/types" - "github.com/kluctl/kluctl/v2/pkg/utils" - "github.com/kluctl/kluctl/v2/pkg/utils/uo" - "github.com/kluctl/kluctl/v2/pkg/yaml" - "github.com/pkg/errors" - "github.com/rogpeppe/go-internal/lockedfile" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/cli/values" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/registry" - "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/repo" - "k8s.io/apimachinery/pkg/runtime/schema" - "net/url" - "os" - "path/filepath" - "regexp" - "sort" - "strings" - "time" -) - -type HelmCredentialsProvider interface { - FindCredentials(repoUrl string, credentialsId *string) *repo.Entry -} - -type HelmChart struct { - ConfigFile string - Config *types.HelmChartConfig - - credentials HelmCredentialsProvider - - chartName string - chartDir string - deprecatedChartDir string - - versions []string -} - -func NewHelmChart(baseChartsDir string, configFile string) (*HelmChart, error) { - var config types.HelmChartConfig - err := yaml.ReadYamlFile(configFile, &config) - if err != nil { - return nil, err - } - - _, err = semver.NewVersion(config.ChartVersion) - if err != nil { - return nil, fmt.Errorf("invalid chart version '%s': %w", config.ChartVersion, err) - } - - hc := &HelmChart{ - ConfigFile: configFile, - Config: &config, - } - - if registry.IsOCI(hc.Config.Repo) { - s := strings.Split(hc.Config.Repo, "/") - chartName := s[len(s)-1] - if m, _ := regexp.MatchString(`[a-zA-Z_-]+`, chartName); !m { - return nil, fmt.Errorf("invalid oci chart url: %s", hc.Config.Repo) - } - hc.chartName = chartName - } else if hc.Config.ChartName == "" { - return nil, fmt.Errorf("chartName is missing in helm-chart.yml") - } else { - hc.chartName = hc.Config.ChartName - } - - dir := filepath.Dir(configFile) - chartDir := filepath.Join(dir, "charts") - chartDir, err = securejoin.SecureJoin(chartDir, hc.chartName) - if err != nil { - return nil, err - } - hc.deprecatedChartDir = chartDir - - hc.chartDir, err = hc.buildChartDir(baseChartsDir) - if err != nil { - return nil, err - } - - return hc, nil -} - -func (c *HelmChart) buildChartDir(baseChartsDir string) (string, error) { - u, err := url.Parse(c.Config.Repo) - if err != nil { - return "", err - } - - scheme := "" - port := "" - switch { - case registry.IsOCI(c.Config.Repo): - scheme = "oci" - case u.Scheme == "http": - scheme = "http" - if u.Port() != "80" { - port = u.Port() - } - case u.Scheme == "https": - scheme = "https" - if u.Port() != "443" { - port = u.Port() - } - default: - return "", fmt.Errorf("unsupported scheme in %s", u.String()) - } - if port != "" { - scheme += "_" + port - } - - dir := filepath.Join( - baseChartsDir, - fmt.Sprintf("%s_%s", scheme, strings.ToLower(u.Hostname())), - filepath.FromSlash(strings.ToLower(u.Path)), - c.chartName, - ) - err = utils.CheckInDir(baseChartsDir, dir) - if err != nil { - return "", err - } - - return dir, nil -} - -func (c *HelmChart) GetChartName() string { - return c.chartName -} - -func (c *HelmChart) GetChartDir(withVersion bool) string { - if withVersion { - return filepath.Join(c.chartDir, c.Config.ChartVersion) - } - return c.chartDir -} - -func (c *HelmChart) GetDeprecatedChartDir() string { - return c.deprecatedChartDir -} - -func (c *HelmChart) GetOutputPath() string { - output := "helm-rendered.yaml" - if c.Config.Output != nil { - output = *c.Config.Output - } - return output -} - -func (c *HelmChart) GetFullOutputPath() (string, error) { - dir := filepath.Dir(c.ConfigFile) - return securejoin.SecureJoin(dir, c.GetOutputPath()) -} - -func (c *HelmChart) buildHelmConfig(k *k8s.K8sCluster) (*action.Configuration, error) { - rc, err := registry.NewClient() - if err != nil { - return nil, err - } - - return &action.Configuration{ - RESTClientGetter: k, - RegistryClient: rc, - }, nil -} - -func (c *HelmChart) checkNeedsPull(chartDir string, isTmp bool) (bool, bool, string, error) { - if !utils.IsDirectory(chartDir) { - return true, false, "", nil - } - - chartYamlPath := yaml.FixPathExt(filepath.Join(chartDir, "Chart.yaml")) - st, err := os.Stat(chartYamlPath) - if err != nil { - if os.IsNotExist(err) { - return true, false, "", nil - } - return false, false, "", err - } - if isTmp && time.Now().Sub(st.ModTime()) >= time.Hour*24 { - // MacOS will delete tmp files after 3 days, so lets be safe and re-pull every day - return true, false, "", nil - } - - chartYaml, err := uo.FromFile(chartYamlPath) - if err != nil { - return false, false, "", err - } - - version, _, _ := chartYaml.GetNestedString("version") - if version != c.Config.ChartVersion { - return true, true, version, nil - } - return false, false, "", nil -} - -func (c *HelmChart) Pull(ctx context.Context) error { - if utils.Exists(c.GetDeprecatedChartDir()) { - err := os.RemoveAll(c.GetDeprecatedChartDir()) - if err != nil { - return err - } - } - - return c.doPull(ctx, c.GetChartDir(true)) -} - -func (c *HelmChart) pullTmpChart(ctx context.Context) (string, error) { - hash := sha256.New() - _, _ = fmt.Fprintf(hash, "%s\n", c.Config.Repo) - _, _ = fmt.Fprintf(hash, "%s\n", c.chartName) - _, _ = fmt.Fprintf(hash, "%s\n", c.Config.ChartVersion) - h := hex.EncodeToString(hash.Sum(nil)) - tmpDir := filepath.Join(utils.GetTmpBaseDir(ctx), "helm-charts") - _ = os.MkdirAll(tmpDir, 0o755) - tmpDir = filepath.Join(tmpDir, fmt.Sprintf("%s-%s", c.chartName, h)) - - lockFile := tmpDir + ".lock" - lock, err := lockedfile.Create(lockFile) - if err != nil { - return "", err - } - defer lock.Close() - - needsPull, _, _, err := c.checkNeedsPull(tmpDir, true) - if err != nil { - return "", err - } - if !needsPull { - return tmpDir, nil - } - err = c.doPull(ctx, tmpDir) - if err != nil { - return "", err - } - return tmpDir, nil -} - -func (c *HelmChart) doPull(ctx context.Context, chartDir string) error { - baseDir := filepath.Dir(chartDir) - - _ = os.RemoveAll(chartDir) - _ = os.MkdirAll(baseDir, 0o755) - - // need to use the same filesystem/volume that we later os.Rename the final pull chart to, as otherwise - // the rename operation will lead to errors - tmpDir, err := os.MkdirTemp(baseDir, c.chartName+"-pull-") - if err != nil { - return err - } - defer os.RemoveAll(tmpDir) - - tmpChartDir := filepath.Join(tmpDir, c.chartName) - - cfg, err := c.buildHelmConfig(nil) - if err != nil { - return err - } - a := action.NewPullWithOpts(action.WithConfig(cfg)) - a.Settings = cli.New() - a.Untar = true - a.DestDir = tmpDir - a.Version = c.Config.ChartVersion - - if c.Config.CredentialsId != nil { - if registry.IsOCI(c.Config.Repo) { - return fmt.Errorf("OCI charts can currently only be authenticated via registry login and not via cli arguments") - } - if c.credentials == nil { - return fmt.Errorf("no credentials provider") - } - } - - if c.credentials != nil { - creds := c.credentials.FindCredentials(c.Config.Repo, c.Config.CredentialsId) - if creds != nil { - a.Username = creds.Username - a.Password = creds.Password - a.CertFile = creds.CertFile - a.CaFile = creds.CAFile - a.KeyFile = creds.KeyFile - a.InsecureSkipTLSverify = creds.InsecureSkipTLSverify - a.PassCredentialsAll = creds.PassCredentialsAll - } - } - - var out string - if registry.IsOCI(c.Config.Repo) { - out, err = a.Run(c.Config.Repo) - } else { - a.RepoURL = c.Config.Repo - out, err = a.Run(c.chartName) - } - if err != nil { - return err - } - - // a bug in the Pull command causes this directory to be created by accident - _ = os.RemoveAll(tmpChartDir + fmt.Sprintf("-%s.tar.gz", a.Version)) - _ = os.RemoveAll(tmpChartDir + fmt.Sprintf("-%s.tgz", a.Version)) - if out != "" { - status.PlainText(ctx, out) - } - - err = os.Rename(tmpChartDir, chartDir) - if err != nil { - return err - } - - return nil -} - -func (c *HelmChart) QueryLatestVersion(ctx context.Context) (string, error) { - if registry.IsOCI(c.Config.Repo) { - return c.queryLatestVersionOci(ctx) - } - return c.queryLatestVersionHelmRepo(ctx) -} - -func (c *HelmChart) queryLatestVersionOci(ctx context.Context) (string, error) { - rh := registries.NewRegistryHelper(ctx) - - imageName := strings.TrimPrefix(c.Config.Repo, "oci://") - tags, err := rh.ListImageTags(imageName) - if err != nil { - return "", err - } - - latestVersion, err := c.findLatestVersion(tags) - if err != nil { - return "", err - } - - return latestVersion, nil -} - -func (c *HelmChart) queryLatestVersionHelmRepo(ctx context.Context) (string, error) { - settings := cli.New() - - var e *repo.Entry - if c.Config.CredentialsId != nil { - if c.credentials == nil { - return "", fmt.Errorf("no credentials provider") - } - e = c.credentials.FindCredentials(c.Config.Repo, c.Config.CredentialsId) - if e == nil { - return "", fmt.Errorf("no credentials provided for Chart %s", c.chartName) - } - } else { - e = &repo.Entry{ - URL: c.Config.Repo, - } - } - - r, err := repo.NewChartRepository(e, getter.All(settings)) - if err != nil { - return "", err - } - - r.CachePath, err = os.MkdirTemp(utils.GetTmpBaseDir(ctx), "helm-check-update-") - if err != nil { - return "", err - } - defer os.RemoveAll(r.CachePath) - - indexFile, err := r.DownloadIndexFile() - if err != nil { - return "", err - } - - index, err := repo.LoadIndexFile(indexFile) - if err != nil { - return "", err - } - - indexEntry, ok := index.Entries[c.chartName] - if !ok || len(indexEntry) == 0 { - return "", fmt.Errorf("helm chart %s not found in repo index", c.chartName) - } - - versions := make([]string, 0, indexEntry.Len()) - for _, x := range indexEntry { - versions = append(versions, x.Version) - } - - latestVersion, err := c.findLatestVersion(versions) - if err != nil { - return "", err - } - - return latestVersion, nil -} - -func (c *HelmChart) findLatestVersion(inputVersions []string) (string, error) { - var err error - var updateConstraints *semver.Constraints - if c.Config.UpdateConstraints != nil { - updateConstraints, err = semver.NewConstraint(*c.Config.UpdateConstraints) - if err != nil { - return "", fmt.Errorf("invalid update constraints '%s': %w", *c.Config.UpdateConstraints, err) - } - } - - var versions semver.Collection - for _, x := range inputVersions { - v, err := semver.NewVersion(x) - if err != nil { - continue - } - - if updateConstraints == nil { - if v.Prerelease() != "" { - // we don't allow pre-releases by default. To allow pre-releases, use 1.0.0-0 as constraint - continue - } - } else if !updateConstraints.Check(v) { - continue - } - versions = append(versions, v) - } - if len(versions) == 0 { - if c.Config.UpdateConstraints == nil { - return "", fmt.Errorf("no version found") - } else { - return "", fmt.Errorf("no version found that satisfies constraints '%s'", *c.Config.UpdateConstraints) - } - } - - sort.Stable(versions) - latestVersion := versions[len(versions)-1].Original() - return latestVersion, nil -} - -func (c *HelmChart) Render(ctx context.Context, k *k8s.K8sCluster, k8sVersion string, sopsDecrypter sops.SopsDecrypter) error { - deprecatedChartDir := c.GetDeprecatedChartDir() - if utils.IsDirectory(deprecatedChartDir) { - status.Deprecation(ctx, "helm-charts-dir", "Your project has pre-pulled charts located beside the helm-chart.yaml, which is deprecated. "+ - "Please run 'kluctl helm-pull' on your project and ensure that the deprecated charts are removed! Future versions of kluctl will ignore these locations.") - } - - err := c.doRender(ctx, k, k8sVersion, sopsDecrypter) - if err != nil { - return fmt.Errorf("rendering helm chart %s for release %s has failed: %w", c.chartName, c.Config.ReleaseName, err) - } - return nil -} - -func (c *HelmChart) doRender(ctx context.Context, k *k8s.K8sCluster, k8sVersion string, sopsDecrypter sops.SopsDecrypter) error { - chartDir := c.deprecatedChartDir - - needsPull, versionChanged, prePulledVersion, err := c.checkNeedsPull(chartDir, false) - if err != nil { - return err - } - if needsPull { - if versionChanged { - return fmt.Errorf("pre-pulled Helm Chart %s need to be pulled (call 'kluctl helm-pull'). "+ - "Desired version is %s while pre-pulled version is %s", c.chartName, c.Config.ChartVersion, prePulledVersion) - } else { - status.Warning(ctx, "Warning, need to pull Helm Chart %s with version %s. "+ - "Please consider pre-pulling it with 'kluctl helm-pull'", c.chartName, c.Config.ChartVersion) - } - - s := status.Start(ctx, "Pulling Helm Chart %s with version %s", c.chartName, c.Config.ChartVersion) - defer s.Failed() - - chartDir, err = c.pullTmpChart(ctx) - if err != nil { - return err - } - s.Success() - } - - outputPath, err := c.GetFullOutputPath() - if err != nil { - return err - } - valuesPath := yaml.FixPathExt(filepath.Join(filepath.Dir(c.ConfigFile), "helm-values.yml")) - - var gvs []schema.GroupVersion - if k != nil { - gvs, err = k.Resources.GetAllGroupVersions() - if err != nil { - return err - } - } - cfg, err := c.buildHelmConfig(k) - if err != nil { - return err - } - - settings := cli.New() - valueOpts := values.Options{} - - if utils.Exists(valuesPath) { - tmpValues, err := sops.MaybeDecryptFileToTmp(ctx, sopsDecrypter, valuesPath) - if err != nil { - return err - } - defer os.Remove(tmpValues) - valueOpts.ValueFiles = append(valueOpts.ValueFiles, tmpValues) - } - - var kubeVersion *chartutil.KubeVersion - if k != nil { - kubeVersion, err = chartutil.ParseKubeVersion(k.ServerVersion.String()) - if err != nil { - return err - } - } - if k8sVersion != "" { - kubeVersion, err = chartutil.ParseKubeVersion(k8sVersion) - if err != nil { - return err - } - } - - namespace := "default" - if c.Config.Namespace != nil { - namespace = *c.Config.Namespace - } - - client := action.NewInstall(cfg) - client.DryRun = true - client.Namespace = namespace - client.ReleaseName = c.Config.ReleaseName - client.Replace = true - client.ClientOnly = true - client.KubeVersion = kubeVersion - - if c.Config.SkipCRDs { - client.SkipCRDs = true - } else { - client.IncludeCRDs = true - } - for _, gv := range gvs { - client.APIVersions = append(client.APIVersions, gv.String()) - } - - p := getter.All(settings) - vals, err := valueOpts.MergeValues(p) - if err != nil { - return err - } - - // Check chart dependencies to make sure all are present in /charts - chartRequested, err := loader.Load(chartDir) - if err != nil { - return err - } - - if err := checkIfInstallable(chartRequested); err != nil { - return err - } - - if chartRequested.Metadata.Deprecated { - status.Warning(ctx, "Chart %s is deprecated", c.Config.ChartName) - } - - rel, err := client.Run(chartRequested, vals) - if err != nil { - return err - } - - parsed, err := c.parseRenderedManifests(rel.Manifest) - if err != nil { - return err - } - - if !client.DisableHooks { - for _, m := range rel.Hooks { - if isTestHook(m) { - continue - } - parsedHooks, err := c.parseRenderedManifests(m.Manifest) - if err != nil { - return err - } - parsed = append(parsed, parsedHooks...) - } - } - - var fixed []interface{} - for _, o := range parsed { - // "helm install" will deploy resources to the given namespace automatically, but "helm template" does not - // add the necessary namespace in the rendered resources - if k != nil { - err = k8s.UnwrapListItems(o, true, func(o *uo.UnstructuredObject) error { - k.Resources.FixNamespace(o, namespace) - return nil - }) - if err != nil { - return err - } - } - fixed = append(fixed, o) - } - rendered, err := yaml.WriteYamlAllBytes(fixed) - if err != nil { - return err - } - - err = os.WriteFile(outputPath, rendered, 0o600) - if err != nil { - return err - } - return nil -} - -func (c *HelmChart) parseRenderedManifests(s string) ([]*uo.UnstructuredObject, error) { - var parsed []*uo.UnstructuredObject - - duplicatesRemoved, err := yaml.RemoveDuplicateFields(strings.NewReader(s)) - if err != nil { - return nil, err - } - - m, err := yaml.ReadYamlAllBytes(duplicatesRemoved) - if err != nil { - return nil, err - } - for _, x := range m { - x2, ok := x.(map[string]interface{}) - if !ok { - return nil, fmt.Errorf("yaml object is not a map") - } - parsed = append(parsed, uo.FromMap(x2)) - } - return parsed, nil -} - -func (c *HelmChart) SetCredentials(p HelmCredentialsProvider) { - c.credentials = p -} - -func checkIfInstallable(ch *chart.Chart) error { - switch ch.Metadata.Type { - case "", "application": - return nil - } - return errors.Errorf("%s charts are not installable", ch.Metadata.Type) -} - -func isTestHook(h *release.Hook) bool { - for _, e := range h.Events { - if e == release.HookTest { - return true - } - } - return false -} - -func (c *HelmChart) Save() error { - return yaml.WriteYamlFile(c.ConfigFile, c.Config) -} diff --git a/pkg/helm/helm_release.go b/pkg/helm/helm_release.go new file mode 100644 index 000000000..885082193 --- /dev/null +++ b/pkg/helm/helm_release.go @@ -0,0 +1,313 @@ +package helm + +import ( + "context" + "fmt" + "github.com/Masterminds/semver/v3" + securejoin "github.com/cyphar/filepath-securejoin" + "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/sops" + "github.com/kluctl/kluctl/v2/pkg/status" + "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/yaml" + "github.com/pkg/errors" + "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/chart/loader" + "helm.sh/helm/v3/pkg/chartutil" + "helm.sh/helm/v3/pkg/cli" + "helm.sh/helm/v3/pkg/cli/values" + "helm.sh/helm/v3/pkg/getter" + "helm.sh/helm/v3/pkg/release" + "k8s.io/apimachinery/pkg/runtime/schema" + "os" + "path/filepath" + "strings" +) + +type Release struct { + ConfigFile string + Config *types.HelmChartConfig + Chart *Chart + + baseChartsDir string +} + +func NewRelease(configFile string, baseChartsDir string, credentialsProvider HelmCredentialsProvider) (*Release, error) { + var config types.HelmChartConfig + err := yaml.ReadYamlFile(configFile, &config) + if err != nil { + return nil, err + } + + _, err = semver.NewVersion(config.ChartVersion) + if err != nil { + return nil, fmt.Errorf("invalid chart version '%s': %w", config.ChartVersion, err) + } + + credentialsId := "" + if config.CredentialsId != nil { + credentialsId = *config.CredentialsId + } + chart, err := NewChart(config.Repo, config.ChartName, credentialsProvider, credentialsId) + if err != nil { + return nil, err + } + + hr := &Release{ + ConfigFile: configFile, + Config: &config, + baseChartsDir: baseChartsDir, + Chart: chart, + } + + return hr, nil +} + +func (hr *Release) GetOutputPath() string { + output := "helm-rendered.yaml" + if hr.Config.Output != nil { + output = *hr.Config.Output + } + return output +} + +func (hr *Release) GetFullOutputPath() (string, error) { + dir := filepath.Dir(hr.ConfigFile) + return securejoin.SecureJoin(dir, hr.GetOutputPath()) +} + +func (hr *Release) Render(ctx context.Context, k *k8s.K8sCluster, k8sVersion string, sopsDecrypter sops.SopsDecrypter) error { + err := hr.doRender(ctx, k, k8sVersion, sopsDecrypter) + if err != nil { + return fmt.Errorf("rendering helm chart %s for release %s has failed: %w", hr.Chart.GetChartName(), hr.Config.ReleaseName, err) + } + return nil +} + +func (hr *Release) GetDeprecatedChartDir() string { + dir := filepath.Dir(hr.ConfigFile) + return filepath.Join(dir, "charts", hr.Chart.GetChartName()) +} + +func (hr *Release) getPulledChart(ctx context.Context) (*PulledChart, error) { + deprecatedPC := NewPulledChart(hr.Chart, hr.Config.ChartVersion, hr.GetDeprecatedChartDir(), false) + if deprecatedPC.CheckExists() { + status.Deprecation(ctx, "helm-charts-dir", "Your project has pre-pulled charts located next to the helm-chart.yaml, which is deprecated. "+ + "Please run 'kluctl helm-pull' on your project and ensure that the deprecated charts are removed! Future versions of kluctl will ignore these locations.") + return deprecatedPC, nil + } + pc, err := hr.Chart.GetPulledChart(hr.baseChartsDir, hr.Config.ChartVersion) + if err != nil { + return nil, err + } + + needsPull, versionChanged, prePulledVersion, err := pc.CheckNeedsPull() + if err != nil { + return nil, err + } + if needsPull { + if versionChanged { + return nil, fmt.Errorf("pre-pulled Helm Chart %s need to be pulled (call 'kluctl helm-pull'). "+ + "Desired version is %s while pre-pulled version is %s", hr.Chart.GetChartName(), hr.Config.ChartVersion, prePulledVersion) + } + + s := status.Start(ctx, "Pulling Helm Chart %s with version %s", hr.Chart.GetChartName(), hr.Config.ChartVersion) + defer s.Failed() + + pc, err = hr.Chart.PullCached(ctx, hr.Config.ChartVersion) + if err != nil { + return nil, err + } + s.Success() + } + + return pc, nil +} + +func (hr *Release) doRender(ctx context.Context, k *k8s.K8sCluster, k8sVersion string, sopsDecrypter sops.SopsDecrypter) error { + pc, err := hr.getPulledChart(ctx) + if err != nil { + return err + } + + outputPath, err := hr.GetFullOutputPath() + if err != nil { + return err + } + valuesPath := yaml.FixPathExt(filepath.Join(filepath.Dir(hr.ConfigFile), "helm-values.yml")) + + var gvs []schema.GroupVersion + if k != nil { + gvs, err = k.Resources.GetAllGroupVersions() + if err != nil { + return err + } + } + cfg, err := buildHelmConfig(k) + if err != nil { + return err + } + + settings := cli.New() + valueOpts := values.Options{} + + if utils.Exists(valuesPath) { + tmpValues, err := sops.MaybeDecryptFileToTmp(ctx, sopsDecrypter, valuesPath) + if err != nil { + return err + } + defer os.Remove(tmpValues) + valueOpts.ValueFiles = append(valueOpts.ValueFiles, tmpValues) + } + + var kubeVersion *chartutil.KubeVersion + if k != nil { + kubeVersion, err = chartutil.ParseKubeVersion(k.ServerVersion.String()) + if err != nil { + return err + } + } + if k8sVersion != "" { + kubeVersion, err = chartutil.ParseKubeVersion(k8sVersion) + if err != nil { + return err + } + } + + namespace := "default" + if hr.Config.Namespace != nil { + namespace = *hr.Config.Namespace + } + + client := action.NewInstall(cfg) + client.DryRun = true + client.Namespace = namespace + client.ReleaseName = hr.Config.ReleaseName + client.Replace = true + client.ClientOnly = true + client.KubeVersion = kubeVersion + + if hr.Config.SkipCRDs { + client.SkipCRDs = true + } else { + client.IncludeCRDs = true + } + for _, gv := range gvs { + client.APIVersions = append(client.APIVersions, gv.String()) + } + + p := getter.All(settings) + vals, err := valueOpts.MergeValues(p) + if err != nil { + return err + } + + // Check chart dependencies to make sure all are present in /charts + chartRequested, err := loader.Load(pc.dir) + if err != nil { + return err + } + + if err := checkIfInstallable(chartRequested); err != nil { + return err + } + + if chartRequested.Metadata.Deprecated { + status.Warning(ctx, "Chart %s is deprecated", hr.Config.ChartName) + } + + rel, err := client.Run(chartRequested, vals) + if err != nil { + return err + } + + parsed, err := hr.parseRenderedManifests(rel.Manifest) + if err != nil { + return err + } + + if !client.DisableHooks { + for _, m := range rel.Hooks { + if isTestHook(m) { + continue + } + parsedHooks, err := hr.parseRenderedManifests(m.Manifest) + if err != nil { + return err + } + parsed = append(parsed, parsedHooks...) + } + } + + var fixed []interface{} + for _, o := range parsed { + // "helm install" will deploy resources to the given namespace automatically, but "helm template" does not + // add the necessary namespace in the rendered resources + if k != nil { + err = k8s.UnwrapListItems(o, true, func(o *uo.UnstructuredObject) error { + k.Resources.FixNamespace(o, namespace) + return nil + }) + if err != nil { + return err + } + } + fixed = append(fixed, o) + } + rendered, err := yaml.WriteYamlAllBytes(fixed) + if err != nil { + return err + } + + err = os.WriteFile(outputPath, rendered, 0o600) + if err != nil { + return err + } + return nil +} + +func (hr *Release) parseRenderedManifests(s string) ([]*uo.UnstructuredObject, error) { + var parsed []*uo.UnstructuredObject + + duplicatesRemoved, err := yaml.RemoveDuplicateFields(strings.NewReader(s)) + if err != nil { + return nil, err + } + + m, err := yaml.ReadYamlAllBytes(duplicatesRemoved) + if err != nil { + return nil, err + } + for _, x := range m { + x2, ok := x.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("yaml object is not a map") + } + parsed = append(parsed, uo.FromMap(x2)) + } + return parsed, nil +} + +func (hr *Release) Save() error { + return yaml.WriteYamlFile(hr.ConfigFile, hr.Config) +} + +func checkIfInstallable(ch *chart.Chart) error { + switch ch.Metadata.Type { + case "", "application": + return nil + } + return errors.Errorf("%s charts are not installable", ch.Metadata.Type) +} + +func isTestHook(h *release.Hook) bool { + for _, e := range h.Events { + if e == release.HookTest { + return true + } + } + return false +} diff --git a/pkg/helm/pulled_chart.go b/pkg/helm/pulled_chart.go new file mode 100644 index 000000000..d8c65a963 --- /dev/null +++ b/pkg/helm/pulled_chart.go @@ -0,0 +1,65 @@ +package helm + +import ( + "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/yaml" + "os" + "path/filepath" + "time" +) + +type PulledChart struct { + chart *Chart + version string + dir string + isTmp bool +} + +func NewPulledChart(chart *Chart, version string, dir string, isTmp bool) *PulledChart { + pc := &PulledChart{ + chart: chart, + version: version, + dir: dir, + isTmp: isTmp, + } + return pc +} + +func (pc *PulledChart) CheckExists() bool { + if !utils.IsDirectory(pc.dir) { + return false + } + return true +} + +func (pc *PulledChart) CheckNeedsPull() (bool, bool, string, error) { + if !utils.IsDirectory(pc.dir) { + return true, false, "", nil + } + + chartYamlPath := yaml.FixPathExt(filepath.Join(pc.dir, "Chart.yaml")) + st, err := os.Stat(chartYamlPath) + if err != nil { + if os.IsNotExist(err) { + return true, false, "", nil + } + return false, false, "", err + } + + if pc.isTmp && time.Now().Sub(st.ModTime()) >= time.Hour*24 { + // MacOS will delete tmp files after 3 days, so lets be safe and re-pull every day + return true, false, "", nil + } + + chartYaml, err := uo.FromFile(chartYamlPath) + if err != nil { + return false, false, "", err + } + + version, _, _ := chartYaml.GetNestedString("version") + if version != pc.version { + return true, true, version, nil + } + return false, false, version, nil +} diff --git a/pkg/helm/utils.go b/pkg/helm/utils.go new file mode 100644 index 000000000..d02c70ef0 --- /dev/null +++ b/pkg/helm/utils.go @@ -0,0 +1,19 @@ +package helm + +import ( + "github.com/kluctl/kluctl/v2/pkg/k8s" + "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/registry" +) + +func buildHelmConfig(k *k8s.K8sCluster) (*action.Configuration, error) { + rc, err := registry.NewClient() + if err != nil { + return nil, err + } + + return &action.Configuration{ + RESTClientGetter: k, + RegistryClient: rc, + }, nil +} From 2e8b8b3187508c65a166549dd2f338080d63219f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 12 Dec 2022 17:20:57 +0100 Subject: [PATCH 1312/2916] docs: Update docs in regard to pre-pulled charts location --- docs/reference/deployments/helm.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/deployments/helm.md b/docs/reference/deployments/helm.md index ee2c4ec21..f42fe69c3 100644 --- a/docs/reference/deployments/helm.md +++ b/docs/reference/deployments/helm.md @@ -17,7 +17,7 @@ The integration is split into 2 parts/steps/layers. The first is the management the second part handles configuration/customization and deployment of the chart. It is recommended to pre-pull Helm Charts with [`kluctl helm-pull`](../commands/helm-pull.md), which will store the -pulled charts near the `helm-chart.yaml` file (inside the `charts` sub-directory). It is however also possible (but not +pulled charts inside `.helm-charts` of the project directory. It is however also possible (but not recommended) to skip the pre-pulling phase and let kluctl pull Charts on-demand. When pre-pulling Helm Charts, you can also add the resulting Chart contents into version control. This is actually From 916db16b085fa09ea42a89db7e09d8ab4890474a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 12 Dec 2022 17:26:41 +0100 Subject: [PATCH 1313/2916] fix: Honor --upgrade without --commit Fixes #158 --- cmd/kluctl/commands/cmd_helm_update.go | 74 ++++++++++++++------------ e2e/helm_test.go | 14 ++--- 2 files changed, 46 insertions(+), 42 deletions(-) diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go index d40b274a6..22e0abc8b 100644 --- a/cmd/kluctl/commands/cmd_helm_update.go +++ b/cmd/kluctl/commands/cmd_helm_update.go @@ -192,10 +192,6 @@ func (cmd *helmUpdateCmd) Run(ctx context.Context) error { status.Info(ctx, "%s: Updated Chart version to %s", relDir, latestVersion) - if !cmd.Commit { - continue - } - key := fmt.Sprintf("%s / %s", hr.Chart.GetRepo(), hr.Chart.GetChartName()) uv := versionUseCounts[key] uv[oldVersion]-- @@ -246,7 +242,7 @@ func (cmd *helmUpdateCmd) pullAndCommitCharts(ctx context.Context, projectDir st return err } - s := status.Start(ctx, "%s: Committing Chart with version %s", relDir, newVersion) + s := status.Start(ctx, "%s: Upgrading Chart to version %s", relDir, newVersion) defer s.Failed() doError := func(err error) error { @@ -263,14 +259,16 @@ func (cmd *helmUpdateCmd) pullAndCommitCharts(ctx context.Context, projectDir st return doError(err) } - // add helm-chart.yaml - relToGit, err := filepath.Rel(gitRootPath, hr.ConfigFile) - if err != nil { - return doError(err) - } - _, err = wt.Add(relToGit) - if err != nil { - return doError(err) + if cmd.Commit { + // add helm-chart.yaml + relToGit, err := filepath.Rel(gitRootPath, hr.ConfigFile) + if err != nil { + return doError(err) + } + _, err = wt.Add(relToGit) + if err != nil { + return doError(err) + } } if deleteChart { @@ -282,9 +280,11 @@ func (cmd *helmUpdateCmd) pullAndCommitCharts(ctx context.Context, projectDir st if err != nil { return doError(err) } - _, err = wt.Remove(relChartDir) - if err != nil && err != index.ErrEntryNotFound { - return doError(err) + if cmd.Commit { + _, err = wt.Remove(relChartDir) + if err != nil && err != index.ErrEntryNotFound { + return doError(err) + } } err = os.RemoveAll(chartDir) if err != nil { @@ -315,34 +315,38 @@ func (cmd *helmUpdateCmd) pullAndCommitCharts(ctx context.Context, projectDir st return doError(err) } - _, err = wt.Add(relChartDir) - if err != nil { - return doError(err) - } - - // figure out what got deleted - for p := range files { - _, err := os.Lstat(filepath.Join(gitRootPath, p)) + if cmd.Commit { + _, err = wt.Add(relChartDir) if err != nil { - if os.IsNotExist(err) { - _, err = wt.Remove(p) - if err != nil { + return doError(err) + } + + // figure out what got deleted + for p := range files { + _, err := os.Lstat(filepath.Join(gitRootPath, p)) + if err != nil { + if os.IsNotExist(err) { + _, err = wt.Remove(p) + if err != nil { + return doError(err) + } + } else { return doError(err) } - } else { - return doError(err) } } } } - commitMsg := fmt.Sprintf("Updated helm chart %s to version %s", relToGit, newVersion) - _, err = wt.Commit(commitMsg, &git.CommitOptions{}) - if err != nil { - return doError(fmt.Errorf("failed to commit: %w", err)) - } + if cmd.Commit { + commitMsg := fmt.Sprintf("Updated helm chart %s to version %s", relDir, newVersion) + _, err = wt.Commit(commitMsg, &git.CommitOptions{}) + if err != nil { + return doError(fmt.Errorf("failed to commit: %w", err)) + } - s.Update("%s: Committed helm chart with version %s", relToGit, newVersion) + s.UpdateAndInfoFallback("%s: Committed helm chart with version %s", relDir, newVersion) + } s.Success() return nil diff --git a/e2e/helm_test.go b/e2e/helm_test.go index 682163cca..03a6540bb 100644 --- a/e2e/helm_test.go +++ b/e2e/helm_test.go @@ -212,18 +212,18 @@ func testHelmUpdate(t *testing.T, oci bool, upgrade bool, commit bool) { assert.Contains(t, stderr, "helm3: Skipped update to version 0.2.0") if upgrade { - assert.Contains(t, stderr, "helm1: Updated Chart version to 0.2.0") - assert.Contains(t, stderr, "helm2: Updated Chart version to 0.3.0") + assert.Contains(t, stderr, "helm1: Upgrading Chart to version 0.2.0") + assert.Contains(t, stderr, "helm2: Upgrading Chart to version 0.3.0") } if commit { - assert.Contains(t, stderr, "helm1: Committing Chart with version 0.2.0") - assert.Contains(t, stderr, "helm2: Committing Chart with version 0.3.0") + assert.Contains(t, stderr, "helm1: Committed helm chart with version 0.2.0") + assert.Contains(t, stderr, "helm2: Committed helm chart with version 0.3.0") } pulledVersions1 := listChartVersions(t, p, repoUrl, "test-chart1") pulledVersions2 := listChartVersions(t, p, repoUrl, "test-chart2") - if commit { + if upgrade { assert.Equal(t, []string{"0.1.0", "0.2.0"}, pulledVersions1) assert.Equal(t, []string{"0.3.0"}, pulledVersions2) } else { @@ -248,8 +248,8 @@ func testHelmUpdate(t *testing.T, oci bool, upgrade bool, commit bool) { return commitList[i].Message < commitList[j].Message }) - assert.Equal(t, "Updated helm chart helm1/helm-chart.yaml to version 0.2.0", commitList[0].Message) - assert.Equal(t, "Updated helm chart helm2/helm-chart.yaml to version 0.3.0", commitList[1].Message) + assert.Equal(t, "Updated helm chart helm1 to version 0.2.0", commitList[0].Message) + assert.Equal(t, "Updated helm chart helm2 to version 0.3.0", commitList[1].Message) } } From eac6fff2eb89c01866ba36035ac41fb34f532852 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 12 Dec 2022 17:39:45 +0100 Subject: [PATCH 1314/2916] chore: Better log output when pulling/ugprading --- cmd/kluctl/commands/cmd_helm_update.go | 8 ++++---- e2e/helm_test.go | 22 +++++++++++----------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go index 22e0abc8b..1cc484d4c 100644 --- a/cmd/kluctl/commands/cmd_helm_update.go +++ b/cmd/kluctl/commands/cmd_helm_update.go @@ -170,7 +170,7 @@ func (cmd *helmUpdateCmd) Run(ctx context.Context) error { continue } - status.Info(ctx, "%s: Chart has new version %s available", relDir, latestVersion) + status.Info(ctx, "%s: Chart %s has new version %s available", relDir, hr.Chart.GetChartName(), latestVersion) if !cmd.Upgrade { continue @@ -242,7 +242,7 @@ func (cmd *helmUpdateCmd) pullAndCommitCharts(ctx context.Context, projectDir st return err } - s := status.Start(ctx, "%s: Upgrading Chart to version %s", relDir, newVersion) + s := status.Start(ctx, "%s: Upgrading Chart %s to version %s", relDir, hr.Chart.GetChartName(), newVersion) defer s.Failed() doError := func(err error) error { @@ -339,13 +339,13 @@ func (cmd *helmUpdateCmd) pullAndCommitCharts(ctx context.Context, projectDir st } if cmd.Commit { - commitMsg := fmt.Sprintf("Updated helm chart %s to version %s", relDir, newVersion) + commitMsg := fmt.Sprintf("Updated helm chart %s in %s to version %s", hr.Chart.GetChartName(), relDir, newVersion) _, err = wt.Commit(commitMsg, &git.CommitOptions{}) if err != nil { return doError(fmt.Errorf("failed to commit: %w", err)) } - s.UpdateAndInfoFallback("%s: Committed helm chart with version %s", relDir, newVersion) + s.UpdateAndInfoFallback("%s: Committed helm chart %s with version %s", relDir, hr.Chart.GetChartName(), newVersion) } s.Success() diff --git a/e2e/helm_test.go b/e2e/helm_test.go index 03a6540bb..5b9a772d5 100644 --- a/e2e/helm_test.go +++ b/e2e/helm_test.go @@ -207,17 +207,17 @@ func testHelmUpdate(t *testing.T, oci bool, upgrade bool, commit bool) { } _, stderr := p.KluctlMust(args...) - assert.Contains(t, stderr, "helm1: Chart has new version 0.2.0 available") - assert.Contains(t, stderr, "helm2: Chart has new version 0.3.0 available") + assert.Contains(t, stderr, "helm1: Chart test-chart1 has new version 0.2.0 available") + assert.Contains(t, stderr, "helm2: Chart test-chart2 has new version 0.3.0 available") assert.Contains(t, stderr, "helm3: Skipped update to version 0.2.0") if upgrade { - assert.Contains(t, stderr, "helm1: Upgrading Chart to version 0.2.0") - assert.Contains(t, stderr, "helm2: Upgrading Chart to version 0.3.0") + assert.Contains(t, stderr, "helm1: Upgrading Chart test-chart1 to version 0.2.0") + assert.Contains(t, stderr, "helm2: Upgrading Chart test-chart2 to version 0.3.0") } if commit { - assert.Contains(t, stderr, "helm1: Committed helm chart with version 0.2.0") - assert.Contains(t, stderr, "helm2: Committed helm chart with version 0.3.0") + assert.Contains(t, stderr, "helm1: Committed helm chart test-chart1 with version 0.2.0") + assert.Contains(t, stderr, "helm2: Committed helm chart test-chart2 with version 0.3.0") } pulledVersions1 := listChartVersions(t, p, repoUrl, "test-chart1") @@ -248,8 +248,8 @@ func testHelmUpdate(t *testing.T, oci bool, upgrade bool, commit bool) { return commitList[i].Message < commitList[j].Message }) - assert.Equal(t, "Updated helm chart helm1 to version 0.2.0", commitList[0].Message) - assert.Equal(t, "Updated helm chart helm2 to version 0.3.0", commitList[1].Message) + assert.Equal(t, "Updated helm chart test-chart1 in helm1 to version 0.2.0", commitList[0].Message) + assert.Equal(t, "Updated helm chart test-chart2 in helm2 to version 0.3.0", commitList[1].Message) } } @@ -316,9 +316,9 @@ func testHelmUpdateConstraints(t *testing.T, oci bool) { args := []string{"helm-update", "--upgrade"} _, stderr := p.KluctlMust(args...) - assert.Contains(t, stderr, "helm1: Chart has new version 0.1.1 available") - assert.Contains(t, stderr, "helm2: Chart has new version 0.2.0 available") - assert.Contains(t, stderr, "helm3: Chart has new version 1.2.1 available") + assert.Contains(t, stderr, "helm1: Chart test-chart1 has new version 0.1.1 available") + assert.Contains(t, stderr, "helm2: Chart test-chart1 has new version 0.2.0 available") + assert.Contains(t, stderr, "helm3: Chart test-chart1 has new version 1.2.1 available") c1 := p.GetYaml("helm1/helm-chart.yaml") c2 := p.GetYaml("helm2/helm-chart.yaml") From c57d5419ff02f4a987e4d37ddcf41783cae7dc24 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Dec 2022 22:08:18 +0000 Subject: [PATCH 1315/2916] chore(deps): Bump github.com/aws/aws-sdk-go from 1.44.157 to 1.44.158 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.157 to 1.44.158. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.157...v1.44.158) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 484ebe921..cdc51572b 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e github.com/Masterminds/semver/v3 v3.2.0 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d - github.com/aws/aws-sdk-go v1.44.157 + github.com/aws/aws-sdk-go v1.44.158 github.com/bitnami-labs/sealed-secrets v0.19.2 github.com/cyphar/filepath-securejoin v0.2.3 github.com/docker/distribution v2.8.1+incompatible diff --git a/go.sum b/go.sum index 5ce647007..8ccdc3d66 100644 --- a/go.sum +++ b/go.sum @@ -128,8 +128,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/aws/aws-sdk-go v1.44.157 h1:JVBPpEWC8+yA7CbfAuTl/ZFFlHS3yoqWFqxFyTCISwg= -github.com/aws/aws-sdk-go v1.44.157/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.158 h1:Q71ei9ijL3KuyQcLJA9TtuYy2gMLsLdVH5Q2ackBq3s= +github.com/aws/aws-sdk-go v1.44.158/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= From 04c2a2833595e07a75e16f1b113ebd476591d25d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Dec 2022 22:08:37 +0000 Subject: [PATCH 1316/2916] chore(deps): Bump github.com/go-git/go-git/v5 from 5.5.0 to 5.5.1 Bumps [github.com/go-git/go-git/v5](https://github.com/go-git/go-git) from 5.5.0 to 5.5.1. - [Release notes](https://github.com/go-git/go-git/releases) - [Commits](https://github.com/go-git/go-git/compare/v5.5.0...v5.5.1) --- updated-dependencies: - dependency-name: github.com/go-git/go-git/v5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 10 ++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 484ebe921..dae75ac18 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/cyphar/filepath-securejoin v0.2.3 github.com/docker/distribution v2.8.1+incompatible github.com/fluxcd/pkg/kustomize v0.11.0 - github.com/go-git/go-git/v5 v5.5.0 + github.com/go-git/go-git/v5 v5.5.1 github.com/go-playground/validator/v10 v10.11.1 github.com/gobwas/glob v0.2.3 // indirect github.com/golang-jwt/jwt/v4 v4.4.3 @@ -37,7 +37,7 @@ require ( github.com/spf13/viper v1.14.0 github.com/stretchr/testify v1.8.1 github.com/whilp/git-urls v1.0.0 - github.com/xanzy/ssh-agent v0.3.3-0.20220920102508-0fa644ba07f4 + github.com/xanzy/ssh-agent v0.3.3 golang.org/x/crypto v0.4.0 golang.org/x/net v0.4.0 golang.org/x/sync v0.1.0 diff --git a/go.sum b/go.sum index 5ce647007..838b0c7b2 100644 --- a/go.sum +++ b/go.sum @@ -257,8 +257,8 @@ github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Ai github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= github.com/go-git/go-git-fixtures/v4 v4.3.1 h1:y5z6dd3qi8Hl+stezc8p3JxDkoTRqMAlKnXHuzrfjTQ= github.com/go-git/go-git-fixtures/v4 v4.3.1/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo= -github.com/go-git/go-git/v5 v5.5.0 h1:StO/ASRvk1Pp74tr7XQ0pQwKlCFignzzTF/NLKdQzUE= -github.com/go-git/go-git/v5 v5.5.0/go.mod h1:g456XI30HAdt7GQtIf8JR6GDAdULGaR4KtfFtQa0uTg= +github.com/go-git/go-git/v5 v5.5.1 h1:5vtv2TB5PM/gPM+EvsHJ16hJh4uAkdGcKilcwY7FYwo= +github.com/go-git/go-git/v5 v5.5.1/go.mod h1:uz5PQ3d0gz7mSgzZhSJToM6ALPaKCdSnl58/Xb5hzr8= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -679,7 +679,6 @@ github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pjbgf/sha1cd v0.2.0/go.mod h1:HOK9QrgzdHpbc2Kzip0Q1yi3M2MFGPADtR6HjG65m5M= github.com/pjbgf/sha1cd v0.2.3 h1:uKQP/7QOzNtKYH7UTohZLcjF5/55EnTw0jO/Ru4jZwI= github.com/pjbgf/sha1cd v0.2.3/go.mod h1:HOK9QrgzdHpbc2Kzip0Q1yi3M2MFGPADtR6HjG65m5M= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -810,9 +809,8 @@ github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaU github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/whilp/git-urls v1.0.0 h1:95f6UMWN5FKW71ECsXRUd3FVYiXdrE7aX4NZKcPmIjU= github.com/whilp/git-urls v1.0.0/go.mod h1:J16SAmobsqc3Qcy98brfl5f5+e0clUvg1krgwk/qCfE= -github.com/xanzy/ssh-agent v0.3.2/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= -github.com/xanzy/ssh-agent v0.3.3-0.20220920102508-0fa644ba07f4 h1:3kHD8uZqiYes9JHdd3FzuyUbG10g9Bp9EOfqkSHIhg4= -github.com/xanzy/ssh-agent v0.3.3-0.20220920102508-0fa644ba07f4/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= From 690228f2b9eb9b07121c3ec37e59fb5140e71806 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 13 Dec 2022 12:13:53 +0100 Subject: [PATCH 1317/2916] fix: Use fluxcd fork of go-git See https://github.com/fluxcd/pkg/issues/397, especially in regard to skeema/knownhosts breaking compatibility --- cmd/kluctl/commands/cmd_helm_update.go | 4 ++-- e2e/helm_test.go | 4 ++-- e2e/test-utils/project.go | 2 +- go.mod | 4 +++- go.sum | 31 +++++++++++++++++++++----- pkg/git/auth/auth_provider.go | 2 +- pkg/git/auth/git_credentials_file.go | 2 +- pkg/git/auth/list_auth_provider.go | 4 ++-- pkg/git/list_refs.go | 10 ++++----- pkg/git/mirrored_repo.go | 8 +++---- pkg/git/poor_mans_clone.go | 4 ++-- pkg/git/ssh-pool/hostport.go | 2 +- pkg/git/test_git_server.go | 2 +- pkg/git/utils.go | 2 +- pkg/vars/vars_loader_test.go | 4 ++-- 15 files changed, 53 insertions(+), 32 deletions(-) diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go index 1cc484d4c..059c70233 100644 --- a/cmd/kluctl/commands/cmd_helm_update.go +++ b/cmd/kluctl/commands/cmd_helm_update.go @@ -3,8 +3,8 @@ package commands import ( "context" "fmt" - "github.com/go-git/go-git/v5" - "github.com/go-git/go-git/v5/plumbing/format/index" + "github.com/fluxcd/go-git/v5" + "github.com/fluxcd/go-git/v5/plumbing/format/index" "github.com/hashicorp/go-multierror" "github.com/kluctl/kluctl/v2/cmd/kluctl/args" git2 "github.com/kluctl/kluctl/v2/pkg/git" diff --git a/e2e/helm_test.go b/e2e/helm_test.go index 5b9a772d5..cf33cf37c 100644 --- a/e2e/helm_test.go +++ b/e2e/helm_test.go @@ -2,8 +2,8 @@ package e2e import ( "fmt" - "github.com/go-git/go-git/v5" - "github.com/go-git/go-git/v5/plumbing/object" + "github.com/fluxcd/go-git/v5" + "github.com/fluxcd/go-git/v5/plumbing/object" test_utils "github.com/kluctl/kluctl/v2/e2e/test-utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/stretchr/testify/assert" diff --git a/e2e/test-utils/project.go b/e2e/test-utils/project.go index b97497f98..3d0b891fb 100644 --- a/e2e/test-utils/project.go +++ b/e2e/test-utils/project.go @@ -4,7 +4,7 @@ import ( "bytes" "context" "fmt" - "github.com/go-git/go-git/v5" + "github.com/fluxcd/go-git/v5" "github.com/huandu/xstrings" "github.com/jinzhu/copier" "github.com/kluctl/kluctl/v2/cmd/kluctl/commands" diff --git a/go.mod b/go.mod index d447c0d8a..b86ceba06 100644 --- a/go.mod +++ b/go.mod @@ -10,8 +10,9 @@ require ( github.com/bitnami-labs/sealed-secrets v0.19.2 github.com/cyphar/filepath-securejoin v0.2.3 github.com/docker/distribution v2.8.1+incompatible + // See https://github.com/fluxcd/pkg/issues/397, especially in regard to skeema/knownhosts breaking compatibility + github.com/fluxcd/go-git/v5 v5.0.0-20221206140629-ec778c2c37df github.com/fluxcd/pkg/kustomize v0.11.0 - github.com/go-git/go-git/v5 v5.5.1 github.com/go-playground/validator/v10 v10.11.1 github.com/gobwas/glob v0.2.3 // indirect github.com/golang-jwt/jwt/v4 v4.4.3 @@ -120,6 +121,7 @@ require ( github.com/go-errors/errors v1.4.2 // indirect github.com/go-git/gcfg v1.5.0 // indirect github.com/go-git/go-billy/v5 v5.3.1 // indirect + github.com/go-git/go-git/v5 v5.4.2 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.20.0 // indirect diff --git a/go.sum b/go.sum index bc25ba223..20b2f6dfc 100644 --- a/go.sum +++ b/go.sum @@ -96,12 +96,15 @@ github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmy github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= github.com/Masterminds/squirrel v1.5.3 h1:YPpoceAcxuzIljlr5iWpNKaql7hLeG1KLSrhvdHpkZc= github.com/Masterminds/squirrel v1.5.3/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= github.com/Microsoft/hcsshim v0.9.4 h1:mnUj0ivWy6UzbB1uLFqKR6F+ZyiDc7j4iGgHTpO+5+I= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 h1:ra2OtmuW0AE5csawV4YXMNGNQQXvLRps3z2Z59OPO+I= github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= @@ -114,6 +117,7 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= @@ -214,6 +218,7 @@ github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9 github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= github.com/emicklei/go-restful/v3 v3.10.0 h1:X4gma4HM7hFm6WMeAsTfqA0GOfdNoCzBIkHGoRLGXuM= github.com/emicklei/go-restful/v3 v3.10.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -238,27 +243,33 @@ github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYF github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= +github.com/fluxcd/go-git/v5 v5.0.0-20221206140629-ec778c2c37df h1:2BHXJp1PwX7D47Q2oaKDekn+BZVZCmxeCWNi+FyownE= +github.com/fluxcd/go-git/v5 v5.0.0-20221206140629-ec778c2c37df/go.mod h1:raWgfUV7lDQVXp4QXUaeNNJkRVKz97UQuF+0kdY7Vmo= github.com/fluxcd/pkg/apis/kustomize v0.7.0 h1:X2htBmJ91nGYv4d93gin665MFWKNGiNwUiZ08/Zz0hY= github.com/fluxcd/pkg/apis/kustomize v0.7.0/go.mod h1:Mu+KdktsEKWA4l/33CZdY5lB4hz51mqfcLzBZSwAqVg= github.com/fluxcd/pkg/kustomize v0.11.0 h1:zseS9LRUuzhP/7KamccmsOgYpJAdhqtsf+2wN/CHF3I= github.com/fluxcd/pkg/kustomize v0.11.0/go.mod h1:awHID4OKe2/WAfTFg4u0fURXZPUkrIslSZNSPX9MEFQ= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= +github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34= github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0= github.com/go-git/go-git-fixtures/v4 v4.3.1 h1:y5z6dd3qi8Hl+stezc8p3JxDkoTRqMAlKnXHuzrfjTQ= github.com/go-git/go-git-fixtures/v4 v4.3.1/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo= -github.com/go-git/go-git/v5 v5.5.1 h1:5vtv2TB5PM/gPM+EvsHJ16hJh4uAkdGcKilcwY7FYwo= -github.com/go-git/go-git/v5 v5.5.1/go.mod h1:uz5PQ3d0gz7mSgzZhSJToM6ALPaKCdSnl58/Xb5hzr8= +github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4= +github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -489,6 +500,7 @@ github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= @@ -524,6 +536,7 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw= github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= +github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -755,6 +768,7 @@ github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5g github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= @@ -809,6 +823,8 @@ github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaU github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/whilp/git-urls v1.0.0 h1:95f6UMWN5FKW71ECsXRUd3FVYiXdrE7aX4NZKcPmIjU= github.com/whilp/git-urls v1.0.0/go.mod h1:J16SAmobsqc3Qcy98brfl5f5+e0clUvg1krgwk/qCfE= +github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= +github.com/xanzy/ssh-agent v0.3.2/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= @@ -860,6 +876,7 @@ go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -869,6 +886,7 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= @@ -878,7 +896,7 @@ golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8= golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -957,6 +975,7 @@ golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -966,7 +985,6 @@ golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU= golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1014,6 +1032,7 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1048,10 +1067,12 @@ golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1073,7 +1094,6 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1081,7 +1101,6 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/pkg/git/auth/auth_provider.go b/pkg/git/auth/auth_provider.go index 19b2d418e..002ea0d12 100644 --- a/pkg/git/auth/auth_provider.go +++ b/pkg/git/auth/auth_provider.go @@ -2,7 +2,7 @@ package auth import ( "context" - "github.com/go-git/go-git/v5/plumbing/transport" + "github.com/fluxcd/go-git/v5/plumbing/transport" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/git/messages" "golang.org/x/crypto/ssh" diff --git a/pkg/git/auth/git_credentials_file.go b/pkg/git/auth/git_credentials_file.go index 68989ca85..eb1696075 100644 --- a/pkg/git/auth/git_credentials_file.go +++ b/pkg/git/auth/git_credentials_file.go @@ -3,7 +3,7 @@ package auth import ( "bufio" "context" - "github.com/go-git/go-git/v5/plumbing/transport/http" + "github.com/fluxcd/go-git/v5/plumbing/transport/http" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/git/messages" giturls "github.com/whilp/git-urls" diff --git a/pkg/git/auth/list_auth_provider.go b/pkg/git/auth/list_auth_provider.go index 44e75c477..3d095e419 100644 --- a/pkg/git/auth/list_auth_provider.go +++ b/pkg/git/auth/list_auth_provider.go @@ -2,8 +2,8 @@ package auth import ( "context" - "github.com/go-git/go-git/v5/plumbing/transport/http" - "github.com/go-git/go-git/v5/plumbing/transport/ssh" + "github.com/fluxcd/go-git/v5/plumbing/transport/http" + "github.com/fluxcd/go-git/v5/plumbing/transport/ssh" "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/git/messages" ssh2 "golang.org/x/crypto/ssh" diff --git a/pkg/git/list_refs.go b/pkg/git/list_refs.go index 735234d6b..a4a8bb7d5 100644 --- a/pkg/git/list_refs.go +++ b/pkg/git/list_refs.go @@ -4,11 +4,11 @@ import ( "bytes" "context" "fmt" - "github.com/go-git/go-git/v5" - "github.com/go-git/go-git/v5/config" - "github.com/go-git/go-git/v5/plumbing" - "github.com/go-git/go-git/v5/plumbing/protocol/packp" - "github.com/go-git/go-git/v5/storage/memory" + "github.com/fluxcd/go-git/v5" + "github.com/fluxcd/go-git/v5/config" + "github.com/fluxcd/go-git/v5/plumbing" + "github.com/fluxcd/go-git/v5/plumbing/protocol/packp" + "github.com/fluxcd/go-git/v5/storage/memory" auth2 "github.com/kluctl/kluctl/v2/pkg/git/auth" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" ssh_pool "github.com/kluctl/kluctl/v2/pkg/git/ssh-pool" diff --git a/pkg/git/mirrored_repo.go b/pkg/git/mirrored_repo.go index 10e2e805c..e1642cdb9 100644 --- a/pkg/git/mirrored_repo.go +++ b/pkg/git/mirrored_repo.go @@ -5,10 +5,10 @@ import ( "crypto/sha256" "encoding/hex" "fmt" - "github.com/go-git/go-git/v5" - "github.com/go-git/go-git/v5/config" - "github.com/go-git/go-git/v5/plumbing" - "github.com/go-git/go-git/v5/plumbing/object" + "github.com/fluxcd/go-git/v5" + "github.com/fluxcd/go-git/v5/config" + "github.com/fluxcd/go-git/v5/plumbing" + "github.com/fluxcd/go-git/v5/plumbing/object" auth2 "github.com/kluctl/kluctl/v2/pkg/git/auth" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" _ "github.com/kluctl/kluctl/v2/pkg/git/ssh-pool" diff --git a/pkg/git/poor_mans_clone.go b/pkg/git/poor_mans_clone.go index 975112564..9804cab66 100644 --- a/pkg/git/poor_mans_clone.go +++ b/pkg/git/poor_mans_clone.go @@ -1,8 +1,8 @@ package git import ( - "github.com/go-git/go-git/v5" - "github.com/go-git/go-git/v5/config" + "github.com/fluxcd/go-git/v5" + "github.com/fluxcd/go-git/v5/config" cp "github.com/otiai10/copy" "os" "path/filepath" diff --git a/pkg/git/ssh-pool/hostport.go b/pkg/git/ssh-pool/hostport.go index 4ca5c5cc7..d4e314ae0 100644 --- a/pkg/git/ssh-pool/hostport.go +++ b/pkg/git/ssh-pool/hostport.go @@ -2,7 +2,7 @@ package ssh_pool import ( "fmt" - "github.com/go-git/go-git/v5/plumbing/transport/ssh" + "github.com/fluxcd/go-git/v5/plumbing/transport/ssh" "strconv" ) diff --git a/pkg/git/test_git_server.go b/pkg/git/test_git_server.go index e7661ab9e..a323901a5 100644 --- a/pkg/git/test_git_server.go +++ b/pkg/git/test_git_server.go @@ -3,7 +3,7 @@ package git import ( "context" "fmt" - "github.com/go-git/go-git/v5" + "github.com/fluxcd/go-git/v5" "github.com/jinzhu/copier" http_server "github.com/kluctl/kluctl/v2/pkg/git/http-server" "gopkg.in/yaml.v3" diff --git a/pkg/git/utils.go b/pkg/git/utils.go index f007fa3ee..121bca567 100644 --- a/pkg/git/utils.go +++ b/pkg/git/utils.go @@ -2,7 +2,7 @@ package git import ( "fmt" - "github.com/go-git/go-git/v5" + "github.com/fluxcd/go-git/v5" "os" "path/filepath" ) diff --git a/pkg/vars/vars_loader_test.go b/pkg/vars/vars_loader_test.go index b4d4a0f38..e7ecd8720 100644 --- a/pkg/vars/vars_loader_test.go +++ b/pkg/vars/vars_loader_test.go @@ -2,8 +2,8 @@ package vars import ( "context" - git2 "github.com/go-git/go-git/v5" - "github.com/go-git/go-git/v5/plumbing" + git2 "github.com/fluxcd/go-git/v5" + "github.com/fluxcd/go-git/v5/plumbing" "github.com/kluctl/kluctl/v2/pkg/git" "github.com/kluctl/kluctl/v2/pkg/git/auth" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" From f51bf628ca4974f67d5428ea6ae1e0d4a6e5db30 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 13 Dec 2022 12:14:51 +0100 Subject: [PATCH 1318/2916] fix: Use wrapper around AuthMethod to override ClientConfig for HostKeyCallback --- pkg/git/auth/auth_provider.go | 13 +++++++++-- pkg/git/auth/list_auth_provider.go | 36 +++++++++++++++++++++--------- pkg/git/auth/ssh_auth_provider.go | 1 - pkg/git/ssh-pool/ssh_pool.go | 4 ++-- 4 files changed, 39 insertions(+), 15 deletions(-) diff --git a/pkg/git/auth/auth_provider.go b/pkg/git/auth/auth_provider.go index 002ea0d12..18809899a 100644 --- a/pkg/git/auth/auth_provider.go +++ b/pkg/git/auth/auth_provider.go @@ -2,7 +2,9 @@ package auth import ( "context" + "fmt" "github.com/fluxcd/go-git/v5/plumbing/transport" + ssh2 "github.com/fluxcd/go-git/v5/plumbing/transport/ssh" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/git/messages" "golang.org/x/crypto/ssh" @@ -12,8 +14,15 @@ type AuthMethodAndCA struct { AuthMethod transport.AuthMethod CABundle []byte - Hash func() ([]byte, error) - ClientConfig func() (*ssh.ClientConfig, error) + Hash func() ([]byte, error) +} + +func (a *AuthMethodAndCA) SshClientConfig() (*ssh.ClientConfig, error) { + gitSshAuth, ok := a.AuthMethod.(ssh2.AuthMethod) + if !ok { + return nil, fmt.Errorf("auth is not a git ssh.AuthMethod") + } + return gitSshAuth.ClientConfig() } type GitAuthProvider interface { diff --git a/pkg/git/auth/list_auth_provider.go b/pkg/git/auth/list_auth_provider.go index 3d095e419..c6876027e 100644 --- a/pkg/git/auth/list_auth_provider.go +++ b/pkg/git/auth/list_auth_provider.go @@ -28,6 +28,28 @@ type AuthEntry struct { CABundle []byte } +type KnownHostsWrapper struct { + authMethod ssh.AuthMethod + hostKeyCallback ssh2.HostKeyCallback +} + +func (w *KnownHostsWrapper) String() string { + return w.authMethod.String() +} + +func (w *KnownHostsWrapper) Name() string { + return w.authMethod.Name() +} + +func (w *KnownHostsWrapper) ClientConfig() (*ssh2.ClientConfig, error) { + ccfg, err := w.authMethod.ClientConfig() + if err != nil { + return nil, err + } + ccfg.HostKeyCallback = w.hostKeyCallback + return ccfg, nil +} + func (a *ListAuthProvider) AddEntry(e AuthEntry) { a.entries = append(a.entries, e) } @@ -80,20 +102,14 @@ func (a *ListAuthProvider) BuildAuth(ctx context.Context, gitUrl git_url.GitUrl) if err != nil { a.MessageCallbacks.Trace("ListAuthProvider: failed to parse private key: %v", err) } else { - hostKeyCallback := buildVerifyHostCallback(a.MessageCallbacks, e.KnownHosts) return AuthMethodAndCA{ - AuthMethod: pk, + AuthMethod: &KnownHostsWrapper{ + authMethod: pk, + hostKeyCallback: buildVerifyHostCallback(a.MessageCallbacks, e.KnownHosts), + }, Hash: func() ([]byte, error) { return buildHash(pk.Signer) }, - ClientConfig: func() (*ssh2.ClientConfig, error) { - ccfg, err := pk.ClientConfig() - if err != nil { - return nil, err - } - ccfg.HostKeyCallback = hostKeyCallback - return ccfg, nil - }, } } } else { diff --git a/pkg/git/auth/ssh_auth_provider.go b/pkg/git/auth/ssh_auth_provider.go index 2e95cb864..5af2d2c0d 100644 --- a/pkg/git/auth/ssh_auth_provider.go +++ b/pkg/git/auth/ssh_auth_provider.go @@ -130,7 +130,6 @@ func (a *GitSshAuthProvider) BuildAuth(ctx context.Context, gitUrl git_url.GitUr } return buildHashForList(signers) }, - ClientConfig: auth.ClientConfig, } } diff --git a/pkg/git/ssh-pool/ssh_pool.go b/pkg/git/ssh-pool/ssh_pool.go index 7b33a4f1c..010fec57a 100644 --- a/pkg/git/ssh-pool/ssh_pool.go +++ b/pkg/git/ssh-pool/ssh_pool.go @@ -139,7 +139,7 @@ func (p *SshPool) newClient(ctx context.Context, addr string, auth auth.AuthMeth return nil, err } - config, err := auth.ClientConfig() + config, err := auth.SshClientConfig() if err != nil { return nil, err } @@ -157,7 +157,7 @@ func (p *SshPool) buildHash(addr string, auth auth.AuthMethodAndCA) (string, err return "", fmt.Errorf("auth has no Hash") } - config, err := auth.ClientConfig() + config, err := auth.SshClientConfig() if err != nil { return "", err } From ba3608599b80a7ae039e54b8b7525382af2ee40e Mon Sep 17 00:00:00 2001 From: Eng Zer Jun Date: Wed, 14 Dec 2022 20:17:10 +0800 Subject: [PATCH 1319/2916] test: use `T.TempDir` to create temporary test directory This commit replaces `os.MkdirTemp` with `t.TempDir` in tests. The directory created by `t.TempDir` is automatically removed when the test and all its subtests complete. Prior to this commit, temporary directory created using `os.MkdirTemp` needs to be removed manually by calling `os.RemoveAll`, which is omitted in some tests. The error handling boilerplate e.g. defer func() { if err := os.RemoveAll(dir); err != nil { t.Fatal(err) } } is also tedious, but `t.TempDir` handles this for us nicely. Reference: https://pkg.go.dev/testing#T.TempDir Signed-off-by: Eng Zer Jun --- pkg/git/test_git_server.go | 22 +++++++-------------- pkg/vars/vars_loader_test.go | 38 +++++++++++++----------------------- 2 files changed, 21 insertions(+), 39 deletions(-) diff --git a/pkg/git/test_git_server.go b/pkg/git/test_git_server.go index a323901a5..7dd0a1614 100644 --- a/pkg/git/test_git_server.go +++ b/pkg/git/test_git_server.go @@ -3,10 +3,6 @@ package git import ( "context" "fmt" - "github.com/fluxcd/go-git/v5" - "github.com/jinzhu/copier" - http_server "github.com/kluctl/kluctl/v2/pkg/git/http-server" - "gopkg.in/yaml.v3" "log" "net" "net/http" @@ -14,6 +10,11 @@ import ( "path/filepath" "reflect" "testing" + + "github.com/fluxcd/go-git/v5" + "github.com/jinzhu/copier" + http_server "github.com/kluctl/kluctl/v2/pkg/git/http-server" + "gopkg.in/yaml.v3" ) type TestGitServer struct { @@ -28,15 +29,10 @@ type TestGitServer struct { func NewTestGitServer(t *testing.T) *TestGitServer { p := &TestGitServer{ - t: t, + t: t, + baseDir: t.TempDir(), } - baseDir, err := os.MkdirTemp(os.TempDir(), "kluctl-tests-") - if err != nil { - p.t.Fatal(err) - } - p.baseDir = baseDir - p.initGitServer() t.Cleanup(func() { @@ -73,10 +69,6 @@ func (p *TestGitServer) Cleanup() { p.gitServer = nil } - if p.baseDir == "" { - return - } - _ = os.RemoveAll(p.baseDir) p.baseDir = "" } diff --git a/pkg/vars/vars_loader_test.go b/pkg/vars/vars_loader_test.go index e7ecd8720..a4a99b4e6 100644 --- a/pkg/vars/vars_loader_test.go +++ b/pkg/vars/vars_loader_test.go @@ -2,6 +2,15 @@ package vars import ( "context" + "io" + "net/http" + "net/http/httptest" + "net/url" + "os" + "path/filepath" + "strings" + "testing" + git2 "github.com/fluxcd/go-git/v5" "github.com/fluxcd/go-git/v5/plumbing" "github.com/kluctl/kluctl/v2/pkg/git" @@ -18,30 +27,11 @@ import ( "github.com/kluctl/kluctl/v2/pkg/vars/sops_test_resources" "github.com/stretchr/testify/assert" "go.mozilla.org/sops/v3/age" - "io" corev1 "k8s.io/api/core/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "net/http" - "net/http/httptest" - "net/url" - "os" - "path/filepath" - "strings" - "testing" ) -func newTestDir(t *testing.T) string { - tmp, err := os.MkdirTemp("", "") - if err != nil { - t.Fatal(err) - } - t.Cleanup(func() { - _ = os.RemoveAll(tmp) - }) - return tmp -} - func newRP(t *testing.T) *repocache.GitRepoCache { grc := repocache.NewGitRepoCache(context.TODO(), &ssh_pool.SshPool{}, auth.NewDefaultAuthProviders("KLUCTL_GIT", nil), nil, 0) t.Cleanup(func() { @@ -77,7 +67,7 @@ func TestVarsLoader_Values(t *testing.T) { } func TestVarsLoader_File(t *testing.T) { - d := newTestDir(t) + d := t.TempDir() _ = os.WriteFile(filepath.Join(d, "test.yaml"), []byte(`{"test1": {"test2": 42}}`), 0o600) testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { @@ -101,7 +91,7 @@ func TestVarsLoader_File(t *testing.T) { } func TestVarsLoader_SopsFile(t *testing.T) { - d := newTestDir(t) + d := t.TempDir() f, _ := sops_test_resources.TestResources.ReadFile("test.yaml") key, _ := sops_test_resources.TestResources.ReadFile("test-key.txt") _ = os.WriteFile(filepath.Join(d, "test.yaml"), f, 0o600) @@ -120,7 +110,7 @@ func TestVarsLoader_SopsFile(t *testing.T) { } func TestVarsLoader_FileWithLoad(t *testing.T) { - d := newTestDir(t) + d := t.TempDir() _ = os.WriteFile(filepath.Join(d, "test.yaml"), []byte(`{"test1": {"test2": {{ load_template("test2.txt") }}}}`), 0o600) _ = os.WriteFile(filepath.Join(d, "test2.txt"), []byte(`42`), 0o600) @@ -136,7 +126,7 @@ func TestVarsLoader_FileWithLoad(t *testing.T) { } func TestVarsLoader_FileWithLoadSubDir(t *testing.T) { - d := newTestDir(t) + d := t.TempDir() _ = os.Mkdir(filepath.Join(d, "subdir"), 0o700) _ = os.WriteFile(filepath.Join(d, "test.yaml"), []byte(`{"test1": {"test2": {{ load_template("test2.txt") }}}}`), 0o600) _ = os.WriteFile(filepath.Join(d, "subdir/test2.txt"), []byte(`42`), 0o600) @@ -153,7 +143,7 @@ func TestVarsLoader_FileWithLoadSubDir(t *testing.T) { } func TestVarsLoader_FileWithLoadNotExists(t *testing.T) { - d := newTestDir(t) + d := t.TempDir() _ = os.WriteFile(filepath.Join(d, "test.yaml"), []byte(`{"test1": {"test2": {{load_template("test3.txt")}}}}`), 0o600) testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { From 9ee0b28807f87b2a9c953cdd1382648daeaadd47 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 14 Dec 2022 17:47:18 +0100 Subject: [PATCH 1320/2916] docs: Set linkTitle to Kluctl --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 1ccac0e55..b1dab7f60 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,7 +2,7 @@ --- title: "Kluctl Documentation" -linkTitle: "Docs" +linkTitle: "Kluctl" description: "The missing glue to put together large Kubernetes deployments." taxonomyCloud: [] weight: 20 From 0fbb20d6d8ebe15efe7f8228e742061fba808490 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 14 Dec 2022 17:51:31 +0100 Subject: [PATCH 1321/2916] docs: Remove Kluctl menu entry from hugo page --- docs/README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/README.md b/docs/README.md index b1dab7f60..9e921ca6b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -6,9 +6,6 @@ linkTitle: "Kluctl" description: "The missing glue to put together large Kubernetes deployments." taxonomyCloud: [] weight: 20 -menu: - main: - weight: 20 --- --> From dc3ebf970f517ad8342eaf11b067c0e292a782ff Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 14 Dec 2022 22:59:09 +0100 Subject: [PATCH 1322/2916] feat: Obfuscate diffs of Secrets --- cmd/kluctl/args/misc.go | 1 + cmd/kluctl/commands/cmd_delete.go | 2 +- cmd/kluctl/commands/cmd_deploy.go | 8 +-- cmd/kluctl/commands/cmd_diff.go | 2 +- cmd/kluctl/commands/cmd_poke_images.go | 2 +- cmd/kluctl/commands/cmd_prune.go | 2 +- cmd/kluctl/commands/command_result.go | 10 +++- docs/reference/commands/delete.md | 1 + docs/reference/commands/deploy.md | 1 + docs/reference/commands/diff.md | 1 + docs/reference/commands/poke-images.md | 1 + docs/reference/commands/prune.md | 1 + e2e/obfuscate_test.go | 48 +++++++++++++++++ e2e/seal_test.go | 4 +- e2e/utils.go | 8 +++ e2e/utils_resources.go | 10 ++-- pkg/diff/diff.go | 73 ++++++++++++++++---------- pkg/diff/obfuscate.go | 42 +++++++++++++++ 18 files changed, 174 insertions(+), 43 deletions(-) create mode 100644 e2e/obfuscate_test.go create mode 100644 pkg/diff/obfuscate.go diff --git a/cmd/kluctl/args/misc.go b/cmd/kluctl/args/misc.go index 52b7b6f4c..513e71165 100644 --- a/cmd/kluctl/args/misc.go +++ b/cmd/kluctl/args/misc.go @@ -42,6 +42,7 @@ type AbortOnErrorFlags struct { type OutputFormatFlags struct { OutputFormat []string `group:"misc" short:"o" help:"Specify output format and target file, in the format 'format=path'. Format can either be 'text' or 'yaml'. Can be specified multiple times. The actual format for yaml is currently not documented and subject to change."` + NoObfuscate bool `group:"misc" help:"Disable obfuscation of sensitive/secret data"` } type OutputFlags struct { diff --git a/cmd/kluctl/commands/cmd_delete.go b/cmd/kluctl/commands/cmd_delete.go index b034c6c4e..ed20376b7 100644 --- a/cmd/kluctl/commands/cmd_delete.go +++ b/cmd/kluctl/commands/cmd_delete.go @@ -65,7 +65,7 @@ func (cmd *deleteCmd) Run(ctx context.Context) error { if err != nil { return err } - err = outputCommandResult(ctx, cmd.OutputFormat, result) + err = outputCommandResult(ctx, cmd.OutputFormat, cmd.NoObfuscate, result) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_deploy.go b/cmd/kluctl/commands/cmd_deploy.go index f819935db..624d6b6e3 100644 --- a/cmd/kluctl/commands/cmd_deploy.go +++ b/cmd/kluctl/commands/cmd_deploy.go @@ -64,7 +64,7 @@ func (cmd *deployCmd) runCmdDeploy(cmdCtx *commandCtx) error { cmd2.NoWait = cmd.NoWait cb := func(diffResult *types.CommandResult) error { - return cmd.diffResultCb(cmdCtx.ctx, diffResult) + return cmd.diffResultCb(cmdCtx.ctx, cmd.NoObfuscate, diffResult) } if cmd.Yes || cmd.DryRun { cb = nil @@ -74,7 +74,7 @@ func (cmd *deployCmd) runCmdDeploy(cmdCtx *commandCtx) error { if err != nil { return err } - err = outputCommandResult(cmdCtx.ctx, cmd.OutputFormat, result) + err = outputCommandResult(cmdCtx.ctx, cmd.OutputFormat, cmd.NoObfuscate, result) if err != nil { return err } @@ -84,8 +84,8 @@ func (cmd *deployCmd) runCmdDeploy(cmdCtx *commandCtx) error { return nil } -func (cmd *deployCmd) diffResultCb(ctx context.Context, diffResult *types.CommandResult) error { - err := outputCommandResult(ctx, nil, diffResult) +func (cmd *deployCmd) diffResultCb(ctx context.Context, noObfuscate bool, diffResult *types.CommandResult) error { + err := outputCommandResult(ctx, nil, noObfuscate, diffResult) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_diff.go b/cmd/kluctl/commands/cmd_diff.go index bb8fcce19..a32991d41 100644 --- a/cmd/kluctl/commands/cmd_diff.go +++ b/cmd/kluctl/commands/cmd_diff.go @@ -50,7 +50,7 @@ func (cmd *diffCmd) Run(ctx context.Context) error { if err != nil { return err } - err = outputCommandResult(ctx, cmd.OutputFormat, result) + err = outputCommandResult(ctx, cmd.OutputFormat, cmd.NoObfuscate, result) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_poke_images.go b/cmd/kluctl/commands/cmd_poke_images.go index 7492acc74..c1593e58e 100644 --- a/cmd/kluctl/commands/cmd_poke_images.go +++ b/cmd/kluctl/commands/cmd_poke_images.go @@ -51,7 +51,7 @@ func (cmd *pokeImagesCmd) Run(ctx context.Context) error { if err != nil { return err } - err = outputCommandResult(ctx, cmd.OutputFormat, result) + err = outputCommandResult(ctx, cmd.OutputFormat, cmd.NoObfuscate, result) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_prune.go b/cmd/kluctl/commands/cmd_prune.go index 13516a1b4..9c887eb1c 100644 --- a/cmd/kluctl/commands/cmd_prune.go +++ b/cmd/kluctl/commands/cmd_prune.go @@ -54,7 +54,7 @@ func (cmd *pruneCmd) runCmdPrune(cmdCtx *commandCtx) error { if err != nil { return err } - err = outputCommandResult(cmdCtx.ctx, cmd.OutputFormat, result) + err = outputCommandResult(cmdCtx.ctx, cmd.OutputFormat, cmd.NoObfuscate, result) if err != nil { return err } diff --git a/cmd/kluctl/commands/command_result.go b/cmd/kluctl/commands/command_result.go index f9dd12408..dcf709fa1 100644 --- a/cmd/kluctl/commands/command_result.go +++ b/cmd/kluctl/commands/command_result.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "fmt" + "github.com/kluctl/kluctl/v2/pkg/diff" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/types/k8s" @@ -197,9 +198,16 @@ func outputHelper(ctx context.Context, output []string, cb func(format string) ( return nil } -func outputCommandResult(ctx context.Context, output []string, cr *types.CommandResult) error { +func outputCommandResult(ctx context.Context, output []string, noObfuscate bool, cr *types.CommandResult) error { status.Flush(ctx) + if !noObfuscate { + var obfuscator diff.Obfuscator + for _, c := range cr.ChangedObjects { + obfuscator.Obfuscate(c.Ref, c.Changes) + } + } + return outputHelper(ctx, output, func(format string) (string, error) { return formatCommandResult(cr, format) }) diff --git a/docs/reference/commands/delete.md b/docs/reference/commands/delete.md index 264e4c553..f75f0f77c 100644 --- a/docs/reference/commands/delete.md +++ b/docs/reference/commands/delete.md @@ -51,6 +51,7 @@ Misc arguments: Must be in the form --helm-username=:, where must match the id specified in the helm-chart.yaml. + --no-obfuscate Disable obfuscation of sensitive/secret data -o, --output-format stringArray Specify output format and target file, in the format 'format=path'. Format can either be 'text' or 'yaml'. Can be specified multiple times. The actual format for yaml is diff --git a/docs/reference/commands/deploy.md b/docs/reference/commands/deploy.md index 044715d7b..edc7d6544 100644 --- a/docs/reference/commands/deploy.md +++ b/docs/reference/commands/deploy.md @@ -54,6 +54,7 @@ Misc arguments: Must be in the form --helm-username=:, where must match the id specified in the helm-chart.yaml. + --no-obfuscate Disable obfuscation of sensitive/secret data --no-wait Don't wait for objects readiness' -o, --output-format stringArray Specify output format and target file, in the format 'format=path'. Format can either be 'text' or 'yaml'. Can be diff --git a/docs/reference/commands/diff.md b/docs/reference/commands/diff.md index bb047a7c4..4441498af 100644 --- a/docs/reference/commands/diff.md +++ b/docs/reference/commands/diff.md @@ -55,6 +55,7 @@ Misc arguments: --ignore-annotations Ignores changes in annotations when diffing --ignore-labels Ignores changes in labels when diffing --ignore-tags Ignores changes in tags when diffing + --no-obfuscate Disable obfuscation of sensitive/secret data -o, --output-format stringArray Specify output format and target file, in the format 'format=path'. Format can either be 'text' or 'yaml'. Can be specified multiple times. The actual format for yaml is diff --git a/docs/reference/commands/poke-images.md b/docs/reference/commands/poke-images.md index 4d16e9589..77a33c404 100644 --- a/docs/reference/commands/poke-images.md +++ b/docs/reference/commands/poke-images.md @@ -48,6 +48,7 @@ Misc arguments: Must be in the form --helm-username=:, where must match the id specified in the helm-chart.yaml. + --no-obfuscate Disable obfuscation of sensitive/secret data -o, --output-format stringArray Specify output format and target file, in the format 'format=path'. Format can either be 'text' or 'yaml'. Can be specified multiple times. The actual format for yaml is diff --git a/docs/reference/commands/prune.md b/docs/reference/commands/prune.md index bef1d7c4b..5bb327f3c 100644 --- a/docs/reference/commands/prune.md +++ b/docs/reference/commands/prune.md @@ -44,6 +44,7 @@ Misc arguments: Must be in the form --helm-username=:, where must match the id specified in the helm-chart.yaml. + --no-obfuscate Disable obfuscation of sensitive/secret data -o, --output-format stringArray Specify output format and target file, in the format 'format=path'. Format can either be 'text' or 'yaml'. Can be specified multiple times. The actual format for yaml is diff --git a/e2e/obfuscate_test.go b/e2e/obfuscate_test.go new file mode 100644 index 000000000..8f0d018e5 --- /dev/null +++ b/e2e/obfuscate_test.go @@ -0,0 +1,48 @@ +package e2e + +import ( + "encoding/base64" + test_utils "github.com/kluctl/kluctl/v2/e2e/test-utils" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestObfuscateSecrets(t *testing.T) { + t.Parallel() + + k := defaultCluster1 + + p := test_utils.NewTestProject(t) + + createNamespace(t, k, p.TestSlug()) + + p.UpdateTarget("test", nil) + + addSecretDeployment(p, "secret", map[string]string{ + "secret": "secret_value_1", + }, resourceOpts{ + name: "secret", + namespace: p.TestSlug(), + }, false) + stdout, _ := p.KluctlMust("deploy", "--yes", "-t", "test") + assertSecretExists(t, k, p.TestSlug(), "secret") + assert.NotContains(t, stdout, "secret_value") + + p.UpdateYaml("secret/secret-secret.yml", func(o *uo.UnstructuredObject) error { + _ = o.SetNestedField("secret_value_2", "stringData", "secret") + return nil + }, "") + stdout, _ = p.KluctlMust("deploy", "--yes", "-t", "test") + assert.NotContains(t, stdout, "secret_value") + assert.Contains(t, stdout, "***** (obfuscated)") + + p.UpdateYaml("secret/secret-secret.yml", func(o *uo.UnstructuredObject) error { + _ = o.SetNestedField("secret_value_3", "stringData", "secret") + return nil + }, "") + stdout, _ = p.KluctlMust("deploy", "--yes", "-t", "test", "--no-obfuscate") + assert.Contains(t, stdout, "-"+base64.StdEncoding.EncodeToString([]byte("secret_value_2"))) + assert.Contains(t, stdout, "+"+base64.StdEncoding.EncodeToString([]byte("secret_value_3"))) + assert.NotContains(t, stdout, "***** (obfuscated)") +} diff --git a/e2e/seal_test.go b/e2e/seal_test.go index 2308ec103..532088948 100644 --- a/e2e/seal_test.go +++ b/e2e/seal_test.go @@ -109,7 +109,7 @@ func prepareSealTest(t *testing.T, k *test_utils.EnvTestCluster, secrets map[str addSecretsSet(p, "test", varsSources) addSecretsSetToTarget(p, "test-target", "test") - addSecretDeployment(p, "secret-deployment", secrets, resourceOpts{name: "secret", namespace: p.TestSlug()}) + addSecretDeployment(p, "secret-deployment", secrets, resourceOpts{name: "secret", namespace: p.TestSlug()}, true) return p } @@ -391,7 +391,7 @@ func TestSeal_MultipleSecrets(t *testing.T) { }, }), }, true) - addSecretDeployment(p, "secret-deployment2", secret2, resourceOpts{name: "secret2", namespace: p.TestSlug()}) + addSecretDeployment(p, "secret-deployment2", secret2, resourceOpts{name: "secret2", namespace: p.TestSlug()}, true) p.KluctlMust("seal", "-t", "test-target") diff --git a/e2e/utils.go b/e2e/utils.go index 1e3b6c61b..270c273fa 100644 --- a/e2e/utils.go +++ b/e2e/utils.go @@ -43,6 +43,14 @@ func assertConfigMapNotExists(t *testing.T, k *test_utils.EnvTestCluster, namesp } } +func assertSecretExists(t *testing.T, k *test_utils.EnvTestCluster, namespace string, name string) *uo.UnstructuredObject { + x, err := k.Get(v1.SchemeGroupVersion.WithResource("secrets"), namespace, name) + if err != nil { + t.Fatalf("unexpected error '%v' while getting Secret %s/%s", err, namespace, name) + } + return x +} + func assertNestedFieldEquals(t *testing.T, o *uo.UnstructuredObject, expected interface{}, keys ...interface{}) { v, ok, err := o.GetNestedField(keys...) if err != nil { diff --git a/e2e/utils_resources.go b/e2e/utils_resources.go index 6d645d980..4131374b7 100644 --- a/e2e/utils_resources.go +++ b/e2e/utils_resources.go @@ -45,7 +45,7 @@ func createConfigMapObject(data map[string]string, opts resourceOpts) *uo.Unstru } func createSecretObject(data map[string]string, opts resourceOpts) *uo.UnstructuredObject { - o := createCoreV1Object("ConfigMap", opts) + o := createCoreV1Object("Secret", opts) if data != nil { o.SetNestedField(data, "stringData") } @@ -59,10 +59,14 @@ func addConfigMapDeployment(p *test_utils.TestProject, dir string, data map[stri }, opts.tags) } -func addSecretDeployment(p *test_utils.TestProject, dir string, data map[string]string, opts resourceOpts) { +func addSecretDeployment(p *test_utils.TestProject, dir string, data map[string]string, opts resourceOpts, sealme bool) { + sealmeExt := "" + if sealme { + sealmeExt = ".sealme" + } o := createSecretObject(data, opts) fname := fmt.Sprintf("secret-%s.yml", opts.name) p.AddKustomizeDeployment(dir, []test_utils.KustomizeResource{ - {fname, fname + ".sealme", o}, + {fname, fname + sealmeExt, o}, }, opts.tags) } diff --git a/pkg/diff/diff.go b/pkg/diff/diff.go index 5781b9312..f9f1a1c55 100644 --- a/pkg/diff/diff.go +++ b/pkg/diff/diff.go @@ -54,6 +54,10 @@ func Diff(oldObject *uo.UnstructuredObject, newObject *uo.UnstructuredObject) ([ if err != nil { return nil, err } + err = updateUnifiedDiff(c2) + if err != nil { + return nil, err + } changes = append(changes, *c2) } @@ -65,59 +69,70 @@ func Diff(oldObject *uo.UnstructuredObject, newObject *uo.UnstructuredObject) ([ func convertChange(c diff2.Change, oldObject *uo.UnstructuredObject, newObject *uo.UnstructuredObject) (*types.Change, error) { switch c.Type { case "create": - ud, err := buildUnifiedDiff(notPresent, c.To, false) - if err != nil { - return nil, err - } p, err := convertPath(c.Path, newObject.Object) if err != nil { return nil, err } return &types.Change{ - Type: "insert", - JsonPath: p, - NewValue: c.To, - UnifiedDiff: ud, + Type: "insert", + JsonPath: p, + NewValue: c.To, }, nil case "delete": - ud, err := buildUnifiedDiff(c.From, notPresent, false) - if err != nil { - return nil, err - } p, err := convertPath(c.Path, oldObject.Object) if err != nil { return nil, err } return &types.Change{ - Type: "delete", - JsonPath: p, - OldValue: c.From, - UnifiedDiff: ud, + Type: "delete", + JsonPath: p, + OldValue: c.From, }, nil case "update": - showType := false - if reflect.TypeOf(c.From) != reflect.TypeOf(c.To) { - showType = true - } - ud, err := buildUnifiedDiff(c.From, c.To, showType) - if err != nil { - return nil, err - } p, err := convertPath(c.Path, newObject.Object) if err != nil { return nil, err } return &types.Change{ - Type: "update", - JsonPath: p, - NewValue: c.To, - OldValue: c.From, - UnifiedDiff: ud, + Type: "update", + JsonPath: p, + NewValue: c.To, + OldValue: c.From, }, nil } return nil, fmt.Errorf("unknown change type %s", c.Type) } +func updateUnifiedDiff(change *types.Change) error { + switch change.Type { + case "insert": + ud, err := buildUnifiedDiff(notPresent, change.NewValue, false) + if err != nil { + return err + } + change.UnifiedDiff = ud + case "delete": + ud, err := buildUnifiedDiff(change.OldValue, notPresent, false) + if err != nil { + return err + } + change.UnifiedDiff = ud + case "update": + showType := false + if reflect.TypeOf(change.OldValue) != reflect.TypeOf(change.NewValue) { + showType = true + } + ud, err := buildUnifiedDiff(change.OldValue, change.NewValue, showType) + if err != nil { + return err + } + change.UnifiedDiff = ud + default: + return fmt.Errorf("unknown change type %s", change.Type) + } + return nil +} + func stableSortChanges(changes []types.Change) { changesStrs := make([]string, len(changes)) changesIndexes := make([]int, len(changes)) diff --git a/pkg/diff/obfuscate.go b/pkg/diff/obfuscate.go new file mode 100644 index 000000000..710ca60c0 --- /dev/null +++ b/pkg/diff/obfuscate.go @@ -0,0 +1,42 @@ +package diff + +import ( + "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "k8s.io/apimachinery/pkg/runtime/schema" + "strings" +) + +var secretGvk = schema.GroupKind{Group: "", Kind: "Secret"} + +type Obfuscator struct { +} + +func (o *Obfuscator) Obfuscate(ref k8s.ObjectRef, changes []types.Change) { + if ref.GVK.GroupKind() == secretGvk { + o.obfuscateSecret(ref, changes) + } +} + +func (o *Obfuscator) obfuscateSecret(ref k8s.ObjectRef, changes []types.Change) { + for i, _ := range changes { + c := &changes[i] + if strings.HasPrefix(c.JsonPath, "data.") || strings.HasPrefix(c.JsonPath, "stringData.") { + if c.NewValue != nil { + c.NewValue = "*****a" + } + if c.OldValue != nil { + c.OldValue = "*****b" + } + _ = updateUnifiedDiff(c) + if c.NewValue != nil { + c.NewValue = "*****" + c.UnifiedDiff = strings.ReplaceAll(c.UnifiedDiff, "*****a", "***** (obfuscated)") + } + if c.OldValue != nil { + c.OldValue = "*****" + c.UnifiedDiff = strings.ReplaceAll(c.UnifiedDiff, "*****b", "***** (obfuscated)") + } + } + } +} From 7554f43670154b128c25183c1fb6f23143f1204d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 14 Dec 2022 21:12:34 +0100 Subject: [PATCH 1323/2916] feat: Remove templateExcludes from deployment projects --- pkg/deployment/deployment_item.go | 5 ----- pkg/types/deployment.go | 3 +-- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 05df656bd..49d2e650f 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -6,7 +6,6 @@ import ( "github.com/kluctl/kluctl/v2/pkg/helm" "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/sops" - "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" @@ -125,10 +124,6 @@ func (di *DeploymentItem) render(forSeal bool) error { var excludePatterns []string excludePatterns = append(excludePatterns, "**/.git") - if len(di.Project.Config.TemplateExcludes) != 0 { - status.Deprecation(di.ctx.Ctx, "template-excludes", "'templateExcludes' are deprecated, use .templateignore files instead.") - } - excludePatterns = append(excludePatterns, di.Project.Config.TemplateExcludes...) err = filepath.WalkDir(*di.dir, func(p string, d fs.DirEntry, err error) error { if d.IsDir() { relDir, err := filepath.Rel(*di.dir, p) diff --git a/pkg/types/deployment.go b/pkg/types/deployment.go index eee074fda..cefcf4b59 100644 --- a/pkg/types/deployment.go +++ b/pkg/types/deployment.go @@ -94,8 +94,7 @@ type DeploymentProjectConfig struct { OverrideNamespace *string `yaml:"overrideNamespace,omitempty"` Tags []string `yaml:"tags,omitempty"` - IgnoreForDiff []*IgnoreForDiffItemConfig `yaml:"ignoreForDiff,omitempty"` - TemplateExcludes []string `yaml:"templateExcludes,omitempty"` + IgnoreForDiff []*IgnoreForDiffItemConfig `yaml:"ignoreForDiff,omitempty"` } func init() { From 3794ed68bcc9e90510004dacc3ee4d27fc901c6f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Dec 2022 22:08:10 +0000 Subject: [PATCH 1324/2916] chore(deps): Bump github.com/aws/aws-sdk-go from 1.44.158 to 1.44.160 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.158 to 1.44.160. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.158...v1.44.160) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b86ceba06..5f69d1c24 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e github.com/Masterminds/semver/v3 v3.2.0 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d - github.com/aws/aws-sdk-go v1.44.158 + github.com/aws/aws-sdk-go v1.44.160 github.com/bitnami-labs/sealed-secrets v0.19.2 github.com/cyphar/filepath-securejoin v0.2.3 github.com/docker/distribution v2.8.1+incompatible diff --git a/go.sum b/go.sum index 20b2f6dfc..434f6f811 100644 --- a/go.sum +++ b/go.sum @@ -132,8 +132,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/aws/aws-sdk-go v1.44.158 h1:Q71ei9ijL3KuyQcLJA9TtuYy2gMLsLdVH5Q2ackBq3s= -github.com/aws/aws-sdk-go v1.44.158/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.160 h1:F41sWUel1CJ69ezoBGCg8sDyu9kyeKEpwmDrLXbCuyA= +github.com/aws/aws-sdk-go v1.44.160/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= From e779ecf2a3a3b21ba43d14daf1330834c3c21202 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Dec 2022 22:08:40 +0000 Subject: [PATCH 1325/2916] chore(deps): Bump helm.sh/helm/v3 from 3.10.2 to 3.10.3 Bumps [helm.sh/helm/v3](https://github.com/helm/helm) from 3.10.2 to 3.10.3. - [Release notes](https://github.com/helm/helm/releases) - [Commits](https://github.com/helm/helm/compare/v3.10.2...v3.10.3) --- updated-dependencies: - dependency-name: helm.sh/helm/v3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b86ceba06..a5bddba73 100644 --- a/go.mod +++ b/go.mod @@ -47,7 +47,7 @@ require ( golang.org/x/text v0.5.0 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 - helm.sh/helm/v3 v3.10.2 + helm.sh/helm/v3 v3.10.3 k8s.io/api v0.26.0 k8s.io/apiextensions-apiserver v0.25.4 k8s.io/apimachinery v0.26.0 diff --git a/go.sum b/go.sum index 20b2f6dfc..2c8e17b78 100644 --- a/go.sum +++ b/go.sum @@ -1339,8 +1339,8 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= -helm.sh/helm/v3 v3.10.2 h1:2PmN9NgmqTn5pswfL5Kh2LxOKjkmh0hxKLe6/J0yUY4= -helm.sh/helm/v3 v3.10.2/go.mod h1:CXOcs02AYvrlPMWARNYNRgf2rNP7gLJQsi/Ubd4EDrI= +helm.sh/helm/v3 v3.10.3 h1:wL7IUZ7Zyukm5Kz0OUmIFZgKHuAgByCrUcJBtY0kDyw= +helm.sh/helm/v3 v3.10.3/go.mod h1:CXOcs02AYvrlPMWARNYNRgf2rNP7gLJQsi/Ubd4EDrI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From e95e3e5908a967acdfb31cb16bbeccdc0ceafd35 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 15 Dec 2022 08:14:22 +0000 Subject: [PATCH 1326/2916] chore(deps): Bump sigs.k8s.io/controller-runtime from 0.13.1 to 0.14.0 Bumps [sigs.k8s.io/controller-runtime](https://github.com/kubernetes-sigs/controller-runtime) from 0.13.1 to 0.14.0. - [Release notes](https://github.com/kubernetes-sigs/controller-runtime/releases) - [Changelog](https://github.com/kubernetes-sigs/controller-runtime/blob/master/RELEASE.md) - [Commits](https://github.com/kubernetes-sigs/controller-runtime/compare/v0.13.1...v0.14.0) --- updated-dependencies: - dependency-name: sigs.k8s.io/controller-runtime dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 14 +++++++------- go.sum | 37 +++++++++++++++++-------------------- 2 files changed, 24 insertions(+), 27 deletions(-) diff --git a/go.mod b/go.mod index 1f310f9c2..673227fa3 100644 --- a/go.mod +++ b/go.mod @@ -49,9 +49,9 @@ require ( gopkg.in/yaml.v3 v3.0.1 helm.sh/helm/v3 v3.10.3 k8s.io/api v0.26.0 - k8s.io/apiextensions-apiserver v0.25.4 + k8s.io/apiextensions-apiserver v0.26.0 k8s.io/apimachinery v0.26.0 - k8s.io/client-go v0.25.4 + k8s.io/client-go v0.26.0 k8s.io/klog/v2 v2.80.1 sigs.k8s.io/kustomize/api v0.12.1 // indirect sigs.k8s.io/kustomize/kyaml v0.13.9 @@ -64,7 +64,7 @@ require ( github.com/huandu/xstrings v1.4.0 github.com/otiai10/copy v1.9.0 go.mozilla.org/sops/v3 v3.7.3 - sigs.k8s.io/controller-runtime v0.13.1 + sigs.k8s.io/controller-runtime v0.14.0 ) require ( @@ -226,7 +226,7 @@ require ( go.uber.org/atomic v1.10.0 // indirect golang.org/x/mod v0.6.0 // indirect golang.org/x/oauth2 v0.2.0 // indirect - golang.org/x/time v0.2.0 // indirect + golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.2.0 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect google.golang.org/api v0.102.0 // indirect @@ -239,12 +239,12 @@ require ( gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/urfave/cli.v1 v1.20.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect - k8s.io/apiserver v0.25.4 // indirect + k8s.io/apiserver v0.26.0 // indirect k8s.io/cli-runtime v0.25.3 // indirect - k8s.io/component-base v0.25.4 // indirect + k8s.io/component-base v0.26.0 // indirect k8s.io/kube-openapi v0.0.0-20221110221610-a28e98eb7c70 // indirect k8s.io/kubectl v0.25.3 // indirect - k8s.io/utils v0.0.0-20221108210102-8e77b1f39fe2 // indirect + k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 // indirect oras.land/oras-go v1.2.1 // indirect sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect sigs.k8s.io/yaml v1.3.0 // indirect diff --git a/go.sum b/go.sum index b087c36fd..c865d24b6 100644 --- a/go.sum +++ b/go.sum @@ -241,7 +241,7 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= -github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= +github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= github.com/fluxcd/go-git/v5 v5.0.0-20221206140629-ec778c2c37df h1:2BHXJp1PwX7D47Q2oaKDekn+BZVZCmxeCWNi+FyownE= github.com/fluxcd/go-git/v5 v5.0.0-20221206140629-ec778c2c37df/go.mod h1:raWgfUV7lDQVXp4QXUaeNNJkRVKz97UQuF+0kdY7Vmo= @@ -657,14 +657,12 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/ohler55/ojg v1.14.5 h1:xCX2oyh/ZaoesbLH6fwVHStSJpk4o4eJs8ttXutzdg0= github.com/ohler55/ojg v1.14.5/go.mod h1:7Ghirupn8NC8hSSDpI0gcjorPxj+vSVIONDWfliHR1k= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo/v2 v2.5.0 h1:TRtrvv2vdQqzkwrQ1ke6vtXf7IK34RBUJafIy1wMwls= +github.com/onsi/ginkgo/v2 v2.6.0 h1:9t9b9vRUbFq3C4qKFCGkVuq/fIHji802N1nrtkh1mNc= github.com/onsi/gomega v1.24.1 h1:KORJXNNTzJXzu4ScJWssJfJMnJ+2QJqhoQSRwNlze9E= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= @@ -873,7 +871,7 @@ go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0 go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -1118,8 +1116,8 @@ golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.2.0 h1:52I/1L54xyEQAYdtcSuxtiT84KGYTBGXwayxmIpNJhE= -golang.org/x/time v0.2.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -1316,7 +1314,6 @@ gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0= gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= @@ -1350,33 +1347,33 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.26.0 h1:IpPlZnxBpV1xl7TGk/X6lFtpgjgntCg8PJ+qrPHAC7I= k8s.io/api v0.26.0/go.mod h1:k6HDTaIFC8yn1i6pSClSqIwLABIcLV9l5Q4EcngKnQg= -k8s.io/apiextensions-apiserver v0.25.4 h1:7hu9pF+xikxQuQZ7/30z/qxIPZc2J1lFElPtr7f+B6U= -k8s.io/apiextensions-apiserver v0.25.4/go.mod h1:bkSGki5YBoZWdn5pWtNIdGvDrrsRWlmnvl9a+tAw5vQ= +k8s.io/apiextensions-apiserver v0.26.0 h1:Gy93Xo1eg2ZIkNX/8vy5xviVSxwQulsnUdQ00nEdpDo= +k8s.io/apiextensions-apiserver v0.26.0/go.mod h1:7ez0LTiyW5nq3vADtK6C3kMESxadD51Bh6uz3JOlqWQ= k8s.io/apimachinery v0.26.0 h1:1feANjElT7MvPqp0JT6F3Ss6TWDwmcjLypwoPpEf7zg= k8s.io/apimachinery v0.26.0/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74= -k8s.io/apiserver v0.25.4 h1:/3TwZcgLqX7wUxq7TtXOUqXeBTwXIblVMQdhR5XZ7yo= -k8s.io/apiserver v0.25.4/go.mod h1:rPcm567XxjOnnd7jedDUnGJGmDGAo+cT6H7QHAN+xV0= +k8s.io/apiserver v0.26.0 h1:q+LqIK5EZwdznGZb8bq0+a+vCqdeEEe4Ux3zsOjbc4o= +k8s.io/apiserver v0.26.0/go.mod h1:aWhlLD+mU+xRo+zhkvP/gFNbShI4wBDHS33o0+JGI84= k8s.io/cli-runtime v0.25.3 h1:Zs7P7l7db/5J+KDePOVtDlArAa9pZXaDinGWGZl0aM8= k8s.io/cli-runtime v0.25.3/go.mod h1:InHHsjkyW5hQsILJGpGjeruiDZT/R0OkROQgD6GzxO4= -k8s.io/client-go v0.25.4 h1:3RNRDffAkNU56M/a7gUfXaEzdhZlYhoW8dgViGy5fn8= -k8s.io/client-go v0.25.4/go.mod h1:8trHCAC83XKY0wsBIpbirZU4NTUpbuhc2JnI7OruGZw= -k8s.io/component-base v0.25.4 h1:n1bjg9Yt+G1C0WnIDJmg2fo6wbEU1UGMRiQSjmj7hNQ= -k8s.io/component-base v0.25.4/go.mod h1:nnZJU8OP13PJEm6/p5V2ztgX2oyteIaAGKGMYb2L2cY= +k8s.io/client-go v0.26.0 h1:lT1D3OfO+wIi9UFolCrifbjUUgu7CpLca0AD8ghRLI8= +k8s.io/client-go v0.26.0/go.mod h1:I2Sh57A79EQsDmn7F7ASpmru1cceh3ocVT9KlX2jEZg= +k8s.io/component-base v0.26.0 h1:0IkChOCohtDHttmKuz+EP3j3+qKmV55rM9gIFTXA7Vs= +k8s.io/component-base v0.26.0/go.mod h1:lqHwlfV1/haa14F/Z5Zizk5QmzaVf23nQzCwVOQpfC8= k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20221110221610-a28e98eb7c70 h1:zfqQc1V6/ZgGpvrOVvr62OjiqQX4lZjfznK34NQwkqw= k8s.io/kube-openapi v0.0.0-20221110221610-a28e98eb7c70/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= k8s.io/kubectl v0.25.3 h1:HnWJziEtmsm4JaJiKT33kG0kadx68MXxUE8UEbXnN4U= k8s.io/kubectl v0.25.3/go.mod h1:glU7PiVj/R6Ud4A9FJdTcJjyzOtCJyc0eO7Mrbh3jlI= -k8s.io/utils v0.0.0-20221108210102-8e77b1f39fe2 h1:GfD9OzL11kvZN5iArC6oTS7RTj7oJOIfnislxYlqTj8= -k8s.io/utils v0.0.0-20221108210102-8e77b1f39fe2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 h1:KTgPnR10d5zhztWptI952TNtt/4u5h3IzDXkdIMuo2Y= +k8s.io/utils v0.0.0-20221128185143-99ec85e7a448/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= oras.land/oras-go v1.2.1 h1:/VcGS8FUy3eEXLl/1vC4QypLHwrfSmgW7ygsoklqKK8= oras.land/oras-go v1.2.1/go.mod h1:3N11Z5E3c4ZzOjroCl1RtAdB4yNAYl7A27j2SVf913A= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/controller-runtime v0.13.1 h1:tUsRCSJVM1QQOOeViGeX3GMT3dQF1eePPw6sEE3xSlg= -sigs.k8s.io/controller-runtime v0.13.1/go.mod h1:Zbz+el8Yg31jubvAEyglRZGdLAjplZl+PgtYNI6WNTI= +sigs.k8s.io/controller-runtime v0.14.0 h1:ju2xsov5Ara6FoQuddg+az+rAxsUsTYn2IYyEKCTyDc= +sigs.k8s.io/controller-runtime v0.14.0/go.mod h1:GaRkrY8a7UZF0kqFFbUKG7n9ICiTY5T55P1RiE3UZlU= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/kustomize/api v0.12.1 h1:7YM7gW3kYBwtKvoY216ZzY+8hM+lV53LUayghNRJ0vM= From 6da364cbf6cc86daabe844a4560d2e02fe9a62d6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 15 Dec 2022 14:35:32 +0100 Subject: [PATCH 1327/2916] feat: Add support for local Helm Charts --- cmd/kluctl/commands/cmd_helm_pull.go | 11 ++++- docs/reference/deployments/helm.md | 6 +++ e2e/helm_test.go | 25 ++++++++++ e2e/test-utils/helm_repo_utils.go | 18 ++++--- e2e/test-utils/project.go | 8 +++- pkg/deployment/deployment_item.go | 8 ++-- pkg/helm/chart.go | 72 ++++++++++++++++++++++++++-- pkg/helm/helm_release.go | 36 ++++++++++++-- pkg/types/helm_chart.go | 47 +++++++++++++++++- 9 files changed, 207 insertions(+), 24 deletions(-) diff --git a/cmd/kluctl/commands/cmd_helm_pull.go b/cmd/kluctl/commands/cmd_helm_pull.go index ee8fc429d..0931ea0b8 100644 --- a/cmd/kluctl/commands/cmd_helm_pull.go +++ b/cmd/kluctl/commands/cmd_helm_pull.go @@ -133,11 +133,20 @@ func loadHelmReleases(projectDir string, baseChartsDir string, credentialsProvid return nil } - hr, err := helm.NewRelease(p, baseChartsDir, credentialsProvider) + relDir, err := filepath.Rel(projectDir, filepath.Dir(p)) if err != nil { return err } + hr, err := helm.NewRelease(projectDir, relDir, p, baseChartsDir, credentialsProvider) + if err != nil { + return err + } + + if hr.Chart.IsLocalChart() { + return nil + } + releases = append(releases, hr) chart := hr.Chart key := fmt.Sprintf("%s / %s", chart.GetRepo(), chart.GetChartName()) diff --git a/docs/reference/deployments/helm.md b/docs/reference/deployments/helm.md index f42fe69c3..bc31615f6 100644 --- a/docs/reference/deployments/helm.md +++ b/docs/reference/deployments/helm.md @@ -99,6 +99,12 @@ helmChart: namespace: pepper ``` +### path +As alternative to `repo`, you can also specify `path`. The path must point to a local Helm Chart that is relative to the +`helm-chart.yaml`. The local Chart must reside in your Kluctl project. + +When `path` is specified, `repo`, `chartName`, `chartVersion` and `updateContrainsts` are not allowed. + ### chartName The name of the chart that can be found in the repository. diff --git a/e2e/helm_test.go b/e2e/helm_test.go index cf33cf37c..9d8f519cb 100644 --- a/e2e/helm_test.go +++ b/e2e/helm_test.go @@ -473,6 +473,31 @@ func TestHelmRenderOfflineKubernetes(t *testing.T) { }, cm1.Object["data"]) } +func TestHelmLocalChart(t *testing.T) { + t.Parallel() + + k := defaultCluster1 + + p := test_utils.NewTestProject(t) + + createNamespace(t, k, p.TestSlug()) + + p.UpdateTarget("test", nil) + p.AddHelmDeployment("helm1", "../test-chart1", "", "", "test-helm-1", p.TestSlug(), nil) + p.AddHelmDeployment("helm2", "test-chart2", "", "", "test-helm-2", p.TestSlug(), nil) + + test_utils.CreateHelmDir(t, "test-chart1", "0.1.0", filepath.Join(p.LocalRepoDir(), "test-chart1")) + test_utils.CreateHelmDir(t, "test-chart2", "0.1.0", filepath.Join(p.LocalRepoDir(), "helm2/test-chart2")) + + p.KluctlMust("deploy", "--yes", "-t", "test") + assertConfigMapExists(t, k, p.TestSlug(), "test-helm-1-test-chart1") + assertConfigMapExists(t, k, p.TestSlug(), "test-helm-2-test-chart2") + + _, stderr := p.KluctlMust("helm-pull") + assert.NotContains(t, stderr, "test-chart1") + assert.NotContains(t, stderr, "test-chart2") +} + func getChartDir(t *testing.T, p *test_utils.TestProject, url2 string, chartName string, chartVersion string) string { u, err := url.Parse(url2) if err != nil { diff --git a/e2e/test-utils/helm_repo_utils.go b/e2e/test-utils/helm_repo_utils.go index aabe7835b..6bf45c0f5 100644 --- a/e2e/test-utils/helm_repo_utils.go +++ b/e2e/test-utils/helm_repo_utils.go @@ -24,25 +24,23 @@ import ( "testing" ) -func createHelmPackage(t *testing.T, name string, version string) string { - tmpDir := t.TempDir() - +func CreateHelmDir(t *testing.T, name string, version string, dest string) { err := fs.WalkDir(test_resources.HelmChartFS, ".", func(path string, d fs.DirEntry, err error) error { if d.IsDir() { - return os.MkdirAll(filepath.Join(tmpDir, path), 0o700) + return os.MkdirAll(filepath.Join(dest, path), 0o700) } else { b, err := test_resources.HelmChartFS.ReadFile(path) if err != nil { return err } - return os.WriteFile(filepath.Join(tmpDir, path), b, 0o600) + return os.WriteFile(filepath.Join(dest, path), b, 0o600) } }) if err != nil { t.Fatal(err) } - c, err := uo.FromFile(filepath.Join(tmpDir, "Chart.yaml")) + c, err := uo.FromFile(filepath.Join(dest, "Chart.yaml")) if err != nil { t.Fatal(err) } @@ -50,10 +48,16 @@ func createHelmPackage(t *testing.T, name string, version string) string { _ = c.SetNestedField(name, "name") _ = c.SetNestedField(version, "version") - err = yaml.WriteYamlFile(filepath.Join(tmpDir, "Chart.yaml"), c) + err = yaml.WriteYamlFile(filepath.Join(dest, "Chart.yaml"), c) if err != nil { t.Fatal(err) } +} + +func createHelmPackage(t *testing.T, name string, version string) string { + tmpDir := t.TempDir() + + CreateHelmDir(t, name, version, tmpDir) settings := cli.New() client := action.NewPackage() diff --git a/e2e/test-utils/project.go b/e2e/test-utils/project.go index 3d0b891fb..b824a5223 100644 --- a/e2e/test-utils/project.go +++ b/e2e/test-utils/project.go @@ -14,6 +14,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" registry2 "helm.sh/helm/v3/pkg/registry" + "net/url" "os" "os/exec" "path/filepath" @@ -259,7 +260,11 @@ func (p *TestProject) AddKustomizeDeployment(dir string, resources []KustomizeRe } func (p *TestProject) AddHelmDeployment(dir string, repoUrl string, chartName, version string, releaseName string, namespace string, values map[string]any) { - if registry2.IsOCI(repoUrl) { + localPath := "" + if u, err := url.Parse(repoUrl); err != nil || u.Host == "" { + localPath = repoUrl + repoUrl = "" + } else if registry2.IsOCI(repoUrl) { repoUrl += "/" + chartName chartName = "" } @@ -272,6 +277,7 @@ func (p *TestProject) AddHelmDeployment(dir string, repoUrl string, chartName, v *o = *uo.FromMap(map[string]interface{}{ "helmChart": map[string]any{ "repo": repoUrl, + "path": localPath, "chartVersion": version, "releaseName": releaseName, "namespace": namespace, diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 49d2e650f..bf5469beb 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -130,9 +130,9 @@ func (di *DeploymentItem) render(forSeal bool) error { if err != nil { return err } - if yaml.Exists(filepath.Join(p, "helm-chart.yml")) { + if yaml.Exists(filepath.Join(p, "Chart.yaml")) { // never try to render helm charts - ep := filepath.Join(relDir, "charts/**") + ep := fmt.Sprintf("%s/**", relDir) ep = filepath.ToSlash(ep) excludePatterns = append(excludePatterns, ep) return filepath.SkipDir @@ -188,7 +188,7 @@ func (di *DeploymentItem) renderHelmCharts() error { return err } - hr, err := helm.NewRelease(p, di.ctx.HelmChartsDir, di.ctx.HelmCredentials) + hr, err := helm.NewRelease(di.Project.source.dir, filepath.Join(di.RelToSourceItemDir, subDir), p, di.ctx.HelmChartsDir, di.ctx.HelmCredentials) if err != nil { return err } @@ -411,7 +411,7 @@ func (di *DeploymentItem) generateKustomizationYaml(subDir string) (*uo.Unstruct if di.isHelmValuesYaml(de.Name()) { continue } else if di.isHelmChartYaml(de.Name()) { - c, err := helm.NewRelease(filepath.Join(di.RenderedDir, subDir, de.Name()), di.ctx.HelmChartsDir, nil) + c, err := helm.NewRelease(di.Project.source.dir, filepath.Join(di.RelToSourceItemDir, subDir), filepath.Join(di.RenderedDir, subDir, de.Name()), di.ctx.HelmChartsDir, nil) if err != nil { return nil, err } diff --git a/pkg/helm/chart.go b/pkg/helm/chart.go index c5c29e54b..6ef7a4a64 100644 --- a/pkg/helm/chart.go +++ b/pkg/helm/chart.go @@ -7,6 +7,8 @@ import ( "github.com/kluctl/kluctl/v2/pkg/registries" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/yaml" cp "github.com/otiai10/copy" "github.com/rogpeppe/go-internal/lockedfile" "helm.sh/helm/v3/pkg/action" @@ -28,6 +30,7 @@ type HelmCredentialsProvider interface { type Chart struct { repo string + localPath string chartName string credentials HelmCredentialsProvider @@ -36,18 +39,32 @@ type Chart struct { versions []string } -func NewChart(repo string, chartName string, credentialsProvider HelmCredentialsProvider, credentialsId string) (*Chart, error) { +func NewChart(repo string, localPath string, chartName string, credentialsProvider HelmCredentialsProvider, credentialsId string) (*Chart, error) { hc := &Chart{ repo: repo, + localPath: localPath, credentials: credentialsProvider, credentialsId: credentialsId, } - if repo == "" { - return nil, fmt.Errorf("repo is missing") + if localPath == "" && repo == "" { + return nil, fmt.Errorf("repo and localPath are missing") } - if registry.IsOCI(repo) { + if hc.IsLocalChart() { + chartYaml, err := uo.FromFile(yaml.FixPathExt(filepath.Join(hc.localPath, "Chart.yaml"))) + if err != nil { + return nil, err + } + x, _, err := chartYaml.GetNestedString("name") + if err != nil { + return nil, err + } + if x == "" { + return nil, fmt.Errorf("invalid/empty chart name") + } + hc.chartName = x + } else if registry.IsOCI(repo) { if chartName != "" { return nil, fmt.Errorf("chartName can't be specified when using OCI repos") } @@ -70,6 +87,29 @@ func (c *Chart) GetRepo() string { return c.repo } +func (c *Chart) GetLocalPath() string { + return c.localPath +} + +func (c *Chart) IsLocalChart() bool { + return c.localPath != "" +} + +func (c *Chart) GetLocalChartVersion() (string, error) { + chartYaml, err := uo.FromFile(yaml.FixPathExt(filepath.Join(c.localPath, "Chart.yaml"))) + if err != nil { + return "", err + } + x, _, err := chartYaml.GetNestedString("version") + if err != nil { + return "", err + } + if x == "" { + return "", fmt.Errorf("invalid/empty chart version") + } + return x, nil +} + func (c *Chart) BuildPulledChartDir(baseDir string, version string) (string, error) { u, err := url.Parse(c.repo) if err != nil { @@ -122,6 +162,10 @@ func (c *Chart) GetChartName() string { } func (c *Chart) PullToTmp(ctx context.Context, version string) (*PulledChart, error) { + if c.IsLocalChart() { + return nil, fmt.Errorf("can not pull local charts") + } + tmpPullDir, err := os.MkdirTemp(utils.GetTmpBaseDir(ctx), c.chartName+"-pull-") if err != nil { return nil, err @@ -199,6 +243,10 @@ func (c *Chart) PullToTmp(ctx context.Context, version string) (*PulledChart, er } func (c *Chart) Pull(ctx context.Context, pc *PulledChart) error { + if c.IsLocalChart() { + return fmt.Errorf("can not pull local charts") + } + newPulled, err := c.PullToTmp(ctx, pc.version) if err != nil { return err @@ -250,6 +298,10 @@ func (c *Chart) doPullCached(ctx context.Context, version string) (*PulledChart, } func (c *Chart) PullCached(ctx context.Context, version string) (*PulledChart, error) { + if c.IsLocalChart() { + return nil, fmt.Errorf("can not pull local charts") + } + pc, lock, err := c.doPullCached(ctx, version) if err != nil { return nil, err @@ -259,6 +311,10 @@ func (c *Chart) PullCached(ctx context.Context, version string) (*PulledChart, e } func (c *Chart) PullInProject(ctx context.Context, baseDir string, version string) (*PulledChart, error) { + if c.IsLocalChart() { + return nil, fmt.Errorf("can not pull local charts") + } + cachePc, lock, err := c.doPullCached(ctx, version) if err != nil { return nil, err @@ -284,6 +340,10 @@ func (c *Chart) PullInProject(ctx context.Context, baseDir string, version strin } func (c *Chart) GetPulledChart(baseDir string, version string) (*PulledChart, error) { + if c.IsLocalChart() { + return nil, fmt.Errorf("can not pull local charts") + } + chartDir, err := c.BuildPulledChartDir(baseDir, version) if err != nil { return nil, err @@ -292,6 +352,10 @@ func (c *Chart) GetPulledChart(baseDir string, version string) (*PulledChart, er } func (c *Chart) QueryVersions(ctx context.Context) error { + if c.IsLocalChart() { + return fmt.Errorf("can not query versions for local charts") + } + if registry.IsOCI(c.repo) { return c.queryVersionsOci(ctx) } diff --git a/pkg/helm/helm_release.go b/pkg/helm/helm_release.go index 885082193..c4d8963e4 100644 --- a/pkg/helm/helm_release.go +++ b/pkg/helm/helm_release.go @@ -35,23 +35,41 @@ type Release struct { baseChartsDir string } -func NewRelease(configFile string, baseChartsDir string, credentialsProvider HelmCredentialsProvider) (*Release, error) { +func NewRelease(projectRoot string, relDirInProject string, configFile string, baseChartsDir string, credentialsProvider HelmCredentialsProvider) (*Release, error) { var config types.HelmChartConfig err := yaml.ReadYamlFile(configFile, &config) if err != nil { return nil, err } - _, err = semver.NewVersion(config.ChartVersion) - if err != nil { - return nil, fmt.Errorf("invalid chart version '%s': %w", config.ChartVersion, err) + if config.ChartVersion != "" { + _, err = semver.NewVersion(config.ChartVersion) + if err != nil { + return nil, fmt.Errorf("invalid chart version '%s': %w", config.ChartVersion, err) + } + } + + localPath := "" + if config.Path != "" { + if filepath.IsAbs(config.Path) { + return nil, fmt.Errorf("absolute path is not allowed in helm-chart.yaml") + } + localPath = filepath.Join(projectRoot, relDirInProject, config.Path) + localPath, err = filepath.Abs(localPath) + if err != nil { + return nil, err + } + err = utils.CheckInDir(projectRoot, localPath) + if err != nil { + return nil, err + } } credentialsId := "" if config.CredentialsId != nil { credentialsId = *config.CredentialsId } - chart, err := NewChart(config.Repo, config.ChartName, credentialsProvider, credentialsId) + chart, err := NewChart(config.Repo, localPath, config.ChartName, credentialsProvider, credentialsId) if err != nil { return nil, err } @@ -93,6 +111,14 @@ func (hr *Release) GetDeprecatedChartDir() string { } func (hr *Release) getPulledChart(ctx context.Context) (*PulledChart, error) { + if hr.Chart.IsLocalChart() { + version, err := hr.Chart.GetLocalChartVersion() + if err != nil { + return nil, err + } + return NewPulledChart(hr.Chart, version, hr.Chart.GetLocalPath(), false), nil + } + deprecatedPC := NewPulledChart(hr.Chart, hr.Config.ChartVersion, hr.GetDeprecatedChartDir(), false) if deprecatedPC.CheckExists() { status.Deprecation(ctx, "helm-charts-dir", "Your project has pre-pulled charts located next to the helm-chart.yaml, which is deprecated. "+ diff --git a/pkg/types/helm_chart.go b/pkg/types/helm_chart.go index 11498426b..e2c45601f 100644 --- a/pkg/types/helm_chart.go +++ b/pkg/types/helm_chart.go @@ -1,10 +1,17 @@ package types +import ( + "github.com/go-playground/validator/v10" + "github.com/kluctl/kluctl/v2/pkg/yaml" + "helm.sh/helm/v3/pkg/registry" +) + type HelmChartConfig2 struct { - Repo string `yaml:"repo" validate:"required"` + Repo string `yaml:"repo,omitempty"` + Path string `yaml:"path,omitempty"` CredentialsId *string `yaml:"credentialsId,omitempty"` ChartName string `yaml:"chartName,omitempty"` - ChartVersion string `yaml:"chartVersion" validate:"required"` + ChartVersion string `yaml:"chartVersion,omitempty"` UpdateConstraints *string `yaml:"updateConstraints,omitempty"` ReleaseName string `yaml:"releaseName" validate:"required"` Namespace *string `yaml:"namespace,omitempty"` @@ -13,6 +20,42 @@ type HelmChartConfig2 struct { SkipUpdate bool `yaml:"skipUpdate,omitempty"` } +func ValidateHelmChartConfig2(sl validator.StructLevel) { + c := sl.Current().Interface().(HelmChartConfig2) + if c.Repo == "" && c.Path == "" { + sl.ReportError("self", "repo", "repo", "either repo or path must be specified", "") + } else if c.Repo != "" && c.Path != "" { + sl.ReportError("self", "repo", "repo", "only one of repo and path can be specified", "") + } else if c.Repo != "" { + if c.ChartVersion == "" { + sl.ReportError("self", "chartVersion", "chartVersion", "chartVersion must be specified when repo is specified", "") + } + if registry.IsOCI(c.Repo) { + if c.ChartName != "" { + sl.ReportError("self", "chartName", "chartName", "chartName can not be specified when repo is a OCI url", "") + } + } else { + if c.ChartName == "" { + sl.ReportError("self", "chartName", "chartName", "chartName must be specified when repo is normal Helm repo", "") + } + } + } else if c.Path != "" { + if c.ChartName != "" { + sl.ReportError("self", "chartName", "chartName", "chartName can not be specified for local Helm charts", "") + } + if c.ChartVersion != "" { + sl.ReportError("self", "chartVersion", "chartVersion", "chartVersion can not be specified for local Helm charts", "") + } + if c.UpdateConstraints != nil { + sl.ReportError("self", "updateConstraints", "updateConstraints", "updateConstraints can not be specified for local Helm charts", "") + } + } +} + type HelmChartConfig struct { HelmChartConfig2 `yaml:"helmChart" validate:"required"` } + +func init() { + yaml.Validator.RegisterStructValidation(ValidateHelmChartConfig2, HelmChartConfig2{}) +} From ef2f7271b9e5e7d6ddf5efdcf580895433503edd Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 15 Dec 2022 14:42:59 +0100 Subject: [PATCH 1328/2916] fix: Show help when kluctl is called without arguments --- cmd/kluctl/commands/root.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index 2c6d696f1..8b7231230 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -18,6 +18,7 @@ package commands import ( "context" "fmt" + flag "github.com/spf13/pflag" "io" "log" "net/http" @@ -194,7 +195,7 @@ func checkNewVersion(ctx context.Context) { } func (c *cli) Run(ctx context.Context) error { - return nil + return flag.ErrHelp } func initViper(ctx context.Context) { From 11325b13aff0988c4f2d324539624dc4df6800fb Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 15 Dec 2022 15:01:07 +0100 Subject: [PATCH 1329/2916] fix: Fix help output of completion command --- cmd/kluctl/commands/cobra_utils.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/cmd/kluctl/commands/cobra_utils.go b/cmd/kluctl/commands/cobra_utils.go index 5ebf48f10..e8e68f528 100644 --- a/cmd/kluctl/commands/cobra_utils.go +++ b/cmd/kluctl/commands/cobra_utils.go @@ -88,7 +88,7 @@ func (c *rootCommand) buildCobraCmd(parent *commandAndGroups, cmdStruct interfac } cg.cmd.SetHelpFunc(func(command *cobra.Command, i []string) { - c.helpFunc(cg) + c.helpFunc(cg, command) }) return cg, nil @@ -283,9 +283,7 @@ func copyViperValuesToCobraFlags(flags *pflag.FlagSet) error { return utils.NewErrorListOrNil(retErr) } -func (c *rootCommand) helpFunc(cg *commandAndGroups) { - cmd := cg.cmd - +func (c *rootCommand) helpFunc(cg *commandAndGroups, cmd *cobra.Command) { termWidth := term.GetWidth() h := "Usage: " From 876cf3fdfc3b5a8f22948e79e2e6fac2dbf6bce2 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 15 Dec 2022 15:05:07 +0100 Subject: [PATCH 1330/2916] fix: Actually install shell completions in brew tap --- .goreleaser.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 9570d3b69..43a80732f 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -115,8 +115,6 @@ brews: description: "kluctl" install: | bin.install "kluctl" - test: | - system "#{bin}/kluctl version" bash_output = Utils.safe_popen_read(bin/"kluctl", "completion", "bash") (bash_completion/"kluctl").write bash_output @@ -126,3 +124,5 @@ brews: fish_output = Utils.safe_popen_read(bin/"kluctl", "completion", "fish") (fish_completion/"kluctl.fish").write fish_output + test: | + system "#{bin}/kluctl version" From 99a8aabb23784f89ed73bde09b7938d6d863a1ab Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 15 Dec 2022 15:08:12 +0100 Subject: [PATCH 1331/2916] fix: Flush status before printing version --- cmd/kluctl/commands/cmd_version.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/kluctl/commands/cmd_version.go b/cmd/kluctl/commands/cmd_version.go index c28a5b37e..3328a0203 100644 --- a/cmd/kluctl/commands/cmd_version.go +++ b/cmd/kluctl/commands/cmd_version.go @@ -2,6 +2,7 @@ package commands import ( "context" + "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/version" ) @@ -9,6 +10,7 @@ type versionCmd struct { } func (cmd *versionCmd) Run(ctx context.Context) error { + status.Flush(ctx) _, err := getStdout(ctx).WriteString(version.GetVersion() + "\n") return err } From c01a8bfb18ee1c96ab704f1004a11d347ec4062e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 15 Dec 2022 22:32:54 +0100 Subject: [PATCH 1332/2916] chore: Run go get -u ./... --- go.mod | 92 ++++++++++----------- go.sum | 248 +++++++++++++++++++++++---------------------------------- 2 files changed, 146 insertions(+), 194 deletions(-) diff --git a/go.mod b/go.mod index 673227fa3..4065cbaa8 100644 --- a/go.mod +++ b/go.mod @@ -3,16 +3,16 @@ module github.com/kluctl/kluctl/v2 go 1.19 require ( - github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e + github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 github.com/Masterminds/semver/v3 v3.2.0 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d - github.com/aws/aws-sdk-go v1.44.160 - github.com/bitnami-labs/sealed-secrets v0.19.2 + github.com/aws/aws-sdk-go v1.44.161 + github.com/bitnami-labs/sealed-secrets v0.19.3 github.com/cyphar/filepath-securejoin v0.2.3 github.com/docker/distribution v2.8.1+incompatible // See https://github.com/fluxcd/pkg/issues/397, especially in regard to skeema/knownhosts breaking compatibility github.com/fluxcd/go-git/v5 v5.0.0-20221206140629-ec778c2c37df - github.com/fluxcd/pkg/kustomize v0.11.0 + github.com/fluxcd/pkg/kustomize v0.12.0 github.com/go-playground/validator/v10 v10.11.1 github.com/gobwas/glob v0.2.3 // indirect github.com/golang-jwt/jwt/v4 v4.4.3 @@ -22,8 +22,8 @@ require ( github.com/imdario/mergo v0.3.13 github.com/jinzhu/copier v0.3.5 github.com/kevinburke/ssh_config v1.2.0 - github.com/kluctl/go-embed-python v0.0.0-3.10.7-20221017-1 - github.com/kluctl/go-jinja2 v0.0.0-20221017153334-80addb432305 + github.com/kluctl/go-embed-python v0.0.0-3.10.8-20221106-1 + github.com/kluctl/go-jinja2 v0.0.0-20221215083015-c3f906953ba1 github.com/mattn/go-colorable v0.1.13 github.com/mattn/go-isatty v0.0.16 github.com/mattn/go-runewidth v0.0.14 @@ -68,16 +68,16 @@ require ( ) require ( - cloud.google.com/go/compute v1.12.1 // indirect - cloud.google.com/go/compute/metadata v0.2.1 // indirect + cloud.google.com/go/compute v1.14.0 // indirect + cloud.google.com/go/compute/metadata v0.2.2 // indirect filippo.io/age v1.0.0 // indirect - github.com/Azure/azure-sdk-for-go v63.3.0+incompatible // indirect + github.com/Azure/azure-sdk-for-go v67.1.0+incompatible // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest v0.11.28 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.21 // indirect github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 // indirect - github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 // indirect + github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 // indirect github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect @@ -86,7 +86,7 @@ require ( github.com/BurntSushi/toml v1.2.1 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/sprig/v3 v3.2.2 // indirect + github.com/Masterminds/sprig/v3 v3.2.3 // indirect github.com/Masterminds/squirrel v1.5.3 // indirect github.com/Microsoft/go-winio v0.6.0 // indirect github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 // indirect @@ -97,20 +97,20 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver v3.5.1+incompatible // indirect github.com/cenkalti/backoff/v3 v3.2.2 // indirect - github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect - github.com/cloudflare/circl v1.2.0 // indirect - github.com/containerd/containerd v1.6.9 // indirect + github.com/cloudflare/circl v1.3.0 // indirect + github.com/containerd/containerd v1.6.13 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dimchansky/utfbom v1.1.1 // indirect - github.com/docker/cli v20.10.20+incompatible // indirect - github.com/docker/docker v20.10.20+incompatible // indirect + github.com/docker/cli v20.10.21+incompatible // indirect + github.com/docker/docker v20.10.21+incompatible // indirect github.com/docker/docker-credential-helpers v0.7.0 // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/drone/envsubst v1.0.3 // indirect - github.com/emicklei/go-restful/v3 v3.10.0 // indirect + github.com/emicklei/go-restful/v3 v3.10.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/evanphx/json-patch v5.6.0+incompatible // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect @@ -121,7 +121,7 @@ require ( github.com/go-errors/errors v1.4.2 // indirect github.com/go-git/gcfg v1.5.0 // indirect github.com/go-git/go-billy/v5 v5.3.1 // indirect - github.com/go-git/go-git/v5 v5.4.2 // indirect + github.com/go-git/go-git/v5 v5.5.1 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.20.0 // indirect @@ -138,17 +138,17 @@ require ( github.com/google/gofuzz v1.2.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.3.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect - github.com/googleapis/gax-go/v2 v2.6.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.2.1 // indirect + github.com/googleapis/gax-go/v2 v2.7.0 // indirect github.com/gorilla/mux v1.8.0 // indirect github.com/gosuri/uitable v0.0.4 // indirect github.com/goware/prefixer v0.0.0-20160118172347-395022866408 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-hclog v1.3.1 // indirect + github.com/hashicorp/go-hclog v1.4.0 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect - github.com/hashicorp/go-plugin v1.4.5 // indirect + github.com/hashicorp/go-plugin v1.4.8 // indirect github.com/hashicorp/go-retryablehttp v0.7.1 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect github.com/hashicorp/go-secure-stdlib/mlock v0.1.2 // indirect @@ -159,24 +159,24 @@ require ( github.com/hashicorp/go-version v1.6.0 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/hashicorp/vault/sdk v0.6.0 // indirect + github.com/hashicorp/vault/sdk v0.6.1 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef // indirect - github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jmoiron/sqlx v1.3.5 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.15.11 // indirect + github.com/klauspost/compress v1.15.13 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/lib/pq v1.10.7 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect - github.com/magiconair/properties v1.8.6 // indirect + github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect @@ -184,7 +184,7 @@ require ( github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moby/locker v1.0.1 // indirect github.com/moby/spdystream v0.2.0 // indirect - github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae // indirect + github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect @@ -194,23 +194,23 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc2 // indirect github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.0.5 // indirect + github.com/pelletier/go-toml/v2 v2.0.6 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pierrec/lz4 v2.6.1+incompatible // indirect github.com/pjbgf/sha1cd v0.2.3 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.14.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect - github.com/prometheus/common v0.37.0 // indirect + github.com/prometheus/common v0.39.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect - github.com/rivo/uniseg v0.4.2 // indirect + github.com/rivo/uniseg v0.4.3 // indirect github.com/rubenv/sql-migrate v1.2.0 // indirect - github.com/russross/blackfriday v1.6.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect github.com/sergi/go-diff v1.2.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect github.com/skeema/knownhosts v1.1.0 // indirect - github.com/spf13/afero v1.9.2 // indirect + github.com/spf13/afero v1.9.3 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/subosito/gotenv v1.4.1 // indirect @@ -219,20 +219,20 @@ require ( github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xlab/treeprint v1.1.0 // indirect - go.etcd.io/etcd/api/v3 v3.5.5 // indirect + go.etcd.io/etcd/api/v3 v3.5.6 // indirect go.mozilla.org/gopgagent v0.0.0-20170926210634-4d7ea76ff71a // indirect - go.opencensus.io v0.23.0 // indirect - go.starlark.net v0.0.0-20221028183056-acb66ad56dd2 // indirect + go.opencensus.io v0.24.0 // indirect + go.starlark.net v0.0.0-20221205180719-3fd0dac74452 // indirect go.uber.org/atomic v1.10.0 // indirect - golang.org/x/mod v0.6.0 // indirect - golang.org/x/oauth2 v0.2.0 // indirect + golang.org/x/mod v0.7.0 // indirect + golang.org/x/oauth2 v0.3.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.2.0 // indirect + golang.org/x/tools v0.4.0 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect - google.golang.org/api v0.102.0 // indirect + google.golang.org/api v0.105.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71 // indirect - google.golang.org/grpc v1.50.1 // indirect + google.golang.org/genproto v0.0.0-20221207170731-23e4bf6bdc37 // indirect + google.golang.org/grpc v1.51.0 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect @@ -240,12 +240,12 @@ require ( gopkg.in/urfave/cli.v1 v1.20.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect k8s.io/apiserver v0.26.0 // indirect - k8s.io/cli-runtime v0.25.3 // indirect + k8s.io/cli-runtime v0.26.0 // indirect k8s.io/component-base v0.26.0 // indirect - k8s.io/kube-openapi v0.0.0-20221110221610-a28e98eb7c70 // indirect - k8s.io/kubectl v0.25.3 // indirect + k8s.io/kube-openapi v0.0.0-20221207184640-f3cff1453715 // indirect + k8s.io/kubectl v0.26.0 // indirect k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 // indirect oras.land/oras-go v1.2.1 // indirect - sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index c865d24b6..54d211f55 100644 --- a/go.sum +++ b/go.sum @@ -20,19 +20,21 @@ cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPT cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.105.0 h1:DNtEKRBAAzeS4KyIory52wWHuClNaXJ5x1F7xa4q+5Y= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute v1.12.1 h1:gKVJMEyqV5c/UnpzjjQbo3Rjvvqpr9B1DFSbJC4OXr0= -cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= -cloud.google.com/go/compute/metadata v0.2.1 h1:efOwf5ymceDhK6PKMnnrTHP4pppY5L22mle96M1yP48= -cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= +cloud.google.com/go/compute v1.14.0 h1:hfm2+FfxVmnRlh6LpB7cg1ZNU+5edAHmW679JePztk0= +cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= +cloud.google.com/go/compute/metadata v0.2.2 h1:aWKAjYaBaOSrpKl57+jnS/3fJRQnxL7TvR/u1VVbt6k= +cloud.google.com/go/compute/metadata v0.2.2/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -46,8 +48,8 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= filippo.io/age v1.0.0 h1:V6q14n0mqYU3qKFkZ6oOaF9oXneOviS3ubXsSVBRSzc= filippo.io/age v1.0.0/go.mod h1:PaX+Si/Sd5G8LgfCwldsSba3H1DDQZhIhFGkhbHaBq8= -github.com/Azure/azure-sdk-for-go v63.3.0+incompatible h1:INepVujzUrmArRZjDLHbtER+FkvCoEwyRCXGqOlmDII= -github.com/Azure/azure-sdk-for-go v63.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v67.1.0+incompatible h1:oziYcaopbnIKfM69DL05wXdypiqfrUKdxUKrKpynJTw= +github.com/Azure/azure-sdk-for-go v67.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= @@ -60,8 +62,9 @@ github.com/Azure/go-autorest/autorest/adal v0.9.21 h1:jjQnVFXPfekaqb8vIsv2G1lxsh github.com/Azure/go-autorest/autorest/adal v0.9.21/go.mod h1:zua7mBUaCc5YnSLKYgGJR/w5ePdMDA6H56upLsHzA9U= github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 h1:P6bYXFoao05z5uhOQzbC3Qd8JqF3jUoocoTeIxkp2cA= github.com/Azure/go-autorest/autorest/azure/auth v0.5.11/go.mod h1:84w/uV8E37feW2NCJ08uT9VBfjfUHpgLVnG2InYD6cg= -github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 h1:0W/yGmFdTIT77fvdlGZ0LMISoLHFJ7Tx4U0yeB+uFs4= github.com/Azure/go-autorest/autorest/azure/cli v0.4.5/go.mod h1:ADQAXrkgm7acgWVUNamOgh8YNrv4p27l3Wc55oVfpzg= +github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 h1:w77/uPk80ZET2F+AfQExZyEWtn+0Rk/uw17m9fv5Ajc= +github.com/Azure/go-autorest/autorest/azure/cli v0.4.6/go.mod h1:piCfgPho7BiIDdEQ1+g4VmKyD5y+p/XtSNqE6Hc4QD0= github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= @@ -75,8 +78,8 @@ github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+Z github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e h1:NeAW1fUYUEWhft7pkxDf6WoUvEZJ/uOKsvtpjLnn8MU= -github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= @@ -92,19 +95,16 @@ github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0 github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Masterminds/sprig/v3 v3.2.0/go.mod h1:tWhwTbUTndesPNeF0C900vKoq283u6zp4APT9vaF3SI= -github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= -github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= +github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= +github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= github.com/Masterminds/squirrel v1.5.3 h1:YPpoceAcxuzIljlr5iWpNKaql7hLeG1KLSrhvdHpkZc= github.com/Masterminds/squirrel v1.5.3/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= -github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= -github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= -github.com/Microsoft/hcsshim v0.9.4 h1:mnUj0ivWy6UzbB1uLFqKR6F+ZyiDc7j4iGgHTpO+5+I= +github.com/Microsoft/hcsshim v0.9.6 h1:VwnDOgLeoi2du6dAznfmspNqTiwczvjv4K7NxuY9jsY= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 h1:ra2OtmuW0AE5csawV4YXMNGNQQXvLRps3z2Z59OPO+I= github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= @@ -116,8 +116,6 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= @@ -132,15 +130,15 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/aws/aws-sdk-go v1.44.160 h1:F41sWUel1CJ69ezoBGCg8sDyu9kyeKEpwmDrLXbCuyA= -github.com/aws/aws-sdk-go v1.44.160/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.161 h1:uZdZJ30mlbaU2wsrd/wzibrX01cbgKE2t486TtRjeHs= +github.com/aws/aws-sdk-go v1.44.161/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bitnami-labs/sealed-secrets v0.19.2 h1:0NpJCAhfDTkZ2u3PJaRWSynxlg55ssHxPQNn8T5JNPU= -github.com/bitnami-labs/sealed-secrets v0.19.2/go.mod h1:KjTgHdD22gpjERjT1rA8yi4P4EkccMhRR2JW6Ctfu6g= +github.com/bitnami-labs/sealed-secrets v0.19.3 h1:D9v0fQt8JNDEoBU6HRKKJ20VhqvGUyaDrK1bl714i4I= +github.com/bitnami-labs/sealed-secrets v0.19.3/go.mod h1:G7Psbu6s3ed7GCO6ZjZ51zff0GGGSymLxFXb4QkJnRw= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= @@ -150,15 +148,14 @@ github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd h1:rFt+Y/IK1aEZ github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembjv71DPz3uX/V/6MMlSyD9JBQ6kQ= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= -github.com/bwesterb/go-ristretto v1.2.1/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= -github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -168,16 +165,16 @@ github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6D github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= -github.com/cloudflare/circl v1.2.0 h1:NheeISPSUcYftKlfrLuOo4T62FkmD4t4jviLfFFYaec= -github.com/cloudflare/circl v1.2.0/go.mod h1:Ch2UgYr6ti2KTtlejELlROl0YIYj7SLjAC8M+INXlMk= +github.com/cloudflare/circl v1.3.0 h1:Anq00jxDtoyX3+aCaYUZ0vXC5r4k4epberfWGDXV1zE= +github.com/cloudflare/circl v1.3.0/go.mod h1:+CauBF6R70Jqcyl8N2hC8pAXYbWkGIezuSbuGLtRhnw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/containerd/cgroups v1.0.3 h1:ADZftAkglvCiD44c77s5YmMqaP2pzVCFZvBmAlBdAP4= -github.com/containerd/containerd v1.6.9 h1:IN/r8DUes/B5lEGTNfIiUkfZBtIQJGx2ai703dV6lRA= -github.com/containerd/containerd v1.6.9/go.mod h1:XVicUvkxOrftE2Q1YWUXgZwkkAxwQYNOFzYWvfVfEfQ= +github.com/containerd/containerd v1.6.13 h1:7llWEzjLH/fao0f2lppn1L6NhjsvxqMdUQa2mgVCs+U= +github.com/containerd/containerd v1.6.13/go.mod h1:vDm+BbU+dD9uvuUlHr+KmcY0HKX8WDyI6dzJjNi4ee8= github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= github.com/containerd/stargz-snapshotter/estargz v0.12.1 h1:+7nYmHJb0tEkcRaAW+MHqoKaJYZmkikupxCqVtmPuY0= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -185,8 +182,7 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= -github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI= github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -196,12 +192,12 @@ github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27N github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= github.com/distribution/distribution/v3 v3.0.0-20220526142353-ffbd94cbe269 h1:hbCT8ZPPMqefiAWD2ZKjn7ypokIGViTvBBg/ExLSdCk= -github.com/docker/cli v20.10.20+incompatible h1:lWQbHSHUFs7KraSN2jOJK7zbMS2jNCHI4mt4xUFUVQ4= -github.com/docker/cli v20.10.20+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v20.10.21+incompatible h1:qVkgyYUnOLQ98LtXBrwd/duVqPT2X4SHndOuGsfwyhU= +github.com/docker/cli v20.10.21+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v20.10.20+incompatible h1:kH9tx6XO+359d+iAkumyKDc5Q1kOwPuAUaeri48nD6E= -github.com/docker/docker v20.10.20+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v20.10.21+incompatible h1:UTLdBmHk3bEY+w8qeO5KttOhy6OmXWsl/FEet9Uswog= +github.com/docker/docker v20.10.21+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= @@ -216,9 +212,8 @@ github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3 github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g= github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= -github.com/emicklei/go-restful/v3 v3.10.0 h1:X4gma4HM7hFm6WMeAsTfqA0GOfdNoCzBIkHGoRLGXuM= -github.com/emicklei/go-restful/v3 v3.10.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= +github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ= +github.com/emicklei/go-restful/v3 v3.10.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -247,29 +242,25 @@ github.com/fluxcd/go-git/v5 v5.0.0-20221206140629-ec778c2c37df h1:2BHXJp1PwX7D47 github.com/fluxcd/go-git/v5 v5.0.0-20221206140629-ec778c2c37df/go.mod h1:raWgfUV7lDQVXp4QXUaeNNJkRVKz97UQuF+0kdY7Vmo= github.com/fluxcd/pkg/apis/kustomize v0.7.0 h1:X2htBmJ91nGYv4d93gin665MFWKNGiNwUiZ08/Zz0hY= github.com/fluxcd/pkg/apis/kustomize v0.7.0/go.mod h1:Mu+KdktsEKWA4l/33CZdY5lB4hz51mqfcLzBZSwAqVg= -github.com/fluxcd/pkg/kustomize v0.11.0 h1:zseS9LRUuzhP/7KamccmsOgYpJAdhqtsf+2wN/CHF3I= -github.com/fluxcd/pkg/kustomize v0.11.0/go.mod h1:awHID4OKe2/WAfTFg4u0fURXZPUkrIslSZNSPX9MEFQ= -github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/fluxcd/pkg/kustomize v0.12.0 h1:4MQdbP3M8NTIcr8TgNMxRCN+2xZoMWtCDDS3RiOT+8M= +github.com/fluxcd/pkg/kustomize v0.12.0/go.mod h1:awHID4OKe2/WAfTFg4u0fURXZPUkrIslSZNSPX9MEFQ= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= -github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34= github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= -github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0= github.com/go-git/go-git-fixtures/v4 v4.3.1 h1:y5z6dd3qi8Hl+stezc8p3JxDkoTRqMAlKnXHuzrfjTQ= github.com/go-git/go-git-fixtures/v4 v4.3.1/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo= -github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4= -github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc= +github.com/go-git/go-git/v5 v5.5.1 h1:5vtv2TB5PM/gPM+EvsHJ16hJh4uAkdGcKilcwY7FYwo= +github.com/go-git/go-git/v5 v5.5.1/go.mod h1:uz5PQ3d0gz7mSgzZhSJToM6ALPaKCdSnl58/Xb5hzr8= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -278,12 +269,9 @@ github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -409,12 +397,12 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.2.0 h1:y8Yozv7SZtlU//QXbezB6QkpuE6jMD2/gfzk4AftXjs= -github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= +github.com/googleapis/enterprise-certificate-proxy v0.2.1 h1:RY7tHKZcRlk788d5WSo/e83gOyyy742E8GSs771ySpg= +github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.6.0 h1:SXk3ABtQYDT/OH8jAyvEOQ58mgawq5C4o/4/89qN2ZU= -github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= +github.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ= +github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= @@ -438,8 +426,8 @@ github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-hclog v1.3.1 h1:vDwF1DFNZhntP4DAjuTpOw3uEgMUpXh1pB5fW9DqHpo= -github.com/hashicorp/go-hclog v1.3.1/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-hclog v1.4.0 h1:ctuWFGrhFha8BnnzxqeRGidlEcQkDyL5u8J8t5eA11I= +github.com/hashicorp/go-hclog v1.4.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= @@ -447,8 +435,8 @@ github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iP github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-plugin v1.4.5 h1:oTE/oQR4eghggRg8VY7PAz3dr++VwDNBGCcOfIvHpBo= -github.com/hashicorp/go-plugin v1.4.5/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= +github.com/hashicorp/go-plugin v1.4.8 h1:CHGwpxYDOttQOY7HOWgETU9dyVjOXzniXDqJcYJE1zM= +github.com/hashicorp/go-plugin v1.4.8/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ= github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= @@ -485,8 +473,8 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/vault/api v1.8.2 h1:C7OL9YtOtwQbTKI9ogB0A1wffRbCN+rH/LLCHO3d8HM= github.com/hashicorp/vault/api v1.8.2/go.mod h1:ML8aYzBIhY5m1MD1B2Q0JV89cC85YVH4t5kBaZiyVaE= -github.com/hashicorp/vault/sdk v0.6.0 h1:6Z+In5DXHiUfZvIZdMx7e2loL1PPyDjA4bVh9ZTIAhs= -github.com/hashicorp/vault/sdk v0.6.0/go.mod h1:+DRpzoXIdMvKc88R4qxr+edwy/RvH5QK8itmxLiDHLc= +github.com/hashicorp/vault/sdk v0.6.1 h1:sjZC1z4j5Rh2GXYbkxn5BLK05S1p7+MhW4AgdUmgRUA= +github.com/hashicorp/vault/sdk v0.6.1/go.mod h1:Ck4JuAC6usTphfrrRJCRH+7/N7O2ozZzkm/fzQFt4uM= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= @@ -495,17 +483,18 @@ github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef h1:A9HsByNhogrvm9cWb github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -521,11 +510,9 @@ github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= @@ -533,22 +520,19 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw= github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= -github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c= -github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= -github.com/kluctl/go-embed-python v0.0.0-3.10.7-20221017-1 h1:4XlPysiwaex+j7+r6pPA12WyuBM9Sy6gSoeKYTqxU9M= -github.com/kluctl/go-embed-python v0.0.0-3.10.7-20221017-1/go.mod h1:fuMI+yFFgrViXsRl+NIWQeuPDTvJtOk2pe0HoeujT54= -github.com/kluctl/go-jinja2 v0.0.0-20221017153334-80addb432305 h1:95WNlnGuRfLXgWoeBTDV0DhHwX26hUfnIzgiYTitsLk= -github.com/kluctl/go-jinja2 v0.0.0-20221017153334-80addb432305/go.mod h1:2G09vDjMA8VSWJT7Kox9EQi8l7DCkGnyBNcd7eomM1s= +github.com/klauspost/compress v1.15.13 h1:NFn1Wr8cfnenSJSA46lLq4wHCcBzKTSjnBIexDMMOV0= +github.com/klauspost/compress v1.15.13/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= +github.com/kluctl/go-embed-python v0.0.0-3.10.8-20221106-1 h1:PXkdM8Z/Lh1+GIjXVG3dFs5enQoQ1eC7JXMz/Mo4R04= +github.com/kluctl/go-embed-python v0.0.0-3.10.8-20221106-1/go.mod h1:qVVSnYkq5NSgUuLzz4hTCXvVsRQyF/ic4U5v7DaiCGM= +github.com/kluctl/go-jinja2 v0.0.0-20221215083015-c3f906953ba1 h1:LwFIJE5cb6AAThM8FPbg589Y7zaToSrBCEQ1bxs8eiY= +github.com/kluctl/go-jinja2 v0.0.0-20221215083015-c3f906953ba1/go.mod h1:hkHz+qOoK67xFAM8M6OkvvXpEsYq8ZKQxkYkat6J058= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kortschak/utter v1.0.1/go.mod h1:vSmSjbyrlKjjsL71193LmzBOKgwePk9DH6uFaWHIInc= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -574,8 +558,8 @@ github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= -github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= @@ -607,8 +591,8 @@ github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions v1.0.2 h1:hAHbPm5IJGijwng3PWk09JkG9WeqChjprR5s9bBZ+OM= -github.com/matttproud/golang_protobuf_extensions v1.0.2/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.4/go.mod h1:vTLESy5mRhKOs9KDp0/RATawxP1UqBmdrpVRMnpcvKQ= @@ -639,8 +623,8 @@ github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQ github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/moby/sys/mountinfo v0.5.0 h1:2Ks8/r6lopsxWi9m58nlwjaeSzUX9iiL1vj5qB/9ObI= -github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae h1:O4SWKdcHVCvYqyDV+9CJA1fcDN2L11Bule0iFy3YlAI= -github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= +github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA= +github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -655,7 +639,6 @@ github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7P github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/ohler55/ojg v1.14.5 h1:xCX2oyh/ZaoesbLH6fwVHStSJpk4o4eJs8ttXutzdg0= github.com/ohler55/ojg v1.14.5/go.mod h1:7Ghirupn8NC8hSSDpI0gcjorPxj+vSVIONDWfliHR1k= @@ -683,8 +666,8 @@ github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144T github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= -github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= +github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= +github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= @@ -708,9 +691,6 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= @@ -722,25 +702,19 @@ github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3d github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= -github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= +github.com/prometheus/common v0.39.0 h1:oOyhkDq05hPZKItWVBkJ6g6AtGxi+fy7F4JvUV8uhsI= +github.com/prometheus/common v0.39.0/go.mod h1:6XBZ7lYdLCbkAVhwRsWTZn+IN5AB9F/NXd5w0BbEX0Y= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/r3labs/diff/v2 v2.15.1 h1:EOrVqPUzi+njlumoqJwiS/TgGgmZo83619FNDB9xQUg= github.com/r3labs/diff/v2 v2.15.1/go.mod h1:I8noH9Fc2fjSaMxqF3G2lhDdC0b+JXCfyx85tWFM9kc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8= -github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw= +github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= @@ -749,9 +723,8 @@ github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZV github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rubenv/sql-migrate v1.2.0 h1:fOXMPLMd41sK7Tg75SXDec15k3zg5WNV6SjuDRiNfcU= github.com/rubenv/sql-migrate v1.2.0/go.mod h1:Z5uVnq7vrIrPmHbVFfR4YLHRZquxeHpckCnRq0P/K9Y= -github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= -github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= @@ -766,9 +739,7 @@ github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5g github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= @@ -779,8 +750,8 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= -github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw= -github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= +github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= @@ -789,7 +760,6 @@ github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= @@ -821,7 +791,6 @@ github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaU github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/whilp/git-urls v1.0.0 h1:95f6UMWN5FKW71ECsXRUd3FVYiXdrE7aX4NZKcPmIjU= github.com/whilp/git-urls v1.0.0/go.mod h1:J16SAmobsqc3Qcy98brfl5f5+e0clUvg1krgwk/qCfE= -github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= github.com/xanzy/ssh-agent v0.3.2/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= @@ -846,8 +815,8 @@ github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMzt github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= -go.etcd.io/etcd/api/v3 v3.5.5 h1:BX4JIbQ7hl7+jL+g+2j5UAr0o1bctCm6/Ct+ArBGkf0= -go.etcd.io/etcd/api/v3 v3.5.5/go.mod h1:KFtNaxGDw4Yx/BA4iPPwevUTAuqcsPxzyX8PHydchN8= +go.etcd.io/etcd/api/v3 v3.5.6 h1:Cy2qx3npLcYqTKqGJzMypnMv2tiRyifZJ17BlWIWA7A= +go.etcd.io/etcd/api/v3 v3.5.6/go.mod h1:KFtNaxGDw4Yx/BA4iPPwevUTAuqcsPxzyX8PHydchN8= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.mozilla.org/gopgagent v0.0.0-20170926210634-4d7ea76ff71a h1:N7VD+PwpJME2ZfQT8+ejxwA4Ow10IkGbU0MGf94ll8k= @@ -860,11 +829,12 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.starlark.net v0.0.0-20221028183056-acb66ad56dd2 h1:5/KzhcSqd4UgY51l17r7C5g/JiE6DRw1Vq7VJfQHuMc= -go.starlark.net v0.0.0-20221028183056-acb66ad56dd2/go.mod h1:kIVgS18CjmEC3PqMd5kaJSGEifyV/CeB9x506ZJ1Vbk= +go.starlark.net v0.0.0-20221205180719-3fd0dac74452 h1:JZtNuL6LPB+scU5yaQ6hqRlJFRiddZm2FwRt2AQqtHA= +go.starlark.net v0.0.0-20221205180719-3fd0dac74452/go.mod h1:kIVgS18CjmEC3PqMd5kaJSGEifyV/CeB9x506ZJ1Vbk= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= @@ -874,7 +844,6 @@ go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -884,17 +853,16 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8= golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -933,8 +901,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I= -golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= +golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -973,16 +941,13 @@ golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= -golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU= golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -997,10 +962,8 @@ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.2.0 h1:GtQkldQ9m7yvzCL1V+LrYow3Khe0eJH0w7RbX/VbaIU= -golang.org/x/oauth2 v0.2.0/go.mod h1:Cwn6afJ8jrQwYMxQDTpISoXmXW9I6qF6vDeuuoX3Ibs= +golang.org/x/oauth2 v0.3.0 h1:6l90koy8/LaBLmLu8jpHeHexzMwEita0zFfYlggy2F8= +golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1030,13 +993,11 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1050,8 +1011,6 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1065,14 +1024,11 @@ golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1080,9 +1036,6 @@ golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1092,6 +1045,7 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1099,6 +1053,7 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1130,7 +1085,6 @@ golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1173,13 +1127,12 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE= -golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= +golang.org/x/tools v0.4.0 h1:7mTAgkunk3fr4GAloyyCasadO6h9zSsQZbwvcaIciV4= +golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY= gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= @@ -1204,8 +1157,8 @@ google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjR google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= -google.golang.org/api v0.102.0 h1:JxJl2qQ85fRMPNvlZY/enexbxpCjLwGhZUtgfGeQ51I= -google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo= +google.golang.org/api v0.105.0 h1:t6P9Jj+6XTn4U9I2wycQai6Q/Kz7iOT+QzjJ3G2V4x8= +google.golang.org/api v0.105.0/go.mod h1:qh7eD5FJks5+BcE+cjBIm6Gz8vioK7EHvnlniqXBnqI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1258,8 +1211,8 @@ google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71 h1:GEgb2jF5zxsFJpJfg9RoDDWm7tiwc/DDSTE2BtLUkXU= -google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= +google.golang.org/genproto v0.0.0-20221207170731-23e4bf6bdc37 h1:jmIfw8+gSvXcZSgaFAGyInDXeWzUhvYH57G/5GKMn70= +google.golang.org/genproto v0.0.0-20221207170731-23e4bf6bdc37/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1282,8 +1235,8 @@ google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= -google.golang.org/grpc v1.50.1 h1:DS/BukOZWp8s6p4Dt/tOaJaTQyPyOoCcrjroHuCeLzY= -google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.51.0 h1:E1eGv1FTqoLIdnBCZufiSHgKjlqG6fKFf6pPWtMTh8U= +google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1334,7 +1287,6 @@ gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= -gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= helm.sh/helm/v3 v3.10.3 h1:wL7IUZ7Zyukm5Kz0OUmIFZgKHuAgByCrUcJBtY0kDyw= helm.sh/helm/v3 v3.10.3/go.mod h1:CXOcs02AYvrlPMWARNYNRgf2rNP7gLJQsi/Ubd4EDrI= @@ -1353,18 +1305,18 @@ k8s.io/apimachinery v0.26.0 h1:1feANjElT7MvPqp0JT6F3Ss6TWDwmcjLypwoPpEf7zg= k8s.io/apimachinery v0.26.0/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74= k8s.io/apiserver v0.26.0 h1:q+LqIK5EZwdznGZb8bq0+a+vCqdeEEe4Ux3zsOjbc4o= k8s.io/apiserver v0.26.0/go.mod h1:aWhlLD+mU+xRo+zhkvP/gFNbShI4wBDHS33o0+JGI84= -k8s.io/cli-runtime v0.25.3 h1:Zs7P7l7db/5J+KDePOVtDlArAa9pZXaDinGWGZl0aM8= -k8s.io/cli-runtime v0.25.3/go.mod h1:InHHsjkyW5hQsILJGpGjeruiDZT/R0OkROQgD6GzxO4= +k8s.io/cli-runtime v0.26.0 h1:aQHa1SyUhpqxAw1fY21x2z2OS5RLtMJOCj7tN4oq8mw= +k8s.io/cli-runtime v0.26.0/go.mod h1:o+4KmwHzO/UK0wepE1qpRk6l3o60/txUZ1fEXWGIKTY= k8s.io/client-go v0.26.0 h1:lT1D3OfO+wIi9UFolCrifbjUUgu7CpLca0AD8ghRLI8= k8s.io/client-go v0.26.0/go.mod h1:I2Sh57A79EQsDmn7F7ASpmru1cceh3ocVT9KlX2jEZg= k8s.io/component-base v0.26.0 h1:0IkChOCohtDHttmKuz+EP3j3+qKmV55rM9gIFTXA7Vs= k8s.io/component-base v0.26.0/go.mod h1:lqHwlfV1/haa14F/Z5Zizk5QmzaVf23nQzCwVOQpfC8= k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20221110221610-a28e98eb7c70 h1:zfqQc1V6/ZgGpvrOVvr62OjiqQX4lZjfznK34NQwkqw= -k8s.io/kube-openapi v0.0.0-20221110221610-a28e98eb7c70/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= -k8s.io/kubectl v0.25.3 h1:HnWJziEtmsm4JaJiKT33kG0kadx68MXxUE8UEbXnN4U= -k8s.io/kubectl v0.25.3/go.mod h1:glU7PiVj/R6Ud4A9FJdTcJjyzOtCJyc0eO7Mrbh3jlI= +k8s.io/kube-openapi v0.0.0-20221207184640-f3cff1453715 h1:tBEbstoM+K0FiBV5KGAKQ0kuvf54v/hwpldiJt69w1s= +k8s.io/kube-openapi v0.0.0-20221207184640-f3cff1453715/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= +k8s.io/kubectl v0.26.0 h1:xmrzoKR9CyNdzxBmXV7jW9Ln8WMrwRK6hGbbf69o4T0= +k8s.io/kubectl v0.26.0/go.mod h1:eInP0b+U9XUJWSYeU9XZnTA+cVYuWyl3iYPGtru0qhQ= k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 h1:KTgPnR10d5zhztWptI952TNtt/4u5h3IzDXkdIMuo2Y= k8s.io/utils v0.0.0-20221128185143-99ec85e7a448/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= oras.land/oras-go v1.2.1 h1:/VcGS8FUy3eEXLl/1vC4QypLHwrfSmgW7ygsoklqKK8= @@ -1374,8 +1326,8 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/controller-runtime v0.14.0 h1:ju2xsov5Ara6FoQuddg+az+rAxsUsTYn2IYyEKCTyDc= sigs.k8s.io/controller-runtime v0.14.0/go.mod h1:GaRkrY8a7UZF0kqFFbUKG7n9ICiTY5T55P1RiE3UZlU= -sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= -sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/kustomize/api v0.12.1 h1:7YM7gW3kYBwtKvoY216ZzY+8hM+lV53LUayghNRJ0vM= sigs.k8s.io/kustomize/api v0.12.1/go.mod h1:y3JUhimkZkR6sbLNwfJHxvo1TCLwuwm14sCYnkH6S1s= sigs.k8s.io/kustomize/kyaml v0.13.9 h1:Qz53EAaFFANyNgyOEJbT/yoIHygK40/ZcvU3rgry2Tk= From 347a2b46673a02b895cc272372458f67600ed054 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 16 Dec 2022 15:18:32 +0100 Subject: [PATCH 1333/2916] fix: Use correct .helm-charts dir when using git includes --- cmd/kluctl/commands/cmd_helm_update.go | 2 +- pkg/deployment/deployment_item.go | 19 +++++++++++++++---- pkg/deployment/shared_context.go | 1 - pkg/kluctl_project/project.go | 1 - pkg/kluctl_project/project_load.go | 1 - pkg/kluctl_project/target_context.go | 1 - 6 files changed, 16 insertions(+), 9 deletions(-) diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go index 059c70233..fd4856c1b 100644 --- a/cmd/kluctl/commands/cmd_helm_update.go +++ b/cmd/kluctl/commands/cmd_helm_update.go @@ -40,7 +40,7 @@ func (cmd *helmUpdateCmd) Run(ctx context.Context) error { } if !yaml.Exists(filepath.Join(projectDir, ".kluctl.yaml")) { - return fmt.Errorf("helm-pull can only be used on the root of a Kluctl project that must have a .kluctl.yaml file") + return fmt.Errorf("helm-update can only be used on the root of a Kluctl project that must have a .kluctl.yaml file") } gitRootPath, err := git2.DetectGitRepositoryRoot(projectDir) diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index bf5469beb..a9ac22883 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -173,6 +173,17 @@ func (di *DeploymentItem) isHelmValuesYaml(p string) bool { return file == "helm-values.yml" || file == "helm-values.yaml" } +func (di *DeploymentItem) newHelmRelease(subDir string) (*helm.Release, error) { + configPath := yaml.FixPathExt(filepath.Join(di.RenderedDir, subDir, "helm-chart.yaml")) + helmChartsDir := filepath.Join(di.Project.source.dir, ".helm-charts") + + hr, err := helm.NewRelease(di.Project.source.dir, filepath.Join(di.RelToSourceItemDir, subDir), configPath, helmChartsDir, di.ctx.HelmCredentials) + if err != nil { + return nil, err + } + return hr, nil +} + func (di *DeploymentItem) renderHelmCharts() error { if di.dir == nil { return nil @@ -188,7 +199,7 @@ func (di *DeploymentItem) renderHelmCharts() error { return err } - hr, err := helm.NewRelease(di.Project.source.dir, filepath.Join(di.RelToSourceItemDir, subDir), p, di.ctx.HelmChartsDir, di.ctx.HelmCredentials) + hr, err := di.newHelmRelease(subDir) if err != nil { return err } @@ -411,12 +422,12 @@ func (di *DeploymentItem) generateKustomizationYaml(subDir string) (*uo.Unstruct if di.isHelmValuesYaml(de.Name()) { continue } else if di.isHelmChartYaml(de.Name()) { - c, err := helm.NewRelease(di.Project.source.dir, filepath.Join(di.RelToSourceItemDir, subDir), filepath.Join(di.RenderedDir, subDir, de.Name()), di.ctx.HelmChartsDir, nil) + hr, err := di.newHelmRelease(subDir) if err != nil { return nil, err } - if !utils.IsFile(filepath.Join(di.RenderedDir, subDir, c.GetOutputPath())) { - resourcePath = c.GetOutputPath() + if !utils.IsFile(filepath.Join(di.RenderedDir, subDir, hr.GetOutputPath())) { + resourcePath = hr.GetOutputPath() } } else if strings.HasSuffix(lname, ".yml") || strings.HasSuffix(lname, ".yaml") { resourcePath = de.Name() diff --git a/pkg/deployment/shared_context.go b/pkg/deployment/shared_context.go index 49133fecb..dec1c4571 100644 --- a/pkg/deployment/shared_context.go +++ b/pkg/deployment/shared_context.go @@ -21,5 +21,4 @@ type SharedContext struct { RenderDir string SealedSecretsDir string DefaultSealedSecretsOutputPattern string - HelmChartsDir string } diff --git a/pkg/kluctl_project/project.go b/pkg/kluctl_project/project.go index d30936121..0df49e01c 100644 --- a/pkg/kluctl_project/project.go +++ b/pkg/kluctl_project/project.go @@ -20,7 +20,6 @@ type LoadedKluctlProject struct { ProjectDir string sealedSecretsDir string - helmChartsDir string Config types2.KluctlProject DynamicTargets []*types2.DynamicTarget diff --git a/pkg/kluctl_project/project_load.go b/pkg/kluctl_project/project_load.go index b809bdd68..4c075fd23 100644 --- a/pkg/kluctl_project/project_load.go +++ b/pkg/kluctl_project/project_load.go @@ -61,7 +61,6 @@ func (c *LoadedKluctlProject) loadKluctlProject() error { } c.sealedSecretsDir = filepath.Join(c.ProjectDir, ".sealed-secrets") - c.helmChartsDir = filepath.Join(c.ProjectDir, ".helm-charts") sealedSecretsUsed := false if c.Config.SecretsConfig != nil { diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index 9ee3c53ec..f07436014 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -113,7 +113,6 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, params Targe RenderDir: params.RenderOutputDir, SealedSecretsDir: p.sealedSecretsDir, DefaultSealedSecretsOutputPattern: target.Name, - HelmChartsDir: p.helmChartsDir, } d, err := deployment.NewDeploymentProject(dctx, varsCtx, deployment.NewSource(deploymentDir), ".", nil) From 347059d4bb06c47f773764ab4f8d584c074d6494 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Dec 2022 22:06:53 +0000 Subject: [PATCH 1334/2916] chore(deps): Bump goreleaser/goreleaser-action from 3 to 4 Bumps [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action) from 3 to 4. - [Release notes](https://github.com/goreleaser/goreleaser-action/releases) - [Commits](https://github.com/goreleaser/goreleaser-action/compare/v3...v4) --- updated-dependencies: - dependency-name: goreleaser/goreleaser-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dd4ea8163..4e2a47b9a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -46,7 +46,7 @@ jobs: restore-keys: | ${{ runner.os }}-goreleaser- - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v3 + uses: goreleaser/goreleaser-action@v4 with: distribution: goreleaser version: latest From 7f1b3c747100468040fa19ac29b8cf28210133f9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Dec 2022 22:10:08 +0000 Subject: [PATCH 1335/2916] chore(deps): Bump github.com/ohler55/ojg from 1.14.5 to 1.15.0 Bumps [github.com/ohler55/ojg](https://github.com/ohler55/ojg) from 1.14.5 to 1.15.0. - [Release notes](https://github.com/ohler55/ojg/releases) - [Changelog](https://github.com/ohler55/ojg/blob/develop/CHANGELOG.md) - [Commits](https://github.com/ohler55/ojg/compare/v1.14.5...v1.15.0) --- updated-dependencies: - dependency-name: github.com/ohler55/ojg dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 4065cbaa8..c3fb32abb 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,7 @@ require ( github.com/mattn/go-isatty v0.0.16 github.com/mattn/go-runewidth v0.0.14 github.com/mitchellh/reflectwalk v1.0.2 - github.com/ohler55/ojg v1.14.5 + github.com/ohler55/ojg v1.15.0 github.com/pkg/errors v0.9.1 github.com/r3labs/diff/v2 v2.15.1 github.com/rogpeppe/go-internal v1.9.0 diff --git a/go.sum b/go.sum index 54d211f55..3e97f3368 100644 --- a/go.sum +++ b/go.sum @@ -640,8 +640,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/ohler55/ojg v1.14.5 h1:xCX2oyh/ZaoesbLH6fwVHStSJpk4o4eJs8ttXutzdg0= -github.com/ohler55/ojg v1.14.5/go.mod h1:7Ghirupn8NC8hSSDpI0gcjorPxj+vSVIONDWfliHR1k= +github.com/ohler55/ojg v1.15.0 h1:Z95FvBiMsMOOGP9Nzv5OVV4ND2KnEMxk0GOS8Kvcahg= +github.com/ohler55/ojg v1.15.0/go.mod h1:7Ghirupn8NC8hSSDpI0gcjorPxj+vSVIONDWfliHR1k= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= From 55633c0df94f465b15f3915c2a97362cb6745219 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Dec 2022 22:10:29 +0000 Subject: [PATCH 1336/2916] chore(deps): Bump github.com/aws/aws-sdk-go from 1.44.161 to 1.44.163 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.161 to 1.44.163. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.161...v1.44.163) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 4065cbaa8..3e4135281 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 github.com/Masterminds/semver/v3 v3.2.0 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d - github.com/aws/aws-sdk-go v1.44.161 + github.com/aws/aws-sdk-go v1.44.163 github.com/bitnami-labs/sealed-secrets v0.19.3 github.com/cyphar/filepath-securejoin v0.2.3 github.com/docker/distribution v2.8.1+incompatible diff --git a/go.sum b/go.sum index 54d211f55..e136f88e8 100644 --- a/go.sum +++ b/go.sum @@ -130,8 +130,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/aws/aws-sdk-go v1.44.161 h1:uZdZJ30mlbaU2wsrd/wzibrX01cbgKE2t486TtRjeHs= -github.com/aws/aws-sdk-go v1.44.161/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.163 h1:XO1A/Laqf/l0IxVPghaQzdnVwxofVFv00IlX0BpmbhQ= +github.com/aws/aws-sdk-go v1.44.163/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= From c248ff003165b81151686122a2fec3f28468edf8 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 20 Dec 2022 10:27:43 +0100 Subject: [PATCH 1337/2916] fix: Don't try to treat final error string as formatted string --- cmd/kluctl/commands/root.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index 8b7231230..f7520e0b5 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -292,7 +292,7 @@ composed of multiple smaller parts (Helm/Kustomize/...) in a manageable and unif err = rootCmd.Execute() if err != nil { - status.Error(ctx, err.Error()) + status.Error(ctx, "%s", err.Error()) return err } return nil From 14a3d829d3e522c7f0c4284091ddfe386613d78b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 20 Dec 2022 11:28:31 +0100 Subject: [PATCH 1338/2916] docs: Fix typo --- docs/reference/deployments/helm.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/deployments/helm.md b/docs/reference/deployments/helm.md index bc31615f6..665f3ef51 100644 --- a/docs/reference/deployments/helm.md +++ b/docs/reference/deployments/helm.md @@ -116,7 +116,7 @@ Specifies version constraints to be used when running [helm-update](../commands/ [Checking Version Constraints](https://github.com/Masterminds/semver#checking-version-constraints) for details on the supported syntax. -If omitted, Kluctl will filter out pre-releases by default. Use a `updateConstraings` like `~1.2.3-0` to enable +If omitted, Kluctl will filter out pre-releases by default. Use a `updateConstraints` like `~1.2.3-0` to enable pre-releases. ### skipUpdate From 3bec1e366ebb4c398a2a5de2c141131d3401bad7 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 20 Dec 2022 15:11:34 +0100 Subject: [PATCH 1339/2916] fix: Fix globals flags being ignored when specified via env variables --- cmd/kluctl/commands/root.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index f7520e0b5..fe314bba0 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -223,7 +223,7 @@ func Main() { initViper(ctx) - err := Execute(ctx, os.Args[1:], func(ctxIn context.Context, cmd *cobra.Command, flags GlobalFlags) (context.Context, error) { + err := Execute(ctx, os.Args[1:], func(ctxIn context.Context, cmd *cobra.Command, flags *GlobalFlags) (context.Context, error) { err := copyViperValuesToCobraCmd(cmd) if err != nil { return ctx, err @@ -257,7 +257,7 @@ func Main() { } } -func Execute(ctx context.Context, args []string, preRun func(ctx context.Context, rootCmd *cobra.Command, flags GlobalFlags) (context.Context, error)) error { +func Execute(ctx context.Context, args []string, preRun func(ctx context.Context, rootCmd *cobra.Command, flags *GlobalFlags) (context.Context, error)) error { root := cli{} rootCmd, err := buildRootCobraCmd(&root, "kluctl", "Deploy and manage complex deployments on Kubernetes", @@ -277,7 +277,7 @@ composed of multiple smaller parts (Helm/Kustomize/...) in a manageable and unif rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error { if preRun != nil { - ctx, err = preRun(ctx, cmd, root.GlobalFlags) + ctx, err = preRun(ctx, cmd, &root.GlobalFlags) if ctx != nil { for c := cmd; c != nil; c = c.Parent() { c.SetContext(ctx) From 07407b8ab90693be05ddbb66cd3d76389ff60c63 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 20 Dec 2022 16:46:13 +0100 Subject: [PATCH 1340/2916] fix: Also obfuscate secrets when data/stringData was empty before --- e2e/obfuscate_test.go | 38 ++++++++++++++++++++++++++++++++++++-- pkg/diff/obfuscate.go | 20 ++++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/e2e/obfuscate_test.go b/e2e/obfuscate_test.go index 8f0d018e5..b2fbfc849 100644 --- a/e2e/obfuscate_test.go +++ b/e2e/obfuscate_test.go @@ -25,16 +25,25 @@ func TestObfuscateSecrets(t *testing.T) { name: "secret", namespace: p.TestSlug(), }, false) + addSecretDeployment(p, "secret2", nil, resourceOpts{ + name: "secret2", + namespace: p.TestSlug(), + }, false) + addSecretDeployment(p, "secret3", nil, resourceOpts{ + name: "secret3", + namespace: p.TestSlug(), + }, false) + stdout, _ := p.KluctlMust("deploy", "--yes", "-t", "test") assertSecretExists(t, k, p.TestSlug(), "secret") - assert.NotContains(t, stdout, "secret_value") + assert.NotContains(t, stdout, base64.StdEncoding.EncodeToString([]byte("secret_value"))) p.UpdateYaml("secret/secret-secret.yml", func(o *uo.UnstructuredObject) error { _ = o.SetNestedField("secret_value_2", "stringData", "secret") return nil }, "") stdout, _ = p.KluctlMust("deploy", "--yes", "-t", "test") - assert.NotContains(t, stdout, "secret_value") + assert.NotContains(t, stdout, base64.StdEncoding.EncodeToString([]byte("secret_value"))) assert.Contains(t, stdout, "***** (obfuscated)") p.UpdateYaml("secret/secret-secret.yml", func(o *uo.UnstructuredObject) error { @@ -45,4 +54,29 @@ func TestObfuscateSecrets(t *testing.T) { assert.Contains(t, stdout, "-"+base64.StdEncoding.EncodeToString([]byte("secret_value_2"))) assert.Contains(t, stdout, "+"+base64.StdEncoding.EncodeToString([]byte("secret_value_3"))) assert.NotContains(t, stdout, "***** (obfuscated)") + + // also test changing from empty data to filled data + p.UpdateYaml("secret2/secret-secret2.yml", func(o *uo.UnstructuredObject) error { + _ = o.SetNestedField(map[string]any{ + "secret2": "secret_value_2", + }, "stringData") + return nil + }, "") + + stdout, _ = p.KluctlMust("deploy", "--yes", "-t", "test") + assert.NotContains(t, stdout, base64.StdEncoding.EncodeToString([]byte("secret_value_2"))) + assert.Contains(t, stdout, "+secret2: '***** (obfuscated)'") + + p.UpdateYaml("secret3/secret-secret3.yml", func(o *uo.UnstructuredObject) error { + _ = o.SetNestedField(map[string]any{ + "secret3": "secret_value_3", + "secret4": "secret_value_4", + }, "stringData") + return nil + }, "") + stdout, _ = p.KluctlMust("deploy", "--yes", "-t", "test") + assert.NotContains(t, stdout, base64.StdEncoding.EncodeToString([]byte("secret_value_3"))) + assert.NotContains(t, stdout, base64.StdEncoding.EncodeToString([]byte("secret_value_4"))) + assert.Contains(t, stdout, "+secret3: '***** (obfuscated)'") + assert.Contains(t, stdout, "+secret4: '***** (obfuscated)'") } diff --git a/pkg/diff/obfuscate.go b/pkg/diff/obfuscate.go index 710ca60c0..44f5a6109 100644 --- a/pkg/diff/obfuscate.go +++ b/pkg/diff/obfuscate.go @@ -19,6 +19,18 @@ func (o *Obfuscator) Obfuscate(ref k8s.ObjectRef, changes []types.Change) { } func (o *Obfuscator) obfuscateSecret(ref k8s.ObjectRef, changes []types.Change) { + setMapValues := func(m any, v string) { + if m == nil { + return + } + m2, ok := m.(map[string]any) + if ok { + for k, _ := range m2 { + m2[k] = v + } + } + } + for i, _ := range changes { c := &changes[i] if strings.HasPrefix(c.JsonPath, "data.") || strings.HasPrefix(c.JsonPath, "stringData.") { @@ -37,6 +49,14 @@ func (o *Obfuscator) obfuscateSecret(ref k8s.ObjectRef, changes []types.Change) c.OldValue = "*****" c.UnifiedDiff = strings.ReplaceAll(c.UnifiedDiff, "*****b", "***** (obfuscated)") } + } else if c.JsonPath == "data" || c.JsonPath == "stringData" { + setMapValues(c.NewValue, "*****a") + setMapValues(c.OldValue, "*****b") + _ = updateUnifiedDiff(c) + c.UnifiedDiff = strings.ReplaceAll(c.UnifiedDiff, "*****a", "***** (obfuscated)") + c.UnifiedDiff = strings.ReplaceAll(c.UnifiedDiff, "*****b", "***** (obfuscated)") + setMapValues(c.NewValue, "*****") + setMapValues(c.OldValue, "*****") } } } From 02c2bfa5361c73b9d04f3d8b7e4dba1b62134dac Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 20 Dec 2022 21:09:07 +0100 Subject: [PATCH 1341/2916] fix: Also obfuscate data["key.with.dot"] secrets --- cmd/kluctl/commands/command_result.go | 5 +- e2e/obfuscate_test.go | 13 +++++ pkg/diff/obfuscate.go | 71 +++++++++++++++------------ 3 files changed, 57 insertions(+), 32 deletions(-) diff --git a/cmd/kluctl/commands/command_result.go b/cmd/kluctl/commands/command_result.go index dcf709fa1..bc6bb1ca9 100644 --- a/cmd/kluctl/commands/command_result.go +++ b/cmd/kluctl/commands/command_result.go @@ -204,7 +204,10 @@ func outputCommandResult(ctx context.Context, output []string, noObfuscate bool, if !noObfuscate { var obfuscator diff.Obfuscator for _, c := range cr.ChangedObjects { - obfuscator.Obfuscate(c.Ref, c.Changes) + err := obfuscator.Obfuscate(c.Ref, c.Changes) + if err != nil { + return err + } } } diff --git a/e2e/obfuscate_test.go b/e2e/obfuscate_test.go index b2fbfc849..7cff67ab1 100644 --- a/e2e/obfuscate_test.go +++ b/e2e/obfuscate_test.go @@ -79,4 +79,17 @@ func TestObfuscateSecrets(t *testing.T) { assert.NotContains(t, stdout, base64.StdEncoding.EncodeToString([]byte("secret_value_4"))) assert.Contains(t, stdout, "+secret3: '***** (obfuscated)'") assert.Contains(t, stdout, "+secret4: '***** (obfuscated)'") + + p.UpdateYaml("secret3/secret-secret3.yml", func(o *uo.UnstructuredObject) error { + _ = o.SetNestedField(map[string]any{ + "secret.dot1": "secret_value_5", + "secret.dot2": "secret_value_6", + }, "stringData") + return nil + }, "") + stdout, _ = p.KluctlMust("deploy", "--yes", "-t", "test") + assert.NotContains(t, stdout, base64.StdEncoding.EncodeToString([]byte("secret_value_5"))) + assert.NotContains(t, stdout, base64.StdEncoding.EncodeToString([]byte("secret_value_6"))) + assert.Contains(t, stdout, "data[\"secret.dot1\"] | +***** (obfuscated)") + assert.Contains(t, stdout, "data[\"secret.dot2\"] | +***** (obfuscated)") } diff --git a/pkg/diff/obfuscate.go b/pkg/diff/obfuscate.go index 44f5a6109..e576c29b9 100644 --- a/pkg/diff/obfuscate.go +++ b/pkg/diff/obfuscate.go @@ -1,8 +1,10 @@ package diff import ( + "fmt" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/ohler55/ojg/jp" "k8s.io/apimachinery/pkg/runtime/schema" "strings" ) @@ -12,51 +14,58 @@ var secretGvk = schema.GroupKind{Group: "", Kind: "Secret"} type Obfuscator struct { } -func (o *Obfuscator) Obfuscate(ref k8s.ObjectRef, changes []types.Change) { +func (o *Obfuscator) Obfuscate(ref k8s.ObjectRef, changes []types.Change) error { if ref.GVK.GroupKind() == secretGvk { - o.obfuscateSecret(ref, changes) + err := o.obfuscateSecret(ref, changes) + if err != nil { + return err + } } + return nil } -func (o *Obfuscator) obfuscateSecret(ref k8s.ObjectRef, changes []types.Change) { - setMapValues := func(m any, v string) { - if m == nil { - return +func (o *Obfuscator) obfuscateSecret(ref k8s.ObjectRef, changes []types.Change) error { + replaceValues := func(x any, v string) any { + if x == nil { + return nil } - m2, ok := m.(map[string]any) - if ok { - for k, _ := range m2 { - m2[k] = v + if m, ok := x.(map[string]any); ok { + for k, _ := range m { + m[k] = v + } + return m + } else if a, ok := x.([]any); ok { + for i, _ := range a { + a[i] = v } + return a } + return v } for i, _ := range changes { c := &changes[i] - if strings.HasPrefix(c.JsonPath, "data.") || strings.HasPrefix(c.JsonPath, "stringData.") { - if c.NewValue != nil { - c.NewValue = "*****a" - } - if c.OldValue != nil { - c.OldValue = "*****b" - } - _ = updateUnifiedDiff(c) - if c.NewValue != nil { - c.NewValue = "*****" - c.UnifiedDiff = strings.ReplaceAll(c.UnifiedDiff, "*****a", "***** (obfuscated)") - } - if c.OldValue != nil { - c.OldValue = "*****" - c.UnifiedDiff = strings.ReplaceAll(c.UnifiedDiff, "*****b", "***** (obfuscated)") - } - } else if c.JsonPath == "data" || c.JsonPath == "stringData" { - setMapValues(c.NewValue, "*****a") - setMapValues(c.OldValue, "*****b") + j, err := jp.ParseString(c.JsonPath) + if err != nil { + return err + } + if len(j) == 0 { + return fmt.Errorf("unexpected empty jsonPath") + } + child, ok := j[0].(jp.Child) + if !ok { + return fmt.Errorf("unexpected jsonPath fragment: %s", c.JsonPath) + } + + if child == "data" || child == "stringData" { + c.NewValue = replaceValues(c.NewValue, "*****a") + c.OldValue = replaceValues(c.OldValue, "*****b") _ = updateUnifiedDiff(c) + c.NewValue = replaceValues(c.NewValue, "*****") + c.OldValue = replaceValues(c.OldValue, "*****") c.UnifiedDiff = strings.ReplaceAll(c.UnifiedDiff, "*****a", "***** (obfuscated)") c.UnifiedDiff = strings.ReplaceAll(c.UnifiedDiff, "*****b", "***** (obfuscated)") - setMapValues(c.NewValue, "*****") - setMapValues(c.OldValue, "*****") } } + return nil } From 076aa90549dc23dd7e5c365844c052fc3887a9b5 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 22 Dec 2022 15:42:52 +0100 Subject: [PATCH 1342/2916] fix: loadFile should NOT ignore all files The "utils.Exists(path)" check always returned false as it did not take searchDirs into account. --- pkg/vars/vars_loader.go | 8 ++++---- pkg/vars/vars_loader_test.go | 12 ++++++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/pkg/vars/vars_loader.go b/pkg/vars/vars_loader.go index 40e0bea86..86b5dc0e3 100644 --- a/pkg/vars/vars_loader.go +++ b/pkg/vars/vars_loader.go @@ -115,12 +115,12 @@ func (v *VarsLoader) mergeVars(varsCtx *VarsCtx, newVars *uo.UnstructuredObject, } func (v *VarsLoader) loadFile(varsCtx *VarsCtx, path string, ignoreMissing bool, searchDirs []string, rootKey string) error { - if ignoreMissing && !utils.Exists(path) { - return nil - } - rendered, err := varsCtx.RenderFile(path, searchDirs) if err != nil { + // TODO the Jinja2 renderer should be able to better report this error + if ignoreMissing && err.Error() == fmt.Sprintf("template %s not found", path) { + return nil + } return fmt.Errorf("failed to render vars file %s: %w", path, err) } diff --git a/pkg/vars/vars_loader_test.go b/pkg/vars/vars_loader_test.go index a4a99b4e6..d89772654 100644 --- a/pkg/vars/vars_loader_test.go +++ b/pkg/vars/vars_loader_test.go @@ -80,6 +80,18 @@ func TestVarsLoader_File(t *testing.T) { assert.Equal(t, int64(42), v) }) + testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { + b := true + err := vl.LoadVars(vc, &types.VarsSource{ + IgnoreMissing: &b, + File: utils.StrPtr("test.yaml"), + }, []string{d}, "") + assert.NoError(t, err) + + v, _, _ := vc.Vars.GetNestedInt("test1", "test2") + assert.Equal(t, int64(42), v) + }) + testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { b := true err := vl.LoadVars(vc, &types.VarsSource{ From 49eff01307db791789572386f26a727deed346fc Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 23 Dec 2022 11:55:29 +0100 Subject: [PATCH 1343/2916] fix: Switch to aws-sdk-go-v2 for AWSSecretsManager vars source --- go.mod | 17 +++++++++++-- go.sum | 33 ++++++++++++++++++++++++-- pkg/vars/aws/clientfactory.go | 38 +++++++++++++----------------- pkg/vars/aws/fake_clientfactory.go | 21 ++++++++++------- pkg/vars/aws/secrets_manager.go | 7 +++--- pkg/vars/vars_loader.go | 9 ++++--- 6 files changed, 83 insertions(+), 42 deletions(-) diff --git a/go.mod b/go.mod index 32b762214..44a1dec60 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,6 @@ require ( github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 github.com/Masterminds/semver/v3 v3.2.0 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d - github.com/aws/aws-sdk-go v1.44.163 github.com/bitnami-labs/sealed-secrets v0.19.3 github.com/cyphar/filepath-securejoin v0.2.3 github.com/docker/distribution v2.8.1+incompatible @@ -53,12 +52,13 @@ require ( k8s.io/apimachinery v0.26.0 k8s.io/client-go v0.26.0 k8s.io/klog/v2 v2.80.1 - sigs.k8s.io/kustomize/api v0.12.1 // indirect sigs.k8s.io/kustomize/kyaml v0.13.9 sigs.k8s.io/structured-merge-diff/v4 v4.2.3 ) require ( + github.com/aws/aws-sdk-go-v2/config v1.18.7 + github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.16.11 github.com/go-logr/logr v1.2.3 github.com/hashicorp/go-multierror v1.1.1 github.com/huandu/xstrings v1.4.0 @@ -94,6 +94,18 @@ require ( github.com/armon/go-metrics v0.4.1 // indirect github.com/armon/go-radix v1.0.0 // indirect github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect + github.com/aws/aws-sdk-go v1.43.43 // indirect + github.com/aws/aws-sdk-go-v2 v1.17.3 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.13.7 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.21 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.27 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.21 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.3.28 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.21 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.11.28 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.11 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.17.7 // indirect + github.com/aws/smithy-go v1.13.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver v3.5.1+incompatible // indirect github.com/cenkalti/backoff/v3 v3.2.2 // indirect @@ -247,5 +259,6 @@ require ( k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 // indirect oras.land/oras-go v1.2.1 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/kustomize/api v0.12.1 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index bbdda4737..15b7d656e 100644 --- a/go.sum +++ b/go.sum @@ -130,8 +130,34 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/aws/aws-sdk-go v1.44.163 h1:XO1A/Laqf/l0IxVPghaQzdnVwxofVFv00IlX0BpmbhQ= -github.com/aws/aws-sdk-go v1.44.163/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.43.43 h1:1L06qzQvl4aC3Skfh5rV7xVhGHjIZoHcqy16NoyQ1o4= +github.com/aws/aws-sdk-go v1.43.43/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go-v2 v1.17.3 h1:shN7NlnVzvDUgPQ+1rLMSxY8OWRNDRYtiqe0p/PgrhY= +github.com/aws/aws-sdk-go-v2 v1.17.3/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= +github.com/aws/aws-sdk-go-v2/config v1.18.7 h1:V94lTcix6jouwmAsgQMAEBozVAGJMFhVj+6/++xfe3E= +github.com/aws/aws-sdk-go-v2/config v1.18.7/go.mod h1:OZYsyHFL5PB9UpyS78NElgKs11qI/B5KJau2XOJDXHA= +github.com/aws/aws-sdk-go-v2/credentials v1.13.7 h1:qUUcNS5Z1092XBFT66IJM7mYkMwgZ8fcC8YDIbEwXck= +github.com/aws/aws-sdk-go-v2/credentials v1.13.7/go.mod h1:AdCcbZXHQCjJh6NaH3pFaw8LUeBFn5+88BZGMVGuBT8= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.21 h1:j9wi1kQ8b+e0FBVHxCqCGo4kxDU175hoDHcWAi0sauU= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.21/go.mod h1:ugwW57Z5Z48bpvUyZuaPy4Kv+vEfJWnIrky7RmkBvJg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.27 h1:I3cakv2Uy1vNmmhRQmFptYDxOvBnwCdNwyw63N0RaRU= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.27/go.mod h1:a1/UpzeyBBerajpnP5nGZa9mGzsBn5cOKxm6NWQsvoI= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.21 h1:5NbbMrIzmUn/TXFqAle6mgrH5m9cOvMLRGL7pnG8tRE= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.21/go.mod h1:+Gxn8jYn5k9ebfHEqlhrMirFjSW0v0C9fI+KN5vk2kE= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.28 h1:KeTxcGdNnQudb46oOl4d90f2I33DF/c6q3RnZAmvQdQ= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.28/go.mod h1:yRZVr/iT0AqyHeep00SZ4YfBAKojXz08w3XMBscdi0c= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.21 h1:5C6XgTViSb0bunmU57b3CT+MhxULqHH2721FVA+/kDM= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.21/go.mod h1:lRToEJsn+DRA9lW4O9L9+/3hjTkUzlzyzHqn8MTds5k= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.16.11 h1:77V7vnw/NC4DORHVgA97+Ky2p1ri0+ZVYXh6ordUZU0= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.16.11/go.mod h1:jAeo/PdIJZuDSwsvxJS94G4d6h8tStj7WXVuKwLHWU8= +github.com/aws/aws-sdk-go-v2/service/sso v1.11.28 h1:gItLq3zBYyRDPmqAClgzTH8PBjDQGeyptYGHIwtYYNA= +github.com/aws/aws-sdk-go-v2/service/sso v1.11.28/go.mod h1:wo/B7uUm/7zw/dWhBJ4FXuw1sySU5lyIhVg1Bu2yL9A= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.11 h1:KCacyVSs/wlcPGx37hcbT3IGYO8P8Jx+TgSDhAXtQMY= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.11/go.mod h1:TZSH7xLO7+phDtViY/KUp9WGCJMQkLJ/VpgkTFd5gh8= +github.com/aws/aws-sdk-go-v2/service/sts v1.17.7 h1:9Mtq1KM6nD8/+HStvWcvYnixJ5N85DX+P+OY3kI3W2k= +github.com/aws/aws-sdk-go-v2/service/sts v1.17.7/go.mod h1:+lGbb3+1ugwKrNTWcf2RT05Xmp543B06zDFTwiTLp7I= +github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8= +github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -368,6 +394,7 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-containerregistry v0.12.1 h1:W1mzdNUTx4Zla4JaixCRLhORcR7G6KxE5hHl5fkPsp8= @@ -944,6 +971,7 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= @@ -1036,6 +1064,7 @@ golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/pkg/vars/aws/clientfactory.go b/pkg/vars/aws/clientfactory.go index db7e43d0e..b0542058c 100644 --- a/pkg/vars/aws/clientfactory.go +++ b/pkg/vars/aws/clientfactory.go @@ -1,42 +1,38 @@ package aws import ( - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/secretsmanager" - "github.com/aws/aws-sdk-go/service/secretsmanager/secretsmanageriface" - "os" + "context" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/secretsmanager" ) +type GetSecretValueInterface interface { + GetSecretValue(ctx context.Context, params *secretsmanager.GetSecretValueInput, optFns ...func(*secretsmanager.Options)) (*secretsmanager.GetSecretValueOutput, error) +} + type AwsClientFactory interface { - SecretsManagerClient(profile *string, region *string) (secretsmanageriface.SecretsManagerAPI, error) + SecretsManagerClient(profile *string, region *string) (GetSecretValueInterface, error) } type awsClientFactory struct { } -func (a *awsClientFactory) getSession(profile *string) (*session.Session, error) { - var opts session.Options - opts.SharedConfigState = session.SharedConfigEnable - // Environment variable always takes precedence - if _, ok := os.LookupEnv("AWS_PROFILE"); !ok && profile != nil { - opts.Profile = *profile - } - s, err := session.NewSessionWithOptions(opts) - if err != nil { - return nil, err +func (a *awsClientFactory) SecretsManagerClient(profile *string, region *string) (GetSecretValueInterface, error) { + var configOpts []func(*config.LoadOptions) error + if profile != nil { + configOpts = append(configOpts, config.WithSharedConfigProfile(*profile)) } - return s, nil -} + if region != nil { + configOpts = append(configOpts, config.WithRegion(*region)) + } -func (a *awsClientFactory) SecretsManagerClient(profile *string, region *string) (secretsmanageriface.SecretsManagerAPI, error) { - s, err := a.getSession(profile) + cfg, err := config.LoadDefaultConfig(context.Background(), configOpts...) if err != nil { return nil, err } - return secretsmanager.New(s, &aws.Config{Region: region}), nil + return secretsmanager.NewFromConfig(cfg), nil } func NewClientFactory() AwsClientFactory { diff --git a/pkg/vars/aws/fake_clientfactory.go b/pkg/vars/aws/fake_clientfactory.go index d06d68fd5..18a170338 100644 --- a/pkg/vars/aws/fake_clientfactory.go +++ b/pkg/vars/aws/fake_clientfactory.go @@ -1,21 +1,21 @@ package aws import ( + "context" "fmt" - "github.com/aws/aws-sdk-go/aws/awserr" - "github.com/aws/aws-sdk-go/service/secretsmanager" - "github.com/aws/aws-sdk-go/service/secretsmanager/secretsmanageriface" + "github.com/aws/aws-sdk-go-v2/service/secretsmanager" + "github.com/aws/aws-sdk-go-v2/service/secretsmanager/types" ) type FakeAwsClientFactory struct { - secretsmanageriface.SecretsManagerAPI + GetSecretValueInterface Secrets map[string]string } -func (f *FakeAwsClientFactory) GetSecretValue(in *secretsmanager.GetSecretValueInput) (*secretsmanager.GetSecretValueOutput, error) { - name := *in.SecretId - arn, err := ParseArn(*in.SecretId) +func (f *FakeAwsClientFactory) GetSecretValue(ctx context.Context, params *secretsmanager.GetSecretValueInput, optFns ...func(*secretsmanager.Options)) (*secretsmanager.GetSecretValueOutput, error) { + name := *params.SecretId + arn, err := ParseArn(*params.SecretId) if err == nil { name = arn.Resource } @@ -28,10 +28,13 @@ func (f *FakeAwsClientFactory) GetSecretValue(in *secretsmanager.GetSecretValueI }, nil } - return nil, awserr.New(secretsmanager.ErrCodeResourceNotFoundException, fmt.Sprintf("secret %s not found", *in.SecretId), nil) + errMsg := fmt.Sprintf("secret %s not found", *params.SecretId) + return nil, &types.ResourceNotFoundException{ + Message: &errMsg, + } } -func (f *FakeAwsClientFactory) SecretsManagerClient(profile *string, region *string) (secretsmanageriface.SecretsManagerAPI, error) { +func (f *FakeAwsClientFactory) SecretsManagerClient(profile *string, region *string) (GetSecretValueInterface, error) { return f, nil } diff --git a/pkg/vars/aws/secrets_manager.go b/pkg/vars/aws/secrets_manager.go index 1a57bd5ae..d3cced231 100644 --- a/pkg/vars/aws/secrets_manager.go +++ b/pkg/vars/aws/secrets_manager.go @@ -1,11 +1,12 @@ package aws import ( + "context" "fmt" - "github.com/aws/aws-sdk-go/service/secretsmanager" + "github.com/aws/aws-sdk-go-v2/service/secretsmanager" ) -func GetAwsSecretsManagerSecret(aws AwsClientFactory, profile *string, region *string, secretName string) (string, error) { +func GetAwsSecretsManagerSecret(ctx context.Context, aws AwsClientFactory, profile *string, region *string, secretName string) (string, error) { if region == nil { arn, err := ParseArn(secretName) if err != nil { @@ -19,7 +20,7 @@ func GetAwsSecretsManagerSecret(aws AwsClientFactory, profile *string, region *s return "", fmt.Errorf("getting secret %s from AWS secrets manager failed: %w", secretName, err) } - r, err := smClient.GetSecretValue(&secretsmanager.GetSecretValueInput{ + r, err := smClient.GetSecretValue(ctx, &secretsmanager.GetSecretValueInput{ SecretId: &secretName, }) if err != nil { diff --git a/pkg/vars/vars_loader.go b/pkg/vars/vars_loader.go index 86b5dc0e3..9f070c54d 100644 --- a/pkg/vars/vars_loader.go +++ b/pkg/vars/vars_loader.go @@ -5,8 +5,7 @@ import ( "encoding/base64" errors2 "errors" "fmt" - "github.com/aws/aws-sdk-go/aws/awserr" - "github.com/aws/aws-sdk-go/service/secretsmanager" + types2 "github.com/aws/aws-sdk-go-v2/service/secretsmanager/types" "github.com/kluctl/go-jinja2" "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/repocache" @@ -207,11 +206,11 @@ func (v *VarsLoader) loadAwsSecretsManager(varsCtx *VarsCtx, source *types.VarsS return fmt.Errorf("no AWS client factory provided") } - secret, err := aws.GetAwsSecretsManagerSecret(v.aws, source.AwsSecretsManager.Profile, source.AwsSecretsManager.Region, source.AwsSecretsManager.SecretName) + secret, err := aws.GetAwsSecretsManagerSecret(v.ctx, v.aws, source.AwsSecretsManager.Profile, source.AwsSecretsManager.Region, source.AwsSecretsManager.SecretName) if err != nil { - var aerr awserr.Error + var aerr *types2.ResourceNotFoundException if errors2.As(err, &aerr) { - if ignoreMissing && aerr.Code() == secretsmanager.ErrCodeResourceNotFoundException { + if ignoreMissing { return nil } } From 972f67a7bef777331785c2091265a94cc0e49581 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 23 Dec 2022 14:47:52 +0100 Subject: [PATCH 1344/2916] fix: Use custom SOPS decrytor implementation, inspired by the Flux decryptor This ensures that the CLI and the controller use the same implemntation. It also allows us to upgrade SOPS to the develop branch without breaking the controller's dependencies. --- go.mod | 16 +- go.sum | 22 +- pkg/deployment/shared_context.go | 4 +- pkg/helm/helm_release.go | 5 +- pkg/kluctl_project/load.go | 5 +- pkg/kluctl_project/project.go | 4 +- pkg/kluctl_project/project_load.go | 4 +- pkg/sops/decryptor/decryptor.go | 636 +++++++++++++ pkg/sops/decryptor/decryptor_test.go | 1229 ++++++++++++++++++++++++++ pkg/sops/sops_decryptor.go | 21 - pkg/sops/utils.go | 9 +- pkg/vars/vars_loader.go | 5 +- pkg/vars/vars_loader_test.go | 7 +- 13 files changed, 1910 insertions(+), 57 deletions(-) create mode 100644 pkg/sops/decryptor/decryptor.go create mode 100644 pkg/sops/decryptor/decryptor_test.go delete mode 100644 pkg/sops/sops_decryptor.go diff --git a/go.mod b/go.mod index 44a1dec60..f4b2b9d17 100644 --- a/go.mod +++ b/go.mod @@ -57,27 +57,30 @@ require ( ) require ( + filippo.io/age v1.0.0 github.com/aws/aws-sdk-go-v2/config v1.18.7 github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.16.11 github.com/go-logr/logr v1.2.3 github.com/hashicorp/go-multierror v1.1.1 github.com/huandu/xstrings v1.4.0 + github.com/onsi/gomega v1.24.1 github.com/otiai10/copy v1.9.0 go.mozilla.org/sops/v3 v3.7.3 sigs.k8s.io/controller-runtime v0.14.0 + sigs.k8s.io/kustomize/api v0.12.1 + sigs.k8s.io/yaml v1.3.0 ) require ( cloud.google.com/go/compute v1.14.0 // indirect cloud.google.com/go/compute/metadata v0.2.2 // indirect - filippo.io/age v1.0.0 // indirect - github.com/Azure/azure-sdk-for-go v67.1.0+incompatible // indirect + github.com/Azure/azure-sdk-for-go v63.3.0+incompatible // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect - github.com/Azure/go-autorest/autorest v0.11.28 // indirect - github.com/Azure/go-autorest/autorest/adal v0.9.21 // indirect + github.com/Azure/go-autorest/autorest v0.11.27 // indirect + github.com/Azure/go-autorest/autorest/adal v0.9.20 // indirect github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 // indirect - github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 // indirect + github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 // indirect github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect @@ -251,6 +254,7 @@ require ( gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/urfave/cli.v1 v1.20.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect + gotest.tools/v3 v3.2.0 // indirect k8s.io/apiserver v0.26.0 // indirect k8s.io/cli-runtime v0.26.0 // indirect k8s.io/component-base v0.26.0 // indirect @@ -259,6 +263,4 @@ require ( k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 // indirect oras.land/oras-go v1.2.1 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/kustomize/api v0.12.1 // indirect - sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index 15b7d656e..af3d44e15 100644 --- a/go.sum +++ b/go.sum @@ -48,23 +48,22 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= filippo.io/age v1.0.0 h1:V6q14n0mqYU3qKFkZ6oOaF9oXneOviS3ubXsSVBRSzc= filippo.io/age v1.0.0/go.mod h1:PaX+Si/Sd5G8LgfCwldsSba3H1DDQZhIhFGkhbHaBq8= -github.com/Azure/azure-sdk-for-go v67.1.0+incompatible h1:oziYcaopbnIKfM69DL05wXdypiqfrUKdxUKrKpynJTw= -github.com/Azure/azure-sdk-for-go v67.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v63.3.0+incompatible h1:INepVujzUrmArRZjDLHbtER+FkvCoEwyRCXGqOlmDII= +github.com/Azure/azure-sdk-for-go v63.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.24/go.mod h1:G6kyRlFnTuSbEYkQGawPfsCswgme4iYf6rfSKUDzbCc= -github.com/Azure/go-autorest/autorest v0.11.28 h1:ndAExarwr5Y+GaHE6VCaY1kyS/HwwGGyuimVhWsHOEM= -github.com/Azure/go-autorest/autorest v0.11.28/go.mod h1:MrkzG3Y3AH668QyF9KRk5neJnGgmhQ6krbhR8Q5eMvA= +github.com/Azure/go-autorest/autorest v0.11.27 h1:F3R3q42aWytozkV8ihzcgMO4OA4cuqr3bNlsEuF6//A= +github.com/Azure/go-autorest/autorest v0.11.27/go.mod h1:7l8ybrIdUmGqZMTD0sRtAr8NvbHjfofbf8RSP2q7w7U= github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= -github.com/Azure/go-autorest/autorest/adal v0.9.21 h1:jjQnVFXPfekaqb8vIsv2G1lxshoW+oGv4MDlhRtnYZk= -github.com/Azure/go-autorest/autorest/adal v0.9.21/go.mod h1:zua7mBUaCc5YnSLKYgGJR/w5ePdMDA6H56upLsHzA9U= +github.com/Azure/go-autorest/autorest/adal v0.9.20 h1:gJ3E98kMpFB1MFqQCvA1yFab8vthOeD4VlFRQULxahg= +github.com/Azure/go-autorest/autorest/adal v0.9.20/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 h1:P6bYXFoao05z5uhOQzbC3Qd8JqF3jUoocoTeIxkp2cA= github.com/Azure/go-autorest/autorest/azure/auth v0.5.11/go.mod h1:84w/uV8E37feW2NCJ08uT9VBfjfUHpgLVnG2InYD6cg= +github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 h1:0W/yGmFdTIT77fvdlGZ0LMISoLHFJ7Tx4U0yeB+uFs4= github.com/Azure/go-autorest/autorest/azure/cli v0.4.5/go.mod h1:ADQAXrkgm7acgWVUNamOgh8YNrv4p27l3Wc55oVfpzg= -github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 h1:w77/uPk80ZET2F+AfQExZyEWtn+0Rk/uw17m9fv5Ajc= -github.com/Azure/go-autorest/autorest/azure/cli v0.4.6/go.mod h1:piCfgPho7BiIDdEQ1+g4VmKyD5y+p/XtSNqE6Hc4QD0= github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= @@ -674,6 +673,7 @@ github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DV github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo/v2 v2.6.0 h1:9t9b9vRUbFq3C4qKFCGkVuq/fIHji802N1nrtkh1mNc= github.com/onsi/gomega v1.24.1 h1:KORJXNNTzJXzu4ScJWssJfJMnJ+2QJqhoQSRwNlze9E= +github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034= @@ -787,6 +787,7 @@ github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= @@ -886,7 +887,6 @@ golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= @@ -1315,8 +1315,8 @@ gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= -gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= +gotest.tools/v3 v3.2.0 h1:I0DwBVMGAx26dttAj1BtJLAkVGncrkkUXfJLC4Flt/I= +gotest.tools/v3 v3.2.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= helm.sh/helm/v3 v3.10.3 h1:wL7IUZ7Zyukm5Kz0OUmIFZgKHuAgByCrUcJBtY0kDyw= helm.sh/helm/v3 v3.10.3/go.mod h1:CXOcs02AYvrlPMWARNYNRgf2rNP7gLJQsi/Ubd4EDrI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/pkg/deployment/shared_context.go b/pkg/deployment/shared_context.go index dec1c4571..859f194ae 100644 --- a/pkg/deployment/shared_context.go +++ b/pkg/deployment/shared_context.go @@ -5,7 +5,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/helm" "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/repocache" - "github.com/kluctl/kluctl/v2/pkg/sops" + "github.com/kluctl/kluctl/v2/pkg/sops/decryptor" "github.com/kluctl/kluctl/v2/pkg/vars" ) @@ -14,7 +14,7 @@ type SharedContext struct { K *k8s.K8sCluster K8sVersion string RP *repocache.GitRepoCache - SopsDecrypter sops.SopsDecrypter + SopsDecrypter *decryptor.Decryptor VarsLoader *vars.VarsLoader HelmCredentials helm.HelmCredentialsProvider diff --git a/pkg/helm/helm_release.go b/pkg/helm/helm_release.go index c4d8963e4..efdaba29a 100644 --- a/pkg/helm/helm_release.go +++ b/pkg/helm/helm_release.go @@ -7,6 +7,7 @@ import ( securejoin "github.com/cyphar/filepath-securejoin" "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/sops" + "github.com/kluctl/kluctl/v2/pkg/sops/decryptor" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" @@ -97,7 +98,7 @@ func (hr *Release) GetFullOutputPath() (string, error) { return securejoin.SecureJoin(dir, hr.GetOutputPath()) } -func (hr *Release) Render(ctx context.Context, k *k8s.K8sCluster, k8sVersion string, sopsDecrypter sops.SopsDecrypter) error { +func (hr *Release) Render(ctx context.Context, k *k8s.K8sCluster, k8sVersion string, sopsDecrypter *decryptor.Decryptor) error { err := hr.doRender(ctx, k, k8sVersion, sopsDecrypter) if err != nil { return fmt.Errorf("rendering helm chart %s for release %s has failed: %w", hr.Chart.GetChartName(), hr.Config.ReleaseName, err) @@ -153,7 +154,7 @@ func (hr *Release) getPulledChart(ctx context.Context) (*PulledChart, error) { return pc, nil } -func (hr *Release) doRender(ctx context.Context, k *k8s.K8sCluster, k8sVersion string, sopsDecrypter sops.SopsDecrypter) error { +func (hr *Release) doRender(ctx context.Context, k *k8s.K8sCluster, k8sVersion string, sopsDecrypter *decryptor.Decryptor) error { pc, err := hr.getPulledChart(ctx) if err != nil { return err diff --git a/pkg/kluctl_project/load.go b/pkg/kluctl_project/load.go index d477429b8..f320482ba 100644 --- a/pkg/kluctl_project/load.go +++ b/pkg/kluctl_project/load.go @@ -3,7 +3,7 @@ package kluctl_project import ( "context" "github.com/kluctl/go-jinja2" - "github.com/kluctl/kluctl/v2/pkg/sops" + "github.com/kluctl/kluctl/v2/pkg/sops/decryptor" "github.com/kluctl/kluctl/v2/pkg/status" ) @@ -21,7 +21,8 @@ func LoadKluctlProject(ctx context.Context, args LoadKluctlProjectArgs, tmpDir s } if p.SopsDecrypter == nil { - p.SopsDecrypter = &sops.LocalSopsDecrypter{} + p.SopsDecrypter = decryptor.NewDecryptor(args.ProjectDir, decryptor.MaxEncryptedFileSize) + p.SopsDecrypter.AddLocalKeyService() } err := p.loadKluctlProject() diff --git a/pkg/kluctl_project/project.go b/pkg/kluctl_project/project.go index 0df49e01c..cc3eb603a 100644 --- a/pkg/kluctl_project/project.go +++ b/pkg/kluctl_project/project.go @@ -5,7 +5,7 @@ import ( "fmt" "github.com/kluctl/go-jinja2" "github.com/kluctl/kluctl/v2/pkg/repocache" - "github.com/kluctl/kluctl/v2/pkg/sops" + "github.com/kluctl/kluctl/v2/pkg/sops/decryptor" types2 "github.com/kluctl/kluctl/v2/pkg/types" ) @@ -26,7 +26,7 @@ type LoadedKluctlProject struct { J2 *jinja2.Jinja2 RP *repocache.GitRepoCache - SopsDecrypter sops.SopsDecrypter + SopsDecrypter *decryptor.Decryptor } func (c *LoadedKluctlProject) FindBaseTarget(name string) (*types2.Target, error) { diff --git a/pkg/kluctl_project/project_load.go b/pkg/kluctl_project/project_load.go index 4c075fd23..c397e5190 100644 --- a/pkg/kluctl_project/project_load.go +++ b/pkg/kluctl_project/project_load.go @@ -2,7 +2,7 @@ package kluctl_project import ( "github.com/kluctl/kluctl/v2/pkg/repocache" - "github.com/kluctl/kluctl/v2/pkg/sops" + "github.com/kluctl/kluctl/v2/pkg/sops/decryptor" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/yaml" @@ -16,7 +16,7 @@ type LoadKluctlProjectArgs struct { ProjectDir string ProjectConfig string - SopsDecrypter sops.SopsDecrypter + SopsDecrypter *decryptor.Decryptor RP *repocache.GitRepoCache ClientConfigGetter func(context *string) (*rest.Config, *api.Config, error) diff --git a/pkg/sops/decryptor/decryptor.go b/pkg/sops/decryptor/decryptor.go new file mode 100644 index 000000000..84d65567b --- /dev/null +++ b/pkg/sops/decryptor/decryptor.go @@ -0,0 +1,636 @@ +/* +Copyright 2020 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package decryptor + +import ( + "bytes" + "encoding/base64" + "errors" + "fmt" + "go.mozilla.org/sops/v3/age" + "go.mozilla.org/sops/v3/keys" + "go.mozilla.org/sops/v3/pgp" + "io/fs" + "os" + "path/filepath" + "sort" + "strings" + "time" + + securejoin "github.com/cyphar/filepath-securejoin" + "go.mozilla.org/sops/v3" + "go.mozilla.org/sops/v3/aes" + "go.mozilla.org/sops/v3/cmd/sops/common" + "go.mozilla.org/sops/v3/cmd/sops/formats" + "go.mozilla.org/sops/v3/keyservice" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/kustomize/api/konfig" + "sigs.k8s.io/kustomize/api/resource" + kustypes "sigs.k8s.io/kustomize/api/types" + "sigs.k8s.io/yaml" +) + +const ( + // MaxEncryptedFileSize is the max allowed file size in bytes of an encrypted + // file. + MaxEncryptedFileSize int64 = 5 << 20 + // unsupportedFormat is used to signal no sopsFormatToMarkerBytes format was + // detected by detectFormatFromMarkerBytes. + unsupportedFormat = formats.Format(-1) +) + +var ( + // sopsFormatToString is the counterpart to + // https://github.com/mozilla/sops/blob/v3.7.2/cmd/sops/formats/formats.go#L16 + sopsFormatToString = map[formats.Format]string{ + formats.Binary: "binary", + formats.Dotenv: "dotenv", + formats.Ini: "INI", + formats.Json: "JSON", + formats.Yaml: "YAML", + } + // sopsFormatToMarkerBytes contains a list of formats and their byte + // order markers, used to detect if a Secret data field is SOPS' encrypted. + sopsFormatToMarkerBytes = map[formats.Format][]byte{ + // formats.Binary is a JSON envelop at encrypted rest + formats.Binary: []byte("\"mac\": \"ENC["), + formats.Dotenv: []byte("sops_mac=ENC["), + formats.Ini: []byte("[sops]"), + formats.Json: []byte("\"mac\": \"ENC["), + formats.Yaml: []byte("mac: ENC["), + } +) + +// Decryptor performs decryption operations for a +// kluctlv1.KluctlDeployment. +// The only supported decryption provider at present is +// DecryptionProviderSOPS. +type Decryptor struct { + // root is the root for file system operations. Any (relative) path or + // symlink is not allowed to traverse outside this path. + root string + // maxFileSize is the max size in bytes a file is allowed to have to be + // decrypted. Defaults to MaxEncryptedFileSize. + maxFileSize int64 + // checkSopsMac instructs the decryptor to perform the SOPS data integrity + // check using the MAC. Not enabled by default, as arbitrary data gets + // injected into most resources, causing the integrity check to fail. + // Mostly kept around for feature completeness and documentation purposes. + checkSopsMac bool + + // keyServices are the SOPS keyservice.KeyServiceClient's available to the + // decryptor. + keyServices []keyservice.KeyServiceClient +} + +// NewDecryptor creates a new Decryptor for the given kluctlDeployment. +// gnuPGHome can be empty, in which case the systems' keyring is used. +func NewDecryptor(root string, maxFileSize int64) *Decryptor { + return &Decryptor{ + root: root, + maxFileSize: maxFileSize, + } +} + +func (d *Decryptor) AddLocalKeyService() { + d.AddKeyServiceClient(keyservice.NewLocalClient()) +} + +func (d *Decryptor) AddKeyServiceClient(s keyservice.KeyServiceClient) { + d.keyServices = append(d.keyServices, s) +} + +// IsEncryptedSecret checks if the given object is a Kubernetes Secret encrypted +// with Mozilla SOPS. +func IsEncryptedSecret(object *unstructured.Unstructured) bool { + if object.GetKind() == "Secret" && object.GetAPIVersion() == "v1" { + if _, found, _ := unstructured.NestedFieldNoCopy(object.Object, "sops"); found { + return true + } + } + return false +} + +// IsOfflineMethod returns true for offline decrypt methods or false otherwise +func IsOfflineMethod(mk keys.MasterKey) bool { + switch mk.(type) { + case *pgp.MasterKey, *age.MasterKey: + return true + default: + return false + } +} + +// SopsDecryptWithFormat attempts to load a SOPS encrypted file using the store +// for the input format, gathers the data key for it from the key service, +// and then decrypts the file data with the retrieved data key. +// It returns the decrypted bytes in the provided output format, or an error. +func (d *Decryptor) SopsDecryptWithFormat(data []byte, inputFormat, outputFormat formats.Format) (_ []byte, err error) { + defer func() { + // It was discovered that malicious input and/or output instructions can + // make SOPS panic. Recover from this panic and return as an error. + if r := recover(); r != nil { + err = fmt.Errorf("failed to emit encrypted %s file as decrypted %s: %v", + sopsFormatToString[inputFormat], sopsFormatToString[outputFormat], r) + } + }() + + store := common.StoreForFormat(inputFormat) + + tree, err := store.LoadEncryptedFile(data) + if err != nil { + return nil, sopsUserErr(fmt.Sprintf("failed to load encrypted %s data", sopsFormatToString[inputFormat]), err) + } + + for _, group := range tree.Metadata.KeyGroups { + // Sort MasterKeys in the group so offline ones are tried first + sort.SliceStable(group, func(i, j int) bool { + return IsOfflineMethod(group[i]) && !IsOfflineMethod(group[j]) + }) + } + + metadataKey, err := tree.Metadata.GetDataKeyWithKeyServices(d.keyServices) + if err != nil { + return nil, sopsUserErr("cannot get sops data key", err) + } + + cipher := aes.NewCipher() + mac, err := tree.Decrypt(metadataKey, cipher) + if err != nil { + return nil, sopsUserErr("error decrypting sops tree", err) + } + + if d.checkSopsMac { + // Compute the hash of the cleartext tree and compare it with + // the one that was stored in the document. If they match, + // integrity was preserved + // Ref: go.mozilla.org/sops/v3/decrypt/decrypt.go + originalMac, err := cipher.Decrypt( + tree.Metadata.MessageAuthenticationCode, + metadataKey, + tree.Metadata.LastModified.Format(time.RFC3339), + ) + if err != nil { + return nil, sopsUserErr("failed to verify sops data integrity", err) + } + if originalMac != mac { + // If the file has an empty MAC, display "no MAC" + if originalMac == "" { + originalMac = "no MAC" + } + return nil, fmt.Errorf("failed to verify sops data integrity: expected mac '%s', got '%s'", originalMac, mac) + } + } + + outputStore := common.StoreForFormat(outputFormat) + out, err := outputStore.EmitPlainFile(tree.Branches) + if err != nil { + return nil, sopsUserErr(fmt.Sprintf("failed to emit encrypted %s file as decrypted %s", + sopsFormatToString[inputFormat], sopsFormatToString[outputFormat]), err) + } + return out, err +} + +// DecryptResource attempts to decrypt the provided resource overwriting the resource +// with the decrypted data. +// It has special support for Kubernetes Secrets with encrypted data entries +// while decrypting with DecryptionProviderSOPS, to allow individual data entries +// injected by e.g. a Kustomize secret generator to be decrypted +func (d *Decryptor) DecryptResource(res *resource.Resource) (*resource.Resource, error) { + if res == nil { + return nil, nil + } + + switch { + case isSOPSEncryptedResource(res): + // As we are expecting to decrypt right before applying, we do not + // care about keeping any other data (e.g. comments) around. + // We can therefore simply work with JSON, which saves us from e.g. + // JSON -> YAML -> JSON transformations. + out, err := res.MarshalJSON() + if err != nil { + return nil, err + } + + data, err := d.SopsDecryptWithFormat(out, formats.Json, formats.Json) + if err != nil { + return nil, fmt.Errorf("failed to decrypt and format '%s/%s' %s data: %w", + res.GetNamespace(), res.GetName(), res.GetKind(), err) + } + + err = res.UnmarshalJSON(data) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal decrypted '%s/%s' %s to JSON: %w", + res.GetNamespace(), res.GetName(), res.GetKind(), err) + } + return res, nil + case res.GetKind() == "Secret": + dataMap := res.GetDataMap() + for key, value := range dataMap { + data, err := base64.StdEncoding.DecodeString(value) + if err != nil { + // If we fail to base64 decode, it is (very) likely to be a + // user input error. Instead of failing here, let it bubble + // up during the actual build. + continue + } + + if inF := detectFormatFromMarkerBytes(data); inF != unsupportedFormat { + outF := formatForPath(key) + out, err := d.SopsDecryptWithFormat(data, inF, outF) + if err != nil { + return nil, fmt.Errorf("failed to decrypt and format '%s/%s' Secret field '%s': %w", + res.GetNamespace(), res.GetName(), key, err) + } + dataMap[key] = base64.StdEncoding.EncodeToString(out) + } + } + res.SetDataMap(dataMap) + return res, nil + } + return nil, nil +} + +// DecryptEnvSources attempts to decrypt all types.SecretArgs FileSources and +// EnvSources a Kustomization file in the directory at the provided path refers +// to, before walking recursively over all other resources it refers to. +// It ignores resource references which refer to absolute or relative paths +// outside the working directory of the decryptor, but returns any decryption +// error. +func (d *Decryptor) DecryptEnvSources(path string) error { + decrypted, visited := make(map[string]struct{}, 0), make(map[string]struct{}, 0) + visit := d.decryptKustomizationEnvSources(decrypted) + return recurseKustomizationFiles(d.root, path, visit, visited) +} + +// decryptKustomizationEnvSources returns a visitKustomization implementation +// which attempts to decrypt any EnvSources entry it finds in the Kustomization +// file with which it is called. +// After decrypting successfully, it adds the absolute path of the file to the +// given map. +func (d *Decryptor) decryptKustomizationEnvSources(visited map[string]struct{}) visitKustomization { + return func(root, path string, kus *kustypes.Kustomization) error { + visitRef := func(sourcePath string, format formats.Format) error { + if !filepath.IsAbs(sourcePath) { + sourcePath = filepath.Join(path, sourcePath) + } + absRef, _, err := securePaths(root, sourcePath) + if err != nil { + return err + } + if _, ok := visited[absRef]; ok { + return nil + } + + if err := d.sopsDecryptFile(absRef, format, format); err != nil { + return securePathErr(root, err) + } + + // Explicitly set _after_ the decryption operation, this makes + // visited work as a list of actually decrypted files + visited[absRef] = struct{}{} + return nil + } + + for _, gen := range kus.SecretGenerator { + for _, fileSrc := range gen.FileSources { + parts := strings.SplitN(fileSrc, "=", 2) + key := parts[0] + var filePath string + if len(parts) > 1 { + filePath = parts[1] + } else { + filePath = key + } + if err := visitRef(filePath, formatForPath(key)); err != nil { + return err + } + } + for _, envFile := range gen.EnvSources { + format := formatForPath(envFile) + if format == formats.Binary { + // Default to dotenv + format = formats.Dotenv + } + if err := visitRef(envFile, format); err != nil { + return err + } + } + } + return nil + } +} + +// sopsDecryptFile attempts to decrypt the file at the given path using SOPS' +// store for the provided input format, and writes it back to the path using +// the store for the output format. +// Path must be absolute and a regular file, the file is not allowed to exceed +// the maxFileSize. +// +// NB: The method only does the simple checks described above and does not +// verify whether the path provided is inside the working directory. Boundary +// enforcement is expected to have been done by the caller. +func (d *Decryptor) sopsDecryptFile(path string, inputFormat, outputFormat formats.Format) error { + fi, err := os.Lstat(path) + if err != nil { + return err + } + + if !fi.Mode().IsRegular() { + return fmt.Errorf("cannot decrypt irregular file as it has file mode type bits set") + } + if fileSize := fi.Size(); d.maxFileSize > 0 && fileSize > d.maxFileSize { + return fmt.Errorf("cannot decrypt file with size (%d bytes) exceeding limit (%d)", fileSize, d.maxFileSize) + } + + data, err := os.ReadFile(path) + if err != nil { + return err + } + + if !bytes.Contains(data, sopsFormatToMarkerBytes[inputFormat]) { + return nil + } + + out, err := d.SopsDecryptWithFormat(data, inputFormat, outputFormat) + if err != nil { + return err + } + err = os.WriteFile(path, out, 0o644) + if err != nil { + return fmt.Errorf("error writing sops decrypted %s data to %s file: %w", + sopsFormatToString[inputFormat], sopsFormatToString[outputFormat], err) + } + return nil +} + +// sopsEncryptWithFormat attempts to load a plain file using the store +// for the input format, gathers the data key for it from the key service, +// and then encrypt the file data with the retrieved data key. +// It returns the encrypted bytes in the provided output format, or an error. +func (d *Decryptor) sopsEncryptWithFormat(metadata sops.Metadata, data []byte, inputFormat, outputFormat formats.Format) ([]byte, error) { + store := common.StoreForFormat(inputFormat) + + branches, err := store.LoadPlainFile(data) + if err != nil { + return nil, err + } + + tree := sops.Tree{ + Branches: branches, + Metadata: metadata, + } + dataKey, errs := tree.GenerateDataKeyWithKeyServices(d.keyServices) + if len(errs) > 0 { + return nil, sopsUserErr("could not generate data key", fmt.Errorf("%s", errs)) + } + + cipher := aes.NewCipher() + unencryptedMac, err := tree.Encrypt(dataKey, cipher) + if err != nil { + return nil, sopsUserErr("error encrypting sops tree", err) + } + tree.Metadata.LastModified = time.Now().UTC() + tree.Metadata.MessageAuthenticationCode, err = cipher.Encrypt(unencryptedMac, dataKey, tree.Metadata.LastModified.Format(time.RFC3339)) + if err != nil { + return nil, sopsUserErr("cannot encrypt sops data tree", err) + } + + outStore := common.StoreForFormat(outputFormat) + out, err := outStore.EmitEncryptedFile(tree) + if err != nil { + return nil, sopsUserErr("failed to emit sops encrypted file", err) + } + return out, nil +} + +// secureLoadKustomizationFile tries to securely load a Kustomization file from +// the given directory path. +// If multiple Kustomization files are found, or the request is ambiguous, an +// error is returned. +func secureLoadKustomizationFile(root, path string) (*kustypes.Kustomization, error) { + if !filepath.IsAbs(root) { + return nil, fmt.Errorf("root '%s' must be absolute", root) + } + if filepath.IsAbs(path) { + return nil, fmt.Errorf("path '%s' must be relative", path) + } + + var loadPath string + for _, fName := range konfig.RecognizedKustomizationFileNames() { + fPath, err := securejoin.SecureJoin(root, filepath.Join(path, fName)) + if err != nil { + return nil, fmt.Errorf("failed to secure join %s: %w", fName, err) + } + fi, err := os.Lstat(fPath) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + continue + } + return nil, fmt.Errorf("failed to lstat %s: %w", fName, securePathErr(root, err)) + } + + if !fi.Mode().IsRegular() { + return nil, fmt.Errorf("expected %s to be a regular file", fName) + } + if loadPath != "" { + return nil, fmt.Errorf("found multiple kustomization files") + } + loadPath = fPath + } + if loadPath == "" { + return nil, fmt.Errorf("no kustomization file found") + } + + data, err := os.ReadFile(loadPath) + if err != nil { + return nil, fmt.Errorf("failed to read kustomization file: %w", securePathErr(root, err)) + } + + kus := kustypes.Kustomization{ + TypeMeta: kustypes.TypeMeta{ + APIVersion: kustypes.KustomizationVersion, + Kind: kustypes.KustomizationKind, + }, + } + if err := yaml.Unmarshal(data, &kus); err != nil { + return nil, fmt.Errorf("failed to unmarshal kustomization file from '%s': %w", loadPath, err) + } + return &kus, nil +} + +// visitKustomization is called by recurseKustomizationFiles after every +// successful Kustomization file load. +type visitKustomization func(root, path string, kus *kustypes.Kustomization) error + +// errRecurseIgnore is a wrapping error to signal to recurseKustomizationFiles +// the error can be ignored during recursion. For example, because the +// Kustomization file can not be loaded for a subsequent call. +type errRecurseIgnore struct { + Err error +} + +// Unwrap returns the actual underlying error. +func (e *errRecurseIgnore) Unwrap() error { + return e.Err +} + +// Error returns the error string of the underlying error. +func (e *errRecurseIgnore) Error() string { + if err := e.Err; err != nil { + return e.Err.Error() + } + return "recurse ignore" +} + +// recurseKustomizationFiles attempts to recursively load and visit +// Kustomization files. +// The provided path is allowed to be relative, in which case it is safely +// joined with root. When absolute, it must be inside root. +func recurseKustomizationFiles(root, path string, visit visitKustomization, visited map[string]struct{}) error { + // Resolve the secure paths + absPath, relPath, err := securePaths(root, path) + if err != nil { + return err + } + + if _, ok := visited[absPath]; ok { + // Short-circuit + return nil + } + visited[absPath] = struct{}{} + + // Confirm we are dealing with a directory + fi, err := os.Lstat(absPath) + if err != nil { + err = securePathErr(root, err) + if errors.Is(err, fs.ErrNotExist) { + err = &errRecurseIgnore{Err: err} + } + return err + } + if !fi.IsDir() { + return &errRecurseIgnore{Err: fmt.Errorf("not a directory")} + } + + // Attempt to load the Kustomization file from the directory + kus, err := secureLoadKustomizationFile(root, relPath) + if err != nil { + return err + } + + // Visit the Kustomization + if err = visit(root, path, kus); err != nil { + return err + } + + // Recurse over other resources in Kustomization, + // repeating the above logic per item + for _, res := range kus.Resources { + if !filepath.IsAbs(res) { + res = filepath.Join(path, res) + } + if err = recurseKustomizationFiles(root, res, visit, visited); err != nil { + // When the resource does not exist at the compiled path, it's + // either an invalid reference, or a URL. + // If the reference is valid but does not point to a directory, + // we have run into a dead end as well. + // In all other cases, the error is of (possible) importance to + // the user, and we should return it. + if _, ok := err.(*errRecurseIgnore); !ok { + return err + } + } + } + return nil +} + +// isSOPSEncryptedResource detects if the given resource is a SOPS' encrypted +// resource by looking for ".sops" and ".sops.mac" fields. +func isSOPSEncryptedResource(res *resource.Resource) bool { + if res == nil { + return false + } + sopsField := res.Field("sops") + if sopsField.IsNilOrEmpty() { + return false + } + macField := sopsField.Value.Field("mac") + return !macField.IsNilOrEmpty() +} + +// securePaths returns the absolute and relative paths for the provided path, +// guaranteed to be scoped inside the provided root. +// When the given path is absolute, the root is stripped before secure joining +// it on root. +func securePaths(root, path string) (string, string, error) { + if filepath.IsAbs(path) { + path = stripRoot(root, path) + } + secureAbsPath, err := securejoin.SecureJoin(root, path) + if err != nil { + return "", "", err + } + return secureAbsPath, stripRoot(root, secureAbsPath), nil +} + +func stripRoot(root, path string) string { + sepStr := string(filepath.Separator) + root, path = filepath.Clean(sepStr+root), filepath.Clean(sepStr+path) + switch { + case path == root: + path = sepStr + case root == sepStr: + // noop + case strings.HasPrefix(path, root+sepStr): + path = strings.TrimPrefix(path, root+sepStr) + } + return filepath.Clean(filepath.Join("."+sepStr, path)) +} + +func sopsUserErr(msg string, err error) error { + if userErr, ok := err.(sops.UserError); ok { + err = fmt.Errorf(userErr.UserError()) + } + return fmt.Errorf("%s: %w", msg, err) +} + +func securePathErr(root string, err error) error { + if pathErr := new(fs.PathError); errors.As(err, &pathErr) { + err = &fs.PathError{Op: pathErr.Op, Path: stripRoot(root, pathErr.Path), Err: pathErr.Err} + } + return err +} + +func formatForPath(path string) formats.Format { + switch { + case strings.HasSuffix(path, corev1.DockerConfigJsonKey): + return formats.Json + default: + return formats.FormatForPath(path) + } +} + +func detectFormatFromMarkerBytes(b []byte) formats.Format { + for k, v := range sopsFormatToMarkerBytes { + if bytes.Contains(b, v) { + return k + } + } + return unsupportedFormat +} diff --git a/pkg/sops/decryptor/decryptor_test.go b/pkg/sops/decryptor/decryptor_test.go new file mode 100644 index 000000000..9f3c642ec --- /dev/null +++ b/pkg/sops/decryptor/decryptor_test.go @@ -0,0 +1,1229 @@ +/* +Copyright 2021 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package decryptor + +import ( + "bytes" + "encoding/base64" + extage "filippo.io/age" + "fmt" + . "github.com/onsi/gomega" + gt "github.com/onsi/gomega/types" + "go.mozilla.org/sops/v3" + sopsage "go.mozilla.org/sops/v3/age" + "go.mozilla.org/sops/v3/cmd/sops/formats" + "io/fs" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "os" + "path/filepath" + "regexp" + "sigs.k8s.io/kustomize/api/konfig" + "sigs.k8s.io/kustomize/api/provider" + "sigs.k8s.io/kustomize/api/resource" + kustypes "sigs.k8s.io/kustomize/api/types" + "sigs.k8s.io/yaml" + "strings" + "testing" +) + +func TestIsEncryptedSecret(t *testing.T) { + tests := []struct { + name string + object []byte + want gt.GomegaMatcher + }{ + {name: "encrypted secret", object: []byte("apiVersion: v1\nkind: Secret\nsops: true\n"), want: BeTrue()}, + {name: "decrypted secret", object: []byte("apiVersion: v1\nkind: Secret\n"), want: BeFalse()}, + {name: "other resource", object: []byte("apiVersion: v1\nkind: Deployment\n"), want: BeFalse()}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + + u := &unstructured.Unstructured{} + g.Expect(yaml.Unmarshal(tt.object, u)).To(Succeed()) + g.Expect(IsEncryptedSecret(u)).To(tt.want) + }) + } +} + +func TestDecryptor_SopsDecryptWithFormat(t *testing.T) { + t.Run("decrypt INI to INI", func(t *testing.T) { + g := NewWithT(t) + + ageID, err := extage.GenerateX25519Identity() + g.Expect(err).ToNot(HaveOccurred()) + + t.Setenv(sopsage.SopsAgeKeyEnv, ageID.String()) + + kd := &Decryptor{ + checkSopsMac: true, + } + kd.AddLocalKeyService() + + format := formats.Ini + data := []byte("[config]\nkey = value\n") + encData, err := kd.sopsEncryptWithFormat(sops.Metadata{ + KeyGroups: []sops.KeyGroup{ + {&sopsage.MasterKey{Recipient: ageID.Recipient().String()}}, + }, + }, data, format, format) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(bytes.Contains(encData, sopsFormatToMarkerBytes[format])).To(BeTrue()) + g.Expect(encData).ToNot(Equal(data)) + + out, err := kd.SopsDecryptWithFormat(encData, format, format) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(out).To(Equal(data)) + }) + + t.Run("decrypt JSON to YAML", func(t *testing.T) { + g := NewWithT(t) + + ageID, err := extage.GenerateX25519Identity() + g.Expect(err).ToNot(HaveOccurred()) + + t.Setenv(sopsage.SopsAgeKeyEnv, ageID.String()) + + kd := &Decryptor{ + checkSopsMac: true, + } + kd.AddLocalKeyService() + + inputFormat, outputFormat := formats.Json, formats.Yaml + data := []byte("{\"key\": \"value\"}\n") + encData, err := kd.sopsEncryptWithFormat(sops.Metadata{ + KeyGroups: []sops.KeyGroup{ + {&sopsage.MasterKey{Recipient: ageID.Recipient().String()}}, + }, + }, data, inputFormat, inputFormat) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(bytes.Contains(encData, sopsFormatToMarkerBytes[inputFormat])).To(BeTrue()) + + out, err := kd.SopsDecryptWithFormat(encData, inputFormat, outputFormat) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(out).To(Equal([]byte("key: value\n"))) + }) + + t.Run("invalid JSON data", func(t *testing.T) { + g := NewWithT(t) + + format := formats.Json + data, err := (&Decryptor{}).SopsDecryptWithFormat([]byte("invalid json"), format, format) + g.Expect(err).To(HaveOccurred()) + g.Expect(err.Error()).To(ContainSubstring("failed to load encrypted JSON data")) + g.Expect(data).To(BeNil()) + }) + + t.Run("no data key", func(t *testing.T) { + g := NewWithT(t) + + ageID, err := extage.GenerateX25519Identity() + g.Expect(err).ToNot(HaveOccurred()) + + kd := &Decryptor{} + kd.AddLocalKeyService() + + format := formats.Binary + encData, err := kd.sopsEncryptWithFormat(sops.Metadata{ + KeyGroups: []sops.KeyGroup{ + {&sopsage.MasterKey{Recipient: ageID.Recipient().String()}}, + }, + }, []byte("foo bar"), format, format) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(bytes.Contains(encData, sopsFormatToMarkerBytes[format])).To(BeTrue()) + + data, err := kd.SopsDecryptWithFormat(encData, format, format) + g.Expect(err).To(HaveOccurred()) + g.Expect(err.Error()).To(ContainSubstring("cannot get sops data key")) + g.Expect(data).To(BeNil()) + }) + + t.Run("with mac check", func(t *testing.T) { + g := NewWithT(t) + + ageID, err := extage.GenerateX25519Identity() + g.Expect(err).ToNot(HaveOccurred()) + + t.Setenv(sopsage.SopsAgeKeyEnv, ageID.String()) + + kd := &Decryptor{ + checkSopsMac: true, + } + kd.AddLocalKeyService() + + format := formats.Dotenv + data := []byte("key=value\n") + encData, err := kd.sopsEncryptWithFormat(sops.Metadata{ + KeyGroups: []sops.KeyGroup{ + {&sopsage.MasterKey{Recipient: ageID.Recipient().String()}}, + }, + }, data, format, format) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(bytes.Contains(encData, sopsFormatToMarkerBytes[format])).To(BeTrue()) + + out, err := kd.SopsDecryptWithFormat(encData, format, format) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(out).To(Equal(data)) + + badMAC := regexp.MustCompile("(?m)[\r\n]+^.*sops_mac=.*$") + badMACData := badMAC.ReplaceAll(encData, []byte("\nsops_mac=\n")) + out, err = kd.SopsDecryptWithFormat(badMACData, format, format) + g.Expect(err).To(HaveOccurred()) + g.Expect(err.Error()).To(ContainSubstring("failed to verify sops data integrity: expected mac 'no MAC'")) + g.Expect(out).To(BeNil()) + }) +} + +func TestDecryptor_DecryptResource(t *testing.T) { + var ( + resourceFactory = provider.NewDefaultDepProvider().GetResourceFactory() + emptyResource = resourceFactory.FromMap(map[string]interface{}{}) + ) + + newSecretResource := func(namespace, name string, data map[string]interface{}) *resource.Resource { + return resourceFactory.FromMap(map[string]interface{}{ + "apiVersion": "v1", + "kind": "Secret", + "metadata": map[string]interface{}{ + "name": name, + "namespace": namespace, + }, + "data": data, + }) + } + + t.Run("SOPS-encrypted Secret resource", func(t *testing.T) { + g := NewWithT(t) + + d := NewDecryptor("", MaxEncryptedFileSize) + d.AddLocalKeyService() + + ageID, err := extage.GenerateX25519Identity() + g.Expect(err).ToNot(HaveOccurred()) + t.Setenv(sopsage.SopsAgeKeyEnv, ageID.String()) + + secret := newSecretResource("test", "secret", map[string]interface{}{ + "key": "value", + }) + g.Expect(isSOPSEncryptedResource(secret)).To(BeFalse()) + + secretData, err := secret.MarshalJSON() + g.Expect(err).ToNot(HaveOccurred()) + + encData, err := d.sopsEncryptWithFormat(sops.Metadata{ + EncryptedRegex: "^(data|stringData)$", + KeyGroups: []sops.KeyGroup{ + {&sopsage.MasterKey{Recipient: ageID.Recipient().String()}}, + }, + }, secretData, formats.Json, formats.Json) + g.Expect(err).ToNot(HaveOccurred()) + + g.Expect(secret.UnmarshalJSON(encData)).To(Succeed()) + g.Expect(isSOPSEncryptedResource(secret)).To(BeTrue()) + + got, err := d.DecryptResource(secret) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(got).ToNot(BeNil()) + g.Expect(got.MarshalJSON()).To(Equal(secretData)) + }) + + t.Run("SOPS-encrypted binary-format Secret data field", func(t *testing.T) { + g := NewWithT(t) + + d := NewDecryptor("", MaxEncryptedFileSize) + d.AddLocalKeyService() + + ageID, err := extage.GenerateX25519Identity() + g.Expect(err).ToNot(HaveOccurred()) + t.Setenv(sopsage.SopsAgeKeyEnv, ageID.String()) + + plainData := []byte("[config]\napp = secret\n") + encData, err := d.sopsEncryptWithFormat(sops.Metadata{ + KeyGroups: []sops.KeyGroup{ + {&sopsage.MasterKey{Recipient: ageID.Recipient().String()}}, + }, + }, plainData, formats.Ini, formats.Yaml) + g.Expect(err).ToNot(HaveOccurred()) + + secret := newSecretResource("test", "secret-data", map[string]interface{}{ + "file.ini": base64.StdEncoding.EncodeToString(encData), + }) + g.Expect(isSOPSEncryptedResource(secret)).To(BeFalse()) + + got, err := d.DecryptResource(secret) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(got).ToNot(BeNil()) + g.Expect(got.GetDataMap()).To(HaveKeyWithValue("file.ini", base64.StdEncoding.EncodeToString(plainData))) + }) + + t.Run("SOPS-encrypted YAML-format Secret data field", func(t *testing.T) { + g := NewWithT(t) + + d := NewDecryptor("", MaxEncryptedFileSize) + d.AddLocalKeyService() + + ageID, err := extage.GenerateX25519Identity() + g.Expect(err).ToNot(HaveOccurred()) + t.Setenv(sopsage.SopsAgeKeyEnv, ageID.String()) + + plainData := []byte("structured:\n data:\n key: value\n") + encData, err := d.sopsEncryptWithFormat(sops.Metadata{ + KeyGroups: []sops.KeyGroup{ + {&sopsage.MasterKey{Recipient: ageID.Recipient().String()}}, + }, + }, plainData, formats.Yaml, formats.Yaml) + g.Expect(err).ToNot(HaveOccurred()) + + secret := newSecretResource("test", "secret-data", map[string]interface{}{ + "key.yaml": base64.StdEncoding.EncodeToString(encData), + }) + g.Expect(isSOPSEncryptedResource(secret)).To(BeFalse()) + + got, err := d.DecryptResource(secret) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(got).ToNot(BeNil()) + g.Expect(got.GetDataMap()).To(HaveKeyWithValue("key.yaml", base64.StdEncoding.EncodeToString(plainData))) + }) + + t.Run("SOPS-encrypted Docker config Secret", func(t *testing.T) { + g := NewWithT(t) + + d := NewDecryptor("", MaxEncryptedFileSize) + d.AddLocalKeyService() + + ageID, err := extage.GenerateX25519Identity() + g.Expect(err).ToNot(HaveOccurred()) + t.Setenv(sopsage.SopsAgeKeyEnv, ageID.String()) + + plainData := []byte(`{ + "auths": { + "my-registry.example:5000": { + "username": "tiger", + "password": "pass1234", + "email": "tiger@acme.example", + "auth": "dGlnZXI6cGFzczEyMzQ=" + } + } +}`) + encData, err := d.sopsEncryptWithFormat(sops.Metadata{ + KeyGroups: []sops.KeyGroup{ + {&sopsage.MasterKey{Recipient: ageID.Recipient().String()}}, + }, + }, plainData, formats.Json, formats.Yaml) + g.Expect(err).ToNot(HaveOccurred()) + + secret := resourceFactory.FromMap(map[string]interface{}{ + "apiVersion": "v1", + "kind": "Secret", + "metadata": map[string]interface{}{ + "name": "secret", + "namespace": "test", + }, + "type": corev1.SecretTypeDockerConfigJson, + "data": map[string]interface{}{ + corev1.DockerConfigJsonKey: base64.StdEncoding.EncodeToString(encData), + }, + }) + g.Expect(isSOPSEncryptedResource(secret)).To(BeFalse()) + + got, err := d.DecryptResource(secret) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(got).ToNot(BeNil()) + g.Expect(got.GetDataMap()).To(HaveKeyWithValue(corev1.DockerConfigJsonKey, base64.StdEncoding.EncodeToString(plainData))) + }) + + t.Run("nil resource", func(t *testing.T) { + g := NewWithT(t) + + d := NewDecryptor("", MaxEncryptedFileSize) + + got, err := d.DecryptResource(nil) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(got).To(BeNil()) + }) + + t.Run("no decryption spec", func(t *testing.T) { + g := NewWithT(t) + + d := NewDecryptor("", MaxEncryptedFileSize) + + got, err := d.DecryptResource(emptyResource.DeepCopy()) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(got).To(BeNil()) + }) + + t.Run("unimplemented decryption provider", func(t *testing.T) { + g := NewWithT(t) + + d := NewDecryptor("", MaxEncryptedFileSize) + + got, err := d.DecryptResource(emptyResource.DeepCopy()) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(got).To(BeNil()) + }) +} + +func TestDecryptor_decryptKustomizationEnvSources(t *testing.T) { + type file struct { + name string + symlink string + data []byte + originalFormat *formats.Format + encrypt bool + expectData bool + } + binaryFormat := formats.Binary + tests := []struct { + name string + wordirSuffix string + path string + files []file + secretGenerator []kustypes.SecretArgs + expectVisited []string + wantErr error + }{ + { + name: "decrypt env sources", + path: "subdir", + files: []file{ + {name: "subdir/app.env", data: []byte("var1=value1\n"), encrypt: true, expectData: true}, + // NB: Despite the file extension representing the SOPS-encrypted JSON output + // format, the original data is plain text, or "binary." + {name: "subdir/combination.json", data: []byte("The safe combination is ..."), originalFormat: &binaryFormat, encrypt: true, expectData: true}, + {name: "subdir/file.txt", data: []byte("file"), encrypt: true, expectData: true}, + {name: "secret.env", data: []byte("var2=value2\n"), encrypt: true, expectData: true}, + }, + secretGenerator: []kustypes.SecretArgs{ + { + GeneratorArgs: kustypes.GeneratorArgs{ + Name: "envSecret", + KvPairSources: kustypes.KvPairSources{ + FileSources: []string{"file.txt", "combo=combination.json"}, + EnvSources: []string{"app.env", "../secret.env"}, + }, + }, + }, + }, + expectVisited: []string{"subdir/app.env", "subdir/combination.json", "subdir/file.txt", "secret.env"}, + }, + { + name: "decryption error", + files: []file{}, + secretGenerator: []kustypes.SecretArgs{ + { + GeneratorArgs: kustypes.GeneratorArgs{ + Name: "envSecret", + KvPairSources: kustypes.KvPairSources{ + EnvSources: []string{"file.txt"}, + }, + }, + }, + }, + expectVisited: []string{}, + wantErr: &fs.PathError{Op: "lstat", Path: "file.txt", Err: fmt.Errorf("")}, + }, + { + name: "follows relative symlink within root", + path: "subdir", + files: []file{ + {name: "subdir/symlink", symlink: "../otherdir/data.env"}, + {name: "otherdir/data.env", data: []byte("key=value\n"), encrypt: true, expectData: true}, + }, + secretGenerator: []kustypes.SecretArgs{ + { + GeneratorArgs: kustypes.GeneratorArgs{ + Name: "envSecret", + KvPairSources: kustypes.KvPairSources{ + EnvSources: []string{"symlink"}, + }, + }, + }, + }, + expectVisited: []string{"otherdir/data.env"}, + }, + { + name: "error on symlink outside root", + wordirSuffix: "subdir", + path: "./", + files: []file{ + {name: "subdir/symlink", symlink: "../otherdir/data.env"}, + {name: "otherdir/data.env", data: []byte("key=value\n"), encrypt: true, expectData: false}, + }, + secretGenerator: []kustypes.SecretArgs{ + { + GeneratorArgs: kustypes.GeneratorArgs{ + Name: "envSecret", + KvPairSources: kustypes.KvPairSources{ + EnvSources: []string{"symlink"}, + }, + }, + }, + }, + wantErr: &fs.PathError{Op: "lstat", Path: "otherdir/data.env", Err: fmt.Errorf("")}, + expectVisited: []string{}, + }, + { + name: "error on reference outside root", + wordirSuffix: "subdir", + path: "./", + files: []file{ + {name: "data.env", data: []byte("key=value\n"), encrypt: true, expectData: false}, + }, + secretGenerator: []kustypes.SecretArgs{ + { + GeneratorArgs: kustypes.GeneratorArgs{ + Name: "envSecret", + KvPairSources: kustypes.KvPairSources{ + EnvSources: []string{"../data.env"}, + }, + }, + }, + }, + wantErr: &fs.PathError{Op: "lstat", Path: "data.env", Err: fmt.Errorf("")}, + expectVisited: []string{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + + tmpDir := t.TempDir() + root := filepath.Join(tmpDir, tt.wordirSuffix) + + id, err := extage.GenerateX25519Identity() + g.Expect(err).ToNot(HaveOccurred()) + t.Setenv(sopsage.SopsAgeKeyEnv, id.String()) + + d := &Decryptor{ + root: root, + } + d.AddLocalKeyService() + + for _, f := range tt.files { + fPath := filepath.Join(tmpDir, f.name) + g.Expect(os.MkdirAll(filepath.Dir(fPath), 0o700)).To(Succeed()) + if f.symlink != "" { + g.Expect(os.Symlink(f.symlink, fPath)).To(Succeed()) + continue + } + data := f.data + if f.encrypt { + var format formats.Format + if f.originalFormat != nil { + format = *f.originalFormat + } else { + format = formats.FormatForPath(f.name) + } + data, err = d.sopsEncryptWithFormat(sops.Metadata{ + KeyGroups: []sops.KeyGroup{ + {&sopsage.MasterKey{Recipient: id.Recipient().String()}}, + }, + }, f.data, format, format) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(data).ToNot(Equal(f.data)) + } + g.Expect(os.WriteFile(fPath, data, 0o644)).To(Succeed()) + } + + visited := make(map[string]struct{}, 0) + visit := d.decryptKustomizationEnvSources(visited) + kus := &kustypes.Kustomization{SecretGenerator: tt.secretGenerator} + + err = visit(root, tt.path, kus) + if tt.wantErr == nil { + g.Expect(err).ToNot(HaveOccurred()) + } else { + g.Expect(err).To(HaveOccurred()) + g.Expect(err).To(BeAssignableToTypeOf(tt.wantErr)) + g.Expect(err.Error()).To(ContainSubstring(tt.wantErr.Error())) + } + + for _, f := range tt.files { + if f.symlink != "" { + continue + } + + b, err := os.ReadFile(filepath.Join(tmpDir, f.name)) + g.Expect(err).ToNot(HaveOccurred()) + if f.expectData { + g.Expect(b).To(Equal(f.data)) + } else { + g.Expect(b).ToNot(Equal(f.data)) + } + } + + absVisited := make(map[string]struct{}, 0) + for _, v := range tt.expectVisited { + absVisited[filepath.Join(tmpDir, v)] = struct{}{} + } + g.Expect(visited).To(Equal(absVisited)) + }) + } +} + +func TestDecryptor_decryptSopsFile(t *testing.T) { + g := NewWithT(t) + + id, err := extage.GenerateX25519Identity() + g.Expect(err).ToNot(HaveOccurred()) + t.Setenv(sopsage.SopsAgeKeyEnv, id.String()) + + type file struct { + name string + symlink string + data []byte + encrypt bool + format formats.Format + expectData bool + } + tests := []struct { + name string + maxFileSize int64 + files []file + path string + format formats.Format + wantErr error + }{ + { + name: "decrypt dotenv file", + files: []file{ + {name: "app.env", data: []byte("app=key\n"), encrypt: true, format: formats.Dotenv, expectData: true}, + }, + path: "app.env", + format: formats.Dotenv, + }, + { + name: "decrypt YAML file", + files: []file{ + {name: "app.yaml", data: []byte("app: key\n"), encrypt: true, format: formats.Yaml, expectData: true}, + }, + path: "app.yaml", + format: formats.Yaml, + }, + { + name: "irregular file", + files: []file{}, + wantErr: fmt.Errorf("cannot decrypt irregular file as it has file mode type bits set"), + }, + { + name: "file exceeds max size", + maxFileSize: 5, + files: []file{ + {name: "app.env", data: []byte("app=key\n"), encrypt: true, format: formats.Dotenv, expectData: false}, + }, + path: "app.env", + wantErr: fmt.Errorf("cannot decrypt file with size (972 bytes) exceeding limit (5)"), + }, + { + name: "wrong file format", + files: []file{ + {name: "app.ini", data: []byte("[app]\nkey = value"), encrypt: true, format: formats.Ini, expectData: false}, + }, + path: "app.ini", + }, + { + name: "does not follow symlink", + files: []file{ + {name: "link", symlink: "../"}, + }, + path: "link", + wantErr: fmt.Errorf("cannot decrypt irregular file as it has file mode type bits set"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + + tmpDir := t.TempDir() + + d := &Decryptor{ + root: tmpDir, + maxFileSize: MaxEncryptedFileSize, + } + d.AddLocalKeyService() + if tt.maxFileSize != 0 { + d.maxFileSize = tt.maxFileSize + } + + for _, f := range tt.files { + fPath := filepath.Join(tmpDir, f.name) + if f.symlink != "" { + g.Expect(os.Symlink(f.symlink, fPath)).To(Succeed()) + continue + } + data := f.data + if f.encrypt { + b, err := d.sopsEncryptWithFormat(sops.Metadata{ + KeyGroups: []sops.KeyGroup{ + {&sopsage.MasterKey{Recipient: id.Recipient().String()}}, + }, + }, data, f.format, f.format) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(b).ToNot(Equal(f.data)) + data = b + } + g.Expect(os.MkdirAll(filepath.Dir(fPath), 0o700)).To(Succeed()) + g.Expect(os.WriteFile(fPath, data, 0o644)).To(Succeed()) + } + + path := filepath.Join(tmpDir, tt.path) + err := d.sopsDecryptFile(path, tt.format, tt.format) + if tt.wantErr != nil { + g.Expect(err).To(HaveOccurred()) + g.Expect(err).To(BeAssignableToTypeOf(tt.wantErr)) + g.Expect(err.Error()).To(ContainSubstring(tt.wantErr.Error())) + } else { + g.Expect(err).ToNot(HaveOccurred()) + } + for _, f := range tt.files { + if f.symlink != "" { + continue + } + + b, err := os.ReadFile(filepath.Join(tmpDir, f.name)) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(bytes.Equal(f.data, b)).To(Equal(f.expectData)) + } + }) + } +} + +func TestDecryptor_secureLoadKustomizationFile(t *testing.T) { + kusType := kustypes.TypeMeta{ + APIVersion: kustypes.KustomizationVersion, + Kind: kustypes.KustomizationKind, + } + type file struct { + name string + symlink string + data []byte + } + tests := []struct { + name string + rootSuffix string + files []file + path string + want *kustypes.Kustomization + wantErr error + }{ + { + name: "loads default kustomization file", + files: []file{ + {name: konfig.DefaultKustomizationFileName(), data: []byte("resources:\n- resource.yaml")}, + }, + path: "./", + want: &kustypes.Kustomization{ + TypeMeta: kusType, + Resources: []string{"resource.yaml"}, + }, + }, + { + name: "loads recognized kustomization file", + files: []file{ + {name: konfig.RecognizedKustomizationFileNames()[1], data: []byte("resources:\n- resource.yaml")}, + }, + path: "./", + want: &kustypes.Kustomization{ + TypeMeta: kusType, + Resources: []string{"resource.yaml"}, + }, + }, + { + name: "error on ambitious file match", + files: []file{ + {name: konfig.RecognizedKustomizationFileNames()[0], data: []byte("resources:\n- resource.yaml")}, + {name: konfig.RecognizedKustomizationFileNames()[1], data: []byte("resources:\n- resource.yaml")}, + }, + path: "./", + wantErr: fmt.Errorf("found multiple kustomization files"), + }, + { + name: "error on no file found", + files: []file{}, + path: "./", + wantErr: fmt.Errorf("no kustomization file found"), + }, + { + name: "error on symlink outside root", + rootSuffix: "subdir", + files: []file{ + {name: konfig.DefaultKustomizationFileName(), data: []byte("resources:\n- resource.yaml")}, + {name: "subdir/" + konfig.DefaultKustomizationFileName(), symlink: "../kustomization.yaml"}, + }, + wantErr: fmt.Errorf("no kustomization file found"), + }, + { + name: "error on invalid file", + files: []file{ + {name: konfig.DefaultKustomizationFileName(), data: []byte("resources")}, + }, + wantErr: fmt.Errorf("failed to unmarshal kustomization file"), + }, + { + name: "error on absolute path", + path: "/absolute/", + wantErr: fmt.Errorf("path '/absolute/' must be relative"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + + tmpDir := t.TempDir() + for _, f := range tt.files { + fPath := filepath.Join(tmpDir, f.name) + if f.symlink != "" { + g.Expect(os.Symlink(f.symlink, fPath)) + continue + } + g.Expect(os.MkdirAll(filepath.Dir(fPath), 0o700)).To(Succeed()) + g.Expect(os.WriteFile(fPath, f.data, 0o644)).To(Succeed()) + } + + root := filepath.Join(tmpDir, tt.rootSuffix) + got, err := secureLoadKustomizationFile(root, tt.path) + if wantErr := tt.wantErr; wantErr != nil { + g.Expect(err).To(HaveOccurred()) + g.Expect(err.Error()).To(ContainSubstring(wantErr.Error())) + g.Expect(got).To(BeNil()) + return + } + + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(got).To(Equal(tt.want)) + }) + } +} + +func TestDecryptor_recurseKustomizationFiles(t *testing.T) { + type kusNode struct { + path string + symlink string + resources []string + visitErr error + visited int + expectVisited int + expectCached bool + } + tests := []struct { + name string + wordirSuffix string + path string + nodes []*kusNode + wantErr error + wantErrStr string + }{ + { + name: "recurse on resources", + wordirSuffix: "foo", + path: "bar", + nodes: []*kusNode{ + { + path: "foo/bar/kustomization.yaml", + resources: []string{"../baz"}, + expectVisited: 1, + expectCached: true, + }, + { + path: "foo/baz/kustomization.yaml", + resources: []string{"/foo/bar/baz"}, + expectVisited: 1, + expectCached: true, + }, + { + path: "foo/bar/baz/kustomization.yaml", + resources: []string{}, + expectVisited: 1, + expectCached: true, + }, + }, + }, + { + name: "recursive loop", + wordirSuffix: "foo", + path: "bar", + nodes: []*kusNode{ + { + path: "foo/bar/kustomization.yaml", + resources: []string{"../baz"}, + expectVisited: 1, + expectCached: true, + }, + { + path: "foo/baz/kustomization.yaml", + resources: []string{"../foobar"}, + expectVisited: 1, + expectCached: true, + }, + { + path: "foo/foobar/kustomization.yaml", + resources: []string{"../bar"}, + expectVisited: 1, + expectCached: true, + }, + }, + }, + { + name: "absolute symlink", + path: "bar", + nodes: []*kusNode{ + { + path: "bar/baz/kustomization.yaml", + resources: []string{"../bar/absolute"}, + expectVisited: 1, + expectCached: true, + }, + { + path: "bar/absolute", + symlink: "/bar/foo/", + }, + { + path: "bar/foo/kustomization.yaml", + expectVisited: 1, + expectCached: true, + }, + }, + }, + { + name: "relative symlink", + path: "bar", + nodes: []*kusNode{ + { + path: "bar/baz/kustomization.yaml", + resources: []string{"../bar/relative"}, + expectVisited: 1, + expectCached: true, + }, + { + path: "bar/relative", + symlink: "../foo/", + }, + { + path: "bar/foo/kustomization.yaml", + expectVisited: 1, + expectCached: true, + }, + }, + }, + { + name: "recognized kustomization names", + path: "./", + nodes: []*kusNode{ + { + path: konfig.RecognizedKustomizationFileNames()[1], + resources: []string{"bar"}, + expectVisited: 1, + expectCached: true, + }, + { + path: filepath.Join("bar", konfig.RecognizedKustomizationFileNames()[0]), + resources: []string{"../baz"}, + expectVisited: 1, + expectCached: true, + }, + { + path: filepath.Join("baz", konfig.RecognizedKustomizationFileNames()[2]), + expectVisited: 1, + expectCached: true, + }, + }, + }, + { + name: "path does not exist", + path: "./invalid", + wantErr: &errRecurseIgnore{Err: fs.ErrNotExist}, + wantErrStr: "lstat invalid", + }, + { + name: "path is not a directory", + path: "./file.txt", + nodes: []*kusNode{ + { + path: "file.txt", + }, + }, + wantErr: &errRecurseIgnore{Err: fmt.Errorf("not a directory")}, + wantErrStr: "not a directory", + }, + { + name: "recurse error is returned", + path: "/foo", + nodes: []*kusNode{ + { + path: "foo/kustomization.yaml", + resources: []string{"../baz"}, + expectVisited: 1, + expectCached: true, + }, + { + path: "baz/wrongfile.yaml", + expectVisited: 0, + expectCached: false, + }, + }, + wantErr: fmt.Errorf("no kustomization file found"), + }, + { + name: "recurse ignores errRecurseIgnore", + path: "/foo", + nodes: []*kusNode{ + { + path: "foo/kustomization.yaml", + resources: []string{"../baz"}, + expectVisited: 1, + expectCached: true, + }, + { + path: "baz", + expectVisited: 0, + expectCached: false, + }, + }, + }, + { + name: "remote build references are ignored", + path: "/foo", + nodes: []*kusNode{ + { + path: "foo/kustomization.yaml", + resources: []string{ + "../baz", + "https://github.com/kubernetes-sigs/kustomize//examples/multibases/dev/?ref=v1.0.6", + }, + expectVisited: 1, + expectCached: true, + }, + { + path: "baz/kustomization.yaml", + resources: []string{ + "github.com/Liujingfang1/mysql?ref=test", + }, + expectVisited: 1, + expectCached: true, + }, + }, + }, + { + name: "visit error is returned", + path: "/", + nodes: []*kusNode{ + { + path: "kustomization.yaml", + resources: []string{ + "baz", + }, + expectVisited: 1, + expectCached: true, + }, + { + path: "baz/kustomization.yaml", + visitErr: fmt.Errorf("visit error"), + expectVisited: 1, + expectCached: true, + }, + }, + wantErr: fmt.Errorf("visit error"), + wantErrStr: "visit error", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + + tmpDir := t.TempDir() + + for _, n := range tt.nodes { + path := filepath.Join(tmpDir, n.path) + if n.symlink != "" { + g.Expect(os.Symlink(strings.Replace(n.symlink, "", tmpDir, 1), path)).To(Succeed()) + return + } + kus := kustypes.Kustomization{ + TypeMeta: kustypes.TypeMeta{ + APIVersion: kustypes.KustomizationVersion, + Kind: kustypes.KustomizationKind, + }, + } + for _, res := range n.resources { + res = strings.Replace(res, "", tmpDir, 1) + kus.Resources = append(kus.Resources, res) + } + b, err := yaml.Marshal(kus) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(os.MkdirAll(filepath.Dir(path), 0o700)).To(Succeed()) + g.Expect(os.WriteFile(path, b, 0o644)) + } + + visit := func(root, path string, kus *kustypes.Kustomization) error { + if filepath.IsAbs(path) { + path = stripRoot(root, path) + } + for _, n := range tt.nodes { + if dir := filepath.Dir(n.path); filepath.Join(tt.wordirSuffix, path) != dir { + continue + } + n.visited++ + if n.visitErr != nil { + return n.visitErr + } + } + return nil + } + + visited := make(map[string]struct{}, 0) + err := recurseKustomizationFiles(filepath.Join(tmpDir, tt.wordirSuffix), tt.path, visit, visited) + if tt.wantErr != nil { + g.Expect(err).To(HaveOccurred()) + g.Expect(err).To(BeAssignableToTypeOf(tt.wantErr)) + if tt.wantErrStr != "" { + g.Expect(err.Error()).To(ContainSubstring(tt.wantErrStr)) + } + return + } + + g.Expect(err).ToNot(HaveOccurred()) + for _, n := range tt.nodes { + g.Expect(n.visited).To(Equal(n.expectVisited), n.path) + + haveCache := HaveKey(filepath.Dir(filepath.Join(tmpDir, n.path))) + if n.expectCached { + g.Expect(visited).To(haveCache) + } else { + g.Expect(visited).ToNot(haveCache) + } + } + }) + } +} + +func TestDecryptor_isSOPSEncryptedResource(t *testing.T) { + g := NewWithT(t) + + resourceFactory := provider.NewDefaultDepProvider().GetResourceFactory() + encrypted := resourceFactory.FromMap(map[string]interface{}{ + "sops": map[string]string{ + "mac": "some mac value", + }, + }) + empty := resourceFactory.FromMap(map[string]interface{}{}) + + g.Expect(isSOPSEncryptedResource(encrypted)).To(BeTrue()) + g.Expect(isSOPSEncryptedResource(empty)).To(BeFalse()) +} + +func TestDecryptor_secureAbsPath(t *testing.T) { + tests := []struct { + name string + root string + path string + wantAbs string + wantRel string + wantErr bool + }{ + { + name: "absolute to root", + root: "/wordir/", + path: "/wordir/foo/", + wantAbs: "/wordir/foo", + wantRel: "foo", + }, + { + name: "relative to root", + root: "/wordir", + path: "./foo", + wantAbs: "/wordir/foo", + wantRel: "foo", + }, + { + name: "illegal traverse", + root: "/wordir/foo", + path: "../../bar", + wantAbs: "/wordir/foo/bar", + wantRel: "bar", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + + gotAbs, gotRel, err := securePaths(tt.root, tt.path) + if tt.wantErr { + g.Expect(err).To(HaveOccurred()) + g.Expect(gotAbs).To(BeEmpty()) + g.Expect(gotRel).To(BeEmpty()) + return + } + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(gotAbs).To(Equal(tt.wantAbs)) + g.Expect(gotRel).To(Equal(tt.wantRel)) + }) + } +} + +func TestDecryptor_formatForPath(t *testing.T) { + tests := []struct { + name string + path string + want formats.Format + }{ + { + name: "docker config", + path: corev1.DockerConfigJsonKey, + want: formats.Json, + }, + { + name: "fallback", + path: "foo.yaml", + want: formats.Yaml, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + + g.Expect(formatForPath(tt.path)).To(Equal(tt.want)) + }) + } +} + +func TestDecryptor_detectFormatFromMarkerBytes(t *testing.T) { + tests := []struct { + name string + b []byte + want formats.Format + }{ + { + name: "detects format", + b: bytes.Join([][]byte{[]byte("random other bytes"), sopsFormatToMarkerBytes[formats.Yaml], []byte("more random bytes")}, []byte(" ")), + want: formats.Yaml, + }, + { + name: "returns unsupported format", + b: []byte("no marker bytes present"), + want: unsupportedFormat, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := detectFormatFromMarkerBytes(tt.b); got != tt.want { + t.Errorf("detectFormatFromMarkerBytes() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/sops/sops_decryptor.go b/pkg/sops/sops_decryptor.go deleted file mode 100644 index 474d9872d..000000000 --- a/pkg/sops/sops_decryptor.go +++ /dev/null @@ -1,21 +0,0 @@ -package sops - -import ( - "fmt" - "go.mozilla.org/sops/v3/cmd/sops/formats" - "go.mozilla.org/sops/v3/decrypt" -) - -type SopsDecrypter interface { - SopsDecryptWithFormat(data []byte, inputFormat, outputFormat formats.Format) ([]byte, error) -} - -type LocalSopsDecrypter struct { -} - -func (_ LocalSopsDecrypter) SopsDecryptWithFormat(data []byte, inputFormat, outputFormat formats.Format) ([]byte, error) { - if inputFormat != outputFormat { - return nil, fmt.Errorf("inputFormat and outputFormat must be equal") - } - return decrypt.DataWithFormat(data, inputFormat) -} diff --git a/pkg/sops/utils.go b/pkg/sops/utils.go index 5b05e20ca..ab6f881ce 100644 --- a/pkg/sops/utils.go +++ b/pkg/sops/utils.go @@ -5,6 +5,7 @@ import ( "context" "errors" "fmt" + "github.com/kluctl/kluctl/v2/pkg/sops/decryptor" "github.com/kluctl/kluctl/v2/pkg/utils" "go.mozilla.org/sops/v3" "go.mozilla.org/sops/v3/cmd/sops/formats" @@ -15,7 +16,7 @@ func IsMaybeSopsFile(s []byte) bool { return bytes.Index(s, []byte("sops")) != -1 } -func MaybeDecrypt(decrypter SopsDecrypter, encrypted []byte, inputFormat, outputFormat formats.Format) ([]byte, bool, error) { +func MaybeDecrypt(decrypter *decryptor.Decryptor, encrypted []byte, inputFormat, outputFormat formats.Format) ([]byte, bool, error) { if decrypter == nil { return encrypted, false, nil } @@ -34,11 +35,11 @@ func MaybeDecrypt(decrypter SopsDecrypter, encrypted []byte, inputFormat, output return d, true, nil } -func MaybeDecryptFile(decrypter SopsDecrypter, path string) error { +func MaybeDecryptFile(decrypter *decryptor.Decryptor, path string) error { return MaybeDecryptFileTo(decrypter, path, path) } -func MaybeDecryptFileTo(decrypter SopsDecrypter, path string, to string) error { +func MaybeDecryptFileTo(decrypter *decryptor.Decryptor, path string, to string) error { format := formats.FormatForPath(path) file, err := os.ReadFile(path) @@ -61,7 +62,7 @@ func MaybeDecryptFileTo(decrypter SopsDecrypter, path string, to string) error { return nil } -func MaybeDecryptFileToTmp(ctx context.Context, decrypter SopsDecrypter, path string) (string, error) { +func MaybeDecryptFileToTmp(ctx context.Context, decrypter *decryptor.Decryptor, path string) (string, error) { tmp, err := os.CreateTemp(utils.GetTmpBaseDir(ctx), "sops-decrypt-") if err != nil { return "", err diff --git a/pkg/vars/vars_loader.go b/pkg/vars/vars_loader.go index 9f070c54d..39161f372 100644 --- a/pkg/vars/vars_loader.go +++ b/pkg/vars/vars_loader.go @@ -10,6 +10,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/repocache" "github.com/kluctl/kluctl/v2/pkg/sops" + "github.com/kluctl/kluctl/v2/pkg/sops/decryptor" "github.com/kluctl/kluctl/v2/pkg/types" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/utils" @@ -32,14 +33,14 @@ type usernamePassword struct { type VarsLoader struct { ctx context.Context k *k8s.K8sCluster - sops sops.SopsDecrypter + sops *decryptor.Decryptor rp *repocache.GitRepoCache aws aws.AwsClientFactory credentialsCache map[string]usernamePassword } -func NewVarsLoader(ctx context.Context, k *k8s.K8sCluster, sops sops.SopsDecrypter, rp *repocache.GitRepoCache, aws aws.AwsClientFactory) *VarsLoader { +func NewVarsLoader(ctx context.Context, k *k8s.K8sCluster, sops *decryptor.Decryptor, rp *repocache.GitRepoCache, aws aws.AwsClientFactory) *VarsLoader { return &VarsLoader{ ctx: ctx, k: k, diff --git a/pkg/vars/vars_loader_test.go b/pkg/vars/vars_loader_test.go index d89772654..f8320e8fd 100644 --- a/pkg/vars/vars_loader_test.go +++ b/pkg/vars/vars_loader_test.go @@ -2,6 +2,7 @@ package vars import ( "context" + "github.com/kluctl/kluctl/v2/pkg/sops/decryptor" "io" "net/http" "net/http/httptest" @@ -19,7 +20,6 @@ import ( ssh_pool "github.com/kluctl/kluctl/v2/pkg/git/ssh-pool" "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/repocache" - "github.com/kluctl/kluctl/v2/pkg/sops" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" @@ -48,7 +48,10 @@ func testVarsLoader(t *testing.T, test func(vl *VarsLoader, vc *VarsCtx, aws *aw grc := newRP(t) fakeAws := aws.NewFakeClientFactory() - vl := NewVarsLoader(context.TODO(), k, &sops.LocalSopsDecrypter{}, grc, fakeAws) + d := decryptor.NewDecryptor("", decryptor.MaxEncryptedFileSize) + d.AddLocalKeyService() + + vl := NewVarsLoader(context.TODO(), k, d, grc, fakeAws) vc := NewVarsCtx(newJinja2Must(t)) test(vl, vc, fakeAws) From 9627fbc354debd8febaa7565ce76619051f54df9 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 23 Dec 2022 15:14:55 +0100 Subject: [PATCH 1345/2916] fix: Upgrade SOPS to the develop branch This also upgrades SOPS to use github.com/aws/aws-sdk-go-v2 for AWS KMS, which is needed to support SSO sessions. --- go.mod | 32 +++++++++++------------ go.sum | 82 ++++++++++++++++++++++++++++------------------------------ 2 files changed, 54 insertions(+), 60 deletions(-) diff --git a/go.mod b/go.mod index f4b2b9d17..0368e53c8 100644 --- a/go.mod +++ b/go.mod @@ -65,7 +65,7 @@ require ( github.com/huandu/xstrings v1.4.0 github.com/onsi/gomega v1.24.1 github.com/otiai10/copy v1.9.0 - go.mozilla.org/sops/v3 v3.7.3 + go.mozilla.org/sops/v3 v3.7.4-0.20220901181616-9124783930b1 sigs.k8s.io/controller-runtime v0.14.0 sigs.k8s.io/kustomize/api v0.12.1 sigs.k8s.io/yaml v1.3.0 @@ -74,18 +74,15 @@ require ( require ( cloud.google.com/go/compute v1.14.0 // indirect cloud.google.com/go/compute/metadata v0.2.2 // indirect - github.com/Azure/azure-sdk-for-go v63.3.0+incompatible // indirect + cloud.google.com/go/iam v0.8.0 // indirect + cloud.google.com/go/kms v1.6.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.5.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.5.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Azure/go-autorest v14.2.0+incompatible // indirect - github.com/Azure/go-autorest/autorest v0.11.27 // indirect - github.com/Azure/go-autorest/autorest/adal v0.9.20 // indirect - github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 // indirect - github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 // indirect - github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect - github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect - github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect - github.com/Azure/go-autorest/logger v0.2.1 // indirect - github.com/Azure/go-autorest/tracing v0.6.0 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v0.5.2 // indirect github.com/BurntSushi/toml v1.2.1 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect @@ -97,7 +94,6 @@ require ( github.com/armon/go-metrics v0.4.1 // indirect github.com/armon/go-radix v1.0.0 // indirect github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect - github.com/aws/aws-sdk-go v1.43.43 // indirect github.com/aws/aws-sdk-go-v2 v1.17.3 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.13.7 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.21 // indirect @@ -105,6 +101,7 @@ require ( github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.21 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.3.28 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.21 // indirect + github.com/aws/aws-sdk-go-v2/service/kms v1.17.5 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.11.28 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.11 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.17.7 // indirect @@ -116,8 +113,8 @@ require ( github.com/chai2010/gettext-go v1.0.2 // indirect github.com/cloudflare/circl v1.3.0 // indirect github.com/containerd/containerd v1.6.13 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/dimchansky/utfbom v1.1.1 // indirect github.com/docker/cli v20.10.21+incompatible // indirect github.com/docker/docker v20.10.21+incompatible // indirect github.com/docker/docker-credential-helpers v0.7.0 // indirect @@ -144,6 +141,7 @@ require ( github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/golang/snappy v0.0.4 // indirect @@ -176,14 +174,13 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/vault/sdk v0.6.1 // indirect github.com/hashicorp/yamux v0.1.1 // indirect - github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect - github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jmoiron/sqlx v1.3.5 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.15.13 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/leodido/go-urn v1.2.1 // indirect @@ -213,6 +210,7 @@ require ( github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pierrec/lz4 v2.6.1+incompatible // indirect github.com/pjbgf/sha1cd v0.2.3 // indirect + github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.14.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect @@ -229,6 +227,7 @@ require ( github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/subosito/gotenv v1.4.1 // indirect + github.com/urfave/cli v1.22.7 // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect @@ -252,7 +251,6 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect - gopkg.in/urfave/cli.v1 v1.20.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gotest.tools/v3 v3.2.0 // indirect k8s.io/apiserver v0.26.0 // indirect diff --git a/go.sum b/go.sum index af3d44e15..cb57720fb 100644 --- a/go.sum +++ b/go.sum @@ -34,6 +34,10 @@ cloud.google.com/go/compute/metadata v0.2.2/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxB cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/iam v0.8.0 h1:E2osAkZzxI/+8pZcxVLcDtAQx/u+hZXVryUaYQ5O0Kk= +cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= +cloud.google.com/go/kms v1.6.0 h1:OWRZzrPmOZUzurjI2FBGtgY2mB1WaJkqhw6oIwSj0Yg= +cloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0= cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= @@ -48,37 +52,22 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= filippo.io/age v1.0.0 h1:V6q14n0mqYU3qKFkZ6oOaF9oXneOviS3ubXsSVBRSzc= filippo.io/age v1.0.0/go.mod h1:PaX+Si/Sd5G8LgfCwldsSba3H1DDQZhIhFGkhbHaBq8= -github.com/Azure/azure-sdk-for-go v63.3.0+incompatible h1:INepVujzUrmArRZjDLHbtER+FkvCoEwyRCXGqOlmDII= -github.com/Azure/azure-sdk-for-go v63.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.1 h1:tz19qLF65vuu2ibfTqGVJxG/zZAI27NEIIbvAOQwYbw= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.1/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0 h1:QkAcEIAKbNL4KoFr4SathZPhDhF4mVwpBMFlYjyAqy8= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0/go.mod h1:bhXu1AjYL+wutSL/kpSq6s7733q2Rb0yuot9Zgfqa/0= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 h1:jp0dGvZ7ZK0mgqnTSClMxa5xuRL7NZgHameVYF6BurY= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w= +github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.5.1 h1:AXFNQ6kLaPODEpGSMWjmbkt6iP7fa1DIEzjx6JRFC9U= +github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.5.1/go.mod h1:yOYJv0tO0TTNcje8ahhBHQcdAiYqRIp5fsog5FPefr4= +github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.5.0 h1:9cn6ICCGiWFNA/slKnrkf+ENyvaCRKHtuoGtnLIAgao= +github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.5.0/go.mod h1:9V2j0jn9jDEkCkv8w/bKTNppX/d0FVA1ud77xCIP4KA= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= -github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest v0.11.24/go.mod h1:G6kyRlFnTuSbEYkQGawPfsCswgme4iYf6rfSKUDzbCc= -github.com/Azure/go-autorest/autorest v0.11.27 h1:F3R3q42aWytozkV8ihzcgMO4OA4cuqr3bNlsEuF6//A= -github.com/Azure/go-autorest/autorest v0.11.27/go.mod h1:7l8ybrIdUmGqZMTD0sRtAr8NvbHjfofbf8RSP2q7w7U= -github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= -github.com/Azure/go-autorest/autorest/adal v0.9.20 h1:gJ3E98kMpFB1MFqQCvA1yFab8vthOeD4VlFRQULxahg= -github.com/Azure/go-autorest/autorest/adal v0.9.20/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= -github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 h1:P6bYXFoao05z5uhOQzbC3Qd8JqF3jUoocoTeIxkp2cA= -github.com/Azure/go-autorest/autorest/azure/auth v0.5.11/go.mod h1:84w/uV8E37feW2NCJ08uT9VBfjfUHpgLVnG2InYD6cg= -github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 h1:0W/yGmFdTIT77fvdlGZ0LMISoLHFJ7Tx4U0yeB+uFs4= -github.com/Azure/go-autorest/autorest/azure/cli v0.4.5/go.mod h1:ADQAXrkgm7acgWVUNamOgh8YNrv4p27l3Wc55oVfpzg= -github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= -github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= -github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw= -github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU= -github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk= -github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= -github.com/Azure/go-autorest/autorest/validation v0.3.1 h1:AgyqjAd94fwNAoTjl/WQXg4VvFeRFpO+UhNyRXqF1ac= -github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E= -github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= -github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= -github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= -github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= +github.com/AzureAD/microsoft-authentication-library-for-go v0.5.2 h1:BGX4OiGP9htYSd6M3pAZctcUUSruhIAUVkv2X0Cn9yE= +github.com/AzureAD/microsoft-authentication-library-for-go v0.5.2/go.mod h1:Vt9sXTKwMyGcOxSmLDMnGPgqsUg7m8pe215qMLrDXw4= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= @@ -129,8 +118,7 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/aws/aws-sdk-go v1.43.43 h1:1L06qzQvl4aC3Skfh5rV7xVhGHjIZoHcqy16NoyQ1o4= -github.com/aws/aws-sdk-go v1.43.43/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go-v2 v1.16.7/go.mod h1:6CpKuLXg2w7If3ABZCl/qZ6rEgwtjZTn4eAf4RcEyuw= github.com/aws/aws-sdk-go-v2 v1.17.3 h1:shN7NlnVzvDUgPQ+1rLMSxY8OWRNDRYtiqe0p/PgrhY= github.com/aws/aws-sdk-go-v2 v1.17.3/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= github.com/aws/aws-sdk-go-v2/config v1.18.7 h1:V94lTcix6jouwmAsgQMAEBozVAGJMFhVj+6/++xfe3E= @@ -139,14 +127,18 @@ github.com/aws/aws-sdk-go-v2/credentials v1.13.7 h1:qUUcNS5Z1092XBFT66IJM7mYkMwg github.com/aws/aws-sdk-go-v2/credentials v1.13.7/go.mod h1:AdCcbZXHQCjJh6NaH3pFaw8LUeBFn5+88BZGMVGuBT8= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.21 h1:j9wi1kQ8b+e0FBVHxCqCGo4kxDU175hoDHcWAi0sauU= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.21/go.mod h1:ugwW57Z5Z48bpvUyZuaPy4Kv+vEfJWnIrky7RmkBvJg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.14/go.mod h1:kdjrMwHwrC3+FsKhNcCMJ7tUVj/8uSD5CZXeQ4wV6fM= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.27 h1:I3cakv2Uy1vNmmhRQmFptYDxOvBnwCdNwyw63N0RaRU= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.27/go.mod h1:a1/UpzeyBBerajpnP5nGZa9mGzsBn5cOKxm6NWQsvoI= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.8/go.mod h1:ZIV8GYoC6WLBW5KGs+o4rsc65/ozd+eQ0L31XF5VDwk= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.21 h1:5NbbMrIzmUn/TXFqAle6mgrH5m9cOvMLRGL7pnG8tRE= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.21/go.mod h1:+Gxn8jYn5k9ebfHEqlhrMirFjSW0v0C9fI+KN5vk2kE= github.com/aws/aws-sdk-go-v2/internal/ini v1.3.28 h1:KeTxcGdNnQudb46oOl4d90f2I33DF/c6q3RnZAmvQdQ= github.com/aws/aws-sdk-go-v2/internal/ini v1.3.28/go.mod h1:yRZVr/iT0AqyHeep00SZ4YfBAKojXz08w3XMBscdi0c= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.21 h1:5C6XgTViSb0bunmU57b3CT+MhxULqHH2721FVA+/kDM= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.21/go.mod h1:lRToEJsn+DRA9lW4O9L9+/3hjTkUzlzyzHqn8MTds5k= +github.com/aws/aws-sdk-go-v2/service/kms v1.17.5 h1:DkubF+BSEy0uX59+pYySzWFReN3fCcXobIO8L5Phh24= +github.com/aws/aws-sdk-go-v2/service/kms v1.17.5/go.mod h1:ubAtMGRUMVv5kX8lpbeDguxZ64pR4kXTGApY4sCM0io= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.16.11 h1:77V7vnw/NC4DORHVgA97+Ky2p1ri0+ZVYXh6ordUZU0= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.16.11/go.mod h1:jAeo/PdIJZuDSwsvxJS94G4d6h8tStj7WXVuKwLHWU8= github.com/aws/aws-sdk-go-v2/service/sso v1.11.28 h1:gItLq3zBYyRDPmqAClgzTH8PBjDQGeyptYGHIwtYYNA= @@ -155,6 +147,7 @@ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.11 h1:KCacyVSs/wlcPGx37hcbT3I github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.11/go.mod h1:TZSH7xLO7+phDtViY/KUp9WGCJMQkLJ/VpgkTFd5gh8= github.com/aws/aws-sdk-go-v2/service/sts v1.17.7 h1:9Mtq1KM6nD8/+HStvWcvYnixJ5N85DX+P+OY3kI3W2k= github.com/aws/aws-sdk-go-v2/service/sts v1.17.7/go.mod h1:+lGbb3+1ugwKrNTWcf2RT05Xmp543B06zDFTwiTLp7I= +github.com/aws/smithy-go v1.12.0/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8= github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -204,7 +197,9 @@ github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvA github.com/containerd/stargz-snapshotter/estargz v0.12.1 h1:+7nYmHJb0tEkcRaAW+MHqoKaJYZmkikupxCqVtmPuY0= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= @@ -214,9 +209,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= -github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= -github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= github.com/distribution/distribution/v3 v3.0.0-20220526142353-ffbd94cbe269 h1:hbCT8ZPPMqefiAWD2ZKjn7ypokIGViTvBBg/ExLSdCk= +github.com/dnaeon/go-vcr v1.1.0 h1:ReYa/UBrRyQdant9B4fNHGoCNKw6qh6P0fsdGmZpR7c= github.com/docker/cli v20.10.21+incompatible h1:qVkgyYUnOLQ98LtXBrwd/duVqPT2X4SHndOuGsfwyhU= github.com/docker/cli v20.10.21+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= @@ -336,8 +330,9 @@ github.com/godror/godror v0.24.2/go.mod h1:wZv/9vPiUib6tkoDl+AZ/QLf5YZgMravZ7jxH github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= -github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU= github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= @@ -505,8 +500,6 @@ github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= -github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef h1:A9HsByNhogrvm9cWb28sjiS3i7tcKCkflWFEkHfuAgM= -github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= @@ -528,9 +521,7 @@ github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg= github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= -github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= @@ -571,6 +562,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= @@ -660,6 +653,7 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= +github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= @@ -702,6 +696,9 @@ github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9F github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pjbgf/sha1cd v0.2.3 h1:uKQP/7QOzNtKYH7UTohZLcjF5/55EnTw0jO/Ru4jZwI= github.com/pjbgf/sha1cd v0.2.3/go.mod h1:HOK9QrgzdHpbc2Kzip0Q1yi3M2MFGPADtR6HjG65m5M= +github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -814,6 +811,8 @@ github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69 github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/urfave/cli v1.22.7 h1:aXiFAgRugfJ27UFDsGJ9DB2FvTC73hlVXFSqq5bo9eU= +github.com/urfave/cli v1.22.7/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/vbatts/tar-split v0.11.2 h1:Via6XqJr0hceW4wff3QRzD5gAk/tatMw/4ZA7cTlIME= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= @@ -849,8 +848,8 @@ go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3 go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.mozilla.org/gopgagent v0.0.0-20170926210634-4d7ea76ff71a h1:N7VD+PwpJME2ZfQT8+ejxwA4Ow10IkGbU0MGf94ll8k= go.mozilla.org/gopgagent v0.0.0-20170926210634-4d7ea76ff71a/go.mod h1:YDKUvO0b//78PaaEro6CAPH6NqohCmL2Cwju5XI2HoE= -go.mozilla.org/sops/v3 v3.7.3 h1:CYx02LnWTATWv6NqWJIt4JCKVKSnGV+MsRiDpvwWQhg= -go.mozilla.org/sops/v3 v3.7.3/go.mod h1:AutdccISG5Nt/faUigaKPU9aGmhyZuCyUiSx5YCa1O8= +go.mozilla.org/sops/v3 v3.7.4-0.20220901181616-9124783930b1 h1:4t4bmkosajncfGrQGfg6wXhyf96KdDzx+QxRd+rpQPM= +go.mozilla.org/sops/v3 v3.7.4-0.20220901181616-9124783930b1/go.mod h1:eC/nw/CfC/v4aFitUY1JyiL+0dP6Drun/cpS/c9mypQ= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -971,7 +970,6 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= @@ -1058,13 +1056,13 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1296,8 +1294,6 @@ gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0= -gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From def1dd4e3bc3144d2970d0566c8d2bdde9b26938 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 27 Dec 2022 13:59:34 +0100 Subject: [PATCH 1346/2916] fix: Disable decryptor tests on windows --- pkg/sops/decryptor/decryptor_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/sops/decryptor/decryptor_test.go b/pkg/sops/decryptor/decryptor_test.go index 9f3c642ec..283901abe 100644 --- a/pkg/sops/decryptor/decryptor_test.go +++ b/pkg/sops/decryptor/decryptor_test.go @@ -1,3 +1,5 @@ +//go:build !windows + /* Copyright 2021 The Flux authors From b528623135b4d383de71a2707cdeb529902a6f42 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Dec 2022 14:16:43 +0000 Subject: [PATCH 1347/2916] chore(deps): Bump sigs.k8s.io/controller-runtime from 0.14.0 to 0.14.1 Bumps [sigs.k8s.io/controller-runtime](https://github.com/kubernetes-sigs/controller-runtime) from 0.14.0 to 0.14.1. - [Release notes](https://github.com/kubernetes-sigs/controller-runtime/releases) - [Changelog](https://github.com/kubernetes-sigs/controller-runtime/blob/master/RELEASE.md) - [Commits](https://github.com/kubernetes-sigs/controller-runtime/compare/v0.14.0...v0.14.1) --- updated-dependencies: - dependency-name: sigs.k8s.io/controller-runtime dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0368e53c8..48d1c83b1 100644 --- a/go.mod +++ b/go.mod @@ -66,7 +66,7 @@ require ( github.com/onsi/gomega v1.24.1 github.com/otiai10/copy v1.9.0 go.mozilla.org/sops/v3 v3.7.4-0.20220901181616-9124783930b1 - sigs.k8s.io/controller-runtime v0.14.0 + sigs.k8s.io/controller-runtime v0.14.1 sigs.k8s.io/kustomize/api v0.12.1 sigs.k8s.io/yaml v1.3.0 ) diff --git a/go.sum b/go.sum index cb57720fb..6a9d1513e 100644 --- a/go.sum +++ b/go.sum @@ -1349,8 +1349,8 @@ oras.land/oras-go v1.2.1/go.mod h1:3N11Z5E3c4ZzOjroCl1RtAdB4yNAYl7A27j2SVf913A= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/controller-runtime v0.14.0 h1:ju2xsov5Ara6FoQuddg+az+rAxsUsTYn2IYyEKCTyDc= -sigs.k8s.io/controller-runtime v0.14.0/go.mod h1:GaRkrY8a7UZF0kqFFbUKG7n9ICiTY5T55P1RiE3UZlU= +sigs.k8s.io/controller-runtime v0.14.1 h1:vThDes9pzg0Y+UbCPY3Wj34CGIYPgdmspPm2GIpxpzM= +sigs.k8s.io/controller-runtime v0.14.1/go.mod h1:GaRkrY8a7UZF0kqFFbUKG7n9ICiTY5T55P1RiE3UZlU= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/kustomize/api v0.12.1 h1:7YM7gW3kYBwtKvoY216ZzY+8hM+lV53LUayghNRJ0vM= From 0fc95ad364104ced0c5db0c313c6d2be5d18bdc2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Dec 2022 22:07:22 +0000 Subject: [PATCH 1348/2916] chore(deps): Bump github.com/onsi/gomega from 1.24.1 to 1.24.2 Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.24.1 to 1.24.2. - [Release notes](https://github.com/onsi/gomega/releases) - [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md) - [Commits](https://github.com/onsi/gomega/compare/v1.24.1...v1.24.2) --- updated-dependencies: - dependency-name: github.com/onsi/gomega dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 0368e53c8..6e2520ea5 100644 --- a/go.mod +++ b/go.mod @@ -63,7 +63,7 @@ require ( github.com/go-logr/logr v1.2.3 github.com/hashicorp/go-multierror v1.1.1 github.com/huandu/xstrings v1.4.0 - github.com/onsi/gomega v1.24.1 + github.com/onsi/gomega v1.24.2 github.com/otiai10/copy v1.9.0 go.mozilla.org/sops/v3 v3.7.4-0.20220901181616-9124783930b1 sigs.k8s.io/controller-runtime v0.14.0 diff --git a/go.sum b/go.sum index cb57720fb..e1e13386c 100644 --- a/go.sum +++ b/go.sum @@ -665,9 +665,9 @@ github.com/ohler55/ojg v1.15.0/go.mod h1:7Ghirupn8NC8hSSDpI0gcjorPxj+vSVIONDWfli github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo/v2 v2.6.0 h1:9t9b9vRUbFq3C4qKFCGkVuq/fIHji802N1nrtkh1mNc= -github.com/onsi/gomega v1.24.1 h1:KORJXNNTzJXzu4ScJWssJfJMnJ+2QJqhoQSRwNlze9E= -github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM= +github.com/onsi/ginkgo/v2 v2.6.1 h1:1xQPCjcqYw/J5LchOcp4/2q/jzJFjiAOc25chhnDw+Q= +github.com/onsi/gomega v1.24.2 h1:J/tulyYK6JwBldPViHJReihxxZ+22FHs0piGjQAvoUE= +github.com/onsi/gomega v1.24.2/go.mod h1:gs3J10IS7Z7r7eXRoNJIrNqU4ToQukCJhFtKrWgHWnk= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034= From aa440a3043c985e3faea5f7003ffe9834332fb96 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Dec 2022 22:07:42 +0000 Subject: [PATCH 1349/2916] chore(deps): Bump filippo.io/age from 1.0.0 to 1.1.1 Bumps [filippo.io/age](https://github.com/FiloSottile/age) from 1.0.0 to 1.1.1. - [Release notes](https://github.com/FiloSottile/age/releases) - [Commits](https://github.com/FiloSottile/age/compare/v1.0.0...v1.1.1) --- updated-dependencies: - dependency-name: filippo.io/age dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0368e53c8..82f4a3c6b 100644 --- a/go.mod +++ b/go.mod @@ -57,7 +57,7 @@ require ( ) require ( - filippo.io/age v1.0.0 + filippo.io/age v1.1.1 github.com/aws/aws-sdk-go-v2/config v1.18.7 github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.16.11 github.com/go-logr/logr v1.2.3 diff --git a/go.sum b/go.sum index cb57720fb..44ad1ecdc 100644 --- a/go.sum +++ b/go.sum @@ -50,8 +50,8 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -filippo.io/age v1.0.0 h1:V6q14n0mqYU3qKFkZ6oOaF9oXneOviS3ubXsSVBRSzc= -filippo.io/age v1.0.0/go.mod h1:PaX+Si/Sd5G8LgfCwldsSba3H1DDQZhIhFGkhbHaBq8= +filippo.io/age v1.1.1 h1:pIpO7l151hCnQ4BdyBujnGP2YlUo0uj6sAVNHGBvXHg= +filippo.io/age v1.1.1/go.mod h1:l03SrzDUrBkdBx8+IILdnn2KZysqQdbEBUQ4p3sqEQE= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.1 h1:tz19qLF65vuu2ibfTqGVJxG/zZAI27NEIIbvAOQwYbw= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.1/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0 h1:QkAcEIAKbNL4KoFr4SathZPhDhF4mVwpBMFlYjyAqy8= From b5536adc70f04f51a54bfdffaa0276fce1cc0287 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 28 Dec 2022 17:47:39 +0100 Subject: [PATCH 1350/2916] fix: Merge external args after target args External args are meant to override target args. --- pkg/kluctl_project/target_context.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index f07436014..f1619b2b9 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -171,7 +171,6 @@ func (p *LoadedKluctlProject) buildVars(target *types.Target, externalArgs *uo.U allArgs := uo.New() - allArgs.Merge(externalArgs) if target != nil { if target.Args != nil { allArgs.Merge(target.Args) @@ -182,6 +181,7 @@ func (p *LoadedKluctlProject) buildVars(target *types.Target, externalArgs *uo.U } } } + allArgs.Merge(externalArgs) deprecatedArgs, err := deployment.LoadDeprecatedDeploymentArgs(p.ctx, p.ProjectDir, varsCtx, allArgs) if err != nil { From 9ae41582a28cce558ed89e5ed98e9ce39eec828a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Dec 2022 22:07:51 +0000 Subject: [PATCH 1351/2916] chore(deps): Bump github.com/mattn/go-isatty from 0.0.16 to 0.0.17 Bumps [github.com/mattn/go-isatty](https://github.com/mattn/go-isatty) from 0.0.16 to 0.0.17. - [Release notes](https://github.com/mattn/go-isatty/releases) - [Commits](https://github.com/mattn/go-isatty/compare/v0.0.16...v0.0.17) --- updated-dependencies: - dependency-name: github.com/mattn/go-isatty dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 8528b8513..59163b399 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/kluctl/go-embed-python v0.0.0-3.10.8-20221106-1 github.com/kluctl/go-jinja2 v0.0.0-20221215083015-c3f906953ba1 github.com/mattn/go-colorable v0.1.13 - github.com/mattn/go-isatty v0.0.16 + github.com/mattn/go-isatty v0.0.17 github.com/mattn/go-runewidth v0.0.14 github.com/mitchellh/reflectwalk v1.0.2 github.com/ohler55/ojg v1.15.0 diff --git a/go.sum b/go.sum index c6c400acc..c5d5c3f9b 100644 --- a/go.sum +++ b/go.sum @@ -599,8 +599,9 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= From 5a9371796bd98fe702bca8155b1b7d1126efeef9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Dec 2022 22:08:52 +0000 Subject: [PATCH 1352/2916] chore(deps): Bump github.com/aws/aws-sdk-go-v2/service/secretsmanager Bumps [github.com/aws/aws-sdk-go-v2/service/secretsmanager](https://github.com/aws/aws-sdk-go-v2) from 1.16.11 to 1.17.0. - [Release notes](https://github.com/aws/aws-sdk-go-v2/releases) - [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go-v2/compare/v1.16.11...v1.17.0) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go-v2/service/secretsmanager dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8528b8513..bb8ec023b 100644 --- a/go.mod +++ b/go.mod @@ -59,7 +59,7 @@ require ( require ( filippo.io/age v1.1.1 github.com/aws/aws-sdk-go-v2/config v1.18.7 - github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.16.11 + github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.17.0 github.com/go-logr/logr v1.2.3 github.com/hashicorp/go-multierror v1.1.1 github.com/huandu/xstrings v1.4.0 diff --git a/go.sum b/go.sum index c6c400acc..522c00323 100644 --- a/go.sum +++ b/go.sum @@ -139,8 +139,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.21 h1:5C6XgTViS github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.21/go.mod h1:lRToEJsn+DRA9lW4O9L9+/3hjTkUzlzyzHqn8MTds5k= github.com/aws/aws-sdk-go-v2/service/kms v1.17.5 h1:DkubF+BSEy0uX59+pYySzWFReN3fCcXobIO8L5Phh24= github.com/aws/aws-sdk-go-v2/service/kms v1.17.5/go.mod h1:ubAtMGRUMVv5kX8lpbeDguxZ64pR4kXTGApY4sCM0io= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.16.11 h1:77V7vnw/NC4DORHVgA97+Ky2p1ri0+ZVYXh6ordUZU0= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.16.11/go.mod h1:jAeo/PdIJZuDSwsvxJS94G4d6h8tStj7WXVuKwLHWU8= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.17.0 h1:6W6BLZcXytRJsVvc2gGwxKE4wbMSlWqdxZivBP/E+ys= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.17.0/go.mod h1:jAeo/PdIJZuDSwsvxJS94G4d6h8tStj7WXVuKwLHWU8= github.com/aws/aws-sdk-go-v2/service/sso v1.11.28 h1:gItLq3zBYyRDPmqAClgzTH8PBjDQGeyptYGHIwtYYNA= github.com/aws/aws-sdk-go-v2/service/sso v1.11.28/go.mod h1:wo/B7uUm/7zw/dWhBJ4FXuw1sySU5lyIhVg1Bu2yL9A= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.11 h1:KCacyVSs/wlcPGx37hcbT3IGYO8P8Jx+TgSDhAXtQMY= From 3e946de42b625c3b39db12d8848a9d4e319cc0fb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Jan 2023 22:07:07 +0000 Subject: [PATCH 1353/2916] chore(deps): Bump github.com/ohler55/ojg from 1.15.0 to 1.16.0 Bumps [github.com/ohler55/ojg](https://github.com/ohler55/ojg) from 1.15.0 to 1.16.0. - [Release notes](https://github.com/ohler55/ojg/releases) - [Changelog](https://github.com/ohler55/ojg/blob/develop/CHANGELOG.md) - [Commits](https://github.com/ohler55/ojg/compare/v1.15.0...v1.16.0) --- updated-dependencies: - dependency-name: github.com/ohler55/ojg dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8528b8513..d91f47188 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( github.com/mattn/go-isatty v0.0.16 github.com/mattn/go-runewidth v0.0.14 github.com/mitchellh/reflectwalk v1.0.2 - github.com/ohler55/ojg v1.15.0 + github.com/ohler55/ojg v1.16.0 github.com/pkg/errors v0.9.1 github.com/r3labs/diff/v2 v2.15.1 github.com/rogpeppe/go-internal v1.9.0 diff --git a/go.sum b/go.sum index c6c400acc..517a76973 100644 --- a/go.sum +++ b/go.sum @@ -660,8 +660,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/ohler55/ojg v1.15.0 h1:Z95FvBiMsMOOGP9Nzv5OVV4ND2KnEMxk0GOS8Kvcahg= -github.com/ohler55/ojg v1.15.0/go.mod h1:7Ghirupn8NC8hSSDpI0gcjorPxj+vSVIONDWfliHR1k= +github.com/ohler55/ojg v1.16.0 h1:JQMJp/ygkak1YiGFB2ohbks2Y6BAE061i5cq5fECiVA= +github.com/ohler55/ojg v1.16.0/go.mod h1:7Ghirupn8NC8hSSDpI0gcjorPxj+vSVIONDWfliHR1k= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= From 1b7af20ce6c17693eb0ba1d5242f3d3c8e2ed803 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 4 Jan 2023 12:12:39 +0100 Subject: [PATCH 1354/2916] refactor: Implement go routine helper and use it whereever possible --- cmd/kluctl/commands/cmd_delete.go | 2 +- cmd/kluctl/commands/cmd_helm_pull.go | 17 +--- cmd/kluctl/commands/cmd_helm_update.go | 26 ++---- cmd/kluctl/commands/cobra_utils.go | 13 +-- pkg/deployment/deployment_collection.go | 116 ++++++++---------------- pkg/deployment/utils/delete_utils.go | 28 ++---- pkg/k8s/k8s_cluster.go | 40 ++++---- pkg/utils/go_helper.go | 68 ++++++++++++++ pkg/utils/limited_routine.go | 42 --------- 9 files changed, 152 insertions(+), 200 deletions(-) create mode 100644 pkg/utils/go_helper.go delete mode 100644 pkg/utils/limited_routine.go diff --git a/cmd/kluctl/commands/cmd_delete.go b/cmd/kluctl/commands/cmd_delete.go index ed20376b7..bdac4154e 100644 --- a/cmd/kluctl/commands/cmd_delete.go +++ b/cmd/kluctl/commands/cmd_delete.go @@ -89,5 +89,5 @@ func confirmedDeleteObjects(ctx context.Context, k *k8s.K8sCluster, refs []k8s2. } } - return utils.DeleteObjects(k, refs, true) + return utils.DeleteObjects(ctx, k, refs, true) } diff --git a/cmd/kluctl/commands/cmd_helm_pull.go b/cmd/kluctl/commands/cmd_helm_pull.go index 0931ea0b8..ae0bf7bfd 100644 --- a/cmd/kluctl/commands/cmd_helm_pull.go +++ b/cmd/kluctl/commands/cmd_helm_pull.go @@ -3,17 +3,14 @@ package commands import ( "context" "fmt" - "github.com/hashicorp/go-multierror" "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/helm" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/yaml" - "golang.org/x/sync/semaphore" "io/fs" "os" "path/filepath" - "sync" ) type helmPullCmd struct { @@ -58,10 +55,7 @@ func (cmd *helmPullCmd) Run(ctx context.Context) error { } } - var errs *multierror.Error - var wg sync.WaitGroup - var mutex sync.Mutex - sem := semaphore.NewWeighted(8) + g := utils.NewGoHelper(ctx, 8) for _, chart := range charts { chart := chart @@ -97,7 +91,7 @@ func (cmd *helmPullCmd) Run(ctx context.Context) error { for version, _ := range versionsToPull { version := version - utils.GoLimitedMultiError(ctx, sem, &errs, &mutex, &wg, func() error { + g.RunE(func() error { s := status.Start(ctx, "%s: Pulling Chart with version %s", statusPrefix, version) defer s.Failed() @@ -112,12 +106,9 @@ func (cmd *helmPullCmd) Run(ctx context.Context) error { }) } } - wg.Wait() - if err != nil { - errs = multierror.Append(errs, err) - } + g.Wait() - if errs.ErrorOrNil() != nil { + if g.ErrorOrNil() != nil { return fmt.Errorf("command failed") } diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go index fd4856c1b..a2df12433 100644 --- a/cmd/kluctl/commands/cmd_helm_update.go +++ b/cmd/kluctl/commands/cmd_helm_update.go @@ -5,18 +5,15 @@ import ( "fmt" "github.com/fluxcd/go-git/v5" "github.com/fluxcd/go-git/v5/plumbing/format/index" - "github.com/hashicorp/go-multierror" "github.com/kluctl/kluctl/v2/cmd/kluctl/args" git2 "github.com/kluctl/kluctl/v2/pkg/git" "github.com/kluctl/kluctl/v2/pkg/helm" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/yaml" - "golang.org/x/sync/semaphore" "io/fs" "os" "path/filepath" - "sync" ) type helmUpdateCmd struct { @@ -70,10 +67,7 @@ func (cmd *helmUpdateCmd) Run(ctx context.Context) error { baseChartsDir := filepath.Join(projectDir, ".helm-charts") - var errs *multierror.Error - var wg sync.WaitGroup - var mutex sync.Mutex - sem := semaphore.NewWeighted(8) + g := utils.NewGoHelper(ctx, 8) releases, charts, err := loadHelmReleases(projectDir, baseChartsDir, &cmd.HelmCredentials) if err != nil { @@ -94,7 +88,7 @@ func (cmd *helmUpdateCmd) Run(ctx context.Context) error { for _, chart := range charts { chart := chart - utils.GoLimitedMultiError(ctx, sem, &errs, &mutex, &wg, func() error { + g.RunE(func() error { s := status.Start(ctx, "%s: Querying versions", chart.GetChartName()) defer s.Failed() err := chart.QueryVersions(ctx) @@ -106,9 +100,9 @@ func (cmd *helmUpdateCmd) Run(ctx context.Context) error { return nil }) } - wg.Wait() - if errs.ErrorOrNil() != nil { - return errs + g.Wait() + if g.ErrorOrNil() != nil { + return g.ErrorOrNil() } for _, chart := range charts { @@ -124,7 +118,7 @@ func (cmd *helmUpdateCmd) Run(ctx context.Context) error { for version, _ := range versionsToPull { version := version - utils.GoLimitedMultiError(ctx, sem, &errs, &mutex, &wg, func() error { + g.RunE(func() error { s := status.Start(ctx, "%s: Downloading Chart with version %s into cache", chart.GetChartName(), version) defer s.Failed() _, err := chart.PullCached(ctx, version) @@ -137,9 +131,9 @@ func (cmd *helmUpdateCmd) Run(ctx context.Context) error { }) } } - wg.Wait() - if errs.ErrorOrNil() != nil { - return errs + g.Wait() + if g.ErrorOrNil() != nil { + return g.ErrorOrNil() } versionUseCounts := map[string]map[string]int{} @@ -212,7 +206,7 @@ func (cmd *helmUpdateCmd) Run(ctx context.Context) error { } } - return errs.ErrorOrNil() + return nil } func (cmd *helmUpdateCmd) collectFiles(root string, dir string, m map[string]bool) error { diff --git a/cmd/kluctl/commands/cobra_utils.go b/cmd/kluctl/commands/cobra_utils.go index e8e68f528..173303bfe 100644 --- a/cmd/kluctl/commands/cobra_utils.go +++ b/cmd/kluctl/commands/cobra_utils.go @@ -3,6 +3,7 @@ package commands import ( "context" "fmt" + "github.com/hashicorp/go-multierror" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/term" "github.com/spf13/cobra" @@ -229,7 +230,7 @@ func copyViperValuesToCobraCmd(cmd *cobra.Command) error { } func copyViperValuesToCobraFlags(flags *pflag.FlagSet) error { - var retErr []error + var errs *multierror.Error flags.VisitAll(func(flag *pflag.Flag) { sliceValue, _ := flag.Value.(pflag.SliceValue) if flag.Changed && sliceValue == nil { @@ -245,13 +246,13 @@ func copyViperValuesToCobraFlags(flags *pflag.FlagSet) error { for _, y := range x { s, ok := y.(string) if !ok { - retErr = append(retErr, fmt.Errorf("viper flag %s has unexpected type", flag.Name)) + errs = multierror.Append(errs, fmt.Errorf("viper flag %s has unexpected type", flag.Name)) return } a = append(a, s) } } else { - retErr = append(retErr, fmt.Errorf("viper flag %s has unexpected type", flag.Name)) + errs = multierror.Append(errs, fmt.Errorf("viper flag %s has unexpected type", flag.Name)) return } } @@ -269,18 +270,18 @@ func copyViperValuesToCobraFlags(flags *pflag.FlagSet) error { a = append(a, sliceValue.GetSlice()...) err := sliceValue.Replace(a) if err != nil { - retErr = append(retErr, err) + errs = multierror.Append(errs, err) } } else { for _, x := range a { err := flag.Value.Set(x) if err != nil { - retErr = append(retErr, err) + errs = multierror.Append(errs, err) } } } }) - return utils.NewErrorListOrNil(retErr) + return errs.ErrorOrNil() } func (c *rootCommand) helpFunc(cg *commandAndGroups, cmd *cobra.Command) { diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index c37a9fc9d..bc442fdc4 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -1,13 +1,11 @@ package deployment import ( - "context" "fmt" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/utils" - "golang.org/x/sync/semaphore" "path/filepath" "sync" ) @@ -114,59 +112,38 @@ func (c *DeploymentCollection) RenderDeployments() error { s := status.Start(c.ctx.Ctx, "Rendering templates") defer s.Failed() - var wg sync.WaitGroup - var mutex sync.Mutex - var errors []error + g := utils.NewGoHelper(c.ctx.Ctx, 0) for _, d := range c.Deployments { d := d - wg.Add(1) - go func() { - defer wg.Done() - err := d.render(c.forSeal) - if err != nil { - mutex.Lock() - errors = append(errors, err) - mutex.Unlock() - } - }() + g.RunE(func() error { + return d.render(c.forSeal) + }) } - wg.Wait() - if len(errors) != 0 { - return utils.NewErrorListOrNil(errors) + g.Wait() + if g.ErrorOrNil() == nil { + s.Success() } - s.Success() - return nil + return g.ErrorOrNil() } func (c *DeploymentCollection) renderHelmCharts() error { s := status.Start(c.ctx.Ctx, "Rendering Helm Charts") defer s.Failed() - var wg sync.WaitGroup - var mutex sync.Mutex - var errors []error + g := utils.NewGoHelper(c.ctx.Ctx, 0) for _, d := range c.Deployments { d := d - wg.Add(1) - go func() { - defer wg.Done() - err := d.renderHelmCharts() - if err != nil { - mutex.Lock() - errors = append(errors, err) - mutex.Unlock() - } - }() + g.RunE(func() error { + return d.renderHelmCharts() + }) } - wg.Wait() - if len(errors) != 0 { - return utils.NewErrorListOrNil(errors) + g.Wait() + if g.ErrorOrNil() == nil { + s.Success() } - - s.Success() - return nil + return g.ErrorOrNil() } func (c *DeploymentCollection) resolveSealedSecrets() error { @@ -184,80 +161,61 @@ func (c *DeploymentCollection) resolveSealedSecrets() error { } func (c *DeploymentCollection) buildKustomizeObjects() error { - var wg sync.WaitGroup - var errs []error - var mutex sync.Mutex - sem := semaphore.NewWeighted(16) - - handleError := func(err error) { - mutex.Lock() - errs = append(errs, err) - mutex.Unlock() - } + g := utils.NewGoHelper(c.ctx.Ctx, 0) s := status.Start(c.ctx.Ctx, "Building kustomize objects") for _, d_ := range c.Deployments { d := d_ - - wg.Add(1) - go func() { + g.RunE(func() error { err := d.buildKustomize() if err != nil { - handleError(fmt.Errorf("building kustomize objects for %s failed. %w", *d.dir, err)) + return fmt.Errorf("building kustomize objects for %s failed. %w", *d.dir, err) } - wg.Done() - }() + return nil + }) } - wg.Wait() + g.Wait() - if len(errs) != 0 { + if g.ErrorOrNil() != nil { s.Failed() - return utils.NewErrorListOrNil(errs) + return g.ErrorOrNil() } s.Success() s = status.Start(c.ctx.Ctx, "Postprocessing objects") for _, d_ := range c.Deployments { d := d_ - - wg.Add(1) - go func() { + g.RunE(func() error { err := d.postprocessCRDs() if err != nil { - handleError(fmt.Errorf("postprocessing CRDs failed: %w", err)) + return fmt.Errorf("postprocessing CRDs failed: %w", err) } - wg.Done() - }() + return nil + }) } - wg.Wait() + g.Wait() + g = utils.NewGoHelper(c.ctx.Ctx, 16) for _, d_ := range c.Deployments { d := d_ - wg.Add(1) - go func() { - _ = sem.Acquire(context.Background(), 1) - defer sem.Release(1) - + g.RunE(func() error { err := d.postprocessObjects(c.Images) if err != nil { - mutex.Lock() - errs = append(errs, fmt.Errorf("postprocessing kustomize objects for %s failed: %w", *d.dir, err)) - mutex.Unlock() + return fmt.Errorf("postprocessing kustomize objects for %s failed: %w", *d.dir, err) } - - wg.Done() - }() + return nil + }) } - wg.Wait() + g.Wait() - if len(errs) == 0 { + if g.ErrorOrNil() == nil { s.Success() } else { s.Failed() } - return utils.NewErrorListOrNil(errs) + return g.ErrorOrNil() } func (c *DeploymentCollection) LocalObjectsByRef() map[k8s2.ObjectRef]bool { diff --git a/pkg/deployment/utils/delete_utils.go b/pkg/deployment/utils/delete_utils.go index d79f5d229..00f72ad39 100644 --- a/pkg/deployment/utils/delete_utils.go +++ b/pkg/deployment/utils/delete_utils.go @@ -7,7 +7,6 @@ import ( k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" - "golang.org/x/sync/semaphore" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "sync" @@ -161,9 +160,8 @@ func FindObjectsForDelete(k *k8s.K8sCluster, allClusterObjects []*uo.Unstructure return ret, nil } -func DeleteObjects(k *k8s.K8sCluster, refs []k8s2.ObjectRef, doWait bool) (*types.CommandResult, error) { - var wg sync.WaitGroup - sem := semaphore.NewWeighted(8) +func DeleteObjects(ctx context.Context, k *k8s.K8sCluster, refs []k8s2.ObjectRef, doWait bool) (*types.CommandResult, error) { + g := utils.NewGoHelper(ctx, 8) var ret types.CommandResult namespaceNames := make(map[string]bool) @@ -193,18 +191,13 @@ func DeleteObjects(k *k8s.K8sCluster, refs []k8s2.ObjectRef, doWait bool) (*type ref := ref_ if ref.GVK.GroupVersion().String() == "v1" && ref.GVK.Kind == "Namespace" { namespaceNames[ref.Name] = true - wg.Add(1) - go func() { - defer wg.Done() - _ = sem.Acquire(context.Background(), 1) - defer sem.Release(1) - + g.Run(func() { apiWarnings, err := k.DeleteSingleObject(ref, k8s.DeleteOptions{NoWait: !doWait, IgnoreNotFoundError: true}) handleResult(ref, apiWarnings, err) - }() + }) } } - wg.Wait() + g.Wait() for _, ref_ := range refs { ref := ref_ @@ -215,17 +208,12 @@ func DeleteObjects(k *k8s.K8sCluster, refs []k8s2.ObjectRef, doWait bool) (*type // already deleted via namespace continue } - wg.Add(1) - go func() { - defer wg.Done() - _ = sem.Acquire(context.Background(), 1) - defer sem.Release(1) - + g.Run(func() { apiWarnings, err := k.DeleteSingleObject(ref, k8s.DeleteOptions{NoWait: !doWait, IgnoreNotFoundError: true}) handleResult(ref, apiWarnings, err) - }() + }) } - wg.Wait() + g.Wait() return &ret, nil } diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index 751664902..36af06bd9 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -136,10 +136,8 @@ func (k *K8sCluster) ListObjects(gvk schema.GroupVersionKind, namespace string, func (k *K8sCluster) ListAllObjects(verbs []string, namespace string, labels map[string]string) ([]*uo.UnstructuredObject, map[schema.GroupVersionKind][]ApiWarning, error) { var ret []*uo.UnstructuredObject - var errs []error retApiWarnings := make(map[schema.GroupVersionKind][]ApiWarning) var mutex sync.Mutex - var wg sync.WaitGroup filter := func(ar *v1.APIResource) bool { foundVerb := false @@ -152,29 +150,27 @@ func (k *K8sCluster) ListAllObjects(verbs []string, namespace string, labels map return foundVerb } + g := utils.NewGoHelper(k.ctx, 0) for _, gvk := range k.Resources.GetFilteredPreferredGVKs(filter) { gvk := gvk - wg.Add(1) - go func() { - defer wg.Done() - + g.RunE(func() error { l, apiWarnings, err := k.ListObjects(gvk, namespace, labels) mutex.Lock() defer mutex.Unlock() if err != nil && !errors.IsNotFound(err) { - errs = append(errs, err) - return + return err } ret = append(ret, l...) if len(apiWarnings) != 0 { retApiWarnings[gvk] = apiWarnings } - }() + return nil + }) } - wg.Wait() + g.Wait() - if len(errs) != 0 { - return nil, retApiWarnings, utils.NewErrorListOrNil(errs) + if g.ErrorOrNil() != nil { + return nil, retApiWarnings, g.ErrorOrNil() } return ret, retApiWarnings, nil @@ -196,16 +192,13 @@ func (k *K8sCluster) GetSingleObject(ref k8s.ObjectRef) (*uo.UnstructuredObject, func (k *K8sCluster) GetObjectsByRefs(refs []k8s.ObjectRef) ([]*uo.UnstructuredObject, map[k8s.ObjectRef][]ApiWarning, error) { var ret []*uo.UnstructuredObject - var errs []error retApiWarnings := make(map[k8s.ObjectRef][]ApiWarning) var mutex sync.Mutex - var wg sync.WaitGroup + g := utils.NewGoHelper(k.ctx, 0) for _, ref_ := range refs { ref := ref_ - wg.Add(1) - go func() { - defer wg.Done() + g.RunE(func() error { o, apiWarnings, err := k.GetSingleObject(ref) mutex.Lock() defer mutex.Unlock() @@ -214,16 +207,17 @@ func (k *K8sCluster) GetObjectsByRefs(refs []k8s.ObjectRef) ([]*uo.UnstructuredO } if err != nil { if !errors.IsNotFound(err) && !meta.IsNoMatchError(err) { - errs = append(errs, err) + return err } - return + return nil } ret = append(ret, o) - }() + return nil + }) } - wg.Wait() - if len(errs) != 0 { - return nil, retApiWarnings, utils.NewErrorListOrNil(errs) + g.Wait() + if g.ErrorOrNil() != nil { + return nil, retApiWarnings, g.ErrorOrNil() } return ret, retApiWarnings, nil diff --git a/pkg/utils/go_helper.go b/pkg/utils/go_helper.go new file mode 100644 index 000000000..b37a0a4de --- /dev/null +++ b/pkg/utils/go_helper.go @@ -0,0 +1,68 @@ +package utils + +import ( + "context" + "github.com/hashicorp/go-multierror" + "golang.org/x/sync/semaphore" + "sync" +) + +type goHelper struct { + ctx context.Context + sem *semaphore.Weighted + wg sync.WaitGroup + mutex sync.Mutex + errs *multierror.Error +} + +func NewGoHelper(ctx context.Context, max int) *goHelper { + g := &goHelper{ + ctx: ctx, + } + if max > 0 { + g.sem = semaphore.NewWeighted(int64(max)) + } + return g +} + +func (g *goHelper) addError(err error) { + g.mutex.Lock() + defer g.mutex.Unlock() + g.errs = multierror.Append(g.errs, err) +} + +func (g *goHelper) RunE(fn func() error) { + g.wg.Add(1) + go func() { + defer g.wg.Done() + if g.sem != nil { + err := g.sem.Acquire(g.ctx, 1) + if err != nil { + g.addError(err) + return + } + } + if g.sem != nil { + defer g.sem.Release(1) + } + err := fn() + if err != nil { + g.addError(err) + } + }() +} + +func (g *goHelper) Run(fn func()) { + g.RunE(func() error { + fn() + return nil + }) +} + +func (g *goHelper) Wait() { + g.wg.Wait() +} + +func (g *goHelper) ErrorOrNil() error { + return g.errs.ErrorOrNil() +} diff --git a/pkg/utils/limited_routine.go b/pkg/utils/limited_routine.go deleted file mode 100644 index f38a1bdde..000000000 --- a/pkg/utils/limited_routine.go +++ /dev/null @@ -1,42 +0,0 @@ -package utils - -import ( - "context" - "github.com/hashicorp/go-multierror" - "golang.org/x/sync/semaphore" - "sync" -) - -func GoLimited(ctx context.Context, sem *semaphore.Weighted, wg *sync.WaitGroup, fn func(), errCb func(err error)) { - if wg != nil { - wg.Add(1) - } - go func() { - if wg != nil { - defer wg.Done() - } - err := sem.Acquire(ctx, 1) - if err != nil { - if errCb != nil { - errCb(err) - } - return - } - defer sem.Release(1) - fn() - }() -} - -func GoLimitedMultiError(ctx context.Context, sem *semaphore.Weighted, merr **multierror.Error, mutex *sync.Mutex, wg *sync.WaitGroup, fn func() error) { - errCb := func(err error) { - mutex.Lock() - defer mutex.Unlock() - *merr = multierror.Append(*merr, err) - } - GoLimited(ctx, sem, wg, func() { - err := fn() - if err != nil { - errCb(err) - } - }, errCb) -} From 4329dbe857bb817f143f2ea443edb9484cdf8cec Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 4 Jan 2023 14:19:55 +0100 Subject: [PATCH 1355/2916] refactor: Replace last uses of NewErrorListOrNil with multierror --- pkg/deployment/deployment_item.go | 9 +++++---- pkg/deployment/utils/errors_holder.go | 4 ++-- pkg/utils/errorlist.go | 23 ----------------------- 3 files changed, 7 insertions(+), 29 deletions(-) delete mode 100644 pkg/utils/errorlist.go diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index a9ac22883..af74a2f46 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -3,6 +3,7 @@ package deployment import ( "fmt" "github.com/fluxcd/pkg/kustomize" + "github.com/hashicorp/go-multierror" "github.com/kluctl/kluctl/v2/pkg/helm" "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/sops" @@ -567,7 +568,7 @@ func (di *DeploymentItem) postprocessObjects(images *Images) error { var objects []interface{} - var errList []error + var errs *multierror.Error for _, o := range di.Objects { commonLabels := di.getCommonLabels() commonAnnotations := di.getCommonAnnotations() @@ -588,7 +589,7 @@ func (di *DeploymentItem) postprocessObjects(images *Images) error { // Resolve image placeholders err := images.ResolvePlaceholders(di.ctx.K, o, di.RelRenderedDir, di.Tags.ListKeys()) if err != nil { - errList = append(errList, err) + errs = multierror.Append(errs, err) } return nil }) @@ -596,8 +597,8 @@ func (di *DeploymentItem) postprocessObjects(images *Images) error { objects = append(objects, o.Object) } - if len(errList) != 0 { - return utils.NewErrorListOrNil(errList) + if errs.ErrorOrNil() != nil { + return errs.ErrorOrNil() } // Need to write it back to disk in case it is needed externally diff --git a/pkg/deployment/utils/errors_holder.go b/pkg/deployment/utils/errors_holder.go index 9b702a378..bf8faef1d 100644 --- a/pkg/deployment/utils/errors_holder.go +++ b/pkg/deployment/utils/errors_holder.go @@ -3,10 +3,10 @@ package utils import ( "errors" "fmt" + "github.com/hashicorp/go-multierror" k8s2 "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/types/k8s" - "github.com/kluctl/kluctl/v2/pkg/utils" "sync" ) @@ -106,5 +106,5 @@ func (dew *DeploymentErrorsAndWarnings) getPlainErrorsList() []error { func (dew *DeploymentErrorsAndWarnings) GetMultiError() error { l := dew.getPlainErrorsList() - return utils.NewErrorListOrNil(l) + return multierror.Append(nil, l...).ErrorOrNil() } diff --git a/pkg/utils/errorlist.go b/pkg/utils/errorlist.go deleted file mode 100644 index 0191aa7d1..000000000 --- a/pkg/utils/errorlist.go +++ /dev/null @@ -1,23 +0,0 @@ -package utils - -type errorList struct { - errors []error -} - -func NewErrorListOrNil(errors []error) error { - if len(errors) == 0 { - return nil - } - return &errorList{errors: errors} -} - -func (el *errorList) Error() string { - s := "" - for _, err := range el.errors { - if len(s) != 0 { - s += "\n" - } - s += err.Error() - } - return s -} From f9951a923dfda20ab9eb361cdf25cf9f3ab9708f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Jan 2023 22:06:21 +0000 Subject: [PATCH 1356/2916] chore(deps): Bump golang.org/x/net from 0.4.0 to 0.5.0 Bumps [golang.org/x/net](https://github.com/golang/net) from 0.4.0 to 0.5.0. - [Release notes](https://github.com/golang/net/releases) - [Commits](https://github.com/golang/net/compare/v0.4.0...v0.5.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index a812c641c..835759cb6 100644 --- a/go.mod +++ b/go.mod @@ -39,11 +39,11 @@ require ( github.com/whilp/git-urls v1.0.0 github.com/xanzy/ssh-agent v0.3.3 golang.org/x/crypto v0.4.0 - golang.org/x/net v0.4.0 + golang.org/x/net v0.5.0 golang.org/x/sync v0.1.0 - golang.org/x/sys v0.3.0 - golang.org/x/term v0.3.0 - golang.org/x/text v0.5.0 + golang.org/x/sys v0.4.0 + golang.org/x/term v0.4.0 + golang.org/x/text v0.6.0 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 helm.sh/helm/v3 v3.10.3 diff --git a/go.sum b/go.sum index c5d9e9066..39517d07d 100644 --- a/go.sum +++ b/go.sum @@ -975,8 +975,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU= -golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1074,16 +1074,16 @@ golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI= -golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg= +golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1094,8 +1094,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= -golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From 66dfb825d58f9605cf3d1e99b8266a2b9cdcbaa7 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 4 Jan 2023 15:21:39 +0100 Subject: [PATCH 1357/2916] refactor: Remove generic ListAllObjects/GetObjectsByRefs from K8sCluster And implement the needed functionality where it is used. Allows for better error handling later. --- pkg/deployment/utils/remote_objects_utils.go | 118 ++++++++++++++----- pkg/k8s/k8s_cluster.go | 75 ------------ 2 files changed, 89 insertions(+), 104 deletions(-) diff --git a/pkg/deployment/utils/remote_objects_utils.go b/pkg/deployment/utils/remote_objects_utils.go index 520a786a0..e9fbd99c2 100644 --- a/pkg/deployment/utils/remote_objects_utils.go +++ b/pkg/deployment/utils/remote_objects_utils.go @@ -2,12 +2,16 @@ package utils import ( "context" + "fmt" "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/status" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" + errors2 "k8s.io/apimachinery/pkg/api/errors" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" + "sync" ) type RemoteObjectUtils struct { @@ -26,53 +30,109 @@ func NewRemoteObjectsUtil(ctx context.Context, dew *DeploymentErrorsAndWarnings) } } -func (u *RemoteObjectUtils) UpdateRemoteObjects(k *k8s.K8sCluster, labels map[string]string, refs []k8s2.ObjectRef) error { - if k == nil { +func (u *RemoteObjectUtils) getAllByLabels(k *k8s.K8sCluster, labels map[string]string) error { + var mutex sync.Mutex + if len(labels) == 0 { return nil } - s := status.Start(u.ctx, "Getting remote objects by commonLabels") + baseStatus := "Getting remote objects by commonLabels" + s := status.Start(u.ctx, baseStatus) defer s.Failed() - if len(labels) != 0 { - allObjects, apiWarnings, err := k.ListAllObjects([]string{"get"}, "", labels) - for gvk, aw := range apiWarnings { - u.dew.AddApiWarnings(k8s2.ObjectRef{GVK: gvk}, aw) - } - if err != nil { - return err - } - for _, o := range allObjects { - u.remoteObjects[o.GetK8sRef()] = o - } + gvks := k.Resources.GetFilteredPreferredGVKs(func(ar *v1.APIResource) bool { + return utils.FindStrInSlice(ar.Verbs, "list") != -1 + }) + + g := utils.NewGoHelper(u.ctx, 0) + for _, gvk := range gvks { + gvk := gvk + g.RunE(func() error { + l, apiWarnings, err := k.ListObjects(gvk, "", labels) + u.dew.AddApiWarnings(k8s2.ObjectRef{GVK: gvk}, apiWarnings) + if err != nil { + if errors2.IsNotFound(err) { + return nil + } + return err + } + mutex.Lock() + defer mutex.Unlock() + for _, o := range l { + u.remoteObjects[o.GetK8sRef()] = o + } + return nil + }) } + g.Wait() + if g.ErrorOrNil() == nil { + s.Success() + } + return g.ErrorOrNil() +} +func (u *RemoteObjectUtils) getMissingObjects(k *k8s.K8sCluster, refs []k8s2.ObjectRef) error { notFoundRefsMap := make(map[k8s2.ObjectRef]bool) - var notFoundRefsList []k8s2.ObjectRef for _, ref := range refs { if _, ok := u.remoteObjects[ref]; !ok { if _, ok = notFoundRefsMap[ref]; !ok { notFoundRefsMap[ref] = true - notFoundRefsList = append(notFoundRefsList, ref) } } } - if len(notFoundRefsList) != 0 { - s.UpdateAndInfoFallback("Getting %d additional remote objects", len(notFoundRefsList)) - r, apiWarnings, err := k.GetObjectsByRefs(notFoundRefsList) - for ref, aw := range apiWarnings { - u.dew.AddApiWarnings(ref, aw) - } - if err != nil { - return err - } - for _, o := range r { - u.remoteObjects[o.GetK8sRef()] = o - } + var mutex sync.Mutex + if len(notFoundRefsMap) == 0 { + return nil } - s.UpdateAndInfoFallback("Getting namespaces") + baseStatus := fmt.Sprintf("Getting %d additional remote objects", len(notFoundRefsMap)) + s := status.Start(u.ctx, baseStatus) + defer s.Failed() + + g := utils.NewGoHelper(u.ctx, 0) + for ref, _ := range notFoundRefsMap { + ref := ref + g.RunE(func() error { + r, apiWarnings, err := k.GetSingleObject(ref) + u.dew.AddApiWarnings(ref, apiWarnings) + if err != nil { + if errors2.IsNotFound(err) { + return nil + } + return err + } + mutex.Lock() + defer mutex.Unlock() + u.remoteObjects[r.GetK8sRef()] = r + return nil + }) + } + g.Wait() + if g.ErrorOrNil() == nil { + s.Success() + } + return g.ErrorOrNil() +} + +func (u *RemoteObjectUtils) UpdateRemoteObjects(k *k8s.K8sCluster, labels map[string]string, refs []k8s2.ObjectRef) error { + if k == nil { + return nil + } + + err := u.getAllByLabels(k, labels) + if err != nil { + return err + } + + err = u.getMissingObjects(k, refs) + if err != nil { + return err + } + + s := status.Start(u.ctx, "Getting namespaces") + defer s.Failed() + r, _, err := k.ListObjects(schema.GroupVersionKind{ Group: "", Version: "v1", diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index 36af06bd9..31b5a227c 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -134,48 +134,6 @@ func (k *K8sCluster) ListObjects(gvk schema.GroupVersionKind, namespace string, return result, apiWarnings, err } -func (k *K8sCluster) ListAllObjects(verbs []string, namespace string, labels map[string]string) ([]*uo.UnstructuredObject, map[schema.GroupVersionKind][]ApiWarning, error) { - var ret []*uo.UnstructuredObject - retApiWarnings := make(map[schema.GroupVersionKind][]ApiWarning) - var mutex sync.Mutex - - filter := func(ar *v1.APIResource) bool { - foundVerb := false - for _, v := range verbs { - if utils.FindStrInSlice(ar.Verbs, v) != -1 { - foundVerb = true - break - } - } - return foundVerb - } - - g := utils.NewGoHelper(k.ctx, 0) - for _, gvk := range k.Resources.GetFilteredPreferredGVKs(filter) { - gvk := gvk - g.RunE(func() error { - l, apiWarnings, err := k.ListObjects(gvk, namespace, labels) - mutex.Lock() - defer mutex.Unlock() - if err != nil && !errors.IsNotFound(err) { - return err - } - ret = append(ret, l...) - if len(apiWarnings) != 0 { - retApiWarnings[gvk] = apiWarnings - } - return nil - }) - } - g.Wait() - - if g.ErrorOrNil() != nil { - return nil, retApiWarnings, g.ErrorOrNil() - } - - return ret, retApiWarnings, nil -} - func (k *K8sCluster) GetSingleObject(ref k8s.ObjectRef) (*uo.UnstructuredObject, []ApiWarning, error) { var result *uo.UnstructuredObject apiWarnings, err := k.clients.withDynamicClientForGVK(k.Resources, ref.GVK, ref.Namespace, func(r dynamic.ResourceInterface) error { @@ -190,39 +148,6 @@ func (k *K8sCluster) GetSingleObject(ref k8s.ObjectRef) (*uo.UnstructuredObject, return result, apiWarnings, err } -func (k *K8sCluster) GetObjectsByRefs(refs []k8s.ObjectRef) ([]*uo.UnstructuredObject, map[k8s.ObjectRef][]ApiWarning, error) { - var ret []*uo.UnstructuredObject - retApiWarnings := make(map[k8s.ObjectRef][]ApiWarning) - var mutex sync.Mutex - - g := utils.NewGoHelper(k.ctx, 0) - for _, ref_ := range refs { - ref := ref_ - g.RunE(func() error { - o, apiWarnings, err := k.GetSingleObject(ref) - mutex.Lock() - defer mutex.Unlock() - if len(apiWarnings) != 0 { - retApiWarnings[ref] = apiWarnings - } - if err != nil { - if !errors.IsNotFound(err) && !meta.IsNoMatchError(err) { - return err - } - return nil - } - ret = append(ret, o) - return nil - }) - } - g.Wait() - if g.ErrorOrNil() != nil { - return nil, retApiWarnings, g.ErrorOrNil() - } - - return ret, retApiWarnings, nil -} - type DeleteOptions struct { ForceDryRun bool NoWait bool From 94b68e1ee2bcf7f68e7371aa8963cb17d0d731c7 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 4 Jan 2023 16:06:37 +0100 Subject: [PATCH 1358/2916] fix: Do not abort when errors occur in UpdateRemoteObjects Instead, record errors and show them later. --- pkg/deployment/utils/remote_objects_utils.go | 37 ++++++++++++++------ 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/pkg/deployment/utils/remote_objects_utils.go b/pkg/deployment/utils/remote_objects_utils.go index e9fbd99c2..f18854073 100644 --- a/pkg/deployment/utils/remote_objects_utils.go +++ b/pkg/deployment/utils/remote_objects_utils.go @@ -40,6 +40,8 @@ func (u *RemoteObjectUtils) getAllByLabels(k *k8s.K8sCluster, labels map[string] s := status.Start(u.ctx, baseStatus) defer s.Failed() + errCount := 0 + gvks := k.Resources.GetFilteredPreferredGVKs(func(ar *v1.APIResource) bool { return utils.FindStrInSlice(ar.Verbs, "list") != -1 }) @@ -47,26 +49,32 @@ func (u *RemoteObjectUtils) getAllByLabels(k *k8s.K8sCluster, labels map[string] g := utils.NewGoHelper(u.ctx, 0) for _, gvk := range gvks { gvk := gvk - g.RunE(func() error { + g.Run(func() { l, apiWarnings, err := k.ListObjects(gvk, "", labels) u.dew.AddApiWarnings(k8s2.ObjectRef{GVK: gvk}, apiWarnings) if err != nil { if errors2.IsNotFound(err) { - return nil + return } - return err + u.dew.AddError(k8s2.ObjectRef{GVK: gvk}, err) + errCount += 1 + return } mutex.Lock() defer mutex.Unlock() for _, o := range l { u.remoteObjects[o.GetK8sRef()] = o } - return nil }) } g.Wait() if g.ErrorOrNil() == nil { - s.Success() + if errCount != 0 { + s.UpdateAndInfoFallback("%s: Failed with %d errors", baseStatus, errCount) + s.Warning() + } else { + s.Success() + } } return g.ErrorOrNil() } @@ -86,6 +94,8 @@ func (u *RemoteObjectUtils) getMissingObjects(k *k8s.K8sCluster, refs []k8s2.Obj return nil } + errCount := 0 + baseStatus := fmt.Sprintf("Getting %d additional remote objects", len(notFoundRefsMap)) s := status.Start(u.ctx, baseStatus) defer s.Failed() @@ -93,24 +103,31 @@ func (u *RemoteObjectUtils) getMissingObjects(k *k8s.K8sCluster, refs []k8s2.Obj g := utils.NewGoHelper(u.ctx, 0) for ref, _ := range notFoundRefsMap { ref := ref - g.RunE(func() error { + g.Run(func() { r, apiWarnings, err := k.GetSingleObject(ref) u.dew.AddApiWarnings(ref, apiWarnings) if err != nil { if errors2.IsNotFound(err) { - return nil + return } - return err + u.dew.AddError(ref, err) + errCount += 1 + return } mutex.Lock() defer mutex.Unlock() u.remoteObjects[r.GetK8sRef()] = r - return nil + return }) } g.Wait() if g.ErrorOrNil() == nil { - s.Success() + if errCount != 0 { + s.UpdateAndInfoFallback("%s: Failed with %d errors", baseStatus, errCount) + s.Warning() + } else { + s.Success() + } } return g.ErrorOrNil() } From 46782658385335515e062833aef982901145bf52 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 4 Jan 2023 17:04:39 +0100 Subject: [PATCH 1359/2916] fix: Properly print errors/warnings without a ref --- cmd/kluctl/commands/command_result.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cmd/kluctl/commands/command_result.go b/cmd/kluctl/commands/command_result.go index bc6bb1ca9..1d43fda5c 100644 --- a/cmd/kluctl/commands/command_result.go +++ b/cmd/kluctl/commands/command_result.go @@ -82,7 +82,11 @@ func prettyObjectRefs(buf io.StringWriter, refs []k8s.ObjectRef) { func prettyErrors(buf io.StringWriter, errors []types.DeploymentError) { for _, e := range errors { - _, _ = buf.WriteString(fmt.Sprintf(" %s: %s\n", e.Ref.String(), e.Error)) + prefix := "" + if s := e.Ref.String(); s != "" { + prefix = fmt.Sprintf("%s: ", s) + } + _, _ = buf.WriteString(fmt.Sprintf(" %s%s\n", prefix, e.Error)) } } From 1a1e63c745f930b9f1c41d5633d7c1001aeca528 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 4 Jan 2023 17:04:54 +0100 Subject: [PATCH 1360/2916] fix: Don't be too verbose about permission errors --- pkg/deployment/utils/remote_objects_utils.go | 22 +++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/pkg/deployment/utils/remote_objects_utils.go b/pkg/deployment/utils/remote_objects_utils.go index f18854073..cb378acd9 100644 --- a/pkg/deployment/utils/remote_objects_utils.go +++ b/pkg/deployment/utils/remote_objects_utils.go @@ -41,6 +41,7 @@ func (u *RemoteObjectUtils) getAllByLabels(k *k8s.K8sCluster, labels map[string] defer s.Failed() errCount := 0 + permissionErrCount := 0 gvks := k.Resources.GetFilteredPreferredGVKs(func(ar *v1.APIResource) bool { return utils.FindStrInSlice(ar.Verbs, "list") != -1 @@ -52,16 +53,20 @@ func (u *RemoteObjectUtils) getAllByLabels(k *k8s.K8sCluster, labels map[string] g.Run(func() { l, apiWarnings, err := k.ListObjects(gvk, "", labels) u.dew.AddApiWarnings(k8s2.ObjectRef{GVK: gvk}, apiWarnings) + mutex.Lock() + defer mutex.Unlock() if err != nil { if errors2.IsNotFound(err) { return } - u.dew.AddError(k8s2.ObjectRef{GVK: gvk}, err) errCount += 1 + if errors2.IsForbidden(err) || errors2.IsUnauthorized(err) { + permissionErrCount += 1 + return + } + u.dew.AddWarning(k8s2.ObjectRef{GVK: gvk}, err) return } - mutex.Lock() - defer mutex.Unlock() for _, o := range l { u.remoteObjects[o.GetK8sRef()] = o } @@ -72,6 +77,9 @@ func (u *RemoteObjectUtils) getAllByLabels(k *k8s.K8sCluster, labels map[string] if errCount != 0 { s.UpdateAndInfoFallback("%s: Failed with %d errors", baseStatus, errCount) s.Warning() + if permissionErrCount != 0 { + u.dew.AddWarning(k8s2.ObjectRef{}, fmt.Errorf("at least one permission error was encountered while gathering objects by labels. This might result in orphan object detection to not work properly")) + } } else { s.Success() } @@ -95,6 +103,7 @@ func (u *RemoteObjectUtils) getMissingObjects(k *k8s.K8sCluster, refs []k8s2.Obj } errCount := 0 + permissionErrCount := 0 baseStatus := fmt.Sprintf("Getting %d additional remote objects", len(notFoundRefsMap)) s := status.Start(u.ctx, baseStatus) @@ -110,6 +119,10 @@ func (u *RemoteObjectUtils) getMissingObjects(k *k8s.K8sCluster, refs []k8s2.Obj if errors2.IsNotFound(err) { return } + if errors2.IsForbidden(err) || errors2.IsUnauthorized(err) { + permissionErrCount += 1 + return + } u.dew.AddError(ref, err) errCount += 1 return @@ -125,6 +138,9 @@ func (u *RemoteObjectUtils) getMissingObjects(k *k8s.K8sCluster, refs []k8s2.Obj if errCount != 0 { s.UpdateAndInfoFallback("%s: Failed with %d errors", baseStatus, errCount) s.Warning() + if permissionErrCount != 0 { + u.dew.AddWarning(k8s2.ObjectRef{}, fmt.Errorf("at least one permission error was encountered while gathering known objects. This might result in orphan object detection and diffs to not work properly")) + } } else { s.Success() } From 8fd992bae1a18e06878ed56ab452b6b0a6b66c2a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 4 Jan 2023 22:41:22 +0100 Subject: [PATCH 1361/2916] tests: Add test for permission errors in RemoteObjectUtils --- .../utils/remote_objects_utils_test.go | 48 ++++++++++++ pkg/k8s/fake_client_factory.go | 74 +++++++++++++++---- pkg/k8s/resources.go | 7 ++ 3 files changed, 114 insertions(+), 15 deletions(-) create mode 100644 pkg/deployment/utils/remote_objects_utils_test.go diff --git a/pkg/deployment/utils/remote_objects_utils_test.go b/pkg/deployment/utils/remote_objects_utils_test.go new file mode 100644 index 000000000..519c8ebf2 --- /dev/null +++ b/pkg/deployment/utils/remote_objects_utils_test.go @@ -0,0 +1,48 @@ +package utils + +import ( + "context" + "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/types" + k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "testing" +) + +func TestRemoteObjectUtils_PermissionErrors(t *testing.T) { + gvr := schema.GroupVersionResource{ + Group: "", + Version: "v1", + Resource: "secrets", + } + expectedErr := errors.NewForbidden(gvr.GroupResource(), "secret", nil) + + secret := &corev1.Secret{ + ObjectMeta: v1.ObjectMeta{Name: "cm1", Namespace: "ns", Labels: map[string]string{"label1": "value1"}}, + Data: map[string][]byte{ + "test": []byte(`test`), + }, + } + + f := k8s.NewFakeClientFactory(secret) + f.AddError(gvr, "", "", expectedErr) + k, err := k8s.NewK8sCluster(context.TODO(), f, false) + if err != nil { + t.Fatal(err) + } + + dew := NewDeploymentErrorsAndWarnings() + u := NewRemoteObjectsUtil(context.Background(), dew) + err = u.UpdateRemoteObjects(k, map[string]string{"l1": "v1"}, []k8s2.ObjectRef{ + k8s2.NewObjectRef("", "v1", "Secret", "secret", "default"), + }) + assert.NoError(t, err) + assert.Equal(t, []types.DeploymentError{{ + Error: "at least one permission error was encountered while gathering objects by labels. This might result in orphan object detection to not work properly"}, + }, dew.GetWarningsList()) + assert.Empty(t, dew.GetErrorsList()) +} diff --git a/pkg/k8s/fake_client_factory.go b/pkg/k8s/fake_client_factory.go index cee966194..6ee62d1e4 100644 --- a/pkg/k8s/fake_client_factory.go +++ b/pkg/k8s/fake_client_factory.go @@ -3,6 +3,7 @@ package k8s import ( v1 "k8s.io/api/core/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -12,15 +13,15 @@ import ( "k8s.io/client-go/kubernetes/fake" corev1 "k8s.io/client-go/kubernetes/typed/core/v1" "k8s.io/client-go/rest" + "k8s.io/client-go/testing" "sigs.k8s.io/kustomize/kyaml/openapi" "sigs.k8s.io/kustomize/kyaml/yaml" "strings" ) type fakeClientFactory struct { - clientSet *fake.Clientset - objects []runtime.Object - scheme *runtime.Scheme + clientSet *fake.Clientset + dynamicClient *fake_dynamic.FakeDynamicClient } func (f *fakeClientFactory) RESTConfig() *rest.Config { @@ -43,10 +44,10 @@ func (f *fakeClientFactory) CoreV1Client(wh rest.WarningHandler) (corev1.CoreV1I } func (f *fakeClientFactory) DynamicClient(wh rest.WarningHandler) (dynamic.Interface, error) { - return fake_dynamic.NewSimpleDynamicClient(f.scheme, f.objects...), nil + return f.dynamicClient, nil } -func NewFakeClientFactory(objects ...runtime.Object) ClientFactory { +func NewFakeClientFactory(objects ...runtime.Object) *fakeClientFactory { scheme := runtime.NewScheme() _ = v1.AddToScheme(scheme) _ = apiextensionsv1.AddToScheme(scheme) @@ -54,30 +55,77 @@ func NewFakeClientFactory(objects ...runtime.Object) ClientFactory { clientSet.Fake.Resources = ConvertSchemeToAPIResources(scheme) + dynamicClient := fake_dynamic.NewSimpleDynamicClient(scheme, objects...) + return &fakeClientFactory{ - clientSet: clientSet, - objects: objects, - scheme: scheme, + clientSet: clientSet, + dynamicClient: dynamicClient, } } +type HasName interface { + GetName() string +} + +func (f *fakeClientFactory) AddError(gvr schema.GroupVersionResource, name string, namespace string, retErr error) { + f.dynamicClient.PrependReactor("*", gvr.Resource, func(action testing.Action) (handled bool, ret runtime.Object, err error) { + if namespace != "" && namespace != action.GetNamespace() { + return false, nil, nil + } + switch a := action.(type) { + case HasName: + if name != "" && name != a.GetName() { + return false, nil, nil + } + return true, nil, retErr + default: + return true, nil, retErr + } + }) +} + func ConvertSchemeToAPIResources(s *runtime.Scheme) []*metav1.APIResourceList { m := map[schema.GroupVersion][]metav1.APIResource{} - + var allTypes []schema.GroupVersionKind + listTypes := map[schema.GroupVersionKind]bool{} for gvk, _ := range s.AllKnownTypes() { + if gvk.Version == "__internal" { + continue + } + if strings.HasSuffix(gvk.Kind, "List") { + listTypes[gvk] = true + } + allTypes = append(allTypes, gvk) + } + + for _, gvk := range allTypes { + if strings.HasSuffix(gvk.Kind, "List") { + continue + } // we misuse kyaml here n, _ := openapi.IsNamespaceScoped(yaml.TypeMeta{ APIVersion: gvk.GroupVersion().String(), Kind: gvk.Kind, }) + verbs := []string{"delete", "deletecollection", "get", "patch", "create", "update", "watch"} + if listTypes[schema.GroupVersionKind{ + Group: gvk.Group, + Version: gvk.Version, + Kind: gvk.Kind + "List", + }] { + verbs = append(verbs, "list") + } + + singularGvr, _ := meta.UnsafeGuessKindToResource(gvk) + ar := metav1.APIResource{ - Name: buildPluralName(gvk.Kind), + Name: singularGvr.Resource, Namespaced: n, Group: gvk.Group, Version: gvk.Version, Kind: gvk.Kind, - Verbs: []string{"delete", "deletecollection", "get", "list", "patch", "create", "update", "watch"}, + Verbs: verbs, } l, _ := m[gvk.GroupVersion()] @@ -95,7 +143,3 @@ func ConvertSchemeToAPIResources(s *runtime.Scheme) []*metav1.APIResourceList { return ret } - -func buildPluralName(n string) string { - return strings.ToLower(n) + "s" -} diff --git a/pkg/k8s/resources.go b/pkg/k8s/resources.go index a6a5aac81..47ce75c2b 100644 --- a/pkg/k8s/resources.go +++ b/pkg/k8s/resources.go @@ -12,6 +12,7 @@ import ( "k8s.io/apimachinery/pkg/version" "k8s.io/client-go/discovery" "k8s.io/client-go/dynamic" + "runtime" "sort" "strings" "sync" @@ -96,6 +97,9 @@ func (k *k8sResources) updateResources() error { } for _, ar := range arl.APIResources { + if ar.Version == "__internal" { + continue + } if strings.Index(ar.Name, "/") != -1 { // skip subresources continue @@ -125,6 +129,9 @@ func (k *k8sResources) updateResources() error { } } } + if len(k.preferredResources) == 0 { + runtime.Breakpoint() + } return nil } From 4a3ecce1c599fcbce0568111616221d852d17690 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 4 Jan 2023 23:01:48 +0100 Subject: [PATCH 1362/2916] tests: Print KUBECONFIG on start of tests --- e2e/default_clusters.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/e2e/default_clusters.go b/e2e/default_clusters.go index 0d5479d27..b50159416 100644 --- a/e2e/default_clusters.go +++ b/e2e/default_clusters.go @@ -2,6 +2,7 @@ package e2e import "C" import ( + "fmt" "github.com/imdario/mergo" "github.com/kluctl/kluctl/v2/e2e/test-utils" "github.com/kluctl/kluctl/v2/e2e/test_resources" @@ -59,6 +60,8 @@ func init() { mergedKubeconfig = tmpKubeconfig.Name() _ = os.Setenv("KUBECONFIG", mergedKubeconfig) + + _, _ = fmt.Fprintf(os.Stderr, "KUBECONFIG=%s\n", mergedKubeconfig) } func mergeKubeconfig(path string, kubeconfig []byte) { From 404f185a6921328a03b5ecbf61d3273bfac8d6a7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Jan 2023 22:10:45 +0000 Subject: [PATCH 1363/2916] chore(deps): Bump golang.org/x/crypto from 0.4.0 to 0.5.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.4.0 to 0.5.0. - [Release notes](https://github.com/golang/crypto/releases) - [Commits](https://github.com/golang/crypto/compare/v0.4.0...v0.5.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 835759cb6..d6909f797 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( github.com/stretchr/testify v1.8.1 github.com/whilp/git-urls v1.0.0 github.com/xanzy/ssh-agent v0.3.3 - golang.org/x/crypto v0.4.0 + golang.org/x/crypto v0.5.0 golang.org/x/net v0.5.0 golang.org/x/sync v0.1.0 golang.org/x/sys v0.4.0 diff --git a/go.sum b/go.sum index 39517d07d..842181df8 100644 --- a/go.sum +++ b/go.sum @@ -890,8 +890,8 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8= -golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= +golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= +golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= From e7fcb90d1e91b03d2bc5abd64a04763d296c42de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aljoscha=20P=C3=B6rtner?= Date: Fri, 6 Jan 2023 17:48:03 +0100 Subject: [PATCH 1364/2916] tests: Add unit tests for the yaml pkg (#241) * test(yaml): add tests for the yaml pkg Signed-off-by: Aljoscha Poertner * test(yaml): switch to temp folder provided by the testing pkg Signed-off-by: Aljoscha Poertner * test(yaml): use assert pkg and fix typos Signed-off-by: Aljoscha Poertner * test(yaml): remove Fs from func name Signed-off-by: Aljoscha Poertner * test(yaml): remove CreateTempFile Signed-off-by: Aljoscha Poertner * test(yaml): remove redundant information in messages Signed-off-by: Aljoscha Poertner * test(yaml): setup test files without use of temp files Signed-off-by: Aljoscha Poertner * test(yaml): use assert.Empty() instead of assert.Nil() Signed-off-by: Aljoscha Poertner * test(yaml): test read with two docs in file Signed-off-by: Aljoscha Poertner * test(yaml): use assert.Equal() with equal object instead of extracting the value Signed-off-by: Aljoscha Poertner Signed-off-by: Aljoscha Poertner --- pkg/yaml/yaml_test.go | 375 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 375 insertions(+) create mode 100644 pkg/yaml/yaml_test.go diff --git a/pkg/yaml/yaml_test.go b/pkg/yaml/yaml_test.go new file mode 100644 index 000000000..bd2d6b772 --- /dev/null +++ b/pkg/yaml/yaml_test.go @@ -0,0 +1,375 @@ +package yaml + +import ( + "bytes" + "errors" + "fmt" + "github.com/stretchr/testify/assert" + "os" + "path/filepath" + "strings" + "testing" + "testing/iotest" +) + +type EmptyYamlConfig struct { +} + +type SimpleYamlConfig struct { + Value string `yaml:"value"` +} + +func TestReadYamlFile(t *testing.T) { + // Setup variables + existingEmptyYamlFileName := "file_existing_empty.yaml" + var existingEmptyYamlConf EmptyYamlConfig + nonExistingEmptyYamlFileName := "non_existing_empty.yaml" + var nonExistingEmptyYamlConf EmptyYamlConfig + + // Setup temporary file + path := filepath.Join(t.TempDir(), existingEmptyYamlFileName) + os.WriteFile(path, nil, 0600) + + //Read existing empty yaml file + existingEmptyYamlErr := ReadYamlFile(path, &existingEmptyYamlConf) + assert.NoError(t, existingEmptyYamlErr, "Can't read empty yaml file: %s", path) + + //Read non-existing empty yaml file + nonExistingEmptyYamlErr := ReadYamlFile(nonExistingEmptyYamlFileName, &nonExistingEmptyYamlConf) + assert.Error(t, nonExistingEmptyYamlErr, "Should throw an error because %s doesn't exist", nonExistingEmptyYamlFileName) +} + +func TestReadYamlAllFile(t *testing.T) { + // Setup variables + twoDocsYamlContent := `value: anyValue1 +--- +value: anyValue2 +` + expectedTwoDocsYaml := []any{ + map[string]any{ + "value": "anyValue1", + }, + map[string]any{ + "value": "anyValue2", + }, + } + existingEmptyYamlFileName := "file_existing_empty.yaml" + existingTwoDocsYamlFileName := "file_existing_two_docs.yaml" + nonExistingEmptyYamlFileName := "non_existing_empty.yaml" + + // Setup temporary file + path := filepath.Join(t.TempDir(), existingEmptyYamlFileName) + os.WriteFile(path, nil, 0600) + + //Read existing empty yaml file + existingEmptyYamlAllFileResult, existingEmptyYamlAllFileErr := ReadYamlAllFile(path) + assert.NoError(t, existingEmptyYamlAllFileErr, "Can't read empty yaml file: %s", path) + assert.Empty(t, existingEmptyYamlAllFileResult, "Empty YAML stream read incorrectly. Value should be empty") + + //Read existing empty yaml file with two documents + path = filepath.Join(t.TempDir(), existingTwoDocsYamlFileName) + os.WriteFile(path, []byte(twoDocsYamlContent), 0600) + + twoDocsYamlAllFileResult, twoDocsYamlAllFileErr := ReadYamlAllFile(path) + assert.NoError(t, twoDocsYamlAllFileErr, "Can't read yaml file: %s", path) + assert.Equal(t, expectedTwoDocsYaml, twoDocsYamlAllFileResult) + + //Read non-existing empty yaml file + nonExistingEmptyYamlAllFileResult, nonExistingEmptyYamlAllFileErr := ReadYamlAllFile(nonExistingEmptyYamlFileName) + assert.Error(t, nonExistingEmptyYamlAllFileErr, "Should throw an error because %s doesn't exist", nonExistingEmptyYamlFileName) + assert.Nil(t, nonExistingEmptyYamlAllFileResult, "Empty YAML stream read incorrectly. Value should be nil because file doesn't exist.") +} + +func TestReadYamlStream(t *testing.T) { + // Setup variables + simpleYamlDataString := ` + value: anyValue + ` + simpleYamlDataReader := bytes.NewReader([]byte(simpleYamlDataString)) + var existingSimpleYamlConf SimpleYamlConfig + + existingReadYamlStreamErr := ReadYamlStream(simpleYamlDataReader, &existingSimpleYamlConf) + assert.NoError(t, existingReadYamlStreamErr, "Can't read simple yaml stream: %s", existingReadYamlStreamErr) + + //Check if it can handle errors + errorReadYamlStreamErr := ReadYamlStream(iotest.ErrReader(errors.New("timeout")), &EmptyYamlConfig{}) + assert.Error(t, errorReadYamlStreamErr, "It should throw an error because of a timeout") +} + +func TestReadYamlString(t *testing.T) { + // Setup variables + simpleYamlDataString := ` + value: anyValue + ` + var existingSimpleYamlConf SimpleYamlConfig + + existingReadYamlStringErr := ReadYamlString(simpleYamlDataString, &existingSimpleYamlConf) + assert.NoError(t, existingReadYamlStringErr, "Can't read simple yaml stream: %s", existingReadYamlStringErr) +} + +func TestReadYamlAllString(t *testing.T) { + // Setup variables + expectedSimpleYamlAllString := []any{ + map[string]any{ + "value": "anyValue", + }, + } + simpleYamlDataString := ` + value: anyValue + ` + simpleYamlAllStringResult, simpleYamlAllStringErr := ReadYamlAllString(simpleYamlDataString) + assert.NoError(t, simpleYamlAllStringErr, "Can't read simple yaml stream: %s", simpleYamlAllStringErr) + + assert.Equal(t, expectedSimpleYamlAllString, simpleYamlAllStringResult, "Simple YAML stream read incorrectly") +} + +func TestReadYamlBytes(t *testing.T) { + // Setup variables + simpleYamlDataString := ` + value: anyValue + ` + var existingSimpleYamlConf SimpleYamlConfig + simpleYamlDataBytes := []byte(simpleYamlDataString) + existingReadYamlBytesErr := ReadYamlBytes(simpleYamlDataBytes, &existingSimpleYamlConf) + assert.NoError(t, existingReadYamlBytesErr, "Can't read simple yaml stream: %s", existingReadYamlBytesErr) +} + +func TestReadYamlAllBytes(t *testing.T) { + // Setup variables + expectedSimpleYamlAllBytes := []any{ + map[string]any{ + "value": "anyValue", + }, + } + simpleYamlDataString := ` + value: anyValue + ` + simpleYamlDataBytes := []byte(simpleYamlDataString) + simpleYamlAllBytesResult, simpleYamlAllBytesErr := ReadYamlAllBytes(simpleYamlDataBytes) + + assert.NoError(t, simpleYamlAllBytesErr, "Can't read simple yaml stream: %s", simpleYamlAllBytesErr) + assert.Equal(t, expectedSimpleYamlAllBytes, simpleYamlAllBytesResult, "Simple YAML stream read incorrectly") +} + +func TestReadYamlAllStream(t *testing.T) { + expectedSimpleYamlAllStream := []any{ + map[string]any{ + "value": "anyValue", + }, + } + // Setup variables + simpleYamlDataReader := bytes.NewReader([]byte("value: 'anyValue'")) + emptyYamlDataReader := bytes.NewReader([]byte("")) + + //Read empty yaml from stream + emptyYamlAllStreamResult, emptyYamlAllStreamResultErr := ReadYamlAllStream(emptyYamlDataReader) + assert.NoError(t, emptyYamlAllStreamResultErr, "Can't read empty yaml stream: %s", emptyYamlAllStreamResultErr) + assert.Nil(t, emptyYamlAllStreamResult, "Empty YAML stream read incorrectly. Value should be nil") + + //Read simple yaml from stream + simpleYamlAllStreamResult, simpleYamlAllStreamResultErr := ReadYamlAllStream(simpleYamlDataReader) + assert.NoError(t, simpleYamlAllStreamResultErr, "Can't read simple yaml stream: %s", simpleYamlAllStreamResultErr) + + assert.Equal(t, expectedSimpleYamlAllStream, simpleYamlAllStreamResult, "Simple YAML stream read incorrectly") + + //Check if it can handle errors + _, errorReadYamlAllStreamErr := ReadYamlAllStream(iotest.ErrReader(errors.New("timeout"))) + assert.Error(t, errorReadYamlAllStreamErr, "It should throw an error because of a timeout") +} + +func TestWriteYamlAllFile(t *testing.T) { + // Setup variables + yamlFileName := "file.yaml" + var yaml []any + yaml = append(yaml, SimpleYamlConfig{ + Value: "anyValue1", + }, SimpleYamlConfig{ + Value: "anyValue2", + }) + expectedString := `value: anyValue1 +--- +value: anyValue2 +` + + // Setup temporary file + path := filepath.Join(t.TempDir(), yamlFileName) + os.WriteFile(path, nil, 0600) + + //Check if writing multiple YAML works + writeYamlAllFileErr := WriteYamlAllFile(path, yaml) + assert.NoError(t, writeYamlAllFileErr, "Error while trying to write YAML to file") + + b, err := os.ReadFile(path) + assert.NoError(t, err, "Error while reading file for evaluation") + assert.Equal(t, expectedString, string(b), "Yaml not written correctly") +} + +func TestWriteYamlFile(t *testing.T) { + // Setup variables + yamlFileName := "file.yaml" + yaml := SimpleYamlConfig{ + Value: "anyValue1", + } + expectedString := `value: anyValue1 +` + + // Setup temporary file + path := filepath.Join(t.TempDir(), yamlFileName) + + //Check if writing a single YAML works + writeYamlFileErr := WriteYamlFile(path, yaml) + assert.NoError(t, writeYamlFileErr, "Error while trying to write YAML to file.") + + b, err := os.ReadFile(path) + assert.NoError(t, err, "Error while reading file for evaluation") + assert.Equal(t, expectedString, string(b), "Yaml not written correctly.") +} + +func TestWriteYamlString(t *testing.T) { + // Setup variables + yaml := SimpleYamlConfig{ + Value: "anyValue1", + } + expectedString := `value: anyValue1 +` + // Write YAML to String + writeYamlStringResult, writeYamlStringErr := WriteYamlString(yaml) + assert.NoError(t, writeYamlStringErr, "Can't write simple yaml string: %s", writeYamlStringErr) + assert.Equal(t, expectedString, writeYamlStringResult, "Yaml not written correctly.") +} + +func TestWriteYamlAllString(t *testing.T) { + // Setup variables + var yaml []any + yaml = append(yaml, SimpleYamlConfig{ + Value: "anyValue1", + }, SimpleYamlConfig{ + Value: "anyValue2", + }) + expectedString := `value: anyValue1 +--- +value: anyValue2 +` + // Write multiple YAML to String + writeYamlAllStringResult, writeYamlAllStringErr := WriteYamlAllString(yaml) + assert.NoError(t, writeYamlAllStringErr, "Can't write simple yaml string: %s", writeYamlAllStringErr) + assert.Equal(t, expectedString, writeYamlAllStringResult, "Yaml not written correctly.") +} + +func TestWriteYamlBytes(t *testing.T) { + // Setup variables + yaml := SimpleYamlConfig{ + Value: "anyValue1", + } + expectedString := `value: anyValue1 +` + expectedBytes := []byte(expectedString) + // Write YAML to bytes + writeYamlBytesResult, writeYamlBytesErr := WriteYamlBytes(yaml) + assert.NoError(t, writeYamlBytesErr, "Can't write simple yaml string: %s", writeYamlBytesErr) + assert.Equal(t, expectedBytes, writeYamlBytesResult, "Yaml not written correctly.") +} + +func TestWriteYamlAllBytes(t *testing.T) { + // Setup variables + var yaml []any + yaml = append(yaml, SimpleYamlConfig{ + Value: "anyValue1", + }, SimpleYamlConfig{ + Value: "anyValue2", + }) + expectedString := `value: anyValue1 +--- +value: anyValue2 +` + expectedBytes := []byte(expectedString) + // Write multiple YAML to bytes + writeYamlAllBytesResult, writeYamlAllBytesErr := WriteYamlAllBytes(yaml) + assert.NoError(t, writeYamlAllBytesErr, "Can't write simple yaml string: %s", writeYamlAllBytesErr) + assert.Equal(t, expectedBytes, writeYamlAllBytesResult, "Yaml not written correctly.") +} + +func TestWriteYamlAllStream(t *testing.T) { + // Setup variables + var yaml []any + yaml = append(yaml, SimpleYamlConfig{ + Value: "anyValue1", + }, SimpleYamlConfig{ + Value: "anyValue2", + }) + expectedString := `value: anyValue1 +--- +value: anyValue2 +` + var buffer bytes.Buffer + // Write multiple YAML to stream + writeYamlAllStreamErr := WriteYamlAllStream(&buffer, yaml) + assert.NoError(t, writeYamlAllStreamErr, "Can't write simple yaml string: %s", writeYamlAllStreamErr) + assert.Equal(t, expectedString, buffer.String(), "Yaml not written correctly.") +} + +func TestConvertYamlToJson(t *testing.T) { + yaml := `value: anyValue1` + expectedJson := `{"value":"anyValue1"}` + yamlBytes := []byte(yaml) + expectedJsonBytes := []byte(expectedJson) + jsonBytes, convertYamlToJsonErr := ConvertYamlToJson(yamlBytes) + assert.NoError(t, convertYamlToJsonErr, "Can't convert yaml to json: %s", convertYamlToJsonErr) + assert.Equal(t, expectedJsonBytes, jsonBytes, "Yaml not converted correctly.") +} + +func TestWriteJsonString(t *testing.T) { + yaml := SimpleYamlConfig{ + Value: "anyValue1", + } + expectedJson := `{"value":"anyValue1"}` + writeJsonStringResult, writeJsonStringErr := WriteJsonString(yaml) + assert.NoError(t, writeJsonStringErr, "Can't write yaml to json string: %s", writeJsonStringErr) + assert.Equal(t, expectedJson, writeJsonStringResult, "Yaml not converted correctly.") +} + +func TestRemoveDuplicateFields(t *testing.T) { + // Check if duplicate field gets removed + duplicateYaml := `value: anyValue1 +value: anyValue2 +` + expectedDuplicateYaml := `value: anyValue2 +` + expectedDuplicateYamlBytes := []byte(expectedDuplicateYaml) + duplicateFieldYamlDataReader := bytes.NewReader([]byte(duplicateYaml)) + removeDuplicateFieldsResult, removeDuplicateFieldsErr := RemoveDuplicateFields(duplicateFieldYamlDataReader) + assert.NoError(t, removeDuplicateFieldsErr, "Can't remove duplicate fields: %s", removeDuplicateFieldsErr) + assert.Equal(t, expectedDuplicateYamlBytes, removeDuplicateFieldsResult, "Duplicate fields not removed correctly.") + + // Check if non-duplicate fields are untouched + nonDuplicateYaml := `value1: anyValue +value2: anyValue +` + expectedNonDuplicateYaml := `value1: anyValue +value2: anyValue +` + expectedNonDuplicateYamlBytes := []byte(expectedNonDuplicateYaml) + + nonDuplicateFieldYamlDataReader := bytes.NewReader([]byte(nonDuplicateYaml)) + removeNonDuplicateFieldsResult, removeNonDuplicateFieldsErr := RemoveDuplicateFields(nonDuplicateFieldYamlDataReader) + assert.NoError(t, removeNonDuplicateFieldsErr, "Can't remove duplicate fields: %s", removeNonDuplicateFieldsErr) + assert.Equal(t, expectedNonDuplicateYamlBytes, removeNonDuplicateFieldsResult, "Duplicate fields not removed correctly.") +} + +func TestFixPathExt(t *testing.T) { + // Check if *.yaml gets converted + path := filepath.Join(t.TempDir(), "fix_path_ext.yml") + os.WriteFile(path, nil, 0600) + + yamlFileName := fmt.Sprintf("%s.yaml", strings.TrimSuffix(path, filepath.Ext(path))) + fixedYamlFileName := FixPathExt(yamlFileName) + assert.Equal(t, path, fixedYamlFileName, "Fix of path extension failed!") + + // Check if *.yml gets converted + path = filepath.Join(t.TempDir(), "fix_path_ext.yaml") + os.WriteFile(path, nil, 0600) + + ymlFileName := fmt.Sprintf("%s.yml", strings.TrimSuffix(path, filepath.Ext(path))) + fixedYmlFileName := FixPathExt(ymlFileName) + assert.Equal(t, path, fixedYmlFileName, "Fix of path extension failed!") +} From 6160c4a1c604630af9c252234a0981b664ed6bf5 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 12 Jan 2023 09:21:55 +0100 Subject: [PATCH 1365/2916] fix: Don't crash when encountering nil maps in SetNestedField --- pkg/utils/uo/nested_fields.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/utils/uo/nested_fields.go b/pkg/utils/uo/nested_fields.go index b3927dc9d..7d3ce0afd 100644 --- a/pkg/utils/uo/nested_fields.go +++ b/pkg/utils/uo/nested_fields.go @@ -28,7 +28,7 @@ func (uo *UnstructuredObject) SetNestedField(value interface{}, keys ...interfac if err != nil { return err } - if ok { + if ok && val != nil { o = val } else { newVal := make(map[string]interface{}) From 74ccf49860080e01aa028c746c7b60a6009a2c10 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 12 Jan 2023 09:41:00 +0100 Subject: [PATCH 1366/2916] fix: Only query for known GroupKinds when calling validate This should remove some load to the apiserver when validate is used periodically (like in the Kluctl Controller). --- pkg/deployment/commands/delete.go | 2 +- pkg/deployment/commands/deploy.go | 2 +- pkg/deployment/commands/diff.go | 2 +- pkg/deployment/commands/poke_images.go | 2 +- pkg/deployment/commands/prune.go | 2 +- pkg/deployment/commands/validate.go | 2 +- pkg/deployment/utils/remote_objects_utils.go | 24 ++++++++++++++++--- .../utils/remote_objects_utils_test.go | 2 +- 8 files changed, 28 insertions(+), 10 deletions(-) diff --git a/pkg/deployment/commands/delete.go b/pkg/deployment/commands/delete.go index e32ae759f..6a3040cda 100644 --- a/pkg/deployment/commands/delete.go +++ b/pkg/deployment/commands/delete.go @@ -42,7 +42,7 @@ func (cmd *DeleteCommand) Run(ctx context.Context, k *k8s.K8sCluster) ([]k8s2.Ob inclusion = cmd.c.Inclusion } - err := ru.UpdateRemoteObjects(k, labels, nil) + err := ru.UpdateRemoteObjects(k, labels, nil, false) if err != nil { return nil, err } diff --git a/pkg/deployment/commands/deploy.go b/pkg/deployment/commands/deploy.go index c82a6a303..bbf4284dc 100644 --- a/pkg/deployment/commands/deploy.go +++ b/pkg/deployment/commands/deploy.go @@ -30,7 +30,7 @@ func (cmd *DeployCommand) Run(ctx context.Context, k *k8s.K8sCluster, diffResult dew := utils2.NewDeploymentErrorsAndWarnings() ru := utils2.NewRemoteObjectsUtil(ctx, dew) - err := ru.UpdateRemoteObjects(k, cmd.c.Project.GetCommonLabels(), cmd.c.LocalObjectRefs()) + err := ru.UpdateRemoteObjects(k, cmd.c.Project.GetCommonLabels(), cmd.c.LocalObjectRefs(), false) if err != nil { return nil, err } diff --git a/pkg/deployment/commands/diff.go b/pkg/deployment/commands/diff.go index dd811a84a..890908fc0 100644 --- a/pkg/deployment/commands/diff.go +++ b/pkg/deployment/commands/diff.go @@ -29,7 +29,7 @@ func (cmd *DiffCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*types.Comm dew := utils.NewDeploymentErrorsAndWarnings() ru := utils.NewRemoteObjectsUtil(ctx, dew) - err := ru.UpdateRemoteObjects(k, cmd.c.Project.GetCommonLabels(), cmd.c.LocalObjectRefs()) + err := ru.UpdateRemoteObjects(k, cmd.c.Project.GetCommonLabels(), cmd.c.LocalObjectRefs(), false) if err != nil { return nil, err } diff --git a/pkg/deployment/commands/poke_images.go b/pkg/deployment/commands/poke_images.go index 3bcaaef36..3c99172d1 100644 --- a/pkg/deployment/commands/poke_images.go +++ b/pkg/deployment/commands/poke_images.go @@ -28,7 +28,7 @@ func (cmd *PokeImagesCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*type dew := utils2.NewDeploymentErrorsAndWarnings() ru := utils2.NewRemoteObjectsUtil(ctx, dew) - err := ru.UpdateRemoteObjects(k, cmd.c.Project.GetCommonLabels(), cmd.c.LocalObjectRefs()) + err := ru.UpdateRemoteObjects(k, nil, cmd.c.LocalObjectRefs(), false) if err != nil { return nil, err } diff --git a/pkg/deployment/commands/prune.go b/pkg/deployment/commands/prune.go index bc2336e74..e3b5f4689 100644 --- a/pkg/deployment/commands/prune.go +++ b/pkg/deployment/commands/prune.go @@ -22,7 +22,7 @@ func (cmd *PruneCommand) Run(ctx context.Context, k *k8s.K8sCluster) ([]k8s2.Obj dew := utils2.NewDeploymentErrorsAndWarnings() ru := utils2.NewRemoteObjectsUtil(ctx, dew) - err := ru.UpdateRemoteObjects(k, cmd.c.Project.GetCommonLabels(), nil) + err := ru.UpdateRemoteObjects(k, cmd.c.Project.GetCommonLabels(), nil, false) if err != nil { return nil, err } diff --git a/pkg/deployment/commands/validate.go b/pkg/deployment/commands/validate.go index a5781f855..010fa9868 100644 --- a/pkg/deployment/commands/validate.go +++ b/pkg/deployment/commands/validate.go @@ -31,7 +31,7 @@ func (cmd *ValidateCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*types. cmd.dew.Init() - err := cmd.ru.UpdateRemoteObjects(k, cmd.c.Project.GetCommonLabels(), cmd.c.LocalObjectRefs()) + err := cmd.ru.UpdateRemoteObjects(k, cmd.c.Project.GetCommonLabels(), cmd.c.LocalObjectRefs(), true) if err != nil { return nil, err } diff --git a/pkg/deployment/utils/remote_objects_utils.go b/pkg/deployment/utils/remote_objects_utils.go index cb378acd9..1ff37177f 100644 --- a/pkg/deployment/utils/remote_objects_utils.go +++ b/pkg/deployment/utils/remote_objects_utils.go @@ -30,7 +30,7 @@ func NewRemoteObjectsUtil(ctx context.Context, dew *DeploymentErrorsAndWarnings) } } -func (u *RemoteObjectUtils) getAllByLabels(k *k8s.K8sCluster, labels map[string]string) error { +func (u *RemoteObjectUtils) getAllByLabels(k *k8s.K8sCluster, labels map[string]string, onlyUsedGKs map[schema.GroupKind]bool) error { var mutex sync.Mutex if len(labels) == 0 { return nil @@ -44,6 +44,15 @@ func (u *RemoteObjectUtils) getAllByLabels(k *k8s.K8sCluster, labels map[string] permissionErrCount := 0 gvks := k.Resources.GetFilteredPreferredGVKs(func(ar *v1.APIResource) bool { + if onlyUsedGKs != nil { + gk := schema.GroupKind{ + Group: ar.Group, + Kind: ar.Kind, + } + if !onlyUsedGKs[gk] { + return false + } + } return utils.FindStrInSlice(ar.Verbs, "list") != -1 }) @@ -148,12 +157,21 @@ func (u *RemoteObjectUtils) getMissingObjects(k *k8s.K8sCluster, refs []k8s2.Obj return g.ErrorOrNil() } -func (u *RemoteObjectUtils) UpdateRemoteObjects(k *k8s.K8sCluster, labels map[string]string, refs []k8s2.ObjectRef) error { +func (u *RemoteObjectUtils) UpdateRemoteObjects(k *k8s.K8sCluster, labels map[string]string, refs []k8s2.ObjectRef, onlyUsedGKs bool) error { if k == nil { return nil } - err := u.getAllByLabels(k, labels) + var usedGKs map[schema.GroupKind]bool + + if onlyUsedGKs { + usedGKs = map[schema.GroupKind]bool{} + for _, ref := range refs { + usedGKs[ref.GVK.GroupKind()] = true + } + } + + err := u.getAllByLabels(k, labels, usedGKs) if err != nil { return err } diff --git a/pkg/deployment/utils/remote_objects_utils_test.go b/pkg/deployment/utils/remote_objects_utils_test.go index 519c8ebf2..aaa1671a2 100644 --- a/pkg/deployment/utils/remote_objects_utils_test.go +++ b/pkg/deployment/utils/remote_objects_utils_test.go @@ -39,7 +39,7 @@ func TestRemoteObjectUtils_PermissionErrors(t *testing.T) { u := NewRemoteObjectsUtil(context.Background(), dew) err = u.UpdateRemoteObjects(k, map[string]string{"l1": "v1"}, []k8s2.ObjectRef{ k8s2.NewObjectRef("", "v1", "Secret", "secret", "default"), - }) + }, false) assert.NoError(t, err) assert.Equal(t, []types.DeploymentError{{ Error: "at least one permission error was encountered while gathering objects by labels. This might result in orphan object detection to not work properly"}, From 8a599de21ef6c3d45749855a302fba641124df22 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 13 Jan 2023 08:54:49 +0100 Subject: [PATCH 1367/2916] Revert "chore: Download kubebuilder-tools for windows from kubebuilder-tools-releases-windows repo" This reverts commit 44982e64a4a974aa27b3ff5f5250043c7f992521. --- Makefile | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/Makefile b/Makefile index dcae8fd0d..69a9805ac 100644 --- a/Makefile +++ b/Makefile @@ -64,21 +64,11 @@ setup-envtest: ## Download envtest-setup locally if necessary. $(call go-install-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest@latest) # Download the envtest binaries to testbin -ENVTEST_KUBERNETES_VERSION?=1.25.0 ENVTEST_ASSETS_DIR=$(BUILD_DIR)/testbin - -ifeq ($(OS),Windows_NT) -ENVTEST_ASSETS_DIR_WINDOWS=$(ENVTEST_ASSETS_DIR)/k8s/$(ENVTEST_KUBERNETES_VERSION)-windows-amd64 -install-envtest: setup-envtest $(ENVTEST_ASSETS_DIR_WINDOWS)/etcd.exe -$(ENVTEST_ASSETS_DIR_WINDOWS)/etcd.exe: - mkdir -p $(ENVTEST_ASSETS_DIR_WINDOWS) - curl -o $(ENVTEST_ASSETS_DIR_WINDOWS)/kubebuilder-tools.tar.gz "https://raw.githubusercontent.com/kluctl/kubebuilder-tools-releases-windows/main/releases/kubebuilder-tools-$(ENVTEST_KUBERNETES_VERSION)_windows_amd64.tar.gz" - cd $(ENVTEST_ASSETS_DIR_WINDOWS) && tar xzf kubebuilder-tools.tar.gz && mv kubebuilder/bin/* . - rm $(ENVTEST_ASSETS_DIR_WINDOWS)/kubebuilder-tools.tar.gz -else +ENVTEST_KUBERNETES_VERSION?=latest install-envtest: setup-envtest + mkdir -p ${ENVTEST_ASSETS_DIR} $(ENVTEST) use $(ENVTEST_KUBERNETES_VERSION) --arch=$(ENVTEST_ARCH) --bin-dir=$(ENVTEST_ASSETS_DIR) -endif ## Test: test: test-unit test-e2e ## Runs the complete test suite From 1253253ab097cd2f1aa11b3b0a35e5bc44dcdefb Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 13 Jan 2023 10:36:36 +0100 Subject: [PATCH 1368/2916] fix: findCommit did not account for tags --- pkg/repocache/cache.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/repocache/cache.go b/pkg/repocache/cache.go index 9d9e70b32..2d5dd519a 100644 --- a/pkg/repocache/cache.go +++ b/pkg/repocache/cache.go @@ -158,13 +158,14 @@ func (e *CacheEntry) GetRepoInfo() RepoInfo { } func (e *CacheEntry) findCommit(ref string) (string, string, error) { - if strings.HasPrefix(ref, "refs/heads") { + switch { + case strings.HasPrefix(ref, "refs/heads"), strings.HasPrefix(ref, "refs/tags"): c, ok := e.refs[ref] if !ok { return "", "", fmt.Errorf("ref %s not found", ref) } return ref, c, nil - } else { + default: ref2 := "refs/heads/" + ref c, ok := e.refs[ref2] if ok { From 712804cd02a6f3291bb43d0c3e3c5b1495f0a4d1 Mon Sep 17 00:00:00 2001 From: Aljoscha Poertner Date: Thu, 19 Jan 2023 17:09:25 +0100 Subject: [PATCH 1369/2916] refactor(vars): use aws arn parser when region is nil Signed-off-by: Aljoscha Poertner --- pkg/vars/aws/secrets_manager.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/vars/aws/secrets_manager.go b/pkg/vars/aws/secrets_manager.go index d3cced231..b258e2c5d 100644 --- a/pkg/vars/aws/secrets_manager.go +++ b/pkg/vars/aws/secrets_manager.go @@ -3,12 +3,13 @@ package aws import ( "context" "fmt" + arn2 "github.com/aws/aws-sdk-go-v2/aws/arn" "github.com/aws/aws-sdk-go-v2/service/secretsmanager" ) func GetAwsSecretsManagerSecret(ctx context.Context, aws AwsClientFactory, profile *string, region *string, secretName string) (string, error) { if region == nil { - arn, err := ParseArn(secretName) + arn, err := arn2.Parse(secretName) if err != nil { return "", fmt.Errorf("when omitting the AWS region, the secret name must be a valid ARN") } From ec62c3175ad8203b9373e6cb5e0faca0c1833b2c Mon Sep 17 00:00:00 2001 From: Aljoscha Poertner Date: Thu, 19 Jan 2023 17:11:01 +0100 Subject: [PATCH 1370/2916] refactor(vars): extract splitting of resource to a separate method Signed-off-by: Aljoscha Poertner --- pkg/vars/aws/arn.go | 38 ++++++++++---------------------------- 1 file changed, 10 insertions(+), 28 deletions(-) diff --git a/pkg/vars/aws/arn.go b/pkg/vars/aws/arn.go index 3b7141e99..8fafed9ec 100644 --- a/pkg/vars/aws/arn.go +++ b/pkg/vars/aws/arn.go @@ -1,42 +1,24 @@ package aws import ( - "fmt" "strings" ) -type Arn struct { - Arn string - Partition string - Service string - Region string - Account string - Resource string +type Resource struct { + ResourceId string ResourceType string } -func ParseArn(arn string) (Arn, error) { - // http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html - elements := strings.SplitN(arn, ":", 6) - if len(elements) < 6 { - return Arn{}, fmt.Errorf("%s is not a valid arn", arn) - } - var result Arn - result.Arn = elements[0] - result.Partition = elements[1] - result.Service = elements[2] - result.Region = elements[3] - result.Account = elements[4] - result.Resource = elements[5] - - if strings.Index(result.Resource, "/") != -1 { - s := strings.SplitN(result.Resource, "/", 2) +func SplitResource(resource string) (Resource, error) { + result := Resource{resource, ""} + if strings.Index(resource, "/") != -1 { + s := strings.SplitN(resource, "/", 2) result.ResourceType = s[0] - result.Resource = s[1] - } else if strings.Index(result.Resource, ":") != -1 { - s := strings.SplitN(result.Resource, ":", 2) + result.ResourceId = s[1] + } else if strings.Index(resource, ":") != -1 { + s := strings.SplitN(resource, ":", 2) result.ResourceType = s[0] - result.Resource = s[1] + result.ResourceId = s[1] } return result, nil } From 7ff58f8bd9a28b45b72f2394988c92ce0f782ef5 Mon Sep 17 00:00:00 2001 From: Aljoscha Poertner Date: Thu, 19 Jan 2023 17:12:10 +0100 Subject: [PATCH 1371/2916] refactor(vars): use aws arn parser in FakeAwsClientFactory Signed-off-by: Aljoscha Poertner --- pkg/vars/aws/fake_clientfactory.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/pkg/vars/aws/fake_clientfactory.go b/pkg/vars/aws/fake_clientfactory.go index 18a170338..45859a5e1 100644 --- a/pkg/vars/aws/fake_clientfactory.go +++ b/pkg/vars/aws/fake_clientfactory.go @@ -3,6 +3,7 @@ package aws import ( "context" "fmt" + arn2 "github.com/aws/aws-sdk-go-v2/aws/arn" "github.com/aws/aws-sdk-go-v2/service/secretsmanager" "github.com/aws/aws-sdk-go-v2/service/secretsmanager/types" ) @@ -14,16 +15,18 @@ type FakeAwsClientFactory struct { } func (f *FakeAwsClientFactory) GetSecretValue(ctx context.Context, params *secretsmanager.GetSecretValueInput, optFns ...func(*secretsmanager.Options)) (*secretsmanager.GetSecretValueOutput, error) { - name := *params.SecretId - arn, err := ParseArn(*params.SecretId) + var arnResource Resource + arn, err := arn2.Parse(*params.SecretId) if err == nil { - name = arn.Resource + arnResource, _ = SplitResource(arn.Resource) + } else { + arnResource, _ = SplitResource(*params.SecretId) } - s, ok := f.Secrets[name] + s, ok := f.Secrets[arnResource.ResourceId] if ok { return &secretsmanager.GetSecretValueOutput{ - Name: &name, + Name: &arnResource.ResourceId, SecretString: &s, }, nil } From 4bba601909ba0a36dd34d5b27b3285039fbb14f7 Mon Sep 17 00:00:00 2001 From: Petr Michalec Date: Fri, 3 Feb 2023 14:21:44 +0100 Subject: [PATCH 1372/2916] feat: Implement --local-git-group-override with group match on repo path (#273) * improve local-git-override * mid-work-updates from review * mid-work-updates from review * refactor: Move clonded dir calculation to the top * feat: Implement logic to filter for git group overrides * update documentation * update docs, revert subDir validation * revert ValidateGitProject changes * update command description --------- Co-authored-by: Petr Michalec Co-authored-by: Alexander Block --- cmd/kluctl/args/project.go | 3 +- cmd/kluctl/commands/utils.go | 30 +++-- docs/reference/commands/common-arguments.md | 73 +++++----- pkg/helm/helm_release.go | 7 +- pkg/repocache/cache.go | 140 +++++++++++++------- 5 files changed, 159 insertions(+), 94 deletions(-) diff --git a/cmd/kluctl/args/project.go b/cmd/kluctl/args/project.go index eff3ec1f0..c14455303 100644 --- a/cmd/kluctl/args/project.go +++ b/cmd/kluctl/args/project.go @@ -27,7 +27,8 @@ type ProjectFlags struct { Timeout time.Duration `group:"project" help:"Specify timeout for all operations, including loading of the project, all external api calls and waiting for readiness." default:"10m"` GitCacheUpdateInterval time.Duration `group:"project" help:"Specify the time to wait between git cache updates. Defaults to not wait at all and always updating caches."` - LocalGitOverride []string `group:"project" help:"Specify a local git override in the form of 'github.com:my-org/my-repo=/local/path/to/override'. This will cause kluctl to not use git to clone for the specified repository but instead use the local directory. This is useful in case you need to test out changes in external git repositories without pushing them. To only override a single branch of the repo, use 'github.com:my-org/my-repo:my-branch=/local/path/to/override'"` + LocalGitOverride []string `group:"project" help:"Specify a single repository local git override in the form of 'github.com:my-org/my-repo=/local/path/to/override'. This will cause kluctl to not use git to clone for the specified repository but instead use the local directory. This is useful in case you need to test out changes in external git repositories without pushing them. To only override a single branch of the repo, use 'github.com:my-org/my-repo:my-branch=/local/path/to/override'"` + LocalGitGroupOverride []string `group:"project" help:"Same as --local-git-override, but for a whole group prefix instead of a single repository. All repositories that have the given prefix will be overridden with the given local path and the repository suffix appended. For example, 'gitlab.com:some-org/sub-org=/local/path/to/my-forks' will override all repositories below 'gitlab.com:some-org/sub-org/' with the repositories found in '/local/path/to/my-forks'. It will however only perform an override if the given repository actually exists locally and otherwise revert to the actual (non-overridden) repository."` } type ArgsFlags struct { diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index a209d2829..63da52161 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -3,6 +3,9 @@ package commands import ( "context" "fmt" + "os" + "strings" + "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/deployment" "github.com/kluctl/kluctl/v2/pkg/git" @@ -20,8 +23,6 @@ import ( "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd/api" - "os" - "strings" ) func withKluctlProjectFromArgs(ctx context.Context, projectFlags args.ProjectFlags, strictTemplates bool, forCompletion bool, cb func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error) error { @@ -54,9 +55,16 @@ func withKluctlProjectFromArgs(ctx context.Context, projectFlags args.ProjectFla var repoOverrides []repocache.RepoOverride for _, x := range projectFlags.LocalGitOverride { - ro, err := parseRepoOverride(x) + ro, err := parseRepoOverride(x, false) if err != nil { - return err + return fmt.Errorf("invalid --local-git-override: %w", err) + } + repoOverrides = append(repoOverrides, ro) + } + for _, x := range projectFlags.LocalGitGroupOverride { + ro, err := parseRepoOverride(x, true) + if err != nil { + return fmt.Errorf("invalid --local-git-group-override: %w", err) } repoOverrides = append(repoOverrides, ro) } @@ -226,23 +234,25 @@ func clientConfigGetter(forCompletion bool) func(context *string) (*rest.Config, } } -func parseRepoOverride(s string) (ret repocache.RepoOverride, err error) { +func parseRepoOverride(s string, isGroup bool) (ret repocache.RepoOverride, err error) { + ret.IsGroup = isGroup + sp := strings.SplitN(s, "=", 2) if len(sp) != 2 { - return repocache.RepoOverride{}, fmt.Errorf("invalid --local-git-override %s", s) + return repocache.RepoOverride{}, fmt.Errorf("%s", s) } sp2 := strings.Split(sp[0], ":") if len(sp2) < 2 || len(sp2) > 3 { - return repocache.RepoOverride{}, fmt.Errorf("invalid --local-git-override %s", s) + return repocache.RepoOverride{}, fmt.Errorf("%s", s) } u, err := git_url.Parse(fmt.Sprintf("%s:%s", sp2[0], sp2[1])) if err != nil { - return repocache.RepoOverride{}, fmt.Errorf("invalid --local-git-override %s: %w", s, err) + return repocache.RepoOverride{}, fmt.Errorf("%s: %w", s, err) } - - ret.RepoKey = u.NormalizedRepoKey() + u = u.Normalize() + ret.RepoUrl = *u if len(sp2) == 3 { ret.Ref = sp2[2] } diff --git a/docs/reference/commands/common-arguments.md b/docs/reference/commands/common-arguments.md index 0f3150151..ac7f3d69a 100644 --- a/docs/reference/commands/common-arguments.md +++ b/docs/reference/commands/common-arguments.md @@ -39,37 +39,48 @@ They control where and how to load the kluctl project and deployment project. Project arguments: Define where and how to load the kluctl project and its components from. - -a, --arg stringArray Passes a template argument in the form of name=value. Nested args can - be set with the '-a my.nested.arg=value' syntax. Values are - interpreted as yaml values, meaning that 'true' and 'false' will lead - to boolean values and numbers will be treated as numbers. Use quotes - if you want these to be treated as strings. If the value starts with - @, it is treated as a file, meaning that the contents of the file - will be loaded and treated as yaml. - --args-from-file stringArray Loads a yaml file and makes it available as arguments, meaning that - they will be available thought the global 'args' variable. - --context string Overrides the context name specified in the target. If the selected - target does not specify a context or the no-name target is used, - --context will override the currently active context. - --git-cache-update-interval duration Specify the time to wait between git cache updates. Defaults to not - wait at all and always updating caches. - --local-git-override stringArray Specify a local git override in the form of - 'github.com:my-org/my-repo=/local/path/to/override'. This will cause - kluctl to not use git to clone for the specified repository but - instead use the local directory. This is useful in case you need to - test out changes in external git repositories without pushing them. - To only override a single branch of the repo, use - 'github.com:my-org/my-repo:my-branch=/local/path/to/override' - -c, --project-config existingfile Location of the .kluctl.yaml config file. Defaults to - $PROJECT/.kluctl.yaml - --project-dir existingdir Specify the project directory. Defaults to the current working directory. - -t, --target string Target name to run command for. Target must exist in .kluctl.yaml. - -T, --target-name-override string Overrides the target name. If -t is used at the same time, then the - target will be looked up based on -t and then renamed to the - value of -T. If no target is specified via -t, then the no-name - target is renamed to the value of -T. - --timeout duration Specify timeout for all operations, including loading of the project, - all external api calls and waiting for readiness. (default 10m0s) + -a, --arg stringArray Passes a template argument in the form of name=value. Nested args + can be set with the '-a my.nested.arg=value' syntax. Values are + interpreted as yaml values, meaning that 'true' and 'false' will + lead to boolean values and numbers will be treated as numbers. Use + quotes if you want these to be treated as strings. If the value + starts with @, it is treated as a file, meaning that the contents + of the file will be loaded and treated as yaml. + --args-from-file stringArray Loads a yaml file and makes it available as arguments, meaning that + they will be available thought the global 'args' variable. + --context string Overrides the context name specified in the target. If the selected + target does not specify a context or the no-name target is used, + --context will override the currently active context. + --git-cache-update-interval duration Specify the time to wait between git cache updates. Defaults to not + wait at all and always updating caches. + --local-git-group-override stringArray Same as --local-git-override, but for a whole group prefix instead + of a single repository. All repositories that have the given prefix + will be overridden with the given local path and the repository + suffix appended. For example, + 'gitlab.com:some-org/sub-org=/local/path/to/my-forks' will override + all repositories below 'gitlab.com:some-org/sub-org/' with the + repositories found in '/local/path/to/my-forks'. It will however + only perform an override if the given repository actually exists + locally and otherwise revert to the actual (non-overridden) repository. + --local-git-override stringArray Specify a single repository local git override in the form of + 'github.com:my-org/my-repo=/local/path/to/override'. This will + cause kluctl to not use git to clone for the specified repository + but instead use the local directory. This is useful in case you + need to test out changes in external git repositories without + pushing them. To only override a single branch of the repo, use + 'github.com:my-org/my-repo:my-branch=/local/path/to/override' + -c, --project-config existingfile Location of the .kluctl.yaml config file. Defaults to + $PROJECT/.kluctl.yaml + --project-dir existingdir Specify the project directory. Defaults to the current working + directory. + -t, --target string Target name to run command for. Target must exist in .kluctl.yaml. + -T, --target-name-override string Overrides the target name. If -t is used at the same time, then the + target will be looked up based on -t and then renamed to the + value of -T. If no target is specified via -t, then the no-name + target is renamed to the value of -T. + --timeout duration Specify timeout for all operations, including loading of the + project, all external api calls and waiting for readiness. (default + 10m0s) ``` diff --git a/pkg/helm/helm_release.go b/pkg/helm/helm_release.go index efdaba29a..7012e00f5 100644 --- a/pkg/helm/helm_release.go +++ b/pkg/helm/helm_release.go @@ -3,6 +3,10 @@ package helm import ( "context" "fmt" + "os" + "path/filepath" + "strings" + "github.com/Masterminds/semver/v3" securejoin "github.com/cyphar/filepath-securejoin" "github.com/kluctl/kluctl/v2/pkg/k8s" @@ -23,9 +27,6 @@ import ( "helm.sh/helm/v3/pkg/getter" "helm.sh/helm/v3/pkg/release" "k8s.io/apimachinery/pkg/runtime/schema" - "os" - "path/filepath" - "strings" ) type Release struct { diff --git a/pkg/repocache/cache.go b/pkg/repocache/cache.go index 2d5dd519a..131567488 100644 --- a/pkg/repocache/cache.go +++ b/pkg/repocache/cache.go @@ -3,6 +3,13 @@ package repocache import ( "context" "fmt" + "os" + "path" + "path/filepath" + "strings" + "sync" + "time" + "github.com/kluctl/kluctl/v2/pkg/git" "github.com/kluctl/kluctl/v2/pkg/git/auth" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" @@ -10,12 +17,6 @@ import ( "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/utils" cp "github.com/otiai10/copy" - "os" - "path" - "path/filepath" - "strings" - "sync" - "time" ) type GitRepoCache struct { @@ -35,12 +36,14 @@ type GitRepoCache struct { type CacheEntry struct { rp *GitRepoCache + url git_url.GitUrl mr *git.MirroredGitRepo defaultRef string refs map[string]string - clonedDirs map[string]clonedDir - updateMutex sync.Mutex + clonedDirs map[string]clonedDir + updateMutex sync.Mutex + overridePath string } type RepoInfo struct { @@ -50,9 +53,10 @@ type RepoInfo struct { } type RepoOverride struct { - RepoKey string + RepoUrl git_url.GitUrl Ref string Override string + IsGroup bool } type clonedDir struct { @@ -85,7 +89,52 @@ func (rp *GitRepoCache) GetEntry(url git_url.GitUrl) (*CacheEntry, error) { rp.reposMutex.Lock() defer rp.reposMutex.Unlock() - e, ok := rp.repos[url.NormalizedRepoKey()] + urlN := url.Normalize() + repoKey := url.NormalizedRepoKey() + + // evaluate overrides + for _, ro := range rp.repoOverrides { + if ro.RepoUrl.Host != urlN.Host { + continue + } + + var overridePath string + if ro.IsGroup { + if !strings.HasPrefix(urlN.Path, ro.RepoUrl.Path+"/") { + continue + } + relPath := strings.TrimPrefix(urlN.Path, ro.RepoUrl.Path+"/") + overridePath = path.Join(ro.Override, relPath) + } else { + if ro.Override != urlN.Path { + continue + } + overridePath = ro.Override + } + + if st, err := os.Stat(overridePath); err != nil { + if os.IsNotExist(err) { + continue + } + return nil, fmt.Errorf("can not override repo %s with %s: %w", url.String(), overridePath, err) + } else if !st.IsDir() { + return nil, fmt.Errorf("can not override repo %s. %s is not a directory", url.String(), overridePath) + } + + status.WarningOnce(rp.ctx, fmt.Sprintf("git-override-%s", repoKey), "Overriding git repo %s with local directory %s", url.String(), overridePath) + + e := &CacheEntry{ + rp: rp, + url: url, + mr: nil, // mark as overridden + clonedDirs: map[string]clonedDir{}, + overridePath: overridePath, + } + rp.repos[repoKey] = e + return e, nil + } + + e, ok := rp.repos[repoKey] if !ok { mr, err := git.NewMirroredGitRepo(rp.ctx, url, filepath.Join(utils.GetTmpBaseDir(rp.ctx), "git-cache"), rp.sshPool, rp.authProviders) if err != nil { @@ -93,10 +142,11 @@ func (rp *GitRepoCache) GetEntry(url git_url.GitUrl) (*CacheEntry, error) { } e = &CacheEntry{ rp: rp, + url: url, mr: mr, clonedDirs: map[string]clonedDir{}, } - rp.repos[url.NormalizedRepoKey()] = e + rp.repos[repoKey] = e } err := e.Update() if err != nil { @@ -109,6 +159,10 @@ func (e *CacheEntry) Update() error { e.updateMutex.Lock() defer e.updateMutex.Unlock() + if e.mr == nil { + return nil + } + err := e.mr.Lock() if err != nil { return err @@ -149,7 +203,7 @@ func (e *CacheEntry) GetRepoInfo() RepoInfo { defer e.updateMutex.Unlock() info := RepoInfo{ - Url: e.mr.Url(), + Url: e.url, RemoteRefs: e.refs, DefaultRef: e.defaultRef, } @@ -184,28 +238,13 @@ func (e *CacheEntry) GetClonedDir(ref string) (string, git.CheckoutInfo, error) e.updateMutex.Lock() defer e.updateMutex.Unlock() - err := e.mr.Lock() - if err != nil { - return "", git.CheckoutInfo{}, err - } - defer e.mr.Unlock() - - if ref == "" { - ref = e.defaultRef - } - - ref2, commit, err := e.findCommit(ref) - if err != nil { - return "", git.CheckoutInfo{}, err - } - tmpDir := filepath.Join(utils.GetTmpBaseDir(e.rp.ctx), "git-cloned") - err = os.MkdirAll(tmpDir, 0700) + err := os.MkdirAll(tmpDir, 0700) if err != nil { return "", git.CheckoutInfo{}, err } - url := e.mr.Url() + url := e.url repoName := path.Base(url.Normalize().Path) + "-" if ref == "" { repoName += "HEAD-" @@ -223,29 +262,32 @@ func (e *CacheEntry) GetClonedDir(ref string) (string, git.CheckoutInfo, error) e.rp.cleanupDirs = append(e.rp.cleanupDirs, p) e.rp.cleeanupDirsMutex.Unlock() - var foundRo *RepoOverride - for _, ro := range e.rp.repoOverrides { - u := e.mr.Url() - if ro.RepoKey == u.NormalizedRepoKey() { - if ro.Ref == "" || strings.HasSuffix(ref2, "/"+ro.Ref) { - foundRo = &ro - break - } - } - } - - if foundRo != nil { - u := e.mr.Url() - status.WarningOnce(e.rp.ctx, fmt.Sprintf("git-override-%s|%s", foundRo.RepoKey, foundRo.Ref), "Overriding git repo %s with local directory %s", u.String(), foundRo.Override) - err = cp.Copy(foundRo.Override, p) - if err != nil { - return "", git.CheckoutInfo{}, err - } - } else { - err = e.mr.CloneProjectByCommit(commit, p) + if e.mr == nil { // local override exist + err = cp.Copy(e.overridePath, p) if err != nil { return "", git.CheckoutInfo{}, err } + return p, git.CheckoutInfo{}, err + } + + err = e.mr.Lock() + if err != nil { + return "", git.CheckoutInfo{}, err + } + defer e.mr.Unlock() + + if ref == "" { + ref = e.defaultRef + } + + ref2, commit, err := e.findCommit(ref) + if err != nil { + return "", git.CheckoutInfo{}, err + } + + err = e.mr.CloneProjectByCommit(commit, p) + if err != nil { + return "", git.CheckoutInfo{}, err } repoInfo, err := git.GetCheckoutInfo(p) From e7ac7c102c6ee8482214c4daf3a8b13972485154 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Feb 2023 14:34:46 +0100 Subject: [PATCH 1373/2916] chore(deps): Bump github.com/aws/aws-sdk-go-v2/service/secretsmanager (#271) Bumps [github.com/aws/aws-sdk-go-v2/service/secretsmanager](https://github.com/aws/aws-sdk-go-v2) from 1.17.0 to 1.18.2. - [Release notes](https://github.com/aws/aws-sdk-go-v2/releases) - [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go-v2/compare/v1.17.0...config/v1.18.2) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go-v2/service/secretsmanager dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index d6909f797..e8c0f20ef 100644 --- a/go.mod +++ b/go.mod @@ -58,8 +58,9 @@ require ( require ( filippo.io/age v1.1.1 + github.com/aws/aws-sdk-go-v2 v1.17.3 github.com/aws/aws-sdk-go-v2/config v1.18.7 - github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.17.0 + github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.2 github.com/go-logr/logr v1.2.3 github.com/hashicorp/go-multierror v1.1.1 github.com/huandu/xstrings v1.4.0 @@ -94,7 +95,6 @@ require ( github.com/armon/go-metrics v0.4.1 // indirect github.com/armon/go-radix v1.0.0 // indirect github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect - github.com/aws/aws-sdk-go-v2 v1.17.3 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.13.7 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.21 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.27 // indirect diff --git a/go.sum b/go.sum index 842181df8..448ad7a51 100644 --- a/go.sum +++ b/go.sum @@ -139,8 +139,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.21 h1:5C6XgTViS github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.21/go.mod h1:lRToEJsn+DRA9lW4O9L9+/3hjTkUzlzyzHqn8MTds5k= github.com/aws/aws-sdk-go-v2/service/kms v1.17.5 h1:DkubF+BSEy0uX59+pYySzWFReN3fCcXobIO8L5Phh24= github.com/aws/aws-sdk-go-v2/service/kms v1.17.5/go.mod h1:ubAtMGRUMVv5kX8lpbeDguxZ64pR4kXTGApY4sCM0io= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.17.0 h1:6W6BLZcXytRJsVvc2gGwxKE4wbMSlWqdxZivBP/E+ys= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.17.0/go.mod h1:jAeo/PdIJZuDSwsvxJS94G4d6h8tStj7WXVuKwLHWU8= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.2 h1:QDVKb2VpuwzIslzshumxksayV5GkpqT+rkVvdPVrA9E= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.2/go.mod h1:jAeo/PdIJZuDSwsvxJS94G4d6h8tStj7WXVuKwLHWU8= github.com/aws/aws-sdk-go-v2/service/sso v1.11.28 h1:gItLq3zBYyRDPmqAClgzTH8PBjDQGeyptYGHIwtYYNA= github.com/aws/aws-sdk-go-v2/service/sso v1.11.28/go.mod h1:wo/B7uUm/7zw/dWhBJ4FXuw1sySU5lyIhVg1Bu2yL9A= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.11 h1:KCacyVSs/wlcPGx37hcbT3IGYO8P8Jx+TgSDhAXtQMY= From 4710e347f8d9881be982cf742f0371500d755492 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Feb 2023 14:35:29 +0100 Subject: [PATCH 1374/2916] chore(deps): Bump github.com/ohler55/ojg from 1.16.0 to 1.17.3 (#274) Bumps [github.com/ohler55/ojg](https://github.com/ohler55/ojg) from 1.16.0 to 1.17.3. - [Release notes](https://github.com/ohler55/ojg/releases) - [Changelog](https://github.com/ohler55/ojg/blob/develop/CHANGELOG.md) - [Commits](https://github.com/ohler55/ojg/compare/v1.16.0...v1.17.3) --- updated-dependencies: - dependency-name: github.com/ohler55/ojg dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e8c0f20ef..20d85c018 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( github.com/mattn/go-isatty v0.0.17 github.com/mattn/go-runewidth v0.0.14 github.com/mitchellh/reflectwalk v1.0.2 - github.com/ohler55/ojg v1.16.0 + github.com/ohler55/ojg v1.17.3 github.com/pkg/errors v0.9.1 github.com/r3labs/diff/v2 v2.15.1 github.com/rogpeppe/go-internal v1.9.0 diff --git a/go.sum b/go.sum index 448ad7a51..abaf6b2b5 100644 --- a/go.sum +++ b/go.sum @@ -661,8 +661,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/ohler55/ojg v1.16.0 h1:JQMJp/ygkak1YiGFB2ohbks2Y6BAE061i5cq5fECiVA= -github.com/ohler55/ojg v1.16.0/go.mod h1:7Ghirupn8NC8hSSDpI0gcjorPxj+vSVIONDWfliHR1k= +github.com/ohler55/ojg v1.17.3 h1:NsyfSN+GScWFzXZCVuVimK0xOqUEqTSwuZ+J1WA50nw= +github.com/ohler55/ojg v1.17.3/go.mod h1:7Ghirupn8NC8hSSDpI0gcjorPxj+vSVIONDWfliHR1k= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= From 94acf99a3118c088866869b564a10d44f7da6072 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Feb 2023 14:40:36 +0100 Subject: [PATCH 1375/2916] chore(deps): Bump github.com/bitnami-labs/sealed-secrets (#263) Bumps [github.com/bitnami-labs/sealed-secrets](https://github.com/bitnami-labs/sealed-secrets) from 0.19.3 to 0.19.4. - [Release notes](https://github.com/bitnami-labs/sealed-secrets/releases) - [Changelog](https://github.com/bitnami-labs/sealed-secrets/blob/main/RELEASE-NOTES.md) - [Commits](https://github.com/bitnami-labs/sealed-secrets/compare/v0.19.3...v0.19.4) --- updated-dependencies: - dependency-name: github.com/bitnami-labs/sealed-secrets dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 20d85c018..5774ed81a 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 github.com/Masterminds/semver/v3 v3.2.0 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d - github.com/bitnami-labs/sealed-secrets v0.19.3 + github.com/bitnami-labs/sealed-secrets v0.19.4 github.com/cyphar/filepath-securejoin v0.2.3 github.com/docker/distribution v2.8.1+incompatible // See https://github.com/fluxcd/pkg/issues/397, especially in regard to skeema/knownhosts breaking compatibility diff --git a/go.sum b/go.sum index abaf6b2b5..9bd56dc4a 100644 --- a/go.sum +++ b/go.sum @@ -155,8 +155,8 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bitnami-labs/sealed-secrets v0.19.3 h1:D9v0fQt8JNDEoBU6HRKKJ20VhqvGUyaDrK1bl714i4I= -github.com/bitnami-labs/sealed-secrets v0.19.3/go.mod h1:G7Psbu6s3ed7GCO6ZjZ51zff0GGGSymLxFXb4QkJnRw= +github.com/bitnami-labs/sealed-secrets v0.19.4 h1:TUad0o/mNp2D53lHMGzjdsdrx+yrHy/fyuEIAbFqj+U= +github.com/bitnami-labs/sealed-secrets v0.19.4/go.mod h1:rXBdWOdAPbuowgFphH8bcCB3jSVlYXg9mMWRcYY49dc= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= @@ -666,7 +666,7 @@ github.com/ohler55/ojg v1.17.3/go.mod h1:7Ghirupn8NC8hSSDpI0gcjorPxj+vSVIONDWfli github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo/v2 v2.6.1 h1:1xQPCjcqYw/J5LchOcp4/2q/jzJFjiAOc25chhnDw+Q= +github.com/onsi/ginkgo/v2 v2.7.0 h1:/XxtEV3I3Eif/HobnVx9YmJgk8ENdRsuUmM+fLCFNow= github.com/onsi/gomega v1.24.2 h1:J/tulyYK6JwBldPViHJReihxxZ+22FHs0piGjQAvoUE= github.com/onsi/gomega v1.24.2/go.mod h1:gs3J10IS7Z7r7eXRoNJIrNqU4ToQukCJhFtKrWgHWnk= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= From 31b3c95858eb3e93f0c1e4c98e1adac5fb89ee90 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 3 Feb 2023 15:15:54 +0100 Subject: [PATCH 1376/2916] docs: Add missing documentation about onlyRender --- docs/reference/deployments/deployment-yml.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/reference/deployments/deployment-yml.md b/docs/reference/deployments/deployment-yml.md index 73e0fb3a5..77115380e 100644 --- a/docs/reference/deployments/deployment-yml.md +++ b/docs/reference/deployments/deployment-yml.md @@ -196,6 +196,17 @@ deployments: - path: kustomizeDeployment2 ``` +### onlyRender +Causes a path to be rendered only but not treated as a deployment item. This can be useful if you for example want to +use Kustomize components which you'd refer from other deployment items. + +```yaml +deployments: +- path: component + onlyRender: true +- path: kustomizeDeployment2 +``` + ## vars (deployment project) A list of variable sets to be loaded into the templating context, which is then available in all [deployment items](#deployments) and [sub-deployments](#includes). From 7b956a0ce14b82b44ad209d0417a5677f75fb881 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 3 Feb 2023 15:37:25 +0100 Subject: [PATCH 1377/2916] tests: Add test for onlyRender --- e2e/deployment_items_test.go | 55 ++++++++++++++++++++++++++++++++++++ pkg/git/test_git_server.go | 4 +++ 2 files changed, 59 insertions(+) diff --git a/e2e/deployment_items_test.go b/e2e/deployment_items_test.go index 71e93a34a..8e07ffcbc 100644 --- a/e2e/deployment_items_test.go +++ b/e2e/deployment_items_test.go @@ -1,8 +1,10 @@ package e2e import ( + "fmt" "github.com/kluctl/kluctl/v2/e2e/test-utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/stretchr/testify/assert" "testing" ) @@ -80,3 +82,56 @@ func TestGeneratedKustomize(t *testing.T) { assertConfigMapExists(t, k, p.TestSlug(), "cm2") assertConfigMapNotExists(t, k, p.TestSlug(), "cm3") } + +func TestOnlyRender(t *testing.T) { + t.Parallel() + + k := defaultCluster1 + + p := test_utils.NewTestProject(t) + + createNamespace(t, k, p.TestSlug()) + + p.UpdateTarget("test", nil) + + p.AddDeploymentItem("", uo.FromMap(map[string]interface{}{ + "path": "only-render", + "onlyRender": true, + })) + p.UpdateFile("only-render/value.txt", func(f string) (string, error) { + return "{{ args.a }}\n", nil + }, "") + p.UpdateFile("only-render/kustomization.yaml", func(f string) (string, error) { + return fmt.Sprintf(` +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component + +generatorOptions: + disableNameSuffixHash: true + +configMapGenerator: +- name: %s-cm + files: + - value.txt +`, p.TestSlug()), nil + }, "") + + p.AddDeploymentItem("", uo.FromMap(map[string]interface{}{ + "path": "d", + })) + p.UpdateFile("d/kustomization.yaml", func(f string) (string, error) { + return fmt.Sprintf(` +components: +- ../only-render +namespace: %s +`, p.TestSlug()), nil + }, "") + + p.KluctlMust("deploy", "--yes", "-t", "test", "-a", "a=v1") + // it should not appear in the default namespace as that would indicate that the component was treated as a deployment item + assertConfigMapNotExists(t, k, "default", p.TestSlug()+"-cm") + s := assertConfigMapExists(t, k, p.TestSlug(), p.TestSlug()+"-cm") + assert.Equal(t, s.Object["data"], map[string]any{ + "value.txt": "v1", + }) +} diff --git a/pkg/git/test_git_server.go b/pkg/git/test_git_server.go index 7dd0a1614..9ecaf8597 100644 --- a/pkg/git/test_git_server.go +++ b/pkg/git/test_git_server.go @@ -183,6 +183,10 @@ func (p *TestGitServer) UpdateFile(repo string, pth string, update func(f string if f == newF { return } + err = os.MkdirAll(filepath.Dir(fullPath), 0o700) + if err != nil { + p.t.Fatal(err) + } err = os.WriteFile(fullPath, []byte(newF), 0o600) if err != nil { p.t.Fatal(err) From b5c5a368ba6037e8c09eeeb46120ef5705a64634 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 3 Feb 2023 15:48:38 +0100 Subject: [PATCH 1378/2916] fix: Fix onlyRender to actually not treat the path as deployment item --- pkg/deployment/deployment_item.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index af74a2f46..3e0f3ec56 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -494,6 +494,9 @@ func (di *DeploymentItem) buildKustomize() error { if di.dir == nil { return nil } + if di.Config.OnlyRender { + return nil + } ky, err := di.prepareKustomizationYaml() if err != nil { From 143c7260bb9b84d487fee766441b4947c6aa543b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 3 Feb 2023 15:50:59 +0100 Subject: [PATCH 1379/2916] feat: Allow to load variables without overriding previously loaded variables (#230) --- docs/reference/templating/variable-sources.md | 5 + pkg/types/vars_source.go | 3 +- pkg/vars/vars_loader.go | 150 +++++++++--------- pkg/vars/vars_loader_http.go | 39 ++--- pkg/vars/vars_loader_test.go | 19 +++ 5 files changed, 115 insertions(+), 101 deletions(-) diff --git a/docs/reference/templating/variable-sources.md b/docs/reference/templating/variable-sources.md index 43c03e071..3f157f400 100644 --- a/docs/reference/templating/variable-sources.md +++ b/docs/reference/templating/variable-sources.md @@ -29,6 +29,8 @@ vars: - file: vars2.yaml - file: optional-vars.yaml ignoreMissing: true +- file: default-vars.yaml + noOverride: true ``` `vars2.yaml` can now use variables that are defined in `vars1.yaml`. At all times, variables defined by @@ -37,6 +39,9 @@ parents of the current sub-deployment project can be used in the current vars fi Each variable source can have the optional field `ignoreMissing` set to `true`, causing Kluctl to ignore if the source can not be found. +When specifying `noOverride: true`, Kluctl will not override variables from the previously loaded variables. This is +useful if you want to load default values for variables. + Different types of vars entries are possible: ### file diff --git a/pkg/types/vars_source.go b/pkg/types/vars_source.go index c32578469..e7db5f765 100644 --- a/pkg/types/vars_source.go +++ b/pkg/types/vars_source.go @@ -55,6 +55,7 @@ type VarsSourceVault struct { type VarsSource struct { IgnoreMissing *bool `yaml:"ignoreMissing,omitempty"` + NoOverride *bool `yaml:"noOverride,omitempty"` Values *uo.UnstructuredObject `yaml:"values,omitempty"` File *string `yaml:"file,omitempty"` @@ -73,7 +74,7 @@ func ValidateVarsSource(sl validator.StructLevel) { count := 0 v := reflect.ValueOf(s) for i := 0; i < v.NumField(); i++ { - if v.Type().Field(i).Name == "IgnoreMissing" { + if v.Type().Field(i).Name == "IgnoreMissing" || v.Type().Field(i).Name == "NoOverride" { continue } if !v.Field(i).IsNil() { diff --git a/pkg/vars/vars_loader.go b/pkg/vars/vars_loader.go index 39161f372..a0dc29267 100644 --- a/pkg/vars/vars_loader.go +++ b/pkg/vars/vars_loader.go @@ -83,27 +83,45 @@ func (v *VarsLoader) LoadVars(varsCtx *VarsCtx, sourceIn *types.VarsSource, sear ignoreMissing = *source.IgnoreMissing } + var newVars *uo.UnstructuredObject if source.Values != nil { - v.mergeVars(varsCtx, source.Values, rootKey) - return nil + newVars = source.Values + if rootKey != "" { + newVars = uo.FromMap(map[string]interface{}{ + rootKey: newVars.Object, + }) + } } else if source.File != nil { - return v.loadFile(varsCtx, *source.File, ignoreMissing, searchDirs, rootKey) + newVars, err = v.loadFile(varsCtx, *source.File, ignoreMissing, searchDirs) } else if source.Git != nil { - return v.loadGit(varsCtx, source.Git, ignoreMissing, rootKey) + newVars, err = v.loadGit(varsCtx, source.Git, ignoreMissing) } else if source.ClusterConfigMap != nil { - return v.loadFromK8sObject(varsCtx, *source.ClusterConfigMap, "ConfigMap", source.ClusterConfigMap.Key, rootKey, ignoreMissing, false) + newVars, err = v.loadFromK8sObject(varsCtx, *source.ClusterConfigMap, "ConfigMap", source.ClusterConfigMap.Key, ignoreMissing, false) } else if source.ClusterSecret != nil { - return v.loadFromK8sObject(varsCtx, *source.ClusterSecret, "Secret", source.ClusterSecret.Key, rootKey, ignoreMissing, true) + newVars, err = v.loadFromK8sObject(varsCtx, *source.ClusterSecret, "Secret", source.ClusterSecret.Key, ignoreMissing, true) } else if source.SystemEnvVars != nil { - return v.loadSystemEnvs(varsCtx, &source, ignoreMissing, rootKey) + newVars, err = v.loadSystemEnvs(varsCtx, &source, ignoreMissing, rootKey) } else if source.Http != nil { - return v.loadHttp(varsCtx, &source, ignoreMissing, rootKey) + newVars, err = v.loadHttp(varsCtx, &source, ignoreMissing) } else if source.AwsSecretsManager != nil { - return v.loadAwsSecretsManager(varsCtx, &source, ignoreMissing, rootKey) + newVars, err = v.loadAwsSecretsManager(varsCtx, &source, ignoreMissing) } else if source.Vault != nil { - return v.loadVault(varsCtx, &source, ignoreMissing, rootKey) + newVars, err = v.loadVault(varsCtx, &source, ignoreMissing) + } else { + return fmt.Errorf("invalid vars source") + } + if err != nil { + return err } - return fmt.Errorf("invalid vars source") + + if source.NoOverride == nil || !*source.NoOverride { + varsCtx.Vars.Merge(newVars) + } else { + newVars.Merge(varsCtx.Vars) + varsCtx.Vars = newVars + } + + return nil } func (v *VarsLoader) mergeVars(varsCtx *VarsCtx, newVars *uo.UnstructuredObject, rootKey string) { @@ -114,45 +132,35 @@ func (v *VarsLoader) mergeVars(varsCtx *VarsCtx, newVars *uo.UnstructuredObject, } } -func (v *VarsLoader) loadFile(varsCtx *VarsCtx, path string, ignoreMissing bool, searchDirs []string, rootKey string) error { +func (v *VarsLoader) loadFile(varsCtx *VarsCtx, path string, ignoreMissing bool, searchDirs []string) (*uo.UnstructuredObject, error) { rendered, err := varsCtx.RenderFile(path, searchDirs) if err != nil { // TODO the Jinja2 renderer should be able to better report this error if ignoreMissing && err.Error() == fmt.Sprintf("template %s not found", path) { - return nil + return uo.New(), nil } - return fmt.Errorf("failed to render vars file %s: %w", path, err) + return nil, fmt.Errorf("failed to render vars file %s: %w", path, err) } format := formats.FormatForPath(path) decrypted, _, err := sops.MaybeDecrypt(v.sops, []byte(rendered), format, format) if err != nil { - return fmt.Errorf("failed to decrypt vars file %s: %w", path, err) + return nil, fmt.Errorf("failed to decrypt vars file %s: %w", path, err) } rendered = string(decrypted) newVars := uo.New() err = yaml.ReadYamlString(rendered, newVars) if err != nil { - return err + return nil, err } if err != nil { - return fmt.Errorf("failed to load vars from %s: %w", path, err) + return nil, fmt.Errorf("failed to load vars from %s: %w", path, err) } - if rootKey != "" { - newVars, _, err = newVars.GetNestedObject(rootKey) - if err != nil { - return err - } - if newVars == nil { - return fmt.Errorf("vars from %s have no '%s' root", path, rootKey) - } - } - v.mergeVars(varsCtx, newVars, rootKey) - return nil + return newVars, nil } -func (v *VarsLoader) loadSystemEnvs(varsCtx *VarsCtx, source *types.VarsSource, ignoreMissing bool, rootKey string) error { +func (v *VarsLoader) loadSystemEnvs(varsCtx *VarsCtx, source *types.VarsSource, ignoreMissing bool, rootKey string) (*uo.UnstructuredObject, error) { newVars := uo.New() err := source.SystemEnvVars.NewIterator().IterateLeafs(func(it *uo.ObjectIterator) error { envName, ok := it.Value().(string) @@ -196,15 +204,19 @@ func (v *VarsLoader) loadSystemEnvs(varsCtx *VarsCtx, source *types.VarsSource, return nil }) if err != nil { - return err + return nil, err } - v.mergeVars(varsCtx, newVars, rootKey) - return nil + if rootKey != "" { + newVars = uo.FromMap(map[string]interface{}{ + rootKey: newVars.Object, + }) + } + return newVars, nil } -func (v *VarsLoader) loadAwsSecretsManager(varsCtx *VarsCtx, source *types.VarsSource, ignoreMissing bool, rootKey string) error { +func (v *VarsLoader) loadAwsSecretsManager(varsCtx *VarsCtx, source *types.VarsSource, ignoreMissing bool) (*uo.UnstructuredObject, error) { if v.aws == nil { - return fmt.Errorf("no AWS client factory provided") + return uo.New(), fmt.Errorf("no AWS client factory provided") } secret, err := aws.GetAwsSecretsManagerSecret(v.ctx, v.aws, source.AwsSecretsManager.Profile, source.AwsSecretsManager.Region, source.AwsSecretsManager.SecretName) @@ -212,45 +224,45 @@ func (v *VarsLoader) loadAwsSecretsManager(varsCtx *VarsCtx, source *types.VarsS var aerr *types2.ResourceNotFoundException if errors2.As(err, &aerr) { if ignoreMissing { - return nil + return uo.New(), nil } } - return err + return nil, err } - return v.loadFromString(varsCtx, secret, "awsSecretsManager", rootKey) + return v.loadFromString(varsCtx, secret) } -func (v *VarsLoader) loadVault(varsCtx *VarsCtx, source *types.VarsSource, ignoreMissing bool, rootKey string) error { +func (v *VarsLoader) loadVault(varsCtx *VarsCtx, source *types.VarsSource, ignoreMissing bool) (*uo.UnstructuredObject, error) { secret, err := vault.GetSecret(source.Vault.Address, source.Vault.Path) if err != nil { - return err + return nil, err } if secret == nil { if ignoreMissing { - return nil + return uo.New(), nil } - return fmt.Errorf("the specified vault secret was not found") + return nil, fmt.Errorf("the specified vault secret was not found") } - return v.loadFromString(varsCtx, *secret, "vault", rootKey) + return v.loadFromString(varsCtx, *secret) } -func (v *VarsLoader) loadGit(varsCtx *VarsCtx, gitFile *types.VarsSourceGit, ignoreMissing bool, rootKey string) error { +func (v *VarsLoader) loadGit(varsCtx *VarsCtx, gitFile *types.VarsSourceGit, ignoreMissing bool) (*uo.UnstructuredObject, error) { ge, err := v.rp.GetEntry(gitFile.Url) if err != nil { - return err + return nil, err } clonedDir, _, err := ge.GetClonedDir(gitFile.Ref) if err != nil { - return fmt.Errorf("failed to load vars from git repository %s: %w", gitFile.Url.String(), err) + return nil, fmt.Errorf("failed to load vars from git repository %s: %w", gitFile.Url.String(), err) } - return v.loadFile(varsCtx, gitFile.Path, ignoreMissing, []string{clonedDir}, rootKey) + return v.loadFile(varsCtx, gitFile.Path, ignoreMissing, []string{clonedDir}) } -func (v *VarsLoader) loadFromK8sObject(varsCtx *VarsCtx, varsSource types.VarsSourceClusterConfigMapOrSecret, kind string, key string, rootKey string, ignoreMissing bool, base64Decode bool) error { +func (v *VarsLoader) loadFromK8sObject(varsCtx *VarsCtx, varsSource types.VarsSourceClusterConfigMapOrSecret, kind string, key string, ignoreMissing bool, base64Decode bool) (*uo.UnstructuredObject, error) { if v.k == nil { - return fmt.Errorf("loading vars from cluster is disabled") + return nil, fmt.Errorf("loading vars from cluster is disabled") } var err error @@ -260,9 +272,9 @@ func (v *VarsLoader) loadFromK8sObject(varsCtx *VarsCtx, varsSource types.VarsSo o, _, err = v.k.GetSingleObject(k8s2.NewObjectRef("", "v1", kind, varsSource.Name, varsSource.Namespace)) if err != nil { if ignoreMissing && errors.IsNotFound(err) { - return nil + return uo.New(), nil } - return err + return nil, err } } else { objs, _, err := v.k.ListObjects(schema.GroupVersionKind{ @@ -271,16 +283,16 @@ func (v *VarsLoader) loadFromK8sObject(varsCtx *VarsCtx, varsSource types.VarsSo Kind: kind, }, varsSource.Namespace, varsSource.Labels) if err != nil { - return err + return nil, err } if len(objs) == 0 { if ignoreMissing { - return nil + return uo.New(), nil } - return fmt.Errorf("no object found with labels %v", varsSource.Labels) + return nil, fmt.Errorf("no object found with labels %v", varsSource.Labels) } if len(objs) > 1 { - return fmt.Errorf("found more than one objects with labels %v", varsSource.Labels) + return nil, fmt.Errorf("found more than one objects with labels %v", varsSource.Labels) } o = objs[0] } @@ -289,10 +301,10 @@ func (v *VarsLoader) loadFromK8sObject(varsCtx *VarsCtx, varsSource types.VarsSo f, found, err := o.GetNestedField("data", key) if err != nil { - return err + return nil, err } if !found { - return fmt.Errorf("key %s not found in %s on cluster", key, ref.String()) + return nil, fmt.Errorf("key %s not found in %s on cluster", key, ref.String()) } var value string @@ -302,7 +314,7 @@ func (v *VarsLoader) loadFromK8sObject(varsCtx *VarsCtx, varsSource types.VarsSo if base64Decode { b, err := base64.StdEncoding.DecodeString(s) if err != nil { - return err + return nil, err } value = string(b) } else { @@ -310,32 +322,20 @@ func (v *VarsLoader) loadFromK8sObject(varsCtx *VarsCtx, varsSource types.VarsSo } } - err = v.loadFromString(varsCtx, value, "k8s", rootKey) + newVars, err := v.loadFromString(varsCtx, value) if err != nil { - return fmt.Errorf("failed to load vars from kubernetes object %s and key %s: %w", ref.String(), key, err) + return nil, fmt.Errorf("failed to load vars from kubernetes object %s and key %s: %w", ref.String(), key, err) } - return nil + return newVars, nil } -func (v *VarsLoader) loadFromString(varsCtx *VarsCtx, s string, secretType string, rootKey string) error { +func (v *VarsLoader) loadFromString(varsCtx *VarsCtx, s string) (*uo.UnstructuredObject, error) { newVars := uo.New() err := v.renderYamlString(varsCtx, s, newVars) if err != nil { - return err - } - - if rootKey != "" { - newVars, _, err = newVars.GetNestedObject(rootKey) - if err != nil { - return err - } - if newVars == nil { - return fmt.Errorf("%s secret has no '%s' root", secretType, rootKey) - } + return nil, err } - - v.mergeVars(varsCtx, newVars, rootKey) - return nil + return newVars, nil } func (v *VarsLoader) renderYamlString(varsCtx *VarsCtx, s string, out interface{}) error { diff --git a/pkg/vars/vars_loader_http.go b/pkg/vars/vars_loader_http.go index 7de4ca0a2..6e0a68f30 100644 --- a/pkg/vars/vars_loader_http.go +++ b/pkg/vars/vars_loader_http.go @@ -64,12 +64,12 @@ func (v *VarsLoader) doHttp(httpSource *types.VarsSourceHttp, ignoreMissing bool return resp, string(respBody), nil } -func (v *VarsLoader) loadHttp(varsCtx *VarsCtx, source *types.VarsSource, ignoreMissing bool, rootKey string) error { +func (v *VarsLoader) loadHttp(varsCtx *VarsCtx, source *types.VarsSource, ignoreMissing bool) (*uo.UnstructuredObject, error) { resp, respBody, err := v.doHttp(source.Http, ignoreMissing, "", "") if err != nil && resp != nil && resp.StatusCode == http.StatusUnauthorized { chgs := challenge.ResponseChallenges(resp) if len(chgs) == 0 { - return err + return nil, err } var realms []string @@ -86,7 +86,7 @@ func (v *VarsLoader) loadHttp(varsCtx *VarsCtx, source *types.VarsSource, ignore if !ok { username, password, err := status.AskForCredentials(v.ctx, fmt.Sprintf("Please enter credentials for host '%s'", source.Http.Url.Host)) if err != nil { - return err + return nil, err } creds = usernamePassword{ username: username, @@ -97,13 +97,13 @@ func (v *VarsLoader) loadHttp(varsCtx *VarsCtx, source *types.VarsSource, ignore resp, respBody, err = v.doHttp(source.Http, ignoreMissing, creds.username, creds.password) if err != nil { - return err + return nil, err } } else if err != nil { if ignoreMissing && resp != nil && resp.StatusCode == http.StatusNotFound { - return nil + return uo.New(), nil } - return err + return nil, err } var respObj interface{} @@ -111,45 +111,34 @@ func (v *VarsLoader) loadHttp(varsCtx *VarsCtx, source *types.VarsSource, ignore err = yaml.ReadYamlString(respBody, &respObj) if err != nil { - return err + return nil, err } if err != nil { - return err + return nil, err } if source.Http.JsonPath != nil { p, err := uo.NewMyJsonPath(*source.Http.JsonPath) if err != nil { - return err + return nil, err } x, ok := p.GetFirstFromAny(respObj) if !ok { - return fmt.Errorf("%s not found in result from http request %s", *source.Http.JsonPath, source.Http.Url.String()) + return nil, fmt.Errorf("%s not found in result from http request %s", *source.Http.JsonPath, source.Http.Url.String()) } s, ok := x.(string) if !ok { - return fmt.Errorf("%s in result of http request %s is not a string", *source.Http.JsonPath, source.Http.Url.String()) + return nil, fmt.Errorf("%s in result of http request %s is not a string", *source.Http.JsonPath, source.Http.Url.String()) } newVars, err = uo.FromString(s) if err != nil { - return err + return nil, err } } else { x, ok := respObj.(map[string]interface{}) if !ok { - return fmt.Errorf("result of http request %s is not an object", source.Http.Url.String()) + return nil, fmt.Errorf("result of http request %s is not an object", source.Http.Url.String()) } newVars = uo.FromMap(x) } - - if rootKey != "" { - newVars, _, err = newVars.GetNestedObject(rootKey) - if err != nil { - return err - } - } - - if newVars != nil { - v.mergeVars(varsCtx, newVars, rootKey) - } - return nil + return newVars, nil } diff --git a/pkg/vars/vars_loader_test.go b/pkg/vars/vars_loader_test.go index f8320e8fd..40c038e99 100644 --- a/pkg/vars/vars_loader_test.go +++ b/pkg/vars/vars_loader_test.go @@ -69,6 +69,25 @@ func TestVarsLoader_Values(t *testing.T) { }) } +func TestVarsLoader_ValuesNoOverrides(t *testing.T) { + testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { + err := vl.LoadVars(vc, &types.VarsSource{ + Values: uo.FromStringMust(`{"test1": {"test2": 42}}`), + }, nil, "") + assert.NoError(t, err) + + b := true + err = vl.LoadVars(vc, &types.VarsSource{ + Values: uo.FromStringMust(`{"test1": {"test2": 43}}`), + NoOverride: &b, + }, nil, "") + assert.NoError(t, err) + + v, _, _ := vc.Vars.GetNestedInt("test1", "test2") + assert.Equal(t, int64(42), v) + }) +} + func TestVarsLoader_File(t *testing.T) { d := t.TempDir() _ = os.WriteFile(filepath.Join(d, "test.yaml"), []byte(`{"test1": {"test2": 42}}`), 0o600) From 5bc357c88efa1ff9f7e544c4d00a1da094590fab Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 3 Feb 2023 15:51:25 +0100 Subject: [PATCH 1380/2916] fix: Respect IdentityAgent from SSH config (#280) --- pkg/git/auth/ssh_auth_provider.go | 36 ++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/pkg/git/auth/ssh_auth_provider.go b/pkg/git/auth/ssh_auth_provider.go index 5af2d2c0d..c6911e846 100644 --- a/pkg/git/auth/ssh_auth_provider.go +++ b/pkg/git/auth/ssh_auth_provider.go @@ -10,10 +10,13 @@ import ( "github.com/kluctl/kluctl/v2/pkg/git/messages" sshagent "github.com/xanzy/ssh-agent" "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh/agent" "io" + "net" "os" "os/user" "path/filepath" + "runtime" "sync" ) @@ -85,12 +88,43 @@ func (a *sshDefaultIdentityAndAgent) addConfigIdentities(gitUrl git_url.GitUrl) } } +func (a *sshDefaultIdentityAndAgent) createAgent(gitUrl git_url.GitUrl) (agent.Agent, error) { + if runtime.GOOS == "windows" { + a, _, err := sshagent.New() + return a, err + } + + // see `man ssh_config` + + sshAuthSock := ssh_config.Get(gitUrl.Hostname(), "IdentityAgent") + if sshAuthSock == "none" { + return nil, nil + } + if sshAuthSock == "" || sshAuthSock == "SSH_AUTH_SOCK" { + a, _, err := sshagent.New() + return a, err + } + + sshAuthSock = os.ExpandEnv(sshAuthSock) + sshAuthSock = expandHomeDir(sshAuthSock) + + conn, err := net.Dial("unix", sshAuthSock) + if err != nil { + return nil, fmt.Errorf("error connecting to unix socket: %v", err) + } + + return agent.NewClient(conn), nil +} + func (a *sshDefaultIdentityAndAgent) addAgentIdentities(gitUrl git_url.GitUrl) { a.authProvider.MessageCallbacks.Trace("trying to add agent keys") - agent, _, err := sshagent.New() + agent, err := a.createAgent(gitUrl) if err != nil { a.authProvider.MessageCallbacks.Warning("Failed to connect to ssh agent for url %s: %v", gitUrl.String(), err) } else { + if agent == nil { + return + } signers, err := agent.Signers() if err != nil { a.authProvider.MessageCallbacks.Warning("Failed to get signers from ssh agent for url %s: %v", gitUrl.String(), err) From b5ac47375adc4a0ab6ca9b7720694a797868bfa2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Feb 2023 15:51:46 +0100 Subject: [PATCH 1381/2916] chore(deps): Bump github.com/aws/aws-sdk-go-v2/config (#277) Bumps [github.com/aws/aws-sdk-go-v2/config](https://github.com/aws/aws-sdk-go-v2) from 1.18.7 to 1.18.11. - [Release notes](https://github.com/aws/aws-sdk-go-v2/releases) - [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go-v2/compare/config/v1.18.7...config/v1.18.11) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go-v2/config dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 5774ed81a..c83c9c98b 100644 --- a/go.mod +++ b/go.mod @@ -59,7 +59,7 @@ require ( require ( filippo.io/age v1.1.1 github.com/aws/aws-sdk-go-v2 v1.17.3 - github.com/aws/aws-sdk-go-v2/config v1.18.7 + github.com/aws/aws-sdk-go-v2/config v1.18.11 github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.2 github.com/go-logr/logr v1.2.3 github.com/hashicorp/go-multierror v1.1.1 @@ -95,16 +95,16 @@ require ( github.com/armon/go-metrics v0.4.1 // indirect github.com/armon/go-radix v1.0.0 // indirect github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.13.7 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.13.11 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.21 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.27 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.21 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.3.28 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.21 // indirect github.com/aws/aws-sdk-go-v2/service/kms v1.17.5 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.11.28 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.11 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.17.7 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.12.0 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.0 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.18.2 // indirect github.com/aws/smithy-go v1.13.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver v3.5.1+incompatible // indirect diff --git a/go.sum b/go.sum index 9bd56dc4a..58e4aa64c 100644 --- a/go.sum +++ b/go.sum @@ -121,10 +121,10 @@ github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:W github.com/aws/aws-sdk-go-v2 v1.16.7/go.mod h1:6CpKuLXg2w7If3ABZCl/qZ6rEgwtjZTn4eAf4RcEyuw= github.com/aws/aws-sdk-go-v2 v1.17.3 h1:shN7NlnVzvDUgPQ+1rLMSxY8OWRNDRYtiqe0p/PgrhY= github.com/aws/aws-sdk-go-v2 v1.17.3/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= -github.com/aws/aws-sdk-go-v2/config v1.18.7 h1:V94lTcix6jouwmAsgQMAEBozVAGJMFhVj+6/++xfe3E= -github.com/aws/aws-sdk-go-v2/config v1.18.7/go.mod h1:OZYsyHFL5PB9UpyS78NElgKs11qI/B5KJau2XOJDXHA= -github.com/aws/aws-sdk-go-v2/credentials v1.13.7 h1:qUUcNS5Z1092XBFT66IJM7mYkMwgZ8fcC8YDIbEwXck= -github.com/aws/aws-sdk-go-v2/credentials v1.13.7/go.mod h1:AdCcbZXHQCjJh6NaH3pFaw8LUeBFn5+88BZGMVGuBT8= +github.com/aws/aws-sdk-go-v2/config v1.18.11 h1:7dJD4p90OyKYIihuwe/LbHfP7uw4yVm5P1hel+b8UZ8= +github.com/aws/aws-sdk-go-v2/config v1.18.11/go.mod h1:FTGKr2F7QL7IAg22dUmEB5NWpLPAOuhrONzXe7TVhAI= +github.com/aws/aws-sdk-go-v2/credentials v1.13.11 h1:QnvlTut1XXKkX4aaM1Ydo5X0CHriv0jmLu8PTVQQJJo= +github.com/aws/aws-sdk-go-v2/credentials v1.13.11/go.mod h1:tqAm4JmQaShel+Qi38hmd1QglSnnxaYt50k/9yGQzzc= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.21 h1:j9wi1kQ8b+e0FBVHxCqCGo4kxDU175hoDHcWAi0sauU= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.21/go.mod h1:ugwW57Z5Z48bpvUyZuaPy4Kv+vEfJWnIrky7RmkBvJg= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.14/go.mod h1:kdjrMwHwrC3+FsKhNcCMJ7tUVj/8uSD5CZXeQ4wV6fM= @@ -141,12 +141,12 @@ github.com/aws/aws-sdk-go-v2/service/kms v1.17.5 h1:DkubF+BSEy0uX59+pYySzWFReN3f github.com/aws/aws-sdk-go-v2/service/kms v1.17.5/go.mod h1:ubAtMGRUMVv5kX8lpbeDguxZ64pR4kXTGApY4sCM0io= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.2 h1:QDVKb2VpuwzIslzshumxksayV5GkpqT+rkVvdPVrA9E= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.2/go.mod h1:jAeo/PdIJZuDSwsvxJS94G4d6h8tStj7WXVuKwLHWU8= -github.com/aws/aws-sdk-go-v2/service/sso v1.11.28 h1:gItLq3zBYyRDPmqAClgzTH8PBjDQGeyptYGHIwtYYNA= -github.com/aws/aws-sdk-go-v2/service/sso v1.11.28/go.mod h1:wo/B7uUm/7zw/dWhBJ4FXuw1sySU5lyIhVg1Bu2yL9A= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.11 h1:KCacyVSs/wlcPGx37hcbT3IGYO8P8Jx+TgSDhAXtQMY= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.11/go.mod h1:TZSH7xLO7+phDtViY/KUp9WGCJMQkLJ/VpgkTFd5gh8= -github.com/aws/aws-sdk-go-v2/service/sts v1.17.7 h1:9Mtq1KM6nD8/+HStvWcvYnixJ5N85DX+P+OY3kI3W2k= -github.com/aws/aws-sdk-go-v2/service/sts v1.17.7/go.mod h1:+lGbb3+1ugwKrNTWcf2RT05Xmp543B06zDFTwiTLp7I= +github.com/aws/aws-sdk-go-v2/service/sso v1.12.0 h1:/2gzjhQowRLarkkBOGPXSRnb8sQ2RVsjdG1C/UliK/c= +github.com/aws/aws-sdk-go-v2/service/sso v1.12.0/go.mod h1:wo/B7uUm/7zw/dWhBJ4FXuw1sySU5lyIhVg1Bu2yL9A= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.0 h1:Jfly6mRxk2ZOSlbCvZfKNS7TukSx1mIzhSsqZ/IGSZI= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.0/go.mod h1:TZSH7xLO7+phDtViY/KUp9WGCJMQkLJ/VpgkTFd5gh8= +github.com/aws/aws-sdk-go-v2/service/sts v1.18.2 h1:J/4wIaGInCEYCGhTSruxCxeoA5cy91a+JT7cHFKFSHQ= +github.com/aws/aws-sdk-go-v2/service/sts v1.18.2/go.mod h1:+lGbb3+1ugwKrNTWcf2RT05Xmp543B06zDFTwiTLp7I= github.com/aws/smithy-go v1.12.0/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8= github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= From be1d16d6c37eb26a9b234029b53c6c72b77d53e3 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 3 Feb 2023 16:40:54 +0100 Subject: [PATCH 1382/2916] tests: Allow to set sub dir for test projects --- e2e/helm_test.go | 8 ++++---- e2e/no_target_test.go | 2 +- e2e/seal_test.go | 18 +++++++++--------- e2e/test-utils/project.go | 32 ++++++++++++++++++++++---------- 4 files changed, 36 insertions(+), 24 deletions(-) diff --git a/e2e/helm_test.go b/e2e/helm_test.go index 9d8f519cb..6993804de 100644 --- a/e2e/helm_test.go +++ b/e2e/helm_test.go @@ -486,8 +486,8 @@ func TestHelmLocalChart(t *testing.T) { p.AddHelmDeployment("helm1", "../test-chart1", "", "", "test-helm-1", p.TestSlug(), nil) p.AddHelmDeployment("helm2", "test-chart2", "", "", "test-helm-2", p.TestSlug(), nil) - test_utils.CreateHelmDir(t, "test-chart1", "0.1.0", filepath.Join(p.LocalRepoDir(), "test-chart1")) - test_utils.CreateHelmDir(t, "test-chart2", "0.1.0", filepath.Join(p.LocalRepoDir(), "helm2/test-chart2")) + test_utils.CreateHelmDir(t, "test-chart1", "0.1.0", filepath.Join(p.LocalProjectDir(), "test-chart1")) + test_utils.CreateHelmDir(t, "test-chart2", "0.1.0", filepath.Join(p.LocalProjectDir(), "helm2/test-chart2")) p.KluctlMust("deploy", "--yes", "-t", "test") assertConfigMapExists(t, k, p.TestSlug(), "test-helm-1-test-chart1") @@ -505,9 +505,9 @@ func getChartDir(t *testing.T, p *test_utils.TestProject, url2 string, chartName } var dir string if u.Scheme == "oci" { - dir = filepath.Join(p.LocalRepoDir(), ".helm-charts", fmt.Sprintf("%s_%s", u.Scheme, u.Hostname()), chartName) + dir = filepath.Join(p.LocalProjectDir(), ".helm-charts", fmt.Sprintf("%s_%s", u.Scheme, u.Hostname()), chartName) } else { - dir = filepath.Join(p.LocalRepoDir(), ".helm-charts", fmt.Sprintf("%s_%s_%s", u.Scheme, u.Port(), u.Hostname()), chartName) + dir = filepath.Join(p.LocalProjectDir(), ".helm-charts", fmt.Sprintf("%s_%s_%s", u.Scheme, u.Port(), u.Hostname()), chartName) } if chartVersion != "" { dir = filepath.Join(dir, chartVersion) diff --git a/e2e/no_target_test.go b/e2e/no_target_test.go index 1451d10d8..90c8086c6 100644 --- a/e2e/no_target_test.go +++ b/e2e/no_target_test.go @@ -26,7 +26,7 @@ func prepareNoTargetTest(t *testing.T, withDeploymentYaml bool) *test_utils.Test p.AddKustomizeDeployment("cm", []test_utils.KustomizeResource{{Name: "cm.yaml", Content: cm}}, nil) } else { p.AddKustomizeResources("", []test_utils.KustomizeResource{{Name: "cm.yaml", Content: cm}}) - err := os.Remove(filepath.Join(p.LocalRepoDir(), "deployment.yml")) + err := os.Remove(filepath.Join(p.LocalProjectDir(), "deployment.yml")) assert.NoError(t, err) } diff --git a/e2e/seal_test.go b/e2e/seal_test.go index 532088948..e0cfab88d 100644 --- a/e2e/seal_test.go +++ b/e2e/seal_test.go @@ -178,7 +178,7 @@ func TestSeal_WithOperator(t *testing.T) { p.KluctlMust("seal", "-t", "test-target") - sealedSecretsDir := p.LocalRepoDir() + sealedSecretsDir := p.LocalProjectDir() assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) p.KluctlMust("deploy", "--yes", "-t", "test-target") @@ -213,7 +213,7 @@ func TestSeal_WithBootstrap(t *testing.T) { p.KluctlMust("seal", "-t", "test-target") - sealedSecretsDir := p.LocalRepoDir() + sealedSecretsDir := p.LocalProjectDir() assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) test_resources.ApplyYaml("sealed-secrets.yaml", k) @@ -261,7 +261,7 @@ func TestSeal_MultipleVarSources(t *testing.T) { p.KluctlMust("seal", "-t", "test-target") - sealedSecretsDir := p.LocalRepoDir() + sealedSecretsDir := p.LocalProjectDir() assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) p.KluctlMust("deploy", "--yes", "-t", "test-target") @@ -301,7 +301,7 @@ func TestSeal_MultipleSecretSets(t *testing.T) { p.KluctlMust("seal", "-t", "test-target") - sealedSecretsDir := p.LocalRepoDir() + sealedSecretsDir := p.LocalProjectDir() assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) p.KluctlMust("deploy", "--yes", "-t", "test-target") @@ -352,7 +352,7 @@ func TestSeal_MultipleTargets(t *testing.T) { p.KluctlMust("seal", "-t", "test-target") p.KluctlMust("seal", "-t", "test-target2") - sealedSecretsDir := p.LocalRepoDir() + sealedSecretsDir := p.LocalProjectDir() assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target2/secret-secret.yml")) @@ -395,7 +395,7 @@ func TestSeal_MultipleSecrets(t *testing.T) { p.KluctlMust("seal", "-t", "test-target") - sealedSecretsDir := p.LocalRepoDir() + sealedSecretsDir := p.LocalProjectDir() assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment2/test-target/secret-secret2.yml")) @@ -442,7 +442,7 @@ func TestSeal_MultipleSecretsInOneFile(t *testing.T) { p.KluctlMust("seal", "-t", "test-target") - sealedSecretsDir := p.LocalRepoDir() + sealedSecretsDir := p.LocalProjectDir() assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) p.KluctlMust("deploy", "--yes", "-t", "test-target") @@ -483,7 +483,7 @@ func TestSeal_File(t *testing.T) { p.KluctlMust("seal", "-t", "test-target") - sealedSecretsDir := p.LocalRepoDir() + sealedSecretsDir := p.LocalProjectDir() assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) p.KluctlMust("deploy", "--yes", "-t", "test-target") @@ -538,7 +538,7 @@ func TestSeal_Vault(t *testing.T) { p.AddExtraEnv("VAULT_TOKEN=root") p.KluctlMust("seal", "-t", "test-target") - sealedSecretsDir := p.LocalRepoDir() + sealedSecretsDir := p.LocalProjectDir() assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) p.KluctlMust("deploy", "--yes", "-t", "test-target") diff --git a/e2e/test-utils/project.go b/e2e/test-utils/project.go index b824a5223..9da4e2a84 100644 --- a/e2e/test-utils/project.go +++ b/e2e/test-utils/project.go @@ -17,6 +17,7 @@ import ( "net/url" "os" "os/exec" + "path" "path/filepath" "reflect" "strings" @@ -29,6 +30,7 @@ type TestProject struct { extraEnv []string useProcess bool + gitSubDir string gitServer *git2.TestGitServer } @@ -41,6 +43,12 @@ func WithUseProcess(useProcess bool) TestProjectOption { } } +func WithGitSubDir(subDir string) TestProjectOption { + return func(p *TestProject) { + p.gitSubDir = subDir + } +} + func NewTestProject(t *testing.T, opts ...TestProjectOption) *TestProject { p := &TestProject{ t: t, @@ -86,8 +94,8 @@ func (p *TestProject) UpdateDeploymentYaml(dir string, update func(o *uo.Unstruc }, "") } -func (p *TestProject) UpdateYaml(path string, update func(o *uo.UnstructuredObject) error, message string) { - p.gitServer.UpdateYaml("kluctl-project", path, func(o map[string]any) error { +func (p *TestProject) UpdateYaml(pth string, update func(o *uo.UnstructuredObject) error, message string) { + p.gitServer.UpdateYaml("kluctl-project", path.Join(p.gitSubDir, pth), func(o map[string]any) error { u := uo.FromMap(o) err := update(u) if err != nil { @@ -98,12 +106,12 @@ func (p *TestProject) UpdateYaml(path string, update func(o *uo.UnstructuredObje }, message) } -func (p *TestProject) UpdateFile(path string, update func(f string) (string, error), message string) { - p.gitServer.UpdateFile("kluctl-project", path, update, message) +func (p *TestProject) UpdateFile(pth string, update func(f string) (string, error), message string) { + p.gitServer.UpdateFile("kluctl-project", path.Join(p.gitSubDir, pth), update, message) } func (p *TestProject) GetYaml(path string) *uo.UnstructuredObject { - o, err := uo.FromFile(filepath.Join(p.LocalRepoDir(), path)) + o, err := uo.FromFile(filepath.Join(p.LocalProjectDir(), path)) if err != nil { p.t.Fatal(err) } @@ -231,7 +239,7 @@ func (p *TestProject) AddKustomizeDeployment(dir string, resources []KustomizeRe p.AddDeploymentIncludes(deploymentDir) } - absKustomizeDir := filepath.Join(p.LocalRepoDir(), dir) + absKustomizeDir := filepath.Join(p.LocalProjectDir(), dir) err := os.MkdirAll(absKustomizeDir, 0o700) if err != nil { @@ -334,11 +342,11 @@ func (p *TestProject) AddKustomizeResources(dir string, resources []KustomizeRes } if r.Content != nil { x := p.convertInterfaceToList(r.Content) - err := yaml.WriteYamlAllFile(filepath.Join(p.LocalRepoDir(), dir, fileName), x) + err := yaml.WriteYamlAllFile(filepath.Join(p.LocalProjectDir(), dir, fileName), x) if err != nil { return err } - _, err = wt.Add(filepath.Join(dir, fileName)) + _, err = wt.Add(filepath.Join(path.Join(p.gitSubDir, dir), fileName)) if err != nil { return err } @@ -372,6 +380,10 @@ func (p *TestProject) LocalRepoDir() string { return p.gitServer.LocalRepoDir("kluctl-project") } +func (p *TestProject) LocalProjectDir() string { + return path.Join(p.LocalRepoDir(), p.gitSubDir) +} + func (p *TestProject) GetGitRepo() *git.Repository { return p.gitServer.GetGitRepo("kluctl-project") } @@ -381,7 +393,7 @@ func (p *TestProject) KluctlProcess(argsIn ...string) (string, string, error) { args = append(args, argsIn...) args = append(args, "--no-update-check") - cwd := p.LocalRepoDir() + cwd := p.LocalProjectDir() args = append(args, "--debug") @@ -422,7 +434,7 @@ func (p *TestProject) KluctlExecute(argsIn ...string) (string, string, error) { } var args []string - args = append(args, "--project-dir", p.LocalRepoDir()) + args = append(args, "--project-dir", p.LocalProjectDir()) args = append(args, argsIn...) p.t.Logf("Runnning kluctl: %s", strings.Join(args, " ")) From f110cb86e5e8ab1ce7db102738f4bf48e405b902 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 3 Feb 2023 16:41:01 +0100 Subject: [PATCH 1383/2916] tests: Implement git include tests --- e2e/git_include_test.go | 55 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 e2e/git_include_test.go diff --git a/e2e/git_include_test.go b/e2e/git_include_test.go new file mode 100644 index 000000000..8eb03fbfe --- /dev/null +++ b/e2e/git_include_test.go @@ -0,0 +1,55 @@ +package e2e + +import ( + "fmt" + test_utils "github.com/kluctl/kluctl/v2/e2e/test-utils" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "testing" +) + +func prepareIncludeProject(t *testing.T, prefix string, subDir string) *test_utils.TestProject { + p := test_utils.NewTestProject(t, test_utils.WithGitSubDir(subDir)) + addConfigMapDeployment(p, "cm", map[string]string{"a": "v"}, resourceOpts{ + name: fmt.Sprintf("%s-cm", prefix), + namespace: p.TestSlug(), + }) + return p +} + +func prepareGitIncludeTest(t *testing.T) (*test_utils.TestProject, *test_utils.TestProject, *test_utils.TestProject) { + k := defaultCluster1 + + p := test_utils.NewTestProject(t) + ip1 := prepareIncludeProject(t, "include1", "") + ip2 := prepareIncludeProject(t, "include2", "subDir") + + createNamespace(t, k, p.TestSlug()) + + p.UpdateTarget("test", func(target *uo.UnstructuredObject) {}) + + p.AddDeploymentItem("", uo.FromMap(map[string]interface{}{ + "git": map[string]any{ + "url": ip1.GitUrl(), + }, + })) + p.AddDeploymentItem("", uo.FromMap(map[string]interface{}{ + "git": map[string]any{ + "url": ip2.GitUrl(), + "subDir": "subDir", + }, + })) + + return p, ip1, ip2 +} + +func TestGitInclude(t *testing.T) { + t.Parallel() + + k := defaultCluster1 + + p, _, _ := prepareGitIncludeTest(t) + + p.KluctlMust("deploy", "--yes", "-t", "test") + assertConfigMapExists(t, k, p.TestSlug(), "include1-cm") + assertConfigMapExists(t, k, p.TestSlug(), "include2-cm") +} From da34b2764dd01602a082816b4935365d8def2158 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 3 Feb 2023 17:20:45 +0100 Subject: [PATCH 1384/2916] tests: Implement --local-git-override tests --- e2e/git_include_test.go | 42 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/e2e/git_include_test.go b/e2e/git_include_test.go index 8eb03fbfe..a78268cb4 100644 --- a/e2e/git_include_test.go +++ b/e2e/git_include_test.go @@ -3,7 +3,12 @@ package e2e import ( "fmt" test_utils "github.com/kluctl/kluctl/v2/e2e/test-utils" + git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/yaml" + cp "github.com/otiai10/copy" + "github.com/stretchr/testify/assert" + "path/filepath" "testing" ) @@ -53,3 +58,40 @@ func TestGitInclude(t *testing.T) { assertConfigMapExists(t, k, p.TestSlug(), "include1-cm") assertConfigMapExists(t, k, p.TestSlug(), "include2-cm") } + +func TestLocalGitOverride(t *testing.T) { + t.Parallel() + + k := defaultCluster1 + p, ip1, ip2 := prepareGitIncludeTest(t) + + override1 := t.TempDir() + err := cp.Copy(ip1.LocalRepoDir(), override1) + assert.NoError(t, err) + + override2 := t.TempDir() + err = cp.Copy(ip2.LocalRepoDir(), override2) + assert.NoError(t, err) + + cm, err := uo.FromFile(filepath.Join(override1, "cm", "configmap-include1-cm.yml")) + assert.NoError(t, err) + _ = cm.SetNestedField("o1", "data", "a") + _ = yaml.WriteYamlFile(filepath.Join(override1, "cm", "configmap-include1-cm.yml"), cm) + + cm, err = uo.FromFile(filepath.Join(override2, "subDir", "cm", "configmap-include2-cm.yml")) + assert.NoError(t, err) + _ = cm.SetNestedField("o2", "data", "a") + _ = yaml.WriteYamlFile(filepath.Join(override2, "subDir", "cm", "configmap-include2-cm.yml"), cm) + + u1, _ := git_url.Parse(ip1.GitUrl()) + u2, _ := git_url.Parse(ip2.GitUrl()) + + p.KluctlMust("deploy", "--yes", "-t", "test", + "--local-git-override", fmt.Sprintf("%s=%s", u1.NormalizedRepoKey(), override1), + "--local-git-override", fmt.Sprintf("%s=%s", u2.NormalizedRepoKey(), override2), + ) + cm = assertConfigMapExists(t, k, p.TestSlug(), "include1-cm") + assertNestedFieldEquals(t, cm, "o1", "data", "a") + cm = assertConfigMapExists(t, k, p.TestSlug(), "include2-cm") + assertNestedFieldEquals(t, cm, "o2", "data", "a") +} From e60ad8feaa9a1c6c6a54dccb0de90ca3c8cc036a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 3 Feb 2023 17:21:16 +0100 Subject: [PATCH 1385/2916] fix: Fix NormalizedRepoKey() with non-default ports --- pkg/git/git-url/url.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/git/git-url/url.go b/pkg/git/git-url/url.go index 452df12ee..727e8796f 100644 --- a/pkg/git/git-url/url.go +++ b/pkg/git/git-url/url.go @@ -101,5 +101,5 @@ func (u *GitUrl) NormalizedRepoKey() string { if u.User != nil && u.User.Username() != "" { username = u.User.Username() + "@" } - return fmt.Sprintf("%s%s:%s", username, u2.Host, u2.Path) + return fmt.Sprintf("%s%s:%s%s", username, u2.Hostname(), u2.Port(), u2.Path) } From 0b0f7271aebf7040dd69ad6119b9e2a8ffe938e7 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 3 Feb 2023 17:23:58 +0100 Subject: [PATCH 1386/2916] feat: Remove broken support for branch overriding --- cmd/kluctl/args/project.go | 2 +- cmd/kluctl/commands/utils.go | 10 +--------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/cmd/kluctl/args/project.go b/cmd/kluctl/args/project.go index c14455303..fc896f813 100644 --- a/cmd/kluctl/args/project.go +++ b/cmd/kluctl/args/project.go @@ -27,7 +27,7 @@ type ProjectFlags struct { Timeout time.Duration `group:"project" help:"Specify timeout for all operations, including loading of the project, all external api calls and waiting for readiness." default:"10m"` GitCacheUpdateInterval time.Duration `group:"project" help:"Specify the time to wait between git cache updates. Defaults to not wait at all and always updating caches."` - LocalGitOverride []string `group:"project" help:"Specify a single repository local git override in the form of 'github.com:my-org/my-repo=/local/path/to/override'. This will cause kluctl to not use git to clone for the specified repository but instead use the local directory. This is useful in case you need to test out changes in external git repositories without pushing them. To only override a single branch of the repo, use 'github.com:my-org/my-repo:my-branch=/local/path/to/override'"` + LocalGitOverride []string `group:"project" help:"Specify a single repository local git override in the form of 'github.com:my-org/my-repo=/local/path/to/override'. This will cause kluctl to not use git to clone for the specified repository but instead use the local directory. This is useful in case you need to test out changes in external git repositories without pushing them."` LocalGitGroupOverride []string `group:"project" help:"Same as --local-git-override, but for a whole group prefix instead of a single repository. All repositories that have the given prefix will be overridden with the given local path and the repository suffix appended. For example, 'gitlab.com:some-org/sub-org=/local/path/to/my-forks' will override all repositories below 'gitlab.com:some-org/sub-org/' with the repositories found in '/local/path/to/my-forks'. It will however only perform an override if the given repository actually exists locally and otherwise revert to the actual (non-overridden) repository."` } diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 63da52161..c789882ac 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -242,20 +242,12 @@ func parseRepoOverride(s string, isGroup bool) (ret repocache.RepoOverride, err return repocache.RepoOverride{}, fmt.Errorf("%s", s) } - sp2 := strings.Split(sp[0], ":") - if len(sp2) < 2 || len(sp2) > 3 { - return repocache.RepoOverride{}, fmt.Errorf("%s", s) - } - - u, err := git_url.Parse(fmt.Sprintf("%s:%s", sp2[0], sp2[1])) + u, err := git_url.Parse(sp[0]) if err != nil { return repocache.RepoOverride{}, fmt.Errorf("%s: %w", s, err) } u = u.Normalize() ret.RepoUrl = *u - if len(sp2) == 3 { - ret.Ref = sp2[2] - } ret.Override = sp[1] return } From aa9f146cefa0fd77e49e76ed7a14bc27f5b659c0 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 3 Feb 2023 21:42:16 +0100 Subject: [PATCH 1387/2916] fix: Check corrent path when looking for overrides --- pkg/repocache/cache.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/repocache/cache.go b/pkg/repocache/cache.go index 131567488..3db2b00c5 100644 --- a/pkg/repocache/cache.go +++ b/pkg/repocache/cache.go @@ -106,7 +106,7 @@ func (rp *GitRepoCache) GetEntry(url git_url.GitUrl) (*CacheEntry, error) { relPath := strings.TrimPrefix(urlN.Path, ro.RepoUrl.Path+"/") overridePath = path.Join(ro.Override, relPath) } else { - if ro.Override != urlN.Path { + if ro.RepoUrl.Path != urlN.Path { continue } overridePath = ro.Override From f314e3eebfcbf7aec7b1789c7252cfef24ce005f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 3 Feb 2023 21:48:20 +0100 Subject: [PATCH 1388/2916] fix: Fix parsing in parseRepoOverride --- cmd/kluctl/commands/utils.go | 5 ++++- e2e/git_include_test.go | 6 ++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index c789882ac..2a610fee0 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -242,7 +242,10 @@ func parseRepoOverride(s string, isGroup bool) (ret repocache.RepoOverride, err return repocache.RepoOverride{}, fmt.Errorf("%s", s) } - u, err := git_url.Parse(sp[0]) + // we need to prepend a dummy scheme to the repo key so that it is properly parsed + dummyUrl := fmt.Sprintf("git://%s", sp[0]) + + u, err := git_url.Parse(dummyUrl) if err != nil { return repocache.RepoOverride{}, fmt.Errorf("%s: %w", s, err) } diff --git a/e2e/git_include_test.go b/e2e/git_include_test.go index a78268cb4..22afa21f8 100644 --- a/e2e/git_include_test.go +++ b/e2e/git_include_test.go @@ -85,10 +85,12 @@ func TestLocalGitOverride(t *testing.T) { u1, _ := git_url.Parse(ip1.GitUrl()) u2, _ := git_url.Parse(ip2.GitUrl()) + k1 := u1.NormalizedRepoKey() + k2 := u2.NormalizedRepoKey() p.KluctlMust("deploy", "--yes", "-t", "test", - "--local-git-override", fmt.Sprintf("%s=%s", u1.NormalizedRepoKey(), override1), - "--local-git-override", fmt.Sprintf("%s=%s", u2.NormalizedRepoKey(), override2), + "--local-git-override", fmt.Sprintf("%s=%s", k1, override1), + "--local-git-override", fmt.Sprintf("%s=%s", k2, override2), ) cm = assertConfigMapExists(t, k, p.TestSlug(), "include1-cm") assertNestedFieldEquals(t, cm, "o1", "data", "a") From 656e102ac357fd9a962a24389ee6dfbc06b8f9a3 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 3 Feb 2023 23:00:30 +0100 Subject: [PATCH 1389/2916] tests: Allow to reuse Git servers for multiple projects --- e2e/test-utils/project.go | 42 ++++++++++++++++++++++++++---------- pkg/git/test_git_server.go | 8 +++++-- pkg/vars/vars_loader_test.go | 6 +++--- 3 files changed, 40 insertions(+), 16 deletions(-) diff --git a/e2e/test-utils/project.go b/e2e/test-utils/project.go index 9da4e2a84..63f918dea 100644 --- a/e2e/test-utils/project.go +++ b/e2e/test-utils/project.go @@ -30,9 +30,10 @@ type TestProject struct { extraEnv []string useProcess bool - gitSubDir string - gitServer *git2.TestGitServer + gitServer *git2.TestGitServer + gitRepoName string + gitSubDir string } type TestProjectOption func(p *TestProject) @@ -43,6 +44,18 @@ func WithUseProcess(useProcess bool) TestProjectOption { } } +func WithGitServer(s *git2.TestGitServer) TestProjectOption { + return func(p *TestProject) { + p.gitServer = s + } +} + +func WithRepoName(n string) TestProjectOption { + return func(p *TestProject) { + p.gitRepoName = n + } +} + func WithGitSubDir(subDir string) TestProjectOption { return func(p *TestProject) { p.gitSubDir = subDir @@ -51,15 +64,18 @@ func WithGitSubDir(subDir string) TestProjectOption { func NewTestProject(t *testing.T, opts ...TestProjectOption) *TestProject { p := &TestProject{ - t: t, + t: t, + gitRepoName: "kluctl-project", } for _, o := range opts { o(p) } - p.gitServer = git2.NewTestGitServer(t) - p.gitServer.GitInit("kluctl-project") + if p.gitServer == nil { + p.gitServer = git2.NewTestGitServer(t) + } + p.gitServer.GitInit(p.gitRepoName) p.UpdateKluctlYaml(func(o *uo.UnstructuredObject) error { return nil @@ -70,6 +86,10 @@ func NewTestProject(t *testing.T, opts ...TestProjectOption) *TestProject { return p } +func (p *TestProject) GitServer() *git2.TestGitServer { + return p.gitServer +} + func (p *TestProject) TestSlug() string { n := p.t.Name() n = xstrings.ToKebabCase(n) @@ -95,7 +115,7 @@ func (p *TestProject) UpdateDeploymentYaml(dir string, update func(o *uo.Unstruc } func (p *TestProject) UpdateYaml(pth string, update func(o *uo.UnstructuredObject) error, message string) { - p.gitServer.UpdateYaml("kluctl-project", path.Join(p.gitSubDir, pth), func(o map[string]any) error { + p.gitServer.UpdateYaml(p.gitRepoName, path.Join(p.gitSubDir, pth), func(o map[string]any) error { u := uo.FromMap(o) err := update(u) if err != nil { @@ -107,7 +127,7 @@ func (p *TestProject) UpdateYaml(pth string, update func(o *uo.UnstructuredObjec } func (p *TestProject) UpdateFile(pth string, update func(f string) (string, error), message string) { - p.gitServer.UpdateFile("kluctl-project", path.Join(p.gitSubDir, pth), update, message) + p.gitServer.UpdateFile(p.gitRepoName, path.Join(p.gitSubDir, pth), update, message) } func (p *TestProject) GetYaml(path string) *uo.UnstructuredObject { @@ -147,7 +167,7 @@ func (p *TestProject) ListDeploymentItemPathes(dir string, fullPath bool) []stri } func (p *TestProject) UpdateKustomizeDeployment(dir string, update func(o *uo.UnstructuredObject, wt *git.Worktree) error) { - wt := p.gitServer.GetWorktree("kluctl-project") + wt := p.gitServer.GetWorktree(p.gitRepoName) pth := filepath.Join(dir, "kustomization.yml") p.UpdateYaml(pth, func(o *uo.UnstructuredObject) error { @@ -373,11 +393,11 @@ func (p *TestProject) DeleteKustomizeDeployment(dir string) { } func (p *TestProject) GitUrl() string { - return p.gitServer.GitUrl("kluctl-project") + return p.gitServer.GitRepoUrl(p.gitRepoName) } func (p *TestProject) LocalRepoDir() string { - return p.gitServer.LocalRepoDir("kluctl-project") + return p.gitServer.LocalRepoDir(p.gitRepoName) } func (p *TestProject) LocalProjectDir() string { @@ -385,7 +405,7 @@ func (p *TestProject) LocalProjectDir() string { } func (p *TestProject) GetGitRepo() *git.Repository { - return p.gitServer.GetGitRepo("kluctl-project") + return p.gitServer.GetGitRepo(p.gitRepoName) } func (p *TestProject) KluctlProcess(argsIn ...string) (string, string, error) { diff --git a/pkg/git/test_git_server.go b/pkg/git/test_git_server.go index 7dd0a1614..fc58d02ab 100644 --- a/pkg/git/test_git_server.go +++ b/pkg/git/test_git_server.go @@ -223,8 +223,12 @@ func (p *TestGitServer) UpdateYaml(repo string, pth string, update func(o map[st p.CommitYaml(repo, pth, message, o) } -func (p *TestGitServer) GitUrl(repo string) string { - return fmt.Sprintf("http://localhost:%d/%s/.git", p.gitServerPort, repo) +func (p *TestGitServer) GitUrl() string { + return fmt.Sprintf("http://localhost:%d", p.gitServerPort) +} + +func (p *TestGitServer) GitRepoUrl(repo string) string { + return fmt.Sprintf("%s/%s/.git", p.GitUrl(), repo) } func (p *TestGitServer) LocalRepoDir(repo string) string { diff --git a/pkg/vars/vars_loader_test.go b/pkg/vars/vars_loader_test.go index 40c038e99..a5b774f45 100644 --- a/pkg/vars/vars_loader_test.go +++ b/pkg/vars/vars_loader_test.go @@ -199,7 +199,7 @@ func TestVarsLoader_Git(t *testing.T) { }, "") testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { - url, _ := git_url.Parse(gs.GitUrl("repo")) + url, _ := git_url.Parse(gs.GitRepoUrl("repo")) err := vl.LoadVars(vc, &types.VarsSource{ Git: &types.VarsSourceGit{ Url: *url, @@ -213,7 +213,7 @@ func TestVarsLoader_Git(t *testing.T) { }) testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { - url, _ := git_url.Parse(gs.GitUrl("repo")) + url, _ := git_url.Parse(gs.GitRepoUrl("repo")) b := true err := vl.LoadVars(vc, &types.VarsSource{ IgnoreMissing: &b, @@ -250,7 +250,7 @@ func TestVarsLoader_GitBranch(t *testing.T) { assert.NoError(t, err) testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { - url, _ := git_url.Parse(gs.GitUrl("repo")) + url, _ := git_url.Parse(gs.GitRepoUrl("repo")) err = vl.LoadVars(vc, &types.VarsSource{ Git: &types.VarsSourceGit{ Url: *url, From 8f98b33eaf96a80f0807b9de80801f8eb62486d6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 3 Feb 2023 23:00:45 +0100 Subject: [PATCH 1390/2916] tests: Implement --local-git-group-override tests --- e2e/git_include_test.go | 59 +++++++++++++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/e2e/git_include_test.go b/e2e/git_include_test.go index 22afa21f8..fc24635e1 100644 --- a/e2e/git_include_test.go +++ b/e2e/git_include_test.go @@ -3,6 +3,7 @@ package e2e import ( "fmt" test_utils "github.com/kluctl/kluctl/v2/e2e/test-utils" + git2 "github.com/kluctl/kluctl/v2/pkg/git" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" @@ -12,8 +13,12 @@ import ( "testing" ) -func prepareIncludeProject(t *testing.T, prefix string, subDir string) *test_utils.TestProject { - p := test_utils.NewTestProject(t, test_utils.WithGitSubDir(subDir)) +func prepareIncludeProject(t *testing.T, prefix string, subDir string, gitServer *git2.TestGitServer) *test_utils.TestProject { + p := test_utils.NewTestProject(t, + test_utils.WithGitSubDir(subDir), + test_utils.WithGitServer(gitServer), + test_utils.WithRepoName(fmt.Sprintf("repos/%s", prefix)), + ) addConfigMapDeployment(p, "cm", map[string]string{"a": "v"}, resourceOpts{ name: fmt.Sprintf("%s-cm", prefix), namespace: p.TestSlug(), @@ -21,12 +26,12 @@ func prepareIncludeProject(t *testing.T, prefix string, subDir string) *test_uti return p } -func prepareGitIncludeTest(t *testing.T) (*test_utils.TestProject, *test_utils.TestProject, *test_utils.TestProject) { +func prepareGitIncludeTest(t *testing.T, gitServer *git2.TestGitServer) (*test_utils.TestProject, *test_utils.TestProject, *test_utils.TestProject) { k := defaultCluster1 - p := test_utils.NewTestProject(t) - ip1 := prepareIncludeProject(t, "include1", "") - ip2 := prepareIncludeProject(t, "include2", "subDir") + p := test_utils.NewTestProject(t, test_utils.WithGitServer(gitServer)) + ip1 := prepareIncludeProject(t, "include1", "", gitServer) + ip2 := prepareIncludeProject(t, "include2", "subDir", gitServer) createNamespace(t, k, p.TestSlug()) @@ -52,7 +57,7 @@ func TestGitInclude(t *testing.T) { k := defaultCluster1 - p, _, _ := prepareGitIncludeTest(t) + p, _, _ := prepareGitIncludeTest(t, nil) p.KluctlMust("deploy", "--yes", "-t", "test") assertConfigMapExists(t, k, p.TestSlug(), "include1-cm") @@ -63,7 +68,7 @@ func TestLocalGitOverride(t *testing.T) { t.Parallel() k := defaultCluster1 - p, ip1, ip2 := prepareGitIncludeTest(t) + p, ip1, ip2 := prepareGitIncludeTest(t, nil) override1 := t.TempDir() err := cp.Copy(ip1.LocalRepoDir(), override1) @@ -97,3 +102,41 @@ func TestLocalGitOverride(t *testing.T) { cm = assertConfigMapExists(t, k, p.TestSlug(), "include2-cm") assertNestedFieldEquals(t, cm, "o2", "data", "a") } + +func TestLocalGitGroupOverride(t *testing.T) { + t.Parallel() + + k := defaultCluster1 + p, ip1, ip2 := prepareGitIncludeTest(t, git2.NewTestGitServer(t)) + + overrideGroupDir := t.TempDir() + + override1 := filepath.Join(overrideGroupDir, "include1") + err := cp.Copy(ip1.LocalRepoDir(), override1) + assert.NoError(t, err) + + override2 := filepath.Join(overrideGroupDir, "include2") + err = cp.Copy(ip2.LocalRepoDir(), override2) + assert.NoError(t, err) + + cm, err := uo.FromFile(filepath.Join(override1, "cm", "configmap-include1-cm.yml")) + assert.NoError(t, err) + _ = cm.SetNestedField("o1", "data", "a") + _ = yaml.WriteYamlFile(filepath.Join(override1, "cm", "configmap-include1-cm.yml"), cm) + + cm, err = uo.FromFile(filepath.Join(override2, "subDir", "cm", "configmap-include2-cm.yml")) + assert.NoError(t, err) + _ = cm.SetNestedField("o2", "data", "a") + _ = yaml.WriteYamlFile(filepath.Join(override2, "subDir", "cm", "configmap-include2-cm.yml"), cm) + + u1, _ := git_url.Parse(p.GitServer().GitUrl() + "/repos") + k1 := u1.NormalizedRepoKey() + + p.KluctlMust("deploy", "--yes", "-t", "test", + "--local-git-group-override", fmt.Sprintf("%s=%s", k1, overrideGroupDir), + ) + cm = assertConfigMapExists(t, k, p.TestSlug(), "include1-cm") + assertNestedFieldEquals(t, cm, "o1", "data", "a") + cm = assertConfigMapExists(t, k, p.TestSlug(), "include2-cm") + assertNestedFieldEquals(t, cm, "o2", "data", "a") +} From 2444089ee2cd682c000f973fd4e93927d6568a59 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 3 Feb 2023 23:05:56 +0100 Subject: [PATCH 1391/2916] docs: Run make replace-commands-help --- docs/reference/commands/common-arguments.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/reference/commands/common-arguments.md b/docs/reference/commands/common-arguments.md index ac7f3d69a..c08d93b4b 100644 --- a/docs/reference/commands/common-arguments.md +++ b/docs/reference/commands/common-arguments.md @@ -67,8 +67,7 @@ Project arguments: cause kluctl to not use git to clone for the specified repository but instead use the local directory. This is useful in case you need to test out changes in external git repositories without - pushing them. To only override a single branch of the repo, use - 'github.com:my-org/my-repo:my-branch=/local/path/to/override' + pushing them. -c, --project-config existingfile Location of the .kluctl.yaml config file. Defaults to $PROJECT/.kluctl.yaml --project-dir existingdir Specify the project directory. Defaults to the current working From 521c62dd6e1d858e11d1a4a6c1d375d886ef36d7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 5 Feb 2023 22:21:11 +0100 Subject: [PATCH 1392/2916] chore(deps): Bump k8s.io/apiextensions-apiserver from 0.26.0 to 0.26.1 (#286) Bumps [k8s.io/apiextensions-apiserver](https://github.com/kubernetes/apiextensions-apiserver) from 0.26.0 to 0.26.1. - [Release notes](https://github.com/kubernetes/apiextensions-apiserver/releases) - [Commits](https://github.com/kubernetes/apiextensions-apiserver/compare/v0.26.0...v0.26.1) --- updated-dependencies: - dependency-name: k8s.io/apiextensions-apiserver dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 12 ++++++------ go.sum | 24 ++++++++++++------------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index c83c9c98b..235ac37db 100644 --- a/go.mod +++ b/go.mod @@ -47,10 +47,10 @@ require ( gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 helm.sh/helm/v3 v3.10.3 - k8s.io/api v0.26.0 - k8s.io/apiextensions-apiserver v0.26.0 - k8s.io/apimachinery v0.26.0 - k8s.io/client-go v0.26.0 + k8s.io/api v0.26.1 + k8s.io/apiextensions-apiserver v0.26.1 + k8s.io/apimachinery v0.26.1 + k8s.io/client-go v0.26.1 k8s.io/klog/v2 v2.80.1 sigs.k8s.io/kustomize/kyaml v0.13.9 sigs.k8s.io/structured-merge-diff/v4 v4.2.3 @@ -253,9 +253,9 @@ require ( gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gotest.tools/v3 v3.2.0 // indirect - k8s.io/apiserver v0.26.0 // indirect + k8s.io/apiserver v0.26.1 // indirect k8s.io/cli-runtime v0.26.0 // indirect - k8s.io/component-base v0.26.0 // indirect + k8s.io/component-base v0.26.1 // indirect k8s.io/kube-openapi v0.0.0-20221207184640-f3cff1453715 // indirect k8s.io/kubectl v0.26.0 // indirect k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 // indirect diff --git a/go.sum b/go.sum index 58e4aa64c..5181e8dd0 100644 --- a/go.sum +++ b/go.sum @@ -1323,20 +1323,20 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.26.0 h1:IpPlZnxBpV1xl7TGk/X6lFtpgjgntCg8PJ+qrPHAC7I= -k8s.io/api v0.26.0/go.mod h1:k6HDTaIFC8yn1i6pSClSqIwLABIcLV9l5Q4EcngKnQg= -k8s.io/apiextensions-apiserver v0.26.0 h1:Gy93Xo1eg2ZIkNX/8vy5xviVSxwQulsnUdQ00nEdpDo= -k8s.io/apiextensions-apiserver v0.26.0/go.mod h1:7ez0LTiyW5nq3vADtK6C3kMESxadD51Bh6uz3JOlqWQ= -k8s.io/apimachinery v0.26.0 h1:1feANjElT7MvPqp0JT6F3Ss6TWDwmcjLypwoPpEf7zg= -k8s.io/apimachinery v0.26.0/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74= -k8s.io/apiserver v0.26.0 h1:q+LqIK5EZwdznGZb8bq0+a+vCqdeEEe4Ux3zsOjbc4o= -k8s.io/apiserver v0.26.0/go.mod h1:aWhlLD+mU+xRo+zhkvP/gFNbShI4wBDHS33o0+JGI84= +k8s.io/api v0.26.1 h1:f+SWYiPd/GsiWwVRz+NbFyCgvv75Pk9NK6dlkZgpCRQ= +k8s.io/api v0.26.1/go.mod h1:xd/GBNgR0f707+ATNyPmQ1oyKSgndzXij81FzWGsejg= +k8s.io/apiextensions-apiserver v0.26.1 h1:cB8h1SRk6e/+i3NOrQgSFij1B2S0Y0wDoNl66bn8RMI= +k8s.io/apiextensions-apiserver v0.26.1/go.mod h1:AptjOSXDGuE0JICx/Em15PaoO7buLwTs0dGleIHixSM= +k8s.io/apimachinery v0.26.1 h1:8EZ/eGJL+hY/MYCNwhmDzVqq2lPl3N3Bo8rvweJwXUQ= +k8s.io/apimachinery v0.26.1/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74= +k8s.io/apiserver v0.26.1 h1:6vmnAqCDO194SVCPU3MU8NcDgSqsUA62tBUSWrFXhsc= +k8s.io/apiserver v0.26.1/go.mod h1:wr75z634Cv+sifswE9HlAo5FQ7UoUauIICRlOE+5dCg= k8s.io/cli-runtime v0.26.0 h1:aQHa1SyUhpqxAw1fY21x2z2OS5RLtMJOCj7tN4oq8mw= k8s.io/cli-runtime v0.26.0/go.mod h1:o+4KmwHzO/UK0wepE1qpRk6l3o60/txUZ1fEXWGIKTY= -k8s.io/client-go v0.26.0 h1:lT1D3OfO+wIi9UFolCrifbjUUgu7CpLca0AD8ghRLI8= -k8s.io/client-go v0.26.0/go.mod h1:I2Sh57A79EQsDmn7F7ASpmru1cceh3ocVT9KlX2jEZg= -k8s.io/component-base v0.26.0 h1:0IkChOCohtDHttmKuz+EP3j3+qKmV55rM9gIFTXA7Vs= -k8s.io/component-base v0.26.0/go.mod h1:lqHwlfV1/haa14F/Z5Zizk5QmzaVf23nQzCwVOQpfC8= +k8s.io/client-go v0.26.1 h1:87CXzYJnAMGaa/IDDfRdhTzxk/wzGZ+/HUQpqgVSZXU= +k8s.io/client-go v0.26.1/go.mod h1:IWNSglg+rQ3OcvDkhY6+QLeasV4OYHDjdqeWkDQZwGE= +k8s.io/component-base v0.26.1 h1:4ahudpeQXHZL5kko+iDHqLj/FSGAEUnSVO0EBbgDd+4= +k8s.io/component-base v0.26.1/go.mod h1:VHrLR0b58oC035w6YQiBSbtsf0ThuSwXP+p5dD/kAWU= k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20221207184640-f3cff1453715 h1:tBEbstoM+K0FiBV5KGAKQ0kuvf54v/hwpldiJt69w1s= From fc14969e8dedba4f8d700218d05919c0b39ca253 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 5 Feb 2023 22:21:32 +0100 Subject: [PATCH 1393/2916] chore(deps): Bump github.com/aws/aws-sdk-go-v2/config (#283) Bumps [github.com/aws/aws-sdk-go-v2/config](https://github.com/aws/aws-sdk-go-v2) from 1.18.11 to 1.18.12. - [Release notes](https://github.com/aws/aws-sdk-go-v2/releases) - [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go-v2/compare/config/v1.18.11...config/v1.18.12) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go-v2/config dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 22 +++++++++++----------- go.sum | 41 ++++++++++++++++++++++------------------- 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/go.mod b/go.mod index 235ac37db..f8f74f210 100644 --- a/go.mod +++ b/go.mod @@ -58,8 +58,8 @@ require ( require ( filippo.io/age v1.1.1 - github.com/aws/aws-sdk-go-v2 v1.17.3 - github.com/aws/aws-sdk-go-v2/config v1.18.11 + github.com/aws/aws-sdk-go-v2 v1.17.4 + github.com/aws/aws-sdk-go-v2/config v1.18.12 github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.2 github.com/go-logr/logr v1.2.3 github.com/hashicorp/go-multierror v1.1.1 @@ -95,16 +95,16 @@ require ( github.com/armon/go-metrics v0.4.1 // indirect github.com/armon/go-radix v1.0.0 // indirect github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.13.11 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.21 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.27 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.21 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.3.28 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.21 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.13.12 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.22 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.28 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.22 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.3.29 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.22 // indirect github.com/aws/aws-sdk-go-v2/service/kms v1.17.5 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.12.0 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.0 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.18.2 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.12.1 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.18.3 // indirect github.com/aws/smithy-go v1.13.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver v3.5.1+incompatible // indirect diff --git a/go.sum b/go.sum index 5181e8dd0..2c9a9ea0c 100644 --- a/go.sum +++ b/go.sum @@ -119,34 +119,37 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkY github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go-v2 v1.16.7/go.mod h1:6CpKuLXg2w7If3ABZCl/qZ6rEgwtjZTn4eAf4RcEyuw= -github.com/aws/aws-sdk-go-v2 v1.17.3 h1:shN7NlnVzvDUgPQ+1rLMSxY8OWRNDRYtiqe0p/PgrhY= github.com/aws/aws-sdk-go-v2 v1.17.3/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= -github.com/aws/aws-sdk-go-v2/config v1.18.11 h1:7dJD4p90OyKYIihuwe/LbHfP7uw4yVm5P1hel+b8UZ8= -github.com/aws/aws-sdk-go-v2/config v1.18.11/go.mod h1:FTGKr2F7QL7IAg22dUmEB5NWpLPAOuhrONzXe7TVhAI= -github.com/aws/aws-sdk-go-v2/credentials v1.13.11 h1:QnvlTut1XXKkX4aaM1Ydo5X0CHriv0jmLu8PTVQQJJo= -github.com/aws/aws-sdk-go-v2/credentials v1.13.11/go.mod h1:tqAm4JmQaShel+Qi38hmd1QglSnnxaYt50k/9yGQzzc= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.21 h1:j9wi1kQ8b+e0FBVHxCqCGo4kxDU175hoDHcWAi0sauU= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.21/go.mod h1:ugwW57Z5Z48bpvUyZuaPy4Kv+vEfJWnIrky7RmkBvJg= +github.com/aws/aws-sdk-go-v2 v1.17.4 h1:wyC6p9Yfq6V2y98wfDsj6OnNQa4w2BLGCLIxzNhwOGY= +github.com/aws/aws-sdk-go-v2 v1.17.4/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= +github.com/aws/aws-sdk-go-v2/config v1.18.12 h1:fKs/I4wccmfrNRO9rdrbMO1NgLxct6H9rNMiPdBxHWw= +github.com/aws/aws-sdk-go-v2/config v1.18.12/go.mod h1:J36fOhj1LQBr+O4hJCiT8FwVvieeoSGOtPuvhKlsNu8= +github.com/aws/aws-sdk-go-v2/credentials v1.13.12 h1:Cb+HhuEnV19zHRaYYVglwvdHGMJWbdsyP4oHhw04xws= +github.com/aws/aws-sdk-go-v2/credentials v1.13.12/go.mod h1:37HG2MBroXK3jXfxVGtbM2J48ra2+Ltu+tmwr/jO0KA= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.22 h1:3aMfcTmoXtTZnaT86QlVaYh+BRMbvrrmZwIQ5jWqCZQ= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.22/go.mod h1:YGSIJyQ6D6FjKMQh16hVFSIUD54L4F7zTGePqYMYYJU= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.14/go.mod h1:kdjrMwHwrC3+FsKhNcCMJ7tUVj/8uSD5CZXeQ4wV6fM= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.27 h1:I3cakv2Uy1vNmmhRQmFptYDxOvBnwCdNwyw63N0RaRU= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.27/go.mod h1:a1/UpzeyBBerajpnP5nGZa9mGzsBn5cOKxm6NWQsvoI= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.28 h1:r+XwaCLpIvCKjBIYy/HVZujQS9tsz5ohHG3ZIe0wKoE= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.28/go.mod h1:3lwChorpIM/BhImY/hy+Z6jekmN92cXGPI1QJasVPYY= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.8/go.mod h1:ZIV8GYoC6WLBW5KGs+o4rsc65/ozd+eQ0L31XF5VDwk= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.21 h1:5NbbMrIzmUn/TXFqAle6mgrH5m9cOvMLRGL7pnG8tRE= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.21/go.mod h1:+Gxn8jYn5k9ebfHEqlhrMirFjSW0v0C9fI+KN5vk2kE= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.28 h1:KeTxcGdNnQudb46oOl4d90f2I33DF/c6q3RnZAmvQdQ= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.28/go.mod h1:yRZVr/iT0AqyHeep00SZ4YfBAKojXz08w3XMBscdi0c= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.21 h1:5C6XgTViSb0bunmU57b3CT+MhxULqHH2721FVA+/kDM= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.21/go.mod h1:lRToEJsn+DRA9lW4O9L9+/3hjTkUzlzyzHqn8MTds5k= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.22 h1:7AwGYXDdqRQYsluvKFmWoqpcOQJ4bH634SkYf3FNj/A= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.22/go.mod h1:EqK7gVrIGAHyZItrD1D8B0ilgwMD1GiWAmbU4u/JHNk= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.29 h1:J4xhFd6zHhdF9jPP0FQJ6WknzBboGMBNjKOv4iTuw4A= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.29/go.mod h1:TwuqRBGzxjQJIwH16/fOZodwXt2Zxa9/cwJC5ke4j7s= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.22 h1:LjFQf8hFuMO22HkV5VWGLBvmCLBCLPivUAmpdpnp4Vs= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.22/go.mod h1:xt0Au8yPIwYXf/GYPy/vl4K3CgwhfQMYbrH7DlUUIws= github.com/aws/aws-sdk-go-v2/service/kms v1.17.5 h1:DkubF+BSEy0uX59+pYySzWFReN3fCcXobIO8L5Phh24= github.com/aws/aws-sdk-go-v2/service/kms v1.17.5/go.mod h1:ubAtMGRUMVv5kX8lpbeDguxZ64pR4kXTGApY4sCM0io= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.2 h1:QDVKb2VpuwzIslzshumxksayV5GkpqT+rkVvdPVrA9E= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.2/go.mod h1:jAeo/PdIJZuDSwsvxJS94G4d6h8tStj7WXVuKwLHWU8= -github.com/aws/aws-sdk-go-v2/service/sso v1.12.0 h1:/2gzjhQowRLarkkBOGPXSRnb8sQ2RVsjdG1C/UliK/c= -github.com/aws/aws-sdk-go-v2/service/sso v1.12.0/go.mod h1:wo/B7uUm/7zw/dWhBJ4FXuw1sySU5lyIhVg1Bu2yL9A= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.0 h1:Jfly6mRxk2ZOSlbCvZfKNS7TukSx1mIzhSsqZ/IGSZI= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.0/go.mod h1:TZSH7xLO7+phDtViY/KUp9WGCJMQkLJ/VpgkTFd5gh8= -github.com/aws/aws-sdk-go-v2/service/sts v1.18.2 h1:J/4wIaGInCEYCGhTSruxCxeoA5cy91a+JT7cHFKFSHQ= -github.com/aws/aws-sdk-go-v2/service/sts v1.18.2/go.mod h1:+lGbb3+1ugwKrNTWcf2RT05Xmp543B06zDFTwiTLp7I= +github.com/aws/aws-sdk-go-v2/service/sso v1.12.1 h1:lQKN/LNa3qqu2cDOQZybP7oL4nMGGiFqob0jZJaR8/4= +github.com/aws/aws-sdk-go-v2/service/sso v1.12.1/go.mod h1:IgV8l3sj22nQDd5qcAGY0WenwCzCphqdbFOpfktZPrI= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.1 h1:0bLhH6DRAqox+g0LatcjGKjjhU6Eudyys6HB6DJVPj8= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.1/go.mod h1:O1YSOg3aekZibh2SngvCRRG+cRHKKlYgxf/JBF/Kr/k= +github.com/aws/aws-sdk-go-v2/service/sts v1.18.3 h1:s49mSnsBZEXjfGBkRfmK+nPqzT7Lt3+t2SmAKNyHblw= +github.com/aws/aws-sdk-go-v2/service/sts v1.18.3/go.mod h1:b+psTJn33Q4qGoDaM7ZiOVVG8uVjGI6HaZ8WBHdgDgU= github.com/aws/smithy-go v1.12.0/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8= github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= From b50bac1e545e641ca38eb364d8efba1ab2e9d049 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 5 Feb 2023 22:21:48 +0100 Subject: [PATCH 1394/2916] chore(deps): Bump github.com/go-playground/validator/v10 (#284) Bumps [github.com/go-playground/validator/v10](https://github.com/go-playground/validator) from 10.11.1 to 10.11.2. - [Release notes](https://github.com/go-playground/validator/releases) - [Commits](https://github.com/go-playground/validator/compare/v10.11.1...v10.11.2) --- updated-dependencies: - dependency-name: github.com/go-playground/validator/v10 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 19 +++++++------------ 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index f8f74f210..1f00bd653 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( // See https://github.com/fluxcd/pkg/issues/397, especially in regard to skeema/knownhosts breaking compatibility github.com/fluxcd/go-git/v5 v5.0.0-20221206140629-ec778c2c37df github.com/fluxcd/pkg/kustomize v0.12.0 - github.com/go-playground/validator/v10 v10.11.1 + github.com/go-playground/validator/v10 v10.11.2 github.com/gobwas/glob v0.2.3 // indirect github.com/golang-jwt/jwt/v4 v4.4.3 github.com/google/go-containerregistry v0.12.1 @@ -138,8 +138,8 @@ require ( github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.20.0 // indirect github.com/go-openapi/swag v0.22.3 // indirect - github.com/go-playground/locales v0.14.0 // indirect - github.com/go-playground/universal-translator v0.18.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect diff --git a/go.sum b/go.sum index 2c9a9ea0c..da078e7e6 100644 --- a/go.sum +++ b/go.sum @@ -306,14 +306,13 @@ github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXym github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= -github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= -github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= -github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= -github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= -github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU= +github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= @@ -559,7 +558,6 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -745,7 +743,6 @@ github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw= github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= @@ -887,7 +884,6 @@ golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -1063,7 +1059,6 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= From c3cbb70d289b83504094166dd65868290de9c06d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 6 Feb 2023 17:38:21 +0100 Subject: [PATCH 1395/2916] fix: Upgrade go-jinja2 and go-embed-python to fix Alpine support --- go.mod | 10 +++++----- go.sum | 21 ++++++++++++--------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index 1f00bd653..7ba28228c 100644 --- a/go.mod +++ b/go.mod @@ -21,8 +21,8 @@ require ( github.com/imdario/mergo v0.3.13 github.com/jinzhu/copier v0.3.5 github.com/kevinburke/ssh_config v1.2.0 - github.com/kluctl/go-embed-python v0.0.0-3.10.8-20221106-1 - github.com/kluctl/go-jinja2 v0.0.0-20221215083015-c3f906953ba1 + github.com/kluctl/go-embed-python v0.0.0-3.10.9-20230206-2 + github.com/kluctl/go-jinja2 v0.0.0-20230206202229-6e5a9f576647 github.com/mattn/go-colorable v0.1.13 github.com/mattn/go-isatty v0.0.17 github.com/mattn/go-runewidth v0.0.14 @@ -132,8 +132,8 @@ require ( github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-git/gcfg v1.5.0 // indirect - github.com/go-git/go-billy/v5 v5.3.1 // indirect - github.com/go-git/go-git/v5 v5.5.1 // indirect + github.com/go-git/go-billy/v5 v5.4.1 // indirect + github.com/go-git/go-git/v5 v5.5.2 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.20.0 // indirect @@ -179,7 +179,7 @@ require ( github.com/jmoiron/sqlx v1.3.5 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.15.13 // indirect + github.com/klauspost/compress v1.15.15 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect diff --git a/go.sum b/go.sum index da078e7e6..a8900a476 100644 --- a/go.sum +++ b/go.sum @@ -277,12 +277,14 @@ github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxI github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= -github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34= github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-billy/v5 v5.4.0/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg= +github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4= +github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg= github.com/go-git/go-git-fixtures/v4 v4.3.1 h1:y5z6dd3qi8Hl+stezc8p3JxDkoTRqMAlKnXHuzrfjTQ= github.com/go-git/go-git-fixtures/v4 v4.3.1/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo= -github.com/go-git/go-git/v5 v5.5.1 h1:5vtv2TB5PM/gPM+EvsHJ16hJh4uAkdGcKilcwY7FYwo= -github.com/go-git/go-git/v5 v5.5.1/go.mod h1:uz5PQ3d0gz7mSgzZhSJToM6ALPaKCdSnl58/Xb5hzr8= +github.com/go-git/go-git/v5 v5.5.2 h1:v8lgZa5k9ylUw+OR/roJHTxR4QItsNFI5nKtAXFuynw= +github.com/go-git/go-git/v5 v5.5.2/go.mod h1:BE5hUJ5yaV2YMxhmaP4l6RBQ08kMxKSPD4BlxtH7OjI= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -545,12 +547,12 @@ github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4 github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.15.13 h1:NFn1Wr8cfnenSJSA46lLq4wHCcBzKTSjnBIexDMMOV0= -github.com/klauspost/compress v1.15.13/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= -github.com/kluctl/go-embed-python v0.0.0-3.10.8-20221106-1 h1:PXkdM8Z/Lh1+GIjXVG3dFs5enQoQ1eC7JXMz/Mo4R04= -github.com/kluctl/go-embed-python v0.0.0-3.10.8-20221106-1/go.mod h1:qVVSnYkq5NSgUuLzz4hTCXvVsRQyF/ic4U5v7DaiCGM= -github.com/kluctl/go-jinja2 v0.0.0-20221215083015-c3f906953ba1 h1:LwFIJE5cb6AAThM8FPbg589Y7zaToSrBCEQ1bxs8eiY= -github.com/kluctl/go-jinja2 v0.0.0-20221215083015-c3f906953ba1/go.mod h1:hkHz+qOoK67xFAM8M6OkvvXpEsYq8ZKQxkYkat6J058= +github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= +github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= +github.com/kluctl/go-embed-python v0.0.0-3.10.9-20230206-2 h1:K96SwIr8MzBQ3kFFz2H/pA2y+EEk04vZ4fWj/YZghBU= +github.com/kluctl/go-embed-python v0.0.0-3.10.9-20230206-2/go.mod h1:vzngsPshNKUtq0gxkYQKNJafrcH7Qy7Qt6yGNt7JmQI= +github.com/kluctl/go-jinja2 v0.0.0-20230206202229-6e5a9f576647 h1:H7qN4RcRsFX6OuKn2La0ueu0nSFE9uPI/AMcgF5fXi8= +github.com/kluctl/go-jinja2 v0.0.0-20230206202229-6e5a9f576647/go.mod h1:oGEuC0taLwg20ORdSN7cdrN4rook79mJJ01QZIU858U= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kortschak/utter v1.0.1/go.mod h1:vSmSjbyrlKjjsL71193LmzBOKgwePk9DH6uFaWHIInc= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= @@ -1072,6 +1074,7 @@ golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= From e5ede940642c035c3e01428eef8e0bd6f4bdf1eb Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 6 Feb 2023 17:38:33 +0100 Subject: [PATCH 1396/2916] feat: Switch to alpine for docker image --- Dockerfile | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index b22c03a9a..f3ee702f7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,6 @@ -# We must use a glibc based distro due to embedded python not supporting musl libc for aarch64 -FROM debian:bullseye-slim +FROM alpine:3.17.1 -RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates && rm -rf /var/lib/apt/lists/* +RUN apk add ca-certificates COPY kluctl /usr/bin/ ENTRYPOINT ["/usr/bin/kluctl"] From 6f132f7f808a8d26722d9a6950049ebda4fc6a38 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Feb 2023 08:37:20 +0100 Subject: [PATCH 1397/2916] chore(deps): Bump github.com/aws/aws-sdk-go-v2/service/secretsmanager (#292) Bumps [github.com/aws/aws-sdk-go-v2/service/secretsmanager](https://github.com/aws/aws-sdk-go-v2) from 1.18.2 to 1.18.3. - [Release notes](https://github.com/aws/aws-sdk-go-v2/releases) - [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go-v2/compare/config/v1.18.2...config/v1.18.3) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go-v2/service/secretsmanager dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 1f00bd653..0e761063f 100644 --- a/go.mod +++ b/go.mod @@ -60,7 +60,7 @@ require ( filippo.io/age v1.1.1 github.com/aws/aws-sdk-go-v2 v1.17.4 github.com/aws/aws-sdk-go-v2/config v1.18.12 - github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.2 + github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.3 github.com/go-logr/logr v1.2.3 github.com/hashicorp/go-multierror v1.1.1 github.com/huandu/xstrings v1.4.0 diff --git a/go.sum b/go.sum index da078e7e6..d98304d6d 100644 --- a/go.sum +++ b/go.sum @@ -119,7 +119,6 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkY github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go-v2 v1.16.7/go.mod h1:6CpKuLXg2w7If3ABZCl/qZ6rEgwtjZTn4eAf4RcEyuw= -github.com/aws/aws-sdk-go-v2 v1.17.3/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= github.com/aws/aws-sdk-go-v2 v1.17.4 h1:wyC6p9Yfq6V2y98wfDsj6OnNQa4w2BLGCLIxzNhwOGY= github.com/aws/aws-sdk-go-v2 v1.17.4/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= github.com/aws/aws-sdk-go-v2/config v1.18.12 h1:fKs/I4wccmfrNRO9rdrbMO1NgLxct6H9rNMiPdBxHWw= @@ -129,11 +128,9 @@ github.com/aws/aws-sdk-go-v2/credentials v1.13.12/go.mod h1:37HG2MBroXK3jXfxVGtb github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.22 h1:3aMfcTmoXtTZnaT86QlVaYh+BRMbvrrmZwIQ5jWqCZQ= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.22/go.mod h1:YGSIJyQ6D6FjKMQh16hVFSIUD54L4F7zTGePqYMYYJU= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.14/go.mod h1:kdjrMwHwrC3+FsKhNcCMJ7tUVj/8uSD5CZXeQ4wV6fM= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.27/go.mod h1:a1/UpzeyBBerajpnP5nGZa9mGzsBn5cOKxm6NWQsvoI= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.28 h1:r+XwaCLpIvCKjBIYy/HVZujQS9tsz5ohHG3ZIe0wKoE= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.28/go.mod h1:3lwChorpIM/BhImY/hy+Z6jekmN92cXGPI1QJasVPYY= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.8/go.mod h1:ZIV8GYoC6WLBW5KGs+o4rsc65/ozd+eQ0L31XF5VDwk= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.21/go.mod h1:+Gxn8jYn5k9ebfHEqlhrMirFjSW0v0C9fI+KN5vk2kE= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.22 h1:7AwGYXDdqRQYsluvKFmWoqpcOQJ4bH634SkYf3FNj/A= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.22/go.mod h1:EqK7gVrIGAHyZItrD1D8B0ilgwMD1GiWAmbU4u/JHNk= github.com/aws/aws-sdk-go-v2/internal/ini v1.3.29 h1:J4xhFd6zHhdF9jPP0FQJ6WknzBboGMBNjKOv4iTuw4A= @@ -142,8 +139,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.22 h1:LjFQf8hFu github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.22/go.mod h1:xt0Au8yPIwYXf/GYPy/vl4K3CgwhfQMYbrH7DlUUIws= github.com/aws/aws-sdk-go-v2/service/kms v1.17.5 h1:DkubF+BSEy0uX59+pYySzWFReN3fCcXobIO8L5Phh24= github.com/aws/aws-sdk-go-v2/service/kms v1.17.5/go.mod h1:ubAtMGRUMVv5kX8lpbeDguxZ64pR4kXTGApY4sCM0io= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.2 h1:QDVKb2VpuwzIslzshumxksayV5GkpqT+rkVvdPVrA9E= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.2/go.mod h1:jAeo/PdIJZuDSwsvxJS94G4d6h8tStj7WXVuKwLHWU8= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.3 h1:Zod/h9QcDvbrrG3jjTUp4lctRb6Qg2nj7ARC/xMsUc4= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.3/go.mod h1:hqPcyOuLU6yWIbLy3qMnQnmidgKuIEwqIlW6+chYnog= github.com/aws/aws-sdk-go-v2/service/sso v1.12.1 h1:lQKN/LNa3qqu2cDOQZybP7oL4nMGGiFqob0jZJaR8/4= github.com/aws/aws-sdk-go-v2/service/sso v1.12.1/go.mod h1:IgV8l3sj22nQDd5qcAGY0WenwCzCphqdbFOpfktZPrI= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.1 h1:0bLhH6DRAqox+g0LatcjGKjjhU6Eudyys6HB6DJVPj8= From 00a4aa3294696a16eda7fc9a8de6501485f5400a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Feb 2023 08:38:42 +0100 Subject: [PATCH 1398/2916] chore(deps): Bump github.com/spf13/viper from 1.14.0 to 1.15.0 (#293) Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.14.0 to 1.15.0. - [Release notes](https://github.com/spf13/viper/releases) - [Commits](https://github.com/spf13/viper/compare/v1.14.0...v1.15.0) --- updated-dependencies: - dependency-name: github.com/spf13/viper dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 13 ++++++------- go.sum | 26 ++++++++++++-------------- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/go.mod b/go.mod index 0e761063f..728bec1e9 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ require ( github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.6.1 github.com/spf13/pflag v1.0.5 - github.com/spf13/viper v1.14.0 + github.com/spf13/viper v1.15.0 github.com/stretchr/testify v1.8.1 github.com/whilp/git-urls v1.0.0 github.com/xanzy/ssh-agent v0.3.3 @@ -74,7 +74,7 @@ require ( require ( cloud.google.com/go/compute v1.14.0 // indirect - cloud.google.com/go/compute/metadata v0.2.2 // indirect + cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/iam v0.8.0 // indirect cloud.google.com/go/kms v1.6.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.1 // indirect @@ -205,7 +205,6 @@ require ( github.com/oklog/run v1.1.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc2 // indirect - github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.0.6 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pierrec/lz4 v2.6.1+incompatible // indirect @@ -226,7 +225,7 @@ require ( github.com/spf13/afero v1.9.3 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/subosito/gotenv v1.4.1 // indirect + github.com/subosito/gotenv v1.4.2 // indirect github.com/urfave/cli v1.22.7 // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect @@ -243,10 +242,10 @@ require ( golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.4.0 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect - google.golang.org/api v0.105.0 // indirect + google.golang.org/api v0.107.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20221207170731-23e4bf6bdc37 // indirect - google.golang.org/grpc v1.51.0 // indirect + google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef // indirect + google.golang.org/grpc v1.52.0 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/go.sum b/go.sum index d98304d6d..4796239a1 100644 --- a/go.sum +++ b/go.sum @@ -29,8 +29,8 @@ cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4g cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/compute v1.14.0 h1:hfm2+FfxVmnRlh6LpB7cg1ZNU+5edAHmW679JePztk0= cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= -cloud.google.com/go/compute/metadata v0.2.2 h1:aWKAjYaBaOSrpKl57+jnS/3fJRQnxL7TvR/u1VVbt6k= -cloud.google.com/go/compute/metadata v0.2.2/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= @@ -684,8 +684,6 @@ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FI github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= -github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= @@ -786,8 +784,8 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= -github.com/spf13/viper v1.14.0 h1:Rg7d3Lo706X9tHsJMUjdiwMpHB7W8WnSVOssIY+JElU= -github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As= +github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU= +github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -806,8 +804,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= -github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= +github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/urfave/cli v1.22.7 h1:aXiFAgRugfJ27UFDsGJ9DB2FvTC73hlVXFSqq5bo9eU= github.com/urfave/cli v1.22.7/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= @@ -1180,8 +1178,8 @@ google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjR google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= -google.golang.org/api v0.105.0 h1:t6P9Jj+6XTn4U9I2wycQai6Q/Kz7iOT+QzjJ3G2V4x8= -google.golang.org/api v0.105.0/go.mod h1:qh7eD5FJks5+BcE+cjBIm6Gz8vioK7EHvnlniqXBnqI= +google.golang.org/api v0.107.0 h1:I2SlFjD8ZWabaIFOfeEDg3pf0BHJDh6iYQ1ic3Yu/UU= +google.golang.org/api v0.107.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1234,8 +1232,8 @@ google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20221207170731-23e4bf6bdc37 h1:jmIfw8+gSvXcZSgaFAGyInDXeWzUhvYH57G/5GKMn70= -google.golang.org/genproto v0.0.0-20221207170731-23e4bf6bdc37/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef h1:uQ2vjV/sHTsWSqdKeLqmwitzgvjMl7o4IdtHwUDXSJY= +google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1258,8 +1256,8 @@ google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= -google.golang.org/grpc v1.51.0 h1:E1eGv1FTqoLIdnBCZufiSHgKjlqG6fKFf6pPWtMTh8U= -google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= +google.golang.org/grpc v1.52.0 h1:kd48UiU7EHsV4rnLyOJRuP/Il/UHE7gdDAQ+SZI7nZk= +google.golang.org/grpc v1.52.0/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= From c07afbb42fd7daef7cb614a0a8a34521fe73a5b8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Feb 2023 08:39:11 +0100 Subject: [PATCH 1399/2916] chore(deps): Bump github.com/fluxcd/pkg/kustomize from 0.12.0 to 0.13.0 (#294) Bumps [github.com/fluxcd/pkg/kustomize](https://github.com/fluxcd/pkg) from 0.12.0 to 0.13.0. - [Release notes](https://github.com/fluxcd/pkg/releases) - [Commits](https://github.com/fluxcd/pkg/compare/oci/v0.12.0...oci/v0.13.0) --- updated-dependencies: - dependency-name: github.com/fluxcd/pkg/kustomize dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 728bec1e9..2ed371303 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/docker/distribution v2.8.1+incompatible // See https://github.com/fluxcd/pkg/issues/397, especially in regard to skeema/knownhosts breaking compatibility github.com/fluxcd/go-git/v5 v5.0.0-20221206140629-ec778c2c37df - github.com/fluxcd/pkg/kustomize v0.12.0 + github.com/fluxcd/pkg/kustomize v0.13.0 github.com/go-playground/validator/v10 v10.11.2 github.com/gobwas/glob v0.2.3 // indirect github.com/golang-jwt/jwt/v4 v4.4.3 @@ -64,7 +64,7 @@ require ( github.com/go-logr/logr v1.2.3 github.com/hashicorp/go-multierror v1.1.1 github.com/huandu/xstrings v1.4.0 - github.com/onsi/gomega v1.24.2 + github.com/onsi/gomega v1.26.0 github.com/otiai10/copy v1.9.0 go.mozilla.org/sops/v3 v3.7.4-0.20220901181616-9124783930b1 sigs.k8s.io/controller-runtime v0.14.1 @@ -128,7 +128,7 @@ require ( github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect github.com/fatih/color v1.13.0 // indirect - github.com/fluxcd/pkg/apis/kustomize v0.7.0 // indirect + github.com/fluxcd/pkg/apis/kustomize v0.8.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-git/gcfg v1.5.0 // indirect diff --git a/go.sum b/go.sum index 4796239a1..0b63075c3 100644 --- a/go.sum +++ b/go.sum @@ -259,10 +259,10 @@ github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBd github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= github.com/fluxcd/go-git/v5 v5.0.0-20221206140629-ec778c2c37df h1:2BHXJp1PwX7D47Q2oaKDekn+BZVZCmxeCWNi+FyownE= github.com/fluxcd/go-git/v5 v5.0.0-20221206140629-ec778c2c37df/go.mod h1:raWgfUV7lDQVXp4QXUaeNNJkRVKz97UQuF+0kdY7Vmo= -github.com/fluxcd/pkg/apis/kustomize v0.7.0 h1:X2htBmJ91nGYv4d93gin665MFWKNGiNwUiZ08/Zz0hY= -github.com/fluxcd/pkg/apis/kustomize v0.7.0/go.mod h1:Mu+KdktsEKWA4l/33CZdY5lB4hz51mqfcLzBZSwAqVg= -github.com/fluxcd/pkg/kustomize v0.12.0 h1:4MQdbP3M8NTIcr8TgNMxRCN+2xZoMWtCDDS3RiOT+8M= -github.com/fluxcd/pkg/kustomize v0.12.0/go.mod h1:awHID4OKe2/WAfTFg4u0fURXZPUkrIslSZNSPX9MEFQ= +github.com/fluxcd/pkg/apis/kustomize v0.8.0 h1:A6aLolxPV2Sll44SOHiX96lbXXmRZmS5BoEerkRHrfM= +github.com/fluxcd/pkg/apis/kustomize v0.8.0/go.mod h1:9DPEVSfVIkiC2H3Dk6Ght4YJkswhYIaufXla4tB5Y84= +github.com/fluxcd/pkg/kustomize v0.13.0 h1:oC50lpGdz/04aH4dPS/kRBjo+7PUx7BgGsJtSS0CmmE= +github.com/fluxcd/pkg/kustomize v0.13.0/go.mod h1:6vAmxEe/41jBEspGq4OZA/4WlnszPyavm74TGSEVpXg= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= @@ -665,8 +665,8 @@ github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo/v2 v2.7.0 h1:/XxtEV3I3Eif/HobnVx9YmJgk8ENdRsuUmM+fLCFNow= -github.com/onsi/gomega v1.24.2 h1:J/tulyYK6JwBldPViHJReihxxZ+22FHs0piGjQAvoUE= -github.com/onsi/gomega v1.24.2/go.mod h1:gs3J10IS7Z7r7eXRoNJIrNqU4ToQukCJhFtKrWgHWnk= +github.com/onsi/gomega v1.26.0 h1:03cDLK28U6hWvCAns6NeydX3zIm4SF3ci69ulidS32Q= +github.com/onsi/gomega v1.26.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034= From a3471516ed1812cd357e41ca3d35ac8e2e171dcc Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 7 Feb 2023 08:41:17 +0100 Subject: [PATCH 1400/2916] fix: Don't fail known_hosts verification when the key-type doesn't match (#287) Instead, ask for confirmation again. --- pkg/git/auth/ssh_known_hosts.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/pkg/git/auth/ssh_known_hosts.go b/pkg/git/auth/ssh_known_hosts.go index 9e8017040..ec375b1f2 100644 --- a/pkg/git/auth/ssh_known_hosts.go +++ b/pkg/git/auth/ssh_known_hosts.go @@ -74,9 +74,6 @@ func verifyHost(messageCallbacks messages.MessageCallbacks, host string, remote for _, f := range files { hostFound, err := goph.CheckKnownHost(host, remote, key, f) - if hostFound && err != nil { - return err - } if hostFound && err == nil { return nil } From 01a03d5086bd485686d0f0ebc071695a63e777ff Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 7 Feb 2023 16:30:12 +0100 Subject: [PATCH 1401/2916] feat: Deprecate fixed images from external targetConfig --- pkg/kluctl_project/targets.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/kluctl_project/targets.go b/pkg/kluctl_project/targets.go index baa2aa52f..e32f627c8 100644 --- a/pkg/kluctl_project/targets.go +++ b/pkg/kluctl_project/targets.go @@ -252,6 +252,9 @@ func (c *LoadedKluctlProject) buildDynamicTarget(targetInfo *dynamicTargetInfo) if targetConfig.Args != nil { target.Args.Merge(targetConfig.Args) } + if len(targetConfig.Images) != 0 { + status.Deprecation(c.ctx, "target-config-images", "specifying fixed images via external 'targetConfig' is deprecated and support for this will be removed in a future kluctl release.") + } // We prepend the dynamic images to ensure they get higher priority later target.Images = append(targetConfig.Images, target.Images...) From c257fb112e04e93e2b1c2fc8cbe6dbafcd1f2f2e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 7 Feb 2023 16:37:32 +0100 Subject: [PATCH 1402/2916] refactor: Load vars in NewDeploymentItem instead of at render time --- pkg/deployment/deployment_item.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 3e0f3ec56..6d02374cd 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -10,6 +10,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/vars" "github.com/kluctl/kluctl/v2/pkg/yaml" "io/fs" "k8s.io/apimachinery/pkg/runtime/schema" @@ -27,6 +28,7 @@ type DeploymentItem struct { Project *DeploymentProject Inclusion *utils.Inclusion Config *types.DeploymentItemConfig + VarsCtx *vars.VarsCtx dir *string index int @@ -51,6 +53,7 @@ func NewDeploymentItem(ctx SharedContext, project *DeploymentProject, collection Project: project, Inclusion: collection.Inclusion, Config: config, + VarsCtx: project.VarsCtx.Copy(), dir: dir, index: index, } @@ -81,6 +84,12 @@ func NewDeploymentItem(ctx SharedContext, project *DeploymentProject, collection di.RenderedDir = filepath.Join(di.RenderedSourceRootDir, di.RelRenderedDir) di.renderedYamlPath = filepath.Join(di.RenderedDir, ".rendered.yml") } + + err = di.Project.loadVarsList(di.VarsCtx, di.Config.Vars) + if err != nil { + return nil, err + } + return di, nil } @@ -115,13 +124,6 @@ func (di *DeploymentItem) render(forSeal bool) error { return err } - varsCtx := di.Project.VarsCtx.Copy() - - err = di.Project.loadVarsList(varsCtx, di.Config.Vars) - if err != nil { - return err - } - var excludePatterns []string excludePatterns = append(excludePatterns, "**/.git") @@ -154,7 +156,7 @@ func (di *DeploymentItem) render(forSeal bool) error { // also add deployment item dir to search dirs searchDirs = append([]string{*di.dir}, searchDirs...) - return varsCtx.RenderDirectory( + return di.VarsCtx.RenderDirectory( filepath.Join(di.Project.source.dir, di.RelToSourceItemDir), di.RenderedDir, excludePatterns, From 4829488670c5b7e40f1988e1221ca39221c66bbb Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 7 Feb 2023 17:25:11 +0100 Subject: [PATCH 1403/2916] fix: Load deprecated args from deployment.yaml after .kluctl.yaml --- pkg/kluctl_project/target_context.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index f1619b2b9..1ccf21812 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -183,6 +183,11 @@ func (p *LoadedKluctlProject) buildVars(target *types.Target, externalArgs *uo.U } allArgs.Merge(externalArgs) + err = deployment.LoadDefaultArgs(p.Config.Args, allArgs) + if err != nil { + return nil, err + } + deprecatedArgs, err := deployment.LoadDeprecatedDeploymentArgs(p.ctx, p.ProjectDir, varsCtx, allArgs) if err != nil { return nil, err @@ -192,11 +197,6 @@ func (p *LoadedKluctlProject) buildVars(target *types.Target, externalArgs *uo.U return nil, fmt.Errorf("mixing deprecated 'args' from deployment.yaml and .kluctl.yaml is not allowed") } - err = deployment.LoadDefaultArgs(p.Config.Args, allArgs) - if err != nil { - return nil, err - } - varsCtx.UpdateChild("args", allArgs) return varsCtx, nil From 405b0b7c39bf3c57c5e62bf4d68e7376c37d4f9b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 7 Feb 2023 17:31:14 +0100 Subject: [PATCH 1404/2916] feat: Support fixed images configuration from vars --- pkg/deployment/deployment_item.go | 2 +- pkg/deployment/images.go | 77 +++++++++++++++++++++++-------- 2 files changed, 59 insertions(+), 20 deletions(-) diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 6d02374cd..05678dd8c 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -592,7 +592,7 @@ func (di *DeploymentItem) postprocessObjects(images *Images) error { } // Resolve image placeholders - err := images.ResolvePlaceholders(di.ctx.K, o, di.RelRenderedDir, di.Tags.ListKeys()) + err := images.ResolvePlaceholders(di.ctx.K, o, di.RelRenderedDir, di.Tags.ListKeys(), di.VarsCtx.Vars) if err != nil { errs = multierror.Append(errs, err) } diff --git a/pkg/deployment/images.go b/pkg/deployment/images.go index 8dc9c5f77..24964fe94 100644 --- a/pkg/deployment/images.go +++ b/pkg/deployment/images.go @@ -66,24 +66,60 @@ func (images *Images) SeenImages(simple bool) []types.FixedImage { return ret } -func (images *Images) GetFixedImage(image string, namespace string, deployment string, container string) *string { - for i := len(images.fixedImages) - 1; i >= 0; i-- { - fi := &images.fixedImages[i] - if fi.Image != image { - continue - } - if fi.Namespace != nil && namespace != *fi.Namespace { - continue - } - if fi.Deployment != nil && deployment != *fi.Deployment { - continue +func (images *Images) parseFixedImagesFromVars(vars *uo.UnstructuredObject) ([]types.FixedImage, error) { + fisU, _, err := vars.GetNestedObjectList("images") + if err != nil { + return nil, fmt.Errorf("failed to parse fixed images from vars: %w", err) + } + fis := make([]types.FixedImage, 0, len(fisU)) + for i, u := range fisU { + var fi types.FixedImage + err = u.ToStruct(&fi) + if err != nil { + return nil, fmt.Errorf("failed to parse fixed image from vars at index %d: %w", i, err) } - if fi.Container != nil && container != *fi.Container { - continue + fis = append(fis, fi) + } + return fis, nil +} + +func (images *Images) getFixedImage(image string, namespace string, deployment string, container string, vars *uo.UnstructuredObject) (*string, error) { + cmpList := func(fis []types.FixedImage) *string { + for i := len(fis) - 1; i >= 0; i-- { + fi := fis[i] + if fi.Image != image { + continue + } + if fi.Namespace != nil && namespace != *fi.Namespace { + continue + } + if fi.Deployment != nil && deployment != *fi.Deployment { + continue + } + if fi.Container != nil && container != *fi.Container { + continue + } + + return &fi.ResultImage } - return &fi.ResultImage + return nil } - return nil + + fisFromVars, err := images.parseFixedImagesFromVars(vars) + if err != nil { + return nil, err + } + + fi := cmpList(images.fixedImages) + if fi != nil { + return fi, nil + } + fi = cmpList(fisFromVars) + if fi != nil { + return fi, nil + } + + return nil, nil } func (images *Images) GetLatestImageFromRegistry(image string, latestVersion string) (*string, error) { @@ -204,7 +240,7 @@ func (images *Images) FindPlaceholders(o *uo.UnstructuredObject) ([]placeHolder, return ret, nil } -func (images *Images) ResolvePlaceholders(k *k8s.K8sCluster, o *uo.UnstructuredObject, deploymentDir string, tags []string) error { +func (images *Images) ResolvePlaceholders(k *k8s.K8sCluster, o *uo.UnstructuredObject, deploymentDir string, tags []string, vars *uo.UnstructuredObject) error { placeholders, err := images.FindPlaceholders(o) if err != nil { return err @@ -240,7 +276,7 @@ func (images *Images) ResolvePlaceholders(k *k8s.K8sCluster, o *uo.UnstructuredO } } - resultImage, err := images.resolveImage(ph, ref, deployment, deployed, deploymentDir, tags) + resultImage, err := images.resolveImage(ph, ref, deployment, deployed, deploymentDir, tags, vars) if err != nil { return err } @@ -257,8 +293,11 @@ func (images *Images) ResolvePlaceholders(k *k8s.K8sCluster, o *uo.UnstructuredO return nil } -func (images *Images) resolveImage(ph placeHolder, ref k8s2.ObjectRef, deployment string, deployed *string, deploymentDir string, tags []string) (*string, error) { - fixed := images.GetFixedImage(ph.Image, ref.Namespace, deployment, ph.Container) +func (images *Images) resolveImage(ph placeHolder, ref k8s2.ObjectRef, deployment string, deployed *string, deploymentDir string, tags []string, vars *uo.UnstructuredObject) (*string, error) { + fixed, err := images.getFixedImage(ph.Image, ref.Namespace, deployment, ph.Container, vars) + if err != nil { + return nil, err + } registry, err := images.GetLatestImageFromRegistry(ph.Image, ph.LatestVersion) if err != nil { From 9ac3b0fc01706159257c8c393803ca62107916d4 Mon Sep 17 00:00:00 2001 From: Petr Michalec Date: Tue, 7 Feb 2023 20:29:30 +0100 Subject: [PATCH 1405/2916] improve subDir path validation (#279) Closes: https://github.com/kluctl/kluctl/issues/269 --- pkg/types/git_project.go | 27 ++++++++++++++++++++++++--- pkg/types/validators_test.go | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 pkg/types/validators_test.go diff --git a/pkg/types/git_project.go b/pkg/types/git_project.go index dd8c7bae6..5183cf964 100644 --- a/pkg/types/git_project.go +++ b/pkg/types/git_project.go @@ -1,12 +1,18 @@ package types import ( + "fmt" + "regexp" + "strings" + "github.com/go-playground/validator/v10" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/yaml" - "strings" ) +// gitDirPatternNeg defines forbiden characters on git directory path/subDir +var gitDirPatternNeg = regexp.MustCompile(`[\\\/:\*?"<>|[:cntrl:]\0^]`) + type GitProject struct { Url git_url.GitUrl `yaml:"url" validate:"required"` Ref string `yaml:"ref,omitempty"` @@ -22,10 +28,25 @@ func (gp *GitProject) UnmarshalYAML(unmarshal func(interface{}) error) error { return unmarshal((*raw)(gp)) } +// invalidDirName evaluate directory name against forbidden characters +func invalidDirName(dirName string) bool { + return gitDirPatternNeg.MatchString(dirName) +} + +// validateGitSubDir evaluate syntax for subdirectory path +func validateGitSubDir(path string) bool { + for _, dirName := range strings.Split(path, "/") { + if invalidDirName(dirName) { + return false + } + } + return true +} + func ValidateGitProject(sl validator.StructLevel) { gp := sl.Current().Interface().(GitProject) - if strings.Index(gp.SubDir, "/") != -1 { - sl.ReportError(gp.SubDir, "subDir", "SubDir", "/ is not allowed in subDir", "") + if !validateGitSubDir(gp.SubDir) { + sl.ReportError(gp.SubDir, "subDir", "SubDir", fmt.Sprintf("'%s' is not valid git subdirectory path", gp.SubDir), "") } } diff --git a/pkg/types/validators_test.go b/pkg/types/validators_test.go new file mode 100644 index 000000000..e7c2067f3 --- /dev/null +++ b/pkg/types/validators_test.go @@ -0,0 +1,35 @@ +package types + +import ( + "testing" + + validator "github.com/go-playground/validator/v10" + "github.com/stretchr/testify/assert" +) + +func TestValidateGitProjectSubDir(t *testing.T) { + + var subDirItems = []struct { + have string + want bool + }{ + {"subDir", true}, + {"subDir/../subDir", true}, + {"subDir?", false}, // wrong characters + {"subDir*/Another", false}, // wrong characters + {"subDir/../sub?D|ir", false}, // wrong characters + } + + validate := validator.New() + validate.RegisterStructValidation(ValidateGitProject, GitProject{}) + + for _, item := range subDirItems { + gp := GitProject{SubDir: item.have} + err := validate.Struct(gp) + if item.want { + assert.Nil(t, err) + } else { + assert.Error(t, err) + } + } +} From dd4d92ea48b55ddc81e82c438fa2a036755ad850 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Feb 2023 09:00:47 +0100 Subject: [PATCH 1406/2916] chore(deps): Bump github.com/ohler55/ojg from 1.17.3 to 1.17.4 (#300) Bumps [github.com/ohler55/ojg](https://github.com/ohler55/ojg) from 1.17.3 to 1.17.4. - [Release notes](https://github.com/ohler55/ojg/releases) - [Changelog](https://github.com/ohler55/ojg/blob/develop/CHANGELOG.md) - [Commits](https://github.com/ohler55/ojg/compare/v1.17.3...v1.17.4) --- updated-dependencies: - dependency-name: github.com/ohler55/ojg dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 36e68afe3..953b86b30 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( github.com/mattn/go-isatty v0.0.17 github.com/mattn/go-runewidth v0.0.14 github.com/mitchellh/reflectwalk v1.0.2 - github.com/ohler55/ojg v1.17.3 + github.com/ohler55/ojg v1.17.4 github.com/pkg/errors v0.9.1 github.com/r3labs/diff/v2 v2.15.1 github.com/rogpeppe/go-internal v1.9.0 diff --git a/go.sum b/go.sum index 410b73528..c8a2b1211 100644 --- a/go.sum +++ b/go.sum @@ -661,8 +661,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/ohler55/ojg v1.17.3 h1:NsyfSN+GScWFzXZCVuVimK0xOqUEqTSwuZ+J1WA50nw= -github.com/ohler55/ojg v1.17.3/go.mod h1:7Ghirupn8NC8hSSDpI0gcjorPxj+vSVIONDWfliHR1k= +github.com/ohler55/ojg v1.17.4 h1:6Ss87DyAZHU0ODZu6Cmuahj5UiVaRD1n8C4KNm0qMYg= +github.com/ohler55/ojg v1.17.4/go.mod h1:7Ghirupn8NC8hSSDpI0gcjorPxj+vSVIONDWfliHR1k= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= From e99b006260cc60ce2b403e3530f4aa25a18086db Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Feb 2023 09:01:04 +0100 Subject: [PATCH 1407/2916] chore(deps): Bump k8s.io/klog/v2 from 2.80.1 to 2.90.0 (#299) Bumps [k8s.io/klog/v2](https://github.com/kubernetes/klog) from 2.80.1 to 2.90.0. - [Release notes](https://github.com/kubernetes/klog/releases) - [Changelog](https://github.com/kubernetes/klog/blob/main/RELEASE.md) - [Commits](https://github.com/kubernetes/klog/compare/v2.80.1...v2.90.0) --- updated-dependencies: - dependency-name: k8s.io/klog/v2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 953b86b30..d84385a11 100644 --- a/go.mod +++ b/go.mod @@ -51,7 +51,7 @@ require ( k8s.io/apiextensions-apiserver v0.26.1 k8s.io/apimachinery v0.26.1 k8s.io/client-go v0.26.1 - k8s.io/klog/v2 v2.80.1 + k8s.io/klog/v2 v2.90.0 sigs.k8s.io/kustomize/kyaml v0.13.9 sigs.k8s.io/structured-merge-diff/v4 v4.2.3 ) diff --git a/go.sum b/go.sum index c8a2b1211..bc2ea3b3d 100644 --- a/go.sum +++ b/go.sum @@ -1333,8 +1333,8 @@ k8s.io/client-go v0.26.1 h1:87CXzYJnAMGaa/IDDfRdhTzxk/wzGZ+/HUQpqgVSZXU= k8s.io/client-go v0.26.1/go.mod h1:IWNSglg+rQ3OcvDkhY6+QLeasV4OYHDjdqeWkDQZwGE= k8s.io/component-base v0.26.1 h1:4ahudpeQXHZL5kko+iDHqLj/FSGAEUnSVO0EBbgDd+4= k8s.io/component-base v0.26.1/go.mod h1:VHrLR0b58oC035w6YQiBSbtsf0ThuSwXP+p5dD/kAWU= -k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= -k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.90.0 h1:VkTxIV/FjRXn1fgNNcKGM8cfmL1Z33ZjXRTVxKCoF5M= +k8s.io/klog/v2 v2.90.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20221207184640-f3cff1453715 h1:tBEbstoM+K0FiBV5KGAKQ0kuvf54v/hwpldiJt69w1s= k8s.io/kube-openapi v0.0.0-20221207184640-f3cff1453715/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= k8s.io/kubectl v0.26.0 h1:xmrzoKR9CyNdzxBmXV7jW9Ln8WMrwRK6hGbbf69o4T0= From 8082e89fcc666ae25f4699293f03b114667b464b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Feb 2023 09:01:30 +0100 Subject: [PATCH 1408/2916] chore(deps): Bump golang.org/x/sys from 0.4.0 to 0.5.0 (#298) Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.4.0 to 0.5.0. - [Release notes](https://github.com/golang/sys/releases) - [Commits](https://github.com/golang/sys/compare/v0.4.0...v0.5.0) --- updated-dependencies: - dependency-name: golang.org/x/sys dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index d84385a11..9f80ab451 100644 --- a/go.mod +++ b/go.mod @@ -41,7 +41,7 @@ require ( golang.org/x/crypto v0.5.0 golang.org/x/net v0.5.0 golang.org/x/sync v0.1.0 - golang.org/x/sys v0.4.0 + golang.org/x/sys v0.5.0 golang.org/x/term v0.4.0 golang.org/x/text v0.6.0 gopkg.in/yaml.v2 v2.4.0 diff --git a/go.sum b/go.sum index bc2ea3b3d..09f9b9014 100644 --- a/go.sum +++ b/go.sum @@ -1070,8 +1070,8 @@ golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= -golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= From 6ca2413a25b76826474427be77e654b3c6bf9789 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Feb 2023 09:09:57 +0100 Subject: [PATCH 1409/2916] chore(deps): Bump golang.org/x/term from 0.4.0 to 0.5.0 (#297) Bumps [golang.org/x/term](https://github.com/golang/term) from 0.4.0 to 0.5.0. - [Release notes](https://github.com/golang/term/releases) - [Commits](https://github.com/golang/term/compare/v0.4.0...v0.5.0) --- updated-dependencies: - dependency-name: golang.org/x/term dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9f80ab451..0a9e7de6f 100644 --- a/go.mod +++ b/go.mod @@ -42,7 +42,7 @@ require ( golang.org/x/net v0.5.0 golang.org/x/sync v0.1.0 golang.org/x/sys v0.5.0 - golang.org/x/term v0.4.0 + golang.org/x/term v0.5.0 golang.org/x/text v0.6.0 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 diff --git a/go.sum b/go.sum index 09f9b9014..a8302d9d2 100644 --- a/go.sum +++ b/go.sum @@ -1078,8 +1078,8 @@ golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg= -golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= +golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From 9c19fd968633ab2372304c11207d0defe74bffe4 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 7 Feb 2023 17:33:08 +0100 Subject: [PATCH 1410/2916] feat: Deprecate querying latest image tag from registries --- cmd/kluctl/args/images.go | 2 +- cmd/kluctl/commands/utils.go | 6 ++++++ docs/reference/commands/common-arguments.md | 5 ++++- pkg/deployment/deployment_item.go | 2 +- pkg/deployment/images.go | 17 ++++++++++++----- pkg/kluctl_jinja2/ext/images_ext.py | 4 ++++ 6 files changed, 28 insertions(+), 8 deletions(-) diff --git a/cmd/kluctl/args/images.go b/cmd/kluctl/args/images.go index 13aaf244d..7d0364722 100644 --- a/cmd/kluctl/args/images.go +++ b/cmd/kluctl/args/images.go @@ -11,7 +11,7 @@ type ImageFlags struct { FixedImage []string `group:"images" short:"F" help:"Pin an image to a given version. Expects '--fixed-image=image<:namespace:deployment:container>=result'"` FixedImagesFile existingFileType `group:"images" help:"Use .yaml file to pin image versions. See output of list-images sub-command or read the documentation for details about the output format" exts:"yml,yaml"` UpdateImages bool `group:"images" short:"u" help:"This causes kluctl to prefer the latest image found in registries, based on the 'latest_image' filters provided to 'images.get_image(...)' calls. Use this flag if you want to update to the latest versions/tags of all images. '-u' takes precedence over '--fixed-image/--fixed-images-file', meaning that the latest images are used even if an older image is given via fixed images."` - OfflineImages bool `group:"images" help:"Omit contacting image registries and do not query for latest image tags."` + OfflineImages bool `group:"images" help:"DEPRECATED: Omit contacting image registries and do not query for latest image tags. This flag is by default set to true. At the same time, the whole requesting of image tags from registries functionality is deprecated and will be removed from kluctl in a future release." default:"true"` } func (args *ImageFlags) LoadFixedImagesFromArgs() ([]types.FixedImage, error) { diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 2a610fee0..909a14c3b 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -130,6 +130,12 @@ func withProjectTargetCommandContext(ctx context.Context, args projectTargetComm if err != nil { return fmt.Errorf("failed to parse registry auth from environment: %w", err) } + if args.imageFlags.UpdateImages { + status.Deprecation(ctx, "update-images", "--update-images is deprecated and will be removed in a future kluctl release.") + } + if !args.imageFlags.OfflineImages { + status.Deprecation(ctx, "online-images", "--offline-images=false is deprecated and will be removed in a future kluctl release.") + } images, err := deployment.NewImages(rh, args.imageFlags.UpdateImages, args.imageFlags.OfflineImages || args.forCompletion) if err != nil { return err diff --git a/docs/reference/commands/common-arguments.md b/docs/reference/commands/common-arguments.md index c08d93b4b..dbdf48621 100644 --- a/docs/reference/commands/common-arguments.md +++ b/docs/reference/commands/common-arguments.md @@ -98,7 +98,10 @@ Image arguments: '--fixed-image=image<:namespace:deployment:container>=result' --fixed-images-file existingfile Use .yaml file to pin image versions. See output of list-images sub-command or read the documentation for details about the output format - --offline-images Omit contacting image registries and do not query for latest image tags. + --offline-images DEPRECATED: Omit contacting image registries and do not query for latest + image tags. This flag is by default set to true. At the same time, the + whole requesting of image tags from registries functionality is + deprecated and will be removed from kluctl in a future release. (default true) -u, --update-images This causes kluctl to prefer the latest image found in registries, based on the 'latest_image' filters provided to 'images.get_image(...)' calls. Use this flag if you want to update to the latest versions/tags of all diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 05678dd8c..6d059e96c 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -592,7 +592,7 @@ func (di *DeploymentItem) postprocessObjects(images *Images) error { } // Resolve image placeholders - err := images.ResolvePlaceholders(di.ctx.K, o, di.RelRenderedDir, di.Tags.ListKeys(), di.VarsCtx.Vars) + err := images.ResolvePlaceholders(di.ctx.Ctx, di.ctx.K, o, di.RelRenderedDir, di.Tags.ListKeys(), di.VarsCtx.Vars) if err != nil { errs = multierror.Append(errs, err) } diff --git a/pkg/deployment/images.go b/pkg/deployment/images.go index 24964fe94..076350faf 100644 --- a/pkg/deployment/images.go +++ b/pkg/deployment/images.go @@ -2,10 +2,12 @@ package deployment import ( "bytes" + "context" "encoding/base64" "fmt" "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/registries" + "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/utils" @@ -158,8 +160,9 @@ const beginPlaceholder = "XXXXXbegin_get_image_" const endPlaceholder = "_end_get_imageXXXXX" type placeHolder struct { - Image string `yaml:"image"` - LatestVersion string `yaml:"latestVersion"` + Image string `yaml:"image"` + LatestVersion string `yaml:"latestVersion"` + HasLatestVersion bool `yaml:"hasLatestVersion"` Container string @@ -240,7 +243,7 @@ func (images *Images) FindPlaceholders(o *uo.UnstructuredObject) ([]placeHolder, return ret, nil } -func (images *Images) ResolvePlaceholders(k *k8s.K8sCluster, o *uo.UnstructuredObject, deploymentDir string, tags []string, vars *uo.UnstructuredObject) error { +func (images *Images) ResolvePlaceholders(ctx context.Context, k *k8s.K8sCluster, o *uo.UnstructuredObject, deploymentDir string, tags []string, vars *uo.UnstructuredObject) error { placeholders, err := images.FindPlaceholders(o) if err != nil { return err @@ -276,7 +279,7 @@ func (images *Images) ResolvePlaceholders(k *k8s.K8sCluster, o *uo.UnstructuredO } } - resultImage, err := images.resolveImage(ph, ref, deployment, deployed, deploymentDir, tags, vars) + resultImage, err := images.resolveImage(ctx, ph, ref, deployment, deployed, deploymentDir, tags, vars) if err != nil { return err } @@ -293,12 +296,16 @@ func (images *Images) ResolvePlaceholders(k *k8s.K8sCluster, o *uo.UnstructuredO return nil } -func (images *Images) resolveImage(ph placeHolder, ref k8s2.ObjectRef, deployment string, deployed *string, deploymentDir string, tags []string, vars *uo.UnstructuredObject) (*string, error) { +func (images *Images) resolveImage(ctx context.Context, ph placeHolder, ref k8s2.ObjectRef, deployment string, deployed *string, deploymentDir string, tags []string, vars *uo.UnstructuredObject) (*string, error) { fixed, err := images.getFixedImage(ph.Image, ref.Namespace, deployment, ph.Container, vars) if err != nil { return nil, err } + if ph.HasLatestVersion { + status.Deprecation(ctx, "latest-version-filter", "latest_version is deprecated when using images.get_image()") + } + registry, err := images.GetLatestImageFromRegistry(ph.Image, ph.LatestVersion) if err != nil { return nil, err diff --git a/pkg/kluctl_jinja2/ext/images_ext.py b/pkg/kluctl_jinja2/ext/images_ext.py index 4c363b194..ba523653a 100644 --- a/pkg/kluctl_jinja2/ext/images_ext.py +++ b/pkg/kluctl_jinja2/ext/images_ext.py @@ -12,11 +12,15 @@ def __init__(self, environment): environment.globals.update(self.build_images_vars()) def get_image_wrapper(self, image, latest_version=None): + has_latest_version = False if latest_version is None: latest_version = "semver()" + else: + has_latest_version = True placeholder = { "image": image, "latestVersion": str(latest_version), + "hasLatestVersion": has_latest_version, } j = json.dumps(placeholder) j = base64.b64encode(j.encode("utf8")).decode("utf8") From 7417b33c01dde40f8255aa1b5ea30a2af189125e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 8 Feb 2023 09:11:09 +0100 Subject: [PATCH 1411/2916] feat: Deprecate check-image-updates command --- cmd/kluctl/commands/cmd_check_image_updates.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/kluctl/commands/cmd_check_image_updates.go b/cmd/kluctl/commands/cmd_check_image_updates.go index 64471dd3b..7e7966ed0 100644 --- a/cmd/kluctl/commands/cmd_check_image_updates.go +++ b/cmd/kluctl/commands/cmd_check_image_updates.go @@ -19,10 +19,11 @@ type checkImageUpdatesCmd struct { } func (cmd *checkImageUpdatesCmd) Help() string { - return `This is based on a best effort approach and might give many false-positives.` + return `DEPRECATED: This is based on a best effort approach and might give many false-positives.` } func (cmd *checkImageUpdatesCmd) Run(ctx context.Context) error { + status.Deprecation(ctx, "check-image-updates", "check-image-updates is deprecated and will be removed in a future kluctl release.") ptArgs := projectTargetCommandArgs{ projectFlags: cmd.ProjectFlags, targetFlags: cmd.TargetFlags, From 8aa562d77d8abb2bb4e97bc94356e39592db2516 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 8 Feb 2023 09:11:59 +0100 Subject: [PATCH 1412/2916] fix: Show deprecation message for sealedSecrets config --- pkg/deployment/deployment_project.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go index 9f3909ab7..7ee143f3d 100644 --- a/pkg/deployment/deployment_project.go +++ b/pkg/deployment/deployment_project.go @@ -3,6 +3,7 @@ package deployment import ( "fmt" securejoin "github.com/cyphar/filepath-securejoin" + "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" @@ -60,6 +61,10 @@ func NewDeploymentProject(ctx SharedContext, varsCtx *vars.VarsCtx, source Sourc return nil, fmt.Errorf("failed to load includes for %s: %w", dir, err) } + if dp.Config.SealedSecrets != nil { + status.Deprecation(ctx.Ctx, "sealed-secrets-config", "'sealedSecrets' is deprecated in deployment projects. Support for it will be removed in a future kluctl release.") + } + return dp, nil } From a48e3a3e877e7f503a1164a13243203ae60211c0 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 8 Feb 2023 09:56:29 +0100 Subject: [PATCH 1413/2916] docs: Update docs in regard to images.get_image changes --- docs/reference/deployments/images.md | 127 +++++++----------- .../kluctl-project/targets/README.md | 2 +- .../templating/predefined-variables.md | 3 - 3 files changed, 50 insertions(+), 82 deletions(-) diff --git a/docs/reference/deployments/images.md b/docs/reference/deployments/images.md index b6270cf7d..0328b5403 100644 --- a/docs/reference/deployments/images.md +++ b/docs/reference/deployments/images.md @@ -13,54 +13,26 @@ description: > There are usually 2 different scenarios where Container Images need to be specified: 1. When deploying third party applications like nginx, redis, ... (e.g. via the [Helm integration](./helm.md)).
- * In this case, image versions/tags rarely change, and if they do, this is an explicit change to the deployment. -1. When deploying your own applications.
- * In this case, image versions/tags might change very rapidly, sometimes multiple times per hour. It would be too much - effort and overhead when this would be managed explicitly via your deployment. Even with Jinja2 templating, this - would be hard to maintain. + * In this case, image versions/tags rarely change, and if they do, this is an explicit change to the deployment. This + means it's fine to have the image versions/tags directly in the deployment manifests. +2. When deploying your own applications.
+ * In this case, image versions/tags might change very rapidly, sometimes multiple times per hour. Having these + versions/tags directly in the deployment manifests can easily lead to commit spam and hard to manage + multi-environment deployments. kluctl offers a better solution for the second case. -## Dynamic versions/tags - -kluctl is able to ask the used container registry for a list of tags/versions available for an image. It then can -sort the list of images via a configurable order and then use the latest image for your deployment. - -It however only does this when the involved resource (e.g. a `Deployment` or `StatefulSet`) is not yet deployed. In case -it is already deployed, the already deployed image will be reused to avoid undesired re-deployment/re-starting of -otherwise unchanged resources. - ## images.get_image() This is solved via a templating function that is available in all templates/resources. The function is part of the global `images` object and expects the following arguments: -`images.get_image(image, latest_version)` +`images.get_image(image)` * image - * The image location, excluding the tag. Please see [supported image registries](#supported-image-registries-and-authentication) to - understand which registries are supported.` -* latest_version - * Configures how tags/versions are sorted and thus how the latest image is determined. Can be: - * `version.semver()`
- Filters and sorts by loose semantic versioning. Versions must start with a number. It allows unlimited - `.` inside the version. It treats versions with a suffix as less then versions without a suffix - (e.g. 1.0-rc1 < 1.0). Two versions which only differ by suffix are sorted semantically. - * `version.prefix(prefix)`
- Only allows tags with the given prefix and then applies the same logic as images.semver() to whatever - follows right after the prefix. You can override the handling of the right part by providing `suffix=xxx`, - while `xxx` is another version filter, e.g. `version.prefix("master-", suffix=version.number()) - * `version.number()`
- Only allows plain numbers as version numbers sorts them accordingly. - * `version.regex(regex)`
- Only allows versions/tags that match the given regex. Sorting is done the same way as in version.semver(), - except that versions do not necessarily need to start with a number. - -The mentioned version filters must be specified as strings. For example, - -`images.get_version("my-image", "prefix('master-', suffix=number())")`. - -If no version_filter is specified, then it defaults to `"semver()"`. + * The image name/repository. It is looked up the list of fixed images. + +The function will lookup the given image in the list of fixed images and return the last match. Example deployment: @@ -77,59 +49,58 @@ spec: image: "{{ images.get_image('registry.gitlab.com/my-group/my-project') }}" ``` -## Always using the latest images -If you want to use the latest image no matter if an older version is already deployed, use the `-u` flag to your -`deploy`, `diff` or `list-images` commands. +## Fixed images -You can restrict updating to individual images by using `-I/--include-tag`. This is useful when using CI/CD for example, -where you often want to perform a deployment that only updates a single application/service from your large deployment. +Fixed images can be configured multiple methods: +1. Command line argument `--fixed-image` +2. Command line argument `--fixed-images-file` +3. Target definition +4. Global 'images' variable -## Fixed images via CLI -The described `images.get_image` logic however leads to a loosely defined state on your target cluster/environment. This -might be fine in a CI/CD environment, but might be undesired when deploying to production. In that case, it might be -desirable to explicitly define which versions need to be deployed. +## Command line argument `--fixed-image` -To achieve this, you can use the `-F FIXED_IMAGE` [argument](../commands/common-arguments.md#image-arguments). -`FIXED_IMAGE` must be in the form of `-F image<:namespace:deployment:container>=result`. For example, to pin the image -`registry.gitlab.com/my-group/my-project` to the tag `1.1.2` you'd have to specify -`-F registry.gitlab.com/my-group/my-project=registry.gitlab.com/my-group/my-project:1.1.2`. +You can pass fixed images configuration via the `--fixed-image` [argument](../commands/common-arguments.md#image-arguments). +Due to [environment variables support](../commands/environment-variables.md) in the CLI, you can also use the +environment variable `KLUCTL_FIXED_IMAGE_XXX` to configure fixed images. -## Fixed images via a yaml file +The format of the `--fixed-image` argument is `--fixed-image image<:namespace:deployment:container>=result`. The simplest +example is `--fixed-image registry.gitlab.com/my-group/my-project=registry.gitlab.com/my-group/my-project:1.1.2`. -As an alternative to specifying each fixed image via CLI (`--fixed-images-file=`), you can also specify a single -yaml file via CLI which then contains a list of entries that define image/deployment -> imageResult mappings. +## Command line argument `--fixed-images-file` -An example fixed-images files looks like this: +You can also configure fixed images via a yaml file by using `--fixed-images-file /path/to/fixed-images.yaml`. +file: ```yaml images: - image: registry.gitlab.com/my-group/my-project - resultImage: registry.gitlab.com/my-group/my-project:1.1.0 - - image: registry.gitlab.com/my-group/my-project2 - resultImage: registry.gitlab.com/my-group/my-project2:2.0.0 - - deployment: StatefulSet/my-sts - resultImage: registry.gitlab.com/my-group/my-project3:1.0.0 + resultImage: registry.gitlab.com/my-group/my-project:1.1.2 +``` + +The file must contain a single root list named `images` with each entry having the following form: + +```yaml +images: + - image: + resultImage: + # optional fields + namespace: + deployment: / + container: ``` -You can also take an existing deployment and export the already deployed image versions into a fixed-images file by -using the `list-images` command. It will produce a compatible fixed-images file based on the calls to -`images.get_image` if a deployment would be performed with the given arguments. The result of that call is quite -expressive, as it contains all the information gathered while images were collected. Use `--simple` to only return -a list with image -> resultImage mappings. +`image` and `resultImage` are required. All the other fields are optional and allow to specify in detail for which +object the fixed is specified. + +## Target definition -## Supported image registries and authentication -All [v2 API](https://docs.docker.com/registry/spec/api/) based image registries are supported, including the Docker Hub, -Gitlab, and many more. Private registries will need credentials to be setup correctly. This can be done by locally -logging in via `docker login ` or by specifying the following environment variables: +The [target](../kluctl-project/targets/README.md#targets) definition can optionally specify an `images` field that can +contain the same fixed images configuration as found in the `--fixed-images-file` file. -Simply set the following environment variables to pass credentials to you private repository: -1. KLUCTL_REGISTRY_HOST=registry.example.com -2. KLUCTL_REGISTRY_USERNAME=username -3. KLUCTL_REGISTRY_PASSWORD=password +## Global 'images' variable -You can also pass credentials for more registries by adding an index to the environment variables, -e.g. "KLUCTL_REGISTRY_1_HOST=registry.gitlab.com" +You can also define a global variable named `images` via one of the [variable sources](../templating/variable-sources.md). +This variable must be a list of the same format as the images list in the `--fixed-images-file` file. -In case your registry uses self-signed TLS certificates, it is currently required to disable TLS verification for these. -You can do this via `KLUCTL_REGISTRY_TLSVERIFY=1`/`KLUCTL_REGISTRY__TLSVERIFY=1` for the corresponding -`KLUCTL_REGISTRY_HOST`/`KLUCTL_REGISTRY__HOST` or by globally disabling it via `KLUCTL_REGISTRY_DEFAULT_TLSVERIFY=1`. +This option allows to externalize fixed images configuration, meaning that you can maintain image versions outside +the deployment project, e.g. in another [Git repository](../templating/variable-sources.md#git). diff --git a/docs/reference/kluctl-project/targets/README.md b/docs/reference/kluctl-project/targets/README.md index 74673d86d..4dc644fc1 100644 --- a/docs/reference/kluctl-project/targets/README.md +++ b/docs/reference/kluctl-project/targets/README.md @@ -54,7 +54,7 @@ have higher priority. ## images This field specifies a list of fixed images to be used by [`images.get_image(...)`](../../deployments/images.md#imagesget_image). -The format is identical to the [fixed images file](../../deployments/images.md#fixed-images-via-a-yaml-file). +The format is identical to the [fixed images file](../../deployments/images.md#command-line-argument---fixed-images-file). The fixed images specified in the [dynamic target config](../../kluctl-project/targets/dynamic-targets.md#images) have higher priority. diff --git a/docs/reference/templating/predefined-variables.md b/docs/reference/templating/predefined-variables.md index a547e1d86..bb1fe4d53 100644 --- a/docs/reference/templating/predefined-variables.md +++ b/docs/reference/templating/predefined-variables.md @@ -23,6 +23,3 @@ This is the target definition of the currently processed target. It contains all ### images This global object provides the dynamic images features described in [images](../deployments/images.md). - -### version -This global object defines latest version filters for `images.get_image(...)`. See [images](../deployments/images.md) for details. From 777f6875432b322f8d3556ab3f78629fe5c7bea7 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 8 Feb 2023 17:25:05 +0100 Subject: [PATCH 1414/2916] docs: Add docs for sha256 filter --- docs/reference/templating/filters.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/reference/templating/filters.md b/docs/reference/templating/filters.md index 6495f57cd..5fed49201 100644 --- a/docs/reference/templating/filters.md +++ b/docs/reference/templating/filters.md @@ -84,5 +84,11 @@ Renders the input string with the current Jinja2 context. Example: {{ a | render }} ``` +### sha256 +Calculates the sha256 digest of the input string. Example: +``` +{{ "some-string" | sha256 }} +``` + ### slugify Slugify a string based on [python-slugify](https://github.com/un33k/python-slugify). From b34e771907f7f33c205afa3c2b2486e07f529b79 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 8 Feb 2023 17:26:12 +0100 Subject: [PATCH 1415/2916] feat: Rename kluctl.io/kustomize_dir annotation to kluctl.io/deployment-item-dir --- pkg/deployment/deployment_item.go | 3 +-- pkg/deployment/utils/remote_objects_utils.go | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 6d059e96c..b32964eae 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -104,9 +104,8 @@ func (di *DeploymentItem) getCommonLabels() map[string]string { } func (di *DeploymentItem) getCommonAnnotations() map[string]string { - // TODO change it to kluctl.io/deployment_dir a := map[string]string{ - "kluctl.io/kustomize_dir": filepath.ToSlash(di.RelToSourceItemDir), + "kluctl.io/deployment-item-dir": filepath.ToSlash(di.RelToSourceItemDir), } if di.Config.SkipDeleteIfTags { a["kluctl.io/skip-delete-if-tags"] = "true" diff --git a/pkg/deployment/utils/remote_objects_utils.go b/pkg/deployment/utils/remote_objects_utils.go index 1ff37177f..ff505702e 100644 --- a/pkg/deployment/utils/remote_objects_utils.go +++ b/pkg/deployment/utils/remote_objects_utils.go @@ -237,7 +237,7 @@ func (u *RemoteObjectUtils) getInclusionEntries(o *uo.UnstructuredObject) []util }) } - if itemDir := o.GetK8sAnnotation("kluctl.io/kustomize_dir"); itemDir != nil { + if itemDir := o.GetK8sAnnotation("kluctl.io/deployment-item-dir"); itemDir != nil { iv = append(iv, utils.InclusionEntry{ Type: "deploymentItemDir", Value: *itemDir, From 0e4a304ada84217c3d38aa142c1d281151982bd9 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 8 Feb 2023 17:29:58 +0100 Subject: [PATCH 1416/2916] refactor: Pass external args via loadArgs instead of TargetContextParams --- cmd/kluctl/commands/cmd_list_targets.go | 2 +- cmd/kluctl/commands/cmd_seal.go | 2 +- cmd/kluctl/commands/completion.go | 19 +++++++----- cmd/kluctl/commands/utils.go | 41 +++++++++++++------------ pkg/kluctl_project/project_load.go | 2 ++ pkg/kluctl_project/target_context.go | 9 +++--- pkg/kluctl_project/targets.go | 7 ++--- 7 files changed, 46 insertions(+), 36 deletions(-) diff --git a/cmd/kluctl/commands/cmd_list_targets.go b/cmd/kluctl/commands/cmd_list_targets.go index 91eacf231..dca1b646d 100644 --- a/cmd/kluctl/commands/cmd_list_targets.go +++ b/cmd/kluctl/commands/cmd_list_targets.go @@ -17,7 +17,7 @@ func (cmd *listTargetsCmd) Help() string { } func (cmd *listTargetsCmd) Run(ctx context.Context) error { - return withKluctlProjectFromArgs(ctx, cmd.ProjectFlags, true, false, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { + return withKluctlProjectFromArgs(ctx, cmd.ProjectFlags, nil, true, false, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { var result []*types.Target for _, t := range p.DynamicTargets { result = append(result, t.Target) diff --git a/cmd/kluctl/commands/cmd_seal.go b/cmd/kluctl/commands/cmd_seal.go index 18fd57647..ec00b4612 100644 --- a/cmd/kluctl/commands/cmd_seal.go +++ b/cmd/kluctl/commands/cmd_seal.go @@ -155,7 +155,7 @@ func (cmd *sealCmd) loadCert(cmdCtx *commandCtx) (*x509.Certificate, error) { } func (cmd *sealCmd) Run(ctx context.Context) error { - return withKluctlProjectFromArgs(ctx, cmd.ProjectFlags, true, false, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { + return withKluctlProjectFromArgs(ctx, cmd.ProjectFlags, nil, true, false, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { hadError := false baseTargets := make(map[string]bool) diff --git a/cmd/kluctl/commands/completion.go b/cmd/kluctl/commands/completion.go index 32ffab84f..225ae0fba 100644 --- a/cmd/kluctl/commands/completion.go +++ b/cmd/kluctl/commands/completion.go @@ -16,6 +16,7 @@ import ( func RegisterFlagCompletionFuncs(cmdStruct interface{}, ccmd *cobra.Command) error { v := reflect.ValueOf(cmdStruct).Elem() projectFlags := v.FieldByName("ProjectFlags") + argsFlags := v.FieldByName("ArgsFlags") targetFlags := v.FieldByName("TargetFlags") inclusionFlags := v.FieldByName("InclusionFlags") imageFlags := v.FieldByName("ImageFlags") @@ -23,7 +24,11 @@ func RegisterFlagCompletionFuncs(cmdStruct interface{}, ccmd *cobra.Command) err ctx := context.Background() if projectFlags.IsValid() && targetFlags.IsValid() { - _ = ccmd.RegisterFlagCompletionFunc("target", buildTargetCompletionFunc(ctx, projectFlags.Addr().Interface().(*args.ProjectFlags))) + var argsFlag2 *args.ArgsFlags + if argsFlags.IsValid() { + argsFlag2 = argsFlags.Addr().Interface().(*args.ArgsFlags) + } + _ = ccmd.RegisterFlagCompletionFunc("target", buildTargetCompletionFunc(ctx, projectFlags.Addr().Interface().(*args.ProjectFlags), argsFlag2)) } if projectFlags.IsValid() && inclusionFlags.IsValid() { @@ -42,18 +47,18 @@ func RegisterFlagCompletionFuncs(cmdStruct interface{}, ccmd *cobra.Command) err return nil } -func withProjectForCompletion(ctx context.Context, projectArgs *args.ProjectFlags, cb func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error) error { +func withProjectForCompletion(ctx context.Context, projectArgs *args.ProjectFlags, argsFlags *args.ArgsFlags, cb func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error) error { // let's not update git caches too often projectArgs.GitCacheUpdateInterval = time.Second * 60 - return withKluctlProjectFromArgs(ctx, *projectArgs, false, true, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { + return withKluctlProjectFromArgs(ctx, *projectArgs, argsFlags, false, true, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { return cb(ctx, p) }) } -func buildTargetCompletionFunc(ctx context.Context, projectArgs *args.ProjectFlags) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { +func buildTargetCompletionFunc(ctx context.Context, projectArgs *args.ProjectFlags, argsFlags *args.ArgsFlags) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { var ret []string - err := withProjectForCompletion(ctx, projectArgs, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { + err := withProjectForCompletion(ctx, projectArgs, argsFlags, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { for _, t := range p.DynamicTargets { ret = append(ret, t.Target.Name) } @@ -99,7 +104,7 @@ func buildInclusionCompletionFunc(ctx context.Context, cmdStruct interface{}, fo var deploymentItemDirs utils.OrderedMap var mutex sync.Mutex - err := withProjectForCompletion(ctx, &ptArgs.projectFlags, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { + err := withProjectForCompletion(ctx, &ptArgs.projectFlags, &ptArgs.argsFlags, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { var targets []string if ptArgs.targetFlags.Target == "" { for _, t := range p.DynamicTargets { @@ -153,7 +158,7 @@ func buildImagesCompletionFunc(ctx context.Context, cmdStruct interface{}) func( var images utils.OrderedMap var mutex sync.Mutex - err := withProjectForCompletion(ctx, &ptArgs.projectFlags, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { + err := withProjectForCompletion(ctx, &ptArgs.projectFlags, &ptArgs.argsFlags, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { var targets []string if ptArgs.targetFlags.Target == "" { for _, t := range p.DynamicTargets { diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 909a14c3b..3259ab704 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -25,7 +25,7 @@ import ( "k8s.io/client-go/tools/clientcmd/api" ) -func withKluctlProjectFromArgs(ctx context.Context, projectFlags args.ProjectFlags, strictTemplates bool, forCompletion bool, cb func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error) error { +func withKluctlProjectFromArgs(ctx context.Context, projectFlags args.ProjectFlags, argsFlags *args.ArgsFlags, strictTemplates bool, forCompletion bool, cb func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error) error { tmpDir, err := os.MkdirTemp(utils.GetTmpBaseDir(ctx), "project-") if err != nil { return fmt.Errorf("creating temporary project directory failed: %w", err) @@ -80,10 +80,30 @@ func withKluctlProjectFromArgs(ctx context.Context, projectFlags args.ProjectFla rp := repocache.NewGitRepoCache(ctx, sshPool, gitAuth, repoOverrides, projectFlags.GitCacheUpdateInterval) defer rp.Clear() + var externalArgs *uo.UnstructuredObject + if argsFlags != nil { + optionArgs, err := deployment.ParseArgs(argsFlags.Arg) + if err != nil { + return err + } + externalArgs, err = deployment.ConvertArgsToVars(optionArgs, true) + if err != nil { + return err + } + for _, a := range argsFlags.ArgsFromFile { + optionArgs2, err := uo.FromFile(a) + if err != nil { + return err + } + externalArgs.Merge(optionArgs2) + } + } + loadArgs := kluctl_project.LoadKluctlProjectArgs{ RepoRoot: repoRoot, ProjectDir: projectDir, ProjectConfig: projectFlags.ProjectConfig.String(), + ExternalArgs: externalArgs, RP: rp, ClientConfigGetter: clientConfigGetter(forCompletion), } @@ -119,7 +139,7 @@ type commandCtx struct { } func withProjectCommandContext(ctx context.Context, args projectTargetCommandArgs, cb func(cmdCtx *commandCtx) error) error { - return withKluctlProjectFromArgs(ctx, args.projectFlags, true, false, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { + return withKluctlProjectFromArgs(ctx, args.projectFlags, &args.argsFlags, true, false, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { return withProjectTargetCommandContext(ctx, args, p, cb) }) } @@ -151,22 +171,6 @@ func withProjectTargetCommandContext(ctx context.Context, args projectTargetComm return err } - optionArgs, err := deployment.ParseArgs(args.argsFlags.Arg) - if err != nil { - return err - } - optionArgs2, err := deployment.ConvertArgsToVars(optionArgs, true) - if err != nil { - return err - } - for _, a := range args.argsFlags.ArgsFromFile { - optionArgs3, err := uo.FromFile(a) - if err != nil { - return err - } - optionArgs2.Merge(optionArgs3) - } - renderOutputDir := args.renderOutputDirFlags.RenderOutputDir if renderOutputDir == "" { tmpDir, err := os.MkdirTemp(p.TmpDir, "rendered") @@ -184,7 +188,6 @@ func withProjectTargetCommandContext(ctx context.Context, args projectTargetComm OfflineK8s: args.offlineKubernetes, K8sVersion: args.kubernetesVersion, DryRun: args.dryRunArgs == nil || args.dryRunArgs.DryRun || args.forCompletion, - ExternalArgs: optionArgs2, ForSeal: args.forSeal, Images: images, Inclusion: inclusion, diff --git a/pkg/kluctl_project/project_load.go b/pkg/kluctl_project/project_load.go index c397e5190..2af17c0d0 100644 --- a/pkg/kluctl_project/project_load.go +++ b/pkg/kluctl_project/project_load.go @@ -5,6 +5,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/sops/decryptor" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd/api" @@ -15,6 +16,7 @@ type LoadKluctlProjectArgs struct { RepoRoot string ProjectDir string ProjectConfig string + ExternalArgs *uo.UnstructuredObject SopsDecrypter *decryptor.Decryptor RP *repocache.GitRepoCache diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index 1ccf21812..0a5e46627 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -34,7 +34,6 @@ type TargetContextParams struct { OfflineK8s bool K8sVersion string DryRun bool - ExternalArgs *uo.UnstructuredObject ForSeal bool Images *deployment.Images Inclusion *utils.Inclusion @@ -73,7 +72,7 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, params Targe } target.Context = &clusterContext - varsCtx, err := p.buildVars(target, params.ExternalArgs, params.ForSeal) + varsCtx, err := p.buildVars(target, params.ForSeal) if err != nil { return nil, err } @@ -160,7 +159,7 @@ func (p *LoadedKluctlProject) loadK8sConfig(target *types.Target, offlineK8s boo return clientConfig, "", nil } -func (p *LoadedKluctlProject) buildVars(target *types.Target, externalArgs *uo.UnstructuredObject, forSeal bool) (*vars.VarsCtx, error) { +func (p *LoadedKluctlProject) buildVars(target *types.Target, forSeal bool) (*vars.VarsCtx, error) { varsCtx := vars.NewVarsCtx(p.J2) targetVars, err := uo.FromStruct(target) @@ -181,7 +180,9 @@ func (p *LoadedKluctlProject) buildVars(target *types.Target, externalArgs *uo.U } } } - allArgs.Merge(externalArgs) + if p.loadArgs.ExternalArgs != nil { + allArgs.Merge(p.loadArgs.ExternalArgs) + } err = deployment.LoadDefaultArgs(p.Config.Args, allArgs) if err != nil { diff --git a/pkg/kluctl_project/targets.go b/pkg/kluctl_project/targets.go index e32f627c8..eda785735 100644 --- a/pkg/kluctl_project/targets.go +++ b/pkg/kluctl_project/targets.go @@ -6,7 +6,6 @@ import ( "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" - "github.com/kluctl/kluctl/v2/pkg/vars" "github.com/kluctl/kluctl/v2/pkg/yaml" "os" "regexp" @@ -52,7 +51,8 @@ func (c *LoadedKluctlProject) loadTargets() error { err = c.renderTarget(target) if err != nil { - return err + status.Warning(c.ctx, "Failed to load target %s: %v", target.Name, err) + continue } if _, ok := targetNames[target.Name]; ok { @@ -77,8 +77,7 @@ func (c *LoadedKluctlProject) renderTarget(target *types.Target) error { var errors []error for i := 0; i < 10; i++ { - varsCtx := vars.NewVarsCtx(c.J2) - err := varsCtx.UpdateChildFromStruct("target", target) + varsCtx, err := c.buildVars(target, false) if err != nil { return err } From ce578df1ffc05554ca2844d56819508d609cc14d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 8 Feb 2023 17:31:06 +0100 Subject: [PATCH 1417/2916] feat: Implement deployment/target discriminator --- cmd/kluctl/commands/cmd_delete.go | 14 +++----- cmd/kluctl/commands/cmd_deploy.go | 2 +- cmd/kluctl/commands/cmd_diff.go | 2 +- cmd/kluctl/commands/cmd_prune.go | 2 +- cmd/kluctl/commands/cmd_validate.go | 2 +- pkg/deployment/commands/delete.go | 36 ++++++------------- pkg/deployment/commands/deploy.go | 18 +++++++--- pkg/deployment/commands/diff.go | 18 +++++++--- pkg/deployment/commands/prune.go | 15 +++++--- pkg/deployment/commands/validate.go | 13 ++++--- pkg/deployment/deployment_item.go | 3 ++ pkg/deployment/shared_context.go | 1 + pkg/deployment/utils/remote_objects_utils.go | 20 +++++++---- .../utils/remote_objects_utils_test.go | 5 +-- pkg/kluctl_project/target_context.go | 1 + pkg/kluctl_project/targets.go | 3 ++ pkg/types/kluctl_project.go | 2 ++ 17 files changed, 94 insertions(+), 63 deletions(-) diff --git a/cmd/kluctl/commands/cmd_delete.go b/cmd/kluctl/commands/cmd_delete.go index bdac4154e..751410b04 100644 --- a/cmd/kluctl/commands/cmd_delete.go +++ b/cmd/kluctl/commands/cmd_delete.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "github.com/kluctl/kluctl/v2/cmd/kluctl/args" - "github.com/kluctl/kluctl/v2/pkg/deployment" "github.com/kluctl/kluctl/v2/pkg/deployment/commands" "github.com/kluctl/kluctl/v2/pkg/deployment/utils" "github.com/kluctl/kluctl/v2/pkg/k8s" @@ -25,7 +24,7 @@ type deleteCmd struct { args.OutputFormatFlags args.RenderOutputDirFlags - DeleteByLabel []string `group:"misc" short:"l" help:"Override the labels used to find objects for deletion."` + Discriminator string `group:"misc" help:"Override the discriminator used to find objects for deletion."` } func (cmd *deleteCmd) Help() string { @@ -48,14 +47,11 @@ func (cmd *deleteCmd) Run(ctx context.Context) error { renderOutputDirFlags: cmd.RenderOutputDirFlags, } return withProjectCommandContext(ctx, ptArgs, func(cmdCtx *commandCtx) error { - cmd2 := commands.NewDeleteCommand(cmdCtx.targetCtx.DeploymentCollection) - - deleteByLabels, err := deployment.ParseArgs(cmd.DeleteByLabel) - if err != nil { - return err + discriminator := cmdCtx.targetCtx.Target.Discriminator + if cmd.Discriminator != "" { + discriminator = cmd.Discriminator } - - cmd2.OverrideDeleteByLabels = deleteByLabels + cmd2 := commands.NewDeleteCommand(discriminator, cmdCtx.targetCtx.DeploymentCollection.Inclusion) objects, err := cmd2.Run(cmdCtx.ctx, cmdCtx.targetCtx.SharedContext.K) if err != nil { diff --git a/cmd/kluctl/commands/cmd_deploy.go b/cmd/kluctl/commands/cmd_deploy.go index 624d6b6e3..083a97e26 100644 --- a/cmd/kluctl/commands/cmd_deploy.go +++ b/cmd/kluctl/commands/cmd_deploy.go @@ -55,7 +55,7 @@ func (cmd *deployCmd) runCmdDeploy(cmdCtx *commandCtx) error { status.Trace(cmdCtx.ctx, "enter runCmdDeploy") defer status.Trace(cmdCtx.ctx, "leave runCmdDeploy") - cmd2 := commands.NewDeployCommand(cmdCtx.targetCtx.DeploymentCollection) + cmd2 := commands.NewDeployCommand(cmdCtx.targetCtx.Target.Discriminator, cmdCtx.targetCtx.DeploymentCollection) cmd2.ForceApply = cmd.ForceApply cmd2.ReplaceOnError = cmd.ReplaceOnError cmd2.ForceReplaceOnError = cmd.ForceReplaceOnError diff --git a/cmd/kluctl/commands/cmd_diff.go b/cmd/kluctl/commands/cmd_diff.go index a32991d41..b04ff519d 100644 --- a/cmd/kluctl/commands/cmd_diff.go +++ b/cmd/kluctl/commands/cmd_diff.go @@ -39,7 +39,7 @@ func (cmd *diffCmd) Run(ctx context.Context) error { renderOutputDirFlags: cmd.RenderOutputDirFlags, } return withProjectCommandContext(ctx, ptArgs, func(cmdCtx *commandCtx) error { - cmd2 := commands.NewDiffCommand(cmdCtx.targetCtx.DeploymentCollection) + cmd2 := commands.NewDiffCommand(cmdCtx.targetCtx.Target.Discriminator, cmdCtx.targetCtx.DeploymentCollection) cmd2.ForceApply = cmd.ForceApply cmd2.ReplaceOnError = cmd.ReplaceOnError cmd2.ForceReplaceOnError = cmd.ForceReplaceOnError diff --git a/cmd/kluctl/commands/cmd_prune.go b/cmd/kluctl/commands/cmd_prune.go index 9c887eb1c..5b542cd27 100644 --- a/cmd/kluctl/commands/cmd_prune.go +++ b/cmd/kluctl/commands/cmd_prune.go @@ -45,7 +45,7 @@ func (cmd *pruneCmd) Run(ctx context.Context) error { } func (cmd *pruneCmd) runCmdPrune(cmdCtx *commandCtx) error { - cmd2 := commands.NewPruneCommand(cmdCtx.targetCtx.DeploymentCollection) + cmd2 := commands.NewPruneCommand(cmdCtx.targetCtx.Target.Discriminator, cmdCtx.targetCtx.DeploymentCollection) objects, err := cmd2.Run(cmdCtx.ctx, cmdCtx.targetCtx.SharedContext.K) if err != nil { return err diff --git a/cmd/kluctl/commands/cmd_validate.go b/cmd/kluctl/commands/cmd_validate.go index 26b43da2f..532f52e28 100644 --- a/cmd/kluctl/commands/cmd_validate.go +++ b/cmd/kluctl/commands/cmd_validate.go @@ -39,7 +39,7 @@ func (cmd *validateCmd) Run(ctx context.Context) error { } return withProjectCommandContext(ctx, ptArgs, func(cmdCtx *commandCtx) error { startTime := time.Now() - cmd2 := commands.NewValidateCommand(cmdCtx.ctx, cmdCtx.targetCtx.DeploymentCollection) + cmd2 := commands.NewValidateCommand(cmdCtx.ctx, cmdCtx.targetCtx.Target.Discriminator, cmdCtx.targetCtx.DeploymentCollection) for true { result, err := cmd2.Run(cmdCtx.ctx, cmdCtx.targetCtx.SharedContext.K) if err != nil { diff --git a/pkg/deployment/commands/delete.go b/pkg/deployment/commands/delete.go index 6a3040cda..da6d7e5d9 100644 --- a/pkg/deployment/commands/delete.go +++ b/pkg/deployment/commands/delete.go @@ -3,7 +3,6 @@ package commands import ( "context" "fmt" - "github.com/kluctl/kluctl/v2/pkg/deployment" utils2 "github.com/kluctl/kluctl/v2/pkg/deployment/utils" "github.com/kluctl/kluctl/v2/pkg/k8s" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" @@ -11,41 +10,28 @@ import ( ) type DeleteCommand struct { - c *deployment.DeploymentCollection - OverrideDeleteByLabels map[string]string + discriminator string + inclusion *utils.Inclusion } -func NewDeleteCommand(c *deployment.DeploymentCollection) *DeleteCommand { +func NewDeleteCommand(discriminator string, inclusion *utils.Inclusion) *DeleteCommand { return &DeleteCommand{ - c: c, + discriminator: discriminator, + inclusion: inclusion, } } func (cmd *DeleteCommand) Run(ctx context.Context, k *k8s.K8sCluster) ([]k8s2.ObjectRef, error) { - dew := utils2.NewDeploymentErrorsAndWarnings() - - ru := utils2.NewRemoteObjectsUtil(ctx, dew) - - var labels map[string]string - if len(cmd.OverrideDeleteByLabels) != 0 { - labels = cmd.OverrideDeleteByLabels - } else { - labels = cmd.c.Project.GetCommonLabels() - } - - if len(labels) == 0 { - return nil, fmt.Errorf("deletion without using commonLabels in the root deployment.yaml is not allowed") - } - - var inclusion *utils.Inclusion - if cmd.c != nil { - inclusion = cmd.c.Inclusion + if cmd.discriminator == "" { + return nil, fmt.Errorf("deletion without a discriminator is not supported") } - err := ru.UpdateRemoteObjects(k, labels, nil, false) + dew := utils2.NewDeploymentErrorsAndWarnings() + ru := utils2.NewRemoteObjectsUtil(ctx, dew) + err := ru.UpdateRemoteObjects(k, &cmd.discriminator, nil, false) if err != nil { return nil, err } - return utils2.FindObjectsForDelete(k, ru.GetFilteredRemoteObjects(inclusion), inclusion.HasType("tags"), nil) + return utils2.FindObjectsForDelete(k, ru.GetFilteredRemoteObjects(cmd.inclusion), cmd.inclusion.HasType("tags"), nil) } diff --git a/pkg/deployment/commands/deploy.go b/pkg/deployment/commands/deploy.go index bbf4284dc..cb3721b41 100644 --- a/pkg/deployment/commands/deploy.go +++ b/pkg/deployment/commands/deploy.go @@ -2,15 +2,19 @@ package commands import ( "context" + "fmt" "github.com/kluctl/kluctl/v2/pkg/deployment" utils2 "github.com/kluctl/kluctl/v2/pkg/deployment/utils" "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" + k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" "time" ) type DeployCommand struct { - c *deployment.DeploymentCollection + c *deployment.DeploymentCollection + discriminator string ForceApply bool ReplaceOnError bool @@ -20,17 +24,23 @@ type DeployCommand struct { NoWait bool } -func NewDeployCommand(c *deployment.DeploymentCollection) *DeployCommand { +func NewDeployCommand(discriminator string, c *deployment.DeploymentCollection) *DeployCommand { return &DeployCommand{ - c: c, + discriminator: discriminator, + c: c, } } func (cmd *DeployCommand) Run(ctx context.Context, k *k8s.K8sCluster, diffResultCb func(diffResult *types.CommandResult) error) (*types.CommandResult, error) { dew := utils2.NewDeploymentErrorsAndWarnings() + if cmd.discriminator == "" { + status.Warning(ctx, "No discriminator configured. Orphan object detection will not work") + dew.AddWarning(k8s2.ObjectRef{}, fmt.Errorf("no discriminator configured. Orphan object detection will not work")) + } + ru := utils2.NewRemoteObjectsUtil(ctx, dew) - err := ru.UpdateRemoteObjects(k, cmd.c.Project.GetCommonLabels(), cmd.c.LocalObjectRefs(), false) + err := ru.UpdateRemoteObjects(k, &cmd.discriminator, cmd.c.LocalObjectRefs(), false) if err != nil { return nil, err } diff --git a/pkg/deployment/commands/diff.go b/pkg/deployment/commands/diff.go index 890908fc0..ff8e8f0ca 100644 --- a/pkg/deployment/commands/diff.go +++ b/pkg/deployment/commands/diff.go @@ -2,14 +2,18 @@ package commands import ( "context" + "fmt" "github.com/kluctl/kluctl/v2/pkg/deployment" "github.com/kluctl/kluctl/v2/pkg/deployment/utils" "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" + k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" ) type DiffCommand struct { - c *deployment.DeploymentCollection + c *deployment.DeploymentCollection + discriminator string ForceApply bool ReplaceOnError bool @@ -19,17 +23,23 @@ type DiffCommand struct { IgnoreAnnotations bool } -func NewDiffCommand(c *deployment.DeploymentCollection) *DiffCommand { +func NewDiffCommand(discriminator string, c *deployment.DeploymentCollection) *DiffCommand { return &DiffCommand{ - c: c, + discriminator: discriminator, + c: c, } } func (cmd *DiffCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*types.CommandResult, error) { dew := utils.NewDeploymentErrorsAndWarnings() + if cmd.discriminator == "" { + status.Warning(ctx, "No discriminator configured. Orphan object detection will not work") + dew.AddWarning(k8s2.ObjectRef{}, fmt.Errorf("no discriminator configured. Orphan object detection will not work")) + } + ru := utils.NewRemoteObjectsUtil(ctx, dew) - err := ru.UpdateRemoteObjects(k, cmd.c.Project.GetCommonLabels(), cmd.c.LocalObjectRefs(), false) + err := ru.UpdateRemoteObjects(k, &cmd.discriminator, cmd.c.LocalObjectRefs(), false) if err != nil { return nil, err } diff --git a/pkg/deployment/commands/prune.go b/pkg/deployment/commands/prune.go index e3b5f4689..4b7a32cfe 100644 --- a/pkg/deployment/commands/prune.go +++ b/pkg/deployment/commands/prune.go @@ -2,6 +2,7 @@ package commands import ( "context" + "fmt" "github.com/kluctl/kluctl/v2/pkg/deployment" utils2 "github.com/kluctl/kluctl/v2/pkg/deployment/utils" "github.com/kluctl/kluctl/v2/pkg/k8s" @@ -9,20 +10,26 @@ import ( ) type PruneCommand struct { - c *deployment.DeploymentCollection + discriminator string + c *deployment.DeploymentCollection } -func NewPruneCommand(c *deployment.DeploymentCollection) *PruneCommand { +func NewPruneCommand(discriminator string, c *deployment.DeploymentCollection) *PruneCommand { return &PruneCommand{ - c: c, + discriminator: discriminator, + c: c, } } func (cmd *PruneCommand) Run(ctx context.Context, k *k8s.K8sCluster) ([]k8s2.ObjectRef, error) { + if cmd.discriminator == "" { + return nil, fmt.Errorf("pruning without a discriminator is not supported") + } + dew := utils2.NewDeploymentErrorsAndWarnings() ru := utils2.NewRemoteObjectsUtil(ctx, dew) - err := ru.UpdateRemoteObjects(k, cmd.c.Project.GetCommonLabels(), nil, false) + err := ru.UpdateRemoteObjects(k, &cmd.discriminator, nil, false) if err != nil { return nil, err } diff --git a/pkg/deployment/commands/validate.go b/pkg/deployment/commands/validate.go index 010fa9868..f279d5042 100644 --- a/pkg/deployment/commands/validate.go +++ b/pkg/deployment/commands/validate.go @@ -11,15 +11,18 @@ import ( ) type ValidateCommand struct { - c *deployment.DeploymentCollection + c *deployment.DeploymentCollection + discriminator string + dew *utils2.DeploymentErrorsAndWarnings ru *utils2.RemoteObjectUtils } -func NewValidateCommand(ctx context.Context, c *deployment.DeploymentCollection) *ValidateCommand { +func NewValidateCommand(ctx context.Context, discriminator string, c *deployment.DeploymentCollection) *ValidateCommand { cmd := &ValidateCommand{ - c: c, - dew: utils2.NewDeploymentErrorsAndWarnings(), + c: c, + discriminator: discriminator, + dew: utils2.NewDeploymentErrorsAndWarnings(), } cmd.ru = utils2.NewRemoteObjectsUtil(ctx, cmd.dew) return cmd @@ -31,7 +34,7 @@ func (cmd *ValidateCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*types. cmd.dew.Init() - err := cmd.ru.UpdateRemoteObjects(k, cmd.c.Project.GetCommonLabels(), cmd.c.LocalObjectRefs(), true) + err := cmd.ru.UpdateRemoteObjects(k, &cmd.discriminator, cmd.c.LocalObjectRefs(), true) if err != nil { return nil, err } diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index b32964eae..607ded1dd 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -95,6 +95,9 @@ func NewDeploymentItem(ctx SharedContext, project *DeploymentProject, collection func (di *DeploymentItem) getCommonLabels() map[string]string { l := di.Project.GetCommonLabels() + if di.ctx.Discriminator != "" { + l["kluctl.io/discriminator"] = di.ctx.Discriminator + } i := 0 for _, t := range di.Tags.ListKeys() { l[fmt.Sprintf("kluctl.io/tag-%d", i)] = t diff --git a/pkg/deployment/shared_context.go b/pkg/deployment/shared_context.go index 859f194ae..9b20d151b 100644 --- a/pkg/deployment/shared_context.go +++ b/pkg/deployment/shared_context.go @@ -18,6 +18,7 @@ type SharedContext struct { VarsLoader *vars.VarsLoader HelmCredentials helm.HelmCredentialsProvider + Discriminator string RenderDir string SealedSecretsDir string DefaultSealedSecretsOutputPattern string diff --git a/pkg/deployment/utils/remote_objects_utils.go b/pkg/deployment/utils/remote_objects_utils.go index ff505702e..37e078595 100644 --- a/pkg/deployment/utils/remote_objects_utils.go +++ b/pkg/deployment/utils/remote_objects_utils.go @@ -30,13 +30,21 @@ func NewRemoteObjectsUtil(ctx context.Context, dew *DeploymentErrorsAndWarnings) } } -func (u *RemoteObjectUtils) getAllByLabels(k *k8s.K8sCluster, labels map[string]string, onlyUsedGKs map[schema.GroupKind]bool) error { +func (u *RemoteObjectUtils) getAllByDiscriminator(k *k8s.K8sCluster, discriminator *string, onlyUsedGKs map[schema.GroupKind]bool) error { var mutex sync.Mutex - if len(labels) == 0 { + if discriminator == nil { return nil } + if *discriminator == "" { + status.Warning(u.ctx, "No discriminator configured for target, retrieval of remote objects will be slow.") + return nil + } + + labels := map[string]string{ + "kluctl.io/discriminator": *discriminator, + } - baseStatus := "Getting remote objects by commonLabels" + baseStatus := "Getting remote objects by discriminator" s := status.Start(u.ctx, baseStatus) defer s.Failed() @@ -87,7 +95,7 @@ func (u *RemoteObjectUtils) getAllByLabels(k *k8s.K8sCluster, labels map[string] s.UpdateAndInfoFallback("%s: Failed with %d errors", baseStatus, errCount) s.Warning() if permissionErrCount != 0 { - u.dew.AddWarning(k8s2.ObjectRef{}, fmt.Errorf("at least one permission error was encountered while gathering objects by labels. This might result in orphan object detection to not work properly")) + u.dew.AddWarning(k8s2.ObjectRef{}, fmt.Errorf("at least one permission error was encountered while gathering objects by discriminator labels. This might result in orphan object detection to not work properly")) } } else { s.Success() @@ -157,7 +165,7 @@ func (u *RemoteObjectUtils) getMissingObjects(k *k8s.K8sCluster, refs []k8s2.Obj return g.ErrorOrNil() } -func (u *RemoteObjectUtils) UpdateRemoteObjects(k *k8s.K8sCluster, labels map[string]string, refs []k8s2.ObjectRef, onlyUsedGKs bool) error { +func (u *RemoteObjectUtils) UpdateRemoteObjects(k *k8s.K8sCluster, discriminator *string, refs []k8s2.ObjectRef, onlyUsedGKs bool) error { if k == nil { return nil } @@ -171,7 +179,7 @@ func (u *RemoteObjectUtils) UpdateRemoteObjects(k *k8s.K8sCluster, labels map[st } } - err := u.getAllByLabels(k, labels, usedGKs) + err := u.getAllByDiscriminator(k, discriminator, usedGKs) if err != nil { return err } diff --git a/pkg/deployment/utils/remote_objects_utils_test.go b/pkg/deployment/utils/remote_objects_utils_test.go index aaa1671a2..8ded2b28c 100644 --- a/pkg/deployment/utils/remote_objects_utils_test.go +++ b/pkg/deployment/utils/remote_objects_utils_test.go @@ -37,12 +37,13 @@ func TestRemoteObjectUtils_PermissionErrors(t *testing.T) { dew := NewDeploymentErrorsAndWarnings() u := NewRemoteObjectsUtil(context.Background(), dew) - err = u.UpdateRemoteObjects(k, map[string]string{"l1": "v1"}, []k8s2.ObjectRef{ + discriminator := "d" + err = u.UpdateRemoteObjects(k, &discriminator, []k8s2.ObjectRef{ k8s2.NewObjectRef("", "v1", "Secret", "secret", "default"), }, false) assert.NoError(t, err) assert.Equal(t, []types.DeploymentError{{ - Error: "at least one permission error was encountered while gathering objects by labels. This might result in orphan object detection to not work properly"}, + Error: "at least one permission error was encountered while gathering objects by discriminator labels. This might result in orphan object detection to not work properly"}, }, dew.GetWarningsList()) assert.Empty(t, dew.GetErrorsList()) } diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index 0a5e46627..6ebb6fa86 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -109,6 +109,7 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, params Targe SopsDecrypter: p.SopsDecrypter, VarsLoader: varsLoader, HelmCredentials: params.HelmCredentials, + Discriminator: target.Discriminator, RenderDir: params.RenderOutputDir, SealedSecretsDir: p.sealedSecretsDir, DefaultSealedSecretsOutputPattern: target.Name, diff --git a/pkg/kluctl_project/targets.go b/pkg/kluctl_project/targets.go index eda785735..1577e230e 100644 --- a/pkg/kluctl_project/targets.go +++ b/pkg/kluctl_project/targets.go @@ -228,6 +228,9 @@ func (c *LoadedKluctlProject) buildDynamicTarget(targetInfo *dynamicTargetInfo) if err != nil { return nil, err } + if target.Discriminator == "" { + target.Discriminator = c.Config.Discriminator + } if targetInfo.baseTarget.TargetConfig == nil { return &target, nil } diff --git a/pkg/types/kluctl_project.go b/pkg/types/kluctl_project.go index 0266fa1a0..33078a4ae 100644 --- a/pkg/types/kluctl_project.go +++ b/pkg/types/kluctl_project.go @@ -34,6 +34,7 @@ type Target struct { TargetConfig *ExternalTargetConfig `yaml:"targetConfig,omitempty"` SealingConfig *SealingConfig `yaml:"sealingConfig,omitempty"` Images []FixedImage `yaml:"images,omitempty"` + Discriminator string `yaml:"discriminator,omitempty"` } type DynamicTarget struct { @@ -66,4 +67,5 @@ type KluctlProject struct { Targets []*Target `yaml:"targets,omitempty"` Args []*DeploymentArg `yaml:"args,omitempty"` SecretsConfig *SecretsConfig `yaml:"secretsConfig,omitempty"` + Discriminator string `yaml:"discriminator,omitempty"` } From 109d3a15d71398200801ad12f28ee984d6bea5fc Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 8 Feb 2023 17:31:20 +0100 Subject: [PATCH 1418/2916] fix: Properly fail if target rendering fails 10 times --- pkg/kluctl_project/targets.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/pkg/kluctl_project/targets.go b/pkg/kluctl_project/targets.go index 1577e230e..66a5afb1f 100644 --- a/pkg/kluctl_project/targets.go +++ b/pkg/kluctl_project/targets.go @@ -75,7 +75,7 @@ func (c *LoadedKluctlProject) renderTarget(target *types.Target) error { // Try rendering the target multiple times, until all values can be rendered successfully. This allows the target // to reference itself in complex ways. We'll also try loading the cluster vars in each iteration. - var errors []error + var retErr error for i := 0; i < 10; i++ { varsCtx, err := c.buildVars(target, false) if err != nil { @@ -86,11 +86,9 @@ func (c *LoadedKluctlProject) renderTarget(target *types.Target) error { if err == nil && !changed { return nil } + retErr = err } - if len(errors) != 0 { - return errors[0] - } - return nil + return retErr } func (c *LoadedKluctlProject) prepareDynamicTargets(baseTarget *types.Target) ([]*dynamicTargetInfo, error) { From 93d346680d05d3cdbc6276d5ab70f2de9d8aca1b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Feb 2023 14:02:02 +0100 Subject: [PATCH 1419/2916] chore(deps): Bump golang.org/x/net from 0.5.0 to 0.6.0 (#305) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.5.0 to 0.6.0. - [Release notes](https://github.com/golang/net/releases) - [Commits](https://github.com/golang/net/compare/v0.5.0...v0.6.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 0a9e7de6f..a768ee877 100644 --- a/go.mod +++ b/go.mod @@ -39,11 +39,11 @@ require ( github.com/whilp/git-urls v1.0.0 github.com/xanzy/ssh-agent v0.3.3 golang.org/x/crypto v0.5.0 - golang.org/x/net v0.5.0 + golang.org/x/net v0.6.0 golang.org/x/sync v0.1.0 golang.org/x/sys v0.5.0 golang.org/x/term v0.5.0 - golang.org/x/text v0.6.0 + golang.org/x/text v0.7.0 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 helm.sh/helm/v3 v3.10.3 diff --git a/go.sum b/go.sum index a8302d9d2..f4d6f2911 100644 --- a/go.sum +++ b/go.sum @@ -971,8 +971,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= -golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= +golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1090,8 +1090,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= -golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From da919e0417a9cc8e7110d9f3b1e8dac21e0fed5a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Feb 2023 14:02:44 +0100 Subject: [PATCH 1420/2916] chore(deps): Bump github.com/google/go-containerregistry (#306) Bumps [github.com/google/go-containerregistry](https://github.com/google/go-containerregistry) from 0.12.1 to 0.13.0. - [Release notes](https://github.com/google/go-containerregistry/releases) - [Changelog](https://github.com/google/go-containerregistry/blob/main/.goreleaser.yml) - [Commits](https://github.com/google/go-containerregistry/compare/v0.12.1...v0.13.0) --- updated-dependencies: - dependency-name: github.com/google/go-containerregistry dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a768ee877..eda6b6dc8 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/go-playground/validator/v10 v10.11.2 github.com/gobwas/glob v0.2.3 // indirect github.com/golang-jwt/jwt/v4 v4.4.3 - github.com/google/go-containerregistry v0.12.1 + github.com/google/go-containerregistry v0.13.0 github.com/hashicorp/vault/api v1.8.2 github.com/hexops/gotextdiff v1.0.3 github.com/imdario/mergo v0.3.13 diff --git a/go.sum b/go.sum index f4d6f2911..c5834f9a6 100644 --- a/go.sum +++ b/go.sum @@ -392,8 +392,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-containerregistry v0.12.1 h1:W1mzdNUTx4Zla4JaixCRLhORcR7G6KxE5hHl5fkPsp8= -github.com/google/go-containerregistry v0.12.1/go.mod h1:sdIK+oHQO7B93xI8UweYdl887YhuIwg9vz8BSLH3+8k= +github.com/google/go-containerregistry v0.13.0 h1:y1C7Z3e149OJbOPDBxLYR8ITPz8dTKqQwjErKVHJC8k= +github.com/google/go-containerregistry v0.13.0/go.mod h1:J9FQ+eSS4a1aC2GNZxvNpbWhgp0487v+cgiilB4FqDo= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= From 086645a5032bc4290254da13a9842f556a91935d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Feb 2023 14:04:41 +0100 Subject: [PATCH 1421/2916] chore(deps): Bump sigs.k8s.io/controller-runtime from 0.14.1 to 0.14.4 (#307) Bumps [sigs.k8s.io/controller-runtime](https://github.com/kubernetes-sigs/controller-runtime) from 0.14.1 to 0.14.4. - [Release notes](https://github.com/kubernetes-sigs/controller-runtime/releases) - [Changelog](https://github.com/kubernetes-sigs/controller-runtime/blob/master/RELEASE.md) - [Commits](https://github.com/kubernetes-sigs/controller-runtime/compare/v0.14.1...v0.14.4) --- updated-dependencies: - dependency-name: sigs.k8s.io/controller-runtime dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index eda6b6dc8..9ca767700 100644 --- a/go.mod +++ b/go.mod @@ -67,7 +67,7 @@ require ( github.com/onsi/gomega v1.26.0 github.com/otiai10/copy v1.9.0 go.mozilla.org/sops/v3 v3.7.4-0.20220901181616-9124783930b1 - sigs.k8s.io/controller-runtime v0.14.1 + sigs.k8s.io/controller-runtime v0.14.4 sigs.k8s.io/kustomize/api v0.12.1 sigs.k8s.io/yaml v1.3.0 ) diff --git a/go.sum b/go.sum index c5834f9a6..56a342515 100644 --- a/go.sum +++ b/go.sum @@ -1346,8 +1346,8 @@ oras.land/oras-go v1.2.1/go.mod h1:3N11Z5E3c4ZzOjroCl1RtAdB4yNAYl7A27j2SVf913A= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/controller-runtime v0.14.1 h1:vThDes9pzg0Y+UbCPY3Wj34CGIYPgdmspPm2GIpxpzM= -sigs.k8s.io/controller-runtime v0.14.1/go.mod h1:GaRkrY8a7UZF0kqFFbUKG7n9ICiTY5T55P1RiE3UZlU= +sigs.k8s.io/controller-runtime v0.14.4 h1:Kd/Qgx5pd2XUL08eOV2vwIq3L9GhIbJ5Nxengbd4/0M= +sigs.k8s.io/controller-runtime v0.14.4/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/kustomize/api v0.12.1 h1:7YM7gW3kYBwtKvoY216ZzY+8hM+lV53LUayghNRJ0vM= From 5f8a882f6d5970277d297e66d92299ed5e3bae59 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Feb 2023 14:41:24 +0100 Subject: [PATCH 1422/2916] chore(deps): Bump github.com/hashicorp/vault/api from 1.8.2 to 1.8.3 (#308) Bumps [github.com/hashicorp/vault/api](https://github.com/hashicorp/vault) from 1.8.2 to 1.8.3. - [Release notes](https://github.com/hashicorp/vault/releases) - [Changelog](https://github.com/hashicorp/vault/blob/main/CHANGELOG.md) - [Commits](https://github.com/hashicorp/vault/compare/v1.8.2...v1.8.3) --- updated-dependencies: - dependency-name: github.com/hashicorp/vault/api dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 9ca767700..984ecb0dc 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/gobwas/glob v0.2.3 // indirect github.com/golang-jwt/jwt/v4 v4.4.3 github.com/google/go-containerregistry v0.13.0 - github.com/hashicorp/vault/api v1.8.2 + github.com/hashicorp/vault/api v1.8.3 github.com/hexops/gotextdiff v1.0.3 github.com/imdario/mergo v0.3.13 github.com/jinzhu/copier v0.3.5 @@ -172,7 +172,7 @@ require ( github.com/hashicorp/go-version v1.6.0 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/hashicorp/vault/sdk v0.6.1 // indirect + github.com/hashicorp/vault/sdk v0.7.0 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect diff --git a/go.sum b/go.sum index 56a342515..931147457 100644 --- a/go.sum +++ b/go.sum @@ -493,10 +493,10 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hashicorp/vault/api v1.8.2 h1:C7OL9YtOtwQbTKI9ogB0A1wffRbCN+rH/LLCHO3d8HM= -github.com/hashicorp/vault/api v1.8.2/go.mod h1:ML8aYzBIhY5m1MD1B2Q0JV89cC85YVH4t5kBaZiyVaE= -github.com/hashicorp/vault/sdk v0.6.1 h1:sjZC1z4j5Rh2GXYbkxn5BLK05S1p7+MhW4AgdUmgRUA= -github.com/hashicorp/vault/sdk v0.6.1/go.mod h1:Ck4JuAC6usTphfrrRJCRH+7/N7O2ozZzkm/fzQFt4uM= +github.com/hashicorp/vault/api v1.8.3 h1:cHQOLcMhBR+aVI0HzhPxO62w2+gJhIrKguQNONPzu6o= +github.com/hashicorp/vault/api v1.8.3/go.mod h1:4g/9lj9lmuJQMtT6CmVMHC5FW1yENaVv+Nv4ZfG8fAg= +github.com/hashicorp/vault/sdk v0.7.0 h1:2pQRO40R1etpKkia5fb4kjrdYMx3BHklPxl1pxpxDHg= +github.com/hashicorp/vault/sdk v0.7.0/go.mod h1:KyfArJkhooyba7gYCKSq8v66QdqJmnbAxtV/OX1+JTs= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= From 93853f1132872210c4d7e2e690f46a50ef15953f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 8 Feb 2023 17:42:50 +0100 Subject: [PATCH 1423/2916] tests: Fix pruning tests --- e2e/inclusion_test.go | 3 ++- e2e/test-utils/project.go | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/e2e/inclusion_test.go b/e2e/inclusion_test.go index 84b427c97..fee93caec 100644 --- a/e2e/inclusion_test.go +++ b/e2e/inclusion_test.go @@ -2,6 +2,7 @@ package e2e import ( "github.com/kluctl/kluctl/v2/e2e/test-utils" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" corev1 "k8s.io/api/core/v1" "path/filepath" "reflect" @@ -14,7 +15,7 @@ func prepareInclusionTestProject(t *testing.T, withIncludes bool) (*test_utils.T createNamespace(t, k, p.TestSlug()) - p.UpdateTarget("test", nil) + p.UpdateTarget("test", func(target *uo.UnstructuredObject) {}) addConfigMapDeployment(p, "cm1", nil, resourceOpts{name: "cm1", namespace: p.TestSlug()}) addConfigMapDeployment(p, "cm2", nil, resourceOpts{name: "cm2", namespace: p.TestSlug()}) diff --git a/e2e/test-utils/project.go b/e2e/test-utils/project.go index 63f918dea..74b64f2e2 100644 --- a/e2e/test-utils/project.go +++ b/e2e/test-utils/project.go @@ -14,6 +14,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" registry2 "helm.sh/helm/v3/pkg/registry" + "k8s.io/apimachinery/pkg/util/rand" "net/url" "os" "os/exec" @@ -78,6 +79,7 @@ func NewTestProject(t *testing.T, opts ...TestProjectOption) *TestProject { p.gitServer.GitInit(p.gitRepoName) p.UpdateKluctlYaml(func(o *uo.UnstructuredObject) error { + _ = o.SetNestedField(fmt.Sprintf("%s-{{ target.name }}", rand.String(16)), "discriminator") return nil }) p.UpdateDeploymentYaml(".", func(c *uo.UnstructuredObject) error { From a21906e502cab23323b39a1c9971316435e9e945 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 8 Feb 2023 20:12:24 +0100 Subject: [PATCH 1424/2916] docs: Run make replace-commands-help --- docs/reference/commands/delete.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/commands/delete.md b/docs/reference/commands/delete.md index f75f0f77c..e8bb56368 100644 --- a/docs/reference/commands/delete.md +++ b/docs/reference/commands/delete.md @@ -34,7 +34,7 @@ In addition, the following arguments are available: Misc arguments: Command specific arguments. - -l, --delete-by-label stringArray Override the labels used to find objects for deletion. + --discriminator string Override the discriminator used to find objects for deletion. --dry-run Performs all kubernetes API calls in dry-run mode. --helm-insecure-skip-tls-verify stringArray Controls skipping of TLS verification. Must be in the form --helm-insecure-skip-tls-verify=, where From 653454c7ec90cbe800928b7fddfbecc05d67741a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 9 Feb 2023 14:55:34 +0100 Subject: [PATCH 1425/2916] docs: Add documentation about discriminators --- docs/reference/kluctl-project/README.md | 9 +++++++ .../kluctl-project/targets/README.md | 25 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/docs/reference/kluctl-project/README.md b/docs/reference/kluctl-project/README.md index e48f33f47..c42783829 100644 --- a/docs/reference/kluctl-project/README.md +++ b/docs/reference/kluctl-project/README.md @@ -19,6 +19,8 @@ available to invoke [commands](../commands) on. An example .kluctl.yaml looks like this: ```yaml +discriminator: "my-project-{{ target.name }}" + targets: # test cluster, dev env - name: dev @@ -42,6 +44,13 @@ args: ## Allowed fields +### discriminator + +Specifies a default discriminator template to be used for targets that don't have +their own discriminator specified. + +See [target discriminator](./targets/README.md#discriminator) for details. + ### targets Please check the [targets](./targets) sub-section for details. diff --git a/docs/reference/kluctl-project/targets/README.md b/docs/reference/kluctl-project/targets/README.md index 4dc644fc1..d3fea1d5f 100644 --- a/docs/reference/kluctl-project/targets/README.md +++ b/docs/reference/kluctl-project/targets/README.md @@ -32,6 +32,7 @@ targets: images: - image: my-image resultImage: my-image:1.2.3 + discriminator: "my-project-{{ target.name }}" ... ``` @@ -58,3 +59,27 @@ The format is identical to the [fixed images file](../../deployments/images.md#c The fixed images specified in the [dynamic target config](../../kluctl-project/targets/dynamic-targets.md#images) have higher priority. + +## discriminator + +Specifies a discriminator which is used to uniquely identify all deployed objects on the cluster. It is added to all +objects as the value of the `kluctl.io/discriminator` label. This label is then later used to identify all objects +belonging to the deployment project and target, so that Kluctl can determine which objects got orphaned and need to +be pruned. The discriminator is also used to identify all objects that need to be deleted when +[kluctl delete](../../commands/delete.md) is called. + +If no discriminator is set for a target, [kluctl prune](../../commands/prune.md) and +[kluctl delete](../../commands/delete.md) are not supported. + +The discriminator can be a [template](../../templating/README.md) which is rendered at project loading time. While +rendering, only the `target` and `args` are available as global variables in the templating context. + +The rendered discriminator should be unique on the target cluster to avoid mis-identification of objects from other +deployments or targets. It's good practice to prefix the discriminator with a project name and at least use the target +name to make it unique. Example discriminator to achieve this: `my-project-name-{{ target.name }}`. + +If a target is meant to be deployed multiple times, e.g. by using external [arguments](../README.md#args), the external +arguments should be taken into account as well. Example: `my-project-name-{{ target.name }}-{{ args.environment_name }}`. + +A [default discriminator](../../kluctl-project/README.md#discriminator) can also be specified which is used whenever +a target has no discriminator configured. From 71c5825fa1530b483fb913ae89731d158c6ae468 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Feb 2023 08:08:57 +0100 Subject: [PATCH 1426/2916] chore(deps): Bump golang.org/x/crypto from 0.5.0 to 0.6.0 (#310) Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.5.0 to 0.6.0. - [Release notes](https://github.com/golang/crypto/releases) - [Commits](https://github.com/golang/crypto/compare/v0.5.0...v0.6.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 984ecb0dc..73a6870d8 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( github.com/stretchr/testify v1.8.1 github.com/whilp/git-urls v1.0.0 github.com/xanzy/ssh-agent v0.3.3 - golang.org/x/crypto v0.5.0 + golang.org/x/crypto v0.6.0 golang.org/x/net v0.6.0 golang.org/x/sync v0.1.0 golang.org/x/sys v0.5.0 diff --git a/go.sum b/go.sum index 931147457..555d4eddd 100644 --- a/go.sum +++ b/go.sum @@ -886,8 +886,8 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= -golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= +golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= From 7b0191038d4ba723fe8081e49e8fa64343668548 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Feb 2023 08:11:26 +0100 Subject: [PATCH 1427/2916] chore(deps): Bump helm.sh/helm/v3 from 3.10.3 to 3.11.1 (#311) Bumps [helm.sh/helm/v3](https://github.com/helm/helm) from 3.10.3 to 3.11.1. - [Release notes](https://github.com/helm/helm/releases) - [Commits](https://github.com/helm/helm/compare/v3.10.3...v3.11.1) --- updated-dependencies: - dependency-name: helm.sh/helm/v3 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 7 +++---- go.sum | 21 ++++++++------------- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/go.mod b/go.mod index 73a6870d8..7963c49ef 100644 --- a/go.mod +++ b/go.mod @@ -46,7 +46,7 @@ require ( golang.org/x/text v0.7.0 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 - helm.sh/helm/v3 v3.10.3 + helm.sh/helm/v3 v3.11.1 k8s.io/api v0.26.1 k8s.io/apiextensions-apiserver v0.26.1 k8s.io/apimachinery v0.26.1 @@ -112,7 +112,7 @@ require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect github.com/cloudflare/circl v1.3.0 // indirect - github.com/containerd/containerd v1.6.13 // indirect + github.com/containerd/containerd v1.6.15 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/docker/cli v20.10.21+incompatible // indirect @@ -232,7 +232,6 @@ require ( github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xlab/treeprint v1.1.0 // indirect - go.etcd.io/etcd/api/v3 v3.5.6 // indirect go.mozilla.org/gopgagent v0.0.0-20170926210634-4d7ea76ff71a // indirect go.opencensus.io v0.24.0 // indirect go.starlark.net v0.0.0-20221205180719-3fd0dac74452 // indirect @@ -258,6 +257,6 @@ require ( k8s.io/kube-openapi v0.0.0-20221207184640-f3cff1453715 // indirect k8s.io/kubectl v0.26.0 // indirect k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 // indirect - oras.land/oras-go v1.2.1 // indirect + oras.land/oras-go v1.2.2 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect ) diff --git a/go.sum b/go.sum index 555d4eddd..a2bfbdd9b 100644 --- a/go.sum +++ b/go.sum @@ -189,10 +189,9 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/containerd/cgroups v1.0.3 h1:ADZftAkglvCiD44c77s5YmMqaP2pzVCFZvBmAlBdAP4= -github.com/containerd/containerd v1.6.13 h1:7llWEzjLH/fao0f2lppn1L6NhjsvxqMdUQa2mgVCs+U= -github.com/containerd/containerd v1.6.13/go.mod h1:vDm+BbU+dD9uvuUlHr+KmcY0HKX8WDyI6dzJjNi4ee8= +github.com/containerd/cgroups v1.0.4 h1:jN/mbWBEaz+T1pi5OFtnkQ+8qnmEbAr1Oo1FRm5B0dA= +github.com/containerd/containerd v1.6.15 h1:4wWexxzLNHNE46aIETc6ge4TofO550v+BlLoANrbses= +github.com/containerd/containerd v1.6.15/go.mod h1:U2NnBPIhzJDm59xF7xB2MMHnKtggpZ+phKg8o2TKj2c= github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= github.com/containerd/stargz-snapshotter/estargz v0.12.1 h1:+7nYmHJb0tEkcRaAW+MHqoKaJYZmkikupxCqVtmPuY0= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -209,7 +208,7 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= -github.com/distribution/distribution/v3 v3.0.0-20220526142353-ffbd94cbe269 h1:hbCT8ZPPMqefiAWD2ZKjn7ypokIGViTvBBg/ExLSdCk= +github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 h1:aBfCb7iqHmDEIp6fBvC/hQUddQfg+3qdYjwzaiP9Hnc= github.com/dnaeon/go-vcr v1.1.0 h1:ReYa/UBrRyQdant9B4fNHGoCNKw6qh6P0fsdGmZpR7c= github.com/docker/cli v20.10.21+incompatible h1:qVkgyYUnOLQ98LtXBrwd/duVqPT2X4SHndOuGsfwyhU= github.com/docker/cli v20.10.21+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= @@ -242,7 +241,6 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= -github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= @@ -840,8 +838,6 @@ github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMzt github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= -go.etcd.io/etcd/api/v3 v3.5.6 h1:Cy2qx3npLcYqTKqGJzMypnMv2tiRyifZJ17BlWIWA7A= -go.etcd.io/etcd/api/v3 v3.5.6/go.mod h1:KFtNaxGDw4Yx/BA4iPPwevUTAuqcsPxzyX8PHydchN8= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.mozilla.org/gopgagent v0.0.0-20170926210634-4d7ea76ff71a h1:N7VD+PwpJME2ZfQT8+ejxwA4Ow10IkGbU0MGf94ll8k= @@ -1258,7 +1254,6 @@ google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= google.golang.org/grpc v1.52.0 h1:kd48UiU7EHsV4rnLyOJRuP/Il/UHE7gdDAQ+SZI7nZk= google.golang.org/grpc v1.52.0/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -1310,8 +1305,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.2.0 h1:I0DwBVMGAx26dttAj1BtJLAkVGncrkkUXfJLC4Flt/I= gotest.tools/v3 v3.2.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= -helm.sh/helm/v3 v3.10.3 h1:wL7IUZ7Zyukm5Kz0OUmIFZgKHuAgByCrUcJBtY0kDyw= -helm.sh/helm/v3 v3.10.3/go.mod h1:CXOcs02AYvrlPMWARNYNRgf2rNP7gLJQsi/Ubd4EDrI= +helm.sh/helm/v3 v3.11.1 h1:cmL9fFohOoNQf+wnp2Wa0OhNFH0KFnSzEkVxi3fcc3I= +helm.sh/helm/v3 v3.11.1/go.mod h1:z/Bu/BylToGno/6dtNGuSmjRqxKq5gaH+FU0BPO+AQ8= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1341,8 +1336,8 @@ k8s.io/kubectl v0.26.0 h1:xmrzoKR9CyNdzxBmXV7jW9Ln8WMrwRK6hGbbf69o4T0= k8s.io/kubectl v0.26.0/go.mod h1:eInP0b+U9XUJWSYeU9XZnTA+cVYuWyl3iYPGtru0qhQ= k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 h1:KTgPnR10d5zhztWptI952TNtt/4u5h3IzDXkdIMuo2Y= k8s.io/utils v0.0.0-20221128185143-99ec85e7a448/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -oras.land/oras-go v1.2.1 h1:/VcGS8FUy3eEXLl/1vC4QypLHwrfSmgW7ygsoklqKK8= -oras.land/oras-go v1.2.1/go.mod h1:3N11Z5E3c4ZzOjroCl1RtAdB4yNAYl7A27j2SVf913A= +oras.land/oras-go v1.2.2 h1:0E9tOHUfrNH7TCDk5KU0jVBEzCqbfdyuVfGmJ7ZeRPE= +oras.land/oras-go v1.2.2/go.mod h1:Apa81sKoZPpP7CDciE006tSZ0x3Q3+dOoBcMZ/aNxvw= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= From 01acac9e893ce05f9341afebf4b844e1b9083c5e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 10 Feb 2023 15:53:33 +0100 Subject: [PATCH 1428/2916] feat: Implement skipPrePull for helm-chart.yaml (#289) * refactor: Move pull code into doHelmPull and allow for dryRuns * refactor: Simplify helm-update implementation by re-using helm-pull internally * feat: Implement skipPrePull for helm-chart.yaml * fix: Limit number of parallel Helm renders * tests: Fix helm tests * docs: Update documentation about skipPrePull --- cmd/kluctl/commands/cmd_helm_pull.go | 65 ++++++--- cmd/kluctl/commands/cmd_helm_update.go | 180 ++++++++++++------------ cmd/kluctl/commands/root.go | 2 +- docs/reference/commands/helm-pull.md | 8 +- docs/reference/deployments/helm.md | 7 +- e2e/helm_test.go | 111 ++++++++++++++- e2e/sops_test.go | 4 + pkg/deployment/deployment_collection.go | 2 +- pkg/helm/helm_release.go | 5 + pkg/types/helm_chart.go | 1 + 10 files changed, 260 insertions(+), 125 deletions(-) diff --git a/cmd/kluctl/commands/cmd_helm_pull.go b/cmd/kluctl/commands/cmd_helm_pull.go index ae0bf7bfd..7bfc1f42a 100644 --- a/cmd/kluctl/commands/cmd_helm_pull.go +++ b/cmd/kluctl/commands/cmd_helm_pull.go @@ -19,9 +19,9 @@ type helmPullCmd struct { } func (cmd *helmPullCmd) Help() string { - return `The Helm charts are stored under the sub-directory 'charts/' next to the -'helm-chart.yaml'. These Helm charts are meant to be added to version control so that -pulling is only needed when really required (e.g. when the chart version changes).` + return `Kluctl requires Helm Charts to be pre-pulled by default, which is handled by this command. It will collect +all required Charts and versions and pre-pull them into .helm-charts. To disable pre-pulling for individual charts, +set 'skipPrePull: true' in helm-chart.yaml.` } func (cmd *helmPullCmd) Run(ctx context.Context) error { @@ -33,24 +33,33 @@ func (cmd *helmPullCmd) Run(ctx context.Context) error { if !yaml.Exists(filepath.Join(projectDir, ".kluctl.yaml")) { return fmt.Errorf("helm-pull can only be used on the root of a Kluctl project that must have a .kluctl.yaml file") } + _, err = doHelmPull(ctx, projectDir, &cmd.HelmCredentials, false, true) + return err +} + +func doHelmPull(ctx context.Context, projectDir string, helmCredentials *args.HelmCredentials, dryRun bool, force bool) (int, error) { + actions := 0 baseChartsDir := filepath.Join(projectDir, ".helm-charts") - releases, charts, err := loadHelmReleases(projectDir, baseChartsDir, &cmd.HelmCredentials) + releases, charts, err := loadHelmReleases(projectDir, baseChartsDir, helmCredentials) if err != nil { - return err + return actions, err } for _, hr := range releases { if utils.Exists(hr.GetDeprecatedChartDir()) { + actions++ rel, err := filepath.Rel(projectDir, hr.GetDeprecatedChartDir()) if err != nil { - return err + return actions, err } - status.Info(ctx, "Removing deprecated charts dir %s", rel) - err = os.RemoveAll(hr.GetDeprecatedChartDir()) - if err != nil { - return err + if !dryRun { + status.Info(ctx, "Removing deprecated charts dir %s", rel) + err = os.RemoveAll(hr.GetDeprecatedChartDir()) + if err != nil { + return actions, err + } } } } @@ -63,34 +72,50 @@ func (cmd *helmPullCmd) Run(ctx context.Context) error { versionsToPull := map[string]bool{} for _, hr := range releases { + if hr.Config.SkipPrePull { + continue + } if hr.Chart == chart { versionsToPull[hr.Config.ChartVersion] = true } } - cleanupDir, err := chart.BuildPulledChartDir(baseChartsDir, "") + chartsDir, err := chart.BuildPulledChartDir(baseChartsDir, "") if err != nil { - return err + return actions, err } - des, err := os.ReadDir(cleanupDir) + des, err := os.ReadDir(chartsDir) if err != nil && !os.IsNotExist(err) { - return err + return actions, err } for _, de := range des { if !de.IsDir() { continue } if _, ok := versionsToPull[de.Name()]; !ok { - status.Info(ctx, "Removing unused Chart with version %s", de.Name()) - err = os.RemoveAll(filepath.Join(cleanupDir, de.Name())) - if err != nil { - return err + actions++ + if !dryRun { + status.Info(ctx, "Removing unused Chart with version %s", de.Name()) + err = os.RemoveAll(filepath.Join(chartsDir, de.Name())) + if err != nil { + return actions, err + } } } } for version, _ := range versionsToPull { version := version + + if yaml.Exists(filepath.Join(chartsDir, version, "Chart.yaml")) && !force { + continue + } + + actions++ + + if dryRun { + continue + } g.RunE(func() error { s := status.Start(ctx, "%s: Pulling Chart with version %s", statusPrefix, version) defer s.Failed() @@ -109,10 +134,10 @@ func (cmd *helmPullCmd) Run(ctx context.Context) error { g.Wait() if g.ErrorOrNil() != nil { - return fmt.Errorf("command failed") + return actions, fmt.Errorf("command failed") } - return nil + return actions, nil } func loadHelmReleases(projectDir string, baseChartsDir string, credentialsProvider helm.HelmCredentialsProvider) ([]*helm.Release, []*helm.Chart, error) { diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go index a2df12433..d079ec1b8 100644 --- a/cmd/kluctl/commands/cmd_helm_update.go +++ b/cmd/kluctl/commands/cmd_helm_update.go @@ -3,8 +3,8 @@ package commands import ( "context" "fmt" - "github.com/fluxcd/go-git/v5" "github.com/fluxcd/go-git/v5/plumbing/format/index" + "github.com/go-git/go-git/v5" "github.com/kluctl/kluctl/v2/cmd/kluctl/args" git2 "github.com/kluctl/kluctl/v2/pkg/git" "github.com/kluctl/kluctl/v2/pkg/helm" @@ -14,6 +14,7 @@ import ( "io/fs" "os" "path/filepath" + "strings" ) type helmUpdateCmd struct { @@ -58,7 +59,10 @@ func (cmd *helmUpdateCmd) Run(ctx context.Context) error { if err != nil { return err } - for _, s := range gitStatus { + for pth, s := range gitStatus { + if strings.HasPrefix(pth, ".helm-charts/") { + return fmt.Errorf("--commit can only be used when .helm-chart directory is clean") + } if (s.Staging != git.Untracked && s.Staging != git.Unmodified) || (s.Worktree != git.Untracked && s.Worktree != git.Unmodified) { return fmt.Errorf("--commit can only be used when the git worktree is unmodified") } @@ -86,6 +90,16 @@ func (cmd *helmUpdateCmd) Run(ctx context.Context) error { } } + if cmd.Commit { + actions, err := doHelmPull(ctx, projectDir, &cmd.HelmCredentials, true, false) + if err != nil { + return err + } + if actions != 0 { + return fmt.Errorf(".helm-charts is not up-to-date. Please run helm-pull before") + } + } + for _, chart := range charts { chart := chart g.RunE(func() error { @@ -136,16 +150,14 @@ func (cmd *helmUpdateCmd) Run(ctx context.Context) error { return g.ErrorOrNil() } - versionUseCounts := map[string]map[string]int{} + upgrades := map[helmUpgradeKey][]*helm.Release{} + for _, hr := range releases { - key := fmt.Sprintf("%s / %s", hr.Chart.GetRepo(), hr.Chart.GetChartName()) - if _, ok := versionUseCounts[key]; !ok { - versionUseCounts[key] = map[string]int{} + cd, err := hr.Chart.BuildPulledChartDir(baseChartsDir, "") + if err != nil { + return err } - versionUseCounts[key][hr.Config.ChartVersion]++ - } - for _, hr := range releases { relDir, err := filepath.Rel(projectDir, filepath.Dir(hr.ConfigFile)) if err != nil { return err @@ -183,24 +195,18 @@ func (cmd *helmUpdateCmd) Run(ctx context.Context) error { if err != nil { return err } - status.Info(ctx, "%s: Updated Chart version to %s", relDir, latestVersion) - key := fmt.Sprintf("%s / %s", hr.Chart.GetRepo(), hr.Chart.GetChartName()) - uv := versionUseCounts[key] - uv[oldVersion]-- - uv[latestVersion]++ - - pullChart := false - deleteChart := false - if uv[latestVersion] == 1 { - pullChart = true - } - if uv[oldVersion] == 0 { - deleteChart = true + k := helmUpgradeKey{ + chartDir: cd, + oldVersion: oldVersion, + newVersion: latestVersion, } + upgrades[k] = append(upgrades[k], hr) + } - err = cmd.pullAndCommitCharts(ctx, projectDir, baseChartsDir, gitRootPath, hr, oldVersion, latestVersion, pullChart, deleteChart) + for k, hrs := range upgrades { + err = cmd.pullAndCommit(ctx, projectDir, baseChartsDir, gitRootPath, hrs, k.oldVersion) if err != nil { return err } @@ -209,7 +215,7 @@ func (cmd *helmUpdateCmd) Run(ctx context.Context) error { return nil } -func (cmd *helmUpdateCmd) collectFiles(root string, dir string, m map[string]bool) error { +func (cmd *helmUpdateCmd) collectFiles(root string, dir string, m map[string]os.FileInfo) error { err := filepath.WalkDir(dir, func(p string, d fs.DirEntry, err error) error { if d == nil || d.IsDir() { return nil @@ -221,7 +227,7 @@ func (cmd *helmUpdateCmd) collectFiles(root string, dir string, m map[string]boo if _, ok := m[relToGit]; ok { return nil } - m[relToGit] = true + m[relToGit], _ = d.Info() return nil }) if os.IsNotExist(err) { @@ -230,17 +236,15 @@ func (cmd *helmUpdateCmd) collectFiles(root string, dir string, m map[string]boo return err } -func (cmd *helmUpdateCmd) pullAndCommitCharts(ctx context.Context, projectDir string, baseChartsDir string, gitRootPath string, hr *helm.Release, oldVersion string, newVersion string, pullChart bool, deleteChart bool) error { - relDir, err := filepath.Rel(projectDir, filepath.Dir(hr.ConfigFile)) - if err != nil { - return err - } +func (cmd *helmUpdateCmd) pullAndCommit(ctx context.Context, projectDir string, baseChartsDir string, gitRootPath string, hrs []*helm.Release, oldVersion string) error { + chart := hrs[0].Chart + newVersion := hrs[0].Config.ChartVersion - s := status.Start(ctx, "%s: Upgrading Chart %s to version %s", relDir, hr.Chart.GetChartName(), newVersion) + s := status.Start(ctx, "Upgrading Chart %s from version %s to %s", chart.GetChartName(), oldVersion, newVersion) defer s.Failed() doError := func(err error) error { - s.FailedWithMessage("%s: %s", relDir, err.Error()) + s.FailedWithMessage("%s", err.Error()) return err } @@ -254,94 +258,86 @@ func (cmd *helmUpdateCmd) pullAndCommitCharts(ctx context.Context, projectDir st } if cmd.Commit { - // add helm-chart.yaml - relToGit, err := filepath.Rel(gitRootPath, hr.ConfigFile) - if err != nil { - return doError(err) - } - _, err = wt.Add(relToGit) - if err != nil { - return doError(err) - } - } - - if deleteChart { - chartDir, err := hr.Chart.BuildPulledChartDir(baseChartsDir, oldVersion) - if err != nil { - return doError(err) - } - relChartDir, err := filepath.Rel(gitRootPath, chartDir) - if err != nil { - return doError(err) - } - if cmd.Commit { - _, err = wt.Remove(relChartDir) - if err != nil && err != index.ErrEntryNotFound { + for _, hr := range hrs { + // add helm-chart.yaml + relToGit, err := filepath.Rel(gitRootPath, hr.ConfigFile) + if err != nil { + return doError(err) + } + _, err = wt.Add(relToGit) + if err != nil { return doError(err) } - } - err = os.RemoveAll(chartDir) - if err != nil { - return doError(err) } } - if pullChart { - chartDir, err := hr.Chart.BuildPulledChartDir(baseChartsDir, newVersion) - if err != nil { - return doError(err) - } - relChartDir, err := filepath.Rel(gitRootPath, chartDir) + // we need to list all files contained inside the charts dir BEFORE doing the pull, so that we later + // know what got deleted + files := map[string]os.FileInfo{} + if cmd.Commit { + err = cmd.collectFiles(gitRootPath, baseChartsDir, files) if err != nil { return doError(err) } + } - // we need to list all files contained inside the charts dir BEFORE doing the pull, so that we later - // know what got deleted - files := map[string]bool{} - err = cmd.collectFiles(gitRootPath, chartDir, files) - if err != nil { - return doError(err) - } + _, err = doHelmPull(ctx, projectDir, &cmd.HelmCredentials, false, false) + if err != nil { + return doError(err) + } - _, err = hr.Chart.PullInProject(ctx, baseChartsDir, newVersion) + if cmd.Commit { + files2 := map[string]os.FileInfo{} + err = cmd.collectFiles(gitRootPath, baseChartsDir, files2) if err != nil { return doError(err) } - if cmd.Commit { - _, err = wt.Add(relChartDir) - if err != nil { - return doError(err) + for pth, st1 := range files { + st2, ok := files2[pth] + if !ok || st1.Mode() != st2.Mode() || st1.ModTime() != st2.ModTime() || st1.Size() != st2.Size() { + // removed or modified + if ok { + if !st2.IsDir() { + _, err = wt.Add(pth) + } + } else { + if !st1.IsDir() { + _, err = wt.Remove(pth) + } + } + if err != nil && err != index.ErrEntryNotFound { + return doError(err) + } } - - // figure out what got deleted - for p := range files { - _, err := os.Lstat(filepath.Join(gitRootPath, p)) - if err != nil { - if os.IsNotExist(err) { - _, err = wt.Remove(p) - if err != nil { - return doError(err) - } - } else { + } + for pth, st1 := range files2 { + if _, ok := files[pth]; !ok { + if !st1.IsDir() { + // added + _, err = wt.Add(pth) + if err != nil && err != index.ErrEntryNotFound { return doError(err) } } } } - } - if cmd.Commit { - commitMsg := fmt.Sprintf("Updated helm chart %s in %s to version %s", hr.Chart.GetChartName(), relDir, newVersion) + commitMsg := fmt.Sprintf("Updated helm chart %s from version %s to version %s", chart.GetChartName(), oldVersion, newVersion) _, err = wt.Commit(commitMsg, &git.CommitOptions{}) if err != nil { return doError(fmt.Errorf("failed to commit: %w", err)) } - s.UpdateAndInfoFallback("%s: Committed helm chart %s with version %s", relDir, hr.Chart.GetChartName(), newVersion) + s.UpdateAndInfoFallback("Committed helm chart %s with version %s", chart.GetChartName(), newVersion) } s.Success() return nil } + +type helmUpgradeKey struct { + chartDir string + oldVersion string + newVersion string +} diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index fe314bba0..64e3e772f 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -58,7 +58,7 @@ type cli struct { Delete deleteCmd `cmd:"" help:"Delete a target (or parts of it) from the corresponding cluster"` Deploy deployCmd `cmd:"" help:"Deploys a target to the corresponding cluster"` Diff diffCmd `cmd:"" help:"Perform a diff between the locally rendered target and the already deployed target"` - HelmPull helmPullCmd `cmd:"" help:"Recursively searches for 'helm-chart.yaml' files and pulls the specified Helm charts"` + HelmPull helmPullCmd `cmd:"" help:"Recursively searches for 'helm-chart.yaml' files and pre-pulls the specified Helm charts"` HelmUpdate helmUpdateCmd `cmd:"" help:"Recursively searches for 'helm-chart.yaml' files and checks for new available versions"` ListImages listImagesCmd `cmd:"" help:"Renders the target and outputs all images used via 'images.get_image(...)"` ListTargets listTargetsCmd `cmd:"" help:"Outputs a yaml list with all target, including dynamic targets"` diff --git a/docs/reference/commands/helm-pull.md b/docs/reference/commands/helm-pull.md index 8cc7ee552..c8c5c8ce9 100644 --- a/docs/reference/commands/helm-pull.md +++ b/docs/reference/commands/helm-pull.md @@ -13,10 +13,10 @@ description: > Usage: kluctl helm-pull [flags] -Recursively searches for 'helm-chart.yaml' files and pulls the specified Helm charts -The Helm charts are stored under the sub-directory 'charts/' next to the -'helm-chart.yaml'. These Helm charts are meant to be added to version control so that -pulling is only needed when really required (e.g. when the chart version changes). +Recursively searches for 'helm-chart.yaml' files and pre-pulls the specified Helm charts +Kluctl requires Helm Charts to be pre-pulled by default, which is handled by this command. It will collect +all required Charts and versions and pre-pull them into .helm-charts. To disable pre-pulling for individual charts, +set 'skipPrePull: true' in helm-chart.yaml. diff --git a/docs/reference/deployments/helm.md b/docs/reference/deployments/helm.md index 665f3ef51..64116a4fe 100644 --- a/docs/reference/deployments/helm.md +++ b/docs/reference/deployments/helm.md @@ -70,6 +70,7 @@ helmChart: chartVersion: 12.1.1 updateConstraints: ~12.1.0 skipUpdate: false + skipPrePull: false releaseName: redis-cache namespace: "{{ my.jinja2.var }}" output: helm-rendered.yaml # this is optional @@ -120,9 +121,13 @@ If omitted, Kluctl will filter out pre-releases by default. Use a `updateConstra pre-releases. ### skipUpdate -Skip this Helm Chart when the [helm-update](../commands/helm-update.md) command is called. +If set to `true`, skip this Helm Chart when the [helm-update](../commands/helm-update.md) command is called. If omitted, defaults to `false`. +### skipPrePull +If set to `true`, skip pre-pulling of this Helm Chart when running [helm-pull](../commands/helm-pull.md). This will +also enable pulling on-demand when the deployment project is rendered/deployed. + ### releaseName The name of the Helm Release. diff --git a/e2e/helm_test.go b/e2e/helm_test.go index 6993804de..848645507 100644 --- a/e2e/helm_test.go +++ b/e2e/helm_test.go @@ -76,6 +76,11 @@ func testHelmPull(t *testing.T, tc testCase, prePull bool) { assert.NoError(t, err) assert.FileExists(t, getChartFile(t, p, repoUrl, "test-chart1", "0.1.0")) } + } else { + p.UpdateYaml("helm1/helm-chart.yaml", func(o *uo.UnstructuredObject) error { + _ = o.SetNestedField(true, "helmChart", "skipPrePull") + return nil + }, "") } args := []string{"deploy", "--yes", "-t", "test"} @@ -198,6 +203,12 @@ func testHelmUpdate(t *testing.T, oci bool, upgrade bool, commit bool) { assert.FileExists(t, getChartFile(t, p, repoUrl, "test-chart1", "0.1.0")) assert.FileExists(t, getChartFile(t, p, repoUrl, "test-chart2", "0.1.0")) + if commit { + wt, _ := p.GetGitRepo().Worktree() + _, _ = wt.Add(".helm-charts") + _, _ = wt.Commit(".helm-charts", &git.CommitOptions{}) + } + args := []string{"helm-update"} if upgrade { args = append(args, "--upgrade") @@ -212,12 +223,12 @@ func testHelmUpdate(t *testing.T, oci bool, upgrade bool, commit bool) { assert.Contains(t, stderr, "helm3: Skipped update to version 0.2.0") if upgrade { - assert.Contains(t, stderr, "helm1: Upgrading Chart test-chart1 to version 0.2.0") - assert.Contains(t, stderr, "helm2: Upgrading Chart test-chart2 to version 0.3.0") + assert.Contains(t, stderr, "Upgrading Chart test-chart1 from version 0.1.0 to 0.2.0") + assert.Contains(t, stderr, "Upgrading Chart test-chart2 from version 0.1.0 to 0.3.0") } if commit { - assert.Contains(t, stderr, "helm1: Committed helm chart test-chart1 with version 0.2.0") - assert.Contains(t, stderr, "helm2: Committed helm chart test-chart2 with version 0.3.0") + assert.Contains(t, stderr, "Committed helm chart test-chart1 with version 0.2.0") + assert.Contains(t, stderr, "Committed helm chart test-chart2 with version 0.3.0") } pulledVersions1 := listChartVersions(t, p, repoUrl, "test-chart1") @@ -248,8 +259,8 @@ func testHelmUpdate(t *testing.T, oci bool, upgrade bool, commit bool) { return commitList[i].Message < commitList[j].Message }) - assert.Equal(t, "Updated helm chart test-chart1 in helm1 to version 0.2.0", commitList[0].Message) - assert.Equal(t, "Updated helm chart test-chart2 in helm2 to version 0.3.0", commitList[1].Message) + assert.Equal(t, "Updated helm chart test-chart1 from version 0.1.0 to version 0.2.0", commitList[0].Message) + assert.Equal(t, "Updated helm chart test-chart2 from version 0.1.0 to version 0.3.0", commitList[1].Message) } } @@ -451,6 +462,10 @@ func TestHelmRenderOfflineKubernetes(t *testing.T) { p.UpdateTarget("test", nil) p.AddHelmDeployment("helm1", repoUrl, "test-chart1", "0.1.0", "test-helm1", p.TestSlug(), nil) + p.UpdateYaml("helm1/helm-chart.yaml", func(o *uo.UnstructuredObject) error { + _ = o.SetNestedField(true, "helmChart", "skipPrePull") + return nil + }, "") stdout, _ := p.KluctlMust("render", "--print-all", "--offline-kubernetes", "-t", "test") cm1 := uo.FromStringMust(stdout) @@ -498,6 +513,90 @@ func TestHelmLocalChart(t *testing.T) { assert.NotContains(t, stderr, "test-chart2") } +func TestHelmSkipPrePull(t *testing.T) { + t.Parallel() + + k := defaultCluster1 + + p := test_utils.NewTestProject(t) + + createNamespace(t, k, p.TestSlug()) + + repoUrl := createHelmOrOciRepo(t, []test_utils.RepoChart{ + {ChartName: "test-chart1", Version: "0.1.0"}, + {ChartName: "test-chart1", Version: "0.1.1"}, + {ChartName: "test-chart1", Version: "0.2.0"}, + }, false, "", "") + u, _ := url.Parse(repoUrl) + + p.UpdateTarget("test", nil) + p.AddHelmDeployment("helm1", repoUrl, "test-chart1", "0.1.0", "test-helm1", p.TestSlug(), nil) + p.AddHelmDeployment("helm2", repoUrl, "test-chart1", "0.1.1", "test-helm2", p.TestSlug(), nil) + + p.UpdateYaml("helm2/helm-chart.yaml", func(o *uo.UnstructuredObject) error { + _ = o.SetNestedField(true, "helmChart", "skipPrePull") + return nil + }, "") + + args := []string{"helm-pull"} + + _, stderr := p.KluctlMust(args...) + assert.Contains(t, stderr, "Pulling Chart with version 0.1.0") + assert.NotContains(t, stderr, "version 0.1.1") + assert.DirExists(t, filepath.Join(p.LocalProjectDir(), fmt.Sprintf(".helm-charts/http_%s_127.0.0.1/test-chart1/0.1.0", u.Port()))) + assert.NoDirExists(t, filepath.Join(p.LocalProjectDir(), fmt.Sprintf(".helm-charts/http_%s_127.0.0.1/test-chart1/0.1.1", u.Port()))) + + p.UpdateYaml("helm1/helm-chart.yaml", func(o *uo.UnstructuredObject) error { + _ = o.SetNestedField(true, "helmChart", "skipPrePull") + return nil + }, "") + _, stderr = p.KluctlMust(args...) + assert.Contains(t, stderr, "Removing unused Chart with version 0.1.0") + assert.NotContains(t, stderr, "version 0.1.1") + assert.NoDirExists(t, filepath.Join(p.LocalProjectDir(), fmt.Sprintf(".helm-charts/http_%s_127.0.0.1/test-chart1/0.1.0", u.Port()))) + assert.NoDirExists(t, filepath.Join(p.LocalProjectDir(), fmt.Sprintf(".helm-charts/http_%s_127.0.0.1/test-chart1/0.1.1", u.Port()))) + + p.UpdateYaml("helm2/helm-chart.yaml", func(o *uo.UnstructuredObject) error { + _ = o.SetNestedField(false, "helmChart", "skipPrePull") + return nil + }, "") + _, stderr = p.KluctlMust(args...) + assert.Contains(t, stderr, "test-chart1: Pulling Chart with version 0.1.1") + assert.NotContains(t, stderr, "version 0.1.0") + assert.NoDirExists(t, filepath.Join(p.LocalProjectDir(), fmt.Sprintf(".helm-charts/http_%s_127.0.0.1/test-chart1/0.1.0", u.Port()))) + assert.DirExists(t, filepath.Join(p.LocalProjectDir(), fmt.Sprintf(".helm-charts/http_%s_127.0.0.1/test-chart1/0.1.1", u.Port()))) + + p.UpdateYaml("helm1/helm-chart.yaml", func(o *uo.UnstructuredObject) error { + _ = o.SetNestedField(false, "helmChart", "skipPrePull") + return nil + }, "") + _, stderr = p.KluctlMust(args...) + assert.Contains(t, stderr, "Pulling Chart with version 0.1.0") + assert.Contains(t, stderr, "Pulling Chart with version 0.1.1") + assert.DirExists(t, filepath.Join(p.LocalProjectDir(), fmt.Sprintf(".helm-charts/http_%s_127.0.0.1/test-chart1/0.1.0", u.Port()))) + assert.DirExists(t, filepath.Join(p.LocalProjectDir(), fmt.Sprintf(".helm-charts/http_%s_127.0.0.1/test-chart1/0.1.1", u.Port()))) + + // not try to update+pull + p.UpdateYaml("helm1/helm-chart.yaml", func(o *uo.UnstructuredObject) error { + _ = o.SetNestedField(true, "helmChart", "skipPrePull") + return nil + }, "") + _, stderr = p.KluctlMust(args...) + p.GitServer().CommitFiles("kluctl-project", []string{".helm-charts"}, false, ".helm-charts") + args = []string{ + "helm-update", + "--upgrade", + "--commit", + } + _, stderr = p.KluctlMust(args...) + assert.NotContains(t, stderr, "Pulling Chart with version 0.1.0") + assert.NotContains(t, stderr, "Pulling Chart with version 0.1.1") + assert.Contains(t, stderr, "Pulling Chart with version 0.2.0") + assert.NoDirExists(t, filepath.Join(p.LocalProjectDir(), fmt.Sprintf(".helm-charts/http_%s_127.0.0.1/test-chart1/0.1.0", u.Port()))) + assert.NoDirExists(t, filepath.Join(p.LocalProjectDir(), fmt.Sprintf(".helm-charts/http_%s_127.0.0.1/test-chart1/0.1.1", u.Port()))) + assert.DirExists(t, filepath.Join(p.LocalProjectDir(), fmt.Sprintf(".helm-charts/http_%s_127.0.0.1/test-chart1/0.2.0", u.Port()))) +} + func getChartDir(t *testing.T, p *test_utils.TestProject, url2 string, chartName string, chartVersion string) string { u, err := url.Parse(url2) if err != nil { diff --git a/e2e/sops_test.go b/e2e/sops_test.go index 2ea1d9ac7..a7c7c3369 100644 --- a/e2e/sops_test.go +++ b/e2e/sops_test.go @@ -109,6 +109,10 @@ func TestSopsHelmValues(t *testing.T) { p.UpdateTarget("test", nil) p.AddHelmDeployment("helm1", repoUrl, "test-chart1", "0.1.0", "test-helm1", p.TestSlug(), values1.Object) + p.UpdateYaml("helm1/helm-chart.yaml", func(o *uo.UnstructuredObject) error { + _ = o.SetNestedField(true, "helmChart", "skipPrePull") + return nil + }, "") p.KluctlMust("deploy", "--yes", "-t", "test") diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index bc442fdc4..ad20b3158 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -131,7 +131,7 @@ func (c *DeploymentCollection) renderHelmCharts() error { s := status.Start(c.ctx.Ctx, "Rendering Helm Charts") defer s.Failed() - g := utils.NewGoHelper(c.ctx.Ctx, 0) + g := utils.NewGoHelper(c.ctx.Ctx, 8) for _, d := range c.Deployments { d := d diff --git a/pkg/helm/helm_release.go b/pkg/helm/helm_release.go index 7012e00f5..5b21b2215 100644 --- a/pkg/helm/helm_release.go +++ b/pkg/helm/helm_release.go @@ -137,6 +137,11 @@ func (hr *Release) getPulledChart(ctx context.Context) (*PulledChart, error) { return nil, err } if needsPull { + if !hr.Config.SkipPrePull { + //goland:noinspection ALL + return nil, fmt.Errorf("Helm Chart %s has not been pre-pulled, which is not allowed when skipPrePull is not enabled. "+ + "Run 'kluctl helm-pull' to pre-pull all Helm Charts", hr.Chart.GetChartName()) + } if versionChanged { return nil, fmt.Errorf("pre-pulled Helm Chart %s need to be pulled (call 'kluctl helm-pull'). "+ "Desired version is %s while pre-pulled version is %s", hr.Chart.GetChartName(), hr.Config.ChartVersion, prePulledVersion) diff --git a/pkg/types/helm_chart.go b/pkg/types/helm_chart.go index e2c45601f..78011ad58 100644 --- a/pkg/types/helm_chart.go +++ b/pkg/types/helm_chart.go @@ -18,6 +18,7 @@ type HelmChartConfig2 struct { Output *string `yaml:"output,omitempty"` SkipCRDs bool `yaml:"skipCRDs,omitempty"` SkipUpdate bool `yaml:"skipUpdate,omitempty"` + SkipPrePull bool `yaml:"skipPrePull,omitempty"` } func ValidateHelmChartConfig2(sl validator.StructLevel) { From cf5930d78a757c66d82100f534be6bd8bb1ead56 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 8 Feb 2023 11:06:46 +0100 Subject: [PATCH 1429/2916] feat: Implement when conditionals --- pkg/deployment/deployment_collection.go | 9 ++++ pkg/deployment/deployment_project.go | 48 ++++++++++++++++++++ pkg/kluctl_jinja2/conditionals.go | 58 +++++++++++++++++++++++++ pkg/types/deployment.go | 3 ++ 4 files changed, 118 insertions(+) create mode 100644 pkg/kluctl_jinja2/conditionals.go diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index bc442fdc4..9f62ff75a 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -2,6 +2,7 @@ package deployment import ( "fmt" + "github.com/kluctl/kluctl/v2/pkg/kluctl_jinja2" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" @@ -81,7 +82,15 @@ func findDeploymentItemIndex(project *DeploymentProject, pth *string, indexes ma func (c *DeploymentCollection) collectAllDeployments(project *DeploymentProject, indexes map[string]int) ([]*DeploymentItem, error) { var ret []*DeploymentItem + if !kluctl_jinja2.IsConditionalTrue(project.Config.When) { + return nil, nil + } + for i, diConfig := range project.Config.Deployments { + if !kluctl_jinja2.IsConditionalTrue(diConfig.When) { + continue + } + if diConfig.Include != nil || diConfig.Git != nil { includedProject, ok := project.includes[i] if !ok { diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go index 7ee143f3d..3431c0a74 100644 --- a/pkg/deployment/deployment_project.go +++ b/pkg/deployment/deployment_project.go @@ -3,6 +3,7 @@ package deployment import ( "fmt" securejoin "github.com/cyphar/filepath-securejoin" + "github.com/kluctl/kluctl/v2/pkg/kluctl_jinja2" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" @@ -56,6 +57,10 @@ func NewDeploymentProject(ctx SharedContext, varsCtx *vars.VarsCtx, source Sourc return nil, fmt.Errorf("failed to load deployment config for %s: %w", dir, err) } + if !kluctl_jinja2.IsConditionalTrue(dp.Config.When) { + return dp, nil + } + err = dp.loadIncludes() if err != nil { return nil, fmt.Errorf("failed to load includes for %s: %w", dir, err) @@ -126,6 +131,11 @@ func (p *DeploymentProject) processConfig() error { return err } + err = p.renderConditionals() + if err != nil { + return err + } + if len(p.Config.Args) != 0 && p.parentProject != nil { return fmt.Errorf("only the root deployment.yml can define args") } @@ -157,11 +167,49 @@ func (p *DeploymentProject) checkDeploymentDirs() error { return nil } +func (p *DeploymentProject) renderConditionals() error { + if p.parentProject == nil && p.Config.When != "" { + return fmt.Errorf("the root deployment project can not contain 'when'") + } + + vars, err := p.VarsCtx.Vars.ToMap() + if err != nil { + return err + } + r, err := kluctl_jinja2.RenderConditional(p.VarsCtx.J2, vars, p.Config.When) + if err != nil { + return err + } + p.Config.When = r + + if !kluctl_jinja2.IsConditionalTrue(p.Config.When) { + // No need to render individual deployment item conditionals + return nil + } + + conditionals := make([]string, 0, len(p.Config.Deployments)) + for _, di := range p.Config.Deployments { + conditionals = append(conditionals, di.When) + } + rendered, err := kluctl_jinja2.RenderConditionals(p.VarsCtx.J2, vars, conditionals) + if err != nil { + return err + } + for i, r := range rendered { + p.Config.Deployments[i].When = r + } + return nil +} + func (p *DeploymentProject) loadIncludes() error { for i, inc := range p.Config.Deployments { var err error var newProject *DeploymentProject + if !kluctl_jinja2.IsConditionalTrue(inc.When) { + continue + } + if inc.Include != nil { newProject, err = p.loadLocalInclude(p.source, filepath.Join(p.relDir, *inc.Include), inc.Vars) if err != nil { diff --git a/pkg/kluctl_jinja2/conditionals.go b/pkg/kluctl_jinja2/conditionals.go new file mode 100644 index 000000000..7cee13000 --- /dev/null +++ b/pkg/kluctl_jinja2/conditionals.go @@ -0,0 +1,58 @@ +package kluctl_jinja2 + +import ( + "fmt" + "github.com/hashicorp/go-multierror" + "github.com/kluctl/go-jinja2" + "strings" +) + +func RenderConditionals(j *jinja2.Jinja2, vars map[string]any, conditionals []string) ([]string, error) { + ret := make([]string, len(conditionals)) + jobs := make([]*jinja2.RenderJob, 0, len(conditionals)) + + for _, c := range conditionals { + job := &jinja2.RenderJob{ + Template: buildConditionalTemplate(c), + } + jobs = append(jobs, job) + } + err := j.RenderStrings(jobs, jinja2.WithGlobals(vars)) + if err != nil { + return nil, err + } + + for i, job := range jobs { + if job.Error != nil { + e := fmt.Errorf("unable to render conditional '%s': %w", conditionals[i], job.Error) + err = multierror.Append(err, e) + } else { + r := strings.TrimSpace(*job.Result) + if r != "" && r != "True" && r != "False" { + err = multierror.Append(err, fmt.Errorf("unable to evaluate conditional: %s", conditionals[i])) + } else { + ret[i] = r + } + } + } + return ret, err +} + +func RenderConditional(j *jinja2.Jinja2, vars map[string]any, conditional string) (string, error) { + rendered, err := RenderConditionals(j, vars, []string{conditional}) + if err != nil { + return "", err + } + return rendered[0], nil +} + +func buildConditionalTemplate(c string) string { + if c == "" { + return "" + } + return fmt.Sprintf("{%% if %s %%} True {%% else %%} False {%% endif %%}", c) +} + +func IsConditionalTrue(c string) bool { + return c == "" || c == "True" +} diff --git a/pkg/types/deployment.go b/pkg/types/deployment.go index cefcf4b59..f792971bd 100644 --- a/pkg/types/deployment.go +++ b/pkg/types/deployment.go @@ -17,6 +17,7 @@ type DeploymentItemConfig struct { OnlyRender bool `yaml:"onlyRender,omitempty"` AlwaysDeploy bool `yaml:"alwaysDeploy,omitempty"` DeleteObjects []DeleteObjectItemConfig `yaml:"deleteObjects,omitempty"` + When string `yaml:"when,omitempty"` } func ValidateDeploymentItemConfig(sl validator.StructLevel) { @@ -88,6 +89,8 @@ type DeploymentProjectConfig struct { Vars []*VarsSource `yaml:"vars,omitempty"` SealedSecrets *SealedSecretsConfig `yaml:"sealedSecrets,omitempty"` + When string `yaml:"when,omitempty"` + Deployments []*DeploymentItemConfig `yaml:"deployments,omitempty"` CommonLabels map[string]string `yaml:"commonLabels,omitempty"` From e88f60c2b73c2eb694eeef8802b03494a3dfa77c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 8 Feb 2023 12:05:41 +0100 Subject: [PATCH 1430/2916] tests: Implement e2e tests for "when" conditionals --- e2e/utils_resources.go | 14 +++++++ e2e/when_test.go | 91 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 e2e/when_test.go diff --git a/e2e/utils_resources.go b/e2e/utils_resources.go index 4131374b7..928627aa6 100644 --- a/e2e/utils_resources.go +++ b/e2e/utils_resources.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/kluctl/kluctl/v2/e2e/test-utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "path/filepath" ) type resourceOpts struct { @@ -12,6 +13,7 @@ type resourceOpts struct { tags []string labels map[string]string annotations map[string]string + when string } func mergeMetadata(o *uo.UnstructuredObject, opts resourceOpts) { @@ -57,6 +59,12 @@ func addConfigMapDeployment(p *test_utils.TestProject, dir string, data map[stri p.AddKustomizeDeployment(dir, []test_utils.KustomizeResource{ {fmt.Sprintf("configmap-%s.yml", opts.name), "", o}, }, opts.tags) + if opts.when != "" { + p.UpdateDeploymentItems(filepath.Dir(dir), func(items []*uo.UnstructuredObject) []*uo.UnstructuredObject { + _ = items[len(items)-1].SetNestedField(opts.when, "when") + return items + }) + } } func addSecretDeployment(p *test_utils.TestProject, dir string, data map[string]string, opts resourceOpts, sealme bool) { @@ -69,4 +77,10 @@ func addSecretDeployment(p *test_utils.TestProject, dir string, data map[string] p.AddKustomizeDeployment(dir, []test_utils.KustomizeResource{ {fname, fname + sealmeExt, o}, }, opts.tags) + if opts.when != "" { + p.UpdateDeploymentItems(filepath.Dir(dir), func(items []*uo.UnstructuredObject) []*uo.UnstructuredObject { + _ = items[len(items)-1].SetNestedField(opts.when, "when") + return items + }) + } } diff --git a/e2e/when_test.go b/e2e/when_test.go new file mode 100644 index 000000000..1eca921b4 --- /dev/null +++ b/e2e/when_test.go @@ -0,0 +1,91 @@ +package e2e + +import ( + test_utils "github.com/kluctl/kluctl/v2/e2e/test-utils" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "path/filepath" + "strings" + "testing" +) + +func TestWhen(t *testing.T) { + k := defaultCluster1 + + p := test_utils.NewTestProject(t) + createNamespace(t, k, p.TestSlug()) + p.UpdateTarget("test", func(target *uo.UnstructuredObject) {}) + + type testCase struct { + dir string + when string + whenInc string + depInc string + want bool + name string + } + + tests := []*testCase{ + {dir: "cm_empty", when: "", want: true}, + {dir: "cm_true", when: "True", want: true}, + {dir: "cm_false", when: "False", want: false}, + {dir: "cm_eq", when: "args.a == 'test'", want: true}, + {dir: "cm_ne", when: "args.a != 'test'", want: false}, + {dir: "cm_eq2", when: "args.b == 'test'", want: false}, + {dir: "cm_ne2", when: "args.b != 'test'", want: true}, + {dir: "inc1/cm_empty", when: "", want: true}, + {dir: "inc2/cm_true", when: "True", want: true}, + {dir: "inc3/cm_false", when: "False", want: false}, + {dir: "inc4/cm_eq", when: "args.a == 'test'", want: true}, + {dir: "inc5/cm_ne", when: "args.a != 'test'", want: false}, + {dir: "inc_when1/cm_empty", whenInc: "", want: true}, + {dir: "inc_when2/cm_true", whenInc: "True", want: true}, + {dir: "inc_when3/cm_false", whenInc: "False", want: false}, + {dir: "inc_when4/cm_eq", whenInc: "args.a == 'test'", want: true}, + {dir: "inc_when5/cm_ne", whenInc: "args.a != 'test'", want: false}, + {dir: "dep_inc_when1/cm_empty", depInc: "", want: true}, + {dir: "dep_inc_when2/cm_true", depInc: "True", want: true}, + {dir: "dep_inc_when3/cm_false", depInc: "False", want: false}, + {dir: "dep_inc_when4/cm_eq", depInc: "args.a == 'test'", want: true}, + {dir: "dep_inc_when5/cm_ne", depInc: "args.a != 'test'", want: false}, + } + + for _, test := range tests { + test.name = strings.ReplaceAll(test.dir, "/", "_") + test.name = strings.ReplaceAll(test.name, "_", "-") + addConfigMapDeployment(p, test.dir, nil, resourceOpts{ + name: test.name, + namespace: p.TestSlug(), + when: test.when, + }) + if test.whenInc != "" { + dir := filepath.Dir(test.dir) + p.UpdateDeploymentItems("", func(items []*uo.UnstructuredObject) []*uo.UnstructuredObject { + for _, it := range items { + inc, _, _ := it.GetNestedString("include") + if inc == dir { + _ = it.SetNestedField(test.whenInc, "when") + break + } + } + return items + }) + } + if test.depInc != "" { + dir := filepath.Dir(test.dir) + p.UpdateDeploymentYaml(dir, func(o *uo.UnstructuredObject) error { + _ = o.SetNestedField(test.depInc, "when") + return nil + }) + } + } + + p.KluctlMust("deploy", "--yes", "-t", "test", "-aa=test", "-ab=test2") + + for _, test := range tests { + if test.want { + assertConfigMapExists(t, k, p.TestSlug(), test.name) + } else { + assertConfigMapNotExists(t, k, p.TestSlug(), test.name) + } + } +} From ef4bf54438ab28fec21396de89c3b72343fac7b5 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 8 Feb 2023 14:23:30 +0100 Subject: [PATCH 1431/2916] feat: Implement "when" conditional for variable sources --- pkg/types/vars_source.go | 5 ++++- pkg/vars/vars_loader.go | 11 +++++++++++ pkg/vars/vars_loader_test.go | 24 ++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/pkg/types/vars_source.go b/pkg/types/vars_source.go index e7db5f765..9e9c3db25 100644 --- a/pkg/types/vars_source.go +++ b/pkg/types/vars_source.go @@ -66,6 +66,8 @@ type VarsSource struct { Http *VarsSourceHttp `yaml:"http,omitempty"` AwsSecretsManager *VarsSourceAwsSecretsManager `yaml:"awsSecretsManager,omitempty"` Vault *VarsSourceVault `yaml:"vault,omitempty"` + + When string `yaml:"when,omitempty"` } func ValidateVarsSource(sl validator.StructLevel) { @@ -74,7 +76,8 @@ func ValidateVarsSource(sl validator.StructLevel) { count := 0 v := reflect.ValueOf(s) for i := 0; i < v.NumField(); i++ { - if v.Type().Field(i).Name == "IgnoreMissing" || v.Type().Field(i).Name == "NoOverride" { + switch v.Type().Field(i).Name { + case "IgnoreMissing", "NoOverride", "When": continue } if !v.Field(i).IsNil() { diff --git a/pkg/vars/vars_loader.go b/pkg/vars/vars_loader.go index a0dc29267..9ce0ff997 100644 --- a/pkg/vars/vars_loader.go +++ b/pkg/vars/vars_loader.go @@ -8,6 +8,7 @@ import ( types2 "github.com/aws/aws-sdk-go-v2/service/secretsmanager/types" "github.com/kluctl/go-jinja2" "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/kluctl_jinja2" "github.com/kluctl/kluctl/v2/pkg/repocache" "github.com/kluctl/kluctl/v2/pkg/sops" "github.com/kluctl/kluctl/v2/pkg/sops/decryptor" @@ -78,6 +79,16 @@ func (v *VarsLoader) LoadVars(varsCtx *VarsCtx, sourceIn *types.VarsSource, sear return err } + if source.When != "" { + r, err := kluctl_jinja2.RenderConditional(varsCtx.J2, globals, source.When) + if err != nil { + return err + } + if !kluctl_jinja2.IsConditionalTrue(r) { + return nil + } + } + ignoreMissing := false if source.IgnoreMissing != nil { ignoreMissing = *source.IgnoreMissing diff --git a/pkg/vars/vars_loader_test.go b/pkg/vars/vars_loader_test.go index a5b774f45..1764788de 100644 --- a/pkg/vars/vars_loader_test.go +++ b/pkg/vars/vars_loader_test.go @@ -88,6 +88,30 @@ func TestVarsLoader_ValuesNoOverrides(t *testing.T) { }) } +func TestVarsLoader_When(t *testing.T) { + testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { + err := vl.LoadVars(vc, &types.VarsSource{ + Values: uo.FromStringMust(`{"test1": "a"}`), + }, nil, "") + assert.NoError(t, err) + err = vl.LoadVars(vc, &types.VarsSource{ + Values: uo.FromStringMust(`{"test1": "b"}`), + When: `test1 == "b"`, + }, nil, "") + assert.NoError(t, err) + v, _, _ := vc.Vars.GetNestedString("test1") + assert.Equal(t, "a", v) + + err = vl.LoadVars(vc, &types.VarsSource{ + Values: uo.FromStringMust(`{"test1": "b"}`), + When: `test1 == "a"`, + }, nil, "") + assert.NoError(t, err) + v, _, _ = vc.Vars.GetNestedString("test1") + assert.Equal(t, "b", v) + }) +} + func TestVarsLoader_File(t *testing.T) { d := t.TempDir() _ = os.WriteFile(filepath.Join(d, "test.yaml"), []byte(`{"test1": {"test2": 42}}`), 0o600) From 4a9f246e85c701dee5441e4395b61183d0277f6f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 8 Feb 2023 14:56:34 +0100 Subject: [PATCH 1432/2916] docs: Add documentation for conditional deployments and variable sources --- docs/reference/deployments/deployment-yml.md | 16 ++++++++++++++++ docs/reference/templating/variable-sources.md | 5 +++++ 2 files changed, 21 insertions(+) diff --git a/docs/reference/deployments/deployment-yml.md b/docs/reference/deployments/deployment-yml.md index 77115380e..013f60ef3 100644 --- a/docs/reference/deployments/deployment-yml.md +++ b/docs/reference/deployments/deployment-yml.md @@ -34,6 +34,8 @@ The following sub-chapters describe the available fields in the `deployment.yaml Individual deployments are performed in parallel, unless a [barrier](#barriers) is encountered which causes kluctl to wait for all previous deployments to finish. +Deployments can also be conditional by using the [when](#when) field. + ### Simple deployments Simple deployments are specified via `path` and are expected to be directories with Kubernetes manifests inside. @@ -150,6 +152,20 @@ deployments: - file: vars2.yaml ``` +### when + +Each deployment item can be conditional with the help of the `when` field. It must be set to a +[Jinja2 based expression](https://jinja.palletsprojects.com/en/latest/templates/#expressions) +that evaluates to a boolean. + +Example: +```yaml +deployments: +- path: item1 +- path: item2 + when: my.var == "my-value" +``` + ### tags (deployment item) A list of tags the deployment should have. See [tags](./tags.md) for more details. For includes, this means that all sub-deployments will get these tags applied to. If not specified, the default tags logic as described in [tags](./tags.md) diff --git a/docs/reference/templating/variable-sources.md b/docs/reference/templating/variable-sources.md index 3f157f400..32d1654b3 100644 --- a/docs/reference/templating/variable-sources.md +++ b/docs/reference/templating/variable-sources.md @@ -31,6 +31,8 @@ vars: ignoreMissing: true - file: default-vars.yaml noOverride: true +- file: vars3.yaml + when: some.var == "value" ``` `vars2.yaml` can now use variables that are defined in `vars1.yaml`. At all times, variables defined by @@ -42,6 +44,9 @@ can not be found. When specifying `noOverride: true`, Kluctl will not override variables from the previously loaded variables. This is useful if you want to load default values for variables. +Variables can also be loaded conditionally by specifying a condition via `when: `. The condition must be in +the same format as described in [conditional deployment items](../deployments/deployment-yml.md#when) + Different types of vars entries are possible: ### file From e335213e5ec4196a86b57073e554e248637f8577 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 7 Feb 2023 13:57:41 +0100 Subject: [PATCH 1433/2916] feat: Deprecate dynamic targets --- pkg/kluctl_project/targets.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/kluctl_project/targets.go b/pkg/kluctl_project/targets.go index 66a5afb1f..dd2eb4ff5 100644 --- a/pkg/kluctl_project/targets.go +++ b/pkg/kluctl_project/targets.go @@ -126,6 +126,10 @@ func (c *LoadedKluctlProject) prepareDynamicTargetsExternal(baseTarget *types.Ta return nil, fmt.Errorf("'refPattern' and 'ref' can't be specified together") } + if baseTarget.TargetConfig.RefPattern != nil { + status.Deprecation(c.ctx, "dynamic-targets-ref-pattern", "'refPattern' and corresponding dynamic targets are deprecated and will be removed in an upcoming release.") + } + targetConfigRef := baseTarget.TargetConfig.Ref refPattern := baseTarget.TargetConfig.RefPattern From 66766066ac3db31033093407cddec42d11efd3e8 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 10 Feb 2023 16:18:24 +0100 Subject: [PATCH 1434/2916] feat: Update deprecation warnings for feature that will be removed in the next release --- cmd/kluctl/commands/cmd_check_image_updates.go | 2 +- cmd/kluctl/commands/utils.go | 4 ++-- pkg/deployment/images.go | 2 +- pkg/kluctl_project/targets.go | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cmd/kluctl/commands/cmd_check_image_updates.go b/cmd/kluctl/commands/cmd_check_image_updates.go index 7e7966ed0..782ed9868 100644 --- a/cmd/kluctl/commands/cmd_check_image_updates.go +++ b/cmd/kluctl/commands/cmd_check_image_updates.go @@ -23,7 +23,7 @@ func (cmd *checkImageUpdatesCmd) Help() string { } func (cmd *checkImageUpdatesCmd) Run(ctx context.Context) error { - status.Deprecation(ctx, "check-image-updates", "check-image-updates is deprecated and will be removed in a future kluctl release.") + status.Deprecation(ctx, "check-image-updates", "check-image-updates is deprecated and will be removed in the next kluctl release.") ptArgs := projectTargetCommandArgs{ projectFlags: cmd.ProjectFlags, targetFlags: cmd.TargetFlags, diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 3259ab704..b2807f72b 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -151,10 +151,10 @@ func withProjectTargetCommandContext(ctx context.Context, args projectTargetComm return fmt.Errorf("failed to parse registry auth from environment: %w", err) } if args.imageFlags.UpdateImages { - status.Deprecation(ctx, "update-images", "--update-images is deprecated and will be removed in a future kluctl release.") + status.Deprecation(ctx, "update-images", "--update-images is deprecated and will be removed in the next kluctl release.") } if !args.imageFlags.OfflineImages { - status.Deprecation(ctx, "online-images", "--offline-images=false is deprecated and will be removed in a future kluctl release.") + status.Deprecation(ctx, "online-images", "--offline-images=false is deprecated and will be removed in the next kluctl release.") } images, err := deployment.NewImages(rh, args.imageFlags.UpdateImages, args.imageFlags.OfflineImages || args.forCompletion) if err != nil { diff --git a/pkg/deployment/images.go b/pkg/deployment/images.go index 076350faf..11112761a 100644 --- a/pkg/deployment/images.go +++ b/pkg/deployment/images.go @@ -303,7 +303,7 @@ func (images *Images) resolveImage(ctx context.Context, ph placeHolder, ref k8s2 } if ph.HasLatestVersion { - status.Deprecation(ctx, "latest-version-filter", "latest_version is deprecated when using images.get_image()") + status.Deprecation(ctx, "latest-version-filter", "latest_version is deprecated when using images.get_image() and will be removed in the next kluctl release.") } registry, err := images.GetLatestImageFromRegistry(ph.Image, ph.LatestVersion) diff --git a/pkg/kluctl_project/targets.go b/pkg/kluctl_project/targets.go index dd2eb4ff5..9f010b542 100644 --- a/pkg/kluctl_project/targets.go +++ b/pkg/kluctl_project/targets.go @@ -127,7 +127,7 @@ func (c *LoadedKluctlProject) prepareDynamicTargetsExternal(baseTarget *types.Ta } if baseTarget.TargetConfig.RefPattern != nil { - status.Deprecation(c.ctx, "dynamic-targets-ref-pattern", "'refPattern' and corresponding dynamic targets are deprecated and will be removed in an upcoming release.") + status.Deprecation(c.ctx, "dynamic-targets-ref-pattern", "'refPattern' and corresponding dynamic targets are deprecated and will be removed in the next kluctl release.") } targetConfigRef := baseTarget.TargetConfig.Ref @@ -249,7 +249,7 @@ func (c *LoadedKluctlProject) buildDynamicTarget(targetInfo *dynamicTargetInfo) } if len(target.DynamicArgs) != 0 { - status.Deprecation(c.ctx, "dynamic-args", "dynamicArgs are deprecated and ignored. The field will be removed in a future kluctl release.") + status.Deprecation(c.ctx, "dynamic-args", "dynamicArgs are deprecated and ignored. The field will be removed in the next kluctl release.") } // check and merge args @@ -257,7 +257,7 @@ func (c *LoadedKluctlProject) buildDynamicTarget(targetInfo *dynamicTargetInfo) target.Args.Merge(targetConfig.Args) } if len(targetConfig.Images) != 0 { - status.Deprecation(c.ctx, "target-config-images", "specifying fixed images via external 'targetConfig' is deprecated and support for this will be removed in a future kluctl release.") + status.Deprecation(c.ctx, "target-config-images", "specifying fixed images via external 'targetConfig' is deprecated and support for this will be removed in the next kluctl release.") } // We prepend the dynamic images to ensure they get higher priority later target.Images = append(targetConfig.Images, target.Images...) From 08baf43e4ddf5c6faa073dac7335a6539fe7a11a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 10 Feb 2023 16:19:02 +0100 Subject: [PATCH 1435/2916] feat: Deprecate targetConfig --- pkg/kluctl_project/targets.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/kluctl_project/targets.go b/pkg/kluctl_project/targets.go index 9f010b542..59c27ee08 100644 --- a/pkg/kluctl_project/targets.go +++ b/pkg/kluctl_project/targets.go @@ -237,6 +237,8 @@ func (c *LoadedKluctlProject) buildDynamicTarget(targetInfo *dynamicTargetInfo) return &target, nil } + status.Deprecation(c.ctx, "target-config", "'targetConfig' in targets is deprecated and will be removed in the next kluctl release.") + configFile, err := c.loadTargetConfigFile(targetInfo) if err != nil { return nil, err From 7cf8bbafe4b329988b268296598c697983ad54a8 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 10 Feb 2023 16:30:08 +0100 Subject: [PATCH 1436/2916] docs: Remove mentions of dynamic targets --- cmd/kluctl/commands/cmd_list_targets.go | 2 +- cmd/kluctl/commands/root.go | 2 +- docs/reference/commands/list-targets.md | 4 +- .../kluctl-project/targets/README.md | 10 +- .../kluctl-project/targets/dynamic-targets.md | 100 ------------------ 5 files changed, 6 insertions(+), 112 deletions(-) delete mode 100644 docs/reference/kluctl-project/targets/dynamic-targets.md diff --git a/cmd/kluctl/commands/cmd_list_targets.go b/cmd/kluctl/commands/cmd_list_targets.go index dca1b646d..7d2649716 100644 --- a/cmd/kluctl/commands/cmd_list_targets.go +++ b/cmd/kluctl/commands/cmd_list_targets.go @@ -13,7 +13,7 @@ type listTargetsCmd struct { } func (cmd *listTargetsCmd) Help() string { - return `Outputs a yaml list with all target, including dynamic targets` + return `Outputs a yaml list with all targets` } func (cmd *listTargetsCmd) Run(ctx context.Context) error { diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index 64e3e772f..2b37ba7d3 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -61,7 +61,7 @@ type cli struct { HelmPull helmPullCmd `cmd:"" help:"Recursively searches for 'helm-chart.yaml' files and pre-pulls the specified Helm charts"` HelmUpdate helmUpdateCmd `cmd:"" help:"Recursively searches for 'helm-chart.yaml' files and checks for new available versions"` ListImages listImagesCmd `cmd:"" help:"Renders the target and outputs all images used via 'images.get_image(...)"` - ListTargets listTargetsCmd `cmd:"" help:"Outputs a yaml list with all target, including dynamic targets"` + ListTargets listTargetsCmd `cmd:"" help:"Outputs a yaml list with all targets"` PokeImages pokeImagesCmd `cmd:"" help:"Replace all images in target"` Prune pruneCmd `cmd:"" help:"Searches the target cluster for prunable objects and deletes them"` Render renderCmd `cmd:"" help:"Renders all resources and configuration files"` diff --git a/docs/reference/commands/list-targets.md b/docs/reference/commands/list-targets.md index f99d5b2e0..52d882321 100644 --- a/docs/reference/commands/list-targets.md +++ b/docs/reference/commands/list-targets.md @@ -13,8 +13,8 @@ description: > Usage: kluctl list-targets [flags] -Outputs a yaml list with all target, including dynamic targets -Outputs a yaml list with all target, including dynamic targets +Outputs a yaml list with all targets +Outputs a yaml list with all targets diff --git a/docs/reference/kluctl-project/targets/README.md b/docs/reference/kluctl-project/targets/README.md index d3fea1d5f..4cb00e161 100644 --- a/docs/reference/kluctl-project/targets/README.md +++ b/docs/reference/kluctl-project/targets/README.md @@ -15,9 +15,9 @@ Specifies a list of targets for which commands can be invoked. A target puts tog configuration and the target cluster. Multiple targets can exist which target the same cluster but with differing configuration (via `args`). -Each value found in the target definition is rendered with a simple Jinja2 context that only contains the target itself. +Each value found in the target definition is rendered with a simple Jinja2 context that only contains the target and args. The rendering process is retried 10 times until it finally succeeds, allowing you to reference -the target itself in complex ways. This is especially useful when using [dynamic targets](./dynamic-targets.md). +the target itself in complex ways. Target entries have the following form: ```yaml @@ -50,16 +50,10 @@ If this field is omitted, Kluctl will always use the currently active context. This fields specifies a map of arguments to be passed to the deployment project when it is rendered. Allowed argument names are configured via [deployment args](../../deployments/deployment-yml.md#args). -The arguments specified in the [dynamic target config](../../kluctl-project/targets/dynamic-targets.md#args) -have higher priority. - ## images This field specifies a list of fixed images to be used by [`images.get_image(...)`](../../deployments/images.md#imagesget_image). The format is identical to the [fixed images file](../../deployments/images.md#command-line-argument---fixed-images-file). -The fixed images specified in the [dynamic target config](../../kluctl-project/targets/dynamic-targets.md#images) -have higher priority. - ## discriminator Specifies a discriminator which is used to uniquely identify all deployed objects on the cluster. It is added to all diff --git a/docs/reference/kluctl-project/targets/dynamic-targets.md b/docs/reference/kluctl-project/targets/dynamic-targets.md deleted file mode 100644 index 6b18cace4..000000000 --- a/docs/reference/kluctl-project/targets/dynamic-targets.md +++ /dev/null @@ -1,100 +0,0 @@ - - -# Dynamic Targets - -Targets can also be "dynamic", meaning that additional configuration can be sourced from another git repository. -This can be based on a single target repository and branch, or on a target repository and branch/ref pattern, resulting -in multiple dynamic targets being created from one target definition. - -Please note that a single entry in `target` might end up with multiple dynamic targets, meaning that the name must be -made unique between these dynamic targets. This can be achieved by using templating in the `name` field. As an example, -`{{ target.targetConfig.ref }}` can be used to set the target name to the branch name of the dynamic target. - -Dynamic targets have the following form: -```yaml -targets: -... - - name: - context: - args: ... - arg1: - arg2: - ... - targetConfig: - project: - url: - ref: - refPattern: - file: -... -``` - -All fields known from normal targets are allowed. In addition, the targetConfig with following fields is available. - -## targetConfig - -The presence of this field causes the target to become a dynamic target. -It specifies where to look for dynamic targets and their addional configuration. It has the following form: - -```yaml -... -targets: -... -- name: - ... - targetConfig: - project: - url: - ref: - refPattern: -... -``` - -### project.url -This field specifies the git clone url of the target configuration project. - -### ref -This field specifies the branch or tag to use. If this field is specified, using `refPattern` is forbidden. -This will result in one single dynamic target. - -### refPattern -This field specifies a regex pattern to use when looking for candidate branches and tags. If this is specified, -using `ref` is forbidden. This will result in multiple dynamic targets. Each dynamic target will have `ref` set to -the actual branch name it belong to. This allows using of `{{ target.targetConfig.ref }}` in all other target fields. - -### file -This field specifies the config file name to read externalized target config from. - -## Format of the target config -The target config file referenced in `targetConfig` must be of the following format: - -```yaml -args: - arg1: value1 - arg2: value2 -images: - - image: registry.gitlab.com/my-group/my-project - resultImage: registry.gitlab.com/my-group/my-project:1.1.0 -``` - -### args -An optional map of arguments, in the same format as in the normal [target args](../../kluctl-project/targets#args). - -The arguments specified here have higher priority. - -### images -An optional list of fixed images, in the same format as in the normal [target images](../../kluctl-project/targets#images) - -## Simple dynamic targets - -A simplified form of dynamic targets is to store target config inside the same directory/project as the `.kluctl.yaml`. -This can be done by omitting `project`, `ref` and `refPattern` from `targetConfig` and only specify `file`. From 95bd6ddf45c3f327f4c79a8f6a1eba5332c0a848 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 13 Feb 2023 10:12:38 +0100 Subject: [PATCH 1437/2916] fix: Use default discriminator for deployments without targets (#315) --- e2e/test-utils/project.go | 2 +- pkg/kluctl_project/target_context.go | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/e2e/test-utils/project.go b/e2e/test-utils/project.go index 74b64f2e2..dc1c5daee 100644 --- a/e2e/test-utils/project.go +++ b/e2e/test-utils/project.go @@ -79,7 +79,7 @@ func NewTestProject(t *testing.T, opts ...TestProjectOption) *TestProject { p.gitServer.GitInit(p.gitRepoName) p.UpdateKluctlYaml(func(o *uo.UnstructuredObject) error { - _ = o.SetNestedField(fmt.Sprintf("%s-{{ target.name }}", rand.String(16)), "discriminator") + _ = o.SetNestedField(fmt.Sprintf("%s-{{ target.name or 'no-name' }}", rand.String(16)), "discriminator") return nil }) p.UpdateDeploymentYaml(".", func(c *uo.UnstructuredObject) error { diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index 6ebb6fa86..a16a9e027 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -48,6 +48,7 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, params Targe } var target *types.Target + needRender := false if params.TargetName != "" { t, err := p.FindDynamicTarget(params.TargetName) if err != nil { @@ -55,7 +56,10 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, params Targe } target = &*t.Target } else { - target = &types.Target{} + target = &types.Target{ + Discriminator: p.Config.Discriminator, + } + needRender = true } if params.TargetNameOverride != "" { target.Name = params.TargetNameOverride @@ -64,6 +68,14 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, params Targe target.Context = ¶ms.ContextOverride } + if needRender { + // we must render the target after handling overrides + err = p.renderTarget(target) + if err != nil { + return nil, err + } + } + params.Images.PrependFixedImages(target.Images) clientConfig, clusterContext, err := p.loadK8sConfig(target, params.OfflineK8s) From 9162ce95222d33045e6e1ac7e7efcb6e5cbd7546 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Feb 2023 10:13:38 +0100 Subject: [PATCH 1438/2916] chore(deps): Bump github.com/hashicorp/vault/api from 1.8.3 to 1.9.0 (#312) Bumps [github.com/hashicorp/vault/api](https://github.com/hashicorp/vault) from 1.8.3 to 1.9.0. - [Release notes](https://github.com/hashicorp/vault/releases) - [Changelog](https://github.com/hashicorp/vault/blob/main/CHANGELOG.md) - [Commits](https://github.com/hashicorp/vault/compare/v1.8.3...v1.9.0) --- updated-dependencies: - dependency-name: github.com/hashicorp/vault/api dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 18 ++---------------- go.sum | 53 ++--------------------------------------------------- 2 files changed, 4 insertions(+), 67 deletions(-) diff --git a/go.mod b/go.mod index 7963c49ef..eeb038245 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/gobwas/glob v0.2.3 // indirect github.com/golang-jwt/jwt/v4 v4.4.3 github.com/google/go-containerregistry v0.13.0 - github.com/hashicorp/vault/api v1.8.3 + github.com/hashicorp/vault/api v1.9.0 github.com/hexops/gotextdiff v1.0.3 github.com/imdario/mergo v0.3.13 github.com/jinzhu/copier v0.3.5 @@ -61,6 +61,7 @@ require ( github.com/aws/aws-sdk-go-v2 v1.17.4 github.com/aws/aws-sdk-go-v2/config v1.18.12 github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.3 + github.com/go-git/go-git/v5 v5.5.2 github.com/go-logr/logr v1.2.3 github.com/hashicorp/go-multierror v1.1.1 github.com/huandu/xstrings v1.4.0 @@ -92,8 +93,6 @@ require ( github.com/Microsoft/go-winio v0.6.0 // indirect github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 // indirect github.com/acomagu/bufpipe v1.0.3 // indirect - github.com/armon/go-metrics v0.4.1 // indirect - github.com/armon/go-radix v1.0.0 // indirect github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect github.com/aws/aws-sdk-go-v2/credentials v1.13.12 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.22 // indirect @@ -133,7 +132,6 @@ require ( github.com/go-errors/errors v1.4.2 // indirect github.com/go-git/gcfg v1.5.0 // indirect github.com/go-git/go-billy/v5 v5.4.1 // indirect - github.com/go-git/go-git/v5 v5.5.2 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.20.0 // indirect @@ -144,7 +142,6 @@ require ( github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect - github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.1.2 // indirect github.com/google/gnostic v0.6.9 // indirect github.com/google/go-cmp v0.5.9 // indirect @@ -160,20 +157,12 @@ require ( github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-hclog v1.4.0 // indirect - github.com/hashicorp/go-immutable-radix v1.3.1 // indirect - github.com/hashicorp/go-plugin v1.4.8 // indirect github.com/hashicorp/go-retryablehttp v0.7.1 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect - github.com/hashicorp/go-secure-stdlib/mlock v0.1.2 // indirect github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 // indirect github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect github.com/hashicorp/go-sockaddr v1.0.2 // indirect - github.com/hashicorp/go-uuid v1.0.3 // indirect - github.com/hashicorp/go-version v1.6.0 // indirect - github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/hashicorp/vault/sdk v0.7.0 // indirect - github.com/hashicorp/yamux v0.1.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jmoiron/sqlx v1.3.5 // indirect @@ -191,7 +180,6 @@ require ( github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect - github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moby/locker v1.0.1 // indirect @@ -202,12 +190,10 @@ require ( github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/oklog/run v1.1.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc2 // indirect github.com/pelletier/go-toml/v2 v2.0.6 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect - github.com/pierrec/lz4 v2.6.1+incompatible // indirect github.com/pjbgf/sha1cd v0.2.3 // indirect github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect diff --git a/go.sum b/go.sum index a2bfbdd9b..a529767df 100644 --- a/go.sum +++ b/go.sum @@ -73,7 +73,6 @@ github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= -github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= @@ -101,19 +100,13 @@ github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat6 github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= -github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= -github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= @@ -171,7 +164,6 @@ github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTx github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= @@ -179,8 +171,6 @@ github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHe github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= -github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= github.com/cloudflare/circl v1.3.0 h1:Anq00jxDtoyX3+aCaYUZ0vXC5r4k4epberfWGDXV1zE= @@ -252,7 +242,6 @@ github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSY github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= github.com/fluxcd/go-git/v5 v5.0.0-20221206140629-ec778c2c37df h1:2BHXJp1PwX7D47Q2oaKDekn+BZVZCmxeCWNi+FyownE= @@ -287,7 +276,6 @@ github.com/go-gorp/gorp/v3 v3.0.2/go.mod h1:BJ3q1ejpV8cVALtcXvXaXyTOlMmJhWDxTmnc github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= @@ -367,8 +355,6 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -441,7 +427,6 @@ github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyN github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= @@ -449,22 +434,15 @@ github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrj github.com/hashicorp/go-hclog v1.4.0 h1:ctuWFGrhFha8BnnzxqeRGidlEcQkDyL5u8J8t5eA11I= github.com/hashicorp/go-hclog v1.4.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= -github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-plugin v1.4.8 h1:CHGwpxYDOttQOY7HOWgETU9dyVjOXzniXDqJcYJE1zM= -github.com/hashicorp/go-plugin v1.4.8/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= -github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ= github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= -github.com/hashicorp/go-secure-stdlib/mlock v0.1.2 h1:p4AKXPPS24tO8Wc8i1gLvSKdmkiSY5xuju57czJ/IJQ= -github.com/hashicorp/go-secure-stdlib/mlock v0.1.2/go.mod h1:zq93CJChV6L9QTfGKtfBxKqD7BqqXx5O04A/ns2p5+I= github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 h1:UpiO20jno/eV1eVZcxqWnUohyKRe1g8FPV/xH1s/2qs= github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U= @@ -476,27 +454,18 @@ github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjG github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= -github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= -github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= -github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hashicorp/vault/api v1.8.3 h1:cHQOLcMhBR+aVI0HzhPxO62w2+gJhIrKguQNONPzu6o= -github.com/hashicorp/vault/api v1.8.3/go.mod h1:4g/9lj9lmuJQMtT6CmVMHC5FW1yENaVv+Nv4ZfG8fAg= -github.com/hashicorp/vault/sdk v0.7.0 h1:2pQRO40R1etpKkia5fb4kjrdYMx3BHklPxl1pxpxDHg= -github.com/hashicorp/vault/sdk v0.7.0/go.mod h1:KyfArJkhooyba7gYCKSq8v66QdqJmnbAxtV/OX1+JTs= -github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= -github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= +github.com/hashicorp/vault/api v1.9.0 h1:ab7dI6W8DuCY7yCU8blo0UCYl2oHre/dloCmzMWg9w8= +github.com/hashicorp/vault/api v1.9.0/go.mod h1:lloELQP4EyhjnCQhF8agKvWIVTmxbpEJj70b98959sM= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= @@ -517,7 +486,6 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= -github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg= github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= @@ -528,7 +496,6 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= @@ -621,8 +588,6 @@ github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= -github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= @@ -661,8 +626,6 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/ohler55/ojg v1.17.4 h1:6Ss87DyAZHU0ODZu6Cmuahj5UiVaRD1n8C4KNm0qMYg= github.com/ohler55/ojg v1.17.4/go.mod h1:7Ghirupn8NC8hSSDpI0gcjorPxj+vSVIONDWfliHR1k= -github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= -github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo/v2 v2.7.0 h1:/XxtEV3I3Eif/HobnVx9YmJgk8ENdRsuUmM+fLCFNow= github.com/onsi/gomega v1.26.0 h1:03cDLK28U6hWvCAns6NeydX3zIm4SF3ci69ulidS32Q= @@ -681,16 +644,12 @@ github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT9 github.com/otiai10/mint v1.4.0 h1:umwcf7gbpEwf7WFzqmWwSv0CzbeMsae2u9ZvpP8j2q4= github.com/otiai10/mint v1.4.0/go.mod h1:gifjb2MYOoULtKLqUAEILUG/9KONW6f7YsJ6vQLTlFI= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= -github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= -github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= -github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pjbgf/sha1cd v0.2.3 h1:uKQP/7QOzNtKYH7UTohZLcjF5/55EnTw0jO/Ru4jZwI= github.com/pjbgf/sha1cd v0.2.3/go.mod h1:HOK9QrgzdHpbc2Kzip0Q1yi3M2MFGPADtR6HjG65m5M= github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ= @@ -711,24 +670,20 @@ github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= -github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.39.0 h1:oOyhkDq05hPZKItWVBkJ6g6AtGxi+fy7F4JvUV8uhsI= github.com/prometheus/common v0.39.0/go.mod h1:6XBZ7lYdLCbkAVhwRsWTZn+IN5AB9F/NXd5w0BbEX0Y= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/r3labs/diff/v2 v2.15.1 h1:EOrVqPUzi+njlumoqJwiS/TgGgmZo83619FNDB9xQUg= @@ -759,7 +714,6 @@ github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5g github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= @@ -806,7 +760,6 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/urfave/cli v1.22.7 h1:aXiFAgRugfJ27UFDsGJ9DB2FvTC73hlVXFSqq5bo9eU= github.com/urfave/cli v1.22.7/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/vbatts/tar-split v0.11.2 h1:Via6XqJr0hceW4wff3QRzD5gAk/tatMw/4ZA7cTlIME= @@ -1005,7 +958,6 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1292,7 +1244,6 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= From 0a998d887e52ff157a53c8a62f9f7e7f93fb5fa2 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 10 Feb 2023 18:58:29 +0100 Subject: [PATCH 1439/2916] feat: Remove targetConfig and dynamic targets related features --- cmd/kluctl/commands/cmd_list_targets.go | 4 +- cmd/kluctl/commands/cmd_seal.go | 27 +--- cmd/kluctl/commands/completion.go | 12 +- pkg/kluctl_project/git.go | 50 ------ pkg/kluctl_project/project.go | 17 +- pkg/kluctl_project/project_load.go | 5 - pkg/kluctl_project/target_context.go | 4 +- pkg/kluctl_project/targets.go | 206 +----------------------- pkg/kluctl_project/targets_test.go | 26 --- pkg/types/kluctl_project.go | 24 +-- 10 files changed, 30 insertions(+), 345 deletions(-) delete mode 100644 pkg/kluctl_project/git.go delete mode 100644 pkg/kluctl_project/targets_test.go diff --git a/cmd/kluctl/commands/cmd_list_targets.go b/cmd/kluctl/commands/cmd_list_targets.go index 7d2649716..ce39e8476 100644 --- a/cmd/kluctl/commands/cmd_list_targets.go +++ b/cmd/kluctl/commands/cmd_list_targets.go @@ -19,8 +19,8 @@ func (cmd *listTargetsCmd) Help() string { func (cmd *listTargetsCmd) Run(ctx context.Context) error { return withKluctlProjectFromArgs(ctx, cmd.ProjectFlags, nil, true, false, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { var result []*types.Target - for _, t := range p.DynamicTargets { - result = append(result, t.Target) + for _, t := range p.Targets { + result = append(result, t) } return outputYamlResult(ctx, cmd.Output, result, false) }) diff --git a/cmd/kluctl/commands/cmd_seal.go b/cmd/kluctl/commands/cmd_seal.go index ec00b4612..69ecad6ee 100644 --- a/cmd/kluctl/commands/cmd_seal.go +++ b/cmd/kluctl/commands/cmd_seal.go @@ -158,35 +158,18 @@ func (cmd *sealCmd) Run(ctx context.Context) error { return withKluctlProjectFromArgs(ctx, cmd.ProjectFlags, nil, true, false, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { hadError := false - baseTargets := make(map[string]bool) noTargetMatch := true - for _, target := range p.DynamicTargets { - if cmd.Target != "" && cmd.Target != target.Target.Name { + for _, target := range p.Targets { + if cmd.Target != "" && cmd.Target != target.Name { continue } - if target.Target.SealingConfig == nil { - status.Info(ctx, "Target %s has no sealingConfig", target.Target.Name) + if target.SealingConfig == nil { + status.Info(ctx, "Target %s has no sealingConfig", target.Name) continue } noTargetMatch = false - sealTarget := target.Target - dynamicSealing := target.Target.SealingConfig.DynamicSealing == nil || *target.Target.SealingConfig.DynamicSealing - isDynamicTarget := target.BaseTargetName != target.Target.Name - if !dynamicSealing && isDynamicTarget { - baseTarget, err := p.FindBaseTarget(target.BaseTargetName) - if err != nil { - return err - } - if baseTargets[target.BaseTargetName] { - // Skip this target as it was already sealed - continue - } - baseTargets[target.BaseTargetName] = true - sealTarget = baseTarget - } - - err := cmd.runCmdSealForTarget(ctx, p, sealTarget.Name) + err := cmd.runCmdSealForTarget(ctx, p, target.Name) if err != nil { hadError = true status.Error(ctx, err.Error()) diff --git a/cmd/kluctl/commands/completion.go b/cmd/kluctl/commands/completion.go index 225ae0fba..77e7fe978 100644 --- a/cmd/kluctl/commands/completion.go +++ b/cmd/kluctl/commands/completion.go @@ -59,8 +59,8 @@ func buildTargetCompletionFunc(ctx context.Context, projectArgs *args.ProjectFla return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { var ret []string err := withProjectForCompletion(ctx, projectArgs, argsFlags, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { - for _, t := range p.DynamicTargets { - ret = append(ret, t.Target.Name) + for _, t := range p.Targets { + ret = append(ret, t.Name) } return nil }) @@ -107,8 +107,8 @@ func buildInclusionCompletionFunc(ctx context.Context, cmdStruct interface{}, fo err := withProjectForCompletion(ctx, &ptArgs.projectFlags, &ptArgs.argsFlags, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { var targets []string if ptArgs.targetFlags.Target == "" { - for _, t := range p.DynamicTargets { - targets = append(targets, t.Target.Name) + for _, t := range p.Targets { + targets = append(targets, t.Name) } } else { targets = append(targets, ptArgs.targetFlags.Target) @@ -161,8 +161,8 @@ func buildImagesCompletionFunc(ctx context.Context, cmdStruct interface{}) func( err := withProjectForCompletion(ctx, &ptArgs.projectFlags, &ptArgs.argsFlags, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { var targets []string if ptArgs.targetFlags.Target == "" { - for _, t := range p.DynamicTargets { - targets = append(targets, t.Target.Name) + for _, t := range p.Targets { + targets = append(targets, t.Name) } } else { targets = append(targets, ptArgs.targetFlags.Target) diff --git a/pkg/kluctl_project/git.go b/pkg/kluctl_project/git.go deleted file mode 100644 index 393836704..000000000 --- a/pkg/kluctl_project/git.go +++ /dev/null @@ -1,50 +0,0 @@ -package kluctl_project - -import ( - "fmt" - git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" - "sync" -) - -func (c *LoadedKluctlProject) updateGitCaches() error { - var waitGroup sync.WaitGroup - var firstError error - var firstErrorLock sync.Mutex - - doError := func(err error) { - firstErrorLock.Lock() - defer firstErrorLock.Unlock() - if firstError == nil { - firstError = err - } - } - - doUpdateGitProject := func(u git_url.GitUrl) error { - waitGroup.Add(1) - go func() { - defer waitGroup.Done() - - _, err := c.RP.GetEntry(u) - if err != nil { - doError(fmt.Errorf("failed to update git project %v: %v", u.String(), err)) - } - }() - - return nil - } - - for _, target := range c.Config.Targets { - if target.TargetConfig == nil || target.TargetConfig.Project == nil { - continue - } - - err := doUpdateGitProject(target.TargetConfig.Project.Url) - if err != nil { - waitGroup.Wait() - return err - } - } - - waitGroup.Wait() - return firstError -} diff --git a/pkg/kluctl_project/project.go b/pkg/kluctl_project/project.go index cc3eb603a..d959f5dc3 100644 --- a/pkg/kluctl_project/project.go +++ b/pkg/kluctl_project/project.go @@ -21,28 +21,19 @@ type LoadedKluctlProject struct { sealedSecretsDir string - Config types2.KluctlProject - DynamicTargets []*types2.DynamicTarget + Config types2.KluctlProject + Targets []*types2.Target J2 *jinja2.Jinja2 RP *repocache.GitRepoCache SopsDecrypter *decryptor.Decryptor } -func (c *LoadedKluctlProject) FindBaseTarget(name string) (*types2.Target, error) { - for _, target := range c.Config.Targets { +func (c *LoadedKluctlProject) FindTarget(name string) (*types2.Target, error) { + for _, target := range c.Targets { if target.Name == name { return target, nil } } return nil, fmt.Errorf("target %s not existent in kluctl project config", name) } - -func (c *LoadedKluctlProject) FindDynamicTarget(name string) (*types2.DynamicTarget, error) { - for _, target := range c.DynamicTargets { - if target.Target.Name == name { - return target, nil - } - } - return nil, fmt.Errorf("target %s not existent in kluctl project config", name) -} diff --git a/pkg/kluctl_project/project_load.go b/pkg/kluctl_project/project_load.go index 2af17c0d0..0d9648db1 100644 --- a/pkg/kluctl_project/project_load.go +++ b/pkg/kluctl_project/project_load.go @@ -57,11 +57,6 @@ func (c *LoadedKluctlProject) loadKluctlProject() error { s := status.Start(c.ctx, "Loading kluctl project") defer s.Failed() - err = c.updateGitCaches() - if err != nil { - return err - } - c.sealedSecretsDir = filepath.Join(c.ProjectDir, ".sealed-secrets") sealedSecretsUsed := false diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index a16a9e027..4985784be 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -50,11 +50,11 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, params Targe var target *types.Target needRender := false if params.TargetName != "" { - t, err := p.FindDynamicTarget(params.TargetName) + t, err := p.FindTarget(params.TargetName) if err != nil { return nil, err } - target = &*t.Target + target = &*t } else { target = &types.Target{ Discriminator: p.Config.Discriminator, diff --git a/pkg/kluctl_project/targets.go b/pkg/kluctl_project/targets.go index 59c27ee08..5a7ceb06d 100644 --- a/pkg/kluctl_project/targets.go +++ b/pkg/kluctl_project/targets.go @@ -1,16 +1,10 @@ package kluctl_project import ( - "fmt" - securejoin "github.com/cyphar/filepath-securejoin" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" - "github.com/kluctl/kluctl/v2/pkg/yaml" - "os" - "regexp" "sort" - "strings" ) type dynamicTargetInfo struct { @@ -27,24 +21,11 @@ func (c *LoadedKluctlProject) loadTargets() error { defer status.Trace(c.ctx, "Done loading targets") targetNames := make(map[string]bool) - c.DynamicTargets = nil + c.Targets = nil - var targetInfos []*dynamicTargetInfo - for _, baseTarget := range c.Config.Targets { - l, err := c.prepareDynamicTargets(baseTarget) + for _, configTarget := range c.Config.Targets { + target, err := c.buildTarget(configTarget) if err != nil { - return err - } - targetInfos = append(targetInfos, l...) - } - - for _, targetInfo := range targetInfos { - target, err := c.buildDynamicTarget(targetInfo) - if err != nil { - // Only fail if non-dynamic targets fail to load - if targetInfo.refPattern == nil { - return err - } status.Warning(c.ctx, "Failed to load dynamic target config for project: %v", err) continue } @@ -59,14 +40,11 @@ func (c *LoadedKluctlProject) loadTargets() error { status.Warning(c.ctx, "Duplicate target %s", target.Name) } else { targetNames[target.Name] = true - c.DynamicTargets = append(c.DynamicTargets, &types.DynamicTarget{ - Target: target, - BaseTargetName: targetInfo.baseTarget.Name, - }) + c.Targets = append(c.Targets, target) } } - sort.SliceStable(c.DynamicTargets, func(i, j int) bool { - return c.DynamicTargets[i].Target.Name < c.DynamicTargets[j].Target.Name + sort.SliceStable(c.Targets, func(i, j int) bool { + return c.Targets[i].Name < c.Targets[j].Name }) return nil } @@ -91,182 +69,14 @@ func (c *LoadedKluctlProject) renderTarget(target *types.Target) error { return retErr } -func (c *LoadedKluctlProject) prepareDynamicTargets(baseTarget *types.Target) ([]*dynamicTargetInfo, error) { - if baseTarget.TargetConfig != nil && baseTarget.TargetConfig.Project != nil { - return c.prepareDynamicTargetsExternal(baseTarget) - } else { - return c.prepareDynamicTargetsSimple(baseTarget) - } -} - -func (c *LoadedKluctlProject) prepareDynamicTargetsSimple(baseTarget *types.Target) ([]*dynamicTargetInfo, error) { - if baseTarget.TargetConfig != nil { - if baseTarget.TargetConfig.Ref != nil || baseTarget.TargetConfig.RefPattern != nil { - return nil, fmt.Errorf("'ref' and/or 'refPattern' are not allowed for non-external dynamic targets") - } - } - dynamicTargets := []*dynamicTargetInfo{ - { - baseTarget: baseTarget, - dir: c.ProjectDir, - }, - } - return dynamicTargets, nil -} - -func (c *LoadedKluctlProject) prepareDynamicTargetsExternal(baseTarget *types.Target) ([]*dynamicTargetInfo, error) { - ge, err := c.RP.GetEntry(baseTarget.TargetConfig.Project.Url) - if err != nil { - return nil, err - } - - repoInfo := ge.GetRepoInfo() - - if baseTarget.TargetConfig.Ref != nil && baseTarget.TargetConfig.RefPattern != nil { - return nil, fmt.Errorf("'refPattern' and 'ref' can't be specified together") - } - - if baseTarget.TargetConfig.RefPattern != nil { - status.Deprecation(c.ctx, "dynamic-targets-ref-pattern", "'refPattern' and corresponding dynamic targets are deprecated and will be removed in the next kluctl release.") - } - - targetConfigRef := baseTarget.TargetConfig.Ref - refPattern := baseTarget.TargetConfig.RefPattern - - defaultBranch := repoInfo.DefaultRef - if defaultBranch == "" { - return nil, fmt.Errorf("git project %v seems to have no default branch", baseTarget.TargetConfig.Project.Url.String()) - } - - if baseTarget.TargetConfig.Ref == nil && baseTarget.TargetConfig.RefPattern == nil { - // use default branch of repo - targetConfigRef = &defaultBranch - } - - refs := repoInfo.RemoteRefs - if targetConfigRef != nil { - if _, ok := refs[fmt.Sprintf("refs/heads/%s", *targetConfigRef)]; !ok { - return nil, fmt.Errorf("git project %s has no ref %s", baseTarget.TargetConfig.Project.Url.String(), *targetConfigRef) - } - refPattern = targetConfigRef - } - - var dynamicTargets []*dynamicTargetInfo - for ref := range refs { - m, refShortName, err := c.matchRef(ref, *refPattern) - if err != nil { - return nil, err - } - if !m { - continue - } - - ge, err := c.RP.GetEntry(baseTarget.TargetConfig.Project.Url) - if err != nil { - return nil, err - } - - dir, _, err := ge.GetClonedDir(refShortName) - if err != nil { - return nil, err - } - - dynamicTargets = append(dynamicTargets, &dynamicTargetInfo{ - baseTarget: baseTarget, - dir: dir, - gitProject: baseTarget.TargetConfig.Project, - ref: &refShortName, - refPattern: refPattern, - defaultBranch: defaultBranch, - }) - } - return dynamicTargets, nil -} - -func (c *LoadedKluctlProject) matchRef(s string, pattern string) (bool, string, error) { - if strings.HasPrefix(pattern, "refs/") { - p, err := regexp.Compile(fmt.Sprintf("^%s$", pattern)) - if err != nil { - return false, "", err - } - return p.MatchString(s), s, nil - } - p1, err := regexp.Compile(fmt.Sprintf("^refs/heads/%s$", pattern)) - if err != nil { - return false, "", err - } - p2, err := regexp.Compile(fmt.Sprintf("^refs/tags/%s$", pattern)) - if err != nil { - return false, "", err - } - if p1.MatchString(s) { - return true, s[len("refs/heads/"):], nil - } else if p2.MatchString(s) { - return true, s[len("refs/tags/"):], nil - } else { - return false, "", nil - } -} - -func (c *LoadedKluctlProject) loadTargetConfigFile(targetInfo *dynamicTargetInfo) ([]byte, error) { - configFile := yaml.FixNameExt(targetInfo.dir, "target-config.yml") - if targetInfo.baseTarget.TargetConfig.File != nil { - configFile = *targetInfo.baseTarget.TargetConfig.File - } - configPath, err := securejoin.SecureJoin(targetInfo.dir, configFile) - if err != nil { - return nil, err - } - if !utils.IsFile(configPath) { - return nil, fmt.Errorf("no target config file with name %s found in target", configFile) - } - - return os.ReadFile(configPath) -} - -func (c *LoadedKluctlProject) buildDynamicTarget(targetInfo *dynamicTargetInfo) (*types.Target, error) { +func (c *LoadedKluctlProject) buildTarget(configTarget *types.Target) (*types.Target, error) { var target types.Target - err := utils.DeepCopy(&target, targetInfo.baseTarget) + err := utils.DeepCopy(&target, configTarget) if err != nil { return nil, err } if target.Discriminator == "" { target.Discriminator = c.Config.Discriminator } - if targetInfo.baseTarget.TargetConfig == nil { - return &target, nil - } - - status.Deprecation(c.ctx, "target-config", "'targetConfig' in targets is deprecated and will be removed in the next kluctl release.") - - configFile, err := c.loadTargetConfigFile(targetInfo) - if err != nil { - return nil, err - } - - var targetConfig types.TargetConfig - err = yaml.ReadYamlBytes(configFile, &targetConfig) - if err != nil { - return nil, err - } - - if len(target.DynamicArgs) != 0 { - status.Deprecation(c.ctx, "dynamic-args", "dynamicArgs are deprecated and ignored. The field will be removed in the next kluctl release.") - } - - // check and merge args - if targetConfig.Args != nil { - target.Args.Merge(targetConfig.Args) - } - if len(targetConfig.Images) != 0 { - status.Deprecation(c.ctx, "target-config-images", "specifying fixed images via external 'targetConfig' is deprecated and support for this will be removed in the next kluctl release.") - } - // We prepend the dynamic images to ensure they get higher priority later - target.Images = append(targetConfig.Images, target.Images...) - - if targetInfo.ref != nil { - target.TargetConfig.Ref = targetInfo.ref - } - return &target, nil } diff --git a/pkg/kluctl_project/targets_test.go b/pkg/kluctl_project/targets_test.go deleted file mode 100644 index 8f1cf30b1..000000000 --- a/pkg/kluctl_project/targets_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package kluctl_project - -import ( - "github.com/kluctl/kluctl/v2/pkg/types" - "github.com/stretchr/testify/assert" - "os" - "path/filepath" - "testing" -) - -func TestLoadTargetConfigFile(t *testing.T) { - c := LoadedKluctlProject{} - ti := &dynamicTargetInfo{ - baseTarget: &types.Target{ - TargetConfig: &types.ExternalTargetConfig{}, - }, - dir: t.TempDir(), - } - err := os.WriteFile(filepath.Join(ti.dir, "target-config.yml"), []byte("test"), 0600) - assert.NoError(t, err) - - data, err := c.loadTargetConfigFile(ti) - assert.NoError(t, err) - - assert.Equal(t, []byte("test"), data) -} diff --git a/pkg/types/kluctl_project.go b/pkg/types/kluctl_project.go index 33078a4ae..840542dd6 100644 --- a/pkg/types/kluctl_project.go +++ b/pkg/types/kluctl_project.go @@ -8,22 +8,10 @@ type DynamicArg struct { Name string `yaml:"name" validate:"required"` } -type ExternalTargetConfig struct { - Project *GitProject `yaml:"project,omitempty"` - // Ref Branch/Tag to be used. Can't be combined with 'refPattern'. If 'branch' and 'branchPattern' are not used, 'branch' defaults to the default branch of targetConfig.project - Ref *string `yaml:"ref,omitempty"` - // RefPattern If set, multiple dynamic targets are created, each with 'ref' being set to the ref that matched the given pattern. - RefPattern *string `yaml:"refPattern,omitempty"` - // File defaults to 'target-config.yml' - File *string `yaml:"file,omitempty"` -} - type SealingConfig struct { - // DynamicSealing Set this to false if you want to disable sealing for every dynamic target - DynamicSealing *bool `yaml:"dynamicSealing,omitempty"` - Args *uo.UnstructuredObject `yaml:"args,omitempty"` - SecretSets []string `yaml:"secretSets,omitempty"` - CertFile *string `yaml:"certFile,omitempty"` + Args *uo.UnstructuredObject `yaml:"args,omitempty"` + SecretSets []string `yaml:"secretSets,omitempty"` + CertFile *string `yaml:"certFile,omitempty"` } type Target struct { @@ -31,17 +19,11 @@ type Target struct { Context *string `yaml:"context,omitempty"` Args *uo.UnstructuredObject `yaml:"args,omitempty"` DynamicArgs []DynamicArg `yaml:"dynamicArgs,omitempty"` - TargetConfig *ExternalTargetConfig `yaml:"targetConfig,omitempty"` SealingConfig *SealingConfig `yaml:"sealingConfig,omitempty"` Images []FixedImage `yaml:"images,omitempty"` Discriminator string `yaml:"discriminator,omitempty"` } -type DynamicTarget struct { - Target *Target `yaml:"target" validate:"required"` - BaseTargetName string `yaml:"baseTargetName"` -} - type DeploymentArg struct { Name string `yaml:"name" validate:"required"` Default interface{} `yaml:"default,omitempty"` From 65ce9dfe09adb391e0c9a0f49c285bd58164f6fd Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 13 Feb 2023 10:26:38 +0100 Subject: [PATCH 1440/2916] refactor: Use semver lib for checkNewVersion --- cmd/kluctl/commands/root.go | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index 2b37ba7d3..3736d00ad 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -18,6 +18,7 @@ package commands import ( "context" "fmt" + "github.com/Masterminds/semver/v3" flag "github.com/spf13/pflag" "io" "log" @@ -28,10 +29,10 @@ import ( "strings" "time" + "github.com/Masterminds/semver/v3" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" - "github.com/kluctl/kluctl/v2/pkg/utils/versions" "github.com/kluctl/kluctl/v2/pkg/version" "github.com/kluctl/kluctl/v2/pkg/yaml" "github.com/mattn/go-colorable" @@ -184,10 +185,18 @@ func checkNewVersion(ctx context.Context) { if strings.HasPrefix(latestVersionStr, "v") { latestVersionStr = latestVersionStr[1:] } - latestVersion := versions.LooseVersion(latestVersionStr) - localVersion := versions.LooseVersion(version.GetVersion()) - if localVersion.Less(latestVersion, true) { - s.Update(fmt.Sprintf("You are using an outdated version (%v) of kluctl. You should update soon to version %v", localVersion, latestVersion)) + latestVersion, err := semver.NewVersion(latestVersionStr) + if err != nil { + s.FailedWithMessage("Failed to parse latest version: %v", err) + return + } + localVersion, err := semver.NewVersion(version.GetVersion()) + if err != nil { + s.FailedWithMessage("Failed to parse local version: %v", err) + return + } + if localVersion.LessThan(latestVersion) { + s.Update(fmt.Sprintf("You are using an outdated version (%v) of kluctl. You should update soon to version %v", localVersion.String(), latestVersion.String())) } else { s.Update("Your kluctl version is up-to-date") } From 1114983aa66007752907810b43201c529b469aa5 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sat, 11 Feb 2023 13:50:24 +0100 Subject: [PATCH 1441/2916] feat: Remove deprecated code in regard to querying latest image versions --- cmd/kluctl/args/images.go | 2 - .../commands/cmd_check_image_updates.go | 135 -------- cmd/kluctl/commands/root.go | 28 +- cmd/kluctl/commands/utils.go | 14 +- docs/reference/commands/common-arguments.md | 10 - pkg/deployment/images.go | 76 +---- pkg/kluctl_jinja2/ext/images_ext.py | 5 +- pkg/types/target_config.go | 2 - pkg/utils/versions/latest_version.go | 160 ---------- pkg/utils/versions/latest_version_parse.go | 299 ------------------ .../versions/latest_version_parse_test.go | 47 --- pkg/utils/versions/looseversion.go | 171 ---------- pkg/utils/versions/looseversion_test.go | 92 ------ 13 files changed, 23 insertions(+), 1018 deletions(-) delete mode 100644 cmd/kluctl/commands/cmd_check_image_updates.go delete mode 100644 pkg/utils/versions/latest_version.go delete mode 100644 pkg/utils/versions/latest_version_parse.go delete mode 100644 pkg/utils/versions/latest_version_parse_test.go delete mode 100644 pkg/utils/versions/looseversion.go delete mode 100644 pkg/utils/versions/looseversion_test.go diff --git a/cmd/kluctl/args/images.go b/cmd/kluctl/args/images.go index 7d0364722..1c6fe4b79 100644 --- a/cmd/kluctl/args/images.go +++ b/cmd/kluctl/args/images.go @@ -10,8 +10,6 @@ import ( type ImageFlags struct { FixedImage []string `group:"images" short:"F" help:"Pin an image to a given version. Expects '--fixed-image=image<:namespace:deployment:container>=result'"` FixedImagesFile existingFileType `group:"images" help:"Use .yaml file to pin image versions. See output of list-images sub-command or read the documentation for details about the output format" exts:"yml,yaml"` - UpdateImages bool `group:"images" short:"u" help:"This causes kluctl to prefer the latest image found in registries, based on the 'latest_image' filters provided to 'images.get_image(...)' calls. Use this flag if you want to update to the latest versions/tags of all images. '-u' takes precedence over '--fixed-image/--fixed-images-file', meaning that the latest images are used even if an older image is given via fixed images."` - OfflineImages bool `group:"images" help:"DEPRECATED: Omit contacting image registries and do not query for latest image tags. This flag is by default set to true. At the same time, the whole requesting of image tags from registries functionality is deprecated and will be removed from kluctl in a future release." default:"true"` } func (args *ImageFlags) LoadFixedImagesFromArgs() ([]types.FixedImage, error) { diff --git a/cmd/kluctl/commands/cmd_check_image_updates.go b/cmd/kluctl/commands/cmd_check_image_updates.go deleted file mode 100644 index 782ed9868..000000000 --- a/cmd/kluctl/commands/cmd_check_image_updates.go +++ /dev/null @@ -1,135 +0,0 @@ -package commands - -import ( - "context" - "github.com/kluctl/kluctl/v2/cmd/kluctl/args" - "github.com/kluctl/kluctl/v2/pkg/registries" - "github.com/kluctl/kluctl/v2/pkg/status" - "github.com/kluctl/kluctl/v2/pkg/utils" - "github.com/kluctl/kluctl/v2/pkg/utils/versions" - "regexp" - "sort" - "strings" - "sync" -) - -type checkImageUpdatesCmd struct { - args.ProjectFlags - args.TargetFlags -} - -func (cmd *checkImageUpdatesCmd) Help() string { - return `DEPRECATED: This is based on a best effort approach and might give many false-positives.` -} - -func (cmd *checkImageUpdatesCmd) Run(ctx context.Context) error { - status.Deprecation(ctx, "check-image-updates", "check-image-updates is deprecated and will be removed in the next kluctl release.") - ptArgs := projectTargetCommandArgs{ - projectFlags: cmd.ProjectFlags, - targetFlags: cmd.TargetFlags, - } - return withProjectCommandContext(ctx, ptArgs, func(cmdCtx *commandCtx) error { - return runCheckImageUpdates(cmdCtx) - }) -} - -func runCheckImageUpdates(cmdCtx *commandCtx) error { - renderedImages := cmdCtx.targetCtx.DeploymentCollection.FindRenderedImages() - - rh := registries.NewRegistryHelper(cmdCtx.ctx) - - imageTags := make(map[string]interface{}) - var mutex sync.Mutex - var wg sync.WaitGroup - - for _, images := range renderedImages { - for _, image := range images { - s := strings.SplitN(image, ":", 2) - if len(s) == 1 { - continue - } - repo := s[0] - if _, ok := imageTags[repo]; !ok { - wg.Add(1) - go func() { - defer wg.Done() - tags, err := rh.ListImageTags(repo) - mutex.Lock() - defer mutex.Unlock() - if err != nil { - imageTags[repo] = err - } else { - imageTags[repo] = tags - } - }() - } - } - } - wg.Wait() - - prefixPattern := regexp.MustCompile("^([a-zA-Z]+[a-zA-Z-_.]*)") - suffixPattern := regexp.MustCompile("([-][a-zA-Z]+[a-zA-Z-_.]*)$") - - var table utils.PrettyTable - table.AddRow("Object", "Image", "Old", "New") - - for ref, images := range renderedImages { - for _, image := range images { - s := strings.SplitN(image, ":", 2) - if len(s) == 1 { - status.Warning(cmdCtx.ctx, "%s: Ignoring image %s as it doesn't specify a tag", ref.String(), image) - continue - } - repo := s[0] - curTag := s[1] - repoTags, _ := imageTags[repo].([]string) - err, _ := imageTags[repo].(error) - if err != nil { - status.Warning(cmdCtx.ctx, "%s: Failed to list tags for %s. %v", ref.String(), repo, err) - continue - } - - prefix := prefixPattern.FindString(curTag) - suffix := suffixPattern.FindString(curTag) - hasDot := strings.Index(curTag, ".") != -1 - - var filteredTags []string - for _, tag := range repoTags { - hasDot2 := strings.Index(tag, ".") != -1 - if hasDot != hasDot2 { - continue - } - if prefix != "" && !strings.HasPrefix(tag, prefix) { - continue - } - if suffix != "" && !strings.HasSuffix(tag, suffix) { - continue - } - filteredTags = append(filteredTags, tag) - } - doKey := func(tag string) versions.LooseVersion { - if prefix != "" { - tag = tag[len(prefix):] - } - if suffix != "" { - tag = tag[:len(tag)-len(suffix)] - } - return versions.LooseVersion(tag) - } - sort.SliceStable(filteredTags, func(i, j int) bool { - a := doKey(filteredTags[i]) - b := doKey(filteredTags[j]) - return a.Less(b, true) - }) - latestTag := filteredTags[len(filteredTags)-1] - - if latestTag != curTag { - table.AddRow(ref.String(), repo, curTag, latestTag) - } - } - } - - table.SortRows(1) - _, _ = getStdout(cmdCtx.ctx).WriteString(table.Render([]int{60})) - return nil -} diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index 3736d00ad..4b949d272 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -18,7 +18,6 @@ package commands import ( "context" "fmt" - "github.com/Masterminds/semver/v3" flag "github.com/spf13/pflag" "io" "log" @@ -55,20 +54,19 @@ type GlobalFlags struct { type cli struct { GlobalFlags - CheckImageUpdates checkImageUpdatesCmd `cmd:"" help:"Render deployment and check if any images have new tags available"` - Delete deleteCmd `cmd:"" help:"Delete a target (or parts of it) from the corresponding cluster"` - Deploy deployCmd `cmd:"" help:"Deploys a target to the corresponding cluster"` - Diff diffCmd `cmd:"" help:"Perform a diff between the locally rendered target and the already deployed target"` - HelmPull helmPullCmd `cmd:"" help:"Recursively searches for 'helm-chart.yaml' files and pre-pulls the specified Helm charts"` - HelmUpdate helmUpdateCmd `cmd:"" help:"Recursively searches for 'helm-chart.yaml' files and checks for new available versions"` - ListImages listImagesCmd `cmd:"" help:"Renders the target and outputs all images used via 'images.get_image(...)"` - ListTargets listTargetsCmd `cmd:"" help:"Outputs a yaml list with all targets"` - PokeImages pokeImagesCmd `cmd:"" help:"Replace all images in target"` - Prune pruneCmd `cmd:"" help:"Searches the target cluster for prunable objects and deletes them"` - Render renderCmd `cmd:"" help:"Renders all resources and configuration files"` - Seal sealCmd `cmd:"" help:"Seal secrets based on target's sealingConfig"` - Validate validateCmd `cmd:"" help:"Validates the already deployed deployment"` - Flux fluxCmd `cmd:"" help:"Flux sub-commands"` + Delete deleteCmd `cmd:"" help:"Delete a target (or parts of it) from the corresponding cluster"` + Deploy deployCmd `cmd:"" help:"Deploys a target to the corresponding cluster"` + Diff diffCmd `cmd:"" help:"Perform a diff between the locally rendered target and the already deployed target"` + HelmPull helmPullCmd `cmd:"" help:"Recursively searches for 'helm-chart.yaml' files and pre-pulls the specified Helm charts"` + HelmUpdate helmUpdateCmd `cmd:"" help:"Recursively searches for 'helm-chart.yaml' files and checks for new available versions"` + ListImages listImagesCmd `cmd:"" help:"Renders the target and outputs all images used via 'images.get_image(...)"` + ListTargets listTargetsCmd `cmd:"" help:"Outputs a yaml list with all targets"` + PokeImages pokeImagesCmd `cmd:"" help:"Replace all images in target"` + Prune pruneCmd `cmd:"" help:"Searches the target cluster for prunable objects and deletes them"` + Render renderCmd `cmd:"" help:"Renders all resources and configuration files"` + Seal sealCmd `cmd:"" help:"Seal secrets based on target's sealingConfig"` + Validate validateCmd `cmd:"" help:"Validates the already deployed deployment"` + Flux fluxCmd `cmd:"" help:"Flux sub-commands"` Version versionCmd `cmd:"" help:"Print kluctl version"` } diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index b2807f72b..35fa12bab 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -15,7 +15,6 @@ import ( ssh_pool "github.com/kluctl/kluctl/v2/pkg/git/ssh-pool" "github.com/kluctl/kluctl/v2/pkg/kluctl_jinja2" "github.com/kluctl/kluctl/v2/pkg/kluctl_project" - "github.com/kluctl/kluctl/v2/pkg/registries" "github.com/kluctl/kluctl/v2/pkg/repocache" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/utils" @@ -145,18 +144,7 @@ func withProjectCommandContext(ctx context.Context, args projectTargetCommandArg } func withProjectTargetCommandContext(ctx context.Context, args projectTargetCommandArgs, p *kluctl_project.LoadedKluctlProject, cb func(cmdCtx *commandCtx) error) error { - rh := registries.NewRegistryHelper(ctx) - err := rh.ParseAuthEntriesFromEnv() - if err != nil { - return fmt.Errorf("failed to parse registry auth from environment: %w", err) - } - if args.imageFlags.UpdateImages { - status.Deprecation(ctx, "update-images", "--update-images is deprecated and will be removed in the next kluctl release.") - } - if !args.imageFlags.OfflineImages { - status.Deprecation(ctx, "online-images", "--offline-images=false is deprecated and will be removed in the next kluctl release.") - } - images, err := deployment.NewImages(rh, args.imageFlags.UpdateImages, args.imageFlags.OfflineImages || args.forCompletion) + images, err := deployment.NewImages() if err != nil { return err } diff --git a/docs/reference/commands/common-arguments.md b/docs/reference/commands/common-arguments.md index dbdf48621..2f2e85433 100644 --- a/docs/reference/commands/common-arguments.md +++ b/docs/reference/commands/common-arguments.md @@ -98,16 +98,6 @@ Image arguments: '--fixed-image=image<:namespace:deployment:container>=result' --fixed-images-file existingfile Use .yaml file to pin image versions. See output of list-images sub-command or read the documentation for details about the output format - --offline-images DEPRECATED: Omit contacting image registries and do not query for latest - image tags. This flag is by default set to true. At the same time, the - whole requesting of image tags from registries functionality is - deprecated and will be removed from kluctl in a future release. (default true) - -u, --update-images This causes kluctl to prefer the latest image found in registries, based - on the 'latest_image' filters provided to 'images.get_image(...)' calls. - Use this flag if you want to update to the latest versions/tags of all - images. '-u' takes precedence over '--fixed-image/--fixed-images-file', - meaning that the latest images are used even if an older image is given - via fixed images. ``` diff --git a/pkg/deployment/images.go b/pkg/deployment/images.go index 11112761a..b1af402d6 100644 --- a/pkg/deployment/images.go +++ b/pkg/deployment/images.go @@ -6,13 +6,10 @@ import ( "encoding/base64" "fmt" "github.com/kluctl/kluctl/v2/pkg/k8s" - "github.com/kluctl/kluctl/v2/pkg/registries" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" - "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" - "github.com/kluctl/kluctl/v2/pkg/utils/versions" "github.com/kluctl/kluctl/v2/pkg/yaml" "k8s.io/apimachinery/pkg/api/errors" "sort" @@ -21,22 +18,13 @@ import ( ) type Images struct { - rh *registries.RegistryHelper - updateImages bool - offline bool - fixedImages []types.FixedImage - seenImages []types.FixedImage - mutex sync.Mutex - - registryCache utils.ThreadSafeMultiCache + fixedImages []types.FixedImage + seenImages []types.FixedImage + mutex sync.Mutex } -func NewImages(rh *registries.RegistryHelper, updateImages bool, offline bool) (*Images, error) { - return &Images{ - rh: rh, - updateImages: updateImages, - offline: offline, - }, nil +func NewImages() (*Images, error) { + return &Images{}, nil } func (images *Images) AddFixedImage(fi types.FixedImage) { @@ -124,44 +112,11 @@ func (images *Images) getFixedImage(image string, namespace string, deployment s return nil, nil } -func (images *Images) GetLatestImageFromRegistry(image string, latestVersion string) (*string, error) { - if images.offline { - return nil, nil - } - - ret, err := images.registryCache.Get(image, "tag", func() (interface{}, error) { - return images.rh.ListImageTags(image) - }) - if err != nil { - return nil, err - } - tags, _ := ret.([]string) - - if len(tags) == 0 { - return nil, nil - } - - lv, err := versions.ParseLatestVersion(latestVersion) - if err != nil { - return nil, err - } - - tags = versions.Filter(lv, tags) - if len(tags) == 0 { - return nil, fmt.Errorf("no tag matched latest_version: %s", latestVersion) - } - - latest := lv.Latest(tags) - result := fmt.Sprintf("%s:%s", image, latest) - return &result, nil -} - const beginPlaceholder = "XXXXXbegin_get_image_" const endPlaceholder = "_end_get_imageXXXXX" type placeHolder struct { Image string `yaml:"image"` - LatestVersion string `yaml:"latestVersion"` HasLatestVersion bool `yaml:"hasLatestVersion"` Container string @@ -284,7 +239,7 @@ func (images *Images) ResolvePlaceholders(ctx context.Context, k *k8s.K8sCluster return err } if resultImage == nil { - return fmt.Errorf("failed to find image for %s and latest version %s", ph.Image, ph.LatestVersion) + return fmt.Errorf("failed to find fixed image for %s", ph.Image) } ph.FieldValue = ph.FieldValue[:ph.StartOffset] + *resultImage + ph.FieldValue[ph.EndOffset:] @@ -297,37 +252,22 @@ func (images *Images) ResolvePlaceholders(ctx context.Context, k *k8s.K8sCluster } func (images *Images) resolveImage(ctx context.Context, ph placeHolder, ref k8s2.ObjectRef, deployment string, deployed *string, deploymentDir string, tags []string, vars *uo.UnstructuredObject) (*string, error) { - fixed, err := images.getFixedImage(ph.Image, ref.Namespace, deployment, ph.Container, vars) - if err != nil { - return nil, err - } - if ph.HasLatestVersion { - status.Deprecation(ctx, "latest-version-filter", "latest_version is deprecated when using images.get_image() and will be removed in the next kluctl release.") + status.Deprecation(ctx, "latest-version-filter", "latest_version is deprecated when using images.get_image() and is completely ignored. Please remove usages of latest_version as it will fail to render in a future kluctl release.") } - registry, err := images.GetLatestImageFromRegistry(ph.Image, ph.LatestVersion) + result, err := images.getFixedImage(ph.Image, ref.Namespace, deployment, ph.Container, vars) if err != nil { return nil, err } - result := deployed - if result == nil || images.updateImages { - result = registry - } - if !images.updateImages && fixed != nil { - result = fixed - } - si := types.FixedImage{ Image: ph.Image, DeployedImage: deployed, - RegistryImage: registry, Namespace: &ref.Namespace, Object: &ref, Deployment: &deployment, Container: &ph.Container, - VersionFilter: &ph.LatestVersion, DeployTags: tags, DeploymentDir: &deploymentDir, } diff --git a/pkg/kluctl_jinja2/ext/images_ext.py b/pkg/kluctl_jinja2/ext/images_ext.py index ba523653a..0d0fd7ebd 100644 --- a/pkg/kluctl_jinja2/ext/images_ext.py +++ b/pkg/kluctl_jinja2/ext/images_ext.py @@ -13,13 +13,10 @@ def __init__(self, environment): def get_image_wrapper(self, image, latest_version=None): has_latest_version = False - if latest_version is None: - latest_version = "semver()" - else: + if latest_version is not None: has_latest_version = True placeholder = { "image": image, - "latestVersion": str(latest_version), "hasLatestVersion": has_latest_version, } j = json.dumps(placeholder) diff --git a/pkg/types/target_config.go b/pkg/types/target_config.go index 4f27a0cf6..7540757f6 100644 --- a/pkg/types/target_config.go +++ b/pkg/types/target_config.go @@ -9,12 +9,10 @@ type FixedImage struct { Image string `yaml:"image" validate:"required"` ResultImage string `yaml:"resultImage" validate:"required"` DeployedImage *string `yaml:"deployedImage,omitempty"` - RegistryImage *string `yaml:"registryImage,omitempty"` Namespace *string `yaml:"namespace,omitempty"` Object *k8s.ObjectRef `yaml:"object,omitempty"` Deployment *string `yaml:"deployment,omitempty"` Container *string `yaml:"container,omitempty"` - VersionFilter *string `yaml:"versionFilter,omitempty"` DeployTags []string `yaml:"deployTags,omitempty"` DeploymentDir *string `yaml:"deploymentDir,omitempty"` } diff --git a/pkg/utils/versions/latest_version.go b/pkg/utils/versions/latest_version.go deleted file mode 100644 index 24edbc5b2..000000000 --- a/pkg/utils/versions/latest_version.go +++ /dev/null @@ -1,160 +0,0 @@ -package versions - -import ( - "fmt" - "github.com/kluctl/kluctl/v2/pkg/utils" - "regexp" - "strconv" -) - -type LatestVersionFilter interface { - Match(version string) bool - Latest(versions []string) string - String() string -} - -func Filter(lv LatestVersionFilter, versions []string) []string { - var filtered []string - for _, v := range versions { - if lv.Match(v) { - filtered = append(filtered, v) - } - } - return filtered -} - -type regexVersionFilter struct { - patternStr string - pattern *regexp.Regexp -} - -func NewRegexVersionFilter(pattern string) (LatestVersionFilter, error) { - p, err := regexp.Compile(fmt.Sprintf("^%s$", pattern)) - if err != nil { - return nil, err - } - return ®exVersionFilter{ - patternStr: pattern, - pattern: p, - }, nil -} - -func NewRegexVersionFilterMust(pattern string) LatestVersionFilter { - ret, err := NewRegexVersionFilter(pattern) - if err != nil { - panic(err) - } - return ret -} - -func (f *regexVersionFilter) Match(version string) bool { - return f.pattern.MatchString(version) -} - -func (f *regexVersionFilter) Latest(versions []string) string { - c := SortLooseVersionStrings(versions) - return string(c[len(c)-1]) -} - -func (f *regexVersionFilter) String() string { - return fmt.Sprintf(`regex(pattern="%s")`, f.patternStr) -} - -type looseSemVerVersionFilter struct { - allowNoNums bool -} - -func NewLooseSemVerVersionFilter(allowNoNums bool) LatestVersionFilter { - return &looseSemVerVersionFilter{allowNoNums: allowNoNums} -} - -func (f *looseSemVerVersionFilter) Match(version string) bool { - groups := looseSemverRegex.FindStringSubmatch(version) - if groups == nil { - return false - } - if !f.allowNoNums && groups[1] == "" { - return false - } - return true -} - -func (f *looseSemVerVersionFilter) Latest(versions []string) string { - c := SortLooseVersionStrings(versions) - return string(c[len(c)-1]) -} - -func (f *looseSemVerVersionFilter) String() string { - return fmt.Sprintf(`semver(allow_no_nums=%s)`, strconv.FormatBool(f.allowNoNums)) -} - -type prefixVersionFilter struct { - prefix string - suffix LatestVersionFilter - pattern *regexp.Regexp -} - -func NewPrefixVersionFilter(prefix string, suffix LatestVersionFilter) (LatestVersionFilter, error) { - if suffix == nil { - suffix = NewLooseSemVerVersionFilter(false) - } - - p, err := regexp.Compile(fmt.Sprintf(`^%s(.*)$`, prefix)) - if err != nil { - return nil, err - } - return &prefixVersionFilter{ - prefix: prefix, - suffix: suffix, - pattern: p, - }, nil -} - -func NewPrefixVersionFilterMust(prefix string, suffix LatestVersionFilter) LatestVersionFilter { - ret, err := NewPrefixVersionFilter(prefix, suffix) - if err != nil { - panic(err) - } - return ret -} - -func (f *prefixVersionFilter) Match(version string) bool { - groups := f.pattern.FindStringSubmatch(version) - if groups == nil { - return false - } - return f.suffix.Match(groups[1]) -} - -func (f *prefixVersionFilter) Latest(versions []string) string { - var filteredVersions []string - var suffixVersions []string - for _, v := range versions { - groups := f.pattern.FindStringSubmatch(v) - if groups == nil { - continue - } - filteredVersions = append(filteredVersions, v) - suffixVersions = append(suffixVersions, groups[1]) - } - latest := f.suffix.Latest(suffixVersions) - i := utils.FindStrInSlice(suffixVersions, latest) - return filteredVersions[i] -} - -func (f *prefixVersionFilter) String() string { - return fmt.Sprintf(`prefix(prefix="%s", suffix=%s)`, f.prefix, f.suffix.String()) -} - -type numberVersionFilter struct { - LatestVersionFilter -} - -func NewNumberVersionFilter() LatestVersionFilter { - f, _ := NewRegexVersionFilter("[0-9]+") - return &numberVersionFilter{f} -} - -func (f *numberVersionFilter) String() string { - return "number()" -} diff --git a/pkg/utils/versions/latest_version_parse.go b/pkg/utils/versions/latest_version_parse.go deleted file mode 100644 index 8317e46e6..000000000 --- a/pkg/utils/versions/latest_version_parse.go +++ /dev/null @@ -1,299 +0,0 @@ -package versions - -import ( - "fmt" - scanner "github.com/kluctl/kluctl/v2/pkg/utils/python_scanner" - "strconv" - "strings" -) - -type tokenAndText struct { - tok rune - text string -} - -type preparsed struct { - tokens []tokenAndText - nextPos int -} - -func (p *preparsed) CurToken() rune { - if p.nextPos > len(p.tokens) { - return scanner.EOF - } - return p.tokens[p.nextPos-1].tok -} - -func (p *preparsed) CurTokenText() string { - if p.nextPos > len(p.tokens) { - panic("CurTokenText at EOF") - } - return p.tokens[p.nextPos-1].text -} - -func (p *preparsed) Next() rune { - if p.nextPos > len(p.tokens) { - return scanner.EOF - } - p.nextPos += 1 - return p.CurToken() -} - -func (p *preparsed) Peek() rune { - return p.PeekN(0) -} - -func (p *preparsed) PeekN(n int) rune { - if p.nextPos+n >= len(p.tokens) { - return scanner.EOF - } - return p.tokens[p.nextPos+n].tok -} - -func ParseLatestVersion(str string) (LatestVersionFilter, error) { - var p preparsed - var s scanner.Scanner - s.Init(strings.NewReader(str)) - s.Mode |= scanner.ScanIdents | scanner.ScanStrings - - for true { - tok := s.Scan() - if tok == scanner.EOF { - break - } - p.tokens = append(p.tokens, tokenAndText{ - tok: tok, - text: s.TokenText(), - }) - } - - return parseFilter(&p) -} - -func parseFilter(p *preparsed) (LatestVersionFilter, error) { - tok := p.Next() - - if tok != scanner.Ident { - return nil, fmt.Errorf("unexpected token %v, expected ident", p.CurToken()) - } - name := p.CurTokenText() - - tok = p.Next() - if tok != '(' { - return nil, fmt.Errorf("unexpected token %v, expected (", tok) - } - - var f LatestVersionFilter - - var err error - switch name { - case "regex": - f, err = parseRegexFilter(p) - case "semver": - f, err = parseSemVerFilter(p) - case "prefix": - f, err = parsePrefixFilter(p) - case "number": - f, err = parseNumberFilter(p) - } - if err != nil { - return nil, err - } - - tok = p.Next() - if tok != ')' { - return nil, fmt.Errorf("unexpected token %v, expected (", tok) - } - - return f, nil -} - -func parseRegexFilter(p *preparsed) (LatestVersionFilter, error) { - args := []*arg{ - {name: "pattern", tok: scanner.String, required: true}, - } - err := parseArgs(p, args) - if err != nil { - return nil, err - } - - if args[0].value == "" { - return nil, fmt.Errorf("pattern can't be empty") - } - - return NewRegexVersionFilter(args[0].value.(string)) -} - -func parseSemVerFilter(p *preparsed) (LatestVersionFilter, error) { - args := []*arg{ - {name: "allow_no_nums", tok: scanner.Ident, value: false, isBool: true}, - } - err := parseArgs(p, args) - if err != nil { - return nil, err - } - - return NewLooseSemVerVersionFilter(args[0].value.(bool)), nil -} - -func parsePrefixFilter(p *preparsed) (LatestVersionFilter, error) { - args := []*arg{ - {name: "prefix", tok: scanner.String, required: true}, - {name: "suffix", tok: scanner.Ident, value: ""}, - } - err := parseArgs(p, args) - if err != nil { - return nil, err - } - - var suffix LatestVersionFilter - if args[1].found { - x, ok := args[1].value.(LatestVersionFilter) - if !ok { - return nil, fmt.Errorf("invalid suffix, must be a filter") - } - suffix = x - } - - return NewPrefixVersionFilter(args[0].value.(string), suffix) -} - -func parseNumberFilter(p *preparsed) (LatestVersionFilter, error) { - return NewNumberVersionFilter(), nil -} - -type arg struct { - name string - tok rune - value interface{} - required bool - isBool bool - - found bool -} - -func parseArgs(p *preparsed, args []*arg) error { - var parsedArgs []arg - parsedKvArgs := make(map[string]arg) - - for true { - var name string - - if p.Peek() == ')' { - break - } - if len(parsedArgs)+len(parsedKvArgs) != 0 { - if tok := p.Next(); tok != ',' { - return fmt.Errorf("unexpected token %v, expected ')' or ','", tok) - } - } - - a, err := parseArg(p) - if err != nil { - return err - } - - if a.name == "" { - if len(parsedKvArgs) != 0 { - return fmt.Errorf("can't have unnamed args after named args") - } - parsedArgs = append(parsedArgs, *a) - } else { - if _, ok := parsedKvArgs[name]; ok { - return fmt.Errorf("duplicate named arg %s", name) - } - parsedKvArgs[a.name] = *a - } - } - - if len(parsedArgs) > len(args) { - return fmt.Errorf("too many arguments") - } - for i, a := range parsedArgs { - if a.tok != args[i].tok { - return fmt.Errorf("unexpected argument type for %s, expected %v, got %v", a.name, args[i].tok, a.tok) - } - args[i].value = a.value - args[i].found = true - } - - for k, v := range parsedKvArgs { - foundArg := false - for _, a := range args { - if k == a.name { - foundArg = true - if a.found { - return fmt.Errorf("named arg %s has already been provided via unnamed args", k) - } - a.value = v.value - a.found = true - } - } - if !foundArg { - return fmt.Errorf("unkown arg %s", k) - } - } - - for _, a := range args { - if a.required && !a.found { - return fmt.Errorf("required arg %s not found", a.name) - } - if a.found && a.isBool { - if a.tok != scanner.Ident { - return fmt.Errorf("invalid value for arg %s, must be a bool", a.name) - } - b, err := strconv.ParseBool(a.value.(string)) - if err != nil { - return fmt.Errorf("invalid value for arg %s, must be a bool", a.name) - } - a.value = b - } - } - - return nil -} - -func parseArg(p *preparsed) (*arg, error) { - parseValue := func() (rune, interface{}, error) { - if p.Peek() == scanner.Ident && p.PeekN(1) == '(' { - f, err := parseFilter(p) - return scanner.Ident, f, err - } - tok := p.Next() - if tok != scanner.String && tok != scanner.Ident && tok != scanner.Int { - return 'e', nil, fmt.Errorf("unexpected token %v, expected string, ident or int", tok) - } - value := p.CurTokenText() - if tok == scanner.String { - value = value[1 : len(value)-1] - } - return tok, value, nil - } - - if p.Peek() == scanner.Ident && p.PeekN(1) == '=' { - p.Next() // consume ident - name := p.CurTokenText() - p.Next() // consume '=' - - valueTok, value, err := parseValue() - if err != nil { - return nil, err - } - a := arg{ - name: name, - tok: valueTok, - value: value, - } - return &a, nil - } - - valueTok, value, err := parseValue() - if err != nil { - return nil, err - } - a := arg{ - tok: valueTok, - value: value, - } - return &a, nil -} diff --git a/pkg/utils/versions/latest_version_parse_test.go b/pkg/utils/versions/latest_version_parse_test.go deleted file mode 100644 index 31ba9c483..000000000 --- a/pkg/utils/versions/latest_version_parse_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package versions - -import ( - "fmt" - "github.com/stretchr/testify/assert" - "testing" -) - -func TestParse(t *testing.T) { - - type testCase struct { - s string - expectedFilter LatestVersionFilter - expectedErr error - } - - testCases := []testCase{ - {s: "regex('a*')", expectedFilter: NewRegexVersionFilterMust("a*")}, - {s: "regex(\"a*\")", expectedFilter: NewRegexVersionFilterMust("a*")}, - {s: "regex(a*\")", expectedErr: fmt.Errorf("unexpected token 42, expected ')' or ','")}, - {s: "semver()", expectedFilter: NewLooseSemVerVersionFilter(false)}, - {s: "semver(allow_no_nums)", expectedErr: fmt.Errorf("invalid value for arg allow_no_nums, must be a bool")}, - {s: "semver(allow_no_nums=false)", expectedFilter: NewLooseSemVerVersionFilter(false)}, - {s: "semver(allow_no_nums=true)", expectedFilter: NewLooseSemVerVersionFilter(true)}, - {s: "prefix('')", expectedFilter: NewPrefixVersionFilterMust("", NewLooseSemVerVersionFilter(false))}, - {s: "prefix('a')", expectedFilter: NewPrefixVersionFilterMust("a", NewLooseSemVerVersionFilter(false))}, - {s: "prefix('a', 'b')", expectedErr: fmt.Errorf("unexpected argument type for , expected -2, got -6")}, - {s: "prefix('a', regex('a*'))", expectedFilter: NewPrefixVersionFilterMust("a", NewRegexVersionFilterMust("a*"))}, - {s: "prefix('a', suffi=regex('a*'))", expectedErr: fmt.Errorf("unkown arg suffi")}, - {s: "prefix('a', suffix=regex('a*'))", expectedFilter: NewPrefixVersionFilterMust("a", NewRegexVersionFilterMust("a*"))}, - {s: "number()", expectedFilter: NewNumberVersionFilter()}, - {s: "number(a=1)", expectedErr: fmt.Errorf("unexpected token -2, expected (")}, - } - - for _, tc := range testCases { - tc := tc - t.Run(tc.s, func(t *testing.T) { - f, err := ParseLatestVersion(tc.s) - assert.Equal(t, tc.expectedFilter, f) - if tc.expectedErr != nil { - assert.Error(t, err, tc.expectedErr) - } else if err != nil { - assert.Fail(t, "unexpected error", err) - } - }) - } -} diff --git a/pkg/utils/versions/looseversion.go b/pkg/utils/versions/looseversion.go deleted file mode 100644 index 0c6bbf529..000000000 --- a/pkg/utils/versions/looseversion.go +++ /dev/null @@ -1,171 +0,0 @@ -package versions - -import ( - "github.com/kluctl/kluctl/v2/pkg/utils" - "regexp" - "sort" - "strconv" - "strings" -) - -var looseSemverRegex = regexp.MustCompile(`^v?(([0-9]+)(\.[0-9]+)*)?(.*)$`) -var suffixComponentRegex = regexp.MustCompile(`\d+|[a-zA-Z]+|\.`) - -// Allows to compare ints and strings. Strings are always considered less than ints. -type LooseVersionSuffixElement struct { - v interface{} -} - -func (x LooseVersionSuffixElement) Less(b LooseVersionSuffixElement) bool { - ia, iaOk := x.v.(int64) - ib, ibOk := b.v.(int64) - sa, _ := x.v.(string) - sb, _ := b.v.(string) - if ibOk == iaOk { - if iaOk { - return ia < ib - } else { - return sa < sb - } - } - if iaOk { - return false - } - return true -} - -type LooseVersion string - -func (lv LooseVersion) SplitVersion() ([]int, string) { - m := looseSemverRegex.FindStringSubmatch(string(lv)) - numsStr := m[1] - suffix := m[4] - - var nums []int - for _, x := range strings.Split(numsStr, ".") { - i, _ := strconv.ParseInt(x, 10, 32) - nums = append(nums, int(i)) - } - return nums, suffix -} - -func regexSplitLikePython(s string, re *regexp.Regexp) []string { - ret := []string{s} - indexes := re.FindAllStringIndex(s, -1) - start := 0 - for _, se := range indexes { - m := s[start:se[0]] - if len(m) != 0 { - ret = append(ret, m) - } - m = s[se[0]:se[1]] - if len(m) != 0 { - ret = append(ret, m) - } - start = se[1] - } - if start < len(s) { - ret = append(ret, s[start:]) - } - return ret -} - -func splitSuffix(suffix string) []LooseVersionSuffixElement { - var components []LooseVersionSuffixElement - for i, x := range regexSplitLikePython(suffix, suffixComponentRegex) { - if i == 0 { - continue - } - if x != "." { - y, err := strconv.ParseInt(x, 10, 32) - if err == nil { - components = append(components, LooseVersionSuffixElement{v: y}) - } else { - components = append(components, LooseVersionSuffixElement{v: x}) - } - } - } - return components -} - -func (lv LooseVersion) Less(b LooseVersion, preferLongSuffix bool) bool { - aNums, aSuffixStr := lv.SplitVersion() - bNums, bSuffixStr := b.SplitVersion() - - cmp := func(a []int, b []int) bool { - l := utils.IntMin(len(a), len(b)) - for i := 0; i < l; i++ { - if a[i] < b[i] { - return true - } - if b[i] < a[i] { - return false - } - } - if len(a) < len(b) { - return true - } - return false - } - - if cmp(aNums, bNums) { - return true - } - if cmp(bNums, aNums) { - return false - } - if len(aSuffixStr) == 0 && len(bSuffixStr) != 0 { - return false - } else if len(aSuffixStr) != 0 && len(bSuffixStr) == 0 { - return true - } - - aSuffix := splitSuffix(aSuffixStr) - bSuffix := splitSuffix(bSuffixStr) - l := utils.IntMin(len(aSuffix), len(bSuffix)) - - for i := 0; i < l; i++ { - if aSuffix[i].Less(bSuffix[i]) { - return true - } else if bSuffix[i].Less(aSuffix[i]) { - return false - } - } - - if preferLongSuffix { - if len(aSuffix) < len(bSuffix) { - return true - } - } else { - if len(bSuffix) < len(aSuffix) { - return true - } - } - return false -} - -func (lv LooseVersion) Compare(b LooseVersion) int { - if lv.Less(b, true) { - return -1 - } else if b.Less(lv, true) { - return 1 - } - return 0 -} - -type LooseVersionSlice []LooseVersion - -func (x LooseVersionSlice) Less(i, j int) bool { - return x[i].Less(x[j], true) -} -func (x LooseVersionSlice) Len() int { return len(x) } -func (x LooseVersionSlice) Swap(i, j int) { x[i], x[j] = x[j], x[i] } - -func SortLooseVersionStrings(versions []string) LooseVersionSlice { - var c LooseVersionSlice - for _, v := range versions { - c = append(c, LooseVersion(v)) - } - sort.Stable(c) - return c -} diff --git a/pkg/utils/versions/looseversion_test.go b/pkg/utils/versions/looseversion_test.go deleted file mode 100644 index b25520f8d..000000000 --- a/pkg/utils/versions/looseversion_test.go +++ /dev/null @@ -1,92 +0,0 @@ -package versions - -import ( - "testing" -) - -func checkEqual(t *testing.T, a_ string, b_ string) { - a := LooseVersion(a_) - b := LooseVersion(b_) - if a.Less(b, true) { - t.Errorf("%s < %s should be false", a, b) - } - if b.Less(a, true) { - t.Errorf("%s > %s should be false", b, a) - } - if a.Compare(b) != 0 { - t.Errorf("%s == %s should be true", a, b) - } -} - -func checkLess(t *testing.T, a_ string, b_ string) { - a := LooseVersion(a_) - b := LooseVersion(b_) - if !a.Less(b, true) { - t.Errorf("%s < %s should be true", a, b) - } - if b.Less(a, true) { - t.Errorf("%s < %s should be false", b, a) - } - if a.Compare(b) != -1 { - t.Errorf("%s.Compare(%s) should be -1", a, b) - } - if b.Compare(a) != 1 { - t.Errorf("%s.Compare(%s) should be 1", b, a) - } -} - -func TestEquality(t *testing.T) { - checkEqual(t, "1", "1") - checkEqual(t, "1.1", "1.1") - checkEqual(t, "1.1a", "1.1a") - checkEqual(t, "1.1-a", "1.1-a") - checkEqual(t, "1.a-1", "1.a-1") -} - -func TestLess(t *testing.T) { - checkLess(t, "1", "2") - checkLess(t, "1", "1.1") - checkLess(t, "1.1a", "1.1") - checkLess(t, "1.1-a", "1.1") - checkLess(t, "1.1a", "1.1b") - checkLess(t, "1.1-a", "1.1-b") - checkLess(t, "1.a-1", "1.a-2") -} - -func TestMavenVersions(t *testing.T) { - checkLess(t, "1.1-SNAPSHOT", "1.1") - checkLess(t, "1", "1.1") - checkLess(t, "1-SNAPSHOT", "1.1") - checkLess(t, "1.1", "1.1.1-SNAPSHOT") - checkLess(t, "1.1-SNAPSHOT", "1.1.1-SNAPSHOT") -} - -func TestSuffixes(t *testing.T) { - checkLess(t, "1.1-1", "1.1-2") - checkLess(t, "1.1-suffix-1", "1.1-suffix-2") - checkLess(t, "1.1-suffix-2", "1.1-suffix-10") - checkLess(t, "1.1-suffix-2", "1.1-suffiy-1") - checkLess(t, "1.1-1-1", "1.1-2-1") - checkLess(t, "1.1-2-1", "1.1-2-2") - checkLess(t, "1.1-2-1", "1.1-100-2") - checkLess(t, "1.1-2-1", "1.1") - checkLess(t, "1.1-2", "1.1-2-1") - checkLess(t, "1.1-a-1", "1.1-1-1") -} - -func TestLooseVersionNoNums(t *testing.T) { - checkLess(t, "-snapshot1", "-snapshot2") - checkLess(t, "-1", "-2") - checkLess(t, "-1.1", "-1.2") -} - -func TestLooseVersionVPrefix(t *testing.T) { - checkLess(t, "v1.0", "v1.1") - checkLess(t, "v2.0", "v2.1") - checkLess(t, "v1.0", "v2.0") - checkLess(t, "1.0", "v2.0") - checkLess(t, "v1.0suffix", "v1.0") - checkLess(t, "v1.0-suffix", "v1.0") - checkLess(t, "v1.0suffix1", "v1.0suffix2") - checkLess(t, "v1.0-suffix1", "v1.0-suffix2") -} From 6f015cc99b5083ae38899b2762759e4d7d5ccb6a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 13 Feb 2023 10:37:14 +0100 Subject: [PATCH 1442/2916] feat: Remove deprecated support of args in deployment.yaml --- e2e/args_test.go | 23 +++++------------ pkg/deployment/deployment_project.go | 3 --- pkg/deployment/external_args.go | 37 ---------------------------- pkg/kluctl_project/target_context.go | 9 ------- pkg/types/deployment.go | 1 - 5 files changed, 6 insertions(+), 67 deletions(-) diff --git a/e2e/args_test.go b/e2e/args_test.go index d754bf3f6..5f689f624 100644 --- a/e2e/args_test.go +++ b/e2e/args_test.go @@ -8,7 +8,7 @@ import ( "testing" ) -func testArgs(t *testing.T, deprecated bool) { +func testArgs(t *testing.T) { t.Parallel() k := defaultCluster1 @@ -36,17 +36,10 @@ func testArgs(t *testing.T, deprecated bool) { }, } - if deprecated { - p.UpdateDeploymentYaml(".", func(o *uo.UnstructuredObject) error { - _ = o.SetNestedField(args, "args") - return nil - }) - } else { - p.UpdateKluctlYaml(func(o *uo.UnstructuredObject) error { - _ = o.SetNestedField(args, "args") - return nil - }) - } + p.UpdateKluctlYaml(func(o *uo.UnstructuredObject) error { + _ = o.SetNestedField(args, "args") + return nil + }) addConfigMapDeployment(p, "cm", map[string]string{ "a": `{{ args.a | default("na") }}`, @@ -119,12 +112,8 @@ d: assertNestedFieldEquals(t, cm, `{"nested": {"nested2": "d4"}}`, "data", "d") } -func TestDeprecatedArgs(t *testing.T) { - testArgs(t, true) -} - func TestArgs(t *testing.T) { - testArgs(t, false) + testArgs(t) } func TestArgsFromEnv(t *testing.T) { diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go index 3431c0a74..8264170d2 100644 --- a/pkg/deployment/deployment_project.go +++ b/pkg/deployment/deployment_project.go @@ -136,9 +136,6 @@ func (p *DeploymentProject) processConfig() error { return err } - if len(p.Config.Args) != 0 && p.parentProject != nil { - return fmt.Errorf("only the root deployment.yml can define args") - } return nil } diff --git a/pkg/deployment/external_args.go b/pkg/deployment/external_args.go index c7f0aefcc..e041ebdfb 100644 --- a/pkg/deployment/external_args.go +++ b/pkg/deployment/external_args.go @@ -1,15 +1,11 @@ package deployment import ( - "context" "fmt" - "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils/uo" - "github.com/kluctl/kluctl/v2/pkg/vars" "github.com/kluctl/kluctl/v2/pkg/yaml" "os" - "path/filepath" "regexp" "strings" ) @@ -58,39 +54,6 @@ func ConvertArgsToVars(args map[string]string, allowLoadFromFiles bool) (*uo.Uns return vars, nil } -func LoadDeprecatedDeploymentArgs(ctx context.Context, dir string, varsCtx *vars.VarsCtx, deployArgs *uo.UnstructuredObject) (bool, error) { - // First try to load the config without templating to avoid getting errors while rendering because required - // args were not set. Otherwise we won't be able to iterator through the 'args' array in the deployment.yml - // when the rendering error is actually args related. - - if !yaml.Exists(filepath.Join(dir, "deployment.yml")) { - return false, nil - } - - var conf types.DeploymentProjectConfig - - err := yaml.ReadYamlFile(yaml.FixPathExt(filepath.Join(dir, "deployment.yml")), &conf) - if err != nil { - // If that failed, it might be that conditional jinja blocks are present in the config, so lets try loading - // the config in rendered form. If it fails due to missing args now, we can't help much with better error - // messages anymore. - varsCtx2 := varsCtx.Copy() - varsCtx2.UpdateChild("args", deployArgs) - err = varsCtx2.RenderYamlFile(yaml.FixNameExt(dir, "deployment.yml"), []string{dir}, &conf) - if err != nil { - return false, err - } - } - - if len(conf.Args) == 0 { - return false, nil - } - - status.Deprecation(ctx, "deployment-args", "'args' in deployment.yaml is deprecated, please use 'args' from .kluctl.yaml instead.") - - return true, LoadDefaultArgs(conf.Args, deployArgs) -} - func LoadDefaultArgs(args []*types.DeploymentArg, deployArgs *uo.UnstructuredObject) error { // load defaults defaults := uo.New() diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index 4985784be..15df07449 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -202,15 +202,6 @@ func (p *LoadedKluctlProject) buildVars(target *types.Target, forSeal bool) (*va return nil, err } - deprecatedArgs, err := deployment.LoadDeprecatedDeploymentArgs(p.ctx, p.ProjectDir, varsCtx, allArgs) - if err != nil { - return nil, err - } - - if deprecatedArgs && len(p.Config.Args) != 0 { - return nil, fmt.Errorf("mixing deprecated 'args' from deployment.yaml and .kluctl.yaml is not allowed") - } - varsCtx.UpdateChild("args", allArgs) return varsCtx, nil diff --git a/pkg/types/deployment.go b/pkg/types/deployment.go index f792971bd..df656745c 100644 --- a/pkg/types/deployment.go +++ b/pkg/types/deployment.go @@ -85,7 +85,6 @@ type IgnoreForDiffItemConfig struct { } type DeploymentProjectConfig struct { - Args []*DeploymentArg `yaml:"args,omitempty"` Vars []*VarsSource `yaml:"vars,omitempty"` SealedSecrets *SealedSecretsConfig `yaml:"sealedSecrets,omitempty"` From 947c4e60e9478454e20c02662746871678b68f42 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 13 Feb 2023 10:47:06 +0100 Subject: [PATCH 1443/2916] feat: Remove deprecated helm chart location support --- cmd/kluctl/commands/cmd_helm_pull.go | 17 ----------------- cmd/kluctl/commands/cmd_helm_update.go | 12 ------------ pkg/helm/helm_release.go | 11 ----------- 3 files changed, 40 deletions(-) diff --git a/cmd/kluctl/commands/cmd_helm_pull.go b/cmd/kluctl/commands/cmd_helm_pull.go index 7bfc1f42a..f13160c75 100644 --- a/cmd/kluctl/commands/cmd_helm_pull.go +++ b/cmd/kluctl/commands/cmd_helm_pull.go @@ -47,23 +47,6 @@ func doHelmPull(ctx context.Context, projectDir string, helmCredentials *args.He return actions, err } - for _, hr := range releases { - if utils.Exists(hr.GetDeprecatedChartDir()) { - actions++ - rel, err := filepath.Rel(projectDir, hr.GetDeprecatedChartDir()) - if err != nil { - return actions, err - } - if !dryRun { - status.Info(ctx, "Removing deprecated charts dir %s", rel) - err = os.RemoveAll(hr.GetDeprecatedChartDir()) - if err != nil { - return actions, err - } - } - } - } - g := utils.NewGoHelper(ctx, 8) for _, chart := range charts { diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go index d079ec1b8..82ce4c2f8 100644 --- a/cmd/kluctl/commands/cmd_helm_update.go +++ b/cmd/kluctl/commands/cmd_helm_update.go @@ -78,18 +78,6 @@ func (cmd *helmUpdateCmd) Run(ctx context.Context) error { return err } - for _, hr := range releases { - if utils.Exists(hr.GetDeprecatedChartDir()) { - relDir, err := filepath.Rel(projectDir, filepath.Dir(hr.ConfigFile)) - if err != nil { - return err - } - status.Error(ctx, "%s: Project is using a pre-pulled Helm Chart that is next to the helm-chart.yaml, which is deprecated. "+ - "Updating is only possible after removing these. Use 'kluctl helm-pull' to remove all deprecated chart folders.", relDir) - return fmt.Errorf("detected deprecated chart folder") - } - } - if cmd.Commit { actions, err := doHelmPull(ctx, projectDir, &cmd.HelmCredentials, true, false) if err != nil { diff --git a/pkg/helm/helm_release.go b/pkg/helm/helm_release.go index 5b21b2215..26fc8ee08 100644 --- a/pkg/helm/helm_release.go +++ b/pkg/helm/helm_release.go @@ -107,11 +107,6 @@ func (hr *Release) Render(ctx context.Context, k *k8s.K8sCluster, k8sVersion str return nil } -func (hr *Release) GetDeprecatedChartDir() string { - dir := filepath.Dir(hr.ConfigFile) - return filepath.Join(dir, "charts", hr.Chart.GetChartName()) -} - func (hr *Release) getPulledChart(ctx context.Context) (*PulledChart, error) { if hr.Chart.IsLocalChart() { version, err := hr.Chart.GetLocalChartVersion() @@ -121,12 +116,6 @@ func (hr *Release) getPulledChart(ctx context.Context) (*PulledChart, error) { return NewPulledChart(hr.Chart, version, hr.Chart.GetLocalPath(), false), nil } - deprecatedPC := NewPulledChart(hr.Chart, hr.Config.ChartVersion, hr.GetDeprecatedChartDir(), false) - if deprecatedPC.CheckExists() { - status.Deprecation(ctx, "helm-charts-dir", "Your project has pre-pulled charts located next to the helm-chart.yaml, which is deprecated. "+ - "Please run 'kluctl helm-pull' on your project and ensure that the deprecated charts are removed! Future versions of kluctl will ignore these locations.") - return deprecatedPC, nil - } pc, err := hr.Chart.GetPulledChart(hr.baseChartsDir, hr.Config.ChartVersion) if err != nil { return nil, err From 7eb4028b724df7de78aa1003dbb28d66cd3a6da2 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 13 Feb 2023 11:51:55 +0100 Subject: [PATCH 1444/2916] refactor: Remove unused dynamicTargetInfo struct --- pkg/kluctl_project/targets.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/pkg/kluctl_project/targets.go b/pkg/kluctl_project/targets.go index 5a7ceb06d..cdc3b6d84 100644 --- a/pkg/kluctl_project/targets.go +++ b/pkg/kluctl_project/targets.go @@ -7,15 +7,6 @@ import ( "sort" ) -type dynamicTargetInfo struct { - baseTarget *types.Target - dir string - gitProject *types.GitProject - ref *string - refPattern *string - defaultBranch string -} - func (c *LoadedKluctlProject) loadTargets() error { status.Trace(c.ctx, "Loading targets") defer status.Trace(c.ctx, "Done loading targets") From 2523d39bc97045752e78d8b2924bd28ec5e30388 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Feb 2023 14:09:21 +0100 Subject: [PATCH 1445/2916] chore(deps): Bump golang.org/x/net from 0.6.0 to 0.7.0 (#317) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.6.0 to 0.7.0. - [Release notes](https://github.com/golang/net/releases) - [Commits](https://github.com/golang/net/compare/v0.6.0...v0.7.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index eeb038245..af0c71f63 100644 --- a/go.mod +++ b/go.mod @@ -39,7 +39,7 @@ require ( github.com/whilp/git-urls v1.0.0 github.com/xanzy/ssh-agent v0.3.3 golang.org/x/crypto v0.6.0 - golang.org/x/net v0.6.0 + golang.org/x/net v0.7.0 golang.org/x/sync v0.1.0 golang.org/x/sys v0.5.0 golang.org/x/term v0.5.0 diff --git a/go.sum b/go.sum index a529767df..10888f833 100644 --- a/go.sum +++ b/go.sum @@ -920,8 +920,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= From 31d87eb8dc364af9b10328b907973888526f2fc5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Feb 2023 08:35:01 +0100 Subject: [PATCH 1446/2916] chore(deps): Bump github.com/aws/aws-sdk-go-v2/service/secretsmanager (#318) Bumps [github.com/aws/aws-sdk-go-v2/service/secretsmanager](https://github.com/aws/aws-sdk-go-v2) from 1.18.3 to 1.18.4. - [Release notes](https://github.com/aws/aws-sdk-go-v2/releases) - [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go-v2/compare/config/v1.18.3...config/v1.18.4) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go-v2/service/secretsmanager dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index af0c71f63..fa7f3bed3 100644 --- a/go.mod +++ b/go.mod @@ -60,7 +60,7 @@ require ( filippo.io/age v1.1.1 github.com/aws/aws-sdk-go-v2 v1.17.4 github.com/aws/aws-sdk-go-v2/config v1.18.12 - github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.3 + github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.4 github.com/go-git/go-git/v5 v5.5.2 github.com/go-logr/logr v1.2.3 github.com/hashicorp/go-multierror v1.1.1 diff --git a/go.sum b/go.sum index 10888f833..5a8ed71b4 100644 --- a/go.sum +++ b/go.sum @@ -132,8 +132,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.22 h1:LjFQf8hFu github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.22/go.mod h1:xt0Au8yPIwYXf/GYPy/vl4K3CgwhfQMYbrH7DlUUIws= github.com/aws/aws-sdk-go-v2/service/kms v1.17.5 h1:DkubF+BSEy0uX59+pYySzWFReN3fCcXobIO8L5Phh24= github.com/aws/aws-sdk-go-v2/service/kms v1.17.5/go.mod h1:ubAtMGRUMVv5kX8lpbeDguxZ64pR4kXTGApY4sCM0io= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.3 h1:Zod/h9QcDvbrrG3jjTUp4lctRb6Qg2nj7ARC/xMsUc4= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.3/go.mod h1:hqPcyOuLU6yWIbLy3qMnQnmidgKuIEwqIlW6+chYnog= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.4 h1:0P9VF9miVGT40WSZSuMzHwkwTVIltpDrTrvswMLjbx0= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.4/go.mod h1:hqPcyOuLU6yWIbLy3qMnQnmidgKuIEwqIlW6+chYnog= github.com/aws/aws-sdk-go-v2/service/sso v1.12.1 h1:lQKN/LNa3qqu2cDOQZybP7oL4nMGGiFqob0jZJaR8/4= github.com/aws/aws-sdk-go-v2/service/sso v1.12.1/go.mod h1:IgV8l3sj22nQDd5qcAGY0WenwCzCphqdbFOpfktZPrI= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.1 h1:0bLhH6DRAqox+g0LatcjGKjjhU6Eudyys6HB6DJVPj8= From aeb437a46f89a8d268cb299c0dd15231801b64ac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Feb 2023 08:35:27 +0100 Subject: [PATCH 1447/2916] chore(deps): Bump github.com/bitnami-labs/sealed-secrets (#319) Bumps [github.com/bitnami-labs/sealed-secrets](https://github.com/bitnami-labs/sealed-secrets) from 0.19.4 to 0.19.5. - [Release notes](https://github.com/bitnami-labs/sealed-secrets/releases) - [Changelog](https://github.com/bitnami-labs/sealed-secrets/blob/main/RELEASE-NOTES.md) - [Commits](https://github.com/bitnami-labs/sealed-secrets/compare/v0.19.4...v0.19.5) --- updated-dependencies: - dependency-name: github.com/bitnami-labs/sealed-secrets dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index fa7f3bed3..860ee3b02 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 github.com/Masterminds/semver/v3 v3.2.0 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d - github.com/bitnami-labs/sealed-secrets v0.19.4 + github.com/bitnami-labs/sealed-secrets v0.19.5 github.com/cyphar/filepath-securejoin v0.2.3 github.com/docker/distribution v2.8.1+incompatible // See https://github.com/fluxcd/pkg/issues/397, especially in regard to skeema/knownhosts breaking compatibility @@ -225,7 +225,7 @@ require ( golang.org/x/mod v0.7.0 // indirect golang.org/x/oauth2 v0.3.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.4.0 // indirect + golang.org/x/tools v0.5.0 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect google.golang.org/api v0.107.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index 5a8ed71b4..833bc336c 100644 --- a/go.sum +++ b/go.sum @@ -148,8 +148,8 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bitnami-labs/sealed-secrets v0.19.4 h1:TUad0o/mNp2D53lHMGzjdsdrx+yrHy/fyuEIAbFqj+U= -github.com/bitnami-labs/sealed-secrets v0.19.4/go.mod h1:rXBdWOdAPbuowgFphH8bcCB3jSVlYXg9mMWRcYY49dc= +github.com/bitnami-labs/sealed-secrets v0.19.5 h1:Llrs8bm5MdJEoPIQo0xZOHu/2i+Ry8N5bQFpc48UZYc= +github.com/bitnami-labs/sealed-secrets v0.19.5/go.mod h1:IC5f2r0c8mxjx8nHs+du+gBso2Wsbdb2lcTwVmOOu2Y= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= @@ -627,7 +627,7 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA github.com/ohler55/ojg v1.17.4 h1:6Ss87DyAZHU0ODZu6Cmuahj5UiVaRD1n8C4KNm0qMYg= github.com/ohler55/ojg v1.17.4/go.mod h1:7Ghirupn8NC8hSSDpI0gcjorPxj+vSVIONDWfliHR1k= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo/v2 v2.7.0 h1:/XxtEV3I3Eif/HobnVx9YmJgk8ENdRsuUmM+fLCFNow= +github.com/onsi/ginkgo/v2 v2.8.0 h1:pAM+oBNPrpXRs+E/8spkeGx9QgekbRVyr74EUvRVOUI= github.com/onsi/gomega v1.26.0 h1:03cDLK28U6hWvCAns6NeydX3zIm4SF3ci69ulidS32Q= github.com/onsi/gomega v1.26.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= @@ -1099,8 +1099,8 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.4.0 h1:7mTAgkunk3fr4GAloyyCasadO6h9zSsQZbwvcaIciV4= -golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= +golang.org/x/tools v0.5.0 h1:+bSpV5HIeWkuvgaMfI3UmKRThoTA5ODJTUd8T17NO+4= +golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From a96783669ed32362491757ebbb247bbce0ae4faa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Feb 2023 08:35:50 +0100 Subject: [PATCH 1448/2916] chore(deps): Bump github.com/fluxcd/pkg/kustomize from 0.13.0 to 0.13.1 (#320) Bumps [github.com/fluxcd/pkg/kustomize](https://github.com/fluxcd/pkg) from 0.13.0 to 0.13.1. - [Release notes](https://github.com/fluxcd/pkg/releases) - [Commits](https://github.com/fluxcd/pkg/compare/oci/v0.13.0...runtime/v0.13.1) --- updated-dependencies: - dependency-name: github.com/fluxcd/pkg/kustomize dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 8 ++++---- go.sum | 20 ++++++++------------ 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/go.mod b/go.mod index 860ee3b02..45d3955b7 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/docker/distribution v2.8.1+incompatible // See https://github.com/fluxcd/pkg/issues/397, especially in regard to skeema/knownhosts breaking compatibility github.com/fluxcd/go-git/v5 v5.0.0-20221206140629-ec778c2c37df - github.com/fluxcd/pkg/kustomize v0.13.0 + github.com/fluxcd/pkg/kustomize v0.13.1 github.com/go-playground/validator/v10 v10.11.2 github.com/gobwas/glob v0.2.3 // indirect github.com/golang-jwt/jwt/v4 v4.4.3 @@ -133,8 +133,8 @@ require ( github.com/go-git/gcfg v1.5.0 // indirect github.com/go-git/go-billy/v5 v5.4.1 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect - github.com/go-openapi/jsonpointer v0.19.5 // indirect - github.com/go-openapi/jsonreference v0.20.0 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.1 // indirect github.com/go-openapi/swag v0.22.3 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect @@ -240,7 +240,7 @@ require ( k8s.io/apiserver v0.26.1 // indirect k8s.io/cli-runtime v0.26.0 // indirect k8s.io/component-base v0.26.1 // indirect - k8s.io/kube-openapi v0.0.0-20221207184640-f3cff1453715 // indirect + k8s.io/kube-openapi v0.0.0-20230109183929-3758b55a6596 // indirect k8s.io/kubectl v0.26.0 // indirect k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 // indirect oras.land/oras-go v1.2.2 // indirect diff --git a/go.sum b/go.sum index 833bc336c..e49b58b96 100644 --- a/go.sum +++ b/go.sum @@ -248,8 +248,8 @@ github.com/fluxcd/go-git/v5 v5.0.0-20221206140629-ec778c2c37df h1:2BHXJp1PwX7D47 github.com/fluxcd/go-git/v5 v5.0.0-20221206140629-ec778c2c37df/go.mod h1:raWgfUV7lDQVXp4QXUaeNNJkRVKz97UQuF+0kdY7Vmo= github.com/fluxcd/pkg/apis/kustomize v0.8.0 h1:A6aLolxPV2Sll44SOHiX96lbXXmRZmS5BoEerkRHrfM= github.com/fluxcd/pkg/apis/kustomize v0.8.0/go.mod h1:9DPEVSfVIkiC2H3Dk6Ght4YJkswhYIaufXla4tB5Y84= -github.com/fluxcd/pkg/kustomize v0.13.0 h1:oC50lpGdz/04aH4dPS/kRBjo+7PUx7BgGsJtSS0CmmE= -github.com/fluxcd/pkg/kustomize v0.13.0/go.mod h1:6vAmxEe/41jBEspGq4OZA/4WlnszPyavm74TGSEVpXg= +github.com/fluxcd/pkg/kustomize v0.13.1 h1:xfDghn/kRaa5vYN64dLTAL1b1B1tDwcXlnOAqmz5W28= +github.com/fluxcd/pkg/kustomize v0.13.1/go.mod h1:W+Nm9P8yUhTb8n3hpvceUnCAjl6DFsU0k5yI+HT2NE8= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= @@ -283,12 +283,10 @@ github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbV github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= -github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= -github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.20.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTrLC1F86HID8= +github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= @@ -544,8 +542,6 @@ github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9 github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/markbates/errx v1.1.0 h1:QDFeR+UP95dO12JgW+tgi2UVfo0V8YBHiUIOaeBPiEI= @@ -1281,8 +1277,8 @@ k8s.io/component-base v0.26.1 h1:4ahudpeQXHZL5kko+iDHqLj/FSGAEUnSVO0EBbgDd+4= k8s.io/component-base v0.26.1/go.mod h1:VHrLR0b58oC035w6YQiBSbtsf0ThuSwXP+p5dD/kAWU= k8s.io/klog/v2 v2.90.0 h1:VkTxIV/FjRXn1fgNNcKGM8cfmL1Z33ZjXRTVxKCoF5M= k8s.io/klog/v2 v2.90.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20221207184640-f3cff1453715 h1:tBEbstoM+K0FiBV5KGAKQ0kuvf54v/hwpldiJt69w1s= -k8s.io/kube-openapi v0.0.0-20221207184640-f3cff1453715/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= +k8s.io/kube-openapi v0.0.0-20230109183929-3758b55a6596 h1:8cNCQs+WqqnSpZ7y0LMQPKD+RZUHU17VqLPMW3qxnxc= +k8s.io/kube-openapi v0.0.0-20230109183929-3758b55a6596/go.mod h1:/BYxry62FuDzmI+i9B+X2pqfySRmSOW2ARmj5Zbqhj0= k8s.io/kubectl v0.26.0 h1:xmrzoKR9CyNdzxBmXV7jW9Ln8WMrwRK6hGbbf69o4T0= k8s.io/kubectl v0.26.0/go.mod h1:eInP0b+U9XUJWSYeU9XZnTA+cVYuWyl3iYPGtru0qhQ= k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 h1:KTgPnR10d5zhztWptI952TNtt/4u5h3IzDXkdIMuo2Y= From 1d1dc496bace5fda7d51e86310d9ff9cfaca333a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 16 Feb 2023 08:36:44 +0100 Subject: [PATCH 1449/2916] feat: Remove dynamicArgs from target spec --- pkg/types/kluctl_project.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pkg/types/kluctl_project.go b/pkg/types/kluctl_project.go index 840542dd6..e00908085 100644 --- a/pkg/types/kluctl_project.go +++ b/pkg/types/kluctl_project.go @@ -4,10 +4,6 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils/uo" ) -type DynamicArg struct { - Name string `yaml:"name" validate:"required"` -} - type SealingConfig struct { Args *uo.UnstructuredObject `yaml:"args,omitempty"` SecretSets []string `yaml:"secretSets,omitempty"` @@ -18,7 +14,6 @@ type Target struct { Name string `yaml:"name" validate:"required"` Context *string `yaml:"context,omitempty"` Args *uo.UnstructuredObject `yaml:"args,omitempty"` - DynamicArgs []DynamicArg `yaml:"dynamicArgs,omitempty"` SealingConfig *SealingConfig `yaml:"sealingConfig,omitempty"` Images []FixedImage `yaml:"images,omitempty"` Discriminator string `yaml:"discriminator,omitempty"` From af17374463caf2eafcc91e43bef8230ccb0481ad Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 16 Feb 2023 08:37:02 +0100 Subject: [PATCH 1450/2916] fix: Fix warning message to not talk about dynamic targets --- pkg/kluctl_project/targets.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/kluctl_project/targets.go b/pkg/kluctl_project/targets.go index cdc3b6d84..a9a8b714d 100644 --- a/pkg/kluctl_project/targets.go +++ b/pkg/kluctl_project/targets.go @@ -17,7 +17,7 @@ func (c *LoadedKluctlProject) loadTargets() error { for _, configTarget := range c.Config.Targets { target, err := c.buildTarget(configTarget) if err != nil { - status.Warning(c.ctx, "Failed to load dynamic target config for project: %v", err) + status.Warning(c.ctx, "Failed to load target config for project: %v", err) continue } From 3f60190c98392ff96b1fdf4fd841fc207ddd0c31 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Feb 2023 09:07:27 +0100 Subject: [PATCH 1451/2916] chore(deps): Bump github.com/aws/aws-sdk-go-v2/config (#321) Bumps [github.com/aws/aws-sdk-go-v2/config](https://github.com/aws/aws-sdk-go-v2) from 1.18.12 to 1.18.13. - [Release notes](https://github.com/aws/aws-sdk-go-v2/releases) - [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go-v2/compare/config/v1.18.12...config/v1.18.13) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go-v2/config dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 45d3955b7..e53d1ad16 100644 --- a/go.mod +++ b/go.mod @@ -59,7 +59,7 @@ require ( require ( filippo.io/age v1.1.1 github.com/aws/aws-sdk-go-v2 v1.17.4 - github.com/aws/aws-sdk-go-v2/config v1.18.12 + github.com/aws/aws-sdk-go-v2/config v1.18.13 github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.4 github.com/go-git/go-git/v5 v5.5.2 github.com/go-logr/logr v1.2.3 @@ -94,15 +94,15 @@ require ( github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 // indirect github.com/acomagu/bufpipe v1.0.3 // indirect github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.13.12 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.13.13 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.22 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.28 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.22 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.3.29 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.22 // indirect github.com/aws/aws-sdk-go-v2/service/kms v1.17.5 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.12.1 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.12.2 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.2 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.18.3 // indirect github.com/aws/smithy-go v1.13.5 // indirect github.com/beorn7/perks v1.0.1 // indirect diff --git a/go.sum b/go.sum index e49b58b96..f411a6e42 100644 --- a/go.sum +++ b/go.sum @@ -114,10 +114,10 @@ github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:W github.com/aws/aws-sdk-go-v2 v1.16.7/go.mod h1:6CpKuLXg2w7If3ABZCl/qZ6rEgwtjZTn4eAf4RcEyuw= github.com/aws/aws-sdk-go-v2 v1.17.4 h1:wyC6p9Yfq6V2y98wfDsj6OnNQa4w2BLGCLIxzNhwOGY= github.com/aws/aws-sdk-go-v2 v1.17.4/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= -github.com/aws/aws-sdk-go-v2/config v1.18.12 h1:fKs/I4wccmfrNRO9rdrbMO1NgLxct6H9rNMiPdBxHWw= -github.com/aws/aws-sdk-go-v2/config v1.18.12/go.mod h1:J36fOhj1LQBr+O4hJCiT8FwVvieeoSGOtPuvhKlsNu8= -github.com/aws/aws-sdk-go-v2/credentials v1.13.12 h1:Cb+HhuEnV19zHRaYYVglwvdHGMJWbdsyP4oHhw04xws= -github.com/aws/aws-sdk-go-v2/credentials v1.13.12/go.mod h1:37HG2MBroXK3jXfxVGtbM2J48ra2+Ltu+tmwr/jO0KA= +github.com/aws/aws-sdk-go-v2/config v1.18.13 h1:v0xlYqbO6/EVlM8tUn2QEOA7btQxcgidEq2JRDBPTho= +github.com/aws/aws-sdk-go-v2/config v1.18.13/go.mod h1:r39wGSZB7wPDW1i54JyQXUpc5KsWjh5z/3S5D9eCqDg= +github.com/aws/aws-sdk-go-v2/credentials v1.13.13 h1:zw1KAc1kl00NYd3ofVmFrb09qnYlSQMeh+fmlQRAihI= +github.com/aws/aws-sdk-go-v2/credentials v1.13.13/go.mod h1:DW9nbIIF9MrIja0cBQrUpeWYQMSlNmP8fevLUyF9W38= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.22 h1:3aMfcTmoXtTZnaT86QlVaYh+BRMbvrrmZwIQ5jWqCZQ= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.22/go.mod h1:YGSIJyQ6D6FjKMQh16hVFSIUD54L4F7zTGePqYMYYJU= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.14/go.mod h1:kdjrMwHwrC3+FsKhNcCMJ7tUVj/8uSD5CZXeQ4wV6fM= @@ -134,10 +134,10 @@ github.com/aws/aws-sdk-go-v2/service/kms v1.17.5 h1:DkubF+BSEy0uX59+pYySzWFReN3f github.com/aws/aws-sdk-go-v2/service/kms v1.17.5/go.mod h1:ubAtMGRUMVv5kX8lpbeDguxZ64pR4kXTGApY4sCM0io= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.4 h1:0P9VF9miVGT40WSZSuMzHwkwTVIltpDrTrvswMLjbx0= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.4/go.mod h1:hqPcyOuLU6yWIbLy3qMnQnmidgKuIEwqIlW6+chYnog= -github.com/aws/aws-sdk-go-v2/service/sso v1.12.1 h1:lQKN/LNa3qqu2cDOQZybP7oL4nMGGiFqob0jZJaR8/4= -github.com/aws/aws-sdk-go-v2/service/sso v1.12.1/go.mod h1:IgV8l3sj22nQDd5qcAGY0WenwCzCphqdbFOpfktZPrI= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.1 h1:0bLhH6DRAqox+g0LatcjGKjjhU6Eudyys6HB6DJVPj8= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.1/go.mod h1:O1YSOg3aekZibh2SngvCRRG+cRHKKlYgxf/JBF/Kr/k= +github.com/aws/aws-sdk-go-v2/service/sso v1.12.2 h1:EN102fWY7hI5u/2FPheTrwwMHkSXfl49RYkeEnJsrCU= +github.com/aws/aws-sdk-go-v2/service/sso v1.12.2/go.mod h1:IgV8l3sj22nQDd5qcAGY0WenwCzCphqdbFOpfktZPrI= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.2 h1:f1lmlce7r13CX1BPyPqt9oh/H+uqOWc9367lDoGGwNQ= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.2/go.mod h1:O1YSOg3aekZibh2SngvCRRG+cRHKKlYgxf/JBF/Kr/k= github.com/aws/aws-sdk-go-v2/service/sts v1.18.3 h1:s49mSnsBZEXjfGBkRfmK+nPqzT7Lt3+t2SmAKNyHblw= github.com/aws/aws-sdk-go-v2/service/sts v1.18.3/go.mod h1:b+psTJn33Q4qGoDaM7ZiOVVG8uVjGI6HaZ8WBHdgDgU= github.com/aws/smithy-go v1.12.0/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= From b1a63589e639211c8b15b02a91c72ba61fd41ed7 Mon Sep 17 00:00:00 2001 From: Jochen Saalfeld Date: Thu, 16 Feb 2023 09:08:49 +0100 Subject: [PATCH 1452/2916] fix typo in .kluctl.yaml example reference for dev env (#322) --- docs/reference/kluctl-project/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/kluctl-project/README.md b/docs/reference/kluctl-project/README.md index c42783829..3eae53d7f 100644 --- a/docs/reference/kluctl-project/README.md +++ b/docs/reference/kluctl-project/README.md @@ -24,7 +24,7 @@ discriminator: "my-project-{{ target.name }}" targets: # test cluster, dev env - name: dev - context: test.example.com + context: dev.example.com args: environment_name: dev # test cluster, test env From 5b41ff888cff84fc1821b03af2d7d2d4fc4f9bef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Feb 2023 09:03:24 +0100 Subject: [PATCH 1453/2916] chore(deps): Bump github.com/onsi/gomega from 1.26.0 to 1.27.0 (#323) Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.26.0 to 1.27.0. - [Release notes](https://github.com/onsi/gomega/releases) - [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md) - [Commits](https://github.com/onsi/gomega/compare/v1.26.0...v1.27.0) --- updated-dependencies: - dependency-name: github.com/onsi/gomega dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index e53d1ad16..a47d9e838 100644 --- a/go.mod +++ b/go.mod @@ -65,7 +65,7 @@ require ( github.com/go-logr/logr v1.2.3 github.com/hashicorp/go-multierror v1.1.1 github.com/huandu/xstrings v1.4.0 - github.com/onsi/gomega v1.26.0 + github.com/onsi/gomega v1.27.0 github.com/otiai10/copy v1.9.0 go.mozilla.org/sops/v3 v3.7.4-0.20220901181616-9124783930b1 sigs.k8s.io/controller-runtime v0.14.4 diff --git a/go.sum b/go.sum index f411a6e42..70544ce08 100644 --- a/go.sum +++ b/go.sum @@ -623,9 +623,9 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA github.com/ohler55/ojg v1.17.4 h1:6Ss87DyAZHU0ODZu6Cmuahj5UiVaRD1n8C4KNm0qMYg= github.com/ohler55/ojg v1.17.4/go.mod h1:7Ghirupn8NC8hSSDpI0gcjorPxj+vSVIONDWfliHR1k= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo/v2 v2.8.0 h1:pAM+oBNPrpXRs+E/8spkeGx9QgekbRVyr74EUvRVOUI= -github.com/onsi/gomega v1.26.0 h1:03cDLK28U6hWvCAns6NeydX3zIm4SF3ci69ulidS32Q= -github.com/onsi/gomega v1.26.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= +github.com/onsi/ginkgo/v2 v2.8.1 h1:xFTEVwOFa1D/Ty24Ws1npBWkDYEV9BqZrsDxVrVkrrU= +github.com/onsi/gomega v1.27.0 h1:QLidEla4bXUuZVFa4KX6JHCsuGgbi85LC/pCHrt/O08= +github.com/onsi/gomega v1.27.0/go.mod h1:i189pavgK95OSIipFBa74gC2V4qrQuvjuyGEr3GmbXA= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034= From 899bb32cece2cdcff8b7434fe973a8c3d9a3b617 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Feb 2023 08:42:36 +0100 Subject: [PATCH 1454/2916] chore(deps): Bump github.com/onsi/gomega from 1.27.0 to 1.27.1 (#327) Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.27.0 to 1.27.1. - [Release notes](https://github.com/onsi/gomega/releases) - [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md) - [Commits](https://github.com/onsi/gomega/compare/v1.27.0...v1.27.1) --- updated-dependencies: - dependency-name: github.com/onsi/gomega dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a47d9e838..3c7f6235c 100644 --- a/go.mod +++ b/go.mod @@ -65,7 +65,7 @@ require ( github.com/go-logr/logr v1.2.3 github.com/hashicorp/go-multierror v1.1.1 github.com/huandu/xstrings v1.4.0 - github.com/onsi/gomega v1.27.0 + github.com/onsi/gomega v1.27.1 github.com/otiai10/copy v1.9.0 go.mozilla.org/sops/v3 v3.7.4-0.20220901181616-9124783930b1 sigs.k8s.io/controller-runtime v0.14.4 diff --git a/go.sum b/go.sum index 70544ce08..4ed1c2dd8 100644 --- a/go.sum +++ b/go.sum @@ -624,8 +624,8 @@ github.com/ohler55/ojg v1.17.4 h1:6Ss87DyAZHU0ODZu6Cmuahj5UiVaRD1n8C4KNm0qMYg= github.com/ohler55/ojg v1.17.4/go.mod h1:7Ghirupn8NC8hSSDpI0gcjorPxj+vSVIONDWfliHR1k= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo/v2 v2.8.1 h1:xFTEVwOFa1D/Ty24Ws1npBWkDYEV9BqZrsDxVrVkrrU= -github.com/onsi/gomega v1.27.0 h1:QLidEla4bXUuZVFa4KX6JHCsuGgbi85LC/pCHrt/O08= -github.com/onsi/gomega v1.27.0/go.mod h1:i189pavgK95OSIipFBa74gC2V4qrQuvjuyGEr3GmbXA= +github.com/onsi/gomega v1.27.1 h1:rfztXRbg6nv/5f+Raen9RcGoSecHIFgBBLQK3Wdj754= +github.com/onsi/gomega v1.27.1/go.mod h1:aHX5xOykVYzWOV4WqQy0sy8BQptgukenXpCXfadcIAw= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034= From 8be6ec2c0913f3702ff090ea6dde9726dc635a98 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Feb 2023 08:42:52 +0100 Subject: [PATCH 1455/2916] chore(deps): Bump github.com/ohler55/ojg from 1.17.4 to 1.17.5 (#329) Bumps [github.com/ohler55/ojg](https://github.com/ohler55/ojg) from 1.17.4 to 1.17.5. - [Release notes](https://github.com/ohler55/ojg/releases) - [Changelog](https://github.com/ohler55/ojg/blob/develop/CHANGELOG.md) - [Commits](https://github.com/ohler55/ojg/compare/v1.17.4...v1.17.5) --- updated-dependencies: - dependency-name: github.com/ohler55/ojg dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 3c7f6235c..db964bf66 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( github.com/mattn/go-isatty v0.0.17 github.com/mattn/go-runewidth v0.0.14 github.com/mitchellh/reflectwalk v1.0.2 - github.com/ohler55/ojg v1.17.4 + github.com/ohler55/ojg v1.17.5 github.com/pkg/errors v0.9.1 github.com/r3labs/diff/v2 v2.15.1 github.com/rogpeppe/go-internal v1.9.0 diff --git a/go.sum b/go.sum index 4ed1c2dd8..f3ee51966 100644 --- a/go.sum +++ b/go.sum @@ -620,8 +620,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/ohler55/ojg v1.17.4 h1:6Ss87DyAZHU0ODZu6Cmuahj5UiVaRD1n8C4KNm0qMYg= -github.com/ohler55/ojg v1.17.4/go.mod h1:7Ghirupn8NC8hSSDpI0gcjorPxj+vSVIONDWfliHR1k= +github.com/ohler55/ojg v1.17.5 h1:SY6/cdhVzsLinNFIBRNSWhJgihvEWco5Y0TJe46XJ1Y= +github.com/ohler55/ojg v1.17.5/go.mod h1:7Ghirupn8NC8hSSDpI0gcjorPxj+vSVIONDWfliHR1k= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo/v2 v2.8.1 h1:xFTEVwOFa1D/Ty24Ws1npBWkDYEV9BqZrsDxVrVkrrU= github.com/onsi/gomega v1.27.1 h1:rfztXRbg6nv/5f+Raen9RcGoSecHIFgBBLQK3Wdj754= From bebde018f5c0b2c3d525dfadde0d64472d1b5882 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Feb 2023 08:43:29 +0100 Subject: [PATCH 1456/2916] chore(deps): Bump github.com/golang-jwt/jwt/v4 from 4.4.3 to 4.5.0 (#331) Bumps [github.com/golang-jwt/jwt/v4](https://github.com/golang-jwt/jwt) from 4.4.3 to 4.5.0. - [Release notes](https://github.com/golang-jwt/jwt/releases) - [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md) - [Commits](https://github.com/golang-jwt/jwt/compare/v4.4.3...v4.5.0) --- updated-dependencies: - dependency-name: github.com/golang-jwt/jwt/v4 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index db964bf66..5cf83491a 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/fluxcd/pkg/kustomize v0.13.1 github.com/go-playground/validator/v10 v10.11.2 github.com/gobwas/glob v0.2.3 // indirect - github.com/golang-jwt/jwt/v4 v4.4.3 + github.com/golang-jwt/jwt/v4 v4.5.0 github.com/google/go-containerregistry v0.13.0 github.com/hashicorp/vault/api v1.9.0 github.com/hexops/gotextdiff v1.0.3 diff --git a/go.sum b/go.sum index f3ee51966..c58e584e6 100644 --- a/go.sum +++ b/go.sum @@ -318,8 +318,8 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69 github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= -github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU= -github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= From e2ac702205798fa90f5e35cfd2bc42fbd41e50b8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Feb 2023 08:43:45 +0100 Subject: [PATCH 1457/2916] chore(deps): Bump github.com/aws/aws-sdk-go-v2/config (#332) Bumps [github.com/aws/aws-sdk-go-v2/config](https://github.com/aws/aws-sdk-go-v2) from 1.18.13 to 1.18.15. - [Release notes](https://github.com/aws/aws-sdk-go-v2/releases) - [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go-v2/compare/config/v1.18.13...config/v1.18.15) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go-v2/config dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 22 +++++++++++----------- go.sum | 41 ++++++++++++++++++++++------------------- 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/go.mod b/go.mod index 5cf83491a..d64f89087 100644 --- a/go.mod +++ b/go.mod @@ -58,8 +58,8 @@ require ( require ( filippo.io/age v1.1.1 - github.com/aws/aws-sdk-go-v2 v1.17.4 - github.com/aws/aws-sdk-go-v2/config v1.18.13 + github.com/aws/aws-sdk-go-v2 v1.17.5 + github.com/aws/aws-sdk-go-v2/config v1.18.15 github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.4 github.com/go-git/go-git/v5 v5.5.2 github.com/go-logr/logr v1.2.3 @@ -94,16 +94,16 @@ require ( github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 // indirect github.com/acomagu/bufpipe v1.0.3 // indirect github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.13.13 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.22 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.28 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.22 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.3.29 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.22 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.13.15 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.23 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.29 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.23 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.3.30 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.23 // indirect github.com/aws/aws-sdk-go-v2/service/kms v1.17.5 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.12.2 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.2 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.18.3 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.12.4 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.4 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.18.5 // indirect github.com/aws/smithy-go v1.13.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver v3.5.1+incompatible // indirect diff --git a/go.sum b/go.sum index c58e584e6..bef14ad07 100644 --- a/go.sum +++ b/go.sum @@ -112,34 +112,37 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkY github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go-v2 v1.16.7/go.mod h1:6CpKuLXg2w7If3ABZCl/qZ6rEgwtjZTn4eAf4RcEyuw= -github.com/aws/aws-sdk-go-v2 v1.17.4 h1:wyC6p9Yfq6V2y98wfDsj6OnNQa4w2BLGCLIxzNhwOGY= github.com/aws/aws-sdk-go-v2 v1.17.4/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= -github.com/aws/aws-sdk-go-v2/config v1.18.13 h1:v0xlYqbO6/EVlM8tUn2QEOA7btQxcgidEq2JRDBPTho= -github.com/aws/aws-sdk-go-v2/config v1.18.13/go.mod h1:r39wGSZB7wPDW1i54JyQXUpc5KsWjh5z/3S5D9eCqDg= -github.com/aws/aws-sdk-go-v2/credentials v1.13.13 h1:zw1KAc1kl00NYd3ofVmFrb09qnYlSQMeh+fmlQRAihI= -github.com/aws/aws-sdk-go-v2/credentials v1.13.13/go.mod h1:DW9nbIIF9MrIja0cBQrUpeWYQMSlNmP8fevLUyF9W38= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.22 h1:3aMfcTmoXtTZnaT86QlVaYh+BRMbvrrmZwIQ5jWqCZQ= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.22/go.mod h1:YGSIJyQ6D6FjKMQh16hVFSIUD54L4F7zTGePqYMYYJU= +github.com/aws/aws-sdk-go-v2 v1.17.5 h1:TzCUW1Nq4H8Xscph5M/skINUitxM5UBAyvm2s7XBzL4= +github.com/aws/aws-sdk-go-v2 v1.17.5/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= +github.com/aws/aws-sdk-go-v2/config v1.18.15 h1:509yMO0pJUGUugBP2H9FOFyV+7Mz7sRR+snfDN5W4NY= +github.com/aws/aws-sdk-go-v2/config v1.18.15/go.mod h1:vS0tddZqpE8cD9CyW0/kITHF5Bq2QasW9Y1DFHD//O0= +github.com/aws/aws-sdk-go-v2/credentials v1.13.15 h1:0rZQIi6deJFjOEgHI9HI2eZcLPPEGQPictX66oRFLL8= +github.com/aws/aws-sdk-go-v2/credentials v1.13.15/go.mod h1:vRMLMD3/rXU+o6j2MW5YefrGMBmdTvkLLGqFwMLBHQc= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.23 h1:Kbiv9PGnQfG/imNI4L/heyUXvzKmcWSBeDvkrQz5pFc= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.23/go.mod h1:mOtmAg65GT1HIL/HT/PynwPbS+UG0BgCZ6vhkPqnxWo= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.14/go.mod h1:kdjrMwHwrC3+FsKhNcCMJ7tUVj/8uSD5CZXeQ4wV6fM= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.28 h1:r+XwaCLpIvCKjBIYy/HVZujQS9tsz5ohHG3ZIe0wKoE= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.28/go.mod h1:3lwChorpIM/BhImY/hy+Z6jekmN92cXGPI1QJasVPYY= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.29 h1:9/aKwwus0TQxppPXFmf010DFrE+ssSbzroLVYINA+xE= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.29/go.mod h1:Dip3sIGv485+xerzVv24emnjX5Sg88utCL8fwGmCeWg= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.8/go.mod h1:ZIV8GYoC6WLBW5KGs+o4rsc65/ozd+eQ0L31XF5VDwk= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.22 h1:7AwGYXDdqRQYsluvKFmWoqpcOQJ4bH634SkYf3FNj/A= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.22/go.mod h1:EqK7gVrIGAHyZItrD1D8B0ilgwMD1GiWAmbU4u/JHNk= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.29 h1:J4xhFd6zHhdF9jPP0FQJ6WknzBboGMBNjKOv4iTuw4A= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.29/go.mod h1:TwuqRBGzxjQJIwH16/fOZodwXt2Zxa9/cwJC5ke4j7s= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.22 h1:LjFQf8hFuMO22HkV5VWGLBvmCLBCLPivUAmpdpnp4Vs= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.22/go.mod h1:xt0Au8yPIwYXf/GYPy/vl4K3CgwhfQMYbrH7DlUUIws= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.23 h1:b/Vn141DBuLVgXbhRWIrl9g+ww7G+ScV5SzniWR13jQ= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.23/go.mod h1:mr6c4cHC+S/MMkrjtSlG4QA36kOznDep+0fga5L/fGQ= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.30 h1:IVx9L7YFhpPq0tTnGo8u8TpluFu7nAn9X3sUDMb11c0= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.30/go.mod h1:vsbq62AOBwQ1LJ/GWKFxX8beUEYeRp/Agitrxee2/qM= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.23 h1:QoOybhwRfciWUBbZ0gp9S7XaDnCuSTeK/fySB99V1ls= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.23/go.mod h1:9uPh+Hrz2Vn6oMnQYiUi/zbh3ovbnQk19YKINkQny44= github.com/aws/aws-sdk-go-v2/service/kms v1.17.5 h1:DkubF+BSEy0uX59+pYySzWFReN3fCcXobIO8L5Phh24= github.com/aws/aws-sdk-go-v2/service/kms v1.17.5/go.mod h1:ubAtMGRUMVv5kX8lpbeDguxZ64pR4kXTGApY4sCM0io= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.4 h1:0P9VF9miVGT40WSZSuMzHwkwTVIltpDrTrvswMLjbx0= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.4/go.mod h1:hqPcyOuLU6yWIbLy3qMnQnmidgKuIEwqIlW6+chYnog= -github.com/aws/aws-sdk-go-v2/service/sso v1.12.2 h1:EN102fWY7hI5u/2FPheTrwwMHkSXfl49RYkeEnJsrCU= -github.com/aws/aws-sdk-go-v2/service/sso v1.12.2/go.mod h1:IgV8l3sj22nQDd5qcAGY0WenwCzCphqdbFOpfktZPrI= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.2 h1:f1lmlce7r13CX1BPyPqt9oh/H+uqOWc9367lDoGGwNQ= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.2/go.mod h1:O1YSOg3aekZibh2SngvCRRG+cRHKKlYgxf/JBF/Kr/k= -github.com/aws/aws-sdk-go-v2/service/sts v1.18.3 h1:s49mSnsBZEXjfGBkRfmK+nPqzT7Lt3+t2SmAKNyHblw= -github.com/aws/aws-sdk-go-v2/service/sts v1.18.3/go.mod h1:b+psTJn33Q4qGoDaM7ZiOVVG8uVjGI6HaZ8WBHdgDgU= +github.com/aws/aws-sdk-go-v2/service/sso v1.12.4 h1:qJdM48OOLl1FBSzI7ZrA1ZfLwOyCYqkXV5lko1hYDBw= +github.com/aws/aws-sdk-go-v2/service/sso v1.12.4/go.mod h1:jtLIhd+V+lft6ktxpItycqHqiVXrPIRjWIsFIlzMriw= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.4 h1:YRkWXQveFb0tFC0TLktmmhGsOcCgLwvq88MC2al47AA= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.4/go.mod h1:zVwRrfdSmbRZWkUkWjOItY7SOalnFnq/Yg2LVPqDjwc= +github.com/aws/aws-sdk-go-v2/service/sts v1.18.5 h1:L1600eLr0YvTT7gNh3Ni24yGI7NSHkq9Gp62vijPRCs= +github.com/aws/aws-sdk-go-v2/service/sts v1.18.5/go.mod h1:1mKZHLLpDMHTNSYPJ7qrcnCQdHCWsNQaT0xRvq2u80s= github.com/aws/smithy-go v1.12.0/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8= github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= From 328f5f3421b12b8c085a39658e2ad6bafc61a1a5 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 27 Feb 2023 10:29:51 +0100 Subject: [PATCH 1458/2916] fix: Also add full resource strings to Helm's APIVersions (#334) Helm internally adds GroupVersion strings AND GroupVersionKind strings to APIVersions. We must replicate this behavior as otherwise things like `.Capabilities.APIVersions.Has "networking.k8s.io/v1/Ingress"` will not work. --- pkg/helm/helm_release.go | 35 ++++++++++++++++++++++++----------- pkg/k8s/resources.go | 2 +- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/pkg/helm/helm_release.go b/pkg/helm/helm_release.go index 26fc8ee08..7295a7f29 100644 --- a/pkg/helm/helm_release.go +++ b/pkg/helm/helm_release.go @@ -26,7 +26,6 @@ import ( "helm.sh/helm/v3/pkg/cli/values" "helm.sh/helm/v3/pkg/getter" "helm.sh/helm/v3/pkg/release" - "k8s.io/apimachinery/pkg/runtime/schema" ) type Release struct { @@ -161,13 +160,6 @@ func (hr *Release) doRender(ctx context.Context, k *k8s.K8sCluster, k8sVersion s } valuesPath := yaml.FixPathExt(filepath.Join(filepath.Dir(hr.ConfigFile), "helm-values.yml")) - var gvs []schema.GroupVersion - if k != nil { - gvs, err = k.Resources.GetAllGroupVersions() - if err != nil { - return err - } - } cfg, err := buildHelmConfig(k) if err != nil { return err @@ -211,15 +203,13 @@ func (hr *Release) doRender(ctx context.Context, k *k8s.K8sCluster, k8sVersion s client.Replace = true client.ClientOnly = true client.KubeVersion = kubeVersion + client.APIVersions = hr.getApiVersions(k) if hr.Config.SkipCRDs { client.SkipCRDs = true } else { client.IncludeCRDs = true } - for _, gv := range gvs { - client.APIVersions = append(client.APIVersions, gv.String()) - } p := getter.All(settings) vals, err := valueOpts.MergeValues(p) @@ -291,6 +281,29 @@ func (hr *Release) doRender(ctx context.Context, k *k8s.K8sCluster, k8sVersion s return nil } +func (hr *Release) getApiVersions(k *k8s.K8sCluster) chartutil.VersionSet { + if k == nil { + return nil + } + + m := map[string]bool{} + + gvks := k.Resources.GetFilteredGVKs(nil) + for _, gvk := range gvks { + gvStr := gvk.GroupVersion().String() + m[gvStr] = true + gvkStr := fmt.Sprintf("%s/%s", gvStr, gvk.Kind) + m[gvkStr] = true + } + + ret := make([]string, 0, len(m)) + for id, _ := range m { + ret = append(ret, id) + } + + return ret +} + func (hr *Release) parseRenderedManifests(s string) ([]*uo.UnstructuredObject, error) { var parsed []*uo.UnstructuredObject diff --git a/pkg/k8s/resources.go b/pkg/k8s/resources.go index 47ce75c2b..9a49b9093 100644 --- a/pkg/k8s/resources.go +++ b/pkg/k8s/resources.go @@ -310,7 +310,7 @@ func (k *k8sResources) GetFilteredGVKs(filter func(ar *v1.APIResource) bool) []s var ret []schema.GroupVersionKind for _, ar := range k.allResources { - if !filter(&ar) { + if filter != nil && !filter(&ar) { continue } gvk := schema.GroupVersionKind{ From 3ac06cea9e7f864a880f53b037dcf5e8b1f58710 Mon Sep 17 00:00:00 2001 From: Jochen Saalfeld Date: Mon, 27 Feb 2023 14:47:14 +0100 Subject: [PATCH 1459/2916] docs: change environment args to a working default case (#335) --- docs/reference/kluctl-project/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/reference/kluctl-project/README.md b/docs/reference/kluctl-project/README.md index 3eae53d7f..7c786c498 100644 --- a/docs/reference/kluctl-project/README.md +++ b/docs/reference/kluctl-project/README.md @@ -26,17 +26,17 @@ targets: - name: dev context: dev.example.com args: - environment_name: dev + environment: dev # test cluster, test env - name: test context: test.example.com args: - environment_name: test + environment: test # prod cluster, prod env - name: prod context: prod.example.com args: - environment_name: prod + environment: prod args: - name: environment From d16793f80fcf668d56467783d45153e82c2d82cb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Feb 2023 08:46:26 +0100 Subject: [PATCH 1460/2916] chore(deps): Bump github.com/aws/aws-sdk-go-v2/service/secretsmanager (#336) Bumps [github.com/aws/aws-sdk-go-v2/service/secretsmanager](https://github.com/aws/aws-sdk-go-v2) from 1.18.4 to 1.18.6. - [Release notes](https://github.com/aws/aws-sdk-go-v2/releases) - [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go-v2/compare/config/v1.18.4...config/v1.18.6) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go-v2/service/secretsmanager dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index d64f89087..2e58406b4 100644 --- a/go.mod +++ b/go.mod @@ -60,7 +60,7 @@ require ( filippo.io/age v1.1.1 github.com/aws/aws-sdk-go-v2 v1.17.5 github.com/aws/aws-sdk-go-v2/config v1.18.15 - github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.4 + github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.6 github.com/go-git/go-git/v5 v5.5.2 github.com/go-logr/logr v1.2.3 github.com/hashicorp/go-multierror v1.1.1 diff --git a/go.sum b/go.sum index bef14ad07..dff4afac9 100644 --- a/go.sum +++ b/go.sum @@ -112,7 +112,6 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkY github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go-v2 v1.16.7/go.mod h1:6CpKuLXg2w7If3ABZCl/qZ6rEgwtjZTn4eAf4RcEyuw= -github.com/aws/aws-sdk-go-v2 v1.17.4/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= github.com/aws/aws-sdk-go-v2 v1.17.5 h1:TzCUW1Nq4H8Xscph5M/skINUitxM5UBAyvm2s7XBzL4= github.com/aws/aws-sdk-go-v2 v1.17.5/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= github.com/aws/aws-sdk-go-v2/config v1.18.15 h1:509yMO0pJUGUugBP2H9FOFyV+7Mz7sRR+snfDN5W4NY= @@ -122,11 +121,9 @@ github.com/aws/aws-sdk-go-v2/credentials v1.13.15/go.mod h1:vRMLMD3/rXU+o6j2MW5Y github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.23 h1:Kbiv9PGnQfG/imNI4L/heyUXvzKmcWSBeDvkrQz5pFc= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.23/go.mod h1:mOtmAg65GT1HIL/HT/PynwPbS+UG0BgCZ6vhkPqnxWo= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.14/go.mod h1:kdjrMwHwrC3+FsKhNcCMJ7tUVj/8uSD5CZXeQ4wV6fM= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.28/go.mod h1:3lwChorpIM/BhImY/hy+Z6jekmN92cXGPI1QJasVPYY= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.29 h1:9/aKwwus0TQxppPXFmf010DFrE+ssSbzroLVYINA+xE= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.29/go.mod h1:Dip3sIGv485+xerzVv24emnjX5Sg88utCL8fwGmCeWg= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.8/go.mod h1:ZIV8GYoC6WLBW5KGs+o4rsc65/ozd+eQ0L31XF5VDwk= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.22/go.mod h1:EqK7gVrIGAHyZItrD1D8B0ilgwMD1GiWAmbU4u/JHNk= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.23 h1:b/Vn141DBuLVgXbhRWIrl9g+ww7G+ScV5SzniWR13jQ= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.23/go.mod h1:mr6c4cHC+S/MMkrjtSlG4QA36kOznDep+0fga5L/fGQ= github.com/aws/aws-sdk-go-v2/internal/ini v1.3.30 h1:IVx9L7YFhpPq0tTnGo8u8TpluFu7nAn9X3sUDMb11c0= @@ -135,8 +132,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.23 h1:QoOybhwRf github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.23/go.mod h1:9uPh+Hrz2Vn6oMnQYiUi/zbh3ovbnQk19YKINkQny44= github.com/aws/aws-sdk-go-v2/service/kms v1.17.5 h1:DkubF+BSEy0uX59+pYySzWFReN3fCcXobIO8L5Phh24= github.com/aws/aws-sdk-go-v2/service/kms v1.17.5/go.mod h1:ubAtMGRUMVv5kX8lpbeDguxZ64pR4kXTGApY4sCM0io= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.4 h1:0P9VF9miVGT40WSZSuMzHwkwTVIltpDrTrvswMLjbx0= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.4/go.mod h1:hqPcyOuLU6yWIbLy3qMnQnmidgKuIEwqIlW6+chYnog= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.6 h1:VjvQw/1Qf/rhDSl+NNOeybSpdPRjBfH60//5vzveVsY= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.6/go.mod h1:CJcdJtrO6ulXfI8l2DotKWmJShhXHCEcd9Wibyx3kC0= github.com/aws/aws-sdk-go-v2/service/sso v1.12.4 h1:qJdM48OOLl1FBSzI7ZrA1ZfLwOyCYqkXV5lko1hYDBw= github.com/aws/aws-sdk-go-v2/service/sso v1.12.4/go.mod h1:jtLIhd+V+lft6ktxpItycqHqiVXrPIRjWIsFIlzMriw= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.4 h1:YRkWXQveFb0tFC0TLktmmhGsOcCgLwvq88MC2al47AA= From 572c96a12763ba367f722f76812300c19c631096 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Feb 2023 08:46:51 +0100 Subject: [PATCH 1461/2916] chore(deps): Bump github.com/stretchr/testify from 1.8.1 to 1.8.2 (#337) Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.1 to 1.8.2. - [Release notes](https://github.com/stretchr/testify/releases) - [Commits](https://github.com/stretchr/testify/compare/v1.8.1...v1.8.2) --- updated-dependencies: - dependency-name: github.com/stretchr/testify dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 2e58406b4..683361d28 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( github.com/spf13/cobra v1.6.1 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.15.0 - github.com/stretchr/testify v1.8.1 + github.com/stretchr/testify v1.8.2 github.com/whilp/git-urls v1.0.0 github.com/xanzy/ssh-agent v0.3.3 golang.org/x/crypto v0.6.0 diff --git a/go.sum b/go.sum index dff4afac9..ca78af9e0 100644 --- a/go.sum +++ b/go.sum @@ -751,8 +751,9 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= From 37a61b1e0d90eddab494579ce009bc6ed3eaabd0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Feb 2023 08:47:07 +0100 Subject: [PATCH 1462/2916] chore(deps): Bump actions/checkout from 2 to 3 (#338) Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/cross-compile.yaml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/tests.yml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cross-compile.yaml b/.github/workflows/cross-compile.yaml index 556c864d7..6b96e9f56 100644 --- a/.github/workflows/cross-compile.yaml +++ b/.github/workflows/cross-compile.yaml @@ -11,7 +11,7 @@ jobs: if: github.event_name != 'pull_request' steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 - uses: actions/setup-go@v3 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4e2a47b9a..b18a49db0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 - name: Fetch all tags diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 92f166bb7..5e3e4cc83 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 - uses: actions/setup-go@v3 @@ -55,7 +55,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - uses: actions/setup-go@v3 with: go-version: '1.19' From fc02acdc89e041622fbd3f5d7d23a468ed9079f7 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 28 Feb 2023 10:30:44 +0100 Subject: [PATCH 1463/2916] fix: Properly track new objects instead of guessing when diffing --- pkg/deployment/commands/deploy.go | 4 +-- pkg/deployment/commands/diff.go | 2 +- pkg/deployment/commands/poke_images.go | 2 +- pkg/deployment/utils/apply_utils.go | 38 ++++++++++++++++---------- pkg/deployment/utils/diff_utils.go | 12 ++------ 5 files changed, 29 insertions(+), 29 deletions(-) diff --git a/pkg/deployment/commands/deploy.go b/pkg/deployment/commands/deploy.go index cb3721b41..a349fd103 100644 --- a/pkg/deployment/commands/deploy.go +++ b/pkg/deployment/commands/deploy.go @@ -65,7 +65,7 @@ func (cmd *DeployCommand) Run(ctx context.Context, k *k8s.K8sCluster, diffResult orphanObjects, err := FindOrphanObjects(k, ru, cmd.c) diffResult := &types.CommandResult{ - NewObjects: du.NewObjects, + NewObjects: au.GetNewObjects(), ChangedObjects: du.ChangedObjects, DeletedObjects: au.GetDeletedObjects(), HookObjects: au.GetAppliedHookObjects(), @@ -99,7 +99,7 @@ func (cmd *DeployCommand) Run(ctx context.Context, k *k8s.K8sCluster, diffResult return nil, err } return &types.CommandResult{ - NewObjects: du.NewObjects, + NewObjects: au.GetNewObjects(), ChangedObjects: du.ChangedObjects, DeletedObjects: au.GetDeletedObjects(), HookObjects: au.GetAppliedHookObjects(), diff --git a/pkg/deployment/commands/diff.go b/pkg/deployment/commands/diff.go index ff8e8f0ca..6a23ef6fb 100644 --- a/pkg/deployment/commands/diff.go +++ b/pkg/deployment/commands/diff.go @@ -66,7 +66,7 @@ func (cmd *DiffCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*types.Comm return nil, err } return &types.CommandResult{ - NewObjects: du.NewObjects, + NewObjects: au.GetNewObjects(), ChangedObjects: du.ChangedObjects, DeletedObjects: au.GetDeletedObjects(), HookObjects: au.GetAppliedHookObjects(), diff --git a/pkg/deployment/commands/poke_images.go b/pkg/deployment/commands/poke_images.go index 3c99172d1..97baf5772 100644 --- a/pkg/deployment/commands/poke_images.go +++ b/pkg/deployment/commands/poke_images.go @@ -95,7 +95,7 @@ func (cmd *PokeImagesCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*type du.Diff() return &types.CommandResult{ - NewObjects: du.NewObjects, + NewObjects: nil, ChangedObjects: du.ChangedObjects, Errors: dew.GetErrorsList(), Warnings: dew.GetWarningsList(), diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index 69c6d7fd4..11717629e 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -40,6 +40,7 @@ type ApplyUtil struct { dew *DeploymentErrorsAndWarnings errorCount int warningCount int + newObjects map[k8s2.ObjectRef]*uo.UnstructuredObject appliedObjects map[k8s2.ObjectRef]*uo.UnstructuredObject appliedHookObjects map[k8s2.ObjectRef]*uo.UnstructuredObject deletedObjects map[k8s2.ObjectRef]bool @@ -95,6 +96,7 @@ func (ad *ApplyDeploymentsUtil) NewApplyUtil(ctx context.Context, statusCtx *sta ret := &ApplyUtil{ ctx: ctx, dew: ad.dew, + newObjects: map[k8s2.ObjectRef]*uo.UnstructuredObject{}, appliedObjects: map[k8s2.ObjectRef]*uo.UnstructuredObject{}, appliedHookObjects: map[k8s2.ObjectRef]*uo.UnstructuredObject{}, deletedObjects: map[k8s2.ObjectRef]bool{}, @@ -119,6 +121,10 @@ func (a *ApplyUtil) handleResult(appliedObject *uo.UnstructuredObject, hook bool a.appliedHookObjects[ref] = appliedObject } a.appliedObjects[ref] = appliedObject + + if !hook && a.ru.GetRemoteObject(ref) == nil { + a.newObjects[ref] = appliedObject + } } func (a *ApplyUtil) handleApiWarnings(ref k8s2.ObjectRef, warnings []k8s.ApiWarning) { @@ -684,13 +690,13 @@ func (a *ApplyUtil) ReplaceObject(ref k8s2.ObjectRef, firstVersion *uo.Unstructu a.HandleError(ref, fmt.Errorf("unexpected end of loop")) } -func (ad *ApplyDeploymentsUtil) GetAppliedObjects() []*types.RefAndObject { +func (ad *ApplyDeploymentsUtil) collectObjects(f func(au *ApplyUtil) map[k8s2.ObjectRef]*uo.UnstructuredObject) []*types.RefAndObject { ad.resultsMutex.Lock() defer ad.resultsMutex.Unlock() var ret []*types.RefAndObject for _, a := range ad.results { - for _, o := range a.appliedObjects { + for _, o := range f(a) { ret = append(ret, &types.RefAndObject{ Ref: o.GetK8sRef(), Object: o, @@ -700,6 +706,18 @@ func (ad *ApplyDeploymentsUtil) GetAppliedObjects() []*types.RefAndObject { return ret } +func (ad *ApplyDeploymentsUtil) GetNewObjects() []*types.RefAndObject { + return ad.collectObjects(func(au *ApplyUtil) map[k8s2.ObjectRef]*uo.UnstructuredObject { + return au.newObjects + }) +} + +func (ad *ApplyDeploymentsUtil) GetAppliedObjects() []*types.RefAndObject { + return ad.collectObjects(func(au *ApplyUtil) map[k8s2.ObjectRef]*uo.UnstructuredObject { + return au.appliedObjects + }) +} + func (ad *ApplyDeploymentsUtil) GetAppliedObjectsMap() map[k8s2.ObjectRef]*uo.UnstructuredObject { ret := make(map[k8s2.ObjectRef]*uo.UnstructuredObject) for _, ro := range ad.GetAppliedObjects() { @@ -709,19 +727,9 @@ func (ad *ApplyDeploymentsUtil) GetAppliedObjectsMap() map[k8s2.ObjectRef]*uo.Un } func (ad *ApplyDeploymentsUtil) GetAppliedHookObjects() []*types.RefAndObject { - ad.resultsMutex.Lock() - defer ad.resultsMutex.Unlock() - - var ret []*types.RefAndObject - for _, a := range ad.results { - for _, o := range a.appliedHookObjects { - ret = append(ret, &types.RefAndObject{ - Ref: o.GetK8sRef(), - Object: o, - }) - } - } - return ret + return ad.collectObjects(func(au *ApplyUtil) map[k8s2.ObjectRef]*uo.UnstructuredObject { + return au.appliedHookObjects + }) } func (ad *ApplyDeploymentsUtil) GetDeletedObjects() []k8s2.ObjectRef { diff --git a/pkg/deployment/utils/diff_utils.go b/pkg/deployment/utils/diff_utils.go index 77b1e31ad..05f740a8f 100644 --- a/pkg/deployment/utils/diff_utils.go +++ b/pkg/deployment/utils/diff_utils.go @@ -22,7 +22,6 @@ type diffUtil struct { IgnoreAnnotations bool remoteDiffObjects map[k8s2.ObjectRef]*uo.UnstructuredObject - NewObjects []*types.RefAndObject ChangedObjects []*types.ChangedObject mutex sync.Mutex } @@ -62,9 +61,6 @@ func (u *diffUtil) Diff() { } wg.Wait() - sort.Slice(u.NewObjects, func(i, j int) bool { - return u.NewObjects[i].Ref.String() < u.NewObjects[j].Ref.String() - }) sort.Slice(u.ChangedObjects, func(i, j int) bool { return u.ChangedObjects[i].Ref.String() < u.ChangedObjects[j].Ref.String() }) @@ -72,12 +68,8 @@ func (u *diffUtil) Diff() { func (u *diffUtil) diffObject(lo *uo.UnstructuredObject, diffRef k8s2.ObjectRef, ao *uo.UnstructuredObject, ro *uo.UnstructuredObject, ignoreForDiffs []*types.IgnoreForDiffItemConfig) { if ao != nil && ro == nil { - u.mutex.Lock() - defer u.mutex.Unlock() - u.NewObjects = append(u.NewObjects, &types.RefAndObject{ - Ref: ao.GetK8sRef(), - Object: ao, - }) + // new? + return } else if ao == nil && ro != nil { // deleted? return From f74692f9daa04621008f16f4fb90ec25e4575a95 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 28 Feb 2023 10:48:28 +0100 Subject: [PATCH 1464/2916] fix: Properly simulate deletion of hooks in dryRun mode --- pkg/deployment/utils/apply_utils.go | 38 ++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index 11717629e..67c17f045 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -169,13 +169,45 @@ func (a *ApplyUtil) DeleteObject(ref k8s2.ObjectRef, hook bool) bool { } apiWarnings, err := a.k.DeleteSingleObject(ref, o) a.handleApiWarnings(ref, apiWarnings) - if err != nil { - if !errors.IsNotFound(err) { - a.HandleError(ref, err) + + if err == nil { + a.mutex.Lock() + defer a.mutex.Unlock() + if hook { + a.deletedHookObjects[ref] = true + } else { + a.deletedObjects[ref] = true } + return true + } + if !errors.IsNotFound(err) { + a.HandleError(ref, err) + return false + } + if !a.o.DryRun { + // just ignore 404 errors return false } + // now simulate deletion of objects that got applied in the same run + + wasApplied := false + if hook { + if _, ok := a.appliedObjects[ref]; ok { + wasApplied = true + } + } else { + if _, ok := a.appliedHookObjects[ref]; ok { + wasApplied = true + } + } + if !wasApplied { + // did not get applied, so just ignore the 404 + return false + } + + // it got applied, so we need to pretend it actually got deleted + a.mutex.Lock() defer a.mutex.Unlock() if hook { From 9c2a5e85a80edff91867519c11494b0681f879c7 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 28 Feb 2023 11:24:38 +0100 Subject: [PATCH 1465/2916] tests: Remove obsolete tests in regard to du.NewObjects --- pkg/deployment/utils/diff_utils_test.go | 47 ------------------------- 1 file changed, 47 deletions(-) diff --git a/pkg/deployment/utils/diff_utils_test.go b/pkg/deployment/utils/diff_utils_test.go index 0e1aa9d81..c97d60e62 100644 --- a/pkg/deployment/utils/diff_utils_test.go +++ b/pkg/deployment/utils/diff_utils_test.go @@ -57,34 +57,12 @@ func newTestConfigMap(name string, data map[string]interface{}) *uo.Unstructured func TestDiff(t *testing.T) { tests := []*diffTestConfig{ - { - name: "One new object", - ro: nil, - lo: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{})}, - ao: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{})}, - a: func(t *testing.T, dtc *diffTestConfig) { - assert.Len(t, dtc.du.NewObjects, 1) - assert.Len(t, dtc.du.ChangedObjects, 0) - assert.Equal(t, dtc.lo[0], dtc.du.NewObjects[0].Object) - }, - }, - { - name: "One deleted object", - ro: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{})}, - lo: nil, - ao: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{})}, - a: func(t *testing.T, dtc *diffTestConfig) { - assert.Len(t, dtc.du.NewObjects, 0) - assert.Len(t, dtc.du.ChangedObjects, 0) - }, - }, { name: "One changed object (changed field)", ro: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v1"})}, lo: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v2"})}, ao: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v2"})}, a: func(t *testing.T, dtc *diffTestConfig) { - assert.Len(t, dtc.du.NewObjects, 0) assert.Len(t, dtc.du.ChangedObjects, 1) assert.Equal(t, dtc.du.ChangedObjects[0].NewObject, dtc.ao[0]) assert.Equal(t, []types.Change{ @@ -98,7 +76,6 @@ func TestDiff(t *testing.T) { lo: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v1", "d2": "v2"})}, ao: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v1", "d2": "v2"})}, a: func(t *testing.T, dtc *diffTestConfig) { - assert.Len(t, dtc.du.NewObjects, 0) assert.Len(t, dtc.du.ChangedObjects, 1) assert.Equal(t, dtc.du.ChangedObjects[0].NewObject, dtc.ao[0]) assert.Equal(t, []types.Change{ @@ -112,7 +89,6 @@ func TestDiff(t *testing.T) { lo: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v1"})}, ao: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v1"})}, a: func(t *testing.T, dtc *diffTestConfig) { - assert.Len(t, dtc.du.NewObjects, 0) assert.Len(t, dtc.du.ChangedObjects, 1) assert.Equal(t, dtc.du.ChangedObjects[0].NewObject, dtc.ao[0]) assert.Equal(t, []types.Change{ @@ -126,7 +102,6 @@ func TestDiff(t *testing.T) { lo: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d2": "v2"})}, ao: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d2": "v2"})}, a: func(t *testing.T, dtc *diffTestConfig) { - assert.Len(t, dtc.du.NewObjects, 0) assert.Len(t, dtc.du.ChangedObjects, 1) assert.Equal(t, dtc.du.ChangedObjects[0].NewObject, dtc.ao[0]) assert.Equal(t, []types.Change{ @@ -141,7 +116,6 @@ func TestDiff(t *testing.T) { lo: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v12", "d3": "v3"})}, ao: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v12", "d3": "v3"})}, a: func(t *testing.T, dtc *diffTestConfig) { - assert.Len(t, dtc.du.NewObjects, 0) assert.Len(t, dtc.du.ChangedObjects, 1) assert.Equal(t, dtc.du.ChangedObjects[0].NewObject, dtc.ao[0]) assert.Equal(t, []types.Change{ @@ -151,27 +125,6 @@ func TestDiff(t *testing.T) { }, dtc.du.ChangedObjects[0].Changes) }, }, - { - name: "Two changed objects + One new object", - ro: []*uo.UnstructuredObject{newTestConfigMap("test1", map[string]interface{}{"d1": "v1", "d2": "v2"}), newTestConfigMap("test2", map[string]interface{}{"xd1": "xv1", "xd2": "xv2"})}, - lo: []*uo.UnstructuredObject{newTestConfigMap("test1", map[string]interface{}{"d1": "v1", "d2": "v3"}), newTestConfigMap("test2", map[string]interface{}{"xd3": "xv2"}), newTestConfigMap("test3", map[string]interface{}{"yd3": "yv2"})}, - ao: []*uo.UnstructuredObject{newTestConfigMap("test1", map[string]interface{}{"d1": "v1", "d2": "v3"}), newTestConfigMap("test2", map[string]interface{}{"xd3": "xv2"}), newTestConfigMap("test3", map[string]interface{}{"yd3": "yv2"})}, - a: func(t *testing.T, dtc *diffTestConfig) { - assert.Len(t, dtc.du.NewObjects, 1) - assert.Len(t, dtc.du.ChangedObjects, 2) - assert.Equal(t, dtc.du.ChangedObjects[0].NewObject, dtc.ao[0]) - assert.Equal(t, dtc.du.ChangedObjects[1].NewObject, dtc.ao[1]) - assert.Equal(t, dtc.du.NewObjects[0].Object, dtc.ao[2]) - assert.Equal(t, []types.Change{ - types.Change{Type: "update", JsonPath: "data.d2", OldValue: "v2", NewValue: "v3", UnifiedDiff: "-v2\n+v3"}, - }, dtc.du.ChangedObjects[0].Changes) - assert.Equal(t, []types.Change{ - types.Change{Type: "delete", JsonPath: "data.xd1", OldValue: "xv1", NewValue: interface{}(nil), UnifiedDiff: "-xv1"}, - types.Change{Type: "delete", JsonPath: "data.xd2", OldValue: "xv2", NewValue: interface{}(nil), UnifiedDiff: "-xv2"}, - types.Change{Type: "insert", JsonPath: "data.xd3", OldValue: interface{}(nil), NewValue: "xv2", UnifiedDiff: "+xv2"}, - }, dtc.du.ChangedObjects[1].Changes) - }, - }, } for _, test := range tests { From ceffd6e9e59788e400dd263df91f920a77369c4e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 28 Feb 2023 11:25:47 +0100 Subject: [PATCH 1466/2916] chore: Remove unused code --- pkg/utils/python_scanner/README.md | 2 - pkg/utils/python_scanner/scanner.go | 793 ---------------------------- 2 files changed, 795 deletions(-) delete mode 100644 pkg/utils/python_scanner/README.md delete mode 100644 pkg/utils/python_scanner/scanner.go diff --git a/pkg/utils/python_scanner/README.md b/pkg/utils/python_scanner/README.md deleted file mode 100644 index 645cb1299..000000000 --- a/pkg/utils/python_scanner/README.md +++ /dev/null @@ -1,2 +0,0 @@ -This package is a copy of golangs text/scanner package, with the difference that it interprets ' and ` the same way as ", so -that it's a little bit more like python. \ No newline at end of file diff --git a/pkg/utils/python_scanner/scanner.go b/pkg/utils/python_scanner/scanner.go deleted file mode 100644 index 006f9107b..000000000 --- a/pkg/utils/python_scanner/scanner.go +++ /dev/null @@ -1,793 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package scanner provides a scanner and tokenizer for UTF-8-encoded text. -// It takes an io.Reader providing the source, which then can be tokenized -// through repeated calls to the Scan function. For compatibility with -// existing tools, the NUL character is not allowed. If the first character -// in the source is a UTF-8 encoded byte order mark (BOM), it is discarded. -// -// By default, a Scanner skips white space and Go comments and recognizes all -// literals as defined by the Go language specification. It may be -// customized to recognize only a subset of those literals and to recognize -// different identifier and white space characters. -package python_scanner - -import ( - "bytes" - "fmt" - "io" - "os" - "unicode" - "unicode/utf8" -) - -// Position is a value that represents a source position. -// A position is valid if Line > 0. -type Position struct { - Filename string // filename, if any - Offset int // byte offset, starting at 0 - Line int // line number, starting at 1 - Column int // column number, starting at 1 (character count per line) -} - -// IsValid reports whether the position is valid. -func (pos *Position) IsValid() bool { return pos.Line > 0 } - -func (pos Position) String() string { - s := pos.Filename - if s == "" { - s = "" - } - if pos.IsValid() { - s += fmt.Sprintf(":%d:%d", pos.Line, pos.Column) - } - return s -} - -// Predefined mode bits to control recognition of tokens. For instance, -// to configure a Scanner such that it only recognizes (Go) identifiers, -// integers, and skips comments, set the Scanner's Mode field to: -// -// ScanIdents | ScanInts | SkipComments -// -// With the exceptions of comments, which are skipped if SkipComments is -// set, unrecognized tokens are not ignored. Instead, the scanner simply -// returns the respective individual characters (or possibly sub-tokens). -// For instance, if the mode is ScanIdents (not ScanStrings), the string -// "foo" is scanned as the token sequence '"' Ident '"'. -// -// Use GoTokens to configure the Scanner such that it accepts all Go -// literal tokens including Go identifiers. Comments will be skipped. -const ( - ScanIdents = 1 << -Ident - ScanInts = 1 << -Int - ScanFloats = 1 << -Float // includes Ints and hexadecimal floats - ScanChars = 1 << -Char - ScanStrings = 1 << -String - ScanRawStrings = 1 << -RawString - ScanComments = 1 << -Comment - SkipComments = 1 << -skipComment // if set with ScanComments, comments become white space - GoTokens = ScanIdents | ScanFloats | ScanChars | ScanStrings | ScanRawStrings | ScanComments | SkipComments -) - -// The result of Scan is one of these tokens or a Unicode character. -const ( - EOF = -(iota + 1) - Ident - Int - Float - Char - String - RawString - Comment - - // internal use only - skipComment -) - -var tokenString = map[rune]string{ - EOF: "EOF", - Ident: "Ident", - Int: "Int", - Float: "Float", - Char: "Char", - String: "String", - RawString: "RawString", - Comment: "Comment", -} - -// TokenString returns a printable string for a token or Unicode character. -func TokenString(tok rune) string { - if s, found := tokenString[tok]; found { - return s - } - return fmt.Sprintf("%q", string(tok)) -} - -// GoWhitespace is the default value for the Scanner's Whitespace field. -// Its value selects Go's white space characters. -const GoWhitespace = 1<<'\t' | 1<<'\n' | 1<<'\r' | 1<<' ' - -const bufLen = 1024 // at least utf8.UTFMax - -// A Scanner implements reading of Unicode characters and tokens from an io.Reader. -type Scanner struct { - // Input - src io.Reader - - // Source buffer - srcBuf [bufLen + 1]byte // +1 for sentinel for common case of s.next() - srcPos int // reading position (srcBuf index) - srcEnd int // source end (srcBuf index) - - // Source position - srcBufOffset int // byte offset of srcBuf[0] in source - line int // line count - column int // character count - lastLineLen int // length of last line in characters (for correct column reporting) - lastCharLen int // length of last character in bytes - - // Token text buffer - // Typically, token text is stored completely in srcBuf, but in general - // the token text's head may be buffered in tokBuf while the token text's - // tail is stored in srcBuf. - tokBuf bytes.Buffer // token text head that is not in srcBuf anymore - tokPos int // token text tail position (srcBuf index); valid if >= 0 - tokEnd int // token text tail end (srcBuf index) - - // One character look-ahead - ch rune // character before current srcPos - - // Error is called for each error encountered. If no Error - // function is set, the error is reported to os.Stderr. - Error func(s *Scanner, msg string) - - // ErrorCount is incremented by one for each error encountered. - ErrorCount int - - // The Mode field controls which tokens are recognized. For instance, - // to recognize Ints, set the ScanInts bit in Mode. The field may be - // changed at any time. - Mode uint - - // The Whitespace field controls which characters are recognized - // as white space. To recognize a character ch <= ' ' as white space, - // set the ch'th bit in Whitespace (the Scanner's behavior is undefined - // for values ch > ' '). The field may be changed at any time. - Whitespace uint64 - - // IsIdentRune is a predicate controlling the characters accepted - // as the ith rune in an identifier. The set of valid characters - // must not intersect with the set of white space characters. - // If no IsIdentRune function is set, regular Go identifiers are - // accepted instead. The field may be changed at any time. - IsIdentRune func(ch rune, i int) bool - - // Start position of most recently scanned token; set by Scan. - // Calling Init or Next invalidates the position (Line == 0). - // The Filename field is always left untouched by the Scanner. - // If an error is reported (via Error) and Position is invalid, - // the scanner is not inside a token. Call Pos to obtain an error - // position in that case, or to obtain the position immediately - // after the most recently scanned token. - Position -} - -// Init initializes a Scanner with a new source and returns s. -// Error is set to nil, ErrorCount is set to 0, Mode is set to GoTokens, -// and Whitespace is set to GoWhitespace. -func (s *Scanner) Init(src io.Reader) *Scanner { - s.src = src - - // initialize source buffer - // (the first call to next() will fill it by calling src.Read) - s.srcBuf[0] = utf8.RuneSelf // sentinel - s.srcPos = 0 - s.srcEnd = 0 - - // initialize source position - s.srcBufOffset = 0 - s.line = 1 - s.column = 0 - s.lastLineLen = 0 - s.lastCharLen = 0 - - // initialize token text buffer - // (required for first call to next()). - s.tokPos = -1 - - // initialize one character look-ahead - s.ch = -2 // no char read yet, not EOF - - // initialize public fields - s.Error = nil - s.ErrorCount = 0 - s.Mode = GoTokens - s.Whitespace = GoWhitespace - s.Line = 0 // invalidate token position - - return s -} - -// next reads and returns the next Unicode character. It is designed such -// that only a minimal amount of work needs to be done in the common ASCII -// case (one test to check for both ASCII and end-of-buffer, and one test -// to check for newlines). -func (s *Scanner) next() rune { - ch, width := rune(s.srcBuf[s.srcPos]), 1 - - if ch >= utf8.RuneSelf { - // uncommon case: not ASCII or not enough bytes - for s.srcPos+utf8.UTFMax > s.srcEnd && !utf8.FullRune(s.srcBuf[s.srcPos:s.srcEnd]) { - // not enough bytes: read some more, but first - // save away token text if any - if s.tokPos >= 0 { - s.tokBuf.Write(s.srcBuf[s.tokPos:s.srcPos]) - s.tokPos = 0 - // s.tokEnd is set by Scan() - } - // move unread bytes to beginning of buffer - copy(s.srcBuf[0:], s.srcBuf[s.srcPos:s.srcEnd]) - s.srcBufOffset += s.srcPos - // read more bytes - // (an io.Reader must return io.EOF when it reaches - // the end of what it is reading - simply returning - // n == 0 will make this loop retry forever; but the - // error is in the reader implementation in that case) - i := s.srcEnd - s.srcPos - n, err := s.src.Read(s.srcBuf[i:bufLen]) - s.srcPos = 0 - s.srcEnd = i + n - s.srcBuf[s.srcEnd] = utf8.RuneSelf // sentinel - if err != nil { - if err != io.EOF { - s.error(err.Error()) - } - if s.srcEnd == 0 { - if s.lastCharLen > 0 { - // previous character was not EOF - s.column++ - } - s.lastCharLen = 0 - return EOF - } - // If err == EOF, we won't be getting more - // bytes; break to avoid infinite loop. If - // err is something else, we don't know if - // we can get more bytes; thus also break. - break - } - } - // at least one byte - ch = rune(s.srcBuf[s.srcPos]) - if ch >= utf8.RuneSelf { - // uncommon case: not ASCII - ch, width = utf8.DecodeRune(s.srcBuf[s.srcPos:s.srcEnd]) - if ch == utf8.RuneError && width == 1 { - // advance for correct error position - s.srcPos += width - s.lastCharLen = width - s.column++ - s.error("invalid UTF-8 encoding") - return ch - } - } - } - - // advance - s.srcPos += width - s.lastCharLen = width - s.column++ - - // special situations - switch ch { - case 0: - // for compatibility with other tools - s.error("invalid character NUL") - case '\n': - s.line++ - s.lastLineLen = s.column - s.column = 0 - } - - return ch -} - -// Next reads and returns the next Unicode character. -// It returns EOF at the end of the source. It reports -// a read error by calling s.Error, if not nil; otherwise -// it prints an error message to os.Stderr. Next does not -// update the Scanner's Position field; use Pos() to -// get the current position. -func (s *Scanner) Next() rune { - s.tokPos = -1 // don't collect token text - s.Line = 0 // invalidate token position - ch := s.Peek() - if ch != EOF { - s.ch = s.next() - } - return ch -} - -// Peek returns the next Unicode character in the source without advancing -// the scanner. It returns EOF if the scanner's position is at the last -// character of the source. -func (s *Scanner) Peek() rune { - if s.ch == -2 { - // this code is only run for the very first character - s.ch = s.next() - if s.ch == '\uFEFF' { - s.ch = s.next() // ignore BOM - } - } - return s.ch -} - -func (s *Scanner) error(msg string) { - s.tokEnd = s.srcPos - s.lastCharLen // make sure token text is terminated - s.ErrorCount++ - if s.Error != nil { - s.Error(s, msg) - return - } - pos := s.Position - if !pos.IsValid() { - pos = s.Pos() - } - fmt.Fprintf(os.Stderr, "%s: %s\n", pos, msg) -} - -func (s *Scanner) errorf(format string, args ...interface{}) { - s.error(fmt.Sprintf(format, args...)) -} - -func (s *Scanner) isIdentRune(ch rune, i int) bool { - if s.IsIdentRune != nil { - return s.IsIdentRune(ch, i) - } - return ch == '_' || unicode.IsLetter(ch) || unicode.IsDigit(ch) && i > 0 -} - -func (s *Scanner) scanIdentifier() rune { - // we know the zero'th rune is OK; start scanning at the next one - ch := s.next() - for i := 1; s.isIdentRune(ch, i); i++ { - ch = s.next() - } - return ch -} - -func lower(ch rune) rune { return ('a' - 'A') | ch } // returns lower-case ch iff ch is ASCII letter -func isDecimal(ch rune) bool { return '0' <= ch && ch <= '9' } -func isHex(ch rune) bool { return '0' <= ch && ch <= '9' || 'a' <= lower(ch) && lower(ch) <= 'f' } - -// digits accepts the sequence { digit | '_' } starting with ch0. -// If base <= 10, digits accepts any decimal digit but records -// the first invalid digit >= base in *invalid if *invalid == 0. -// digits returns the first rune that is not part of the sequence -// anymore, and a bitset describing whether the sequence contained -// digits (bit 0 is set), or separators '_' (bit 1 is set). -func (s *Scanner) digits(ch0 rune, base int, invalid *rune) (ch rune, digsep int) { - ch = ch0 - if base <= 10 { - max := rune('0' + base) - for isDecimal(ch) || ch == '_' { - ds := 1 - if ch == '_' { - ds = 2 - } else if ch >= max && *invalid == 0 { - *invalid = ch - } - digsep |= ds - ch = s.next() - } - } else { - for isHex(ch) || ch == '_' { - ds := 1 - if ch == '_' { - ds = 2 - } - digsep |= ds - ch = s.next() - } - } - return -} - -func (s *Scanner) scanNumber(ch rune, seenDot bool) (rune, rune) { - base := 10 // number base - prefix := rune(0) // one of 0 (decimal), '0' (0-octal), 'x', 'o', or 'b' - digsep := 0 // bit 0: digit present, bit 1: '_' present - invalid := rune(0) // invalid digit in literal, or 0 - - // integer part - var tok rune - var ds int - if !seenDot { - tok = Int - if ch == '0' { - ch = s.next() - switch lower(ch) { - case 'x': - ch = s.next() - base, prefix = 16, 'x' - case 'o': - ch = s.next() - base, prefix = 8, 'o' - case 'b': - ch = s.next() - base, prefix = 2, 'b' - default: - base, prefix = 8, '0' - digsep = 1 // leading 0 - } - } - ch, ds = s.digits(ch, base, &invalid) - digsep |= ds - if ch == '.' && s.Mode&ScanFloats != 0 { - ch = s.next() - seenDot = true - } - } - - // fractional part - if seenDot { - tok = Float - if prefix == 'o' || prefix == 'b' { - s.error("invalid radix point in " + litname(prefix)) - } - ch, ds = s.digits(ch, base, &invalid) - digsep |= ds - } - - if digsep&1 == 0 { - s.error(litname(prefix) + " has no digits") - } - - // exponent - if e := lower(ch); (e == 'e' || e == 'p') && s.Mode&ScanFloats != 0 { - switch { - case e == 'e' && prefix != 0 && prefix != '0': - s.errorf("%q exponent requires decimal mantissa", ch) - case e == 'p' && prefix != 'x': - s.errorf("%q exponent requires hexadecimal mantissa", ch) - } - ch = s.next() - tok = Float - if ch == '+' || ch == '-' { - ch = s.next() - } - ch, ds = s.digits(ch, 10, nil) - digsep |= ds - if ds&1 == 0 { - s.error("exponent has no digits") - } - } else if prefix == 'x' && tok == Float { - s.error("hexadecimal mantissa requires a 'p' exponent") - } - - if tok == Int && invalid != 0 { - s.errorf("invalid digit %q in %s", invalid, litname(prefix)) - } - - if digsep&2 != 0 { - s.tokEnd = s.srcPos - s.lastCharLen // make sure token text is terminated - if i := invalidSep(s.TokenText()); i >= 0 { - s.error("'_' must separate successive digits") - } - } - - return tok, ch -} - -func litname(prefix rune) string { - switch prefix { - default: - return "decimal literal" - case 'x': - return "hexadecimal literal" - case 'o', '0': - return "octal literal" - case 'b': - return "binary literal" - } -} - -// invalidSep returns the index of the first invalid separator in x, or -1. -func invalidSep(x string) int { - x1 := ' ' // prefix char, we only care if it's 'x' - d := '.' // digit, one of '_', '0' (a digit), or '.' (anything else) - i := 0 - - // a prefix counts as a digit - if len(x) >= 2 && x[0] == '0' { - x1 = lower(rune(x[1])) - if x1 == 'x' || x1 == 'o' || x1 == 'b' { - d = '0' - i = 2 - } - } - - // mantissa and exponent - for ; i < len(x); i++ { - p := d // previous digit - d = rune(x[i]) - switch { - case d == '_': - if p != '0' { - return i - } - case isDecimal(d) || x1 == 'x' && isHex(d): - d = '0' - default: - if p == '_' { - return i - 1 - } - d = '.' - } - } - if d == '_' { - return len(x) - 1 - } - - return -1 -} - -func digitVal(ch rune) int { - switch { - case '0' <= ch && ch <= '9': - return int(ch - '0') - case 'a' <= lower(ch) && lower(ch) <= 'f': - return int(lower(ch) - 'a' + 10) - } - return 16 // larger than any legal digit val -} - -func (s *Scanner) scanDigits(ch rune, base, n int) rune { - for n > 0 && digitVal(ch) < base { - ch = s.next() - n-- - } - if n > 0 { - s.error("invalid char escape") - } - return ch -} - -func (s *Scanner) scanEscape(quote rune) rune { - ch := s.next() // read character after '/' - switch ch { - case 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', quote: - // nothing to do - ch = s.next() - case '0', '1', '2', '3', '4', '5', '6', '7': - ch = s.scanDigits(ch, 8, 3) - case 'x': - ch = s.scanDigits(s.next(), 16, 2) - case 'u': - ch = s.scanDigits(s.next(), 16, 4) - case 'U': - ch = s.scanDigits(s.next(), 16, 8) - default: - s.error("invalid char escape") - } - return ch -} - -func (s *Scanner) scanString(quote rune) (n int) { - ch := s.next() // read character after quote - for ch != quote { - if ch == '\n' || ch < 0 { - s.error("literal not terminated") - return - } - if ch == '\\' { - ch = s.scanEscape(quote) - } else { - ch = s.next() - } - n++ - } - return -} - -func (s *Scanner) scanRawString(quote rune) { - ch := s.next() // read character after '`' - for ch != quote { - if ch < 0 { - s.error("literal not terminated") - return - } - ch = s.next() - } -} - -func (s *Scanner) scanChar() { - if s.scanString('\'') != 1 { - s.error("invalid char literal") - } -} - -func (s *Scanner) scanComment(ch rune) rune { - // ch == '/' || ch == '*' - if ch == '/' { - // line comment - ch = s.next() // read character after "//" - for ch != '\n' && ch >= 0 { - ch = s.next() - } - return ch - } - - // general comment - ch = s.next() // read character after "/*" - for { - if ch < 0 { - s.error("comment not terminated") - break - } - ch0 := ch - ch = s.next() - if ch0 == '*' && ch == '/' { - ch = s.next() - break - } - } - return ch -} - -// Scan reads the next token or Unicode character from source and returns it. -// It only recognizes tokens t for which the respective Mode bit (1<<-t) is set. -// It returns EOF at the end of the source. It reports scanner errors (read and -// token errors) by calling s.Error, if not nil; otherwise it prints an error -// message to os.Stderr. -func (s *Scanner) Scan() rune { - ch := s.Peek() - - // reset token text position - s.tokPos = -1 - s.Line = 0 - -redo: - // skip white space - for s.Whitespace&(1< 0 { - // common case: last character was not a '\n' - s.Line = s.line - s.Column = s.column - } else { - // last character was a '\n' - // (we cannot be at the beginning of the source - // since we have called next() at least once) - s.Line = s.line - 1 - s.Column = s.lastLineLen - } - - // determine token value - tok := ch - switch { - case s.isIdentRune(ch, 0): - if s.Mode&ScanIdents != 0 { - tok = Ident - ch = s.scanIdentifier() - } else { - ch = s.next() - } - case isDecimal(ch): - if s.Mode&(ScanInts|ScanFloats) != 0 { - tok, ch = s.scanNumber(ch, false) - } else { - ch = s.next() - } - default: - switch ch { - case EOF: - break - case '"': - if s.Mode&ScanStrings != 0 { - s.scanRawString('"') - tok = String - } - ch = s.next() - case '\'': - // This is the difference to golang's text/scanner package, we handle ' as a string and not as a char - if s.Mode&ScanStrings != 0 { - s.scanRawString('\'') - tok = String - } - ch = s.next() - case '.': - ch = s.next() - if isDecimal(ch) && s.Mode&ScanFloats != 0 { - tok, ch = s.scanNumber(ch, true) - } - case '/': - ch = s.next() - if (ch == '/' || ch == '*') && s.Mode&ScanComments != 0 { - if s.Mode&SkipComments != 0 { - s.tokPos = -1 // don't collect token text - ch = s.scanComment(ch) - goto redo - } - ch = s.scanComment(ch) - tok = Comment - } - case '`': - if s.Mode&ScanRawStrings != 0 { - s.scanRawString('`') - tok = RawString - } - ch = s.next() - default: - ch = s.next() - } - } - - // end of token text - s.tokEnd = s.srcPos - s.lastCharLen - - s.ch = ch - return tok -} - -// Pos returns the position of the character immediately after -// the character or token returned by the last call to Next or Scan. -// Use the Scanner's Position field for the start position of the most -// recently scanned token. -func (s *Scanner) Pos() (pos Position) { - pos.Filename = s.Filename - pos.Offset = s.srcBufOffset + s.srcPos - s.lastCharLen - switch { - case s.column > 0: - // common case: last character was not a '\n' - pos.Line = s.line - pos.Column = s.column - case s.lastLineLen > 0: - // last character was a '\n' - pos.Line = s.line - 1 - pos.Column = s.lastLineLen - default: - // at the beginning of the source - pos.Line = 1 - pos.Column = 1 - } - return -} - -// TokenText returns the string corresponding to the most recently scanned token. -// Valid after calling Scan and in calls of Scanner.Error. -func (s *Scanner) TokenText() string { - if s.tokPos < 0 { - // no token text - return "" - } - - if s.tokEnd < s.tokPos { - // if EOF was reached, s.tokEnd is set to -1 (s.srcPos == 0) - s.tokEnd = s.tokPos - } - // s.tokEnd >= s.tokPos - - if s.tokBuf.Len() == 0 { - // common case: the entire token text is still in srcBuf - return string(s.srcBuf[s.tokPos:s.tokEnd]) - } - - // part of the token text was saved in tokBuf: save the rest in - // tokBuf as well and return its content - s.tokBuf.Write(s.srcBuf[s.tokPos:s.tokEnd]) - s.tokPos = s.tokEnd // ensure idempotency of TokenText() call - return s.tokBuf.String() -} From c49ee633d28995a88d514b87dd3809d84fdd9585 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 28 Feb 2023 11:32:26 +0100 Subject: [PATCH 1467/2916] fix: Don't consider objects as ready when the observedGeneration != generation (#340) This fixes issues with Deployment objects being marked as ready right after applying changes that would make in non-ready. --- pkg/utils/uo/k8s_fields.go | 8 ++++++++ pkg/validation/validation.go | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/pkg/utils/uo/k8s_fields.go b/pkg/utils/uo/k8s_fields.go index 2758945b1..f97296dee 100644 --- a/pkg/utils/uo/k8s_fields.go +++ b/pkg/utils/uo/k8s_fields.go @@ -186,6 +186,14 @@ func (uo *UnstructuredObject) GetK8sAnnotationsWithRegex(r interface{}) map[stri return ret } +func (uo *UnstructuredObject) GetK8sGeneration() int64 { + ret, ok, _ := uo.GetNestedInt("metadata", "generation") + if !ok { + return -1 + } + return ret +} + func (uo *UnstructuredObject) GetK8sResourceVersion() string { ret, _, _ := uo.GetNestedString("metadata", "resourceVersion") return ret diff --git a/pkg/validation/validation.go b/pkg/validation/validation.go index 36cf3445e..36a034637 100644 --- a/pkg/validation/validation.go +++ b/pkg/validation/validation.go @@ -258,6 +258,12 @@ func ValidateObject(k *k8s.K8sCluster, o *uo.UnstructuredObject, notReadyIsError return i, nil } + observedGeneration := getStatusFieldInt("observedGeneration", reactIgnore, false, -1) + if observedGeneration != -1 && observedGeneration != o.GetK8sGeneration() { + addNotReady("Waiting for reconciliation") + return + } + switch o.GetK8sGVK().GroupKind() { case schema.GroupKind{Group: "", Kind: "Pod"}: containerStatuses, _, err := status.GetNestedObjectList("containerStatuses") From 6af108299f3ae3fbfc11310382fd95b8720c30de Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 6 Mar 2023 15:19:50 +0100 Subject: [PATCH 1468/2916] fix: Also try to parse repo key without dummy scheme --- cmd/kluctl/commands/utils.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 35fa12bab..32f447dd3 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -239,13 +239,16 @@ func parseRepoOverride(s string, isGroup bool) (ret repocache.RepoOverride, err return repocache.RepoOverride{}, fmt.Errorf("%s", s) } - // we need to prepend a dummy scheme to the repo key so that it is properly parsed - dummyUrl := fmt.Sprintf("git://%s", sp[0]) - - u, err := git_url.Parse(dummyUrl) + u, err := git_url.Parse(sp[0]) if err != nil { - return repocache.RepoOverride{}, fmt.Errorf("%s: %w", s, err) + // we need to prepend a dummy scheme to the repo key so that it is properly parsed + dummyUrl := fmt.Sprintf("git://%s", sp[0]) + u, err = git_url.Parse(dummyUrl) + if err != nil { + return repocache.RepoOverride{}, fmt.Errorf("%s: %w", s, err) + } } + u = u.Normalize() ret.RepoUrl = *u ret.Override = sp[1] From c712cf0b8516dad783ce88f49e67c4da06512f0a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 6 Mar 2023 17:11:43 +0100 Subject: [PATCH 1469/2916] fix: Fix RemoveNestedField to properly delete slice elements (#353) --- pkg/utils/uo/nested_fields.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/utils/uo/nested_fields.go b/pkg/utils/uo/nested_fields.go index 7d3ce0afd..13560b767 100644 --- a/pkg/utils/uo/nested_fields.go +++ b/pkg/utils/uo/nested_fields.go @@ -62,7 +62,7 @@ func (uo *UnstructuredObject) RemoveNestedField(keys ...interface{}) error { return nil } l = append(l[:i], l[i+1:]...) - return SetChild(o, i, l) + return uo.SetNestedField(l, keys[0:len(keys)-1]...) } else { return fmt.Errorf("key is not an index") } From 7dd2b430f74f6515f04e55fda376dc91c4c774f2 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 6 Mar 2023 17:31:52 +0100 Subject: [PATCH 1470/2916] refactor: Use private copy of giturls --- go.mod | 1 - go.sum | 2 - pkg/git/auth/git_credentials_file.go | 2 +- pkg/git/git-url/giturls/LICENSE | 21 +++ pkg/git/git-url/giturls/urls.go | 150 +++++++++++++++++++ pkg/git/git-url/giturls/urls_test.go | 215 +++++++++++++++++++++++++++ pkg/git/git-url/url.go | 2 +- 7 files changed, 388 insertions(+), 5 deletions(-) create mode 100644 pkg/git/git-url/giturls/LICENSE create mode 100644 pkg/git/git-url/giturls/urls.go create mode 100644 pkg/git/git-url/giturls/urls_test.go diff --git a/go.mod b/go.mod index 683361d28..199e0bb85 100644 --- a/go.mod +++ b/go.mod @@ -36,7 +36,6 @@ require ( github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.15.0 github.com/stretchr/testify v1.8.2 - github.com/whilp/git-urls v1.0.0 github.com/xanzy/ssh-agent v0.3.3 golang.org/x/crypto v0.6.0 golang.org/x/net v0.7.0 diff --git a/go.sum b/go.sum index ca78af9e0..46b38de43 100644 --- a/go.sum +++ b/go.sum @@ -762,8 +762,6 @@ github.com/urfave/cli v1.22.7/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX github.com/vbatts/tar-split v0.11.2 h1:Via6XqJr0hceW4wff3QRzD5gAk/tatMw/4ZA7cTlIME= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= -github.com/whilp/git-urls v1.0.0 h1:95f6UMWN5FKW71ECsXRUd3FVYiXdrE7aX4NZKcPmIjU= -github.com/whilp/git-urls v1.0.0/go.mod h1:J16SAmobsqc3Qcy98brfl5f5+e0clUvg1krgwk/qCfE= github.com/xanzy/ssh-agent v0.3.2/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= diff --git a/pkg/git/auth/git_credentials_file.go b/pkg/git/auth/git_credentials_file.go index eb1696075..3f66d4b45 100644 --- a/pkg/git/auth/git_credentials_file.go +++ b/pkg/git/auth/git_credentials_file.go @@ -5,8 +5,8 @@ import ( "context" "github.com/fluxcd/go-git/v5/plumbing/transport/http" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" + "github.com/kluctl/kluctl/v2/pkg/git/git-url/giturls" "github.com/kluctl/kluctl/v2/pkg/git/messages" - giturls "github.com/whilp/git-urls" "os" "path/filepath" ) diff --git a/pkg/git/git-url/giturls/LICENSE b/pkg/git/git-url/giturls/LICENSE new file mode 100644 index 000000000..2aa848db5 --- /dev/null +++ b/pkg/git/git-url/giturls/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Will Maier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/pkg/git/git-url/giturls/urls.go b/pkg/git/git-url/giturls/urls.go new file mode 100644 index 000000000..02341030a --- /dev/null +++ b/pkg/git/git-url/giturls/urls.go @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2014 Will Maier + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* +Package giturls parses Git URLs. + +These URLs include standard RFC 3986 URLs as well as special formats that +are specific to Git. Examples are provided in the Git documentation at +https://www.kernel.org/pub/software/scm/git/docs/git-clone.html. +*/ +package giturls + +import ( + "fmt" + "net/url" + "regexp" + "strings" +) + +var ( + // scpSyntax was modified from https://golang.org/src/cmd/go/vcs.go. + scpSyntax = regexp.MustCompile(`^([a-zA-Z0-9_]+@)?([a-zA-Z0-9._-]+):([a-zA-Z0-9./._-]+)(?:\?||$)(.*)$`) + + // Transports is a set of known Git URL schemes. + Transports = NewTransportSet( + "ssh", + "git", + "git+ssh", + "http", + "https", + "ftp", + "ftps", + "rsync", + "file", + ) +) + +// Parser converts a string into a URL. +type Parser func(string) (*url.URL, error) + +// Parse parses rawurl into a URL structure. Parse first attempts to +// find a standard URL with a valid Git transport as its scheme. If +// that cannot be found, it then attempts to find a SCP-like URL. And +// if that cannot be found, it assumes rawurl is a local path. If none +// of these rules apply, Parse returns an error. +func Parse(rawurl string) (u *url.URL, err error) { + parsers := []Parser{ + ParseTransport, + ParseScp, + ParseLocal, + } + + // Apply each parser in turn; if the parser succeeds, accept its + // result and return. + for _, p := range parsers { + u, err = p(rawurl) + if err == nil { + return u, err + } + } + + // It's unlikely that none of the parsers will succeed, since + // ParseLocal is very forgiving. + return new(url.URL), fmt.Errorf("failed to parse %q", rawurl) +} + +// ParseTransport parses rawurl into a URL object. Unless the URL's +// scheme is a known Git transport, ParseTransport returns an error. +func ParseTransport(rawurl string) (*url.URL, error) { + u, err := url.Parse(rawurl) + if err == nil && !Transports.Valid(u.Scheme) { + err = fmt.Errorf("scheme %q is not a valid transport", u.Scheme) + } + return u, err +} + +// ParseScp parses rawurl into a URL object. The rawurl must be +// an SCP-like URL, otherwise ParseScp returns an error. +func ParseScp(rawurl string) (*url.URL, error) { + match := scpSyntax.FindAllStringSubmatch(rawurl, -1) + if len(match) == 0 { + return nil, fmt.Errorf("no scp URL found in %q", rawurl) + } + m := match[0] + user := strings.TrimRight(m[1], "@") + var userinfo *url.Userinfo + if user != "" { + userinfo = url.User(user) + } + rawquery := "" + if len(m) > 3 { + rawquery = m[4] + } + return &url.URL{ + Scheme: "ssh", + User: userinfo, + Host: m[2], + Path: m[3], + RawQuery: rawquery, + }, nil +} + +// ParseLocal parses rawurl into a URL object with a "file" +// scheme. This will effectively never return an error. +func ParseLocal(rawurl string) (*url.URL, error) { + return &url.URL{ + Scheme: "file", + Host: "", + Path: rawurl, + }, nil +} + +// TransportSet represents a set of valid Git transport schemes. It +// maps these schemes to empty structs, providing a set-like +// interface. +type TransportSet struct { + Transports map[string]struct{} +} + +// NewTransportSet returns a TransportSet with the items keys mapped +// to empty struct values. +func NewTransportSet(items ...string) *TransportSet { + t := &TransportSet{ + Transports: map[string]struct{}{}, + } + for _, i := range items { + t.Transports[i] = struct{}{} + } + return t +} + +// Valid returns true if transport is a known Git URL scheme and false +// if not. +func (t *TransportSet) Valid(transport string) bool { + _, ok := t.Transports[transport] + return ok +} diff --git a/pkg/git/git-url/giturls/urls_test.go b/pkg/git/git-url/giturls/urls_test.go new file mode 100644 index 000000000..4e3f6cb33 --- /dev/null +++ b/pkg/git/git-url/giturls/urls_test.go @@ -0,0 +1,215 @@ +package giturls + +import ( + "net/url" + "reflect" + "strings" + "testing" +) + +var tests []*Test + +type Test struct { + in string + wantURL *url.URL + wantStr string // expected result of reserializing the URL; empty means same as "in". +} + +func NewTest(in, transport, user, host, path, str, rawquery string) *Test { + var userinfo *url.Userinfo + + if user != "" { + if strings.Contains(user, ":") { + username := strings.Split(user, ":")[0] + password := strings.Split(user, ":")[1] + userinfo = url.UserPassword(username, password) + } else { + userinfo = url.User(user) + } + } + if str == "" { + str = in + } + + return &Test{ + in: in, + wantURL: &url.URL{ + Scheme: transport, + Host: host, + Path: path, + User: userinfo, + RawQuery: rawquery, + }, + wantStr: str, + } +} + +func init() { + // https://www.kernel.org/pub/software/scm/git/docs/git-clone.html + tests = []*Test{ + NewTest( + "user@host.xz:path/to/repo.git/", + "ssh", "user", "host.xz", "path/to/repo.git/", + "ssh://user@host.xz/path/to/repo.git/", "", + ), + NewTest( + "host.xz:path/to/repo.git/", + "ssh", "", "host.xz", "path/to/repo.git/", + "ssh://host.xz/path/to/repo.git/", "", + ), + NewTest( + "host.xz:/path/to/repo.git/", + "ssh", "", "host.xz", "/path/to/repo.git/", + "ssh://host.xz/path/to/repo.git/", "", + ), + NewTest( + "host.xz:path/to/repo-with_specials.git/", + "ssh", "", "host.xz", "path/to/repo-with_specials.git/", + "ssh://host.xz/path/to/repo-with_specials.git/", "", + ), + NewTest( + "git://host.xz/path/to/repo.git/", + "git", "", "host.xz", "/path/to/repo.git/", + "", "", + ), + NewTest( + "git://host.xz:1234/path/to/repo.git/", + "git", "", "host.xz:1234", "/path/to/repo.git/", + "", "", + ), + NewTest( + "http://host.xz/path/to/repo.git/", + "http", "", "host.xz", "/path/to/repo.git/", + "", "", + ), + NewTest( + "http://host.xz:1234/path/to/repo.git/", + "http", "", "host.xz:1234", "/path/to/repo.git/", + "", "", + ), + NewTest( + "https://host.xz/path/to/repo.git/", + "https", "", "host.xz", "/path/to/repo.git/", + "", "", + ), + NewTest( + "https://host.xz:1234/path/to/repo.git/", + "https", "", "host.xz:1234", "/path/to/repo.git/", + "", "", + ), + NewTest( + "ftp://host.xz/path/to/repo.git/", + "ftp", "", "host.xz", "/path/to/repo.git/", + "", "", + ), + NewTest( + "ftp://host.xz:1234/path/to/repo.git/", + "ftp", "", "host.xz:1234", "/path/to/repo.git/", + "", "", + ), + NewTest( + "ftps://host.xz/path/to/repo.git/", + "ftps", "", "host.xz", "/path/to/repo.git/", + "", "", + ), + NewTest( + "ftps://host.xz:1234/path/to/repo.git/", + "ftps", "", "host.xz:1234", "/path/to/repo.git/", + "", "", + ), + NewTest( + "rsync://host.xz/path/to/repo.git/", + "rsync", "", "host.xz", "/path/to/repo.git/", + "", "", + ), + NewTest( + "ssh://user@host.xz:1234/path/to/repo.git/", + "ssh", "user", "host.xz:1234", "/path/to/repo.git/", + "", "", + ), + NewTest( + "ssh://host.xz:1234/path/to/repo.git/", + "ssh", "", "host.xz:1234", "/path/to/repo.git/", + "", "", + ), + NewTest( + "ssh://host.xz/path/to/repo.git/", + "ssh", "", "host.xz", "/path/to/repo.git/", + "", "", + ), + NewTest( + "git+ssh://host.xz/path/to/repo.git/", + "git+ssh", "", "host.xz", "/path/to/repo.git/", + "", "", + ), + NewTest( + "/path/to/repo.git/", + "file", "", "", "/path/to/repo.git/", + "file:///path/to/repo.git/", "", + ), + NewTest( + "file:///path/to/repo.git/", + "file", "", "", "/path/to/repo.git/", + "", "", + ), + // Tests with query strings + NewTest( + "https://host.xz/organization/repo.git?ref=", + "https", "", "host.xz", "/organization/repo.git", + "", "ref=", + ), + NewTest( + "https://host.xz/organization/repo.git?ref=test", + "https", "", "host.xz", "/organization/repo.git", + "", "ref=test", + ), + NewTest( + "https://host.xz/organization/repo.git?ref=feature/test", + "https", "", "host.xz", "/organization/repo.git", + "", "ref=feature/test", + ), + NewTest( + "git@host.xz:organization/repo.git?ref=test", + "ssh", "git", "host.xz", "organization/repo.git", + "ssh://git@host.xz/organization/repo.git?ref=test", "ref=test", + ), + NewTest( + "git@host.xz:organization/repo.git?ref=feature/test", + "ssh", "git", "host.xz", "organization/repo.git", + "ssh://git@host.xz/organization/repo.git?ref=feature/test", "ref=feature/test", + ), + // Tests with user+password and some with query strings + NewTest( + "https://user:password@host.xz/organization/repo.git/", + "https", "user:password", "host.xz", "/organization/repo.git/", + "", "", + ), + NewTest( + "https://user:password@host.xz/organization/repo.git?ref=test", + "https", "user:password", "host.xz", "/organization/repo.git", + "", "ref=test", + ), + NewTest( + "https://user:password@host.xz/organization/repo.git?ref=feature/test", + "https", "user:password", "host.xz", "/organization/repo.git", + "", "ref=feature/test", + ), + } +} + +func TestParse(t *testing.T) { + for _, tt := range tests { + got, err := Parse(tt.in) + if err != nil { + t.Errorf("Parse(%q) = unexpected err %q, want %q", tt.in, err, tt.wantURL) + continue + } + if !reflect.DeepEqual(got, tt.wantURL) { + t.Errorf("Parse(%q) = %q, want %q", tt.in, got, tt.wantURL) + } + str := got.String() + if str != tt.wantStr { + t.Errorf("Parse(%q).String() = %q, want %q", tt.in, str, tt.wantStr) + } + } +} diff --git a/pkg/git/git-url/url.go b/pkg/git/git-url/url.go index 727e8796f..5bf30d9ee 100644 --- a/pkg/git/git-url/url.go +++ b/pkg/git/git-url/url.go @@ -2,7 +2,7 @@ package git_url import ( "fmt" - giturls "github.com/whilp/git-urls" + "github.com/kluctl/kluctl/v2/pkg/git/git-url/giturls" "net/url" "strings" ) From fa04e67501c32b5325b291fa1ee4fcb4f0a8f1e1 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 6 Mar 2023 17:50:34 +0100 Subject: [PATCH 1471/2916] fix: Porperly parse SCP urls with ports --- pkg/git/git-url/giturls/urls.go | 18 +++++++++++++----- pkg/git/git-url/giturls/urls_test.go | 5 +++++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/pkg/git/git-url/giturls/urls.go b/pkg/git/git-url/giturls/urls.go index 02341030a..3fa2ebe80 100644 --- a/pkg/git/git-url/giturls/urls.go +++ b/pkg/git/git-url/giturls/urls.go @@ -32,7 +32,7 @@ import ( var ( // scpSyntax was modified from https://golang.org/src/cmd/go/vcs.go. - scpSyntax = regexp.MustCompile(`^([a-zA-Z0-9_]+@)?([a-zA-Z0-9._-]+):([a-zA-Z0-9./._-]+)(?:\?||$)(.*)$`) + scpSyntax = regexp.MustCompile(`^([a-zA-Z0-9_]+@)?([a-zA-Z0-9._-]+):([0-9]+/)?([a-zA-Z0-9./._-]+)(?:\?||$)(.*)$`) // Transports is a set of known Git URL schemes. Transports = NewTransportSet( @@ -101,14 +101,22 @@ func ParseScp(rawurl string) (*url.URL, error) { userinfo = url.User(user) } rawquery := "" - if len(m) > 3 { - rawquery = m[4] + if len(m) > 4 { + rawquery = m[5] + } + host := m[2] + path := m[4] + if m[3] != "" { + port := strings.TrimRight(m[3], "/") + host = fmt.Sprintf("%s:%s", host, port) + // host.xyz:1234/path/ only supports absolute paths + path = "/" + path } return &url.URL{ Scheme: "ssh", User: userinfo, - Host: m[2], - Path: m[3], + Host: host, + Path: path, RawQuery: rawquery, }, nil } diff --git a/pkg/git/git-url/giturls/urls_test.go b/pkg/git/git-url/giturls/urls_test.go index 4e3f6cb33..0a8f2cfc7 100644 --- a/pkg/git/git-url/giturls/urls_test.go +++ b/pkg/git/git-url/giturls/urls_test.go @@ -67,6 +67,11 @@ func init() { "ssh", "", "host.xz", "path/to/repo-with_specials.git/", "ssh://host.xz/path/to/repo-with_specials.git/", "", ), + NewTest( + "host.xz:1234/path/to/repo-with_specials.git/", + "ssh", "", "host.xz:1234", "/path/to/repo-with_specials.git/", + "ssh://host.xz:1234/path/to/repo-with_specials.git/", "", + ), NewTest( "git://host.xz/path/to/repo.git/", "git", "", "host.xz", "/path/to/repo.git/", From 61ad1ccaa5532249e6204fa6f1114bb31585a65f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Mar 2023 08:55:11 +0100 Subject: [PATCH 1472/2916] chore(deps): Bump github.com/onsi/gomega from 1.27.1 to 1.27.2 (#341) Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.27.1 to 1.27.2. - [Release notes](https://github.com/onsi/gomega/releases) - [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md) - [Commits](https://github.com/onsi/gomega/compare/v1.27.1...v1.27.2) --- updated-dependencies: - dependency-name: github.com/onsi/gomega dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 16 +++++++++------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 199e0bb85..ee6420223 100644 --- a/go.mod +++ b/go.mod @@ -64,7 +64,7 @@ require ( github.com/go-logr/logr v1.2.3 github.com/hashicorp/go-multierror v1.1.1 github.com/huandu/xstrings v1.4.0 - github.com/onsi/gomega v1.27.1 + github.com/onsi/gomega v1.27.2 github.com/otiai10/copy v1.9.0 go.mozilla.org/sops/v3 v3.7.4-0.20220901181616-9124783930b1 sigs.k8s.io/controller-runtime v0.14.4 @@ -221,10 +221,10 @@ require ( go.opencensus.io v0.24.0 // indirect go.starlark.net v0.0.0-20221205180719-3fd0dac74452 // indirect go.uber.org/atomic v1.10.0 // indirect - golang.org/x/mod v0.7.0 // indirect + golang.org/x/mod v0.8.0 // indirect golang.org/x/oauth2 v0.3.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.5.0 // indirect + golang.org/x/tools v0.6.0 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect google.golang.org/api v0.107.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index 46b38de43..238912823 100644 --- a/go.sum +++ b/go.sum @@ -301,6 +301,7 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= github.com/gobuffalo/logger v1.0.6 h1:nnZNpxYo0zx+Aj9RfMPBm+x9zAU2OayFh/xrAWi34HU= github.com/gobuffalo/logger v1.0.6/go.mod h1:J31TBEHR1QLV2683OXTAItYIg8pv2JMHnF/quuAbMjs= @@ -394,6 +395,7 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= @@ -623,9 +625,9 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA github.com/ohler55/ojg v1.17.5 h1:SY6/cdhVzsLinNFIBRNSWhJgihvEWco5Y0TJe46XJ1Y= github.com/ohler55/ojg v1.17.5/go.mod h1:7Ghirupn8NC8hSSDpI0gcjorPxj+vSVIONDWfliHR1k= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo/v2 v2.8.1 h1:xFTEVwOFa1D/Ty24Ws1npBWkDYEV9BqZrsDxVrVkrrU= -github.com/onsi/gomega v1.27.1 h1:rfztXRbg6nv/5f+Raen9RcGoSecHIFgBBLQK3Wdj754= -github.com/onsi/gomega v1.27.1/go.mod h1:aHX5xOykVYzWOV4WqQy0sy8BQptgukenXpCXfadcIAw= +github.com/onsi/ginkgo/v2 v2.8.4 h1:gf5mIQ8cLFieruNLAdgijHF1PYfLphKm2dxxcUtcqK0= +github.com/onsi/gomega v1.27.2 h1:SKU0CXeKE/WVgIV1T61kSa3+IRE8Ekrv9rdXDwwTqnY= +github.com/onsi/gomega v1.27.2/go.mod h1:5mR3phAHpkAVIDkHEUBY6HGVsU+cpcEscrGPB4oPlZI= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034= @@ -868,8 +870,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= -golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1094,8 +1096,8 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.5.0 h1:+bSpV5HIeWkuvgaMfI3UmKRThoTA5ODJTUd8T17NO+4= -golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From fbfdd91ea2f4a00f4849ea588993458986533ce2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Mar 2023 08:55:18 +0100 Subject: [PATCH 1473/2916] chore(deps): Bump k8s.io/api from 0.26.1 to 0.26.2 (#342) Bumps [k8s.io/api](https://github.com/kubernetes/api) from 0.26.1 to 0.26.2. - [Release notes](https://github.com/kubernetes/api/releases) - [Commits](https://github.com/kubernetes/api/compare/v0.26.1...v0.26.2) --- updated-dependencies: - dependency-name: k8s.io/api dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index ee6420223..3dad10915 100644 --- a/go.mod +++ b/go.mod @@ -46,9 +46,9 @@ require ( gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 helm.sh/helm/v3 v3.11.1 - k8s.io/api v0.26.1 + k8s.io/api v0.26.2 k8s.io/apiextensions-apiserver v0.26.1 - k8s.io/apimachinery v0.26.1 + k8s.io/apimachinery v0.26.2 k8s.io/client-go v0.26.1 k8s.io/klog/v2 v2.90.0 sigs.k8s.io/kustomize/kyaml v0.13.9 diff --git a/go.sum b/go.sum index 238912823..67a41d297 100644 --- a/go.sum +++ b/go.sum @@ -1262,12 +1262,12 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.26.1 h1:f+SWYiPd/GsiWwVRz+NbFyCgvv75Pk9NK6dlkZgpCRQ= -k8s.io/api v0.26.1/go.mod h1:xd/GBNgR0f707+ATNyPmQ1oyKSgndzXij81FzWGsejg= +k8s.io/api v0.26.2 h1:dM3cinp3PGB6asOySalOZxEG4CZ0IAdJsrYZXE/ovGQ= +k8s.io/api v0.26.2/go.mod h1:1kjMQsFE+QHPfskEcVNgL3+Hp88B80uj0QtSOlj8itU= k8s.io/apiextensions-apiserver v0.26.1 h1:cB8h1SRk6e/+i3NOrQgSFij1B2S0Y0wDoNl66bn8RMI= k8s.io/apiextensions-apiserver v0.26.1/go.mod h1:AptjOSXDGuE0JICx/Em15PaoO7buLwTs0dGleIHixSM= -k8s.io/apimachinery v0.26.1 h1:8EZ/eGJL+hY/MYCNwhmDzVqq2lPl3N3Bo8rvweJwXUQ= -k8s.io/apimachinery v0.26.1/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74= +k8s.io/apimachinery v0.26.2 h1:da1u3D5wfR5u2RpLhE/ZtZS2P7QvDgLZTi9wrNZl/tQ= +k8s.io/apimachinery v0.26.2/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I= k8s.io/apiserver v0.26.1 h1:6vmnAqCDO194SVCPU3MU8NcDgSqsUA62tBUSWrFXhsc= k8s.io/apiserver v0.26.1/go.mod h1:wr75z634Cv+sifswE9HlAo5FQ7UoUauIICRlOE+5dCg= k8s.io/cli-runtime v0.26.0 h1:aQHa1SyUhpqxAw1fY21x2z2OS5RLtMJOCj7tN4oq8mw= From 731f36fa22fbc80bf565b3c6b32d35bbd1b12f86 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Mar 2023 08:55:28 +0100 Subject: [PATCH 1474/2916] chore(deps): Bump k8s.io/apimachinery from 0.26.1 to 0.26.2 (#344) Bumps [k8s.io/apimachinery](https://github.com/kubernetes/apimachinery) from 0.26.1 to 0.26.2. - [Release notes](https://github.com/kubernetes/apimachinery/releases) - [Commits](https://github.com/kubernetes/apimachinery/compare/v0.26.1...v0.26.2) --- updated-dependencies: - dependency-name: k8s.io/apimachinery dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> From cf467677c7ee999ca9f91190e42953480cf86077 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Mar 2023 09:10:08 +0100 Subject: [PATCH 1475/2916] chore(deps): Bump k8s.io/client-go from 0.26.1 to 0.26.2 (#343) Bumps [k8s.io/client-go](https://github.com/kubernetes/client-go) from 0.26.1 to 0.26.2. - [Release notes](https://github.com/kubernetes/client-go/releases) - [Changelog](https://github.com/kubernetes/client-go/blob/master/CHANGELOG.md) - [Commits](https://github.com/kubernetes/client-go/compare/v0.26.1...v0.26.2) --- updated-dependencies: - dependency-name: k8s.io/client-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 3dad10915..bf1c2e2b5 100644 --- a/go.mod +++ b/go.mod @@ -49,7 +49,7 @@ require ( k8s.io/api v0.26.2 k8s.io/apiextensions-apiserver v0.26.1 k8s.io/apimachinery v0.26.2 - k8s.io/client-go v0.26.1 + k8s.io/client-go v0.26.2 k8s.io/klog/v2 v2.90.0 sigs.k8s.io/kustomize/kyaml v0.13.9 sigs.k8s.io/structured-merge-diff/v4 v4.2.3 diff --git a/go.sum b/go.sum index 67a41d297..e06dde8c9 100644 --- a/go.sum +++ b/go.sum @@ -1272,8 +1272,8 @@ k8s.io/apiserver v0.26.1 h1:6vmnAqCDO194SVCPU3MU8NcDgSqsUA62tBUSWrFXhsc= k8s.io/apiserver v0.26.1/go.mod h1:wr75z634Cv+sifswE9HlAo5FQ7UoUauIICRlOE+5dCg= k8s.io/cli-runtime v0.26.0 h1:aQHa1SyUhpqxAw1fY21x2z2OS5RLtMJOCj7tN4oq8mw= k8s.io/cli-runtime v0.26.0/go.mod h1:o+4KmwHzO/UK0wepE1qpRk6l3o60/txUZ1fEXWGIKTY= -k8s.io/client-go v0.26.1 h1:87CXzYJnAMGaa/IDDfRdhTzxk/wzGZ+/HUQpqgVSZXU= -k8s.io/client-go v0.26.1/go.mod h1:IWNSglg+rQ3OcvDkhY6+QLeasV4OYHDjdqeWkDQZwGE= +k8s.io/client-go v0.26.2 h1:s1WkVujHX3kTp4Zn4yGNFK+dlDXy1bAAkIl+cFAiuYI= +k8s.io/client-go v0.26.2/go.mod h1:u5EjOuSyBa09yqqyY7m3abZeovO/7D/WehVVlZ2qcqU= k8s.io/component-base v0.26.1 h1:4ahudpeQXHZL5kko+iDHqLj/FSGAEUnSVO0EBbgDd+4= k8s.io/component-base v0.26.1/go.mod h1:VHrLR0b58oC035w6YQiBSbtsf0ThuSwXP+p5dD/kAWU= k8s.io/klog/v2 v2.90.0 h1:VkTxIV/FjRXn1fgNNcKGM8cfmL1Z33ZjXRTVxKCoF5M= From c0119ecf2a8a2e289c0ec7d7ace94b925af08fea Mon Sep 17 00:00:00 2001 From: Mathias Gebbe Date: Tue, 7 Mar 2023 10:00:19 +0100 Subject: [PATCH 1476/2916] doc: link to target discriminator gives a 404 (#354) update link to `target discriminator` target page --- docs/reference/kluctl-project/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/kluctl-project/README.md b/docs/reference/kluctl-project/README.md index 7c786c498..e6c3186d2 100644 --- a/docs/reference/kluctl-project/README.md +++ b/docs/reference/kluctl-project/README.md @@ -49,7 +49,7 @@ args: Specifies a default discriminator template to be used for targets that don't have their own discriminator specified. -See [target discriminator](./targets/README.md#discriminator) for details. +See [target discriminator](./targets/#discriminator) for details. ### targets From bb32a90767ddb52bb686be83a372669cb32c9fc9 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 10 Mar 2023 09:14:02 +0100 Subject: [PATCH 1477/2916] fix: Trim spaces when extracting hook annotations (#359) --- pkg/deployment/utils/hooks_util.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/deployment/utils/hooks_util.go b/pkg/deployment/utils/hooks_util.go index 6ebcd928f..6dba39f65 100644 --- a/pkg/deployment/utils/hooks_util.go +++ b/pkg/deployment/utils/hooks_util.go @@ -149,6 +149,7 @@ func (u *HooksUtil) GetHook(o *uo.UnstructuredObject) *hook { return ret } for _, x := range strings.Split(*a, ",") { + x = strings.TrimSpace(x) if x != "" { ret[x] = true } From e46ca90da9c29a591bf2415c389e15a713bcc108 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 10 Mar 2023 12:07:06 +0100 Subject: [PATCH 1478/2916] fix: Upgrade go-jinja2 to get in fixes for to_yaml and get_var (#360) --- go.mod | 6 +++--- go.sum | 16 +++++++++++----- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index bf1c2e2b5..65e8f5caf 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/jinzhu/copier v0.3.5 github.com/kevinburke/ssh_config v1.2.0 github.com/kluctl/go-embed-python v0.0.0-3.10.9-20230206-2 - github.com/kluctl/go-jinja2 v0.0.0-20230206202229-6e5a9f576647 + github.com/kluctl/go-jinja2 v0.0.0-20230310104535-8136715a1e5a github.com/mattn/go-colorable v0.1.13 github.com/mattn/go-isatty v0.0.17 github.com/mattn/go-runewidth v0.0.14 @@ -60,7 +60,7 @@ require ( github.com/aws/aws-sdk-go-v2 v1.17.5 github.com/aws/aws-sdk-go-v2/config v1.18.15 github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.6 - github.com/go-git/go-git/v5 v5.5.2 + github.com/go-git/go-git/v5 v5.6.0 github.com/go-logr/logr v1.2.3 github.com/hashicorp/go-multierror v1.1.1 github.com/huandu/xstrings v1.4.0 @@ -193,7 +193,7 @@ require ( github.com/opencontainers/image-spec v1.1.0-rc2 // indirect github.com/pelletier/go-toml/v2 v2.0.6 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect - github.com/pjbgf/sha1cd v0.2.3 // indirect + github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.14.0 // indirect diff --git a/go.sum b/go.sum index e06dde8c9..b82cee1bb 100644 --- a/go.sum +++ b/go.sum @@ -267,8 +267,8 @@ github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8 github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg= github.com/go-git/go-git-fixtures/v4 v4.3.1 h1:y5z6dd3qi8Hl+stezc8p3JxDkoTRqMAlKnXHuzrfjTQ= github.com/go-git/go-git-fixtures/v4 v4.3.1/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo= -github.com/go-git/go-git/v5 v5.5.2 h1:v8lgZa5k9ylUw+OR/roJHTxR4QItsNFI5nKtAXFuynw= -github.com/go-git/go-git/v5 v5.5.2/go.mod h1:BE5hUJ5yaV2YMxhmaP4l6RBQ08kMxKSPD4BlxtH7OjI= +github.com/go-git/go-git/v5 v5.6.0 h1:JvBdYfcttd+0kdpuWO7KTu0FYgCf5W0t5VwkWGobaa4= +github.com/go-git/go-git/v5 v5.6.0/go.mod h1:6nmJ0tJ3N4noMV1Omv7rC5FG3/o8Cm51TB4CJp7mRmE= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -513,8 +513,8 @@ github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7y github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= github.com/kluctl/go-embed-python v0.0.0-3.10.9-20230206-2 h1:K96SwIr8MzBQ3kFFz2H/pA2y+EEk04vZ4fWj/YZghBU= github.com/kluctl/go-embed-python v0.0.0-3.10.9-20230206-2/go.mod h1:vzngsPshNKUtq0gxkYQKNJafrcH7Qy7Qt6yGNt7JmQI= -github.com/kluctl/go-jinja2 v0.0.0-20230206202229-6e5a9f576647 h1:H7qN4RcRsFX6OuKn2La0ueu0nSFE9uPI/AMcgF5fXi8= -github.com/kluctl/go-jinja2 v0.0.0-20230206202229-6e5a9f576647/go.mod h1:oGEuC0taLwg20ORdSN7cdrN4rook79mJJ01QZIU858U= +github.com/kluctl/go-jinja2 v0.0.0-20230310104535-8136715a1e5a h1:48Mz5ZZmv64Pg/cKR4nIo3Ry49Hqs5YggLPGju0e8PU= +github.com/kluctl/go-jinja2 v0.0.0-20230310104535-8136715a1e5a/go.mod h1:qMY3lmIUPcCfjj5fZm39j+h7FilaqaQFYAze19xG0Dw= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kortschak/utter v1.0.1/go.mod h1:vSmSjbyrlKjjsL71193LmzBOKgwePk9DH6uFaWHIInc= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= @@ -599,6 +599,7 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mmcloughlin/avo v0.5.0/go.mod h1:ChHFdoV7ql95Wi7vuq2YT1bwCJqiWdZrQ1im3VujLYM= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= @@ -648,8 +649,9 @@ github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= -github.com/pjbgf/sha1cd v0.2.3 h1:uKQP/7QOzNtKYH7UTohZLcjF5/55EnTw0jO/Ru4jZwI= github.com/pjbgf/sha1cd v0.2.3/go.mod h1:HOK9QrgzdHpbc2Kzip0Q1yi3M2MFGPADtR6HjG65m5M= +github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= +github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= @@ -813,6 +815,7 @@ go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9i go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +golang.org/x/arch v0.1.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -870,6 +873,7 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1096,6 +1100,7 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1287,6 +1292,7 @@ k8s.io/utils v0.0.0-20221128185143-99ec85e7a448/go.mod h1:OLgZIPagt7ERELqWJFomSt oras.land/oras-go v1.2.2 h1:0E9tOHUfrNH7TCDk5KU0jVBEzCqbfdyuVfGmJ7ZeRPE= oras.land/oras-go v1.2.2/go.mod h1:Apa81sKoZPpP7CDciE006tSZ0x3Q3+dOoBcMZ/aNxvw= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/controller-runtime v0.14.4 h1:Kd/Qgx5pd2XUL08eOV2vwIq3L9GhIbJ5Nxengbd4/0M= From 55858276cc952d6d28ff3a8535e2d3e852275780 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Mar 2023 22:53:21 +0100 Subject: [PATCH 1479/2916] chore(deps): Bump github.com/fluxcd/pkg/kustomize from 0.13.1 to 0.13.2 (#355) Bumps [github.com/fluxcd/pkg/kustomize](https://github.com/fluxcd/pkg) from 0.13.1 to 0.13.2. - [Release notes](https://github.com/fluxcd/pkg/releases) - [Commits](https://github.com/fluxcd/pkg/compare/runtime/v0.13.1...runtime/v0.13.2) --- updated-dependencies: - dependency-name: github.com/fluxcd/pkg/kustomize dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 65e8f5caf..de2232db8 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/docker/distribution v2.8.1+incompatible // See https://github.com/fluxcd/pkg/issues/397, especially in regard to skeema/knownhosts breaking compatibility github.com/fluxcd/go-git/v5 v5.0.0-20221206140629-ec778c2c37df - github.com/fluxcd/pkg/kustomize v0.13.1 + github.com/fluxcd/pkg/kustomize v0.13.2 github.com/go-playground/validator/v10 v10.11.2 github.com/gobwas/glob v0.2.3 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 @@ -47,7 +47,7 @@ require ( gopkg.in/yaml.v3 v3.0.1 helm.sh/helm/v3 v3.11.1 k8s.io/api v0.26.2 - k8s.io/apiextensions-apiserver v0.26.1 + k8s.io/apiextensions-apiserver v0.26.2 k8s.io/apimachinery v0.26.2 k8s.io/client-go v0.26.2 k8s.io/klog/v2 v2.90.0 @@ -67,7 +67,7 @@ require ( github.com/onsi/gomega v1.27.2 github.com/otiai10/copy v1.9.0 go.mozilla.org/sops/v3 v3.7.4-0.20220901181616-9124783930b1 - sigs.k8s.io/controller-runtime v0.14.4 + sigs.k8s.io/controller-runtime v0.14.5 sigs.k8s.io/kustomize/api v0.12.1 sigs.k8s.io/yaml v1.3.0 ) @@ -236,9 +236,9 @@ require ( gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gotest.tools/v3 v3.2.0 // indirect - k8s.io/apiserver v0.26.1 // indirect + k8s.io/apiserver v0.26.2 // indirect k8s.io/cli-runtime v0.26.0 // indirect - k8s.io/component-base v0.26.1 // indirect + k8s.io/component-base v0.26.2 // indirect k8s.io/kube-openapi v0.0.0-20230109183929-3758b55a6596 // indirect k8s.io/kubectl v0.26.0 // indirect k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 // indirect diff --git a/go.sum b/go.sum index b82cee1bb..cebd71946 100644 --- a/go.sum +++ b/go.sum @@ -248,8 +248,8 @@ github.com/fluxcd/go-git/v5 v5.0.0-20221206140629-ec778c2c37df h1:2BHXJp1PwX7D47 github.com/fluxcd/go-git/v5 v5.0.0-20221206140629-ec778c2c37df/go.mod h1:raWgfUV7lDQVXp4QXUaeNNJkRVKz97UQuF+0kdY7Vmo= github.com/fluxcd/pkg/apis/kustomize v0.8.0 h1:A6aLolxPV2Sll44SOHiX96lbXXmRZmS5BoEerkRHrfM= github.com/fluxcd/pkg/apis/kustomize v0.8.0/go.mod h1:9DPEVSfVIkiC2H3Dk6Ght4YJkswhYIaufXla4tB5Y84= -github.com/fluxcd/pkg/kustomize v0.13.1 h1:xfDghn/kRaa5vYN64dLTAL1b1B1tDwcXlnOAqmz5W28= -github.com/fluxcd/pkg/kustomize v0.13.1/go.mod h1:W+Nm9P8yUhTb8n3hpvceUnCAjl6DFsU0k5yI+HT2NE8= +github.com/fluxcd/pkg/kustomize v0.13.2 h1:isA9yi+m7sSIxdTrFR1U7+LyS2BraG07ZkKLHw3bnGo= +github.com/fluxcd/pkg/kustomize v0.13.2/go.mod h1:1H9qednPxL/JvZE5at/f6wVHTH4WmxJYqfgVOZJ3uAk= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= @@ -1269,18 +1269,18 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.26.2 h1:dM3cinp3PGB6asOySalOZxEG4CZ0IAdJsrYZXE/ovGQ= k8s.io/api v0.26.2/go.mod h1:1kjMQsFE+QHPfskEcVNgL3+Hp88B80uj0QtSOlj8itU= -k8s.io/apiextensions-apiserver v0.26.1 h1:cB8h1SRk6e/+i3NOrQgSFij1B2S0Y0wDoNl66bn8RMI= -k8s.io/apiextensions-apiserver v0.26.1/go.mod h1:AptjOSXDGuE0JICx/Em15PaoO7buLwTs0dGleIHixSM= +k8s.io/apiextensions-apiserver v0.26.2 h1:/yTG2B9jGY2Q70iGskMf41qTLhL9XeNN2KhI0uDgwko= +k8s.io/apiextensions-apiserver v0.26.2/go.mod h1:Y7UPgch8nph8mGCuVk0SK83LnS8Esf3n6fUBgew8SH8= k8s.io/apimachinery v0.26.2 h1:da1u3D5wfR5u2RpLhE/ZtZS2P7QvDgLZTi9wrNZl/tQ= k8s.io/apimachinery v0.26.2/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I= -k8s.io/apiserver v0.26.1 h1:6vmnAqCDO194SVCPU3MU8NcDgSqsUA62tBUSWrFXhsc= -k8s.io/apiserver v0.26.1/go.mod h1:wr75z634Cv+sifswE9HlAo5FQ7UoUauIICRlOE+5dCg= +k8s.io/apiserver v0.26.2 h1:Pk8lmX4G14hYqJd1poHGC08G03nIHVqdJMR0SD3IH3o= +k8s.io/apiserver v0.26.2/go.mod h1:GHcozwXgXsPuOJ28EnQ/jXEM9QeG6HT22YxSNmpYNh8= k8s.io/cli-runtime v0.26.0 h1:aQHa1SyUhpqxAw1fY21x2z2OS5RLtMJOCj7tN4oq8mw= k8s.io/cli-runtime v0.26.0/go.mod h1:o+4KmwHzO/UK0wepE1qpRk6l3o60/txUZ1fEXWGIKTY= k8s.io/client-go v0.26.2 h1:s1WkVujHX3kTp4Zn4yGNFK+dlDXy1bAAkIl+cFAiuYI= k8s.io/client-go v0.26.2/go.mod h1:u5EjOuSyBa09yqqyY7m3abZeovO/7D/WehVVlZ2qcqU= -k8s.io/component-base v0.26.1 h1:4ahudpeQXHZL5kko+iDHqLj/FSGAEUnSVO0EBbgDd+4= -k8s.io/component-base v0.26.1/go.mod h1:VHrLR0b58oC035w6YQiBSbtsf0ThuSwXP+p5dD/kAWU= +k8s.io/component-base v0.26.2 h1:IfWgCGUDzrD6wLLgXEstJKYZKAFS2kO+rBRi0p3LqcI= +k8s.io/component-base v0.26.2/go.mod h1:DxbuIe9M3IZPRxPIzhch2m1eT7uFrSBJUBuVCQEBivs= k8s.io/klog/v2 v2.90.0 h1:VkTxIV/FjRXn1fgNNcKGM8cfmL1Z33ZjXRTVxKCoF5M= k8s.io/klog/v2 v2.90.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20230109183929-3758b55a6596 h1:8cNCQs+WqqnSpZ7y0LMQPKD+RZUHU17VqLPMW3qxnxc= @@ -1295,8 +1295,8 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8 rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/controller-runtime v0.14.4 h1:Kd/Qgx5pd2XUL08eOV2vwIq3L9GhIbJ5Nxengbd4/0M= -sigs.k8s.io/controller-runtime v0.14.4/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0= +sigs.k8s.io/controller-runtime v0.14.5 h1:6xaWFqzT5KuAQ9ufgUaj1G/+C4Y1GRkhrxl+BJ9i+5s= +sigs.k8s.io/controller-runtime v0.14.5/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/kustomize/api v0.12.1 h1:7YM7gW3kYBwtKvoY216ZzY+8hM+lV53LUayghNRJ0vM= From ee89808f5be927d2be573031fac8a1645504893d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Mar 2023 22:53:38 +0100 Subject: [PATCH 1480/2916] chore(deps): Bump k8s.io/apiextensions-apiserver from 0.26.1 to 0.26.2 (#356) Bumps [k8s.io/apiextensions-apiserver](https://github.com/kubernetes/apiextensions-apiserver) from 0.26.1 to 0.26.2. - [Release notes](https://github.com/kubernetes/apiextensions-apiserver/releases) - [Commits](https://github.com/kubernetes/apiextensions-apiserver/compare/v0.26.1...v0.26.2) --- updated-dependencies: - dependency-name: k8s.io/apiextensions-apiserver dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> From 67fdb4bd514bbab0e67a1262134a89ee96997394 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Mar 2023 22:53:54 +0100 Subject: [PATCH 1481/2916] chore(deps): Bump sigs.k8s.io/controller-runtime from 0.14.4 to 0.14.5 (#357) Bumps [sigs.k8s.io/controller-runtime](https://github.com/kubernetes-sigs/controller-runtime) from 0.14.4 to 0.14.5. - [Release notes](https://github.com/kubernetes-sigs/controller-runtime/releases) - [Changelog](https://github.com/kubernetes-sigs/controller-runtime/blob/main/RELEASE.md) - [Commits](https://github.com/kubernetes-sigs/controller-runtime/compare/v0.14.4...v0.14.5) --- updated-dependencies: - dependency-name: sigs.k8s.io/controller-runtime dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> From b47e54319701d7571970a17308a6784cb3b49d07 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Mar 2023 22:54:32 +0100 Subject: [PATCH 1482/2916] chore(deps): Bump golang.org/x/sys from 0.5.0 to 0.6.0 (#358) Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.5.0 to 0.6.0. - [Release notes](https://github.com/golang/sys/releases) - [Commits](https://github.com/golang/sys/compare/v0.5.0...v0.6.0) --- updated-dependencies: - dependency-name: golang.org/x/sys dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index de2232db8..098fbc2bc 100644 --- a/go.mod +++ b/go.mod @@ -40,7 +40,7 @@ require ( golang.org/x/crypto v0.6.0 golang.org/x/net v0.7.0 golang.org/x/sync v0.1.0 - golang.org/x/sys v0.5.0 + golang.org/x/sys v0.6.0 golang.org/x/term v0.5.0 golang.org/x/text v0.7.0 gopkg.in/yaml.v2 v2.4.0 diff --git a/go.sum b/go.sum index cebd71946..0d0c2b615 100644 --- a/go.sum +++ b/go.sum @@ -1019,8 +1019,8 @@ golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= From 9aeb4b82bbcfa60c1ea580bcd1eedfc6ddc28a3d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Mar 2023 09:41:57 +0100 Subject: [PATCH 1483/2916] chore(deps): Bump github.com/onsi/gomega from 1.27.2 to 1.27.3 (#361) Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.27.2 to 1.27.3. - [Release notes](https://github.com/onsi/gomega/releases) - [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md) - [Commits](https://github.com/onsi/gomega/compare/v1.27.2...v1.27.3) --- updated-dependencies: - dependency-name: github.com/onsi/gomega dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 098fbc2bc..9951c665a 100644 --- a/go.mod +++ b/go.mod @@ -64,7 +64,7 @@ require ( github.com/go-logr/logr v1.2.3 github.com/hashicorp/go-multierror v1.1.1 github.com/huandu/xstrings v1.4.0 - github.com/onsi/gomega v1.27.2 + github.com/onsi/gomega v1.27.3 github.com/otiai10/copy v1.9.0 go.mozilla.org/sops/v3 v3.7.4-0.20220901181616-9124783930b1 sigs.k8s.io/controller-runtime v0.14.5 @@ -140,7 +140,7 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/google/btree v1.1.2 // indirect github.com/google/gnostic v0.6.9 // indirect github.com/google/go-cmp v0.5.9 // indirect diff --git a/go.sum b/go.sum index 0d0c2b615..df06a4abd 100644 --- a/go.sum +++ b/go.sum @@ -352,8 +352,9 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -626,9 +627,9 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA github.com/ohler55/ojg v1.17.5 h1:SY6/cdhVzsLinNFIBRNSWhJgihvEWco5Y0TJe46XJ1Y= github.com/ohler55/ojg v1.17.5/go.mod h1:7Ghirupn8NC8hSSDpI0gcjorPxj+vSVIONDWfliHR1k= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo/v2 v2.8.4 h1:gf5mIQ8cLFieruNLAdgijHF1PYfLphKm2dxxcUtcqK0= -github.com/onsi/gomega v1.27.2 h1:SKU0CXeKE/WVgIV1T61kSa3+IRE8Ekrv9rdXDwwTqnY= -github.com/onsi/gomega v1.27.2/go.mod h1:5mR3phAHpkAVIDkHEUBY6HGVsU+cpcEscrGPB4oPlZI= +github.com/onsi/ginkgo/v2 v2.9.0 h1:Tugw2BKlNHTMfG+CheOITkYvk4LAh6MFOvikhGVnhE8= +github.com/onsi/gomega v1.27.3 h1:5VwIwnBY3vbBDOJrNtA4rVdiTZCsq9B5F12pvy1Drmk= +github.com/onsi/gomega v1.27.3/go.mod h1:5vG284IBtfDAmDyrK+eGyZmUgUlmi+Wngqo557cZ6Gw= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034= From c3d59bcea4b4dbae1292dc6957e65d1906c00167 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Mar 2023 09:42:58 +0100 Subject: [PATCH 1484/2916] chore(deps): Bump golang.org/x/crypto from 0.6.0 to 0.7.0 (#362) Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.6.0 to 0.7.0. - [Release notes](https://github.com/golang/crypto/releases) - [Commits](https://github.com/golang/crypto/compare/v0.6.0...v0.7.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 9951c665a..a91a277e2 100644 --- a/go.mod +++ b/go.mod @@ -37,12 +37,12 @@ require ( github.com/spf13/viper v1.15.0 github.com/stretchr/testify v1.8.2 github.com/xanzy/ssh-agent v0.3.3 - golang.org/x/crypto v0.6.0 - golang.org/x/net v0.7.0 + golang.org/x/crypto v0.7.0 + golang.org/x/net v0.8.0 golang.org/x/sync v0.1.0 golang.org/x/sys v0.6.0 - golang.org/x/term v0.5.0 - golang.org/x/text v0.7.0 + golang.org/x/term v0.6.0 + golang.org/x/text v0.8.0 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 helm.sh/helm/v3 v3.11.1 diff --git a/go.sum b/go.sum index df06a4abd..4c9aef3c8 100644 --- a/go.sum +++ b/go.sum @@ -836,8 +836,8 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -922,8 +922,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1028,8 +1028,8 @@ golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1040,8 +1040,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From faeff0802b242285e34d20e922fcaafc847dbcf2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Mar 2023 11:15:32 +0100 Subject: [PATCH 1485/2916] chore(deps): Bump helm.sh/helm/v3 from 3.11.1 to 3.11.2 (#364) Bumps [helm.sh/helm/v3](https://github.com/helm/helm) from 3.11.1 to 3.11.2. - [Release notes](https://github.com/helm/helm/releases) - [Commits](https://github.com/helm/helm/compare/v3.11.1...v3.11.2) --- updated-dependencies: - dependency-name: helm.sh/helm/v3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 +-- go.sum | 94 ++++++++++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 81 insertions(+), 17 deletions(-) diff --git a/go.mod b/go.mod index a91a277e2..db9644015 100644 --- a/go.mod +++ b/go.mod @@ -45,7 +45,7 @@ require ( golang.org/x/text v0.8.0 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 - helm.sh/helm/v3 v3.11.1 + helm.sh/helm/v3 v3.11.2 k8s.io/api v0.26.2 k8s.io/apiextensions-apiserver v0.26.2 k8s.io/apimachinery v0.26.2 @@ -201,7 +201,7 @@ require ( github.com/prometheus/common v0.39.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect github.com/rivo/uniseg v0.4.3 // indirect - github.com/rubenv/sql-migrate v1.2.0 // indirect + github.com/rubenv/sql-migrate v1.3.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect github.com/sergi/go-diff v1.2.0 // indirect diff --git a/go.sum b/go.sum index 4c9aef3c8..6df32aa4c 100644 --- a/go.sum +++ b/go.sum @@ -75,13 +75,12 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= -github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= -github.com/Masterminds/sprig/v3 v3.2.0/go.mod h1:tWhwTbUTndesPNeF0C900vKoq283u6zp4APT9vaF3SI= +github.com/Masterminds/sprig/v3 v3.2.1/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= github.com/Masterminds/squirrel v1.5.3 h1:YPpoceAcxuzIljlr5iWpNKaql7hLeG1KLSrhvdHpkZc= @@ -95,6 +94,7 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 h1:ra2OtmuW0AE5csawV4YXMNGNQQXvLRps3z2Z59OPO+I= github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= +github.com/a8m/expect v1.0.0/go.mod h1:4IwSCMumY49ScypDnjNbYEjgVeqy1/U2cEs3Lat96eA= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= @@ -105,8 +105,10 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFI github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= @@ -184,8 +186,13 @@ github.com/containerd/containerd v1.6.15 h1:4wWexxzLNHNE46aIETc6ge4TofO550v+BlLo github.com/containerd/containerd v1.6.15/go.mod h1:U2NnBPIhzJDm59xF7xB2MMHnKtggpZ+phKg8o2TKj2c= github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= github.com/containerd/stargz-snapshotter/estargz v0.12.1 h1:+7nYmHJb0tEkcRaAW+MHqoKaJYZmkikupxCqVtmPuY0= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= @@ -198,6 +205,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 h1:aBfCb7iqHmDEIp6fBvC/hQUddQfg+3qdYjwzaiP9Hnc= github.com/dnaeon/go-vcr v1.1.0 h1:ReYa/UBrRyQdant9B4fNHGoCNKw6qh6P0fsdGmZpR7c= github.com/docker/cli v20.10.21+incompatible h1:qVkgyYUnOLQ98LtXBrwd/duVqPT2X4SHndOuGsfwyhU= @@ -240,6 +249,7 @@ github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2Vvl github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4= github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= @@ -251,6 +261,8 @@ github.com/fluxcd/pkg/apis/kustomize v0.8.0/go.mod h1:9DPEVSfVIkiC2H3Dk6Ght4YJks github.com/fluxcd/pkg/kustomize v0.13.2 h1:isA9yi+m7sSIxdTrFR1U7+LyS2BraG07ZkKLHw3bnGo= github.com/fluxcd/pkg/kustomize v0.13.2/go.mod h1:1H9qednPxL/JvZE5at/f6wVHTH4WmxJYqfgVOZJ3uAk= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= @@ -272,7 +284,7 @@ github.com/go-git/go-git/v5 v5.6.0/go.mod h1:6nmJ0tJ3N4noMV1Omv7rC5FG3/o8Cm51TB4 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gorp/gorp/v3 v3.0.2/go.mod h1:BJ3q1ejpV8cVALtcXvXaXyTOlMmJhWDxTmncaR6rwBY= +github.com/go-gorp/gorp/v3 v3.0.5/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -296,8 +308,6 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU= github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s= -github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -314,6 +324,7 @@ github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJA github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godror/godror v0.24.2/go.mod h1:wZv/9vPiUib6tkoDl+AZ/QLf5YZgMravZ7jxH2eQWAE= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= @@ -323,6 +334,7 @@ github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOW github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -373,6 +385,7 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -415,6 +428,7 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORR github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= @@ -422,6 +436,9 @@ github.com/goware/prefixer v0.0.0-20160118172347-395022866408 h1:Y9iQJfEqnN3/Nce github.com/goware/prefixer v0.0.0-20160118172347-395022866408/go.mod h1:PE1ycukgRPJ7bJ9a1fdfQ9j8i/cEcRAoLZzbxYpNB/s= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= @@ -493,6 +510,7 @@ github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHW github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -508,6 +526,7 @@ github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= @@ -523,7 +542,9 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -537,11 +558,11 @@ github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6Fm github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= @@ -556,11 +577,14 @@ github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kN github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -570,16 +594,15 @@ github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mN github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= +github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/cli v1.1.4/go.mod h1:vTLESy5mRhKOs9KDp0/RATawxP1UqBmdrpVRMnpcvKQ= +github.com/mitchellh/cli v1.1.5/go.mod h1:v8+iFts2sPIKUV1ltktPXMCC8fumSKFItNcD2cLtRR4= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= @@ -623,9 +646,12 @@ github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7P github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nelsam/hel/v2 v2.3.2/go.mod h1:1ZTGfU2PFTOd5mx22i5O0Lc2GY933lQ2wb/ggy+rL3w= +github.com/nelsam/hel/v2 v2.3.3/go.mod h1:1ZTGfU2PFTOd5mx22i5O0Lc2GY933lQ2wb/ggy+rL3w= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/ohler55/ojg v1.17.5 h1:SY6/cdhVzsLinNFIBRNSWhJgihvEWco5Y0TJe46XJ1Y= github.com/ohler55/ojg v1.17.5/go.mod h1:7Ghirupn8NC8hSSDpI0gcjorPxj+vSVIONDWfliHR1k= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo/v2 v2.9.0 h1:Tugw2BKlNHTMfG+CheOITkYvk4LAh6MFOvikhGVnhE8= github.com/onsi/gomega v1.27.3 h1:5VwIwnBY3vbBDOJrNtA4rVdiTZCsq9B5F12pvy1Drmk= @@ -644,6 +670,7 @@ github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT9 github.com/otiai10/mint v1.4.0 h1:umwcf7gbpEwf7WFzqmWwSv0CzbeMsae2u9ZvpP8j2q4= github.com/otiai10/mint v1.4.0/go.mod h1:gifjb2MYOoULtKLqUAEILUG/9KONW6f7YsJ6vQLTlFI= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= @@ -666,9 +693,12 @@ github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qR github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/poy/onpar v0.0.0-20190519213022-ee068f8ea4d1/go.mod h1:nSbFQvMj97ZyhFRSJYtut+msi4sOY6zJDGCdSc+/rZU= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/poy/onpar v0.0.0-20200406201722-06f95a1c68e8/go.mod h1:nSbFQvMj97ZyhFRSJYtut+msi4sOY6zJDGCdSc+/rZU= github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= +github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= @@ -678,27 +708,33 @@ github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1: github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.39.0 h1:oOyhkDq05hPZKItWVBkJ6g6AtGxi+fy7F4JvUV8uhsI= github.com/prometheus/common v0.39.0/go.mod h1:6XBZ7lYdLCbkAVhwRsWTZn+IN5AB9F/NXd5w0BbEX0Y= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/r3labs/diff/v2 v2.15.1 h1:EOrVqPUzi+njlumoqJwiS/TgGgmZo83619FNDB9xQUg= github.com/r3labs/diff/v2 v2.15.1/go.mod h1:I8noH9Fc2fjSaMxqF3G2lhDdC0b+JXCfyx85tWFM9kc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw= github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rubenv/sql-migrate v1.2.0 h1:fOXMPLMd41sK7Tg75SXDec15k3zg5WNV6SjuDRiNfcU= -github.com/rubenv/sql-migrate v1.2.0/go.mod h1:Z5uVnq7vrIrPmHbVFfR4YLHRZquxeHpckCnRq0P/K9Y= +github.com/rubenv/sql-migrate v1.3.1 h1:Vx+n4Du8X8VTYuXbhNxdEUoh6wiJERA0GlWocR5FrbA= +github.com/rubenv/sql-migrate v1.3.1/go.mod h1:YzG/Vh82CwyhTFXy+Mf5ahAiiEOpAlHurg+23VEzcsk= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -723,21 +759,27 @@ github.com/skeema/knownhosts v1.1.0 h1:Wvr9V0MxhjRbl3f9nMnKnFfiWTJmtECJ9Njkea3ys github.com/skeema/knownhosts v1.1.0/go.mod h1:sKFq3RD6/TKZkSWn8boUbDC7Qkgcv+8XXijpFO6roag= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU= github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA= @@ -762,6 +804,8 @@ github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/urfave/cli v1.22.7 h1:aXiFAgRugfJ27UFDsGJ9DB2FvTC73hlVXFSqq5bo9eU= github.com/urfave/cli v1.22.7/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/vbatts/tar-split v0.11.2 h1:Via6XqJr0hceW4wff3QRzD5gAk/tatMw/4ZA7cTlIME= @@ -777,8 +821,10 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v1.1.0 h1:G/1DjNkPpfZCFt9CSh6b5/nY4VimlbHF3Rh4obvtzDk= github.com/xlab/treeprint v1.1.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -789,7 +835,7 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI= github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= -github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= @@ -809,11 +855,14 @@ go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.starlark.net v0.0.0-20221205180719-3fd0dac74452 h1:JZtNuL6LPB+scU5yaQ6hqRlJFRiddZm2FwRt2AQqtHA= go.starlark.net v0.0.0-20221205180719-3fd0dac74452/go.mod h1:kIVgS18CjmEC3PqMd5kaJSGEifyV/CeB9x506ZJ1Vbk= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= golang.org/x/arch v0.1.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= @@ -836,6 +885,7 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -882,12 +932,14 @@ golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -922,6 +974,7 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -956,8 +1009,10 @@ golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1017,9 +1072,11 @@ golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1028,6 +1085,7 @@ golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1040,6 +1098,7 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1047,6 +1106,7 @@ golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -1081,6 +1141,7 @@ golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200313205530-4303120df7d8/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -1190,6 +1251,7 @@ google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef h1:uQ2vjV/sHTsWSqd google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= @@ -1239,10 +1301,12 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -1259,8 +1323,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.2.0 h1:I0DwBVMGAx26dttAj1BtJLAkVGncrkkUXfJLC4Flt/I= gotest.tools/v3 v3.2.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= -helm.sh/helm/v3 v3.11.1 h1:cmL9fFohOoNQf+wnp2Wa0OhNFH0KFnSzEkVxi3fcc3I= -helm.sh/helm/v3 v3.11.1/go.mod h1:z/Bu/BylToGno/6dtNGuSmjRqxKq5gaH+FU0BPO+AQ8= +helm.sh/helm/v3 v3.11.2 h1:P3cLaFxfoxaGLGJVnoPrhf1j86LC5EDINSpYSpMUkkA= +helm.sh/helm/v3 v3.11.2/go.mod h1:Hw+09mfpDiRRKAgAIZlFkPSeOkvv7Acl5McBvQyNPVw= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From f8d707b3de53ac284eb7d29104743a6dcce6d739 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Mar 2023 08:43:32 +0100 Subject: [PATCH 1486/2916] chore(deps): Bump github.com/onsi/gomega from 1.27.3 to 1.27.4 (#370) Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.27.3 to 1.27.4. - [Release notes](https://github.com/onsi/gomega/releases) - [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md) - [Commits](https://github.com/onsi/gomega/compare/v1.27.3...v1.27.4) --- updated-dependencies: - dependency-name: github.com/onsi/gomega dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index db9644015..1d983cdc1 100644 --- a/go.mod +++ b/go.mod @@ -64,7 +64,7 @@ require ( github.com/go-logr/logr v1.2.3 github.com/hashicorp/go-multierror v1.1.1 github.com/huandu/xstrings v1.4.0 - github.com/onsi/gomega v1.27.3 + github.com/onsi/gomega v1.27.4 github.com/otiai10/copy v1.9.0 go.mozilla.org/sops/v3 v3.7.4-0.20220901181616-9124783930b1 sigs.k8s.io/controller-runtime v0.14.5 @@ -221,10 +221,10 @@ require ( go.opencensus.io v0.24.0 // indirect go.starlark.net v0.0.0-20221205180719-3fd0dac74452 // indirect go.uber.org/atomic v1.10.0 // indirect - golang.org/x/mod v0.8.0 // indirect + golang.org/x/mod v0.9.0 // indirect golang.org/x/oauth2 v0.3.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.6.0 // indirect + golang.org/x/tools v0.7.0 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect google.golang.org/api v0.107.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index 6df32aa4c..5013129ba 100644 --- a/go.sum +++ b/go.sum @@ -653,9 +653,9 @@ github.com/ohler55/ojg v1.17.5 h1:SY6/cdhVzsLinNFIBRNSWhJgihvEWco5Y0TJe46XJ1Y= github.com/ohler55/ojg v1.17.5/go.mod h1:7Ghirupn8NC8hSSDpI0gcjorPxj+vSVIONDWfliHR1k= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo/v2 v2.9.0 h1:Tugw2BKlNHTMfG+CheOITkYvk4LAh6MFOvikhGVnhE8= -github.com/onsi/gomega v1.27.3 h1:5VwIwnBY3vbBDOJrNtA4rVdiTZCsq9B5F12pvy1Drmk= -github.com/onsi/gomega v1.27.3/go.mod h1:5vG284IBtfDAmDyrK+eGyZmUgUlmi+Wngqo557cZ6Gw= +github.com/onsi/ginkgo/v2 v2.9.1 h1:zie5Ly042PD3bsCvsSOPvRnFwyo3rKe64TJlD6nu0mk= +github.com/onsi/gomega v1.27.4 h1:Z2AnStgsdSayCMDiCU42qIz+HLqEPcgiOCXjAU/w+8E= +github.com/onsi/gomega v1.27.4/go.mod h1:riYq/GJKh8hhoM01HN6Vmuy93AarCXCBGpvFDK3q3fQ= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034= @@ -925,8 +925,8 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= -golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1163,8 +1163,8 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= -golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From d80d25daeedd87a0993f5afc42dc5b96c4dd5066 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Mar 2023 08:43:46 +0100 Subject: [PATCH 1487/2916] chore(deps): Bump k8s.io/klog/v2 from 2.90.0 to 2.90.1 (#371) Bumps [k8s.io/klog/v2](https://github.com/kubernetes/klog) from 2.90.0 to 2.90.1. - [Release notes](https://github.com/kubernetes/klog/releases) - [Changelog](https://github.com/kubernetes/klog/blob/main/RELEASE.md) - [Commits](https://github.com/kubernetes/klog/compare/v2.90.0...v2.90.1) --- updated-dependencies: - dependency-name: k8s.io/klog/v2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 1d983cdc1..55cc0c6d8 100644 --- a/go.mod +++ b/go.mod @@ -50,7 +50,7 @@ require ( k8s.io/apiextensions-apiserver v0.26.2 k8s.io/apimachinery v0.26.2 k8s.io/client-go v0.26.2 - k8s.io/klog/v2 v2.90.0 + k8s.io/klog/v2 v2.90.1 sigs.k8s.io/kustomize/kyaml v0.13.9 sigs.k8s.io/structured-merge-diff/v4 v4.2.3 ) diff --git a/go.sum b/go.sum index 5013129ba..c5ee9e088 100644 --- a/go.sum +++ b/go.sum @@ -1346,8 +1346,8 @@ k8s.io/client-go v0.26.2 h1:s1WkVujHX3kTp4Zn4yGNFK+dlDXy1bAAkIl+cFAiuYI= k8s.io/client-go v0.26.2/go.mod h1:u5EjOuSyBa09yqqyY7m3abZeovO/7D/WehVVlZ2qcqU= k8s.io/component-base v0.26.2 h1:IfWgCGUDzrD6wLLgXEstJKYZKAFS2kO+rBRi0p3LqcI= k8s.io/component-base v0.26.2/go.mod h1:DxbuIe9M3IZPRxPIzhch2m1eT7uFrSBJUBuVCQEBivs= -k8s.io/klog/v2 v2.90.0 h1:VkTxIV/FjRXn1fgNNcKGM8cfmL1Z33ZjXRTVxKCoF5M= -k8s.io/klog/v2 v2.90.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw= +k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20230109183929-3758b55a6596 h1:8cNCQs+WqqnSpZ7y0LMQPKD+RZUHU17VqLPMW3qxnxc= k8s.io/kube-openapi v0.0.0-20230109183929-3758b55a6596/go.mod h1:/BYxry62FuDzmI+i9B+X2pqfySRmSOW2ARmj5Zbqhj0= k8s.io/kubectl v0.26.0 h1:xmrzoKR9CyNdzxBmXV7jW9Ln8WMrwRK6hGbbf69o4T0= From 6cc003b102a3067226aae523608e5725c18844d7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Mar 2023 08:44:07 +0100 Subject: [PATCH 1488/2916] chore(deps): Bump github.com/aws/aws-sdk-go-v2/config (#376) Bumps [github.com/aws/aws-sdk-go-v2/config](https://github.com/aws/aws-sdk-go-v2) from 1.18.15 to 1.18.18. - [Release notes](https://github.com/aws/aws-sdk-go-v2/releases) - [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go-v2/compare/config/v1.18.15...config/v1.18.18) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go-v2/config dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 22 +++++++++++----------- go.sum | 41 ++++++++++++++++++++++------------------- 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/go.mod b/go.mod index 55cc0c6d8..0496161c6 100644 --- a/go.mod +++ b/go.mod @@ -57,8 +57,8 @@ require ( require ( filippo.io/age v1.1.1 - github.com/aws/aws-sdk-go-v2 v1.17.5 - github.com/aws/aws-sdk-go-v2/config v1.18.15 + github.com/aws/aws-sdk-go-v2 v1.17.6 + github.com/aws/aws-sdk-go-v2/config v1.18.18 github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.6 github.com/go-git/go-git/v5 v5.6.0 github.com/go-logr/logr v1.2.3 @@ -93,16 +93,16 @@ require ( github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 // indirect github.com/acomagu/bufpipe v1.0.3 // indirect github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.13.15 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.23 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.29 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.23 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.3.30 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.23 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.13.17 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.0 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.30 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.24 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.3.31 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.24 // indirect github.com/aws/aws-sdk-go-v2/service/kms v1.17.5 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.12.4 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.4 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.18.5 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.12.5 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.5 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.18.6 // indirect github.com/aws/smithy-go v1.13.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver v3.5.1+incompatible // indirect diff --git a/go.sum b/go.sum index c5ee9e088..d11adf871 100644 --- a/go.sum +++ b/go.sum @@ -114,34 +114,37 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkY github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go-v2 v1.16.7/go.mod h1:6CpKuLXg2w7If3ABZCl/qZ6rEgwtjZTn4eAf4RcEyuw= -github.com/aws/aws-sdk-go-v2 v1.17.5 h1:TzCUW1Nq4H8Xscph5M/skINUitxM5UBAyvm2s7XBzL4= github.com/aws/aws-sdk-go-v2 v1.17.5/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= -github.com/aws/aws-sdk-go-v2/config v1.18.15 h1:509yMO0pJUGUugBP2H9FOFyV+7Mz7sRR+snfDN5W4NY= -github.com/aws/aws-sdk-go-v2/config v1.18.15/go.mod h1:vS0tddZqpE8cD9CyW0/kITHF5Bq2QasW9Y1DFHD//O0= -github.com/aws/aws-sdk-go-v2/credentials v1.13.15 h1:0rZQIi6deJFjOEgHI9HI2eZcLPPEGQPictX66oRFLL8= -github.com/aws/aws-sdk-go-v2/credentials v1.13.15/go.mod h1:vRMLMD3/rXU+o6j2MW5YefrGMBmdTvkLLGqFwMLBHQc= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.23 h1:Kbiv9PGnQfG/imNI4L/heyUXvzKmcWSBeDvkrQz5pFc= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.23/go.mod h1:mOtmAg65GT1HIL/HT/PynwPbS+UG0BgCZ6vhkPqnxWo= +github.com/aws/aws-sdk-go-v2 v1.17.6 h1:Y773UK7OBqhzi5VDXMi1zVGsoj+CVHs2eaC2bDsLwi0= +github.com/aws/aws-sdk-go-v2 v1.17.6/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= +github.com/aws/aws-sdk-go-v2/config v1.18.18 h1:/ePABXvXl3ESlzUGnkkvvNnRFw3Gh13dyqaq0Qo3JcU= +github.com/aws/aws-sdk-go-v2/config v1.18.18/go.mod h1:Lj3E7XcxJnxMa+AYo89YiL68s1cFJRGduChynYU67VA= +github.com/aws/aws-sdk-go-v2/credentials v1.13.17 h1:IubQO/RNeIVKF5Jy77w/LfUvmmCxTnk2TP1UZZIMiF4= +github.com/aws/aws-sdk-go-v2/credentials v1.13.17/go.mod h1:K9xeFo1g/YPMguMUD69YpwB4Nyi6W/5wn706xIInJFg= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.0 h1:/2Cb3SK3xVOQA7Xfr5nCWCo5H3UiNINtsVvVdk8sQqA= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.0/go.mod h1:neYVaeKr5eT7BzwULuG2YbLhzWZ22lpjKdCybR7AXrQ= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.14/go.mod h1:kdjrMwHwrC3+FsKhNcCMJ7tUVj/8uSD5CZXeQ4wV6fM= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.29 h1:9/aKwwus0TQxppPXFmf010DFrE+ssSbzroLVYINA+xE= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.29/go.mod h1:Dip3sIGv485+xerzVv24emnjX5Sg88utCL8fwGmCeWg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.30 h1:y+8n9AGDjikyXoMBTRaHHHSaFEB8267ykmvyPodJfys= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.30/go.mod h1:LUBAO3zNXQjoONBKn/kR1y0Q4cj/D02Ts0uHYjcCQLM= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.8/go.mod h1:ZIV8GYoC6WLBW5KGs+o4rsc65/ozd+eQ0L31XF5VDwk= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.23 h1:b/Vn141DBuLVgXbhRWIrl9g+ww7G+ScV5SzniWR13jQ= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.23/go.mod h1:mr6c4cHC+S/MMkrjtSlG4QA36kOznDep+0fga5L/fGQ= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.30 h1:IVx9L7YFhpPq0tTnGo8u8TpluFu7nAn9X3sUDMb11c0= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.30/go.mod h1:vsbq62AOBwQ1LJ/GWKFxX8beUEYeRp/Agitrxee2/qM= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.23 h1:QoOybhwRfciWUBbZ0gp9S7XaDnCuSTeK/fySB99V1ls= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.23/go.mod h1:9uPh+Hrz2Vn6oMnQYiUi/zbh3ovbnQk19YKINkQny44= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.24 h1:r+Kv+SEJquhAZXaJ7G4u44cIwXV3f8K+N482NNAzJZA= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.24/go.mod h1:gAuCezX/gob6BSMbItsSlMb6WZGV7K2+fWOvk8xBSto= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.31 h1:hf+Vhp5WtTdcSdE+yEcUz8L73sAzN0R+0jQv+Z51/mI= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.31/go.mod h1:5zUjguZfG5qjhG9/wqmuyHRyUftl2B5Cp6NNxNC6kRA= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.24 h1:c5qGfdbCHav6viBwiyDns3OXqhqAbGjfIB4uVu2ayhk= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.24/go.mod h1:HMA4FZG6fyib+NDo5bpIxX1EhYjrAOveZJY2YR0xrNE= github.com/aws/aws-sdk-go-v2/service/kms v1.17.5 h1:DkubF+BSEy0uX59+pYySzWFReN3fCcXobIO8L5Phh24= github.com/aws/aws-sdk-go-v2/service/kms v1.17.5/go.mod h1:ubAtMGRUMVv5kX8lpbeDguxZ64pR4kXTGApY4sCM0io= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.6 h1:VjvQw/1Qf/rhDSl+NNOeybSpdPRjBfH60//5vzveVsY= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.6/go.mod h1:CJcdJtrO6ulXfI8l2DotKWmJShhXHCEcd9Wibyx3kC0= -github.com/aws/aws-sdk-go-v2/service/sso v1.12.4 h1:qJdM48OOLl1FBSzI7ZrA1ZfLwOyCYqkXV5lko1hYDBw= -github.com/aws/aws-sdk-go-v2/service/sso v1.12.4/go.mod h1:jtLIhd+V+lft6ktxpItycqHqiVXrPIRjWIsFIlzMriw= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.4 h1:YRkWXQveFb0tFC0TLktmmhGsOcCgLwvq88MC2al47AA= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.4/go.mod h1:zVwRrfdSmbRZWkUkWjOItY7SOalnFnq/Yg2LVPqDjwc= -github.com/aws/aws-sdk-go-v2/service/sts v1.18.5 h1:L1600eLr0YvTT7gNh3Ni24yGI7NSHkq9Gp62vijPRCs= -github.com/aws/aws-sdk-go-v2/service/sts v1.18.5/go.mod h1:1mKZHLLpDMHTNSYPJ7qrcnCQdHCWsNQaT0xRvq2u80s= +github.com/aws/aws-sdk-go-v2/service/sso v1.12.5 h1:bdKIX6SVF3nc3xJFw6Nf0igzS6Ff/louGq8Z6VP/3Hs= +github.com/aws/aws-sdk-go-v2/service/sso v1.12.5/go.mod h1:vuWiaDB30M/QTC+lI3Wj6S/zb7tpUK2MSYgy3Guh2L0= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.5 h1:xLPZMyuZ4GuqRCIec/zWuIhRFPXh2UOJdLXBSi64ZWQ= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.5/go.mod h1:QjxpHmCwAg0ESGtPQnLIVp7SedTOBMYy+Slr3IfMKeI= +github.com/aws/aws-sdk-go-v2/service/sts v1.18.6 h1:rIFn5J3yDoeuKCE9sESXqM5POTAhOP1du3bv/qTL+tE= +github.com/aws/aws-sdk-go-v2/service/sts v1.18.6/go.mod h1:48WJ9l3dwP0GSHWGc5sFGGlCkuA82Mc2xnw+T6Q8aDw= github.com/aws/smithy-go v1.12.0/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8= github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= From 794a68238db95966ce9273398aed81a2e5775926 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Mar 2023 08:54:23 +0100 Subject: [PATCH 1489/2916] chore(deps): Bump actions/setup-go from 3 to 4 (#377) Bumps [actions/setup-go](https://github.com/actions/setup-go) from 3 to 4. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/setup-go dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/cross-compile.yaml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/tests.yml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cross-compile.yaml b/.github/workflows/cross-compile.yaml index 6b96e9f56..e9651b820 100644 --- a/.github/workflows/cross-compile.yaml +++ b/.github/workflows/cross-compile.yaml @@ -14,7 +14,7 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 0 - - uses: actions/setup-go@v3 + - uses: actions/setup-go@v4 with: go-version: '1.19' - uses: actions/cache@v3 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b18a49db0..5289b22a7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,7 +21,7 @@ jobs: - name: Fetch all tags run: git fetch --force --tags - name: Set up Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v4 with: go-version: 1.19 - name: Setup QEMU diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5e3e4cc83..e23e52c2b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,7 +16,7 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 0 - - uses: actions/setup-go@v3 + - uses: actions/setup-go@v4 with: go-version: '1.19' - uses: actions/cache@v3 @@ -56,7 +56,7 @@ jobs: steps: - name: Checkout uses: actions/checkout@v3 - - uses: actions/setup-go@v3 + - uses: actions/setup-go@v4 with: go-version: '1.19' - uses: actions/cache@v3 From fcbe7896975882d1d36ad17b0291e40416dba664 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Mar 2023 08:55:06 +0100 Subject: [PATCH 1490/2916] chore(deps): Bump github.com/imdario/mergo from 0.3.13 to 0.3.14 (#379) Bumps [github.com/imdario/mergo](https://github.com/imdario/mergo) from 0.3.13 to 0.3.14. - [Release notes](https://github.com/imdario/mergo/releases) - [Commits](https://github.com/imdario/mergo/compare/v0.3.13...v0.3.14) --- updated-dependencies: - dependency-name: github.com/imdario/mergo dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 0496161c6..52010cc19 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/google/go-containerregistry v0.13.0 github.com/hashicorp/vault/api v1.9.0 github.com/hexops/gotextdiff v1.0.3 - github.com/imdario/mergo v0.3.13 + github.com/imdario/mergo v0.3.14 github.com/jinzhu/copier v0.3.5 github.com/kevinburke/ssh_config v1.2.0 github.com/kluctl/go-embed-python v0.0.0-3.10.9-20230206-2 diff --git a/go.sum b/go.sum index d11adf871..f29c11df0 100644 --- a/go.sum +++ b/go.sum @@ -497,8 +497,9 @@ github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= +github.com/imdario/mergo v0.3.14 h1:fOqeC1+nCuuk6PKQdg9YmosXX7Y7mHX6R/0ZldI9iHo= +github.com/imdario/mergo v0.3.14/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= From eff52e78069417dba7ddf70813da7c9cd08faa53 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Mar 2023 08:59:11 +0100 Subject: [PATCH 1491/2916] chore(deps): Bump github.com/go-git/go-git/v5 from 5.6.0 to 5.6.1 (#380) Bumps [github.com/go-git/go-git/v5](https://github.com/go-git/go-git) from 5.6.0 to 5.6.1. - [Release notes](https://github.com/go-git/go-git/releases) - [Commits](https://github.com/go-git/go-git/compare/v5.6.0...v5.6.1) --- updated-dependencies: - dependency-name: github.com/go-git/go-git/v5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 17 ++++++++++++----- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 52010cc19..73986b291 100644 --- a/go.mod +++ b/go.mod @@ -60,7 +60,7 @@ require ( github.com/aws/aws-sdk-go-v2 v1.17.6 github.com/aws/aws-sdk-go-v2/config v1.18.18 github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.6 - github.com/go-git/go-git/v5 v5.6.0 + github.com/go-git/go-git/v5 v5.6.1 github.com/go-logr/logr v1.2.3 github.com/hashicorp/go-multierror v1.1.1 github.com/huandu/xstrings v1.4.0 @@ -90,8 +90,8 @@ require ( github.com/Masterminds/sprig/v3 v3.2.3 // indirect github.com/Masterminds/squirrel v1.5.3 // indirect github.com/Microsoft/go-winio v0.6.0 // indirect - github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 // indirect - github.com/acomagu/bufpipe v1.0.3 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect + github.com/acomagu/bufpipe v1.0.4 // indirect github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect github.com/aws/aws-sdk-go-v2/credentials v1.13.17 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.0 // indirect diff --git a/go.sum b/go.sum index f29c11df0..de7843866 100644 --- a/go.sum +++ b/go.sum @@ -91,14 +91,16 @@ github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2B github.com/Microsoft/hcsshim v0.9.6 h1:VwnDOgLeoi2du6dAznfmspNqTiwczvjv4K7NxuY9jsY= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 h1:ra2OtmuW0AE5csawV4YXMNGNQQXvLRps3z2Z59OPO+I= github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8= +github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA= +github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= github.com/a8m/expect v1.0.0/go.mod h1:4IwSCMumY49ScypDnjNbYEjgVeqy1/U2cEs3Lat96eA= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= -github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= +github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= +github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= @@ -277,13 +279,12 @@ github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3Bop github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= -github.com/go-git/go-billy/v5 v5.4.0/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg= github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4= github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg= github.com/go-git/go-git-fixtures/v4 v4.3.1 h1:y5z6dd3qi8Hl+stezc8p3JxDkoTRqMAlKnXHuzrfjTQ= github.com/go-git/go-git-fixtures/v4 v4.3.1/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo= -github.com/go-git/go-git/v5 v5.6.0 h1:JvBdYfcttd+0kdpuWO7KTu0FYgCf5W0t5VwkWGobaa4= -github.com/go-git/go-git/v5 v5.6.0/go.mod h1:6nmJ0tJ3N4noMV1Omv7rC5FG3/o8Cm51TB4CJp7mRmE= +github.com/go-git/go-git/v5 v5.6.1 h1:q4ZRqQl4pR/ZJHc1L5CFjGA1a10u76aV1iC+nh+bHsk= +github.com/go-git/go-git/v5 v5.6.1/go.mod h1:mvyoL6Unz0PiTQrGQfSfiLFhBH1c1e84ylC2MDs4ee8= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -890,6 +891,7 @@ golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -979,6 +981,8 @@ golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfS golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1081,6 +1085,7 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1090,6 +1095,7 @@ golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1103,6 +1109,7 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From b246b80498009865eaebf21d8811689d4737a274 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 21 Mar 2023 10:05:27 +0100 Subject: [PATCH 1492/2916] fix: Switch back to official go-git version --- cmd/kluctl/commands/cmd_helm_update.go | 2 +- e2e/helm_test.go | 4 ++-- e2e/test-utils/project.go | 2 +- go.mod | 2 -- pkg/git/auth/auth_provider.go | 4 ++-- pkg/git/auth/git_credentials_file.go | 2 +- pkg/git/auth/list_auth_provider.go | 4 ++-- pkg/git/list_refs.go | 10 +++++----- pkg/git/mirrored_repo.go | 8 ++++---- pkg/git/poor_mans_clone.go | 4 ++-- pkg/git/ssh-pool/hostport.go | 2 +- pkg/git/test_git_server.go | 2 +- pkg/git/utils.go | 2 +- pkg/vars/vars_loader_test.go | 4 ++-- 14 files changed, 25 insertions(+), 27 deletions(-) diff --git a/cmd/kluctl/commands/cmd_helm_update.go b/cmd/kluctl/commands/cmd_helm_update.go index 82ce4c2f8..34b75ccf8 100644 --- a/cmd/kluctl/commands/cmd_helm_update.go +++ b/cmd/kluctl/commands/cmd_helm_update.go @@ -3,8 +3,8 @@ package commands import ( "context" "fmt" - "github.com/fluxcd/go-git/v5/plumbing/format/index" "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing/format/index" "github.com/kluctl/kluctl/v2/cmd/kluctl/args" git2 "github.com/kluctl/kluctl/v2/pkg/git" "github.com/kluctl/kluctl/v2/pkg/helm" diff --git a/e2e/helm_test.go b/e2e/helm_test.go index 848645507..a3c8ae976 100644 --- a/e2e/helm_test.go +++ b/e2e/helm_test.go @@ -2,8 +2,8 @@ package e2e import ( "fmt" - "github.com/fluxcd/go-git/v5" - "github.com/fluxcd/go-git/v5/plumbing/object" + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing/object" test_utils "github.com/kluctl/kluctl/v2/e2e/test-utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/stretchr/testify/assert" diff --git a/e2e/test-utils/project.go b/e2e/test-utils/project.go index dc1c5daee..ae402a1b9 100644 --- a/e2e/test-utils/project.go +++ b/e2e/test-utils/project.go @@ -4,7 +4,7 @@ import ( "bytes" "context" "fmt" - "github.com/fluxcd/go-git/v5" + "github.com/go-git/go-git/v5" "github.com/huandu/xstrings" "github.com/jinzhu/copier" "github.com/kluctl/kluctl/v2/cmd/kluctl/commands" diff --git a/go.mod b/go.mod index 73986b291..d51f94ac4 100644 --- a/go.mod +++ b/go.mod @@ -9,8 +9,6 @@ require ( github.com/bitnami-labs/sealed-secrets v0.19.5 github.com/cyphar/filepath-securejoin v0.2.3 github.com/docker/distribution v2.8.1+incompatible - // See https://github.com/fluxcd/pkg/issues/397, especially in regard to skeema/knownhosts breaking compatibility - github.com/fluxcd/go-git/v5 v5.0.0-20221206140629-ec778c2c37df github.com/fluxcd/pkg/kustomize v0.13.2 github.com/go-playground/validator/v10 v10.11.2 github.com/gobwas/glob v0.2.3 // indirect diff --git a/pkg/git/auth/auth_provider.go b/pkg/git/auth/auth_provider.go index 18809899a..ab2607dba 100644 --- a/pkg/git/auth/auth_provider.go +++ b/pkg/git/auth/auth_provider.go @@ -3,8 +3,8 @@ package auth import ( "context" "fmt" - "github.com/fluxcd/go-git/v5/plumbing/transport" - ssh2 "github.com/fluxcd/go-git/v5/plumbing/transport/ssh" + "github.com/go-git/go-git/v5/plumbing/transport" + ssh2 "github.com/go-git/go-git/v5/plumbing/transport/ssh" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/git/messages" "golang.org/x/crypto/ssh" diff --git a/pkg/git/auth/git_credentials_file.go b/pkg/git/auth/git_credentials_file.go index 3f66d4b45..bf754be36 100644 --- a/pkg/git/auth/git_credentials_file.go +++ b/pkg/git/auth/git_credentials_file.go @@ -3,7 +3,7 @@ package auth import ( "bufio" "context" - "github.com/fluxcd/go-git/v5/plumbing/transport/http" + "github.com/go-git/go-git/v5/plumbing/transport/http" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/git/git-url/giturls" "github.com/kluctl/kluctl/v2/pkg/git/messages" diff --git a/pkg/git/auth/list_auth_provider.go b/pkg/git/auth/list_auth_provider.go index c6876027e..0e2d2b453 100644 --- a/pkg/git/auth/list_auth_provider.go +++ b/pkg/git/auth/list_auth_provider.go @@ -2,8 +2,8 @@ package auth import ( "context" - "github.com/fluxcd/go-git/v5/plumbing/transport/http" - "github.com/fluxcd/go-git/v5/plumbing/transport/ssh" + "github.com/go-git/go-git/v5/plumbing/transport/http" + "github.com/go-git/go-git/v5/plumbing/transport/ssh" "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/git/messages" ssh2 "golang.org/x/crypto/ssh" diff --git a/pkg/git/list_refs.go b/pkg/git/list_refs.go index a4a8bb7d5..735234d6b 100644 --- a/pkg/git/list_refs.go +++ b/pkg/git/list_refs.go @@ -4,11 +4,11 @@ import ( "bytes" "context" "fmt" - "github.com/fluxcd/go-git/v5" - "github.com/fluxcd/go-git/v5/config" - "github.com/fluxcd/go-git/v5/plumbing" - "github.com/fluxcd/go-git/v5/plumbing/protocol/packp" - "github.com/fluxcd/go-git/v5/storage/memory" + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/config" + "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/protocol/packp" + "github.com/go-git/go-git/v5/storage/memory" auth2 "github.com/kluctl/kluctl/v2/pkg/git/auth" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" ssh_pool "github.com/kluctl/kluctl/v2/pkg/git/ssh-pool" diff --git a/pkg/git/mirrored_repo.go b/pkg/git/mirrored_repo.go index e1642cdb9..10e2e805c 100644 --- a/pkg/git/mirrored_repo.go +++ b/pkg/git/mirrored_repo.go @@ -5,10 +5,10 @@ import ( "crypto/sha256" "encoding/hex" "fmt" - "github.com/fluxcd/go-git/v5" - "github.com/fluxcd/go-git/v5/config" - "github.com/fluxcd/go-git/v5/plumbing" - "github.com/fluxcd/go-git/v5/plumbing/object" + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/config" + "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/object" auth2 "github.com/kluctl/kluctl/v2/pkg/git/auth" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" _ "github.com/kluctl/kluctl/v2/pkg/git/ssh-pool" diff --git a/pkg/git/poor_mans_clone.go b/pkg/git/poor_mans_clone.go index 9804cab66..975112564 100644 --- a/pkg/git/poor_mans_clone.go +++ b/pkg/git/poor_mans_clone.go @@ -1,8 +1,8 @@ package git import ( - "github.com/fluxcd/go-git/v5" - "github.com/fluxcd/go-git/v5/config" + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/config" cp "github.com/otiai10/copy" "os" "path/filepath" diff --git a/pkg/git/ssh-pool/hostport.go b/pkg/git/ssh-pool/hostport.go index d4e314ae0..4ca5c5cc7 100644 --- a/pkg/git/ssh-pool/hostport.go +++ b/pkg/git/ssh-pool/hostport.go @@ -2,7 +2,7 @@ package ssh_pool import ( "fmt" - "github.com/fluxcd/go-git/v5/plumbing/transport/ssh" + "github.com/go-git/go-git/v5/plumbing/transport/ssh" "strconv" ) diff --git a/pkg/git/test_git_server.go b/pkg/git/test_git_server.go index ed737c2ff..3e87f6531 100644 --- a/pkg/git/test_git_server.go +++ b/pkg/git/test_git_server.go @@ -11,7 +11,7 @@ import ( "reflect" "testing" - "github.com/fluxcd/go-git/v5" + "github.com/go-git/go-git/v5" "github.com/jinzhu/copier" http_server "github.com/kluctl/kluctl/v2/pkg/git/http-server" "gopkg.in/yaml.v3" diff --git a/pkg/git/utils.go b/pkg/git/utils.go index 121bca567..f007fa3ee 100644 --- a/pkg/git/utils.go +++ b/pkg/git/utils.go @@ -2,7 +2,7 @@ package git import ( "fmt" - "github.com/fluxcd/go-git/v5" + "github.com/go-git/go-git/v5" "os" "path/filepath" ) diff --git a/pkg/vars/vars_loader_test.go b/pkg/vars/vars_loader_test.go index 1764788de..64b8ba404 100644 --- a/pkg/vars/vars_loader_test.go +++ b/pkg/vars/vars_loader_test.go @@ -12,8 +12,8 @@ import ( "strings" "testing" - git2 "github.com/fluxcd/go-git/v5" - "github.com/fluxcd/go-git/v5/plumbing" + git2 "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing" "github.com/kluctl/kluctl/v2/pkg/git" "github.com/kluctl/kluctl/v2/pkg/git/auth" git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" From 86335fd767854e3f94ec1c3f98a48d177a6bdb10 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 21 Mar 2023 10:05:49 +0100 Subject: [PATCH 1493/2916] fix: Properly handle HostKeyAlgorithms calls --- pkg/git/auth/ssh_known_hosts.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/pkg/git/auth/ssh_known_hosts.go b/pkg/git/auth/ssh_known_hosts.go index ec375b1f2..4543bad6c 100644 --- a/pkg/git/auth/ssh_known_hosts.go +++ b/pkg/git/auth/ssh_known_hosts.go @@ -1,10 +1,12 @@ package auth import ( + "errors" "fmt" "github.com/kluctl/kluctl/v2/pkg/git/auth/goph" "github.com/kluctl/kluctl/v2/pkg/git/messages" "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh/knownhosts" "net" "os" "path/filepath" @@ -72,6 +74,24 @@ func verifyHost(messageCallbacks messages.MessageCallbacks, host string, remote files = append(files, tmpFile.Name()) } + if key.Type() == "fake-public-key" { + // this makes us compatible to knownhosts.HostKeyAlgorithms which calls us with a fake public key and expects us + // to return all known keys + var keyErr knownhosts.KeyError + for _, f := range files { + hostFound, err := goph.CheckKnownHost(host, remote, key, f) + if hostFound && err == nil { + return fmt.Errorf("fake-public-key was unexpectadly found") + } + var tmpKeyErr *knownhosts.KeyError + if !errors.As(err, &tmpKeyErr) { + return fmt.Errorf("CheckKnownHost did not return expected KeyError: %v", err) + } + keyErr.Want = append(keyErr.Want, tmpKeyErr.Want...) + } + return &keyErr + } + for _, f := range files { hostFound, err := goph.CheckKnownHost(host, remote, key, f) if hostFound && err == nil { From 8b6df3beeee0c983f641ba23b28958e1404fab48 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Mar 2023 10:12:29 +0100 Subject: [PATCH 1494/2916] chore(deps): Bump github.com/aws/aws-sdk-go-v2/service/secretsmanager (#381) Bumps [github.com/aws/aws-sdk-go-v2/service/secretsmanager](https://github.com/aws/aws-sdk-go-v2) from 1.18.6 to 1.19.0. - [Release notes](https://github.com/aws/aws-sdk-go-v2/releases) - [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go-v2/compare/config/v1.18.6...service/s3/v1.19.0) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go-v2/service/secretsmanager dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 73986b291..30f18acf5 100644 --- a/go.mod +++ b/go.mod @@ -59,7 +59,7 @@ require ( filippo.io/age v1.1.1 github.com/aws/aws-sdk-go-v2 v1.17.6 github.com/aws/aws-sdk-go-v2/config v1.18.18 - github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.6 + github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.0 github.com/go-git/go-git/v5 v5.6.1 github.com/go-logr/logr v1.2.3 github.com/hashicorp/go-multierror v1.1.1 diff --git a/go.sum b/go.sum index de7843866..286d2b2e4 100644 --- a/go.sum +++ b/go.sum @@ -116,7 +116,6 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkY github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go-v2 v1.16.7/go.mod h1:6CpKuLXg2w7If3ABZCl/qZ6rEgwtjZTn4eAf4RcEyuw= -github.com/aws/aws-sdk-go-v2 v1.17.5/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= github.com/aws/aws-sdk-go-v2 v1.17.6 h1:Y773UK7OBqhzi5VDXMi1zVGsoj+CVHs2eaC2bDsLwi0= github.com/aws/aws-sdk-go-v2 v1.17.6/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= github.com/aws/aws-sdk-go-v2/config v1.18.18 h1:/ePABXvXl3ESlzUGnkkvvNnRFw3Gh13dyqaq0Qo3JcU= @@ -126,11 +125,9 @@ github.com/aws/aws-sdk-go-v2/credentials v1.13.17/go.mod h1:K9xeFo1g/YPMguMUD69Y github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.0 h1:/2Cb3SK3xVOQA7Xfr5nCWCo5H3UiNINtsVvVdk8sQqA= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.0/go.mod h1:neYVaeKr5eT7BzwULuG2YbLhzWZ22lpjKdCybR7AXrQ= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.14/go.mod h1:kdjrMwHwrC3+FsKhNcCMJ7tUVj/8uSD5CZXeQ4wV6fM= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.29/go.mod h1:Dip3sIGv485+xerzVv24emnjX5Sg88utCL8fwGmCeWg= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.30 h1:y+8n9AGDjikyXoMBTRaHHHSaFEB8267ykmvyPodJfys= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.30/go.mod h1:LUBAO3zNXQjoONBKn/kR1y0Q4cj/D02Ts0uHYjcCQLM= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.8/go.mod h1:ZIV8GYoC6WLBW5KGs+o4rsc65/ozd+eQ0L31XF5VDwk= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.23/go.mod h1:mr6c4cHC+S/MMkrjtSlG4QA36kOznDep+0fga5L/fGQ= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.24 h1:r+Kv+SEJquhAZXaJ7G4u44cIwXV3f8K+N482NNAzJZA= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.24/go.mod h1:gAuCezX/gob6BSMbItsSlMb6WZGV7K2+fWOvk8xBSto= github.com/aws/aws-sdk-go-v2/internal/ini v1.3.31 h1:hf+Vhp5WtTdcSdE+yEcUz8L73sAzN0R+0jQv+Z51/mI= @@ -139,8 +136,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.24 h1:c5qGfdbCH github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.24/go.mod h1:HMA4FZG6fyib+NDo5bpIxX1EhYjrAOveZJY2YR0xrNE= github.com/aws/aws-sdk-go-v2/service/kms v1.17.5 h1:DkubF+BSEy0uX59+pYySzWFReN3fCcXobIO8L5Phh24= github.com/aws/aws-sdk-go-v2/service/kms v1.17.5/go.mod h1:ubAtMGRUMVv5kX8lpbeDguxZ64pR4kXTGApY4sCM0io= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.6 h1:VjvQw/1Qf/rhDSl+NNOeybSpdPRjBfH60//5vzveVsY= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.6/go.mod h1:CJcdJtrO6ulXfI8l2DotKWmJShhXHCEcd9Wibyx3kC0= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.0 h1:B4LvuBxrxh2WXakqwJL22EPAWgqGGK9/E4YQV/IIkYo= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.0/go.mod h1:XF4Gbmcn6V9xIIm6lhwtyX1NXConNJ8x6yizt2Ejx/0= github.com/aws/aws-sdk-go-v2/service/sso v1.12.5 h1:bdKIX6SVF3nc3xJFw6Nf0igzS6Ff/louGq8Z6VP/3Hs= github.com/aws/aws-sdk-go-v2/service/sso v1.12.5/go.mod h1:vuWiaDB30M/QTC+lI3Wj6S/zb7tpUK2MSYgy3Guh2L0= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.5 h1:xLPZMyuZ4GuqRCIec/zWuIhRFPXh2UOJdLXBSi64ZWQ= From cdde647e47e1578288c0634f5e9a116988da77b4 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 24 Mar 2023 14:52:12 +0100 Subject: [PATCH 1495/2916] fix: Fix .git-credentials checking/loading/matching (#386) * fix: Fix checking of .git-credentials existence * fix: Fix matching of credentials in .git-credentials --- pkg/git/auth/git_credentials_file.go | 44 +++++++++++++++++++++------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/pkg/git/auth/git_credentials_file.go b/pkg/git/auth/git_credentials_file.go index bf754be36..cd9231154 100644 --- a/pkg/git/auth/git_credentials_file.go +++ b/pkg/git/auth/git_credentials_file.go @@ -7,6 +7,7 @@ import ( git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/git/git-url/giturls" "github.com/kluctl/kluctl/v2/pkg/git/messages" + "net/url" "os" "path/filepath" ) @@ -19,6 +20,7 @@ func (a *GitCredentialsFileAuthProvider) BuildAuth(ctx context.Context, gitUrl g if gitUrl.Scheme != "http" && gitUrl.Scheme != "https" { return AuthMethodAndCA{} } + a.MessageCallbacks.Trace("GitCredentialsFileAuthProvider: BuildAuth for %s", gitUrl.String()) home, err := os.UserHomeDir() if err != nil { @@ -46,10 +48,12 @@ func (a *GitCredentialsFileAuthProvider) BuildAuth(ctx context.Context, gitUrl g func (a *GitCredentialsFileAuthProvider) tryBuildAuth(gitUrl git_url.GitUrl, gitCredentialsPath string) *AuthMethodAndCA { st, err := os.Stat(gitCredentialsPath) - if err != nil || !st.Mode().IsDir() { + if err != nil || st.Mode().IsDir() { return nil } + a.MessageCallbacks.Trace("GitCredentialsFileAuthProvider: trying file %s", gitCredentialsPath) + f, err := os.Open(gitCredentialsPath) if err != nil { a.MessageCallbacks.Warning("Failed to open %s: %v", gitCredentialsPath, err) @@ -59,25 +63,43 @@ func (a *GitCredentialsFileAuthProvider) tryBuildAuth(gitUrl git_url.GitUrl, git s := bufio.NewScanner(f) for s.Scan() { + if s.Text() == "" || s.Text()[0] == '#' { + continue + } u, err := giturls.Parse(s.Text()) if err != nil { continue } + // create temporary url without password, which can be printed + tmpU := *u + tmpU.User = url.User(u.User.Username()) + + if u.User == nil || u.User.Username() == "" { + a.MessageCallbacks.Trace("GitCredentialsFileAuthProvider: ignoring %s", tmpU.String()) + continue + } + a.MessageCallbacks.Trace("GitCredentialsFileAuthProvider: trying %s", tmpU.String()) + if u.Host != gitUrl.Host { + a.MessageCallbacks.Trace("GitCredentialsFileAuthProvider: host does not match") continue } - if u.User == nil { + if gitUrl.User != nil && gitUrl.User.Username() != u.User.Username() { + a.MessageCallbacks.Trace("GitCredentialsFileAuthProvider: user does not match") continue } - if password, ok := u.User.Password(); ok { - if u.User.Username() != "" { - return &AuthMethodAndCA{ - AuthMethod: &http.BasicAuth{ - Username: u.User.Username(), - Password: password, - }, - } - } + password, ok := u.User.Password() + if !ok { + a.MessageCallbacks.Trace("GitCredentialsFileAuthProvider: no password provided") + continue + } + + a.MessageCallbacks.Trace("GitCredentialsFileAuthProvider: matched") + return &AuthMethodAndCA{ + AuthMethod: &http.BasicAuth{ + Username: u.User.Username(), + Password: password, + }, } } return nil From 01c1318075a29a600c9d972398a351f1afc5c6d5 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 29 Mar 2023 21:09:42 +0200 Subject: [PATCH 1496/2916] fix: Honor ignore-conflicts even if force-apply is active for a field (#387) --- pkg/diff/managed_fields.go | 37 ++++++++++++++++-------------- pkg/diff/managed_fields_test.go | 40 +++++++++++++++++++++++++++++++-- 2 files changed, 58 insertions(+), 19 deletions(-) diff --git a/pkg/diff/managed_fields.go b/pkg/diff/managed_fields.go index e2b4e6797..9c7f11cc8 100644 --- a/pkg/diff/managed_fields.go +++ b/pkg/diff/managed_fields.go @@ -229,9 +229,24 @@ func ResolveFieldManagerConflicts(local *uo.UnstructuredObject, remote *uo.Unstr panic(fmt.Sprintf("field '%s' not found in remote object...which can't be!", cause.Field)) } - overwrite := true - ignoreConflict := ignoreConflictsAll - if !forceApplyAll { + ignoreConflict := false + if ignoreConflictsAll { + ignoreConflict = true + } else if _, ok := ignoreConflictFields[localKeyPath.ToJsonPath()]; ok { + ignoreConflict = true + } else if _, ok := ignoreConflictFields[remoteKeyPath.ToJsonPath()]; ok { + ignoreConflict = true + } + + overwrite := false + if !ignoreConflict { + if forceApplyAll { + overwrite = true + } else if _, ok := forceApplyFields[localKeyPath.ToJsonPath()]; ok { + overwrite = true + } else if _, ok := forceApplyFields[remoteKeyPath.ToJsonPath()]; ok { + overwrite = true + } for _, mfn := range mf.managers { found := false for _, oa := range overwriteAllowedManagers { @@ -240,23 +255,11 @@ func ResolveFieldManagerConflicts(local *uo.UnstructuredObject, remote *uo.Unstr break } } - if !found { - overwrite = false + if found { + overwrite = true break } } - if _, ok := forceApplyFields[localKeyPath.ToJsonPath()]; ok { - overwrite = true - } - if _, ok := forceApplyFields[remoteKeyPath.ToJsonPath()]; ok { - overwrite = true - } - } - if _, ok := ignoreConflictFields[localKeyPath.ToJsonPath()]; ok { - ignoreConflict = true - } - if _, ok := ignoreConflictFields[remoteKeyPath.ToJsonPath()]; ok { - ignoreConflict = true } if !overwrite { diff --git a/pkg/diff/managed_fields_test.go b/pkg/diff/managed_fields_test.go index 3c6fcd96e..1cae73f91 100644 --- a/pkg/diff/managed_fields_test.go +++ b/pkg/diff/managed_fields_test.go @@ -164,6 +164,42 @@ func TestResolveFieldManagerConflicts(t *testing.T) { lost: buildLost("d1"), anns: buildAnnotations("kluctl.io/ignore-conflicts-field-123", "data.d3"), }, + { + name: "force-apply-object-ignore-conflicts", + remote: buildConfigMap(fieldInfo{"d1", "v1", "c1"}, fieldInfo{"d2", "v2", "m1"}, fieldInfo{"d3", "v3", "c1"}), + local: buildConfigMap(fieldInfo{"d1", "x", "m1"}, fieldInfo{"d2", "x", "m1"}, fieldInfo{"d3", "x", "m1"}), + status: buildConflicts("d1", "d3"), + result: buildConfigMap(fieldInfo{"d2", "x", "m1"}), + lost: buildLost(), + anns: buildAnnotations("kluctl.io/force-apply", "true", "kluctl.io/ignore-conflicts", "true"), + }, + { + name: "force-apply-object-ignore-conflicts-field", + remote: buildConfigMap(fieldInfo{"d1", "v1", "c1"}, fieldInfo{"d2", "v2", "m1"}, fieldInfo{"d3", "v3", "c1"}), + local: buildConfigMap(fieldInfo{"d1", "x", "m1"}, fieldInfo{"d2", "x", "m1"}, fieldInfo{"d3", "x", "m1"}), + status: buildConflicts("d1", "d3"), + result: buildConfigMap(fieldInfo{"d2", "x", "m1"}, fieldInfo{"d3", "x", "m1"}), + lost: buildLost(), + anns: buildAnnotations("kluctl.io/force-apply", "true", "kluctl.io/ignore-conflicts-field", "data.d1"), + }, + { + name: "force-apply-field-ignore-conflicts", + remote: buildConfigMap(fieldInfo{"d1", "v1", "c1"}, fieldInfo{"d2", "v2", "m1"}, fieldInfo{"d3", "v3", "c1"}), + local: buildConfigMap(fieldInfo{"d1", "x", "m1"}, fieldInfo{"d2", "x", "m1"}, fieldInfo{"d3", "x", "m1"}), + status: buildConflicts("d1", "d3"), + result: buildConfigMap(fieldInfo{"d2", "x", "m1"}), + lost: buildLost(), + anns: buildAnnotations("kluctl.io/force-apply-field", "data.d1", "kluctl.io/ignore-conflicts", "true"), + }, + { + name: "force-apply-field-ignore-conflicts-field", + remote: buildConfigMap(fieldInfo{"d1", "v1", "c1"}, fieldInfo{"d2", "v2", "m1"}, fieldInfo{"d3", "v3", "c1"}), + local: buildConfigMap(fieldInfo{"d1", "x", "m1"}, fieldInfo{"d2", "x", "m1"}, fieldInfo{"d3", "x", "m1"}), + status: buildConflicts("d1", "d3"), + result: buildConfigMap(fieldInfo{"d1", "x", "m1"}, fieldInfo{"d2", "x", "m1"}), + lost: buildLost(), + anns: buildAnnotations("kluctl.io/force-apply-field", "data.d1", "kluctl.io/ignore-conflicts-field", "data.d3"), + }, } for _, tc := range tests { @@ -185,8 +221,8 @@ func TestResolveFieldManagerConflicts(t *testing.T) { r, l, err := ResolveFieldManagerConflicts(tc.local, tc.remote, tc.status) assert.NoError(t, err) - assert.Equal(t, r, tc.result) - assert.Equal(t, l, tc.lost) + assert.Equal(t, tc.result, r) + assert.Equal(t, tc.lost, l) }) } } From 4e2dff288202e81f8195313327f9927d80e79ba9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Apr 2023 10:00:12 +0200 Subject: [PATCH 1497/2916] chore(deps): Bump k8s.io/client-go from 0.26.2 to 0.26.3 (#383) Bumps [k8s.io/client-go](https://github.com/kubernetes/client-go) from 0.26.2 to 0.26.3. - [Release notes](https://github.com/kubernetes/client-go/releases) - [Changelog](https://github.com/kubernetes/client-go/blob/master/CHANGELOG.md) - [Commits](https://github.com/kubernetes/client-go/compare/v0.26.2...v0.26.3) --- updated-dependencies: - dependency-name: k8s.io/client-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 18 ++++++------------ 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index aef2530c4..176c8ebf3 100644 --- a/go.mod +++ b/go.mod @@ -44,10 +44,10 @@ require ( gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 helm.sh/helm/v3 v3.11.2 - k8s.io/api v0.26.2 + k8s.io/api v0.26.3 k8s.io/apiextensions-apiserver v0.26.2 - k8s.io/apimachinery v0.26.2 - k8s.io/client-go v0.26.2 + k8s.io/apimachinery v0.26.3 + k8s.io/client-go v0.26.3 k8s.io/klog/v2 v2.90.1 sigs.k8s.io/kustomize/kyaml v0.13.9 sigs.k8s.io/structured-merge-diff/v4 v4.2.3 diff --git a/go.sum b/go.sum index 286d2b2e4..2acf695ec 100644 --- a/go.sum +++ b/go.sum @@ -91,14 +91,12 @@ github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2B github.com/Microsoft/hcsshim v0.9.6 h1:VwnDOgLeoi2du6dAznfmspNqTiwczvjv4K7NxuY9jsY= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8= github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA= github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= github.com/a8m/expect v1.0.0/go.mod h1:4IwSCMumY49ScypDnjNbYEjgVeqy1/U2cEs3Lat96eA= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= -github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -256,8 +254,6 @@ github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= -github.com/fluxcd/go-git/v5 v5.0.0-20221206140629-ec778c2c37df h1:2BHXJp1PwX7D47Q2oaKDekn+BZVZCmxeCWNi+FyownE= -github.com/fluxcd/go-git/v5 v5.0.0-20221206140629-ec778c2c37df/go.mod h1:raWgfUV7lDQVXp4QXUaeNNJkRVKz97UQuF+0kdY7Vmo= github.com/fluxcd/pkg/apis/kustomize v0.8.0 h1:A6aLolxPV2Sll44SOHiX96lbXXmRZmS5BoEerkRHrfM= github.com/fluxcd/pkg/apis/kustomize v0.8.0/go.mod h1:9DPEVSfVIkiC2H3Dk6Ght4YJkswhYIaufXla4tB5Y84= github.com/fluxcd/pkg/kustomize v0.13.2 h1:isA9yi+m7sSIxdTrFR1U7+LyS2BraG07ZkKLHw3bnGo= @@ -679,7 +675,6 @@ github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= -github.com/pjbgf/sha1cd v0.2.3/go.mod h1:HOK9QrgzdHpbc2Kzip0Q1yi3M2MFGPADtR6HjG65m5M= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ= @@ -813,7 +808,6 @@ github.com/urfave/cli v1.22.7/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX github.com/vbatts/tar-split v0.11.2 h1:Via6XqJr0hceW4wff3QRzD5gAk/tatMw/4ZA7cTlIME= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= -github.com/xanzy/ssh-agent v0.3.2/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= @@ -1340,18 +1334,18 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.26.2 h1:dM3cinp3PGB6asOySalOZxEG4CZ0IAdJsrYZXE/ovGQ= -k8s.io/api v0.26.2/go.mod h1:1kjMQsFE+QHPfskEcVNgL3+Hp88B80uj0QtSOlj8itU= +k8s.io/api v0.26.3 h1:emf74GIQMTik01Aum9dPP0gAypL8JTLl/lHa4V9RFSU= +k8s.io/api v0.26.3/go.mod h1:PXsqwPMXBSBcL1lJ9CYDKy7kIReUydukS5JiRlxC3qE= k8s.io/apiextensions-apiserver v0.26.2 h1:/yTG2B9jGY2Q70iGskMf41qTLhL9XeNN2KhI0uDgwko= k8s.io/apiextensions-apiserver v0.26.2/go.mod h1:Y7UPgch8nph8mGCuVk0SK83LnS8Esf3n6fUBgew8SH8= -k8s.io/apimachinery v0.26.2 h1:da1u3D5wfR5u2RpLhE/ZtZS2P7QvDgLZTi9wrNZl/tQ= -k8s.io/apimachinery v0.26.2/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I= +k8s.io/apimachinery v0.26.3 h1:dQx6PNETJ7nODU3XPtrwkfuubs6w7sX0M8n61zHIV/k= +k8s.io/apimachinery v0.26.3/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I= k8s.io/apiserver v0.26.2 h1:Pk8lmX4G14hYqJd1poHGC08G03nIHVqdJMR0SD3IH3o= k8s.io/apiserver v0.26.2/go.mod h1:GHcozwXgXsPuOJ28EnQ/jXEM9QeG6HT22YxSNmpYNh8= k8s.io/cli-runtime v0.26.0 h1:aQHa1SyUhpqxAw1fY21x2z2OS5RLtMJOCj7tN4oq8mw= k8s.io/cli-runtime v0.26.0/go.mod h1:o+4KmwHzO/UK0wepE1qpRk6l3o60/txUZ1fEXWGIKTY= -k8s.io/client-go v0.26.2 h1:s1WkVujHX3kTp4Zn4yGNFK+dlDXy1bAAkIl+cFAiuYI= -k8s.io/client-go v0.26.2/go.mod h1:u5EjOuSyBa09yqqyY7m3abZeovO/7D/WehVVlZ2qcqU= +k8s.io/client-go v0.26.3 h1:k1UY+KXfkxV2ScEL3gilKcF7761xkYsSD6BC9szIu8s= +k8s.io/client-go v0.26.3/go.mod h1:ZPNu9lm8/dbRIPAgteN30RSXea6vrCpFvq+MateTUuQ= k8s.io/component-base v0.26.2 h1:IfWgCGUDzrD6wLLgXEstJKYZKAFS2kO+rBRi0p3LqcI= k8s.io/component-base v0.26.2/go.mod h1:DxbuIe9M3IZPRxPIzhch2m1eT7uFrSBJUBuVCQEBivs= k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw= From 7eb0d5123e7622b8c14d7cf0b59f882864af76a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Apr 2023 10:00:47 +0200 Subject: [PATCH 1498/2916] chore(deps): Bump github.com/ohler55/ojg from 1.17.5 to 1.18.4 (#392) Bumps [github.com/ohler55/ojg](https://github.com/ohler55/ojg) from 1.17.5 to 1.18.4. - [Release notes](https://github.com/ohler55/ojg/releases) - [Changelog](https://github.com/ohler55/ojg/blob/develop/CHANGELOG.md) - [Commits](https://github.com/ohler55/ojg/compare/v1.17.5...v1.18.4) --- updated-dependencies: - dependency-name: github.com/ohler55/ojg dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 176c8ebf3..5b4218bf7 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/mattn/go-isatty v0.0.17 github.com/mattn/go-runewidth v0.0.14 github.com/mitchellh/reflectwalk v1.0.2 - github.com/ohler55/ojg v1.17.5 + github.com/ohler55/ojg v1.18.4 github.com/pkg/errors v0.9.1 github.com/r3labs/diff/v2 v2.15.1 github.com/rogpeppe/go-internal v1.9.0 diff --git a/go.sum b/go.sum index 2acf695ec..cc65b2183 100644 --- a/go.sum +++ b/go.sum @@ -647,8 +647,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW github.com/nelsam/hel/v2 v2.3.2/go.mod h1:1ZTGfU2PFTOd5mx22i5O0Lc2GY933lQ2wb/ggy+rL3w= github.com/nelsam/hel/v2 v2.3.3/go.mod h1:1ZTGfU2PFTOd5mx22i5O0Lc2GY933lQ2wb/ggy+rL3w= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/ohler55/ojg v1.17.5 h1:SY6/cdhVzsLinNFIBRNSWhJgihvEWco5Y0TJe46XJ1Y= -github.com/ohler55/ojg v1.17.5/go.mod h1:7Ghirupn8NC8hSSDpI0gcjorPxj+vSVIONDWfliHR1k= +github.com/ohler55/ojg v1.18.4 h1:FXHUddnBgrx5RwlkTDJnXBL5s3DNOMlZfv4K27nNNtM= +github.com/ohler55/ojg v1.18.4/go.mod h1:uHcD1ErbErC27Zhb5Df2jUjbseLLcmOCo6oxSr3jZxo= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo/v2 v2.9.1 h1:zie5Ly042PD3bsCvsSOPvRnFwyo3rKe64TJlD6nu0mk= From 6effa40de3a86db9141539f365777928aada95ab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Apr 2023 11:17:04 +0200 Subject: [PATCH 1499/2916] chore(deps): Bump k8s.io/apiextensions-apiserver from 0.26.2 to 0.26.3 (#385) Bumps [k8s.io/apiextensions-apiserver](https://github.com/kubernetes/apiextensions-apiserver) from 0.26.2 to 0.26.3. - [Release notes](https://github.com/kubernetes/apiextensions-apiserver/releases) - [Commits](https://github.com/kubernetes/apiextensions-apiserver/compare/v0.26.2...v0.26.3) --- updated-dependencies: - dependency-name: k8s.io/apiextensions-apiserver dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 5b4218bf7..66e385b36 100644 --- a/go.mod +++ b/go.mod @@ -45,7 +45,7 @@ require ( gopkg.in/yaml.v3 v3.0.1 helm.sh/helm/v3 v3.11.2 k8s.io/api v0.26.3 - k8s.io/apiextensions-apiserver v0.26.2 + k8s.io/apiextensions-apiserver v0.26.3 k8s.io/apimachinery v0.26.3 k8s.io/client-go v0.26.3 k8s.io/klog/v2 v2.90.1 @@ -234,9 +234,9 @@ require ( gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gotest.tools/v3 v3.2.0 // indirect - k8s.io/apiserver v0.26.2 // indirect + k8s.io/apiserver v0.26.3 // indirect k8s.io/cli-runtime v0.26.0 // indirect - k8s.io/component-base v0.26.2 // indirect + k8s.io/component-base v0.26.3 // indirect k8s.io/kube-openapi v0.0.0-20230109183929-3758b55a6596 // indirect k8s.io/kubectl v0.26.0 // indirect k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 // indirect diff --git a/go.sum b/go.sum index cc65b2183..d11a72886 100644 --- a/go.sum +++ b/go.sum @@ -1336,18 +1336,18 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.26.3 h1:emf74GIQMTik01Aum9dPP0gAypL8JTLl/lHa4V9RFSU= k8s.io/api v0.26.3/go.mod h1:PXsqwPMXBSBcL1lJ9CYDKy7kIReUydukS5JiRlxC3qE= -k8s.io/apiextensions-apiserver v0.26.2 h1:/yTG2B9jGY2Q70iGskMf41qTLhL9XeNN2KhI0uDgwko= -k8s.io/apiextensions-apiserver v0.26.2/go.mod h1:Y7UPgch8nph8mGCuVk0SK83LnS8Esf3n6fUBgew8SH8= +k8s.io/apiextensions-apiserver v0.26.3 h1:5PGMm3oEzdB1W/FTMgGIDmm100vn7IaUP5er36dB+YE= +k8s.io/apiextensions-apiserver v0.26.3/go.mod h1:jdA5MdjNWGP+njw1EKMZc64xAT5fIhN6VJrElV3sfpQ= k8s.io/apimachinery v0.26.3 h1:dQx6PNETJ7nODU3XPtrwkfuubs6w7sX0M8n61zHIV/k= k8s.io/apimachinery v0.26.3/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I= -k8s.io/apiserver v0.26.2 h1:Pk8lmX4G14hYqJd1poHGC08G03nIHVqdJMR0SD3IH3o= -k8s.io/apiserver v0.26.2/go.mod h1:GHcozwXgXsPuOJ28EnQ/jXEM9QeG6HT22YxSNmpYNh8= +k8s.io/apiserver v0.26.3 h1:blBpv+yOiozkPH2aqClhJmJY+rp53Tgfac4SKPDJnU4= +k8s.io/apiserver v0.26.3/go.mod h1:CJe/VoQNcXdhm67EvaVjYXxR3QyfwpceKPuPaeLibTA= k8s.io/cli-runtime v0.26.0 h1:aQHa1SyUhpqxAw1fY21x2z2OS5RLtMJOCj7tN4oq8mw= k8s.io/cli-runtime v0.26.0/go.mod h1:o+4KmwHzO/UK0wepE1qpRk6l3o60/txUZ1fEXWGIKTY= k8s.io/client-go v0.26.3 h1:k1UY+KXfkxV2ScEL3gilKcF7761xkYsSD6BC9szIu8s= k8s.io/client-go v0.26.3/go.mod h1:ZPNu9lm8/dbRIPAgteN30RSXea6vrCpFvq+MateTUuQ= -k8s.io/component-base v0.26.2 h1:IfWgCGUDzrD6wLLgXEstJKYZKAFS2kO+rBRi0p3LqcI= -k8s.io/component-base v0.26.2/go.mod h1:DxbuIe9M3IZPRxPIzhch2m1eT7uFrSBJUBuVCQEBivs= +k8s.io/component-base v0.26.3 h1:oC0WMK/ggcbGDTkdcqefI4wIZRYdK3JySx9/HADpV0g= +k8s.io/component-base v0.26.3/go.mod h1:5kj1kZYwSC6ZstHJN7oHBqcJC6yyn41eR+Sqa/mQc8E= k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw= k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20230109183929-3758b55a6596 h1:8cNCQs+WqqnSpZ7y0LMQPKD+RZUHU17VqLPMW3qxnxc= From 6f4352afd5a1823255443859420ab404e5f067e6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 12 Apr 2023 08:59:16 +0200 Subject: [PATCH 1500/2916] fix: Pin markdown-link-check to 3.10.3 until the latest version gets fixed (#400) --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 69a9805ac..d8f9ce4aa 100644 --- a/Makefile +++ b/Makefile @@ -115,8 +115,9 @@ endif replace-commands-help: ## Replace commands help in docs $(GOCMD) run ./internal/replace-commands-help --docs-dir ./docs/reference/commands +MARKDOWN_LINK_CHECK_VERSION=3.10.3 # warning, 3.11.x is broken markdown-link-check: ## Check markdown files for dead links - find . -name '*.md' | xargs docker run -v ${PWD}:/tmp:ro --rm -i -w /tmp ghcr.io/tcort/markdown-link-check:stable + find . -name '*.md' | xargs docker run -v ${PWD}:/tmp:ro --rm -i -w /tmp ghcr.io/tcort/markdown-link-check:$(MARKDOWN_LINK_CHECK_VERSION) ## Release: version: ## Write next version into version file From a8452df46957c1d691ae6e703eccd1d3ef710e7c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Apr 2023 23:53:57 +0200 Subject: [PATCH 1501/2916] chore(deps): Bump golang.org/x/text from 0.8.0 to 0.9.0 (#397) Bumps [golang.org/x/text](https://github.com/golang/text) from 0.8.0 to 0.9.0. - [Release notes](https://github.com/golang/text/releases) - [Commits](https://github.com/golang/text/compare/v0.8.0...v0.9.0) --- updated-dependencies: - dependency-name: golang.org/x/text dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 66e385b36..5bd149ad5 100644 --- a/go.mod +++ b/go.mod @@ -40,7 +40,7 @@ require ( golang.org/x/sync v0.1.0 golang.org/x/sys v0.6.0 golang.org/x/term v0.6.0 - golang.org/x/text v0.8.0 + golang.org/x/text v0.9.0 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 helm.sh/helm/v3 v3.11.2 diff --git a/go.sum b/go.sum index d11a72886..1802c6399 100644 --- a/go.sum +++ b/go.sum @@ -1101,8 +1101,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From 780d1322338f7906eab9741101f61b6214f855e6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Apr 2023 23:54:19 +0200 Subject: [PATCH 1502/2916] chore(deps): Bump github.com/otiai10/copy from 1.9.0 to 1.10.0 (#398) Bumps [github.com/otiai10/copy](https://github.com/otiai10/copy) from 1.9.0 to 1.10.0. - [Release notes](https://github.com/otiai10/copy/releases) - [Commits](https://github.com/otiai10/copy/compare/v1.9.0...v1.10.0) --- updated-dependencies: - dependency-name: github.com/otiai10/copy dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 10 +++------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 5bd149ad5..900c9155a 100644 --- a/go.mod +++ b/go.mod @@ -63,7 +63,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 github.com/huandu/xstrings v1.4.0 github.com/onsi/gomega v1.27.4 - github.com/otiai10/copy v1.9.0 + github.com/otiai10/copy v1.10.0 go.mozilla.org/sops/v3 v3.7.4-0.20220901181616-9124783930b1 sigs.k8s.io/controller-runtime v0.14.5 sigs.k8s.io/kustomize/api v0.12.1 diff --git a/go.sum b/go.sum index 1802c6399..466f95ec2 100644 --- a/go.sum +++ b/go.sum @@ -660,13 +660,9 @@ github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7X github.com/opencontainers/image-spec v1.1.0-rc2/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= github.com/opencontainers/runc v1.1.2 h1:2VSZwLx5k/BfsBxMMipG/LYUnmqOD/BPkIVgQUcTlLw= github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= -github.com/otiai10/copy v1.9.0 h1:7KFNiCgZ91Ru4qW4CWPf/7jqtxLagGRmIxWldPP9VY4= -github.com/otiai10/copy v1.9.0/go.mod h1:hsfX19wcn0UWIHUQ3/4fHuehhk2UyArQ9dVFAn3FczI= -github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= -github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= -github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= -github.com/otiai10/mint v1.4.0 h1:umwcf7gbpEwf7WFzqmWwSv0CzbeMsae2u9ZvpP8j2q4= -github.com/otiai10/mint v1.4.0/go.mod h1:gifjb2MYOoULtKLqUAEILUG/9KONW6f7YsJ6vQLTlFI= +github.com/otiai10/copy v1.10.0 h1:znyI7l134wNg/wDktoVQPxPkgvhDfGCYUasey+h0rDQ= +github.com/otiai10/copy v1.10.0/go.mod h1:rSaLseMUsZFFbsFGc7wCJnnkTAvdc5L6VWxPE4308Ww= +github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= From 5148c8fdc8928f3bd9ee81e2a5ec8ad5a8b6b6a7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Apr 2023 23:54:47 +0200 Subject: [PATCH 1503/2916] chore(deps): Bump k8s.io/apimachinery from 0.26.3 to 0.27.0 (#399) Bumps [k8s.io/apimachinery](https://github.com/kubernetes/apimachinery) from 0.26.3 to 0.27.0. - [Release notes](https://github.com/kubernetes/apimachinery/releases) - [Commits](https://github.com/kubernetes/apimachinery/compare/v0.26.3...v0.27.0) --- updated-dependencies: - dependency-name: k8s.io/apimachinery dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 15 +++++++-------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index 900c9155a..e8e104afa 100644 --- a/go.mod +++ b/go.mod @@ -46,7 +46,7 @@ require ( helm.sh/helm/v3 v3.11.2 k8s.io/api v0.26.3 k8s.io/apiextensions-apiserver v0.26.3 - k8s.io/apimachinery v0.26.3 + k8s.io/apimachinery v0.27.0 k8s.io/client-go v0.26.3 k8s.io/klog/v2 v2.90.1 sigs.k8s.io/kustomize/kyaml v0.13.9 @@ -237,9 +237,9 @@ require ( k8s.io/apiserver v0.26.3 // indirect k8s.io/cli-runtime v0.26.0 // indirect k8s.io/component-base v0.26.3 // indirect - k8s.io/kube-openapi v0.0.0-20230109183929-3758b55a6596 // indirect + k8s.io/kube-openapi v0.0.0-20230308215209-15aac26d736a // indirect k8s.io/kubectl v0.26.0 // indirect - k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 // indirect + k8s.io/utils v0.0.0-20230209194617-a36077c30491 // indirect oras.land/oras-go v1.2.2 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect ) diff --git a/go.sum b/go.sum index 466f95ec2..1e9193c3d 100644 --- a/go.sum +++ b/go.sum @@ -228,7 +228,6 @@ github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arX github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g= github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g= -github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ= github.com/emicklei/go-restful/v3 v3.10.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= @@ -406,7 +405,7 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= @@ -1334,8 +1333,8 @@ k8s.io/api v0.26.3 h1:emf74GIQMTik01Aum9dPP0gAypL8JTLl/lHa4V9RFSU= k8s.io/api v0.26.3/go.mod h1:PXsqwPMXBSBcL1lJ9CYDKy7kIReUydukS5JiRlxC3qE= k8s.io/apiextensions-apiserver v0.26.3 h1:5PGMm3oEzdB1W/FTMgGIDmm100vn7IaUP5er36dB+YE= k8s.io/apiextensions-apiserver v0.26.3/go.mod h1:jdA5MdjNWGP+njw1EKMZc64xAT5fIhN6VJrElV3sfpQ= -k8s.io/apimachinery v0.26.3 h1:dQx6PNETJ7nODU3XPtrwkfuubs6w7sX0M8n61zHIV/k= -k8s.io/apimachinery v0.26.3/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I= +k8s.io/apimachinery v0.27.0 h1:vEyy/PVMbPMCPutrssCVHCf0JNZ0Px+YqPi82K2ALlk= +k8s.io/apimachinery v0.27.0/go.mod h1:5ikh59fK3AJ287GUvpUsryoMFtH9zj/ARfWCo3AyXTM= k8s.io/apiserver v0.26.3 h1:blBpv+yOiozkPH2aqClhJmJY+rp53Tgfac4SKPDJnU4= k8s.io/apiserver v0.26.3/go.mod h1:CJe/VoQNcXdhm67EvaVjYXxR3QyfwpceKPuPaeLibTA= k8s.io/cli-runtime v0.26.0 h1:aQHa1SyUhpqxAw1fY21x2z2OS5RLtMJOCj7tN4oq8mw= @@ -1346,12 +1345,12 @@ k8s.io/component-base v0.26.3 h1:oC0WMK/ggcbGDTkdcqefI4wIZRYdK3JySx9/HADpV0g= k8s.io/component-base v0.26.3/go.mod h1:5kj1kZYwSC6ZstHJN7oHBqcJC6yyn41eR+Sqa/mQc8E= k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw= k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20230109183929-3758b55a6596 h1:8cNCQs+WqqnSpZ7y0LMQPKD+RZUHU17VqLPMW3qxnxc= -k8s.io/kube-openapi v0.0.0-20230109183929-3758b55a6596/go.mod h1:/BYxry62FuDzmI+i9B+X2pqfySRmSOW2ARmj5Zbqhj0= +k8s.io/kube-openapi v0.0.0-20230308215209-15aac26d736a h1:gmovKNur38vgoWfGtP5QOGNOA7ki4n6qNYoFAgMlNvg= +k8s.io/kube-openapi v0.0.0-20230308215209-15aac26d736a/go.mod h1:y5VtZWM9sHHc2ZodIH/6SHzXj+TPU5USoA8lcIeKEKY= k8s.io/kubectl v0.26.0 h1:xmrzoKR9CyNdzxBmXV7jW9Ln8WMrwRK6hGbbf69o4T0= k8s.io/kubectl v0.26.0/go.mod h1:eInP0b+U9XUJWSYeU9XZnTA+cVYuWyl3iYPGtru0qhQ= -k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 h1:KTgPnR10d5zhztWptI952TNtt/4u5h3IzDXkdIMuo2Y= -k8s.io/utils v0.0.0-20221128185143-99ec85e7a448/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20230209194617-a36077c30491 h1:r0BAOLElQnnFhE/ApUsg3iHdVYYPBjNSSOMowRZxxsY= +k8s.io/utils v0.0.0-20230209194617-a36077c30491/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= oras.land/oras-go v1.2.2 h1:0E9tOHUfrNH7TCDk5KU0jVBEzCqbfdyuVfGmJ7ZeRPE= oras.land/oras-go v1.2.2/go.mod h1:Apa81sKoZPpP7CDciE006tSZ0x3Q3+dOoBcMZ/aNxvw= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= From 1b833d259bba71d34bf3fee92ca18e7ba88ddc6c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 13 Apr 2023 14:11:44 +0200 Subject: [PATCH 1504/2916] feat: Allow to specify regex based path in ignoreForDiff --- .../deployments/annotations/all-resources.md | 6 +++ docs/reference/deployments/deployment-yml.md | 15 ++++++- pkg/deployment/utils/diff_utils.go | 12 +++++- pkg/diff/normalize.go | 41 ++++++++++++------- pkg/types/deployment.go | 19 ++++++--- pkg/utils/uo/jsonpath.go | 2 +- pkg/utils/uo/nested_fields.go | 29 +++++++++++++ 7 files changed, 99 insertions(+), 25 deletions(-) diff --git a/docs/reference/deployments/annotations/all-resources.md b/docs/reference/deployments/annotations/all-resources.md index 9340ebdbf..dacdc5b04 100644 --- a/docs/reference/deployments/annotations/all-resources.md +++ b/docs/reference/deployments/annotations/all-resources.md @@ -104,3 +104,9 @@ Specifies a [JSON Path](https://goessner.net/articles/JsonPath/) for fields that diffs. If more than one field needs to be specified, add `-xxx` to the annotation key, where `xxx` is an arbitrary number. + +### kluctl.io/ignore-diff-field-regex +Same as [kluctl.io/ignore-diff-field](#kluctlioignore-diff-field) but specifying a regular expressions instead of a +JSON Path. + +If more than one field needs to be specified, add `-xxx` to the annotation key, where `xxx` is an arbitrary number. diff --git a/docs/reference/deployments/deployment-yml.md b/docs/reference/deployments/deployment-yml.md index 013f60ef3..9d40ee88e 100644 --- a/docs/reference/deployments/deployment-yml.md +++ b/docs/reference/deployments/deployment-yml.md @@ -294,8 +294,19 @@ This will remove the `spec.replicas` field from every resource that matches the `group`, `kind`, `namespace` and `name` can be omitted, which results in all objects matching. `fieldPath` must be a valid [JSON Path](https://goessner.net/articles/JsonPath/). `fieldPath` may also be a list of JSON paths. -The JSON Path implementation used in kluctl has extended support for wildcards in field -names, allowing you to also specify paths like `metadata.labels.my-prefix-*`. +Using regex expressions instead of JSON Pathes is also supported: + +```yaml +deployments: + - ... + +ignoreForDiff: + - group: apps + kind: Deployment + namespace: my-namespace + name: my-deployment + fieldPathRegex: metadata.labels.my-label-.* +``` As an alternative, [annotations](./annotations/all-resources.md#control-diff-behavior) can be used to control diff behavior of individual resources. diff --git a/pkg/deployment/utils/diff_utils.go b/pkg/deployment/utils/diff_utils.go index 05f740a8f..e62f33e16 100644 --- a/pkg/deployment/utils/diff_utils.go +++ b/pkg/deployment/utils/diff_utils.go @@ -77,8 +77,16 @@ func (u *diffUtil) diffObject(lo *uo.UnstructuredObject, diffRef k8s2.ObjectRef, // did not apply? (e.g. in downscale command) return } else { - nao := diff.NormalizeObject(ao, ignoreForDiffs, lo) - nro := diff.NormalizeObject(ro, ignoreForDiffs, lo) + nao, err := diff.NormalizeObject(ao, ignoreForDiffs, lo) + if err != nil { + u.dew.AddError(lo.GetK8sRef(), err) + return + } + nro, err := diff.NormalizeObject(ro, ignoreForDiffs, lo) + if err != nil { + u.dew.AddError(lo.GetK8sRef(), err) + return + } changes, err := diff.Diff(nro, nao) if err != nil { u.dew.AddError(lo.GetK8sRef(), err) diff --git a/pkg/diff/normalize.go b/pkg/diff/normalize.go index 5123a347f..81e94fb4a 100644 --- a/pkg/diff/normalize.go +++ b/pkg/diff/normalize.go @@ -118,9 +118,10 @@ func normalizeMisc(o *uo.UnstructuredObject) { } var ignoreDiffFieldAnnotationRegex = regexp.MustCompile(`^kluctl.io/ignore-diff-field(-\d*)?$`) +var ignoreDiffFieldRegexAnnotationRegex = regexp.MustCompile(`^kluctl.io/ignore-diff-field-regex(-\d*)?$`) // NormalizeObject Performs some deterministic sorting and other normalizations to avoid ugly diffs due to order changes -func NormalizeObject(o_ *uo.UnstructuredObject, ignoreForDiffs []*types.IgnoreForDiffItemConfig, localObject *uo.UnstructuredObject) *uo.UnstructuredObject { +func NormalizeObject(o_ *uo.UnstructuredObject, ignoreForDiffs []*types.IgnoreForDiffItemConfig, localObject *uo.UnstructuredObject) (*uo.UnstructuredObject, error) { gvk := o_.GetK8sGVK() name := o_.GetK8sName() ns := o_.GetK8sNamespace() @@ -138,6 +139,11 @@ func NormalizeObject(o_ *uo.UnstructuredObject, ignoreForDiffs []*types.IgnoreFo normalizeServiceAccount(o) } + if utils.ParseBoolOrFalse(localObject.GetK8sAnnotation("kluctl.io/ignore-diff")) { + // Return empty object so that diffs will always be empty + return &uo.UnstructuredObject{Object: map[string]interface{}{}}, nil + } + checkMatch := func(v string, m *string) bool { if v == "" || m == nil { return true @@ -145,6 +151,18 @@ func NormalizeObject(o_ *uo.UnstructuredObject, ignoreForDiffs []*types.IgnoreFo return v == *m } + ignoreForDiffs = append([]*types.IgnoreForDiffItemConfig{}, ignoreForDiffs...) + for _, v := range localObject.GetK8sAnnotationsWithRegex(ignoreDiffFieldAnnotationRegex) { + ignoreForDiffs = append(ignoreForDiffs, &types.IgnoreForDiffItemConfig{ + FieldPath: []string{v}, + }) + } + for _, v := range localObject.GetK8sAnnotationsWithRegex(ignoreDiffFieldRegexAnnotationRegex) { + ignoreForDiffs = append(ignoreForDiffs, &types.IgnoreForDiffItemConfig{ + FieldPathRegex: []string{v}, + }) + } + for _, ifd := range ignoreForDiffs { if !checkMatch(gvk.Group, ifd.Group) { continue @@ -162,24 +180,17 @@ func NormalizeObject(o_ *uo.UnstructuredObject, ignoreForDiffs []*types.IgnoreFo for _, fp := range ifd.FieldPath { jp, err := uo.NewMyJsonPath(fp) if err != nil { - continue + return nil, err } _ = jp.Del(o) } - } - - if utils.ParseBoolOrFalse(localObject.GetK8sAnnotation("kluctl.io/ignore-diff")) { - // Return empty object so that diffs will always be empty - return &uo.UnstructuredObject{Object: map[string]interface{}{}} - } - - for _, v := range localObject.GetK8sAnnotationsWithRegex(ignoreDiffFieldAnnotationRegex) { - j, err := uo.NewMyJsonPath(v) - if err != nil { - continue + for _, fp := range ifd.FieldPathRegex { + err := o.RemoveFieldsByPathRegex(fp) + if err != nil { + return nil, err + } } - _ = j.Del(o) } - return o + return o, nil } diff --git a/pkg/types/deployment.go b/pkg/types/deployment.go index df656745c..22e2a1914 100644 --- a/pkg/types/deployment.go +++ b/pkg/types/deployment.go @@ -77,11 +77,19 @@ func (s *SingleStringOrList) UnmarshalYAML(unmarshal func(interface{}) error) er } type IgnoreForDiffItemConfig struct { - FieldPath SingleStringOrList `yaml:"fieldPath" validate:"required"` - Group *string `yaml:"group,omitempty"` - Kind *string `yaml:"kind,omitempty"` - Name *string `yaml:"name,omitempty"` - Namespace *string `yaml:"namespace,omitempty"` + FieldPath SingleStringOrList `yaml:"fieldPath" validate:"required"` + FieldPathRegex SingleStringOrList `yaml:"fieldPathRegex,omitempty"` + Group *string `yaml:"group,omitempty"` + Kind *string `yaml:"kind,omitempty"` + Name *string `yaml:"name,omitempty"` + Namespace *string `yaml:"namespace,omitempty"` +} + +func ValidateIgnoreForDiffItemConfig(sl validator.StructLevel) { + s := sl.Current().Interface().(IgnoreForDiffItemConfig) + if len(s.FieldPath)+len(s.FieldPathRegex) == 0 { + sl.ReportError(s, "self", "self", "at least one of fieldPath or fieldPathRegex must be set", "") + } } type DeploymentProjectConfig struct { @@ -102,4 +110,5 @@ type DeploymentProjectConfig struct { func init() { yaml.Validator.RegisterStructValidation(ValidateDeploymentItemConfig, DeploymentItemConfig{}) yaml.Validator.RegisterStructValidation(ValidateDeleteObjectItemConfig, DeleteObjectItemConfig{}) + yaml.Validator.RegisterStructValidation(ValidateIgnoreForDiffItemConfig, IgnoreForDiffItemConfig{}) } diff --git a/pkg/utils/uo/jsonpath.go b/pkg/utils/uo/jsonpath.go index 5263f8639..44e375534 100644 --- a/pkg/utils/uo/jsonpath.go +++ b/pkg/utils/uo/jsonpath.go @@ -7,7 +7,7 @@ import ( "strings" ) -var isSimpleIdentifier = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]+$`) +var isSimpleIdentifier = regexp.MustCompile(`^[A-Za-z_-][A-Za-z0-9_-]+$`) type KeyPath []interface{} diff --git a/pkg/utils/uo/nested_fields.go b/pkg/utils/uo/nested_fields.go index 13560b767..6da20472a 100644 --- a/pkg/utils/uo/nested_fields.go +++ b/pkg/utils/uo/nested_fields.go @@ -3,6 +3,7 @@ package uo import ( "fmt" "reflect" + "regexp" ) func (uo *UnstructuredObject) GetNestedField(keys ...interface{}) (interface{}, bool, error) { @@ -77,6 +78,34 @@ func (uo *UnstructuredObject) RemoveNestedField(keys ...interface{}) error { return nil } +func (uo *UnstructuredObject) RemoveFieldsByPathRegex(path string) error { + r, err := regexp.Compile(path) + if err != nil { + return err + } + + var toDelete []KeyPath + err = uo.NewIterator().IterateLeafs(func(it *ObjectIterator) error { + jp := it.KeyPath().ToJsonPath() + if r.MatchString(jp) { + toDelete = append(toDelete, it.KeyPathCopy()) + } + return nil + }) + if err != nil { + return err + } + + for _, p := range toDelete { + err = uo.RemoveNestedField(p...) + if err != nil { + return err + } + } + + return nil +} + func (uo *UnstructuredObject) GetNestedString(keys ...interface{}) (string, bool, error) { v, found, err := uo.GetNestedField(keys...) if err != nil { From 6aa0469b830757f8c080864425192ffb297ff486 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 13 Apr 2023 14:12:15 +0200 Subject: [PATCH 1505/2916] fix: Fix --ignore-tags to actually work The JSON Path library used in Kluctl does not support extended wildcards. --- pkg/deployment/deployment_project.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go index 8264170d2..49e3263ba 100644 --- a/pkg/deployment/deployment_project.go +++ b/pkg/deployment/deployment_project.go @@ -344,7 +344,7 @@ func (p *DeploymentProject) GetIgnoreForDiffs(ignoreTags, ignoreLabels, ignoreAn ret = append(ret, e.p.Config.IgnoreForDiff...) } if ignoreTags { - ret = append(ret, &types.IgnoreForDiffItemConfig{FieldPath: []string{`metadata.labels."kluctl.io/tag-*"`}}) + ret = append(ret, &types.IgnoreForDiffItemConfig{FieldPathRegex: []string{`metadata\.labels\["kluctl\.io/tag-.*"\]`}}) } if ignoreLabels { ret = append(ret, &types.IgnoreForDiffItemConfig{FieldPath: []string{`metadata.labels.*`}}) From 1a4a00722604680806cc05c0ab86f295e265c527 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 13 Apr 2023 15:19:17 +0200 Subject: [PATCH 1506/2916] tests: Add tests for object normalization --- pkg/diff/normalize_test.go | 203 +++++++++++++++++++++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 pkg/diff/normalize_test.go diff --git a/pkg/diff/normalize_test.go b/pkg/diff/normalize_test.go new file mode 100644 index 000000000..d380ea9b0 --- /dev/null +++ b/pkg/diff/normalize_test.go @@ -0,0 +1,203 @@ +package diff + +import ( + "fmt" + "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/yaml" + "github.com/stretchr/testify/assert" + "testing" +) + +func buildObject(j ...string) *uo.UnstructuredObject { + o := uo.FromMap(map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]any{ + "name": "test", + "namespace": "ns", + }, + }) + + for _, x := range j { + o2 := uo.FromStringMust(x) + o.Merge(o2) + } + return o +} + +func buildResultObject(j ...string) *uo.UnstructuredObject { + o := buildObject(`{"metadata": {"labels": {}, "annotations": {}}}`) + for _, x := range j { + o2 := uo.FromStringMust(x) + o.Merge(o2) + } + return o +} + +type testCase struct { + remote *uo.UnstructuredObject + local *uo.UnstructuredObject + result *uo.UnstructuredObject + ignoreForDiffs []*types.IgnoreForDiffItemConfig +} + +func runTests(t *testing.T, tests []testCase) { + for i, tc := range tests { + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + r, err := NormalizeObject(tc.remote, tc.ignoreForDiffs, tc.local) + if err != nil { + t.Error(err) + } else { + rj, _ := yaml.WriteJsonString(r) + ej, _ := yaml.WriteJsonString(tc.result) + assert.Equal(t, ej, rj) + } + }) + } +} + +func TestNormalizeNoop(t *testing.T) { + testCases := []testCase{ + {remote: buildObject(), local: buildObject(), result: buildResultObject()}, + } + runTests(t, testCases) +} + +func TestNormalizeMetadata(t *testing.T) { + testCases := []testCase{ + {remote: buildObject(`{"metadata": {"labels": null, "annotations": null}}`), local: buildObject(), result: buildResultObject()}, + {remote: buildObject(`{"metadata": {"managedFields": {}, "creationTimestamp": "test", "generation": "test", "resourceVersion": 123, "selfLink": "test", "uid": "test", "good": "keep"}}`), local: buildObject(), result: buildResultObject(`{"metadata": {"good": "keep"}}`)}, + {remote: buildObject(`{"metadata": {"annotations": {"kubectl.kubernetes.io/last-applied-configuration": "test", "good": "keep"}}}`), local: buildObject(), result: buildResultObject(`{"metadata": {"annotations": {"good": "keep"}}}`)}, + } + runTests(t, testCases) +} + +func TestNormalizeMisc(t *testing.T) { + testCases := []testCase{ + {remote: buildObject(`{"spec": {"template": {"metadata": {"labels": {"controller-uid": "test", "good": "keep"}}}}, "selector": {"controller-uid": "test", "good": "keep"}}`), local: buildObject(), result: buildResultObject(`{"metadata":{"annotations":{},"labels":{}},"selector":{"controller-uid":"test","good":"keep"},"spec":{"template":{"metadata":{"labels":{"good":"keep"}}}}}`)}, + {remote: buildObject(`{"status": {"test": "test"}}`), local: buildObject(), result: buildResultObject()}, + } + runTests(t, testCases) +} + +func TestNormalizeContainers(t *testing.T) { + testCases := []testCase{ + {remote: buildObject(`{"spec": {"template": {"spec": {"containers": ["env": [{"name": "a", "value": "x"}]]}}}}`), local: buildObject(), result: buildResultObject(`{"spec": {"template": {"spec": {"containers": ["env": {"a":{"name":"a","value":"x"}}]}}}}`)}, + } + runTests(t, testCases) +} + +func TestNormalizeData(t *testing.T) { + testCases := []testCase{ + {remote: buildObject(`{"apiVersion": "v1", "kind": "ConfigMap", "data": {"good": "keep"}}`), local: buildObject(), result: buildResultObject(`{"apiVersion": "v1", "kind": "ConfigMap", "data": {"good": "keep"}}`)}, + {remote: buildObject(`{"apiVersion": "v1", "kind": "ConfigMap", "data": {}}`), local: buildObject(), result: buildResultObject(`{"apiVersion": "v1", "kind": "ConfigMap"}`)}, + {remote: buildObject(`{"apiVersion": "v1", "kind": "ConfigMap", "data": null}`), local: buildObject(), result: buildResultObject(`{"apiVersion": "v1", "kind": "ConfigMap"}`)}, + {remote: buildObject(`{"apiVersion": "v1", "kind": "ConfigMap"}`), local: buildObject(), result: buildResultObject(`{"apiVersion": "v1", "kind": "ConfigMap"}`)}, + } + runTests(t, testCases) +} + +func TestNormalizeServiceAccounts(t *testing.T) { + testCases := []testCase{ + {remote: buildObject(`{"apiVersion": "v1", "kind": "ServiceAccount", "secrets": [{"name": "test-remove"},{"name": "good"}]}`), local: buildObject(), result: buildResultObject(`{"apiVersion": "v1", "kind": "ServiceAccount", "secrets": [{"name": "good"}]}`)}, + } + runTests(t, testCases) +} + +func TestNormalizeIgnoreForDiffs(t *testing.T) { + testCases := []testCase{ + { + remote: buildObject(`{"metadata": {"labels": {"l1": "v1", "l2": "l2", "good": "keep"}}}`), + local: buildObject(), + result: buildResultObject(`{"metadata": {"labels": {"good": "keep"}}}`), + ignoreForDiffs: []*types.IgnoreForDiffItemConfig{ + {FieldPath: []string{"metadata.labels.l1", "metadata.labels.l2"}}, + }, + }, + { + remote: buildObject(`{"metadata": {"labels": {"l1": "v1", "l2": "l2"}}}`), + local: buildObject(), + result: buildResultObject(`{"metadata": {"labels": {}}}`), + ignoreForDiffs: []*types.IgnoreForDiffItemConfig{ + {FieldPath: []string{"metadata.labels.*"}}, + }, + }, + { + remote: buildObject(`{"metadata": {"labels": {"l1": "v1", "l2": "l2", "good": "keep"}}}`), + local: buildObject(), + result: buildResultObject(`{"metadata": {"labels": {"good": "keep"}}}`), + ignoreForDiffs: []*types.IgnoreForDiffItemConfig{ + {FieldPathRegex: []string{`metadata\.labels\.l.*`}}, + }, + }, + { + remote: buildObject(`{"metadata": {"labels": {"l1": "v1", "l2": "l2", "good": "keep"}}}`), + local: buildObject(), + result: buildResultObject(`{"metadata": {"labels": {"l1": "v1", "l2": "l2", "good": "keep"}}}`), + ignoreForDiffs: []*types.IgnoreForDiffItemConfig{ + {FieldPath: []string{"metadata.labels.*"}, Group: utils.StrPtr("Nope")}, + }, + }, + { + remote: buildObject(`{"metadata": {"labels": {"l1": "v1", "l2": "l2", "good": "keep"}}}`), + local: buildObject(), + result: buildResultObject(`{"metadata": {"labels": {"l1": "v1", "l2": "l2", "good": "keep"}}}`), + ignoreForDiffs: []*types.IgnoreForDiffItemConfig{ + {FieldPath: []string{"metadata.labels.*"}, Kind: utils.StrPtr("Nope")}, + }, + }, + { + remote: buildObject(`{"metadata": {"labels": {"l1": "v1", "l2": "l2", "good": "keep"}}}`), + local: buildObject(), + result: buildResultObject(`{"metadata": {"labels": {"l1": "v1", "l2": "l2", "good": "keep"}}}`), + ignoreForDiffs: []*types.IgnoreForDiffItemConfig{ + {FieldPath: []string{"metadata.labels.*"}, Name: utils.StrPtr("Nope")}, + }, + }, + { + remote: buildObject(`{"metadata": {"labels": {"l1": "v1", "l2": "l2", "good": "keep"}}}`), + local: buildObject(), + result: buildResultObject(`{"metadata": {"labels": {"l1": "v1", "l2": "l2", "good": "keep"}}}`), + ignoreForDiffs: []*types.IgnoreForDiffItemConfig{ + {FieldPath: []string{"metadata.labels.*"}, Namespace: utils.StrPtr("Nope")}, + }, + }, + { + remote: buildObject(`{"metadata": {"labels": {"l1": "v1", "l2": "l2"}}}`), + local: buildObject(), + result: buildResultObject(`{"metadata": {"labels": {}}}`), + ignoreForDiffs: []*types.IgnoreForDiffItemConfig{ + {FieldPath: []string{"metadata.labels.*"}, Group: utils.StrPtr("apps"), Kind: utils.StrPtr("Deployment"), Name: utils.StrPtr("test"), Namespace: utils.StrPtr("ns")}, + }, + }, + } + runTests(t, testCases) +} + +func TestNormalizeIgnoreForDiffsByAnnotations(t *testing.T) { + testCases := []testCase{ + { + remote: buildObject(`{"metadata": {"labels": {"l1": "v1", "l2": "l2", "good": "keep"}}}`), + local: buildObject(`{"metadata": {"annotations": {"kluctl.io/ignore-diff": "true"}}}`), + result: uo.FromStringMust(`{}`), + }, + { + remote: buildObject(`{"metadata": {"labels": {"l1": "v1", "l2": "l2"}}}`), + local: buildObject(`{"metadata": {"annotations": {"kluctl.io/ignore-diff-field": "metadata.labels.l1"}}}`), + result: buildResultObject(`{"metadata": {"labels": {"l2": "l2"}}}`), + }, + { + remote: buildObject(`{"metadata": {"labels": {"l1": "v1", "l2": "l2", "l3": "l3", "good": "keep"}}}`), + local: buildObject(`{"metadata": {"annotations": {"kluctl.io/ignore-diff-field": "metadata.labels.l1", "kluctl.io/ignore-diff-field-1": "metadata.labels.l2", "kluctl.io/ignore-diff-field-3": "metadata.labels.l3"}}}`), + result: buildResultObject(`{"metadata": {"labels": {"good": "keep"}}}`), + }, + { + remote: buildObject(`{"metadata": {"labels": {"l1": "v1", "l2": "l2", "l3": "l3", "good": "keep"}}}`), + local: buildObject(`{"metadata": {"annotations": {"kluctl.io/ignore-diff-field-regex": "metadata\\.labels\\.l[12]", "kluctl.io/ignore-diff-field-regex-1": "metadata\\.labels\\.l3"}}}`), + result: buildResultObject(`{"metadata": {"labels": {"good": "keep"}}}`), + }, + } + runTests(t, testCases) +} From 3d9386cf8bce5ec75192ea0ee135e97ef0d80f43 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 13 Apr 2023 15:19:51 +0200 Subject: [PATCH 1507/2916] fix: GetNestedObject should not treat nil fields as not found --- pkg/utils/uo/nested_fields.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/utils/uo/nested_fields.go b/pkg/utils/uo/nested_fields.go index 6da20472a..35e3be0c0 100644 --- a/pkg/utils/uo/nested_fields.go +++ b/pkg/utils/uo/nested_fields.go @@ -182,6 +182,10 @@ func (uo *UnstructuredObject) GetNestedObject(keys ...interface{}) (*Unstructure return nil, false, nil } + if a == nil { + return nil, true, nil + } + m, ok := a.(map[string]interface{}) if !ok { return nil, false, fmt.Errorf("nested value is not a map") From 6064653bf95009cc4fc14949c9b4a2e582fc6225 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 13 Apr 2023 15:20:20 +0200 Subject: [PATCH 1508/2916] fix: Fix multiple issues with normalization found while implementing tests --- pkg/diff/normalize.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/diff/normalize.go b/pkg/diff/normalize.go index 81e94fb4a..f32f7a2d7 100644 --- a/pkg/diff/normalize.go +++ b/pkg/diff/normalize.go @@ -64,8 +64,8 @@ func normalizeContainers(containers []*uo.UnstructuredObject) { func normalizeSecretAndConfigMaps(o *uo.UnstructuredObject) { data, found, _ := o.GetNestedObject("data") - if found && len(data.Object) == 0 { - _ = data.RemoveNestedField("data") + if found && (data == nil || len(data.Object) == 0) { + _ = o.RemoveNestedField("data") } } @@ -95,7 +95,7 @@ func normalizeServiceAccount(o *uo.UnstructuredObject) { func normalizeMetadata(o *uo.UnstructuredObject) { // We don't care about managedFields when diffing (they just produce noise) _ = o.RemoveNestedField("metadata", "managedFields") - _ = o.RemoveNestedField("metadata", "annotations", "managedFields", "kubectl.kubernetes.io/last-applied-configuration") + _ = o.RemoveNestedField("metadata", "annotations", "kubectl.kubernetes.io/last-applied-configuration") // We don't want to see this in diffs _ = o.RemoveNestedField("metadata", "creationTimestamp") @@ -105,8 +105,8 @@ func normalizeMetadata(o *uo.UnstructuredObject) { _ = o.RemoveNestedField("metadata", "uid") // Ensure empty labels/metadata exist - _ = o.SetNestedFieldDefault(map[string]string{}, "metadata", "labels") - _ = o.SetNestedFieldDefault(map[string]string{}, "metadata", "annotations") + _ = o.SetNestedFieldDefault(map[string]any{}, "metadata", "labels") + _ = o.SetNestedFieldDefault(map[string]any{}, "metadata", "annotations") } func normalizeMisc(o *uo.UnstructuredObject) { From 7daec3454bae322734282127a066a6e8b9f20147 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Apr 2023 15:35:40 +0200 Subject: [PATCH 1509/2916] chore(deps): Bump github.com/mattn/go-isatty from 0.0.17 to 0.0.18 (#401) Bumps [github.com/mattn/go-isatty](https://github.com/mattn/go-isatty) from 0.0.17 to 0.0.18. - [Release notes](https://github.com/mattn/go-isatty/releases) - [Commits](https://github.com/mattn/go-isatty/compare/v0.0.17...v0.0.18) --- updated-dependencies: - dependency-name: github.com/mattn/go-isatty dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index e8e104afa..f002c7f3b 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/kluctl/go-embed-python v0.0.0-3.10.9-20230206-2 github.com/kluctl/go-jinja2 v0.0.0-20230310104535-8136715a1e5a github.com/mattn/go-colorable v0.1.13 - github.com/mattn/go-isatty v0.0.17 + github.com/mattn/go-isatty v0.0.18 github.com/mattn/go-runewidth v0.0.14 github.com/mitchellh/reflectwalk v1.0.2 github.com/ohler55/ojg v1.18.4 diff --git a/go.sum b/go.sum index 1e9193c3d..5a0c373a8 100644 --- a/go.sum +++ b/go.sum @@ -585,8 +585,9 @@ github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOA github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= +github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= From 2c5b0f9c274b5293a566e20acd2344a4c98dd829 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Apr 2023 15:35:52 +0200 Subject: [PATCH 1510/2916] chore(deps): Bump github.com/Masterminds/semver/v3 from 3.2.0 to 3.2.1 (#402) Bumps [github.com/Masterminds/semver/v3](https://github.com/Masterminds/semver) from 3.2.0 to 3.2.1. - [Release notes](https://github.com/Masterminds/semver/releases) - [Changelog](https://github.com/Masterminds/semver/blob/master/CHANGELOG.md) - [Commits](https://github.com/Masterminds/semver/compare/v3.2.0...v3.2.1) --- updated-dependencies: - dependency-name: github.com/Masterminds/semver/v3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index f002c7f3b..429cb1ebb 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.19 require ( github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 - github.com/Masterminds/semver/v3 v3.2.0 + github.com/Masterminds/semver/v3 v3.2.1 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d github.com/bitnami-labs/sealed-secrets v0.19.5 github.com/cyphar/filepath-securejoin v0.2.3 diff --git a/go.sum b/go.sum index 5a0c373a8..548fa93e1 100644 --- a/go.sum +++ b/go.sum @@ -78,8 +78,9 @@ github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6 github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= +github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Masterminds/sprig/v3 v3.2.1/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= From fb20232ffe962b9dba8ece05fdfe4fe298fcda79 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Apr 2023 15:37:36 +0200 Subject: [PATCH 1511/2916] chore(deps): Bump github.com/imdario/mergo from 0.3.14 to 0.3.15 (#403) Bumps [github.com/imdario/mergo](https://github.com/imdario/mergo) from 0.3.14 to 0.3.15. - [Release notes](https://github.com/imdario/mergo/releases) - [Commits](https://github.com/imdario/mergo/compare/v0.3.14...v0.3.15) --- updated-dependencies: - dependency-name: github.com/imdario/mergo dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 429cb1ebb..8656e3d5d 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/google/go-containerregistry v0.13.0 github.com/hashicorp/vault/api v1.9.0 github.com/hexops/gotextdiff v1.0.3 - github.com/imdario/mergo v0.3.14 + github.com/imdario/mergo v0.3.15 github.com/jinzhu/copier v0.3.5 github.com/kevinburke/ssh_config v1.2.0 github.com/kluctl/go-embed-python v0.0.0-3.10.9-20230206-2 diff --git a/go.sum b/go.sum index 548fa93e1..942dca7f5 100644 --- a/go.sum +++ b/go.sum @@ -492,8 +492,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= -github.com/imdario/mergo v0.3.14 h1:fOqeC1+nCuuk6PKQdg9YmosXX7Y7mHX6R/0ZldI9iHo= -github.com/imdario/mergo v0.3.14/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= +github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= From 1379ed7e79a04f773df9e3f0635690cab03dd567 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Apr 2023 09:16:28 +0200 Subject: [PATCH 1512/2916] chore(deps): Bump github.com/go-playground/validator/v10 (#408) Bumps [github.com/go-playground/validator/v10](https://github.com/go-playground/validator) from 10.11.2 to 10.12.0. - [Release notes](https://github.com/go-playground/validator/releases) - [Commits](https://github.com/go-playground/validator/compare/v10.11.2...v10.12.0) --- updated-dependencies: - dependency-name: github.com/go-playground/validator/v10 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 8656e3d5d..3f5d22cc3 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/cyphar/filepath-securejoin v0.2.3 github.com/docker/distribution v2.8.1+incompatible github.com/fluxcd/pkg/kustomize v0.13.2 - github.com/go-playground/validator/v10 v10.11.2 + github.com/go-playground/validator/v10 v10.12.0 github.com/gobwas/glob v0.2.3 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 github.com/google/go-containerregistry v0.13.0 @@ -169,7 +169,7 @@ require ( github.com/kylelemons/godebug v1.1.0 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect - github.com/leodido/go-urn v1.2.1 // indirect + github.com/leodido/go-urn v1.2.2 // indirect github.com/lib/pq v1.10.7 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/magiconair/properties v1.8.7 // indirect diff --git a/go.sum b/go.sum index 942dca7f5..b6c2847f4 100644 --- a/go.sum +++ b/go.sum @@ -303,8 +303,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU= -github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s= +github.com/go-playground/validator/v10 v10.12.0 h1:E4gtWgxWxp8YSxExrQFv5BpCahla0PVF2oTTEYaWQGI= +github.com/go-playground/validator/v10 v10.12.0/go.mod h1:hCAPuzYvKdP33pxWa+2+6AIKXEKqjIUyqsNCtbsSJrA= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -553,8 +553,8 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= -github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= -github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/leodido/go-urn v1.2.2 h1:7z68G0FCGvDk646jz1AelTYNYWrTNm0bEcFAo147wt4= +github.com/leodido/go-urn v1.2.2/go.mod h1:kUaIbLZWttglzwNuG0pgsh5vuV6u2YcGBYz1hIPjtOQ= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= @@ -732,6 +732,7 @@ github.com/rubenv/sql-migrate v1.3.1/go.mod h1:YzG/Vh82CwyhTFXy+Mf5ahAiiEOpAlHur github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/rwtodd/Go.Sed v0.0.0-20210816025313-55464686f9ef/go.mod h1:8AEUvGVi2uQ5b24BIhcr0GCcpd/RNAFWaN2CJFrWIIQ= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= From 0a4395515a076459f78fa49a52f005638ce4c7ad Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 14 Apr 2023 09:27:36 +0200 Subject: [PATCH 1513/2916] fix: Revert handling - as "simple identifier" when building a json path (#409) --- pkg/utils/uo/jsonpath.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/utils/uo/jsonpath.go b/pkg/utils/uo/jsonpath.go index 44e375534..5263f8639 100644 --- a/pkg/utils/uo/jsonpath.go +++ b/pkg/utils/uo/jsonpath.go @@ -7,7 +7,7 @@ import ( "strings" ) -var isSimpleIdentifier = regexp.MustCompile(`^[A-Za-z_-][A-Za-z0-9_-]+$`) +var isSimpleIdentifier = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]+$`) type KeyPath []interface{} From a4987270fca40079873e626ccc0ac0115cbb104d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Apr 2023 09:14:18 +0200 Subject: [PATCH 1514/2916] chore(deps): Bump k8s.io/apimachinery from 0.27.0 to 0.27.1 (#410) Bumps [k8s.io/apimachinery](https://github.com/kubernetes/apimachinery) from 0.27.0 to 0.27.1. - [Release notes](https://github.com/kubernetes/apimachinery/releases) - [Commits](https://github.com/kubernetes/apimachinery/compare/v0.27.0...v0.27.1) --- updated-dependencies: - dependency-name: k8s.io/apimachinery dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 3f5d22cc3..d1822c4f6 100644 --- a/go.mod +++ b/go.mod @@ -46,7 +46,7 @@ require ( helm.sh/helm/v3 v3.11.2 k8s.io/api v0.26.3 k8s.io/apiextensions-apiserver v0.26.3 - k8s.io/apimachinery v0.27.0 + k8s.io/apimachinery v0.27.1 k8s.io/client-go v0.26.3 k8s.io/klog/v2 v2.90.1 sigs.k8s.io/kustomize/kyaml v0.13.9 diff --git a/go.sum b/go.sum index b6c2847f4..239984801 100644 --- a/go.sum +++ b/go.sum @@ -1336,8 +1336,8 @@ k8s.io/api v0.26.3 h1:emf74GIQMTik01Aum9dPP0gAypL8JTLl/lHa4V9RFSU= k8s.io/api v0.26.3/go.mod h1:PXsqwPMXBSBcL1lJ9CYDKy7kIReUydukS5JiRlxC3qE= k8s.io/apiextensions-apiserver v0.26.3 h1:5PGMm3oEzdB1W/FTMgGIDmm100vn7IaUP5er36dB+YE= k8s.io/apiextensions-apiserver v0.26.3/go.mod h1:jdA5MdjNWGP+njw1EKMZc64xAT5fIhN6VJrElV3sfpQ= -k8s.io/apimachinery v0.27.0 h1:vEyy/PVMbPMCPutrssCVHCf0JNZ0Px+YqPi82K2ALlk= -k8s.io/apimachinery v0.27.0/go.mod h1:5ikh59fK3AJ287GUvpUsryoMFtH9zj/ARfWCo3AyXTM= +k8s.io/apimachinery v0.27.1 h1:EGuZiLI95UQQcClhanryclaQE6xjg1Bts6/L3cD7zyc= +k8s.io/apimachinery v0.27.1/go.mod h1:5ikh59fK3AJ287GUvpUsryoMFtH9zj/ARfWCo3AyXTM= k8s.io/apiserver v0.26.3 h1:blBpv+yOiozkPH2aqClhJmJY+rp53Tgfac4SKPDJnU4= k8s.io/apiserver v0.26.3/go.mod h1:CJe/VoQNcXdhm67EvaVjYXxR3QyfwpceKPuPaeLibTA= k8s.io/cli-runtime v0.26.0 h1:aQHa1SyUhpqxAw1fY21x2z2OS5RLtMJOCj7tN4oq8mw= From 8bb0011d686e85e91bec6b93258a370f013f8edc Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 19 Apr 2023 10:08:04 +0200 Subject: [PATCH 1515/2916] feat: Switch to using json tags instead of yaml tags (#416) * refactor: Remove unused TargetConfig * refactor: Remove unused WriteYamlToTar * feat: Use k8s yaml parsing based on json for all structs --- cmd/kluctl/commands/root.go | 2 +- pkg/deployment/images.go | 4 +- pkg/deployment/utils/diff_utils_test.go | 2 +- pkg/git/git-url/url.go | 9 +- pkg/git/test_git_server.go | 2 +- pkg/git/utils.go | 4 +- pkg/repocache/cache.go | 6 +- pkg/types/command_result.go | 56 ++++----- pkg/types/deployment.go | 69 +++++------ pkg/types/git_project.go | 17 +-- pkg/types/helm_chart.go | 26 ++-- pkg/types/k8s/ref.go | 2 +- pkg/types/kluctl_project.go | 44 +++---- pkg/types/target_config.go | 26 ++-- pkg/types/url.go | 9 +- pkg/types/vars_source.go | 58 ++++----- pkg/utils/uo/nested_fields.go | 2 + pkg/utils/uo/uo.go | 11 +- pkg/vars/vars_loader_test.go | 6 +- pkg/vars/vars_test.go | 8 +- pkg/yaml/yaml.go | 150 +++++++++--------------- pkg/yaml/yaml_test.go | 12 +- 22 files changed, 239 insertions(+), 286 deletions(-) diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index 4b949d272..291655957 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -141,7 +141,7 @@ func setupProfiling(cpuProfile string) error { } type VersionCheckState struct { - LastVersionCheck time.Time `yaml:"lastVersionCheck"` + LastVersionCheck time.Time `json:"lastVersionCheck"` } func checkNewVersion(ctx context.Context) { diff --git a/pkg/deployment/images.go b/pkg/deployment/images.go index b1af402d6..715f0c327 100644 --- a/pkg/deployment/images.go +++ b/pkg/deployment/images.go @@ -116,8 +116,8 @@ const beginPlaceholder = "XXXXXbegin_get_image_" const endPlaceholder = "_end_get_imageXXXXX" type placeHolder struct { - Image string `yaml:"image"` - HasLatestVersion bool `yaml:"hasLatestVersion"` + Image string `json:"image"` + HasLatestVersion bool `json:"hasLatestVersion"` Container string diff --git a/pkg/deployment/utils/diff_utils_test.go b/pkg/deployment/utils/diff_utils_test.go index c97d60e62..042fd32ef 100644 --- a/pkg/deployment/utils/diff_utils_test.go +++ b/pkg/deployment/utils/diff_utils_test.go @@ -119,9 +119,9 @@ func TestDiff(t *testing.T) { assert.Len(t, dtc.du.ChangedObjects, 1) assert.Equal(t, dtc.du.ChangedObjects[0].NewObject, dtc.ao[0]) assert.Equal(t, []types.Change{ + types.Change{Type: "update", JsonPath: "data.d1", OldValue: "v1", NewValue: "v12", UnifiedDiff: "-v1\n+v12"}, types.Change{Type: "delete", JsonPath: "data.d2", OldValue: "v2", NewValue: interface{}(nil), UnifiedDiff: "-v2"}, types.Change{Type: "insert", JsonPath: "data.d3", OldValue: interface{}(nil), NewValue: "v3", UnifiedDiff: "+v3"}, - types.Change{Type: "update", JsonPath: "data.d1", OldValue: "v1", NewValue: "v12", UnifiedDiff: "-v1\n+v12"}, }, dtc.du.ChangedObjects[0].Changes) }, }, diff --git a/pkg/git/git-url/url.go b/pkg/git/git-url/url.go index 5bf30d9ee..57f5cfa0f 100644 --- a/pkg/git/git-url/url.go +++ b/pkg/git/git-url/url.go @@ -1,6 +1,7 @@ package git_url import ( + "encoding/json" "fmt" "github.com/kluctl/kluctl/v2/pkg/git/git-url/giturls" "net/url" @@ -19,9 +20,9 @@ func Parse(u string) (*GitUrl, error) { return &GitUrl{*u2}, nil } -func (u *GitUrl) UnmarshalYAML(unmarshal func(interface{}) error) error { +func (u *GitUrl) UnmarshalJSON(b []byte) error { var s string - err := unmarshal(&s) + err := json.Unmarshal(b, &s) if err != nil { return err } @@ -33,8 +34,8 @@ func (u *GitUrl) UnmarshalYAML(unmarshal func(interface{}) error) error { return err } -func (u GitUrl) MarshalYAML() (interface{}, error) { - return u.String(), nil +func (u GitUrl) MarshalJSON() ([]byte, error) { + return json.Marshal(u.String()) } func (u *GitUrl) IsSsh() bool { diff --git a/pkg/git/test_git_server.go b/pkg/git/test_git_server.go index 3e87f6531..26b656b5c 100644 --- a/pkg/git/test_git_server.go +++ b/pkg/git/test_git_server.go @@ -9,12 +9,12 @@ import ( "os" "path/filepath" "reflect" + "sigs.k8s.io/yaml" "testing" "github.com/go-git/go-git/v5" "github.com/jinzhu/copier" http_server "github.com/kluctl/kluctl/v2/pkg/git/http-server" - "gopkg.in/yaml.v3" ) type TestGitServer struct { diff --git a/pkg/git/utils.go b/pkg/git/utils.go index f007fa3ee..2fae8fd60 100644 --- a/pkg/git/utils.go +++ b/pkg/git/utils.go @@ -8,8 +8,8 @@ import ( ) type CheckoutInfo struct { - CheckedOutRef string `yaml:"checkedOutRef"` - CheckedOutCommit string `yaml:"checkedOutCommit"` + CheckedOutRef string `json:"checkedOutRef"` + CheckedOutCommit string `json:"checkedOutCommit"` } func GetCheckoutInfo(path string) (ri CheckoutInfo, err error) { diff --git a/pkg/repocache/cache.go b/pkg/repocache/cache.go index 3db2b00c5..b3ca0907f 100644 --- a/pkg/repocache/cache.go +++ b/pkg/repocache/cache.go @@ -47,9 +47,9 @@ type CacheEntry struct { } type RepoInfo struct { - Url git_url.GitUrl `yaml:"url"` - RemoteRefs map[string]string `yaml:"remoteRefs"` - DefaultRef string `yaml:"defaultRef"` + Url git_url.GitUrl `json:"url"` + RemoteRefs map[string]string `json:"remoteRefs"` + DefaultRef string `json:"defaultRef"` } type RepoOverride struct { diff --git a/pkg/types/command_result.go b/pkg/types/command_result.go index 5142d73c0..d6a235c9a 100644 --- a/pkg/types/command_result.go +++ b/pkg/types/command_result.go @@ -6,50 +6,50 @@ import ( ) type Change struct { - Type string `yaml:"type" validate:"required"` - JsonPath string `yaml:"jsonPath" validate:"required"` - OldValue interface{} `yaml:"oldValue,omitempty"` - NewValue interface{} `yaml:"newValue,omitempty"` - UnifiedDiff string `yaml:"unifiedDiff,omitempty"` + Type string `json:"type" validate:"required"` + JsonPath string `json:"jsonPath" validate:"required"` + OldValue interface{} `json:"oldValue,omitempty"` + NewValue interface{} `json:"newValue,omitempty"` + UnifiedDiff string `json:"unifiedDiff,omitempty"` } type ChangedObject struct { - Ref k8s.ObjectRef `yaml:"ref"` - NewObject *uo.UnstructuredObject `yaml:"newObject,omitempty"` - OldObject *uo.UnstructuredObject `yaml:"oldObject,omitempty"` - Changes []Change `yaml:"changes,omitempty"` + Ref k8s.ObjectRef `json:"ref"` + NewObject *uo.UnstructuredObject `json:"newObject,omitempty"` + OldObject *uo.UnstructuredObject `json:"oldObject,omitempty"` + Changes []Change `json:"changes,omitempty"` } type RefAndObject struct { - Ref k8s.ObjectRef `yaml:"ref"` - Object *uo.UnstructuredObject `yaml:"object,omitempty"` + Ref k8s.ObjectRef `json:"ref"` + Object *uo.UnstructuredObject `json:"object,omitempty"` } type DeploymentError struct { - Ref k8s.ObjectRef `yaml:"ref"` - Error string `yaml:"error"` + Ref k8s.ObjectRef `json:"ref"` + Error string `json:"error"` } type CommandResult struct { - NewObjects []*RefAndObject `yaml:"newObjects,omitempty"` - ChangedObjects []*ChangedObject `yaml:"changedObjects,omitempty"` - HookObjects []*RefAndObject `yaml:"hookObjects,omitempty"` - OrphanObjects []k8s.ObjectRef `yaml:"orphanObjects,omitempty"` - DeletedObjects []k8s.ObjectRef `yaml:"deletedObjects,omitempty"` - Errors []DeploymentError `yaml:"errors,omitempty"` - Warnings []DeploymentError `yaml:"warnings,omitempty"` - SeenImages []FixedImage `yaml:"seenImages,omitempty"` + NewObjects []*RefAndObject `json:"newObjects,omitempty"` + ChangedObjects []*ChangedObject `json:"changedObjects,omitempty"` + HookObjects []*RefAndObject `json:"hookObjects,omitempty"` + OrphanObjects []k8s.ObjectRef `json:"orphanObjects,omitempty"` + DeletedObjects []k8s.ObjectRef `json:"deletedObjects,omitempty"` + Errors []DeploymentError `json:"errors,omitempty"` + Warnings []DeploymentError `json:"warnings,omitempty"` + SeenImages []FixedImage `json:"seenImages,omitempty"` } type ValidateResultEntry struct { - Ref k8s.ObjectRef `yaml:"ref"` - Annotation string `yaml:"annotation"` - Message string `yaml:"message"` + Ref k8s.ObjectRef `json:"ref"` + Annotation string `json:"annotation"` + Message string `json:"message"` } type ValidateResult struct { - Ready bool `yaml:"ready"` - Warnings []DeploymentError `yaml:"warnings,omitempty"` - Errors []DeploymentError `yaml:"errors,omitempty"` - Results []ValidateResultEntry `yaml:"results,omitempty"` + Ready bool `json:"ready"` + Warnings []DeploymentError `json:"warnings,omitempty"` + Errors []DeploymentError `json:"errors,omitempty"` + Results []ValidateResultEntry `json:"results,omitempty"` } diff --git a/pkg/types/deployment.go b/pkg/types/deployment.go index 22e2a1914..dfa43ce2c 100644 --- a/pkg/types/deployment.go +++ b/pkg/types/deployment.go @@ -1,23 +1,24 @@ package types import ( + "encoding/json" "github.com/go-playground/validator/v10" "github.com/kluctl/kluctl/v2/pkg/yaml" ) type DeploymentItemConfig struct { - Path *string `yaml:"path,omitempty"` - Include *string `yaml:"include,omitempty"` - Git *GitProject `yaml:"git,omitempty"` - Tags []string `yaml:"tags,omitempty"` - Barrier bool `yaml:"barrier,omitempty"` - WaitReadiness bool `yaml:"waitReadiness,omitempty"` - Vars []*VarsSource `yaml:"vars,omitempty"` - SkipDeleteIfTags bool `yaml:"skipDeleteIfTags,omitempty"` - OnlyRender bool `yaml:"onlyRender,omitempty"` - AlwaysDeploy bool `yaml:"alwaysDeploy,omitempty"` - DeleteObjects []DeleteObjectItemConfig `yaml:"deleteObjects,omitempty"` - When string `yaml:"when,omitempty"` + Path *string `json:"path,omitempty"` + Include *string `json:"include,omitempty"` + Git *GitProject `json:"git,omitempty"` + Tags []string `json:"tags,omitempty"` + Barrier bool `json:"barrier,omitempty"` + WaitReadiness bool `json:"waitReadiness,omitempty"` + Vars []*VarsSource `json:"vars,omitempty"` + SkipDeleteIfTags bool `json:"skipDeleteIfTags,omitempty"` + OnlyRender bool `json:"onlyRender,omitempty"` + AlwaysDeploy bool `json:"alwaysDeploy,omitempty"` + DeleteObjects []DeleteObjectItemConfig `json:"deleteObjects,omitempty"` + When string `json:"when,omitempty"` } func ValidateDeploymentItemConfig(sl validator.StructLevel) { @@ -41,10 +42,10 @@ func ValidateDeploymentItemConfig(sl validator.StructLevel) { } type DeleteObjectItemConfig struct { - Group *string `yaml:"group,omitempty"` - Kind *string `yaml:"kind,omitempty"` - Name string `yaml:"name" validate:"required"` - Namespace string `yaml:"namespace,omitempty"` + Group *string `json:"group,omitempty"` + Kind *string `json:"kind,omitempty"` + Name string `json:"name" validate:"required"` + Namespace string `json:"namespace,omitempty"` } func ValidateDeleteObjectItemConfig(sl validator.StructLevel) { @@ -55,21 +56,21 @@ func ValidateDeleteObjectItemConfig(sl validator.StructLevel) { } type SealedSecretsConfig struct { - OutputPattern *string `yaml:"outputPattern,omitempty"` + OutputPattern *string `json:"outputPattern,omitempty"` } type SingleStringOrList []string -func (s *SingleStringOrList) UnmarshalYAML(unmarshal func(interface{}) error) error { +func (s *SingleStringOrList) UnmarshalJSON(b []byte) error { var single string - if err := unmarshal(&single); err == nil { + if err := json.Unmarshal(b, &single); err == nil { // it's a single project *s = []string{single} return nil } // try as array var arr []string - if err := unmarshal(&arr); err != nil { + if err := json.Unmarshal(b, &arr); err != nil { return err } *s = arr @@ -77,12 +78,12 @@ func (s *SingleStringOrList) UnmarshalYAML(unmarshal func(interface{}) error) er } type IgnoreForDiffItemConfig struct { - FieldPath SingleStringOrList `yaml:"fieldPath" validate:"required"` - FieldPathRegex SingleStringOrList `yaml:"fieldPathRegex,omitempty"` - Group *string `yaml:"group,omitempty"` - Kind *string `yaml:"kind,omitempty"` - Name *string `yaml:"name,omitempty"` - Namespace *string `yaml:"namespace,omitempty"` + FieldPath SingleStringOrList `json:"fieldPath,omitempty"` + FieldPathRegex SingleStringOrList `json:"fieldPathRegex,omitempty"` + Group *string `json:"group,omitempty"` + Kind *string `json:"kind,omitempty"` + Name *string `json:"name,omitempty"` + Namespace *string `json:"namespace,omitempty"` } func ValidateIgnoreForDiffItemConfig(sl validator.StructLevel) { @@ -93,18 +94,18 @@ func ValidateIgnoreForDiffItemConfig(sl validator.StructLevel) { } type DeploymentProjectConfig struct { - Vars []*VarsSource `yaml:"vars,omitempty"` - SealedSecrets *SealedSecretsConfig `yaml:"sealedSecrets,omitempty"` + Vars []*VarsSource `json:"vars,omitempty"` + SealedSecrets *SealedSecretsConfig `json:"sealedSecrets,omitempty"` - When string `yaml:"when,omitempty"` + When string `json:"when,omitempty"` - Deployments []*DeploymentItemConfig `yaml:"deployments,omitempty"` + Deployments []*DeploymentItemConfig `json:"deployments,omitempty"` - CommonLabels map[string]string `yaml:"commonLabels,omitempty"` - OverrideNamespace *string `yaml:"overrideNamespace,omitempty"` - Tags []string `yaml:"tags,omitempty"` + CommonLabels map[string]string `json:"commonLabels,omitempty"` + OverrideNamespace *string `json:"overrideNamespace,omitempty"` + Tags []string `json:"tags,omitempty"` - IgnoreForDiff []*IgnoreForDiffItemConfig `yaml:"ignoreForDiff,omitempty"` + IgnoreForDiff []*IgnoreForDiffItemConfig `json:"ignoreForDiff,omitempty"` } func init() { diff --git a/pkg/types/git_project.go b/pkg/types/git_project.go index 5183cf964..e43997353 100644 --- a/pkg/types/git_project.go +++ b/pkg/types/git_project.go @@ -1,6 +1,7 @@ package types import ( + "encoding/json" "fmt" "regexp" "strings" @@ -14,18 +15,18 @@ import ( var gitDirPatternNeg = regexp.MustCompile(`[\\\/:\*?"<>|[:cntrl:]\0^]`) type GitProject struct { - Url git_url.GitUrl `yaml:"url" validate:"required"` - Ref string `yaml:"ref,omitempty"` - SubDir string `yaml:"subDir,omitempty"` + Url git_url.GitUrl `json:"url" validate:"required"` + Ref string `json:"ref,omitempty"` + SubDir string `json:"subDir,omitempty"` } -func (gp *GitProject) UnmarshalYAML(unmarshal func(interface{}) error) error { - if err := unmarshal(&gp.Url); err == nil { +func (gp *GitProject) UnmarshalJSON(b []byte) error { + if err := json.Unmarshal(b, &gp.Url); err == nil { // it's a simple string return nil } type raw GitProject - return unmarshal((*raw)(gp)) + return json.Unmarshal(b, (*raw)(gp)) } // invalidDirName evaluate directory name against forbidden characters @@ -51,8 +52,8 @@ func ValidateGitProject(sl validator.StructLevel) { } type ExternalProject struct { - Project *GitProject `yaml:"project,omitempty"` - Path *string `yaml:"path,omitempty"` + Project *GitProject `json:"project,omitempty"` + Path *string `json:"path,omitempty"` } func ValidateExternalProject(sl validator.StructLevel) { diff --git a/pkg/types/helm_chart.go b/pkg/types/helm_chart.go index 78011ad58..fe1260358 100644 --- a/pkg/types/helm_chart.go +++ b/pkg/types/helm_chart.go @@ -7,18 +7,18 @@ import ( ) type HelmChartConfig2 struct { - Repo string `yaml:"repo,omitempty"` - Path string `yaml:"path,omitempty"` - CredentialsId *string `yaml:"credentialsId,omitempty"` - ChartName string `yaml:"chartName,omitempty"` - ChartVersion string `yaml:"chartVersion,omitempty"` - UpdateConstraints *string `yaml:"updateConstraints,omitempty"` - ReleaseName string `yaml:"releaseName" validate:"required"` - Namespace *string `yaml:"namespace,omitempty"` - Output *string `yaml:"output,omitempty"` - SkipCRDs bool `yaml:"skipCRDs,omitempty"` - SkipUpdate bool `yaml:"skipUpdate,omitempty"` - SkipPrePull bool `yaml:"skipPrePull,omitempty"` + Repo string `json:"repo,omitempty"` + Path string `json:"path,omitempty"` + CredentialsId *string `json:"credentialsId,omitempty"` + ChartName string `json:"chartName,omitempty"` + ChartVersion string `json:"chartVersion,omitempty"` + UpdateConstraints *string `json:"updateConstraints,omitempty"` + ReleaseName string `json:"releaseName" validate:"required"` + Namespace *string `json:"namespace,omitempty"` + Output *string `json:"output,omitempty"` + SkipCRDs bool `json:"skipCRDs,omitempty"` + SkipUpdate bool `json:"skipUpdate,omitempty"` + SkipPrePull bool `json:"skipPrePull,omitempty"` } func ValidateHelmChartConfig2(sl validator.StructLevel) { @@ -54,7 +54,7 @@ func ValidateHelmChartConfig2(sl validator.StructLevel) { } type HelmChartConfig struct { - HelmChartConfig2 `yaml:"helmChart" validate:"required"` + HelmChartConfig2 `json:"helmChart" validate:"required"` } func init() { diff --git a/pkg/types/k8s/ref.go b/pkg/types/k8s/ref.go index d166f88bd..c3e3da2bc 100644 --- a/pkg/types/k8s/ref.go +++ b/pkg/types/k8s/ref.go @@ -6,7 +6,7 @@ import ( ) type ObjectRef struct { - GVK schema.GroupVersionKind `yaml:"gvk,inline"` + GVK schema.GroupVersionKind `json:"gvk,inline"` Name string Namespace string } diff --git a/pkg/types/kluctl_project.go b/pkg/types/kluctl_project.go index e00908085..063a41398 100644 --- a/pkg/types/kluctl_project.go +++ b/pkg/types/kluctl_project.go @@ -5,44 +5,44 @@ import ( ) type SealingConfig struct { - Args *uo.UnstructuredObject `yaml:"args,omitempty"` - SecretSets []string `yaml:"secretSets,omitempty"` - CertFile *string `yaml:"certFile,omitempty"` + Args *uo.UnstructuredObject `json:"args,omitempty"` + SecretSets []string `json:"secretSets,omitempty"` + CertFile *string `json:"certFile,omitempty"` } type Target struct { - Name string `yaml:"name" validate:"required"` - Context *string `yaml:"context,omitempty"` - Args *uo.UnstructuredObject `yaml:"args,omitempty"` - SealingConfig *SealingConfig `yaml:"sealingConfig,omitempty"` - Images []FixedImage `yaml:"images,omitempty"` - Discriminator string `yaml:"discriminator,omitempty"` + Name string `json:"name" validate:"required"` + Context *string `json:"context,omitempty"` + Args *uo.UnstructuredObject `json:"args,omitempty"` + SealingConfig *SealingConfig `json:"sealingConfig,omitempty"` + Images []FixedImage `json:"images,omitempty"` + Discriminator string `json:"discriminator,omitempty"` } type DeploymentArg struct { - Name string `yaml:"name" validate:"required"` - Default interface{} `yaml:"default,omitempty"` + Name string `json:"name" validate:"required"` + Default interface{} `json:"default,omitempty"` } type SecretSet struct { - Name string `yaml:"name" validate:"required"` - Vars []*VarsSource `yaml:"vars,omitempty"` + Name string `json:"name" validate:"required"` + Vars []*VarsSource `json:"vars,omitempty"` } type GlobalSealedSecretsConfig struct { - Bootstrap *bool `yaml:"bootstrap,omitempty"` - Namespace *string `yaml:"namespace,omitempty"` - ControllerName *string `yaml:"controllerName,omitempty"` + Bootstrap *bool `json:"bootstrap,omitempty"` + Namespace *string `json:"namespace,omitempty"` + ControllerName *string `json:"controllerName,omitempty"` } type SecretsConfig struct { - SealedSecrets *GlobalSealedSecretsConfig `yaml:"sealedSecrets,omitempty"` - SecretSets []SecretSet `yaml:"secretSets,omitempty"` + SealedSecrets *GlobalSealedSecretsConfig `json:"sealedSecrets,omitempty"` + SecretSets []SecretSet `json:"secretSets,omitempty"` } type KluctlProject struct { - Targets []*Target `yaml:"targets,omitempty"` - Args []*DeploymentArg `yaml:"args,omitempty"` - SecretsConfig *SecretsConfig `yaml:"secretsConfig,omitempty"` - Discriminator string `yaml:"discriminator,omitempty"` + Targets []*Target `json:"targets,omitempty"` + Args []*DeploymentArg `json:"args,omitempty"` + SecretsConfig *SecretsConfig `json:"secretsConfig,omitempty"` + Discriminator string `json:"discriminator,omitempty"` } diff --git a/pkg/types/target_config.go b/pkg/types/target_config.go index 7540757f6..262bf793a 100644 --- a/pkg/types/target_config.go +++ b/pkg/types/target_config.go @@ -2,26 +2,20 @@ package types import ( "github.com/kluctl/kluctl/v2/pkg/types/k8s" - "github.com/kluctl/kluctl/v2/pkg/utils/uo" ) type FixedImage struct { - Image string `yaml:"image" validate:"required"` - ResultImage string `yaml:"resultImage" validate:"required"` - DeployedImage *string `yaml:"deployedImage,omitempty"` - Namespace *string `yaml:"namespace,omitempty"` - Object *k8s.ObjectRef `yaml:"object,omitempty"` - Deployment *string `yaml:"deployment,omitempty"` - Container *string `yaml:"container,omitempty"` - DeployTags []string `yaml:"deployTags,omitempty"` - DeploymentDir *string `yaml:"deploymentDir,omitempty"` + Image string `json:"image" validate:"required"` + ResultImage string `json:"resultImage" validate:"required"` + DeployedImage *string `json:"deployedImage,omitempty"` + Namespace *string `json:"namespace,omitempty"` + Object *k8s.ObjectRef `json:"object,omitempty"` + Deployment *string `json:"deployment,omitempty"` + Container *string `json:"container,omitempty"` + DeployTags []string `json:"deployTags,omitempty"` + DeploymentDir *string `json:"deploymentDir,omitempty"` } type FixedImagesConfig struct { - Images []FixedImage `yaml:"images,omitempty"` -} - -type TargetConfig struct { - FixedImagesConfig `yaml:"fixed_images_config,inline"` - Args *uo.UnstructuredObject `yaml:"args,omitempty"` + Images []FixedImage `json:"images,omitempty"` } diff --git a/pkg/types/url.go b/pkg/types/url.go index 703f8dfba..59560e2df 100644 --- a/pkg/types/url.go +++ b/pkg/types/url.go @@ -1,6 +1,7 @@ package types import ( + "encoding/json" "net/url" ) @@ -8,9 +9,9 @@ type YamlUrl struct { url.URL } -func (u *YamlUrl) UnmarshalYAML(unmarshal func(interface{}) error) error { +func (u *YamlUrl) UnmarshalJSON(b []byte) error { var s string - err := unmarshal(&s) + err := json.Unmarshal(b, &s) if err != nil { return err } @@ -22,6 +23,6 @@ func (u *YamlUrl) UnmarshalYAML(unmarshal func(interface{}) error) error { return err } -func (u YamlUrl) MarshalYAML() (interface{}, error) { - return u.String(), nil +func (u YamlUrl) MarshalJSON() ([]byte, error) { + return json.Marshal(u.String()) } diff --git a/pkg/types/vars_source.go b/pkg/types/vars_source.go index 9e9c3db25..942cd6654 100644 --- a/pkg/types/vars_source.go +++ b/pkg/types/vars_source.go @@ -9,16 +9,16 @@ import ( ) type VarsSourceGit struct { - Url git_url.GitUrl `yaml:"url" validate:"required"` - Ref string `yaml:"ref,omitempty"` - Path string `yaml:"path" validate:"required"` + Url git_url.GitUrl `json:"url" validate:"required"` + Ref string `json:"ref,omitempty"` + Path string `json:"path" validate:"required"` } type VarsSourceClusterConfigMapOrSecret struct { - Name string `yaml:"name,omitempty"` - Labels map[string]string `yaml:"labels,omitempty"` - Namespace string `yaml:"namespace" validate:"required"` - Key string `yaml:"key" validate:"required"` + Name string `json:"name,omitempty"` + Labels map[string]string `json:"labels,omitempty"` + Namespace string `json:"namespace" validate:"required"` + Key string `json:"key" validate:"required"` } func ValidateVarsSourceClusterConfigMapOrSecret(sl validator.StructLevel) { @@ -32,42 +32,42 @@ func ValidateVarsSourceClusterConfigMapOrSecret(sl validator.StructLevel) { } type VarsSourceHttp struct { - Url YamlUrl `yaml:"url,omitempty" validate:"required"` - Method *string `yaml:"method,omitempty"` - Body *string `yaml:"body,omitempty"` - Headers map[string]string `yaml:"headers,omitempty"` - JsonPath *string `yaml:"jsonPath,omitempty"` + Url YamlUrl `json:"url,omitempty" validate:"required"` + Method *string `json:"method,omitempty"` + Body *string `json:"body,omitempty"` + Headers map[string]string `json:"headers,omitempty"` + JsonPath *string `json:"jsonPath,omitempty"` } type VarsSourceAwsSecretsManager struct { // Name or ARN of the secret. In case a name is given, the region must be specified as well - SecretName string `yaml:"secretName" validate:"required"` + SecretName string `json:"secretName" validate:"required"` // The aws region - Region *string `yaml:"region,omitempty"` + Region *string `json:"region,omitempty"` // AWS credentials profile to use. The AWS_PROFILE environemnt variables will take precedence in case it is also set - Profile *string `yaml:"profile,omitempty"` + Profile *string `json:"profile,omitempty"` } type VarsSourceVault struct { - Address string `yaml:"address" validate:"required"` - Path string `yaml:"path" validate:"required"` + Address string `json:"address" validate:"required"` + Path string `json:"path" validate:"required"` } type VarsSource struct { - IgnoreMissing *bool `yaml:"ignoreMissing,omitempty"` - NoOverride *bool `yaml:"noOverride,omitempty"` + IgnoreMissing *bool `json:"ignoreMissing,omitempty"` + NoOverride *bool `json:"noOverride,omitempty"` - Values *uo.UnstructuredObject `yaml:"values,omitempty"` - File *string `yaml:"file,omitempty"` - Git *VarsSourceGit `yaml:"git,omitempty"` - ClusterConfigMap *VarsSourceClusterConfigMapOrSecret `yaml:"clusterConfigMap,omitempty"` - ClusterSecret *VarsSourceClusterConfigMapOrSecret `yaml:"clusterSecret,omitempty"` - SystemEnvVars *uo.UnstructuredObject `yaml:"systemEnvVars,omitempty"` - Http *VarsSourceHttp `yaml:"http,omitempty"` - AwsSecretsManager *VarsSourceAwsSecretsManager `yaml:"awsSecretsManager,omitempty"` - Vault *VarsSourceVault `yaml:"vault,omitempty"` + Values *uo.UnstructuredObject `json:"values,omitempty"` + File *string `json:"file,omitempty"` + Git *VarsSourceGit `json:"git,omitempty"` + ClusterConfigMap *VarsSourceClusterConfigMapOrSecret `json:"clusterConfigMap,omitempty"` + ClusterSecret *VarsSourceClusterConfigMapOrSecret `json:"clusterSecret,omitempty"` + SystemEnvVars *uo.UnstructuredObject `json:"systemEnvVars,omitempty"` + Http *VarsSourceHttp `json:"http,omitempty"` + AwsSecretsManager *VarsSourceAwsSecretsManager `json:"awsSecretsManager,omitempty"` + Vault *VarsSourceVault `json:"vault,omitempty"` - When string `yaml:"when,omitempty"` + When string `json:"when,omitempty"` } func ValidateVarsSource(sl validator.StructLevel) { diff --git a/pkg/utils/uo/nested_fields.go b/pkg/utils/uo/nested_fields.go index 35e3be0c0..a30a992a5 100644 --- a/pkg/utils/uo/nested_fields.go +++ b/pkg/utils/uo/nested_fields.go @@ -134,6 +134,8 @@ func (uo *UnstructuredObject) GetNestedInt(keys ...interface{}) (int64, bool, er return vv.Int(), true, nil } else if vv.CanUint() { return int64(vv.Uint()), true, nil + } else if vv.CanFloat() { + return int64(vv.Float()), true, nil } else { return 0, false, fmt.Errorf("value at %s is not an int", KeyPath(keys).ToJsonPath()) } diff --git a/pkg/utils/uo/uo.go b/pkg/utils/uo/uo.go index d5190d1ae..7983e3f23 100644 --- a/pkg/utils/uo/uo.go +++ b/pkg/utils/uo/uo.go @@ -1,6 +1,7 @@ package uo import ( + "encoding/json" "fmt" "github.com/jinzhu/copier" "github.com/kluctl/kluctl/v2/pkg/yaml" @@ -9,15 +10,15 @@ import ( ) type UnstructuredObject struct { - Object map[string]interface{} `yaml:"object,omitempty,inline"` + Object map[string]interface{} `json:"object,omitempty,inline"` } -func (uo *UnstructuredObject) MarshalYAML() (interface{}, error) { - return &uo.Object, nil +func (uo *UnstructuredObject) MarshalJSON() ([]byte, error) { + return json.Marshal(uo.Object) } -func (uo *UnstructuredObject) UnmarshalYAML(unmarshal func(interface{}) error) error { - return unmarshal(&uo.Object) +func (uo *UnstructuredObject) UnmarshalJSON(b []byte) error { + return json.Unmarshal(b, &uo.Object) } func (uo *UnstructuredObject) IsZero() bool { diff --git a/pkg/vars/vars_loader_test.go b/pkg/vars/vars_loader_test.go index 64b8ba404..d4387a66d 100644 --- a/pkg/vars/vars_loader_test.go +++ b/pkg/vars/vars_loader_test.go @@ -479,19 +479,19 @@ func TestVarsLoader_SystemEnv(t *testing.T) { assert.NoError(t, err) v, _, _ := vc.Vars.GetNestedField("test1") - assert.Equal(t, 42, v) + assert.Equal(t, 42., v) v, _, _ = vc.Vars.GetNestedField("test2") assert.Equal(t, "43", v) v, _, _ = vc.Vars.GetNestedField("test3", "test4") - assert.Equal(t, 44, v) + assert.Equal(t, 44., v) v, _, _ = vc.Vars.GetNestedField("test5") assert.Equal(t, "def", v) v, _, _ = vc.Vars.GetNestedField("test6") - assert.Equal(t, 42, v) + assert.Equal(t, 42., v) v, _, _ = vc.Vars.GetNestedField("test7") assert.Equal(t, "", v) diff --git a/pkg/vars/vars_test.go b/pkg/vars/vars_test.go index 46c822e8b..a69c7077c 100644 --- a/pkg/vars/vars_test.go +++ b/pkg/vars/vars_test.go @@ -52,10 +52,12 @@ func TestVarsCtxStruct(t *testing.T) { s := struct { Test1 struct { - Test2 int - } + Test2 int `json:"test2"` + } `json:"test1"` }{ - Test1: struct{ Test2 int }{Test2: 42}, + Test1: struct { + Test2 int `json:"test2"` + }{Test2: 42}, } err := varsCtx.UpdateChildFromStruct("child", s) diff --git a/pkg/yaml/yaml.go b/pkg/yaml/yaml.go index 89610c94a..95bfb5a33 100644 --- a/pkg/yaml/yaml.go +++ b/pkg/yaml/yaml.go @@ -1,7 +1,6 @@ package yaml import ( - "archive/tar" "bytes" "encoding/json" "errors" @@ -10,41 +9,14 @@ import ( "golang.org/x/text/encoding/unicode" "golang.org/x/text/transform" yaml2 "gopkg.in/yaml.v2" - yaml3 "gopkg.in/yaml.v3" "io" "os" "path/filepath" + "regexp" + "sigs.k8s.io/yaml" "strings" ) -type Decoder interface { - Decode(v interface{}) error -} - -type decoderWrapper struct { - d *yaml3.Decoder -} - -func (w *decoderWrapper) Decode(v interface{}) error { - err := w.d.Decode(v) - if err != nil { - return err - } - - err = ValidateStructs(v) - if err != nil { - return err - } - - return nil -} - -func newDecoder(r io.Reader, out any) Decoder { - d := yaml3.NewDecoder(r) - d.KnownFields(true) - return &decoderWrapper{d: d} -} - func newUnicodeReader(r io.Reader) io.Reader { utf16bom := unicode.BOMOverride(unicode.UTF8.NewDecoder()) return transform.NewReader(r, utf16bom) @@ -75,13 +47,17 @@ func ReadYamlBytes(b []byte, o interface{}) error { func ReadYamlStream(r io.Reader, o interface{}) error { r = newUnicodeReader(r) - d := newDecoder(r, o) + b, err := io.ReadAll(r) + if err != nil { + return err + } - err := d.Decode(o) - if err != nil && errors.Is(err, io.EOF) { - return nil + err = yaml.UnmarshalStrict(b, o) + if err != nil { + return err } - return err + + return ValidateStructs(o) } func ReadYamlAllFile(p string) ([]interface{}, error) { @@ -102,27 +78,45 @@ func ReadYamlAllBytes(b []byte) ([]interface{}, error) { return ReadYamlAllStream(bytes.NewReader(b)) } +var docsSep = regexp.MustCompile("(?:^|\\s*\n)---\\s*") + func ReadYamlAllStream(r io.Reader) ([]interface{}, error) { r = newUnicodeReader(r) - d := newDecoder(r, nil) + b, err := io.ReadAll(r) + if err != nil { + return nil, err + } + s := string(b) + s = strings.TrimSpace(s) - var l []interface{} - for true { - var o interface{} - err := d.Decode(&o) + if s == "" { + return nil, nil + } + + docs := docsSep.Split(s, -1) + + ret := make([]any, 0, len(docs)) + for _, doc := range docs { + if doc == "" { + continue + } + + var x any + err = yaml.UnmarshalStrict([]byte(doc), &x) if err != nil { - if errors.Is(err, io.EOF) { - break - } return nil, err } - if o != nil { - l = append(l, o) + if x == nil { + continue } + err = ValidateStructs(x) + if err != nil { + return nil, err + } + ret = append(ret, x) } - - return l, nil + return ret, nil } func WriteYamlString(o interface{}) (string, error) { @@ -166,65 +160,31 @@ func WriteYamlAllString(l []interface{}) (string, error) { } func WriteYamlAllStream(w io.Writer, l []interface{}) error { - enc := yaml3.NewEncoder(w) - defer enc.Close() - - enc.SetIndent(2) - - for _, o := range l { - err := enc.Encode(o) + for i, o := range l { + if i != 0 { + _, err := w.Write([]byte("---\n")) + if err != nil { + return err + } + } + b, err := yaml.Marshal(o) + if err != nil { + return err + } + _, err = w.Write(b) if err != nil { return err } - } - return nil -} - -func WriteYamlToTar(tw *tar.Writer, o interface{}, name string) error { - str, err := WriteYamlBytes(o) - if err != nil { - return err - } - - err = tw.WriteHeader(&tar.Header{ - Name: name, - Size: int64(len(str)), - Mode: 0o666 | tar.TypeReg, - }) - if err != nil { - return err - } - _, err = tw.Write(str) - if err != nil { - return err } return nil } func WriteJsonString(o interface{}) (string, error) { - x, err := WriteYamlBytes(o) - if err != nil { - return "", err - } - - x, err = ConvertYamlToJson(x) + b, err := json.Marshal(o) if err != nil { return "", err } - return string(x), nil -} - -func ConvertYamlToJson(b []byte) ([]byte, error) { - var x interface{} - err := ReadYamlBytes(b, &x) - if err != nil { - return nil, err - } - b, err = json.Marshal(x) - if err != nil { - return nil, err - } - return b, nil + return string(b), nil } // RemoveDuplicateFields is a helper/hack to remove duplicate fields from yaml maps/structs. The yaml spec explicitly diff --git a/pkg/yaml/yaml_test.go b/pkg/yaml/yaml_test.go index bd2d6b772..65144ea1e 100644 --- a/pkg/yaml/yaml_test.go +++ b/pkg/yaml/yaml_test.go @@ -16,7 +16,7 @@ type EmptyYamlConfig struct { } type SimpleYamlConfig struct { - Value string `yaml:"value"` + Value string `json:"value"` } func TestReadYamlFile(t *testing.T) { @@ -308,16 +308,6 @@ value: anyValue2 assert.Equal(t, expectedString, buffer.String(), "Yaml not written correctly.") } -func TestConvertYamlToJson(t *testing.T) { - yaml := `value: anyValue1` - expectedJson := `{"value":"anyValue1"}` - yamlBytes := []byte(yaml) - expectedJsonBytes := []byte(expectedJson) - jsonBytes, convertYamlToJsonErr := ConvertYamlToJson(yamlBytes) - assert.NoError(t, convertYamlToJsonErr, "Can't convert yaml to json: %s", convertYamlToJsonErr) - assert.Equal(t, expectedJsonBytes, jsonBytes, "Yaml not converted correctly.") -} - func TestWriteJsonString(t *testing.T) { yaml := SimpleYamlConfig{ Value: "anyValue1", From 545ed5261e981a8c3605d80b9692d23abf46b251 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 20 Apr 2023 08:42:16 +0200 Subject: [PATCH 1516/2916] docs: Update commonLabels documentation to not mention the use for deletion/pruning (#419) --- docs/reference/deployments/deployment-yml.md | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/docs/reference/deployments/deployment-yml.md b/docs/reference/deployments/deployment-yml.md index 9d40ee88e..c9ffb0808 100644 --- a/docs/reference/deployments/deployment-yml.md +++ b/docs/reference/deployments/deployment-yml.md @@ -231,10 +231,7 @@ See [templating](../templating/variable-sources.md) for more details. ## commonLabels A dictionary of [labels](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/) and values to be -added to all resources deployed by any of the kustomize deployments in this deployment project. - -This feature is mainly meant to make it possible to identify all objects in a kubernetes cluster that were once deployed -through a specific deployment project. +added to all resources deployed by any of the deployment items in this deployment project. Consider the following example `deployment.yaml`: ```yaml @@ -249,16 +246,13 @@ commonLabels: my.prefix/label-2: value-2 ``` -Every resource deployed by the kustomize deployment `nginx` will now get the two provided labels attached. All included -sub-deployment projects (e.g. `sub-deployment1`) will also recursively inherit these labels and pass them to further +Every resource deployed by the kustomize deployment `nginx` will now get the four provided labels attached. All included +sub-deployment projects (e.g. `sub-deployment1`) will also recursively inherit these labels and pass them further down. -In case an included sub-deployment project also contains `commonLabels`, both dictionaries of common labels are merged +In case an included sub-deployment project also contains `commonLabels`, both dictionaries of commonLabels are merged inside the included sub-deployment project. In case of conflicts, the included common labels override the inherited. -The root deployment's `commonLabels` is also used to identify objects to be deleted when performing `kluctl delete` -or `kluctl prune` operations - Please note that these `commonLabels` are not related to `commonLabels` supported in `kustomization.yaml` files. It was decided to not rely on this feature but instead attach labels manually to resources right before sending them to kubernetes. This is due to an [implementation detail](https://github.com/kubernetes-sigs/kustomize/issues/1009) in From 08ad2e31474f8722669669aacda86d419684e00a Mon Sep 17 00:00:00 2001 From: Pat Riehecky <3534830+jcpunk@users.noreply.github.com> Date: Mon, 24 Apr 2023 02:24:14 -0500 Subject: [PATCH 1517/2916] docs: Add example for Kustomize integration (#421) Signed-off-by: Pat Riehecky --- docs/reference/deployments/kustomize.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/reference/deployments/kustomize.md b/docs/reference/deployments/kustomize.md index bcf694096..d772b5208 100644 --- a/docs/reference/deployments/kustomize.md +++ b/docs/reference/deployments/kustomize.md @@ -20,3 +20,24 @@ Generally, everything is possible via `kustomization.yaml`, is thus possible in We advise to read the kustomize [reference](https://kubectl.docs.kubernetes.io/references/kustomize/). You can also look into the official kustomize [example](https://github.com/kubernetes-sigs/kustomize/tree/master/examples). + +One way you might use this is to Kustomize a set of manifests from an external project. + +For example: +```yaml +# deployment.yml +deployments: +- git: git@github.com/example/example.git + onlyRender: true +- path: kustomize_example +``` +```yaml +# kustomize_example/kustomization.yml +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +bases: + - ../example +patches: + - # your patches here +``` From 5533d3b4efb3d9a02cf132cafedad233ea5f19f9 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 27 Apr 2023 09:13:03 +0200 Subject: [PATCH 1518/2916] chore: Upgrade many k8s api/client related libs to latest version --- e2e/test-utils/envtest_cluster_callback.go | 4 +- go.mod | 33 ++++++------ go.sum | 62 +++++++++++----------- 3 files changed, 53 insertions(+), 46 deletions(-) diff --git a/e2e/test-utils/envtest_cluster_callback.go b/e2e/test-utils/envtest_cluster_callback.go index 54b95fc02..d28a532a9 100644 --- a/e2e/test-utils/envtest_cluster_callback.go +++ b/e2e/test-utils/envtest_cluster_callback.go @@ -26,7 +26,9 @@ func (k *EnvTestCluster) buildServeCallback(gvr schema.GroupVersionResource, cb return admission.Allowed("") }), } - _ = wh.InjectLogger(logr.New(log.NullLogSink{})) + wh.LogConstructor = func(base logr.Logger, req *admission.Request) logr.Logger { + return logr.New(log.NullLogSink{}) + } return wh } diff --git a/go.mod b/go.mod index d1822c4f6..4cebb73b0 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,7 @@ require ( github.com/ohler55/ojg v1.18.4 github.com/pkg/errors v0.9.1 github.com/r3labs/diff/v2 v2.15.1 - github.com/rogpeppe/go-internal v1.9.0 + github.com/rogpeppe/go-internal v1.10.0 github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.6.1 github.com/spf13/pflag v1.0.5 @@ -38,16 +38,16 @@ require ( golang.org/x/crypto v0.7.0 golang.org/x/net v0.8.0 golang.org/x/sync v0.1.0 - golang.org/x/sys v0.6.0 + golang.org/x/sys v0.7.0 golang.org/x/term v0.6.0 golang.org/x/text v0.9.0 gopkg.in/yaml.v2 v2.4.0 - gopkg.in/yaml.v3 v3.0.1 + gopkg.in/yaml.v3 v3.0.1 // indirect helm.sh/helm/v3 v3.11.2 - k8s.io/api v0.26.3 - k8s.io/apiextensions-apiserver v0.26.3 + k8s.io/api v0.27.1 + k8s.io/apiextensions-apiserver v0.27.1 k8s.io/apimachinery v0.27.1 - k8s.io/client-go v0.26.3 + k8s.io/client-go v0.27.1 k8s.io/klog/v2 v2.90.1 sigs.k8s.io/kustomize/kyaml v0.13.9 sigs.k8s.io/structured-merge-diff/v4 v4.2.3 @@ -59,10 +59,10 @@ require ( github.com/aws/aws-sdk-go-v2/config v1.18.18 github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.0 github.com/go-git/go-git/v5 v5.6.1 - github.com/go-logr/logr v1.2.3 + github.com/go-logr/logr v1.2.4 github.com/hashicorp/go-multierror v1.1.1 github.com/huandu/xstrings v1.4.0 - github.com/onsi/gomega v1.27.4 + github.com/onsi/gomega v1.27.6 github.com/otiai10/copy v1.10.0 go.mozilla.org/sops/v3 v3.7.4-0.20220901181616-9124783930b1 sigs.k8s.io/controller-runtime v0.14.5 @@ -70,6 +70,9 @@ require ( sigs.k8s.io/yaml v1.3.0 ) +// TODO: when controller-runtime past v0.14.6 is released, remove this line +replace sigs.k8s.io/controller-runtime => sigs.k8s.io/controller-runtime v0.13.1-0.20230426175615-dca0be70fd22 + require ( cloud.google.com/go/compute v1.14.0 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect @@ -194,10 +197,10 @@ require ( github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.14.0 // indirect + github.com/prometheus/client_golang v1.15.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect - github.com/prometheus/common v0.39.0 // indirect - github.com/prometheus/procfs v0.8.0 // indirect + github.com/prometheus/common v0.42.0 // indirect + github.com/prometheus/procfs v0.9.0 // indirect github.com/rivo/uniseg v0.4.3 // indirect github.com/rubenv/sql-migrate v1.3.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect @@ -220,7 +223,7 @@ require ( go.starlark.net v0.0.0-20221205180719-3fd0dac74452 // indirect go.uber.org/atomic v1.10.0 // indirect golang.org/x/mod v0.9.0 // indirect - golang.org/x/oauth2 v0.3.0 // indirect + golang.org/x/oauth2 v0.5.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.7.0 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect @@ -228,15 +231,15 @@ require ( google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef // indirect google.golang.org/grpc v1.52.0 // indirect - google.golang.org/protobuf v1.28.1 // indirect + google.golang.org/protobuf v1.30.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gotest.tools/v3 v3.2.0 // indirect - k8s.io/apiserver v0.26.3 // indirect + k8s.io/apiserver v0.27.1 // indirect k8s.io/cli-runtime v0.26.0 // indirect - k8s.io/component-base v0.26.3 // indirect + k8s.io/component-base v0.27.1 // indirect k8s.io/kube-openapi v0.0.0-20230308215209-15aac26d736a // indirect k8s.io/kubectl v0.26.0 // indirect k8s.io/utils v0.0.0-20230209194617-a36077c30491 // indirect diff --git a/go.sum b/go.sum index 239984801..4ae3c05a0 100644 --- a/go.sum +++ b/go.sum @@ -289,8 +289,8 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= -github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= @@ -308,7 +308,7 @@ github.com/go-playground/validator/v10 v10.12.0/go.mod h1:hCAPuzYvKdP33pxWa+2+6A github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= github.com/gobuffalo/logger v1.0.6 h1:nnZNpxYo0zx+Aj9RfMPBm+x9zAU2OayFh/xrAWi34HU= github.com/gobuffalo/logger v1.0.6/go.mod h1:J31TBEHR1QLV2683OXTAItYIg8pv2JMHnF/quuAbMjs= @@ -652,9 +652,9 @@ github.com/ohler55/ojg v1.18.4 h1:FXHUddnBgrx5RwlkTDJnXBL5s3DNOMlZfv4K27nNNtM= github.com/ohler55/ojg v1.18.4/go.mod h1:uHcD1ErbErC27Zhb5Df2jUjbseLLcmOCo6oxSr3jZxo= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo/v2 v2.9.1 h1:zie5Ly042PD3bsCvsSOPvRnFwyo3rKe64TJlD6nu0mk= -github.com/onsi/gomega v1.27.4 h1:Z2AnStgsdSayCMDiCU42qIz+HLqEPcgiOCXjAU/w+8E= -github.com/onsi/gomega v1.27.4/go.mod h1:riYq/GJKh8hhoM01HN6Vmuy93AarCXCBGpvFDK3q3fQ= +github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU= +github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= +github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034= @@ -695,8 +695,8 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= -github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= -github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= +github.com/prometheus/client_golang v1.15.0 h1:5fCgGYogn0hFdhyhLbw7hEsWxufKtY9klyvdNfFlFhM= +github.com/prometheus/client_golang v1.15.0/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -706,14 +706,14 @@ github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7q github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.39.0 h1:oOyhkDq05hPZKItWVBkJ6g6AtGxi+fy7F4JvUV8uhsI= -github.com/prometheus/common v0.39.0/go.mod h1:6XBZ7lYdLCbkAVhwRsWTZn+IN5AB9F/NXd5w0BbEX0Y= +github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= +github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= -github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= +github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= +github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/r3labs/diff/v2 v2.15.1 h1:EOrVqPUzi+njlumoqJwiS/TgGgmZo83619FNDB9xQUg= github.com/r3labs/diff/v2 v2.15.1/go.mod h1:I8noH9Fc2fjSaMxqF3G2lhDdC0b+JXCfyx85tWFM9kc= @@ -725,8 +725,9 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rubenv/sql-migrate v1.3.1 h1:Vx+n4Du8X8VTYuXbhNxdEUoh6wiJERA0GlWocR5FrbA= github.com/rubenv/sql-migrate v1.3.1/go.mod h1:YzG/Vh82CwyhTFXy+Mf5ahAiiEOpAlHurg+23VEzcsk= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -986,8 +987,8 @@ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.3.0 h1:6l90koy8/LaBLmLu8jpHeHexzMwEita0zFfYlggy2F8= -golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk= +golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s= +golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1075,8 +1076,9 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1286,8 +1288,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -1332,20 +1334,20 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.26.3 h1:emf74GIQMTik01Aum9dPP0gAypL8JTLl/lHa4V9RFSU= -k8s.io/api v0.26.3/go.mod h1:PXsqwPMXBSBcL1lJ9CYDKy7kIReUydukS5JiRlxC3qE= -k8s.io/apiextensions-apiserver v0.26.3 h1:5PGMm3oEzdB1W/FTMgGIDmm100vn7IaUP5er36dB+YE= -k8s.io/apiextensions-apiserver v0.26.3/go.mod h1:jdA5MdjNWGP+njw1EKMZc64xAT5fIhN6VJrElV3sfpQ= +k8s.io/api v0.27.1 h1:Z6zUGQ1Vd10tJ+gHcNNNgkV5emCyW+v2XTmn+CLjSd0= +k8s.io/api v0.27.1/go.mod h1:z5g/BpAiD+f6AArpqNjkY+cji8ueZDU/WV1jcj5Jk4E= +k8s.io/apiextensions-apiserver v0.27.1 h1:Hp7B3KxKHBZ/FxmVFVpaDiXI6CCSr49P1OJjxKO6o4g= +k8s.io/apiextensions-apiserver v0.27.1/go.mod h1:8jEvRDtKjVtWmdkhOqE84EcNWJt/uwF8PC4627UZghY= k8s.io/apimachinery v0.27.1 h1:EGuZiLI95UQQcClhanryclaQE6xjg1Bts6/L3cD7zyc= k8s.io/apimachinery v0.27.1/go.mod h1:5ikh59fK3AJ287GUvpUsryoMFtH9zj/ARfWCo3AyXTM= -k8s.io/apiserver v0.26.3 h1:blBpv+yOiozkPH2aqClhJmJY+rp53Tgfac4SKPDJnU4= -k8s.io/apiserver v0.26.3/go.mod h1:CJe/VoQNcXdhm67EvaVjYXxR3QyfwpceKPuPaeLibTA= +k8s.io/apiserver v0.27.1 h1:phY+BtXjjzd+ta3a4kYbomC81azQSLa1K8jo9RBw7Lg= +k8s.io/apiserver v0.27.1/go.mod h1:UGrOjLY2KsieA9Fw6lLiTObxTb8Z1xEba4uqSuMY0WU= k8s.io/cli-runtime v0.26.0 h1:aQHa1SyUhpqxAw1fY21x2z2OS5RLtMJOCj7tN4oq8mw= k8s.io/cli-runtime v0.26.0/go.mod h1:o+4KmwHzO/UK0wepE1qpRk6l3o60/txUZ1fEXWGIKTY= -k8s.io/client-go v0.26.3 h1:k1UY+KXfkxV2ScEL3gilKcF7761xkYsSD6BC9szIu8s= -k8s.io/client-go v0.26.3/go.mod h1:ZPNu9lm8/dbRIPAgteN30RSXea6vrCpFvq+MateTUuQ= -k8s.io/component-base v0.26.3 h1:oC0WMK/ggcbGDTkdcqefI4wIZRYdK3JySx9/HADpV0g= -k8s.io/component-base v0.26.3/go.mod h1:5kj1kZYwSC6ZstHJN7oHBqcJC6yyn41eR+Sqa/mQc8E= +k8s.io/client-go v0.27.1 h1:oXsfhW/qncM1wDmWBIuDzRHNS2tLhK3BZv512Nc59W8= +k8s.io/client-go v0.27.1/go.mod h1:f8LHMUkVb3b9N8bWturc+EDtVVVwZ7ueTVquFAJb2vA= +k8s.io/component-base v0.27.1 h1:kEB8p8lzi4gCs5f2SPU242vOumHJ6EOsOnDM3tTuDTM= +k8s.io/component-base v0.27.1/go.mod h1:UGEd8+gxE4YWoigz5/lb3af3Q24w98pDseXcXZjw+E0= k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw= k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20230308215209-15aac26d736a h1:gmovKNur38vgoWfGtP5QOGNOA7ki4n6qNYoFAgMlNvg= @@ -1360,8 +1362,8 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8 rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/controller-runtime v0.14.5 h1:6xaWFqzT5KuAQ9ufgUaj1G/+C4Y1GRkhrxl+BJ9i+5s= -sigs.k8s.io/controller-runtime v0.14.5/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0= +sigs.k8s.io/controller-runtime v0.13.1-0.20230426175615-dca0be70fd22 h1:KnAZ+ITT57UpjlUM9AsuuPzzF0OjZAhtSgEPeQUHRzg= +sigs.k8s.io/controller-runtime v0.13.1-0.20230426175615-dca0be70fd22/go.mod h1:ujEX5tSkpg5cCOhcwDWLsXwNuMCO+j4rpmmkIn6BGGc= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/kustomize/api v0.12.1 h1:7YM7gW3kYBwtKvoY216ZzY+8hM+lV53LUayghNRJ0vM= From a63ddcbbc908dcfe2f78dcfaa9ab142054b27ae6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 27 Apr 2023 09:44:12 +0200 Subject: [PATCH 1519/2916] chore: Upgrade kustomize --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 4cebb73b0..1c82bd6db 100644 --- a/go.mod +++ b/go.mod @@ -49,7 +49,7 @@ require ( k8s.io/apimachinery v0.27.1 k8s.io/client-go v0.27.1 k8s.io/klog/v2 v2.90.1 - sigs.k8s.io/kustomize/kyaml v0.13.9 + sigs.k8s.io/kustomize/kyaml v0.14.1 sigs.k8s.io/structured-merge-diff/v4 v4.2.3 ) @@ -66,7 +66,7 @@ require ( github.com/otiai10/copy v1.10.0 go.mozilla.org/sops/v3 v3.7.4-0.20220901181616-9124783930b1 sigs.k8s.io/controller-runtime v0.14.5 - sigs.k8s.io/kustomize/api v0.12.1 + sigs.k8s.io/kustomize/api v0.13.2 sigs.k8s.io/yaml v1.3.0 ) @@ -238,7 +238,7 @@ require ( gopkg.in/warnings.v0 v0.1.2 // indirect gotest.tools/v3 v3.2.0 // indirect k8s.io/apiserver v0.27.1 // indirect - k8s.io/cli-runtime v0.26.0 // indirect + k8s.io/cli-runtime v0.27.1 // indirect k8s.io/component-base v0.27.1 // indirect k8s.io/kube-openapi v0.0.0-20230308215209-15aac26d736a // indirect k8s.io/kubectl v0.26.0 // indirect diff --git a/go.sum b/go.sum index 4ae3c05a0..3f52a2aab 100644 --- a/go.sum +++ b/go.sum @@ -1342,8 +1342,8 @@ k8s.io/apimachinery v0.27.1 h1:EGuZiLI95UQQcClhanryclaQE6xjg1Bts6/L3cD7zyc= k8s.io/apimachinery v0.27.1/go.mod h1:5ikh59fK3AJ287GUvpUsryoMFtH9zj/ARfWCo3AyXTM= k8s.io/apiserver v0.27.1 h1:phY+BtXjjzd+ta3a4kYbomC81azQSLa1K8jo9RBw7Lg= k8s.io/apiserver v0.27.1/go.mod h1:UGrOjLY2KsieA9Fw6lLiTObxTb8Z1xEba4uqSuMY0WU= -k8s.io/cli-runtime v0.26.0 h1:aQHa1SyUhpqxAw1fY21x2z2OS5RLtMJOCj7tN4oq8mw= -k8s.io/cli-runtime v0.26.0/go.mod h1:o+4KmwHzO/UK0wepE1qpRk6l3o60/txUZ1fEXWGIKTY= +k8s.io/cli-runtime v0.27.1 h1:MMzp5Q/Xmr5L1Lrowuc+Y/r95XINC6c6/fE3aN7JDRM= +k8s.io/cli-runtime v0.27.1/go.mod h1:tEbTB1XP/nTH3wujsi52bw91gWpErtWiS15R6CwYsAI= k8s.io/client-go v0.27.1 h1:oXsfhW/qncM1wDmWBIuDzRHNS2tLhK3BZv512Nc59W8= k8s.io/client-go v0.27.1/go.mod h1:f8LHMUkVb3b9N8bWturc+EDtVVVwZ7ueTVquFAJb2vA= k8s.io/component-base v0.27.1 h1:kEB8p8lzi4gCs5f2SPU242vOumHJ6EOsOnDM3tTuDTM= @@ -1366,10 +1366,10 @@ sigs.k8s.io/controller-runtime v0.13.1-0.20230426175615-dca0be70fd22 h1:KnAZ+ITT sigs.k8s.io/controller-runtime v0.13.1-0.20230426175615-dca0be70fd22/go.mod h1:ujEX5tSkpg5cCOhcwDWLsXwNuMCO+j4rpmmkIn6BGGc= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/kustomize/api v0.12.1 h1:7YM7gW3kYBwtKvoY216ZzY+8hM+lV53LUayghNRJ0vM= -sigs.k8s.io/kustomize/api v0.12.1/go.mod h1:y3JUhimkZkR6sbLNwfJHxvo1TCLwuwm14sCYnkH6S1s= -sigs.k8s.io/kustomize/kyaml v0.13.9 h1:Qz53EAaFFANyNgyOEJbT/yoIHygK40/ZcvU3rgry2Tk= -sigs.k8s.io/kustomize/kyaml v0.13.9/go.mod h1:QsRbD0/KcU+wdk0/L0fIp2KLnohkVzs6fQ85/nOXac4= +sigs.k8s.io/kustomize/api v0.13.2 h1:kejWfLeJhUsTGioDoFNJET5LQe/ajzXhJGYoU+pJsiA= +sigs.k8s.io/kustomize/api v0.13.2/go.mod h1:DUp325VVMFVcQSq+ZxyDisA8wtldwHxLZbr1g94UHsw= +sigs.k8s.io/kustomize/kyaml v0.14.1 h1:c8iibius7l24G2wVAGZn/Va2wNys03GXLjYVIcFVxKA= +sigs.k8s.io/kustomize/kyaml v0.14.1/go.mod h1:AN1/IpawKilWD7V+YvQwRGUvuUOOWpjsHu6uHwonSF4= sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= From c946a2f2ba7a632a52ce5392806a6b0cea50e95f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 27 Apr 2023 10:03:32 +0200 Subject: [PATCH 1520/2916] chore: Upgrade helm --- go.mod | 32 +++++++++++++----------- go.sum | 78 ++++++++++++++++++++++++++++++++-------------------------- 2 files changed, 61 insertions(+), 49 deletions(-) diff --git a/go.mod b/go.mod index 1c82bd6db..4d68198ed 100644 --- a/go.mod +++ b/go.mod @@ -43,7 +43,7 @@ require ( golang.org/x/text v0.9.0 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 // indirect - helm.sh/helm/v3 v3.11.2 + helm.sh/helm/v3 v3.11.3 k8s.io/api v0.27.1 k8s.io/apiextensions-apiserver v0.27.1 k8s.io/apimachinery v0.27.1 @@ -74,10 +74,11 @@ require ( replace sigs.k8s.io/controller-runtime => sigs.k8s.io/controller-runtime v0.13.1-0.20230426175615-dca0be70fd22 require ( - cloud.google.com/go/compute v1.14.0 // indirect + cloud.google.com/go/compute v1.18.0 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect - cloud.google.com/go/iam v0.8.0 // indirect - cloud.google.com/go/kms v1.6.0 // indirect + cloud.google.com/go/iam v0.12.0 // indirect + cloud.google.com/go/kms v1.9.0 // indirect + github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 // indirect github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 // indirect @@ -111,11 +112,11 @@ require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect github.com/cloudflare/circl v1.3.0 // indirect - github.com/containerd/containerd v1.6.15 // indirect + github.com/containerd/containerd v1.7.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/docker/cli v20.10.21+incompatible // indirect - github.com/docker/docker v20.10.21+incompatible // indirect + github.com/docker/docker v20.10.24+incompatible // indirect github.com/docker/docker-credential-helpers v0.7.0 // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-metrics v0.0.1 // indirect @@ -133,6 +134,7 @@ require ( github.com/go-git/gcfg v1.5.0 // indirect github.com/go-git/go-billy/v5 v5.4.1 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.1 // indirect github.com/go-openapi/swag v0.22.3 // indirect @@ -148,7 +150,7 @@ require ( github.com/google/gofuzz v1.2.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.3.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.2.1 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect github.com/googleapis/gax-go/v2 v2.7.0 // indirect github.com/gorilla/mux v1.8.0 // indirect github.com/gosuri/uitable v0.0.4 // indirect @@ -168,7 +170,7 @@ require ( github.com/jmoiron/sqlx v1.3.5 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.15.15 // indirect + github.com/klauspost/compress v1.16.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect @@ -191,7 +193,7 @@ require ( github.com/morikuni/aec v1.0.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.0-rc2 // indirect + github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect github.com/pelletier/go-toml/v2 v2.0.6 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect @@ -212,7 +214,7 @@ require ( github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/subosito/gotenv v1.4.2 // indirect - github.com/urfave/cli v1.22.7 // indirect + github.com/urfave/cli v1.22.12 // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect @@ -220,6 +222,8 @@ require ( github.com/xlab/treeprint v1.1.0 // indirect go.mozilla.org/gopgagent v0.0.0-20170926210634-4d7ea76ff71a // indirect go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/otel v1.14.0 // indirect + go.opentelemetry.io/otel/trace v1.14.0 // indirect go.starlark.net v0.0.0-20221205180719-3fd0dac74452 // indirect go.uber.org/atomic v1.10.0 // indirect golang.org/x/mod v0.9.0 // indirect @@ -227,10 +231,10 @@ require ( golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.7.0 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect - google.golang.org/api v0.107.0 // indirect + google.golang.org/api v0.110.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef // indirect - google.golang.org/grpc v1.52.0 // indirect + google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect + google.golang.org/grpc v1.53.0 // indirect google.golang.org/protobuf v1.30.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect @@ -242,7 +246,7 @@ require ( k8s.io/component-base v0.27.1 // indirect k8s.io/kube-openapi v0.0.0-20230308215209-15aac26d736a // indirect k8s.io/kubectl v0.26.0 // indirect - k8s.io/utils v0.0.0-20230209194617-a36077c30491 // indirect + k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 // indirect oras.land/oras-go v1.2.2 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect ) diff --git a/go.sum b/go.sum index 3f52a2aab..b7133dd0c 100644 --- a/go.sum +++ b/go.sum @@ -20,25 +20,25 @@ cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPT cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= -cloud.google.com/go v0.105.0 h1:DNtEKRBAAzeS4KyIory52wWHuClNaXJ5x1F7xa4q+5Y= +cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute v1.14.0 h1:hfm2+FfxVmnRlh6LpB7cg1ZNU+5edAHmW679JePztk0= -cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= +cloud.google.com/go/compute v1.18.0 h1:FEigFqoDbys2cvFkZ9Fjq4gnHBP55anJ0yQyau2f9oY= +cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= -cloud.google.com/go/iam v0.8.0 h1:E2osAkZzxI/+8pZcxVLcDtAQx/u+hZXVryUaYQ5O0Kk= -cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= -cloud.google.com/go/kms v1.6.0 h1:OWRZzrPmOZUzurjI2FBGtgY2mB1WaJkqhw6oIwSj0Yg= -cloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0= -cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs= +cloud.google.com/go/iam v0.12.0 h1:DRtTY29b75ciH6Ov1PHb4/iat2CLCvrOm40Q0a6DFpE= +cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY= +cloud.google.com/go/kms v1.9.0 h1:b0votJQa/9DSsxgHwN33/tTLA7ZHVzfWhDCrfiXijSo= +cloud.google.com/go/kms v1.9.0/go.mod h1:qb1tPTgfF9RQP8e1wq4cLFErVuTJv7UsSC915J8dh3w= +cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -52,6 +52,8 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= filippo.io/age v1.1.1 h1:pIpO7l151hCnQ4BdyBujnGP2YlUo0uj6sAVNHGBvXHg= filippo.io/age v1.1.1/go.mod h1:l03SrzDUrBkdBx8+IILdnn2KZysqQdbEBUQ4p3sqEQE= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 h1:EKPd1INOIyr5hWOWhvpmQpY6tKjeG0hT1s3AMC/9fic= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1/go.mod h1:VzwV+t+dZ9j/H867F1M2ziD+yLHtB46oM35FxxMJ4d0= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.1 h1:tz19qLF65vuu2ibfTqGVJxG/zZAI27NEIIbvAOQwYbw= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.1/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0 h1:QkAcEIAKbNL4KoFr4SathZPhDhF4mVwpBMFlYjyAqy8= @@ -89,7 +91,7 @@ github.com/Masterminds/squirrel v1.5.3/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA4 github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= -github.com/Microsoft/hcsshim v0.9.6 h1:VwnDOgLeoi2du6dAznfmspNqTiwczvjv4K7NxuY9jsY= +github.com/Microsoft/hcsshim v0.10.0-rc.7 h1:HBytQPxcv8Oy4244zbQbe6hnOnx544eL5QPUqhJldz8= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA= @@ -182,9 +184,9 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/containerd/cgroups v1.0.4 h1:jN/mbWBEaz+T1pi5OFtnkQ+8qnmEbAr1Oo1FRm5B0dA= -github.com/containerd/containerd v1.6.15 h1:4wWexxzLNHNE46aIETc6ge4TofO550v+BlLoANrbses= -github.com/containerd/containerd v1.6.15/go.mod h1:U2NnBPIhzJDm59xF7xB2MMHnKtggpZ+phKg8o2TKj2c= +github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= +github.com/containerd/containerd v1.7.0 h1:G/ZQr3gMZs6ZT0qPUZ15znx5QSdQdASW11nXTLTM2Pg= +github.com/containerd/containerd v1.7.0/go.mod h1:QfR7Efgb/6X2BDpTPJRvPTYDE9rsF0FsXX9J8sIs/sc= github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= github.com/containerd/stargz-snapshotter/estargz v0.12.1 h1:+7nYmHJb0tEkcRaAW+MHqoKaJYZmkikupxCqVtmPuY0= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= @@ -194,7 +196,6 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= @@ -214,8 +215,8 @@ github.com/docker/cli v20.10.21+incompatible h1:qVkgyYUnOLQ98LtXBrwd/duVqPT2X4SH github.com/docker/cli v20.10.21+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v20.10.21+incompatible h1:UTLdBmHk3bEY+w8qeO5KttOhy6OmXWsl/FEet9Uswog= -github.com/docker/docker v20.10.21+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v20.10.24+incompatible h1:Ugvxm7a8+Gz6vqQYQQ2W7GYq5EUPaAiuPgIfVyI3dYE= +github.com/docker/docker v20.10.24+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= @@ -289,8 +290,11 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= @@ -414,8 +418,8 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.2.1 h1:RY7tHKZcRlk788d5WSo/e83gOyyy742E8GSs771ySpg= -github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= +github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ= @@ -527,8 +531,8 @@ github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= -github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= +github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= +github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/kluctl/go-embed-python v0.0.0-3.10.9-20230206-2 h1:K96SwIr8MzBQ3kFFz2H/pA2y+EEk04vZ4fWj/YZghBU= github.com/kluctl/go-embed-python v0.0.0-3.10.9-20230206-2/go.mod h1:vzngsPshNKUtq0gxkYQKNJafrcH7Qy7Qt6yGNt7JmQI= github.com/kluctl/go-jinja2 v0.0.0-20230310104535-8136715a1e5a h1:48Mz5ZZmv64Pg/cKR4nIo3Ry49Hqs5YggLPGju0e8PU= @@ -627,7 +631,7 @@ github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= -github.com/moby/sys/mountinfo v0.5.0 h1:2Ks8/r6lopsxWi9m58nlwjaeSzUX9iiL1vj5qB/9ObI= +github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA= github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -657,9 +661,9 @@ github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034= -github.com/opencontainers/image-spec v1.1.0-rc2/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= -github.com/opencontainers/runc v1.1.2 h1:2VSZwLx5k/BfsBxMMipG/LYUnmqOD/BPkIVgQUcTlLw= +github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b h1:YWuSjZCQAPM8UUBLkYUk1e+rZcvWHJmFb6i6rM44Xs8= +github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= +github.com/opencontainers/runc v1.1.4 h1:nRCz/8sKg6K6jgYAFLDlXzPeITBZJyX28DBVhWD+5dg= github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= github.com/otiai10/copy v1.10.0 h1:znyI7l134wNg/wDktoVQPxPkgvhDfGCYUasey+h0rDQ= github.com/otiai10/copy v1.10.0/go.mod h1:rSaLseMUsZFFbsFGc7wCJnnkTAvdc5L6VWxPE4308Ww= @@ -802,8 +806,8 @@ github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8 github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -github.com/urfave/cli v1.22.7 h1:aXiFAgRugfJ27UFDsGJ9DB2FvTC73hlVXFSqq5bo9eU= -github.com/urfave/cli v1.22.7/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli v1.22.12 h1:igJgVw1JdKH+trcLWLeLwZjU9fEfPesQ+9/e4MQ44S8= +github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8= github.com/vbatts/tar-split v0.11.2 h1:Via6XqJr0hceW4wff3QRzD5gAk/tatMw/4ZA7cTlIME= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= @@ -847,6 +851,10 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/otel v1.14.0 h1:/79Huy8wbf5DnIPhemGB+zEPVwnN6fuQybr/SRXa6hM= +go.opentelemetry.io/otel v1.14.0/go.mod h1:o4buv+dJzx8rohcUeRmWUZhqupFvzWis188WlggnNeU= +go.opentelemetry.io/otel/trace v1.14.0 h1:wp2Mmvj41tDsyAJXiWDWpfNsOiIyd38fy85pyKcFq/M= +go.opentelemetry.io/otel/trace v1.14.0/go.mod h1:8avnQLK+CG77yNLUae4ea2JDQ6iT+gozhnZjy/rw9G8= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.starlark.net v0.0.0-20221205180719-3fd0dac74452 h1:JZtNuL6LPB+scU5yaQ6hqRlJFRiddZm2FwRt2AQqtHA= go.starlark.net v0.0.0-20221205180719-3fd0dac74452/go.mod h1:kIVgS18CjmEC3PqMd5kaJSGEifyV/CeB9x506ZJ1Vbk= @@ -1195,8 +1203,8 @@ google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjR google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= -google.golang.org/api v0.107.0 h1:I2SlFjD8ZWabaIFOfeEDg3pf0BHJDh6iYQ1ic3Yu/UU= -google.golang.org/api v0.107.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.110.0 h1:l+rh0KYUooe9JGbGVx71tbFo4SMbMTXK3I3ia2QSEeU= +google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1249,8 +1257,8 @@ google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef h1:uQ2vjV/sHTsWSqdKeLqmwitzgvjMl7o4IdtHwUDXSJY= -google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA= +google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1273,8 +1281,8 @@ google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.52.0 h1:kd48UiU7EHsV4rnLyOJRuP/Il/UHE7gdDAQ+SZI7nZk= -google.golang.org/grpc v1.52.0/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= +google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc= +google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1325,8 +1333,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.2.0 h1:I0DwBVMGAx26dttAj1BtJLAkVGncrkkUXfJLC4Flt/I= gotest.tools/v3 v3.2.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= -helm.sh/helm/v3 v3.11.2 h1:P3cLaFxfoxaGLGJVnoPrhf1j86LC5EDINSpYSpMUkkA= -helm.sh/helm/v3 v3.11.2/go.mod h1:Hw+09mfpDiRRKAgAIZlFkPSeOkvv7Acl5McBvQyNPVw= +helm.sh/helm/v3 v3.11.3 h1:n1X5yaQTP5DYywlBOZMl2gX398Gp6YwFp/IAVj6+5D4= +helm.sh/helm/v3 v3.11.3/go.mod h1:S+sOdQc3BLvt09a9rSlKKVs9x0N/yx+No0y3qFw+FQ8= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1354,8 +1362,8 @@ k8s.io/kube-openapi v0.0.0-20230308215209-15aac26d736a h1:gmovKNur38vgoWfGtP5QOG k8s.io/kube-openapi v0.0.0-20230308215209-15aac26d736a/go.mod h1:y5VtZWM9sHHc2ZodIH/6SHzXj+TPU5USoA8lcIeKEKY= k8s.io/kubectl v0.26.0 h1:xmrzoKR9CyNdzxBmXV7jW9Ln8WMrwRK6hGbbf69o4T0= k8s.io/kubectl v0.26.0/go.mod h1:eInP0b+U9XUJWSYeU9XZnTA+cVYuWyl3iYPGtru0qhQ= -k8s.io/utils v0.0.0-20230209194617-a36077c30491 h1:r0BAOLElQnnFhE/ApUsg3iHdVYYPBjNSSOMowRZxxsY= -k8s.io/utils v0.0.0-20230209194617-a36077c30491/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 h1:kmDqav+P+/5e1i9tFfHq1qcF3sOrDp+YEkVDAHu7Jwk= +k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= oras.land/oras-go v1.2.2 h1:0E9tOHUfrNH7TCDk5KU0jVBEzCqbfdyuVfGmJ7ZeRPE= oras.land/oras-go v1.2.2/go.mod h1:Apa81sKoZPpP7CDciE006tSZ0x3Q3+dOoBcMZ/aNxvw= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= From 5adbddfc4caa641d03308464bc92ede38c5fda65 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 27 Apr 2023 10:04:55 +0200 Subject: [PATCH 1521/2916] refactor: Simplify RemoveDuplicateFields --- go.mod | 4 ++-- pkg/yaml/yaml.go | 41 ++++++++++++++--------------------------- 2 files changed, 16 insertions(+), 29 deletions(-) diff --git a/go.mod b/go.mod index 4d68198ed..37d242d34 100644 --- a/go.mod +++ b/go.mod @@ -41,8 +41,6 @@ require ( golang.org/x/sys v0.7.0 golang.org/x/term v0.6.0 golang.org/x/text v0.9.0 - gopkg.in/yaml.v2 v2.4.0 - gopkg.in/yaml.v3 v3.0.1 // indirect helm.sh/helm/v3 v3.11.3 k8s.io/api v0.27.1 k8s.io/apiextensions-apiserver v0.27.1 @@ -240,6 +238,8 @@ require ( gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.2.0 // indirect k8s.io/apiserver v0.27.1 // indirect k8s.io/cli-runtime v0.27.1 // indirect diff --git a/pkg/yaml/yaml.go b/pkg/yaml/yaml.go index 95bfb5a33..b28c789eb 100644 --- a/pkg/yaml/yaml.go +++ b/pkg/yaml/yaml.go @@ -3,12 +3,10 @@ package yaml import ( "bytes" "encoding/json" - "errors" "fmt" "github.com/kluctl/kluctl/v2/pkg/utils" "golang.org/x/text/encoding/unicode" "golang.org/x/text/transform" - yaml2 "gopkg.in/yaml.v2" "io" "os" "path/filepath" @@ -81,6 +79,10 @@ func ReadYamlAllBytes(b []byte) ([]interface{}, error) { var docsSep = regexp.MustCompile("(?:^|\\s*\n)---\\s*") func ReadYamlAllStream(r io.Reader) ([]interface{}, error) { + return readYamlAllStream(r, true) +} + +func readYamlAllStream(r io.Reader, strict bool) ([]interface{}, error) { r = newUnicodeReader(r) b, err := io.ReadAll(r) @@ -103,7 +105,11 @@ func ReadYamlAllStream(r io.Reader) ([]interface{}, error) { } var x any - err = yaml.UnmarshalStrict([]byte(doc), &x) + if strict { + err = yaml.UnmarshalStrict([]byte(doc), &x) + } else { + err = yaml.Unmarshal([]byte(doc), &x) + } if err != nil { return nil, err } @@ -188,32 +194,13 @@ func WriteJsonString(o interface{}) (string, error) { } // RemoveDuplicateFields is a helper/hack to remove duplicate fields from yaml maps/structs. The yaml spec explicitly -// forbids duplicate keys, but yaml.v2 ignored those by default, leading to some tools (e.g. Helm) ignoring these. This -// forces us to also ignore/remove them in some cases. We do this by loading the yaml via yaml.v2 and then writing them -// back to a string which can then be parsed by yaml.v3 -// TODO Remove this helper method when https://github.com/go-yaml/yaml/issues/751 is implemented +// forbids duplicate keys, but yaml.v2 ignored those by default, leading to some tools (e.g. Helm) ignoring these func RemoveDuplicateFields(r io.Reader) ([]byte, error) { - buf := bytes.NewBuffer(nil) - d := yaml2.NewDecoder(r) - e := yaml2.NewEncoder(buf) - - for { - var o interface{} - err := d.Decode(&o) - if err != nil { - if errors.Is(err, io.EOF) { - break - } - return nil, err - } - if o != nil { - err = e.Encode(o) - if err != nil { - return nil, err - } - } + docs, err := readYamlAllStream(r, false) + if err != nil { + return nil, err } - return buf.Bytes(), nil + return WriteYamlAllBytes(docs) } func FixNameExt(dir string, name string) string { From 7eded7c74e93c0d380eaf9e1238154f6ec3122a6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 27 Apr 2023 10:17:35 +0200 Subject: [PATCH 1522/2916] chore: Upgrade go-containerregistry --- go.mod | 11 +++++------ go.sum | 26 +++++++++++++------------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/go.mod b/go.mod index 37d242d34..fa099ed8a 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/go-playground/validator/v10 v10.12.0 github.com/gobwas/glob v0.2.3 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 - github.com/google/go-containerregistry v0.13.0 + github.com/google/go-containerregistry v0.14.0 github.com/hashicorp/vault/api v1.9.0 github.com/hexops/gotextdiff v1.0.3 github.com/imdario/mergo v0.3.15 @@ -113,8 +113,8 @@ require ( github.com/containerd/containerd v1.7.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/docker/cli v20.10.21+incompatible // indirect - github.com/docker/docker v20.10.24+incompatible // indirect + github.com/docker/cli v23.0.1+incompatible // indirect + github.com/docker/docker v23.0.1+incompatible // indirect github.com/docker/docker-credential-helpers v0.7.0 // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-metrics v0.0.1 // indirect @@ -225,7 +225,7 @@ require ( go.starlark.net v0.0.0-20221205180719-3fd0dac74452 // indirect go.uber.org/atomic v1.10.0 // indirect golang.org/x/mod v0.9.0 // indirect - golang.org/x/oauth2 v0.5.0 // indirect + golang.org/x/oauth2 v0.6.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.7.0 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect @@ -240,13 +240,12 @@ require ( gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - gotest.tools/v3 v3.2.0 // indirect k8s.io/apiserver v0.27.1 // indirect k8s.io/cli-runtime v0.27.1 // indirect k8s.io/component-base v0.27.1 // indirect k8s.io/kube-openapi v0.0.0-20230308215209-15aac26d736a // indirect k8s.io/kubectl v0.26.0 // indirect k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 // indirect - oras.land/oras-go v1.2.2 // indirect + oras.land/oras-go v1.2.3 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect ) diff --git a/go.sum b/go.sum index b7133dd0c..df3bf349c 100644 --- a/go.sum +++ b/go.sum @@ -188,7 +188,7 @@ github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaD github.com/containerd/containerd v1.7.0 h1:G/ZQr3gMZs6ZT0qPUZ15znx5QSdQdASW11nXTLTM2Pg= github.com/containerd/containerd v1.7.0/go.mod h1:QfR7Efgb/6X2BDpTPJRvPTYDE9rsF0FsXX9J8sIs/sc= github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= -github.com/containerd/stargz-snapshotter/estargz v0.12.1 h1:+7nYmHJb0tEkcRaAW+MHqoKaJYZmkikupxCqVtmPuY0= +github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -211,12 +211,12 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 h1:aBfCb7iqHmDEIp6fBvC/hQUddQfg+3qdYjwzaiP9Hnc= github.com/dnaeon/go-vcr v1.1.0 h1:ReYa/UBrRyQdant9B4fNHGoCNKw6qh6P0fsdGmZpR7c= -github.com/docker/cli v20.10.21+incompatible h1:qVkgyYUnOLQ98LtXBrwd/duVqPT2X4SHndOuGsfwyhU= -github.com/docker/cli v20.10.21+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v23.0.1+incompatible h1:LRyWITpGzl2C9e9uGxzisptnxAn1zfZKXy13Ul2Q5oM= +github.com/docker/cli v23.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v20.10.24+incompatible h1:Ugvxm7a8+Gz6vqQYQQ2W7GYq5EUPaAiuPgIfVyI3dYE= -github.com/docker/docker v20.10.24+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v23.0.1+incompatible h1:vjgvJZxprTTE1A37nm+CLNAdwu6xZekyoiVlUZEINcY= +github.com/docker/docker v23.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= @@ -390,8 +390,8 @@ github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8 github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-containerregistry v0.13.0 h1:y1C7Z3e149OJbOPDBxLYR8ITPz8dTKqQwjErKVHJC8k= -github.com/google/go-containerregistry v0.13.0/go.mod h1:J9FQ+eSS4a1aC2GNZxvNpbWhgp0487v+cgiilB4FqDo= +github.com/google/go-containerregistry v0.14.0 h1:z58vMqHxuwvAsVwvKEkmVBz2TlgBgH5k6koEXBtlYkw= +github.com/google/go-containerregistry v0.14.0/go.mod h1:aiJ2fp/SXvkWgmYHioXnbMdlgB8eXiiYOY55gfN91Wk= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -995,8 +995,8 @@ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s= -golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= +golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw= +golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1331,8 +1331,8 @@ gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.2.0 h1:I0DwBVMGAx26dttAj1BtJLAkVGncrkkUXfJLC4Flt/I= -gotest.tools/v3 v3.2.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= helm.sh/helm/v3 v3.11.3 h1:n1X5yaQTP5DYywlBOZMl2gX398Gp6YwFp/IAVj6+5D4= helm.sh/helm/v3 v3.11.3/go.mod h1:S+sOdQc3BLvt09a9rSlKKVs9x0N/yx+No0y3qFw+FQ8= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1364,8 +1364,8 @@ k8s.io/kubectl v0.26.0 h1:xmrzoKR9CyNdzxBmXV7jW9Ln8WMrwRK6hGbbf69o4T0= k8s.io/kubectl v0.26.0/go.mod h1:eInP0b+U9XUJWSYeU9XZnTA+cVYuWyl3iYPGtru0qhQ= k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 h1:kmDqav+P+/5e1i9tFfHq1qcF3sOrDp+YEkVDAHu7Jwk= k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -oras.land/oras-go v1.2.2 h1:0E9tOHUfrNH7TCDk5KU0jVBEzCqbfdyuVfGmJ7ZeRPE= -oras.land/oras-go v1.2.2/go.mod h1:Apa81sKoZPpP7CDciE006tSZ0x3Q3+dOoBcMZ/aNxvw= +oras.land/oras-go v1.2.3 h1:v8PJl+gEAntI1pJ/LCrDgsuk+1PKVavVEPsYIHFE5uY= +oras.land/oras-go v1.2.3/go.mod h1:M/uaPdYklze0Vf3AakfarnpoEckvw0ESbRdN8Z1vdJg= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= From 1012a959c32c9e5d12f4c2f75f8c5fbb6dd56aab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 28 Apr 2023 09:16:15 +0200 Subject: [PATCH 1523/2916] chore(deps): Bump golang.org/x/term from 0.6.0 to 0.7.0 (#431) Bumps [golang.org/x/term](https://github.com/golang/term) from 0.6.0 to 0.7.0. - [Release notes](https://github.com/golang/term/releases) - [Commits](https://github.com/golang/term/compare/v0.6.0...v0.7.0) --- updated-dependencies: - dependency-name: golang.org/x/term dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index fa099ed8a..3cdeda2d3 100644 --- a/go.mod +++ b/go.mod @@ -39,7 +39,7 @@ require ( golang.org/x/net v0.8.0 golang.org/x/sync v0.1.0 golang.org/x/sys v0.7.0 - golang.org/x/term v0.6.0 + golang.org/x/term v0.7.0 golang.org/x/text v0.9.0 helm.sh/helm/v3 v3.11.3 k8s.io/api v0.27.1 diff --git a/go.sum b/go.sum index df3bf349c..c7a8f30d2 100644 --- a/go.sum +++ b/go.sum @@ -1095,8 +1095,8 @@ golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From caf94eaefa0d43109b195f453ce65edfd4081776 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 28 Apr 2023 09:16:29 +0200 Subject: [PATCH 1524/2916] chore(deps): Bump golang.org/x/net from 0.8.0 to 0.9.0 (#432) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.8.0 to 0.9.0. - [Release notes](https://github.com/golang/net/releases) - [Commits](https://github.com/golang/net/compare/v0.8.0...v0.9.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 3cdeda2d3..801ac99ae 100644 --- a/go.mod +++ b/go.mod @@ -36,7 +36,7 @@ require ( github.com/stretchr/testify v1.8.2 github.com/xanzy/ssh-agent v0.3.3 golang.org/x/crypto v0.7.0 - golang.org/x/net v0.8.0 + golang.org/x/net v0.9.0 golang.org/x/sync v0.1.0 golang.org/x/sys v0.7.0 golang.org/x/term v0.7.0 diff --git a/go.sum b/go.sum index c7a8f30d2..a436a6384 100644 --- a/go.sum +++ b/go.sum @@ -981,8 +981,8 @@ golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= From bb95b8269fcd5b191e5561fdc831d3dfa9daa316 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 28 Apr 2023 09:24:24 +0200 Subject: [PATCH 1525/2916] chore(deps): Bump github.com/otiai10/copy from 1.10.0 to 1.11.0 (#433) Bumps [github.com/otiai10/copy](https://github.com/otiai10/copy) from 1.10.0 to 1.11.0. - [Release notes](https://github.com/otiai10/copy/releases) - [Commits](https://github.com/otiai10/copy/compare/v1.10.0...v1.11.0) --- updated-dependencies: - dependency-name: github.com/otiai10/copy dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 801ac99ae..43dd48a8f 100644 --- a/go.mod +++ b/go.mod @@ -61,7 +61,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 github.com/huandu/xstrings v1.4.0 github.com/onsi/gomega v1.27.6 - github.com/otiai10/copy v1.10.0 + github.com/otiai10/copy v1.11.0 go.mozilla.org/sops/v3 v3.7.4-0.20220901181616-9124783930b1 sigs.k8s.io/controller-runtime v0.14.5 sigs.k8s.io/kustomize/api v0.13.2 diff --git a/go.sum b/go.sum index a436a6384..130804335 100644 --- a/go.sum +++ b/go.sum @@ -665,8 +665,8 @@ github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b h1 github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= github.com/opencontainers/runc v1.1.4 h1:nRCz/8sKg6K6jgYAFLDlXzPeITBZJyX28DBVhWD+5dg= github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= -github.com/otiai10/copy v1.10.0 h1:znyI7l134wNg/wDktoVQPxPkgvhDfGCYUasey+h0rDQ= -github.com/otiai10/copy v1.10.0/go.mod h1:rSaLseMUsZFFbsFGc7wCJnnkTAvdc5L6VWxPE4308Ww= +github.com/otiai10/copy v1.11.0 h1:OKBD80J/mLBrwnzXqGtFCzprFSGioo30JcmR4APsNwc= +github.com/otiai10/copy v1.11.0/go.mod h1:rSaLseMUsZFFbsFGc7wCJnnkTAvdc5L6VWxPE4308Ww= github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= From 080ac65dd5cddd54effcc3ed40f621038ac76ba4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 28 Apr 2023 09:24:37 +0200 Subject: [PATCH 1526/2916] chore(deps): Bump github.com/aws/aws-sdk-go-v2/config (#434) Bumps [github.com/aws/aws-sdk-go-v2/config](https://github.com/aws/aws-sdk-go-v2) from 1.18.18 to 1.18.22. - [Release notes](https://github.com/aws/aws-sdk-go-v2/releases) - [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go-v2/compare/config/v1.18.18...config/v1.18.22) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go-v2/config dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 22 +++++++++++----------- go.sum | 41 ++++++++++++++++++++++------------------- 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/go.mod b/go.mod index 43dd48a8f..a88d7e4f2 100644 --- a/go.mod +++ b/go.mod @@ -53,8 +53,8 @@ require ( require ( filippo.io/age v1.1.1 - github.com/aws/aws-sdk-go-v2 v1.17.6 - github.com/aws/aws-sdk-go-v2/config v1.18.18 + github.com/aws/aws-sdk-go-v2 v1.18.0 + github.com/aws/aws-sdk-go-v2/config v1.18.22 github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.0 github.com/go-git/go-git/v5 v5.6.1 github.com/go-logr/logr v1.2.4 @@ -93,16 +93,16 @@ require ( github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect github.com/acomagu/bufpipe v1.0.4 // indirect github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.13.17 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.0 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.30 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.24 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.3.31 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.24 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.13.21 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.3.34 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27 // indirect github.com/aws/aws-sdk-go-v2/service/kms v1.17.5 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.12.5 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.5 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.18.6 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.12.9 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.9 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.18.10 // indirect github.com/aws/smithy-go v1.13.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver v3.5.1+incompatible // indirect diff --git a/go.sum b/go.sum index 130804335..8a19761c3 100644 --- a/go.sum +++ b/go.sum @@ -117,34 +117,37 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkY github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go-v2 v1.16.7/go.mod h1:6CpKuLXg2w7If3ABZCl/qZ6rEgwtjZTn4eAf4RcEyuw= -github.com/aws/aws-sdk-go-v2 v1.17.6 h1:Y773UK7OBqhzi5VDXMi1zVGsoj+CVHs2eaC2bDsLwi0= github.com/aws/aws-sdk-go-v2 v1.17.6/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= -github.com/aws/aws-sdk-go-v2/config v1.18.18 h1:/ePABXvXl3ESlzUGnkkvvNnRFw3Gh13dyqaq0Qo3JcU= -github.com/aws/aws-sdk-go-v2/config v1.18.18/go.mod h1:Lj3E7XcxJnxMa+AYo89YiL68s1cFJRGduChynYU67VA= -github.com/aws/aws-sdk-go-v2/credentials v1.13.17 h1:IubQO/RNeIVKF5Jy77w/LfUvmmCxTnk2TP1UZZIMiF4= -github.com/aws/aws-sdk-go-v2/credentials v1.13.17/go.mod h1:K9xeFo1g/YPMguMUD69YpwB4Nyi6W/5wn706xIInJFg= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.0 h1:/2Cb3SK3xVOQA7Xfr5nCWCo5H3UiNINtsVvVdk8sQqA= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.0/go.mod h1:neYVaeKr5eT7BzwULuG2YbLhzWZ22lpjKdCybR7AXrQ= +github.com/aws/aws-sdk-go-v2 v1.18.0 h1:882kkTpSFhdgYRKVZ/VCgf7sd0ru57p2JCxz4/oN5RY= +github.com/aws/aws-sdk-go-v2 v1.18.0/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= +github.com/aws/aws-sdk-go-v2/config v1.18.22 h1:7vkUEmjjv+giht4wIROqLs+49VWmiQMMHSduxmoNKLU= +github.com/aws/aws-sdk-go-v2/config v1.18.22/go.mod h1:mN7Li1wxaPxSSy4Xkr6stFuinJGf3VZW3ZSNvO0q6sI= +github.com/aws/aws-sdk-go-v2/credentials v1.13.21 h1:VRiXnPEaaPeGeoFcXvMZOB5K/yfIXOYE3q97Kgb0zbU= +github.com/aws/aws-sdk-go-v2/credentials v1.13.21/go.mod h1:90Dk1lJoMyspa/EDUrldTxsPns0wn6+KpRKpdAWc0uA= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3 h1:jJPgroehGvjrde3XufFIJUZVK5A2L9a3KwSFgKy9n8w= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3/go.mod h1:4Q0UFP0YJf0NrsEuEYHpM9fTSEVnD16Z3uyEF7J9JGM= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.14/go.mod h1:kdjrMwHwrC3+FsKhNcCMJ7tUVj/8uSD5CZXeQ4wV6fM= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.30 h1:y+8n9AGDjikyXoMBTRaHHHSaFEB8267ykmvyPodJfys= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.30/go.mod h1:LUBAO3zNXQjoONBKn/kR1y0Q4cj/D02Ts0uHYjcCQLM= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33 h1:kG5eQilShqmJbv11XL1VpyDbaEJzWxd4zRiCG30GSn4= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33/go.mod h1:7i0PF1ME/2eUPFcjkVIwq+DOygHEoK92t5cDqNgYbIw= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.8/go.mod h1:ZIV8GYoC6WLBW5KGs+o4rsc65/ozd+eQ0L31XF5VDwk= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.24 h1:r+Kv+SEJquhAZXaJ7G4u44cIwXV3f8K+N482NNAzJZA= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.24/go.mod h1:gAuCezX/gob6BSMbItsSlMb6WZGV7K2+fWOvk8xBSto= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.31 h1:hf+Vhp5WtTdcSdE+yEcUz8L73sAzN0R+0jQv+Z51/mI= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.31/go.mod h1:5zUjguZfG5qjhG9/wqmuyHRyUftl2B5Cp6NNxNC6kRA= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.24 h1:c5qGfdbCHav6viBwiyDns3OXqhqAbGjfIB4uVu2ayhk= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.24/go.mod h1:HMA4FZG6fyib+NDo5bpIxX1EhYjrAOveZJY2YR0xrNE= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27 h1:vFQlirhuM8lLlpI7imKOMsjdQLuN9CPi+k44F/OFVsk= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27/go.mod h1:UrHnn3QV/d0pBZ6QBAEQcqFLf8FAzLmoUfPVIueOvoM= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.34 h1:gGLG7yKaXG02/jBlg210R7VgQIotiQntNhsCFejawx8= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.34/go.mod h1:Etz2dj6UHYuw+Xw830KfzCfWGMzqvUTCjUj5b76GVDc= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27 h1:0iKliEXAcCa2qVtRs7Ot5hItA2MsufrphbRFlz1Owxo= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27/go.mod h1:EOwBD4J4S5qYszS5/3DpkejfuK+Z5/1uzICfPaZLtqw= github.com/aws/aws-sdk-go-v2/service/kms v1.17.5 h1:DkubF+BSEy0uX59+pYySzWFReN3fCcXobIO8L5Phh24= github.com/aws/aws-sdk-go-v2/service/kms v1.17.5/go.mod h1:ubAtMGRUMVv5kX8lpbeDguxZ64pR4kXTGApY4sCM0io= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.0 h1:B4LvuBxrxh2WXakqwJL22EPAWgqGGK9/E4YQV/IIkYo= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.0/go.mod h1:XF4Gbmcn6V9xIIm6lhwtyX1NXConNJ8x6yizt2Ejx/0= -github.com/aws/aws-sdk-go-v2/service/sso v1.12.5 h1:bdKIX6SVF3nc3xJFw6Nf0igzS6Ff/louGq8Z6VP/3Hs= -github.com/aws/aws-sdk-go-v2/service/sso v1.12.5/go.mod h1:vuWiaDB30M/QTC+lI3Wj6S/zb7tpUK2MSYgy3Guh2L0= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.5 h1:xLPZMyuZ4GuqRCIec/zWuIhRFPXh2UOJdLXBSi64ZWQ= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.5/go.mod h1:QjxpHmCwAg0ESGtPQnLIVp7SedTOBMYy+Slr3IfMKeI= -github.com/aws/aws-sdk-go-v2/service/sts v1.18.6 h1:rIFn5J3yDoeuKCE9sESXqM5POTAhOP1du3bv/qTL+tE= -github.com/aws/aws-sdk-go-v2/service/sts v1.18.6/go.mod h1:48WJ9l3dwP0GSHWGc5sFGGlCkuA82Mc2xnw+T6Q8aDw= +github.com/aws/aws-sdk-go-v2/service/sso v1.12.9 h1:GAiaQWuQhQQui76KjuXeShmyXqECwQ0mGRMc/rwsL+c= +github.com/aws/aws-sdk-go-v2/service/sso v1.12.9/go.mod h1:ouy2P4z6sJN70fR3ka3wD3Ro3KezSxU6eKGQI2+2fjI= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.9 h1:TraLwncRJkWqtIBVKI/UqBymq4+hL+3MzUOtUATuzkA= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.9/go.mod h1:AFvkxc8xfBe8XA+5St5XIHHrQQtkxqrRincx4hmMHOk= +github.com/aws/aws-sdk-go-v2/service/sts v1.18.10 h1:6UbNM/KJhMBfOI5+lpVcJ/8OA7cBSz0O6OX37SRKlSw= +github.com/aws/aws-sdk-go-v2/service/sts v1.18.10/go.mod h1:BgQOMsg8av8jset59jelyPW7NoZcZXLVpDsXunGDrk8= github.com/aws/smithy-go v1.12.0/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8= github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= From 126666d54b700c660eb451fd17172218c81d0483 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 28 Apr 2023 09:25:08 +0200 Subject: [PATCH 1527/2916] fix: Add support for all known default SSH keys (#428) This includes: - ~/.ssh/id_rsa - ~/.ssh/id_ecdsa - ~/.ssh/id_ed25519 - ~/.ssh/id_xmss - ~/.ssh/id_dsa --- pkg/git/auth/ssh_auth_provider.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/pkg/git/auth/ssh_auth_provider.go b/pkg/git/auth/ssh_auth_provider.go index c6911e846..313e342ef 100644 --- a/pkg/git/auth/ssh_auth_provider.go +++ b/pkg/git/auth/ssh_auth_provider.go @@ -56,13 +56,16 @@ func (a *sshDefaultIdentityAndAgent) Signers() ([]ssh.Signer, error) { return a.signers, nil } -func (a *sshDefaultIdentityAndAgent) addDefaultIdentity(gitUrl git_url.GitUrl) { +func (a *sshDefaultIdentityAndAgent) addDefaultIdentities(gitUrl git_url.GitUrl) { a.authProvider.MessageCallbacks.Trace("trying to add default identity") u, err := user.Current() if err != nil { a.authProvider.MessageCallbacks.Trace("No current user: %v", err) - } else { - path := filepath.Join(u.HomeDir, ".ssh", "id_rsa") + return + } + + doAdd := func(name string) { + path := filepath.Join(u.HomeDir, ".ssh", name) signer, err := a.authProvider.readKey(a.ctx, path) if err != nil && !os.IsNotExist(err) { a.authProvider.MessageCallbacks.Warning("Failed to read default identity file for url %s: %v", gitUrl.String(), err) @@ -71,6 +74,12 @@ func (a *sshDefaultIdentityAndAgent) addDefaultIdentity(gitUrl git_url.GitUrl) { a.signers = append(a.signers, signer) } } + + doAdd("id_rsa") + doAdd("id_ecdsa") + doAdd("id_ed25519") + doAdd("id_xmss") + doAdd("id_dsa") } func (a *sshDefaultIdentityAndAgent) addConfigIdentities(gitUrl git_url.GitUrl) { @@ -152,7 +161,7 @@ func (a *GitSshAuthProvider) BuildAuth(ctx context.Context, gitUrl git_url.GitUr // Try agent identities first. They might be unencrypted already, making passphrase prompts unnecessary auth.addAgentIdentities(gitUrl) - auth.addDefaultIdentity(gitUrl) + auth.addDefaultIdentities(gitUrl) auth.addConfigIdentities(gitUrl) return AuthMethodAndCA{ From 544a99e384eb9ddfe0dc695c3c48175ef7f9b001 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 28 Apr 2023 09:25:23 +0200 Subject: [PATCH 1528/2916] feat: Upgrade go-jinja2 to latest main branch to implement sha256 prefix_length (#429) --- docs/reference/templating/filters.md | 7 ++++++- go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/reference/templating/filters.md b/docs/reference/templating/filters.md index 5fed49201..b0050eeab 100644 --- a/docs/reference/templating/filters.md +++ b/docs/reference/templating/filters.md @@ -84,11 +84,16 @@ Renders the input string with the current Jinja2 context. Example: {{ a | render }} ``` -### sha256 +### sha256(digest_len) Calculates the sha256 digest of the input string. Example: ``` {{ "some-string" | sha256 }} ``` +`digest_len` is an optional parameter that allows to limit the length of the returned hex digest. Example: +``` +{{ "some-string" | sha256(6) }} +``` + ### slugify Slugify a string based on [python-slugify](https://github.com/un33k/python-slugify). diff --git a/go.mod b/go.mod index a88d7e4f2..c556d3c57 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/jinzhu/copier v0.3.5 github.com/kevinburke/ssh_config v1.2.0 github.com/kluctl/go-embed-python v0.0.0-3.10.9-20230206-2 - github.com/kluctl/go-jinja2 v0.0.0-20230310104535-8136715a1e5a + github.com/kluctl/go-jinja2 v0.0.0-20230427204639-8226cd4a231e github.com/mattn/go-colorable v0.1.13 github.com/mattn/go-isatty v0.0.18 github.com/mattn/go-runewidth v0.0.14 diff --git a/go.sum b/go.sum index 8a19761c3..7648c9561 100644 --- a/go.sum +++ b/go.sum @@ -538,8 +538,8 @@ github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/kluctl/go-embed-python v0.0.0-3.10.9-20230206-2 h1:K96SwIr8MzBQ3kFFz2H/pA2y+EEk04vZ4fWj/YZghBU= github.com/kluctl/go-embed-python v0.0.0-3.10.9-20230206-2/go.mod h1:vzngsPshNKUtq0gxkYQKNJafrcH7Qy7Qt6yGNt7JmQI= -github.com/kluctl/go-jinja2 v0.0.0-20230310104535-8136715a1e5a h1:48Mz5ZZmv64Pg/cKR4nIo3Ry49Hqs5YggLPGju0e8PU= -github.com/kluctl/go-jinja2 v0.0.0-20230310104535-8136715a1e5a/go.mod h1:qMY3lmIUPcCfjj5fZm39j+h7FilaqaQFYAze19xG0Dw= +github.com/kluctl/go-jinja2 v0.0.0-20230427204639-8226cd4a231e h1:x5QMWvWLiuwJWojKXSH/3SAVkT2/y35Q5WlMSfnsDGk= +github.com/kluctl/go-jinja2 v0.0.0-20230427204639-8226cd4a231e/go.mod h1:16hIQ1ibrf02WYqeCPggfIBQx23lTUQ+jlk5kJGF0xI= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kortschak/utter v1.0.1/go.mod h1:vSmSjbyrlKjjsL71193LmzBOKgwePk9DH6uFaWHIInc= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= From 03a5f2526b3ccc00c448c213f818eaae7421a8c4 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 28 Apr 2023 09:25:39 +0200 Subject: [PATCH 1529/2916] feat: Implement commonAnnotations (#430) * docs: Remove reference to deleteByLabels from delete command * feat: Implement commonAnnotations --- cmd/kluctl/commands/cmd_delete.go | 4 ++-- docs/reference/commands/delete.md | 4 ++-- docs/reference/deployments/deployment-yml.md | 6 ++++++ pkg/deployment/deployment_item.go | 5 ++--- pkg/deployment/deployment_project.go | 10 ++++++++++ pkg/types/deployment.go | 1 + 6 files changed, 23 insertions(+), 7 deletions(-) diff --git a/cmd/kluctl/commands/cmd_delete.go b/cmd/kluctl/commands/cmd_delete.go index 751410b04..e9dbe2091 100644 --- a/cmd/kluctl/commands/cmd_delete.go +++ b/cmd/kluctl/commands/cmd_delete.go @@ -28,10 +28,10 @@ type deleteCmd struct { } func (cmd *deleteCmd) Help() string { - return `Objects are located based on 'commonLabels', configured in 'deployment.yaml' + return `Objects are located based on the target discriminator. WARNING: This command will also delete objects which are not part of your deployment -project (anymore). It really only decides based on the 'deleteByLabel' labels and does NOT +project (anymore). It really only decides based on the discriminator and does NOT take the local target/state into account!` } diff --git a/docs/reference/commands/delete.md b/docs/reference/commands/delete.md index e8bb56368..4b1f73c93 100644 --- a/docs/reference/commands/delete.md +++ b/docs/reference/commands/delete.md @@ -14,10 +14,10 @@ description: > Usage: kluctl delete [flags] Delete a target (or parts of it) from the corresponding cluster -Objects are located based on 'commonLabels', configured in 'deployment.yaml' +Objects are located based on the target discriminator. WARNING: This command will also delete objects which are not part of your deployment -project (anymore). It really only decides based on the 'deleteByLabel' labels and does NOT +project (anymore). It really only decides based on the discriminator and does NOT take the local target/state into account! diff --git a/docs/reference/deployments/deployment-yml.md b/docs/reference/deployments/deployment-yml.md index c9ffb0808..aead29af5 100644 --- a/docs/reference/deployments/deployment-yml.md +++ b/docs/reference/deployments/deployment-yml.md @@ -259,6 +259,12 @@ kubernetes. This is due to an [implementation detail](https://github.com/kuberne kustomize which causes `commonLabels` to also be applied to label selectors, which makes otherwise editable resources read-only when it comes to `commonLabels`. +## commonAnnotations +A dictionary of [annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) and values to be +added to all resources deployed by any of the deployment items in this deployment project. + +`commonAnnotations` are handled the same as [commonLabels](#commonlabels) in regard to inheriting, merging and overriding. + ## overrideNamespace A string that is used as the default namespace for all kustomize deployments which don't have a `namespace` set in their `kustomization.yaml`. diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 607ded1dd..11d769a6e 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -107,9 +107,8 @@ func (di *DeploymentItem) getCommonLabels() map[string]string { } func (di *DeploymentItem) getCommonAnnotations() map[string]string { - a := map[string]string{ - "kluctl.io/deployment-item-dir": filepath.ToSlash(di.RelToSourceItemDir), - } + a := di.Project.GetCommonAnnotations() + a["kluctl.io/deployment-item-dir"] = filepath.ToSlash(di.RelToSourceItemDir) if di.Config.SkipDeleteIfTags { a["kluctl.io/skip-delete-if-tags"] = "true" } diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go index 49e3263ba..88c496924 100644 --- a/pkg/deployment/deployment_project.go +++ b/pkg/deployment/deployment_project.go @@ -318,6 +318,16 @@ func (p *DeploymentProject) GetCommonLabels() map[string]string { return ret } +func (p *DeploymentProject) GetCommonAnnotations() map[string]string { + ret := make(map[string]string) + parents := p.getParents() + for i, _ := range parents { + d := parents[len(parents)-i-1] + uo.MergeStrMap(ret, d.p.Config.CommonAnnotations) + } + return ret +} + func (p *DeploymentProject) getOverrideNamespace() *string { for _, e := range p.getParents() { if e.p.Config.OverrideNamespace != nil { diff --git a/pkg/types/deployment.go b/pkg/types/deployment.go index dfa43ce2c..e2a5a4a8d 100644 --- a/pkg/types/deployment.go +++ b/pkg/types/deployment.go @@ -102,6 +102,7 @@ type DeploymentProjectConfig struct { Deployments []*DeploymentItemConfig `json:"deployments,omitempty"` CommonLabels map[string]string `json:"commonLabels,omitempty"` + CommonAnnotations map[string]string `json:"commonAnnotations,omitempty"` OverrideNamespace *string `json:"overrideNamespace,omitempty"` Tags []string `json:"tags,omitempty"` From 4ee6d5e47989afeaf11874f8577a8d86e81189ee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 28 Apr 2023 09:26:01 +0200 Subject: [PATCH 1530/2916] chore(deps): Bump github.com/aws/aws-sdk-go-v2/service/secretsmanager (#435) Bumps [github.com/aws/aws-sdk-go-v2/service/secretsmanager](https://github.com/aws/aws-sdk-go-v2) from 1.19.0 to 1.19.6. - [Release notes](https://github.com/aws/aws-sdk-go-v2/releases) - [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go-v2/compare/service/s3/v1.19.0...service/efs/v1.19.6) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go-v2/service/secretsmanager dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index c556d3c57..7e1109e3b 100644 --- a/go.mod +++ b/go.mod @@ -55,7 +55,7 @@ require ( filippo.io/age v1.1.1 github.com/aws/aws-sdk-go-v2 v1.18.0 github.com/aws/aws-sdk-go-v2/config v1.18.22 - github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.0 + github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.6 github.com/go-git/go-git/v5 v5.6.1 github.com/go-logr/logr v1.2.4 github.com/hashicorp/go-multierror v1.1.1 diff --git a/go.sum b/go.sum index 7648c9561..bc154f69f 100644 --- a/go.sum +++ b/go.sum @@ -117,7 +117,6 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkY github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go-v2 v1.16.7/go.mod h1:6CpKuLXg2w7If3ABZCl/qZ6rEgwtjZTn4eAf4RcEyuw= -github.com/aws/aws-sdk-go-v2 v1.17.6/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= github.com/aws/aws-sdk-go-v2 v1.18.0 h1:882kkTpSFhdgYRKVZ/VCgf7sd0ru57p2JCxz4/oN5RY= github.com/aws/aws-sdk-go-v2 v1.18.0/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= github.com/aws/aws-sdk-go-v2/config v1.18.22 h1:7vkUEmjjv+giht4wIROqLs+49VWmiQMMHSduxmoNKLU= @@ -127,11 +126,9 @@ github.com/aws/aws-sdk-go-v2/credentials v1.13.21/go.mod h1:90Dk1lJoMyspa/EDUrld github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3 h1:jJPgroehGvjrde3XufFIJUZVK5A2L9a3KwSFgKy9n8w= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3/go.mod h1:4Q0UFP0YJf0NrsEuEYHpM9fTSEVnD16Z3uyEF7J9JGM= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.14/go.mod h1:kdjrMwHwrC3+FsKhNcCMJ7tUVj/8uSD5CZXeQ4wV6fM= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.30/go.mod h1:LUBAO3zNXQjoONBKn/kR1y0Q4cj/D02Ts0uHYjcCQLM= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33 h1:kG5eQilShqmJbv11XL1VpyDbaEJzWxd4zRiCG30GSn4= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33/go.mod h1:7i0PF1ME/2eUPFcjkVIwq+DOygHEoK92t5cDqNgYbIw= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.8/go.mod h1:ZIV8GYoC6WLBW5KGs+o4rsc65/ozd+eQ0L31XF5VDwk= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.24/go.mod h1:gAuCezX/gob6BSMbItsSlMb6WZGV7K2+fWOvk8xBSto= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27 h1:vFQlirhuM8lLlpI7imKOMsjdQLuN9CPi+k44F/OFVsk= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27/go.mod h1:UrHnn3QV/d0pBZ6QBAEQcqFLf8FAzLmoUfPVIueOvoM= github.com/aws/aws-sdk-go-v2/internal/ini v1.3.34 h1:gGLG7yKaXG02/jBlg210R7VgQIotiQntNhsCFejawx8= @@ -140,8 +137,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27 h1:0iKliEXAc github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27/go.mod h1:EOwBD4J4S5qYszS5/3DpkejfuK+Z5/1uzICfPaZLtqw= github.com/aws/aws-sdk-go-v2/service/kms v1.17.5 h1:DkubF+BSEy0uX59+pYySzWFReN3fCcXobIO8L5Phh24= github.com/aws/aws-sdk-go-v2/service/kms v1.17.5/go.mod h1:ubAtMGRUMVv5kX8lpbeDguxZ64pR4kXTGApY4sCM0io= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.0 h1:B4LvuBxrxh2WXakqwJL22EPAWgqGGK9/E4YQV/IIkYo= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.0/go.mod h1:XF4Gbmcn6V9xIIm6lhwtyX1NXConNJ8x6yizt2Ejx/0= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.6 h1:xC25kY/HSssnA1lC0GFT8mfhmrpMql/24bkyWYDRgzU= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.6/go.mod h1:3ARttS6G6U3auEdKfaN4GlnfS9UxYE9nqub1+0YGycA= github.com/aws/aws-sdk-go-v2/service/sso v1.12.9 h1:GAiaQWuQhQQui76KjuXeShmyXqECwQ0mGRMc/rwsL+c= github.com/aws/aws-sdk-go-v2/service/sso v1.12.9/go.mod h1:ouy2P4z6sJN70fR3ka3wD3Ro3KezSxU6eKGQI2+2fjI= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.9 h1:TraLwncRJkWqtIBVKI/UqBymq4+hL+3MzUOtUATuzkA= From cfaa3428870b9a134e216703edb6caee7fac5d0b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 28 Apr 2023 14:12:45 +0200 Subject: [PATCH 1531/2916] feat: Allow .templateignore files in parent folders (#437) --- e2e/deployment_items_test.go | 55 +++++++++++++++++++++++++++++++ go.mod | 2 +- go.sum | 4 +-- pkg/deployment/deployment_item.go | 1 + pkg/vars/vars.go | 4 +-- 5 files changed, 61 insertions(+), 5 deletions(-) diff --git a/e2e/deployment_items_test.go b/e2e/deployment_items_test.go index 8e07ffcbc..855e20120 100644 --- a/e2e/deployment_items_test.go +++ b/e2e/deployment_items_test.go @@ -135,3 +135,58 @@ namespace: %s "value.txt": "v1", }) } + +func TestTemplateIgnore(t *testing.T) { + t.Parallel() + + k := defaultCluster1 + + p := test_utils.NewTestProject(t) + + createNamespace(t, k, p.TestSlug()) + + p.UpdateTarget("test", nil) + + addConfigMapDeployment(p, "cm1", map[string]string{ + "k1": `{{ "a" }}`, + }, resourceOpts{ + name: "cm1", + namespace: p.TestSlug(), + }) + addConfigMapDeployment(p, "cm2", map[string]string{ + "k1": `{{ "a" }}`, + }, resourceOpts{ + name: "cm2", + namespace: p.TestSlug(), + }) + addConfigMapDeployment(p, "cm3", map[string]string{ + "k1": `{{ "a" }}`, + }, resourceOpts{ + name: "cm3", + namespace: p.TestSlug(), + }) + + // .templateignore outside of deployment item + p.UpdateFile(".templateignore", func(f string) (string, error) { + return `cm2/configmap-cm2.yml`, nil + }, "") + // .templateignore inside of deployment item + p.UpdateFile("cm3/.templateignore", func(f string) (string, error) { + return `/configmap-cm3.yml`, nil + }, "") + + p.KluctlMust("deploy", "--yes", "-t", "test") + cm1 := assertConfigMapExists(t, k, p.TestSlug(), "cm1") + cm2 := assertConfigMapExists(t, k, p.TestSlug(), "cm2") + cm3 := assertConfigMapExists(t, k, p.TestSlug(), "cm3") + + assert.Equal(t, map[string]any{ + "k1": "a", + }, cm1.Object["data"]) + assert.Equal(t, map[string]any{ + "k1": `{{ "a" }}`, + }, cm2.Object["data"]) + assert.Equal(t, map[string]any{ + "k1": `{{ "a" }}`, + }, cm3.Object["data"]) +} diff --git a/go.mod b/go.mod index 7e1109e3b..8ae0028fe 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/jinzhu/copier v0.3.5 github.com/kevinburke/ssh_config v1.2.0 github.com/kluctl/go-embed-python v0.0.0-3.10.9-20230206-2 - github.com/kluctl/go-jinja2 v0.0.0-20230427204639-8226cd4a231e + github.com/kluctl/go-jinja2 v0.0.0-20230428103343-a832225dc94c github.com/mattn/go-colorable v0.1.13 github.com/mattn/go-isatty v0.0.18 github.com/mattn/go-runewidth v0.0.14 diff --git a/go.sum b/go.sum index bc154f69f..db7836ad8 100644 --- a/go.sum +++ b/go.sum @@ -535,8 +535,8 @@ github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/kluctl/go-embed-python v0.0.0-3.10.9-20230206-2 h1:K96SwIr8MzBQ3kFFz2H/pA2y+EEk04vZ4fWj/YZghBU= github.com/kluctl/go-embed-python v0.0.0-3.10.9-20230206-2/go.mod h1:vzngsPshNKUtq0gxkYQKNJafrcH7Qy7Qt6yGNt7JmQI= -github.com/kluctl/go-jinja2 v0.0.0-20230427204639-8226cd4a231e h1:x5QMWvWLiuwJWojKXSH/3SAVkT2/y35Q5WlMSfnsDGk= -github.com/kluctl/go-jinja2 v0.0.0-20230427204639-8226cd4a231e/go.mod h1:16hIQ1ibrf02WYqeCPggfIBQx23lTUQ+jlk5kJGF0xI= +github.com/kluctl/go-jinja2 v0.0.0-20230428103343-a832225dc94c h1:qAIvhYamCEU/tY6NaENEIQCynGV5sdON7zgZKnbrhhw= +github.com/kluctl/go-jinja2 v0.0.0-20230428103343-a832225dc94c/go.mod h1:16hIQ1ibrf02WYqeCPggfIBQx23lTUQ+jlk5kJGF0xI= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kortschak/utter v1.0.1/go.mod h1:vSmSjbyrlKjjsL71193LmzBOKgwePk9DH6uFaWHIInc= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 11d769a6e..4ac1d153e 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -162,6 +162,7 @@ func (di *DeploymentItem) render(forSeal bool) error { di.RenderedDir, excludePatterns, searchDirs, + di.Project.source.dir, ) } diff --git a/pkg/vars/vars.go b/pkg/vars/vars.go index cc3599471..11151f24d 100644 --- a/pkg/vars/vars.go +++ b/pkg/vars/vars.go @@ -91,10 +91,10 @@ func (vc *VarsCtx) RenderYamlFile(p string, searchDirs []string, out interface{} return nil } -func (vc *VarsCtx) RenderDirectory(sourceDir string, targetDir string, excludePatterns []string, searchDirs []string) error { +func (vc *VarsCtx) RenderDirectory(sourceDir string, targetDir string, excludePatterns []string, searchDirs []string, templateIgnoreRoot string) error { globals, err := vc.Vars.ToMap() if err != nil { return err } - return vc.J2.RenderDirectory(sourceDir, targetDir, excludePatterns, jinja2.WithGlobals(globals), jinja2.WithSearchDirs(searchDirs)) + return vc.J2.RenderDirectory(sourceDir, targetDir, excludePatterns, jinja2.WithGlobals(globals), jinja2.WithSearchDirs(searchDirs), jinja2.WithTemplateIgnoreRootDir(templateIgnoreRoot)) } From ee46c662e6a313940465df662965603d62c41de2 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 28 Apr 2023 16:35:36 +0200 Subject: [PATCH 1532/2916] feat: Simulate applying of CRs where the CRD is not applied yet (#438) --- pkg/deployment/utils/apply_utils.go | 36 +++++++++++++++++++++++++++-- pkg/yaml/validator.go | 7 ++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index 67c17f045..e77c78c36 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -15,8 +15,10 @@ import ( "github.com/kluctl/kluctl/v2/pkg/validation" "github.com/kluctl/kluctl/v2/pkg/yaml" "golang.org/x/sync/semaphore" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime/schema" "reflect" "strings" "sync" @@ -49,6 +51,7 @@ type ApplyUtil struct { abortSignal *atomic.Value allNamespaces *sync.Map + allCRDs *sync.Map ru *RemoteObjectUtils k *k8s.K8sCluster @@ -67,10 +70,11 @@ type ApplyDeploymentsUtil struct { abortSignal atomic.Value - // Used to track all created namespaces + // Used to track all created namespaces and CRDs // All ApplyUtil instances write to this in parallel and we ignore that order might be unstable - // This is only used to simulate dryRun apply into new namespaces + // This is only used to simulate dryRun apply allNamespaces sync.Map + allCRDs sync.Map resultsMutex sync.Mutex results []*ApplyUtil @@ -103,6 +107,7 @@ func (ad *ApplyDeploymentsUtil) NewApplyUtil(ctx context.Context, statusCtx *sta deletedHookObjects: map[k8s2.ObjectRef]bool{}, abortSignal: &ad.abortSignal, allNamespaces: &ad.allNamespaces, + allCRDs: &ad.allCRDs, ru: ad.ru, k: ad.k, o: ad.o, @@ -371,10 +376,19 @@ func (a *ApplyUtil) ApplyObject(x *uo.UnstructuredObject, replaced bool, hook bo _ = r.ReplaceKeys(tmpName, ref.Name) _ = r.ReplaceValues(tmpName, ref.Name) r.SetK8sNamespace(ref.Namespace) + } else if a.o.DryRun && errors.IsNotFound(err) { + if _, ok := a.allCRDs.Load(x.GetK8sGVK()); ok { + a.handleResult(x, hook) + a.HandleWarning(x.GetK8sRef(), fmt.Errorf("the underyling custom resource definition for %s has not been applied yet as Kluctl is running in dry-run mode. It is not guaranteed that the object will actually sucessfully apply", x.GetK8sRef().String())) + return + } } if r != nil && ref.GVK.GroupKind().String() == "Namespace" { a.allNamespaces.Store(ref.Name, r) } + if r != nil && ref.GVK.GroupKind().String() == "CustomResourceDefinition.apiextensions.k8s.io" { + a.handleObservedCRD(r) + } a.handleApiWarnings(ref, apiWarnings) if err == nil { a.handleResult(r, hook) @@ -387,6 +401,24 @@ func (a *ApplyUtil) ApplyObject(x *uo.UnstructuredObject, replaced bool, hook bo } } +func (a *ApplyUtil) handleObservedCRD(r *uo.UnstructuredObject) { + y, err := yaml.WriteYamlBytes(r) + if err == nil { + var crd *apiextensionsv1.CustomResourceDefinition + err = yaml.ReadYamlBytes(y, &crd) + if err == nil { + for _, v := range crd.Spec.Versions { + gvk := schema.GroupVersionKind{ + Group: crd.Spec.Group, + Version: v.Name, + Kind: crd.Spec.Names.Kind, + } + a.allCRDs.Store(gvk, &crd) + } + } + } +} + func (a *ApplyUtil) WaitReadiness(ref k8s2.ObjectRef, timeout time.Duration) bool { if a.o.DryRun { return true diff --git a/pkg/yaml/validator.go b/pkg/yaml/validator.go index 4c3265da2..45f120744 100644 --- a/pkg/yaml/validator.go +++ b/pkg/yaml/validator.go @@ -4,16 +4,23 @@ import ( "github.com/go-playground/validator/v10" "github.com/mitchellh/reflectwalk" "reflect" + "time" ) var ( Validator = validator.New() + timeType = reflect.TypeOf(time.Time{}) ) type structValidationWalker struct { } func (w *structValidationWalker) Struct(v reflect.Value) error { + if v.Type().ConvertibleTo(timeType) { + // this is for some reason causing validation to fail + return reflectwalk.SkipEntry + } + v2 := v.Interface() err := Validator.Struct(v2) if err != nil { From 6f355698164f76d30e4c570642cc0a1e28edd4fd Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 28 Apr 2023 16:36:03 +0200 Subject: [PATCH 1533/2916] fix: Ignore ESC in readLine (#439) --- pkg/utils/term/read_line.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/utils/term/read_line.go b/pkg/utils/term/read_line.go index 910b039e7..8f5c9f6fd 100644 --- a/pkg/utils/term/read_line.go +++ b/pkg/utils/term/read_line.go @@ -17,6 +17,9 @@ func readLine(reader io.Reader, cb func(ret []byte)) ([]byte, error) { n, err := reader.Read(buf[:]) if n > 0 { switch buf[0] { + case '': + // ignore ESC sequences + continue case '', '\b': if len(ret) > 0 { ret = ret[:len(ret)-1] From 51758273cf141d8395e18f16f92d00b199995e7f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 28 Apr 2023 17:15:38 +0200 Subject: [PATCH 1534/2916] docs: Add docs for deleteObjects (#440) --- docs/reference/deployments/deployment-yml.md | 20 ++++++++++++++++++++ pkg/types/deployment.go | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/docs/reference/deployments/deployment-yml.md b/docs/reference/deployments/deployment-yml.md index aead29af5..45d144ad3 100644 --- a/docs/reference/deployments/deployment-yml.md +++ b/docs/reference/deployments/deployment-yml.md @@ -128,6 +128,26 @@ deployments: - path: kustomizeDeployment3 ``` +### deleteObjects +Causes kluctl to delete matching objects, specified by a list of group/kind/name/namespace dictionaries. +The order/parallelization of deletion is identical to the order and parallelization of normal deployment items, +meaning that it happens in parallel by default until a barrier is encountered. + +Example: +```yaml +deployments: + - deleteObjects: + - group: apps + kind: DaemonSet + namespace: kube-system + name: kube-proxy + - barrier: true + - path: my-cni +``` + +The above example shows how to delete the kube-proxy DaemonSet before installing a CNI (e.g. Cilium in +proxy-replacement mode). + ## deployments common properties All entries in `deployments` can have the following common properties: diff --git a/pkg/types/deployment.go b/pkg/types/deployment.go index e2a5a4a8d..f5695951f 100644 --- a/pkg/types/deployment.go +++ b/pkg/types/deployment.go @@ -51,7 +51,7 @@ type DeleteObjectItemConfig struct { func ValidateDeleteObjectItemConfig(sl validator.StructLevel) { s := sl.Current().Interface().(DeleteObjectItemConfig) if s.Group == nil && s.Kind == nil { - sl.ReportError(s, "self", "self", "at least one of group/version/kind must be set", "") + sl.ReportError(s, "self", "self", "at least one of group or kind must be set", "") } } From 7701039161c20f8d30679fdae5335710fc59319e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 29 Apr 2023 19:24:43 +0200 Subject: [PATCH 1535/2916] chore(deps): Bump github.com/bitnami-labs/sealed-secrets (#441) Bumps [github.com/bitnami-labs/sealed-secrets](https://github.com/bitnami-labs/sealed-secrets) from 0.19.5 to 0.20.5. - [Release notes](https://github.com/bitnami-labs/sealed-secrets/releases) - [Changelog](https://github.com/bitnami-labs/sealed-secrets/blob/main/RELEASE-NOTES.md) - [Commits](https://github.com/bitnami-labs/sealed-secrets/compare/v0.19.5...v0.20.5) --- updated-dependencies: - dependency-name: github.com/bitnami-labs/sealed-secrets dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 8ae0028fe..cd55454c1 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 github.com/Masterminds/semver/v3 v3.2.1 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d - github.com/bitnami-labs/sealed-secrets v0.19.5 + github.com/bitnami-labs/sealed-secrets v0.20.5 github.com/cyphar/filepath-securejoin v0.2.3 github.com/docker/distribution v2.8.1+incompatible github.com/fluxcd/pkg/kustomize v0.13.2 @@ -35,7 +35,7 @@ require ( github.com/spf13/viper v1.15.0 github.com/stretchr/testify v1.8.2 github.com/xanzy/ssh-agent v0.3.3 - golang.org/x/crypto v0.7.0 + golang.org/x/crypto v0.8.0 golang.org/x/net v0.9.0 golang.org/x/sync v0.1.0 golang.org/x/sys v0.7.0 diff --git a/go.sum b/go.sum index db7836ad8..7c61ef871 100644 --- a/go.sum +++ b/go.sum @@ -153,8 +153,8 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bitnami-labs/sealed-secrets v0.19.5 h1:Llrs8bm5MdJEoPIQo0xZOHu/2i+Ry8N5bQFpc48UZYc= -github.com/bitnami-labs/sealed-secrets v0.19.5/go.mod h1:IC5f2r0c8mxjx8nHs+du+gBso2Wsbdb2lcTwVmOOu2Y= +github.com/bitnami-labs/sealed-secrets v0.20.5 h1:ejHSbd7GcJOWgAjBnkn+QFPTFSsNOZAJOomS4bM/LWk= +github.com/bitnami-labs/sealed-secrets v0.20.5/go.mod h1:EAN3XxSWbJOi2AwF0wAEo0bCAReAD0ToTP9euRw5nVQ= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= @@ -890,8 +890,8 @@ golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= +golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= From a5700bbc6cfc0d633085f2afcc7141580ec68181 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 29 Apr 2023 19:28:10 +0200 Subject: [PATCH 1536/2916] chore(deps): Bump github.com/spf13/cobra from 1.6.1 to 1.7.0 (#442) Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.6.1 to 1.7.0. - [Release notes](https://github.com/spf13/cobra/releases) - [Commits](https://github.com/spf13/cobra/compare/v1.6.1...v1.7.0) --- updated-dependencies: - dependency-name: github.com/spf13/cobra dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index cd55454c1..3e681d428 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( github.com/r3labs/diff/v2 v2.15.1 github.com/rogpeppe/go-internal v1.10.0 github.com/sirupsen/logrus v1.9.0 - github.com/spf13/cobra v1.6.1 + github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.15.0 github.com/stretchr/testify v1.8.2 diff --git a/go.sum b/go.sum index 7c61ef871..c4051945a 100644 --- a/go.sum +++ b/go.sum @@ -499,7 +499,6 @@ github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= @@ -771,8 +770,8 @@ github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= -github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= -github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= From a583a31659a182b661b11a91997418e235057bda Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 29 Apr 2023 19:29:21 +0200 Subject: [PATCH 1537/2916] chore(deps): Bump github.com/ohler55/ojg from 1.18.4 to 1.18.5 (#443) Bumps [github.com/ohler55/ojg](https://github.com/ohler55/ojg) from 1.18.4 to 1.18.5. - [Release notes](https://github.com/ohler55/ojg/releases) - [Changelog](https://github.com/ohler55/ojg/blob/develop/CHANGELOG.md) - [Commits](https://github.com/ohler55/ojg/compare/v1.18.4...v1.18.5) --- updated-dependencies: - dependency-name: github.com/ohler55/ojg dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 3e681d428..b0e2715cd 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/mattn/go-isatty v0.0.18 github.com/mattn/go-runewidth v0.0.14 github.com/mitchellh/reflectwalk v1.0.2 - github.com/ohler55/ojg v1.18.4 + github.com/ohler55/ojg v1.18.5 github.com/pkg/errors v0.9.1 github.com/r3labs/diff/v2 v2.15.1 github.com/rogpeppe/go-internal v1.10.0 diff --git a/go.sum b/go.sum index c4051945a..c5b2aa562 100644 --- a/go.sum +++ b/go.sum @@ -651,8 +651,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW github.com/nelsam/hel/v2 v2.3.2/go.mod h1:1ZTGfU2PFTOd5mx22i5O0Lc2GY933lQ2wb/ggy+rL3w= github.com/nelsam/hel/v2 v2.3.3/go.mod h1:1ZTGfU2PFTOd5mx22i5O0Lc2GY933lQ2wb/ggy+rL3w= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/ohler55/ojg v1.18.4 h1:FXHUddnBgrx5RwlkTDJnXBL5s3DNOMlZfv4K27nNNtM= -github.com/ohler55/ojg v1.18.4/go.mod h1:uHcD1ErbErC27Zhb5Df2jUjbseLLcmOCo6oxSr3jZxo= +github.com/ohler55/ojg v1.18.5 h1:tzn5LJtkSyXowCo8SlGieU0zEc7WF4143Ri9MYlQy7Q= +github.com/ohler55/ojg v1.18.5/go.mod h1:uHcD1ErbErC27Zhb5Df2jUjbseLLcmOCo6oxSr3jZxo= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU= From 97075430041c404ab0d1dda5d85e5100ac4bc697 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 29 Apr 2023 19:30:13 +0200 Subject: [PATCH 1538/2916] chore(deps): Bump github.com/hashicorp/vault/api from 1.9.0 to 1.9.1 (#445) Bumps [github.com/hashicorp/vault/api](https://github.com/hashicorp/vault) from 1.9.0 to 1.9.1. - [Release notes](https://github.com/hashicorp/vault/releases) - [Changelog](https://github.com/hashicorp/vault/blob/main/CHANGELOG.md) - [Commits](https://github.com/hashicorp/vault/compare/v1.9.0...v1.9.1) --- updated-dependencies: - dependency-name: github.com/hashicorp/vault/api dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b0e2715cd..0aad818e1 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/gobwas/glob v0.2.3 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 github.com/google/go-containerregistry v0.14.0 - github.com/hashicorp/vault/api v1.9.0 + github.com/hashicorp/vault/api v1.9.1 github.com/hexops/gotextdiff v1.0.3 github.com/imdario/mergo v0.3.15 github.com/jinzhu/copier v0.3.5 diff --git a/go.sum b/go.sum index c5b2aa562..35c16e215 100644 --- a/go.sum +++ b/go.sum @@ -483,8 +483,8 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hashicorp/vault/api v1.9.0 h1:ab7dI6W8DuCY7yCU8blo0UCYl2oHre/dloCmzMWg9w8= -github.com/hashicorp/vault/api v1.9.0/go.mod h1:lloELQP4EyhjnCQhF8agKvWIVTmxbpEJj70b98959sM= +github.com/hashicorp/vault/api v1.9.1 h1:LtY/I16+5jVGU8rufyyAkwopgq/HpUnxFBg+QLOAV38= +github.com/hashicorp/vault/api v1.9.1/go.mod h1:78kktNcQYbBGSrOjQfHjXN32OhhxXnbYl3zxpd2uPUs= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= From b87cc3ee6e03ffdc74f3831be8d9ac746288513f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 29 Apr 2023 23:11:57 +0200 Subject: [PATCH 1539/2916] chore(deps): Bump github.com/fluxcd/pkg/kustomize from 0.13.2 to 1.1.1 (#444) Bumps [github.com/fluxcd/pkg/kustomize](https://github.com/fluxcd/pkg) from 0.13.2 to 1.1.1. - [Release notes](https://github.com/fluxcd/pkg/releases) - [Commits](https://github.com/fluxcd/pkg/compare/runtime/v0.13.2...kustomize/v1.1.1) --- updated-dependencies: - dependency-name: github.com/fluxcd/pkg/kustomize dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 ++++-- go.sum | 16 ++++++++++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 0aad818e1..5fc4bdaf8 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/bitnami-labs/sealed-secrets v0.20.5 github.com/cyphar/filepath-securejoin v0.2.3 github.com/docker/distribution v2.8.1+incompatible - github.com/fluxcd/pkg/kustomize v0.13.2 + github.com/fluxcd/pkg/kustomize v1.1.1 github.com/go-playground/validator/v10 v10.12.0 github.com/gobwas/glob v0.2.3 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 @@ -126,7 +126,9 @@ require ( github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect github.com/fatih/color v1.13.0 // indirect - github.com/fluxcd/pkg/apis/kustomize v0.8.0 // indirect + github.com/fluxcd/go-git/v5 v5.0.0-20221219190809-2e5c9d01cfc4 // indirect + github.com/fluxcd/pkg/apis/kustomize v1.0.0 // indirect + github.com/fluxcd/pkg/sourceignore v0.3.3 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-git/gcfg v1.5.0 // indirect diff --git a/go.sum b/go.sum index 35c16e215..9ad1ee69d 100644 --- a/go.sum +++ b/go.sum @@ -94,12 +94,14 @@ github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2B github.com/Microsoft/hcsshim v0.10.0-rc.7 h1:HBytQPxcv8Oy4244zbQbe6hnOnx544eL5QPUqhJldz8= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8= github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA= github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= github.com/a8m/expect v1.0.0/go.mod h1:4IwSCMumY49ScypDnjNbYEjgVeqy1/U2cEs3Lat96eA= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= +github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -255,10 +257,14 @@ github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= -github.com/fluxcd/pkg/apis/kustomize v0.8.0 h1:A6aLolxPV2Sll44SOHiX96lbXXmRZmS5BoEerkRHrfM= -github.com/fluxcd/pkg/apis/kustomize v0.8.0/go.mod h1:9DPEVSfVIkiC2H3Dk6Ght4YJkswhYIaufXla4tB5Y84= -github.com/fluxcd/pkg/kustomize v0.13.2 h1:isA9yi+m7sSIxdTrFR1U7+LyS2BraG07ZkKLHw3bnGo= -github.com/fluxcd/pkg/kustomize v0.13.2/go.mod h1:1H9qednPxL/JvZE5at/f6wVHTH4WmxJYqfgVOZJ3uAk= +github.com/fluxcd/go-git/v5 v5.0.0-20221219190809-2e5c9d01cfc4 h1:Gm5sGGk+/Wq6RhX4xpCZ2IqjDp5XkjlhENaAuAlpdKc= +github.com/fluxcd/go-git/v5 v5.0.0-20221219190809-2e5c9d01cfc4/go.mod h1:raWgfUV7lDQVXp4QXUaeNNJkRVKz97UQuF+0kdY7Vmo= +github.com/fluxcd/pkg/apis/kustomize v1.0.0 h1:5T2b/mRZiGWtP7fvSU8gZOApIc06H6SdLX3MlsE6LRo= +github.com/fluxcd/pkg/apis/kustomize v1.0.0/go.mod h1:XaDYlKxrf9D2zZWcZ0BnSIqGtcm8mdNtJGzZWYjCnQo= +github.com/fluxcd/pkg/kustomize v1.1.1 h1:hYFJGi+fiaecY4gXvx52fumlvDEq/1RdFbaev67oBhE= +github.com/fluxcd/pkg/kustomize v1.1.1/go.mod h1:i+Z9iPAoSz28oH0FmDI73iqZ3oXZxQR2O3HfhdsWhfo= +github.com/fluxcd/pkg/sourceignore v0.3.3 h1:Ue29JAuPECEYdvIqdpXpQaDxpeySn7amarLArp7XoIs= +github.com/fluxcd/pkg/sourceignore v0.3.3/go.mod h1:yuJzKggph0Bdbk9LgXjJQhvJZSTJV/1vS7mJuB7mPa0= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -675,6 +681,7 @@ github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= +github.com/pjbgf/sha1cd v0.2.3/go.mod h1:HOK9QrgzdHpbc2Kzip0Q1yi3M2MFGPADtR6HjG65m5M= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ= @@ -810,6 +817,7 @@ github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN github.com/vbatts/tar-split v0.11.2 h1:Via6XqJr0hceW4wff3QRzD5gAk/tatMw/4ZA7cTlIME= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= +github.com/xanzy/ssh-agent v0.3.2/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= From a323315b881463e590544ce4c28b792805531583 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sat, 29 Apr 2023 23:48:14 +0200 Subject: [PATCH 1540/2916] fix: Don't perform version check in completion command (#448) --- cmd/kluctl/commands/root.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index 291655957..5a4b2589d 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -245,7 +245,9 @@ func Main() { } ctx = initStatusHandler(ctxIn, flags.Debug, flags.NoColor) if !flags.NoUpdateCheck { - checkNewVersion(ctx) + if len(os.Args) < 2 || (os.Args[1] != "completion" && os.Args[1] != "__complete") { + checkNewVersion(ctx) + } } return ctx, nil }) From 6443ac0a70fcb04c06cb9be51e5d13261459ac69 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 1 May 2023 21:30:24 +0200 Subject: [PATCH 1541/2916] feat: Implement --short-output flag (#451) * refactor: Pass OutputFormatFlags to outputCommandResult * feat: Implement --short-output flag --- cmd/kluctl/args/misc.go | 1 + cmd/kluctl/commands/cmd_delete.go | 2 +- cmd/kluctl/commands/cmd_deploy.go | 11 ++++++---- cmd/kluctl/commands/cmd_diff.go | 2 +- cmd/kluctl/commands/cmd_poke_images.go | 2 +- cmd/kluctl/commands/cmd_prune.go | 2 +- cmd/kluctl/commands/command_result.go | 28 +++++++++++++++----------- docs/reference/commands/delete.md | 3 +++ docs/reference/commands/deploy.md | 3 +++ docs/reference/commands/diff.md | 3 +++ docs/reference/commands/poke-images.md | 3 +++ docs/reference/commands/prune.md | 3 +++ 12 files changed, 43 insertions(+), 20 deletions(-) diff --git a/cmd/kluctl/args/misc.go b/cmd/kluctl/args/misc.go index 513e71165..45ec1050d 100644 --- a/cmd/kluctl/args/misc.go +++ b/cmd/kluctl/args/misc.go @@ -43,6 +43,7 @@ type AbortOnErrorFlags struct { type OutputFormatFlags struct { OutputFormat []string `group:"misc" short:"o" help:"Specify output format and target file, in the format 'format=path'. Format can either be 'text' or 'yaml'. Can be specified multiple times. The actual format for yaml is currently not documented and subject to change."` NoObfuscate bool `group:"misc" help:"Disable obfuscation of sensitive/secret data"` + ShortOutput bool `group:"misc" help:"When using the 'text' output format (which is the default), only names of changes objects are shown instead of showing all changes."` } type OutputFlags struct { diff --git a/cmd/kluctl/commands/cmd_delete.go b/cmd/kluctl/commands/cmd_delete.go index e9dbe2091..91d099332 100644 --- a/cmd/kluctl/commands/cmd_delete.go +++ b/cmd/kluctl/commands/cmd_delete.go @@ -61,7 +61,7 @@ func (cmd *deleteCmd) Run(ctx context.Context) error { if err != nil { return err } - err = outputCommandResult(ctx, cmd.OutputFormat, cmd.NoObfuscate, result) + err = outputCommandResult(ctx, cmd.OutputFormatFlags, result) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_deploy.go b/cmd/kluctl/commands/cmd_deploy.go index 083a97e26..d6b6a23a3 100644 --- a/cmd/kluctl/commands/cmd_deploy.go +++ b/cmd/kluctl/commands/cmd_deploy.go @@ -64,7 +64,7 @@ func (cmd *deployCmd) runCmdDeploy(cmdCtx *commandCtx) error { cmd2.NoWait = cmd.NoWait cb := func(diffResult *types.CommandResult) error { - return cmd.diffResultCb(cmdCtx.ctx, cmd.NoObfuscate, diffResult) + return cmd.diffResultCb(cmdCtx.ctx, diffResult) } if cmd.Yes || cmd.DryRun { cb = nil @@ -74,7 +74,7 @@ func (cmd *deployCmd) runCmdDeploy(cmdCtx *commandCtx) error { if err != nil { return err } - err = outputCommandResult(cmdCtx.ctx, cmd.OutputFormat, cmd.NoObfuscate, result) + err = outputCommandResult(cmdCtx.ctx, cmd.OutputFormatFlags, result) if err != nil { return err } @@ -84,8 +84,11 @@ func (cmd *deployCmd) runCmdDeploy(cmdCtx *commandCtx) error { return nil } -func (cmd *deployCmd) diffResultCb(ctx context.Context, noObfuscate bool, diffResult *types.CommandResult) error { - err := outputCommandResult(ctx, nil, noObfuscate, diffResult) +func (cmd *deployCmd) diffResultCb(ctx context.Context, diffResult *types.CommandResult) error { + flags := cmd.OutputFormatFlags + flags.OutputFormat = nil // use default output format + + err := outputCommandResult(ctx, flags, diffResult) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_diff.go b/cmd/kluctl/commands/cmd_diff.go index b04ff519d..21edf81f1 100644 --- a/cmd/kluctl/commands/cmd_diff.go +++ b/cmd/kluctl/commands/cmd_diff.go @@ -50,7 +50,7 @@ func (cmd *diffCmd) Run(ctx context.Context) error { if err != nil { return err } - err = outputCommandResult(ctx, cmd.OutputFormat, cmd.NoObfuscate, result) + err = outputCommandResult(ctx, cmd.OutputFormatFlags, result) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_poke_images.go b/cmd/kluctl/commands/cmd_poke_images.go index c1593e58e..76b69a538 100644 --- a/cmd/kluctl/commands/cmd_poke_images.go +++ b/cmd/kluctl/commands/cmd_poke_images.go @@ -51,7 +51,7 @@ func (cmd *pokeImagesCmd) Run(ctx context.Context) error { if err != nil { return err } - err = outputCommandResult(ctx, cmd.OutputFormat, cmd.NoObfuscate, result) + err = outputCommandResult(ctx, cmd.OutputFormatFlags, result) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_prune.go b/cmd/kluctl/commands/cmd_prune.go index 5b542cd27..c975dc8bd 100644 --- a/cmd/kluctl/commands/cmd_prune.go +++ b/cmd/kluctl/commands/cmd_prune.go @@ -54,7 +54,7 @@ func (cmd *pruneCmd) runCmdPrune(cmdCtx *commandCtx) error { if err != nil { return err } - err = outputCommandResult(cmdCtx.ctx, cmd.OutputFormat, cmd.NoObfuscate, result) + err = outputCommandResult(cmdCtx.ctx, cmd.OutputFormatFlags, result) if err != nil { return err } diff --git a/cmd/kluctl/commands/command_result.go b/cmd/kluctl/commands/command_result.go index 1d43fda5c..27f648079 100644 --- a/cmd/kluctl/commands/command_result.go +++ b/cmd/kluctl/commands/command_result.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "fmt" + "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/diff" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" @@ -15,7 +16,7 @@ import ( "strings" ) -func formatCommandResultText(cr *types.CommandResult) string { +func formatCommandResultText(cr *types.CommandResult, short bool) string { buf := bytes.NewBuffer(nil) if len(cr.Warnings) != 0 { @@ -39,12 +40,15 @@ func formatCommandResultText(cr *types.CommandResult) string { } prettyObjectRefs(buf, refs) - buf.WriteString("\n") - for i, co := range cr.ChangedObjects { - if i != 0 { - buf.WriteString("\n") + if !short { + buf.WriteString("\n") + + for i, co := range cr.ChangedObjects { + if i != 0 { + buf.WriteString("\n") + } + prettyChanges(buf, co.Ref, co.Changes) } - prettyChanges(buf, co.Ref, co.Changes) } } @@ -111,10 +115,10 @@ func formatCommandResultYaml(cr *types.CommandResult) (string, error) { return b, nil } -func formatCommandResult(cr *types.CommandResult, format string) (string, error) { +func formatCommandResult(cr *types.CommandResult, format string, short bool) (string, error) { switch format { case "text": - return formatCommandResultText(cr), nil + return formatCommandResultText(cr, short), nil case "yaml": return formatCommandResultYaml(cr) default: @@ -202,10 +206,10 @@ func outputHelper(ctx context.Context, output []string, cb func(format string) ( return nil } -func outputCommandResult(ctx context.Context, output []string, noObfuscate bool, cr *types.CommandResult) error { +func outputCommandResult(ctx context.Context, flags args.OutputFormatFlags, cr *types.CommandResult) error { status.Flush(ctx) - if !noObfuscate { + if !flags.NoObfuscate { var obfuscator diff.Obfuscator for _, c := range cr.ChangedObjects { err := obfuscator.Obfuscate(c.Ref, c.Changes) @@ -215,8 +219,8 @@ func outputCommandResult(ctx context.Context, output []string, noObfuscate bool, } } - return outputHelper(ctx, output, func(format string) (string, error) { - return formatCommandResult(cr, format) + return outputHelper(ctx, flags.OutputFormat, func(format string) (string, error) { + return formatCommandResult(cr, format, flags.ShortOutput) }) } diff --git a/docs/reference/commands/delete.md b/docs/reference/commands/delete.md index 4b1f73c93..da4ad7739 100644 --- a/docs/reference/commands/delete.md +++ b/docs/reference/commands/delete.md @@ -58,6 +58,9 @@ Misc arguments: currently not documented and subject to change. --render-output-dir string Specifies the target directory to render the project into. If omitted, a temporary directory is used. + --short-output When using the 'text' output format (which is the default), + only names of changes objects are shown instead of showing all + changes. -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. diff --git a/docs/reference/commands/deploy.md b/docs/reference/commands/deploy.md index edc7d6544..ea4d88e11 100644 --- a/docs/reference/commands/deploy.md +++ b/docs/reference/commands/deploy.md @@ -68,6 +68,9 @@ Misc arguments: omitted, a temporary directory is used. --replace-on-error When patching an object fails, try to replace it. See documentation for more details. + --short-output When using the 'text' output format (which is the default), + only names of changes objects are shown instead of showing all + changes. -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. diff --git a/docs/reference/commands/diff.md b/docs/reference/commands/diff.md index 4441498af..19909b274 100644 --- a/docs/reference/commands/diff.md +++ b/docs/reference/commands/diff.md @@ -64,6 +64,9 @@ Misc arguments: omitted, a temporary directory is used. --replace-on-error When patching an object fails, try to replace it. See documentation for more details. + --short-output When using the 'text' output format (which is the default), + only names of changes objects are shown instead of showing all + changes. ``` diff --git a/docs/reference/commands/poke-images.md b/docs/reference/commands/poke-images.md index 77a33c404..55ce70bbc 100644 --- a/docs/reference/commands/poke-images.md +++ b/docs/reference/commands/poke-images.md @@ -55,6 +55,9 @@ Misc arguments: currently not documented and subject to change. --render-output-dir string Specifies the target directory to render the project into. If omitted, a temporary directory is used. + --short-output When using the 'text' output format (which is the default), + only names of changes objects are shown instead of showing all + changes. -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. diff --git a/docs/reference/commands/prune.md b/docs/reference/commands/prune.md index 5bb327f3c..6d012920e 100644 --- a/docs/reference/commands/prune.md +++ b/docs/reference/commands/prune.md @@ -51,6 +51,9 @@ Misc arguments: currently not documented and subject to change. --render-output-dir string Specifies the target directory to render the project into. If omitted, a temporary directory is used. + --short-output When using the 'text' output format (which is the default), + only names of changes objects are shown instead of showing all + changes. -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. From 1468e535713cd4696f88d8413ebf74efe6225fa8 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 1 May 2023 21:30:36 +0200 Subject: [PATCH 1542/2916] feat: Support SOPS decryption for all files involved in Kustomize deployments (#450) This is solved with the help of the decryptingFs that automatically decrypts all files that are loaded by Kustomize. --- pkg/deployment/deployment_item.go | 16 +++--- pkg/sops/decrypting_fs.go | 86 +++++++++++++++++++++++++++++++ pkg/sops/utils.go | 2 +- 3 files changed, 93 insertions(+), 11 deletions(-) create mode 100644 pkg/sops/decrypting_fs.go diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 4ac1d153e..7c87d24b6 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -3,6 +3,7 @@ package deployment import ( "fmt" "github.com/fluxcd/pkg/kustomize" + securefs "github.com/fluxcd/pkg/kustomize/filesys" "github.com/hashicorp/go-multierror" "github.com/kluctl/kluctl/v2/pkg/helm" "github.com/kluctl/kluctl/v2/pkg/k8s" @@ -513,18 +514,13 @@ func (di *DeploymentItem) buildKustomize() error { return err } - resources, _, _ := ky.GetNestedStringList("resources") - for _, r := range resources { - p := filepath.Join(di.RenderedDir, r) - if utils.IsFile(p) { - err = sops.MaybeDecryptFile(di.ctx.SopsDecrypter, p) - if err != nil { - return err - } - } + fs, err := securefs.MakeFsOnDiskSecureBuild(di.RenderedSourceRootDir) + if err != nil { + return err } - rm, err := kustomize.SecureBuild(di.RenderedSourceRootDir, di.RenderedDir, true) + fs = sops.NewDecryptingFs(fs, di.ctx.SopsDecrypter) + rm, err := kustomize.Build(fs, di.RenderedDir) if err != nil { return err } diff --git a/pkg/sops/decrypting_fs.go b/pkg/sops/decrypting_fs.go new file mode 100644 index 000000000..9bfc6f0b5 --- /dev/null +++ b/pkg/sops/decrypting_fs.go @@ -0,0 +1,86 @@ +package sops + +import ( + "bytes" + "fmt" + "github.com/kluctl/kluctl/v2/pkg/sops/decryptor" + "go.mozilla.org/sops/v3/cmd/sops/formats" + "io" + "os" + "sigs.k8s.io/kustomize/kyaml/filesys" +) + +type decryptingFs struct { + filesys.FileSystem + decryptor *decryptor.Decryptor +} + +type file struct { + os.FileInfo + data *bytes.Buffer + size int64 +} + +func NewDecryptingFs(fs filesys.FileSystem, decryptor *decryptor.Decryptor) filesys.FileSystem { + return &decryptingFs{ + FileSystem: fs, + decryptor: decryptor, + } +} + +// Open opens the named file for reading. +func (fs *decryptingFs) Open(path string) (filesys.File, error) { + f, err := fs.FileSystem.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + st, err := f.Stat() + if err != nil { + return nil, err + } + b, err := io.ReadAll(f) + if err != nil { + return nil, err + } + + format := formats.FormatForPath(path) + b2, _, err := MaybeDecrypt(fs.decryptor, b, format, format) + if err != nil { + return nil, fmt.Errorf("failed to decrypt file %s: %w", path, err) + } + ret := &file{ + FileInfo: st, + data: bytes.NewBuffer(b2), + } + return ret, nil +} + +// ReadFile returns the contents of the file at the given path. +func (fs *decryptingFs) ReadFile(path string) ([]byte, error) { + f, err := fs.Open(path) + if err != nil { + return nil, err + } + return io.ReadAll(f) +} + +func (f file) Read(p []byte) (n int, err error) { + return f.data.Read(p) +} + +func (f file) Write(p []byte) (n int, err error) { + panic("write is not implemented") +} + +func (f file) Close() error { + return nil +} + +func (f file) Stat() (os.FileInfo, error) { + return f, nil +} + +func (f file) Size() int64 { + return int64(f.data.Len()) +} diff --git a/pkg/sops/utils.go b/pkg/sops/utils.go index ab6f881ce..66a733e72 100644 --- a/pkg/sops/utils.go +++ b/pkg/sops/utils.go @@ -17,7 +17,7 @@ func IsMaybeSopsFile(s []byte) bool { } func MaybeDecrypt(decrypter *decryptor.Decryptor, encrypted []byte, inputFormat, outputFormat formats.Format) ([]byte, bool, error) { - if decrypter == nil { + if decrypter == nil || inputFormat == formats.Binary { return encrypted, false, nil } From ae8bb1e78aa395577ceba475086cf4351a17edf0 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 1 May 2023 22:47:42 +0200 Subject: [PATCH 1543/2916] feat: Implement targetPath in configMap/secret vars sources (#452) * refactor: Remove unneded key parameter from loadFromK8sObject * feat: Implement targetPath in configMap/secret vars sources --- docs/reference/templating/variable-sources.md | 36 ++++++++++++++--- pkg/types/vars_source.go | 9 +++-- pkg/utils/uo/jsonpath.go | 4 ++ pkg/vars/vars_loader.go | 39 +++++++++++++++---- pkg/vars/vars_loader_test.go | 29 +++++++++++++- 5 files changed, 98 insertions(+), 19 deletions(-) diff --git a/docs/reference/templating/variable-sources.md b/docs/reference/templating/variable-sources.md index 32d1654b3..4ed5dac62 100644 --- a/docs/reference/templating/variable-sources.md +++ b/docs/reference/templating/variable-sources.md @@ -99,10 +99,14 @@ Kluctl also supports variable files encrypted with [SOPS](https://github.com/moz [sops integration](../deployments/sops.md) integration for more details. ### clusterConfigMap -Loads a configmap from the target's cluster and loads the specified key's value as a yaml file into the jinja2 variables -context. +Loads a configmap from the target's cluster and loads the specified key's value into the templating context. The value +is treated and loaded as YAML and thus can either be a simple value or a complex nested structure. In case of a simple +value (e.g. a number), you must also specify `targetPath`. -Assume the following configmap to be deployed to the target cluster: +The referred ConfigMap must already exist while the Kluctl project is loaded, meaning that it is not possible to use +a ConfigMap that is deployed as part of the Kluctl project itself. + +Assume the following ConfigMap to be already deployed to the target cluster: ```yaml apiVersion: v1 kind: ConfigMap @@ -118,7 +122,7 @@ data: - l2 ``` -This configmap can be loaded via: +This ConfigMap can be loaded via: ```yaml vars: @@ -128,8 +132,28 @@ vars: key: vars ``` -It assumes that the configmap is already deployed before the kluctl deployment happens. This might for example be -useful to store meta information about the cluster itself and then make it available to kluctl deployments. +The following example uses a simple value: + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: my-vars + namespace: my-namespace +data: + value: 123 +``` + +This ConfigMap can be loaded via: + +```yaml +vars: + - clusterConfigMap: + name: my-vars + namespace: my-namespace + key: value + targetPath: deep.nested.path +``` ### clusterSecret Same as clusterConfigMap, but for secrets. diff --git a/pkg/types/vars_source.go b/pkg/types/vars_source.go index 942cd6654..c195cdace 100644 --- a/pkg/types/vars_source.go +++ b/pkg/types/vars_source.go @@ -15,10 +15,11 @@ type VarsSourceGit struct { } type VarsSourceClusterConfigMapOrSecret struct { - Name string `json:"name,omitempty"` - Labels map[string]string `json:"labels,omitempty"` - Namespace string `json:"namespace" validate:"required"` - Key string `json:"key" validate:"required"` + Name string `json:"name,omitempty"` + Labels map[string]string `json:"labels,omitempty"` + Namespace string `json:"namespace" validate:"required"` + Key string `json:"key" validate:"required"` + TargetPath string `json:"targetPath,omitempty"` } func ValidateVarsSourceClusterConfigMapOrSecret(sl validator.StructLevel) { diff --git a/pkg/utils/uo/jsonpath.go b/pkg/utils/uo/jsonpath.go index 5263f8639..e21876c7f 100644 --- a/pkg/utils/uo/jsonpath.go +++ b/pkg/utils/uo/jsonpath.go @@ -146,3 +146,7 @@ func (j *MyJsonPath) GetFirstListOfObjects(o *UnstructuredObject) ([]*Unstructur func (j *MyJsonPath) Del(o *UnstructuredObject) error { return j.exp.Del(o.Object) } + +func (j *MyJsonPath) Set(o *UnstructuredObject, v any) error { + return j.exp.Set(o.Object, v) +} diff --git a/pkg/vars/vars_loader.go b/pkg/vars/vars_loader.go index 9ce0ff997..49170f161 100644 --- a/pkg/vars/vars_loader.go +++ b/pkg/vars/vars_loader.go @@ -107,9 +107,9 @@ func (v *VarsLoader) LoadVars(varsCtx *VarsCtx, sourceIn *types.VarsSource, sear } else if source.Git != nil { newVars, err = v.loadGit(varsCtx, source.Git, ignoreMissing) } else if source.ClusterConfigMap != nil { - newVars, err = v.loadFromK8sObject(varsCtx, *source.ClusterConfigMap, "ConfigMap", source.ClusterConfigMap.Key, ignoreMissing, false) + newVars, err = v.loadFromK8sObject(varsCtx, *source.ClusterConfigMap, "ConfigMap", ignoreMissing, false) } else if source.ClusterSecret != nil { - newVars, err = v.loadFromK8sObject(varsCtx, *source.ClusterSecret, "Secret", source.ClusterSecret.Key, ignoreMissing, true) + newVars, err = v.loadFromK8sObject(varsCtx, *source.ClusterSecret, "Secret", ignoreMissing, true) } else if source.SystemEnvVars != nil { newVars, err = v.loadSystemEnvs(varsCtx, &source, ignoreMissing, rootKey) } else if source.Http != nil { @@ -271,7 +271,7 @@ func (v *VarsLoader) loadGit(varsCtx *VarsCtx, gitFile *types.VarsSourceGit, ign return v.loadFile(varsCtx, gitFile.Path, ignoreMissing, []string{clonedDir}) } -func (v *VarsLoader) loadFromK8sObject(varsCtx *VarsCtx, varsSource types.VarsSourceClusterConfigMapOrSecret, kind string, key string, ignoreMissing bool, base64Decode bool) (*uo.UnstructuredObject, error) { +func (v *VarsLoader) loadFromK8sObject(varsCtx *VarsCtx, varsSource types.VarsSourceClusterConfigMapOrSecret, kind string, ignoreMissing bool, base64Decode bool) (*uo.UnstructuredObject, error) { if v.k == nil { return nil, fmt.Errorf("loading vars from cluster is disabled") } @@ -310,12 +310,12 @@ func (v *VarsLoader) loadFromK8sObject(varsCtx *VarsCtx, varsSource types.VarsSo ref := o.GetK8sRef() - f, found, err := o.GetNestedField("data", key) + f, found, err := o.GetNestedField("data", varsSource.Key) if err != nil { return nil, err } if !found { - return nil, fmt.Errorf("key %s not found in %s on cluster", key, ref.String()) + return nil, fmt.Errorf("key %s not found in %s on cluster", varsSource.Key, ref.String()) } var value string @@ -333,11 +333,34 @@ func (v *VarsLoader) loadFromK8sObject(varsCtx *VarsCtx, varsSource types.VarsSo } } - newVars, err := v.loadFromString(varsCtx, value) + doError := func(err error) (*uo.UnstructuredObject, error) { + return nil, fmt.Errorf("failed to load vars from kubernetes object %s and key %s: %w", ref.String(), varsSource.Key, err) + } + + var parsed any + err = v.renderYamlString(varsCtx, value, &parsed) if err != nil { - return nil, fmt.Errorf("failed to load vars from kubernetes object %s and key %s: %w", ref.String(), key, err) + return doError(err) + } + + if varsSource.TargetPath == "" { + m, ok := parsed.(map[string]any) + if !ok { + return doError(fmt.Errorf("value is not a YAML dictionary")) + } + return uo.FromMap(m), nil + } else { + p, err := uo.NewMyJsonPath(varsSource.TargetPath) + if err != nil { + return doError(err) + } + newVars := uo.New() + err = p.Set(newVars, parsed) + if err != nil { + return doError(err) + } + return newVars, nil } - return newVars, nil } func (v *VarsLoader) loadFromString(varsCtx *VarsCtx, s string) (*uo.UnstructuredObject, error) { diff --git a/pkg/vars/vars_loader_test.go b/pkg/vars/vars_loader_test.go index d4387a66d..b119e5278 100644 --- a/pkg/vars/vars_loader_test.go +++ b/pkg/vars/vars_loader_test.go @@ -293,7 +293,8 @@ func TestVarsLoader_ClusterConfigMap(t *testing.T) { cm := corev1.ConfigMap{ ObjectMeta: v1.ObjectMeta{Name: "cm", Namespace: "ns"}, Data: map[string]string{ - "vars": `{"test1": {"test2": 42}}`, + "vars": `{"test1": {"test2": 42}}`, + "value": "42", }, } @@ -343,6 +344,32 @@ func TestVarsLoader_ClusterConfigMap(t *testing.T) { }, nil, "") assert.NoError(t, err) }, &cm) + + testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { + err := vl.LoadVars(vc, &types.VarsSource{ + ClusterConfigMap: &types.VarsSourceClusterConfigMapOrSecret{ + Name: "cm", + Namespace: "ns", + Key: "value", + }, + }, nil, "") + assert.Errorf(t, err, "failed to load vars from kubernetes object ns/ConfigMap/cm and key value: value is not a YAML dictionary") + }, &cm) + + testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { + err := vl.LoadVars(vc, &types.VarsSource{ + ClusterConfigMap: &types.VarsSourceClusterConfigMapOrSecret{ + Name: "cm", + Namespace: "ns", + Key: "value", + TargetPath: "deep.nested.path", + }, + }, nil, "") + assert.NoError(t, err) + + v, _, _ := vc.Vars.GetNestedInt("deep", "nested", "path") + assert.Equal(t, int64(42), v) + }, &cm) } func TestVarsLoader_ClusterSecret(t *testing.T) { From 809c15465025b312d56b2335627d87f2b8c16649 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 May 2023 09:49:49 +0200 Subject: [PATCH 1544/2916] chore(deps): Bump github.com/go-playground/validator/v10 (#453) Bumps [github.com/go-playground/validator/v10](https://github.com/go-playground/validator) from 10.12.0 to 10.13.0. - [Release notes](https://github.com/go-playground/validator/releases) - [Commits](https://github.com/go-playground/validator/compare/v10.12.0...v10.13.0) --- updated-dependencies: - dependency-name: github.com/go-playground/validator/v10 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 5fc4bdaf8..b64d72345 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/cyphar/filepath-securejoin v0.2.3 github.com/docker/distribution v2.8.1+incompatible github.com/fluxcd/pkg/kustomize v1.1.1 - github.com/go-playground/validator/v10 v10.12.0 + github.com/go-playground/validator/v10 v10.13.0 github.com/gobwas/glob v0.2.3 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 github.com/google/go-containerregistry v0.14.0 @@ -174,7 +174,7 @@ require ( github.com/kylelemons/godebug v1.1.0 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect - github.com/leodido/go-urn v1.2.2 // indirect + github.com/leodido/go-urn v1.2.3 // indirect github.com/lib/pq v1.10.7 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/magiconair/properties v1.8.7 // indirect diff --git a/go.sum b/go.sum index 9ad1ee69d..5603be931 100644 --- a/go.sum +++ b/go.sum @@ -313,8 +313,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.12.0 h1:E4gtWgxWxp8YSxExrQFv5BpCahla0PVF2oTTEYaWQGI= -github.com/go-playground/validator/v10 v10.12.0/go.mod h1:hCAPuzYvKdP33pxWa+2+6AIKXEKqjIUyqsNCtbsSJrA= +github.com/go-playground/validator/v10 v10.13.0 h1:cFRQdfaSMCOSfGCCLB20MHvuoHb/s5G8L5pu2ppK5AQ= +github.com/go-playground/validator/v10 v10.13.0/go.mod h1:dwu7+CG8/CtBiJFZDz4e+5Upb6OLw04gtBYw0mcG/z4= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -562,8 +562,8 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= -github.com/leodido/go-urn v1.2.2 h1:7z68G0FCGvDk646jz1AelTYNYWrTNm0bEcFAo147wt4= -github.com/leodido/go-urn v1.2.2/go.mod h1:kUaIbLZWttglzwNuG0pgsh5vuV6u2YcGBYz1hIPjtOQ= +github.com/leodido/go-urn v1.2.3 h1:6BE2vPT0lqoz3fmOesHZiaiFh7889ssCo2GMvLCfiuA= +github.com/leodido/go-urn v1.2.3/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= @@ -743,7 +743,6 @@ github.com/rubenv/sql-migrate v1.3.1/go.mod h1:YzG/Vh82CwyhTFXy+Mf5ahAiiEOpAlHur github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/rwtodd/Go.Sed v0.0.0-20210816025313-55464686f9ef/go.mod h1:8AEUvGVi2uQ5b24BIhcr0GCcpd/RNAFWaN2CJFrWIIQ= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= From 60886ec9f02e37a05fbfceb5574814b40be666c3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 May 2023 09:50:02 +0200 Subject: [PATCH 1545/2916] chore(deps): Bump k8s.io/klog/v2 from 2.90.1 to 2.100.1 (#454) Bumps [k8s.io/klog/v2](https://github.com/kubernetes/klog) from 2.90.1 to 2.100.1. - [Release notes](https://github.com/kubernetes/klog/releases) - [Changelog](https://github.com/kubernetes/klog/blob/main/RELEASE.md) - [Commits](https://github.com/kubernetes/klog/compare/v2.90.1...v2.100.1) --- updated-dependencies: - dependency-name: k8s.io/klog/v2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b64d72345..99855b111 100644 --- a/go.mod +++ b/go.mod @@ -46,7 +46,7 @@ require ( k8s.io/apiextensions-apiserver v0.27.1 k8s.io/apimachinery v0.27.1 k8s.io/client-go v0.27.1 - k8s.io/klog/v2 v2.90.1 + k8s.io/klog/v2 v2.100.1 sigs.k8s.io/kustomize/kyaml v0.14.1 sigs.k8s.io/structured-merge-diff/v4 v4.2.3 ) diff --git a/go.sum b/go.sum index 5603be931..c955af503 100644 --- a/go.sum +++ b/go.sum @@ -1362,8 +1362,8 @@ k8s.io/client-go v0.27.1 h1:oXsfhW/qncM1wDmWBIuDzRHNS2tLhK3BZv512Nc59W8= k8s.io/client-go v0.27.1/go.mod h1:f8LHMUkVb3b9N8bWturc+EDtVVVwZ7ueTVquFAJb2vA= k8s.io/component-base v0.27.1 h1:kEB8p8lzi4gCs5f2SPU242vOumHJ6EOsOnDM3tTuDTM= k8s.io/component-base v0.27.1/go.mod h1:UGEd8+gxE4YWoigz5/lb3af3Q24w98pDseXcXZjw+E0= -k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw= -k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= +k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20230308215209-15aac26d736a h1:gmovKNur38vgoWfGtP5QOGNOA7ki4n6qNYoFAgMlNvg= k8s.io/kube-openapi v0.0.0-20230308215209-15aac26d736a/go.mod h1:y5VtZWM9sHHc2ZodIH/6SHzXj+TPU5USoA8lcIeKEKY= k8s.io/kubectl v0.26.0 h1:xmrzoKR9CyNdzxBmXV7jW9Ln8WMrwRK6hGbbf69o4T0= From 7ce6b68202b3f08e1df240480f180ce6b7ff7f03 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 May 2023 09:08:04 +0200 Subject: [PATCH 1546/2916] chore(deps): Bump golang.org/x/sync from 0.1.0 to 0.2.0 (#455) Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.1.0 to 0.2.0. - [Commits](https://github.com/golang/sync/compare/v0.1.0...v0.2.0) --- updated-dependencies: - dependency-name: golang.org/x/sync dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 99855b111..ec5a2fb01 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,7 @@ require ( github.com/xanzy/ssh-agent v0.3.3 golang.org/x/crypto v0.8.0 golang.org/x/net v0.9.0 - golang.org/x/sync v0.1.0 + golang.org/x/sync v0.2.0 golang.org/x/sys v0.7.0 golang.org/x/term v0.7.0 golang.org/x/text v0.9.0 diff --git a/go.sum b/go.sum index c955af503..a90cde171 100644 --- a/go.sum +++ b/go.sum @@ -1015,8 +1015,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= +golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= From 4c1edffdd859345e5ae4d82a75909cd3a93fd420 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 May 2023 09:08:32 +0200 Subject: [PATCH 1547/2916] chore(deps): Bump github.com/aws/aws-sdk-go-v2/service/secretsmanager (#456) Bumps [github.com/aws/aws-sdk-go-v2/service/secretsmanager](https://github.com/aws/aws-sdk-go-v2) from 1.19.6 to 1.19.7. - [Release notes](https://github.com/aws/aws-sdk-go-v2/releases) - [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go-v2/compare/service/efs/v1.19.6...service/efs/v1.19.7) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go-v2/service/secretsmanager dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ec5a2fb01..b1fb9bc5b 100644 --- a/go.mod +++ b/go.mod @@ -55,7 +55,7 @@ require ( filippo.io/age v1.1.1 github.com/aws/aws-sdk-go-v2 v1.18.0 github.com/aws/aws-sdk-go-v2/config v1.18.22 - github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.6 + github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.7 github.com/go-git/go-git/v5 v5.6.1 github.com/go-logr/logr v1.2.4 github.com/hashicorp/go-multierror v1.1.1 diff --git a/go.sum b/go.sum index a90cde171..029663850 100644 --- a/go.sum +++ b/go.sum @@ -139,8 +139,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27 h1:0iKliEXAc github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27/go.mod h1:EOwBD4J4S5qYszS5/3DpkejfuK+Z5/1uzICfPaZLtqw= github.com/aws/aws-sdk-go-v2/service/kms v1.17.5 h1:DkubF+BSEy0uX59+pYySzWFReN3fCcXobIO8L5Phh24= github.com/aws/aws-sdk-go-v2/service/kms v1.17.5/go.mod h1:ubAtMGRUMVv5kX8lpbeDguxZ64pR4kXTGApY4sCM0io= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.6 h1:xC25kY/HSssnA1lC0GFT8mfhmrpMql/24bkyWYDRgzU= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.6/go.mod h1:3ARttS6G6U3auEdKfaN4GlnfS9UxYE9nqub1+0YGycA= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.7 h1:W88E2kZGo+NHOsyvQbsOZYqxXJdLIqRzKadeVlv5J7k= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.7/go.mod h1:3ARttS6G6U3auEdKfaN4GlnfS9UxYE9nqub1+0YGycA= github.com/aws/aws-sdk-go-v2/service/sso v1.12.9 h1:GAiaQWuQhQQui76KjuXeShmyXqECwQ0mGRMc/rwsL+c= github.com/aws/aws-sdk-go-v2/service/sso v1.12.9/go.mod h1:ouy2P4z6sJN70fR3ka3wD3Ro3KezSxU6eKGQI2+2fjI= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.9 h1:TraLwncRJkWqtIBVKI/UqBymq4+hL+3MzUOtUATuzkA= From 7d39bb7e66cb59d52ee23e1a1dce5059157e4b7c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 May 2023 09:24:22 +0200 Subject: [PATCH 1548/2916] chore(deps): Bump golang.org/x/term from 0.7.0 to 0.8.0 (#457) Bumps [golang.org/x/term](https://github.com/golang/term) from 0.7.0 to 0.8.0. - [Commits](https://github.com/golang/term/compare/v0.7.0...v0.8.0) --- updated-dependencies: - dependency-name: golang.org/x/term dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index b1fb9bc5b..169af2d54 100644 --- a/go.mod +++ b/go.mod @@ -38,8 +38,8 @@ require ( golang.org/x/crypto v0.8.0 golang.org/x/net v0.9.0 golang.org/x/sync v0.2.0 - golang.org/x/sys v0.7.0 - golang.org/x/term v0.7.0 + golang.org/x/sys v0.8.0 + golang.org/x/term v0.8.0 golang.org/x/text v0.9.0 helm.sh/helm/v3 v3.11.3 k8s.io/api v0.27.1 diff --git a/go.sum b/go.sum index 029663850..926923bc1 100644 --- a/go.sum +++ b/go.sum @@ -1091,8 +1091,8 @@ golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1101,8 +1101,8 @@ golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ= -golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= +golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From 6b9590e4773ca8ca5b4ba5a7aea079082af3c9cb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 May 2023 09:24:31 +0200 Subject: [PATCH 1549/2916] chore(deps): Bump github.com/aws/aws-sdk-go-v2/config (#459) Bumps [github.com/aws/aws-sdk-go-v2/config](https://github.com/aws/aws-sdk-go-v2) from 1.18.22 to 1.18.23. - [Release notes](https://github.com/aws/aws-sdk-go-v2/releases) - [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go-v2/compare/config/v1.18.22...config/v1.18.23) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go-v2/config dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 169af2d54..baacdc9e8 100644 --- a/go.mod +++ b/go.mod @@ -54,7 +54,7 @@ require ( require ( filippo.io/age v1.1.1 github.com/aws/aws-sdk-go-v2 v1.18.0 - github.com/aws/aws-sdk-go-v2/config v1.18.22 + github.com/aws/aws-sdk-go-v2/config v1.18.23 github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.7 github.com/go-git/go-git/v5 v5.6.1 github.com/go-logr/logr v1.2.4 @@ -93,16 +93,16 @@ require ( github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect github.com/acomagu/bufpipe v1.0.4 // indirect github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.13.21 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.13.22 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.3.34 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27 // indirect github.com/aws/aws-sdk-go-v2/service/kms v1.17.5 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.12.9 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.9 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.18.10 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.12.10 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.10 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.18.11 // indirect github.com/aws/smithy-go v1.13.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver v3.5.1+incompatible // indirect diff --git a/go.sum b/go.sum index 926923bc1..10c068ca4 100644 --- a/go.sum +++ b/go.sum @@ -121,10 +121,10 @@ github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:W github.com/aws/aws-sdk-go-v2 v1.16.7/go.mod h1:6CpKuLXg2w7If3ABZCl/qZ6rEgwtjZTn4eAf4RcEyuw= github.com/aws/aws-sdk-go-v2 v1.18.0 h1:882kkTpSFhdgYRKVZ/VCgf7sd0ru57p2JCxz4/oN5RY= github.com/aws/aws-sdk-go-v2 v1.18.0/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= -github.com/aws/aws-sdk-go-v2/config v1.18.22 h1:7vkUEmjjv+giht4wIROqLs+49VWmiQMMHSduxmoNKLU= -github.com/aws/aws-sdk-go-v2/config v1.18.22/go.mod h1:mN7Li1wxaPxSSy4Xkr6stFuinJGf3VZW3ZSNvO0q6sI= -github.com/aws/aws-sdk-go-v2/credentials v1.13.21 h1:VRiXnPEaaPeGeoFcXvMZOB5K/yfIXOYE3q97Kgb0zbU= -github.com/aws/aws-sdk-go-v2/credentials v1.13.21/go.mod h1:90Dk1lJoMyspa/EDUrldTxsPns0wn6+KpRKpdAWc0uA= +github.com/aws/aws-sdk-go-v2/config v1.18.23 h1:gc3lPsAnZpwfi2exupmgHfva0JiAY2BWDg5JWYlmA28= +github.com/aws/aws-sdk-go-v2/config v1.18.23/go.mod h1:rx0ruaQ+gk3OrLFHRRx56lA//XxP8K8uPzeNiKNuWVY= +github.com/aws/aws-sdk-go-v2/credentials v1.13.22 h1:Hp9rwJS4giQ48xqonRV/s7QcDf/wxF6UY7osRmBabvI= +github.com/aws/aws-sdk-go-v2/credentials v1.13.22/go.mod h1:BfNcm6A9nSd+bzejDcMJ5RE+k6WbkCwWkQil7q4heRk= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3 h1:jJPgroehGvjrde3XufFIJUZVK5A2L9a3KwSFgKy9n8w= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3/go.mod h1:4Q0UFP0YJf0NrsEuEYHpM9fTSEVnD16Z3uyEF7J9JGM= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.14/go.mod h1:kdjrMwHwrC3+FsKhNcCMJ7tUVj/8uSD5CZXeQ4wV6fM= @@ -141,12 +141,12 @@ github.com/aws/aws-sdk-go-v2/service/kms v1.17.5 h1:DkubF+BSEy0uX59+pYySzWFReN3f github.com/aws/aws-sdk-go-v2/service/kms v1.17.5/go.mod h1:ubAtMGRUMVv5kX8lpbeDguxZ64pR4kXTGApY4sCM0io= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.7 h1:W88E2kZGo+NHOsyvQbsOZYqxXJdLIqRzKadeVlv5J7k= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.7/go.mod h1:3ARttS6G6U3auEdKfaN4GlnfS9UxYE9nqub1+0YGycA= -github.com/aws/aws-sdk-go-v2/service/sso v1.12.9 h1:GAiaQWuQhQQui76KjuXeShmyXqECwQ0mGRMc/rwsL+c= -github.com/aws/aws-sdk-go-v2/service/sso v1.12.9/go.mod h1:ouy2P4z6sJN70fR3ka3wD3Ro3KezSxU6eKGQI2+2fjI= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.9 h1:TraLwncRJkWqtIBVKI/UqBymq4+hL+3MzUOtUATuzkA= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.9/go.mod h1:AFvkxc8xfBe8XA+5St5XIHHrQQtkxqrRincx4hmMHOk= -github.com/aws/aws-sdk-go-v2/service/sts v1.18.10 h1:6UbNM/KJhMBfOI5+lpVcJ/8OA7cBSz0O6OX37SRKlSw= -github.com/aws/aws-sdk-go-v2/service/sts v1.18.10/go.mod h1:BgQOMsg8av8jset59jelyPW7NoZcZXLVpDsXunGDrk8= +github.com/aws/aws-sdk-go-v2/service/sso v1.12.10 h1:UBQjaMTCKwyUYwiVnUt6toEJwGXsLBI6al083tpjJzY= +github.com/aws/aws-sdk-go-v2/service/sso v1.12.10/go.mod h1:ouy2P4z6sJN70fR3ka3wD3Ro3KezSxU6eKGQI2+2fjI= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.10 h1:PkHIIJs8qvq0e5QybnZoG1K/9QTrLr9OsqCIo59jOBA= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.10/go.mod h1:AFvkxc8xfBe8XA+5St5XIHHrQQtkxqrRincx4hmMHOk= +github.com/aws/aws-sdk-go-v2/service/sts v1.18.11 h1:uBE+Zj478pfxV98L6SEpvxYiADNjTlMNY714PJLE7uo= +github.com/aws/aws-sdk-go-v2/service/sts v1.18.11/go.mod h1:BgQOMsg8av8jset59jelyPW7NoZcZXLVpDsXunGDrk8= github.com/aws/smithy-go v1.12.0/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8= github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= From ca55014c355afc4347099ba10c897cb5c6d62f7a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 20 Mar 2023 20:00:17 +0100 Subject: [PATCH 1550/2916] refactor: Move Command result into its own package --- cmd/kluctl/commands/cmd_delete.go | 4 +-- cmd/kluctl/commands/cmd_deploy.go | 6 ++-- cmd/kluctl/commands/command_result.go | 24 ++++++++-------- pkg/deployment/commands/deploy.go | 8 +++--- pkg/deployment/commands/diff.go | 6 ++-- pkg/deployment/commands/poke_images.go | 5 ++-- pkg/deployment/commands/validate.go | 24 ++++++++-------- pkg/deployment/utils/apply_utils.go | 14 +++++----- pkg/deployment/utils/delete_utils.go | 10 +++---- pkg/deployment/utils/diff_utils.go | 5 ++-- pkg/deployment/utils/diff_utils_test.go | 28 +++++++++---------- pkg/deployment/utils/errors_holder.go | 26 ++++++++--------- .../utils/remote_objects_utils_test.go | 4 +-- pkg/diff/diff.go | 20 ++++++------- pkg/diff/obfuscate.go | 6 ++-- pkg/types/{ => result}/command_result.go | 19 +++++++------ pkg/validation/validation.go | 14 +++++----- 17 files changed, 113 insertions(+), 110 deletions(-) rename pkg/types/{ => result}/command_result.go (70%) diff --git a/cmd/kluctl/commands/cmd_delete.go b/cmd/kluctl/commands/cmd_delete.go index 91d099332..3dd6f51a1 100644 --- a/cmd/kluctl/commands/cmd_delete.go +++ b/cmd/kluctl/commands/cmd_delete.go @@ -8,8 +8,8 @@ import ( "github.com/kluctl/kluctl/v2/pkg/deployment/utils" "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/status" - "github.com/kluctl/kluctl/v2/pkg/types" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/types/result" ) type deleteCmd struct { @@ -72,7 +72,7 @@ func (cmd *deleteCmd) Run(ctx context.Context) error { }) } -func confirmedDeleteObjects(ctx context.Context, k *k8s.K8sCluster, refs []k8s2.ObjectRef, dryRun bool, forceYes bool) (*types.CommandResult, error) { +func confirmedDeleteObjects(ctx context.Context, k *k8s.K8sCluster, refs []k8s2.ObjectRef, dryRun bool, forceYes bool) (*result.CommandResult, error) { if len(refs) != 0 { _, _ = getStderr(ctx).WriteString("The following objects will be deleted:\n") for _, ref := range refs { diff --git a/cmd/kluctl/commands/cmd_deploy.go b/cmd/kluctl/commands/cmd_deploy.go index d6b6a23a3..fba082d2e 100644 --- a/cmd/kluctl/commands/cmd_deploy.go +++ b/cmd/kluctl/commands/cmd_deploy.go @@ -6,7 +6,7 @@ import ( "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/deployment/commands" "github.com/kluctl/kluctl/v2/pkg/status" - "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/types/result" ) type deployCmd struct { @@ -63,7 +63,7 @@ func (cmd *deployCmd) runCmdDeploy(cmdCtx *commandCtx) error { cmd2.ReadinessTimeout = cmd.ReadinessTimeout cmd2.NoWait = cmd.NoWait - cb := func(diffResult *types.CommandResult) error { + cb := func(diffResult *result.CommandResult) error { return cmd.diffResultCb(cmdCtx.ctx, diffResult) } if cmd.Yes || cmd.DryRun { @@ -84,7 +84,7 @@ func (cmd *deployCmd) runCmdDeploy(cmdCtx *commandCtx) error { return nil } -func (cmd *deployCmd) diffResultCb(ctx context.Context, diffResult *types.CommandResult) error { +func (cmd *deployCmd) diffResultCb(ctx context.Context, diffResult *result.CommandResult) error { flags := cmd.OutputFormatFlags flags.OutputFormat = nil // use default output format diff --git a/cmd/kluctl/commands/command_result.go b/cmd/kluctl/commands/command_result.go index 27f648079..fe960eae7 100644 --- a/cmd/kluctl/commands/command_result.go +++ b/cmd/kluctl/commands/command_result.go @@ -7,8 +7,8 @@ import ( "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/diff" "github.com/kluctl/kluctl/v2/pkg/status" - "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/types/result" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/yaml" "io" @@ -16,7 +16,7 @@ import ( "strings" ) -func formatCommandResultText(cr *types.CommandResult, short bool) string { +func formatCommandResultText(cr *result.CommandResult, short bool) string { buf := bytes.NewBuffer(nil) if len(cr.Warnings) != 0 { @@ -84,7 +84,7 @@ func prettyObjectRefs(buf io.StringWriter, refs []k8s.ObjectRef) { } } -func prettyErrors(buf io.StringWriter, errors []types.DeploymentError) { +func prettyErrors(buf io.StringWriter, errors []result.DeploymentError) { for _, e := range errors { prefix := "" if s := e.Ref.String(); s != "" { @@ -94,7 +94,7 @@ func prettyErrors(buf io.StringWriter, errors []types.DeploymentError) { } } -func prettyChanges(buf io.StringWriter, ref k8s.ObjectRef, changes []types.Change) { +func prettyChanges(buf io.StringWriter, ref k8s.ObjectRef, changes []result.Change) { _, _ = buf.WriteString(fmt.Sprintf("Diff for object %s\n", ref.String())) var t utils.PrettyTable @@ -107,7 +107,7 @@ func prettyChanges(buf io.StringWriter, ref k8s.ObjectRef, changes []types.Chang _, _ = buf.WriteString(s) } -func formatCommandResultYaml(cr *types.CommandResult) (string, error) { +func formatCommandResultYaml(cr *result.CommandResult) (string, error) { b, err := yaml.WriteYamlString(cr) if err != nil { return "", err @@ -115,7 +115,7 @@ func formatCommandResultYaml(cr *types.CommandResult) (string, error) { return b, nil } -func formatCommandResult(cr *types.CommandResult, format string, short bool) (string, error) { +func formatCommandResult(cr *result.CommandResult, format string, short bool) (string, error) { switch format { case "text": return formatCommandResultText(cr, short), nil @@ -126,7 +126,7 @@ func formatCommandResult(cr *types.CommandResult, format string, short bool) (st } } -func prettyValidationResults(buf io.StringWriter, results []types.ValidateResultEntry) { +func prettyValidationResults(buf io.StringWriter, results []result.ValidateResultEntry) { var t utils.PrettyTable t.AddRow("Object", "Message") @@ -137,7 +137,7 @@ func prettyValidationResults(buf io.StringWriter, results []types.ValidateResult _, _ = buf.WriteString(s) } -func formatValidateResultText(vr *types.ValidateResult) string { +func formatValidateResultText(vr *result.ValidateResult) string { buf := bytes.NewBuffer(nil) if len(vr.Warnings) != 0 { @@ -163,7 +163,7 @@ func formatValidateResultText(vr *types.ValidateResult) string { return buf.String() } -func formatValidateResultYaml(vr *types.ValidateResult) (string, error) { +func formatValidateResultYaml(vr *result.ValidateResult) (string, error) { b, err := yaml.WriteYamlString(vr) if err != nil { return "", err @@ -171,7 +171,7 @@ func formatValidateResultYaml(vr *types.ValidateResult) (string, error) { return string(b), nil } -func formatValidateResult(vr *types.ValidateResult, format string) (string, error) { +func formatValidateResult(vr *result.ValidateResult, format string) (string, error) { switch format { case "text": return formatValidateResultText(vr), nil @@ -206,7 +206,7 @@ func outputHelper(ctx context.Context, output []string, cb func(format string) ( return nil } -func outputCommandResult(ctx context.Context, flags args.OutputFormatFlags, cr *types.CommandResult) error { +func outputCommandResult(ctx context.Context, flags args.OutputFormatFlags, cr *result.CommandResult) error { status.Flush(ctx) if !flags.NoObfuscate { @@ -224,7 +224,7 @@ func outputCommandResult(ctx context.Context, flags args.OutputFormatFlags, cr * }) } -func outputValidateResult(ctx context.Context, output []string, vr *types.ValidateResult) error { +func outputValidateResult(ctx context.Context, output []string, vr *result.ValidateResult) error { status.Flush(ctx) return outputHelper(ctx, output, func(format string) (string, error) { diff --git a/pkg/deployment/commands/deploy.go b/pkg/deployment/commands/deploy.go index a349fd103..a1f98531f 100644 --- a/pkg/deployment/commands/deploy.go +++ b/pkg/deployment/commands/deploy.go @@ -7,8 +7,8 @@ import ( utils2 "github.com/kluctl/kluctl/v2/pkg/deployment/utils" "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/status" - "github.com/kluctl/kluctl/v2/pkg/types" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/types/result" "time" ) @@ -31,7 +31,7 @@ func NewDeployCommand(discriminator string, c *deployment.DeploymentCollection) } } -func (cmd *DeployCommand) Run(ctx context.Context, k *k8s.K8sCluster, diffResultCb func(diffResult *types.CommandResult) error) (*types.CommandResult, error) { +func (cmd *DeployCommand) Run(ctx context.Context, k *k8s.K8sCluster, diffResultCb func(diffResult *result.CommandResult) error) (*result.CommandResult, error) { dew := utils2.NewDeploymentErrorsAndWarnings() if cmd.discriminator == "" { @@ -64,7 +64,7 @@ func (cmd *DeployCommand) Run(ctx context.Context, k *k8s.K8sCluster, diffResult du.Diff() orphanObjects, err := FindOrphanObjects(k, ru, cmd.c) - diffResult := &types.CommandResult{ + diffResult := &result.CommandResult{ NewObjects: au.GetNewObjects(), ChangedObjects: du.ChangedObjects, DeletedObjects: au.GetDeletedObjects(), @@ -98,7 +98,7 @@ func (cmd *DeployCommand) Run(ctx context.Context, k *k8s.K8sCluster, diffResult if err != nil { return nil, err } - return &types.CommandResult{ + return &result.CommandResult{ NewObjects: au.GetNewObjects(), ChangedObjects: du.ChangedObjects, DeletedObjects: au.GetDeletedObjects(), diff --git a/pkg/deployment/commands/diff.go b/pkg/deployment/commands/diff.go index 6a23ef6fb..335fecc91 100644 --- a/pkg/deployment/commands/diff.go +++ b/pkg/deployment/commands/diff.go @@ -7,8 +7,8 @@ import ( "github.com/kluctl/kluctl/v2/pkg/deployment/utils" "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/status" - "github.com/kluctl/kluctl/v2/pkg/types" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/types/result" ) type DiffCommand struct { @@ -30,7 +30,7 @@ func NewDiffCommand(discriminator string, c *deployment.DeploymentCollection) *D } } -func (cmd *DiffCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*types.CommandResult, error) { +func (cmd *DiffCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*result.CommandResult, error) { dew := utils.NewDeploymentErrorsAndWarnings() if cmd.discriminator == "" { @@ -65,7 +65,7 @@ func (cmd *DiffCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*types.Comm if err != nil { return nil, err } - return &types.CommandResult{ + return &result.CommandResult{ NewObjects: au.GetNewObjects(), ChangedObjects: du.ChangedObjects, DeletedObjects: au.GetDeletedObjects(), diff --git a/pkg/deployment/commands/poke_images.go b/pkg/deployment/commands/poke_images.go index 97baf5772..8af5d519d 100644 --- a/pkg/deployment/commands/poke_images.go +++ b/pkg/deployment/commands/poke_images.go @@ -8,6 +8,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/types" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/types/result" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "sync" ) @@ -22,7 +23,7 @@ func NewPokeImagesCommand(c *deployment.DeploymentCollection) *PokeImagesCommand } } -func (cmd *PokeImagesCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*types.CommandResult, error) { +func (cmd *PokeImagesCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*result.CommandResult, error) { var wg sync.WaitGroup dew := utils2.NewDeploymentErrorsAndWarnings() @@ -94,7 +95,7 @@ func (cmd *PokeImagesCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*type du := utils2.NewDiffUtil(dew, cmd.c.Deployments, ru, ad.GetAppliedObjectsMap()) du.Diff() - return &types.CommandResult{ + return &result.CommandResult{ NewObjects: nil, ChangedObjects: du.ChangedObjects, Errors: dew.GetErrorsList(), diff --git a/pkg/deployment/commands/validate.go b/pkg/deployment/commands/validate.go index f279d5042..b2dd91991 100644 --- a/pkg/deployment/commands/validate.go +++ b/pkg/deployment/commands/validate.go @@ -5,8 +5,8 @@ import ( "github.com/kluctl/kluctl/v2/pkg/deployment" utils2 "github.com/kluctl/kluctl/v2/pkg/deployment/utils" "github.com/kluctl/kluctl/v2/pkg/k8s" - "github.com/kluctl/kluctl/v2/pkg/types" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/types/result" "github.com/kluctl/kluctl/v2/pkg/validation" ) @@ -28,9 +28,9 @@ func NewValidateCommand(ctx context.Context, discriminator string, c *deployment return cmd } -func (cmd *ValidateCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*types.ValidateResult, error) { - var result types.ValidateResult - result.Ready = true +func (cmd *ValidateCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*result.ValidateResult, error) { + var r result.ValidateResult + r.Ready = true cmd.dew.Init() @@ -53,23 +53,23 @@ func (cmd *ValidateCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*types. remoteObject := cmd.ru.GetRemoteObject(ref) if remoteObject == nil { - result.Errors = append(result.Errors, types.DeploymentError{Ref: ref, Error: "object not found"}) + r.Errors = append(r.Errors, result.DeploymentError{Ref: ref, Error: "object not found"}) continue } r := validation.ValidateObject(k, remoteObject, true, false) if !r.Ready { - result.Ready = false + r.Ready = false } - result.Errors = append(result.Errors, r.Errors...) - result.Warnings = append(result.Warnings, r.Warnings...) - result.Results = append(result.Results, r.Results...) + r.Errors = append(r.Errors, r.Errors...) + r.Warnings = append(r.Warnings, r.Warnings...) + r.Results = append(r.Results, r.Results...) } } - result.Warnings = append(result.Warnings, cmd.dew.GetWarningsList()...) - result.Errors = append(result.Errors, cmd.dew.GetErrorsList()...) + r.Warnings = append(r.Warnings, cmd.dew.GetWarningsList()...) + r.Errors = append(r.Errors, cmd.dew.GetErrorsList()...) - return &result, nil + return &r, nil } func (cmd *ValidateCommand) ForgetRemoteObject(ref k8s2.ObjectRef) { diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index e77c78c36..d467c10c3 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -8,8 +8,8 @@ import ( "github.com/kluctl/kluctl/v2/pkg/diff" "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/status" - "github.com/kluctl/kluctl/v2/pkg/types" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/types/result" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/validation" @@ -754,14 +754,14 @@ func (a *ApplyUtil) ReplaceObject(ref k8s2.ObjectRef, firstVersion *uo.Unstructu a.HandleError(ref, fmt.Errorf("unexpected end of loop")) } -func (ad *ApplyDeploymentsUtil) collectObjects(f func(au *ApplyUtil) map[k8s2.ObjectRef]*uo.UnstructuredObject) []*types.RefAndObject { +func (ad *ApplyDeploymentsUtil) collectObjects(f func(au *ApplyUtil) map[k8s2.ObjectRef]*uo.UnstructuredObject) []*result.RefAndObject { ad.resultsMutex.Lock() defer ad.resultsMutex.Unlock() - var ret []*types.RefAndObject + var ret []*result.RefAndObject for _, a := range ad.results { for _, o := range f(a) { - ret = append(ret, &types.RefAndObject{ + ret = append(ret, &result.RefAndObject{ Ref: o.GetK8sRef(), Object: o, }) @@ -770,13 +770,13 @@ func (ad *ApplyDeploymentsUtil) collectObjects(f func(au *ApplyUtil) map[k8s2.Ob return ret } -func (ad *ApplyDeploymentsUtil) GetNewObjects() []*types.RefAndObject { +func (ad *ApplyDeploymentsUtil) GetNewObjects() []*result.RefAndObject { return ad.collectObjects(func(au *ApplyUtil) map[k8s2.ObjectRef]*uo.UnstructuredObject { return au.newObjects }) } -func (ad *ApplyDeploymentsUtil) GetAppliedObjects() []*types.RefAndObject { +func (ad *ApplyDeploymentsUtil) GetAppliedObjects() []*result.RefAndObject { return ad.collectObjects(func(au *ApplyUtil) map[k8s2.ObjectRef]*uo.UnstructuredObject { return au.appliedObjects }) @@ -790,7 +790,7 @@ func (ad *ApplyDeploymentsUtil) GetAppliedObjectsMap() map[k8s2.ObjectRef]*uo.Un return ret } -func (ad *ApplyDeploymentsUtil) GetAppliedHookObjects() []*types.RefAndObject { +func (ad *ApplyDeploymentsUtil) GetAppliedHookObjects() []*result.RefAndObject { return ad.collectObjects(func(au *ApplyUtil) map[k8s2.ObjectRef]*uo.UnstructuredObject { return au.appliedHookObjects }) diff --git a/pkg/deployment/utils/delete_utils.go b/pkg/deployment/utils/delete_utils.go index 00f72ad39..82a459eac 100644 --- a/pkg/deployment/utils/delete_utils.go +++ b/pkg/deployment/utils/delete_utils.go @@ -3,8 +3,8 @@ package utils import ( "context" "github.com/kluctl/kluctl/v2/pkg/k8s" - "github.com/kluctl/kluctl/v2/pkg/types" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/types/result" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -160,10 +160,10 @@ func FindObjectsForDelete(k *k8s.K8sCluster, allClusterObjects []*uo.Unstructure return ret, nil } -func DeleteObjects(ctx context.Context, k *k8s.K8sCluster, refs []k8s2.ObjectRef, doWait bool) (*types.CommandResult, error) { +func DeleteObjects(ctx context.Context, k *k8s.K8sCluster, refs []k8s2.ObjectRef, doWait bool) (*result.CommandResult, error) { g := utils.NewGoHelper(ctx, 8) - var ret types.CommandResult + var ret result.CommandResult namespaceNames := make(map[string]bool) var mutex sync.Mutex @@ -174,13 +174,13 @@ func DeleteObjects(ctx context.Context, k *k8s.K8sCluster, refs []k8s2.ObjectRef if err == nil { ret.DeletedObjects = append(ret.DeletedObjects, ref) } else { - ret.Errors = append(ret.Errors, types.DeploymentError{ + ret.Errors = append(ret.Errors, result.DeploymentError{ Ref: ref, Error: err.Error(), }) } for _, w := range apiWarnings { - ret.Warnings = append(ret.Warnings, types.DeploymentError{ + ret.Warnings = append(ret.Warnings, result.DeploymentError{ Ref: ref, Error: w.Text, }) diff --git a/pkg/deployment/utils/diff_utils.go b/pkg/deployment/utils/diff_utils.go index e62f33e16..dbfd952c3 100644 --- a/pkg/deployment/utils/diff_utils.go +++ b/pkg/deployment/utils/diff_utils.go @@ -5,6 +5,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/diff" "github.com/kluctl/kluctl/v2/pkg/types" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/types/result" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "sort" "sync" @@ -22,7 +23,7 @@ type diffUtil struct { IgnoreAnnotations bool remoteDiffObjects map[k8s2.ObjectRef]*uo.UnstructuredObject - ChangedObjects []*types.ChangedObject + ChangedObjects []*result.ChangedObject mutex sync.Mutex } @@ -98,7 +99,7 @@ func (u *diffUtil) diffObject(lo *uo.UnstructuredObject, diffRef k8s2.ObjectRef, u.mutex.Lock() defer u.mutex.Unlock() - u.ChangedObjects = append(u.ChangedObjects, &types.ChangedObject{ + u.ChangedObjects = append(u.ChangedObjects, &result.ChangedObject{ Ref: diffRef, NewObject: ao, OldObject: ro, diff --git a/pkg/deployment/utils/diff_utils_test.go b/pkg/deployment/utils/diff_utils_test.go index 042fd32ef..0f7296e1f 100644 --- a/pkg/deployment/utils/diff_utils_test.go +++ b/pkg/deployment/utils/diff_utils_test.go @@ -3,8 +3,8 @@ package utils import ( "context" "github.com/kluctl/kluctl/v2/pkg/deployment" - "github.com/kluctl/kluctl/v2/pkg/types" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/types/result" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/stretchr/testify/assert" "testing" @@ -65,8 +65,8 @@ func TestDiff(t *testing.T) { a: func(t *testing.T, dtc *diffTestConfig) { assert.Len(t, dtc.du.ChangedObjects, 1) assert.Equal(t, dtc.du.ChangedObjects[0].NewObject, dtc.ao[0]) - assert.Equal(t, []types.Change{ - types.Change{Type: "update", JsonPath: "data.d1", OldValue: "v1", NewValue: "v2", UnifiedDiff: "-v1\n+v2"}, + assert.Equal(t, []result.Change{ + result.Change{Type: "update", JsonPath: "data.d1", OldValue: "v1", NewValue: "v2", UnifiedDiff: "-v1\n+v2"}, }, dtc.du.ChangedObjects[0].Changes) }, }, @@ -78,8 +78,8 @@ func TestDiff(t *testing.T) { a: func(t *testing.T, dtc *diffTestConfig) { assert.Len(t, dtc.du.ChangedObjects, 1) assert.Equal(t, dtc.du.ChangedObjects[0].NewObject, dtc.ao[0]) - assert.Equal(t, []types.Change{ - types.Change{Type: "insert", JsonPath: "data.d2", OldValue: interface{}(nil), NewValue: "v2", UnifiedDiff: "+v2"}, + assert.Equal(t, []result.Change{ + result.Change{Type: "insert", JsonPath: "data.d2", OldValue: interface{}(nil), NewValue: "v2", UnifiedDiff: "+v2"}, }, dtc.du.ChangedObjects[0].Changes) }, }, @@ -91,8 +91,8 @@ func TestDiff(t *testing.T) { a: func(t *testing.T, dtc *diffTestConfig) { assert.Len(t, dtc.du.ChangedObjects, 1) assert.Equal(t, dtc.du.ChangedObjects[0].NewObject, dtc.ao[0]) - assert.Equal(t, []types.Change{ - types.Change{Type: "delete", JsonPath: "data.d2", OldValue: "v2", NewValue: interface{}(nil), UnifiedDiff: "-v2"}, + assert.Equal(t, []result.Change{ + result.Change{Type: "delete", JsonPath: "data.d2", OldValue: "v2", NewValue: interface{}(nil), UnifiedDiff: "-v2"}, }, dtc.du.ChangedObjects[0].Changes) }, }, @@ -104,9 +104,9 @@ func TestDiff(t *testing.T) { a: func(t *testing.T, dtc *diffTestConfig) { assert.Len(t, dtc.du.ChangedObjects, 1) assert.Equal(t, dtc.du.ChangedObjects[0].NewObject, dtc.ao[0]) - assert.Equal(t, []types.Change{ - types.Change{Type: "delete", JsonPath: "data.d1", OldValue: "v1", NewValue: interface{}(nil), UnifiedDiff: "-v1"}, - types.Change{Type: "insert", JsonPath: "data.d2", OldValue: interface{}(nil), NewValue: "v2", UnifiedDiff: "+v2"}, + assert.Equal(t, []result.Change{ + result.Change{Type: "delete", JsonPath: "data.d1", OldValue: "v1", NewValue: interface{}(nil), UnifiedDiff: "-v1"}, + result.Change{Type: "insert", JsonPath: "data.d2", OldValue: interface{}(nil), NewValue: "v2", UnifiedDiff: "+v2"}, }, dtc.du.ChangedObjects[0].Changes) }, }, @@ -118,10 +118,10 @@ func TestDiff(t *testing.T) { a: func(t *testing.T, dtc *diffTestConfig) { assert.Len(t, dtc.du.ChangedObjects, 1) assert.Equal(t, dtc.du.ChangedObjects[0].NewObject, dtc.ao[0]) - assert.Equal(t, []types.Change{ - types.Change{Type: "update", JsonPath: "data.d1", OldValue: "v1", NewValue: "v12", UnifiedDiff: "-v1\n+v12"}, - types.Change{Type: "delete", JsonPath: "data.d2", OldValue: "v2", NewValue: interface{}(nil), UnifiedDiff: "-v2"}, - types.Change{Type: "insert", JsonPath: "data.d3", OldValue: interface{}(nil), NewValue: "v3", UnifiedDiff: "+v3"}, + assert.Equal(t, []result.Change{ + result.Change{Type: "update", JsonPath: "data.d1", OldValue: "v1", NewValue: "v12", UnifiedDiff: "-v1\n+v12"}, + result.Change{Type: "delete", JsonPath: "data.d2", OldValue: "v2", NewValue: interface{}(nil), UnifiedDiff: "-v2"}, + result.Change{Type: "insert", JsonPath: "data.d3", OldValue: interface{}(nil), NewValue: "v3", UnifiedDiff: "+v3"}, }, dtc.du.ChangedObjects[0].Changes) }, }, diff --git a/pkg/deployment/utils/errors_holder.go b/pkg/deployment/utils/errors_holder.go index bf8faef1d..dd3c18a2d 100644 --- a/pkg/deployment/utils/errors_holder.go +++ b/pkg/deployment/utils/errors_holder.go @@ -5,14 +5,14 @@ import ( "fmt" "github.com/hashicorp/go-multierror" k8s2 "github.com/kluctl/kluctl/v2/pkg/k8s" - "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/types/result" "sync" ) type DeploymentErrorsAndWarnings struct { - errors map[k8s.ObjectRef]map[types.DeploymentError]bool - warnings map[k8s.ObjectRef]map[types.DeploymentError]bool + errors map[k8s.ObjectRef]map[result.DeploymentError]bool + warnings map[k8s.ObjectRef]map[result.DeploymentError]bool mutex sync.Mutex } @@ -25,12 +25,12 @@ func NewDeploymentErrorsAndWarnings() *DeploymentErrorsAndWarnings { func (dew *DeploymentErrorsAndWarnings) Init() { dew.mutex.Lock() defer dew.mutex.Unlock() - dew.warnings = map[k8s.ObjectRef]map[types.DeploymentError]bool{} - dew.errors = map[k8s.ObjectRef]map[types.DeploymentError]bool{} + dew.warnings = map[k8s.ObjectRef]map[result.DeploymentError]bool{} + dew.errors = map[k8s.ObjectRef]map[result.DeploymentError]bool{} } func (dew *DeploymentErrorsAndWarnings) AddWarning(ref k8s.ObjectRef, warning error) { - de := types.DeploymentError{ + de := result.DeploymentError{ Ref: ref, Error: warning.Error(), } @@ -38,14 +38,14 @@ func (dew *DeploymentErrorsAndWarnings) AddWarning(ref k8s.ObjectRef, warning er defer dew.mutex.Unlock() m, ok := dew.warnings[ref] if !ok { - m = make(map[types.DeploymentError]bool) + m = make(map[result.DeploymentError]bool) dew.warnings[ref] = m } m[de] = true } func (dew *DeploymentErrorsAndWarnings) AddError(ref k8s.ObjectRef, err error) { - de := types.DeploymentError{ + de := result.DeploymentError{ Ref: ref, Error: err.Error(), } @@ -53,7 +53,7 @@ func (dew *DeploymentErrorsAndWarnings) AddError(ref k8s.ObjectRef, err error) { defer dew.mutex.Unlock() m, ok := dew.errors[ref] if !ok { - m = make(map[types.DeploymentError]bool) + m = make(map[result.DeploymentError]bool) dew.errors[ref] = m } m[de] = true @@ -72,10 +72,10 @@ func (dew *DeploymentErrorsAndWarnings) HadError(ref k8s.ObjectRef) bool { return ok } -func (dew *DeploymentErrorsAndWarnings) GetErrorsList() []types.DeploymentError { +func (dew *DeploymentErrorsAndWarnings) GetErrorsList() []result.DeploymentError { dew.mutex.Lock() defer dew.mutex.Unlock() - var ret []types.DeploymentError + var ret []result.DeploymentError for _, m := range dew.errors { for e := range m { ret = append(ret, e) @@ -84,10 +84,10 @@ func (dew *DeploymentErrorsAndWarnings) GetErrorsList() []types.DeploymentError return ret } -func (dew *DeploymentErrorsAndWarnings) GetWarningsList() []types.DeploymentError { +func (dew *DeploymentErrorsAndWarnings) GetWarningsList() []result.DeploymentError { dew.mutex.Lock() defer dew.mutex.Unlock() - var ret []types.DeploymentError + var ret []result.DeploymentError for _, m := range dew.warnings { for e := range m { ret = append(ret, e) diff --git a/pkg/deployment/utils/remote_objects_utils_test.go b/pkg/deployment/utils/remote_objects_utils_test.go index 8ded2b28c..d1ba3a0d8 100644 --- a/pkg/deployment/utils/remote_objects_utils_test.go +++ b/pkg/deployment/utils/remote_objects_utils_test.go @@ -3,8 +3,8 @@ package utils import ( "context" "github.com/kluctl/kluctl/v2/pkg/k8s" - "github.com/kluctl/kluctl/v2/pkg/types" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/types/result" "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" @@ -42,7 +42,7 @@ func TestRemoteObjectUtils_PermissionErrors(t *testing.T) { k8s2.NewObjectRef("", "v1", "Secret", "secret", "default"), }, false) assert.NoError(t, err) - assert.Equal(t, []types.DeploymentError{{ + assert.Equal(t, []result.DeploymentError{{ Error: "at least one permission error was encountered while gathering objects by discriminator labels. This might result in orphan object detection to not work properly"}, }, dew.GetWarningsList()) assert.Empty(t, dew.GetErrorsList()) diff --git a/pkg/diff/diff.go b/pkg/diff/diff.go index f9f1a1c55..f654b0507 100644 --- a/pkg/diff/diff.go +++ b/pkg/diff/diff.go @@ -5,7 +5,7 @@ import ( "github.com/hexops/gotextdiff" "github.com/hexops/gotextdiff/myers" "github.com/hexops/gotextdiff/span" - "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/types/result" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" diff2 "github.com/r3labs/diff/v2" @@ -38,7 +38,7 @@ func convertPath(path []string, o interface{}) (string, error) { return ret.ToJsonPath(), nil } -func Diff(oldObject *uo.UnstructuredObject, newObject *uo.UnstructuredObject) ([]types.Change, error) { +func Diff(oldObject *uo.UnstructuredObject, newObject *uo.UnstructuredObject) ([]result.Change, error) { differ, err := diff2.NewDiffer(diff2.AllowTypeMismatch(true)) if err != nil { return nil, err @@ -48,7 +48,7 @@ func Diff(oldObject *uo.UnstructuredObject, newObject *uo.UnstructuredObject) ([ return nil, err } - var changes []types.Change + var changes []result.Change for _, c := range cl { c2, err := convertChange(c, oldObject, newObject) if err != nil { @@ -66,14 +66,14 @@ func Diff(oldObject *uo.UnstructuredObject, newObject *uo.UnstructuredObject) ([ return changes, nil } -func convertChange(c diff2.Change, oldObject *uo.UnstructuredObject, newObject *uo.UnstructuredObject) (*types.Change, error) { +func convertChange(c diff2.Change, oldObject *uo.UnstructuredObject, newObject *uo.UnstructuredObject) (*result.Change, error) { switch c.Type { case "create": p, err := convertPath(c.Path, newObject.Object) if err != nil { return nil, err } - return &types.Change{ + return &result.Change{ Type: "insert", JsonPath: p, NewValue: c.To, @@ -83,7 +83,7 @@ func convertChange(c diff2.Change, oldObject *uo.UnstructuredObject, newObject * if err != nil { return nil, err } - return &types.Change{ + return &result.Change{ Type: "delete", JsonPath: p, OldValue: c.From, @@ -93,7 +93,7 @@ func convertChange(c diff2.Change, oldObject *uo.UnstructuredObject, newObject * if err != nil { return nil, err } - return &types.Change{ + return &result.Change{ Type: "update", JsonPath: p, NewValue: c.To, @@ -103,7 +103,7 @@ func convertChange(c diff2.Change, oldObject *uo.UnstructuredObject, newObject * return nil, fmt.Errorf("unknown change type %s", c.Type) } -func updateUnifiedDiff(change *types.Change) error { +func updateUnifiedDiff(change *result.Change) error { switch change.Type { case "insert": ud, err := buildUnifiedDiff(notPresent, change.NewValue, false) @@ -133,7 +133,7 @@ func updateUnifiedDiff(change *types.Change) error { return nil } -func stableSortChanges(changes []types.Change) { +func stableSortChanges(changes []result.Change) { changesStrs := make([]string, len(changes)) changesIndexes := make([]int, len(changes)) for i, _ := range changes { @@ -149,7 +149,7 @@ func stableSortChanges(changes []types.Change) { return changesStrs[changesIndexes[i]] < changesStrs[changesIndexes[j]] }) - changesSorted := make([]types.Change, len(changes)) + changesSorted := make([]result.Change, len(changes)) for i, _ := range changes { changesSorted[i] = changes[changesIndexes[i]] } diff --git a/pkg/diff/obfuscate.go b/pkg/diff/obfuscate.go index e576c29b9..89036c142 100644 --- a/pkg/diff/obfuscate.go +++ b/pkg/diff/obfuscate.go @@ -2,8 +2,8 @@ package diff import ( "fmt" - "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/types/result" "github.com/ohler55/ojg/jp" "k8s.io/apimachinery/pkg/runtime/schema" "strings" @@ -14,7 +14,7 @@ var secretGvk = schema.GroupKind{Group: "", Kind: "Secret"} type Obfuscator struct { } -func (o *Obfuscator) Obfuscate(ref k8s.ObjectRef, changes []types.Change) error { +func (o *Obfuscator) Obfuscate(ref k8s.ObjectRef, changes []result.Change) error { if ref.GVK.GroupKind() == secretGvk { err := o.obfuscateSecret(ref, changes) if err != nil { @@ -24,7 +24,7 @@ func (o *Obfuscator) Obfuscate(ref k8s.ObjectRef, changes []types.Change) error return nil } -func (o *Obfuscator) obfuscateSecret(ref k8s.ObjectRef, changes []types.Change) error { +func (o *Obfuscator) obfuscateSecret(ref k8s.ObjectRef, changes []result.Change) error { replaceValues := func(x any, v string) any { if x == nil { return nil diff --git a/pkg/types/command_result.go b/pkg/types/result/command_result.go similarity index 70% rename from pkg/types/command_result.go rename to pkg/types/result/command_result.go index d6a235c9a..fce09787e 100644 --- a/pkg/types/command_result.go +++ b/pkg/types/result/command_result.go @@ -1,6 +1,7 @@ -package types +package result import ( + "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/utils/uo" ) @@ -31,14 +32,14 @@ type DeploymentError struct { } type CommandResult struct { - NewObjects []*RefAndObject `json:"newObjects,omitempty"` - ChangedObjects []*ChangedObject `json:"changedObjects,omitempty"` - HookObjects []*RefAndObject `json:"hookObjects,omitempty"` - OrphanObjects []k8s.ObjectRef `json:"orphanObjects,omitempty"` - DeletedObjects []k8s.ObjectRef `json:"deletedObjects,omitempty"` - Errors []DeploymentError `json:"errors,omitempty"` - Warnings []DeploymentError `json:"warnings,omitempty"` - SeenImages []FixedImage `json:"seenImages,omitempty"` + NewObjects []*RefAndObject `json:"newObjects,omitempty"` + ChangedObjects []*ChangedObject `json:"changedObjects,omitempty"` + HookObjects []*RefAndObject `json:"hookObjects,omitempty"` + OrphanObjects []k8s.ObjectRef `json:"orphanObjects,omitempty"` + DeletedObjects []k8s.ObjectRef `json:"deletedObjects,omitempty"` + Errors []DeploymentError `json:"errors,omitempty"` + Warnings []DeploymentError `json:"warnings,omitempty"` + SeenImages []types.FixedImage `json:"seenImages,omitempty"` } type ValidateResultEntry struct { diff --git a/pkg/validation/validation.go b/pkg/validation/validation.go index 36a034637..94adf5079 100644 --- a/pkg/validation/validation.go +++ b/pkg/validation/validation.go @@ -3,7 +3,7 @@ package validation import ( "fmt" "github.com/kluctl/kluctl/v2/pkg/k8s" - "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/types/result" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "k8s.io/apimachinery/pkg/api/errors" @@ -45,7 +45,7 @@ const ( reactNotReady ) -func ValidateObject(k *k8s.K8sCluster, o *uo.UnstructuredObject, notReadyIsError bool, forceStatusRequired bool) (ret types.ValidateResult) { +func ValidateObject(k *k8s.K8sCluster, o *uo.UnstructuredObject, notReadyIsError bool, forceStatusRequired bool) (ret result.ValidateResult) { ref := o.GetK8sRef() // We assume all is good in case no validation is performed @@ -61,17 +61,17 @@ func ValidateObject(k *k8s.K8sCluster, o *uo.UnstructuredObject, notReadyIsError // all good } else if e, ok := r.(error); ok { err := fmt.Errorf("panic in ValidateObject: %w", e) - ret.Errors = append(ret.Errors, types.DeploymentError{Ref: ref, Error: err.Error()}) + ret.Errors = append(ret.Errors, result.DeploymentError{Ref: ref, Error: err.Error()}) } else { err := fmt.Errorf("panic in ValidateObject: %v", e) - ret.Errors = append(ret.Errors, types.DeploymentError{Ref: ref, Error: err.Error()}) + ret.Errors = append(ret.Errors, result.DeploymentError{Ref: ref, Error: err.Error()}) } ret.Ready = false } }() for k, v := range o.GetK8sAnnotationsWithRegex(resultAnnotation) { - ret.Results = append(ret.Results, types.ValidateResultEntry{ + ret.Results = append(ret.Results, result.ValidateResultEntry{ Ref: ref, Annotation: k, Message: v, @@ -79,14 +79,14 @@ func ValidateObject(k *k8s.K8sCluster, o *uo.UnstructuredObject, notReadyIsError } addError := func(message string) { - ret.Errors = append(ret.Errors, types.DeploymentError{ + ret.Errors = append(ret.Errors, result.DeploymentError{ Ref: ref, Error: message, }) ret.Ready = false } addWarning := func(message string) { - ret.Warnings = append(ret.Warnings, types.DeploymentError{ + ret.Warnings = append(ret.Warnings, result.DeploymentError{ Ref: ref, Error: message, }) From 2f9019be3a8b9f9921d5dedb8077d34773146b9c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 20 Mar 2023 20:18:54 +0100 Subject: [PATCH 1551/2916] feat: Obfuscate new and hook objects --- cmd/kluctl/commands/command_result.go | 14 +++++++- pkg/diff/obfuscate.go | 47 ++++++++++++++++++++++++--- 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/cmd/kluctl/commands/command_result.go b/cmd/kluctl/commands/command_result.go index fe960eae7..d8203d5c0 100644 --- a/cmd/kluctl/commands/command_result.go +++ b/cmd/kluctl/commands/command_result.go @@ -212,7 +212,19 @@ func outputCommandResult(ctx context.Context, flags args.OutputFormatFlags, cr * if !flags.NoObfuscate { var obfuscator diff.Obfuscator for _, c := range cr.ChangedObjects { - err := obfuscator.Obfuscate(c.Ref, c.Changes) + err := obfuscator.ObfuscateChanges(c.Ref, c.Changes) + if err != nil { + return err + } + } + for _, n := range cr.NewObjects { + err := obfuscator.ObfuscateObject(n.Object) + if err != nil { + return err + } + } + for _, h := range cr.HookObjects { + err := obfuscator.ObfuscateObject(h.Object) if err != nil { return err } diff --git a/pkg/diff/obfuscate.go b/pkg/diff/obfuscate.go index 89036c142..00e9c0deb 100644 --- a/pkg/diff/obfuscate.go +++ b/pkg/diff/obfuscate.go @@ -1,22 +1,24 @@ package diff import ( + "encoding/base64" "fmt" "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/types/result" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/ohler55/ojg/jp" "k8s.io/apimachinery/pkg/runtime/schema" "strings" ) -var secretGvk = schema.GroupKind{Group: "", Kind: "Secret"} +var secretGk = schema.GroupKind{Group: "", Kind: "Secret"} type Obfuscator struct { } -func (o *Obfuscator) Obfuscate(ref k8s.ObjectRef, changes []result.Change) error { - if ref.GVK.GroupKind() == secretGvk { - err := o.obfuscateSecret(ref, changes) +func (o *Obfuscator) ObfuscateChanges(ref k8s.ObjectRef, changes []result.Change) error { + if ref.GVK.GroupKind() == secretGk { + err := o.obfuscateSecretChanges(ref, changes) if err != nil { return err } @@ -24,7 +26,18 @@ func (o *Obfuscator) Obfuscate(ref k8s.ObjectRef, changes []result.Change) error return nil } -func (o *Obfuscator) obfuscateSecret(ref k8s.ObjectRef, changes []result.Change) error { +func (o *Obfuscator) ObfuscateObject(x *uo.UnstructuredObject) error { + ref := x.GetK8sRef() + if ref.GVK.GroupKind() == secretGk { + err := o.obfuscateSecret(x) + if err != nil { + return err + } + } + return nil +} + +func (o *Obfuscator) obfuscateSecretChanges(ref k8s.ObjectRef, changes []result.Change) error { replaceValues := func(x any, v string) any { if x == nil { return nil @@ -69,3 +82,27 @@ func (o *Obfuscator) obfuscateSecret(ref k8s.ObjectRef, changes []result.Change) } return nil } + +func (o *Obfuscator) obfuscateSecret(x *uo.UnstructuredObject) error { + data, ok, _ := x.GetNestedField("data") + if ok { + if m, ok := data.(map[string]any); ok { + for k, _ := range m { + m[k] = base64.StdEncoding.EncodeToString([]byte("*****")) + } + } else { + return fmt.Errorf("'data' is not a map of strings") + } + } + data, ok, _ = x.GetNestedField("stringData") + if ok { + if m, ok := data.(map[string]any); ok { + for k, _ := range m { + m[k] = "*****" + } + } else { + return fmt.Errorf("'data' is not a map of strings") + } + } + return nil +} From e62348f04163529ce6e8eae796ebbb3c6d48babc Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 20 Mar 2023 20:24:43 +0100 Subject: [PATCH 1552/2916] refactor: Add ObfuscateResult to Obfuscator --- cmd/kluctl/commands/command_result.go | 20 +++----------------- pkg/diff/obfuscate.go | 22 ++++++++++++++++++++++ 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/cmd/kluctl/commands/command_result.go b/cmd/kluctl/commands/command_result.go index d8203d5c0..bbdfffc7e 100644 --- a/cmd/kluctl/commands/command_result.go +++ b/cmd/kluctl/commands/command_result.go @@ -211,23 +211,9 @@ func outputCommandResult(ctx context.Context, flags args.OutputFormatFlags, cr * if !flags.NoObfuscate { var obfuscator diff.Obfuscator - for _, c := range cr.ChangedObjects { - err := obfuscator.ObfuscateChanges(c.Ref, c.Changes) - if err != nil { - return err - } - } - for _, n := range cr.NewObjects { - err := obfuscator.ObfuscateObject(n.Object) - if err != nil { - return err - } - } - for _, h := range cr.HookObjects { - err := obfuscator.ObfuscateObject(h.Object) - if err != nil { - return err - } + err := obfuscator.ObfuscateResult(cr) + if err != nil { + return err } } diff --git a/pkg/diff/obfuscate.go b/pkg/diff/obfuscate.go index 00e9c0deb..1bcb7d96a 100644 --- a/pkg/diff/obfuscate.go +++ b/pkg/diff/obfuscate.go @@ -16,6 +16,28 @@ var secretGk = schema.GroupKind{Group: "", Kind: "Secret"} type Obfuscator struct { } +func (o *Obfuscator) ObfuscateResult(r *result.CommandResult) error { + for _, c := range r.ChangedObjects { + err := o.ObfuscateChanges(c.Ref, c.Changes) + if err != nil { + return err + } + } + for _, n := range r.NewObjects { + err := o.ObfuscateObject(n.Object) + if err != nil { + return err + } + } + for _, h := range r.HookObjects { + err := o.ObfuscateObject(h.Object) + if err != nil { + return err + } + } + return nil +} + func (o *Obfuscator) ObfuscateChanges(ref k8s.ObjectRef, changes []result.Change) error { if ref.GVK.GroupKind() == secretGk { err := o.obfuscateSecretChanges(ref, changes) From 1ad8778d14247e847144c818566dc2195edcc95f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 21 Mar 2023 17:47:23 +0100 Subject: [PATCH 1553/2916] feat: Add rendered objects/inlucdes/vars to types --- pkg/deployment/deployment_item.go | 3 +++ pkg/deployment/deployment_project.go | 13 +++++++++++++ pkg/types/deployment.go | 6 ++++++ pkg/types/vars_source.go | 3 +++ pkg/vars/vars_loader.go | 6 ++++++ 5 files changed, 31 insertions(+) diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 7c87d24b6..23f1192fa 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -225,6 +225,8 @@ func (di *DeploymentItem) renderHelmCharts() error { } } + di.Config.RenderedHelmChartConfig = hr.Config + return hr.Render(di.ctx.Ctx, di.ctx.K, di.ctx.K8sVersion, di.ctx.SopsDecrypter) }) if err != nil { @@ -533,6 +535,7 @@ func (di *DeploymentItem) buildKustomize() error { } o := uo.FromMap(y) di.Objects = append(di.Objects, o) + di.Config.RenderedObjects = append(di.Config.RenderedObjects, o) } return nil diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go index 88c496924..929073405 100644 --- a/pkg/deployment/deployment_project.go +++ b/pkg/deployment/deployment_project.go @@ -108,6 +108,18 @@ func (p *DeploymentProject) generateSingleKustomizeProject() error { } func (p *DeploymentProject) processConfig() error { + for _, item := range p.Config.Deployments { + if len(item.RenderedObjects) != 0 { + return fmt.Errorf("renderedObjects is not allowed here") + } + if item.RenderedHelmChartConfig != nil { + return fmt.Errorf("renderedHelmChartConfig is not allowed here") + } + if item.RenderedInclude != nil { + return fmt.Errorf("renderedInclude is not allowed here") + } + } + err := p.loadVarsList(p.VarsCtx, p.Config.Vars) if err != nil { return fmt.Errorf("failed to load deployment.yml vars: %w", err) @@ -228,6 +240,7 @@ func (p *DeploymentProject) loadIncludes() error { } else { continue } + inc.RenderedInclude = &newProject.Config newProject.parentProjectInclude = inc p.includes[i] = newProject } diff --git a/pkg/types/deployment.go b/pkg/types/deployment.go index f5695951f..6b39b7d0f 100644 --- a/pkg/types/deployment.go +++ b/pkg/types/deployment.go @@ -3,6 +3,7 @@ package types import ( "encoding/json" "github.com/go-playground/validator/v10" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" ) @@ -19,6 +20,11 @@ type DeploymentItemConfig struct { AlwaysDeploy bool `json:"alwaysDeploy,omitempty"` DeleteObjects []DeleteObjectItemConfig `json:"deleteObjects,omitempty"` When string `json:"when,omitempty"` + + // these are only allowed when writing the command result + RenderedHelmChartConfig *HelmChartConfig `json:"renderedHelmChartConfig,omitempty"` + RenderedObjects []*uo.UnstructuredObject `json:"renderedObjects,omitempty"` + RenderedInclude *DeploymentProjectConfig `json:"renderedInclude,omitempty"` } func ValidateDeploymentItemConfig(sl validator.StructLevel) { diff --git a/pkg/types/vars_source.go b/pkg/types/vars_source.go index c195cdace..f2c520f4e 100644 --- a/pkg/types/vars_source.go +++ b/pkg/types/vars_source.go @@ -69,6 +69,9 @@ type VarsSource struct { Vault *VarsSourceVault `json:"vault,omitempty"` When string `json:"when,omitempty"` + + // these are only allowed when writing the command result + RenderedVars *uo.UnstructuredObject `json:"renderedVars,omitempty"` } func ValidateVarsSource(sl validator.StructLevel) { diff --git a/pkg/vars/vars_loader.go b/pkg/vars/vars_loader.go index 49170f161..9c90b4000 100644 --- a/pkg/vars/vars_loader.go +++ b/pkg/vars/vars_loader.go @@ -63,6 +63,10 @@ func (v *VarsLoader) LoadVarsList(varsCtx *VarsCtx, varsList []*types.VarsSource } func (v *VarsLoader) LoadVars(varsCtx *VarsCtx, sourceIn *types.VarsSource, searchDirs []string, rootKey string) error { + if sourceIn.RenderedVars != nil && len(sourceIn.RenderedVars.Object) != 0 { + return fmt.Errorf("renderedVars is not allowed here") + } + var source types.VarsSource err := utils.DeepCopy(&source, sourceIn) if err != nil { @@ -125,6 +129,8 @@ func (v *VarsLoader) LoadVars(varsCtx *VarsCtx, sourceIn *types.VarsSource, sear return err } + sourceIn.RenderedVars = newVars.Clone() + if source.NoOverride == nil || !*source.NoOverride { varsCtx.Vars.Merge(newVars) } else { From 480fbd09737026a8b411d4e875db36fa52d449a3 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 21 Mar 2023 17:49:11 +0100 Subject: [PATCH 1554/2916] feat: Add info about the command to CommandResult --- cmd/kluctl/commands/cmd_delete.go | 4 +++ cmd/kluctl/commands/cmd_deploy.go | 4 +++ cmd/kluctl/commands/cmd_diff.go | 4 +++ cmd/kluctl/commands/cmd_poke_images.go | 4 +++ cmd/kluctl/commands/cmd_prune.go | 4 +++ cmd/kluctl/commands/utils.go | 45 ++++++++++++++++++++++++++ pkg/kluctl_project/load.go | 2 +- pkg/kluctl_project/project.go | 2 +- pkg/kluctl_project/project_load.go | 6 ++-- pkg/kluctl_project/target_context.go | 6 ++-- pkg/types/result/command_result.go | 38 ++++++++++++++++++++++ 11 files changed, 111 insertions(+), 8 deletions(-) diff --git a/cmd/kluctl/commands/cmd_delete.go b/cmd/kluctl/commands/cmd_delete.go index 3dd6f51a1..bb238bff3 100644 --- a/cmd/kluctl/commands/cmd_delete.go +++ b/cmd/kluctl/commands/cmd_delete.go @@ -61,6 +61,10 @@ func (cmd *deleteCmd) Run(ctx context.Context) error { if err != nil { return err } + err = addCommandInfo(result, "delete", cmdCtx, &cmd.TargetFlags, &cmd.ImageFlags, &cmd.InclusionFlags, &cmd.DryRunFlags, nil, nil, nil, false) + if err != nil { + return err + } err = outputCommandResult(ctx, cmd.OutputFormatFlags, result) if err != nil { return err diff --git a/cmd/kluctl/commands/cmd_deploy.go b/cmd/kluctl/commands/cmd_deploy.go index fba082d2e..469d93ae1 100644 --- a/cmd/kluctl/commands/cmd_deploy.go +++ b/cmd/kluctl/commands/cmd_deploy.go @@ -74,6 +74,10 @@ func (cmd *deployCmd) runCmdDeploy(cmdCtx *commandCtx) error { if err != nil { return err } + err = addCommandInfo(result, "deploy", cmdCtx, &cmd.TargetFlags, &cmd.ImageFlags, &cmd.InclusionFlags, &cmd.DryRunFlags, &cmd.ForceApplyFlags, &cmd.ReplaceOnErrorFlags, &cmd.AbortOnErrorFlags, cmd.NoWait) + if err != nil { + return err + } err = outputCommandResult(cmdCtx.ctx, cmd.OutputFormatFlags, result) if err != nil { return err diff --git a/cmd/kluctl/commands/cmd_diff.go b/cmd/kluctl/commands/cmd_diff.go index 21edf81f1..46838fcd3 100644 --- a/cmd/kluctl/commands/cmd_diff.go +++ b/cmd/kluctl/commands/cmd_diff.go @@ -50,6 +50,10 @@ func (cmd *diffCmd) Run(ctx context.Context) error { if err != nil { return err } + err = addCommandInfo(result, "diff", cmdCtx, &cmd.TargetFlags, &cmd.ImageFlags, &cmd.InclusionFlags, nil, &cmd.ForceApplyFlags, &cmd.ReplaceOnErrorFlags, nil, false) + if err != nil { + return err + } err = outputCommandResult(ctx, cmd.OutputFormatFlags, result) if err != nil { return err diff --git a/cmd/kluctl/commands/cmd_poke_images.go b/cmd/kluctl/commands/cmd_poke_images.go index 76b69a538..ac72efe70 100644 --- a/cmd/kluctl/commands/cmd_poke_images.go +++ b/cmd/kluctl/commands/cmd_poke_images.go @@ -51,6 +51,10 @@ func (cmd *pokeImagesCmd) Run(ctx context.Context) error { if err != nil { return err } + err = addCommandInfo(result, "poke-images", cmdCtx, &cmd.TargetFlags, &cmd.ImageFlags, &cmd.InclusionFlags, &cmd.DryRunFlags, nil, nil, nil, false) + if err != nil { + return err + } err = outputCommandResult(ctx, cmd.OutputFormatFlags, result) if err != nil { return err diff --git a/cmd/kluctl/commands/cmd_prune.go b/cmd/kluctl/commands/cmd_prune.go index c975dc8bd..ba60b59a8 100644 --- a/cmd/kluctl/commands/cmd_prune.go +++ b/cmd/kluctl/commands/cmd_prune.go @@ -54,6 +54,10 @@ func (cmd *pruneCmd) runCmdPrune(cmdCtx *commandCtx) error { if err != nil { return err } + err = addCommandInfo(result, "prune", cmdCtx, &cmd.TargetFlags, &cmd.ImageFlags, &cmd.InclusionFlags, &cmd.DryRunFlags, nil, nil, nil, false) + if err != nil { + return err + } err = outputCommandResult(cmdCtx.ctx, cmd.OutputFormatFlags, result) if err != nil { return err diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 32f447dd3..e41e9acd1 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -3,6 +3,7 @@ package commands import ( "context" "fmt" + "github.com/kluctl/kluctl/v2/pkg/types/result" "os" "strings" @@ -204,6 +205,50 @@ func withProjectTargetCommandContext(ctx context.Context, args projectTargetComm return cb(cmdCtx) } +func addCommandInfo(r *result.CommandResult, command string, ctx *commandCtx, targetFlags *args.TargetFlags, + imageFlags *args.ImageFlags, inclusionFlags *args.InclusionFlags, + dryRunFlags *args.DryRunFlags, forceApplyFlags *args.ForceApplyFlags, replaceOnErrorFlags *args.ReplaceOnErrorFlags, abortOnErrorFlags *args.AbortOnErrorFlags, noWait bool) error { + r.Command = &result.CommandInfo{ + Initiator: result.CommandInititiator_CommandLine, + Command: command, + Target: ctx.targetCtx.Target, + Args: ctx.targetCtx.KluctlProject.LoadArgs.ExternalArgs, + NoWait: noWait, + } + if targetFlags != nil { + r.Command.TargetNameOverride = targetFlags.TargetNameOverride + r.Command.ContextOverride = targetFlags.Context + } + if imageFlags != nil { + var err error + r.Command.Images, err = imageFlags.LoadFixedImagesFromArgs() + if err != nil { + return err + } + } + if inclusionFlags != nil { + r.Command.IncludeTags = inclusionFlags.IncludeTag + r.Command.ExcludeTags = inclusionFlags.ExcludeTag + r.Command.IncludeDeploymentDirs = inclusionFlags.IncludeDeploymentDir + r.Command.ExcludeDeploymentDirs = inclusionFlags.ExcludeDeploymentDir + } + if dryRunFlags != nil { + r.Command.DryRun = dryRunFlags.DryRun + } + if forceApplyFlags != nil { + r.Command.ForceApply = forceApplyFlags.ForceApply + } + if replaceOnErrorFlags != nil { + r.Command.ReplaceOnError = replaceOnErrorFlags.ReplaceOnError + r.Command.ForceReplaceOnError = replaceOnErrorFlags.ForceReplaceOnError + } + if abortOnErrorFlags != nil { + r.Command.AbortOnError = abortOnErrorFlags.AbortOnError + } + r.Deployment = &ctx.targetCtx.DeploymentProject.Config + return nil +} + func clientConfigGetter(forCompletion bool) func(context *string) (*rest.Config, *api.Config, error) { return func(context *string) (*rest.Config, *api.Config, error) { if forCompletion { diff --git a/pkg/kluctl_project/load.go b/pkg/kluctl_project/load.go index f320482ba..0535cbc61 100644 --- a/pkg/kluctl_project/load.go +++ b/pkg/kluctl_project/load.go @@ -13,7 +13,7 @@ func LoadKluctlProject(ctx context.Context, args LoadKluctlProjectArgs, tmpDir s p := &LoadedKluctlProject{ ctx: ctx, - loadArgs: args, + LoadArgs: args, TmpDir: tmpDir, J2: j2, RP: args.RP, diff --git a/pkg/kluctl_project/project.go b/pkg/kluctl_project/project.go index d959f5dc3..4fe0161ad 100644 --- a/pkg/kluctl_project/project.go +++ b/pkg/kluctl_project/project.go @@ -12,7 +12,7 @@ import ( type LoadedKluctlProject struct { ctx context.Context - loadArgs LoadKluctlProjectArgs + LoadArgs LoadKluctlProjectArgs TmpDir string diff --git a/pkg/kluctl_project/project_load.go b/pkg/kluctl_project/project_load.go index 0d9648db1..2982be334 100644 --- a/pkg/kluctl_project/project_load.go +++ b/pkg/kluctl_project/project_load.go @@ -25,7 +25,7 @@ type LoadKluctlProjectArgs struct { } func (c *LoadedKluctlProject) getConfigPath() string { - configPath := c.loadArgs.ProjectConfig + configPath := c.LoadArgs.ProjectConfig if configPath == "" { p := yaml.FixPathExt(filepath.Join(c.ProjectDir, ".kluctl.yml")) if utils.IsFile(p) { @@ -38,8 +38,8 @@ func (c *LoadedKluctlProject) getConfigPath() string { func (c *LoadedKluctlProject) loadKluctlProject() error { var err error - c.projectRootDir = c.loadArgs.RepoRoot - c.ProjectDir = c.loadArgs.ProjectDir + c.projectRootDir = c.LoadArgs.RepoRoot + c.ProjectDir = c.LoadArgs.ProjectDir err = utils.CheckInDir(c.projectRootDir, c.ProjectDir) if err != nil { return err diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index 15df07449..c431fc0e0 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -159,7 +159,7 @@ func (p *LoadedKluctlProject) loadK8sConfig(target *types.Target, offlineK8s boo var err error var clientConfig *rest.Config var restConfig *api.Config - clientConfig, restConfig, err = p.loadArgs.ClientConfigGetter(contextName) + clientConfig, restConfig, err = p.LoadArgs.ClientConfigGetter(contextName) if err != nil { return nil, "", err } @@ -193,8 +193,8 @@ func (p *LoadedKluctlProject) buildVars(target *types.Target, forSeal bool) (*va } } } - if p.loadArgs.ExternalArgs != nil { - allArgs.Merge(p.loadArgs.ExternalArgs) + if p.LoadArgs.ExternalArgs != nil { + allArgs.Merge(p.LoadArgs.ExternalArgs) } err = deployment.LoadDefaultArgs(p.Config.Args, allArgs) diff --git a/pkg/types/result/command_result.go b/pkg/types/result/command_result.go index fce09787e..414edeec6 100644 --- a/pkg/types/result/command_result.go +++ b/pkg/types/result/command_result.go @@ -31,7 +31,45 @@ type DeploymentError struct { Error string `json:"error"` } +type KluctlDeploymentInfo struct { + Name string `json:"name"` + Namespace string `json:"namespace"` + GitUrl string `json:"gitUrl"` + GitRef string `json:"gitRef"` +} + +type CommandInitiator string + +const ( + CommandInititiator_CommandLine CommandInitiator = "CommandLine" + CommandInititiator_KluctlDeployment = "KluctlDeployment" +) + +type CommandInfo struct { + Initiator CommandInitiator `json:"initiator" validate:"oneof=CommandLine KluctlDeployment"` + KluctlDeployment *KluctlDeploymentInfo `json:"kluctlDeployment,omitempty"` + Command string `json:"command,omitempty"` + Target *types.Target `json:"target,omitempty"` + TargetNameOverride string `json:"targetNameOverride,omitempty"` + ContextOverride string `json:"contextOverride,omitempty"` + Args *uo.UnstructuredObject `json:"args,omitempty"` + Images []types.FixedImage `json:"images,omitempty"` + DryRun bool `json:"dryRun,omitempty"` + NoWait bool `json:"noWait,omitempty"` + ForceApply bool `json:"forceApply,omitempty"` + ReplaceOnError bool `json:"replaceOnError,omitempty"` + ForceReplaceOnError bool `json:"forceReplaceOnError,omitempty"` + AbortOnError bool `json:"abortOnError,omitempty"` + IncludeTags []string `json:"includeTags,omitempty"` + ExcludeTags []string `json:"excludeTags,omitempty"` + IncludeDeploymentDirs []string `json:"includeDeploymentDirs,omitempty"` + ExcludeDeploymentDirs []string `json:"excludeDeploymentDirs,omitempty"` +} + type CommandResult struct { + Command *CommandInfo `json:"command,omitempty"` + Deployment *types.DeploymentProjectConfig `json:"deployment,omitempty"` + NewObjects []*RefAndObject `json:"newObjects,omitempty"` ChangedObjects []*ChangedObject `json:"changedObjects,omitempty"` HookObjects []*RefAndObject `json:"hookObjects,omitempty"` From ea216f912f6682d79fbf84207fef9521d50d915b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 21 Mar 2023 20:29:12 +0100 Subject: [PATCH 1555/2916] feat: Remove old/new objects from ChangedObject --- pkg/deployment/utils/diff_utils.go | 6 ++---- pkg/deployment/utils/diff_utils_test.go | 5 ----- pkg/types/result/command_result.go | 6 ++---- 3 files changed, 4 insertions(+), 13 deletions(-) diff --git a/pkg/deployment/utils/diff_utils.go b/pkg/deployment/utils/diff_utils.go index dbfd952c3..edffa3ef5 100644 --- a/pkg/deployment/utils/diff_utils.go +++ b/pkg/deployment/utils/diff_utils.go @@ -100,10 +100,8 @@ func (u *diffUtil) diffObject(lo *uo.UnstructuredObject, diffRef k8s2.ObjectRef, u.mutex.Lock() defer u.mutex.Unlock() u.ChangedObjects = append(u.ChangedObjects, &result.ChangedObject{ - Ref: diffRef, - NewObject: ao, - OldObject: ro, - Changes: changes, + Ref: diffRef, + Changes: changes, }) } } diff --git a/pkg/deployment/utils/diff_utils_test.go b/pkg/deployment/utils/diff_utils_test.go index 0f7296e1f..cb319d09a 100644 --- a/pkg/deployment/utils/diff_utils_test.go +++ b/pkg/deployment/utils/diff_utils_test.go @@ -64,7 +64,6 @@ func TestDiff(t *testing.T) { ao: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v2"})}, a: func(t *testing.T, dtc *diffTestConfig) { assert.Len(t, dtc.du.ChangedObjects, 1) - assert.Equal(t, dtc.du.ChangedObjects[0].NewObject, dtc.ao[0]) assert.Equal(t, []result.Change{ result.Change{Type: "update", JsonPath: "data.d1", OldValue: "v1", NewValue: "v2", UnifiedDiff: "-v1\n+v2"}, }, dtc.du.ChangedObjects[0].Changes) @@ -77,7 +76,6 @@ func TestDiff(t *testing.T) { ao: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v1", "d2": "v2"})}, a: func(t *testing.T, dtc *diffTestConfig) { assert.Len(t, dtc.du.ChangedObjects, 1) - assert.Equal(t, dtc.du.ChangedObjects[0].NewObject, dtc.ao[0]) assert.Equal(t, []result.Change{ result.Change{Type: "insert", JsonPath: "data.d2", OldValue: interface{}(nil), NewValue: "v2", UnifiedDiff: "+v2"}, }, dtc.du.ChangedObjects[0].Changes) @@ -90,7 +88,6 @@ func TestDiff(t *testing.T) { ao: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v1"})}, a: func(t *testing.T, dtc *diffTestConfig) { assert.Len(t, dtc.du.ChangedObjects, 1) - assert.Equal(t, dtc.du.ChangedObjects[0].NewObject, dtc.ao[0]) assert.Equal(t, []result.Change{ result.Change{Type: "delete", JsonPath: "data.d2", OldValue: "v2", NewValue: interface{}(nil), UnifiedDiff: "-v2"}, }, dtc.du.ChangedObjects[0].Changes) @@ -103,7 +100,6 @@ func TestDiff(t *testing.T) { ao: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d2": "v2"})}, a: func(t *testing.T, dtc *diffTestConfig) { assert.Len(t, dtc.du.ChangedObjects, 1) - assert.Equal(t, dtc.du.ChangedObjects[0].NewObject, dtc.ao[0]) assert.Equal(t, []result.Change{ result.Change{Type: "delete", JsonPath: "data.d1", OldValue: "v1", NewValue: interface{}(nil), UnifiedDiff: "-v1"}, result.Change{Type: "insert", JsonPath: "data.d2", OldValue: interface{}(nil), NewValue: "v2", UnifiedDiff: "+v2"}, @@ -117,7 +113,6 @@ func TestDiff(t *testing.T) { ao: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v12", "d3": "v3"})}, a: func(t *testing.T, dtc *diffTestConfig) { assert.Len(t, dtc.du.ChangedObjects, 1) - assert.Equal(t, dtc.du.ChangedObjects[0].NewObject, dtc.ao[0]) assert.Equal(t, []result.Change{ result.Change{Type: "update", JsonPath: "data.d1", OldValue: "v1", NewValue: "v12", UnifiedDiff: "-v1\n+v12"}, result.Change{Type: "delete", JsonPath: "data.d2", OldValue: "v2", NewValue: interface{}(nil), UnifiedDiff: "-v2"}, diff --git a/pkg/types/result/command_result.go b/pkg/types/result/command_result.go index 414edeec6..c04523a59 100644 --- a/pkg/types/result/command_result.go +++ b/pkg/types/result/command_result.go @@ -15,10 +15,8 @@ type Change struct { } type ChangedObject struct { - Ref k8s.ObjectRef `json:"ref"` - NewObject *uo.UnstructuredObject `json:"newObject,omitempty"` - OldObject *uo.UnstructuredObject `json:"oldObject,omitempty"` - Changes []Change `json:"changes,omitempty"` + Ref k8s.ObjectRef `json:"ref"` + Changes []Change `json:"changes,omitempty"` } type RefAndObject struct { From 962c0ed66681fb53f76bf674bdcd1074ce3c5c51 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 21 Mar 2023 20:36:36 +0100 Subject: [PATCH 1556/2916] feat: Remove RefAndObject from CommandResult --- cmd/kluctl/commands/command_result.go | 4 ++-- pkg/deployment/utils/apply_utils.go | 20 ++++++++------------ pkg/diff/obfuscate.go | 4 ++-- pkg/types/result/command_result.go | 21 ++++++++------------- 4 files changed, 20 insertions(+), 29 deletions(-) diff --git a/cmd/kluctl/commands/command_result.go b/cmd/kluctl/commands/command_result.go index bbdfffc7e..384f2739d 100644 --- a/cmd/kluctl/commands/command_result.go +++ b/cmd/kluctl/commands/command_result.go @@ -28,7 +28,7 @@ func formatCommandResultText(cr *result.CommandResult, short bool) string { buf.WriteString("\nNew objects:\n") var refs []k8s.ObjectRef for _, o := range cr.NewObjects { - refs = append(refs, o.Ref) + refs = append(refs, o.GetK8sRef()) } prettyObjectRefs(buf, refs) } @@ -61,7 +61,7 @@ func formatCommandResultText(cr *result.CommandResult, short bool) string { buf.WriteString("\nApplied hooks:\n") var refs []k8s.ObjectRef for _, o := range cr.HookObjects { - refs = append(refs, o.Ref) + refs = append(refs, o.GetK8sRef()) } prettyObjectRefs(buf, refs) } diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index d467c10c3..6e1b67b75 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -9,7 +9,6 @@ import ( "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/status" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" - "github.com/kluctl/kluctl/v2/pkg/types/result" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/validation" @@ -754,29 +753,26 @@ func (a *ApplyUtil) ReplaceObject(ref k8s2.ObjectRef, firstVersion *uo.Unstructu a.HandleError(ref, fmt.Errorf("unexpected end of loop")) } -func (ad *ApplyDeploymentsUtil) collectObjects(f func(au *ApplyUtil) map[k8s2.ObjectRef]*uo.UnstructuredObject) []*result.RefAndObject { +func (ad *ApplyDeploymentsUtil) collectObjects(f func(au *ApplyUtil) map[k8s2.ObjectRef]*uo.UnstructuredObject) []*uo.UnstructuredObject { ad.resultsMutex.Lock() defer ad.resultsMutex.Unlock() - var ret []*result.RefAndObject + var ret []*uo.UnstructuredObject for _, a := range ad.results { for _, o := range f(a) { - ret = append(ret, &result.RefAndObject{ - Ref: o.GetK8sRef(), - Object: o, - }) + ret = append(ret, o) } } return ret } -func (ad *ApplyDeploymentsUtil) GetNewObjects() []*result.RefAndObject { +func (ad *ApplyDeploymentsUtil) GetNewObjects() []*uo.UnstructuredObject { return ad.collectObjects(func(au *ApplyUtil) map[k8s2.ObjectRef]*uo.UnstructuredObject { return au.newObjects }) } -func (ad *ApplyDeploymentsUtil) GetAppliedObjects() []*result.RefAndObject { +func (ad *ApplyDeploymentsUtil) GetAppliedObjects() []*uo.UnstructuredObject { return ad.collectObjects(func(au *ApplyUtil) map[k8s2.ObjectRef]*uo.UnstructuredObject { return au.appliedObjects }) @@ -784,13 +780,13 @@ func (ad *ApplyDeploymentsUtil) GetAppliedObjects() []*result.RefAndObject { func (ad *ApplyDeploymentsUtil) GetAppliedObjectsMap() map[k8s2.ObjectRef]*uo.UnstructuredObject { ret := make(map[k8s2.ObjectRef]*uo.UnstructuredObject) - for _, ro := range ad.GetAppliedObjects() { - ret[ro.Ref] = ro.Object + for _, o := range ad.GetAppliedObjects() { + ret[o.GetK8sRef()] = o } return ret } -func (ad *ApplyDeploymentsUtil) GetAppliedHookObjects() []*result.RefAndObject { +func (ad *ApplyDeploymentsUtil) GetAppliedHookObjects() []*uo.UnstructuredObject { return ad.collectObjects(func(au *ApplyUtil) map[k8s2.ObjectRef]*uo.UnstructuredObject { return au.appliedHookObjects }) diff --git a/pkg/diff/obfuscate.go b/pkg/diff/obfuscate.go index 1bcb7d96a..10f0080d0 100644 --- a/pkg/diff/obfuscate.go +++ b/pkg/diff/obfuscate.go @@ -24,13 +24,13 @@ func (o *Obfuscator) ObfuscateResult(r *result.CommandResult) error { } } for _, n := range r.NewObjects { - err := o.ObfuscateObject(n.Object) + err := o.ObfuscateObject(n) if err != nil { return err } } for _, h := range r.HookObjects { - err := o.ObfuscateObject(h.Object) + err := o.ObfuscateObject(h) if err != nil { return err } diff --git a/pkg/types/result/command_result.go b/pkg/types/result/command_result.go index c04523a59..0c1251b5d 100644 --- a/pkg/types/result/command_result.go +++ b/pkg/types/result/command_result.go @@ -19,11 +19,6 @@ type ChangedObject struct { Changes []Change `json:"changes,omitempty"` } -type RefAndObject struct { - Ref k8s.ObjectRef `json:"ref"` - Object *uo.UnstructuredObject `json:"object,omitempty"` -} - type DeploymentError struct { Ref k8s.ObjectRef `json:"ref"` Error string `json:"error"` @@ -68,14 +63,14 @@ type CommandResult struct { Command *CommandInfo `json:"command,omitempty"` Deployment *types.DeploymentProjectConfig `json:"deployment,omitempty"` - NewObjects []*RefAndObject `json:"newObjects,omitempty"` - ChangedObjects []*ChangedObject `json:"changedObjects,omitempty"` - HookObjects []*RefAndObject `json:"hookObjects,omitempty"` - OrphanObjects []k8s.ObjectRef `json:"orphanObjects,omitempty"` - DeletedObjects []k8s.ObjectRef `json:"deletedObjects,omitempty"` - Errors []DeploymentError `json:"errors,omitempty"` - Warnings []DeploymentError `json:"warnings,omitempty"` - SeenImages []types.FixedImage `json:"seenImages,omitempty"` + NewObjects []*uo.UnstructuredObject `json:"newObjects,omitempty"` + ChangedObjects []*ChangedObject `json:"changedObjects,omitempty"` + HookObjects []*uo.UnstructuredObject `json:"hookObjects,omitempty"` + OrphanObjects []k8s.ObjectRef `json:"orphanObjects,omitempty"` + DeletedObjects []k8s.ObjectRef `json:"deletedObjects,omitempty"` + Errors []DeploymentError `json:"errors,omitempty"` + Warnings []DeploymentError `json:"warnings,omitempty"` + SeenImages []types.FixedImage `json:"seenImages,omitempty"` } type ValidateResultEntry struct { From b3bc713e7d6d20c2fc692420e735ceb12114dc5c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 24 Mar 2023 20:57:17 +0100 Subject: [PATCH 1557/2916] fix: Ignore RenderedVars in ValidateVarsSource --- pkg/types/vars_source.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/types/vars_source.go b/pkg/types/vars_source.go index f2c520f4e..6e09d549c 100644 --- a/pkg/types/vars_source.go +++ b/pkg/types/vars_source.go @@ -81,7 +81,7 @@ func ValidateVarsSource(sl validator.StructLevel) { v := reflect.ValueOf(s) for i := 0; i < v.NumField(); i++ { switch v.Type().Field(i).Name { - case "IgnoreMissing", "NoOverride", "When": + case "IgnoreMissing", "NoOverride", "When", "RenderedVars": continue } if !v.Field(i).IsNil() { From 4028a41ae7242309e0a76ddb4f1d2d6e2803d7b8 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 24 Mar 2023 22:59:27 +0100 Subject: [PATCH 1558/2916] fix: Flatten ObjectRef --- cmd/kluctl/commands/cmd_flux_reconcile.go | 4 +- cmd/kluctl/commands/cmd_flux_resume.go | 2 +- cmd/kluctl/commands/cmd_flux_suspend.go | 2 +- pkg/deployment/deployment_collection.go | 15 +++++-- pkg/deployment/deployment_item.go | 2 +- pkg/deployment/images.go | 2 +- pkg/deployment/utils/apply_utils.go | 8 ++-- pkg/deployment/utils/delete_utils.go | 8 ++-- pkg/deployment/utils/remote_objects_utils.go | 14 +++++-- pkg/diff/obfuscate.go | 4 +- pkg/k8s/k8s_cluster.go | 8 ++-- pkg/k8s/resources.go | 4 +- pkg/seal/bootstrap.go | 10 +++-- pkg/types/k8s/ref.go | 44 +++++++++++++++----- pkg/utils/uo/k8s_fields.go | 5 ++- pkg/validation/validation.go | 2 +- 16 files changed, 91 insertions(+), 43 deletions(-) diff --git a/cmd/kluctl/commands/cmd_flux_reconcile.go b/cmd/kluctl/commands/cmd_flux_reconcile.go index 91e345112..d4d49ed7c 100644 --- a/cmd/kluctl/commands/cmd_flux_reconcile.go +++ b/cmd/kluctl/commands/cmd_flux_reconcile.go @@ -36,7 +36,7 @@ func (cmd *fluxReconcileCmd) Run(ctx context.Context) error { return err } - ref := k8s2.ObjectRef{GVK: args.KluctlDeploymentGVK, Name: kd, Namespace: ns} + ref := k8s2.NewObjectRef(args.KluctlDeploymentGVK.Group, args.KluctlDeploymentGVK.Version, args.KluctlDeploymentGVK.Kind, kd, ns) patch := []k8s.JsonPatch{{ Op: "replace", Path: "/metadata/annotations", @@ -59,7 +59,7 @@ func (cmd *fluxReconcileCmd) Run(ctx context.Context) error { if err != nil { return err } - ref2 := k8s2.ObjectRef{GVK: args.GitRepositoryGVK, Name: sourceName, Namespace: sourceNamespace} + ref2 := k8s2.NewObjectRef(args.GitRepositoryGVK.Group, args.GitRepositoryGVK.Version, args.GitRepositoryGVK.Kind, sourceName, sourceNamespace) s := status.Start(ctx, "Annotating Source %s in %s namespace", sourceName, sourceNamespace) defer s.Failed() diff --git a/cmd/kluctl/commands/cmd_flux_resume.go b/cmd/kluctl/commands/cmd_flux_resume.go index d2fbc7d69..8d116bc1e 100644 --- a/cmd/kluctl/commands/cmd_flux_resume.go +++ b/cmd/kluctl/commands/cmd_flux_resume.go @@ -27,7 +27,7 @@ func (cmd *fluxResumeCmd) Run(ctx context.Context) error { return err } - ref := k8s2.ObjectRef{GVK: args.KluctlDeploymentGVK, Name: kd, Namespace: ns} + ref := k8s2.NewObjectRef(args.KluctlDeploymentGVK.Group, args.KluctlDeploymentGVK.Version, args.KluctlDeploymentGVK.Kind, kd, ns) patch := []k8s.JsonPatch{{ Op: "replace", diff --git a/cmd/kluctl/commands/cmd_flux_suspend.go b/cmd/kluctl/commands/cmd_flux_suspend.go index dc90d9c2b..d7981c1a6 100644 --- a/cmd/kluctl/commands/cmd_flux_suspend.go +++ b/cmd/kluctl/commands/cmd_flux_suspend.go @@ -26,7 +26,7 @@ func (cmd *fluxSuspendCmd) Run(ctx context.Context) error { return err } - ref := k8s2.ObjectRef{GVK: args.KluctlDeploymentGVK, Name: kd, Namespace: ns} + ref := k8s2.NewObjectRef(args.KluctlDeploymentGVK.Group, args.KluctlDeploymentGVK.Version, args.KluctlDeploymentGVK.Kind, kd, ns) patch := []k8s.JsonPatch{{ Op: "replace", Path: "/spec/suspend", diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index c89c289f1..dd7bfb426 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -7,6 +7,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/types" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" "path/filepath" "sync" ) @@ -227,11 +228,19 @@ func (c *DeploymentCollection) buildKustomizeObjects() error { return g.ErrorOrNil() } -func (c *DeploymentCollection) LocalObjectsByRef() map[k8s2.ObjectRef]bool { - ret := make(map[k8s2.ObjectRef]bool) +func (c *DeploymentCollection) LocalObjects() []*uo.UnstructuredObject { + var ret []*uo.UnstructuredObject + for _, d := range c.Deployments { + ret = append(ret, d.Objects...) + } + return ret +} + +func (c *DeploymentCollection) LocalObjectsByRef() map[k8s2.ObjectRef]*uo.UnstructuredObject { + ret := make(map[k8s2.ObjectRef]*uo.UnstructuredObject) for _, d := range c.Deployments { for _, o := range d.Objects { - ret[o.GetK8sRef()] = true + ret[o.GetK8sRef()] = o } } return ret diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 23f1192fa..f3cb14777 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -535,7 +535,7 @@ func (di *DeploymentItem) buildKustomize() error { } o := uo.FromMap(y) di.Objects = append(di.Objects, o) - di.Config.RenderedObjects = append(di.Config.RenderedObjects, o) + di.Config.RenderedObjects = append(di.Config.RenderedObjects, o.GetK8sRef()) } return nil diff --git a/pkg/deployment/images.go b/pkg/deployment/images.go index 715f0c327..f9e6df475 100644 --- a/pkg/deployment/images.go +++ b/pkg/deployment/images.go @@ -205,7 +205,7 @@ func (images *Images) ResolvePlaceholders(ctx context.Context, k *k8s.K8sCluster } ref := o.GetK8sRef() - deployment := fmt.Sprintf("%s/%s", ref.GVK.Kind, ref.Name) + deployment := fmt.Sprintf("%s/%s", ref.Kind, ref.Name) var remoteObject *uo.UnstructuredObject triedRemoteObject := false diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index 6e1b67b75..4f9d84fef 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -382,10 +382,10 @@ func (a *ApplyUtil) ApplyObject(x *uo.UnstructuredObject, replaced bool, hook bo return } } - if r != nil && ref.GVK.GroupKind().String() == "Namespace" { + if r != nil && ref.GroupKind().String() == "Namespace" { a.allNamespaces.Store(ref.Name, r) } - if r != nil && ref.GVK.GroupKind().String() == "CustomResourceDefinition.apiextensions.k8s.io" { + if r != nil && ref.GroupKind().String() == "CustomResourceDefinition.apiextensions.k8s.io" { a.handleObservedCRD(r) } a.handleApiWarnings(ref, apiWarnings) @@ -506,7 +506,9 @@ func (a *ApplyUtil) applyDeploymentItem(d *deployment.DeploymentItem) { for _, x := range d.Config.DeleteObjects { for _, gvk := range a.k.Resources.GetFilteredGVKs(k8s.BuildGVKFilter(x.Group, nil, x.Kind)) { ref := k8s2.ObjectRef{ - GVK: gvk, + Group: gvk.Group, + Version: gvk.Version, + Kind: gvk.Kind, Name: x.Name, Namespace: x.Namespace, } diff --git a/pkg/deployment/utils/delete_utils.go b/pkg/deployment/utils/delete_utils.go index 82a459eac..90a24c04e 100644 --- a/pkg/deployment/utils/delete_utils.go +++ b/pkg/deployment/utils/delete_utils.go @@ -42,7 +42,7 @@ var deleteOrder = [][]string{ func objectRefForExclusion(k *k8s.K8sCluster, ref k8s2.ObjectRef) k8s2.ObjectRef { ref = k.Resources.FixNamespaceInRef(ref) - ref.GVK.Version = "" + ref.Version = "" return ref } @@ -84,7 +84,7 @@ func filterObjectsForDelete(k *k8s.K8sCluster, objects []*uo.UnstructuredObject, for _, o := range objects { ref := o.GetK8sRef() - if _, ok := filteredResources[ref.GVK.GroupKind()]; !ok { + if _, ok := filteredResources[ref.GroupKind()]; !ok { continue } @@ -189,7 +189,7 @@ func DeleteObjects(ctx context.Context, k *k8s.K8sCluster, refs []k8s2.ObjectRef for _, ref_ := range refs { ref := ref_ - if ref.GVK.GroupVersion().String() == "v1" && ref.GVK.Kind == "Namespace" { + if ref.GroupVersion().String() == "v1" && ref.Kind == "Namespace" { namespaceNames[ref.Name] = true g.Run(func() { apiWarnings, err := k.DeleteSingleObject(ref, k8s.DeleteOptions{NoWait: !doWait, IgnoreNotFoundError: true}) @@ -201,7 +201,7 @@ func DeleteObjects(ctx context.Context, k *k8s.K8sCluster, refs []k8s2.ObjectRef for _, ref_ := range refs { ref := ref_ - if ref.GVK.GroupVersion().String() == "v1" && ref.GVK.Kind == "Namespace" { + if ref.GroupVersion().String() == "v1" && ref.Kind == "Namespace" { continue } if _, ok := namespaceNames[ref.Namespace]; ok { diff --git a/pkg/deployment/utils/remote_objects_utils.go b/pkg/deployment/utils/remote_objects_utils.go index 37e078595..0676f86e8 100644 --- a/pkg/deployment/utils/remote_objects_utils.go +++ b/pkg/deployment/utils/remote_objects_utils.go @@ -69,7 +69,11 @@ func (u *RemoteObjectUtils) getAllByDiscriminator(k *k8s.K8sCluster, discriminat gvk := gvk g.Run(func() { l, apiWarnings, err := k.ListObjects(gvk, "", labels) - u.dew.AddApiWarnings(k8s2.ObjectRef{GVK: gvk}, apiWarnings) + u.dew.AddApiWarnings(k8s2.ObjectRef{ + Group: gvk.Group, + Version: gvk.Version, + Kind: gvk.Kind, + }, apiWarnings) mutex.Lock() defer mutex.Unlock() if err != nil { @@ -81,7 +85,11 @@ func (u *RemoteObjectUtils) getAllByDiscriminator(k *k8s.K8sCluster, discriminat permissionErrCount += 1 return } - u.dew.AddWarning(k8s2.ObjectRef{GVK: gvk}, err) + u.dew.AddWarning(k8s2.ObjectRef{ + Group: gvk.Group, + Version: gvk.Version, + Kind: gvk.Kind, + }, err) return } for _, o := range l { @@ -175,7 +183,7 @@ func (u *RemoteObjectUtils) UpdateRemoteObjects(k *k8s.K8sCluster, discriminator if onlyUsedGKs { usedGKs = map[schema.GroupKind]bool{} for _, ref := range refs { - usedGKs[ref.GVK.GroupKind()] = true + usedGKs[ref.GroupKind()] = true } } diff --git a/pkg/diff/obfuscate.go b/pkg/diff/obfuscate.go index 10f0080d0..80a16e151 100644 --- a/pkg/diff/obfuscate.go +++ b/pkg/diff/obfuscate.go @@ -39,7 +39,7 @@ func (o *Obfuscator) ObfuscateResult(r *result.CommandResult) error { } func (o *Obfuscator) ObfuscateChanges(ref k8s.ObjectRef, changes []result.Change) error { - if ref.GVK.GroupKind() == secretGk { + if ref.GroupKind() == secretGk { err := o.obfuscateSecretChanges(ref, changes) if err != nil { return err @@ -50,7 +50,7 @@ func (o *Obfuscator) ObfuscateChanges(ref k8s.ObjectRef, changes []result.Change func (o *Obfuscator) ObfuscateObject(x *uo.UnstructuredObject) error { ref := x.GetK8sRef() - if ref.GVK.GroupKind() == secretGk { + if ref.GroupKind() == secretGk { err := o.obfuscateSecret(x) if err != nil { return err diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index 31b5a227c..e76955bb1 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -136,7 +136,7 @@ func (k *K8sCluster) ListObjects(gvk schema.GroupVersionKind, namespace string, func (k *K8sCluster) GetSingleObject(ref k8s.ObjectRef) (*uo.UnstructuredObject, []ApiWarning, error) { var result *uo.UnstructuredObject - apiWarnings, err := k.clients.withDynamicClientForGVK(k.Resources, ref.GVK, ref.Namespace, func(r dynamic.ResourceInterface) error { + apiWarnings, err := k.clients.withDynamicClientForGVK(k.Resources, ref.GroupVersionKind(), ref.Namespace, func(r dynamic.ResourceInterface) error { o := v1.GetOptions{} x, err := r.Get(k.ctx, ref.Name, o) if err != nil { @@ -165,7 +165,7 @@ func (k *K8sCluster) DeleteSingleObject(ref k8s.ObjectRef, options DeleteOptions o.DryRun = []string{"All"} } - apiWarnings, err := k.clients.withDynamicClientForGVK(k.Resources, ref.GVK, ref.Namespace, func(r dynamic.ResourceInterface) error { + apiWarnings, err := k.clients.withDynamicClientForGVK(k.Resources, ref.GroupVersionKind(), ref.Namespace, func(r dynamic.ResourceInterface) error { err := r.Delete(k.ctx, ref.Name, o) if err != nil { if options.IgnoreNotFoundError && errors.IsNotFound(err) { @@ -318,7 +318,7 @@ func (k *K8sCluster) doPatch(ref k8s.ObjectRef, data []byte, patchType types.Pat status.Trace(k.ctx, "patching %s", ref.String()) var result *uo.UnstructuredObject - apiWarnings, err := k.clients.withDynamicClientForGVK(k.Resources, ref.GVK, ref.Namespace, func(r dynamic.ResourceInterface) error { + apiWarnings, err := k.clients.withDynamicClientForGVK(k.Resources, ref.GroupVersionKind(), ref.Namespace, func(r dynamic.ResourceInterface) error { x, err := r.Patch(k.ctx, ref.Name, patchType, data, po) if err != nil { return fmt.Errorf("failed to patch %s: %w", ref.String(), err) @@ -370,7 +370,7 @@ func (k *K8sCluster) UpdateObject(o *uo.UnstructuredObject, options UpdateOption status.Trace(k.ctx, "updating %s", ref.String()) var result *uo.UnstructuredObject - apiWarnings, err := k.clients.withDynamicClientForGVK(k.Resources, ref.GVK, ref.Namespace, func(r dynamic.ResourceInterface) error { + apiWarnings, err := k.clients.withDynamicClientForGVK(k.Resources, ref.GroupVersionKind(), ref.Namespace, func(r dynamic.ResourceInterface) error { x, err := r.Update(k.ctx, o.ToUnstructured(), updateOpts) if err != nil { return err diff --git a/pkg/k8s/resources.go b/pkg/k8s/resources.go index 9a49b9093..902131e07 100644 --- a/pkg/k8s/resources.go +++ b/pkg/k8s/resources.go @@ -252,7 +252,7 @@ func (k *k8sResources) IsNamespaced(gv schema.GroupKind) *bool { func (k *k8sResources) FixNamespace(o *uo.UnstructuredObject, def string) { ref := o.GetK8sRef() - namespaced := k.IsNamespaced(ref.GVK.GroupKind()) + namespaced := k.IsNamespaced(ref.GroupKind()) if namespaced == nil { return } @@ -264,7 +264,7 @@ func (k *k8sResources) FixNamespace(o *uo.UnstructuredObject, def string) { } func (k *k8sResources) FixNamespaceInRef(ref k8s.ObjectRef) k8s.ObjectRef { - namespaced := k.IsNamespaced(ref.GVK.GroupKind()) + namespaced := k.IsNamespaced(ref.GroupKind()) if namespaced == nil { return ref } diff --git a/pkg/seal/bootstrap.go b/pkg/seal/bootstrap.go index 84761159d..81c786346 100644 --- a/pkg/seal/bootstrap.go +++ b/pkg/seal/bootstrap.go @@ -24,8 +24,10 @@ const configMapName = "sealed-secrets-key-kluctl-bootstrap" func BootstrapSealedSecrets(ctx context.Context, k *k8s.K8sCluster, namespace string) error { existing, _, err := k.GetSingleObject(k8s2.ObjectRef{ - GVK: schema.GroupVersionKind{Group: "apiextensions.k8s.io", Version: "v1", Kind: "CustomResourceDefinition"}, - Name: "sealedsecrets.bitnami.com", + Group: "apiextensions.k8s.io", + Version: "v1", + Kind: "CustomResourceDefinition", + Name: "sealedsecrets.bitnami.com", }) if existing != nil { // no bootstrap needed as the sealed-secrets operator seams to be installed already @@ -33,7 +35,9 @@ func BootstrapSealedSecrets(ctx context.Context, k *k8s.K8sCluster, namespace st } existing, _, err = k.GetSingleObject(k8s2.ObjectRef{ - GVK: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "ConfigMap"}, + Group: "", + Version: "v1", + Kind: "ConfigMap", Name: configMapName, Namespace: namespace, }) diff --git a/pkg/types/k8s/ref.go b/pkg/types/k8s/ref.go index c3e3da2bc..fa91157e3 100644 --- a/pkg/types/k8s/ref.go +++ b/pkg/types/k8s/ref.go @@ -6,30 +6,52 @@ import ( ) type ObjectRef struct { - GVK schema.GroupVersionKind `json:"gvk,inline"` - Name string - Namespace string + Group string `json:"group"` + Version string `json:"version"` + Kind string `json:"kind"` + Name string `json:"name"` + Namespace string `json:"namespace"` } func (r ObjectRef) String() string { if r.Namespace != "" { - return fmt.Sprintf("%s/%s/%s", r.Namespace, r.GVK.Kind, r.Name) + return fmt.Sprintf("%s/%s/%s", r.Namespace, r.Kind, r.Name) } else { if r.Name != "" { - return fmt.Sprintf("%s/%s", r.GVK.Kind, r.Name) + return fmt.Sprintf("%s/%s", r.Kind, r.Name) } else { - return r.GVK.Kind + return r.Kind } } } +func (r ObjectRef) GroupVersionKind() schema.GroupVersionKind { + return schema.GroupVersionKind{ + Group: r.Group, + Version: r.Version, + Kind: r.Kind, + } +} + +func (r ObjectRef) GroupKind() schema.GroupKind { + return schema.GroupKind{ + Group: r.Group, + Kind: r.Kind, + } +} + +func (r ObjectRef) GroupVersion() schema.GroupVersion { + return schema.GroupVersion{ + Group: r.Group, + Version: r.Version, + } +} + func NewObjectRef(group string, version string, kind string, name string, namespace string) ObjectRef { return ObjectRef{ - GVK: schema.GroupVersionKind{ - Group: group, - Version: version, - Kind: kind, - }, + Group: group, + Version: version, + Kind: kind, Name: name, Namespace: namespace, } diff --git a/pkg/utils/uo/k8s_fields.go b/pkg/utils/uo/k8s_fields.go index f97296dee..88cc49884 100644 --- a/pkg/utils/uo/k8s_fields.go +++ b/pkg/utils/uo/k8s_fields.go @@ -83,8 +83,11 @@ func (uo *UnstructuredObject) SetK8sNamespace(namespace string) { } func (uo *UnstructuredObject) GetK8sRef() k8s.ObjectRef { + gvk := uo.GetK8sGVK() return k8s.ObjectRef{ - GVK: uo.GetK8sGVK(), + Group: gvk.Group, + Version: gvk.Version, + Kind: gvk.Kind, Name: uo.GetK8sName(), Namespace: uo.GetK8sNamespace(), } diff --git a/pkg/validation/validation.go b/pkg/validation/validation.go index 94adf5079..eb88b480b 100644 --- a/pkg/validation/validation.go +++ b/pkg/validation/validation.go @@ -126,7 +126,7 @@ func ValidateObject(k *k8s.K8sCluster, o *uo.UnstructuredObject, notReadyIsError // can't really say anything... return } - s, err := k.Resources.GetSchemaForGVK(ref.GVK) + s, err := k.Resources.GetSchemaForGVK(ref.GroupVersionKind()) if err != nil && !errors.IsNotFound(err) { addError(err.Error()) return From 4baec6191bfee28e8956156e8ab57b486d016844 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 27 Mar 2023 09:00:43 +0200 Subject: [PATCH 1559/2916] feat: Only store refs in RenderedObjects of DeploymentItems --- cmd/kluctl/commands/utils.go | 1 + pkg/types/deployment.go | 4 ++-- pkg/types/result/command_result.go | 5 +++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index e41e9acd1..229ecd281 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -246,6 +246,7 @@ func addCommandInfo(r *result.CommandResult, command string, ctx *commandCtx, ta r.Command.AbortOnError = abortOnErrorFlags.AbortOnError } r.Deployment = &ctx.targetCtx.DeploymentProject.Config + r.RenderedObjects = ctx.targetCtx.DeploymentCollection.LocalObjects() return nil } diff --git a/pkg/types/deployment.go b/pkg/types/deployment.go index 6b39b7d0f..22517edab 100644 --- a/pkg/types/deployment.go +++ b/pkg/types/deployment.go @@ -3,7 +3,7 @@ package types import ( "encoding/json" "github.com/go-playground/validator/v10" - "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/yaml" ) @@ -23,7 +23,7 @@ type DeploymentItemConfig struct { // these are only allowed when writing the command result RenderedHelmChartConfig *HelmChartConfig `json:"renderedHelmChartConfig,omitempty"` - RenderedObjects []*uo.UnstructuredObject `json:"renderedObjects,omitempty"` + RenderedObjects []k8s.ObjectRef `json:"renderedObjects,omitempty"` RenderedInclude *DeploymentProjectConfig `json:"renderedInclude,omitempty"` } diff --git a/pkg/types/result/command_result.go b/pkg/types/result/command_result.go index 0c1251b5d..f509cf07e 100644 --- a/pkg/types/result/command_result.go +++ b/pkg/types/result/command_result.go @@ -60,8 +60,9 @@ type CommandInfo struct { } type CommandResult struct { - Command *CommandInfo `json:"command,omitempty"` - Deployment *types.DeploymentProjectConfig `json:"deployment,omitempty"` + Command *CommandInfo `json:"command,omitempty"` + Deployment *types.DeploymentProjectConfig `json:"deployment,omitempty"` + RenderedObjects []*uo.UnstructuredObject `json:"renderedObjects,omitempty"` NewObjects []*uo.UnstructuredObject `json:"newObjects,omitempty"` ChangedObjects []*ChangedObject `json:"changedObjects,omitempty"` From 51c32f7843a71e729025c8acbac16359b167d6ce Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 27 Mar 2023 21:22:56 +0200 Subject: [PATCH 1560/2916] feat: Rename error to message --- cmd/kluctl/commands/command_result.go | 2 +- pkg/deployment/commands/validate.go | 2 +- pkg/deployment/utils/apply_utils.go | 2 +- pkg/deployment/utils/delete_utils.go | 8 ++++---- pkg/deployment/utils/errors_holder.go | 10 +++++----- pkg/deployment/utils/remote_objects_utils_test.go | 2 +- pkg/types/result/command_result.go | 4 ++-- pkg/validation/validation.go | 12 ++++++------ 8 files changed, 21 insertions(+), 21 deletions(-) diff --git a/cmd/kluctl/commands/command_result.go b/cmd/kluctl/commands/command_result.go index 384f2739d..6815a8b89 100644 --- a/cmd/kluctl/commands/command_result.go +++ b/cmd/kluctl/commands/command_result.go @@ -90,7 +90,7 @@ func prettyErrors(buf io.StringWriter, errors []result.DeploymentError) { if s := e.Ref.String(); s != "" { prefix = fmt.Sprintf("%s: ", s) } - _, _ = buf.WriteString(fmt.Sprintf(" %s%s\n", prefix, e.Error)) + _, _ = buf.WriteString(fmt.Sprintf(" %s%s\n", prefix, e.Message)) } } diff --git a/pkg/deployment/commands/validate.go b/pkg/deployment/commands/validate.go index b2dd91991..08cd62004 100644 --- a/pkg/deployment/commands/validate.go +++ b/pkg/deployment/commands/validate.go @@ -53,7 +53,7 @@ func (cmd *ValidateCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*result remoteObject := cmd.ru.GetRemoteObject(ref) if remoteObject == nil { - r.Errors = append(r.Errors, result.DeploymentError{Ref: ref, Error: "object not found"}) + r.Errors = append(r.Errors, result.DeploymentError{Ref: ref, Message: "object not found"}) continue } r := validation.ValidateObject(k, remoteObject, true, false) diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index 4f9d84fef..0b610412e 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -461,7 +461,7 @@ func (a *ApplyUtil) WaitReadiness(ref k8s2.ObjectRef, timeout time.Duration) boo status.Warning(a.ctx, "Cancelled waiting for %s due to errors (%ds elapsed)", ref.String(), elapsed) } for _, e := range v.Errors { - a.HandleError(ref, fmt.Errorf(e.Error)) + a.HandleError(ref, fmt.Errorf(e.Message)) } return false } diff --git a/pkg/deployment/utils/delete_utils.go b/pkg/deployment/utils/delete_utils.go index 90a24c04e..f247f9f27 100644 --- a/pkg/deployment/utils/delete_utils.go +++ b/pkg/deployment/utils/delete_utils.go @@ -175,14 +175,14 @@ func DeleteObjects(ctx context.Context, k *k8s.K8sCluster, refs []k8s2.ObjectRef ret.DeletedObjects = append(ret.DeletedObjects, ref) } else { ret.Errors = append(ret.Errors, result.DeploymentError{ - Ref: ref, - Error: err.Error(), + Ref: ref, + Message: err.Error(), }) } for _, w := range apiWarnings { ret.Warnings = append(ret.Warnings, result.DeploymentError{ - Ref: ref, - Error: w.Text, + Ref: ref, + Message: w.Text, }) } } diff --git a/pkg/deployment/utils/errors_holder.go b/pkg/deployment/utils/errors_holder.go index dd3c18a2d..363c5eeac 100644 --- a/pkg/deployment/utils/errors_holder.go +++ b/pkg/deployment/utils/errors_holder.go @@ -31,8 +31,8 @@ func (dew *DeploymentErrorsAndWarnings) Init() { func (dew *DeploymentErrorsAndWarnings) AddWarning(ref k8s.ObjectRef, warning error) { de := result.DeploymentError{ - Ref: ref, - Error: warning.Error(), + Ref: ref, + Message: warning.Error(), } dew.mutex.Lock() defer dew.mutex.Unlock() @@ -46,8 +46,8 @@ func (dew *DeploymentErrorsAndWarnings) AddWarning(ref k8s.ObjectRef, warning er func (dew *DeploymentErrorsAndWarnings) AddError(ref k8s.ObjectRef, err error) { de := result.DeploymentError{ - Ref: ref, - Error: err.Error(), + Ref: ref, + Message: err.Error(), } dew.mutex.Lock() defer dew.mutex.Unlock() @@ -99,7 +99,7 @@ func (dew *DeploymentErrorsAndWarnings) GetWarningsList() []result.DeploymentErr func (dew *DeploymentErrorsAndWarnings) getPlainErrorsList() []error { var ret []error for _, e := range dew.GetErrorsList() { - ret = append(ret, errors.New(e.Error)) + ret = append(ret, errors.New(e.Message)) } return ret } diff --git a/pkg/deployment/utils/remote_objects_utils_test.go b/pkg/deployment/utils/remote_objects_utils_test.go index d1ba3a0d8..449cfedf7 100644 --- a/pkg/deployment/utils/remote_objects_utils_test.go +++ b/pkg/deployment/utils/remote_objects_utils_test.go @@ -43,7 +43,7 @@ func TestRemoteObjectUtils_PermissionErrors(t *testing.T) { }, false) assert.NoError(t, err) assert.Equal(t, []result.DeploymentError{{ - Error: "at least one permission error was encountered while gathering objects by discriminator labels. This might result in orphan object detection to not work properly"}, + Message: "at least one permission error was encountered while gathering objects by discriminator labels. This might result in orphan object detection to not work properly"}, }, dew.GetWarningsList()) assert.Empty(t, dew.GetErrorsList()) } diff --git a/pkg/types/result/command_result.go b/pkg/types/result/command_result.go index f509cf07e..ef1737e8e 100644 --- a/pkg/types/result/command_result.go +++ b/pkg/types/result/command_result.go @@ -20,8 +20,8 @@ type ChangedObject struct { } type DeploymentError struct { - Ref k8s.ObjectRef `json:"ref"` - Error string `json:"error"` + Ref k8s.ObjectRef `json:"ref"` + Message string `json:"message"` } type KluctlDeploymentInfo struct { diff --git a/pkg/validation/validation.go b/pkg/validation/validation.go index eb88b480b..09f23d401 100644 --- a/pkg/validation/validation.go +++ b/pkg/validation/validation.go @@ -61,10 +61,10 @@ func ValidateObject(k *k8s.K8sCluster, o *uo.UnstructuredObject, notReadyIsError // all good } else if e, ok := r.(error); ok { err := fmt.Errorf("panic in ValidateObject: %w", e) - ret.Errors = append(ret.Errors, result.DeploymentError{Ref: ref, Error: err.Error()}) + ret.Errors = append(ret.Errors, result.DeploymentError{Ref: ref, Message: err.Error()}) } else { err := fmt.Errorf("panic in ValidateObject: %v", e) - ret.Errors = append(ret.Errors, result.DeploymentError{Ref: ref, Error: err.Error()}) + ret.Errors = append(ret.Errors, result.DeploymentError{Ref: ref, Message: err.Error()}) } ret.Ready = false } @@ -80,15 +80,15 @@ func ValidateObject(k *k8s.K8sCluster, o *uo.UnstructuredObject, notReadyIsError addError := func(message string) { ret.Errors = append(ret.Errors, result.DeploymentError{ - Ref: ref, - Error: message, + Ref: ref, + Message: message, }) ret.Ready = false } addWarning := func(message string) { ret.Warnings = append(ret.Warnings, result.DeploymentError{ - Ref: ref, - Error: message, + Ref: ref, + Message: message, }) } addNotReady := func(message string) { From 4fe6777744e8f06fa40f5d94ab5de8c79ade24ed Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 3 Apr 2023 10:08:28 +0200 Subject: [PATCH 1561/2916] refactor: Let delete/prune commands actually perform the deletion and return a proper CommandResult --- cmd/kluctl/commands/cmd_delete.go | 20 +++++++----------- cmd/kluctl/commands/cmd_prune.go | 9 ++++---- pkg/deployment/commands/delete.go | 31 +++++++++++++++++++++++++--- pkg/deployment/commands/prune.go | 26 +++++++++++++++++++++-- pkg/deployment/utils/delete_utils.go | 21 ++++++------------- 5 files changed, 69 insertions(+), 38 deletions(-) diff --git a/cmd/kluctl/commands/cmd_delete.go b/cmd/kluctl/commands/cmd_delete.go index bb238bff3..08c432eac 100644 --- a/cmd/kluctl/commands/cmd_delete.go +++ b/cmd/kluctl/commands/cmd_delete.go @@ -5,11 +5,8 @@ import ( "fmt" "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/deployment/commands" - "github.com/kluctl/kluctl/v2/pkg/deployment/utils" - "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/status" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" - "github.com/kluctl/kluctl/v2/pkg/types/result" ) type deleteCmd struct { @@ -51,13 +48,11 @@ func (cmd *deleteCmd) Run(ctx context.Context) error { if cmd.Discriminator != "" { discriminator = cmd.Discriminator } - cmd2 := commands.NewDeleteCommand(discriminator, cmdCtx.targetCtx.DeploymentCollection.Inclusion) + cmd2 := commands.NewDeleteCommand(discriminator, cmdCtx.targetCtx.DeploymentCollection, cmdCtx.targetCtx.DeploymentCollection.Inclusion) - objects, err := cmd2.Run(cmdCtx.ctx, cmdCtx.targetCtx.SharedContext.K) - if err != nil { - return err - } - result, err := confirmedDeleteObjects(cmdCtx.ctx, cmdCtx.targetCtx.SharedContext.K, objects, cmd.DryRun, cmd.Yes) + result, err := cmd2.Run(cmdCtx.ctx, cmdCtx.targetCtx.SharedContext.K, func(refs []k8s2.ObjectRef) error { + return confirmDeletion(ctx, refs, cmd.DryRun, cmd.Yes) + }) if err != nil { return err } @@ -76,7 +71,7 @@ func (cmd *deleteCmd) Run(ctx context.Context) error { }) } -func confirmedDeleteObjects(ctx context.Context, k *k8s.K8sCluster, refs []k8s2.ObjectRef, dryRun bool, forceYes bool) (*result.CommandResult, error) { +func confirmDeletion(ctx context.Context, refs []k8s2.ObjectRef, dryRun bool, forceYes bool) error { if len(refs) != 0 { _, _ = getStderr(ctx).WriteString("The following objects will be deleted:\n") for _, ref := range refs { @@ -84,10 +79,9 @@ func confirmedDeleteObjects(ctx context.Context, k *k8s.K8sCluster, refs []k8s2. } if !forceYes && !dryRun { if !status.AskForConfirmation(ctx, fmt.Sprintf("Do you really want to delete %d objects?", len(refs))) { - return nil, fmt.Errorf("aborted") + return fmt.Errorf("aborted") } } } - - return utils.DeleteObjects(ctx, k, refs, true) + return nil } diff --git a/cmd/kluctl/commands/cmd_prune.go b/cmd/kluctl/commands/cmd_prune.go index ba60b59a8..4c8b86d17 100644 --- a/cmd/kluctl/commands/cmd_prune.go +++ b/cmd/kluctl/commands/cmd_prune.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/deployment/commands" + k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" ) type pruneCmd struct { @@ -46,11 +47,9 @@ func (cmd *pruneCmd) Run(ctx context.Context) error { func (cmd *pruneCmd) runCmdPrune(cmdCtx *commandCtx) error { cmd2 := commands.NewPruneCommand(cmdCtx.targetCtx.Target.Discriminator, cmdCtx.targetCtx.DeploymentCollection) - objects, err := cmd2.Run(cmdCtx.ctx, cmdCtx.targetCtx.SharedContext.K) - if err != nil { - return err - } - result, err := confirmedDeleteObjects(cmdCtx.ctx, cmdCtx.targetCtx.SharedContext.K, objects, cmd.DryRun, cmd.Yes) + result, err := cmd2.Run(cmdCtx.ctx, cmdCtx.targetCtx.SharedContext.K, func(refs []k8s2.ObjectRef) error { + return confirmDeletion(cmdCtx.ctx, refs, cmd.DryRun, cmd.Yes) + }) if err != nil { return err } diff --git a/pkg/deployment/commands/delete.go b/pkg/deployment/commands/delete.go index da6d7e5d9..49a0e27a3 100644 --- a/pkg/deployment/commands/delete.go +++ b/pkg/deployment/commands/delete.go @@ -3,25 +3,29 @@ package commands import ( "context" "fmt" + "github.com/kluctl/kluctl/v2/pkg/deployment" utils2 "github.com/kluctl/kluctl/v2/pkg/deployment/utils" "github.com/kluctl/kluctl/v2/pkg/k8s" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/types/result" "github.com/kluctl/kluctl/v2/pkg/utils" ) type DeleteCommand struct { + c *deployment.DeploymentCollection discriminator string inclusion *utils.Inclusion } -func NewDeleteCommand(discriminator string, inclusion *utils.Inclusion) *DeleteCommand { +func NewDeleteCommand(discriminator string, c *deployment.DeploymentCollection, inclusion *utils.Inclusion) *DeleteCommand { return &DeleteCommand{ discriminator: discriminator, + c: c, inclusion: inclusion, } } -func (cmd *DeleteCommand) Run(ctx context.Context, k *k8s.K8sCluster) ([]k8s2.ObjectRef, error) { +func (cmd *DeleteCommand) Run(ctx context.Context, k *k8s.K8sCluster, confirmCb func(refs []k8s2.ObjectRef) error) (*result.CommandResult, error) { if cmd.discriminator == "" { return nil, fmt.Errorf("deletion without a discriminator is not supported") } @@ -33,5 +37,26 @@ func (cmd *DeleteCommand) Run(ctx context.Context, k *k8s.K8sCluster) ([]k8s2.Ob return nil, err } - return utils2.FindObjectsForDelete(k, ru.GetFilteredRemoteObjects(cmd.inclusion), cmd.inclusion.HasType("tags"), nil) + deleteRefs, err := utils2.FindObjectsForDelete(k, ru.GetFilteredRemoteObjects(cmd.inclusion), cmd.inclusion.HasType("tags"), nil) + if err != nil { + return nil, err + } + + if confirmCb != nil { + err = confirmCb(deleteRefs) + if err != nil { + return nil, err + } + } + + deleted, err := utils2.DeleteObjects(ctx, k, deleteRefs, dew, true) + if err != nil { + return nil, err + } + + return &result.CommandResult{ + DeletedObjects: deleted, + Errors: dew.GetErrorsList(), + Warnings: dew.GetWarningsList(), + }, nil } diff --git a/pkg/deployment/commands/prune.go b/pkg/deployment/commands/prune.go index 4b7a32cfe..c285bb12a 100644 --- a/pkg/deployment/commands/prune.go +++ b/pkg/deployment/commands/prune.go @@ -7,6 +7,7 @@ import ( utils2 "github.com/kluctl/kluctl/v2/pkg/deployment/utils" "github.com/kluctl/kluctl/v2/pkg/k8s" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/types/result" ) type PruneCommand struct { @@ -21,7 +22,7 @@ func NewPruneCommand(discriminator string, c *deployment.DeploymentCollection) * } } -func (cmd *PruneCommand) Run(ctx context.Context, k *k8s.K8sCluster) ([]k8s2.ObjectRef, error) { +func (cmd *PruneCommand) Run(ctx context.Context, k *k8s.K8sCluster, confirmCb func(refs []k8s2.ObjectRef) error) (*result.CommandResult, error) { if cmd.discriminator == "" { return nil, fmt.Errorf("pruning without a discriminator is not supported") } @@ -34,7 +35,28 @@ func (cmd *PruneCommand) Run(ctx context.Context, k *k8s.K8sCluster) ([]k8s2.Obj return nil, err } - return FindOrphanObjects(k, ru, cmd.c) + deleteRefs, err := FindOrphanObjects(k, ru, cmd.c) + if err != nil { + return nil, err + } + + if confirmCb != nil { + err = confirmCb(deleteRefs) + if err != nil { + return nil, err + } + } + + deleted, err := utils2.DeleteObjects(ctx, k, deleteRefs, dew, true) + if err != nil { + return nil, err + } + + return &result.CommandResult{ + DeletedObjects: deleted, + Errors: dew.GetErrorsList(), + Warnings: dew.GetWarningsList(), + }, nil } func FindOrphanObjects(k *k8s.K8sCluster, ru *utils2.RemoteObjectUtils, c *deployment.DeploymentCollection) ([]k8s2.ObjectRef, error) { diff --git a/pkg/deployment/utils/delete_utils.go b/pkg/deployment/utils/delete_utils.go index f247f9f27..0cab9e94e 100644 --- a/pkg/deployment/utils/delete_utils.go +++ b/pkg/deployment/utils/delete_utils.go @@ -4,7 +4,6 @@ import ( "context" "github.com/kluctl/kluctl/v2/pkg/k8s" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" - "github.com/kluctl/kluctl/v2/pkg/types/result" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -160,10 +159,10 @@ func FindObjectsForDelete(k *k8s.K8sCluster, allClusterObjects []*uo.Unstructure return ret, nil } -func DeleteObjects(ctx context.Context, k *k8s.K8sCluster, refs []k8s2.ObjectRef, doWait bool) (*result.CommandResult, error) { +func DeleteObjects(ctx context.Context, k *k8s.K8sCluster, refs []k8s2.ObjectRef, dew *DeploymentErrorsAndWarnings, doWait bool) ([]k8s2.ObjectRef, error) { g := utils.NewGoHelper(ctx, 8) - var ret result.CommandResult + var ret []k8s2.ObjectRef namespaceNames := make(map[string]bool) var mutex sync.Mutex @@ -172,19 +171,11 @@ func DeleteObjects(ctx context.Context, k *k8s.K8sCluster, refs []k8s2.ObjectRef defer mutex.Unlock() if err == nil { - ret.DeletedObjects = append(ret.DeletedObjects, ref) + ret = append(ret, ref) } else { - ret.Errors = append(ret.Errors, result.DeploymentError{ - Ref: ref, - Message: err.Error(), - }) - } - for _, w := range apiWarnings { - ret.Warnings = append(ret.Warnings, result.DeploymentError{ - Ref: ref, - Message: w.Text, - }) + dew.AddError(ref, err) } + dew.AddApiWarnings(ref, apiWarnings) } for _, ref_ := range refs { @@ -215,5 +206,5 @@ func DeleteObjects(ctx context.Context, k *k8s.K8sCluster, refs []k8s2.ObjectRef } g.Wait() - return &ret, nil + return ret, nil } From b7fcc1117075c7891953c21b349c2a5dcb534c1e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sun, 2 Apr 2023 00:46:36 +0200 Subject: [PATCH 1562/2916] fix: Preserve when fields instead of rendering+overwriting them --- pkg/deployment/deployment_collection.go | 11 +++--- pkg/deployment/deployment_project.go | 48 ++++++------------------- pkg/vars/vars.go | 17 +++++++++ pkg/vars/vars_loader.go | 15 ++++---- 4 files changed, 40 insertions(+), 51 deletions(-) diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index dd7bfb426..53f31c0de 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -2,7 +2,6 @@ package deployment import ( "fmt" - "github.com/kluctl/kluctl/v2/pkg/kluctl_jinja2" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" @@ -83,12 +82,16 @@ func findDeploymentItemIndex(project *DeploymentProject, pth *string, indexes ma func (c *DeploymentCollection) collectAllDeployments(project *DeploymentProject, indexes map[string]int) ([]*DeploymentItem, error) { var ret []*DeploymentItem - if !kluctl_jinja2.IsConditionalTrue(project.Config.When) { - return nil, nil + if x, err := project.CheckWhenTrue(); !x || err != nil { + return nil, err } for i, diConfig := range project.Config.Deployments { - if !kluctl_jinja2.IsConditionalTrue(diConfig.When) { + whenTrue, err := project.VarsCtx.CheckConditional(diConfig.When) + if err != nil { + return nil, err + } + if !whenTrue { continue } diff --git a/pkg/deployment/deployment_project.go b/pkg/deployment/deployment_project.go index 929073405..34c7d713e 100644 --- a/pkg/deployment/deployment_project.go +++ b/pkg/deployment/deployment_project.go @@ -3,7 +3,6 @@ package deployment import ( "fmt" securejoin "github.com/cyphar/filepath-securejoin" - "github.com/kluctl/kluctl/v2/pkg/kluctl_jinja2" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" @@ -57,8 +56,8 @@ func NewDeploymentProject(ctx SharedContext, varsCtx *vars.VarsCtx, source Sourc return nil, fmt.Errorf("failed to load deployment config for %s: %w", dir, err) } - if !kluctl_jinja2.IsConditionalTrue(dp.Config.When) { - return dp, nil + if x, err := dp.CheckWhenTrue(); !x || err != nil { + return dp, err } err = dp.loadIncludes() @@ -143,11 +142,6 @@ func (p *DeploymentProject) processConfig() error { return err } - err = p.renderConditionals() - if err != nil { - return err - } - return nil } @@ -176,38 +170,12 @@ func (p *DeploymentProject) checkDeploymentDirs() error { return nil } -func (p *DeploymentProject) renderConditionals() error { +func (p *DeploymentProject) CheckWhenTrue() (bool, error) { if p.parentProject == nil && p.Config.When != "" { - return fmt.Errorf("the root deployment project can not contain 'when'") - } - - vars, err := p.VarsCtx.Vars.ToMap() - if err != nil { - return err - } - r, err := kluctl_jinja2.RenderConditional(p.VarsCtx.J2, vars, p.Config.When) - if err != nil { - return err + return false, fmt.Errorf("the root deployment project can not contain 'when'") } - p.Config.When = r - if !kluctl_jinja2.IsConditionalTrue(p.Config.When) { - // No need to render individual deployment item conditionals - return nil - } - - conditionals := make([]string, 0, len(p.Config.Deployments)) - for _, di := range p.Config.Deployments { - conditionals = append(conditionals, di.When) - } - rendered, err := kluctl_jinja2.RenderConditionals(p.VarsCtx.J2, vars, conditionals) - if err != nil { - return err - } - for i, r := range rendered { - p.Config.Deployments[i].When = r - } - return nil + return p.VarsCtx.CheckConditional(p.Config.When) } func (p *DeploymentProject) loadIncludes() error { @@ -215,7 +183,11 @@ func (p *DeploymentProject) loadIncludes() error { var err error var newProject *DeploymentProject - if !kluctl_jinja2.IsConditionalTrue(inc.When) { + whenTrue, err := p.VarsCtx.CheckConditional(inc.When) + if err != nil { + return err + } + if !whenTrue { continue } diff --git a/pkg/vars/vars.go b/pkg/vars/vars.go index 11151f24d..826233fb5 100644 --- a/pkg/vars/vars.go +++ b/pkg/vars/vars.go @@ -2,6 +2,7 @@ package vars import ( "github.com/kluctl/go-jinja2" + "github.com/kluctl/kluctl/v2/pkg/kluctl_jinja2" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" ) @@ -98,3 +99,19 @@ func (vc *VarsCtx) RenderDirectory(sourceDir string, targetDir string, excludePa } return vc.J2.RenderDirectory(sourceDir, targetDir, excludePatterns, jinja2.WithGlobals(globals), jinja2.WithSearchDirs(searchDirs), jinja2.WithTemplateIgnoreRootDir(templateIgnoreRoot)) } + +func (vc *VarsCtx) CheckConditional(c string) (bool, error) { + if kluctl_jinja2.IsConditionalTrue(c) { + return true, nil + } + + m, err := vc.Vars.ToMap() + if err != nil { + return false, err + } + c, err = kluctl_jinja2.RenderConditional(vc.J2, m, c) + if err != nil { + return false, err + } + return kluctl_jinja2.IsConditionalTrue(c), nil +} diff --git a/pkg/vars/vars_loader.go b/pkg/vars/vars_loader.go index 9c90b4000..8ef2b6ed5 100644 --- a/pkg/vars/vars_loader.go +++ b/pkg/vars/vars_loader.go @@ -8,7 +8,6 @@ import ( types2 "github.com/aws/aws-sdk-go-v2/service/secretsmanager/types" "github.com/kluctl/go-jinja2" "github.com/kluctl/kluctl/v2/pkg/k8s" - "github.com/kluctl/kluctl/v2/pkg/kluctl_jinja2" "github.com/kluctl/kluctl/v2/pkg/repocache" "github.com/kluctl/kluctl/v2/pkg/sops" "github.com/kluctl/kluctl/v2/pkg/sops/decryptor" @@ -83,14 +82,12 @@ func (v *VarsLoader) LoadVars(varsCtx *VarsCtx, sourceIn *types.VarsSource, sear return err } - if source.When != "" { - r, err := kluctl_jinja2.RenderConditional(varsCtx.J2, globals, source.When) - if err != nil { - return err - } - if !kluctl_jinja2.IsConditionalTrue(r) { - return nil - } + whenTrue, err := varsCtx.CheckConditional(source.When) + if err != nil { + return err + } + if !whenTrue { + return nil } ignoreMissing := false From 704b2800159953f7810a1842f68fce7f2518eeb9 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 4 Apr 2023 09:16:59 +0200 Subject: [PATCH 1563/2916] feat: Store rendered/remote/applied objects in CommandResult --- cmd/kluctl/commands/command_result.go | 10 ++----- cmd/kluctl/commands/utils.go | 1 - pkg/deployment/commands/delete.go | 8 ++++-- pkg/deployment/commands/deploy.go | 38 +++++++++++++++----------- pkg/deployment/commands/diff.go | 19 +++++++------ pkg/deployment/commands/poke_images.go | 27 ++++++++++++------ pkg/deployment/commands/prune.go | 8 ++++-- pkg/deployment/utils/apply_utils.go | 17 ++++++++++-- pkg/diff/obfuscate.go | 18 ++++++++++-- pkg/types/result/command_result.go | 25 +++++++++-------- 10 files changed, 109 insertions(+), 62 deletions(-) diff --git a/cmd/kluctl/commands/command_result.go b/cmd/kluctl/commands/command_result.go index 6815a8b89..f8a874cc3 100644 --- a/cmd/kluctl/commands/command_result.go +++ b/cmd/kluctl/commands/command_result.go @@ -26,11 +26,7 @@ func formatCommandResultText(cr *result.CommandResult, short bool) string { if len(cr.NewObjects) != 0 { buf.WriteString("\nNew objects:\n") - var refs []k8s.ObjectRef - for _, o := range cr.NewObjects { - refs = append(refs, o.GetK8sRef()) - } - prettyObjectRefs(buf, refs) + prettyObjectRefs(buf, cr.NewObjects) } if len(cr.ChangedObjects) != 0 { buf.WriteString("\nChanged objects:\n") @@ -57,10 +53,10 @@ func formatCommandResultText(cr *result.CommandResult, short bool) string { prettyObjectRefs(buf, cr.DeletedObjects) } - if len(cr.HookObjects) != 0 { + if len(cr.AppliedHookObjects) != 0 { buf.WriteString("\nApplied hooks:\n") var refs []k8s.ObjectRef - for _, o := range cr.HookObjects { + for _, o := range cr.AppliedHookObjects { refs = append(refs, o.GetK8sRef()) } prettyObjectRefs(buf, refs) diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 229ecd281..e41e9acd1 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -246,7 +246,6 @@ func addCommandInfo(r *result.CommandResult, command string, ctx *commandCtx, ta r.Command.AbortOnError = abortOnErrorFlags.AbortOnError } r.Deployment = &ctx.targetCtx.DeploymentProject.Config - r.RenderedObjects = ctx.targetCtx.DeploymentCollection.LocalObjects() return nil } diff --git a/pkg/deployment/commands/delete.go b/pkg/deployment/commands/delete.go index 49a0e27a3..6d7ebe07b 100644 --- a/pkg/deployment/commands/delete.go +++ b/pkg/deployment/commands/delete.go @@ -55,8 +55,10 @@ func (cmd *DeleteCommand) Run(ctx context.Context, k *k8s.K8sCluster, confirmCb } return &result.CommandResult{ - DeletedObjects: deleted, - Errors: dew.GetErrorsList(), - Warnings: dew.GetWarningsList(), + RenderedObjects: cmd.c.LocalObjects(), + RemoteObjects: ru.GetFilteredRemoteObjects(nil), + DeletedObjects: deleted, + Errors: dew.GetErrorsList(), + Warnings: dew.GetWarningsList(), }, nil } diff --git a/pkg/deployment/commands/deploy.go b/pkg/deployment/commands/deploy.go index a1f98531f..427710916 100644 --- a/pkg/deployment/commands/deploy.go +++ b/pkg/deployment/commands/deploy.go @@ -65,14 +65,17 @@ func (cmd *DeployCommand) Run(ctx context.Context, k *k8s.K8sCluster, diffResult orphanObjects, err := FindOrphanObjects(k, ru, cmd.c) diffResult := &result.CommandResult{ - NewObjects: au.GetNewObjects(), - ChangedObjects: du.ChangedObjects, - DeletedObjects: au.GetDeletedObjects(), - HookObjects: au.GetAppliedHookObjects(), - OrphanObjects: orphanObjects, - Errors: dew.GetErrorsList(), - Warnings: dew.GetWarningsList(), - SeenImages: cmd.c.Images.SeenImages(false), + RenderedObjects: cmd.c.LocalObjects(), + RemoteObjects: ru.GetFilteredRemoteObjects(nil), + AppliedObjects: au.GetAppliedObjects(), + AppliedHookObjects: au.GetAppliedHookObjects(), + NewObjects: au.GetNewObjectRefs(), + ChangedObjects: du.ChangedObjects, + DeletedObjects: au.GetDeletedObjects(), + OrphanObjects: orphanObjects, + Errors: dew.GetErrorsList(), + Warnings: dew.GetWarningsList(), + SeenImages: cmd.c.Images.SeenImages(false), } err = diffResultCb(diffResult) @@ -99,13 +102,16 @@ func (cmd *DeployCommand) Run(ctx context.Context, k *k8s.K8sCluster, diffResult return nil, err } return &result.CommandResult{ - NewObjects: au.GetNewObjects(), - ChangedObjects: du.ChangedObjects, - DeletedObjects: au.GetDeletedObjects(), - HookObjects: au.GetAppliedHookObjects(), - OrphanObjects: orphanObjects, - Errors: dew.GetErrorsList(), - Warnings: dew.GetWarningsList(), - SeenImages: cmd.c.Images.SeenImages(false), + RenderedObjects: cmd.c.LocalObjects(), + RemoteObjects: ru.GetFilteredRemoteObjects(nil), + AppliedObjects: au.GetAppliedObjects(), + AppliedHookObjects: au.GetAppliedHookObjects(), + NewObjects: au.GetNewObjectRefs(), + ChangedObjects: du.ChangedObjects, + DeletedObjects: au.GetDeletedObjects(), + OrphanObjects: orphanObjects, + Errors: dew.GetErrorsList(), + Warnings: dew.GetWarningsList(), + SeenImages: cmd.c.Images.SeenImages(false), }, nil } diff --git a/pkg/deployment/commands/diff.go b/pkg/deployment/commands/diff.go index 335fecc91..c15f97864 100644 --- a/pkg/deployment/commands/diff.go +++ b/pkg/deployment/commands/diff.go @@ -66,13 +66,16 @@ func (cmd *DiffCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*result.Com return nil, err } return &result.CommandResult{ - NewObjects: au.GetNewObjects(), - ChangedObjects: du.ChangedObjects, - DeletedObjects: au.GetDeletedObjects(), - HookObjects: au.GetAppliedHookObjects(), - OrphanObjects: orphanObjects, - Errors: dew.GetErrorsList(), - Warnings: dew.GetWarningsList(), - SeenImages: cmd.c.Images.SeenImages(false), + RenderedObjects: cmd.c.LocalObjects(), + RemoteObjects: ru.GetFilteredRemoteObjects(nil), + AppliedObjects: au.GetAppliedObjects(), + AppliedHookObjects: au.GetAppliedHookObjects(), + NewObjects: au.GetNewObjectRefs(), + ChangedObjects: du.ChangedObjects, + DeletedObjects: au.GetDeletedObjects(), + OrphanObjects: orphanObjects, + Errors: dew.GetErrorsList(), + Warnings: dew.GetWarningsList(), + SeenImages: cmd.c.Images.SeenImages(false), }, nil } diff --git a/pkg/deployment/commands/poke_images.go b/pkg/deployment/commands/poke_images.go index 8af5d519d..d2c4de720 100644 --- a/pkg/deployment/commands/poke_images.go +++ b/pkg/deployment/commands/poke_images.go @@ -71,7 +71,7 @@ func (cmd *PokeImagesCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*resu return o, nil } - ad := utils2.NewApplyDeploymentsUtil(ctx, dew, cmd.c.Deployments, ru, k, &utils2.ApplyUtilOptions{}) + au := utils2.NewApplyDeploymentsUtil(ctx, dew, cmd.c.Deployments, ru, k, &utils2.ApplyUtilOptions{}) for ref, containers := range containersAndImages { ref := ref @@ -79,7 +79,7 @@ func (cmd *PokeImagesCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*resu wg.Add(1) go func() { defer wg.Done() - au := ad.NewApplyUtil(ctx, nil) + au := au.NewApplyUtil(ctx, nil) remote := ru.GetRemoteObject(ref) if remote == nil { dew.AddWarning(ref, fmt.Errorf("remote object not found, skipped image replacement")) @@ -92,14 +92,25 @@ func (cmd *PokeImagesCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*resu } wg.Wait() - du := utils2.NewDiffUtil(dew, cmd.c.Deployments, ru, ad.GetAppliedObjectsMap()) + du := utils2.NewDiffUtil(dew, cmd.c.Deployments, ru, au.GetAppliedObjectsMap()) du.Diff() + orphanObjects, err := FindOrphanObjects(k, ru, cmd.c) + if err != nil { + return nil, err + } + return &result.CommandResult{ - NewObjects: nil, - ChangedObjects: du.ChangedObjects, - Errors: dew.GetErrorsList(), - Warnings: dew.GetWarningsList(), - SeenImages: cmd.c.Images.SeenImages(false), + RenderedObjects: cmd.c.LocalObjects(), + RemoteObjects: ru.GetFilteredRemoteObjects(nil), + AppliedObjects: au.GetAppliedObjects(), + AppliedHookObjects: au.GetAppliedHookObjects(), + NewObjects: au.GetNewObjectRefs(), + ChangedObjects: du.ChangedObjects, + DeletedObjects: au.GetDeletedObjects(), + OrphanObjects: orphanObjects, + Errors: dew.GetErrorsList(), + Warnings: dew.GetWarningsList(), + SeenImages: cmd.c.Images.SeenImages(false), }, nil } diff --git a/pkg/deployment/commands/prune.go b/pkg/deployment/commands/prune.go index c285bb12a..5791db8d7 100644 --- a/pkg/deployment/commands/prune.go +++ b/pkg/deployment/commands/prune.go @@ -53,9 +53,11 @@ func (cmd *PruneCommand) Run(ctx context.Context, k *k8s.K8sCluster, confirmCb f } return &result.CommandResult{ - DeletedObjects: deleted, - Errors: dew.GetErrorsList(), - Warnings: dew.GetWarningsList(), + RenderedObjects: cmd.c.LocalObjects(), + RemoteObjects: ru.GetFilteredRemoteObjects(nil), + DeletedObjects: deleted, + Errors: dew.GetErrorsList(), + Warnings: dew.GetWarningsList(), }, nil } diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index 0b610412e..750195855 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -768,8 +768,21 @@ func (ad *ApplyDeploymentsUtil) collectObjects(f func(au *ApplyUtil) map[k8s2.Ob return ret } -func (ad *ApplyDeploymentsUtil) GetNewObjects() []*uo.UnstructuredObject { - return ad.collectObjects(func(au *ApplyUtil) map[k8s2.ObjectRef]*uo.UnstructuredObject { +func (ad *ApplyDeploymentsUtil) collectObjectRefs(f func(au *ApplyUtil) map[k8s2.ObjectRef]*uo.UnstructuredObject) []k8s2.ObjectRef { + ad.resultsMutex.Lock() + defer ad.resultsMutex.Unlock() + + var ret []k8s2.ObjectRef + for _, a := range ad.results { + for _, o := range f(a) { + ret = append(ret, o.GetK8sRef()) + } + } + return ret +} + +func (ad *ApplyDeploymentsUtil) GetNewObjectRefs() []k8s2.ObjectRef { + return ad.collectObjectRefs(func(au *ApplyUtil) map[k8s2.ObjectRef]*uo.UnstructuredObject { return au.newObjects }) } diff --git a/pkg/diff/obfuscate.go b/pkg/diff/obfuscate.go index 80a16e151..d00332960 100644 --- a/pkg/diff/obfuscate.go +++ b/pkg/diff/obfuscate.go @@ -23,14 +23,26 @@ func (o *Obfuscator) ObfuscateResult(r *result.CommandResult) error { return err } } - for _, n := range r.NewObjects { + for _, n := range r.RenderedObjects { err := o.ObfuscateObject(n) if err != nil { return err } } - for _, h := range r.HookObjects { - err := o.ObfuscateObject(h) + for _, n := range r.RemoteObjects { + err := o.ObfuscateObject(n) + if err != nil { + return err + } + } + for _, n := range r.AppliedObjects { + err := o.ObfuscateObject(n) + if err != nil { + return err + } + } + for _, n := range r.AppliedHookObjects { + err := o.ObfuscateObject(n) if err != nil { return err } diff --git a/pkg/types/result/command_result.go b/pkg/types/result/command_result.go index ef1737e8e..ea55be518 100644 --- a/pkg/types/result/command_result.go +++ b/pkg/types/result/command_result.go @@ -60,18 +60,21 @@ type CommandInfo struct { } type CommandResult struct { - Command *CommandInfo `json:"command,omitempty"` - Deployment *types.DeploymentProjectConfig `json:"deployment,omitempty"` - RenderedObjects []*uo.UnstructuredObject `json:"renderedObjects,omitempty"` + Command *CommandInfo `json:"command,omitempty"` + Deployment *types.DeploymentProjectConfig `json:"deployment,omitempty"` - NewObjects []*uo.UnstructuredObject `json:"newObjects,omitempty"` - ChangedObjects []*ChangedObject `json:"changedObjects,omitempty"` - HookObjects []*uo.UnstructuredObject `json:"hookObjects,omitempty"` - OrphanObjects []k8s.ObjectRef `json:"orphanObjects,omitempty"` - DeletedObjects []k8s.ObjectRef `json:"deletedObjects,omitempty"` - Errors []DeploymentError `json:"errors,omitempty"` - Warnings []DeploymentError `json:"warnings,omitempty"` - SeenImages []types.FixedImage `json:"seenImages,omitempty"` + RenderedObjects []*uo.UnstructuredObject `json:"renderedObjects,omitempty"` + RemoteObjects []*uo.UnstructuredObject `json:"remoteObjects,omitempty"` + AppliedObjects []*uo.UnstructuredObject `json:"appliedObjects,omitempty"` + AppliedHookObjects []*uo.UnstructuredObject `json:"appliedHookObjects,omitempty"` + + NewObjects []k8s.ObjectRef `json:"newObjects,omitempty"` + ChangedObjects []*ChangedObject `json:"changedObjects,omitempty"` + OrphanObjects []k8s.ObjectRef `json:"orphanObjects,omitempty"` + DeletedObjects []k8s.ObjectRef `json:"deletedObjects,omitempty"` + Errors []DeploymentError `json:"errors,omitempty"` + Warnings []DeploymentError `json:"warnings,omitempty"` + SeenImages []types.FixedImage `json:"seenImages,omitempty"` } type ValidateResultEntry struct { From 5532a019649809dab2cd69146221f43ce8551b20 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 4 Apr 2023 09:17:49 +0200 Subject: [PATCH 1564/2916] fix: Fix crash on secrets/configmaps with null data --- pkg/diff/obfuscate.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/diff/obfuscate.go b/pkg/diff/obfuscate.go index d00332960..96a8ea254 100644 --- a/pkg/diff/obfuscate.go +++ b/pkg/diff/obfuscate.go @@ -119,7 +119,7 @@ func (o *Obfuscator) obfuscateSecretChanges(ref k8s.ObjectRef, changes []result. func (o *Obfuscator) obfuscateSecret(x *uo.UnstructuredObject) error { data, ok, _ := x.GetNestedField("data") - if ok { + if ok && data != nil { if m, ok := data.(map[string]any); ok { for k, _ := range m { m[k] = base64.StdEncoding.EncodeToString([]byte("*****")) @@ -129,7 +129,7 @@ func (o *Obfuscator) obfuscateSecret(x *uo.UnstructuredObject) error { } } data, ok, _ = x.GetNestedField("stringData") - if ok { + if ok && data != nil { if m, ok := data.(map[string]any); ok { for k, _ := range m { m[k] = "*****" From e441a9ad42ff7a3cc50edcb2d5fe46ee927a59c6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 4 Apr 2023 09:18:09 +0200 Subject: [PATCH 1565/2916] fix: Omit empty group/namespace from json ObjectRef --- pkg/types/k8s/ref.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/types/k8s/ref.go b/pkg/types/k8s/ref.go index fa91157e3..b257ca428 100644 --- a/pkg/types/k8s/ref.go +++ b/pkg/types/k8s/ref.go @@ -6,11 +6,11 @@ import ( ) type ObjectRef struct { - Group string `json:"group"` - Version string `json:"version"` + Group string `json:"group,omitempty"` + Version string `json:"version,omitempty"` Kind string `json:"kind"` Name string `json:"name"` - Namespace string `json:"namespace"` + Namespace string `json:"namespace,omitempty"` } func (r ObjectRef) String() string { From 29d1abdd06ad980ca441aeec74df51f9dc69ec43 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 4 Apr 2023 15:54:26 +0200 Subject: [PATCH 1566/2916] feat: Add start/end time to CommandResult --- cmd/kluctl/commands/cmd_delete.go | 4 ++- cmd/kluctl/commands/cmd_deploy.go | 8 ++++-- cmd/kluctl/commands/cmd_diff.go | 4 ++- cmd/kluctl/commands/cmd_poke_images.go | 4 ++- cmd/kluctl/commands/cmd_prune.go | 8 ++++-- cmd/kluctl/commands/utils.go | 6 +++- pkg/types/result/command_result.go | 2 ++ pkg/types/time.go | 39 ++++++++++++++++++++++++++ 8 files changed, 65 insertions(+), 10 deletions(-) create mode 100644 pkg/types/time.go diff --git a/cmd/kluctl/commands/cmd_delete.go b/cmd/kluctl/commands/cmd_delete.go index 08c432eac..57f7c1a74 100644 --- a/cmd/kluctl/commands/cmd_delete.go +++ b/cmd/kluctl/commands/cmd_delete.go @@ -7,6 +7,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/deployment/commands" "github.com/kluctl/kluctl/v2/pkg/status" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "time" ) type deleteCmd struct { @@ -43,6 +44,7 @@ func (cmd *deleteCmd) Run(ctx context.Context) error { dryRunArgs: &cmd.DryRunFlags, renderOutputDirFlags: cmd.RenderOutputDirFlags, } + startTime := time.Now() return withProjectCommandContext(ctx, ptArgs, func(cmdCtx *commandCtx) error { discriminator := cmdCtx.targetCtx.Target.Discriminator if cmd.Discriminator != "" { @@ -56,7 +58,7 @@ func (cmd *deleteCmd) Run(ctx context.Context) error { if err != nil { return err } - err = addCommandInfo(result, "delete", cmdCtx, &cmd.TargetFlags, &cmd.ImageFlags, &cmd.InclusionFlags, &cmd.DryRunFlags, nil, nil, nil, false) + err = addCommandInfo(result, startTime, "delete", cmdCtx, &cmd.TargetFlags, &cmd.ImageFlags, &cmd.InclusionFlags, &cmd.DryRunFlags, nil, nil, nil, false) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_deploy.go b/cmd/kluctl/commands/cmd_deploy.go index 469d93ae1..e6e918d58 100644 --- a/cmd/kluctl/commands/cmd_deploy.go +++ b/cmd/kluctl/commands/cmd_deploy.go @@ -7,6 +7,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/deployment/commands" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types/result" + "time" ) type deployCmd struct { @@ -46,12 +47,13 @@ func (cmd *deployCmd) Run(ctx context.Context) error { dryRunArgs: &cmd.DryRunFlags, renderOutputDirFlags: cmd.RenderOutputDirFlags, } + startTime := time.Now() return withProjectCommandContext(ctx, ptArgs, func(cmdCtx *commandCtx) error { - return cmd.runCmdDeploy(cmdCtx) + return cmd.runCmdDeploy(cmdCtx, startTime) }) } -func (cmd *deployCmd) runCmdDeploy(cmdCtx *commandCtx) error { +func (cmd *deployCmd) runCmdDeploy(cmdCtx *commandCtx, startTime time.Time) error { status.Trace(cmdCtx.ctx, "enter runCmdDeploy") defer status.Trace(cmdCtx.ctx, "leave runCmdDeploy") @@ -74,7 +76,7 @@ func (cmd *deployCmd) runCmdDeploy(cmdCtx *commandCtx) error { if err != nil { return err } - err = addCommandInfo(result, "deploy", cmdCtx, &cmd.TargetFlags, &cmd.ImageFlags, &cmd.InclusionFlags, &cmd.DryRunFlags, &cmd.ForceApplyFlags, &cmd.ReplaceOnErrorFlags, &cmd.AbortOnErrorFlags, cmd.NoWait) + err = addCommandInfo(result, startTime, "deploy", cmdCtx, &cmd.TargetFlags, &cmd.ImageFlags, &cmd.InclusionFlags, &cmd.DryRunFlags, &cmd.ForceApplyFlags, &cmd.ReplaceOnErrorFlags, &cmd.AbortOnErrorFlags, cmd.NoWait) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_diff.go b/cmd/kluctl/commands/cmd_diff.go index 46838fcd3..640caecee 100644 --- a/cmd/kluctl/commands/cmd_diff.go +++ b/cmd/kluctl/commands/cmd_diff.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/deployment/commands" + "time" ) type diffCmd struct { @@ -38,6 +39,7 @@ func (cmd *diffCmd) Run(ctx context.Context) error { helmCredentials: cmd.HelmCredentials, renderOutputDirFlags: cmd.RenderOutputDirFlags, } + startTime := time.Now() return withProjectCommandContext(ctx, ptArgs, func(cmdCtx *commandCtx) error { cmd2 := commands.NewDiffCommand(cmdCtx.targetCtx.Target.Discriminator, cmdCtx.targetCtx.DeploymentCollection) cmd2.ForceApply = cmd.ForceApply @@ -50,7 +52,7 @@ func (cmd *diffCmd) Run(ctx context.Context) error { if err != nil { return err } - err = addCommandInfo(result, "diff", cmdCtx, &cmd.TargetFlags, &cmd.ImageFlags, &cmd.InclusionFlags, nil, &cmd.ForceApplyFlags, &cmd.ReplaceOnErrorFlags, nil, false) + err = addCommandInfo(result, startTime, "diff", cmdCtx, &cmd.TargetFlags, &cmd.ImageFlags, &cmd.InclusionFlags, nil, &cmd.ForceApplyFlags, &cmd.ReplaceOnErrorFlags, nil, false) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_poke_images.go b/cmd/kluctl/commands/cmd_poke_images.go index ac72efe70..fccb20bc9 100644 --- a/cmd/kluctl/commands/cmd_poke_images.go +++ b/cmd/kluctl/commands/cmd_poke_images.go @@ -6,6 +6,7 @@ import ( "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/deployment/commands" "github.com/kluctl/kluctl/v2/pkg/status" + "time" ) type pokeImagesCmd struct { @@ -38,6 +39,7 @@ func (cmd *pokeImagesCmd) Run(ctx context.Context) error { dryRunArgs: &cmd.DryRunFlags, renderOutputDirFlags: cmd.RenderOutputDirFlags, } + startTime := time.Now() return withProjectCommandContext(ctx, ptArgs, func(cmdCtx *commandCtx) error { if !cmd.Yes && !cmd.DryRun { if !status.AskForConfirmation(ctx, fmt.Sprintf("Do you really want to poke images to the context/cluster %s?", cmdCtx.targetCtx.ClusterContext)) { @@ -51,7 +53,7 @@ func (cmd *pokeImagesCmd) Run(ctx context.Context) error { if err != nil { return err } - err = addCommandInfo(result, "poke-images", cmdCtx, &cmd.TargetFlags, &cmd.ImageFlags, &cmd.InclusionFlags, &cmd.DryRunFlags, nil, nil, nil, false) + err = addCommandInfo(result, startTime, "poke-images", cmdCtx, &cmd.TargetFlags, &cmd.ImageFlags, &cmd.InclusionFlags, &cmd.DryRunFlags, nil, nil, nil, false) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_prune.go b/cmd/kluctl/commands/cmd_prune.go index 4c8b86d17..cd19d2e33 100644 --- a/cmd/kluctl/commands/cmd_prune.go +++ b/cmd/kluctl/commands/cmd_prune.go @@ -6,6 +6,7 @@ import ( "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/deployment/commands" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "time" ) type pruneCmd struct { @@ -40,12 +41,13 @@ func (cmd *pruneCmd) Run(ctx context.Context) error { dryRunArgs: &cmd.DryRunFlags, renderOutputDirFlags: cmd.RenderOutputDirFlags, } + startTime := time.Now() return withProjectCommandContext(ctx, ptArgs, func(cmdCtx *commandCtx) error { - return cmd.runCmdPrune(cmdCtx) + return cmd.runCmdPrune(cmdCtx, startTime) }) } -func (cmd *pruneCmd) runCmdPrune(cmdCtx *commandCtx) error { +func (cmd *pruneCmd) runCmdPrune(cmdCtx *commandCtx, startTime time.Time) error { cmd2 := commands.NewPruneCommand(cmdCtx.targetCtx.Target.Discriminator, cmdCtx.targetCtx.DeploymentCollection) result, err := cmd2.Run(cmdCtx.ctx, cmdCtx.targetCtx.SharedContext.K, func(refs []k8s2.ObjectRef) error { return confirmDeletion(cmdCtx.ctx, refs, cmd.DryRun, cmd.Yes) @@ -53,7 +55,7 @@ func (cmd *pruneCmd) runCmdPrune(cmdCtx *commandCtx) error { if err != nil { return err } - err = addCommandInfo(result, "prune", cmdCtx, &cmd.TargetFlags, &cmd.ImageFlags, &cmd.InclusionFlags, &cmd.DryRunFlags, nil, nil, nil, false) + err = addCommandInfo(result, startTime, "prune", cmdCtx, &cmd.TargetFlags, &cmd.ImageFlags, &cmd.InclusionFlags, &cmd.DryRunFlags, nil, nil, nil, false) if err != nil { return err } diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index e41e9acd1..91e801504 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -3,9 +3,11 @@ package commands import ( "context" "fmt" + "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/types/result" "os" "strings" + "time" "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/deployment" @@ -205,11 +207,13 @@ func withProjectTargetCommandContext(ctx context.Context, args projectTargetComm return cb(cmdCtx) } -func addCommandInfo(r *result.CommandResult, command string, ctx *commandCtx, targetFlags *args.TargetFlags, +func addCommandInfo(r *result.CommandResult, startTime time.Time, command string, ctx *commandCtx, targetFlags *args.TargetFlags, imageFlags *args.ImageFlags, inclusionFlags *args.InclusionFlags, dryRunFlags *args.DryRunFlags, forceApplyFlags *args.ForceApplyFlags, replaceOnErrorFlags *args.ReplaceOnErrorFlags, abortOnErrorFlags *args.AbortOnErrorFlags, noWait bool) error { r.Command = &result.CommandInfo{ Initiator: result.CommandInititiator_CommandLine, + StartTime: types.FromTime(startTime), + EndTime: types.FromTime(time.Now()), Command: command, Target: ctx.targetCtx.Target, Args: ctx.targetCtx.KluctlProject.LoadArgs.ExternalArgs, diff --git a/pkg/types/result/command_result.go b/pkg/types/result/command_result.go index ea55be518..2596242b2 100644 --- a/pkg/types/result/command_result.go +++ b/pkg/types/result/command_result.go @@ -40,6 +40,8 @@ const ( type CommandInfo struct { Initiator CommandInitiator `json:"initiator" validate:"oneof=CommandLine KluctlDeployment"` + StartTime types.JsonTime `json:"startTime"` + EndTime types.JsonTime `json:"endTime"` KluctlDeployment *KluctlDeploymentInfo `json:"kluctlDeployment,omitempty"` Command string `json:"command,omitempty"` Target *types.Target `json:"target,omitempty"` diff --git a/pkg/types/time.go b/pkg/types/time.go new file mode 100644 index 000000000..1706b8606 --- /dev/null +++ b/pkg/types/time.go @@ -0,0 +1,39 @@ +package types + +import ( + "encoding/json" + "time" +) + +type JsonTime string + +func FromTime(t time.Time) JsonTime { + return JsonTime(t.Format(time.RFC3339Nano)) +} + +func (t JsonTime) ToTime() (time.Time, error) { + t2, err := time.Parse(time.RFC3339Nano, string(t)) + if err != nil { + return time.Time{}, err + } + return t2, nil +} + +func (t *JsonTime) UnmarshalJSON(b []byte) error { + var s string + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + t2 := JsonTime(s) + _, err = t2.ToTime() + if err != nil { + return err + } + *t = t2 + return nil +} + +func (t JsonTime) MarshalJSON() ([]byte, error) { + return json.Marshal(string(t)) +} From 25b32ffeed9d8e1f50375e96de67ae3fdb1b0429 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 4 Apr 2023 21:42:02 +0200 Subject: [PATCH 1567/2916] feat: Add CommandResultSummary --- pkg/types/result/summary.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 pkg/types/result/summary.go diff --git a/pkg/types/result/summary.go b/pkg/types/result/summary.go new file mode 100644 index 000000000..d8d8adfb7 --- /dev/null +++ b/pkg/types/result/summary.go @@ -0,0 +1,20 @@ +package result + +type CommandResultSummary struct { + Id string `json:"id"` + Command *CommandInfo `json:"commandInfo"` + + RenderedObjects int `json:"renderedObjects"` + RemoteObjects int `json:"remoteObjects"` + AppliedObjects int `json:"appliedObjects"` + AppliedHookObjects int `json:"appliedHookObjects"` + + NewObjects int `json:"newObjects"` + ChangedObjects int `json:"changedObjects"` + OrphanObjects int `json:"orphanObjects"` + DeletedObjects int `json:"deletedObjects"` + Errors int `json:"errors"` + Warnings int `json:"warnings"` + + TotalChanges int `json:"totalChanges"` +} From da4121be687e3b9ec05da4de38161e94f8bb9b58 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 5 Apr 2023 23:16:49 +0200 Subject: [PATCH 1568/2916] feat: Compact objects in CommandResult --- cmd/kluctl/commands/command_result.go | 66 ++++++---- pkg/deployment/commands/delete.go | 8 +- pkg/deployment/commands/deploy.go | 30 ++--- pkg/deployment/commands/diff.go | 15 +-- pkg/deployment/commands/poke_images.go | 15 +-- pkg/deployment/commands/prune.go | 7 +- pkg/deployment/commands/utils.go | 87 +++++++++++++ pkg/deployment/utils/diff_utils.go | 14 +-- pkg/deployment/utils/diff_utils_test.go | 2 +- pkg/diff/obfuscate.go | 25 ++-- pkg/types/result/command_result.go | 36 ++++-- pkg/types/result/compact.go | 155 ++++++++++++++++++++++++ 12 files changed, 347 insertions(+), 113 deletions(-) create mode 100644 pkg/deployment/commands/utils.go create mode 100644 pkg/types/result/compact.go diff --git a/cmd/kluctl/commands/command_result.go b/cmd/kluctl/commands/command_result.go index f8a874cc3..c77356586 100644 --- a/cmd/kluctl/commands/command_result.go +++ b/cmd/kluctl/commands/command_result.go @@ -24,46 +24,64 @@ func formatCommandResultText(cr *result.CommandResult, short bool) string { prettyErrors(buf, cr.Warnings) } - if len(cr.NewObjects) != 0 { + var newObjects []k8s.ObjectRef + var changedObjects []k8s.ObjectRef + var deletedObjects []k8s.ObjectRef + var orphanObjects []k8s.ObjectRef + var appliedHookObjects []k8s.ObjectRef + + for _, o := range cr.Objects { + if o.New { + newObjects = append(newObjects, o.Ref) + } + if len(o.Changes) != 0 { + changedObjects = append(changedObjects, o.Ref) + } + if o.Deleted { + deletedObjects = append(deletedObjects, o.Ref) + } + if o.Orphan { + orphanObjects = append(orphanObjects, o.Ref) + } + if o.Hook { + appliedHookObjects = append(appliedHookObjects, o.Ref) + } + } + + if len(newObjects) != 0 { buf.WriteString("\nNew objects:\n") - prettyObjectRefs(buf, cr.NewObjects) + prettyObjectRefs(buf, newObjects) } - if len(cr.ChangedObjects) != 0 { + if len(changedObjects) != 0 { buf.WriteString("\nChanged objects:\n") - var refs []k8s.ObjectRef - for _, co := range cr.ChangedObjects { - refs = append(refs, co.Ref) - } - prettyObjectRefs(buf, refs) + prettyObjectRefs(buf, changedObjects) if !short { buf.WriteString("\n") - - for i, co := range cr.ChangedObjects { + for i, o := range cr.Objects { + if len(o.Changes) == 0 { + continue + } if i != 0 { buf.WriteString("\n") } - prettyChanges(buf, co.Ref, co.Changes) + prettyChanges(buf, o.Ref, o.Changes) } } } - if len(cr.DeletedObjects) != 0 { + if len(deletedObjects) != 0 { buf.WriteString("\nDeleted objects:\n") - prettyObjectRefs(buf, cr.DeletedObjects) + prettyObjectRefs(buf, deletedObjects) } - if len(cr.AppliedHookObjects) != 0 { + if len(appliedHookObjects) != 0 { buf.WriteString("\nApplied hooks:\n") - var refs []k8s.ObjectRef - for _, o := range cr.AppliedHookObjects { - refs = append(refs, o.GetK8sRef()) - } - prettyObjectRefs(buf, refs) + prettyObjectRefs(buf, appliedHookObjects) } - if len(cr.OrphanObjects) != 0 { + if len(orphanObjects) != 0 { buf.WriteString("\nOrphan objects:\n") - prettyObjectRefs(buf, cr.OrphanObjects) + prettyObjectRefs(buf, orphanObjects) } if len(cr.Errors) != 0 { @@ -104,7 +122,11 @@ func prettyChanges(buf io.StringWriter, ref k8s.ObjectRef, changes []result.Chan } func formatCommandResultYaml(cr *result.CommandResult) (string, error) { - b, err := yaml.WriteYamlString(cr) + compactedCr := *cr + compactedCr.CompactedObjects = compactedCr.Objects + compactedCr.Objects = nil + + b, err := yaml.WriteYamlString(compactedCr) if err != nil { return "", err } diff --git a/pkg/deployment/commands/delete.go b/pkg/deployment/commands/delete.go index 6d7ebe07b..970362d97 100644 --- a/pkg/deployment/commands/delete.go +++ b/pkg/deployment/commands/delete.go @@ -55,10 +55,8 @@ func (cmd *DeleteCommand) Run(ctx context.Context, k *k8s.K8sCluster, confirmCb } return &result.CommandResult{ - RenderedObjects: cmd.c.LocalObjects(), - RemoteObjects: ru.GetFilteredRemoteObjects(nil), - DeletedObjects: deleted, - Errors: dew.GetErrorsList(), - Warnings: dew.GetWarningsList(), + Objects: collectObjects(cmd.c, ru, nil, nil, nil, deleted), + Errors: dew.GetErrorsList(), + Warnings: dew.GetWarningsList(), }, nil } diff --git a/pkg/deployment/commands/deploy.go b/pkg/deployment/commands/deploy.go index 427710916..6b58c8762 100644 --- a/pkg/deployment/commands/deploy.go +++ b/pkg/deployment/commands/deploy.go @@ -65,17 +65,10 @@ func (cmd *DeployCommand) Run(ctx context.Context, k *k8s.K8sCluster, diffResult orphanObjects, err := FindOrphanObjects(k, ru, cmd.c) diffResult := &result.CommandResult{ - RenderedObjects: cmd.c.LocalObjects(), - RemoteObjects: ru.GetFilteredRemoteObjects(nil), - AppliedObjects: au.GetAppliedObjects(), - AppliedHookObjects: au.GetAppliedHookObjects(), - NewObjects: au.GetNewObjectRefs(), - ChangedObjects: du.ChangedObjects, - DeletedObjects: au.GetDeletedObjects(), - OrphanObjects: orphanObjects, - Errors: dew.GetErrorsList(), - Warnings: dew.GetWarningsList(), - SeenImages: cmd.c.Images.SeenImages(false), + Objects: collectObjects(cmd.c, ru, au, du, orphanObjects, nil), + Errors: dew.GetErrorsList(), + Warnings: dew.GetWarningsList(), + SeenImages: cmd.c.Images.SeenImages(false), } err = diffResultCb(diffResult) @@ -102,16 +95,9 @@ func (cmd *DeployCommand) Run(ctx context.Context, k *k8s.K8sCluster, diffResult return nil, err } return &result.CommandResult{ - RenderedObjects: cmd.c.LocalObjects(), - RemoteObjects: ru.GetFilteredRemoteObjects(nil), - AppliedObjects: au.GetAppliedObjects(), - AppliedHookObjects: au.GetAppliedHookObjects(), - NewObjects: au.GetNewObjectRefs(), - ChangedObjects: du.ChangedObjects, - DeletedObjects: au.GetDeletedObjects(), - OrphanObjects: orphanObjects, - Errors: dew.GetErrorsList(), - Warnings: dew.GetWarningsList(), - SeenImages: cmd.c.Images.SeenImages(false), + Objects: collectObjects(cmd.c, ru, au, du, orphanObjects, nil), + Errors: dew.GetErrorsList(), + Warnings: dew.GetWarningsList(), + SeenImages: cmd.c.Images.SeenImages(false), }, nil } diff --git a/pkg/deployment/commands/diff.go b/pkg/deployment/commands/diff.go index c15f97864..66a5f5fdc 100644 --- a/pkg/deployment/commands/diff.go +++ b/pkg/deployment/commands/diff.go @@ -66,16 +66,9 @@ func (cmd *DiffCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*result.Com return nil, err } return &result.CommandResult{ - RenderedObjects: cmd.c.LocalObjects(), - RemoteObjects: ru.GetFilteredRemoteObjects(nil), - AppliedObjects: au.GetAppliedObjects(), - AppliedHookObjects: au.GetAppliedHookObjects(), - NewObjects: au.GetNewObjectRefs(), - ChangedObjects: du.ChangedObjects, - DeletedObjects: au.GetDeletedObjects(), - OrphanObjects: orphanObjects, - Errors: dew.GetErrorsList(), - Warnings: dew.GetWarningsList(), - SeenImages: cmd.c.Images.SeenImages(false), + Objects: collectObjects(cmd.c, ru, au, du, orphanObjects, nil), + Errors: dew.GetErrorsList(), + Warnings: dew.GetWarningsList(), + SeenImages: cmd.c.Images.SeenImages(false), }, nil } diff --git a/pkg/deployment/commands/poke_images.go b/pkg/deployment/commands/poke_images.go index d2c4de720..7d8e0d704 100644 --- a/pkg/deployment/commands/poke_images.go +++ b/pkg/deployment/commands/poke_images.go @@ -101,16 +101,9 @@ func (cmd *PokeImagesCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*resu } return &result.CommandResult{ - RenderedObjects: cmd.c.LocalObjects(), - RemoteObjects: ru.GetFilteredRemoteObjects(nil), - AppliedObjects: au.GetAppliedObjects(), - AppliedHookObjects: au.GetAppliedHookObjects(), - NewObjects: au.GetNewObjectRefs(), - ChangedObjects: du.ChangedObjects, - DeletedObjects: au.GetDeletedObjects(), - OrphanObjects: orphanObjects, - Errors: dew.GetErrorsList(), - Warnings: dew.GetWarningsList(), - SeenImages: cmd.c.Images.SeenImages(false), + Objects: collectObjects(cmd.c, ru, au, du, orphanObjects, nil), + Errors: dew.GetErrorsList(), + Warnings: dew.GetWarningsList(), + SeenImages: cmd.c.Images.SeenImages(false), }, nil } diff --git a/pkg/deployment/commands/prune.go b/pkg/deployment/commands/prune.go index 5791db8d7..c60bc6bb4 100644 --- a/pkg/deployment/commands/prune.go +++ b/pkg/deployment/commands/prune.go @@ -53,11 +53,8 @@ func (cmd *PruneCommand) Run(ctx context.Context, k *k8s.K8sCluster, confirmCb f } return &result.CommandResult{ - RenderedObjects: cmd.c.LocalObjects(), - RemoteObjects: ru.GetFilteredRemoteObjects(nil), - DeletedObjects: deleted, - Errors: dew.GetErrorsList(), - Warnings: dew.GetWarningsList(), + Objects: collectObjects(cmd.c, ru, nil, nil, nil, deleted), + Warnings: dew.GetWarningsList(), }, nil } diff --git a/pkg/deployment/commands/utils.go b/pkg/deployment/commands/utils.go new file mode 100644 index 000000000..074d3222d --- /dev/null +++ b/pkg/deployment/commands/utils.go @@ -0,0 +1,87 @@ +package commands + +import ( + "github.com/kluctl/kluctl/v2/pkg/deployment" + "github.com/kluctl/kluctl/v2/pkg/deployment/utils" + "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/types/result" + "sort" +) + +func collectObjects(c *deployment.DeploymentCollection, ru *utils.RemoteObjectUtils, au *utils.ApplyDeploymentsUtil, du *utils.DiffUtil, orphans []k8s.ObjectRef, deleted []k8s.ObjectRef) []result.ResultObject { + m := map[k8s.ObjectRef]*result.ResultObject{} + + getOrCreate := func(ref k8s.ObjectRef) *result.ResultObject { + x, ok := m[ref] + if !ok { + x = &result.ResultObject{} + x.Ref = ref + m[ref] = x + } + return x + } + + if c != nil { + for _, x := range c.LocalObjects() { + o := getOrCreate(x.GetK8sRef()) + o.Rendered = x + } + } + if ru != nil { + for _, x := range ru.GetFilteredRemoteObjects(nil) { + o := getOrCreate(x.GetK8sRef()) + o.Remote = x + } + } + + if au != nil { + for _, x := range au.GetAppliedObjects() { + o := getOrCreate(x.GetK8sRef()) + o.Applied = x + } + + for _, x := range au.GetAppliedHookObjects() { + o := getOrCreate(x.GetK8sRef()) + o.Hook = true + } + for _, x := range au.GetNewObjectRefs() { + o := getOrCreate(x) + o.New = true + } + for _, x := range au.GetDeletedObjects() { + o := getOrCreate(x) + o.Deleted = true + } + } + if du != nil { + for _, x := range du.ChangedObjects { + o := getOrCreate(x.Ref) + o.Changes = x.Changes + } + } + for _, x := range orphans { + o := getOrCreate(x) + o.Orphan = true + } + for _, x := range deleted { + o := getOrCreate(x) + o.Deleted = true + } + + for ref, o := range m { + if o.Rendered == nil && o.Applied == nil && !o.Deleted && !o.Orphan { + // exclude all objects that are clearly not under our control, but got retrieved anyway because some + // controller passed through the discriminator labels + delete(m, ref) + } + } + + ret := make([]result.ResultObject, 0, len(m)) + for _, o := range m { + ret = append(ret, *o) + } + sort.Slice(ret, func(i, j int) bool { + return ret[i].Ref.GroupVersionKind().String() < ret[j].Ref.GroupVersionKind().String() + }) + return ret +} diff --git a/pkg/deployment/utils/diff_utils.go b/pkg/deployment/utils/diff_utils.go index edffa3ef5..55ca28566 100644 --- a/pkg/deployment/utils/diff_utils.go +++ b/pkg/deployment/utils/diff_utils.go @@ -12,7 +12,7 @@ import ( "time" ) -type diffUtil struct { +type DiffUtil struct { dew *DeploymentErrorsAndWarnings deployments []*deployment.DeploymentItem appliedObjects map[k8s2.ObjectRef]*uo.UnstructuredObject @@ -27,8 +27,8 @@ type diffUtil struct { mutex sync.Mutex } -func NewDiffUtil(dew *DeploymentErrorsAndWarnings, deployments []*deployment.DeploymentItem, ru *RemoteObjectUtils, appliedObjects map[k8s2.ObjectRef]*uo.UnstructuredObject) *diffUtil { - return &diffUtil{ +func NewDiffUtil(dew *DeploymentErrorsAndWarnings, deployments []*deployment.DeploymentItem, ru *RemoteObjectUtils, appliedObjects map[k8s2.ObjectRef]*uo.UnstructuredObject) *DiffUtil { + return &DiffUtil{ dew: dew, deployments: deployments, ru: ru, @@ -36,7 +36,7 @@ func NewDiffUtil(dew *DeploymentErrorsAndWarnings, deployments []*deployment.Dep } } -func (u *diffUtil) Diff() { +func (u *DiffUtil) Diff() { var wg sync.WaitGroup u.calcRemoteObjectsForDiff() @@ -67,7 +67,7 @@ func (u *diffUtil) Diff() { }) } -func (u *diffUtil) diffObject(lo *uo.UnstructuredObject, diffRef k8s2.ObjectRef, ao *uo.UnstructuredObject, ro *uo.UnstructuredObject, ignoreForDiffs []*types.IgnoreForDiffItemConfig) { +func (u *DiffUtil) diffObject(lo *uo.UnstructuredObject, diffRef k8s2.ObjectRef, ao *uo.UnstructuredObject, ro *uo.UnstructuredObject, ignoreForDiffs []*types.IgnoreForDiffItemConfig) { if ao != nil && ro == nil { // new? return @@ -106,7 +106,7 @@ func (u *diffUtil) diffObject(lo *uo.UnstructuredObject, diffRef k8s2.ObjectRef, } } -func (u *diffUtil) calcRemoteObjectsForDiff() { +func (u *DiffUtil) calcRemoteObjectsForDiff() { u.remoteDiffObjects = make(map[k8s2.ObjectRef]*uo.UnstructuredObject) for _, o := range u.ru.remoteObjects { diffName := o.GetK8sAnnotation("kluctl.io/diff-name") @@ -126,7 +126,7 @@ func (u *diffUtil) calcRemoteObjectsForDiff() { } } -func (u *diffUtil) getRemoteObjectForDiff(localObject *uo.UnstructuredObject) (k8s2.ObjectRef, *uo.UnstructuredObject) { +func (u *DiffUtil) getRemoteObjectForDiff(localObject *uo.UnstructuredObject) (k8s2.ObjectRef, *uo.UnstructuredObject) { ref := localObject.GetK8sRef() diffName := localObject.GetK8sAnnotation("kluctl.io/diff-name") if diffName != nil { diff --git a/pkg/deployment/utils/diff_utils_test.go b/pkg/deployment/utils/diff_utils_test.go index cb319d09a..49bd2c7b7 100644 --- a/pkg/deployment/utils/diff_utils_test.go +++ b/pkg/deployment/utils/diff_utils_test.go @@ -19,7 +19,7 @@ type diffTestConfig struct { dew *DeploymentErrorsAndWarnings ru *RemoteObjectUtils - du *diffUtil + du *DiffUtil } func (dtc *diffTestConfig) newRemoteObjects(dew *DeploymentErrorsAndWarnings) *RemoteObjectUtils { diff --git a/pkg/diff/obfuscate.go b/pkg/diff/obfuscate.go index 96a8ea254..e2f299019 100644 --- a/pkg/diff/obfuscate.go +++ b/pkg/diff/obfuscate.go @@ -17,32 +17,20 @@ type Obfuscator struct { } func (o *Obfuscator) ObfuscateResult(r *result.CommandResult) error { - for _, c := range r.ChangedObjects { - err := o.ObfuscateChanges(c.Ref, c.Changes) + for _, x := range r.Objects { + err := o.ObfuscateObject(x.Rendered) if err != nil { return err } - } - for _, n := range r.RenderedObjects { - err := o.ObfuscateObject(n) - if err != nil { - return err - } - } - for _, n := range r.RemoteObjects { - err := o.ObfuscateObject(n) + err = o.ObfuscateObject(x.Remote) if err != nil { return err } - } - for _, n := range r.AppliedObjects { - err := o.ObfuscateObject(n) + err = o.ObfuscateObject(x.Applied) if err != nil { return err } - } - for _, n := range r.AppliedHookObjects { - err := o.ObfuscateObject(n) + err = o.ObfuscateChanges(x.Ref, x.Changes) if err != nil { return err } @@ -61,6 +49,9 @@ func (o *Obfuscator) ObfuscateChanges(ref k8s.ObjectRef, changes []result.Change } func (o *Obfuscator) ObfuscateObject(x *uo.UnstructuredObject) error { + if x == nil { + return nil + } ref := x.GetK8sRef() if ref.GroupKind() == secretGk { err := o.obfuscateSecret(x) diff --git a/pkg/types/result/command_result.go b/pkg/types/result/command_result.go index 2596242b2..7990df8ee 100644 --- a/pkg/types/result/command_result.go +++ b/pkg/types/result/command_result.go @@ -61,22 +61,34 @@ type CommandInfo struct { ExcludeDeploymentDirs []string `json:"excludeDeploymentDirs,omitempty"` } +type BaseObject struct { + Ref k8s.ObjectRef `json:"ref"` + Changes []Change `json:"changes,omitempty"` + + New bool `json:"new,omitempty"` + Orphan bool `json:"orphan,omitempty"` + Deleted bool `json:"deleted,omitempty"` + Hook bool `json:"hook,omitempty"` +} + +type ResultObject struct { + BaseObject + + Rendered *uo.UnstructuredObject `json:"rendered,omitempty"` + Remote *uo.UnstructuredObject `json:"remote,omitempty"` + Applied *uo.UnstructuredObject `json:"applied,omitempty"` +} + type CommandResult struct { Command *CommandInfo `json:"command,omitempty"` Deployment *types.DeploymentProjectConfig `json:"deployment,omitempty"` - RenderedObjects []*uo.UnstructuredObject `json:"renderedObjects,omitempty"` - RemoteObjects []*uo.UnstructuredObject `json:"remoteObjects,omitempty"` - AppliedObjects []*uo.UnstructuredObject `json:"appliedObjects,omitempty"` - AppliedHookObjects []*uo.UnstructuredObject `json:"appliedHookObjects,omitempty"` - - NewObjects []k8s.ObjectRef `json:"newObjects,omitempty"` - ChangedObjects []*ChangedObject `json:"changedObjects,omitempty"` - OrphanObjects []k8s.ObjectRef `json:"orphanObjects,omitempty"` - DeletedObjects []k8s.ObjectRef `json:"deletedObjects,omitempty"` - Errors []DeploymentError `json:"errors,omitempty"` - Warnings []DeploymentError `json:"warnings,omitempty"` - SeenImages []types.FixedImage `json:"seenImages,omitempty"` + Objects []ResultObject `json:"objects,omitempty"` + CompactedObjects CompactedObjects `json:"compactedObjects,omitempty"` + + Errors []DeploymentError `json:"errors,omitempty"` + Warnings []DeploymentError `json:"warnings,omitempty"` + SeenImages []types.FixedImage `json:"seenImages,omitempty"` } type ValidateResultEntry struct { diff --git a/pkg/types/result/compact.go b/pkg/types/result/compact.go new file mode 100644 index 000000000..6ae9f8dfa --- /dev/null +++ b/pkg/types/result/compact.go @@ -0,0 +1,155 @@ +package result + +import ( + "context" + "encoding/json" + "fmt" + "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/yaml" + "github.com/sergi/go-diff/diffmatchpatch" + "strings" + "sync" +) + +type CompactedObjects []ResultObject + +type CompactedObject struct { + BaseObject + + Rendered string `json:"rendered,omitempty"` + Remote string `json:"remote,omitempty"` + Applied string `json:"applied,omitempty"` +} + +func (l CompactedObjects) MarshalJSON() ([]byte, error) { + compactedList := make([]CompactedObject, len(l)) + + d := diffmatchpatch.New() + + createPatchOrFull := func(prevJson *string, o *uo.UnstructuredObject) string { + if o == nil { + return "" + } + j, err := yaml.WriteJsonString(o) + if err != nil { + // we are a point where this was parsed/written so many times, it really shouldn't error + panic(err) + } + if *prevJson == "" { + *prevJson = j + return "full: " + j + } + + diff := d.DiffMain(*prevJson, j, false) + delta := d.DiffToDelta(diff) + *prevJson = j + + if len(delta) < len(j) { + return "delta: " + delta + } else { + return "full: " + j + } + } + + var wg sync.WaitGroup + for i, o := range l { + i := i + o := o + wg.Add(1) + go func() { + defer wg.Done() + var prevJson string + compactedList[i].BaseObject = o.BaseObject + compactedList[i].Rendered = createPatchOrFull(&prevJson, o.Rendered) + compactedList[i].Remote = createPatchOrFull(&prevJson, o.Remote) + compactedList[i].Applied = createPatchOrFull(&prevJson, o.Applied) + }() + } + wg.Wait() + + return json.Marshal(compactedList) +} + +func (l *CompactedObjects) UnmarshalJSON(b []byte) error { + var compactedList []CompactedObject + err := json.Unmarshal(b, &compactedList) + if err != nil { + return err + } + + d := diffmatchpatch.New() + + patchAndUnmarshal := func(prevJson *string, s string) (*uo.UnstructuredObject, error) { + if s == "" { + return nil, nil + } + + if strings.HasPrefix(s, "full: ") { + full := s[6:] + *prevJson = full + return uo.FromString(full) + } else if strings.HasPrefix(s, "delta: ") { + if *prevJson == "" { + return nil, fmt.Errorf("prevJson empty") + } + delta := s[7:] + diff, err := d.DiffFromDelta(*prevJson, delta) + if err != nil { + return nil, err + } + patch := d.PatchMake(diff) + newJson, result := d.PatchApply(patch, *prevJson) + for _, b := range result { + if !b { + return nil, fmt.Errorf("patch did not fully apply") + } + } + o, err := uo.FromString(newJson) + if err != nil { + return nil, err + } + *prevJson = newJson + return o, nil + } else { + return nil, fmt.Errorf("unexpected object/delta") + } + } + + ret := make([]ResultObject, len(compactedList)) + + gh := utils.NewGoHelper(context.Background(), -1) + for i, o := range compactedList { + i := i + o := o + gh.RunE(func() error { + var err error + + o2 := ResultObject{} + o2.BaseObject = o.BaseObject + + prevJson := "" + o2.Rendered, err = patchAndUnmarshal(&prevJson, o.Rendered) + if err != nil { + return err + } + o2.Remote, err = patchAndUnmarshal(&prevJson, o.Remote) + if err != nil { + return err + } + o2.Applied, err = patchAndUnmarshal(&prevJson, o.Applied) + if err != nil { + return err + } + ret[i] = o2 + return nil + }) + } + gh.Wait() + err = gh.ErrorOrNil() + if err != nil { + return err + } + *l = ret + return nil +} From a6a9b0757aeae7fecf070544e0de7993441e7d52 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 6 Apr 2023 16:01:37 +0200 Subject: [PATCH 1569/2916] feat: Add ProjectSummary and GitInfo --- cmd/kluctl/commands/utils.go | 82 +++++++++++++++++++++++++++++- pkg/types/result/command_result.go | 19 ++++++- pkg/types/result/summary.go | 15 +++++- 3 files changed, 112 insertions(+), 4 deletions(-) diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 91e801504..bb2d01751 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -3,9 +3,12 @@ package commands import ( "context" "fmt" + git2 "github.com/go-git/go-git/v5" + "github.com/google/uuid" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/types/result" "os" + "path/filepath" "strings" "time" @@ -210,7 +213,8 @@ func withProjectTargetCommandContext(ctx context.Context, args projectTargetComm func addCommandInfo(r *result.CommandResult, startTime time.Time, command string, ctx *commandCtx, targetFlags *args.TargetFlags, imageFlags *args.ImageFlags, inclusionFlags *args.InclusionFlags, dryRunFlags *args.DryRunFlags, forceApplyFlags *args.ForceApplyFlags, replaceOnErrorFlags *args.ReplaceOnErrorFlags, abortOnErrorFlags *args.AbortOnErrorFlags, noWait bool) error { - r.Command = &result.CommandInfo{ + r.Command = result.CommandInfo{ + Id: uuid.New().String(), Initiator: result.CommandInititiator_CommandLine, StartTime: types.FromTime(startTime), EndTime: types.FromTime(time.Now()), @@ -250,6 +254,82 @@ func addCommandInfo(r *result.CommandResult, startTime time.Time, command string r.Command.AbortOnError = abortOnErrorFlags.AbortOnError } r.Deployment = &ctx.targetCtx.DeploymentProject.Config + + err := addGitInfo(r, ctx) + if err != nil { + return err + } + + return nil +} + +func addGitInfo(r *result.CommandResult, ctx *commandCtx) error { + if ctx.targetCtx.KluctlProject.LoadArgs.RepoRoot == "" { + return nil + } + + projectDirAbs, err := filepath.Abs(ctx.targetCtx.KluctlProject.LoadArgs.ProjectDir) + if err != nil { + return err + } + + subDir, err := filepath.Rel(ctx.targetCtx.KluctlProject.LoadArgs.RepoRoot, projectDirAbs) + if err != nil { + return err + } + if subDir == "." { + subDir = "" + } + + g, err := git2.PlainOpen(ctx.targetCtx.KluctlProject.LoadArgs.RepoRoot) + if err != nil { + return err + } + + w, err := g.Worktree() + if err != nil { + return err + } + + s, err := w.Status() + if err != nil { + return err + } + + head, err := g.Head() + if err != nil { + return err + } + + remotes, err := g.Remotes() + if err != nil { + return err + } + + var originUrl *git_url.GitUrl + for _, r := range remotes { + if r.Config().Name == "origin" { + originUrl, err = git_url.Parse(r.Config().URLs[0]) + if err != nil { + return err + } + } + } + + var normaliedUrl string + if originUrl != nil { + normaliedUrl = originUrl.NormalizedRepoKey() + } + + r.GitInfo = &result.GitInfo{ + Url: originUrl, + Ref: head.Name().String(), + SubDir: subDir, + Commit: head.Hash().String(), + Dirty: !s.IsClean(), + } + r.Project.NormalizedGitUrl = normaliedUrl + r.Project.SubDir = subDir return nil } diff --git a/pkg/types/result/command_result.go b/pkg/types/result/command_result.go index 7990df8ee..20efd9906 100644 --- a/pkg/types/result/command_result.go +++ b/pkg/types/result/command_result.go @@ -1,6 +1,7 @@ package result import ( + git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/utils/uo" @@ -38,8 +39,14 @@ const ( CommandInititiator_KluctlDeployment = "KluctlDeployment" ) +type ProjectKey struct { + NormalizedGitUrl string `json:"normalizedGitUrl,omitempty"` + SubDir string `json:"subDir,omitempty"` +} + type CommandInfo struct { Initiator CommandInitiator `json:"initiator" validate:"oneof=CommandLine KluctlDeployment"` + Id string `json:"id"` StartTime types.JsonTime `json:"startTime"` EndTime types.JsonTime `json:"endTime"` KluctlDeployment *KluctlDeploymentInfo `json:"kluctlDeployment,omitempty"` @@ -61,6 +68,14 @@ type CommandInfo struct { ExcludeDeploymentDirs []string `json:"excludeDeploymentDirs,omitempty"` } +type GitInfo struct { + Url *git_url.GitUrl `json:"url"` + Ref string `json:"ref"` + SubDir string `json:"subDir"` + Commit string `json:"commit"` + Dirty bool `json:"dirty"` +} + type BaseObject struct { Ref k8s.ObjectRef `json:"ref"` Changes []Change `json:"changes,omitempty"` @@ -80,7 +95,9 @@ type ResultObject struct { } type CommandResult struct { - Command *CommandInfo `json:"command,omitempty"` + Project ProjectKey `json:"project"` + Command CommandInfo `json:"command,omitempty"` + GitInfo *GitInfo `json:"gitInfo,omitempty"` Deployment *types.DeploymentProjectConfig `json:"deployment,omitempty"` Objects []ResultObject `json:"objects,omitempty"` diff --git a/pkg/types/result/summary.go b/pkg/types/result/summary.go index d8d8adfb7..39e1e3765 100644 --- a/pkg/types/result/summary.go +++ b/pkg/types/result/summary.go @@ -1,8 +1,9 @@ package result type CommandResultSummary struct { - Id string `json:"id"` - Command *CommandInfo `json:"commandInfo"` + Project ProjectKey `json:"project"` + Command CommandInfo `json:"commandInfo"` + GitInfo *GitInfo `json:"gitInfo,omitempty"` RenderedObjects int `json:"renderedObjects"` RemoteObjects int `json:"remoteObjects"` @@ -18,3 +19,13 @@ type CommandResultSummary struct { TotalChanges int `json:"totalChanges"` } + +type ProjectSummary struct { + Project ProjectKey `json:"project"` + + LastValidateResult *ValidateResult `json:"lastValidateResult,omitempty"` + + LastDeployCommand *CommandResultSummary `json:"lastDeployCommand,omitempty"` + LastDeleteCommand *CommandResultSummary `json:"LastDeleteCommand,omitempty"` + LastPruneCommand *CommandResultSummary `json:"lastPruneCommand,omitempty"` +} From bebc2c6e92f350a05196c904af74038b8a028100 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 7 Apr 2023 23:21:08 +0200 Subject: [PATCH 1570/2916] refactor: Clearly separate between CommandResult and CompactedCommandResult --- cmd/kluctl/commands/command_result.go | 6 +----- pkg/types/result/command_result.go | 24 ++++++++++++++++++++++-- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/cmd/kluctl/commands/command_result.go b/cmd/kluctl/commands/command_result.go index c77356586..c9710d7a5 100644 --- a/cmd/kluctl/commands/command_result.go +++ b/cmd/kluctl/commands/command_result.go @@ -122,11 +122,7 @@ func prettyChanges(buf io.StringWriter, ref k8s.ObjectRef, changes []result.Chan } func formatCommandResultYaml(cr *result.CommandResult) (string, error) { - compactedCr := *cr - compactedCr.CompactedObjects = compactedCr.Objects - compactedCr.Objects = nil - - b, err := yaml.WriteYamlString(compactedCr) + b, err := yaml.WriteYamlString(cr.ToCompacted()) if err != nil { return "", err } diff --git a/pkg/types/result/command_result.go b/pkg/types/result/command_result.go index 20efd9906..0d3d319e0 100644 --- a/pkg/types/result/command_result.go +++ b/pkg/types/result/command_result.go @@ -100,14 +100,34 @@ type CommandResult struct { GitInfo *GitInfo `json:"gitInfo,omitempty"` Deployment *types.DeploymentProjectConfig `json:"deployment,omitempty"` - Objects []ResultObject `json:"objects,omitempty"` - CompactedObjects CompactedObjects `json:"compactedObjects,omitempty"` + Objects []ResultObject `json:"objects,omitempty"` Errors []DeploymentError `json:"errors,omitempty"` Warnings []DeploymentError `json:"warnings,omitempty"` SeenImages []types.FixedImage `json:"seenImages,omitempty"` } +func (cr *CommandResult) ToCompacted() *CompactedCommandResult { + ret := &CompactedCommandResult{ + CommandResult: *cr, + } + ret.CompactedObjects = ret.Objects + ret.Objects = nil + return ret +} + +type CompactedCommandResult struct { + CommandResult + + CompactedObjects CompactedObjects `json:"compactedObjects,omitempty"` +} + +func (ccr *CompactedCommandResult) ToNonCompacted() *CommandResult { + ret := ccr.CommandResult + ret.Objects = ccr.CompactedObjects + return &ret +} + type ValidateResultEntry struct { Ref k8s.ObjectRef `json:"ref"` Annotation string `json:"annotation"` From eb19472960a788d0756b940f605d7e1ee1976be0 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 7 Apr 2023 23:56:21 +0200 Subject: [PATCH 1571/2916] feat: Store id directly in result --- cmd/kluctl/commands/utils.go | 2 -- pkg/deployment/commands/delete.go | 2 ++ pkg/deployment/commands/deploy.go | 3 +++ pkg/deployment/commands/diff.go | 2 ++ pkg/deployment/commands/poke_images.go | 2 ++ pkg/deployment/commands/prune.go | 2 ++ pkg/deployment/commands/validate.go | 7 +++++-- pkg/types/result/command_result.go | 3 ++- pkg/types/result/summary.go | 1 + 9 files changed, 19 insertions(+), 5 deletions(-) diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index bb2d01751..c9fdcdc1b 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -4,7 +4,6 @@ import ( "context" "fmt" git2 "github.com/go-git/go-git/v5" - "github.com/google/uuid" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/types/result" "os" @@ -214,7 +213,6 @@ func addCommandInfo(r *result.CommandResult, startTime time.Time, command string imageFlags *args.ImageFlags, inclusionFlags *args.InclusionFlags, dryRunFlags *args.DryRunFlags, forceApplyFlags *args.ForceApplyFlags, replaceOnErrorFlags *args.ReplaceOnErrorFlags, abortOnErrorFlags *args.AbortOnErrorFlags, noWait bool) error { r.Command = result.CommandInfo{ - Id: uuid.New().String(), Initiator: result.CommandInititiator_CommandLine, StartTime: types.FromTime(startTime), EndTime: types.FromTime(time.Now()), diff --git a/pkg/deployment/commands/delete.go b/pkg/deployment/commands/delete.go index 970362d97..e87cffa50 100644 --- a/pkg/deployment/commands/delete.go +++ b/pkg/deployment/commands/delete.go @@ -3,6 +3,7 @@ package commands import ( "context" "fmt" + "github.com/google/uuid" "github.com/kluctl/kluctl/v2/pkg/deployment" utils2 "github.com/kluctl/kluctl/v2/pkg/deployment/utils" "github.com/kluctl/kluctl/v2/pkg/k8s" @@ -55,6 +56,7 @@ func (cmd *DeleteCommand) Run(ctx context.Context, k *k8s.K8sCluster, confirmCb } return &result.CommandResult{ + Id: uuid.New().String(), Objects: collectObjects(cmd.c, ru, nil, nil, nil, deleted), Errors: dew.GetErrorsList(), Warnings: dew.GetWarningsList(), diff --git a/pkg/deployment/commands/deploy.go b/pkg/deployment/commands/deploy.go index 6b58c8762..417e2e27d 100644 --- a/pkg/deployment/commands/deploy.go +++ b/pkg/deployment/commands/deploy.go @@ -3,6 +3,7 @@ package commands import ( "context" "fmt" + "github.com/google/uuid" "github.com/kluctl/kluctl/v2/pkg/deployment" utils2 "github.com/kluctl/kluctl/v2/pkg/deployment/utils" "github.com/kluctl/kluctl/v2/pkg/k8s" @@ -65,6 +66,7 @@ func (cmd *DeployCommand) Run(ctx context.Context, k *k8s.K8sCluster, diffResult orphanObjects, err := FindOrphanObjects(k, ru, cmd.c) diffResult := &result.CommandResult{ + Id: uuid.New().String(), Objects: collectObjects(cmd.c, ru, au, du, orphanObjects, nil), Errors: dew.GetErrorsList(), Warnings: dew.GetWarningsList(), @@ -95,6 +97,7 @@ func (cmd *DeployCommand) Run(ctx context.Context, k *k8s.K8sCluster, diffResult return nil, err } return &result.CommandResult{ + Id: uuid.New().String(), Objects: collectObjects(cmd.c, ru, au, du, orphanObjects, nil), Errors: dew.GetErrorsList(), Warnings: dew.GetWarningsList(), diff --git a/pkg/deployment/commands/diff.go b/pkg/deployment/commands/diff.go index 66a5f5fdc..aa2f185c5 100644 --- a/pkg/deployment/commands/diff.go +++ b/pkg/deployment/commands/diff.go @@ -3,6 +3,7 @@ package commands import ( "context" "fmt" + "github.com/google/uuid" "github.com/kluctl/kluctl/v2/pkg/deployment" "github.com/kluctl/kluctl/v2/pkg/deployment/utils" "github.com/kluctl/kluctl/v2/pkg/k8s" @@ -66,6 +67,7 @@ func (cmd *DiffCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*result.Com return nil, err } return &result.CommandResult{ + Id: uuid.New().String(), Objects: collectObjects(cmd.c, ru, au, du, orphanObjects, nil), Errors: dew.GetErrorsList(), Warnings: dew.GetWarningsList(), diff --git a/pkg/deployment/commands/poke_images.go b/pkg/deployment/commands/poke_images.go index 7d8e0d704..14fdb9e0c 100644 --- a/pkg/deployment/commands/poke_images.go +++ b/pkg/deployment/commands/poke_images.go @@ -3,6 +3,7 @@ package commands import ( "context" "fmt" + "github.com/google/uuid" "github.com/kluctl/kluctl/v2/pkg/deployment" utils2 "github.com/kluctl/kluctl/v2/pkg/deployment/utils" "github.com/kluctl/kluctl/v2/pkg/k8s" @@ -101,6 +102,7 @@ func (cmd *PokeImagesCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*resu } return &result.CommandResult{ + Id: uuid.New().String(), Objects: collectObjects(cmd.c, ru, au, du, orphanObjects, nil), Errors: dew.GetErrorsList(), Warnings: dew.GetWarningsList(), diff --git a/pkg/deployment/commands/prune.go b/pkg/deployment/commands/prune.go index c60bc6bb4..e989b120c 100644 --- a/pkg/deployment/commands/prune.go +++ b/pkg/deployment/commands/prune.go @@ -3,6 +3,7 @@ package commands import ( "context" "fmt" + "github.com/google/uuid" "github.com/kluctl/kluctl/v2/pkg/deployment" utils2 "github.com/kluctl/kluctl/v2/pkg/deployment/utils" "github.com/kluctl/kluctl/v2/pkg/k8s" @@ -53,6 +54,7 @@ func (cmd *PruneCommand) Run(ctx context.Context, k *k8s.K8sCluster, confirmCb f } return &result.CommandResult{ + Id: uuid.New().String(), Objects: collectObjects(cmd.c, ru, nil, nil, nil, deleted), Warnings: dew.GetWarningsList(), }, nil diff --git a/pkg/deployment/commands/validate.go b/pkg/deployment/commands/validate.go index 08cd62004..938e9d4f2 100644 --- a/pkg/deployment/commands/validate.go +++ b/pkg/deployment/commands/validate.go @@ -2,6 +2,7 @@ package commands import ( "context" + "github.com/google/uuid" "github.com/kluctl/kluctl/v2/pkg/deployment" utils2 "github.com/kluctl/kluctl/v2/pkg/deployment/utils" "github.com/kluctl/kluctl/v2/pkg/k8s" @@ -29,8 +30,10 @@ func NewValidateCommand(ctx context.Context, discriminator string, c *deployment } func (cmd *ValidateCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*result.ValidateResult, error) { - var r result.ValidateResult - r.Ready = true + r := result.ValidateResult{ + Id: uuid.New().String(), + Ready: true, + } cmd.dew.Init() diff --git a/pkg/types/result/command_result.go b/pkg/types/result/command_result.go index 0d3d319e0..3ae943626 100644 --- a/pkg/types/result/command_result.go +++ b/pkg/types/result/command_result.go @@ -46,7 +46,6 @@ type ProjectKey struct { type CommandInfo struct { Initiator CommandInitiator `json:"initiator" validate:"oneof=CommandLine KluctlDeployment"` - Id string `json:"id"` StartTime types.JsonTime `json:"startTime"` EndTime types.JsonTime `json:"endTime"` KluctlDeployment *KluctlDeploymentInfo `json:"kluctlDeployment,omitempty"` @@ -95,6 +94,7 @@ type ResultObject struct { } type CommandResult struct { + Id string `json:"id"` Project ProjectKey `json:"project"` Command CommandInfo `json:"command,omitempty"` GitInfo *GitInfo `json:"gitInfo,omitempty"` @@ -135,6 +135,7 @@ type ValidateResultEntry struct { } type ValidateResult struct { + Id string `json:"id"` Ready bool `json:"ready"` Warnings []DeploymentError `json:"warnings,omitempty"` Errors []DeploymentError `json:"errors,omitempty"` diff --git a/pkg/types/result/summary.go b/pkg/types/result/summary.go index 39e1e3765..01e4a1b65 100644 --- a/pkg/types/result/summary.go +++ b/pkg/types/result/summary.go @@ -1,6 +1,7 @@ package result type CommandResultSummary struct { + Id string `json:"id"` Project ProjectKey `json:"project"` Command CommandInfo `json:"commandInfo"` GitInfo *GitInfo `json:"gitInfo,omitempty"` From bbf51bfbc5494a9816434c640987128816f824fb Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sat, 8 Apr 2023 00:06:40 +0200 Subject: [PATCH 1572/2916] feat: Introduce BuildSummary function --- pkg/types/result/summary.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/pkg/types/result/summary.go b/pkg/types/result/summary.go index 01e4a1b65..1fad0f1cb 100644 --- a/pkg/types/result/summary.go +++ b/pkg/types/result/summary.go @@ -30,3 +30,36 @@ type ProjectSummary struct { LastDeleteCommand *CommandResultSummary `json:"LastDeleteCommand,omitempty"` LastPruneCommand *CommandResultSummary `json:"lastPruneCommand,omitempty"` } + +func (cr *CommandResult) BuildSummary() *CommandResultSummary { + count := func(f func(o ResultObject) bool) int { + cnt := 0 + for _, o := range cr.Objects { + if f(o) { + cnt++ + } + } + return cnt + } + + ret := &CommandResultSummary{ + Id: cr.Id, + Project: cr.Project, + Command: cr.Command, + GitInfo: cr.GitInfo, + RenderedObjects: count(func(o ResultObject) bool { return o.Rendered != nil }), + RemoteObjects: count(func(o ResultObject) bool { return o.Remote != nil }), + AppliedObjects: count(func(o ResultObject) bool { return o.Applied != nil }), + AppliedHookObjects: count(func(o ResultObject) bool { return o.Hook }), + NewObjects: count(func(o ResultObject) bool { return o.New }), + ChangedObjects: count(func(o ResultObject) bool { return len(o.Changes) != 0 }), + OrphanObjects: count(func(o ResultObject) bool { return o.Orphan }), + DeletedObjects: count(func(o ResultObject) bool { return o.Deleted }), + Errors: len(cr.Errors), + Warnings: len(cr.Warnings), + } + for _, o := range cr.Objects { + ret.TotalChanges += len(o.Changes) + } + return ret +} From 4f058a5a5bf291a691dcf50f8e9207ade07826cb Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sat, 8 Apr 2023 23:47:44 +0200 Subject: [PATCH 1573/2916] feat: Implement result store --- pkg/k8s/client.go | 11 +- pkg/k8s/client_factory.go | 8 + pkg/k8s/fake_client_factory.go | 17 +- pkg/k8s/k8s_cluster.go | 36 ++++ pkg/results/result-store-secrets.go | 290 ++++++++++++++++++++++++++++ pkg/results/result-store.go | 27 +++ pkg/types/result/command_result.go | 12 ++ pkg/types/result/compact.go | 25 +++ pkg/utils/go_helper.go | 9 + pkg/utils/gzip.go | 39 ++++ 10 files changed, 468 insertions(+), 6 deletions(-) create mode 100644 pkg/results/result-store-secrets.go create mode 100644 pkg/results/result-store.go create mode 100644 pkg/utils/gzip.go diff --git a/pkg/k8s/client.go b/pkg/k8s/client.go index 829240fc5..2d8069549 100644 --- a/pkg/k8s/client.go +++ b/pkg/k8s/client.go @@ -6,6 +6,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/dynamic" corev1 "k8s.io/client-go/kubernetes/typed/core/v1" + "k8s.io/client-go/metadata" ) type k8sClients struct { @@ -16,8 +17,9 @@ type k8sClients struct { } type parallelClientEntry struct { - corev1 corev1.CoreV1Interface - dynamicClient dynamic.Interface + corev1 corev1.CoreV1Interface + dynamicClient dynamic.Interface + metadataClient metadata.Interface warnings []ApiWarning } @@ -59,6 +61,11 @@ func newK8sClients(ctx context.Context, clientFactory ClientFactory, count int) return nil, err } + p.metadataClient, err = clientFactory.MetadataClient(p) + if err != nil { + return nil, err + } + k.clientPool <- p } return k, nil diff --git a/pkg/k8s/client_factory.go b/pkg/k8s/client_factory.go index f5e1f4b7a..2edff9b44 100644 --- a/pkg/k8s/client_factory.go +++ b/pkg/k8s/client_factory.go @@ -7,6 +7,7 @@ import ( "k8s.io/client-go/discovery/cached/disk" "k8s.io/client-go/dynamic" corev1 "k8s.io/client-go/kubernetes/typed/core/v1" + "k8s.io/client-go/metadata" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" "net/http" @@ -24,6 +25,7 @@ type ClientFactory interface { DiscoveryClient() (discovery.DiscoveryInterface, error) CoreV1Client(wh rest.WarningHandler) (corev1.CoreV1Interface, error) DynamicClient(wh rest.WarningHandler) (dynamic.Interface, error) + MetadataClient(wh rest.WarningHandler) (metadata.Interface, error) } type realClientFactory struct { @@ -65,6 +67,12 @@ func (r *realClientFactory) DynamicClient(wh rest.WarningHandler) (dynamic.Inter return dynamic.NewForConfigAndClient(config, r.httpClient) } +func (r *realClientFactory) MetadataClient(wh rest.WarningHandler) (metadata.Interface, error) { + config := rest.CopyConfig(r.config) + config.WarningHandler = wh + return metadata.NewForConfigAndClient(config, r.httpClient) +} + func (r *realClientFactory) CloseIdleConnections() { r.httpClient.CloseIdleConnections() } diff --git a/pkg/k8s/fake_client_factory.go b/pkg/k8s/fake_client_factory.go index 6ee62d1e4..2de043f74 100644 --- a/pkg/k8s/fake_client_factory.go +++ b/pkg/k8s/fake_client_factory.go @@ -12,6 +12,8 @@ import ( fake_dynamic "k8s.io/client-go/dynamic/fake" "k8s.io/client-go/kubernetes/fake" corev1 "k8s.io/client-go/kubernetes/typed/core/v1" + "k8s.io/client-go/metadata" + fake_metadata "k8s.io/client-go/metadata/fake" "k8s.io/client-go/rest" "k8s.io/client-go/testing" "sigs.k8s.io/kustomize/kyaml/openapi" @@ -20,8 +22,9 @@ import ( ) type fakeClientFactory struct { - clientSet *fake.Clientset - dynamicClient *fake_dynamic.FakeDynamicClient + clientSet *fake.Clientset + dynamicClient *fake_dynamic.FakeDynamicClient + metadataClient *fake_metadata.FakeMetadataClient } func (f *fakeClientFactory) RESTConfig() *rest.Config { @@ -47,6 +50,10 @@ func (f *fakeClientFactory) DynamicClient(wh rest.WarningHandler) (dynamic.Inter return f.dynamicClient, nil } +func (f *fakeClientFactory) MetadataClient(wh rest.WarningHandler) (metadata.Interface, error) { + return f.metadataClient, nil +} + func NewFakeClientFactory(objects ...runtime.Object) *fakeClientFactory { scheme := runtime.NewScheme() _ = v1.AddToScheme(scheme) @@ -56,10 +63,12 @@ func NewFakeClientFactory(objects ...runtime.Object) *fakeClientFactory { clientSet.Fake.Resources = ConvertSchemeToAPIResources(scheme) dynamicClient := fake_dynamic.NewSimpleDynamicClient(scheme, objects...) + metadataClient := fake_metadata.NewSimpleMetadataClient(scheme, objects...) return &fakeClientFactory{ - clientSet: clientSet, - dynamicClient: dynamicClient, + clientSet: clientSet, + dynamicClient: dynamicClient, + metadataClient: metadataClient, } } diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index e76955bb1..eb7e931f5 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "io" + "k8s.io/client-go/metadata" "net/http" "strings" "sync" @@ -134,6 +135,41 @@ func (k *K8sCluster) ListObjects(gvk schema.GroupVersionKind, namespace string, return result, apiWarnings, err } +func (k *K8sCluster) ListMetadata(gvk schema.GroupVersionKind, namespace string, labels map[string]string) ([]*uo.UnstructuredObject, []ApiWarning, error) { + var result []*uo.UnstructuredObject + + gvr, err := k.Resources.GetGVRForGVK(gvk) + if err != nil { + return nil, nil, err + } + + apiWarnings, err := k.clients.withClientFromPool(func(p *parallelClientEntry) error { + var r metadata.ResourceInterface + if namespace == "" { + r = p.metadataClient.Resource(*gvr) + } else { + r = p.metadataClient.Resource(*gvr).Namespace(namespace) + } + o := v1.ListOptions{ + LabelSelector: k.buildLabelSelector(labels), + } + x, err := r.List(k.ctx, o) + if err != nil { + return err + } + for _, o := range x.Items { + u, err := uo.FromStruct(o) + if err != nil { + return err + } + u.SetK8sGVK(gvk) + result = append(result, u) + } + return nil + }) + return result, apiWarnings, err +} + func (k *K8sCluster) GetSingleObject(ref k8s.ObjectRef) (*uo.UnstructuredObject, []ApiWarning, error) { var result *uo.UnstructuredObject apiWarnings, err := k.clients.withDynamicClientForGVK(k.Resources, ref.GroupVersionKind(), ref.Namespace, func(r dynamic.ResourceInterface) error { diff --git a/pkg/results/result-store-secrets.go b/pkg/results/result-store-secrets.go new file mode 100644 index 000000000..c409bf552 --- /dev/null +++ b/pkg/results/result-store-secrets.go @@ -0,0 +1,290 @@ +package results + +import ( + "compress/gzip" + "context" + "encoding/base64" + "fmt" + "github.com/kluctl/kluctl/v2/pkg/k8s" + k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/types/result" + "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/yaml" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime/schema" + "path" + "regexp" + "sort" + "strings" +) + +type ResultStoreSecrets struct { + k *k8s.K8sCluster + writeNamespace string +} + +func NewResultStoreSecrets(k *k8s.K8sCluster, writeNamespace string) (*ResultStoreSecrets, error) { + s := &ResultStoreSecrets{ + k: k, + writeNamespace: writeNamespace, + } + + if s.writeNamespace != "" { + _, _, err := k.GetSingleObject(k8s2.NewObjectRef("", "v1", "Namespace", s.writeNamespace, "")) + if err != nil && errors.IsNotFound(err) { + ns := uo.FromMap(map[string]interface{}{ + "apiVersion": "v1", + "kind": "Namespace", + "metadata": map[string]any{ + "name": s.writeNamespace, + }, + }) + _, _, err = k.ReadWrite().PatchObject(ns, k8s.PatchOptions{}) + if err != nil { + return nil, err + } + } else if err != nil { + return nil, err + } + } + + return s, nil +} + +var invalidChars = regexp.MustCompile(`[^a-zA-Z0-9-]`) + +func (s *ResultStoreSecrets) buildName(cr *result.CommandResult) string { + var name string + + if cr.Project.NormalizedGitUrl != "" { + s := path.Base(cr.Project.NormalizedGitUrl) + if s != "" { + name = s + "-" + } + } + + name = strings.ReplaceAll(name, "_", "-") + name = invalidChars.ReplaceAllString(name, "") + + nameWithId := name + cr.Id + if len(nameWithId) > 63 { + trimmedName := []byte(name[:63-len(cr.Id)]) + trimmedName[len(trimmedName)-1] = '-' + nameWithId = string(trimmedName) + cr.Id + } + + return nameWithId +} + +func (s *ResultStoreSecrets) WriteCommandResult(ctx context.Context, cr *result.CommandResult) error { + crJson, err := yaml.WriteJsonString(cr.ToReducedObjects()) + if err != nil { + return err + } + compressedCr, err := utils.CompressGzip([]byte(crJson), gzip.BestCompression) + if err != nil { + return err + } + + objectsJson, err := yaml.WriteJsonString(result.CompactedObjects(cr.Objects)) + if err != nil { + return err + } + compressedObjects, err := utils.CompressGzip([]byte(objectsJson), gzip.BestCompression) + if err != nil { + return err + } + + summary := cr.BuildSummary() + summaryJson, err := yaml.WriteJsonString(summary) + if err != nil { + return err + } + + secret := uo.FromMap(map[string]interface{}{ + "apiVersion": "v1", + "kind": "Secret", + "metadata": map[string]any{ + "name": s.buildName(cr), + "namespace": s.writeNamespace, + "labels": map[string]any{ + "kluctl.io/result-id": cr.Id, + }, + "annotations": map[string]any{ + "kluctl.io/result-summary": summaryJson, + }, + }, + "data": map[string]any{ + "reducedResult": compressedCr, + "compactedObjects": compressedObjects, + }, + }) + if cr.Project.NormalizedGitUrl != "" { + secret.SetK8sAnnotation("kluctl.io/result-project-normalized-url", cr.Project.NormalizedGitUrl) + } + if cr.Project.SubDir != "" { + secret.SetK8sAnnotation("kluctl.io/result-project-subdir", cr.Project.SubDir) + } + + _, _, err = s.k.ReadWrite().PatchObject(secret, k8s.PatchOptions{}) + return err +} + +func (s *ResultStoreSecrets) ListProjects(ctx context.Context, options ListProjectsOptions) ([]result.ProjectSummary, error) { + summaries, err := s.ListCommandResultSummaries(ctx, ListCommandResultSummariesOptions{ + ProjectFilter: options.ProjectFilter, + }) + if err != nil { + return nil, err + } + + m := map[result.ProjectKey]result.ProjectSummary{} + for _, s := range summaries { + if _, ok := m[s.Project]; !ok { + m[s.Project] = result.ProjectSummary{Project: s.Project} + } + } + + ret := make([]result.ProjectSummary, 0, len(m)) + for _, p := range m { + for _, rs := range summaries { + switch rs.Command.Command { + case "deploy": + if p.LastDeployCommand == nil { + rs := rs + p.LastDeployCommand = &rs + } + case "delete": + if p.LastDeleteCommand == nil { + rs := rs + p.LastDeleteCommand = &rs + } + case "prune": + if p.LastPruneCommand == nil { + rs := rs + p.LastPruneCommand = &rs + } + } + } + ret = append(ret, p) + } + + return ret, nil +} + +func (s *ResultStoreSecrets) ListCommandResultSummaries(ctx context.Context, options ListCommandResultSummariesOptions) ([]result.CommandResultSummary, error) { + l, _, err := s.k.ListMetadata(schema.GroupVersionKind{ + Version: "v1", + Kind: "Secret", + }, "", map[string]string{ + "kluctl.io/result-id": "", + }) + if err != nil { + return nil, err + } + + ret := make([]result.CommandResultSummary, 0, len(l)) + + for _, x := range l { + summaryJson := x.GetK8sAnnotation("kluctl.io/result-summary") + if summaryJson == nil || *summaryJson == "" { + continue + } + + var summary result.CommandResultSummary + err = yaml.ReadYamlString(*summaryJson, &summary) + if err != nil { + continue + } + + if options.ProjectFilter != nil { + if summary.Project != *options.ProjectFilter { + continue + } + } + + ret = append(ret, summary) + } + + sort.Slice(ret, func(i, j int) bool { + return ret[i].Command.StartTime >= ret[j].Command.StartTime + }) + + return ret, nil +} + +func (s *ResultStoreSecrets) GetCommandResult(ctx context.Context, options GetCommandResultOptions) (*result.CommandResult, error) { + l, _, err := s.k.ListObjects(schema.GroupVersionKind{ + Version: "v1", + Kind: "Secret", + }, "", map[string]string{ + "kluctl.io/result-id": options.Id, + }) + if err != nil { + return nil, err + } + if len(l) == 0 { + return nil, nil + } + + var crJson, objectsJson []byte + err = utils.RunParallelE(ctx, func() error { + s, ok, err := l[0].GetNestedString("data", "reducedResult") + if err != nil { + return err + } + if !ok { + return fmt.Errorf("reducedResult field not present for %s", options.Id) + } + crJson, err = base64.StdEncoding.DecodeString(s) + if err != nil { + return err + } + crJson, err = utils.UncompressGzip(crJson) + if err != nil { + return err + } + return nil + }, func() error { + if options.Reduced { + return nil + } + s, ok, err := l[0].GetNestedString("data", "compactedObjects") + if err != nil { + return err + } + if !ok { + return fmt.Errorf("compactedObjects field not present for %s", options.Id) + } + objectsJson, err = base64.StdEncoding.DecodeString(s) + if err != nil { + return err + } + + objectsJson, err = utils.UncompressGzip(objectsJson) + if err != nil { + return err + } + return err + }) + if err != nil { + return nil, err + } + + var cr result.CommandResult + err = yaml.ReadYamlBytes(crJson, &cr) + if err != nil { + return nil, err + } + if !options.Reduced { + var objects result.CompactedObjects + err = yaml.ReadYamlBytes(objectsJson, &objects) + if err != nil { + return nil, err + } + cr.Objects = objects + } + + return &cr, nil +} diff --git a/pkg/results/result-store.go b/pkg/results/result-store.go new file mode 100644 index 000000000..6d4b633ee --- /dev/null +++ b/pkg/results/result-store.go @@ -0,0 +1,27 @@ +package results + +import ( + "context" + "github.com/kluctl/kluctl/v2/pkg/types/result" +) + +type ListProjectsOptions struct { + ProjectFilter *result.ProjectKey `json:"projectFilter,omitempty"` +} + +type ListCommandResultSummariesOptions struct { + ProjectFilter *result.ProjectKey `json:"projectFilter,omitempty"` +} + +type GetCommandResultOptions struct { + Id string `json:"id"` + Reduced bool `json:"reduced,omitempty"` +} + +type ResultStore interface { + WriteCommandResult(ctx context.Context, cr *result.CommandResult) error + + ListProjects(ctx context.Context, options ListProjectsOptions) ([]result.ProjectSummary, error) + ListCommandResultSummaries(ctx context.Context, options ListCommandResultSummariesOptions) ([]result.CommandResultSummary, error) + GetCommandResult(ctx context.Context, options GetCommandResultOptions) (*result.CommandResult, error) +} diff --git a/pkg/types/result/command_result.go b/pkg/types/result/command_result.go index 3ae943626..f5b673122 100644 --- a/pkg/types/result/command_result.go +++ b/pkg/types/result/command_result.go @@ -116,6 +116,18 @@ func (cr *CommandResult) ToCompacted() *CompactedCommandResult { return ret } +func (cr *CommandResult) ToReducedObjects() *CommandResult { + ret := *cr + ret.Objects = make([]ResultObject, len(ret.Objects)) + for i, o := range cr.Objects { + ret.Objects[i] = o + ret.Objects[i].Rendered = buildReducedObject(o.Rendered) + ret.Objects[i].Remote = buildReducedObject(o.Remote) + ret.Objects[i].Applied = buildReducedObject(o.Applied) + } + return &ret +} + type CompactedCommandResult struct { CommandResult diff --git a/pkg/types/result/compact.go b/pkg/types/result/compact.go index 6ae9f8dfa..ae94847c3 100644 --- a/pkg/types/result/compact.go +++ b/pkg/types/result/compact.go @@ -153,3 +153,28 @@ func (l *CompactedObjects) UnmarshalJSON(b []byte) error { *l = ret return nil } + +func buildReducedObject(o *uo.UnstructuredObject) *uo.UnstructuredObject { + if o == nil { + return nil + } + ref := o.GetK8sRef() + m := map[string]any{ + "apiVersion": ref.GroupVersion().String(), + "kind": ref.Kind, + "metadata": map[string]any{ + "name": ref.Name, + }, + } + ret := uo.FromMap(m) + if ref.Namespace != "" { + ret.SetK8sNamespace(ref.Namespace) + } + if len(o.GetK8sLabels()) != 0 { + ret.SetK8sLabels(o.GetK8sLabels()) + } + if len(o.GetK8sAnnotations()) != 0 { + ret.SetK8sAnnotations(o.GetK8sAnnotations()) + } + return ret +} diff --git a/pkg/utils/go_helper.go b/pkg/utils/go_helper.go index b37a0a4de..eb10fc9fc 100644 --- a/pkg/utils/go_helper.go +++ b/pkg/utils/go_helper.go @@ -66,3 +66,12 @@ func (g *goHelper) Wait() { func (g *goHelper) ErrorOrNil() error { return g.errs.ErrorOrNil() } + +func RunParallelE(ctx context.Context, fs ...func() error) error { + g := NewGoHelper(ctx, 0) + for _, f := range fs { + g.RunE(f) + } + g.Wait() + return g.ErrorOrNil() +} diff --git a/pkg/utils/gzip.go b/pkg/utils/gzip.go new file mode 100644 index 000000000..7d331f5ed --- /dev/null +++ b/pkg/utils/gzip.go @@ -0,0 +1,39 @@ +package utils + +import ( + "bytes" + "compress/gzip" + "io" +) + +func CompressGzip(input []byte, level int) ([]byte, error) { + buf := bytes.NewBuffer(make([]byte, 0, len(input))) + w, err := gzip.NewWriterLevel(buf, level) + if err != nil { + return nil, err + } + _, err = w.Write(input) + if err != nil { + return nil, err + } + err = w.Close() + if err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +func UncompressGzip(input []byte) ([]byte, error) { + r, err := gzip.NewReader(bytes.NewReader(input)) + if err != nil { + return nil, err + } + + buf := bytes.NewBuffer(nil) + _, err = io.Copy(buf, r) + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} From 4f07bd54fcae97de6bd5a3b9b11fcaf2d4a8384e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 10 Apr 2023 21:42:25 +0200 Subject: [PATCH 1574/2916] fix: Fix empty label selectors --- pkg/k8s/k8s_cluster.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index eb7e931f5..be727ffd8 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -111,7 +111,11 @@ func (k *K8sCluster) buildLabelSelector(labels map[string]string) string { if len(ret) != 0 { ret += "," } - ret += fmt.Sprintf("%s=%s", k, v) + if v == "" { + ret += k + } else { + ret += fmt.Sprintf("%s=%s", k, v) + } } return ret } From 5335ca579ba108ded3d59389f251f38bf1a4fe49 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 10 Apr 2023 23:04:44 +0200 Subject: [PATCH 1575/2916] feat: Automatically record results to the kluctl-results namespace --- cmd/kluctl/commands/cmd_delete.go | 3 ++- cmd/kluctl/commands/cmd_deploy.go | 13 +++++++------ cmd/kluctl/commands/cmd_diff.go | 2 +- cmd/kluctl/commands/cmd_poke_images.go | 3 ++- cmd/kluctl/commands/cmd_prune.go | 3 ++- cmd/kluctl/commands/command_result.go | 16 +++++++++++++--- cmd/kluctl/commands/utils.go | 24 ++++++++++++++++++------ 7 files changed, 45 insertions(+), 19 deletions(-) diff --git a/cmd/kluctl/commands/cmd_delete.go b/cmd/kluctl/commands/cmd_delete.go index 57f7c1a74..83ff899ad 100644 --- a/cmd/kluctl/commands/cmd_delete.go +++ b/cmd/kluctl/commands/cmd_delete.go @@ -43,6 +43,7 @@ func (cmd *deleteCmd) Run(ctx context.Context) error { helmCredentials: cmd.HelmCredentials, dryRunArgs: &cmd.DryRunFlags, renderOutputDirFlags: cmd.RenderOutputDirFlags, + needsResultStore: true, } startTime := time.Now() return withProjectCommandContext(ctx, ptArgs, func(cmdCtx *commandCtx) error { @@ -62,7 +63,7 @@ func (cmd *deleteCmd) Run(ctx context.Context) error { if err != nil { return err } - err = outputCommandResult(ctx, cmd.OutputFormatFlags, result) + err = outputCommandResult(cmdCtx, cmd.OutputFormatFlags, result, true) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_deploy.go b/cmd/kluctl/commands/cmd_deploy.go index e6e918d58..f53dd2dfb 100644 --- a/cmd/kluctl/commands/cmd_deploy.go +++ b/cmd/kluctl/commands/cmd_deploy.go @@ -46,6 +46,7 @@ func (cmd *deployCmd) Run(ctx context.Context) error { helmCredentials: cmd.HelmCredentials, dryRunArgs: &cmd.DryRunFlags, renderOutputDirFlags: cmd.RenderOutputDirFlags, + needsResultStore: true, } startTime := time.Now() return withProjectCommandContext(ctx, ptArgs, func(cmdCtx *commandCtx) error { @@ -66,7 +67,7 @@ func (cmd *deployCmd) runCmdDeploy(cmdCtx *commandCtx, startTime time.Time) erro cmd2.NoWait = cmd.NoWait cb := func(diffResult *result.CommandResult) error { - return cmd.diffResultCb(cmdCtx.ctx, diffResult) + return cmd.diffResultCb(cmdCtx, diffResult) } if cmd.Yes || cmd.DryRun { cb = nil @@ -80,7 +81,7 @@ func (cmd *deployCmd) runCmdDeploy(cmdCtx *commandCtx, startTime time.Time) erro if err != nil { return err } - err = outputCommandResult(cmdCtx.ctx, cmd.OutputFormatFlags, result) + err = outputCommandResult(cmdCtx, cmd.OutputFormatFlags, result, true) if err != nil { return err } @@ -90,11 +91,11 @@ func (cmd *deployCmd) runCmdDeploy(cmdCtx *commandCtx, startTime time.Time) erro return nil } -func (cmd *deployCmd) diffResultCb(ctx context.Context, diffResult *result.CommandResult) error { +func (cmd *deployCmd) diffResultCb(ctx *commandCtx, diffResult *result.CommandResult) error { flags := cmd.OutputFormatFlags flags.OutputFormat = nil // use default output format - err := outputCommandResult(ctx, flags, diffResult) + err := outputCommandResult(ctx, flags, diffResult, false) if err != nil { return err } @@ -102,11 +103,11 @@ func (cmd *deployCmd) diffResultCb(ctx context.Context, diffResult *result.Comma return nil } if len(diffResult.Errors) != 0 { - if !status.AskForConfirmation(ctx, "The diff resulted in errors, do you still want to proceed?") { + if !status.AskForConfirmation(ctx.ctx, "The diff resulted in errors, do you still want to proceed?") { return fmt.Errorf("aborted") } } else { - if !status.AskForConfirmation(ctx, "The diff succeeded, do you want to proceed?") { + if !status.AskForConfirmation(ctx.ctx, "The diff succeeded, do you want to proceed?") { return fmt.Errorf("aborted") } } diff --git a/cmd/kluctl/commands/cmd_diff.go b/cmd/kluctl/commands/cmd_diff.go index 640caecee..91be42dfa 100644 --- a/cmd/kluctl/commands/cmd_diff.go +++ b/cmd/kluctl/commands/cmd_diff.go @@ -56,7 +56,7 @@ func (cmd *diffCmd) Run(ctx context.Context) error { if err != nil { return err } - err = outputCommandResult(ctx, cmd.OutputFormatFlags, result) + err = outputCommandResult(cmdCtx, cmd.OutputFormatFlags, result, false) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_poke_images.go b/cmd/kluctl/commands/cmd_poke_images.go index fccb20bc9..c08e60966 100644 --- a/cmd/kluctl/commands/cmd_poke_images.go +++ b/cmd/kluctl/commands/cmd_poke_images.go @@ -38,6 +38,7 @@ func (cmd *pokeImagesCmd) Run(ctx context.Context) error { helmCredentials: cmd.HelmCredentials, dryRunArgs: &cmd.DryRunFlags, renderOutputDirFlags: cmd.RenderOutputDirFlags, + needsResultStore: true, } startTime := time.Now() return withProjectCommandContext(ctx, ptArgs, func(cmdCtx *commandCtx) error { @@ -57,7 +58,7 @@ func (cmd *pokeImagesCmd) Run(ctx context.Context) error { if err != nil { return err } - err = outputCommandResult(ctx, cmd.OutputFormatFlags, result) + err = outputCommandResult(cmdCtx, cmd.OutputFormatFlags, result, true) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_prune.go b/cmd/kluctl/commands/cmd_prune.go index cd19d2e33..cd3b57697 100644 --- a/cmd/kluctl/commands/cmd_prune.go +++ b/cmd/kluctl/commands/cmd_prune.go @@ -40,6 +40,7 @@ func (cmd *pruneCmd) Run(ctx context.Context) error { helmCredentials: cmd.HelmCredentials, dryRunArgs: &cmd.DryRunFlags, renderOutputDirFlags: cmd.RenderOutputDirFlags, + needsResultStore: true, } startTime := time.Now() return withProjectCommandContext(ctx, ptArgs, func(cmdCtx *commandCtx) error { @@ -59,7 +60,7 @@ func (cmd *pruneCmd) runCmdPrune(cmdCtx *commandCtx, startTime time.Time) error if err != nil { return err } - err = outputCommandResult(cmdCtx.ctx, cmd.OutputFormatFlags, result) + err = outputCommandResult(cmdCtx, cmd.OutputFormatFlags, result, true) if err != nil { return err } diff --git a/cmd/kluctl/commands/command_result.go b/cmd/kluctl/commands/command_result.go index c9710d7a5..167745f9c 100644 --- a/cmd/kluctl/commands/command_result.go +++ b/cmd/kluctl/commands/command_result.go @@ -220,8 +220,8 @@ func outputHelper(ctx context.Context, output []string, cb func(format string) ( return nil } -func outputCommandResult(ctx context.Context, flags args.OutputFormatFlags, cr *result.CommandResult) error { - status.Flush(ctx) +func outputCommandResult(ctx *commandCtx, flags args.OutputFormatFlags, cr *result.CommandResult, writeToResultStore bool) error { + status.Flush(ctx.ctx) if !flags.NoObfuscate { var obfuscator diff.Obfuscator @@ -231,7 +231,17 @@ func outputCommandResult(ctx context.Context, flags args.OutputFormatFlags, cr * } } - return outputHelper(ctx, flags.OutputFormat, func(format string) (string, error) { + if writeToResultStore && ctx.resultStore != nil { + s := status.Start(ctx.ctx, "Writing command result") + defer s.Failed() + err := ctx.resultStore.WriteCommandResult(ctx.ctx, cr) + if err != nil { + return err + } + s.Success() + } + + return outputHelper(ctx.ctx, flags.OutputFormat, func(format string) (string, error) { return formatCommandResult(cr, format, flags.ShortOutput) }) } diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index c9fdcdc1b..adeb7d792 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -4,6 +4,7 @@ import ( "context" "fmt" git2 "github.com/go-git/go-git/v5" + "github.com/kluctl/kluctl/v2/pkg/results" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/types/result" "os" @@ -134,12 +135,14 @@ type projectTargetCommandArgs struct { forCompletion bool offlineKubernetes bool kubernetesVersion string + needsResultStore bool } type commandCtx struct { - ctx context.Context - targetCtx *kluctl_project.TargetContext - images *deployment.Images + ctx context.Context + targetCtx *kluctl_project.TargetContext + images *deployment.Images + resultStore results.ResultStore } func withProjectCommandContext(ctx context.Context, args projectTargetCommandArgs, cb func(cmdCtx *commandCtx) error) error { @@ -200,10 +203,19 @@ func withProjectTargetCommandContext(ctx context.Context, args projectTargetComm } } + var resultStore results.ResultStore + if args.needsResultStore { + resultStore, err = results.NewResultStoreSecrets(targetCtx.SharedContext.K, "kluctl-results") + if err != nil { + return err + } + } + cmdCtx := &commandCtx{ - ctx: ctx, - targetCtx: targetCtx, - images: images, + ctx: ctx, + targetCtx: targetCtx, + images: images, + resultStore: resultStore, } return cb(cmdCtx) From 627e9f353c09bb95c7f861320258a18e1d3e7926 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 11 Apr 2023 11:22:39 +0200 Subject: [PATCH 1576/2916] chore: Run go mod tidy --- go.mod | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index baacdc9e8..42fbe969b 100644 --- a/go.mod +++ b/go.mod @@ -58,10 +58,12 @@ require ( github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.7 github.com/go-git/go-git/v5 v5.6.1 github.com/go-logr/logr v1.2.4 + github.com/google/uuid v1.3.0 github.com/hashicorp/go-multierror v1.1.1 github.com/huandu/xstrings v1.4.0 github.com/onsi/gomega v1.27.6 github.com/otiai10/copy v1.11.0 + github.com/sergi/go-diff v1.2.0 go.mozilla.org/sops/v3 v3.7.4-0.20220901181616-9124783930b1 sigs.k8s.io/controller-runtime v0.14.5 sigs.k8s.io/kustomize/api v0.13.2 @@ -149,7 +151,6 @@ require ( github.com/google/go-cmp v0.5.9 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/google/uuid v1.3.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect github.com/googleapis/gax-go/v2 v2.7.0 // indirect github.com/gorilla/mux v1.8.0 // indirect @@ -207,7 +208,6 @@ require ( github.com/rubenv/sql-migrate v1.3.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect - github.com/sergi/go-diff v1.2.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect github.com/skeema/knownhosts v1.1.0 // indirect github.com/spf13/afero v1.9.3 // indirect From b2df735d9a87a22b899790015c00cffd346243e6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 11 Apr 2023 11:50:47 +0200 Subject: [PATCH 1577/2916] fix: Still print result in case storing to the result store failed --- cmd/kluctl/commands/command_result.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/cmd/kluctl/commands/command_result.go b/cmd/kluctl/commands/command_result.go index 167745f9c..482ff068e 100644 --- a/cmd/kluctl/commands/command_result.go +++ b/cmd/kluctl/commands/command_result.go @@ -231,19 +231,25 @@ func outputCommandResult(ctx *commandCtx, flags args.OutputFormatFlags, cr *resu } } + var resultStoreErr error if writeToResultStore && ctx.resultStore != nil { s := status.Start(ctx.ctx, "Writing command result") defer s.Failed() - err := ctx.resultStore.WriteCommandResult(ctx.ctx, cr) - if err != nil { - return err + resultStoreErr = ctx.resultStore.WriteCommandResult(ctx.ctx, cr) + if resultStoreErr != nil { + s.FailedWithMessage("Failed to write result to result store: %s", resultStoreErr.Error()) + } else { + s.Success() } - s.Success() } - return outputHelper(ctx.ctx, flags.OutputFormat, func(format string) (string, error) { + err := outputHelper(ctx.ctx, flags.OutputFormat, func(format string) (string, error) { return formatCommandResult(cr, format, flags.ShortOutput) }) + if err == nil && resultStoreErr != nil { + return resultStoreErr + } + return err } func outputValidateResult(ctx context.Context, output []string, vr *result.ValidateResult) error { From ecf2220bcac4722b2249c0596d58de0df542e622 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 11 Apr 2023 13:28:22 +0200 Subject: [PATCH 1578/2916] feat: Only write command result when --write-command-result is specified --- cmd/kluctl/args/project.go | 5 +++++ cmd/kluctl/commands/cmd_delete.go | 3 ++- cmd/kluctl/commands/cmd_deploy.go | 3 ++- cmd/kluctl/commands/cmd_diff.go | 4 +++- cmd/kluctl/commands/cmd_poke_images.go | 3 ++- cmd/kluctl/commands/cmd_prune.go | 3 ++- cmd/kluctl/commands/root.go | 1 + cmd/kluctl/commands/utils.go | 6 +++--- docs/reference/commands/common-arguments.md | 17 +++++++++++++++++ 9 files changed, 37 insertions(+), 8 deletions(-) diff --git a/cmd/kluctl/args/project.go b/cmd/kluctl/args/project.go index fc896f813..272b51723 100644 --- a/cmd/kluctl/args/project.go +++ b/cmd/kluctl/args/project.go @@ -41,3 +41,8 @@ type TargetFlags struct { TargetNameOverride string `group:"project" short:"T" help:"Overrides the target name. If -t is used at the same time, then the target will be looked up based on -t and then renamed to the value of -T. If no target is specified via -t, then the no-name target is renamed to the value of -T."` Context string `group:"project" help:"Overrides the context name specified in the target. If the selected target does not specify a context or the no-name target is used, --context will override the currently active context."` } + +type CommandResultFlags struct { + WriteCommandResult bool `group:"results" help:"Enable experimental writing of command results into the cluster. Command results will be written into ConfigMaps in the kluctl-results namespace."` + CommandResultNamespace string `group:"results" help:"Override the namespace to be used when writing command results." default:"kluctl-results"` +} diff --git a/cmd/kluctl/commands/cmd_delete.go b/cmd/kluctl/commands/cmd_delete.go index 83ff899ad..39ab7f6a5 100644 --- a/cmd/kluctl/commands/cmd_delete.go +++ b/cmd/kluctl/commands/cmd_delete.go @@ -21,6 +21,7 @@ type deleteCmd struct { args.DryRunFlags args.OutputFormatFlags args.RenderOutputDirFlags + args.CommandResultFlags Discriminator string `group:"misc" help:"Override the discriminator used to find objects for deletion."` } @@ -43,7 +44,7 @@ func (cmd *deleteCmd) Run(ctx context.Context) error { helmCredentials: cmd.HelmCredentials, dryRunArgs: &cmd.DryRunFlags, renderOutputDirFlags: cmd.RenderOutputDirFlags, - needsResultStore: true, + commandResultFlags: &cmd.CommandResultFlags, } startTime := time.Now() return withProjectCommandContext(ctx, ptArgs, func(cmdCtx *commandCtx) error { diff --git a/cmd/kluctl/commands/cmd_deploy.go b/cmd/kluctl/commands/cmd_deploy.go index f53dd2dfb..c3ba95bd4 100644 --- a/cmd/kluctl/commands/cmd_deploy.go +++ b/cmd/kluctl/commands/cmd_deploy.go @@ -25,6 +25,7 @@ type deployCmd struct { args.HookFlags args.OutputFormatFlags args.RenderOutputDirFlags + args.CommandResultFlags NoWait bool `group:"misc" help:"Don't wait for objects readiness'"` } @@ -46,7 +47,7 @@ func (cmd *deployCmd) Run(ctx context.Context) error { helmCredentials: cmd.HelmCredentials, dryRunArgs: &cmd.DryRunFlags, renderOutputDirFlags: cmd.RenderOutputDirFlags, - needsResultStore: true, + commandResultFlags: &cmd.CommandResultFlags, } startTime := time.Now() return withProjectCommandContext(ctx, ptArgs, func(cmdCtx *commandCtx) error { diff --git a/cmd/kluctl/commands/cmd_diff.go b/cmd/kluctl/commands/cmd_diff.go index 91be42dfa..fd6fb7689 100644 --- a/cmd/kluctl/commands/cmd_diff.go +++ b/cmd/kluctl/commands/cmd_diff.go @@ -20,6 +20,7 @@ type diffCmd struct { args.IgnoreFlags args.OutputFormatFlags args.RenderOutputDirFlags + args.CommandResultFlags } func (cmd *diffCmd) Help() string { @@ -38,6 +39,7 @@ func (cmd *diffCmd) Run(ctx context.Context) error { inclusionFlags: cmd.InclusionFlags, helmCredentials: cmd.HelmCredentials, renderOutputDirFlags: cmd.RenderOutputDirFlags, + commandResultFlags: &cmd.CommandResultFlags, } startTime := time.Now() return withProjectCommandContext(ctx, ptArgs, func(cmdCtx *commandCtx) error { @@ -56,7 +58,7 @@ func (cmd *diffCmd) Run(ctx context.Context) error { if err != nil { return err } - err = outputCommandResult(cmdCtx, cmd.OutputFormatFlags, result, false) + err = outputCommandResult(cmdCtx, cmd.OutputFormatFlags, result, true) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_poke_images.go b/cmd/kluctl/commands/cmd_poke_images.go index c08e60966..888ecddde 100644 --- a/cmd/kluctl/commands/cmd_poke_images.go +++ b/cmd/kluctl/commands/cmd_poke_images.go @@ -20,6 +20,7 @@ type pokeImagesCmd struct { args.DryRunFlags args.OutputFormatFlags args.RenderOutputDirFlags + args.CommandResultFlags } func (cmd *pokeImagesCmd) Help() string { @@ -38,7 +39,7 @@ func (cmd *pokeImagesCmd) Run(ctx context.Context) error { helmCredentials: cmd.HelmCredentials, dryRunArgs: &cmd.DryRunFlags, renderOutputDirFlags: cmd.RenderOutputDirFlags, - needsResultStore: true, + commandResultFlags: &cmd.CommandResultFlags, } startTime := time.Now() return withProjectCommandContext(ctx, ptArgs, func(cmdCtx *commandCtx) error { diff --git a/cmd/kluctl/commands/cmd_prune.go b/cmd/kluctl/commands/cmd_prune.go index cd3b57697..567458b99 100644 --- a/cmd/kluctl/commands/cmd_prune.go +++ b/cmd/kluctl/commands/cmd_prune.go @@ -20,6 +20,7 @@ type pruneCmd struct { args.DryRunFlags args.OutputFormatFlags args.RenderOutputDirFlags + args.CommandResultFlags } func (cmd *pruneCmd) Help() string { @@ -40,7 +41,7 @@ func (cmd *pruneCmd) Run(ctx context.Context) error { helmCredentials: cmd.HelmCredentials, dryRunArgs: &cmd.DryRunFlags, renderOutputDirFlags: cmd.RenderOutputDirFlags, - needsResultStore: true, + commandResultFlags: &cmd.CommandResultFlags, } startTime := time.Now() return withProjectCommandContext(ctx, ptArgs, func(cmdCtx *commandCtx) error { diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index 5a4b2589d..7ec3fb4b1 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -78,6 +78,7 @@ var flagGroups = []groupInfo{ {group: "inclusion", title: "Inclusion/Exclusion arguments:", description: "Control inclusion/exclusion."}, {group: "misc", title: "Misc arguments:", description: "Command specific arguments."}, {group: "flux", title: "Flux arguments:", description: "EXPERIMENTAL: Subcommands for interaction with flux-kluctl-controller"}, + {group: "results", title: "Command Results:", description: "Configure how command results are stored."}, } var origStderr = os.Stderr diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index adeb7d792..420624f47 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -130,12 +130,12 @@ type projectTargetCommandArgs struct { helmCredentials args.HelmCredentials dryRunArgs *args.DryRunFlags renderOutputDirFlags args.RenderOutputDirFlags + commandResultFlags *args.CommandResultFlags forSeal bool forCompletion bool offlineKubernetes bool kubernetesVersion string - needsResultStore bool } type commandCtx struct { @@ -204,8 +204,8 @@ func withProjectTargetCommandContext(ctx context.Context, args projectTargetComm } var resultStore results.ResultStore - if args.needsResultStore { - resultStore, err = results.NewResultStoreSecrets(targetCtx.SharedContext.K, "kluctl-results") + if args.commandResultFlags != nil && args.commandResultFlags.WriteCommandResult { + resultStore, err = results.NewResultStoreSecrets(targetCtx.SharedContext.K, args.commandResultFlags.CommandResultNamespace) if err != nil { return err } diff --git a/docs/reference/commands/common-arguments.md b/docs/reference/commands/common-arguments.md index 2f2e85433..2263c15b2 100644 --- a/docs/reference/commands/common-arguments.md +++ b/docs/reference/commands/common-arguments.md @@ -124,3 +124,20 @@ Inclusion/Exclusion arguments: ``` + +## Command Results arguments + +These arguments control how command results are stored. + + +``` +Command Results: + Configure how command results are stored. + + --command-result-namespace string Override the namespace to be used when writing command results. (default + "kluctl-results") + --write-command-result Enable experimental writing of command results into the cluster. Command + results will be written into ConfigMaps in the kluctl-results namespace. + +``` + From cd3d4b786efb0af2d956ea2e73b938b23e2ba3b2 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 11 Apr 2023 22:58:58 +0200 Subject: [PATCH 1579/2916] feat: Store cluster info in command result --- cmd/kluctl/commands/utils.go | 23 +++++++++++++++++++++++ pkg/types/result/command_result.go | 15 ++++++++++----- pkg/types/result/summary.go | 10 ++++++---- pkg/utils/uo/k8s_fields.go | 9 ++++++++- 4 files changed, 47 insertions(+), 10 deletions(-) diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 420624f47..0b783d808 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -6,6 +6,7 @@ import ( git2 "github.com/go-git/go-git/v5" "github.com/kluctl/kluctl/v2/pkg/results" "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/types/result" "os" "path/filepath" @@ -270,6 +271,11 @@ func addCommandInfo(r *result.CommandResult, startTime time.Time, command string return err } + err = addClusterInfo(r, ctx) + if err != nil { + return err + } + return nil } @@ -343,6 +349,23 @@ func addGitInfo(r *result.CommandResult, ctx *commandCtx) error { return nil } +func addClusterInfo(r *result.CommandResult, ctx *commandCtx) error { + kubeSystemNs, _, err := ctx.targetCtx.SharedContext.K.GetSingleObject( + k8s.NewObjectRef("", "v1", "Namespace", "kube-system", "")) + if err != nil { + return err + } + // we reuse the kube-system namespace uid as global cluster id + clusterId := kubeSystemNs.GetK8sUid() + if clusterId == "" { + return fmt.Errorf("kube-system namespace has no uid") + } + r.ClusterInfo = &result.ClusterInfo{ + ClusterId: clusterId, + } + return nil +} + func clientConfigGetter(forCompletion bool) func(context *string) (*rest.Config, *api.Config, error) { return func(context *string) (*rest.Config, *api.Config, error) { if forCompletion { diff --git a/pkg/types/result/command_result.go b/pkg/types/result/command_result.go index f5b673122..c7047fa63 100644 --- a/pkg/types/result/command_result.go +++ b/pkg/types/result/command_result.go @@ -75,6 +75,10 @@ type GitInfo struct { Dirty bool `json:"dirty"` } +type ClusterInfo struct { + ClusterId string `json:"clusterId"` +} + type BaseObject struct { Ref k8s.ObjectRef `json:"ref"` Changes []Change `json:"changes,omitempty"` @@ -94,11 +98,12 @@ type ResultObject struct { } type CommandResult struct { - Id string `json:"id"` - Project ProjectKey `json:"project"` - Command CommandInfo `json:"command,omitempty"` - GitInfo *GitInfo `json:"gitInfo,omitempty"` - Deployment *types.DeploymentProjectConfig `json:"deployment,omitempty"` + Id string `json:"id"` + Project ProjectKey `json:"project"` + Command CommandInfo `json:"command,omitempty"` + GitInfo *GitInfo `json:"gitInfo,omitempty"` + ClusterInfo *ClusterInfo `json:"clusterInfo,omitempty"` + Deployment *types.DeploymentProjectConfig `json:"deployment,omitempty"` Objects []ResultObject `json:"objects,omitempty"` diff --git a/pkg/types/result/summary.go b/pkg/types/result/summary.go index 1fad0f1cb..3dfa79a75 100644 --- a/pkg/types/result/summary.go +++ b/pkg/types/result/summary.go @@ -1,10 +1,11 @@ package result type CommandResultSummary struct { - Id string `json:"id"` - Project ProjectKey `json:"project"` - Command CommandInfo `json:"commandInfo"` - GitInfo *GitInfo `json:"gitInfo,omitempty"` + Id string `json:"id"` + Project ProjectKey `json:"project"` + Command CommandInfo `json:"commandInfo"` + GitInfo *GitInfo `json:"gitInfo,omitempty"` + ClusterInfo *ClusterInfo `json:"clusterInfo,omitempty"` RenderedObjects int `json:"renderedObjects"` RemoteObjects int `json:"remoteObjects"` @@ -47,6 +48,7 @@ func (cr *CommandResult) BuildSummary() *CommandResultSummary { Project: cr.Project, Command: cr.Command, GitInfo: cr.GitInfo, + ClusterInfo: cr.ClusterInfo, RenderedObjects: count(func(o ResultObject) bool { return o.Rendered != nil }), RemoteObjects: count(func(o ResultObject) bool { return o.Remote != nil }), AppliedObjects: count(func(o ResultObject) bool { return o.Applied != nil }), diff --git a/pkg/utils/uo/k8s_fields.go b/pkg/utils/uo/k8s_fields.go index 88cc49884..c45e23f57 100644 --- a/pkg/utils/uo/k8s_fields.go +++ b/pkg/utils/uo/k8s_fields.go @@ -79,7 +79,6 @@ func (uo *UnstructuredObject) SetK8sNamespace(namespace string) { panic(err) } } - } func (uo *UnstructuredObject) GetK8sRef() k8s.ObjectRef { @@ -93,6 +92,14 @@ func (uo *UnstructuredObject) GetK8sRef() k8s.ObjectRef { } } +func (uo *UnstructuredObject) GetK8sUid() string { + s, _, err := uo.GetNestedString("metadata", "uid") + if err != nil { + panic(err) + } + return s +} + func (uo *UnstructuredObject) GetK8sLabels() map[string]string { ret, ok, err := uo.GetNestedStringMapCopy("metadata", "labels") if err != nil { From 39bfb51a0539090c67d9ffa403044c4ab11d1b8a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 14 Apr 2023 21:46:27 +0200 Subject: [PATCH 1580/2916] refactor: Move ListProjects out of the ResultStore interface --- pkg/results/result-store-secrets.go | 42 ---------------------------- pkg/results/result-store.go | 43 ++++++++++++++++++++++++++++- 2 files changed, 42 insertions(+), 43 deletions(-) diff --git a/pkg/results/result-store-secrets.go b/pkg/results/result-store-secrets.go index c409bf552..bc4782a1c 100644 --- a/pkg/results/result-store-secrets.go +++ b/pkg/results/result-store-secrets.go @@ -131,48 +131,6 @@ func (s *ResultStoreSecrets) WriteCommandResult(ctx context.Context, cr *result. return err } -func (s *ResultStoreSecrets) ListProjects(ctx context.Context, options ListProjectsOptions) ([]result.ProjectSummary, error) { - summaries, err := s.ListCommandResultSummaries(ctx, ListCommandResultSummariesOptions{ - ProjectFilter: options.ProjectFilter, - }) - if err != nil { - return nil, err - } - - m := map[result.ProjectKey]result.ProjectSummary{} - for _, s := range summaries { - if _, ok := m[s.Project]; !ok { - m[s.Project] = result.ProjectSummary{Project: s.Project} - } - } - - ret := make([]result.ProjectSummary, 0, len(m)) - for _, p := range m { - for _, rs := range summaries { - switch rs.Command.Command { - case "deploy": - if p.LastDeployCommand == nil { - rs := rs - p.LastDeployCommand = &rs - } - case "delete": - if p.LastDeleteCommand == nil { - rs := rs - p.LastDeleteCommand = &rs - } - case "prune": - if p.LastPruneCommand == nil { - rs := rs - p.LastPruneCommand = &rs - } - } - } - ret = append(ret, p) - } - - return ret, nil -} - func (s *ResultStoreSecrets) ListCommandResultSummaries(ctx context.Context, options ListCommandResultSummariesOptions) ([]result.CommandResultSummary, error) { l, _, err := s.k.ListMetadata(schema.GroupVersionKind{ Version: "v1", diff --git a/pkg/results/result-store.go b/pkg/results/result-store.go index 6d4b633ee..464dc6594 100644 --- a/pkg/results/result-store.go +++ b/pkg/results/result-store.go @@ -21,7 +21,48 @@ type GetCommandResultOptions struct { type ResultStore interface { WriteCommandResult(ctx context.Context, cr *result.CommandResult) error - ListProjects(ctx context.Context, options ListProjectsOptions) ([]result.ProjectSummary, error) ListCommandResultSummaries(ctx context.Context, options ListCommandResultSummariesOptions) ([]result.CommandResultSummary, error) GetCommandResult(ctx context.Context, options GetCommandResultOptions) (*result.CommandResult, error) } + +func ListProjects(ctx context.Context, rs ResultStore, options ListProjectsOptions) ([]result.ProjectSummary, error) { + summaries, err := rs.ListCommandResultSummaries(ctx, ListCommandResultSummariesOptions{ + ProjectFilter: options.ProjectFilter, + }) + if err != nil { + return nil, err + } + + m := map[result.ProjectKey]result.ProjectSummary{} + for _, s := range summaries { + if _, ok := m[s.Project]; !ok { + m[s.Project] = result.ProjectSummary{Project: s.Project} + } + } + + ret := make([]result.ProjectSummary, 0, len(m)) + for _, p := range m { + for _, rs := range summaries { + switch rs.Command.Command { + case "deploy": + if p.LastDeployCommand == nil { + rs := rs + p.LastDeployCommand = &rs + } + case "delete": + if p.LastDeleteCommand == nil { + rs := rs + p.LastDeleteCommand = &rs + } + case "prune": + if p.LastPruneCommand == nil { + rs := rs + p.LastPruneCommand = &rs + } + } + } + ret = append(ret, p) + } + + return ret, nil +} From a6f27f9b63a940fa3ab52a26b9c6c29c99556d86 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 19 Apr 2023 09:38:10 +0200 Subject: [PATCH 1581/2916] fix: Fix error message when apply gets cancelled --- pkg/deployment/utils/apply_utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index 750195855..d66580227 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -492,7 +492,7 @@ func (a *ApplyUtil) WaitReadiness(ref k8s2.ObjectRef, timeout time.Duration) boo a.HandleError(ref, err) return false case <-a.ctx.Done(): - err := fmt.Errorf("failed waiting for readiness of %s: %w", ref.String(), err) + err := fmt.Errorf("context cancelled while waiting for readiness of %s", ref.String()) status.Warning(a.ctx, "%s (%ds elapsed)", err.Error(), elapsed) a.HandleError(ref, err) return false From a040aee28fcaccbed5ce74683bb92aae26b65dac Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 19 Apr 2023 09:38:35 +0200 Subject: [PATCH 1582/2916] fix: Clone object before obfuscating to avoid manipulating reused objects --- pkg/diff/obfuscate.go | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/pkg/diff/obfuscate.go b/pkg/diff/obfuscate.go index e2f299019..ea16017d9 100644 --- a/pkg/diff/obfuscate.go +++ b/pkg/diff/obfuscate.go @@ -18,15 +18,16 @@ type Obfuscator struct { func (o *Obfuscator) ObfuscateResult(r *result.CommandResult) error { for _, x := range r.Objects { - err := o.ObfuscateObject(x.Rendered) + var err error + x.Rendered, err = o.ObfuscateObject(x.Rendered) if err != nil { return err } - err = o.ObfuscateObject(x.Remote) + x.Remote, err = o.ObfuscateObject(x.Remote) if err != nil { return err } - err = o.ObfuscateObject(x.Applied) + x.Applied, err = o.ObfuscateObject(x.Applied) if err != nil { return err } @@ -48,18 +49,19 @@ func (o *Obfuscator) ObfuscateChanges(ref k8s.ObjectRef, changes []result.Change return nil } -func (o *Obfuscator) ObfuscateObject(x *uo.UnstructuredObject) error { +func (o *Obfuscator) ObfuscateObject(x *uo.UnstructuredObject) (*uo.UnstructuredObject, error) { if x == nil { - return nil + return nil, nil } ref := x.GetK8sRef() if ref.GroupKind() == secretGk { - err := o.obfuscateSecret(x) + var err error + x, err = o.obfuscateSecret(x) if err != nil { - return err + return x, err } } - return nil + return x, nil } func (o *Obfuscator) obfuscateSecretChanges(ref k8s.ObjectRef, changes []result.Change) error { @@ -108,26 +110,30 @@ func (o *Obfuscator) obfuscateSecretChanges(ref k8s.ObjectRef, changes []result. return nil } -func (o *Obfuscator) obfuscateSecret(x *uo.UnstructuredObject) error { +func (o *Obfuscator) obfuscateSecret(x *uo.UnstructuredObject) (*uo.UnstructuredObject, error) { data, ok, _ := x.GetNestedField("data") if ok && data != nil { + x = x.Clone() + data, _, _ = x.GetNestedField("data") if m, ok := data.(map[string]any); ok { for k, _ := range m { m[k] = base64.StdEncoding.EncodeToString([]byte("*****")) } } else { - return fmt.Errorf("'data' is not a map of strings") + return x, fmt.Errorf("'data' is not a map of strings") } } data, ok, _ = x.GetNestedField("stringData") if ok && data != nil { + x = x.Clone() + data, _, _ = x.GetNestedField("stringData") if m, ok := data.(map[string]any); ok { for k, _ := range m { m[k] = "*****" } } else { - return fmt.Errorf("'data' is not a map of strings") + return x, fmt.Errorf("'data' is not a map of strings") } } - return nil + return x, nil } From a26c175f92c3d8b425243c727e9d1b1c4694ddde Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 20 Apr 2023 17:14:52 +0200 Subject: [PATCH 1583/2916] fix: Fix merging of validate results --- pkg/deployment/commands/validate.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pkg/deployment/commands/validate.go b/pkg/deployment/commands/validate.go index 938e9d4f2..64223b2fe 100644 --- a/pkg/deployment/commands/validate.go +++ b/pkg/deployment/commands/validate.go @@ -30,7 +30,7 @@ func NewValidateCommand(ctx context.Context, discriminator string, c *deployment } func (cmd *ValidateCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*result.ValidateResult, error) { - r := result.ValidateResult{ + ret := result.ValidateResult{ Id: uuid.New().String(), Ready: true, } @@ -56,23 +56,23 @@ func (cmd *ValidateCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*result remoteObject := cmd.ru.GetRemoteObject(ref) if remoteObject == nil { - r.Errors = append(r.Errors, result.DeploymentError{Ref: ref, Message: "object not found"}) + ret.Errors = append(ret.Errors, result.DeploymentError{Ref: ref, Message: "object not found"}) continue } r := validation.ValidateObject(k, remoteObject, true, false) if !r.Ready { - r.Ready = false + ret.Ready = false } - r.Errors = append(r.Errors, r.Errors...) - r.Warnings = append(r.Warnings, r.Warnings...) - r.Results = append(r.Results, r.Results...) + ret.Errors = append(ret.Errors, r.Errors...) + ret.Warnings = append(ret.Warnings, r.Warnings...) + ret.Results = append(ret.Results, r.Results...) } } - r.Warnings = append(r.Warnings, cmd.dew.GetWarningsList()...) - r.Errors = append(r.Errors, cmd.dew.GetErrorsList()...) + ret.Warnings = append(ret.Warnings, cmd.dew.GetWarningsList()...) + ret.Errors = append(ret.Errors, cmd.dew.GetErrorsList()...) - return &r, nil + return &ret, nil } func (cmd *ValidateCommand) ForgetRemoteObject(ref k8s2.ObjectRef) { From b1a0d8c78f60e66812a681e932d4170fdbf747e5 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 24 Apr 2023 23:52:54 +0200 Subject: [PATCH 1584/2916] refactor: Allow to create DiffUtil and ApplyDeploymentsUtil without a deployment collection --- pkg/deployment/commands/deploy.go | 16 +++---- pkg/deployment/commands/diff.go | 8 ++-- pkg/deployment/commands/poke_images.go | 6 +-- pkg/deployment/utils/apply_utils.go | 28 ++++++------ pkg/deployment/utils/diff_utils.go | 61 +++++++++++++++---------- pkg/deployment/utils/diff_utils_test.go | 4 +- 6 files changed, 67 insertions(+), 56 deletions(-) diff --git a/pkg/deployment/commands/deploy.go b/pkg/deployment/commands/deploy.go index 417e2e27d..ec814632c 100644 --- a/pkg/deployment/commands/deploy.go +++ b/pkg/deployment/commands/deploy.go @@ -58,11 +58,11 @@ func (cmd *DeployCommand) Run(ctx context.Context, k *k8s.K8sCluster, diffResult } if diffResultCb != nil { - au := utils2.NewApplyDeploymentsUtil(ctx, dew, cmd.c.Deployments, ru, k, o) - au.ApplyDeployments() + au := utils2.NewApplyDeploymentsUtil(ctx, dew, ru, k, o) + au.ApplyDeployments(cmd.c.Deployments) - du := utils2.NewDiffUtil(dew, cmd.c.Deployments, ru, au.GetAppliedObjectsMap()) - du.Diff() + du := utils2.NewDiffUtil(dew, ru, au.GetAppliedObjectsMap()) + du.DiffDeploymentItems(cmd.c.Deployments) orphanObjects, err := FindOrphanObjects(k, ru, cmd.c) diffResult := &result.CommandResult{ @@ -86,11 +86,11 @@ func (cmd *DeployCommand) Run(ctx context.Context, k *k8s.K8sCluster, diffResult o.DryRun = k.DryRun o.AbortOnError = cmd.AbortOnError - au := utils2.NewApplyDeploymentsUtil(ctx, dew, cmd.c.Deployments, ru, k, o) - au.ApplyDeployments() + au := utils2.NewApplyDeploymentsUtil(ctx, dew, ru, k, o) + au.ApplyDeployments(cmd.c.Deployments) - du := utils2.NewDiffUtil(dew, cmd.c.Deployments, ru, au.GetAppliedObjectsMap()) - du.Diff() + du := utils2.NewDiffUtil(dew, ru, au.GetAppliedObjectsMap()) + du.DiffDeploymentItems(cmd.c.Deployments) orphanObjects, err := FindOrphanObjects(k, ru, cmd.c) if err != nil { diff --git a/pkg/deployment/commands/diff.go b/pkg/deployment/commands/diff.go index aa2f185c5..dd4a4ecd3 100644 --- a/pkg/deployment/commands/diff.go +++ b/pkg/deployment/commands/diff.go @@ -53,14 +53,14 @@ func (cmd *DiffCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*result.Com AbortOnError: false, ReadinessTimeout: 0, } - au := utils.NewApplyDeploymentsUtil(ctx, dew, cmd.c.Deployments, ru, k, o) - au.ApplyDeployments() + au := utils.NewApplyDeploymentsUtil(ctx, dew, ru, k, o) + au.ApplyDeployments(cmd.c.Deployments) - du := utils.NewDiffUtil(dew, cmd.c.Deployments, ru, au.GetAppliedObjectsMap()) + du := utils.NewDiffUtil(dew, ru, au.GetAppliedObjectsMap()) du.IgnoreTags = cmd.IgnoreTags du.IgnoreLabels = cmd.IgnoreLabels du.IgnoreAnnotations = cmd.IgnoreAnnotations - du.Diff() + du.DiffDeploymentItems(cmd.c.Deployments) orphanObjects, err := FindOrphanObjects(k, ru, cmd.c) if err != nil { diff --git a/pkg/deployment/commands/poke_images.go b/pkg/deployment/commands/poke_images.go index 14fdb9e0c..7144f2c2f 100644 --- a/pkg/deployment/commands/poke_images.go +++ b/pkg/deployment/commands/poke_images.go @@ -72,7 +72,7 @@ func (cmd *PokeImagesCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*resu return o, nil } - au := utils2.NewApplyDeploymentsUtil(ctx, dew, cmd.c.Deployments, ru, k, &utils2.ApplyUtilOptions{}) + au := utils2.NewApplyDeploymentsUtil(ctx, dew, ru, k, &utils2.ApplyUtilOptions{}) for ref, containers := range containersAndImages { ref := ref @@ -93,8 +93,8 @@ func (cmd *PokeImagesCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*resu } wg.Wait() - du := utils2.NewDiffUtil(dew, cmd.c.Deployments, ru, au.GetAppliedObjectsMap()) - du.Diff() + du := utils2.NewDiffUtil(dew, ru, au.GetAppliedObjectsMap()) + du.DiffDeploymentItems(cmd.c.Deployments) orphanObjects, err := FindOrphanObjects(k, ru, cmd.c) if err != nil { diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index d66580227..6a7847e51 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -61,11 +61,10 @@ type ApplyUtil struct { type ApplyDeploymentsUtil struct { ctx context.Context - dew *DeploymentErrorsAndWarnings - deployments []*deployment.DeploymentItem - ru *RemoteObjectUtils - k *k8s.K8sCluster - o *ApplyUtilOptions + dew *DeploymentErrorsAndWarnings + ru *RemoteObjectUtils + k *k8s.K8sCluster + o *ApplyUtilOptions abortSignal atomic.Value @@ -79,14 +78,13 @@ type ApplyDeploymentsUtil struct { results []*ApplyUtil } -func NewApplyDeploymentsUtil(ctx context.Context, dew *DeploymentErrorsAndWarnings, deployments []*deployment.DeploymentItem, ru *RemoteObjectUtils, k *k8s.K8sCluster, o *ApplyUtilOptions) *ApplyDeploymentsUtil { +func NewApplyDeploymentsUtil(ctx context.Context, dew *DeploymentErrorsAndWarnings, ru *RemoteObjectUtils, k *k8s.K8sCluster, o *ApplyUtilOptions) *ApplyDeploymentsUtil { ret := &ApplyDeploymentsUtil{ - ctx: ctx, - dew: dew, - deployments: deployments, - ru: ru, - k: k, - o: o, + ctx: ctx, + dew: dew, + ru: ru, + k: k, + o: o, } ret.abortSignal.Store(false) return ret @@ -642,7 +640,7 @@ func (a *ApplyDeploymentsUtil) buildProgressName(d *deployment.DeploymentItem) * return nil } -func (a *ApplyDeploymentsUtil) ApplyDeployments() { +func (a *ApplyDeploymentsUtil) ApplyDeployments(deployments []*deployment.DeploymentItem) { s := status.Start(a.ctx, "Running server-side apply for all objects") defer s.Failed() @@ -650,7 +648,7 @@ func (a *ApplyDeploymentsUtil) ApplyDeployments() { sem := semaphore.NewWeighted(8) maxNameLen := 0 - for _, d := range a.deployments { + for _, d := range deployments { name := a.buildProgressName(d) if name != nil { if len(*name) > maxNameLen { @@ -659,7 +657,7 @@ func (a *ApplyDeploymentsUtil) ApplyDeployments() { } } - for _, d_ := range a.deployments { + for _, d_ := range deployments { d := d_ if a.abortSignal.Load().(bool) { break diff --git a/pkg/deployment/utils/diff_utils.go b/pkg/deployment/utils/diff_utils.go index 55ca28566..8e0b00bc2 100644 --- a/pkg/deployment/utils/diff_utils.go +++ b/pkg/deployment/utils/diff_utils.go @@ -14,7 +14,6 @@ import ( type DiffUtil struct { dew *DeploymentErrorsAndWarnings - deployments []*deployment.DeploymentItem appliedObjects map[k8s2.ObjectRef]*uo.UnstructuredObject ru *RemoteObjectUtils @@ -27,46 +26,60 @@ type DiffUtil struct { mutex sync.Mutex } -func NewDiffUtil(dew *DeploymentErrorsAndWarnings, deployments []*deployment.DeploymentItem, ru *RemoteObjectUtils, appliedObjects map[k8s2.ObjectRef]*uo.UnstructuredObject) *DiffUtil { - return &DiffUtil{ +func NewDiffUtil(dew *DeploymentErrorsAndWarnings, ru *RemoteObjectUtils, appliedObjects map[k8s2.ObjectRef]*uo.UnstructuredObject) *DiffUtil { + u := &DiffUtil{ dew: dew, - deployments: deployments, ru: ru, appliedObjects: appliedObjects, } + u.calcRemoteObjectsForDiff() + return u } -func (u *DiffUtil) Diff() { +func (u *DiffUtil) DiffDeploymentItems(deployments []*deployment.DeploymentItem) { var wg sync.WaitGroup - u.calcRemoteObjectsForDiff() - - for _, d := range u.deployments { + for _, d := range deployments { ignoreForDiffs := d.Project.GetIgnoreForDiffs(u.IgnoreTags, u.IgnoreLabels, u.IgnoreAnnotations) - for _, o := range d.Objects { - o := o - ref := o.GetK8sRef() - ao, ok := u.appliedObjects[ref] - if !ok { - // if we can't even find it in appliedObjects, it probably ran into an error - continue - } - diffRef, ro := u.getRemoteObjectForDiff(o) - - wg.Add(1) - go func() { - defer wg.Done() - u.diffObject(o, diffRef, ao, ro, ignoreForDiffs) - }() - } + u.diffObjects(d.Objects, ignoreForDiffs, &wg) } wg.Wait() + u.sortChanges() +} + +func (u *DiffUtil) DiffObjects(objects []*uo.UnstructuredObject) { + var wg sync.WaitGroup + u.diffObjects(objects, nil, &wg) + wg.Wait() + u.sortChanges() +} + +func (u *DiffUtil) sortChanges() { sort.Slice(u.ChangedObjects, func(i, j int) bool { return u.ChangedObjects[i].Ref.String() < u.ChangedObjects[j].Ref.String() }) } +func (u *DiffUtil) diffObjects(objects []*uo.UnstructuredObject, ignoreForDiffs []*types.IgnoreForDiffItemConfig, wg *sync.WaitGroup) { + for _, o := range objects { + o := o + ref := o.GetK8sRef() + ao, ok := u.appliedObjects[ref] + if !ok { + // if we can't even find it in appliedObjects, it probably ran into an error + continue + } + diffRef, ro := u.getRemoteObjectForDiff(o) + + wg.Add(1) + go func() { + defer wg.Done() + u.diffObject(o, diffRef, ao, ro, ignoreForDiffs) + }() + } +} + func (u *DiffUtil) diffObject(lo *uo.UnstructuredObject, diffRef k8s2.ObjectRef, ao *uo.UnstructuredObject, ro *uo.UnstructuredObject, ignoreForDiffs []*types.IgnoreForDiffItemConfig) { if ao != nil && ro == nil { // new? diff --git a/pkg/deployment/utils/diff_utils_test.go b/pkg/deployment/utils/diff_utils_test.go index 49bd2c7b7..430d39427 100644 --- a/pkg/deployment/utils/diff_utils_test.go +++ b/pkg/deployment/utils/diff_utils_test.go @@ -126,8 +126,8 @@ func TestDiff(t *testing.T) { t.Run(test.name, func(t *testing.T) { test.dew = NewDeploymentErrorsAndWarnings() test.ru = test.newRemoteObjects(test.dew) - test.du = NewDiffUtil(test.dew, test.newDeploymentItems(), test.ru, test.appliedObjectsMap()) - test.du.Diff() + test.du = NewDiffUtil(test.dew, test.ru, test.appliedObjectsMap()) + test.du.DiffDeploymentItems(test.newDeploymentItems()) test.a(t, test) }) } From 9b850a6b6d955153a93e756385310b538e7f42f8 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 25 Apr 2023 00:02:43 +0200 Subject: [PATCH 1585/2916] refactor: Make ExistingXXXTypes public --- cmd/kluctl/args/cobra_types.go | 30 +++++++++++++++--------------- cmd/kluctl/args/images.go | 2 +- cmd/kluctl/args/project.go | 4 ++-- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/cmd/kluctl/args/cobra_types.go b/cmd/kluctl/args/cobra_types.go index c6907ea51..ade735c6e 100644 --- a/cmd/kluctl/args/cobra_types.go +++ b/cmd/kluctl/args/cobra_types.go @@ -5,27 +5,27 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils" ) -type existingPathType string +type ExistingPathType string -func (s *existingPathType) Set(val string) error { +func (s *ExistingPathType) Set(val string) error { if val != "-" { val = utils.ExpandPath(val) } if !utils.Exists(val) { return fmt.Errorf("%s does not exist", val) } - *s = existingPathType(val) + *s = ExistingPathType(val) return nil } -func (s *existingPathType) Type() string { +func (s *ExistingPathType) Type() string { return "existingpath" } -func (s *existingPathType) String() string { return string(*s) } +func (s *ExistingPathType) String() string { return string(*s) } -type existingFileType string +type ExistingFileType string -func (s *existingFileType) Set(val string) error { +func (s *ExistingFileType) Set(val string) error { if val != "-" { val = utils.ExpandPath(val) } @@ -35,18 +35,18 @@ func (s *existingFileType) Set(val string) error { if utils.IsDirectory(val) { return fmt.Errorf("%s exists but is a directory", val) } - *s = existingFileType(val) + *s = ExistingFileType(val) return nil } -func (s *existingFileType) Type() string { +func (s *ExistingFileType) Type() string { return "existingfile" } -func (s *existingFileType) String() string { return string(*s) } +func (s *ExistingFileType) String() string { return string(*s) } -type existingDirType string +type ExistingDirType string -func (s *existingDirType) Set(val string) error { +func (s *ExistingDirType) Set(val string) error { if val != "-" { val = utils.ExpandPath(val) } @@ -56,11 +56,11 @@ func (s *existingDirType) Set(val string) error { if !utils.IsDirectory(val) { return fmt.Errorf("%s exists but is not a directory", val) } - *s = existingDirType(val) + *s = ExistingDirType(val) return nil } -func (s *existingDirType) Type() string { +func (s *ExistingDirType) Type() string { return "existingdir" } -func (s *existingDirType) String() string { return string(*s) } +func (s *ExistingDirType) String() string { return string(*s) } diff --git a/cmd/kluctl/args/images.go b/cmd/kluctl/args/images.go index 1c6fe4b79..6c19f0f8d 100644 --- a/cmd/kluctl/args/images.go +++ b/cmd/kluctl/args/images.go @@ -9,7 +9,7 @@ import ( type ImageFlags struct { FixedImage []string `group:"images" short:"F" help:"Pin an image to a given version. Expects '--fixed-image=image<:namespace:deployment:container>=result'"` - FixedImagesFile existingFileType `group:"images" help:"Use .yaml file to pin image versions. See output of list-images sub-command or read the documentation for details about the output format" exts:"yml,yaml"` + FixedImagesFile ExistingFileType `group:"images" help:"Use .yaml file to pin image versions. See output of list-images sub-command or read the documentation for details about the output format" exts:"yml,yaml"` } func (args *ImageFlags) LoadFixedImagesFromArgs() ([]types.FixedImage, error) { diff --git a/cmd/kluctl/args/project.go b/cmd/kluctl/args/project.go index 272b51723..e3ed5ab0e 100644 --- a/cmd/kluctl/args/project.go +++ b/cmd/kluctl/args/project.go @@ -6,7 +6,7 @@ import ( ) type ProjectDir struct { - ProjectDir existingDirType `group:"project" help:"Specify the project directory. Defaults to the current working directory."` + ProjectDir ExistingDirType `group:"project" help:"Specify the project directory. Defaults to the current working directory."` } func (a ProjectDir) GetProjectDir() (string, error) { @@ -23,7 +23,7 @@ func (a ProjectDir) GetProjectDir() (string, error) { type ProjectFlags struct { ProjectDir - ProjectConfig existingFileType `group:"project" short:"c" help:"Location of the .kluctl.yaml config file. Defaults to $PROJECT/.kluctl.yaml" exts:"yml,yaml"` + ProjectConfig ExistingFileType `group:"project" short:"c" help:"Location of the .kluctl.yaml config file. Defaults to $PROJECT/.kluctl.yaml" exts:"yml,yaml"` Timeout time.Duration `group:"project" help:"Specify timeout for all operations, including loading of the project, all external api calls and waiting for readiness." default:"10m"` GitCacheUpdateInterval time.Duration `group:"project" help:"Specify the time to wait between git cache updates. Defaults to not wait at all and always updating caches."` From f47553fe6bf7e7f06ca29f2f525437566e1e3d8a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 25 Apr 2023 01:25:07 +0200 Subject: [PATCH 1586/2916] feat: Implement drift detection via validate command --- cmd/kluctl/commands/cmd_validate.go | 114 ++++++++++------ cmd/kluctl/commands/command_result.go | 17 +++ e2e/utils.go | 8 +- e2e/validate_test.go | 183 ++++++++++++++++++++++++++ pkg/deployment/commands/validate.go | 88 +++++++++---- pkg/deployment/utils/diff_utils.go | 11 +- pkg/types/result/command_result.go | 6 +- pkg/yaml/yaml.go | 8 ++ 8 files changed, 369 insertions(+), 66 deletions(-) create mode 100644 e2e/validate_test.go diff --git a/cmd/kluctl/commands/cmd_validate.go b/cmd/kluctl/commands/cmd_validate.go index 532f52e28..03471c507 100644 --- a/cmd/kluctl/commands/cmd_validate.go +++ b/cmd/kluctl/commands/cmd_validate.go @@ -5,6 +5,9 @@ import ( "fmt" "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/deployment/commands" + k8s2 "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/types/result" + "github.com/kluctl/kluctl/v2/pkg/yaml" "time" ) @@ -17,6 +20,8 @@ type validateCmd struct { args.OutputFlags args.RenderOutputDirFlags + CommandResult args.ExistingFileType `group:"misc" help:"Specify a command result to use instead of loading a project. This will also perform drift detection."` + Wait time.Duration `group:"misc" help:"Wait for the given amount of time until the deployment validates"` Sleep time.Duration `group:"misc" help:"Sleep duration between validation attempts" default:"5s"` WarningsAsErrors bool `group:"misc" help:"Consider warnings as failures"` @@ -37,43 +42,76 @@ func (cmd *validateCmd) Run(ctx context.Context) error { helmCredentials: cmd.HelmCredentials, renderOutputDirFlags: cmd.RenderOutputDirFlags, } - return withProjectCommandContext(ctx, ptArgs, func(cmdCtx *commandCtx) error { - startTime := time.Now() - cmd2 := commands.NewValidateCommand(cmdCtx.ctx, cmdCtx.targetCtx.Target.Discriminator, cmdCtx.targetCtx.DeploymentCollection) - for true { - result, err := cmd2.Run(cmdCtx.ctx, cmdCtx.targetCtx.SharedContext.K) - if err != nil { - return err - } - failed := len(result.Errors) != 0 || (cmd.WarningsAsErrors && len(result.Warnings) != 0) - - err = outputValidateResult(ctx, cmd.Output, result) - if err != nil { - return err - } - - if !failed { - _, _ = getStderr(ctx).WriteString("Validation succeeded\n") - return nil - } - - if cmd.Wait <= 0 || time.Now().Sub(startTime) > cmd.Wait { - return fmt.Errorf("Validation failed") - } - - time.Sleep(cmd.Sleep) - - // Need to force re-requesting these objects - for _, e := range result.Results { - cmd2.ForgetRemoteObject(e.Ref) - } - for _, e := range result.Warnings { - cmd2.ForgetRemoteObject(e.Ref) - } - for _, e := range result.Errors { - cmd2.ForgetRemoteObject(e.Ref) - } + + if cmd.CommandResult.String() != "" { + var ccr result.CompactedCommandResult + err := yaml.ReadYamlFile(cmd.CommandResult.String(), &ccr) + if err != nil { + return err + } + commandResult := ccr.ToNonCompacted() + + var k8sContext *string + if cmd.Context != "" { + k8sContext = &cmd.Context + } else if commandResult.Command.Target != nil { + k8sContext = commandResult.Command.Target.Context + } + clientFactory, err := k8s2.NewClientFactoryFromDefaultConfig(ctx, k8sContext) + if err != nil { + return err + } + k, err := k8s2.NewK8sCluster(ctx, clientFactory, true) + if err != nil { + return err + } + + cmd2 := commands.NewValidateCommand(ctx, "", nil, commandResult) + return cmd.doValidate(ctx, k, cmd2) + + } else { + return withProjectCommandContext(ctx, ptArgs, func(cmdCtx *commandCtx) error { + cmd2 := commands.NewValidateCommand(cmdCtx.ctx, cmdCtx.targetCtx.Target.Discriminator, cmdCtx.targetCtx.DeploymentCollection, nil) + return cmd.doValidate(cmdCtx.ctx, cmdCtx.targetCtx.SharedContext.K, cmd2) + }) + } +} + +func (cmd *validateCmd) doValidate(ctx context.Context, k *k8s2.K8sCluster, cmd2 *commands.ValidateCommand) error { + startTime := time.Now() + for true { + result, err := cmd2.Run(ctx, k) + if err != nil { + return err } - return nil - }) + failed := len(result.Errors) != 0 || (cmd.WarningsAsErrors && len(result.Warnings) != 0) + + err = outputValidateResult(ctx, cmd.Output, result) + if err != nil { + return err + } + + if !failed { + _, _ = getStderr(ctx).WriteString("Validation succeeded\n") + return nil + } + + if cmd.Wait <= 0 || time.Now().Sub(startTime) > cmd.Wait { + return fmt.Errorf("Validation failed") + } + + time.Sleep(cmd.Sleep) + + // Need to force re-requesting these objects + for _, e := range result.Results { + cmd2.ForgetRemoteObject(e.Ref) + } + for _, e := range result.Warnings { + cmd2.ForgetRemoteObject(e.Ref) + } + for _, e := range result.Errors { + cmd2.ForgetRemoteObject(e.Ref) + } + } + return nil } diff --git a/cmd/kluctl/commands/command_result.go b/cmd/kluctl/commands/command_result.go index 482ff068e..cedfb9319 100644 --- a/cmd/kluctl/commands/command_result.go +++ b/cmd/kluctl/commands/command_result.go @@ -174,6 +174,23 @@ func formatValidateResultText(vr *result.ValidateResult) string { buf.WriteString("Results:\n") prettyValidationResults(buf, vr.Results) } + + if len(vr.Drift) != 0 { + if buf.Len() != 0 { + buf.WriteString("\n") + } + buf.WriteString("Drift:\n") + for i, o := range vr.Drift { + if len(o.Changes) == 0 { + continue + } + if i != 0 { + buf.WriteString("\n") + } + prettyChanges(buf, o.Ref, o.Changes) + } + } + return buf.String() } diff --git a/e2e/utils.go b/e2e/utils.go index 270c273fa..9909bc07a 100644 --- a/e2e/utils.go +++ b/e2e/utils.go @@ -25,14 +25,18 @@ func createNamespace(t *testing.T, k *test_utils.EnvTestCluster, namespace strin } } -func assertConfigMapExists(t *testing.T, k *test_utils.EnvTestCluster, namespace string, name string) *uo.UnstructuredObject { - x, err := k.Get(v1.SchemeGroupVersion.WithResource("configmaps"), namespace, name) +func assertObjectExists(t *testing.T, k *test_utils.EnvTestCluster, gvr schema.GroupVersionResource, namespace string, name string) *uo.UnstructuredObject { + x, err := k.Get(gvr, namespace, name) if err != nil { t.Fatalf("unexpected error '%v' while getting ConfigMap %s/%s", err, namespace, name) } return x } +func assertConfigMapExists(t *testing.T, k *test_utils.EnvTestCluster, namespace string, name string) *uo.UnstructuredObject { + return assertObjectExists(t, k, v1.SchemeGroupVersion.WithResource("configmaps"), namespace, name) +} + func assertConfigMapNotExists(t *testing.T, k *test_utils.EnvTestCluster, namespace string, name string) { _, err := k.Get(v1.SchemeGroupVersion.WithResource("configmaps"), namespace, name) if err == nil { diff --git a/e2e/validate_test.go b/e2e/validate_test.go new file mode 100644 index 000000000..9fdb6cf5c --- /dev/null +++ b/e2e/validate_test.go @@ -0,0 +1,183 @@ +package e2e + +import ( + "context" + "fmt" + test_utils "github.com/kluctl/kluctl/v2/e2e/test-utils" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/yaml" + "github.com/stretchr/testify/assert" + appsv1 "k8s.io/api/apps/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "path/filepath" + "testing" +) + +func buildDeployment(name string, namespace string, ready bool) *uo.UnstructuredObject { + deployment := uo.FromStringMust(fmt.Sprintf(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: %s + namespace: %s + labels: + app: nginx +spec: + replicas: 1 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.14.2 + ports: + - containerPort: 80 +`, name, namespace)) + if ready { + deployment.Merge(uo.FromStringMust(` +status: + availableReplicas: 1 + conditions: + - lastTransitionTime: "2023-03-29T19:23:12Z" + lastUpdateTime: "2023-03-29T19:23:12Z" + message: Deployment has minimum availability. + reason: MinimumReplicasAvailable + status: "True" + type: Available + - lastTransitionTime: "2023-03-29T19:22:30Z" + lastUpdateTime: "2023-03-29T19:23:12Z" + message: ReplicaSet "argocd-redis-8f7689686" has successfully progressed. + reason: NewReplicaSetAvailable + status: "True" + type: Progressing + observedGeneration: 1 + readyReplicas: 1 + replicas: 1 +`)) + } + return deployment +} + +func prepareValidateTest(t *testing.T, k *test_utils.EnvTestCluster) *test_utils.TestProject { + p := test_utils.NewTestProject(t) + + createNamespace(t, k, p.TestSlug()) + + p.UpdateTarget("test", nil) + + p.AddKustomizeDeployment("d1", []test_utils.KustomizeResource{ + {fmt.Sprintf("configmap-%s.yml", "d1"), "", buildDeployment("d1", p.TestSlug(), false)}, + }, nil) + + return p +} + +func assertValidate(t *testing.T, p *test_utils.TestProject, commandResult *string, succeed bool) (string, string) { + args := []string{"validate"} + if commandResult != nil { + args = append(args, "--command-result", *commandResult) + } else { + args = append(args, "-t", "test") + } + stdout, stderr, err := p.Kluctl(args...) + + if succeed { + assert.NoError(t, err) + assert.NotContains(t, stdout, fmt.Sprintf("%s/Deployment/d1: readyReplicas field not in status or empty", p.TestSlug())) + assert.NotContains(t, stderr, "Validation failed") + } else { + assert.Error(t, err) + assert.Contains(t, stdout, fmt.Sprintf("%s/Deployment/d1: readyReplicas field not in status or empty", p.TestSlug())) + assert.Contains(t, stderr, "Validation failed") + } + + return stdout, stderr +} + +func TestValidate(t *testing.T) { + t.Parallel() + + k := defaultCluster1 + + p := prepareValidateTest(t, k) + + p.KluctlMust("deploy", "--yes", "-t", "test") + assertObjectExists(t, k, appsv1.SchemeGroupVersion.WithResource("deployments"), p.TestSlug(), "d1") + + assertValidate(t, p, nil, false) + + readyDeployment := buildDeployment("d1", p.TestSlug(), true) + + _, err := k.DynamicClient.Resource(appsv1.SchemeGroupVersion.WithResource("deployments")).Namespace(p.TestSlug()). + Patch(context.Background(), "d1", types.ApplyPatchType, []byte(yaml.WriteJsonStringMust(readyDeployment)), metav1.PatchOptions{ + FieldManager: "test", + }, "status") + assert.NoError(t, err) + + assertValidate(t, p, nil, true) +} + +func TestValidateCommandResult(t *testing.T) { + t.Parallel() + + k := defaultCluster1 + + p := prepareValidateTest(t, k) + + resultPath := filepath.Join(t.TempDir(), "result.yaml") + + p.KluctlMust("deploy", "--yes", "-t", "test", "-oyaml="+resultPath) + assertObjectExists(t, k, appsv1.SchemeGroupVersion.WithResource("deployments"), p.TestSlug(), "d1") + + assertValidate(t, p, &resultPath, false) + + readyDeployment := buildDeployment("d1", p.TestSlug(), true) + + _, err := k.DynamicClient.Resource(appsv1.SchemeGroupVersion.WithResource("deployments")).Namespace(p.TestSlug()). + Patch(context.Background(), "d1", types.ApplyPatchType, []byte(yaml.WriteJsonStringMust(readyDeployment)), metav1.PatchOptions{ + FieldManager: "test", + }, "status") + assert.NoError(t, err) + + assertValidate(t, p, &resultPath, true) +} + +func TestValidateDrift(t *testing.T) { + t.Parallel() + + k := defaultCluster1 + + p := prepareValidateTest(t, k) + + resultPath := filepath.Join(t.TempDir(), "result.yaml") + + p.KluctlMust("deploy", "--yes", "-t", "test", "-oyaml="+resultPath) + assertObjectExists(t, k, appsv1.SchemeGroupVersion.WithResource("deployments"), p.TestSlug(), "d1") + + stdout, _ := assertValidate(t, p, &resultPath, false) + assert.NotContains(t, stdout, "Drift:") + + changedDeployment := buildDeployment("d1", p.TestSlug(), false) + changedDeployment.Merge(uo.FromStringMust(` +spec: + replicas: 2 +`)) + + b := true + _, err := k.DynamicClient.Resource(appsv1.SchemeGroupVersion.WithResource("deployments")).Namespace(p.TestSlug()). + Patch(context.Background(), "d1", types.ApplyPatchType, []byte(yaml.WriteJsonStringMust(changedDeployment)), metav1.PatchOptions{ + FieldManager: "test", + Force: &b, + }) + assert.NoError(t, err) + + stdout, _ = assertValidate(t, p, &resultPath, false) + assert.Contains(t, stdout, "Drift:") + assert.Contains(t, stdout, "spec.replicas") +} diff --git a/pkg/deployment/commands/validate.go b/pkg/deployment/commands/validate.go index 64223b2fe..e3000a09d 100644 --- a/pkg/deployment/commands/validate.go +++ b/pkg/deployment/commands/validate.go @@ -2,26 +2,30 @@ package commands import ( "context" + "fmt" "github.com/google/uuid" "github.com/kluctl/kluctl/v2/pkg/deployment" utils2 "github.com/kluctl/kluctl/v2/pkg/deployment/utils" "github.com/kluctl/kluctl/v2/pkg/k8s" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/types/result" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/validation" ) type ValidateCommand struct { c *deployment.DeploymentCollection + r *result.CommandResult discriminator string dew *utils2.DeploymentErrorsAndWarnings ru *utils2.RemoteObjectUtils } -func NewValidateCommand(ctx context.Context, discriminator string, c *deployment.DeploymentCollection) *ValidateCommand { +func NewValidateCommand(ctx context.Context, discriminator string, c *deployment.DeploymentCollection, r *result.CommandResult) *ValidateCommand { cmd := &ValidateCommand{ c: c, + r: r, discriminator: discriminator, dew: utils2.NewDeploymentErrorsAndWarnings(), } @@ -37,41 +41,81 @@ func (cmd *ValidateCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*result cmd.dew.Init() - err := cmd.ru.UpdateRemoteObjects(k, &cmd.discriminator, cmd.c.LocalObjectRefs(), true) + var refs []k8s2.ObjectRef + var renderedObjects []*uo.UnstructuredObject + appliedObjects := map[k8s2.ObjectRef]*uo.UnstructuredObject{} + var discriminator string + + if cmd.c != nil && cmd.r != nil { + return nil, fmt.Errorf("passing both deployment collection and command result is not allowed") + } else if cmd.c != nil { + for _, d := range cmd.c.Deployments { + for _, o := range d.Objects { + ref := o.GetK8sRef() + refs = append(refs, ref) + renderedObjects = append(renderedObjects, o) + } + } + } else if cmd.r != nil { + for _, o := range cmd.r.Objects { + refs = append(refs, o.Ref) + if o.Rendered != nil { + renderedObjects = append(renderedObjects, o.Rendered) + } + if o.Applied != nil { + appliedObjects[o.Ref] = o.Applied + } + } + discriminator = cmd.r.Target.Discriminator + } else { + return nil, fmt.Errorf("either deployment collection or command result must be passed") + } + + if discriminator == "" { + discriminator = cmd.discriminator + } + + err := cmd.ru.UpdateRemoteObjects(k, &cmd.discriminator, refs, true) if err != nil { return nil, err } - ad := utils2.NewApplyDeploymentsUtil(ctx, cmd.dew, cmd.c.Deployments, cmd.ru, k, &utils2.ApplyUtilOptions{}) - for _, d := range cmd.c.Deployments { + ad := utils2.NewApplyDeploymentsUtil(ctx, cmd.dew, cmd.ru, k, &utils2.ApplyUtilOptions{}) + for _, o := range renderedObjects { au := ad.NewApplyUtil(ctx, nil) h := utils2.NewHooksUtil(au) - for _, o := range d.Objects { - hook := h.GetHook(o) - if hook != nil && !hook.IsPersistent() { - continue - } + hook := h.GetHook(o) + if hook != nil && !hook.IsPersistent() { + continue + } - ref := o.GetK8sRef() + ref := o.GetK8sRef() - remoteObject := cmd.ru.GetRemoteObject(ref) - if remoteObject == nil { - ret.Errors = append(ret.Errors, result.DeploymentError{Ref: ref, Message: "object not found"}) - continue - } - r := validation.ValidateObject(k, remoteObject, true, false) - if !r.Ready { - ret.Ready = false - } - ret.Errors = append(ret.Errors, r.Errors...) - ret.Warnings = append(ret.Warnings, r.Warnings...) - ret.Results = append(ret.Results, r.Results...) + remoteObject := cmd.ru.GetRemoteObject(ref) + if remoteObject == nil { + ret.Errors = append(ret.Errors, result.DeploymentError{Ref: ref, Message: "object not found"}) + continue } + r := validation.ValidateObject(k, remoteObject, true, false) + if !r.Ready { + ret.Ready = false + } + ret.Errors = append(ret.Errors, r.Errors...) + ret.Warnings = append(ret.Warnings, r.Warnings...) + ret.Results = append(ret.Results, r.Results...) } + du := utils2.NewDiffUtil(cmd.dew, cmd.ru, appliedObjects) + du.Swapped = true + du.DiffObjects(renderedObjects) + ret.Warnings = append(ret.Warnings, cmd.dew.GetWarningsList()...) ret.Errors = append(ret.Errors, cmd.dew.GetErrorsList()...) + if len(du.ChangedObjects) != 0 { + ret.Drift = du.ChangedObjects + } + return &ret, nil } diff --git a/pkg/deployment/utils/diff_utils.go b/pkg/deployment/utils/diff_utils.go index 8e0b00bc2..57c37b981 100644 --- a/pkg/deployment/utils/diff_utils.go +++ b/pkg/deployment/utils/diff_utils.go @@ -20,9 +20,10 @@ type DiffUtil struct { IgnoreTags bool IgnoreLabels bool IgnoreAnnotations bool + Swapped bool remoteDiffObjects map[k8s2.ObjectRef]*uo.UnstructuredObject - ChangedObjects []*result.ChangedObject + ChangedObjects []result.ChangedObject mutex sync.Mutex } @@ -75,7 +76,11 @@ func (u *DiffUtil) diffObjects(objects []*uo.UnstructuredObject, ignoreForDiffs wg.Add(1) go func() { defer wg.Done() - u.diffObject(o, diffRef, ao, ro, ignoreForDiffs) + if u.Swapped { + u.diffObject(o, diffRef, ro, ao, ignoreForDiffs) + } else { + u.diffObject(o, diffRef, ao, ro, ignoreForDiffs) + } }() } } @@ -112,7 +117,7 @@ func (u *DiffUtil) diffObject(lo *uo.UnstructuredObject, diffRef k8s2.ObjectRef, u.mutex.Lock() defer u.mutex.Unlock() - u.ChangedObjects = append(u.ChangedObjects, &result.ChangedObject{ + u.ChangedObjects = append(u.ChangedObjects, result.ChangedObject{ Ref: diffRef, Changes: changes, }) diff --git a/pkg/types/result/command_result.go b/pkg/types/result/command_result.go index c7047fa63..59dbddf81 100644 --- a/pkg/types/result/command_result.go +++ b/pkg/types/result/command_result.go @@ -141,7 +141,9 @@ type CompactedCommandResult struct { func (ccr *CompactedCommandResult) ToNonCompacted() *CommandResult { ret := ccr.CommandResult - ret.Objects = ccr.CompactedObjects + if ccr.CompactedObjects != nil { + ret.Objects = ccr.CompactedObjects + } return &ret } @@ -157,4 +159,6 @@ type ValidateResult struct { Warnings []DeploymentError `json:"warnings,omitempty"` Errors []DeploymentError `json:"errors,omitempty"` Results []ValidateResultEntry `json:"results,omitempty"` + + Drift []ChangedObject `json:"drift,omitempty"` } diff --git a/pkg/yaml/yaml.go b/pkg/yaml/yaml.go index b28c789eb..029c968bb 100644 --- a/pkg/yaml/yaml.go +++ b/pkg/yaml/yaml.go @@ -193,6 +193,14 @@ func WriteJsonString(o interface{}) (string, error) { return string(b), nil } +func WriteJsonStringMust(o interface{}) string { + b, err := json.Marshal(o) + if err != nil { + panic(err) + } + return string(b) +} + // RemoveDuplicateFields is a helper/hack to remove duplicate fields from yaml maps/structs. The yaml spec explicitly // forbids duplicate keys, but yaml.v2 ignored those by default, leading to some tools (e.g. Helm) ignoring these func RemoveDuplicateFields(r io.Reader) ([]byte, error) { From bc4b8df7398b6b5e165ff5d09b3972ec830bb8e6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 25 Apr 2023 01:25:37 +0200 Subject: [PATCH 1587/2916] fix: Normalize float64 values to be interpreted as ints if possible --- pkg/diff/normalize.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pkg/diff/normalize.go b/pkg/diff/normalize.go index f32f7a2d7..08555cef5 100644 --- a/pkg/diff/normalize.go +++ b/pkg/diff/normalize.go @@ -117,6 +117,18 @@ func normalizeMisc(o *uo.UnstructuredObject) { _ = o.RemoveNestedField("status") } +func normalizeFloats(o *uo.UnstructuredObject) { + _ = o.NewIterator().IterateLeafs(func(it *uo.ObjectIterator) error { + if f, ok := it.Value().(float64); ok { + i := int64(f) + if f == float64(i) { + _ = it.SetValue(i) + } + } + return nil + }) +} + var ignoreDiffFieldAnnotationRegex = regexp.MustCompile(`^kluctl.io/ignore-diff-field(-\d*)?$`) var ignoreDiffFieldRegexAnnotationRegex = regexp.MustCompile(`^kluctl.io/ignore-diff-field-regex(-\d*)?$`) @@ -127,6 +139,7 @@ func NormalizeObject(o_ *uo.UnstructuredObject, ignoreForDiffs []*types.IgnoreFo ns := o_.GetK8sNamespace() o := o_.Clone() + normalizeFloats(o) normalizeMetadata(o) normalizeMisc(o) From edcee7cb22c97a7fbf6d0db6260960b068fcace0 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 25 Apr 2023 13:48:07 +0200 Subject: [PATCH 1588/2916] fix: Make target name optional via json tags and verify it later This allows to represent no-target deployments in command results. --- pkg/kluctl_project/targets.go | 7 ++++++- pkg/types/kluctl_project.go | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/pkg/kluctl_project/targets.go b/pkg/kluctl_project/targets.go index a9a8b714d..9ef106813 100644 --- a/pkg/kluctl_project/targets.go +++ b/pkg/kluctl_project/targets.go @@ -14,7 +14,12 @@ func (c *LoadedKluctlProject) loadTargets() error { targetNames := make(map[string]bool) c.Targets = nil - for _, configTarget := range c.Config.Targets { + for i, configTarget := range c.Config.Targets { + if configTarget.Name == "" { + status.Error(c.ctx, "Target at index %d has no name", i) + continue + } + target, err := c.buildTarget(configTarget) if err != nil { status.Warning(c.ctx, "Failed to load target config for project: %v", err) diff --git a/pkg/types/kluctl_project.go b/pkg/types/kluctl_project.go index 063a41398..cde09e90c 100644 --- a/pkg/types/kluctl_project.go +++ b/pkg/types/kluctl_project.go @@ -11,7 +11,7 @@ type SealingConfig struct { } type Target struct { - Name string `json:"name" validate:"required"` + Name string `json:"name,omitempty"` Context *string `json:"context,omitempty"` Args *uo.UnstructuredObject `json:"args,omitempty"` SealingConfig *SealingConfig `json:"sealingConfig,omitempty"` From 7a6b68e2c96b785facd2b50ef9b666b0cd1def03 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 12 Apr 2023 08:41:44 +0200 Subject: [PATCH 1589/2916] feat: Implement TargetSummary and use it inside ProjectSummary --- cmd/kluctl/commands/utils.go | 14 ++++++- pkg/types/result/command_result.go | 30 +++++++++++++++ pkg/types/result/summary.go | 60 +++++++++++++++++++++++++++--- 3 files changed, 98 insertions(+), 6 deletions(-) diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 0b783d808..23375c1e8 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -206,7 +206,11 @@ func withProjectTargetCommandContext(ctx context.Context, args projectTargetComm var resultStore results.ResultStore if args.commandResultFlags != nil && args.commandResultFlags.WriteCommandResult { - resultStore, err = results.NewResultStoreSecrets(targetCtx.SharedContext.K, args.commandResultFlags.CommandResultNamespace) + rc, err := targetCtx.SharedContext.K.ToRESTConfig() + if err != nil { + return err + } + resultStore, err = results.NewResultStoreSecrets(ctx, rc, args.commandResultFlags.CommandResultNamespace) if err != nil { return err } @@ -276,6 +280,14 @@ func addCommandInfo(r *result.CommandResult, startTime time.Time, command string return err } + if ctx.targetCtx.Target != nil { + r.Target.TargetName = ctx.targetCtx.Target.Name + r.Target.Discriminator = ctx.targetCtx.Target.Discriminator + } + if r.ClusterInfo != nil { + r.Target.ClusterId = r.ClusterInfo.ClusterId + } + return nil } diff --git a/pkg/types/result/command_result.go b/pkg/types/result/command_result.go index 59dbddf81..b84b552f4 100644 --- a/pkg/types/result/command_result.go +++ b/pkg/types/result/command_result.go @@ -44,6 +44,35 @@ type ProjectKey struct { SubDir string `json:"subDir,omitempty"` } +func (k ProjectKey) Less(o ProjectKey) bool { + if k.NormalizedGitUrl != o.NormalizedGitUrl { + return k.NormalizedGitUrl < o.NormalizedGitUrl + } + if k.SubDir != o.SubDir { + return k.SubDir < o.SubDir + } + return false +} + +type TargetKey struct { + TargetName string `json:"targetName,omitempty"` + ClusterId string `json:"clusterId"` + Discriminator string `json:"discriminator,omitempty"` +} + +func (k TargetKey) Less(o TargetKey) bool { + if k.TargetName != o.TargetName { + return k.TargetName < o.TargetName + } + if k.ClusterId != o.ClusterId { + return k.ClusterId < o.ClusterId + } + if k.Discriminator != o.Discriminator { + return k.Discriminator < o.Discriminator + } + return false +} + type CommandInfo struct { Initiator CommandInitiator `json:"initiator" validate:"oneof=CommandLine KluctlDeployment"` StartTime types.JsonTime `json:"startTime"` @@ -100,6 +129,7 @@ type ResultObject struct { type CommandResult struct { Id string `json:"id"` Project ProjectKey `json:"project"` + Target TargetKey `json:"target"` Command CommandInfo `json:"command,omitempty"` GitInfo *GitInfo `json:"gitInfo,omitempty"` ClusterInfo *ClusterInfo `json:"clusterInfo,omitempty"` diff --git a/pkg/types/result/summary.go b/pkg/types/result/summary.go index 3dfa79a75..fbc4c25b2 100644 --- a/pkg/types/result/summary.go +++ b/pkg/types/result/summary.go @@ -1,8 +1,13 @@ package result +import ( + "sort" +) + type CommandResultSummary struct { Id string `json:"id"` Project ProjectKey `json:"project"` + Target TargetKey `json:"target"` Command CommandInfo `json:"commandInfo"` GitInfo *GitInfo `json:"gitInfo,omitempty"` ClusterInfo *ClusterInfo `json:"clusterInfo,omitempty"` @@ -22,14 +27,17 @@ type CommandResultSummary struct { TotalChanges int `json:"totalChanges"` } +type TargetSummary struct { + Target TargetKey `json:"target"` + + LastValidateResult *ValidateResult `json:"lastValidateResult,omitempty"` + CommandResults []CommandResultSummary `json:"commandResults,omitempty"` +} + type ProjectSummary struct { Project ProjectKey `json:"project"` - LastValidateResult *ValidateResult `json:"lastValidateResult,omitempty"` - - LastDeployCommand *CommandResultSummary `json:"lastDeployCommand,omitempty"` - LastDeleteCommand *CommandResultSummary `json:"LastDeleteCommand,omitempty"` - LastPruneCommand *CommandResultSummary `json:"lastPruneCommand,omitempty"` + Targets []*TargetSummary `json:"targets"` } func (cr *CommandResult) BuildSummary() *CommandResultSummary { @@ -46,6 +54,7 @@ func (cr *CommandResult) BuildSummary() *CommandResultSummary { ret := &CommandResultSummary{ Id: cr.Id, Project: cr.Project, + Target: cr.Target, Command: cr.Command, GitInfo: cr.GitInfo, ClusterInfo: cr.ClusterInfo, @@ -65,3 +74,44 @@ func (cr *CommandResult) BuildSummary() *CommandResultSummary { } return ret } + +func BuildProjectSummaries(summaries []CommandResultSummary) []*ProjectSummary { + m := map[ProjectKey]*ProjectSummary{} + for _, rs := range summaries { + p, ok := m[rs.Project] + if !ok { + p = &ProjectSummary{Project: rs.Project} + m[rs.Project] = p + } + + var target *TargetSummary + for i, t := range p.Targets { + if t.Target == rs.Target { + target = p.Targets[i] + break + } + } + if target == nil { + target = &TargetSummary{ + Target: rs.Target, + } + p.Targets = append(p.Targets, target) + } + + target.CommandResults = append(target.CommandResults, rs) + } + + ret := make([]*ProjectSummary, 0, len(m)) + for _, p := range m { + sort.Slice(p.Targets, func(i, j int) bool { + return p.Targets[i].Target.Less(p.Targets[j].Target) + }) + ret = append(ret, p) + } + + sort.Slice(ret, func(i, j int) bool { + return ret[i].Project.Less(ret[j].Project) + }) + + return ret +} From e2de36f7b0591e2ae89e8fce9d7d5e77bd4e1797 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 2 May 2023 21:40:18 +0200 Subject: [PATCH 1590/2916] feat: Implement ResultCollector --- cmd/kluctl/commands/command_result.go | 2 +- pkg/results/result-store-secrets.go | 346 +++++++++++++++++++------- pkg/results/result-store.go | 59 ++--- pkg/results/results-collector.go | 174 +++++++++++++ 4 files changed, 454 insertions(+), 127 deletions(-) create mode 100644 pkg/results/results-collector.go diff --git a/cmd/kluctl/commands/command_result.go b/cmd/kluctl/commands/command_result.go index cedfb9319..25e4a9e5e 100644 --- a/cmd/kluctl/commands/command_result.go +++ b/cmd/kluctl/commands/command_result.go @@ -252,7 +252,7 @@ func outputCommandResult(ctx *commandCtx, flags args.OutputFormatFlags, cr *resu if writeToResultStore && ctx.resultStore != nil { s := status.Start(ctx.ctx, "Writing command result") defer s.Failed() - resultStoreErr = ctx.resultStore.WriteCommandResult(ctx.ctx, cr) + resultStoreErr = ctx.resultStore.WriteCommandResult(cr) if resultStoreErr != nil { s.FailedWithMessage("Failed to write result to result store: %s", resultStoreErr.Error()) } else { diff --git a/pkg/results/result-store-secrets.go b/pkg/results/result-store-secrets.go index bc4782a1c..4eb5f9ead 100644 --- a/pkg/results/result-store-secrets.go +++ b/pkg/results/result-store-secrets.go @@ -3,53 +3,92 @@ package results import ( "compress/gzip" "context" - "encoding/base64" "fmt" - "github.com/kluctl/kluctl/v2/pkg/k8s" - k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/types/result" "github.com/kluctl/kluctl/v2/pkg/utils" - "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/rest" + toolscache "k8s.io/client-go/tools/cache" "path" "regexp" + "sigs.k8s.io/controller-runtime/pkg/cache" + "sigs.k8s.io/controller-runtime/pkg/client" "sort" "strings" + "sync" ) type ResultStoreSecrets struct { - k *k8s.K8sCluster + ctx context.Context + + config *rest.Config writeNamespace string + + mutex sync.Mutex + client client.Client + cache cache.Cache + informer cache.Informer + cancelCache context.CancelFunc } -func NewResultStoreSecrets(k *k8s.K8sCluster, writeNamespace string) (*ResultStoreSecrets, error) { +func NewResultStoreSecrets(ctx context.Context, config *rest.Config, writeNamespace string) (*ResultStoreSecrets, error) { s := &ResultStoreSecrets{ - k: k, + ctx: ctx, + config: config, writeNamespace: writeNamespace, } + return s, nil +} - if s.writeNamespace != "" { - _, _, err := k.GetSingleObject(k8s2.NewObjectRef("", "v1", "Namespace", s.writeNamespace, "")) - if err != nil && errors.IsNotFound(err) { - ns := uo.FromMap(map[string]interface{}{ - "apiVersion": "v1", - "kind": "Namespace", - "metadata": map[string]any{ - "name": s.writeNamespace, - }, - }) - _, _, err = k.ReadWrite().PatchObject(ns, k8s.PatchOptions{}) - if err != nil { - return nil, err - } - } else if err != nil { - return nil, err - } +func (s *ResultStoreSecrets) ensureClientAndCache() error { + s.mutex.Lock() + defer s.mutex.Unlock() + + if s.client != nil { + return nil } - return s, nil + c, err := client.New(s.config, client.Options{}) + if err != nil { + return err + } + + labelSelector, _ := labels.Parse("kluctl.io/result-id") + + var partialSecret metav1.PartialObjectMetadata + partialSecret.SetGroupVersionKind(schema.GroupVersionKind{Version: "v1", Kind: "Secret"}) + + ca, err := cache.New(s.config, cache.Options{ + DefaultLabelSelector: labelSelector, + }) + if err != nil { + return err + } + + informer, err := ca.GetInformer(s.ctx, &partialSecret) + if err != nil { + return err + } + + s.client = c + s.cache = ca + s.informer = informer + + newCtx, cancel := context.WithCancel(s.ctx) + s.cancelCache = cancel + + go func() { + _ = ca.Start(newCtx) + }() + + s.cache.WaitForCacheSync(s.ctx) + + return nil } var invalidChars = regexp.MustCompile(`[^a-zA-Z0-9-]`) @@ -77,7 +116,40 @@ func (s *ResultStoreSecrets) buildName(cr *result.CommandResult) string { return nameWithId } -func (s *ResultStoreSecrets) WriteCommandResult(ctx context.Context, cr *result.CommandResult) error { +func (s *ResultStoreSecrets) ensureWriteNamespace() error { + if s.writeNamespace == "" { + return fmt.Errorf("missing writeNamespace") + } + var ns corev1.Namespace + err := s.client.Get(s.ctx, client.ObjectKey{Name: s.writeNamespace}, &ns) + if err != nil && errors.IsNotFound(err) { + ns := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: s.writeNamespace, + }, + } + err = s.client.Create(s.ctx, ns) + if err != nil { + return err + } + } else if err != nil { + return err + } + + return nil +} + +func (s *ResultStoreSecrets) WriteCommandResult(cr *result.CommandResult) error { + err := s.ensureClientAndCache() + if err != nil { + return err + } + + err = s.ensureWriteNamespace() + if err != nil { + return err + } + crJson, err := yaml.WriteJsonString(cr.ToReducedObjects()) if err != nil { return err @@ -102,67 +174,65 @@ func (s *ResultStoreSecrets) WriteCommandResult(ctx context.Context, cr *result. return err } - secret := uo.FromMap(map[string]interface{}{ - "apiVersion": "v1", - "kind": "Secret", - "metadata": map[string]any{ - "name": s.buildName(cr), - "namespace": s.writeNamespace, - "labels": map[string]any{ + secret := corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Secret", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: s.buildName(cr), + Namespace: s.writeNamespace, + Labels: map[string]string{ "kluctl.io/result-id": cr.Id, }, - "annotations": map[string]any{ + Annotations: map[string]string{ "kluctl.io/result-summary": summaryJson, }, }, - "data": map[string]any{ + Data: map[string][]byte{ "reducedResult": compressedCr, "compactedObjects": compressedObjects, }, - }) + } if cr.Project.NormalizedGitUrl != "" { - secret.SetK8sAnnotation("kluctl.io/result-project-normalized-url", cr.Project.NormalizedGitUrl) + secret.Annotations["kluctl.io/result-project-normalized-url"] = cr.Project.NormalizedGitUrl } if cr.Project.SubDir != "" { - secret.SetK8sAnnotation("kluctl.io/result-project-subdir", cr.Project.SubDir) + secret.Annotations["kluctl.io/result-project-subdir"] = cr.Project.SubDir } - _, _, err = s.k.ReadWrite().PatchObject(secret, k8s.PatchOptions{}) - return err + err = s.client.Patch(s.ctx, &secret, client.Apply, client.FieldOwner("kluctl-results")) + if err != nil { + return err + } + return nil } -func (s *ResultStoreSecrets) ListCommandResultSummaries(ctx context.Context, options ListCommandResultSummariesOptions) ([]result.CommandResultSummary, error) { - l, _, err := s.k.ListMetadata(schema.GroupVersionKind{ - Version: "v1", - Kind: "Secret", - }, "", map[string]string{ - "kluctl.io/result-id": "", - }) +func (s *ResultStoreSecrets) ListCommandResultSummaries(options ListCommandResultSummariesOptions) ([]result.CommandResultSummary, error) { + err := s.ensureClientAndCache() if err != nil { return nil, err } - ret := make([]result.CommandResultSummary, 0, len(l)) + var l metav1.PartialObjectMetadataList + l.SetGroupVersionKind(schema.GroupVersionKind{Version: "v1", Kind: "SecretList"}) + err = s.cache.List(s.ctx, &l, client.HasLabels{"kluctl.io/result-id"}) + if err != nil { + return nil, err + } - for _, x := range l { - summaryJson := x.GetK8sAnnotation("kluctl.io/result-summary") - if summaryJson == nil || *summaryJson == "" { - continue - } + ret := make([]result.CommandResultSummary, 0, len(l.Items)) - var summary result.CommandResultSummary - err = yaml.ReadYamlString(*summaryJson, &summary) + for _, x := range l.Items { + summary, err := s.parseSummary(&x) if err != nil { continue } - - if options.ProjectFilter != nil { - if summary.Project != *options.ProjectFilter { - continue - } + if !s.filterSummary(summary, options.ProjectFilter) { + continue } - ret = append(ret, summary) + ret = append(ret, *summary) } sort.Slice(ret, func(i, j int) bool { @@ -172,33 +242,146 @@ func (s *ResultStoreSecrets) ListCommandResultSummaries(ctx context.Context, opt return ret, nil } -func (s *ResultStoreSecrets) GetCommandResult(ctx context.Context, options GetCommandResultOptions) (*result.CommandResult, error) { - l, _, err := s.k.ListObjects(schema.GroupVersionKind{ - Version: "v1", - Kind: "Secret", - }, "", map[string]string{ +func (s *ResultStoreSecrets) parseSummary(obj client.Object) (*result.CommandResultSummary, error) { + if len(obj.GetAnnotations()) == 0 { + return nil, nil + } + summaryJson := obj.GetAnnotations()["kluctl.io/result-summary"] + if summaryJson == "" { + return nil, nil + } + + var summary result.CommandResultSummary + err := yaml.ReadYamlString(summaryJson, &summary) + if err != nil { + return nil, err + } + + return &summary, nil +} + +func (s *ResultStoreSecrets) filterSummary(summary *result.CommandResultSummary, project *result.ProjectKey) bool { + if project != nil { + if project.NormalizedGitUrl != "" && summary.Project.NormalizedGitUrl != project.NormalizedGitUrl { + return false + } + if project.SubDir != "" && summary.Project.SubDir != project.SubDir { + return false + } + } + + return true +} + +func (s *ResultStoreSecrets) WatchCommandResultSummaries(options ListCommandResultSummariesOptions, update func(summary *result.CommandResultSummary), delete func(id string)) (func(), error) { + err := s.ensureClientAndCache() + if err != nil { + return nil, err + } + + handler := func(obj any, deleted bool) { + obj2, ok := obj.(client.Object) + if !ok { + return + } + summary, err := s.parseSummary(obj2) + if err != nil { + return + } + if !s.filterSummary(summary, options.ProjectFilter) { + return + } + if deleted { + delete(summary.Id) + } else { + update(summary) + } + } + + r, err := s.informer.AddEventHandler(toolscache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + handler(obj, false) + }, + UpdateFunc: func(oldObj, newObj interface{}) { + handler(newObj, false) + }, + DeleteFunc: func(obj interface{}) { + handler(obj, true) + }, + }) + if err != nil { + return nil, err + } + + cancel := func() { + _ = s.informer.RemoveEventHandler(r) + } + return cancel, nil +} + +func (s *ResultStoreSecrets) getCommandResultSecret(id string) (*metav1.PartialObjectMetadata, error) { + err := s.ensureClientAndCache() + if err != nil { + return nil, err + } + + var l metav1.PartialObjectMetadataList + l.SetGroupVersionKind(schema.GroupVersionKind{Version: "v1", Kind: "SecretList"}) + err = s.cache.List(s.ctx, &l, client.MatchingLabels{"kluctl.io/result-id": id}) + if err != nil { + return nil, err + } + if len(l.Items) == 0 { + return nil, nil + } + return &l.Items[0], nil +} + +func (s *ResultStoreSecrets) HasCommandResult(id string) (bool, error) { + secret, err := s.getCommandResultSecret(id) + if err != nil { + return false, err + } + return secret != nil, nil +} + +func (s *ResultStoreSecrets) GetCommandResultSummary(id string) (*result.CommandResultSummary, error) { + secret, err := s.getCommandResultSecret(id) + if err != nil { + return nil, err + } + return s.parseSummary(secret) +} + +func (s *ResultStoreSecrets) GetCommandResult(options GetCommandResultOptions) (*result.CommandResult, error) { + has, err := s.HasCommandResult(options.Id) + if err != nil { + return nil, err + } + if !has { + return nil, nil + } + + var l corev1.SecretList + err = s.client.List(s.ctx, &l, client.MatchingLabels{ "kluctl.io/result-id": options.Id, }) if err != nil { return nil, err } - if len(l) == 0 { + if len(l.Items) == 0 { return nil, nil } + secret := l.Items[0] + var crJson, objectsJson []byte - err = utils.RunParallelE(ctx, func() error { - s, ok, err := l[0].GetNestedString("data", "reducedResult") - if err != nil { - return err - } + err = utils.RunParallelE(s.ctx, func() error { + var ok bool + crJson, ok = secret.Data["reducedResult"] if !ok { return fmt.Errorf("reducedResult field not present for %s", options.Id) } - crJson, err = base64.StdEncoding.DecodeString(s) - if err != nil { - return err - } crJson, err = utils.UncompressGzip(crJson) if err != nil { return err @@ -208,18 +391,11 @@ func (s *ResultStoreSecrets) GetCommandResult(ctx context.Context, options GetCo if options.Reduced { return nil } - s, ok, err := l[0].GetNestedString("data", "compactedObjects") - if err != nil { - return err - } + var ok bool + objectsJson, ok = secret.Data["compactedObjects"] if !ok { return fmt.Errorf("compactedObjects field not present for %s", options.Id) } - objectsJson, err = base64.StdEncoding.DecodeString(s) - if err != nil { - return err - } - objectsJson, err = utils.UncompressGzip(objectsJson) if err != nil { return err diff --git a/pkg/results/result-store.go b/pkg/results/result-store.go index 464dc6594..46d002d5d 100644 --- a/pkg/results/result-store.go +++ b/pkg/results/result-store.go @@ -1,7 +1,6 @@ package results import ( - "context" "github.com/kluctl/kluctl/v2/pkg/types/result" ) @@ -9,6 +8,10 @@ type ListProjectsOptions struct { ProjectFilter *result.ProjectKey `json:"projectFilter,omitempty"` } +type ListTargetsOptions struct { + ProjectFilter *result.ProjectKey `json:"projectFilter,omitempty"` +} + type ListCommandResultSummariesOptions struct { ProjectFilter *result.ProjectKey `json:"projectFilter,omitempty"` } @@ -19,50 +22,24 @@ type GetCommandResultOptions struct { } type ResultStore interface { - WriteCommandResult(ctx context.Context, cr *result.CommandResult) error + WriteCommandResult(cr *result.CommandResult) error - ListCommandResultSummaries(ctx context.Context, options ListCommandResultSummariesOptions) ([]result.CommandResultSummary, error) - GetCommandResult(ctx context.Context, options GetCommandResultOptions) (*result.CommandResult, error) + ListCommandResultSummaries(options ListCommandResultSummariesOptions) ([]result.CommandResultSummary, error) + WatchCommandResultSummaries(options ListCommandResultSummariesOptions, update func(summary *result.CommandResultSummary), delete func(id string)) (func(), error) + HasCommandResult(id string) (bool, error) + GetCommandResultSummary(id string) (*result.CommandResultSummary, error) + GetCommandResult(options GetCommandResultOptions) (*result.CommandResult, error) } -func ListProjects(ctx context.Context, rs ResultStore, options ListProjectsOptions) ([]result.ProjectSummary, error) { - summaries, err := rs.ListCommandResultSummaries(ctx, ListCommandResultSummariesOptions{ - ProjectFilter: options.ProjectFilter, - }) - if err != nil { - return nil, err +func FilterSummary(x *result.CommandResultSummary, filter *result.ProjectKey) bool { + if filter == nil { + return true } - - m := map[result.ProjectKey]result.ProjectSummary{} - for _, s := range summaries { - if _, ok := m[s.Project]; !ok { - m[s.Project] = result.ProjectSummary{Project: s.Project} - } + if x.Project.NormalizedGitUrl != filter.NormalizedGitUrl { + return false } - - ret := make([]result.ProjectSummary, 0, len(m)) - for _, p := range m { - for _, rs := range summaries { - switch rs.Command.Command { - case "deploy": - if p.LastDeployCommand == nil { - rs := rs - p.LastDeployCommand = &rs - } - case "delete": - if p.LastDeleteCommand == nil { - rs := rs - p.LastDeleteCommand = &rs - } - case "prune": - if p.LastPruneCommand == nil { - rs := rs - p.LastPruneCommand = &rs - } - } - } - ret = append(ret, p) + if x.Project.SubDir != filter.SubDir { + return false } - - return ret, nil + return true } diff --git a/pkg/results/results-collector.go b/pkg/results/results-collector.go new file mode 100644 index 000000000..583a5b9b2 --- /dev/null +++ b/pkg/results/results-collector.go @@ -0,0 +1,174 @@ +package results + +import ( + "context" + "fmt" + "github.com/kluctl/kluctl/v2/pkg/types/result" + "sort" + "sync" + "time" +) + +type ResultsCollector struct { + ctx context.Context + + stores []ResultStore + + resultSummaries map[string]summaryEntry + handlers []*handlerEntry + + mutex sync.Mutex +} + +type summaryEntry struct { + store ResultStore + summary *result.CommandResultSummary +} + +type handlerEntry struct { + options ListCommandResultSummariesOptions + update func(result *result.CommandResultSummary) + delete func(id string) +} + +func NewResultsCollector(ctx context.Context, stores []ResultStore) *ResultsCollector { + ret := &ResultsCollector{ + ctx: ctx, + stores: stores, + resultSummaries: map[string]summaryEntry{}, + } + + return ret +} + +func (rc *ResultsCollector) Start() { + rc.startWatchResults() +} + +func (rc *ResultsCollector) startWatchResults() { + for _, store := range rc.stores { + go rc.runWatchResults(store) + } +} + +func (rc *ResultsCollector) runWatchResults(store ResultStore) { + for { + _, err := store.WatchCommandResultSummaries(ListCommandResultSummariesOptions{}, func(summary *result.CommandResultSummary) { + rc.handleUpdate(store, summary) + }, func(id string) { + rc.handleDelete(store, id) + }) + if err == nil { + break + } + time.Sleep(5 * time.Second) + } +} + +func (rc *ResultsCollector) handleUpdate(store ResultStore, summary *result.CommandResultSummary) { + rc.mutex.Lock() + defer rc.mutex.Unlock() + + rc.resultSummaries[summary.Id] = summaryEntry{ + store: store, + summary: summary, + } + for _, h := range rc.handlers { + if FilterSummary(summary, h.options.ProjectFilter) { + h.update(summary) + } + } +} + +func (rc *ResultsCollector) handleDelete(store ResultStore, id string) { + rc.mutex.Lock() + defer rc.mutex.Unlock() + se, ok := rc.resultSummaries[id] + if !ok { + return + } + + delete(rc.resultSummaries, id) + for _, h := range rc.handlers { + if FilterSummary(se.summary, h.options.ProjectFilter) { + h.delete(id) + } + } +} + +func (rc *ResultsCollector) WriteCommandResult(cr *result.CommandResult) error { + return fmt.Errorf("WriteCommandResult is not supported in ResultsCollector") +} + +func (rc *ResultsCollector) ListCommandResultSummaries(options ListCommandResultSummariesOptions) ([]result.CommandResultSummary, error) { + rc.mutex.Lock() + defer rc.mutex.Unlock() + summaries := make([]result.CommandResultSummary, 0, len(rc.resultSummaries)) + for _, x := range rc.resultSummaries { + if !FilterSummary(x.summary, options.ProjectFilter) { + continue + } + summaries = append(summaries, *x.summary) + } + sort.Slice(summaries, func(i, j int) bool { + return summaries[i].Command.StartTime >= summaries[j].Command.StartTime + }) + return summaries, nil +} + +func (rc *ResultsCollector) WatchCommandResultSummaries(options ListCommandResultSummariesOptions, update func(summary *result.CommandResultSummary), delete func(id string)) (func(), error) { + rc.mutex.Lock() + defer rc.mutex.Unlock() + + h := &handlerEntry{ + options: options, + update: update, + delete: delete, + } + + rc.handlers = append(rc.handlers, h) + + for _, se := range rc.resultSummaries { + if FilterSummary(se.summary, h.options.ProjectFilter) { + h.update(se.summary) + } + } + + return func() { + rc.mutex.Lock() + defer rc.mutex.Unlock() + for i, h2 := range rc.handlers { + if h2 == h { + rc.handlers = append(rc.handlers[:i], rc.handlers[i+1:]...) + break + } + } + }, nil +} + +func (rc *ResultsCollector) HasCommandResult(id string) (bool, error) { + rc.mutex.Lock() + defer rc.mutex.Unlock() + _, ok := rc.resultSummaries[id] + return ok, nil +} + +func (rc *ResultsCollector) GetCommandResultSummary(id string) (*result.CommandResultSummary, error) { + rc.mutex.Lock() + defer rc.mutex.Unlock() + rs, ok := rc.resultSummaries[id] + if !ok { + return nil, nil + } + return rs.summary, nil +} + +func (rc *ResultsCollector) GetCommandResult(options GetCommandResultOptions) (*result.CommandResult, error) { + rc.mutex.Lock() + se, ok := rc.resultSummaries[options.Id] + rc.mutex.Unlock() + if !ok { + return nil, nil + } + return se.store.GetCommandResult(options) +} From 3636581d43cace6dd3e3c8f77dafa40e321d34fb Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 2 May 2023 21:40:38 +0200 Subject: [PATCH 1591/2916] fix: Exclude hooks from validation --- pkg/deployment/commands/validate.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/deployment/commands/validate.go b/pkg/deployment/commands/validate.go index e3000a09d..32a3dda68 100644 --- a/pkg/deployment/commands/validate.go +++ b/pkg/deployment/commands/validate.go @@ -59,6 +59,9 @@ func (cmd *ValidateCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*result } else if cmd.r != nil { for _, o := range cmd.r.Objects { refs = append(refs, o.Ref) + if o.Hook { + continue + } if o.Rendered != nil { renderedObjects = append(renderedObjects, o.Rendered) } From 8ab60327ae39e5c8d6edaab54a6a7c4a806ce755 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 2 May 2023 21:48:27 +0200 Subject: [PATCH 1592/2916] feat: Enable writing of command results by default --- cmd/kluctl/args/project.go | 2 +- cmd/kluctl/commands/utils.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/kluctl/args/project.go b/cmd/kluctl/args/project.go index e3ed5ab0e..b2b7c6189 100644 --- a/cmd/kluctl/args/project.go +++ b/cmd/kluctl/args/project.go @@ -43,6 +43,6 @@ type TargetFlags struct { } type CommandResultFlags struct { - WriteCommandResult bool `group:"results" help:"Enable experimental writing of command results into the cluster. Command results will be written into ConfigMaps in the kluctl-results namespace."` + NoWriteCommandResult bool `group:"results" help:"Disable writing of command results into the cluster."` CommandResultNamespace string `group:"results" help:"Override the namespace to be used when writing command results." default:"kluctl-results"` } diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 23375c1e8..9857b57b2 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -205,7 +205,7 @@ func withProjectTargetCommandContext(ctx context.Context, args projectTargetComm } var resultStore results.ResultStore - if args.commandResultFlags != nil && args.commandResultFlags.WriteCommandResult { + if args.commandResultFlags != nil && !args.commandResultFlags.NoWriteCommandResult { rc, err := targetCtx.SharedContext.K.ToRESTConfig() if err != nil { return err From 7daee7ff209551276ed37376e770fd11dc08ad13 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 3 May 2023 22:58:24 +0200 Subject: [PATCH 1593/2916] fix: Make ClusterInfo mandatory --- cmd/kluctl/commands/utils.go | 6 ++---- pkg/types/result/command_result.go | 2 +- pkg/types/result/summary.go | 12 ++++++------ 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 9857b57b2..d0fa4450e 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -284,9 +284,7 @@ func addCommandInfo(r *result.CommandResult, startTime time.Time, command string r.Target.TargetName = ctx.targetCtx.Target.Name r.Target.Discriminator = ctx.targetCtx.Target.Discriminator } - if r.ClusterInfo != nil { - r.Target.ClusterId = r.ClusterInfo.ClusterId - } + r.Target.ClusterId = r.ClusterInfo.ClusterId return nil } @@ -372,7 +370,7 @@ func addClusterInfo(r *result.CommandResult, ctx *commandCtx) error { if clusterId == "" { return fmt.Errorf("kube-system namespace has no uid") } - r.ClusterInfo = &result.ClusterInfo{ + r.ClusterInfo = result.ClusterInfo{ ClusterId: clusterId, } return nil diff --git a/pkg/types/result/command_result.go b/pkg/types/result/command_result.go index b84b552f4..06cee1183 100644 --- a/pkg/types/result/command_result.go +++ b/pkg/types/result/command_result.go @@ -132,7 +132,7 @@ type CommandResult struct { Target TargetKey `json:"target"` Command CommandInfo `json:"command,omitempty"` GitInfo *GitInfo `json:"gitInfo,omitempty"` - ClusterInfo *ClusterInfo `json:"clusterInfo,omitempty"` + ClusterInfo ClusterInfo `json:"clusterInfo"` Deployment *types.DeploymentProjectConfig `json:"deployment,omitempty"` Objects []ResultObject `json:"objects,omitempty"` diff --git a/pkg/types/result/summary.go b/pkg/types/result/summary.go index fbc4c25b2..900645e66 100644 --- a/pkg/types/result/summary.go +++ b/pkg/types/result/summary.go @@ -5,12 +5,12 @@ import ( ) type CommandResultSummary struct { - Id string `json:"id"` - Project ProjectKey `json:"project"` - Target TargetKey `json:"target"` - Command CommandInfo `json:"commandInfo"` - GitInfo *GitInfo `json:"gitInfo,omitempty"` - ClusterInfo *ClusterInfo `json:"clusterInfo,omitempty"` + Id string `json:"id"` + Project ProjectKey `json:"project"` + Target TargetKey `json:"target"` + Command CommandInfo `json:"commandInfo"` + GitInfo *GitInfo `json:"gitInfo,omitempty"` + ClusterInfo ClusterInfo `json:"clusterInfo,omitempty"` RenderedObjects int `json:"renderedObjects"` RemoteObjects int `json:"remoteObjects"` From 36396a03b27f5a7543e704a94a7dd8db9c46daea Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 3 May 2023 23:04:30 +0200 Subject: [PATCH 1594/2916] feat: Don't write CommandResult in diff command or dryRun mode --- cmd/kluctl/args/project.go | 5 +++-- cmd/kluctl/commands/cmd_delete.go | 2 +- cmd/kluctl/commands/cmd_deploy.go | 2 +- cmd/kluctl/commands/cmd_diff.go | 4 +--- cmd/kluctl/commands/cmd_poke_images.go | 2 +- cmd/kluctl/commands/cmd_prune.go | 2 +- 6 files changed, 8 insertions(+), 9 deletions(-) diff --git a/cmd/kluctl/args/project.go b/cmd/kluctl/args/project.go index b2b7c6189..3714186cb 100644 --- a/cmd/kluctl/args/project.go +++ b/cmd/kluctl/args/project.go @@ -43,6 +43,7 @@ type TargetFlags struct { } type CommandResultFlags struct { - NoWriteCommandResult bool `group:"results" help:"Disable writing of command results into the cluster."` - CommandResultNamespace string `group:"results" help:"Override the namespace to be used when writing command results." default:"kluctl-results"` + NoWriteCommandResult bool `group:"results" help:"Disable writing of command results into the cluster."` + ForceWriteCommandResult bool `group:"results" help:"Force writing of command results, even if the command is run in dry-run mode."` + CommandResultNamespace string `group:"results" help:"Override the namespace to be used when writing command results." default:"kluctl-results"` } diff --git a/cmd/kluctl/commands/cmd_delete.go b/cmd/kluctl/commands/cmd_delete.go index 39ab7f6a5..70d455375 100644 --- a/cmd/kluctl/commands/cmd_delete.go +++ b/cmd/kluctl/commands/cmd_delete.go @@ -64,7 +64,7 @@ func (cmd *deleteCmd) Run(ctx context.Context) error { if err != nil { return err } - err = outputCommandResult(cmdCtx, cmd.OutputFormatFlags, result, true) + err = outputCommandResult(cmdCtx, cmd.OutputFormatFlags, result, !cmd.DryRun || cmd.ForceWriteCommandResult) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_deploy.go b/cmd/kluctl/commands/cmd_deploy.go index c3ba95bd4..7b70de7a2 100644 --- a/cmd/kluctl/commands/cmd_deploy.go +++ b/cmd/kluctl/commands/cmd_deploy.go @@ -82,7 +82,7 @@ func (cmd *deployCmd) runCmdDeploy(cmdCtx *commandCtx, startTime time.Time) erro if err != nil { return err } - err = outputCommandResult(cmdCtx, cmd.OutputFormatFlags, result, true) + err = outputCommandResult(cmdCtx, cmd.OutputFormatFlags, result, !cmd.DryRun || cmd.ForceWriteCommandResult) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_diff.go b/cmd/kluctl/commands/cmd_diff.go index fd6fb7689..91be42dfa 100644 --- a/cmd/kluctl/commands/cmd_diff.go +++ b/cmd/kluctl/commands/cmd_diff.go @@ -20,7 +20,6 @@ type diffCmd struct { args.IgnoreFlags args.OutputFormatFlags args.RenderOutputDirFlags - args.CommandResultFlags } func (cmd *diffCmd) Help() string { @@ -39,7 +38,6 @@ func (cmd *diffCmd) Run(ctx context.Context) error { inclusionFlags: cmd.InclusionFlags, helmCredentials: cmd.HelmCredentials, renderOutputDirFlags: cmd.RenderOutputDirFlags, - commandResultFlags: &cmd.CommandResultFlags, } startTime := time.Now() return withProjectCommandContext(ctx, ptArgs, func(cmdCtx *commandCtx) error { @@ -58,7 +56,7 @@ func (cmd *diffCmd) Run(ctx context.Context) error { if err != nil { return err } - err = outputCommandResult(cmdCtx, cmd.OutputFormatFlags, result, true) + err = outputCommandResult(cmdCtx, cmd.OutputFormatFlags, result, false) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_poke_images.go b/cmd/kluctl/commands/cmd_poke_images.go index 888ecddde..571b501cd 100644 --- a/cmd/kluctl/commands/cmd_poke_images.go +++ b/cmd/kluctl/commands/cmd_poke_images.go @@ -59,7 +59,7 @@ func (cmd *pokeImagesCmd) Run(ctx context.Context) error { if err != nil { return err } - err = outputCommandResult(cmdCtx, cmd.OutputFormatFlags, result, true) + err = outputCommandResult(cmdCtx, cmd.OutputFormatFlags, result, !cmd.DryRun || cmd.ForceWriteCommandResult) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_prune.go b/cmd/kluctl/commands/cmd_prune.go index 567458b99..3fe34a470 100644 --- a/cmd/kluctl/commands/cmd_prune.go +++ b/cmd/kluctl/commands/cmd_prune.go @@ -61,7 +61,7 @@ func (cmd *pruneCmd) runCmdPrune(cmdCtx *commandCtx, startTime time.Time) error if err != nil { return err } - err = outputCommandResult(cmdCtx, cmd.OutputFormatFlags, result, true) + err = outputCommandResult(cmdCtx, cmd.OutputFormatFlags, result, !cmd.DryRun || cmd.ForceWriteCommandResult) if err != nil { return err } From 6de4583de1e7a006eeff1c0e8c348da99219c88f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 3 May 2023 23:14:16 +0200 Subject: [PATCH 1595/2916] chore: Run make replace-commands-help --- docs/reference/commands/common-arguments.md | 4 ++-- docs/reference/commands/validate.md | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/reference/commands/common-arguments.md b/docs/reference/commands/common-arguments.md index 2263c15b2..e77bf78e3 100644 --- a/docs/reference/commands/common-arguments.md +++ b/docs/reference/commands/common-arguments.md @@ -136,8 +136,8 @@ Command Results: --command-result-namespace string Override the namespace to be used when writing command results. (default "kluctl-results") - --write-command-result Enable experimental writing of command results into the cluster. Command - results will be written into ConfigMaps in the kluctl-results namespace. + --force-write-command-result Force writing of command results, even if the command is run in dry-run mode. + --no-write-command-result Disable writing of command results into the cluster. ``` diff --git a/docs/reference/commands/validate.md b/docs/reference/commands/validate.md index 77a0abb98..ef79bc0ba 100644 --- a/docs/reference/commands/validate.md +++ b/docs/reference/commands/validate.md @@ -31,6 +31,8 @@ In addition, the following arguments are available: Misc arguments: Command specific arguments. + --command-result existingfile Specify a command result to use instead of loading a project. + This will also perform drift detection. --helm-insecure-skip-tls-verify stringArray Controls skipping of TLS verification. Must be in the form --helm-insecure-skip-tls-verify=, where must match the id specified in the helm-chart.yaml. From 9ba8da18f526c9fb34686e0f0e7103421430eabc Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 3 May 2023 23:25:22 +0200 Subject: [PATCH 1596/2916] fix: Don't omit empty target name --- pkg/types/kluctl_project.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/types/kluctl_project.go b/pkg/types/kluctl_project.go index cde09e90c..83aa87f8d 100644 --- a/pkg/types/kluctl_project.go +++ b/pkg/types/kluctl_project.go @@ -11,7 +11,7 @@ type SealingConfig struct { } type Target struct { - Name string `json:"name,omitempty"` + Name string `json:"name"` Context *string `json:"context,omitempty"` Args *uo.UnstructuredObject `json:"args,omitempty"` SealingConfig *SealingConfig `json:"sealingConfig,omitempty"` From c1b599039eb74d6fa7637ac3812a3e9c4be21564 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 7 May 2023 22:36:54 +0200 Subject: [PATCH 1597/2916] chore(deps): Bump sigs.k8s.io/kustomize/kyaml from 0.14.1 to 0.14.2 (#460) Bumps [sigs.k8s.io/kustomize/kyaml](https://github.com/kubernetes-sigs/kustomize) from 0.14.1 to 0.14.2. - [Release notes](https://github.com/kubernetes-sigs/kustomize/releases) - [Commits](https://github.com/kubernetes-sigs/kustomize/compare/kyaml/v0.14.1...kyaml/v0.14.2) --- updated-dependencies: - dependency-name: sigs.k8s.io/kustomize/kyaml dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index baacdc9e8..8a38efb6a 100644 --- a/go.mod +++ b/go.mod @@ -47,7 +47,7 @@ require ( k8s.io/apimachinery v0.27.1 k8s.io/client-go v0.27.1 k8s.io/klog/v2 v2.100.1 - sigs.k8s.io/kustomize/kyaml v0.14.1 + sigs.k8s.io/kustomize/kyaml v0.14.2 sigs.k8s.io/structured-merge-diff/v4 v4.2.3 ) diff --git a/go.sum b/go.sum index 10c068ca4..6ff437002 100644 --- a/go.sum +++ b/go.sum @@ -1382,8 +1382,8 @@ sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMm sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/kustomize/api v0.13.2 h1:kejWfLeJhUsTGioDoFNJET5LQe/ajzXhJGYoU+pJsiA= sigs.k8s.io/kustomize/api v0.13.2/go.mod h1:DUp325VVMFVcQSq+ZxyDisA8wtldwHxLZbr1g94UHsw= -sigs.k8s.io/kustomize/kyaml v0.14.1 h1:c8iibius7l24G2wVAGZn/Va2wNys03GXLjYVIcFVxKA= -sigs.k8s.io/kustomize/kyaml v0.14.1/go.mod h1:AN1/IpawKilWD7V+YvQwRGUvuUOOWpjsHu6uHwonSF4= +sigs.k8s.io/kustomize/kyaml v0.14.2 h1:9WSwztbzwGszG1bZTziQUmVMrJccnyrLb5ZMKpJGvXw= +sigs.k8s.io/kustomize/kyaml v0.14.2/go.mod h1:AN1/IpawKilWD7V+YvQwRGUvuUOOWpjsHu6uHwonSF4= sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= From 751a0f181eaa5ac48f51fd27ebebd5abf66b1176 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 7 May 2023 22:37:43 +0200 Subject: [PATCH 1598/2916] chore(deps): Bump github.com/google/go-containerregistry (#462) Bumps [github.com/google/go-containerregistry](https://github.com/google/go-containerregistry) from 0.14.0 to 0.15.1. - [Release notes](https://github.com/google/go-containerregistry/releases) - [Changelog](https://github.com/google/go-containerregistry/blob/main/.goreleaser.yml) - [Commits](https://github.com/google/go-containerregistry/compare/v0.14.0...v0.15.1) --- updated-dependencies: - dependency-name: github.com/google/go-containerregistry dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 34 +++++++++++++++-------------- go.sum | 69 ++++++++++++++++++++++++++++++---------------------------- 2 files changed, 54 insertions(+), 49 deletions(-) diff --git a/go.mod b/go.mod index 8a38efb6a..a4dcb7e46 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/go-playground/validator/v10 v10.13.0 github.com/gobwas/glob v0.2.3 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 - github.com/google/go-containerregistry v0.14.0 + github.com/google/go-containerregistry v0.15.1 github.com/hashicorp/vault/api v1.9.1 github.com/hexops/gotextdiff v1.0.3 github.com/imdario/mergo v0.3.15 @@ -72,10 +72,10 @@ require ( replace sigs.k8s.io/controller-runtime => sigs.k8s.io/controller-runtime v0.13.1-0.20230426175615-dca0be70fd22 require ( - cloud.google.com/go/compute v1.18.0 // indirect + cloud.google.com/go/compute v1.19.1 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect - cloud.google.com/go/iam v0.12.0 // indirect - cloud.google.com/go/kms v1.9.0 // indirect + cloud.google.com/go/iam v0.13.0 // indirect + cloud.google.com/go/kms v1.10.0 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 // indirect github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0 // indirect @@ -89,7 +89,7 @@ require ( github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/sprig/v3 v3.2.3 // indirect github.com/Masterminds/squirrel v1.5.3 // indirect - github.com/Microsoft/go-winio v0.6.0 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect github.com/acomagu/bufpipe v1.0.4 // indirect github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect @@ -111,10 +111,11 @@ require ( github.com/chai2010/gettext-go v1.0.2 // indirect github.com/cloudflare/circl v1.3.0 // indirect github.com/containerd/containerd v1.7.0 // indirect + github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/docker/cli v23.0.1+incompatible // indirect - github.com/docker/docker v23.0.1+incompatible // indirect + github.com/docker/cli v23.0.5+incompatible // indirect + github.com/docker/docker v23.0.5+incompatible // indirect github.com/docker/docker-credential-helpers v0.7.0 // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-metrics v0.0.1 // indirect @@ -151,7 +152,7 @@ require ( github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.3.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect - github.com/googleapis/gax-go/v2 v2.7.0 // indirect + github.com/googleapis/gax-go/v2 v2.7.1 // indirect github.com/gorilla/mux v1.8.0 // indirect github.com/gosuri/uitable v0.0.4 // indirect github.com/goware/prefixer v0.0.0-20160118172347-395022866408 // indirect @@ -170,7 +171,7 @@ require ( github.com/jmoiron/sqlx v1.3.5 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.16.0 // indirect + github.com/klauspost/compress v1.16.5 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect @@ -193,7 +194,7 @@ require ( github.com/morikuni/aec v1.0.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect + github.com/opencontainers/image-spec v1.1.0-rc3 // indirect github.com/pelletier/go-toml/v2 v2.0.6 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect @@ -215,6 +216,7 @@ require ( github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/subosito/gotenv v1.4.2 // indirect github.com/urfave/cli v1.22.12 // indirect + github.com/vbatts/tar-split v0.11.3 // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect @@ -226,15 +228,15 @@ require ( go.opentelemetry.io/otel/trace v1.14.0 // indirect go.starlark.net v0.0.0-20221205180719-3fd0dac74452 // indirect go.uber.org/atomic v1.10.0 // indirect - golang.org/x/mod v0.9.0 // indirect - golang.org/x/oauth2 v0.6.0 // indirect + golang.org/x/mod v0.10.0 // indirect + golang.org/x/oauth2 v0.7.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.7.0 // indirect + golang.org/x/tools v0.8.0 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect - google.golang.org/api v0.110.0 // indirect + google.golang.org/api v0.114.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect - google.golang.org/grpc v1.53.0 // indirect + google.golang.org/genproto v0.0.0-20230323212658-478b75c54725 // indirect + google.golang.org/grpc v1.54.0 // indirect google.golang.org/protobuf v1.30.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/go.sum b/go.sum index 6ff437002..356be0ab3 100644 --- a/go.sum +++ b/go.sum @@ -27,17 +27,17 @@ cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvf cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute v1.18.0 h1:FEigFqoDbys2cvFkZ9Fjq4gnHBP55anJ0yQyau2f9oY= -cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= +cloud.google.com/go/compute v1.19.1 h1:am86mquDUgjGNWxiGn+5PGLbmgiWXlE/yNWpIpNvuXY= +cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= -cloud.google.com/go/iam v0.12.0 h1:DRtTY29b75ciH6Ov1PHb4/iat2CLCvrOm40Q0a6DFpE= -cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY= -cloud.google.com/go/kms v1.9.0 h1:b0votJQa/9DSsxgHwN33/tTLA7ZHVzfWhDCrfiXijSo= -cloud.google.com/go/kms v1.9.0/go.mod h1:qb1tPTgfF9RQP8e1wq4cLFErVuTJv7UsSC915J8dh3w= +cloud.google.com/go/iam v0.13.0 h1:+CmB+K0J/33d0zSQ9SlFWUeCCEn5XJA0ZMZ3pHE9u8k= +cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= +cloud.google.com/go/kms v1.10.0 h1:Imrtp8792uqNP9bdfPrjtUkjjqOMBcAJ2bdFaAnLhnk= +cloud.google.com/go/kms v1.10.0/go.mod h1:ng3KTUtQQU9bPX3+QGLsflZIHlkbn8amFAMY63m8d24= cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= @@ -89,8 +89,8 @@ github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBa github.com/Masterminds/squirrel v1.5.3 h1:YPpoceAcxuzIljlr5iWpNKaql7hLeG1KLSrhvdHpkZc= github.com/Masterminds/squirrel v1.5.3/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= -github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Microsoft/hcsshim v0.10.0-rc.7 h1:HBytQPxcv8Oy4244zbQbe6hnOnx544eL5QPUqhJldz8= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= @@ -191,6 +191,7 @@ github.com/containerd/containerd v1.7.0 h1:G/ZQr3gMZs6ZT0qPUZ15znx5QSdQdASW11nXT github.com/containerd/containerd v1.7.0/go.mod h1:QfR7Efgb/6X2BDpTPJRvPTYDE9rsF0FsXX9J8sIs/sc= github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k= +github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -213,12 +214,12 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 h1:aBfCb7iqHmDEIp6fBvC/hQUddQfg+3qdYjwzaiP9Hnc= github.com/dnaeon/go-vcr v1.1.0 h1:ReYa/UBrRyQdant9B4fNHGoCNKw6qh6P0fsdGmZpR7c= -github.com/docker/cli v23.0.1+incompatible h1:LRyWITpGzl2C9e9uGxzisptnxAn1zfZKXy13Ul2Q5oM= -github.com/docker/cli v23.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v23.0.5+incompatible h1:ufWmAOuD3Vmr7JP2G5K3cyuNC4YZWiAsuDEvFVVDafE= +github.com/docker/cli v23.0.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v23.0.1+incompatible h1:vjgvJZxprTTE1A37nm+CLNAdwu6xZekyoiVlUZEINcY= -github.com/docker/docker v23.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v23.0.5+incompatible h1:DaxtlTJjFSnLOXVNUBU1+6kXGz2lpDoEAH6QoxaSg8k= +github.com/docker/docker v23.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= @@ -396,8 +397,8 @@ github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8 github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-containerregistry v0.14.0 h1:z58vMqHxuwvAsVwvKEkmVBz2TlgBgH5k6koEXBtlYkw= -github.com/google/go-containerregistry v0.14.0/go.mod h1:aiJ2fp/SXvkWgmYHioXnbMdlgB8eXiiYOY55gfN91Wk= +github.com/google/go-containerregistry v0.15.1 h1:RsJ9NbfxYWF8Wl4VmvkpN3zYATwuvlPq2j20zmcs63E= +github.com/google/go-containerregistry v0.15.1/go.mod h1:wWK+LnOv4jXMM23IT/F1wdYftGWGr47Is8CG+pmHK1Q= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -428,8 +429,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9 github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ= -github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= +github.com/googleapis/gax-go/v2 v2.7.1 h1:gF4c0zjUP2H/s/hEGyLA3I0fA2ZWjzYiONAD6cvPr8A= +github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= @@ -536,8 +537,8 @@ github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI= +github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/kluctl/go-embed-python v0.0.0-3.10.9-20230206-2 h1:K96SwIr8MzBQ3kFFz2H/pA2y+EEk04vZ4fWj/YZghBU= github.com/kluctl/go-embed-python v0.0.0-3.10.9-20230206-2/go.mod h1:vzngsPshNKUtq0gxkYQKNJafrcH7Qy7Qt6yGNt7JmQI= github.com/kluctl/go-jinja2 v0.0.0-20230428103343-a832225dc94c h1:qAIvhYamCEU/tY6NaENEIQCynGV5sdON7zgZKnbrhhw= @@ -666,8 +667,8 @@ github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b h1:YWuSjZCQAPM8UUBLkYUk1e+rZcvWHJmFb6i6rM44Xs8= -github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= +github.com/opencontainers/image-spec v1.1.0-rc3 h1:fzg1mXZFj8YdPeNkRXMg+zb88BFV0Ys52cJydRwBkb8= +github.com/opencontainers/image-spec v1.1.0-rc3/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= github.com/opencontainers/runc v1.1.4 h1:nRCz/8sKg6K6jgYAFLDlXzPeITBZJyX28DBVhWD+5dg= github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= github.com/otiai10/copy v1.11.0 h1:OKBD80J/mLBrwnzXqGtFCzprFSGioo30JcmR4APsNwc= @@ -813,7 +814,8 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1 github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/urfave/cli v1.22.12 h1:igJgVw1JdKH+trcLWLeLwZjU9fEfPesQ+9/e4MQ44S8= github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8= -github.com/vbatts/tar-split v0.11.2 h1:Via6XqJr0hceW4wff3QRzD5gAk/tatMw/4ZA7cTlIME= +github.com/vbatts/tar-split v0.11.3 h1:hLFqsOLQ1SsppQNTMpkpPXClLDfC2A3Zgy9OUU+RVck= +github.com/vbatts/tar-split v0.11.3/go.mod h1:9QlHN18E+fEH7RdG+QAJJcuya3rqT7eXSTY7wGrAokY= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/xanzy/ssh-agent v0.3.2/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= @@ -935,8 +937,8 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= -golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= -golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1001,8 +1003,8 @@ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw= -golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= +golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g= +golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1083,6 +1085,7 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1179,8 +1182,8 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= -golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= -golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y= +golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1209,8 +1212,8 @@ google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjR google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= -google.golang.org/api v0.110.0 h1:l+rh0KYUooe9JGbGVx71tbFo4SMbMTXK3I3ia2QSEeU= -google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= +google.golang.org/api v0.114.0 h1:1xQPji6cO2E2vLiI+C/XiFAnsn1WV3mjaEwGLhi3grE= +google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1263,8 +1266,8 @@ google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA= -google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/genproto v0.0.0-20230323212658-478b75c54725 h1:VmCWItVXcKboEMCwZaWge+1JLiTCQSngZeINF+wzO+g= +google.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1287,8 +1290,8 @@ google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc= -google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= +google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag= +google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= From 17918dad58867dc5a4645a37112e979b1f80cc65 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sun, 7 May 2023 21:54:29 +0200 Subject: [PATCH 1599/2916] feat: Clean up old command results --- cmd/kluctl/args/project.go | 1 + cmd/kluctl/commands/utils.go | 2 +- docs/reference/commands/common-arguments.md | 1 + pkg/results/result-store-secrets.go | 52 ++++++++++++++++++--- 4 files changed, 49 insertions(+), 7 deletions(-) diff --git a/cmd/kluctl/args/project.go b/cmd/kluctl/args/project.go index 3714186cb..893c5062e 100644 --- a/cmd/kluctl/args/project.go +++ b/cmd/kluctl/args/project.go @@ -46,4 +46,5 @@ type CommandResultFlags struct { NoWriteCommandResult bool `group:"results" help:"Disable writing of command results into the cluster."` ForceWriteCommandResult bool `group:"results" help:"Force writing of command results, even if the command is run in dry-run mode."` CommandResultNamespace string `group:"results" help:"Override the namespace to be used when writing command results." default:"kluctl-results"` + KeepCommandResultsCount int `group:"results" help:"Configure how many old command results to keep." default:"10"` } diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index d0fa4450e..949db7a20 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -210,7 +210,7 @@ func withProjectTargetCommandContext(ctx context.Context, args projectTargetComm if err != nil { return err } - resultStore, err = results.NewResultStoreSecrets(ctx, rc, args.commandResultFlags.CommandResultNamespace) + resultStore, err = results.NewResultStoreSecrets(ctx, rc, args.commandResultFlags.CommandResultNamespace, args.commandResultFlags.KeepCommandResultsCount) if err != nil { return err } diff --git a/docs/reference/commands/common-arguments.md b/docs/reference/commands/common-arguments.md index e77bf78e3..055c8e2cb 100644 --- a/docs/reference/commands/common-arguments.md +++ b/docs/reference/commands/common-arguments.md @@ -137,6 +137,7 @@ Command Results: --command-result-namespace string Override the namespace to be used when writing command results. (default "kluctl-results") --force-write-command-result Force writing of command results, even if the command is run in dry-run mode. + --keep-command-results-count int Configure how many old command results to keep. (default 10) --no-write-command-result Disable writing of command results into the cluster. ``` diff --git a/pkg/results/result-store-secrets.go b/pkg/results/result-store-secrets.go index 4eb5f9ead..7edb7a87d 100644 --- a/pkg/results/result-store-secrets.go +++ b/pkg/results/result-store-secrets.go @@ -4,6 +4,7 @@ import ( "compress/gzip" "context" "fmt" + "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types/result" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/yaml" @@ -26,8 +27,9 @@ import ( type ResultStoreSecrets struct { ctx context.Context - config *rest.Config - writeNamespace string + config *rest.Config + writeNamespace string + keepResultsCount int mutex sync.Mutex client client.Client @@ -36,11 +38,12 @@ type ResultStoreSecrets struct { cancelCache context.CancelFunc } -func NewResultStoreSecrets(ctx context.Context, config *rest.Config, writeNamespace string) (*ResultStoreSecrets, error) { +func NewResultStoreSecrets(ctx context.Context, config *rest.Config, writeNamespace string, keepResultsCount int) (*ResultStoreSecrets, error) { s := &ResultStoreSecrets{ - ctx: ctx, - config: config, - writeNamespace: writeNamespace, + ctx: ctx, + config: config, + writeNamespace: writeNamespace, + keepResultsCount: keepResultsCount, } return s, nil } @@ -205,6 +208,43 @@ func (s *ResultStoreSecrets) WriteCommandResult(cr *result.CommandResult) error if err != nil { return err } + + err = s.cleanupResults(cr.Project, cr.Target) + if err != nil { + return err + } + + return nil +} + +func (s *ResultStoreSecrets) cleanupResults(project result.ProjectKey, target result.TargetKey) error { + results, err := s.ListCommandResultSummaries(ListCommandResultSummariesOptions{ + ProjectFilter: &project, + }) + if err != nil { + return err + } + + cnt := 0 + for _, rs := range results { + rs := rs + + if rs.Target != target { + continue + } + cnt++ + + if cnt > s.keepResultsCount { + err := s.client.DeleteAllOf(s.ctx, &corev1.Secret{}, client.InNamespace(s.writeNamespace), client.MatchingLabels{ + "kluctl.io/result-id": rs.Id, + }) + if err != nil { + status.Warning(s.ctx, "Failed to delete old command result %s: %s", rs.Id, err) + } else { + status.Info(s.ctx, "Deleted old command result %s", rs.Id) + } + } + } return nil } From 9d6dc946578a35a11c8a598103317496d64e4f69 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sun, 7 May 2023 22:47:40 +0200 Subject: [PATCH 1600/2916] fix: Use apimachinery YAMLReader utils to parsy multi-doc YAML (#463) This fixes issues with new-lines being lost in ConfigMaps. --- pkg/yaml/yaml.go | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/pkg/yaml/yaml.go b/pkg/yaml/yaml.go index b28c789eb..7ce32045d 100644 --- a/pkg/yaml/yaml.go +++ b/pkg/yaml/yaml.go @@ -1,6 +1,7 @@ package yaml import ( + "bufio" "bytes" "encoding/json" "fmt" @@ -8,6 +9,7 @@ import ( "golang.org/x/text/encoding/unicode" "golang.org/x/text/transform" "io" + apimachinery_yaml "k8s.io/apimachinery/pkg/util/yaml" "os" "path/filepath" "regexp" @@ -85,30 +87,23 @@ func ReadYamlAllStream(r io.Reader) ([]interface{}, error) { func readYamlAllStream(r io.Reader, strict bool) ([]interface{}, error) { r = newUnicodeReader(r) - b, err := io.ReadAll(r) - if err != nil { - return nil, err - } - s := string(b) - s = strings.TrimSpace(s) - - if s == "" { - return nil, nil - } + yr := apimachinery_yaml.NewYAMLReader(bufio.NewReader(r)) - docs := docsSep.Split(s, -1) - - ret := make([]any, 0, len(docs)) - for _, doc := range docs { - if doc == "" { - continue + var ret []any + for { + doc, err := yr.Read() + if err != nil { + if err == io.EOF { + break + } + return nil, err } var x any if strict { - err = yaml.UnmarshalStrict([]byte(doc), &x) + err = yaml.UnmarshalStrict(doc, &x) } else { - err = yaml.Unmarshal([]byte(doc), &x) + err = yaml.Unmarshal(doc, &x) } if err != nil { return nil, err From f927c26d2e9efcde65269e5d4ba293a3180e2b01 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 May 2023 17:26:47 +0200 Subject: [PATCH 1601/2916] chore(deps): Bump github.com/sergi/go-diff from 1.2.0 to 1.3.1 (#465) Bumps [github.com/sergi/go-diff](https://github.com/sergi/go-diff) from 1.2.0 to 1.3.1. - [Commits](https://github.com/sergi/go-diff/compare/v1.2.0...v1.3.1) --- updated-dependencies: - dependency-name: github.com/sergi/go-diff dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index d32c66cd1..c9b74a84d 100644 --- a/go.mod +++ b/go.mod @@ -63,7 +63,7 @@ require ( github.com/huandu/xstrings v1.4.0 github.com/onsi/gomega v1.27.6 github.com/otiai10/copy v1.11.0 - github.com/sergi/go-diff v1.2.0 + github.com/sergi/go-diff v1.3.1 go.mozilla.org/sops/v3 v3.7.4-0.20220901181616-9124783930b1 sigs.k8s.io/controller-runtime v0.14.5 sigs.k8s.io/kustomize/api v0.13.2 diff --git a/go.sum b/go.sum index 356be0ab3..d736f2c84 100644 --- a/go.sum +++ b/go.sum @@ -750,8 +750,8 @@ github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkB github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= -github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= From 13e464033128153c64f57c35f724856867689040 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 May 2023 17:26:59 +0200 Subject: [PATCH 1602/2916] chore(deps): Bump golang.org/x/net from 0.9.0 to 0.10.0 (#467) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.9.0 to 0.10.0. - [Commits](https://github.com/golang/net/compare/v0.9.0...v0.10.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c9b74a84d..fdee8993b 100644 --- a/go.mod +++ b/go.mod @@ -36,7 +36,7 @@ require ( github.com/stretchr/testify v1.8.2 github.com/xanzy/ssh-agent v0.3.3 golang.org/x/crypto v0.8.0 - golang.org/x/net v0.9.0 + golang.org/x/net v0.10.0 golang.org/x/sync v0.2.0 golang.org/x/sys v0.8.0 golang.org/x/term v0.8.0 diff --git a/go.sum b/go.sum index d736f2c84..856909ddd 100644 --- a/go.sum +++ b/go.sum @@ -989,8 +989,8 @@ golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= -golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= From 9fd93f4c980d3ce03ea03f545da386609cca32b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 May 2023 17:27:08 +0200 Subject: [PATCH 1603/2916] chore(deps): Bump github.com/aws/aws-sdk-go-v2/config (#471) Bumps [github.com/aws/aws-sdk-go-v2/config](https://github.com/aws/aws-sdk-go-v2) from 1.18.23 to 1.18.25. - [Release notes](https://github.com/aws/aws-sdk-go-v2/releases) - [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go-v2/compare/config/v1.18.23...config/v1.18.25) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go-v2/config dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index fdee8993b..44f28861a 100644 --- a/go.mod +++ b/go.mod @@ -54,7 +54,7 @@ require ( require ( filippo.io/age v1.1.1 github.com/aws/aws-sdk-go-v2 v1.18.0 - github.com/aws/aws-sdk-go-v2/config v1.18.23 + github.com/aws/aws-sdk-go-v2/config v1.18.25 github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.7 github.com/go-git/go-git/v5 v5.6.1 github.com/go-logr/logr v1.2.4 @@ -95,7 +95,7 @@ require ( github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect github.com/acomagu/bufpipe v1.0.4 // indirect github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.13.22 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.13.24 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27 // indirect @@ -104,7 +104,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/kms v1.17.5 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.12.10 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.10 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.18.11 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.19.0 // indirect github.com/aws/smithy-go v1.13.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver v3.5.1+incompatible // indirect diff --git a/go.sum b/go.sum index 856909ddd..85288bd3f 100644 --- a/go.sum +++ b/go.sum @@ -121,10 +121,10 @@ github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:W github.com/aws/aws-sdk-go-v2 v1.16.7/go.mod h1:6CpKuLXg2w7If3ABZCl/qZ6rEgwtjZTn4eAf4RcEyuw= github.com/aws/aws-sdk-go-v2 v1.18.0 h1:882kkTpSFhdgYRKVZ/VCgf7sd0ru57p2JCxz4/oN5RY= github.com/aws/aws-sdk-go-v2 v1.18.0/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= -github.com/aws/aws-sdk-go-v2/config v1.18.23 h1:gc3lPsAnZpwfi2exupmgHfva0JiAY2BWDg5JWYlmA28= -github.com/aws/aws-sdk-go-v2/config v1.18.23/go.mod h1:rx0ruaQ+gk3OrLFHRRx56lA//XxP8K8uPzeNiKNuWVY= -github.com/aws/aws-sdk-go-v2/credentials v1.13.22 h1:Hp9rwJS4giQ48xqonRV/s7QcDf/wxF6UY7osRmBabvI= -github.com/aws/aws-sdk-go-v2/credentials v1.13.22/go.mod h1:BfNcm6A9nSd+bzejDcMJ5RE+k6WbkCwWkQil7q4heRk= +github.com/aws/aws-sdk-go-v2/config v1.18.25 h1:JuYyZcnMPBiFqn87L2cRppo+rNwgah6YwD3VuyvaW6Q= +github.com/aws/aws-sdk-go-v2/config v1.18.25/go.mod h1:dZnYpD5wTW/dQF0rRNLVypB396zWCcPiBIvdvSWHEg4= +github.com/aws/aws-sdk-go-v2/credentials v1.13.24 h1:PjiYyls3QdCrzqUN35jMWtUK1vqVZ+zLfdOa/UPFDp0= +github.com/aws/aws-sdk-go-v2/credentials v1.13.24/go.mod h1:jYPYi99wUOPIFi0rhiOvXeSEReVOzBqFNOX5bXYoG2o= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3 h1:jJPgroehGvjrde3XufFIJUZVK5A2L9a3KwSFgKy9n8w= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3/go.mod h1:4Q0UFP0YJf0NrsEuEYHpM9fTSEVnD16Z3uyEF7J9JGM= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.14/go.mod h1:kdjrMwHwrC3+FsKhNcCMJ7tUVj/8uSD5CZXeQ4wV6fM= @@ -145,8 +145,8 @@ github.com/aws/aws-sdk-go-v2/service/sso v1.12.10 h1:UBQjaMTCKwyUYwiVnUt6toEJwGX github.com/aws/aws-sdk-go-v2/service/sso v1.12.10/go.mod h1:ouy2P4z6sJN70fR3ka3wD3Ro3KezSxU6eKGQI2+2fjI= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.10 h1:PkHIIJs8qvq0e5QybnZoG1K/9QTrLr9OsqCIo59jOBA= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.10/go.mod h1:AFvkxc8xfBe8XA+5St5XIHHrQQtkxqrRincx4hmMHOk= -github.com/aws/aws-sdk-go-v2/service/sts v1.18.11 h1:uBE+Zj478pfxV98L6SEpvxYiADNjTlMNY714PJLE7uo= -github.com/aws/aws-sdk-go-v2/service/sts v1.18.11/go.mod h1:BgQOMsg8av8jset59jelyPW7NoZcZXLVpDsXunGDrk8= +github.com/aws/aws-sdk-go-v2/service/sts v1.19.0 h1:2DQLAKDteoEDI8zpCzqBMaZlJuoE9iTYD0gFmXVax9E= +github.com/aws/aws-sdk-go-v2/service/sts v1.19.0/go.mod h1:BgQOMsg8av8jset59jelyPW7NoZcZXLVpDsXunGDrk8= github.com/aws/smithy-go v1.12.0/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8= github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= From 71748e3a9d20f9a24aa6ee8e5350aa9b794a8702 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 May 2023 17:27:34 +0200 Subject: [PATCH 1604/2916] chore(deps): Bump sigs.k8s.io/kustomize/api from 0.13.2 to 0.13.4 (#472) Bumps [sigs.k8s.io/kustomize/api](https://github.com/kubernetes-sigs/kustomize) from 0.13.2 to 0.13.4. - [Release notes](https://github.com/kubernetes-sigs/kustomize/releases) - [Commits](https://github.com/kubernetes-sigs/kustomize/compare/api/v0.13.2...api/v0.13.4) --- updated-dependencies: - dependency-name: sigs.k8s.io/kustomize/api dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 44f28861a..27a4a39d1 100644 --- a/go.mod +++ b/go.mod @@ -66,7 +66,7 @@ require ( github.com/sergi/go-diff v1.3.1 go.mozilla.org/sops/v3 v3.7.4-0.20220901181616-9124783930b1 sigs.k8s.io/controller-runtime v0.14.5 - sigs.k8s.io/kustomize/api v0.13.2 + sigs.k8s.io/kustomize/api v0.13.4 sigs.k8s.io/yaml v1.3.0 ) diff --git a/go.sum b/go.sum index 85288bd3f..a55c70084 100644 --- a/go.sum +++ b/go.sum @@ -1383,8 +1383,8 @@ sigs.k8s.io/controller-runtime v0.13.1-0.20230426175615-dca0be70fd22 h1:KnAZ+ITT sigs.k8s.io/controller-runtime v0.13.1-0.20230426175615-dca0be70fd22/go.mod h1:ujEX5tSkpg5cCOhcwDWLsXwNuMCO+j4rpmmkIn6BGGc= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/kustomize/api v0.13.2 h1:kejWfLeJhUsTGioDoFNJET5LQe/ajzXhJGYoU+pJsiA= -sigs.k8s.io/kustomize/api v0.13.2/go.mod h1:DUp325VVMFVcQSq+ZxyDisA8wtldwHxLZbr1g94UHsw= +sigs.k8s.io/kustomize/api v0.13.4 h1:E38Hfx0G9R9v7vRgKshviPotJQETG0S2gD3JdHLCAsI= +sigs.k8s.io/kustomize/api v0.13.4/go.mod h1:Bkaavz5RKK6ZzP0zgPrB7QbpbBJKiHuD3BB0KujY7Ls= sigs.k8s.io/kustomize/kyaml v0.14.2 h1:9WSwztbzwGszG1bZTziQUmVMrJccnyrLb5ZMKpJGvXw= sigs.k8s.io/kustomize/kyaml v0.14.2/go.mod h1:AN1/IpawKilWD7V+YvQwRGUvuUOOWpjsHu6uHwonSF4= sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= From f9a5c0ef531eb4f0174b4b6bcb0a5df330969847 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 May 2023 22:18:44 +0200 Subject: [PATCH 1605/2916] chore(deps): Bump golang.org/x/crypto from 0.8.0 to 0.9.0 (#473) Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.8.0 to 0.9.0. - [Commits](https://github.com/golang/crypto/compare/v0.8.0...v0.9.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 27a4a39d1..39a495786 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( github.com/spf13/viper v1.15.0 github.com/stretchr/testify v1.8.2 github.com/xanzy/ssh-agent v0.3.3 - golang.org/x/crypto v0.8.0 + golang.org/x/crypto v0.9.0 golang.org/x/net v0.10.0 golang.org/x/sync v0.2.0 golang.org/x/sys v0.8.0 diff --git a/go.sum b/go.sum index a55c70084..e7da825eb 100644 --- a/go.sum +++ b/go.sum @@ -898,8 +898,8 @@ golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= -golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= From 22ae21caac010a7b9a23dcd1ab00fba1b1b4b6ad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 13 May 2023 16:44:32 +0200 Subject: [PATCH 1606/2916] chore(deps): Bump helm.sh/helm/v3 from 3.11.3 to 3.12.0 (#475) Bumps [helm.sh/helm/v3](https://github.com/helm/helm) from 3.11.3 to 3.12.0. - [Release notes](https://github.com/helm/helm/releases) - [Commits](https://github.com/helm/helm/compare/v3.11.3...v3.12.0) --- updated-dependencies: - dependency-name: helm.sh/helm/v3 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 10 ++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 39a495786..0b8c91a8f 100644 --- a/go.mod +++ b/go.mod @@ -41,7 +41,7 @@ require ( golang.org/x/sys v0.8.0 golang.org/x/term v0.8.0 golang.org/x/text v0.9.0 - helm.sh/helm/v3 v3.11.3 + helm.sh/helm/v3 v3.12.0 k8s.io/api v0.27.1 k8s.io/apiextensions-apiserver v0.27.1 k8s.io/apimachinery v0.27.1 @@ -248,7 +248,7 @@ require ( k8s.io/cli-runtime v0.27.1 // indirect k8s.io/component-base v0.27.1 // indirect k8s.io/kube-openapi v0.0.0-20230308215209-15aac26d736a // indirect - k8s.io/kubectl v0.26.0 // indirect + k8s.io/kubectl v0.27.1 // indirect k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 // indirect oras.land/oras-go v1.2.3 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect diff --git a/go.sum b/go.sum index e7da825eb..f98f646a7 100644 --- a/go.sum +++ b/go.sum @@ -266,6 +266,7 @@ github.com/fluxcd/pkg/kustomize v1.1.1 h1:hYFJGi+fiaecY4gXvx52fumlvDEq/1RdFbaev6 github.com/fluxcd/pkg/kustomize v1.1.1/go.mod h1:i+Z9iPAoSz28oH0FmDI73iqZ3oXZxQR2O3HfhdsWhfo= github.com/fluxcd/pkg/sourceignore v0.3.3 h1:Ue29JAuPECEYdvIqdpXpQaDxpeySn7amarLArp7XoIs= github.com/fluxcd/pkg/sourceignore v0.3.3/go.mod h1:yuJzKggph0Bdbk9LgXjJQhvJZSTJV/1vS7mJuB7mPa0= +github.com/foxcpp/go-mockdns v1.0.0 h1:7jBqxd3WDWwi/6WhDvacvH1XsN3rOLXyHM1uhvIx6FI= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -610,6 +611,7 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5 github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.25 h1:dFwPR6SfLtrSwgDcIq2bcU/gVutB4sNApq2HBdqcakg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.5/go.mod h1:v8+iFts2sPIKUV1ltktPXMCC8fumSKFItNcD2cLtRR4= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= @@ -1342,8 +1344,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= -helm.sh/helm/v3 v3.11.3 h1:n1X5yaQTP5DYywlBOZMl2gX398Gp6YwFp/IAVj6+5D4= -helm.sh/helm/v3 v3.11.3/go.mod h1:S+sOdQc3BLvt09a9rSlKKVs9x0N/yx+No0y3qFw+FQ8= +helm.sh/helm/v3 v3.12.0 h1:rOq2TPVzg5jt4q5ermAZGZFxNW2uQhKjRhBneAutMEM= +helm.sh/helm/v3 v3.12.0/go.mod h1:8K/469yxjUMu6BaD2EagCitkPjELUL/l2AgCO142G94= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1369,8 +1371,8 @@ k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20230308215209-15aac26d736a h1:gmovKNur38vgoWfGtP5QOGNOA7ki4n6qNYoFAgMlNvg= k8s.io/kube-openapi v0.0.0-20230308215209-15aac26d736a/go.mod h1:y5VtZWM9sHHc2ZodIH/6SHzXj+TPU5USoA8lcIeKEKY= -k8s.io/kubectl v0.26.0 h1:xmrzoKR9CyNdzxBmXV7jW9Ln8WMrwRK6hGbbf69o4T0= -k8s.io/kubectl v0.26.0/go.mod h1:eInP0b+U9XUJWSYeU9XZnTA+cVYuWyl3iYPGtru0qhQ= +k8s.io/kubectl v0.27.1 h1:9T5c5KdpburYiW8XKQSH0Uly1kMNE90aGSnbYUZNdcA= +k8s.io/kubectl v0.27.1/go.mod h1:QsAkSmrRsKTPlAFzF8kODGDl4p35BIwQnc9XFhkcsy8= k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 h1:kmDqav+P+/5e1i9tFfHq1qcF3sOrDp+YEkVDAHu7Jwk= k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= oras.land/oras-go v1.2.3 h1:v8PJl+gEAntI1pJ/LCrDgsuk+1PKVavVEPsYIHFE5uY= From f818c01f563a25c633f3d97f39bfa9f7f301579b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 13 May 2023 16:45:03 +0200 Subject: [PATCH 1607/2916] chore(deps): Bump github.com/docker/distribution (#476) Bumps [github.com/docker/distribution](https://github.com/docker/distribution) from 2.8.1+incompatible to 2.8.2+incompatible. - [Release notes](https://github.com/docker/distribution/releases) - [Commits](https://github.com/docker/distribution/compare/v2.8.1...v2.8.2) --- updated-dependencies: - dependency-name: github.com/docker/distribution dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0b8c91a8f..53d6566c9 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d github.com/bitnami-labs/sealed-secrets v0.20.5 github.com/cyphar/filepath-securejoin v0.2.3 - github.com/docker/distribution v2.8.1+incompatible + github.com/docker/distribution v2.8.2+incompatible github.com/fluxcd/pkg/kustomize v1.1.1 github.com/go-playground/validator/v10 v10.13.0 github.com/gobwas/glob v0.2.3 // indirect diff --git a/go.sum b/go.sum index f98f646a7..992d6212a 100644 --- a/go.sum +++ b/go.sum @@ -216,8 +216,8 @@ github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 h1:aB github.com/dnaeon/go-vcr v1.1.0 h1:ReYa/UBrRyQdant9B4fNHGoCNKw6qh6P0fsdGmZpR7c= github.com/docker/cli v23.0.5+incompatible h1:ufWmAOuD3Vmr7JP2G5K3cyuNC4YZWiAsuDEvFVVDafE= github.com/docker/cli v23.0.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= -github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= +github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v23.0.5+incompatible h1:DaxtlTJjFSnLOXVNUBU1+6kXGz2lpDoEAH6QoxaSg8k= github.com/docker/docker v23.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= From e44bcc2b5da2e64356062ded13e0918488f8ac62 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 May 2023 07:18:29 +0200 Subject: [PATCH 1608/2916] chore(deps): Bump k8s.io/api from 0.27.1 to 0.27.2 (#487) Bumps [k8s.io/api](https://github.com/kubernetes/api) from 0.27.1 to 0.27.2. - [Commits](https://github.com/kubernetes/api/compare/v0.27.1...v0.27.2) --- updated-dependencies: - dependency-name: k8s.io/api dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 53d6566c9..8343b592c 100644 --- a/go.mod +++ b/go.mod @@ -42,9 +42,9 @@ require ( golang.org/x/term v0.8.0 golang.org/x/text v0.9.0 helm.sh/helm/v3 v3.12.0 - k8s.io/api v0.27.1 + k8s.io/api v0.27.2 k8s.io/apiextensions-apiserver v0.27.1 - k8s.io/apimachinery v0.27.1 + k8s.io/apimachinery v0.27.2 k8s.io/client-go v0.27.1 k8s.io/klog/v2 v2.100.1 sigs.k8s.io/kustomize/kyaml v0.14.2 @@ -247,7 +247,7 @@ require ( k8s.io/apiserver v0.27.1 // indirect k8s.io/cli-runtime v0.27.1 // indirect k8s.io/component-base v0.27.1 // indirect - k8s.io/kube-openapi v0.0.0-20230308215209-15aac26d736a // indirect + k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect k8s.io/kubectl v0.27.1 // indirect k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 // indirect oras.land/oras-go v1.2.3 // indirect diff --git a/go.sum b/go.sum index 992d6212a..3d3691e21 100644 --- a/go.sum +++ b/go.sum @@ -1353,12 +1353,12 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.27.1 h1:Z6zUGQ1Vd10tJ+gHcNNNgkV5emCyW+v2XTmn+CLjSd0= -k8s.io/api v0.27.1/go.mod h1:z5g/BpAiD+f6AArpqNjkY+cji8ueZDU/WV1jcj5Jk4E= +k8s.io/api v0.27.2 h1:+H17AJpUMvl+clT+BPnKf0E3ksMAzoBBg7CntpSuADo= +k8s.io/api v0.27.2/go.mod h1:ENmbocXfBT2ADujUXcBhHV55RIT31IIEvkntP6vZKS4= k8s.io/apiextensions-apiserver v0.27.1 h1:Hp7B3KxKHBZ/FxmVFVpaDiXI6CCSr49P1OJjxKO6o4g= k8s.io/apiextensions-apiserver v0.27.1/go.mod h1:8jEvRDtKjVtWmdkhOqE84EcNWJt/uwF8PC4627UZghY= -k8s.io/apimachinery v0.27.1 h1:EGuZiLI95UQQcClhanryclaQE6xjg1Bts6/L3cD7zyc= -k8s.io/apimachinery v0.27.1/go.mod h1:5ikh59fK3AJ287GUvpUsryoMFtH9zj/ARfWCo3AyXTM= +k8s.io/apimachinery v0.27.2 h1:vBjGaKKieaIreI+oQwELalVG4d8f3YAMNpWLzDXkxeg= +k8s.io/apimachinery v0.27.2/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E= k8s.io/apiserver v0.27.1 h1:phY+BtXjjzd+ta3a4kYbomC81azQSLa1K8jo9RBw7Lg= k8s.io/apiserver v0.27.1/go.mod h1:UGrOjLY2KsieA9Fw6lLiTObxTb8Z1xEba4uqSuMY0WU= k8s.io/cli-runtime v0.27.1 h1:MMzp5Q/Xmr5L1Lrowuc+Y/r95XINC6c6/fE3aN7JDRM= @@ -1369,8 +1369,8 @@ k8s.io/component-base v0.27.1 h1:kEB8p8lzi4gCs5f2SPU242vOumHJ6EOsOnDM3tTuDTM= k8s.io/component-base v0.27.1/go.mod h1:UGEd8+gxE4YWoigz5/lb3af3Q24w98pDseXcXZjw+E0= k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20230308215209-15aac26d736a h1:gmovKNur38vgoWfGtP5QOGNOA7ki4n6qNYoFAgMlNvg= -k8s.io/kube-openapi v0.0.0-20230308215209-15aac26d736a/go.mod h1:y5VtZWM9sHHc2ZodIH/6SHzXj+TPU5USoA8lcIeKEKY= +k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5FJ2kxm1WrQFanWchyKuqGg= +k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f/go.mod h1:byini6yhqGC14c3ebc/QwanvYwhuMWF6yz2F8uwW8eg= k8s.io/kubectl v0.27.1 h1:9T5c5KdpburYiW8XKQSH0Uly1kMNE90aGSnbYUZNdcA= k8s.io/kubectl v0.27.1/go.mod h1:QsAkSmrRsKTPlAFzF8kODGDl4p35BIwQnc9XFhkcsy8= k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 h1:kmDqav+P+/5e1i9tFfHq1qcF3sOrDp+YEkVDAHu7Jwk= From 87b267ae546a120145b8a8b67b9faa054ad12316 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 May 2023 07:18:42 +0200 Subject: [PATCH 1609/2916] chore(deps): Bump github.com/sirupsen/logrus from 1.9.0 to 1.9.2 (#485) Bumps [github.com/sirupsen/logrus](https://github.com/sirupsen/logrus) from 1.9.0 to 1.9.2. - [Release notes](https://github.com/sirupsen/logrus/releases) - [Changelog](https://github.com/sirupsen/logrus/blob/master/CHANGELOG.md) - [Commits](https://github.com/sirupsen/logrus/compare/v1.9.0...v1.9.2) --- updated-dependencies: - dependency-name: github.com/sirupsen/logrus dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 8343b592c..f97c74f98 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/r3labs/diff/v2 v2.15.1 github.com/rogpeppe/go-internal v1.10.0 - github.com/sirupsen/logrus v1.9.0 + github.com/sirupsen/logrus v1.9.2 github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.15.0 diff --git a/go.sum b/go.sum index 3d3691e21..6eacedad4 100644 --- a/go.sum +++ b/go.sum @@ -761,8 +761,9 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= +github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/skeema/knownhosts v1.1.0 h1:Wvr9V0MxhjRbl3f9nMnKnFfiWTJmtECJ9Njkea3ysW0= github.com/skeema/knownhosts v1.1.0/go.mod h1:sKFq3RD6/TKZkSWn8boUbDC7Qkgcv+8XXijpFO6roag= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= From 7af7c84e0dabff9708e94663fc41355ead3178d5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 May 2023 07:18:56 +0200 Subject: [PATCH 1610/2916] chore(deps): Bump github.com/aws/aws-sdk-go-v2/service/secretsmanager (#483) Bumps [github.com/aws/aws-sdk-go-v2/service/secretsmanager](https://github.com/aws/aws-sdk-go-v2) from 1.19.7 to 1.19.8. - [Release notes](https://github.com/aws/aws-sdk-go-v2/releases) - [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go-v2/compare/service/efs/v1.19.7...service/efs/v1.19.8) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go-v2/service/secretsmanager dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f97c74f98..d28c52fb1 100644 --- a/go.mod +++ b/go.mod @@ -55,7 +55,7 @@ require ( filippo.io/age v1.1.1 github.com/aws/aws-sdk-go-v2 v1.18.0 github.com/aws/aws-sdk-go-v2/config v1.18.25 - github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.7 + github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.8 github.com/go-git/go-git/v5 v5.6.1 github.com/go-logr/logr v1.2.4 github.com/google/uuid v1.3.0 diff --git a/go.sum b/go.sum index 6eacedad4..8ac7cac3a 100644 --- a/go.sum +++ b/go.sum @@ -139,8 +139,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27 h1:0iKliEXAc github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27/go.mod h1:EOwBD4J4S5qYszS5/3DpkejfuK+Z5/1uzICfPaZLtqw= github.com/aws/aws-sdk-go-v2/service/kms v1.17.5 h1:DkubF+BSEy0uX59+pYySzWFReN3fCcXobIO8L5Phh24= github.com/aws/aws-sdk-go-v2/service/kms v1.17.5/go.mod h1:ubAtMGRUMVv5kX8lpbeDguxZ64pR4kXTGApY4sCM0io= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.7 h1:W88E2kZGo+NHOsyvQbsOZYqxXJdLIqRzKadeVlv5J7k= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.7/go.mod h1:3ARttS6G6U3auEdKfaN4GlnfS9UxYE9nqub1+0YGycA= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.8 h1:eB91eEYUlh8+O2dXr189W8GJJd+/T8N/c5HocH2KzVo= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.8/go.mod h1:3ARttS6G6U3auEdKfaN4GlnfS9UxYE9nqub1+0YGycA= github.com/aws/aws-sdk-go-v2/service/sso v1.12.10 h1:UBQjaMTCKwyUYwiVnUt6toEJwGXsLBI6al083tpjJzY= github.com/aws/aws-sdk-go-v2/service/sso v1.12.10/go.mod h1:ouy2P4z6sJN70fR3ka3wD3Ro3KezSxU6eKGQI2+2fjI= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.10 h1:PkHIIJs8qvq0e5QybnZoG1K/9QTrLr9OsqCIo59jOBA= From da97e257d9621a5642dfb5c072bc3ddc33d17f81 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 May 2023 07:19:11 +0200 Subject: [PATCH 1611/2916] chore(deps): Bump github.com/google/go-containerregistry (#482) Bumps [github.com/google/go-containerregistry](https://github.com/google/go-containerregistry) from 0.15.1 to 0.15.2. - [Release notes](https://github.com/google/go-containerregistry/releases) - [Changelog](https://github.com/google/go-containerregistry/blob/main/.goreleaser.yml) - [Commits](https://github.com/google/go-containerregistry/compare/v0.15.1...v0.15.2) --- updated-dependencies: - dependency-name: github.com/google/go-containerregistry dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index d28c52fb1..21eccb931 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/go-playground/validator/v10 v10.13.0 github.com/gobwas/glob v0.2.3 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 - github.com/google/go-containerregistry v0.15.1 + github.com/google/go-containerregistry v0.15.2 github.com/hashicorp/vault/api v1.9.1 github.com/hexops/gotextdiff v1.0.3 github.com/imdario/mergo v0.3.15 diff --git a/go.sum b/go.sum index 8ac7cac3a..5f809c948 100644 --- a/go.sum +++ b/go.sum @@ -398,8 +398,8 @@ github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8 github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-containerregistry v0.15.1 h1:RsJ9NbfxYWF8Wl4VmvkpN3zYATwuvlPq2j20zmcs63E= -github.com/google/go-containerregistry v0.15.1/go.mod h1:wWK+LnOv4jXMM23IT/F1wdYftGWGr47Is8CG+pmHK1Q= +github.com/google/go-containerregistry v0.15.2 h1:MMkSh+tjSdnmJZO7ljvEqV1DjfekB6VUEAZgy3a+TQE= +github.com/google/go-containerregistry v0.15.2/go.mod h1:wWK+LnOv4jXMM23IT/F1wdYftGWGr47Is8CG+pmHK1Q= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= From 786baf47050bb626354177f305257deb0c696141 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 May 2023 07:19:49 +0200 Subject: [PATCH 1612/2916] chore(deps): Bump github.com/bitnami-labs/sealed-secrets (#480) Bumps [github.com/bitnami-labs/sealed-secrets](https://github.com/bitnami-labs/sealed-secrets) from 0.20.5 to 0.21.0. - [Release notes](https://github.com/bitnami-labs/sealed-secrets/releases) - [Changelog](https://github.com/bitnami-labs/sealed-secrets/blob/main/RELEASE-NOTES.md) - [Commits](https://github.com/bitnami-labs/sealed-secrets/compare/v0.20.5...v0.21.0) --- updated-dependencies: - dependency-name: github.com/bitnami-labs/sealed-secrets dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 21eccb931..0095a9bbc 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 github.com/Masterminds/semver/v3 v3.2.1 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d - github.com/bitnami-labs/sealed-secrets v0.20.5 + github.com/bitnami-labs/sealed-secrets v0.21.0 github.com/cyphar/filepath-securejoin v0.2.3 github.com/docker/distribution v2.8.2+incompatible github.com/fluxcd/pkg/kustomize v1.1.1 @@ -201,7 +201,7 @@ require ( github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.15.0 // indirect + github.com/prometheus/client_golang v1.15.1 // indirect github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect diff --git a/go.sum b/go.sum index 5f809c948..d29697d79 100644 --- a/go.sum +++ b/go.sum @@ -155,8 +155,8 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bitnami-labs/sealed-secrets v0.20.5 h1:ejHSbd7GcJOWgAjBnkn+QFPTFSsNOZAJOomS4bM/LWk= -github.com/bitnami-labs/sealed-secrets v0.20.5/go.mod h1:EAN3XxSWbJOi2AwF0wAEo0bCAReAD0ToTP9euRw5nVQ= +github.com/bitnami-labs/sealed-secrets v0.21.0 h1:oFghyEDmsPjXgEOgBnplBjA0NqpMUFMj3BNFs4E1lZ4= +github.com/bitnami-labs/sealed-secrets v0.21.0/go.mod h1:+nLA44ZWp/05ILUN7Fu3UiqNZJBwdTi/FHfEJ5btN7I= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= @@ -664,7 +664,7 @@ github.com/ohler55/ojg v1.18.5 h1:tzn5LJtkSyXowCo8SlGieU0zEc7WF4143Ri9MYlQy7Q= github.com/ohler55/ojg v1.18.5/go.mod h1:uHcD1ErbErC27Zhb5Df2jUjbseLLcmOCo6oxSr3jZxo= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU= +github.com/onsi/ginkgo/v2 v2.9.4 h1:xR7vG4IXt5RWx6FfIjyAtsoMAtnc3C/rFXBBd2AjZwE= github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= @@ -708,8 +708,8 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= -github.com/prometheus/client_golang v1.15.0 h1:5fCgGYogn0hFdhyhLbw7hEsWxufKtY9klyvdNfFlFhM= -github.com/prometheus/client_golang v1.15.0/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= +github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI= +github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= From 5f359220a068059668984841ccdc9379225e461a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 20 May 2023 22:04:05 +0200 Subject: [PATCH 1613/2916] chore(deps): Bump github.com/onsi/gomega from 1.27.6 to 1.27.7 (#489) Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.27.6 to 1.27.7. - [Release notes](https://github.com/onsi/gomega/releases) - [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md) - [Commits](https://github.com/onsi/gomega/compare/v1.27.6...v1.27.7) --- updated-dependencies: - dependency-name: github.com/onsi/gomega dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 0095a9bbc..ba7e8fa79 100644 --- a/go.mod +++ b/go.mod @@ -61,7 +61,7 @@ require ( github.com/google/uuid v1.3.0 github.com/hashicorp/go-multierror v1.1.1 github.com/huandu/xstrings v1.4.0 - github.com/onsi/gomega v1.27.6 + github.com/onsi/gomega v1.27.7 github.com/otiai10/copy v1.11.0 github.com/sergi/go-diff v1.3.1 go.mozilla.org/sops/v3 v3.7.4-0.20220901181616-9124783930b1 @@ -231,7 +231,7 @@ require ( golang.org/x/mod v0.10.0 // indirect golang.org/x/oauth2 v0.7.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.8.0 // indirect + golang.org/x/tools v0.9.1 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect google.golang.org/api v0.114.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index d29697d79..5226d801a 100644 --- a/go.sum +++ b/go.sum @@ -664,9 +664,9 @@ github.com/ohler55/ojg v1.18.5 h1:tzn5LJtkSyXowCo8SlGieU0zEc7WF4143Ri9MYlQy7Q= github.com/ohler55/ojg v1.18.5/go.mod h1:uHcD1ErbErC27Zhb5Df2jUjbseLLcmOCo6oxSr3jZxo= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo/v2 v2.9.4 h1:xR7vG4IXt5RWx6FfIjyAtsoMAtnc3C/rFXBBd2AjZwE= -github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= -github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= +github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= +github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU= +github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc3 h1:fzg1mXZFj8YdPeNkRXMg+zb88BFV0Ys52cJydRwBkb8= @@ -1185,8 +1185,8 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= -golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y= -golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= +golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= +golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 38f09f037c229ff576ba71e5f9aa7f8abb34b643 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 20 May 2023 22:04:19 +0200 Subject: [PATCH 1614/2916] chore(deps): Bump github.com/stretchr/testify from 1.8.2 to 1.8.3 (#488) Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.2 to 1.8.3. - [Release notes](https://github.com/stretchr/testify/releases) - [Commits](https://github.com/stretchr/testify/compare/v1.8.2...v1.8.3) --- updated-dependencies: - dependency-name: github.com/stretchr/testify dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index ba7e8fa79..4dae43c9f 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,7 @@ require ( github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.15.0 - github.com/stretchr/testify v1.8.2 + github.com/stretchr/testify v1.8.3 github.com/xanzy/ssh-agent v0.3.3 golang.org/x/crypto v0.9.0 golang.org/x/net v0.10.0 diff --git a/go.sum b/go.sum index 5226d801a..df231b097 100644 --- a/go.sum +++ b/go.sum @@ -808,8 +808,9 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= From c6475a23ca3667d08f013c3c52906ca724c05bc9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 20 May 2023 22:04:42 +0200 Subject: [PATCH 1615/2916] chore(deps): Bump k8s.io/client-go from 0.27.1 to 0.27.2 (#490) Bumps [k8s.io/client-go](https://github.com/kubernetes/client-go) from 0.27.1 to 0.27.2. - [Changelog](https://github.com/kubernetes/client-go/blob/master/CHANGELOG.md) - [Commits](https://github.com/kubernetes/client-go/compare/v0.27.1...v0.27.2) --- updated-dependencies: - dependency-name: k8s.io/client-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 4dae43c9f..5cdf9ca67 100644 --- a/go.mod +++ b/go.mod @@ -45,7 +45,7 @@ require ( k8s.io/api v0.27.2 k8s.io/apiextensions-apiserver v0.27.1 k8s.io/apimachinery v0.27.2 - k8s.io/client-go v0.27.1 + k8s.io/client-go v0.27.2 k8s.io/klog/v2 v2.100.1 sigs.k8s.io/kustomize/kyaml v0.14.2 sigs.k8s.io/structured-merge-diff/v4 v4.2.3 diff --git a/go.sum b/go.sum index df231b097..948d07f7c 100644 --- a/go.sum +++ b/go.sum @@ -1365,8 +1365,8 @@ k8s.io/apiserver v0.27.1 h1:phY+BtXjjzd+ta3a4kYbomC81azQSLa1K8jo9RBw7Lg= k8s.io/apiserver v0.27.1/go.mod h1:UGrOjLY2KsieA9Fw6lLiTObxTb8Z1xEba4uqSuMY0WU= k8s.io/cli-runtime v0.27.1 h1:MMzp5Q/Xmr5L1Lrowuc+Y/r95XINC6c6/fE3aN7JDRM= k8s.io/cli-runtime v0.27.1/go.mod h1:tEbTB1XP/nTH3wujsi52bw91gWpErtWiS15R6CwYsAI= -k8s.io/client-go v0.27.1 h1:oXsfhW/qncM1wDmWBIuDzRHNS2tLhK3BZv512Nc59W8= -k8s.io/client-go v0.27.1/go.mod h1:f8LHMUkVb3b9N8bWturc+EDtVVVwZ7ueTVquFAJb2vA= +k8s.io/client-go v0.27.2 h1:vDLSeuYvCHKeoQRhCXjxXO45nHVv2Ip4Fe0MfioMrhE= +k8s.io/client-go v0.27.2/go.mod h1:tY0gVmUsHrAmjzHX9zs7eCjxcBsf8IiNe7KQ52biTcQ= k8s.io/component-base v0.27.1 h1:kEB8p8lzi4gCs5f2SPU242vOumHJ6EOsOnDM3tTuDTM= k8s.io/component-base v0.27.1/go.mod h1:UGEd8+gxE4YWoigz5/lb3af3Q24w98pDseXcXZjw+E0= k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= From 59dd1f01985272a026f01583125694ce273038fd Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 22 May 2023 12:07:30 +0200 Subject: [PATCH 1616/2916] chore: Upgrade controller-runtime to v0.15.0-beta.0 --- e2e/test-utils/envtest_cluster_callback.go | 49 +++++++++++++++------- go.mod | 13 +++--- go.sum | 22 +++++----- 3 files changed, 49 insertions(+), 35 deletions(-) diff --git a/e2e/test-utils/envtest_cluster_callback.go b/e2e/test-utils/envtest_cluster_callback.go index d28a532a9..17d9518e0 100644 --- a/e2e/test-utils/envtest_cluster_callback.go +++ b/e2e/test-utils/envtest_cluster_callback.go @@ -7,10 +7,12 @@ import ( admissionv1 "k8s.io/api/admissionregistration/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" + "net" "net/http" "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/webhook" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + "time" ) type CallbackHandler func(request admission.Request) @@ -19,10 +21,10 @@ type CallbackHandlerEntry struct { Callback CallbackHandler } -func (k *EnvTestCluster) buildServeCallback(gvr schema.GroupVersionResource, cb CallbackHandler) http.Handler { +func (k *EnvTestCluster) buildServeCallback() http.Handler { wh := &webhook.Admission{ Handler: admission.HandlerFunc(func(ctx context.Context, request admission.Request) admission.Response { - k.serveCallback(gvr, request, cb) + k.handleWebhook(request) return admission.Allowed("") }), } @@ -32,18 +34,17 @@ func (k *EnvTestCluster) buildServeCallback(gvr schema.GroupVersionResource, cb return wh } -func (k *EnvTestCluster) serveCallback(gvr schema.GroupVersionResource, request admission.Request, cb CallbackHandler) { - if request.Resource.Group != gvr.Group || request.Resource.Version != gvr.Version || request.Resource.Resource != gvr.Resource { - return - } - - cb(request) -} - func (k *EnvTestCluster) startCallbackServer() error { - k.callbackServer.Host = k.env.WebhookInstallOptions.LocalServingHost - k.callbackServer.Port = k.env.WebhookInstallOptions.LocalServingPort - k.callbackServer.CertDir = k.env.WebhookInstallOptions.LocalServingCertDir + k.callbackServer = webhook.NewServer(webhook.Options{ + Host: k.env.WebhookInstallOptions.LocalServingHost, + Port: k.env.WebhookInstallOptions.LocalServingPort, + CertDir: k.env.WebhookInstallOptions.LocalServingCertDir, + }) + + for _, vwh := range k.env.WebhookInstallOptions.ValidatingWebhooks { + path := "/" + vwh.Name + k.callbackServer.Register(path, k.buildServeCallback()) + } ctx, cancel := context.WithCancel(context.Background()) k.callbackServerStop = cancel @@ -52,6 +53,25 @@ func (k *EnvTestCluster) startCallbackServer() error { _ = k.callbackServer.Start(ctx) }() + tcpAddr, err := net.ResolveTCPAddr("tcp", net.JoinHostPort(k.env.WebhookInstallOptions.LocalServingHost, fmt.Sprintf("%d", k.env.WebhookInstallOptions.LocalServingPort))) + if err != nil { + return err + } + + endTime := time.Now().Add(time.Second * 10) + for true { + if time.Now().After(endTime) { + return fmt.Errorf("timeout while waiting for webhook server") + } + c, err := net.DialTCP("tcp", nil, tcpAddr) + if err != nil { + time.Sleep(100 * time.Millisecond) + continue + } + _ = c.Close() + break + } + return nil } @@ -71,7 +91,6 @@ func (k *EnvTestCluster) InitWebhookCallback(gvr schema.GroupVersionResource, is group = "none" } name := fmt.Sprintf("%s-%s-%s-callback", group, gvr.Version, gvr.Resource) - path := "/" + name k.env.WebhookInstallOptions.ValidatingWebhooks = append(k.env.WebhookInstallOptions.ValidatingWebhooks, &admissionv1.ValidatingWebhookConfiguration{ ObjectMeta: metav1.ObjectMeta{ @@ -108,8 +127,6 @@ func (k *EnvTestCluster) InitWebhookCallback(gvr schema.GroupVersionResource, is }, }, }) - - k.callbackServer.Register(path, k.buildServeCallback(gvr, k.handleWebhook)) } func (k *EnvTestCluster) handleWebhook(request admission.Request) { diff --git a/go.mod b/go.mod index 5cdf9ca67..a815bcfcb 100644 --- a/go.mod +++ b/go.mod @@ -43,7 +43,7 @@ require ( golang.org/x/text v0.9.0 helm.sh/helm/v3 v3.12.0 k8s.io/api v0.27.2 - k8s.io/apiextensions-apiserver v0.27.1 + k8s.io/apiextensions-apiserver v0.27.2 k8s.io/apimachinery v0.27.2 k8s.io/client-go v0.27.2 k8s.io/klog/v2 v2.100.1 @@ -65,14 +65,11 @@ require ( github.com/otiai10/copy v1.11.0 github.com/sergi/go-diff v1.3.1 go.mozilla.org/sops/v3 v3.7.4-0.20220901181616-9124783930b1 - sigs.k8s.io/controller-runtime v0.14.5 + sigs.k8s.io/controller-runtime v0.15.0-beta.0 sigs.k8s.io/kustomize/api v0.13.4 sigs.k8s.io/yaml v1.3.0 ) -// TODO: when controller-runtime past v0.14.6 is released, remove this line -replace sigs.k8s.io/controller-runtime => sigs.k8s.io/controller-runtime v0.13.1-0.20230426175615-dca0be70fd22 - require ( cloud.google.com/go/compute v1.19.1 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect @@ -202,7 +199,7 @@ require ( github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.15.1 // indirect - github.com/prometheus/client_model v0.3.0 // indirect + github.com/prometheus/client_model v0.4.0 // indirect github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect github.com/rivo/uniseg v0.4.3 // indirect @@ -244,9 +241,9 @@ require ( gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiserver v0.27.1 // indirect + k8s.io/apiserver v0.27.2 // indirect k8s.io/cli-runtime v0.27.1 // indirect - k8s.io/component-base v0.27.1 // indirect + k8s.io/component-base v0.27.2 // indirect k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect k8s.io/kubectl v0.27.1 // indirect k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 // indirect diff --git a/go.sum b/go.sum index 948d07f7c..40eb65340 100644 --- a/go.sum +++ b/go.sum @@ -303,7 +303,7 @@ github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= +github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonreference v0.20.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTrLC1F86HID8= @@ -713,8 +713,8 @@ github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= +github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= @@ -1357,18 +1357,18 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.27.2 h1:+H17AJpUMvl+clT+BPnKf0E3ksMAzoBBg7CntpSuADo= k8s.io/api v0.27.2/go.mod h1:ENmbocXfBT2ADujUXcBhHV55RIT31IIEvkntP6vZKS4= -k8s.io/apiextensions-apiserver v0.27.1 h1:Hp7B3KxKHBZ/FxmVFVpaDiXI6CCSr49P1OJjxKO6o4g= -k8s.io/apiextensions-apiserver v0.27.1/go.mod h1:8jEvRDtKjVtWmdkhOqE84EcNWJt/uwF8PC4627UZghY= +k8s.io/apiextensions-apiserver v0.27.2 h1:iwhyoeS4xj9Y7v8YExhUwbVuBhMr3Q4bd/laClBV6Bo= +k8s.io/apiextensions-apiserver v0.27.2/go.mod h1:Oz9UdvGguL3ULgRdY9QMUzL2RZImotgxvGjdWRq6ZXQ= k8s.io/apimachinery v0.27.2 h1:vBjGaKKieaIreI+oQwELalVG4d8f3YAMNpWLzDXkxeg= k8s.io/apimachinery v0.27.2/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E= -k8s.io/apiserver v0.27.1 h1:phY+BtXjjzd+ta3a4kYbomC81azQSLa1K8jo9RBw7Lg= -k8s.io/apiserver v0.27.1/go.mod h1:UGrOjLY2KsieA9Fw6lLiTObxTb8Z1xEba4uqSuMY0WU= +k8s.io/apiserver v0.27.2 h1:p+tjwrcQEZDrEorCZV2/qE8osGTINPuS5ZNqWAvKm5E= +k8s.io/apiserver v0.27.2/go.mod h1:EsOf39d75rMivgvvwjJ3OW/u9n1/BmUMK5otEOJrb1Y= k8s.io/cli-runtime v0.27.1 h1:MMzp5Q/Xmr5L1Lrowuc+Y/r95XINC6c6/fE3aN7JDRM= k8s.io/cli-runtime v0.27.1/go.mod h1:tEbTB1XP/nTH3wujsi52bw91gWpErtWiS15R6CwYsAI= k8s.io/client-go v0.27.2 h1:vDLSeuYvCHKeoQRhCXjxXO45nHVv2Ip4Fe0MfioMrhE= k8s.io/client-go v0.27.2/go.mod h1:tY0gVmUsHrAmjzHX9zs7eCjxcBsf8IiNe7KQ52biTcQ= -k8s.io/component-base v0.27.1 h1:kEB8p8lzi4gCs5f2SPU242vOumHJ6EOsOnDM3tTuDTM= -k8s.io/component-base v0.27.1/go.mod h1:UGEd8+gxE4YWoigz5/lb3af3Q24w98pDseXcXZjw+E0= +k8s.io/component-base v0.27.2 h1:neju+7s/r5O4x4/txeUONNTS9r1HsPbyoPBAtHsDCpo= +k8s.io/component-base v0.27.2/go.mod h1:5UPk7EjfgrfgRIuDBFtsEFAe4DAvP3U+M8RTzoSJkpo= k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5FJ2kxm1WrQFanWchyKuqGg= @@ -1383,8 +1383,8 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8 rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/controller-runtime v0.13.1-0.20230426175615-dca0be70fd22 h1:KnAZ+ITT57UpjlUM9AsuuPzzF0OjZAhtSgEPeQUHRzg= -sigs.k8s.io/controller-runtime v0.13.1-0.20230426175615-dca0be70fd22/go.mod h1:ujEX5tSkpg5cCOhcwDWLsXwNuMCO+j4rpmmkIn6BGGc= +sigs.k8s.io/controller-runtime v0.15.0-beta.0 h1:pkhYMops8jZrVuI0kBHeF6q9UVu1JljIGGG4Ox5ZJmk= +sigs.k8s.io/controller-runtime v0.15.0-beta.0/go.mod h1:YUTa+du31rqOu4mJaijiuhGFax9ecCJgO/v0/yW09gE= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/kustomize/api v0.13.4 h1:E38Hfx0G9R9v7vRgKshviPotJQETG0S2gD3JdHLCAsI= From 69f9ed1d67efcc4d89cff9fadf3eff51d4c08e57 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 22 May 2023 14:23:47 +0200 Subject: [PATCH 1617/2916] fix: Ignore AlreadyExists errors in ensureWriteNamespace --- pkg/results/result-store-secrets.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/results/result-store-secrets.go b/pkg/results/result-store-secrets.go index 7edb7a87d..206c3c2d0 100644 --- a/pkg/results/result-store-secrets.go +++ b/pkg/results/result-store-secrets.go @@ -132,7 +132,7 @@ func (s *ResultStoreSecrets) ensureWriteNamespace() error { }, } err = s.client.Create(s.ctx, ns) - if err != nil { + if err != nil && !errors.IsAlreadyExists(err) { return err } } else if err != nil { From 40993d0ee3a6c5dc4ab3d941ad44e79e10044882 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 12 May 2023 15:40:45 +0200 Subject: [PATCH 1618/2916] refactor: Move GitUrl into types package --- cmd/kluctl/commands/utils.go | 9 ++++----- e2e/git_include_test.go | 8 ++++---- pkg/git/auth/auth_provider.go | 6 +++--- pkg/git/auth/env_auth_provider.go | 4 ++-- pkg/git/auth/git_credentials_file.go | 8 ++++---- pkg/git/auth/list_auth_provider.go | 4 ++-- pkg/git/auth/ssh_auth_provider.go | 12 ++++++------ pkg/git/{git-url => }/giturls/LICENSE | 0 pkg/git/{git-url => }/giturls/urls.go | 0 pkg/git/{git-url => }/giturls/urls_test.go | 0 pkg/git/list_refs.go | 8 ++++---- pkg/git/mirrored_repo.go | 10 +++++----- pkg/repocache/cache.go | 10 +++++----- pkg/types/git_project.go | 7 +++---- pkg/{git/git-url/url.go => types/git_url.go} | 18 +++++++++++++----- pkg/types/result/command_result.go | 11 +++++------ pkg/types/vars_source.go | 7 +++---- pkg/types/{url.go => yaml_url.go} | 0 pkg/vars/vars_loader_test.go | 7 +++---- 19 files changed, 66 insertions(+), 63 deletions(-) rename pkg/git/{git-url => }/giturls/LICENSE (100%) rename pkg/git/{git-url => }/giturls/urls.go (100%) rename pkg/git/{git-url => }/giturls/urls_test.go (100%) rename pkg/{git/git-url/url.go => types/git_url.go} (86%) rename pkg/types/{url.go => yaml_url.go} (100%) diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 949db7a20..046f5609c 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -17,7 +17,6 @@ import ( "github.com/kluctl/kluctl/v2/pkg/deployment" "github.com/kluctl/kluctl/v2/pkg/git" "github.com/kluctl/kluctl/v2/pkg/git/auth" - git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/git/messages" ssh_pool "github.com/kluctl/kluctl/v2/pkg/git/ssh-pool" "github.com/kluctl/kluctl/v2/pkg/kluctl_jinja2" @@ -332,10 +331,10 @@ func addGitInfo(r *result.CommandResult, ctx *commandCtx) error { return err } - var originUrl *git_url.GitUrl + var originUrl *types.GitUrl for _, r := range remotes { if r.Config().Name == "origin" { - originUrl, err = git_url.Parse(r.Config().URLs[0]) + originUrl, err = types.ParseGitUrl(r.Config().URLs[0]) if err != nil { return err } @@ -411,11 +410,11 @@ func parseRepoOverride(s string, isGroup bool) (ret repocache.RepoOverride, err return repocache.RepoOverride{}, fmt.Errorf("%s", s) } - u, err := git_url.Parse(sp[0]) + u, err := types.ParseGitUrl(sp[0]) if err != nil { // we need to prepend a dummy scheme to the repo key so that it is properly parsed dummyUrl := fmt.Sprintf("git://%s", sp[0]) - u, err = git_url.Parse(dummyUrl) + u, err = types.ParseGitUrl(dummyUrl) if err != nil { return repocache.RepoOverride{}, fmt.Errorf("%s: %w", s, err) } diff --git a/e2e/git_include_test.go b/e2e/git_include_test.go index fc24635e1..5330ff33f 100644 --- a/e2e/git_include_test.go +++ b/e2e/git_include_test.go @@ -4,7 +4,7 @@ import ( "fmt" test_utils "github.com/kluctl/kluctl/v2/e2e/test-utils" git2 "github.com/kluctl/kluctl/v2/pkg/git" - git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" + "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" cp "github.com/otiai10/copy" @@ -88,8 +88,8 @@ func TestLocalGitOverride(t *testing.T) { _ = cm.SetNestedField("o2", "data", "a") _ = yaml.WriteYamlFile(filepath.Join(override2, "subDir", "cm", "configmap-include2-cm.yml"), cm) - u1, _ := git_url.Parse(ip1.GitUrl()) - u2, _ := git_url.Parse(ip2.GitUrl()) + u1, _ := types.ParseGitUrl(ip1.GitUrl()) + u2, _ := types.ParseGitUrl(ip2.GitUrl()) k1 := u1.NormalizedRepoKey() k2 := u2.NormalizedRepoKey() @@ -129,7 +129,7 @@ func TestLocalGitGroupOverride(t *testing.T) { _ = cm.SetNestedField("o2", "data", "a") _ = yaml.WriteYamlFile(filepath.Join(override2, "subDir", "cm", "configmap-include2-cm.yml"), cm) - u1, _ := git_url.Parse(p.GitServer().GitUrl() + "/repos") + u1, _ := types.ParseGitUrl(p.GitServer().GitUrl() + "/repos") k1 := u1.NormalizedRepoKey() p.KluctlMust("deploy", "--yes", "-t", "test", diff --git a/pkg/git/auth/auth_provider.go b/pkg/git/auth/auth_provider.go index ab2607dba..8e9c3430b 100644 --- a/pkg/git/auth/auth_provider.go +++ b/pkg/git/auth/auth_provider.go @@ -5,8 +5,8 @@ import ( "fmt" "github.com/go-git/go-git/v5/plumbing/transport" ssh2 "github.com/go-git/go-git/v5/plumbing/transport/ssh" - git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/git/messages" + "github.com/kluctl/kluctl/v2/pkg/types" "golang.org/x/crypto/ssh" ) @@ -26,7 +26,7 @@ func (a *AuthMethodAndCA) SshClientConfig() (*ssh.ClientConfig, error) { } type GitAuthProvider interface { - BuildAuth(ctx context.Context, gitUrl git_url.GitUrl) AuthMethodAndCA + BuildAuth(ctx context.Context, gitUrl types.GitUrl) AuthMethodAndCA } type GitAuthProviders struct { @@ -41,7 +41,7 @@ func (a *GitAuthProviders) RegisterAuthProvider(p GitAuthProvider, last bool) { } } -func (a *GitAuthProviders) BuildAuth(ctx context.Context, gitUrl git_url.GitUrl) AuthMethodAndCA { +func (a *GitAuthProviders) BuildAuth(ctx context.Context, gitUrl types.GitUrl) AuthMethodAndCA { for _, p := range a.authProviders { auth := p.BuildAuth(ctx, gitUrl) if auth.AuthMethod != nil { diff --git a/pkg/git/auth/env_auth_provider.go b/pkg/git/auth/env_auth_provider.go index d88c54e89..359aaa224 100644 --- a/pkg/git/auth/env_auth_provider.go +++ b/pkg/git/auth/env_auth_provider.go @@ -3,8 +3,8 @@ package auth import ( "context" "fmt" - git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/git/messages" + "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" "os" ) @@ -15,7 +15,7 @@ type GitEnvAuthProvider struct { Prefix string } -func (a *GitEnvAuthProvider) BuildAuth(ctx context.Context, gitUrl git_url.GitUrl) AuthMethodAndCA { +func (a *GitEnvAuthProvider) BuildAuth(ctx context.Context, gitUrl types.GitUrl) AuthMethodAndCA { var la ListAuthProvider for _, m := range utils.ParseEnvConfigSets(a.Prefix) { diff --git a/pkg/git/auth/git_credentials_file.go b/pkg/git/auth/git_credentials_file.go index cd9231154..58fff60c3 100644 --- a/pkg/git/auth/git_credentials_file.go +++ b/pkg/git/auth/git_credentials_file.go @@ -4,9 +4,9 @@ import ( "bufio" "context" "github.com/go-git/go-git/v5/plumbing/transport/http" - git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" - "github.com/kluctl/kluctl/v2/pkg/git/git-url/giturls" + "github.com/kluctl/kluctl/v2/pkg/git/giturls" "github.com/kluctl/kluctl/v2/pkg/git/messages" + "github.com/kluctl/kluctl/v2/pkg/types" "net/url" "os" "path/filepath" @@ -16,7 +16,7 @@ type GitCredentialsFileAuthProvider struct { MessageCallbacks messages.MessageCallbacks } -func (a *GitCredentialsFileAuthProvider) BuildAuth(ctx context.Context, gitUrl git_url.GitUrl) AuthMethodAndCA { +func (a *GitCredentialsFileAuthProvider) BuildAuth(ctx context.Context, gitUrl types.GitUrl) AuthMethodAndCA { if gitUrl.Scheme != "http" && gitUrl.Scheme != "https" { return AuthMethodAndCA{} } @@ -46,7 +46,7 @@ func (a *GitCredentialsFileAuthProvider) BuildAuth(ctx context.Context, gitUrl g return AuthMethodAndCA{} } -func (a *GitCredentialsFileAuthProvider) tryBuildAuth(gitUrl git_url.GitUrl, gitCredentialsPath string) *AuthMethodAndCA { +func (a *GitCredentialsFileAuthProvider) tryBuildAuth(gitUrl types.GitUrl, gitCredentialsPath string) *AuthMethodAndCA { st, err := os.Stat(gitCredentialsPath) if err != nil || st.Mode().IsDir() { return nil diff --git a/pkg/git/auth/list_auth_provider.go b/pkg/git/auth/list_auth_provider.go index 0e2d2b453..b69f0b2df 100644 --- a/pkg/git/auth/list_auth_provider.go +++ b/pkg/git/auth/list_auth_provider.go @@ -4,8 +4,8 @@ import ( "context" "github.com/go-git/go-git/v5/plumbing/transport/http" "github.com/go-git/go-git/v5/plumbing/transport/ssh" - "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/git/messages" + "github.com/kluctl/kluctl/v2/pkg/types" ssh2 "golang.org/x/crypto/ssh" "strings" ) @@ -54,7 +54,7 @@ func (a *ListAuthProvider) AddEntry(e AuthEntry) { a.entries = append(a.entries, e) } -func (a *ListAuthProvider) BuildAuth(ctx context.Context, gitUrl git_url.GitUrl) AuthMethodAndCA { +func (a *ListAuthProvider) BuildAuth(ctx context.Context, gitUrl types.GitUrl) AuthMethodAndCA { a.MessageCallbacks.Trace("ListAuthProvider: BuildAuth for %s", gitUrl.String()) a.MessageCallbacks.Trace("ListAuthProvider: path=%s, username=%s, scheme=%s", gitUrl.Path, gitUrl.User.Username(), gitUrl.Scheme) for _, e := range a.entries { diff --git a/pkg/git/auth/ssh_auth_provider.go b/pkg/git/auth/ssh_auth_provider.go index 313e342ef..7793f5747 100644 --- a/pkg/git/auth/ssh_auth_provider.go +++ b/pkg/git/auth/ssh_auth_provider.go @@ -6,8 +6,8 @@ import ( "encoding/binary" "fmt" "github.com/kevinburke/ssh_config" - git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/git/messages" + "github.com/kluctl/kluctl/v2/pkg/types" sshagent "github.com/xanzy/ssh-agent" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/agent" @@ -56,7 +56,7 @@ func (a *sshDefaultIdentityAndAgent) Signers() ([]ssh.Signer, error) { return a.signers, nil } -func (a *sshDefaultIdentityAndAgent) addDefaultIdentities(gitUrl git_url.GitUrl) { +func (a *sshDefaultIdentityAndAgent) addDefaultIdentities(gitUrl types.GitUrl) { a.authProvider.MessageCallbacks.Trace("trying to add default identity") u, err := user.Current() if err != nil { @@ -82,7 +82,7 @@ func (a *sshDefaultIdentityAndAgent) addDefaultIdentities(gitUrl git_url.GitUrl) doAdd("id_dsa") } -func (a *sshDefaultIdentityAndAgent) addConfigIdentities(gitUrl git_url.GitUrl) { +func (a *sshDefaultIdentityAndAgent) addConfigIdentities(gitUrl types.GitUrl) { a.authProvider.MessageCallbacks.Trace("trying to add identities from ssh config") for _, id := range ssh_config.GetAll(gitUrl.Hostname(), "IdentityFile") { expanded := expandHomeDir(id) @@ -97,7 +97,7 @@ func (a *sshDefaultIdentityAndAgent) addConfigIdentities(gitUrl git_url.GitUrl) } } -func (a *sshDefaultIdentityAndAgent) createAgent(gitUrl git_url.GitUrl) (agent.Agent, error) { +func (a *sshDefaultIdentityAndAgent) createAgent(gitUrl types.GitUrl) (agent.Agent, error) { if runtime.GOOS == "windows" { a, _, err := sshagent.New() return a, err @@ -125,7 +125,7 @@ func (a *sshDefaultIdentityAndAgent) createAgent(gitUrl git_url.GitUrl) (agent.A return agent.NewClient(conn), nil } -func (a *sshDefaultIdentityAndAgent) addAgentIdentities(gitUrl git_url.GitUrl) { +func (a *sshDefaultIdentityAndAgent) addAgentIdentities(gitUrl types.GitUrl) { a.authProvider.MessageCallbacks.Trace("trying to add agent keys") agent, err := a.createAgent(gitUrl) if err != nil { @@ -144,7 +144,7 @@ func (a *sshDefaultIdentityAndAgent) addAgentIdentities(gitUrl git_url.GitUrl) { } } -func (a *GitSshAuthProvider) BuildAuth(ctx context.Context, gitUrl git_url.GitUrl) AuthMethodAndCA { +func (a *GitSshAuthProvider) BuildAuth(ctx context.Context, gitUrl types.GitUrl) AuthMethodAndCA { if !gitUrl.IsSsh() { return AuthMethodAndCA{} } diff --git a/pkg/git/git-url/giturls/LICENSE b/pkg/git/giturls/LICENSE similarity index 100% rename from pkg/git/git-url/giturls/LICENSE rename to pkg/git/giturls/LICENSE diff --git a/pkg/git/git-url/giturls/urls.go b/pkg/git/giturls/urls.go similarity index 100% rename from pkg/git/git-url/giturls/urls.go rename to pkg/git/giturls/urls.go diff --git a/pkg/git/git-url/giturls/urls_test.go b/pkg/git/giturls/urls_test.go similarity index 100% rename from pkg/git/git-url/giturls/urls_test.go rename to pkg/git/giturls/urls_test.go diff --git a/pkg/git/list_refs.go b/pkg/git/list_refs.go index 735234d6b..d5a199fc5 100644 --- a/pkg/git/list_refs.go +++ b/pkg/git/list_refs.go @@ -10,13 +10,13 @@ import ( "github.com/go-git/go-git/v5/plumbing/protocol/packp" "github.com/go-git/go-git/v5/storage/memory" auth2 "github.com/kluctl/kluctl/v2/pkg/git/auth" - git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" ssh_pool "github.com/kluctl/kluctl/v2/pkg/git/ssh-pool" + "github.com/kluctl/kluctl/v2/pkg/types" "strconv" ) // ListRemoteRefsFastSsh will reuse existing ssh connections from a pool -func ListRemoteRefsFastSsh(ctx context.Context, url git_url.GitUrl, sshPool *ssh_pool.SshPool, auth auth2.AuthMethodAndCA) ([]*plumbing.Reference, error) { +func ListRemoteRefsFastSsh(ctx context.Context, url types.GitUrl, sshPool *ssh_pool.SshPool, auth auth2.AuthMethodAndCA) ([]*plumbing.Reference, error) { var portInt int64 = 22 if url.Port() != "" { var err error @@ -75,7 +75,7 @@ func ListRemoteRefsFastSsh(ctx context.Context, url git_url.GitUrl, sshPool *ssh return resultRefs, nil } -func ListRemoteRefsSlow(ctx context.Context, url git_url.GitUrl, auth auth2.AuthMethodAndCA) ([]*plumbing.Reference, error) { +func ListRemoteRefsSlow(ctx context.Context, url types.GitUrl, auth auth2.AuthMethodAndCA) ([]*plumbing.Reference, error) { storage := memory.NewStorage() remote := git.NewRemote(storage, &config.RemoteConfig{ Name: "origin", @@ -93,7 +93,7 @@ func ListRemoteRefsSlow(ctx context.Context, url git_url.GitUrl, auth auth2.Auth return remoteRefs, nil } -func ListRemoteRefs(ctx context.Context, url git_url.GitUrl, sshPool *ssh_pool.SshPool, auth auth2.AuthMethodAndCA) ([]*plumbing.Reference, error) { +func ListRemoteRefs(ctx context.Context, url types.GitUrl, sshPool *ssh_pool.SshPool, auth auth2.AuthMethodAndCA) ([]*plumbing.Reference, error) { if url.IsSsh() { refs, err := ListRemoteRefsFastSsh(ctx, url, sshPool, auth) if err == nil { diff --git a/pkg/git/mirrored_repo.go b/pkg/git/mirrored_repo.go index 10e2e805c..b00becabb 100644 --- a/pkg/git/mirrored_repo.go +++ b/pkg/git/mirrored_repo.go @@ -10,9 +10,9 @@ import ( "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/object" auth2 "github.com/kluctl/kluctl/v2/pkg/git/auth" - git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" _ "github.com/kluctl/kluctl/v2/pkg/git/ssh-pool" ssh_pool "github.com/kluctl/kluctl/v2/pkg/git/ssh-pool" + "github.com/kluctl/kluctl/v2/pkg/types" "github.com/rogpeppe/go-internal/lockedfile" "os" "path/filepath" @@ -33,7 +33,7 @@ type MirroredGitRepo struct { sshPool *ssh_pool.SshPool authProviders *auth2.GitAuthProviders - url git_url.GitUrl + url types.GitUrl mirrorDir string hasUpdated bool @@ -44,7 +44,7 @@ type MirroredGitRepo struct { mutex sync.Mutex } -func NewMirroredGitRepo(ctx context.Context, u git_url.GitUrl, baseDir string, sshPool *ssh_pool.SshPool, authProviders *auth2.GitAuthProviders) (*MirroredGitRepo, error) { +func NewMirroredGitRepo(ctx context.Context, u types.GitUrl, baseDir string, sshPool *ssh_pool.SshPool, authProviders *auth2.GitAuthProviders) (*MirroredGitRepo, error) { mirrorRepoName := buildMirrorRepoName(u) o := &MirroredGitRepo{ ctx: ctx, @@ -69,7 +69,7 @@ func NewMirroredGitRepo(ctx context.Context, u git_url.GitUrl, baseDir string, s return o, nil } -func (g *MirroredGitRepo) Url() git_url.GitUrl { +func (g *MirroredGitRepo) Url() types.GitUrl { return g.url } @@ -389,7 +389,7 @@ func (g *MirroredGitRepo) GetGitTreeByCommit(commitHash string) (*object.Tree, e return tree, nil } -func buildMirrorRepoName(u git_url.GitUrl) string { +func buildMirrorRepoName(u types.GitUrl) string { h := sha256.New() h.Write([]byte(u.String())) h2 := hex.EncodeToString(h.Sum(nil)) diff --git a/pkg/repocache/cache.go b/pkg/repocache/cache.go index b3ca0907f..a31d37938 100644 --- a/pkg/repocache/cache.go +++ b/pkg/repocache/cache.go @@ -3,6 +3,7 @@ package repocache import ( "context" "fmt" + "github.com/kluctl/kluctl/v2/pkg/types" "os" "path" "path/filepath" @@ -12,7 +13,6 @@ import ( "github.com/kluctl/kluctl/v2/pkg/git" "github.com/kluctl/kluctl/v2/pkg/git/auth" - git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" ssh_pool "github.com/kluctl/kluctl/v2/pkg/git/ssh-pool" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/utils" @@ -36,7 +36,7 @@ type GitRepoCache struct { type CacheEntry struct { rp *GitRepoCache - url git_url.GitUrl + url types.GitUrl mr *git.MirroredGitRepo defaultRef string refs map[string]string @@ -47,13 +47,13 @@ type CacheEntry struct { } type RepoInfo struct { - Url git_url.GitUrl `json:"url"` + Url types.GitUrl `json:"url"` RemoteRefs map[string]string `json:"remoteRefs"` DefaultRef string `json:"defaultRef"` } type RepoOverride struct { - RepoUrl git_url.GitUrl + RepoUrl types.GitUrl Ref string Override string IsGroup bool @@ -85,7 +85,7 @@ func (rp *GitRepoCache) Clear() { rp.cleanupDirs = nil } -func (rp *GitRepoCache) GetEntry(url git_url.GitUrl) (*CacheEntry, error) { +func (rp *GitRepoCache) GetEntry(url types.GitUrl) (*CacheEntry, error) { rp.reposMutex.Lock() defer rp.reposMutex.Unlock() diff --git a/pkg/types/git_project.go b/pkg/types/git_project.go index e43997353..32a785541 100644 --- a/pkg/types/git_project.go +++ b/pkg/types/git_project.go @@ -7,7 +7,6 @@ import ( "strings" "github.com/go-playground/validator/v10" - git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/yaml" ) @@ -15,9 +14,9 @@ import ( var gitDirPatternNeg = regexp.MustCompile(`[\\\/:\*?"<>|[:cntrl:]\0^]`) type GitProject struct { - Url git_url.GitUrl `json:"url" validate:"required"` - Ref string `json:"ref,omitempty"` - SubDir string `json:"subDir,omitempty"` + Url GitUrl `json:"url" validate:"required"` + Ref string `json:"ref,omitempty"` + SubDir string `json:"subDir,omitempty"` } func (gp *GitProject) UnmarshalJSON(b []byte) error { diff --git a/pkg/git/git-url/url.go b/pkg/types/git_url.go similarity index 86% rename from pkg/git/git-url/url.go rename to pkg/types/git_url.go index 57f5cfa0f..f7fa8c6a7 100644 --- a/pkg/git/git-url/url.go +++ b/pkg/types/git_url.go @@ -1,18 +1,18 @@ -package git_url +package types import ( "encoding/json" "fmt" - "github.com/kluctl/kluctl/v2/pkg/git/git-url/giturls" + "github.com/kluctl/kluctl/v2/pkg/git/giturls" "net/url" "strings" ) type GitUrl struct { - url.URL + url.URL `json:"-"` } -func Parse(u string) (*GitUrl, error) { +func ParseGitUrl(u string) (*GitUrl, error) { u2, err := giturls.Parse(u) if err != nil { return nil, err @@ -20,13 +20,21 @@ func Parse(u string) (*GitUrl, error) { return &GitUrl{*u2}, nil } +func ParseGitUrlMust(u string) *GitUrl { + u2, err := ParseGitUrl(u) + if err != nil { + panic(err) + } + return u2 +} + func (u *GitUrl) UnmarshalJSON(b []byte) error { var s string err := json.Unmarshal(b, &s) if err != nil { return err } - u2, err := Parse(s) + u2, err := ParseGitUrl(s) if err != nil { return err } diff --git a/pkg/types/result/command_result.go b/pkg/types/result/command_result.go index 06cee1183..4d2ec8dcb 100644 --- a/pkg/types/result/command_result.go +++ b/pkg/types/result/command_result.go @@ -1,7 +1,6 @@ package result import ( - git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/utils/uo" @@ -97,11 +96,11 @@ type CommandInfo struct { } type GitInfo struct { - Url *git_url.GitUrl `json:"url"` - Ref string `json:"ref"` - SubDir string `json:"subDir"` - Commit string `json:"commit"` - Dirty bool `json:"dirty"` + Url *types.GitUrl `json:"url"` + Ref string `json:"ref"` + SubDir string `json:"subDir"` + Commit string `json:"commit"` + Dirty bool `json:"dirty"` } type ClusterInfo struct { diff --git a/pkg/types/vars_source.go b/pkg/types/vars_source.go index 6e09d549c..7eacffe4e 100644 --- a/pkg/types/vars_source.go +++ b/pkg/types/vars_source.go @@ -2,16 +2,15 @@ package types import ( "github.com/go-playground/validator/v10" - git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" "reflect" ) type VarsSourceGit struct { - Url git_url.GitUrl `json:"url" validate:"required"` - Ref string `json:"ref,omitempty"` - Path string `json:"path" validate:"required"` + Url GitUrl `json:"url" validate:"required"` + Ref string `json:"ref,omitempty"` + Path string `json:"path" validate:"required"` } type VarsSourceClusterConfigMapOrSecret struct { diff --git a/pkg/types/url.go b/pkg/types/yaml_url.go similarity index 100% rename from pkg/types/url.go rename to pkg/types/yaml_url.go diff --git a/pkg/vars/vars_loader_test.go b/pkg/vars/vars_loader_test.go index b119e5278..205a68c2c 100644 --- a/pkg/vars/vars_loader_test.go +++ b/pkg/vars/vars_loader_test.go @@ -16,7 +16,6 @@ import ( "github.com/go-git/go-git/v5/plumbing" "github.com/kluctl/kluctl/v2/pkg/git" "github.com/kluctl/kluctl/v2/pkg/git/auth" - git_url "github.com/kluctl/kluctl/v2/pkg/git/git-url" ssh_pool "github.com/kluctl/kluctl/v2/pkg/git/ssh-pool" "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/repocache" @@ -223,7 +222,7 @@ func TestVarsLoader_Git(t *testing.T) { }, "") testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { - url, _ := git_url.Parse(gs.GitRepoUrl("repo")) + url, _ := types.ParseGitUrl(gs.GitRepoUrl("repo")) err := vl.LoadVars(vc, &types.VarsSource{ Git: &types.VarsSourceGit{ Url: *url, @@ -237,7 +236,7 @@ func TestVarsLoader_Git(t *testing.T) { }) testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { - url, _ := git_url.Parse(gs.GitRepoUrl("repo")) + url, _ := types.ParseGitUrl(gs.GitRepoUrl("repo")) b := true err := vl.LoadVars(vc, &types.VarsSource{ IgnoreMissing: &b, @@ -274,7 +273,7 @@ func TestVarsLoader_GitBranch(t *testing.T) { assert.NoError(t, err) testVarsLoader(t, func(vl *VarsLoader, vc *VarsCtx, aws *aws.FakeAwsClientFactory) { - url, _ := git_url.Parse(gs.GitRepoUrl("repo")) + url, _ := types.ParseGitUrl(gs.GitRepoUrl("repo")) err = vl.LoadVars(vc, &types.VarsSource{ Git: &types.VarsSourceGit{ Url: *url, From 6f4ecf337a79d11b0fea532ff8cc917ddd8c5787 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 12 May 2023 16:28:30 +0200 Subject: [PATCH 1619/2916] refactor: Pass targetCtx to command objects --- cmd/kluctl/commands/cmd_delete.go | 8 ++--- cmd/kluctl/commands/cmd_deploy.go | 4 +-- cmd/kluctl/commands/cmd_diff.go | 4 +-- cmd/kluctl/commands/cmd_poke_images.go | 4 +-- cmd/kluctl/commands/cmd_prune.go | 4 +-- pkg/deployment/commands/delete.go | 32 ++++++++--------- pkg/deployment/commands/deploy.go | 48 ++++++++++++-------------- pkg/deployment/commands/diff.go | 34 ++++++++---------- pkg/deployment/commands/poke_images.go | 32 ++++++++--------- pkg/deployment/commands/prune.go | 20 +++++------ pkg/kluctl_project/target_context.go | 2 +- 11 files changed, 89 insertions(+), 103 deletions(-) diff --git a/cmd/kluctl/commands/cmd_delete.go b/cmd/kluctl/commands/cmd_delete.go index 70d455375..28aafcd2d 100644 --- a/cmd/kluctl/commands/cmd_delete.go +++ b/cmd/kluctl/commands/cmd_delete.go @@ -48,13 +48,9 @@ func (cmd *deleteCmd) Run(ctx context.Context) error { } startTime := time.Now() return withProjectCommandContext(ctx, ptArgs, func(cmdCtx *commandCtx) error { - discriminator := cmdCtx.targetCtx.Target.Discriminator - if cmd.Discriminator != "" { - discriminator = cmd.Discriminator - } - cmd2 := commands.NewDeleteCommand(discriminator, cmdCtx.targetCtx.DeploymentCollection, cmdCtx.targetCtx.DeploymentCollection.Inclusion) + cmd2 := commands.NewDeleteCommand(cmd.Discriminator, cmdCtx.targetCtx) - result, err := cmd2.Run(cmdCtx.ctx, cmdCtx.targetCtx.SharedContext.K, func(refs []k8s2.ObjectRef) error { + result, err := cmd2.Run(func(refs []k8s2.ObjectRef) error { return confirmDeletion(ctx, refs, cmd.DryRun, cmd.Yes) }) if err != nil { diff --git a/cmd/kluctl/commands/cmd_deploy.go b/cmd/kluctl/commands/cmd_deploy.go index 7b70de7a2..da8f92439 100644 --- a/cmd/kluctl/commands/cmd_deploy.go +++ b/cmd/kluctl/commands/cmd_deploy.go @@ -59,7 +59,7 @@ func (cmd *deployCmd) runCmdDeploy(cmdCtx *commandCtx, startTime time.Time) erro status.Trace(cmdCtx.ctx, "enter runCmdDeploy") defer status.Trace(cmdCtx.ctx, "leave runCmdDeploy") - cmd2 := commands.NewDeployCommand(cmdCtx.targetCtx.Target.Discriminator, cmdCtx.targetCtx.DeploymentCollection) + cmd2 := commands.NewDeployCommand(cmdCtx.targetCtx) cmd2.ForceApply = cmd.ForceApply cmd2.ReplaceOnError = cmd.ReplaceOnError cmd2.ForceReplaceOnError = cmd.ForceReplaceOnError @@ -74,7 +74,7 @@ func (cmd *deployCmd) runCmdDeploy(cmdCtx *commandCtx, startTime time.Time) erro cb = nil } - result, err := cmd2.Run(cmdCtx.ctx, cmdCtx.targetCtx.SharedContext.K, cb) + result, err := cmd2.Run(cb) if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_diff.go b/cmd/kluctl/commands/cmd_diff.go index 91be42dfa..429d61b39 100644 --- a/cmd/kluctl/commands/cmd_diff.go +++ b/cmd/kluctl/commands/cmd_diff.go @@ -41,14 +41,14 @@ func (cmd *diffCmd) Run(ctx context.Context) error { } startTime := time.Now() return withProjectCommandContext(ctx, ptArgs, func(cmdCtx *commandCtx) error { - cmd2 := commands.NewDiffCommand(cmdCtx.targetCtx.Target.Discriminator, cmdCtx.targetCtx.DeploymentCollection) + cmd2 := commands.NewDiffCommand(cmdCtx.targetCtx) cmd2.ForceApply = cmd.ForceApply cmd2.ReplaceOnError = cmd.ReplaceOnError cmd2.ForceReplaceOnError = cmd.ForceReplaceOnError cmd2.IgnoreTags = cmd.IgnoreTags cmd2.IgnoreLabels = cmd.IgnoreLabels cmd2.IgnoreAnnotations = cmd.IgnoreAnnotations - result, err := cmd2.Run(cmdCtx.ctx, cmdCtx.targetCtx.SharedContext.K) + result, err := cmd2.Run() if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_poke_images.go b/cmd/kluctl/commands/cmd_poke_images.go index 571b501cd..4d3f2a982 100644 --- a/cmd/kluctl/commands/cmd_poke_images.go +++ b/cmd/kluctl/commands/cmd_poke_images.go @@ -49,9 +49,9 @@ func (cmd *pokeImagesCmd) Run(ctx context.Context) error { } } - cmd2 := commands.NewPokeImagesCommand(cmdCtx.targetCtx.DeploymentCollection) + cmd2 := commands.NewPokeImagesCommand(cmdCtx.targetCtx) - result, err := cmd2.Run(ctx, cmdCtx.targetCtx.SharedContext.K) + result, err := cmd2.Run() if err != nil { return err } diff --git a/cmd/kluctl/commands/cmd_prune.go b/cmd/kluctl/commands/cmd_prune.go index 3fe34a470..43b785ed1 100644 --- a/cmd/kluctl/commands/cmd_prune.go +++ b/cmd/kluctl/commands/cmd_prune.go @@ -50,8 +50,8 @@ func (cmd *pruneCmd) Run(ctx context.Context) error { } func (cmd *pruneCmd) runCmdPrune(cmdCtx *commandCtx, startTime time.Time) error { - cmd2 := commands.NewPruneCommand(cmdCtx.targetCtx.Target.Discriminator, cmdCtx.targetCtx.DeploymentCollection) - result, err := cmd2.Run(cmdCtx.ctx, cmdCtx.targetCtx.SharedContext.K, func(refs []k8s2.ObjectRef) error { + cmd2 := commands.NewPruneCommand(cmdCtx.targetCtx.Target.Discriminator, cmdCtx.targetCtx) + result, err := cmd2.Run(func(refs []k8s2.ObjectRef) error { return confirmDeletion(cmdCtx.ctx, refs, cmd.DryRun, cmd.Yes) }) if err != nil { diff --git a/pkg/deployment/commands/delete.go b/pkg/deployment/commands/delete.go index e87cffa50..349028e96 100644 --- a/pkg/deployment/commands/delete.go +++ b/pkg/deployment/commands/delete.go @@ -1,44 +1,44 @@ package commands import ( - "context" "fmt" "github.com/google/uuid" - "github.com/kluctl/kluctl/v2/pkg/deployment" utils2 "github.com/kluctl/kluctl/v2/pkg/deployment/utils" - "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/kluctl_project" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/types/result" - "github.com/kluctl/kluctl/v2/pkg/utils" ) type DeleteCommand struct { - c *deployment.DeploymentCollection discriminator string - inclusion *utils.Inclusion + targetCtx *kluctl_project.TargetContext } -func NewDeleteCommand(discriminator string, c *deployment.DeploymentCollection, inclusion *utils.Inclusion) *DeleteCommand { +func NewDeleteCommand(discriminator string, targetCtx *kluctl_project.TargetContext) *DeleteCommand { return &DeleteCommand{ discriminator: discriminator, - c: c, - inclusion: inclusion, + targetCtx: targetCtx, } } -func (cmd *DeleteCommand) Run(ctx context.Context, k *k8s.K8sCluster, confirmCb func(refs []k8s2.ObjectRef) error) (*result.CommandResult, error) { - if cmd.discriminator == "" { +func (cmd *DeleteCommand) Run(confirmCb func(refs []k8s2.ObjectRef) error) (*result.CommandResult, error) { + discriminator := cmd.targetCtx.Target.Discriminator + if cmd.discriminator != "" { + discriminator = cmd.discriminator + } + + if discriminator == "" { return nil, fmt.Errorf("deletion without a discriminator is not supported") } dew := utils2.NewDeploymentErrorsAndWarnings() - ru := utils2.NewRemoteObjectsUtil(ctx, dew) - err := ru.UpdateRemoteObjects(k, &cmd.discriminator, nil, false) + ru := utils2.NewRemoteObjectsUtil(cmd.targetCtx.SharedContext.Ctx, dew) + err := ru.UpdateRemoteObjects(cmd.targetCtx.SharedContext.K, &discriminator, nil, false) if err != nil { return nil, err } - deleteRefs, err := utils2.FindObjectsForDelete(k, ru.GetFilteredRemoteObjects(cmd.inclusion), cmd.inclusion.HasType("tags"), nil) + deleteRefs, err := utils2.FindObjectsForDelete(cmd.targetCtx.SharedContext.K, ru.GetFilteredRemoteObjects(cmd.targetCtx.DeploymentCollection.Inclusion), cmd.targetCtx.DeploymentCollection.Inclusion.HasType("tags"), nil) if err != nil { return nil, err } @@ -50,14 +50,14 @@ func (cmd *DeleteCommand) Run(ctx context.Context, k *k8s.K8sCluster, confirmCb } } - deleted, err := utils2.DeleteObjects(ctx, k, deleteRefs, dew, true) + deleted, err := utils2.DeleteObjects(cmd.targetCtx.SharedContext.Ctx, cmd.targetCtx.SharedContext.K, deleteRefs, dew, true) if err != nil { return nil, err } return &result.CommandResult{ Id: uuid.New().String(), - Objects: collectObjects(cmd.c, ru, nil, nil, nil, deleted), + Objects: collectObjects(cmd.targetCtx.DeploymentCollection, ru, nil, nil, nil, deleted), Errors: dew.GetErrorsList(), Warnings: dew.GetWarningsList(), }, nil diff --git a/pkg/deployment/commands/deploy.go b/pkg/deployment/commands/deploy.go index ec814632c..291d05464 100644 --- a/pkg/deployment/commands/deploy.go +++ b/pkg/deployment/commands/deploy.go @@ -1,12 +1,10 @@ package commands import ( - "context" "fmt" "github.com/google/uuid" - "github.com/kluctl/kluctl/v2/pkg/deployment" utils2 "github.com/kluctl/kluctl/v2/pkg/deployment/utils" - "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/kluctl_project" "github.com/kluctl/kluctl/v2/pkg/status" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/types/result" @@ -14,8 +12,7 @@ import ( ) type DeployCommand struct { - c *deployment.DeploymentCollection - discriminator string + targetCtx *kluctl_project.TargetContext ForceApply bool ReplaceOnError bool @@ -25,23 +22,22 @@ type DeployCommand struct { NoWait bool } -func NewDeployCommand(discriminator string, c *deployment.DeploymentCollection) *DeployCommand { +func NewDeployCommand(targetCtx *kluctl_project.TargetContext) *DeployCommand { return &DeployCommand{ - discriminator: discriminator, - c: c, + targetCtx: targetCtx, } } -func (cmd *DeployCommand) Run(ctx context.Context, k *k8s.K8sCluster, diffResultCb func(diffResult *result.CommandResult) error) (*result.CommandResult, error) { +func (cmd *DeployCommand) Run(diffResultCb func(diffResult *result.CommandResult) error) (*result.CommandResult, error) { dew := utils2.NewDeploymentErrorsAndWarnings() - if cmd.discriminator == "" { - status.Warning(ctx, "No discriminator configured. Orphan object detection will not work") + if cmd.targetCtx.Target.Discriminator == "" { + status.Warning(cmd.targetCtx.SharedContext.Ctx, "No discriminator configured. Orphan object detection will not work") dew.AddWarning(k8s2.ObjectRef{}, fmt.Errorf("no discriminator configured. Orphan object detection will not work")) } - ru := utils2.NewRemoteObjectsUtil(ctx, dew) - err := ru.UpdateRemoteObjects(k, &cmd.discriminator, cmd.c.LocalObjectRefs(), false) + ru := utils2.NewRemoteObjectsUtil(cmd.targetCtx.SharedContext.Ctx, dew) + err := ru.UpdateRemoteObjects(cmd.targetCtx.SharedContext.K, &cmd.targetCtx.Target.Discriminator, cmd.targetCtx.DeploymentCollection.LocalObjectRefs(), false) if err != nil { return nil, err } @@ -58,19 +54,19 @@ func (cmd *DeployCommand) Run(ctx context.Context, k *k8s.K8sCluster, diffResult } if diffResultCb != nil { - au := utils2.NewApplyDeploymentsUtil(ctx, dew, ru, k, o) - au.ApplyDeployments(cmd.c.Deployments) + au := utils2.NewApplyDeploymentsUtil(cmd.targetCtx.SharedContext.Ctx, dew, ru, cmd.targetCtx.SharedContext.K, o) + au.ApplyDeployments(cmd.targetCtx.DeploymentCollection.Deployments) du := utils2.NewDiffUtil(dew, ru, au.GetAppliedObjectsMap()) - du.DiffDeploymentItems(cmd.c.Deployments) + du.DiffDeploymentItems(cmd.targetCtx.DeploymentCollection.Deployments) - orphanObjects, err := FindOrphanObjects(k, ru, cmd.c) + orphanObjects, err := FindOrphanObjects(cmd.targetCtx.SharedContext.K, ru, cmd.targetCtx.DeploymentCollection) diffResult := &result.CommandResult{ Id: uuid.New().String(), - Objects: collectObjects(cmd.c, ru, au, du, orphanObjects, nil), + Objects: collectObjects(cmd.targetCtx.DeploymentCollection, ru, au, du, orphanObjects, nil), Errors: dew.GetErrorsList(), Warnings: dew.GetWarningsList(), - SeenImages: cmd.c.Images.SeenImages(false), + SeenImages: cmd.targetCtx.DeploymentCollection.Images.SeenImages(false), } err = diffResultCb(diffResult) @@ -83,24 +79,24 @@ func (cmd *DeployCommand) Run(ctx context.Context, k *k8s.K8sCluster, diffResult dew.Init() // modify options to become a deploy - o.DryRun = k.DryRun + o.DryRun = cmd.targetCtx.SharedContext.K.DryRun o.AbortOnError = cmd.AbortOnError - au := utils2.NewApplyDeploymentsUtil(ctx, dew, ru, k, o) - au.ApplyDeployments(cmd.c.Deployments) + au := utils2.NewApplyDeploymentsUtil(cmd.targetCtx.SharedContext.Ctx, dew, ru, cmd.targetCtx.SharedContext.K, o) + au.ApplyDeployments(cmd.targetCtx.DeploymentCollection.Deployments) du := utils2.NewDiffUtil(dew, ru, au.GetAppliedObjectsMap()) - du.DiffDeploymentItems(cmd.c.Deployments) + du.DiffDeploymentItems(cmd.targetCtx.DeploymentCollection.Deployments) - orphanObjects, err := FindOrphanObjects(k, ru, cmd.c) + orphanObjects, err := FindOrphanObjects(cmd.targetCtx.SharedContext.K, ru, cmd.targetCtx.DeploymentCollection) if err != nil { return nil, err } return &result.CommandResult{ Id: uuid.New().String(), - Objects: collectObjects(cmd.c, ru, au, du, orphanObjects, nil), + Objects: collectObjects(cmd.targetCtx.DeploymentCollection, ru, au, du, orphanObjects, nil), Errors: dew.GetErrorsList(), Warnings: dew.GetWarningsList(), - SeenImages: cmd.c.Images.SeenImages(false), + SeenImages: cmd.targetCtx.DeploymentCollection.Images.SeenImages(false), }, nil } diff --git a/pkg/deployment/commands/diff.go b/pkg/deployment/commands/diff.go index dd4a4ecd3..7ce43d317 100644 --- a/pkg/deployment/commands/diff.go +++ b/pkg/deployment/commands/diff.go @@ -1,20 +1,17 @@ package commands import ( - "context" "fmt" "github.com/google/uuid" - "github.com/kluctl/kluctl/v2/pkg/deployment" "github.com/kluctl/kluctl/v2/pkg/deployment/utils" - "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/kluctl_project" "github.com/kluctl/kluctl/v2/pkg/status" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/types/result" ) type DiffCommand struct { - c *deployment.DeploymentCollection - discriminator string + targetCtx *kluctl_project.TargetContext ForceApply bool ReplaceOnError bool @@ -24,23 +21,22 @@ type DiffCommand struct { IgnoreAnnotations bool } -func NewDiffCommand(discriminator string, c *deployment.DeploymentCollection) *DiffCommand { +func NewDiffCommand(targetCtx *kluctl_project.TargetContext) *DiffCommand { return &DiffCommand{ - discriminator: discriminator, - c: c, + targetCtx: targetCtx, } } -func (cmd *DiffCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*result.CommandResult, error) { +func (cmd *DiffCommand) Run() (*result.CommandResult, error) { dew := utils.NewDeploymentErrorsAndWarnings() - if cmd.discriminator == "" { - status.Warning(ctx, "No discriminator configured. Orphan object detection will not work") + if cmd.targetCtx.Target.Discriminator == "" { + status.Warning(cmd.targetCtx.SharedContext.Ctx, "No discriminator configured. Orphan object detection will not work") dew.AddWarning(k8s2.ObjectRef{}, fmt.Errorf("no discriminator configured. Orphan object detection will not work")) } - ru := utils.NewRemoteObjectsUtil(ctx, dew) - err := ru.UpdateRemoteObjects(k, &cmd.discriminator, cmd.c.LocalObjectRefs(), false) + ru := utils.NewRemoteObjectsUtil(cmd.targetCtx.SharedContext.Ctx, dew) + err := ru.UpdateRemoteObjects(cmd.targetCtx.SharedContext.K, &cmd.targetCtx.Target.Discriminator, cmd.targetCtx.DeploymentCollection.LocalObjectRefs(), false) if err != nil { return nil, err } @@ -53,24 +49,24 @@ func (cmd *DiffCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*result.Com AbortOnError: false, ReadinessTimeout: 0, } - au := utils.NewApplyDeploymentsUtil(ctx, dew, ru, k, o) - au.ApplyDeployments(cmd.c.Deployments) + au := utils.NewApplyDeploymentsUtil(cmd.targetCtx.SharedContext.Ctx, dew, ru, cmd.targetCtx.SharedContext.K, o) + au.ApplyDeployments(cmd.targetCtx.DeploymentCollection.Deployments) du := utils.NewDiffUtil(dew, ru, au.GetAppliedObjectsMap()) du.IgnoreTags = cmd.IgnoreTags du.IgnoreLabels = cmd.IgnoreLabels du.IgnoreAnnotations = cmd.IgnoreAnnotations - du.DiffDeploymentItems(cmd.c.Deployments) + du.DiffDeploymentItems(cmd.targetCtx.DeploymentCollection.Deployments) - orphanObjects, err := FindOrphanObjects(k, ru, cmd.c) + orphanObjects, err := FindOrphanObjects(cmd.targetCtx.SharedContext.K, ru, cmd.targetCtx.DeploymentCollection) if err != nil { return nil, err } return &result.CommandResult{ Id: uuid.New().String(), - Objects: collectObjects(cmd.c, ru, au, du, orphanObjects, nil), + Objects: collectObjects(cmd.targetCtx.DeploymentCollection, ru, au, du, orphanObjects, nil), Errors: dew.GetErrorsList(), Warnings: dew.GetWarningsList(), - SeenImages: cmd.c.Images.SeenImages(false), + SeenImages: cmd.targetCtx.DeploymentCollection.Images.SeenImages(false), }, nil } diff --git a/pkg/deployment/commands/poke_images.go b/pkg/deployment/commands/poke_images.go index 7144f2c2f..994b1bca1 100644 --- a/pkg/deployment/commands/poke_images.go +++ b/pkg/deployment/commands/poke_images.go @@ -1,12 +1,10 @@ package commands import ( - "context" "fmt" "github.com/google/uuid" - "github.com/kluctl/kluctl/v2/pkg/deployment" utils2 "github.com/kluctl/kluctl/v2/pkg/deployment/utils" - "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/kluctl_project" "github.com/kluctl/kluctl/v2/pkg/types" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/types/result" @@ -15,35 +13,35 @@ import ( ) type PokeImagesCommand struct { - c *deployment.DeploymentCollection + targetCtx *kluctl_project.TargetContext } -func NewPokeImagesCommand(c *deployment.DeploymentCollection) *PokeImagesCommand { +func NewPokeImagesCommand(targetCtx *kluctl_project.TargetContext) *PokeImagesCommand { return &PokeImagesCommand{ - c: c, + targetCtx: targetCtx, } } -func (cmd *PokeImagesCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*result.CommandResult, error) { +func (cmd *PokeImagesCommand) Run() (*result.CommandResult, error) { var wg sync.WaitGroup dew := utils2.NewDeploymentErrorsAndWarnings() - ru := utils2.NewRemoteObjectsUtil(ctx, dew) - err := ru.UpdateRemoteObjects(k, nil, cmd.c.LocalObjectRefs(), false) + ru := utils2.NewRemoteObjectsUtil(cmd.targetCtx.SharedContext.Ctx, dew) + err := ru.UpdateRemoteObjects(cmd.targetCtx.SharedContext.K, nil, cmd.targetCtx.DeploymentCollection.LocalObjectRefs(), false) if err != nil { return nil, err } allObjects := make(map[k8s2.ObjectRef]*uo.UnstructuredObject) - for _, d := range cmd.c.Deployments { + for _, d := range cmd.targetCtx.DeploymentCollection.Deployments { for _, o := range d.Objects { allObjects[o.GetK8sRef()] = o } } containersAndImages := make(map[k8s2.ObjectRef][]types.FixedImage) - for _, fi := range cmd.c.Images.SeenImages(false) { + for _, fi := range cmd.targetCtx.DeploymentCollection.Images.SeenImages(false) { _, ok := allObjects[*fi.Object] if !ok { dew.AddError(*fi.Object, fmt.Errorf("object not found while trying to associate image with deployed object")) @@ -72,7 +70,7 @@ func (cmd *PokeImagesCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*resu return o, nil } - au := utils2.NewApplyDeploymentsUtil(ctx, dew, ru, k, &utils2.ApplyUtilOptions{}) + au := utils2.NewApplyDeploymentsUtil(cmd.targetCtx.SharedContext.Ctx, dew, ru, cmd.targetCtx.SharedContext.K, &utils2.ApplyUtilOptions{}) for ref, containers := range containersAndImages { ref := ref @@ -80,7 +78,7 @@ func (cmd *PokeImagesCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*resu wg.Add(1) go func() { defer wg.Done() - au := au.NewApplyUtil(ctx, nil) + au := au.NewApplyUtil(cmd.targetCtx.SharedContext.Ctx, nil) remote := ru.GetRemoteObject(ref) if remote == nil { dew.AddWarning(ref, fmt.Errorf("remote object not found, skipped image replacement")) @@ -94,18 +92,18 @@ func (cmd *PokeImagesCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*resu wg.Wait() du := utils2.NewDiffUtil(dew, ru, au.GetAppliedObjectsMap()) - du.DiffDeploymentItems(cmd.c.Deployments) + du.DiffDeploymentItems(cmd.targetCtx.DeploymentCollection.Deployments) - orphanObjects, err := FindOrphanObjects(k, ru, cmd.c) + orphanObjects, err := FindOrphanObjects(cmd.targetCtx.SharedContext.K, ru, cmd.targetCtx.DeploymentCollection) if err != nil { return nil, err } return &result.CommandResult{ Id: uuid.New().String(), - Objects: collectObjects(cmd.c, ru, au, du, orphanObjects, nil), + Objects: collectObjects(cmd.targetCtx.DeploymentCollection, ru, au, du, orphanObjects, nil), Errors: dew.GetErrorsList(), Warnings: dew.GetWarningsList(), - SeenImages: cmd.c.Images.SeenImages(false), + SeenImages: cmd.targetCtx.DeploymentCollection.Images.SeenImages(false), }, nil } diff --git a/pkg/deployment/commands/prune.go b/pkg/deployment/commands/prune.go index e989b120c..595582298 100644 --- a/pkg/deployment/commands/prune.go +++ b/pkg/deployment/commands/prune.go @@ -1,42 +1,42 @@ package commands import ( - "context" "fmt" "github.com/google/uuid" "github.com/kluctl/kluctl/v2/pkg/deployment" utils2 "github.com/kluctl/kluctl/v2/pkg/deployment/utils" "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/kluctl_project" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/types/result" ) type PruneCommand struct { discriminator string - c *deployment.DeploymentCollection + targetCtx *kluctl_project.TargetContext } -func NewPruneCommand(discriminator string, c *deployment.DeploymentCollection) *PruneCommand { +func NewPruneCommand(discriminator string, targetCtx *kluctl_project.TargetContext) *PruneCommand { return &PruneCommand{ discriminator: discriminator, - c: c, + targetCtx: targetCtx, } } -func (cmd *PruneCommand) Run(ctx context.Context, k *k8s.K8sCluster, confirmCb func(refs []k8s2.ObjectRef) error) (*result.CommandResult, error) { +func (cmd *PruneCommand) Run(confirmCb func(refs []k8s2.ObjectRef) error) (*result.CommandResult, error) { if cmd.discriminator == "" { return nil, fmt.Errorf("pruning without a discriminator is not supported") } dew := utils2.NewDeploymentErrorsAndWarnings() - ru := utils2.NewRemoteObjectsUtil(ctx, dew) - err := ru.UpdateRemoteObjects(k, &cmd.discriminator, nil, false) + ru := utils2.NewRemoteObjectsUtil(cmd.targetCtx.SharedContext.Ctx, dew) + err := ru.UpdateRemoteObjects(cmd.targetCtx.SharedContext.K, &cmd.discriminator, nil, false) if err != nil { return nil, err } - deleteRefs, err := FindOrphanObjects(k, ru, cmd.c) + deleteRefs, err := FindOrphanObjects(cmd.targetCtx.SharedContext.K, ru, cmd.targetCtx.DeploymentCollection) if err != nil { return nil, err } @@ -48,14 +48,14 @@ func (cmd *PruneCommand) Run(ctx context.Context, k *k8s.K8sCluster, confirmCb f } } - deleted, err := utils2.DeleteObjects(ctx, k, deleteRefs, dew, true) + deleted, err := utils2.DeleteObjects(cmd.targetCtx.SharedContext.Ctx, cmd.targetCtx.SharedContext.K, deleteRefs, dew, true) if err != nil { return nil, err } return &result.CommandResult{ Id: uuid.New().String(), - Objects: collectObjects(cmd.c, ru, nil, nil, nil, deleted), + Objects: collectObjects(cmd.targetCtx.DeploymentCollection, ru, nil, nil, nil, deleted), Warnings: dew.GetWarningsList(), }, nil } diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index c431fc0e0..453bfa022 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -21,7 +21,7 @@ type TargetContext struct { SharedContext deployment.SharedContext KluctlProject *LoadedKluctlProject - Target *types.Target + Target types.Target ClusterContext string DeploymentProject *deployment.DeploymentProject DeploymentCollection *deployment.DeploymentCollection From 229bc58bde2dc9d84bbec1f81721d92a61901458 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 12 May 2023 17:04:16 +0200 Subject: [PATCH 1620/2916] refactor: Move command info gathering into command objects --- cmd/kluctl/commands/cmd_delete.go | 6 - cmd/kluctl/commands/cmd_deploy.go | 10 +- cmd/kluctl/commands/cmd_diff.go | 6 - cmd/kluctl/commands/cmd_poke_images.go | 6 - cmd/kluctl/commands/cmd_prune.go | 10 +- cmd/kluctl/commands/cmd_validate.go | 4 +- cmd/kluctl/commands/command_result.go | 2 + cmd/kluctl/commands/utils.go | 164 +----------------------- pkg/deployment/commands/delete.go | 9 +- pkg/deployment/commands/deploy.go | 14 +- pkg/deployment/commands/diff.go | 12 +- pkg/deployment/commands/poke_images.go | 9 +- pkg/deployment/commands/prune.go | 17 ++- pkg/deployment/commands/result_utils.go | 134 +++++++++++++++++++ pkg/deployment/commands/validate.go | 2 +- pkg/deployment/images.go | 10 ++ pkg/kluctl_project/load.go | 2 + pkg/kluctl_project/project.go | 2 + pkg/kluctl_project/target_context.go | 5 +- pkg/results/result-store-secrets.go | 14 +- pkg/types/result/command_result.go | 7 +- pkg/types/result/summary.go | 4 +- pkg/utils/inclusion.go | 26 ++++ 23 files changed, 253 insertions(+), 222 deletions(-) create mode 100644 pkg/deployment/commands/result_utils.go diff --git a/cmd/kluctl/commands/cmd_delete.go b/cmd/kluctl/commands/cmd_delete.go index 28aafcd2d..f485c134c 100644 --- a/cmd/kluctl/commands/cmd_delete.go +++ b/cmd/kluctl/commands/cmd_delete.go @@ -7,7 +7,6 @@ import ( "github.com/kluctl/kluctl/v2/pkg/deployment/commands" "github.com/kluctl/kluctl/v2/pkg/status" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" - "time" ) type deleteCmd struct { @@ -46,7 +45,6 @@ func (cmd *deleteCmd) Run(ctx context.Context) error { renderOutputDirFlags: cmd.RenderOutputDirFlags, commandResultFlags: &cmd.CommandResultFlags, } - startTime := time.Now() return withProjectCommandContext(ctx, ptArgs, func(cmdCtx *commandCtx) error { cmd2 := commands.NewDeleteCommand(cmd.Discriminator, cmdCtx.targetCtx) @@ -56,10 +54,6 @@ func (cmd *deleteCmd) Run(ctx context.Context) error { if err != nil { return err } - err = addCommandInfo(result, startTime, "delete", cmdCtx, &cmd.TargetFlags, &cmd.ImageFlags, &cmd.InclusionFlags, &cmd.DryRunFlags, nil, nil, nil, false) - if err != nil { - return err - } err = outputCommandResult(cmdCtx, cmd.OutputFormatFlags, result, !cmd.DryRun || cmd.ForceWriteCommandResult) if err != nil { return err diff --git a/cmd/kluctl/commands/cmd_deploy.go b/cmd/kluctl/commands/cmd_deploy.go index da8f92439..e292dc27d 100644 --- a/cmd/kluctl/commands/cmd_deploy.go +++ b/cmd/kluctl/commands/cmd_deploy.go @@ -7,7 +7,6 @@ import ( "github.com/kluctl/kluctl/v2/pkg/deployment/commands" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types/result" - "time" ) type deployCmd struct { @@ -49,13 +48,12 @@ func (cmd *deployCmd) Run(ctx context.Context) error { renderOutputDirFlags: cmd.RenderOutputDirFlags, commandResultFlags: &cmd.CommandResultFlags, } - startTime := time.Now() return withProjectCommandContext(ctx, ptArgs, func(cmdCtx *commandCtx) error { - return cmd.runCmdDeploy(cmdCtx, startTime) + return cmd.runCmdDeploy(cmdCtx) }) } -func (cmd *deployCmd) runCmdDeploy(cmdCtx *commandCtx, startTime time.Time) error { +func (cmd *deployCmd) runCmdDeploy(cmdCtx *commandCtx) error { status.Trace(cmdCtx.ctx, "enter runCmdDeploy") defer status.Trace(cmdCtx.ctx, "leave runCmdDeploy") @@ -78,10 +76,6 @@ func (cmd *deployCmd) runCmdDeploy(cmdCtx *commandCtx, startTime time.Time) erro if err != nil { return err } - err = addCommandInfo(result, startTime, "deploy", cmdCtx, &cmd.TargetFlags, &cmd.ImageFlags, &cmd.InclusionFlags, &cmd.DryRunFlags, &cmd.ForceApplyFlags, &cmd.ReplaceOnErrorFlags, &cmd.AbortOnErrorFlags, cmd.NoWait) - if err != nil { - return err - } err = outputCommandResult(cmdCtx, cmd.OutputFormatFlags, result, !cmd.DryRun || cmd.ForceWriteCommandResult) if err != nil { return err diff --git a/cmd/kluctl/commands/cmd_diff.go b/cmd/kluctl/commands/cmd_diff.go index 429d61b39..c0801e80c 100644 --- a/cmd/kluctl/commands/cmd_diff.go +++ b/cmd/kluctl/commands/cmd_diff.go @@ -5,7 +5,6 @@ import ( "fmt" "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/deployment/commands" - "time" ) type diffCmd struct { @@ -39,7 +38,6 @@ func (cmd *diffCmd) Run(ctx context.Context) error { helmCredentials: cmd.HelmCredentials, renderOutputDirFlags: cmd.RenderOutputDirFlags, } - startTime := time.Now() return withProjectCommandContext(ctx, ptArgs, func(cmdCtx *commandCtx) error { cmd2 := commands.NewDiffCommand(cmdCtx.targetCtx) cmd2.ForceApply = cmd.ForceApply @@ -52,10 +50,6 @@ func (cmd *diffCmd) Run(ctx context.Context) error { if err != nil { return err } - err = addCommandInfo(result, startTime, "diff", cmdCtx, &cmd.TargetFlags, &cmd.ImageFlags, &cmd.InclusionFlags, nil, &cmd.ForceApplyFlags, &cmd.ReplaceOnErrorFlags, nil, false) - if err != nil { - return err - } err = outputCommandResult(cmdCtx, cmd.OutputFormatFlags, result, false) if err != nil { return err diff --git a/cmd/kluctl/commands/cmd_poke_images.go b/cmd/kluctl/commands/cmd_poke_images.go index 4d3f2a982..35df34c91 100644 --- a/cmd/kluctl/commands/cmd_poke_images.go +++ b/cmd/kluctl/commands/cmd_poke_images.go @@ -6,7 +6,6 @@ import ( "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/deployment/commands" "github.com/kluctl/kluctl/v2/pkg/status" - "time" ) type pokeImagesCmd struct { @@ -41,7 +40,6 @@ func (cmd *pokeImagesCmd) Run(ctx context.Context) error { renderOutputDirFlags: cmd.RenderOutputDirFlags, commandResultFlags: &cmd.CommandResultFlags, } - startTime := time.Now() return withProjectCommandContext(ctx, ptArgs, func(cmdCtx *commandCtx) error { if !cmd.Yes && !cmd.DryRun { if !status.AskForConfirmation(ctx, fmt.Sprintf("Do you really want to poke images to the context/cluster %s?", cmdCtx.targetCtx.ClusterContext)) { @@ -55,10 +53,6 @@ func (cmd *pokeImagesCmd) Run(ctx context.Context) error { if err != nil { return err } - err = addCommandInfo(result, startTime, "poke-images", cmdCtx, &cmd.TargetFlags, &cmd.ImageFlags, &cmd.InclusionFlags, &cmd.DryRunFlags, nil, nil, nil, false) - if err != nil { - return err - } err = outputCommandResult(cmdCtx, cmd.OutputFormatFlags, result, !cmd.DryRun || cmd.ForceWriteCommandResult) if err != nil { return err diff --git a/cmd/kluctl/commands/cmd_prune.go b/cmd/kluctl/commands/cmd_prune.go index 43b785ed1..9e3eea413 100644 --- a/cmd/kluctl/commands/cmd_prune.go +++ b/cmd/kluctl/commands/cmd_prune.go @@ -6,7 +6,6 @@ import ( "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/deployment/commands" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" - "time" ) type pruneCmd struct { @@ -43,13 +42,12 @@ func (cmd *pruneCmd) Run(ctx context.Context) error { renderOutputDirFlags: cmd.RenderOutputDirFlags, commandResultFlags: &cmd.CommandResultFlags, } - startTime := time.Now() return withProjectCommandContext(ctx, ptArgs, func(cmdCtx *commandCtx) error { - return cmd.runCmdPrune(cmdCtx, startTime) + return cmd.runCmdPrune(cmdCtx) }) } -func (cmd *pruneCmd) runCmdPrune(cmdCtx *commandCtx, startTime time.Time) error { +func (cmd *pruneCmd) runCmdPrune(cmdCtx *commandCtx) error { cmd2 := commands.NewPruneCommand(cmdCtx.targetCtx.Target.Discriminator, cmdCtx.targetCtx) result, err := cmd2.Run(func(refs []k8s2.ObjectRef) error { return confirmDeletion(cmdCtx.ctx, refs, cmd.DryRun, cmd.Yes) @@ -57,10 +55,6 @@ func (cmd *pruneCmd) runCmdPrune(cmdCtx *commandCtx, startTime time.Time) error if err != nil { return err } - err = addCommandInfo(result, startTime, "prune", cmdCtx, &cmd.TargetFlags, &cmd.ImageFlags, &cmd.InclusionFlags, &cmd.DryRunFlags, nil, nil, nil, false) - if err != nil { - return err - } err = outputCommandResult(cmdCtx, cmd.OutputFormatFlags, result, !cmd.DryRun || cmd.ForceWriteCommandResult) if err != nil { return err diff --git a/cmd/kluctl/commands/cmd_validate.go b/cmd/kluctl/commands/cmd_validate.go index 03471c507..80387c674 100644 --- a/cmd/kluctl/commands/cmd_validate.go +++ b/cmd/kluctl/commands/cmd_validate.go @@ -54,8 +54,8 @@ func (cmd *validateCmd) Run(ctx context.Context) error { var k8sContext *string if cmd.Context != "" { k8sContext = &cmd.Context - } else if commandResult.Command.Target != nil { - k8sContext = commandResult.Command.Target.Context + } else if commandResult.Target.Context != nil { + k8sContext = commandResult.Target.Context } clientFactory, err := k8s2.NewClientFactoryFromDefaultConfig(ctx, k8sContext) if err != nil { diff --git a/cmd/kluctl/commands/command_result.go b/cmd/kluctl/commands/command_result.go index 25e4a9e5e..a08e79722 100644 --- a/cmd/kluctl/commands/command_result.go +++ b/cmd/kluctl/commands/command_result.go @@ -240,6 +240,8 @@ func outputHelper(ctx context.Context, output []string, cb func(format string) ( func outputCommandResult(ctx *commandCtx, flags args.OutputFormatFlags, cr *result.CommandResult, writeToResultStore bool) error { status.Flush(ctx.ctx) + cr.Command.Initiator = result.CommandInititiator_CommandLine + if !flags.NoObfuscate { var obfuscator diff.Obfuscator err := obfuscator.ObfuscateResult(cr) diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 046f5609c..cd2e6a923 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -3,16 +3,6 @@ package commands import ( "context" "fmt" - git2 "github.com/go-git/go-git/v5" - "github.com/kluctl/kluctl/v2/pkg/results" - "github.com/kluctl/kluctl/v2/pkg/types" - "github.com/kluctl/kluctl/v2/pkg/types/k8s" - "github.com/kluctl/kluctl/v2/pkg/types/result" - "os" - "path/filepath" - "strings" - "time" - "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/deployment" "github.com/kluctl/kluctl/v2/pkg/git" @@ -22,12 +12,16 @@ import ( "github.com/kluctl/kluctl/v2/pkg/kluctl_jinja2" "github.com/kluctl/kluctl/v2/pkg/kluctl_project" "github.com/kluctl/kluctl/v2/pkg/repocache" + "github.com/kluctl/kluctl/v2/pkg/results" "github.com/kluctl/kluctl/v2/pkg/status" + "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd/api" + "os" + "strings" ) func withKluctlProjectFromArgs(ctx context.Context, projectFlags args.ProjectFlags, argsFlags *args.ArgsFlags, strictTemplates bool, forCompletion bool, cb func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error) error { @@ -225,156 +219,6 @@ func withProjectTargetCommandContext(ctx context.Context, args projectTargetComm return cb(cmdCtx) } -func addCommandInfo(r *result.CommandResult, startTime time.Time, command string, ctx *commandCtx, targetFlags *args.TargetFlags, - imageFlags *args.ImageFlags, inclusionFlags *args.InclusionFlags, - dryRunFlags *args.DryRunFlags, forceApplyFlags *args.ForceApplyFlags, replaceOnErrorFlags *args.ReplaceOnErrorFlags, abortOnErrorFlags *args.AbortOnErrorFlags, noWait bool) error { - r.Command = result.CommandInfo{ - Initiator: result.CommandInititiator_CommandLine, - StartTime: types.FromTime(startTime), - EndTime: types.FromTime(time.Now()), - Command: command, - Target: ctx.targetCtx.Target, - Args: ctx.targetCtx.KluctlProject.LoadArgs.ExternalArgs, - NoWait: noWait, - } - if targetFlags != nil { - r.Command.TargetNameOverride = targetFlags.TargetNameOverride - r.Command.ContextOverride = targetFlags.Context - } - if imageFlags != nil { - var err error - r.Command.Images, err = imageFlags.LoadFixedImagesFromArgs() - if err != nil { - return err - } - } - if inclusionFlags != nil { - r.Command.IncludeTags = inclusionFlags.IncludeTag - r.Command.ExcludeTags = inclusionFlags.ExcludeTag - r.Command.IncludeDeploymentDirs = inclusionFlags.IncludeDeploymentDir - r.Command.ExcludeDeploymentDirs = inclusionFlags.ExcludeDeploymentDir - } - if dryRunFlags != nil { - r.Command.DryRun = dryRunFlags.DryRun - } - if forceApplyFlags != nil { - r.Command.ForceApply = forceApplyFlags.ForceApply - } - if replaceOnErrorFlags != nil { - r.Command.ReplaceOnError = replaceOnErrorFlags.ReplaceOnError - r.Command.ForceReplaceOnError = replaceOnErrorFlags.ForceReplaceOnError - } - if abortOnErrorFlags != nil { - r.Command.AbortOnError = abortOnErrorFlags.AbortOnError - } - r.Deployment = &ctx.targetCtx.DeploymentProject.Config - - err := addGitInfo(r, ctx) - if err != nil { - return err - } - - err = addClusterInfo(r, ctx) - if err != nil { - return err - } - - if ctx.targetCtx.Target != nil { - r.Target.TargetName = ctx.targetCtx.Target.Name - r.Target.Discriminator = ctx.targetCtx.Target.Discriminator - } - r.Target.ClusterId = r.ClusterInfo.ClusterId - - return nil -} - -func addGitInfo(r *result.CommandResult, ctx *commandCtx) error { - if ctx.targetCtx.KluctlProject.LoadArgs.RepoRoot == "" { - return nil - } - - projectDirAbs, err := filepath.Abs(ctx.targetCtx.KluctlProject.LoadArgs.ProjectDir) - if err != nil { - return err - } - - subDir, err := filepath.Rel(ctx.targetCtx.KluctlProject.LoadArgs.RepoRoot, projectDirAbs) - if err != nil { - return err - } - if subDir == "." { - subDir = "" - } - - g, err := git2.PlainOpen(ctx.targetCtx.KluctlProject.LoadArgs.RepoRoot) - if err != nil { - return err - } - - w, err := g.Worktree() - if err != nil { - return err - } - - s, err := w.Status() - if err != nil { - return err - } - - head, err := g.Head() - if err != nil { - return err - } - - remotes, err := g.Remotes() - if err != nil { - return err - } - - var originUrl *types.GitUrl - for _, r := range remotes { - if r.Config().Name == "origin" { - originUrl, err = types.ParseGitUrl(r.Config().URLs[0]) - if err != nil { - return err - } - } - } - - var normaliedUrl string - if originUrl != nil { - normaliedUrl = originUrl.NormalizedRepoKey() - } - - r.GitInfo = &result.GitInfo{ - Url: originUrl, - Ref: head.Name().String(), - SubDir: subDir, - Commit: head.Hash().String(), - Dirty: !s.IsClean(), - } - r.Project.NormalizedGitUrl = normaliedUrl - r.Project.SubDir = subDir - return nil -} - -func addClusterInfo(r *result.CommandResult, ctx *commandCtx) error { - kubeSystemNs, _, err := ctx.targetCtx.SharedContext.K.GetSingleObject( - k8s.NewObjectRef("", "v1", "Namespace", "kube-system", "")) - if err != nil { - return err - } - // we reuse the kube-system namespace uid as global cluster id - clusterId := kubeSystemNs.GetK8sUid() - if clusterId == "" { - return fmt.Errorf("kube-system namespace has no uid") - } - r.ClusterInfo = result.ClusterInfo{ - ClusterId: clusterId, - } - return nil -} - func clientConfigGetter(forCompletion bool) func(context *string) (*rest.Config, *api.Config, error) { return func(context *string) (*rest.Config, *api.Config, error) { if forCompletion { diff --git a/pkg/deployment/commands/delete.go b/pkg/deployment/commands/delete.go index 349028e96..ffd08dae1 100644 --- a/pkg/deployment/commands/delete.go +++ b/pkg/deployment/commands/delete.go @@ -55,10 +55,15 @@ func (cmd *DeleteCommand) Run(confirmCb func(refs []k8s2.ObjectRef) error) (*res return nil, err } - return &result.CommandResult{ + r := &result.CommandResult{ Id: uuid.New().String(), Objects: collectObjects(cmd.targetCtx.DeploymentCollection, ru, nil, nil, nil, deleted), Errors: dew.GetErrorsList(), Warnings: dew.GetWarningsList(), - }, nil + } + err = addBaseCommandInfoToResult(cmd.targetCtx, r, "delete") + if err != nil { + return r, err + } + return r, nil } diff --git a/pkg/deployment/commands/deploy.go b/pkg/deployment/commands/deploy.go index 291d05464..f2fd0a5b7 100644 --- a/pkg/deployment/commands/deploy.go +++ b/pkg/deployment/commands/deploy.go @@ -92,11 +92,21 @@ func (cmd *DeployCommand) Run(diffResultCb func(diffResult *result.CommandResult if err != nil { return nil, err } - return &result.CommandResult{ + r := &result.CommandResult{ Id: uuid.New().String(), Objects: collectObjects(cmd.targetCtx.DeploymentCollection, ru, au, du, orphanObjects, nil), Errors: dew.GetErrorsList(), Warnings: dew.GetWarningsList(), SeenImages: cmd.targetCtx.DeploymentCollection.Images.SeenImages(false), - }, nil + } + r.Command.ForceApply = cmd.ForceApply + r.Command.ReplaceOnError = cmd.ReplaceOnError + r.Command.ForceReplaceOnError = cmd.ForceReplaceOnError + r.Command.AbortOnError = cmd.AbortOnError + r.Command.NoWait = cmd.NoWait + err = addBaseCommandInfoToResult(cmd.targetCtx, r, "deploy") + if err != nil { + return r, err + } + return r, nil } diff --git a/pkg/deployment/commands/diff.go b/pkg/deployment/commands/diff.go index 7ce43d317..188937786 100644 --- a/pkg/deployment/commands/diff.go +++ b/pkg/deployment/commands/diff.go @@ -62,11 +62,19 @@ func (cmd *DiffCommand) Run() (*result.CommandResult, error) { if err != nil { return nil, err } - return &result.CommandResult{ + r := &result.CommandResult{ Id: uuid.New().String(), Objects: collectObjects(cmd.targetCtx.DeploymentCollection, ru, au, du, orphanObjects, nil), Errors: dew.GetErrorsList(), Warnings: dew.GetWarningsList(), SeenImages: cmd.targetCtx.DeploymentCollection.Images.SeenImages(false), - }, nil + } + r.Command.ForceApply = cmd.ForceApply + r.Command.ReplaceOnError = cmd.ReplaceOnError + r.Command.ForceReplaceOnError = cmd.ForceReplaceOnError + err = addBaseCommandInfoToResult(cmd.targetCtx, r, "diff") + if err != nil { + return r, err + } + return r, nil } diff --git a/pkg/deployment/commands/poke_images.go b/pkg/deployment/commands/poke_images.go index 994b1bca1..5197687b3 100644 --- a/pkg/deployment/commands/poke_images.go +++ b/pkg/deployment/commands/poke_images.go @@ -99,11 +99,16 @@ func (cmd *PokeImagesCommand) Run() (*result.CommandResult, error) { return nil, err } - return &result.CommandResult{ + r := &result.CommandResult{ Id: uuid.New().String(), Objects: collectObjects(cmd.targetCtx.DeploymentCollection, ru, au, du, orphanObjects, nil), Errors: dew.GetErrorsList(), Warnings: dew.GetWarningsList(), SeenImages: cmd.targetCtx.DeploymentCollection.Images.SeenImages(false), - }, nil + } + err = addBaseCommandInfoToResult(cmd.targetCtx, r, "deploy") + if err != nil { + return r, err + } + return r, nil } diff --git a/pkg/deployment/commands/prune.go b/pkg/deployment/commands/prune.go index 595582298..1c3bed093 100644 --- a/pkg/deployment/commands/prune.go +++ b/pkg/deployment/commands/prune.go @@ -24,14 +24,18 @@ func NewPruneCommand(discriminator string, targetCtx *kluctl_project.TargetConte } func (cmd *PruneCommand) Run(confirmCb func(refs []k8s2.ObjectRef) error) (*result.CommandResult, error) { - if cmd.discriminator == "" { + discriminator := cmd.discriminator + if discriminator == "" && cmd.targetCtx != nil { + discriminator = cmd.targetCtx.Target.Discriminator + } + if discriminator == "" { return nil, fmt.Errorf("pruning without a discriminator is not supported") } dew := utils2.NewDeploymentErrorsAndWarnings() ru := utils2.NewRemoteObjectsUtil(cmd.targetCtx.SharedContext.Ctx, dew) - err := ru.UpdateRemoteObjects(cmd.targetCtx.SharedContext.K, &cmd.discriminator, nil, false) + err := ru.UpdateRemoteObjects(cmd.targetCtx.SharedContext.K, &discriminator, nil, false) if err != nil { return nil, err } @@ -53,11 +57,16 @@ func (cmd *PruneCommand) Run(confirmCb func(refs []k8s2.ObjectRef) error) (*resu return nil, err } - return &result.CommandResult{ + r := &result.CommandResult{ Id: uuid.New().String(), Objects: collectObjects(cmd.targetCtx.DeploymentCollection, ru, nil, nil, nil, deleted), Warnings: dew.GetWarningsList(), - }, nil + } + err = addBaseCommandInfoToResult(cmd.targetCtx, r, "prune") + if err != nil { + return r, err + } + return r, nil } func FindOrphanObjects(k *k8s.K8sCluster, ru *utils2.RemoteObjectUtils, c *deployment.DeploymentCollection) ([]k8s2.ObjectRef, error) { diff --git a/pkg/deployment/commands/result_utils.go b/pkg/deployment/commands/result_utils.go new file mode 100644 index 000000000..dba10686f --- /dev/null +++ b/pkg/deployment/commands/result_utils.go @@ -0,0 +1,134 @@ +package commands + +import ( + "fmt" + git2 "github.com/go-git/go-git/v5" + "github.com/kluctl/kluctl/v2/pkg/kluctl_project" + "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/types/result" + "path/filepath" + "time" +) + +func addBaseCommandInfoToResult(targetCtx *kluctl_project.TargetContext, r *result.CommandResult, command string) error { + r.Target = targetCtx.Target + r.Command = result.CommandInfo{ + StartTime: types.FromTime(targetCtx.KluctlProject.LoadTime), + EndTime: types.FromTime(time.Now()), + Command: command, + Args: targetCtx.KluctlProject.LoadArgs.ExternalArgs, + } + r.Command.TargetNameOverride = targetCtx.Params.TargetNameOverride + r.Command.ContextOverride = targetCtx.Params.ContextOverride + r.Command.Images = targetCtx.Params.Images.FixedImages() + r.Command.IncludeTags = targetCtx.Params.Inclusion.GetIncludes("tags") + r.Command.ExcludeTags = targetCtx.Params.Inclusion.GetExcludes("tags") + r.Command.IncludeDeploymentDirs = targetCtx.Params.Inclusion.GetIncludes("deploymentItemDir") + r.Command.ExcludeDeploymentDirs = targetCtx.Params.Inclusion.GetExcludes("deploymentItemDir") + r.Command.DryRun = targetCtx.Params.DryRun + + r.Deployment = &targetCtx.DeploymentProject.Config + + err := addGitInfo(targetCtx, r) + if err != nil { + return err + } + + err = addClusterInfo(targetCtx, r) + if err != nil { + return err + } + + r.TargetKey.TargetName = targetCtx.Target.Name + r.TargetKey.Discriminator = targetCtx.Target.Discriminator + r.TargetKey.ClusterId = r.ClusterInfo.ClusterId + return nil +} + +func addGitInfo(targetCtx *kluctl_project.TargetContext, r *result.CommandResult) error { + if targetCtx.KluctlProject.LoadArgs.RepoRoot == "" { + return nil + } + + projectDirAbs, err := filepath.Abs(targetCtx.KluctlProject.LoadArgs.ProjectDir) + if err != nil { + return err + } + + subDir, err := filepath.Rel(targetCtx.KluctlProject.LoadArgs.RepoRoot, projectDirAbs) + if err != nil { + return err + } + if subDir == "." { + subDir = "" + } + + g, err := git2.PlainOpen(targetCtx.KluctlProject.LoadArgs.RepoRoot) + if err != nil { + return err + } + + w, err := g.Worktree() + if err != nil { + return err + } + + s, err := w.Status() + if err != nil { + return err + } + + head, err := g.Head() + if err != nil { + return err + } + + remotes, err := g.Remotes() + if err != nil { + return err + } + + var originUrl *types.GitUrl + for _, r := range remotes { + if r.Config().Name == "origin" { + originUrl, err = types.ParseGitUrl(r.Config().URLs[0]) + if err != nil { + return err + } + } + } + + var normaliedUrl string + if originUrl != nil { + normaliedUrl = originUrl.NormalizedRepoKey() + } + + r.GitInfo = &result.GitInfo{ + Url: originUrl, + Ref: head.Name().String(), + SubDir: subDir, + Commit: head.Hash().String(), + Dirty: !s.IsClean(), + } + r.ProjectKey.NormalizedGitUrl = normaliedUrl + r.ProjectKey.SubDir = subDir + return nil +} + +func addClusterInfo(targetCtx *kluctl_project.TargetContext, r *result.CommandResult) error { + kubeSystemNs, _, err := targetCtx.SharedContext.K.GetSingleObject( + k8s.NewObjectRef("", "v1", "Namespace", "kube-system", "")) + if err != nil { + return err + } + // we reuse the kube-system namespace uid as global cluster id + clusterId := kubeSystemNs.GetK8sUid() + if clusterId == "" { + return fmt.Errorf("kube-system namespace has no uid") + } + r.ClusterInfo = result.ClusterInfo{ + ClusterId: clusterId, + } + return nil +} diff --git a/pkg/deployment/commands/validate.go b/pkg/deployment/commands/validate.go index 32a3dda68..1a1fee162 100644 --- a/pkg/deployment/commands/validate.go +++ b/pkg/deployment/commands/validate.go @@ -69,7 +69,7 @@ func (cmd *ValidateCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*result appliedObjects[o.Ref] = o.Applied } } - discriminator = cmd.r.Target.Discriminator + discriminator = cmd.r.TargetKey.Discriminator } else { return nil, fmt.Errorf("either deployment collection or command result must be passed") } diff --git a/pkg/deployment/images.go b/pkg/deployment/images.go index f9e6df475..5eb899b59 100644 --- a/pkg/deployment/images.go +++ b/pkg/deployment/images.go @@ -38,7 +38,17 @@ func (images *Images) PrependFixedImages(fis []types.FixedImage) { images.fixedImages = newFixedImages } +func (images *Images) FixedImages() []types.FixedImage { + if images == nil { + return nil + } + return images.fixedImages +} + func (images *Images) SeenImages(simple bool) []types.FixedImage { + if images == nil { + return nil + } var ret []types.FixedImage for _, fi := range images.seenImages { if simple { diff --git a/pkg/kluctl_project/load.go b/pkg/kluctl_project/load.go index 0535cbc61..5c98eff20 100644 --- a/pkg/kluctl_project/load.go +++ b/pkg/kluctl_project/load.go @@ -5,6 +5,7 @@ import ( "github.com/kluctl/go-jinja2" "github.com/kluctl/kluctl/v2/pkg/sops/decryptor" "github.com/kluctl/kluctl/v2/pkg/status" + "time" ) func LoadKluctlProject(ctx context.Context, args LoadKluctlProjectArgs, tmpDir string, j2 *jinja2.Jinja2) (*LoadedKluctlProject, error) { @@ -14,6 +15,7 @@ func LoadKluctlProject(ctx context.Context, args LoadKluctlProjectArgs, tmpDir s p := &LoadedKluctlProject{ ctx: ctx, LoadArgs: args, + LoadTime: time.Now(), TmpDir: tmpDir, J2: j2, RP: args.RP, diff --git a/pkg/kluctl_project/project.go b/pkg/kluctl_project/project.go index 4fe0161ad..ad2d230c9 100644 --- a/pkg/kluctl_project/project.go +++ b/pkg/kluctl_project/project.go @@ -7,12 +7,14 @@ import ( "github.com/kluctl/kluctl/v2/pkg/repocache" "github.com/kluctl/kluctl/v2/pkg/sops/decryptor" types2 "github.com/kluctl/kluctl/v2/pkg/types" + "time" ) type LoadedKluctlProject struct { ctx context.Context LoadArgs LoadKluctlProjectArgs + LoadTime time.Time TmpDir string diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index 453bfa022..93d92ca94 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -18,6 +18,8 @@ import ( ) type TargetContext struct { + Params TargetContextParams + SharedContext deployment.SharedContext KluctlProject *LoadedKluctlProject @@ -138,9 +140,10 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, params Targe } targetCtx := &TargetContext{ + Params: params, SharedContext: dctx, KluctlProject: p, - Target: target, + Target: *target, ClusterContext: clusterContext, DeploymentProject: d, DeploymentCollection: c, diff --git a/pkg/results/result-store-secrets.go b/pkg/results/result-store-secrets.go index 206c3c2d0..ab1db402a 100644 --- a/pkg/results/result-store-secrets.go +++ b/pkg/results/result-store-secrets.go @@ -99,8 +99,8 @@ var invalidChars = regexp.MustCompile(`[^a-zA-Z0-9-]`) func (s *ResultStoreSecrets) buildName(cr *result.CommandResult) string { var name string - if cr.Project.NormalizedGitUrl != "" { - s := path.Base(cr.Project.NormalizedGitUrl) + if cr.ProjectKey.NormalizedGitUrl != "" { + s := path.Base(cr.ProjectKey.NormalizedGitUrl) if s != "" { name = s + "-" } @@ -197,11 +197,11 @@ func (s *ResultStoreSecrets) WriteCommandResult(cr *result.CommandResult) error "compactedObjects": compressedObjects, }, } - if cr.Project.NormalizedGitUrl != "" { - secret.Annotations["kluctl.io/result-project-normalized-url"] = cr.Project.NormalizedGitUrl + if cr.ProjectKey.NormalizedGitUrl != "" { + secret.Annotations["kluctl.io/result-project-normalized-url"] = cr.ProjectKey.NormalizedGitUrl } - if cr.Project.SubDir != "" { - secret.Annotations["kluctl.io/result-project-subdir"] = cr.Project.SubDir + if cr.ProjectKey.SubDir != "" { + secret.Annotations["kluctl.io/result-project-subdir"] = cr.ProjectKey.SubDir } err = s.client.Patch(s.ctx, &secret, client.Apply, client.FieldOwner("kluctl-results")) @@ -209,7 +209,7 @@ func (s *ResultStoreSecrets) WriteCommandResult(cr *result.CommandResult) error return err } - err = s.cleanupResults(cr.Project, cr.Target) + err = s.cleanupResults(cr.ProjectKey, cr.TargetKey) if err != nil { return err } diff --git a/pkg/types/result/command_result.go b/pkg/types/result/command_result.go index 4d2ec8dcb..141bd055d 100644 --- a/pkg/types/result/command_result.go +++ b/pkg/types/result/command_result.go @@ -78,7 +78,7 @@ type CommandInfo struct { EndTime types.JsonTime `json:"endTime"` KluctlDeployment *KluctlDeploymentInfo `json:"kluctlDeployment,omitempty"` Command string `json:"command,omitempty"` - Target *types.Target `json:"target,omitempty"` + Target string `json:"target,omitempty"` TargetNameOverride string `json:"targetNameOverride,omitempty"` ContextOverride string `json:"contextOverride,omitempty"` Args *uo.UnstructuredObject `json:"args,omitempty"` @@ -127,8 +127,9 @@ type ResultObject struct { type CommandResult struct { Id string `json:"id"` - Project ProjectKey `json:"project"` - Target TargetKey `json:"target"` + ProjectKey ProjectKey `json:"projectKey"` + TargetKey TargetKey `json:"targetKey"` + Target types.Target `json:"target"` Command CommandInfo `json:"command,omitempty"` GitInfo *GitInfo `json:"gitInfo,omitempty"` ClusterInfo ClusterInfo `json:"clusterInfo"` diff --git a/pkg/types/result/summary.go b/pkg/types/result/summary.go index 900645e66..dc05e095a 100644 --- a/pkg/types/result/summary.go +++ b/pkg/types/result/summary.go @@ -53,8 +53,8 @@ func (cr *CommandResult) BuildSummary() *CommandResultSummary { ret := &CommandResultSummary{ Id: cr.Id, - Project: cr.Project, - Target: cr.Target, + Project: cr.ProjectKey, + Target: cr.TargetKey, Command: cr.Command, GitInfo: cr.GitInfo, ClusterInfo: cr.ClusterInfo, diff --git a/pkg/utils/inclusion.go b/pkg/utils/inclusion.go index 5300f269f..41de49e69 100644 --- a/pkg/utils/inclusion.go +++ b/pkg/utils/inclusion.go @@ -42,6 +42,32 @@ func (inc *Inclusion) HasType(typ string) bool { return false } +func (inc *Inclusion) GetIncludes(typ string) []string { + if inc == nil { + return nil + } + var ret []string + for e, _ := range inc.includes { + if e.Type == typ { + ret = append(ret, e.Value) + } + } + return ret +} + +func (inc *Inclusion) GetExcludes(typ string) []string { + if inc == nil { + return nil + } + var ret []string + for e, _ := range inc.excludes { + if e.Type == typ { + ret = append(ret, e.Value) + } + } + return ret +} + func (inc *Inclusion) checkList(l []InclusionEntry, m map[InclusionEntry]bool) bool { for _, e := range l { if _, ok := m[e]; ok { From edb5bf7eab38a89a9bb22502a3745646adc383e0 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sun, 14 May 2023 22:27:37 +0200 Subject: [PATCH 1621/2916] refactor: Get rid of fluxcd dependency --- pkg/deployment/deployment_item.go | 4 +- .../flux_utils/kustomize/filesys/fs_secure.go | 286 ++++++++++++++++++ .../kustomize/kustomize_generator.go | 81 +++++ 3 files changed, 369 insertions(+), 2 deletions(-) create mode 100644 pkg/utils/flux_utils/kustomize/filesys/fs_secure.go create mode 100644 pkg/utils/flux_utils/kustomize/kustomize_generator.go diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index f3cb14777..982be5d05 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -2,14 +2,14 @@ package deployment import ( "fmt" - "github.com/fluxcd/pkg/kustomize" - securefs "github.com/fluxcd/pkg/kustomize/filesys" "github.com/hashicorp/go-multierror" "github.com/kluctl/kluctl/v2/pkg/helm" "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/sops" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/flux_utils/kustomize" + securefs "github.com/kluctl/kluctl/v2/pkg/utils/flux_utils/kustomize/filesys" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/vars" "github.com/kluctl/kluctl/v2/pkg/yaml" diff --git a/pkg/utils/flux_utils/kustomize/filesys/fs_secure.go b/pkg/utils/flux_utils/kustomize/filesys/fs_secure.go new file mode 100644 index 000000000..267d72616 --- /dev/null +++ b/pkg/utils/flux_utils/kustomize/filesys/fs_secure.go @@ -0,0 +1,286 @@ +/* +Copyright 2022 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package filesys + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "sigs.k8s.io/kustomize/kyaml/filesys" +) + +var ( + // tmpConfirmedDirPrefix is the prefix used by filesys.NewTmpConfirmedDir(), + // the absolute prefix is the value prefixed with os.TempDir(). + tmpConfirmedDirPrefix = "kustomize-" +) + +// MakeFsOnDiskSecure returns a secure file system which asserts any paths it +// handles to be inside root. When allowed prefixes are provided, followed +// absolute links are allowed to traverse into these directories. +// The root is not allowed to match an allowed prefix, this to ensure an FS +// instance can not reach into another FS when the allowed prefix configuration +// is static. +func MakeFsOnDiskSecure(root string, allowPrefixes ...string) (filesys.FileSystem, error) { + unsafeFS := filesys.MakeFsOnDisk() + cleanedAbs, _, err := unsafeFS.CleanedAbs(root) + if err != nil { + return nil, err + } + if ok, prefix := hasOneOfPrefixes(cleanedAbs.String(), allowPrefixes); ok { + return nil, fmt.Errorf("root '%s' cannot be prefixed with '%s'", root, prefix) + } + return fsSecure{root: cleanedAbs, unsafeFS: unsafeFS, allowPrefixes: allowPrefixes}, nil +} + +// TmpConfirmedDirPrefix returns the absolute prefix path as constructed by +// filesys.NewTmpConfirmedDir(). +func TmpConfirmedDirPrefix() (string, error) { + // Some OS-es seem to symlink TempDir. + evaluated, err := filepath.EvalSymlinks(os.TempDir()) + if err != nil { + return "", err + } + return filepath.Join(evaluated, tmpConfirmedDirPrefix), nil +} + +// MakeFsOnDiskSecureBuild calls MakeFsOnDiskSecure, but configures the +// TmpConfirmedDirPrefix as an allowed prefix. +// NOTE: When e.g. being able to load remote bases is not a concern, opt to use +// MakeFsOnDiskSecure instead, unless running into specific traversal issues. +func MakeFsOnDiskSecureBuild(root string, allowPrefixes ...string) (filesys.FileSystem, error) { + dirPrefix, err := TmpConfirmedDirPrefix() + if err != nil { + return nil, err + } + allowPrefixes = append([]string{dirPrefix}, allowPrefixes...) + return MakeFsOnDiskSecure(root, allowPrefixes...) +} + +// fsSecure wraps an unsafe FileSystem implementation, and secures it +// by confirming paths are inside root. +type fsSecure struct { + root filesys.ConfirmedDir + unsafeFS filesys.FileSystem + allowPrefixes []string +} + +// ConstraintError records an error and the operation and file that +// violated it. +type ConstraintError struct { + Op string + Path string + Err error +} + +func (e *ConstraintError) Error() string { + return "fs-security-constraint " + e.Op + " " + e.Path + ": " + e.Err.Error() +} + +func (e *ConstraintError) Unwrap() error { return e.Err } + +// Create delegates to the embedded unsafe FS after having confirmed the path +// to be inside root. If the provided path violates this constraint, an error +// of type ConstraintError is returned. +func (fs fsSecure) Create(path string) (filesys.File, error) { + if err := isSecurePath(fs.unsafeFS, fs.root, path, fs.allowPrefixes...); err != nil { + return nil, &ConstraintError{Op: "create", Path: path, Err: err} + } + return fs.unsafeFS.Create(path) +} + +// Mkdir delegates to the embedded unsafe FS after having confirmed the path +// to be inside root. If the provided path violates this constraint, an error +// of type ConstraintError is returned. +func (fs fsSecure) Mkdir(path string) error { + if err := isSecurePath(fs.unsafeFS, fs.root, path, fs.allowPrefixes...); err != nil { + return &ConstraintError{Op: "mkdir", Path: path, Err: err} + } + return fs.unsafeFS.Mkdir(path) +} + +// MkdirAll delegates to the embedded unsafe FS after having confirmed the path +// to be inside root. If the provided path violates this constraint, an error +// type ConstraintError is returned. +func (fs fsSecure) MkdirAll(path string) error { + if err := isSecurePath(fs.unsafeFS, fs.root, path, fs.allowPrefixes...); err != nil { + return &ConstraintError{Op: "mkdir", Path: path, Err: err} + } + return fs.unsafeFS.MkdirAll(path) +} + +// RemoveAll delegates to the embedded unsafe FS after having confirmed the +// path to be inside root. If the provided path violates this constraint, an +// error of type ConstraintError is returned. +func (fs fsSecure) RemoveAll(path string) error { + if err := isSecurePath(fs.unsafeFS, fs.root, path, fs.allowPrefixes...); err != nil { + return &ConstraintError{Op: "remove", Path: path, Err: err} + } + return fs.unsafeFS.RemoveAll(path) +} + +// Open delegates to the embedded unsafe FS after having confirmed the path +// to be inside root. If the provided path violates this constraint, an error +// of type ConstraintError is returned. +func (fs fsSecure) Open(path string) (filesys.File, error) { + if err := isSecurePath(fs.unsafeFS, fs.root, path, fs.allowPrefixes...); err != nil { + return nil, &ConstraintError{Op: "open", Path: path, Err: err} + } + return fs.unsafeFS.Open(path) +} + +// IsDir delegates to the embedded unsafe FS after having confirmed the path +// to be inside root. If the provided path violates this constraint, it returns +// false. +func (fs fsSecure) IsDir(path string) bool { + if err := isSecurePath(fs.unsafeFS, fs.root, path, fs.allowPrefixes...); err != nil { + return false + } + return fs.unsafeFS.IsDir(path) +} + +// ReadDir delegates to the embedded unsafe FS after having confirmed the path +// to be inside root. If the provided path violates this constraint, an error +// of type ConstraintError is returned. +func (fs fsSecure) ReadDir(path string) ([]string, error) { + if err := isSecurePath(fs.unsafeFS, fs.root, path, fs.allowPrefixes...); err != nil { + return nil, &ConstraintError{Op: "open", Path: path, Err: err} + } + return fs.unsafeFS.ReadDir(path) +} + +// CleanedAbs delegates to the embedded unsafe FS, but confirms the returned +// result to be within root. If the results violates this constraint, an error +// of type ConstraintError is returned. +// In essence, it functions the same as Kustomize's loader.RestrictionRootOnly, +// but on FS levels, and while allowing file paths. +func (fs fsSecure) CleanedAbs(path string) (filesys.ConfirmedDir, string, error) { + d, f, err := fs.unsafeFS.CleanedAbs(path) + if err != nil { + return d, f, err + } + if ok, _ := hasOneOfPrefixes(d.String(), fs.allowPrefixes); ok { + return d, f, err + } + if !d.HasPrefix(fs.root) { + return "", "", &ConstraintError{Op: "abs", Path: path, Err: rootConstraintErr(path, fs.root.String())} + } + return d, f, err +} + +// Exists delegates to the embedded unsafe FS after having confirmed the path +// to be inside root. If the provided path violates this constraint, it returns +// false. +func (fs fsSecure) Exists(path string) bool { + if err := isSecurePath(fs.unsafeFS, fs.root, path, fs.allowPrefixes...); err != nil { + return false + } + return fs.unsafeFS.Exists(path) +} + +// Glob delegates to the embedded unsafe FS, but filters the returned paths to +// only include items inside root. +func (fs fsSecure) Glob(pattern string) ([]string, error) { + paths, err := fs.unsafeFS.Glob(pattern) + if err != nil { + return nil, err + } + var securePaths []string + for _, p := range paths { + if err := isSecurePath(fs.unsafeFS, fs.root, p, fs.allowPrefixes...); err == nil { + securePaths = append(securePaths, p) + } + } + return securePaths, err +} + +// ReadFile delegates to the embedded unsafe FS after having confirmed the path +// to be inside root. If the provided path violates this constraint, an error +// of type ConstraintError is returned. +func (fs fsSecure) ReadFile(path string) ([]byte, error) { + if err := isSecurePath(fs.unsafeFS, fs.root, path, fs.allowPrefixes...); err != nil { + return nil, &ConstraintError{Op: "read", Path: path, Err: err} + } + return fs.unsafeFS.ReadFile(path) +} + +// WriteFile delegates to the embedded unsafe FS after having confirmed the +// path to be inside root. If the provided path violates this constraint, an +// error of type ConstraintError is returned. +func (fs fsSecure) WriteFile(path string, data []byte) error { + if err := isSecurePath(fs.unsafeFS, fs.root, path, fs.allowPrefixes...); err != nil { + return &ConstraintError{Op: "write", Path: path, Err: err} + } + return fs.unsafeFS.WriteFile(path, data) +} + +// Walk delegates to the embedded unsafe FS, wrapping falkFn in a callback which +// confirms the path to be inside root. If the path violates this constraint, +// an error of type ConstraintError is returned and walkFn is not called. +func (fs fsSecure) Walk(path string, walkFn filepath.WalkFunc) error { + wrapWalkFn := func(path string, info os.FileInfo, err error) error { + if err := isSecurePath(fs.unsafeFS, fs.root, path, fs.allowPrefixes...); err != nil { + return &ConstraintError{Op: "walk", Path: path, Err: err} + } + return walkFn(path, info, err) + } + return fs.unsafeFS.Walk(path, wrapWalkFn) +} + +// isSecurePath confirms the given path is inside root using the provided file +// system. At present, it assumes the file system implementation to be on disk +// and makes use of filepath.EvalSymlinks. +func isSecurePath(fs filesys.FileSystem, root filesys.ConfirmedDir, path string, allowedPrefixes ...string) error { + absRoot, err := filepath.Abs(path) + if err != nil { + return fmt.Errorf("abs path error on '%s': %v", path, err) + } + d := filesys.ConfirmedDir(absRoot) + if fs.Exists(absRoot) { + evaluated, err := filepath.EvalSymlinks(absRoot) + if err != nil { + return fmt.Errorf("evalsymlink failure on '%s': %w", path, err) + } + evaluatedDir := evaluated + if !fs.IsDir(evaluatedDir) { + evaluatedDir = filepath.Dir(evaluatedDir) + } + d = filesys.ConfirmedDir(evaluatedDir) + } + if ok, _ := hasOneOfPrefixes(d.String(), allowedPrefixes); ok { + return nil + } + if !d.HasPrefix(root) { + return rootConstraintErr(path, root.String()) + } + return nil +} + +func rootConstraintErr(path, root string) error { + return fmt.Errorf("path '%s' is not in or below '%s'", path, root) +} + +func hasOneOfPrefixes(s string, prefixes []string) (bool, string) { + for _, p := range prefixes { + if strings.HasPrefix(s, p) { + return true, p + } + } + return false, "" +} diff --git a/pkg/utils/flux_utils/kustomize/kustomize_generator.go b/pkg/utils/flux_utils/kustomize/kustomize_generator.go new file mode 100644 index 000000000..2a21e8b69 --- /dev/null +++ b/pkg/utils/flux_utils/kustomize/kustomize_generator.go @@ -0,0 +1,81 @@ +/* +Copyright 2022 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kustomize + +import ( + "fmt" + "sync" + + securefs "github.com/kluctl/kluctl/v2/pkg/utils/flux_utils/kustomize/filesys" + "sigs.k8s.io/kustomize/api/krusty" + "sigs.k8s.io/kustomize/api/resmap" + kustypes "sigs.k8s.io/kustomize/api/types" + "sigs.k8s.io/kustomize/kyaml/filesys" +) + +// buildMutex protects against kustomize concurrent map read/write panic +var kustomizeBuildMutex sync.Mutex + +// Secure Build wraps krusty.MakeKustomizer with the following settings: +// - secure on-disk FS denying operations outside root +// - load files from outside the kustomization dir path +// (but not outside root) +// - disable plugins except for the builtin ones +func SecureBuild(root, dirPath string, allowRemoteBases bool) (res resmap.ResMap, err error) { + var fs filesys.FileSystem + + // Create secure FS for root with or without remote base support + if allowRemoteBases { + fs, err = securefs.MakeFsOnDiskSecureBuild(root) + if err != nil { + return nil, err + } + } else { + fs, err = securefs.MakeFsOnDiskSecure(root) + if err != nil { + return nil, err + } + } + return Build(fs, dirPath) +} + +// Build wraps krusty.MakeKustomizer with the following settings: +// - load files from outside the kustomization.yaml root +// - disable plugins except for the builtin ones +func Build(fs filesys.FileSystem, dirPath string) (res resmap.ResMap, err error) { + // temporary workaround for concurrent map read and map write bug + // https://github.com/kubernetes-sigs/kustomize/issues/3659 + kustomizeBuildMutex.Lock() + defer kustomizeBuildMutex.Unlock() + + // Kustomize tends to panic in unpredicted ways due to (accidental) + // invalid object data; recover when this happens to ensure continuity of + // operations + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("recovered from kustomize build panic: %v", r) + } + }() + + buildOptions := &krusty.Options{ + LoadRestrictions: kustypes.LoadRestrictionsNone, + PluginConfig: kustypes.DisabledPluginConfig(), + } + + k := krusty.MakeKustomizer(buildOptions) + return k.Run(fs, dirPath) +} From 51a0116e3a286b9316031cb55cbba23123276675 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sun, 14 May 2023 22:33:50 +0200 Subject: [PATCH 1622/2916] refactor: Use metav1.Time instead of own JsonTime --- pkg/deployment/commands/result_utils.go | 5 ++-- pkg/types/result/command_result.go | 5 ++-- pkg/types/time.go | 39 ------------------------- 3 files changed, 6 insertions(+), 43 deletions(-) delete mode 100644 pkg/types/time.go diff --git a/pkg/deployment/commands/result_utils.go b/pkg/deployment/commands/result_utils.go index dba10686f..9e06da59d 100644 --- a/pkg/deployment/commands/result_utils.go +++ b/pkg/deployment/commands/result_utils.go @@ -7,6 +7,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/types/result" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "path/filepath" "time" ) @@ -14,8 +15,8 @@ import ( func addBaseCommandInfoToResult(targetCtx *kluctl_project.TargetContext, r *result.CommandResult, command string) error { r.Target = targetCtx.Target r.Command = result.CommandInfo{ - StartTime: types.FromTime(targetCtx.KluctlProject.LoadTime), - EndTime: types.FromTime(time.Now()), + StartTime: metav1.NewTime(targetCtx.KluctlProject.LoadTime), + EndTime: metav1.Now(), Command: command, Args: targetCtx.KluctlProject.LoadArgs.ExternalArgs, } diff --git a/pkg/types/result/command_result.go b/pkg/types/result/command_result.go index 141bd055d..44629c3d5 100644 --- a/pkg/types/result/command_result.go +++ b/pkg/types/result/command_result.go @@ -4,6 +4,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/utils/uo" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type Change struct { @@ -74,8 +75,8 @@ func (k TargetKey) Less(o TargetKey) bool { type CommandInfo struct { Initiator CommandInitiator `json:"initiator" validate:"oneof=CommandLine KluctlDeployment"` - StartTime types.JsonTime `json:"startTime"` - EndTime types.JsonTime `json:"endTime"` + StartTime metav1.Time `json:"startTime"` + EndTime metav1.Time `json:"endTime"` KluctlDeployment *KluctlDeploymentInfo `json:"kluctlDeployment,omitempty"` Command string `json:"command,omitempty"` Target string `json:"target,omitempty"` diff --git a/pkg/types/time.go b/pkg/types/time.go deleted file mode 100644 index 1706b8606..000000000 --- a/pkg/types/time.go +++ /dev/null @@ -1,39 +0,0 @@ -package types - -import ( - "encoding/json" - "time" -) - -type JsonTime string - -func FromTime(t time.Time) JsonTime { - return JsonTime(t.Format(time.RFC3339Nano)) -} - -func (t JsonTime) ToTime() (time.Time, error) { - t2, err := time.Parse(time.RFC3339Nano, string(t)) - if err != nil { - return time.Time{}, err - } - return t2, nil -} - -func (t *JsonTime) UnmarshalJSON(b []byte) error { - var s string - err := json.Unmarshal(b, &s) - if err != nil { - return err - } - t2 := JsonTime(s) - _, err = t2.ToTime() - if err != nil { - return err - } - *t = t2 - return nil -} - -func (t JsonTime) MarshalJSON() ([]byte, error) { - return json.Marshal(string(t)) -} From 54822d5869a0a3e9298cf8d97c9109b376e3660b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sun, 14 May 2023 22:35:29 +0200 Subject: [PATCH 1623/2916] refactor: Allow delete command without targetCtx --- cmd/kluctl/commands/cmd_delete.go | 4 +- pkg/deployment/commands/delete.go | 52 ++++++++++++++++++------- pkg/deployment/commands/result_utils.go | 29 ++++++++++++-- 3 files changed, 67 insertions(+), 18 deletions(-) diff --git a/cmd/kluctl/commands/cmd_delete.go b/cmd/kluctl/commands/cmd_delete.go index f485c134c..f93e08d14 100644 --- a/cmd/kluctl/commands/cmd_delete.go +++ b/cmd/kluctl/commands/cmd_delete.go @@ -46,9 +46,9 @@ func (cmd *deleteCmd) Run(ctx context.Context) error { commandResultFlags: &cmd.CommandResultFlags, } return withProjectCommandContext(ctx, ptArgs, func(cmdCtx *commandCtx) error { - cmd2 := commands.NewDeleteCommand(cmd.Discriminator, cmdCtx.targetCtx) + cmd2 := commands.NewDeleteCommand(cmd.Discriminator, cmdCtx.targetCtx, nil) - result, err := cmd2.Run(func(refs []k8s2.ObjectRef) error { + result, err := cmd2.Run(cmdCtx.targetCtx.SharedContext.Ctx, cmdCtx.targetCtx.SharedContext.K, func(refs []k8s2.ObjectRef) error { return confirmDeletion(ctx, refs, cmd.DryRun, cmd.Yes) }) if err != nil { diff --git a/pkg/deployment/commands/delete.go b/pkg/deployment/commands/delete.go index ffd08dae1..11c49e8bb 100644 --- a/pkg/deployment/commands/delete.go +++ b/pkg/deployment/commands/delete.go @@ -1,30 +1,44 @@ package commands import ( + "context" "fmt" "github.com/google/uuid" + "github.com/kluctl/kluctl/v2/pkg/deployment" utils2 "github.com/kluctl/kluctl/v2/pkg/deployment/utils" + "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/kluctl_project" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/types/result" + "github.com/kluctl/kluctl/v2/pkg/utils" + "time" ) type DeleteCommand struct { discriminator string targetCtx *kluctl_project.TargetContext + inclusion *utils.Inclusion } -func NewDeleteCommand(discriminator string, targetCtx *kluctl_project.TargetContext) *DeleteCommand { +func NewDeleteCommand(discriminator string, targetCtx *kluctl_project.TargetContext, inclusion *utils.Inclusion) *DeleteCommand { return &DeleteCommand{ discriminator: discriminator, targetCtx: targetCtx, + inclusion: inclusion, } } -func (cmd *DeleteCommand) Run(confirmCb func(refs []k8s2.ObjectRef) error) (*result.CommandResult, error) { - discriminator := cmd.targetCtx.Target.Discriminator - if cmd.discriminator != "" { - discriminator = cmd.discriminator +func (cmd *DeleteCommand) Run(ctx context.Context, k *k8s.K8sCluster, confirmCb func(refs []k8s2.ObjectRef) error) (*result.CommandResult, error) { + startTime := time.Now() + + inclusion := cmd.inclusion + if inclusion == nil && cmd.targetCtx != nil { + inclusion = cmd.targetCtx.DeploymentCollection.Inclusion + } + + discriminator := cmd.discriminator + if discriminator == "" && cmd.targetCtx != nil { + discriminator = cmd.targetCtx.Target.Discriminator } if discriminator == "" { @@ -32,13 +46,13 @@ func (cmd *DeleteCommand) Run(confirmCb func(refs []k8s2.ObjectRef) error) (*res } dew := utils2.NewDeploymentErrorsAndWarnings() - ru := utils2.NewRemoteObjectsUtil(cmd.targetCtx.SharedContext.Ctx, dew) - err := ru.UpdateRemoteObjects(cmd.targetCtx.SharedContext.K, &discriminator, nil, false) + ru := utils2.NewRemoteObjectsUtil(ctx, dew) + err := ru.UpdateRemoteObjects(k, &discriminator, nil, false) if err != nil { return nil, err } - deleteRefs, err := utils2.FindObjectsForDelete(cmd.targetCtx.SharedContext.K, ru.GetFilteredRemoteObjects(cmd.targetCtx.DeploymentCollection.Inclusion), cmd.targetCtx.DeploymentCollection.Inclusion.HasType("tags"), nil) + deleteRefs, err := utils2.FindObjectsForDelete(k, ru.GetFilteredRemoteObjects(inclusion), inclusion.HasType("tags"), nil) if err != nil { return nil, err } @@ -50,20 +64,32 @@ func (cmd *DeleteCommand) Run(confirmCb func(refs []k8s2.ObjectRef) error) (*res } } - deleted, err := utils2.DeleteObjects(cmd.targetCtx.SharedContext.Ctx, cmd.targetCtx.SharedContext.K, deleteRefs, dew, true) + deleted, err := utils2.DeleteObjects(ctx, k, deleteRefs, dew, true) if err != nil { return nil, err } + var c *deployment.DeploymentCollection + if cmd.targetCtx != nil { + c = cmd.targetCtx.DeploymentCollection + } + r := &result.CommandResult{ Id: uuid.New().String(), - Objects: collectObjects(cmd.targetCtx.DeploymentCollection, ru, nil, nil, nil, deleted), + Objects: collectObjects(c, ru, nil, nil, nil, deleted), Errors: dew.GetErrorsList(), Warnings: dew.GetWarningsList(), } - err = addBaseCommandInfoToResult(cmd.targetCtx, r, "delete") - if err != nil { - return r, err + if cmd.targetCtx != nil { + err = addBaseCommandInfoToResult(cmd.targetCtx, r, "delete") + if err != nil { + return r, err + } + } else { + err = addDeleteCommandInfoToResult(r, k, startTime, inclusion) + if err != nil { + return r, err + } } return r, nil } diff --git a/pkg/deployment/commands/result_utils.go b/pkg/deployment/commands/result_utils.go index 9e06da59d..ecd108d56 100644 --- a/pkg/deployment/commands/result_utils.go +++ b/pkg/deployment/commands/result_utils.go @@ -3,10 +3,12 @@ package commands import ( "fmt" git2 "github.com/go-git/go-git/v5" + k8s2 "github.com/kluctl/kluctl/v2/pkg/k8s" "github.com/kluctl/kluctl/v2/pkg/kluctl_project" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/types/result" + "github.com/kluctl/kluctl/v2/pkg/utils" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "path/filepath" "time" @@ -36,7 +38,7 @@ func addBaseCommandInfoToResult(targetCtx *kluctl_project.TargetContext, r *resu return err } - err = addClusterInfo(targetCtx, r) + err = addClusterInfo(targetCtx.SharedContext.K, r) if err != nil { return err } @@ -44,6 +46,27 @@ func addBaseCommandInfoToResult(targetCtx *kluctl_project.TargetContext, r *resu r.TargetKey.TargetName = targetCtx.Target.Name r.TargetKey.Discriminator = targetCtx.Target.Discriminator r.TargetKey.ClusterId = r.ClusterInfo.ClusterId + + return nil +} + +func addDeleteCommandInfoToResult(r *result.CommandResult, k *k8s2.K8sCluster, startTime time.Time, inclusion *utils.Inclusion) error { + r.Command = result.CommandInfo{ + StartTime: metav1.NewTime(startTime), + EndTime: metav1.Now(), + Command: "delete", + } + + r.Command.IncludeTags = inclusion.GetIncludes("tags") + r.Command.ExcludeTags = inclusion.GetExcludes("tags") + r.Command.IncludeDeploymentDirs = inclusion.GetIncludes("deploymentItemDir") + r.Command.ExcludeDeploymentDirs = inclusion.GetExcludes("deploymentItemDir") + + err := addClusterInfo(k, r) + if err != nil { + return err + } + return nil } @@ -117,8 +140,8 @@ func addGitInfo(targetCtx *kluctl_project.TargetContext, r *result.CommandResult return nil } -func addClusterInfo(targetCtx *kluctl_project.TargetContext, r *result.CommandResult) error { - kubeSystemNs, _, err := targetCtx.SharedContext.K.GetSingleObject( +func addClusterInfo(k *k8s2.K8sCluster, r *result.CommandResult) error { + kubeSystemNs, _, err := k.GetSingleObject( k8s.NewObjectRef("", "v1", "Namespace", "kube-system", "")) if err != nil { return err From 1b7645e261aa2804aa912a4ab7b87568eaaafbf2 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sun, 14 May 2023 22:55:45 +0200 Subject: [PATCH 1624/2916] refactor: Use apiextensionsv1.JSON for any types --- e2e/args_test.go | 12 ++++---- pkg/deployment/external_args.go | 8 ++++- pkg/deployment/utils/diff_utils_test.go | 40 ++++++++++++++++++++----- pkg/diff/diff.go | 40 ++++++++++++++++++++++--- pkg/diff/obfuscate.go | 25 ++++++++++++---- pkg/results/result-store-secrets.go | 2 +- pkg/results/results-collector.go | 2 +- pkg/types/kluctl_project.go | 5 ++-- pkg/types/result/command_result.go | 11 +++---- 9 files changed, 113 insertions(+), 32 deletions(-) diff --git a/e2e/args_test.go b/e2e/args_test.go index 5f689f624..b52698b47 100644 --- a/e2e/args_test.go +++ b/e2e/args_test.go @@ -8,7 +8,7 @@ import ( "testing" ) -func testArgs(t *testing.T) { +func TestArgs(t *testing.T) { t.Parallel() k := defaultCluster1 @@ -34,6 +34,10 @@ func testArgs(t *testing.T) { "nested": "default", }, }, + map[string]any{ + "name": "e", + "default": 42, + }, } p.UpdateKluctlYaml(func(o *uo.UnstructuredObject) error { @@ -46,6 +50,7 @@ func testArgs(t *testing.T) { "b": `{{ args.b | default("na") }}`, "c": `{{ args.c | default("na") }}`, "d": "{{ args.d | to_json }}", + "e": "{{ args.e + 1 }}", }, resourceOpts{ name: "cm", namespace: p.TestSlug(), @@ -57,6 +62,7 @@ func testArgs(t *testing.T) { assertNestedFieldEquals(t, cm, "default", "data", "b") assertNestedFieldEquals(t, cm, "na", "data", "c") assertNestedFieldEquals(t, cm, `{"nested": "default"}`, "data", "d") + assertNestedFieldEquals(t, cm, `43`, "data", "e") p.KluctlMust("deploy", "--yes", "-t", "test", "-aa=a", "-ab=b") cm = k.MustGetCoreV1(t, "configmaps", p.TestSlug(), "cm") @@ -112,10 +118,6 @@ d: assertNestedFieldEquals(t, cm, `{"nested": {"nested2": "d4"}}`, "data", "d") } -func TestArgs(t *testing.T) { - testArgs(t) -} - func TestArgsFromEnv(t *testing.T) { k := defaultCluster1 diff --git a/pkg/deployment/external_args.go b/pkg/deployment/external_args.go index e041ebdfb..808d65dbf 100644 --- a/pkg/deployment/external_args.go +++ b/pkg/deployment/external_args.go @@ -1,6 +1,7 @@ package deployment import ( + "encoding/json" "fmt" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils/uo" @@ -59,8 +60,13 @@ func LoadDefaultArgs(args []*types.DeploymentArg, deployArgs *uo.UnstructuredObj defaults := uo.New() for _, a := range args { if a.Default != nil { + var v any + err := json.Unmarshal(a.Default.Raw, &v) + if err != nil { + return err + } a2 := uo.FromMap(map[string]interface{}{ - a.Name: a.Default, + a.Name: v, }) defaults.Merge(a2) } diff --git a/pkg/deployment/utils/diff_utils_test.go b/pkg/deployment/utils/diff_utils_test.go index 430d39427..60e3fa6e9 100644 --- a/pkg/deployment/utils/diff_utils_test.go +++ b/pkg/deployment/utils/diff_utils_test.go @@ -2,11 +2,13 @@ package utils import ( "context" + "encoding/json" "github.com/kluctl/kluctl/v2/pkg/deployment" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/types/result" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/stretchr/testify/assert" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "testing" ) @@ -56,6 +58,28 @@ func newTestConfigMap(name string, data map[string]interface{}) *uo.Unstructured } func TestDiff(t *testing.T) { + buildRaw := func(x any) *apiextensionsv1.JSON { + if x == nil { + return nil + } + b, err := json.Marshal(x) + if err != nil { + t.Fatal(err) + } + return &apiextensionsv1.JSON{ + Raw: b, + } + } + buildChange := func(typ string, jsonPath string, oldValue any, newValue any, unifiedDiff string) result.Change { + return result.Change{ + Type: typ, + JsonPath: jsonPath, + NewValue: buildRaw(newValue), + OldValue: buildRaw(oldValue), + UnifiedDiff: unifiedDiff, + } + } + tests := []*diffTestConfig{ { name: "One changed object (changed field)", @@ -65,7 +89,7 @@ func TestDiff(t *testing.T) { a: func(t *testing.T, dtc *diffTestConfig) { assert.Len(t, dtc.du.ChangedObjects, 1) assert.Equal(t, []result.Change{ - result.Change{Type: "update", JsonPath: "data.d1", OldValue: "v1", NewValue: "v2", UnifiedDiff: "-v1\n+v2"}, + buildChange("update", "data.d1", buildRaw("v1"), buildRaw("v2"), "-v1\n+v2"), }, dtc.du.ChangedObjects[0].Changes) }, }, @@ -77,7 +101,7 @@ func TestDiff(t *testing.T) { a: func(t *testing.T, dtc *diffTestConfig) { assert.Len(t, dtc.du.ChangedObjects, 1) assert.Equal(t, []result.Change{ - result.Change{Type: "insert", JsonPath: "data.d2", OldValue: interface{}(nil), NewValue: "v2", UnifiedDiff: "+v2"}, + buildChange("insert", "data.d2", nil, buildRaw("v2"), "+v2"), }, dtc.du.ChangedObjects[0].Changes) }, }, @@ -89,7 +113,7 @@ func TestDiff(t *testing.T) { a: func(t *testing.T, dtc *diffTestConfig) { assert.Len(t, dtc.du.ChangedObjects, 1) assert.Equal(t, []result.Change{ - result.Change{Type: "delete", JsonPath: "data.d2", OldValue: "v2", NewValue: interface{}(nil), UnifiedDiff: "-v2"}, + buildChange("delete", "data.d2", buildRaw("v2"), nil, "-v2"), }, dtc.du.ChangedObjects[0].Changes) }, }, @@ -101,8 +125,8 @@ func TestDiff(t *testing.T) { a: func(t *testing.T, dtc *diffTestConfig) { assert.Len(t, dtc.du.ChangedObjects, 1) assert.Equal(t, []result.Change{ - result.Change{Type: "delete", JsonPath: "data.d1", OldValue: "v1", NewValue: interface{}(nil), UnifiedDiff: "-v1"}, - result.Change{Type: "insert", JsonPath: "data.d2", OldValue: interface{}(nil), NewValue: "v2", UnifiedDiff: "+v2"}, + buildChange("delete", "data.d1", buildRaw("v1"), nil, "-v1"), + buildChange("insert", "data.d2", nil, buildRaw("v2"), "+v2"), }, dtc.du.ChangedObjects[0].Changes) }, }, @@ -114,9 +138,9 @@ func TestDiff(t *testing.T) { a: func(t *testing.T, dtc *diffTestConfig) { assert.Len(t, dtc.du.ChangedObjects, 1) assert.Equal(t, []result.Change{ - result.Change{Type: "update", JsonPath: "data.d1", OldValue: "v1", NewValue: "v12", UnifiedDiff: "-v1\n+v12"}, - result.Change{Type: "delete", JsonPath: "data.d2", OldValue: "v2", NewValue: interface{}(nil), UnifiedDiff: "-v2"}, - result.Change{Type: "insert", JsonPath: "data.d3", OldValue: interface{}(nil), NewValue: "v3", UnifiedDiff: "+v3"}, + buildChange("update", "data.d1", buildRaw("v1"), buildRaw("v12"), "-v1\n+v12"), + buildChange("delete", "data.d2", buildRaw("v2"), nil, "-v2"), + buildChange("insert", "data.d3", nil, buildRaw("v3"), "+v3"), }, dtc.du.ChangedObjects[0].Changes) }, }, diff --git a/pkg/diff/diff.go b/pkg/diff/diff.go index f654b0507..0d9accd67 100644 --- a/pkg/diff/diff.go +++ b/pkg/diff/diff.go @@ -9,6 +9,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" diff2 "github.com/r3labs/diff/v2" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "reflect" "sort" "strconv" @@ -73,31 +74,47 @@ func convertChange(c diff2.Change, oldObject *uo.UnstructuredObject, newObject * if err != nil { return nil, err } + jto, err := yaml.WriteJsonString(c.To) + if err != nil { + return nil, err + } return &result.Change{ Type: "insert", JsonPath: p, - NewValue: c.To, + NewValue: &apiextensionsv1.JSON{Raw: []byte(jto)}, }, nil case "delete": p, err := convertPath(c.Path, oldObject.Object) if err != nil { return nil, err } + jfrom, err := yaml.WriteJsonString(c.From) + if err != nil { + return nil, err + } return &result.Change{ Type: "delete", JsonPath: p, - OldValue: c.From, + OldValue: &apiextensionsv1.JSON{Raw: []byte(jfrom)}, }, nil case "update": p, err := convertPath(c.Path, newObject.Object) if err != nil { return nil, err } + jto, err := yaml.WriteJsonString(c.To) + if err != nil { + return nil, err + } + jfrom, err := yaml.WriteJsonString(c.From) + if err != nil { + return nil, err + } return &result.Change{ Type: "update", JsonPath: p, - NewValue: c.To, - OldValue: c.From, + NewValue: &apiextensionsv1.JSON{Raw: []byte(jto)}, + OldValue: &apiextensionsv1.JSON{Raw: []byte(jfrom)}, }, nil } return nil, fmt.Errorf("unknown change type %s", c.Type) @@ -204,6 +221,21 @@ func objectToDiffableStringNoType(o interface{}) (string, error) { if o == notPresent { return "", nil } + + if reflect.TypeOf(reflect.Indirect(reflect.ValueOf(o))).Kind() == reflect.Struct { + // writing and re-reading yaml to normalise custom serialization + s, err := yaml.WriteYamlString(o) + if err != nil { + return "", err + } + var o2 any + err = yaml.ReadYamlString(s, &o2) + if err != nil { + return "", err + } + o = o2 + } + if v, ok := o.(string); ok { return v, nil } diff --git a/pkg/diff/obfuscate.go b/pkg/diff/obfuscate.go index ea16017d9..f8f1a7fc8 100644 --- a/pkg/diff/obfuscate.go +++ b/pkg/diff/obfuscate.go @@ -2,11 +2,13 @@ package diff import ( "encoding/base64" + "encoding/json" "fmt" "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/types/result" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/ohler55/ojg/jp" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/runtime/schema" "strings" ) @@ -65,22 +67,35 @@ func (o *Obfuscator) ObfuscateObject(x *uo.UnstructuredObject) (*uo.Unstructured } func (o *Obfuscator) obfuscateSecretChanges(ref k8s.ObjectRef, changes []result.Change) error { - replaceValues := func(x any, v string) any { - if x == nil { + replaceValues := func(j *apiextensionsv1.JSON, v string) *apiextensionsv1.JSON { + if j == nil { return nil } + + var x any + err := json.Unmarshal(j.Raw, &x) + if err != nil { + return nil + } + if m, ok := x.(map[string]any); ok { for k, _ := range m { m[k] = v } - return m } else if a, ok := x.([]any); ok { for i, _ := range a { a[i] = v } - return a + } else { + x = v } - return v + + b, err := json.Marshal(x) + if err != nil { + return nil + } + + return &apiextensionsv1.JSON{Raw: b} } for i, _ := range changes { diff --git a/pkg/results/result-store-secrets.go b/pkg/results/result-store-secrets.go index ab1db402a..f918e5b2e 100644 --- a/pkg/results/result-store-secrets.go +++ b/pkg/results/result-store-secrets.go @@ -276,7 +276,7 @@ func (s *ResultStoreSecrets) ListCommandResultSummaries(options ListCommandResul } sort.Slice(ret, func(i, j int) bool { - return ret[i].Command.StartTime >= ret[j].Command.StartTime + return ret[i].Command.StartTime.After(ret[j].Command.StartTime.Time) }) return ret, nil diff --git a/pkg/results/results-collector.go b/pkg/results/results-collector.go index 583a5b9b2..5ae9cbd4a 100644 --- a/pkg/results/results-collector.go +++ b/pkg/results/results-collector.go @@ -111,7 +111,7 @@ func (rc *ResultsCollector) ListCommandResultSummaries(options ListCommandResult summaries = append(summaries, *x.summary) } sort.Slice(summaries, func(i, j int) bool { - return summaries[i].Command.StartTime >= summaries[j].Command.StartTime + return summaries[i].Command.StartTime.After(summaries[j].Command.StartTime.Time) }) return summaries, nil } diff --git a/pkg/types/kluctl_project.go b/pkg/types/kluctl_project.go index 83aa87f8d..c8293da48 100644 --- a/pkg/types/kluctl_project.go +++ b/pkg/types/kluctl_project.go @@ -2,6 +2,7 @@ package types import ( "github.com/kluctl/kluctl/v2/pkg/utils/uo" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" ) type SealingConfig struct { @@ -20,8 +21,8 @@ type Target struct { } type DeploymentArg struct { - Name string `json:"name" validate:"required"` - Default interface{} `json:"default,omitempty"` + Name string `json:"name" validate:"required"` + Default *apiextensionsv1.JSON `json:"default,omitempty"` } type SecretSet struct { diff --git a/pkg/types/result/command_result.go b/pkg/types/result/command_result.go index 44629c3d5..80d30e378 100644 --- a/pkg/types/result/command_result.go +++ b/pkg/types/result/command_result.go @@ -4,15 +4,16 @@ import ( "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/utils/uo" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type Change struct { - Type string `json:"type" validate:"required"` - JsonPath string `json:"jsonPath" validate:"required"` - OldValue interface{} `json:"oldValue,omitempty"` - NewValue interface{} `json:"newValue,omitempty"` - UnifiedDiff string `json:"unifiedDiff,omitempty"` + Type string `json:"type" validate:"required"` + JsonPath string `json:"jsonPath" validate:"required"` + OldValue *apiextensionsv1.JSON `json:"oldValue,omitempty"` + NewValue *apiextensionsv1.JSON `json:"newValue,omitempty"` + UnifiedDiff string `json:"unifiedDiff,omitempty"` } type ChangedObject struct { From 2618516cb084cc0591ccfa9830fedd53cf370de7 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 15 May 2023 12:05:19 +0200 Subject: [PATCH 1625/2916] refactor: Allow to call KluctlExecute without a project --- e2e/test-utils/kluctl_execute.go | 46 ++++++++++++++++++++++++++++++++ e2e/test-utils/project.go | 38 +------------------------- 2 files changed, 47 insertions(+), 37 deletions(-) create mode 100644 e2e/test-utils/kluctl_execute.go diff --git a/e2e/test-utils/kluctl_execute.go b/e2e/test-utils/kluctl_execute.go new file mode 100644 index 000000000..bab9a76e2 --- /dev/null +++ b/e2e/test-utils/kluctl_execute.go @@ -0,0 +1,46 @@ +package test_utils + +import ( + "bytes" + "context" + "github.com/kluctl/kluctl/v2/cmd/kluctl/commands" + "github.com/kluctl/kluctl/v2/pkg/status" + "github.com/kluctl/kluctl/v2/pkg/utils" + "strings" + "sync" + "testing" +) + +func KluctlExecute(t *testing.T, ctx context.Context, args ...string) (string, string, error) { + t.Logf("Runnning kluctl: %s", strings.Join(args, " ")) + + var m sync.Mutex + stdoutBuf := bytes.NewBuffer(nil) + stdout := status.NewLineRedirector(func(line string) { + m.Lock() + defer m.Unlock() + t.Log(line) + stdoutBuf.WriteString(line + "\n") + }) + stderrBuf := bytes.NewBuffer(nil) + + ctx = utils.WithTmpBaseDir(ctx, t.TempDir()) + ctx = commands.WithStdStreams(ctx, stdout, stderrBuf) + sh := status.NewSimpleStatusHandler(func(message string) { + m.Lock() + defer m.Unlock() + t.Log(message) + stderrBuf.WriteString(message + "\n") + }, false, true) + defer func() { + if sh != nil { + sh.Stop() + } + }() + ctx = status.NewContext(ctx, sh) + err := commands.Execute(ctx, args, nil) + sh.Stop() + sh = nil + _ = stdout.Close() + return stdoutBuf.String(), stderrBuf.String(), err +} diff --git a/e2e/test-utils/project.go b/e2e/test-utils/project.go index ae402a1b9..e8c33d1ec 100644 --- a/e2e/test-utils/project.go +++ b/e2e/test-utils/project.go @@ -1,16 +1,12 @@ package test_utils import ( - "bytes" "context" "fmt" "github.com/go-git/go-git/v5" "github.com/huandu/xstrings" "github.com/jinzhu/copier" - "github.com/kluctl/kluctl/v2/cmd/kluctl/commands" git2 "github.com/kluctl/kluctl/v2/pkg/git" - "github.com/kluctl/kluctl/v2/pkg/status" - "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/yaml" registry2 "helm.sh/helm/v3/pkg/registry" @@ -22,7 +18,6 @@ import ( "path/filepath" "reflect" "strings" - "sync" "testing" ) @@ -459,38 +454,7 @@ func (p *TestProject) KluctlExecute(argsIn ...string) (string, string, error) { args = append(args, "--project-dir", p.LocalProjectDir()) args = append(args, argsIn...) - p.t.Logf("Runnning kluctl: %s", strings.Join(args, " ")) - - var m sync.Mutex - stdoutBuf := bytes.NewBuffer(nil) - stdout := status.NewLineRedirector(func(line string) { - m.Lock() - defer m.Unlock() - p.t.Log(line) - stdoutBuf.WriteString(line + "\n") - }) - stderrBuf := bytes.NewBuffer(nil) - - ctx := context.Background() - ctx = utils.WithTmpBaseDir(ctx, p.t.TempDir()) - ctx = commands.WithStdStreams(ctx, stdout, stderrBuf) - sh := status.NewSimpleStatusHandler(func(message string) { - m.Lock() - defer m.Unlock() - p.t.Log(message) - stderrBuf.WriteString(message + "\n") - }, false, true) - defer func() { - if sh != nil { - sh.Stop() - } - }() - ctx = status.NewContext(ctx, sh) - err := commands.Execute(ctx, args, nil) - sh.Stop() - sh = nil - _ = stdout.Close() - return stdoutBuf.String(), stderrBuf.String(), err + return KluctlExecute(p.t, context.Background(), args...) } func (p *TestProject) Kluctl(argsIn ...string) (string, string, error) { From 732378e75386212cb697759a7486036498928b59 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 3 May 2023 23:59:56 +0200 Subject: [PATCH 1626/2916] fix: Allow to use non-exported fields in command structs --- cmd/kluctl/commands/cobra_utils.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cmd/kluctl/commands/cobra_utils.go b/cmd/kluctl/commands/cobra_utils.go index 173303bfe..d4b4fb013 100644 --- a/cmd/kluctl/commands/cobra_utils.go +++ b/cmd/kluctl/commands/cobra_utils.go @@ -100,6 +100,9 @@ func (c *rootCommand) buildCobraSubCommands(cg *commandAndGroups, cmdStruct inte t := v.Type() for i := 0; i < t.NumField(); i++ { f := t.Field(i) + if !f.IsExported() { + continue + } v2 := v.Field(i).Addr().Interface() name := buildCobraName(f.Name) @@ -126,6 +129,9 @@ func (c *rootCommand) buildCobraArgs(cg *commandAndGroups, cmdStruct interface{} t := v.Type() for i := 0; i < t.NumField(); i++ { f := t.Field(i) + if !f.IsExported() { + continue + } if _, ok := f.Tag.Lookup("cmd"); ok { continue } From 4a6aee2e7054cb7940ff4ce092dbfdc14e59559e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 15 May 2023 14:07:21 +0200 Subject: [PATCH 1627/2916] refactor: Move main.go to cmd/ --- main.go => cmd/main.go | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename main.go => cmd/main.go (100%) diff --git a/main.go b/cmd/main.go similarity index 100% rename from main.go rename to cmd/main.go From b1c73cc7ed05e042062fb56e602a4b4435a06a4c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 15 May 2023 14:09:37 +0200 Subject: [PATCH 1628/2916] fix: Make code controller-gen compatible --- e2e/default_clusters.go | 1 - e2e/hooks_test.go | 2 +- e2e/utils_resources.go | 4 ++-- e2e/validate_test.go | 2 +- pkg/types/doc.go | 19 +++++++++++++++++++ pkg/types/git_url.go | 8 ++++++++ pkg/types/k8s/ref.go | 1 + pkg/types/result/doc.go | 19 +++++++++++++++++++ pkg/types/yaml_url.go | 7 +++++++ pkg/utils/uo/k8s_fields.go | 1 - pkg/utils/uo/uo.go | 11 ++++++++++- 11 files changed, 68 insertions(+), 7 deletions(-) create mode 100644 pkg/types/doc.go create mode 100644 pkg/types/result/doc.go diff --git a/e2e/default_clusters.go b/e2e/default_clusters.go index b50159416..373957a89 100644 --- a/e2e/default_clusters.go +++ b/e2e/default_clusters.go @@ -1,6 +1,5 @@ package e2e -import "C" import ( "fmt" "github.com/imdario/mergo" diff --git a/e2e/hooks_test.go b/e2e/hooks_test.go index 504559c23..994174ff3 100644 --- a/e2e/hooks_test.go +++ b/e2e/hooks_test.go @@ -96,7 +96,7 @@ func (s *hooksTestContext) addConfigMap(dir string, opts resourceOpts) { mergeMetadata(o, opts) o.SetNestedField(map[string]interface{}{}, "data") s.p.AddKustomizeResources(dir, []test_utils.KustomizeResource{ - {fmt.Sprintf("%s.yml", opts.name), "", o}, + {Name: fmt.Sprintf("%s.yml", opts.name), Content: o}, }) } diff --git a/e2e/utils_resources.go b/e2e/utils_resources.go index 928627aa6..a78a2d0fd 100644 --- a/e2e/utils_resources.go +++ b/e2e/utils_resources.go @@ -57,7 +57,7 @@ func createSecretObject(data map[string]string, opts resourceOpts) *uo.Unstructu func addConfigMapDeployment(p *test_utils.TestProject, dir string, data map[string]string, opts resourceOpts) { o := createConfigMapObject(data, opts) p.AddKustomizeDeployment(dir, []test_utils.KustomizeResource{ - {fmt.Sprintf("configmap-%s.yml", opts.name), "", o}, + {Name: fmt.Sprintf("configmap-%s.yml", opts.name), Content: o}, }, opts.tags) if opts.when != "" { p.UpdateDeploymentItems(filepath.Dir(dir), func(items []*uo.UnstructuredObject) []*uo.UnstructuredObject { @@ -75,7 +75,7 @@ func addSecretDeployment(p *test_utils.TestProject, dir string, data map[string] o := createSecretObject(data, opts) fname := fmt.Sprintf("secret-%s.yml", opts.name) p.AddKustomizeDeployment(dir, []test_utils.KustomizeResource{ - {fname, fname + sealmeExt, o}, + {Name: fname, FileName: fname + sealmeExt, Content: o}, }, opts.tags) if opts.when != "" { p.UpdateDeploymentItems(filepath.Dir(dir), func(items []*uo.UnstructuredObject) []*uo.UnstructuredObject { diff --git a/e2e/validate_test.go b/e2e/validate_test.go index 9fdb6cf5c..1eec3fd3e 100644 --- a/e2e/validate_test.go +++ b/e2e/validate_test.go @@ -72,7 +72,7 @@ func prepareValidateTest(t *testing.T, k *test_utils.EnvTestCluster) *test_utils p.UpdateTarget("test", nil) p.AddKustomizeDeployment("d1", []test_utils.KustomizeResource{ - {fmt.Sprintf("configmap-%s.yml", "d1"), "", buildDeployment("d1", p.TestSlug(), false)}, + {Name: fmt.Sprintf("configmap-%s.yml", "d1"), Content: buildDeployment("d1", p.TestSlug(), false)}, }, nil) return p diff --git a/pkg/types/doc.go b/pkg/types/doc.go new file mode 100644 index 000000000..b4756cc8a --- /dev/null +++ b/pkg/types/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2023. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package types contains types used in Kluctl projects +// +kubebuilder:object:generate=true +package types diff --git a/pkg/types/git_url.go b/pkg/types/git_url.go index f7fa8c6a7..44db3e65c 100644 --- a/pkg/types/git_url.go +++ b/pkg/types/git_url.go @@ -8,6 +8,7 @@ import ( "strings" ) +// +kubebuilder:validation:Type=string type GitUrl struct { url.URL `json:"-"` } @@ -28,6 +29,13 @@ func ParseGitUrlMust(u string) *GitUrl { return u2 } +func (in *GitUrl) DeepCopyInto(out *GitUrl) { + out.URL = in.URL + if out.URL.User != nil { + out.URL.User = &*in.URL.User + } +} + func (u *GitUrl) UnmarshalJSON(b []byte) error { var s string err := json.Unmarshal(b, &s) diff --git a/pkg/types/k8s/ref.go b/pkg/types/k8s/ref.go index b257ca428..478e2bc42 100644 --- a/pkg/types/k8s/ref.go +++ b/pkg/types/k8s/ref.go @@ -1,3 +1,4 @@ +// +kubebuilder:object:generate=true package k8s import ( diff --git a/pkg/types/result/doc.go b/pkg/types/result/doc.go new file mode 100644 index 000000000..ec02a69c4 --- /dev/null +++ b/pkg/types/result/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2023. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package result contains result types used in Kluctl projects +// +kubebuilder:object:generate=true +package result diff --git a/pkg/types/yaml_url.go b/pkg/types/yaml_url.go index 59560e2df..f2ffac1df 100644 --- a/pkg/types/yaml_url.go +++ b/pkg/types/yaml_url.go @@ -26,3 +26,10 @@ func (u *YamlUrl) UnmarshalJSON(b []byte) error { func (u YamlUrl) MarshalJSON() ([]byte, error) { return json.Marshal(u.String()) } + +func (in *YamlUrl) DeepCopyInto(out *YamlUrl) { + out.URL = in.URL + if out.URL.User != nil { + out.URL.User = &*in.URL.User + } +} diff --git a/pkg/utils/uo/k8s_fields.go b/pkg/utils/uo/k8s_fields.go index c45e23f57..e3b507121 100644 --- a/pkg/utils/uo/k8s_fields.go +++ b/pkg/utils/uo/k8s_fields.go @@ -251,5 +251,4 @@ func (ui *UnstructuredObject) getRegexp(r interface{}) *regexp.Regexp { } } panic(fmt.Sprintf("unknown type %s", reflect.TypeOf(r).String())) - return nil } diff --git a/pkg/utils/uo/uo.go b/pkg/utils/uo/uo.go index 7983e3f23..92d2e73d7 100644 --- a/pkg/utils/uo/uo.go +++ b/pkg/utils/uo/uo.go @@ -9,8 +9,9 @@ import ( "reflect" ) +// +kubebuilder:pruning:PreserveUnknownFields type UnstructuredObject struct { - Object map[string]interface{} `json:"object,omitempty,inline"` + Object map[string]interface{} `json:"-"` } func (uo *UnstructuredObject) MarshalJSON() ([]byte, error) { @@ -145,6 +146,14 @@ func (uo *UnstructuredObject) Clone() *UnstructuredObject { return FromMap(c) } +func (uo *UnstructuredObject) DeepCopyInto(out *UnstructuredObject) { + *out = *uo.Clone() +} + +func (uo *UnstructuredObject) DeepCopy() *UnstructuredObject { + return uo.Clone() +} + func (uo *UnstructuredObject) Merge(other *UnstructuredObject) { MergeMap(uo.Object, other.Object) } From dc3e5fa3a3031691187573f0861026213be828c4 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 15 May 2023 14:10:42 +0200 Subject: [PATCH 1629/2916] tests: Add controller-runtime client and CRD loading to EnvTestCluster --- e2e/test-utils/envtest_cluster.go | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/e2e/test-utils/envtest_cluster.go b/e2e/test-utils/envtest_cluster.go index df6893c7d..158b79e65 100644 --- a/e2e/test-utils/envtest_cluster.go +++ b/e2e/test-utils/envtest_cluster.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "fmt" + kluctlv1 "github.com/kluctl/kluctl/v2/api/v1beta1" "github.com/kluctl/kluctl/v2/pkg/utils/uo" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" @@ -13,6 +14,7 @@ import ( "k8s.io/client-go/rest" "k8s.io/client-go/util/flowcontrol" "net/http" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/envtest" "sigs.k8s.io/controller-runtime/pkg/webhook" "sync" @@ -20,6 +22,8 @@ import ( ) type EnvTestCluster struct { + CRDDirectoryPaths []string + env envtest.Environment started bool @@ -30,6 +34,7 @@ type EnvTestCluster struct { HttpClient *http.Client DynamicClient dynamic.Interface + Client client.Client ServerVersion *version.Info callbackServer webhook.Server @@ -47,12 +52,16 @@ func CreateEnvTestCluster(context string) *EnvTestCluster { } func (k *EnvTestCluster) Start() error { + k.env.CRDDirectoryPaths = k.CRDDirectoryPaths + _, err := k.env.Start() if err != nil { return err } k.started = true + _ = kluctlv1.AddToScheme(k.env.Scheme) + err = k.startCallbackServer() if err != nil { return err @@ -76,11 +85,11 @@ func (k *EnvTestCluster) Start() error { k.Kubeconfig = kcfg - client, err := rest.HTTPClientFor(k.config) + httpClient, err := rest.HTTPClientFor(k.config) if err != nil { return err } - k.HttpClient = client + k.HttpClient = httpClient dynamicClient, err := dynamic.NewForConfigAndClient(k.config, k.HttpClient) if err != nil { @@ -88,7 +97,12 @@ func (k *EnvTestCluster) Start() error { } k.DynamicClient = dynamicClient - discoveryClient, err := discovery.NewDiscoveryClientForConfigAndClient(k.config, client) + c, err := client.New(k.config, client.Options{ + HTTPClient: httpClient, + }) + k.Client = c + + discoveryClient, err := discovery.NewDiscoveryClientForConfigAndClient(k.config, httpClient) if err != nil { return err } From 3ce754edbcc1b912fbb04ad3efb8649a9bda86c2 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 15 May 2023 14:12:50 +0200 Subject: [PATCH 1630/2916] feat: Remove flux subcommands --- cmd/kluctl/commands/cmd_flux.go | 70 - cmd/kluctl/commands/cmd_flux_reconcile.go | 108 - cmd/kluctl/commands/cmd_flux_resume.go | 48 - cmd/kluctl/commands/cmd_flux_suspend.go | 47 - cmd/kluctl/commands/root.go | 2 - e2e/flux_test.go | 94 - e2e/test_resources/README.md | 15 - e2e/test_resources/flux-source-crd.yaml | 2623 --------------------- e2e/test_resources/kluctl-crds.yaml | 567 ----- e2e/test_resources/kluctl-deployment.yaml | 35 - 10 files changed, 3609 deletions(-) delete mode 100644 cmd/kluctl/commands/cmd_flux.go delete mode 100644 cmd/kluctl/commands/cmd_flux_reconcile.go delete mode 100644 cmd/kluctl/commands/cmd_flux_resume.go delete mode 100644 cmd/kluctl/commands/cmd_flux_suspend.go delete mode 100644 e2e/flux_test.go delete mode 100644 e2e/test_resources/flux-source-crd.yaml delete mode 100644 e2e/test_resources/kluctl-crds.yaml delete mode 100644 e2e/test_resources/kluctl-deployment.yaml diff --git a/cmd/kluctl/commands/cmd_flux.go b/cmd/kluctl/commands/cmd_flux.go deleted file mode 100644 index 2b68aa514..000000000 --- a/cmd/kluctl/commands/cmd_flux.go +++ /dev/null @@ -1,70 +0,0 @@ -package commands - -import ( - "context" - "fmt" - "time" - - kube "github.com/kluctl/kluctl/v2/pkg/k8s" - "github.com/kluctl/kluctl/v2/pkg/status" - "github.com/kluctl/kluctl/v2/pkg/types/k8s" - "github.com/kluctl/kluctl/v2/pkg/utils/uo" -) - -type fluxCmd struct { - Reconcile fluxReconcileCmd `cmd:"" help:"Reconcile KluctlDeployment"` - Suspend fluxSuspendCmd `cmd:"" help:"Suspend KluctlDeployment"` - Resume fluxResumeCmd `cmd:"" help:"Resume KluctlDeployment"` -} - -func WaitForReady(k *kube.K8sCluster, ref k8s.ObjectRef) (bool, error) { - o, _, err := k.GetSingleObject(ref) - if o == nil { - return false, err - } - retry := 0 - finalStatus := true - var errorMsg error - for s, err := GetObjectStatus(o); s != true; { - if retry >= 5 || err != nil { - finalStatus = false - errorMsg = err - break - } - retry++ - time.Sleep(8 * time.Second) - } - return finalStatus, errorMsg -} - -func GetObjectStatus(o *uo.UnstructuredObject) (bool, error) { - conditions, ok, err := o.GetNestedField("status", "conditions") - if !ok { - return false, err - } - for _, v := range conditions.([]interface{}) { - if (v.(map[string]interface{})["type"]) == "Ready" { - return true, nil - } - } - - return false, err -} - -func GetObjectSource(o *uo.UnstructuredObject, name string, namespace string, ctx context.Context) (string, string, error) { - status.Trace(ctx, "fetching %s in %s", name, namespace) - - sourceName, ok, err := o.GetNestedField("spec", "sourceRef", "name") - if !ok { - return "", "", err - } - - sourceNamespace, ok, err := o.GetNestedField("spec", "sourceRef", "namespace") - if !ok { - return "", "", err - } - - return fmt.Sprintf("%v", sourceName), - fmt.Sprintf("%v", sourceNamespace), - err -} diff --git a/cmd/kluctl/commands/cmd_flux_reconcile.go b/cmd/kluctl/commands/cmd_flux_reconcile.go deleted file mode 100644 index d4d49ed7c..000000000 --- a/cmd/kluctl/commands/cmd_flux_reconcile.go +++ /dev/null @@ -1,108 +0,0 @@ -package commands - -import ( - "context" - "fmt" - "os" - "time" - - "github.com/kluctl/kluctl/v2/cmd/kluctl/args" - "github.com/kluctl/kluctl/v2/pkg/k8s" - "github.com/kluctl/kluctl/v2/pkg/status" - k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" -) - -type fluxReconcileCmd struct { - args.KluctlDeploymentFlags -} - -func (cmd *fluxReconcileCmd) Run(ctx context.Context) error { - var ( - sourceNamespace string - sourceName string - ) - ns := cmd.KluctlDeploymentFlags.Namespace - kd := cmd.KluctlDeploymentFlags.KluctlDeployment - source := cmd.KluctlDeploymentFlags.WithSource - noWait := cmd.KluctlDeploymentFlags.NoWait - timestamp := time.Now().Format(time.RFC3339) - - cf, err := k8s.NewClientFactoryFromDefaultConfig(ctx, nil) - if err != nil { - return err - } - k, err := k8s.NewK8sCluster(context.TODO(), cf, false) - if err != nil { - return err - } - - ref := k8s2.NewObjectRef(args.KluctlDeploymentGVK.Group, args.KluctlDeploymentGVK.Version, args.KluctlDeploymentGVK.Kind, kd, ns) - patch := []k8s.JsonPatch{{ - Op: "replace", - Path: "/metadata/annotations", - Value: map[string]string{ - "fluxcd.io/reconcileAt": timestamp, - }, - }} - - if source { - if !cmd.VerifyFlags() { - fmt.Println("KluctlDeployment name flag not provided..") - os.Exit(1) - } - o, _, err := k.GetSingleObject(ref) - if o == nil { - return err - } - - sourceName, sourceNamespace, err = GetObjectSource(o, kd, ns, context.TODO()) - if err != nil { - return err - } - ref2 := k8s2.NewObjectRef(args.GitRepositoryGVK.Group, args.GitRepositoryGVK.Version, args.GitRepositoryGVK.Kind, sourceName, sourceNamespace) - - s := status.Start(ctx, "Annotating Source %s in %s namespace", sourceName, sourceNamespace) - defer s.Failed() - - _, _, err = k.PatchObjectWithJsonPatch(ref2, patch, k8s.PatchOptions{}) - if err != nil { - return err - } - s.Success() - - s = status.Start(ctx, "Waiting for Source %s to finish reconciliation", sourceName) - - if !noWait { - ready, err := WaitForReady(k, ref2) - if !ready { - s.FailedWithMessage("Failed while waiting for Source %s to get ready..", sourceName) - return err - } - } - - s.Success() - } - - s := status.Start(ctx, "Annotating KluctlDeployment %s in %s namespace", kd, ns) - defer s.Failed() - - _, _, err = k.PatchObjectWithJsonPatch(ref, patch, k8s.PatchOptions{}) - if err != nil { - return err - } - s.Success() - - s = status.Start(ctx, "Waiting for KluctlDeployment %s in %s namespace to finish reconciliation", kd, ns) - - if !noWait { - ready, err := WaitForReady(k, ref) - if !ready { - s.FailedWithMessage("Failed while waiting for KluctlDeployment %s to get ready..", kd) - return err - } - } - - s.Success() - - return err -} diff --git a/cmd/kluctl/commands/cmd_flux_resume.go b/cmd/kluctl/commands/cmd_flux_resume.go deleted file mode 100644 index 8d116bc1e..000000000 --- a/cmd/kluctl/commands/cmd_flux_resume.go +++ /dev/null @@ -1,48 +0,0 @@ -package commands - -import ( - "context" - - "github.com/kluctl/kluctl/v2/cmd/kluctl/args" - "github.com/kluctl/kluctl/v2/pkg/k8s" - "github.com/kluctl/kluctl/v2/pkg/status" - k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" -) - -type fluxResumeCmd struct { - args.KluctlDeploymentFlags -} - -// TODO add reconciliation after resume -func (cmd *fluxResumeCmd) Run(ctx context.Context) error { - ns := cmd.KluctlDeploymentFlags.Namespace - kd := cmd.KluctlDeploymentFlags.KluctlDeployment - - cf, err := k8s.NewClientFactoryFromDefaultConfig(ctx, nil) - if err != nil { - return err - } - k, err := k8s.NewK8sCluster(context.TODO(), cf, false) - if err != nil { - return err - } - - ref := k8s2.NewObjectRef(args.KluctlDeploymentGVK.Group, args.KluctlDeploymentGVK.Version, args.KluctlDeploymentGVK.Kind, kd, ns) - - patch := []k8s.JsonPatch{{ - Op: "replace", - Path: "/spec/suspend", - Value: false, - }} - - s := status.Start(ctx, "Resuming KluctlDeployment %s in %s namespace", kd, ns) - defer s.Failed() - - _, _, err = k.PatchObjectWithJsonPatch(ref, patch, k8s.PatchOptions{}) - if err != nil { - return err - } - s.Success() - - return err -} diff --git a/cmd/kluctl/commands/cmd_flux_suspend.go b/cmd/kluctl/commands/cmd_flux_suspend.go deleted file mode 100644 index d7981c1a6..000000000 --- a/cmd/kluctl/commands/cmd_flux_suspend.go +++ /dev/null @@ -1,47 +0,0 @@ -package commands - -import ( - "context" - - "github.com/kluctl/kluctl/v2/cmd/kluctl/args" - "github.com/kluctl/kluctl/v2/pkg/k8s" - "github.com/kluctl/kluctl/v2/pkg/status" - k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" -) - -type fluxSuspendCmd struct { - args.KluctlDeploymentFlags -} - -func (cmd *fluxSuspendCmd) Run(ctx context.Context) error { - ns := cmd.KluctlDeploymentFlags.Namespace - kd := cmd.KluctlDeploymentFlags.KluctlDeployment - - cf, err := k8s.NewClientFactoryFromDefaultConfig(ctx, nil) - if err != nil { - return err - } - k, err := k8s.NewK8sCluster(context.TODO(), cf, false) - if err != nil { - return err - } - - ref := k8s2.NewObjectRef(args.KluctlDeploymentGVK.Group, args.KluctlDeploymentGVK.Version, args.KluctlDeploymentGVK.Kind, kd, ns) - patch := []k8s.JsonPatch{{ - Op: "replace", - Path: "/spec/suspend", - Value: true, - }} - - s := status.Start(ctx, "Suspending KluctlDeployment %s in %s namespace", kd, ns) - defer s.Failed() - - _, _, err = k.PatchObjectWithJsonPatch(ref, patch, k8s.PatchOptions{}) - if err != nil { - return err - } - - s.Success() - - return err -} diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index 7ec3fb4b1..200d0b9d7 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -66,7 +66,6 @@ type cli struct { Render renderCmd `cmd:"" help:"Renders all resources and configuration files"` Seal sealCmd `cmd:"" help:"Seal secrets based on target's sealingConfig"` Validate validateCmd `cmd:"" help:"Validates the already deployed deployment"` - Flux fluxCmd `cmd:"" help:"Flux sub-commands"` Version versionCmd `cmd:"" help:"Print kluctl version"` } @@ -77,7 +76,6 @@ var flagGroups = []groupInfo{ {group: "images", title: "Image arguments:", description: "Control fixed images and update behaviour."}, {group: "inclusion", title: "Inclusion/Exclusion arguments:", description: "Control inclusion/exclusion."}, {group: "misc", title: "Misc arguments:", description: "Command specific arguments."}, - {group: "flux", title: "Flux arguments:", description: "EXPERIMENTAL: Subcommands for interaction with flux-kluctl-controller"}, {group: "results", title: "Command Results:", description: "Configure how command results are stored."}, } diff --git a/e2e/flux_test.go b/e2e/flux_test.go deleted file mode 100644 index 5967270c4..000000000 --- a/e2e/flux_test.go +++ /dev/null @@ -1,94 +0,0 @@ -package e2e - -import ( - "context" - "fmt" - "github.com/kluctl/kluctl/v2/e2e/test-utils" - "github.com/kluctl/kluctl/v2/pkg/utils/uo" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - "sync" - "testing" - - "github.com/kluctl/kluctl/v2/e2e/test_resources" - "github.com/stretchr/testify/assert" -) - -var kluctlDeploymentGVR = schema.GroupVersionResource{ - Group: "flux.kluctl.io", - Version: "v1alpha1", - Resource: "kluctldeployments", -} - -func getKluctlDeploymentObject(t *testing.T, k *test_utils.EnvTestCluster) *uo.UnstructuredObject { - kd, err := k.DynamicClient.Resource(kluctlDeploymentGVR).Namespace("flux-test").Get(context.Background(), "microservices-demo-test", v1.GetOptions{}) - if err != nil { - t.Fatal(err) - } - if kd == nil { - t.Fatal("kluctldeployment not found") - } - return uo.FromUnstructured(kd) -} - -func TestFluxCommands(t *testing.T) { - t.Parallel() - - k := defaultCluster1 - - p := test_utils.NewTestProject(t, test_utils.WithUseProcess(true)) - - var wg sync.WaitGroup - wg.Add(2) - go func() { - test_resources.ApplyYaml("flux-source-crd.yaml", k) - wg.Done() - }() - go func() { - test_resources.ApplyYaml("kluctl-crds.yaml", k) - wg.Done() - }() - wg.Wait() - test_resources.ApplyYaml("kluctl-deployment.yaml", k) - - // assert that it was created - _ = getKluctlDeploymentObject(t, k) - - p.KluctlMust("flux", "suspend", "--namespace", "flux-test", "--kluctl-deployment", "microservices-demo-test") - suspend := getKluctlSuspendField(t, k) - assert.Equal(t, true, suspend, "Field status.suspend is not false") - - p.KluctlMust("flux", "resume", "--namespace", "flux-test", "--kluctl-deployment", "microservices-demo-test", "--no-wait") - resume := getKluctlSuspendField(t, k) - assert.Equal(t, false, resume, "Field status.suspend is not true") - - p.KluctlMust("flux", "reconcile", "--namespace", "flux-test", "--kluctl-deployment", "microservices-demo-test", "--with-source", "--no-wait") - annotation := getKluctlAnnotations(t, k) - assert.Len(t, annotation, 1, "Annotation not present") -} - -func getKluctlSuspendField(t *testing.T, k *test_utils.EnvTestCluster) interface{} { - o := getKluctlDeploymentObject(t, k) - result, ok, err := o.GetNestedField("spec", "suspend") - fmt.Println(result) - if err != nil { - t.Fatal(err) - } - if !ok { - t.Fatalf("result not found") - } - return result -} - -func getKluctlAnnotations(t *testing.T, k *test_utils.EnvTestCluster) interface{} { - o := getKluctlDeploymentObject(t, k) - result, ok, err := o.GetNestedField("metadata", "annotations") - if err != nil { - t.Fatal(err) - } - if !ok { - t.Fatalf("result not found") - } - fmt.Println(result) - return result -} diff --git a/e2e/test_resources/README.md b/e2e/test_resources/README.md index 742fd70c2..9be43ed76 100644 --- a/e2e/test_resources/README.md +++ b/e2e/test_resources/README.md @@ -3,18 +3,3 @@ helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets helm template sealed-secrets-controller sealed-secrets/sealed-secrets -n kube-system --include-crds --skip-tests > sealed-secrets.yaml ``` - -# kluctl-crds.yaml -Fetches flux-kluctl-controller crd definitions -```bash -kustomize build https://github.com/kluctl/flux-kluctl-controller/config/crd > kluctl-crds.yaml -``` - -# kluctl-deployment.yaml -This is a simple KluctlDeployment that is not really valid. It points to a non-existing GitRepository. This object is only used to test `kluctl flux` subcommands (which only sets annotations and updates field 'suspend'). - -# flux-source-crd.yaml -Fetches flux-source-controller crd definitions -```bash -kustomize build https://github.com/fluxcd/source-controller/config/crd > flux-source-crd.yaml -``` \ No newline at end of file diff --git a/e2e/test_resources/flux-source-crd.yaml b/e2e/test_resources/flux-source-crd.yaml deleted file mode 100644 index 4eda189ac..000000000 --- a/e2e/test_resources/flux-source-crd.yaml +++ /dev/null @@ -1,2623 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.7.0 - creationTimestamp: null - name: buckets.source.toolkit.fluxcd.io -spec: - group: source.toolkit.fluxcd.io - names: - kind: Bucket - listKind: BucketList - plural: buckets - singular: bucket - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .spec.endpoint - name: Endpoint - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1beta1 - schema: - openAPIV3Schema: - description: Bucket is the Schema for the buckets API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: BucketSpec defines the desired state of an S3 compatible - bucket - properties: - accessFrom: - description: AccessFrom defines an Access Control List for allowing - cross-namespace references to this object. - properties: - namespaceSelectors: - description: NamespaceSelectors is the list of namespace selectors - to which this ACL applies. Items in this list are evaluated - using a logical OR operation. - items: - description: NamespaceSelector selects the namespaces to which - this ACL applies. An empty map of MatchLabels matches all - namespaces in a cluster. - properties: - matchLabels: - additionalProperties: - type: string - description: MatchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field is - "key", the operator is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - type: array - required: - - namespaceSelectors - type: object - bucketName: - description: The bucket name. - type: string - endpoint: - description: The bucket endpoint address. - type: string - ignore: - description: Ignore overrides the set of excluded patterns in the - .sourceignore format (which is the same as .gitignore). If not provided, - a default will be used, consult the documentation for your version - to find out what those are. - type: string - insecure: - description: Insecure allows connecting to a non-TLS S3 HTTP endpoint. - type: boolean - interval: - description: The interval at which to check for bucket updates. - type: string - provider: - default: generic - description: The S3 compatible storage provider name, default ('generic'). - enum: - - generic - - aws - - gcp - type: string - region: - description: The bucket region. - type: string - secretRef: - description: The name of the secret containing authentication credentials - for the Bucket. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - suspend: - description: This flag tells the controller to suspend the reconciliation - of this source. - type: boolean - timeout: - default: 60s - description: The timeout for download operations, defaults to 60s. - type: string - required: - - bucketName - - endpoint - - interval - type: object - status: - default: - observedGeneration: -1 - description: BucketStatus defines the observed state of a bucket - properties: - artifact: - description: Artifact represents the output of the last successful - Bucket sync. - properties: - checksum: - description: Checksum is the SHA256 checksum of the artifact. - type: string - lastUpdateTime: - description: LastUpdateTime is the timestamp corresponding to - the last update of this artifact. - format: date-time - type: string - path: - description: Path is the relative file path of this artifact. - type: string - revision: - description: Revision is a human readable identifier traceable - in the origin source system. It can be a Git commit SHA, Git - tag, a Helm index timestamp, a Helm chart version, etc. - type: string - url: - description: URL is the HTTP address of this artifact. - type: string - required: - - path - - url - type: object - conditions: - description: Conditions holds the conditions for the Bucket. - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - \n \ttype FooStatus struct{ \t // Represents the observations - of a foo's current state. \t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\" \t // - +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map - \t // +listMapKey=type \t Conditions []metav1.Condition - `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" - protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields - \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - lastHandledReconcileAt: - description: LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value can - be detected. - type: string - observedGeneration: - description: ObservedGeneration is the last observed generation. - format: int64 - type: integer - url: - description: URL is the download link for the artifact output of the - last Bucket sync. - type: string - type: object - type: object - served: true - storage: false - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .spec.endpoint - name: Endpoint - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - name: v1beta2 - schema: - openAPIV3Schema: - description: Bucket is the Schema for the buckets API. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: BucketSpec specifies the required configuration to produce - an Artifact for an object storage bucket. - properties: - accessFrom: - description: 'AccessFrom specifies an Access Control List for allowing - cross-namespace references to this object. NOTE: Not implemented, - provisional as of https://github.com/fluxcd/flux2/pull/2092' - properties: - namespaceSelectors: - description: NamespaceSelectors is the list of namespace selectors - to which this ACL applies. Items in this list are evaluated - using a logical OR operation. - items: - description: NamespaceSelector selects the namespaces to which - this ACL applies. An empty map of MatchLabels matches all - namespaces in a cluster. - properties: - matchLabels: - additionalProperties: - type: string - description: MatchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field is - "key", the operator is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - type: array - required: - - namespaceSelectors - type: object - bucketName: - description: BucketName is the name of the object storage bucket. - type: string - endpoint: - description: Endpoint is the object storage address the BucketName - is located at. - type: string - ignore: - description: Ignore overrides the set of excluded patterns in the - .sourceignore format (which is the same as .gitignore). If not provided, - a default will be used, consult the documentation for your version - to find out what those are. - type: string - insecure: - description: Insecure allows connecting to a non-TLS HTTP Endpoint. - type: boolean - interval: - description: Interval at which to check the Endpoint for updates. - type: string - provider: - default: generic - description: Provider of the object storage bucket. Defaults to 'generic', - which expects an S3 (API) compatible object storage. - enum: - - generic - - aws - - gcp - - azure - type: string - region: - description: Region of the Endpoint where the BucketName is located - in. - type: string - secretRef: - description: SecretRef specifies the Secret containing authentication - credentials for the Bucket. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - suspend: - description: Suspend tells the controller to suspend the reconciliation - of this Bucket. - type: boolean - timeout: - default: 60s - description: Timeout for fetch operations, defaults to 60s. - type: string - required: - - bucketName - - endpoint - - interval - type: object - status: - default: - observedGeneration: -1 - description: BucketStatus records the observed state of a Bucket. - properties: - artifact: - description: Artifact represents the last successful Bucket reconciliation. - properties: - checksum: - description: Checksum is the SHA256 checksum of the Artifact file. - type: string - lastUpdateTime: - description: LastUpdateTime is the timestamp corresponding to - the last update of the Artifact. - format: date-time - type: string - metadata: - additionalProperties: - type: string - description: Metadata holds upstream information such as OCI annotations. - type: object - path: - description: Path is the relative file path of the Artifact. It - can be used to locate the file in the root of the Artifact storage - on the local file system of the controller managing the Source. - type: string - revision: - description: Revision is a human-readable identifier traceable - in the origin source system. It can be a Git commit SHA, Git - tag, a Helm chart version, etc. - type: string - size: - description: Size is the number of bytes in the file. - format: int64 - type: integer - url: - description: URL is the HTTP address of the Artifact as exposed - by the controller managing the Source. It can be used to retrieve - the Artifact for consumption, e.g. by another controller applying - the Artifact contents. - type: string - required: - - path - - url - type: object - conditions: - description: Conditions holds the conditions for the Bucket. - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - \n \ttype FooStatus struct{ \t // Represents the observations - of a foo's current state. \t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\" \t // - +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map - \t // +listMapKey=type \t Conditions []metav1.Condition - `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" - protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields - \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - lastHandledReconcileAt: - description: LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value can - be detected. - type: string - observedGeneration: - description: ObservedGeneration is the last observed generation of - the Bucket object. - format: int64 - type: integer - url: - description: URL is the dynamic fetch link for the latest Artifact. - It is provided on a "best effort" basis, and using the precise BucketStatus.Artifact - data is recommended. - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.7.0 - creationTimestamp: null - name: gitrepositories.source.toolkit.fluxcd.io -spec: - group: source.toolkit.fluxcd.io - names: - kind: GitRepository - listKind: GitRepositoryList - plural: gitrepositories - shortNames: - - gitrepo - singular: gitrepository - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .spec.url - name: URL - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1beta1 - schema: - openAPIV3Schema: - description: GitRepository is the Schema for the gitrepositories API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: GitRepositorySpec defines the desired state of a Git repository. - properties: - accessFrom: - description: AccessFrom defines an Access Control List for allowing - cross-namespace references to this object. - properties: - namespaceSelectors: - description: NamespaceSelectors is the list of namespace selectors - to which this ACL applies. Items in this list are evaluated - using a logical OR operation. - items: - description: NamespaceSelector selects the namespaces to which - this ACL applies. An empty map of MatchLabels matches all - namespaces in a cluster. - properties: - matchLabels: - additionalProperties: - type: string - description: MatchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field is - "key", the operator is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - type: array - required: - - namespaceSelectors - type: object - gitImplementation: - default: go-git - description: Determines which git client library to use. Defaults - to go-git, valid values are ('go-git', 'libgit2'). - enum: - - go-git - - libgit2 - type: string - ignore: - description: Ignore overrides the set of excluded patterns in the - .sourceignore format (which is the same as .gitignore). If not provided, - a default will be used, consult the documentation for your version - to find out what those are. - type: string - include: - description: Extra git repositories to map into the repository - items: - description: GitRepositoryInclude defines a source with a from and - to path. - properties: - fromPath: - description: The path to copy contents from, defaults to the - root directory. - type: string - repository: - description: Reference to a GitRepository to include. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - toPath: - description: The path to copy contents to, defaults to the name - of the source ref. - type: string - required: - - repository - type: object - type: array - interval: - description: The interval at which to check for repository updates. - type: string - recurseSubmodules: - description: When enabled, after the clone is created, initializes - all submodules within, using their default settings. This option - is available only when using the 'go-git' GitImplementation. - type: boolean - ref: - description: The Git reference to checkout and monitor for changes, - defaults to master branch. - properties: - branch: - description: The Git branch to checkout, defaults to master. - type: string - commit: - description: The Git commit SHA to checkout, if specified Tag - filters will be ignored. - type: string - semver: - description: The Git tag semver expression, takes precedence over - Tag. - type: string - tag: - description: The Git tag to checkout, takes precedence over Branch. - type: string - type: object - secretRef: - description: The secret name containing the Git credentials. For HTTPS - repositories the secret must contain username and password fields. - For SSH repositories the secret must contain identity and known_hosts - fields. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - suspend: - description: This flag tells the controller to suspend the reconciliation - of this source. - type: boolean - timeout: - default: 60s - description: The timeout for remote Git operations like cloning, defaults - to 60s. - type: string - url: - description: The repository URL, can be a HTTP/S or SSH address. - pattern: ^(http|https|ssh)://.*$ - type: string - verify: - description: Verify OpenPGP signature for the Git commit HEAD points - to. - properties: - mode: - description: Mode describes what git object should be verified, - currently ('head'). - enum: - - head - type: string - secretRef: - description: The secret name containing the public keys of all - trusted Git authors. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - required: - - mode - type: object - required: - - interval - - url - type: object - status: - default: - observedGeneration: -1 - description: GitRepositoryStatus defines the observed state of a Git repository. - properties: - artifact: - description: Artifact represents the output of the last successful - repository sync. - properties: - checksum: - description: Checksum is the SHA256 checksum of the artifact. - type: string - lastUpdateTime: - description: LastUpdateTime is the timestamp corresponding to - the last update of this artifact. - format: date-time - type: string - path: - description: Path is the relative file path of this artifact. - type: string - revision: - description: Revision is a human readable identifier traceable - in the origin source system. It can be a Git commit SHA, Git - tag, a Helm index timestamp, a Helm chart version, etc. - type: string - url: - description: URL is the HTTP address of this artifact. - type: string - required: - - path - - url - type: object - conditions: - description: Conditions holds the conditions for the GitRepository. - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - \n \ttype FooStatus struct{ \t // Represents the observations - of a foo's current state. \t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\" \t // - +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map - \t // +listMapKey=type \t Conditions []metav1.Condition - `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" - protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields - \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - includedArtifacts: - description: IncludedArtifacts represents the included artifacts from - the last successful repository sync. - items: - description: Artifact represents the output of a source synchronisation. - properties: - checksum: - description: Checksum is the SHA256 checksum of the artifact. - type: string - lastUpdateTime: - description: LastUpdateTime is the timestamp corresponding to - the last update of this artifact. - format: date-time - type: string - path: - description: Path is the relative file path of this artifact. - type: string - revision: - description: Revision is a human readable identifier traceable - in the origin source system. It can be a Git commit SHA, Git - tag, a Helm index timestamp, a Helm chart version, etc. - type: string - url: - description: URL is the HTTP address of this artifact. - type: string - required: - - path - - url - type: object - type: array - lastHandledReconcileAt: - description: LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value can - be detected. - type: string - observedGeneration: - description: ObservedGeneration is the last observed generation. - format: int64 - type: integer - url: - description: URL is the download link for the artifact output of the - last repository sync. - type: string - type: object - type: object - served: true - storage: false - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .spec.url - name: URL - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - name: v1beta2 - schema: - openAPIV3Schema: - description: GitRepository is the Schema for the gitrepositories API. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: GitRepositorySpec specifies the required configuration to - produce an Artifact for a Git repository. - properties: - accessFrom: - description: 'AccessFrom specifies an Access Control List for allowing - cross-namespace references to this object. NOTE: Not implemented, - provisional as of https://github.com/fluxcd/flux2/pull/2092' - properties: - namespaceSelectors: - description: NamespaceSelectors is the list of namespace selectors - to which this ACL applies. Items in this list are evaluated - using a logical OR operation. - items: - description: NamespaceSelector selects the namespaces to which - this ACL applies. An empty map of MatchLabels matches all - namespaces in a cluster. - properties: - matchLabels: - additionalProperties: - type: string - description: MatchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field is - "key", the operator is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - type: array - required: - - namespaceSelectors - type: object - gitImplementation: - default: go-git - description: GitImplementation specifies which Git client library - implementation to use. Defaults to 'go-git', valid values are ('go-git', - 'libgit2'). - enum: - - go-git - - libgit2 - type: string - ignore: - description: Ignore overrides the set of excluded patterns in the - .sourceignore format (which is the same as .gitignore). If not provided, - a default will be used, consult the documentation for your version - to find out what those are. - type: string - include: - description: Include specifies a list of GitRepository resources which - Artifacts should be included in the Artifact produced for this GitRepository. - items: - description: GitRepositoryInclude specifies a local reference to - a GitRepository which Artifact (sub-)contents must be included, - and where they should be placed. - properties: - fromPath: - description: FromPath specifies the path to copy contents from, - defaults to the root of the Artifact. - type: string - repository: - description: GitRepositoryRef specifies the GitRepository which - Artifact contents must be included. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - toPath: - description: ToPath specifies the path to copy contents to, - defaults to the name of the GitRepositoryRef. - type: string - required: - - repository - type: object - type: array - interval: - description: Interval at which to check the GitRepository for updates. - type: string - recurseSubmodules: - description: RecurseSubmodules enables the initialization of all submodules - within the GitRepository as cloned from the URL, using their default - settings. This option is available only when using the 'go-git' - GitImplementation. - type: boolean - ref: - description: Reference specifies the Git reference to resolve and - monitor for changes, defaults to the 'master' branch. - properties: - branch: - description: "Branch to check out, defaults to 'master' if no - other field is defined. \n When GitRepositorySpec.GitImplementation - is set to 'go-git', a shallow clone of the specified branch - is performed." - type: string - commit: - description: "Commit SHA to check out, takes precedence over all - reference fields. \n When GitRepositorySpec.GitImplementation - is set to 'go-git', this can be combined with Branch to shallow - clone the branch, in which the commit is expected to exist." - type: string - semver: - description: SemVer tag expression to check out, takes precedence - over Tag. - type: string - tag: - description: Tag to check out, takes precedence over Branch. - type: string - type: object - secretRef: - description: SecretRef specifies the Secret containing authentication - credentials for the GitRepository. For HTTPS repositories the Secret - must contain 'username' and 'password' fields. For SSH repositories - the Secret must contain 'identity' and 'known_hosts' fields. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - suspend: - description: Suspend tells the controller to suspend the reconciliation - of this GitRepository. - type: boolean - timeout: - default: 60s - description: Timeout for Git operations like cloning, defaults to - 60s. - type: string - url: - description: URL specifies the Git repository URL, it can be an HTTP/S - or SSH address. - pattern: ^(http|https|ssh)://.*$ - type: string - verify: - description: Verification specifies the configuration to verify the - Git commit signature(s). - properties: - mode: - description: Mode specifies what Git object should be verified, - currently ('head'). - enum: - - head - type: string - secretRef: - description: SecretRef specifies the Secret containing the public - keys of trusted Git authors. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - required: - - mode - type: object - required: - - interval - - url - type: object - status: - default: - observedGeneration: -1 - description: GitRepositoryStatus records the observed state of a Git repository. - properties: - artifact: - description: Artifact represents the last successful GitRepository - reconciliation. - properties: - checksum: - description: Checksum is the SHA256 checksum of the Artifact file. - type: string - lastUpdateTime: - description: LastUpdateTime is the timestamp corresponding to - the last update of the Artifact. - format: date-time - type: string - metadata: - additionalProperties: - type: string - description: Metadata holds upstream information such as OCI annotations. - type: object - path: - description: Path is the relative file path of the Artifact. It - can be used to locate the file in the root of the Artifact storage - on the local file system of the controller managing the Source. - type: string - revision: - description: Revision is a human-readable identifier traceable - in the origin source system. It can be a Git commit SHA, Git - tag, a Helm chart version, etc. - type: string - size: - description: Size is the number of bytes in the file. - format: int64 - type: integer - url: - description: URL is the HTTP address of the Artifact as exposed - by the controller managing the Source. It can be used to retrieve - the Artifact for consumption, e.g. by another controller applying - the Artifact contents. - type: string - required: - - path - - url - type: object - conditions: - description: Conditions holds the conditions for the GitRepository. - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - \n \ttype FooStatus struct{ \t // Represents the observations - of a foo's current state. \t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\" \t // - +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map - \t // +listMapKey=type \t Conditions []metav1.Condition - `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" - protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields - \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - contentConfigChecksum: - description: 'ContentConfigChecksum is a checksum of all the configurations - related to the content of the source artifact: - .spec.ignore - - .spec.recurseSubmodules - .spec.included and the checksum of the - included artifacts observed in .status.observedGeneration version - of the object. This can be used to determine if the content of the - included repository has changed. It has the format of `:`, - for example: `sha256:`.' - type: string - includedArtifacts: - description: IncludedArtifacts contains a list of the last successfully - included Artifacts as instructed by GitRepositorySpec.Include. - items: - description: Artifact represents the output of a Source reconciliation. - properties: - checksum: - description: Checksum is the SHA256 checksum of the Artifact - file. - type: string - lastUpdateTime: - description: LastUpdateTime is the timestamp corresponding to - the last update of the Artifact. - format: date-time - type: string - metadata: - additionalProperties: - type: string - description: Metadata holds upstream information such as OCI - annotations. - type: object - path: - description: Path is the relative file path of the Artifact. - It can be used to locate the file in the root of the Artifact - storage on the local file system of the controller managing - the Source. - type: string - revision: - description: Revision is a human-readable identifier traceable - in the origin source system. It can be a Git commit SHA, Git - tag, a Helm chart version, etc. - type: string - size: - description: Size is the number of bytes in the file. - format: int64 - type: integer - url: - description: URL is the HTTP address of the Artifact as exposed - by the controller managing the Source. It can be used to retrieve - the Artifact for consumption, e.g. by another controller applying - the Artifact contents. - type: string - required: - - path - - url - type: object - type: array - lastHandledReconcileAt: - description: LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value can - be detected. - type: string - observedGeneration: - description: ObservedGeneration is the last observed generation of - the GitRepository object. - format: int64 - type: integer - url: - description: URL is the dynamic fetch link for the latest Artifact. - It is provided on a "best effort" basis, and using the precise GitRepositoryStatus.Artifact - data is recommended. - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.7.0 - creationTimestamp: null - name: helmcharts.source.toolkit.fluxcd.io -spec: - group: source.toolkit.fluxcd.io - names: - kind: HelmChart - listKind: HelmChartList - plural: helmcharts - shortNames: - - hc - singular: helmchart - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .spec.chart - name: Chart - type: string - - jsonPath: .spec.version - name: Version - type: string - - jsonPath: .spec.sourceRef.kind - name: Source Kind - type: string - - jsonPath: .spec.sourceRef.name - name: Source Name - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1beta1 - schema: - openAPIV3Schema: - description: HelmChart is the Schema for the helmcharts API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: HelmChartSpec defines the desired state of a Helm chart. - properties: - accessFrom: - description: AccessFrom defines an Access Control List for allowing - cross-namespace references to this object. - properties: - namespaceSelectors: - description: NamespaceSelectors is the list of namespace selectors - to which this ACL applies. Items in this list are evaluated - using a logical OR operation. - items: - description: NamespaceSelector selects the namespaces to which - this ACL applies. An empty map of MatchLabels matches all - namespaces in a cluster. - properties: - matchLabels: - additionalProperties: - type: string - description: MatchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field is - "key", the operator is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - type: array - required: - - namespaceSelectors - type: object - chart: - description: The name or path the Helm chart is available at in the - SourceRef. - type: string - interval: - description: The interval at which to check the Source for updates. - type: string - reconcileStrategy: - default: ChartVersion - description: Determines what enables the creation of a new artifact. - Valid values are ('ChartVersion', 'Revision'). See the documentation - of the values for an explanation on their behavior. Defaults to - ChartVersion when omitted. - enum: - - ChartVersion - - Revision - type: string - sourceRef: - description: The reference to the Source the chart is available at. - properties: - apiVersion: - description: APIVersion of the referent. - type: string - kind: - description: Kind of the referent, valid values are ('HelmRepository', - 'GitRepository', 'Bucket'). - enum: - - HelmRepository - - GitRepository - - Bucket - type: string - name: - description: Name of the referent. - type: string - required: - - kind - - name - type: object - suspend: - description: This flag tells the controller to suspend the reconciliation - of this source. - type: boolean - valuesFile: - description: Alternative values file to use as the default chart values, - expected to be a relative path in the SourceRef. Deprecated in favor - of ValuesFiles, for backwards compatibility the file defined here - is merged before the ValuesFiles items. Ignored when omitted. - type: string - valuesFiles: - description: Alternative list of values files to use as the chart - values (values.yaml is not included by default), expected to be - a relative path in the SourceRef. Values files are merged in the - order of this list with the last file overriding the first. Ignored - when omitted. - items: - type: string - type: array - version: - default: '*' - description: The chart version semver expression, ignored for charts - from GitRepository and Bucket sources. Defaults to latest when omitted. - type: string - required: - - chart - - interval - - sourceRef - type: object - status: - default: - observedGeneration: -1 - description: HelmChartStatus defines the observed state of the HelmChart. - properties: - artifact: - description: Artifact represents the output of the last successful - chart sync. - properties: - checksum: - description: Checksum is the SHA256 checksum of the artifact. - type: string - lastUpdateTime: - description: LastUpdateTime is the timestamp corresponding to - the last update of this artifact. - format: date-time - type: string - path: - description: Path is the relative file path of this artifact. - type: string - revision: - description: Revision is a human readable identifier traceable - in the origin source system. It can be a Git commit SHA, Git - tag, a Helm index timestamp, a Helm chart version, etc. - type: string - url: - description: URL is the HTTP address of this artifact. - type: string - required: - - path - - url - type: object - conditions: - description: Conditions holds the conditions for the HelmChart. - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - \n \ttype FooStatus struct{ \t // Represents the observations - of a foo's current state. \t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\" \t // - +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map - \t // +listMapKey=type \t Conditions []metav1.Condition - `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" - protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields - \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - lastHandledReconcileAt: - description: LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value can - be detected. - type: string - observedGeneration: - description: ObservedGeneration is the last observed generation. - format: int64 - type: integer - url: - description: URL is the download link for the last chart pulled. - type: string - type: object - type: object - served: true - storage: false - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .spec.chart - name: Chart - type: string - - jsonPath: .spec.version - name: Version - type: string - - jsonPath: .spec.sourceRef.kind - name: Source Kind - type: string - - jsonPath: .spec.sourceRef.name - name: Source Name - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - name: v1beta2 - schema: - openAPIV3Schema: - description: HelmChart is the Schema for the helmcharts API. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: HelmChartSpec specifies the desired state of a Helm chart. - properties: - accessFrom: - description: 'AccessFrom specifies an Access Control List for allowing - cross-namespace references to this object. NOTE: Not implemented, - provisional as of https://github.com/fluxcd/flux2/pull/2092' - properties: - namespaceSelectors: - description: NamespaceSelectors is the list of namespace selectors - to which this ACL applies. Items in this list are evaluated - using a logical OR operation. - items: - description: NamespaceSelector selects the namespaces to which - this ACL applies. An empty map of MatchLabels matches all - namespaces in a cluster. - properties: - matchLabels: - additionalProperties: - type: string - description: MatchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field is - "key", the operator is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - type: array - required: - - namespaceSelectors - type: object - chart: - description: Chart is the name or path the Helm chart is available - at in the SourceRef. - type: string - interval: - description: Interval is the interval at which to check the Source - for updates. - type: string - reconcileStrategy: - default: ChartVersion - description: ReconcileStrategy determines what enables the creation - of a new artifact. Valid values are ('ChartVersion', 'Revision'). - See the documentation of the values for an explanation on their - behavior. Defaults to ChartVersion when omitted. - enum: - - ChartVersion - - Revision - type: string - sourceRef: - description: SourceRef is the reference to the Source the chart is - available at. - properties: - apiVersion: - description: APIVersion of the referent. - type: string - kind: - description: Kind of the referent, valid values are ('HelmRepository', - 'GitRepository', 'Bucket'). - enum: - - HelmRepository - - GitRepository - - Bucket - type: string - name: - description: Name of the referent. - type: string - required: - - kind - - name - type: object - suspend: - description: Suspend tells the controller to suspend the reconciliation - of this source. - type: boolean - valuesFile: - description: ValuesFile is an alternative values file to use as the - default chart values, expected to be a relative path in the SourceRef. - Deprecated in favor of ValuesFiles, for backwards compatibility - the file specified here is merged before the ValuesFiles items. - Ignored when omitted. - type: string - valuesFiles: - description: ValuesFiles is an alternative list of values files to - use as the chart values (values.yaml is not included by default), - expected to be a relative path in the SourceRef. Values files are - merged in the order of this list with the last file overriding the - first. Ignored when omitted. - items: - type: string - type: array - version: - default: '*' - description: Version is the chart version semver expression, ignored - for charts from GitRepository and Bucket sources. Defaults to latest - when omitted. - type: string - required: - - chart - - interval - - sourceRef - type: object - status: - default: - observedGeneration: -1 - description: HelmChartStatus records the observed state of the HelmChart. - properties: - artifact: - description: Artifact represents the output of the last successful - reconciliation. - properties: - checksum: - description: Checksum is the SHA256 checksum of the Artifact file. - type: string - lastUpdateTime: - description: LastUpdateTime is the timestamp corresponding to - the last update of the Artifact. - format: date-time - type: string - metadata: - additionalProperties: - type: string - description: Metadata holds upstream information such as OCI annotations. - type: object - path: - description: Path is the relative file path of the Artifact. It - can be used to locate the file in the root of the Artifact storage - on the local file system of the controller managing the Source. - type: string - revision: - description: Revision is a human-readable identifier traceable - in the origin source system. It can be a Git commit SHA, Git - tag, a Helm chart version, etc. - type: string - size: - description: Size is the number of bytes in the file. - format: int64 - type: integer - url: - description: URL is the HTTP address of the Artifact as exposed - by the controller managing the Source. It can be used to retrieve - the Artifact for consumption, e.g. by another controller applying - the Artifact contents. - type: string - required: - - path - - url - type: object - conditions: - description: Conditions holds the conditions for the HelmChart. - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - \n \ttype FooStatus struct{ \t // Represents the observations - of a foo's current state. \t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\" \t // - +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map - \t // +listMapKey=type \t Conditions []metav1.Condition - `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" - protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields - \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - lastHandledReconcileAt: - description: LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value can - be detected. - type: string - observedChartName: - description: ObservedChartName is the last observed chart name as - specified by the resolved chart reference. - type: string - observedGeneration: - description: ObservedGeneration is the last observed generation of - the HelmChart object. - format: int64 - type: integer - observedSourceArtifactRevision: - description: ObservedSourceArtifactRevision is the last observed Artifact.Revision - of the HelmChartSpec.SourceRef. - type: string - url: - description: URL is the dynamic fetch link for the latest Artifact. - It is provided on a "best effort" basis, and using the precise BucketStatus.Artifact - data is recommended. - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.7.0 - creationTimestamp: null - name: helmrepositories.source.toolkit.fluxcd.io -spec: - group: source.toolkit.fluxcd.io - names: - kind: HelmRepository - listKind: HelmRepositoryList - plural: helmrepositories - shortNames: - - helmrepo - singular: helmrepository - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .spec.url - name: URL - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1beta1 - schema: - openAPIV3Schema: - description: HelmRepository is the Schema for the helmrepositories API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: HelmRepositorySpec defines the reference to a Helm repository. - properties: - accessFrom: - description: AccessFrom defines an Access Control List for allowing - cross-namespace references to this object. - properties: - namespaceSelectors: - description: NamespaceSelectors is the list of namespace selectors - to which this ACL applies. Items in this list are evaluated - using a logical OR operation. - items: - description: NamespaceSelector selects the namespaces to which - this ACL applies. An empty map of MatchLabels matches all - namespaces in a cluster. - properties: - matchLabels: - additionalProperties: - type: string - description: MatchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field is - "key", the operator is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - type: array - required: - - namespaceSelectors - type: object - interval: - description: The interval at which to check the upstream for updates. - type: string - passCredentials: - description: PassCredentials allows the credentials from the SecretRef - to be passed on to a host that does not match the host as defined - in URL. This may be required if the host of the advertised chart - URLs in the index differ from the defined URL. Enabling this should - be done with caution, as it can potentially result in credentials - getting stolen in a MITM-attack. - type: boolean - secretRef: - description: The name of the secret containing authentication credentials - for the Helm repository. For HTTP/S basic auth the secret must contain - username and password fields. For TLS the secret must contain a - certFile and keyFile, and/or caCert fields. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - suspend: - description: This flag tells the controller to suspend the reconciliation - of this source. - type: boolean - timeout: - default: 60s - description: The timeout of index downloading, defaults to 60s. - type: string - url: - description: The Helm repository URL, a valid URL contains at least - a protocol and host. - type: string - required: - - interval - - url - type: object - status: - default: - observedGeneration: -1 - description: HelmRepositoryStatus defines the observed state of the HelmRepository. - properties: - artifact: - description: Artifact represents the output of the last successful - repository sync. - properties: - checksum: - description: Checksum is the SHA256 checksum of the artifact. - type: string - lastUpdateTime: - description: LastUpdateTime is the timestamp corresponding to - the last update of this artifact. - format: date-time - type: string - path: - description: Path is the relative file path of this artifact. - type: string - revision: - description: Revision is a human readable identifier traceable - in the origin source system. It can be a Git commit SHA, Git - tag, a Helm index timestamp, a Helm chart version, etc. - type: string - url: - description: URL is the HTTP address of this artifact. - type: string - required: - - path - - url - type: object - conditions: - description: Conditions holds the conditions for the HelmRepository. - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - \n \ttype FooStatus struct{ \t // Represents the observations - of a foo's current state. \t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\" \t // - +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map - \t // +listMapKey=type \t Conditions []metav1.Condition - `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" - protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields - \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - lastHandledReconcileAt: - description: LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value can - be detected. - type: string - observedGeneration: - description: ObservedGeneration is the last observed generation. - format: int64 - type: integer - url: - description: URL is the download link for the last index fetched. - type: string - type: object - type: object - served: true - storage: false - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .spec.url - name: URL - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - name: v1beta2 - schema: - openAPIV3Schema: - description: HelmRepository is the Schema for the helmrepositories API. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: HelmRepositorySpec specifies the required configuration to - produce an Artifact for a Helm repository index YAML. - properties: - accessFrom: - description: 'AccessFrom specifies an Access Control List for allowing - cross-namespace references to this object. NOTE: Not implemented, - provisional as of https://github.com/fluxcd/flux2/pull/2092' - properties: - namespaceSelectors: - description: NamespaceSelectors is the list of namespace selectors - to which this ACL applies. Items in this list are evaluated - using a logical OR operation. - items: - description: NamespaceSelector selects the namespaces to which - this ACL applies. An empty map of MatchLabels matches all - namespaces in a cluster. - properties: - matchLabels: - additionalProperties: - type: string - description: MatchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field is - "key", the operator is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - type: array - required: - - namespaceSelectors - type: object - interval: - description: Interval at which to check the URL for updates. - type: string - passCredentials: - description: PassCredentials allows the credentials from the SecretRef - to be passed on to a host that does not match the host as defined - in URL. This may be required if the host of the advertised chart - URLs in the index differ from the defined URL. Enabling this should - be done with caution, as it can potentially result in credentials - getting stolen in a MITM-attack. - type: boolean - provider: - default: generic - description: Provider used for authentication, can be 'aws', 'azure', - 'gcp' or 'generic'. This field is optional, and only taken into - account if the .spec.type field is set to 'oci'. When not specified, - defaults to 'generic'. - enum: - - generic - - aws - - azure - - gcp - type: string - secretRef: - description: SecretRef specifies the Secret containing authentication - credentials for the HelmRepository. For HTTP/S basic auth the secret - must contain 'username' and 'password' fields. For TLS the secret - must contain a 'certFile' and 'keyFile', and/or 'caCert' fields. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - suspend: - description: Suspend tells the controller to suspend the reconciliation - of this HelmRepository. - type: boolean - timeout: - default: 60s - description: Timeout is used for the index fetch operation for an - HTTPS helm repository, and for remote OCI Repository operations - like pulling for an OCI helm repository. Its default value is 60s. - type: string - type: - description: Type of the HelmRepository. When this field is set to "oci", - the URL field value must be prefixed with "oci://". - enum: - - default - - oci - type: string - url: - description: URL of the Helm repository, a valid URL contains at least - a protocol and host. - type: string - required: - - interval - - url - type: object - status: - default: - observedGeneration: -1 - description: HelmRepositoryStatus records the observed state of the HelmRepository. - properties: - artifact: - description: Artifact represents the last successful HelmRepository - reconciliation. - properties: - checksum: - description: Checksum is the SHA256 checksum of the Artifact file. - type: string - lastUpdateTime: - description: LastUpdateTime is the timestamp corresponding to - the last update of the Artifact. - format: date-time - type: string - metadata: - additionalProperties: - type: string - description: Metadata holds upstream information such as OCI annotations. - type: object - path: - description: Path is the relative file path of the Artifact. It - can be used to locate the file in the root of the Artifact storage - on the local file system of the controller managing the Source. - type: string - revision: - description: Revision is a human-readable identifier traceable - in the origin source system. It can be a Git commit SHA, Git - tag, a Helm chart version, etc. - type: string - size: - description: Size is the number of bytes in the file. - format: int64 - type: integer - url: - description: URL is the HTTP address of the Artifact as exposed - by the controller managing the Source. It can be used to retrieve - the Artifact for consumption, e.g. by another controller applying - the Artifact contents. - type: string - required: - - path - - url - type: object - conditions: - description: Conditions holds the conditions for the HelmRepository. - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - \n \ttype FooStatus struct{ \t // Represents the observations - of a foo's current state. \t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\" \t // - +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map - \t // +listMapKey=type \t Conditions []metav1.Condition - `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" - protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields - \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - lastHandledReconcileAt: - description: LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value can - be detected. - type: string - observedGeneration: - description: ObservedGeneration is the last observed generation of - the HelmRepository object. - format: int64 - type: integer - url: - description: URL is the dynamic fetch link for the latest Artifact. - It is provided on a "best effort" basis, and using the precise HelmRepositoryStatus.Artifact - data is recommended. - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.7.0 - creationTimestamp: null - name: ocirepositories.source.toolkit.fluxcd.io -spec: - group: source.toolkit.fluxcd.io - names: - kind: OCIRepository - listKind: OCIRepositoryList - plural: ocirepositories - shortNames: - - ocirepo - singular: ocirepository - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .spec.url - name: URL - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1beta2 - schema: - openAPIV3Schema: - description: OCIRepository is the Schema for the ocirepositories API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: OCIRepositorySpec defines the desired state of OCIRepository - properties: - certSecretRef: - description: "CertSecretRef can be given the name of a secret containing - either or both of \n - a PEM-encoded client certificate (`certFile`) - and private key (`keyFile`); - a PEM-encoded CA certificate (`caFile`) - \n and whichever are supplied, will be used for connecting to the - \ registry. The client cert and key are useful if you are authenticating - with a certificate; the CA cert is useful if you are using a self-signed - server certificate." - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - ignore: - description: Ignore overrides the set of excluded patterns in the - .sourceignore format (which is the same as .gitignore). If not provided, - a default will be used, consult the documentation for your version - to find out what those are. - type: string - insecure: - description: Insecure allows connecting to a non-TLS HTTP container - registry. - type: boolean - interval: - description: The interval at which to check for image updates. - type: string - layerSelector: - description: LayerSelector specifies which layer should be extracted - from the OCI artifact. When not specified, the first layer found - in the artifact is selected. - properties: - mediaType: - description: MediaType specifies the OCI media type of the layer - which should be extracted from the OCI Artifact. The first layer - matching this type is selected. - type: string - type: object - provider: - default: generic - description: The provider used for authentication, can be 'aws', 'azure', - 'gcp' or 'generic'. When not specified, defaults to 'generic'. - enum: - - generic - - aws - - azure - - gcp - type: string - ref: - description: The OCI reference to pull and monitor for changes, defaults - to the latest tag. - properties: - digest: - description: Digest is the image digest to pull, takes precedence - over SemVer. The value should be in the format 'sha256:'. - type: string - semver: - description: SemVer is the range of tags to pull selecting the - latest within the range, takes precedence over Tag. - type: string - tag: - description: Tag is the image tag to pull, defaults to latest. - type: string - type: object - secretRef: - description: SecretRef contains the secret name containing the registry - login credentials to resolve image metadata. The secret must be - of type kubernetes.io/dockerconfigjson. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - serviceAccountName: - description: 'ServiceAccountName is the name of the Kubernetes ServiceAccount - used to authenticate the image pull if the service account has attached - pull secrets. For more information: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#add-imagepullsecrets-to-a-service-account' - type: string - suspend: - description: This flag tells the controller to suspend the reconciliation - of this source. - type: boolean - timeout: - default: 60s - description: The timeout for remote OCI Repository operations like - pulling, defaults to 60s. - type: string - url: - description: URL is a reference to an OCI artifact repository hosted - on a remote container registry. - pattern: ^oci://.*$ - type: string - required: - - interval - - url - type: object - status: - default: - observedGeneration: -1 - description: OCIRepositoryStatus defines the observed state of OCIRepository - properties: - artifact: - description: Artifact represents the output of the last successful - OCI Repository sync. - properties: - checksum: - description: Checksum is the SHA256 checksum of the Artifact file. - type: string - lastUpdateTime: - description: LastUpdateTime is the timestamp corresponding to - the last update of the Artifact. - format: date-time - type: string - metadata: - additionalProperties: - type: string - description: Metadata holds upstream information such as OCI annotations. - type: object - path: - description: Path is the relative file path of the Artifact. It - can be used to locate the file in the root of the Artifact storage - on the local file system of the controller managing the Source. - type: string - revision: - description: Revision is a human-readable identifier traceable - in the origin source system. It can be a Git commit SHA, Git - tag, a Helm chart version, etc. - type: string - size: - description: Size is the number of bytes in the file. - format: int64 - type: integer - url: - description: URL is the HTTP address of the Artifact as exposed - by the controller managing the Source. It can be used to retrieve - the Artifact for consumption, e.g. by another controller applying - the Artifact contents. - type: string - required: - - path - - url - type: object - conditions: - description: Conditions holds the conditions for the OCIRepository. - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - \n \ttype FooStatus struct{ \t // Represents the observations - of a foo's current state. \t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\" \t // - +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map - \t // +listMapKey=type \t Conditions []metav1.Condition - `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" - protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields - \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - lastHandledReconcileAt: - description: LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value can - be detected. - type: string - observedGeneration: - description: ObservedGeneration is the last observed generation. - format: int64 - type: integer - url: - description: URL is the download link for the artifact output of the - last OCI Repository sync. - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/e2e/test_resources/kluctl-crds.yaml b/e2e/test_resources/kluctl-crds.yaml deleted file mode 100644 index 83b9cffcc..000000000 --- a/e2e/test_resources/kluctl-crds.yaml +++ /dev/null @@ -1,567 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.7.0 - creationTimestamp: null - name: kluctldeployments.flux.kluctl.io -spec: - group: flux.kluctl.io - names: - kind: KluctlDeployment - listKind: KluctlDeploymentList - plural: kluctldeployments - singular: kluctldeployment - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .status.lastDeployResult.time - name: Deployed - type: date - - jsonPath: .status.lastPruneResult.time - name: Pruned - type: date - - jsonPath: .status.lastValidateResult.time - name: Validated - type: date - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha1 - schema: - openAPIV3Schema: - description: KluctlDeployment is the Schema for the kluctldeployments API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - properties: - abortOnError: - default: false - description: ForceReplaceOnError instructs kluctl to abort deployments - immediately when something fails. Equivalent to using '--abort-on-error' - when calling kluctl. - type: boolean - args: - description: Args specifies dynamic target args. - type: object - x-kubernetes-preserve-unknown-fields: true - context: - description: If specified, overrides the context to be used. This - will effectively make kluctl ignore the context specified in the - target. - type: string - decryption: - description: Decrypt Kubernetes secrets before applying them on the - cluster. - properties: - provider: - description: Provider is the name of the decryption engine. - enum: - - sops - type: string - secretRef: - description: The secret name containing the private OpenPGP keys - used for decryption. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - required: - - provider - type: object - deployInterval: - description: DeployInterval specifies the interval at which to deploy - the KluctlDeployment. It defaults to the Interval value, meaning - that it will re-deploy on every reconciliation. If you set DeployInterval - to a different value, - pattern: ^(([0-9]+(\.[0-9]+)?(ms|s|m|h))+)|never$ - type: string - deployMode: - default: full-deploy - description: DeployMode specifies what deploy mode should be used - enum: - - full-deploy - - poke-images - type: string - deployOnChanges: - default: true - description: DeployOnChanges will cause a re-deployment whenever the - rendered resources change in the deployment. This check is performed - on every reconciliation. This means that a deployment will be triggered - even before the DeployInterval has passed in case something has - changed in the rendered resources. - type: boolean - dryRun: - default: false - description: DryRun instructs kluctl to run everything in dry-run - mode. Equivalent to using '--dry-run' when calling kluctl. - type: boolean - excludeDeploymentDirs: - description: ExcludeDeploymentDirs instructs kluctl to exclude deployments - with the given dir. Equivalent to using '--exclude-deployment-dir' - when calling kluctl. - items: - type: string - type: array - excludeTags: - description: ExcludeTags instructs kluctl to exclude deployments with - given tags. Equivalent to using '--exclude-tag' when calling kluctl. - items: - type: string - type: array - forceApply: - default: false - description: ForceApply instructs kluctl to force-apply in case of - SSA conflicts. Equivalent to using '--force-apply' when calling - kluctl. - type: boolean - forceReplaceOnError: - default: false - description: ForceReplaceOnError instructs kluctl to force-replace - resources in case a normal replace fails. Equivalent to using '--force-replace-on-error' - when calling kluctl. - type: boolean - images: - description: Images contains a list of fixed image overrides. Equivalent - to using '--fixed-images-file' when calling kluctl. - items: - properties: - container: - type: string - deployTags: - items: - type: string - type: array - deployedImage: - type: string - deployment: - type: string - deploymentDir: - type: string - image: - type: string - namespace: - type: string - object: - description: ObjectRef contains the information necessary to - locate a resource within a cluster. - properties: - group: - type: string - kind: - type: string - name: - type: string - namespace: - type: string - version: - type: string - required: - - group - - kind - - name - - namespace - - version - type: object - registryImage: - type: string - resultImage: - type: string - versionFilter: - type: string - required: - - image - - resultImage - type: object - type: array - includeDeploymentDirs: - description: IncludeDeploymentDirs instructs kluctl to only include - deployments with the given dir. Equivalent to using '--include-deployment-dir' - when calling kluctl. - items: - type: string - type: array - includeTags: - description: IncludeTags instructs kluctl to only include deployments - with given tags. Equivalent to using '--include-tag' when calling - kluctl. - items: - type: string - type: array - interval: - description: The interval at which to reconcile the KluctlDeployment. - By default, the controller will re-deploy and validate the deployment - on each reconciliation. To override this behavior, change the DeployInterval - and/or ValidateInterval values. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - kubeConfig: - description: The KubeConfig for deploying to the target cluster. Specifies - the kubeconfig to be used when invoking kluctl. Contexts in this - kubeconfig must match the context found in the kluctl target. As - an alternative, specify the context to be used via 'context' - properties: - secretRef: - description: SecretRef holds the name of a secret that contains - a key with the kubeconfig file as the value. If no key is set, - the key will default to 'value'. The secret must be in the same - namespace as the Kustomization. It is recommended that the kubeconfig - is self-contained, and the secret is regularly updated if credentials - such as a cloud-access-token expire. Cloud specific `cmd-path` - auth helpers will not function without adding binaries and credentials - to the Pod that is responsible for reconciling the KluctlDeployment. - properties: - key: - description: Key in the Secret, when not specified an implementation-specific - default key is used. - type: string - name: - description: Name of the Secret. - type: string - required: - - name - type: object - type: object - noWait: - default: false - description: NoWait instructs kluctl to not wait for any resources - to become ready, including hooks. Equivalent to using '--no-wait' - when calling kluctl. - type: boolean - path: - description: Path to the directory containing the .kluctl.yaml file, - or the Defaults to 'None', which translates to the root path of - the SourceRef. - type: string - prune: - default: false - description: Prune enables pruning after deploying. - type: boolean - registrySecrets: - description: RegistrySecrets is a list of secret references to be - used for image registry authentication. The secrets must either - have ".dockerconfigjson" included or "registry", "username" and - "password". Additionally, "caFile" and "insecure" can be specified. - items: - description: LocalObjectReference contains enough information to - locate the referenced Kubernetes resource object. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - type: array - renameContexts: - description: RenameContexts specifies a list of context rename operations. - This is useful when the kluctl target's context does not match with - the contexts found in the kubeconfig while deploying. This is the - case when using kubeconfigs generated from service accounts, in - which case the context name is always "default". - items: - description: RenameContext specifies a single rename of a context - properties: - newContext: - description: NewContext is the new name of the context - type: string - oldContext: - description: OldContext is the name of the context to be renamed - type: string - required: - - newContext - - oldContext - type: object - type: array - replaceOnError: - default: false - description: ReplaceOnError instructs kluctl to replace resources - on error. Equivalent to using '--replace-on-error' when calling - kluctl. - type: boolean - retryInterval: - description: The interval at which to retry a previously failed reconciliation. - When not specified, the controller uses the Interval value to retry - failures. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - serviceAccountName: - description: The name of the Kubernetes service account to use while - deploying. If not specified, the default service account is used. - type: string - sourceRef: - description: Reference of the source where the kluctl project is. - The authentication secrets from the source are also used to authenticate - dependent git repositories which are cloned while deploying the - kluctl project. - properties: - apiVersion: - description: API version of the referent, if not specified the - Kubernetes preferred version will be used. - type: string - kind: - description: Kind of the referent. - type: string - name: - description: Name of the referent. - type: string - namespace: - description: Namespace of the referent, when not specified it - acts as LocalObjectReference. - type: string - required: - - kind - - name - type: object - suspend: - description: This flag tells the controller to suspend subsequent - kluctl executions, it does not apply to already started executions. - Defaults to false. - type: boolean - target: - description: Target specifies the kluctl target to deploy. If not - specified, an empty target is used that has no name and no context. - Use 'TargetName' and 'Context' to specify the name and context in - that case. - maxLength: 63 - minLength: 1 - type: string - targetNameOverride: - description: TargetNameOverride sets or overrides the target name. - This is especially useful when deployment without a target. - maxLength: 63 - minLength: 1 - type: string - timeout: - description: Timeout for all operations. Defaults to 'Interval' duration. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - updateImages: - default: false - description: UpdateImages instructs kluctl to update dynamic images. - Equivalent to using '-u' when calling kluctl. - type: boolean - validate: - default: true - description: Validate enables validation after deploying - type: boolean - validateInterval: - description: ValidateInterval specifies the interval at which to validate - the KluctlDeployment. Validation is performed the same way as with - 'kluctl validate -t '. Defaults to the same value as specified - in Interval. Validate is also performed whenever a deployment is - performed, independent of the value of ValidateInterval - pattern: ^(([0-9]+(\.[0-9]+)?(ms|s|m|h))+)|never$ - type: string - required: - - interval - - sourceRef - type: object - status: - description: KluctlDeploymentStatus defines the observed state of KluctlDeployment - properties: - commonLabels: - additionalProperties: - type: string - description: CommonLabels are the commonLabels found in the deployment - project when the last deployment was done. This is used to perform - cleanup/deletion in case the KluctlDeployment project is deleted - type: object - conditions: - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - \n \ttype FooStatus struct{ \t // Represents the observations - of a foo's current state. \t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\" \t // - +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map - \t // +listMapKey=type \t Conditions []metav1.Condition - `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" - protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields - \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - lastAttemptedRevision: - description: LastAttemptedRevision is the revision of the last reconciliation - attempt. - type: string - lastDeployResult: - description: LastDeployResult is the result of the last deploy command - properties: - error: - type: string - objectsHash: - description: ObjectsHash is the hash of all rendered objects - type: string - rawResult: - type: string - revision: - description: Revision is the source revision. Please note that - kluctl projects have dependent git repositories which are not - considered in the source revision - type: string - target: - type: string - targetNameOverride: - type: string - time: - description: AttemptedAt is the time when the attempt was performed - format: date-time - type: string - required: - - time - type: object - lastHandledDeployAt: - type: string - lastHandledReconcileAt: - description: LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value can - be detected. - type: string - lastPruneResult: - description: LastDeployResult is the result of the last prune command - properties: - error: - type: string - objectsHash: - description: ObjectsHash is the hash of all rendered objects - type: string - rawResult: - type: string - revision: - description: Revision is the source revision. Please note that - kluctl projects have dependent git repositories which are not - considered in the source revision - type: string - target: - type: string - targetNameOverride: - type: string - time: - description: AttemptedAt is the time when the attempt was performed - format: date-time - type: string - required: - - time - type: object - lastValidateResult: - description: LastValidateResult is the result of the last validate - command - properties: - error: - type: string - objectsHash: - description: ObjectsHash is the hash of all rendered objects - type: string - rawResult: - type: string - revision: - description: Revision is the source revision. Please note that - kluctl projects have dependent git repositories which are not - considered in the source revision - type: string - target: - type: string - targetNameOverride: - type: string - time: - description: AttemptedAt is the time when the attempt was performed - format: date-time - type: string - required: - - time - type: object - observedGeneration: - description: ObservedGeneration is the last reconciled generation. - format: int64 - type: integer - rawTarget: - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/e2e/test_resources/kluctl-deployment.yaml b/e2e/test_resources/kluctl-deployment.yaml deleted file mode 100644 index eae1bec80..000000000 --- a/e2e/test_resources/kluctl-deployment.yaml +++ /dev/null @@ -1,35 +0,0 @@ ---- -apiVersion: v1 -kind: Namespace -metadata: - name: flux-test ---- -apiVersion: flux.kluctl.io/v1alpha1 -kind: KluctlDeployment -metadata: - name: microservices-demo-test - namespace: flux-test -spec: - interval: 10m - path: ./simple - prune: true - renameContexts: - - newContext: kind-kind - oldContext: default - sourceRef: - kind: GitRepository - name: microservices-demo - namespace: flux-test - target: simple - timeout: 2m ---- -apiVersion: source.toolkit.fluxcd.io/v1beta1 -kind: GitRepository -metadata: - name: microservices-demo - namespace: flux-test -spec: - interval: 5m - ref: - branch: main - url: https://github.com/gitbluf/kluctl-examples.git From 39b6b2e11ed12869a2b1c5c28b7db0f62e980ec6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 15 May 2023 14:25:45 +0200 Subject: [PATCH 1631/2916] chore: Add flux_utils package This package contains some code from the flux codebase. It is used to keep some compatibility to the old flux-kluctl-controller codebase. The plan is to remove this code as well in the long-term. --- pkg/utils/flux_utils/client.go | 84 +++++++++++++++++ pkg/utils/flux_utils/conditions/doc.go | 24 +++++ pkg/utils/flux_utils/conditions/getter.go | 52 +++++++++++ pkg/utils/flux_utils/conditions/setter.go | 16 ++++ pkg/utils/flux_utils/meta/conditions.go | 93 +++++++++++++++++++ pkg/utils/flux_utils/metrics.go | 106 +++++++++++++++++++++ pkg/utils/flux_utils/metrics/doc.go | 18 ++++ pkg/utils/flux_utils/metrics/recorder.go | 108 ++++++++++++++++++++++ 8 files changed, 501 insertions(+) create mode 100644 pkg/utils/flux_utils/client.go create mode 100644 pkg/utils/flux_utils/conditions/doc.go create mode 100644 pkg/utils/flux_utils/conditions/getter.go create mode 100644 pkg/utils/flux_utils/conditions/setter.go create mode 100644 pkg/utils/flux_utils/meta/conditions.go create mode 100644 pkg/utils/flux_utils/metrics.go create mode 100644 pkg/utils/flux_utils/metrics/doc.go create mode 100644 pkg/utils/flux_utils/metrics/recorder.go diff --git a/pkg/utils/flux_utils/client.go b/pkg/utils/flux_utils/client.go new file mode 100644 index 000000000..05e796cc7 --- /dev/null +++ b/pkg/utils/flux_utils/client.go @@ -0,0 +1,84 @@ +/* +Copyright 2021 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package flux_utils + +import ( + "context" + + "github.com/spf13/pflag" + "k8s.io/client-go/rest" + "sigs.k8s.io/cli-utils/pkg/flowcontrol" + ctrl "sigs.k8s.io/controller-runtime" +) + +const ( + flagQPS = "kube-api-qps" + flagBurst = "kube-api-burst" +) + +// Options contains the runtime configuration for a Kubernetes client. +// +// The struct can be used in the main.go file of your controller by binding it to the main flag set, and then utilizing +// the configured options later: +// +// func main() { +// var ( +// // other controller specific configuration variables +// clientOptions client.Options +// ) +// +// // Bind the options to the main flag set, and parse it +// clientOptions.BindFlags(flag.CommandLine) +// flag.Parse() +// +// // Get a runtime Kubernetes client configuration with the options set +// restConfig := client.GetConfigOrDie(clientOptions) +// } +type Options struct { + // QPS indicates the maximum queries-per-second of requests sent to the Kubernetes API, defaults to 50. + QPS float32 + + // Burst indicates the maximum burst queries-per-second of requests sent to the Kubernetes API, defaults to 300. + Burst int +} + +// BindFlags will parse the given pflag.FlagSet for Kubernetes client option flags and set the Options accordingly. +func (o *Options) BindFlags(fs *pflag.FlagSet) { + fs.Float32Var(&o.QPS, flagQPS, 50.0, + "The maximum queries-per-second of requests sent to the Kubernetes API.") + fs.IntVar(&o.Burst, flagBurst, 300, + "The maximum burst queries-per-second of requests sent to the Kubernetes API.") +} + +// GetConfigOrDie wraps ctrl.GetConfigOrDie and checks if the Kubernetes apiserver +// has PriorityAndFairness flow control filter enabled. If true, it returns a rest.Config +// with client side throttling disabled. Otherwise, it returns a modified rest.Config +// configured with the provided Options. +func GetConfigOrDie(opts Options) *rest.Config { + config := ctrl.GetConfigOrDie() + enabled, err := flowcontrol.IsEnabled(context.Background(), config) + if err == nil && enabled { + // A negative QPS and Burst indicates that the client should not have a rate limiter. + // Ref: https://github.com/kubernetes/kubernetes/blob/v1.24.0/staging/src/k8s.io/client-go/rest/config.go#L354-L364 + config.QPS = -1 + config.Burst = -1 + return config + } + config.QPS = opts.QPS + config.Burst = opts.Burst + return config +} diff --git a/pkg/utils/flux_utils/conditions/doc.go b/pkg/utils/flux_utils/conditions/doc.go new file mode 100644 index 000000000..9dfbc882f --- /dev/null +++ b/pkg/utils/flux_utils/conditions/doc.go @@ -0,0 +1,24 @@ +/* +Copyright 2021 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package conditions provides utilities for manipulating the status conditions of Kubernetes resource objects that +// implement the Getter and/or Setter interfaces. +// +// Usage of this package within GitOps Toolkit components working with conditions is RECOMMENDED, as it provides a wide +// range of helpers to work around common reconciler problems, like setting a Condition status based on a +// summarization of other conditions, producing an aggregate Condition based on the conditions of a list of Kubernetes +// resources objects, recognition of negative polarity or "abnormal-true" conditions, etc. +package conditions diff --git a/pkg/utils/flux_utils/conditions/getter.go b/pkg/utils/flux_utils/conditions/getter.go new file mode 100644 index 000000000..594625f39 --- /dev/null +++ b/pkg/utils/flux_utils/conditions/getter.go @@ -0,0 +1,52 @@ +/* +Copyright 2020 The Kubernetes Authors. +Copyright 2021 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +This file is modified from the source at +https://github.com/kubernetes-sigs/cluster-api/tree/7478817225e0a75acb6e14fc7b438231578073d2/util/conditions/getter.go, +and initially adapted to work with the `metav1.Condition` and `metav1.ConditionStatus` types. +More concretely, this includes the removal of "condition severity" related functionalities, as this is not supported by +the `metav1.Condition` type. +*/ + +package conditions + +import ( + "github.com/kluctl/kluctl/v2/pkg/utils/flux_utils/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// Getter interface defines methods that a Kubernetes resource object should implement in order to use the conditions +// package for getting conditions. +type Getter interface { + client.Object + meta.ObjectWithConditions +} + +// Get returns the condition with the given type, if the condition does not exists, it returns nil. +func Get(from Getter, t string) *metav1.Condition { + conditions := from.GetConditions() + if conditions == nil { + return nil + } + + for _, condition := range conditions { + if condition.Type == t { + return &condition + } + } + return nil +} diff --git a/pkg/utils/flux_utils/conditions/setter.go b/pkg/utils/flux_utils/conditions/setter.go new file mode 100644 index 000000000..85aab127e --- /dev/null +++ b/pkg/utils/flux_utils/conditions/setter.go @@ -0,0 +1,16 @@ +package conditions + +import ( + "fmt" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// UnknownCondition returns a condition with Status=Unknown and the given type, reason and message. +func UnknownCondition(t, reason, messageFormat string, messageArgs ...interface{}) *metav1.Condition { + return &metav1.Condition{ + Type: t, + Status: metav1.ConditionUnknown, + Reason: reason, + Message: fmt.Sprintf(messageFormat, messageArgs...), + } +} diff --git a/pkg/utils/flux_utils/meta/conditions.go b/pkg/utils/flux_utils/meta/conditions.go new file mode 100644 index 000000000..fe798792f --- /dev/null +++ b/pkg/utils/flux_utils/meta/conditions.go @@ -0,0 +1,93 @@ +/* +Copyright 2022 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package meta + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// These constants define generic Condition types to be used by GitOps Toolkit components. +// +// The ReadyCondition SHOULD be implemented by all components' Kubernetes resources to indicate they have been fully +// reconciled by their respective reconciler. This MAY suffice for simple resources, e.g. a resource that just declares +// state once and is not expected to receive any updates afterwards. +// +// For Kubernetes resources that are expected to receive spec updates over time, take a longer time to reconcile, or +// deal with more complex logic in which for example a finite error state can be observed, it is RECOMMENDED to +// implement the StalledCondition and ReconcilingCondition. +// +// By doing this, observers making use of kstatus to determine the current state of the resource will have a better +// experience while they are e.g. waiting for a change to be reconciled, and will be able to stop waiting for a change +// if a StalledCondition is observed, without having to rely on a timeout. +// +// For more information on kstatus, see: +// https://github.com/kubernetes-sigs/cli-utils/blob/v0.25.0/pkg/kstatus/README.md +const ( + // ReadyCondition indicates the resource is ready and fully reconciled. + // If the Condition is False, the resource SHOULD be considered to be in the process of reconciling and not a + // representation of actual state. + ReadyCondition string = "Ready" + + // StalledCondition indicates the reconciliation of the resource has stalled, e.g. because the controller has + // encountered an error during the reconcile process or it has made insufficient progress (timeout). + // The Condition adheres to an "abnormal-true" polarity pattern, and MUST only be present on the resource if the + // Condition is True. + // For more information about polarity patterns, see: + // https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties + StalledCondition string = "Stalled" + + // ReconcilingCondition indicates the controller is currently working on reconciling the latest changes. This MAY be + // True for multiple reconciliation attempts, e.g. when an transient error occurred. + // The Condition adheres to an "abnormal-true" polarity pattern, and MUST only be present on the resource if the + // Condition is True. + // For more information about polarity patterns, see: + // https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties + ReconcilingCondition string = "Reconciling" +) + +// These constants define generic Condition reasons to be used by GitOps Toolkit components. +// +// Making use of a generic Reason is RECOMMENDED whenever it can be applied to a Condition in which it provides +// sufficient context together with the type to summarize the meaning of the Condition cause. +// +// Where any of the generic Condition reasons does not suffice, GitOps Toolkit components can introduce new reasons to +// their API specification, or use an arbitrary PascalCase string when setting the Condition. +// Declaration of domain common Condition reasons in the API specification is RECOMMENDED, as it eases observations +// for user and computer. +// +// For more information on Condition reason conventions, see: +// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties +const ( + + // ProgressingReason indicates a condition or event observed progression, for example when the reconciliation of a + // resource or an action has started. + // + // When this reason is given, other conditions and types MAY no longer be considered as an up-to-date observation. + // Producers of the specific condition type or event SHOULD provide more information about the expectations and + // precise meaning in their API specification. + // + // More information about the reason or the current state of the progression MAY be available as additional metadata + // in an attached message. + ProgressingReason string = "Progressing" +) + +// ObjectWithConditions describes a Kubernetes resource object with status conditions. +// +k8s:deepcopy-gen=false +type ObjectWithConditions interface { + // GetConditions returns a slice of metav1.Condition + GetConditions() []metav1.Condition +} diff --git a/pkg/utils/flux_utils/metrics.go b/pkg/utils/flux_utils/metrics.go new file mode 100644 index 000000000..afb122d74 --- /dev/null +++ b/pkg/utils/flux_utils/metrics.go @@ -0,0 +1,106 @@ +/* +Copyright 2020 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package flux_utils + +import ( + "context" + "github.com/kluctl/kluctl/v2/pkg/utils/flux_utils/conditions" + "github.com/kluctl/kluctl/v2/pkg/utils/flux_utils/meta" + "github.com/kluctl/kluctl/v2/pkg/utils/flux_utils/metrics" + "time" + + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/tools/reference" +) + +// Metrics is a helper struct that adds the capability for recording GitOps Toolkit standard metrics to a reconciler. +// +// Use it by embedding it in your reconciler struct: +// +// type MyTypeReconciler { +// client.Client +// // ... etc. +// controller.Metrics +// } +// +// Following the GitOps Toolkit conventions, API types used in GOTK SHOULD implement conditions.Getter to work with +// status condition types, and this convention MUST be followed to be able to record metrics using this helper. +// +// Use MustMakeMetrics to create a working Metrics value; you can supply the same value to all reconcilers. +// +// Once initialised, metrics can be recorded by calling one of the available `Record*` methods. +type Metrics struct { + Scheme *runtime.Scheme + MetricsRecorder *metrics.Recorder +} + +// RecordDuration records the duration of a reconcile attempt for the given obj based on the given startTime. +func (m Metrics) RecordDuration(ctx context.Context, obj conditions.Getter, startTime time.Time) { + if m.MetricsRecorder != nil { + ref, err := reference.GetReference(m.Scheme, obj) + if err != nil { + logr.FromContextOrDiscard(ctx).Error(err, "unable to get object reference to record duration") + return + } + m.MetricsRecorder.RecordDuration(*ref, startTime) + } +} + +// RecordSuspend records the suspension of the given obj based on the given suspend value. +func (m Metrics) RecordSuspend(ctx context.Context, obj conditions.Getter, suspend bool) { + if m.MetricsRecorder != nil { + ref, err := reference.GetReference(m.Scheme, obj) + if err != nil { + logr.FromContextOrDiscard(ctx).Error(err, "unable to get object reference to record suspend") + return + } + m.MetricsRecorder.RecordSuspend(*ref, suspend) + } +} + +// RecordReadiness records the flux_utils.ReadyCondition status for the given obj. +func (m Metrics) RecordReadiness(ctx context.Context, obj conditions.Getter) { + m.RecordCondition(ctx, obj, meta.ReadyCondition) +} + +// RecordReconciling records the flux_utils.ReconcilingCondition status for the given obj. +func (m Metrics) RecordReconciling(ctx context.Context, obj conditions.Getter) { + m.RecordCondition(ctx, obj, meta.ReconcilingCondition) +} + +// RecordStalled records the flux_utils.StalledCondition status for the given obj. +func (m Metrics) RecordStalled(ctx context.Context, obj conditions.Getter) { + m.RecordCondition(ctx, obj, meta.StalledCondition) +} + +// RecordCondition records the status of the given conditionType for the given obj. +func (m Metrics) RecordCondition(ctx context.Context, obj conditions.Getter, conditionType string) { + if m.MetricsRecorder == nil { + return + } + ref, err := reference.GetReference(m.Scheme, obj) + if err != nil { + logr.FromContextOrDiscard(ctx).Error(err, "unable to get object reference to record condition metric") + return + } + rc := conditions.Get(obj, conditionType) + if rc == nil { + rc = conditions.UnknownCondition(conditionType, "", "") + } + m.MetricsRecorder.RecordCondition(*ref, *rc, !obj.GetDeletionTimestamp().IsZero()) +} diff --git a/pkg/utils/flux_utils/metrics/doc.go b/pkg/utils/flux_utils/metrics/doc.go new file mode 100644 index 000000000..19e456747 --- /dev/null +++ b/pkg/utils/flux_utils/metrics/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2021 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package metrics contains a Recorder and helpers for recoding standard metrics for all GitOps Toolkit components. +package metrics diff --git a/pkg/utils/flux_utils/metrics/recorder.go b/pkg/utils/flux_utils/metrics/recorder.go new file mode 100644 index 000000000..88ead2fe6 --- /dev/null +++ b/pkg/utils/flux_utils/metrics/recorder.go @@ -0,0 +1,108 @@ +/* +Copyright 2021 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package metrics + +import ( + "time" + + "github.com/prometheus/client_golang/prometheus" + corev1 "k8s.io/api/core/v1" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + ConditionDeleted = "Deleted" +) + +// Recorder is a struct for recording GitOps Toolkit metrics for a controller. +// +// Use NewRecorder to initialise it with properly configured metric names. +type Recorder struct { + conditionGauge *prometheus.GaugeVec + suspendGauge *prometheus.GaugeVec + durationHistogram *prometheus.HistogramVec +} + +// NewRecorder returns a new Recorder with all metric names configured confirm GitOps Toolkit standards. +func NewRecorder() *Recorder { + return &Recorder{ + conditionGauge: prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "gotk_reconcile_condition", + Help: "The current condition status of a GitOps Toolkit resource reconciliation.", + }, + []string{"kind", "name", "namespace", "type", "status"}, + ), + suspendGauge: prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "gotk_suspend_status", + Help: "The current suspend status of a GitOps Toolkit resource.", + }, + []string{"kind", "name", "namespace"}, + ), + durationHistogram: prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Name: "gotk_reconcile_duration_seconds", + Help: "The duration in seconds of a GitOps Toolkit resource reconciliation.", + // Use a histogram with 10 count buckets between 1ms - 1hour + Buckets: prometheus.ExponentialBucketsRange(10e-3, 1800, 10), + }, + []string{"kind", "name", "namespace"}, + ), + } +} + +// Collectors returns a slice of Prometheus collectors, which can be used to register them in a metrics registry. +func (r *Recorder) Collectors() []prometheus.Collector { + return []prometheus.Collector{ + r.conditionGauge, + r.suspendGauge, + r.durationHistogram, + } +} + +// RecordCondition records the condition as given for the ref. +func (r *Recorder) RecordCondition(ref corev1.ObjectReference, condition metav1.Condition, deleted bool) { + for _, status := range []string{string(metav1.ConditionTrue), string(metav1.ConditionFalse), string(metav1.ConditionUnknown), ConditionDeleted} { + var value float64 + if deleted { + if status == ConditionDeleted { + value = 1 + } + } else { + if status == string(condition.Status) { + value = 1 + } + } + r.conditionGauge.WithLabelValues(ref.Kind, ref.Name, ref.Namespace, condition.Type, status).Set(value) + } +} + +// RecordSuspend records the suspend status as given for the ref. +func (r *Recorder) RecordSuspend(ref corev1.ObjectReference, suspend bool) { + var value float64 + if suspend { + value = 1 + } + r.suspendGauge.WithLabelValues(ref.Kind, ref.Name, ref.Namespace).Set(value) +} + +// RecordDuration records the duration since start for the given ref. +func (r *Recorder) RecordDuration(ref corev1.ObjectReference, start time.Time) { + r.durationHistogram.WithLabelValues(ref.Kind, ref.Name, ref.Namespace).Observe(time.Since(start).Seconds()) +} From 0a5e70197573b8d035a2f90ed21019dde3870555 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 15 May 2023 15:20:00 +0200 Subject: [PATCH 1632/2916] feat: Implement Kluctl controller inside Kluctl itself --- PROJECT | 20 + api/v1beta1/condition_types.go | 38 + api/v1beta1/doc.go | 20 + api/v1beta1/groupversion_info.go | 36 + api/v1beta1/kluctldeployment_types.go | 370 ++++++ api/v1beta1/util_types.go | 60 + cmd/kluctl/commands/cmd_controller.go | 140 +++ cmd/kluctl/commands/root.go | 5 +- .../gitops.kluctl.io_kluctldeployments.yaml | 1015 +++++++++++++++++ config/crd/kustomization.yaml | 21 + config/crd/kustomizeconfig.yaml | 19 + .../cainjection_in_kluctldeployments.yaml | 7 + .../patches/webhook_in_kluctldeployments.yaml | 16 + config/default/kustomization.yaml | 137 +++ config/default/manager_config_patch.yaml | 10 + config/manager/kustomization.yaml | 2 + config/manager/manager.yaml | 102 ++ config/prometheus/kustomization.yaml | 2 + config/prometheus/monitor.yaml | 26 + config/rbac/kluctldeployment_editor_role.yaml | 31 + config/rbac/kluctldeployment_viewer_role.yaml | 27 + config/rbac/kustomization.yaml | 11 + config/rbac/leader_election_role.yaml | 44 + config/rbac/leader_election_role_binding.yaml | 19 + config/rbac/role.yaml | 54 + config/rbac/role_binding.yaml | 19 + config/rbac/service_account.yaml | 12 + docs/reference/commands/controller.md | 25 + hack/boilerplate.go.txt | 15 + .../internal/metrics/kluctl_project.go | 122 ++ .../metrics/kluctldeployment_controller.go | 89 ++ pkg/controllers/internal/sops/LICENSE | 373 ++++++ pkg/controllers/internal/sops/aws_config.go | 24 + pkg/controllers/internal/sops/azure_config.go | 156 +++ .../internal/sops/key_server_from_secret.go | 106 ++ .../sops/key_server_from_service_account.go | 91 ++ .../internal/sops/keyservice/keyservice.go | 23 + .../internal/sops/keyservice/options.go | 87 ++ .../internal/sops/keyservice/server.go | 363 ++++++ .../internal/sops/keyservice/server_test.go | 249 ++++ .../sops/keyservice/testdata/private.gpg | 81 ++ .../sops/keyservice/testdata/public.gpg | 41 + .../internal/sops/keyservice/utils_test.go | 105 ++ pkg/controllers/kluctl_project.go | 820 +++++++++++++ .../kluctldeployment_controller.go | 506 ++++++++ .../kluctldeployment_controller_setup.go | 28 + .../kluctldeployment_controller_source.go | 109 ++ .../kluctldeployment_controller_utils.go | 72 ++ pkg/controllers/predicates.go | 66 ++ pkg/controllers/utils.go | 31 + pkg/deployment/commands/result_utils.go | 2 +- pkg/deployment/deployment_collection.go | 41 + pkg/types/result/command_result.go | 2 +- pkg/types/result/summary.go | 6 +- 54 files changed, 5892 insertions(+), 4 deletions(-) create mode 100644 PROJECT create mode 100644 api/v1beta1/condition_types.go create mode 100644 api/v1beta1/doc.go create mode 100644 api/v1beta1/groupversion_info.go create mode 100644 api/v1beta1/kluctldeployment_types.go create mode 100644 api/v1beta1/util_types.go create mode 100644 cmd/kluctl/commands/cmd_controller.go create mode 100644 config/crd/bases/gitops.kluctl.io_kluctldeployments.yaml create mode 100644 config/crd/kustomization.yaml create mode 100644 config/crd/kustomizeconfig.yaml create mode 100644 config/crd/patches/cainjection_in_kluctldeployments.yaml create mode 100644 config/crd/patches/webhook_in_kluctldeployments.yaml create mode 100644 config/default/kustomization.yaml create mode 100644 config/default/manager_config_patch.yaml create mode 100644 config/manager/kustomization.yaml create mode 100644 config/manager/manager.yaml create mode 100644 config/prometheus/kustomization.yaml create mode 100644 config/prometheus/monitor.yaml create mode 100644 config/rbac/kluctldeployment_editor_role.yaml create mode 100644 config/rbac/kluctldeployment_viewer_role.yaml create mode 100644 config/rbac/kustomization.yaml create mode 100644 config/rbac/leader_election_role.yaml create mode 100644 config/rbac/leader_election_role_binding.yaml create mode 100644 config/rbac/role.yaml create mode 100644 config/rbac/role_binding.yaml create mode 100644 config/rbac/service_account.yaml create mode 100644 docs/reference/commands/controller.md create mode 100644 hack/boilerplate.go.txt create mode 100644 pkg/controllers/internal/metrics/kluctl_project.go create mode 100644 pkg/controllers/internal/metrics/kluctldeployment_controller.go create mode 100644 pkg/controllers/internal/sops/LICENSE create mode 100644 pkg/controllers/internal/sops/aws_config.go create mode 100644 pkg/controllers/internal/sops/azure_config.go create mode 100644 pkg/controllers/internal/sops/key_server_from_secret.go create mode 100644 pkg/controllers/internal/sops/key_server_from_service_account.go create mode 100644 pkg/controllers/internal/sops/keyservice/keyservice.go create mode 100644 pkg/controllers/internal/sops/keyservice/options.go create mode 100644 pkg/controllers/internal/sops/keyservice/server.go create mode 100644 pkg/controllers/internal/sops/keyservice/server_test.go create mode 100644 pkg/controllers/internal/sops/keyservice/testdata/private.gpg create mode 100644 pkg/controllers/internal/sops/keyservice/testdata/public.gpg create mode 100644 pkg/controllers/internal/sops/keyservice/utils_test.go create mode 100644 pkg/controllers/kluctl_project.go create mode 100644 pkg/controllers/kluctldeployment_controller.go create mode 100644 pkg/controllers/kluctldeployment_controller_setup.go create mode 100644 pkg/controllers/kluctldeployment_controller_source.go create mode 100644 pkg/controllers/kluctldeployment_controller_utils.go create mode 100644 pkg/controllers/predicates.go create mode 100644 pkg/controllers/utils.go diff --git a/PROJECT b/PROJECT new file mode 100644 index 000000000..9a4660f2d --- /dev/null +++ b/PROJECT @@ -0,0 +1,20 @@ +# Code generated by tool. DO NOT EDIT. +# This file is used to track the info used to scaffold your project +# and allow the plugins properly work. +# More info: https://book.kubebuilder.io/reference/project-config.html +domain: kluctl.io +layout: +- go.kubebuilder.io/v4 +projectName: controller +repo: github.com/kluctl/kluctl/v2 +resources: +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: kluctl.io + group: gitops + kind: KluctlDeployment + path: github.com/kluctl/kluctl/v2/api/v1beta1 + version: v1beta1 +version: "3" diff --git a/api/v1beta1/condition_types.go b/api/v1beta1/condition_types.go new file mode 100644 index 000000000..e559651c3 --- /dev/null +++ b/api/v1beta1/condition_types.go @@ -0,0 +1,38 @@ +/* +Copyright 2023. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +const ( + // DeployFailedReason represents the fact that the + // kluctl deploy command failed. + DeployFailedReason string = "DeployFailed" + + // PruneFailedReason represents the fact that the + // pruning of the KluctlDeployment failed. + PruneFailedReason string = "PruneFailed" + + // ValidateFailedReason represents the fact that the + // validate of the KluctlDeployment failed. + ValidateFailedReason string = "ValidateFailed" + + // PrepareFailedReason represents failure in the kluctl preparation phase + PrepareFailedReason string = "PrepareFailed" + + // ReconciliationSucceededReason represents the fact that + // the reconciliation succeeded. + ReconciliationSucceededReason string = "ReconciliationSucceeded" +) diff --git a/api/v1beta1/doc.go b/api/v1beta1/doc.go new file mode 100644 index 000000000..7dae3af55 --- /dev/null +++ b/api/v1beta1/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2023. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package v1beta1 contains API Schema definitions for the flux.kluctl.io v1beta1 API group. +// +kubebuilder:object:generate=true +// +groupName=gitops.kluctl.io +package v1beta1 diff --git a/api/v1beta1/groupversion_info.go b/api/v1beta1/groupversion_info.go new file mode 100644 index 000000000..1092fbc63 --- /dev/null +++ b/api/v1beta1/groupversion_info.go @@ -0,0 +1,36 @@ +/* +Copyright 2023. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package v1beta1 contains API Schema definitions for the v1beta1 API group +// +kubebuilder:object:generate=true +// +groupName=gitops.kluctl.io +package v1beta1 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +var ( + // GroupVersion is group version used to register these objects + GroupVersion = schema.GroupVersion{Group: "gitops.kluctl.io", Version: "v1beta1"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} + + // AddToScheme adds the types in this group-version to the given scheme. + AddToScheme = SchemeBuilder.AddToScheme +) diff --git a/api/v1beta1/kluctldeployment_types.go b/api/v1beta1/kluctldeployment_types.go new file mode 100644 index 000000000..3da415e20 --- /dev/null +++ b/api/v1beta1/kluctldeployment_types.go @@ -0,0 +1,370 @@ +/* +Copyright 2023. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/types/result" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "time" +) + +const ( + KluctlDeploymentKind = "KluctlDeployment" + KluctlDeploymentFinalizer = "finalizers.gitops.kluctl.io" + MaxConditionMessageLength = 20000 + + KluctlDeployModeFull = "full-deploy" + KluctlDeployPokeImages = "poke-images" + + KluctlRequestReconcileAnnotation = "kluctl.io/request-reconcile" + KluctlRequestDeployAnnotation = "kluctl.io/request-deploy" +) + +type KluctlDeploymentSpec struct { + // Specifies the project source location + Source ProjectSource `json:"source"` + + // Decrypt Kubernetes secrets before applying them on the cluster. + // +optional + Decryption *Decryption `json:"decryption,omitempty"` + + // The interval at which to reconcile the KluctlDeployment. + // Reconciliation means that the deployment is fully rendered and only deployed when the result changes compared + // to the last deployment. + // To override this behavior, set the DeployInterval value. + // +required + // +kubebuilder:validation:Type=string + // +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$" + Interval metav1.Duration `json:"interval"` + + // The interval at which to retry a previously failed reconciliation. + // When not specified, the controller uses the Interval + // value to retry failures. + // +optional + // +kubebuilder:validation:Type=string + // +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$" + RetryInterval *metav1.Duration `json:"retryInterval,omitempty"` + + // DeployInterval specifies the interval at which to deploy the KluctlDeployment, even in cases the rendered + // result does not change. + // +optional + DeployInterval *SafeDuration `json:"deployInterval,omitempty"` + + // ValidateInterval specifies the interval at which to validate the KluctlDeployment. + // Validation is performed the same way as with 'kluctl validate -t '. + // Defaults to the same value as specified in Interval. + // Validate is also performed whenever a deployment is performed, independent of the value of ValidateInterval + // +optional + ValidateInterval *SafeDuration `json:"validateInterval,omitempty"` + + // Timeout for all operations. + // Defaults to 'Interval' duration. + // +optional + // +kubebuilder:validation:Type=string + // +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$" + Timeout *metav1.Duration `json:"timeout,omitempty"` + + // This flag tells the controller to suspend subsequent kluctl executions, + // it does not apply to already started executions. Defaults to false. + // +optional + Suspend bool `json:"suspend,omitempty"` + + // HelmCredentials is a list of Helm credentials used when non pre-pulled Helm Charts are used inside a + // Kluctl deployment. + // +optional + HelmCredentials []HelmCredentials `json:"helmCredentials,omitempty"` + + // The name of the Kubernetes service account to use while deploying. + // If not specified, the default service account is used. + // +optional + ServiceAccountName string `json:"serviceAccountName,omitempty"` + + // The KubeConfig for deploying to the target cluster. + // Specifies the kubeconfig to be used when invoking kluctl. Contexts in this kubeconfig must match + // the context found in the kluctl target. As an alternative, specify the context to be used via 'context' + // +optional + KubeConfig *KubeConfig `json:"kubeConfig"` + + // Target specifies the kluctl target to deploy. If not specified, an empty target is used that has no name and no + // context. Use 'TargetName' and 'Context' to specify the name and context in that case. + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=63 + // +optional + Target *string `json:"target,omitempty"` + + // TargetNameOverride sets or overrides the target name. This is especially useful when deployment without a target. + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=63 + // +optional + TargetNameOverride *string `json:"targetNameOverride,omitempty"` + + // If specified, overrides the context to be used. This will effectively make kluctl ignore the context specified + // in the target. + // +optional + Context *string `json:"context,omitempty"` + + // Args specifies dynamic target args. + // +optional + // +kubebuilder:pruning:PreserveUnknownFields + Args runtime.RawExtension `json:"args,omitempty"` + + // Images contains a list of fixed image overrides. + // Equivalent to using '--fixed-images-file' when calling kluctl. + // +optional + Images []types.FixedImage `json:"images,omitempty"` + + // DryRun instructs kluctl to run everything in dry-run mode. + // Equivalent to using '--dry-run' when calling kluctl. + // +kubebuilder:default:=false + // +optional + DryRun bool `json:"dryRun,omitempty"` + + // NoWait instructs kluctl to not wait for any resources to become ready, including hooks. + // Equivalent to using '--no-wait' when calling kluctl. + // +kubebuilder:default:=false + // +optional + NoWait bool `json:"noWait,omitempty"` + + // ForceApply instructs kluctl to force-apply in case of SSA conflicts. + // Equivalent to using '--force-apply' when calling kluctl. + // +kubebuilder:default:=false + // +optional + ForceApply bool `json:"forceApply,omitempty"` + + // ReplaceOnError instructs kluctl to replace resources on error. + // Equivalent to using '--replace-on-error' when calling kluctl. + // +kubebuilder:default:=false + // +optional + ReplaceOnError bool `json:"replaceOnError,omitempty"` + + // ForceReplaceOnError instructs kluctl to force-replace resources in case a normal replace fails. + // Equivalent to using '--force-replace-on-error' when calling kluctl. + // +kubebuilder:default:=false + // +optional + ForceReplaceOnError bool `json:"forceReplaceOnError,omitempty"` + + // ForceReplaceOnError instructs kluctl to abort deployments immediately when something fails. + // Equivalent to using '--abort-on-error' when calling kluctl. + // +kubebuilder:default:=false + // +optional + AbortOnError bool `json:"abortOnError,omitempty"` + + // IncludeTags instructs kluctl to only include deployments with given tags. + // Equivalent to using '--include-tag' when calling kluctl. + // +optional + IncludeTags []string `json:"includeTags,omitempty"` + + // ExcludeTags instructs kluctl to exclude deployments with given tags. + // Equivalent to using '--exclude-tag' when calling kluctl. + // +optional + ExcludeTags []string `json:"excludeTags,omitempty"` + + // IncludeDeploymentDirs instructs kluctl to only include deployments with the given dir. + // Equivalent to using '--include-deployment-dir' when calling kluctl. + // +optional + IncludeDeploymentDirs []string `json:"includeDeploymentDirs,omitempty"` + + // ExcludeDeploymentDirs instructs kluctl to exclude deployments with the given dir. + // Equivalent to using '--exclude-deployment-dir' when calling kluctl. + // +optional + ExcludeDeploymentDirs []string `json:"excludeDeploymentDirs,omitempty"` + + // DeployMode specifies what deploy mode should be used. + // The options 'full-deploy' and 'poke-images' are supported. + // With the 'poke-images' option, only images are patched into the target without performing a full deployment. + // +kubebuilder:default:=full-deploy + // +kubebuilder:validation:Enum=full-deploy;poke-images + // +optional + DeployMode string `json:"deployMode,omitempty"` + + // Validate enables validation after deploying + // +kubebuilder:default:=true + // +optional + Validate bool `json:"validate"` + + // Prune enables pruning after deploying. + // +kubebuilder:default:=false + // +optional + Prune bool `json:"prune,omitempty"` + + // Delete enables deletion of the specified target when the KluctlDeployment object gets deleted. + // +kubebuilder:default:=false + // +optional + Delete bool `json:"delete,omitempty"` +} + +// GetRetryInterval returns the retry interval +func (in KluctlDeploymentSpec) GetRetryInterval() time.Duration { + if in.RetryInterval != nil { + return in.RetryInterval.Duration + } + return in.Interval.Duration +} + +type ProjectSource struct { + // Url specifies the Git url where the project source is located + // +required + URL types.GitUrl `json:"url"` + + // Ref specifies the branch, tag or commit that should be used. If omitted, the default branch of the repo is used. + // +optional + Ref *GitRef `json:"ref,omitempty"` + + // Path specifies the sub-directory to be used as project directory + // +optional + Path string `json:"path,omitempty"` + + // SecretRef specifies the Secret containing authentication credentials for + // the git repository. + // For HTTPS repositories the Secret must contain 'username' and 'password' + // fields. + // For SSH repositories the Secret must contain 'identity' + // and 'known_hosts' fields. + // +optional + SecretRef *LocalObjectReference `json:"secretRef,omitempty"` +} + +// Decryption defines how decryption is handled for Kubernetes manifests. +type Decryption struct { + // Provider is the name of the decryption engine. + // +kubebuilder:validation:Enum=sops + // +required + Provider string `json:"provider"` + + // The secret name containing the private OpenPGP keys used for decryption. + // +optional + SecretRef *LocalObjectReference `json:"secretRef,omitempty"` + + // ServiceAccount specifies the service account used to authenticate against cloud providers. + // This is currently only usable for AWS KMS keys. The specified service account will be used to authenticate to AWS + // by signing a token in an IRSA compliant way. + // +optional + ServiceAccount string `json:"serviceAccount,omitempty"` +} + +type HelmCredentials struct { + // SecretRef holds the name of a secret that contains the Helm credentials. + // The secret must either contain the fields `credentialsId` which refers to the credentialsId + // found in https://kluctl.io/docs/kluctl/reference/deployments/helm/#private-chart-repositories or an `url` used + // to match the credentials found in Kluctl projects helm-chart.yaml files. + // The secret can either container basic authentication credentials via `username` and `password` or + // TLS authentication via `certFile` and `keyFile`. `caFile` can be specified to override the CA to use while + // contacting the repository. + // The secret can also contain `insecureSkipTlsVerify: "true"`, which will disable TLS verification. + // `passCredentialsAll: "true"` can be specified to make the controller pass credentials to all requests, even if + // the hostname changes in-between. + // +required + SecretRef LocalObjectReference `json:"secretRef,omitempty"` +} + +// KubeConfig references a Kubernetes secret that contains a kubeconfig file. +type KubeConfig struct { + // SecretRef holds the name of a secret that contains a key with + // the kubeconfig file as the value. If no key is set, the key will default + // to 'value'. The secret must be in the same namespace as + // the Kustomization. + // It is recommended that the kubeconfig is self-contained, and the secret + // is regularly updated if credentials such as a cloud-access-token expire. + // Cloud specific `cmd-path` auth helpers will not function without adding + // binaries and credentials to the Pod that is responsible for reconciling + // the KluctlDeployment. + // +required + SecretRef SecretKeyReference `json:"secretRef,omitempty"` +} + +// KluctlDeploymentStatus defines the observed state of KluctlDeployment +type KluctlDeploymentStatus struct { + // LastHandledReconcileAt holds the value of the most recent + // reconcile request value, so a change of the annotation value + // can be detected. + // +optional + LastHandledReconcileAt string `json:"lastHandledReconcileAt,omitempty"` + + // +optional + LastHandledDeployAt string `json:"LastHandledDeployAt,omitempty"` + + // ObservedGeneration is the last reconciled generation. + // +optional + ObservedGeneration int64 `json:"observedGeneration,omitempty"` + + // +optional + Conditions []metav1.Condition `json:"conditions,omitempty"` + + // +optional + ProjectKey *result.ProjectKey `json:"projectKey,omitempty"` + + // +optional + TargetKey *result.TargetKey `json:"targetKey,omitempty"` + + // +optional + LastObjectsHash string `json:"lastObjectsHash,omitempty"` + + // LastDeployResult is the result of the last deploy command + // +optional + LastDeployResult *result.CommandResultSummary `json:"lastDeployResult,omitempty"` + + // LastDeployResult is the result of the last prune command + // +optional + LastPruneResult *result.CommandResultSummary `json:"lastPruneResult,omitempty"` + + // LastValidateResult is the result of the last validate command + // +optional + LastValidateResult *result.ValidateResult `json:"lastValidateResult,omitempty"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +//+kubebuilder:printcolumn:name="DryRun",type="boolean",JSONPath=".spec.dryRun",description="" +//+kubebuilder:printcolumn:name="Deployed",type="date",JSONPath=".status.lastDeployResult.command.endTime",description="" +//+kubebuilder:printcolumn:name="Pruned",type="date",JSONPath=".status.lastPruneResult.command.endTime",description="" +//+kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].status",description="" +//+kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].message",description="" +//+kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="" + +// KluctlDeployment is the Schema for the kluctldeployments API +type KluctlDeployment struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec KluctlDeploymentSpec `json:"spec,omitempty"` + Status KluctlDeploymentStatus `json:"status,omitempty"` +} + +// GetConditions returns the status conditions of the object. +func (in *KluctlDeployment) GetConditions() []metav1.Condition { + return in.Status.Conditions +} + +// SetConditions sets the status conditions on the object. +func (in *KluctlDeployment) SetConditions(conditions []metav1.Condition) { + in.Status.Conditions = conditions +} + +//+kubebuilder:object:root=true + +// KluctlDeploymentList contains a list of KluctlDeployment +type KluctlDeploymentList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []KluctlDeployment `json:"items"` +} + +func init() { + SchemeBuilder.Register(&KluctlDeployment{}, &KluctlDeploymentList{}) +} diff --git a/api/v1beta1/util_types.go b/api/v1beta1/util_types.go new file mode 100644 index 000000000..efe1e987a --- /dev/null +++ b/api/v1beta1/util_types.go @@ -0,0 +1,60 @@ +package v1beta1 + +import ( + "fmt" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type LocalObjectReference struct { + // Name of the referent. + // +required + Name string `json:"name"` +} + +// SecretKeyReference contains enough information to locate the referenced Kubernetes Secret object in the same +// namespace. Optionally a key can be specified. +// Use this type instead of core/v1 SecretKeySelector when the Key is optional and the Optional field is not +// applicable. +type SecretKeyReference struct { + // Name of the Secret. + // +required + Name string `json:"name"` + + // Key in the Secret, when not specified an implementation-specific default key is used. + // +optional + Key string `json:"key,omitempty"` +} + +// +kubebuilder:validation:Type=string +// +kubebuilder:validation:Pattern="^(([0-9]+(\\.[0-9]+)?(ms|s|m|h))+)|never$" +type SafeDuration struct { + metav1.Duration +} + +type GitRef struct { + // Branch to filter for. Can also be a regex. + // +optional + Branch string `json:"branch,omitempty"` + + // Branch to filter for. Can also be a regex. + // +optional + Tag string `json:"tag,omitempty"` + + // TODO + // Commit SHA to check out, takes precedence over all reference fields. + // +optional + // Commit string `json:"commit,omitempty"` +} + +func (r *GitRef) String() string { + if r == nil { + return "" + } + if r.Tag != "" { + return fmt.Sprintf("refs/tags/%s", r.Tag) + } else if r.Branch != "" { + return fmt.Sprintf("refs/heads/%s", r.Branch) + } else { + return "" + } +} diff --git a/cmd/kluctl/commands/cmd_controller.go b/cmd/kluctl/commands/cmd_controller.go new file mode 100644 index 000000000..655a5071d --- /dev/null +++ b/cmd/kluctl/commands/cmd_controller.go @@ -0,0 +1,140 @@ +package commands + +import ( + "context" + kluctlv1 "github.com/kluctl/kluctl/v2/api/v1beta1" + "github.com/kluctl/kluctl/v2/pkg/controllers" + ssh_pool "github.com/kluctl/kluctl/v2/pkg/git/ssh-pool" + "github.com/kluctl/kluctl/v2/pkg/utils/flux_utils" + "github.com/kluctl/kluctl/v2/pkg/utils/flux_utils/metrics" + "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/client-go/kubernetes" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "os" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/healthz" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + crtlmetrics "sigs.k8s.io/controller-runtime/pkg/metrics" +) + +var ( + setupLog = ctrl.Log.WithName("setup") + metricsRecorder = metrics.NewRecorder() +) + +func init() { + crtlmetrics.Registry.MustRegister(metricsRecorder.Collectors()...) +} + +const controllerName = "kluctl-controller" + +type controllerCmd struct { + scheme *runtime.Scheme + + MetricsBindAddress string `group:"controller" help:"The address the metric endpoint binds to." default:":8080"` + HealthProbeBindAddress string `group:"controller" help:"The address the probe endpoint binds to." default:":8081"` + LeaderElect bool `group:"controller" help:"Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager."` + + DefaultServiceAccount string `group:"controller" help:"Default service account used for impersonation."` + DryRun bool `group:"controller" help:"Run all deployments in dryRun=true mode."` +} + +func (cmd *controllerCmd) Help() string { + return `This command will run the Kluctl Controller. This is usually meant to be run inside a cluster and not from your local machine. +` +} + +func (cmd *controllerCmd) initScheme() { + cmd.scheme = runtime.NewScheme() + scheme := cmd.scheme + + utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + + utilruntime.Must(kluctlv1.AddToScheme(scheme)) + + //+kubebuilder:scaffold:scheme +} + +func (cmd *controllerCmd) Run(ctx context.Context) error { + cmd.initScheme() + + opts := zap.Options{ + Development: true, + } + ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) + + restConfig := flux_utils.GetConfigOrDie(flux_utils.Options{}) + clientSet, err := kubernetes.NewForConfig(restConfig) + if err != nil { + setupLog.Error(err, "unable to start manager") + os.Exit(1) + } + + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ + Scheme: cmd.scheme, + MetricsBindAddress: cmd.MetricsBindAddress, + Port: 9443, + HealthProbeBindAddress: cmd.HealthProbeBindAddress, + LeaderElection: cmd.LeaderElect, + LeaderElectionID: "5ab5d0f9.kluctl.io", + // LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily + // when the Manager ends. This requires the binary to immediately end when the + // Manager is stopped, otherwise, this setting is unsafe. Setting this significantly + // speeds up voluntary leader transitions as the new leader don't have to wait + // LeaseDuration time first. + // + // In the default scaffold provided, the program ends immediately after + // the manager stops, so would be fine to enable this option. However, + // if you are doing or is intended to do any operation such as perform cleanups + // after the manager stops then its usage might be unsafe. + // LeaderElectionReleaseOnCancel: true, + }) + if err != nil { + setupLog.Error(err, "unable to start manager") + os.Exit(1) + } + + eventRecorder := mgr.GetEventRecorderFor(controllerName) + + sshPool := &ssh_pool.SshPool{} + + r := controllers.KluctlDeploymentReconciler{ + ControllerName: controllerName, + DefaultServiceAccount: cmd.DefaultServiceAccount, + DryRun: cmd.DryRun, + RestConfig: restConfig, + Client: mgr.GetClient(), + ClientSet: clientSet, + Scheme: mgr.GetScheme(), + EventRecorder: eventRecorder, + MetricsRecorder: metricsRecorder, + SshPool: sshPool, + } + + if err = r.SetupWithManager(mgr, controllers.KluctlDeploymentReconcilerOpts{ + HTTPRetry: 9, + }); err != nil { + setupLog.Error(err, "unable to create controller", "controller", kluctlv1.KluctlDeploymentKind) + os.Exit(1) + } + + //+kubebuilder:scaffold:builder + + if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up health check") + os.Exit(1) + } + if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up ready check") + os.Exit(1) + } + + setupLog.Info("starting manager") + if err := mgr.Start(ctx); err != nil { + setupLog.Error(err, "problem running manager") + os.Exit(1) + } + + return nil +} diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index 200d0b9d7..51b37957c 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -25,6 +25,7 @@ import ( "os" "path/filepath" "runtime/pprof" + ctrl "sigs.k8s.io/controller-runtime" "strings" "time" @@ -66,6 +67,7 @@ type cli struct { Render renderCmd `cmd:"" help:"Renders all resources and configuration files"` Seal sealCmd `cmd:"" help:"Seal secrets based on target's sealingConfig"` Validate validateCmd `cmd:"" help:"Validates the already deployed deployment"` + Controller controllerCmd `cmd:"" help:"Run the Kluctl controller"` Version versionCmd `cmd:"" help:"Print kluctl version"` } @@ -77,6 +79,7 @@ var flagGroups = []groupInfo{ {group: "inclusion", title: "Inclusion/Exclusion arguments:", description: "Control inclusion/exclusion."}, {group: "misc", title: "Misc arguments:", description: "Command specific arguments."}, {group: "results", title: "Command Results:", description: "Configure how command results are stored."}, + {group: "controller", title: "Controller:", description: "Controller arguments."}, } var origStderr = os.Stderr @@ -219,7 +222,7 @@ func initViper(ctx context.Context) { func Main() { colorable.EnableColorsStdout(nil) - ctx := context.Background() + ctx := ctrl.SetupSignalHandler() ctx = initStatusHandler(ctx, false, true) redirectLogsAndStderr(func() context.Context { diff --git a/config/crd/bases/gitops.kluctl.io_kluctldeployments.yaml b/config/crd/bases/gitops.kluctl.io_kluctldeployments.yaml new file mode 100644 index 000000000..3e81ebc5e --- /dev/null +++ b/config/crd/bases/gitops.kluctl.io_kluctldeployments.yaml @@ -0,0 +1,1015 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.3 + creationTimestamp: null + name: kluctldeployments.gitops.kluctl.io +spec: + group: gitops.kluctl.io + names: + kind: KluctlDeployment + listKind: KluctlDeploymentList + plural: kluctldeployments + singular: kluctldeployment + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.dryRun + name: DryRun + type: boolean + - jsonPath: .status.lastDeployResult.command.endTime + name: Deployed + type: date + - jsonPath: .status.lastPruneResult.command.endTime + name: Pruned + type: date + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].message + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: KluctlDeployment is the Schema for the kluctldeployments API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + abortOnError: + default: false + description: ForceReplaceOnError instructs kluctl to abort deployments + immediately when something fails. Equivalent to using '--abort-on-error' + when calling kluctl. + type: boolean + args: + description: Args specifies dynamic target args. + type: object + x-kubernetes-preserve-unknown-fields: true + context: + description: If specified, overrides the context to be used. This + will effectively make kluctl ignore the context specified in the + target. + type: string + decryption: + description: Decrypt Kubernetes secrets before applying them on the + cluster. + properties: + provider: + description: Provider is the name of the decryption engine. + enum: + - sops + type: string + secretRef: + description: The secret name containing the private OpenPGP keys + used for decryption. + properties: + name: + description: Name of the referent. + type: string + required: + - name + type: object + serviceAccount: + description: ServiceAccount specifies the service account used + to authenticate against cloud providers. This is currently only + usable for AWS KMS keys. The specified service account will + be used to authenticate to AWS by signing a token in an IRSA + compliant way. + type: string + required: + - provider + type: object + delete: + default: false + description: Delete enables deletion of the specified target when + the KluctlDeployment object gets deleted. + type: boolean + deployInterval: + description: DeployInterval specifies the interval at which to deploy + the KluctlDeployment, even in cases the rendered result does not + change. + pattern: ^(([0-9]+(\.[0-9]+)?(ms|s|m|h))+)|never$ + type: string + deployMode: + default: full-deploy + description: DeployMode specifies what deploy mode should be used. + The options 'full-deploy' and 'poke-images' are supported. With + the 'poke-images' option, only images are patched into the target + without performing a full deployment. + enum: + - full-deploy + - poke-images + type: string + dryRun: + default: false + description: DryRun instructs kluctl to run everything in dry-run + mode. Equivalent to using '--dry-run' when calling kluctl. + type: boolean + excludeDeploymentDirs: + description: ExcludeDeploymentDirs instructs kluctl to exclude deployments + with the given dir. Equivalent to using '--exclude-deployment-dir' + when calling kluctl. + items: + type: string + type: array + excludeTags: + description: ExcludeTags instructs kluctl to exclude deployments with + given tags. Equivalent to using '--exclude-tag' when calling kluctl. + items: + type: string + type: array + forceApply: + default: false + description: ForceApply instructs kluctl to force-apply in case of + SSA conflicts. Equivalent to using '--force-apply' when calling + kluctl. + type: boolean + forceReplaceOnError: + default: false + description: ForceReplaceOnError instructs kluctl to force-replace + resources in case a normal replace fails. Equivalent to using '--force-replace-on-error' + when calling kluctl. + type: boolean + helmCredentials: + description: HelmCredentials is a list of Helm credentials used when + non pre-pulled Helm Charts are used inside a Kluctl deployment. + items: + properties: + secretRef: + description: 'SecretRef holds the name of a secret that contains + the Helm credentials. The secret must either contain the fields + `credentialsId` which refers to the credentialsId found in + https://kluctl.io/docs/kluctl/reference/deployments/helm/#private-chart-repositories + or an `url` used to match the credentials found in Kluctl + projects helm-chart.yaml files. The secret can either container + basic authentication credentials via `username` and `password` + or TLS authentication via `certFile` and `keyFile`. `caFile` + can be specified to override the CA to use while contacting + the repository. The secret can also contain `insecureSkipTlsVerify: + "true"`, which will disable TLS verification. `passCredentialsAll: + "true"` can be specified to make the controller pass credentials + to all requests, even if the hostname changes in-between.' + properties: + name: + description: Name of the referent. + type: string + required: + - name + type: object + type: object + type: array + images: + description: Images contains a list of fixed image overrides. Equivalent + to using '--fixed-images-file' when calling kluctl. + items: + properties: + container: + type: string + deployTags: + items: + type: string + type: array + deployedImage: + type: string + deployment: + type: string + deploymentDir: + type: string + image: + type: string + namespace: + type: string + object: + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + required: + - kind + - name + type: object + resultImage: + type: string + required: + - image + - resultImage + type: object + type: array + includeDeploymentDirs: + description: IncludeDeploymentDirs instructs kluctl to only include + deployments with the given dir. Equivalent to using '--include-deployment-dir' + when calling kluctl. + items: + type: string + type: array + includeTags: + description: IncludeTags instructs kluctl to only include deployments + with given tags. Equivalent to using '--include-tag' when calling + kluctl. + items: + type: string + type: array + interval: + description: The interval at which to reconcile the KluctlDeployment. + Reconciliation means that the deployment is fully rendered and only + deployed when the result changes compared to the last deployment. + To override this behavior, set the DeployInterval value. + pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ + type: string + kubeConfig: + description: The KubeConfig for deploying to the target cluster. Specifies + the kubeconfig to be used when invoking kluctl. Contexts in this + kubeconfig must match the context found in the kluctl target. As + an alternative, specify the context to be used via 'context' + properties: + secretRef: + description: SecretRef holds the name of a secret that contains + a key with the kubeconfig file as the value. If no key is set, + the key will default to 'value'. The secret must be in the same + namespace as the Kustomization. It is recommended that the kubeconfig + is self-contained, and the secret is regularly updated if credentials + such as a cloud-access-token expire. Cloud specific `cmd-path` + auth helpers will not function without adding binaries and credentials + to the Pod that is responsible for reconciling the KluctlDeployment. + properties: + key: + description: Key in the Secret, when not specified an implementation-specific + default key is used. + type: string + name: + description: Name of the Secret. + type: string + required: + - name + type: object + type: object + noWait: + default: false + description: NoWait instructs kluctl to not wait for any resources + to become ready, including hooks. Equivalent to using '--no-wait' + when calling kluctl. + type: boolean + prune: + default: false + description: Prune enables pruning after deploying. + type: boolean + replaceOnError: + default: false + description: ReplaceOnError instructs kluctl to replace resources + on error. Equivalent to using '--replace-on-error' when calling + kluctl. + type: boolean + retryInterval: + description: The interval at which to retry a previously failed reconciliation. + When not specified, the controller uses the Interval value to retry + failures. + pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ + type: string + serviceAccountName: + description: The name of the Kubernetes service account to use while + deploying. If not specified, the default service account is used. + type: string + source: + description: Specifies the project source location + properties: + path: + description: Path specifies the sub-directory to be used as project + directory + type: string + ref: + description: Ref specifies the branch, tag or commit that should + be used. If omitted, the default branch of the repo is used. + properties: + branch: + description: Branch to filter for. Can also be a regex. + type: string + tag: + description: Branch to filter for. Can also be a regex. + type: string + type: object + secretRef: + description: SecretRef specifies the Secret containing authentication + credentials for the git repository. For HTTPS repositories the + Secret must contain 'username' and 'password' fields. For SSH + repositories the Secret must contain 'identity' and 'known_hosts' + fields. + properties: + name: + description: Name of the referent. + type: string + required: + - name + type: object + url: + description: Url specifies the Git url where the project source + is located + type: string + required: + - url + type: object + suspend: + description: This flag tells the controller to suspend subsequent + kluctl executions, it does not apply to already started executions. + Defaults to false. + type: boolean + target: + description: Target specifies the kluctl target to deploy. If not + specified, an empty target is used that has no name and no context. + Use 'TargetName' and 'Context' to specify the name and context in + that case. + maxLength: 63 + minLength: 1 + type: string + targetNameOverride: + description: TargetNameOverride sets or overrides the target name. + This is especially useful when deployment without a target. + maxLength: 63 + minLength: 1 + type: string + timeout: + description: Timeout for all operations. Defaults to 'Interval' duration. + pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ + type: string + validate: + default: true + description: Validate enables validation after deploying + type: boolean + validateInterval: + description: ValidateInterval specifies the interval at which to validate + the KluctlDeployment. Validation is performed the same way as with + 'kluctl validate -t '. Defaults to the same value as specified + in Interval. Validate is also performed whenever a deployment is + performed, independent of the value of ValidateInterval + pattern: ^(([0-9]+(\.[0-9]+)?(ms|s|m|h))+)|never$ + type: string + required: + - interval + - source + type: object + status: + description: KluctlDeploymentStatus defines the observed state of KluctlDeployment + properties: + LastHandledDeployAt: + type: string + conditions: + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + lastDeployResult: + description: LastDeployResult is the result of the last deploy command + properties: + appliedHookObjects: + type: integer + appliedObjects: + type: integer + changedObjects: + type: integer + clusterInfo: + properties: + clusterId: + type: string + required: + - clusterId + type: object + commandInfo: + properties: + abortOnError: + type: boolean + args: + type: object + x-kubernetes-preserve-unknown-fields: true + command: + type: string + contextOverride: + type: string + dryRun: + type: boolean + endTime: + format: date-time + type: string + excludeDeploymentDirs: + items: + type: string + type: array + excludeTags: + items: + type: string + type: array + forceApply: + type: boolean + forceReplaceOnError: + type: boolean + images: + items: + properties: + container: + type: string + deployTags: + items: + type: string + type: array + deployedImage: + type: string + deployment: + type: string + deploymentDir: + type: string + image: + type: string + namespace: + type: string + object: + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + required: + - kind + - name + type: object + resultImage: + type: string + required: + - image + - resultImage + type: object + type: array + includeDeploymentDirs: + items: + type: string + type: array + includeTags: + items: + type: string + type: array + initiator: + type: string + kluctlDeployment: + properties: + gitRef: + type: string + gitUrl: + type: string + name: + type: string + namespace: + type: string + required: + - gitRef + - gitUrl + - name + - namespace + type: object + noWait: + type: boolean + replaceOnError: + type: boolean + startTime: + format: date-time + type: string + target: + type: string + targetNameOverride: + type: string + required: + - endTime + - initiator + - startTime + type: object + deletedObjects: + type: integer + errors: + type: integer + gitInfo: + properties: + commit: + type: string + dirty: + type: boolean + ref: + type: string + subDir: + type: string + url: + type: string + required: + - commit + - dirty + - ref + - subDir + - url + type: object + id: + type: string + newObjects: + type: integer + orphanObjects: + type: integer + project: + properties: + normalizedGitUrl: + type: string + subDir: + type: string + type: object + remoteObjects: + type: integer + renderedObjects: + type: integer + target: + properties: + clusterId: + type: string + discriminator: + type: string + targetName: + type: string + required: + - clusterId + type: object + totalChanges: + type: integer + warnings: + type: integer + required: + - appliedHookObjects + - appliedObjects + - changedObjects + - commandInfo + - deletedObjects + - errors + - id + - newObjects + - orphanObjects + - project + - remoteObjects + - renderedObjects + - target + - totalChanges + - warnings + type: object + lastHandledReconcileAt: + description: LastHandledReconcileAt holds the value of the most recent + reconcile request value, so a change of the annotation value can + be detected. + type: string + lastObjectsHash: + type: string + lastPruneResult: + description: LastDeployResult is the result of the last prune command + properties: + appliedHookObjects: + type: integer + appliedObjects: + type: integer + changedObjects: + type: integer + clusterInfo: + properties: + clusterId: + type: string + required: + - clusterId + type: object + commandInfo: + properties: + abortOnError: + type: boolean + args: + type: object + x-kubernetes-preserve-unknown-fields: true + command: + type: string + contextOverride: + type: string + dryRun: + type: boolean + endTime: + format: date-time + type: string + excludeDeploymentDirs: + items: + type: string + type: array + excludeTags: + items: + type: string + type: array + forceApply: + type: boolean + forceReplaceOnError: + type: boolean + images: + items: + properties: + container: + type: string + deployTags: + items: + type: string + type: array + deployedImage: + type: string + deployment: + type: string + deploymentDir: + type: string + image: + type: string + namespace: + type: string + object: + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + required: + - kind + - name + type: object + resultImage: + type: string + required: + - image + - resultImage + type: object + type: array + includeDeploymentDirs: + items: + type: string + type: array + includeTags: + items: + type: string + type: array + initiator: + type: string + kluctlDeployment: + properties: + gitRef: + type: string + gitUrl: + type: string + name: + type: string + namespace: + type: string + required: + - gitRef + - gitUrl + - name + - namespace + type: object + noWait: + type: boolean + replaceOnError: + type: boolean + startTime: + format: date-time + type: string + target: + type: string + targetNameOverride: + type: string + required: + - endTime + - initiator + - startTime + type: object + deletedObjects: + type: integer + errors: + type: integer + gitInfo: + properties: + commit: + type: string + dirty: + type: boolean + ref: + type: string + subDir: + type: string + url: + type: string + required: + - commit + - dirty + - ref + - subDir + - url + type: object + id: + type: string + newObjects: + type: integer + orphanObjects: + type: integer + project: + properties: + normalizedGitUrl: + type: string + subDir: + type: string + type: object + remoteObjects: + type: integer + renderedObjects: + type: integer + target: + properties: + clusterId: + type: string + discriminator: + type: string + targetName: + type: string + required: + - clusterId + type: object + totalChanges: + type: integer + warnings: + type: integer + required: + - appliedHookObjects + - appliedObjects + - changedObjects + - commandInfo + - deletedObjects + - errors + - id + - newObjects + - orphanObjects + - project + - remoteObjects + - renderedObjects + - target + - totalChanges + - warnings + type: object + lastValidateResult: + description: LastValidateResult is the result of the last validate + command + properties: + drift: + items: + properties: + changes: + items: + properties: + jsonPath: + type: string + newValue: + x-kubernetes-preserve-unknown-fields: true + oldValue: + x-kubernetes-preserve-unknown-fields: true + type: + type: string + unifiedDiff: + type: string + required: + - jsonPath + - type + type: object + type: array + ref: + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + required: + - kind + - name + type: object + required: + - ref + type: object + type: array + errors: + items: + properties: + message: + type: string + ref: + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + required: + - kind + - name + type: object + required: + - message + - ref + type: object + type: array + id: + type: string + ready: + type: boolean + results: + items: + properties: + annotation: + type: string + message: + type: string + ref: + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + required: + - kind + - name + type: object + required: + - annotation + - message + - ref + type: object + type: array + warnings: + items: + properties: + message: + type: string + ref: + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + required: + - kind + - name + type: object + required: + - message + - ref + type: object + type: array + required: + - id + - ready + type: object + observedGeneration: + description: ObservedGeneration is the last reconciled generation. + format: int64 + type: integer + projectKey: + properties: + normalizedGitUrl: + type: string + subDir: + type: string + type: object + targetKey: + properties: + clusterId: + type: string + discriminator: + type: string + targetName: + type: string + required: + - clusterId + type: object + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml new file mode 100644 index 000000000..d5f5b903f --- /dev/null +++ b/config/crd/kustomization.yaml @@ -0,0 +1,21 @@ +# This kustomization.yaml is not intended to be run by itself, +# since it depends on service name and namespace that are out of this kustomize package. +# It should be run by config/default +resources: +- bases/gitops.kluctl.io_kluctldeployments.yaml +#+kubebuilder:scaffold:crdkustomizeresource + +patchesStrategicMerge: +# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. +# patches here are for enabling the conversion webhook for each CRD +#- patches/webhook_in_kluctldeployments.yaml +#+kubebuilder:scaffold:crdkustomizewebhookpatch + +# [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. +# patches here are for enabling the CA injection for each CRD +#- patches/cainjection_in_kluctldeployments.yaml +#+kubebuilder:scaffold:crdkustomizecainjectionpatch + +# the following config is for teaching kustomize how to do kustomization for CRDs. +configurations: +- kustomizeconfig.yaml diff --git a/config/crd/kustomizeconfig.yaml b/config/crd/kustomizeconfig.yaml new file mode 100644 index 000000000..ec5c150a9 --- /dev/null +++ b/config/crd/kustomizeconfig.yaml @@ -0,0 +1,19 @@ +# This file is for teaching kustomize how to substitute name and namespace reference in CRD +nameReference: +- kind: Service + version: v1 + fieldSpecs: + - kind: CustomResourceDefinition + version: v1 + group: apiextensions.k8s.io + path: spec/conversion/webhook/clientConfig/service/name + +namespace: +- kind: CustomResourceDefinition + version: v1 + group: apiextensions.k8s.io + path: spec/conversion/webhook/clientConfig/service/namespace + create: false + +varReference: +- path: metadata/annotations diff --git a/config/crd/patches/cainjection_in_kluctldeployments.yaml b/config/crd/patches/cainjection_in_kluctldeployments.yaml new file mode 100644 index 000000000..4adca8433 --- /dev/null +++ b/config/crd/patches/cainjection_in_kluctldeployments.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: CERTIFICATE_NAMESPACE/CERTIFICATE_NAME + name: kluctldeployments.gitops.kluctl.io diff --git a/config/crd/patches/webhook_in_kluctldeployments.yaml b/config/crd/patches/webhook_in_kluctldeployments.yaml new file mode 100644 index 000000000..fa1689a1d --- /dev/null +++ b/config/crd/patches/webhook_in_kluctldeployments.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: kluctldeployments.gitops.kluctl.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/default/kustomization.yaml b/config/default/kustomization.yaml new file mode 100644 index 000000000..fc2356a99 --- /dev/null +++ b/config/default/kustomization.yaml @@ -0,0 +1,137 @@ +# Adds namespace to all resources. +namespace: controller-system + +# Value of this field is prepended to the +# names of all resources, e.g. a deployment named +# "wordpress" becomes "alices-wordpress". +# Note that it should also match with the prefix (text before '-') of the namespace +# field above. +namePrefix: controller- + +# Labels to add to all resources and selectors. +#labels: +#- includeSelectors: true +# pairs: +# someName: someValue + +resources: +- ../crd +- ../rbac +- ../manager +# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in +# crd/kustomization.yaml +#- ../webhook +# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. +#- ../certmanager +# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. +#- ../prometheus + + +# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in +# crd/kustomization.yaml +#- manager_webhook_patch.yaml + +# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. +# Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. +# 'CERTMANAGER' needs to be enabled to use ca injection +#- webhookcainjection_patch.yaml + +# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. +# Uncomment the following replacements to add the cert-manager CA injection annotations +#replacements: +# - source: # Add cert-manager annotation to ValidatingWebhookConfiguration, MutatingWebhookConfiguration and CRDs +# kind: Certificate +# group: cert-manager.io +# version: v1 +# name: serving-cert # this name should match the one in certificate.yaml +# fieldPath: .metadata.namespace # namespace of the certificate CR +# targets: +# - select: +# kind: ValidatingWebhookConfiguration +# fieldPaths: +# - .metadata.annotations.[cert-manager.io/inject-ca-from] +# options: +# delimiter: '/' +# index: 0 +# create: true +# - select: +# kind: MutatingWebhookConfiguration +# fieldPaths: +# - .metadata.annotations.[cert-manager.io/inject-ca-from] +# options: +# delimiter: '/' +# index: 0 +# create: true +# - select: +# kind: CustomResourceDefinition +# fieldPaths: +# - .metadata.annotations.[cert-manager.io/inject-ca-from] +# options: +# delimiter: '/' +# index: 0 +# create: true +# - source: +# kind: Certificate +# group: cert-manager.io +# version: v1 +# name: serving-cert # this name should match the one in certificate.yaml +# fieldPath: .metadata.name +# targets: +# - select: +# kind: ValidatingWebhookConfiguration +# fieldPaths: +# - .metadata.annotations.[cert-manager.io/inject-ca-from] +# options: +# delimiter: '/' +# index: 1 +# create: true +# - select: +# kind: MutatingWebhookConfiguration +# fieldPaths: +# - .metadata.annotations.[cert-manager.io/inject-ca-from] +# options: +# delimiter: '/' +# index: 1 +# create: true +# - select: +# kind: CustomResourceDefinition +# fieldPaths: +# - .metadata.annotations.[cert-manager.io/inject-ca-from] +# options: +# delimiter: '/' +# index: 1 +# create: true +# - source: # Add cert-manager annotation to the webhook Service +# kind: Service +# version: v1 +# name: webhook-service +# fieldPath: .metadata.name # namespace of the service +# targets: +# - select: +# kind: Certificate +# group: cert-manager.io +# version: v1 +# fieldPaths: +# - .spec.dnsNames.0 +# - .spec.dnsNames.1 +# options: +# delimiter: '.' +# index: 0 +# create: true +# - source: +# kind: Service +# version: v1 +# name: webhook-service +# fieldPath: .metadata.namespace # namespace of the service +# targets: +# - select: +# kind: Certificate +# group: cert-manager.io +# version: v1 +# fieldPaths: +# - .spec.dnsNames.0 +# - .spec.dnsNames.1 +# options: +# delimiter: '.' +# index: 1 +# create: true diff --git a/config/default/manager_config_patch.yaml b/config/default/manager_config_patch.yaml new file mode 100644 index 000000000..f6f589169 --- /dev/null +++ b/config/default/manager_config_patch.yaml @@ -0,0 +1,10 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: controller-manager + namespace: system +spec: + template: + spec: + containers: + - name: manager diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml new file mode 100644 index 000000000..5c5f0b84c --- /dev/null +++ b/config/manager/kustomization.yaml @@ -0,0 +1,2 @@ +resources: +- manager.yaml diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml new file mode 100644 index 000000000..d0a98b913 --- /dev/null +++ b/config/manager/manager.yaml @@ -0,0 +1,102 @@ +apiVersion: v1 +kind: Namespace +metadata: + labels: + control-plane: controller-manager + app.kubernetes.io/name: namespace + app.kubernetes.io/instance: system + app.kubernetes.io/component: manager + app.kubernetes.io/created-by: controller + app.kubernetes.io/part-of: controller + app.kubernetes.io/managed-by: kustomize + name: system +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: controller-manager + namespace: system + labels: + control-plane: controller-manager + app.kubernetes.io/name: deployment + app.kubernetes.io/instance: controller-manager + app.kubernetes.io/component: manager + app.kubernetes.io/created-by: controller + app.kubernetes.io/part-of: controller + app.kubernetes.io/managed-by: kustomize +spec: + selector: + matchLabels: + control-plane: controller-manager + replicas: 1 + template: + metadata: + annotations: + kubectl.kubernetes.io/default-container: manager + labels: + control-plane: controller-manager + spec: + # TODO(user): Uncomment the following code to configure the nodeAffinity expression + # according to the platforms which are supported by your solution. + # It is considered best practice to support multiple architectures. You can + # build your manager image using the makefile target docker-buildx. + # affinity: + # nodeAffinity: + # requiredDuringSchedulingIgnoredDuringExecution: + # nodeSelectorTerms: + # - matchExpressions: + # - key: kubernetes.io/arch + # operator: In + # values: + # - amd64 + # - arm64 + # - ppc64le + # - s390x + # - key: kubernetes.io/os + # operator: In + # values: + # - linux + securityContext: + runAsNonRoot: true + # TODO(user): For common cases that do not require escalating privileges + # it is recommended to ensure that all your Pods/Containers are restrictive. + # More info: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted + # Please uncomment the following code if your project does NOT have to work on old Kubernetes + # versions < 1.19 or on vendors versions which do NOT support this field by default (i.e. Openshift < 4.11 ). + # seccompProfile: + # type: RuntimeDefault + containers: + - command: + - /manager + args: + - --leader-elect + image: controller:latest + name: manager + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - "ALL" + livenessProbe: + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 15 + periodSeconds: 20 + readinessProbe: + httpGet: + path: /readyz + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 10 + # TODO(user): Configure the resources accordingly based on the project requirements. + # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + resources: + limits: + cpu: 500m + memory: 128Mi + requests: + cpu: 10m + memory: 64Mi + serviceAccountName: controller-manager + terminationGracePeriodSeconds: 10 diff --git a/config/prometheus/kustomization.yaml b/config/prometheus/kustomization.yaml new file mode 100644 index 000000000..ed137168a --- /dev/null +++ b/config/prometheus/kustomization.yaml @@ -0,0 +1,2 @@ +resources: +- monitor.yaml diff --git a/config/prometheus/monitor.yaml b/config/prometheus/monitor.yaml new file mode 100644 index 000000000..62e8a7ccb --- /dev/null +++ b/config/prometheus/monitor.yaml @@ -0,0 +1,26 @@ + +# Prometheus Monitor Service (Metrics) +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + labels: + control-plane: controller-manager + app.kubernetes.io/name: servicemonitor + app.kubernetes.io/instance: controller-manager-metrics-monitor + app.kubernetes.io/component: metrics + app.kubernetes.io/created-by: controller + app.kubernetes.io/part-of: controller + app.kubernetes.io/managed-by: kustomize + name: controller-manager-metrics-monitor + namespace: system +spec: + endpoints: + - path: /metrics + port: https + scheme: https + bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token + tlsConfig: + insecureSkipVerify: true + selector: + matchLabels: + control-plane: controller-manager diff --git a/config/rbac/kluctldeployment_editor_role.yaml b/config/rbac/kluctldeployment_editor_role.yaml new file mode 100644 index 000000000..8304fdd0c --- /dev/null +++ b/config/rbac/kluctldeployment_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit kluctldeployments. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: kluctldeployment-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: controller + app.kubernetes.io/part-of: controller + app.kubernetes.io/managed-by: kustomize + name: kluctldeployment-editor-role +rules: +- apiGroups: + - gitops.kluctl.io + resources: + - kluctldeployments + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - gitops.kluctl.io + resources: + - kluctldeployments/status + verbs: + - get diff --git a/config/rbac/kluctldeployment_viewer_role.yaml b/config/rbac/kluctldeployment_viewer_role.yaml new file mode 100644 index 000000000..50685c666 --- /dev/null +++ b/config/rbac/kluctldeployment_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view kluctldeployments. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: kluctldeployment-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: controller + app.kubernetes.io/part-of: controller + app.kubernetes.io/managed-by: kustomize + name: kluctldeployment-viewer-role +rules: +- apiGroups: + - gitops.kluctl.io + resources: + - kluctldeployments + verbs: + - get + - list + - watch +- apiGroups: + - gitops.kluctl.io + resources: + - kluctldeployments/status + verbs: + - get diff --git a/config/rbac/kustomization.yaml b/config/rbac/kustomization.yaml new file mode 100644 index 000000000..166fe7986 --- /dev/null +++ b/config/rbac/kustomization.yaml @@ -0,0 +1,11 @@ +resources: +# All RBAC will be applied under this service account in +# the deployment namespace. You may comment out this resource +# if your manager will use a service account that exists at +# runtime. Be sure to update RoleBinding and ClusterRoleBinding +# subjects if changing service account names. +- service_account.yaml +- role.yaml +- role_binding.yaml +- leader_election_role.yaml +- leader_election_role_binding.yaml diff --git a/config/rbac/leader_election_role.yaml b/config/rbac/leader_election_role.yaml new file mode 100644 index 000000000..9bf1a8ac0 --- /dev/null +++ b/config/rbac/leader_election_role.yaml @@ -0,0 +1,44 @@ +# permissions to do leader election. +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + labels: + app.kubernetes.io/name: role + app.kubernetes.io/instance: leader-election-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: controller + app.kubernetes.io/part-of: controller + app.kubernetes.io/managed-by: kustomize + name: leader-election-role +rules: +- apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch diff --git a/config/rbac/leader_election_role_binding.yaml b/config/rbac/leader_election_role_binding.yaml new file mode 100644 index 000000000..d8cfaffd6 --- /dev/null +++ b/config/rbac/leader_election_role_binding.yaml @@ -0,0 +1,19 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + app.kubernetes.io/name: rolebinding + app.kubernetes.io/instance: leader-election-rolebinding + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: controller + app.kubernetes.io/part-of: controller + app.kubernetes.io/managed-by: kustomize + name: leader-election-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: leader-election-role +subjects: +- kind: ServiceAccount + name: controller-manager + namespace: system diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml new file mode 100644 index 000000000..5e7aaeef7 --- /dev/null +++ b/config/rbac/role.yaml @@ -0,0 +1,54 @@ +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + creationTimestamp: null + name: manager-role +rules: +- apiGroups: + - "" + resources: + - configmaps + - secrets + - serviceaccounts + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +- apiGroups: + - gitops.kluctl.io + resources: + - kluctldeployments + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - gitops.kluctl.io + resources: + - kluctldeployments/finalizers + verbs: + - create + - delete + - get + - patch + - update +- apiGroups: + - gitops.kluctl.io + resources: + - kluctldeployments/status + verbs: + - get + - patch + - update diff --git a/config/rbac/role_binding.yaml b/config/rbac/role_binding.yaml new file mode 100644 index 000000000..5c650c9a8 --- /dev/null +++ b/config/rbac/role_binding.yaml @@ -0,0 +1,19 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app.kubernetes.io/name: clusterrolebinding + app.kubernetes.io/instance: manager-rolebinding + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: controller + app.kubernetes.io/part-of: controller + app.kubernetes.io/managed-by: kustomize + name: manager-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: manager-role +subjects: +- kind: ServiceAccount + name: controller-manager + namespace: system diff --git a/config/rbac/service_account.yaml b/config/rbac/service_account.yaml new file mode 100644 index 000000000..a0e977167 --- /dev/null +++ b/config/rbac/service_account.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app.kubernetes.io/name: serviceaccount + app.kubernetes.io/instance: controller-manager-sa + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: controller + app.kubernetes.io/part-of: controller + app.kubernetes.io/managed-by: kustomize + name: controller-manager + namespace: system diff --git a/docs/reference/commands/controller.md b/docs/reference/commands/controller.md new file mode 100644 index 000000000..ff5fe4938 --- /dev/null +++ b/docs/reference/commands/controller.md @@ -0,0 +1,25 @@ + + +## Command + +Usage: kluctl controller [flags] + + + +## Arguments + +The following arguments are available: + +``` + +``` + diff --git a/hack/boilerplate.go.txt b/hack/boilerplate.go.txt new file mode 100644 index 000000000..65b862271 --- /dev/null +++ b/hack/boilerplate.go.txt @@ -0,0 +1,15 @@ +/* +Copyright 2023. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ \ No newline at end of file diff --git a/pkg/controllers/internal/metrics/kluctl_project.go b/pkg/controllers/internal/metrics/kluctl_project.go new file mode 100644 index 000000000..c1cdcde09 --- /dev/null +++ b/pkg/controllers/internal/metrics/kluctl_project.go @@ -0,0 +1,122 @@ +package metrics + +import ( + "github.com/prometheus/client_golang/prometheus" + "sigs.k8s.io/controller-runtime/pkg/metrics" +) + +const ( + DeploymentDurationKey = "deployment_duration_seconds" + NumberOfChangedObjectsKey = "number_of_changed_objects" + NumberOfDeletedObjectsKey = "number_of_deleted_objects" + NumberOfErrorsKey = "number_of_errors" + NumberOfOrphanObjectsKey = "number_of_orphan_objects" + NumberOfWarningsKey = "number_of_warnings" + PruneDurationKey = "prune_duration_seconds" + DeleteDurationKey = "delete_duration_seconds" + ValidateDurationKey = "validate_duration_seconds" +) + +var ( + deploymentDuration = prometheus.NewHistogramVec(prometheus.HistogramOpts{ + Subsystem: KluctlDeploymentControllerSubsystem, + Name: DeploymentDurationKey, + Help: "How long a single deployment takes in seconds.", + }, []string{"namespace", "name", "mode"}) + + numberOfChangedObjects = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Subsystem: KluctlDeploymentControllerSubsystem, + Name: NumberOfChangedObjectsKey, + Help: "How many objects have been changed by a single project.", + }, []string{"namespace", "name"}) + + numberOfDeletedObjects = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Subsystem: KluctlDeploymentControllerSubsystem, + Name: NumberOfDeletedObjectsKey, + Help: "How many things has been deleted by a single project.", + }, []string{"namespace", "name"}) + + numberOfErrors = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Subsystem: KluctlDeploymentControllerSubsystem, + Name: NumberOfErrorsKey, + Help: "How many errors are related to a single project.", + }, []string{"namespace", "name", "action"}) + + numberOfOrphanObjects = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Subsystem: KluctlDeploymentControllerSubsystem, + Name: NumberOfOrphanObjectsKey, + Help: "How many orphans are related to a single project.", + }, []string{"namespace", "name"}) + + numberOfWarnings = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Subsystem: KluctlDeploymentControllerSubsystem, + Name: NumberOfWarningsKey, + Help: "How many warnings are related to a single project.", + }, []string{"namespace", "name", "action"}) + + pruneDuration = prometheus.NewHistogramVec(prometheus.HistogramOpts{ + Subsystem: KluctlDeploymentControllerSubsystem, + Name: PruneDurationKey, + Help: "How long a single prune takes in seconds.", + }, []string{"namespace", "name"}) + + deleteDuration = prometheus.NewHistogramVec(prometheus.HistogramOpts{ + Subsystem: KluctlDeploymentControllerSubsystem, + Name: DeleteDurationKey, + Help: "How long a single delete takes in seconds.", + }, []string{"namespace", "name"}) + + validateDuration = prometheus.NewHistogramVec(prometheus.HistogramOpts{ + Subsystem: KluctlDeploymentControllerSubsystem, + Name: ValidateDurationKey, + Help: "How long a single validate takes in seconds.", + }, []string{"namespace", "name"}) +) + +func init() { + metrics.Registry.MustRegister(deploymentDuration) + metrics.Registry.MustRegister(numberOfChangedObjects) + metrics.Registry.MustRegister(numberOfDeletedObjects) + metrics.Registry.MustRegister(numberOfErrors) + metrics.Registry.MustRegister(numberOfOrphanObjects) + metrics.Registry.MustRegister(numberOfWarnings) + metrics.Registry.MustRegister(pruneDuration) + metrics.Registry.MustRegister(deleteDuration) + metrics.Registry.MustRegister(validateDuration) +} + +func NewKluctlDeploymentDuration(namespace string, name string, mode string) prometheus.Observer { + return deploymentDuration.WithLabelValues(namespace, name, mode) +} + +func NewKluctlNumberOfChangedObjects(namespace string, name string) prometheus.Gauge { + return numberOfChangedObjects.WithLabelValues(namespace, name) +} + +func NewKluctlNumberOfDeletedObjects(namespace string, name string) prometheus.Gauge { + return numberOfDeletedObjects.WithLabelValues(namespace, name) +} + +func NewKluctlNumberOfErrors(namespace string, name string, action string) prometheus.Gauge { + return numberOfErrors.WithLabelValues(namespace, name, action) +} + +func NewKluctlNumberOfOrphanObjects(namespace string, name string) prometheus.Gauge { + return numberOfOrphanObjects.WithLabelValues(namespace, name) +} + +func NewKluctlNumberOfWarnings(namespace string, name string, action string) prometheus.Gauge { + return numberOfWarnings.WithLabelValues(namespace, name, action) +} + +func NewKluctlPruneDuration(namespace string, name string) prometheus.Observer { + return pruneDuration.WithLabelValues(namespace, name) +} + +func NewKluctlDeleteDuration(namespace string, name string) prometheus.Observer { + return deleteDuration.WithLabelValues(namespace, name) +} + +func NewKluctlValidateDuration(namespace string, name string) prometheus.Observer { + return validateDuration.WithLabelValues(namespace, name) +} diff --git a/pkg/controllers/internal/metrics/kluctldeployment_controller.go b/pkg/controllers/internal/metrics/kluctldeployment_controller.go new file mode 100644 index 000000000..8900146ad --- /dev/null +++ b/pkg/controllers/internal/metrics/kluctldeployment_controller.go @@ -0,0 +1,89 @@ +package metrics + +import ( + "github.com/prometheus/client_golang/prometheus" + "sigs.k8s.io/controller-runtime/pkg/metrics" +) + +// Metrics subsystem and all keys used by the kluctldeployment controller. +const ( + KluctlDeploymentControllerSubsystem = "kluctldeployments" + + DeploymentIntervalKey = "deployment_interval_seconds" + DryRunEnabledKey = "dry_run_enabled" + LastObjectStatusKey = "last_object_status" + PruneEnabledKey = "prune_enabled" + DeleteEnabledKey = "delete_enabled" + SourceSpecKey = "source_spec" +) + +var ( + deploymentInterval = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Subsystem: KluctlDeploymentControllerSubsystem, + Name: DeploymentIntervalKey, + Help: "The configured deployment interval of a single deployment.", + }, []string{"namespace", "name"}) + + dryRunEnabled = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Subsystem: KluctlDeploymentControllerSubsystem, + Name: DryRunEnabledKey, + Help: "Is dry-run enabled for a single deployment.", + }, []string{"namespace", "name"}) + + lastObjectStatus = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Subsystem: KluctlDeploymentControllerSubsystem, + Name: LastObjectStatusKey, + Help: "Last object status of a single deployment.", + }, []string{"namespace", "name"}) + + pruneEnabled = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Subsystem: KluctlDeploymentControllerSubsystem, + Name: PruneEnabledKey, + Help: "Is pruning enabled for a single deployment.", + }, []string{"namespace", "name"}) + + deleteEnabled = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Subsystem: KluctlDeploymentControllerSubsystem, + Name: DeleteEnabledKey, + Help: "Is deletion enabled for a single deployment.", + }, []string{"namespace", "name"}) + + sourceSpec = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Subsystem: KluctlDeploymentControllerSubsystem, + Name: SourceSpecKey, + Help: "The configured source spec of a single deployment.", + }, []string{"namespace", "name", "url", "path", "ref"}) +) + +func init() { + metrics.Registry.MustRegister(deploymentInterval) + metrics.Registry.MustRegister(dryRunEnabled) + metrics.Registry.MustRegister(lastObjectStatus) + metrics.Registry.MustRegister(pruneEnabled) + metrics.Registry.MustRegister(deleteEnabled) + metrics.Registry.MustRegister(sourceSpec) +} + +func NewKluctlDeploymentInterval(namespace string, name string) prometheus.Gauge { + return dryRunEnabled.WithLabelValues(namespace, name) +} + +func NewKluctlDryRunEnabled(namespace string, name string) prometheus.Gauge { + return dryRunEnabled.WithLabelValues(namespace, name) +} + +func NewKluctlLastObjectStatus(namespace string, name string) prometheus.Gauge { + return lastObjectStatus.WithLabelValues(namespace, name) +} + +func NewKluctlPruneEnabled(namespace string, name string) prometheus.Gauge { + return pruneEnabled.WithLabelValues(namespace, name) +} + +func NewKluctlDeleteEnabled(namespace string, name string) prometheus.Gauge { + return deleteEnabled.WithLabelValues(namespace, name) +} + +func NewKluctlSourceSpec(namespace string, name string, url string, path string, ref string) prometheus.Gauge { + return sourceSpec.WithLabelValues(namespace, name, url, path, ref) +} diff --git a/pkg/controllers/internal/sops/LICENSE b/pkg/controllers/internal/sops/LICENSE new file mode 100644 index 000000000..a612ad981 --- /dev/null +++ b/pkg/controllers/internal/sops/LICENSE @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/pkg/controllers/internal/sops/aws_config.go b/pkg/controllers/internal/sops/aws_config.go new file mode 100644 index 000000000..fe00c00c2 --- /dev/null +++ b/pkg/controllers/internal/sops/aws_config.go @@ -0,0 +1,24 @@ +package sops + +import ( + "fmt" + "github.com/aws/aws-sdk-go-v2/credentials" + "go.mozilla.org/sops/v3/kms" + "sigs.k8s.io/yaml" +) + +// LoadCredsProviderFromYaml parses the given YAML returns a CredsProvider object +// which contains the credentials provider used for authenticating towards AWS KMS. +func LoadCredsProviderFromYaml(b []byte) (*kms.CredentialsProvider, error) { + credInfo := struct { + AccessKeyID string `json:"aws_access_key_id"` + SecretAccessKey string `json:"aws_secret_access_key"` + SessionToken string `json:"aws_session_token"` + }{} + if err := yaml.Unmarshal(b, &credInfo); err != nil { + return nil, fmt.Errorf("failed to unmarshal AWS credentials file: %w", err) + } + cp := kms.NewCredentialsProvider(credentials.NewStaticCredentialsProvider(credInfo.AccessKeyID, credInfo.SecretAccessKey, credInfo.SessionToken)) + + return cp, nil +} diff --git a/pkg/controllers/internal/sops/azure_config.go b/pkg/controllers/internal/sops/azure_config.go new file mode 100644 index 000000000..0b654b7b0 --- /dev/null +++ b/pkg/controllers/internal/sops/azure_config.go @@ -0,0 +1,156 @@ +// Copyright (C) 2022 The Flux authors +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +package sops + +import ( + "bytes" + "encoding/binary" + "fmt" + "github.com/dimchansky/utfbom" + "io/ioutil" + "unicode/utf16" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud" + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + "sigs.k8s.io/yaml" +) + +// LoadAADConfigFromBytes attempts to load the given bytes into the given AADConfig. +// By first decoding it if UTF-16, and then unmarshalling it into the given struct. +// It returns an error for any failure. +func LoadAADConfigFromBytes(b []byte, s *AADConfig) error { + b, err := decode(b) + if err != nil { + return fmt.Errorf("failed to decode Azure authentication file bytes: %w", err) + } + if err = yaml.Unmarshal(b, s); err != nil { + err = fmt.Errorf("failed to unmarshal Azure authentication file: %w", err) + } + return err +} + +// AADConfig contains the selection of fields from an Azure authentication file +// required for Active Directory authentication. +type AADConfig struct { + AZConfig + TenantID string `json:"tenantId,omitempty"` + ClientID string `json:"clientId,omitempty"` + ClientSecret string `json:"clientSecret,omitempty"` + ClientCertificate string `json:"clientCertificate,omitempty"` + ClientCertificatePassword string `json:"clientCertificatePassword,omitempty"` + ClientCertificateSendChain bool `json:"clientCertificateSendChain,omitempty"` + AuthorityHost string `json:"authorityHost,omitempty"` +} + +// AZConfig contains the Service Principal fields as generated by `az`. +// Ref: https://docs.microsoft.com/en-us/azure/aks/kubernetes-service-principal?tabs=azure-cli#manually-create-a-service-principal +type AZConfig struct { + AppID string `json:"appId,omitempty"` + Tenant string `json:"tenant,omitempty"` + Password string `json:"password,omitempty"` +} + +// TokenFromAADConfig attempts to construct a Token using the AADConfig values. +// It detects credentials in the following order: +// +// - azidentity.ClientSecretCredential when `tenantId`, `clientId` and +// `clientSecret` fields are found. +// - azidentity.ClientCertificateCredential when `tenantId`, +// `clientCertificate` (and optionally `clientCertificatePassword`) fields +// are found. +// - azidentity.ClientSecretCredential when AZConfig fields are found. +// - azidentity.ManagedIdentityCredential for a User ID, when a `clientId` +// field but no `tenantId` is found. +// +// If no set of credentials is found or the azcore.TokenCredential can not be +// created, an error is returned. +func TokenFromAADConfig(c AADConfig) (_ azcore.TokenCredential, err error) { + var token azcore.TokenCredential + if c.TenantID != "" && c.ClientID != "" { + if c.ClientSecret != "" { + if token, err = azidentity.NewClientSecretCredential(c.TenantID, c.ClientID, c.ClientSecret, &azidentity.ClientSecretCredentialOptions{ + ClientOptions: azcore.ClientOptions{ + Cloud: c.GetCloudConfig(), + }, + }); err != nil { + return + } + return token, nil + } + if c.ClientCertificate != "" { + certs, pk, err := azidentity.ParseCertificates([]byte(c.ClientCertificate), []byte(c.ClientCertificatePassword)) + if err != nil { + return nil, err + } + if token, err = azidentity.NewClientCertificateCredential(c.TenantID, c.ClientID, certs, pk, &azidentity.ClientCertificateCredentialOptions{ + SendCertificateChain: c.ClientCertificateSendChain, + ClientOptions: azcore.ClientOptions{ + Cloud: c.GetCloudConfig(), + }, + }); err != nil { + return nil, err + } + return token, nil + } + } + + switch { + case c.Tenant != "" && c.AppID != "" && c.Password != "": + if token, err = azidentity.NewClientSecretCredential(c.Tenant, c.AppID, c.Password, &azidentity.ClientSecretCredentialOptions{ + ClientOptions: azcore.ClientOptions{ + Cloud: c.GetCloudConfig(), + }, + }); err != nil { + return + } + return token, nil + case c.ClientID != "": + if token, err = azidentity.NewManagedIdentityCredential(&azidentity.ManagedIdentityCredentialOptions{ + ID: azidentity.ClientID(c.ClientID), + }); err != nil { + return + } + return token, nil + default: + return nil, fmt.Errorf("invalid data: requires a '%s' field, a combination of '%s', '%s' and '%s', or '%s', '%s' and '%s'", + "clientId", "tenantId", "clientId", "clientSecret", "tenantId", "clientId", "clientCertificate") + } +} + +// GetCloudConfig returns a cloud.Configuration with the AuthorityHost, or the +// Azure Public Cloud default. +func (s AADConfig) GetCloudConfig() cloud.Configuration { + if s.AuthorityHost != "" { + return cloud.Configuration{ + ActiveDirectoryAuthorityHost: s.AuthorityHost, + Services: map[cloud.ServiceName]cloud.ServiceConfiguration{}, + } + } + return cloud.AzurePublic +} + +func decode(b []byte) ([]byte, error) { + reader, enc := utfbom.Skip(bytes.NewReader(b)) + switch enc { + case utfbom.UTF16LittleEndian: + u16 := make([]uint16, (len(b)/2)-1) + err := binary.Read(reader, binary.LittleEndian, &u16) + if err != nil { + return nil, err + } + return []byte(string(utf16.Decode(u16))), nil + case utfbom.UTF16BigEndian: + u16 := make([]uint16, (len(b)/2)-1) + err := binary.Read(reader, binary.BigEndian, &u16) + if err != nil { + return nil, err + } + return []byte(string(utf16.Decode(u16))), nil + } + return ioutil.ReadAll(reader) +} diff --git a/pkg/controllers/internal/sops/key_server_from_secret.go b/pkg/controllers/internal/sops/key_server_from_secret.go new file mode 100644 index 000000000..f687825d9 --- /dev/null +++ b/pkg/controllers/internal/sops/key_server_from_secret.go @@ -0,0 +1,106 @@ +package sops + +import ( + "bytes" + "fmt" + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + intkeyservice "github.com/kluctl/kluctl/v2/pkg/controllers/internal/sops/keyservice" + "go.mozilla.org/sops/v3/age" + "go.mozilla.org/sops/v3/azkv" + "go.mozilla.org/sops/v3/hcvault" + "go.mozilla.org/sops/v3/keyservice" + "go.mozilla.org/sops/v3/kms" + "go.mozilla.org/sops/v3/pgp" + corev1 "k8s.io/api/core/v1" + "path/filepath" + "strings" +) + +const ( + // DecryptionPGPExt is the extension of the file containing an armored PGP + // key. + DecryptionPGPExt = ".asc" + // DecryptionAgeExt is the extension of the file containing an age key + // file. + DecryptionAgeExt = ".agekey" + // DecryptionVaultTokenFileName is the name of the file containing the + // Hashicorp Vault token. + DecryptionVaultTokenFileName = "sops.vault-token" + // DecryptionAWSKmsFile is the name of the file containing the AWS KMS + // credentials. + DecryptionAWSKmsFile = "sops.aws-kms" + // DecryptionAzureAuthFile is the name of the file containing the Azure + // credentials. + DecryptionAzureAuthFile = "sops.azure-kv" + // DecryptionGCPCredsFile is the name of the file containing the GCP + // credentials. + DecryptionGCPCredsFile = "sops.gcp-kms" +) + +func BuildSopsKeyServerFromSecret(secret *corev1.Secret, gnuPGHomeDir string, opts ...intkeyservice.ServerOption) (keyservice.KeyServiceClient, error) { + gnuPGHome := pgp.GnuPGHome(gnuPGHomeDir) + + var ageIdentities age.ParsedIdentities + var vaultToken hcvault.Token + var awsCredsProvider *kms.CredentialsProvider + var azureToken azcore.TokenCredential + var gcpCredsJSON []byte + + var err error + + for name, value := range secret.Data { + switch filepath.Ext(name) { + case DecryptionPGPExt: + if err = gnuPGHome.Import(value); err != nil { + return nil, fmt.Errorf("failed to import '%s' data from decryption Secret: %w", name, err) + } + case DecryptionAgeExt: + if err = ageIdentities.Import(string(value)); err != nil { + return nil, fmt.Errorf("failed to import '%s' data from decryption Secret: %w", name, err) + } + case filepath.Ext(DecryptionVaultTokenFileName): + // Make sure we have the absolute name + if name == DecryptionVaultTokenFileName { + token := string(value) + token = strings.Trim(strings.TrimSpace(token), "\n") + vaultToken = hcvault.Token(token) + } + case filepath.Ext(DecryptionAWSKmsFile): + if name == DecryptionAWSKmsFile { + if awsCredsProvider, err = LoadCredsProviderFromYaml(value); err != nil { + return nil, fmt.Errorf("failed to import data from decryption Secret '%s': %w", name, err) + } + } + case filepath.Ext(DecryptionAzureAuthFile): + // Make sure we have the absolute name + if name == DecryptionAzureAuthFile { + conf := AADConfig{} + if err = LoadAADConfigFromBytes(value, &conf); err != nil { + return nil, fmt.Errorf("failed to import '%s' data from decryption Secret: %w", name, err) + } + if azureToken, err = TokenFromAADConfig(conf); err != nil { + return nil, fmt.Errorf("failed to import '%s' data from decryption Secret: %w", name, err) + } + } + case filepath.Ext(DecryptionGCPCredsFile): + if name == DecryptionGCPCredsFile { + gcpCredsJSON = bytes.Trim(value, "\n") + } + } + } + + serverOpts := []intkeyservice.ServerOption{ + intkeyservice.WithGnuPGHome(gnuPGHome), + intkeyservice.WithVaultToken(vaultToken), + intkeyservice.WithAgeIdentities(ageIdentities), + intkeyservice.WithGCPCredsJSON(gcpCredsJSON), + } + serverOpts = append(serverOpts, opts...) + if azureToken != nil { + serverOpts = append(serverOpts, intkeyservice.WithAzureToken{Token: azkv.NewTokenCredential(azureToken)}) + } + serverOpts = append(serverOpts, intkeyservice.WithAWSKeys{CredsProvider: awsCredsProvider}) + server := intkeyservice.NewServer(serverOpts...) + + return keyservice.NewCustomLocalClient(server), nil +} diff --git a/pkg/controllers/internal/sops/key_server_from_service_account.go b/pkg/controllers/internal/sops/key_server_from_service_account.go new file mode 100644 index 000000000..105e27ea5 --- /dev/null +++ b/pkg/controllers/internal/sops/key_server_from_service_account.go @@ -0,0 +1,91 @@ +package sops + +import ( + "context" + "fmt" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/aws/arn" + "github.com/aws/aws-sdk-go-v2/credentials/stscreds" + "github.com/aws/aws-sdk-go-v2/service/sts" + "github.com/aws/smithy-go/logging" + intkeyservice "github.com/kluctl/kluctl/v2/pkg/controllers/internal/sops/keyservice" + "go.mozilla.org/sops/v3/keyservice" + "go.mozilla.org/sops/v3/kms" + authenticationv1 "k8s.io/api/authentication/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "os" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func BuildSopsKeyServerFromServiceAccount(ctx context.Context, client client.Client, sa *corev1.ServiceAccount) (keyservice.KeyServiceClient, error) { + var serverOpts []intkeyservice.ServerOption + + x, err := withAWSWebIdentity(ctx, client, sa) + if err != nil { + return nil, err + } + serverOpts = append(serverOpts, x...) + + server := intkeyservice.NewServer(serverOpts...) + return keyservice.NewCustomLocalClient(server), nil +} + +type webIdentityToken string + +func (t webIdentityToken) GetIdentityToken() ([]byte, error) { + return []byte(t), nil +} + +func withAWSWebIdentity(ctx context.Context, client client.Client, sa *corev1.ServiceAccount) ([]intkeyservice.ServerOption, error) { + roleArnStr := "" + if sa.GetAnnotations() != nil { + roleArnStr, _ = sa.GetAnnotations()["eks.amazonaws.com/role-arn"] + } + if roleArnStr == "" { + return nil, nil + } + roleArn, err := arn.Parse(roleArnStr) + if err != nil { + return nil, err + } + + exp := int64(60 * 10) + + tokenRequest := authenticationv1.TokenRequest{ + ObjectMeta: metav1.ObjectMeta{ + Name: sa.Name, + Namespace: sa.Namespace, + }, + Spec: authenticationv1.TokenRequestSpec{ + Audiences: []string{"sts.amazonaws.com"}, + ExpirationSeconds: &exp, + }, + } + + err = client.SubResource("token").Create(ctx, sa, &tokenRequest) + if err != nil { + return nil, fmt.Errorf("failed to create token for AWS STS: %w", err) + } + + cfg := aws.Config{ + Credentials: aws.AnonymousCredentials{}, + Logger: logging.NewStandardLogger(os.Stderr), + Region: roleArn.Region, + } + if cfg.Region == "" { + cfg.Region = "aws-global" + } + + optFns := []func(*stscreds.WebIdentityRoleOptions){ + func(options *stscreds.WebIdentityRoleOptions) { + options.RoleSessionName = "kluctl-sops-decrypter" + }, + } + + provider := stscreds.NewWebIdentityRoleProvider(sts.NewFromConfig(cfg), roleArnStr, webIdentityToken(tokenRequest.Status.Token), optFns...) + + var serverOpts []intkeyservice.ServerOption + serverOpts = append(serverOpts, intkeyservice.WithAWSKeys{CredsProvider: kms.NewCredentialsProvider(provider)}) + return serverOpts, nil +} diff --git a/pkg/controllers/internal/sops/keyservice/keyservice.go b/pkg/controllers/internal/sops/keyservice/keyservice.go new file mode 100644 index 000000000..4d2dda569 --- /dev/null +++ b/pkg/controllers/internal/sops/keyservice/keyservice.go @@ -0,0 +1,23 @@ +// Copyright (C) 2022 The Flux authors +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +package keyservice + +import ( + "go.mozilla.org/sops/v3/age" + "go.mozilla.org/sops/v3/keys" + "go.mozilla.org/sops/v3/pgp" +) + +// IsOfflineMethod returns true for offline decrypt methods or false otherwise +func IsOfflineMethod(mk keys.MasterKey) bool { + switch mk.(type) { + case *pgp.MasterKey, *age.MasterKey: + return true + default: + return false + } +} diff --git a/pkg/controllers/internal/sops/keyservice/options.go b/pkg/controllers/internal/sops/keyservice/options.go new file mode 100644 index 000000000..555452ceb --- /dev/null +++ b/pkg/controllers/internal/sops/keyservice/options.go @@ -0,0 +1,87 @@ +// Copyright (C) 2022 The Flux authors +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +package keyservice + +import ( + extage "filippo.io/age" + "go.mozilla.org/sops/v3/age" + "go.mozilla.org/sops/v3/azkv" + "go.mozilla.org/sops/v3/gcpkms" + "go.mozilla.org/sops/v3/hcvault" + "go.mozilla.org/sops/v3/keyservice" + "go.mozilla.org/sops/v3/kms" + "go.mozilla.org/sops/v3/pgp" +) + +// ServerOption is some configuration that modifies the Server. +type ServerOption interface { + // ApplyToServer applies this configuration to the given Server. + ApplyToServer(s *Server) +} + +// WithGnuPGHome configures the GnuPG home directory on the Server. +type WithGnuPGHome string + +// ApplyToServer applies this configuration to the given Server. +func (o WithGnuPGHome) ApplyToServer(s *Server) { + s.gnuPGHome = pgp.GnuPGHome(o) +} + +// WithVaultToken configures the Hashicorp Vault token on the Server. +type WithVaultToken string + +// ApplyToServer applies this configuration to the given Server. +func (o WithVaultToken) ApplyToServer(s *Server) { + s.vaultToken = hcvault.Token(o) +} + +// WithAgeIdentities configures the parsed age identities on the Server. +type WithAgeIdentities []extage.Identity + +// ApplyToServer applies this configuration to the given Server. +func (o WithAgeIdentities) ApplyToServer(s *Server) { + s.ageIdentities = age.ParsedIdentities(o) +} + +// WithAWSKeys configures the AWS credentials on the Server +type WithAWSKeys struct { + CredsProvider *kms.CredentialsProvider +} + +// ApplyToServer applies this configuration to the given Server. +func (o WithAWSKeys) ApplyToServer(s *Server) { + s.awsCredsProvider = o.CredsProvider +} + +// WithGCPCredsJSON configures the GCP service account credentials JSON on the +// Server. +type WithGCPCredsJSON []byte + +// ApplyToServer applies this configuration to the given Server. +func (o WithGCPCredsJSON) ApplyToServer(s *Server) { + s.gcpCredsJSON = gcpkms.CredentialJSON(o) +} + +// WithAzureToken configures the Azure credential token on the Server. +type WithAzureToken struct { + Token *azkv.TokenCredential +} + +// ApplyToServer applies this configuration to the given Server. +func (o WithAzureToken) ApplyToServer(s *Server) { + s.azureToken = o.Token +} + +// WithDefaultServer configures the fallback default server on the Server. +type WithDefaultServer struct { + Server keyservice.KeyServiceServer +} + +// ApplyToServer applies this configuration to the given Server. +func (o WithDefaultServer) ApplyToServer(s *Server) { + s.defaultServer = o.Server +} diff --git a/pkg/controllers/internal/sops/keyservice/server.go b/pkg/controllers/internal/sops/keyservice/server.go new file mode 100644 index 000000000..584516957 --- /dev/null +++ b/pkg/controllers/internal/sops/keyservice/server.go @@ -0,0 +1,363 @@ +// Copyright (C) 2022 The Flux authors +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +package keyservice + +import ( + "fmt" + "go.mozilla.org/sops/v3/age" + "go.mozilla.org/sops/v3/azkv" + "go.mozilla.org/sops/v3/gcpkms" + "go.mozilla.org/sops/v3/hcvault" + "go.mozilla.org/sops/v3/keyservice" + "go.mozilla.org/sops/v3/kms" + "go.mozilla.org/sops/v3/pgp" + + "golang.org/x/net/context" +) + +// Server is a key service server that uses SOPS MasterKeys to fulfill +// requests. It intercepts Encrypt and Decrypt requests made for key types +// that need to run in a contained environment, instead of the default +// implementation which heavily utilizes environment variables or the runtime +// environment. Any request not handled by the Server is forwarded to the +// embedded default server. +type Server struct { + // gnuPGHome is the GnuPG home directory used for the Encrypt and Decrypt + // operations for PGP key types. + // When empty, the requests will be handled using the systems' runtime + // keyring. + gnuPGHome pgp.GnuPGHome + + // ageIdentities are the parsed age identities used for Decrypt + // operations for age key types. + ageIdentities age.ParsedIdentities + + // vaultToken is the token used for Encrypt and Decrypt operations of + // Hashicorp Vault requests. + // When empty, the request will be handled by defaultServer. + vaultToken hcvault.Token + + // azureToken is the credential token used for Encrypt and Decrypt + // operations of Azure Key Vault requests. + // When nil, the request will be handled by defaultServer. + azureToken *azkv.TokenCredential + + // awsCredsProvider is the Credentials object used for Encrypt and Decrypt + // operations of AWS KMS requests. + // When nil, the request will be handled by defaultServer. + awsCredsProvider *kms.CredentialsProvider + + // gcpCredsJSON is the JSON credentials used for Decrypt and Encrypt + // operations of GCP KMS requests. When nil, a default client with + // environmental runtime settings will be used. + gcpCredsJSON gcpkms.CredentialJSON + + // defaultServer is the fallback server, used to handle any request that + // is not eligible to be handled by this Server. + defaultServer keyservice.KeyServiceServer +} + +// NewServer constructs a new Server, configuring it with the provided options +// before returning the result. +// When WithDefaultServer() is not provided as an option, the SOPS server +// implementation is configured as default. +func NewServer(options ...ServerOption) keyservice.KeyServiceServer { + s := &Server{} + for _, opt := range options { + opt.ApplyToServer(s) + } + if s.defaultServer == nil { + s.defaultServer = &keyservice.Server{ + Prompt: false, + } + } + return s +} + +// Encrypt takes an encrypt request and encrypts the provided plaintext with +// the provided key, returning the encrypted result. +func (ks Server) Encrypt(ctx context.Context, req *keyservice.EncryptRequest) (*keyservice.EncryptResponse, error) { + key := req.Key + switch k := key.KeyType.(type) { + case *keyservice.Key_PgpKey: + ciphertext, err := ks.encryptWithPgp(k.PgpKey, req.Plaintext) + if err != nil { + return nil, err + } + return &keyservice.EncryptResponse{ + Ciphertext: ciphertext, + }, nil + case *keyservice.Key_AgeKey: + ciphertext, err := ks.encryptWithAge(k.AgeKey, req.Plaintext) + if err != nil { + return nil, err + } + return &keyservice.EncryptResponse{ + Ciphertext: ciphertext, + }, nil + case *keyservice.Key_VaultKey: + if ks.vaultToken != "" { + ciphertext, err := ks.encryptWithHCVault(k.VaultKey, req.Plaintext) + if err != nil { + return nil, err + } + return &keyservice.EncryptResponse{ + Ciphertext: ciphertext, + }, nil + } + case *keyservice.Key_KmsKey: + cipherText, err := ks.encryptWithAWSKMS(k.KmsKey, req.Plaintext) + if err != nil { + return nil, err + } + return &keyservice.EncryptResponse{ + Ciphertext: cipherText, + }, nil + case *keyservice.Key_AzureKeyvaultKey: + if ks.azureToken != nil { + ciphertext, err := ks.encryptWithAzureKeyVault(k.AzureKeyvaultKey, req.Plaintext) + if err != nil { + return nil, err + } + return &keyservice.EncryptResponse{ + Ciphertext: ciphertext, + }, nil + } + case *keyservice.Key_GcpKmsKey: + ciphertext, err := ks.encryptWithGCPKMS(k.GcpKmsKey, req.Plaintext) + if err != nil { + return nil, err + } + return &keyservice.EncryptResponse{ + Ciphertext: ciphertext, + }, nil + case nil: + return nil, fmt.Errorf("must provide a key") + } + // Fallback to default server for any other request. + return ks.defaultServer.Encrypt(ctx, req) +} + +// Decrypt takes a decrypt request and decrypts the provided ciphertext with +// the provided key, returning the decrypted result. +func (ks Server) Decrypt(ctx context.Context, req *keyservice.DecryptRequest) (*keyservice.DecryptResponse, error) { + key := req.Key + switch k := key.KeyType.(type) { + case *keyservice.Key_PgpKey: + plaintext, err := ks.decryptWithPgp(k.PgpKey, req.Ciphertext) + if err != nil { + return nil, err + } + return &keyservice.DecryptResponse{ + Plaintext: plaintext, + }, nil + case *keyservice.Key_AgeKey: + plaintext, err := ks.decryptWithAge(k.AgeKey, req.Ciphertext) + if err != nil { + return nil, err + } + return &keyservice.DecryptResponse{ + Plaintext: plaintext, + }, nil + case *keyservice.Key_VaultKey: + if ks.vaultToken != "" { + plaintext, err := ks.decryptWithHCVault(k.VaultKey, req.Ciphertext) + if err != nil { + return nil, err + } + return &keyservice.DecryptResponse{ + Plaintext: plaintext, + }, nil + } + case *keyservice.Key_KmsKey: + plaintext, err := ks.decryptWithAWSKMS(k.KmsKey, req.Ciphertext) + if err != nil { + return nil, err + } + return &keyservice.DecryptResponse{ + Plaintext: plaintext, + }, nil + case *keyservice.Key_AzureKeyvaultKey: + if ks.azureToken != nil { + plaintext, err := ks.decryptWithAzureKeyVault(k.AzureKeyvaultKey, req.Ciphertext) + if err != nil { + return nil, err + } + return &keyservice.DecryptResponse{ + Plaintext: plaintext, + }, nil + } + case *keyservice.Key_GcpKmsKey: + plaintext, err := ks.decryptWithGCPKMS(k.GcpKmsKey, req.Ciphertext) + if err != nil { + return nil, err + } + return &keyservice.DecryptResponse{ + Plaintext: plaintext, + }, nil + case nil: + return nil, fmt.Errorf("must provide a key") + } + // Fallback to default server for any other request. + return ks.defaultServer.Decrypt(ctx, req) +} + +func (ks *Server) encryptWithPgp(key *keyservice.PgpKey, plaintext []byte) ([]byte, error) { + pgpKey := pgp.NewMasterKeyFromFingerprint(key.Fingerprint) + if ks.gnuPGHome != "" { + ks.gnuPGHome.ApplyToMasterKey(pgpKey) + } + err := pgpKey.Encrypt(plaintext) + if err != nil { + return nil, err + } + return []byte(pgpKey.EncryptedKey), nil +} + +func (ks *Server) decryptWithPgp(key *keyservice.PgpKey, ciphertext []byte) ([]byte, error) { + pgpKey := pgp.NewMasterKeyFromFingerprint(key.Fingerprint) + if ks.gnuPGHome != "" { + ks.gnuPGHome.ApplyToMasterKey(pgpKey) + } + pgpKey.EncryptedKey = string(ciphertext) + plaintext, err := pgpKey.Decrypt() + return plaintext, err +} + +func (ks Server) encryptWithAge(key *keyservice.AgeKey, plaintext []byte) ([]byte, error) { + // Unlike the other encrypt and decrypt methods, validation of configuration + // is not required here. As the encryption happens purely based on the + // Recipient from the key. + + ageKey := age.MasterKey{ + Recipient: key.Recipient, + } + if err := ageKey.Encrypt(plaintext); err != nil { + return nil, err + } + return []byte(ageKey.EncryptedKey), nil +} + +func (ks *Server) decryptWithAge(key *keyservice.AgeKey, ciphertext []byte) ([]byte, error) { + ageKey := age.MasterKey{ + Recipient: key.Recipient, + } + ks.ageIdentities.ApplyToMasterKey(&ageKey) + ageKey.EncryptedKey = string(ciphertext) + plaintext, err := ageKey.Decrypt() + return plaintext, err +} + +func (ks *Server) encryptWithHCVault(key *keyservice.VaultKey, plaintext []byte) ([]byte, error) { + vaultKey := hcvault.MasterKey{ + VaultAddress: key.VaultAddress, + EnginePath: key.EnginePath, + KeyName: key.KeyName, + } + ks.vaultToken.ApplyToMasterKey(&vaultKey) + if err := vaultKey.Encrypt(plaintext); err != nil { + return nil, err + } + return []byte(vaultKey.EncryptedKey), nil +} + +func (ks *Server) decryptWithHCVault(key *keyservice.VaultKey, ciphertext []byte) ([]byte, error) { + vaultKey := hcvault.MasterKey{ + VaultAddress: key.VaultAddress, + EnginePath: key.EnginePath, + KeyName: key.KeyName, + } + vaultKey.EncryptedKey = string(ciphertext) + ks.vaultToken.ApplyToMasterKey(&vaultKey) + plaintext, err := vaultKey.Decrypt() + return plaintext, err +} + +func (ks *Server) encryptWithAWSKMS(key *keyservice.KmsKey, plaintext []byte) ([]byte, error) { + context := make(map[string]*string) + for key, val := range key.Context { + val := val + context[key] = &val + } + awsKey := kms.MasterKey{ + Arn: key.Arn, + Role: key.Role, + EncryptionContext: context, + } + if ks.awsCredsProvider != nil { + ks.awsCredsProvider.ApplyToMasterKey(&awsKey) + } + if err := awsKey.Encrypt(plaintext); err != nil { + return nil, err + } + return []byte(awsKey.EncryptedKey), nil +} + +func (ks *Server) decryptWithAWSKMS(key *keyservice.KmsKey, cipherText []byte) ([]byte, error) { + context := make(map[string]*string) + for key, val := range key.Context { + val := val + context[key] = &val + } + awsKey := kms.MasterKey{ + Arn: key.Arn, + Role: key.Role, + EncryptionContext: context, + } + awsKey.EncryptedKey = string(cipherText) + + if ks.awsCredsProvider != nil { + ks.awsCredsProvider.ApplyToMasterKey(&awsKey) + } + return awsKey.Decrypt() +} + +func (ks *Server) encryptWithAzureKeyVault(key *keyservice.AzureKeyVaultKey, plaintext []byte) ([]byte, error) { + azureKey := azkv.MasterKey{ + VaultURL: key.VaultUrl, + Name: key.Name, + Version: key.Version, + } + ks.azureToken.ApplyToMasterKey(&azureKey) + if err := azureKey.Encrypt(plaintext); err != nil { + return nil, err + } + return []byte(azureKey.EncryptedKey), nil +} + +func (ks *Server) decryptWithAzureKeyVault(key *keyservice.AzureKeyVaultKey, ciphertext []byte) ([]byte, error) { + azureKey := azkv.MasterKey{ + VaultURL: key.VaultUrl, + Name: key.Name, + Version: key.Version, + } + ks.azureToken.ApplyToMasterKey(&azureKey) + azureKey.EncryptedKey = string(ciphertext) + plaintext, err := azureKey.Decrypt() + return plaintext, err +} + +func (ks *Server) encryptWithGCPKMS(key *keyservice.GcpKmsKey, plaintext []byte) ([]byte, error) { + gcpKey := gcpkms.MasterKey{ + ResourceID: key.ResourceId, + } + ks.gcpCredsJSON.ApplyToMasterKey(&gcpKey) + if err := gcpKey.Encrypt(plaintext); err != nil { + return nil, err + } + return gcpKey.EncryptedDataKey(), nil +} + +func (ks *Server) decryptWithGCPKMS(key *keyservice.GcpKmsKey, ciphertext []byte) ([]byte, error) { + gcpKey := gcpkms.MasterKey{ + ResourceID: key.ResourceId, + } + ks.gcpCredsJSON.ApplyToMasterKey(&gcpKey) + gcpKey.EncryptedKey = string(ciphertext) + plaintext, err := gcpKey.Decrypt() + return plaintext, err +} diff --git a/pkg/controllers/internal/sops/keyservice/server_test.go b/pkg/controllers/internal/sops/keyservice/server_test.go new file mode 100644 index 000000000..d7e0b7b33 --- /dev/null +++ b/pkg/controllers/internal/sops/keyservice/server_test.go @@ -0,0 +1,249 @@ +// Copyright (C) 2022 The Flux authors +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +package keyservice + +import ( + "fmt" + "go.mozilla.org/sops/v3/age" + "go.mozilla.org/sops/v3/azkv" + "go.mozilla.org/sops/v3/gcpkms" + "go.mozilla.org/sops/v3/hcvault" + "go.mozilla.org/sops/v3/kms" + "go.mozilla.org/sops/v3/pgp" + "os" + "testing" + + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + "github.com/aws/aws-sdk-go-v2/credentials" + . "github.com/onsi/gomega" + "go.mozilla.org/sops/v3/keyservice" + "golang.org/x/net/context" +) + +func TestServer_EncryptDecrypt_PGP(t *testing.T) { + const ( + mockPublicKey = "testdata/public.gpg" + mockPrivateKey = "testdata/private.gpg" + mockFingerprint = "B59DAF469E8C948138901A649732075EA221A7EA" + ) + + g := NewWithT(t) + + gnuPGHome, err := pgp.NewGnuPGHome() + g.Expect(err).ToNot(HaveOccurred()) + t.Cleanup(func() { + _ = os.RemoveAll(gnuPGHome.String()) + }) + g.Expect(gnuPGHome.ImportFile(mockPublicKey)).To(Succeed()) + + s := NewServer(WithGnuPGHome(gnuPGHome)) + key := KeyFromMasterKey(pgp.NewMasterKeyFromFingerprint(mockFingerprint)) + dataKey := []byte("some data key") + encResp, err := s.Encrypt(context.TODO(), &keyservice.EncryptRequest{ + Key: &key, + Plaintext: dataKey, + }) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(encResp.Ciphertext).ToNot(BeEmpty()) + g.Expect(encResp.Ciphertext).ToNot(Equal(dataKey)) + + g.Expect(gnuPGHome.ImportFile(mockPrivateKey)).To(Succeed()) + decResp, err := s.Decrypt(context.TODO(), &keyservice.DecryptRequest{ + Key: &key, + Ciphertext: encResp.Ciphertext, + }) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(decResp.Plaintext).To(Equal(dataKey)) +} + +func TestServer_EncryptDecrypt_age(t *testing.T) { + g := NewWithT(t) + + const ( + mockRecipient string = "age1lzd99uklcjnc0e7d860axevet2cz99ce9pq6tzuzd05l5nr28ams36nvun" + mockIdentity string = "AGE-SECRET-KEY-1G0Q5K9TV4REQ3ZSQRMTMG8NSWQGYT0T7TZ33RAZEE0GZYVZN0APSU24RK7" + ) + + s := NewServer() + key := KeyFromMasterKey(&age.MasterKey{Recipient: mockRecipient}) + dataKey := []byte("some data key") + encResp, err := s.Encrypt(context.TODO(), &keyservice.EncryptRequest{ + Key: &key, + Plaintext: dataKey, + }) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(encResp.Ciphertext).ToNot(BeEmpty()) + g.Expect(encResp.Ciphertext).ToNot(Equal(dataKey)) + + i := make(age.ParsedIdentities, 0) + g.Expect(i.Import(mockIdentity)).To(Succeed()) + + s = NewServer(WithAgeIdentities(i)) + decResp, err := s.Decrypt(context.TODO(), &keyservice.DecryptRequest{ + Key: &key, + Ciphertext: encResp.Ciphertext, + }) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(decResp.Plaintext).To(Equal(dataKey)) +} + +func TestServer_EncryptDecrypt_HCVault(t *testing.T) { + g := NewWithT(t) + + s := NewServer(WithVaultToken("token")) + key := KeyFromMasterKey(hcvault.NewMasterKey("https://example.com", "engine-path", "key-name")) + _, err := s.Encrypt(context.TODO(), &keyservice.EncryptRequest{ + Key: &key, + }) + g.Expect(err).To(HaveOccurred()) + g.Expect(err.Error()).To(ContainSubstring("failed to encrypt sops data key to Vault transit backend")) + + _, err = s.Decrypt(context.TODO(), &keyservice.DecryptRequest{ + Key: &key, + }) + g.Expect(err).To(HaveOccurred()) + g.Expect(err.Error()).To(ContainSubstring("failed to decrypt sops data key from Vault transit backend")) +} + +func TestServer_EncryptDecrypt_HCVault_Fallback(t *testing.T) { + g := NewWithT(t) + + fallback := NewMockKeyServer() + s := NewServer(WithDefaultServer{Server: fallback}) + + key := KeyFromMasterKey(hcvault.NewMasterKey("https://example.com", "engine-path", "key-name")) + encReq := &keyservice.EncryptRequest{ + Key: &key, + Plaintext: []byte("some data key"), + } + _, err := s.Encrypt(context.TODO(), encReq) + g.Expect(err).To(HaveOccurred()) + g.Expect(fallback.encryptReqs).To(HaveLen(1)) + g.Expect(fallback.encryptReqs).To(ContainElement(encReq)) + g.Expect(fallback.decryptReqs).To(HaveLen(0)) + + fallback = NewMockKeyServer() + s = NewServer(WithDefaultServer{Server: fallback}) + decReq := &keyservice.DecryptRequest{ + Key: &key, + Ciphertext: []byte("some ciphertext"), + } + _, err = s.Decrypt(context.TODO(), decReq) + g.Expect(fallback.decryptReqs).To(HaveLen(1)) + g.Expect(fallback.decryptReqs).To(ContainElement(decReq)) + g.Expect(fallback.encryptReqs).To(HaveLen(0)) +} + +func TestServer_EncryptDecrypt_awskms(t *testing.T) { + g := NewWithT(t) + s := NewServer(WithAWSKeys{ + CredsProvider: kms.NewCredentialsProvider(credentials.StaticCredentialsProvider{}), + }) + + key := KeyFromMasterKey(kms.NewMasterKeyFromArn("arn:aws:kms:us-west-2:107501996527:key/612d5f0p-p1l3-45e6-aca6-a5b005693a48", nil, "")) + _, err := s.Encrypt(context.TODO(), &keyservice.EncryptRequest{ + Key: &key, + }) + g.Expect(err).To(HaveOccurred()) + g.Expect(err.Error()).To(ContainSubstring("failed to encrypt sops data key with AWS KMS")) + + _, err = s.Decrypt(context.TODO(), &keyservice.DecryptRequest{ + Key: &key, + }) + g.Expect(err).To(HaveOccurred()) + g.Expect(err.Error()).To(ContainSubstring("failed to decrypt sops data key with AWS KMS")) +} + +func TestServer_EncryptDecrypt_azkv(t *testing.T) { + g := NewWithT(t) + + identity, err := azidentity.NewDefaultAzureCredential(nil) + g.Expect(err).ToNot(HaveOccurred()) + s := NewServer(WithAzureToken{Token: azkv.NewTokenCredential(identity)}) + + key := KeyFromMasterKey(azkv.NewMasterKey("", "", "")) + _, err = s.Encrypt(context.TODO(), &keyservice.EncryptRequest{ + Key: &key, + }) + g.Expect(err).To(HaveOccurred()) + g.Expect(err.Error()).To(ContainSubstring("failed to encrypt sops data key with Azure Key Vault")) + + _, err = s.Decrypt(context.TODO(), &keyservice.DecryptRequest{ + Key: &key, + }) + g.Expect(err).To(HaveOccurred()) + g.Expect(err.Error()).To(ContainSubstring("failed to decrypt sops data key with Azure Key Vault")) + +} + +func TestServer_EncryptDecrypt_azkv_Fallback(t *testing.T) { + g := NewWithT(t) + + fallback := NewMockKeyServer() + s := NewServer(WithDefaultServer{Server: fallback}) + + key := KeyFromMasterKey(azkv.NewMasterKey("", "", "")) + encReq := &keyservice.EncryptRequest{ + Key: &key, + Plaintext: []byte("some data key"), + } + _, err := s.Encrypt(context.TODO(), encReq) + g.Expect(err).To(HaveOccurred()) + g.Expect(fallback.encryptReqs).To(HaveLen(1)) + g.Expect(fallback.encryptReqs).To(ContainElement(encReq)) + g.Expect(fallback.decryptReqs).To(HaveLen(0)) + + fallback = NewMockKeyServer() + s = NewServer(WithDefaultServer{Server: fallback}) + + decReq := &keyservice.DecryptRequest{ + Key: &key, + Ciphertext: []byte("some ciphertext"), + } + _, err = s.Decrypt(context.TODO(), decReq) + g.Expect(fallback.decryptReqs).To(HaveLen(1)) + g.Expect(fallback.decryptReqs).To(ContainElement(decReq)) + g.Expect(fallback.encryptReqs).To(HaveLen(0)) +} + +func TestServer_EncryptDecrypt_gcpkms(t *testing.T) { + g := NewWithT(t) + + creds := `{ "client_id": ".apps.googleusercontent.com", + "client_secret": "", + "type": "authorized_user"}` + s := NewServer(WithGCPCredsJSON([]byte(creds))) + + resourceID := "projects/test-flux/locations/global/keyRings/test-flux/cryptoKeys/sops" + key := KeyFromMasterKey(gcpkms.NewMasterKeyFromResourceID(resourceID)) + _, err := s.Encrypt(context.TODO(), &keyservice.EncryptRequest{ + Key: &key, + }) + g.Expect(err).To(HaveOccurred()) + g.Expect(err.Error()).To(ContainSubstring("failed to encrypt sops data key with GCP KMS")) + + _, err = s.Decrypt(context.TODO(), &keyservice.DecryptRequest{ + Key: &key, + }) + g.Expect(err).To(HaveOccurred()) + g.Expect(err.Error()).To(ContainSubstring("failed to decrypt sops data key with GCP KMS")) + +} + +func TestServer_EncryptDecrypt_Nil_KeyType(t *testing.T) { + g := NewWithT(t) + + s := NewServer(WithDefaultServer{NewMockKeyServer()}) + + expectErr := fmt.Errorf("must provide a key") + + _, err := s.Encrypt(context.TODO(), &keyservice.EncryptRequest{Key: &keyservice.Key{KeyType: nil}}) + g.Expect(err).To(Equal(expectErr)) + + _, err = s.Decrypt(context.TODO(), &keyservice.DecryptRequest{Key: &keyservice.Key{KeyType: nil}}) + g.Expect(err).To(Equal(expectErr)) +} diff --git a/pkg/controllers/internal/sops/keyservice/testdata/private.gpg b/pkg/controllers/internal/sops/keyservice/testdata/private.gpg new file mode 100644 index 000000000..f3ca23e77 --- /dev/null +++ b/pkg/controllers/internal/sops/keyservice/testdata/private.gpg @@ -0,0 +1,81 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- + +lQVYBGJGqrsBDAC7OxFP6Z2E+AkVZpQySjLFAeYJWdnadx0GOHnckOOFkQvVJauz +9KibgzLUkO9h0oIoP7dLyPEiRPhKgmbrktyCDfysvNeKCgI5XemJCJqCmwA/vWwp +GnVltcgsVVjVZ3vvD8VMfhKF77pkmMDj7mnCPw9x39R8SVpe2K9RO0QLk/Dt+o8t +MO+sXTz4ba4aMdjJvMoaoQKw6RXAouZa4H09i6tiAgXrRLxQDxJ58sGg/ZCWa5G4 +aI6PdObY41fzQlcobtifCbktbICVb1Ms1s0iZWttFmr0oTSkJTv3FPWhf6n126w4 +LEkF9d6YW+/0H9cqXa4GMfxXg4XBmJNJfYkLDVUlbp3xi+I+Lg1Sit6QlqkW93EW +etpYPK1KmDcW3IA6ausYnkyrcQbt1m5/hh9KJoQb6He/RytXEBxp90+v9y7THZGr +2U49ZEHQg6DAIj4j1p9NAGgqjKr9am6yk2pvpK3ZWmHQ6CZfCiBrEPCvdmEhrx4U +lj6wyd00YJknpDkAEQEAAQAL/iU3C+1g55TvAks1LP7D/cxn4LP6HpHMfEHoxtwf +FoJNftcamkL2Pe9PSDK1Lke44nMimwnewoNHxzx0KAXqFpdpNVCWZpdC/wctEgbR +ZXjRW17QBWg0IKKbW9LoEfS1EY7GiTZ3lrH1oQxuymRj1rSr+SNu1Jrxr5tLoalZ +SOCuQsTiuUPHxtPxYnWUw3bkco1Cz780QscsRU0ZdAUbOvmZQfMEqO2HJ5EYNdl0 +daVM0Uj8z6Wibre4CkyQ/8HT7Qy+Z7fgGzcSfwSzqqVJ5GtJimHK8CfX3HnHovHm +o7MtZGnr9fiug8m3XN8b1zM/ADXbVMXAmQY+QsI44bQPMxocLij3/HL5/oJvXGJ8 +rBc5xFbzvclteD2MPTHx7hpZP+ys84LpieMw9Fojk9/k1tfNNJEESVp1OTI42YFA +bmQ52Qgehn6skcs1oAygsSjaoCkhFMnhHVNtOnoSUG+2O7xpaT6cSNV4ySo/0stQ +85RrEu0skjzChI0D1Tb4o2qI2wYAyUQtUxwN46uqfo/NpzzBHujpizdQ9VJxfof5 +2xCrLZ2AzLAhhpiqUiNTu7PPC+fXh3T1ru4pOg6JZYc6Ok0R3Huu16S51wpK66LE +xgSkzGRET1kZFYg4G99g7jgmhhOyKOeh7J1LL5raOETX7hoPfO9M99tmRSVBuHFr +48e0SQpb/mf6yYEr5ndsX3uvONo+86rerWDigZhOyvIYY8OyMzof0h7UP1jOg0uG +JGj80rQDdqb48mTZW+d4ZC+1EstzBgDuJcG+Z/ufe2cymUwZUS6gpWuFddhab09a +ZeS4ojlg40Jv8LcBtX29tuFSk228YWWa10u7KOzVSc9MIIVYyfTNhUXyKr+tyAFg +jwVXWqKsDo53uwWClCwB7cvmep0sArR2hx/JdO2zAOrc0Myhx0XhwFdvV3lnNzZr +8hbXcwLtT/HGJVl3ivmiXyfWo2MZDlY7mAw5+84WaBqcmKe0+ugRFIOhvO9GR5cU +NysXNfEur7qRuEKqFU6BPePg8olc/qMF/3undrcOcPfyME1VG1mKkBJDFek15VxZ +URiLhKyQtDSR1BJKeicGiVPVVeLoqyQvslXihm3c7EPPnz+Q8fQiR4sUWbh2BgPv +Ckv0CP5a4RqiuV9B9pXqew2voJtRB1fU95JWIV08CLlcqLVArZFlxvUAVmQDF64Z +EW+2dvXr42+KwBWIteyblvlirVztknqoO3gyk3AvYe16uX9K1Mu+7xLUByg6Rv83 +duS2YUjm6xj/ogYD6wU0zUXQ5Lx0JhKzA+jktBVGbHV4IDxzb3BzQGZsdXhjZC5p +bz6JAc4EEwEIADgWIQS1na9GnoyUgTiQGmSXMgdeoiGn6gUCYkaquwIbAwULCQgH +AgYVCgkICwIEFgIDAQIeAQIXgAAKCRCXMgdeoiGn6ppqDACakBksOfI9xkcV2J4o +KkElDPzPMVlWeuulegHbS8R6srEJDxdR4jUpFVIlDp88xwMurpAZkwJdxzWLWj8N +GuW5Z0s3WuM1h17BYE/ZKGc4pBf7/A2OzDhS8IrqNuE7kNfupPgorVwbuNs5C8ho +w6MP7yqrxVOkWRcz1lx29FJIes+I46P+H/5rAv+4fiGWLj33wHhHpxTo4JWViYyl +8S3aKN0yrpNqzU/b/cQoEydsNks8cuHBh4QMjH+1sh3s0ery2Z5VPBBccxGe94Sp +vbh1fkhe2LIZKdfrh47WVPJVyUaUJC/JzCcINCNpGjpOxAIM8NxfUIF2k+SlgREN +TQztXAnHYWHcTSP8ojbN8vODka6LMgEtOo0WB/H7/NvBDuc09izP1HwdNM2dTkPu +plbmEdg9NkFgp+H8QgAxHGWVN22gzYaxJVO9PFrpF2D83Zch4zAYt/wYEWC/SK9A +cdbevVdetFPCCV5dSJCWq+e9llnJaxR4bDbTf3EQW7aulg2dBVgEYkaquwEMAMQV +snEOeBV1iX6hCiMWwgjnyS+ggIZN2F15NgM76VMLYMyOt7Y3nkBAFCZgEA9IymrC +UiPSk+YzDtebWgprqAgNgqovSl2c1xuacjuHgpG7DQiV20sb752BMMDDEUY05YyQ +a5NmCUqJIB2F0mxtIqbUpPgdHLSidgRX/5VjugKSlkD+JqURIpW6lmAaJ5RgbWbX +Puuy8yFsHtd75jp5fQFDSMxSG30ZBQAky+4vw8zTxbOLdXS3FrZr6YvLfmAMafoG +aZKAKEOxAZCp152JxUm6yvgXGIlgDDPLHyt5tpWwi98vw633NVE3MkKwic0HuSHE +PXemwZJi3z4yaBsofv7HCo9KtGx4I+t/cqEk8qri3dPqsEkiWKfN3FdzKndgd894 +GtFlO22y9/8pcjpeG3ErTq4rTmo3lkLnxbGxgENDoPEcJ8Q/xu+ZAdjqs5kRnivQ +57Qk0KCRU8HrzBDBWs13Sac8qbgR+Hvyq29UQJOQo0phKVOfb6oqym9ZsB8q7wAR +AQABAAv/VMgu2exQJrMl6o8V03slFXWmywWCXM+u1CezH23ZojMCvR+eNlbRAWXT +cI5Lk1g9UTDJFD0Z/sgnzDibE3Nd+XFiBFSjOlu0tHYwmyWp4nn2ljY5Vb3z+m2g +F1CgmPMJJ6BQKzDMpqIotSsmAwSjHXBHDhKEVWQDVDh6RW0TwcYA2oQpUGjaw9Oj +7lSQtXqGAxfhWEcNEe/uW+xx7OmXj6K4iMOdqBbXzyqZ1FhpuBf+3PVZKUh6tRBu +sCeh8kSbEOh4xpPFcs17EAcZfXTfdo9vtqjQkRUASuEzctR/91qI3c7IPMFilSHo +HdVQLyUiJTuY0k/00Je1QtPgh7lZtffkq6Bd11I53cfD+44l7g7Lcc2zFzuHjpyp +F+SDBs3tD8uKqbam8Hnop49/BRe1mgNdzobUEem39zKNSXWqUFemr15sAW2J4SKM +m657y0hdpGDE/QlD0ruE6sFFa8zk+92UElnzgyLl69YO139Sbjm+jSZfMVxm+nO6 +vrW16U75BgDIIH6PgZshXxMO1LzWxMP+d+6MWKpgeWYes/tkI6bfmM+LBh5/zzd2 +lV+9Zae/YJYMn30BrpWfE1uVcWVxlAilHYM92wtH8CjXIYkLhez5uTQQ7UKcLqyS +3nmO+u5ADqwPeaygS+gTTWhRFX0F5dTYhXFQABiK94lfyMJ8tzmjAuwWxPINLL0m +IdilK8crZayDarDBe2amiP/gG6W7zzIV8DvJn2ZZtlraFxTV8+mqq9Lh0cKJHT6/ +1/Lt90eubhcGAPrUTKv/jvMEZWlsw1HNzPfP+VyAtebmgDY0o2Rkpikaie/bsnAR +umPuRdGNMct5UAG8WLrgO/RIFb0dsJy1CA+zvZdluAHFaq89ikwFrvcvegc0325w +Iom8xiH0C9pLPiPcyFoFedQ3ZRaQB4oLhhikNDvD2ANA9HIY+k7dpfXP8LWY5jSs +UM3NdXC8RIdBW7DllfDNjWi/xaAyBDxcXRBPuWjIYWlLcHjmqablB/xdmZgIEJoU +VMPCRf7JAl3I6QX9Htkv4ocJyzRDxmhTuFdc7YOc3zmTqwx7/q+kuJDGROA1VeFT +HCtWrSF7Ax5WNIIRRFH1AEk8j//2yoycVCWKNMXV0d8xeHblyGVX+Huhq8rsokNU +MFbDY4wFDTzTK8F8fCa6Z0Q1nts6HOf5ZXv2xYMjyh93gJKF2/NhobX7noDMe/oR +CUzbd6Ogg3JLqnlrfIhR/Kh3yk+w/FhGRiQsV1rIqx4FrWvA3CTkr2zHTAH/gQvt +1CKrnh3iKiqJ9uV26yiJAbYEGAEIACAWIQS1na9GnoyUgTiQGmSXMgdeoiGn6gUC +YkaquwIbDAAKCRCXMgdeoiGn6jSVDACYkZWrhX/TM6bBVCGvhzl3EmwHqMuMT/Qx +N5Sc5QVawRD36+L/yuFYzK+MK9s9p5Z/9VmTsO/KQxcaPiuYub5vsJ38AxsaSiPE +VCtXY1QH0R3AYMh7tCGW+qhyf8IZyynkiOIZmo8PdrSwRnBCWGPvHYqJEr7c5LJD +0RYZFwR+ujPhr5mavERVziF2EfUor33la5vpax+CD+XLeMQaWorGegFN6wEpoGoQ +1rP10xtM+txU7/w0fkYHaEzvQfnRN4QVNg/EgQx7U+HyklAM36tGYgj2CRF5qm+K +Whv+ipymfmAngrjNMqcM15uXi1MF3UGFG7QkbKUBqpeK9UfG4lnZKHcSwhffcgL6 +clz1mGfriCEJvw9CfvlLm7RDM2m/MRxFr2yNQgpIJFoXgDVCthBCuH1dIMvhgCYA +frIIYzTK2ZKLJlTv3O8SCTf1Zhjru2f3z85YAqOXmQUGYKrQZL2T9NE2mQnXL0n+ +sgS+XwT2h+fdCBJHJYmboxXpxC02xHY= +=G8JF +-----END PGP PRIVATE KEY BLOCK----- diff --git a/pkg/controllers/internal/sops/keyservice/testdata/public.gpg b/pkg/controllers/internal/sops/keyservice/testdata/public.gpg new file mode 100644 index 000000000..c59ba01a6 --- /dev/null +++ b/pkg/controllers/internal/sops/keyservice/testdata/public.gpg @@ -0,0 +1,41 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQGNBGJGqrsBDAC7OxFP6Z2E+AkVZpQySjLFAeYJWdnadx0GOHnckOOFkQvVJauz +9KibgzLUkO9h0oIoP7dLyPEiRPhKgmbrktyCDfysvNeKCgI5XemJCJqCmwA/vWwp +GnVltcgsVVjVZ3vvD8VMfhKF77pkmMDj7mnCPw9x39R8SVpe2K9RO0QLk/Dt+o8t +MO+sXTz4ba4aMdjJvMoaoQKw6RXAouZa4H09i6tiAgXrRLxQDxJ58sGg/ZCWa5G4 +aI6PdObY41fzQlcobtifCbktbICVb1Ms1s0iZWttFmr0oTSkJTv3FPWhf6n126w4 +LEkF9d6YW+/0H9cqXa4GMfxXg4XBmJNJfYkLDVUlbp3xi+I+Lg1Sit6QlqkW93EW +etpYPK1KmDcW3IA6ausYnkyrcQbt1m5/hh9KJoQb6He/RytXEBxp90+v9y7THZGr +2U49ZEHQg6DAIj4j1p9NAGgqjKr9am6yk2pvpK3ZWmHQ6CZfCiBrEPCvdmEhrx4U +lj6wyd00YJknpDkAEQEAAbQVRmx1eCA8c29wc0BmbHV4Y2QuaW8+iQHOBBMBCAA4 +FiEEtZ2vRp6MlIE4kBpklzIHXqIhp+oFAmJGqrsCGwMFCwkIBwIGFQoJCAsCBBYC +AwECHgECF4AACgkQlzIHXqIhp+qaagwAmpAZLDnyPcZHFdieKCpBJQz8zzFZVnrr +pXoB20vEerKxCQ8XUeI1KRVSJQ6fPMcDLq6QGZMCXcc1i1o/DRrluWdLN1rjNYde +wWBP2ShnOKQX+/wNjsw4UvCK6jbhO5DX7qT4KK1cG7jbOQvIaMOjD+8qq8VTpFkX +M9ZcdvRSSHrPiOOj/h/+awL/uH4hli4998B4R6cU6OCVlYmMpfEt2ijdMq6Tas1P +2/3EKBMnbDZLPHLhwYeEDIx/tbId7NHq8tmeVTwQXHMRnveEqb24dX5IXtiyGSnX +64eO1lTyVclGlCQvycwnCDQjaRo6TsQCDPDcX1CBdpPkpYERDU0M7VwJx2Fh3E0j +/KI2zfLzg5GuizIBLTqNFgfx+/zbwQ7nNPYsz9R8HTTNnU5D7qZW5hHYPTZBYKfh +/EIAMRxllTdtoM2GsSVTvTxa6Rdg/N2XIeMwGLf8GBFgv0ivQHHW3r1XXrRTwgle +XUiQlqvnvZZZyWsUeGw2039xEFu2rpYNuQGNBGJGqrsBDADEFbJxDngVdYl+oQoj +FsII58kvoICGTdhdeTYDO+lTC2DMjre2N55AQBQmYBAPSMpqwlIj0pPmMw7Xm1oK +a6gIDYKqL0pdnNcbmnI7h4KRuw0IldtLG++dgTDAwxFGNOWMkGuTZglKiSAdhdJs +bSKm1KT4HRy0onYEV/+VY7oCkpZA/ialESKVupZgGieUYG1m1z7rsvMhbB7Xe+Y6 +eX0BQ0jMUht9GQUAJMvuL8PM08Wzi3V0txa2a+mLy35gDGn6BmmSgChDsQGQqded +icVJusr4FxiJYAwzyx8rebaVsIvfL8Ot9zVRNzJCsInNB7khxD13psGSYt8+Mmgb +KH7+xwqPSrRseCPrf3KhJPKq4t3T6rBJIlinzdxXcyp3YHfPeBrRZTttsvf/KXI6 +XhtxK06uK05qN5ZC58WxsYBDQ6DxHCfEP8bvmQHY6rOZEZ4r0Oe0JNCgkVPB68wQ +wVrNd0mnPKm4Efh78qtvVECTkKNKYSlTn2+qKspvWbAfKu8AEQEAAYkBtgQYAQgA +IBYhBLWdr0aejJSBOJAaZJcyB16iIafqBQJiRqq7AhsMAAoJEJcyB16iIafqNJUM +AJiRlauFf9MzpsFUIa+HOXcSbAeoy4xP9DE3lJzlBVrBEPfr4v/K4VjMr4wr2z2n +ln/1WZOw78pDFxo+K5i5vm+wnfwDGxpKI8RUK1djVAfRHcBgyHu0IZb6qHJ/whnL +KeSI4hmajw92tLBGcEJYY+8diokSvtzkskPRFhkXBH66M+GvmZq8RFXOIXYR9Siv +feVrm+lrH4IP5ct4xBpaisZ6AU3rASmgahDWs/XTG0z63FTv/DR+RgdoTO9B+dE3 +hBU2D8SBDHtT4fKSUAzfq0ZiCPYJEXmqb4paG/6KnKZ+YCeCuM0ypwzXm5eLUwXd +QYUbtCRspQGql4r1R8biWdkodxLCF99yAvpyXPWYZ+uIIQm/D0J++UubtEMzab8x +HEWvbI1CCkgkWheANUK2EEK4fV0gy+GAJgB+sghjNMrZkosmVO/c7xIJN/VmGOu7 +Z/fPzlgCo5eZBQZgqtBkvZP00TaZCdcvSf6yBL5fBPaH590IEkcliZujFenELTbE +dg== +=05GI +-----END PGP PUBLIC KEY BLOCK----- diff --git a/pkg/controllers/internal/sops/keyservice/utils_test.go b/pkg/controllers/internal/sops/keyservice/utils_test.go new file mode 100644 index 000000000..097dc5fbd --- /dev/null +++ b/pkg/controllers/internal/sops/keyservice/utils_test.go @@ -0,0 +1,105 @@ +// Copyright (C) 2022 The Flux authors +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +package keyservice + +import ( + "context" + "fmt" + "go.mozilla.org/sops/v3/kms" + + "go.mozilla.org/sops/v3/keys" + "go.mozilla.org/sops/v3/keyservice" + + "go.mozilla.org/sops/v3/age" + "go.mozilla.org/sops/v3/azkv" + "go.mozilla.org/sops/v3/gcpkms" + "go.mozilla.org/sops/v3/hcvault" + "go.mozilla.org/sops/v3/pgp" +) + +// KeyFromMasterKey converts a SOPS internal MasterKey to an RPC Key that can +// be serialized with Protocol Buffers. +func KeyFromMasterKey(k keys.MasterKey) keyservice.Key { + switch mk := k.(type) { + case *pgp.MasterKey: + return keyservice.Key{ + KeyType: &keyservice.Key_PgpKey{ + PgpKey: &keyservice.PgpKey{ + Fingerprint: mk.Fingerprint, + }, + }, + } + case *hcvault.MasterKey: + return keyservice.Key{ + KeyType: &keyservice.Key_VaultKey{ + VaultKey: &keyservice.VaultKey{ + VaultAddress: mk.VaultAddress, + EnginePath: mk.EnginePath, + KeyName: mk.KeyName, + }, + }, + } + case *kms.MasterKey: + return keyservice.Key{ + KeyType: &keyservice.Key_KmsKey{ + KmsKey: &keyservice.KmsKey{ + Arn: mk.Arn, + }, + }, + } + case *azkv.MasterKey: + return keyservice.Key{ + KeyType: &keyservice.Key_AzureKeyvaultKey{ + AzureKeyvaultKey: &keyservice.AzureKeyVaultKey{ + VaultUrl: mk.VaultURL, + Name: mk.Name, + Version: mk.Version, + }, + }, + } + case *age.MasterKey: + return keyservice.Key{ + KeyType: &keyservice.Key_AgeKey{ + AgeKey: &keyservice.AgeKey{ + Recipient: mk.Recipient, + }, + }, + } + case *gcpkms.MasterKey: + return keyservice.Key{ + KeyType: &keyservice.Key_GcpKmsKey{ + GcpKmsKey: &keyservice.GcpKmsKey{ + ResourceId: mk.ResourceID, + }, + }, + } + default: + panic(fmt.Sprintf("tried to convert unknown MasterKey type %T to keyservice.Key", mk)) + } +} + +type MockKeyServer struct { + encryptReqs []*keyservice.EncryptRequest + decryptReqs []*keyservice.DecryptRequest +} + +func NewMockKeyServer() *MockKeyServer { + return &MockKeyServer{ + encryptReqs: make([]*keyservice.EncryptRequest, 0), + decryptReqs: make([]*keyservice.DecryptRequest, 0), + } +} + +func (ks *MockKeyServer) Encrypt(_ context.Context, req *keyservice.EncryptRequest) (*keyservice.EncryptResponse, error) { + ks.encryptReqs = append(ks.encryptReqs, req) + return nil, fmt.Errorf("not actually implemented") +} + +func (ks *MockKeyServer) Decrypt(_ context.Context, req *keyservice.DecryptRequest) (*keyservice.DecryptResponse, error) { + ks.decryptReqs = append(ks.decryptReqs, req) + return nil, fmt.Errorf("not actually implemented") +} diff --git a/pkg/controllers/kluctl_project.go b/pkg/controllers/kluctl_project.go new file mode 100644 index 000000000..41396bd18 --- /dev/null +++ b/pkg/controllers/kluctl_project.go @@ -0,0 +1,820 @@ +package controllers + +import ( + "context" + "fmt" + internal_metrics "github.com/kluctl/kluctl/v2/pkg/controllers/internal/metrics" + "github.com/kluctl/kluctl/v2/pkg/controllers/internal/sops" + "github.com/kluctl/kluctl/v2/pkg/helm" + "github.com/kluctl/kluctl/v2/pkg/kluctl_jinja2" + "github.com/kluctl/kluctl/v2/pkg/repocache" + "github.com/kluctl/kluctl/v2/pkg/sops/decryptor" + "github.com/kluctl/kluctl/v2/pkg/types/result" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/prometheus/client_golang/prometheus" + "helm.sh/helm/v3/pkg/repo" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "os" + "path/filepath" + "strings" + "time" + + securejoin "github.com/cyphar/filepath-securejoin" + kluctlv1 "github.com/kluctl/kluctl/v2/api/v1beta1" + "github.com/kluctl/kluctl/v2/pkg/deployment" + "github.com/kluctl/kluctl/v2/pkg/deployment/commands" + k8s2 "github.com/kluctl/kluctl/v2/pkg/k8s" + "github.com/kluctl/kluctl/v2/pkg/kluctl_project" + "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/utils" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/tools/clientcmd/api" + ctrl "sigs.k8s.io/controller-runtime" +) + +type preparedProject struct { + r *KluctlDeploymentReconciler + obj *kluctlv1.KluctlDeployment + + startTime time.Time + + rp *repocache.GitRepoCache + + tmpDir string + repoDir string + projectDir string +} + +type preparedTarget struct { + pp *preparedProject +} + +func prepareProject(ctx context.Context, + r *KluctlDeploymentReconciler, + obj *kluctlv1.KluctlDeployment, + doCloneSource bool) (*preparedProject, error) { + + pp := &preparedProject{ + r: r, + obj: obj, + startTime: time.Now(), + } + + // create tmp dir + tmpDir, err := os.MkdirTemp("", obj.GetName()+"-") + if err != nil { + return pp, fmt.Errorf("failed to create temp dir for kluctl project: %w", err) + } + cleanup := true + defer func() { + if !cleanup { + return + } + pp.cleanup() + }() + + pp.tmpDir = tmpDir + + gitSecret, err := r.getGitSecret(ctx, &pp.obj.Spec.Source, obj.GetNamespace()) + if err != nil { + return nil, err + } + + pp.rp, err = r.buildRepoCache(ctx, gitSecret) + if err != nil { + return nil, err + } + + if doCloneSource { + rpEntry, err := pp.rp.GetEntry(pp.obj.Spec.Source.URL) + if err != nil { + return nil, fmt.Errorf("failed clone source: %w", err) + } + + clonedDir, _, err := rpEntry.GetClonedDir(pp.obj.Spec.Source.Ref.String()) + if err != nil { + return nil, err + } + + pp.repoDir = clonedDir + + // check kluctl project path exists + pp.projectDir, err = securejoin.SecureJoin(pp.repoDir, pp.obj.Spec.Source.Path) + if err != nil { + return pp, err + } + if _, err := os.Stat(pp.projectDir); err != nil { + return pp, fmt.Errorf("kluctlDeployment path not found: %w", err) + } + } + + cleanup = false + return pp, nil +} + +func (pp *preparedProject) cleanup() { + _ = os.RemoveAll(pp.tmpDir) + if pp.rp != nil { + pp.rp.Clear() + pp.rp = nil + } +} + +func (pp *preparedProject) newTarget() (*preparedTarget, error) { + pt := preparedTarget{ + pp: pp, + } + + return &pt, nil +} + +func (pt *preparedTarget) restConfigToKubeconfig(restConfig *rest.Config) *api.Config { + kubeConfig := api.NewConfig() + cluster := api.NewCluster() + cluster.Server = restConfig.Host + cluster.CertificateAuthority = restConfig.TLSClientConfig.CAFile + cluster.CertificateAuthorityData = restConfig.TLSClientConfig.CAData + cluster.InsecureSkipTLSVerify = restConfig.TLSClientConfig.Insecure + kubeConfig.Clusters["default"] = cluster + + user := api.NewAuthInfo() + user.ClientKey = restConfig.KeyFile + user.ClientKeyData = restConfig.KeyData + user.ClientCertificate = restConfig.CertFile + user.ClientCertificateData = restConfig.CertData + user.TokenFile = restConfig.BearerTokenFile + user.Token = restConfig.BearerToken + user.Impersonate = restConfig.Impersonate.UserName + user.ImpersonateUID = restConfig.Impersonate.UID + user.ImpersonateUserExtra = restConfig.Impersonate.Extra + user.ImpersonateGroups = restConfig.Impersonate.Groups + user.Username = restConfig.Username + user.Password = restConfig.Password + user.AuthProvider = restConfig.AuthProvider + user.Exec = restConfig.ExecProvider + kubeConfig.AuthInfos["default"] = user + + kctx := api.NewContext() + kctx.Cluster = "default" + kctx.AuthInfo = "default" + kubeConfig.Contexts["default"] = kctx + kubeConfig.CurrentContext = "default" + + return kubeConfig +} + +func (pt *preparedTarget) getKubeconfigFromSecret(ctx context.Context) ([]byte, error) { + secretName := types.NamespacedName{ + Namespace: pt.pp.obj.GetNamespace(), + Name: pt.pp.obj.Spec.KubeConfig.SecretRef.Name, + } + + var secret corev1.Secret + if err := pt.pp.r.Get(ctx, secretName, &secret); err != nil { + return nil, fmt.Errorf("unable to read KubeConfig secret '%s' error: %w", secretName.String(), err) + } + + var kubeConfig []byte + switch { + case pt.pp.obj.Spec.KubeConfig.SecretRef.Key != "": + key := pt.pp.obj.Spec.KubeConfig.SecretRef.Key + kubeConfig = secret.Data[key] + if kubeConfig == nil { + return nil, fmt.Errorf("KubeConfig secret '%s' does not contain a '%s' key with a kubeconfig", secretName, key) + } + case secret.Data["value"] != nil: + kubeConfig = secret.Data["value"] + case secret.Data["value.yaml"] != nil: + kubeConfig = secret.Data["value.yaml"] + default: + // User did not specify a key, and the 'value' key was not defined. + return nil, fmt.Errorf("KubeConfig secret '%s' does not contain a 'value' key with a kubeconfig", secretName) + } + + return kubeConfig, nil +} + +func (pt *preparedTarget) setImpersonationConfig(restConfig *rest.Config) { + name := pt.pp.r.DefaultServiceAccount + if sa := pt.pp.obj.Spec.ServiceAccountName; sa != "" { + name = sa + } + if name != "" { + username := fmt.Sprintf("system:serviceaccount:%s:%s", pt.pp.obj.GetNamespace(), name) + restConfig.Impersonate = rest.ImpersonationConfig{UserName: username} + } +} + +func (pt *preparedTarget) buildRestConfig(ctx context.Context) (*rest.Config, error) { + var restConfig *rest.Config + + if pt.pp.obj.Spec.KubeConfig != nil { + kubeConfig, err := pt.getKubeconfigFromSecret(ctx) + if err != nil { + return nil, err + } + restConfig, err = clientcmd.RESTConfigFromKubeConfig(kubeConfig) + if err != nil { + return nil, err + } + } else { + restConfig = rest.CopyConfig(pt.pp.r.RestConfig) + } + + pt.setImpersonationConfig(restConfig) + + return restConfig, nil +} + +func (pt *preparedTarget) buildKubeconfig(ctx context.Context) (*api.Config, error) { + restConfig, err := pt.buildRestConfig(ctx) + if err != nil { + return nil, err + } + + kubeConfig := pt.restConfigToKubeconfig(restConfig) + return kubeConfig, nil +} + +/* +func (pt *preparedTarget) getRegistrySecrets(ctx context.Context) ([]*corev1.Secret, error) { + var ret []*corev1.Secret + for _, ref := range pt.pp.obj.Spec.RegistrySecrets { + name := types.NamespacedName{ + Namespace: pt.pp.obj.GetNamespace(), + Name: ref.Name, + } + var secret corev1.Secret + if err := pt.pp.r.Get(ctx, name, &secret); err != nil { + return nil, fmt.Errorf("failed to get secret '%s': %w", name.String(), err) + } + ret = append(ret, &secret) + } + return ret, nil +} + +func (pt *preparedTarget) buildRegistryHelper(ctx context.Context) (*registries.RegistryHelper, error) { + secrets, err := pt.getRegistrySecrets(ctx) + if err != nil { + return nil, err + } + + rh := registries.NewRegistryHelper(ctx) + err = rh.ParseAuthEntriesFromEnv() + if err != nil { + return nil, err + } + + for _, s := range secrets { + caFile := s.Data["caFile"] + insecure := false + if x, ok := s.Data["insecure"]; ok { + insecure, err = strconv.ParseBool(string(x)) + if err != nil { + return nil, fmt.Errorf("failed parsing insecure flag from secret %s: %w", s.Name, err) + } + } + + if dockerConfig, ok := s.Data[".dockerconfigjson"]; ok { + maxFields := 1 + if _, ok := s.Data["caFile"]; ok { + maxFields++ + } + if _, ok := s.Data["insecure"]; ok { + maxFields++ + } + if len(s.Data) != maxFields { + return nil, fmt.Errorf("when using .dockerconfigjson in registry secret, only caFile and insecure fields are allowed additionally") + } + + c := configfile.New(".dockerconfigjson") + err = c.LoadFromReader(bytes.NewReader(dockerConfig)) + if err != nil { + return nil, fmt.Errorf("failed to parse .dockerconfigjson from secret %s: %w", s.Name, err) + } + for registry, ac := range c.GetAuthConfigs() { + var e registries.AuthEntry + e.Registry = registry + e.Username = ac.Username + e.Password = ac.Password + e.Auth = ac.Auth + e.CABundle = caFile + e.Insecure = insecure + + rh.AddAuthEntry(e) + } + } else { + var e registries.AuthEntry + e.Registry = string(s.Data["registry"]) + e.Username = string(s.Data["username"]) + e.Password = string(s.Data["password"]) + e.Auth = string(s.Data["auth"]) + e.CABundle = caFile + e.Insecure = insecure + + if e.Registry == "" || (e.Username == "" && e.Auth == "") { + return nil, fmt.Errorf("registry secret is incomplete") + } + rh.AddAuthEntry(e) + } + } + return rh, nil +} +*/ + +func (pt *preparedTarget) buildImages(ctx context.Context) (*deployment.Images, error) { + /*rh, err := pt.buildRegistryHelper(ctx) + if err != nil { + return nil, err + } + offline := !pt.pp.obj.Spec.UpdateImages + images, err := deployment.NewImages(rh, pt.pp.obj.Spec.UpdateImages, offline)*/ + + images, err := deployment.NewImages() + if err != nil { + return nil, err + } + for _, fi := range pt.pp.obj.Spec.Images { + images.AddFixedImage(fi) + } + return images, nil +} + +type helmCredentialsProvider []repo.Entry + +func (p helmCredentialsProvider) FindCredentials(repoUrl string, credentialsId *string) *repo.Entry { + if credentialsId != nil { + for _, e := range p { + if e.Name != "" && e.Name == *credentialsId { + return &e + } + } + } + if repoUrl == "" { + return nil + } + for _, e := range p { + if e.URL == repoUrl { + return &e + } + } + return nil +} + +func (pt *preparedTarget) buildHelmCredentials(ctx context.Context) (helm.HelmCredentialsProvider, error) { + var creds []repo.Entry + + tmpDirBase := filepath.Join(pt.pp.tmpDir, "helm-certs") + _ = os.MkdirAll(tmpDirBase, 0o700) + + var writeTmpFilErr error + writeTmpFile := func(secretData map[string][]byte, name string) string { + b, ok := secretData["certFile"] + if ok { + tmpFile, err := os.CreateTemp(tmpDirBase, name+"-") + if err != nil { + writeTmpFilErr = err + return "" + } + defer tmpFile.Close() + _, err = tmpFile.Write(b) + if err != nil { + writeTmpFilErr = err + } + return tmpFile.Name() + } + return "" + } + + for _, e := range pt.pp.obj.Spec.HelmCredentials { + var secret corev1.Secret + err := pt.pp.r.Client.Get(ctx, types.NamespacedName{ + Namespace: pt.pp.obj.GetNamespace(), + Name: e.SecretRef.Name, + }, &secret) + if err != nil { + return nil, err + } + + var entry repo.Entry + + credentialsId := string(secret.Data["credentialsId"]) + url := string(secret.Data["url"]) + if credentialsId == "" && url == "" { + return nil, fmt.Errorf("secret %s must at least contain 'credentialsId' or 'url'", e.SecretRef.Name) + } + entry.Name = credentialsId + entry.URL = url + entry.Username = string(secret.Data["username"]) + entry.Password = string(secret.Data["password"]) + entry.CertFile = writeTmpFile(secret.Data, "certFile") + entry.KeyFile = writeTmpFile(secret.Data, "keyFile") + entry.CAFile = writeTmpFile(secret.Data, "caFile") + if writeTmpFilErr != nil { + return nil, writeTmpFilErr + } + + b, _ := secret.Data["insecureSkipTlsVerify"] + s := string(b) + if utils.ParseBoolOrFalse(&s) { + entry.InsecureSkipTLSverify = true + } + b, _ = secret.Data["passCredentialsAll"] + s = string(b) + if utils.ParseBoolOrFalse(&s) { + entry.PassCredentialsAll = true + } + creds = append(creds, entry) + } + + return helmCredentialsProvider(creds), nil +} + +func (pt *preparedTarget) buildInclusion() *utils.Inclusion { + inc := utils.NewInclusion() + for _, x := range pt.pp.obj.Spec.IncludeTags { + inc.AddInclude("tag", x) + } + for _, x := range pt.pp.obj.Spec.ExcludeTags { + inc.AddExclude("tag", x) + } + for _, x := range pt.pp.obj.Spec.IncludeDeploymentDirs { + inc.AddInclude("deploymentItemDir", x) + } + for _, x := range pt.pp.obj.Spec.ExcludeDeploymentDirs { + inc.AddExclude("deploymentItemDir", x) + } + return inc +} + +func (pt *preparedTarget) clientConfigGetter(ctx context.Context) func(context *string) (*rest.Config, *api.Config, error) { + return func(context *string) (*rest.Config, *api.Config, error) { + kubeConfig, err := pt.buildKubeconfig(ctx) + if err != nil { + return nil, nil, err + } + + configOverrides := &clientcmd.ConfigOverrides{} + if context != nil { + configOverrides.CurrentContext = *context + } + clientConfig := clientcmd.NewDefaultClientConfig(*kubeConfig, configOverrides) + rawConfig, err := clientConfig.RawConfig() + if err != nil { + return nil, nil, err + } + if context != nil { + rawConfig.CurrentContext = *context + } + restConfig, err := clientConfig.ClientConfig() + if err != nil { + return nil, nil, err + } + return restConfig, &rawConfig, nil + } +} + +func (pp *preparedProject) buildSopsDecrypter(ctx context.Context) (*decryptor.Decryptor, error) { + if pp.obj.Spec.Decryption == nil { + return nil, nil + } + if pp.obj.Spec.Decryption.Provider != "sops" { + return nil, fmt.Errorf("not supported decryption provider %s", pp.obj.Spec.Decryption.Provider) + } + + d := decryptor.NewDecryptor(filepath.Join(pp.tmpDir, "project"), decryptor.MaxEncryptedFileSize) + + err := pp.addSecretBasedKeyServers(ctx, d) + if err != nil { + return nil, err + } + err = pp.addServiceAccountBasedKeyServers(ctx, d) + if err != nil { + return nil, err + } + return d, nil +} + +func (pp *preparedProject) addSecretBasedKeyServers(ctx context.Context, d *decryptor.Decryptor) error { + if pp.obj.Spec.Decryption.SecretRef == nil { + return nil + } + + secretName := types.NamespacedName{ + Namespace: pp.obj.GetNamespace(), + Name: pp.obj.Spec.Decryption.SecretRef.Name, + } + + var secret corev1.Secret + if err := pp.r.Get(ctx, secretName, &secret); err != nil { + if apierrors.IsNotFound(err) { + return err + } + return fmt.Errorf("cannot get decryption Secret '%s': %w", secretName, err) + } + + gnuPGHome := filepath.Join(pp.tmpDir, "sops-gnupghome") + err := os.MkdirAll(gnuPGHome, 0o700) + if err != nil { + return err + } + + ks, err := sops.BuildSopsKeyServerFromSecret(&secret, gnuPGHome) + if err != nil { + return err + } + if ks != nil { + d.AddKeyServiceClient(ks) + } + return nil +} + +func (pp *preparedProject) addServiceAccountBasedKeyServers(ctx context.Context, d *decryptor.Decryptor) error { + name := pp.r.DefaultServiceAccount + if pp.obj.Spec.Decryption != nil && pp.obj.Spec.Decryption.ServiceAccount != "" { + name = pp.obj.Spec.Decryption.ServiceAccount + } else if sa := pp.obj.Spec.ServiceAccountName; sa != "" { + name = sa + } + if name == "" { + return nil + } + sa, err := pp.r.ClientSet.CoreV1().ServiceAccounts(pp.obj.Namespace).Get(ctx, name, metav1.GetOptions{}) + if err != nil { + return fmt.Errorf("failed to retrieve service account %s: %w", name, err) + } + + ks, err := sops.BuildSopsKeyServerFromServiceAccount(ctx, pp.r.Client, sa) + if err != nil { + return err + } + if ks != nil { + d.AddKeyServiceClient(ks) + } + return nil +} + +func (pp *preparedProject) withKluctlProject(ctx context.Context, pt *preparedTarget, cb func(p *kluctl_project.LoadedKluctlProject) error) error { + j2, err := kluctl_jinja2.NewKluctlJinja2(true) + if err != nil { + return err + } + defer j2.Close() + + var sopsDecrypter *decryptor.Decryptor + if pp.obj.Spec.Decryption != nil { + sopsDecrypter, err = pp.buildSopsDecrypter(ctx) + if err != nil { + return err + } + } + + externalArgs, err := uo.FromString(string(pt.pp.obj.Spec.Args.Raw)) + if err != nil { + return err + } + + loadArgs := kluctl_project.LoadKluctlProjectArgs{ + RepoRoot: pp.repoDir, + ExternalArgs: externalArgs, + ProjectDir: pp.projectDir, + RP: pp.rp, + SopsDecrypter: sopsDecrypter, + } + if pt != nil { + loadArgs.ClientConfigGetter = pt.clientConfigGetter(ctx) + } + + p, err := kluctl_project.LoadKluctlProject(ctx, loadArgs, filepath.Join(pp.tmpDir, "project"), j2) + if err != nil { + return err + } + + return cb(p) +} + +func (pt *preparedTarget) withKluctlProjectTarget(ctx context.Context, cb func(targetContext *kluctl_project.TargetContext) error) error { + return pt.pp.withKluctlProject(ctx, pt, func(p *kluctl_project.LoadedKluctlProject) error { + renderOutputDir, err := os.MkdirTemp(pt.pp.tmpDir, "render-") + if err != nil { + return err + } + images, err := pt.buildImages(ctx) + if err != nil { + return err + } + helmCredentials, err := pt.buildHelmCredentials(ctx) + if err != nil { + return err + } + inclusion := pt.buildInclusion() + + props := kluctl_project.TargetContextParams{ + DryRun: pt.pp.r.DryRun || pt.pp.obj.Spec.DryRun, + Images: images, + Inclusion: inclusion, + HelmCredentials: helmCredentials, + RenderOutputDir: renderOutputDir, + } + if pt.pp.obj.Spec.Target != nil { + props.TargetName = *pt.pp.obj.Spec.Target + } + if pt.pp.obj.Spec.TargetNameOverride != nil { + props.TargetNameOverride = *pt.pp.obj.Spec.TargetNameOverride + } + if pt.pp.obj.Spec.Context != nil { + props.ContextOverride = *pt.pp.obj.Spec.Context + } + targetContext, err := p.NewTargetContext(ctx, props) + if err != nil { + return err + } + err = targetContext.DeploymentCollection.Prepare() + if err != nil { + return err + } + return cb(targetContext) + }) +} + +func (pt *preparedTarget) handleCommandResult(ctx context.Context, cmdErr error, cmdResult *result.CommandResult, commandName string) error { + log := ctrl.LoggerFrom(ctx) + + cmdResult.Command.Initiator = result.CommandInititiator_KluctlDeployment + cmdResult.GitInfo.Url = &pt.pp.obj.Spec.Source.URL + cmdResult.GitInfo.Ref = pt.pp.obj.Spec.Source.Ref.String() + cmdResult.ProjectKey.NormalizedGitUrl = pt.pp.obj.Spec.Source.URL.NormalizedRepoKey() + + summary := cmdResult.BuildSummary() + + log.Info(fmt.Sprintf("command finished with err=%v", cmdErr)) + defer pt.exportCommandResultMetricsToProm(summary) + if cmdErr != nil { + pt.pp.r.event(ctx, pt.pp.obj, true, fmt.Sprintf("%s failed. %s", commandName, cmdErr.Error()), nil) + return cmdErr + } + + msg := fmt.Sprintf("%s succeeded.", commandName) + if summary.NewObjects != 0 { + msg += fmt.Sprintf(" %d new objects.", summary.NewObjects) + } + if summary.ChangedObjects != 0 { + msg += fmt.Sprintf(" %d changed objects.", summary.ChangedObjects) + } + if summary.AppliedHookObjects != 0 { + msg += fmt.Sprintf(" %d hooks run.", summary.AppliedHookObjects) + } + if summary.DeletedObjects != 0 { + msg += fmt.Sprintf(" %d deleted objects.", summary.DeletedObjects) + } + if summary.OrphanObjects != 0 { + msg += fmt.Sprintf(" %d orphan objects.", summary.OrphanObjects) + } + if len(cmdResult.Errors) != 0 { + msg += fmt.Sprintf(" %d errors.", len(cmdResult.Errors)) + } + if len(cmdResult.Warnings) != 0 { + msg += fmt.Sprintf(" %d warnings.", len(cmdResult.Warnings)) + } + + warning := false + var err error + if len(cmdResult.Errors) != 0 { + warning = true + err = fmt.Errorf("%s failed with %d errors", commandName, len(cmdResult.Errors)) + } + pt.pp.r.event(ctx, pt.pp.obj, warning, msg, nil) + + return err +} + +func (pt *preparedTarget) kluctlDeploy(ctx context.Context, targetContext *kluctl_project.TargetContext) (*result.CommandResult, error) { + timer := prometheus.NewTimer(internal_metrics.NewKluctlDeploymentDuration(pt.pp.obj.ObjectMeta.Namespace, pt.pp.obj.ObjectMeta.Name, pt.pp.obj.Spec.DeployMode)) + defer timer.ObserveDuration() + cmd := commands.NewDeployCommand(targetContext) + cmd.ForceApply = pt.pp.obj.Spec.ForceApply + cmd.ReplaceOnError = pt.pp.obj.Spec.ReplaceOnError + cmd.ForceReplaceOnError = pt.pp.obj.Spec.ForceReplaceOnError + cmd.AbortOnError = pt.pp.obj.Spec.AbortOnError + cmd.ReadinessTimeout = time.Minute * 10 + cmd.NoWait = pt.pp.obj.Spec.NoWait + + cmdResult, err := cmd.Run(nil) + err = pt.handleCommandResult(ctx, err, cmdResult, "deploy") + return cmdResult, err +} + +func (pt *preparedTarget) kluctlPokeImages(ctx context.Context, targetContext *kluctl_project.TargetContext) (*result.CommandResult, error) { + timer := prometheus.NewTimer(internal_metrics.NewKluctlDeploymentDuration(pt.pp.obj.ObjectMeta.Namespace, pt.pp.obj.ObjectMeta.Name, pt.pp.obj.Spec.DeployMode)) + defer timer.ObserveDuration() + cmd := commands.NewPokeImagesCommand(targetContext) + + cmdResult, err := cmd.Run() + err = pt.handleCommandResult(ctx, err, cmdResult, "poke-images") + return cmdResult, err +} + +func (pt *preparedTarget) kluctlPrune(ctx context.Context, targetContext *kluctl_project.TargetContext) (*result.CommandResult, error) { + if !pt.pp.obj.Spec.Prune { + return nil, nil + } + + timer := prometheus.NewTimer(internal_metrics.NewKluctlPruneDuration(pt.pp.obj.ObjectMeta.Namespace, pt.pp.obj.ObjectMeta.Name)) + defer timer.ObserveDuration() + + cmd := commands.NewPruneCommand("", targetContext) + cmdResult, err := cmd.Run(func(refs []k8s.ObjectRef) error { + pt.printDeletedRefs(ctx, refs) + return nil + }) + if err != nil { + return nil, err + } + err = pt.handleCommandResult(ctx, err, cmdResult, "prune") + return cmdResult, err +} + +func (pt *preparedTarget) kluctlValidate(ctx context.Context, targetContext *kluctl_project.TargetContext, cmdResult *result.CommandResult) (*result.ValidateResult, error) { + timer := prometheus.NewTimer(internal_metrics.NewKluctlValidateDuration(pt.pp.obj.ObjectMeta.Namespace, pt.pp.obj.ObjectMeta.Name)) + defer timer.ObserveDuration() + + c := targetContext.DeploymentCollection + if cmdResult != nil { + c = nil + } + cmd := commands.NewValidateCommand(ctx, targetContext.Target.Discriminator, c, cmdResult) + + validateResult, err := cmd.Run(ctx, targetContext.SharedContext.K) + return validateResult, err +} + +func (pt *preparedTarget) kluctlDelete(ctx context.Context, discriminator string) (*result.CommandResult, error) { + if !pt.pp.obj.Spec.Delete { + return nil, nil + } + + timer := prometheus.NewTimer(internal_metrics.NewKluctlDeleteDuration(pt.pp.obj.ObjectMeta.Namespace, pt.pp.obj.ObjectMeta.Name)) + defer timer.ObserveDuration() + + inclusion := pt.buildInclusion() + + cmd := commands.NewDeleteCommand(discriminator, nil, inclusion) + + restConfig, err := pt.buildRestConfig(ctx) + if err != nil { + return nil, err + } + clientFactory, err := k8s2.NewClientFactory(ctx, restConfig) + if err != nil { + return nil, err + } + k, err := k8s2.NewK8sCluster(ctx, clientFactory, pt.pp.r.DryRun || pt.pp.obj.Spec.DryRun) + if err != nil { + return nil, err + } + + cmdResult, err := cmd.Run(ctx, k, func(refs []k8s.ObjectRef) error { + pt.printDeletedRefs(ctx, refs) + return nil + }) + if err != nil { + return nil, err + } + + if pt.pp.obj.Spec.Target != nil { + cmdResult.TargetKey.TargetName = *pt.pp.obj.Spec.Target + } + if pt.pp.obj.Spec.TargetNameOverride != nil { + cmdResult.TargetKey.TargetName = *pt.pp.obj.Spec.TargetNameOverride + } + + cmdResult.TargetKey.Discriminator = discriminator + cmdResult.TargetKey.ClusterId = cmdResult.ClusterInfo.ClusterId + + err = pt.handleCommandResult(ctx, err, cmdResult, "delete") + return cmdResult, err +} + +func (pt *preparedTarget) printDeletedRefs(ctx context.Context, refs []k8s.ObjectRef) { + log := ctrl.LoggerFrom(ctx) + + var refStrs []string + for _, ref := range refs { + refStrs = append(refStrs, ref.String()) + } + if len(refStrs) != 0 { + log.Info(fmt.Sprintf("deleting (without waiting): %s", strings.Join(refStrs, ", "))) + } +} + +func (pt *preparedTarget) exportCommandResultMetricsToProm(summary *result.CommandResultSummary) { + internal_metrics.NewKluctlNumberOfDeletedObjects(pt.pp.obj.Namespace, pt.pp.obj.Name).Set(float64(summary.DeletedObjects)) + internal_metrics.NewKluctlNumberOfChangedObjects(pt.pp.obj.Namespace, pt.pp.obj.Name).Set(float64(summary.ChangedObjects)) + internal_metrics.NewKluctlNumberOfOrphanObjects(pt.pp.obj.Namespace, pt.pp.obj.Name).Set(float64(summary.OrphanObjects)) + internal_metrics.NewKluctlNumberOfWarnings(pt.pp.obj.Namespace, pt.pp.obj.Name, summary.Command.Command).Set(float64(summary.Warnings)) + internal_metrics.NewKluctlNumberOfErrors(pt.pp.obj.Namespace, pt.pp.obj.Name, summary.Command.Command).Set(float64(summary.Errors)) +} diff --git a/pkg/controllers/kluctldeployment_controller.go b/pkg/controllers/kluctldeployment_controller.go new file mode 100644 index 000000000..69670165a --- /dev/null +++ b/pkg/controllers/kluctldeployment_controller.go @@ -0,0 +1,506 @@ +package controllers + +import ( + "context" + "fmt" + "github.com/hashicorp/go-retryablehttp" + kluctlv1 "github.com/kluctl/kluctl/v2/api/v1beta1" + internal_metrics "github.com/kluctl/kluctl/v2/pkg/controllers/internal/metrics" + ssh_pool "github.com/kluctl/kluctl/v2/pkg/git/ssh-pool" + "github.com/kluctl/kluctl/v2/pkg/kluctl_project" + "github.com/kluctl/kluctl/v2/pkg/status" + "github.com/kluctl/kluctl/v2/pkg/types/result" + "github.com/kluctl/kluctl/v2/pkg/utils/flux_utils/meta" + "github.com/kluctl/kluctl/v2/pkg/utils/flux_utils/metrics" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + kuberecorder "k8s.io/client-go/tools/record" + "k8s.io/client-go/tools/reference" + "path" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "time" +) + +type KluctlDeploymentReconciler struct { + client.Client + RestConfig *rest.Config + ClientSet *kubernetes.Clientset + httpClient *retryablehttp.Client + requeueDependency time.Duration + Scheme *runtime.Scheme + EventRecorder kuberecorder.EventRecorder + MetricsRecorder *metrics.Recorder + ControllerName string + DefaultServiceAccount string + DryRun bool + + SshPool *ssh_pool.SshPool +} + +// KluctlDeploymentReconcilerOpts contains options for the BaseReconciler. +type KluctlDeploymentReconcilerOpts struct { + HTTPRetry int +} + +// +kubebuilder:rbac:groups=gitops.kluctl.io,resources=kluctldeployments,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=gitops.kluctl.io,resources=kluctldeployments/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=gitops.kluctl.io,resources=kluctldeployments/finalizers,verbs=get;create;update;patch;delete +// +kubebuilder:rbac:groups="",resources=configmaps;secrets;serviceaccounts,verbs=get;list;watch +// +kubebuilder:rbac:groups="",resources=events,verbs=create;patch + +func (r *KluctlDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := ctrl.LoggerFrom(ctx) + reconcileStart := time.Now() + + ctx = status.NewContext(ctx, status.NewSimpleStatusHandler(func(message string) { + log.Info(message) + }, false, false)) + + obj := &kluctlv1.KluctlDeployment{} + if err := r.Get(ctx, req.NamespacedName, obj); err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, r.calcTimeout(obj)) + defer cancel() + + retryInterval := obj.Spec.GetRetryInterval() + interval := obj.Spec.Interval.Duration + + // Record suspended status metric + defer r.recordSuspension(ctx, obj) + + // Add our finalizer if it does not exist + if !controllerutil.ContainsFinalizer(obj, kluctlv1.KluctlDeploymentFinalizer) { + patch := client.MergeFrom(obj.DeepCopy()) + controllerutil.AddFinalizer(obj, kluctlv1.KluctlDeploymentFinalizer) + if err := r.Patch(ctx, obj, patch, client.FieldOwner(r.ControllerName)); err != nil { + log.Error(err, "unable to register finalizer") + return ctrl.Result{}, err + } + } + + // Examine if the object is under deletion + if !obj.GetDeletionTimestamp().IsZero() { + return r.finalize(ctx, obj) + } + + // Return early if the KluctlDeployment is suspended. + if obj.Spec.Suspend { + log.Info("Reconciliation is suspended for this object") + return ctrl.Result{}, nil + } + + // record reconciliation duration + if r.MetricsRecorder != nil { + objRef, err := reference.GetReference(r.Scheme, obj) + if err != nil { + return ctrl.Result{}, err + } + defer r.MetricsRecorder.RecordDuration(*objRef, reconcileStart) + } + + // set the reconciliation status to progressing + if obj.Status.ObservedGeneration == 0 { + patch := client.MergeFrom(obj.DeepCopy()) + setReadiness(obj, metav1.ConditionUnknown, meta.ProgressingReason, "reconciliation in progress") + if err := r.Status().Patch(ctx, obj, patch, client.FieldOwner(r.ControllerName)); err != nil { + return ctrl.Result{Requeue: true}, err + } + + r.recordReadiness(ctx, obj) + } + + // record the value of the reconciliation request, if any + if v, ok := obj.GetAnnotations()[kluctlv1.KluctlRequestReconcileAnnotation]; ok { + obj.Status.LastHandledReconcileAt = v + } + + // reconcile kluctlDeployment by applying the latest revision + patch := client.MergeFrom(obj.DeepCopy()) + ctrlResult, reconcileErr := r.doReconcile(ctx, obj) + if err := r.Status().Patch(ctx, obj, patch, client.FieldOwner(r.ControllerName)); err != nil { + return ctrl.Result{Requeue: true}, err + } + + r.recordReadiness(ctx, obj) + + if ctrlResult == nil { + if reconcileErr != nil { + ctrlResult = &ctrl.Result{RequeueAfter: retryInterval} + } else { + ctrlResult = &ctrl.Result{RequeueAfter: interval} + } + } + + // broadcast the reconciliation failure and requeue at the specified retry interval + if reconcileErr != nil { + log.Info(fmt.Sprintf("Reconciliation failed after %s, next try in %s: %s", + time.Since(reconcileStart).String(), + ctrlResult.RequeueAfter.String(), reconcileErr.Error(), + )) + r.event(ctx, obj, true, + reconcileErr.Error(), nil) + return *ctrlResult, nil + } + + // broadcast the reconciliation result and requeue at the specified interval + msg := fmt.Sprintf("Reconciliation finished in %s, next run in %s", + time.Since(reconcileStart).String(), + ctrlResult.RequeueAfter.String()) + log.Info(msg) + r.event(ctx, obj, true, + msg, map[string]string{kluctlv1.GroupVersion.Group + "/commit_status": "update"}) + return *ctrlResult, nil +} + +func (r *KluctlDeploymentReconciler) doReconcile( + ctx context.Context, + obj *kluctlv1.KluctlDeployment) (*ctrl.Result, error) { + + r.exportDeploymentObjectToProm(obj) + + pp, err := prepareProject(ctx, r, obj, true) + if err != nil { + setReadiness(obj, metav1.ConditionFalse, kluctlv1.PrepareFailedReason, err.Error()) + return nil, err + } + defer pp.cleanup() + + pt, err := pp.newTarget() + if err != nil { + setReadiness(obj, metav1.ConditionFalse, kluctlv1.PrepareFailedReason, err.Error()) + return nil, err + } + + err = pt.withKluctlProjectTarget(ctx, func(targetContext *kluctl_project.TargetContext) error { + obj.Status.ProjectKey = &result.ProjectKey{ + NormalizedGitUrl: obj.Spec.Source.URL.NormalizedRepoKey(), + SubDir: path.Clean(obj.Spec.Source.Path), + } + obj.Status.TargetKey = &result.TargetKey{ + TargetName: targetContext.Target.Name, + Discriminator: targetContext.Target.Discriminator, + } + + if obj.Status.ProjectKey.SubDir == "." { + obj.Status.ProjectKey.SubDir = "" + } + + objectsHash, err := targetContext.DeploymentCollection.CalcObjectsHash() + if err != nil { + return err + } + + needDeploy := false + needPrune := false + needValidate := false + + if obj.Status.LastDeployResult == nil || obj.Status.LastObjectsHash != objectsHash { + // either never deployed or source code changed + needDeploy = true + } else if r.checkRequestedDeploy(obj) { + // explicitly requested a deploy + needDeploy = true + } else if obj.Status.ObservedGeneration != obj.GetGeneration() { + // spec has changed + needDeploy = true + } else { + // was deployed before, let's check if we need to do periodic deployments + nextDeployTime := r.nextDeployTime(obj) + if nextDeployTime != nil { + needDeploy = nextDeployTime.Before(time.Now()) + } + } + + if obj.Spec.Validate { + if obj.Status.LastValidateResult == nil || needDeploy { + // either never validated before or a deployment requested (which required re-validation) + needValidate = true + } else { + nextValidateTime := r.nextValidateTime(obj) + if nextValidateTime != nil { + needValidate = nextValidateTime.Before(time.Now()) + } + } + } else { + obj.Status.LastValidateResult = nil + } + + if obj.Spec.Prune { + needPrune = needDeploy + } else { + obj.Status.LastPruneResult = nil + } + + obj.Status.LastObjectsHash = objectsHash + + var deployResult *result.CommandResult + if needDeploy { + // deploy the kluctl project + if obj.Spec.DeployMode == kluctlv1.KluctlDeployModeFull { + deployResult, err = pt.kluctlDeploy(ctx, targetContext) + } else if obj.Spec.DeployMode == kluctlv1.KluctlDeployPokeImages { + deployResult, err = pt.kluctlPokeImages(ctx, targetContext) + } else { + err = fmt.Errorf("deployMode '%s' not supported", obj.Spec.DeployMode) + setReadiness(obj, metav1.ConditionFalse, kluctlv1.DeployFailedReason, err.Error()) + return nil + } + obj.Status.LastDeployResult = deployResult.BuildSummary() + if err != nil { + setReadiness(obj, metav1.ConditionFalse, kluctlv1.DeployFailedReason, err.Error()) + return nil + } + } + + if needPrune { + // run garbage collection for stale objects that do not have pruning disabled + pruneResult, err := pt.kluctlPrune(ctx, targetContext) + obj.Status.LastPruneResult = pruneResult.BuildSummary() + if err != nil { + setReadiness(obj, metav1.ConditionFalse, kluctlv1.PruneFailedReason, err.Error()) + return nil + } + } + + if needValidate { + validateResult, err := pt.kluctlValidate(ctx, targetContext, deployResult) + obj.Status.LastValidateResult = validateResult + if err != nil { + setReadiness(obj, metav1.ConditionFalse, kluctlv1.ValidateFailedReason, err.Error()) + return nil + } + } + return nil + }) + obj.Status.ObservedGeneration = obj.GetGeneration() + if v, ok := obj.GetAnnotations()[kluctlv1.KluctlRequestDeployAnnotation]; ok { + obj.Status.LastHandledDeployAt = v + } + if err != nil { + setReadiness(obj, metav1.ConditionFalse, kluctlv1.PrepareFailedReason, err.Error()) + return nil, err + } + + var ctrlResult ctrl.Result + ctrlResult.RequeueAfter = r.nextReconcileTime(obj).Sub(time.Now()) + if ctrlResult.RequeueAfter < 0 { + ctrlResult.RequeueAfter = 0 + ctrlResult.Requeue = true + } + + finalStatus, reason := r.buildFinalStatus(obj) + if reason != kluctlv1.ReconciliationSucceededReason { + setReadiness(obj, metav1.ConditionFalse, reason, finalStatus) + internal_metrics.NewKluctlLastObjectStatus(obj.Namespace, obj.Name).Set(0.0) + return &ctrlResult, fmt.Errorf(finalStatus) + } + setReadiness(obj, metav1.ConditionTrue, reason, finalStatus) + internal_metrics.NewKluctlLastObjectStatus(obj.Namespace, obj.Name).Set(1.0) + return &ctrlResult, nil +} + +func (r *KluctlDeploymentReconciler) buildFinalStatus(obj *kluctlv1.KluctlDeployment) (finalStatus string, reason string) { + deployOk := obj.Status.LastDeployResult != nil && obj.Status.LastDeployResult.Errors == 0 + pruneOk := obj.Status.LastPruneResult != nil && obj.Status.LastPruneResult.Errors == 0 + validateOk := obj.Status.LastValidateResult != nil && len(obj.Status.LastValidateResult.Errors) == 0 && obj.Status.LastValidateResult.Ready + + if !obj.Spec.Prune { + pruneOk = true + } + if !obj.Spec.Validate { + validateOk = true + } + + if obj.Status.LastDeployResult != nil { + finalStatus += "deploy: " + if deployOk { + finalStatus += "ok" + } else { + finalStatus += "failed" + } + } + if obj.Spec.Prune && obj.Status.LastPruneResult != nil { + if finalStatus != "" { + finalStatus += ", " + } + finalStatus += "prune: " + if pruneOk { + finalStatus += "ok" + } else { + finalStatus += "failed" + } + } + if obj.Spec.Validate && obj.Status.LastValidateResult != nil { + if finalStatus != "" { + finalStatus += ", " + } + finalStatus += "validate: " + if validateOk { + finalStatus += "ok" + } else { + finalStatus += "failed" + } + } + + if deployOk && pruneOk { + if validateOk { + reason = kluctlv1.ReconciliationSucceededReason + } else { + reason = kluctlv1.ValidateFailedReason + return + } + } else { + reason = kluctlv1.DeployFailedReason + return + } + return +} + +func (r *KluctlDeploymentReconciler) calcTimeout(obj *kluctlv1.KluctlDeployment) time.Duration { + var d time.Duration + if obj.Spec.Timeout != nil { + d = obj.Spec.Timeout.Duration + } else if obj.Spec.DeployInterval != nil { + d = obj.Spec.DeployInterval.Duration.Duration + } else { + d = obj.Spec.Interval.Duration + } + if d < time.Second*30 { + d = time.Second * 30 + } + return d +} + +func (r *KluctlDeploymentReconciler) nextReconcileTime(obj *kluctlv1.KluctlDeployment) time.Time { + t1 := time.Now().Add(obj.Spec.Interval.Duration) + t2 := r.nextDeployTime(obj) + t3 := r.nextValidateTime(obj) + if t2 != nil && t2.Before(t1) { + t1 = *t2 + } + if obj.Spec.Validate && t3 != nil && t3.Before(t1) { + t1 = *t3 + } + return t1 +} + +func (r *KluctlDeploymentReconciler) nextDeployTime(obj *kluctlv1.KluctlDeployment) *time.Time { + if obj.Status.LastDeployResult == nil { + // was never deployed before. Return early. + return nil + } + if obj.Spec.DeployInterval == nil { + // periodic deployments disabled + return nil + } + + t := obj.Status.LastDeployResult.Command.EndTime.Add(obj.Spec.DeployInterval.Duration.Duration) + return &t +} + +func (r *KluctlDeploymentReconciler) checkRequestedDeploy(obj *kluctlv1.KluctlDeployment) bool { + v, ok := obj.Annotations[kluctlv1.KluctlRequestDeployAnnotation] + if !ok { + return false + } + if v != obj.Status.LastHandledDeployAt { + return true + } + return false +} + +func (r *KluctlDeploymentReconciler) nextValidateTime(obj *kluctlv1.KluctlDeployment) *time.Time { + if obj.Status.LastValidateResult == nil { + // was never validated before. Return early. + return nil + } + d := obj.Spec.Interval.Duration + if obj.Spec.ValidateInterval != nil { + d = obj.Spec.ValidateInterval.Duration.Duration + } + + t := obj.Status.LastValidateResult.EndTime.Add(d) + return &t +} + +func (r *KluctlDeploymentReconciler) finalize(ctx context.Context, obj *kluctlv1.KluctlDeployment) (ctrl.Result, error) { + r.doFinalize(ctx, obj) + + // Record deleted status + r.recordReadiness(ctx, obj) + + // Remove our finalizer from the list and update it + patch := client.MergeFrom(obj.DeepCopy()) + controllerutil.RemoveFinalizer(obj, kluctlv1.KluctlDeploymentFinalizer) + if err := r.Patch(ctx, obj, patch, client.FieldOwner(r.ControllerName)); err != nil { + return ctrl.Result{}, err + } + + // Stop reconciliation as the object is being deleted + return ctrl.Result{}, nil +} + +func (r *KluctlDeploymentReconciler) doFinalize(ctx context.Context, obj *kluctlv1.KluctlDeployment) { + log := ctrl.LoggerFrom(ctx) + + if !obj.Spec.Delete || obj.Spec.Suspend { + return + } + + if obj.Status.ProjectKey == nil || obj.Status.TargetKey == nil { + log.V(1).Info("No project/target key set, skipping deletion") + return + } + + log.V(1).Info("Deleting target") + + pp, err := prepareProject(ctx, r, obj, false) + if err != nil { + return + } + defer pp.cleanup() + + pt, err := pp.newTarget() + if err != nil { + return + } + + _, _ = pt.kluctlDelete(ctx, obj.Status.TargetKey.Discriminator) +} + +func (r *KluctlDeploymentReconciler) exportDeploymentObjectToProm(obj *kluctlv1.KluctlDeployment) { + pruneEnabled := 0.0 + deleteEnabled := 0.0 + dryRunEnabled := 0.0 + deploymentInterval := 0.0 + + if obj.Spec.Prune { + pruneEnabled = 1.0 + } + if obj.Spec.Delete { + deleteEnabled = 1.0 + } + if obj.Spec.DryRun { + dryRunEnabled = 1.0 + } + //If not set, it defaults to interval + if obj.Spec.DeployInterval == nil { + deploymentInterval = obj.Spec.Interval.Seconds() + } else { + deploymentInterval = obj.Spec.DeployInterval.Duration.Seconds() + } + + //Export as Prometheus metric + internal_metrics.NewKluctlPruneEnabled(obj.Namespace, obj.Name).Set(pruneEnabled) + internal_metrics.NewKluctlDeleteEnabled(obj.Namespace, obj.Name).Set(deleteEnabled) + internal_metrics.NewKluctlDryRunEnabled(obj.Namespace, obj.Name).Set(dryRunEnabled) + internal_metrics.NewKluctlDeploymentInterval(obj.Namespace, obj.Name).Set(deploymentInterval) + internal_metrics.NewKluctlSourceSpec(obj.Namespace, obj.Name, obj.Spec.Source.URL.String(), obj.Spec.Source.Path, obj.Spec.Source.Ref.String()).Set(0.0) +} diff --git a/pkg/controllers/kluctldeployment_controller_setup.go b/pkg/controllers/kluctldeployment_controller_setup.go new file mode 100644 index 000000000..cb6af53f7 --- /dev/null +++ b/pkg/controllers/kluctldeployment_controller_setup.go @@ -0,0 +1,28 @@ +package controllers + +import ( + "github.com/hashicorp/go-retryablehttp" + kluctlv1 "github.com/kluctl/kluctl/v2/api/v1beta1" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "time" +) + +// SetupWithManager sets up the controller with the Manager. +func (r *KluctlDeploymentReconciler) SetupWithManager(mgr ctrl.Manager, opts KluctlDeploymentReconcilerOpts) error { + // Configure the retryable http client used for fetching artifacts. + // By default it retries 10 times within a 3.5 minutes window. + httpClient := retryablehttp.NewClient() + httpClient.RetryWaitMin = 5 * time.Second + httpClient.RetryWaitMax = 30 * time.Second + httpClient.RetryMax = opts.HTTPRetry + httpClient.Logger = nil + r.httpClient = httpClient + + return ctrl.NewControllerManagedBy(mgr). + For(&kluctlv1.KluctlDeployment{}, builder.WithPredicates( + predicate.Or(predicate.GenerationChangedPredicate{}, ReconcileRequestedPredicate{}, DeployRequestedPredicate{}), + )). + Complete(r) +} diff --git a/pkg/controllers/kluctldeployment_controller_source.go b/pkg/controllers/kluctldeployment_controller_source.go new file mode 100644 index 000000000..d206217b4 --- /dev/null +++ b/pkg/controllers/kluctldeployment_controller_source.go @@ -0,0 +1,109 @@ +package controllers + +import ( + "context" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + kluctlv1 "github.com/kluctl/kluctl/v2/api/v1beta1" + "github.com/kluctl/kluctl/v2/pkg/git/auth" + "github.com/kluctl/kluctl/v2/pkg/git/messages" + "github.com/kluctl/kluctl/v2/pkg/repocache" + "github.com/kluctl/kluctl/v2/pkg/utils" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "os" + "path/filepath" + ctrl "sigs.k8s.io/controller-runtime" +) + +func (r *KluctlDeploymentReconciler) getGitSecret(ctx context.Context, source *kluctlv1.ProjectSource, objNs string) (*corev1.Secret, error) { + if source == nil || source.SecretRef == nil { + return nil, nil + } + + // Attempt to retrieve secret + name := types.NamespacedName{ + Namespace: objNs, + Name: source.SecretRef.Name, + } + var secret corev1.Secret + if err := r.Get(ctx, name, &secret); err != nil { + return nil, fmt.Errorf("failed to get secret '%s': %w", name.String(), err) + } + return &secret, nil +} + +func (r *KluctlDeploymentReconciler) buildGitAuth(ctx context.Context, gitSecret *corev1.Secret) (*auth.GitAuthProviders, error) { + log := ctrl.LoggerFrom(ctx) + ga := auth.NewDefaultAuthProviders("KLUCTL_GIT", &messages.MessageCallbacks{ + WarningFn: func(s string) { + log.Info(s) + }, + TraceFn: func(s string) { + log.V(1).Info(s) + }, + }) + + if gitSecret == nil { + return ga, nil + } + + e := auth.AuthEntry{ + Host: "*", + Username: "*", + } + + if x, ok := gitSecret.Data["username"]; ok { + e.Username = string(x) + } + if x, ok := gitSecret.Data["password"]; ok { + e.Password = string(x) + } + if x, ok := gitSecret.Data["caFile"]; ok { + e.CABundle = x + } + if x, ok := gitSecret.Data["known_hosts"]; ok { + e.KnownHosts = x + } + if x, ok := gitSecret.Data["identity"]; ok { + e.SshKey = x + } + + var la auth.ListAuthProvider + la.AddEntry(e) + ga.RegisterAuthProvider(&la, false) + return ga, nil +} + +func (r *KluctlDeploymentReconciler) buildRepoCache(ctx context.Context, secret *corev1.Secret) (*repocache.GitRepoCache, error) { + // make sure we use a unique repo cache per set of credentials + h := sha256.New() + if secret == nil { + h.Write([]byte("no-secret")) + } else { + m := json.NewEncoder(h) + err := m.Encode(secret.Data) + if err != nil { + return nil, err + } + } + h2 := hex.EncodeToString(h.Sum(nil)) + + tmpBaseDir := filepath.Join(os.TempDir(), "kluctl-controller-repo-cache", h2) + err := os.MkdirAll(tmpBaseDir, 0o700) + if err != nil { + return nil, err + } + + ctx = utils.WithTmpBaseDir(ctx, tmpBaseDir) + + ga, err := r.buildGitAuth(ctx, secret) + if err != nil { + return nil, err + } + + rc := repocache.NewGitRepoCache(ctx, r.SshPool, ga, nil, 0) + return rc, nil +} diff --git a/pkg/controllers/kluctldeployment_controller_utils.go b/pkg/controllers/kluctldeployment_controller_utils.go new file mode 100644 index 000000000..7489c3b2d --- /dev/null +++ b/pkg/controllers/kluctldeployment_controller_utils.go @@ -0,0 +1,72 @@ +package controllers + +import ( + "context" + kluctlv1 "github.com/kluctl/kluctl/v2/api/v1beta1" + "github.com/kluctl/kluctl/v2/pkg/utils/flux_utils/meta" + apimeta "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/tools/reference" + ctrl "sigs.k8s.io/controller-runtime" +) + +func (r *KluctlDeploymentReconciler) event(ctx context.Context, obj *kluctlv1.KluctlDeployment, warning bool, msg string, metadata map[string]string) { + if metadata == nil { + metadata = map[string]string{} + } + + reason := "info" + if warning { + reason = "warning" + } + if c := apimeta.FindStatusCondition(obj.GetConditions(), meta.ReadyCondition); c != nil { + reason = c.Reason + } + + eventtype := "Normal" + if warning { + eventtype = "Warning" + } + + r.EventRecorder.AnnotatedEventf(obj, metadata, eventtype, reason, msg) +} + +func (r *KluctlDeploymentReconciler) recordReadiness(ctx context.Context, obj *kluctlv1.KluctlDeployment) { + if r.MetricsRecorder == nil { + return + } + log := ctrl.LoggerFrom(ctx) + + objRef, err := reference.GetReference(r.Scheme, obj) + if err != nil { + log.Error(err, "unable to record readiness metric") + return + } + if rc := apimeta.FindStatusCondition(obj.GetConditions(), meta.ReadyCondition); rc != nil { + r.MetricsRecorder.RecordCondition(*objRef, *rc, !obj.GetDeletionTimestamp().IsZero()) + } else { + r.MetricsRecorder.RecordCondition(*objRef, metav1.Condition{ + Type: meta.ReadyCondition, + Status: metav1.ConditionUnknown, + }, !obj.GetDeletionTimestamp().IsZero()) + } +} + +func (r *KluctlDeploymentReconciler) recordSuspension(ctx context.Context, obj *kluctlv1.KluctlDeployment) { + if r.MetricsRecorder == nil { + return + } + log := ctrl.LoggerFrom(ctx) + + objRef, err := reference.GetReference(r.Scheme, obj) + if err != nil { + log.Error(err, "unable to record suspended metric") + return + } + + if !obj.GetDeletionTimestamp().IsZero() { + r.MetricsRecorder.RecordSuspend(*objRef, false) + } else { + r.MetricsRecorder.RecordSuspend(*objRef, obj.Spec.Suspend) + } +} diff --git a/pkg/controllers/predicates.go b/pkg/controllers/predicates.go new file mode 100644 index 000000000..1c6f52c30 --- /dev/null +++ b/pkg/controllers/predicates.go @@ -0,0 +1,66 @@ +/* +Copyright 2020 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controllers + +import ( + kluctlv1 "github.com/kluctl/kluctl/v2/api/v1beta1" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/predicate" +) + +type ReconcileRequestedPredicate struct { + predicate.Funcs +} + +// Update implements the default UpdateEvent filter for validating flux_utils.ReconcileRequestAnnotation changes. +func (ReconcileRequestedPredicate) Update(e event.UpdateEvent) bool { + if e.ObjectOld == nil || e.ObjectNew == nil { + return false + } + + if val, ok := e.ObjectNew.GetAnnotations()[kluctlv1.KluctlRequestReconcileAnnotation]; ok { + if valOld, okOld := e.ObjectOld.GetAnnotations()[kluctlv1.KluctlRequestReconcileAnnotation]; okOld { + return val != valOld + } + return true + } + return false +} + +type DeployRequestedPredicate struct { + predicate.Funcs +} + +func (DeployRequestedPredicate) Update(e event.UpdateEvent) bool { + if e.ObjectOld == nil || e.ObjectNew == nil { + return false + } + + if val, ok := e.ObjectNew.GetAnnotations()[kluctlv1.KluctlRequestReconcileAnnotation]; ok { + if valOld, okOld := e.ObjectOld.GetAnnotations()[kluctlv1.KluctlRequestReconcileAnnotation]; okOld { + return val != valOld + } + return true + } + if val, ok := e.ObjectNew.GetAnnotations()[kluctlv1.KluctlRequestDeployAnnotation]; ok { + if valOld, okOld := e.ObjectOld.GetAnnotations()[kluctlv1.KluctlRequestDeployAnnotation]; okOld { + return val != valOld + } + return true + } + return false +} diff --git a/pkg/controllers/utils.go b/pkg/controllers/utils.go new file mode 100644 index 000000000..7d4476e8d --- /dev/null +++ b/pkg/controllers/utils.go @@ -0,0 +1,31 @@ +package controllers + +import ( + kluctlv1 "github.com/kluctl/kluctl/v2/api/v1beta1" + meta "github.com/kluctl/kluctl/v2/pkg/utils/flux_utils/meta" + apimeta "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func setReadiness(obj *kluctlv1.KluctlDeployment, status metav1.ConditionStatus, reason, message string) { + newCondition := metav1.Condition{ + Type: meta.ReadyCondition, + Status: status, + Reason: reason, + Message: trimString(message, kluctlv1.MaxConditionMessageLength), + } + + c := obj.GetConditions() + apimeta.SetStatusCondition(&c, newCondition) + obj.SetConditions(c) + + obj.Status.ObservedGeneration = obj.GetGeneration() +} + +func trimString(str string, limit int) string { + if len(str) <= limit { + return str + } + + return str[0:limit] + "..." +} diff --git a/pkg/deployment/commands/result_utils.go b/pkg/deployment/commands/result_utils.go index ecd108d56..43c26f685 100644 --- a/pkg/deployment/commands/result_utils.go +++ b/pkg/deployment/commands/result_utils.go @@ -128,7 +128,7 @@ func addGitInfo(targetCtx *kluctl_project.TargetContext, r *result.CommandResult normaliedUrl = originUrl.NormalizedRepoKey() } - r.GitInfo = &result.GitInfo{ + r.GitInfo = result.GitInfo{ Url: originUrl, Ref: head.Name().String(), SubDir: subDir, diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index 53f31c0de..17faff1a6 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -1,12 +1,16 @@ package deployment import ( + "context" + "crypto/sha256" + "encoding/hex" "fmt" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" k8s2 "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/yaml" "path/filepath" "sync" ) @@ -297,3 +301,40 @@ func (c *DeploymentCollection) FindRenderedImages() map[k8s2.ObjectRef][]string } return ret } + +func (c *DeploymentCollection) CalcObjectsHash() (string, error) { + cnt := 0 + for _, di := range c.Deployments { + cnt += len(di.Objects) + } + + hashes := make([][32]byte, cnt) + gh := utils.NewGoHelper(context.Background(), 8) + i := 0 + for _, di := range c.Deployments { + for _, o := range di.Objects { + i2 := i + o := o + gh.RunE(func() error { + j, err := yaml.WriteJsonString(o) + if err != nil { + return err + } + hashes[i2] = sha256.Sum256([]byte(j)) + return nil + }) + i++ + } + } + gh.Wait() + if gh.ErrorOrNil() != nil { + return "", gh.ErrorOrNil() + } + + h := sha256.New() + for _, x := range hashes { + h.Write(x[:]) + } + + return hex.EncodeToString(h.Sum(nil)), nil +} diff --git a/pkg/types/result/command_result.go b/pkg/types/result/command_result.go index 80d30e378..3f8a948ac 100644 --- a/pkg/types/result/command_result.go +++ b/pkg/types/result/command_result.go @@ -133,7 +133,7 @@ type CommandResult struct { TargetKey TargetKey `json:"targetKey"` Target types.Target `json:"target"` Command CommandInfo `json:"command,omitempty"` - GitInfo *GitInfo `json:"gitInfo,omitempty"` + GitInfo GitInfo `json:"gitInfo,omitempty"` ClusterInfo ClusterInfo `json:"clusterInfo"` Deployment *types.DeploymentProjectConfig `json:"deployment,omitempty"` diff --git a/pkg/types/result/summary.go b/pkg/types/result/summary.go index dc05e095a..5cf639762 100644 --- a/pkg/types/result/summary.go +++ b/pkg/types/result/summary.go @@ -9,7 +9,7 @@ type CommandResultSummary struct { Project ProjectKey `json:"project"` Target TargetKey `json:"target"` Command CommandInfo `json:"commandInfo"` - GitInfo *GitInfo `json:"gitInfo,omitempty"` + GitInfo GitInfo `json:"gitInfo,omitempty"` ClusterInfo ClusterInfo `json:"clusterInfo,omitempty"` RenderedObjects int `json:"renderedObjects"` @@ -41,6 +41,10 @@ type ProjectSummary struct { } func (cr *CommandResult) BuildSummary() *CommandResultSummary { + if cr == nil { + return nil + } + count := func(f func(o ResultObject) bool) int { cnt := 0 for _, o := range cr.Objects { From 6c38f31d751882a0098989c23fec3be1089adf29 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 15 May 2023 15:34:16 +0200 Subject: [PATCH 1633/2916] chore: Run make generate --- api/v1beta1/zz_generated.deepcopy.go | 369 ++++++++ .../gitops.kluctl.io_kluctldeployments.yaml | 3 +- config/rbac/role.yaml | 1 - pkg/types/k8s/zz_generated.deepcopy.go | 39 + pkg/types/result/zz_generated.deepcopy.go | 512 +++++++++++ pkg/types/zz_generated.deepcopy.go | 863 ++++++++++++++++++ 6 files changed, 1784 insertions(+), 3 deletions(-) create mode 100644 api/v1beta1/zz_generated.deepcopy.go create mode 100644 pkg/types/k8s/zz_generated.deepcopy.go create mode 100644 pkg/types/result/zz_generated.deepcopy.go create mode 100644 pkg/types/zz_generated.deepcopy.go diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go new file mode 100644 index 000000000..79c1afc75 --- /dev/null +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -0,0 +1,369 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 2023. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/types/result" + "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Decryption) DeepCopyInto(out *Decryption) { + *out = *in + if in.SecretRef != nil { + in, out := &in.SecretRef, &out.SecretRef + *out = new(LocalObjectReference) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Decryption. +func (in *Decryption) DeepCopy() *Decryption { + if in == nil { + return nil + } + out := new(Decryption) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GitRef) DeepCopyInto(out *GitRef) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitRef. +func (in *GitRef) DeepCopy() *GitRef { + if in == nil { + return nil + } + out := new(GitRef) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HelmCredentials) DeepCopyInto(out *HelmCredentials) { + *out = *in + out.SecretRef = in.SecretRef +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HelmCredentials. +func (in *HelmCredentials) DeepCopy() *HelmCredentials { + if in == nil { + return nil + } + out := new(HelmCredentials) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KluctlDeployment) DeepCopyInto(out *KluctlDeployment) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KluctlDeployment. +func (in *KluctlDeployment) DeepCopy() *KluctlDeployment { + if in == nil { + return nil + } + out := new(KluctlDeployment) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *KluctlDeployment) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KluctlDeploymentList) DeepCopyInto(out *KluctlDeploymentList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]KluctlDeployment, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KluctlDeploymentList. +func (in *KluctlDeploymentList) DeepCopy() *KluctlDeploymentList { + if in == nil { + return nil + } + out := new(KluctlDeploymentList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *KluctlDeploymentList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KluctlDeploymentSpec) DeepCopyInto(out *KluctlDeploymentSpec) { + *out = *in + in.Source.DeepCopyInto(&out.Source) + if in.Decryption != nil { + in, out := &in.Decryption, &out.Decryption + *out = new(Decryption) + (*in).DeepCopyInto(*out) + } + out.Interval = in.Interval + if in.RetryInterval != nil { + in, out := &in.RetryInterval, &out.RetryInterval + *out = new(v1.Duration) + **out = **in + } + if in.DeployInterval != nil { + in, out := &in.DeployInterval, &out.DeployInterval + *out = new(SafeDuration) + **out = **in + } + if in.ValidateInterval != nil { + in, out := &in.ValidateInterval, &out.ValidateInterval + *out = new(SafeDuration) + **out = **in + } + if in.Timeout != nil { + in, out := &in.Timeout, &out.Timeout + *out = new(v1.Duration) + **out = **in + } + if in.HelmCredentials != nil { + in, out := &in.HelmCredentials, &out.HelmCredentials + *out = make([]HelmCredentials, len(*in)) + copy(*out, *in) + } + if in.KubeConfig != nil { + in, out := &in.KubeConfig, &out.KubeConfig + *out = new(KubeConfig) + **out = **in + } + if in.Target != nil { + in, out := &in.Target, &out.Target + *out = new(string) + **out = **in + } + if in.TargetNameOverride != nil { + in, out := &in.TargetNameOverride, &out.TargetNameOverride + *out = new(string) + **out = **in + } + if in.Context != nil { + in, out := &in.Context, &out.Context + *out = new(string) + **out = **in + } + in.Args.DeepCopyInto(&out.Args) + if in.Images != nil { + in, out := &in.Images, &out.Images + *out = make([]types.FixedImage, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.IncludeTags != nil { + in, out := &in.IncludeTags, &out.IncludeTags + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ExcludeTags != nil { + in, out := &in.ExcludeTags, &out.ExcludeTags + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.IncludeDeploymentDirs != nil { + in, out := &in.IncludeDeploymentDirs, &out.IncludeDeploymentDirs + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ExcludeDeploymentDirs != nil { + in, out := &in.ExcludeDeploymentDirs, &out.ExcludeDeploymentDirs + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KluctlDeploymentSpec. +func (in *KluctlDeploymentSpec) DeepCopy() *KluctlDeploymentSpec { + if in == nil { + return nil + } + out := new(KluctlDeploymentSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KluctlDeploymentStatus) DeepCopyInto(out *KluctlDeploymentStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.ProjectKey != nil { + in, out := &in.ProjectKey, &out.ProjectKey + *out = new(result.ProjectKey) + **out = **in + } + if in.TargetKey != nil { + in, out := &in.TargetKey, &out.TargetKey + *out = new(result.TargetKey) + **out = **in + } + if in.LastDeployResult != nil { + in, out := &in.LastDeployResult, &out.LastDeployResult + *out = new(result.CommandResultSummary) + (*in).DeepCopyInto(*out) + } + if in.LastPruneResult != nil { + in, out := &in.LastPruneResult, &out.LastPruneResult + *out = new(result.CommandResultSummary) + (*in).DeepCopyInto(*out) + } + if in.LastValidateResult != nil { + in, out := &in.LastValidateResult, &out.LastValidateResult + *out = new(result.ValidateResult) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KluctlDeploymentStatus. +func (in *KluctlDeploymentStatus) DeepCopy() *KluctlDeploymentStatus { + if in == nil { + return nil + } + out := new(KluctlDeploymentStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KubeConfig) DeepCopyInto(out *KubeConfig) { + *out = *in + out.SecretRef = in.SecretRef +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeConfig. +func (in *KubeConfig) DeepCopy() *KubeConfig { + if in == nil { + return nil + } + out := new(KubeConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LocalObjectReference) DeepCopyInto(out *LocalObjectReference) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LocalObjectReference. +func (in *LocalObjectReference) DeepCopy() *LocalObjectReference { + if in == nil { + return nil + } + out := new(LocalObjectReference) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ProjectSource) DeepCopyInto(out *ProjectSource) { + *out = *in + in.URL.DeepCopyInto(&out.URL) + if in.Ref != nil { + in, out := &in.Ref, &out.Ref + *out = new(GitRef) + **out = **in + } + if in.SecretRef != nil { + in, out := &in.SecretRef, &out.SecretRef + *out = new(LocalObjectReference) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProjectSource. +func (in *ProjectSource) DeepCopy() *ProjectSource { + if in == nil { + return nil + } + out := new(ProjectSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SafeDuration) DeepCopyInto(out *SafeDuration) { + *out = *in + out.Duration = in.Duration +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SafeDuration. +func (in *SafeDuration) DeepCopy() *SafeDuration { + if in == nil { + return nil + } + out := new(SafeDuration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SecretKeyReference) DeepCopyInto(out *SecretKeyReference) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretKeyReference. +func (in *SecretKeyReference) DeepCopy() *SecretKeyReference { + if in == nil { + return nil + } + out := new(SecretKeyReference) + in.DeepCopyInto(out) + return out +} diff --git a/config/crd/bases/gitops.kluctl.io_kluctldeployments.yaml b/config/crd/bases/gitops.kluctl.io_kluctldeployments.yaml index 3e81ebc5e..4abd9c5ce 100644 --- a/config/crd/bases/gitops.kluctl.io_kluctldeployments.yaml +++ b/config/crd/bases/gitops.kluctl.io_kluctldeployments.yaml @@ -3,8 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.11.3 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.12.0 name: kluctldeployments.gitops.kluctl.io spec: group: gitops.kluctl.io diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 5e7aaeef7..e043f192e 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -2,7 +2,6 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - creationTimestamp: null name: manager-role rules: - apiGroups: diff --git a/pkg/types/k8s/zz_generated.deepcopy.go b/pkg/types/k8s/zz_generated.deepcopy.go new file mode 100644 index 000000000..a753e48cd --- /dev/null +++ b/pkg/types/k8s/zz_generated.deepcopy.go @@ -0,0 +1,39 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 2023. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package k8s + +import () + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ObjectRef) DeepCopyInto(out *ObjectRef) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectRef. +func (in *ObjectRef) DeepCopy() *ObjectRef { + if in == nil { + return nil + } + out := new(ObjectRef) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/types/result/zz_generated.deepcopy.go b/pkg/types/result/zz_generated.deepcopy.go new file mode 100644 index 000000000..c7c90926a --- /dev/null +++ b/pkg/types/result/zz_generated.deepcopy.go @@ -0,0 +1,512 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 2023. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package result + +import ( + "github.com/kluctl/kluctl/v2/pkg/types" + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BaseObject) DeepCopyInto(out *BaseObject) { + *out = *in + out.Ref = in.Ref + if in.Changes != nil { + in, out := &in.Changes, &out.Changes + *out = make([]Change, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BaseObject. +func (in *BaseObject) DeepCopy() *BaseObject { + if in == nil { + return nil + } + out := new(BaseObject) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Change) DeepCopyInto(out *Change) { + *out = *in + if in.OldValue != nil { + in, out := &in.OldValue, &out.OldValue + *out = new(v1.JSON) + (*in).DeepCopyInto(*out) + } + if in.NewValue != nil { + in, out := &in.NewValue, &out.NewValue + *out = new(v1.JSON) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Change. +func (in *Change) DeepCopy() *Change { + if in == nil { + return nil + } + out := new(Change) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ChangedObject) DeepCopyInto(out *ChangedObject) { + *out = *in + out.Ref = in.Ref + if in.Changes != nil { + in, out := &in.Changes, &out.Changes + *out = make([]Change, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChangedObject. +func (in *ChangedObject) DeepCopy() *ChangedObject { + if in == nil { + return nil + } + out := new(ChangedObject) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterInfo) DeepCopyInto(out *ClusterInfo) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterInfo. +func (in *ClusterInfo) DeepCopy() *ClusterInfo { + if in == nil { + return nil + } + out := new(ClusterInfo) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CommandInfo) DeepCopyInto(out *CommandInfo) { + *out = *in + in.StartTime.DeepCopyInto(&out.StartTime) + in.EndTime.DeepCopyInto(&out.EndTime) + if in.KluctlDeployment != nil { + in, out := &in.KluctlDeployment, &out.KluctlDeployment + *out = new(KluctlDeploymentInfo) + **out = **in + } + if in.Args != nil { + in, out := &in.Args, &out.Args + *out = (*in).DeepCopy() + } + if in.Images != nil { + in, out := &in.Images, &out.Images + *out = make([]types.FixedImage, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.IncludeTags != nil { + in, out := &in.IncludeTags, &out.IncludeTags + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ExcludeTags != nil { + in, out := &in.ExcludeTags, &out.ExcludeTags + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.IncludeDeploymentDirs != nil { + in, out := &in.IncludeDeploymentDirs, &out.IncludeDeploymentDirs + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ExcludeDeploymentDirs != nil { + in, out := &in.ExcludeDeploymentDirs, &out.ExcludeDeploymentDirs + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommandInfo. +func (in *CommandInfo) DeepCopy() *CommandInfo { + if in == nil { + return nil + } + out := new(CommandInfo) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CommandResult) DeepCopyInto(out *CommandResult) { + *out = *in + out.ProjectKey = in.ProjectKey + out.TargetKey = in.TargetKey + in.Target.DeepCopyInto(&out.Target) + in.Command.DeepCopyInto(&out.Command) + in.GitInfo.DeepCopyInto(&out.GitInfo) + out.ClusterInfo = in.ClusterInfo + if in.Deployment != nil { + in, out := &in.Deployment, &out.Deployment + *out = new(types.DeploymentProjectConfig) + (*in).DeepCopyInto(*out) + } + if in.Objects != nil { + in, out := &in.Objects, &out.Objects + *out = make([]ResultObject, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Errors != nil { + in, out := &in.Errors, &out.Errors + *out = make([]DeploymentError, len(*in)) + copy(*out, *in) + } + if in.Warnings != nil { + in, out := &in.Warnings, &out.Warnings + *out = make([]DeploymentError, len(*in)) + copy(*out, *in) + } + if in.SeenImages != nil { + in, out := &in.SeenImages, &out.SeenImages + *out = make([]types.FixedImage, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommandResult. +func (in *CommandResult) DeepCopy() *CommandResult { + if in == nil { + return nil + } + out := new(CommandResult) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CommandResultSummary) DeepCopyInto(out *CommandResultSummary) { + *out = *in + out.Project = in.Project + out.Target = in.Target + in.Command.DeepCopyInto(&out.Command) + in.GitInfo.DeepCopyInto(&out.GitInfo) + out.ClusterInfo = in.ClusterInfo +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommandResultSummary. +func (in *CommandResultSummary) DeepCopy() *CommandResultSummary { + if in == nil { + return nil + } + out := new(CommandResultSummary) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CompactedCommandResult) DeepCopyInto(out *CompactedCommandResult) { + *out = *in + in.CommandResult.DeepCopyInto(&out.CommandResult) + if in.CompactedObjects != nil { + in, out := &in.CompactedObjects, &out.CompactedObjects + *out = make(CompactedObjects, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CompactedCommandResult. +func (in *CompactedCommandResult) DeepCopy() *CompactedCommandResult { + if in == nil { + return nil + } + out := new(CompactedCommandResult) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CompactedObject) DeepCopyInto(out *CompactedObject) { + *out = *in + in.BaseObject.DeepCopyInto(&out.BaseObject) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CompactedObject. +func (in *CompactedObject) DeepCopy() *CompactedObject { + if in == nil { + return nil + } + out := new(CompactedObject) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in CompactedObjects) DeepCopyInto(out *CompactedObjects) { + { + in := &in + *out = make(CompactedObjects, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CompactedObjects. +func (in CompactedObjects) DeepCopy() CompactedObjects { + if in == nil { + return nil + } + out := new(CompactedObjects) + in.DeepCopyInto(out) + return *out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DeploymentError) DeepCopyInto(out *DeploymentError) { + *out = *in + out.Ref = in.Ref +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeploymentError. +func (in *DeploymentError) DeepCopy() *DeploymentError { + if in == nil { + return nil + } + out := new(DeploymentError) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GitInfo) DeepCopyInto(out *GitInfo) { + *out = *in + if in.Url != nil { + in, out := &in.Url, &out.Url + *out = (*in).DeepCopy() + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitInfo. +func (in *GitInfo) DeepCopy() *GitInfo { + if in == nil { + return nil + } + out := new(GitInfo) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KluctlDeploymentInfo) DeepCopyInto(out *KluctlDeploymentInfo) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KluctlDeploymentInfo. +func (in *KluctlDeploymentInfo) DeepCopy() *KluctlDeploymentInfo { + if in == nil { + return nil + } + out := new(KluctlDeploymentInfo) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ProjectKey) DeepCopyInto(out *ProjectKey) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProjectKey. +func (in *ProjectKey) DeepCopy() *ProjectKey { + if in == nil { + return nil + } + out := new(ProjectKey) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ProjectSummary) DeepCopyInto(out *ProjectSummary) { + *out = *in + out.Project = in.Project + if in.Targets != nil { + in, out := &in.Targets, &out.Targets + *out = make([]*TargetSummary, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(TargetSummary) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProjectSummary. +func (in *ProjectSummary) DeepCopy() *ProjectSummary { + if in == nil { + return nil + } + out := new(ProjectSummary) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResultObject) DeepCopyInto(out *ResultObject) { + *out = *in + in.BaseObject.DeepCopyInto(&out.BaseObject) + if in.Rendered != nil { + in, out := &in.Rendered, &out.Rendered + *out = (*in).DeepCopy() + } + if in.Remote != nil { + in, out := &in.Remote, &out.Remote + *out = (*in).DeepCopy() + } + if in.Applied != nil { + in, out := &in.Applied, &out.Applied + *out = (*in).DeepCopy() + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResultObject. +func (in *ResultObject) DeepCopy() *ResultObject { + if in == nil { + return nil + } + out := new(ResultObject) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TargetKey) DeepCopyInto(out *TargetKey) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TargetKey. +func (in *TargetKey) DeepCopy() *TargetKey { + if in == nil { + return nil + } + out := new(TargetKey) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TargetSummary) DeepCopyInto(out *TargetSummary) { + *out = *in + out.Target = in.Target + if in.LastValidateResult != nil { + in, out := &in.LastValidateResult, &out.LastValidateResult + *out = new(ValidateResult) + (*in).DeepCopyInto(*out) + } + if in.CommandResults != nil { + in, out := &in.CommandResults, &out.CommandResults + *out = make([]CommandResultSummary, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TargetSummary. +func (in *TargetSummary) DeepCopy() *TargetSummary { + if in == nil { + return nil + } + out := new(TargetSummary) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ValidateResult) DeepCopyInto(out *ValidateResult) { + *out = *in + if in.Warnings != nil { + in, out := &in.Warnings, &out.Warnings + *out = make([]DeploymentError, len(*in)) + copy(*out, *in) + } + if in.Errors != nil { + in, out := &in.Errors, &out.Errors + *out = make([]DeploymentError, len(*in)) + copy(*out, *in) + } + if in.Results != nil { + in, out := &in.Results, &out.Results + *out = make([]ValidateResultEntry, len(*in)) + copy(*out, *in) + } + if in.Drift != nil { + in, out := &in.Drift, &out.Drift + *out = make([]ChangedObject, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValidateResult. +func (in *ValidateResult) DeepCopy() *ValidateResult { + if in == nil { + return nil + } + out := new(ValidateResult) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ValidateResultEntry) DeepCopyInto(out *ValidateResultEntry) { + *out = *in + out.Ref = in.Ref +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValidateResultEntry. +func (in *ValidateResultEntry) DeepCopy() *ValidateResultEntry { + if in == nil { + return nil + } + out := new(ValidateResultEntry) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/types/zz_generated.deepcopy.go b/pkg/types/zz_generated.deepcopy.go new file mode 100644 index 000000000..ec75fae41 --- /dev/null +++ b/pkg/types/zz_generated.deepcopy.go @@ -0,0 +1,863 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 2023. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package types + +import ( + "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DeleteObjectItemConfig) DeepCopyInto(out *DeleteObjectItemConfig) { + *out = *in + if in.Group != nil { + in, out := &in.Group, &out.Group + *out = new(string) + **out = **in + } + if in.Kind != nil { + in, out := &in.Kind, &out.Kind + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeleteObjectItemConfig. +func (in *DeleteObjectItemConfig) DeepCopy() *DeleteObjectItemConfig { + if in == nil { + return nil + } + out := new(DeleteObjectItemConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DeploymentArg) DeepCopyInto(out *DeploymentArg) { + *out = *in + if in.Default != nil { + in, out := &in.Default, &out.Default + *out = new(v1.JSON) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeploymentArg. +func (in *DeploymentArg) DeepCopy() *DeploymentArg { + if in == nil { + return nil + } + out := new(DeploymentArg) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DeploymentItemConfig) DeepCopyInto(out *DeploymentItemConfig) { + *out = *in + if in.Path != nil { + in, out := &in.Path, &out.Path + *out = new(string) + **out = **in + } + if in.Include != nil { + in, out := &in.Include, &out.Include + *out = new(string) + **out = **in + } + if in.Git != nil { + in, out := &in.Git, &out.Git + *out = new(GitProject) + (*in).DeepCopyInto(*out) + } + if in.Tags != nil { + in, out := &in.Tags, &out.Tags + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Vars != nil { + in, out := &in.Vars, &out.Vars + *out = make([]*VarsSource, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(VarsSource) + (*in).DeepCopyInto(*out) + } + } + } + if in.DeleteObjects != nil { + in, out := &in.DeleteObjects, &out.DeleteObjects + *out = make([]DeleteObjectItemConfig, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.RenderedHelmChartConfig != nil { + in, out := &in.RenderedHelmChartConfig, &out.RenderedHelmChartConfig + *out = new(HelmChartConfig) + (*in).DeepCopyInto(*out) + } + if in.RenderedObjects != nil { + in, out := &in.RenderedObjects, &out.RenderedObjects + *out = make([]k8s.ObjectRef, len(*in)) + copy(*out, *in) + } + if in.RenderedInclude != nil { + in, out := &in.RenderedInclude, &out.RenderedInclude + *out = new(DeploymentProjectConfig) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeploymentItemConfig. +func (in *DeploymentItemConfig) DeepCopy() *DeploymentItemConfig { + if in == nil { + return nil + } + out := new(DeploymentItemConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DeploymentProjectConfig) DeepCopyInto(out *DeploymentProjectConfig) { + *out = *in + if in.Vars != nil { + in, out := &in.Vars, &out.Vars + *out = make([]*VarsSource, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(VarsSource) + (*in).DeepCopyInto(*out) + } + } + } + if in.SealedSecrets != nil { + in, out := &in.SealedSecrets, &out.SealedSecrets + *out = new(SealedSecretsConfig) + (*in).DeepCopyInto(*out) + } + if in.Deployments != nil { + in, out := &in.Deployments, &out.Deployments + *out = make([]*DeploymentItemConfig, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(DeploymentItemConfig) + (*in).DeepCopyInto(*out) + } + } + } + if in.CommonLabels != nil { + in, out := &in.CommonLabels, &out.CommonLabels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.CommonAnnotations != nil { + in, out := &in.CommonAnnotations, &out.CommonAnnotations + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.OverrideNamespace != nil { + in, out := &in.OverrideNamespace, &out.OverrideNamespace + *out = new(string) + **out = **in + } + if in.Tags != nil { + in, out := &in.Tags, &out.Tags + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.IgnoreForDiff != nil { + in, out := &in.IgnoreForDiff, &out.IgnoreForDiff + *out = make([]*IgnoreForDiffItemConfig, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(IgnoreForDiffItemConfig) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeploymentProjectConfig. +func (in *DeploymentProjectConfig) DeepCopy() *DeploymentProjectConfig { + if in == nil { + return nil + } + out := new(DeploymentProjectConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExternalProject) DeepCopyInto(out *ExternalProject) { + *out = *in + if in.Project != nil { + in, out := &in.Project, &out.Project + *out = new(GitProject) + (*in).DeepCopyInto(*out) + } + if in.Path != nil { + in, out := &in.Path, &out.Path + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalProject. +func (in *ExternalProject) DeepCopy() *ExternalProject { + if in == nil { + return nil + } + out := new(ExternalProject) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FixedImage) DeepCopyInto(out *FixedImage) { + *out = *in + if in.DeployedImage != nil { + in, out := &in.DeployedImage, &out.DeployedImage + *out = new(string) + **out = **in + } + if in.Namespace != nil { + in, out := &in.Namespace, &out.Namespace + *out = new(string) + **out = **in + } + if in.Object != nil { + in, out := &in.Object, &out.Object + *out = new(k8s.ObjectRef) + **out = **in + } + if in.Deployment != nil { + in, out := &in.Deployment, &out.Deployment + *out = new(string) + **out = **in + } + if in.Container != nil { + in, out := &in.Container, &out.Container + *out = new(string) + **out = **in + } + if in.DeployTags != nil { + in, out := &in.DeployTags, &out.DeployTags + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.DeploymentDir != nil { + in, out := &in.DeploymentDir, &out.DeploymentDir + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FixedImage. +func (in *FixedImage) DeepCopy() *FixedImage { + if in == nil { + return nil + } + out := new(FixedImage) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FixedImagesConfig) DeepCopyInto(out *FixedImagesConfig) { + *out = *in + if in.Images != nil { + in, out := &in.Images, &out.Images + *out = make([]FixedImage, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FixedImagesConfig. +func (in *FixedImagesConfig) DeepCopy() *FixedImagesConfig { + if in == nil { + return nil + } + out := new(FixedImagesConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GitProject) DeepCopyInto(out *GitProject) { + *out = *in + in.Url.DeepCopyInto(&out.Url) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitProject. +func (in *GitProject) DeepCopy() *GitProject { + if in == nil { + return nil + } + out := new(GitProject) + in.DeepCopyInto(out) + return out +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitUrl. +func (in *GitUrl) DeepCopy() *GitUrl { + if in == nil { + return nil + } + out := new(GitUrl) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GlobalSealedSecretsConfig) DeepCopyInto(out *GlobalSealedSecretsConfig) { + *out = *in + if in.Bootstrap != nil { + in, out := &in.Bootstrap, &out.Bootstrap + *out = new(bool) + **out = **in + } + if in.Namespace != nil { + in, out := &in.Namespace, &out.Namespace + *out = new(string) + **out = **in + } + if in.ControllerName != nil { + in, out := &in.ControllerName, &out.ControllerName + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GlobalSealedSecretsConfig. +func (in *GlobalSealedSecretsConfig) DeepCopy() *GlobalSealedSecretsConfig { + if in == nil { + return nil + } + out := new(GlobalSealedSecretsConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HelmChartConfig) DeepCopyInto(out *HelmChartConfig) { + *out = *in + in.HelmChartConfig2.DeepCopyInto(&out.HelmChartConfig2) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HelmChartConfig. +func (in *HelmChartConfig) DeepCopy() *HelmChartConfig { + if in == nil { + return nil + } + out := new(HelmChartConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HelmChartConfig2) DeepCopyInto(out *HelmChartConfig2) { + *out = *in + if in.CredentialsId != nil { + in, out := &in.CredentialsId, &out.CredentialsId + *out = new(string) + **out = **in + } + if in.UpdateConstraints != nil { + in, out := &in.UpdateConstraints, &out.UpdateConstraints + *out = new(string) + **out = **in + } + if in.Namespace != nil { + in, out := &in.Namespace, &out.Namespace + *out = new(string) + **out = **in + } + if in.Output != nil { + in, out := &in.Output, &out.Output + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HelmChartConfig2. +func (in *HelmChartConfig2) DeepCopy() *HelmChartConfig2 { + if in == nil { + return nil + } + out := new(HelmChartConfig2) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IgnoreForDiffItemConfig) DeepCopyInto(out *IgnoreForDiffItemConfig) { + *out = *in + if in.FieldPath != nil { + in, out := &in.FieldPath, &out.FieldPath + *out = make(SingleStringOrList, len(*in)) + copy(*out, *in) + } + if in.FieldPathRegex != nil { + in, out := &in.FieldPathRegex, &out.FieldPathRegex + *out = make(SingleStringOrList, len(*in)) + copy(*out, *in) + } + if in.Group != nil { + in, out := &in.Group, &out.Group + *out = new(string) + **out = **in + } + if in.Kind != nil { + in, out := &in.Kind, &out.Kind + *out = new(string) + **out = **in + } + if in.Name != nil { + in, out := &in.Name, &out.Name + *out = new(string) + **out = **in + } + if in.Namespace != nil { + in, out := &in.Namespace, &out.Namespace + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IgnoreForDiffItemConfig. +func (in *IgnoreForDiffItemConfig) DeepCopy() *IgnoreForDiffItemConfig { + if in == nil { + return nil + } + out := new(IgnoreForDiffItemConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KluctlProject) DeepCopyInto(out *KluctlProject) { + *out = *in + if in.Targets != nil { + in, out := &in.Targets, &out.Targets + *out = make([]*Target, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(Target) + (*in).DeepCopyInto(*out) + } + } + } + if in.Args != nil { + in, out := &in.Args, &out.Args + *out = make([]*DeploymentArg, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(DeploymentArg) + (*in).DeepCopyInto(*out) + } + } + } + if in.SecretsConfig != nil { + in, out := &in.SecretsConfig, &out.SecretsConfig + *out = new(SecretsConfig) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KluctlProject. +func (in *KluctlProject) DeepCopy() *KluctlProject { + if in == nil { + return nil + } + out := new(KluctlProject) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SealedSecretsConfig) DeepCopyInto(out *SealedSecretsConfig) { + *out = *in + if in.OutputPattern != nil { + in, out := &in.OutputPattern, &out.OutputPattern + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SealedSecretsConfig. +func (in *SealedSecretsConfig) DeepCopy() *SealedSecretsConfig { + if in == nil { + return nil + } + out := new(SealedSecretsConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SealingConfig) DeepCopyInto(out *SealingConfig) { + *out = *in + if in.Args != nil { + in, out := &in.Args, &out.Args + *out = (*in).DeepCopy() + } + if in.SecretSets != nil { + in, out := &in.SecretSets, &out.SecretSets + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.CertFile != nil { + in, out := &in.CertFile, &out.CertFile + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SealingConfig. +func (in *SealingConfig) DeepCopy() *SealingConfig { + if in == nil { + return nil + } + out := new(SealingConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SecretSet) DeepCopyInto(out *SecretSet) { + *out = *in + if in.Vars != nil { + in, out := &in.Vars, &out.Vars + *out = make([]*VarsSource, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(VarsSource) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretSet. +func (in *SecretSet) DeepCopy() *SecretSet { + if in == nil { + return nil + } + out := new(SecretSet) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SecretsConfig) DeepCopyInto(out *SecretsConfig) { + *out = *in + if in.SealedSecrets != nil { + in, out := &in.SealedSecrets, &out.SealedSecrets + *out = new(GlobalSealedSecretsConfig) + (*in).DeepCopyInto(*out) + } + if in.SecretSets != nil { + in, out := &in.SecretSets, &out.SecretSets + *out = make([]SecretSet, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretsConfig. +func (in *SecretsConfig) DeepCopy() *SecretsConfig { + if in == nil { + return nil + } + out := new(SecretsConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in SingleStringOrList) DeepCopyInto(out *SingleStringOrList) { + { + in := &in + *out = make(SingleStringOrList, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SingleStringOrList. +func (in SingleStringOrList) DeepCopy() SingleStringOrList { + if in == nil { + return nil + } + out := new(SingleStringOrList) + in.DeepCopyInto(out) + return *out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Target) DeepCopyInto(out *Target) { + *out = *in + if in.Context != nil { + in, out := &in.Context, &out.Context + *out = new(string) + **out = **in + } + if in.Args != nil { + in, out := &in.Args, &out.Args + *out = (*in).DeepCopy() + } + if in.SealingConfig != nil { + in, out := &in.SealingConfig, &out.SealingConfig + *out = new(SealingConfig) + (*in).DeepCopyInto(*out) + } + if in.Images != nil { + in, out := &in.Images, &out.Images + *out = make([]FixedImage, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Target. +func (in *Target) DeepCopy() *Target { + if in == nil { + return nil + } + out := new(Target) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VarsSource) DeepCopyInto(out *VarsSource) { + *out = *in + if in.IgnoreMissing != nil { + in, out := &in.IgnoreMissing, &out.IgnoreMissing + *out = new(bool) + **out = **in + } + if in.NoOverride != nil { + in, out := &in.NoOverride, &out.NoOverride + *out = new(bool) + **out = **in + } + if in.Values != nil { + in, out := &in.Values, &out.Values + *out = (*in).DeepCopy() + } + if in.File != nil { + in, out := &in.File, &out.File + *out = new(string) + **out = **in + } + if in.Git != nil { + in, out := &in.Git, &out.Git + *out = new(VarsSourceGit) + (*in).DeepCopyInto(*out) + } + if in.ClusterConfigMap != nil { + in, out := &in.ClusterConfigMap, &out.ClusterConfigMap + *out = new(VarsSourceClusterConfigMapOrSecret) + (*in).DeepCopyInto(*out) + } + if in.ClusterSecret != nil { + in, out := &in.ClusterSecret, &out.ClusterSecret + *out = new(VarsSourceClusterConfigMapOrSecret) + (*in).DeepCopyInto(*out) + } + if in.SystemEnvVars != nil { + in, out := &in.SystemEnvVars, &out.SystemEnvVars + *out = (*in).DeepCopy() + } + if in.Http != nil { + in, out := &in.Http, &out.Http + *out = new(VarsSourceHttp) + (*in).DeepCopyInto(*out) + } + if in.AwsSecretsManager != nil { + in, out := &in.AwsSecretsManager, &out.AwsSecretsManager + *out = new(VarsSourceAwsSecretsManager) + (*in).DeepCopyInto(*out) + } + if in.Vault != nil { + in, out := &in.Vault, &out.Vault + *out = new(VarsSourceVault) + **out = **in + } + if in.RenderedVars != nil { + in, out := &in.RenderedVars, &out.RenderedVars + *out = (*in).DeepCopy() + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VarsSource. +func (in *VarsSource) DeepCopy() *VarsSource { + if in == nil { + return nil + } + out := new(VarsSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VarsSourceAwsSecretsManager) DeepCopyInto(out *VarsSourceAwsSecretsManager) { + *out = *in + if in.Region != nil { + in, out := &in.Region, &out.Region + *out = new(string) + **out = **in + } + if in.Profile != nil { + in, out := &in.Profile, &out.Profile + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VarsSourceAwsSecretsManager. +func (in *VarsSourceAwsSecretsManager) DeepCopy() *VarsSourceAwsSecretsManager { + if in == nil { + return nil + } + out := new(VarsSourceAwsSecretsManager) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VarsSourceClusterConfigMapOrSecret) DeepCopyInto(out *VarsSourceClusterConfigMapOrSecret) { + *out = *in + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VarsSourceClusterConfigMapOrSecret. +func (in *VarsSourceClusterConfigMapOrSecret) DeepCopy() *VarsSourceClusterConfigMapOrSecret { + if in == nil { + return nil + } + out := new(VarsSourceClusterConfigMapOrSecret) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VarsSourceGit) DeepCopyInto(out *VarsSourceGit) { + *out = *in + in.Url.DeepCopyInto(&out.Url) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VarsSourceGit. +func (in *VarsSourceGit) DeepCopy() *VarsSourceGit { + if in == nil { + return nil + } + out := new(VarsSourceGit) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VarsSourceHttp) DeepCopyInto(out *VarsSourceHttp) { + *out = *in + in.Url.DeepCopyInto(&out.Url) + if in.Method != nil { + in, out := &in.Method, &out.Method + *out = new(string) + **out = **in + } + if in.Body != nil { + in, out := &in.Body, &out.Body + *out = new(string) + **out = **in + } + if in.Headers != nil { + in, out := &in.Headers, &out.Headers + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.JsonPath != nil { + in, out := &in.JsonPath, &out.JsonPath + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VarsSourceHttp. +func (in *VarsSourceHttp) DeepCopy() *VarsSourceHttp { + if in == nil { + return nil + } + out := new(VarsSourceHttp) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VarsSourceVault) DeepCopyInto(out *VarsSourceVault) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VarsSourceVault. +func (in *VarsSourceVault) DeepCopy() *VarsSourceVault { + if in == nil { + return nil + } + out := new(VarsSourceVault) + in.DeepCopyInto(out) + return out +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new YamlUrl. +func (in *YamlUrl) DeepCopy() *YamlUrl { + if in == nil { + return nil + } + out := new(YamlUrl) + in.DeepCopyInto(out) + return out +} From 011e330c56767a9259cdd2617a80d8876fcc9a41 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 15 May 2023 15:34:30 +0200 Subject: [PATCH 1634/2916] tests: Add controller tests --- e2e/default_clusters.go | 3 + e2e/gitops_test.go | 609 ++++++++++++++++++++++++++++++++++++++++ e2e/utils.go | 15 +- 3 files changed, 626 insertions(+), 1 deletion(-) create mode 100644 e2e/gitops_test.go diff --git a/e2e/default_clusters.go b/e2e/default_clusters.go index 373957a89..8765afb63 100644 --- a/e2e/default_clusters.go +++ b/e2e/default_clusters.go @@ -22,6 +22,9 @@ func init() { return } + defaultCluster1.CRDDirectoryPaths = []string{"../config/crd/bases"} + defaultCluster2.CRDDirectoryPaths = []string{"../config/crd/bases"} + var wg sync.WaitGroup wg.Add(2) go func() { diff --git a/e2e/gitops_test.go b/e2e/gitops_test.go new file mode 100644 index 000000000..56d1cdfe0 --- /dev/null +++ b/e2e/gitops_test.go @@ -0,0 +1,609 @@ +package e2e + +import ( + "context" + "encoding/json" + kluctlv1 "github.com/kluctl/kluctl/v2/api/v1beta1" + test_utils "github.com/kluctl/kluctl/v2/e2e/test-utils" + types2 "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/onsi/gomega" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "strings" + "testing" + "time" + + . "github.com/onsi/gomega" +) + +const ( + timeout = time.Second * 300 + interval = time.Second * 5 +) + +func startKluctlController(t *testing.T) context.CancelFunc { + ctx, ctxCancel := context.WithCancel(context.Background()) + args := []string{ + "controller", + } + done := make(chan struct{}) + go func() { + _, _, err := test_utils.KluctlExecute(t, ctx, args...) + if err != nil { + t.Error(err) + } + close(done) + }() + + cancel := func() { + ctxCancel() + <-done + } + + t.Cleanup(cancel) + return cancel +} + +func waitForReconcile(t *testing.T, k *test_utils.EnvTestCluster, key client.ObjectKey) { + g := gomega.NewWithT(t) + + var kd kluctlv1.KluctlDeployment + err := k.Client.Get(context.TODO(), key, &kd) + assert.NoError(t, err) + + var lastReconcileTime metav1.Time + if kd.Status.LastDeployResult != nil { + lastReconcileTime = kd.Status.LastDeployResult.Command.StartTime + } + g.Eventually(func() bool { + err = k.Client.Get(context.TODO(), key, &kd) + g.Expect(err).To(Succeed()) + if kd.Status.LastDeployResult == nil { + return false + } + return kd.Status.LastDeployResult.Command.StartTime != lastReconcileTime + }, timeout, time.Second).Should(BeTrue()) +} + +func waitForCommit(t *testing.T, k *test_utils.EnvTestCluster, key client.ObjectKey, commit string) { + g := gomega.NewWithT(t) + + g.Eventually(func() bool { + var obj kluctlv1.KluctlDeployment + _ = k.Client.Get(context.Background(), key, &obj) + if obj.Status.LastDeployResult == nil { + return false + } + return obj.Status.LastDeployResult.GitInfo.Commit == commit + }, timeout, time.Second).Should(BeTrue()) +} + +func createKluctlDeployment(t *testing.T, p *test_utils.TestProject, k *test_utils.EnvTestCluster, target string, args map[string]any) client.ObjectKey { + gitopsNs := p.TestSlug() + "-gitops" + createNamespace(t, k, gitopsNs) + + jargs, err := json.Marshal(args) + if err != nil { + t.Fatal(err) + } + + kluctlDeployment := &kluctlv1.KluctlDeployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: p.TestSlug(), + Namespace: gitopsNs, + }, + Spec: kluctlv1.KluctlDeploymentSpec{ + Interval: metav1.Duration{Duration: interval}, + Timeout: &metav1.Duration{Duration: timeout}, + Target: &target, + Args: runtime.RawExtension{ + Raw: jargs, + }, + Source: kluctlv1.ProjectSource{ + URL: *types2.ParseGitUrlMust(p.GitUrl()), + }, + }, + } + + err = k.Client.Create(context.Background(), kluctlDeployment) + if err != nil { + t.Fatal(err) + } + + return client.ObjectKeyFromObject(kluctlDeployment) +} + +func TestGitOpsFieldManager(t *testing.T) { + g := NewWithT(t) + + startKluctlController(t) + + k := defaultCluster1 + + p := test_utils.NewTestProject(t) + createNamespace(t, k, p.TestSlug()) + + p.UpdateTarget("target1", nil) + p.AddKustomizeDeployment("d1", []test_utils.KustomizeResource{ + {Name: "cm1.yaml", Content: uo.FromStringMust(`apiVersion: v1 +kind: ConfigMap +metadata: + name: cm1 + namespace: "{{ args.namespace }}" +data: + k1: v1 + k2: "{{ args.k2 + 1 }}" +`)}, + }, nil) + + key := createKluctlDeployment(t, p, k, "target1", map[string]any{ + "namespace": p.TestSlug(), + "k2": 42, + }) + + t.Run("initial deployment", func(t *testing.T) { + waitForCommit(t, k, key, getHeadRevision(t, p)) + }) + + var kd kluctlv1.KluctlDeployment + err := k.Client.Get(context.TODO(), key, &kd) + g.Expect(err).To(Succeed()) + + kd.Spec.DeployInterval = &kluctlv1.SafeDuration{Duration: metav1.Duration{Duration: interval}} + err = k.Client.Update(context.TODO(), &kd) + g.Expect(err).To(Succeed()) + + cm := &corev1.ConfigMap{} + + t.Run("cm1 is deployed", func(t *testing.T) { + err := k.Client.Get(context.TODO(), client.ObjectKey{ + Name: "cm1", + Namespace: p.TestSlug(), + }, cm) + g.Expect(err).To(Succeed()) + g.Expect(cm.Data).To(HaveKeyWithValue("k1", "v1")) + g.Expect(cm.Data).To(HaveKeyWithValue("k2", "43")) + }) + + t.Run("cm1 is modified and restored", func(t *testing.T) { + cm.Data["k1"] = "v2" + err := k.Client.Update(context.TODO(), cm, client.FieldOwner("kubectl")) + g.Expect(err).To(Succeed()) + + g.Eventually(func() bool { + err := k.Client.Get(context.TODO(), client.ObjectKey{ + Name: "cm1", + Namespace: p.TestSlug(), + }, cm) + g.Expect(err).To(Succeed()) + return cm.Data["k1"] == "v1" + }, timeout, time.Second).Should(BeTrue()) + }) + + t.Run("cm1 gets a key added which is not modified by the controller", func(t *testing.T) { + cm.Data["k1"] = "v2" + cm.Data["k3"] = "v3" + err := k.Client.Update(context.TODO(), cm, client.FieldOwner("kubectl")) + g.Expect(err).To(Succeed()) + + g.Eventually(func() bool { + err := k.Client.Get(context.TODO(), client.ObjectKey{ + Name: "cm1", + Namespace: p.TestSlug(), + }, cm) + g.Expect(err).To(Succeed()) + return cm.Data["k1"] == "v1" + }, timeout, time.Second).Should(BeTrue()) + + g.Expect(cm.Data).To(HaveKeyWithValue("k3", "v3")) + }) + + t.Run("cm1 gets modified with another field manager", func(t *testing.T) { + patch := client.MergeFrom(cm.DeepCopy()) + cm.Data["k1"] = "v2" + + err := k.Client.Patch(context.TODO(), cm, patch, client.FieldOwner("test-field-manager")) + g.Expect(err).To(Succeed()) + + for i := 0; i < 2; i++ { + waitForReconcile(t, k, key) + } + + err = k.Client.Get(context.TODO(), client.ObjectKey{ + Name: "cm1", + Namespace: p.TestSlug(), + }, cm) + g.Expect(err).To(Succeed()) + g.Expect(cm.Data).To(HaveKeyWithValue("k1", "v2")) + }) + + err = k.Client.Get(context.TODO(), key, &kd) + g.Expect(err).To(Succeed()) + + kd.Spec.ForceApply = true + err = k.Client.Update(context.TODO(), &kd) + g.Expect(err).To(Succeed()) + + t.Run("forceApply is true and cm1 gets restored even with another field manager", func(t *testing.T) { + patch := client.MergeFrom(cm.DeepCopy()) + cm.Data["k1"] = "v2" + + err := k.Client.Patch(context.TODO(), cm, patch, client.FieldOwner("test-field-manager")) + g.Expect(err).To(Succeed()) + + g.Eventually(func() bool { + err := k.Client.Get(context.TODO(), client.ObjectKey{ + Name: "cm1", + Namespace: p.TestSlug(), + }, cm) + g.Expect(err).To(Succeed()) + return cm.Data["k1"] == "v1" + }, timeout, time.Second).Should(BeTrue()) + }) +} + +func TestKluctlDeploymentReconciler_Helm(t *testing.T) { + g := NewWithT(t) + startKluctlController(t) + + k := defaultCluster1 + + p := test_utils.NewTestProject(t) + createNamespace(t, k, p.TestSlug()) + + p.UpdateTarget("target1", nil) + + repoUrl := test_utils.CreateHelmRepo(t, []test_utils.RepoChart{ + {ChartName: "test-chart1", Version: "0.1.0"}, + }, "", "") + repoUrlWithCreds := test_utils.CreateHelmRepo(t, []test_utils.RepoChart{ + {ChartName: "test-chart2", Version: "0.1.0"}, + }, "test-user", "test-password") + ociRepoUrlWithCreds := test_utils.CreateOciRepo(t, []test_utils.RepoChart{ + {ChartName: "test-chart3", Version: "0.1.0"}, + }, "test-user", "test-password") + + p.AddHelmDeployment("d1", repoUrl, "test-chart1", "0.1.0", "test-helm-1", p.TestSlug(), nil) + p.UpdateYaml("d1/helm-chart.yaml", func(o *uo.UnstructuredObject) error { + _ = o.SetNestedField(true, "helmChart", "skipPrePull") + return nil + }, "") + + key := createKluctlDeployment(t, p, k, "target1", map[string]any{ + "namespace": p.TestSlug(), + }) + + waitForCommit(t, k, key, getHeadRevision(t, p)) + + cm := &corev1.ConfigMap{} + + t.Run("chart got deployed", func(t *testing.T) { + err := k.Client.Get(context.TODO(), client.ObjectKey{ + Name: "test-helm-1-test-chart1", + Namespace: p.TestSlug(), + }, cm) + g.Expect(err).To(Succeed()) + g.Expect(cm.Data).To(HaveKeyWithValue("a", "v1")) + }) + + p.AddHelmDeployment("d2", repoUrlWithCreds, "test-chart2", "0.1.0", "test-helm-2", p.TestSlug(), nil) + p.UpdateYaml("d2/helm-chart.yaml", func(o *uo.UnstructuredObject) error { + _ = o.SetNestedField(true, "helmChart", "skipPrePull") + return nil + }, "") + + kd := &kluctlv1.KluctlDeployment{} + + t.Run("chart with credentials fails with 401", func(t *testing.T) { + g.Eventually(func() bool { + err := k.Client.Get(context.TODO(), key, kd) + g.Expect(err).To(Succeed()) + for _, c := range kd.Status.Conditions { + _ = c + if c.Type == "Ready" && c.Reason == "PrepareFailed" && strings.Contains(c.Message, "401 Unauthorized") { + return true + } + } + return false + }, timeout, time.Second).Should(BeTrue()) + }) + + credsSecret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: key.Namespace, + Name: "helm-secrets-1", + }, + Data: map[string][]byte{ + "url": []byte(repoUrlWithCreds), + "username": []byte("test-user"), + "password": []byte("test-password"), + }, + } + err := k.Client.Create(context.TODO(), credsSecret) + g.Expect(err).To(Succeed()) + + kd.Spec.HelmCredentials = append(kd.Spec.HelmCredentials, kluctlv1.HelmCredentials{SecretRef: kluctlv1.LocalObjectReference{Name: "helm-secrets-1"}}) + err = k.Client.Update(context.TODO(), kd) + g.Expect(err).To(Succeed()) + + t.Run("chart with credentials succeeds", func(t *testing.T) { + g.Eventually(func() bool { + err := k.Client.Get(context.TODO(), client.ObjectKey{ + Name: "test-helm-2-test-chart2", + Namespace: p.TestSlug(), + }, cm) + if err != nil { + return false + } + g.Expect(cm.Data).To(HaveKeyWithValue("a", "v1")) + return true + }, timeout, time.Second).Should(BeTrue()) + }) + + p.AddHelmDeployment("d3", ociRepoUrlWithCreds, "test-chart3", "0.1.0", "test-helm-3", p.TestSlug(), nil) + p.UpdateYaml("d3/helm-chart.yaml", func(o *uo.UnstructuredObject) error { + _ = o.SetNestedField(true, "helmChart", "skipPrePull") + return nil + }, "") + + t.Run("OCI chart with credentials fails with 401", func(t *testing.T) { + g.Eventually(func() bool { + err = k.Client.Get(context.TODO(), key, kd) + g.Expect(err).To(Succeed()) + for _, c := range kd.Status.Conditions { + _ = c + if c.Type == "Ready" && c.Reason == "PrepareFailed" && strings.Contains(c.Message, "401 Unauthorized") { + return true + } + } + return false + }, timeout, time.Second).Should(BeTrue()) + }) + + /* + TODO enable this when Kluctl supports OCI authentication + url, err := url2.Parse(ociRepoUrlWithCreds) + g.Expect(err).To(Succeed()) + + dockerJson := map[string]any{ + "auths": map[string]any{ + url.Host: map[string]any{ + "username": "test-user", + "password": "test-password,", + "auth": base64.StdEncoding.EncodeToString([]byte("test-user:test-password")), + }, + }, + } + dockerJsonStr, err := json.Marshal(dockerJson) + g.Expect(err).To(Succeed()) + + credsSecret2 := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: "helm-secrets-2", + }, + Data: map[string][]byte{ + "url": []byte(ociRepoUrlWithCreds), + ".dockerconfigjson": dockerJsonStr, + }, + } + err = k.Client.Create(context.TODO(), credsSecret2) + g.Expect(err).To(Succeed()) + + kluctlDeployment.Spec.HelmCredentials = append(kluctlDeployment.Spec.HelmCredentials, flux_utils.LocalObjectReference{Name: "helm-secrets-2"}) + err = k.Client.Update(context.TODO(), kluctlDeployment) + g.Expect(err).To(Succeed()) + + t.Run("OCI chart with credentials succeeds", func(t *testing.T) { + g.Eventually(func() bool { + err := k.Client.Get(context.TODO(), client.ObjectKey{ + Name: "test-helm-3-test-chart3", + Namespace: namespace, + }, cm) + if err != nil { + return false + } + g.Expect(cm.Data).To(HaveKeyWithValue("a", "v1")) + return true + }, timeout, time.Second).Should(BeTrue()) + })*/ +} + +func TestKluctlDeploymentReconciler_Prune(t *testing.T) { + g := NewWithT(t) + startKluctlController(t) + + k := defaultCluster1 + + p := test_utils.NewTestProject(t) + createNamespace(t, k, p.TestSlug()) + + p.UpdateTarget("target1", nil) + + p.AddKustomizeDeployment("d1", []test_utils.KustomizeResource{ + {Name: "cm1.yaml", Content: uo.FromStringMust(`apiVersion: v1 +kind: ConfigMap +metadata: + name: cm1 + namespace: "{{ args.namespace }}" +data: + k1: v1 +`)}, + }, nil) + p.AddKustomizeDeployment("d2", []test_utils.KustomizeResource{ + {Name: "cm2.yaml", Content: uo.FromStringMust(`apiVersion: v1 +kind: ConfigMap +metadata: + name: cm2 + namespace: "{{ args.namespace }}" +data: + k1: v1 +`)}, + }, nil) + + key := createKluctlDeployment(t, p, k, "target1", map[string]any{ + "namespace": p.TestSlug(), + }) + + waitForCommit(t, k, key, getHeadRevision(t, p)) + + cm := &corev1.ConfigMap{} + + t.Run("cm1 and cm2 got deployed", func(t *testing.T) { + err := k.Client.Get(context.TODO(), client.ObjectKey{ + Name: "cm1", + Namespace: p.TestSlug(), + }, cm) + g.Expect(err).To(Succeed()) + err = k.Client.Get(context.TODO(), client.ObjectKey{ + Name: "cm2", + Namespace: p.TestSlug(), + }, cm) + g.Expect(err).To(Succeed()) + }) + + p.UpdateDeploymentYaml("", func(o *uo.UnstructuredObject) error { + _ = o.RemoveNestedField("deployments", 1) + return nil + }) + + g.Eventually(func() bool { + var obj kluctlv1.KluctlDeployment + _ = k.Client.Get(context.Background(), key, &obj) + if obj.Status.LastDeployResult == nil { + return false + } + return obj.Status.LastDeployResult.GitInfo.Commit == getHeadRevision(t, p) + }, timeout, time.Second).Should(BeTrue()) + + t.Run("cm1 and cm2 were not deleted", func(t *testing.T) { + err := k.Client.Get(context.TODO(), client.ObjectKey{ + Name: "cm1", + Namespace: p.TestSlug(), + }, cm) + g.Expect(err).To(Succeed()) + err = k.Client.Get(context.TODO(), client.ObjectKey{ + Name: "cm2", + Namespace: p.TestSlug(), + }, cm) + g.Expect(err).To(Succeed()) + }) + + kd := &kluctlv1.KluctlDeployment{} + g.Expect(k.Client.Get(context.TODO(), key, kd)).To(Succeed()) + kd.Spec.Prune = true + + g.Expect(k.Client.Update(context.TODO(), kd)).To(Succeed()) + g.Eventually(func() bool { + var obj kluctlv1.KluctlDeployment + _ = k.Client.Get(context.Background(), key, &obj) + if obj.Status.LastDeployResult == nil { + return false + } + return obj.Status.ObservedGeneration == obj.Generation && obj.Status.LastDeployResult.GitInfo.Commit == getHeadRevision(t, p) + }, timeout, time.Second).Should(BeTrue()) + + t.Run("cm1 did not get deleted and cm2 got deleted", func(t *testing.T) { + err := k.Client.Get(context.TODO(), client.ObjectKey{ + Name: "cm1", + Namespace: p.TestSlug(), + }, cm) + g.Expect(err).To(Succeed()) + err = k.Client.Get(context.TODO(), client.ObjectKey{ + Name: "cm2", + Namespace: p.TestSlug(), + }, cm) + g.Expect(err).To(MatchError("configmaps \"cm2\" not found")) + }) +} + +func doTestDelete(t *testing.T, delete bool) { + g := NewWithT(t) + startKluctlController(t) + + k := defaultCluster1 + + p := test_utils.NewTestProject(t) + createNamespace(t, k, p.TestSlug()) + + p.UpdateTarget("target1", nil) + + p.AddKustomizeDeployment("d1", []test_utils.KustomizeResource{ + {Name: "cm1.yaml", Content: uo.FromStringMust(`apiVersion: v1 +kind: ConfigMap +metadata: + name: cm1 + namespace: "{{ args.namespace }}" +data: + k1: v1 +`)}, + }, nil) + + key := createKluctlDeployment(t, p, k, "target1", map[string]any{ + "namespace": p.TestSlug(), + }) + + kd := &kluctlv1.KluctlDeployment{} + err := k.Client.Get(context.TODO(), key, kd) + g.Expect(err).To(Succeed()) + + kd.Spec.Delete = delete + g.Expect(k.Client.Update(context.TODO(), kd)).To(Succeed()) + + waitForCommit(t, k, key, getHeadRevision(t, p)) + + cm := &corev1.ConfigMap{} + + t.Run("cm1 got deployed", func(t *testing.T) { + err := k.Client.Get(context.TODO(), client.ObjectKey{ + Name: "cm1", + Namespace: p.TestSlug(), + }, cm) + g.Expect(err).To(Succeed()) + }) + + g.Expect(k.Client.Delete(context.TODO(), kd)).To(Succeed()) + + g.Eventually(func() bool { + var obj kluctlv1.KluctlDeployment + err := k.Client.Get(context.Background(), key, &obj) + if err == nil { + return false + } + if !errors.IsNotFound(err) { + return false + } + return true + }, timeout, time.Second).Should(BeTrue()) + + if delete { + t.Run("cm1 was deleted", func(t *testing.T) { + err := k.Client.Get(context.TODO(), client.ObjectKey{ + Name: "cm1", + Namespace: p.TestSlug(), + }, cm) + g.Expect(err).To(MatchError("configmaps \"cm1\" not found")) + }) + } else { + t.Run("cm1 was not deleted", func(t *testing.T) { + err := k.Client.Get(context.TODO(), client.ObjectKey{ + Name: "cm1", + Namespace: p.TestSlug(), + }, cm) + g.Expect(err).To(Succeed()) + }) + } +} + +func TestKluctlDeploymentReconciler_Delete_True(t *testing.T) { + doTestDelete(t, true) +} + +func TestKluctlDeploymentReconciler_Delete_False(t *testing.T) { + doTestDelete(t, false) +} diff --git a/e2e/utils.go b/e2e/utils.go index 9909bc07a..061402c77 100644 --- a/e2e/utils.go +++ b/e2e/utils.go @@ -15,14 +15,27 @@ import ( ) func createNamespace(t *testing.T, k *test_utils.EnvTestCluster, namespace string) { + r := k.DynamicClient.Resource(v1.SchemeGroupVersion.WithResource("namespaces")) + if _, err := r.Get(context.Background(), namespace, metav1.GetOptions{}); err == nil { + return + } + var ns unstructured.Unstructured ns.SetName(namespace) + _, err := r.Create(context.Background(), &ns, metav1.CreateOptions{}) - _, err := k.DynamicClient.Resource(v1.SchemeGroupVersion.WithResource("namespaces")).Create(context.Background(), &ns, metav1.CreateOptions{}) + if err != nil { + t.Fatal(err) + } +} +func getHeadRevision(t *testing.T, p *test_utils.TestProject) string { + r := p.GetGitRepo() + h, err := r.Head() if err != nil { t.Fatal(err) } + return h.Hash().String() } func assertObjectExists(t *testing.T, k *test_utils.EnvTestCluster, gvr schema.GroupVersionResource, namespace string, name string) *uo.UnstructuredObject { From 676725d274ad2225e831a0d13270f574f2df44db Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 15 May 2023 15:34:57 +0200 Subject: [PATCH 1635/2916] chore: Run go mod tidy --- go.mod | 24 ++++++++++++------------ go.sum | 26 ++++++++++++-------------- 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/go.mod b/go.mod index a815bcfcb..fb641e2f3 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,6 @@ require ( github.com/bitnami-labs/sealed-secrets v0.21.0 github.com/cyphar/filepath-securejoin v0.2.3 github.com/docker/distribution v2.8.2+incompatible - github.com/fluxcd/pkg/kustomize v1.1.1 github.com/go-playground/validator/v10 v10.13.0 github.com/gobwas/glob v0.2.3 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 @@ -53,18 +52,27 @@ require ( require ( filippo.io/age v1.1.1 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.1 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0 github.com/aws/aws-sdk-go-v2 v1.18.0 github.com/aws/aws-sdk-go-v2/config v1.18.25 + github.com/aws/aws-sdk-go-v2/credentials v1.13.24 github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.8 + github.com/aws/aws-sdk-go-v2/service/sts v1.19.0 + github.com/aws/smithy-go v1.13.5 + github.com/dimchansky/utfbom v1.1.1 github.com/go-git/go-git/v5 v5.6.1 github.com/go-logr/logr v1.2.4 github.com/google/uuid v1.3.0 github.com/hashicorp/go-multierror v1.1.1 + github.com/hashicorp/go-retryablehttp v0.7.1 github.com/huandu/xstrings v1.4.0 github.com/onsi/gomega v1.27.7 github.com/otiai10/copy v1.11.0 + github.com/prometheus/client_golang v1.15.1 github.com/sergi/go-diff v1.3.1 go.mozilla.org/sops/v3 v3.7.4-0.20220901181616-9124783930b1 + sigs.k8s.io/cli-utils v0.34.0 sigs.k8s.io/controller-runtime v0.15.0-beta.0 sigs.k8s.io/kustomize/api v0.13.4 sigs.k8s.io/yaml v1.3.0 @@ -76,8 +84,6 @@ require ( cloud.google.com/go/iam v0.13.0 // indirect cloud.google.com/go/kms v1.10.0 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 // indirect - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.1 // indirect - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.5.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.5.0 // indirect @@ -92,7 +98,6 @@ require ( github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect github.com/acomagu/bufpipe v1.0.4 // indirect github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.13.24 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27 // indirect @@ -101,8 +106,6 @@ require ( github.com/aws/aws-sdk-go-v2/service/kms v1.17.5 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.12.10 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.10 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.19.0 // indirect - github.com/aws/smithy-go v1.13.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver v3.5.1+incompatible // indirect github.com/cenkalti/backoff/v3 v3.2.2 // indirect @@ -119,22 +122,19 @@ require ( github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/drone/envsubst v1.0.3 // indirect github.com/emicklei/go-restful/v3 v3.10.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/evanphx/json-patch v5.6.0+incompatible // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect github.com/fatih/color v1.13.0 // indirect - github.com/fluxcd/go-git/v5 v5.0.0-20221219190809-2e5c9d01cfc4 // indirect - github.com/fluxcd/pkg/apis/kustomize v1.0.0 // indirect - github.com/fluxcd/pkg/sourceignore v0.3.3 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-git/gcfg v1.5.0 // indirect github.com/go-git/go-billy/v5 v5.4.1 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-logr/zapr v1.2.4 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.1 // indirect github.com/go-openapi/swag v0.22.3 // indirect @@ -158,7 +158,6 @@ require ( github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-hclog v1.4.0 // indirect - github.com/hashicorp/go-retryablehttp v0.7.1 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 // indirect github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect @@ -198,7 +197,6 @@ require ( github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.15.1 // indirect github.com/prometheus/client_model v0.4.0 // indirect github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect @@ -225,6 +223,8 @@ require ( go.opentelemetry.io/otel/trace v1.14.0 // indirect go.starlark.net v0.0.0-20221205180719-3fd0dac74452 // indirect go.uber.org/atomic v1.10.0 // indirect + go.uber.org/multierr v1.8.0 // indirect + go.uber.org/zap v1.24.0 // indirect golang.org/x/mod v0.10.0 // indirect golang.org/x/oauth2 v0.7.0 // indirect golang.org/x/time v0.3.0 // indirect diff --git a/go.sum b/go.sum index 40eb65340..e6e48f5e8 100644 --- a/go.sum +++ b/go.sum @@ -94,14 +94,12 @@ github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5 github.com/Microsoft/hcsshim v0.10.0-rc.7 h1:HBytQPxcv8Oy4244zbQbe6hnOnx544eL5QPUqhJldz8= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8= github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA= github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= github.com/a8m/expect v1.0.0/go.mod h1:4IwSCMumY49ScypDnjNbYEjgVeqy1/U2cEs3Lat96eA= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= -github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -150,6 +148,8 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.19.0/go.mod h1:BgQOMsg8av8jset59jely github.com/aws/smithy-go v1.12.0/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8= github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -212,6 +212,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= +github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 h1:aBfCb7iqHmDEIp6fBvC/hQUddQfg+3qdYjwzaiP9Hnc= github.com/dnaeon/go-vcr v1.1.0 h1:ReYa/UBrRyQdant9B4fNHGoCNKw6qh6P0fsdGmZpR7c= github.com/docker/cli v23.0.5+incompatible h1:ufWmAOuD3Vmr7JP2G5K3cyuNC4YZWiAsuDEvFVVDafE= @@ -231,8 +233,6 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4 github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g= -github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g= github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ= github.com/emicklei/go-restful/v3 v3.10.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= @@ -258,14 +258,6 @@ github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= -github.com/fluxcd/go-git/v5 v5.0.0-20221219190809-2e5c9d01cfc4 h1:Gm5sGGk+/Wq6RhX4xpCZ2IqjDp5XkjlhENaAuAlpdKc= -github.com/fluxcd/go-git/v5 v5.0.0-20221219190809-2e5c9d01cfc4/go.mod h1:raWgfUV7lDQVXp4QXUaeNNJkRVKz97UQuF+0kdY7Vmo= -github.com/fluxcd/pkg/apis/kustomize v1.0.0 h1:5T2b/mRZiGWtP7fvSU8gZOApIc06H6SdLX3MlsE6LRo= -github.com/fluxcd/pkg/apis/kustomize v1.0.0/go.mod h1:XaDYlKxrf9D2zZWcZ0BnSIqGtcm8mdNtJGzZWYjCnQo= -github.com/fluxcd/pkg/kustomize v1.1.1 h1:hYFJGi+fiaecY4gXvx52fumlvDEq/1RdFbaev67oBhE= -github.com/fluxcd/pkg/kustomize v1.1.1/go.mod h1:i+Z9iPAoSz28oH0FmDI73iqZ3oXZxQR2O3HfhdsWhfo= -github.com/fluxcd/pkg/sourceignore v0.3.3 h1:Ue29JAuPECEYdvIqdpXpQaDxpeySn7amarLArp7XoIs= -github.com/fluxcd/pkg/sourceignore v0.3.3/go.mod h1:yuJzKggph0Bdbk9LgXjJQhvJZSTJV/1vS7mJuB7mPa0= github.com/foxcpp/go-mockdns v1.0.0 h1:7jBqxd3WDWwi/6WhDvacvH1XsN3rOLXyHM1uhvIx6FI= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= @@ -304,6 +296,7 @@ github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbV github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo= +github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonreference v0.20.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTrLC1F86HID8= @@ -684,7 +677,6 @@ github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= -github.com/pjbgf/sha1cd v0.2.3/go.mod h1:HOK9QrgzdHpbc2Kzip0Q1yi3M2MFGPADtR6HjG65m5M= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ= @@ -822,7 +814,6 @@ github.com/vbatts/tar-split v0.11.3 h1:hLFqsOLQ1SsppQNTMpkpPXClLDfC2A3Zgy9OUU+RV github.com/vbatts/tar-split v0.11.3/go.mod h1:9QlHN18E+fEH7RdG+QAJJcuya3rqT7eXSTY7wGrAokY= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= -github.com/xanzy/ssh-agent v0.3.2/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= @@ -874,12 +865,16 @@ go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= +go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= golang.org/x/arch v0.1.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -1183,6 +1178,7 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= @@ -1383,6 +1379,8 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8 rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/cli-utils v0.34.0 h1:zCUitt54f0/MYj/ajVFnG6XSXMhpZ72O/3RewIchW8w= +sigs.k8s.io/cli-utils v0.34.0/go.mod h1:EXyMwPMu9OL+LRnj0JEMsGG/fRvbgFadcVlSnE8RhFs= sigs.k8s.io/controller-runtime v0.15.0-beta.0 h1:pkhYMops8jZrVuI0kBHeF6q9UVu1JljIGGG4Ox5ZJmk= sigs.k8s.io/controller-runtime v0.15.0-beta.0/go.mod h1:YUTa+du31rqOu4mJaijiuhGFax9ecCJgO/v0/yW09gE= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= From 07fd5e2fd17397f75c2e1eb0d66e47bd85f55de8 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 15 May 2023 15:43:36 +0200 Subject: [PATCH 1636/2916] chore: Run make replace-commands-help --- docs/reference/commands/controller.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/reference/commands/controller.md b/docs/reference/commands/controller.md index ff5fe4938..a7584c0b7 100644 --- a/docs/reference/commands/controller.md +++ b/docs/reference/commands/controller.md @@ -13,6 +13,9 @@ description: > Usage: kluctl controller [flags] +Run the Kluctl controller +This command will run the Kluctl Controller. This is usually meant to be run inside a cluster and not from your local machine. + ## Arguments @@ -20,6 +23,15 @@ Usage: kluctl controller [flags] The following arguments are available: ``` +Controller: + Controller arguments. + + --default-service-account string Default service account used for impersonation. + --dry-run Run all deployments in dryRun=true mode. + --health-probe-bind-address string The address the probe endpoint binds to. (default ":8081") + --leader-elect Enable leader election for controller manager. Enabling this will + ensure there is only one active controller manager. + --metrics-bind-address string The address the metric endpoint binds to. (default ":8080") ``` From 805f2bfc66f1550a86dadaa59824eb39846a86ef Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 15 May 2023 15:58:48 +0200 Subject: [PATCH 1637/2916] ci: Update Makefile to be controller-gen based --- Makefile | 231 +++++++++++++++++++++++++++---------------------------- 1 file changed, 113 insertions(+), 118 deletions(-) diff --git a/Makefile b/Makefile index d8f9ce4aa..aa370ad74 100644 --- a/Makefile +++ b/Makefile @@ -13,144 +13,139 @@ ifeq ($(GOOS), linux) RACE=-race endif -GOCMD=go -GOTEST=$(GOCMD) test -GOVET=$(GOCMD) vet -BINARY_NAME=kluctl$(EXE) -TEST_BINARY_NAME=kluctl-e2e$(EXE) -EXPORT_RESULT?=false - -# If gobin not set, create one on ./build and add to path. +# Image URL to use all building/pushing image targets +IMG ?= kluctl/kluctl:latest +# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. +ENVTEST_K8S_VERSION = 1.26.1 + +# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) ifeq (,$(shell go env GOBIN)) -GOBIN=$(BUILD_DIR)/gobin +GOBIN=$(shell go env GOPATH)/bin else GOBIN=$(shell go env GOBIN) endif -export PATH:=$(GOBIN):${PATH} -# Architecture to use envtest with -ENVTEST_ARCH ?= amd64 +# Setting SHELL to bash allows bash commands to be executed by recipes. +# Options are set to exit when a recipe line exits non-zero or a piped command fails. +SHELL = /usr/bin/env bash -o pipefail +.SHELLFLAGS = -ec -GREEN := $(shell tput -Txterm setaf 2) -YELLOW := $(shell tput -Txterm setaf 3) -WHITE := $(shell tput -Txterm setaf 7) -CYAN := $(shell tput -Txterm setaf 6) -RESET := $(shell tput -Txterm sgr0) +.PHONY: all +all: build -.PHONY: all test build +##@ General -all: help +# The help target prints out all targets with their descriptions organized +# beneath their categories. The categories are represented by '##@' and the +# target descriptions by '##'. The awk commands is responsible for reading the +# entire set of makefiles included in this invocation, looking for lines of the +# file as xyz: ## something, and then pretty-format the target and help. Then, +# if there's a line with ##@ something, that gets pretty-printed as a category. +# More info on the usage of ANSI control characters for terminal formatting: +# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters +# More info on the awk command: +# http://linuxcommand.org/lc3_adv_awk.php -## Build: -build: build-go ## Run the complete build pipeline +.PHONY: help +help: ## Display this help. + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) -build-go: ## Build your project and put the output binary in ./bin/ - mkdir -p ./bin - $(GOCMD) build -o ./bin/$(BINARY_NAME) +##@ Development -clean: ## Remove build related file - rm -fr ./bin - rm -fr ./out - rm -fr ./reports - rm -fr ./download-python +.PHONY: manifests +manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. + $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases -## Envtest setup -# Repository root based on Git metadata -REPOSITORY_ROOT := $(shell git rev-parse --show-toplevel) -BUILD_DIR := $(REPOSITORY_ROOT)/build -ENVTEST = $(GOBIN)/setup-envtest -.PHONY: envtest -setup-envtest: ## Download envtest-setup locally if necessary. - $(call go-install-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest@latest) +.PHONY: generate +generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. + $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." -# Download the envtest binaries to testbin -ENVTEST_ASSETS_DIR=$(BUILD_DIR)/testbin -ENVTEST_KUBERNETES_VERSION?=latest -install-envtest: setup-envtest - mkdir -p ${ENVTEST_ASSETS_DIR} - $(ENVTEST) use $(ENVTEST_KUBERNETES_VERSION) --arch=$(ENVTEST_ARCH) --bin-dir=$(ENVTEST_ASSETS_DIR) +.PHONY: fmt +fmt: ## Run go fmt against code. + go fmt ./... -## Test: -test: test-unit test-e2e ## Runs the complete test suite +.PHONY: vet +vet: ## Run go vet against code. + go vet ./... -KUBEBUILDER_ASSETS?="$(shell $(ENVTEST) --arch=$(ENVTEST_ARCH) use -i $(ENVTEST_KUBERNETES_VERSION) --bin-dir=$(ENVTEST_ASSETS_DIR) -p path)" -test-e2e: test-e2e-build test-e2e-pre-built ## Runs the end to end tests +.PHONY: test +test: manifests generate fmt vet envtest ## Run tests. + KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test $(RACE) ./... -coverprofile cover.out -test-e2e-build: ## Builds the end to end tests - $(GOCMD) test $(RACE) -c ./e2e -o ./bin/$(TEST_BINARY_NAME) +replace-commands-help: ## Replace commands help in docs + go run ./internal/replace-commands-help --docs-dir ./docs/reference/commands -test-e2e-pre-built: install-envtest - KUBEBUILDER_ASSETS=$(KUBEBUILDER_ASSETS) ./bin/$(TEST_BINARY_NAME) -test.v +MARKDOWN_LINK_CHECK_VERSION=3.11.2 +markdown-link-check: ## Check markdown files for dead links + find . -name '*.md' | xargs docker run -v ${PWD}:/tmp:ro --rm -i -w /tmp ghcr.io/tcort/markdown-link-check:$(MARKDOWN_LINK_CHECK_VERSION) -test-unit: ## Run the unit tests of the project -ifeq ($(EXPORT_RESULT), true) - mkdir -p reports/test-unit - GO111MODULE=off $(GOCMD) get -u github.com/jstemmer/go-junit-report - $(eval OUTPUT_OPTIONS = | tee /dev/tty | go-junit-report -set-exit-code > reports/test-unit/junit-report.xml) -endif - $(GOTEST) -v $(RACE) $(shell go list ./... | grep -v 'v2/e2e') $(OUTPUT_OPTIONS) - -coverage-unit: ## Run the unit tests of the project and export the coverage - $(GOTEST) -cover -covermode=count -coverprofile=reports/coverage-unit/profile.cov $(shell go list ./... | grep -v /e2e/) - $(GOCMD) tool cover -func profile.cov -ifeq ($(EXPORT_RESULT), true) - mkdir -p reports/coverage-unit - GO111MODULE=off $(GOCMD) get -u github.com/AlekSi/gocov-xml - GO111MODULE=off $(GOCMD) get -u github.com/axw/gocov/gocov - gocov convert reports/coverage-unit/profile.cov | gocov-xml > reports/coverage-unit/coverage.xml -endif +##@ Build -## Lint: -lint: lint-go ## Run all available linters +.PHONY: build +build: manifests generate fmt vet ## Build manager binary. + go build -o bin/kluctl$(EXE) cmd/main.go -lint-go: ## Use golintci-lint on your project -ifeq ($(EXPORT_RESULT), true) - mkdir -p reports/lint-go - $(eval OUTPUT_OPTIONS = $(shell echo "--out-format checkstyle ./... | tee /dev/tty > reports/lint-go/checkstyle-report.xml" )) -else - $(eval OUTPUT_OPTIONS = $(shell echo "")) -endif - docker run --rm -v $(shell pwd):/app -w /app golangci/golangci-lint:latest-alpine golangci-lint run $(OUTPUT_OPTIONS) +.PHONY: run +run: manifests generate fmt vet ## Run a controller from your host. + go run ./cmd/main.go -replace-commands-help: ## Replace commands help in docs - $(GOCMD) run ./internal/replace-commands-help --docs-dir ./docs/reference/commands -MARKDOWN_LINK_CHECK_VERSION=3.10.3 # warning, 3.11.x is broken -markdown-link-check: ## Check markdown files for dead links - find . -name '*.md' | xargs docker run -v ${PWD}:/tmp:ro --rm -i -w /tmp ghcr.io/tcort/markdown-link-check:$(MARKDOWN_LINK_CHECK_VERSION) +##@ Deployment -## Release: -version: ## Write next version into version file - $(GOCMD) install github.com/bvieira/sv4git/v2/cmd/git-sv@v2.7.0 - $(eval KLUCTL_VERSION:=$(shell git sv next-version)) - sed -ibak "s/0.0.0/$(KLUCTL_VERSION)/g" pkg/version/version.go - -changelog: ## Generating changelog - git sv changelog -n 1 > CHANGELOG.md - -## Help: -help: ## Show this help. - @echo '' - @echo 'Usage:' - @echo ' ${YELLOW}make${RESET} ${GREEN}${RESET}' - @echo '' - @echo 'Targets:' - @awk 'BEGIN {FS = ":.*?## "} { \ - if (/^[0-9a-zA-Z_-]+:.*?##.*$$/) {printf " ${YELLOW}%-20s${GREEN}%s${RESET}\n", $$1, $$2} \ - else if (/^## .*$$/) {printf " ${CYAN}%s${RESET}\n", substr($$1,4)} \ - }' $(MAKEFILE_LIST) - -## Tools -# go-install-tool will 'go install' any package $2 and install it to $1. -PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST)))) -define go-install-tool -@[ -f $(1) ] || { \ -set -e ;\ -TMP_DIR=$$(mktemp -d) ;\ -cd $$TMP_DIR ;\ -go mod init tmp ;\ -echo "Downloading $(2)" ;\ -GOBIN=$(GOBIN) go install $(2) ;\ -rm -rf $$TMP_DIR ;\ -} -endef +ifndef ignore-not-found + ignore-not-found = false +endif + +.PHONY: install +install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. + $(KUSTOMIZE) build config/crd | kubectl apply -f - + +.PHONY: uninstall +uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. + $(KUSTOMIZE) build config/crd | kubectl delete --ignore-not-found=$(ignore-not-found) -f - + +.PHONY: deploy +deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. + cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} + $(KUSTOMIZE) build config/default | kubectl apply -f - + +.PHONY: undeploy +undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. + $(KUSTOMIZE) build config/default | kubectl delete --ignore-not-found=$(ignore-not-found) -f - + +##@ Build Dependencies + +## Location to install dependencies to +LOCALBIN ?= $(shell pwd)/bin +$(LOCALBIN): + mkdir -p $(LOCALBIN) + +## Tool Binaries +KUSTOMIZE ?= $(LOCALBIN)/kustomize +CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen +ENVTEST ?= $(LOCALBIN)/setup-envtest + +## Tool Versions +KUSTOMIZE_VERSION ?= v5.0.3 +CONTROLLER_TOOLS_VERSION ?= v0.12.0 + +KUSTOMIZE_INSTALL_SCRIPT ?= "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" +.PHONY: kustomize +kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. If wrong version is installed, it will be removed before downloading. +$(KUSTOMIZE): $(LOCALBIN) + @if test -x $(LOCALBIN)/kustomize && ! $(LOCALBIN)/kustomize version | grep -q $(KUSTOMIZE_VERSION); then \ + echo "$(LOCALBIN)/kustomize version is not expected $(KUSTOMIZE_VERSION). Removing it before installing."; \ + rm -rf $(LOCALBIN)/kustomize; \ + fi + test -s $(LOCALBIN)/kustomize || { curl -Ss $(KUSTOMIZE_INSTALL_SCRIPT) --output install_kustomize.sh && bash install_kustomize.sh $(subst v,,$(KUSTOMIZE_VERSION)) $(LOCALBIN); rm install_kustomize.sh; } + +.PHONY: controller-gen +controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary. If wrong version is installed, it will be overwritten. +$(CONTROLLER_GEN): $(LOCALBIN) + test -s $(LOCALBIN)/controller-gen && $(LOCALBIN)/controller-gen --version | grep -q $(CONTROLLER_TOOLS_VERSION) || \ + GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_TOOLS_VERSION) + +.PHONY: envtest +envtest: $(ENVTEST) ## Download envtest-setup locally if necessary. +$(ENVTEST): $(LOCALBIN) + test -s $(LOCALBIN)/setup-envtest || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest From e9936ce67d1e8053a0f3d11eb667ad13104ffff1 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 15 May 2023 16:15:56 +0200 Subject: [PATCH 1638/2916] ci: Verify that make generate was called and committed --- .github/workflows/tests.yml | 11 ++++++++++- Makefile | 1 + 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e23e52c2b..b4d09f840 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -9,7 +9,7 @@ on: - main jobs: - docs-checks: + generate-checks: runs-on: ubuntu-latest steps: - name: Checkout @@ -39,6 +39,15 @@ jobs: git diff exit 1 fi + - name: Verify generated source is up-to-date + run: | + make generate + if [ ! -z "$(git status --porcelain)" ]; then + echo "make generate must be invoked and the result committed" + git status + git diff + exit 1 + fi tests: strategy: diff --git a/Makefile b/Makefile index aa370ad74..28db1bfd4 100644 --- a/Makefile +++ b/Makefile @@ -58,6 +58,7 @@ manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and Cust .PHONY: generate generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. + go generate ./... $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." .PHONY: fmt From 8b0eafb86205b6a0758ecce933625c25f5c12a1a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 15 May 2023 16:16:07 +0200 Subject: [PATCH 1639/2916] ci: Fix .goreleaser.yaml to use correct main --- .goreleaser.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 43a80732f..8d657a092 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -6,6 +6,7 @@ builds: binary: kluctl env: - CGO_ENABLED=0 + main: ./cmd id: linux goos: - linux From 0415b74d67f77e13232ba810a6782b88c40c2ebb Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 15 May 2023 17:19:25 +0200 Subject: [PATCH 1640/2916] ci: Add .dockerignore --- .dockerignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..9b1c8b133 --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +/dist From 84f09fcf161e6c9e811085dd8f2063eeff9ee2fd Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 15 May 2023 16:22:16 +0200 Subject: [PATCH 1641/2916] fix: Upgrade to alpine 3.18.0 to fix DNS resolver issues --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index f3ee702f7..478b37095 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.17.1 +FROM alpine:3.18.0 RUN apk add ca-certificates From ad6241a2a8b76d0392206e74ea127abb74160078 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 15 May 2023 16:22:42 +0200 Subject: [PATCH 1642/2916] fix: Add git to docker images and set HELM_CACHE_HOME=/tmp/helm-cache --- Dockerfile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Dockerfile b/Dockerfile index 478b37095..6a31351b5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,5 +2,11 @@ FROM alpine:3.18.0 RUN apk add ca-certificates +# We need git for kustomize to support overlays from git +RUN apk add git + +# Ensure helm is not trying to access / +ENV HELM_CACHE_HOME=/tmp/helm-cache + COPY kluctl /usr/bin/ ENTRYPOINT ["/usr/bin/kluctl"] From ccedfb7ffed23e8736ffa1e40228f94324c49b85 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 15 May 2023 16:56:40 +0200 Subject: [PATCH 1643/2916] fix: Run as non-root user --- Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Dockerfile b/Dockerfile index 6a31351b5..847404a86 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,4 +9,7 @@ RUN apk add git ENV HELM_CACHE_HOME=/tmp/helm-cache COPY kluctl /usr/bin/ + +USER 65532:65532 + ENTRYPOINT ["/usr/bin/kluctl"] From 0a6a5c1a9d554ea872f098812802cb89f187bcb1 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 15 May 2023 16:57:22 +0200 Subject: [PATCH 1644/2916] chore: Easy/Fast way to deploy to kind --- Dockerfile | 3 ++- Makefile | 8 ++++++++ config/default/kustomization.yaml | 2 +- config/manager/manager.yaml | 4 +++- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 847404a86..77958da58 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,8 @@ RUN apk add git # Ensure helm is not trying to access / ENV HELM_CACHE_HOME=/tmp/helm-cache -COPY kluctl /usr/bin/ +ARG BIN_PATH=kluctl +COPY $BIN_PATH /usr/bin/ USER 65532:65532 diff --git a/Makefile b/Makefile index 28db1bfd4..d640b9f2b 100644 --- a/Makefile +++ b/Makefile @@ -110,6 +110,14 @@ deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} $(KUSTOMIZE) build config/default | kubectl apply -f - +.PHONY: deploy-kind +deploy-kind: manifests kustomize + GOOS=linux GOARCH=amd64 make build + docker build -t kluctl-kind:latest --build-arg BIN_PATH=./bin/kluctl . + kind load docker-image kluctl-kind:latest + cd config/manager && $(KUSTOMIZE) edit set image controller=kluctl-kind + $(KUSTOMIZE) build config/default | kubectl apply -f - + .PHONY: undeploy undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. $(KUSTOMIZE) build config/default | kubectl delete --ignore-not-found=$(ignore-not-found) -f - diff --git a/config/default/kustomization.yaml b/config/default/kustomization.yaml index fc2356a99..ce073d33f 100644 --- a/config/default/kustomization.yaml +++ b/config/default/kustomization.yaml @@ -6,7 +6,7 @@ namespace: controller-system # "wordpress" becomes "alices-wordpress". # Note that it should also match with the prefix (text before '-') of the namespace # field above. -namePrefix: controller- +namePrefix: kluctl- # Labels to add to all resources and selectors. #labels: diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index d0a98b913..0eaa207d9 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -67,10 +67,12 @@ spec: # type: RuntimeDefault containers: - command: - - /manager + - kluctl + - controller args: - --leader-elect image: controller:latest + imagePullPolicy: IfNotPresent name: manager securityContext: allowPrivilegeEscalation: false From 01370abdfd5b0b0cd9aef6db8f346a84b8667643 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 15 May 2023 17:19:12 +0200 Subject: [PATCH 1645/2916] feat: Add full target object to result summary --- pkg/results/result-store-secrets.go | 6 +++--- pkg/results/result-store.go | 4 ++-- pkg/types/result/summary.go | 29 ++++++++++++++++------------- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/pkg/results/result-store-secrets.go b/pkg/results/result-store-secrets.go index f918e5b2e..82713e27f 100644 --- a/pkg/results/result-store-secrets.go +++ b/pkg/results/result-store-secrets.go @@ -229,7 +229,7 @@ func (s *ResultStoreSecrets) cleanupResults(project result.ProjectKey, target re for _, rs := range results { rs := rs - if rs.Target != target { + if rs.TargetKey != target { continue } cnt++ @@ -302,10 +302,10 @@ func (s *ResultStoreSecrets) parseSummary(obj client.Object) (*result.CommandRes func (s *ResultStoreSecrets) filterSummary(summary *result.CommandResultSummary, project *result.ProjectKey) bool { if project != nil { - if project.NormalizedGitUrl != "" && summary.Project.NormalizedGitUrl != project.NormalizedGitUrl { + if project.NormalizedGitUrl != "" && summary.ProjectKey.NormalizedGitUrl != project.NormalizedGitUrl { return false } - if project.SubDir != "" && summary.Project.SubDir != project.SubDir { + if project.SubDir != "" && summary.ProjectKey.SubDir != project.SubDir { return false } } diff --git a/pkg/results/result-store.go b/pkg/results/result-store.go index 46d002d5d..baa5e7313 100644 --- a/pkg/results/result-store.go +++ b/pkg/results/result-store.go @@ -35,10 +35,10 @@ func FilterSummary(x *result.CommandResultSummary, filter *result.ProjectKey) bo if filter == nil { return true } - if x.Project.NormalizedGitUrl != filter.NormalizedGitUrl { + if x.ProjectKey.NormalizedGitUrl != filter.NormalizedGitUrl { return false } - if x.Project.SubDir != filter.SubDir { + if x.ProjectKey.SubDir != filter.SubDir { return false } return true diff --git a/pkg/types/result/summary.go b/pkg/types/result/summary.go index 5cf639762..85327294a 100644 --- a/pkg/types/result/summary.go +++ b/pkg/types/result/summary.go @@ -1,16 +1,18 @@ package result import ( + "github.com/kluctl/kluctl/v2/pkg/types" "sort" ) type CommandResultSummary struct { - Id string `json:"id"` - Project ProjectKey `json:"project"` - Target TargetKey `json:"target"` - Command CommandInfo `json:"commandInfo"` - GitInfo GitInfo `json:"gitInfo,omitempty"` - ClusterInfo ClusterInfo `json:"clusterInfo,omitempty"` + Id string `json:"id"` + ProjectKey ProjectKey `json:"projectKey"` + TargetKey TargetKey `json:"targetKey"` + Target types.Target `json:"target"` + Command CommandInfo `json:"commandInfo"` + GitInfo GitInfo `json:"gitInfo,omitempty"` + ClusterInfo ClusterInfo `json:"clusterInfo,omitempty"` RenderedObjects int `json:"renderedObjects"` RemoteObjects int `json:"remoteObjects"` @@ -57,8 +59,9 @@ func (cr *CommandResult) BuildSummary() *CommandResultSummary { ret := &CommandResultSummary{ Id: cr.Id, - Project: cr.ProjectKey, - Target: cr.TargetKey, + ProjectKey: cr.ProjectKey, + TargetKey: cr.TargetKey, + Target: cr.Target, Command: cr.Command, GitInfo: cr.GitInfo, ClusterInfo: cr.ClusterInfo, @@ -82,22 +85,22 @@ func (cr *CommandResult) BuildSummary() *CommandResultSummary { func BuildProjectSummaries(summaries []CommandResultSummary) []*ProjectSummary { m := map[ProjectKey]*ProjectSummary{} for _, rs := range summaries { - p, ok := m[rs.Project] + p, ok := m[rs.ProjectKey] if !ok { - p = &ProjectSummary{Project: rs.Project} - m[rs.Project] = p + p = &ProjectSummary{Project: rs.ProjectKey} + m[rs.ProjectKey] = p } var target *TargetSummary for i, t := range p.Targets { - if t.Target == rs.Target { + if t.Target == rs.TargetKey { target = p.Targets[i] break } } if target == nil { target = &TargetSummary{ - Target: rs.Target, + Target: rs.TargetKey, } p.Targets = append(p.Targets, target) } From 6e1a948d1eb7e06893afab573c68912afa7f2bcb Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 17 May 2023 12:44:17 +0200 Subject: [PATCH 1646/2916] feat: Let the controller write command results --- cmd/kluctl/commands/cmd_controller.go | 2 +- pkg/controllers/kluctl_project.go | 7 ++++++- pkg/controllers/kluctldeployment_controller.go | 3 +++ pkg/controllers/kluctldeployment_controller_setup.go | 10 +++++++++- 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/cmd/kluctl/commands/cmd_controller.go b/cmd/kluctl/commands/cmd_controller.go index 655a5071d..3e3f9df86 100644 --- a/cmd/kluctl/commands/cmd_controller.go +++ b/cmd/kluctl/commands/cmd_controller.go @@ -112,7 +112,7 @@ func (cmd *controllerCmd) Run(ctx context.Context) error { SshPool: sshPool, } - if err = r.SetupWithManager(mgr, controllers.KluctlDeploymentReconcilerOpts{ + if err = r.SetupWithManager(ctx, mgr, controllers.KluctlDeploymentReconcilerOpts{ HTTPRetry: 9, }); err != nil { setupLog.Error(err, "unable to create controller", "controller", kluctlv1.KluctlDeploymentKind) diff --git a/pkg/controllers/kluctl_project.go b/pkg/controllers/kluctl_project.go index 41396bd18..3eee59d44 100644 --- a/pkg/controllers/kluctl_project.go +++ b/pkg/controllers/kluctl_project.go @@ -649,6 +649,12 @@ func (pt *preparedTarget) handleCommandResult(ctx context.Context, cmdErr error, cmdResult.GitInfo.Ref = pt.pp.obj.Spec.Source.Ref.String() cmdResult.ProjectKey.NormalizedGitUrl = pt.pp.obj.Spec.Source.URL.NormalizedRepoKey() + log.Info(fmt.Sprintf("Writing command result %s", cmdResult.Id)) + err := pt.pp.r.ResultStore.WriteCommandResult(cmdResult) + if err != nil { + log.Error(err, "Writing command result failed") + } + summary := cmdResult.BuildSummary() log.Info(fmt.Sprintf("command finished with err=%v", cmdErr)) @@ -682,7 +688,6 @@ func (pt *preparedTarget) handleCommandResult(ctx context.Context, cmdErr error, } warning := false - var err error if len(cmdResult.Errors) != 0 { warning = true err = fmt.Errorf("%s failed with %d errors", commandName, len(cmdResult.Errors)) diff --git a/pkg/controllers/kluctldeployment_controller.go b/pkg/controllers/kluctldeployment_controller.go index 69670165a..cb4cfa953 100644 --- a/pkg/controllers/kluctldeployment_controller.go +++ b/pkg/controllers/kluctldeployment_controller.go @@ -8,6 +8,7 @@ import ( internal_metrics "github.com/kluctl/kluctl/v2/pkg/controllers/internal/metrics" ssh_pool "github.com/kluctl/kluctl/v2/pkg/git/ssh-pool" "github.com/kluctl/kluctl/v2/pkg/kluctl_project" + "github.com/kluctl/kluctl/v2/pkg/results" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types/result" "github.com/kluctl/kluctl/v2/pkg/utils/flux_utils/meta" @@ -39,6 +40,8 @@ type KluctlDeploymentReconciler struct { DryRun bool SshPool *ssh_pool.SshPool + + ResultStore results.ResultStore } // KluctlDeploymentReconcilerOpts contains options for the BaseReconciler. diff --git a/pkg/controllers/kluctldeployment_controller_setup.go b/pkg/controllers/kluctldeployment_controller_setup.go index cb6af53f7..61f66cba1 100644 --- a/pkg/controllers/kluctldeployment_controller_setup.go +++ b/pkg/controllers/kluctldeployment_controller_setup.go @@ -1,8 +1,10 @@ package controllers import ( + "context" "github.com/hashicorp/go-retryablehttp" kluctlv1 "github.com/kluctl/kluctl/v2/api/v1beta1" + "github.com/kluctl/kluctl/v2/pkg/results" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/predicate" @@ -10,7 +12,7 @@ import ( ) // SetupWithManager sets up the controller with the Manager. -func (r *KluctlDeploymentReconciler) SetupWithManager(mgr ctrl.Manager, opts KluctlDeploymentReconcilerOpts) error { +func (r *KluctlDeploymentReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, opts KluctlDeploymentReconcilerOpts) error { // Configure the retryable http client used for fetching artifacts. // By default it retries 10 times within a 3.5 minutes window. httpClient := retryablehttp.NewClient() @@ -20,6 +22,12 @@ func (r *KluctlDeploymentReconciler) SetupWithManager(mgr ctrl.Manager, opts Klu httpClient.Logger = nil r.httpClient = httpClient + resultStore, err := results.NewResultStoreSecrets(ctx, mgr.GetConfig(), "kluctl-results", 10) + if err != nil { + return err + } + r.ResultStore = resultStore + return ctrl.NewControllerManagedBy(mgr). For(&kluctlv1.KluctlDeployment{}, builder.WithPredicates( predicate.Or(predicate.GenerationChangedPredicate{}, ReconcileRequestedPredicate{}, DeployRequestedPredicate{}), From 322350b80a3c73632a0720a765e5b113789f8f22 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 17 May 2023 12:45:45 +0200 Subject: [PATCH 1647/2916] fix: Make summary sorting more stable --- pkg/results/result-store-secrets.go | 2 +- pkg/results/result-store.go | 10 ++++++++++ pkg/results/results-collector.go | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/pkg/results/result-store-secrets.go b/pkg/results/result-store-secrets.go index 82713e27f..038133321 100644 --- a/pkg/results/result-store-secrets.go +++ b/pkg/results/result-store-secrets.go @@ -276,7 +276,7 @@ func (s *ResultStoreSecrets) ListCommandResultSummaries(options ListCommandResul } sort.Slice(ret, func(i, j int) bool { - return ret[i].Command.StartTime.After(ret[j].Command.StartTime.Time) + return lessSummary(&ret[i], &ret[j]) }) return ret, nil diff --git a/pkg/results/result-store.go b/pkg/results/result-store.go index baa5e7313..fae620b41 100644 --- a/pkg/results/result-store.go +++ b/pkg/results/result-store.go @@ -43,3 +43,13 @@ func FilterSummary(x *result.CommandResultSummary, filter *result.ProjectKey) bo } return true } + +func lessSummary(a *result.CommandResultSummary, b *result.CommandResultSummary) bool { + if a.Command.StartTime != b.Command.StartTime { + return a.Command.StartTime.After(b.Command.StartTime.Time) + } + if a.Command.EndTime != b.Command.EndTime { + return a.Command.EndTime.After(b.Command.EndTime.Time) + } + return a.Command.Command < b.Command.Command +} diff --git a/pkg/results/results-collector.go b/pkg/results/results-collector.go index 5ae9cbd4a..053cbbe37 100644 --- a/pkg/results/results-collector.go +++ b/pkg/results/results-collector.go @@ -111,7 +111,7 @@ func (rc *ResultsCollector) ListCommandResultSummaries(options ListCommandResult summaries = append(summaries, *x.summary) } sort.Slice(summaries, func(i, j int) bool { - return summaries[i].Command.StartTime.After(summaries[j].Command.StartTime.Time) + return lessSummary(&summaries[i], &summaries[j]) }) return summaries, nil } From c5ea94913f77b9e1e366ee5ad86e92a25d043ca9 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 17 May 2023 12:46:06 +0200 Subject: [PATCH 1648/2916] feat: Add start/stop time to validation results --- pkg/deployment/commands/validate.go | 8 ++++++-- pkg/types/result/command_result.go | 12 +++++++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/pkg/deployment/commands/validate.go b/pkg/deployment/commands/validate.go index 1a1fee162..deb477af1 100644 --- a/pkg/deployment/commands/validate.go +++ b/pkg/deployment/commands/validate.go @@ -11,6 +11,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/types/result" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/validation" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type ValidateCommand struct { @@ -35,8 +36,9 @@ func NewValidateCommand(ctx context.Context, discriminator string, c *deployment func (cmd *ValidateCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*result.ValidateResult, error) { ret := result.ValidateResult{ - Id: uuid.New().String(), - Ready: true, + Id: uuid.New().String(), + StartTime: metav1.Now(), + Ready: true, } cmd.dew.Init() @@ -119,6 +121,8 @@ func (cmd *ValidateCommand) Run(ctx context.Context, k *k8s.K8sCluster) (*result ret.Drift = du.ChangedObjects } + ret.EndTime = metav1.Now() + return &ret, nil } diff --git a/pkg/types/result/command_result.go b/pkg/types/result/command_result.go index 3f8a948ac..ec899dbcf 100644 --- a/pkg/types/result/command_result.go +++ b/pkg/types/result/command_result.go @@ -186,11 +186,13 @@ type ValidateResultEntry struct { } type ValidateResult struct { - Id string `json:"id"` - Ready bool `json:"ready"` - Warnings []DeploymentError `json:"warnings,omitempty"` - Errors []DeploymentError `json:"errors,omitempty"` - Results []ValidateResultEntry `json:"results,omitempty"` + Id string `json:"id"` + StartTime metav1.Time `json:"startTime"` + EndTime metav1.Time `json:"endTime"` + Ready bool `json:"ready"` + Warnings []DeploymentError `json:"warnings,omitempty"` + Errors []DeploymentError `json:"errors,omitempty"` + Results []ValidateResultEntry `json:"results,omitempty"` Drift []ChangedObject `json:"drift,omitempty"` } From a3f4d2c7ea1bbaa64c7fcacbff4bff8a342e1b34 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 17 May 2023 12:46:48 +0200 Subject: [PATCH 1649/2916] refactor: Rename NormalizedGitUrl -> GitRepoKey --- pkg/controllers/kluctl_project.go | 2 +- pkg/controllers/kluctldeployment_controller.go | 4 ++-- pkg/deployment/commands/result_utils.go | 2 +- pkg/results/result-store-secrets.go | 10 +++++----- pkg/results/result-store.go | 2 +- pkg/types/result/command_result.go | 8 ++++---- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/pkg/controllers/kluctl_project.go b/pkg/controllers/kluctl_project.go index 3eee59d44..2317adce4 100644 --- a/pkg/controllers/kluctl_project.go +++ b/pkg/controllers/kluctl_project.go @@ -647,7 +647,7 @@ func (pt *preparedTarget) handleCommandResult(ctx context.Context, cmdErr error, cmdResult.Command.Initiator = result.CommandInititiator_KluctlDeployment cmdResult.GitInfo.Url = &pt.pp.obj.Spec.Source.URL cmdResult.GitInfo.Ref = pt.pp.obj.Spec.Source.Ref.String() - cmdResult.ProjectKey.NormalizedGitUrl = pt.pp.obj.Spec.Source.URL.NormalizedRepoKey() + cmdResult.ProjectKey.GitRepoKey = pt.pp.obj.Spec.Source.URL.NormalizedRepoKey() log.Info(fmt.Sprintf("Writing command result %s", cmdResult.Id)) err := pt.pp.r.ResultStore.WriteCommandResult(cmdResult) diff --git a/pkg/controllers/kluctldeployment_controller.go b/pkg/controllers/kluctldeployment_controller.go index cb4cfa953..7f432a097 100644 --- a/pkg/controllers/kluctldeployment_controller.go +++ b/pkg/controllers/kluctldeployment_controller.go @@ -183,8 +183,8 @@ func (r *KluctlDeploymentReconciler) doReconcile( err = pt.withKluctlProjectTarget(ctx, func(targetContext *kluctl_project.TargetContext) error { obj.Status.ProjectKey = &result.ProjectKey{ - NormalizedGitUrl: obj.Spec.Source.URL.NormalizedRepoKey(), - SubDir: path.Clean(obj.Spec.Source.Path), + GitRepoKey: obj.Spec.Source.URL.NormalizedRepoKey(), + SubDir: path.Clean(obj.Spec.Source.Path), } obj.Status.TargetKey = &result.TargetKey{ TargetName: targetContext.Target.Name, diff --git a/pkg/deployment/commands/result_utils.go b/pkg/deployment/commands/result_utils.go index 43c26f685..ee128c9cd 100644 --- a/pkg/deployment/commands/result_utils.go +++ b/pkg/deployment/commands/result_utils.go @@ -135,7 +135,7 @@ func addGitInfo(targetCtx *kluctl_project.TargetContext, r *result.CommandResult Commit: head.Hash().String(), Dirty: !s.IsClean(), } - r.ProjectKey.NormalizedGitUrl = normaliedUrl + r.ProjectKey.GitRepoKey = normaliedUrl r.ProjectKey.SubDir = subDir return nil } diff --git a/pkg/results/result-store-secrets.go b/pkg/results/result-store-secrets.go index 038133321..8425d11bd 100644 --- a/pkg/results/result-store-secrets.go +++ b/pkg/results/result-store-secrets.go @@ -99,8 +99,8 @@ var invalidChars = regexp.MustCompile(`[^a-zA-Z0-9-]`) func (s *ResultStoreSecrets) buildName(cr *result.CommandResult) string { var name string - if cr.ProjectKey.NormalizedGitUrl != "" { - s := path.Base(cr.ProjectKey.NormalizedGitUrl) + if cr.ProjectKey.GitRepoKey != "" { + s := path.Base(cr.ProjectKey.GitRepoKey) if s != "" { name = s + "-" } @@ -197,8 +197,8 @@ func (s *ResultStoreSecrets) WriteCommandResult(cr *result.CommandResult) error "compactedObjects": compressedObjects, }, } - if cr.ProjectKey.NormalizedGitUrl != "" { - secret.Annotations["kluctl.io/result-project-normalized-url"] = cr.ProjectKey.NormalizedGitUrl + if cr.ProjectKey.GitRepoKey != "" { + secret.Annotations["kluctl.io/result-project-normalized-url"] = cr.ProjectKey.GitRepoKey } if cr.ProjectKey.SubDir != "" { secret.Annotations["kluctl.io/result-project-subdir"] = cr.ProjectKey.SubDir @@ -302,7 +302,7 @@ func (s *ResultStoreSecrets) parseSummary(obj client.Object) (*result.CommandRes func (s *ResultStoreSecrets) filterSummary(summary *result.CommandResultSummary, project *result.ProjectKey) bool { if project != nil { - if project.NormalizedGitUrl != "" && summary.ProjectKey.NormalizedGitUrl != project.NormalizedGitUrl { + if project.GitRepoKey != "" && summary.ProjectKey.GitRepoKey != project.GitRepoKey { return false } if project.SubDir != "" && summary.ProjectKey.SubDir != project.SubDir { diff --git a/pkg/results/result-store.go b/pkg/results/result-store.go index fae620b41..ad01a76cd 100644 --- a/pkg/results/result-store.go +++ b/pkg/results/result-store.go @@ -35,7 +35,7 @@ func FilterSummary(x *result.CommandResultSummary, filter *result.ProjectKey) bo if filter == nil { return true } - if x.ProjectKey.NormalizedGitUrl != filter.NormalizedGitUrl { + if x.ProjectKey.GitRepoKey != filter.GitRepoKey { return false } if x.ProjectKey.SubDir != filter.SubDir { diff --git a/pkg/types/result/command_result.go b/pkg/types/result/command_result.go index ec899dbcf..e8b8bef4d 100644 --- a/pkg/types/result/command_result.go +++ b/pkg/types/result/command_result.go @@ -41,13 +41,13 @@ const ( ) type ProjectKey struct { - NormalizedGitUrl string `json:"normalizedGitUrl,omitempty"` - SubDir string `json:"subDir,omitempty"` + GitRepoKey string `json:"gitRepoKey,omitempty"` + SubDir string `json:"subDir,omitempty"` } func (k ProjectKey) Less(o ProjectKey) bool { - if k.NormalizedGitUrl != o.NormalizedGitUrl { - return k.NormalizedGitUrl < o.NormalizedGitUrl + if k.GitRepoKey != o.GitRepoKey { + return k.GitRepoKey < o.GitRepoKey } if k.SubDir != o.SubDir { return k.SubDir < o.SubDir From cfdf3a69edc28eec84bf2daefb3f4e82e054567b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 17 May 2023 17:07:04 +0200 Subject: [PATCH 1650/2916] refactor: Introduce dedicated GitRepoKey struct --- cmd/kluctl/commands/utils.go | 3 +- e2e/git_include_test.go | 6 +- pkg/controllers/kluctl_project.go | 2 +- .../kluctldeployment_controller.go | 2 +- pkg/deployment/commands/result_utils.go | 6 +- pkg/repocache/cache.go | 17 +++--- pkg/results/result-store-secrets.go | 10 +-- pkg/types/git_url.go | 61 +++++++++++++++---- pkg/types/result/command_result.go | 6 +- 9 files changed, 76 insertions(+), 37 deletions(-) diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index cd2e6a923..83c566afd 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -264,8 +264,7 @@ func parseRepoOverride(s string, isGroup bool) (ret repocache.RepoOverride, err } } - u = u.Normalize() - ret.RepoUrl = *u + ret.RepoKey = u.RepoKey() ret.Override = sp[1] return } diff --git a/e2e/git_include_test.go b/e2e/git_include_test.go index 5330ff33f..61ab9c593 100644 --- a/e2e/git_include_test.go +++ b/e2e/git_include_test.go @@ -90,8 +90,8 @@ func TestLocalGitOverride(t *testing.T) { u1, _ := types.ParseGitUrl(ip1.GitUrl()) u2, _ := types.ParseGitUrl(ip2.GitUrl()) - k1 := u1.NormalizedRepoKey() - k2 := u2.NormalizedRepoKey() + k1 := u1.RepoKey().String() + k2 := u2.RepoKey().String() p.KluctlMust("deploy", "--yes", "-t", "test", "--local-git-override", fmt.Sprintf("%s=%s", k1, override1), @@ -130,7 +130,7 @@ func TestLocalGitGroupOverride(t *testing.T) { _ = yaml.WriteYamlFile(filepath.Join(override2, "subDir", "cm", "configmap-include2-cm.yml"), cm) u1, _ := types.ParseGitUrl(p.GitServer().GitUrl() + "/repos") - k1 := u1.NormalizedRepoKey() + k1 := u1.RepoKey().String() p.KluctlMust("deploy", "--yes", "-t", "test", "--local-git-group-override", fmt.Sprintf("%s=%s", k1, overrideGroupDir), diff --git a/pkg/controllers/kluctl_project.go b/pkg/controllers/kluctl_project.go index 2317adce4..840b7b048 100644 --- a/pkg/controllers/kluctl_project.go +++ b/pkg/controllers/kluctl_project.go @@ -647,7 +647,7 @@ func (pt *preparedTarget) handleCommandResult(ctx context.Context, cmdErr error, cmdResult.Command.Initiator = result.CommandInititiator_KluctlDeployment cmdResult.GitInfo.Url = &pt.pp.obj.Spec.Source.URL cmdResult.GitInfo.Ref = pt.pp.obj.Spec.Source.Ref.String() - cmdResult.ProjectKey.GitRepoKey = pt.pp.obj.Spec.Source.URL.NormalizedRepoKey() + cmdResult.ProjectKey.GitRepoKey = pt.pp.obj.Spec.Source.URL.RepoKey() log.Info(fmt.Sprintf("Writing command result %s", cmdResult.Id)) err := pt.pp.r.ResultStore.WriteCommandResult(cmdResult) diff --git a/pkg/controllers/kluctldeployment_controller.go b/pkg/controllers/kluctldeployment_controller.go index 7f432a097..4bf07cefa 100644 --- a/pkg/controllers/kluctldeployment_controller.go +++ b/pkg/controllers/kluctldeployment_controller.go @@ -183,7 +183,7 @@ func (r *KluctlDeploymentReconciler) doReconcile( err = pt.withKluctlProjectTarget(ctx, func(targetContext *kluctl_project.TargetContext) error { obj.Status.ProjectKey = &result.ProjectKey{ - GitRepoKey: obj.Spec.Source.URL.NormalizedRepoKey(), + GitRepoKey: obj.Spec.Source.URL.RepoKey(), SubDir: path.Clean(obj.Spec.Source.Path), } obj.Status.TargetKey = &result.TargetKey{ diff --git a/pkg/deployment/commands/result_utils.go b/pkg/deployment/commands/result_utils.go index ee128c9cd..4b987d949 100644 --- a/pkg/deployment/commands/result_utils.go +++ b/pkg/deployment/commands/result_utils.go @@ -123,9 +123,9 @@ func addGitInfo(targetCtx *kluctl_project.TargetContext, r *result.CommandResult } } - var normaliedUrl string + var repoKey types.GitRepoKey if originUrl != nil { - normaliedUrl = originUrl.NormalizedRepoKey() + repoKey = originUrl.RepoKey() } r.GitInfo = result.GitInfo{ @@ -135,7 +135,7 @@ func addGitInfo(targetCtx *kluctl_project.TargetContext, r *result.CommandResult Commit: head.Hash().String(), Dirty: !s.IsClean(), } - r.ProjectKey.GitRepoKey = normaliedUrl + r.ProjectKey.GitRepoKey = repoKey r.ProjectKey.SubDir = subDir return nil } diff --git a/pkg/repocache/cache.go b/pkg/repocache/cache.go index a31d37938..782c8a4b0 100644 --- a/pkg/repocache/cache.go +++ b/pkg/repocache/cache.go @@ -25,7 +25,7 @@ type GitRepoCache struct { sshPool *ssh_pool.SshPool updateInterval time.Duration - repos map[string]*CacheEntry + repos map[types.GitRepoKey]*CacheEntry reposMutex sync.Mutex repoOverrides []RepoOverride @@ -53,7 +53,7 @@ type RepoInfo struct { } type RepoOverride struct { - RepoUrl types.GitUrl + RepoKey types.GitRepoKey Ref string Override string IsGroup bool @@ -70,7 +70,7 @@ func NewGitRepoCache(ctx context.Context, sshPool *ssh_pool.SshPool, authProvide sshPool: sshPool, authProviders: authProviders, updateInterval: updateInterval, - repos: map[string]*CacheEntry{}, + repos: map[types.GitRepoKey]*CacheEntry{}, repoOverrides: repoOverrides, } } @@ -90,23 +90,24 @@ func (rp *GitRepoCache) GetEntry(url types.GitUrl) (*CacheEntry, error) { defer rp.reposMutex.Unlock() urlN := url.Normalize() - repoKey := url.NormalizedRepoKey() + repoKey := url.RepoKey() // evaluate overrides for _, ro := range rp.repoOverrides { - if ro.RepoUrl.Host != urlN.Host { + if ro.RepoKey.Host != urlN.Host { continue } var overridePath string if ro.IsGroup { - if !strings.HasPrefix(urlN.Path, ro.RepoUrl.Path+"/") { + prefix := "/" + ro.RepoKey.Path + "/" + if !strings.HasPrefix(urlN.Path, prefix) { continue } - relPath := strings.TrimPrefix(urlN.Path, ro.RepoUrl.Path+"/") + relPath := strings.TrimPrefix(urlN.Path, prefix) overridePath = path.Join(ro.Override, relPath) } else { - if ro.RepoUrl.Path != urlN.Path { + if "/"+ro.RepoKey.Path != urlN.Path { continue } overridePath = ro.Override diff --git a/pkg/results/result-store-secrets.go b/pkg/results/result-store-secrets.go index 8425d11bd..7c09464d8 100644 --- a/pkg/results/result-store-secrets.go +++ b/pkg/results/result-store-secrets.go @@ -99,8 +99,8 @@ var invalidChars = regexp.MustCompile(`[^a-zA-Z0-9-]`) func (s *ResultStoreSecrets) buildName(cr *result.CommandResult) string { var name string - if cr.ProjectKey.GitRepoKey != "" { - s := path.Base(cr.ProjectKey.GitRepoKey) + if cr.ProjectKey.GitRepoKey.Path != "" { + s := path.Base(cr.ProjectKey.GitRepoKey.Path) if s != "" { name = s + "-" } @@ -197,8 +197,8 @@ func (s *ResultStoreSecrets) WriteCommandResult(cr *result.CommandResult) error "compactedObjects": compressedObjects, }, } - if cr.ProjectKey.GitRepoKey != "" { - secret.Annotations["kluctl.io/result-project-normalized-url"] = cr.ProjectKey.GitRepoKey + if cr.ProjectKey.GitRepoKey.String() != "" { + secret.Annotations["kluctl.io/result-project-repo-key"] = cr.ProjectKey.GitRepoKey.String() } if cr.ProjectKey.SubDir != "" { secret.Annotations["kluctl.io/result-project-subdir"] = cr.ProjectKey.SubDir @@ -302,7 +302,7 @@ func (s *ResultStoreSecrets) parseSummary(obj client.Object) (*result.CommandRes func (s *ResultStoreSecrets) filterSummary(summary *result.CommandResultSummary, project *result.ProjectKey) bool { if project != nil { - if project.GitRepoKey != "" && summary.ProjectKey.GitRepoKey != project.GitRepoKey { + if project.GitRepoKey.String() != "" && summary.ProjectKey.GitRepoKey != project.GitRepoKey { return false } if project.SubDir != "" && summary.ProjectKey.SubDir != project.SubDir { diff --git a/pkg/types/git_url.go b/pkg/types/git_url.go index 44db3e65c..c88947750 100644 --- a/pkg/types/git_url.go +++ b/pkg/types/git_url.go @@ -94,12 +94,9 @@ func (u *GitUrl) NormalizePort() string { func (u *GitUrl) Normalize() *GitUrl { path := strings.ToLower(u.Path) - if strings.HasSuffix(path, ".git") { - path = path[:len(path)-len(".git")] - } - if strings.HasSuffix(path, "/") { - path = path[:len(path)-1] - } + path = strings.TrimSuffix(path, ".git") + path = strings.TrimSuffix(path, "/") + hostname := strings.ToLower(u.Hostname()) port := u.NormalizePort() @@ -112,11 +109,53 @@ func (u *GitUrl) Normalize() *GitUrl { return &u2 } -func (u *GitUrl) NormalizedRepoKey() string { +func (u *GitUrl) RepoKey() GitRepoKey { u2 := u.Normalize() - username := "" - if u.User != nil && u.User.Username() != "" { - username = u.User.Username() + "@" + path := strings.TrimPrefix(u2.Path, "/") + return GitRepoKey{ + Host: u2.Host, + Path: path, } - return fmt.Sprintf("%s%s:%s%s", username, u2.Hostname(), u2.Port(), u2.Path) +} + +// +kubebuilder:validation:Type=string +type GitRepoKey struct { + Host string `json:"-"` + Path string `json:"-"` +} + +func (u GitRepoKey) String() string { + if u.Host == "" && u.Path == "" { + return "" + } + return fmt.Sprintf("%s/%s", u.Host, u.Path) +} + +func (u *GitRepoKey) UnmarshalJSON(b []byte) error { + var s string + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + if s == "" { + u.Host = "" + u.Path = "" + return nil + } + s2 := strings.SplitN(s, "/", 2) + if len(s2) != 2 { + return fmt.Errorf("invalid git repo key: %s", s) + } + u.Host = s2[0] + u.Path = s2[1] + return nil +} + +func (u GitRepoKey) MarshalJSON() ([]byte, error) { + b, err := json.Marshal(u.String()) + if err != nil { + return nil, err + } + + return b, err } diff --git a/pkg/types/result/command_result.go b/pkg/types/result/command_result.go index e8b8bef4d..7bc46c032 100644 --- a/pkg/types/result/command_result.go +++ b/pkg/types/result/command_result.go @@ -41,13 +41,13 @@ const ( ) type ProjectKey struct { - GitRepoKey string `json:"gitRepoKey,omitempty"` - SubDir string `json:"subDir,omitempty"` + GitRepoKey types.GitRepoKey `json:"gitRepoKey,omitempty"` + SubDir string `json:"subDir,omitempty"` } func (k ProjectKey) Less(o ProjectKey) bool { if k.GitRepoKey != o.GitRepoKey { - return k.GitRepoKey < o.GitRepoKey + return k.GitRepoKey.String() < o.GitRepoKey.String() } if k.SubDir != o.SubDir { return k.SubDir < o.SubDir From 25622069dfc87ef0ca3c215494c38be13838b0f0 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 17 May 2023 17:07:17 +0200 Subject: [PATCH 1651/2916] fix: Don't accept local Git urls in ParseGitUrl --- pkg/git/auth/git_credentials_file.go | 3 +-- pkg/types/git_url.go | 13 +++++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/pkg/git/auth/git_credentials_file.go b/pkg/git/auth/git_credentials_file.go index 58fff60c3..ca89766a8 100644 --- a/pkg/git/auth/git_credentials_file.go +++ b/pkg/git/auth/git_credentials_file.go @@ -4,7 +4,6 @@ import ( "bufio" "context" "github.com/go-git/go-git/v5/plumbing/transport/http" - "github.com/kluctl/kluctl/v2/pkg/git/giturls" "github.com/kluctl/kluctl/v2/pkg/git/messages" "github.com/kluctl/kluctl/v2/pkg/types" "net/url" @@ -66,7 +65,7 @@ func (a *GitCredentialsFileAuthProvider) tryBuildAuth(gitUrl types.GitUrl, gitCr if s.Text() == "" || s.Text()[0] == '#' { continue } - u, err := giturls.Parse(s.Text()) + u, err := types.ParseGitUrl(s.Text()) if err != nil { continue } diff --git a/pkg/types/git_url.go b/pkg/types/git_url.go index c88947750..68dbc5ff1 100644 --- a/pkg/types/git_url.go +++ b/pkg/types/git_url.go @@ -14,11 +14,16 @@ type GitUrl struct { } func ParseGitUrl(u string) (*GitUrl, error) { - u2, err := giturls.Parse(u) - if err != nil { - return nil, err + // we explicitly only test ParseTransport and ParseScp to avoid parsing local Git urls (as done by giturls.Parse) + u2, err := giturls.ParseTransport(u) + if err == nil { + return &GitUrl{*u2}, nil + } + u2, err = giturls.ParseScp(u) + if err == nil { + return &GitUrl{*u2}, nil } - return &GitUrl{*u2}, nil + return nil, fmt.Errorf("failed to parse git url: %s", u) } func ParseGitUrlMust(u string) *GitUrl { From 424cc67544e3f2f53bad729447e8282547910dd0 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 17 May 2023 17:08:25 +0200 Subject: [PATCH 1652/2916] chore: Run make generate manifest --- .../gitops.kluctl.io_kluctldeployments.yaml | 160 +++++++++++++++++- pkg/types/result/zz_generated.deepcopy.go | 8 +- pkg/types/zz_generated.deepcopy.go | 15 ++ 3 files changed, 174 insertions(+), 9 deletions(-) diff --git a/config/crd/bases/gitops.kluctl.io_kluctldeployments.yaml b/config/crd/bases/gitops.kluctl.io_kluctldeployments.yaml index 4abd9c5ce..100a3e89a 100644 --- a/config/crd/bases/gitops.kluctl.io_kluctldeployments.yaml +++ b/config/crd/bases/gitops.kluctl.io_kluctldeployments.yaml @@ -600,9 +600,9 @@ spec: type: integer orphanObjects: type: integer - project: + projectKey: properties: - normalizedGitUrl: + gitRepoKey: type: string subDir: type: string @@ -612,6 +612,74 @@ spec: renderedObjects: type: integer target: + properties: + args: + type: object + x-kubernetes-preserve-unknown-fields: true + context: + type: string + discriminator: + type: string + images: + items: + properties: + container: + type: string + deployTags: + items: + type: string + type: array + deployedImage: + type: string + deployment: + type: string + deploymentDir: + type: string + image: + type: string + namespace: + type: string + object: + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + required: + - kind + - name + type: object + resultImage: + type: string + required: + - image + - resultImage + type: object + type: array + name: + type: string + sealingConfig: + properties: + args: + type: object + x-kubernetes-preserve-unknown-fields: true + certFile: + type: string + secretSets: + items: + type: string + type: array + type: object + required: + - name + type: object + targetKey: properties: clusterId: type: string @@ -636,10 +704,11 @@ spec: - id - newObjects - orphanObjects - - project + - projectKey - remoteObjects - renderedObjects - target + - targetKey - totalChanges - warnings type: object @@ -807,9 +876,9 @@ spec: type: integer orphanObjects: type: integer - project: + projectKey: properties: - normalizedGitUrl: + gitRepoKey: type: string subDir: type: string @@ -819,6 +888,74 @@ spec: renderedObjects: type: integer target: + properties: + args: + type: object + x-kubernetes-preserve-unknown-fields: true + context: + type: string + discriminator: + type: string + images: + items: + properties: + container: + type: string + deployTags: + items: + type: string + type: array + deployedImage: + type: string + deployment: + type: string + deploymentDir: + type: string + image: + type: string + namespace: + type: string + object: + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + required: + - kind + - name + type: object + resultImage: + type: string + required: + - image + - resultImage + type: object + type: array + name: + type: string + sealingConfig: + properties: + args: + type: object + x-kubernetes-preserve-unknown-fields: true + certFile: + type: string + secretSets: + items: + type: string + type: array + type: object + required: + - name + type: object + targetKey: properties: clusterId: type: string @@ -843,10 +980,11 @@ spec: - id - newObjects - orphanObjects - - project + - projectKey - remoteObjects - renderedObjects - target + - targetKey - totalChanges - warnings type: object @@ -895,6 +1033,9 @@ spec: - ref type: object type: array + endTime: + format: date-time + type: string errors: items: properties: @@ -954,6 +1095,9 @@ spec: - ref type: object type: array + startTime: + format: date-time + type: string warnings: items: properties: @@ -981,8 +1125,10 @@ spec: type: object type: array required: + - endTime - id - ready + - startTime type: object observedGeneration: description: ObservedGeneration is the last reconciled generation. @@ -990,7 +1136,7 @@ spec: type: integer projectKey: properties: - normalizedGitUrl: + gitRepoKey: type: string subDir: type: string diff --git a/pkg/types/result/zz_generated.deepcopy.go b/pkg/types/result/zz_generated.deepcopy.go index c7c90926a..40ade0029 100644 --- a/pkg/types/result/zz_generated.deepcopy.go +++ b/pkg/types/result/zz_generated.deepcopy.go @@ -218,8 +218,9 @@ func (in *CommandResult) DeepCopy() *CommandResult { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CommandResultSummary) DeepCopyInto(out *CommandResultSummary) { *out = *in - out.Project = in.Project - out.Target = in.Target + out.ProjectKey = in.ProjectKey + out.TargetKey = in.TargetKey + in.Target.DeepCopyInto(&out.Target) in.Command.DeepCopyInto(&out.Command) in.GitInfo.DeepCopyInto(&out.GitInfo) out.ClusterInfo = in.ClusterInfo @@ -348,6 +349,7 @@ func (in *KluctlDeploymentInfo) DeepCopy() *KluctlDeploymentInfo { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ProjectKey) DeepCopyInto(out *ProjectKey) { *out = *in + out.GitRepoKey = in.GitRepoKey } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProjectKey. @@ -461,6 +463,8 @@ func (in *TargetSummary) DeepCopy() *TargetSummary { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ValidateResult) DeepCopyInto(out *ValidateResult) { *out = *in + in.StartTime.DeepCopyInto(&out.StartTime) + in.EndTime.DeepCopyInto(&out.EndTime) if in.Warnings != nil { in, out := &in.Warnings, &out.Warnings *out = make([]DeploymentError, len(*in)) diff --git a/pkg/types/zz_generated.deepcopy.go b/pkg/types/zz_generated.deepcopy.go index ec75fae41..59a658fca 100644 --- a/pkg/types/zz_generated.deepcopy.go +++ b/pkg/types/zz_generated.deepcopy.go @@ -329,6 +329,21 @@ func (in *GitProject) DeepCopy() *GitProject { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GitRepoKey) DeepCopyInto(out *GitRepoKey) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitRepoKey. +func (in *GitRepoKey) DeepCopy() *GitRepoKey { + if in == nil { + return nil + } + out := new(GitRepoKey) + in.DeepCopyInto(out) + return out +} + // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitUrl. func (in *GitUrl) DeepCopy() *GitUrl { if in == nil { From 7c0f3bfc397f5d8972fc6e50daf8b6497d13706f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 18 May 2023 08:27:32 +0200 Subject: [PATCH 1653/2916] ci: Fix Github tests workflow --- .github/workflows/tests.yml | 5 ----- Makefile | 11 +++++++++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b4d09f840..851719d81 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -80,11 +80,6 @@ jobs: shell: bash run: | make test-unit - - name: setup-envtest - if: matrix.run_on_pull_requests || github.event_name != 'pull_request' - shell: bash - run: | - make install-envtest - name: Run e2e tests if: matrix.run_on_pull_requests || github.event_name != 'pull_request' shell: bash diff --git a/Makefile b/Makefile index d640b9f2b..c30be6440 100644 --- a/Makefile +++ b/Makefile @@ -70,8 +70,15 @@ vet: ## Run go vet against code. go vet ./... .PHONY: test -test: manifests generate fmt vet envtest ## Run tests. - KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test $(RACE) ./... -coverprofile cover.out +test: test-unit test-e2e fmt vet ## Run all tests. + +.PHONY: test-unit +test-unit: generate ## Run unit tests. + go test $(RACE) $(shell go list ./... | grep -v v2/e2e) -coverprofile cover.out + +.PHONY: test-e2e +test-e2e: manifests generate envtest ## Run e2e tests. + KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test $(RACE) ./e2e -coverprofile cover.out replace-commands-help: ## Replace commands help in docs go run ./internal/replace-commands-help --docs-dir ./docs/reference/commands From a19fc52e2765d4b517bf1a3ca118e78f8dd1443f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 18 May 2023 22:20:25 +0200 Subject: [PATCH 1654/2916] fix: Url normalizing should also normalize leading / --- pkg/types/git_url.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/types/git_url.go b/pkg/types/git_url.go index 68dbc5ff1..4c649bb04 100644 --- a/pkg/types/git_url.go +++ b/pkg/types/git_url.go @@ -105,6 +105,10 @@ func (u *GitUrl) Normalize() *GitUrl { hostname := strings.ToLower(u.Hostname()) port := u.NormalizePort() + if path != "" && hostname != "" && !strings.HasPrefix(path, "/") { + path = "/" + path + } + u2 := *u u2.Path = path u2.Host = hostname From e224bd610b2bce5d00000cc9e8e5fb035fd449c8 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 18 May 2023 22:21:34 +0200 Subject: [PATCH 1655/2916] tests: Fix EnvTestCluster race condition --- e2e/test-utils/envtest_cluster.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/e2e/test-utils/envtest_cluster.go b/e2e/test-utils/envtest_cluster.go index 158b79e65..c6e6d9fd6 100644 --- a/e2e/test-utils/envtest_cluster.go +++ b/e2e/test-utils/envtest_cluster.go @@ -6,7 +6,9 @@ import ( "fmt" kluctlv1 "github.com/kluctl/kluctl/v2/api/v1beta1" "github.com/kluctl/kluctl/v2/pkg/utils/uo" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/version" "k8s.io/client-go/discovery" @@ -47,6 +49,9 @@ type EnvTestCluster struct { func CreateEnvTestCluster(context string) *EnvTestCluster { k := &EnvTestCluster{ Context: context, + env: envtest.Environment{ + Scheme: runtime.NewScheme(), + }, } return k } @@ -61,6 +66,7 @@ func (k *EnvTestCluster) Start() error { k.started = true _ = kluctlv1.AddToScheme(k.env.Scheme) + _ = corev1.AddToScheme(k.env.Scheme) err = k.startCallbackServer() if err != nil { @@ -99,6 +105,7 @@ func (k *EnvTestCluster) Start() error { c, err := client.New(k.config, client.Options{ HTTPClient: httpClient, + Scheme: k.env.Scheme, }) k.Client = c From 99fbf56ce97d3c6668ecdf14d116d2edc0861df5 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 18 May 2023 22:57:01 +0200 Subject: [PATCH 1656/2916] ci: Don't run generate for unit tests --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c30be6440..a2bbf37e2 100644 --- a/Makefile +++ b/Makefile @@ -73,7 +73,7 @@ vet: ## Run go vet against code. test: test-unit test-e2e fmt vet ## Run all tests. .PHONY: test-unit -test-unit: generate ## Run unit tests. +test-unit: ## Run unit tests. go test $(RACE) $(shell go list ./... | grep -v v2/e2e) -coverprofile cover.out .PHONY: test-e2e From e3359a935cb9703f57bd46b3266b50173695c4e0 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 19 May 2023 00:02:06 +0200 Subject: [PATCH 1657/2916] tests: Skip TestServer_EncryptDecrypt_PGP on windows --- pkg/controllers/internal/sops/keyservice/server_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/controllers/internal/sops/keyservice/server_test.go b/pkg/controllers/internal/sops/keyservice/server_test.go index d7e0b7b33..ea34cecaf 100644 --- a/pkg/controllers/internal/sops/keyservice/server_test.go +++ b/pkg/controllers/internal/sops/keyservice/server_test.go @@ -15,6 +15,7 @@ import ( "go.mozilla.org/sops/v3/kms" "go.mozilla.org/sops/v3/pgp" "os" + "runtime" "testing" "github.com/Azure/azure-sdk-for-go/sdk/azidentity" @@ -25,6 +26,11 @@ import ( ) func TestServer_EncryptDecrypt_PGP(t *testing.T) { + if runtime.GOOS == "windows" { + // skipping due to error: cannot import armored key data into GnuPG keyring: GNUPGHOME has invalid permissions: got 0777 wanted 0700 + return + } + const ( mockPublicKey = "testdata/public.gpg" mockPrivateKey = "testdata/private.gpg" From 27f4beb3ebe49f6df5d85423944b909d00d1f43d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 19 May 2023 08:12:50 +0200 Subject: [PATCH 1658/2916] refactor: Pass initialized client/cache instead of RESTConfig to ResultStoreSecrets --- cmd/kluctl/commands/utils.go | 19 ++++- .../kluctldeployment_controller_setup.go | 2 +- pkg/results/result-store-secrets.go | 72 +++++++------------ 3 files changed, 46 insertions(+), 47 deletions(-) diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 83c566afd..0bcb6bcbf 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -21,6 +21,8 @@ import ( "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd/api" "os" + cache2 "sigs.k8s.io/controller-runtime/pkg/cache" + client2 "sigs.k8s.io/controller-runtime/pkg/client" "strings" ) @@ -203,7 +205,22 @@ func withProjectTargetCommandContext(ctx context.Context, args projectTargetComm if err != nil { return err } - resultStore, err = results.NewResultStoreSecrets(ctx, rc, args.commandResultFlags.CommandResultNamespace, args.commandResultFlags.KeepCommandResultsCount) + client, err := client2.New(rc, client2.Options{}) + if err != nil { + return err + } + cache, err := cache2.New(rc, cache2.Options{}) + if err != nil { + return err + } + + cancelCtx, cancelFunc := context.WithCancel(ctx) + defer cancelFunc() + go func() { + _ = cache.Start(cancelCtx) + }() + + resultStore, err = results.NewResultStoreSecrets(cancelCtx, client, cache, args.commandResultFlags.CommandResultNamespace, args.commandResultFlags.KeepCommandResultsCount) if err != nil { return err } diff --git a/pkg/controllers/kluctldeployment_controller_setup.go b/pkg/controllers/kluctldeployment_controller_setup.go index 61f66cba1..0f8fe2e73 100644 --- a/pkg/controllers/kluctldeployment_controller_setup.go +++ b/pkg/controllers/kluctldeployment_controller_setup.go @@ -22,7 +22,7 @@ func (r *KluctlDeploymentReconciler) SetupWithManager(ctx context.Context, mgr c httpClient.Logger = nil r.httpClient = httpClient - resultStore, err := results.NewResultStoreSecrets(ctx, mgr.GetConfig(), "kluctl-results", 10) + resultStore, err := results.NewResultStoreSecrets(ctx, mgr.GetClient(), mgr.GetCache(), "kluctl-results", 10) if err != nil { return err } diff --git a/pkg/results/result-store-secrets.go b/pkg/results/result-store-secrets.go index 7c09464d8..88527503c 100644 --- a/pkg/results/result-store-secrets.go +++ b/pkg/results/result-store-secrets.go @@ -11,9 +11,7 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/client-go/rest" toolscache "k8s.io/client-go/tools/cache" "path" "regexp" @@ -25,70 +23,54 @@ import ( ) type ResultStoreSecrets struct { - ctx context.Context + ctx context.Context + client client.Client + cache cache.Cache + reader client.Reader - config *rest.Config writeNamespace string keepResultsCount int - mutex sync.Mutex - client client.Client - cache cache.Cache - informer cache.Informer - cancelCache context.CancelFunc + mutex sync.Mutex + informer cache.Informer } -func NewResultStoreSecrets(ctx context.Context, config *rest.Config, writeNamespace string, keepResultsCount int) (*ResultStoreSecrets, error) { +func NewResultStoreSecrets(ctx context.Context, client client.Client, cache cache.Cache, writeNamespace string, keepResultsCount int) (*ResultStoreSecrets, error) { s := &ResultStoreSecrets{ ctx: ctx, - config: config, + client: client, + cache: cache, writeNamespace: writeNamespace, keepResultsCount: keepResultsCount, } + if cache != nil { + s.reader = cache + } else { + s.reader = client + } return s, nil } -func (s *ResultStoreSecrets) ensureClientAndCache() error { +func (s *ResultStoreSecrets) ensureInformer() error { s.mutex.Lock() defer s.mutex.Unlock() - if s.client != nil { + if s.informer != nil { return nil } - - c, err := client.New(s.config, client.Options{}) - if err != nil { - return err + if s.cache == nil { + return nil } - labelSelector, _ := labels.Parse("kluctl.io/result-id") - var partialSecret metav1.PartialObjectMetadata partialSecret.SetGroupVersionKind(schema.GroupVersionKind{Version: "v1", Kind: "Secret"}) - ca, err := cache.New(s.config, cache.Options{ - DefaultLabelSelector: labelSelector, - }) - if err != nil { - return err - } - - informer, err := ca.GetInformer(s.ctx, &partialSecret) + informer, err := s.cache.GetInformer(s.ctx, &partialSecret) if err != nil { return err } - s.client = c - s.cache = ca s.informer = informer - - newCtx, cancel := context.WithCancel(s.ctx) - s.cancelCache = cancel - - go func() { - _ = ca.Start(newCtx) - }() - s.cache.WaitForCacheSync(s.ctx) return nil @@ -124,7 +106,7 @@ func (s *ResultStoreSecrets) ensureWriteNamespace() error { return fmt.Errorf("missing writeNamespace") } var ns corev1.Namespace - err := s.client.Get(s.ctx, client.ObjectKey{Name: s.writeNamespace}, &ns) + err := s.reader.Get(s.ctx, client.ObjectKey{Name: s.writeNamespace}, &ns) if err != nil && errors.IsNotFound(err) { ns := &corev1.Namespace{ ObjectMeta: metav1.ObjectMeta{ @@ -143,7 +125,7 @@ func (s *ResultStoreSecrets) ensureWriteNamespace() error { } func (s *ResultStoreSecrets) WriteCommandResult(cr *result.CommandResult) error { - err := s.ensureClientAndCache() + err := s.ensureInformer() if err != nil { return err } @@ -249,14 +231,14 @@ func (s *ResultStoreSecrets) cleanupResults(project result.ProjectKey, target re } func (s *ResultStoreSecrets) ListCommandResultSummaries(options ListCommandResultSummariesOptions) ([]result.CommandResultSummary, error) { - err := s.ensureClientAndCache() + err := s.ensureInformer() if err != nil { return nil, err } var l metav1.PartialObjectMetadataList l.SetGroupVersionKind(schema.GroupVersionKind{Version: "v1", Kind: "SecretList"}) - err = s.cache.List(s.ctx, &l, client.HasLabels{"kluctl.io/result-id"}) + err = s.reader.List(s.ctx, &l, client.HasLabels{"kluctl.io/result-id"}) if err != nil { return nil, err } @@ -314,7 +296,7 @@ func (s *ResultStoreSecrets) filterSummary(summary *result.CommandResultSummary, } func (s *ResultStoreSecrets) WatchCommandResultSummaries(options ListCommandResultSummariesOptions, update func(summary *result.CommandResultSummary), delete func(id string)) (func(), error) { - err := s.ensureClientAndCache() + err := s.ensureInformer() if err != nil { return nil, err } @@ -360,14 +342,14 @@ func (s *ResultStoreSecrets) WatchCommandResultSummaries(options ListCommandResu } func (s *ResultStoreSecrets) getCommandResultSecret(id string) (*metav1.PartialObjectMetadata, error) { - err := s.ensureClientAndCache() + err := s.ensureInformer() if err != nil { return nil, err } var l metav1.PartialObjectMetadataList l.SetGroupVersionKind(schema.GroupVersionKind{Version: "v1", Kind: "SecretList"}) - err = s.cache.List(s.ctx, &l, client.MatchingLabels{"kluctl.io/result-id": id}) + err = s.reader.List(s.ctx, &l, client.MatchingLabels{"kluctl.io/result-id": id}) if err != nil { return nil, err } @@ -403,7 +385,7 @@ func (s *ResultStoreSecrets) GetCommandResult(options GetCommandResultOptions) ( } var l corev1.SecretList - err = s.client.List(s.ctx, &l, client.MatchingLabels{ + err = s.reader.List(s.ctx, &l, client.MatchingLabels{ "kluctl.io/result-id": options.Id, }) if err != nil { From 5f36b04bbe4a5d8e7b8721ae25234a25d24fe7ef Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 19 May 2023 11:55:14 +0200 Subject: [PATCH 1659/2916] fix: Fix controller swallowing errors --- api/v1beta1/kluctldeployment_types.go | 10 + .../gitops.kluctl.io_kluctldeployments.yaml | 9 + pkg/controllers/kluctl_project.go | 109 ++++---- .../kluctldeployment_controller.go | 238 ++++++++++-------- pkg/controllers/utils.go | 11 +- 5 files changed, 209 insertions(+), 168 deletions(-) diff --git a/api/v1beta1/kluctldeployment_types.go b/api/v1beta1/kluctldeployment_types.go index 3da415e20..b5021142c 100644 --- a/api/v1beta1/kluctldeployment_types.go +++ b/api/v1beta1/kluctldeployment_types.go @@ -303,6 +303,9 @@ type KluctlDeploymentStatus struct { // +optional ObservedGeneration int64 `json:"observedGeneration,omitempty"` + // ObservedCommit is the last commit observed + ObservedCommit string `json:"observedCommit,omitempty"` + // +optional Conditions []metav1.Condition `json:"conditions,omitempty"` @@ -315,6 +318,13 @@ type KluctlDeploymentStatus struct { // +optional LastObjectsHash string `json:"lastObjectsHash,omitempty"` + // +optional + LastDeployError string `json:"lastDeployError,omitempty"` + // +optional + LastPruneError string `json:"lastPruneError,omitempty"` + // +optional + LastValidateError string `json:"lastValidateError,omitempty"` + // LastDeployResult is the result of the last deploy command // +optional LastDeployResult *result.CommandResultSummary `json:"lastDeployResult,omitempty"` diff --git a/config/crd/bases/gitops.kluctl.io_kluctldeployments.yaml b/config/crd/bases/gitops.kluctl.io_kluctldeployments.yaml index 100a3e89a..d07ed9349 100644 --- a/config/crd/bases/gitops.kluctl.io_kluctldeployments.yaml +++ b/config/crd/bases/gitops.kluctl.io_kluctldeployments.yaml @@ -443,6 +443,8 @@ spec: - type type: object type: array + lastDeployError: + type: string lastDeployResult: description: LastDeployResult is the result of the last deploy command properties: @@ -719,6 +721,8 @@ spec: type: string lastObjectsHash: type: string + lastPruneError: + type: string lastPruneResult: description: LastDeployResult is the result of the last prune command properties: @@ -988,6 +992,8 @@ spec: - totalChanges - warnings type: object + lastValidateError: + type: string lastValidateResult: description: LastValidateResult is the result of the last validate command @@ -1130,6 +1136,9 @@ spec: - ready - startTime type: object + observedCommit: + description: ObservedCommit is the last commit observed + type: string observedGeneration: description: ObservedGeneration is the last reconciled generation. format: int64 diff --git a/pkg/controllers/kluctl_project.go b/pkg/controllers/kluctl_project.go index 840b7b048..d0c306032 100644 --- a/pkg/controllers/kluctl_project.go +++ b/pkg/controllers/kluctl_project.go @@ -3,10 +3,10 @@ package controllers import ( "context" "fmt" + "github.com/kluctl/go-jinja2" internal_metrics "github.com/kluctl/kluctl/v2/pkg/controllers/internal/metrics" "github.com/kluctl/kluctl/v2/pkg/controllers/internal/sops" "github.com/kluctl/kluctl/v2/pkg/helm" - "github.com/kluctl/kluctl/v2/pkg/kluctl_jinja2" "github.com/kluctl/kluctl/v2/pkg/repocache" "github.com/kluctl/kluctl/v2/pkg/sops/decryptor" "github.com/kluctl/kluctl/v2/pkg/types/result" @@ -44,6 +44,7 @@ type preparedProject struct { rp *repocache.GitRepoCache + commit string tmpDir string repoDir string projectDir string @@ -95,11 +96,12 @@ func prepareProject(ctx context.Context, return nil, fmt.Errorf("failed clone source: %w", err) } - clonedDir, _, err := rpEntry.GetClonedDir(pp.obj.Spec.Source.Ref.String()) + clonedDir, gi, err := rpEntry.GetClonedDir(pp.obj.Spec.Source.Ref.String()) if err != nil { return nil, err } + pp.commit = gi.CheckedOutCommit pp.repoDir = clonedDir // check kluctl project path exists @@ -124,12 +126,12 @@ func (pp *preparedProject) cleanup() { } } -func (pp *preparedProject) newTarget() (*preparedTarget, error) { +func (pp *preparedProject) newTarget() *preparedTarget { pt := preparedTarget{ pp: pp, } - return &pt, nil + return &pt } func (pt *preparedTarget) restConfigToKubeconfig(restConfig *rest.Config) *api.Config { @@ -558,24 +560,19 @@ func (pp *preparedProject) addServiceAccountBasedKeyServers(ctx context.Context, return nil } -func (pp *preparedProject) withKluctlProject(ctx context.Context, pt *preparedTarget, cb func(p *kluctl_project.LoadedKluctlProject) error) error { - j2, err := kluctl_jinja2.NewKluctlJinja2(true) - if err != nil { - return err - } - defer j2.Close() - +func (pp *preparedProject) loadKluctlProject(ctx context.Context, pt *preparedTarget, j2 *jinja2.Jinja2) (*kluctl_project.LoadedKluctlProject, error) { + var err error var sopsDecrypter *decryptor.Decryptor if pp.obj.Spec.Decryption != nil { sopsDecrypter, err = pp.buildSopsDecrypter(ctx) if err != nil { - return err + return nil, err } } externalArgs, err := uo.FromString(string(pt.pp.obj.Spec.Args.Raw)) if err != nil { - return err + return nil, err } loadArgs := kluctl_project.LoadKluctlProjectArgs{ @@ -591,54 +588,52 @@ func (pp *preparedProject) withKluctlProject(ctx context.Context, pt *preparedTa p, err := kluctl_project.LoadKluctlProject(ctx, loadArgs, filepath.Join(pp.tmpDir, "project"), j2) if err != nil { - return err + return nil, err } - return cb(p) + return p, nil } -func (pt *preparedTarget) withKluctlProjectTarget(ctx context.Context, cb func(targetContext *kluctl_project.TargetContext) error) error { - return pt.pp.withKluctlProject(ctx, pt, func(p *kluctl_project.LoadedKluctlProject) error { - renderOutputDir, err := os.MkdirTemp(pt.pp.tmpDir, "render-") - if err != nil { - return err - } - images, err := pt.buildImages(ctx) - if err != nil { - return err - } - helmCredentials, err := pt.buildHelmCredentials(ctx) - if err != nil { - return err - } - inclusion := pt.buildInclusion() - - props := kluctl_project.TargetContextParams{ - DryRun: pt.pp.r.DryRun || pt.pp.obj.Spec.DryRun, - Images: images, - Inclusion: inclusion, - HelmCredentials: helmCredentials, - RenderOutputDir: renderOutputDir, - } - if pt.pp.obj.Spec.Target != nil { - props.TargetName = *pt.pp.obj.Spec.Target - } - if pt.pp.obj.Spec.TargetNameOverride != nil { - props.TargetNameOverride = *pt.pp.obj.Spec.TargetNameOverride - } - if pt.pp.obj.Spec.Context != nil { - props.ContextOverride = *pt.pp.obj.Spec.Context - } - targetContext, err := p.NewTargetContext(ctx, props) - if err != nil { - return err - } - err = targetContext.DeploymentCollection.Prepare() - if err != nil { - return err - } - return cb(targetContext) - }) +func (pt *preparedTarget) loadTarget(ctx context.Context, p *kluctl_project.LoadedKluctlProject) (*kluctl_project.TargetContext, error) { + renderOutputDir, err := os.MkdirTemp(pt.pp.tmpDir, "render-") + if err != nil { + return nil, err + } + images, err := pt.buildImages(ctx) + if err != nil { + return nil, err + } + helmCredentials, err := pt.buildHelmCredentials(ctx) + if err != nil { + return nil, err + } + inclusion := pt.buildInclusion() + + props := kluctl_project.TargetContextParams{ + DryRun: pt.pp.r.DryRun || pt.pp.obj.Spec.DryRun, + Images: images, + Inclusion: inclusion, + HelmCredentials: helmCredentials, + RenderOutputDir: renderOutputDir, + } + if pt.pp.obj.Spec.Target != nil { + props.TargetName = *pt.pp.obj.Spec.Target + } + if pt.pp.obj.Spec.TargetNameOverride != nil { + props.TargetNameOverride = *pt.pp.obj.Spec.TargetNameOverride + } + if pt.pp.obj.Spec.Context != nil { + props.ContextOverride = *pt.pp.obj.Spec.Context + } + targetContext, err := p.NewTargetContext(ctx, props) + if err != nil { + return nil, err + } + err = targetContext.DeploymentCollection.Prepare() + if err != nil { + return nil, err + } + return targetContext, nil } func (pt *preparedTarget) handleCommandResult(ctx context.Context, cmdErr error, cmdResult *result.CommandResult, commandName string) error { diff --git a/pkg/controllers/kluctldeployment_controller.go b/pkg/controllers/kluctldeployment_controller.go index 4bf07cefa..5e9653f2d 100644 --- a/pkg/controllers/kluctldeployment_controller.go +++ b/pkg/controllers/kluctldeployment_controller.go @@ -7,7 +7,7 @@ import ( kluctlv1 "github.com/kluctl/kluctl/v2/api/v1beta1" internal_metrics "github.com/kluctl/kluctl/v2/pkg/controllers/internal/metrics" ssh_pool "github.com/kluctl/kluctl/v2/pkg/git/ssh-pool" - "github.com/kluctl/kluctl/v2/pkg/kluctl_project" + "github.com/kluctl/kluctl/v2/pkg/kluctl_jinja2" "github.com/kluctl/kluctl/v2/pkg/results" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types/result" @@ -68,6 +68,8 @@ func (r *KluctlDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Req return ctrl.Result{}, client.IgnoreNotFound(err) } + patch := client.MergeFrom(obj.DeepCopy()) + var cancel context.CancelFunc ctx, cancel = context.WithTimeout(ctx, r.calcTimeout(obj)) defer cancel() @@ -80,7 +82,6 @@ func (r *KluctlDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Req // Add our finalizer if it does not exist if !controllerutil.ContainsFinalizer(obj, kluctlv1.KluctlDeploymentFinalizer) { - patch := client.MergeFrom(obj.DeepCopy()) controllerutil.AddFinalizer(obj, kluctlv1.KluctlDeploymentFinalizer) if err := r.Patch(ctx, obj, patch, client.FieldOwner(r.ControllerName)); err != nil { log.Error(err, "unable to register finalizer") @@ -110,7 +111,6 @@ func (r *KluctlDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Req // set the reconciliation status to progressing if obj.Status.ObservedGeneration == 0 { - patch := client.MergeFrom(obj.DeepCopy()) setReadiness(obj, metav1.ConditionUnknown, meta.ProgressingReason, "reconciliation in progress") if err := r.Status().Patch(ctx, obj, patch, client.FieldOwner(r.ControllerName)); err != nil { return ctrl.Result{Requeue: true}, err @@ -125,7 +125,6 @@ func (r *KluctlDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Req } // reconcile kluctlDeployment by applying the latest revision - patch := client.MergeFrom(obj.DeepCopy()) ctrlResult, reconcileErr := r.doReconcile(ctx, obj) if err := r.Status().Patch(ctx, obj, patch, client.FieldOwner(r.ControllerName)); err != nil { return ctrl.Result{Requeue: true}, err @@ -168,127 +167,145 @@ func (r *KluctlDeploymentReconciler) doReconcile( r.exportDeploymentObjectToProm(obj) + doFail := func(reason string, err error) (*ctrl.Result, error) { + setReadiness(obj, metav1.ConditionFalse, reason, err.Error()) + internal_metrics.NewKluctlLastObjectStatus(obj.Namespace, obj.Name).Set(0.0) + return nil, err + } + + doFailPrepare := func(err error) (*ctrl.Result, error) { + return doFail(kluctlv1.PrepareFailedReason, err) + } + + oldGeneration := obj.Status.ObservedGeneration + obj.Status.ObservedGeneration = obj.GetGeneration() + if v, ok := obj.GetAnnotations()[kluctlv1.KluctlRequestDeployAnnotation]; ok { + obj.Status.LastHandledDeployAt = v + } + pp, err := prepareProject(ctx, r, obj, true) if err != nil { - setReadiness(obj, metav1.ConditionFalse, kluctlv1.PrepareFailedReason, err.Error()) - return nil, err + return doFailPrepare(err) } defer pp.cleanup() - pt, err := pp.newTarget() + obj.Status.ObservedCommit = pp.commit + + j2, err := kluctl_jinja2.NewKluctlJinja2(true) if err != nil { - setReadiness(obj, metav1.ConditionFalse, kluctlv1.PrepareFailedReason, err.Error()) - return nil, err + return doFailPrepare(err) } + defer j2.Close() - err = pt.withKluctlProjectTarget(ctx, func(targetContext *kluctl_project.TargetContext) error { - obj.Status.ProjectKey = &result.ProjectKey{ - GitRepoKey: obj.Spec.Source.URL.RepoKey(), - SubDir: path.Clean(obj.Spec.Source.Path), - } - obj.Status.TargetKey = &result.TargetKey{ - TargetName: targetContext.Target.Name, - Discriminator: targetContext.Target.Discriminator, - } + pt := pp.newTarget() - if obj.Status.ProjectKey.SubDir == "." { - obj.Status.ProjectKey.SubDir = "" - } + lp, err := pp.loadKluctlProject(ctx, pt, j2) + if err != nil { + return doFailPrepare(err) + } - objectsHash, err := targetContext.DeploymentCollection.CalcObjectsHash() - if err != nil { - return err + targetContext, err := pt.loadTarget(ctx, lp) + if err != nil { + return doFailPrepare(err) + } + + obj.Status.ProjectKey = &result.ProjectKey{ + GitRepoKey: obj.Spec.Source.URL.RepoKey(), + SubDir: path.Clean(obj.Spec.Source.Path), + } + obj.Status.TargetKey = &result.TargetKey{ + TargetName: targetContext.Target.Name, + Discriminator: targetContext.Target.Discriminator, + } + + if obj.Status.ProjectKey.SubDir == "." { + obj.Status.ProjectKey.SubDir = "" + } + + objectsHash, err := targetContext.DeploymentCollection.CalcObjectsHash() + if err != nil { + return doFailPrepare(err) + } + + needDeploy := false + needPrune := false + needValidate := false + + if obj.Status.LastDeployResult == nil || obj.Status.LastObjectsHash != objectsHash { + // either never deployed or source code changed + needDeploy = true + } else if r.checkRequestedDeploy(obj) { + // explicitly requested a deploy + needDeploy = true + } else if oldGeneration != obj.GetGeneration() { + // spec has changed + needDeploy = true + } else { + // was deployed before, let's check if we need to do periodic deployments + nextDeployTime := r.nextDeployTime(obj) + if nextDeployTime != nil { + needDeploy = nextDeployTime.Before(time.Now()) } + } - needDeploy := false - needPrune := false - needValidate := false - - if obj.Status.LastDeployResult == nil || obj.Status.LastObjectsHash != objectsHash { - // either never deployed or source code changed - needDeploy = true - } else if r.checkRequestedDeploy(obj) { - // explicitly requested a deploy - needDeploy = true - } else if obj.Status.ObservedGeneration != obj.GetGeneration() { - // spec has changed - needDeploy = true + if obj.Spec.Validate { + if obj.Status.LastValidateResult == nil || needDeploy { + // either never validated before or a deployment requested (which required re-validation) + needValidate = true } else { - // was deployed before, let's check if we need to do periodic deployments - nextDeployTime := r.nextDeployTime(obj) - if nextDeployTime != nil { - needDeploy = nextDeployTime.Before(time.Now()) + nextValidateTime := r.nextValidateTime(obj) + if nextValidateTime != nil { + needValidate = nextValidateTime.Before(time.Now()) } } + } else { + obj.Status.LastValidateResult = nil + obj.Status.LastValidateError = "" + } - if obj.Spec.Validate { - if obj.Status.LastValidateResult == nil || needDeploy { - // either never validated before or a deployment requested (which required re-validation) - needValidate = true - } else { - nextValidateTime := r.nextValidateTime(obj) - if nextValidateTime != nil { - needValidate = nextValidateTime.Before(time.Now()) - } - } - } else { - obj.Status.LastValidateResult = nil - } + if obj.Spec.Prune { + needPrune = needDeploy + } else { + obj.Status.LastPruneResult = nil + obj.Status.LastPruneError = "" + } - if obj.Spec.Prune { - needPrune = needDeploy + obj.Status.LastObjectsHash = objectsHash + + var deployResult *result.CommandResult + if needDeploy { + // deploy the kluctl project + if obj.Spec.DeployMode == kluctlv1.KluctlDeployModeFull { + deployResult, err = pt.kluctlDeploy(ctx, targetContext) + } else if obj.Spec.DeployMode == kluctlv1.KluctlDeployPokeImages { + deployResult, err = pt.kluctlPokeImages(ctx, targetContext) } else { - obj.Status.LastPruneResult = nil + err = fmt.Errorf("deployMode '%s' not supported", obj.Spec.DeployMode) } - - obj.Status.LastObjectsHash = objectsHash - - var deployResult *result.CommandResult - if needDeploy { - // deploy the kluctl project - if obj.Spec.DeployMode == kluctlv1.KluctlDeployModeFull { - deployResult, err = pt.kluctlDeploy(ctx, targetContext) - } else if obj.Spec.DeployMode == kluctlv1.KluctlDeployPokeImages { - deployResult, err = pt.kluctlPokeImages(ctx, targetContext) - } else { - err = fmt.Errorf("deployMode '%s' not supported", obj.Spec.DeployMode) - setReadiness(obj, metav1.ConditionFalse, kluctlv1.DeployFailedReason, err.Error()) - return nil - } - obj.Status.LastDeployResult = deployResult.BuildSummary() - if err != nil { - setReadiness(obj, metav1.ConditionFalse, kluctlv1.DeployFailedReason, err.Error()) - return nil - } + obj.Status.LastDeployResult = deployResult.BuildSummary() + obj.Status.LastDeployError = "" + if err != nil { + obj.Status.LastDeployError = err.Error() } + } - if needPrune { - // run garbage collection for stale objects that do not have pruning disabled - pruneResult, err := pt.kluctlPrune(ctx, targetContext) - obj.Status.LastPruneResult = pruneResult.BuildSummary() - if err != nil { - setReadiness(obj, metav1.ConditionFalse, kluctlv1.PruneFailedReason, err.Error()) - return nil - } + if needPrune { + // run garbage collection for stale objects that do not have pruning disabled + pruneResult, err := pt.kluctlPrune(ctx, targetContext) + obj.Status.LastPruneResult = pruneResult.BuildSummary() + obj.Status.LastPruneError = "" + if err != nil { + obj.Status.LastPruneError = err.Error() } + } - if needValidate { - validateResult, err := pt.kluctlValidate(ctx, targetContext, deployResult) - obj.Status.LastValidateResult = validateResult - if err != nil { - setReadiness(obj, metav1.ConditionFalse, kluctlv1.ValidateFailedReason, err.Error()) - return nil - } + if needValidate { + validateResult, err := pt.kluctlValidate(ctx, targetContext, deployResult) + obj.Status.LastValidateResult = validateResult + obj.Status.LastValidateError = "" + if err != nil { + obj.Status.LastValidateError = err.Error() } - return nil - }) - obj.Status.ObservedGeneration = obj.GetGeneration() - if v, ok := obj.GetAnnotations()[kluctlv1.KluctlRequestDeployAnnotation]; ok { - obj.Status.LastHandledDeployAt = v - } - if err != nil { - setReadiness(obj, metav1.ConditionFalse, kluctlv1.PrepareFailedReason, err.Error()) - return nil, err } var ctrlResult ctrl.Result @@ -310,6 +327,20 @@ func (r *KluctlDeploymentReconciler) doReconcile( } func (r *KluctlDeploymentReconciler) buildFinalStatus(obj *kluctlv1.KluctlDeployment) (finalStatus string, reason string) { + if obj.Status.LastDeployError != "" { + finalStatus = obj.Status.LastDeployError + reason = kluctlv1.DeployFailedReason + return + } else if obj.Status.LastPruneError != "" { + finalStatus = obj.Status.LastPruneError + reason = kluctlv1.PruneFailedReason + return + } else if obj.Status.LastValidateError != "" { + finalStatus = obj.Status.LastValidateError + reason = kluctlv1.ValidateFailedReason + return + } + deployOk := obj.Status.LastDeployResult != nil && obj.Status.LastDeployResult.Errors == 0 pruneOk := obj.Status.LastPruneResult != nil && obj.Status.LastPruneResult.Errors == 0 validateOk := obj.Status.LastValidateResult != nil && len(obj.Status.LastValidateResult.Errors) == 0 && obj.Status.LastValidateResult.Ready @@ -470,10 +501,7 @@ func (r *KluctlDeploymentReconciler) doFinalize(ctx context.Context, obj *kluctl } defer pp.cleanup() - pt, err := pp.newTarget() - if err != nil { - return - } + pt := pp.newTarget() _, _ = pt.kluctlDelete(ctx, obj.Status.TargetKey.Discriminator) } diff --git a/pkg/controllers/utils.go b/pkg/controllers/utils.go index 7d4476e8d..050f21805 100644 --- a/pkg/controllers/utils.go +++ b/pkg/controllers/utils.go @@ -9,17 +9,16 @@ import ( func setReadiness(obj *kluctlv1.KluctlDeployment, status metav1.ConditionStatus, reason, message string) { newCondition := metav1.Condition{ - Type: meta.ReadyCondition, - Status: status, - Reason: reason, - Message: trimString(message, kluctlv1.MaxConditionMessageLength), + Type: meta.ReadyCondition, + Status: status, + Reason: reason, + Message: trimString(message, kluctlv1.MaxConditionMessageLength), + ObservedGeneration: obj.Generation, } c := obj.GetConditions() apimeta.SetStatusCondition(&c, newCondition) obj.SetConditions(c) - - obj.Status.ObservedGeneration = obj.GetGeneration() } func trimString(str string, limit int) string { From 0c5f350c1a60c44d7eb2fd1fa0be4d5a7dad01f0 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 19 May 2023 11:55:25 +0200 Subject: [PATCH 1660/2916] tests: Implement controller error tests --- e2e/gitops_errors_test.go | 243 ++++++++++++++++++++++++++++++++++++++ e2e/gitops_test.go | 111 ++++++++--------- 2 files changed, 301 insertions(+), 53 deletions(-) create mode 100644 e2e/gitops_errors_test.go diff --git a/e2e/gitops_errors_test.go b/e2e/gitops_errors_test.go new file mode 100644 index 000000000..ffbc0e008 --- /dev/null +++ b/e2e/gitops_errors_test.go @@ -0,0 +1,243 @@ +package e2e + +import ( + "context" + kluctlv1 "github.com/kluctl/kluctl/v2/api/v1beta1" + test_utils "github.com/kluctl/kluctl/v2/e2e/test-utils" + "github.com/kluctl/kluctl/v2/pkg/results" + "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/types/result" + "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/utils/flux_utils/meta" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + . "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + "testing" +) + +func getReadiness(obj *kluctlv1.KluctlDeployment) *metav1.Condition { + for _, c := range obj.Status.Conditions { + if c.Type == meta.ReadyCondition { + return &c + } + } + return nil +} + +func assertErrors(t *testing.T, k *test_utils.EnvTestCluster, key client.ObjectKey, rstatus metav1.ConditionStatus, rreason string, rmessage string, expectedErrors []result.DeploymentError, expectedWarnings []result.DeploymentError) { + g := NewWithT(t) + + var kd kluctlv1.KluctlDeployment + err := k.Client.Get(context.TODO(), key, &kd) + g.Expect(err).To(Succeed()) + + g.Expect(kd.Status.LastDeployResult).ToNot(BeNil()) + + readinessCondition := getReadiness(&kd) + g.Expect(readinessCondition).ToNot(BeNil()) + + g.Expect(readinessCondition.Status).To(Equal(rstatus)) + g.Expect(readinessCondition.Reason).To(Equal(rreason)) + g.Expect(readinessCondition.Message).To(ContainSubstring(rmessage)) + + rs, err := results.NewResultStoreSecrets(context.TODO(), k.Client, nil, "", 0) + g.Expect(err).To(Succeed()) + + cr, err := rs.GetCommandResult(results.GetCommandResultOptions{ + Id: kd.Status.LastDeployResult.Id, + }) + g.Expect(err).To(Succeed()) + + g.Expect(cr.Errors).To(ConsistOf(expectedErrors)) + g.Expect(cr.Warnings).To(ConsistOf(expectedWarnings)) + + g.Expect(kd.Status.LastDeployResult.Errors).To(Equal(len(expectedErrors))) + g.Expect(kd.Status.LastDeployResult.Warnings).To(Equal(len(expectedWarnings))) +} + +func TestGitOpsErrors(t *testing.T) { + g := NewWithT(t) + _ = g + + startKluctlController(t) + + k := defaultCluster1 + + p := test_utils.NewTestProject(t) + createNamespace(t, k, p.TestSlug()) + + goodCm1 := `apiVersion: v1 +kind: ConfigMap +metadata: + name: cm1 + namespace: "{{ args.namespace }}" +data: + k1: v1 +` + badCm1_1 := `apiVersion: v1 +kind: ConfigMap +metadata: + name: cm1 + namespace: "{{ args.namespace }}" +data_error: + k1: v1 +` + badCm1_2 := `apiVersion: v1 +kind: ConfigMap +metadata: + name: cm1 + namespace: "{{ args.namespace }}" +data: + k1: { +` + + p.UpdateTarget("target1", nil) + p.AddKustomizeDeployment("d1", []test_utils.KustomizeResource{ + {Name: "cm1.yaml", Content: uo.FromStringMust(goodCm1)}, + }, nil) + + key := createKluctlDeployment(t, p, k, "target1", map[string]any{ + "namespace": p.TestSlug(), + }) + + t.Run("initial deployment", func(t *testing.T) { + waitForCommit(t, k, key, getHeadRevision(t, p)) + }) + + cm1Ref := k8s.NewObjectRef("", "v1", "ConfigMap", "cm1", p.TestSlug()) + + t.Run("cm1 causes error while applying", func(t *testing.T) { + p.UpdateFile("d1/cm1.yaml", func(f string) (string, error) { + return badCm1_1, nil + }, "") + waitForReconcile(t, k, key) + assertErrors(t, k, key, metav1.ConditionFalse, kluctlv1.DeployFailedReason, "deploy failed with 1 errors", []result.DeploymentError{ + { + Ref: cm1Ref, + Message: "failed to patch test-git-ops-errors/ConfigMap/cm1: failed to create typed patch object (test-git-ops-errors/cm1; /v1, Kind=ConfigMap): .data_error: field not declared in schema", + }, + }, nil) + p.UpdateFile("d1/cm1.yaml", func(f string) (string, error) { + return goodCm1, nil + }, "") + waitForCommit(t, k, key, getHeadRevision(t, p)) + }) + + t.Run("cm1 causes error while loading", func(t *testing.T) { + p.UpdateFile("d1/cm1.yaml", func(f string) (string, error) { + return badCm1_2, nil + }, "") + waitForReconcile(t, k, key) + assertErrors(t, k, key, metav1.ConditionFalse, kluctlv1.PrepareFailedReason, "MalformedYAMLError: yaml: line 7: did not find expected node content in File: cm1.yaml", nil, nil) + p.UpdateFile("d1/cm1.yaml", func(f string) (string, error) { + return goodCm1, nil + }, "") + waitForCommit(t, k, key, getHeadRevision(t, p)) + }) + + t.Run("project can't be loaded", func(t *testing.T) { + kluctlBackup := "" + p.UpdateFile(".kluctl.yml", func(f string) (string, error) { + kluctlBackup = f + return "a: b", nil + }, "") + waitForReconcile(t, k, key) + assertErrors(t, k, key, metav1.ConditionFalse, kluctlv1.PrepareFailedReason, ".kluctl.yml failed: error unmarshaling JSON: while decoding JSON: json: unknown field \"a\"", nil, nil) + p.UpdateFile(".kluctl.yml", func(f string) (string, error) { + return kluctlBackup, nil + }, "") + waitForCommit(t, k, key, getHeadRevision(t, p)) + }) + + t.Run("deployment can't be loaded", func(t *testing.T) { + deploymentBackup := "" + p.UpdateFile("deployment.yml", func(f string) (string, error) { + deploymentBackup = f + return "a: b", nil + }, "") + waitForReconcile(t, k, key) + assertErrors(t, k, key, metav1.ConditionFalse, kluctlv1.PrepareFailedReason, "failed to load deployment.yml: error unmarshaling JSON: while decoding JSON: json: unknown field \"a\"", nil, nil) + p.UpdateFile("deployment.yml", func(f string) (string, error) { + return deploymentBackup, nil + }, "") + waitForCommit(t, k, key, getHeadRevision(t, p)) + }) + + t.Run("invalid target", func(t *testing.T) { + updateKluctlDeployment(t, k, key, func(kd *kluctlv1.KluctlDeployment) { + kd.Spec.Target = utils.StrPtr("invalid") + }) + waitForReconcile(t, k, key) + assertErrors(t, k, key, metav1.ConditionFalse, kluctlv1.PrepareFailedReason, "target invalid not existent in kluctl project config", nil, nil) + updateKluctlDeployment(t, k, key, func(kd *kluctlv1.KluctlDeployment) { + kd.Spec.Target = utils.StrPtr("target1") + }) + waitForCommit(t, k, key, getHeadRevision(t, p)) + }) + + t.Run("invalid context", func(t *testing.T) { + updateKluctlDeployment(t, k, key, func(kd *kluctlv1.KluctlDeployment) { + kd.Spec.Context = utils.StrPtr("invalid") + }) + waitForReconcile(t, k, key) + assertErrors(t, k, key, metav1.ConditionFalse, kluctlv1.PrepareFailedReason, "context \"invalid\" does not exist", nil, nil) + updateKluctlDeployment(t, k, key, func(kd *kluctlv1.KluctlDeployment) { + kd.Spec.Context = utils.StrPtr("default") + }) + waitForCommit(t, k, key, getHeadRevision(t, p)) + }) + + t.Run("non existing git repo", func(t *testing.T) { + var backup types.GitUrl + updateKluctlDeployment(t, k, key, func(kd *kluctlv1.KluctlDeployment) { + backup = kd.Spec.Source.URL + kd.Spec.Source.URL = *types.ParseGitUrlMust(backup.String() + "/invalid") + }) + waitForReconcile(t, k, key) + assertErrors(t, k, key, metav1.ConditionFalse, kluctlv1.PrepareFailedReason, "failed clone source: repository not found", nil, nil) + updateKluctlDeployment(t, k, key, func(kd *kluctlv1.KluctlDeployment) { + kd.Spec.Source.URL = backup + }) + waitForCommit(t, k, key, getHeadRevision(t, p)) + }) + t.Run("non existing git branch", func(t *testing.T) { + var backup *kluctlv1.GitRef + updateKluctlDeployment(t, k, key, func(kd *kluctlv1.KluctlDeployment) { + backup = kd.Spec.Source.Ref + kd.Spec.Source.Ref = &kluctlv1.GitRef{ + Branch: "invalid", + } + }) + waitForReconcile(t, k, key) + assertErrors(t, k, key, metav1.ConditionFalse, kluctlv1.PrepareFailedReason, "ref refs/heads/invalid not found", nil, nil) + updateKluctlDeployment(t, k, key, func(kd *kluctlv1.KluctlDeployment) { + kd.Spec.Source.Ref = backup + }) + waitForCommit(t, k, key, getHeadRevision(t, p)) + }) + + t.Run("prune without discriminator", func(t *testing.T) { + var backup any + updateKluctlDeployment(t, k, key, func(kd *kluctlv1.KluctlDeployment) { + kd.Spec.Prune = true + }) + p.UpdateKluctlYaml(func(o *uo.UnstructuredObject) error { + backup, _, _ = o.GetNestedField("discriminator") + _ = o.RemoveNestedField("discriminator") + return nil + }) + waitForCommit(t, k, key, getHeadRevision(t, p)) + assertErrors(t, k, key, metav1.ConditionFalse, kluctlv1.PruneFailedReason, "pruning without a discriminator is not supported", nil, nil) + p.UpdateKluctlYaml(func(o *uo.UnstructuredObject) error { + _ = o.SetNestedField(backup, "discriminator") + return nil + }) + waitForCommit(t, k, key, getHeadRevision(t, p)) + assertErrors(t, k, key, metav1.ConditionTrue, kluctlv1.ReconciliationSucceededReason, "deploy: ok, prune: ok", nil, nil) + updateKluctlDeployment(t, k, key, func(kd *kluctlv1.KluctlDeployment) { + kd.Spec.Prune = false + }) + }) +} diff --git a/e2e/gitops_test.go b/e2e/gitops_test.go index 56d1cdfe0..95e2b3631 100644 --- a/e2e/gitops_test.go +++ b/e2e/gitops_test.go @@ -3,16 +3,17 @@ package e2e import ( "context" "encoding/json" + "fmt" kluctlv1 "github.com/kluctl/kluctl/v2/api/v1beta1" test_utils "github.com/kluctl/kluctl/v2/e2e/test-utils" types2 "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/onsi/gomega" - "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "math/rand" "sigs.k8s.io/controller-runtime/pkg/client" "strings" "testing" @@ -49,37 +50,42 @@ func startKluctlController(t *testing.T) context.CancelFunc { return cancel } +func triggerReconcile(t *testing.T, k *test_utils.EnvTestCluster, key client.ObjectKey) string { + reconcileId := fmt.Sprintf("%d", rand.Int63()) + + updateKluctlDeployment(t, k, key, func(kd *kluctlv1.KluctlDeployment) { + a := kd.GetAnnotations() + if a == nil { + a = map[string]string{} + } + a[kluctlv1.KluctlRequestReconcileAnnotation] = reconcileId + kd.SetAnnotations(a) + }) + return reconcileId +} + func waitForReconcile(t *testing.T, k *test_utils.EnvTestCluster, key client.ObjectKey) { g := gomega.NewWithT(t) - var kd kluctlv1.KluctlDeployment - err := k.Client.Get(context.TODO(), key, &kd) - assert.NoError(t, err) + reconcileId := triggerReconcile(t, k, key) - var lastReconcileTime metav1.Time - if kd.Status.LastDeployResult != nil { - lastReconcileTime = kd.Status.LastDeployResult.Command.StartTime - } g.Eventually(func() bool { - err = k.Client.Get(context.TODO(), key, &kd) + var kd kluctlv1.KluctlDeployment + err := k.Client.Get(context.TODO(), key, &kd) g.Expect(err).To(Succeed()) - if kd.Status.LastDeployResult == nil { - return false - } - return kd.Status.LastDeployResult.Command.StartTime != lastReconcileTime + return kd.Status.LastHandledReconcileAt == reconcileId }, timeout, time.Second).Should(BeTrue()) } func waitForCommit(t *testing.T, k *test_utils.EnvTestCluster, key client.ObjectKey, commit string) { g := gomega.NewWithT(t) + reconcileId := triggerReconcile(t, k, key) + g.Eventually(func() bool { - var obj kluctlv1.KluctlDeployment - _ = k.Client.Get(context.Background(), key, &obj) - if obj.Status.LastDeployResult == nil { - return false - } - return obj.Status.LastDeployResult.GitInfo.Commit == commit + var kd kluctlv1.KluctlDeployment + _ = k.Client.Get(context.Background(), key, &kd) + return kd.Status.LastHandledReconcileAt == reconcileId && kd.Status.ObservedCommit == commit }, timeout, time.Second).Should(BeTrue()) } @@ -118,6 +124,23 @@ func createKluctlDeployment(t *testing.T, p *test_utils.TestProject, k *test_uti return client.ObjectKeyFromObject(kluctlDeployment) } +func updateKluctlDeployment(t *testing.T, k *test_utils.EnvTestCluster, key client.ObjectKey, update func(kd *kluctlv1.KluctlDeployment)) *kluctlv1.KluctlDeployment { + g := NewWithT(t) + + var kd kluctlv1.KluctlDeployment + err := k.Client.Get(context.TODO(), key, &kd) + g.Expect(err).To(Succeed()) + + patch := client.MergeFrom(kd.DeepCopy()) + + update(&kd) + + err = k.Client.Patch(context.TODO(), &kd, patch, client.FieldOwner("kubectl")) + g.Expect(err).To(Succeed()) + + return &kd +} + func TestGitOpsFieldManager(t *testing.T) { g := NewWithT(t) @@ -150,13 +173,9 @@ data: waitForCommit(t, k, key, getHeadRevision(t, p)) }) - var kd kluctlv1.KluctlDeployment - err := k.Client.Get(context.TODO(), key, &kd) - g.Expect(err).To(Succeed()) - - kd.Spec.DeployInterval = &kluctlv1.SafeDuration{Duration: metav1.Duration{Duration: interval}} - err = k.Client.Update(context.TODO(), &kd) - g.Expect(err).To(Succeed()) + updateKluctlDeployment(t, k, key, func(kd *kluctlv1.KluctlDeployment) { + kd.Spec.DeployInterval = &kluctlv1.SafeDuration{Duration: metav1.Duration{Duration: interval}} + }) cm := &corev1.ConfigMap{} @@ -222,12 +241,9 @@ data: g.Expect(cm.Data).To(HaveKeyWithValue("k1", "v2")) }) - err = k.Client.Get(context.TODO(), key, &kd) - g.Expect(err).To(Succeed()) - - kd.Spec.ForceApply = true - err = k.Client.Update(context.TODO(), &kd) - g.Expect(err).To(Succeed()) + updateKluctlDeployment(t, k, key, func(kd *kluctlv1.KluctlDeployment) { + kd.Spec.ForceApply = true + }) t.Run("forceApply is true and cm1 gets restored even with another field manager", func(t *testing.T) { patch := client.MergeFrom(cm.DeepCopy()) @@ -327,9 +343,9 @@ func TestKluctlDeploymentReconciler_Helm(t *testing.T) { err := k.Client.Create(context.TODO(), credsSecret) g.Expect(err).To(Succeed()) - kd.Spec.HelmCredentials = append(kd.Spec.HelmCredentials, kluctlv1.HelmCredentials{SecretRef: kluctlv1.LocalObjectReference{Name: "helm-secrets-1"}}) - err = k.Client.Update(context.TODO(), kd) - g.Expect(err).To(Succeed()) + updateKluctlDeployment(t, k, key, func(kd *kluctlv1.KluctlDeployment) { + kd.Spec.HelmCredentials = append(kd.Spec.HelmCredentials, kluctlv1.HelmCredentials{SecretRef: kluctlv1.LocalObjectReference{Name: "helm-secrets-1"}}) + }) t.Run("chart with credentials succeeds", func(t *testing.T) { g.Eventually(func() bool { @@ -494,19 +510,11 @@ data: g.Expect(err).To(Succeed()) }) - kd := &kluctlv1.KluctlDeployment{} - g.Expect(k.Client.Get(context.TODO(), key, kd)).To(Succeed()) - kd.Spec.Prune = true + updateKluctlDeployment(t, k, key, func(kd *kluctlv1.KluctlDeployment) { + kd.Spec.Prune = true + }) - g.Expect(k.Client.Update(context.TODO(), kd)).To(Succeed()) - g.Eventually(func() bool { - var obj kluctlv1.KluctlDeployment - _ = k.Client.Get(context.Background(), key, &obj) - if obj.Status.LastDeployResult == nil { - return false - } - return obj.Status.ObservedGeneration == obj.Generation && obj.Status.LastDeployResult.GitInfo.Commit == getHeadRevision(t, p) - }, timeout, time.Second).Should(BeTrue()) + waitForReconcile(t, k, key) t.Run("cm1 did not get deleted and cm2 got deleted", func(t *testing.T) { err := k.Client.Get(context.TODO(), client.ObjectKey{ @@ -548,12 +556,9 @@ data: "namespace": p.TestSlug(), }) - kd := &kluctlv1.KluctlDeployment{} - err := k.Client.Get(context.TODO(), key, kd) - g.Expect(err).To(Succeed()) - - kd.Spec.Delete = delete - g.Expect(k.Client.Update(context.TODO(), kd)).To(Succeed()) + kd := updateKluctlDeployment(t, k, key, func(kd *kluctlv1.KluctlDeployment) { + kd.Spec.Delete = delete + }) waitForCommit(t, k, key, getHeadRevision(t, p)) From ae204b7b1e28ad5fdd9ae7d09000ca4219a62daa Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 19 May 2023 12:59:57 +0200 Subject: [PATCH 1661/2916] fix: Fix race condition in GetCommandResult --- pkg/results/result-store-secrets.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pkg/results/result-store-secrets.go b/pkg/results/result-store-secrets.go index 88527503c..5d244d538 100644 --- a/pkg/results/result-store-secrets.go +++ b/pkg/results/result-store-secrets.go @@ -399,30 +399,30 @@ func (s *ResultStoreSecrets) GetCommandResult(options GetCommandResultOptions) ( var crJson, objectsJson []byte err = utils.RunParallelE(s.ctx, func() error { - var ok bool - crJson, ok = secret.Data["reducedResult"] + j, ok := secret.Data["reducedResult"] if !ok { return fmt.Errorf("reducedResult field not present for %s", options.Id) } - crJson, err = utils.UncompressGzip(crJson) + j, err := utils.UncompressGzip(j) if err != nil { return err } + crJson = j return nil }, func() error { if options.Reduced { return nil } - var ok bool - objectsJson, ok = secret.Data["compactedObjects"] + j, ok := secret.Data["compactedObjects"] if !ok { return fmt.Errorf("compactedObjects field not present for %s", options.Id) } - objectsJson, err = utils.UncompressGzip(objectsJson) + j, err := utils.UncompressGzip(j) if err != nil { return err } - return err + objectsJson = j + return nil }) if err != nil { return nil, err From 07bedd0505273994e92ab331d7812afe7b812b74 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 19 May 2023 15:22:17 +0200 Subject: [PATCH 1662/2916] feat: Allow to override kubeconfig and context --- cmd/kluctl/commands/cmd_controller.go | 84 ++++++++++++++++++++++++++- pkg/utils/flux_utils/client.go | 84 --------------------------- 2 files changed, 81 insertions(+), 87 deletions(-) delete mode 100644 pkg/utils/flux_utils/client.go diff --git a/cmd/kluctl/commands/cmd_controller.go b/cmd/kluctl/commands/cmd_controller.go index 3e3f9df86..132e7a55b 100644 --- a/cmd/kluctl/commands/cmd_controller.go +++ b/cmd/kluctl/commands/cmd_controller.go @@ -2,16 +2,23 @@ package commands import ( "context" + "fmt" kluctlv1 "github.com/kluctl/kluctl/v2/api/v1beta1" "github.com/kluctl/kluctl/v2/pkg/controllers" ssh_pool "github.com/kluctl/kluctl/v2/pkg/git/ssh-pool" - "github.com/kluctl/kluctl/v2/pkg/utils/flux_utils" "github.com/kluctl/kluctl/v2/pkg/utils/flux_utils/metrics" + log "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/client-go/kubernetes" clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" "os" + "os/user" + "path/filepath" + "sigs.k8s.io/cli-utils/pkg/flowcontrol" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" @@ -32,6 +39,9 @@ const controllerName = "kluctl-controller" type controllerCmd struct { scheme *runtime.Scheme + Kubeconfig string `group:"controller" help:"Override the kubeconfig to use."` + Context string `group:"controller" help:"Override the context to use."` + MetricsBindAddress string `group:"controller" help:"The address the metric endpoint binds to." default:":8080"` HealthProbeBindAddress string `group:"controller" help:"The address the probe endpoint binds to." default:":8081"` LeaderElect bool `group:"controller" help:"Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager."` @@ -64,14 +74,27 @@ func (cmd *controllerCmd) Run(ctx context.Context) error { } ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) - restConfig := flux_utils.GetConfigOrDie(flux_utils.Options{}) + restConfig, err := cmd.loadConfig(cmd.Kubeconfig, cmd.Context) + if err != nil { + setupLog.Error(err, "unable to load kubeconfig") + os.Exit(1) + } + + enabled, err := flowcontrol.IsEnabled(context.Background(), restConfig) + if err == nil && enabled { + // A negative QPS and Burst indicates that the client should not have a rate limiter. + // Ref: https://github.com/kubernetes/kubernetes/blob/v1.24.0/staging/src/k8s.io/client-go/rest/config.go#L354-L364 + restConfig.QPS = -1 + restConfig.Burst = -1 + } + clientSet, err := kubernetes.NewForConfig(restConfig) if err != nil { setupLog.Error(err, "unable to start manager") os.Exit(1) } - mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ + mgr, err := ctrl.NewManager(restConfig, ctrl.Options{ Scheme: cmd.scheme, MetricsBindAddress: cmd.MetricsBindAddress, Port: 9443, @@ -138,3 +161,58 @@ func (cmd *controllerCmd) Run(ctx context.Context) error { return nil } + +// taken from clientcmd +func (cmd *controllerCmd) loadConfig(kubeconfig string, context string) (config *rest.Config, configErr error) { + // If a flag is specified with the config location, use that + if len(kubeconfig) > 0 { + return cmd.loadConfigWithContext("", &clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfig}, context) + } + + // If the recommended kubeconfig env variable is not specified, + // try the in-cluster config. + kubeconfigPath := os.Getenv(clientcmd.RecommendedConfigPathEnvVar) + if len(kubeconfigPath) == 0 { + c, err := rest.InClusterConfig() + if err == nil { + return c, nil + } + + defer func() { + if configErr != nil { + log.Error(err, "unable to load in-cluster config") + } + }() + } + + // If the recommended kubeconfig env variable is set, or there + // is no in-cluster config, try the default recommended locations. + // + // NOTE: For default config file locations, upstream only checks + // $HOME for the user's home directory, but we can also try + // os/user.HomeDir when $HOME is unset. + // + // TODO(jlanford): could this be done upstream? + loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() + if _, ok := os.LookupEnv("HOME"); !ok { + u, err := user.Current() + if err != nil { + return nil, fmt.Errorf("could not get current user: %w", err) + } + loadingRules.Precedence = append(loadingRules.Precedence, filepath.Join(u.HomeDir, clientcmd.RecommendedHomeDir, clientcmd.RecommendedFileName)) + } + + return cmd.loadConfigWithContext("", loadingRules, context) +} + +// taken from clientcmd +func (cmd *controllerCmd) loadConfigWithContext(apiServerURL string, loader clientcmd.ClientConfigLoader, context string) (*rest.Config, error) { + return clientcmd.NewNonInteractiveDeferredLoadingClientConfig( + loader, + &clientcmd.ConfigOverrides{ + ClusterInfo: clientcmdapi.Cluster{ + Server: apiServerURL, + }, + CurrentContext: context, + }).ClientConfig() +} diff --git a/pkg/utils/flux_utils/client.go b/pkg/utils/flux_utils/client.go deleted file mode 100644 index 05e796cc7..000000000 --- a/pkg/utils/flux_utils/client.go +++ /dev/null @@ -1,84 +0,0 @@ -/* -Copyright 2021 The Flux authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package flux_utils - -import ( - "context" - - "github.com/spf13/pflag" - "k8s.io/client-go/rest" - "sigs.k8s.io/cli-utils/pkg/flowcontrol" - ctrl "sigs.k8s.io/controller-runtime" -) - -const ( - flagQPS = "kube-api-qps" - flagBurst = "kube-api-burst" -) - -// Options contains the runtime configuration for a Kubernetes client. -// -// The struct can be used in the main.go file of your controller by binding it to the main flag set, and then utilizing -// the configured options later: -// -// func main() { -// var ( -// // other controller specific configuration variables -// clientOptions client.Options -// ) -// -// // Bind the options to the main flag set, and parse it -// clientOptions.BindFlags(flag.CommandLine) -// flag.Parse() -// -// // Get a runtime Kubernetes client configuration with the options set -// restConfig := client.GetConfigOrDie(clientOptions) -// } -type Options struct { - // QPS indicates the maximum queries-per-second of requests sent to the Kubernetes API, defaults to 50. - QPS float32 - - // Burst indicates the maximum burst queries-per-second of requests sent to the Kubernetes API, defaults to 300. - Burst int -} - -// BindFlags will parse the given pflag.FlagSet for Kubernetes client option flags and set the Options accordingly. -func (o *Options) BindFlags(fs *pflag.FlagSet) { - fs.Float32Var(&o.QPS, flagQPS, 50.0, - "The maximum queries-per-second of requests sent to the Kubernetes API.") - fs.IntVar(&o.Burst, flagBurst, 300, - "The maximum burst queries-per-second of requests sent to the Kubernetes API.") -} - -// GetConfigOrDie wraps ctrl.GetConfigOrDie and checks if the Kubernetes apiserver -// has PriorityAndFairness flow control filter enabled. If true, it returns a rest.Config -// with client side throttling disabled. Otherwise, it returns a modified rest.Config -// configured with the provided Options. -func GetConfigOrDie(opts Options) *rest.Config { - config := ctrl.GetConfigOrDie() - enabled, err := flowcontrol.IsEnabled(context.Background(), config) - if err == nil && enabled { - // A negative QPS and Burst indicates that the client should not have a rate limiter. - // Ref: https://github.com/kubernetes/kubernetes/blob/v1.24.0/staging/src/k8s.io/client-go/rest/config.go#L354-L364 - config.QPS = -1 - config.Burst = -1 - return config - } - config.QPS = opts.QPS - config.Burst = opts.Burst - return config -} From eebfafc9ca0c37306112f4efd2e4bae59be98252 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 19 May 2023 15:22:35 +0200 Subject: [PATCH 1663/2916] tests: Run gitops tests in own test suite with own cluster --- e2e/default_clusters.go | 3 - e2e/gitops_errors_test.go | 127 ++++++++-------- e2e/gitops_test.go | 309 +++++++++++++++++++++++--------------- 3 files changed, 252 insertions(+), 187 deletions(-) diff --git a/e2e/default_clusters.go b/e2e/default_clusters.go index 8765afb63..373957a89 100644 --- a/e2e/default_clusters.go +++ b/e2e/default_clusters.go @@ -22,9 +22,6 @@ func init() { return } - defaultCluster1.CRDDirectoryPaths = []string{"../config/crd/bases"} - defaultCluster2.CRDDirectoryPaths = []string{"../config/crd/bases"} - var wg sync.WaitGroup wg.Add(2) go func() { diff --git a/e2e/gitops_errors_test.go b/e2e/gitops_errors_test.go index ffbc0e008..56249cf9b 100644 --- a/e2e/gitops_errors_test.go +++ b/e2e/gitops_errors_test.go @@ -14,10 +14,9 @@ import ( . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" - "testing" ) -func getReadiness(obj *kluctlv1.KluctlDeployment) *metav1.Condition { +func (suite *GitopsTestSuite) getReadiness(obj *kluctlv1.KluctlDeployment) *metav1.Condition { for _, c := range obj.Status.Conditions { if c.Type == meta.ReadyCondition { return &c @@ -26,23 +25,23 @@ func getReadiness(obj *kluctlv1.KluctlDeployment) *metav1.Condition { return nil } -func assertErrors(t *testing.T, k *test_utils.EnvTestCluster, key client.ObjectKey, rstatus metav1.ConditionStatus, rreason string, rmessage string, expectedErrors []result.DeploymentError, expectedWarnings []result.DeploymentError) { - g := NewWithT(t) +func (suite *GitopsTestSuite) assertErrors(key client.ObjectKey, rstatus metav1.ConditionStatus, rreason string, rmessage string, expectedErrors []result.DeploymentError, expectedWarnings []result.DeploymentError) { + g := NewWithT(suite.T()) var kd kluctlv1.KluctlDeployment - err := k.Client.Get(context.TODO(), key, &kd) + err := suite.k.Client.Get(context.TODO(), key, &kd) g.Expect(err).To(Succeed()) g.Expect(kd.Status.LastDeployResult).ToNot(BeNil()) - readinessCondition := getReadiness(&kd) + readinessCondition := suite.getReadiness(&kd) g.Expect(readinessCondition).ToNot(BeNil()) g.Expect(readinessCondition.Status).To(Equal(rstatus)) g.Expect(readinessCondition.Reason).To(Equal(rreason)) g.Expect(readinessCondition.Message).To(ContainSubstring(rmessage)) - rs, err := results.NewResultStoreSecrets(context.TODO(), k.Client, nil, "", 0) + rs, err := results.NewResultStoreSecrets(context.TODO(), suite.k.Client, nil, "", 0) g.Expect(err).To(Succeed()) cr, err := rs.GetCommandResult(results.GetCommandResultOptions{ @@ -57,16 +56,12 @@ func assertErrors(t *testing.T, k *test_utils.EnvTestCluster, key client.ObjectK g.Expect(kd.Status.LastDeployResult.Warnings).To(Equal(len(expectedWarnings))) } -func TestGitOpsErrors(t *testing.T) { - g := NewWithT(t) +func (suite *GitopsTestSuite) TestGitOpsErrors() { + g := NewWithT(suite.T()) _ = g - startKluctlController(t) - - k := defaultCluster1 - - p := test_utils.NewTestProject(t) - createNamespace(t, k, p.TestSlug()) + p := test_utils.NewTestProject(suite.T()) + createNamespace(suite.T(), suite.k, p.TestSlug()) goodCm1 := `apiVersion: v1 kind: ConfigMap @@ -98,129 +93,129 @@ data: {Name: "cm1.yaml", Content: uo.FromStringMust(goodCm1)}, }, nil) - key := createKluctlDeployment(t, p, k, "target1", map[string]any{ + key := suite.createKluctlDeployment(p, "target1", map[string]any{ "namespace": p.TestSlug(), }) - t.Run("initial deployment", func(t *testing.T) { - waitForCommit(t, k, key, getHeadRevision(t, p)) + suite.Run("initial deployment", func() { + suite.waitForCommit(key, getHeadRevision(suite.T(), p)) }) cm1Ref := k8s.NewObjectRef("", "v1", "ConfigMap", "cm1", p.TestSlug()) - t.Run("cm1 causes error while applying", func(t *testing.T) { + suite.Run("cm1 causes error while applying", func() { p.UpdateFile("d1/cm1.yaml", func(f string) (string, error) { return badCm1_1, nil }, "") - waitForReconcile(t, k, key) - assertErrors(t, k, key, metav1.ConditionFalse, kluctlv1.DeployFailedReason, "deploy failed with 1 errors", []result.DeploymentError{ + suite.waitForReconcile(key) + suite.assertErrors(key, metav1.ConditionFalse, kluctlv1.DeployFailedReason, "deploy failed with 1 errors", []result.DeploymentError{ { Ref: cm1Ref, - Message: "failed to patch test-git-ops-errors/ConfigMap/cm1: failed to create typed patch object (test-git-ops-errors/cm1; /v1, Kind=ConfigMap): .data_error: field not declared in schema", + Message: "failed to patch test-git-ops-test-git-ops-errors/ConfigMap/cm1: failed to create typed patch object (test-git-ops-test-git-ops-errors/cm1; /v1, Kind=ConfigMap): .data_error: field not declared in schema", }, }, nil) p.UpdateFile("d1/cm1.yaml", func(f string) (string, error) { return goodCm1, nil }, "") - waitForCommit(t, k, key, getHeadRevision(t, p)) + suite.waitForCommit(key, getHeadRevision(suite.T(), p)) }) - t.Run("cm1 causes error while loading", func(t *testing.T) { + suite.Run("cm1 causes error while loading", func() { p.UpdateFile("d1/cm1.yaml", func(f string) (string, error) { return badCm1_2, nil }, "") - waitForReconcile(t, k, key) - assertErrors(t, k, key, metav1.ConditionFalse, kluctlv1.PrepareFailedReason, "MalformedYAMLError: yaml: line 7: did not find expected node content in File: cm1.yaml", nil, nil) + suite.waitForReconcile(key) + suite.assertErrors(key, metav1.ConditionFalse, kluctlv1.PrepareFailedReason, "MalformedYAMLError: yaml: line 7: did not find expected node content in File: cm1.yaml", nil, nil) p.UpdateFile("d1/cm1.yaml", func(f string) (string, error) { return goodCm1, nil }, "") - waitForCommit(t, k, key, getHeadRevision(t, p)) + suite.waitForCommit(key, getHeadRevision(suite.T(), p)) }) - t.Run("project can't be loaded", func(t *testing.T) { + suite.Run("project can't be loaded", func() { kluctlBackup := "" p.UpdateFile(".kluctl.yml", func(f string) (string, error) { kluctlBackup = f return "a: b", nil }, "") - waitForReconcile(t, k, key) - assertErrors(t, k, key, metav1.ConditionFalse, kluctlv1.PrepareFailedReason, ".kluctl.yml failed: error unmarshaling JSON: while decoding JSON: json: unknown field \"a\"", nil, nil) + suite.waitForReconcile(key) + suite.assertErrors(key, metav1.ConditionFalse, kluctlv1.PrepareFailedReason, ".kluctl.yml failed: error unmarshaling JSON: while decoding JSON: json: unknown field \"a\"", nil, nil) p.UpdateFile(".kluctl.yml", func(f string) (string, error) { return kluctlBackup, nil }, "") - waitForCommit(t, k, key, getHeadRevision(t, p)) + suite.waitForCommit(key, getHeadRevision(suite.T(), p)) }) - t.Run("deployment can't be loaded", func(t *testing.T) { + suite.Run("deployment can't be loaded", func() { deploymentBackup := "" p.UpdateFile("deployment.yml", func(f string) (string, error) { deploymentBackup = f return "a: b", nil }, "") - waitForReconcile(t, k, key) - assertErrors(t, k, key, metav1.ConditionFalse, kluctlv1.PrepareFailedReason, "failed to load deployment.yml: error unmarshaling JSON: while decoding JSON: json: unknown field \"a\"", nil, nil) + suite.waitForReconcile(key) + suite.assertErrors(key, metav1.ConditionFalse, kluctlv1.PrepareFailedReason, "failed to load deployment.yml: error unmarshaling JSON: while decoding JSON: json: unknown field \"a\"", nil, nil) p.UpdateFile("deployment.yml", func(f string) (string, error) { return deploymentBackup, nil }, "") - waitForCommit(t, k, key, getHeadRevision(t, p)) + suite.waitForCommit(key, getHeadRevision(suite.T(), p)) }) - t.Run("invalid target", func(t *testing.T) { - updateKluctlDeployment(t, k, key, func(kd *kluctlv1.KluctlDeployment) { + suite.Run("invalid target", func() { + suite.updateKluctlDeployment(key, func(kd *kluctlv1.KluctlDeployment) { kd.Spec.Target = utils.StrPtr("invalid") }) - waitForReconcile(t, k, key) - assertErrors(t, k, key, metav1.ConditionFalse, kluctlv1.PrepareFailedReason, "target invalid not existent in kluctl project config", nil, nil) - updateKluctlDeployment(t, k, key, func(kd *kluctlv1.KluctlDeployment) { + suite.waitForReconcile(key) + suite.assertErrors(key, metav1.ConditionFalse, kluctlv1.PrepareFailedReason, "target invalid not existent in kluctl project config", nil, nil) + suite.updateKluctlDeployment(key, func(kd *kluctlv1.KluctlDeployment) { kd.Spec.Target = utils.StrPtr("target1") }) - waitForCommit(t, k, key, getHeadRevision(t, p)) + suite.waitForCommit(key, getHeadRevision(suite.T(), p)) }) - t.Run("invalid context", func(t *testing.T) { - updateKluctlDeployment(t, k, key, func(kd *kluctlv1.KluctlDeployment) { + suite.Run("invalid context", func() { + suite.updateKluctlDeployment(key, func(kd *kluctlv1.KluctlDeployment) { kd.Spec.Context = utils.StrPtr("invalid") }) - waitForReconcile(t, k, key) - assertErrors(t, k, key, metav1.ConditionFalse, kluctlv1.PrepareFailedReason, "context \"invalid\" does not exist", nil, nil) - updateKluctlDeployment(t, k, key, func(kd *kluctlv1.KluctlDeployment) { + suite.waitForReconcile(key) + suite.assertErrors(key, metav1.ConditionFalse, kluctlv1.PrepareFailedReason, "context \"invalid\" does not exist", nil, nil) + suite.updateKluctlDeployment(key, func(kd *kluctlv1.KluctlDeployment) { kd.Spec.Context = utils.StrPtr("default") }) - waitForCommit(t, k, key, getHeadRevision(t, p)) + suite.waitForCommit(key, getHeadRevision(suite.T(), p)) }) - t.Run("non existing git repo", func(t *testing.T) { + suite.Run("non existing git repo", func() { var backup types.GitUrl - updateKluctlDeployment(t, k, key, func(kd *kluctlv1.KluctlDeployment) { + suite.updateKluctlDeployment(key, func(kd *kluctlv1.KluctlDeployment) { backup = kd.Spec.Source.URL kd.Spec.Source.URL = *types.ParseGitUrlMust(backup.String() + "/invalid") }) - waitForReconcile(t, k, key) - assertErrors(t, k, key, metav1.ConditionFalse, kluctlv1.PrepareFailedReason, "failed clone source: repository not found", nil, nil) - updateKluctlDeployment(t, k, key, func(kd *kluctlv1.KluctlDeployment) { + suite.waitForReconcile(key) + suite.assertErrors(key, metav1.ConditionFalse, kluctlv1.PrepareFailedReason, "failed clone source: repository not found", nil, nil) + suite.updateKluctlDeployment(key, func(kd *kluctlv1.KluctlDeployment) { kd.Spec.Source.URL = backup }) - waitForCommit(t, k, key, getHeadRevision(t, p)) + suite.waitForCommit(key, getHeadRevision(suite.T(), p)) }) - t.Run("non existing git branch", func(t *testing.T) { + suite.Run("non existing git branch", func() { var backup *kluctlv1.GitRef - updateKluctlDeployment(t, k, key, func(kd *kluctlv1.KluctlDeployment) { + suite.updateKluctlDeployment(key, func(kd *kluctlv1.KluctlDeployment) { backup = kd.Spec.Source.Ref kd.Spec.Source.Ref = &kluctlv1.GitRef{ Branch: "invalid", } }) - waitForReconcile(t, k, key) - assertErrors(t, k, key, metav1.ConditionFalse, kluctlv1.PrepareFailedReason, "ref refs/heads/invalid not found", nil, nil) - updateKluctlDeployment(t, k, key, func(kd *kluctlv1.KluctlDeployment) { + suite.waitForReconcile(key) + suite.assertErrors(key, metav1.ConditionFalse, kluctlv1.PrepareFailedReason, "ref refs/heads/invalid not found", nil, nil) + suite.updateKluctlDeployment(key, func(kd *kluctlv1.KluctlDeployment) { kd.Spec.Source.Ref = backup }) - waitForCommit(t, k, key, getHeadRevision(t, p)) + suite.waitForCommit(key, getHeadRevision(suite.T(), p)) }) - t.Run("prune without discriminator", func(t *testing.T) { + suite.Run("prune without discriminator", func() { var backup any - updateKluctlDeployment(t, k, key, func(kd *kluctlv1.KluctlDeployment) { + suite.updateKluctlDeployment(key, func(kd *kluctlv1.KluctlDeployment) { kd.Spec.Prune = true }) p.UpdateKluctlYaml(func(o *uo.UnstructuredObject) error { @@ -228,15 +223,15 @@ data: _ = o.RemoveNestedField("discriminator") return nil }) - waitForCommit(t, k, key, getHeadRevision(t, p)) - assertErrors(t, k, key, metav1.ConditionFalse, kluctlv1.PruneFailedReason, "pruning without a discriminator is not supported", nil, nil) + suite.waitForCommit(key, getHeadRevision(suite.T(), p)) + suite.assertErrors(key, metav1.ConditionFalse, kluctlv1.PruneFailedReason, "pruning without a discriminator is not supported", nil, nil) p.UpdateKluctlYaml(func(o *uo.UnstructuredObject) error { _ = o.SetNestedField(backup, "discriminator") return nil }) - waitForCommit(t, k, key, getHeadRevision(t, p)) - assertErrors(t, k, key, metav1.ConditionTrue, kluctlv1.ReconciliationSucceededReason, "deploy: ok, prune: ok", nil, nil) - updateKluctlDeployment(t, k, key, func(kd *kluctlv1.KluctlDeployment) { + suite.waitForCommit(key, getHeadRevision(suite.T(), p)) + suite.assertErrors(key, metav1.ConditionTrue, kluctlv1.ReconciliationSucceededReason, "deploy: ok, prune: ok", nil, nil) + suite.updateKluctlDeployment(key, func(kd *kluctlv1.KluctlDeployment) { kd.Spec.Prune = false }) }) diff --git a/e2e/gitops_test.go b/e2e/gitops_test.go index 95e2b3631..191a65c5b 100644 --- a/e2e/gitops_test.go +++ b/e2e/gitops_test.go @@ -9,11 +9,14 @@ import ( types2 "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/onsi/gomega" + "github.com/stretchr/testify/suite" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "math/rand" + "os" + "path/filepath" "sigs.k8s.io/controller-runtime/pkg/client" "strings" "testing" @@ -27,16 +30,82 @@ const ( interval = time.Second * 5 ) -func startKluctlController(t *testing.T) context.CancelFunc { +type GitopsTestSuite struct { + suite.Suite + + k *test_utils.EnvTestCluster + + cancelController context.CancelFunc + + deployments []client.ObjectKey +} + +func (suite *GitopsTestSuite) SetupSuite() { + suite.startCluster() + suite.startController() +} + +func (suite *GitopsTestSuite) TearDownSuite() { + if suite.cancelController != nil { + suite.cancelController() + } + + if suite.k != nil { + suite.k.Stop() + } +} + +func (suite *GitopsTestSuite) TearDownTest() { + g := NewWithT(suite.T()) + + for _, key := range suite.deployments { + suite.deleteKluctlDeployment(key) + } + + g.Eventually(func() bool { + for _, key := range suite.deployments { + var kd kluctlv1.KluctlDeployment + err := suite.k.Client.Get(context.TODO(), key, &kd) + if err == nil { + return false + } + } + return true + }, timeout, time.Second).Should(BeTrue()) + + suite.deployments = nil +} + +func (suite *GitopsTestSuite) startCluster() { + suite.k = test_utils.CreateEnvTestCluster("context1") + suite.k.CRDDirectoryPaths = []string{"../config/crd/bases"} + + err := suite.k.Start() + if err != nil { + suite.T().Fatal(err) + } +} + +func (suite *GitopsTestSuite) startController() { + tmpKubeconfig := filepath.Join(suite.T().TempDir(), "kubeconfig") + err := os.WriteFile(tmpKubeconfig, suite.k.Kubeconfig, 0o600) + if err != nil { + suite.T().Fatal(err) + } + ctx, ctxCancel := context.WithCancel(context.Background()) args := []string{ "controller", + "--kubeconfig", + tmpKubeconfig, + "--context", + "context1", } done := make(chan struct{}) go func() { - _, _, err := test_utils.KluctlExecute(t, ctx, args...) + _, _, err := test_utils.KluctlExecute(suite.T(), ctx, args...) if err != nil { - t.Error(err) + suite.T().Error(err) } close(done) }() @@ -45,15 +114,13 @@ func startKluctlController(t *testing.T) context.CancelFunc { ctxCancel() <-done } - - t.Cleanup(cancel) - return cancel + suite.cancelController = cancel } -func triggerReconcile(t *testing.T, k *test_utils.EnvTestCluster, key client.ObjectKey) string { +func (suite *GitopsTestSuite) triggerReconcile(key client.ObjectKey) string { reconcileId := fmt.Sprintf("%d", rand.Int63()) - updateKluctlDeployment(t, k, key, func(kd *kluctlv1.KluctlDeployment) { + suite.updateKluctlDeployment(key, func(kd *kluctlv1.KluctlDeployment) { a := kd.GetAnnotations() if a == nil { a = map[string]string{} @@ -64,38 +131,38 @@ func triggerReconcile(t *testing.T, k *test_utils.EnvTestCluster, key client.Obj return reconcileId } -func waitForReconcile(t *testing.T, k *test_utils.EnvTestCluster, key client.ObjectKey) { - g := gomega.NewWithT(t) +func (suite *GitopsTestSuite) waitForReconcile(key client.ObjectKey) { + g := gomega.NewWithT(suite.T()) - reconcileId := triggerReconcile(t, k, key) + reconcileId := suite.triggerReconcile(key) g.Eventually(func() bool { var kd kluctlv1.KluctlDeployment - err := k.Client.Get(context.TODO(), key, &kd) + err := suite.k.Client.Get(context.TODO(), key, &kd) g.Expect(err).To(Succeed()) return kd.Status.LastHandledReconcileAt == reconcileId }, timeout, time.Second).Should(BeTrue()) } -func waitForCommit(t *testing.T, k *test_utils.EnvTestCluster, key client.ObjectKey, commit string) { - g := gomega.NewWithT(t) +func (suite *GitopsTestSuite) waitForCommit(key client.ObjectKey, commit string) { + g := gomega.NewWithT(suite.T()) - reconcileId := triggerReconcile(t, k, key) + reconcileId := suite.triggerReconcile(key) g.Eventually(func() bool { var kd kluctlv1.KluctlDeployment - _ = k.Client.Get(context.Background(), key, &kd) + _ = suite.k.Client.Get(context.Background(), key, &kd) return kd.Status.LastHandledReconcileAt == reconcileId && kd.Status.ObservedCommit == commit }, timeout, time.Second).Should(BeTrue()) } -func createKluctlDeployment(t *testing.T, p *test_utils.TestProject, k *test_utils.EnvTestCluster, target string, args map[string]any) client.ObjectKey { +func (suite *GitopsTestSuite) createKluctlDeployment(p *test_utils.TestProject, target string, args map[string]any) client.ObjectKey { gitopsNs := p.TestSlug() + "-gitops" - createNamespace(t, k, gitopsNs) + createNamespace(suite.T(), suite.k, gitopsNs) jargs, err := json.Marshal(args) if err != nil { - t.Fatal(err) + suite.T().Fatal(err) } kluctlDeployment := &kluctlv1.KluctlDeployment{ @@ -116,40 +183,50 @@ func createKluctlDeployment(t *testing.T, p *test_utils.TestProject, k *test_uti }, } - err = k.Client.Create(context.Background(), kluctlDeployment) + err = suite.k.Client.Create(context.Background(), kluctlDeployment) if err != nil { - t.Fatal(err) + suite.T().Fatal(err) } - return client.ObjectKeyFromObject(kluctlDeployment) + key := client.ObjectKeyFromObject(kluctlDeployment) + suite.deployments = append(suite.deployments, key) + return key } -func updateKluctlDeployment(t *testing.T, k *test_utils.EnvTestCluster, key client.ObjectKey, update func(kd *kluctlv1.KluctlDeployment)) *kluctlv1.KluctlDeployment { - g := NewWithT(t) +func (suite *GitopsTestSuite) updateKluctlDeployment(key client.ObjectKey, update func(kd *kluctlv1.KluctlDeployment)) *kluctlv1.KluctlDeployment { + g := NewWithT(suite.T()) var kd kluctlv1.KluctlDeployment - err := k.Client.Get(context.TODO(), key, &kd) + err := suite.k.Client.Get(context.TODO(), key, &kd) g.Expect(err).To(Succeed()) patch := client.MergeFrom(kd.DeepCopy()) update(&kd) - err = k.Client.Patch(context.TODO(), &kd, patch, client.FieldOwner("kubectl")) + err = suite.k.Client.Patch(context.TODO(), &kd, patch, client.FieldOwner("kubectl")) g.Expect(err).To(Succeed()) return &kd } -func TestGitOpsFieldManager(t *testing.T) { - g := NewWithT(t) +func (suite *GitopsTestSuite) deleteKluctlDeployment(key client.ObjectKey) { + g := NewWithT(suite.T()) - startKluctlController(t) + var kd kluctlv1.KluctlDeployment + kd.Name = key.Name + kd.Namespace = key.Namespace + err := suite.k.Client.Delete(context.Background(), &kd) + if err != nil && !errors.IsNotFound(err) { + g.Expect(err).To(Succeed()) + } +} - k := defaultCluster1 +func (suite *GitopsTestSuite) TestGitOpsFieldManager() { + g := NewWithT(suite.T()) - p := test_utils.NewTestProject(t) - createNamespace(t, k, p.TestSlug()) + p := test_utils.NewTestProject(suite.T()) + createNamespace(suite.T(), suite.k, p.TestSlug()) p.UpdateTarget("target1", nil) p.AddKustomizeDeployment("d1", []test_utils.KustomizeResource{ @@ -164,23 +241,23 @@ data: `)}, }, nil) - key := createKluctlDeployment(t, p, k, "target1", map[string]any{ + key := suite.createKluctlDeployment(p, "target1", map[string]any{ "namespace": p.TestSlug(), "k2": 42, }) - t.Run("initial deployment", func(t *testing.T) { - waitForCommit(t, k, key, getHeadRevision(t, p)) + suite.Run("initial deployment", func() { + suite.waitForCommit(key, getHeadRevision(suite.T(), p)) }) - updateKluctlDeployment(t, k, key, func(kd *kluctlv1.KluctlDeployment) { + suite.updateKluctlDeployment(key, func(kd *kluctlv1.KluctlDeployment) { kd.Spec.DeployInterval = &kluctlv1.SafeDuration{Duration: metav1.Duration{Duration: interval}} }) cm := &corev1.ConfigMap{} - t.Run("cm1 is deployed", func(t *testing.T) { - err := k.Client.Get(context.TODO(), client.ObjectKey{ + suite.Run("cm1 is deployed", func() { + err := suite.k.Client.Get(context.TODO(), client.ObjectKey{ Name: "cm1", Namespace: p.TestSlug(), }, cm) @@ -189,13 +266,13 @@ data: g.Expect(cm.Data).To(HaveKeyWithValue("k2", "43")) }) - t.Run("cm1 is modified and restored", func(t *testing.T) { + suite.Run("cm1 is modified and restored", func() { cm.Data["k1"] = "v2" - err := k.Client.Update(context.TODO(), cm, client.FieldOwner("kubectl")) + err := suite.k.Client.Update(context.TODO(), cm, client.FieldOwner("kubectl")) g.Expect(err).To(Succeed()) g.Eventually(func() bool { - err := k.Client.Get(context.TODO(), client.ObjectKey{ + err := suite.k.Client.Get(context.TODO(), client.ObjectKey{ Name: "cm1", Namespace: p.TestSlug(), }, cm) @@ -204,14 +281,14 @@ data: }, timeout, time.Second).Should(BeTrue()) }) - t.Run("cm1 gets a key added which is not modified by the controller", func(t *testing.T) { + suite.Run("cm1 gets a key added which is not modified by the controller", func() { cm.Data["k1"] = "v2" cm.Data["k3"] = "v3" - err := k.Client.Update(context.TODO(), cm, client.FieldOwner("kubectl")) + err := suite.k.Client.Update(context.TODO(), cm, client.FieldOwner("kubectl")) g.Expect(err).To(Succeed()) g.Eventually(func() bool { - err := k.Client.Get(context.TODO(), client.ObjectKey{ + err := suite.k.Client.Get(context.TODO(), client.ObjectKey{ Name: "cm1", Namespace: p.TestSlug(), }, cm) @@ -222,18 +299,18 @@ data: g.Expect(cm.Data).To(HaveKeyWithValue("k3", "v3")) }) - t.Run("cm1 gets modified with another field manager", func(t *testing.T) { + suite.Run("cm1 gets modified with another field manager", func() { patch := client.MergeFrom(cm.DeepCopy()) cm.Data["k1"] = "v2" - err := k.Client.Patch(context.TODO(), cm, patch, client.FieldOwner("test-field-manager")) + err := suite.k.Client.Patch(context.TODO(), cm, patch, client.FieldOwner("test-field-manager")) g.Expect(err).To(Succeed()) for i := 0; i < 2; i++ { - waitForReconcile(t, k, key) + suite.waitForReconcile(key) } - err = k.Client.Get(context.TODO(), client.ObjectKey{ + err = suite.k.Client.Get(context.TODO(), client.ObjectKey{ Name: "cm1", Namespace: p.TestSlug(), }, cm) @@ -241,19 +318,19 @@ data: g.Expect(cm.Data).To(HaveKeyWithValue("k1", "v2")) }) - updateKluctlDeployment(t, k, key, func(kd *kluctlv1.KluctlDeployment) { + suite.updateKluctlDeployment(key, func(kd *kluctlv1.KluctlDeployment) { kd.Spec.ForceApply = true }) - t.Run("forceApply is true and cm1 gets restored even with another field manager", func(t *testing.T) { + suite.Run("forceApply is true and cm1 gets restored even with another field manager", func() { patch := client.MergeFrom(cm.DeepCopy()) cm.Data["k1"] = "v2" - err := k.Client.Patch(context.TODO(), cm, patch, client.FieldOwner("test-field-manager")) + err := suite.k.Client.Patch(context.TODO(), cm, patch, client.FieldOwner("test-field-manager")) g.Expect(err).To(Succeed()) g.Eventually(func() bool { - err := k.Client.Get(context.TODO(), client.ObjectKey{ + err := suite.k.Client.Get(context.TODO(), client.ObjectKey{ Name: "cm1", Namespace: p.TestSlug(), }, cm) @@ -263,24 +340,21 @@ data: }) } -func TestKluctlDeploymentReconciler_Helm(t *testing.T) { - g := NewWithT(t) - startKluctlController(t) +func (suite *GitopsTestSuite) TestKluctlDeploymentReconciler_Helm() { + g := NewWithT(suite.T()) - k := defaultCluster1 - - p := test_utils.NewTestProject(t) - createNamespace(t, k, p.TestSlug()) + p := test_utils.NewTestProject(suite.T()) + createNamespace(suite.T(), suite.k, p.TestSlug()) p.UpdateTarget("target1", nil) - repoUrl := test_utils.CreateHelmRepo(t, []test_utils.RepoChart{ + repoUrl := test_utils.CreateHelmRepo(suite.T(), []test_utils.RepoChart{ {ChartName: "test-chart1", Version: "0.1.0"}, }, "", "") - repoUrlWithCreds := test_utils.CreateHelmRepo(t, []test_utils.RepoChart{ + repoUrlWithCreds := test_utils.CreateHelmRepo(suite.T(), []test_utils.RepoChart{ {ChartName: "test-chart2", Version: "0.1.0"}, }, "test-user", "test-password") - ociRepoUrlWithCreds := test_utils.CreateOciRepo(t, []test_utils.RepoChart{ + ociRepoUrlWithCreds := test_utils.CreateOciRepo(suite.T(), []test_utils.RepoChart{ {ChartName: "test-chart3", Version: "0.1.0"}, }, "test-user", "test-password") @@ -290,16 +364,16 @@ func TestKluctlDeploymentReconciler_Helm(t *testing.T) { return nil }, "") - key := createKluctlDeployment(t, p, k, "target1", map[string]any{ + key := suite.createKluctlDeployment(p, "target1", map[string]any{ "namespace": p.TestSlug(), }) - waitForCommit(t, k, key, getHeadRevision(t, p)) + suite.waitForCommit(key, getHeadRevision(suite.T(), p)) cm := &corev1.ConfigMap{} - t.Run("chart got deployed", func(t *testing.T) { - err := k.Client.Get(context.TODO(), client.ObjectKey{ + suite.Run("chart got deployed", func() { + err := suite.k.Client.Get(context.TODO(), client.ObjectKey{ Name: "test-helm-1-test-chart1", Namespace: p.TestSlug(), }, cm) @@ -315,9 +389,9 @@ func TestKluctlDeploymentReconciler_Helm(t *testing.T) { kd := &kluctlv1.KluctlDeployment{} - t.Run("chart with credentials fails with 401", func(t *testing.T) { + suite.Run("chart with credentials fails with 401", func() { g.Eventually(func() bool { - err := k.Client.Get(context.TODO(), key, kd) + err := suite.k.Client.Get(context.TODO(), key, kd) g.Expect(err).To(Succeed()) for _, c := range kd.Status.Conditions { _ = c @@ -340,16 +414,16 @@ func TestKluctlDeploymentReconciler_Helm(t *testing.T) { "password": []byte("test-password"), }, } - err := k.Client.Create(context.TODO(), credsSecret) + err := suite.k.Client.Create(context.TODO(), credsSecret) g.Expect(err).To(Succeed()) - updateKluctlDeployment(t, k, key, func(kd *kluctlv1.KluctlDeployment) { + suite.updateKluctlDeployment(key, func(kd *kluctlv1.KluctlDeployment) { kd.Spec.HelmCredentials = append(kd.Spec.HelmCredentials, kluctlv1.HelmCredentials{SecretRef: kluctlv1.LocalObjectReference{Name: "helm-secrets-1"}}) }) - t.Run("chart with credentials succeeds", func(t *testing.T) { + suite.Run("chart with credentials succeeds", func() { g.Eventually(func() bool { - err := k.Client.Get(context.TODO(), client.ObjectKey{ + err := suite.k.Client.Get(context.TODO(), client.ObjectKey{ Name: "test-helm-2-test-chart2", Namespace: p.TestSlug(), }, cm) @@ -367,9 +441,9 @@ func TestKluctlDeploymentReconciler_Helm(t *testing.T) { return nil }, "") - t.Run("OCI chart with credentials fails with 401", func(t *testing.T) { + suite.Run("OCI chart with credentials fails with 401", func() { g.Eventually(func() bool { - err = k.Client.Get(context.TODO(), key, kd) + err = suite.k.Client.Get(context.TODO(), key, kd) g.Expect(err).To(Succeed()) for _, c := range kd.Status.Conditions { _ = c @@ -430,14 +504,11 @@ func TestKluctlDeploymentReconciler_Helm(t *testing.T) { })*/ } -func TestKluctlDeploymentReconciler_Prune(t *testing.T) { - g := NewWithT(t) - startKluctlController(t) - - k := defaultCluster1 +func (suite *GitopsTestSuite) TestKluctlDeploymentReconciler_Prune() { + g := NewWithT(suite.T()) - p := test_utils.NewTestProject(t) - createNamespace(t, k, p.TestSlug()) + p := test_utils.NewTestProject(suite.T()) + createNamespace(suite.T(), suite.k, p.TestSlug()) p.UpdateTarget("target1", nil) @@ -462,21 +533,21 @@ data: `)}, }, nil) - key := createKluctlDeployment(t, p, k, "target1", map[string]any{ + key := suite.createKluctlDeployment(p, "target1", map[string]any{ "namespace": p.TestSlug(), }) - waitForCommit(t, k, key, getHeadRevision(t, p)) + suite.waitForCommit(key, getHeadRevision(suite.T(), p)) cm := &corev1.ConfigMap{} - t.Run("cm1 and cm2 got deployed", func(t *testing.T) { - err := k.Client.Get(context.TODO(), client.ObjectKey{ + suite.Run("cm1 and cm2 got deployed", func() { + err := suite.k.Client.Get(context.TODO(), client.ObjectKey{ Name: "cm1", Namespace: p.TestSlug(), }, cm) g.Expect(err).To(Succeed()) - err = k.Client.Get(context.TODO(), client.ObjectKey{ + err = suite.k.Client.Get(context.TODO(), client.ObjectKey{ Name: "cm2", Namespace: p.TestSlug(), }, cm) @@ -490,39 +561,39 @@ data: g.Eventually(func() bool { var obj kluctlv1.KluctlDeployment - _ = k.Client.Get(context.Background(), key, &obj) + _ = suite.k.Client.Get(context.Background(), key, &obj) if obj.Status.LastDeployResult == nil { return false } - return obj.Status.LastDeployResult.GitInfo.Commit == getHeadRevision(t, p) + return obj.Status.LastDeployResult.GitInfo.Commit == getHeadRevision(suite.T(), p) }, timeout, time.Second).Should(BeTrue()) - t.Run("cm1 and cm2 were not deleted", func(t *testing.T) { - err := k.Client.Get(context.TODO(), client.ObjectKey{ + suite.Run("cm1 and cm2 were not deleted", func() { + err := suite.k.Client.Get(context.TODO(), client.ObjectKey{ Name: "cm1", Namespace: p.TestSlug(), }, cm) g.Expect(err).To(Succeed()) - err = k.Client.Get(context.TODO(), client.ObjectKey{ + err = suite.k.Client.Get(context.TODO(), client.ObjectKey{ Name: "cm2", Namespace: p.TestSlug(), }, cm) g.Expect(err).To(Succeed()) }) - updateKluctlDeployment(t, k, key, func(kd *kluctlv1.KluctlDeployment) { + suite.updateKluctlDeployment(key, func(kd *kluctlv1.KluctlDeployment) { kd.Spec.Prune = true }) - waitForReconcile(t, k, key) + suite.waitForReconcile(key) - t.Run("cm1 did not get deleted and cm2 got deleted", func(t *testing.T) { - err := k.Client.Get(context.TODO(), client.ObjectKey{ + suite.Run("cm1 did not get deleted and cm2 got deleted", func() { + err := suite.k.Client.Get(context.TODO(), client.ObjectKey{ Name: "cm1", Namespace: p.TestSlug(), }, cm) g.Expect(err).To(Succeed()) - err = k.Client.Get(context.TODO(), client.ObjectKey{ + err = suite.k.Client.Get(context.TODO(), client.ObjectKey{ Name: "cm2", Namespace: p.TestSlug(), }, cm) @@ -530,14 +601,11 @@ data: }) } -func doTestDelete(t *testing.T, delete bool) { - g := NewWithT(t) - startKluctlController(t) - - k := defaultCluster1 +func (suite *GitopsTestSuite) doTestDelete(delete bool) { + g := NewWithT(suite.T()) - p := test_utils.NewTestProject(t) - createNamespace(t, k, p.TestSlug()) + p := test_utils.NewTestProject(suite.T()) + createNamespace(suite.T(), suite.k, p.TestSlug()) p.UpdateTarget("target1", nil) @@ -552,31 +620,31 @@ data: `)}, }, nil) - key := createKluctlDeployment(t, p, k, "target1", map[string]any{ + key := suite.createKluctlDeployment(p, "target1", map[string]any{ "namespace": p.TestSlug(), }) - kd := updateKluctlDeployment(t, k, key, func(kd *kluctlv1.KluctlDeployment) { + suite.updateKluctlDeployment(key, func(kd *kluctlv1.KluctlDeployment) { kd.Spec.Delete = delete }) - waitForCommit(t, k, key, getHeadRevision(t, p)) + suite.waitForCommit(key, getHeadRevision(suite.T(), p)) cm := &corev1.ConfigMap{} - t.Run("cm1 got deployed", func(t *testing.T) { - err := k.Client.Get(context.TODO(), client.ObjectKey{ + suite.Run("cm1 got deployed", func() { + err := suite.k.Client.Get(context.TODO(), client.ObjectKey{ Name: "cm1", Namespace: p.TestSlug(), }, cm) g.Expect(err).To(Succeed()) }) - g.Expect(k.Client.Delete(context.TODO(), kd)).To(Succeed()) + suite.deleteKluctlDeployment(key) g.Eventually(func() bool { var obj kluctlv1.KluctlDeployment - err := k.Client.Get(context.Background(), key, &obj) + err := suite.k.Client.Get(context.Background(), key, &obj) if err == nil { return false } @@ -587,16 +655,16 @@ data: }, timeout, time.Second).Should(BeTrue()) if delete { - t.Run("cm1 was deleted", func(t *testing.T) { - err := k.Client.Get(context.TODO(), client.ObjectKey{ + suite.Run("cm1 was deleted", func() { + err := suite.k.Client.Get(context.TODO(), client.ObjectKey{ Name: "cm1", Namespace: p.TestSlug(), }, cm) g.Expect(err).To(MatchError("configmaps \"cm1\" not found")) }) } else { - t.Run("cm1 was not deleted", func(t *testing.T) { - err := k.Client.Get(context.TODO(), client.ObjectKey{ + suite.Run("cm1 was not deleted", func() { + err := suite.k.Client.Get(context.TODO(), client.ObjectKey{ Name: "cm1", Namespace: p.TestSlug(), }, cm) @@ -605,10 +673,15 @@ data: } } -func TestKluctlDeploymentReconciler_Delete_True(t *testing.T) { - doTestDelete(t, true) +func (suite *GitopsTestSuite) Test_Delete_True() { + suite.doTestDelete(true) +} + +func (suite *GitopsTestSuite) Test_Delete_False() { + suite.doTestDelete(false) } -func TestKluctlDeploymentReconciler_Delete_False(t *testing.T) { - doTestDelete(t, false) +func TestGitOps(t *testing.T) { + t.Parallel() + suite.Run(t, new(GitopsTestSuite)) } From 8047bed47e72d36291ee86ddbc9e4a2153dacfac Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 19 May 2023 15:34:17 +0200 Subject: [PATCH 1664/2916] chore: Run make replace-commands-help --- docs/reference/commands/controller.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/reference/commands/controller.md b/docs/reference/commands/controller.md index a7584c0b7..588b04f6e 100644 --- a/docs/reference/commands/controller.md +++ b/docs/reference/commands/controller.md @@ -26,9 +26,11 @@ The following arguments are available: Controller: Controller arguments. + --context string Override the context to use. --default-service-account string Default service account used for impersonation. --dry-run Run all deployments in dryRun=true mode. --health-probe-bind-address string The address the probe endpoint binds to. (default ":8081") + --kubeconfig string Override the kubeconfig to use. --leader-elect Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager. --metrics-bind-address string The address the metric endpoint binds to. (default ":8080") From 8faca6ef582d41045b3e0ca92bf7852f94a5805e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 May 2023 16:13:18 +0200 Subject: [PATCH 1665/2916] chore(deps): Bump github.com/mattn/go-isatty from 0.0.18 to 0.0.19 (#493) Bumps [github.com/mattn/go-isatty](https://github.com/mattn/go-isatty) from 0.0.18 to 0.0.19. - [Commits](https://github.com/mattn/go-isatty/compare/v0.0.18...v0.0.19) --- updated-dependencies: - dependency-name: github.com/mattn/go-isatty dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index fb641e2f3..0c06edca2 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/kluctl/go-embed-python v0.0.0-3.10.9-20230206-2 github.com/kluctl/go-jinja2 v0.0.0-20230428103343-a832225dc94c github.com/mattn/go-colorable v0.1.13 - github.com/mattn/go-isatty v0.0.18 + github.com/mattn/go-isatty v0.0.19 github.com/mattn/go-runewidth v0.0.14 github.com/mitchellh/reflectwalk v1.0.2 github.com/ohler55/ojg v1.18.5 diff --git a/go.sum b/go.sum index e6e48f5e8..1d9641d4f 100644 --- a/go.sum +++ b/go.sum @@ -591,8 +591,8 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= -github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= From b14d199eb218ba1ff70533d9138238dcd797599d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 22 May 2023 17:02:42 +0200 Subject: [PATCH 1666/2916] ci: Only run manifests/generate targets on test target (#494) --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index a2bbf37e2..adc7a5a7c 100644 --- a/Makefile +++ b/Makefile @@ -70,14 +70,14 @@ vet: ## Run go vet against code. go vet ./... .PHONY: test -test: test-unit test-e2e fmt vet ## Run all tests. +test: manifests generate test-unit test-e2e fmt vet ## Run all tests. .PHONY: test-unit test-unit: ## Run unit tests. go test $(RACE) $(shell go list ./... | grep -v v2/e2e) -coverprofile cover.out .PHONY: test-e2e -test-e2e: manifests generate envtest ## Run e2e tests. +test-e2e: envtest ## Run e2e tests. KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test $(RACE) ./e2e -coverprofile cover.out replace-commands-help: ## Replace commands help in docs From f35f839e22d781c7b6cbb8f67229e035e38441b5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 May 2023 07:35:17 +0200 Subject: [PATCH 1667/2916] chore(deps): Bump github.com/hashicorp/go-retryablehttp (#496) Bumps [github.com/hashicorp/go-retryablehttp](https://github.com/hashicorp/go-retryablehttp) from 0.7.1 to 0.7.2. - [Commits](https://github.com/hashicorp/go-retryablehttp/compare/v0.7.1...v0.7.2) --- updated-dependencies: - dependency-name: github.com/hashicorp/go-retryablehttp dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0c06edca2..0cd0314ef 100644 --- a/go.mod +++ b/go.mod @@ -65,7 +65,7 @@ require ( github.com/go-logr/logr v1.2.4 github.com/google/uuid v1.3.0 github.com/hashicorp/go-multierror v1.1.1 - github.com/hashicorp/go-retryablehttp v0.7.1 + github.com/hashicorp/go-retryablehttp v0.7.2 github.com/huandu/xstrings v1.4.0 github.com/onsi/gomega v1.27.7 github.com/otiai10/copy v1.11.0 diff --git a/go.sum b/go.sum index 1d9641d4f..4bcbe7a52 100644 --- a/go.sum +++ b/go.sum @@ -458,8 +458,8 @@ github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iP github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ= -github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUDDYFRKq/RAd0= +github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= From 832624cd973a65fe8717a9aec6b42d31380a24b5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 May 2023 07:35:56 +0200 Subject: [PATCH 1668/2916] chore(deps): Bump github.com/Azure/azure-sdk-for-go/sdk/azidentity (#497) Bumps [github.com/Azure/azure-sdk-for-go/sdk/azidentity](https://github.com/Azure/azure-sdk-for-go) from 1.1.0 to 1.3.0. - [Release notes](https://github.com/Azure/azure-sdk-for-go/releases) - [Changelog](https://github.com/Azure/azure-sdk-for-go/blob/main/documentation/release.md) - [Commits](https://github.com/Azure/azure-sdk-for-go/compare/v1.1...sdk/azcore/v1.3.0) --- updated-dependencies: - dependency-name: github.com/Azure/azure-sdk-for-go/sdk/azidentity dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 9 ++++----- go.sum | 23 +++++++++-------------- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/go.mod b/go.mod index 0cd0314ef..6864047a7 100644 --- a/go.mod +++ b/go.mod @@ -52,8 +52,8 @@ require ( require ( filippo.io/age v1.1.1 - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.1 - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 github.com/aws/aws-sdk-go-v2 v1.18.0 github.com/aws/aws-sdk-go-v2/config v1.18.25 github.com/aws/aws-sdk-go-v2/credentials v1.13.24 @@ -84,11 +84,11 @@ require ( cloud.google.com/go/iam v0.13.0 // indirect cloud.google.com/go/kms v1.10.0 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 // indirect - github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.5.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.5.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v0.5.2 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect github.com/BurntSushi/toml v1.2.1 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect @@ -141,7 +141,6 @@ require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/btree v1.1.2 // indirect diff --git a/go.sum b/go.sum index 4bcbe7a52..48f663318 100644 --- a/go.sum +++ b/go.sum @@ -54,12 +54,12 @@ filippo.io/age v1.1.1 h1:pIpO7l151hCnQ4BdyBujnGP2YlUo0uj6sAVNHGBvXHg= filippo.io/age v1.1.1/go.mod h1:l03SrzDUrBkdBx8+IILdnn2KZysqQdbEBUQ4p3sqEQE= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 h1:EKPd1INOIyr5hWOWhvpmQpY6tKjeG0hT1s3AMC/9fic= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1/go.mod h1:VzwV+t+dZ9j/H867F1M2ziD+yLHtB46oM35FxxMJ4d0= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.1 h1:tz19qLF65vuu2ibfTqGVJxG/zZAI27NEIIbvAOQwYbw= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.1/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0 h1:QkAcEIAKbNL4KoFr4SathZPhDhF4mVwpBMFlYjyAqy8= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0/go.mod h1:bhXu1AjYL+wutSL/kpSq6s7733q2Rb0yuot9Zgfqa/0= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 h1:jp0dGvZ7ZK0mgqnTSClMxa5xuRL7NZgHameVYF6BurY= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0 h1:8kDqDngH+DmVBiCtIjCFTGa7MBnsIOkF9IccInFEbjk= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 h1:vcYCAze6p19qBW7MhZybIsqD8sMV8js0NyQM8JDnVtg= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9OrhHJoDD8ZDq51FHgXjqtP9z6bEwBq9U= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM= github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.5.1 h1:AXFNQ6kLaPODEpGSMWjmbkt6iP7fa1DIEzjx6JRFC9U= github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.5.1/go.mod h1:yOYJv0tO0TTNcje8ahhBHQcdAiYqRIp5fsog5FPefr4= github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.5.0 h1:9cn6ICCGiWFNA/slKnrkf+ENyvaCRKHtuoGtnLIAgao= @@ -68,8 +68,8 @@ github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOEl github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= -github.com/AzureAD/microsoft-authentication-library-for-go v0.5.2 h1:BGX4OiGP9htYSd6M3pAZctcUUSruhIAUVkv2X0Cn9yE= -github.com/AzureAD/microsoft-authentication-library-for-go v0.5.2/go.mod h1:Vt9sXTKwMyGcOxSmLDMnGPgqsUg7m8pe215qMLrDXw4= +github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 h1:OBhqkivkhkMqLPymWEppkm7vgPQY2XsHoEkaMQ0AdZY= +github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= @@ -215,7 +215,7 @@ github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8 github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 h1:aBfCb7iqHmDEIp6fBvC/hQUddQfg+3qdYjwzaiP9Hnc= -github.com/dnaeon/go-vcr v1.1.0 h1:ReYa/UBrRyQdant9B4fNHGoCNKw6qh6P0fsdGmZpR7c= +github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= github.com/docker/cli v23.0.5+incompatible h1:ufWmAOuD3Vmr7JP2G5K3cyuNC4YZWiAsuDEvFVVDafE= github.com/docker/cli v23.0.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= @@ -329,9 +329,6 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= -github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= -github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= @@ -644,7 +641,6 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= -github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= @@ -679,7 +675,6 @@ github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= -github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= From 1fb3178e51752d85635684798264051a63918f94 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 May 2023 07:36:17 +0200 Subject: [PATCH 1669/2916] chore(deps): Bump github.com/go-playground/validator/v10 (#492) Bumps [github.com/go-playground/validator/v10](https://github.com/go-playground/validator) from 10.13.0 to 10.14.0. - [Release notes](https://github.com/go-playground/validator/releases) - [Commits](https://github.com/go-playground/validator/compare/v10.13.0...v10.14.0) --- updated-dependencies: - dependency-name: github.com/go-playground/validator/v10 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 5 +++-- go.sum | 10 ++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 6864047a7..825eafab4 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/bitnami-labs/sealed-secrets v0.21.0 github.com/cyphar/filepath-securejoin v0.2.3 github.com/docker/distribution v2.8.2+incompatible - github.com/go-playground/validator/v10 v10.13.0 + github.com/go-playground/validator/v10 v10.14.0 github.com/gobwas/glob v0.2.3 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 github.com/google/go-containerregistry v0.15.2 @@ -129,6 +129,7 @@ require ( github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect github.com/fatih/color v1.13.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-git/gcfg v1.5.0 // indirect github.com/go-git/go-billy/v5 v5.4.1 // indirect @@ -171,7 +172,7 @@ require ( github.com/kylelemons/godebug v1.1.0 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect - github.com/leodido/go-urn v1.2.3 // indirect + github.com/leodido/go-urn v1.2.4 // indirect github.com/lib/pq v1.10.7 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/magiconair/properties v1.8.7 // indirect diff --git a/go.sum b/go.sum index 48f663318..b868dcef2 100644 --- a/go.sum +++ b/go.sum @@ -265,6 +265,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= +github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= @@ -308,8 +310,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.13.0 h1:cFRQdfaSMCOSfGCCLB20MHvuoHb/s5G8L5pu2ppK5AQ= -github.com/go-playground/validator/v10 v10.13.0/go.mod h1:dwu7+CG8/CtBiJFZDz4e+5Upb6OLw04gtBYw0mcG/z4= +github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= +github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -554,8 +556,8 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= -github.com/leodido/go-urn v1.2.3 h1:6BE2vPT0lqoz3fmOesHZiaiFh7889ssCo2GMvLCfiuA= -github.com/leodido/go-urn v1.2.3/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= From e3ae4f140e14bba9305a5762a5ff3d4b6f6a5d7f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 23 May 2023 08:20:15 +0200 Subject: [PATCH 1670/2916] ci: Fix windows tests (#495) * ci: Fix windows path being used in Makefile * ci: Reset caches --- .github/workflows/tests.yml | 4 ++-- Makefile | 9 +++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 851719d81..8559911f4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -73,9 +73,9 @@ jobs: path: | ~/go/pkg/mod ~/.cache/go-build - key: tests1-go-${{ runner.os }}-${{ hashFiles('**/go.sum') }} + key: tests-go-${{ runner.os }}-${{ hashFiles('**/go.sum') }} restore-keys: | - tests1-go-${{ runner.os }}- + tests-go-${{ runner.os }}- - name: Run unit tests shell: bash run: | diff --git a/Makefile b/Makefile index adc7a5a7c..46c182d03 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,11 @@ ifeq ($(GOOS), linux) RACE=-race endif +PATHCONF=cat +ifeq ($(GOOS), windows) +PATHCONF=cygpath -f- -u +endif + # Image URL to use all building/pushing image targets IMG ?= kluctl/kluctl:latest # ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. @@ -74,11 +79,11 @@ test: manifests generate test-unit test-e2e fmt vet ## Run all tests. .PHONY: test-unit test-unit: ## Run unit tests. - go test $(RACE) $(shell go list ./... | grep -v v2/e2e) -coverprofile cover.out + go test $(RACE) $(shell go list ./... | grep -v v2/e2e) -coverprofile cover.out -test.v .PHONY: test-e2e test-e2e: envtest ## Run e2e tests. - KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test $(RACE) ./e2e -coverprofile cover.out + KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir=$(LOCALBIN) -p path | $(PATHCONF))" go test $(RACE) ./e2e -coverprofile cover.out -test.v replace-commands-help: ## Replace commands help in docs go run ./internal/replace-commands-help --docs-dir ./docs/reference/commands From 53df330aba232e23910d8feca77d226070636bc7 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 23 May 2023 09:23:09 +0200 Subject: [PATCH 1671/2916] fix: Make metrics package non-internal and don't register metrics on init (#499) * chore: Move metrics out of internal package It needs to be used by the legacy flux-kluctl-controller * fix: Don't register metrics collectors in global init() --- cmd/kluctl/commands/cmd_controller.go | 10 ++++------ pkg/controllers/kluctl_project.go | 2 +- pkg/controllers/kluctldeployment_controller.go | 2 +- .../{internal => }/metrics/kluctl_project.go | 0 .../metrics/kluctldeployment_controller.go | 0 5 files changed, 6 insertions(+), 8 deletions(-) rename pkg/controllers/{internal => }/metrics/kluctl_project.go (100%) rename pkg/controllers/{internal => }/metrics/kluctldeployment_controller.go (100%) diff --git a/cmd/kluctl/commands/cmd_controller.go b/cmd/kluctl/commands/cmd_controller.go index 132e7a55b..e96e67773 100644 --- a/cmd/kluctl/commands/cmd_controller.go +++ b/cmd/kluctl/commands/cmd_controller.go @@ -26,14 +26,9 @@ import ( ) var ( - setupLog = ctrl.Log.WithName("setup") - metricsRecorder = metrics.NewRecorder() + setupLog = ctrl.Log.WithName("setup") ) -func init() { - crtlmetrics.Registry.MustRegister(metricsRecorder.Collectors()...) -} - const controllerName = "kluctl-controller" type controllerCmd struct { @@ -69,6 +64,9 @@ func (cmd *controllerCmd) initScheme() { func (cmd *controllerCmd) Run(ctx context.Context) error { cmd.initScheme() + metricsRecorder := metrics.NewRecorder() + crtlmetrics.Registry.MustRegister(metricsRecorder.Collectors()...) + opts := zap.Options{ Development: true, } diff --git a/pkg/controllers/kluctl_project.go b/pkg/controllers/kluctl_project.go index d0c306032..d6ca5372e 100644 --- a/pkg/controllers/kluctl_project.go +++ b/pkg/controllers/kluctl_project.go @@ -4,8 +4,8 @@ import ( "context" "fmt" "github.com/kluctl/go-jinja2" - internal_metrics "github.com/kluctl/kluctl/v2/pkg/controllers/internal/metrics" "github.com/kluctl/kluctl/v2/pkg/controllers/internal/sops" + internal_metrics "github.com/kluctl/kluctl/v2/pkg/controllers/metrics" "github.com/kluctl/kluctl/v2/pkg/helm" "github.com/kluctl/kluctl/v2/pkg/repocache" "github.com/kluctl/kluctl/v2/pkg/sops/decryptor" diff --git a/pkg/controllers/kluctldeployment_controller.go b/pkg/controllers/kluctldeployment_controller.go index 5e9653f2d..86dd5e118 100644 --- a/pkg/controllers/kluctldeployment_controller.go +++ b/pkg/controllers/kluctldeployment_controller.go @@ -5,7 +5,7 @@ import ( "fmt" "github.com/hashicorp/go-retryablehttp" kluctlv1 "github.com/kluctl/kluctl/v2/api/v1beta1" - internal_metrics "github.com/kluctl/kluctl/v2/pkg/controllers/internal/metrics" + internal_metrics "github.com/kluctl/kluctl/v2/pkg/controllers/metrics" ssh_pool "github.com/kluctl/kluctl/v2/pkg/git/ssh-pool" "github.com/kluctl/kluctl/v2/pkg/kluctl_jinja2" "github.com/kluctl/kluctl/v2/pkg/results" diff --git a/pkg/controllers/internal/metrics/kluctl_project.go b/pkg/controllers/metrics/kluctl_project.go similarity index 100% rename from pkg/controllers/internal/metrics/kluctl_project.go rename to pkg/controllers/metrics/kluctl_project.go diff --git a/pkg/controllers/internal/metrics/kluctldeployment_controller.go b/pkg/controllers/metrics/kluctldeployment_controller.go similarity index 100% rename from pkg/controllers/internal/metrics/kluctldeployment_controller.go rename to pkg/controllers/metrics/kluctldeployment_controller.go From 83639459e7bc8123e39309d8a8c08ddc0149122b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 23 May 2023 10:29:51 +0200 Subject: [PATCH 1672/2916] feat: Disable writing of command results by default --- cmd/kluctl/args/project.go | 2 +- cmd/kluctl/commands/utils.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/kluctl/args/project.go b/cmd/kluctl/args/project.go index 893c5062e..21468a46b 100644 --- a/cmd/kluctl/args/project.go +++ b/cmd/kluctl/args/project.go @@ -43,7 +43,7 @@ type TargetFlags struct { } type CommandResultFlags struct { - NoWriteCommandResult bool `group:"results" help:"Disable writing of command results into the cluster."` + WriteCommandResult bool `group:"results" help:"Enable writing of command results into the cluster."` ForceWriteCommandResult bool `group:"results" help:"Force writing of command results, even if the command is run in dry-run mode."` CommandResultNamespace string `group:"results" help:"Override the namespace to be used when writing command results." default:"kluctl-results"` KeepCommandResultsCount int `group:"results" help:"Configure how many old command results to keep." default:"10"` diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 0bcb6bcbf..630199ca5 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -200,7 +200,7 @@ func withProjectTargetCommandContext(ctx context.Context, args projectTargetComm } var resultStore results.ResultStore - if args.commandResultFlags != nil && !args.commandResultFlags.NoWriteCommandResult { + if args.commandResultFlags != nil && args.commandResultFlags.WriteCommandResult { rc, err := targetCtx.SharedContext.K.ToRESTConfig() if err != nil { return err From 0a694c74758d13585b13959aec8c4ab75d9dc0c2 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 23 May 2023 10:34:21 +0200 Subject: [PATCH 1673/2916] feat: Make writing of results configurable in the controller --- cmd/kluctl/commands/cmd_controller.go | 12 ++++++++++++ pkg/controllers/kluctldeployment_controller_setup.go | 7 ------- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/cmd/kluctl/commands/cmd_controller.go b/cmd/kluctl/commands/cmd_controller.go index e96e67773..c0baaa66a 100644 --- a/cmd/kluctl/commands/cmd_controller.go +++ b/cmd/kluctl/commands/cmd_controller.go @@ -4,8 +4,10 @@ import ( "context" "fmt" kluctlv1 "github.com/kluctl/kluctl/v2/api/v1beta1" + "github.com/kluctl/kluctl/v2/cmd/kluctl/args" "github.com/kluctl/kluctl/v2/pkg/controllers" ssh_pool "github.com/kluctl/kluctl/v2/pkg/git/ssh-pool" + "github.com/kluctl/kluctl/v2/pkg/results" "github.com/kluctl/kluctl/v2/pkg/utils/flux_utils/metrics" log "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/runtime" @@ -43,6 +45,8 @@ type controllerCmd struct { DefaultServiceAccount string `group:"controller" help:"Default service account used for impersonation."` DryRun bool `group:"controller" help:"Run all deployments in dryRun=true mode."` + + args.CommandResultFlags } func (cmd *controllerCmd) Help() string { @@ -133,6 +137,14 @@ func (cmd *controllerCmd) Run(ctx context.Context) error { SshPool: sshPool, } + if cmd.WriteCommandResult { + resultStore, err := results.NewResultStoreSecrets(ctx, mgr.GetClient(), mgr.GetCache(), cmd.CommandResultNamespace, cmd.KeepCommandResultsCount) + if err != nil { + return err + } + r.ResultStore = resultStore + } + if err = r.SetupWithManager(ctx, mgr, controllers.KluctlDeploymentReconcilerOpts{ HTTPRetry: 9, }); err != nil { diff --git a/pkg/controllers/kluctldeployment_controller_setup.go b/pkg/controllers/kluctldeployment_controller_setup.go index 0f8fe2e73..c0ca400d8 100644 --- a/pkg/controllers/kluctldeployment_controller_setup.go +++ b/pkg/controllers/kluctldeployment_controller_setup.go @@ -4,7 +4,6 @@ import ( "context" "github.com/hashicorp/go-retryablehttp" kluctlv1 "github.com/kluctl/kluctl/v2/api/v1beta1" - "github.com/kluctl/kluctl/v2/pkg/results" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/predicate" @@ -22,12 +21,6 @@ func (r *KluctlDeploymentReconciler) SetupWithManager(ctx context.Context, mgr c httpClient.Logger = nil r.httpClient = httpClient - resultStore, err := results.NewResultStoreSecrets(ctx, mgr.GetClient(), mgr.GetCache(), "kluctl-results", 10) - if err != nil { - return err - } - r.ResultStore = resultStore - return ctrl.NewControllerManagedBy(mgr). For(&kluctlv1.KluctlDeployment{}, builder.WithPredicates( predicate.Or(predicate.GenerationChangedPredicate{}, ReconcileRequestedPredicate{}, DeployRequestedPredicate{}), From fd1b7ee1b1b034b546aea81f51879e99ceaa2dea Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 23 May 2023 13:39:36 +0200 Subject: [PATCH 1674/2916] fix: Don't use ctrl.SetupSignalHandler() This caused ctrl+c not reacting as expected, as one needed 2 strokes to make it work. --- cmd/kluctl/commands/root.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index 51b37957c..74559d894 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -25,7 +25,6 @@ import ( "os" "path/filepath" "runtime/pprof" - ctrl "sigs.k8s.io/controller-runtime" "strings" "time" @@ -111,6 +110,7 @@ func redirectLogsAndStderr(ctxGetter func() context.Context) { klog.LogToStderr(false) klog.SetOutput(lr1) log.SetOutput(lr2) + //ctrl.SetLogger(klog.NewKlogr()) pr, pw, err := os.Pipe() if err != nil { @@ -222,7 +222,7 @@ func initViper(ctx context.Context) { func Main() { colorable.EnableColorsStdout(nil) - ctx := ctrl.SetupSignalHandler() + ctx := context.Background() ctx = initStatusHandler(ctx, false, true) redirectLogsAndStderr(func() context.Context { From 5fc26667b33744ec6a58208f5665b1501572a080 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 23 May 2023 13:39:49 +0200 Subject: [PATCH 1675/2916] tests: Add command results tests --- e2e/gitops_test.go | 1 + e2e/results_test.go | 100 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 e2e/results_test.go diff --git a/e2e/gitops_test.go b/e2e/gitops_test.go index 191a65c5b..1df30c7fa 100644 --- a/e2e/gitops_test.go +++ b/e2e/gitops_test.go @@ -100,6 +100,7 @@ func (suite *GitopsTestSuite) startController() { tmpKubeconfig, "--context", "context1", + "--write-command-result", } done := make(chan struct{}) go func() { diff --git a/e2e/results_test.go b/e2e/results_test.go new file mode 100644 index 000000000..b2ceea17d --- /dev/null +++ b/e2e/results_test.go @@ -0,0 +1,100 @@ +package e2e + +import ( + "context" + test_utils "github.com/kluctl/kluctl/v2/e2e/test-utils" + "github.com/kluctl/kluctl/v2/pkg/results" + "github.com/kluctl/kluctl/v2/pkg/types/result" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/stretchr/testify/assert" + "testing" +) + +func assertSummary(t *testing.T, expected result.CommandResultSummary, actual result.CommandResultSummary) { + assert.Equal(t, expected.AppliedObjects, actual.AppliedObjects) + assert.Equal(t, expected.NewObjects, actual.NewObjects) + assert.Equal(t, expected.ChangedObjects, actual.ChangedObjects) + assert.Equal(t, expected.OrphanObjects, actual.OrphanObjects) + assert.Equal(t, expected.DeletedObjects, actual.DeletedObjects) + assert.Equal(t, expected.Errors, actual.Errors) + assert.Equal(t, expected.Warnings, actual.Warnings) + assert.Equal(t, expected.TotalChanges, actual.TotalChanges) +} + +func TestWriteResult(t *testing.T) { + t.Parallel() + + k := defaultCluster1 + + p := test_utils.NewTestProject(t) + + createNamespace(t, k, p.TestSlug()) + + p.UpdateTarget("test", nil) + + addConfigMapDeployment(p, "cm", map[string]string{ + "d1": "v1", + }, resourceOpts{ + name: "cm", + namespace: p.TestSlug(), + }) + p.KluctlMust("deploy", "--yes", "-t", "test", "--write-command-result") + assertConfigMapExists(t, k, p.TestSlug(), "cm") + + rs, err := results.NewResultStoreSecrets(context.Background(), k.Client, nil, "kluctl-results", 0) + assert.NoError(t, err) + + summaries, err := rs.ListCommandResultSummaries(results.ListCommandResultSummariesOptions{}) + assert.NoError(t, err) + assert.Len(t, summaries, 1) + assertSummary(t, result.CommandResultSummary{ + AppliedObjects: 1, + NewObjects: 1, + }, summaries[0]) + + addConfigMapDeployment(p, "cm2", nil, resourceOpts{ + name: "cm2", + namespace: p.TestSlug(), + }) + p.UpdateYaml("cm/configmap-cm.yml", func(o *uo.UnstructuredObject) error { + _ = o.SetNestedField("v2", "data", "d1") + return nil + }, "") + p.KluctlMust("deploy", "--yes", "-t", "test", "--write-command-result") + assertConfigMapExists(t, k, p.TestSlug(), "cm2") + + summaries, err = rs.ListCommandResultSummaries(results.ListCommandResultSummariesOptions{}) + assert.NoError(t, err) + assert.Len(t, summaries, 2) + assertSummary(t, result.CommandResultSummary{ + AppliedObjects: 2, + NewObjects: 1, + ChangedObjects: 1, + TotalChanges: 1, + }, summaries[0]) + + p.UpdateDeploymentYaml("", func(o *uo.UnstructuredObject) error { + _ = o.RemoveNestedField("deployments", 1) + return nil + }) + p.KluctlMust("deploy", "--yes", "-t", "test", "--write-command-result") + assertConfigMapExists(t, k, p.TestSlug(), "cm2") + + summaries, err = rs.ListCommandResultSummaries(results.ListCommandResultSummariesOptions{}) + assert.NoError(t, err) + assert.Len(t, summaries, 3) + assertSummary(t, result.CommandResultSummary{ + AppliedObjects: 1, + OrphanObjects: 1, + }, summaries[0]) + + p.KluctlMust("prune", "--yes", "-t", "test", "--write-command-result") + assertConfigMapNotExists(t, k, p.TestSlug(), "cm2") + + summaries, err = rs.ListCommandResultSummaries(results.ListCommandResultSummariesOptions{}) + assert.NoError(t, err) + assert.Len(t, summaries, 4) + assertSummary(t, result.CommandResultSummary{ + DeletedObjects: 1, + }, summaries[0]) +} From 863004016fac114154d1335e836bb6b23e8ecc23 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 23 May 2023 13:49:50 +0200 Subject: [PATCH 1676/2916] chore: Run make replace-commands-help --- docs/reference/commands/common-arguments.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/commands/common-arguments.md b/docs/reference/commands/common-arguments.md index 055c8e2cb..192cf6433 100644 --- a/docs/reference/commands/common-arguments.md +++ b/docs/reference/commands/common-arguments.md @@ -138,7 +138,7 @@ Command Results: "kluctl-results") --force-write-command-result Force writing of command results, even if the command is run in dry-run mode. --keep-command-results-count int Configure how many old command results to keep. (default 10) - --no-write-command-result Disable writing of command results into the cluster. + --write-command-result Enable writing of command results into the cluster. ``` From 756c528769045e1eaefb04404245ee648d17345d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 23 May 2023 15:15:32 +0200 Subject: [PATCH 1677/2916] refactor: Add controller-runtime client to K8sCluster --- pkg/k8s/client.go | 8 ++++++++ pkg/k8s/client_factory.go | 13 +++++++++++++ pkg/k8s/fake_client_factory.go | 12 ++++++++++++ 3 files changed, 33 insertions(+) diff --git a/pkg/k8s/client.go b/pkg/k8s/client.go index 2d8069549..32beb055f 100644 --- a/pkg/k8s/client.go +++ b/pkg/k8s/client.go @@ -7,6 +7,7 @@ import ( "k8s.io/client-go/dynamic" corev1 "k8s.io/client-go/kubernetes/typed/core/v1" "k8s.io/client-go/metadata" + "sigs.k8s.io/controller-runtime/pkg/client" ) type k8sClients struct { @@ -17,6 +18,8 @@ type k8sClients struct { } type parallelClientEntry struct { + client client.Client + corev1 corev1.CoreV1Interface dynamicClient dynamic.Interface metadataClient metadata.Interface @@ -51,6 +54,11 @@ func newK8sClients(ctx context.Context, clientFactory ClientFactory, count int) for i := 0; i < count; i++ { p := ¶llelClientEntry{} + p.client, err = clientFactory.Client(p) + if err != nil { + return nil, err + } + p.corev1, err = clientFactory.CoreV1Client(p) if err != nil { return nil, err diff --git a/pkg/k8s/client_factory.go b/pkg/k8s/client_factory.go index 2edff9b44..594ddecc1 100644 --- a/pkg/k8s/client_factory.go +++ b/pkg/k8s/client_factory.go @@ -13,6 +13,7 @@ import ( "net/http" "net/url" "path/filepath" + "sigs.k8s.io/controller-runtime/pkg/client" "time" ) @@ -22,6 +23,8 @@ type ClientFactory interface { CloseIdleConnections() + Client(wh rest.WarningHandler) (client.Client, error) + DiscoveryClient() (discovery.DiscoveryInterface, error) CoreV1Client(wh rest.WarningHandler) (corev1.CoreV1Interface, error) DynamicClient(wh rest.WarningHandler) (dynamic.Interface, error) @@ -42,6 +45,16 @@ func (r *realClientFactory) GetCA() []byte { return r.config.CAData } +func (r *realClientFactory) Client(wh rest.WarningHandler) (client.Client, error) { + config := rest.CopyConfig(r.config) + config.WarningHandler = wh + return client.New(config, client.Options{ + WarningHandler: client.WarningHandlerOptions{ + SuppressWarnings: true, + }, + }) +} + func (r *realClientFactory) DiscoveryClient() (discovery.DiscoveryInterface, error) { apiHost, err := url.Parse(r.config.Host) if err != nil { diff --git a/pkg/k8s/fake_client_factory.go b/pkg/k8s/fake_client_factory.go index 2de043f74..6ea0dd26d 100644 --- a/pkg/k8s/fake_client_factory.go +++ b/pkg/k8s/fake_client_factory.go @@ -16,6 +16,8 @@ import ( fake_metadata "k8s.io/client-go/metadata/fake" "k8s.io/client-go/rest" "k8s.io/client-go/testing" + "sigs.k8s.io/controller-runtime/pkg/client" + fake2 "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/kustomize/kyaml/openapi" "sigs.k8s.io/kustomize/kyaml/yaml" "strings" @@ -25,6 +27,7 @@ type fakeClientFactory struct { clientSet *fake.Clientset dynamicClient *fake_dynamic.FakeDynamicClient metadataClient *fake_metadata.FakeMetadataClient + clientBuilder *fake2.ClientBuilder } func (f *fakeClientFactory) RESTConfig() *rest.Config { @@ -38,6 +41,10 @@ func (f *fakeClientFactory) GetCA() []byte { func (f *fakeClientFactory) CloseIdleConnections() { } +func (f *fakeClientFactory) Client(wh rest.WarningHandler) (client.Client, error) { + return f.clientBuilder.Build(), nil +} + func (f *fakeClientFactory) DiscoveryClient() (discovery.DiscoveryInterface, error) { return f.clientSet.Discovery(), nil } @@ -65,10 +72,15 @@ func NewFakeClientFactory(objects ...runtime.Object) *fakeClientFactory { dynamicClient := fake_dynamic.NewSimpleDynamicClient(scheme, objects...) metadataClient := fake_metadata.NewSimpleMetadataClient(scheme, objects...) + clientBuilder := fake2.NewClientBuilder(). + WithScheme(scheme). + WithRuntimeObjects(objects...) + return &fakeClientFactory{ clientSet: clientSet, dynamicClient: dynamicClient, metadataClient: metadataClient, + clientBuilder: clientBuilder, } } From 1e8daf502bfdb5e8fff3853e585e10a46b1cb3e9 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 23 May 2023 16:34:27 +0200 Subject: [PATCH 1678/2916] refactor: Use controller-runtime client for Get/List/Patch/Update/Delete --- pkg/deployment/utils/apply_utils.go | 6 +- pkg/k8s/client.go | 18 +-- pkg/k8s/k8s_cluster.go | 205 +++++++++++----------------- pkg/seal/bootstrap.go | 4 +- 4 files changed, 94 insertions(+), 139 deletions(-) diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index 6a7847e51..72e1c0289 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -250,7 +250,7 @@ func (a *ApplyUtil) retryApplyForceReplace(x *uo.UnstructuredObject, hook bool, o := k8s.PatchOptions{ ForceDryRun: a.o.DryRun, } - r, apiWarnings, err := a.k.PatchObject(x, o) + r, apiWarnings, err := a.k.ApplyObject(x, o) a.handleApiWarnings(ref, apiWarnings) if err != nil { a.HandleError(ref, err) @@ -324,7 +324,7 @@ func (a *ApplyUtil) retryApplyWithConflicts(x *uo.UnstructuredObject, hook bool, ForceDryRun: a.o.DryRun, ForceApply: true, } - r, apiWarnings, err := a.k.PatchObject(x2, options) + r, apiWarnings, err := a.k.ApplyObject(x2, options) a.handleApiWarnings(ref, apiWarnings) if err != nil { // We didn't manage to solve it, better to abort (and not retry with replace!) @@ -367,7 +367,7 @@ func (a *ApplyUtil) ApplyObject(x *uo.UnstructuredObject, replaced bool, hook bo options := k8s.PatchOptions{ ForceDryRun: a.o.DryRun, } - r, apiWarnings, err := a.k.PatchObject(x, options) + r, apiWarnings, err := a.k.ApplyObject(x, options) if r != nil && usesDummyName { tmpName := r.GetK8sName() _ = r.ReplaceKeys(tmpName, ref.Name) diff --git a/pkg/k8s/client.go b/pkg/k8s/client.go index 32beb055f..fe0044ebc 100644 --- a/pkg/k8s/client.go +++ b/pkg/k8s/client.go @@ -102,6 +102,16 @@ func (k *k8sClients) withClientFromPool(cb func(p *parallelClientEntry) error) ( } } +func (k *k8sClients) withCClientFromPool(dryRun bool, cb func(c client.Client) error) ([]ApiWarning, error) { + return k.withClientFromPool(func(p *parallelClientEntry) error { + c := p.client + if dryRun { + c = client.NewDryRunClient(c) + } + return cb(c) + }) +} + func (k *k8sClients) withDynamicClientForGVR(gvr *schema.GroupVersionResource, namespace string, cb func(r dynamic.ResourceInterface) error) ([]ApiWarning, error) { return k.withClientFromPool(func(p *parallelClientEntry) error { if namespace != "" { @@ -111,11 +121,3 @@ func (k *k8sClients) withDynamicClientForGVR(gvr *schema.GroupVersionResource, n } }) } - -func (k *k8sClients) withDynamicClientForGVK(resources *k8sResources, gvk schema.GroupVersionKind, namespace string, cb func(r dynamic.ResourceInterface) error) ([]ApiWarning, error) { - gvr, err := resources.GetGVRForGVK(gvk) - if err != nil { - return nil, err - } - return k.withDynamicClientForGVR(gvr, namespace, cb) -} diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index be727ffd8..4353326ad 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -2,11 +2,11 @@ package k8s import ( "context" - "encoding/json" "fmt" "io" - "k8s.io/client-go/metadata" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "net/http" + "sigs.k8s.io/controller-runtime/pkg/client" "strings" "sync" "time" @@ -17,15 +17,12 @@ import ( "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" - "github.com/kluctl/kluctl/v2/pkg/yaml" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/version" "k8s.io/client-go/discovery" - "k8s.io/client-go/dynamic" _ "k8s.io/client-go/plugin/pkg/client/auth" "k8s.io/client-go/rest" "k8s.io/client-go/restmapper" @@ -120,72 +117,57 @@ func (k *K8sCluster) buildLabelSelector(labels map[string]string) string { return ret } -func (k *K8sCluster) ListObjects(gvk schema.GroupVersionKind, namespace string, labels map[string]string) ([]*uo.UnstructuredObject, []ApiWarning, error) { - var result []*uo.UnstructuredObject - - apiWarnings, err := k.clients.withDynamicClientForGVK(k.Resources, gvk, namespace, func(r dynamic.ResourceInterface) error { - o := v1.ListOptions{ - LabelSelector: k.buildLabelSelector(labels), - } - x, err := r.List(k.ctx, o) - if err != nil { - return err - } - for _, o := range x.Items { - result = append(result, uo.FromUnstructured(&o)) - } - return nil +func (k *K8sCluster) doList(l client.ObjectList, namespace string, labels map[string]string) ([]*uo.UnstructuredObject, []ApiWarning, error) { + apiWarnings, err := k.clients.withCClientFromPool(true, func(c client.Client) error { + return c.List(k.ctx, l, client.InNamespace(namespace), client.MatchingLabels(labels)) }) - return result, apiWarnings, err -} - -func (k *K8sCluster) ListMetadata(gvk schema.GroupVersionKind, namespace string, labels map[string]string) ([]*uo.UnstructuredObject, []ApiWarning, error) { - var result []*uo.UnstructuredObject - - gvr, err := k.Resources.GetGVRForGVK(gvk) if err != nil { - return nil, nil, err + return nil, apiWarnings, err } - apiWarnings, err := k.clients.withClientFromPool(func(p *parallelClientEntry) error { - var r metadata.ResourceInterface - if namespace == "" { - r = p.metadataClient.Resource(*gvr) - } else { - r = p.metadataClient.Resource(*gvr).Namespace(namespace) - } - o := v1.ListOptions{ - LabelSelector: k.buildLabelSelector(labels), - } - x, err := r.List(k.ctx, o) - if err != nil { - return err + var result []*uo.UnstructuredObject + if l2, ok := l.(*unstructured.UnstructuredList); ok { + for _, o := range l2.Items { + result = append(result, uo.FromUnstructured(&o)) } - for _, o := range x.Items { - u, err := uo.FromStruct(o) + } else if l2, ok := l.(*v1.PartialObjectMetadataList); ok { + for _, o := range l2.Items { + x, err := uo.FromStruct(&o) if err != nil { - return err + return nil, apiWarnings, err } - u.SetK8sGVK(gvk) - result = append(result, u) + result = append(result, x) } - return nil - }) + } return result, apiWarnings, err } +func (k *K8sCluster) ListObjects(gvk schema.GroupVersionKind, namespace string, labels map[string]string) ([]*uo.UnstructuredObject, []ApiWarning, error) { + var l unstructured.UnstructuredList + l.SetGroupVersionKind(gvk) + return k.doList(&l, namespace, labels) +} + +func (k *K8sCluster) ListMetadata(gvk schema.GroupVersionKind, namespace string, labels map[string]string) ([]*uo.UnstructuredObject, []ApiWarning, error) { + var l v1.PartialObjectMetadataList + l.SetGroupVersionKind(gvk) + return k.doList(&l, namespace, labels) +} func (k *K8sCluster) GetSingleObject(ref k8s.ObjectRef) (*uo.UnstructuredObject, []ApiWarning, error) { - var result *uo.UnstructuredObject - apiWarnings, err := k.clients.withDynamicClientForGVK(k.Resources, ref.GroupVersionKind(), ref.Namespace, func(r dynamic.ResourceInterface) error { - o := v1.GetOptions{} - x, err := r.Get(k.ctx, ref.Name, o) - if err != nil { - return err - } - result = uo.FromUnstructured(x) - return nil + var o unstructured.Unstructured + o.SetGroupVersionKind(ref.GroupVersionKind()) + + apiWarnings, err := k.clients.withCClientFromPool(true, func(c client.Client) error { + return c.Get(k.ctx, client.ObjectKey{ + Name: ref.Name, + Namespace: ref.Namespace, + }, &o) + }) - return result, apiWarnings, err + if err != nil { + return nil, apiWarnings, err + } + return uo.FromUnstructured(&o), apiWarnings, nil } type DeleteOptions struct { @@ -197,25 +179,19 @@ type DeleteOptions struct { func (k *K8sCluster) DeleteSingleObject(ref k8s.ObjectRef, options DeleteOptions) ([]ApiWarning, error) { dryRun := k.DryRun || options.ForceDryRun - pp := v1.DeletePropagationBackground - o := v1.DeleteOptions{ - PropagationPolicy: &pp, - } - if dryRun { - o.DryRun = []string{"All"} - } + var o unstructured.Unstructured + o.SetGroupVersionKind(ref.GroupVersionKind()) + o.SetName(ref.Name) + o.SetNamespace(ref.Namespace) - apiWarnings, err := k.clients.withDynamicClientForGVK(k.Resources, ref.GroupVersionKind(), ref.Namespace, func(r dynamic.ResourceInterface) error { - err := r.Delete(k.ctx, ref.Name, o) - if err != nil { - if options.IgnoreNotFoundError && errors.IsNotFound(err) { - return nil - } - return err - } - return nil + apiWarnings, err := k.clients.withCClientFromPool(dryRun, func(c client.Client) error { + return c.Delete(k.ctx, &o, client.PropagationPolicy(v1.DeletePropagationBackground)) }) + if err != nil { + if options.IgnoreNotFoundError && errors.IsNotFound(err) { + return apiWarnings, nil + } return apiWarnings, err } @@ -342,54 +318,35 @@ type PatchOptions struct { ForceApply bool } -func (k *K8sCluster) doPatch(ref k8s.ObjectRef, data []byte, patchType types.PatchType, options PatchOptions) (*uo.UnstructuredObject, []ApiWarning, error) { - dryRun := k.DryRun || options.ForceDryRun +func (k *K8sCluster) doPatch(ref k8s.ObjectRef, obj client.Object, patch client.Patch, options PatchOptions) ([]ApiWarning, error) { + status.Trace(k.ctx, "patching %s", ref.String()) - po := v1.PatchOptions{ - FieldManager: "kluctl", - } - if dryRun { - po.DryRun = []string{"All"} - } + var opts []client.PatchOption if options.ForceApply { - po.Force = &options.ForceApply + opts = append(opts, client.ForceOwnership) } + if options.ForceDryRun { + opts = append(opts, client.DryRunAll) + } + opts = append(opts, client.FieldOwner("kluctl")) - status.Trace(k.ctx, "patching %s", ref.String()) - - var result *uo.UnstructuredObject - apiWarnings, err := k.clients.withDynamicClientForGVK(k.Resources, ref.GroupVersionKind(), ref.Namespace, func(r dynamic.ResourceInterface) error { - x, err := r.Patch(k.ctx, ref.Name, patchType, data, po) + apiWarnings, err := k.clients.withCClientFromPool(k.DryRun, func(c client.Client) error { + err := c.Patch(k.ctx, obj, patch, opts...) if err != nil { return fmt.Errorf("failed to patch %s: %w", ref.String(), err) } - result = uo.FromUnstructured(x) return nil }) - return result, apiWarnings, err + return apiWarnings, err } -func (k *K8sCluster) PatchObject(o *uo.UnstructuredObject, options PatchOptions) (*uo.UnstructuredObject, []ApiWarning, error) { - data, err := yaml.WriteYamlBytes(o) +func (k *K8sCluster) ApplyObject(o *uo.UnstructuredObject, options PatchOptions) (*uo.UnstructuredObject, []ApiWarning, error) { + obj := o.Clone().ToUnstructured() + apiWarnings, err := k.doPatch(o.GetK8sRef(), obj, client.Apply, options) if err != nil { - return nil, nil, err + return nil, apiWarnings, err } - - return k.doPatch(o.GetK8sRef(), data, types.ApplyPatchType, options) -} - -type JsonPatch struct { - Op string `json:"op"` - Path string `json:"path"` - Value any `json:"value"` -} - -func (k *K8sCluster) PatchObjectWithJsonPatch(ref k8s.ObjectRef, patch interface{}, options PatchOptions) (*uo.UnstructuredObject, []ApiWarning, error) { - data, err := json.Marshal(patch) - if err != nil { - return nil, nil, err - } - return k.doPatch(ref, data, types.JSONPatchType, options) + return uo.FromUnstructured(obj), apiWarnings, nil } type UpdateOptions struct { @@ -397,28 +354,24 @@ type UpdateOptions struct { } func (k *K8sCluster) UpdateObject(o *uo.UnstructuredObject, options UpdateOptions) (*uo.UnstructuredObject, []ApiWarning, error) { - dryRun := k.DryRun || options.ForceDryRun ref := o.GetK8sRef() - - updateOpts := v1.UpdateOptions{ - FieldManager: "kluctl", - } - if dryRun { - updateOpts.DryRun = []string{"All"} - } + obj := o.Clone().ToUnstructured() status.Trace(k.ctx, "updating %s", ref.String()) - var result *uo.UnstructuredObject - apiWarnings, err := k.clients.withDynamicClientForGVK(k.Resources, ref.GroupVersionKind(), ref.Namespace, func(r dynamic.ResourceInterface) error { - x, err := r.Update(k.ctx, o.ToUnstructured(), updateOpts) - if err != nil { - return err - } - result = uo.FromUnstructured(x) - return nil + var opts []client.UpdateOption + if options.ForceDryRun { + opts = append(opts, client.DryRunAll) + } + opts = append(opts, client.FieldOwner("kluctl")) + + apiWarnings, err := k.clients.withCClientFromPool(k.DryRun, func(c client.Client) error { + return c.Update(k.ctx, obj, opts...) }) - return result, apiWarnings, err + if err != nil { + return nil, apiWarnings, err + } + return uo.FromUnstructured(obj), apiWarnings, nil } // envtestProxyGet checks the environment variables KLUCTL_K8S_SERVICE_PROXY_XXX to enable testing of proxy requests with envtest diff --git a/pkg/seal/bootstrap.go b/pkg/seal/bootstrap.go index 81c786346..94932cafb 100644 --- a/pkg/seal/bootstrap.go +++ b/pkg/seal/bootstrap.go @@ -87,11 +87,11 @@ func writeKey(k *k8s.K8sCluster, key *rsa.PrivateKey, certs []*x509.Certificate, v1.TLSCertKey: string(certbytes), } - _, _, err := k.ReadWrite().PatchObject(secret, k8s.PatchOptions{}) + _, _, err := k.ReadWrite().ApplyObject(secret, k8s.PatchOptions{}) if err != nil { return err } - _, _, err = k.ReadWrite().PatchObject(configMap, k8s.PatchOptions{}) + _, _, err = k.ReadWrite().ApplyObject(configMap, k8s.PatchOptions{}) if err != nil { return err } From 2f6772e14bcd33ffa956ca00215e57c7cee571fc Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 23 May 2023 16:34:54 +0200 Subject: [PATCH 1679/2916] refactor: Move IsNamespae/FixNamespace into K8sCluster --- pkg/deployment/deployment_item.go | 2 +- pkg/deployment/utils/delete_utils.go | 2 +- pkg/helm/helm_release.go | 2 +- pkg/k8s/k8s_cluster.go | 45 ++++++++++++++++++++++++++++ pkg/k8s/resources.go | 35 ---------------------- 5 files changed, 48 insertions(+), 38 deletions(-) diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index 982be5d05..b1ef0acba 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -581,7 +581,7 @@ func (di *DeploymentItem) postprocessObjects(images *Images) error { _ = k8s.UnwrapListItems(o, true, func(o *uo.UnstructuredObject) error { if di.ctx.K != nil { - di.ctx.K.Resources.FixNamespace(o, "default") + di.ctx.K.FixNamespace(o, "default") } // Set common labels/annotations diff --git a/pkg/deployment/utils/delete_utils.go b/pkg/deployment/utils/delete_utils.go index 0cab9e94e..1ca1f49f5 100644 --- a/pkg/deployment/utils/delete_utils.go +++ b/pkg/deployment/utils/delete_utils.go @@ -40,7 +40,7 @@ var deleteOrder = [][]string{ } func objectRefForExclusion(k *k8s.K8sCluster, ref k8s2.ObjectRef) k8s2.ObjectRef { - ref = k.Resources.FixNamespaceInRef(ref) + ref = k.FixNamespaceInRef(ref) ref.Version = "" return ref } diff --git a/pkg/helm/helm_release.go b/pkg/helm/helm_release.go index 7295a7f29..2f6bd364a 100644 --- a/pkg/helm/helm_release.go +++ b/pkg/helm/helm_release.go @@ -260,7 +260,7 @@ func (hr *Release) doRender(ctx context.Context, k *k8s.K8sCluster, k8sVersion s // add the necessary namespace in the rendered resources if k != nil { err = k8s.UnwrapListItems(o, true, func(o *uo.UnstructuredObject) error { - k.Resources.FixNamespace(o, namespace) + k.FixNamespace(o, namespace) return nil }) if err != nil { diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index 4353326ad..8f0805761 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -424,6 +424,51 @@ func (k *K8sCluster) ProxyGet(scheme, namespace, name, port, path string, params return ret.Stream(k.ctx) } +func (k *K8sCluster) IsNamespaced(gvk schema.GroupVersionKind) *bool { + var obj unstructured.Unstructured + obj.SetGroupVersionKind(gvk) + + ret := false + _, err := k.clients.withCClientFromPool(true, func(c client.Client) error { + x, err := c.IsObjectNamespaced(&obj) + if err != nil { + return err + } + ret = x + return nil + }) + if err != nil { + return nil + } + return &ret +} + +func (k *K8sCluster) FixNamespace(o *uo.UnstructuredObject, def string) { + ref := o.GetK8sRef() + namespaced := k.IsNamespaced(ref.GroupVersionKind()) + if namespaced == nil { + return + } + if !*namespaced && ref.Namespace != "" { + o.SetK8sNamespace("") + } else if *namespaced && ref.Namespace == "" { + o.SetK8sNamespace(def) + } +} + +func (k *K8sCluster) FixNamespaceInRef(ref k8s.ObjectRef) k8s.ObjectRef { + namespaced := k.IsNamespaced(ref.GroupVersionKind()) + if namespaced == nil { + return ref + } + if !*namespaced && ref.Namespace != "" { + ref.Namespace = "" + } else if *namespaced && ref.Namespace == "" { + ref.Namespace = "default" + } + return ref +} + func (k *K8sCluster) ToRESTConfig() (*rest.Config, error) { return k.clientFactory.RESTConfig(), nil } diff --git a/pkg/k8s/resources.go b/pkg/k8s/resources.go index 902131e07..ec5188a7b 100644 --- a/pkg/k8s/resources.go +++ b/pkg/k8s/resources.go @@ -3,7 +3,6 @@ package k8s import ( "context" "fmt" - "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "k8s.io/apimachinery/pkg/api/meta" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -242,40 +241,6 @@ func (k *k8sResources) UpdateResourcesFromCRD(crd *uo.UnstructuredObject) error return nil } -func (k *k8sResources) IsNamespaced(gv schema.GroupKind) *bool { - ar := k.GetPreferredResource(gv) - if ar == nil { - return nil - } - return &ar.Namespaced -} - -func (k *k8sResources) FixNamespace(o *uo.UnstructuredObject, def string) { - ref := o.GetK8sRef() - namespaced := k.IsNamespaced(ref.GroupKind()) - if namespaced == nil { - return - } - if !*namespaced && ref.Namespace != "" { - o.SetK8sNamespace("") - } else if *namespaced && ref.Namespace == "" { - o.SetK8sNamespace(def) - } -} - -func (k *k8sResources) FixNamespaceInRef(ref k8s.ObjectRef) k8s.ObjectRef { - namespaced := k.IsNamespaced(ref.GroupKind()) - if namespaced == nil { - return ref - } - if !*namespaced && ref.Namespace != "" { - ref.Namespace = "" - } else if *namespaced && ref.Namespace == "" { - ref.Namespace = "default" - } - return ref -} - func (k *k8sResources) GetAllGroupVersions() ([]schema.GroupVersion, error) { k.mutex.Lock() defer k.mutex.Unlock() From 0c7ce866024b148c203a50d22e751201db505515 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 23 May 2023 23:28:30 +0200 Subject: [PATCH 1680/2916] refactor: Use RESTMapper instead of custom k8sResources implementation --- pkg/deployment/deployment_collection.go | 11 - pkg/deployment/deployment_item.go | 27 -- pkg/deployment/utils/apply_utils.go | 20 +- pkg/deployment/utils/delete_utils.go | 6 +- pkg/deployment/utils/remote_objects_utils.go | 8 +- pkg/helm/helm_release.go | 16 +- pkg/k8s/client.go | 11 - pkg/k8s/client_factory.go | 51 ++- pkg/k8s/fake_client_factory.go | 4 + pkg/k8s/k8s_cluster.go | 93 +++-- pkg/k8s/resources.go | 347 ++----------------- pkg/validation/validation.go | 2 +- 12 files changed, 183 insertions(+), 413 deletions(-) diff --git a/pkg/deployment/deployment_collection.go b/pkg/deployment/deployment_collection.go index 17faff1a6..7e099c31e 100644 --- a/pkg/deployment/deployment_collection.go +++ b/pkg/deployment/deployment_collection.go @@ -200,17 +200,6 @@ func (c *DeploymentCollection) buildKustomizeObjects() error { s.Success() s = status.Start(c.ctx.Ctx, "Postprocessing objects") - for _, d_ := range c.Deployments { - d := d_ - g.RunE(func() error { - err := d.postprocessCRDs() - if err != nil { - return fmt.Errorf("postprocessing CRDs failed: %w", err) - } - return nil - }) - } - g.Wait() g = utils.NewGoHelper(c.ctx.Ctx, 16) for _, d_ := range c.Deployments { diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index b1ef0acba..d291acbd0 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -14,7 +14,6 @@ import ( "github.com/kluctl/kluctl/v2/pkg/vars" "github.com/kluctl/kluctl/v2/pkg/yaml" "io/fs" - "k8s.io/apimachinery/pkg/runtime/schema" "os" "path" "path/filepath" @@ -541,32 +540,6 @@ func (di *DeploymentItem) buildKustomize() error { return nil } -var crdGV = schema.GroupKind{Group: "apiextensions.k8s.io", Kind: "CustomResourceDefinition"} - -// postprocessCRDs will update api resources from freshly deployed CRDs -// value even if the CRD is not deployed yet. -func (di *DeploymentItem) postprocessCRDs() error { - if di.dir == nil { - return nil - } - if di.ctx.K == nil { - return nil - } - - for _, o := range di.Objects { - gvk := o.GetK8sGVK() - if gvk.GroupKind() != crdGV { - continue - } - - err := di.ctx.K.Resources.UpdateResourcesFromCRD(o) - if err != nil { - return err - } - } - return nil -} - func (di *DeploymentItem) postprocessObjects(images *Images) error { if di.dir == nil { return nil diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index 72e1c0289..a4d928ba9 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -373,11 +373,17 @@ func (a *ApplyUtil) ApplyObject(x *uo.UnstructuredObject, replaced bool, hook bo _ = r.ReplaceKeys(tmpName, ref.Name) _ = r.ReplaceValues(tmpName, ref.Name) r.SetK8sNamespace(ref.Namespace) - } else if a.o.DryRun && errors.IsNotFound(err) { + } else if meta.IsNoMatchError(err) { if _, ok := a.allCRDs.Load(x.GetK8sGVK()); ok { - a.handleResult(x, hook) - a.HandleWarning(x.GetK8sRef(), fmt.Errorf("the underyling custom resource definition for %s has not been applied yet as Kluctl is running in dry-run mode. It is not guaranteed that the object will actually sucessfully apply", x.GetK8sRef().String())) - return + if a.o.DryRun { + a.handleResult(x, hook) + a.HandleWarning(x.GetK8sRef(), fmt.Errorf("the underyling custom resource definition for %s has not been applied yet as Kluctl is running in dry-run mode. It is not guaranteed that the object will actually sucessfully apply", x.GetK8sRef().String())) + return + } else { + // retry with invalidated discovery + a.k.ResetMapper() + r, apiWarnings, err = a.k.ApplyObject(x, options) + } } } if r != nil && ref.GroupKind().String() == "Namespace" { @@ -502,7 +508,11 @@ func (a *ApplyUtil) WaitReadiness(ref k8s2.ObjectRef, timeout time.Duration) boo func (a *ApplyUtil) applyDeploymentItem(d *deployment.DeploymentItem) { toDelete := map[k8s2.ObjectRef]bool{} for _, x := range d.Config.DeleteObjects { - for _, gvk := range a.k.Resources.GetFilteredGVKs(k8s.BuildGVKFilter(x.Group, nil, x.Kind)) { + gvks, err := a.k.GetFilteredGVKs(k8s.BuildGVKFilter(x.Group, nil, x.Kind)) + if err != nil { + a.HandleError(k8s2.ObjectRef{}, err) + } + for _, gvk := range gvks { ref := k8s2.ObjectRef{ Group: gvk.Group, Version: gvk.Version, diff --git a/pkg/deployment/utils/delete_utils.go b/pkg/deployment/utils/delete_utils.go index 1ca1f49f5..c32f41358 100644 --- a/pkg/deployment/utils/delete_utils.go +++ b/pkg/deployment/utils/delete_utils.go @@ -75,7 +75,11 @@ func filterObjectsForDelete(k *k8s.K8sCluster, objects []*uo.UnstructuredObject, } filteredResources := make(map[schema.GroupKind]bool) - for _, gvk := range k.Resources.GetFilteredPreferredGVKs(filterFunc) { + gvks, err := k.GetFilteredPreferredGVKs(filterFunc) + if err != nil { + return nil, err + } + for _, gvk := range gvks { filteredResources[gvk.GroupKind()] = true } diff --git a/pkg/deployment/utils/remote_objects_utils.go b/pkg/deployment/utils/remote_objects_utils.go index 0676f86e8..6cee1e0b7 100644 --- a/pkg/deployment/utils/remote_objects_utils.go +++ b/pkg/deployment/utils/remote_objects_utils.go @@ -9,6 +9,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" errors2 "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "sync" @@ -51,7 +52,7 @@ func (u *RemoteObjectUtils) getAllByDiscriminator(k *k8s.K8sCluster, discriminat errCount := 0 permissionErrCount := 0 - gvks := k.Resources.GetFilteredPreferredGVKs(func(ar *v1.APIResource) bool { + gvks, err := k.GetFilteredPreferredGVKs(func(ar *v1.APIResource) bool { if onlyUsedGKs != nil { gk := schema.GroupKind{ Group: ar.Group, @@ -63,6 +64,9 @@ func (u *RemoteObjectUtils) getAllByDiscriminator(k *k8s.K8sCluster, discriminat } return utils.FindStrInSlice(ar.Verbs, "list") != -1 }) + if err != nil { + return err + } g := utils.NewGoHelper(u.ctx, 0) for _, gvk := range gvks { @@ -141,7 +145,7 @@ func (u *RemoteObjectUtils) getMissingObjects(k *k8s.K8sCluster, refs []k8s2.Obj r, apiWarnings, err := k.GetSingleObject(ref) u.dew.AddApiWarnings(ref, apiWarnings) if err != nil { - if errors2.IsNotFound(err) { + if errors2.IsNotFound(err) || meta.IsNoMatchError(err) { return } if errors2.IsForbidden(err) || errors2.IsUnauthorized(err) { diff --git a/pkg/helm/helm_release.go b/pkg/helm/helm_release.go index 2f6bd364a..a5f741722 100644 --- a/pkg/helm/helm_release.go +++ b/pkg/helm/helm_release.go @@ -203,7 +203,10 @@ func (hr *Release) doRender(ctx context.Context, k *k8s.K8sCluster, k8sVersion s client.Replace = true client.ClientOnly = true client.KubeVersion = kubeVersion - client.APIVersions = hr.getApiVersions(k) + client.APIVersions, err = hr.getApiVersions(k) + if err != nil { + return err + } if hr.Config.SkipCRDs { client.SkipCRDs = true @@ -281,14 +284,17 @@ func (hr *Release) doRender(ctx context.Context, k *k8s.K8sCluster, k8sVersion s return nil } -func (hr *Release) getApiVersions(k *k8s.K8sCluster) chartutil.VersionSet { +func (hr *Release) getApiVersions(k *k8s.K8sCluster) (chartutil.VersionSet, error) { if k == nil { - return nil + return nil, nil } m := map[string]bool{} - gvks := k.Resources.GetFilteredGVKs(nil) + gvks, err := k.GetFilteredGVKs(nil) + if err != nil { + return nil, err + } for _, gvk := range gvks { gvStr := gvk.GroupVersion().String() m[gvStr] = true @@ -301,7 +307,7 @@ func (hr *Release) getApiVersions(k *k8s.K8sCluster) chartutil.VersionSet { ret = append(ret, id) } - return ret + return ret, nil } func (hr *Release) parseRenderedManifests(s string) ([]*uo.UnstructuredObject, error) { diff --git a/pkg/k8s/client.go b/pkg/k8s/client.go index fe0044ebc..8cf3b6b75 100644 --- a/pkg/k8s/client.go +++ b/pkg/k8s/client.go @@ -3,7 +3,6 @@ package k8s import ( "context" "fmt" - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/dynamic" corev1 "k8s.io/client-go/kubernetes/typed/core/v1" "k8s.io/client-go/metadata" @@ -111,13 +110,3 @@ func (k *k8sClients) withCClientFromPool(dryRun bool, cb func(c client.Client) e return cb(c) }) } - -func (k *k8sClients) withDynamicClientForGVR(gvr *schema.GroupVersionResource, namespace string, cb func(r dynamic.ResourceInterface) error) ([]ApiWarning, error) { - return k.withClientFromPool(func(p *parallelClientEntry) error { - if namespace != "" { - return cb(p.dynamicClient.Resource(*gvr).Namespace(namespace)) - } else { - return cb(p.dynamicClient.Resource(*gvr)) - } - }) -} diff --git a/pkg/k8s/client_factory.go b/pkg/k8s/client_factory.go index 594ddecc1..049575e8e 100644 --- a/pkg/k8s/client_factory.go +++ b/pkg/k8s/client_factory.go @@ -3,12 +3,14 @@ package k8s import ( "context" "github.com/kluctl/kluctl/v2/pkg/utils" + "k8s.io/apimachinery/pkg/api/meta" "k8s.io/client-go/discovery" "k8s.io/client-go/discovery/cached/disk" "k8s.io/client-go/dynamic" corev1 "k8s.io/client-go/kubernetes/typed/core/v1" "k8s.io/client-go/metadata" "k8s.io/client-go/rest" + "k8s.io/client-go/restmapper" "k8s.io/client-go/tools/clientcmd" "net/http" "net/url" @@ -23,6 +25,7 @@ type ClientFactory interface { CloseIdleConnections() + Mapper() meta.ResettableRESTMapper Client(wh rest.WarningHandler) (client.Client, error) DiscoveryClient() (discovery.DiscoveryInterface, error) @@ -35,6 +38,9 @@ type realClientFactory struct { ctx context.Context config *rest.Config httpClient *http.Client + + discoveryClient discovery.DiscoveryInterface + mapper meta.ResettableRESTMapper } func (r *realClientFactory) RESTConfig() *rest.Config { @@ -45,10 +51,16 @@ func (r *realClientFactory) GetCA() []byte { return r.config.CAData } +func (r *realClientFactory) Mapper() meta.ResettableRESTMapper { + return r.mapper +} + func (r *realClientFactory) Client(wh rest.WarningHandler) (client.Client, error) { config := rest.CopyConfig(r.config) config.WarningHandler = wh + return client.New(config, client.Options{ + Mapper: r.mapper, WarningHandler: client.WarningHandlerOptions{ SuppressWarnings: true, }, @@ -56,16 +68,7 @@ func (r *realClientFactory) Client(wh rest.WarningHandler) (client.Client, error } func (r *realClientFactory) DiscoveryClient() (discovery.DiscoveryInterface, error) { - apiHost, err := url.Parse(r.config.Host) - if err != nil { - return nil, err - } - discoveryCacheDir := filepath.Join(utils.GetTmpBaseDir(r.ctx), "kube-cache/discovery", apiHost.Hostname()) - discovery2, err := disk.NewCachedDiscoveryClientForConfig(dynamic.ConfigFor(r.config), discoveryCacheDir, "", time.Hour*24) - if err != nil { - return nil, err - } - return discovery2, nil + return r.discoveryClient, nil } func (r *realClientFactory) CoreV1Client(wh rest.WarningHandler) (corev1.CoreV1Interface, error) { @@ -90,6 +93,19 @@ func (r *realClientFactory) CloseIdleConnections() { r.httpClient.CloseIdleConnections() } +func initDiscoveryClient(ctx context.Context, config *rest.Config) (discovery.CachedDiscoveryInterface, error) { + apiHost, err := url.Parse(config.Host) + if err != nil { + return nil, err + } + discoveryCacheDir := filepath.Join(utils.GetTmpBaseDir(ctx), "kube-cache/discovery", apiHost.Hostname()) + discovery2, err := disk.NewCachedDiscoveryClientForConfig(dynamic.ConfigFor(config), discoveryCacheDir, "", time.Hour*24) + if err != nil { + return nil, err + } + return discovery2, nil +} + func NewClientFactory(ctx context.Context, configIn *rest.Config) (ClientFactory, error) { restConfig := rest.CopyConfig(configIn) restConfig.QPS = 10 @@ -100,10 +116,19 @@ func NewClientFactory(ctx context.Context, configIn *rest.Config) (ClientFactory return nil, err } + dc, err := initDiscoveryClient(ctx, restConfig) + if err != nil { + return nil, err + } + + mapper := restmapper.NewDeferredDiscoveryRESTMapper(dc) + return &realClientFactory{ - ctx: ctx, - config: restConfig, - httpClient: httpClient, + ctx: ctx, + config: restConfig, + httpClient: httpClient, + discoveryClient: dc, + mapper: mapper, }, nil } diff --git a/pkg/k8s/fake_client_factory.go b/pkg/k8s/fake_client_factory.go index 6ea0dd26d..31abd072d 100644 --- a/pkg/k8s/fake_client_factory.go +++ b/pkg/k8s/fake_client_factory.go @@ -41,6 +41,10 @@ func (f *fakeClientFactory) GetCA() []byte { func (f *fakeClientFactory) CloseIdleConnections() { } +func (f *fakeClientFactory) Mapper() meta.ResettableRESTMapper { + return nil +} + func (f *fakeClientFactory) Client(wh rest.WarningHandler) (client.Client, error) { return f.clientBuilder.Build(), nil } diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index 8f0805761..810e495f3 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -4,11 +4,12 @@ import ( "context" "fmt" "io" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "net/http" + "runtime" "sigs.k8s.io/controller-runtime/pkg/client" "strings" - "sync" "time" "github.com/Masterminds/semver/v3" @@ -34,11 +35,11 @@ type K8sCluster struct { DryRun bool clientFactory ClientFactory - clients *k8sClients - ServerVersion *version.Info + discovery discovery.DiscoveryInterface + clients *k8sClients - Resources *k8sResources + ServerVersion *version.Info } func NewK8sCluster(ctx context.Context, clientFactory ClientFactory, dryRun bool) (*K8sCluster, error) { @@ -50,7 +51,7 @@ func NewK8sCluster(ctx context.Context, clientFactory ClientFactory, dryRun bool clientFactory: clientFactory, } - k.Resources, err = newK8sResources(ctx, clientFactory) + k.discovery, err = clientFactory.DiscoveryClient() if err != nil { return nil, err } @@ -60,34 +61,12 @@ func NewK8sCluster(ctx context.Context, clientFactory ClientFactory, dryRun bool return nil, err } - v, err := k.Resources.discovery.ServerVersion() + v, err := k.discovery.ServerVersion() if err != nil { return nil, err } k.ServerVersion = v - var wg sync.WaitGroup - wg.Add(2) - - var err1 error - var err2 error - go func() { - err1 = k.Resources.updateResources() - wg.Done() - }() - go func() { - err2 = k.Resources.updateResourcesFromCRDs(k.clients) - wg.Done() - }() - wg.Wait() - - if err1 != nil { - return nil, err1 - } - if err2 != nil { - return nil, err2 - } - return k, nil } @@ -201,6 +180,7 @@ func (k *K8sCluster) DeleteSingleObject(ref k8s.ObjectRef, options DeleteOptions return apiWarnings, err } } + return apiWarnings, nil } @@ -469,6 +449,63 @@ func (k *K8sCluster) FixNamespaceInRef(ref k8s.ObjectRef) k8s.ObjectRef { return ref } +func (k *K8sCluster) GetSchemaForGVK(gvk schema.GroupVersionKind) (*uo.UnstructuredObject, error) { + if gvk.Kind == "KluctlDeployment" { + runtime.Breakpoint() + } + + rms, err := k.clientFactory.Mapper().RESTMappings(gvk.GroupKind(), gvk.Version) + if err != nil { + return nil, err + } + + var rm *meta.RESTMapping + for _, x := range rms { + if x.GroupVersionKind == gvk { + rm = x + break + } + } + if rm == nil { + return nil, fmt.Errorf("rest mapping not found") + } + + ref := k8s.NewObjectRef(apiextensionsv1.GroupName, "v1", "CustomResourceDefinition", fmt.Sprintf("%s.%s", rm.Resource.Resource, gvk.Group), "") + + crd, _, err := k.GetSingleObject(ref) + if err != nil { + return nil, err + } + + versions, ok, err := crd.GetNestedObjectList("spec", "versions") + if err != nil { + return nil, err + } + if !ok { + return nil, fmt.Errorf("versions not found in CRD") + } + + for _, v := range versions { + name, _, _ := v.GetNestedString("name") + if name != gvk.Version { + continue + } + s, ok, err := v.GetNestedObject("schema", "openAPIV3Schema") + if err != nil { + return nil, err + } + if !ok { + return nil, fmt.Errorf("version %s has no schema", name) + } + return s, nil + } + return nil, fmt.Errorf("schema for %s not found", gvk.String()) +} + +func (k *K8sCluster) ResetMapper() { + k.clientFactory.Mapper().Reset() +} + func (k *K8sCluster) ToRESTConfig() (*rest.Config, error) { return k.clientFactory.RESTConfig(), nil } diff --git a/pkg/k8s/resources.go b/pkg/k8s/resources.go index ec5188a7b..8295b7498 100644 --- a/pkg/k8s/resources.go +++ b/pkg/k8s/resources.go @@ -1,20 +1,12 @@ package k8s import ( - "context" "fmt" - "github.com/kluctl/kluctl/v2/pkg/utils/uo" - "k8s.io/apimachinery/pkg/api/meta" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/version" "k8s.io/client-go/discovery" - "k8s.io/client-go/dynamic" - "runtime" - "sort" + "k8s.io/client-go/restmapper" "strings" - "sync" ) var ( @@ -24,49 +16,14 @@ var ( } ) -type k8sResources struct { - ctx context.Context - discovery discovery.DiscoveryInterface - - allResources map[schema.GroupVersionKind]v1.APIResource - preferredResources map[schema.GroupKind]v1.APIResource - crds map[schema.GroupKind]*uo.UnstructuredObject - mutex sync.Mutex -} - -func newK8sResources(ctx context.Context, clientFactory ClientFactory) (*k8sResources, error) { - k := &k8sResources{ - ctx: ctx, - allResources: map[schema.GroupVersionKind]v1.APIResource{}, - preferredResources: map[schema.GroupKind]v1.APIResource{}, - crds: map[schema.GroupKind]*uo.UnstructuredObject{}, - mutex: sync.Mutex{}, - } - - var err error - k.discovery, err = clientFactory.DiscoveryClient() - if err != nil { - return nil, err - } - - return k, nil -} - -func (k *k8sResources) updateResources() error { - k.mutex.Lock() - defer k.mutex.Unlock() - - k.allResources = map[schema.GroupVersionKind]v1.APIResource{} - k.preferredResources = map[schema.GroupKind]v1.APIResource{} - k.crds = map[schema.GroupKind]*uo.UnstructuredObject{} +func (k *K8sCluster) doGetApiGroupResources() ([]*restmapper.APIGroupResources, error) { + var ret []*restmapper.APIGroupResources // the discovery client doesn't support cancellation, so we need to run it in the background and wait for it - var ags []*v1.APIGroup - var arls []*v1.APIResourceList finished := make(chan error) go func() { var err error - ags, arls, err = k.discovery.ServerGroupsAndResources() + ret, err = restmapper.GetAPIGroupResources(k.discovery) if err != nil && !discovery.IsGroupDiscoveryFailedError(err) { finished <- err return @@ -77,234 +34,65 @@ func (k *k8sResources) updateResources() error { select { case err := <-finished: if err != nil { - return err + return nil, err } case <-k.ctx.Done(): - return fmt.Errorf("failed listing api resources: %w", k.ctx.Err()) + return nil, fmt.Errorf("failed listing api resources: %w", k.ctx.Err()) } - - for _, arl := range arls { - var ag *v1.APIGroup - for _, x := range ags { - if x.Name == arl.GroupVersionKind().Group { - ag = x - break - } - } - if ag == nil { - continue - } - - for _, ar := range arl.APIResources { - if ar.Version == "__internal" { - continue - } - if strings.Index(ar.Name, "/") != -1 { - // skip subresources - continue - } - gv, err := schema.ParseGroupVersion(arl.GroupVersion) - if err != nil { - continue - } - - ar := ar - ar.Group = gv.Group - ar.Version = gv.Version - - gvk := schema.GroupVersionKind{ - Group: ar.Group, - Version: ar.Version, - Kind: ar.Kind, - } - if _, ok := deprecatedResources[gvk.GroupKind()]; ok { - continue - } - - k.allResources[gvk] = ar - - if gvk.Version == ag.PreferredVersion.Version { - k.preferredResources[gvk.GroupKind()] = ar - } - } - } - if len(k.preferredResources) == 0 { - runtime.Breakpoint() - } - - return nil + return ret, nil } -var crdGVR = schema.GroupVersionResource{Group: "apiextensions.k8s.io", Version: "v1", Resource: "customresourcedefinitions"} - -func (k *k8sResources) updateResourcesFromCRDs(clients *k8sClients) error { - var crdList *unstructured.UnstructuredList - _, err := clients.withDynamicClientForGVR(&crdGVR, "", func(r dynamic.ResourceInterface) error { - var err error - crdList, err = r.List(k.ctx, v1.ListOptions{}) - return err - }) - if err != nil { - return err - } - - for _, x := range crdList.Items { - x := x - crd := uo.FromUnstructured(&x) - err = k.UpdateResourcesFromCRD(crd) - if err != nil { - return err - } - } - - return nil -} - -func (k *k8sResources) UpdateResourcesFromCRD(crd *uo.UnstructuredObject) error { - var err error - var ar v1.APIResource - ar.Group, _, err = crd.GetNestedString("spec", "group") - if err != nil { - return err - } - ar.Name, _, err = crd.GetNestedString("spec", "names", "plural") - if err != nil { - return err - } - ar.Kind, _, err = crd.GetNestedString("spec", "names", "kind") - if err != nil { - return err - } - ar.SingularName, _, err = crd.GetNestedString("spec", "names", "singular") - if err != nil { - return err - } - scope, _, err := crd.GetNestedString("spec", "scope") - if err != nil { - return err - } - ar.Namespaced = strings.ToLower(scope) == "namespaced" - ar.ShortNames, _, err = crd.GetNestedStringList("spec", "names", "shortNames") - if err != nil { - return err - } - ar.Categories, _, err = crd.GetNestedStringList("spec", "names", "categories") - if err != nil { - return err - } - versions, _, err := crd.GetNestedObjectList("spec", "versions") - if err != nil { - return err - } - - ar.Verbs = []string{"delete", "deletecollection", "get", "list", "patch", "create", "update", "watch"} - - gk := schema.GroupKind{ - Group: ar.Group, - Kind: ar.Kind, - } - - k.mutex.Lock() - defer k.mutex.Unlock() - - k.crds[gk] = crd - - var versionStrs []string - for _, v := range versions { - name, _, err := v.GetNestedString("name") - if err != nil { - return err +func (k *K8sCluster) doGetFilteredGVKs(ret *[]schema.GroupVersionKind, g v1.APIGroup, v string, vrs []v1.APIResource, filter func(ar *v1.APIResource) bool) { + for _, vr := range vrs { + if strings.Index(vr.Name, "/") != -1 { + // skip sub-resources + continue } - versionStrs = append(versionStrs, name) - } - - // Sort the same way as api discovery does it. The first entry is then the preferred version - sort.Slice(versionStrs, func(i, j int) bool { - return version.CompareKubeAwareVersionStrings(versionStrs[i], versionStrs[j]) > 0 - }) - - for i, v := range versionStrs { - ar2 := ar - ar2.Version = v gvk := schema.GroupVersionKind{ - Group: gk.Group, - Version: ar2.Version, - Kind: gk.Kind, + Group: g.Name, + Version: v, + Kind: vr.Kind, } - k.allResources[gvk] = ar2 - - if i == 0 { - k.preferredResources[gk] = ar2 + if _, ok := deprecatedResources[gvk.GroupKind()]; ok { + continue } - } - - return nil -} - -func (k *k8sResources) GetAllGroupVersions() ([]schema.GroupVersion, error) { - k.mutex.Lock() - defer k.mutex.Unlock() - - m := make(map[schema.GroupVersion]bool) - var l []schema.GroupVersion - - for gvk, _ := range k.allResources { - gv := gvk.GroupVersion() - if _, ok := m[gv]; !ok { - m[gv] = true - l = append(l, gv) + if filter != nil && !filter(&vr) { + continue } + *ret = append(*ret, gvk) } - return l, nil } -func (k *k8sResources) GetPreferredResource(gk schema.GroupKind) *v1.APIResource { - k.mutex.Lock() - defer k.mutex.Unlock() - - ar, ok := k.preferredResources[gk] - if !ok { - return nil +func (k *K8sCluster) GetFilteredGVKs(filter func(ar *v1.APIResource) bool) ([]schema.GroupVersionKind, error) { + agrs, err := k.doGetApiGroupResources() + if err != nil { + return nil, err } - return &ar -} - -func (k *k8sResources) GetFilteredGVKs(filter func(ar *v1.APIResource) bool) []schema.GroupVersionKind { - k.mutex.Lock() - defer k.mutex.Unlock() var ret []schema.GroupVersionKind - for _, ar := range k.allResources { - if filter != nil && !filter(&ar) { - continue + for _, agr := range agrs { + for v, vrs := range agr.VersionedResources { + k.doGetFilteredGVKs(&ret, agr.Group, v, vrs, filter) } - gvk := schema.GroupVersionKind{ - Group: ar.Group, - Version: ar.Version, - Kind: ar.Kind, - } - ret = append(ret, gvk) } - return ret + return ret, nil } -func (k *k8sResources) GetFilteredPreferredGVKs(filter func(ar *v1.APIResource) bool) []schema.GroupVersionKind { - k.mutex.Lock() - defer k.mutex.Unlock() +func (k *K8sCluster) GetFilteredPreferredGVKs(filter func(ar *v1.APIResource) bool) ([]schema.GroupVersionKind, error) { + agrs, err := k.doGetApiGroupResources() + if err != nil { + return nil, err + } var ret []schema.GroupVersionKind - for _, ar := range k.preferredResources { - if !filter(&ar) { + for _, agr := range agrs { + vrs, ok := agr.VersionedResources[agr.Group.PreferredVersion.Version] + if !ok { continue } - gvk := schema.GroupVersionKind{ - Group: ar.Group, - Version: ar.Version, - Kind: ar.Kind, - } - ret = append(ret, gvk) + k.doGetFilteredGVKs(&ret, agr.Group, agr.Group.PreferredVersion.Version, vrs, filter) } - return ret + return ret, nil } func BuildGVKFilter(group *string, version *string, kind *string) func(ar *v1.APIResource) bool { @@ -321,62 +109,3 @@ func BuildGVKFilter(group *string, version *string, kind *string) func(ar *v1.AP return true } } - -func (k *k8sResources) GetGVRForGVK(gvk schema.GroupVersionKind) (*schema.GroupVersionResource, error) { - k.mutex.Lock() - defer k.mutex.Unlock() - - ar, ok := k.allResources[gvk] - if !ok { - return nil, &meta.NoKindMatchError{ - GroupKind: gvk.GroupKind(), - SearchedVersions: []string{gvk.Version}, - } - } - - return &schema.GroupVersionResource{ - Group: ar.Group, - Version: ar.Version, - Resource: ar.Name, - }, nil -} - -func (k *k8sResources) GetCRDForGK(gk schema.GroupKind) *uo.UnstructuredObject { - k.mutex.Lock() - defer k.mutex.Unlock() - - crd, _ := k.crds[gk] - - return crd -} - -func (k *k8sResources) GetSchemaForGVK(gvk schema.GroupVersionKind) (*uo.UnstructuredObject, error) { - crd := k.GetCRDForGK(gvk.GroupKind()) - if crd == nil { - return nil, nil - } - - versions, ok, err := crd.GetNestedObjectList("spec", "versions") - if err != nil { - return nil, err - } - if !ok { - return nil, fmt.Errorf("versions not found in CRD") - } - - for _, v := range versions { - name, _, _ := v.GetNestedString("name") - if name != gvk.Version { - continue - } - s, ok, err := v.GetNestedObject("schema", "openAPIV3Schema") - if err != nil { - return nil, err - } - if !ok { - return nil, fmt.Errorf("version %s has no schema", name) - } - return s, nil - } - return nil, fmt.Errorf("schema for %s not found", gvk.String()) -} diff --git a/pkg/validation/validation.go b/pkg/validation/validation.go index 09f23d401..673a346ba 100644 --- a/pkg/validation/validation.go +++ b/pkg/validation/validation.go @@ -126,7 +126,7 @@ func ValidateObject(k *k8s.K8sCluster, o *uo.UnstructuredObject, notReadyIsError // can't really say anything... return } - s, err := k.Resources.GetSchemaForGVK(ref.GroupVersionKind()) + s, err := k.GetSchemaForGVK(ref.GroupVersionKind()) if err != nil && !errors.IsNotFound(err) { addError(err.Error()) return From d21e6b7d815e4475c143f592b2c00771ded7b3a2 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 24 May 2023 09:38:06 +0200 Subject: [PATCH 1681/2916] fix: Use client from K8sCluster for ResultStore --- cmd/kluctl/commands/utils.go | 20 ++------------------ pkg/k8s/client.go | 17 +---------------- pkg/k8s/client_factory.go | 6 ++---- pkg/k8s/k8s_cluster.go | 24 +++++++++--------------- 4 files changed, 14 insertions(+), 53 deletions(-) diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 630199ca5..66e6103b0 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -21,8 +21,6 @@ import ( "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd/api" "os" - cache2 "sigs.k8s.io/controller-runtime/pkg/cache" - client2 "sigs.k8s.io/controller-runtime/pkg/client" "strings" ) @@ -201,26 +199,12 @@ func withProjectTargetCommandContext(ctx context.Context, args projectTargetComm var resultStore results.ResultStore if args.commandResultFlags != nil && args.commandResultFlags.WriteCommandResult { - rc, err := targetCtx.SharedContext.K.ToRESTConfig() + client, err := targetCtx.SharedContext.K.ToClient() if err != nil { return err } - client, err := client2.New(rc, client2.Options{}) - if err != nil { - return err - } - cache, err := cache2.New(rc, cache2.Options{}) - if err != nil { - return err - } - - cancelCtx, cancelFunc := context.WithCancel(ctx) - defer cancelFunc() - go func() { - _ = cache.Start(cancelCtx) - }() - resultStore, err = results.NewResultStoreSecrets(cancelCtx, client, cache, args.commandResultFlags.CommandResultNamespace, args.commandResultFlags.KeepCommandResultsCount) + resultStore, err = results.NewResultStoreSecrets(ctx, client, nil, args.commandResultFlags.CommandResultNamespace, args.commandResultFlags.KeepCommandResultsCount) if err != nil { return err } diff --git a/pkg/k8s/client.go b/pkg/k8s/client.go index 8cf3b6b75..b65df6d28 100644 --- a/pkg/k8s/client.go +++ b/pkg/k8s/client.go @@ -3,9 +3,7 @@ package k8s import ( "context" "fmt" - "k8s.io/client-go/dynamic" corev1 "k8s.io/client-go/kubernetes/typed/core/v1" - "k8s.io/client-go/metadata" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -18,10 +16,7 @@ type k8sClients struct { type parallelClientEntry struct { client client.Client - - corev1 corev1.CoreV1Interface - dynamicClient dynamic.Interface - metadataClient metadata.Interface + corev1 corev1.CoreV1Interface warnings []ApiWarning } @@ -63,16 +58,6 @@ func newK8sClients(ctx context.Context, clientFactory ClientFactory, count int) return nil, err } - p.dynamicClient, err = clientFactory.DynamicClient(p) - if err != nil { - return nil, err - } - - p.metadataClient, err = clientFactory.MetadataClient(p) - if err != nil { - return nil, err - } - k.clientPool <- p } return k, nil diff --git a/pkg/k8s/client_factory.go b/pkg/k8s/client_factory.go index 049575e8e..46786fc21 100644 --- a/pkg/k8s/client_factory.go +++ b/pkg/k8s/client_factory.go @@ -25,13 +25,11 @@ type ClientFactory interface { CloseIdleConnections() - Mapper() meta.ResettableRESTMapper + Mapper() meta.RESTMapper Client(wh rest.WarningHandler) (client.Client, error) DiscoveryClient() (discovery.DiscoveryInterface, error) CoreV1Client(wh rest.WarningHandler) (corev1.CoreV1Interface, error) - DynamicClient(wh rest.WarningHandler) (dynamic.Interface, error) - MetadataClient(wh rest.WarningHandler) (metadata.Interface, error) } type realClientFactory struct { @@ -51,7 +49,7 @@ func (r *realClientFactory) GetCA() []byte { return r.config.CAData } -func (r *realClientFactory) Mapper() meta.ResettableRESTMapper { +func (r *realClientFactory) Mapper() meta.RESTMapper { return r.mapper } diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index 810e495f3..f5a974eb0 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -26,7 +26,6 @@ import ( "k8s.io/client-go/discovery" _ "k8s.io/client-go/plugin/pkg/client/auth" "k8s.io/client-go/rest" - "k8s.io/client-go/restmapper" ) type K8sCluster struct { @@ -503,7 +502,13 @@ func (k *K8sCluster) GetSchemaForGVK(gvk schema.GroupVersionKind) (*uo.Unstructu } func (k *K8sCluster) ResetMapper() { - k.clientFactory.Mapper().Reset() + if m, ok := k.clientFactory.Mapper().(meta.ResettableRESTMapper); ok { + m.Reset() + } +} + +func (k *K8sCluster) ToClient() (client.Client, error) { + return k.clientFactory.Client(nil) } func (k *K8sCluster) ToRESTConfig() (*rest.Config, error) { @@ -511,11 +516,7 @@ func (k *K8sCluster) ToRESTConfig() (*rest.Config, error) { } func (k *K8sCluster) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) { - d, err := k.clientFactory.DiscoveryClient() - if err != nil { - return nil, err - } - cd, ok := d.(discovery.CachedDiscoveryInterface) + cd, ok := k.discovery.(discovery.CachedDiscoveryInterface) if !ok { return nil, fmt.Errorf("not a CachedDiscoveryInterface") } @@ -523,12 +524,5 @@ func (k *K8sCluster) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, er } func (k *K8sCluster) ToRESTMapper() (meta.RESTMapper, error) { - discoveryClient, err := k.ToDiscoveryClient() - if err != nil { - return nil, err - } - - mapper := restmapper.NewDeferredDiscoveryRESTMapper(discoveryClient) - expander := restmapper.NewShortcutExpander(mapper, discoveryClient) - return expander, nil + return k.clientFactory.Mapper(), nil } From c42efce9c6898c7ad9b9b8eb24df06219da1cf4b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 24 May 2023 09:38:33 +0200 Subject: [PATCH 1682/2916] refactor: Use meta.ExtractList --- pkg/k8s/k8s_cluster.go | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index f5a974eb0..363054b6e 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -103,20 +103,24 @@ func (k *K8sCluster) doList(l client.ObjectList, namespace string, labels map[st return nil, apiWarnings, err } - var result []*uo.UnstructuredObject - if l2, ok := l.(*unstructured.UnstructuredList); ok { - for _, o := range l2.Items { - result = append(result, uo.FromUnstructured(&o)) - } - } else if l2, ok := l.(*v1.PartialObjectMetadataList); ok { - for _, o := range l2.Items { - x, err := uo.FromStruct(&o) + items, err := meta.ExtractList(l) + if err != nil { + return nil, apiWarnings, err + } + + result := make([]*uo.UnstructuredObject, len(items)) + for i, o := range items { + if u, ok := o.(*unstructured.Unstructured); ok { + result[i] = uo.FromUnstructured(u) + } else { + x, err := uo.FromStruct(o) if err != nil { return nil, apiWarnings, err } - result = append(result, x) + result[i] = x } } + return result, apiWarnings, err } func (k *K8sCluster) ListObjects(gvk schema.GroupVersionKind, namespace string, labels map[string]string) ([]*uo.UnstructuredObject, []ApiWarning, error) { From 932a1dc08737d4a60f1094424f8841763f4bab4a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 24 May 2023 09:38:44 +0200 Subject: [PATCH 1683/2916] tests: Fix fake K8sCluster tests --- pkg/k8s/fake_client_factory.go | 172 ++++++++++++++++++++++++--------- 1 file changed, 124 insertions(+), 48 deletions(-) diff --git a/pkg/k8s/fake_client_factory.go b/pkg/k8s/fake_client_factory.go index 31abd072d..f5e191d32 100644 --- a/pkg/k8s/fake_client_factory.go +++ b/pkg/k8s/fake_client_factory.go @@ -1,6 +1,7 @@ package k8s import ( + "context" v1 "k8s.io/api/core/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/api/meta" @@ -8,26 +9,37 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/discovery" - "k8s.io/client-go/dynamic" - fake_dynamic "k8s.io/client-go/dynamic/fake" + fake4 "k8s.io/client-go/discovery/fake" + fake3 "k8s.io/client-go/dynamic/fake" "k8s.io/client-go/kubernetes/fake" corev1 "k8s.io/client-go/kubernetes/typed/core/v1" - "k8s.io/client-go/metadata" - fake_metadata "k8s.io/client-go/metadata/fake" "k8s.io/client-go/rest" + "k8s.io/client-go/restmapper" "k8s.io/client-go/testing" "sigs.k8s.io/controller-runtime/pkg/client" fake2 "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/client/interceptor" "sigs.k8s.io/kustomize/kyaml/openapi" "sigs.k8s.io/kustomize/kyaml/yaml" "strings" ) type fakeClientFactory struct { - clientSet *fake.Clientset - dynamicClient *fake_dynamic.FakeDynamicClient - metadataClient *fake_metadata.FakeMetadataClient - clientBuilder *fake2.ClientBuilder + scheme *runtime.Scheme + mapper meta.RESTMapper + objects []runtime.Object + simpleClientSet *fake.Clientset + dynamicClientSet *fake3.FakeDynamicClient + discovery discovery.DiscoveryInterface + + errors []errorEntry +} + +type errorEntry struct { + gvr schema.GroupVersionResource + name string + namespace string + retErr error } func (f *fakeClientFactory) RESTConfig() *rest.Config { @@ -41,71 +53,135 @@ func (f *fakeClientFactory) GetCA() []byte { func (f *fakeClientFactory) CloseIdleConnections() { } -func (f *fakeClientFactory) Mapper() meta.ResettableRESTMapper { - return nil +func (f *fakeClientFactory) Mapper() meta.RESTMapper { + return f.mapper } func (f *fakeClientFactory) Client(wh rest.WarningHandler) (client.Client, error) { - return f.clientBuilder.Build(), nil + return fake2.NewClientBuilder(). + WithScheme(f.scheme). + WithRESTMapper(f.mapper). + WithObjectTracker(f.dynamicClientSet.Tracker()). + WithInterceptorFuncs(f.buildErrorInterceptor()). + Build(), nil } func (f *fakeClientFactory) DiscoveryClient() (discovery.DiscoveryInterface, error) { - return f.clientSet.Discovery(), nil + return f.discovery, nil } func (f *fakeClientFactory) CoreV1Client(wh rest.WarningHandler) (corev1.CoreV1Interface, error) { - return f.clientSet.CoreV1(), nil -} - -func (f *fakeClientFactory) DynamicClient(wh rest.WarningHandler) (dynamic.Interface, error) { - return f.dynamicClient, nil -} - -func (f *fakeClientFactory) MetadataClient(wh rest.WarningHandler) (metadata.Interface, error) { - return f.metadataClient, nil + return f.simpleClientSet.CoreV1(), nil } func NewFakeClientFactory(objects ...runtime.Object) *fakeClientFactory { scheme := runtime.NewScheme() _ = v1.AddToScheme(scheme) _ = apiextensionsv1.AddToScheme(scheme) - clientSet := fake.NewSimpleClientset(objects...) - - clientSet.Fake.Resources = ConvertSchemeToAPIResources(scheme) - - dynamicClient := fake_dynamic.NewSimpleDynamicClient(scheme, objects...) - metadataClient := fake_metadata.NewSimpleMetadataClient(scheme, objects...) - - clientBuilder := fake2.NewClientBuilder(). - WithScheme(scheme). - WithRuntimeObjects(objects...) - return &fakeClientFactory{ - clientSet: clientSet, - dynamicClient: dynamicClient, - metadataClient: metadataClient, - clientBuilder: clientBuilder, + simpleClientSet := fake.NewSimpleClientset(objects...) + dynamicClientSet := fake3.NewSimpleDynamicClient(scheme, objects...) + dynamicClientSet.Fake.Resources = ConvertSchemeToAPIResources(scheme) + dc := &fake4.FakeDiscovery{Fake: &dynamicClientSet.Fake} + + agrs, _ := restmapper.GetAPIGroupResources(dc) + mapper := restmapper.NewDiscoveryRESTMapper(agrs) + + f := &fakeClientFactory{ + scheme: scheme, + mapper: mapper, + objects: objects, + simpleClientSet: simpleClientSet, + dynamicClientSet: dynamicClientSet, + discovery: dc, } + simpleClientSet.PrependReactor("*", "*", f.errorReactor) + dynamicClientSet.PrependReactor("*", "*", f.errorReactor) + return f } type HasName interface { GetName() string } -func (f *fakeClientFactory) AddError(gvr schema.GroupVersionResource, name string, namespace string, retErr error) { - f.dynamicClient.PrependReactor("*", gvr.Resource, func(action testing.Action) (handled bool, ret runtime.Object, err error) { - if namespace != "" && namespace != action.GetNamespace() { - return false, nil, nil +func (f *fakeClientFactory) findError(gvr schema.GroupVersionResource, name string, namespace string) error { + for _, ee := range f.errors { + if ee.gvr != gvr { + continue } - switch a := action.(type) { - case HasName: - if name != "" && name != a.GetName() { - return false, nil, nil - } - return true, nil, retErr - default: - return true, nil, retErr + if ee.namespace != "" && ee.namespace != namespace { + continue + } + + if ee.name != "" && ee.namespace != name { + continue } + + return ee.retErr + } + return nil +} + +func (f *fakeClientFactory) buildErrorInterceptor() interceptor.Funcs { + h := func(key *client.ObjectKey, obj runtime.Object) error { + name := "" + namespace := "" + if key != nil { + name = key.Name + namespace = key.Namespace + } else if o, ok := obj.(client.Object); ok { + name = o.GetName() + namespace = o.GetNamespace() + } + + gk := obj.GetObjectKind().GroupVersionKind().GroupKind() + rm, err := f.mapper.RESTMapping(gk, obj.GetObjectKind().GroupVersionKind().Version) + if err != nil { + return nil + } + return f.findError(rm.Resource, name, namespace) + } + + return interceptor.Funcs{ + Get: func(ctx context.Context, client client.WithWatch, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { + err := h(&key, obj) + if err != nil { + return err + } + return client.Get(ctx, key, obj) + }, + List: func(ctx context.Context, client client.WithWatch, list client.ObjectList, opts ...client.ListOption) error { + err := h(nil, list) + if err != nil { + return err + } + return client.List(ctx, list, opts...) + }, + } +} + +func (f *fakeClientFactory) errorReactor(action testing.Action) (handled bool, ret runtime.Object, err error) { + a, ok := action.(HasName) + if !ok { + return false, nil, nil + } + if err != nil { + return false, nil, nil + } + + err = f.findError(action.GetResource(), a.GetName(), action.GetNamespace()) + if err != nil { + return true, nil, err + } + return false, nil, err +} + +func (f *fakeClientFactory) AddError(gvr schema.GroupVersionResource, name string, namespace string, retErr error) { + f.errors = append(f.errors, errorEntry{ + gvr: gvr, + name: name, + namespace: namespace, + retErr: retErr, }) } From 7f8cad0b211770beb07dd710b35b1601edaebf5e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 24 May 2023 09:49:48 +0200 Subject: [PATCH 1684/2916] chore: Upgrade controller-runtime to v0.15.0 --- go.mod | 4 ++-- go.sum | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 825eafab4..005915bda 100644 --- a/go.mod +++ b/go.mod @@ -73,7 +73,7 @@ require ( github.com/sergi/go-diff v1.3.1 go.mozilla.org/sops/v3 v3.7.4-0.20220901181616-9124783930b1 sigs.k8s.io/cli-utils v0.34.0 - sigs.k8s.io/controller-runtime v0.15.0-beta.0 + sigs.k8s.io/controller-runtime v0.15.0 sigs.k8s.io/kustomize/api v0.13.4 sigs.k8s.io/yaml v1.3.0 ) @@ -229,7 +229,7 @@ require ( golang.org/x/oauth2 v0.7.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.9.1 // indirect - gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect google.golang.org/api v0.114.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230323212658-478b75c54725 // indirect diff --git a/go.sum b/go.sum index b868dcef2..55fc6f5d6 100644 --- a/go.sum +++ b/go.sum @@ -245,7 +245,6 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= @@ -1185,8 +1184,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY= -gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= +gomodules.xyz/jsonpatch/v2 v2.3.0 h1:8NFhfS6gzxNqjLIYnZxg319wZ5Qjnx4m/CcX+Klzazc= +gomodules.xyz/jsonpatch/v2 v2.3.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -1378,8 +1377,8 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/cli-utils v0.34.0 h1:zCUitt54f0/MYj/ajVFnG6XSXMhpZ72O/3RewIchW8w= sigs.k8s.io/cli-utils v0.34.0/go.mod h1:EXyMwPMu9OL+LRnj0JEMsGG/fRvbgFadcVlSnE8RhFs= -sigs.k8s.io/controller-runtime v0.15.0-beta.0 h1:pkhYMops8jZrVuI0kBHeF6q9UVu1JljIGGG4Ox5ZJmk= -sigs.k8s.io/controller-runtime v0.15.0-beta.0/go.mod h1:YUTa+du31rqOu4mJaijiuhGFax9ecCJgO/v0/yW09gE= +sigs.k8s.io/controller-runtime v0.15.0 h1:ML+5Adt3qZnMSYxZ7gAverBLNPSMQEibtzAgp0UPojU= +sigs.k8s.io/controller-runtime v0.15.0/go.mod h1:7ngYvp1MLT+9GeZ+6lH3LOlcHkp/+tzA/fmHa4iq9kk= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/kustomize/api v0.13.4 h1:E38Hfx0G9R9v7vRgKshviPotJQETG0S2gD3JdHLCAsI= From 7189adad8e89da1de8279d7e2a123fc6609e2416 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 24 May 2023 10:23:31 +0200 Subject: [PATCH 1685/2916] fix: Fix race condition in parallel discovery --- pkg/k8s/k8s_cluster.go | 7 +++++-- pkg/k8s/resources.go | 5 +++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index 363054b6e..a6991b95e 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -10,6 +10,7 @@ import ( "runtime" "sigs.k8s.io/controller-runtime/pkg/client" "strings" + "sync" "time" "github.com/Masterminds/semver/v3" @@ -35,8 +36,10 @@ type K8sCluster struct { clientFactory ClientFactory - discovery discovery.DiscoveryInterface - clients *k8sClients + discovery discovery.DiscoveryInterface + discoveryMutex sync.Mutex + + clients *k8sClients ServerVersion *version.Info } diff --git a/pkg/k8s/resources.go b/pkg/k8s/resources.go index 8295b7498..d9dce9c21 100644 --- a/pkg/k8s/resources.go +++ b/pkg/k8s/resources.go @@ -22,6 +22,11 @@ func (k *K8sCluster) doGetApiGroupResources() ([]*restmapper.APIGroupResources, // the discovery client doesn't support cancellation, so we need to run it in the background and wait for it finished := make(chan error) go func() { + // there is a race deep inside the cached discovery client + // see https://github.com/kubernetes/apimachinery/issues/156 + k.discoveryMutex.Lock() + defer k.discoveryMutex.Unlock() + var err error ret, err = restmapper.GetAPIGroupResources(k.discovery) if err != nil && !discovery.IsGroupDiscoveryFailedError(err) { From 1c653d0a34b673a6a589b99836c2b2e331754002 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 24 May 2023 11:00:18 +0200 Subject: [PATCH 1686/2916] fix: Don't copy mutex by value (#504) --- pkg/k8s/k8s_cluster.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index a6991b95e..b7b37d515 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -37,7 +37,7 @@ type K8sCluster struct { clientFactory ClientFactory discovery discovery.DiscoveryInterface - discoveryMutex sync.Mutex + discoveryMutex *sync.Mutex clients *k8sClients @@ -48,9 +48,10 @@ func NewK8sCluster(ctx context.Context, clientFactory ClientFactory, dryRun bool var err error k := &K8sCluster{ - ctx: ctx, - DryRun: dryRun, - clientFactory: clientFactory, + ctx: ctx, + DryRun: dryRun, + clientFactory: clientFactory, + discoveryMutex: &sync.Mutex{}, } k.discovery, err = clientFactory.DiscoveryClient() From b292ea24bb109aed2f09372cfe91f10518ffc305 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 May 2023 11:03:09 +0200 Subject: [PATCH 1687/2916] chore(deps): Bump github.com/go-git/go-git/v5 from 5.6.1 to 5.7.0 (#502) Bumps [github.com/go-git/go-git/v5](https://github.com/go-git/go-git) from 5.6.1 to 5.7.0. - [Release notes](https://github.com/go-git/go-git/releases) - [Commits](https://github.com/go-git/go-git/compare/v5.6.1...v5.7.0) --- updated-dependencies: - dependency-name: github.com/go-git/go-git/v5 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 10 +++++----- go.sum | 56 +++++++++++++++++++------------------------------------- 2 files changed, 24 insertions(+), 42 deletions(-) diff --git a/go.mod b/go.mod index 005915bda..0a686c573 100644 --- a/go.mod +++ b/go.mod @@ -61,7 +61,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/sts v1.19.0 github.com/aws/smithy-go v1.13.5 github.com/dimchansky/utfbom v1.1.1 - github.com/go-git/go-git/v5 v5.6.1 + github.com/go-git/go-git/v5 v5.7.0 github.com/go-logr/logr v1.2.4 github.com/google/uuid v1.3.0 github.com/hashicorp/go-multierror v1.1.1 @@ -95,7 +95,7 @@ require ( github.com/Masterminds/sprig/v3 v3.2.3 // indirect github.com/Masterminds/squirrel v1.5.3 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903 // indirect github.com/acomagu/bufpipe v1.0.4 // indirect github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3 // indirect @@ -111,7 +111,7 @@ require ( github.com/cenkalti/backoff/v3 v3.2.2 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect - github.com/cloudflare/circl v1.3.0 // indirect + github.com/cloudflare/circl v1.3.3 // indirect github.com/containerd/containerd v1.7.0 // indirect github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect @@ -131,7 +131,7 @@ require ( github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/go-errors/errors v1.4.2 // indirect - github.com/go-git/gcfg v1.5.0 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.4.1 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -205,7 +205,7 @@ require ( github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect - github.com/skeema/knownhosts v1.1.0 // indirect + github.com/skeema/knownhosts v1.1.1 // indirect github.com/spf13/afero v1.9.3 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect diff --git a/go.sum b/go.sum index 55fc6f5d6..6afa7e683 100644 --- a/go.sum +++ b/go.sum @@ -94,8 +94,8 @@ github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5 github.com/Microsoft/hcsshim v0.10.0-rc.7 h1:HBytQPxcv8Oy4244zbQbe6hnOnx544eL5QPUqhJldz8= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA= -github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= +github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903 h1:ZK3C5DtzV2nVAQTx5S5jQvMeDqWtD1By5mOoyY/xJek= +github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= github.com/a8m/expect v1.0.0/go.mod h1:4IwSCMumY49ScypDnjNbYEjgVeqy1/U2cEs3Lat96eA= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= @@ -105,7 +105,6 @@ github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= -github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= @@ -113,7 +112,6 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go-v2 v1.16.7/go.mod h1:6CpKuLXg2w7If3ABZCl/qZ6rEgwtjZTn4eAf4RcEyuw= @@ -180,8 +178,8 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= -github.com/cloudflare/circl v1.3.0 h1:Anq00jxDtoyX3+aCaYUZ0vXC5r4k4epberfWGDXV1zE= -github.com/cloudflare/circl v1.3.0/go.mod h1:+CauBF6R70Jqcyl8N2hC8pAXYbWkGIezuSbuGLtRhnw= +github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= +github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -233,6 +231,7 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4 github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3Oy0r2gRX4ui7tuhiZq2SuTtTCi0/0= github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ= github.com/emicklei/go-restful/v3 v3.10.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= @@ -268,18 +267,15 @@ github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= -github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= -github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= -github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= -github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4= github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg= -github.com/go-git/go-git-fixtures/v4 v4.3.1 h1:y5z6dd3qi8Hl+stezc8p3JxDkoTRqMAlKnXHuzrfjTQ= -github.com/go-git/go-git-fixtures/v4 v4.3.1/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo= -github.com/go-git/go-git/v5 v5.6.1 h1:q4ZRqQl4pR/ZJHc1L5CFjGA1a10u76aV1iC+nh+bHsk= -github.com/go-git/go-git/v5 v5.6.1/go.mod h1:mvyoL6Unz0PiTQrGQfSfiLFhBH1c1e84ylC2MDs4ee8= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0DHeFij3XFhoBRGUDPzSJ+w2UcK5/0JvF8DRI58r8= +github.com/go-git/go-git/v5 v5.7.0 h1:t9AudWVLmqzlo+4bqdf7GY+46SUuRsx59SboFxkq2aE= +github.com/go-git/go-git/v5 v5.7.0/go.mod h1:coJHKEOk5kUClpsNlXrUvPrDxY3w3gjHvhcZd8Fodw8= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -503,7 +499,6 @@ github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg= github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= @@ -625,7 +620,6 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/mmcloughlin/avo v0.5.0/go.mod h1:ChHFdoV7ql95Wi7vuq2YT1bwCJqiWdZrQ1im3VujLYM= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= @@ -739,7 +733,6 @@ github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFo github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= @@ -752,8 +745,8 @@ github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/skeema/knownhosts v1.1.0 h1:Wvr9V0MxhjRbl3f9nMnKnFfiWTJmtECJ9Njkea3ysW0= -github.com/skeema/knownhosts v1.1.0/go.mod h1:sKFq3RD6/TKZkSWn8boUbDC7Qkgcv+8XXijpFO6roag= +github.com/skeema/knownhosts v1.1.1 h1:MTk78x9FPgDFVFkDLTrsnnfCJl7g1C/nnKvePgrIngE= +github.com/skeema/knownhosts v1.1.1/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= @@ -871,7 +864,6 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= -golang.org/x/arch v0.1.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -886,13 +878,10 @@ golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -931,7 +920,7 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -978,12 +967,10 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1012,6 +999,7 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1077,13 +1065,10 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1094,11 +1079,10 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1113,6 +1097,7 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1177,7 +1162,7 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1307,7 +1292,6 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= @@ -1325,7 +1309,6 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= @@ -1372,7 +1355,6 @@ k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5/go.mod h1:OLgZIPagt7ERELqWJFomSt oras.land/oras-go v1.2.3 h1:v8PJl+gEAntI1pJ/LCrDgsuk+1PKVavVEPsYIHFE5uY= oras.land/oras-go v1.2.3/go.mod h1:M/uaPdYklze0Vf3AakfarnpoEckvw0ESbRdN8Z1vdJg= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/cli-utils v0.34.0 h1:zCUitt54f0/MYj/ajVFnG6XSXMhpZ72O/3RewIchW8w= From dfec936ddffec51318bf1fb125261608df1b78d6 Mon Sep 17 00:00:00 2001 From: Marcel Frehe <43266827+devMarci@users.noreply.github.com> Date: Thu, 25 May 2023 13:06:43 +0200 Subject: [PATCH 1688/2916] feat: Add ability to include custom message in barriers (#506) * feat: Add ability to include custom message in barriers * docs: Update documentation for optional custom messages in barriers * chore: remove debug code * refactor: Remove unnecessary property * refactor: Handle non-provided message property --- docs/reference/deployments/deployment-yml.md | 18 ++++++++++++++++++ pkg/deployment/utils/apply_utils.go | 6 +++++- pkg/types/deployment.go | 1 + pkg/types/zz_generated.deepcopy.go | 5 +++++ 4 files changed, 29 insertions(+), 1 deletion(-) diff --git a/docs/reference/deployments/deployment-yml.md b/docs/reference/deployments/deployment-yml.md index 45d144ad3..8689ada5a 100644 --- a/docs/reference/deployments/deployment-yml.md +++ b/docs/reference/deployments/deployment-yml.md @@ -128,6 +128,24 @@ deployments: - path: kustomizeDeployment3 ``` +To create a barrier with a custom message, include the message parameter when creating the barrier. The message parameter accepts a string value that represents the custom message. + +Example: +```yaml +deployments: +- path: kustomizeDeployment1 +- path: kustomizeDeployment2 +- include: subDeployment1 +- barrier: true + message: "Waiting for subDeployment1 to be finished" +# At this point, it's ensured that kustomizeDeployment1, kustomizeDeployment2 and all sub-deployments from +# subDeployment1 are fully deployed. +- path: kustomizeDeployment3 +``` +If no custom message is provided, the barrier will be created without a specific message, and the default behavior will be applied. + +When viewing the `kluctl deploy` status, the custom message, if provided, will be displayed along with default barrier information. + ### deleteObjects Causes kluctl to delete matching objects, specified by a list of group/kind/name/namespace dictionaries. The order/parallelization of deletion is identical to the order and parallelization of normal deployment items, diff --git a/pkg/deployment/utils/apply_utils.go b/pkg/deployment/utils/apply_utils.go index a4d928ba9..f0e323d00 100644 --- a/pkg/deployment/utils/apply_utils.go +++ b/pkg/deployment/utils/apply_utils.go @@ -699,7 +699,11 @@ func (a *ApplyDeploymentsUtil) ApplyDeployments(deployments []*deployment.Deploy barrier := d.Config.Barrier || d.Barrier if barrier { - sctx := status.StartWithOptions(a.ctx, status.WithStatus("Waiting on barrier..."), status.WithTotal(1)) + barrierMessage := "Waiting on barrier..." + if d.Config.Message != nil { + barrierMessage = fmt.Sprintf("Waiting on barrier: %s", *d.Config.Message) + } + sctx := status.StartWithOptions(a.ctx, status.WithStatus(barrierMessage), status.WithTotal(1)) wg.Wait() sctx.UpdateAndInfoFallback(fmt.Sprintf("Finished waiting")) sctx.Success() diff --git a/pkg/types/deployment.go b/pkg/types/deployment.go index 22517edab..a4f8ee8ee 100644 --- a/pkg/types/deployment.go +++ b/pkg/types/deployment.go @@ -13,6 +13,7 @@ type DeploymentItemConfig struct { Git *GitProject `json:"git,omitempty"` Tags []string `json:"tags,omitempty"` Barrier bool `json:"barrier,omitempty"` + Message *string `json:"message,omitempty"` WaitReadiness bool `json:"waitReadiness,omitempty"` Vars []*VarsSource `json:"vars,omitempty"` SkipDeleteIfTags bool `json:"skipDeleteIfTags,omitempty"` diff --git a/pkg/types/zz_generated.deepcopy.go b/pkg/types/zz_generated.deepcopy.go index 59a658fca..4f8c638e0 100644 --- a/pkg/types/zz_generated.deepcopy.go +++ b/pkg/types/zz_generated.deepcopy.go @@ -94,6 +94,11 @@ func (in *DeploymentItemConfig) DeepCopyInto(out *DeploymentItemConfig) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.Message != nil { + in, out := &in.Message, &out.Message + *out = new(string) + **out = **in + } if in.Vars != nil { in, out := &in.Vars, &out.Vars *out = make([]*VarsSource, len(*in)) From 138ae848b8d88ee195b0a6c77c7b8be61d6fb703 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 25 May 2023 13:09:07 +0200 Subject: [PATCH 1689/2916] feat: Implement migration checks for legacy flux-kluctl-controller (#507) * refactor: Get rid of ClientSet * feat: Implement migration checks for legacy flux-kluctl-controller --- cmd/kluctl/commands/cmd_controller.go | 8 --- config/rbac/role.yaml | 14 +++++ pkg/controllers/kluctl_project.go | 7 +-- .../kluctldeployment_controller.go | 53 ++++++++++++++++++- pkg/utils/uo/nested_fields.go | 20 +++++++ 5 files changed, 89 insertions(+), 13 deletions(-) diff --git a/cmd/kluctl/commands/cmd_controller.go b/cmd/kluctl/commands/cmd_controller.go index c0baaa66a..241169b7f 100644 --- a/cmd/kluctl/commands/cmd_controller.go +++ b/cmd/kluctl/commands/cmd_controller.go @@ -12,7 +12,6 @@ import ( log "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/client-go/kubernetes" clientgoscheme "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" @@ -90,12 +89,6 @@ func (cmd *controllerCmd) Run(ctx context.Context) error { restConfig.Burst = -1 } - clientSet, err := kubernetes.NewForConfig(restConfig) - if err != nil { - setupLog.Error(err, "unable to start manager") - os.Exit(1) - } - mgr, err := ctrl.NewManager(restConfig, ctrl.Options{ Scheme: cmd.scheme, MetricsBindAddress: cmd.MetricsBindAddress, @@ -130,7 +123,6 @@ func (cmd *controllerCmd) Run(ctx context.Context) error { DryRun: cmd.DryRun, RestConfig: restConfig, Client: mgr.GetClient(), - ClientSet: clientSet, Scheme: mgr.GetScheme(), EventRecorder: eventRecorder, MetricsRecorder: metricsRecorder, diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index e043f192e..14fffd85c 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -21,6 +21,20 @@ rules: verbs: - create - patch +- apiGroups: + - flux.kluctl.io + resources: + - kluctldeployments + verbs: + - get + - list + - watch +- apiGroups: + - flux.kluctl.io + resources: + - kluctldeployments/status + verbs: + - get - apiGroups: - gitops.kluctl.io resources: diff --git a/pkg/controllers/kluctl_project.go b/pkg/controllers/kluctl_project.go index d6ca5372e..7af6cda94 100644 --- a/pkg/controllers/kluctl_project.go +++ b/pkg/controllers/kluctl_project.go @@ -14,9 +14,9 @@ import ( "github.com/prometheus/client_golang/prometheus" "helm.sh/helm/v3/pkg/repo" apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "os" "path/filepath" + "sigs.k8s.io/controller-runtime/pkg/client" "strings" "time" @@ -545,12 +545,13 @@ func (pp *preparedProject) addServiceAccountBasedKeyServers(ctx context.Context, if name == "" { return nil } - sa, err := pp.r.ClientSet.CoreV1().ServiceAccounts(pp.obj.Namespace).Get(ctx, name, metav1.GetOptions{}) + var sa corev1.ServiceAccount + err := pp.r.Client.Get(ctx, client.ObjectKey{Name: name, Namespace: pp.obj.Namespace}, &sa) if err != nil { return fmt.Errorf("failed to retrieve service account %s: %w", name, err) } - ks, err := sops.BuildSopsKeyServerFromServiceAccount(ctx, pp.r.Client, sa) + ks, err := sops.BuildSopsKeyServerFromServiceAccount(ctx, pp.r.Client, &sa) if err != nil { return err } diff --git a/pkg/controllers/kluctldeployment_controller.go b/pkg/controllers/kluctldeployment_controller.go index 86dd5e118..2e6532208 100644 --- a/pkg/controllers/kluctldeployment_controller.go +++ b/pkg/controllers/kluctldeployment_controller.go @@ -2,6 +2,7 @@ package controllers import ( "context" + errors2 "errors" "fmt" "github.com/hashicorp/go-retryablehttp" kluctlv1 "github.com/kluctl/kluctl/v2/api/v1beta1" @@ -13,9 +14,13 @@ import ( "github.com/kluctl/kluctl/v2/pkg/types/result" "github.com/kluctl/kluctl/v2/pkg/utils/flux_utils/meta" "github.com/kluctl/kluctl/v2/pkg/utils/flux_utils/metrics" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "k8s.io/apimachinery/pkg/api/errors" + meta2 "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/kubernetes" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/discovery" "k8s.io/client-go/rest" kuberecorder "k8s.io/client-go/tools/record" "k8s.io/client-go/tools/reference" @@ -29,7 +34,6 @@ import ( type KluctlDeploymentReconciler struct { client.Client RestConfig *rest.Config - ClientSet *kubernetes.Clientset httpClient *retryablehttp.Client requeueDependency time.Duration Scheme *runtime.Scheme @@ -52,6 +56,8 @@ type KluctlDeploymentReconcilerOpts struct { // +kubebuilder:rbac:groups=gitops.kluctl.io,resources=kluctldeployments,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=gitops.kluctl.io,resources=kluctldeployments/status,verbs=get;update;patch // +kubebuilder:rbac:groups=gitops.kluctl.io,resources=kluctldeployments/finalizers,verbs=get;create;update;patch;delete +// +kubebuilder:rbac:groups=flux.kluctl.io,resources=kluctldeployments,verbs=get;list;watch +// +kubebuilder:rbac:groups=flux.kluctl.io,resources=kluctldeployments/status,verbs=get // +kubebuilder:rbac:groups="",resources=configmaps;secrets;serviceaccounts,verbs=get;list;watch // +kubebuilder:rbac:groups="",resources=events,verbs=create;patch @@ -94,6 +100,10 @@ func (r *KluctlDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Req return r.finalize(ctx, obj) } + if r.checkLegacyKluctlDeployment(ctx, obj) { + return ctrl.Result{}, nil + } + // Return early if the KluctlDeployment is suspended. if obj.Spec.Suspend { log.Info("Reconciliation is suspended for this object") @@ -506,6 +516,45 @@ func (r *KluctlDeploymentReconciler) doFinalize(ctx context.Context, obj *kluctl _, _ = pt.kluctlDelete(ctx, obj.Status.TargetKey.Discriminator) } +// checkLegacyKluctlDeployment checks if a legacy KluctlDeployment from the old flux.kluctl.io group is present. If yes +// we must ensure that this object is served by a recent legacy controller version which understands that it should stop +// reconciliation in case the new gitops.kluctl.io object is present +func (r *KluctlDeploymentReconciler) checkLegacyKluctlDeployment(ctx context.Context, obj *kluctlv1.KluctlDeployment) bool { + log := ctrl.LoggerFrom(ctx) + + obj2 := uo.New() + obj2.SetK8sGVK(schema.GroupVersionKind{ + Group: "flux.kluctl.io", + Version: "v1alpha1", + Kind: "KluctlDeployment", + }) + err := r.Get(ctx, client.ObjectKeyFromObject(obj), obj2.ToUnstructured()) + if err != nil { + err = errors2.Unwrap(err) + if meta2.IsNoMatchError(err) || errors.IsNotFound(err) || discovery.IsGroupDiscoveryFailedError(err) { + // legacy object not present, we're safe to continue + return false + } + log.Error(err, "Failed to retrieve legacy KluctlDeployment. Skipping reconciliation.") + // some unexpected error...we should be on the safe side and bail out reconciliation + return true + } + + readyForMigration, _, err := obj2.GetNestedBool("") + if err != nil { + // some unexpected error...we should be on the safe side and bail out reconciliation + log.Error(err, "Failed to retrieve readyForMigration value. Skipping reconciliation.") + return true + } + if !readyForMigration { + log.V(1).Info("legacy KluctlDeployment does not have the readyForMigration status set. Skipping reconciliation. " + + "Please ensure that you have upgraded to the latest version of the legacy flux-kluctl-controller and that is is still running.") + return true + } + + return false +} + func (r *KluctlDeploymentReconciler) exportDeploymentObjectToProm(obj *kluctlv1.KluctlDeployment) { pruneEnabled := 0.0 deleteEnabled := 0.0 diff --git a/pkg/utils/uo/nested_fields.go b/pkg/utils/uo/nested_fields.go index a30a992a5..da3c05758 100644 --- a/pkg/utils/uo/nested_fields.go +++ b/pkg/utils/uo/nested_fields.go @@ -270,3 +270,23 @@ func (uo *UnstructuredObject) SetNestedFieldDefault(defaultValue interface{}, ke } return uo.SetNestedField(defaultValue, keys...) } + +func (uo *UnstructuredObject) GetNestedBool(keys ...interface{}) (bool, bool, error) { + v, found, err := uo.GetNestedField(keys...) + if err != nil { + return false, false, err + } + if !found { + return false, true, nil + } + if x, ok := v.(bool); ok { + return x, true, nil + } else if x, ok := v.(*bool); ok { + if x == nil { + return false, true, nil + } + return *x, true, nil + } else { + return false, false, fmt.Errorf("field is not a bool") + } +} From 81aae0ef249582c4aedc60f2c4f516381434993b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 25 May 2023 14:31:05 +0200 Subject: [PATCH 1690/2916] fix: Cleanup config dir to use fixed name --- Makefile | 2 +- .../patches/webhook_in_kluctldeployments.yaml | 2 +- config/default/kustomization.yaml | 132 ------------------ config/default/manager_config_patch.yaml | 10 -- config/manager/manager.yaml | 24 ++-- config/prometheus/kustomization.yaml | 2 - config/prometheus/monitor.yaml | 26 ---- config/rbac/kluctldeployment_editor_role.yaml | 2 +- config/rbac/kluctldeployment_viewer_role.yaml | 2 +- config/rbac/leader_election_role.yaml | 5 +- config/rbac/leader_election_role_binding.yaml | 11 +- config/rbac/role.yaml | 2 +- config/rbac/role_binding.yaml | 12 +- config/rbac/service_account.yaml | 8 +- 14 files changed, 36 insertions(+), 204 deletions(-) delete mode 100644 config/default/manager_config_patch.yaml delete mode 100644 config/prometheus/kustomization.yaml delete mode 100644 config/prometheus/monitor.yaml diff --git a/Makefile b/Makefile index 46c182d03..bb1058f15 100644 --- a/Makefile +++ b/Makefile @@ -59,7 +59,7 @@ help: ## Display this help. .PHONY: manifests manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. - $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases + $(CONTROLLER_GEN) rbac:roleName=kluctl-controller-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases .PHONY: generate generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. diff --git a/config/crd/patches/webhook_in_kluctldeployments.yaml b/config/crd/patches/webhook_in_kluctldeployments.yaml index fa1689a1d..07d04c73e 100644 --- a/config/crd/patches/webhook_in_kluctldeployments.yaml +++ b/config/crd/patches/webhook_in_kluctldeployments.yaml @@ -9,7 +9,7 @@ spec: webhook: clientConfig: service: - namespace: system + namespace: kluctl-system name: webhook-service path: /convert conversionReviewVersions: diff --git a/config/default/kustomization.yaml b/config/default/kustomization.yaml index ce073d33f..98deb64cd 100644 --- a/config/default/kustomization.yaml +++ b/config/default/kustomization.yaml @@ -1,137 +1,5 @@ -# Adds namespace to all resources. -namespace: controller-system - -# Value of this field is prepended to the -# names of all resources, e.g. a deployment named -# "wordpress" becomes "alices-wordpress". -# Note that it should also match with the prefix (text before '-') of the namespace -# field above. -namePrefix: kluctl- - -# Labels to add to all resources and selectors. -#labels: -#- includeSelectors: true -# pairs: -# someName: someValue resources: - ../crd - ../rbac - ../manager -# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in -# crd/kustomization.yaml -#- ../webhook -# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. -#- ../certmanager -# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. -#- ../prometheus - - -# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in -# crd/kustomization.yaml -#- manager_webhook_patch.yaml - -# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. -# Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. -# 'CERTMANAGER' needs to be enabled to use ca injection -#- webhookcainjection_patch.yaml - -# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. -# Uncomment the following replacements to add the cert-manager CA injection annotations -#replacements: -# - source: # Add cert-manager annotation to ValidatingWebhookConfiguration, MutatingWebhookConfiguration and CRDs -# kind: Certificate -# group: cert-manager.io -# version: v1 -# name: serving-cert # this name should match the one in certificate.yaml -# fieldPath: .metadata.namespace # namespace of the certificate CR -# targets: -# - select: -# kind: ValidatingWebhookConfiguration -# fieldPaths: -# - .metadata.annotations.[cert-manager.io/inject-ca-from] -# options: -# delimiter: '/' -# index: 0 -# create: true -# - select: -# kind: MutatingWebhookConfiguration -# fieldPaths: -# - .metadata.annotations.[cert-manager.io/inject-ca-from] -# options: -# delimiter: '/' -# index: 0 -# create: true -# - select: -# kind: CustomResourceDefinition -# fieldPaths: -# - .metadata.annotations.[cert-manager.io/inject-ca-from] -# options: -# delimiter: '/' -# index: 0 -# create: true -# - source: -# kind: Certificate -# group: cert-manager.io -# version: v1 -# name: serving-cert # this name should match the one in certificate.yaml -# fieldPath: .metadata.name -# targets: -# - select: -# kind: ValidatingWebhookConfiguration -# fieldPaths: -# - .metadata.annotations.[cert-manager.io/inject-ca-from] -# options: -# delimiter: '/' -# index: 1 -# create: true -# - select: -# kind: MutatingWebhookConfiguration -# fieldPaths: -# - .metadata.annotations.[cert-manager.io/inject-ca-from] -# options: -# delimiter: '/' -# index: 1 -# create: true -# - select: -# kind: CustomResourceDefinition -# fieldPaths: -# - .metadata.annotations.[cert-manager.io/inject-ca-from] -# options: -# delimiter: '/' -# index: 1 -# create: true -# - source: # Add cert-manager annotation to the webhook Service -# kind: Service -# version: v1 -# name: webhook-service -# fieldPath: .metadata.name # namespace of the service -# targets: -# - select: -# kind: Certificate -# group: cert-manager.io -# version: v1 -# fieldPaths: -# - .spec.dnsNames.0 -# - .spec.dnsNames.1 -# options: -# delimiter: '.' -# index: 0 -# create: true -# - source: -# kind: Service -# version: v1 -# name: webhook-service -# fieldPath: .metadata.namespace # namespace of the service -# targets: -# - select: -# kind: Certificate -# group: cert-manager.io -# version: v1 -# fieldPaths: -# - .spec.dnsNames.0 -# - .spec.dnsNames.1 -# options: -# delimiter: '.' -# index: 1 -# create: true diff --git a/config/default/manager_config_patch.yaml b/config/default/manager_config_patch.yaml deleted file mode 100644 index f6f589169..000000000 --- a/config/default/manager_config_patch.yaml +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: controller-manager - namespace: system -spec: - template: - spec: - containers: - - name: manager diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index 0eaa207d9..60bc2cbf5 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -8,33 +8,33 @@ metadata: app.kubernetes.io/component: manager app.kubernetes.io/created-by: controller app.kubernetes.io/part-of: controller - app.kubernetes.io/managed-by: kustomize - name: system + app.kubernetes.io/managed-by: kluctl + name: kluctl-system --- apiVersion: apps/v1 kind: Deployment metadata: - name: controller-manager - namespace: system + name: kluctl-controller + namespace: kluctl-system labels: - control-plane: controller-manager + control-plane: kluctl-controller app.kubernetes.io/name: deployment - app.kubernetes.io/instance: controller-manager + app.kubernetes.io/instance: kluctl-controller app.kubernetes.io/component: manager app.kubernetes.io/created-by: controller app.kubernetes.io/part-of: controller - app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/managed-by: kluctl spec: selector: matchLabels: - control-plane: controller-manager + control-plane: kluctl-controller replicas: 1 template: metadata: annotations: kubectl.kubernetes.io/default-container: manager labels: - control-plane: controller-manager + control-plane: kluctl-controller spec: # TODO(user): Uncomment the following code to configure the nodeAffinity expression # according to the platforms which are supported by your solution. @@ -71,9 +71,9 @@ spec: - controller args: - --leader-elect - image: controller:latest + image: ghcr.io/kluctl/kluctl:latest imagePullPolicy: IfNotPresent - name: manager + name: controller securityContext: allowPrivilegeEscalation: false capabilities: @@ -100,5 +100,5 @@ spec: requests: cpu: 10m memory: 64Mi - serviceAccountName: controller-manager + serviceAccountName: kluctl-controller terminationGracePeriodSeconds: 10 diff --git a/config/prometheus/kustomization.yaml b/config/prometheus/kustomization.yaml deleted file mode 100644 index ed137168a..000000000 --- a/config/prometheus/kustomization.yaml +++ /dev/null @@ -1,2 +0,0 @@ -resources: -- monitor.yaml diff --git a/config/prometheus/monitor.yaml b/config/prometheus/monitor.yaml deleted file mode 100644 index 62e8a7ccb..000000000 --- a/config/prometheus/monitor.yaml +++ /dev/null @@ -1,26 +0,0 @@ - -# Prometheus Monitor Service (Metrics) -apiVersion: monitoring.coreos.com/v1 -kind: ServiceMonitor -metadata: - labels: - control-plane: controller-manager - app.kubernetes.io/name: servicemonitor - app.kubernetes.io/instance: controller-manager-metrics-monitor - app.kubernetes.io/component: metrics - app.kubernetes.io/created-by: controller - app.kubernetes.io/part-of: controller - app.kubernetes.io/managed-by: kustomize - name: controller-manager-metrics-monitor - namespace: system -spec: - endpoints: - - path: /metrics - port: https - scheme: https - bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token - tlsConfig: - insecureSkipVerify: true - selector: - matchLabels: - control-plane: controller-manager diff --git a/config/rbac/kluctldeployment_editor_role.yaml b/config/rbac/kluctldeployment_editor_role.yaml index 8304fdd0c..bdd62da70 100644 --- a/config/rbac/kluctldeployment_editor_role.yaml +++ b/config/rbac/kluctldeployment_editor_role.yaml @@ -8,7 +8,7 @@ metadata: app.kubernetes.io/component: rbac app.kubernetes.io/created-by: controller app.kubernetes.io/part-of: controller - app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/managed-by: kluctl name: kluctldeployment-editor-role rules: - apiGroups: diff --git a/config/rbac/kluctldeployment_viewer_role.yaml b/config/rbac/kluctldeployment_viewer_role.yaml index 50685c666..bee8b81ea 100644 --- a/config/rbac/kluctldeployment_viewer_role.yaml +++ b/config/rbac/kluctldeployment_viewer_role.yaml @@ -8,7 +8,7 @@ metadata: app.kubernetes.io/component: rbac app.kubernetes.io/created-by: controller app.kubernetes.io/part-of: controller - app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/managed-by: kluctl name: kluctldeployment-viewer-role rules: - apiGroups: diff --git a/config/rbac/leader_election_role.yaml b/config/rbac/leader_election_role.yaml index 9bf1a8ac0..4d4c486d0 100644 --- a/config/rbac/leader_election_role.yaml +++ b/config/rbac/leader_election_role.yaml @@ -8,8 +8,9 @@ metadata: app.kubernetes.io/component: rbac app.kubernetes.io/created-by: controller app.kubernetes.io/part-of: controller - app.kubernetes.io/managed-by: kustomize - name: leader-election-role + app.kubernetes.io/managed-by: kluctl + name: kluctl-controller-leader-election-role + namespace: kluctl-system rules: - apiGroups: - "" diff --git a/config/rbac/leader_election_role_binding.yaml b/config/rbac/leader_election_role_binding.yaml index d8cfaffd6..1ddbed5d3 100644 --- a/config/rbac/leader_election_role_binding.yaml +++ b/config/rbac/leader_election_role_binding.yaml @@ -7,13 +7,14 @@ metadata: app.kubernetes.io/component: rbac app.kubernetes.io/created-by: controller app.kubernetes.io/part-of: controller - app.kubernetes.io/managed-by: kustomize - name: leader-election-rolebinding + app.kubernetes.io/managed-by: kluctl + name: kluctl-controller-leader-election-rolebinding + namespace: kluctl-system roleRef: apiGroup: rbac.authorization.k8s.io kind: Role - name: leader-election-role + name: kluctl-controller-leader-election-role subjects: - kind: ServiceAccount - name: controller-manager - namespace: system + name: kluctl-controller + namespace: kluctl-system diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 14fffd85c..5f7215c78 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -2,7 +2,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - name: manager-role + name: kluctl-controller-role rules: - apiGroups: - "" diff --git a/config/rbac/role_binding.yaml b/config/rbac/role_binding.yaml index 5c650c9a8..7381f094e 100644 --- a/config/rbac/role_binding.yaml +++ b/config/rbac/role_binding.yaml @@ -3,17 +3,17 @@ kind: ClusterRoleBinding metadata: labels: app.kubernetes.io/name: clusterrolebinding - app.kubernetes.io/instance: manager-rolebinding + app.kubernetes.io/instance: kluctl-controller-rolebinding app.kubernetes.io/component: rbac app.kubernetes.io/created-by: controller app.kubernetes.io/part-of: controller - app.kubernetes.io/managed-by: kustomize - name: manager-rolebinding + app.kubernetes.io/managed-by: kluctl + name: kluctl-controller-rolebinding roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole - name: manager-role + name: kluctl-controller-role subjects: - kind: ServiceAccount - name: controller-manager - namespace: system + name: kluctl-controller + namespace: kluctl-system diff --git a/config/rbac/service_account.yaml b/config/rbac/service_account.yaml index a0e977167..b5f5dd8fc 100644 --- a/config/rbac/service_account.yaml +++ b/config/rbac/service_account.yaml @@ -3,10 +3,10 @@ kind: ServiceAccount metadata: labels: app.kubernetes.io/name: serviceaccount - app.kubernetes.io/instance: controller-manager-sa + app.kubernetes.io/instance: kluctl-controller-sa app.kubernetes.io/component: rbac app.kubernetes.io/created-by: controller app.kubernetes.io/part-of: controller - app.kubernetes.io/managed-by: kustomize - name: controller-manager - namespace: system + app.kubernetes.io/managed-by: kluctl + name: kluctl-controller + namespace: kluctl-system From 1ac87b7a1af0f3db13c39a0fbfb1b23f01a1abc2 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 25 May 2023 14:32:21 +0200 Subject: [PATCH 1691/2916] feat: Add embedded controller deployment --- .github/workflows/tests.yml | 9 + Makefile | 5 +- install/controller/.kluctl.yaml | 5 + .../controller/controller-version-patch.yaml | 0 install/controller/controller/crd.yaml | 1168 +++++++++++++++++ .../controller/controller/kustomization.yaml | 13 + install/controller/controller/manager.yaml | 75 ++ install/controller/controller/rbac.yaml | 165 +++ install/controller/deployment.yaml | 3 + install/controller/embed.go | 6 + 10 files changed, 1448 insertions(+), 1 deletion(-) create mode 100644 install/controller/.kluctl.yaml create mode 100644 install/controller/controller/controller-version-patch.yaml create mode 100644 install/controller/controller/crd.yaml create mode 100644 install/controller/controller/kustomization.yaml create mode 100644 install/controller/controller/manager.yaml create mode 100644 install/controller/controller/rbac.yaml create mode 100644 install/controller/deployment.yaml create mode 100644 install/controller/embed.go diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8559911f4..fd0338d69 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -48,6 +48,15 @@ jobs: git diff exit 1 fi + - name: Verify generated manifests are up-to-date + run: | + make manifests + if [ ! -z "$(git status --porcelain)" ]; then + echo "make manifests must be invoked and the result committed" + git status + git diff + exit 1 + fi tests: strategy: diff --git a/Makefile b/Makefile index bb1058f15..c8d2eb5a3 100644 --- a/Makefile +++ b/Makefile @@ -58,8 +58,11 @@ help: ## Display this help. ##@ Development .PHONY: manifests -manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. +manifests: controller-gen kustomize ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. $(CONTROLLER_GEN) rbac:roleName=kluctl-controller-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases + $(KUSTOMIZE) build config/crd > install/controller/controller/crd.yaml + $(KUSTOMIZE) build config/rbac > install/controller/controller/rbac.yaml + $(KUSTOMIZE) build config/manager > install/controller/controller/manager.yaml .PHONY: generate generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. diff --git a/install/controller/.kluctl.yaml b/install/controller/.kluctl.yaml new file mode 100644 index 000000000..7f7283c68 --- /dev/null +++ b/install/controller/.kluctl.yaml @@ -0,0 +1,5 @@ +discriminator: kluctl.io-controller + +args: + - name: controller_version + default: v0.0.0 \ No newline at end of file diff --git a/install/controller/controller/controller-version-patch.yaml b/install/controller/controller/controller-version-patch.yaml new file mode 100644 index 000000000..e69de29bb diff --git a/install/controller/controller/crd.yaml b/install/controller/controller/crd.yaml new file mode 100644 index 000000000..11208546d --- /dev/null +++ b/install/controller/controller/crd.yaml @@ -0,0 +1,1168 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.12.0 + name: kluctldeployments.gitops.kluctl.io +spec: + group: gitops.kluctl.io + names: + kind: KluctlDeployment + listKind: KluctlDeploymentList + plural: kluctldeployments + singular: kluctldeployment + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.dryRun + name: DryRun + type: boolean + - jsonPath: .status.lastDeployResult.command.endTime + name: Deployed + type: date + - jsonPath: .status.lastPruneResult.command.endTime + name: Pruned + type: date + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].message + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: KluctlDeployment is the Schema for the kluctldeployments API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + abortOnError: + default: false + description: ForceReplaceOnError instructs kluctl to abort deployments + immediately when something fails. Equivalent to using '--abort-on-error' + when calling kluctl. + type: boolean + args: + description: Args specifies dynamic target args. + type: object + x-kubernetes-preserve-unknown-fields: true + context: + description: If specified, overrides the context to be used. This + will effectively make kluctl ignore the context specified in the + target. + type: string + decryption: + description: Decrypt Kubernetes secrets before applying them on the + cluster. + properties: + provider: + description: Provider is the name of the decryption engine. + enum: + - sops + type: string + secretRef: + description: The secret name containing the private OpenPGP keys + used for decryption. + properties: + name: + description: Name of the referent. + type: string + required: + - name + type: object + serviceAccount: + description: ServiceAccount specifies the service account used + to authenticate against cloud providers. This is currently only + usable for AWS KMS keys. The specified service account will + be used to authenticate to AWS by signing a token in an IRSA + compliant way. + type: string + required: + - provider + type: object + delete: + default: false + description: Delete enables deletion of the specified target when + the KluctlDeployment object gets deleted. + type: boolean + deployInterval: + description: DeployInterval specifies the interval at which to deploy + the KluctlDeployment, even in cases the rendered result does not + change. + pattern: ^(([0-9]+(\.[0-9]+)?(ms|s|m|h))+)|never$ + type: string + deployMode: + default: full-deploy + description: DeployMode specifies what deploy mode should be used. + The options 'full-deploy' and 'poke-images' are supported. With + the 'poke-images' option, only images are patched into the target + without performing a full deployment. + enum: + - full-deploy + - poke-images + type: string + dryRun: + default: false + description: DryRun instructs kluctl to run everything in dry-run + mode. Equivalent to using '--dry-run' when calling kluctl. + type: boolean + excludeDeploymentDirs: + description: ExcludeDeploymentDirs instructs kluctl to exclude deployments + with the given dir. Equivalent to using '--exclude-deployment-dir' + when calling kluctl. + items: + type: string + type: array + excludeTags: + description: ExcludeTags instructs kluctl to exclude deployments with + given tags. Equivalent to using '--exclude-tag' when calling kluctl. + items: + type: string + type: array + forceApply: + default: false + description: ForceApply instructs kluctl to force-apply in case of + SSA conflicts. Equivalent to using '--force-apply' when calling + kluctl. + type: boolean + forceReplaceOnError: + default: false + description: ForceReplaceOnError instructs kluctl to force-replace + resources in case a normal replace fails. Equivalent to using '--force-replace-on-error' + when calling kluctl. + type: boolean + helmCredentials: + description: HelmCredentials is a list of Helm credentials used when + non pre-pulled Helm Charts are used inside a Kluctl deployment. + items: + properties: + secretRef: + description: 'SecretRef holds the name of a secret that contains + the Helm credentials. The secret must either contain the fields + `credentialsId` which refers to the credentialsId found in + https://kluctl.io/docs/kluctl/reference/deployments/helm/#private-chart-repositories + or an `url` used to match the credentials found in Kluctl + projects helm-chart.yaml files. The secret can either container + basic authentication credentials via `username` and `password` + or TLS authentication via `certFile` and `keyFile`. `caFile` + can be specified to override the CA to use while contacting + the repository. The secret can also contain `insecureSkipTlsVerify: + "true"`, which will disable TLS verification. `passCredentialsAll: + "true"` can be specified to make the controller pass credentials + to all requests, even if the hostname changes in-between.' + properties: + name: + description: Name of the referent. + type: string + required: + - name + type: object + type: object + type: array + images: + description: Images contains a list of fixed image overrides. Equivalent + to using '--fixed-images-file' when calling kluctl. + items: + properties: + container: + type: string + deployTags: + items: + type: string + type: array + deployedImage: + type: string + deployment: + type: string + deploymentDir: + type: string + image: + type: string + namespace: + type: string + object: + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + required: + - kind + - name + type: object + resultImage: + type: string + required: + - image + - resultImage + type: object + type: array + includeDeploymentDirs: + description: IncludeDeploymentDirs instructs kluctl to only include + deployments with the given dir. Equivalent to using '--include-deployment-dir' + when calling kluctl. + items: + type: string + type: array + includeTags: + description: IncludeTags instructs kluctl to only include deployments + with given tags. Equivalent to using '--include-tag' when calling + kluctl. + items: + type: string + type: array + interval: + description: The interval at which to reconcile the KluctlDeployment. + Reconciliation means that the deployment is fully rendered and only + deployed when the result changes compared to the last deployment. + To override this behavior, set the DeployInterval value. + pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ + type: string + kubeConfig: + description: The KubeConfig for deploying to the target cluster. Specifies + the kubeconfig to be used when invoking kluctl. Contexts in this + kubeconfig must match the context found in the kluctl target. As + an alternative, specify the context to be used via 'context' + properties: + secretRef: + description: SecretRef holds the name of a secret that contains + a key with the kubeconfig file as the value. If no key is set, + the key will default to 'value'. The secret must be in the same + namespace as the Kustomization. It is recommended that the kubeconfig + is self-contained, and the secret is regularly updated if credentials + such as a cloud-access-token expire. Cloud specific `cmd-path` + auth helpers will not function without adding binaries and credentials + to the Pod that is responsible for reconciling the KluctlDeployment. + properties: + key: + description: Key in the Secret, when not specified an implementation-specific + default key is used. + type: string + name: + description: Name of the Secret. + type: string + required: + - name + type: object + type: object + noWait: + default: false + description: NoWait instructs kluctl to not wait for any resources + to become ready, including hooks. Equivalent to using '--no-wait' + when calling kluctl. + type: boolean + prune: + default: false + description: Prune enables pruning after deploying. + type: boolean + replaceOnError: + default: false + description: ReplaceOnError instructs kluctl to replace resources + on error. Equivalent to using '--replace-on-error' when calling + kluctl. + type: boolean + retryInterval: + description: The interval at which to retry a previously failed reconciliation. + When not specified, the controller uses the Interval value to retry + failures. + pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ + type: string + serviceAccountName: + description: The name of the Kubernetes service account to use while + deploying. If not specified, the default service account is used. + type: string + source: + description: Specifies the project source location + properties: + path: + description: Path specifies the sub-directory to be used as project + directory + type: string + ref: + description: Ref specifies the branch, tag or commit that should + be used. If omitted, the default branch of the repo is used. + properties: + branch: + description: Branch to filter for. Can also be a regex. + type: string + tag: + description: Branch to filter for. Can also be a regex. + type: string + type: object + secretRef: + description: SecretRef specifies the Secret containing authentication + credentials for the git repository. For HTTPS repositories the + Secret must contain 'username' and 'password' fields. For SSH + repositories the Secret must contain 'identity' and 'known_hosts' + fields. + properties: + name: + description: Name of the referent. + type: string + required: + - name + type: object + url: + description: Url specifies the Git url where the project source + is located + type: string + required: + - url + type: object + suspend: + description: This flag tells the controller to suspend subsequent + kluctl executions, it does not apply to already started executions. + Defaults to false. + type: boolean + target: + description: Target specifies the kluctl target to deploy. If not + specified, an empty target is used that has no name and no context. + Use 'TargetName' and 'Context' to specify the name and context in + that case. + maxLength: 63 + minLength: 1 + type: string + targetNameOverride: + description: TargetNameOverride sets or overrides the target name. + This is especially useful when deployment without a target. + maxLength: 63 + minLength: 1 + type: string + timeout: + description: Timeout for all operations. Defaults to 'Interval' duration. + pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ + type: string + validate: + default: true + description: Validate enables validation after deploying + type: boolean + validateInterval: + description: ValidateInterval specifies the interval at which to validate + the KluctlDeployment. Validation is performed the same way as with + 'kluctl validate -t '. Defaults to the same value as specified + in Interval. Validate is also performed whenever a deployment is + performed, independent of the value of ValidateInterval + pattern: ^(([0-9]+(\.[0-9]+)?(ms|s|m|h))+)|never$ + type: string + required: + - interval + - source + type: object + status: + description: KluctlDeploymentStatus defines the observed state of KluctlDeployment + properties: + LastHandledDeployAt: + type: string + conditions: + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + lastDeployError: + type: string + lastDeployResult: + description: LastDeployResult is the result of the last deploy command + properties: + appliedHookObjects: + type: integer + appliedObjects: + type: integer + changedObjects: + type: integer + clusterInfo: + properties: + clusterId: + type: string + required: + - clusterId + type: object + commandInfo: + properties: + abortOnError: + type: boolean + args: + type: object + x-kubernetes-preserve-unknown-fields: true + command: + type: string + contextOverride: + type: string + dryRun: + type: boolean + endTime: + format: date-time + type: string + excludeDeploymentDirs: + items: + type: string + type: array + excludeTags: + items: + type: string + type: array + forceApply: + type: boolean + forceReplaceOnError: + type: boolean + images: + items: + properties: + container: + type: string + deployTags: + items: + type: string + type: array + deployedImage: + type: string + deployment: + type: string + deploymentDir: + type: string + image: + type: string + namespace: + type: string + object: + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + required: + - kind + - name + type: object + resultImage: + type: string + required: + - image + - resultImage + type: object + type: array + includeDeploymentDirs: + items: + type: string + type: array + includeTags: + items: + type: string + type: array + initiator: + type: string + kluctlDeployment: + properties: + gitRef: + type: string + gitUrl: + type: string + name: + type: string + namespace: + type: string + required: + - gitRef + - gitUrl + - name + - namespace + type: object + noWait: + type: boolean + replaceOnError: + type: boolean + startTime: + format: date-time + type: string + target: + type: string + targetNameOverride: + type: string + required: + - endTime + - initiator + - startTime + type: object + deletedObjects: + type: integer + errors: + type: integer + gitInfo: + properties: + commit: + type: string + dirty: + type: boolean + ref: + type: string + subDir: + type: string + url: + type: string + required: + - commit + - dirty + - ref + - subDir + - url + type: object + id: + type: string + newObjects: + type: integer + orphanObjects: + type: integer + projectKey: + properties: + gitRepoKey: + type: string + subDir: + type: string + type: object + remoteObjects: + type: integer + renderedObjects: + type: integer + target: + properties: + args: + type: object + x-kubernetes-preserve-unknown-fields: true + context: + type: string + discriminator: + type: string + images: + items: + properties: + container: + type: string + deployTags: + items: + type: string + type: array + deployedImage: + type: string + deployment: + type: string + deploymentDir: + type: string + image: + type: string + namespace: + type: string + object: + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + required: + - kind + - name + type: object + resultImage: + type: string + required: + - image + - resultImage + type: object + type: array + name: + type: string + sealingConfig: + properties: + args: + type: object + x-kubernetes-preserve-unknown-fields: true + certFile: + type: string + secretSets: + items: + type: string + type: array + type: object + required: + - name + type: object + targetKey: + properties: + clusterId: + type: string + discriminator: + type: string + targetName: + type: string + required: + - clusterId + type: object + totalChanges: + type: integer + warnings: + type: integer + required: + - appliedHookObjects + - appliedObjects + - changedObjects + - commandInfo + - deletedObjects + - errors + - id + - newObjects + - orphanObjects + - projectKey + - remoteObjects + - renderedObjects + - target + - targetKey + - totalChanges + - warnings + type: object + lastHandledReconcileAt: + description: LastHandledReconcileAt holds the value of the most recent + reconcile request value, so a change of the annotation value can + be detected. + type: string + lastObjectsHash: + type: string + lastPruneError: + type: string + lastPruneResult: + description: LastDeployResult is the result of the last prune command + properties: + appliedHookObjects: + type: integer + appliedObjects: + type: integer + changedObjects: + type: integer + clusterInfo: + properties: + clusterId: + type: string + required: + - clusterId + type: object + commandInfo: + properties: + abortOnError: + type: boolean + args: + type: object + x-kubernetes-preserve-unknown-fields: true + command: + type: string + contextOverride: + type: string + dryRun: + type: boolean + endTime: + format: date-time + type: string + excludeDeploymentDirs: + items: + type: string + type: array + excludeTags: + items: + type: string + type: array + forceApply: + type: boolean + forceReplaceOnError: + type: boolean + images: + items: + properties: + container: + type: string + deployTags: + items: + type: string + type: array + deployedImage: + type: string + deployment: + type: string + deploymentDir: + type: string + image: + type: string + namespace: + type: string + object: + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + required: + - kind + - name + type: object + resultImage: + type: string + required: + - image + - resultImage + type: object + type: array + includeDeploymentDirs: + items: + type: string + type: array + includeTags: + items: + type: string + type: array + initiator: + type: string + kluctlDeployment: + properties: + gitRef: + type: string + gitUrl: + type: string + name: + type: string + namespace: + type: string + required: + - gitRef + - gitUrl + - name + - namespace + type: object + noWait: + type: boolean + replaceOnError: + type: boolean + startTime: + format: date-time + type: string + target: + type: string + targetNameOverride: + type: string + required: + - endTime + - initiator + - startTime + type: object + deletedObjects: + type: integer + errors: + type: integer + gitInfo: + properties: + commit: + type: string + dirty: + type: boolean + ref: + type: string + subDir: + type: string + url: + type: string + required: + - commit + - dirty + - ref + - subDir + - url + type: object + id: + type: string + newObjects: + type: integer + orphanObjects: + type: integer + projectKey: + properties: + gitRepoKey: + type: string + subDir: + type: string + type: object + remoteObjects: + type: integer + renderedObjects: + type: integer + target: + properties: + args: + type: object + x-kubernetes-preserve-unknown-fields: true + context: + type: string + discriminator: + type: string + images: + items: + properties: + container: + type: string + deployTags: + items: + type: string + type: array + deployedImage: + type: string + deployment: + type: string + deploymentDir: + type: string + image: + type: string + namespace: + type: string + object: + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + required: + - kind + - name + type: object + resultImage: + type: string + required: + - image + - resultImage + type: object + type: array + name: + type: string + sealingConfig: + properties: + args: + type: object + x-kubernetes-preserve-unknown-fields: true + certFile: + type: string + secretSets: + items: + type: string + type: array + type: object + required: + - name + type: object + targetKey: + properties: + clusterId: + type: string + discriminator: + type: string + targetName: + type: string + required: + - clusterId + type: object + totalChanges: + type: integer + warnings: + type: integer + required: + - appliedHookObjects + - appliedObjects + - changedObjects + - commandInfo + - deletedObjects + - errors + - id + - newObjects + - orphanObjects + - projectKey + - remoteObjects + - renderedObjects + - target + - targetKey + - totalChanges + - warnings + type: object + lastValidateError: + type: string + lastValidateResult: + description: LastValidateResult is the result of the last validate + command + properties: + drift: + items: + properties: + changes: + items: + properties: + jsonPath: + type: string + newValue: + x-kubernetes-preserve-unknown-fields: true + oldValue: + x-kubernetes-preserve-unknown-fields: true + type: + type: string + unifiedDiff: + type: string + required: + - jsonPath + - type + type: object + type: array + ref: + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + required: + - kind + - name + type: object + required: + - ref + type: object + type: array + endTime: + format: date-time + type: string + errors: + items: + properties: + message: + type: string + ref: + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + required: + - kind + - name + type: object + required: + - message + - ref + type: object + type: array + id: + type: string + ready: + type: boolean + results: + items: + properties: + annotation: + type: string + message: + type: string + ref: + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + required: + - kind + - name + type: object + required: + - annotation + - message + - ref + type: object + type: array + startTime: + format: date-time + type: string + warnings: + items: + properties: + message: + type: string + ref: + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + required: + - kind + - name + type: object + required: + - message + - ref + type: object + type: array + required: + - endTime + - id + - ready + - startTime + type: object + observedCommit: + description: ObservedCommit is the last commit observed + type: string + observedGeneration: + description: ObservedGeneration is the last reconciled generation. + format: int64 + type: integer + projectKey: + properties: + gitRepoKey: + type: string + subDir: + type: string + type: object + targetKey: + properties: + clusterId: + type: string + discriminator: + type: string + targetName: + type: string + required: + - clusterId + type: object + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/install/controller/controller/kustomization.yaml b/install/controller/controller/kustomization.yaml new file mode 100644 index 000000000..4c41784cc --- /dev/null +++ b/install/controller/controller/kustomization.yaml @@ -0,0 +1,13 @@ +resources: + - crd.yaml + - manager.yaml + - rbac.yaml + +patches: + - target: + kind: Deployment + name: kluctl-controller + patch: |- + - op: add + path: /spec/template/spec/containers/0/image + value: ghcr.io/kluctl/kluctl:{{ args.controller_version }} diff --git a/install/controller/controller/manager.yaml b/install/controller/controller/manager.yaml new file mode 100644 index 000000000..02f78bc76 --- /dev/null +++ b/install/controller/controller/manager.yaml @@ -0,0 +1,75 @@ +apiVersion: v1 +kind: Namespace +metadata: + labels: + app.kubernetes.io/component: manager + app.kubernetes.io/created-by: controller + app.kubernetes.io/instance: system + app.kubernetes.io/managed-by: kluctl + app.kubernetes.io/name: namespace + app.kubernetes.io/part-of: controller + control-plane: controller-manager + name: kluctl-system +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app.kubernetes.io/component: manager + app.kubernetes.io/created-by: controller + app.kubernetes.io/instance: kluctl-controller + app.kubernetes.io/managed-by: kluctl + app.kubernetes.io/name: deployment + app.kubernetes.io/part-of: controller + control-plane: kluctl-controller + name: kluctl-controller + namespace: kluctl-system +spec: + replicas: 1 + selector: + matchLabels: + control-plane: kluctl-controller + template: + metadata: + annotations: + kubectl.kubernetes.io/default-container: manager + labels: + control-plane: kluctl-controller + spec: + containers: + - args: + - --leader-elect + command: + - kluctl + - controller + image: ghcr.io/kluctl/kluctl:latest + imagePullPolicy: IfNotPresent + livenessProbe: + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 15 + periodSeconds: 20 + name: controller + readinessProbe: + httpGet: + path: /readyz + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 10 + resources: + limits: + cpu: 500m + memory: 128Mi + requests: + cpu: 10m + memory: 64Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + securityContext: + runAsNonRoot: true + serviceAccountName: kluctl-controller + terminationGracePeriodSeconds: 10 diff --git a/install/controller/controller/rbac.yaml b/install/controller/controller/rbac.yaml new file mode 100644 index 000000000..1845b4548 --- /dev/null +++ b/install/controller/controller/rbac.yaml @@ -0,0 +1,165 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: controller + app.kubernetes.io/instance: kluctl-controller-sa + app.kubernetes.io/managed-by: kluctl + app.kubernetes.io/name: serviceaccount + app.kubernetes.io/part-of: controller + name: kluctl-controller + namespace: kluctl-system +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + labels: + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: controller + app.kubernetes.io/instance: leader-election-role + app.kubernetes.io/managed-by: kluctl + app.kubernetes.io/name: role + app.kubernetes.io/part-of: controller + name: kluctl-controller-leader-election-role + namespace: kluctl-system +rules: +- apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: kluctl-controller-role +rules: +- apiGroups: + - "" + resources: + - configmaps + - secrets + - serviceaccounts + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +- apiGroups: + - flux.kluctl.io + resources: + - kluctldeployments + verbs: + - get + - list + - watch +- apiGroups: + - flux.kluctl.io + resources: + - kluctldeployments/status + verbs: + - get +- apiGroups: + - gitops.kluctl.io + resources: + - kluctldeployments + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - gitops.kluctl.io + resources: + - kluctldeployments/finalizers + verbs: + - create + - delete + - get + - patch + - update +- apiGroups: + - gitops.kluctl.io + resources: + - kluctldeployments/status + verbs: + - get + - patch + - update +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: controller + app.kubernetes.io/instance: leader-election-rolebinding + app.kubernetes.io/managed-by: kluctl + app.kubernetes.io/name: rolebinding + app.kubernetes.io/part-of: controller + name: kluctl-controller-leader-election-rolebinding + namespace: kluctl-system +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: kluctl-controller-leader-election-role +subjects: +- kind: ServiceAccount + name: kluctl-controller + namespace: kluctl-system +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: controller + app.kubernetes.io/instance: kluctl-controller-rolebinding + app.kubernetes.io/managed-by: kluctl + app.kubernetes.io/name: clusterrolebinding + app.kubernetes.io/part-of: controller + name: kluctl-controller-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: kluctl-controller-role +subjects: +- kind: ServiceAccount + name: kluctl-controller + namespace: kluctl-system diff --git a/install/controller/deployment.yaml b/install/controller/deployment.yaml new file mode 100644 index 000000000..1ab7a6d3e --- /dev/null +++ b/install/controller/deployment.yaml @@ -0,0 +1,3 @@ + +deployments: + - path: controller diff --git a/install/controller/embed.go b/install/controller/embed.go new file mode 100644 index 000000000..3bf3cd004 --- /dev/null +++ b/install/controller/embed.go @@ -0,0 +1,6 @@ +package controller + +import "embed" + +//go:embed all:* +var Config embed.FS From 201449734ad6189499e3a2e1a5fbf887a3840aa8 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 25 May 2023 14:33:51 +0200 Subject: [PATCH 1692/2916] chore: Add prepeare-release.sh script --- hack/prepare-release.sh | 45 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100755 hack/prepare-release.sh diff --git a/hack/prepare-release.sh b/hack/prepare-release.sh new file mode 100755 index 000000000..609c006d5 --- /dev/null +++ b/hack/prepare-release.sh @@ -0,0 +1,45 @@ +#!/bin/sh + +set -e + +VERSION=$1 + +VERSION_REGEX='v([0-9]*)\.([0-9]*)\.([0-9]*)' +VERSION_REGEX_SED='v\([0-9]*\)\.\([0-9]*\)\.\([0-9]*\)' + +if [ ! -z "$(git status --porcelain)" ]; then + echo "working directory is dirty!" + exit 1 +fi + +if [ -z "$VERSION" ]; then + echo "No version specified, using 'git sv next-version'" + VERSION=v$(git sv next-version) +fi + +if [[ ! ($VERSION =~ $VERSION_REGEX) ]]; then + echo "version is invalid" + exit 1 +fi + +echo VERSION=$VERSION + +FILES="install/controller/.kluctl.yaml" + +for f in $FILES; do + cat $f | sed "s/$VERSION_REGEX_SED/$VERSION/g" > $f.tmp + mv $f.tmp $f + + git add $f +done + +if [ -z "$(git status --porcelain)" ]; then + echo "nothing has changed!" + exit 1 +fi + +echo "committing" +git commit -o -m "build: Preparing release $VERSION" -- $FILES + +echo "tagging" +git tag -f $VERSION From 47bf5a0b4bcbda41536f8524edfc7e26c2ab32b1 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 25 May 2023 14:37:00 +0200 Subject: [PATCH 1693/2916] ci: Increment minor version instead of patch version when building snapshots --- .goreleaser.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 8d657a092..ba0d196f9 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -52,7 +52,7 @@ sboms: checksum: name_template: '{{ .ProjectName }}_v{{ .Version }}_checksums.txt' snapshot: - name_template: "{{ incpatch .Version }}-next" + name_template: "{{ incminor .Version }}-next" changelog: sort: asc filters: From 29e4b21cef922c971c536799610b5b7c08a7fab0 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 25 May 2023 14:43:50 +0200 Subject: [PATCH 1694/2916] feat: Move "controller" sub-command to "controller run" --- cmd/kluctl/commands/cmd_controller.go | 217 +------------------- cmd/kluctl/commands/cmd_controller_run.go | 220 +++++++++++++++++++++ cmd/kluctl/commands/cobra_utils.go | 5 +- cmd/kluctl/commands/root.go | 2 +- config/manager/manager.yaml | 1 + e2e/gitops_test.go | 1 + install/controller/controller/manager.yaml | 1 + 7 files changed, 229 insertions(+), 218 deletions(-) create mode 100644 cmd/kluctl/commands/cmd_controller_run.go diff --git a/cmd/kluctl/commands/cmd_controller.go b/cmd/kluctl/commands/cmd_controller.go index 241169b7f..508c75220 100644 --- a/cmd/kluctl/commands/cmd_controller.go +++ b/cmd/kluctl/commands/cmd_controller.go @@ -1,220 +1,5 @@ package commands -import ( - "context" - "fmt" - kluctlv1 "github.com/kluctl/kluctl/v2/api/v1beta1" - "github.com/kluctl/kluctl/v2/cmd/kluctl/args" - "github.com/kluctl/kluctl/v2/pkg/controllers" - ssh_pool "github.com/kluctl/kluctl/v2/pkg/git/ssh-pool" - "github.com/kluctl/kluctl/v2/pkg/results" - "github.com/kluctl/kluctl/v2/pkg/utils/flux_utils/metrics" - log "github.com/sirupsen/logrus" - "k8s.io/apimachinery/pkg/runtime" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" - clientgoscheme "k8s.io/client-go/kubernetes/scheme" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" - clientcmdapi "k8s.io/client-go/tools/clientcmd/api" - "os" - "os/user" - "path/filepath" - "sigs.k8s.io/cli-utils/pkg/flowcontrol" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/healthz" - "sigs.k8s.io/controller-runtime/pkg/log/zap" - crtlmetrics "sigs.k8s.io/controller-runtime/pkg/metrics" -) - -var ( - setupLog = ctrl.Log.WithName("setup") -) - -const controllerName = "kluctl-controller" - type controllerCmd struct { - scheme *runtime.Scheme - - Kubeconfig string `group:"controller" help:"Override the kubeconfig to use."` - Context string `group:"controller" help:"Override the context to use."` - - MetricsBindAddress string `group:"controller" help:"The address the metric endpoint binds to." default:":8080"` - HealthProbeBindAddress string `group:"controller" help:"The address the probe endpoint binds to." default:":8081"` - LeaderElect bool `group:"controller" help:"Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager."` - - DefaultServiceAccount string `group:"controller" help:"Default service account used for impersonation."` - DryRun bool `group:"controller" help:"Run all deployments in dryRun=true mode."` - - args.CommandResultFlags -} - -func (cmd *controllerCmd) Help() string { - return `This command will run the Kluctl Controller. This is usually meant to be run inside a cluster and not from your local machine. -` -} - -func (cmd *controllerCmd) initScheme() { - cmd.scheme = runtime.NewScheme() - scheme := cmd.scheme - - utilruntime.Must(clientgoscheme.AddToScheme(scheme)) - - utilruntime.Must(kluctlv1.AddToScheme(scheme)) - - //+kubebuilder:scaffold:scheme -} - -func (cmd *controllerCmd) Run(ctx context.Context) error { - cmd.initScheme() - - metricsRecorder := metrics.NewRecorder() - crtlmetrics.Registry.MustRegister(metricsRecorder.Collectors()...) - - opts := zap.Options{ - Development: true, - } - ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) - - restConfig, err := cmd.loadConfig(cmd.Kubeconfig, cmd.Context) - if err != nil { - setupLog.Error(err, "unable to load kubeconfig") - os.Exit(1) - } - - enabled, err := flowcontrol.IsEnabled(context.Background(), restConfig) - if err == nil && enabled { - // A negative QPS and Burst indicates that the client should not have a rate limiter. - // Ref: https://github.com/kubernetes/kubernetes/blob/v1.24.0/staging/src/k8s.io/client-go/rest/config.go#L354-L364 - restConfig.QPS = -1 - restConfig.Burst = -1 - } - - mgr, err := ctrl.NewManager(restConfig, ctrl.Options{ - Scheme: cmd.scheme, - MetricsBindAddress: cmd.MetricsBindAddress, - Port: 9443, - HealthProbeBindAddress: cmd.HealthProbeBindAddress, - LeaderElection: cmd.LeaderElect, - LeaderElectionID: "5ab5d0f9.kluctl.io", - // LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily - // when the Manager ends. This requires the binary to immediately end when the - // Manager is stopped, otherwise, this setting is unsafe. Setting this significantly - // speeds up voluntary leader transitions as the new leader don't have to wait - // LeaseDuration time first. - // - // In the default scaffold provided, the program ends immediately after - // the manager stops, so would be fine to enable this option. However, - // if you are doing or is intended to do any operation such as perform cleanups - // after the manager stops then its usage might be unsafe. - // LeaderElectionReleaseOnCancel: true, - }) - if err != nil { - setupLog.Error(err, "unable to start manager") - os.Exit(1) - } - - eventRecorder := mgr.GetEventRecorderFor(controllerName) - - sshPool := &ssh_pool.SshPool{} - - r := controllers.KluctlDeploymentReconciler{ - ControllerName: controllerName, - DefaultServiceAccount: cmd.DefaultServiceAccount, - DryRun: cmd.DryRun, - RestConfig: restConfig, - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - EventRecorder: eventRecorder, - MetricsRecorder: metricsRecorder, - SshPool: sshPool, - } - - if cmd.WriteCommandResult { - resultStore, err := results.NewResultStoreSecrets(ctx, mgr.GetClient(), mgr.GetCache(), cmd.CommandResultNamespace, cmd.KeepCommandResultsCount) - if err != nil { - return err - } - r.ResultStore = resultStore - } - - if err = r.SetupWithManager(ctx, mgr, controllers.KluctlDeploymentReconcilerOpts{ - HTTPRetry: 9, - }); err != nil { - setupLog.Error(err, "unable to create controller", "controller", kluctlv1.KluctlDeploymentKind) - os.Exit(1) - } - - //+kubebuilder:scaffold:builder - - if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { - setupLog.Error(err, "unable to set up health check") - os.Exit(1) - } - if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { - setupLog.Error(err, "unable to set up ready check") - os.Exit(1) - } - - setupLog.Info("starting manager") - if err := mgr.Start(ctx); err != nil { - setupLog.Error(err, "problem running manager") - os.Exit(1) - } - - return nil -} - -// taken from clientcmd -func (cmd *controllerCmd) loadConfig(kubeconfig string, context string) (config *rest.Config, configErr error) { - // If a flag is specified with the config location, use that - if len(kubeconfig) > 0 { - return cmd.loadConfigWithContext("", &clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfig}, context) - } - - // If the recommended kubeconfig env variable is not specified, - // try the in-cluster config. - kubeconfigPath := os.Getenv(clientcmd.RecommendedConfigPathEnvVar) - if len(kubeconfigPath) == 0 { - c, err := rest.InClusterConfig() - if err == nil { - return c, nil - } - - defer func() { - if configErr != nil { - log.Error(err, "unable to load in-cluster config") - } - }() - } - - // If the recommended kubeconfig env variable is set, or there - // is no in-cluster config, try the default recommended locations. - // - // NOTE: For default config file locations, upstream only checks - // $HOME for the user's home directory, but we can also try - // os/user.HomeDir when $HOME is unset. - // - // TODO(jlanford): could this be done upstream? - loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() - if _, ok := os.LookupEnv("HOME"); !ok { - u, err := user.Current() - if err != nil { - return nil, fmt.Errorf("could not get current user: %w", err) - } - loadingRules.Precedence = append(loadingRules.Precedence, filepath.Join(u.HomeDir, clientcmd.RecommendedHomeDir, clientcmd.RecommendedFileName)) - } - - return cmd.loadConfigWithContext("", loadingRules, context) -} - -// taken from clientcmd -func (cmd *controllerCmd) loadConfigWithContext(apiServerURL string, loader clientcmd.ClientConfigLoader, context string) (*rest.Config, error) { - return clientcmd.NewNonInteractiveDeferredLoadingClientConfig( - loader, - &clientcmd.ConfigOverrides{ - ClusterInfo: clientcmdapi.Cluster{ - Server: apiServerURL, - }, - CurrentContext: context, - }).ClientConfig() + Run_ controllerRunCmd `cmd:"run" help:"Run the Kluctl controller"` } diff --git a/cmd/kluctl/commands/cmd_controller_run.go b/cmd/kluctl/commands/cmd_controller_run.go new file mode 100644 index 000000000..6d9cb40b2 --- /dev/null +++ b/cmd/kluctl/commands/cmd_controller_run.go @@ -0,0 +1,220 @@ +package commands + +import ( + "context" + "fmt" + kluctlv1 "github.com/kluctl/kluctl/v2/api/v1beta1" + "github.com/kluctl/kluctl/v2/cmd/kluctl/args" + "github.com/kluctl/kluctl/v2/pkg/controllers" + ssh_pool "github.com/kluctl/kluctl/v2/pkg/git/ssh-pool" + "github.com/kluctl/kluctl/v2/pkg/results" + "github.com/kluctl/kluctl/v2/pkg/utils/flux_utils/metrics" + log "github.com/sirupsen/logrus" + "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" + "os" + "os/user" + "path/filepath" + "sigs.k8s.io/cli-utils/pkg/flowcontrol" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/healthz" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + crtlmetrics "sigs.k8s.io/controller-runtime/pkg/metrics" +) + +var ( + setupLog = ctrl.Log.WithName("setup") +) + +const controllerName = "kluctl-controller" + +type controllerRunCmd struct { + scheme *runtime.Scheme + + Kubeconfig string `group:"controller" help:"Override the kubeconfig to use."` + Context string `group:"controller" help:"Override the context to use."` + + MetricsBindAddress string `group:"controller" help:"The address the metric endpoint binds to." default:":8080"` + HealthProbeBindAddress string `group:"controller" help:"The address the probe endpoint binds to." default:":8081"` + LeaderElect bool `group:"controller" help:"Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager."` + + DefaultServiceAccount string `group:"controller" help:"Default service account used for impersonation."` + DryRun bool `group:"controller" help:"Run all deployments in dryRun=true mode."` + + args.CommandResultFlags +} + +func (cmd *controllerRunCmd) Help() string { + return `This command will run the Kluctl Controller. This is usually meant to be run inside a cluster and not from your local machine. +` +} + +func (cmd *controllerRunCmd) initScheme() { + cmd.scheme = runtime.NewScheme() + scheme := cmd.scheme + + utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + + utilruntime.Must(kluctlv1.AddToScheme(scheme)) + + //+kubebuilder:scaffold:scheme +} + +func (cmd *controllerRunCmd) Run(ctx context.Context) error { + cmd.initScheme() + + metricsRecorder := metrics.NewRecorder() + crtlmetrics.Registry.MustRegister(metricsRecorder.Collectors()...) + + opts := zap.Options{ + Development: true, + } + ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) + + restConfig, err := cmd.loadConfig(cmd.Kubeconfig, cmd.Context) + if err != nil { + setupLog.Error(err, "unable to load kubeconfig") + os.Exit(1) + } + + enabled, err := flowcontrol.IsEnabled(context.Background(), restConfig) + if err == nil && enabled { + // A negative QPS and Burst indicates that the client should not have a rate limiter. + // Ref: https://github.com/kubernetes/kubernetes/blob/v1.24.0/staging/src/k8s.io/client-go/rest/config.go#L354-L364 + restConfig.QPS = -1 + restConfig.Burst = -1 + } + + mgr, err := ctrl.NewManager(restConfig, ctrl.Options{ + Scheme: cmd.scheme, + MetricsBindAddress: cmd.MetricsBindAddress, + Port: 9443, + HealthProbeBindAddress: cmd.HealthProbeBindAddress, + LeaderElection: cmd.LeaderElect, + LeaderElectionID: "5ab5d0f9.kluctl.io", + // LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily + // when the Manager ends. This requires the binary to immediately end when the + // Manager is stopped, otherwise, this setting is unsafe. Setting this significantly + // speeds up voluntary leader transitions as the new leader don't have to wait + // LeaseDuration time first. + // + // In the default scaffold provided, the program ends immediately after + // the manager stops, so would be fine to enable this option. However, + // if you are doing or is intended to do any operation such as perform cleanups + // after the manager stops then its usage might be unsafe. + // LeaderElectionReleaseOnCancel: true, + }) + if err != nil { + setupLog.Error(err, "unable to start manager") + os.Exit(1) + } + + eventRecorder := mgr.GetEventRecorderFor(controllerName) + + sshPool := &ssh_pool.SshPool{} + + r := controllers.KluctlDeploymentReconciler{ + ControllerName: controllerName, + DefaultServiceAccount: cmd.DefaultServiceAccount, + DryRun: cmd.DryRun, + RestConfig: restConfig, + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + EventRecorder: eventRecorder, + MetricsRecorder: metricsRecorder, + SshPool: sshPool, + } + + if cmd.WriteCommandResult { + resultStore, err := results.NewResultStoreSecrets(ctx, mgr.GetClient(), mgr.GetCache(), cmd.CommandResultNamespace, cmd.KeepCommandResultsCount) + if err != nil { + return err + } + r.ResultStore = resultStore + } + + if err = r.SetupWithManager(ctx, mgr, controllers.KluctlDeploymentReconcilerOpts{ + HTTPRetry: 9, + }); err != nil { + setupLog.Error(err, "unable to create controller", "controller", kluctlv1.KluctlDeploymentKind) + os.Exit(1) + } + + //+kubebuilder:scaffold:builder + + if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up health check") + os.Exit(1) + } + if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up ready check") + os.Exit(1) + } + + setupLog.Info("starting manager") + if err := mgr.Start(ctx); err != nil { + setupLog.Error(err, "problem running manager") + os.Exit(1) + } + + return nil +} + +// taken from clientcmd +func (cmd *controllerRunCmd) loadConfig(kubeconfig string, context string) (config *rest.Config, configErr error) { + // If a flag is specified with the config location, use that + if len(kubeconfig) > 0 { + return cmd.loadConfigWithContext("", &clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfig}, context) + } + + // If the recommended kubeconfig env variable is not specified, + // try the in-cluster config. + kubeconfigPath := os.Getenv(clientcmd.RecommendedConfigPathEnvVar) + if len(kubeconfigPath) == 0 { + c, err := rest.InClusterConfig() + if err == nil { + return c, nil + } + + defer func() { + if configErr != nil { + log.Error(err, "unable to load in-cluster config") + } + }() + } + + // If the recommended kubeconfig env variable is set, or there + // is no in-cluster config, try the default recommended locations. + // + // NOTE: For default config file locations, upstream only checks + // $HOME for the user's home directory, but we can also try + // os/user.HomeDir when $HOME is unset. + // + // TODO(jlanford): could this be done upstream? + loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() + if _, ok := os.LookupEnv("HOME"); !ok { + u, err := user.Current() + if err != nil { + return nil, fmt.Errorf("could not get current user: %w", err) + } + loadingRules.Precedence = append(loadingRules.Precedence, filepath.Join(u.HomeDir, clientcmd.RecommendedHomeDir, clientcmd.RecommendedFileName)) + } + + return cmd.loadConfigWithContext("", loadingRules, context) +} + +// taken from clientcmd +func (cmd *controllerRunCmd) loadConfigWithContext(apiServerURL string, loader clientcmd.ClientConfigLoader, context string) (*rest.Config, error) { + return clientcmd.NewNonInteractiveDeferredLoadingClientConfig( + loader, + &clientcmd.ConfigOverrides{ + ClusterInfo: clientcmdapi.Cluster{ + Server: apiServerURL, + }, + CurrentContext: context, + }).ClientConfig() +} diff --git a/cmd/kluctl/commands/cobra_utils.go b/cmd/kluctl/commands/cobra_utils.go index d4b4fb013..de630a332 100644 --- a/cmd/kluctl/commands/cobra_utils.go +++ b/cmd/kluctl/commands/cobra_utils.go @@ -106,7 +106,10 @@ func (c *rootCommand) buildCobraSubCommands(cg *commandAndGroups, cmdStruct inte v2 := v.Field(i).Addr().Interface() name := buildCobraName(f.Name) - if _, ok := f.Tag.Lookup("cmd"); ok { + if cmd, ok := f.Tag.Lookup("cmd"); ok { + if cmd != "" { + name = cmd + } // subcommand shortHelp := f.Tag.Get("help") longHelp := "" diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index 74559d894..14b15c02e 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -66,7 +66,7 @@ type cli struct { Render renderCmd `cmd:"" help:"Renders all resources and configuration files"` Seal sealCmd `cmd:"" help:"Seal secrets based on target's sealingConfig"` Validate validateCmd `cmd:"" help:"Validates the already deployed deployment"` - Controller controllerCmd `cmd:"" help:"Run the Kluctl controller"` + Controller controllerCmd `cmd:"" help:"Kluctl controller sub-commands"` Version versionCmd `cmd:"" help:"Print kluctl version"` } diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index 60bc2cbf5..c1f1d3e68 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -69,6 +69,7 @@ spec: - command: - kluctl - controller + - run args: - --leader-elect image: ghcr.io/kluctl/kluctl:latest diff --git a/e2e/gitops_test.go b/e2e/gitops_test.go index 1df30c7fa..edb46e916 100644 --- a/e2e/gitops_test.go +++ b/e2e/gitops_test.go @@ -96,6 +96,7 @@ func (suite *GitopsTestSuite) startController() { ctx, ctxCancel := context.WithCancel(context.Background()) args := []string{ "controller", + "run", "--kubeconfig", tmpKubeconfig, "--context", diff --git a/install/controller/controller/manager.yaml b/install/controller/controller/manager.yaml index 02f78bc76..7cee4bf75 100644 --- a/install/controller/controller/manager.yaml +++ b/install/controller/controller/manager.yaml @@ -42,6 +42,7 @@ spec: command: - kluctl - controller + - run image: ghcr.io/kluctl/kluctl:latest imagePullPolicy: IfNotPresent livenessProbe: From f6e30a1129cc6a4191eccaa30f0a07cae23d3d9e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 26 May 2023 00:16:54 +0200 Subject: [PATCH 1695/2916] feat: Add controller install sub-command --- cmd/kluctl/commands/cmd_controller.go | 3 +- cmd/kluctl/commands/cmd_controller_install.go | 54 +++++++++++++++++++ cmd/kluctl/commands/cmd_deploy.go | 3 ++ cmd/kluctl/commands/cmd_list_targets.go | 2 +- cmd/kluctl/commands/cmd_seal.go | 2 +- cmd/kluctl/commands/completion.go | 2 +- cmd/kluctl/commands/utils.go | 14 +++-- install/controller/embed.go | 2 +- pkg/kluctl_project/project.go | 3 +- pkg/kluctl_project/project_load.go | 10 ++-- 10 files changed, 79 insertions(+), 16 deletions(-) create mode 100644 cmd/kluctl/commands/cmd_controller_install.go diff --git a/cmd/kluctl/commands/cmd_controller.go b/cmd/kluctl/commands/cmd_controller.go index 508c75220..5a49c5187 100644 --- a/cmd/kluctl/commands/cmd_controller.go +++ b/cmd/kluctl/commands/cmd_controller.go @@ -1,5 +1,6 @@ package commands type controllerCmd struct { - Run_ controllerRunCmd `cmd:"run" help:"Run the Kluctl controller"` + Install controllerInstallCmd `cmd:"" help:"Install the Kluctl controller"` + Run_ controllerRunCmd `cmd:"run" help:"Run the Kluctl controller"` } diff --git a/cmd/kluctl/commands/cmd_controller_install.go b/cmd/kluctl/commands/cmd_controller_install.go new file mode 100644 index 000000000..319a0f496 --- /dev/null +++ b/cmd/kluctl/commands/cmd_controller_install.go @@ -0,0 +1,54 @@ +package commands + +import ( + "context" + "fmt" + "github.com/kluctl/go-embed-python/embed_util" + "github.com/kluctl/kluctl/v2/cmd/kluctl/args" + "github.com/kluctl/kluctl/v2/install/controller" + "time" +) + +type controllerInstallCmd struct { + args.YesFlags + args.DryRunFlags + args.CommandResultFlags + + Context string `group:"controller" help:"Override the context to use."` + ControllerVersion string `group:"controller" help:"Specify the controller version to install."` +} + +func (cmd *controllerInstallCmd) Help() string { + return `This command will install the kluctl-controller to the current Kubernetes clusters.` +} + +func (cmd *controllerInstallCmd) Run(ctx context.Context) error { + src, err := embed_util.NewEmbeddedFiles(controller.Project, "kluctl-controller-deployment") + if err != nil { + return err + } + + var deployArgs []string + if cmd.ControllerVersion != "" { + deployArgs = append(deployArgs, fmt.Sprintf("controller_version=%s", cmd.ControllerVersion)) + } + + cmd2 := deployCmd{ + ProjectFlags: args.ProjectFlags{ + ProjectDir: args.ProjectDir{ + ProjectDir: args.ExistingDirType(src.GetExtractedPath()), + }, + Timeout: 10 * time.Minute, + }, + TargetFlags: args.TargetFlags{ + Context: cmd.Context, + }, + ArgsFlags: args.ArgsFlags{ + Arg: deployArgs, + }, + DryRunFlags: cmd.DryRunFlags, + CommandResultFlags: cmd.CommandResultFlags, + internal: true, + } + return cmd2.Run(ctx) +} diff --git a/cmd/kluctl/commands/cmd_deploy.go b/cmd/kluctl/commands/cmd_deploy.go index e292dc27d..e86f1a53d 100644 --- a/cmd/kluctl/commands/cmd_deploy.go +++ b/cmd/kluctl/commands/cmd_deploy.go @@ -27,6 +27,8 @@ type deployCmd struct { args.CommandResultFlags NoWait bool `group:"misc" help:"Don't wait for objects readiness'"` + + internal bool } func (cmd *deployCmd) Help() string { @@ -47,6 +49,7 @@ func (cmd *deployCmd) Run(ctx context.Context) error { dryRunArgs: &cmd.DryRunFlags, renderOutputDirFlags: cmd.RenderOutputDirFlags, commandResultFlags: &cmd.CommandResultFlags, + internalDeploy: cmd.internal, } return withProjectCommandContext(ctx, ptArgs, func(cmdCtx *commandCtx) error { return cmd.runCmdDeploy(cmdCtx) diff --git a/cmd/kluctl/commands/cmd_list_targets.go b/cmd/kluctl/commands/cmd_list_targets.go index ce39e8476..8df9d4373 100644 --- a/cmd/kluctl/commands/cmd_list_targets.go +++ b/cmd/kluctl/commands/cmd_list_targets.go @@ -17,7 +17,7 @@ func (cmd *listTargetsCmd) Help() string { } func (cmd *listTargetsCmd) Run(ctx context.Context) error { - return withKluctlProjectFromArgs(ctx, cmd.ProjectFlags, nil, true, false, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { + return withKluctlProjectFromArgs(ctx, cmd.ProjectFlags, nil, false, true, false, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { var result []*types.Target for _, t := range p.Targets { result = append(result, t) diff --git a/cmd/kluctl/commands/cmd_seal.go b/cmd/kluctl/commands/cmd_seal.go index 69ecad6ee..c081a61f8 100644 --- a/cmd/kluctl/commands/cmd_seal.go +++ b/cmd/kluctl/commands/cmd_seal.go @@ -155,7 +155,7 @@ func (cmd *sealCmd) loadCert(cmdCtx *commandCtx) (*x509.Certificate, error) { } func (cmd *sealCmd) Run(ctx context.Context) error { - return withKluctlProjectFromArgs(ctx, cmd.ProjectFlags, nil, true, false, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { + return withKluctlProjectFromArgs(ctx, cmd.ProjectFlags, nil, false, true, false, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { hadError := false noTargetMatch := true diff --git a/cmd/kluctl/commands/completion.go b/cmd/kluctl/commands/completion.go index 77e7fe978..653b032f7 100644 --- a/cmd/kluctl/commands/completion.go +++ b/cmd/kluctl/commands/completion.go @@ -50,7 +50,7 @@ func RegisterFlagCompletionFuncs(cmdStruct interface{}, ccmd *cobra.Command) err func withProjectForCompletion(ctx context.Context, projectArgs *args.ProjectFlags, argsFlags *args.ArgsFlags, cb func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error) error { // let's not update git caches too often projectArgs.GitCacheUpdateInterval = time.Second * 60 - return withKluctlProjectFromArgs(ctx, *projectArgs, argsFlags, false, true, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { + return withKluctlProjectFromArgs(ctx, *projectArgs, argsFlags, false, false, true, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { return cb(ctx, p) }) } diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index 66e6103b0..a03b70b77 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -24,7 +24,7 @@ import ( "strings" ) -func withKluctlProjectFromArgs(ctx context.Context, projectFlags args.ProjectFlags, argsFlags *args.ArgsFlags, strictTemplates bool, forCompletion bool, cb func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error) error { +func withKluctlProjectFromArgs(ctx context.Context, projectFlags args.ProjectFlags, argsFlags *args.ArgsFlags, internalDeploy bool, strictTemplates bool, forCompletion bool, cb func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error) error { tmpDir, err := os.MkdirTemp(utils.GetTmpBaseDir(ctx), "project-") if err != nil { return fmt.Errorf("creating temporary project directory failed: %w", err) @@ -42,9 +42,12 @@ func withKluctlProjectFromArgs(ctx context.Context, projectFlags args.ProjectFla return err } - repoRoot, err := git.DetectGitRepositoryRoot(projectDir) - if err != nil { - status.Warning(ctx, "Failed to detect git project root. This might cause follow-up errors") + var repoRoot string + if !internalDeploy { + repoRoot, err = git.DetectGitRepositoryRoot(projectDir) + if err != nil { + status.Warning(ctx, "Failed to detect git project root. This might cause follow-up errors") + } } ctx, cancel := context.WithTimeout(ctx, projectFlags.Timeout) @@ -126,6 +129,7 @@ type projectTargetCommandArgs struct { renderOutputDirFlags args.RenderOutputDirFlags commandResultFlags *args.CommandResultFlags + internalDeploy bool forSeal bool forCompletion bool offlineKubernetes bool @@ -140,7 +144,7 @@ type commandCtx struct { } func withProjectCommandContext(ctx context.Context, args projectTargetCommandArgs, cb func(cmdCtx *commandCtx) error) error { - return withKluctlProjectFromArgs(ctx, args.projectFlags, &args.argsFlags, true, false, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { + return withKluctlProjectFromArgs(ctx, args.projectFlags, &args.argsFlags, args.internalDeploy, true, false, func(ctx context.Context, p *kluctl_project.LoadedKluctlProject) error { return withProjectTargetCommandContext(ctx, args, p, cb) }) } diff --git a/install/controller/embed.go b/install/controller/embed.go index 3bf3cd004..dc5e78645 100644 --- a/install/controller/embed.go +++ b/install/controller/embed.go @@ -3,4 +3,4 @@ package controller import "embed" //go:embed all:* -var Config embed.FS +var Project embed.FS diff --git a/pkg/kluctl_project/project.go b/pkg/kluctl_project/project.go index ad2d230c9..7afc4918f 100644 --- a/pkg/kluctl_project/project.go +++ b/pkg/kluctl_project/project.go @@ -18,8 +18,7 @@ type LoadedKluctlProject struct { TmpDir string - projectRootDir string - ProjectDir string + ProjectDir string sealedSecretsDir string diff --git a/pkg/kluctl_project/project_load.go b/pkg/kluctl_project/project_load.go index 2982be334..5ed7aa53f 100644 --- a/pkg/kluctl_project/project_load.go +++ b/pkg/kluctl_project/project_load.go @@ -38,11 +38,13 @@ func (c *LoadedKluctlProject) getConfigPath() string { func (c *LoadedKluctlProject) loadKluctlProject() error { var err error - c.projectRootDir = c.LoadArgs.RepoRoot c.ProjectDir = c.LoadArgs.ProjectDir - err = utils.CheckInDir(c.projectRootDir, c.ProjectDir) - if err != nil { - return err + + if c.LoadArgs.RepoRoot != "" { + err = utils.CheckInDir(c.LoadArgs.RepoRoot, c.ProjectDir) + if err != nil { + return err + } } configPath := c.getConfigPath() From be6d540f683a0a5b7eee85b2a7a272e1b10c485e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 26 May 2023 08:27:27 +0200 Subject: [PATCH 1696/2916] docs: Add links to command results arguments --- docs/reference/commands/delete.md | 1 + docs/reference/commands/deploy.md | 1 + docs/reference/commands/poke-images.md | 1 + docs/reference/commands/prune.md | 1 + 4 files changed, 4 insertions(+) diff --git a/docs/reference/commands/delete.md b/docs/reference/commands/delete.md index da4ad7739..058367db7 100644 --- a/docs/reference/commands/delete.md +++ b/docs/reference/commands/delete.md @@ -27,6 +27,7 @@ The following sets of arguments are available: 1. [project arguments](./common-arguments.md#project-arguments) 1. [image arguments](./common-arguments.md#image-arguments) 1. [inclusion/exclusion arguments](./common-arguments.md#inclusionexclusion-arguments) +1. [command results arguments](./common-arguments.md#command-results-arguments) In addition, the following arguments are available: diff --git a/docs/reference/commands/deploy.md b/docs/reference/commands/deploy.md index ea4d88e11..344fb448b 100644 --- a/docs/reference/commands/deploy.md +++ b/docs/reference/commands/deploy.md @@ -25,6 +25,7 @@ The following sets of arguments are available: 1. [project arguments](./common-arguments.md#project-arguments) 1. [image arguments](./common-arguments.md#image-arguments) 1. [inclusion/exclusion arguments](./common-arguments.md#inclusionexclusion-arguments) +1. [command results arguments](./common-arguments.md#command-results-arguments) In addition, the following arguments are available: diff --git a/docs/reference/commands/poke-images.md b/docs/reference/commands/poke-images.md index 55ce70bbc..6dc316676 100644 --- a/docs/reference/commands/poke-images.md +++ b/docs/reference/commands/poke-images.md @@ -25,6 +25,7 @@ The following sets of arguments are available: 1. [project arguments](./common-arguments.md#project-arguments) 1. [image arguments](./common-arguments.md#image-arguments) 1. [inclusion/exclusion arguments](./common-arguments.md#inclusionexclusion-arguments) +1. [command results arguments](./common-arguments.md#command-results-arguments) In addition, the following arguments are available: diff --git a/docs/reference/commands/prune.md b/docs/reference/commands/prune.md index 6d012920e..40b7f211f 100644 --- a/docs/reference/commands/prune.md +++ b/docs/reference/commands/prune.md @@ -21,6 +21,7 @@ The following sets of arguments are available: 1. [project arguments](./common-arguments.md#project-arguments) 1. [image arguments](./common-arguments.md#image-arguments) 1. [inclusion/exclusion arguments](./common-arguments.md#inclusionexclusion-arguments) +1. [command results arguments](./common-arguments.md#command-results-arguments) In addition, the following arguments are available: From 9de8794983f23e0c779959b9847d3b9b2ab27cff Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 26 May 2023 08:31:55 +0200 Subject: [PATCH 1697/2916] docs: Add docs about new subcommands --- cmd/kluctl/commands/cmd_controller_install.go | 4 +- cmd/kluctl/commands/cmd_controller_run.go | 14 +++---- cmd/kluctl/commands/root.go | 1 - docs/reference/commands/controller-install.md | 37 +++++++++++++++++++ .../{controller.md => controller-run.md} | 14 +++---- internal/replace-commands-help/main.go | 5 ++- 6 files changed, 57 insertions(+), 18 deletions(-) create mode 100644 docs/reference/commands/controller-install.md rename docs/reference/commands/{controller.md => controller-run.md} (81%) diff --git a/cmd/kluctl/commands/cmd_controller_install.go b/cmd/kluctl/commands/cmd_controller_install.go index 319a0f496..d5e3db490 100644 --- a/cmd/kluctl/commands/cmd_controller_install.go +++ b/cmd/kluctl/commands/cmd_controller_install.go @@ -14,8 +14,8 @@ type controllerInstallCmd struct { args.DryRunFlags args.CommandResultFlags - Context string `group:"controller" help:"Override the context to use."` - ControllerVersion string `group:"controller" help:"Specify the controller version to install."` + Context string `group:"misc" help:"Override the context to use."` + ControllerVersion string `group:"misc" help:"Specify the controller version to install."` } func (cmd *controllerInstallCmd) Help() string { diff --git a/cmd/kluctl/commands/cmd_controller_run.go b/cmd/kluctl/commands/cmd_controller_run.go index 6d9cb40b2..bc83b0a5d 100644 --- a/cmd/kluctl/commands/cmd_controller_run.go +++ b/cmd/kluctl/commands/cmd_controller_run.go @@ -35,15 +35,15 @@ const controllerName = "kluctl-controller" type controllerRunCmd struct { scheme *runtime.Scheme - Kubeconfig string `group:"controller" help:"Override the kubeconfig to use."` - Context string `group:"controller" help:"Override the context to use."` + Kubeconfig string `group:"misc" help:"Override the kubeconfig to use."` + Context string `group:"misc" help:"Override the context to use."` - MetricsBindAddress string `group:"controller" help:"The address the metric endpoint binds to." default:":8080"` - HealthProbeBindAddress string `group:"controller" help:"The address the probe endpoint binds to." default:":8081"` - LeaderElect bool `group:"controller" help:"Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager."` + MetricsBindAddress string `group:"misc" help:"The address the metric endpoint binds to." default:":8080"` + HealthProbeBindAddress string `group:"misc" help:"The address the probe endpoint binds to." default:":8081"` + LeaderElect bool `group:"misc" help:"Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager."` - DefaultServiceAccount string `group:"controller" help:"Default service account used for impersonation."` - DryRun bool `group:"controller" help:"Run all deployments in dryRun=true mode."` + DefaultServiceAccount string `group:"misc" help:"Default service account used for impersonation."` + DryRun bool `group:"misc" help:"Run all deployments in dryRun=true mode."` args.CommandResultFlags } diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index 14b15c02e..cc629d689 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -78,7 +78,6 @@ var flagGroups = []groupInfo{ {group: "inclusion", title: "Inclusion/Exclusion arguments:", description: "Control inclusion/exclusion."}, {group: "misc", title: "Misc arguments:", description: "Command specific arguments."}, {group: "results", title: "Command Results:", description: "Configure how command results are stored."}, - {group: "controller", title: "Controller:", description: "Controller arguments."}, } var origStderr = os.Stderr diff --git a/docs/reference/commands/controller-install.md b/docs/reference/commands/controller-install.md new file mode 100644 index 000000000..516e9ee91 --- /dev/null +++ b/docs/reference/commands/controller-install.md @@ -0,0 +1,37 @@ + + +## Command + +Usage: kluctl controller install [flags] + +Install the Kluctl controller +This command will install the kluctl-controller to the current Kubernetes clusters. + + + +## Arguments +The following sets of arguments are available: +1. [command results arguments](./common-arguments.md#command-results-arguments) + +In addition, the following arguments are available: + +``` +Misc arguments: + Command specific arguments. + + --context string Override the context to use. + --controller-version string Specify the controller version to install. + --dry-run Performs all kubernetes API calls in dry-run mode. + -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. + +``` + diff --git a/docs/reference/commands/controller.md b/docs/reference/commands/controller-run.md similarity index 81% rename from docs/reference/commands/controller.md rename to docs/reference/commands/controller-run.md index 588b04f6e..53589381f 100644 --- a/docs/reference/commands/controller.md +++ b/docs/reference/commands/controller-run.md @@ -1,8 +1,8 @@ ## Command - -Usage: kluctl controller [flags] + +Usage: kluctl controller run [flags] Run the Kluctl controller This command will run the Kluctl Controller. This is usually meant to be run inside a cluster and not from your local machine. @@ -21,10 +21,10 @@ This command will run the Kluctl Controller. This is usually meant to be run ins ## Arguments The following arguments are available: - + ``` -Controller: - Controller arguments. +Misc arguments: + Command specific arguments. --context string Override the context to use. --default-service-account string Default service account used for impersonation. diff --git a/internal/replace-commands-help/main.go b/internal/replace-commands-help/main.go index 2f47f1c08..6fbbf7924 100644 --- a/internal/replace-commands-help/main.go +++ b/internal/replace-commands-help/main.go @@ -135,7 +135,10 @@ func getHelpSection(command string, section string) []string { log.Fatal(err) } - helpCmd := exec.Command(exe, command, "--help") + args := strings.Split(command, " ") + args = append(args, "--help") + + helpCmd := exec.Command(exe, args...) helpCmd.Env = os.Environ() helpCmd.Env = append(helpCmd.Env, "CALL_KLUCTL=true") From 211d65eecd10a8011dc7dd9de3b67eac8ae20b90 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 May 2023 08:39:19 +0200 Subject: [PATCH 1698/2916] chore(deps): Bump github.com/ohler55/ojg from 1.18.5 to 1.18.6 (#508) Bumps [github.com/ohler55/ojg](https://github.com/ohler55/ojg) from 1.18.5 to 1.18.6. - [Changelog](https://github.com/ohler55/ojg/blob/develop/CHANGELOG.md) - [Commits](https://github.com/ohler55/ojg/compare/v1.18.5...v1.18.6) --- updated-dependencies: - dependency-name: github.com/ohler55/ojg dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0a686c573..03f26806f 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/mattn/go-isatty v0.0.19 github.com/mattn/go-runewidth v0.0.14 github.com/mitchellh/reflectwalk v1.0.2 - github.com/ohler55/ojg v1.18.5 + github.com/ohler55/ojg v1.18.6 github.com/pkg/errors v0.9.1 github.com/r3labs/diff/v2 v2.15.1 github.com/rogpeppe/go-internal v1.10.0 diff --git a/go.sum b/go.sum index 6afa7e683..8c298eb8c 100644 --- a/go.sum +++ b/go.sum @@ -644,8 +644,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW github.com/nelsam/hel/v2 v2.3.2/go.mod h1:1ZTGfU2PFTOd5mx22i5O0Lc2GY933lQ2wb/ggy+rL3w= github.com/nelsam/hel/v2 v2.3.3/go.mod h1:1ZTGfU2PFTOd5mx22i5O0Lc2GY933lQ2wb/ggy+rL3w= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/ohler55/ojg v1.18.5 h1:tzn5LJtkSyXowCo8SlGieU0zEc7WF4143Ri9MYlQy7Q= -github.com/ohler55/ojg v1.18.5/go.mod h1:uHcD1ErbErC27Zhb5Df2jUjbseLLcmOCo6oxSr3jZxo= +github.com/ohler55/ojg v1.18.6 h1:ZY5I/8I+zW8s+/QpX9E/P9QJwECi4lNx67VgdOzTTno= +github.com/ohler55/ojg v1.18.6/go.mod h1:uHcD1ErbErC27Zhb5Df2jUjbseLLcmOCo6oxSr3jZxo= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= From 7ecd12bfeb6c7913b7e3e19f21befc51fae2fb5e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 26 May 2023 11:56:37 +0200 Subject: [PATCH 1699/2916] fix: Use strict yaml unmarshalling in custom unmarshallers (#510) * chore: Add .gitignore for webui * fix: Use strict yaml unmarshalling in custom unmarshallers --- pkg/deployment/external_args.go | 3 +-- pkg/types/deployment.go | 5 ++--- pkg/types/git_project.go | 5 ++--- pkg/types/git_url.go | 5 +++-- pkg/types/result/compact.go | 2 +- pkg/types/yaml_url.go | 3 ++- pkg/utils/uo/uo.go | 2 +- pkg/webui/ui/.gitignore | 23 +++++++++++++++++++++++ 8 files changed, 35 insertions(+), 13 deletions(-) create mode 100644 pkg/webui/ui/.gitignore diff --git a/pkg/deployment/external_args.go b/pkg/deployment/external_args.go index 808d65dbf..17c003d84 100644 --- a/pkg/deployment/external_args.go +++ b/pkg/deployment/external_args.go @@ -1,7 +1,6 @@ package deployment import ( - "encoding/json" "fmt" "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/utils/uo" @@ -61,7 +60,7 @@ func LoadDefaultArgs(args []*types.DeploymentArg, deployArgs *uo.UnstructuredObj for _, a := range args { if a.Default != nil { var v any - err := json.Unmarshal(a.Default.Raw, &v) + err := yaml.ReadYamlBytes(a.Default.Raw, &v) if err != nil { return err } diff --git a/pkg/types/deployment.go b/pkg/types/deployment.go index a4f8ee8ee..843186106 100644 --- a/pkg/types/deployment.go +++ b/pkg/types/deployment.go @@ -1,7 +1,6 @@ package types import ( - "encoding/json" "github.com/go-playground/validator/v10" "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/yaml" @@ -70,14 +69,14 @@ type SingleStringOrList []string func (s *SingleStringOrList) UnmarshalJSON(b []byte) error { var single string - if err := json.Unmarshal(b, &single); err == nil { + if err := yaml.ReadYamlBytes(b, &single); err == nil { // it's a single project *s = []string{single} return nil } // try as array var arr []string - if err := json.Unmarshal(b, &arr); err != nil { + if err := yaml.ReadYamlBytes(b, &arr); err != nil { return err } *s = arr diff --git a/pkg/types/git_project.go b/pkg/types/git_project.go index 32a785541..e73cff058 100644 --- a/pkg/types/git_project.go +++ b/pkg/types/git_project.go @@ -1,7 +1,6 @@ package types import ( - "encoding/json" "fmt" "regexp" "strings" @@ -20,12 +19,12 @@ type GitProject struct { } func (gp *GitProject) UnmarshalJSON(b []byte) error { - if err := json.Unmarshal(b, &gp.Url); err == nil { + if err := yaml.ReadYamlBytes(b, &gp.Url); err == nil { // it's a simple string return nil } type raw GitProject - return json.Unmarshal(b, (*raw)(gp)) + return yaml.ReadYamlBytes(b, (*raw)(gp)) } // invalidDirName evaluate directory name against forbidden characters diff --git a/pkg/types/git_url.go b/pkg/types/git_url.go index 4c649bb04..8392967bf 100644 --- a/pkg/types/git_url.go +++ b/pkg/types/git_url.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "github.com/kluctl/kluctl/v2/pkg/git/giturls" + "github.com/kluctl/kluctl/v2/pkg/yaml" "net/url" "strings" ) @@ -43,7 +44,7 @@ func (in *GitUrl) DeepCopyInto(out *GitUrl) { func (u *GitUrl) UnmarshalJSON(b []byte) error { var s string - err := json.Unmarshal(b, &s) + err := yaml.ReadYamlBytes(b, &s) if err != nil { return err } @@ -142,7 +143,7 @@ func (u GitRepoKey) String() string { func (u *GitRepoKey) UnmarshalJSON(b []byte) error { var s string - err := json.Unmarshal(b, &s) + err := yaml.ReadYamlBytes(b, &s) if err != nil { return err } diff --git a/pkg/types/result/compact.go b/pkg/types/result/compact.go index ae94847c3..6caa48aaf 100644 --- a/pkg/types/result/compact.go +++ b/pkg/types/result/compact.go @@ -73,7 +73,7 @@ func (l CompactedObjects) MarshalJSON() ([]byte, error) { func (l *CompactedObjects) UnmarshalJSON(b []byte) error { var compactedList []CompactedObject - err := json.Unmarshal(b, &compactedList) + err := yaml.ReadYamlBytes(b, &compactedList) if err != nil { return err } diff --git a/pkg/types/yaml_url.go b/pkg/types/yaml_url.go index f2ffac1df..0f7ffda06 100644 --- a/pkg/types/yaml_url.go +++ b/pkg/types/yaml_url.go @@ -2,6 +2,7 @@ package types import ( "encoding/json" + "github.com/kluctl/kluctl/v2/pkg/yaml" "net/url" ) @@ -11,7 +12,7 @@ type YamlUrl struct { func (u *YamlUrl) UnmarshalJSON(b []byte) error { var s string - err := json.Unmarshal(b, &s) + err := yaml.ReadYamlBytes(b, &s) if err != nil { return err } diff --git a/pkg/utils/uo/uo.go b/pkg/utils/uo/uo.go index 92d2e73d7..c9ab95bf1 100644 --- a/pkg/utils/uo/uo.go +++ b/pkg/utils/uo/uo.go @@ -19,7 +19,7 @@ func (uo *UnstructuredObject) MarshalJSON() ([]byte, error) { } func (uo *UnstructuredObject) UnmarshalJSON(b []byte) error { - return json.Unmarshal(b, &uo.Object) + return yaml.ReadYamlBytes(b, &uo.Object) } func (uo *UnstructuredObject) IsZero() bool { diff --git a/pkg/webui/ui/.gitignore b/pkg/webui/ui/.gitignore new file mode 100644 index 000000000..4d29575de --- /dev/null +++ b/pkg/webui/ui/.gitignore @@ -0,0 +1,23 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* From c2fc47f0f26ed8981e5a8f4c4556d7e47ee42e2d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 26 May 2023 09:11:06 +0200 Subject: [PATCH 1700/2916] refactor: Introduce ParseGitRepoKey --- pkg/types/git_url.go | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/pkg/types/git_url.go b/pkg/types/git_url.go index 8392967bf..5ab73fdd0 100644 --- a/pkg/types/git_url.go +++ b/pkg/types/git_url.go @@ -134,6 +134,21 @@ type GitRepoKey struct { Path string `json:"-"` } +func ParseGitRepoKey(s string) (GitRepoKey, error) { + if s == "" { + return GitRepoKey{}, nil + } + + s2 := strings.SplitN(s, "/", 2) + if len(s2) != 2 { + return GitRepoKey{}, fmt.Errorf("invalid git repo key: %s", s) + } + return GitRepoKey{ + Host: s2[0], + Path: s2[1], + }, nil +} + func (u GitRepoKey) String() string { if u.Host == "" && u.Path == "" { return "" @@ -152,12 +167,11 @@ func (u *GitRepoKey) UnmarshalJSON(b []byte) error { u.Path = "" return nil } - s2 := strings.SplitN(s, "/", 2) - if len(s2) != 2 { - return fmt.Errorf("invalid git repo key: %s", s) + x, err := ParseGitRepoKey(s) + if err != nil { + return err } - u.Host = s2[0] - u.Path = s2[1] + *u = x return nil } From 9ec07a6fdc2fa54d777c6757247f81269f093fa0 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 26 May 2023 13:58:36 +0200 Subject: [PATCH 1701/2916] fix: Fix crash in WatchCommandResultSummaries handlers --- pkg/results/result-store-secrets.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/results/result-store-secrets.go b/pkg/results/result-store-secrets.go index 5d244d538..1389db4ab 100644 --- a/pkg/results/result-store-secrets.go +++ b/pkg/results/result-store-secrets.go @@ -310,6 +310,9 @@ func (s *ResultStoreSecrets) WatchCommandResultSummaries(options ListCommandResu if err != nil { return } + if summary == nil { + return + } if !s.filterSummary(summary, options.ProjectFilter) { return } From 10fa182d84ebcdac8b23bc317dd4f23086fbb8c6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 26 May 2023 13:59:06 +0200 Subject: [PATCH 1702/2916] fix: Use imagePullPolicy: Always for snapshot builds --- install/controller/controller/kustomization.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/install/controller/controller/kustomization.yaml b/install/controller/controller/kustomization.yaml index 4c41784cc..d4711bbb7 100644 --- a/install/controller/controller/kustomization.yaml +++ b/install/controller/controller/kustomization.yaml @@ -11,3 +11,12 @@ patches: - op: add path: /spec/template/spec/containers/0/image value: ghcr.io/kluctl/kluctl:{{ args.controller_version }} +{% if args.controller_version.endswith("-next-amd64") or args.controller_version.endswith("-next-arm64") %} + - target: + kind: Deployment + name: kluctl-controller + patch: |- + - op: add + path: /spec/template/spec/containers/0/imagePullPolicy + value: Always +{% endif %} From 00ba739db2996a1509c9dda1ee5af99d97e74d3f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 26 May 2023 13:59:18 +0200 Subject: [PATCH 1703/2916] fix: Fix crash in handleCommandResult --- pkg/controllers/kluctl_project.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pkg/controllers/kluctl_project.go b/pkg/controllers/kluctl_project.go index 7af6cda94..5c9a946bc 100644 --- a/pkg/controllers/kluctl_project.go +++ b/pkg/controllers/kluctl_project.go @@ -640,6 +640,11 @@ func (pt *preparedTarget) loadTarget(ctx context.Context, p *kluctl_project.Load func (pt *preparedTarget) handleCommandResult(ctx context.Context, cmdErr error, cmdResult *result.CommandResult, commandName string) error { log := ctrl.LoggerFrom(ctx) + if cmdErr != nil { + pt.pp.r.event(ctx, pt.pp.obj, true, fmt.Sprintf("%s failed. %s", commandName, cmdErr.Error()), nil) + return cmdErr + } + cmdResult.Command.Initiator = result.CommandInititiator_KluctlDeployment cmdResult.GitInfo.Url = &pt.pp.obj.Spec.Source.URL cmdResult.GitInfo.Ref = pt.pp.obj.Spec.Source.Ref.String() @@ -655,10 +660,6 @@ func (pt *preparedTarget) handleCommandResult(ctx context.Context, cmdErr error, log.Info(fmt.Sprintf("command finished with err=%v", cmdErr)) defer pt.exportCommandResultMetricsToProm(summary) - if cmdErr != nil { - pt.pp.r.event(ctx, pt.pp.obj, true, fmt.Sprintf("%s failed. %s", commandName, cmdErr.Error()), nil) - return cmdErr - } msg := fmt.Sprintf("%s succeeded.", commandName) if summary.NewObjects != 0 { From 0616517baca3d6f692996a916a9e60c8fb36f96b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 26 May 2023 13:59:28 +0200 Subject: [PATCH 1704/2916] fix: Give the controller more cpu+memory --- config/manager/manager.yaml | 6 +++--- install/controller/controller/manager.yaml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index c1f1d3e68..f544bbcee 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -97,9 +97,9 @@ spec: resources: limits: cpu: 500m - memory: 128Mi + memory: 512Mi requests: - cpu: 10m - memory: 64Mi + cpu: 500m + memory: 512Mi serviceAccountName: kluctl-controller terminationGracePeriodSeconds: 10 diff --git a/install/controller/controller/manager.yaml b/install/controller/controller/manager.yaml index 7cee4bf75..ed9e00985 100644 --- a/install/controller/controller/manager.yaml +++ b/install/controller/controller/manager.yaml @@ -61,10 +61,10 @@ spec: resources: limits: cpu: 500m - memory: 128Mi + memory: 512Mi requests: - cpu: 10m - memory: 64Mi + cpu: 500m + memory: 512Mi securityContext: allowPrivilegeEscalation: false capabilities: From 23fe48ab5a4e554717deae92f9e4714f3ecefc03 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 26 May 2023 14:35:20 +0200 Subject: [PATCH 1705/2916] fix: Add missing cluster-admin binding for the controller (#512) --- config/rbac/kustomization.yaml | 1 + config/rbac/reconciler_binding.yaml | 12 ++++++++++++ install/controller/controller/rbac.yaml | 13 +++++++++++++ 3 files changed, 26 insertions(+) create mode 100644 config/rbac/reconciler_binding.yaml diff --git a/config/rbac/kustomization.yaml b/config/rbac/kustomization.yaml index 166fe7986..588dbee4b 100644 --- a/config/rbac/kustomization.yaml +++ b/config/rbac/kustomization.yaml @@ -9,3 +9,4 @@ resources: - role_binding.yaml - leader_election_role.yaml - leader_election_role_binding.yaml +- reconciler_binding.yaml diff --git a/config/rbac/reconciler_binding.yaml b/config/rbac/reconciler_binding.yaml new file mode 100644 index 000000000..f85f09807 --- /dev/null +++ b/config/rbac/reconciler_binding.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: kluctl-controller-cluster-admin +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin +subjects: + - kind: ServiceAccount + name: kluctl-controller + namespace: kluctl-system diff --git a/install/controller/controller/rbac.yaml b/install/controller/controller/rbac.yaml index 1845b4548..aa7d7bccb 100644 --- a/install/controller/controller/rbac.yaml +++ b/install/controller/controller/rbac.yaml @@ -146,6 +146,19 @@ subjects: --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding +metadata: + name: kluctl-controller-cluster-admin +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin +subjects: +- kind: ServiceAccount + name: kluctl-controller + namespace: kluctl-system +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding metadata: labels: app.kubernetes.io/component: rbac From b8af67f8213a8ab60d896a6631d2e74077e14936 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 May 2023 13:31:59 +0200 Subject: [PATCH 1706/2916] chore(deps): Bump github.com/hashicorp/vault/api from 1.9.1 to 1.9.2 (#513) Bumps [github.com/hashicorp/vault/api](https://github.com/hashicorp/vault) from 1.9.1 to 1.9.2. - [Release notes](https://github.com/hashicorp/vault/releases) - [Changelog](https://github.com/hashicorp/vault/blob/main/CHANGELOG.md) - [Commits](https://github.com/hashicorp/vault/compare/v1.9.1...v1.9.2) --- updated-dependencies: - dependency-name: github.com/hashicorp/vault/api dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 03f26806f..f4c9b9273 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/gobwas/glob v0.2.3 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 github.com/google/go-containerregistry v0.15.2 - github.com/hashicorp/vault/api v1.9.1 + github.com/hashicorp/vault/api v1.9.2 github.com/hexops/gotextdiff v1.0.3 github.com/imdario/mergo v0.3.15 github.com/jinzhu/copier v0.3.5 @@ -134,6 +134,7 @@ require ( github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.4.1 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect + github.com/go-jose/go-jose/v3 v3.0.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/zapr v1.2.4 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect @@ -237,7 +238,6 @@ require ( google.golang.org/protobuf v1.30.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 8c298eb8c..332d6da82 100644 --- a/go.sum +++ b/go.sum @@ -282,6 +282,8 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2 github.com/go-gorp/gorp/v3 v3.0.5/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= +github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo= +github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= @@ -478,8 +480,8 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hashicorp/vault/api v1.9.1 h1:LtY/I16+5jVGU8rufyyAkwopgq/HpUnxFBg+QLOAV38= -github.com/hashicorp/vault/api v1.9.1/go.mod h1:78kktNcQYbBGSrOjQfHjXN32OhhxXnbYl3zxpd2uPUs= +github.com/hashicorp/vault/api v1.9.2 h1:YjkZLJ7K3inKgMZ0wzCU9OHqc+UqMQyXsPXnf3Cl2as= +github.com/hashicorp/vault/api v1.9.2/go.mod h1:jo5Y/ET+hNyz+JnKDt8XLAdKs+AM0G5W0Vp1IrFI8N8= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= @@ -871,6 +873,7 @@ golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -1301,8 +1304,6 @@ gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= -gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= From c01a2f623b7a999ab6e44264155f493baa08b799 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 May 2023 13:32:31 +0200 Subject: [PATCH 1707/2916] chore(deps): Bump github.com/imdario/mergo from 0.3.15 to 0.3.16 (#514) Bumps [github.com/imdario/mergo](https://github.com/imdario/mergo) from 0.3.15 to 0.3.16. - [Release notes](https://github.com/imdario/mergo/releases) - [Commits](https://github.com/imdario/mergo/compare/v0.3.15...v0.3.16) --- updated-dependencies: - dependency-name: github.com/imdario/mergo dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f4c9b9273..3fc55f456 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/google/go-containerregistry v0.15.2 github.com/hashicorp/vault/api v1.9.2 github.com/hexops/gotextdiff v1.0.3 - github.com/imdario/mergo v0.3.15 + github.com/imdario/mergo v0.3.16 github.com/jinzhu/copier v0.3.5 github.com/kevinburke/ssh_config v1.2.0 github.com/kluctl/go-embed-python v0.0.0-3.10.9-20230206-2 diff --git a/go.sum b/go.sum index 332d6da82..845de9aaa 100644 --- a/go.sum +++ b/go.sum @@ -493,8 +493,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= -github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= -github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= From 2c1b544f63e160c7ca72e48f21e4e60de52cbee9 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 30 May 2023 14:11:23 +0200 Subject: [PATCH 1708/2916] fix: Fix bogus breakpoint (#515) * fix: Fix bogus breakpoint * chore: Add .gitignore entry for public/staticdata --- pkg/k8s/k8s_cluster.go | 5 ----- pkg/webui/ui/.gitignore | 2 ++ 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index b7b37d515..50063ca8e 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -7,7 +7,6 @@ import ( apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "net/http" - "runtime" "sigs.k8s.io/controller-runtime/pkg/client" "strings" "sync" @@ -457,10 +456,6 @@ func (k *K8sCluster) FixNamespaceInRef(ref k8s.ObjectRef) k8s.ObjectRef { } func (k *K8sCluster) GetSchemaForGVK(gvk schema.GroupVersionKind) (*uo.UnstructuredObject, error) { - if gvk.Kind == "KluctlDeployment" { - runtime.Breakpoint() - } - rms, err := k.clientFactory.Mapper().RESTMappings(gvk.GroupKind(), gvk.Version) if err != nil { return nil, err diff --git a/pkg/webui/ui/.gitignore b/pkg/webui/ui/.gitignore index 4d29575de..b2ba12633 100644 --- a/pkg/webui/ui/.gitignore +++ b/pkg/webui/ui/.gitignore @@ -21,3 +21,5 @@ npm-debug.log* yarn-debug.log* yarn-error.log* + +/public/staticdata From 89b0cfaef2f17cc43e62e3cec6560868806576f9 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 30 May 2023 14:17:11 +0200 Subject: [PATCH 1709/2916] fix: Give controller more CPU limits (#516) --- config/manager/manager.yaml | 2 +- install/controller/controller/controller-version-patch.yaml | 0 install/controller/controller/manager.yaml | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 install/controller/controller/controller-version-patch.yaml diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index f544bbcee..83c13b50a 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -96,7 +96,7 @@ spec: # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ resources: limits: - cpu: 500m + cpu: 2000m memory: 512Mi requests: cpu: 500m diff --git a/install/controller/controller/controller-version-patch.yaml b/install/controller/controller/controller-version-patch.yaml deleted file mode 100644 index e69de29bb..000000000 diff --git a/install/controller/controller/manager.yaml b/install/controller/controller/manager.yaml index ed9e00985..5544b30e2 100644 --- a/install/controller/controller/manager.yaml +++ b/install/controller/controller/manager.yaml @@ -60,7 +60,7 @@ spec: periodSeconds: 10 resources: limits: - cpu: 500m + cpu: 2000m memory: 512Mi requests: cpu: 500m From 9985d95aba8f55d5165368d5e9fe27dcae069516 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 30 May 2023 14:34:34 +0200 Subject: [PATCH 1710/2916] fix: Don't write result when ResultStore is not initialized (#517) --- pkg/controllers/kluctl_project.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pkg/controllers/kluctl_project.go b/pkg/controllers/kluctl_project.go index 5c9a946bc..52dfdc8fe 100644 --- a/pkg/controllers/kluctl_project.go +++ b/pkg/controllers/kluctl_project.go @@ -650,10 +650,13 @@ func (pt *preparedTarget) handleCommandResult(ctx context.Context, cmdErr error, cmdResult.GitInfo.Ref = pt.pp.obj.Spec.Source.Ref.String() cmdResult.ProjectKey.GitRepoKey = pt.pp.obj.Spec.Source.URL.RepoKey() - log.Info(fmt.Sprintf("Writing command result %s", cmdResult.Id)) - err := pt.pp.r.ResultStore.WriteCommandResult(cmdResult) - if err != nil { - log.Error(err, "Writing command result failed") + var err error + if pt.pp.r.ResultStore != nil { + log.Info(fmt.Sprintf("Writing command result %s", cmdResult.Id)) + err = pt.pp.r.ResultStore.WriteCommandResult(cmdResult) + if err != nil { + log.Error(err, "Writing command result failed") + } } summary := cmdResult.BuildSummary() From adea0679900faac66a81f751eb7b5ab7f891f379 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 30 May 2023 14:42:55 +0200 Subject: [PATCH 1711/2916] fix: Don't wait for deletion when performed by the controller (#518) --- cmd/kluctl/commands/cmd_delete.go | 2 +- cmd/kluctl/commands/cmd_prune.go | 2 +- pkg/controllers/kluctl_project.go | 4 ++-- pkg/deployment/commands/delete.go | 6 ++++-- pkg/deployment/commands/prune.go | 6 ++++-- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/cmd/kluctl/commands/cmd_delete.go b/cmd/kluctl/commands/cmd_delete.go index f93e08d14..d60bcc85e 100644 --- a/cmd/kluctl/commands/cmd_delete.go +++ b/cmd/kluctl/commands/cmd_delete.go @@ -46,7 +46,7 @@ func (cmd *deleteCmd) Run(ctx context.Context) error { commandResultFlags: &cmd.CommandResultFlags, } return withProjectCommandContext(ctx, ptArgs, func(cmdCtx *commandCtx) error { - cmd2 := commands.NewDeleteCommand(cmd.Discriminator, cmdCtx.targetCtx, nil) + cmd2 := commands.NewDeleteCommand(cmd.Discriminator, cmdCtx.targetCtx, nil, true) result, err := cmd2.Run(cmdCtx.targetCtx.SharedContext.Ctx, cmdCtx.targetCtx.SharedContext.K, func(refs []k8s2.ObjectRef) error { return confirmDeletion(ctx, refs, cmd.DryRun, cmd.Yes) diff --git a/cmd/kluctl/commands/cmd_prune.go b/cmd/kluctl/commands/cmd_prune.go index 9e3eea413..f6bbd79fe 100644 --- a/cmd/kluctl/commands/cmd_prune.go +++ b/cmd/kluctl/commands/cmd_prune.go @@ -48,7 +48,7 @@ func (cmd *pruneCmd) Run(ctx context.Context) error { } func (cmd *pruneCmd) runCmdPrune(cmdCtx *commandCtx) error { - cmd2 := commands.NewPruneCommand(cmdCtx.targetCtx.Target.Discriminator, cmdCtx.targetCtx) + cmd2 := commands.NewPruneCommand(cmdCtx.targetCtx.Target.Discriminator, cmdCtx.targetCtx, true) result, err := cmd2.Run(func(refs []k8s2.ObjectRef) error { return confirmDeletion(cmdCtx.ctx, refs, cmd.DryRun, cmd.Yes) }) diff --git a/pkg/controllers/kluctl_project.go b/pkg/controllers/kluctl_project.go index 52dfdc8fe..715fbf36b 100644 --- a/pkg/controllers/kluctl_project.go +++ b/pkg/controllers/kluctl_project.go @@ -731,7 +731,7 @@ func (pt *preparedTarget) kluctlPrune(ctx context.Context, targetContext *kluctl timer := prometheus.NewTimer(internal_metrics.NewKluctlPruneDuration(pt.pp.obj.ObjectMeta.Namespace, pt.pp.obj.ObjectMeta.Name)) defer timer.ObserveDuration() - cmd := commands.NewPruneCommand("", targetContext) + cmd := commands.NewPruneCommand("", targetContext, false) cmdResult, err := cmd.Run(func(refs []k8s.ObjectRef) error { pt.printDeletedRefs(ctx, refs) return nil @@ -767,7 +767,7 @@ func (pt *preparedTarget) kluctlDelete(ctx context.Context, discriminator string inclusion := pt.buildInclusion() - cmd := commands.NewDeleteCommand(discriminator, nil, inclusion) + cmd := commands.NewDeleteCommand(discriminator, nil, inclusion, false) restConfig, err := pt.buildRestConfig(ctx) if err != nil { diff --git a/pkg/deployment/commands/delete.go b/pkg/deployment/commands/delete.go index 11c49e8bb..28794cb9a 100644 --- a/pkg/deployment/commands/delete.go +++ b/pkg/deployment/commands/delete.go @@ -18,13 +18,15 @@ type DeleteCommand struct { discriminator string targetCtx *kluctl_project.TargetContext inclusion *utils.Inclusion + wait bool } -func NewDeleteCommand(discriminator string, targetCtx *kluctl_project.TargetContext, inclusion *utils.Inclusion) *DeleteCommand { +func NewDeleteCommand(discriminator string, targetCtx *kluctl_project.TargetContext, inclusion *utils.Inclusion, wait bool) *DeleteCommand { return &DeleteCommand{ discriminator: discriminator, targetCtx: targetCtx, inclusion: inclusion, + wait: wait, } } @@ -64,7 +66,7 @@ func (cmd *DeleteCommand) Run(ctx context.Context, k *k8s.K8sCluster, confirmCb } } - deleted, err := utils2.DeleteObjects(ctx, k, deleteRefs, dew, true) + deleted, err := utils2.DeleteObjects(ctx, k, deleteRefs, dew, cmd.wait) if err != nil { return nil, err } diff --git a/pkg/deployment/commands/prune.go b/pkg/deployment/commands/prune.go index 1c3bed093..e48093d7b 100644 --- a/pkg/deployment/commands/prune.go +++ b/pkg/deployment/commands/prune.go @@ -14,12 +14,14 @@ import ( type PruneCommand struct { discriminator string targetCtx *kluctl_project.TargetContext + wait bool } -func NewPruneCommand(discriminator string, targetCtx *kluctl_project.TargetContext) *PruneCommand { +func NewPruneCommand(discriminator string, targetCtx *kluctl_project.TargetContext, wait bool) *PruneCommand { return &PruneCommand{ discriminator: discriminator, targetCtx: targetCtx, + wait: wait, } } @@ -52,7 +54,7 @@ func (cmd *PruneCommand) Run(confirmCb func(refs []k8s2.ObjectRef) error) (*resu } } - deleted, err := utils2.DeleteObjects(cmd.targetCtx.SharedContext.Ctx, cmd.targetCtx.SharedContext.K, deleteRefs, dew, true) + deleted, err := utils2.DeleteObjects(cmd.targetCtx.SharedContext.Ctx, cmd.targetCtx.SharedContext.K, deleteRefs, dew, cmd.wait) if err != nil { return nil, err } From a46f4f8a801c7192b5e405641f1621ceadcad5dc Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 30 May 2023 23:36:21 +0200 Subject: [PATCH 1712/2916] fix: Create patch object right before the actual modification (#519) --- pkg/controllers/kluctldeployment_controller.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/controllers/kluctldeployment_controller.go b/pkg/controllers/kluctldeployment_controller.go index 2e6532208..201c3ab8d 100644 --- a/pkg/controllers/kluctldeployment_controller.go +++ b/pkg/controllers/kluctldeployment_controller.go @@ -74,8 +74,6 @@ func (r *KluctlDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Req return ctrl.Result{}, client.IgnoreNotFound(err) } - patch := client.MergeFrom(obj.DeepCopy()) - var cancel context.CancelFunc ctx, cancel = context.WithTimeout(ctx, r.calcTimeout(obj)) defer cancel() @@ -88,6 +86,7 @@ func (r *KluctlDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Req // Add our finalizer if it does not exist if !controllerutil.ContainsFinalizer(obj, kluctlv1.KluctlDeploymentFinalizer) { + patch := client.MergeFrom(obj.DeepCopy()) controllerutil.AddFinalizer(obj, kluctlv1.KluctlDeploymentFinalizer) if err := r.Patch(ctx, obj, patch, client.FieldOwner(r.ControllerName)); err != nil { log.Error(err, "unable to register finalizer") @@ -121,6 +120,7 @@ func (r *KluctlDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Req // set the reconciliation status to progressing if obj.Status.ObservedGeneration == 0 { + patch := client.MergeFrom(obj.DeepCopy()) setReadiness(obj, metav1.ConditionUnknown, meta.ProgressingReason, "reconciliation in progress") if err := r.Status().Patch(ctx, obj, patch, client.FieldOwner(r.ControllerName)); err != nil { return ctrl.Result{Requeue: true}, err @@ -129,6 +129,8 @@ func (r *KluctlDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Req r.recordReadiness(ctx, obj) } + patch := client.MergeFrom(obj.DeepCopy()) + // record the value of the reconciliation request, if any if v, ok := obj.GetAnnotations()[kluctlv1.KluctlRequestReconcileAnnotation]; ok { obj.Status.LastHandledReconcileAt = v From 62b305cb9629733cab50434dec488fc55642ecb9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 31 May 2023 09:14:36 +0200 Subject: [PATCH 1713/2916] chore(deps): Bump github.com/spf13/viper from 1.15.0 to 1.16.0 (#520) Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.15.0 to 1.16.0. - [Release notes](https://github.com/spf13/viper/releases) - [Commits](https://github.com/spf13/viper/compare/v1.15.0...v1.16.0) --- updated-dependencies: - dependency-name: github.com/spf13/viper dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 19 ++++++++++--------- go.sum | 50 +++++++++++++++++++++++++++++++------------------- 2 files changed, 41 insertions(+), 28 deletions(-) diff --git a/go.mod b/go.mod index 3fc55f456..ad7176a81 100644 --- a/go.mod +++ b/go.mod @@ -31,7 +31,7 @@ require ( github.com/sirupsen/logrus v1.9.2 github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 - github.com/spf13/viper v1.15.0 + github.com/spf13/viper v1.16.0 github.com/stretchr/testify v1.8.3 github.com/xanzy/ssh-agent v0.3.3 golang.org/x/crypto v0.9.0 @@ -82,7 +82,7 @@ require ( cloud.google.com/go/compute v1.19.1 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/iam v0.13.0 // indirect - cloud.google.com/go/kms v1.10.0 // indirect + cloud.google.com/go/kms v1.10.1 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.5.1 // indirect @@ -149,9 +149,10 @@ require ( github.com/google/gnostic v0.6.9 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/gofuzz v1.2.0 // indirect + github.com/google/s2a-go v0.1.3 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect - github.com/googleapis/gax-go/v2 v2.7.1 // indirect + github.com/googleapis/gax-go/v2 v2.8.0 // indirect github.com/gorilla/mux v1.8.0 // indirect github.com/gosuri/uitable v0.0.4 // indirect github.com/goware/prefixer v0.0.0-20160118172347-395022866408 // indirect @@ -193,7 +194,7 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc3 // indirect - github.com/pelletier/go-toml/v2 v2.0.6 // indirect + github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect @@ -207,8 +208,8 @@ require ( github.com/ryanuber/go-glob v1.0.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect github.com/skeema/knownhosts v1.1.1 // indirect - github.com/spf13/afero v1.9.3 // indirect - github.com/spf13/cast v1.5.0 // indirect + github.com/spf13/afero v1.9.5 // indirect + github.com/spf13/cast v1.5.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/subosito/gotenv v1.4.2 // indirect github.com/urfave/cli v1.22.12 // indirect @@ -231,10 +232,10 @@ require ( golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.9.1 // indirect gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect - google.golang.org/api v0.114.0 // indirect + google.golang.org/api v0.122.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230323212658-478b75c54725 // indirect - google.golang.org/grpc v1.54.0 // indirect + google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect + google.golang.org/grpc v1.55.0 // indirect google.golang.org/protobuf v1.30.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/go.sum b/go.sum index 845de9aaa..d229ef45a 100644 --- a/go.sum +++ b/go.sum @@ -36,8 +36,8 @@ cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1 cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/iam v0.13.0 h1:+CmB+K0J/33d0zSQ9SlFWUeCCEn5XJA0ZMZ3pHE9u8k= cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= -cloud.google.com/go/kms v1.10.0 h1:Imrtp8792uqNP9bdfPrjtUkjjqOMBcAJ2bdFaAnLhnk= -cloud.google.com/go/kms v1.10.0/go.mod h1:ng3KTUtQQU9bPX3+QGLsflZIHlkbn8amFAMY63m8d24= +cloud.google.com/go/kms v1.10.1 h1:7hm1bRqGCA1GBRQUrp831TwJ9TWhP+tvLuP497CQS2g= +cloud.google.com/go/kms v1.10.1/go.mod h1:rIWk/TryCkR59GMC3YtHtXeLzd634lBbKenvyySAyYI= cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= @@ -169,6 +169,7 @@ github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTx github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= @@ -183,7 +184,11 @@ github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUK github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/containerd v1.7.0 h1:G/ZQr3gMZs6ZT0qPUZ15znx5QSdQdASW11nXTLTM2Pg= github.com/containerd/containerd v1.7.0/go.mod h1:QfR7Efgb/6X2BDpTPJRvPTYDE9rsF0FsXX9J8sIs/sc= @@ -243,6 +248,7 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= @@ -257,8 +263,8 @@ github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYF github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= github.com/foxcpp/go-mockdns v1.0.0 h1:7jBqxd3WDWwi/6WhDvacvH1XsN3rOLXyHM1uhvIx6FI= -github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= @@ -409,6 +415,8 @@ github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/s2a-go v0.1.3 h1:FAgZmpLl/SXurPEZyCMPBIiiYeTbqfjlbdnCNTAkbGE= +github.com/google/s2a-go v0.1.3/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -419,8 +427,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9 github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.7.1 h1:gF4c0zjUP2H/s/hEGyLA3I0fA2ZWjzYiONAD6cvPr8A= -github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= +github.com/googleapis/gax-go/v2 v2.8.0 h1:UBtEZqx1bjXtOQ5BVTkuYghXrr3N4V123VKJK67vJZc= +github.com/googleapis/gax-go/v2 v2.8.0/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= @@ -665,8 +673,8 @@ github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= -github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= +github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= +github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= @@ -755,12 +763,13 @@ github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4k github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= -github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= -github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= +github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= +github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= @@ -773,8 +782,8 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= -github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU= -github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA= +github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= +github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -880,8 +889,9 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= @@ -1097,6 +1107,7 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= @@ -1196,8 +1207,8 @@ google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjR google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= -google.golang.org/api v0.114.0 h1:1xQPji6cO2E2vLiI+C/XiFAnsn1WV3mjaEwGLhi3grE= -google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= +google.golang.org/api v0.122.0 h1:zDobeejm3E7pEG1mNHvdxvjs5XJoCMzyNH+CmwL94Es= +google.golang.org/api v0.122.0/go.mod h1:gcitW0lvnyWjSp9nKxAbdHKIZ6vF4aajGueeslZOyms= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1250,8 +1261,8 @@ google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20230323212658-478b75c54725 h1:VmCWItVXcKboEMCwZaWge+1JLiTCQSngZeINF+wzO+g= -google.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1274,8 +1285,9 @@ google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag= -google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag= +google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= From 483e6f5c33b4a5706f11f9d0774aeee86ca85aba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 31 May 2023 09:44:19 +0200 Subject: [PATCH 1714/2916] chore(deps): Bump github.com/stretchr/testify from 1.8.3 to 1.8.4 (#521) Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.3 to 1.8.4. - [Release notes](https://github.com/stretchr/testify/releases) - [Commits](https://github.com/stretchr/testify/compare/v1.8.3...v1.8.4) --- updated-dependencies: - dependency-name: github.com/stretchr/testify dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index ad7176a81..c7d16d423 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,7 @@ require ( github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.16.0 - github.com/stretchr/testify v1.8.3 + github.com/stretchr/testify v1.8.4 github.com/xanzy/ssh-agent v0.3.3 golang.org/x/crypto v0.9.0 golang.org/x/net v0.10.0 diff --git a/go.sum b/go.sum index d229ef45a..77124abb6 100644 --- a/go.sum +++ b/go.sum @@ -801,8 +801,9 @@ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1F github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= From a9f3eac34673c86969ab4b5f2d0fde05c1494f68 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 31 May 2023 11:14:40 +0200 Subject: [PATCH 1715/2916] fix: Directly use unstructured.Unstructured when getting legacy KD object (#522) --- pkg/controllers/kluctldeployment_controller.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/pkg/controllers/kluctldeployment_controller.go b/pkg/controllers/kluctldeployment_controller.go index 201c3ab8d..cd7b4ce47 100644 --- a/pkg/controllers/kluctldeployment_controller.go +++ b/pkg/controllers/kluctldeployment_controller.go @@ -14,10 +14,10 @@ import ( "github.com/kluctl/kluctl/v2/pkg/types/result" "github.com/kluctl/kluctl/v2/pkg/utils/flux_utils/meta" "github.com/kluctl/kluctl/v2/pkg/utils/flux_utils/metrics" - "github.com/kluctl/kluctl/v2/pkg/utils/uo" "k8s.io/apimachinery/pkg/api/errors" meta2 "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/discovery" @@ -524,15 +524,17 @@ func (r *KluctlDeploymentReconciler) doFinalize(ctx context.Context, obj *kluctl func (r *KluctlDeploymentReconciler) checkLegacyKluctlDeployment(ctx context.Context, obj *kluctlv1.KluctlDeployment) bool { log := ctrl.LoggerFrom(ctx) - obj2 := uo.New() - obj2.SetK8sGVK(schema.GroupVersionKind{ + var obj2 unstructured.Unstructured + obj2.SetGroupVersionKind(schema.GroupVersionKind{ Group: "flux.kluctl.io", Version: "v1alpha1", Kind: "KluctlDeployment", }) - err := r.Get(ctx, client.ObjectKeyFromObject(obj), obj2.ToUnstructured()) + err := r.Get(ctx, client.ObjectKeyFromObject(obj), &obj2) if err != nil { - err = errors2.Unwrap(err) + if errors2.Unwrap(err) != nil { + err = errors2.Unwrap(err) + } if meta2.IsNoMatchError(err) || errors.IsNotFound(err) || discovery.IsGroupDiscoveryFailedError(err) { // legacy object not present, we're safe to continue return false @@ -542,7 +544,7 @@ func (r *KluctlDeploymentReconciler) checkLegacyKluctlDeployment(ctx context.Con return true } - readyForMigration, _, err := obj2.GetNestedBool("") + readyForMigration, _, err := unstructured.NestedBool(obj2.Object, "status", "readyForMigration") if err != nil { // some unexpected error...we should be on the safe side and bail out reconciliation log.Error(err, "Failed to retrieve readyForMigration value. Skipping reconciliation.") From 8ef0c58c3452d91ac3094cdb6dc479830445bade Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 31 May 2023 13:26:48 +0200 Subject: [PATCH 1716/2916] fix: Update LastHandledReconcileAt much earlier --- pkg/controllers/kluctldeployment_controller.go | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/pkg/controllers/kluctldeployment_controller.go b/pkg/controllers/kluctldeployment_controller.go index cd7b4ce47..2aa95c042 100644 --- a/pkg/controllers/kluctldeployment_controller.go +++ b/pkg/controllers/kluctldeployment_controller.go @@ -118,6 +118,15 @@ func (r *KluctlDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Req defer r.MetricsRecorder.RecordDuration(*objRef, reconcileStart) } + // record the value of the reconciliation request, if any + if v, ok := obj.GetAnnotations()[kluctlv1.KluctlRequestReconcileAnnotation]; ok { + patch := client.MergeFrom(obj.DeepCopy()) + obj.Status.LastHandledReconcileAt = v + if err := r.Status().Patch(ctx, obj, patch, client.FieldOwner(r.ControllerName)); err != nil { + return ctrl.Result{}, err + } + } + // set the reconciliation status to progressing if obj.Status.ObservedGeneration == 0 { patch := client.MergeFrom(obj.DeepCopy()) @@ -130,12 +139,6 @@ func (r *KluctlDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Req } patch := client.MergeFrom(obj.DeepCopy()) - - // record the value of the reconciliation request, if any - if v, ok := obj.GetAnnotations()[kluctlv1.KluctlRequestReconcileAnnotation]; ok { - obj.Status.LastHandledReconcileAt = v - } - // reconcile kluctlDeployment by applying the latest revision ctrlResult, reconcileErr := r.doReconcile(ctx, obj) if err := r.Status().Patch(ctx, obj, patch, client.FieldOwner(r.ControllerName)); err != nil { From af2f67edceaf0402660b9a476f6a12f9cf7e5d1a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 31 May 2023 13:27:19 +0200 Subject: [PATCH 1717/2916] feat: Set readiness status while migrating --- api/v1beta1/condition_types.go | 3 ++ .../kluctldeployment_controller.go | 47 ++++++++++++++----- 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/api/v1beta1/condition_types.go b/api/v1beta1/condition_types.go index e559651c3..9bd45ed52 100644 --- a/api/v1beta1/condition_types.go +++ b/api/v1beta1/condition_types.go @@ -35,4 +35,7 @@ const ( // ReconciliationSucceededReason represents the fact that // the reconciliation succeeded. ReconciliationSucceededReason string = "ReconciliationSucceeded" + + // WaitingForLegacyMigrationReason means that the controller is waiting for the legacy controller to set `readyForMigration=true` + WaitingForLegacyMigrationReason string = "WaitingForLegacyMigration" ) diff --git a/pkg/controllers/kluctldeployment_controller.go b/pkg/controllers/kluctldeployment_controller.go index 2aa95c042..cdfa9f903 100644 --- a/pkg/controllers/kluctldeployment_controller.go +++ b/pkg/controllers/kluctldeployment_controller.go @@ -14,6 +14,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/types/result" "github.com/kluctl/kluctl/v2/pkg/utils/flux_utils/meta" "github.com/kluctl/kluctl/v2/pkg/utils/flux_utils/metrics" + log "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/api/errors" meta2 "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -62,7 +63,6 @@ type KluctlDeploymentReconcilerOpts struct { // +kubebuilder:rbac:groups="",resources=events,verbs=create;patch func (r *KluctlDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - log := ctrl.LoggerFrom(ctx) reconcileStart := time.Now() ctx = status.NewContext(ctx, status.NewSimpleStatusHandler(func(message string) { @@ -99,10 +99,6 @@ func (r *KluctlDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Req return r.finalize(ctx, obj) } - if r.checkLegacyKluctlDeployment(ctx, obj) { - return ctrl.Result{}, nil - } - // Return early if the KluctlDeployment is suspended. if obj.Spec.Suspend { log.Info("Reconciliation is suspended for this object") @@ -180,6 +176,8 @@ func (r *KluctlDeploymentReconciler) doReconcile( ctx context.Context, obj *kluctlv1.KluctlDeployment) (*ctrl.Result, error) { + log := ctrl.LoggerFrom(ctx) + r.exportDeploymentObjectToProm(obj) doFail := func(reason string, err error) (*ctrl.Result, error) { @@ -192,6 +190,31 @@ func (r *KluctlDeploymentReconciler) doReconcile( return doFail(kluctlv1.PrepareFailedReason, err) } + legacyKd, err := r.checkLegacyKluctlDeployment(ctx, obj) + if err != nil { + return nil, err + } + if legacyKd { + c := meta2.FindStatusCondition(obj.Status.Conditions, meta.ReadyCondition) + if c != nil && c.Reason == kluctlv1.WaitingForLegacyMigrationReason { + log.Info("legacy KluctlDeployment does not have the readyForMigration status set. Skipping reconciliation. ") + return &ctrl.Result{RequeueAfter: 5 * time.Second}, err + } + log.Info("legacy KluctlDeployment does not have the readyForMigration status set. Skipping reconciliation. " + + "Please ensure that you have upgraded to the latest version of the legacy flux-kluctl-controller and that is is still running.") + setReadiness(obj, metav1.ConditionFalse, kluctlv1.WaitingForLegacyMigrationReason, "waiting for the legacy controller to set readyForMigration=true") + r.recordReadiness(ctx, obj) + return &ctrl.Result{Requeue: true}, nil + } else { + c := meta2.FindStatusCondition(obj.Status.Conditions, meta.ReadyCondition) + if c != nil && c.Reason == kluctlv1.WaitingForLegacyMigrationReason { + log.Info("legacy KluctlDeployment has the readyForMigration status set now") + setReadiness(obj, metav1.ConditionFalse, meta.ProgressingReason, "migration is finished") + r.recordReadiness(ctx, obj) + return &ctrl.Result{Requeue: true}, nil + } + } + oldGeneration := obj.Status.ObservedGeneration obj.Status.ObservedGeneration = obj.GetGeneration() if v, ok := obj.GetAnnotations()[kluctlv1.KluctlRequestDeployAnnotation]; ok { @@ -524,7 +547,7 @@ func (r *KluctlDeploymentReconciler) doFinalize(ctx context.Context, obj *kluctl // checkLegacyKluctlDeployment checks if a legacy KluctlDeployment from the old flux.kluctl.io group is present. If yes // we must ensure that this object is served by a recent legacy controller version which understands that it should stop // reconciliation in case the new gitops.kluctl.io object is present -func (r *KluctlDeploymentReconciler) checkLegacyKluctlDeployment(ctx context.Context, obj *kluctlv1.KluctlDeployment) bool { +func (r *KluctlDeploymentReconciler) checkLegacyKluctlDeployment(ctx context.Context, obj *kluctlv1.KluctlDeployment) (bool, error) { log := ctrl.LoggerFrom(ctx) var obj2 unstructured.Unstructured @@ -540,26 +563,24 @@ func (r *KluctlDeploymentReconciler) checkLegacyKluctlDeployment(ctx context.Con } if meta2.IsNoMatchError(err) || errors.IsNotFound(err) || discovery.IsGroupDiscoveryFailedError(err) { // legacy object not present, we're safe to continue - return false + return false, nil } log.Error(err, "Failed to retrieve legacy KluctlDeployment. Skipping reconciliation.") // some unexpected error...we should be on the safe side and bail out reconciliation - return true + return true, err } readyForMigration, _, err := unstructured.NestedBool(obj2.Object, "status", "readyForMigration") if err != nil { // some unexpected error...we should be on the safe side and bail out reconciliation log.Error(err, "Failed to retrieve readyForMigration value. Skipping reconciliation.") - return true + return true, err } if !readyForMigration { - log.V(1).Info("legacy KluctlDeployment does not have the readyForMigration status set. Skipping reconciliation. " + - "Please ensure that you have upgraded to the latest version of the legacy flux-kluctl-controller and that is is still running.") - return true + return true, nil } - return false + return false, nil } func (r *KluctlDeploymentReconciler) exportDeploymentObjectToProm(obj *kluctlv1.KluctlDeployment) { From 330d9f418c8af9d8caee28efaf199853f77243ac Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 31 May 2023 13:54:17 +0200 Subject: [PATCH 1718/2916] fix: Move LastHandledReconcileAt and ObservedGeneration handling into doReconcile --- .../kluctldeployment_controller.go | 31 +++++++------------ 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/pkg/controllers/kluctldeployment_controller.go b/pkg/controllers/kluctldeployment_controller.go index cdfa9f903..2f584b49d 100644 --- a/pkg/controllers/kluctldeployment_controller.go +++ b/pkg/controllers/kluctldeployment_controller.go @@ -114,26 +114,6 @@ func (r *KluctlDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Req defer r.MetricsRecorder.RecordDuration(*objRef, reconcileStart) } - // record the value of the reconciliation request, if any - if v, ok := obj.GetAnnotations()[kluctlv1.KluctlRequestReconcileAnnotation]; ok { - patch := client.MergeFrom(obj.DeepCopy()) - obj.Status.LastHandledReconcileAt = v - if err := r.Status().Patch(ctx, obj, patch, client.FieldOwner(r.ControllerName)); err != nil { - return ctrl.Result{}, err - } - } - - // set the reconciliation status to progressing - if obj.Status.ObservedGeneration == 0 { - patch := client.MergeFrom(obj.DeepCopy()) - setReadiness(obj, metav1.ConditionUnknown, meta.ProgressingReason, "reconciliation in progress") - if err := r.Status().Patch(ctx, obj, patch, client.FieldOwner(r.ControllerName)); err != nil { - return ctrl.Result{Requeue: true}, err - } - - r.recordReadiness(ctx, obj) - } - patch := client.MergeFrom(obj.DeepCopy()) // reconcile kluctlDeployment by applying the latest revision ctrlResult, reconcileErr := r.doReconcile(ctx, obj) @@ -190,6 +170,11 @@ func (r *KluctlDeploymentReconciler) doReconcile( return doFail(kluctlv1.PrepareFailedReason, err) } + // record the value of the reconciliation request, if any + if v, ok := obj.GetAnnotations()[kluctlv1.KluctlRequestReconcileAnnotation]; ok { + obj.Status.LastHandledReconcileAt = v + } + legacyKd, err := r.checkLegacyKluctlDeployment(ctx, obj) if err != nil { return nil, err @@ -215,6 +200,12 @@ func (r *KluctlDeploymentReconciler) doReconcile( } } + // set the reconciliation status to progressing + if obj.Status.ObservedGeneration == 0 { + setReadiness(obj, metav1.ConditionUnknown, meta.ProgressingReason, "reconciliation in progress") + r.recordReadiness(ctx, obj) + } + oldGeneration := obj.Status.ObservedGeneration obj.Status.ObservedGeneration = obj.GetGeneration() if v, ok := obj.GetAnnotations()[kluctlv1.KluctlRequestDeployAnnotation]; ok { From bdc40813ea7ab7e7cad471788e6cbe4a3ef8ccdb Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 31 May 2023 13:27:35 +0200 Subject: [PATCH 1719/2916] tests: Add migration tests --- e2e/default_clusters.go | 4 +- e2e/gitops_migration_test.go | 119 ++++ e2e/seal_test.go | 2 +- .../flux.kluctl.io_kluctldeployments.yaml | 659 ++++++++++++++++++ e2e/test_resources/resources.go | 20 +- 5 files changed, 796 insertions(+), 8 deletions(-) create mode 100644 e2e/gitops_migration_test.go create mode 100644 e2e/test_resources/flux.kluctl.io_kluctldeployments.yaml diff --git a/e2e/default_clusters.go b/e2e/default_clusters.go index 373957a89..964029bcd 100644 --- a/e2e/default_clusters.go +++ b/e2e/default_clusters.go @@ -30,7 +30,7 @@ func init() { if err != nil { panic(err) } - test_resources.ApplyYaml("sealed-secrets.yaml", defaultCluster1) + test_resources.ApplyYaml(nil, "sealed-secrets.yaml", defaultCluster1) }() go func() { defer wg.Done() @@ -41,7 +41,7 @@ func init() { if err != nil { panic(err) } - test_resources.ApplyYaml("sealed-secrets.yaml", defaultCluster2) + test_resources.ApplyYaml(nil, "sealed-secrets.yaml", defaultCluster2) }() wg.Wait() diff --git a/e2e/gitops_migration_test.go b/e2e/gitops_migration_test.go new file mode 100644 index 000000000..283f459aa --- /dev/null +++ b/e2e/gitops_migration_test.go @@ -0,0 +1,119 @@ +package e2e + +import ( + "context" + "fmt" + kluctlv1 "github.com/kluctl/kluctl/v2/api/v1beta1" + test_utils "github.com/kluctl/kluctl/v2/e2e/test-utils" + "github.com/kluctl/kluctl/v2/e2e/test_resources" + meta2 "github.com/kluctl/kluctl/v2/pkg/utils/flux_utils/meta" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/stretchr/testify/assert" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/client" + "time" +) +import . "github.com/onsi/gomega" + +var legacyGvk = schema.GroupVersionKind{ + Group: "flux.kluctl.io", + Version: "v1alpha1", + Kind: "KluctlDeployment", +} + +func (suite *GitopsTestSuite) createLegacyKluctlDeployment(p *test_utils.TestProject, target string) client.ObjectKey { + gitopsNs := p.TestSlug() + "-gitops" + createNamespace(suite.T(), suite.k, gitopsNs) + + kluctlDeployment := uo.New() + kluctlDeployment.SetK8sGVK(legacyGvk) + + kluctlDeployment.SetK8sName(p.TestSlug()) + kluctlDeployment.SetK8sNamespace(gitopsNs) + _ = kluctlDeployment.SetNestedField(map[string]any{ + "interval": interval.String(), + "deployInterval": "never", + "timeout": timeout.String(), + "target": target, + "source": map[string]any{ + "url": p.GitUrl(), + }, + }, "spec") + + err := suite.k.Client.Create(context.Background(), kluctlDeployment.ToUnstructured()) + if err != nil { + suite.T().Fatal(err) + } + + key := client.ObjectKeyFromObject(kluctlDeployment.ToUnstructured()) + return key +} + +func (suite *GitopsTestSuite) TestGitOpsLegacyMigration() { + g := NewWithT(suite.T()) + + test_resources.ApplyYaml(suite.T(), "flux.kluctl.io_kluctldeployments.yaml", suite.k) + suite.T().Cleanup(func() { + obj := uo.New() + obj.SetK8sGVK(apiextensionsv1.SchemeGroupVersion.WithKind("CustomResourceDefinition")) + obj.SetK8sName("kluctldeployments.flux.kluctl.io") + _ = suite.k.Client.Delete(context.TODO(), obj.ToUnstructured()) + }) + + p := test_utils.NewTestProject(suite.T()) + createNamespace(suite.T(), suite.k, p.TestSlug()) + + p.UpdateTarget("target1", nil) + p.AddKustomizeDeployment("d1", []test_utils.KustomizeResource{ + {Name: "cm1.yaml", Content: uo.FromStringMust(fmt.Sprintf(`apiVersion: v1 +kind: ConfigMap +metadata: + name: cm1 + namespace: "%s" +data: + k1: v1 +`, p.TestSlug()))}, + }, nil) + + key := suite.createLegacyKluctlDeployment(p, "target1") + key2 := suite.createKluctlDeployment(p, "target1", nil) + assert.Equal(suite.T(), key, key2) + + checkMigrationStatus := func() bool { + var kd kluctlv1.KluctlDeployment + _ = suite.k.Client.Get(context.Background(), key, &kd) + c := meta.FindStatusCondition(kd.Status.Conditions, meta2.ReadyCondition) + return c != nil && c.Reason == kluctlv1.WaitingForLegacyMigrationReason + } + + suite.Run("wait for migration to start", func() { + g.Eventually(checkMigrationStatus, timeout, time.Second).Should(BeTrue()) + }) + + suite.Run("stays in migration state", func() { + suite.triggerReconcile(key) + g.Consistently(checkMigrationStatus, 3*time.Second, time.Second).Should(BeTrue()) + }) + + suite.Run("legacy controller marks the deployment with readyForMigration", func() { + var obj unstructured.Unstructured + obj.SetGroupVersionKind(legacyGvk) + err := suite.k.Client.Get(context.TODO(), key, &obj) + g.Expect(err).To(Succeed()) + + _ = unstructured.SetNestedField(obj.Object, true, "status", "readyForMigration") + err = suite.k.Client.Status().Update(context.TODO(), &obj) + g.Expect(err).To(Succeed()) + }) + + suite.Run("wait for migration to end", func() { + g.Eventually(checkMigrationStatus, timeout, time.Second).Should(BeFalse()) + }) + + suite.Run("initial deployment", func() { + suite.waitForCommit(key, getHeadRevision(suite.T(), p)) + }) +} diff --git a/e2e/seal_test.go b/e2e/seal_test.go index e0cfab88d..aa21459fb 100644 --- a/e2e/seal_test.go +++ b/e2e/seal_test.go @@ -216,7 +216,7 @@ func TestSeal_WithBootstrap(t *testing.T) { sealedSecretsDir := p.LocalProjectDir() assert.FileExists(t, filepath.Join(sealedSecretsDir, ".sealed-secrets/secret-deployment/test-target/secret-secret.yml")) - test_resources.ApplyYaml("sealed-secrets.yaml", k) + test_resources.ApplyYaml(t, "sealed-secrets.yaml", k) p.KluctlMust("deploy", "--yes", "-t", "test-target") diff --git a/e2e/test_resources/flux.kluctl.io_kluctldeployments.yaml b/e2e/test_resources/flux.kluctl.io_kluctldeployments.yaml new file mode 100644 index 000000000..fe7d3b8d6 --- /dev/null +++ b/e2e/test_resources/flux.kluctl.io_kluctldeployments.yaml @@ -0,0 +1,659 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + name: kluctldeployments.flux.kluctl.io +spec: + group: flux.kluctl.io + names: + kind: KluctlDeployment + listKind: KluctlDeploymentList + plural: kluctldeployments + singular: kluctldeployment + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.dryRun + name: DryRun + type: boolean + - jsonPath: .status.lastDeployResult.time + name: Deployed + type: date + - jsonPath: .status.lastPruneResult.time + name: Pruned + type: date + - jsonPath: .status.lastValidateResult.time + name: Validated + type: date + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].message + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: KluctlDeployment is the Schema for the kluctldeployments API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + abortOnError: + default: false + description: ForceReplaceOnError instructs kluctl to abort deployments + immediately when something fails. Equivalent to using '--abort-on-error' + when calling kluctl. + type: boolean + args: + description: Args specifies dynamic target args. + type: object + x-kubernetes-preserve-unknown-fields: true + context: + description: If specified, overrides the context to be used. This + will effectively make kluctl ignore the context specified in the + target. + type: string + decryption: + description: Decrypt Kubernetes secrets before applying them on the + cluster. + properties: + provider: + description: Provider is the name of the decryption engine. + enum: + - sops + type: string + secretRef: + description: The secret name containing the private OpenPGP keys + used for decryption. + properties: + name: + description: Name of the referent. + type: string + required: + - name + type: object + serviceAccount: + description: ServiceAccount specifies the service account used + to authenticate against cloud providers. This is currently only + usable for AWS KMS keys. The specified service account will + be used to authenticate to AWS by signing a token in an IRSA + compliant way. + type: string + required: + - provider + type: object + delete: + default: false + description: Delete enables deletion of the specified target when + the KluctlDeployment object gets deleted. + type: boolean + deployInterval: + description: DeployInterval specifies the interval at which to deploy + the KluctlDeployment. It defaults to the Interval value, meaning + that it will re-deploy on every reconciliation. If you set DeployInterval + to a different value, + pattern: ^(([0-9]+(\.[0-9]+)?(ms|s|m|h))+)|never$ + type: string + deployMode: + default: full-deploy + description: DeployMode specifies what deploy mode should be used. + The options 'full-deploy' and 'poke-images' are supported. With + 'poke images' option, only the images from the fixed images are + exchanged and no complete deployment is triggered. + enum: + - full-deploy + - poke-images + type: string + deployOnChanges: + default: true + description: DeployOnChanges will cause a re-deployment whenever the + rendered resources change in the deployment. This check is performed + on every reconciliation. This means that a deployment will be triggered + even before the DeployInterval has passed in case something has + changed in the rendered resources. + type: boolean + dryRun: + default: false + description: DryRun instructs kluctl to run everything in dry-run + mode. Equivalent to using '--dry-run' when calling kluctl. + type: boolean + excludeDeploymentDirs: + description: ExcludeDeploymentDirs instructs kluctl to exclude deployments + with the given dir. Equivalent to using '--exclude-deployment-dir' + when calling kluctl. + items: + type: string + type: array + excludeTags: + description: ExcludeTags instructs kluctl to exclude deployments with + given tags. Equivalent to using '--exclude-tag' when calling kluctl. + items: + type: string + type: array + forceApply: + default: false + description: ForceApply instructs kluctl to force-apply in case of + SSA conflicts. Equivalent to using '--force-apply' when calling + kluctl. + type: boolean + forceReplaceOnError: + default: false + description: ForceReplaceOnError instructs kluctl to force-replace + resources in case a normal replace fails. Equivalent to using '--force-replace-on-error' + when calling kluctl. + type: boolean + helmCredentials: + description: HelmCredentials is a list of Helm credentials used when + non pre-pulled Helm Charts are used inside a Kluctl deployment. + items: + properties: + secretRef: + description: 'SecretRef holds the name of a secret that contains + the Helm credentials. The secret must either contain the fields + `credentialsId` which refers to the credentialsId found in + https://kluctl.io/docs/kluctl/reference/deployments/helm/#private-chart-repositories + or an `url` used to match the credentials found in Kluctl + projects helm-chart.yaml files. The secret can either container + basic authentication credentials via `username` and `password` + or TLS authentication via `certFile` and `keyFile`. `caFile` + can be specified to override the CA to use while contacting + the repository. The secret can also contain `insecureSkipTlsVerify: + "true"`, which will disable TLS verification. `passCredentialsAll: + "true"` can be specified to make the controller pass credentials + to all requests, even if the hostname changes in-between.' + properties: + name: + description: Name of the referent. + type: string + required: + - name + type: object + type: object + type: array + images: + description: Images contains a list of fixed image overrides. Equivalent + to using '--fixed-images-file' when calling kluctl. + items: + properties: + container: + type: string + deployTags: + items: + type: string + type: array + deployedImage: + type: string + deployment: + type: string + deploymentDir: + type: string + image: + type: string + namespace: + type: string + object: + description: ObjectRef contains the information necessary to + locate a resource within a cluster. + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + required: + - group + - kind + - name + - namespace + - version + type: object + registryImage: + type: string + resultImage: + type: string + versionFilter: + type: string + required: + - image + - resultImage + type: object + type: array + includeDeploymentDirs: + description: IncludeDeploymentDirs instructs kluctl to only include + deployments with the given dir. Equivalent to using '--include-deployment-dir' + when calling kluctl. + items: + type: string + type: array + includeTags: + description: IncludeTags instructs kluctl to only include deployments + with given tags. Equivalent to using '--include-tag' when calling + kluctl. + items: + type: string + type: array + interval: + description: The interval at which to reconcile the KluctlDeployment. + By default, the controller will re-deploy and validate the deployment + on each reconciliation. To override this behavior, change the DeployInterval + and/or ValidateInterval values. + pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ + type: string + kubeConfig: + description: The KubeConfig for deploying to the target cluster. Specifies + the kubeconfig to be used when invoking kluctl. Contexts in this + kubeconfig must match the context found in the kluctl target. As + an alternative, specify the context to be used via 'context' + properties: + secretRef: + description: SecretRef holds the name of a secret that contains + a key with the kubeconfig file as the value. If no key is set, + the key will default to 'value'. The secret must be in the same + namespace as the Kustomization. It is recommended that the kubeconfig + is self-contained, and the secret is regularly updated if credentials + such as a cloud-access-token expire. Cloud specific `cmd-path` + auth helpers will not function without adding binaries and credentials + to the Pod that is responsible for reconciling the KluctlDeployment. + properties: + key: + description: Key in the Secret, when not specified an implementation-specific + default key is used. + type: string + name: + description: Name of the Secret. + type: string + required: + - name + type: object + type: object + noWait: + default: false + description: NoWait instructs kluctl to not wait for any resources + to become ready, including hooks. Equivalent to using '--no-wait' + when calling kluctl. + type: boolean + path: + description: 'Path to the directory containing the .kluctl.yaml file, + or the Defaults to ''None'', which translates to the root path of + the SourceRef. Deprecated: Use source.path instead' + type: string + prune: + default: false + description: Prune enables pruning after deploying. + type: boolean + registrySecrets: + description: DEPRECATED RegistrySecrets is a list of secret references + to be used for image registry authentication. The secrets must either + have ".dockerconfigjson" included or "registry", "username" and + "password". Additionally, "caFile" and "insecure" can be specified. + Kluctl has deprecated querying the registry at deploy time and thus + this field is also deprecated. + items: + description: LocalObjectReference contains enough information to + locate the referenced Kubernetes resource object. + properties: + name: + description: Name of the referent. + type: string + required: + - name + type: object + type: array + renameContexts: + description: RenameContexts specifies a list of context rename operations. + This is useful when the kluctl target's context does not match with + the contexts found in the kubeconfig while deploying. This is the + case when using kubeconfigs generated from service accounts, in + which case the context name is always "default". + items: + description: RenameContext specifies a single rename of a context + properties: + newContext: + description: NewContext is the new name of the context + type: string + oldContext: + description: OldContext is the name of the context to be renamed + type: string + required: + - newContext + - oldContext + type: object + type: array + replaceOnError: + default: false + description: ReplaceOnError instructs kluctl to replace resources + on error. Equivalent to using '--replace-on-error' when calling + kluctl. + type: boolean + retryInterval: + description: The interval at which to retry a previously failed reconciliation. + When not specified, the controller uses the Interval value to retry + failures. + pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ + type: string + serviceAccountName: + description: The name of the Kubernetes service account to use while + deploying. If not specified, the default service account is used. + type: string + source: + description: Specifies the project source location + properties: + path: + description: Path specifies the sub-directory to be used as project + directory + type: string + ref: + description: Ref specifies the branch, tag or commit that should + be used. If omitted, the default branch of the repo is used. + properties: + branch: + description: Branch to filter for. Can also be a regex. + type: string + tag: + description: Branch to filter for. Can also be a regex. + type: string + type: object + secretRef: + description: SecretRef specifies the Secret containing authentication + credentials for the git repository. For HTTPS repositories the + Secret must contain 'username' and 'password' fields. For SSH + repositories the Secret must contain 'identity' and 'known_hosts' + fields. + properties: + name: + description: Name of the referent. + type: string + required: + - name + type: object + url: + description: Url specifies the Git url where the project source + is located + type: string + required: + - url + type: object + sourceRef: + description: 'Reference of the source where the kluctl project is. + The authentication secrets from the source are also used to authenticate + dependent git repositories which are cloned while deploying the + kluctl project. Deprecated: Use source instead' + properties: + apiVersion: + description: API version of the referent, if not specified the + Kubernetes preferred version will be used. + type: string + kind: + description: Kind of the referent. + type: string + name: + description: Name of the referent. + type: string + namespace: + description: Namespace of the referent, when not specified it + acts as LocalObjectReference. + type: string + required: + - kind + - name + type: object + suspend: + description: This flag tells the controller to suspend subsequent + kluctl executions, it does not apply to already started executions. + Defaults to false. + type: boolean + target: + description: Target specifies the kluctl target to deploy. If not + specified, an empty target is used that has no name and no context. + Use 'TargetName' and 'Context' to specify the name and context in + that case. + maxLength: 63 + minLength: 1 + type: string + targetNameOverride: + description: TargetNameOverride sets or overrides the target name. + This is especially useful when deployment without a target. + maxLength: 63 + minLength: 1 + type: string + timeout: + description: Timeout for all operations. Defaults to 'Interval' duration. + pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ + type: string + updateImages: + default: false + description: DEPRECATED UpdateImages instructs kluctl to update dynamic + images. Equivalent to using '-u' when calling kluctl. Setting this + field to true is deprecated. + type: boolean + validate: + default: true + description: Validate enables validation after deploying + type: boolean + validateInterval: + description: ValidateInterval specifies the interval at which to validate + the KluctlDeployment. Validation is performed the same way as with + 'kluctl validate -t '. Defaults to the same value as specified + in Interval. Validate is also performed whenever a deployment is + performed, independent of the value of ValidateInterval + pattern: ^(([0-9]+(\.[0-9]+)?(ms|s|m|h))+)|never$ + type: string + required: + - interval + type: object + status: + description: KluctlDeploymentStatus defines the observed state of KluctlDeployment + properties: + conditions: + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n \ttype FooStatus struct{ \t // Represents the observations + of a foo's current state. \t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\" \t // + +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map + \t // +listMapKey=type \t Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields + \t}" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + discriminator: + description: Discriminator is the discriminator found in the target + when the last deployment was done. This is used to perform cleanup/deletion + in case the KluctlDeployment project is deleted + type: string + lastAttemptedRevision: + description: LastAttemptedRevision is the revision of the last reconciliation + attempt. + type: string + lastDeployResult: + description: LastDeployResult is the result of the last deploy command + properties: + error: + type: string + objectsHash: + description: ObjectsHash is the hash of all rendered objects + type: string + rawResult: + type: string + revision: + description: Revision is the source revision. Please note that + kluctl projects have dependent git repositories which are not + considered in the source revision + type: string + target: + type: string + targetNameOverride: + type: string + time: + description: AttemptedAt is the time when the attempt was performed + format: date-time + type: string + required: + - time + type: object + lastHandledDeployAt: + type: string + lastHandledReconcileAt: + description: LastHandledReconcileAt holds the value of the most recent + reconcile request value, so a change of the annotation value can + be detected. + type: string + lastPruneResult: + description: LastDeployResult is the result of the last prune command + properties: + error: + type: string + objectsHash: + description: ObjectsHash is the hash of all rendered objects + type: string + rawResult: + type: string + revision: + description: Revision is the source revision. Please note that + kluctl projects have dependent git repositories which are not + considered in the source revision + type: string + target: + type: string + targetNameOverride: + type: string + time: + description: AttemptedAt is the time when the attempt was performed + format: date-time + type: string + required: + - time + type: object + lastValidateResult: + description: LastValidateResult is the result of the last validate + command + properties: + error: + type: string + objectsHash: + description: ObjectsHash is the hash of all rendered objects + type: string + rawResult: + type: string + revision: + description: Revision is the source revision. Please note that + kluctl projects have dependent git repositories which are not + considered in the source revision + type: string + target: + type: string + targetNameOverride: + type: string + time: + description: AttemptedAt is the time when the attempt was performed + format: date-time + type: string + required: + - time + type: object + observedGeneration: + description: ObservedGeneration is the last reconciled generation. + format: int64 + type: integer + rawTarget: + type: string + readyForMigration: + description: ReadyForMigration is used to signal the new controller + that this object is handled by a legacy controller version that + will honor the existence of KluctlDeployment objects from the gitops.kluctl.io + group. + type: boolean + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/e2e/test_resources/resources.go b/e2e/test_resources/resources.go index 23dad66c9..0d7b050c2 100644 --- a/e2e/test_resources/resources.go +++ b/e2e/test_resources/resources.go @@ -3,6 +3,7 @@ package test_resources import ( "context" "embed" + "fmt" "github.com/kluctl/kluctl/v2/e2e/test-utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/kluctl/kluctl/v2/pkg/validation" @@ -14,6 +15,7 @@ import ( "sort" "strings" "sync" + "testing" "time" ) @@ -79,20 +81,28 @@ func waitReadiness(k *test_utils.EnvTestCluster, x *uo.UnstructuredObject) { } } -func ApplyYaml(name string, k *test_utils.EnvTestCluster) { +func ApplyYaml(t *testing.T, name string, k *test_utils.EnvTestCluster) { tmpFile := GetYamlTmpFile(name) defer os.Remove(tmpFile) + doPanic := func(err error) { + if t != nil { + t.Fatal(err) + } else { + panic(err) + } + } + docs, err := yaml.ReadYamlAllFile(tmpFile) if err != nil { - panic(err) + doPanic(err) } var objects []*uo.UnstructuredObject for _, doc := range docs { m, ok := doc.(map[string]any) if !ok { - panic("not a map!") + doPanic(fmt.Errorf("not a map!")) } x := uo.FromMap(m) objects = append(objects, x) @@ -119,7 +129,7 @@ func ApplyYaml(name string, k *test_utils.EnvTestCluster) { data, err := yaml.WriteYamlBytes(x) if err != nil { - panic(err) + doPanic(err) } gvr := guessGVR(x.GetK8sGVK()) @@ -129,7 +139,7 @@ func ApplyYaml(name string, k *test_utils.EnvTestCluster) { FieldManager: "e2e-tests", }) if err != nil { - panic(err) + doPanic(err) } // wait for CRDs to get accepted From c31d6a201833cb1f615de406566a94647eb7ff70 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 31 May 2023 15:37:59 +0200 Subject: [PATCH 1720/2916] fix: Fix inclusion of relative base dirs when the project is not at the repo root (#525) * refactor: Use LoadArgs directly instead of copying the ProjectDir into LoadedKluctlProject * fix: Allow all includes inside the same repo root * tests: Add tests for local includes --- cmd/kluctl/commands/cmd_seal.go | 2 +- e2e/deployment_items_test.go | 69 ++++++++++++++++++++++++++++ e2e/test-utils/project.go | 23 +++++++--- pkg/kluctl_project/project.go | 2 - pkg/kluctl_project/project_load.go | 8 ++-- pkg/kluctl_project/target_context.go | 10 ++-- 6 files changed, 96 insertions(+), 18 deletions(-) diff --git a/cmd/kluctl/commands/cmd_seal.go b/cmd/kluctl/commands/cmd_seal.go index c081a61f8..b272c5966 100644 --- a/cmd/kluctl/commands/cmd_seal.go +++ b/cmd/kluctl/commands/cmd_seal.go @@ -93,7 +93,7 @@ func (cmd *sealCmd) loadCert(cmdCtx *commandCtx) (*x509.Certificate, error) { var certFile string if sealingConfig != nil && sealingConfig.CertFile != nil { - path, err := securejoin.SecureJoin(cmdCtx.targetCtx.KluctlProject.ProjectDir, *sealingConfig.CertFile) + path, err := securejoin.SecureJoin(cmdCtx.targetCtx.KluctlProject.LoadArgs.ProjectDir, *sealingConfig.CertFile) if err != nil { return nil, err } diff --git a/e2e/deployment_items_test.go b/e2e/deployment_items_test.go index 855e20120..9f8dfdd96 100644 --- a/e2e/deployment_items_test.go +++ b/e2e/deployment_items_test.go @@ -5,6 +5,7 @@ import ( "github.com/kluctl/kluctl/v2/e2e/test-utils" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/stretchr/testify/assert" + "path/filepath" "testing" ) @@ -190,3 +191,71 @@ func TestTemplateIgnore(t *testing.T) { "k1": `{{ "a" }}`, }, cm3.Object["data"]) } + +func testLocalIncludes(t *testing.T, projectDir string) { + t.Parallel() + + k := defaultCluster1 + + p := test_utils.NewTestProject(t, test_utils.WithBareProject()) + + createNamespace(t, k, p.TestSlug()) + + p.UpdateDeploymentYaml("base", func(o *uo.UnstructuredObject) error { + *o = *uo.FromMap(map[string]interface{}{ + "deployments": []map[string]any{ + {"path": "cm"}, + }, + }) + return nil + }) + p.UpdateYaml("base/cm/cm.yaml", func(o *uo.UnstructuredObject) error { + *o = *createConfigMapObject(map[string]string{ + "d1": "v1", + }, resourceOpts{name: "{{ name }}", namespace: p.TestSlug()}) + return nil + }, "") + + baseDir, _ := filepath.Rel(filepath.Join(p.LocalProjectDir(), projectDir), filepath.Join(p.LocalRepoDir(), "base")) + baseDir = filepath.ToSlash(baseDir) + + p.UpdateDeploymentYaml(projectDir, func(o *uo.UnstructuredObject) error { + *o = *uo.FromMap(map[string]interface{}{ + "deployments": []map[string]any{ + { + "include": baseDir, + "vars": []map[string]any{ + { + "values": map[string]any{ + "name": "cm-inc1", + }, + }, + }, + }, + { + "include": baseDir, + "vars": []map[string]any{ + { + "values": map[string]any{ + "name": "cm-inc2", + }, + }, + }, + }, + }, + }) + return nil + }) + + p.KluctlMust("deploy", "--yes", "--project-dir", filepath.Join(p.LocalProjectDir(), projectDir)) + assertConfigMapExists(t, k, p.TestSlug(), "cm-inc1") + assertConfigMapExists(t, k, p.TestSlug(), "cm-inc2") +} + +func TestIncludeLocalFromRoot(t *testing.T) { + testLocalIncludes(t, ".") +} + +func TestIncludeLocalFromSubdir(t *testing.T) { + testLocalIncludes(t, "foo") +} diff --git a/e2e/test-utils/project.go b/e2e/test-utils/project.go index e8c33d1ec..358fe1dfe 100644 --- a/e2e/test-utils/project.go +++ b/e2e/test-utils/project.go @@ -26,6 +26,7 @@ type TestProject struct { extraEnv []string useProcess bool + bare bool gitServer *git2.TestGitServer gitRepoName string @@ -58,6 +59,12 @@ func WithGitSubDir(subDir string) TestProjectOption { } } +func WithBareProject() TestProjectOption { + return func(p *TestProject) { + p.bare = true + } +} + func NewTestProject(t *testing.T, opts ...TestProjectOption) *TestProject { p := &TestProject{ t: t, @@ -73,13 +80,15 @@ func NewTestProject(t *testing.T, opts ...TestProjectOption) *TestProject { } p.gitServer.GitInit(p.gitRepoName) - p.UpdateKluctlYaml(func(o *uo.UnstructuredObject) error { - _ = o.SetNestedField(fmt.Sprintf("%s-{{ target.name or 'no-name' }}", rand.String(16)), "discriminator") - return nil - }) - p.UpdateDeploymentYaml(".", func(c *uo.UnstructuredObject) error { - return nil - }) + if !p.bare { + p.UpdateKluctlYaml(func(o *uo.UnstructuredObject) error { + _ = o.SetNestedField(fmt.Sprintf("%s-{{ target.name or 'no-name' }}", rand.String(16)), "discriminator") + return nil + }) + p.UpdateDeploymentYaml(".", func(c *uo.UnstructuredObject) error { + return nil + }) + } return p } diff --git a/pkg/kluctl_project/project.go b/pkg/kluctl_project/project.go index 7afc4918f..50aea59fc 100644 --- a/pkg/kluctl_project/project.go +++ b/pkg/kluctl_project/project.go @@ -18,8 +18,6 @@ type LoadedKluctlProject struct { TmpDir string - ProjectDir string - sealedSecretsDir string Config types2.KluctlProject diff --git a/pkg/kluctl_project/project_load.go b/pkg/kluctl_project/project_load.go index 5ed7aa53f..04493fb1d 100644 --- a/pkg/kluctl_project/project_load.go +++ b/pkg/kluctl_project/project_load.go @@ -27,7 +27,7 @@ type LoadKluctlProjectArgs struct { func (c *LoadedKluctlProject) getConfigPath() string { configPath := c.LoadArgs.ProjectConfig if configPath == "" { - p := yaml.FixPathExt(filepath.Join(c.ProjectDir, ".kluctl.yml")) + p := yaml.FixPathExt(filepath.Join(c.LoadArgs.ProjectDir, ".kluctl.yml")) if utils.IsFile(p) { configPath = p } @@ -38,10 +38,8 @@ func (c *LoadedKluctlProject) getConfigPath() string { func (c *LoadedKluctlProject) loadKluctlProject() error { var err error - c.ProjectDir = c.LoadArgs.ProjectDir - if c.LoadArgs.RepoRoot != "" { - err = utils.CheckInDir(c.LoadArgs.RepoRoot, c.ProjectDir) + err = utils.CheckInDir(c.LoadArgs.RepoRoot, c.LoadArgs.ProjectDir) if err != nil { return err } @@ -59,7 +57,7 @@ func (c *LoadedKluctlProject) loadKluctlProject() error { s := status.Start(c.ctx, "Loading kluctl project") defer s.Failed() - c.sealedSecretsDir = filepath.Join(c.ProjectDir, ".sealed-secrets") + c.sealedSecretsDir = filepath.Join(c.LoadArgs.ProjectDir, ".sealed-secrets") sealedSecretsUsed := false if c.Config.SecretsConfig != nil { diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index 93d92ca94..4761c9a0d 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -44,7 +44,11 @@ type TargetContextParams struct { } func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, params TargetContextParams) (*TargetContext, error) { - deploymentDir, err := filepath.Abs(p.ProjectDir) + repoRoot, err := filepath.Abs(p.LoadArgs.RepoRoot) + if err != nil { + return nil, err + } + relProjectDir, err := filepath.Rel(repoRoot, p.LoadArgs.ProjectDir) if err != nil { return nil, err } @@ -129,7 +133,7 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, params Targe DefaultSealedSecretsOutputPattern: target.Name, } - d, err := deployment.NewDeploymentProject(dctx, varsCtx, deployment.NewSource(deploymentDir), ".", nil) + d, err := deployment.NewDeploymentProject(dctx, varsCtx, deployment.NewSource(repoRoot), relProjectDir, nil) if err != nil { return nil, err } @@ -220,7 +224,7 @@ func (p *LoadedKluctlProject) findSecretsEntry(name string) (*types.SecretSet, e } func (p *LoadedKluctlProject) loadSecrets(target *types.Target, varsCtx *vars.VarsCtx, varsLoader *vars.VarsLoader) error { - searchDirs := []string{p.ProjectDir} + searchDirs := []string{p.LoadArgs.ProjectDir} for _, secretSetName := range target.SealingConfig.SecretSets { secretEntry, err := p.findSecretsEntry(secretSetName) From 1e8d63d562b3584294698912f45f33427b9ca036 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 31 May 2023 15:48:32 +0200 Subject: [PATCH 1721/2916] build: Preparing release v2.20.0 --- install/controller/.kluctl.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/controller/.kluctl.yaml b/install/controller/.kluctl.yaml index 7f7283c68..481c21a87 100644 --- a/install/controller/.kluctl.yaml +++ b/install/controller/.kluctl.yaml @@ -2,4 +2,4 @@ discriminator: kluctl.io-controller args: - name: controller_version - default: v0.0.0 \ No newline at end of file + default: v2.20.0 \ No newline at end of file From 600160b98a5ddd0c30d2c1524966e24f89dd7f1b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 26 May 2023 16:48:12 +0200 Subject: [PATCH 1722/2916] docs: Remove mentions of flux from source --- api/v1beta1/doc.go | 2 +- pkg/controllers/predicates.go | 16 ---------------- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/api/v1beta1/doc.go b/api/v1beta1/doc.go index 7dae3af55..5d3a94b0e 100644 --- a/api/v1beta1/doc.go +++ b/api/v1beta1/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package v1beta1 contains API Schema definitions for the flux.kluctl.io v1beta1 API group. +// Package v1beta1 contains API Schema definitions for the gitops.kluctl.io v1beta1 API group. // +kubebuilder:object:generate=true // +groupName=gitops.kluctl.io package v1beta1 diff --git a/pkg/controllers/predicates.go b/pkg/controllers/predicates.go index 1c6f52c30..feec05a2c 100644 --- a/pkg/controllers/predicates.go +++ b/pkg/controllers/predicates.go @@ -1,19 +1,3 @@ -/* -Copyright 2020 The Flux authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - package controllers import ( From d1c2ef53fe21160bd3f10d52ac481c46dfc9dccd Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 26 May 2023 16:48:22 +0200 Subject: [PATCH 1723/2916] chore: Remove unused code --- cmd/kluctl/args/flux.go | 34 ---------------------------------- 1 file changed, 34 deletions(-) delete mode 100644 cmd/kluctl/args/flux.go diff --git a/cmd/kluctl/args/flux.go b/cmd/kluctl/args/flux.go deleted file mode 100644 index a1307a16a..000000000 --- a/cmd/kluctl/args/flux.go +++ /dev/null @@ -1,34 +0,0 @@ -package args - -import ( - "k8s.io/apimachinery/pkg/runtime/schema" -) - -type KluctlDeploymentFlags struct { - KluctlDeployment string `group:"flux" short:"k" help:"Name of the KluctlDeployment to interact with"` - Namespace string `group:"flux" short:"n" help:"Namespace where KluctlDeployment is located"` - WithSource bool `group:"flux" help:"--with-source will annotate Source object as well, triggering pulling"` - NoWait bool `group:"flux" help:"Don't wait for objects readiness'"` -} - -var KluctlDeploymentGVK = schema.GroupVersionKind{ - Group: "flux.kluctl.io", - Version: "v1alpha1", - Kind: "KluctlDeployment", -} - -var GitRepositoryGVK = schema.GroupVersionKind{ - Group: "source.toolkit.fluxcd.io", - Version: "v1beta2", - Kind: "GitRepository", -} - -func (cmd *KluctlDeploymentFlags) VerifyFlags() bool { - if cmd.KluctlDeployment == "" { - return false - } - if cmd.Namespace == "" { - cmd.Namespace = "default" - } - return true -} From 68deddec416e75d0bbcd88d166f876194541bce5 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 26 May 2023 16:48:41 +0200 Subject: [PATCH 1724/2916] docs: Update readme to mention that flux-kluctl-controller is deprecated --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 51c58facb..33ed42f81 100644 --- a/README.md +++ b/README.md @@ -17,11 +17,16 @@ Kluctl is centered around "targets", which can be a cluster or a specific enviro or multiple clusters. Targets can be deployed, diffed, pruned, deleted, and so on. The idea is to have the same set of operations for every target, no matter how simple or complex the deployment and/or target is. -Kluctl does not depend on external operators/controllers and allows to use the same deployment wherever you want, +Kluctl does not strictly depend on a controller and allows to use the same deployment wherever you want, as long as access to the kluctl project and clusters is available. This means, that you can use it from your local machine, from your CI/CD pipelines or any automation platform/system that allows to call custom tools. -Flux support is in alpha stadium and available via the [flux-kluctl-controller](https://github.com/kluctl/flux-kluctl-controller). +If you want to follow a pull based GitOps flow, then you can use the Kluctl Controller, which then allows you to use +`KluctlDeployment` custom resources to define your Kluctl deployments. + +Please note: GitOps support was previously implemented via the now deprecated [flux-kluctl-controller](https://github.com/kluctl/flux-kluctl-controller). +Historically, the flux-kluctl-controller depended on the Flux ecosystem (the source-controller to be specific), which +has changed in the meantime, meaning that it runs completely independent and thus is not part of the Flux ecosystem anymore. ## What can I do with Kluctl? From 88a0bf0b67ae351213556d1b71eaca460c56424f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sat, 27 May 2023 23:52:01 +0200 Subject: [PATCH 1725/2916] docs: Add installation instructions for new controller --- docs/installation.md | 38 ++++++++++++++++++++++++++++++++------ hack/prepare-release.sh | 2 +- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/docs/installation.md b/docs/installation.md index bd42145bf..656d3d93c 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -10,13 +10,17 @@ description: "Installing kluctl." # Installation -## Binaries +Kluctl is available as a CLI and as a GitOps controller. + +## Installing the CLI + +### Binaries The kluctl CLI is available as a binary executable for all major platforms, the binaries can be downloaded form GitHub [releases page](https://github.com/kluctl/kluctl/releases). -## Installation with Homebrew +### Installation with Homebrew With [Homebrew](https://brew.sh) for macOS and Linux: @@ -24,7 +28,7 @@ With [Homebrew](https://brew.sh) for macOS and Linux: brew install kluctl/tap/kluctl ``` -## Installation with Bash +### Installation with Bash With [Bash](https://www.gnu.org/software/bash/) for macOS and Linux: @@ -38,7 +42,7 @@ The install script does the following: * copies the kluctl binary to `/usr/local/bin` * removes the temporary directory -## Build from source +### Build from source Clone the repository: @@ -61,7 +65,7 @@ Run the binary: -## Container images +### Container images A container image with `kluctl` is available on GitHub: * `ghcr.io/kluctl/kluctl:` + +## Installing the GitOps Controller + +The controller can be installed via two available options. + +### Using the "install" sub-command + +The [`kluctl controller install`](./reference/commands/controller-install.md) command can be used to install the +controller. It will use an embedded version of the Controller Kluctl deployment project +found [here](https://github.com/kluctl/kluctl/tree/main/install/controller). + +### Using a Kluctl deployment + +To manage and install the controller via Kluctl, you can use a Git include in your own deployment: + +```yaml +deployments: + - git: + url: https://github.com/kluctl/kluctl.git + subDir: install/controller + ref: v0.0.0 +``` diff --git a/hack/prepare-release.sh b/hack/prepare-release.sh index 609c006d5..496c9b6bd 100755 --- a/hack/prepare-release.sh +++ b/hack/prepare-release.sh @@ -24,7 +24,7 @@ fi echo VERSION=$VERSION -FILES="install/controller/.kluctl.yaml" +FILES="install/controller/.kluctl.yaml docs/installation.md" for f in $FILES; do cat $f | sed "s/$VERSION_REGEX_SED/$VERSION/g" > $f.tmp From 3fffac350b59b01ce710b4147a176e38fd28ad15 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sun, 28 May 2023 00:10:14 +0200 Subject: [PATCH 1726/2916] docs: Add GitOps docs --- docs/reference/README.md | 3 +- docs/reference/gitops/README.md | 107 ++++ docs/reference/gitops/metrics/README.md | 9 + .../gitops/metrics/v1beta1/README.md | 19 + .../v1beta1/kluctldeployment_controller.md | 29 + docs/reference/gitops/migration/README.md | 71 +++ docs/reference/gitops/spec/README.md | 9 + docs/reference/gitops/spec/v1beta1/README.md | 25 + .../gitops/spec/v1beta1/kluctldeployment.md | 551 ++++++++++++++++++ 9 files changed, 822 insertions(+), 1 deletion(-) create mode 100644 docs/reference/gitops/README.md create mode 100644 docs/reference/gitops/metrics/README.md create mode 100644 docs/reference/gitops/metrics/v1beta1/README.md create mode 100644 docs/reference/gitops/metrics/v1beta1/kluctldeployment_controller.md create mode 100644 docs/reference/gitops/migration/README.md create mode 100644 docs/reference/gitops/spec/README.md create mode 100644 docs/reference/gitops/spec/v1beta1/README.md create mode 100644 docs/reference/gitops/spec/v1beta1/kluctldeployment.md diff --git a/docs/reference/README.md b/docs/reference/README.md index 19695290e..729f8fb45 100644 --- a/docs/reference/README.md +++ b/docs/reference/README.md @@ -13,4 +13,5 @@ weight: 110 1. [.kluctl.yaml](./kluctl-project) 2. [Deployments](./deployments) -3. [Kluctl Commands](./commands) +3. [GitOps](./gitops) +4. [Kluctl Commands](./commands) diff --git a/docs/reference/gitops/README.md b/docs/reference/gitops/README.md new file mode 100644 index 000000000..63c6fd4ae --- /dev/null +++ b/docs/reference/gitops/README.md @@ -0,0 +1,107 @@ + + +# GitOps + +GitOps in Kluctl is implemented through the Kluctl Controller, which must be [installed](../../installation.md#installing-the-gitops-controller) +to your target cluster. + +The Kluctl Controller is a Kubernetes operator which implements the [`KluctlDeployment`](./spec/v1beta1/kluctldeployment.md#kluctldeployment) +custom resource. This resource allows to define a Kluctl deployment that should be constantly reconciled (re-deployed) +whenever the deployment changes. + +## Motivation and Philosophy + +Kluctl tries its best to implement all its features via [Kluctl projects](../kluctl-project/README.md), meaning that +the deployments are, at least theoretically, deployable from the CLI at all times. The Kluctl Controller does not +add functionality on top of that and thus does not couple your deployments to a running controller. + +Instead, the `KluctlDeployment` custom resource acts as an interface to the deployment. It tries to offer the same +functionality and options as offered by the CLI, but through a custom resource instead of a CLI invocation. + +As an example, arguments passed via `-a arg=value` can be passed to the custom resource via the `spec.args` field. +The same applies to options like `--dry-run`, which equals to `spec.dryRun: true` in the custom resource. Check the +documentation of [`KluctlDeployment`](./spec/v1beta1/kluctldeployment.md#spec-fields) for more such options. + +## Installation + +Installation instructions can be found [here](../../installation.md#installing-the-gitops-controller) + +## Design + +The reconciliation process consists of multiple steps which are constantly repeated: + +- **clone** the root Kluctl project via Git +- **prepare** the Kluctl deployment by rendering the whole deployment +- **deploy** the specified target via [kluctl deploy](../commands/deploy.md) if the rendered resources changed +- **prune** orphaned objects via [kluctl prune](../commands/prune.md) +- **validate** the deployment status via [kluctl validate](../commands/validate.md) + +Reconciliation is performed on a configurable [interval](./spec/v1beta1/kluctldeployment.md#interval). A single +reconciliation iteration will first clone and prepare the project. Only when the rendered resources indicate a change +(by using a hash internally), the controller will initiate a deployment. After the deployment, the controller will +also perform pruning (only if [prune: true](./spec/v1beta1/kluctldeployment.md#prune) is set). + +When the `KluctlDeployment` is removed from the cluster, the controller cal also delete all resources belonging to +that deployment. This will only happen if [delete: true](./spec/v1beta1/kluctldeployment.md#delete) is set. + +Deletion and pruning is based on the [discriminator](../kluctl-project/README.md#discriminator) of the given target. + +A `KluctlDeployment` can be [suspended](./spec/v1beta1/kluctldeployment.md#suspend). While suspended, the controller +will skip reconciliation, including deletion and pruning. + +The API design of the controller can be found at [kluctldeployment.gitops.kluctl.io/v1beta1](./spec/v1beta1/README.md). + +## Example + +After installing the Kluctl Controller, we can create a `KluctlDeployment` that automatically deploys the +[Microservices Demo](https://kluctl.io/docs/guides/tutorials/microservices-demo/3-templating-and-multi-env/). + +Create a KluctlDeployment that uses the demo project source to deploy the `test` target to the same cluster that the +controller runs on. + +```yaml +apiVersion: gitops.kluctl.io/v1beta1 +kind: KluctlDeployment +metadata: + name: microservices-demo-test + namespace: kluctl-system +spec: + interval: 10m + source: + url: https://github.com/kluctl/kluctl-examples.git + path: "./microservices-demo/3-templating-and-multi-env/" + timeout: 2m + target: test + context: default + prune: true +``` + +This example will deploy a fully-fledged microservices application with multiple backend services, frontends and +databases, all via one single `KluctlDeployment`. + +To deploy the same Kluctl project to another target (e.g. prod), simply create the following resource. + +```yaml +apiVersion: gitops.kluctl.io/v1beta1 +kind: KluctlDeployment +metadata: + name: microservices-demo-prod + namespace: kluctl-system +spec: + interval: 10m + source: + url: https://github.com/kluctl/kluctl-examples.git + path: "./microservices-demo/3-templating-and-multi-env/" + timeout: 2m + target: prod + context: default + prune: true +``` diff --git a/docs/reference/gitops/metrics/README.md b/docs/reference/gitops/metrics/README.md new file mode 100644 index 000000000..2ae19f029 --- /dev/null +++ b/docs/reference/gitops/metrics/README.md @@ -0,0 +1,9 @@ + diff --git a/docs/reference/gitops/metrics/v1beta1/README.md b/docs/reference/gitops/metrics/v1beta1/README.md new file mode 100644 index 000000000..d13eb0dc2 --- /dev/null +++ b/docs/reference/gitops/metrics/v1beta1/README.md @@ -0,0 +1,19 @@ + + +# Prometheus Metrics + +The controller exports several metrics in the [OpenMetrics compatible format](https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md). +They can be scraped by all sorts of monitoring solutions (e.g. Prometheus) or stored in a database. Because the +controller is based on [controller-runtime](https://github.com/kubernetes-sigs/controller-runtime), all +the [default metrics](https://book.kubebuilder.io/reference/metrics-reference.html) as well as the +following controller-specific custom metrics are exported: + +- [kluctldeployment_controller](kluctldeployment_controller.md) diff --git a/docs/reference/gitops/metrics/v1beta1/kluctldeployment_controller.md b/docs/reference/gitops/metrics/v1beta1/kluctldeployment_controller.md new file mode 100644 index 000000000..9c5163f5f --- /dev/null +++ b/docs/reference/gitops/metrics/v1beta1/kluctldeployment_controller.md @@ -0,0 +1,29 @@ + + +# Exported Metrics References + +| Metrics name | Type | Description | +|-----------------------------|-----------|--------------------------------------------------------------------------------------| +| deployment_duration_seconds | Histogram | How long a single deployment takes in seconds. | +| number_of_changed_objects | Gauge | How many objects have been changed by a single deployment. | +| number_of_deleted_objects | Gauge | How many objects have been deleted by a single deployment. | +| number_of_errors | Gauge | How many errors are related to a single deployment. | +| number_of_images | Gauge | Number of images of a single deployment. | +| number_of_orphan_objects | Gauge | How many orphans are related to a single deployment. | +| number_of_warnings | Gauge | How many warnings are related to a single deployment. | +| prune_duration_seconds | Histogram | How long a single prune takes in seconds. | +| validate_duration_seconds | Histogram | How long a single validate takes in seconds. | +| deployment_interval_seconds | Gauge | The configured deployment interval of a single deployment. | +| dry_run_enabled | Gauge | Is dry-run enabled for a single deployment. | +| last_object_status | Gauge | Last object status of a single deployment. Zero means failure and one means success. | +| prune_enabled | Gauge | Is pruning enabled for a single deployment. | +| delete_enabled | Gauge | Is deletion enabled for a single deployment. | +| source_spec | Gauge | The configured source spec of a single deployment exported via labels. | diff --git a/docs/reference/gitops/migration/README.md b/docs/reference/gitops/migration/README.md new file mode 100644 index 000000000..9fb8d394c --- /dev/null +++ b/docs/reference/gitops/migration/README.md @@ -0,0 +1,71 @@ + + +# Legacy Controller Migration + +Older versions of Kluctl (pre v2.20.0) relied on a legacy version of the Kluctl controller, named +[flux-kluctl-controller](https://github.com/kluctl/flux-kluctl-controller). If you upgraded from such an older +version and were already using `KluctlDeployments` from the `flux.kluctl.io` API group, you must migrate these +deployments to the new `gitops.kluctl.io` group. + +To do this, follow the following steps: + +1. Upgrade the legacy flux-kluctl-controller to at least v0.16.0. This version will introduce a special marker field +into the legacy `KluctlDeployment` status and set it to true. This marker field is used to inform the new Kluctl Controller +that the legacy controller is now aware of the existence of the new controller. +2. If not already done, [install](../../../installation.md#installing-the-gitops-controller) the new Kluctl Controller. +3. To be on the safe side, disable [pruning](https://kluctl.io/docs/flux/spec/v1alpha1/kluctldeployment/#prune) and +[deletion](https://kluctl.io/docs/flux/spec/v1alpha1/kluctldeployment/#delete) for all legacy `KluctlDeployment` objects. +Don't forget to deploy/apply these changes before continuing with the next step. +4. Modify your `KluctlDeployment` manifests to use the `gitops.kluctl.io/v1beta1` as `apiVersion`. It's important +to use the same name and namespace as used in the legacy resources. Also read the [breaking changes](#breaking-changes) +section. +5. Deploy/Apply the modified `KluctlDeployment` resources. +6. At this point, the legacy controller will detect that the `KluctlDeployment` exists twice, once for the legacy +API group/version and once for the new group/version. Based on that knowledge, the legacy controller will stop reconciling +the legacy `KluctlDeployment`. +7. At the same time, the new controller will detect that the legacy `KluctlDeployment` has the marker field set, which +means that the legacy controller is known to honor the new controller's existence. +8. This will lead to the new controller taking over and reconciling the new `KluctlDeployment`. +9. If you disabled deletion/pruning in step 3., you should undo this on the new `KluctlDeployments` now. + +After these steps, the legacy `KluctlDeployment` resources will be excluded from reconciliation by the legacy controller. +This means, you can safely remove/prune the legacy resources. + +# Breaking changes + +There exist some breaking changes between the legacy `flux.kluctl.io/v1alpha1` and `gitops.kluctl.io/v1beta1` custom +resources and controllers. These are: + +### Only deploy when resources change + +The legacy controller did a full deploy on each reconciliation, following the Flux way of reconciliations. This +behaviour was configurable by allowing you to set `spec.deployInterval: never`, which disabled full deployments and +caused the controller to only deploy when the resulting rendered resources actually changed. + +The new controller will behave this way by default, unless you explicitly set `spec.deployInterval` to some interval +value. + +This means, you will have to introduce `spec.deployInterval` in case you expect the controller to behave as before or +remove `spec.deployInterval: never` if you already used the Kluctl specific behavior. + +### renameContexts has been removed + +The `spec.renameContexts` field is not available anymore. Use `spec.context` instead. + +### status will not contain full result anymore + +The legacy controller wrote the full command result (with objects, diffs, ...) into the status field. The new +controller will instead only write a summary of the result. + +# Why no fully automated migration? + +I have decided against a fully automated migration as the move of the API group causes resources to have a different +identity. This can easily lead to unexpected behaviour and does not play well with GitOps. diff --git a/docs/reference/gitops/spec/README.md b/docs/reference/gitops/spec/README.md new file mode 100644 index 000000000..cb95e02f6 --- /dev/null +++ b/docs/reference/gitops/spec/README.md @@ -0,0 +1,9 @@ + diff --git a/docs/reference/gitops/spec/v1beta1/README.md b/docs/reference/gitops/spec/v1beta1/README.md new file mode 100644 index 000000000..19271ca12 --- /dev/null +++ b/docs/reference/gitops/spec/v1beta1/README.md @@ -0,0 +1,25 @@ + + +# gitops.kluctl.io/v1beta1 + +This is the v1beta1 API specification for defining continuous delivery pipelines +of Kluctl Deployments. + +## Specification + +- [KluctlDeployment CRD](kluctldeployment.md) + + [Spec fields](kluctldeployment.md#spec-fields) + + [Reconciliation](kluctldeployment.md#reconciliation) + + [Kubeconfigs and RBAC](kluctldeployment.md#kubeconfigs-and-rbac) + + [Git authentication](kluctldeployment.md#git-authentication) + + [Helm Repository authentication](kluctldeployment.md#helm-repository-authentication) + + [Secrets Decryption](kluctldeployment.md#secrets-decryption) + + [Status](kluctldeployment.md#status) diff --git a/docs/reference/gitops/spec/v1beta1/kluctldeployment.md b/docs/reference/gitops/spec/v1beta1/kluctldeployment.md new file mode 100644 index 000000000..db46f4cf0 --- /dev/null +++ b/docs/reference/gitops/spec/v1beta1/kluctldeployment.md @@ -0,0 +1,551 @@ + + +# KluctlDeployment + +The `KluctlDeployment` API defines a deployment of a [target](../../../kluctl-project/targets) +from a [Kluctl Project](../../../kluctl-project). + +## Example + +```yaml +apiVersion: gitops.kluctl.io/v1beta1 +kind: KluctlDeployment +metadata: + name: microservices-demo-prod +spec: + interval: 5m + source: + url: https://github.com/kluctl/kluctl-examples.git + path: "./microservices-demo/3-templating-and-multi-env/" + timeout: 2m + target: prod + context: default + prune: true + delete: true +``` + +In the above example a KluctlDeployment is being created that defines the deployment based on the Kluctl project. + +The deployment is performed every 5 minutes. It will deploy the `prod` +[target](../../../kluctl-project/targets) and then prune orphaned objects afterward. + +When the KluctlDeployment gets deleted, `delete: true` will cause the controller to actually delete the target +resources. + +It uses the `default` context provided by the default service account and thus overrides the context specified in the +target definition. + +## Spec fields + +### source + +The KluctlDeployment `spec.source` specifies the source repository to be used. Example: + +```yaml +apiVersion: gitops.kluctl.io/v1beta1 +kind: KluctlDeployment +metadata: + name: example +spec: + source: + url: https://github.com/kluctl/kluctl-examples.git + path: path/to/project + secretRef: + name: git-credentials + ref: + branch: my-branch + ... +``` + +The `url` specifies the git clone url. It can either be a https or a git/ssh url. Git/Ssh url will require a secret +to be provided with credentials. + +The `path` specifies the sub-directory where the Kluctl project is located. + +The `ref` provides the Git reference to be used. It can either be a branch or a tag. + +See [Git authentication](#git-authentication) for details on authentication. + +### interval +See [Reconciliation](#reconciliation). + +### suspend +See [Reconciliation](#reconciliation). + +### target +`spec.target` specifies the target to be deployed. It must exist in the Kluctl projects +[kluctl.yaml targets](../../../kluctl-project/targets) list. + +This field is optional and can be omitted if the referenced Kluctl project allows deployments without targets. + +### targetNameOverride +`spec.targetNameOverride` will set or override the name of the target. This is equivalent to passing +`--target-name-override` to `kluctl deploy`. + +### context +`spec.context` will override the context used while deploying. This is equivalent to passing `--context` to +`kluctl deploy`. + +### deployMode +By default, the operator will perform a full deployment, which is equivalent to using the `kluctl deploy` command. +As an alternative, the controller can be instructed to only perform a `kluctl poke-images` command. Please +see [poke-images](../../../commands/poke-images.md) for details on the command. To do so, set `spec.deployMode` +field to `poke-images`. + +Example: +``` +apiVersion: gitops.kluctl.io/v1beta1 +kind: KluctlDeployment +metadata: + name: microservices-demo-prod +spec: + interval: 5m + source: + url: https://github.com/kluctl/kluctl-examples.git + path: "./microservices-demo/3-templating-and-multi-env/" + timeout: 2m + target: prod + context: default + deployMode: poke-images +``` + +### prune + +To enable pruning, set `spec.prune` to `true`. This will cause the controller to run `kluctl prune` after each +successful deployment. + +### delete + +To enable deletion, set `spec.delete` to `true`. This will cause the controller to run `kluctl delete` when the +KluctlDeployment gets deleted. + +### args +`spec.args` is an object representing [arguments](../../../kluctl-project/#args) +passed to the deployment. Example: + +```yaml +apiVersion: gitops.kluctl.io/v1beta1 +kind: KluctlDeployment +metadata: + name: example +spec: + interval: 5m + source: + url: https://github.com/kluctl/kluctl-examples.git + path: "./microservices-demo/3-templating-and-multi-env/" + timeout: 2m + target: prod + context: default + args: + arg1: value1 + arg2: value2 + arg3: + k1: v1 + k2: v2 +``` + +The above example is equivalent to calling `kluctl deploy -t prod -a arg1=value1 -a arg2=value2`. + +### images +`spec.images` specifies a list of fixed images to be used by +[`image.get_image(...)`](../../../deployments/images#imagesget_image). Example: + +``` +apiVersion: gitops.kluctl.io/v1beta1 +kind: KluctlDeployment +metadata: + name: example +spec: + interval: 5m + source: + url: https://example.com + timeout: 2m + target: prod + images: + - image: nginx + resultImage: nginx:1.21.6 + namespace: example-namespace + deployment: Deployment/example + - image: registry.gitlab.com/my-org/my-repo/image + resultImage: registry.gitlab.com/my-org/my-repo/image:1.2.3 +``` + +The above example will cause the `images.get_image("nginx")` invocations of the `example` Deployment to return +`nginx:1.21.6`. It will also cause all `images.get_image("registry.gitlab.com/my-org/my-repo/image")` invocations +to return `registry.gitlab.com/my-org/my-repo/image:1.2.3`. + +The fixed images provided here take precedence over the ones provided in the +[target definition](../../../kluctl-project/targets#images). + +`spec.images` is equivalent to calling `kluctl deploy -t prod --fixed-image=nginx:example-namespace:Deployment/example=nginx:1.21.6 ...` +and to `kluctl deploy -t prod --fixed-images-file=fixed-images.yaml` with `fixed-images.yaml` containing: + +```yaml +images: +- image: nginx + resultImage: nginx:1.21.6 + namespace: example-namespace + deployment: Deployment/example +- image: registry.gitlab.com/my-org/my-repo/image + resultImage: registry.gitlab.com/my-org/my-repo/image:1.2.3 +``` + +### dryRun +`spec.dryRun` is a boolean value that turns the deployment into a dry-run deployment. This is equivalent to calling +`kluctl deploy -t prod --dry-run`. + +### noWait +`spec.noWait` is a boolean value that disables all internal waiting (hooks and readiness). This is equivalent to calling +`kluctl deploy -t prod --no-wait`. + +### forceApply +`spec.forceApply` is a boolean value that causes kluctl to solve conflicts via force apply. This is equivalent to calling +`kluctl deploy -t prod --force-apply`. + +### replaceOnError and forceReplaceOnError +`spec.replaceOnError` and `spec.forceReplaceOnError` are both boolean values that cause kluctl to perform a replace +after a failed apply. `forceReplaceOnError` goes a step further and deletes and recreates the object in question. +These are equivalent to calling `kluctl deploy -t prod --replace-on-error` and `kluctl deploy -t prod --force-replace-on-error`. + +### abortOnError +`spec.abortOnError` is a boolean value that causes kluctl to abort as fast as possible in case of errors. This is equivalent to calling +`kluctl deploy -t prod --abort-on-error`. + +### includeTags, excludeTags, includeDeploymentDirs and excludeDeploymentDirs +`spec.includeTags` and `spec.excludeTags` are lists of tags to be used in inclusion/exclusion logic while deploying. +These are equivalent to calling `kluctl deploy -t prod --include-tag ` and `kluctl deploy -t prod --exclude-tag `. + +`spec.includeDeploymentDirs` and `spec.excludeDeploymentDirs` are lists of relative deployment directories to be used in +inclusion/exclusion logic while deploying. These are equivalent to calling `kluctl deploy -t prod --include-tag ` +and `kluctl deploy -t prod --exclude-tag `. + +## Reconciliation + +The KluctlDeployment `spec.interval` tells the controller at which interval to try reconciliations. +The interval time units are `s`, `m` and `h` e.g. `interval: 5m`, the minimum value should be over 60 seconds. + +At each reconciliation run, the controller will check if any rendered objects have been changes since the last +deployment and then perform a new deployment if changes are detected. Changes are tracked via a hash consisting of +all rendered objects. + +To enforce periodic full deployments even if nothing has changed, `spec.deployInterval` can be used to specify an +interval at which forced deployments must be performed by the controller. + +The KluctlDeployment reconciliation can be suspended by setting `spec.suspend` to `true`. + +The controller can be told to reconcile the KluctlDeployment outside of the specified interval +by annotating the KluctlDeployment object with `kluctl.io/request-reconcile`. + +On-demand reconciliation example: + +```bash +kubectl annotate --overwrite kluctldeployment/microservices-demo-prod kluctl.io/request-reconcile="$(date +%s)" +``` + +Similarly, a deployment can be forced even if the source has not changed by using the `kluctl.io/request-deploy` +annotation: + +```bash +kubectl annotate --overwrite kluctldeployment/microservices-demo-prod kluctl.io/request-deploy="$(date +%s)" +``` + + +## Kubeconfigs and RBAC + +As Kluctl is meant to be a CLI-first tool, it expects a kubeconfig to be present while deployments are +performed. The controller will generate such kubeconfigs on-the-fly before performing the actual deployment. + +The kubeconfig can be generated from 3 different sources: +1. The default impersonation service account specified at controller startup (via `--default-service-account`) +2. The service account specified via `spec.serviceAccountName` in the KluctlDeployment +3. The secret specified via `spec.kubeConfig` in the KluctlDeployment. + +The behavior/functionality of 1. and 2. is comparable to how the [kustomize-controller](https://fluxcd.io/docs/components/kustomize/kustomization/#role-based-access-control) +handles impersonation, with the difference that a kubeconfig with a "default" context is created in-between. + +`spec.kubeConfig` will simply load the kubeconfig from `data.value` of the specified secret. + +Kluctl [targets](../../../kluctl-project/targets) specify a context name that is expected to +be present in the kubeconfig while deploying. As the context found in the generated kubeconfig does not necessarily +have the correct name, `spec.context` can be used to while deploying. This is especially useful +when using service account based kubeconfigs, as these always have the same context with the name "default". + +Here is an example of a deployment that uses the service account "prod-service-account" and overrides the context +appropriately (assuming the Kluctl cluster config for the given target expects a "prod" context): + +```yaml +apiVersion: gitops.kluctl.io/v1beta1 +kind: KluctlDeployment +metadata: + name: example + namespace: kluctl-system +spec: + interval: 10m + source: + url: https://github.com/kluctl/kluctl-examples.git + path: "./microservices-demo/3-templating-and-multi-env/" + target: prod + serviceAccountName: prod-service-account + context: default +``` + +## Git authentication + +The `spec.source` can optionally specify a `spec.source.secretRef` (see [here](#source)) which must point to an existing +secret (in the same namespace) containing Git credentials. + +### Basic access authentication + +To authenticate towards a Git repository over HTTPS using basic access +authentication (in other words: using a username and password), the referenced +Secret is expected to contain `.data.username` and `.data.password` values. + +```yaml +--- +apiVersion: v1 +kind: Secret +metadata: + name: basic-access-auth +type: Opaque +data: + username: + password: +``` + +### HTTPS Certificate Authority + +To provide a Certificate Authority to trust while connecting with a Git +repository over HTTPS, the referenced Secret can contain a `.data.caFile` +value. + +```yaml +--- +apiVersion: v1 +kind: Secret +metadata: + name: https-ca-credentials + namespace: default +type: Opaque +data: + caFile: +``` + +### SSH authentication + +To authenticate towards a Git repository over SSH, the referenced Secret is +expected to contain `identity` and `known_hosts` fields. With the respective +private key of the SSH key pair, and the host keys of the Git repository. + +```yaml +--- +apiVersion: v1 +kind: Secret +metadata: + name: ssh-credentials +type: Opaque +stringData: + identity: | + -----BEGIN OPENSSH PRIVATE KEY----- + ... + -----END OPENSSH PRIVATE KEY----- + known_hosts: | + github.com ecdsa-sha2-nistp256 AAAA... +``` + +## Helm Repository authentication + +Kluctl allows to [integrate Helm Charts](../../../deployments/helm) in two different ways. +One is to [pre-pull charts](../../../commands/helm-pull) and put them into version control, +making it unnecessary to pull them at deploy time. This option also means that you don't have to take any special care +on the controller side. + +The other way is to let Kluctl pull Helm Charts at deploy time. In that case, you have to ensure that the controller +has the necessary access to the Helm repositories. To add credentials for authentication, set the `spec.helmCredentials` +field to a list of secret references: + +### Basic access authentication + +```yaml +apiVersion: gitops.kluctl.io/v1beta1 +kind: KluctlDeployment +metadata: + name: example + namespace: kluctl-system +spec: + interval: 10m + source: + url: https://github.com/kluctl/kluctl-examples.git + path: "./microservices-demo/3-templating-and-multi-env/" + target: prod + serviceAccountName: prod-service-account + context: default + + helmCredentials: + - secretRef: + name: helm-creds +--- +apiVersion: v1 +kind: Secret +metadata: + name: helm-creds + namespace: kluctl-system +stringData: + url: https://example-repo.com + username: my-user + password: my-password +``` + +### TLS authentication + +For TLS authentication, see the following example secret: + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: helm-creds + namespace: kluctl-system +data: + certFile: + keyFile: + # NOTE: Can be supplied without the above values + caFile: +``` + +### Disabling TLS verification + +In case you need to disable TLS verification (not recommended!), add the key `insecureSkipTlsVerify` with the value +`"true"` (make sure it's a string, so surround it with `"`). + +### Pass credentials + +To enable passing of credentials to all requests, add the key `passCredentialsAll` with the value `"true"`. +This will pass the credentials to all requests, even if the hostname changes. + +## Secrets Decryption + +Kluctl offers a [SOPS Integration](../../../deployments/sops) that allows to use encrypted +manifests and variable sources in Kluctl deployments. Decryption by the controller is also supported and currently +mirrors how the [Secrets Decryption configuration](https://fluxcd.io/flux/components/kustomize/kustomization/#secrets-decryption) +of the Flux Kustomize Controller. To configure it in the `KluctlDeployment`, simply set the `decryption` field in the +spec: + +``` +apiVersion: gitops.kluctl.io/v1beta1 +kind: KluctlDeployment +metadata: + name: example + namespace: kluctl-system +spec: + decryption: + provider: sops + secretRef: + name: sops-keys + ... +``` + +The `sops-keys` Secret has the same format as in the +[Flux Kustomize Controller](https://fluxcd.io/flux/components/kustomize/kustomization/#decryption-secret-reference). + +### AWS KMS with IRSA + +In addition to the [AWS KMS Secret Entry](https://fluxcd.io/flux/components/kustomize/kustomization/#aws-kms-secret-entry) +in the secret and the [global AWS KMS](https://fluxcd.io/flux/components/kustomize/kustomization/#aws-kms) +authentication via the controller's service account, the Kluctl controller also supports using the IRSA role of the +impersonated service account of the `KluctlDeployment` (specified via `serviceAccountName` in the spec or +`--default-service-account`): + +```yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: kluctl-deployment + namespace: kluctl-system + annotations: + eks.amazonaws.com/role-arn: arn:aws:iam::123456:role/my-irsa-enabled-role +--- +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: kluctl-deployment + namespace: kluctl-system +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + # watch out, don't use cluster-admin if you don't trust the deployment + name: cluster-admin +subjects: + - kind: ServiceAccount + name: kluctl-deployment + namespace: kluctl-system +--- +apiVersion: gitops.kluctl.io/v1beta1 +kind: KluctlDeployment +metadata: + name: example + namespace: kluctl-system +spec: + serviceAccountName: kluctl-deployment + decryption: + provider: sops + # you can also leave out the secretRef if you don't provide addinional keys + secretRef: + name: sops-keys + ... +``` + +## Status + +When the controller completes a deployments, it reports the result in the `status` sub-resource. + +A successful reconciliation sets the ready condition to `true`. + +```yaml +status: + conditions: + - lastTransitionTime: "2022-07-07T11:48:14Z" + message: "deploy: ok" + reason: ReconciliationSucceeded + status: "True" + type: Ready + lastDeployResult: + ... + lastPruneResult: + ... + lastValidateResult: + ... +``` + +You can wait for the controller to complete a reconciliation with: + +```bash +kubectl wait kluctldeployment/backend --for=condition=ready +``` + +A failed reconciliation sets the ready condition to `false`: + +```yaml +status: + conditions: + - lastTransitionTime: "2022-05-04T10:18:11Z" + message: target invalid-name not found in kluctl project + reason: PrepareFailed + status: "False" + type: Ready + lastDeployResult: + ... + lastPruneResult: + ... + lastValidateResult: + ... +``` + +> **Note** that the lastDeployResult, lastPruneResult and lastValidateResult are only updated on a successful reconciliation. From 28b0ef4b02c6deaba9988bf47bf96b2660702926 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 31 May 2023 16:21:56 +0200 Subject: [PATCH 1727/2916] docs: Add api docs generation --- .github/workflows/tests.yml | 9 + Makefile | 10 + .../api/kluctl-controller.front-matter.yaml | 4 + .../reference/gitops/api/kluctl-controller.md | 1465 +++++++++++++++++ hack/api-docs/config.json | 29 + hack/api-docs/template/members.tpl | 46 + hack/api-docs/template/pkg.tpl | 46 + hack/api-docs/template/type.tpl | 60 + 8 files changed, 1669 insertions(+) create mode 100644 docs/reference/gitops/api/kluctl-controller.front-matter.yaml create mode 100644 docs/reference/gitops/api/kluctl-controller.md create mode 100644 hack/api-docs/config.json create mode 100644 hack/api-docs/template/members.tpl create mode 100644 hack/api-docs/template/pkg.tpl create mode 100644 hack/api-docs/template/type.tpl diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index fd0338d69..2a4ffa48f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -57,6 +57,15 @@ jobs: git diff exit 1 fi + - name: Verify generated api-docs are up-to-date + run: | + make api-docs + if [ ! -z "$(git status --porcelain)" ]; then + echo "make api-docs must be invoked and the result committed" + git status + git diff + exit 1 + fi tests: strategy: diff --git a/Makefile b/Makefile index c8d2eb5a3..58d9424aa 100644 --- a/Makefile +++ b/Makefile @@ -64,6 +64,10 @@ manifests: controller-gen kustomize ## Generate WebhookConfiguration, ClusterRol $(KUSTOMIZE) build config/rbac > install/controller/controller/rbac.yaml $(KUSTOMIZE) build config/manager > install/controller/controller/manager.yaml +# Generate API reference documentation +api-docs: gen-crd-api-reference-docs + $(GEN_CRD_API_REFERENCE_DOCS) -v=4 -api-dir=./api/v1beta1 -config=./hack/api-docs/config.json -template-dir=./hack/api-docs/template -out-file=./docs/reference/controller/api/kluctl-controller.md + .PHONY: generate generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. go generate ./... @@ -169,6 +173,12 @@ $(CONTROLLER_GEN): $(LOCALBIN) test -s $(LOCALBIN)/controller-gen && $(LOCALBIN)/controller-gen --version | grep -q $(CONTROLLER_TOOLS_VERSION) || \ GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_TOOLS_VERSION) +# Find or download gen-crd-api-reference-docs +GEN_CRD_API_REFERENCE_DOCS = $(LOCALBIN)/gen-crd-api-reference-docs +.PHONY: gen-crd-api-reference-docs +gen-crd-api-reference-docs: + GOBIN=$(LOCALBIN) go install github.com/ahmetb/gen-crd-api-reference-docs@v0.3.0 + .PHONY: envtest envtest: $(ENVTEST) ## Download envtest-setup locally if necessary. $(ENVTEST): $(LOCALBIN) diff --git a/docs/reference/gitops/api/kluctl-controller.front-matter.yaml b/docs/reference/gitops/api/kluctl-controller.front-matter.yaml new file mode 100644 index 000000000..be96cbbcb --- /dev/null +++ b/docs/reference/gitops/api/kluctl-controller.front-matter.yaml @@ -0,0 +1,4 @@ +title: Kluctl Controller API reference +linkTitle: Kluctl Controller API +description: Kluctl Controller API reference +weight: 300 diff --git a/docs/reference/gitops/api/kluctl-controller.md b/docs/reference/gitops/api/kluctl-controller.md new file mode 100644 index 000000000..f1c1d35ee --- /dev/null +++ b/docs/reference/gitops/api/kluctl-controller.md @@ -0,0 +1,1465 @@ +

Kluctl Controller API reference

+

Packages:

+ +

gitops.kluctl.io/v1beta1

+

Package v1beta1 contains API Schema definitions for the gitops.kluctl.io v1beta1 API group.

+Resource Types: +
    +

    Decryption +

    +

    +(Appears on: +KluctlDeploymentSpec) +

    +

    Decryption defines how decryption is handled for Kubernetes manifests.

    +
    +
    + + + + + + + + + + + + + + + + + + + + + +
    FieldDescription
    +provider
    + +string + +
    +

    Provider is the name of the decryption engine.

    +
    +secretRef
    + + +LocalObjectReference + + +
    +(Optional) +

    The secret name containing the private OpenPGP keys used for decryption.

    +
    +serviceAccount
    + +string + +
    +(Optional) +

    ServiceAccount specifies the service account used to authenticate against cloud providers. +This is currently only usable for AWS KMS keys. The specified service account will be used to authenticate to AWS +by signing a token in an IRSA compliant way.

    +
    +
    +
    +

    GitRef +

    +

    +(Appears on: +ProjectSource) +

    +
    +
    + + + + + + + + + + + + + + + + + +
    FieldDescription
    +branch
    + +string + +
    +(Optional) +

    Branch to filter for. Can also be a regex.

    +
    +tag
    + +string + +
    +(Optional) +

    Branch to filter for. Can also be a regex.

    +
    +
    +
    +

    HelmCredentials +

    +

    +(Appears on: +KluctlDeploymentSpec) +

    +
    +
    + + + + + + + + + + + + + +
    FieldDescription
    +secretRef
    + + +LocalObjectReference + + +
    +

    SecretRef holds the name of a secret that contains the Helm credentials. +The secret must either contain the fields credentialsId which refers to the credentialsId +found in https://kluctl.io/docs/kluctl/reference/deployments/helm/#private-chart-repositories or an url used +to match the credentials found in Kluctl projects helm-chart.yaml files. +The secret can either container basic authentication credentials via username and password or +TLS authentication via certFile and keyFile. caFile can be specified to override the CA to use while +contacting the repository. +The secret can also contain insecureSkipTlsVerify: "true", which will disable TLS verification. +passCredentialsAll: "true" can be specified to make the controller pass credentials to all requests, even if +the hostname changes in-between.

    +
    +
    +
    +

    KluctlDeployment +

    +

    KluctlDeployment is the Schema for the kluctldeployments API

    +
    +
    + + + + + + + + + + + + + + + + + + + + + +
    FieldDescription
    +metadata
    + + +Kubernetes meta/v1.ObjectMeta + + +
    +Refer to the Kubernetes API documentation for the fields of the +metadata field. +
    +spec
    + + +KluctlDeploymentSpec + + +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +source
    + + +ProjectSource + + +
    +

    Specifies the project source location

    +
    +decryption
    + + +Decryption + + +
    +(Optional) +

    Decrypt Kubernetes secrets before applying them on the cluster.

    +
    +interval
    + + +Kubernetes meta/v1.Duration + + +
    +

    The interval at which to reconcile the KluctlDeployment. +Reconciliation means that the deployment is fully rendered and only deployed when the result changes compared +to the last deployment. +To override this behavior, set the DeployInterval value.

    +
    +retryInterval
    + + +Kubernetes meta/v1.Duration + + +
    +(Optional) +

    The interval at which to retry a previously failed reconciliation. +When not specified, the controller uses the Interval +value to retry failures.

    +
    +deployInterval
    + + +SafeDuration + + +
    +(Optional) +

    DeployInterval specifies the interval at which to deploy the KluctlDeployment, even in cases the rendered +result does not change.

    +
    +validateInterval
    + + +SafeDuration + + +
    +(Optional) +

    ValidateInterval specifies the interval at which to validate the KluctlDeployment. +Validation is performed the same way as with ‘kluctl validate -t ’. +Defaults to the same value as specified in Interval. +Validate is also performed whenever a deployment is performed, independent of the value of ValidateInterval

    +
    +timeout
    + + +Kubernetes meta/v1.Duration + + +
    +(Optional) +

    Timeout for all operations. +Defaults to ‘Interval’ duration.

    +
    +suspend
    + +bool + +
    +(Optional) +

    This flag tells the controller to suspend subsequent kluctl executions, +it does not apply to already started executions. Defaults to false.

    +
    +helmCredentials
    + + +[]HelmCredentials + + +
    +(Optional) +

    HelmCredentials is a list of Helm credentials used when non pre-pulled Helm Charts are used inside a +Kluctl deployment.

    +
    +serviceAccountName
    + +string + +
    +(Optional) +

    The name of the Kubernetes service account to use while deploying. +If not specified, the default service account is used.

    +
    +kubeConfig
    + + +KubeConfig + + +
    +(Optional) +

    The KubeConfig for deploying to the target cluster. +Specifies the kubeconfig to be used when invoking kluctl. Contexts in this kubeconfig must match +the context found in the kluctl target. As an alternative, specify the context to be used via ‘context’

    +
    +target
    + +string + +
    +(Optional) +

    Target specifies the kluctl target to deploy. If not specified, an empty target is used that has no name and no +context. Use ‘TargetName’ and ‘Context’ to specify the name and context in that case.

    +
    +targetNameOverride
    + +string + +
    +(Optional) +

    TargetNameOverride sets or overrides the target name. This is especially useful when deployment without a target.

    +
    +context
    + +string + +
    +(Optional) +

    If specified, overrides the context to be used. This will effectively make kluctl ignore the context specified +in the target.

    +
    +args
    + +k8s.io/apimachinery/pkg/runtime.RawExtension + +
    +(Optional) +

    Args specifies dynamic target args.

    +
    +images
    + +[]github.com/kluctl/kluctl/v2/pkg/types.FixedImage + +
    +(Optional) +

    Images contains a list of fixed image overrides. +Equivalent to using ‘–fixed-images-file’ when calling kluctl.

    +
    +dryRun
    + +bool + +
    +(Optional) +

    DryRun instructs kluctl to run everything in dry-run mode. +Equivalent to using ‘–dry-run’ when calling kluctl.

    +
    +noWait
    + +bool + +
    +(Optional) +

    NoWait instructs kluctl to not wait for any resources to become ready, including hooks. +Equivalent to using ‘–no-wait’ when calling kluctl.

    +
    +forceApply
    + +bool + +
    +(Optional) +

    ForceApply instructs kluctl to force-apply in case of SSA conflicts. +Equivalent to using ‘–force-apply’ when calling kluctl.

    +
    +replaceOnError
    + +bool + +
    +(Optional) +

    ReplaceOnError instructs kluctl to replace resources on error. +Equivalent to using ‘–replace-on-error’ when calling kluctl.

    +
    +forceReplaceOnError
    + +bool + +
    +(Optional) +

    ForceReplaceOnError instructs kluctl to force-replace resources in case a normal replace fails. +Equivalent to using ‘–force-replace-on-error’ when calling kluctl.

    +
    +abortOnError
    + +bool + +
    +(Optional) +

    ForceReplaceOnError instructs kluctl to abort deployments immediately when something fails. +Equivalent to using ‘–abort-on-error’ when calling kluctl.

    +
    +includeTags
    + +[]string + +
    +(Optional) +

    IncludeTags instructs kluctl to only include deployments with given tags. +Equivalent to using ‘–include-tag’ when calling kluctl.

    +
    +excludeTags
    + +[]string + +
    +(Optional) +

    ExcludeTags instructs kluctl to exclude deployments with given tags. +Equivalent to using ‘–exclude-tag’ when calling kluctl.

    +
    +includeDeploymentDirs
    + +[]string + +
    +(Optional) +

    IncludeDeploymentDirs instructs kluctl to only include deployments with the given dir. +Equivalent to using ‘–include-deployment-dir’ when calling kluctl.

    +
    +excludeDeploymentDirs
    + +[]string + +
    +(Optional) +

    ExcludeDeploymentDirs instructs kluctl to exclude deployments with the given dir. +Equivalent to using ‘–exclude-deployment-dir’ when calling kluctl.

    +
    +deployMode
    + +string + +
    +(Optional) +

    DeployMode specifies what deploy mode should be used. +The options ‘full-deploy’ and ‘poke-images’ are supported. +With the ‘poke-images’ option, only images are patched into the target without performing a full deployment.

    +
    +validate
    + +bool + +
    +(Optional) +

    Validate enables validation after deploying

    +
    +prune
    + +bool + +
    +(Optional) +

    Prune enables pruning after deploying.

    +
    +delete
    + +bool + +
    +(Optional) +

    Delete enables deletion of the specified target when the KluctlDeployment object gets deleted.

    +
    +
    +status
    + + +KluctlDeploymentStatus + + +
    +
    +
    +
    +

    KluctlDeploymentSpec +

    +

    +(Appears on: +KluctlDeployment) +

    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescription
    +source
    + + +ProjectSource + + +
    +

    Specifies the project source location

    +
    +decryption
    + + +Decryption + + +
    +(Optional) +

    Decrypt Kubernetes secrets before applying them on the cluster.

    +
    +interval
    + + +Kubernetes meta/v1.Duration + + +
    +

    The interval at which to reconcile the KluctlDeployment. +Reconciliation means that the deployment is fully rendered and only deployed when the result changes compared +to the last deployment. +To override this behavior, set the DeployInterval value.

    +
    +retryInterval
    + + +Kubernetes meta/v1.Duration + + +
    +(Optional) +

    The interval at which to retry a previously failed reconciliation. +When not specified, the controller uses the Interval +value to retry failures.

    +
    +deployInterval
    + + +SafeDuration + + +
    +(Optional) +

    DeployInterval specifies the interval at which to deploy the KluctlDeployment, even in cases the rendered +result does not change.

    +
    +validateInterval
    + + +SafeDuration + + +
    +(Optional) +

    ValidateInterval specifies the interval at which to validate the KluctlDeployment. +Validation is performed the same way as with ‘kluctl validate -t ’. +Defaults to the same value as specified in Interval. +Validate is also performed whenever a deployment is performed, independent of the value of ValidateInterval

    +
    +timeout
    + + +Kubernetes meta/v1.Duration + + +
    +(Optional) +

    Timeout for all operations. +Defaults to ‘Interval’ duration.

    +
    +suspend
    + +bool + +
    +(Optional) +

    This flag tells the controller to suspend subsequent kluctl executions, +it does not apply to already started executions. Defaults to false.

    +
    +helmCredentials
    + + +[]HelmCredentials + + +
    +(Optional) +

    HelmCredentials is a list of Helm credentials used when non pre-pulled Helm Charts are used inside a +Kluctl deployment.

    +
    +serviceAccountName
    + +string + +
    +(Optional) +

    The name of the Kubernetes service account to use while deploying. +If not specified, the default service account is used.

    +
    +kubeConfig
    + + +KubeConfig + + +
    +(Optional) +

    The KubeConfig for deploying to the target cluster. +Specifies the kubeconfig to be used when invoking kluctl. Contexts in this kubeconfig must match +the context found in the kluctl target. As an alternative, specify the context to be used via ‘context’

    +
    +target
    + +string + +
    +(Optional) +

    Target specifies the kluctl target to deploy. If not specified, an empty target is used that has no name and no +context. Use ‘TargetName’ and ‘Context’ to specify the name and context in that case.

    +
    +targetNameOverride
    + +string + +
    +(Optional) +

    TargetNameOverride sets or overrides the target name. This is especially useful when deployment without a target.

    +
    +context
    + +string + +
    +(Optional) +

    If specified, overrides the context to be used. This will effectively make kluctl ignore the context specified +in the target.

    +
    +args
    + +k8s.io/apimachinery/pkg/runtime.RawExtension + +
    +(Optional) +

    Args specifies dynamic target args.

    +
    +images
    + +[]github.com/kluctl/kluctl/v2/pkg/types.FixedImage + +
    +(Optional) +

    Images contains a list of fixed image overrides. +Equivalent to using ‘–fixed-images-file’ when calling kluctl.

    +
    +dryRun
    + +bool + +
    +(Optional) +

    DryRun instructs kluctl to run everything in dry-run mode. +Equivalent to using ‘–dry-run’ when calling kluctl.

    +
    +noWait
    + +bool + +
    +(Optional) +

    NoWait instructs kluctl to not wait for any resources to become ready, including hooks. +Equivalent to using ‘–no-wait’ when calling kluctl.

    +
    +forceApply
    + +bool + +
    +(Optional) +

    ForceApply instructs kluctl to force-apply in case of SSA conflicts. +Equivalent to using ‘–force-apply’ when calling kluctl.

    +
    +replaceOnError
    + +bool + +
    +(Optional) +

    ReplaceOnError instructs kluctl to replace resources on error. +Equivalent to using ‘–replace-on-error’ when calling kluctl.

    +
    +forceReplaceOnError
    + +bool + +
    +(Optional) +

    ForceReplaceOnError instructs kluctl to force-replace resources in case a normal replace fails. +Equivalent to using ‘–force-replace-on-error’ when calling kluctl.

    +
    +abortOnError
    + +bool + +
    +(Optional) +

    ForceReplaceOnError instructs kluctl to abort deployments immediately when something fails. +Equivalent to using ‘–abort-on-error’ when calling kluctl.

    +
    +includeTags
    + +[]string + +
    +(Optional) +

    IncludeTags instructs kluctl to only include deployments with given tags. +Equivalent to using ‘–include-tag’ when calling kluctl.

    +
    +excludeTags
    + +[]string + +
    +(Optional) +

    ExcludeTags instructs kluctl to exclude deployments with given tags. +Equivalent to using ‘–exclude-tag’ when calling kluctl.

    +
    +includeDeploymentDirs
    + +[]string + +
    +(Optional) +

    IncludeDeploymentDirs instructs kluctl to only include deployments with the given dir. +Equivalent to using ‘–include-deployment-dir’ when calling kluctl.

    +
    +excludeDeploymentDirs
    + +[]string + +
    +(Optional) +

    ExcludeDeploymentDirs instructs kluctl to exclude deployments with the given dir. +Equivalent to using ‘–exclude-deployment-dir’ when calling kluctl.

    +
    +deployMode
    + +string + +
    +(Optional) +

    DeployMode specifies what deploy mode should be used. +The options ‘full-deploy’ and ‘poke-images’ are supported. +With the ‘poke-images’ option, only images are patched into the target without performing a full deployment.

    +
    +validate
    + +bool + +
    +(Optional) +

    Validate enables validation after deploying

    +
    +prune
    + +bool + +
    +(Optional) +

    Prune enables pruning after deploying.

    +
    +delete
    + +bool + +
    +(Optional) +

    Delete enables deletion of the specified target when the KluctlDeployment object gets deleted.

    +
    +
    +
    +

    KluctlDeploymentStatus +

    +

    +(Appears on: +KluctlDeployment) +

    +

    KluctlDeploymentStatus defines the observed state of KluctlDeployment

    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescription
    +lastHandledReconcileAt
    + +string + +
    +(Optional) +

    LastHandledReconcileAt holds the value of the most recent +reconcile request value, so a change of the annotation value +can be detected.

    +
    +LastHandledDeployAt
    + +string + +
    +(Optional) +
    +observedGeneration
    + +int64 + +
    +(Optional) +

    ObservedGeneration is the last reconciled generation.

    +
    +observedCommit
    + +string + +
    +

    ObservedCommit is the last commit observed

    +
    +conditions
    + + +[]Kubernetes meta/v1.Condition + + +
    +(Optional) +
    +projectKey
    + +github.com/kluctl/kluctl/v2/pkg/types/result.ProjectKey + +
    +(Optional) +
    +targetKey
    + +github.com/kluctl/kluctl/v2/pkg/types/result.TargetKey + +
    +(Optional) +
    +lastObjectsHash
    + +string + +
    +(Optional) +
    +lastDeployError
    + +string + +
    +(Optional) +
    +lastPruneError
    + +string + +
    +(Optional) +
    +lastValidateError
    + +string + +
    +(Optional) +
    +lastDeployResult
    + +github.com/kluctl/kluctl/v2/pkg/types/result.CommandResultSummary + +
    +(Optional) +

    LastDeployResult is the result of the last deploy command

    +
    +lastPruneResult
    + +github.com/kluctl/kluctl/v2/pkg/types/result.CommandResultSummary + +
    +(Optional) +

    LastDeployResult is the result of the last prune command

    +
    +lastValidateResult
    + +github.com/kluctl/kluctl/v2/pkg/types/result.ValidateResult + +
    +(Optional) +

    LastValidateResult is the result of the last validate command

    +
    +
    +
    +

    KubeConfig +

    +

    +(Appears on: +KluctlDeploymentSpec) +

    +

    KubeConfig references a Kubernetes secret that contains a kubeconfig file.

    +
    +
    + + + + + + + + + + + + + +
    FieldDescription
    +secretRef
    + + +SecretKeyReference + + +
    +

    SecretRef holds the name of a secret that contains a key with +the kubeconfig file as the value. If no key is set, the key will default +to ‘value’. The secret must be in the same namespace as +the Kustomization. +It is recommended that the kubeconfig is self-contained, and the secret +is regularly updated if credentials such as a cloud-access-token expire. +Cloud specific cmd-path auth helpers will not function without adding +binaries and credentials to the Pod that is responsible for reconciling +the KluctlDeployment.

    +
    +
    +
    +

    LocalObjectReference +

    +

    +(Appears on: +Decryption, +HelmCredentials, +ProjectSource) +

    +
    +
    + + + + + + + + + + + + + +
    FieldDescription
    +name
    + +string + +
    +

    Name of the referent.

    +
    +
    +
    +

    ProjectSource +

    +

    +(Appears on: +KluctlDeploymentSpec) +

    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescription
    +url
    + +github.com/kluctl/kluctl/v2/pkg/types.GitUrl + +
    +

    Url specifies the Git url where the project source is located

    +
    +ref
    + + +GitRef + + +
    +(Optional) +

    Ref specifies the branch, tag or commit that should be used. If omitted, the default branch of the repo is used.

    +
    +path
    + +string + +
    +(Optional) +

    Path specifies the sub-directory to be used as project directory

    +
    +secretRef
    + + +LocalObjectReference + + +
    +(Optional) +

    SecretRef specifies the Secret containing authentication credentials for +the git repository. +For HTTPS repositories the Secret must contain ‘username’ and ‘password’ +fields. +For SSH repositories the Secret must contain ‘identity’ +and ‘known_hosts’ fields.

    +
    +
    +
    +

    SafeDuration +

    +

    +(Appears on: +KluctlDeploymentSpec) +

    +
    +
    + + + + + + + + + + + + + +
    FieldDescription
    +Duration
    + + +Kubernetes meta/v1.Duration + + +
    +
    +
    +
    +

    SecretKeyReference +

    +

    +(Appears on: +KubeConfig) +

    +

    SecretKeyReference contains enough information to locate the referenced Kubernetes Secret object in the same +namespace. Optionally a key can be specified. +Use this type instead of core/v1 SecretKeySelector when the Key is optional and the Optional field is not +applicable.

    +
    +
    + + + + + + + + + + + + + + + + + +
    FieldDescription
    +name
    + +string + +
    +

    Name of the Secret.

    +
    +key
    + +string + +
    +(Optional) +

    Key in the Secret, when not specified an implementation-specific default key is used.

    +
    +
    +
    +
    +

    This page was automatically generated with gen-crd-api-reference-docs

    +
    diff --git a/hack/api-docs/config.json b/hack/api-docs/config.json new file mode 100644 index 000000000..ae9d840f1 --- /dev/null +++ b/hack/api-docs/config.json @@ -0,0 +1,29 @@ +{ + "hideMemberFields": [ + "TypeMeta" + ], + "hideTypePatterns": [ + "ParseError$", + "List$" + ], + "externalPackages": [ + { + "typeMatchPrefix": "^k8s\\.io/apimachinery/pkg/apis/meta/v1\\.Duration$", + "docsURLTemplate": "https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#Duration" + }, + { + "typeMatchPrefix": "^k8s\\.io/apiextensions-apiserver/pkg/apis/apiextensions/v1\\.JSON$", + "docsURLTemplate": "https://pkg.go.dev/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1?tab=doc#JSON" + }, + { + "typeMatchPrefix": "^k8s\\.io/(api|apimachinery/pkg/apis)/", + "docsURLTemplate": "https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#{{lower .TypeIdentifier}}-{{arrIndex .PackageSegments -1}}-{{arrIndex .PackageSegments -2}}" + } + ], + "typeDisplayNamePrefixOverrides": { + "k8s.io/api/": "Kubernetes ", + "k8s.io/apimachinery/pkg/apis/": "Kubernetes ", + "k8s.io/apiextensions-apiserver/": "Kubernetes " + }, + "markdownDisabled": false +} diff --git a/hack/api-docs/template/members.tpl b/hack/api-docs/template/members.tpl new file mode 100644 index 000000000..26e7251e4 --- /dev/null +++ b/hack/api-docs/template/members.tpl @@ -0,0 +1,46 @@ +{{ define "members" }} + {{ range .Members }} + {{ if not (hiddenMember .)}} + + + {{ fieldName . }}
    + + {{ if linkForType .Type }} + + {{ typeDisplayName .Type }} + + {{ else }} + {{ typeDisplayName .Type }} + {{ end }} + + + + {{ if fieldEmbedded . }} +

    + (Members of {{ fieldName . }} are embedded into this type.) +

    + {{ end}} + + {{ if isOptionalMember .}} + (Optional) + {{ end }} + + {{ safe (renderComments .CommentLines) }} + + {{ if and (eq (.Type.Name.Name) "ObjectMeta") }} + Refer to the Kubernetes API documentation for the fields of the + metadata field. + {{ end }} + + {{ if or (eq (fieldName .) "spec") }} +
    +
    + + {{ template "members" .Type }} +
    + {{ end }} + + + {{ end }} + {{ end }} +{{ end }} diff --git a/hack/api-docs/template/pkg.tpl b/hack/api-docs/template/pkg.tpl new file mode 100644 index 000000000..0a4c0224e --- /dev/null +++ b/hack/api-docs/template/pkg.tpl @@ -0,0 +1,46 @@ +{{ define "packages" }} +

    Kluctl Controller API reference

    + + {{ with .packages}} +

    Packages:

    + + {{ end}} + + {{ range .packages }} +

    + {{- packageDisplayName . -}} +

    + + {{ with (index .GoPackages 0 )}} + {{ with .DocComments }} + {{ safe (renderComments .) }} + {{ end }} + {{ end }} + + Resource Types: + +
      + {{- range (visibleTypes (sortedTypes .Types)) -}} + {{ if isExportedType . -}} +
    • + {{ typeDisplayName . }} +
    • + {{- end }} + {{- end -}} +
    + + {{ range (visibleTypes (sortedTypes .Types))}} + {{ template "type" . }} + {{ end }} + {{ end }} + +
    +

    This page was automatically generated with gen-crd-api-reference-docs

    +
    +{{ end }} diff --git a/hack/api-docs/template/type.tpl b/hack/api-docs/template/type.tpl new file mode 100644 index 000000000..cd2fa6981 --- /dev/null +++ b/hack/api-docs/template/type.tpl @@ -0,0 +1,60 @@ +{{ define "type" }} +

    + {{- .Name.Name }} + {{ if eq .Kind "Alias" }}({{.Underlying}} alias){{ end -}} +

    + + {{ with (typeReferences .) }} +

    + (Appears on: + {{- $prev := "" -}} + {{- range . -}} + {{- if $prev -}}, {{ end -}} + {{ $prev = . }} + {{ typeDisplayName . }} + {{- end -}} + ) +

    + {{ end }} + + {{ with .CommentLines }} + {{ safe (renderComments .) }} + {{ end }} + + {{ if .Members }} +
    +
    + + + + + + + + + {{ if isExportedType . }} + + + + + + + + + {{ end }} + {{ template "members" . }} + +
    FieldDescription
    + apiVersion
    + string
    + {{ apiGroup . }} +
    + kind
    + string +
    + {{ .Name.Name }} +
    +
    +
    + {{ end }} +{{ end }} From f79ca8222c187193a19ce2a2f511ba6d911f2e10 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 31 May 2023 16:22:35 +0200 Subject: [PATCH 1728/2916] docs: Update Kluctl version in installation.md --- docs/installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation.md b/docs/installation.md index 656d3d93c..31c012d25 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -113,5 +113,5 @@ deployments: - git: url: https://github.com/kluctl/kluctl.git subDir: install/controller - ref: v0.0.0 + ref: v2.20.0 ``` From a143ba523a50be5e3f69eb4256803a8c007cdec9 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 31 May 2023 16:32:04 +0200 Subject: [PATCH 1729/2916] docs: Fix links to .md files --- docs/reference/gitops/spec/v1beta1/kluctldeployment.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/reference/gitops/spec/v1beta1/kluctldeployment.md b/docs/reference/gitops/spec/v1beta1/kluctldeployment.md index db46f4cf0..85396a984 100644 --- a/docs/reference/gitops/spec/v1beta1/kluctldeployment.md +++ b/docs/reference/gitops/spec/v1beta1/kluctldeployment.md @@ -362,8 +362,8 @@ stringData: ## Helm Repository authentication -Kluctl allows to [integrate Helm Charts](../../../deployments/helm) in two different ways. -One is to [pre-pull charts](../../../commands/helm-pull) and put them into version control, +Kluctl allows to [integrate Helm Charts](../../../deployments/helm.md) in two different ways. +One is to [pre-pull charts](../../../commands/helm-pull.md) and put them into version control, making it unnecessary to pull them at deploy time. This option also means that you don't have to take any special care on the controller side. @@ -432,7 +432,7 @@ This will pass the credentials to all requests, even if the hostname changes. ## Secrets Decryption -Kluctl offers a [SOPS Integration](../../../deployments/sops) that allows to use encrypted +Kluctl offers a [SOPS Integration](../../../deployments/sops.md) that allows to use encrypted manifests and variable sources in Kluctl deployments. Decryption by the controller is also supported and currently mirrors how the [Secrets Decryption configuration](https://fluxcd.io/flux/components/kustomize/kustomization/#secrets-decryption) of the Flux Kustomize Controller. To configure it in the `KluctlDeployment`, simply set the `decryption` field in the From 442e3f675766929d2c2bbd091b55bf7f09ed1815 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 31 May 2023 16:42:04 +0200 Subject: [PATCH 1730/2916] build: Preparing release v2.20.1 --- docs/installation.md | 2 +- install/controller/.kluctl.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/installation.md b/docs/installation.md index 31c012d25..c899bccd5 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -113,5 +113,5 @@ deployments: - git: url: https://github.com/kluctl/kluctl.git subDir: install/controller - ref: v2.20.0 + ref: v2.20.1 ``` diff --git a/install/controller/.kluctl.yaml b/install/controller/.kluctl.yaml index 481c21a87..f74304426 100644 --- a/install/controller/.kluctl.yaml +++ b/install/controller/.kluctl.yaml @@ -2,4 +2,4 @@ discriminator: kluctl.io-controller args: - name: controller_version - default: v2.20.0 \ No newline at end of file + default: v2.20.1 \ No newline at end of file From c61c60d907f49dbc6d6c604f9bcf5fef3913cfe0 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 31 May 2023 18:08:04 +0200 Subject: [PATCH 1731/2916] fix: Use projectDir as repoRoot in case there is no Git repo involved --- cmd/kluctl/commands/utils.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmd/kluctl/commands/utils.go b/cmd/kluctl/commands/utils.go index a03b70b77..0f524badc 100644 --- a/cmd/kluctl/commands/utils.go +++ b/cmd/kluctl/commands/utils.go @@ -50,6 +50,10 @@ func withKluctlProjectFromArgs(ctx context.Context, projectFlags args.ProjectFla } } + if repoRoot == "" { + repoRoot = projectDir + } + ctx, cancel := context.WithTimeout(ctx, projectFlags.Timeout) defer cancel() From c3636d2556b28233fc6719789e05a30e83b53230 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 31 May 2023 18:13:00 +0200 Subject: [PATCH 1732/2916] docs: Fix link to images.md --- docs/reference/gitops/spec/v1beta1/kluctldeployment.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/gitops/spec/v1beta1/kluctldeployment.md b/docs/reference/gitops/spec/v1beta1/kluctldeployment.md index 85396a984..391bbd7cb 100644 --- a/docs/reference/gitops/spec/v1beta1/kluctldeployment.md +++ b/docs/reference/gitops/spec/v1beta1/kluctldeployment.md @@ -156,7 +156,7 @@ The above example is equivalent to calling `kluctl deploy -t prod -a arg1=value1 ### images `spec.images` specifies a list of fixed images to be used by -[`image.get_image(...)`](../../../deployments/images#imagesget_image). Example: +[`image.get_image(...)`](../../../deployments/images.md#imagesget_image). Example: ``` apiVersion: gitops.kluctl.io/v1beta1 From 4bff0959cd734a2bc6e80f4ed53e157d6b6ac5c6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 31 May 2023 19:49:56 +0200 Subject: [PATCH 1733/2916] fix: Fix pre-pulled helm chart pathes --- pkg/deployment/deployment_item.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pkg/deployment/deployment_item.go b/pkg/deployment/deployment_item.go index d291acbd0..9ad65c80c 100644 --- a/pkg/deployment/deployment_item.go +++ b/pkg/deployment/deployment_item.go @@ -180,7 +180,16 @@ func (di *DeploymentItem) isHelmValuesYaml(p string) bool { func (di *DeploymentItem) newHelmRelease(subDir string) (*helm.Release, error) { configPath := yaml.FixPathExt(filepath.Join(di.RenderedDir, subDir, "helm-chart.yaml")) - helmChartsDir := filepath.Join(di.Project.source.dir, ".helm-charts") + + var helmChartsDir string + if di.Project.source == di.Project.getRootProject().source { + // deployment item is part of the root project repo, so it's allowed to be in a relative dir + helmChartsDir = filepath.Join(di.Project.source.dir, di.Project.getRootProject().relDir, ".helm-charts") + } else { + // deployment item is part of a git-included project, so it must be at the repo root + // TODO this limitation should be lifted in some way (search multiple pathes?) + helmChartsDir = filepath.Join(di.Project.source.dir, ".helm-charts") + } hr, err := helm.NewRelease(di.Project.source.dir, filepath.Join(di.RelToSourceItemDir, subDir), configPath, helmChartsDir, di.ctx.HelmCredentials) if err != nil { From f80248ea7cc2a810db8210612af6095be0410c5c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 31 May 2023 19:58:21 +0200 Subject: [PATCH 1734/2916] build: Preparing release v2.20.2 --- docs/installation.md | 2 +- install/controller/.kluctl.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/installation.md b/docs/installation.md index c899bccd5..7e40fabdb 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -113,5 +113,5 @@ deployments: - git: url: https://github.com/kluctl/kluctl.git subDir: install/controller - ref: v2.20.1 + ref: v2.20.2 ``` diff --git a/install/controller/.kluctl.yaml b/install/controller/.kluctl.yaml index f74304426..166bbf0ae 100644 --- a/install/controller/.kluctl.yaml +++ b/install/controller/.kluctl.yaml @@ -2,4 +2,4 @@ discriminator: kluctl.io-controller args: - name: controller_version - default: v2.20.1 \ No newline at end of file + default: v2.20.2 \ No newline at end of file From cbb7fc433c5c333aa26c027fd76a92be41d2c80e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 31 May 2023 22:48:13 +0200 Subject: [PATCH 1735/2916] build: Multiple fixes to docs generation and checks (#527) * build: Write api-docs result to correct directory * build: Exclude api-docs from markdown-link-check --- Makefile | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 58d9424aa..c7bea97f0 100644 --- a/Makefile +++ b/Makefile @@ -66,7 +66,7 @@ manifests: controller-gen kustomize ## Generate WebhookConfiguration, ClusterRol # Generate API reference documentation api-docs: gen-crd-api-reference-docs - $(GEN_CRD_API_REFERENCE_DOCS) -v=4 -api-dir=./api/v1beta1 -config=./hack/api-docs/config.json -template-dir=./hack/api-docs/template -out-file=./docs/reference/controller/api/kluctl-controller.md + $(GEN_CRD_API_REFERENCE_DOCS) -v=4 -api-dir=./api/v1beta1 -config=./hack/api-docs/config.json -template-dir=./hack/api-docs/template -out-file=./docs/reference/gitops/api/kluctl-controller.md .PHONY: generate generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. @@ -97,7 +97,11 @@ replace-commands-help: ## Replace commands help in docs MARKDOWN_LINK_CHECK_VERSION=3.11.2 markdown-link-check: ## Check markdown files for dead links - find . -name '*.md' | xargs docker run -v ${PWD}:/tmp:ro --rm -i -w /tmp ghcr.io/tcort/markdown-link-check:$(MARKDOWN_LINK_CHECK_VERSION) + find . -name '*.md' \ + -and -not -path './docs/reference/gitops/api/kluctl-controller.md' \ + -and -not -path './pkg/webui/ui/node_modules/*' \ + -and -not -path './pkg/webui/ui/build/*' | \ + xargs docker run -v ${PWD}:/tmp:ro --rm -i -w /tmp ghcr.io/tcort/markdown-link-check:$(MARKDOWN_LINK_CHECK_VERSION) ##@ Build From cb6b4f62be770e8a6758c86032a5f5063a2216f6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 1 Jun 2023 12:38:14 +0200 Subject: [PATCH 1736/2916] fix: Don't show error when addGitInfo fails (#528) --- pkg/deployment/commands/result_utils.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/deployment/commands/result_utils.go b/pkg/deployment/commands/result_utils.go index 4b987d949..52e210d98 100644 --- a/pkg/deployment/commands/result_utils.go +++ b/pkg/deployment/commands/result_utils.go @@ -90,6 +90,9 @@ func addGitInfo(targetCtx *kluctl_project.TargetContext, r *result.CommandResult g, err := git2.PlainOpen(targetCtx.KluctlProject.LoadArgs.RepoRoot) if err != nil { + if err == git2.ErrRepositoryNotExists { + return nil + } return err } From 932661e4d57505eebb60f90aebaa3cb737c12251 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 1 Jun 2023 13:03:43 +0200 Subject: [PATCH 1737/2916] fix: Allow to specify controller_version via args and via vars --- hack/prepare-release.sh | 2 +- install/controller/controller/kustomization.yaml | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/hack/prepare-release.sh b/hack/prepare-release.sh index 496c9b6bd..f63be7543 100755 --- a/hack/prepare-release.sh +++ b/hack/prepare-release.sh @@ -24,7 +24,7 @@ fi echo VERSION=$VERSION -FILES="install/controller/.kluctl.yaml docs/installation.md" +FILES="install/controller/.kluctl.yaml install/controller/controller/kustomization.yaml docs/installation.md" for f in $FILES; do cat $f | sed "s/$VERSION_REGEX_SED/$VERSION/g" > $f.tmp diff --git a/install/controller/controller/kustomization.yaml b/install/controller/controller/kustomization.yaml index d4711bbb7..961e9fca4 100644 --- a/install/controller/controller/kustomization.yaml +++ b/install/controller/controller/kustomization.yaml @@ -1,3 +1,5 @@ +{% set controller_version = (args.controller_version if args.controller_version is defined else controller_version) | default("v2.20.2") %} + resources: - crd.yaml - manager.yaml @@ -10,8 +12,8 @@ patches: patch: |- - op: add path: /spec/template/spec/containers/0/image - value: ghcr.io/kluctl/kluctl:{{ args.controller_version }} -{% if args.controller_version.endswith("-next-amd64") or args.controller_version.endswith("-next-arm64") %} + value: ghcr.io/kluctl/kluctl:{{ controller_version }} +{% if controller_version.endswith("-next-amd64") or controller_version.endswith("-next-arm64") %} - target: kind: Deployment name: kluctl-controller From 3ac2dfae67c70b481375794428a044ffb58e9adc Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 1 Jun 2023 13:36:07 +0200 Subject: [PATCH 1738/2916] fix: Switch back to debian based base images due to missing arm+musl support --- Dockerfile | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 77958da58..233c596fe 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,11 @@ -FROM alpine:3.18.0 +# We must use a glibc based distro due to embedded python not supporting musl libc for aarch64 (only amd64+musl is supported) +# see https://github.com/indygreg/python-build-standalone/issues/87 +FROM debian:bullseye-slim -RUN apk add ca-certificates +RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates && rm -rf /var/lib/apt/lists/* # We need git for kustomize to support overlays from git -RUN apk add git +RUN apt-get update && apt-get install -y --no-install-recommends git && rm -rf /var/lib/apt/lists/* # Ensure helm is not trying to access / ENV HELM_CACHE_HOME=/tmp/helm-cache From 3d6033ceb99d9cc156310e645b2c3f4993a0ece2 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 1 Jun 2023 14:07:38 +0200 Subject: [PATCH 1739/2916] fix: Properly pass hostkey callback to ssh auth method The KnownHostsWrapper used in the past is not needed anymore and actually caused host key verification to fail when ~/.ssh/known_hosts was missing, even if known_hosts were explicitely passed. --- pkg/git/auth/list_auth_provider.go | 29 ++--------------------------- 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/pkg/git/auth/list_auth_provider.go b/pkg/git/auth/list_auth_provider.go index b69f0b2df..444e38d4d 100644 --- a/pkg/git/auth/list_auth_provider.go +++ b/pkg/git/auth/list_auth_provider.go @@ -6,7 +6,6 @@ import ( "github.com/go-git/go-git/v5/plumbing/transport/ssh" "github.com/kluctl/kluctl/v2/pkg/git/messages" "github.com/kluctl/kluctl/v2/pkg/types" - ssh2 "golang.org/x/crypto/ssh" "strings" ) @@ -28,28 +27,6 @@ type AuthEntry struct { CABundle []byte } -type KnownHostsWrapper struct { - authMethod ssh.AuthMethod - hostKeyCallback ssh2.HostKeyCallback -} - -func (w *KnownHostsWrapper) String() string { - return w.authMethod.String() -} - -func (w *KnownHostsWrapper) Name() string { - return w.authMethod.Name() -} - -func (w *KnownHostsWrapper) ClientConfig() (*ssh2.ClientConfig, error) { - ccfg, err := w.authMethod.ClientConfig() - if err != nil { - return nil, err - } - ccfg.HostKeyCallback = w.hostKeyCallback - return ccfg, nil -} - func (a *ListAuthProvider) AddEntry(e AuthEntry) { a.entries = append(a.entries, e) } @@ -102,11 +79,9 @@ func (a *ListAuthProvider) BuildAuth(ctx context.Context, gitUrl types.GitUrl) A if err != nil { a.MessageCallbacks.Trace("ListAuthProvider: failed to parse private key: %v", err) } else { + pk.HostKeyCallback = buildVerifyHostCallback(a.MessageCallbacks, e.KnownHosts) return AuthMethodAndCA{ - AuthMethod: &KnownHostsWrapper{ - authMethod: pk, - hostKeyCallback: buildVerifyHostCallback(a.MessageCallbacks, e.KnownHosts), - }, + AuthMethod: pk, Hash: func() ([]byte, error) { return buildHash(pk.Signer) }, From 8a93c5785eaeff25623f5daea0c78fd0ca358121 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 1 Jun 2023 14:29:14 +0200 Subject: [PATCH 1740/2916] build: Preparing release v2.20.3 --- docs/installation.md | 2 +- install/controller/.kluctl.yaml | 2 +- install/controller/controller/kustomization.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/installation.md b/docs/installation.md index 7e40fabdb..ffb8a3055 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -113,5 +113,5 @@ deployments: - git: url: https://github.com/kluctl/kluctl.git subDir: install/controller - ref: v2.20.2 + ref: v2.20.3 ``` diff --git a/install/controller/.kluctl.yaml b/install/controller/.kluctl.yaml index 166bbf0ae..aa23bb3d9 100644 --- a/install/controller/.kluctl.yaml +++ b/install/controller/.kluctl.yaml @@ -2,4 +2,4 @@ discriminator: kluctl.io-controller args: - name: controller_version - default: v2.20.2 \ No newline at end of file + default: v2.20.3 \ No newline at end of file diff --git a/install/controller/controller/kustomization.yaml b/install/controller/controller/kustomization.yaml index 961e9fca4..77c7fc8cf 100644 --- a/install/controller/controller/kustomization.yaml +++ b/install/controller/controller/kustomization.yaml @@ -1,4 +1,4 @@ -{% set controller_version = (args.controller_version if args.controller_version is defined else controller_version) | default("v2.20.2") %} +{% set controller_version = (args.controller_version if args.controller_version is defined else controller_version) | default("v2.20.3") %} resources: - crd.yaml From 198f4db40069ad7e7e2f691a0683431429c5fcac Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 2 Jun 2023 10:10:18 +0200 Subject: [PATCH 1741/2916] ci: Add nightly/devel builds for main branch (#530) --- .github/workflows/nightly.yml | 56 +++++++++++++++++++++++++++++++++++ .github/workflows/release.yml | 11 +++---- .goreleaser.yaml | 9 ++++-- 3 files changed, 69 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/nightly.yml diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml new file mode 100644 index 000000000..1aa476299 --- /dev/null +++ b/.github/workflows/nightly.yml @@ -0,0 +1,56 @@ +name: goreleaser-release + +on: + push: + branches: + - main + +permissions: + contents: write + packages: write + +jobs: + goreleaser: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Fetch all tags + run: git fetch --force --tags + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: 1.19 + - name: Setup QEMU + uses: docker/setup-qemu-action@v2 + - name: Setup Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v2 + - name: Setup Syft + uses: anchore/sbom-action/download-syft@v0 + - name: Login to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: kluctlbot + password: ${{ secrets.GHCR_TOKEN }} + - uses: actions/cache@v3 + with: + path: | + ~/go/pkg/mod + ~/.cache/go-build + key: ${{ runner.os }}-goreleaser-nightly-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-goreleaser-nightly- + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v4 + with: + distribution: goreleaser-pro + version: latest + args: release --nightly --clean + env: + GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5289b22a7..56a929fcc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,4 +1,4 @@ -name: goreleaser +name: goreleaser-release on: push: @@ -42,15 +42,16 @@ jobs: path: | ~/go/pkg/mod ~/.cache/go-build - key: ${{ runner.os }}-goreleaser-${{ hashFiles('**/go.sum') }} + key: ${{ runner.os }}-goreleaser-release-${{ hashFiles('**/go.sum') }} restore-keys: | - ${{ runner.os }}-goreleaser- + ${{ runner.os }}-goreleaser-release- - name: Run GoReleaser uses: goreleaser/goreleaser-action@v4 with: - distribution: goreleaser + distribution: goreleaser-pro version: latest - args: release --rm-dist + args: release --clean env: + GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }} diff --git a/.goreleaser.yaml b/.goreleaser.yaml index ba0d196f9..2a6f9fc00 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -52,7 +52,12 @@ sboms: checksum: name_template: '{{ .ProjectName }}_v{{ .Version }}_checksums.txt' snapshot: - name_template: "{{ incminor .Version }}-next" + name_template: "{{ incminor .Version }}-snapshot" +nightly: + name_template: 'devel' + tag_name: devel + publish_release: true + keep_single_release: true changelog: sort: asc filters: @@ -100,7 +105,7 @@ dockers: - "ghcr.io/kluctl/kluctl:v{{ .Version }}-arm64" docker_manifests: - - name_template: ghcr.io/kluctl/kluctl:{{ .Tag }} + - name_template: ghcr.io/kluctl/kluctl:{{ .Version }} image_templates: - "ghcr.io/kluctl/kluctl:v{{ .Version }}-amd64" - "ghcr.io/kluctl/kluctl:v{{ .Version }}-arm64" From 6b8d0cb991d2f6a61009948f762626bc38e3a43a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 2 Jun 2023 10:14:41 +0200 Subject: [PATCH 1742/2916] ci: Fix name of nightly release workflow (#531) --- .github/workflows/nightly.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 1aa476299..7419b12ca 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -1,4 +1,4 @@ -name: goreleaser-release +name: goreleaser-nightly on: push: From d3bbc566328a862d3899db86250f5bf481b2462b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 2 Jun 2023 10:35:14 +0200 Subject: [PATCH 1743/2916] ci: Use next version number in devel version (#532) --- .goreleaser.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 2a6f9fc00..53c42dfc4 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -54,7 +54,7 @@ checksum: snapshot: name_template: "{{ incminor .Version }}-snapshot" nightly: - name_template: 'devel' + name_template: '{{ incminor .Version }}-devel' tag_name: devel publish_release: true keep_single_release: true From 8d37f7842d7c1c1026f9a36db7c7fbda3d8b807d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 2 Jun 2023 11:10:44 +0200 Subject: [PATCH 1744/2916] ci: Fix missing version in nightlies and mark as non-draft (#533) * ci: Use .Version instead of .Tag for release name_template This fixes missing versions in nightlies. * ci: Set draft=false for nightlies --- .github/workflows/nightly.yml | 4 ++++ .goreleaser.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 7419b12ca..54fb5e8c3 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -44,6 +44,10 @@ jobs: key: ${{ runner.os }}-goreleaser-nightly-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-goreleaser-nightly- + - name: Set .goreleaser.yaml's release.draft=false + shell: bash + run: | + cat .goreleaser.yaml | sed 's/draft: true/draft: false/g' > .goreleaser.yaml.tmp && mv .goreleaser.yaml.tmp .goreleaser.yaml - name: Run GoReleaser uses: goreleaser/goreleaser-action@v4 with: diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 53c42dfc4..9bb7f9c57 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -72,7 +72,7 @@ changelog: release: draft: true prerelease: auto - name_template: "{{.ProjectName}}-{{.Tag}}" + name_template: "{{ .ProjectName }}-v{{ .Version }}" dockers: - id: linux-amd64 From 8ce7fcbaa3f9abc35e49b0beebaa683fdfe35dbe Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 2 Jun 2023 11:27:26 +0200 Subject: [PATCH 1745/2916] ci: Fix docker tags and add header to nightly releases (#534) * ci: Fix docker tag of releases * ci: Add header to nightly releases --- .goreleaser.yaml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 9bb7f9c57..c8ebae4c9 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -73,6 +73,12 @@ release: draft: true prerelease: auto name_template: "{{ .ProjectName }}-v{{ .Version }}" + header: | + {{- if .IsNightly -}} + ## Development build + This is a development build of the main branch and not meant for production use. + Docker images are also available via: `ghcr.io/kluctl/kluctl:v{{ .Version }}` + {{- end -}} dockers: - id: linux-amd64 @@ -105,7 +111,7 @@ dockers: - "ghcr.io/kluctl/kluctl:v{{ .Version }}-arm64" docker_manifests: - - name_template: ghcr.io/kluctl/kluctl:{{ .Version }} + - name_template: ghcr.io/kluctl/kluctl:v{{ .Version }} image_templates: - "ghcr.io/kluctl/kluctl:v{{ .Version }}-amd64" - "ghcr.io/kluctl/kluctl:v{{ .Version }}-arm64" From 5ecc3aca34fdeef2432001d871c75fd32a169320 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 2 Jun 2023 13:58:43 +0200 Subject: [PATCH 1746/2916] docs: Don't include pre-releases in version badge (#535) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 33ed42f81..1347f626b 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![tests](https://github.com/kluctl/kluctl/workflows/tests/badge.svg)](https://github.com/kluctl/kluctl/actions) [![license](https://img.shields.io/github/license/kluctl/kluctl.svg)](https://github.com/kluctl/kluctl/blob/main/LICENSE) -[![release](https://img.shields.io/github/release/kluctl/kluctl/all.svg)](https://github.com/kluctl/kluctl/releases) +[![release](https://img.shields.io/github/release/kluctl/kluctl.svg)](https://github.com/kluctl/kluctl/releases) kluctl From 5648bb9294888be9779528af7eb582d7498b1fad Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 2 Jun 2023 15:00:37 +0200 Subject: [PATCH 1747/2916] fix: Use absolute version of --project-dir (#537) --- cmd/kluctl/args/project.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/kluctl/args/project.go b/cmd/kluctl/args/project.go index 21468a46b..880426843 100644 --- a/cmd/kluctl/args/project.go +++ b/cmd/kluctl/args/project.go @@ -2,6 +2,7 @@ package args import ( "os" + "path/filepath" "time" ) @@ -11,7 +12,7 @@ type ProjectDir struct { func (a ProjectDir) GetProjectDir() (string, error) { if a.ProjectDir != "" { - return a.ProjectDir.String(), nil + return filepath.Abs(a.ProjectDir.String()) } cwd, err := os.Getwd() if err != nil { From 29e2b9b45cf2b7133b575897822e4a68bde85c26 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 2 Jun 2023 15:01:30 +0200 Subject: [PATCH 1748/2916] feat: Use chainguards wolfi-base image as base (#536) This gives us a very slim alpine-like image with glibc instead of musl. --- Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 233c596fe..5c2caa191 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,11 @@ # We must use a glibc based distro due to embedded python not supporting musl libc for aarch64 (only amd64+musl is supported) # see https://github.com/indygreg/python-build-standalone/issues/87 -FROM debian:bullseye-slim +FROM cgr.dev/chainguard/wolfi-base -RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates && rm -rf /var/lib/apt/lists/* +RUN apk add ca-certificates # We need git for kustomize to support overlays from git -RUN apt-get update && apt-get install -y --no-install-recommends git && rm -rf /var/lib/apt/lists/* +RUN apk add git # Ensure helm is not trying to access / ENV HELM_CACHE_HOME=/tmp/helm-cache From 41e5b749cd42cc2a3b0e3a192133b5346aba0c15 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 2 Jun 2023 15:11:52 +0200 Subject: [PATCH 1749/2916] ci: Only allow a single nightly workflow to run (#538) --- .github/workflows/nightly.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 54fb5e8c3..24933597b 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -9,6 +9,8 @@ permissions: contents: write packages: write +concurrency: goreleaser-nightly + jobs: goreleaser: runs-on: ubuntu-latest From 0c4b3a36ab13478aca5e0841a73367f2170ca01e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 2 Jun 2023 16:05:29 +0200 Subject: [PATCH 1750/2916] chore: Remove unneeded ca-certificates installation from Dockerfile (#539) wolfi-base already has this installed. --- Dockerfile | 2 -- 1 file changed, 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 5c2caa191..91818c999 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,8 +2,6 @@ # see https://github.com/indygreg/python-build-standalone/issues/87 FROM cgr.dev/chainguard/wolfi-base -RUN apk add ca-certificates - # We need git for kustomize to support overlays from git RUN apk add git From 225cc331eeeafe03a164c87009eced9e53187ef6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 2 Jun 2023 16:33:30 +0200 Subject: [PATCH 1751/2916] fix: Check for proper suffix when deciding on imagePullPolicy: Always --- install/controller/controller/kustomization.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/controller/controller/kustomization.yaml b/install/controller/controller/kustomization.yaml index 77c7fc8cf..c159e432b 100644 --- a/install/controller/controller/kustomization.yaml +++ b/install/controller/controller/kustomization.yaml @@ -13,7 +13,7 @@ patches: - op: add path: /spec/template/spec/containers/0/image value: ghcr.io/kluctl/kluctl:{{ controller_version }} -{% if controller_version.endswith("-next-amd64") or controller_version.endswith("-next-arm64") %} +{% if "-devel" in controller_version %} - target: kind: Deployment name: kluctl-controller From 03ac5bee4b7c69bdf9fe0cc20fcb1a9b3752f4ce Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 2 Jun 2023 16:58:28 +0200 Subject: [PATCH 1752/2916] refactor: Use get_var to simplify controller_version defaulting --- install/controller/controller/kustomization.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/controller/controller/kustomization.yaml b/install/controller/controller/kustomization.yaml index c159e432b..4d84df07c 100644 --- a/install/controller/controller/kustomization.yaml +++ b/install/controller/controller/kustomization.yaml @@ -1,4 +1,4 @@ -{% set controller_version = (args.controller_version if args.controller_version is defined else controller_version) | default("v2.20.3") %} +{% set controller_version = get_var("args.controller_version", "v2.20.3") %} resources: - crd.yaml From 53d56edde0f504daf12a3f046ed5ddd0a8a8b431 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 2 Jun 2023 16:59:07 +0200 Subject: [PATCH 1753/2916] feat: Allow to specify controller args and env vars --- config/manager/manager.yaml | 9 +++++---- install/controller/.kluctl.yaml | 6 +++++- install/controller/controller/kustomization.yaml | 15 ++++++++++++++- install/controller/controller/manager.yaml | 1 + 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index 83c13b50a..4700ace09 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -66,15 +66,16 @@ spec: # seccompProfile: # type: RuntimeDefault containers: - - command: + - name: controller + image: ghcr.io/kluctl/kluctl:latest + imagePullPolicy: IfNotPresent + command: - kluctl - controller - run args: - --leader-elect - image: ghcr.io/kluctl/kluctl:latest - imagePullPolicy: IfNotPresent - name: controller + env: [] securityContext: allowPrivilegeEscalation: false capabilities: diff --git a/install/controller/.kluctl.yaml b/install/controller/.kluctl.yaml index aa23bb3d9..d45892605 100644 --- a/install/controller/.kluctl.yaml +++ b/install/controller/.kluctl.yaml @@ -2,4 +2,8 @@ discriminator: kluctl.io-controller args: - name: controller_version - default: v2.20.3 \ No newline at end of file + default: v2.20.3 + - name: controller_args + default: [] + - name: controller_envs + default: [] diff --git a/install/controller/controller/kustomization.yaml b/install/controller/controller/kustomization.yaml index 4d84df07c..06923f9bd 100644 --- a/install/controller/controller/kustomization.yaml +++ b/install/controller/controller/kustomization.yaml @@ -13,12 +13,25 @@ patches: - op: add path: /spec/template/spec/containers/0/image value: ghcr.io/kluctl/kluctl:{{ controller_version }} -{% if "-devel" in controller_version %} - target: kind: Deployment name: kluctl-controller patch: |- + - op: test + path: /kind + value: Deployment # this is just a dummy test to avoid empty patches +{% if "-devel" in controller_version %} - op: add path: /spec/template/spec/containers/0/imagePullPolicy value: Always {% endif %} +{% for a in get_var("args.controller_args", []) %} + - op: add + path: /spec/template/spec/containers/0/args/- + value: "{{ a }}" +{% endfor %} +{% for a in get_var("args.controller_envs", []) %} + - op: add + path: /spec/template/spec/containers/0/env/- + value: {{ a | to_json }} +{% endfor %} diff --git a/install/controller/controller/manager.yaml b/install/controller/controller/manager.yaml index 5544b30e2..8ef9d3e81 100644 --- a/install/controller/controller/manager.yaml +++ b/install/controller/controller/manager.yaml @@ -43,6 +43,7 @@ spec: - kluctl - controller - run + env: [] image: ghcr.io/kluctl/kluctl:latest imagePullPolicy: IfNotPresent livenessProbe: From bdb1b4a5f647093be2d9196fa1bbdfc0aa3fe5bb Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 2 Jun 2023 17:28:16 +0200 Subject: [PATCH 1754/2916] fix: Use runtime.RawExtension for deploy/prune/validate results This removes the need to stay strictly compatible in results --- api/v1beta1/kluctldeployment_types.go | 95 ++- api/v1beta1/zz_generated.deepcopy.go | 6 +- .../gitops.kluctl.io_kluctldeployments.yaml | 673 +----------------- .../reference/gitops/api/kluctl-controller.md | 6 +- e2e/gitops_errors_test.go | 4 +- install/controller/controller/crd.yaml | 673 +----------------- .../kluctldeployment_controller.go | 66 +- 7 files changed, 157 insertions(+), 1366 deletions(-) diff --git a/api/v1beta1/kluctldeployment_types.go b/api/v1beta1/kluctldeployment_types.go index b5021142c..30bfa45cc 100644 --- a/api/v1beta1/kluctldeployment_types.go +++ b/api/v1beta1/kluctldeployment_types.go @@ -19,6 +19,7 @@ package v1beta1 import ( "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/types/result" + "github.com/kluctl/kluctl/v2/pkg/yaml" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "time" @@ -327,15 +328,103 @@ type KluctlDeploymentStatus struct { // LastDeployResult is the result of the last deploy command // +optional - LastDeployResult *result.CommandResultSummary `json:"lastDeployResult,omitempty"` + // +kubebuilder:pruning:PreserveUnknownFields + LastDeployResult *runtime.RawExtension `json:"lastDeployResult,omitempty"` // LastDeployResult is the result of the last prune command // +optional - LastPruneResult *result.CommandResultSummary `json:"lastPruneResult,omitempty"` + LastPruneResult *runtime.RawExtension `json:"lastPruneResult,omitempty"` // LastValidateResult is the result of the last validate command // +optional - LastValidateResult *result.ValidateResult `json:"lastValidateResult,omitempty"` + LastValidateResult *runtime.RawExtension `json:"lastValidateResult,omitempty"` +} + +func (s *KluctlDeploymentStatus) SetLastDeployResult(crs *result.CommandResultSummary, err error) error { + s.LastDeployError = "" + if err != nil { + s.LastDeployError = err.Error() + } + if crs == nil { + s.LastDeployResult = nil + } else { + b, err := yaml.WriteJsonString(crs) + if err != nil { + return err + } + s.LastDeployResult = &runtime.RawExtension{Raw: []byte(b)} + } + return nil +} + +func (s *KluctlDeploymentStatus) SetLastPruneResult(crs *result.CommandResultSummary, err error) error { + s.LastPruneError = "" + if err != nil { + s.LastPruneError = err.Error() + } + if crs == nil { + s.LastPruneResult = nil + } else { + b, err := yaml.WriteJsonString(crs) + if err != nil { + return err + } + s.LastPruneResult = &runtime.RawExtension{Raw: []byte(b)} + } + return nil +} + +func (s *KluctlDeploymentStatus) SetLastValidateResult(crs *result.ValidateResult, err error) error { + s.LastValidateError = "" + if err != nil { + s.LastValidateError = err.Error() + } + if crs == nil { + s.LastValidateResult = nil + } else { + b, err := yaml.WriteJsonString(crs) + if err != nil { + return err + } + s.LastValidateResult = &runtime.RawExtension{Raw: []byte(b)} + } + return nil +} + +func (s *KluctlDeploymentStatus) GetLastDeployResult() (*result.CommandResultSummary, error) { + if s.LastDeployResult == nil { + return nil, nil + } + var ret result.CommandResultSummary + err := yaml.ReadYamlBytes(s.LastDeployResult.Raw, &ret) + if err != nil { + return nil, err + } + return &ret, nil +} + +func (s *KluctlDeploymentStatus) GetLastPruneResult() (*result.CommandResultSummary, error) { + if s.LastPruneResult == nil { + return nil, nil + } + var ret result.CommandResultSummary + err := yaml.ReadYamlBytes(s.LastPruneResult.Raw, &ret) + if err != nil { + return nil, err + } + return &ret, nil +} + +func (s *KluctlDeploymentStatus) GetLastValidateResult() (*result.ValidateResult, error) { + if s.LastValidateResult == nil { + return nil, nil + } + var ret result.ValidateResult + err := yaml.ReadYamlBytes(s.LastValidateResult.Raw, &ret) + if err != nil { + return nil, err + } + return &ret, nil } //+kubebuilder:object:root=true diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 79c1afc75..1483d24ad 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -255,17 +255,17 @@ func (in *KluctlDeploymentStatus) DeepCopyInto(out *KluctlDeploymentStatus) { } if in.LastDeployResult != nil { in, out := &in.LastDeployResult, &out.LastDeployResult - *out = new(result.CommandResultSummary) + *out = new(runtime.RawExtension) (*in).DeepCopyInto(*out) } if in.LastPruneResult != nil { in, out := &in.LastPruneResult, &out.LastPruneResult - *out = new(result.CommandResultSummary) + *out = new(runtime.RawExtension) (*in).DeepCopyInto(*out) } if in.LastValidateResult != nil { in, out := &in.LastValidateResult, &out.LastValidateResult - *out = new(result.ValidateResult) + *out = new(runtime.RawExtension) (*in).DeepCopyInto(*out) } } diff --git a/config/crd/bases/gitops.kluctl.io_kluctldeployments.yaml b/config/crd/bases/gitops.kluctl.io_kluctldeployments.yaml index d07ed9349..3254ed420 100644 --- a/config/crd/bases/gitops.kluctl.io_kluctldeployments.yaml +++ b/config/crd/bases/gitops.kluctl.io_kluctldeployments.yaml @@ -447,273 +447,8 @@ spec: type: string lastDeployResult: description: LastDeployResult is the result of the last deploy command - properties: - appliedHookObjects: - type: integer - appliedObjects: - type: integer - changedObjects: - type: integer - clusterInfo: - properties: - clusterId: - type: string - required: - - clusterId - type: object - commandInfo: - properties: - abortOnError: - type: boolean - args: - type: object - x-kubernetes-preserve-unknown-fields: true - command: - type: string - contextOverride: - type: string - dryRun: - type: boolean - endTime: - format: date-time - type: string - excludeDeploymentDirs: - items: - type: string - type: array - excludeTags: - items: - type: string - type: array - forceApply: - type: boolean - forceReplaceOnError: - type: boolean - images: - items: - properties: - container: - type: string - deployTags: - items: - type: string - type: array - deployedImage: - type: string - deployment: - type: string - deploymentDir: - type: string - image: - type: string - namespace: - type: string - object: - properties: - group: - type: string - kind: - type: string - name: - type: string - namespace: - type: string - version: - type: string - required: - - kind - - name - type: object - resultImage: - type: string - required: - - image - - resultImage - type: object - type: array - includeDeploymentDirs: - items: - type: string - type: array - includeTags: - items: - type: string - type: array - initiator: - type: string - kluctlDeployment: - properties: - gitRef: - type: string - gitUrl: - type: string - name: - type: string - namespace: - type: string - required: - - gitRef - - gitUrl - - name - - namespace - type: object - noWait: - type: boolean - replaceOnError: - type: boolean - startTime: - format: date-time - type: string - target: - type: string - targetNameOverride: - type: string - required: - - endTime - - initiator - - startTime - type: object - deletedObjects: - type: integer - errors: - type: integer - gitInfo: - properties: - commit: - type: string - dirty: - type: boolean - ref: - type: string - subDir: - type: string - url: - type: string - required: - - commit - - dirty - - ref - - subDir - - url - type: object - id: - type: string - newObjects: - type: integer - orphanObjects: - type: integer - projectKey: - properties: - gitRepoKey: - type: string - subDir: - type: string - type: object - remoteObjects: - type: integer - renderedObjects: - type: integer - target: - properties: - args: - type: object - x-kubernetes-preserve-unknown-fields: true - context: - type: string - discriminator: - type: string - images: - items: - properties: - container: - type: string - deployTags: - items: - type: string - type: array - deployedImage: - type: string - deployment: - type: string - deploymentDir: - type: string - image: - type: string - namespace: - type: string - object: - properties: - group: - type: string - kind: - type: string - name: - type: string - namespace: - type: string - version: - type: string - required: - - kind - - name - type: object - resultImage: - type: string - required: - - image - - resultImage - type: object - type: array - name: - type: string - sealingConfig: - properties: - args: - type: object - x-kubernetes-preserve-unknown-fields: true - certFile: - type: string - secretSets: - items: - type: string - type: array - type: object - required: - - name - type: object - targetKey: - properties: - clusterId: - type: string - discriminator: - type: string - targetName: - type: string - required: - - clusterId - type: object - totalChanges: - type: integer - warnings: - type: integer - required: - - appliedHookObjects - - appliedObjects - - changedObjects - - commandInfo - - deletedObjects - - errors - - id - - newObjects - - orphanObjects - - projectKey - - remoteObjects - - renderedObjects - - target - - targetKey - - totalChanges - - warnings type: object + x-kubernetes-preserve-unknown-fields: true lastHandledReconcileAt: description: LastHandledReconcileAt holds the value of the most recent reconcile request value, so a change of the annotation value can @@ -725,417 +460,15 @@ spec: type: string lastPruneResult: description: LastDeployResult is the result of the last prune command - properties: - appliedHookObjects: - type: integer - appliedObjects: - type: integer - changedObjects: - type: integer - clusterInfo: - properties: - clusterId: - type: string - required: - - clusterId - type: object - commandInfo: - properties: - abortOnError: - type: boolean - args: - type: object - x-kubernetes-preserve-unknown-fields: true - command: - type: string - contextOverride: - type: string - dryRun: - type: boolean - endTime: - format: date-time - type: string - excludeDeploymentDirs: - items: - type: string - type: array - excludeTags: - items: - type: string - type: array - forceApply: - type: boolean - forceReplaceOnError: - type: boolean - images: - items: - properties: - container: - type: string - deployTags: - items: - type: string - type: array - deployedImage: - type: string - deployment: - type: string - deploymentDir: - type: string - image: - type: string - namespace: - type: string - object: - properties: - group: - type: string - kind: - type: string - name: - type: string - namespace: - type: string - version: - type: string - required: - - kind - - name - type: object - resultImage: - type: string - required: - - image - - resultImage - type: object - type: array - includeDeploymentDirs: - items: - type: string - type: array - includeTags: - items: - type: string - type: array - initiator: - type: string - kluctlDeployment: - properties: - gitRef: - type: string - gitUrl: - type: string - name: - type: string - namespace: - type: string - required: - - gitRef - - gitUrl - - name - - namespace - type: object - noWait: - type: boolean - replaceOnError: - type: boolean - startTime: - format: date-time - type: string - target: - type: string - targetNameOverride: - type: string - required: - - endTime - - initiator - - startTime - type: object - deletedObjects: - type: integer - errors: - type: integer - gitInfo: - properties: - commit: - type: string - dirty: - type: boolean - ref: - type: string - subDir: - type: string - url: - type: string - required: - - commit - - dirty - - ref - - subDir - - url - type: object - id: - type: string - newObjects: - type: integer - orphanObjects: - type: integer - projectKey: - properties: - gitRepoKey: - type: string - subDir: - type: string - type: object - remoteObjects: - type: integer - renderedObjects: - type: integer - target: - properties: - args: - type: object - x-kubernetes-preserve-unknown-fields: true - context: - type: string - discriminator: - type: string - images: - items: - properties: - container: - type: string - deployTags: - items: - type: string - type: array - deployedImage: - type: string - deployment: - type: string - deploymentDir: - type: string - image: - type: string - namespace: - type: string - object: - properties: - group: - type: string - kind: - type: string - name: - type: string - namespace: - type: string - version: - type: string - required: - - kind - - name - type: object - resultImage: - type: string - required: - - image - - resultImage - type: object - type: array - name: - type: string - sealingConfig: - properties: - args: - type: object - x-kubernetes-preserve-unknown-fields: true - certFile: - type: string - secretSets: - items: - type: string - type: array - type: object - required: - - name - type: object - targetKey: - properties: - clusterId: - type: string - discriminator: - type: string - targetName: - type: string - required: - - clusterId - type: object - totalChanges: - type: integer - warnings: - type: integer - required: - - appliedHookObjects - - appliedObjects - - changedObjects - - commandInfo - - deletedObjects - - errors - - id - - newObjects - - orphanObjects - - projectKey - - remoteObjects - - renderedObjects - - target - - targetKey - - totalChanges - - warnings type: object + x-kubernetes-preserve-unknown-fields: true lastValidateError: type: string lastValidateResult: description: LastValidateResult is the result of the last validate command - properties: - drift: - items: - properties: - changes: - items: - properties: - jsonPath: - type: string - newValue: - x-kubernetes-preserve-unknown-fields: true - oldValue: - x-kubernetes-preserve-unknown-fields: true - type: - type: string - unifiedDiff: - type: string - required: - - jsonPath - - type - type: object - type: array - ref: - properties: - group: - type: string - kind: - type: string - name: - type: string - namespace: - type: string - version: - type: string - required: - - kind - - name - type: object - required: - - ref - type: object - type: array - endTime: - format: date-time - type: string - errors: - items: - properties: - message: - type: string - ref: - properties: - group: - type: string - kind: - type: string - name: - type: string - namespace: - type: string - version: - type: string - required: - - kind - - name - type: object - required: - - message - - ref - type: object - type: array - id: - type: string - ready: - type: boolean - results: - items: - properties: - annotation: - type: string - message: - type: string - ref: - properties: - group: - type: string - kind: - type: string - name: - type: string - namespace: - type: string - version: - type: string - required: - - kind - - name - type: object - required: - - annotation - - message - - ref - type: object - type: array - startTime: - format: date-time - type: string - warnings: - items: - properties: - message: - type: string - ref: - properties: - group: - type: string - kind: - type: string - name: - type: string - namespace: - type: string - version: - type: string - required: - - kind - - name - type: object - required: - - message - - ref - type: object - type: array - required: - - endTime - - id - - ready - - startTime type: object + x-kubernetes-preserve-unknown-fields: true observedCommit: description: ObservedCommit is the last commit observed type: string diff --git a/docs/reference/gitops/api/kluctl-controller.md b/docs/reference/gitops/api/kluctl-controller.md index f1c1d35ee..7d64b8644 100644 --- a/docs/reference/gitops/api/kluctl-controller.md +++ b/docs/reference/gitops/api/kluctl-controller.md @@ -1194,7 +1194,7 @@ string lastDeployResult
    -github.com/kluctl/kluctl/v2/pkg/types/result.CommandResultSummary +k8s.io/apimachinery/pkg/runtime.RawExtension @@ -1206,7 +1206,7 @@ github.com/kluctl/kluctl/v2/pkg/types/result.CommandResultSummary lastPruneResult
    -github.com/kluctl/kluctl/v2/pkg/types/result.CommandResultSummary +k8s.io/apimachinery/pkg/runtime.RawExtension @@ -1218,7 +1218,7 @@ github.com/kluctl/kluctl/v2/pkg/types/result.CommandResultSummary lastValidateResult
    -github.com/kluctl/kluctl/v2/pkg/types/result.ValidateResult +k8s.io/apimachinery/pkg/runtime.RawExtension diff --git a/e2e/gitops_errors_test.go b/e2e/gitops_errors_test.go index 56249cf9b..d3ddf8a71 100644 --- a/e2e/gitops_errors_test.go +++ b/e2e/gitops_errors_test.go @@ -44,8 +44,10 @@ func (suite *GitopsTestSuite) assertErrors(key client.ObjectKey, rstatus metav1. rs, err := results.NewResultStoreSecrets(context.TODO(), suite.k.Client, nil, "", 0) g.Expect(err).To(Succeed()) + lastDeployResult, err := kd.Status.GetLastDeployResult() + g.Expect(err).To(Succeed()) cr, err := rs.GetCommandResult(results.GetCommandResultOptions{ - Id: kd.Status.LastDeployResult.Id, + Id: lastDeployResult.Id, }) g.Expect(err).To(Succeed()) diff --git a/install/controller/controller/crd.yaml b/install/controller/controller/crd.yaml index 11208546d..0515fbe6e 100644 --- a/install/controller/controller/crd.yaml +++ b/install/controller/controller/crd.yaml @@ -446,273 +446,8 @@ spec: type: string lastDeployResult: description: LastDeployResult is the result of the last deploy command - properties: - appliedHookObjects: - type: integer - appliedObjects: - type: integer - changedObjects: - type: integer - clusterInfo: - properties: - clusterId: - type: string - required: - - clusterId - type: object - commandInfo: - properties: - abortOnError: - type: boolean - args: - type: object - x-kubernetes-preserve-unknown-fields: true - command: - type: string - contextOverride: - type: string - dryRun: - type: boolean - endTime: - format: date-time - type: string - excludeDeploymentDirs: - items: - type: string - type: array - excludeTags: - items: - type: string - type: array - forceApply: - type: boolean - forceReplaceOnError: - type: boolean - images: - items: - properties: - container: - type: string - deployTags: - items: - type: string - type: array - deployedImage: - type: string - deployment: - type: string - deploymentDir: - type: string - image: - type: string - namespace: - type: string - object: - properties: - group: - type: string - kind: - type: string - name: - type: string - namespace: - type: string - version: - type: string - required: - - kind - - name - type: object - resultImage: - type: string - required: - - image - - resultImage - type: object - type: array - includeDeploymentDirs: - items: - type: string - type: array - includeTags: - items: - type: string - type: array - initiator: - type: string - kluctlDeployment: - properties: - gitRef: - type: string - gitUrl: - type: string - name: - type: string - namespace: - type: string - required: - - gitRef - - gitUrl - - name - - namespace - type: object - noWait: - type: boolean - replaceOnError: - type: boolean - startTime: - format: date-time - type: string - target: - type: string - targetNameOverride: - type: string - required: - - endTime - - initiator - - startTime - type: object - deletedObjects: - type: integer - errors: - type: integer - gitInfo: - properties: - commit: - type: string - dirty: - type: boolean - ref: - type: string - subDir: - type: string - url: - type: string - required: - - commit - - dirty - - ref - - subDir - - url - type: object - id: - type: string - newObjects: - type: integer - orphanObjects: - type: integer - projectKey: - properties: - gitRepoKey: - type: string - subDir: - type: string - type: object - remoteObjects: - type: integer - renderedObjects: - type: integer - target: - properties: - args: - type: object - x-kubernetes-preserve-unknown-fields: true - context: - type: string - discriminator: - type: string - images: - items: - properties: - container: - type: string - deployTags: - items: - type: string - type: array - deployedImage: - type: string - deployment: - type: string - deploymentDir: - type: string - image: - type: string - namespace: - type: string - object: - properties: - group: - type: string - kind: - type: string - name: - type: string - namespace: - type: string - version: - type: string - required: - - kind - - name - type: object - resultImage: - type: string - required: - - image - - resultImage - type: object - type: array - name: - type: string - sealingConfig: - properties: - args: - type: object - x-kubernetes-preserve-unknown-fields: true - certFile: - type: string - secretSets: - items: - type: string - type: array - type: object - required: - - name - type: object - targetKey: - properties: - clusterId: - type: string - discriminator: - type: string - targetName: - type: string - required: - - clusterId - type: object - totalChanges: - type: integer - warnings: - type: integer - required: - - appliedHookObjects - - appliedObjects - - changedObjects - - commandInfo - - deletedObjects - - errors - - id - - newObjects - - orphanObjects - - projectKey - - remoteObjects - - renderedObjects - - target - - targetKey - - totalChanges - - warnings type: object + x-kubernetes-preserve-unknown-fields: true lastHandledReconcileAt: description: LastHandledReconcileAt holds the value of the most recent reconcile request value, so a change of the annotation value can @@ -724,417 +459,15 @@ spec: type: string lastPruneResult: description: LastDeployResult is the result of the last prune command - properties: - appliedHookObjects: - type: integer - appliedObjects: - type: integer - changedObjects: - type: integer - clusterInfo: - properties: - clusterId: - type: string - required: - - clusterId - type: object - commandInfo: - properties: - abortOnError: - type: boolean - args: - type: object - x-kubernetes-preserve-unknown-fields: true - command: - type: string - contextOverride: - type: string - dryRun: - type: boolean - endTime: - format: date-time - type: string - excludeDeploymentDirs: - items: - type: string - type: array - excludeTags: - items: - type: string - type: array - forceApply: - type: boolean - forceReplaceOnError: - type: boolean - images: - items: - properties: - container: - type: string - deployTags: - items: - type: string - type: array - deployedImage: - type: string - deployment: - type: string - deploymentDir: - type: string - image: - type: string - namespace: - type: string - object: - properties: - group: - type: string - kind: - type: string - name: - type: string - namespace: - type: string - version: - type: string - required: - - kind - - name - type: object - resultImage: - type: string - required: - - image - - resultImage - type: object - type: array - includeDeploymentDirs: - items: - type: string - type: array - includeTags: - items: - type: string - type: array - initiator: - type: string - kluctlDeployment: - properties: - gitRef: - type: string - gitUrl: - type: string - name: - type: string - namespace: - type: string - required: - - gitRef - - gitUrl - - name - - namespace - type: object - noWait: - type: boolean - replaceOnError: - type: boolean - startTime: - format: date-time - type: string - target: - type: string - targetNameOverride: - type: string - required: - - endTime - - initiator - - startTime - type: object - deletedObjects: - type: integer - errors: - type: integer - gitInfo: - properties: - commit: - type: string - dirty: - type: boolean - ref: - type: string - subDir: - type: string - url: - type: string - required: - - commit - - dirty - - ref - - subDir - - url - type: object - id: - type: string - newObjects: - type: integer - orphanObjects: - type: integer - projectKey: - properties: - gitRepoKey: - type: string - subDir: - type: string - type: object - remoteObjects: - type: integer - renderedObjects: - type: integer - target: - properties: - args: - type: object - x-kubernetes-preserve-unknown-fields: true - context: - type: string - discriminator: - type: string - images: - items: - properties: - container: - type: string - deployTags: - items: - type: string - type: array - deployedImage: - type: string - deployment: - type: string - deploymentDir: - type: string - image: - type: string - namespace: - type: string - object: - properties: - group: - type: string - kind: - type: string - name: - type: string - namespace: - type: string - version: - type: string - required: - - kind - - name - type: object - resultImage: - type: string - required: - - image - - resultImage - type: object - type: array - name: - type: string - sealingConfig: - properties: - args: - type: object - x-kubernetes-preserve-unknown-fields: true - certFile: - type: string - secretSets: - items: - type: string - type: array - type: object - required: - - name - type: object - targetKey: - properties: - clusterId: - type: string - discriminator: - type: string - targetName: - type: string - required: - - clusterId - type: object - totalChanges: - type: integer - warnings: - type: integer - required: - - appliedHookObjects - - appliedObjects - - changedObjects - - commandInfo - - deletedObjects - - errors - - id - - newObjects - - orphanObjects - - projectKey - - remoteObjects - - renderedObjects - - target - - targetKey - - totalChanges - - warnings type: object + x-kubernetes-preserve-unknown-fields: true lastValidateError: type: string lastValidateResult: description: LastValidateResult is the result of the last validate command - properties: - drift: - items: - properties: - changes: - items: - properties: - jsonPath: - type: string - newValue: - x-kubernetes-preserve-unknown-fields: true - oldValue: - x-kubernetes-preserve-unknown-fields: true - type: - type: string - unifiedDiff: - type: string - required: - - jsonPath - - type - type: object - type: array - ref: - properties: - group: - type: string - kind: - type: string - name: - type: string - namespace: - type: string - version: - type: string - required: - - kind - - name - type: object - required: - - ref - type: object - type: array - endTime: - format: date-time - type: string - errors: - items: - properties: - message: - type: string - ref: - properties: - group: - type: string - kind: - type: string - name: - type: string - namespace: - type: string - version: - type: string - required: - - kind - - name - type: object - required: - - message - - ref - type: object - type: array - id: - type: string - ready: - type: boolean - results: - items: - properties: - annotation: - type: string - message: - type: string - ref: - properties: - group: - type: string - kind: - type: string - name: - type: string - namespace: - type: string - version: - type: string - required: - - kind - - name - type: object - required: - - annotation - - message - - ref - type: object - type: array - startTime: - format: date-time - type: string - warnings: - items: - properties: - message: - type: string - ref: - properties: - group: - type: string - kind: - type: string - name: - type: string - namespace: - type: string - version: - type: string - required: - - kind - - name - type: object - required: - - message - - ref - type: object - type: array - required: - - endTime - - id - - ready - - startTime type: object + x-kubernetes-preserve-unknown-fields: true observedCommit: description: ObservedCommit is the last commit observed type: string diff --git a/pkg/controllers/kluctldeployment_controller.go b/pkg/controllers/kluctldeployment_controller.go index 2f584b49d..3a7a4428f 100644 --- a/pkg/controllers/kluctldeployment_controller.go +++ b/pkg/controllers/kluctldeployment_controller.go @@ -14,6 +14,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/types/result" "github.com/kluctl/kluctl/v2/pkg/utils/flux_utils/meta" "github.com/kluctl/kluctl/v2/pkg/utils/flux_utils/metrics" + "github.com/kluctl/kluctl/v2/pkg/yaml" log "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/api/errors" meta2 "k8s.io/apimachinery/pkg/api/meta" @@ -311,29 +312,26 @@ func (r *KluctlDeploymentReconciler) doReconcile( } else { err = fmt.Errorf("deployMode '%s' not supported", obj.Spec.DeployMode) } - obj.Status.LastDeployResult = deployResult.BuildSummary() - obj.Status.LastDeployError = "" + err = obj.Status.SetLastDeployResult(deployResult.BuildSummary(), err) if err != nil { - obj.Status.LastDeployError = err.Error() + log.Error(err, "Failed to write deploy result") } } if needPrune { // run garbage collection for stale objects that do not have pruning disabled pruneResult, err := pt.kluctlPrune(ctx, targetContext) - obj.Status.LastPruneResult = pruneResult.BuildSummary() - obj.Status.LastPruneError = "" + err = obj.Status.SetLastPruneResult(pruneResult.BuildSummary(), err) if err != nil { - obj.Status.LastPruneError = err.Error() + log.Error(err, "Failed to write prune result") } } if needValidate { validateResult, err := pt.kluctlValidate(ctx, targetContext, deployResult) - obj.Status.LastValidateResult = validateResult - obj.Status.LastValidateError = "" + err = obj.Status.SetLastValidateResult(validateResult, err) if err != nil { - obj.Status.LastValidateError = err.Error() + log.Error(err, "Failed to write validate result") } } @@ -344,7 +342,7 @@ func (r *KluctlDeploymentReconciler) doReconcile( ctrlResult.Requeue = true } - finalStatus, reason := r.buildFinalStatus(obj) + finalStatus, reason := r.buildFinalStatus(ctx, obj) if reason != kluctlv1.ReconciliationSucceededReason { setReadiness(obj, metav1.ConditionFalse, reason, finalStatus) internal_metrics.NewKluctlLastObjectStatus(obj.Namespace, obj.Name).Set(0.0) @@ -355,7 +353,9 @@ func (r *KluctlDeploymentReconciler) doReconcile( return &ctrlResult, nil } -func (r *KluctlDeploymentReconciler) buildFinalStatus(obj *kluctlv1.KluctlDeployment) (finalStatus string, reason string) { +func (r *KluctlDeploymentReconciler) buildFinalStatus(ctx context.Context, obj *kluctlv1.KluctlDeployment) (finalStatus string, reason string) { + log := ctrl.LoggerFrom(ctx) + if obj.Status.LastDeployError != "" { finalStatus = obj.Status.LastDeployError reason = kluctlv1.DeployFailedReason @@ -370,9 +370,31 @@ func (r *KluctlDeploymentReconciler) buildFinalStatus(obj *kluctlv1.KluctlDeploy return } - deployOk := obj.Status.LastDeployResult != nil && obj.Status.LastDeployResult.Errors == 0 - pruneOk := obj.Status.LastPruneResult != nil && obj.Status.LastPruneResult.Errors == 0 - validateOk := obj.Status.LastValidateResult != nil && len(obj.Status.LastValidateResult.Errors) == 0 && obj.Status.LastValidateResult.Ready + var lastDeployResult *result.CommandResultSummary + var lastPruneResult *result.CommandResultSummary + var lastValidateResult *result.ValidateResult + if obj.Status.LastDeployResult != nil { + err := yaml.ReadYamlBytes(obj.Status.LastDeployResult.Raw, &lastDeployResult) + if err != nil { + log.Info(fmt.Sprintf("Failed to parse last deploy result: %s", err.Error())) + } + } + if obj.Status.LastPruneResult != nil { + err := yaml.ReadYamlBytes(obj.Status.LastPruneResult.Raw, &lastPruneResult) + if err != nil { + log.Info(fmt.Sprintf("Failed to parse last prune result: %s", err.Error())) + } + } + if obj.Status.LastValidateResult != nil { + err := yaml.ReadYamlBytes(obj.Status.LastValidateResult.Raw, &lastValidateResult) + if err != nil { + log.Info(fmt.Sprintf("Failed to parse last validate result: %s", err.Error())) + } + } + + deployOk := lastDeployResult != nil && lastDeployResult.Errors == 0 + pruneOk := lastPruneResult != nil && lastPruneResult.Errors == 0 + validateOk := lastValidateResult != nil && len(lastValidateResult.Errors) == 0 && lastValidateResult.Ready if !obj.Spec.Prune { pruneOk = true @@ -464,7 +486,13 @@ func (r *KluctlDeploymentReconciler) nextDeployTime(obj *kluctlv1.KluctlDeployme return nil } - t := obj.Status.LastDeployResult.Command.EndTime.Add(obj.Spec.DeployInterval.Duration.Duration) + var lastDeployResult result.CommandResultSummary + err := yaml.ReadYamlBytes(obj.Status.LastDeployResult.Raw, &lastDeployResult) + if err != nil { + return nil + } + + t := lastDeployResult.Command.EndTime.Add(obj.Spec.DeployInterval.Duration.Duration) return &t } @@ -489,7 +517,13 @@ func (r *KluctlDeploymentReconciler) nextValidateTime(obj *kluctlv1.KluctlDeploy d = obj.Spec.ValidateInterval.Duration.Duration } - t := obj.Status.LastValidateResult.EndTime.Add(d) + var lastValidateResult result.ValidateResult + err := yaml.ReadYamlBytes(obj.Status.LastValidateResult.Raw, &lastValidateResult) + if err != nil { + return nil + } + + t := lastValidateResult.EndTime.Add(d) return &t } From 505991b8efb6f8fa08b57bbf97f99fcb106aeef5 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 2 Jun 2023 17:30:55 +0200 Subject: [PATCH 1755/2916] fix: Actually write errors and not only error count into status --- e2e/gitops_errors_test.go | 4 ++-- e2e/gitops_test.go | 4 +++- pkg/controllers/kluctl_project.go | 4 ++-- pkg/controllers/kluctldeployment_controller.go | 4 ++-- pkg/types/result/summary.go | 9 +++++---- pkg/types/result/zz_generated.deepcopy.go | 10 ++++++++++ 6 files changed, 24 insertions(+), 11 deletions(-) diff --git a/e2e/gitops_errors_test.go b/e2e/gitops_errors_test.go index d3ddf8a71..a243b0edc 100644 --- a/e2e/gitops_errors_test.go +++ b/e2e/gitops_errors_test.go @@ -54,8 +54,8 @@ func (suite *GitopsTestSuite) assertErrors(key client.ObjectKey, rstatus metav1. g.Expect(cr.Errors).To(ConsistOf(expectedErrors)) g.Expect(cr.Warnings).To(ConsistOf(expectedWarnings)) - g.Expect(kd.Status.LastDeployResult.Errors).To(Equal(len(expectedErrors))) - g.Expect(kd.Status.LastDeployResult.Warnings).To(Equal(len(expectedWarnings))) + g.Expect(lastDeployResult.Errors).To(ConsistOf(expectedErrors)) + g.Expect(lastDeployResult.Warnings).To(ConsistOf(expectedWarnings)) } func (suite *GitopsTestSuite) TestGitOpsErrors() { diff --git a/e2e/gitops_test.go b/e2e/gitops_test.go index edb46e916..72d4dc89f 100644 --- a/e2e/gitops_test.go +++ b/e2e/gitops_test.go @@ -567,7 +567,9 @@ data: if obj.Status.LastDeployResult == nil { return false } - return obj.Status.LastDeployResult.GitInfo.Commit == getHeadRevision(suite.T(), p) + ldr, err := obj.Status.GetLastDeployResult() + g.Expect(err).To(Succeed()) + return ldr.GitInfo.Commit == getHeadRevision(suite.T(), p) }, timeout, time.Second).Should(BeTrue()) suite.Run("cm1 and cm2 were not deleted", func() { diff --git a/pkg/controllers/kluctl_project.go b/pkg/controllers/kluctl_project.go index 715fbf36b..10f1e589c 100644 --- a/pkg/controllers/kluctl_project.go +++ b/pkg/controllers/kluctl_project.go @@ -820,6 +820,6 @@ func (pt *preparedTarget) exportCommandResultMetricsToProm(summary *result.Comma internal_metrics.NewKluctlNumberOfDeletedObjects(pt.pp.obj.Namespace, pt.pp.obj.Name).Set(float64(summary.DeletedObjects)) internal_metrics.NewKluctlNumberOfChangedObjects(pt.pp.obj.Namespace, pt.pp.obj.Name).Set(float64(summary.ChangedObjects)) internal_metrics.NewKluctlNumberOfOrphanObjects(pt.pp.obj.Namespace, pt.pp.obj.Name).Set(float64(summary.OrphanObjects)) - internal_metrics.NewKluctlNumberOfWarnings(pt.pp.obj.Namespace, pt.pp.obj.Name, summary.Command.Command).Set(float64(summary.Warnings)) - internal_metrics.NewKluctlNumberOfErrors(pt.pp.obj.Namespace, pt.pp.obj.Name, summary.Command.Command).Set(float64(summary.Errors)) + internal_metrics.NewKluctlNumberOfWarnings(pt.pp.obj.Namespace, pt.pp.obj.Name, summary.Command.Command).Set(float64(len(summary.Warnings))) + internal_metrics.NewKluctlNumberOfErrors(pt.pp.obj.Namespace, pt.pp.obj.Name, summary.Command.Command).Set(float64(len(summary.Errors))) } diff --git a/pkg/controllers/kluctldeployment_controller.go b/pkg/controllers/kluctldeployment_controller.go index 3a7a4428f..9759d04d2 100644 --- a/pkg/controllers/kluctldeployment_controller.go +++ b/pkg/controllers/kluctldeployment_controller.go @@ -392,8 +392,8 @@ func (r *KluctlDeploymentReconciler) buildFinalStatus(ctx context.Context, obj * } } - deployOk := lastDeployResult != nil && lastDeployResult.Errors == 0 - pruneOk := lastPruneResult != nil && lastPruneResult.Errors == 0 + deployOk := lastDeployResult != nil && len(lastDeployResult.Errors) == 0 + pruneOk := lastPruneResult != nil && len(lastPruneResult.Errors) == 0 validateOk := lastValidateResult != nil && len(lastValidateResult.Errors) == 0 && lastValidateResult.Ready if !obj.Spec.Prune { diff --git a/pkg/types/result/summary.go b/pkg/types/result/summary.go index 85327294a..dc4c38b74 100644 --- a/pkg/types/result/summary.go +++ b/pkg/types/result/summary.go @@ -23,8 +23,9 @@ type CommandResultSummary struct { ChangedObjects int `json:"changedObjects"` OrphanObjects int `json:"orphanObjects"` DeletedObjects int `json:"deletedObjects"` - Errors int `json:"errors"` - Warnings int `json:"warnings"` + + Errors []DeploymentError `json:"errors"` + Warnings []DeploymentError `json:"warnings"` TotalChanges int `json:"totalChanges"` } @@ -73,8 +74,8 @@ func (cr *CommandResult) BuildSummary() *CommandResultSummary { ChangedObjects: count(func(o ResultObject) bool { return len(o.Changes) != 0 }), OrphanObjects: count(func(o ResultObject) bool { return o.Orphan }), DeletedObjects: count(func(o ResultObject) bool { return o.Deleted }), - Errors: len(cr.Errors), - Warnings: len(cr.Warnings), + Errors: cr.Errors, + Warnings: cr.Warnings, } for _, o := range cr.Objects { ret.TotalChanges += len(o.Changes) diff --git a/pkg/types/result/zz_generated.deepcopy.go b/pkg/types/result/zz_generated.deepcopy.go index 40ade0029..a461c0278 100644 --- a/pkg/types/result/zz_generated.deepcopy.go +++ b/pkg/types/result/zz_generated.deepcopy.go @@ -224,6 +224,16 @@ func (in *CommandResultSummary) DeepCopyInto(out *CommandResultSummary) { in.Command.DeepCopyInto(&out.Command) in.GitInfo.DeepCopyInto(&out.GitInfo) out.ClusterInfo = in.ClusterInfo + if in.Errors != nil { + in, out := &in.Errors, &out.Errors + *out = make([]DeploymentError, len(*in)) + copy(*out, *in) + } + if in.Warnings != nil { + in, out := &in.Warnings, &out.Warnings + *out = make([]DeploymentError, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommandResultSummary. From 4ba9b138804da6b877e5e8dc0623a4e644c3bc03 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sat, 3 Jun 2023 00:08:33 +0200 Subject: [PATCH 1756/2916] ci: Add .sv4git.yml to filter tags (#543) --- .sv4git.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .sv4git.yml diff --git a/.sv4git.yml b/.sv4git.yml new file mode 100644 index 000000000..92d3bea10 --- /dev/null +++ b/.sv4git.yml @@ -0,0 +1,2 @@ +tag: + filter: 'v[0-9]*' From 7a2096d2e8db253e3ea363bfe7df325a9e9a1599 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sat, 3 Jun 2023 00:09:28 +0200 Subject: [PATCH 1757/2916] build: Preparing release v2.20.4 --- docs/installation.md | 2 +- install/controller/.kluctl.yaml | 2 +- install/controller/controller/kustomization.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/installation.md b/docs/installation.md index ffb8a3055..d1f2bbc17 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -113,5 +113,5 @@ deployments: - git: url: https://github.com/kluctl/kluctl.git subDir: install/controller - ref: v2.20.3 + ref: v2.20.4 ``` diff --git a/install/controller/.kluctl.yaml b/install/controller/.kluctl.yaml index d45892605..61be49c62 100644 --- a/install/controller/.kluctl.yaml +++ b/install/controller/.kluctl.yaml @@ -2,7 +2,7 @@ discriminator: kluctl.io-controller args: - name: controller_version - default: v2.20.3 + default: v2.20.4 - name: controller_args default: [] - name: controller_envs diff --git a/install/controller/controller/kustomization.yaml b/install/controller/controller/kustomization.yaml index 06923f9bd..be62c808a 100644 --- a/install/controller/controller/kustomization.yaml +++ b/install/controller/controller/kustomization.yaml @@ -1,4 +1,4 @@ -{% set controller_version = get_var("args.controller_version", "v2.20.3") %} +{% set controller_version = get_var("args.controller_version", "v2.20.4") %} resources: - crd.yaml From 556a3e951b03534fed8fc5f384a6718a9d280bc2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Jun 2023 08:36:22 +0200 Subject: [PATCH 1758/2916] chore(deps): Bump github.com/ohler55/ojg from 1.18.6 to 1.18.7 (#544) Bumps [github.com/ohler55/ojg](https://github.com/ohler55/ojg) from 1.18.6 to 1.18.7. - [Changelog](https://github.com/ohler55/ojg/blob/develop/CHANGELOG.md) - [Commits](https://github.com/ohler55/ojg/compare/v1.18.6...v1.18.7) --- updated-dependencies: - dependency-name: github.com/ohler55/ojg dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c7d16d423..ecbb99ab8 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/mattn/go-isatty v0.0.19 github.com/mattn/go-runewidth v0.0.14 github.com/mitchellh/reflectwalk v1.0.2 - github.com/ohler55/ojg v1.18.6 + github.com/ohler55/ojg v1.18.7 github.com/pkg/errors v0.9.1 github.com/r3labs/diff/v2 v2.15.1 github.com/rogpeppe/go-internal v1.10.0 diff --git a/go.sum b/go.sum index 77124abb6..45dbf8ec4 100644 --- a/go.sum +++ b/go.sum @@ -654,8 +654,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW github.com/nelsam/hel/v2 v2.3.2/go.mod h1:1ZTGfU2PFTOd5mx22i5O0Lc2GY933lQ2wb/ggy+rL3w= github.com/nelsam/hel/v2 v2.3.3/go.mod h1:1ZTGfU2PFTOd5mx22i5O0Lc2GY933lQ2wb/ggy+rL3w= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/ohler55/ojg v1.18.6 h1:ZY5I/8I+zW8s+/QpX9E/P9QJwECi4lNx67VgdOzTTno= -github.com/ohler55/ojg v1.18.6/go.mod h1:uHcD1ErbErC27Zhb5Df2jUjbseLLcmOCo6oxSr3jZxo= +github.com/ohler55/ojg v1.18.7 h1:sC7zy0usEiWa6bvx3NU1yZH4kCA2F3Qzs6iiDX4+xdk= +github.com/ohler55/ojg v1.18.7/go.mod h1:uHcD1ErbErC27Zhb5Df2jUjbseLLcmOCo6oxSr3jZxo= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= From 8b09e0fec9718962dc820737ab1bd9829cd3b968 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 5 Jun 2023 18:31:45 +0200 Subject: [PATCH 1759/2916] fix: Properly convert viber bool/int to string args (#548) --- cmd/kluctl/commands/cobra_utils.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmd/kluctl/commands/cobra_utils.go b/cmd/kluctl/commands/cobra_utils.go index de630a332..8ecf0fbf2 100644 --- a/cmd/kluctl/commands/cobra_utils.go +++ b/cmd/kluctl/commands/cobra_utils.go @@ -251,6 +251,10 @@ func copyViperValuesToCobraFlags(flags *pflag.FlagSet) error { if v != nil { if x, ok := v.(string); ok { a = []string{x} + } else if x, ok := v.(bool); ok { + a = []string{strconv.FormatBool(x)} + } else if x, ok := v.(int); ok { + a = []string{strconv.FormatInt(int64(x), 10)} } else if x, ok := v.([]any); ok { for _, y := range x { s, ok := y.(string) From 189264cbaebdbd0ee7d421e03643255ba441686a Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 5 Jun 2023 23:10:25 +0200 Subject: [PATCH 1760/2916] tests: Add origin remote to test git repos --- pkg/git/test_git_server.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pkg/git/test_git_server.go b/pkg/git/test_git_server.go index 26b656b5c..eee519037 100644 --- a/pkg/git/test_git_server.go +++ b/pkg/git/test_git_server.go @@ -3,6 +3,7 @@ package git import ( "context" "fmt" + config2 "github.com/go-git/go-git/v5/config" "log" "net" "net/http" @@ -84,6 +85,15 @@ func (p *TestGitServer) GitInit(repo string) { if err != nil { p.t.Fatal(err) } + + _, err = r.CreateRemote(&config2.RemoteConfig{ + Name: "origin", + URLs: []string{p.GitRepoUrl(repo)}, + }) + if err != nil { + p.t.Fatal(err) + } + config, err := r.Config() if err != nil { p.t.Fatal(err) From d560e337c77a0380ca46836f7df63990d404aee0 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 5 Jun 2023 23:11:06 +0200 Subject: [PATCH 1761/2916] tests: Filter for project key in TestWriteResult --- e2e/results_test.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/e2e/results_test.go b/e2e/results_test.go index b2ceea17d..000265647 100644 --- a/e2e/results_test.go +++ b/e2e/results_test.go @@ -4,6 +4,7 @@ import ( "context" test_utils "github.com/kluctl/kluctl/v2/e2e/test-utils" "github.com/kluctl/kluctl/v2/pkg/results" + "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/types/result" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "github.com/stretchr/testify/assert" @@ -44,7 +45,13 @@ func TestWriteResult(t *testing.T) { rs, err := results.NewResultStoreSecrets(context.Background(), k.Client, nil, "kluctl-results", 0) assert.NoError(t, err) - summaries, err := rs.ListCommandResultSummaries(results.ListCommandResultSummariesOptions{}) + opts := results.ListCommandResultSummariesOptions{ + ProjectFilter: &result.ProjectKey{ + GitRepoKey: types.ParseGitUrlMust(p.GitUrl()).RepoKey(), + }, + } + + summaries, err := rs.ListCommandResultSummaries(opts) assert.NoError(t, err) assert.Len(t, summaries, 1) assertSummary(t, result.CommandResultSummary{ @@ -63,7 +70,7 @@ func TestWriteResult(t *testing.T) { p.KluctlMust("deploy", "--yes", "-t", "test", "--write-command-result") assertConfigMapExists(t, k, p.TestSlug(), "cm2") - summaries, err = rs.ListCommandResultSummaries(results.ListCommandResultSummariesOptions{}) + summaries, err = rs.ListCommandResultSummaries(opts) assert.NoError(t, err) assert.Len(t, summaries, 2) assertSummary(t, result.CommandResultSummary{ @@ -80,7 +87,7 @@ func TestWriteResult(t *testing.T) { p.KluctlMust("deploy", "--yes", "-t", "test", "--write-command-result") assertConfigMapExists(t, k, p.TestSlug(), "cm2") - summaries, err = rs.ListCommandResultSummaries(results.ListCommandResultSummariesOptions{}) + summaries, err = rs.ListCommandResultSummaries(opts) assert.NoError(t, err) assert.Len(t, summaries, 3) assertSummary(t, result.CommandResultSummary{ @@ -91,7 +98,7 @@ func TestWriteResult(t *testing.T) { p.KluctlMust("prune", "--yes", "-t", "test", "--write-command-result") assertConfigMapNotExists(t, k, p.TestSlug(), "cm2") - summaries, err = rs.ListCommandResultSummaries(results.ListCommandResultSummariesOptions{}) + summaries, err = rs.ListCommandResultSummaries(opts) assert.NoError(t, err) assert.Len(t, summaries, 4) assertSummary(t, result.CommandResultSummary{ From fde5c6e065412d196bbe6c6d62e6b2d996da3d5c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 5 Jun 2023 23:12:09 +0200 Subject: [PATCH 1762/2916] feat: Implement --prune flag for deploy command --- cmd/kluctl/commands/cmd_deploy.go | 3 ++ docs/reference/commands/deploy.md | 2 + e2e/prune_test.go | 73 ++++++++++++++++++++++++++++ pkg/deployment/commands/delete.go | 5 +- pkg/deployment/commands/deploy.go | 25 +++++++++- pkg/deployment/commands/prune.go | 5 +- pkg/deployment/utils/delete_utils.go | 4 +- 7 files changed, 106 insertions(+), 11 deletions(-) create mode 100644 e2e/prune_test.go diff --git a/cmd/kluctl/commands/cmd_deploy.go b/cmd/kluctl/commands/cmd_deploy.go index e86f1a53d..fbd88f62c 100644 --- a/cmd/kluctl/commands/cmd_deploy.go +++ b/cmd/kluctl/commands/cmd_deploy.go @@ -27,6 +27,7 @@ type deployCmd struct { args.CommandResultFlags NoWait bool `group:"misc" help:"Don't wait for objects readiness'"` + Prune bool `group:"misc" help:"Prune orphaned objects directly after deploying. See the help for the 'prune' sub-command for details.'"` internal bool } @@ -67,6 +68,8 @@ func (cmd *deployCmd) runCmdDeploy(cmdCtx *commandCtx) error { cmd2.AbortOnError = cmd.AbortOnError cmd2.ReadinessTimeout = cmd.ReadinessTimeout cmd2.NoWait = cmd.NoWait + cmd2.Prune = cmd.Prune + cmd2.WaitPrune = !cmd.NoWait cb := func(diffResult *result.CommandResult) error { return cmd.diffResultCb(cmdCtx, diffResult) diff --git a/docs/reference/commands/deploy.md b/docs/reference/commands/deploy.md index 344fb448b..ff964cf64 100644 --- a/docs/reference/commands/deploy.md +++ b/docs/reference/commands/deploy.md @@ -61,6 +61,8 @@ Misc arguments: 'format=path'. Format can either be 'text' or 'yaml'. Can be specified multiple times. The actual format for yaml is currently not documented and subject to change. + --prune Prune orphaned objects directly after deploying. See the help + for the 'prune' sub-command for details.' --readiness-timeout duration Maximum time to wait for object readiness. The timeout is meant per-object. Timeouts are in the duration format (1s, 1m, 1h, ...). If not specified, a default timeout of 5m is used. diff --git a/e2e/prune_test.go b/e2e/prune_test.go new file mode 100644 index 000000000..d70c55fb6 --- /dev/null +++ b/e2e/prune_test.go @@ -0,0 +1,73 @@ +package e2e + +import ( + test_utils "github.com/kluctl/kluctl/v2/e2e/test-utils" + "testing" +) + +func TestPrune(t *testing.T) { + t.Parallel() + + k := defaultCluster1 + + p := test_utils.NewTestProject(t) + + createNamespace(t, k, p.TestSlug()) + + p.UpdateTarget("test", nil) + + addConfigMapDeployment(p, "cm1", map[string]string{}, resourceOpts{ + name: "cm1", + namespace: p.TestSlug(), + }) + addConfigMapDeployment(p, "cm2", map[string]string{}, resourceOpts{ + name: "cm2", + namespace: p.TestSlug(), + }) + + p.KluctlMust("deploy", "--yes", "-t", "test") + assertConfigMapExists(t, k, p.TestSlug(), "cm1") + assertConfigMapExists(t, k, p.TestSlug(), "cm2") + + p.DeleteKustomizeDeployment("cm2") + + p.KluctlMust("prune", "--yes", "-t", "test") + assertConfigMapExists(t, k, p.TestSlug(), "cm1") + assertConfigMapNotExists(t, k, p.TestSlug(), "cm2") +} + +func TestDeployWithPrune(t *testing.T) { + t.Parallel() + + k := defaultCluster1 + + p := test_utils.NewTestProject(t) + + createNamespace(t, k, p.TestSlug()) + + p.UpdateTarget("test", nil) + + addConfigMapDeployment(p, "cm1", map[string]string{}, resourceOpts{ + name: "cm1", + namespace: p.TestSlug(), + }) + addConfigMapDeployment(p, "cm2", map[string]string{}, resourceOpts{ + name: "cm2", + namespace: p.TestSlug(), + }) + + p.KluctlMust("deploy", "--yes", "-t", "test") + assertConfigMapExists(t, k, p.TestSlug(), "cm1") + assertConfigMapExists(t, k, p.TestSlug(), "cm2") + + p.DeleteKustomizeDeployment("cm2") + addConfigMapDeployment(p, "cm3", map[string]string{}, resourceOpts{ + name: "cm3", + namespace: p.TestSlug(), + }) + + p.KluctlMust("deploy", "--yes", "-t", "test", "--prune") + assertConfigMapExists(t, k, p.TestSlug(), "cm1") + assertConfigMapNotExists(t, k, p.TestSlug(), "cm2") + assertConfigMapExists(t, k, p.TestSlug(), "cm3") +} diff --git a/pkg/deployment/commands/delete.go b/pkg/deployment/commands/delete.go index 28794cb9a..6edf12e0c 100644 --- a/pkg/deployment/commands/delete.go +++ b/pkg/deployment/commands/delete.go @@ -66,10 +66,7 @@ func (cmd *DeleteCommand) Run(ctx context.Context, k *k8s.K8sCluster, confirmCb } } - deleted, err := utils2.DeleteObjects(ctx, k, deleteRefs, dew, cmd.wait) - if err != nil { - return nil, err - } + deleted := utils2.DeleteObjects(ctx, k, deleteRefs, dew, cmd.wait) var c *deployment.DeploymentCollection if cmd.targetCtx != nil { diff --git a/pkg/deployment/commands/deploy.go b/pkg/deployment/commands/deploy.go index f2fd0a5b7..2d0979c7a 100644 --- a/pkg/deployment/commands/deploy.go +++ b/pkg/deployment/commands/deploy.go @@ -20,6 +20,8 @@ type DeployCommand struct { AbortOnError bool ReadinessTimeout time.Duration NoWait bool + Prune bool + WaitPrune bool } func NewDeployCommand(targetCtx *kluctl_project.TargetContext) *DeployCommand { @@ -92,9 +94,30 @@ func (cmd *DeployCommand) Run(diffResultCb func(diffResult *result.CommandResult if err != nil { return nil, err } + + var deleted []k8s2.ObjectRef + if cmd.Prune && cmd.targetCtx.Target.Discriminator == "" { + dew.AddError(k8s2.ObjectRef{}, fmt.Errorf("pruning without a discriminator is not supported")) + } else if cmd.Prune { + deleted = utils2.DeleteObjects(cmd.targetCtx.SharedContext.Ctx, cmd.targetCtx.SharedContext.K, orphanObjects, dew, cmd.WaitPrune) + + // now clean up the list of orphan objects (remove the ones that got deleted) + ds := map[k8s2.ObjectRef]bool{} + for _, x := range deleted { + ds[x] = true + } + var tmp []k8s2.ObjectRef + for _, x := range orphanObjects { + if _, ok := ds[x]; !ok { + tmp = append(tmp, x) + } + } + orphanObjects = tmp + } + r := &result.CommandResult{ Id: uuid.New().String(), - Objects: collectObjects(cmd.targetCtx.DeploymentCollection, ru, au, du, orphanObjects, nil), + Objects: collectObjects(cmd.targetCtx.DeploymentCollection, ru, au, du, orphanObjects, deleted), Errors: dew.GetErrorsList(), Warnings: dew.GetWarningsList(), SeenImages: cmd.targetCtx.DeploymentCollection.Images.SeenImages(false), diff --git a/pkg/deployment/commands/prune.go b/pkg/deployment/commands/prune.go index e48093d7b..fa851d0d9 100644 --- a/pkg/deployment/commands/prune.go +++ b/pkg/deployment/commands/prune.go @@ -54,10 +54,7 @@ func (cmd *PruneCommand) Run(confirmCb func(refs []k8s2.ObjectRef) error) (*resu } } - deleted, err := utils2.DeleteObjects(cmd.targetCtx.SharedContext.Ctx, cmd.targetCtx.SharedContext.K, deleteRefs, dew, cmd.wait) - if err != nil { - return nil, err - } + deleted := utils2.DeleteObjects(cmd.targetCtx.SharedContext.Ctx, cmd.targetCtx.SharedContext.K, deleteRefs, dew, cmd.wait) r := &result.CommandResult{ Id: uuid.New().String(), diff --git a/pkg/deployment/utils/delete_utils.go b/pkg/deployment/utils/delete_utils.go index c32f41358..8e02c38a1 100644 --- a/pkg/deployment/utils/delete_utils.go +++ b/pkg/deployment/utils/delete_utils.go @@ -163,7 +163,7 @@ func FindObjectsForDelete(k *k8s.K8sCluster, allClusterObjects []*uo.Unstructure return ret, nil } -func DeleteObjects(ctx context.Context, k *k8s.K8sCluster, refs []k8s2.ObjectRef, dew *DeploymentErrorsAndWarnings, doWait bool) ([]k8s2.ObjectRef, error) { +func DeleteObjects(ctx context.Context, k *k8s.K8sCluster, refs []k8s2.ObjectRef, dew *DeploymentErrorsAndWarnings, doWait bool) []k8s2.ObjectRef { g := utils.NewGoHelper(ctx, 8) var ret []k8s2.ObjectRef @@ -210,5 +210,5 @@ func DeleteObjects(ctx context.Context, k *k8s.K8sCluster, refs []k8s2.ObjectRef } g.Wait() - return ret, nil + return ret } From 89be6d63900c1809f3d09fe1c0f7a4833b273ff5 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 5 Jun 2023 23:23:42 +0200 Subject: [PATCH 1763/2916] feat: Use new prune feature of deploy command in controller --- api/v1beta1/condition_types.go | 4 -- api/v1beta1/kluctldeployment_types.go | 35 --------------- api/v1beta1/zz_generated.deepcopy.go | 5 --- .../gitops.kluctl.io_kluctldeployments.yaml | 6 --- .../reference/gitops/api/kluctl-controller.md | 23 ---------- e2e/gitops_errors_test.go | 6 ++- install/controller/controller/crd.yaml | 6 --- pkg/controllers/kluctl_project.go | 22 +-------- .../kluctldeployment_controller.go | 45 +------------------ 9 files changed, 7 insertions(+), 145 deletions(-) diff --git a/api/v1beta1/condition_types.go b/api/v1beta1/condition_types.go index 9bd45ed52..6a0711228 100644 --- a/api/v1beta1/condition_types.go +++ b/api/v1beta1/condition_types.go @@ -21,10 +21,6 @@ const ( // kluctl deploy command failed. DeployFailedReason string = "DeployFailed" - // PruneFailedReason represents the fact that the - // pruning of the KluctlDeployment failed. - PruneFailedReason string = "PruneFailed" - // ValidateFailedReason represents the fact that the // validate of the KluctlDeployment failed. ValidateFailedReason string = "ValidateFailed" diff --git a/api/v1beta1/kluctldeployment_types.go b/api/v1beta1/kluctldeployment_types.go index 30bfa45cc..3150f4130 100644 --- a/api/v1beta1/kluctldeployment_types.go +++ b/api/v1beta1/kluctldeployment_types.go @@ -322,8 +322,6 @@ type KluctlDeploymentStatus struct { // +optional LastDeployError string `json:"lastDeployError,omitempty"` // +optional - LastPruneError string `json:"lastPruneError,omitempty"` - // +optional LastValidateError string `json:"lastValidateError,omitempty"` // LastDeployResult is the result of the last deploy command @@ -331,10 +329,6 @@ type KluctlDeploymentStatus struct { // +kubebuilder:pruning:PreserveUnknownFields LastDeployResult *runtime.RawExtension `json:"lastDeployResult,omitempty"` - // LastDeployResult is the result of the last prune command - // +optional - LastPruneResult *runtime.RawExtension `json:"lastPruneResult,omitempty"` - // LastValidateResult is the result of the last validate command // +optional LastValidateResult *runtime.RawExtension `json:"lastValidateResult,omitempty"` @@ -357,23 +351,6 @@ func (s *KluctlDeploymentStatus) SetLastDeployResult(crs *result.CommandResultSu return nil } -func (s *KluctlDeploymentStatus) SetLastPruneResult(crs *result.CommandResultSummary, err error) error { - s.LastPruneError = "" - if err != nil { - s.LastPruneError = err.Error() - } - if crs == nil { - s.LastPruneResult = nil - } else { - b, err := yaml.WriteJsonString(crs) - if err != nil { - return err - } - s.LastPruneResult = &runtime.RawExtension{Raw: []byte(b)} - } - return nil -} - func (s *KluctlDeploymentStatus) SetLastValidateResult(crs *result.ValidateResult, err error) error { s.LastValidateError = "" if err != nil { @@ -403,18 +380,6 @@ func (s *KluctlDeploymentStatus) GetLastDeployResult() (*result.CommandResultSum return &ret, nil } -func (s *KluctlDeploymentStatus) GetLastPruneResult() (*result.CommandResultSummary, error) { - if s.LastPruneResult == nil { - return nil, nil - } - var ret result.CommandResultSummary - err := yaml.ReadYamlBytes(s.LastPruneResult.Raw, &ret) - if err != nil { - return nil, err - } - return &ret, nil -} - func (s *KluctlDeploymentStatus) GetLastValidateResult() (*result.ValidateResult, error) { if s.LastValidateResult == nil { return nil, nil diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 1483d24ad..338ac5635 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -258,11 +258,6 @@ func (in *KluctlDeploymentStatus) DeepCopyInto(out *KluctlDeploymentStatus) { *out = new(runtime.RawExtension) (*in).DeepCopyInto(*out) } - if in.LastPruneResult != nil { - in, out := &in.LastPruneResult, &out.LastPruneResult - *out = new(runtime.RawExtension) - (*in).DeepCopyInto(*out) - } if in.LastValidateResult != nil { in, out := &in.LastValidateResult, &out.LastValidateResult *out = new(runtime.RawExtension) diff --git a/config/crd/bases/gitops.kluctl.io_kluctldeployments.yaml b/config/crd/bases/gitops.kluctl.io_kluctldeployments.yaml index 3254ed420..535e56610 100644 --- a/config/crd/bases/gitops.kluctl.io_kluctldeployments.yaml +++ b/config/crd/bases/gitops.kluctl.io_kluctldeployments.yaml @@ -456,12 +456,6 @@ spec: type: string lastObjectsHash: type: string - lastPruneError: - type: string - lastPruneResult: - description: LastDeployResult is the result of the last prune command - type: object - x-kubernetes-preserve-unknown-fields: true lastValidateError: type: string lastValidateResult: diff --git a/docs/reference/gitops/api/kluctl-controller.md b/docs/reference/gitops/api/kluctl-controller.md index 7d64b8644..9a87889ce 100644 --- a/docs/reference/gitops/api/kluctl-controller.md +++ b/docs/reference/gitops/api/kluctl-controller.md @@ -1170,17 +1170,6 @@ string -lastPruneError
    - -string - - - -(Optional) - - - - lastValidateError
    string @@ -1204,18 +1193,6 @@ k8s.io/apimachinery/pkg/runtime.RawExtension -lastPruneResult
    - -k8s.io/apimachinery/pkg/runtime.RawExtension - - - -(Optional) -

    LastDeployResult is the result of the last prune command

    - - - - lastValidateResult
    k8s.io/apimachinery/pkg/runtime.RawExtension diff --git a/e2e/gitops_errors_test.go b/e2e/gitops_errors_test.go index a243b0edc..c2df3e467 100644 --- a/e2e/gitops_errors_test.go +++ b/e2e/gitops_errors_test.go @@ -226,13 +226,15 @@ data: return nil }) suite.waitForCommit(key, getHeadRevision(suite.T(), p)) - suite.assertErrors(key, metav1.ConditionFalse, kluctlv1.PruneFailedReason, "pruning without a discriminator is not supported", nil, nil) + suite.assertErrors(key, metav1.ConditionFalse, kluctlv1.DeployFailedReason, "deploy failed with 1 errors", []result.DeploymentError{ + {Message: "pruning without a discriminator is not supported"}, + }, nil) p.UpdateKluctlYaml(func(o *uo.UnstructuredObject) error { _ = o.SetNestedField(backup, "discriminator") return nil }) suite.waitForCommit(key, getHeadRevision(suite.T(), p)) - suite.assertErrors(key, metav1.ConditionTrue, kluctlv1.ReconciliationSucceededReason, "deploy: ok, prune: ok", nil, nil) + suite.assertErrors(key, metav1.ConditionTrue, kluctlv1.ReconciliationSucceededReason, "deploy: ok", nil, nil) suite.updateKluctlDeployment(key, func(kd *kluctlv1.KluctlDeployment) { kd.Spec.Prune = false }) diff --git a/install/controller/controller/crd.yaml b/install/controller/controller/crd.yaml index 0515fbe6e..b8c794f09 100644 --- a/install/controller/controller/crd.yaml +++ b/install/controller/controller/crd.yaml @@ -455,12 +455,6 @@ spec: type: string lastObjectsHash: type: string - lastPruneError: - type: string - lastPruneResult: - description: LastDeployResult is the result of the last prune command - type: object - x-kubernetes-preserve-unknown-fields: true lastValidateError: type: string lastValidateResult: diff --git a/pkg/controllers/kluctl_project.go b/pkg/controllers/kluctl_project.go index 10f1e589c..ccbf7fd04 100644 --- a/pkg/controllers/kluctl_project.go +++ b/pkg/controllers/kluctl_project.go @@ -707,6 +707,8 @@ func (pt *preparedTarget) kluctlDeploy(ctx context.Context, targetContext *kluct cmd.AbortOnError = pt.pp.obj.Spec.AbortOnError cmd.ReadinessTimeout = time.Minute * 10 cmd.NoWait = pt.pp.obj.Spec.NoWait + cmd.Prune = pt.pp.obj.Spec.Prune + cmd.WaitPrune = false cmdResult, err := cmd.Run(nil) err = pt.handleCommandResult(ctx, err, cmdResult, "deploy") @@ -723,26 +725,6 @@ func (pt *preparedTarget) kluctlPokeImages(ctx context.Context, targetContext *k return cmdResult, err } -func (pt *preparedTarget) kluctlPrune(ctx context.Context, targetContext *kluctl_project.TargetContext) (*result.CommandResult, error) { - if !pt.pp.obj.Spec.Prune { - return nil, nil - } - - timer := prometheus.NewTimer(internal_metrics.NewKluctlPruneDuration(pt.pp.obj.ObjectMeta.Namespace, pt.pp.obj.ObjectMeta.Name)) - defer timer.ObserveDuration() - - cmd := commands.NewPruneCommand("", targetContext, false) - cmdResult, err := cmd.Run(func(refs []k8s.ObjectRef) error { - pt.printDeletedRefs(ctx, refs) - return nil - }) - if err != nil { - return nil, err - } - err = pt.handleCommandResult(ctx, err, cmdResult, "prune") - return cmdResult, err -} - func (pt *preparedTarget) kluctlValidate(ctx context.Context, targetContext *kluctl_project.TargetContext, cmdResult *result.CommandResult) (*result.ValidateResult, error) { timer := prometheus.NewTimer(internal_metrics.NewKluctlValidateDuration(pt.pp.obj.ObjectMeta.Namespace, pt.pp.obj.ObjectMeta.Name)) defer timer.ObserveDuration() diff --git a/pkg/controllers/kluctldeployment_controller.go b/pkg/controllers/kluctldeployment_controller.go index 9759d04d2..bd1aded1d 100644 --- a/pkg/controllers/kluctldeployment_controller.go +++ b/pkg/controllers/kluctldeployment_controller.go @@ -258,7 +258,6 @@ func (r *KluctlDeploymentReconciler) doReconcile( } needDeploy := false - needPrune := false needValidate := false if obj.Status.LastDeployResult == nil || obj.Status.LastObjectsHash != objectsHash { @@ -293,13 +292,6 @@ func (r *KluctlDeploymentReconciler) doReconcile( obj.Status.LastValidateError = "" } - if obj.Spec.Prune { - needPrune = needDeploy - } else { - obj.Status.LastPruneResult = nil - obj.Status.LastPruneError = "" - } - obj.Status.LastObjectsHash = objectsHash var deployResult *result.CommandResult @@ -318,15 +310,6 @@ func (r *KluctlDeploymentReconciler) doReconcile( } } - if needPrune { - // run garbage collection for stale objects that do not have pruning disabled - pruneResult, err := pt.kluctlPrune(ctx, targetContext) - err = obj.Status.SetLastPruneResult(pruneResult.BuildSummary(), err) - if err != nil { - log.Error(err, "Failed to write prune result") - } - } - if needValidate { validateResult, err := pt.kluctlValidate(ctx, targetContext, deployResult) err = obj.Status.SetLastValidateResult(validateResult, err) @@ -360,10 +343,6 @@ func (r *KluctlDeploymentReconciler) buildFinalStatus(ctx context.Context, obj * finalStatus = obj.Status.LastDeployError reason = kluctlv1.DeployFailedReason return - } else if obj.Status.LastPruneError != "" { - finalStatus = obj.Status.LastPruneError - reason = kluctlv1.PruneFailedReason - return } else if obj.Status.LastValidateError != "" { finalStatus = obj.Status.LastValidateError reason = kluctlv1.ValidateFailedReason @@ -371,7 +350,6 @@ func (r *KluctlDeploymentReconciler) buildFinalStatus(ctx context.Context, obj * } var lastDeployResult *result.CommandResultSummary - var lastPruneResult *result.CommandResultSummary var lastValidateResult *result.ValidateResult if obj.Status.LastDeployResult != nil { err := yaml.ReadYamlBytes(obj.Status.LastDeployResult.Raw, &lastDeployResult) @@ -379,12 +357,6 @@ func (r *KluctlDeploymentReconciler) buildFinalStatus(ctx context.Context, obj * log.Info(fmt.Sprintf("Failed to parse last deploy result: %s", err.Error())) } } - if obj.Status.LastPruneResult != nil { - err := yaml.ReadYamlBytes(obj.Status.LastPruneResult.Raw, &lastPruneResult) - if err != nil { - log.Info(fmt.Sprintf("Failed to parse last prune result: %s", err.Error())) - } - } if obj.Status.LastValidateResult != nil { err := yaml.ReadYamlBytes(obj.Status.LastValidateResult.Raw, &lastValidateResult) if err != nil { @@ -393,12 +365,8 @@ func (r *KluctlDeploymentReconciler) buildFinalStatus(ctx context.Context, obj * } deployOk := lastDeployResult != nil && len(lastDeployResult.Errors) == 0 - pruneOk := lastPruneResult != nil && len(lastPruneResult.Errors) == 0 validateOk := lastValidateResult != nil && len(lastValidateResult.Errors) == 0 && lastValidateResult.Ready - if !obj.Spec.Prune { - pruneOk = true - } if !obj.Spec.Validate { validateOk = true } @@ -411,17 +379,6 @@ func (r *KluctlDeploymentReconciler) buildFinalStatus(ctx context.Context, obj * finalStatus += "failed" } } - if obj.Spec.Prune && obj.Status.LastPruneResult != nil { - if finalStatus != "" { - finalStatus += ", " - } - finalStatus += "prune: " - if pruneOk { - finalStatus += "ok" - } else { - finalStatus += "failed" - } - } if obj.Spec.Validate && obj.Status.LastValidateResult != nil { if finalStatus != "" { finalStatus += ", " @@ -434,7 +391,7 @@ func (r *KluctlDeploymentReconciler) buildFinalStatus(ctx context.Context, obj * } } - if deployOk && pruneOk { + if deployOk { if validateOk { reason = kluctlv1.ReconciliationSucceededReason } else { From 5329041e6cf84ac885679e4fe6c7de5fe0cde7e4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Jun 2023 21:32:28 +0200 Subject: [PATCH 1764/2916] chore(deps): Bump github.com/go-playground/validator/v10 (#551) Bumps [github.com/go-playground/validator/v10](https://github.com/go-playground/validator) from 10.14.0 to 10.14.1. - [Release notes](https://github.com/go-playground/validator/releases) - [Commits](https://github.com/go-playground/validator/compare/v10.14.0...v10.14.1) --- updated-dependencies: - dependency-name: github.com/go-playground/validator/v10 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ecbb99ab8..0f9fb0fba 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/bitnami-labs/sealed-secrets v0.21.0 github.com/cyphar/filepath-securejoin v0.2.3 github.com/docker/distribution v2.8.2+incompatible - github.com/go-playground/validator/v10 v10.14.0 + github.com/go-playground/validator/v10 v10.14.1 github.com/gobwas/glob v0.2.3 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 github.com/google/go-containerregistry v0.15.2 diff --git a/go.sum b/go.sum index 45dbf8ec4..3e300ece1 100644 --- a/go.sum +++ b/go.sum @@ -313,8 +313,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= -github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k= +github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= From fca4d93868dcb6f84a99e1c4c22a72de8c5eb3ee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Jun 2023 21:32:42 +0200 Subject: [PATCH 1765/2916] chore(deps): Bump github.com/sirupsen/logrus from 1.9.2 to 1.9.3 (#552) Bumps [github.com/sirupsen/logrus](https://github.com/sirupsen/logrus) from 1.9.2 to 1.9.3. - [Release notes](https://github.com/sirupsen/logrus/releases) - [Changelog](https://github.com/sirupsen/logrus/blob/master/CHANGELOG.md) - [Commits](https://github.com/sirupsen/logrus/compare/v1.9.2...v1.9.3) --- updated-dependencies: - dependency-name: github.com/sirupsen/logrus dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0f9fb0fba..8a1c1250a 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/r3labs/diff/v2 v2.15.1 github.com/rogpeppe/go-internal v1.10.0 - github.com/sirupsen/logrus v1.9.2 + github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.16.0 diff --git a/go.sum b/go.sum index 3e300ece1..92bed86a7 100644 --- a/go.sum +++ b/go.sum @@ -753,8 +753,8 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= -github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/skeema/knownhosts v1.1.1 h1:MTk78x9FPgDFVFkDLTrsnnfCJl7g1C/nnKvePgrIngE= github.com/skeema/knownhosts v1.1.1/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= From 52dcf1cc7d7b1234327db169dc1272393f082bea Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 6 Jun 2023 22:21:15 +0200 Subject: [PATCH 1766/2916] feat: Remove GitUrl/GitRef from KluctlDeploymentInfo This is duplicated info. --- pkg/types/result/command_result.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/types/result/command_result.go b/pkg/types/result/command_result.go index 7bc46c032..683086d98 100644 --- a/pkg/types/result/command_result.go +++ b/pkg/types/result/command_result.go @@ -29,8 +29,6 @@ type DeploymentError struct { type KluctlDeploymentInfo struct { Name string `json:"name"` Namespace string `json:"namespace"` - GitUrl string `json:"gitUrl"` - GitRef string `json:"gitRef"` } type CommandInitiator string From 67e301c3553ab9e9828efbd7688b28d6bf7c5618 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 6 Jun 2023 22:21:43 +0200 Subject: [PATCH 1767/2916] fix: Fill KluctlDeploymentInfo in CommandResult --- pkg/controllers/kluctl_project.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/controllers/kluctl_project.go b/pkg/controllers/kluctl_project.go index ccbf7fd04..4b426668b 100644 --- a/pkg/controllers/kluctl_project.go +++ b/pkg/controllers/kluctl_project.go @@ -646,6 +646,11 @@ func (pt *preparedTarget) handleCommandResult(ctx context.Context, cmdErr error, } cmdResult.Command.Initiator = result.CommandInititiator_KluctlDeployment + cmdResult.Command.KluctlDeployment = &result.KluctlDeploymentInfo{ + Name: pt.pp.obj.Name, + Namespace: pt.pp.obj.Namespace, + } + cmdResult.GitInfo.Url = &pt.pp.obj.Spec.Source.URL cmdResult.GitInfo.Ref = pt.pp.obj.Spec.Source.Ref.String() cmdResult.ProjectKey.GitRepoKey = pt.pp.obj.Spec.Source.URL.RepoKey() From e21886e3581a6c3b5fa0be870e15c79ad4151d88 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 7 Jun 2023 16:38:49 +0200 Subject: [PATCH 1768/2916] fix: Fix check for request-deploy annotation --- .../kluctldeployment_controller.go | 19 +++-------- .../kluctldeployment_controller_setup.go | 2 +- pkg/controllers/predicates.go | 34 +++---------------- 3 files changed, 10 insertions(+), 45 deletions(-) diff --git a/pkg/controllers/kluctldeployment_controller.go b/pkg/controllers/kluctldeployment_controller.go index bd1aded1d..6612a18fd 100644 --- a/pkg/controllers/kluctldeployment_controller.go +++ b/pkg/controllers/kluctldeployment_controller.go @@ -208,10 +208,10 @@ func (r *KluctlDeploymentReconciler) doReconcile( } oldGeneration := obj.Status.ObservedGeneration + oldDeployRequest := obj.Status.LastHandledDeployAt + curDeployRequest, _ := obj.GetAnnotations()[kluctlv1.KluctlRequestDeployAnnotation] obj.Status.ObservedGeneration = obj.GetGeneration() - if v, ok := obj.GetAnnotations()[kluctlv1.KluctlRequestDeployAnnotation]; ok { - obj.Status.LastHandledDeployAt = v - } + obj.Status.LastHandledDeployAt = curDeployRequest pp, err := prepareProject(ctx, r, obj, true) if err != nil { @@ -263,7 +263,7 @@ func (r *KluctlDeploymentReconciler) doReconcile( if obj.Status.LastDeployResult == nil || obj.Status.LastObjectsHash != objectsHash { // either never deployed or source code changed needDeploy = true - } else if r.checkRequestedDeploy(obj) { + } else if curDeployRequest != "" && oldDeployRequest != curDeployRequest { // explicitly requested a deploy needDeploy = true } else if oldGeneration != obj.GetGeneration() { @@ -453,17 +453,6 @@ func (r *KluctlDeploymentReconciler) nextDeployTime(obj *kluctlv1.KluctlDeployme return &t } -func (r *KluctlDeploymentReconciler) checkRequestedDeploy(obj *kluctlv1.KluctlDeployment) bool { - v, ok := obj.Annotations[kluctlv1.KluctlRequestDeployAnnotation] - if !ok { - return false - } - if v != obj.Status.LastHandledDeployAt { - return true - } - return false -} - func (r *KluctlDeploymentReconciler) nextValidateTime(obj *kluctlv1.KluctlDeployment) *time.Time { if obj.Status.LastValidateResult == nil { // was never validated before. Return early. diff --git a/pkg/controllers/kluctldeployment_controller_setup.go b/pkg/controllers/kluctldeployment_controller_setup.go index c0ca400d8..fd549f1f2 100644 --- a/pkg/controllers/kluctldeployment_controller_setup.go +++ b/pkg/controllers/kluctldeployment_controller_setup.go @@ -23,7 +23,7 @@ func (r *KluctlDeploymentReconciler) SetupWithManager(ctx context.Context, mgr c return ctrl.NewControllerManagedBy(mgr). For(&kluctlv1.KluctlDeployment{}, builder.WithPredicates( - predicate.Or(predicate.GenerationChangedPredicate{}, ReconcileRequestedPredicate{}, DeployRequestedPredicate{}), + predicate.Or(predicate.GenerationChangedPredicate{}, ReconcileRequestedPredicate{}), )). Complete(r) } diff --git a/pkg/controllers/predicates.go b/pkg/controllers/predicates.go index feec05a2c..ec5964016 100644 --- a/pkg/controllers/predicates.go +++ b/pkg/controllers/predicates.go @@ -16,35 +16,11 @@ func (ReconcileRequestedPredicate) Update(e event.UpdateEvent) bool { return false } - if val, ok := e.ObjectNew.GetAnnotations()[kluctlv1.KluctlRequestReconcileAnnotation]; ok { - if valOld, okOld := e.ObjectOld.GetAnnotations()[kluctlv1.KluctlRequestReconcileAnnotation]; okOld { - return val != valOld - } - return true - } - return false -} - -type DeployRequestedPredicate struct { - predicate.Funcs -} - -func (DeployRequestedPredicate) Update(e event.UpdateEvent) bool { - if e.ObjectOld == nil || e.ObjectNew == nil { - return false + check := func(aname string) bool { + v1, ok1 := e.ObjectNew.GetAnnotations()[aname] + v2, ok2 := e.ObjectOld.GetAnnotations()[aname] + return ok1 != ok2 || v1 != v2 } - if val, ok := e.ObjectNew.GetAnnotations()[kluctlv1.KluctlRequestReconcileAnnotation]; ok { - if valOld, okOld := e.ObjectOld.GetAnnotations()[kluctlv1.KluctlRequestReconcileAnnotation]; okOld { - return val != valOld - } - return true - } - if val, ok := e.ObjectNew.GetAnnotations()[kluctlv1.KluctlRequestDeployAnnotation]; ok { - if valOld, okOld := e.ObjectOld.GetAnnotations()[kluctlv1.KluctlRequestDeployAnnotation]; okOld { - return val != valOld - } - return true - } - return false + return check(kluctlv1.KluctlRequestReconcileAnnotation) || check(kluctlv1.KluctlRequestDeployAnnotation) } From 118fc5224d5866710db7001e8111153c7e7d024f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Jun 2023 10:25:46 +0200 Subject: [PATCH 1769/2916] chore(deps): Bump github.com/hashicorp/go-retryablehttp (#554) Bumps [github.com/hashicorp/go-retryablehttp](https://github.com/hashicorp/go-retryablehttp) from 0.7.2 to 0.7.4. - [Changelog](https://github.com/hashicorp/go-retryablehttp/blob/main/CHANGELOG.md) - [Commits](https://github.com/hashicorp/go-retryablehttp/compare/v0.7.2...v0.7.4) --- updated-dependencies: - dependency-name: github.com/hashicorp/go-retryablehttp dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8a1c1250a..958bb7456 100644 --- a/go.mod +++ b/go.mod @@ -65,7 +65,7 @@ require ( github.com/go-logr/logr v1.2.4 github.com/google/uuid v1.3.0 github.com/hashicorp/go-multierror v1.1.1 - github.com/hashicorp/go-retryablehttp v0.7.2 + github.com/hashicorp/go-retryablehttp v0.7.4 github.com/huandu/xstrings v1.4.0 github.com/onsi/gomega v1.27.7 github.com/otiai10/copy v1.11.0 diff --git a/go.sum b/go.sum index 92bed86a7..f7ffc0052 100644 --- a/go.sum +++ b/go.sum @@ -462,8 +462,8 @@ github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iP github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUDDYFRKq/RAd0= -github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= +github.com/hashicorp/go-retryablehttp v0.7.4 h1:ZQgVdpTdAL7WpMIwLzCfbalOcSUdkDZnpUv3/+BxzFA= +github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= From b627f4d752ab0583ae58788914f5a06a19dac08d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Jun 2023 10:26:09 +0200 Subject: [PATCH 1770/2916] chore(deps): Bump github.com/Azure/azure-sdk-for-go/sdk/azcore (#555) Bumps [github.com/Azure/azure-sdk-for-go/sdk/azcore](https://github.com/Azure/azure-sdk-for-go) from 1.6.0 to 1.6.1. - [Release notes](https://github.com/Azure/azure-sdk-for-go/releases) - [Changelog](https://github.com/Azure/azure-sdk-for-go/blob/main/documentation/release.md) - [Commits](https://github.com/Azure/azure-sdk-for-go/compare/sdk/azcore/v1.6.0...sdk/azcore/v1.6.1) --- updated-dependencies: - dependency-name: github.com/Azure/azure-sdk-for-go/sdk/azcore dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 958bb7456..f0f0aab43 100644 --- a/go.mod +++ b/go.mod @@ -52,7 +52,7 @@ require ( require ( filippo.io/age v1.1.1 - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.1 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 github.com/aws/aws-sdk-go-v2 v1.18.0 github.com/aws/aws-sdk-go-v2/config v1.18.25 diff --git a/go.sum b/go.sum index f7ffc0052..a9b491fba 100644 --- a/go.sum +++ b/go.sum @@ -54,8 +54,8 @@ filippo.io/age v1.1.1 h1:pIpO7l151hCnQ4BdyBujnGP2YlUo0uj6sAVNHGBvXHg= filippo.io/age v1.1.1/go.mod h1:l03SrzDUrBkdBx8+IILdnn2KZysqQdbEBUQ4p3sqEQE= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 h1:EKPd1INOIyr5hWOWhvpmQpY6tKjeG0hT1s3AMC/9fic= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1/go.mod h1:VzwV+t+dZ9j/H867F1M2ziD+yLHtB46oM35FxxMJ4d0= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0 h1:8kDqDngH+DmVBiCtIjCFTGa7MBnsIOkF9IccInFEbjk= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.1 h1:SEy2xmstIphdPwNBUi7uhvjyjhVKISfwjfOJmuy7kg4= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 h1:vcYCAze6p19qBW7MhZybIsqD8sMV8js0NyQM8JDnVtg= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9OrhHJoDD8ZDq51FHgXjqtP9z6bEwBq9U= github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY= From 949f8c3c6a7265f23b2262d9c61b78b34186062d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 5 Jun 2023 18:30:40 +0200 Subject: [PATCH 1771/2916] fix: Honor kluctl.io/diff-name again --- pkg/deployment/commands/utils.go | 47 +++++++++++++++++++++++------- pkg/deployment/utils/diff_utils.go | 21 +++++++------ 2 files changed, 47 insertions(+), 21 deletions(-) diff --git a/pkg/deployment/commands/utils.go b/pkg/deployment/commands/utils.go index 074d3222d..ebb2b08b2 100644 --- a/pkg/deployment/commands/utils.go +++ b/pkg/deployment/commands/utils.go @@ -10,6 +10,8 @@ import ( func collectObjects(c *deployment.DeploymentCollection, ru *utils.RemoteObjectUtils, au *utils.ApplyDeploymentsUtil, du *utils.DiffUtil, orphans []k8s.ObjectRef, deleted []k8s.ObjectRef) []result.ResultObject { m := map[k8s.ObjectRef]*result.ResultObject{} + remoteDiffNames := map[k8s.ObjectRef]k8s.ObjectRef{} + appliedDiffNames := map[k8s.ObjectRef]k8s.ObjectRef{} getOrCreate := func(ref k8s.ObjectRef) *result.ResultObject { x, ok := m[ref] @@ -23,42 +25,67 @@ func collectObjects(c *deployment.DeploymentCollection, ru *utils.RemoteObjectUt if c != nil { for _, x := range c.LocalObjects() { - o := getOrCreate(x.GetK8sRef()) + dn := du.GetDiffRef(x) + o := getOrCreate(dn) o.Rendered = x } } if ru != nil { for _, x := range ru.GetFilteredRemoteObjects(nil) { - o := getOrCreate(x.GetK8sRef()) + dn := du.GetDiffRef(x) + remoteDiffNames[x.GetK8sRef()] = dn + + o := getOrCreate(dn) o.Remote = x } } if au != nil { for _, x := range au.GetAppliedObjects() { - o := getOrCreate(x.GetK8sRef()) + dn := du.GetDiffRef(x) + appliedDiffNames[x.GetK8sRef()] = dn + o := getOrCreate(dn) o.Applied = x } for _, x := range au.GetAppliedHookObjects() { - o := getOrCreate(x.GetK8sRef()) + dn := du.GetDiffRef(x) + appliedDiffNames[x.GetK8sRef()] = dn + o := getOrCreate(dn) o.Hook = true } - for _, x := range au.GetNewObjectRefs() { - o := getOrCreate(x) - o.New = true - } for _, x := range au.GetDeletedObjects() { - o := getOrCreate(x) + dn, ok := remoteDiffNames[x] + if !ok { + dn = x + } + o := getOrCreate(dn) o.Deleted = true } } if du != nil { for _, x := range du.ChangedObjects { - o := getOrCreate(x.Ref) + dn, ok := appliedDiffNames[x.Ref] + if !ok { + dn = x.Ref + } + o := getOrCreate(dn) o.Changes = x.Changes } } + if au != nil { + for _, x := range au.GetNewObjectRefs() { + dn, ok := appliedDiffNames[x] + if !ok { + dn = x + } + o := getOrCreate(dn) + if len(o.Changes) != 0 { + continue + } + o.New = true + } + } for _, x := range orphans { o := getOrCreate(x) o.Orphan = true diff --git a/pkg/deployment/utils/diff_utils.go b/pkg/deployment/utils/diff_utils.go index 57c37b981..074020ace 100644 --- a/pkg/deployment/utils/diff_utils.go +++ b/pkg/deployment/utils/diff_utils.go @@ -127,13 +127,7 @@ func (u *DiffUtil) diffObject(lo *uo.UnstructuredObject, diffRef k8s2.ObjectRef, func (u *DiffUtil) calcRemoteObjectsForDiff() { u.remoteDiffObjects = make(map[k8s2.ObjectRef]*uo.UnstructuredObject) for _, o := range u.ru.remoteObjects { - diffName := o.GetK8sAnnotation("kluctl.io/diff-name") - if diffName == nil { - x := o.GetK8sName() - diffName = &x - } - diffRef := o.GetK8sRef() - diffRef.Name = *diffName + diffRef := u.GetDiffRef(o) oldCreationTime := time.Time{} if old, ok := u.remoteDiffObjects[diffRef]; ok { oldCreationTime = old.GetK8sCreationTime() @@ -145,11 +139,16 @@ func (u *DiffUtil) calcRemoteObjectsForDiff() { } func (u *DiffUtil) getRemoteObjectForDiff(localObject *uo.UnstructuredObject) (k8s2.ObjectRef, *uo.UnstructuredObject) { - ref := localObject.GetK8sRef() - diffName := localObject.GetK8sAnnotation("kluctl.io/diff-name") + ref := u.GetDiffRef(localObject) + o, _ := u.remoteDiffObjects[ref] + return ref, o +} + +func (u *DiffUtil) GetDiffRef(o *uo.UnstructuredObject) k8s2.ObjectRef { + ref := o.GetK8sRef() + diffName := o.GetK8sAnnotation("kluctl.io/diff-name") if diffName != nil { ref.Name = *diffName } - o, _ := u.remoteDiffObjects[ref] - return ref, o + return ref } From 9765e19a0565c149dbb881ec7976476494d8404c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 9 Jun 2023 11:18:39 +0200 Subject: [PATCH 1772/2916] test: Add tests for kluctl.io/diff-name --- e2e/diff_name_test.go | 87 +++++++++++++++++++++++++ e2e/utils_resources.go | 7 +- pkg/deployment/utils/diff_utils_test.go | 46 ++++++++----- 3 files changed, 123 insertions(+), 17 deletions(-) create mode 100644 e2e/diff_name_test.go diff --git a/e2e/diff_name_test.go b/e2e/diff_name_test.go new file mode 100644 index 000000000..aee6ad745 --- /dev/null +++ b/e2e/diff_name_test.go @@ -0,0 +1,87 @@ +package e2e + +import ( + test_utils "github.com/kluctl/kluctl/v2/e2e/test-utils" + "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/types/result" + "github.com/kluctl/kluctl/v2/pkg/yaml" + "github.com/stretchr/testify/assert" + v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "sort" + "testing" +) + +func TestDiffName(t *testing.T) { + t.Parallel() + + k := defaultCluster1 + + p := test_utils.NewTestProject(t) + + createNamespace(t, k, p.TestSlug()) + + p.UpdateTarget("test", nil) + + addConfigMapDeployment(p, "cm", map[string]string{ + "d1": "{{ args.v }}", + }, resourceOpts{ + name: "{{ args.cm_name }}", + fname: "cm.yaml", + namespace: p.TestSlug(), + annotations: map[string]string{ + "kluctl.io/diff-name": "cm", + }, + }) + p.KluctlMust("deploy", "--yes", "-t", "test", "-a", "cm_name=cm-1", "-a", "v=a") + assertConfigMapExists(t, k, p.TestSlug(), "cm-1") + + resultStr, _ := p.KluctlMust("deploy", "--yes", "-t", "test", "-a", "cm_name=cm-2", "-a", "v=b", "-oyaml") + assertConfigMapExists(t, k, p.TestSlug(), "cm-2") + + var cr result.CompactedCommandResult + err := yaml.ReadYamlString(resultStr, &cr) + assert.NoError(t, err) + + r := cr.ToNonCompacted() + sort.Slice(r.Objects, func(i, j int) bool { + return r.Objects[i].Ref.String() < r.Objects[j].Ref.String() + }) + + assert.Len(t, r.Objects, 2) + assert.Equal(t, result.BaseObject{ + Ref: k8s.ObjectRef{Version: "v1", Kind: "ConfigMap", Name: "cm", Namespace: p.TestSlug()}, + Changes: []result.Change{ + {Type: "update", JsonPath: "data.d1", OldValue: &v1.JSON{Raw: []byte("\"a\"")}, NewValue: &v1.JSON{Raw: []byte("\"b\"")}, UnifiedDiff: "-a\n+b"}, + {Type: "update", JsonPath: "metadata.name", OldValue: &v1.JSON{Raw: []byte("\"cm-1\"")}, NewValue: &v1.JSON{Raw: []byte("\"cm-2\"")}, UnifiedDiff: "-cm-1\n+cm-2"}}, + }, r.Objects[0].BaseObject) + assert.Equal(t, result.BaseObject{ + Ref: k8s.ObjectRef{Version: "v1", Kind: "ConfigMap", Name: "cm-1", Namespace: "test-diff-name"}, + Orphan: true, + }, r.Objects[1].BaseObject) + + resultStr, _ = p.KluctlMust("deploy", "--yes", "-t", "test", "-a", "cm_name=cm-3", "-a", "v=c", "-oyaml") + assertConfigMapExists(t, k, p.TestSlug(), "cm-2") + err = yaml.ReadYamlString(resultStr, &cr) + assert.NoError(t, err) + + r = cr.ToNonCompacted() + sort.Slice(r.Objects, func(i, j int) bool { + return r.Objects[i].Ref.String() < r.Objects[j].Ref.String() + }) + + assert.Len(t, r.Objects, 3) + assert.Equal(t, result.BaseObject{ + Ref: k8s.ObjectRef{Version: "v1", Kind: "ConfigMap", Name: "cm", Namespace: p.TestSlug()}, + Changes: []result.Change{ + {Type: "update", JsonPath: "data.d1", OldValue: &v1.JSON{Raw: []byte("\"b\"")}, NewValue: &v1.JSON{Raw: []byte("\"c\"")}, UnifiedDiff: "-b\n+c"}, + {Type: "update", JsonPath: "metadata.name", OldValue: &v1.JSON{Raw: []byte("\"cm-2\"")}, NewValue: &v1.JSON{Raw: []byte("\"cm-3\"")}, UnifiedDiff: "-cm-2\n+cm-3"}}, + }, r.Objects[0].BaseObject) + assert.Equal(t, result.BaseObject{ + Ref: k8s.ObjectRef{Version: "v1", Kind: "ConfigMap", Name: "cm-1", Namespace: "test-diff-name"}, + Orphan: true, + }, r.Objects[1].BaseObject) + assert.Equal(t, result.BaseObject{ + Ref: k8s.ObjectRef{Version: "v1", Kind: "ConfigMap", Name: "cm-2", Namespace: "test-diff-name"}, + Orphan: true, + }, r.Objects[2].BaseObject) +} diff --git a/e2e/utils_resources.go b/e2e/utils_resources.go index a78a2d0fd..5595851bb 100644 --- a/e2e/utils_resources.go +++ b/e2e/utils_resources.go @@ -9,6 +9,7 @@ import ( type resourceOpts struct { name string + fname string namespace string tags []string labels map[string]string @@ -56,8 +57,12 @@ func createSecretObject(data map[string]string, opts resourceOpts) *uo.Unstructu func addConfigMapDeployment(p *test_utils.TestProject, dir string, data map[string]string, opts resourceOpts) { o := createConfigMapObject(data, opts) + fname := opts.fname + if fname == "" { + fname = fmt.Sprintf("configmap-%s.yml", opts.name) + } p.AddKustomizeDeployment(dir, []test_utils.KustomizeResource{ - {Name: fmt.Sprintf("configmap-%s.yml", opts.name), Content: o}, + {Name: fname, Content: o}, }, opts.tags) if opts.when != "" { p.UpdateDeploymentItems(filepath.Dir(dir), func(items []*uo.UnstructuredObject) []*uo.UnstructuredObject { diff --git a/pkg/deployment/utils/diff_utils_test.go b/pkg/deployment/utils/diff_utils_test.go index 60e3fa6e9..8f69247d9 100644 --- a/pkg/deployment/utils/diff_utils_test.go +++ b/pkg/deployment/utils/diff_utils_test.go @@ -48,12 +48,13 @@ func (dtc *diffTestConfig) appliedObjectsMap() map[k8s2.ObjectRef]*uo.Unstructur return ret } -func newTestConfigMap(name string, data map[string]interface{}) *uo.UnstructuredObject { +func newTestConfigMap(name string, data map[string]interface{}, annotations map[string]string) *uo.UnstructuredObject { o := uo.New() o.SetK8sGVKs("", "v1", "ConfigMap") o.SetK8sName(name) o.SetK8sNamespace("default") o.SetNestedField(data, "data") + o.SetK8sAnnotations(annotations) return o } @@ -83,9 +84,9 @@ func TestDiff(t *testing.T) { tests := []*diffTestConfig{ { name: "One changed object (changed field)", - ro: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v1"})}, - lo: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v2"})}, - ao: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v2"})}, + ro: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v1"}, nil)}, + lo: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v2"}, nil)}, + ao: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v2"}, nil)}, a: func(t *testing.T, dtc *diffTestConfig) { assert.Len(t, dtc.du.ChangedObjects, 1) assert.Equal(t, []result.Change{ @@ -95,9 +96,9 @@ func TestDiff(t *testing.T) { }, { name: "One changed object (new field)", - ro: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v1"})}, - lo: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v1", "d2": "v2"})}, - ao: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v1", "d2": "v2"})}, + ro: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v1"}, nil)}, + lo: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v1", "d2": "v2"}, nil)}, + ao: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v1", "d2": "v2"}, nil)}, a: func(t *testing.T, dtc *diffTestConfig) { assert.Len(t, dtc.du.ChangedObjects, 1) assert.Equal(t, []result.Change{ @@ -107,9 +108,9 @@ func TestDiff(t *testing.T) { }, { name: "One changed object (removed field)", - ro: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v1", "d2": "v2"})}, - lo: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v1"})}, - ao: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v1"})}, + ro: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v1", "d2": "v2"}, nil)}, + lo: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v1"}, nil)}, + ao: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v1"}, nil)}, a: func(t *testing.T, dtc *diffTestConfig) { assert.Len(t, dtc.du.ChangedObjects, 1) assert.Equal(t, []result.Change{ @@ -119,9 +120,9 @@ func TestDiff(t *testing.T) { }, { name: "One changed object (new + removed field)", - ro: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v1"})}, - lo: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d2": "v2"})}, - ao: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d2": "v2"})}, + ro: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v1"}, nil)}, + lo: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d2": "v2"}, nil)}, + ao: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d2": "v2"}, nil)}, a: func(t *testing.T, dtc *diffTestConfig) { assert.Len(t, dtc.du.ChangedObjects, 1) assert.Equal(t, []result.Change{ @@ -132,9 +133,9 @@ func TestDiff(t *testing.T) { }, { name: "One changed object (mixed changes)", - ro: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v1", "d2": "v2"})}, - lo: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v12", "d3": "v3"})}, - ao: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v12", "d3": "v3"})}, + ro: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v1", "d2": "v2"}, nil)}, + lo: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v12", "d3": "v3"}, nil)}, + ao: []*uo.UnstructuredObject{newTestConfigMap("test", map[string]interface{}{"d1": "v12", "d3": "v3"}, nil)}, a: func(t *testing.T, dtc *diffTestConfig) { assert.Len(t, dtc.du.ChangedObjects, 1) assert.Equal(t, []result.Change{ @@ -144,6 +145,19 @@ func TestDiff(t *testing.T) { }, dtc.du.ChangedObjects[0].Changes) }, }, + { + name: "kluctl.io/diff-name being set", + ro: []*uo.UnstructuredObject{newTestConfigMap("test1", map[string]interface{}{"d1": "v1"}, map[string]string{"kluctl.io/diff-name": "test"})}, + lo: []*uo.UnstructuredObject{newTestConfigMap("test2", map[string]interface{}{"d1": "v2"}, map[string]string{"kluctl.io/diff-name": "test"})}, + ao: []*uo.UnstructuredObject{newTestConfigMap("test2", map[string]interface{}{"d1": "v2"}, map[string]string{"kluctl.io/diff-name": "test"})}, + a: func(t *testing.T, dtc *diffTestConfig) { + assert.Len(t, dtc.du.ChangedObjects, 1) + assert.Equal(t, []result.Change{ + buildChange("update", "data.d1", buildRaw("v1"), buildRaw("v2"), "-v1\n+v2"), + buildChange("update", "metadata.name", buildRaw("test1"), buildRaw("test2"), "-test1\n+test2"), + }, dtc.du.ChangedObjects[0].Changes) + }, + }, } for _, test := range tests { From cdd50760bdf1a657f04adecca09c4b7576417bde Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Jun 2023 11:19:05 +0200 Subject: [PATCH 1773/2916] chore(deps): Bump github.com/onsi/gomega from 1.27.7 to 1.27.8 (#557) Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.27.7 to 1.27.8. - [Release notes](https://github.com/onsi/gomega/releases) - [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md) - [Commits](https://github.com/onsi/gomega/compare/v1.27.7...v1.27.8) --- updated-dependencies: - dependency-name: github.com/onsi/gomega dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index f0f0aab43..1b51519b1 100644 --- a/go.mod +++ b/go.mod @@ -67,7 +67,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-retryablehttp v0.7.4 github.com/huandu/xstrings v1.4.0 - github.com/onsi/gomega v1.27.7 + github.com/onsi/gomega v1.27.8 github.com/otiai10/copy v1.11.0 github.com/prometheus/client_golang v1.15.1 github.com/sergi/go-diff v1.3.1 diff --git a/go.sum b/go.sum index a9b491fba..63ad11bf5 100644 --- a/go.sum +++ b/go.sum @@ -658,9 +658,9 @@ github.com/ohler55/ojg v1.18.7 h1:sC7zy0usEiWa6bvx3NU1yZH4kCA2F3Qzs6iiDX4+xdk= github.com/ohler55/ojg v1.18.7/go.mod h1:uHcD1ErbErC27Zhb5Df2jUjbseLLcmOCo6oxSr3jZxo= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= -github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU= -github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4= +github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss= +github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc= +github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc3 h1:fzg1mXZFj8YdPeNkRXMg+zb88BFV0Ys52cJydRwBkb8= From abb03226dded6642d229fd719b9868220042ccd5 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 10 Apr 2023 23:46:11 +0200 Subject: [PATCH 1774/2916] feat: Add initial implementation of webui --- cmd/kluctl/commands/cmd_webui.go | 112 + cmd/kluctl/commands/root.go | 1 + go.mod | 11 + go.sum | 25 + pkg/results/result-store-secrets.go | 3 + pkg/results/results-collector.go | 14 +- pkg/utils/fs_copy.go | 84 + pkg/webui/clusteraccessor.go | 110 + pkg/webui/embed.go | 19 + pkg/webui/generate-ts/main.go | 32 + pkg/webui/generate.go | 3 + pkg/webui/server.go | 280 + pkg/webui/shortnames.go | 217 + pkg/webui/staticbuilder.go | 144 + pkg/webui/ui/build.js | 5 + pkg/webui/ui/package-lock.json | 18441 ++++++++++++++++ pkg/webui/ui/package.json | 67 + pkg/webui/ui/public/.gitignore | 1 + pkg/webui/ui/public/favicon.ico | Bin 0 -> 15406 bytes pkg/webui/ui/public/index.html | 44 + pkg/webui/ui/public/logo192.png | Bin 0 -> 13703 bytes pkg/webui/ui/public/logo512.png | Bin 0 -> 26403 bytes pkg/webui/ui/public/manifest.json | 25 + pkg/webui/ui/public/robots.txt | 3 + pkg/webui/ui/public/staticbuild.js | 2 + pkg/webui/ui/src/api.tsx | 251 + pkg/webui/ui/src/components/ActionsMenu.tsx | 93 + pkg/webui/ui/src/components/App.css | 38 + pkg/webui/ui/src/components/App.test.tsx | 9 + pkg/webui/ui/src/components/App.tsx | 30 + pkg/webui/ui/src/components/CodeViewer.tsx | 29 + pkg/webui/ui/src/components/ErrorsTable.tsx | 56 + pkg/webui/ui/src/components/Jdenticon.tsx | 17 + pkg/webui/ui/src/components/LeftDrawer.tsx | 170 + pkg/webui/ui/src/components/Loading.tsx | 15 + pkg/webui/ui/src/components/ObjectYaml.tsx | 26 + .../ui/src/components/PropertiesTable.tsx | 31 + pkg/webui/ui/src/components/Router.tsx | 41 + .../components/result-view/ChangesTable.tsx | 105 + .../result-view/CommandResultStatusLine.tsx | 59 + .../result-view/CommandResultTree.tsx | 95 + .../result-view/CommandResultView.tsx | 53 + .../result-view/NodeStatusFilter.tsx | 97 + .../src/components/result-view/SidePanel.tsx | 78 + .../result-view/nodes/CommandResultNode.tsx | 60 + .../DeletedOrOrphanObjectsCollectionNode.tsx | 54 + .../nodes/DeploymentItemIncludeNode.tsx | 78 + .../result-view/nodes/DeploymentItemNode.tsx | 72 + .../result-view/nodes/NodeBuilder.ts | 194 + .../components/result-view/nodes/NodeData.tsx | 188 + .../result-view/nodes/ObjectNode.tsx | 101 + .../nodes/VarsSourceCollectionNode.tsx | 26 + .../result-view/nodes/VarsSourceNode.tsx | 238 + .../CommandResultDetailsDrawer.tsx | 54 + .../targets-view/CommandResultItem.tsx | 91 + .../src/components/targets-view/Projects.tsx | 50 + .../targets-view/TargetDetailsDrawer.tsx | 108 + .../src/components/targets-view/Targets.tsx | 147 + .../components/targets-view/TargetsView.tsx | 96 + pkg/webui/ui/src/icons/GitIcon.tsx | 6 + pkg/webui/ui/src/icons/KluctlLogo.tsx | 6 + pkg/webui/ui/src/icons/git.svg | 1 + pkg/webui/ui/src/icons/kluctl-logo.svg | 1 + pkg/webui/ui/src/index.css | 25 + pkg/webui/ui/src/index.tsx | 27 + pkg/webui/ui/src/loadscript.ts | 35 + pkg/webui/ui/src/logo.svg | 1 + pkg/webui/ui/src/models.ts | 900 + pkg/webui/ui/src/react-app-env.d.ts | 1 + pkg/webui/ui/src/reportWebVitals.ts | 15 + pkg/webui/ui/src/setupTests.ts | 5 + pkg/webui/ui/src/staticbuild.d.ts | 7 + pkg/webui/ui/src/utils/duration.ts | 30 + pkg/webui/ui/src/utils/misc.ts | 14 + pkg/webui/ui/tsconfig.json | 26 + pkg/webui/validator.go | 223 + 76 files changed, 23815 insertions(+), 1 deletion(-) create mode 100644 cmd/kluctl/commands/cmd_webui.go create mode 100644 pkg/utils/fs_copy.go create mode 100644 pkg/webui/clusteraccessor.go create mode 100644 pkg/webui/embed.go create mode 100644 pkg/webui/generate-ts/main.go create mode 100644 pkg/webui/generate.go create mode 100644 pkg/webui/server.go create mode 100644 pkg/webui/shortnames.go create mode 100644 pkg/webui/staticbuilder.go create mode 100644 pkg/webui/ui/build.js create mode 100644 pkg/webui/ui/package-lock.json create mode 100644 pkg/webui/ui/package.json create mode 100644 pkg/webui/ui/public/.gitignore create mode 100644 pkg/webui/ui/public/favicon.ico create mode 100644 pkg/webui/ui/public/index.html create mode 100644 pkg/webui/ui/public/logo192.png create mode 100644 pkg/webui/ui/public/logo512.png create mode 100644 pkg/webui/ui/public/manifest.json create mode 100644 pkg/webui/ui/public/robots.txt create mode 100644 pkg/webui/ui/public/staticbuild.js create mode 100644 pkg/webui/ui/src/api.tsx create mode 100644 pkg/webui/ui/src/components/ActionsMenu.tsx create mode 100644 pkg/webui/ui/src/components/App.css create mode 100644 pkg/webui/ui/src/components/App.test.tsx create mode 100644 pkg/webui/ui/src/components/App.tsx create mode 100644 pkg/webui/ui/src/components/CodeViewer.tsx create mode 100644 pkg/webui/ui/src/components/ErrorsTable.tsx create mode 100644 pkg/webui/ui/src/components/Jdenticon.tsx create mode 100644 pkg/webui/ui/src/components/LeftDrawer.tsx create mode 100644 pkg/webui/ui/src/components/Loading.tsx create mode 100644 pkg/webui/ui/src/components/ObjectYaml.tsx create mode 100644 pkg/webui/ui/src/components/PropertiesTable.tsx create mode 100644 pkg/webui/ui/src/components/Router.tsx create mode 100644 pkg/webui/ui/src/components/result-view/ChangesTable.tsx create mode 100644 pkg/webui/ui/src/components/result-view/CommandResultStatusLine.tsx create mode 100644 pkg/webui/ui/src/components/result-view/CommandResultTree.tsx create mode 100644 pkg/webui/ui/src/components/result-view/CommandResultView.tsx create mode 100644 pkg/webui/ui/src/components/result-view/NodeStatusFilter.tsx create mode 100644 pkg/webui/ui/src/components/result-view/SidePanel.tsx create mode 100644 pkg/webui/ui/src/components/result-view/nodes/CommandResultNode.tsx create mode 100644 pkg/webui/ui/src/components/result-view/nodes/DeletedOrOrphanObjectsCollectionNode.tsx create mode 100644 pkg/webui/ui/src/components/result-view/nodes/DeploymentItemIncludeNode.tsx create mode 100644 pkg/webui/ui/src/components/result-view/nodes/DeploymentItemNode.tsx create mode 100644 pkg/webui/ui/src/components/result-view/nodes/NodeBuilder.ts create mode 100644 pkg/webui/ui/src/components/result-view/nodes/NodeData.tsx create mode 100644 pkg/webui/ui/src/components/result-view/nodes/ObjectNode.tsx create mode 100644 pkg/webui/ui/src/components/result-view/nodes/VarsSourceCollectionNode.tsx create mode 100644 pkg/webui/ui/src/components/result-view/nodes/VarsSourceNode.tsx create mode 100644 pkg/webui/ui/src/components/targets-view/CommandResultDetailsDrawer.tsx create mode 100644 pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx create mode 100644 pkg/webui/ui/src/components/targets-view/Projects.tsx create mode 100644 pkg/webui/ui/src/components/targets-view/TargetDetailsDrawer.tsx create mode 100644 pkg/webui/ui/src/components/targets-view/Targets.tsx create mode 100644 pkg/webui/ui/src/components/targets-view/TargetsView.tsx create mode 100644 pkg/webui/ui/src/icons/GitIcon.tsx create mode 100644 pkg/webui/ui/src/icons/KluctlLogo.tsx create mode 100644 pkg/webui/ui/src/icons/git.svg create mode 100644 pkg/webui/ui/src/icons/kluctl-logo.svg create mode 100644 pkg/webui/ui/src/index.css create mode 100644 pkg/webui/ui/src/index.tsx create mode 100644 pkg/webui/ui/src/loadscript.ts create mode 100644 pkg/webui/ui/src/logo.svg create mode 100644 pkg/webui/ui/src/models.ts create mode 100644 pkg/webui/ui/src/react-app-env.d.ts create mode 100644 pkg/webui/ui/src/reportWebVitals.ts create mode 100644 pkg/webui/ui/src/setupTests.ts create mode 100644 pkg/webui/ui/src/staticbuild.d.ts create mode 100644 pkg/webui/ui/src/utils/duration.ts create mode 100644 pkg/webui/ui/src/utils/misc.ts create mode 100644 pkg/webui/ui/tsconfig.json create mode 100644 pkg/webui/validator.go diff --git a/cmd/kluctl/commands/cmd_webui.go b/cmd/kluctl/commands/cmd_webui.go new file mode 100644 index 000000000..73838107a --- /dev/null +++ b/cmd/kluctl/commands/cmd_webui.go @@ -0,0 +1,112 @@ +package commands + +import ( + "context" + "fmt" + "github.com/kluctl/kluctl/v2/pkg/results" + "github.com/kluctl/kluctl/v2/pkg/webui" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + "sigs.k8s.io/controller-runtime/pkg/cache" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type webuiCmd struct { + Port int `group:"misc" help:"Port to serve the api and webui." default:"8080"` + Context []string `group:"misc" help:"List of kubernetes contexts to use."` + AllContexts bool `group:"misc" help:"Use all Kubernetes contexts found in the kubeconfig."` + StaticPath string `group:"misc" help:"Build static webui."` +} + +func (cmd *webuiCmd) Help() string { + return `TODO` +} + +func (cmd *webuiCmd) Run(ctx context.Context) error { + + stores, configs, err := cmd.createResultStores(ctx) + if err != nil { + return err + } + + collector := results.NewResultsCollector(ctx, stores) + collector.Start() + + if cmd.StaticPath != "" { + collector.WaitForInitialSync() + sbw := webui.NewStaticWebuiBuilder(collector) + return sbw.Build(cmd.StaticPath) + } else { + server := webui.NewCommandResultsServer(ctx, collector, configs) + return server.Run(cmd.Port) + } +} + +func (cmd *webuiCmd) createResultStores(ctx context.Context) ([]results.ResultStore, []*rest.Config, error) { + r := clientcmd.NewDefaultClientConfigLoadingRules() + + kcfg, err := r.Load() + if err != nil { + return nil, nil, err + } + + var stores []results.ResultStore + var configs []*rest.Config + + var contexts []string + if cmd.AllContexts { + for name, _ := range kcfg.Contexts { + contexts = append(contexts, name) + } + } else { + if len(cmd.Context) == 0 { + // placeholder for current context + contexts = append(contexts, "") + } + for _, c := range cmd.Context { + found := false + for name, _ := range kcfg.Contexts { + if c == name { + contexts = append(contexts, name) + found = true + break + } + } + if !found { + return nil, nil, fmt.Errorf("context '%s' not found in kubeconfig", c) + } + } + } + + for _, c := range contexts { + configOverrides := &clientcmd.ConfigOverrides{ + CurrentContext: c, + } + config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( + r, + configOverrides).ClientConfig() + if err != nil { + return nil, nil, err + } + + client, err := client.New(config, client.Options{}) + if err != nil { + return nil, nil, err + } + + cache, err := cache.New(config, cache.Options{}) + if err != nil { + return nil, nil, err + } + go cache.Start(ctx) + + store, err := results.NewResultStoreSecrets(ctx, client, cache, "", 0) + if err != nil { + return nil, nil, err + } + stores = append(stores, store) + configs = append(configs, config) + } + + return stores, configs, nil +} diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index cc629d689..67dbc8cfe 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -67,6 +67,7 @@ type cli struct { Seal sealCmd `cmd:"" help:"Seal secrets based on target's sealingConfig"` Validate validateCmd `cmd:"" help:"Validates the already deployed deployment"` Controller controllerCmd `cmd:"" help:"Kluctl controller sub-commands"` + Webui webuiCmd `cmd:"" help:"TODO"` Version versionCmd `cmd:"" help:"Print kluctl version"` } diff --git a/go.mod b/go.mod index 1b51519b1..4fa6e6eff 100644 --- a/go.mod +++ b/go.mod @@ -61,6 +61,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/sts v1.19.0 github.com/aws/smithy-go v1.13.5 github.com/dimchansky/utfbom v1.1.1 + github.com/gin-gonic/gin v1.9.0 github.com/go-git/go-git/v5 v5.7.0 github.com/go-logr/logr v1.2.4 github.com/google/uuid v1.3.0 @@ -71,6 +72,7 @@ require ( github.com/otiai10/copy v1.11.0 github.com/prometheus/client_golang v1.15.1 github.com/sergi/go-diff v1.3.1 + github.com/tkrajina/typescriptify-golang-structs v0.1.10 go.mozilla.org/sops/v3 v3.7.4-0.20220901181616-9124783930b1 sigs.k8s.io/cli-utils v0.34.0 sigs.k8s.io/controller-runtime v0.15.0 @@ -108,9 +110,11 @@ require ( github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.10 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver v3.5.1+incompatible // indirect + github.com/bytedance/sonic v1.8.0 // indirect github.com/cenkalti/backoff/v3 v3.2.2 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/cloudflare/circl v1.3.3 // indirect github.com/containerd/containerd v1.7.0 // indirect github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect @@ -130,6 +134,7 @@ require ( github.com/fatih/color v1.13.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.4.1 // indirect @@ -142,6 +147,7 @@ require ( github.com/go-openapi/swag v0.22.3 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/goccy/go-json v0.10.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect @@ -171,6 +177,7 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.16.5 // indirect + github.com/klauspost/cpuid/v2 v2.0.9 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect @@ -212,6 +219,9 @@ require ( github.com/spf13/cast v1.5.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/subosito/gotenv v1.4.2 // indirect + github.com/tkrajina/go-reflector v0.5.5 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.11 // indirect github.com/urfave/cli v1.22.12 // indirect github.com/vbatts/tar-split v0.11.3 // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect @@ -227,6 +237,7 @@ require ( go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.8.0 // indirect go.uber.org/zap v1.24.0 // indirect + golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect golang.org/x/mod v0.10.0 // indirect golang.org/x/oauth2 v0.7.0 // indirect golang.org/x/time v0.3.0 // indirect diff --git a/go.sum b/go.sum index 63ad11bf5..992fed9f8 100644 --- a/go.sum +++ b/go.sum @@ -164,6 +164,9 @@ github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd h1:rFt+Y/IK1aEZ github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembjv71DPz3uX/V/6MMlSyD9JBQ6kQ= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA= +github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= @@ -174,6 +177,9 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -272,6 +278,10 @@ github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbS github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8= +github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k= github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= @@ -328,6 +338,8 @@ github.com/gobuffalo/packr/v2 v2.8.3 h1:xE1yzvnO56cUC0sTpKR3DIbxZgB54AftTFMhB2XE github.com/gobuffalo/packr/v2 v2.8.3/go.mod h1:0SahksCVcx4IMnigTjiFuyldmTrdTctXsOdiU5KwbKc= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= +github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godror/godror v0.24.2/go.mod h1:wZv/9vPiUib6tkoDl+AZ/QLf5YZgMravZ7jxH2eQWAE= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -536,6 +548,8 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI= github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/kluctl/go-embed-python v0.0.0-3.10.9-20230206-2 h1:K96SwIr8MzBQ3kFFz2H/pA2y+EEk04vZ4fWj/YZghBU= github.com/kluctl/go-embed-python v0.0.0-3.10.9-20230206-2/go.mod h1:vzngsPshNKUtq0gxkYQKNJafrcH7Qy7Qt6yGNt7JmQI= github.com/kluctl/go-jinja2 v0.0.0-20230428103343-a832225dc94c h1:qAIvhYamCEU/tY6NaENEIQCynGV5sdON7zgZKnbrhhw= @@ -807,8 +821,16 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/tkrajina/go-reflector v0.5.5 h1:gwoQFNye30Kk7NrExj8zm3zFtrGPqOkzFMLuQZg1DtQ= +github.com/tkrajina/go-reflector v0.5.5/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4= +github.com/tkrajina/typescriptify-golang-structs v0.1.10 h1:W/Ta9Kqo2lV+7bVXuQoUhZ0bDlnjwtPpKsy3A9M1nYg= +github.com/tkrajina/typescriptify-golang-structs v0.1.10/go.mod h1:sjU00nti/PMEOZb07KljFlR+lJ+RotsC0GBQMv9EKls= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/urfave/cli v1.22.12 h1:igJgVw1JdKH+trcLWLeLwZjU9fEfPesQ+9/e4MQ44S8= github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8= github.com/vbatts/tar-split v0.11.3 h1:hLFqsOLQ1SsppQNTMpkpPXClLDfC2A3Zgy9OUU+RVck= @@ -876,6 +898,8 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -1369,6 +1393,7 @@ k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5/go.mod h1:OLgZIPagt7ERELqWJFomSt oras.land/oras-go v1.2.3 h1:v8PJl+gEAntI1pJ/LCrDgsuk+1PKVavVEPsYIHFE5uY= oras.land/oras-go v1.2.3/go.mod h1:M/uaPdYklze0Vf3AakfarnpoEckvw0ESbRdN8Z1vdJg= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/cli-utils v0.34.0 h1:zCUitt54f0/MYj/ajVFnG6XSXMhpZ72O/3RewIchW8w= diff --git a/pkg/results/result-store-secrets.go b/pkg/results/result-store-secrets.go index 1389db4ab..9b62f35c0 100644 --- a/pkg/results/result-store-secrets.go +++ b/pkg/results/result-store-secrets.go @@ -338,6 +338,9 @@ func (s *ResultStoreSecrets) WatchCommandResultSummaries(options ListCommandResu return nil, err } + stopCh := make(chan struct{}) + toolscache.WaitForCacheSync(stopCh, r.HasSynced) + cancel := func() { _ = s.informer.RemoveEventHandler(r) } diff --git a/pkg/results/results-collector.go b/pkg/results/results-collector.go index 053cbbe37..f5376ad28 100644 --- a/pkg/results/results-collector.go +++ b/pkg/results/results-collector.go @@ -17,7 +17,8 @@ type ResultsCollector struct { resultSummaries map[string]summaryEntry handlers []*handlerEntry - mutex sync.Mutex + initialWG sync.WaitGroup + mutex sync.Mutex } type summaryEntry struct { @@ -38,6 +39,8 @@ func NewResultsCollector(ctx context.Context, stores []ResultStore) *ResultsColl resultSummaries: map[string]summaryEntry{}, } + ret.initialWG.Add(len(stores)) + return ret } @@ -45,6 +48,10 @@ func (rc *ResultsCollector) Start() { rc.startWatchResults() } +func (rc *ResultsCollector) WaitForInitialSync() { + rc.initialWG.Wait() +} + func (rc *ResultsCollector) startWatchResults() { for _, store := range rc.stores { go rc.runWatchResults(store) @@ -52,12 +59,17 @@ func (rc *ResultsCollector) startWatchResults() { } func (rc *ResultsCollector) runWatchResults(store ResultStore) { + initial := true for { _, err := store.WatchCommandResultSummaries(ListCommandResultSummariesOptions{}, func(summary *result.CommandResultSummary) { rc.handleUpdate(store, summary) }, func(id string) { rc.handleDelete(store, id) }) + if initial { + rc.initialWG.Done() + initial = false + } if err == nil { break } diff --git a/pkg/utils/fs_copy.go b/pkg/utils/fs_copy.go new file mode 100644 index 000000000..ef5fef165 --- /dev/null +++ b/pkg/utils/fs_copy.go @@ -0,0 +1,84 @@ +package utils + +import ( + "fmt" + "io" + "io/fs" + "os" + "path" + "path/filepath" +) + +// TODO get rid of all this when https://github.com/otiai10/copy/issues/71 gets implemented + +func CopyFile(src string, dst string) error { + if !IsFile(src) { + return fmt.Errorf("%s is not a regular file", src) + } + f, err := os.Open(src) + if err != nil { + return err + } + defer f.Close() + return CopyFileStream(f, dst) +} + +func FsCopyFile(srcFs fs.FS, src, dst string) error { + src = filepath.ToSlash(src) + source, err := srcFs.Open(src) + if err != nil { + return err + } + defer source.Close() + + sourceFileStat, err := source.Stat() + if err != nil { + return err + } + + if !sourceFileStat.Mode().IsRegular() { + return fmt.Errorf("%s is not a regular file", src) + } + + return CopyFileStream(source, dst) +} + +func CopyFileStream(src io.Reader, dst string) error { + destination, err := os.Create(dst) + if err != nil { + return err + } + defer destination.Close() + + _, err = io.Copy(destination, src) + return err +} + +func FsCopyDir(srcFs fs.FS, src string, dst string) error { + var err error + var fds []fs.DirEntry + + src = filepath.ToSlash(src) + + if fds, err = fs.ReadDir(srcFs, src); err != nil { + return err + } + if err = os.MkdirAll(dst, 0o700); err != nil { + return err + } + for _, fd := range fds { + srcfp := path.Join(src, fd.Name()) + dstfp := filepath.Join(dst, fd.Name()) + + if fd.IsDir() { + if err = FsCopyDir(srcFs, srcfp, dstfp); err != nil { + return err + } + } else { + if err = FsCopyFile(srcFs, srcfp, dstfp); err != nil { + return err + } + } + } + return nil +} diff --git a/pkg/webui/clusteraccessor.go b/pkg/webui/clusteraccessor.go new file mode 100644 index 000000000..fe83386f2 --- /dev/null +++ b/pkg/webui/clusteraccessor.go @@ -0,0 +1,110 @@ +package webui + +import ( + "context" + k8s2 "github.com/kluctl/kluctl/v2/pkg/k8s" + corev1 "k8s.io/api/core/v1" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + "sync" + "time" +) + +type clusterAccessorManager struct { + ctx context.Context + accessors []*clusterAccessor +} + +type clusterAccessor struct { + ctx context.Context + config *rest.Config + client client.Client + k *k8s2.K8sCluster + clusterId string + mutex sync.Mutex +} + +func (cam *clusterAccessorManager) add(config *rest.Config) { + cam.accessors = append(cam.accessors, &clusterAccessor{ + ctx: cam.ctx, + config: config, + }) +} + +func (cam *clusterAccessorManager) start() { + for _, ca := range cam.accessors { + ca.start() + } +} + +func (cam *clusterAccessorManager) getForClusterId(clusterId string) *clusterAccessor { + for _, ca := range cam.accessors { + if ca.getClusterId() == clusterId { + return ca + } + } + return nil +} + +func (ca *clusterAccessor) start() { + go ca.initClient() +} + +func (ca *clusterAccessor) initClient() { + for { + err := ca.tryInitClient() + if err == nil { + break + } + time.Sleep(5 * time.Second) + } +} + +func (ca *clusterAccessor) tryInitClient() error { + var err error + c, err := client.New(ca.config, client.Options{}) + if err != nil { + return err + } + var ns corev1.Namespace + err = c.Get(context.Background(), client.ObjectKey{Name: "kube-system"}, &ns) + if err != nil { + return err + } + + cf, err := k8s2.NewClientFactory(context.Background(), ca.config) + if err != nil { + return err + } + + k, err := k8s2.NewK8sCluster(context.Background(), cf, true) + if err != nil { + return err + } + + ca.mutex.Lock() + defer ca.mutex.Unlock() + ca.client = c + ca.k = k + ca.clusterId = string(ns.UID) + + return nil +} + +func (ca *clusterAccessor) getClusterId() string { + ca.mutex.Lock() + defer ca.mutex.Unlock() + return ca.clusterId +} + +func (ca *clusterAccessor) getClient() client.Client { + ca.mutex.Lock() + defer ca.mutex.Unlock() + return ca.client +} + +func (ca *clusterAccessor) getK() *k8s2.K8sCluster { + ca.mutex.Lock() + defer ca.mutex.Unlock() + return ca.k +} diff --git a/pkg/webui/embed.go b/pkg/webui/embed.go new file mode 100644 index 000000000..be8818c85 --- /dev/null +++ b/pkg/webui/embed.go @@ -0,0 +1,19 @@ +package webui + +import ( + "embed" + log "github.com/sirupsen/logrus" + "io/fs" +) + +//go:embed ui/build +var uiBuildFS embed.FS +var uiFS fs.FS + +func init() { + var err error + uiFS, err = fs.Sub(uiBuildFS, "ui/build") + if err != nil { + log.Fatal("failed to get ui fs", err) + } +} diff --git a/pkg/webui/generate-ts/main.go b/pkg/webui/generate-ts/main.go new file mode 100644 index 000000000..23a26e7ee --- /dev/null +++ b/pkg/webui/generate-ts/main.go @@ -0,0 +1,32 @@ +package main + +import ( + "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/types/result" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "github.com/kluctl/kluctl/v2/pkg/webui" + "github.com/tkrajina/typescriptify-golang-structs/typescriptify" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func main() { + converter := typescriptify.New(). + WithBackupDir(""). + Add(result.CommandResult{}). + Add(result.ProjectSummary{}). + Add(result.CommandResultSummary{}). + Add(webui.ShortName{}). + Add(uo.UnstructuredObject{}). + ManageType(types.GitUrl{}, typescriptify.TypeOptions{TSType: "string"}). + ManageType(types.GitRepoKey{}, typescriptify.TypeOptions{TSType: "string"}). + ManageType(types.YamlUrl{}, typescriptify.TypeOptions{TSType: "string"}). + ManageType(uo.UnstructuredObject{}, typescriptify.TypeOptions{TSType: "any"}). + ManageType(metav1.Time{}, typescriptify.TypeOptions{TSType: "string"}). + ManageType(apiextensionsv1.JSON{}, typescriptify.TypeOptions{TSType: "any"}) + + err := converter.ConvertToFile("ui/src/models.ts") + if err != nil { + panic(err.Error()) + } +} diff --git a/pkg/webui/generate.go b/pkg/webui/generate.go new file mode 100644 index 000000000..f4aa787b8 --- /dev/null +++ b/pkg/webui/generate.go @@ -0,0 +1,3 @@ +package webui + +//go:generate go run github.com/kluctl/kluctl/v2/pkg/webui/generate-ts diff --git a/pkg/webui/server.go b/pkg/webui/server.go new file mode 100644 index 000000000..746f3b0da --- /dev/null +++ b/pkg/webui/server.go @@ -0,0 +1,280 @@ +package webui + +import ( + "context" + "fmt" + "github.com/gin-gonic/gin" + "github.com/kluctl/kluctl/v2/pkg/results" + "github.com/kluctl/kluctl/v2/pkg/status" + "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/types/k8s" + "github.com/kluctl/kluctl/v2/pkg/types/result" + "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "k8s.io/client-go/rest" + "net" + "net/http" +) + +type CommandResultsServer struct { + ctx context.Context + collector *results.ResultsCollector + cam *clusterAccessorManager + vam *validatorManager +} + +func NewCommandResultsServer(ctx context.Context, collector *results.ResultsCollector, configs []*rest.Config) *CommandResultsServer { + ret := &CommandResultsServer{ + ctx: ctx, + collector: collector, + cam: &clusterAccessorManager{ + ctx: ctx, + }, + } + + for _, config := range configs { + ret.cam.add(config) + } + + ret.vam = newValidatorManager(ctx, collector, ret.cam) + + return ret +} + +func (s *CommandResultsServer) Run(port int) error { + _, _ = s.collector.WatchCommandResultSummaries(results.ListCommandResultSummariesOptions{}, func(summary *result.CommandResultSummary) { + status.Info(s.ctx, "Updated result summary for %s", summary.Id) + }, func(id string) { + status.Info(s.ctx, "Deleted result summary for %s", id) + }) + + s.cam.start() + s.vam.start() + + router := gin.Default() + + router.StaticFS("/webui", http.FS(uiFS)) + + api := router.Group("/api") + api.GET("/getShortNames", s.getShortNames) + api.GET("/listProjects", s.listProjects) + api.GET("/listResults", s.listResults) + api.GET("/getResult", s.getResult) + api.GET("/getResultObject", s.getResultObject) + api.POST("/validateNow", s.validateNow) + + address := fmt.Sprintf(":%d", port) + listener, err := net.Listen("tcp", address) + if err != nil { + return err + } + + httpServer := http.Server{ + Addr: address, + BaseContext: func(listener net.Listener) context.Context { + return s.ctx + }, + Handler: router.Handler(), + } + + return httpServer.Serve(listener) +} + +func (s *CommandResultsServer) getShortNames(c *gin.Context) { + c.JSON(http.StatusOK, GetShortNames()) +} + +func (s *CommandResultsServer) listResults(c *gin.Context) { + args := struct { + FilterProject string `form:"filterProject"` + FilterSubDir string `form:"filterSubDir"` + }{} + err := c.BindQuery(&args) + if err != nil { + _ = c.AbortWithError(http.StatusBadRequest, err) + return + } + + repoKey, err := types.ParseGitRepoKey(args.FilterProject) + if err != nil { + _ = c.AbortWithError(http.StatusBadRequest, err) + return + } + + var filter *result.ProjectKey + if args.FilterProject != "" { + filter = &result.ProjectKey{ + GitRepoKey: repoKey, + SubDir: args.FilterSubDir, + } + } + + summaries, err := s.collector.ListCommandResultSummaries(results.ListCommandResultSummariesOptions{ + ProjectFilter: filter, + }) + if err != nil { + _ = c.AbortWithError(http.StatusBadRequest, err) + return + } + + c.JSON(http.StatusOK, summaries) +} + +func (s *CommandResultsServer) listProjects(c *gin.Context) { + summaries, err := s.collector.ListCommandResultSummaries(results.ListCommandResultSummariesOptions{}) + if err != nil { + _ = c.AbortWithError(http.StatusBadRequest, err) + return + } + + projects := result.BuildProjectSummaries(summaries) + + for _, p := range projects { + for _, t := range p.Targets { + key := projectTargetKey{ + Project: p.Project, + Target: t.Target, + } + vr, err := s.vam.getValidateResult(key) + if err == nil { + t.LastValidateResult = vr + } + } + } + + c.JSON(http.StatusOK, projects) +} + +type resultIdParam struct { + ResultId string `form:"resultId"` +} +type refParam struct { + Group string `form:"group"` + Version string `form:"version"` + Kind string `form:"kind"` + Name string `form:"name"` + Namespace string `form:"namespace"` +} +type objectTypeParams struct { + ObjectType string `form:"objectType"` +} + +func (ref refParam) toK8sRef() k8s.ObjectRef { + return k8s.ObjectRef{ + Group: ref.Group, + Version: ref.Version, + Kind: ref.Kind, + Name: ref.Name, + Namespace: ref.Namespace, + } +} + +func (s *CommandResultsServer) getResult(c *gin.Context) { + var params resultIdParam + + err := c.Bind(¶ms) + if err != nil { + _ = c.AbortWithError(http.StatusBadRequest, err) + return + } + + sr, err := s.collector.GetCommandResult(results.GetCommandResultOptions{ + Id: params.ResultId, + Reduced: true, + }) + if err != nil { + _ = c.AbortWithError(http.StatusBadRequest, err) + return + } + if sr == nil { + c.AbortWithStatus(http.StatusNotFound) + return + } + + c.JSON(http.StatusOK, sr) +} + +func (s *CommandResultsServer) getResultObject(c *gin.Context) { + var params resultIdParam + var ref refParam + var objectType objectTypeParams + + err := c.Bind(¶ms) + if err != nil { + _ = c.AbortWithError(http.StatusBadRequest, err) + return + } + err = c.Bind(&ref) + if err != nil { + _ = c.AbortWithError(http.StatusBadRequest, err) + return + } + err = c.Bind(&objectType) + if err != nil { + _ = c.AbortWithError(http.StatusBadRequest, err) + return + } + + sr, err := s.collector.GetCommandResult(results.GetCommandResultOptions{ + Id: params.ResultId, + Reduced: false, + }) + if err != nil { + _ = c.AbortWithError(http.StatusBadRequest, err) + return + } + if sr == nil { + c.AbortWithStatus(http.StatusNotFound) + return + } + + ref2 := ref.toK8sRef() + + var found *result.ResultObject + for _, o := range sr.Objects { + if o.Ref == ref2 { + found = &o + break + } + } + if found == nil { + c.AbortWithStatus(http.StatusNotFound) + return + } + + var o2 *uo.UnstructuredObject + switch objectType.ObjectType { + case "rendered": + o2 = found.Rendered + case "remote": + o2 = found.Remote + case "applied": + o2 = found.Applied + } + if o2 == nil { + c.AbortWithStatus(http.StatusNotFound) + return + } + + c.JSON(http.StatusOK, o2) +} + +func (s *CommandResultsServer) validateNow(c *gin.Context) { + var params projectTargetKey + err := c.Bind(¶ms) + if err != nil { + _ = c.AbortWithError(http.StatusBadRequest, err) + return + } + + key := projectTargetKey{ + Project: params.Project, + Target: params.Target, + } + + if !s.vam.validateNow(key) { + _ = c.AbortWithError(http.StatusNotFound, err) + return + } + + c.Status(http.StatusOK) +} diff --git a/pkg/webui/shortnames.go b/pkg/webui/shortnames.go new file mode 100644 index 000000000..04ce897cb --- /dev/null +++ b/pkg/webui/shortnames.go @@ -0,0 +1,217 @@ +package webui + +import ( + "strings" +) + +// output of "kubectl api-resources" +const apiResourcesOutput = ` +bindings v1 true Binding +componentstatuses cs v1 false ComponentStatus +configmaps cm v1 true ConfigMap +endpoints ep v1 true Endpoints +events ev v1 true Event +limitranges limits v1 true LimitRange +namespaces ns v1 false Namespace +nodes no v1 false Node +persistentvolumeclaims pvc v1 true PersistentVolumeClaim +persistentvolumes pv v1 false PersistentVolume +pods po v1 true Pod +podtemplates v1 true PodTemplate +replicationcontrollers rc v1 true ReplicationController +resourcequotas quota v1 true ResourceQuota +secrets v1 true Secret +serviceaccounts sa v1 true ServiceAccount +services svc v1 true Service +postgresqls pg acid.zalan.do/v1 true postgresql +challenges acme.cert-manager.io/v1 true Challenge +orders acme.cert-manager.io/v1 true Order +mutatingwebhookconfigurations admissionregistration.k8s.io/v1 false MutatingWebhookConfiguration +validatingwebhookconfigurations admissionregistration.k8s.io/v1 false ValidatingWebhookConfiguration +agents agent agent.k8s.elastic.co/v1alpha1 true Agent +customresourcedefinitions crd,crds apiextensions.k8s.io/v1 false CustomResourceDefinition +apiservices apiregistration.k8s.io/v1 false APIService +apmservers apm apm.k8s.elastic.co/v1 true ApmServer +controllerrevisions apps/v1 true ControllerRevision +daemonsets ds apps/v1 true DaemonSet +deployments deploy apps/v1 true Deployment +replicasets rs apps/v1 true ReplicaSet +statefulsets sts apps/v1 true StatefulSet +tokenreviews authentication.k8s.io/v1 false TokenReview +localsubjectaccessreviews authorization.k8s.io/v1 true LocalSubjectAccessReview +selfsubjectaccessreviews authorization.k8s.io/v1 false SelfSubjectAccessReview +selfsubjectrulesreviews authorization.k8s.io/v1 false SelfSubjectRulesReview +subjectaccessreviews authorization.k8s.io/v1 false SubjectAccessReview +horizontalpodautoscalers hpa autoscaling/v2 true HorizontalPodAutoscaler +elasticsearchautoscalers esa autoscaling.k8s.elastic.co/v1alpha1 true ElasticsearchAutoscaler +cronjobs cj batch/v1 true CronJob +jobs batch/v1 true Job +beats beat beat.k8s.elastic.co/v1beta1 true Beat +sealedsecrets bitnami.com/v1alpha1 true SealedSecret +certificaterequests cr,crs cert-manager.io/v1 true CertificateRequest +certificates cert,certs cert-manager.io/v1 true Certificate +clusterissuers cert-manager.io/v1 false ClusterIssuer +issuers cert-manager.io/v1 true Issuer +certificatesigningrequests csr certificates.k8s.io/v1 false CertificateSigningRequest +ciliumclusterwidenetworkpolicies ccnp cilium.io/v2 false CiliumClusterwideNetworkPolicy +ciliumendpoints cep,ciliumep cilium.io/v2 true CiliumEndpoint +ciliumexternalworkloads cew cilium.io/v2 false CiliumExternalWorkload +ciliumidentities ciliumid cilium.io/v2 false CiliumIdentity +ciliumnetworkpolicies cnp,ciliumnp cilium.io/v2 true CiliumNetworkPolicy +ciliumnodes cn,ciliumn cilium.io/v2 false CiliumNode +configs config.gatekeeper.sh/v1alpha1 true Config +k8sallowedingressclasses constraints.gatekeeper.sh/v1beta1 false K8sAllowedIngressClasses +k8spspallowedusers constraints.gatekeeper.sh/v1beta1 false K8sPSPAllowedUsers +k8spspallowprivilegeescalationcontainer constraints.gatekeeper.sh/v1beta1 false K8sPSPAllowPrivilegeEscalationContainer +k8spspapparmor constraints.gatekeeper.sh/v1beta1 false K8sPSPAppArmor +k8spspcapabilities constraints.gatekeeper.sh/v1beta1 false K8sPSPCapabilities +k8spspflexvolumes constraints.gatekeeper.sh/v1beta1 false K8sPSPFlexVolumes +k8spspforbiddensysctls constraints.gatekeeper.sh/v1beta1 false K8sPSPForbiddenSysctls +k8spspfsgroup constraints.gatekeeper.sh/v1beta1 false K8sPSPFSGroup +k8spsphostfilesystem constraints.gatekeeper.sh/v1beta1 false K8sPSPHostFilesystem +k8spsphostnamespace constraints.gatekeeper.sh/v1beta1 false K8sPSPHostNamespace +k8spsphostnetworkingports constraints.gatekeeper.sh/v1beta1 false K8sPSPHostNetworkingPorts +k8spspprivilegedcontainer constraints.gatekeeper.sh/v1beta1 false K8sPSPPrivilegedContainer +k8spspprocmount constraints.gatekeeper.sh/v1beta1 false K8sPSPProcMount +k8spspreadonlyrootfilesystem constraints.gatekeeper.sh/v1beta1 false K8sPSPReadOnlyRootFilesystem +k8spspseccomp constraints.gatekeeper.sh/v1beta1 false K8sPSPSeccomp +k8spspselinuxv2 constraints.gatekeeper.sh/v1beta1 false K8sPSPSELinuxV2 +k8spspvolumetypes constraints.gatekeeper.sh/v1beta1 false K8sPSPVolumeTypes +leases coordination.k8s.io/v1 true Lease +strimzipodsets sps core.strimzi.io/v1beta2 true StrimziPodSet +eniconfigs crd.k8s.amazonaws.com/v1alpha1 false ENIConfig +endpointslices discovery.k8s.io/v1 true EndpointSlice +elasticsearches es elasticsearch.k8s.elastic.co/v1 true Elasticsearch +ingressclassparams elbv2.k8s.aws/v1beta1 false IngressClassParams +targetgroupbindings elbv2.k8s.aws/v1beta1 true TargetGroupBinding +enterprisesearches ent enterprisesearch.k8s.elastic.co/v1 true EnterpriseSearch +events ev events.k8s.io/v1 true Event +expansiontemplate expansion.gatekeeper.sh/v1alpha1 false ExpansionTemplate +providers externaldata.gatekeeper.sh/v1beta1 false Provider +flowschemas flowcontrol.apiserver.k8s.io/v1beta2 false FlowSchema +prioritylevelconfigurations flowcontrol.apiserver.k8s.io/v1beta2 false PriorityLevelConfiguration +kluctldeployments flux.kluctl.io/v1alpha1 true KluctlDeployment +jaegers jaegertracing.io/v1 true Jaeger +kafkabridges kb kafka.strimzi.io/v1beta2 true KafkaBridge +kafkaconnectors kctr kafka.strimzi.io/v1beta2 true KafkaConnector +kafkaconnects kc kafka.strimzi.io/v1beta2 true KafkaConnect +kafkamirrormaker2s kmm2 kafka.strimzi.io/v1beta2 true KafkaMirrorMaker2 +kafkamirrormakers kmm kafka.strimzi.io/v1beta2 true KafkaMirrorMaker +kafkarebalances kr kafka.strimzi.io/v1beta2 true KafkaRebalance +kafkas k kafka.strimzi.io/v1beta2 true Kafka +kafkatopics kt kafka.strimzi.io/v1beta2 true KafkaTopic +kafkausers ku kafka.strimzi.io/v1beta2 true KafkaUser +kibanas kb kibana.k8s.elastic.co/v1 true Kibana +elasticmapsservers ems maps.k8s.elastic.co/v1alpha1 true ElasticMapsServer +nodes metrics.k8s.io/v1beta1 false NodeMetrics +pods metrics.k8s.io/v1beta1 true PodMetrics +tenants tenant minio.min.io/v2 true Tenant +alertmanagerconfigs amcfg monitoring.coreos.com/v1alpha1 true AlertmanagerConfig +alertmanagers am monitoring.coreos.com/v1 true Alertmanager +podmonitors pmon monitoring.coreos.com/v1 true PodMonitor +probes prb monitoring.coreos.com/v1 true Probe +prometheuses prom monitoring.coreos.com/v1 true Prometheus +prometheusrules promrule monitoring.coreos.com/v1 true PrometheusRule +servicemonitors smon monitoring.coreos.com/v1 true ServiceMonitor +thanosrulers ruler monitoring.coreos.com/v1 true ThanosRuler +assign mutations.gatekeeper.sh/v1 false Assign +assignmetadata mutations.gatekeeper.sh/v1 false AssignMetadata +modifyset mutations.gatekeeper.sh/v1 false ModifySet +ingressclasses networking.k8s.io/v1 false IngressClass +ingresses ing networking.k8s.io/v1 true Ingress +networkpolicies netpol networking.k8s.io/v1 true NetworkPolicy +runtimeclasses node.k8s.io/v1 false RuntimeClass +poddisruptionbudgets pdb policy/v1 true PodDisruptionBudget +podsecuritypolicies psp policy/v1beta1 false PodSecurityPolicy +clusterrolebindings rbac.authorization.k8s.io/v1 false ClusterRoleBinding +clusterroles rbac.authorization.k8s.io/v1 false ClusterRole +rolebindings rbac.authorization.k8s.io/v1 true RoleBinding +roles rbac.authorization.k8s.io/v1 true Role +dbclusterparametergroups rds.services.k8s.aws/v1alpha1 true DBClusterParameterGroup +dbclusters rds.services.k8s.aws/v1alpha1 true DBCluster +dbinstances rds.services.k8s.aws/v1alpha1 true DBInstance +dbparametergroups rds.services.k8s.aws/v1alpha1 true DBParameterGroup +dbproxies rds.services.k8s.aws/v1alpha1 true DBProxy +dbsecuritygroups rds.services.k8s.aws/v1alpha1 true DBSecurityGroup +dbsubnetgroups rds.services.k8s.aws/v1alpha1 true DBSubnetGroup +globalclusters rds.services.k8s.aws/v1alpha1 true GlobalCluster +buckets s3.services.k8s.aws/v1alpha1 true Bucket +priorityclasses pc scheduling.k8s.io/v1 false PriorityClass +basicauths secretgenerator.mittwald.de/v1alpha1 true BasicAuth +sshkeypairs secretgenerator.mittwald.de/v1alpha1 true SSHKeyPair +stringsecrets secretgenerator.mittwald.de/v1alpha1 true StringSecret +adoptedresources services.k8s.aws/v1alpha1 true AdoptedResource +fieldexports services.k8s.aws/v1alpha1 true FieldExport +volumesnapshotclasses vsclass,vsclasses snapshot.storage.k8s.io/v1 false VolumeSnapshotClass +volumesnapshotcontents vsc,vscs snapshot.storage.k8s.io/v1 false VolumeSnapshotContent +volumesnapshots vs snapshot.storage.k8s.io/v1 true VolumeSnapshot +stackconfigpolicies scp stackconfigpolicy.k8s.elastic.co/v1alpha1 true StackConfigPolicy +constraintpodstatuses status.gatekeeper.sh/v1beta1 true ConstraintPodStatus +constrainttemplatepodstatuses status.gatekeeper.sh/v1beta1 true ConstraintTemplatePodStatus +mutatorpodstatuses status.gatekeeper.sh/v1beta1 true MutatorPodStatus +csidrivers storage.k8s.io/v1 false CSIDriver +csinodes storage.k8s.io/v1 false CSINode +csistoragecapacities storage.k8s.io/v1beta1 true CSIStorageCapacity +storageclasses sc storage.k8s.io/v1 false StorageClass +volumeattachments storage.k8s.io/v1 false VolumeAttachment +constrainttemplates templates.gatekeeper.sh/v1 false ConstraintTemplate +githubcomments templates.kluctl.io/v1alpha1 true GithubComment +gitlabcomments templates.kluctl.io/v1alpha1 true GitlabComment +gitprojectors templates.kluctl.io/v1alpha1 true GitProjector +listgithubpullrequests templates.kluctl.io/v1alpha1 true ListGithubPullRequests +listgitlabmergerequests templates.kluctl.io/v1alpha1 true ListGitlabMergeRequests +objecthandlers templates.kluctl.io/v1alpha1 true ObjectHandler +objecttemplates templates.kluctl.io/v1alpha1 true ObjectTemplate +texttemplates templates.kluctl.io/v1alpha1 true TextTemplate +backuprepositories velero.io/v1 true BackupRepository +backups velero.io/v1 true Backup +backupstoragelocations bsl velero.io/v1 true BackupStorageLocation +deletebackuprequests velero.io/v1 true DeleteBackupRequest +downloadrequests velero.io/v1 true DownloadRequest +podvolumebackups velero.io/v1 true PodVolumeBackup +podvolumerestores velero.io/v1 true PodVolumeRestore +resticrepositories velero.io/v1 true ResticRepository +restores velero.io/v1 true Restore +schedules velero.io/v1 true Schedule +serverstatusrequests ssr velero.io/v1 true ServerStatusRequest +volumesnapshotlocations vsl velero.io/v1 true VolumeSnapshotLocation +securitygrouppolicies sgp vpcresources.k8s.aws/v1beta1 true SecurityGroupPolicy +` + +type ShortName struct { + Group string `json:"group,omitempty"` + Kind string `json:"kind"` + ShortName string `json:"shortName"` +} + +var shortNames []ShortName + +func init() { + for _, l := range strings.Split(apiResourcesOutput, "\n") { + if l == "" { + continue + } + s := strings.Fields(l) + if len(s) == 5 { + sns := strings.Split(s[1], ",") + apiVersion := s[2] + kind := s[4] + + group := "" + s2 := strings.Split(apiVersion, "/") + if len(s2) == 2 { + group = s2[0] + } + + shortNames = append(shortNames, ShortName{ + Group: group, + Kind: kind, + ShortName: sns[0], + }) + } + } +} + +func GetShortNames() []ShortName { + return shortNames +} diff --git a/pkg/webui/staticbuilder.go b/pkg/webui/staticbuilder.go new file mode 100644 index 000000000..fd7f95726 --- /dev/null +++ b/pkg/webui/staticbuilder.go @@ -0,0 +1,144 @@ +package webui + +import ( + "bytes" + "context" + "fmt" + "github.com/kluctl/kluctl/v2/pkg/results" + "github.com/kluctl/kluctl/v2/pkg/types/result" + "github.com/kluctl/kluctl/v2/pkg/utils" + "github.com/kluctl/kluctl/v2/pkg/yaml" + cp "github.com/otiai10/copy" + "os" + "path/filepath" +) + +type StaticWebuiBuilder struct { + store results.ResultStore +} + +func NewStaticWebuiBuilder(store results.ResultStore) *StaticWebuiBuilder { + ret := &StaticWebuiBuilder{ + store: store, + } + return ret +} + +func (swb *StaticWebuiBuilder) Build(path string) error { + tmpDir, err := os.MkdirTemp("", "") + if err != nil { + return err + } + //defer os.RemoveAll(tmpDir) + + summaries, err := swb.store.ListCommandResultSummaries(results.ListCommandResultSummariesOptions{}) + if err != nil { + return err + } + + projects := result.BuildProjectSummaries(summaries) + + err = os.MkdirAll(filepath.Join(tmpDir, "staticdata"), 0o700) + if err != nil { + return err + } + + gh := utils.NewGoHelper(context.Background(), 8) + for _, rs := range summaries { + rs := rs + gh.RunE(func() error { + cr, err := swb.store.GetCommandResult(results.GetCommandResultOptions{ + Id: rs.Id, + }) + + var js string + + if err != nil { + jerr := struct { + Error string `json:"error"` + }{ + Error: err.Error(), + } + j, err := yaml.WriteJsonString(jerr) + if err != nil { + return err + } + js = fmt.Sprintf("staticResults.set(\"%s\", %s)\n", rs.Id, j) + } else { + j, err := yaml.WriteJsonString(cr) + if err != nil { + return err + } + js = fmt.Sprintf("staticResults.set(\"%s\", %s)\n", rs.Id, j) + } + err = os.WriteFile(filepath.Join(tmpDir, fmt.Sprintf("staticdata/result-%s.js", rs.Id)), []byte(js), 0o600) + if err != nil { + return err + } + return nil + }) + } + gh.Wait() + + j, err := yaml.WriteJsonString(summaries) + if err != nil { + return err + } + j = `const staticSummaries=` + j + err = os.WriteFile(filepath.Join(tmpDir, "staticdata/summaries.js"), []byte(j), 0o600) + if err != nil { + return err + } + + j, err = yaml.WriteJsonString(projects) + if err != nil { + return err + } + j = `const staticProjects=` + j + err = os.WriteFile(filepath.Join(tmpDir, "staticdata/projects.js"), []byte(j), 0o600) + if err != nil { + return err + } + + j, err = yaml.WriteJsonString(GetShortNames()) + if err != nil { + return err + } + j = `const staticShortNames=` + j + err = os.WriteFile(filepath.Join(tmpDir, "staticdata/shortnames.js"), []byte(j), 0o600) + if err != nil { + return err + } + + err = utils.FsCopyDir(uiFS, ".", tmpDir) + if err != nil { + return err + } + + indexHtml, err := os.ReadFile(filepath.Join(tmpDir, "index.html")) + if err != nil { + return err + } + indexHtml = bytes.ReplaceAll(indexHtml, []byte("/webui/"), []byte("./")) + err = os.WriteFile(filepath.Join(tmpDir, "index.html"), indexHtml, 0) + if err != nil { + return err + } + + staticbuildJsBytes, err := os.ReadFile(filepath.Join(tmpDir, "staticbuild.js")) + if err != nil { + return err + } + staticbuildJs := string(staticbuildJsBytes) + "\nisStaticBuild = true\n" + err = os.WriteFile(filepath.Join(tmpDir, "staticbuild.js"), []byte(staticbuildJs), 0) + if err != nil { + return err + } + + err = cp.Copy(tmpDir, path) + if err != nil { + return err + } + + return nil +} diff --git a/pkg/webui/ui/build.js b/pkg/webui/ui/build.js new file mode 100644 index 000000000..0386a1a3c --- /dev/null +++ b/pkg/webui/ui/build.js @@ -0,0 +1,5 @@ +const rewire = require('rewire'); +const defaults = rewire('react-scripts/scripts/build.js'); +const config = defaults.__get__('config'); + +config.optimization.minimize = false diff --git a/pkg/webui/ui/package-lock.json b/pkg/webui/ui/package-lock.json new file mode 100644 index 000000000..014cd172d --- /dev/null +++ b/pkg/webui/ui/package-lock.json @@ -0,0 +1,18441 @@ +{ + "name": "react-demo", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "react-demo", + "version": "0.1.0", + "dependencies": { + "@emotion/react": "^11.10.6", + "@emotion/styled": "^11.10.6", + "@fontsource/roboto": "^4.5.8", + "@mui/icons-material": "^5.11.11", + "@mui/lab": "^5.0.0-alpha.124", + "@mui/material": "^5.11.14", + "@testing-library/jest-dom": "^5.16.5", + "@testing-library/react": "^13.4.0", + "@testing-library/user-event": "^13.5.0", + "@types/jest": "^27.5.2", + "@types/node": "^16.18.18", + "@types/react": "^18.0.28", + "@types/react-dom": "^18.0.11", + "jdenticon": "^3.2.0", + "js-sha256": "^0.9.0", + "js-yaml": "^4.1.0", + "localforage": "^1.10.0", + "lodash-core": "^4.17.19", + "match-sorter": "^6.3.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router": "^6.10.0", + "react-router-dom": "^6.10.0", + "react-scripts": "5.0.1", + "react-syntax-highlighter": "^15.5.0", + "sort-by": "^1.2.0", + "typescript": "^4.9.5", + "web-vitals": "^2.1.4" + }, + "devDependencies": { + "@types/js-yaml": "^4.0.5", + "@types/lodash": "^4.14.192", + "@types/react-syntax-highlighter": "^15.5.6", + "rewire": "^6.0.0" + } + }, + "node_modules/@adobe/css-tools": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.2.0.tgz", + "integrity": "sha512-E09FiIft46CmH5Qnjb0wsW54/YQd69LsxeKUOWawmws1XWvyFGURnAChH0mlr7YPFR1ofwvUQfcL0J3lMxXqPA==" + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dependencies": { + "@babel/highlight": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.0.tgz", + "integrity": "sha512-gMuZsmsgxk/ENC3O/fRw5QY8A9/uxQbbCEypnLIiYYc/qVJtEV7ouxC3EllIIwNzMqAQee5tanFabWsUOutS7g==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.3.tgz", + "integrity": "sha512-qIJONzoa/qiHghnm0l1n4i/6IIziDpzqc36FBs4pzMhDUraHqponwJLiAKm1hGLP3OSB/TVNz6rMwVGpwxxySw==", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.21.3", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-module-transforms": "^7.21.2", + "@babel/helpers": "^7.21.0", + "@babel/parser": "^7.21.3", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.3", + "@babel/types": "^7.21.3", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/eslint-parser": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.21.3.tgz", + "integrity": "sha512-kfhmPimwo6k4P8zxNs8+T7yR44q1LdpsZdE1NkCsVlfiuTPRfnGgjaF8Qgug9q9Pou17u6wneYF0lDCZJATMFg==", + "dependencies": { + "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", + "eslint-visitor-keys": "^2.1.0", + "semver": "^6.3.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || >=14.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.11.0", + "eslint": "^7.5.0 || ^8.0.0" + } + }, + "node_modules/@babel/eslint-parser/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "engines": { + "node": ">=10" + } + }, + "node_modules/@babel/eslint-parser/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.3.tgz", + "integrity": "sha512-QS3iR1GYC/YGUnW7IdggFeN5c1poPUurnGttOV/bZgPGV+izC/D8HnD6DLwod0fsatNyVn1G3EVWMYIF0nHbeA==", + "dependencies": { + "@babel/types": "^7.21.3", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", + "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", + "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", + "dependencies": { + "@babel/helper-explode-assignable-expression": "^7.18.6", + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", + "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", + "dependencies": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-validator-option": "^7.18.6", + "browserslist": "^4.21.3", + "lru-cache": "^5.1.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.0.tgz", + "integrity": "sha512-Q8wNiMIdwsv5la5SPxNYzzkPnjgC0Sy0i7jLkVOCdllu/xcVNkr3TeZzbHBJrj+XXRqzX5uCyCoV9eu6xUG7KQ==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.21.0", + "@babel/helper-member-expression-to-functions": "^7.21.0", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-replace-supers": "^7.20.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/helper-split-export-declaration": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.21.0.tgz", + "integrity": "sha512-N+LaFW/auRSWdx7SHD/HiARwXQju1vXTW4fKr4u5SgBUTm51OKEjKgj+cs00ggW3kEvNqwErnlwuq7Y3xBe4eg==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "regexpu-core": "^5.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", + "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", + "dependencies": { + "@babel/helper-compilation-targets": "^7.17.7", + "@babel/helper-plugin-utils": "^7.16.7", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0-0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-explode-assignable-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", + "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", + "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "dependencies": { + "@babel/template": "^7.20.7", + "@babel/types": "^7.21.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.21.0.tgz", + "integrity": "sha512-Muu8cdZwNN6mRRNG6lAYErJ5X3bRevgYR2O8wN0yn7jJSnGDu6eG59RfT29JHxGUovyfrh6Pj0XzmR7drNVL3Q==", + "dependencies": { + "@babel/types": "^7.21.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.21.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz", + "integrity": "sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==", + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.20.2", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.19.1", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.2", + "@babel/types": "^7.21.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", + "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", + "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", + "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-wrap-function": "^7.18.9", + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.20.7.tgz", + "integrity": "sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A==", + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-member-expression-to-functions": "^7.20.7", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.7", + "@babel/types": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", + "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", + "dependencies": { + "@babel/types": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz", + "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==", + "dependencies": { + "@babel/types": "^7.20.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz", + "integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.20.5.tgz", + "integrity": "sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q==", + "dependencies": { + "@babel/helper-function-name": "^7.19.0", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.5", + "@babel/types": "^7.20.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.0.tgz", + "integrity": "sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==", + "dependencies": { + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.0", + "@babel/types": "^7.21.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.3.tgz", + "integrity": "sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ==", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz", + "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.20.7.tgz", + "integrity": "sha512-sbr9+wNE5aXMBBFBICk01tt7sBf2Oc9ikRFEcem/ZORup9IMUdNhW7/wVLEbbtlWOsEubJet46mHAL2C8+2jKQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/plugin-proposal-optional-chaining": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-proposal-async-generator-functions": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz", + "integrity": "sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==", + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-remap-async-to-generator": "^7.18.9", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-static-block": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.21.0.tgz", + "integrity": "sha512-XP5G9MWNUskFuP30IfFSEFB0Z6HzLIUcjYM4bYOPHXl7eiJ9HFv8tWj6TXTN5QODiEhDZAeI4hLok2iHFFV4hw==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-proposal-decorators": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.21.0.tgz", + "integrity": "sha512-MfgX49uRrFUTL/HvWtmx3zmpyzMMr4MTj3d527MLlr/4RTT9G/ytFFP7qet2uM2Ve03b+BkpWUpK+lRXnQ+v9w==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-replace-supers": "^7.20.7", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/plugin-syntax-decorators": "^7.21.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-dynamic-import": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", + "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-export-namespace-from": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", + "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-json-strings": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", + "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz", + "integrity": "sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-numeric-separator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", + "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-object-rest-spread": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", + "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", + "dependencies": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-catch-binding": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", + "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-chaining": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", + "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-methods": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", + "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0.tgz", + "integrity": "sha512-ha4zfehbJjc5MmXBlHec1igel5TJXXLDDRbuJ4+XT2TJcyD9/V1919BA8gMvsdHcNMBy4WBUBiRb3nw/EQUtBw==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-unicode-property-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", + "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.21.0.tgz", + "integrity": "sha512-tIoPpGBR8UuM4++ccWN3gifhVvQu7ZizuR1fklhRJrd5ewgbkUS+0KVFeWWxELtn18NTLoW32XV7zyOgIAiz+w==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-flow": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.18.6.tgz", + "integrity": "sha512-LUbR+KNTBWCUAqRG9ex5Gnzu2IOkt8jRJbHHXFT9q+L9zm7M/QQbEqXyw1n1pohYvOyWC8CjeyjrSaIwiYjK7A==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz", + "integrity": "sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", + "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz", + "integrity": "sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.20.7.tgz", + "integrity": "sha512-3poA5E7dzDomxj9WXWwuD6A5F3kc7VXwIJO+E+J8qtDtS+pXPAhrgEyh+9GBwBgPq1Z+bB+/JD60lp5jsN7JPQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.20.7.tgz", + "integrity": "sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q==", + "dependencies": { + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-remap-async-to-generator": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", + "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.21.0.tgz", + "integrity": "sha512-Mdrbunoh9SxwFZapeHVrwFmri16+oYotcZysSzhNIVDwIAb1UV+kvnxULSYq9J3/q5MDG+4X6w8QVgD1zhBXNQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.21.0.tgz", + "integrity": "sha512-RZhbYTCEUAe6ntPehC4hlslPWosNHDox+vAs4On/mCLRLfoDVHf6hVEd7kuxr1RnHwJmxFfUM3cZiZRmPxJPXQ==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.21.0", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-replace-supers": "^7.20.7", + "@babel/helper-split-export-declaration": "^7.18.6", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.20.7.tgz", + "integrity": "sha512-Lz7MvBK6DTjElHAmfu6bfANzKcxpyNPeYBGEafyA6E5HtRpjpZwU+u7Qrgz/2OR0z+5TvKYbPdphfSaAcZBrYQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/template": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.21.3.tgz", + "integrity": "sha512-bp6hwMFzuiE4HqYEyoGJ/V2LeIWn+hLVKc4pnj++E5XQptwhtcGmSayM029d/j2X1bPKGTlsyPwAubuU22KhMA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", + "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", + "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", + "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-flow-strip-types": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.21.0.tgz", + "integrity": "sha512-FlFA2Mj87a6sDkW4gfGrQQqwY/dLlBAyJa2dJEZ+FHXUVHBflO2wyKvg+OOEzXfrKYIa4HWl0mgmbCzt0cMb7w==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-flow": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.21.0.tgz", + "integrity": "sha512-LlUYlydgDkKpIY7mcBWvyPPmMcOphEyYA27Ef4xpbh1IiDNLr0kZsos2nf92vz3IccvJI25QUwp86Eo5s6HmBQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", + "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", + "dependencies": { + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", + "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", + "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.20.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.20.11.tgz", + "integrity": "sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g==", + "dependencies": { + "@babel/helper-module-transforms": "^7.20.11", + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.21.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.21.2.tgz", + "integrity": "sha512-Cln+Yy04Gxua7iPdj6nOV96smLGjpElir5YwzF0LBPKoPlLDNJePNlrGGaybAJkd0zKRnOVXOgizSqPYMNYkzA==", + "dependencies": { + "@babel/helper-module-transforms": "^7.21.2", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-simple-access": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.20.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.20.11.tgz", + "integrity": "sha512-vVu5g9BPQKSFEmvt2TA4Da5N+QVS66EX21d8uoOihC+OCpUoGvzVsXeqFdtAEfVa5BILAeFt+U7yVmLbQnAJmw==", + "dependencies": { + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-module-transforms": "^7.20.11", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-validator-identifier": "^7.19.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", + "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", + "dependencies": { + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.20.5.tgz", + "integrity": "sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.20.5", + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", + "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", + "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-replace-supers": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.21.3.tgz", + "integrity": "sha512-Wxc+TvppQG9xWFYatvCGPvZ6+SIUxQ2ZdiBP+PHYMIjnPXD+uThCshaz4NZOnODAtBjjcVQQ/3OKs9LW28purQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", + "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-constant-elements": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.21.3.tgz", + "integrity": "sha512-4DVcFeWe/yDYBLp0kBmOGFJ6N2UYg7coGid1gdxb4co62dy/xISDMaYBXBVXEDhfgMk7qkbcYiGtwd5Q/hwDDQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.18.6.tgz", + "integrity": "sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.21.0.tgz", + "integrity": "sha512-6OAWljMvQrZjR2DaNhVfRz6dkCAVV+ymcLUmaf8bccGOHn2v5rHJK3tTpij0BuhdYWP4LLaqj5lwcdlpAAPuvg==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-jsx": "^7.18.6", + "@babel/types": "^7.21.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.18.6.tgz", + "integrity": "sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA==", + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-pure-annotations": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.18.6.tgz", + "integrity": "sha512-I8VfEPg9r2TRDdvnHgPepTKvuRomzA8+u+nhY7qSI1fR2hRNebasZEETLyM5mAUr0Ku56OkXJ0I7NHJnO6cJiQ==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.20.5.tgz", + "integrity": "sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "regenerator-transform": "^0.15.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", + "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.21.0.tgz", + "integrity": "sha512-ReY6pxwSzEU0b3r2/T/VhqMKg/AkceBT19X0UptA3/tYi5Pe2eXgEUH+NNMC5nok6c6XQz5tyVTUpuezRfSMSg==", + "dependencies": { + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.20.2", + "babel-plugin-polyfill-corejs2": "^0.3.3", + "babel-plugin-polyfill-corejs3": "^0.6.0", + "babel-plugin-polyfill-regenerator": "^0.4.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", + "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.20.7.tgz", + "integrity": "sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", + "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", + "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", + "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.21.3.tgz", + "integrity": "sha512-RQxPz6Iqt8T0uw/WsJNReuBpWpBqs/n7mNo18sKLoTbMp+UrEekhH+pKSVC7gWz+DNjo9gryfV8YzCiT45RgMw==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-typescript": "^7.20.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", + "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", + "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.20.2.tgz", + "integrity": "sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg==", + "dependencies": { + "@babel/compat-data": "^7.20.1", + "@babel/helper-compilation-targets": "^7.20.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-validator-option": "^7.18.6", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-async-generator-functions": "^7.20.1", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-class-static-block": "^7.18.6", + "@babel/plugin-proposal-dynamic-import": "^7.18.6", + "@babel/plugin-proposal-export-namespace-from": "^7.18.9", + "@babel/plugin-proposal-json-strings": "^7.18.6", + "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", + "@babel/plugin-proposal-numeric-separator": "^7.18.6", + "@babel/plugin-proposal-object-rest-spread": "^7.20.2", + "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", + "@babel/plugin-proposal-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-private-methods": "^7.18.6", + "@babel/plugin-proposal-private-property-in-object": "^7.18.6", + "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.20.0", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-transform-arrow-functions": "^7.18.6", + "@babel/plugin-transform-async-to-generator": "^7.18.6", + "@babel/plugin-transform-block-scoped-functions": "^7.18.6", + "@babel/plugin-transform-block-scoping": "^7.20.2", + "@babel/plugin-transform-classes": "^7.20.2", + "@babel/plugin-transform-computed-properties": "^7.18.9", + "@babel/plugin-transform-destructuring": "^7.20.2", + "@babel/plugin-transform-dotall-regex": "^7.18.6", + "@babel/plugin-transform-duplicate-keys": "^7.18.9", + "@babel/plugin-transform-exponentiation-operator": "^7.18.6", + "@babel/plugin-transform-for-of": "^7.18.8", + "@babel/plugin-transform-function-name": "^7.18.9", + "@babel/plugin-transform-literals": "^7.18.9", + "@babel/plugin-transform-member-expression-literals": "^7.18.6", + "@babel/plugin-transform-modules-amd": "^7.19.6", + "@babel/plugin-transform-modules-commonjs": "^7.19.6", + "@babel/plugin-transform-modules-systemjs": "^7.19.6", + "@babel/plugin-transform-modules-umd": "^7.18.6", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.19.1", + "@babel/plugin-transform-new-target": "^7.18.6", + "@babel/plugin-transform-object-super": "^7.18.6", + "@babel/plugin-transform-parameters": "^7.20.1", + "@babel/plugin-transform-property-literals": "^7.18.6", + "@babel/plugin-transform-regenerator": "^7.18.6", + "@babel/plugin-transform-reserved-words": "^7.18.6", + "@babel/plugin-transform-shorthand-properties": "^7.18.6", + "@babel/plugin-transform-spread": "^7.19.0", + "@babel/plugin-transform-sticky-regex": "^7.18.6", + "@babel/plugin-transform-template-literals": "^7.18.9", + "@babel/plugin-transform-typeof-symbol": "^7.18.9", + "@babel/plugin-transform-unicode-escapes": "^7.18.10", + "@babel/plugin-transform-unicode-regex": "^7.18.6", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.20.2", + "babel-plugin-polyfill-corejs2": "^0.3.3", + "babel-plugin-polyfill-corejs3": "^0.6.0", + "babel-plugin-polyfill-regenerator": "^0.4.1", + "core-js-compat": "^3.25.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", + "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-react": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.18.6.tgz", + "integrity": "sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-validator-option": "^7.18.6", + "@babel/plugin-transform-react-display-name": "^7.18.6", + "@babel/plugin-transform-react-jsx": "^7.18.6", + "@babel/plugin-transform-react-jsx-development": "^7.18.6", + "@babel/plugin-transform-react-pure-annotations": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.21.0.tgz", + "integrity": "sha512-myc9mpoVA5m1rF8K8DgLEatOYFDpwC+RkMkjZ0Du6uI62YvDe8uxIEYVs/VCdSJ097nlALiU/yBC7//3nI+hNg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-validator-option": "^7.21.0", + "@babel/plugin-transform-typescript": "^7.21.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" + }, + "node_modules/@babel/runtime": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", + "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", + "dependencies": { + "regenerator-runtime": "^0.13.11" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", + "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.3.tgz", + "integrity": "sha512-XLyopNeaTancVitYZe2MlUEvgKb6YVVPXzofHgqHijCImG33b/uTurMS488ht/Hbsb2XK3U2BnSTxKVNGV3nGQ==", + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.21.3", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.21.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.21.3", + "@babel/types": "^7.21.3", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.3.tgz", + "integrity": "sha512-sBGdETxC+/M4o/zKC0sl6sjWv62WFR/uzxrJ6uYyMLZOUlPnwzw0tKgVHOXxaAd5l2g8pEDM5RZ495GPQI77kg==", + "dependencies": { + "@babel/helper-string-parser": "^7.19.4", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==" + }, + "node_modules/@csstools/normalize.css": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-12.0.0.tgz", + "integrity": "sha512-M0qqxAcwCsIVfpFQSlGN5XjXWu8l5JDZN+fPt1LeW5SZexQTgnaEvgXAY+CeygRw0EeppWHi12JxESWiWrB0Sg==" + }, + "node_modules/@csstools/postcss-cascade-layers": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-1.1.1.tgz", + "integrity": "sha512-+KdYrpKC5TgomQr2DlZF4lDEpHcoxnj5IGddYYfBWJAKfj1JtuHUIqMa+E1pJJ+z3kvDViWMqyqPlG4Ja7amQA==", + "dependencies": { + "@csstools/selector-specificity": "^2.0.2", + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-color-function": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-1.1.1.tgz", + "integrity": "sha512-Bc0f62WmHdtRDjf5f3e2STwRAl89N2CLb+9iAwzrv4L2hncrbDwnQD9PCq0gtAt7pOI2leIV08HIBUd4jxD8cw==", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-font-format-keywords": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-1.0.1.tgz", + "integrity": "sha512-ZgrlzuUAjXIOc2JueK0X5sZDjCtgimVp/O5CEqTcs5ShWBa6smhWYbS0x5cVc/+rycTDbjjzoP0KTDnUneZGOg==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-hwb-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-1.0.2.tgz", + "integrity": "sha512-YHdEru4o3Rsbjmu6vHy4UKOXZD+Rn2zmkAmLRfPet6+Jz4Ojw8cbWxe1n42VaXQhD3CQUXXTooIy8OkVbUcL+w==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-ic-unit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-1.0.1.tgz", + "integrity": "sha512-Ot1rcwRAaRHNKC9tAqoqNZhjdYBzKk1POgWfhN4uCOE47ebGcLRqXjKkApVDpjifL6u2/55ekkpnFcp+s/OZUw==", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-is-pseudo-class": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-2.0.7.tgz", + "integrity": "sha512-7JPeVVZHd+jxYdULl87lvjgvWldYu+Bc62s9vD/ED6/QTGjy0jy0US/f6BG53sVMTBJ1lzKZFpYmofBN9eaRiA==", + "dependencies": { + "@csstools/selector-specificity": "^2.0.0", + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-nested-calc": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-1.0.0.tgz", + "integrity": "sha512-JCsQsw1wjYwv1bJmgjKSoZNvf7R6+wuHDAbi5f/7MbFhl2d/+v+TvBTU4BJH3G1X1H87dHl0mh6TfYogbT/dJQ==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-normalize-display-values": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-1.0.1.tgz", + "integrity": "sha512-jcOanIbv55OFKQ3sYeFD/T0Ti7AMXc9nM1hZWu8m/2722gOTxFg7xYu4RDLJLeZmPUVQlGzo4jhzvTUq3x4ZUw==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-oklab-function": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-1.1.1.tgz", + "integrity": "sha512-nJpJgsdA3dA9y5pgyb/UfEzE7W5Ka7u0CX0/HIMVBNWzWemdcTH3XwANECU6anWv/ao4vVNLTMxhiPNZsTK6iA==", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-progressive-custom-properties": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-1.3.0.tgz", + "integrity": "sha512-ASA9W1aIy5ygskZYuWams4BzafD12ULvSypmaLJT2jvQ8G0M3I8PRQhC0h7mG0Z3LI05+agZjqSR9+K9yaQQjA==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.3" + } + }, + "node_modules/@csstools/postcss-stepped-value-functions": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-1.0.1.tgz", + "integrity": "sha512-dz0LNoo3ijpTOQqEJLY8nyaapl6umbmDcgj4AD0lgVQ572b2eqA1iGZYTTWhrcrHztWDDRAX2DGYyw2VBjvCvQ==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-text-decoration-shorthand": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-1.0.0.tgz", + "integrity": "sha512-c1XwKJ2eMIWrzQenN0XbcfzckOLLJiczqy+YvfGmzoVXd7pT9FfObiSEfzs84bpE/VqfpEuAZ9tCRbZkZxxbdw==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-trigonometric-functions": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-1.0.2.tgz", + "integrity": "sha512-woKaLO///4bb+zZC2s80l+7cm07M7268MsyG3M0ActXXEFi6SuhvriQYcb58iiKGbjwwIU7n45iRLEHypB47Og==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-unset-value": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-1.0.2.tgz", + "integrity": "sha512-c8J4roPBILnelAsdLr4XOAR/GsTm0GJi4XpcfvoWk3U6KiTCqiFYc63KhRMQQX35jYMp4Ao8Ij9+IZRgMfJp1g==", + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/selector-specificity": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.2.0.tgz", + "integrity": "sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw==", + "engines": { + "node": "^14 || ^16 || >=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss-selector-parser": "^6.0.10" + } + }, + "node_modules/@emotion/babel-plugin": { + "version": "11.10.6", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.10.6.tgz", + "integrity": "sha512-p2dAqtVrkhSa7xz1u/m9eHYdLi+en8NowrmXeF/dKtJpU8lCWli8RUAati7NcSl0afsBott48pdnANuD0wh9QQ==", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.0", + "@emotion/memoize": "^0.8.0", + "@emotion/serialize": "^1.1.1", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.1.3" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.10.5", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.10.5.tgz", + "integrity": "sha512-dGYHWyzTdmK+f2+EnIGBpkz1lKc4Zbj2KHd4cX3Wi8/OWr5pKslNjc3yABKH4adRGCvSX4VDC0i04mrrq0aiRA==", + "dependencies": { + "@emotion/memoize": "^0.8.0", + "@emotion/sheet": "^1.2.1", + "@emotion/utils": "^1.2.0", + "@emotion/weak-memoize": "^0.3.0", + "stylis": "4.1.3" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.0.tgz", + "integrity": "sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ==" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", + "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==", + "dependencies": { + "@emotion/memoize": "^0.8.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", + "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" + }, + "node_modules/@emotion/react": { + "version": "11.10.6", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.10.6.tgz", + "integrity": "sha512-6HT8jBmcSkfzO7mc+N1L9uwvOnlcGoix8Zn7srt+9ga0MjREo6lRpuVX0kzo6Jp6oTqDhREOFsygN6Ew4fEQbw==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.10.6", + "@emotion/cache": "^11.10.5", + "@emotion/serialize": "^1.1.1", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", + "@emotion/utils": "^1.2.0", + "@emotion/weak-memoize": "^0.3.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.1.tgz", + "integrity": "sha512-Zl/0LFggN7+L1liljxXdsVSVlg6E/Z/olVWpfxUTxOAmi8NU7YoeWeLfi1RmnB2TATHoaWwIBRoL+FvAJiTUQA==", + "dependencies": { + "@emotion/hash": "^0.9.0", + "@emotion/memoize": "^0.8.0", + "@emotion/unitless": "^0.8.0", + "@emotion/utils": "^1.2.0", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.1.tgz", + "integrity": "sha512-zxRBwl93sHMsOj4zs+OslQKg/uhF38MB+OMKoCrVuS0nyTkqnau+BM3WGEoOptg9Oz45T/aIGs1qbVAsEFo3nA==" + }, + "node_modules/@emotion/styled": { + "version": "11.10.6", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.10.6.tgz", + "integrity": "sha512-OXtBzOmDSJo5Q0AFemHCfl+bUueT8BIcPSxu0EGTpGk6DmI5dnhSzQANm1e1ze0YZL7TDyAyy6s/b/zmGOS3Og==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.10.6", + "@emotion/is-prop-valid": "^1.2.0", + "@emotion/serialize": "^1.1.1", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", + "@emotion/utils": "^1.2.0" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz", + "integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.0.tgz", + "integrity": "sha512-1eEgUGmkaljiBnRMTdksDV1W4kUnmwgp7X9G8B++9GYwl1lUdqSndSriIrTJ0N7LQaoauY9JJ2yhiOYK5+NI4A==", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.0.tgz", + "integrity": "sha512-sn3WH53Kzpw8oQ5mgMmIzzyAaH2ZqFEbozVVBSYp538E06OSE6ytOp7pRAjNQR+Q/orwqdQYJSe2m3hCOeznkw==" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz", + "integrity": "sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg==" + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.3.0.tgz", + "integrity": "sha512-v3oplH6FYCULtFuCeqyuTd9D2WKO937Dxdq+GmHOLL72TTRriLxz2VLlNfkZRsvj6PKnOPAtuT6dwrs/pA5DvA==", + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.4.0.tgz", + "integrity": "sha512-A9983Q0LnDGdLPjxyXQ00sbV+K+O+ko2Dr+CZigbHWtX9pNfxlaBkMR8X1CztI73zuEyEBXTVjx7CE+/VSwDiQ==", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.1.tgz", + "integrity": "sha512-eFRmABvW2E5Ho6f5fHLqgena46rOj7r7OKHYfLElqcBfGFHHpjBhivyi5+jOEQuSpdc/1phIZJlbC2te+tZNIw==", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.5.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "8.36.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.36.0.tgz", + "integrity": "sha512-lxJ9R5ygVm8ZWgYdUweoq5ownDlJ4upvoWmO4eLxBYHdMo+vZ/Rx0EN6MbKWDJOSUGrqJy2Gt+Dyv/VKml0fjg==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@fontsource/roboto": { + "version": "4.5.8", + "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-4.5.8.tgz", + "integrity": "sha512-CnD7zLItIzt86q4Sj3kZUiLcBk1dSk81qcqgMGaZe7SQ1P8hFNxhMl5AZthK1zrDM5m74VVhaOpuMGIL4gagaA==" + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.5.1.tgz", + "integrity": "sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg==", + "dependencies": { + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^27.5.1", + "jest-util": "^27.5.1", + "slash": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/console/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/console/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/console/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/console/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/@jest/console/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/core": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.5.1.tgz", + "integrity": "sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ==", + "dependencies": { + "@jest/console": "^27.5.1", + "@jest/reporters": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.8.1", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^27.5.1", + "jest-config": "^27.5.1", + "jest-haste-map": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-resolve-dependencies": "^27.5.1", + "jest-runner": "^27.5.1", + "jest-runtime": "^27.5.1", + "jest-snapshot": "^27.5.1", + "jest-util": "^27.5.1", + "jest-validate": "^27.5.1", + "jest-watcher": "^27.5.1", + "micromatch": "^4.0.4", + "rimraf": "^3.0.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/core/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/core/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/core/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/core/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/@jest/core/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/core/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/environment": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.5.1.tgz", + "integrity": "sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==", + "dependencies": { + "@jest/fake-timers": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "jest-mock": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz", + "integrity": "sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ==", + "dependencies": { + "@jest/types": "^27.5.1", + "@sinonjs/fake-timers": "^8.0.1", + "@types/node": "*", + "jest-message-util": "^27.5.1", + "jest-mock": "^27.5.1", + "jest-util": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-27.5.1.tgz", + "integrity": "sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q==", + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/types": "^27.5.1", + "expect": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-27.5.1.tgz", + "integrity": "sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw==", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.2", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-haste-map": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-util": "^27.5.1", + "jest-worker": "^27.5.1", + "slash": "^3.0.0", + "source-map": "^0.6.0", + "string-length": "^4.0.1", + "terminal-link": "^2.0.0", + "v8-to-istanbul": "^8.1.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/reporters/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/reporters/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/@jest/reporters/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/reporters/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@jest/reporters/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/schemas": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", + "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "dependencies": { + "@sinclair/typebox": "^0.24.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.5.1.tgz", + "integrity": "sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg==", + "dependencies": { + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9", + "source-map": "^0.6.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/source-map/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@jest/test-result": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.5.1.tgz", + "integrity": "sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag==", + "dependencies": { + "@jest/console": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.5.1.tgz", + "integrity": "sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ==", + "dependencies": { + "@jest/test-result": "^27.5.1", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^27.5.1", + "jest-runtime": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.5.1.tgz", + "integrity": "sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw==", + "dependencies": { + "@babel/core": "^7.1.0", + "@jest/types": "^27.5.1", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-util": "^27.5.1", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "source-map": "^0.6.1", + "write-file-atomic": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/transform/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/transform/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/transform/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/transform/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/@jest/transform/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/transform/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@jest/transform/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/types/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/types/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/types/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/types/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/@jest/types/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/types/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dependencies": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", + "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/source-map/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", + "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" + }, + "node_modules/@mui/base": { + "version": "5.0.0-alpha.122", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.122.tgz", + "integrity": "sha512-IgZEFQyHa39J1+Q3tekVdhPuUm1fr3icddaNLmiAIeYTVXmR7KR5FhBAIL0P+4shlPq0liUPGlXryoTm0iCeFg==", + "dependencies": { + "@babel/runtime": "^7.21.0", + "@emotion/is-prop-valid": "^1.2.0", + "@mui/types": "^7.2.3", + "@mui/utils": "^5.11.13", + "@popperjs/core": "^2.11.6", + "clsx": "^1.2.1", + "prop-types": "^15.8.1", + "react-is": "^18.2.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/base/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, + "node_modules/@mui/core-downloads-tracker": { + "version": "5.11.14", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.11.14.tgz", + "integrity": "sha512-rfc08z6+3Fif+Gopx2/qmk+MEQlwYeA+gOcSK048BHkTty/ol/boHuVeL2BNC/cf9OVRjJLYHtVb/DeW791LSQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + } + }, + "node_modules/@mui/icons-material": { + "version": "5.11.11", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.11.11.tgz", + "integrity": "sha512-Eell3ADmQVE8HOpt/LZ3zIma8JSvPh3XgnhwZLT0k5HRqZcd6F/QDHc7xsWtgz09t+UEFvOYJXjtrwKmLdwwpw==", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@mui/material": "^5.0.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/lab": { + "version": "5.0.0-alpha.124", + "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-5.0.0-alpha.124.tgz", + "integrity": "sha512-K/XEv1zYyLi3tS63tyDta1mqWql+at5w7rWp5Yd63Jx1NjPUtgopAvyRZG2SVYPs/yBwfyDPW1iqQXpRw8h9Xw==", + "dependencies": { + "@babel/runtime": "^7.21.0", + "@mui/base": "5.0.0-alpha.122", + "@mui/system": "^5.11.14", + "@mui/types": "^7.2.3", + "@mui/utils": "^5.11.13", + "clsx": "^1.2.1", + "prop-types": "^15.8.1", + "react-is": "^18.2.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material": "^5.0.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/lab/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, + "node_modules/@mui/material": { + "version": "5.11.14", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.11.14.tgz", + "integrity": "sha512-uoiUyybmo+M+nYARBygmbXgX6s/hH0NKD56LCAv9XvmdGVoXhEGjOvxI5/Bng6FS3NNybnA8V+rgZW1Z/9OJtA==", + "dependencies": { + "@babel/runtime": "^7.21.0", + "@mui/base": "5.0.0-alpha.122", + "@mui/core-downloads-tracker": "^5.11.14", + "@mui/system": "^5.11.14", + "@mui/types": "^7.2.3", + "@mui/utils": "^5.11.13", + "@types/react-transition-group": "^4.4.5", + "clsx": "^1.2.1", + "csstype": "^3.1.1", + "prop-types": "^15.8.1", + "react-is": "^18.2.0", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, + "node_modules/@mui/private-theming": { + "version": "5.11.13", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.11.13.tgz", + "integrity": "sha512-PJnYNKzW5LIx3R+Zsp6WZVPs6w5sEKJ7mgLNnUXuYB1zo5aX71FVLtV7geyPXRcaN2tsoRNK7h444ED0t7cIjA==", + "dependencies": { + "@babel/runtime": "^7.21.0", + "@mui/utils": "^5.11.13", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "5.11.11", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.11.11.tgz", + "integrity": "sha512-wV0UgW4lN5FkDBXefN8eTYeuE9sjyQdg5h94vtwZCUamGQEzmCOtir4AakgmbWMy0x8OLjdEUESn9wnf5J9MOg==", + "dependencies": { + "@babel/runtime": "^7.21.0", + "@emotion/cache": "^11.10.5", + "csstype": "^3.1.1", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "5.11.14", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.11.14.tgz", + "integrity": "sha512-/MBv5dUoijJNEKEGi5tppIszGN0o2uejmeISi5vl0CLcaQsI1cd+uBgK+JYUP1VWvI/MtkWRLVSWtF2FWhu5Nw==", + "dependencies": { + "@babel/runtime": "^7.21.0", + "@mui/private-theming": "^5.11.13", + "@mui/styled-engine": "^5.11.11", + "@mui/types": "^7.2.3", + "@mui/utils": "^5.11.13", + "clsx": "^1.2.1", + "csstype": "^3.1.1", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.3.tgz", + "integrity": "sha512-tZ+CQggbe9Ol7e/Fs5RcKwg/woU+o8DCtOnccX6KmbBc7YrfqMYEYuaIcXHuhpT880QwNkZZ3wQwvtlDFA2yOw==", + "peerDependencies": { + "@types/react": "*" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "5.11.13", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.11.13.tgz", + "integrity": "sha512-5ltA58MM9euOuUcnvwFJqpLdEugc9XFsRR8Gt4zZNb31XzMfSKJPR4eumulyhsOTK1rWf7K4D63NKFPfX0AxqA==", + "dependencies": { + "@babel/runtime": "^7.21.0", + "@types/prop-types": "^15.7.5", + "@types/react-is": "^16.7.1 || ^17.0.0", + "prop-types": "^15.8.1", + "react-is": "^18.2.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0" + } + }, + "node_modules/@mui/utils/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { + "version": "5.1.1-v1", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", + "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", + "dependencies": { + "eslint-scope": "5.1.1" + } + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pmmmwh/react-refresh-webpack-plugin": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.10.tgz", + "integrity": "sha512-j0Ya0hCFZPd4x40qLzbhGsh9TMtdb+CJQiso+WxLOPNasohq9cc5SNUcwsZaRH6++Xh91Xkm/xHCkuIiIu0LUA==", + "dependencies": { + "ansi-html-community": "^0.0.8", + "common-path-prefix": "^3.0.0", + "core-js-pure": "^3.23.3", + "error-stack-parser": "^2.0.6", + "find-up": "^5.0.0", + "html-entities": "^2.1.0", + "loader-utils": "^2.0.4", + "schema-utils": "^3.0.0", + "source-map": "^0.7.3" + }, + "engines": { + "node": ">= 10.13" + }, + "peerDependencies": { + "@types/webpack": "4.x || 5.x", + "react-refresh": ">=0.10.0 <1.0.0", + "sockjs-client": "^1.4.0", + "type-fest": ">=0.17.0 <4.0.0", + "webpack": ">=4.43.0 <6.0.0", + "webpack-dev-server": "3.x || 4.x", + "webpack-hot-middleware": "2.x", + "webpack-plugin-serve": "0.x || 1.x" + }, + "peerDependenciesMeta": { + "@types/webpack": { + "optional": true + }, + "sockjs-client": { + "optional": true + }, + "type-fest": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + }, + "webpack-hot-middleware": { + "optional": true + }, + "webpack-plugin-serve": { + "optional": true + } + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.6", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", + "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@remix-run/router": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.5.0.tgz", + "integrity": "sha512-bkUDCp8o1MvFO+qxkODcbhSqRa6P2GXgrGZVpt0dCXNW2HCSCqYI0ZoAqEOSAjRWmmlKcYgFvN4B4S+zo/f8kg==", + "engines": { + "node": ">=14" + } + }, + "node_modules/@rollup/plugin-babel": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", + "integrity": "sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==", + "dependencies": { + "@babel/helper-module-imports": "^7.10.4", + "@rollup/pluginutils": "^3.1.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "@types/babel__core": "^7.1.9", + "rollup": "^1.20.0||^2.0.0" + }, + "peerDependenciesMeta": { + "@types/babel__core": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "11.2.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.1.tgz", + "integrity": "sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg==", + "dependencies": { + "@rollup/pluginutils": "^3.1.0", + "@types/resolve": "1.17.1", + "builtin-modules": "^3.1.0", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0" + } + }, + "node_modules/@rollup/plugin-replace": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.4.2.tgz", + "integrity": "sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==", + "dependencies": { + "@rollup/pluginutils": "^3.1.0", + "magic-string": "^0.25.7" + }, + "peerDependencies": { + "rollup": "^1.20.0 || ^2.0.0" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", + "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "dependencies": { + "@types/estree": "0.0.39", + "estree-walker": "^1.0.1", + "picomatch": "^2.2.2" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0" + } + }, + "node_modules/@rollup/pluginutils/node_modules/@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==" + }, + "node_modules/@rushstack/eslint-patch": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz", + "integrity": "sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==" + }, + "node_modules/@sinclair/typebox": { + "version": "0.24.51", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", + "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==" + }, + "node_modules/@sinonjs/commons": { + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", + "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", + "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, + "node_modules/@surma/rollup-plugin-off-main-thread": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", + "integrity": "sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==", + "dependencies": { + "ejs": "^3.1.6", + "json5": "^2.2.0", + "magic-string": "^0.25.0", + "string.prototype.matchall": "^4.0.6" + } + }, + "node_modules/@svgr/babel-plugin-add-jsx-attribute": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz", + "integrity": "sha512-ZFf2gs/8/6B8PnSofI0inYXr2SDNTDScPXhN7k5EqD4aZ3gi6u+rbmZHVB8IM3wDyx8ntKACZbtXSm7oZGRqVg==", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-5.4.0.tgz", + "integrity": "sha512-yaS4o2PgUtwLFGTKbsiAy6D0o3ugcUhWK0Z45umJ66EPWunAz9fuFw2gJuje6wqQvQWOTJvIahUwndOXb7QCPg==", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-5.0.1.tgz", + "integrity": "sha512-LA72+88A11ND/yFIMzyuLRSMJ+tRKeYKeQ+mR3DcAZ5I4h5CPWN9AHyUzJbWSYp/u2u0xhmgOe0+E41+GjEueA==", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-5.0.1.tgz", + "integrity": "sha512-PoiE6ZD2Eiy5mK+fjHqwGOS+IXX0wq/YDtNyIgOrc6ejFnxN4b13pRpiIPbtPwHEc+NT2KCjteAcq33/F1Y9KQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-svg-dynamic-title": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-5.4.0.tgz", + "integrity": "sha512-zSOZH8PdZOpuG1ZVx/cLVePB2ibo3WPpqo7gFIjLV9a0QsuQAzJiwwqmuEdTaW2pegyBE17Uu15mOgOcgabQZg==", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-svg-em-dimensions": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-5.4.0.tgz", + "integrity": "sha512-cPzDbDA5oT/sPXDCUYoVXEmm3VIoAWAPT6mSPTJNbQaBNUuEKVKyGH93oDY4e42PYHRW67N5alJx/eEol20abw==", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-transform-react-native-svg": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-5.4.0.tgz", + "integrity": "sha512-3eYP/SaopZ41GHwXma7Rmxcv9uRslRDTY1estspeB1w1ueZWd/tPlMfEOoccYpEMZU3jD4OU7YitnXcF5hLW2Q==", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-transform-svg-component": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-5.5.0.tgz", + "integrity": "sha512-q4jSH1UUvbrsOtlo/tKcgSeiCHRSBdXoIoqX1pgcKK/aU3JD27wmMKwGtpB8qRYUYoyXvfGxUVKchLuR5pB3rQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-preset": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-5.5.0.tgz", + "integrity": "sha512-4FiXBjvQ+z2j7yASeGPEi8VD/5rrGQk4Xrq3EdJmoZgz/tpqChpo5hgXDvmEauwtvOc52q8ghhZK4Oy7qph4ig==", + "dependencies": { + "@svgr/babel-plugin-add-jsx-attribute": "^5.4.0", + "@svgr/babel-plugin-remove-jsx-attribute": "^5.4.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "^5.0.1", + "@svgr/babel-plugin-replace-jsx-attribute-value": "^5.0.1", + "@svgr/babel-plugin-svg-dynamic-title": "^5.4.0", + "@svgr/babel-plugin-svg-em-dimensions": "^5.4.0", + "@svgr/babel-plugin-transform-react-native-svg": "^5.4.0", + "@svgr/babel-plugin-transform-svg-component": "^5.5.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/core": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-5.5.0.tgz", + "integrity": "sha512-q52VOcsJPvV3jO1wkPtzTuKlvX7Y3xIcWRpCMtBF3MrteZJtBfQw/+u0B1BHy5ColpQc1/YVTrPEtSYIMNZlrQ==", + "dependencies": { + "@svgr/plugin-jsx": "^5.5.0", + "camelcase": "^6.2.0", + "cosmiconfig": "^7.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/hast-util-to-babel-ast": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-5.5.0.tgz", + "integrity": "sha512-cAaR/CAiZRB8GP32N+1jocovUtvlj0+e65TB50/6Lcime+EA49m/8l+P2ko+XPJ4dw3xaPS3jOL4F2X4KWxoeQ==", + "dependencies": { + "@babel/types": "^7.12.6" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/plugin-jsx": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-5.5.0.tgz", + "integrity": "sha512-V/wVh33j12hGh05IDg8GpIUXbjAPnTdPTKuP4VNLggnwaHMPNQNae2pRnyTAILWCQdz5GyMqtO488g7CKM8CBA==", + "dependencies": { + "@babel/core": "^7.12.3", + "@svgr/babel-preset": "^5.5.0", + "@svgr/hast-util-to-babel-ast": "^5.5.0", + "svg-parser": "^2.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/plugin-svgo": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-5.5.0.tgz", + "integrity": "sha512-r5swKk46GuQl4RrVejVwpeeJaydoxkdwkM1mBKOgJLBUJPGaLci6ylg/IjhrRsREKDkr4kbMWdgOtbXEh0fyLQ==", + "dependencies": { + "cosmiconfig": "^7.0.0", + "deepmerge": "^4.2.2", + "svgo": "^1.2.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/webpack": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-5.5.0.tgz", + "integrity": "sha512-DOBOK255wfQxguUta2INKkzPj6AIS6iafZYiYmHn6W3pHlycSRRlvWKCfLDG10fXfLWqE3DJHgRUOyJYmARa7g==", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/plugin-transform-react-constant-elements": "^7.12.1", + "@babel/preset-env": "^7.12.1", + "@babel/preset-react": "^7.12.5", + "@svgr/core": "^5.5.0", + "@svgr/plugin-jsx": "^5.5.0", + "@svgr/plugin-svgo": "^5.5.0", + "loader-utils": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@testing-library/dom": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.0.1.tgz", + "integrity": "sha512-fTOVsMY9QLFCCXRHG3Ese6cMH5qIWwSbgxZsgeF5TNsy81HKaZ4kgehnSF8FsR3OF+numlIV2YcU79MzbnhSig==", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "^5.0.0", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@testing-library/dom/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "peer": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@testing-library/dom/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@testing-library/dom/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "peer": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@testing-library/dom/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "peer": true + }, + "node_modules/@testing-library/dom/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/dom/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "5.16.5", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz", + "integrity": "sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA==", + "dependencies": { + "@adobe/css-tools": "^4.0.1", + "@babel/runtime": "^7.9.2", + "@types/testing-library__jest-dom": "^5.9.1", + "aria-query": "^5.0.0", + "chalk": "^3.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.5.6", + "lodash": "^4.17.15", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=8", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/@testing-library/jest-dom/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/react": { + "version": "13.4.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-13.4.0.tgz", + "integrity": "sha512-sXOGON+WNTh3MLE9rve97ftaZukN3oNf2KjDy7YTx6hcTO2uuLHuCGynMDhFwGw/jYf4OJ2Qk0i4i79qMNNkyw==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@testing-library/dom": "^8.5.0", + "@types/react-dom": "^18.0.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/@testing-library/react/node_modules/@testing-library/dom": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.0.tgz", + "integrity": "sha512-d9ULIT+a4EXLX3UU8FBjauG9NnsZHkHztXoIcTsOKoOw030fyjheN9svkTULjJxtYag9DZz5Jz5qkWZDPxTFwA==", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "^5.0.0", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.4.4", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@testing-library/react/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@testing-library/react/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@testing-library/react/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@testing-library/react/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/@testing-library/react/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/react/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/user-event": { + "version": "13.5.0", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-13.5.0.tgz", + "integrity": "sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg==", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, + "node_modules/@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.1.tgz", + "integrity": "sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q==" + }, + "node_modules/@types/babel__core": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz", + "integrity": "sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.3.tgz", + "integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==", + "dependencies": { + "@babel/types": "^7.3.0" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz", + "integrity": "sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz", + "integrity": "sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==", + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/eslint": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.21.3.tgz", + "integrity": "sha512-fa7GkppZVEByMWGbTtE5MbmXWJTVbrjjaS8K6uQj+XtuuUv1fsuPAxhygfqLmsb/Ufb3CV8deFCpiMfAgi00Sw==", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", + "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", + "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==" + }, + "node_modules/@types/express": { + "version": "4.17.17", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", + "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.33", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.33.tgz", + "integrity": "sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", + "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/hast": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.4.tgz", + "integrity": "sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==" + }, + "node_modules/@types/http-proxy": { + "version": "1.17.10", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.10.tgz", + "integrity": "sha512-Qs5aULi+zV1bwKAg5z1PWnDXWmsn+LxIvUGv6E2+OOMYhclZMO+OXd9pYVf2gLykf2I7IV2u7oTHwChPNsvJ7g==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "27.5.2", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.5.2.tgz", + "integrity": "sha512-mpT8LJJ4CMeeahobofYWIjFo0xonRS/HfxnVEPMPFSQdGUt1uHCnoPT7Zhb+sjDU2wz0oKV0OLUR0WzrHNgfeA==", + "dependencies": { + "jest-matcher-utils": "^27.0.0", + "pretty-format": "^27.0.0" + } + }, + "node_modules/@types/js-yaml": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz", + "integrity": "sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" + }, + "node_modules/@types/lodash": { + "version": "4.14.192", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.192.tgz", + "integrity": "sha512-km+Vyn3BYm5ytMO13k9KTp27O75rbQ0NFw+U//g+PX7VZyjCioXaRFisqSIJRECljcTv73G3i6BpglNGHgUQ5A==", + "dev": true + }, + "node_modules/@types/mime": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", + "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==" + }, + "node_modules/@types/node": { + "version": "16.18.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.18.tgz", + "integrity": "sha512-fwGw1uvQAzabxL1pyoknPlJIF2t7+K90uTqynleKRx24n3lYcxWa3+KByLhgkF8GEAK2c7hC8Ki0RkNM5H15jQ==" + }, + "node_modules/@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" + }, + "node_modules/@types/prettier": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", + "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==" + }, + "node_modules/@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + }, + "node_modules/@types/q": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.5.tgz", + "integrity": "sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ==" + }, + "node_modules/@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" + }, + "node_modules/@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" + }, + "node_modules/@types/react": { + "version": "18.0.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.28.tgz", + "integrity": "sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.0.11", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.11.tgz", + "integrity": "sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react-is": { + "version": "17.0.3", + "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-17.0.3.tgz", + "integrity": "sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react-syntax-highlighter": { + "version": "15.5.6", + "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.6.tgz", + "integrity": "sha512-i7wFuLbIAFlabTeD2I1cLjEOrG/xdMa/rpx2zwzAoGHuXJDhSqp9BSfDlMHSh9JSuNfxHk9eEmMX6D55GiyjGg==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/resolve": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", + "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" + }, + "node_modules/@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" + }, + "node_modules/@types/semver": { + "version": "7.3.13", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", + "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==" + }, + "node_modules/@types/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.1.tgz", + "integrity": "sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==", + "dependencies": { + "@types/mime": "*", + "@types/node": "*" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.33", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", + "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==" + }, + "node_modules/@types/testing-library__jest-dom": { + "version": "5.14.5", + "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.5.tgz", + "integrity": "sha512-SBwbxYoyPIvxHbeHxTZX2Pe/74F/tX2/D3mMvzabdeJ25bBojfW0TyB8BHrbq/9zaaKICJZjLP+8r6AeZMFCuQ==", + "dependencies": { + "@types/jest": "*" + } + }, + "node_modules/@types/trusted-types": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.3.tgz", + "integrity": "sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g==" + }, + "node_modules/@types/unist": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", + "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==" + }, + "node_modules/@types/ws": { + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", + "integrity": "sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yargs": { + "version": "16.0.5", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.5.tgz", + "integrity": "sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.56.0.tgz", + "integrity": "sha512-ZNW37Ccl3oMZkzxrYDUX4o7cnuPgU+YrcaYXzsRtLB16I1FR5SHMqga3zGsaSliZADCWo2v8qHWqAYIj8nWCCg==", + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.56.0", + "@typescript-eslint/type-utils": "5.56.0", + "@typescript-eslint/utils": "5.56.0", + "debug": "^4.3.4", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/experimental-utils": { + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.56.0.tgz", + "integrity": "sha512-sxWuj0eO5nItmKgZmsBbChVt90EhfkuncDCPbLAVeEJ+SCjXMcZN3AhhNbxed7IeGJ4XwsdL3/FMvD4r+FLqqA==", + "dependencies": { + "@typescript-eslint/utils": "5.56.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.56.0.tgz", + "integrity": "sha512-sn1OZmBxUsgxMmR8a8U5QM/Wl+tyqlH//jTqCg8daTAmhAk26L2PFhcqPLlYBhYUJMZJK276qLXlHN3a83o2cg==", + "dependencies": { + "@typescript-eslint/scope-manager": "5.56.0", + "@typescript-eslint/types": "5.56.0", + "@typescript-eslint/typescript-estree": "5.56.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.56.0.tgz", + "integrity": "sha512-jGYKyt+iBakD0SA5Ww8vFqGpoV2asSjwt60Gl6YcO8ksQ8s2HlUEyHBMSa38bdLopYqGf7EYQMUIGdT/Luw+sw==", + "dependencies": { + "@typescript-eslint/types": "5.56.0", + "@typescript-eslint/visitor-keys": "5.56.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.56.0.tgz", + "integrity": "sha512-8WxgOgJjWRy6m4xg9KoSHPzBNZeQbGlQOH7l2QEhQID/+YseaFxg5J/DLwWSsi9Axj4e/cCiKx7PVzOq38tY4A==", + "dependencies": { + "@typescript-eslint/typescript-estree": "5.56.0", + "@typescript-eslint/utils": "5.56.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.56.0.tgz", + "integrity": "sha512-JyAzbTJcIyhuUhogmiu+t79AkdnqgPUEsxMTMc/dCZczGMJQh1MK2wgrju++yMN6AWroVAy2jxyPcPr3SWCq5w==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.56.0.tgz", + "integrity": "sha512-41CH/GncsLXOJi0jb74SnC7jVPWeVJ0pxQj8bOjH1h2O26jXN3YHKDT1ejkVz5YeTEQPeLCCRY0U2r68tfNOcg==", + "dependencies": { + "@typescript-eslint/types": "5.56.0", + "@typescript-eslint/visitor-keys": "5.56.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.56.0.tgz", + "integrity": "sha512-XhZDVdLnUJNtbzaJeDSCIYaM+Tgr59gZGbFuELgF7m0IY03PlciidS7UQNKLE0+WpUTn1GlycEr6Ivb/afjbhA==", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.56.0", + "@typescript-eslint/types": "5.56.0", + "@typescript-eslint/typescript-estree": "5.56.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.56.0.tgz", + "integrity": "sha512-1mFdED7u5bZpX6Xxf5N9U2c18sb+8EvU3tyOIj6LQZ5OOvnmj8BVeNNP603OFPm5KkS1a7IvCIcwrdHXaEMG/Q==", + "dependencies": { + "@typescript-eslint/types": "5.56.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", + "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", + "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", + "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", + "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", + "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", + "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", + "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", + "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", + "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", + "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", + "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/helper-wasm-section": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-opt": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "@webassemblyjs/wast-printer": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", + "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", + "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", + "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", + "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" + }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "dependencies": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" + } + }, + "node_modules/acorn-globals/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-assertions": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", + "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-node": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", + "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", + "dependencies": { + "acorn": "^7.0.0", + "acorn-walk": "^7.0.0", + "xtend": "^4.0.2" + } + }, + "node_modules/acorn-node/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/address": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/address/-/address-1.2.2.tgz", + "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/adjust-sourcemap-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", + "integrity": "sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==", + "dependencies": { + "loader-utils": "^2.0.0", + "regex-parser": "^2.2.11" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "engines": [ + "node >= 0.8.0" + ], + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/aria-query": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dependencies": { + "deep-equal": "^2.0.5" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dependencies": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==" + }, + "node_modules/array-includes": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", + "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", + "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", + "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.reduce": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.5.tgz", + "integrity": "sha512-kDdugMl7id9COE8R7MHF5jWk7Dqt/fs4Pv+JXoICnYwqpjjjbUurz6w5fT5IG6brLdJhv6/VoHB0H7oyIBXd+Q==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz", + "integrity": "sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.1.3" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" + }, + "node_modules/ast-types-flow": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", + "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==" + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.14", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz", + "integrity": "sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + } + ], + "dependencies": { + "browserslist": "^4.21.5", + "caniuse-lite": "^1.0.30001464", + "fraction.js": "^4.2.0", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.6.3.tgz", + "integrity": "sha512-/BQzOX780JhsxDnPpH4ZiyrJAzcd8AfzFPkv+89veFSr1rcMjuq2JDCwypKaPeB6ljHp9KjXhPpjgCvQlWYuqg==", + "engines": { + "node": ">=4" + } + }, + "node_modules/axobject-query": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz", + "integrity": "sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==", + "dependencies": { + "deep-equal": "^2.0.5" + } + }, + "node_modules/babel-jest": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz", + "integrity": "sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==", + "dependencies": { + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^27.5.1", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-jest/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/babel-jest/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/babel-jest/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/babel-jest/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/babel-jest/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-jest/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-loader": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.3.0.tgz", + "integrity": "sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q==", + "dependencies": { + "find-cache-dir": "^3.3.1", + "loader-utils": "^2.0.0", + "make-dir": "^3.1.0", + "schema-utils": "^2.6.5" + }, + "engines": { + "node": ">= 8.9" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "webpack": ">=2" + } + }, + "node_modules/babel-loader/node_modules/schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dependencies": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz", + "integrity": "sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ==", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.0.0", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/babel-plugin-named-asset-import": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.8.tgz", + "integrity": "sha512-WXiAc++qo7XcJ1ZnTYGtLxmBCVbddAml3CEXgWaBzNzLNoxtQ8AiGEFDMOhot9XjTCQbvP5E77Fj9Gk924f00Q==", + "peerDependencies": { + "@babel/core": "^7.1.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", + "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", + "dependencies": { + "@babel/compat-data": "^7.17.7", + "@babel/helper-define-polyfill-provider": "^0.3.3", + "semver": "^6.1.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz", + "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.3.3", + "core-js-compat": "^3.25.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", + "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.3.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-transform-react-remove-prop-types": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", + "integrity": "sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA==" + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz", + "integrity": "sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag==", + "dependencies": { + "babel-plugin-jest-hoist": "^27.5.1", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-react-app": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-react-app/-/babel-preset-react-app-10.0.1.tgz", + "integrity": "sha512-b0D9IZ1WhhCWkrTXyFuIIgqGzSkRIH5D5AmB0bXbzYAB1OBAwHcUeyWW2LorutLWF5btNo/N7r/cIdmvvKJlYg==", + "dependencies": { + "@babel/core": "^7.16.0", + "@babel/plugin-proposal-class-properties": "^7.16.0", + "@babel/plugin-proposal-decorators": "^7.16.4", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.0", + "@babel/plugin-proposal-numeric-separator": "^7.16.0", + "@babel/plugin-proposal-optional-chaining": "^7.16.0", + "@babel/plugin-proposal-private-methods": "^7.16.0", + "@babel/plugin-transform-flow-strip-types": "^7.16.0", + "@babel/plugin-transform-react-display-name": "^7.16.0", + "@babel/plugin-transform-runtime": "^7.16.4", + "@babel/preset-env": "^7.16.4", + "@babel/preset-react": "^7.16.0", + "@babel/preset-typescript": "^7.16.0", + "@babel/runtime": "^7.16.3", + "babel-plugin-macros": "^3.1.0", + "babel-plugin-transform-react-remove-prop-types": "^0.4.24" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==" + }, + "node_modules/bfj": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/bfj/-/bfj-7.0.2.tgz", + "integrity": "sha512-+e/UqUzwmzJamNF50tBV6tZPTORow7gQ96iFow+8b562OdMpEK0BcJEq2OSPEDmAbSMBQ7PKZ87ubFkgxpYWgw==", + "dependencies": { + "bluebird": "^3.5.5", + "check-types": "^11.1.1", + "hoopy": "^0.1.4", + "tryer": "^1.0.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, + "node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/bonjour-service": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.1.1.tgz", + "integrity": "sha512-Z/5lQRMOG9k7W+FkeGTNjh7htqn/2LMnfOvBZ8pynNZCM9MwkQkI3zeI4oz09uWdcgmgHugVvBqxGg4VQJ5PCg==", + "dependencies": { + "array-flatten": "^2.1.2", + "dns-equal": "^1.0.0", + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" + }, + "node_modules/browserslist": { + "version": "4.21.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", + "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001449", + "electron-to-chromium": "^1.4.284", + "node-releases": "^2.0.8", + "update-browserslist-db": "^1.0.10" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "dependencies": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001469", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001469.tgz", + "integrity": "sha512-Rcp7221ScNqQPP3W+lVOYDyjdR6dC+neEQCttoNr5bAyz54AboB4iwpnWgyi8P4YUsPybVzT4LgWiBbI3drL4g==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] + }, + "node_modules/canvas-renderer": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/canvas-renderer/-/canvas-renderer-2.2.1.tgz", + "integrity": "sha512-RrBgVL5qCEDIXpJ6NrzyRNoTnXxYarqm/cS/W6ERhUJts5UQtt/XPEosGN3rqUkZ4fjBArlnCbsISJ+KCFnIAg==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/case-sensitive-paths-webpack-plugin": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz", + "integrity": "sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "engines": { + "node": ">=10" + } + }, + "node_modules/character-entities": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/check-types": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.2.2.tgz", + "integrity": "sha512-HBiYvXvn9Z70Z88XKjz3AEKd4HJhBXsa3j7xFnITAzoS8+q6eIGi8qDB8FKPBAjtuxjI/zFpwuiCb8oDtKOYrA==" + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/ci-info": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", + "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==" + }, + "node_modules/clean-css": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.2.tgz", + "integrity": "sha512-JVJbM+f3d3Q704rF4bqQ5UUyTtuJ0JRKNbTKVEeujCCBoMdkEi+V+e8oktO9qGQNSvHrFTM6JZRXrUvGR1czww==", + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 10.0" + } + }, + "node_modules/clean-css/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/coa": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", + "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", + "dependencies": { + "@types/q": "^1.5.1", + "chalk": "^2.4.1", + "q": "^1.1.2" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==" + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==" + }, + "node_modules/colorette": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", + "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/comma-separated-tokens": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", + "integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/common-path-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", + "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==" + }, + "node_modules/common-tags": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", + "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/compression/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/confusing-browser-globals": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==" + }, + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/core-js": { + "version": "3.29.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.29.1.tgz", + "integrity": "sha512-+jwgnhg6cQxKYIIjGtAHq2nwUOolo9eoFZ4sHfUH09BLXBgxnH4gA0zEd+t+BO2cNB8idaBtZFcFTRjQJRJmAw==", + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-compat": { + "version": "3.29.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.29.1.tgz", + "integrity": "sha512-QmchCua884D8wWskMX8tW5ydINzd8oSJVx38lx/pVkFGqztxt73GYre3pm/hyYq8bPf+MW5In4I/uRShFDsbrA==", + "dependencies": { + "browserslist": "^4.21.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-pure": { + "version": "3.29.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.29.1.tgz", + "integrity": "sha512-4En6zYVi0i0XlXHVz/bi6l1XDjCqkKRq765NXuX+SnaIatlE96Odt5lMLjdxUiNI1v9OXI5DSLWYPlmTfkTktg==", + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/css-blank-pseudo": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-3.0.3.tgz", + "integrity": "sha512-VS90XWtsHGqoM0t4KpH053c4ehxZ2E6HtGI7x68YFV0pTo/QmkV/YFA+NnlvK8guxZVNWGQhVNJGC39Q8XF4OQ==", + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "bin": { + "css-blank-pseudo": "dist/cli.cjs" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-declaration-sorter": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.3.1.tgz", + "integrity": "sha512-fBffmak0bPAnyqc/HO8C3n2sHrp9wcqQz6ES9koRF2/mLOVAx9zIQ3Y7R29sYCteTPqMCwns4WYQoCX91Xl3+w==", + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.0.9" + } + }, + "node_modules/css-has-pseudo": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-3.0.4.tgz", + "integrity": "sha512-Vse0xpR1K9MNlp2j5w1pgWIJtm1a8qS0JwS9goFYcImjlHEmywP9VUF05aGBXzGpDJF86QXk4L0ypBmwPhGArw==", + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "bin": { + "css-has-pseudo": "dist/cli.cjs" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-loader": { + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.3.tgz", + "integrity": "sha512-qhOH1KlBMnZP8FzRO6YCH9UHXQhVMcEGLyNdb7Hv2cpcmJbW0YrddO+tG1ab5nT41KpHIYGsbeHqxB9xPu1pKQ==", + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.19", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.3.8" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/css-minimizer-webpack-plugin": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.4.1.tgz", + "integrity": "sha512-1u6D71zeIfgngN2XNRJefc/hY7Ybsxd74Jm4qngIXyUEk7fss3VUzuHxLAq/R8NAba4QU9OUSaMZlbpRc7bM4Q==", + "dependencies": { + "cssnano": "^5.0.6", + "jest-worker": "^27.0.2", + "postcss": "^8.3.5", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@parcel/css": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/css-minimizer-webpack-plugin/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/css-minimizer-webpack-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/css-minimizer-webpack-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/css-minimizer-webpack-plugin/node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/css-minimizer-webpack-plugin/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/css-prefers-color-scheme": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-6.0.3.tgz", + "integrity": "sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA==", + "bin": { + "css-prefers-color-scheme": "dist/cli.cjs" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-select-base-adapter": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", + "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" + }, + "node_modules/css-tree": { + "version": "1.0.0-alpha.37", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", + "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", + "dependencies": { + "mdn-data": "2.0.4", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/css-tree/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==" + }, + "node_modules/cssdb": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-7.4.1.tgz", + "integrity": "sha512-0Q8NOMpXJ3iTDDbUv9grcmQAfdDx4qz+fN/+Md2FGbevT+6+bJNQ2LjB2YIUlLbpBTM32idU1Sb+tb/uGt6/XQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssnano": { + "version": "5.1.15", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.1.15.tgz", + "integrity": "sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw==", + "dependencies": { + "cssnano-preset-default": "^5.2.14", + "lilconfig": "^2.0.3", + "yaml": "^1.10.2" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/cssnano" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/cssnano-preset-default": { + "version": "5.2.14", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.2.14.tgz", + "integrity": "sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A==", + "dependencies": { + "css-declaration-sorter": "^6.3.1", + "cssnano-utils": "^3.1.0", + "postcss-calc": "^8.2.3", + "postcss-colormin": "^5.3.1", + "postcss-convert-values": "^5.1.3", + "postcss-discard-comments": "^5.1.2", + "postcss-discard-duplicates": "^5.1.0", + "postcss-discard-empty": "^5.1.1", + "postcss-discard-overridden": "^5.1.0", + "postcss-merge-longhand": "^5.1.7", + "postcss-merge-rules": "^5.1.4", + "postcss-minify-font-values": "^5.1.0", + "postcss-minify-gradients": "^5.1.1", + "postcss-minify-params": "^5.1.4", + "postcss-minify-selectors": "^5.2.1", + "postcss-normalize-charset": "^5.1.0", + "postcss-normalize-display-values": "^5.1.0", + "postcss-normalize-positions": "^5.1.1", + "postcss-normalize-repeat-style": "^5.1.1", + "postcss-normalize-string": "^5.1.0", + "postcss-normalize-timing-functions": "^5.1.0", + "postcss-normalize-unicode": "^5.1.1", + "postcss-normalize-url": "^5.1.0", + "postcss-normalize-whitespace": "^5.1.1", + "postcss-ordered-values": "^5.1.3", + "postcss-reduce-initial": "^5.1.2", + "postcss-reduce-transforms": "^5.1.0", + "postcss-svgo": "^5.1.0", + "postcss-unique-selectors": "^5.1.1" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/cssnano-utils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", + "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/csso": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "dependencies": { + "css-tree": "^1.1.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" + }, + "node_modules/csso/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cssom": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", + "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==" + }, + "node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" + }, + "node_modules/csstype": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", + "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==" + }, + "node_modules/data-urls": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", + "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", + "dependencies": { + "abab": "^2.0.3", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==" + }, + "node_modules/dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==" + }, + "node_modules/deep-equal": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.0.tgz", + "integrity": "sha512-RdpzE0Hv4lhowpIUKKMJfeH6C1pXdtT1/it80ubgWqwI3qpuxUBpC1S4hnHg+zjnuOoDkzUtUCEEkG+XG5l3Mw==", + "dependencies": { + "call-bind": "^1.0.2", + "es-get-iterator": "^1.1.2", + "get-intrinsic": "^1.1.3", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.1", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "engines": { + "node": ">=8" + } + }, + "node_modules/define-properties": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/defined": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.1.tgz", + "integrity": "sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" + }, + "node_modules/detect-port-alt": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/detect-port-alt/-/detect-port-alt-1.1.6.tgz", + "integrity": "sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q==", + "dependencies": { + "address": "^1.0.1", + "debug": "^2.6.0" + }, + "bin": { + "detect": "bin/detect-port", + "detect-port": "bin/detect-port" + }, + "engines": { + "node": ">= 4.2.1" + } + }, + "node_modules/detect-port-alt/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/detect-port-alt/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/detective": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.1.tgz", + "integrity": "sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==", + "dependencies": { + "acorn-node": "^1.8.2", + "defined": "^1.0.0", + "minimist": "^1.2.6" + }, + "bin": { + "detective": "bin/detective.js" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" + }, + "node_modules/diff-sequences": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", + "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" + }, + "node_modules/dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==" + }, + "node_modules/dns-packet": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.4.0.tgz", + "integrity": "sha512-EgqGeaBB8hLiHLZtp/IbaDQTL8pZ0+IvwzSHA6d7VyMDM+B9hgddEMa9xjK5oYnw0ci0JQ6g2XCD7/f6cafU6g==", + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==" + }, + "node_modules/dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "dependencies": { + "utila": "~0.4" + } + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domexception": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", + "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", + "dependencies": { + "webidl-conversions": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/domexception/node_modules/webidl-conversions": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "engines": { + "node": ">=10" + } + }, + "node_modules/dotenv-expand": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", + "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==" + }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/ejs": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz", + "integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.335", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.335.tgz", + "integrity": "sha512-l/eowQqTnrq3gu+WSrdfkhfNHnPgYqlKAwxz7MTOj6mom19vpEDHNXl6dxDxyTiYuhemydprKr/HCrHfgk+OfQ==" + }, + "node_modules/emittery": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", + "integrity": "sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", + "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "dependencies": { + "ansi-colors": "^4.1.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/error-stack-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "dependencies": { + "stackframe": "^1.3.4" + } + }, + "node_modules/es-abstract": { + "version": "1.21.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", + "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.2.0", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.7", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==" + }, + "node_modules/es-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-module-lexer": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", + "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==" + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "dependencies": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dependencies": { + "has": "^1.0.3" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/escodegen": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", + "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/escodegen/node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eslint": { + "version": "8.36.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.36.0.tgz", + "integrity": "sha512-Y956lmS7vDqomxlaaQAHVmeb4tNMp2FWIvU/RnU5BD3IKMD/MJPr76xdyr68P8tV1iNMvN2mRK0yy3c+UjL+bw==", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.4.0", + "@eslint/eslintrc": "^2.0.1", + "@eslint/js": "8.36.0", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.5.0", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-react-app": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-7.0.1.tgz", + "integrity": "sha512-K6rNzvkIeHaTd8m/QEh1Zko0KI7BACWkkneSs6s9cKZC/J27X3eZR6Upt1jkmZ/4FK+XUOPPxMEN7+lbUXfSlA==", + "dependencies": { + "@babel/core": "^7.16.0", + "@babel/eslint-parser": "^7.16.3", + "@rushstack/eslint-patch": "^1.1.0", + "@typescript-eslint/eslint-plugin": "^5.5.0", + "@typescript-eslint/parser": "^5.5.0", + "babel-preset-react-app": "^10.0.1", + "confusing-browser-globals": "^1.0.11", + "eslint-plugin-flowtype": "^8.0.3", + "eslint-plugin-import": "^2.25.3", + "eslint-plugin-jest": "^25.3.0", + "eslint-plugin-jsx-a11y": "^6.5.1", + "eslint-plugin-react": "^7.27.1", + "eslint-plugin-react-hooks": "^4.3.0", + "eslint-plugin-testing-library": "^5.0.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "eslint": "^8.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", + "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.11.0", + "resolve": "^1.22.1" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz", + "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-flowtype": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-8.0.3.tgz", + "integrity": "sha512-dX8l6qUL6O+fYPtpNRideCFSpmWOUVx5QcaGLVqe/vlDiBSe4vYljDWDETwnyFzpl7By/WVIu6rcrniCgH9BqQ==", + "dependencies": { + "lodash": "^4.17.21", + "string-natural-compare": "^3.0.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@babel/plugin-syntax-flow": "^7.14.5", + "@babel/plugin-transform-react-jsx": "^7.14.9", + "eslint": "^8.1.0" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.27.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", + "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "array.prototype.flatmap": "^1.3.1", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.7", + "eslint-module-utils": "^2.7.4", + "has": "^1.0.3", + "is-core-module": "^2.11.0", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.6", + "resolve": "^1.22.1", + "semver": "^6.3.0", + "tsconfig-paths": "^3.14.1" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-jest": { + "version": "25.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-25.7.0.tgz", + "integrity": "sha512-PWLUEXeeF7C9QGKqvdSbzLOiLTx+bno7/HC9eefePfEb257QFHg7ye3dh80AZVkaa/RQsBB1Q/ORQvg2X7F0NQ==", + "dependencies": { + "@typescript-eslint/experimental-utils": "^5.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^4.0.0 || ^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "jest": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.7.1.tgz", + "integrity": "sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA==", + "dependencies": { + "@babel/runtime": "^7.20.7", + "aria-query": "^5.1.3", + "array-includes": "^3.1.6", + "array.prototype.flatmap": "^1.3.1", + "ast-types-flow": "^0.0.7", + "axe-core": "^4.6.2", + "axobject-query": "^3.1.1", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "has": "^1.0.3", + "jsx-ast-utils": "^3.3.3", + "language-tags": "=1.0.5", + "minimatch": "^3.1.2", + "object.entries": "^1.1.6", + "object.fromentries": "^2.0.6", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.32.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.32.2.tgz", + "integrity": "sha512-t2fBMa+XzonrrNkyVirzKlvn5RXzzPwRHtMvLAtVZrt8oxgnTQaYbU6SXTOO1mwQgp1y5+toMSKInnzGr0Knqg==", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flatmap": "^1.3.1", + "array.prototype.tosorted": "^1.1.1", + "doctrine": "^2.1.0", + "estraverse": "^5.3.0", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.6", + "object.fromentries": "^2.0.6", + "object.hasown": "^1.1.2", + "object.values": "^1.1.6", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.4", + "semver": "^6.3.0", + "string.prototype.matchall": "^4.0.8" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", + "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-testing-library": { + "version": "5.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-5.10.2.tgz", + "integrity": "sha512-f1DmDWcz5SDM+IpCkEX0lbFqrrTs8HRsEElzDEqN/EBI0hpRj8Cns5+IVANXswE8/LeybIJqPAOQIFu2j5Y5sw==", + "dependencies": { + "@typescript-eslint/utils": "^5.43.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0", + "npm": ">=6" + }, + "peerDependencies": { + "eslint": "^7.5.0 || ^8.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint-webpack-plugin": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/eslint-webpack-plugin/-/eslint-webpack-plugin-3.2.0.tgz", + "integrity": "sha512-avrKcGncpPbPSUHX6B3stNGzkKFto3eL+DKM4+VyMrVnhPc3vRczVlCq3uhuFOdRvDHTVXuzwk1ZKUrqDQHQ9w==", + "dependencies": { + "@types/eslint": "^7.29.0 || ^8.4.1", + "jest-worker": "^28.0.2", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0", + "webpack": "^5.0.0" + } + }, + "node_modules/eslint-webpack-plugin/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint-webpack-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/eslint-webpack-plugin/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint-webpack-plugin/node_modules/jest-worker": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz", + "integrity": "sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/eslint-webpack-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/eslint-webpack-plugin/node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/eslint-webpack-plugin/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.0.tgz", + "integrity": "sha512-JPbJGhKc47++oo4JkEoTe2wjy4fmMwvFpgJT9cQzmfXKp22Dr6Hf1tdCteLz1h0P3t+mGvWZ+4Uankvh8+c6zw==", + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz", + "integrity": "sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==", + "dependencies": { + "@jest/types": "^27.5.1", + "jest-get-type": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fault": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz", + "integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==", + "dependencies": { + "format": "^0.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/file-loader": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", + "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==", + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/filesize": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-8.0.7.tgz", + "integrity": "sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" + }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/fork-ts-checker-webpack-plugin": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.3.tgz", + "integrity": "sha512-SbH/l9ikmMWycd5puHJKTkZJKddF4iRLyW3DeZ08HTI7NGyLS38MXd/KGgeWumQO7YNQbW2u/NtPT2YowbPaGQ==", + "dependencies": { + "@babel/code-frame": "^7.8.3", + "@types/json-schema": "^7.0.5", + "chalk": "^4.1.0", + "chokidar": "^3.4.2", + "cosmiconfig": "^6.0.0", + "deepmerge": "^4.2.2", + "fs-extra": "^9.0.0", + "glob": "^7.1.6", + "memfs": "^3.1.2", + "minimatch": "^3.0.4", + "schema-utils": "2.7.0", + "semver": "^7.3.2", + "tapable": "^1.0.0" + }, + "engines": { + "node": ">=10", + "yarn": ">=1.0.0" + }, + "peerDependencies": { + "eslint": ">= 6", + "typescript": ">= 2.7", + "vue-template-compiler": "*", + "webpack": ">= 4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + }, + "vue-template-compiler": { + "optional": true + } + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/schema-utils": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", + "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", + "dependencies": { + "@types/json-schema": "^7.0.4", + "ajv": "^6.12.2", + "ajv-keywords": "^3.4.1" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", + "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://www.patreon.com/infusion" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs-monkey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz", + "integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "dev": true + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-own-enumerable-property-symbols": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", + "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==" + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" + }, + "node_modules/global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "dependencies": { + "global-prefix": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "dependencies": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==" + }, + "node_modules/gzip-size": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", + "dependencies": { + "duplexer": "^0.1.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==" + }, + "node_modules/harmony-reflect": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.2.tgz", + "integrity": "sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==" + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz", + "integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz", + "integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^1.0.0", + "hast-util-parse-selector": "^2.0.0", + "property-information": "^5.0.0", + "space-separated-tokens": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "bin": { + "he": "bin/he" + } + }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "engines": { + "node": "*" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/hoopy": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", + "integrity": "sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hpack.js/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hpack.js/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/hpack.js/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", + "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", + "dependencies": { + "whatwg-encoding": "^1.0.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/html-entities": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", + "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==" + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==" + }, + "node_modules/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", + "dependencies": { + "camel-case": "^4.1.2", + "clean-css": "^5.2.2", + "commander": "^8.3.0", + "he": "^1.2.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.10.0" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/html-webpack-plugin": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz", + "integrity": "sha512-sy88PC2cRTVxvETRgUHFrL4No3UxvcH8G1NepGhqaTT+GXN2kTamqasot0inS5hXeg1cMbFDt27zzo9p35lZVw==", + "dependencies": { + "@types/html-minifier-terser": "^6.0.0", + "html-minifier-terser": "^6.0.2", + "lodash": "^4.17.21", + "pretty-error": "^4.0.0", + "tapable": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/html-webpack-plugin" + }, + "peerDependencies": { + "webpack": "^5.20.0" + } + }, + "node_modules/htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==" + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-proxy-middleware": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/idb": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", + "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==" + }, + "node_modules/identity-obj-proxy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz", + "integrity": "sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA==", + "dependencies": { + "harmony-reflect": "^1.4.6" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + }, + "node_modules/immer": { + "version": "9.0.19", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.19.tgz", + "integrity": "sha512-eY+Y0qcsB4TZKwgQzLaE/lqYMlKhv5J9dyd2RhhtGhNo2njPXDqU9XPfcNfa3MIDsdtZt5KlkIsirlo4dHsWdQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "node_modules/internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dependencies": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ipaddr.js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", + "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "dependencies": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", + "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==" + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-root": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-root/-/is-root-2.1.0.tgz", + "integrity": "sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-set": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", + "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + }, + "node_modules/is-weakmap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", + "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jake": { + "version": "10.8.5", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz", + "integrity": "sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==", + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.1", + "minimatch": "^3.0.4" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jake/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jake/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jake/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jake/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/jake/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jake/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jdenticon": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jdenticon/-/jdenticon-3.2.0.tgz", + "integrity": "sha512-z6Iq3fTODUMSOiR2nNYrqigS6Y0GvdXfyQWrUby7htDHvX7GNEwaWR4hcaL+FmhEgBe08Xkup/BKxXQhDJByPA==", + "dependencies": { + "canvas-renderer": "~2.2.0" + }, + "bin": { + "jdenticon": "bin/jdenticon.js" + }, + "engines": { + "node": ">=6.4.0" + } + }, + "node_modules/jest": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz", + "integrity": "sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==", + "dependencies": { + "@jest/core": "^27.5.1", + "import-local": "^3.0.2", + "jest-cli": "^27.5.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.5.1.tgz", + "integrity": "sha512-buBLMiByfWGCoMsLLzGUUSpAmIAGnbR2KJoMN10ziLhOLvP4e0SlypHnAel8iqQXTrcbmfEY9sSqae5sgUsTvw==", + "dependencies": { + "@jest/types": "^27.5.1", + "execa": "^5.0.0", + "throat": "^6.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-circus": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.5.1.tgz", + "integrity": "sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw==", + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "expect": "^27.5.1", + "is-generator-fn": "^2.0.0", + "jest-each": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-runtime": "^27.5.1", + "jest-snapshot": "^27.5.1", + "jest-util": "^27.5.1", + "pretty-format": "^27.5.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3", + "throat": "^6.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-circus/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-circus/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-circus/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-circus/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/jest-circus/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-circus/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-cli": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.5.1.tgz", + "integrity": "sha512-Hc6HOOwYq4/74/c62dEE3r5elx8wjYqxY0r0G/nFrLDPMFRu6RA/u8qINOIkvhxG7mMQ5EJsOGfRpI8L6eFUVw==", + "dependencies": { + "@jest/core": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/types": "^27.5.1", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^27.5.1", + "jest-util": "^27.5.1", + "jest-validate": "^27.5.1", + "prompts": "^2.0.1", + "yargs": "^16.2.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-cli/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-cli/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-cli/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-cli/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/jest-cli/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-cli/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-config": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-27.5.1.tgz", + "integrity": "sha512-5sAsjm6tGdsVbW9ahcChPAFCk4IlkQUknH5AvKjuLTSlcO/wCZKyFdn7Rg0EkC+OGgWODEy2hDpWB1PgzH0JNA==", + "dependencies": { + "@babel/core": "^7.8.0", + "@jest/test-sequencer": "^27.5.1", + "@jest/types": "^27.5.1", + "babel-jest": "^27.5.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.1", + "graceful-fs": "^4.2.9", + "jest-circus": "^27.5.1", + "jest-environment-jsdom": "^27.5.1", + "jest-environment-node": "^27.5.1", + "jest-get-type": "^27.5.1", + "jest-jasmine2": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-runner": "^27.5.1", + "jest-util": "^27.5.1", + "jest-validate": "^27.5.1", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^27.5.1", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-config/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-config/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-config/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-config/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/jest-config/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-config/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-diff": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", + "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-diff/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-diff/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/jest-diff/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-diff/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-docblock": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.5.1.tgz", + "integrity": "sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ==", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-each": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.5.1.tgz", + "integrity": "sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ==", + "dependencies": { + "@jest/types": "^27.5.1", + "chalk": "^4.0.0", + "jest-get-type": "^27.5.1", + "jest-util": "^27.5.1", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-each/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-each/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-each/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-each/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/jest-each/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-each/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-environment-jsdom": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz", + "integrity": "sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw==", + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/fake-timers": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "jest-mock": "^27.5.1", + "jest-util": "^27.5.1", + "jsdom": "^16.6.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.5.1.tgz", + "integrity": "sha512-Jt4ZUnxdOsTGwSRAfKEnE6BcwsSPNOijjwifq5sDFSA2kesnXTvNqKHYgM0hDq3549Uf/KzdXNYn4wMZJPlFLw==", + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/fake-timers": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "jest-mock": "^27.5.1", + "jest-util": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", + "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.5.1.tgz", + "integrity": "sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng==", + "dependencies": { + "@jest/types": "^27.5.1", + "@types/graceful-fs": "^4.1.2", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^27.5.1", + "jest-serializer": "^27.5.1", + "jest-util": "^27.5.1", + "jest-worker": "^27.5.1", + "micromatch": "^4.0.4", + "walker": "^1.0.7" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-jasmine2": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.5.1.tgz", + "integrity": "sha512-jtq7VVyG8SqAorDpApwiJJImd0V2wv1xzdheGHRGyuT7gZm6gG47QEskOlzsN1PG/6WNaCo5pmwMHDf3AkG2pQ==", + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/source-map": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "expect": "^27.5.1", + "is-generator-fn": "^2.0.0", + "jest-each": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-runtime": "^27.5.1", + "jest-snapshot": "^27.5.1", + "jest-util": "^27.5.1", + "pretty-format": "^27.5.1", + "throat": "^6.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-jasmine2/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-jasmine2/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-jasmine2/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-jasmine2/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/jest-jasmine2/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-jasmine2/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-leak-detector": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz", + "integrity": "sha512-POXfWAMvfU6WMUXftV4HolnJfnPOGEu10fscNCA76KBpRRhcMN2c8d3iT2pxQS3HLbA+5X4sOUPzYO2NUyIlHQ==", + "dependencies": { + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", + "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/jest-matcher-utils/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-matcher-utils/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-message-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", + "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^27.5.1", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^27.5.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-message-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-message-util/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/jest-message-util/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-message-util/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-mock": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz", + "integrity": "sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==", + "dependencies": { + "@jest/types": "^27.5.1", + "@types/node": "*" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz", + "integrity": "sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==", + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.5.1.tgz", + "integrity": "sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw==", + "dependencies": { + "@jest/types": "^27.5.1", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^27.5.1", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^27.5.1", + "jest-validate": "^27.5.1", + "resolve": "^1.20.0", + "resolve.exports": "^1.1.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.5.1.tgz", + "integrity": "sha512-QQOOdY4PE39iawDn5rzbIePNigfe5B9Z91GDD1ae/xNDlu9kaat8QQ5EKnNmVWPV54hUdxCVwwj6YMgR2O7IOg==", + "dependencies": { + "@jest/types": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-snapshot": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-resolve/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-resolve/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-resolve/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-resolve/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/jest-resolve/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-resolve/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runner": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-27.5.1.tgz", + "integrity": "sha512-g4NPsM4mFCOwFKXO4p/H/kWGdJp9V8kURY2lX8Me2drgXqG7rrZAx5kv+5H7wtt/cdFIjhqYx1HrlqWHaOvDaQ==", + "dependencies": { + "@jest/console": "^27.5.1", + "@jest/environment": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.8.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^27.5.1", + "jest-environment-jsdom": "^27.5.1", + "jest-environment-node": "^27.5.1", + "jest-haste-map": "^27.5.1", + "jest-leak-detector": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-runtime": "^27.5.1", + "jest-util": "^27.5.1", + "jest-worker": "^27.5.1", + "source-map-support": "^0.5.6", + "throat": "^6.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-runner/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-runner/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-runner/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-runner/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/jest-runner/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runner/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runtime": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.5.1.tgz", + "integrity": "sha512-o7gxw3Gf+H2IGt8fv0RiyE1+r83FJBRruoA+FXrlHw6xEyBsU8ugA6IPfTdVyA0w8HClpbK+DGJxH59UrNMx8A==", + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/fake-timers": "^27.5.1", + "@jest/globals": "^27.5.1", + "@jest/source-map": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "execa": "^5.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-mock": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-snapshot": "^27.5.1", + "jest-util": "^27.5.1", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-runtime/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-runtime/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-runtime/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-runtime/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/jest-runtime/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runtime/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-serializer": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.5.1.tgz", + "integrity": "sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w==", + "dependencies": { + "@types/node": "*", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.5.1.tgz", + "integrity": "sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA==", + "dependencies": { + "@babel/core": "^7.7.2", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.0.0", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/babel__traverse": "^7.0.4", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^27.5.1", + "graceful-fs": "^4.2.9", + "jest-diff": "^27.5.1", + "jest-get-type": "^27.5.1", + "jest-haste-map": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-util": "^27.5.1", + "natural-compare": "^1.4.0", + "pretty-format": "^27.5.1", + "semver": "^7.3.2" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-snapshot/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-snapshot/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/jest-snapshot/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-snapshot/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", + "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", + "dependencies": { + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-util/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-util/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/jest-util/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-util/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-validate": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.5.1.tgz", + "integrity": "sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ==", + "dependencies": { + "@jest/types": "^27.5.1", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^27.5.1", + "leven": "^3.1.0", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-validate/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-validate/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-validate/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-validate/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/jest-validate/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-validate/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watch-typeahead": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jest-watch-typeahead/-/jest-watch-typeahead-1.1.0.tgz", + "integrity": "sha512-Va5nLSJTN7YFtC2jd+7wsoe1pNe5K4ShLux/E5iHEwlB9AxaxmggY7to9KUqKojhaJw3aXqt5WAb4jGPOolpEw==", + "dependencies": { + "ansi-escapes": "^4.3.1", + "chalk": "^4.0.0", + "jest-regex-util": "^28.0.0", + "jest-watcher": "^28.0.0", + "slash": "^4.0.0", + "string-length": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "jest": "^27.0.0 || ^28.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/@jest/console": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-28.1.3.tgz", + "integrity": "sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==", + "dependencies": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3", + "slash": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/@jest/console/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watch-typeahead/node_modules/@jest/test-result": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.3.tgz", + "integrity": "sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==", + "dependencies": { + "@jest/console": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/@jest/types": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", + "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "dependencies": { + "@jest/schemas": "^28.1.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/@types/yargs": { + "version": "17.0.23", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.23.tgz", + "integrity": "sha512-yuogunc04OnzGQCrfHx+Kk883Q4X0aSwmYZhKjI21m+SVYzjIbrWl8dOOwSv5hf2Um2pdCOXWo9isteZTNXUZQ==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-watch-typeahead/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-watch-typeahead/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-watch-typeahead/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/jest-watch-typeahead/node_modules/emittery": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", + "integrity": "sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/jest-watch-typeahead/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watch-typeahead/node_modules/jest-message-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", + "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^28.1.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/jest-message-util/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watch-typeahead/node_modules/jest-regex-util": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", + "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "dependencies": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/jest-watcher": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-28.1.3.tgz", + "integrity": "sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g==", + "dependencies": { + "@jest/test-result": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.10.2", + "jest-util": "^28.1.3", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/jest-watcher/node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-watch-typeahead/node_modules/jest-watcher/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watch-typeahead/node_modules/pretty-format": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", + "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "dependencies": { + "@jest/schemas": "^28.1.3", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-watch-typeahead/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, + "node_modules/jest-watch-typeahead/node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watch-typeahead/node_modules/string-length": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-5.0.1.tgz", + "integrity": "sha512-9Ep08KAMUn0OadnVaBuRdE2l615CQ508kr0XMadjClfYpdCyvrbFp6Taebo8yyxokQ4viUd/xPPUA4FGgUa0ow==", + "dependencies": { + "char-regex": "^2.0.0", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watch-typeahead/node_modules/string-length/node_modules/char-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-2.0.1.tgz", + "integrity": "sha512-oSvEeo6ZUD7NepqAat3RqoucZ5SeqLJgOvVIwkafu6IP3V0pO38s/ypdVUmDDK6qIIHNlYHJAKX9E7R7HoKElw==", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/jest-watch-typeahead/node_modules/strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/jest-watch-typeahead/node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/jest-watch-typeahead/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watcher": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.5.1.tgz", + "integrity": "sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw==", + "dependencies": { + "@jest/test-result": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "jest-util": "^27.5.1", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-watcher/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-watcher/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-watcher/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-watcher/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/jest-watcher/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watcher/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-sdsl": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", + "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/js-sha256": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz", + "integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "16.7.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", + "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", + "dependencies": { + "abab": "^2.0.5", + "acorn": "^8.2.4", + "acorn-globals": "^6.0.0", + "cssom": "^0.4.4", + "cssstyle": "^2.3.0", + "data-urls": "^2.0.0", + "decimal.js": "^10.2.1", + "domexception": "^2.0.1", + "escodegen": "^2.0.0", + "form-data": "^3.0.0", + "html-encoding-sniffer": "^2.0.1", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.0", + "parse5": "6.0.1", + "saxes": "^5.0.1", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.0.0", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^2.0.0", + "webidl-conversions": "^6.1.0", + "whatwg-encoding": "^1.0.5", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.5.0", + "ws": "^7.4.6", + "xml-name-validator": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz", + "integrity": "sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==", + "dependencies": { + "array-includes": "^3.1.5", + "object.assign": "^4.1.3" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "engines": { + "node": ">=6" + } + }, + "node_modules/klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", + "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==" + }, + "node_modules/language-tags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", + "integrity": "sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==", + "dependencies": { + "language-subtag-registry": "~0.3.2" + } + }, + "node_modules/launch-editor": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.6.0.tgz", + "integrity": "sha512-JpDCcQnyAAzZZaZ7vEiSqL690w7dAEyLao+KC96zBplnYbJS7TYNjvM3M7y3dGz+v7aIsJk3hllWuc0kWAjyRQ==", + "dependencies": { + "picocolors": "^1.0.0", + "shell-quote": "^1.7.3" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lie": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", + "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/localforage": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", + "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", + "dependencies": { + "lie": "3.1.1" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash-core": { + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash-core/-/lodash-core-4.17.19.tgz", + "integrity": "sha512-FpSbOooU9HcASpL7oJ/ZQGxR7oGzeqlVe1M/iAhnUMTk7eMJBkszS5tUPZCnRtNGPWe8ChswdByFTzW58iGQEQ==" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "node_modules/lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==" + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lowlight": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz", + "integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==", + "dependencies": { + "fault": "^1.0.0", + "highlight.js": "~10.7.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "dependencies": { + "sourcemap-codec": "^1.4.8" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/match-sorter": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.1.tgz", + "integrity": "sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "remove-accents": "0.4.2" + } + }, + "node_modules/mdn-data": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", + "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==" + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "3.4.13", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.13.tgz", + "integrity": "sha512-omTM41g3Skpvx5dSYeZIbXKcXoAVc/AoMNwn9TKx++L/gaen/+4TTttmu8ZSch5vfVJ8uJvGbroTsIlslRg6lg==", + "dependencies": { + "fs-monkey": "^1.0.3" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "engines": { + "node": ">=4" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "2.7.5", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.5.tgz", + "integrity": "sha512-9HaR++0mlgom81s95vvNjxkg52n2b5s//3ZTI1EtzFb98awsLSivs2LMsVqnQ3ay0PVhqWcGNyDaTE961FOcjQ==", + "dependencies": { + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/mini-css-extract-plugin/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/mini-css-extract-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/mini-css-extract-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/mini-css-extract-plugin/node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==" + }, + "node_modules/node-releases": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", + "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/nwsapi": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz", + "integrity": "sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object-path": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/object-path/-/object-path-0.6.0.tgz", + "integrity": "sha512-fxrwsCFi3/p+LeLOAwo/wyRMODZxdGBtUlWRzsEpsUVrisZbEfZ21arxLGfaWfcnqb8oHPNihIb4XPE8CQPN5A==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz", + "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz", + "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.getownpropertydescriptors": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.5.tgz", + "integrity": "sha512-yDNzckpM6ntyQiGTik1fKV1DcVDRS+w8bvpWNCBanvH5LfRX9O8WTHqQzG4RZwRAM4I0oU7TV11Lj5v0g20ibw==", + "dependencies": { + "array.prototype.reduce": "^1.0.5", + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.hasown": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.2.tgz", + "integrity": "sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==", + "dependencies": { + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", + "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==" + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", + "dependencies": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-up": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", + "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-up/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-up/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss": { + "version": "8.4.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", + "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + ], + "dependencies": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-attribute-case-insensitive": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-5.0.2.tgz", + "integrity": "sha512-XIidXV8fDr0kKt28vqki84fRK8VW8eTuIa4PChv2MqKuT6C9UjmSKzen6KaWhWEoYvwxFCa7n/tC1SZ3tyq4SQ==", + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-browser-comments": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-browser-comments/-/postcss-browser-comments-4.0.0.tgz", + "integrity": "sha512-X9X9/WN3KIvY9+hNERUqX9gncsgBA25XaeR+jshHz2j8+sYyHktHw1JdKuMjeLpGktXidqDhA7b/qm1mrBDmgg==", + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "browserslist": ">=4", + "postcss": ">=8" + } + }, + "node_modules/postcss-calc": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.2.4.tgz", + "integrity": "sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==", + "dependencies": { + "postcss-selector-parser": "^6.0.9", + "postcss-value-parser": "^4.2.0" + }, + "peerDependencies": { + "postcss": "^8.2.2" + } + }, + "node_modules/postcss-clamp": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz", + "integrity": "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=7.6.0" + }, + "peerDependencies": { + "postcss": "^8.4.6" + } + }, + "node_modules/postcss-color-functional-notation": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-4.2.4.tgz", + "integrity": "sha512-2yrTAUZUab9s6CpxkxC4rVgFEVaR6/2Pipvi6qcgvnYiVqZcbDHEoBDhrXzyb7Efh2CCfHQNtcqWcIruDTIUeg==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-color-hex-alpha": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-8.0.4.tgz", + "integrity": "sha512-nLo2DCRC9eE4w2JmuKgVA3fGL3d01kGq752pVALF68qpGLmx2Qrk91QTKkdUqqp45T1K1XV8IhQpcu1hoAQflQ==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-color-rebeccapurple": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-7.1.1.tgz", + "integrity": "sha512-pGxkuVEInwLHgkNxUc4sdg4g3py7zUeCQ9sMfwyHAT+Ezk8a4OaaVZ8lIY5+oNqA/BXXgLyXv0+5wHP68R79hg==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-colormin": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.3.1.tgz", + "integrity": "sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ==", + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-api": "^3.0.0", + "colord": "^2.9.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-convert-values": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.1.3.tgz", + "integrity": "sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==", + "dependencies": { + "browserslist": "^4.21.4", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-custom-media": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-8.0.2.tgz", + "integrity": "sha512-7yi25vDAoHAkbhAzX9dHx2yc6ntS4jQvejrNcC+csQJAXjj15e7VcWfMgLqBNAbOvqi5uIa9huOVwdHbf+sKqg==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.3" + } + }, + "node_modules/postcss-custom-properties": { + "version": "12.1.11", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-12.1.11.tgz", + "integrity": "sha512-0IDJYhgU8xDv1KY6+VgUwuQkVtmYzRwu+dMjnmdMafXYv86SWqfxkc7qdDvWS38vsjaEtv8e0vGOUQrAiMBLpQ==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-custom-selectors": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-6.0.3.tgz", + "integrity": "sha512-fgVkmyiWDwmD3JbpCmB45SvvlCD6z9CG6Ie6Iere22W5aHea6oWa7EM2bpnv2Fj3I94L3VbtvX9KqwSi5aFzSg==", + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.3" + } + }, + "node_modules/postcss-dir-pseudo-class": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-6.0.5.tgz", + "integrity": "sha512-eqn4m70P031PF7ZQIvSgy9RSJ5uI2171O/OO/zcRNYpJbvaeKFUlar1aJ7rmgiQtbm0FSPsRewjpdS0Oew7MPA==", + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-discard-comments": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz", + "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-duplicates": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", + "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-empty": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", + "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-overridden": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", + "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-double-position-gradients": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-3.1.2.tgz", + "integrity": "sha512-GX+FuE/uBR6eskOK+4vkXgT6pDkexLokPaz/AbJna9s5Kzp/yl488pKPjhy0obB475ovfT1Wv8ho7U/cHNaRgQ==", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-env-function": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/postcss-env-function/-/postcss-env-function-4.0.6.tgz", + "integrity": "sha512-kpA6FsLra+NqcFnL81TnsU+Z7orGtDTxcOhl6pwXeEq1yFPpRMkCDpHhrz8CFQDr/Wfm0jLiNQ1OsGGPjlqPwA==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-flexbugs-fixes": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-5.0.2.tgz", + "integrity": "sha512-18f9voByak7bTktR2QgDveglpn9DTbBWPUzSOe9g0N4WR/2eSt6Vrcbf0hmspvMI6YWGywz6B9f7jzpFNJJgnQ==", + "peerDependencies": { + "postcss": "^8.1.4" + } + }, + "node_modules/postcss-focus-visible": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-6.0.4.tgz", + "integrity": "sha512-QcKuUU/dgNsstIK6HELFRT5Y3lbrMLEOwG+A4s5cA+fx3A3y/JTq3X9LaOj3OC3ALH0XqyrgQIgey/MIZ8Wczw==", + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-focus-within": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-5.0.4.tgz", + "integrity": "sha512-vvjDN++C0mu8jz4af5d52CB184ogg/sSxAFS+oUJQq2SuCe7T5U2iIsVJtsCp2d6R4j0jr5+q3rPkBVZkXD9fQ==", + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-font-variant": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", + "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-gap-properties": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-3.0.5.tgz", + "integrity": "sha512-IuE6gKSdoUNcvkGIqdtjtcMtZIFyXZhmFd5RUlg97iVEvp1BZKV5ngsAjCjrVy+14uhGBQl9tzmi1Qwq4kqVOg==", + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-image-set-function": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-4.0.7.tgz", + "integrity": "sha512-9T2r9rsvYzm5ndsBE8WgtrMlIT7VbtTfE7b3BQnudUqnBcBo7L758oc+o+pdj/dUV0l5wjwSdjeOH2DZtfv8qw==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-import": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-14.1.0.tgz", + "integrity": "sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-initial": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-4.0.1.tgz", + "integrity": "sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==", + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-lab-function": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-4.2.1.tgz", + "integrity": "sha512-xuXll4isR03CrQsmxyz92LJB2xX9n+pZJ5jE9JgcnmsCammLyKdlzrBin+25dy6wIjfhJpKBAN80gsTlCgRk2w==", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-load-config": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", + "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^1.10.2" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-loader": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-6.2.1.tgz", + "integrity": "sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q==", + "dependencies": { + "cosmiconfig": "^7.0.0", + "klona": "^2.0.5", + "semver": "^7.3.5" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + } + }, + "node_modules/postcss-logical": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-5.0.4.tgz", + "integrity": "sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==", + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-media-minmax": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz", + "integrity": "sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-merge-longhand": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz", + "integrity": "sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "stylehacks": "^5.1.1" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-merge-rules": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.1.4.tgz", + "integrity": "sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g==", + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-api": "^3.0.0", + "cssnano-utils": "^3.1.0", + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-font-values": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz", + "integrity": "sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-gradients": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz", + "integrity": "sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==", + "dependencies": { + "colord": "^2.9.1", + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-params": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.1.4.tgz", + "integrity": "sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==", + "dependencies": { + "browserslist": "^4.21.4", + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-selectors": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz", + "integrity": "sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==", + "dependencies": { + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", + "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", + "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-nested": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.0.tgz", + "integrity": "sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==", + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-nesting": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-10.2.0.tgz", + "integrity": "sha512-EwMkYchxiDiKUhlJGzWsD9b2zvq/r2SSubcRrgP+jujMXFzqvANLt16lJANC+5uZ6hjI7lpRmI6O8JIl+8l1KA==", + "dependencies": { + "@csstools/selector-specificity": "^2.0.0", + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-normalize": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize/-/postcss-normalize-10.0.1.tgz", + "integrity": "sha512-+5w18/rDev5mqERcG3W5GZNMJa1eoYYNGo8gB7tEwaos0ajk3ZXAI4mHGcNT47NE+ZnZD1pEpUOFLvltIwmeJA==", + "dependencies": { + "@csstools/normalize.css": "*", + "postcss-browser-comments": "^4", + "sanitize.css": "*" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "browserslist": ">= 4", + "postcss": ">= 8" + } + }, + "node_modules/postcss-normalize-charset": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", + "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-display-values": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz", + "integrity": "sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-positions": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz", + "integrity": "sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-repeat-style": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz", + "integrity": "sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-string": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz", + "integrity": "sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-timing-functions": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz", + "integrity": "sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-unicode": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.1.tgz", + "integrity": "sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==", + "dependencies": { + "browserslist": "^4.21.4", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-url": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz", + "integrity": "sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==", + "dependencies": { + "normalize-url": "^6.0.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-whitespace": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz", + "integrity": "sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-opacity-percentage": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-1.1.3.tgz", + "integrity": "sha512-An6Ba4pHBiDtyVpSLymUUERMo2cU7s+Obz6BTrS+gxkbnSBNKSuD0AVUc+CpBMrpVPKKfoVz0WQCX+Tnst0i4A==", + "funding": [ + { + "type": "kofi", + "url": "https://ko-fi.com/mrcgrtz" + }, + { + "type": "liberapay", + "url": "https://liberapay.com/mrcgrtz" + } + ], + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-ordered-values": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz", + "integrity": "sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==", + "dependencies": { + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-overflow-shorthand": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-3.0.4.tgz", + "integrity": "sha512-otYl/ylHK8Y9bcBnPLo3foYFLL6a6Ak+3EQBPOTR7luMYCOsiVTUk1iLvNf6tVPNGXcoL9Hoz37kpfriRIFb4A==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-page-break": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", + "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", + "peerDependencies": { + "postcss": "^8" + } + }, + "node_modules/postcss-place": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-7.0.5.tgz", + "integrity": "sha512-wR8igaZROA6Z4pv0d+bvVrvGY4GVHihBCBQieXFY3kuSuMyOmEnnfFzHl/tQuqHZkfkIVBEbDvYcFfHmpSet9g==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-preset-env": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-7.8.3.tgz", + "integrity": "sha512-T1LgRm5uEVFSEF83vHZJV2z19lHg4yJuZ6gXZZkqVsqv63nlr6zabMH3l4Pc01FQCyfWVrh2GaUeCVy9Po+Aag==", + "dependencies": { + "@csstools/postcss-cascade-layers": "^1.1.1", + "@csstools/postcss-color-function": "^1.1.1", + "@csstools/postcss-font-format-keywords": "^1.0.1", + "@csstools/postcss-hwb-function": "^1.0.2", + "@csstools/postcss-ic-unit": "^1.0.1", + "@csstools/postcss-is-pseudo-class": "^2.0.7", + "@csstools/postcss-nested-calc": "^1.0.0", + "@csstools/postcss-normalize-display-values": "^1.0.1", + "@csstools/postcss-oklab-function": "^1.1.1", + "@csstools/postcss-progressive-custom-properties": "^1.3.0", + "@csstools/postcss-stepped-value-functions": "^1.0.1", + "@csstools/postcss-text-decoration-shorthand": "^1.0.0", + "@csstools/postcss-trigonometric-functions": "^1.0.2", + "@csstools/postcss-unset-value": "^1.0.2", + "autoprefixer": "^10.4.13", + "browserslist": "^4.21.4", + "css-blank-pseudo": "^3.0.3", + "css-has-pseudo": "^3.0.4", + "css-prefers-color-scheme": "^6.0.3", + "cssdb": "^7.1.0", + "postcss-attribute-case-insensitive": "^5.0.2", + "postcss-clamp": "^4.1.0", + "postcss-color-functional-notation": "^4.2.4", + "postcss-color-hex-alpha": "^8.0.4", + "postcss-color-rebeccapurple": "^7.1.1", + "postcss-custom-media": "^8.0.2", + "postcss-custom-properties": "^12.1.10", + "postcss-custom-selectors": "^6.0.3", + "postcss-dir-pseudo-class": "^6.0.5", + "postcss-double-position-gradients": "^3.1.2", + "postcss-env-function": "^4.0.6", + "postcss-focus-visible": "^6.0.4", + "postcss-focus-within": "^5.0.4", + "postcss-font-variant": "^5.0.0", + "postcss-gap-properties": "^3.0.5", + "postcss-image-set-function": "^4.0.7", + "postcss-initial": "^4.0.1", + "postcss-lab-function": "^4.2.1", + "postcss-logical": "^5.0.4", + "postcss-media-minmax": "^5.0.0", + "postcss-nesting": "^10.2.0", + "postcss-opacity-percentage": "^1.1.2", + "postcss-overflow-shorthand": "^3.0.4", + "postcss-page-break": "^3.0.4", + "postcss-place": "^7.0.5", + "postcss-pseudo-class-any-link": "^7.1.6", + "postcss-replace-overflow-wrap": "^4.0.0", + "postcss-selector-not": "^6.0.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-pseudo-class-any-link": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-7.1.6.tgz", + "integrity": "sha512-9sCtZkO6f/5ML9WcTLcIyV1yz9D1rf0tWc+ulKcvV30s0iZKS/ONyETvoWsr6vnrmW+X+KmuK3gV/w5EWnT37w==", + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-reduce-initial": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.1.2.tgz", + "integrity": "sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg==", + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-api": "^3.0.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-reduce-transforms": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz", + "integrity": "sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-replace-overflow-wrap": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", + "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", + "peerDependencies": { + "postcss": "^8.0.3" + } + }, + "node_modules/postcss-selector-not": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-6.0.1.tgz", + "integrity": "sha512-1i9affjAe9xu/y9uqWH+tD4r6/hDaXJruk8xn2x1vzxC2U3J3LKO3zJW4CyxlNhA56pADJ/djpEwpH1RClI2rQ==", + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz", + "integrity": "sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-svgo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.1.0.tgz", + "integrity": "sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "svgo": "^2.7.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-svgo/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/postcss-svgo/node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/postcss-svgo/node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" + }, + "node_modules/postcss-svgo/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postcss-svgo/node_modules/svgo": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", + "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", + "dependencies": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^4.1.3", + "css-tree": "^1.1.3", + "csso": "^4.2.0", + "picocolors": "^1.0.0", + "stable": "^0.1.8" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/postcss-unique-selectors": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz", + "integrity": "sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==", + "dependencies": { + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pretty-error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", + "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", + "dependencies": { + "lodash": "^4.17.20", + "renderkid": "^3.0.0" + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prismjs": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", + "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "engines": { + "node": ">=6" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/promise": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", + "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", + "dependencies": { + "asap": "~2.0.6" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/property-information": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", + "integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==", + "dependencies": { + "xtend": "^4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "dependencies": { + "performance-now": "^2.1.0" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-app-polyfill": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-3.0.0.tgz", + "integrity": "sha512-sZ41cxiU5llIB003yxxQBYrARBqe0repqPTTYBTmMqTz9szeBbE37BehCE891NZsmdZqqP+xWKdT3eo3vOzN8w==", + "dependencies": { + "core-js": "^3.19.2", + "object-assign": "^4.1.1", + "promise": "^8.1.0", + "raf": "^3.4.1", + "regenerator-runtime": "^0.13.9", + "whatwg-fetch": "^3.6.2" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/react-dev-utils": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", + "integrity": "sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ==", + "dependencies": { + "@babel/code-frame": "^7.16.0", + "address": "^1.1.2", + "browserslist": "^4.18.1", + "chalk": "^4.1.2", + "cross-spawn": "^7.0.3", + "detect-port-alt": "^1.1.6", + "escape-string-regexp": "^4.0.0", + "filesize": "^8.0.6", + "find-up": "^5.0.0", + "fork-ts-checker-webpack-plugin": "^6.5.0", + "global-modules": "^2.0.0", + "globby": "^11.0.4", + "gzip-size": "^6.0.0", + "immer": "^9.0.7", + "is-root": "^2.1.0", + "loader-utils": "^3.2.0", + "open": "^8.4.0", + "pkg-up": "^3.1.0", + "prompts": "^2.4.2", + "react-error-overlay": "^6.0.11", + "recursive-readdir": "^2.2.2", + "shell-quote": "^1.7.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/react-dev-utils/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/react-dev-utils/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/react-dev-utils/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/react-dev-utils/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/react-dev-utils/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react-dev-utils/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/react-dev-utils/node_modules/loader-utils": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz", + "integrity": "sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/react-dev-utils/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/react-error-overlay": { + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", + "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + }, + "node_modules/react-refresh": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", + "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.10.0.tgz", + "integrity": "sha512-Nrg0BWpQqrC3ZFFkyewrflCud9dio9ME3ojHCF/WLsprJVzkq3q3UeEhMCAW1dobjeGbWgjNn/PVF6m46ANxXQ==", + "dependencies": { + "@remix-run/router": "1.5.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.10.0.tgz", + "integrity": "sha512-E5dfxRPuXKJqzwSe/qGcqdwa18QiWC6f3H3cWXM24qj4N0/beCIf/CWTipop2xm7mR0RCS99NnaqPNjHtrAzCg==", + "dependencies": { + "@remix-run/router": "1.5.0", + "react-router": "6.10.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/react-scripts": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", + "integrity": "sha512-8VAmEm/ZAwQzJ+GOMLbBsTdDKOpuZh7RPs0UymvBR2vRk4iZWCskjbFnxqjrzoIvlNNRZ3QJFx6/qDSi6zSnaQ==", + "dependencies": { + "@babel/core": "^7.16.0", + "@pmmmwh/react-refresh-webpack-plugin": "^0.5.3", + "@svgr/webpack": "^5.5.0", + "babel-jest": "^27.4.2", + "babel-loader": "^8.2.3", + "babel-plugin-named-asset-import": "^0.3.8", + "babel-preset-react-app": "^10.0.1", + "bfj": "^7.0.2", + "browserslist": "^4.18.1", + "camelcase": "^6.2.1", + "case-sensitive-paths-webpack-plugin": "^2.4.0", + "css-loader": "^6.5.1", + "css-minimizer-webpack-plugin": "^3.2.0", + "dotenv": "^10.0.0", + "dotenv-expand": "^5.1.0", + "eslint": "^8.3.0", + "eslint-config-react-app": "^7.0.1", + "eslint-webpack-plugin": "^3.1.1", + "file-loader": "^6.2.0", + "fs-extra": "^10.0.0", + "html-webpack-plugin": "^5.5.0", + "identity-obj-proxy": "^3.0.0", + "jest": "^27.4.3", + "jest-resolve": "^27.4.2", + "jest-watch-typeahead": "^1.0.0", + "mini-css-extract-plugin": "^2.4.5", + "postcss": "^8.4.4", + "postcss-flexbugs-fixes": "^5.0.2", + "postcss-loader": "^6.2.1", + "postcss-normalize": "^10.0.1", + "postcss-preset-env": "^7.0.1", + "prompts": "^2.4.2", + "react-app-polyfill": "^3.0.0", + "react-dev-utils": "^12.0.1", + "react-refresh": "^0.11.0", + "resolve": "^1.20.0", + "resolve-url-loader": "^4.0.0", + "sass-loader": "^12.3.0", + "semver": "^7.3.5", + "source-map-loader": "^3.0.0", + "style-loader": "^3.3.1", + "tailwindcss": "^3.0.2", + "terser-webpack-plugin": "^5.2.5", + "webpack": "^5.64.4", + "webpack-dev-server": "^4.6.0", + "webpack-manifest-plugin": "^4.0.2", + "workbox-webpack-plugin": "^6.4.1" + }, + "bin": { + "react-scripts": "bin/react-scripts.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + }, + "peerDependencies": { + "react": ">= 16", + "typescript": "^3.2.1 || ^4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/react-syntax-highlighter": { + "version": "15.5.0", + "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.5.0.tgz", + "integrity": "sha512-+zq2myprEnQmH5yw6Gqc8lD55QHnpKaU8TOcFeC/Lg/MQSs8UknEA0JC4nTZGFAXC2J2Hyj/ijJ7NlabyPi2gg==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "highlight.js": "^10.4.1", + "lowlight": "^1.17.0", + "prismjs": "^1.27.0", + "refractor": "^3.6.0" + }, + "peerDependencies": { + "react": ">= 0.14.0" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/recursive-readdir": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz", + "integrity": "sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==", + "dependencies": { + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/refractor": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz", + "integrity": "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==", + "dependencies": { + "hastscript": "^6.0.0", + "parse-entities": "^2.0.0", + "prismjs": "~1.27.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/prismjs": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz", + "integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", + "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, + "node_modules/regenerator-transform": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz", + "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==", + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regex-parser": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz", + "integrity": "sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==" + }, + "node_modules/regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/regexpu-core": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "dependencies": { + "@babel/regjsgen": "^0.8.0", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/remove-accents": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz", + "integrity": "sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==" + }, + "node_modules/renderkid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", + "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", + "dependencies": { + "css-select": "^4.1.3", + "dom-converter": "^0.2.0", + "htmlparser2": "^6.1.0", + "lodash": "^4.17.21", + "strip-ansi": "^6.0.1" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, + "node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-url-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-4.0.0.tgz", + "integrity": "sha512-05VEMczVREcbtT7Bz+C+96eUO5HDNvdthIiMB34t7FcF8ehcu4wC0sSgPUubs3XW2Q3CNLJk/BJrCU9wVRymiA==", + "dependencies": { + "adjust-sourcemap-loader": "^4.0.0", + "convert-source-map": "^1.7.0", + "loader-utils": "^2.0.0", + "postcss": "^7.0.35", + "source-map": "0.6.1" + }, + "engines": { + "node": ">=8.9" + }, + "peerDependencies": { + "rework": "1.0.1", + "rework-visit": "1.0.0" + }, + "peerDependenciesMeta": { + "rework": { + "optional": true + }, + "rework-visit": { + "optional": true + } + } + }, + "node_modules/resolve-url-loader/node_modules/picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" + }, + "node_modules/resolve-url-loader/node_modules/postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dependencies": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + } + }, + "node_modules/resolve-url-loader/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve.exports": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.1.tgz", + "integrity": "sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rewire": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/rewire/-/rewire-6.0.0.tgz", + "integrity": "sha512-7sZdz5dptqBCapJYocw9EcppLU62KMEqDLIILJnNET2iqzXHaQfaVP5SOJ06XvjX+dNIDJbzjw0ZWzrgDhtjYg==", + "dev": true, + "dependencies": { + "eslint": "^7.32.0" + } + }, + "node_modules/rewire/node_modules/@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.10.4" + } + }, + "node_modules/rewire/node_modules/@eslint/eslintrc": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", + "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/rewire/node_modules/@humanwhocodes/config-array": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", + "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.0", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/rewire/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/rewire/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/rewire/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/rewire/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/rewire/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/rewire/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/rewire/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rewire/node_modules/eslint": { + "version": "7.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", + "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.3", + "@humanwhocodes/config-array": "^0.5.0", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/rewire/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/rewire/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/rewire/node_modules/espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "dependencies": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/rewire/node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/rewire/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/rewire/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/rewire/node_modules/globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rewire/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/rewire/node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/rewire/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/rewire/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/rewire/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "2.79.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", + "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=10.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup-plugin-terser": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz", + "integrity": "sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==", + "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-terser", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "jest-worker": "^26.2.1", + "serialize-javascript": "^4.0.0", + "terser": "^5.0.0" + }, + "peerDependencies": { + "rollup": "^2.0.0" + } + }, + "node_modules/rollup-plugin-terser/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/rollup-plugin-terser/node_modules/jest-worker": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", + "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/rollup-plugin-terser/node_modules/serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/rollup-plugin-terser/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/sanitize.css": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/sanitize.css/-/sanitize.css-13.0.0.tgz", + "integrity": "sha512-ZRwKbh/eQ6w9vmTjkuG0Ioi3HBwPFce0O+v//ve+aOq1oeCy7jMV2qzzAlpsNuqpqCBjjriM1lbtZbF/Q8jVyA==" + }, + "node_modules/sass-loader": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-12.6.0.tgz", + "integrity": "sha512-oLTaH0YCtX4cfnJZxKSLAyglED0naiYfNG1iXfU5w1LNZ+ukoA5DtyDIN5zmKVZwYNJP4KRc5Y3hkWga+7tYfA==", + "dependencies": { + "klona": "^2.0.4", + "neo-async": "^2.6.2" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "fibers": ">= 3.1.0", + "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", + "sass": "^1.3.0", + "sass-embedded": "*", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "fibers": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + } + } + }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "node_modules/saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==" + }, + "node_modules/selfsigned": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.1.1.tgz", + "integrity": "sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==", + "dependencies": { + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.0.tgz", + "integrity": "sha512-QHsz8GgQIGKlRi24yFc6a6lN69Idnx634w49ay6+jA5yFh7a1UY+4Rp6HPx/L/1zcEDPEij8cIsiqR6bQsE5VQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/slice-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/sort-by": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/sort-by/-/sort-by-1.2.0.tgz", + "integrity": "sha512-aRyW65r3xMnf4nxJRluCg0H/woJpksU1dQxRtXYzau30sNBOmf5HACpDd9MZDhKh7ALQ5FgSOfMPwZEtUmMqcg==", + "dependencies": { + "object-path": "0.6.0" + } + }, + "node_modules/source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==" + }, + "node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-loader": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-3.0.2.tgz", + "integrity": "sha512-BokxPoLjyl3iOrgkWaakaxqnelAJSS+0V+De0kKIq6lyWrXuiPgYTGp6z3iHmqljKAaLXwZa+ctD8GccRJeVvg==", + "dependencies": { + "abab": "^2.0.5", + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "deprecated": "Please use @jridgewell/sourcemap-codec instead" + }, + "node_modules/space-separated-tokens": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", + "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + }, + "node_modules/stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "deprecated": "Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/stackframe": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==" + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stop-iteration-iterator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", + "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", + "dependencies": { + "internal-slot": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-natural-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/string-natural-compare/-/string-natural-compare-3.0.1.tgz", + "integrity": "sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz", + "integrity": "sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "regexp.prototype.flags": "^1.4.3", + "side-channel": "^1.0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", + "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "dependencies": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz", + "integrity": "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==", + "engines": { + "node": ">=10" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-loader": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.2.tgz", + "integrity": "sha512-RHs/vcrKdQK8wZliteNK4NKzxvLBzpuHMqYmUVWeKa6MkaIQ97ZTOS0b+zapZhy6GcrgWnvWYCMHRirC3FsUmw==", + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/stylehacks": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", + "integrity": "sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==", + "dependencies": { + "browserslist": "^4.21.4", + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/stylis": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.1.3.tgz", + "integrity": "sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==" + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-hyperlinks": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", + "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svg-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==" + }, + "node_modules/svgo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", + "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==", + "deprecated": "This SVGO version is no longer supported. Upgrade to v2.x.x.", + "dependencies": { + "chalk": "^2.4.1", + "coa": "^2.0.2", + "css-select": "^2.0.0", + "css-select-base-adapter": "^0.1.1", + "css-tree": "1.0.0-alpha.37", + "csso": "^4.0.2", + "js-yaml": "^3.13.1", + "mkdirp": "~0.5.1", + "object.values": "^1.1.0", + "sax": "~1.2.4", + "stable": "^0.1.8", + "unquote": "~1.1.1", + "util.promisify": "~1.0.0" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/svgo/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/svgo/node_modules/css-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", + "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^3.2.1", + "domutils": "^1.7.0", + "nth-check": "^1.0.2" + } + }, + "node_modules/svgo/node_modules/css-what": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz", + "integrity": "sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/svgo/node_modules/dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "dependencies": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + } + }, + "node_modules/svgo/node_modules/domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "dependencies": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "node_modules/svgo/node_modules/domutils/node_modules/domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" + }, + "node_modules/svgo/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/svgo/node_modules/nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "dependencies": { + "boolbase": "~1.0.0" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" + }, + "node_modules/table": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", + "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/table/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/tailwindcss": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.2.7.tgz", + "integrity": "sha512-B6DLqJzc21x7wntlH/GsZwEXTBttVSl1FtCzC8WP4oBc/NKef7kaax5jeihkkCEWc831/5NDJ9gRNDK6NEioQQ==", + "dependencies": { + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "color-name": "^1.1.4", + "detective": "^5.2.1", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.2.12", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "lilconfig": "^2.0.6", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.0.9", + "postcss-import": "^14.1.0", + "postcss-js": "^4.0.0", + "postcss-load-config": "^3.1.4", + "postcss-nested": "6.0.0", + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0", + "quick-lru": "^5.1.1", + "resolve": "^1.22.1" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=12.13.0" + }, + "peerDependencies": { + "postcss": "^8.0.9" + } + }, + "node_modules/tailwindcss/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/temp-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", + "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/tempy": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.6.0.tgz", + "integrity": "sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==", + "dependencies": { + "is-stream": "^2.0.0", + "temp-dir": "^2.0.0", + "type-fest": "^0.16.0", + "unique-string": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tempy/node_modules/type-fest": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", + "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terminal-link": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", + "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "dependencies": { + "ansi-escapes": "^4.2.1", + "supports-hyperlinks": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terser": { + "version": "5.16.6", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.6.tgz", + "integrity": "sha512-IBZ+ZQIA9sMaXmRZCUMDjNH0D5AQQfdn4WUjHL0+1lF4TP1IHRJbrhb6fNaXWikrYQTSkb7SLxkeXAiy1p7mbg==", + "dependencies": { + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.7.tgz", + "integrity": "sha512-AfKwIktyP7Cu50xNjXF/6Qb5lBNzYaWpU6YfoX3uZicTx0zTy0stDDCsvjDapKsSDvOeWo5MEq4TmdBy2cNoHw==", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.17", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.16.5" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" + }, + "node_modules/throat": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.2.tgz", + "integrity": "sha512-WKexMoJj3vEuK0yFEapj8y64V0A6xcuPuK9Gt1d0R+dzCSJc0lHqQytAbSB4cDAK0dWh4T0E2ETkoLE2WZ41OQ==" + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==" + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tough-cookie": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", + "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/tr46": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", + "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tryer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", + "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==" + }, + "node_modules/tsconfig-paths": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", + "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "engines": { + "node": ">=4" + } + }, + "node_modules/unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dependencies": { + "crypto-random-string": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/unquote": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", + "integrity": "sha512-vRCqFv6UhXpWxZPyGDh/F3ZpNv8/qo7w6iufLpQg9aKnQ71qM4B5KiI7Mia9COcjEhrO9LueHpMYjYzsWH3OIg==" + }, + "node_modules/upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "engines": { + "node": ">=4", + "yarn": "*" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", + "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist-lint": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/util.promisify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz", + "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.2", + "has-symbols": "^1.0.1", + "object.getownpropertydescriptors": "^2.1.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, + "node_modules/v8-to-istanbul": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz", + "integrity": "sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0", + "source-map": "^0.7.3" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "deprecated": "Use your platform's native performance.now() and performance.timeOrigin.", + "dependencies": { + "browser-process-hrtime": "^1.0.0" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", + "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", + "dependencies": { + "xml-name-validator": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/web-vitals": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-2.1.4.tgz", + "integrity": "sha512-sVWcwhU5mX6crfI5Vd2dC4qchyTqxV8URinzt25XqVh+bHEPGH4C3NPrNionCP7Obx59wrYEbNlw4Z8sjALzZg==" + }, + "node_modules/webidl-conversions": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", + "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", + "engines": { + "node": ">=10.4" + } + }, + "node_modules/webpack": { + "version": "5.76.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.2.tgz", + "integrity": "sha512-Th05ggRm23rVzEOlX8y67NkYCHa9nTNcwHPBhdg+lKG+mtiW7XgggjAeeLnADAe7mLjJ6LUNfgHAuRRh+Z6J7w==", + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^0.0.51", + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/wasm-edit": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.7.6", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.10.0", + "es-module-lexer": "^0.9.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.1.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.1.3", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-middleware": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", + "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^3.4.3", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/webpack-dev-middleware/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/webpack-dev-middleware/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/webpack-dev-middleware/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/webpack-dev-middleware/node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/webpack-dev-server": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.13.1.tgz", + "integrity": "sha512-5tWg00bnWbYgkN+pd5yISQKDejRBYGEw15RaEEslH+zdbNDxxaZvEAO2WulaSaFKb5n3YG8JXsGaDsut1D0xdA==", + "dependencies": { + "@types/bonjour": "^3.5.9", + "@types/connect-history-api-fallback": "^1.3.5", + "@types/express": "^4.17.13", + "@types/serve-index": "^1.9.1", + "@types/serve-static": "^1.13.10", + "@types/sockjs": "^0.3.33", + "@types/ws": "^8.5.1", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.0.11", + "chokidar": "^3.5.3", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", + "graceful-fs": "^4.2.6", + "html-entities": "^2.3.2", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.0.1", + "launch-editor": "^2.6.0", + "open": "^8.0.9", + "p-retry": "^4.5.0", + "rimraf": "^3.0.2", + "schema-utils": "^4.0.0", + "selfsigned": "^2.1.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^5.3.1", + "ws": "^8.13.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.37.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/webpack-dev-server/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/webpack-dev-server/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/webpack-dev-server/node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/webpack-dev-server/node_modules/ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/webpack-manifest-plugin": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/webpack-manifest-plugin/-/webpack-manifest-plugin-4.1.1.tgz", + "integrity": "sha512-YXUAwxtfKIJIKkhg03MKuiFAD72PlrqCiwdwO4VEXdRO5V0ORCNwaOwAZawPZalCbmH9kBDmXnNeQOw+BIEiow==", + "dependencies": { + "tapable": "^2.0.0", + "webpack-sources": "^2.2.0" + }, + "engines": { + "node": ">=12.22.0" + }, + "peerDependencies": { + "webpack": "^4.44.2 || ^5.47.0" + } + }, + "node_modules/webpack-manifest-plugin/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-manifest-plugin/node_modules/webpack-sources": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.3.1.tgz", + "integrity": "sha512-y9EI9AO42JjEcrTJFOYmVywVZdKVUfOvDUPsJea5GIr1JOEGFVqwlY2K098fFoIjOkDzHn2AjRvM8dsBZu+gCA==", + "dependencies": { + "source-list-map": "^2.0.1", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/@types/estree": { + "version": "0.0.51", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", + "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==" + }, + "node_modules/webpack/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/webpack/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "dependencies": { + "iconv-lite": "0.4.24" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-fetch": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", + "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==" + }, + "node_modules/whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==" + }, + "node_modules/whatwg-url": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", + "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", + "dependencies": { + "lodash": "^4.7.0", + "tr46": "^2.1.0", + "webidl-conversions": "^6.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", + "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "dependencies": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/workbox-background-sync": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-6.5.4.tgz", + "integrity": "sha512-0r4INQZMyPky/lj4Ou98qxcThrETucOde+7mRGJl13MPJugQNKeZQOdIJe/1AchOP23cTqHcN/YVpD6r8E6I8g==", + "dependencies": { + "idb": "^7.0.1", + "workbox-core": "6.5.4" + } + }, + "node_modules/workbox-broadcast-update": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-6.5.4.tgz", + "integrity": "sha512-I/lBERoH1u3zyBosnpPEtcAVe5lwykx9Yg1k6f8/BGEPGaMMgZrwVrqL1uA9QZ1NGGFoyE6t9i7lBjOlDhFEEw==", + "dependencies": { + "workbox-core": "6.5.4" + } + }, + "node_modules/workbox-build": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-6.5.4.tgz", + "integrity": "sha512-kgRevLXEYvUW9WS4XoziYqZ8Q9j/2ziJYEtTrjdz5/L/cTUa2XfyMP2i7c3p34lgqJ03+mTiz13SdFef2POwbA==", + "dependencies": { + "@apideck/better-ajv-errors": "^0.3.1", + "@babel/core": "^7.11.1", + "@babel/preset-env": "^7.11.0", + "@babel/runtime": "^7.11.2", + "@rollup/plugin-babel": "^5.2.0", + "@rollup/plugin-node-resolve": "^11.2.1", + "@rollup/plugin-replace": "^2.4.1", + "@surma/rollup-plugin-off-main-thread": "^2.2.3", + "ajv": "^8.6.0", + "common-tags": "^1.8.0", + "fast-json-stable-stringify": "^2.1.0", + "fs-extra": "^9.0.1", + "glob": "^7.1.6", + "lodash": "^4.17.20", + "pretty-bytes": "^5.3.0", + "rollup": "^2.43.1", + "rollup-plugin-terser": "^7.0.0", + "source-map": "^0.8.0-beta.0", + "stringify-object": "^3.3.0", + "strip-comments": "^2.0.1", + "tempy": "^0.6.0", + "upath": "^1.2.0", + "workbox-background-sync": "6.5.4", + "workbox-broadcast-update": "6.5.4", + "workbox-cacheable-response": "6.5.4", + "workbox-core": "6.5.4", + "workbox-expiration": "6.5.4", + "workbox-google-analytics": "6.5.4", + "workbox-navigation-preload": "6.5.4", + "workbox-precaching": "6.5.4", + "workbox-range-requests": "6.5.4", + "workbox-recipes": "6.5.4", + "workbox-routing": "6.5.4", + "workbox-strategies": "6.5.4", + "workbox-streams": "6.5.4", + "workbox-sw": "6.5.4", + "workbox-window": "6.5.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/workbox-build/node_modules/@apideck/better-ajv-errors": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.6.tgz", + "integrity": "sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==", + "dependencies": { + "json-schema": "^0.4.0", + "jsonpointer": "^5.0.0", + "leven": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "ajv": ">=8" + } + }, + "node_modules/workbox-build/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/workbox-build/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/workbox-build/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/workbox-build/node_modules/source-map": { + "version": "0.8.0-beta.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", + "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "dependencies": { + "whatwg-url": "^7.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/workbox-build/node_modules/tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/workbox-build/node_modules/webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" + }, + "node_modules/workbox-build/node_modules/whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dependencies": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "node_modules/workbox-cacheable-response": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-6.5.4.tgz", + "integrity": "sha512-DCR9uD0Fqj8oB2TSWQEm1hbFs/85hXXoayVwFKLVuIuxwJaihBsLsp4y7J9bvZbqtPJ1KlCkmYVGQKrBU4KAug==", + "dependencies": { + "workbox-core": "6.5.4" + } + }, + "node_modules/workbox-core": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-6.5.4.tgz", + "integrity": "sha512-OXYb+m9wZm8GrORlV2vBbE5EC1FKu71GGp0H4rjmxmF4/HLbMCoTFws87M3dFwgpmg0v00K++PImpNQ6J5NQ6Q==" + }, + "node_modules/workbox-expiration": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-6.5.4.tgz", + "integrity": "sha512-jUP5qPOpH1nXtjGGh1fRBa1wJL2QlIb5mGpct3NzepjGG2uFFBn4iiEBiI9GUmfAFR2ApuRhDydjcRmYXddiEQ==", + "dependencies": { + "idb": "^7.0.1", + "workbox-core": "6.5.4" + } + }, + "node_modules/workbox-google-analytics": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-6.5.4.tgz", + "integrity": "sha512-8AU1WuaXsD49249Wq0B2zn4a/vvFfHkpcFfqAFHNHwln3jK9QUYmzdkKXGIZl9wyKNP+RRX30vcgcyWMcZ9VAg==", + "dependencies": { + "workbox-background-sync": "6.5.4", + "workbox-core": "6.5.4", + "workbox-routing": "6.5.4", + "workbox-strategies": "6.5.4" + } + }, + "node_modules/workbox-navigation-preload": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-6.5.4.tgz", + "integrity": "sha512-IIwf80eO3cr8h6XSQJF+Hxj26rg2RPFVUmJLUlM0+A2GzB4HFbQyKkrgD5y2d84g2IbJzP4B4j5dPBRzamHrng==", + "dependencies": { + "workbox-core": "6.5.4" + } + }, + "node_modules/workbox-precaching": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-6.5.4.tgz", + "integrity": "sha512-hSMezMsW6btKnxHB4bFy2Qfwey/8SYdGWvVIKFaUm8vJ4E53JAY+U2JwLTRD8wbLWoP6OVUdFlXsTdKu9yoLTg==", + "dependencies": { + "workbox-core": "6.5.4", + "workbox-routing": "6.5.4", + "workbox-strategies": "6.5.4" + } + }, + "node_modules/workbox-range-requests": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-6.5.4.tgz", + "integrity": "sha512-Je2qR1NXCFC8xVJ/Lux6saH6IrQGhMpDrPXWZWWS8n/RD+WZfKa6dSZwU+/QksfEadJEr/NfY+aP/CXFFK5JFg==", + "dependencies": { + "workbox-core": "6.5.4" + } + }, + "node_modules/workbox-recipes": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-6.5.4.tgz", + "integrity": "sha512-QZNO8Ez708NNwzLNEXTG4QYSKQ1ochzEtRLGaq+mr2PyoEIC1xFW7MrWxrONUxBFOByksds9Z4//lKAX8tHyUA==", + "dependencies": { + "workbox-cacheable-response": "6.5.4", + "workbox-core": "6.5.4", + "workbox-expiration": "6.5.4", + "workbox-precaching": "6.5.4", + "workbox-routing": "6.5.4", + "workbox-strategies": "6.5.4" + } + }, + "node_modules/workbox-routing": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-6.5.4.tgz", + "integrity": "sha512-apQswLsbrrOsBUWtr9Lf80F+P1sHnQdYodRo32SjiByYi36IDyL2r7BH1lJtFX8fwNHDa1QOVY74WKLLS6o5Pg==", + "dependencies": { + "workbox-core": "6.5.4" + } + }, + "node_modules/workbox-strategies": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-6.5.4.tgz", + "integrity": "sha512-DEtsxhx0LIYWkJBTQolRxG4EI0setTJkqR4m7r4YpBdxtWJH1Mbg01Cj8ZjNOO8etqfA3IZaOPHUxCs8cBsKLw==", + "dependencies": { + "workbox-core": "6.5.4" + } + }, + "node_modules/workbox-streams": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-6.5.4.tgz", + "integrity": "sha512-FXKVh87d2RFXkliAIheBojBELIPnWbQdyDvsH3t74Cwhg0fDheL1T8BqSM86hZvC0ZESLsznSYWw+Va+KVbUzg==", + "dependencies": { + "workbox-core": "6.5.4", + "workbox-routing": "6.5.4" + } + }, + "node_modules/workbox-sw": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-6.5.4.tgz", + "integrity": "sha512-vo2RQo7DILVRoH5LjGqw3nphavEjK4Qk+FenXeUsknKn14eCNedHOXWbmnvP4ipKhlE35pvJ4yl4YYf6YsJArA==" + }, + "node_modules/workbox-webpack-plugin": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/workbox-webpack-plugin/-/workbox-webpack-plugin-6.5.4.tgz", + "integrity": "sha512-LmWm/zoaahe0EGmMTrSLUi+BjyR3cdGEfU3fS6PN1zKFYbqAKuQ+Oy/27e4VSXsyIwAw8+QDfk1XHNGtZu9nQg==", + "dependencies": { + "fast-json-stable-stringify": "^2.1.0", + "pretty-bytes": "^5.4.1", + "upath": "^1.2.0", + "webpack-sources": "^1.4.3", + "workbox-build": "6.5.4" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "webpack": "^4.4.0 || ^5.9.0" + } + }, + "node_modules/workbox-webpack-plugin/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/workbox-webpack-plugin/node_modules/webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "dependencies": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + } + }, + "node_modules/workbox-window": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-6.5.4.tgz", + "integrity": "sha512-HnLZJDwYBE+hpG25AQBO8RUWBJRaCsI9ksQJEp3aCOFCaG5kqaToAYXFRAHxzRluM2cQbGzdQF5rjKPWPA1fug==", + "dependencies": { + "@types/trusted-types": "^2.0.2", + "workbox-core": "6.5.4" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==" + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/pkg/webui/ui/package.json b/pkg/webui/ui/package.json new file mode 100644 index 000000000..eea5b6cc0 --- /dev/null +++ b/pkg/webui/ui/package.json @@ -0,0 +1,67 @@ +{ + "name": "react-demo", + "version": "0.1.0", + "homepage": "/", + "private": true, + "proxy": "http://localhost:8080", + "dependencies": { + "@emotion/react": "^11.10.6", + "@emotion/styled": "^11.10.6", + "@fontsource/roboto": "^4.5.8", + "@mui/icons-material": "^5.11.11", + "@mui/lab": "^5.0.0-alpha.124", + "@mui/material": "^5.11.14", + "@testing-library/jest-dom": "^5.16.5", + "@testing-library/react": "^13.4.0", + "@testing-library/user-event": "^13.5.0", + "@types/jest": "^27.5.2", + "@types/node": "^16.18.18", + "@types/react": "^18.0.28", + "@types/react-dom": "^18.0.11", + "jdenticon": "^3.2.0", + "js-sha256": "^0.9.0", + "js-yaml": "^4.1.0", + "localforage": "^1.10.0", + "lodash-core": "^4.17.19", + "match-sorter": "^6.3.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router": "^6.10.0", + "react-router-dom": "^6.10.0", + "react-scripts": "5.0.1", + "react-syntax-highlighter": "^15.5.0", + "sort-by": "^1.2.0", + "typescript": "^4.9.5", + "web-vitals": "^2.1.4" + }, + "scripts": { + "start": "react-scripts start", + "build": "node build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "devDependencies": { + "@types/js-yaml": "^4.0.5", + "@types/lodash": "^4.14.192", + "@types/react-syntax-highlighter": "^15.5.6", + "rewire": "^6.0.0" + } +} diff --git a/pkg/webui/ui/public/.gitignore b/pkg/webui/ui/public/.gitignore new file mode 100644 index 000000000..c9e9f1beb --- /dev/null +++ b/pkg/webui/ui/public/.gitignore @@ -0,0 +1 @@ +staticdata \ No newline at end of file diff --git a/pkg/webui/ui/public/favicon.ico b/pkg/webui/ui/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..496bbc69cabfce158be2fbc7e709e8a81a5bc95e GIT binary patch literal 15406 zcmeHO3v^V)8D6vxTWwWZwYJ*gQCn+UYisN2sUD@R)x%@&y$Qy*0*Z(SJr#k_~K1sI8X6j^_Pc$c$$5q)hSqBj@C_1{Q@LcIGU`Zm8@bQIo zzk~u!KkI6i3>TnrqpY`Mdm0cH{5ED!1ok3XvY zW7L#BgJX;QCBv(4O7+QDKo8fdFm~@AkLM(R#N7+HyD*LmAlGiZ?+HY- zc^H?EeNm&YXF+FJYug{k6#Xs)?1m)Yk%WMrMqw<+B;NJnM4Pr5_Tcl>8Z>Y72i#J& zjhWh4JU^xP3;{i}0X$=T_|7`en?|_eNjHn}vj>i2J%7HiG2+>pyTts}6$ge+?Ece4 zE$|~+BX~8)Z}jQ>XKU^}Fnh&|A~b)<2w7h;v&*-^=MFrV$+|$5erZBpT?C?en3ahi zVF*hcS!Y)UqQ?Eiub4X+_{RNqFq!H+5u?-db-fzv>Yv->jk_<%1bZxP^fR6CbjCyf z)ZU*zYCMSkHp6BPKtGj5@#{`^qQy4AhA=*#(N_`I2{xB{)o;l$gA5TTT-Z@K1&1-h z_zKui4=4C+^_Z8{D!t_U=MR>o+X(#Bx6poAg0adXs{m4@{jy8b(8wMcrh^hUe=(gyZ2+0Js#n=gsj!on8Pu)-eA zcOb0w1id3F?LbJ&x6*(QxLLxCs=G(TUh}pD!uowyxb}SeBJMjuyHyWieYlkl+DA(M z$+Eu{=2*UA+ujuYu(sMt3+v0dJUo7jbWnpFa-(pUU z16(C}vK>78NZHLY%sH$Lby^3wCMDtaVQs(5w2iR7FG0fySb6fBw7enB0~k~G1T5kJ zlQ-5)^e6q%8hsJtPOB~pqwYVZ!zS>95$$&>f3E425&dbj#fj?V-Nt97!)co_kDj9L zO&bX5qtjs${1H!#B1~eOw4s1==$mfFNoYR=9n5l~XH$RBrp~H6TNuWvm9IW7%I6Fc zQ|q4-4O{9N8%_&iJqArPR!q_+KILXiw+YY3c&t2f`%c(J6Y!gc$8Xv?{*?iTYF;lE zQ{EUUiYMQ^YFJhNc^ogsK=i5JsBxjk<2p$lL%t8I)4o?&rCkMGt;{A1#$XlW% z+B(g#ArJDh<74G({l?_jg$Fj?*{e#PQkq!!MM%l5ys7;_LE6v>$`-^lZFS z?O|<>FO>hYW95&1%y+Oaco_ZOD`}&DlNr;XTqx^ME`krtV{DYFGvhVgmu+8WC!;@_ z_iOr8JRhd$nR7rJD`R-tkZj{#EJ>#WY_vqyn`M-~Q5#LaWyedc!y526;Bd~R%`dEP zRqD9X&r3MQ^a`C!cCyhMcK=m@O}#>Q=YZZPlBO)jPP-4XX!VTmQowkDkGodFSZS7) zUhz$|+3ocN)}MWzioCN`T6_omZIYfWm#mG8aWfv+>kD;Fvn4NL8wm%ReXR5_cITsg zPYMP)9Y1S;Sg^VhYp45GD?InVD(u2F5mCRsmg6wM3d?WU-%Apne%~I({g8xVIrpuX z#p1Ow5eS?6@ir&}XzLMuBjIXae@1NDzEX^=zJ0w_?-B`iCikTD#Ya=|?wFw`~omgRFE7H>B#5KGoh65fdBIUclgp}}?B7uAN1p4w;k=&ASaX1s5g zzsZHx|2howB;0WH!gDNn5}>#Fc`Mu(aExH5+hDJUkq=qmuhhDE%6pyC1~9`1VPRJq zXmH>4IqYsbXbmUr3~0yPl#Jh($vLK!pJI(3;^dQ6#tGkR48hv@nSw8U)>3b!_6rsM zc)iEDPYr8zHatzK-yW z?wc_FU*LHHbUI|9)d4x@#~@>yyb1dZg9c`w6gXTbN@!6G*dP%7pl5F7usE{1{)u2fU`r|IFlD#mbGdaaQGT zDT?-(X?N!2M6W&d>;qy{?#I4Rb${21o?6d50_JmTJ~OH}$Q&f$=o3Mk zc~8^!;b%XjA5&>Ky>m~EHT#hy{+5J{TBTg$$OrV)oRyqk^zk(uk6iXJIIDgD@Y1&& z#NPU84{66KyP0E8=6?;u+{Qgl^2&PhByXJM@~kgyy`jvM9p@6v-)CA=kz+T8Fuy8u zWRizkQr5P4T%46g^%dZ=0`w7ICv*?lm%+!{<{wCpJc4oUBH%p*zTlnaucUqraK;gMm+7#AP(Fcj^)yCP-GR<6NbXDD$=13qPEuDHYE1!k@=w$RI* zCG%nZbH#OlLmXt-#X?#7VB%!Ngq>|4b98N&OKY0KLxIKH*Fvp zk6HJloYV9f`_a#RfZ5N7dc^8DyHsHG%WSQ{MGQ!0C^%tU_6XwwE}!z2?{$y@JT=qW(7c;hrWO@>Iw< zjzXVm@%=vh40*RhX7ip?9enK=(*|)@34D8z4a;@cJ;0*|yeMOTtcT}N z_S6r4i+d0DiEBnCU&(ww?^>8^Vj|z(e6;dI&&^naI@53Q95xd_&ULP(dV`FuGH~v~mvWE7_#C-e?r9F-k-GnscaDZt z=by*e4D}(UjW8|1`v`ur+8^$bx|8T6N3QkMb)Y8i2k^C*fqpTb)OoSOpX*sUV;M=8 zevUd#!G!gxs*MyBAeS07&-tutCi`*_{31mW{m=CAr7QY6=JO;!Yf0bEJHF6a5HimO zuiYtGT6IcZhhRr9QeKtDAjGI>E0;LPSyk{3unJRDT5vkrN8@4tNz3vuKdmi>9)@GQ z-MQKp?__{hIdCX*TN|2c77G`7$%tjeBLm>9?IpJnN4(XO3G^F6=xRj{ zF$nGIpQ@dv@r5oS{{km{RJ{6_n7M2$`f$re*w>$|`10G_GvY2?D;;e$>twsN3?hdD zTlse@d>j3X=WBb3@B)v(S^7NQ*|*}$Z{7h${M2Yir)vI_$Zt1@QB&{X9oj*e_Qc*$ zkBgJ`m$VCQN3c@6Nb<3k)G6Y%f#`$Xd^UCV!R{u3xU(_S{T1deA-g3XrHj&;4~VV1 z-W4;KzW{lRWr(F5bF`H0=_+>@s}6X_-72^G?TO#e5p&g=-!kv|u56qmcJAFGo~`Yf zF~7=r2OEp2yyLhdK-stKSSOyV>4|ko>!$Lxd&k|Jwz(e8gT&d49eApaYZ%~;aE@`G z6^!NMuB=b&JFrX4Uo{alv{$&}t=>H0czpS5GpX58;66T>Inw2QjS6lY7K@Ev)k3}O7Q z1^g)N9eWg`q$G!Y8Sf}rW#>2i%aKfTSn?(h@&XR>Xa;@wPV{LVWs)$tl*Ki9XM3aF zuje|(`M-@rS4#uGAGvn zzGDpD{;FijRb~|q|Ci7O`bQj=u6yQeQ2&vzn5;E5tX&w~m> k-LAl1ECQML7Eiu)AL7+G?;m^__cBhDe*a&N|4t432jt`J!vFvP literal 0 HcmV?d00001 diff --git a/pkg/webui/ui/public/index.html b/pkg/webui/ui/public/index.html new file mode 100644 index 000000000..42dfc725c --- /dev/null +++ b/pkg/webui/ui/public/index.html @@ -0,0 +1,44 @@ + + + + + + + + + + + + + React App + + + + +
    + + + diff --git a/pkg/webui/ui/public/logo192.png b/pkg/webui/ui/public/logo192.png new file mode 100644 index 0000000000000000000000000000000000000000..11f22322c00fe673d7aa1530ecfd53fbc82b2373 GIT binary patch literal 13703 zcmXY&V|3tLw1!h#Q@c}RYE3(}+o|oTt*LE$YTLGL+qP}@_Ph7~NY2X2TFFkQfeI4yI5rx8rLi^(Sp1sbHxjepRrplYlV8kRo&iEf>^m3#fc#ghbythAIJiJ0* z>$!qgq+BcBVwf31A&eY@uAqnJq`?n}A(sV(yTRNw=zPe@nKFXYR?bm7(X~u8FWMib zG=Z4yoU_+I_inoKbhj9P%0gHeZ+!#W{aBvhn0Zu!F-&Mn4idJIq3x$MWdH_-Vr* z!Ei6Y#99^_k!g1t{Pzr;-3$RGyO^)gYQSyDgR9F7CMq1#>oDS9#iNoG+T=x_3E%$kma_H-6hmO+$xl*)UM* z2hrhMjH05Jr{OmL5C~;rwYj1Z`dQu^B6jJ8($aXmS8F7P#!^e}jgy37vD^s9-0=*I zvVVqyWsLA4je@w^_ij8)dT@BUw^K4^iA#wH;{lNN_3PzO-_?Z3O?vrEjZr1(<%FtAqKfWUifaF~P@;i|3&Key|ro#mw}>!428ftwh~ z!R73g@Wsa{K0H7-GI$PD1qanAs2Gxa)m~tNOn;iE*brZd_j1jDkS8r-J^o`AByX#G zr#iAT(7csubPxntAR5st<2de7{8rHPNp#wcf9k} z`r>u#r@IakGd6wXM!=j74Ar5;MAqVDf7eERF!tn)$7`O# z`8E{^?2~7_fX-=5JT({lTvTBS6Uuu+n?QZfBC$x#7-&*242A5U;M_Lgax^tc-(%QHcgDK;(K**8=E`ZVj8crXt0mXmcN5r`9bjsC^g+$#J!=QoJL<>P>6}t#(i-bW zC!GB0j|k4sUvQ(1FHsKN>Yp`ZqMgiB?y+*{Um;dV(>cG{2h7ED_D4}A7*uaR_^52h zWEk^ZkT1rCe7b;bUOuhM@F05Y2iA7O{soT0)0cs0t9z4mlql_($cORBGX%|FuQ1MP zeIQx2o1pz)abORL0emeB?Mk9~nSW8F&E`SuR6Kn9I~U$(LOMb09rfOR)_rsd!vd@X z)I`uW?TCXQS)GKt)R$J2mPy@WOf&V+Nm9rVOPtu-4qx8 zzmwW;ugCO|>P+if0gl#=I(lZ)R$y90i~p`b2Xr5ZP8VNQt5zwHX5?LxrG&hsmG)2J zamZu}X9s;3U@(DSg1Z^;`?-}?!#{GWAXr37N!5Rhy2&~%5i5)D1%@>>o(p>`jV|C0 zo@2IwdOIo~WhUi8bDlkZkjmLX%K{6;jHZo$8?_t<6KwrMS9oUD>E;@Kz5a9o=|;=U z&q55^D~!xw*o8B>D_uDIsi+Aa9;DxMY)?(&y0W4klQp-XuwB06!K?GAS6I6caA@-b zf*zL+m`31Y#w7R;Ywl~^il+rrF4q!;LhPr3- z_YD4{Kc>uWo*`acNoK3Fk~JHMQ=ooG$N|+9!AV`=tZOY#m=p-#&FF-Y+3!tv2fyG=y9LZnD(>9IJ_#aN+eYtGqFi4nrXe3`c%7BF4P1f+H;UW-;`A0`x1 zUONOTjo|H>2bT{ewv8vD?*#zSe7Z(gmH|Ku55phvZNN%7t~0JcCXOpjPNiZZb7eFq zm{7|8EGPzkkkA{F4l>E)C)PYwfI_WUuoqqgj`qpFysq)G`Q>axSFSC6c4JgRSG z;)RJj>JpqLF8>jZ#H(KgOKtw~)hwzV zEnbr@{b1z)!L_=M`o+GTdtX}hlo;RP4pR@uOdY&cSDaydn8#7-8?iclf(_}7_BdAy zv*Q7L6wCNt-T2(b{JkD_;KknW^7<@9ANqU{RCInH&)tM3)I!b^OE2BKtR-Vw#r@~A z196g8OEw4}Tk`pd?_iCVjep+qBS;0&VK3v!zGe&9Zapiuv9OIOrG{>N(50+g?Z(g(*5 z_-u5|*cqpPva1J-iq+E(W13Puj{xZJ<9?A=iCEyq;;s#EZQ&^~F_$fIXm9LO*$Xh^ zQ^hX0WvkDxjD?Lc5Y{@0+E^>E7VsAfh0?3amahn2?{!$eKsUgJ+@1cqMoJ1{DZ}6b zB%5RB1uJn*0FPEB1%_}x#8=>r2raO*mJT!K&vO!?S8vuBxz6{8AEl7tWNcx^!E&4a zuB$exk;(F~T2ZKb->Mh`pnpN^zRbW63h1*0RtkH*W-iqX1`(wZcCck>$-{*cvcMl^ zIG1E{MnFiY&_fe1cssy;=vz${iH?~9@zM4*ku{AAyImIW(dgONWo zoUZw}91TqVm4fbyzGlNVhTMAq)}5Oe-al;lc1to;*lAadTk{#kWl|;MJ(y^C&?H!| z(CVoQL?=&Kx;+fTlQ{+K3Ry0L&xg{uWd?+QU@mSCCEja)zHrBt!6fN_>oino+datR zc#k#3-d7BEJ*!Q7&r*+Qr5;vQiut$Bs}6WNe9SeJ_J0 zmp|iuzPxaMb~1O$&KJysg^8>wP{eLABH))GV*oL585dcyhTpwM{aCRixtr-3n#>8g zY`0k*N=D&#V3flSST#8A1Q5LFwi7ddi$nqJi zSr|v{WU^^5!A-Y6CuI7Cx!(j;j_--=E@r2eF3CPSbYxuB3ThWhzl)KG07UsFCscW+ zzX7IwVCk9SzQ6sDoMd_@Q=!q|-q_i$9}2nNm)}2Lx!>+1jir!FY5l1G!f_ZT<-4`W zcZQB1qm1p-xDZ{9JB9dfcv&At&$EplDw6Fxki7Cbrsgk+ms{oao>2Z?kFfhWUkk}= z=Ja=xHDHGwvwtXS^ciO3uxUp`F~TZbfzbM);!o)GTsWXtU@XC0`zd415FYpY>zgI& zpDc2OcE(ZuW43t(T5J16%%PQ>c?(`;a(eFY0mYZPq$*9l`EyI5A@{`NsLKoIYtMF+;<6cygab=K${n{PBIQMwTWwE@u=~WeeL(QvY4qGTujk$QpQUy(Ll&VmWlMLcX@nS%Rt;z0 zuh#pqIRf21{de@&DGE*<0QSIKurc~Lg99w4MCie&J>ND(;qAo;%?FDi!enHFwPZnt z6P8cfz&}pb{q5GH=#M1*r`_0am5Va)7#IMtI0_y9`9cXdxh4ZF_X=LK9ufQCLD>rn zLqaQBPk+RC65B}n;kT_b8%Tn#V=`gjZ`W_7jS!q8_S3nCvnj*&V4t8Eu7(ZLk+Ja)9c*gwYbb(LI^&+bi9-7)4gGaoF}k5wFcdE-zV%q`Zg8J!899@yx4Sb!-(oBK^zVc} zX=xi(t(CnCyMM+M4x0$xADO2vfuerIg?i$*>U$|c9C&?VA9!AV78q}#GE`8F+dt7m z1#bOO1%mhbD66EHVq54#)`s6bjD<3^c59++dG;YpU7a7&$B6&cmv8(O-k~5Z1W=k!TTSx7MK#; z2oPHVLXhiVOE6X0E8f&HsZ%Fm5m?BZ9+kfTk=hOKsZe~iq2eCPoI})PHlQ$`sz&oU zoP~Sm@NK+ero`%xeL8c_?-oIAC-MgADoIGWIjv4EJkgwc0UeW{o1gM3OTg7SlvXQA zW|#h*Lk*+6?Rk^TDxFE{93J6k1E-E3e0-mcDy_!{hN*hIV&mv=mjQRVfsK`E%kH!o zp>J*LZ3TpC1$)E7u9J{K(ye6zI=lZUE*cwPpf5UZbC0P!L}NBc%o8eRal$UpS?gOj z16>m-&MUuOAr9{fzZI=d8-*8V^phaWU<$EZbC}(AQfvibdO=&6*6W9qo3m7~twTxB z8WjSzB$bU6sQ}&9a8`nBy*DP^9Iu(Y^QWS&O2 znD}3r(~_=NYOr1sJ=shY7ml}&82F$I+~E3g97w=LWGyFju}f@zu_9QUcDlq_vWY#! z_nvK@+c4N$K2IOj6ceo@+rT2y9Vboos500f^A*Z8S-FN@xFk=Gz!ahVuS&B z;t42WANW9EI>U2!&Nkyq;j=0&Su<5cjyl*IN>&Ig!fwv}A<2?K|0ABq?xawWy^t0r8KNE^^_fIcG-jb~c=hj{y&0 za;0J}%m590D8p7cfImyOWyqi!w#RaGY3JD-7#N~)#c!uGet7Sh3Ne`4oTvNno;4` z=WO7FIT;p4&q~kXx1Qd}WWNBv0w`glOvr8K1`G@y&kiyrDWyQMJ-T;J(CK+Mf{eg- zE6w&aKRC0z7Jy_8m`HTT>B7o!`iWq7<7G1^iK4D{b>SPyM;?Iz%30|t+gX{y8QCIo2ea^ zASx%^I}q=VTSopQ*@_IteU>n#e`V!6&@X(;f0~znTrD~>5ChQR z!S3 wqli$aKQ2SbKZFKg{E9g@WEz#e7APR8O^xSmtxq&tn!vCgCj&k^n$_Wq;zA zAM`Dv;VH~GnZEgN+f{tP6GI?QJ%CAnC)TK!g(l-IRitx%djDsAT3SP8(raAI-V-XJPO#E7uu)q zC5$n<^Wi}vP<#<2(vH+LMoTN9q}D1A)V(1`*&-=i){4-*;cZeF2-e!U<;2#`V;balnGP<`|Sh9?U$#nvTrMq z2ZvdG(bpSzWSX=I^9<1%KE*Fq^dfkU9l%0~;KTQi(2=GY9xApw<-xM+u)7awFaN$5>r(HwEOb^^?O96vocq-^A~aW7ganJsxg>my^}3eei%8 zyRcD;3X-a|zRxQ5k>SmA{5kCO)4khDq6fAfq+9E22pk~A(|!L<{M-Wi!yUC)@1i*f2chw_?%TD5bN zOzu;LoAxIo4*TOPVjnwdRe;6}{8+QQ$A!4o$~Fj;p&j~dsOJ(z)R(`Qm7=T_@|~jk z5`=4&X3i;>%1#Qp9Qsp4m2zOLh$ho9^HGo@wMrANpx9y~Il-2JS7p8+{?^2PEe5;A z*Gp0%DHEub!<9;f9=fP&6#S^}C{$Rh7zQ5_S~(vJe*B(3nne_Z=$at%l2SZv8G6jl zr^Ddx^bPGAek=6{ZhRVX7d&mi;x8Xs7+&(C6ys0NEMd4Tyq*G+BLPXoXj3QRy*}Ge z*=lh=`GAs=5utG-2&LjOb!j7E7hr4Jn~FBgzR|P=iceE;# zWQp4yeu~XCS9UAPWKQDu_zkTa8P;zF#U@4EukzvPioqG3@f`eGnW0`+fEdKqZnW$p zu8zsN#22ntJHfK---fqVE$G70I3CaGvof2*<-SfEcn*nXXvBq@B!?_wPbr4fn}?Or zM2py;i727GNDW{Pe9~!tQu&!FNhIk}4K1W1G!5a8fLXvIHK)953L3E>LudT@@oaZFbR$=kpCmi^sIyRT5P)U2grZ$FJrs(Gq_r<-BImTq<%Y9Do)7J5mJ976Nn{_OJnA z1QRk=jM_Pc>KvnyR+Dv{<1q^kR9AKv{)VuT0e&M(^K@%?rCBu7!eU&hg&VI*M`YsOvIU%mlhOy zPqT>jJ-Tmcu1Jh@N>?pUKxmKU0_!KP(L{!$Gx6d(8dK1l?q2a1N4 zi@i0ryR@muM9prolt>*JqW8-NaIqGPbw?NA`foj$fRP^x43{-qvyH(#N4IQ*ylI(| zdKW07IMWZk;ZzQYU#pONxuF!nrC-*r6DFR@{0Zy;!4HgPC0<`ofx00V;8F;CFqz9K zu4&Rsd=`F^og9|F@^uQzn|Lu^ptrSpHe&EW5m_WI{}%>+tI=!;3KU=aXjXHj_tC#gi7&1t=g#kJ2RBkgZ*@lO4|Q!(^KX;i zw_;3i+eDsvy9P1FV;L_w40Fn6I!<*p{u zDRy4^94Jzbsayb}ai#7xu@7BX!35(dCTM~)*G__?m?rP-3k=j)4Jw?g14vd%wHk-u zt-QPr3A&ELag(@iVHG=#^Ub$#!@~>a|22b9#Rq{!40 z90vwj$rMoEfG&aMNa$&I`*uzRBl`9S!QuFzn#{Eoa{hJMmyMXp2&N<6Qf#L0ElM3q z?MlJ36MFJj9m~w*G3BrN{*xRKX-{6%CcV-5HU!7q?JJpdl|6IhiQwI#;4CP3aVIyZjp`&Z18iJg6N#@t@0jvSjwi;#x$ldZ8n8 zO0H><4Eir>;Y1*%;s-Rn?~$ZQir z?j&`+5jT3B$5lfB5Hg(E+Rn?@{aN}!nM9yM0SBDVEc?_EV+ib7H8ASAAUHm&@7+Qk z1sSk3?n%!_$TKNnt{Oz+o?gjy`5ej8NKq`>iHI11F3QVFrUk)}pJw`wqTKIGN4t`x zP^-M8)<3TC@wPW58x1O^+GXq2D5Z_o zeWIr-+llKMr=^WSe4mTOGOyV6YL{<7H0Q5J9E>}oG`6cIJbk&vB8HI^;g&8~z} zOtyJn@Gzx`=jbAJk58m}i;Ye=IL{~0%a10!Z&)CPi6X=v{4aUEQ20n-I!`|iLtsko zMIkM+aUvF@MER`2Asvn*{u(3jzZ#WK^`so(zWE4y&M7*V$dX0En?U#VQKrge^qU2K*a1K=TB+>@jgv&B00@0uC&s92(-|`t`InVCjikxiAFu* zMIwj<(Q@t1!Q;rPvOTSIK8bx^QgE4)p-160RgxYrP<)(=#op{&abA%n5fCKf$LnqguuA+WF&kHo%0R5ymcI7K{=Md0wyZ|!l==nO*D*h5p1|YO~;X&O-oQJ;IJo7sTFytPlWlRe}#XfKj_;{kzsS^7#NaJ#$r%DPk9M== zte+1BLBz~ut1eVMuIY#ePzXXAH8N=b4_LyBZ7hV2*Re3lS;nY*d30Ii#zQGVYF#B$ zBl&<8S~p#O?`x|4Im<}%jl|Yq8g-pk`!7A}A2_~^t;)P7z}NBI_+tHzrVD1GO*q2;rM zph_6QHE=rQhQ3l@+_^`ROgd943qz{lZ1Sj{RXc1eT|N(12N>9(i+V?x2(MQs8GaGS ze?}vvB`BxHau$bTk^3ir$xezbAuK||WeDZM7N(J_su9#zR-GWEkHfcRD^J_qS`Q__ zLq;g)U;tg0INzJAQMF%fX`K7PIy_KC4kRfFBh>#Ke+ zyE)`Wyr>5%+}I69`>KGLp(A|EJ@Iw1W$RcRs}4dD}M-VL>B?p z61HQJN!tucMRW0_H?afZ*z3rh?6QFm$+B5KBZ>6)zIb>>gzygFgaI}MGtQOub3&(8 z636-JBYB;BABNB6XHVoDTVo0|J+x2GjO1W~$Xzb~>@Y<0BbCr(;6AQCAu5joRw+2# zW<6Jt1e_iZSU3n#iQqe?Z@aB`tpt!77#<1Cf0hE5rA_+BmL_y}Jp7AJosDTlZ|M^DPB zW}0PZ;HO4Ejb+K>8xDb7C+-lH@a#*>L3}LbZQ~1e4D~g|_owcfGQ8(!^m^f%Jxi#-XB-{^lL+Z1#$1@NG$Pa}`qL9ZF=SAS*oWLB zl_9N_L+Dd8d5&LG9A82B9PbhqR+)*e@P-r{42BgxV+J-7K_|D_je=MVGzc&;z{PR0 z4sQ*Rm^Jg0BcmVG+!g`ln2cjw?dy4cy6<)`s)e3LeWb=aO{K0)5?N zmS(%TJ>H&rSwb9sQd<3asByS$DIe||>6B7Geu}_uBvp;!$%uX6+j4l%TKP6Car5>& zzKRR!pQ|4k|wI4Aea7dw*}sny}2&>^XS9D!Mp_jQex z8;li1sLOaEfwQGo4A~1+Jo+IOz7F0}{FAv=HOlh!j7o)%(h9)0ANr4592EZAYa=5c z8Oj-6SE{`KL-Z2MNF$u*^Q;lk_YTKIPFq0_ElSpYZ)pU*FO4h$c2vL{`o_)JzNG2A zF@|(4UbJcS6w@1a!@1Jd2JO$^QpcDen{O;sgsEU=F>Id9HZN}*e8FviiA6~@ZZoty z%BWBhTd8X88`t z>2UgORu$8DMlGOc1d0T;ciodhquxal7;&MUG;pf9O zd`mcy>*y}Psu;?241vZ8_}D^vNl7wWDo;X9*M#BP14v4ev?-jytaK2nn+HVtPT~c* zmPL-IrPfY|mQbojwbKfoeiFSBGd225_obS|R@Kw4AthI;(Y7GW6evv)qv1Hwic_Rf z{iE&i4r6cpIr+<@*<-YVpnT^?uL~88q!}02&hF`Dl%)RiH=|oh=(uUpg&7v#T{#!g zur~{X^gOVI^g$LdNeYI^l9!`!w{;|Z%t2A^=*(RK*XlL>akkT(XcS9$2`J`>jQB-0 zKgUo=`TZmXrPGFIeL{5vVy!~RI|nP-jRC9t6EJHQu=~AS*cv6<N=f{{5!2ag-gR9wzPO=x(ao@jiTUvf_62EYbOO ze)3jqsT5$0>}*iQiKX_Yh%?yZ zvNp)f$7s4R>Y3eMTh+-DE4~U}Y^63daLW-^!2{}DiOG1`!_Zu*Bv-m@S#xHO|2}w-;=v``}5K_lHe`w*Oe1+l?wxwy~)0$FSwpLxk~4f zUzZAynS>cu>h!AE(^x7Ly|=4)$4|I`x2BW!#-j4jR#oyQ+E&6dIk&FDTC)@KkuX?b zk3j!!v&+Z*gzJ|;n#F6R*arf1X>W}{=;~p$Mh0=FH7WfvA59jd06*AgG~mm7Ggtp@ zc7Sj!Dd!i6?L|LqKZG|W)xu&53{iY07LvouWi|J<12mhfLpKM+pLy13`pk)fo zjU)Bg`%G_dIf+-@Y=oiM_@V|Rp=s&mDGqdiU&pdb-Lm=lSE|Q`mE-@Ju1zvJwuus= z5ndqA($gq*Y%&9UiHHYGI_#7v15qVgqlbq3Z)Qe^WRpoV3f0s%2Kr8ZXJrk=;$VUt zJUY?qMY6MBgyWLS;$3(3U6N(INLxoBBxdStAmuJqW`jKFc6cWD6%JtWk6nhi05<=DX}|U0T~9eHM`}(IxS^3J5LC$HEctYWcjdLC=d3c z!XP_zq&e_ZLqM)Z&NM4nfFRw|5@Rn)ID1DZT)lo?!oZ<0emK@NUKGY1fs(7g%w+{) zvwJ$xrv=k6Oyo(F#(XyjOO*77c6hA$Jn3GtDEh>4+=yr#e4drsFz}hIBxrC97jv0C zEyVIh!9x?YYtSIup-B{EkG-bY(g0#tVo-#fUS%umYj)gVbqKoaCdR5ec)vYuRyA6E zpg{7-p<3jU)Q_M$>I=x~oNPgNBlVWNSUHfXk$;QLV`gmiAPDGF6WvW`EYrQG#8HcO z>X3LVMq#TsbHEVEbmYR4@%UMa|p&VRWxv6Jv#guMf0wSm@-qKb>3=2OLLkb zbU$&#TMJ6;bZ_n8@FC^VC{VgSQuRXZV*%u0H7Ss{xltz`z?n0MQdbqVf@0@HSz3X~~CQHd+=<2E>cOim{~vQpeU)fQM{sf8z5x@teQZXx{D=fB%)iWBHeA z%XLr-dD4agmsnIwpF2M!iSk#))OgAR$RxE4JIoQy81bJJ)!YT~!Z9rrDE@Yk!6wc^ zLz$YU1cOC^bUa2F6xJuAGBI3M>L8@2b!+88b`GX8?m|+M8QfXlNdoVhf1c3Bs7dgd znk3pirBjs^@K^H&6qEkiWJ29D)_+A?dwsNnboyT=TdPV`(IN;o4B^K$7uN z4_8|Cyfp`5u0$VGp&v?7oP_lCiD2bdk_7GgvToQ{+#GKP2XuUC4s0*l{6ps)Lh;1S zk_a-t%;eBC*K&ESu$>`NG@`t9ZlEBZXmtjYe5mrVBSi(R_(jtul7|L_&KXn?|EXBw-KfsGVbA3I>Y6B ze)@Pd(X!uhD_Ac}B<@4zV?7p^1m^uE-$8Hb${}xyN-*ICR!jKLW}r2ivn9Jxqa{9a z&9V~~QT=9r^FBjIG}Zv>qGii=F-Z`YB{)G$JL|Ff39s93RxK`k`S14B2FVc1l{ zAZu=v|MJB##q8vFEIC?DL%JkJ3AW~)Ggze8S9MJ|5f8GURs!YYpFj=IK-Ls2?n_@4xVeafvB`EGtJ+6Y^D`h@3#!H}ufpNP!jW>o{SEv{7B8XunL&=)!wi}6O%t{TRw zBy<=GKow?_6~r@nmpDq4qIdUCz+2e7vv2gzi`H)*u`aEC>`3?EN9I7f3rxlFBSq$mlE&h0u}oNX>CVvX<|CNC zRP!PmWdhU8AR1s0uCU(R56`p*n|0CM^CtAA-G8?yZSjCcmZY_jG7!j+djY-wGP|Mm zzQXsEly~kNljV2BX8Qy{bSkFA+ZC$uBxSQ70MVLT5(?0@iFNZ2hybbHx{XpyjVx% z3?L=qgeD{UbJK2w%;w_$gI3=ZS4&0mT<4dI48JNlJNpr~6xA~jK2J#WJ?-B4*ln>y zKlqkP7LT1$q7n~yrWwz<+b4*8j05e#E;yh*xK0qqrr#GnXtTj4&tpoTo*d*W;Iph~ zGQupHh!%JPgR2Dj0Atl_sp%Io(MY$P_Bng)->rm{qS;Q(yPaz$?T&e(%B>^LDb zrBHET=!p!oC6ESh+KxXJz(J=q|3rw${y)1&Jczr&;K&@v;3`aId#>hZDW&2S%Vs&qyqp!r2tp$v(? zn_DEh4-&j1CL~i~q+KERzO^&khghwKqdNt`cPuIkhcgTMmFP*$%F3gqT8**CQe_N` z50a1Pnrp+4B&les3r`0(xxXAHA)RnS+^vCYjgLLBE&O-DqfUvy9h$cl6(#nK{aoE?2*tU&}2< z=l+HlFyHC(7gCHOo>P|{E3=4g4dd9|#B~)veC;m;u&lEg7_HYDjt&nNMgyFnj~Qfs zsU%fNXmoVqqiO)`&qH)cmfR-kdyN{V_-ri%VsQ}sv2sqMfk~ksi;tMgilbhKdE7M= zp{dw4=7acv88O42gR$PP)O~RJqQsmjlfa+x{E5iXHbs$=;94W$_p+vu{WNUO4{nX| z5l#f~9HG@Dd|!U(@lkFwOt|S}c+G(~fqly^; literal 0 HcmV?d00001 diff --git a/pkg/webui/ui/public/logo512.png b/pkg/webui/ui/public/logo512.png new file mode 100644 index 0000000000000000000000000000000000000000..2bc3b0b03aa840a3beb5879dafeaa47c65dd1ce7 GIT binary patch literal 26403 zcmb@tbySpV8#nsQFvt+XP?EwBItWTBEi#0pgft={NOw0gC@~-UG=g*qBB3JP zUDDkR=V9;te&0F&pS4)RTHencSI6%PRa23>PDD=x0Kjzxc^P#8fP#NQ0r)lW*Pdtp z3HS@*qAn)|6m>E#0{{|Gkdf5%G+cX6=*ehy-iFz~at5Z`v?_OuL~udNT{`B}ZzZ>fR!~15M%|Isaeo4hYkDnOKLs54Xux3l<%4D)+XpVYbHMm`#Vy<`U6?O1LIc zEv@UOp2=C4Gj?`p%)7nv?@xR^EC3h21wf|bs0IVDFj!f|ILYi80mY|Y9iq*V8b0;j z@#q_HY@;j;3Gh-N(}94R3OyQU!hb{Q%U7 z6@VbJ?K2W|5tf#hozX?Jf+Z(Se`Pk^W-qY1e^6mKG6)=dv_}!&jK!KKeIf6$=i|ug zqOhH4IDr#=V4qs zsgcZmUk6MuSwBNNm--`4Tg(Vm!JEc|BBIF}h#`P{hsjK>PrXf&4R5;S=<=L)_jdnf z^1{`+cjCu_fcfih3&^`h!cfQnVIZbo?LF-d0fZU$p#3hz+vvVMuj^BpE3WZTw$1Mb z%jcVgSy>;V(6oxe(10+Ishq4$Py|96UHXg}X@WZ4KPbnG%ttw3*_M7#OOv)R95r4_*O#yW<3hoC?*i_o1q=`OL>1rqyGIhZURtw z>9HbfF#U@afU~CpFmw|EFdrg~-gCo9=#eStJZ^2!*EMjAuQ6OM1Njq*+NewdhYZ*3{tU(olg*lgd5Kb%T$ z$6%s((yFMYiJ8MC(^)||3&RxyusX_|TDDqz=I8azwIN$~Etn90gbq@WLR7MpD2WfMu{E`vw~{t_>zlWw8vrPUU8Hoam))UmovdczD{x zjkR#)g@J@C##19Fzk$UHLEY?1RTk>@utKYrg~3__-4hlD-0YzZ=Zhlz%&U7VtN8jt zvOWUAH{n?HB}i}Hn@9u#Lfv&CB>VK|1LBYO=Y%TzN}0WUED`RIz%oDuZA=LYIxP_b z{J`Lw)?)ZPp5x7$b{~dsa#~O(Vb_ImJB~J;{R^9QKbT_3fsgh_$&qF=Wi?a;?71c52R@!AOk_Dj_2WoIeg!HI01#Wkw;inR$!T&!Hs=gI`k-y*!!(Y4#s9u|QE;93SsEYSD_iv^(N+N-tU!}5@#IG64AQHS0e(jh*- zPdnQ%k8gf?v_5)62wEe14G%FK2*7|~C=7;>pWVNl9Io4do~_njb{{f1Uk<*60}yFq zn*Sp?t60RxGc!I_ZZgL>i?L~{sKH3hXGk#PVw1l~eup#ZwH;)>nH z^~+m%r%j2BQQw0OuJ2v^o{t>*VuZU14E;0)pu>9j2vUhoP8c5|2jj#@-K_r3ijWrs zEr#-`M1vD&mPW2WLFPw4LAS*5`s;{9Am&^|df(*AW6Tpe5P~=B_LvJ9Ao*C^hOvmn zoL#*3=(*4QZQkpdBVprxQQB9H7FWVaf<$6Ln`ed_yG|#or1K?xAj@RFg<2a89Ld57 zkXR3h@ijF1iVWmb?@zISVhP?@3&Wx{v^3&fTz~B|S z0F|SK4O3T4`r6UHMc~0^pMa-yPCT~zNYQA9b#yd6m0;||QDDH+w>SAXNkYLF2&c0E zO*7#f+o174Try=gMT5c^h(`}a{(qNZ&F42*XDvdgxIex)(x{kpR+D-oH*&>O7*;LS zG2taN+Ys|BJ=spKNRj__x{Ye$kv;M4iaI7&#v_6NcF>*5OC>%vwS+m+A$9wQFUS&j z>QHaM3w%9^u*SQcuD4*qI{Iju_m^u|?wCElb&TG_ylaqckTA{ngW6z}!ly+xg8JGM z`wFkfVLIM#{l0(bKkJ%?Hr|J`|8E;T>OKe? zP(+`~)R;9FyqX-3`#Jmc0UQRJ6C|?OF~t@-jE@+@q@KS4WdNym8zj645CDLbfCv7z ztbWfIOuPrz0p$v`!4L#I80|N@vHGRHgF|=n1JC16)hw`_~p4^dP4SW0+iQosN zG&+Fo`B%6xO`}nR)4^sGbAjalG5uH8(4#8+C<4D8Um|-i5|M!9MP6AeI2%+OKUTyWa?t)hQ|NXyqoqs6r#Ge$(?aCz2tVz7Jlj~sttq+_|xan3b;0{SWd{5p` zo?iLH-98{5B#t3SFVBzH7!ZyM0JKqrH}zc1A}Ag?9Rl8)*}E7Y5&|nb7`NoyS-i=u zy2lU4%2YD9gvf(DkQs)@U-2LZG&(#?Zbp6K4Vs3b)Zm`}pkf}~g8LvG!FB2U9I59z z=7H2mWEi+7N6Z!;lo}qM)>mW0*K&cUc7A0WU9o6Skn<0}4i=2-$@tzTUtjIP=gS}y z?h&uBf4(q4u-4BCI=PEet+iRP2@m`BF>5!f5${`SL-tHR-?)mSMB~V>A51SA$+*@R zGDz$j6H{jFcpxrP5jkCpk@vDZ1BC5nUe*r@)HUmip(>snT9b)Y6Iv!z$Kit8-o`v?=}6+^aiu8 z%T2fEf;mjs%z0tfg=>qnT61?jH0m5iYf6-O`)33=q5F1)&q7P3sJ7{PGG5uI-gs=Q zWg-Ra_2kxwEVzEi;L5UMUEGX1*6AW0v8`ZNR*`SM4|#rVn94xe`^}48`iAXLiyYp# zFzn#q$z7+4hPw(`y=ys$MnFEfMDz#dfHCduP|hQe18J0maK2HTWmeU$1(f{5H- zL0&K)(4l;KUHCi-sJI+R3nq4`&N``|VRrxZoKOi&0Rk7~-PvFMg+kCW<=PuI^KDVT zhtn^dn;;rl_IB@V-V4|WsbmfH7ot5EzKqqBq&R+Yq`d+8toK>HHuhHMqlV(#hlir!uyPO+OfswwypRCj_(KN=^rTmQY0+<|fFq)sDpBeM92=-#=%$(|sJiZh15 z6iz}lP4KY^LTN}@0s>x*kF`%u5Fw<`(P%hedr3^2DR0nh9xQ_Cwzn3!qyCN@U-c7! z>zF>Er*BK$-Bkv$@s$o}45o-8JD?ZnIH@N*+u43}58wv3nJB8!{Z!YrFXMc$P zR@i?s(bi~jrnm*H>{hcWg)QoGR8{Is z{fcaeo@mPq0%n1hk*7D6e;t>jO`FypIjK9(0U)-+0?Z?0ITojNlbSjD+qxVik<^$gpF z6~Immq@Me{Pj*}@^C=on>s_{Z9i|USAM9C*4QlP_voLy1s=q?adk zQD*lvp}uEJqh1@$-b(KF7jSbaAscM^K4`HE<^NK@p7+Amvg<2$ynQucn``|^`{BGY z??_VUWK!PL93XmF+$SCITO_-5(MTz65AJ8;eb-2vS`4C=nC3-jk-I9L^ngY()%Vz8 zZDSRTa5jF=b7zp$QL%!_*~ds3X4|0KG|{DeJZ{PG$q`J(MkI$d@nPaX( z^SRRtz`v}pviUd^*W?*8FR#5#|K!}4M&H5fKJC=jN)Q$&r;)h1;^2@vd&}2SaH56D z`khDll;>l8A(QQPtS%f2Rya^DhTe@1I@D1dk33c*D>^!|$ud)jPwJa+JRs0USLzhRtOmTcTxkAnE|4`VIw!mv{_*T04) zGI~Pa7jMoqNuR9*N%R$VJzQ(sRM!`(*qQ?>X5p3z$3*1ocRDlcJ|Ka=D-q^N^QEEd z_2ya)hb*oe-%*D)7d>I~4>ofNta?&-sz{xFNuqkTPZ_1|V&w+=D?3ygMP@>e zyJLFWjO182RWEXd{2Uk}v@tt4Sy>1vqV=o5*7B9io|75$O=l(d}wcGUwP;N#M{F z&+6AcR86griIX_mmTT<#V|E}O&QI{6>UMyOsJ~3Rc8H71+1s&A6^+QgbLz?V(I~ju z`B82euywz#>2q5^CK4Yla>i`r7hL3b=1-7hTf^S+M$YX)#}>w>AbW{-MHwCMbseB`>7v;8C7X-f(n3j8s*b9SCc14m40 zze&}bEhWvL%?CZwe&PkzM}Eyk1aOiSlfCRsvQF?~BlnZ8X@=bn)T4y=W#e9(IGq#LTTCyV+Z$%6YVAyj_&H`3hdLJnj2x8z1i z`{?kalp4iJ`w%s)Va_|i*Ad&5+@}MNy;{4xZo72Vq+Zh+5EgPQ`Krm;bL8%zTBq)Y zHJE;Cl55>Gc)dT_)%%TCgvM)Mx0DoDih99FmqET4qsc=Ar^mbA6k4&9wbq8Fna)!R z7na8?>puiZe{e~a90YCcJeJ7TN$?(tFp(Sf-ZkL7RU`! z9+QyK)WP;UEk&Wi5$GnK7>OoDL2DJ!c7Gugu#yD1ho{q9IYT;owN2Y{vxM6imBEvMR7oP!Js#51-eAU^?gy=_w}21?{E$Z82`FnR_Aq}FZr%la#63wdgD@_ z@Z%e8PjyKHCpX6k>lr2V!fsRDrdG7A#{5SF1u9iOFt(+As{YRW>)E}j6DGb21NQIu1X|1C@{Y^uRc~V7?Lqf&HxEAL zwyA8HD00>6#6G$dO|1CGCG=%IWp9rs2Nal1Bv_~0FsZ6!4f`?KkRRX2Cm#Ve<+!${}N1E~#W;acOheW0V}7OY5ECB^qkwxV`*8#$eTAP2AU} zX5qo6P;l+d*{YUOVjOi0abqKVnf zlDyzRPg)Idt}K#~5IJrqp-R5tzap7@**o{aR*TMDwMD^}iU87xGpBMj{oMS{H02rN zf}Exczmae<|0(n8uL(PhfjqI%X$Y6TR%8-bEE%OgpRFxZvO0?H5WhA1_*Vj+Q^u|L zH6`)c>AX8nkfa2TT2nC#)mAaI)?T}Z%}c@xy>D5se@TQqdR7w7y;RqubsgNrQOl4q z7 z{1IhJ>&2RWlEetZW>@$K(kFJ=$C?Z^)CT{Ah365LTl&9}7FgRBwjpz0MYxT6 zyEg6YdI#p1P)Bf;#zloqEG7K#A>EIk)L@6_kXq%8rYM|UkK`k*A=}r7Dc*U7qhl3@ zo>sE-x%T}pTqoaAiq8X(X~Gm*wsAbQB-8WBknU=NwC7$gzEInT(ANS*TSWQ=E8Jjm zTTGs_BtJlxtNX4Izt*+_-#}u+M3rfRiAyWV?-n=YLD{9FypGd6k?MP;=%;;4nK|2U zOmFJc`R)^m*3Pv>4mp$+w+%*Ee`zgtQ9%#9uH__a_wQ9d z00EF6J{@>%JUm<^?8|tw{`4a9#|M|q^0I=qu{Wbfs0UwnZNRcSZADv3TE23{)5JW-U z<(_R>lhlZ%Co(^ZjC87^+}a^<=%sF`9W%fh=CeQ_X1cu0(W@3@-B|bg9Lb6^3QR(D zxtd0md+idnF0D+nwV+iAK*WV`jNRJ{BK8Bx0&rF#fR*t^?XuUg~lM{WTdIY~};l-Pa zvLk`YBqx2LI8Ullelg(Rc1MpLs`$fF^xImEcHMf`FpH^Up$^ZN_APFH+{WQ!N#?hL zC-nn*p5?kXh5>Lon=?h&&bh{uozjAp~vgQ+S%|8YAv>@|sgkQx@o-ik~RbG>8c4 z@=VQpFUTw@^kF+9Z1BT1|MxnwuKtoj{(5}y8&$W{-vJz>HBaYjgCd#P)TjXm4&_$c>}I7jMOLZ9S?bt;_gd!EuB2jG&|@ye0gAC zJ7DdtVRyg+^rzmR(L3}|NMD|`u?lMQz>ka7T*qT!EI@~VHR2bXvx=pEcDl}gOPu~l zJA=jfNG5wjrw<0-Tfu##FZ9%SejJo?XD8<+D^nc0+`-Bh+L-WrD3|^jkSViz{a3N( z<*FxPG%jHpg<<6gUNl|)xv>%w)qW62(}Q83hXLGpM-C@wrV6Gyx(V&W!s`i+ow@!S zce?`UvfO!=`Yz^1AS6QwwZAWP)4*Dk$g_nsET`C`3$BO|0%{L27GRdorzyY6bX>@? zy+hV_;jjlIp*(cHuJd6o=V)f@xh*=le6WBoToFU|v!0_;qTFpRB_s9LK$LkB7tT^% zeO0H>UiLv|I>UYHKlT=cm4-1f-WqA>poA!9y;|_3$hqFvv zvk3ZMc*1I}oEHT)*PBAQ4$Wfv{o_})_hE9sZesZzN5}%5PI$vd%q>nXLM^`B`4t5> zmg%B)HN6xrA*{VIvS>Jf_hQ&@jmL1}=E%LHX={h5^w=z1;QaT{Nw7t0pmkSY4+4-Q zE~?fm=jE-%=b=WdSPp^7!ECp{G{1+eQb2QN)VUUm4`xnzO5ct1jbRo(`n}l=5%Nj& zqMbckX`TW`*wt_@N?SGo+Z14S`AY5Bn08G}Nd}MS=j!IG% zUKBFIgtJRQf9Lb}r|iYu&Tp&99SK?$4U5S>OD?|M-PWL9cm*(?|Rzi1Q zl;RzxVA7483*=V%K^r-1qu%99YG5g?@1opLvDtstRzUlg4ATBSlNk6SFfz#hIhAN- z8U4l-S{3>S!U+cmqS+1jxiRa4cPdWs6#Ar};UREiiJ0pCVM451Euew36#T5G0975X zamtBF3W<9FL(8W>4w<=1#p`~^JQ(6V%UWtbfXxN)N(IreB|wb};g$yI=9M3Tp}Mc& zj#nrFjp)kZ>bLtZ&`uAfDMTKAB@%io(Ar0{S-hXH?No5D0-8Qv@T}c8nB0#qGUAPo z@*QpZiML4?Qsb+8>S=@K+-xd8>V~gb;X%434DLDH2%RaJm+Uk`d=uF|728j^|7z>r z>n>eltQ%{3Bklp6F&lvgY%8c&h^K>p>UKs~btc`J^ z@$1dL%BOuj4yy-rQBZDUnaW?+n2$AYV|n70=>{t2KPL#MX(bs}adqb_d)Xv-F_XV% zvPU60cp%ytyTp_cMko7hwSKHHbTy%y-TlMA5BI~?u1or-P&CrC2R~tdZ+7;|G_z-5 zXF(^lr)xV&I|7($8PuQDyl2C6d|MVM2zkxmR2QzWCy0PB9H47N%*)0*X@6v3~?I&!73-lXz{je?#V0+?M3k*f1jGQB6}{kQHl=kYqA>bD)j9!zU(7NQu)~r>Mw_& z9=dDRU1M2pM*)`K-t%=*sHgr`xr%S{YI2+j723tEEY9msQ* zr|2Yiwj5hUu-Ve2*FI|z0WuCRY-lWcQ33OVHHLd${mHVDAWoQ zDIALp`5oW`YW#F}lpkpQ7!KQzK$P%3 z8gJAD0?NSd=-^sG`!Sktka3Y$6rSkfKI3@1cEGHzk+A! zzO^;ksn1n5PU@{u?2WJl!~K}I4~O1Uwmh0TVKudlD`O7yL}{DI7~NUg#;9Ou?E(RK z^jGuwNw(QBd|GIc5C{d}Vg1+4Mwh(c`Q!*Do5G2Tm32m(2N z@!Upx!rg@pA_LuOZ7g|bBxnWo;JKdB( z^CU8g9zu$&PrjT^)R1Je1kBWPHl^3I?bZ7znKgAi;kMgkDp?Oy*XxaE%XCaNVqXQJ z0r}yw(Yk7JQ%!%086;t!26vXQzsQUQ_IuQe_h2eP=_h_9uMpNqqVLdoE7v5+CJ2bG z7MnK+5Ztw%bq)G!rI=JLXs?{-L;(Rciq*S+Fhchsi$>{_6r8nIio?i`SvdE-m*EUA+li0Qu7F^# z`qXU7FJj2WbiVhqsfv)YrgvJ`2T-1x|K$}@M3UOt>=t(MwSGe~=m+gAe|5u4ue{%J$0S^i;yGH4uDZ~58=q|EAAPucj`bx(NA zQ|MLq2Ps9LgNCBy59-$WrzjP!P1!r!C^6M)9FtvR-{POqubf&Z9HVz%MsyYx?=RSx{ZT3BxvS>^`Cs9b z&gZU@O1NSh_1-dBdkonQJQ9>3N879e-%06_?@BjxzQl?Cg2$N3G~8Qi{Y4(QA@m6> z{qPjTJrNQ4^;nR+cw>lvLd98iHaXdKpA>9=Rtkrfqsq)jEPoByI zE$a_U5M9yLyd+QOyd8~K=_XAyqmI#WuT^*3_hGWe)hpz^vFmfP-^G|3aS)CFgQYRK z9&ms0&v-2={)EQSR~EU0O182yCveKss79ss<#m3xY|&g#9rsL_-PKy4mDyOPn6mrc zoUo(Rww0&`LI?=$d@*KGHZ}w5snl4rdjUBWB?6%1$9!P&VhrIGwrdNr^u@QZaG#)ggk+k~2$tjiBdw2)E_PYPLa*YlYyFfPsMJ#e z4YfuRP9>^Nf)$y$$7D{q1a3#**VAb)3dAk#s;4LxQLQ!dsFrwj)Cgs^_|nVVApy4p zFD6vYkNkQH_f-3ml<+sNIt7YYe-l?yEiSq}C?8*Lxgod>!Xglx7ex6grGHdIEpvE7 z!bFKoxM~NJ1%jV0Z-%jMzY{!_(0B0f&va@qPc~CwVLfwyj}zWb^H{k@6J}0=*=7NE zNHYvpC+sZvO`!cukfZ)kdzYTU@BD-B^G6=GVcRm7lQqpF!$HuBtkqHT4}ZK?0@NPC zb_l!%2P5$Qs#4O)DuY_MLdrpFRErWy?mhFu*P!{pk4hHdINu{%5a@Jazt(B#Fu`Z= z(t%Kw{TiuF@AAC`0Ts71Z_pR6E#$u$VCK#E&c|USLkMOx0Yb0BkVyz;kSA8I2s6+b zF4AT8NFHeqqB5CgHE-a}Uw#@AZ_Iu+;+>&X7lNz7Jw4|Co@q0Q{pod+uY!&|U{ThO z@bp^yQY-ZBV-Q$@P`Ll`o2H+-fz(*gSJTJXO_SAo4y}3YRFw1}wxe6EO-${-cKEEp ztvo!40-RT{qw~R>xtzl2M{9Znkms(_Teo_5*Iwm;Lhl|3?&VOy);p|rjBy#ldJ{qM z!?H)<^yxu9f0;@65->*wL!*HJr2i{UWCHJKk?ldR&T;A-1doz2Gd5` z5*H%x-H9EGFcK{`0FwT!jp~mMXlm*qwRhf$s$UD>AgnW4ZJm628$0|Tn-#TgGO@C2 ziA9Gu!-^+(oLo%e=wayaQLo|Ubp^C2NNLNj1-HPGrniY}Xu&ZH4+8!g9ZGYk zKl}hMYLFC9(8LxEqQmjX?||)>f9jDts@Jv&pbBhT4xrCaeAbhr!vreP&K_$|l4XJy zB{}}Zha(wj+3jA0yxWlNlv5}5rcx||LV+EmxJ-q?WX84<#LMUPpFo6X(_n;jkmoB( z#MTu;1%-b129>T?EkU`>*5VT^hrmUmm1}&u>soDfT_0#LHtf}E+Ne^(&y zkimp|+QAZS6oJM&EIJ*uXwVkoMSsHS7_8B&;;0z(5bnimn)mK^{CDWhC=sCB~Bm^=-7#G&^ z9IbDXi|;|2nQe7IHJziD{gc<>I)+M(8(mSME?$+46gRurbVXXcZ!jTI$i z(?AS^*e~ESt02r#0V!A*SA!T|Lb>@wrxJr_avQ(MJ%b^ke>dO$rkiayn%`hKjJSgO z-Zqf~Sds4c;xmxG$4Wid*jcV%P3U)yx5ywkz{{IF-hEBV!1WC{qU)zJlG44zvXHGgXfpu&}2qKlhyWkA-mTVGQr3-ed}9o$m4lBT8b z>X}dFLBt?drc6)wEdpp#YkJ#!b&x%&I;C9tAit5hj8Et;T$S&(+E+KWn{y{>^zZ`E zq+%uIVF9I^HRh#e0|sgA8M;){LvBl@9nJ&TtNwhymot;BC9T6*tk9&n~9lu_2 zX*y{fcSr|bhD6!~3=P;n4}2pE`Ulub1j1O=a`fZ}uJG)bvlx@>kmxv-OcfB^j{g5) zsaQZw9SDwat@j`+enJ9iT^vhFD5cQgAyU2zzP-8PkzSBZf?ksgPt1S|^MJFykou#w z7er7+5Y48Rp)B9a1F^k4z4h*{&+}{Y+6}SK?!nONVrj)SEC9uzW7@nFa0G+9+ybA( z(Q})9IRq{3<5QD=#BWHjMqtuptF)XfenZf3`&rX(*=jca7pioswCi*<#3hW!&%W9Ya-r(8*fc zug}<(IVn(AxZgkVym@_HCu)t;y&=VsghK)bt&?fqv&Sm2D3Tjb<{D%Z zTvJ#2+AI*ZQZU|^dITZ=jO|8l(FI%qWlBfxHglLu-2o+9v7Bq<*Z(qORY81Bp8El= zcgh!Eh4vZ7gTS?G(A6u0bK}!(5a^>yp3Ot?RB=V-EhF4k(O6(E>&ZteZ-^rrEelmA zx+<*Egy#g{t{+0yB?@HrgF;*T9{DytV28T7JZt!+VarLrPOB~-2npbTyOQ_xg~ulo zB`se@vO=0&RY28LwU7}a@9FF;6ny?mq|hY%LOa&~q{=H|H_Y%H_5u|I!M6nG|f^F}Ml!?i2^SL`TAKFxv=@ z%KH{TK0w)X5Flr0j* z2ZSP6bT6Uf>=(Wag3#d~Ev_d~hXN{E%feg*es1ffq}=;)krv@9EE$x6BBZ~b{tssH z$&o4y$Vut*rd2Iz2-ebk=OnzCuYSiJQ_{ScOK{^6rk* zd1C7|UYSWm3Z(|-HU&L6w0p!PX+G9qRL*jpuisHT zuRohg4YRrM$dZs<=Cc{QF0Z-=I zvmZeu;QSB>rTv5|{xcXsF8T7#^DAm*$*iFy*@@Hs{SX-~15M3yU+d3?PdugJ1Xk{r zzM<4Ym5AifU40AwKyPm`gOw>7YH1uGystAvmZw+a8=v!On&7_$=yV5Yg+2Uu<6!+0 zx5O?*r}|Ll)Bkn{pfiXZ1rM_1p?CKr^m=#GcpxC^c#1M}?x9==3mgRTUj1NE&^y`! zHEj3i@l{)8Ce!dh6t;~_S;|mjF&+c~+64d-4=2gb`#4cC90{VziAQaQ2g`PGc8`=U zJ3s^xSNo;XrtcZ#e0A=yi7P+*2y-xUpJ|_>yN?tsgnu)-Sg%gR7DN8qDhZ4fGczSB zh5QbcEC>~Li%|Z`H@}?KpZ!TNz6fjK8I3hR`%&mJ1O=1eM4TmaWk500e@}hM#%t28 zR%6^?x-?u*MtG}S&d@)`|L8AXld0Q(Gh_>`C0ti;H0m3*xO){A|3kz$mg1oW{hqiI zO3Sg1$~e}=`TR#&rNk!8NplB7kVt%VYT6=C%L63gzpw3rxsUlOiQ4QvExRQ3>0Njo ztS%bAv&Qx02hG$enX(@T&)rW9a&RaVeiq0gA}o)ps30Hc-0|-Hv~L2d%**E zB6Mm%c)LZVLWwoRhvM4`mhV3v7qB=EY*SkGyVtgUXuZE#_hbt5^1~NVt_d>Ubf9st zf#-nt+CTh(;~Mv~pQCGSMfj1M&Q<4IvA6aHPVINzl3>0M>dPK3rW=7M(&{$c(aa;( zA5{O*7m9M+CycfHV(0#O2haEH%#Fs!@^~eFZ`h`*xA5}lwW-dc-wcPS+f=5dnp=b^ zLpE`m@9wSS}K`3`;jG{pO(#RQg{YfavjQoZZl5>%lOALsi*cg|Gjd z=0^$Xvq3+hp4GIw+X*C3e+@>s8nyZn0Ui98N;3Lh5!b+Z%YU9BFB;HJCLR$vvWsS+ zT^V)$aK2Yb+?@tAjx<>GU(JABEep6L{~6T~9z1yD6Z~CFNh19X&G|T&w*6}BZB^M( z8KD2!!YK*Q$u<$4`+xdWOc+o|a6M~mrD6R|_>uuu83t^e9S$(lzW!H}z=@>=A(fA= zd0k37)VcL1QV3lT^kR5p5sN;6SI9 zmPITd4BE(NfA@bBoraG){>CpW{o%m?PN2;b4H{k3EyYWpcZm(%PI=9B{evVp-5EaY zYWNBN;L`%;AX4+H$kLmh_aj$c2w}R73DPZoM>+HUK4$}Dzt>f{V^gfvXxo1?qa2(V z(EhZYdh|!85&iZf#Xl&Q;oDuSBkTkvAg<8|_j8PlQo7}}F|zmr&@efu|7|`Lqo6 zspMZ+LNUdq^9z&qxeR`~DRU;xzk8oJFk6|(0L>M4pQbW`$7V%()jZ_eK{5}d;#7|m zm;w}88@~V?Q!ris2fQ%!j=54+*roMqE4d`9KyM*JW-lUruCYNAwo_sMghVt`wB0v` z@+K8H7!ySkaL?vi-_1Fm)k+Qs)VSjckg=W2u%AD(hn;xoD-tGEOnC!;f6J6~&wmeD z+OxBe7TWVxvK6~|gb=p<%hdl+uv9^k*{>#!TlYv~DyWDYNFI2m+I-{a;2HsPIv&Ut zto_wppZun*Z^xX5FYm@O<+o2P-J3o=KRVUgQyNgRZ9z9_w)k z2q`#8MHmRoofSxFw4b3&T$}TA{=w^knN&71Q!fjR**rQCKoiYmKIf6$tQdZbEAH4b6nJkc?q3~zI_+vRN;3b%Y zAob5Z*>hW>hI=Hn(CHC1xYB$ZqXh{}-aPiB-isoK2RH1$U>U;2Z9_|WX z{|YSE=PwEf-iX!QYb`gSK}T$$me(4-L_R39u&D_fm|v`&CWBqOn&pHnCjzE>u~Mx^ zf{4Of>GS%VEUi$C|3gn7RJ4ihbGgsQk2aZR&Gc-NFfV1~+VVLy+5Vl%;oFIwJyW`= zd=C@;RG~@nJ2#d$EUO7GWDv~#&^whFEN zws{Q%iZMKU@n!yvT|K^P4+G${ezF1`{2vxzqV&K-D=YEG2Fa{~0``7khwoeAh!}ox zcItJ{{o|KR4LuS1}@A$*1^bBgf zkUYH_{&kJo;?VG=P5cRA-TFV7U9`18&tdu(lH&8tLLRWx#+*==FJz;etB8#cTP$_n z5l+t?l_K+U@ou)~kK8vVjCF%;dA)U^LIP*|k4yhxD0&vpy#mTPVS{y~v`}=s`|+RH zl2`AoiVm0Y7Z87XJ3(f=8|>FxkPzvynrT|QNUap6C*A4GiOrG8Q$W6IHA_h>9 z0@nkHJm+Gsxiud+bgrK6gL554gA zspx)!leNxA*@L5kpF^lMAaIDX!}?r|oPp9%MutM!`23t70c89NUXSYQbB_%mGsTUw z08)YLyi}%Z4&bRTNE1zy0O+$x-w@gvI##G|@OlOhWDA_wqH<@&EhaUFRM^|Dj?f2x z0P{FcS(+Rce|!YTr2l&xFBdvK`WwowGH%M)o;LT^;jgxbBc$kK%yi=dy&2UtaMDXt zj+~Znh@x>(rc(h-go}9ogCZMzOJc;tP%fQ|1OU->di8zz3S@MmU$(!-6J;^hY6`U5 zvSx^saNWIzFA2y@HQyiA_jp1xqTgfjisXci7YkU{hvE!iu3yPHF479SJT)r{V%~vp zmVDgSnJb-78n^a(opCRf@3DM0W8poaB;f|VSDMjH!7wHe`*JyWX~eJw82e~^)q8P9 zF4p&|?Le{rhV7+Nf4H)S*U(@@m%{rOO)-51gBS|FhlaW_-yG_cDslnK`-$Xj*Ka;O zZ11GKW73@2iMM?zhfXp5Aa&@ipgAQCgR9TW=! zLlt*O#u|cLf1uuM=4`F4$2?%kj~on$&Ro9JHGI0?=bU+#bel)I!+IhKSO2!|_k2zj zkJES>a1@9WR3mbZLrmQjUepjxc~%tZ5M-?r{5nze{0BF7ygsk|4zs;S70}TJ2{Z%dqpn91k{wo@ol6>4QZ8Kg}NUAv$o*`5cS0n*)Iv z$1L4x98B`S^mnvBPOP|4LBc@bixX2(gorWqe9Zmb(uVFIVO@`t@Jwpium*rfUB=7TE09H5l>MZ(#Hb(8!H;%>0fszu{^s>qtJW#~z@YdhT-JPn{L{eyaa$ z7HvyES}I`Bva(}iOg8!dGAlScb}O#@60*p7ke!Igf%H-c8%LmI4=Z&%Yua{ zsO%LJ8S;V|wRSwh!Xf*LiI3iKD3PZSJtGLkXKkUm1_`C7XW6k}FHVvYoZ~fJ#gc#~ zD6&~k#_q_dPBQoBKnU8v6TS|E%ipM#ExE17+K+o~f-fFMgKdUf1_Ho@i#To4Dyt-8 zWU&0eDLp<)N6dXyYkKD|&Jqi``p&wf@w*Av)})^%lwqsGKA*nY{rIIUcS<-(osYZD zKHn42B@KPR>aVSQ(d9;z&b~s&{SO}#cUMaTdLN}D4nGay=$CFDHEn-|&S!?(=$1x> zs=?bl5D)dkRk>7bi1!n2PE6`^Xcq4OX5J-f<8a3a%)Iet0I*KtNH-W!RmXBJf@g2A zT4*;aJrt=U%DPB2$|+0MOUKWJmyLF8%Qa>tIAPp)Gr4RH#Y_+R=qCEKi^}JaW$vq; z>C%1ha~$Q_g@)2`dxaNzC)R(DU8T_#KTKZr?)&Qry4-|DHekHWS*J?GJ6p)q}M#aqrS^w2n0;boVsKMkjhl!+!Ev+ z-X*KfJw7+H$d>tSS@3~#Wc+X1uR9)9^9<+%O51q-X)F5e&cKzTw|93hsvT2FZE@gy z!N9--Fx1}VTj9Cz4TkYP)p}(v1F(UyFP*&AvDp2l+NIC-O1wG~8FhTa=Hu;O&Zb=6 z@1Ob%J+352XFRiS(TK;aXb&n>dA8<<+iY+cY?&fT+23nn&VClzot}X&M14c{t)##a zSQTSph4YkEnxP)-sWFZ!b~g1{ENzj3?GHL%#XfWOV7{JXMdLg9nzuI@fR2!>235Qg z3TS=(nIB~|&Y0}FVJZ(4*BCi)NcQj)hpV@idgLzBNzP@_%uWnVj3B*HLkwD*Yvy4)08qKwp;u<4cl|DwLED1F-se1>$q2569}~)>vLP zVYl2MjTXLAiXZt|BM@K^@W*q)Di5Aq0q`_I`t+Y*XWfT%%iUEs+7q1mU(8W1uX63EN)rN zjUFYu(tlk)*qCH)B1ABLnLPbP@J-23I0OfqQypVa8D6l{3!$z)r$jmg1`;(t2b0(< zL+;LeN&b^d{vbK;%7o7w)B5M&bowTD6a7R_*q2Wls*k-&$ldMmhV`ui$Zff|;pX>c zNg>Oz{DGv;mO4e+O#-s@lBj(HfO@bPKULsVhbg7za(f<@C2lVR;VX>cu1LZgbZ^@36HbF{XhHSWeqAiRcMe0+eu>U&! z>5*y#yJZ6QyBPm6O#k-Bd?Y}elzp6mzb0tG33vnl>h!LPE2i_r(#$Xriekr9= z8YLo|?v`3-e8;QMnKGp6Cs_`&k0D_oQ}`FLl@OIHKFpOx7CTJQ(038r{*&zGX*#%2 znsXmObvV~vE+W<2^a2>z&F69c+iJP-*&K23xfy9@aR3X-%nd>gX69>5Ff2JhyPZ^hHzH(36|`DO<)t60fowE}CMuS{ zGC(N2;f_`b(>FarFjRnkPL358?swAVcjkW)nkh;4@^Q3M7D!BQ)?e)KG&!Qyo``o` z7Cp686z$R0fpTavMq+G}O8(fhSR+<|K?StLs*9z4@tg(a;QsDIR}t+6g_iZ_>9#QA zf~O_QrmLlXA}Jdh0v-#5|5?1WhoC>1UJ7__l>KIrDVrqw>Wx5v0#Hg^%QN!YUrQ@g zVSHme$Ns9=kEvzie4MU&`imWw`J3qf^W>ny4yYIh+0kK1~t-fkM0Z!eKht8TZ}7KYgx0pKwxwRV$nb zu$V*?G_NIZDE%_ts;h``a7bQ>#IAQ~w)c7!qxQ1t;1iko=qpm50-Dqn;M63-%z3p< z0FwsT6+D*D7gtH@pycYN(^c1^pj6$Pbj_pmBemY98!P2sMd6?Y30m+4&a?o(73>kd z(7|TnYJsOJxxb4cz7x1#I?w!vc_D{M=;qkJSoU))?W^vYD|97}p;MDy9S&UOv0YCe zYkxkz8*LKLBng7n8|x5Y!YynfiE^@L>kQ+qY&d#lk?i$j1;=fVdxZEJAmn~(3Ef#l z8kr|1@MQrFye_JAV0V+vAJKk!?lfN!s-;Fd6ox%@Yz$?SBrr1rUn~IfI@3`6D~rg? zJb(RHxMRhKfp$Lmj`{Z+#Uq0LUqex`_atk)9R71%&^>C^b|mQZwghX<`MciYmdU7< z$v4p{vV@|Fi{>XB^Hl`urGytzJskI@Ur0gi4b2q=j_b1HMWhjwe#1^o#x2yyFW3-L zikDcTf*%HBKD@2l+)|syKvv=7&{!vZ(H~m;UyI#^F?ZXoxrEMIAqFoN3U~-+WX22d zGJPv*QXS>AoSp^e!)C7Xe|50lpOxSL;J3E*^ZZb;=<=6bE-(zV38KGM7i(0-3*UbF zUW3hWSs$2E^l|%%N!YC+o74R7mkSzQzmvO&f11iX@11Q5J6&q5CB#F~ zgPrgIBLKKMV-P5RWkZZswkEqBWqL&c+8@!m15&DInVo!IxQmhyDP<&9>}ZUDRuB2A zle!Lin0~j$$=~m9v__VJ+<)LZML7(s?V4&tY>jkLxTe%ancX8!D6akp6j6_-20807 zT&N&R-BcwZv4g)tNY@}q#5MppTL|urVaa6n3og56?F5J5$2VFV!Q zw?!o!glWy`XBEz4k-cB*`D7{B{U+J4!K}Fau++Y{HnB@stbFP0(aoBV!y8^Q^zWt- ztJzgL&~whxNvjt?UMQ*mO^msXuhmOBDXCJW46=*j~r?byZWN#R5!?Q3yk!?9=Q|r85d?? zxJLMA9hXss^4=3yhelENt-J6~v<4~Ts;NbRkt``rD3!J06fsLc)@)AzGxPP~v9z3! zs65Jd+tKHGN5ILIdeMMeyl7}mYOXb9SmZF&Smg8CG%dsUqdVm{6G{*^MQ+^eUhEEb zHIw(@kki+ccKy~bo+*@%y+F@wHj3_}IHCn(3bP6;<>m_$W*C|HK1%{1)Cz9rgeY&e zrWym<)Qr-znTz4H$s~PcA~AWtJltv&h;fk*!S|ZrH&3MA1l1l56@Ko`5XdU?Im&Fh zwIqMR_}vI#61`Jl(vFIkyIuOJR~V?%bF+~10fhMebqDSzDGdhwmx`!0kJ>x3b~7yY z!nC%{yiZ8}JGZf9K*TY!DjiBkXr)Y9tOP))P%Sum;6~&3PPQO&!GpOtJPZWmT062W z;c7y6{xnQsaSF+BS`LAlXe2!_10oe_}(fixD`#Z~%l5{9#=+ zFn!CIw~4P^pFY+3vSMoZlO?^9G|>>Ch=w-dR&;}}e;W$N&40nrL5xlu>a;|wiZnT_ zb)ufGv?<44AK-OoFgs8rS+?6IN+pCh`~iB99=~vE7P(Ud^s;)+wAwK;HcMJTMj!0K z_i9vn`{(5*9j!2%CTq6MhrGQv)20oNur6sOp2BT3pN3NIJV-LJ{9{(D-k+NiY&$|sWQwaTq+@^?ndQC|NaGezX`c% zD4hAUvV=}vUKau50W3eMzlU-uzG;^3E?KKKmC-^EO0Oa|l#Tc>tLSyqUa#+-+;dj? zf}CHD{~Wc|JTqP&V5)T7nJA8nfV$V}qQikum6UWTpD7su$#kv@ak!8|ql}VahZBwo z1FPKe;cixEIxw>xYWqgZDoEZrh8ZS;<|r7?Qi|$neN6H8;rXP~S|=^Wsaonn^^5FB!EDE& zz#*pCOc{6wy!IuzPYf+BeS;oH!sAetgyDf z8wSWp$o9049m$^8FQ11$gC2DaX7PL*Hh(aLAarB%a7q37ug1H{pK7e(TxZJoeUg6( zq9sv_g(l7ehLPz@a6yge3M%sF-jukLHACe#Z*de~?ySUITDiKQDU~#|n$DN(%#TF> z^lGgSxPEzkW&U_eMdg)VUkBetvr|WBd4IiMWH$yQ`VQbLE=H zd+Y^_WdAN1cf3u&W#X!b&pcSre&ARp@P-`00u$Mh1-d2{Tm7|-YBpqEV z*yIc~CBk4%w>4#acc*zY&6#+kZUgluAYIghin6ZR^zexv1K{Y(z|mt>KlE7uVO|L- zn^w}vEsW(1y^!rWn*jEKf@$Z+TZ4niEN`Uaw(vM-=w_Emgw|iDdsl% zWu|^f0$D|dh+&kDBVG>9FZ&sR%kZPAi6@Od*S@F^*d{s}bL**4OcYg3GLm8#kt=zD zjz%AW9Afj@fKN*^yi9pQUu$fmO<&8ieQkH?t!TE>D{7553QxkJ-b#82k_QBEbS9qi ztZ7e8KWitB-$^Z&S5YdAIC1VpS-+s#ERrOIS|eTINf?CMe+`K7JZg*I09h3QNvp^8 za`ayYIe~^^D@;>5RHM3>Pcm(GvUt1!|`D)Zc&pNyE&a|N3-QY0#P}8W{AS}{pj6yzM1>q zdm|XI(A3YS9bzAjQZqTzr=6d$F<|a{YcVfP2t2(-Edz0m-(ObbxRCmr`o1|%ME!Jr zdE|zx>cWy{%Z!PCdT;ZXqX4;D!r_K}>Se5*EDfhLxJttAdns#HK6fH>Ssoxipu*8j zWdtO5a}3t&f)VLx!eX=;*=39doh6;Q7gRBAAg@KRZ$z8VG1HPc_gnu~z5P=rJmd~C zc-%w`$91(?HHGN;p~Y65aj}Bww(RI$(A4eGZ|?;FLorWhlT6~^aI*TItO%pZJaJ0? zuhd-j7gl+FI~Ig&YFN*1@4b9fRl+f!NLyD&F>6W9{EszQpMMzb{r9p+z&2#S1f&K< zWX4-tFU`1)0-vM6^ozIJiXGy#w`aU11>6mKd)&0s=`gnEIFwh4kERyBIhD!rI7~TL z_sxz7jmQJ(7PIDgD|Q!^t+T!GVdU)}+mFlMYKvK0#pi0ykVt;=B?T0#`kJAWvV$+{ zFtg6ZwT940W(cYNeh^TLJXKp;1;j_z*na5?bv;*-pzyO0Wvd4g_IP3A`ZD`AFe+q|gO)T&!!U3poUfLNj z#4qi1nCQAC1`RY`2!0lSFhJ?qTgPLhbUoE9)y>%UfFTC{=3*DKedWOviKWQ~h|{pX zkq|9sJ=zq$T@y? zkVHTI@$JAeveL@$mRzA}`$yiwo&YE`PvW1U=}(tmplq6ik{#KeT^pc+F8v2JP~#4q zeBo@PAfpE6oXPRBP%tkP`YEk`C&;Egsz4g7A3X3`@WZu_M(O0EY$_}+A6TF)TTv{j zS{ORi&vvczWXO$9BM7V_0_zw2l@a94Xk zR+2wZZyWc*4cGO?>*vlt2RSyHzx+=GWG~exxW$~c7Z?TGY6N#0Qt0|K*e&|5)>&GQqY`4FF>|+Y)1^4peQLAA>mwwF?`%DjxAWmI0#v$`(Zq{(V0D}W z2(!!%c3(I`N;J0CCk_AgP~Y4zn@f-a-g&-ZdnbMu?XA;&L}=9`6ImYbdgFwsG``Vq zIl{n?vq^4~JQ#8w=q<+PiAQm%Bwx~%9hUFOLzPYO8FU!4mS@fMn6Q?akKb*;C1tQd;oPnEM<~&iNmWd?i#QyvYB7(vhfAe$x}Esa7vHe( z)`M2wsrdipF8L$kDx>(_AT7yfpz%oJNc{5Q)*kq$-x6P*9v2I+FgFf{S|-xX7MwK* z^9-Z8jVOW&@=9@h(hB1EAsmT|d17?EL=PSw2KpUcc2#8=MStEr`Iqu4IbYH>BnI}vt(&EG?aR}nT8Wd>1Bh_`Z zxu8$775f&dSyTX5bkg1J57#Ej{d;Hp!wC?LBXNtf@h& z3T)7xu~YSYnwJsT#^Y&;Wkiv!UGu{J-I2GRJjaGX)oGSZ_Kd%xA@R!}^=Q4q785;g z4xT$$kgj{dE%Uf3t(!JQ2HKg%>JYV@$yv(+gvceA`4O))?REbENGX0x)_dbB%pi66<;TVwIA7Z*s=b#EJrWKsZ2qj*_eXk|VYw1Z z3nx0(>#{&At431O@ykt0$N_9#;fnQOC$XQZ^hQTXw+AFtkd-eo*!V63SC918sY%*@ zk-Uu61ILgT$y00e@9aM1H{=?71};Wnjng?iv8Xez{NnCkkK@UJZMWuAi(>QHwz0ff%9C?i5^>k+G?fvHXk!O_`(+N$ew?7keD8UB|G(EYZx37_f<%JrQ`lKHZz5cBbFJ%_n3pzFS10H1Vd&}FOG3}ljfE@PafE` zJNMY}3`WWu-rkf*MQ-Kz{P`+ri~DE8$q^oX=G$toFek zN?zu9wnHF(A{9(fX1{UtXS0BG_fT>Y7}iTTCuKck=3-S+av@A?#Jb3AJ3JtE4712# z-NO-HJmZgLpAvssCh`3@QF(+Rc)@BZM+;1o)f7IvK3_WPA!xRi14*tgmm{OMXqZ5L(DFT3#ktOVJO%XT=?TFexCNb0M}f zpe>f?dw!fO+2=;8C0q;v#eVU=a9w-I;&U{cGPq_TC*|vyA(w{qN^GU;Z zVc&*r?Z*_uDIXvhn)V@2nr40%?11Z6w+}T%R~$ZchmE2fM_IX0mC#Or*RM~FAf+}2 z3C%8rKJw)qkdQ%oIOjwGcA~N_VzsJ%kRDfVcH_DykS-nikd3XwXi&Ul6|%R}nCoU@l2%*%CaYe+jRjeI5J z;7r*#M(a09DrV9FnfMDKN^};F_DMAi)g$rNZvM)R-ijQ1)%E@lj1d341Z+pO%tw5S z#vj})?L9a7LjrklFr~HQ0{dfgjGH#Z8F&=$2EkY;zj<#uO7Fu-jg^s6rIoQ*X)KCN kC)Jgl7IyDt1M(=32|CI*DI7{v>LBo^D2GCp%9sWI59Y;SdH?_b literal 0 HcmV?d00001 diff --git a/pkg/webui/ui/public/manifest.json b/pkg/webui/ui/public/manifest.json new file mode 100644 index 000000000..080d6c77a --- /dev/null +++ b/pkg/webui/ui/public/manifest.json @@ -0,0 +1,25 @@ +{ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "logo192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "logo512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/pkg/webui/ui/public/robots.txt b/pkg/webui/ui/public/robots.txt new file mode 100644 index 000000000..e9e57dc4d --- /dev/null +++ b/pkg/webui/ui/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/pkg/webui/ui/public/staticbuild.js b/pkg/webui/ui/public/staticbuild.js new file mode 100644 index 000000000..58271d740 --- /dev/null +++ b/pkg/webui/ui/public/staticbuild.js @@ -0,0 +1,2 @@ +let isStaticBuild = false; +const staticResults = new Map() diff --git a/pkg/webui/ui/src/api.tsx b/pkg/webui/ui/src/api.tsx new file mode 100644 index 000000000..173f2a42d --- /dev/null +++ b/pkg/webui/ui/src/api.tsx @@ -0,0 +1,251 @@ +import { + CommandResult, + CommandResultSummary, + ObjectRef, + ProjectKey, + ProjectSummary, + ResultObject, + ShortName, + TargetKey +} from "./models"; +import _ from "lodash"; +import React from "react"; +import { Box, Typography } from "@mui/material"; +import Tooltip from "@mui/material/Tooltip"; +import "./staticbuild.d.ts" +import { loadScript } from "./loadscript"; + +const apiUrl = "/api" +const staticPath = "./staticdata" + +export enum ObjectType { + Rendered = "rendered", + Remote = "remote", + Applied = "applied", +} + +export interface Api { + getShortNames(): Promise + listProjects(): Promise + listResults(filterProject?: string, filterSubDir?: string): Promise + getResult(resultId: string): Promise + getResultObject(resultId: string, ref: ObjectRef, objectType: string): Promise + validateNow(project: ProjectKey, target: TargetKey): Promise +} + +class RealApi implements Api { + async getShortNames(): Promise { + let url = `${apiUrl}/getShortNames` + return fetch(url) + .then(handleErrors) + .then((response) => response.json()); + } + + async listProjects(): Promise { + let url = `${apiUrl}/listProjects` + return fetch(url) + .then(handleErrors) + .then((response) => response.json()); + } + + async listResults(filterProject?: string, filterSubDir?: string): Promise { + let url = `${apiUrl}/listResults` + const params = new URLSearchParams() + if (filterProject) { + params.set("filterProject", filterProject) + } + if (filterSubDir) { + params.set("filterSubDir", filterSubDir) + } + url += "?" + params.toString() + return fetch(url) + .then(handleErrors) + .then((response) => response.json()); + } + + async getResult(resultId: string) { + let url = `${apiUrl}/getResult?resultId=${resultId}` + return fetch(url) + .then(handleErrors) + .then(response => response.text()) + .then(json => { + return new CommandResult(json) + }); + } + + async getResultObject(resultId: string, ref: ObjectRef, objectType: string) { + let url = `${apiUrl}/getResultObject?resultId=${resultId}&${buildRefParams(ref)}&objectType=${objectType}` + return fetch(url) + .then(handleErrors) + .then(response => response.json()); + } + + async validateNow(project: ProjectKey, target: TargetKey) { + const key = { + "project": project, + "target": target, + } + + let url = `${apiUrl}/validateNow` + return fetch(url, { + method: "POST", + body: JSON.stringify(key), + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + }).then(handleErrors); + } +} + +class StaticApi implements Api { + async getShortNames(): Promise { + await loadScript(staticPath + "/shortnames.js") + return staticShortNames + } + async listProjects(): Promise { + await loadScript(staticPath + "/projects.js") + return staticProjects + } + async listResults(filterProject?: string, filterSubDir?: string): Promise { + await loadScript(staticPath + "/summaries.js") + return staticSummaries.filter(s => { + if (filterProject && filterProject != s.project.normalizedGitUrl) { + return false + } + if (filterSubDir && filterSubDir != s.project.subDir) { + return false + } + return true + }) + } + async getResult(resultId: string): Promise { + await loadScript(staticPath + `/result-${resultId}.js`) + return staticResults.get(resultId) + } + async getResultObject(resultId: string, ref: ObjectRef, objectType: string): Promise { + const result = await this.getResult(resultId) + const object = result.objects?.find(x => _.isEqual(x.ref, ref)) + if (!object) { + throw new Error("object not found") + } + switch (objectType) { + case ObjectType.Rendered: + return object.rendered + case ObjectType.Remote: + return object.remote + case ObjectType.Applied: + return object.applied + default: + throw new Error("unknown object type " + objectType) + } + } + validateNow(project: ProjectKey, target: TargetKey): Promise { + throw new Error("not implemented") + } +} + +export let api: Api +if (isStaticBuild) { + api = new StaticApi() +} else { + api = new RealApi() +} + +function handleErrors(response: Response) { + if (!response.ok) { + throw Error(response.statusText) + } + return response +} + +function buildRefParams(ref: ObjectRef): string { + const params = new URLSearchParams() + params.set("kind", ref.kind) + params.set("name", ref.name) + if (ref.group) { + params.set("group", ref.group) + } + if (ref.version) { + params.set("version", ref.version) + } + if (ref.namespace) { + params.set("namespace", ref.namespace) + } + return params.toString() +} + +export function buildRefString(ref: ObjectRef): string { + if (ref.namespace) { + return `${ref.namespace}/${ref.kind}/${ref.name}` + } else { + if (ref.name) { + return `${ref.kind}/${ref.name}` + } else { + return ref.kind + } + } +} + +export function buildRefKindElement(ref: ObjectRef, element?: React.ReactElement): React.ReactElement { + const tooltip = + ApiVersion: {[ref.group, ref.version].filter(x => x).join("/")}
    + Kind: {ref.kind} +
    + return + {element ? element : {ref.kind}} + +} + +export function buildObjectRefFromObject(obj: any): ObjectRef { + const apiVersion: string = obj.apiVersion + const s = apiVersion.split("/", 2) + let ref = new ObjectRef() + if (s.length === 1) { + ref.version = s[0] + } else { + ref.group = s[0] + ref.version = s[1] + } + ref.kind = obj.kind + ref.namespace = obj.metadata.namespace + ref.name = obj.metadata.name + return ref +} + +export function findObjectByRef(l: ResultObject[] | undefined, ref: ObjectRef, filter?: (o: ResultObject) => boolean): ResultObject | undefined { + return l?.find(x => { + if (filter && !filter(x)) { + return false + } + return _.isEqual(x.ref, ref) + }) +} + +export function usePromise(p?: Promise): T { + if (p === undefined) { + throw new Promise(() => undefined) + } + + const promise = p as any + if (promise.status === 'fulfilled') { + return promise.value; + } else if (promise.status === 'rejected') { + throw promise.reason; + } else if (promise.status === 'pending') { + throw promise; + } else { + promise.status = 'pending'; + p.then( + result => { + promise.status = 'fulfilled'; + promise.value = result; + }, + reason => { + promise.status = 'rejected'; + promise.reason = reason; + }, + ); + throw promise; + } +} \ No newline at end of file diff --git a/pkg/webui/ui/src/components/ActionsMenu.tsx b/pkg/webui/ui/src/components/ActionsMenu.tsx new file mode 100644 index 000000000..8247b7ed8 --- /dev/null +++ b/pkg/webui/ui/src/components/ActionsMenu.tsx @@ -0,0 +1,93 @@ +import React from "react"; +import { MoreVert } from "@mui/icons-material"; +import { IconButton, Menu, MenuItem } from "@mui/material"; +import { Link } from "react-router-dom"; + +export interface ActionMenuItem { + icon: React.ReactNode + text: string + handler?: () => void + to?: string +} + +export const ActionsMenu = (props: { icon?: React.ReactNode, menuItems: ActionMenuItem[] }) => { + const [anchorEl, setAnchorEl] = React.useState(null); + const menuOpen = Boolean(anchorEl); + + const handleMenuClick = (e: React.MouseEvent) => { + e.stopPropagation() + setAnchorEl(e.currentTarget); + }; + const handleMenuClose = () => { + setAnchorEl(null); + }; + + const icon = props.icon || + + return + + {icon} + + {e.stopPropagation(); handleMenuClose()}} + PaperProps={{ + elevation: 0, + sx: { + overflow: 'visible', + filter: 'drop-shadow(0px 2px 8px rgba(0,0,0,0.32))', + mt: 1.5, + '& .MuiAvatar-root': { + width: 32, + height: 32, + ml: -0.5, + mr: 1, + }, + '&:before': { + content: '""', + display: 'block', + position: 'absolute', + top: 0, + right: 14, + width: 10, + height: 10, + bgcolor: 'background.paper', + transform: 'translateY(-50%) rotate(45deg)', + zIndex: 0, + }, + }, + }} + transformOrigin={{ horizontal: 'right', vertical: 'top' }} + anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }} + > + {props.menuItems.map((item, i) => { + const handler = (e: React.SyntheticEvent) => { + e.stopPropagation() + if (item.handler) { + item.handler() + } + handleMenuClose() + } + if (item.to) { + return + {item.icon} {item.text} + + } else { + return + {item.icon} {item.text} + + } + })} + + + +} diff --git a/pkg/webui/ui/src/components/App.css b/pkg/webui/ui/src/components/App.css new file mode 100644 index 000000000..78b8850cf --- /dev/null +++ b/pkg/webui/ui/src/components/App.css @@ -0,0 +1,38 @@ +.App { + text-align: center; +} + +.App-logo { + height: 40vmin; + pointer-events: none; +} + +@media (prefers-reduced-motion: no-preference) { + .App-logo { + animation: App-logo-spin infinite 20s linear; + } +} + +.App-header { + background-color: #282c34; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: calc(10px + 2vmin); + color: white; +} + +.App-link { + color: #61dafb; +} + +@keyframes App-logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} diff --git a/pkg/webui/ui/src/components/App.test.tsx b/pkg/webui/ui/src/components/App.test.tsx new file mode 100644 index 000000000..2a68616d9 --- /dev/null +++ b/pkg/webui/ui/src/components/App.test.tsx @@ -0,0 +1,9 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import App from './App'; + +test('renders learn react link', () => { + render(); + const linkElement = screen.getByText(/learn react/i); + expect(linkElement).toBeInTheDocument(); +}); diff --git a/pkg/webui/ui/src/components/App.tsx b/pkg/webui/ui/src/components/App.tsx new file mode 100644 index 000000000..ce3a71aa9 --- /dev/null +++ b/pkg/webui/ui/src/components/App.tsx @@ -0,0 +1,30 @@ +import React, { useState } from 'react'; + +import '../index.css'; +import { Box } from "@mui/material"; +import { Outlet, useOutletContext } from "react-router-dom"; +import LeftDrawer from "./LeftDrawer"; + +export interface AppOutletContext { + filters?: React.ReactNode + setFilters: (filter: React.ReactNode) => void +} + +export function useAppOutletContext(): AppOutletContext { + return useOutletContext() +} + +const App = () => { + const [filters, setFilters] = useState() + + const context: AppOutletContext = { + filters: filters, + setFilters: setFilters, + } + + return + } context={context}/> + +}; + +export default App; diff --git a/pkg/webui/ui/src/components/CodeViewer.tsx b/pkg/webui/ui/src/components/CodeViewer.tsx new file mode 100644 index 000000000..2e55a82c0 --- /dev/null +++ b/pkg/webui/ui/src/components/CodeViewer.tsx @@ -0,0 +1,29 @@ +import React from 'react'; + +import { Light as SyntaxHighlighter } from 'react-syntax-highlighter'; +import { docco } from 'react-syntax-highlighter/dist/esm/styles/hljs'; + +import yamlLanguage from 'react-syntax-highlighter/dist/esm/languages/hljs/yaml'; +import diffLanguage from 'react-syntax-highlighter/dist/esm/languages/hljs/diff'; + +SyntaxHighlighter.registerLanguage('yaml', yamlLanguage); +SyntaxHighlighter.registerLanguage('diff', diffLanguage); + +export function CodeViewer(props: { code: string, language: string }) { + return + {props.code!} + + + + /*return + + {props.code!} + + */ +} \ No newline at end of file diff --git a/pkg/webui/ui/src/components/ErrorsTable.tsx b/pkg/webui/ui/src/components/ErrorsTable.tsx new file mode 100644 index 000000000..f15abf73a --- /dev/null +++ b/pkg/webui/ui/src/components/ErrorsTable.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import Table from '@mui/material/Table'; +import TableBody from '@mui/material/TableBody'; +import TableCell from '@mui/material/TableCell'; +import TableContainer from '@mui/material/TableContainer'; +import TableHead from '@mui/material/TableHead'; +import TableRow from '@mui/material/TableRow'; +import Paper from '@mui/material/Paper'; +import { DeploymentError } from "../models"; +import { Box, Divider, List, ListItem, ListItemText } from "@mui/material"; +import { buildRefKindElement } from "../api"; + +export function ErrorsTable(props: { errors: DeploymentError[] }) { + return <> + + + + + + Ref + Message + + + + {props.errors?.map((e, i) => ( + + + + {buildRefKindElement(e.ref, <> + + + + )} + + + + + {e.ref.namespace && <> + + + + + } + + + + {e.message} + + + ))} + +
    +
    +
    + +} \ No newline at end of file diff --git a/pkg/webui/ui/src/components/Jdenticon.tsx b/pkg/webui/ui/src/components/Jdenticon.tsx new file mode 100644 index 000000000..b46d01ffd --- /dev/null +++ b/pkg/webui/ui/src/components/Jdenticon.tsx @@ -0,0 +1,17 @@ +import React, { useEffect, useRef } from 'react'; +import * as jdenticon from 'jdenticon'; +import { Box, SvgIcon } from "@mui/material"; + +export const Jdenticon = (props: { value: string, size: string }) => { + const icon = useRef(null); + useEffect(() => { + if (icon.current === null) { + return + } + jdenticon.update(icon.current, props.value); + }, [props, icon]) + + return + + +} diff --git a/pkg/webui/ui/src/components/LeftDrawer.tsx b/pkg/webui/ui/src/components/LeftDrawer.tsx new file mode 100644 index 000000000..7c23a7be3 --- /dev/null +++ b/pkg/webui/ui/src/components/LeftDrawer.tsx @@ -0,0 +1,170 @@ +import * as React from 'react'; +import { useState } from 'react'; +import { CSSObject, styled, Theme, useTheme } from '@mui/material/styles'; +import Box from '@mui/material/Box'; +import MuiDrawer from '@mui/material/Drawer'; +import MuiAppBar, { AppBarProps as MuiAppBarProps } from '@mui/material/AppBar'; +import Toolbar from '@mui/material/Toolbar'; +import List from '@mui/material/List'; +import Divider from '@mui/material/Divider'; +import IconButton from '@mui/material/IconButton'; +import MenuIcon from '@mui/icons-material/Menu'; +import ChevronLeftIcon from '@mui/icons-material/ChevronLeft'; +import ChevronRightIcon from '@mui/icons-material/ChevronRight'; +import ListItem from '@mui/material/ListItem'; +import ListItemButton from '@mui/material/ListItemButton'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import ListItemText from '@mui/material/ListItemText'; +import { Adjust } from "@mui/icons-material"; +import { Link } from "react-router-dom"; +import { AppOutletContext } from "./App"; +import { KluctlLogo } from "../icons/KluctlLogo"; + +const drawerWidth = 240; + +const openedMixin = (theme: Theme): CSSObject => ({ + width: drawerWidth, + transition: theme.transitions.create('width', { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.enteringScreen, + }), + overflowX: 'hidden', +}); + +const closedMixin = (theme: Theme): CSSObject => ({ + transition: theme.transitions.create('width', { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen, + }), + overflowX: 'hidden', + width: `calc(${theme.spacing(7)} + 1px)`, + [theme.breakpoints.up('sm')]: { + width: `calc(${theme.spacing(8)} + 1px)`, + }, +}); + +const DrawerHeader = styled('div')(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + justifyContent: 'flex-end', + padding: theme.spacing(0, 1), + // necessary for content to be below app bar + ...theme.mixins.toolbar, +})); + +interface AppBarProps extends MuiAppBarProps { + open?: boolean; +} + +const AppBar = styled(MuiAppBar, { + shouldForwardProp: (prop) => prop !== 'open', +})(({ theme, open }) => ({ + zIndex: theme.zIndex.drawer + 1, + transition: theme.transitions.create(['width', 'margin'], { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen, + }), + ...(open && { + marginLeft: drawerWidth, + width: `calc(100% - ${drawerWidth}px)`, + transition: theme.transitions.create(['width', 'margin'], { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.enteringScreen, + }), + }), +})); + +const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => prop !== 'open' })( + ({ theme, open }) => ({ + width: drawerWidth, + flexShrink: 0, + whiteSpace: 'nowrap', + boxSizing: 'border-box', + ...(open && { + ...openedMixin(theme), + '& .MuiDrawer-paper': openedMixin(theme), + }), + ...(!open && { + ...closedMixin(theme), + '& .MuiDrawer-paper': closedMixin(theme), + }), + }), +); + +function Item(props: { text: string, open: boolean, icon: React.ReactNode, to: string }) { + return + + + {props.icon} + + + + +} + +export default function LeftDrawer(props: { content: React.ReactNode, context: AppOutletContext }) { + const context = props.context + + const theme = useTheme(); + const [open, setOpen] = useState(true); + + const handleDrawerOpen = () => { + setOpen(true); + }; + + const handleDrawerClose = () => { + setOpen(false); + }; + + return ( + + + + + + + + + + + + + {theme.direction === 'rtl' ? : } + + + + + } to={"targets"}/> + + {context.filters} + + + + + + {props.content} + + + ); +} diff --git a/pkg/webui/ui/src/components/Loading.tsx b/pkg/webui/ui/src/components/Loading.tsx new file mode 100644 index 000000000..9b26bf95e --- /dev/null +++ b/pkg/webui/ui/src/components/Loading.tsx @@ -0,0 +1,15 @@ +import { Box, CircularProgress } from "@mui/material"; + +export const Loading = () => { + return + + + + +} \ No newline at end of file diff --git a/pkg/webui/ui/src/components/ObjectYaml.tsx b/pkg/webui/ui/src/components/ObjectYaml.tsx new file mode 100644 index 000000000..11f50f428 --- /dev/null +++ b/pkg/webui/ui/src/components/ObjectYaml.tsx @@ -0,0 +1,26 @@ +import { CommandResultProps } from "./result-view/CommandResultView"; +import { ObjectRef } from "../models"; +import { api, ObjectType, usePromise } from "../api"; +import React, { Suspense, useEffect, useState } from "react"; +import { CodeViewer } from "./CodeViewer"; + +import * as yaml from 'js-yaml'; +import { Loading } from "./Loading"; + +export const ObjectYaml = (props: {treeProps: CommandResultProps, objectRef: ObjectRef, objectType: ObjectType}) => { + const [promise, setPromise] = useState>() + + useEffect(() => { + const p = api.getResultObject(props.treeProps.summary.id, props.objectRef, props.objectType) + .then(yaml.dump) + setPromise(p) + }, [props.treeProps, props.objectRef, props.objectType]) + + const Content = () => { + return + } + + return }> + + +} \ No newline at end of file diff --git a/pkg/webui/ui/src/components/PropertiesTable.tsx b/pkg/webui/ui/src/components/PropertiesTable.tsx new file mode 100644 index 000000000..20cdd967e --- /dev/null +++ b/pkg/webui/ui/src/components/PropertiesTable.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import Table from '@mui/material/Table'; +import TableBody from '@mui/material/TableBody'; +import TableCell from '@mui/material/TableCell'; +import TableContainer from '@mui/material/TableContainer'; +import TableHead from '@mui/material/TableHead'; +import TableRow from '@mui/material/TableRow'; +import Paper from '@mui/material/Paper'; + +export function PropertiesTable(props: {properties: {name: string, value: React.ReactNode}[]}) { + return ( + + + + + Name + Value + + + + {props.properties.map((row) => ( + + {row.name} + {row.value} + + ))} + +
    +
    + ); +} \ No newline at end of file diff --git a/pkg/webui/ui/src/components/Router.tsx b/pkg/webui/ui/src/components/Router.tsx new file mode 100644 index 000000000..ee3417a1a --- /dev/null +++ b/pkg/webui/ui/src/components/Router.tsx @@ -0,0 +1,41 @@ +import { createBrowserRouter, createHashRouter, useRouteError } from "react-router-dom"; +import React from "react"; +import App from "./App"; +import { projectsLoader, TargetsView } from "./targets-view/TargetsView"; +import { commandResultLoader, CommandResultView } from "./result-view/CommandResultView"; + +function ErrorPage() { + const error = useRouteError() as any; + + return ( +
    +

    Oops!

    +

    Sorry, an unexpected error has occurred.

    +

    + {error.statusText || error.message} +

    +
    + ); +} + +export const Router = createHashRouter([ + { + path: "/", + element: , + errorElement: , + children: [ + { + path: "targets", + element: , + loader: projectsLoader, + errorElement: , + }, + { + path: "results/:id", + element: , + loader: commandResultLoader, + errorElement: , + }, + ], + }, +]); diff --git a/pkg/webui/ui/src/components/result-view/ChangesTable.tsx b/pkg/webui/ui/src/components/result-view/ChangesTable.tsx new file mode 100644 index 000000000..b802201f3 --- /dev/null +++ b/pkg/webui/ui/src/components/result-view/ChangesTable.tsx @@ -0,0 +1,105 @@ +import React from 'react'; +import Table from '@mui/material/Table'; +import TableBody from '@mui/material/TableBody'; +import TableCell from '@mui/material/TableCell'; +import TableContainer from '@mui/material/TableContainer'; +import TableHead from '@mui/material/TableHead'; +import TableRow from '@mui/material/TableRow'; +import Paper from '@mui/material/Paper'; +import { buildRefKindElement, buildRefString } from "../../api"; +import { Box, Typography } from "@mui/material"; +import { CodeViewer } from "../CodeViewer"; +import { DiffStatus } from "./nodes/NodeData"; +import Divider from "@mui/material/Divider"; +import { ObjectRef } from "../../models"; + +const RefList = (props: { title: string, refs: ObjectRef[] }) => { + return + {props.title} + + + + + + Kind + + + Namespace + + + Name + + + + + {props.refs.map((ref, i) => { + return + + {buildRefKindElement(ref)} + + + {ref.namespace} + + + {ref.name} + + + })} + +
    +
    + +
    +} + +export function ChangesTable(props: { diffStatus: DiffStatus }) { + let changedObjects: React.ReactElement | undefined + + if (props.diffStatus.changedObjects.length) { + changedObjects = + Changed Objects + {props.diffStatus.changedObjects.map((co, i) => ( + + + + + + {buildRefString(co.ref)} + + + + Path + Changes + + + + {co.changes?.map((c, i) => ( + + + + {c.jsonPath} + + + + + + + ))} + +
    +
    + ))} + +
    + } + + return + {props.diffStatus.newObjects.length ? + : <>} + {props.diffStatus.deletedObjects.length ? + : <>} + {props.diffStatus.orphanObjects.length ? + : <>} + {changedObjects} + +} \ No newline at end of file diff --git a/pkg/webui/ui/src/components/result-view/CommandResultStatusLine.tsx b/pkg/webui/ui/src/components/result-view/CommandResultStatusLine.tsx new file mode 100644 index 000000000..d5a48a05b --- /dev/null +++ b/pkg/webui/ui/src/components/result-view/CommandResultStatusLine.tsx @@ -0,0 +1,59 @@ +import { CommandResultSummary, ValidateResult } from "../../models"; +import { Box, Tooltip, Typography } from "@mui/material"; +import { Dangerous, Delete, Difference, LibraryAdd, LinkOff, Warning } from "@mui/icons-material"; +import React from "react"; + +export interface StatusLineProps { + errors?: number + warnings?: number + changedObjects?: number + newObjects?: number + deletedObjects?: number + orphanObjects?: number +} + +export const StatusLine = (props: StatusLineProps) => { + const children: React.ReactElement[] = [] + + const doPush = (n: number | undefined, t: string, icon: React.ReactElement) => { + if (n) { + children.push( + + + {icon} + + {n} + + ) + } + } + + doPush(props.errors, "total errors.", ) + doPush(props.warnings, "total warnings.", ) + doPush(props.newObjects, "new objects.", ) + doPush(props.deletedObjects, "deleted objects.", ) + doPush(props.orphanObjects, "orphan objects.", ) + doPush(props.changedObjects, "changed objects.", ) + + return + {children} + +} + +export const CommandResultStatusLine = (props: { rs: CommandResultSummary }) => { + return +} + + +export const ValidateResultStatusLine = (props: { vr?: ValidateResult }) => { + return +} \ No newline at end of file diff --git a/pkg/webui/ui/src/components/result-view/CommandResultTree.tsx b/pkg/webui/ui/src/components/result-view/CommandResultTree.tsx new file mode 100644 index 000000000..d1986046a --- /dev/null +++ b/pkg/webui/ui/src/components/result-view/CommandResultTree.tsx @@ -0,0 +1,95 @@ +import * as React from 'react'; +import { useEffect, useMemo, useState } from 'react'; +import TreeView from '@mui/lab/TreeView'; +import { TreeItem } from "@mui/lab"; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import ChevronRightIcon from '@mui/icons-material/ChevronRight'; +import { NodeBuilder } from "./nodes/NodeBuilder"; +import { NodeData } from "./nodes/NodeData"; +import { ActiveFilters, FilterNode } from "./NodeStatusFilter"; +import { CommandResultProps } from "./CommandResultView"; +import { Loading } from "../Loading"; + +export interface CommandResultTreeProps { + commandResultProps?: CommandResultProps + + onSelectNode: (node?: NodeData) => void + activeFilters?: ActiveFilters +} + +const CommandResultTree = (props: CommandResultTreeProps) => { + const [expanded, setExpanded] = useState(["root"]); + const [selectedNodeId, setSelectedNodeId] = useState() + + const [rootNode, nodeMap] = useMemo(() => { + if (!props.commandResultProps) { + return [undefined, undefined] + } + + const builder = new NodeBuilder(props.commandResultProps) + return builder.buildRoot() + }, [props.commandResultProps]) + + const handleToggle = (event: React.SyntheticEvent, nodeIds: string[]) => { + setExpanded(nodeIds); + }; + + const handleDoubleClick = (e: React.SyntheticEvent, node: NodeData) => { + if (expanded.includes(node.id)) { + setExpanded(expanded.filter((item) => item !== node.id)); + } else { + setExpanded([...expanded, node.id]); + } + e.stopPropagation() + }; + + const handleItemClick = (e: React.SyntheticEvent, node: NodeData) => { + setSelectedNodeId(node.id) + e.stopPropagation() + } + + const onSelectNode = props.onSelectNode + useEffect(() => { + if (!nodeMap || !selectedNodeId) { + return + } + const node = nodeMap.get(selectedNodeId) + if (!node) { + setSelectedNodeId(undefined) + } + onSelectNode(node) + }, [nodeMap, selectedNodeId, onSelectNode]) + + const renderTree = (nodes: NodeData) => { + if (!FilterNode(nodes, props.activeFilters)) { + return null + } + return handleItemClick(e, nodes)}>{nodes.buildTreeItem()}} + sx={{ marginBottom: "5px", marginTop: "5px" }} + onDoubleClick={(e: React.SyntheticEvent) => handleDoubleClick(e, nodes)} + > + {Array.isArray(nodes.children) + ? nodes.children.map((node) => renderTree(node)) + : null} + + }; + + if (!rootNode) { + return + } + + return } + defaultExpandIcon={} + sx={{ width: "100%" }} + > + {renderTree(rootNode)} + +} + +export default CommandResultTree; diff --git a/pkg/webui/ui/src/components/result-view/CommandResultView.tsx b/pkg/webui/ui/src/components/result-view/CommandResultView.tsx new file mode 100644 index 000000000..607bc925b --- /dev/null +++ b/pkg/webui/ui/src/components/result-view/CommandResultView.tsx @@ -0,0 +1,53 @@ +import * as React from 'react'; +import { useEffect, useState } from 'react'; +import { Box, Divider } from "@mui/material"; +import { CommandResult, CommandResultSummary, ShortName } from "../../models"; +import { NodeData } from "./nodes/NodeData"; +import { SidePanel } from "./SidePanel"; +import { ActiveFilters, NodeStatusFilter } from "./NodeStatusFilter"; +import CommandResultTree from "./CommandResultTree"; +import { useLoaderData } from "react-router-dom"; +import { api } from "../../api"; +import { useAppOutletContext } from "../App"; + +export interface CommandResultProps { + shortNames: ShortName[] + summary: CommandResultSummary + commandResult: CommandResult +} + +export async function commandResultLoader({ params }: any) { + const result = api.getResult(params.id) + const shortNames = api.getShortNames() + const summaries = api.listResults() + + return { + shortNames: await shortNames, + summary: (await summaries).find(x => x.id === params.id), + commandResult: await result, + } +} + +export const CommandResultView = () => { + const context = useAppOutletContext() + const commandResultProps = useLoaderData() as CommandResultProps + const [activeFilters, setActiveFilters] = useState() + const [sidePanelNode, setSidePanelNode] = useState() + + useEffect(() => { + context.setFilters(<> + + ) + }, []) + + return + + + + + + + + +} diff --git a/pkg/webui/ui/src/components/result-view/NodeStatusFilter.tsx b/pkg/webui/ui/src/components/result-view/NodeStatusFilter.tsx new file mode 100644 index 000000000..3c78613aa --- /dev/null +++ b/pkg/webui/ui/src/components/result-view/NodeStatusFilter.tsx @@ -0,0 +1,97 @@ +import React, { useState } from "react"; +import { Box, Chip } from "@mui/material"; +import { AutoFixHigh, ErrorOutline, Grade } from "@mui/icons-material"; +import { OverridableComponent } from "@mui/material/OverridableComponent"; +import Tooltip from "@mui/material/Tooltip"; +import { NodeData } from "./nodes/NodeData"; + +export interface ActiveFilters { + onlyImportant: boolean + onlyChanged: boolean + onlyWithErrorsOrWarnings: boolean +} + +export function FilterNode(node: NodeData, activeFilters?: ActiveFilters) { + const hasChanges = node.diffStatus?.hasDiffs() + const hasErrorsOrWarnings = node.healthStatus?.errors.length || node.healthStatus?.warnings.length + if (activeFilters?.onlyImportant && !hasChanges && !hasErrorsOrWarnings) { + return false + } + if (activeFilters?.onlyChanged && !hasChanges) { + return false + } + if (activeFilters?.onlyWithErrorsOrWarnings && !hasErrorsOrWarnings) { + return false + } + return true +} + +const FilterButton = (props: { Icon: OverridableComponent, tooltip: string, color: any, active: boolean, handler: (active: boolean) => void }) => { + const handleClick = () => { + props.handler(!props.active) + } + + const Icon = props.Icon + const chipColor = props.active ? props.color : "default"; + return + + } + onClick={handleClick} + sx={{ + "& .MuiChip-label": { + display: "flex", + justifyContent: "center", + alignItems: "center" + } + }} + /> + +} + +export const NodeStatusFilter = (props: { onFilterChange: (f: ActiveFilters) => void }) => { + const [activeFilters, setActiveFilters] = useState({ + onlyImportant: false, + onlyChanged: false, + onlyWithErrorsOrWarnings: false, + }) + + const doSetActiveFilters = (newActiveFilters: ActiveFilters) => { + setActiveFilters(newActiveFilters) + props.onFilterChange(newActiveFilters) + } + + return ( + + Filters + + { + doSetActiveFilters({ ...activeFilters, onlyImportant: active }); + }}/> + { + doSetActiveFilters({ ...activeFilters, onlyChanged: active }); + }}/> + { + doSetActiveFilters({ ...activeFilters, onlyWithErrorsOrWarnings: active }); + }}/> + + + ) +} diff --git a/pkg/webui/ui/src/components/result-view/SidePanel.tsx b/pkg/webui/ui/src/components/result-view/SidePanel.tsx new file mode 100644 index 000000000..da68288f9 --- /dev/null +++ b/pkg/webui/ui/src/components/result-view/SidePanel.tsx @@ -0,0 +1,78 @@ +import { Box, Tab, Typography } from "@mui/material"; +import React, { useEffect, useState } from "react"; +import { TabContext, TabList, TabPanel } from "@mui/lab"; + +export interface SidePanelTab { + label: string + content: React.ReactNode +} + +export interface SidePanelProvider { + buildSidePanelTitle(): React.ReactNode + buildSidePanelTabs(): SidePanelTab[] +} + +export interface SidePanelProps { + provider?: SidePanelProvider; +} + +export const SidePanel = (props: SidePanelProps) => { + let [selectedTab, setSelectedTab] = useState(); + + function handleTabChange(_e: React.SyntheticEvent, value: string) { + setSelectedTab(value); + } + + let tabs = props.provider?.buildSidePanelTabs() + if (!tabs) { + tabs = [] + } + + useEffect(() => { + if (!tabs?.length) { + setSelectedTab("") + return + } + + if (!selectedTab) { + setSelectedTab(tabs[0].label) + return + } + + if (!tabs.find(x => x.label === selectedTab)) { + // reset it after the type of selected item has changed + setSelectedTab(tabs[0].label) + } + // ignore that it wants us to add selectedTab to the deps (it would cause and endless loop) + // eslint-disable-next-line + }, [props.provider]) + + if (!selectedTab || !tabs.find(x => x.label === selectedTab)) { + return <> + } + + if (!props.provider) { + return <> + } + + return + + {props.provider.buildSidePanelTitle()} + + + + + + {tabs.map((tab, i) => { + return + })} + + + {tabs.map((tab, index) => { + return + {tab.content} + + })} + + +} diff --git a/pkg/webui/ui/src/components/result-view/nodes/CommandResultNode.tsx b/pkg/webui/ui/src/components/result-view/nodes/CommandResultNode.tsx new file mode 100644 index 000000000..170b653af --- /dev/null +++ b/pkg/webui/ui/src/components/result-view/nodes/CommandResultNode.tsx @@ -0,0 +1,60 @@ +import React from 'react'; + +import { NodeData } from "./NodeData"; +import { CloudSync } from "@mui/icons-material"; +import { PropertiesTable } from "../../PropertiesTable"; +import { CodeViewer } from "../../CodeViewer"; +import { CommandResultProps } from "../CommandResultView"; + +import * as yaml from 'js-yaml'; +import { SidePanelTab } from "../SidePanel"; + +export class CommandResultNodeData extends NodeData { + dumpedTargetYaml?: string + + constructor(props: CommandResultProps, id: string) { + super(props, id, true, true); + + if (this.props.commandResult.command?.target) { + this.dumpedTargetYaml = yaml.dump(this.props.commandResult.command.target) + } + } + + buildSidePanelTitle(): React.ReactNode { + return "CommandResult"; + } + + buildIcon(): [React.ReactNode, string] { + return [, "result"] + } + + buildSidePanelTabs(): SidePanelTab[] { + const tabs = [ + {label: "Summary", content: this.buildSummaryPage()} + ] + + if (this.dumpedTargetYaml) { + const page = this.buildTargetPage() + tabs.push({label: "Target", content: page}) + } + + this.buildDiffAndHealthPages(tabs) + + return tabs + } + + buildSummaryPage(): React.ReactNode { + const props = [ + {name: "Initiator", value: this.props.commandResult.command?.initiator}, + {name: "Command", value: this.props.commandResult.command?.command}, + ] + + return <> + + + } + + buildTargetPage(): React.ReactNode { + return + } +} diff --git a/pkg/webui/ui/src/components/result-view/nodes/DeletedOrOrphanObjectsCollectionNode.tsx b/pkg/webui/ui/src/components/result-view/nodes/DeletedOrOrphanObjectsCollectionNode.tsx new file mode 100644 index 000000000..5b3cba216 --- /dev/null +++ b/pkg/webui/ui/src/components/result-view/nodes/DeletedOrOrphanObjectsCollectionNode.tsx @@ -0,0 +1,54 @@ +import { NodeData } from "./NodeData"; +import React from "react"; +import { Delete, LinkOff } from "@mui/icons-material"; +import { CommandResultProps } from "../CommandResultView"; +import { PropertiesTable } from "../../PropertiesTable"; +import { SidePanelTab } from "../SidePanel"; + +export class DeletedOrOrphanObjectsCollectionNode extends NodeData { + deleted: boolean + + constructor(props: CommandResultProps, id: string, deleted: boolean) { + super(props, id, false, true); + this.deleted = deleted + } + + buildSidePanelTitle(): React.ReactNode { + if (this.deleted) { + return `${this.diffStatus?.deletedObjects.length} objects deleted` + } else { + return `${this.diffStatus?.orphanObjects.length} objects orphaned` + } + } + + buildIcon(): [React.ReactNode, string] { + if (this.deleted) { + return [, "deleted"] + } else { + return [, "orphans"] + } + } + + buildSidePanelTabs(): SidePanelTab[] { + const tabs = [ + {label: "Summary", content: this.buildSummaryPage()} + ] + + return tabs + } + + buildSummaryPage(): React.ReactNode { + const props = [] + + if (this.diffStatus?.deletedObjects.length) { + props.push({ name: "Deleted", value: this.diffStatus?.deletedObjects.length }) + } + if (this.diffStatus?.orphanObjects.length) { + props.push({ name: "Orphaned", value: this.diffStatus?.orphanObjects.length }) + } + + return <> + + + } +} diff --git a/pkg/webui/ui/src/components/result-view/nodes/DeploymentItemIncludeNode.tsx b/pkg/webui/ui/src/components/result-view/nodes/DeploymentItemIncludeNode.tsx new file mode 100644 index 000000000..8d5ecc36c --- /dev/null +++ b/pkg/webui/ui/src/components/result-view/nodes/DeploymentItemIncludeNode.tsx @@ -0,0 +1,78 @@ +import React from 'react'; + +import { DeploymentItemConfig, DeploymentProjectConfig } from "../../../models"; +import { NodeData } from "./NodeData"; +import { FolderZip } from "@mui/icons-material"; +import { GitIcon } from "../../../icons/GitIcon"; +import { CommandResultProps } from "../CommandResultView"; +import { PropertiesTable } from "../../PropertiesTable"; +import { buildDeploymentItemSummaryProps } from "./DeploymentItemNode"; +import { SidePanelTab } from "../SidePanel"; + + +export class DeploymentItemIncludeNodeData extends NodeData { + deploymentItem: DeploymentItemConfig + includedDeployment: DeploymentProjectConfig + + constructor(props: CommandResultProps, id: string, deploymentItem: DeploymentItemConfig, includedDeployment: DeploymentProjectConfig) { + super(props, id, true, true); + this.deploymentItem = deploymentItem + this.includedDeployment = includedDeployment + } + + buildSidePanelTitle(): React.ReactNode { + if (this.deploymentItem.include) { + return this.deploymentItem.include + } else if (this.deploymentItem.git) { + const s = this.deploymentItem.git!.url.split("/") + const name = s[s.length-1] + return <> + {name} + {this.deploymentItem.git!.subDir && (<>
    {this.deploymentItem.git!.subDir})} + + } else { + return "unknown include" + } + } + + buildIcon(): [React.ReactNode, string] { + if (this.deploymentItem.git) { + return [, "git"] + } + return [, "include"] + } + + buildSidePanelTabs(): SidePanelTab[] { + const tabs = [ + {label: "Summary", content: this.buildSummaryPage()}, + ] + this.buildDiffAndHealthPages(tabs) + return tabs; + } + + buildSummaryPage(): React.ReactNode { + const props = [] + + if (this.deploymentItem.include) { + props.push({ name: "Type", value: "LocalInclude" }) + props.push({ name: "Path", value: this.deploymentItem.include }) + } else if (this.deploymentItem.git) { + props.push({name: "Type", value: "GitInclude"}) + props.push({name: "Url", value: this.deploymentItem.git.url}) + props.push({name: "SubDir", value: this.deploymentItem.git.subDir}) + let ref = "HEAD" + if (this.deploymentItem.git.ref) { + ref = this.deploymentItem.git.ref! + } + props.push({name: "Ref", value: ref}) + } else { + props.push({name: "Type", value: "Unknown"}) + } + + buildDeploymentItemSummaryProps(this.deploymentItem, props) + + return <> + + + } +} diff --git a/pkg/webui/ui/src/components/result-view/nodes/DeploymentItemNode.tsx b/pkg/webui/ui/src/components/result-view/nodes/DeploymentItemNode.tsx new file mode 100644 index 000000000..bebab6f96 --- /dev/null +++ b/pkg/webui/ui/src/components/result-view/nodes/DeploymentItemNode.tsx @@ -0,0 +1,72 @@ +import React from 'react'; + +import { DeploymentItemConfig } from "../../../models"; +import { NodeData } from "./NodeData"; +import { Source } from "@mui/icons-material"; +import { PropertiesTable } from "../../PropertiesTable"; +import { CommandResultProps } from "../CommandResultView"; +import { SidePanelTab } from "../SidePanel"; + + +export class DeploymentItemNodeData extends NodeData { + deploymentItem: DeploymentItemConfig + + constructor(props: CommandResultProps, id: string, deploymentItem: DeploymentItemConfig) { + super(props, id, true, true); + this.deploymentItem = deploymentItem + } + + buildSidePanelTitle(): React.ReactNode { + return this.deploymentItem.path + } + + buildIcon(): [React.ReactNode, string] { + let iconText = "di" + return [, iconText] + } + + buildSidePanelTabs(): SidePanelTab[] { + const tabs = [ + {label: "Summary", content: this.buildSummaryPage()}, + ] + this.buildDiffAndHealthPages(tabs) + return tabs + } + + buildSummaryPage(): React.ReactNode { + const props = [] + + props.push({name: "Path", value: this.deploymentItem.path}) + + buildDeploymentItemSummaryProps(this.deploymentItem, props) + + return <> + + + } +} + +export function buildDeploymentItemSummaryProps(di: DeploymentItemConfig, props: {name: string, value: React.ReactNode}[]) { + if (di.barrier !== undefined) { + props.push({name: "Barrier", value: di.barrier + ""}) + } + if (di.waitReadiness !== undefined) { + props.push({name: "WaitReadiness", value: di.waitReadiness + ""}) + } + if (di.skipDeleteIfTags !== undefined) { + props.push({name: "SkipDeleteIfTags", value: di.skipDeleteIfTags + ""}) + } + if (di.onlyRender !== undefined) { + props.push({name: "OnlyRender", value: di.onlyRender + ""}) + } + if (di.alwaysDeploy !== undefined) { + props.push({name: "AlwaysDeploy", value: di.alwaysDeploy + ""}) + } + if (di.deleteObjects) { + // TODO this is ugly + props.push({name: "DeleteObjects", value: JSON.stringify(di.deleteObjects)}) + } + if (di.when) { + props.push({name: "When", value: di.when}) + } +} diff --git a/pkg/webui/ui/src/components/result-view/nodes/NodeBuilder.ts b/pkg/webui/ui/src/components/result-view/nodes/NodeBuilder.ts new file mode 100644 index 000000000..c3ab9769b --- /dev/null +++ b/pkg/webui/ui/src/components/result-view/nodes/NodeBuilder.ts @@ -0,0 +1,194 @@ +import { + DeploymentError, + DeploymentItemConfig, + DeploymentProjectConfig, + ObjectRef, + ResultObject, + VarsSource +} from "../../../models"; +import { VarsSourceNodeData } from "./VarsSourceNode"; +import { DeploymentItemNodeData } from "./DeploymentItemNode"; +import { ObjectNodeData } from "./ObjectNode"; +import { NodeData } from "./NodeData"; +import { CommandResultNodeData } from "./CommandResultNode"; +import { VarsSourceCollectionNodeData } from "./VarsSourceCollectionNode"; +import { DeploymentItemIncludeNodeData } from "./DeploymentItemIncludeNode"; +import { CommandResultProps } from "../CommandResultView"; +import { DeletedOrOrphanObjectsCollectionNode } from "./DeletedOrOrphanObjectsCollectionNode"; + +export class NodeBuilder { + private props: CommandResultProps + private changedObjectsMap: Map = new Map() + private newObjectsMap: Map = new Map() + private orphanObjectsMap: Map = new Map() + private deletedObjectsMap: Map = new Map() + private errorsMap: Map = new Map() + private warningsMap: Map = new Map() + + constructor(props: CommandResultProps) { + this.props = props + + props.commandResult.objects?.forEach(o => { + const key = buildObjectRefKey(o.ref) + if (o.changes?.length) { + this.changedObjectsMap.set(key, o) + } + if (o.new) { + this.newObjectsMap.set(key, o.ref) + } + if (o.orphan) { + this.orphanObjectsMap.set(key, o.ref) + } + if (o.deleted) { + this.deletedObjectsMap.set(key, o.ref) + } + }) + props.commandResult.errors?.forEach(e => { + const key = buildObjectRefKey(e.ref) + let l = this.errorsMap.get(key) + if (!l) { + l = [e] + this.errorsMap.set(key, l) + } else { + l.push(e) + } + }) + props.commandResult.warnings?.forEach(e => { + const key = buildObjectRefKey(e.ref) + let l = this.warningsMap.get(key) + if (!l) { + l = [e] + this.warningsMap.set(key, l) + } else { + l.push(e) + } + }) + } + + buildRoot(): [CommandResultNodeData, Map] { + const rootNode = new CommandResultNodeData(this.props, "root") + + this.buildDeploymentProjectChildren(rootNode, this.props.commandResult.deployment!) + + if (this.deletedObjectsMap.size) { + this.buildDeletedOrOrphanNode(rootNode, true, Array.from(this.deletedObjectsMap.values())) + } + if (this.orphanObjectsMap.size) { + this.buildDeletedOrOrphanNode(rootNode, false, Array.from(this.orphanObjectsMap.values())) + } + + const nodeMap: Map = new Map() + function collect(n: NodeData) { + nodeMap.set(n.id, n) + n.children.forEach(c => { + collect(c) + }) + } + collect(rootNode) + + return [rootNode, nodeMap] + } + + buildDeploymentProjectChildren(node: NodeData, deploymentProject: DeploymentProjectConfig) { + if (deploymentProject.vars) { + this.buildVarsSourceCollectionNode(node, deploymentProject.vars) + } + deploymentProject.deployments?.forEach((deploymentItem, i) => { + this.buildDeploymentItemNode(node, deploymentItem, i) + }) + } + + buildVarsSourceCollectionNode(parentNode: NodeData, varsSources?: VarsSource[]) { + if (varsSources === undefined) { + return + } + + const newId = `${parentNode.id}/(vars)` + const node = new VarsSourceCollectionNodeData(this.props, newId) + node.varsSources.push(...varsSources) + + varsSources.forEach((vs, i) => { + this.buildVarsSourceNode(node, vs, i) + }) + + parentNode.pushChild(node) + + return node + } + + buildVarsSourceNode(parentNode: NodeData, varsSource: VarsSource, index: number): VarsSourceNodeData { + const newId = `${parentNode.id}/${index}}` + const node = new VarsSourceNodeData(this.props, newId, varsSource) + parentNode.pushChild(node) + return node + } + + buildDeploymentItemNode(parentNode: NodeData, deploymentItem: DeploymentItemConfig, index: number): NodeData | undefined{ + let node: NodeData | undefined + const newId = `${parentNode.id}/(dis)/${index}` + if (deploymentItem.path) { + node = new DeploymentItemNodeData(this.props, newId, deploymentItem) + this.buildVarsSourceCollectionNode(node, deploymentItem.vars) + deploymentItem.renderedObjects?.forEach(renderedObject => { + this.buildObjectNode(node!, renderedObject) + }) + } else if (deploymentItem.include || deploymentItem.git) { + node = new DeploymentItemIncludeNodeData(this.props, newId, deploymentItem, deploymentItem.renderedInclude!) + this.buildVarsSourceCollectionNode(node, deploymentItem.vars) + if (deploymentItem.renderedInclude) { + this.buildDeploymentProjectChildren(node, deploymentItem.renderedInclude) + } + } else { + return node + } + + parentNode.pushChild(node) + + return node + } + + buildObjectNode(parentNode: NodeData, objectRef: ObjectRef): ObjectNodeData { + const newId = `${parentNode.id}/(obj)/${buildObjectRefKey(objectRef)}` + const node = new ObjectNodeData(this.props, newId, objectRef) + + const key = buildObjectRefKey(objectRef) + if (this.changedObjectsMap.has(key)) { + node.diffStatus?.addChangedObject(this.changedObjectsMap.get(key)!) + } + if (this.newObjectsMap.has(key)) { + node.diffStatus?.newObjects.push(objectRef) + } + if (this.deletedObjectsMap.has(key)) { + node.diffStatus?.deletedObjects.push(objectRef) + } + if (this.orphanObjectsMap.has(key)) { + node.diffStatus?.orphanObjects.push(objectRef) + } + + this.errorsMap.get(key)?.forEach(e => { + node.healthStatus?.errors.push(e) + }) + this.warningsMap.get(key)?.forEach(e => { + node.healthStatus?.warnings.push(e) + }) + + parentNode.pushChild(node) + + return node + } + + buildDeletedOrOrphanNode(parentNode: NodeData, deleted: boolean, refs: ObjectRef[]): DeletedOrOrphanObjectsCollectionNode { + const idType = deleted ? "deleted" : "orphaned" + const newId = `${parentNode.id}/(${idType})` + const node = new DeletedOrOrphanObjectsCollectionNode(this.props, newId, deleted) + refs.forEach(ref => { + this.buildObjectNode(node, ref) + }) + parentNode.pushChild(node, true) + return node + } +} + +function buildObjectRefKey(ref: ObjectRef): string { + return [ref.group, ref.version, ref.kind, ref.namespace, ref.name].join("+") +} diff --git a/pkg/webui/ui/src/components/result-view/nodes/NodeData.tsx b/pkg/webui/ui/src/components/result-view/nodes/NodeData.tsx new file mode 100644 index 000000000..55c3d66d4 --- /dev/null +++ b/pkg/webui/ui/src/components/result-view/nodes/NodeData.tsx @@ -0,0 +1,188 @@ +import { ChangedObject, DeploymentError, ObjectRef, ResultObject } from "../../../models"; +import React from "react"; +import { Box, Typography } from "@mui/material"; +import { CommandResultProps } from "../CommandResultView"; +import { ChangesTable } from "../ChangesTable"; +import { ErrorsTable } from "../../ErrorsTable"; +import { ObjectType } from "../../../api"; +import { ObjectYaml } from "../../ObjectYaml"; +import { StatusLine } from "../CommandResultStatusLine"; +import { SidePanelProvider, SidePanelTab } from "../SidePanel"; + +export class DiffStatus { + newObjects: ObjectRef[] = []; + deletedObjects: ObjectRef[] = []; + orphanObjects: ObjectRef[] = []; + changedObjects: ChangedObject[] = []; + + totalInsertions: number = 0; + totalDeletions: number = 0; + totalUpdates: number = 0; + + addChangedObject(co: ResultObject) { + this.changedObjects.push(co) + co.changes?.forEach(x => { + switch (x.type) { + case "insert": + this.totalInsertions++ + break + case "delete": + this.totalDeletions++ + break + case "update": + this.totalUpdates++ + break + } + }) + } + + merge(other: DiffStatus) { + this.newObjects = this.newObjects.concat(other.newObjects) + this.deletedObjects = this.deletedObjects.concat(other.deletedObjects) + this.orphanObjects = this.orphanObjects.concat(other.orphanObjects) + this.changedObjects = this.changedObjects.concat(other.changedObjects) + + this.totalInsertions += other.totalInsertions + this.totalDeletions += other.totalDeletions + this.totalUpdates += other.totalUpdates + } + + hasDiffs() { + if (this.newObjects.length || this.deletedObjects.length || this.orphanObjects.length) { + return true + } + if (this.changedObjects.find(co => co.changes?.length !== 0)) { + return true + } + return false + } +} + +export class HealthStatus { + errors: DeploymentError[] = [] + warnings: DeploymentError[] = [] + + merge(other: HealthStatus) { + this.errors = this.errors.concat(other.errors) + this.warnings = this.warnings.concat(other.warnings) + } +} + +export abstract class NodeData implements SidePanelProvider { + props: CommandResultProps + + id: string + children: NodeData[] = [] + + healthStatus?: HealthStatus; + diffStatus?: DiffStatus; + + protected constructor(props: CommandResultProps, id: string, hasHealthStatus: boolean, hasDiffStatus: boolean) { + this.props = props + this.id = id + if (hasHealthStatus) { + this.healthStatus = new HealthStatus() + } + if (hasDiffStatus) { + this.diffStatus = new DiffStatus() + } + } + + pushChild(child: NodeData, front?: boolean) { + if (front) { + this.children.unshift(child) + } else { + this.children.push(child) + } + this.merge(child) + } + + merge(other: NodeData) { + if (this.diffStatus && other.diffStatus) { + this.diffStatus.merge(other.diffStatus) + } + if (this.healthStatus && other.healthStatus) { + this.healthStatus.merge(other.healthStatus) + } + } + + abstract buildSidePanelTitle(): React.ReactNode + + abstract buildIcon(): [React.ReactNode, string] + + abstract buildSidePanelTabs(): SidePanelTab[] + + buildStatusLine(): React.ReactNode { + return + } + + buildTreeItem(): React.ReactNode { + const [icon, iconText] = this.buildIcon() + + return + + + + {icon} + {iconText} + + + + + {this.buildSidePanelTitle()}
    +
    + + {this.buildStatusLine()} + +
    +
    +
    + } + + buildDiffAndHealthPages(tabs: { label: string, content: React.ReactNode }[]) { + this.buildChangesPage(tabs) + this.buildErrorsPage(tabs) + this.buildWarningsPage(tabs) + } + + buildObjectPage(ref: ObjectRef, objectType: ObjectType): React.ReactNode { + return + } + + buildChangesPage(tabs: { label: string, content: React.ReactNode }[]) { + if (!this.diffStatus?.hasDiffs()) { + return undefined + } + tabs.push({ + label: "Changes", + content: + }) + } + + buildErrorsPage(tabs: { label: string, content: React.ReactNode }[]) { + if (!this.healthStatus?.errors.length) { + return undefined + } + tabs.push({ + label: "Errors", + content: + }) + } + + buildWarningsPage(tabs: { label: string, content: React.ReactNode }[]) { + if (!this.healthStatus?.warnings.length) { + return undefined + } + tabs.push({ + label: "Warnings", + content: + }) + } +} diff --git a/pkg/webui/ui/src/components/result-view/nodes/ObjectNode.tsx b/pkg/webui/ui/src/components/result-view/nodes/ObjectNode.tsx new file mode 100644 index 000000000..62a5b133f --- /dev/null +++ b/pkg/webui/ui/src/components/result-view/nodes/ObjectNode.tsx @@ -0,0 +1,101 @@ +import React from 'react'; + +import { ObjectRef } from "../../../models"; +import { NodeData } from "./NodeData"; +import { + DataObject, + PublishedWithChanges, + Settings, + SettingsEthernet, + SmartToy, + SvgIconComponent +} from "@mui/icons-material"; +import { PropertiesTable } from "../../PropertiesTable"; +import { findObjectByRef, ObjectType } from "../../../api"; +import { CommandResultProps } from "../CommandResultView"; +import { SidePanelTab } from "../SidePanel"; + +const kindMapping: {[key: string]: {icon: SvgIconComponent}} = { + "/ConfigMap": {icon: Settings}, + "apps/Deployment": {icon: PublishedWithChanges}, + "Service": {icon: SettingsEthernet}, + "ServiceAccount": {icon: SmartToy} +} + +export class ObjectNodeData extends NodeData { + objectRef: ObjectRef + + constructor(props: CommandResultProps, id: string, objectRef: ObjectRef) { + super(props, id, true, true); + this.objectRef = objectRef + } + + buildSidePanelTitle(): React.ReactNode { + return this.objectRef.name + } + + buildIcon(): [React.ReactNode, string] { + const sn = this.props.shortNames.find(sn => sn.group === this.objectRef.group && sn.kind === this.objectRef.kind) + const snStr = sn?.shortName || "" + + const m = kindMapping[this.objectRef.group + "/" + this.objectRef.kind] + if (m !== undefined) { + return [React.createElement(m.icon, {fontSize: "large"}), snStr] + } + + return [, snStr] + } + + buildSidePanelTabs(): SidePanelTab[] { + const tabs = [ + {label: "Summary", content: this.buildSummaryPage()} + ] + + this.buildDiffAndHealthPages(tabs) + + if (findObjectByRef(this.props.commandResult.objects, this.objectRef)?.rendered) { + tabs.push({ label: "Rendered", content: this.buildObjectPage(this.objectRef, ObjectType.Rendered) }) + } + + if (findObjectByRef(this.props.commandResult.objects, this.objectRef)?.remote) { + tabs.push({label: "Remote", content: this.buildObjectPage(this.objectRef, ObjectType.Remote)}) + } + if (findObjectByRef(this.props.commandResult.objects, this.objectRef)?.applied) { + tabs.push({label: "Applied", content: this.buildObjectPage(this.objectRef, ObjectType.Applied)}) + } + + return tabs + } + + buildSummaryPage(): React.ReactNode { + const props = [] + + let apiVersion = this.objectRef.version + if (this.objectRef.group) { + apiVersion = this.objectRef.group + "/" + this.objectRef.version + } + props.push({name: "ApiVersion", value: apiVersion}) + props.push({name: "Kind", value: this.objectRef.kind}) + + props.push({name: "Name", value: this.objectRef.name}) + if (this.objectRef.namespace) { + props.push({ name: "Namespace", value: this.objectRef.namespace }) + } + + const o = findObjectByRef(this.props.commandResult.objects, this.objectRef) + const annotations: {[key: string]: string} = o?.rendered?.metadata.annotations + if (annotations) { + Object.keys(annotations).forEach(k => { + if (k.indexOf("kluctl.io/") !== -1) { + props.push({ name: k, value: annotations[k] }) + } + } + ) + } + + return <> + + + } +} + diff --git a/pkg/webui/ui/src/components/result-view/nodes/VarsSourceCollectionNode.tsx b/pkg/webui/ui/src/components/result-view/nodes/VarsSourceCollectionNode.tsx new file mode 100644 index 000000000..20af76274 --- /dev/null +++ b/pkg/webui/ui/src/components/result-view/nodes/VarsSourceCollectionNode.tsx @@ -0,0 +1,26 @@ +import { VarsSource } from "../../../models"; +import { NodeData } from "./NodeData"; +import React from "react"; +import { DataArray } from "@mui/icons-material"; +import { CommandResultProps } from "../CommandResultView"; +import { SidePanelTab } from "../SidePanel"; + +export class VarsSourceCollectionNodeData extends NodeData { + varsSources: VarsSource[] = [] + + constructor(props: CommandResultProps, id: string) { + super(props, id, false, false); + } + + buildSidePanelTitle(): React.ReactNode { + return "vars: " + this.varsSources.length + } + + buildIcon(): [React.ReactNode, string] { + return [, "vars"] + } + + buildSidePanelTabs(): SidePanelTab[] { + return []; + } +} diff --git a/pkg/webui/ui/src/components/result-view/nodes/VarsSourceNode.tsx b/pkg/webui/ui/src/components/result-view/nodes/VarsSourceNode.tsx new file mode 100644 index 000000000..d53838eeb --- /dev/null +++ b/pkg/webui/ui/src/components/result-view/nodes/VarsSourceNode.tsx @@ -0,0 +1,238 @@ +import { VarsSource } from "../../../models"; +import { NodeData } from "./NodeData"; +import React from "react"; +import { Category, Cloud, Dvr, Http, Lock, Settings, Source } from "@mui/icons-material"; +import { GitIcon } from "../../../icons/GitIcon"; +import { PropertiesTable } from "../../PropertiesTable"; +import { CodeViewer } from "../../CodeViewer"; +import { Box } from "@mui/material"; +import { CommandResultProps } from "../CommandResultView"; + +import * as yaml from 'js-yaml'; +import { SidePanelTab } from "../SidePanel"; + +interface VarsSourceHandler { + type: string + label: () => React.ReactNode + icon: () => React.ReactNode + sourceProps: () => {name: string, value: React.ReactNode}[] +} + +export class VarsSourceNodeData extends NodeData { + varsSource: VarsSource + + labelsYaml?: string + renderedVarsYaml: string + + constructor(props: CommandResultProps, id: string, varsSource: VarsSource) { + super(props, id, false, false); + this.varsSource = varsSource + + let labels = this.varsSource.clusterConfigMap?.labels + if (!labels) { + labels = this.varsSource.clusterSecret?.labels + } + if (labels) { + this.labelsYaml = yaml.dump(labels) + } + + this.renderedVarsYaml = yaml.dump(this.varsSource.renderedVars) + } + + getVarsSourceHandler(): VarsSourceHandler { + if (this.varsSource.values) { + return { + type: "values", + label: () => { + if (this.varsSource.renderedVars) { + return "values: " + Object.keys(this.varsSource.renderedVars).length + } else { + return "empty values" + } + }, + icon: () => , + sourceProps: () => [] + } + } else if (this.varsSource.file) { + return { + type: "file", + label: () => { + return this.varsSource.file + }, + icon: () => , + sourceProps: () => [ + {name: "File", value: this.varsSource.file} + ] + } + } else if (this.varsSource.git) { + return { + type: "git", + label: () => { + const s = this.varsSource.git!.url.split("/") + const name = s[s.length-1] + return <> + {name}
    + {this.varsSource.git!.path} + + }, + icon: () => , + sourceProps: () => { + const sourceProps = [] + sourceProps.push({name: "Url", value: this.varsSource.git!.url}) + sourceProps.push({name: "Path", value: this.varsSource.git!.path}) + let ref = "HEAD" + if (this.varsSource.git!.ref) { + ref = this.varsSource.git!.ref! + } + sourceProps.push({name: "Ref", value: ref}) + return sourceProps + } + } + } else if (this.varsSource.clusterConfigMap || this.varsSource.clusterSecret) { + const vs = (this.varsSource.clusterConfigMap ? this.varsSource.clusterConfigMap : this.varsSource.clusterSecret)! + const type = this.varsSource.clusterConfigMap ? "cm" : "secret" + const icon = this.varsSource.clusterConfigMap ? : + return { + type: type, + label: () => { + return this.varsSource.clusterConfigMap?.name! + }, + icon: () => icon, + sourceProps: () => { + const sourceProps = [] + sourceProps.push({name: "Name", value: vs.name}) + sourceProps.push({name: "Namespace", value: vs.namespace}) + sourceProps.push({name: "Key", value: vs.key}) + if (vs.labels) { + sourceProps.push({name: "Labels", value: }) + } + return sourceProps + } + } + } else if (this.varsSource.systemEnvVars) { + return { + type: "systemEnvVar", + label: () => { + return "systemEnvVars" + }, + icon: () => , + sourceProps: () => { + // TODO + return [] + } + } + } else if (this.varsSource.http) { + return { + type: "http", + label: () => { + return this.varsSource.http!.url + }, + icon: () => , + sourceProps: () => { + const sourceProps = [] + sourceProps.push({name: "Url", value: this.varsSource.http!.url}) + sourceProps.push({name: "Method", value: this.varsSource.http!.method || "GET"}) + if (this.varsSource.http!.jsonPath) { + sourceProps.push({name: "JsonPath", value: this.varsSource.http!.jsonPath}) + } + return sourceProps + } + } + } else if (this.varsSource.awsSecretsManager) { + return { + type: "awsSecretsManager", + label: () => { + return this.varsSource.awsSecretsManager!.secretName + }, + icon: () => , + sourceProps: () => { + const sourceProps = [] + sourceProps.push({name: "SecretName", value: this.varsSource.awsSecretsManager!.secretName}) + if (this.varsSource.awsSecretsManager!.region) { + sourceProps.push({ name: "Region", value: this.varsSource.awsSecretsManager!.region }) + } + if (this.varsSource.awsSecretsManager!.profile) { + sourceProps.push({ name: "Profile", value: this.varsSource.awsSecretsManager!.profile }) + } + return sourceProps + } + } + } else if (this.varsSource.vault) { + return { + type: "vault", + label: () => { + return <> + {this.varsSource.vault!.address}
    + {this.varsSource.vault!.path} + + }, + icon: () => , + sourceProps: () => { + const sourceProps = [] + sourceProps.push({name: "Address", value: this.varsSource.vault!.address}) + sourceProps.push({name: "Path", value: this.varsSource.vault!.path}) + return sourceProps + } + } + } else { + return { + type: "unknown", + label: () => { + return "values: " + Object.keys(this.varsSource.renderedVars).length + }, + icon: () => , + sourceProps: () => [] + } + } + } + + buildSidePanelTitle(): React.ReactNode { + return this.getVarsSourceHandler().label() + } + + buildIcon(): [React.ReactNode, string] { + const h = this.getVarsSourceHandler() + return [h.icon(), h.type] + } + + buildSidePanelTabs(): SidePanelTab[] { + const tabs = [ + {label: "Summary", content: this.buildSummaryPage()}, + {label: "Vars", content: this.buildVarsPage()} + ] + this.buildDiffAndHealthPages(tabs) + return tabs + } + + buildSummaryPage(): React.ReactNode { + const h = this.getVarsSourceHandler() + + const props = [ + {name: "Type", value: h.type}, + ...h.sourceProps(), + {name: "IgnoreMissing", value: (!!this.varsSource.ignoreMissing) + ""}, + {name: "NoOverride", value: (!!this.varsSource.noOverride) + ""}, + ] + + if (this.varsSource.when) { + props.push({name: "When", value: this.varsSource.when}) + } + + return <> + + + } + + buildVarsPage(): React.ReactNode { + return +
    +                
    +            
    +
    + } +} diff --git a/pkg/webui/ui/src/components/targets-view/CommandResultDetailsDrawer.tsx b/pkg/webui/ui/src/components/targets-view/CommandResultDetailsDrawer.tsx new file mode 100644 index 000000000..298fbe66a --- /dev/null +++ b/pkg/webui/ui/src/components/targets-view/CommandResultDetailsDrawer.tsx @@ -0,0 +1,54 @@ +import { CommandResultSummary } from "../../models"; +import { api, usePromise } from "../../api"; +import { NodeBuilder } from "../result-view/nodes/NodeBuilder"; +import React, { Suspense, useEffect, useState } from "react"; +import { NodeData } from "../result-view/nodes/NodeData"; +import { SidePanel } from "../result-view/SidePanel"; +import { Box, Drawer } from "@mui/material"; +import { Loading } from "../Loading"; + +async function doGetRootNode(rs: CommandResultSummary) { + const shortNames = api.getShortNames() + const r = api.getResult(rs.id) + const builder = new NodeBuilder({ + shortNames: await shortNames, + summary: rs, + commandResult: await r, + }) + const [node, nodeMap] = builder.buildRoot() + return node +} + +export const CommandResultDetailsDrawer = (props: { rs?: CommandResultSummary, onClose: () => void }) => { + const [prevId, setPrevId] = useState() + const [promise, setPromise] = useState>(new Promise(() => undefined)) + + useEffect(() => { + if (props.rs === undefined) { + return + } + if (props.rs.id === prevId) { + return + } + setPrevId(props.rs.id) + setPromise(doGetRootNode(props.rs)) + }, [props.rs]) + + const Content = () => { + const node = usePromise(promise) + return + } + + return props.onClose()} + > + + }> + + + + +} \ No newline at end of file diff --git a/pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx b/pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx new file mode 100644 index 000000000..8c23fb84e --- /dev/null +++ b/pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx @@ -0,0 +1,91 @@ +import { CommandResultSummary, ProjectSummary, TargetSummary } from "../../models"; +import { AccountTree, CleaningServices, CloudSync, Delete, Difference } from "@mui/icons-material"; +import React, { useEffect, useMemo, useState } from "react"; +import * as yaml from "js-yaml"; +import { CodeViewer } from "../CodeViewer"; +import Paper from "@mui/material/Paper"; +import { Box, IconButton, Tooltip, Typography } from "@mui/material"; +import { CommandResultStatusLine } from "../result-view/CommandResultStatusLine"; +import { useNavigate } from "react-router"; +import { formatDurationShort } from "../../utils/duration"; + +export const CommandResultItem = (props: { ps: ProjectSummary, ts: TargetSummary, rs: CommandResultSummary, onSelectCommandResult: (rs?: CommandResultSummary) => void }) => { + const calcAgo = () => { + const t1 = new Date(props.rs.commandInfo.startTime) + const t2 = new Date() + const d = t2.getTime() - t1.getTime() + return formatDurationShort(d) + } + + const navigate = useNavigate() + const [ago, setAgo] = useState(calcAgo()) + + let Icon = Difference + switch (props.rs.commandInfo?.command) { + case "delete": + Icon = Delete + break + case "deploy": + Icon = CloudSync + break + case "diff": + Icon = Difference + break + case "poke-images": + Icon = CloudSync + break + case "prune": + Icon = CleaningServices + break + } + + const cmdInfoYaml = useMemo(() => { + return yaml.dump(props.rs.commandInfo) + }, [props.rs]) + let iconTooltip = + + useEffect(() => { + const interval = setInterval(() => setAgo(calcAgo()), 5000); + return () => clearInterval(interval); + }, []) + + return props.onSelectCommandResult(props.rs)} + > + + + + + + + + {ago} + + + + + {props.rs.commandInfo?.command} + + + + + + + { + e.stopPropagation(); + navigate(`/results/${props.rs.id}`) + + }}> + + + + + + + + + + +} \ No newline at end of file diff --git a/pkg/webui/ui/src/components/targets-view/Projects.tsx b/pkg/webui/ui/src/components/targets-view/Projects.tsx new file mode 100644 index 000000000..390fb9ee1 --- /dev/null +++ b/pkg/webui/ui/src/components/targets-view/Projects.tsx @@ -0,0 +1,50 @@ +import { ProjectSummary } from "../../models"; +import { getLastPathElement } from "../../utils/misc"; +import Paper from "@mui/material/Paper"; +import { Box, Typography } from "@mui/material"; +import React from "react"; +import Tooltip from "@mui/material/Tooltip"; + +export const ProjectItem = (props: { ps: ProjectSummary }) => { + const name = getLastPathElement(props.ps.project.gitRepoKey) + const subDir = props.ps.project.subDir + + const projectInfo = + {props.ps.project.gitRepoKey}
    + {props.ps.project.subDir ? <> + SubDir: {props.ps.project.subDir}
    + : <>} +
    + + return + + + {name ? + + + {name} + + + : <>} + + + {subDir ? + {subDir} + : <>} + + + + + {/**/} + + + + + +} diff --git a/pkg/webui/ui/src/components/targets-view/TargetDetailsDrawer.tsx b/pkg/webui/ui/src/components/targets-view/TargetDetailsDrawer.tsx new file mode 100644 index 000000000..6c76ebbc4 --- /dev/null +++ b/pkg/webui/ui/src/components/targets-view/TargetDetailsDrawer.tsx @@ -0,0 +1,108 @@ +import { TargetSummary } from "../../models"; +import { Box, Drawer } from "@mui/material"; +import { SidePanel, SidePanelProvider, SidePanelTab } from "../result-view/SidePanel"; +import React from "react"; +import { PropertiesTable } from "../PropertiesTable"; +import { DiffStatus } from "../result-view/nodes/NodeData"; +import { ChangesTable } from "../result-view/ChangesTable"; +import { ErrorsTable } from "../ErrorsTable"; + +class MyProvider implements SidePanelProvider { + private ts?: TargetSummary; + private diffStatus: DiffStatus; + + constructor(ts?: TargetSummary) { + this.ts = ts + this.diffStatus = new DiffStatus() + + this.ts?.lastValidateResult?.drift?.forEach(co => { + this.diffStatus.addChangedObject(co) + }) + } + + buildSidePanelTabs(): SidePanelTab[] { + if (!this.ts) { + return [] + } + + const tabs = [ + {label: "Summary", content: this.buildSummaryTab()} + ] + + if (this.ts.target) + + if (this.diffStatus.changedObjects.length) { + tabs.push({ + label: "Drift", + content: + }) + } + if (this.ts.lastValidateResult?.errors?.length) { + tabs.push({ + label: "Errors", + content: + }) + } + if (this.ts.lastValidateResult?.warnings?.length) { + tabs.push({ + label: "Warnings", + content: + }) + } + + return tabs + } + + buildSummaryTab(): React.ReactNode { + const props = [ + {name: "Target Name", value: this.getTargetName()}, + {name: "Discriminator", value: this.ts?.target.discriminator}, + ] + + if (this.ts?.lastValidateResult) { + props.push({name: "Ready", value: this.ts.lastValidateResult.ready + ""}) + } + if (this.ts?.lastValidateResult?.errors?.length) { + props.push({name: "Errors", value: this.ts.lastValidateResult.errors.length + ""}) + } + if (this.ts?.lastValidateResult?.warnings?.length) { + props.push({name: "Warnings", value: this.ts.lastValidateResult.warnings.length + ""}) + } + if (this.ts?.lastValidateResult?.drift?.length) { + props.push({name: "Drifted Objects", value: this.ts.lastValidateResult.drift.length + ""}) + } + + return <> + + + } + + getTargetName() { + if (!this.ts) { + return "" + } + + let name = "" + if (this.ts.target.targetName) { + name = this.ts.target.targetName + } + return name + } + + buildSidePanelTitle(): React.ReactNode { + return this.getTargetName() + } +} + +export const TargetDetailsDrawer = (props: { ts?: TargetSummary, onClose: () => void }) => { + return props.onClose()} + > + + + + +} \ No newline at end of file diff --git a/pkg/webui/ui/src/components/targets-view/Targets.tsx b/pkg/webui/ui/src/components/targets-view/Targets.tsx new file mode 100644 index 000000000..b7b99849f --- /dev/null +++ b/pkg/webui/ui/src/components/targets-view/Targets.tsx @@ -0,0 +1,147 @@ +import { ProjectSummary, TargetSummary } from "../../models"; +import { ActionMenuItem, ActionsMenu } from "../ActionsMenu"; +import Paper from "@mui/material/Paper"; +import { Box, Typography } from "@mui/material"; +import React from "react"; +import { Jdenticon } from "../Jdenticon"; +import Tooltip from "@mui/material/Tooltip"; +import { Favorite, Fingerprint, HeartBroken, PublishedWithChanges, QuestionMark } from "@mui/icons-material"; +import { api } from "../../api"; + +const StatusIcon = (props: { ps: ProjectSummary, ts: TargetSummary }) => { + let icon: React.ReactElement + + if (props.ts.lastValidateResult === undefined) { + icon = + } else if (props.ts.lastValidateResult.ready && !props.ts.lastValidateResult.errors) { + if (props.ts.lastValidateResult.warnings?.length) { + icon = + } else if (props.ts.lastValidateResult.drift?.length) { + icon = + } else { + icon = + } + } else { + icon = + } + + const tooltip: string[] = [] + if (props.ts.lastValidateResult === undefined) { + tooltip.push("No validation result available.") + } else { + if (props.ts.lastValidateResult.ready && !props.ts.lastValidateResult.errors?.length) { + tooltip.push("Target is ready.") + } else { + tooltip.push("Target is not ready.") + } + if (props.ts.lastValidateResult.errors?.length) { + tooltip.push(`Target has ${props.ts.lastValidateResult.errors.length} validation errors.`) + } + if (props.ts.lastValidateResult.warnings?.length) { + tooltip.push(`Target has ${props.ts.lastValidateResult.warnings.length} validation warnings.`) + } + if (props.ts.lastValidateResult.drift?.length) { + tooltip.push(`Target has ${props.ts.lastValidateResult.drift.length} drifted objects.`) + } + } + + return {t})}> + {icon} + +} + +export const TargetItem = (props: { ps: ProjectSummary, ts: TargetSummary, onSelectTarget: (ts?: TargetSummary) => void }) => { + const actionMenuItems: ActionMenuItem[] = [] + + actionMenuItems.push({ + icon: , + text: "Validate now", + handler: () => { + api.validateNow(props.ps.project, props.ts.target) + } + }) + + const allContexts: string[] = [] + + const handleContext = (c?: string) => { + if (!c) { + return + } + if (allContexts.find(x => x === c)) { + return + } + allContexts.push(c) + } + + props.ts.commandResults?.forEach(rs => { + handleContext(rs.commandInfo.contextOverride) + handleContext(rs.target.context) + }) + + const contextTooltip = + All known contexts: + {allContexts.map((context, i) => ( + {context} + ))} + + + let targetName = props.ts.target.targetName + if (!targetName) { + targetName = "" + } + + return props.onSelectTarget(props.ts)} + > + + + + {targetName} + + {allContexts.length ? + + + {allContexts[0]} + + + + : <>} + + + + + +
    + +
    +
    + +
    + +
    +
    +
    + + + + +
    +
    +
    +
    +} diff --git a/pkg/webui/ui/src/components/targets-view/TargetsView.tsx b/pkg/webui/ui/src/components/targets-view/TargetsView.tsx new file mode 100644 index 000000000..581d8ce82 --- /dev/null +++ b/pkg/webui/ui/src/components/targets-view/TargetsView.tsx @@ -0,0 +1,96 @@ +import { useLoaderData } from "react-router-dom"; +import { CommandResultSummary, ProjectSummary, TargetSummary } from "../../models"; +import { Box, Typography } from "@mui/material"; +import React, { useEffect, useState } from "react"; +import { useAppOutletContext } from "../App"; +import { api } from "../../api"; +import { ProjectItem } from "./Projects"; +import { TargetItem } from "./Targets"; +import Divider from "@mui/material/Divider"; +import { CommandResultItem } from "./CommandResultItem"; +import { CommandResultDetailsDrawer } from "./CommandResultDetailsDrawer"; +import { TargetDetailsDrawer } from "./TargetDetailsDrawer"; + +const targetWidth = "220px" +const targetHeight = "110px" + +export async function projectsLoader() { + const projects = await api.listProjects() + return projects +} + +export const TargetsView = () => { + const context = useAppOutletContext() + const [selectedCommandResult, setSelectedCommandResult] = useState() + const [selectedTargetSummary, setSelectedTargetSummary] = useState() + + const projects = useLoaderData() as ProjectSummary[]; + + useEffect(() => { + context.setFilters(undefined) + }) + + const doSetSelectedCommandResult = (rs?: CommandResultSummary) => { + setSelectedCommandResult(rs) + setSelectedTargetSummary(undefined) + } + const doSetSelectedTargetSummary = (ts?: TargetSummary) => { + setSelectedCommandResult(undefined) + setSelectedTargetSummary(ts) + } + + return + setSelectedCommandResult(undefined)}/> + setSelectedTargetSummary(undefined)}/> + + + Projects + + + + Targets + + + + History + + + + {projects.map((ps, i) => { + return + + + + + + + + + + {ps.targets.map((ts, i) => { + return + doSetSelectedTargetSummary(ts)}/> + + })} + + + + + {ps.targets.map((ts, i) => { + return + {ts.commandResults?.map((rs, i) => { + return + doSetSelectedCommandResult(rs)}/> + + })} + + })} + + + + + })} + +} diff --git a/pkg/webui/ui/src/icons/GitIcon.tsx b/pkg/webui/ui/src/icons/GitIcon.tsx new file mode 100644 index 000000000..6948ffe5a --- /dev/null +++ b/pkg/webui/ui/src/icons/GitIcon.tsx @@ -0,0 +1,6 @@ +import { ReactComponent as GitIconSvg } from './git.svg'; +import React from "react"; + +export const GitIcon = () => { + return +} diff --git a/pkg/webui/ui/src/icons/KluctlLogo.tsx b/pkg/webui/ui/src/icons/KluctlLogo.tsx new file mode 100644 index 000000000..b89f71f95 --- /dev/null +++ b/pkg/webui/ui/src/icons/KluctlLogo.tsx @@ -0,0 +1,6 @@ +import { ReactComponent as KluctlLogoSvg } from './kluctl-logo.svg'; +import React from "react"; + +export const KluctlLogo = () => { + return +} diff --git a/pkg/webui/ui/src/icons/git.svg b/pkg/webui/ui/src/icons/git.svg new file mode 100644 index 000000000..6b6a26a2b --- /dev/null +++ b/pkg/webui/ui/src/icons/git.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/pkg/webui/ui/src/icons/kluctl-logo.svg b/pkg/webui/ui/src/icons/kluctl-logo.svg new file mode 100644 index 000000000..d61023e05 --- /dev/null +++ b/pkg/webui/ui/src/icons/kluctl-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/pkg/webui/ui/src/index.css b/pkg/webui/ui/src/index.css new file mode 100644 index 000000000..63775a2e6 --- /dev/null +++ b/pkg/webui/ui/src/index.css @@ -0,0 +1,25 @@ +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; +} + +* { + box-sizing: border-box; +} + +html, body, #root { + width: 100%; + height: 100%; + margin: 0; + padding: 0; + font-family: sans-serif; +} diff --git a/pkg/webui/ui/src/index.tsx b/pkg/webui/ui/src/index.tsx new file mode 100644 index 000000000..f7e132268 --- /dev/null +++ b/pkg/webui/ui/src/index.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { RouterProvider } from "react-router-dom"; + +import './index.css'; +import reportWebVitals from './reportWebVitals'; + +import '@fontsource/roboto/300.css'; +import '@fontsource/roboto/400.css'; +import '@fontsource/roboto/500.css'; +import '@fontsource/roboto/700.css'; + +import { Router } from "./components/Router"; + +const root = ReactDOM.createRoot( + document.getElementById('root') as HTMLElement +); +root.render( + + + +); + +// If you want to start measuring performance in your app, pass a function +// to log results (for example: reportWebVitals(console.log)) +// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals +reportWebVitals(); diff --git a/pkg/webui/ui/src/loadscript.ts b/pkg/webui/ui/src/loadscript.ts new file mode 100644 index 000000000..e8db35596 --- /dev/null +++ b/pkg/webui/ui/src/loadscript.ts @@ -0,0 +1,35 @@ +const loadedScripts = new Map() + +export const loadScript = (fileUrl: string, async = true, type = "text/javascript") => { + if (loadedScripts.has(fileUrl)) { + return loadedScripts.get(fileUrl) + } + + const p = new Promise((resolve, reject) => { + try { + const scriptEle = document.createElement("script"); + scriptEle.type = type; + scriptEle.async = async; + scriptEle.src = fileUrl; + + scriptEle.addEventListener("load", (ev) => { + resolve({ status: true }); + }); + + scriptEle.addEventListener("error", (ev) => { + reject({ + status: false, + message: `Failed to load the script ${FILE_URL}` + }); + }); + + document.body.appendChild(scriptEle); + } catch (error) { + reject(error); + } + }); + + loadedScripts.set(fileUrl, p) + + return p +}; diff --git a/pkg/webui/ui/src/logo.svg b/pkg/webui/ui/src/logo.svg new file mode 100644 index 000000000..9dfc1c058 --- /dev/null +++ b/pkg/webui/ui/src/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/pkg/webui/ui/src/models.ts b/pkg/webui/ui/src/models.ts new file mode 100644 index 000000000..2aa413ce5 --- /dev/null +++ b/pkg/webui/ui/src/models.ts @@ -0,0 +1,900 @@ +/* Do not change, this code is generated from Golang structs */ + + +export class DeploymentError { + ref: ObjectRef; + message: string; + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.ref = this.convertValues(source["ref"], ObjectRef); + this.message = source["message"]; + } + + convertValues(a: any, classs: any, asMap: boolean = false): any { + if (!a) { + return a; + } + if (a.slice) { + return (a as any[]).map(elem => this.convertValues(elem, classs)); + } else if ("object" === typeof a) { + if (asMap) { + for (const key of Object.keys(a)) { + a[key] = new classs(a[key]); + } + return a; + } + return new classs(a); + } + return a; + } +} +export class Change { + type: string; + jsonPath: string; + oldValue?: any; + newValue?: any; + unifiedDiff?: string; + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.type = source["type"]; + this.jsonPath = source["jsonPath"]; + this.oldValue = source["oldValue"]; + this.newValue = source["newValue"]; + this.unifiedDiff = source["unifiedDiff"]; + } +} +export class ResultObject { + ref: ObjectRef; + changes?: Change[]; + new?: boolean; + orphan?: boolean; + deleted?: boolean; + hook?: boolean; + rendered?: any; + remote?: any; + applied?: any; + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.ref = this.convertValues(source["ref"], ObjectRef); + this.changes = this.convertValues(source["changes"], Change); + this.new = source["new"]; + this.orphan = source["orphan"]; + this.deleted = source["deleted"]; + this.hook = source["hook"]; + this.rendered = source["rendered"]; + this.remote = source["remote"]; + this.applied = source["applied"]; + } + + convertValues(a: any, classs: any, asMap: boolean = false): any { + if (!a) { + return a; + } + if (a.slice) { + return (a as any[]).map(elem => this.convertValues(elem, classs)); + } else if ("object" === typeof a) { + if (asMap) { + for (const key of Object.keys(a)) { + a[key] = new classs(a[key]); + } + return a; + } + return new classs(a); + } + return a; + } +} +export class IgnoreForDiffItemConfig { + fieldPath?: string[]; + fieldPathRegex?: string[]; + group?: string; + kind?: string; + name?: string; + namespace?: string; + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.fieldPath = source["fieldPath"]; + this.fieldPathRegex = source["fieldPathRegex"]; + this.group = source["group"]; + this.kind = source["kind"]; + this.name = source["name"]; + this.namespace = source["namespace"]; + } +} +export class HelmChartConfig { + repo?: string; + path?: string; + credentialsId?: string; + chartName?: string; + chartVersion?: string; + updateConstraints?: string; + releaseName: string; + namespace?: string; + output?: string; + skipCRDs?: boolean; + skipUpdate?: boolean; + skipPrePull?: boolean; + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.repo = source["repo"]; + this.path = source["path"]; + this.credentialsId = source["credentialsId"]; + this.chartName = source["chartName"]; + this.chartVersion = source["chartVersion"]; + this.updateConstraints = source["updateConstraints"]; + this.releaseName = source["releaseName"]; + this.namespace = source["namespace"]; + this.output = source["output"]; + this.skipCRDs = source["skipCRDs"]; + this.skipUpdate = source["skipUpdate"]; + this.skipPrePull = source["skipPrePull"]; + } +} +export class DeleteObjectItemConfig { + group?: string; + kind?: string; + name: string; + namespace?: string; + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.group = source["group"]; + this.kind = source["kind"]; + this.name = source["name"]; + this.namespace = source["namespace"]; + } +} +export class GitProject { + url: string; + ref?: string; + subDir?: string; + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.url = source["url"]; + this.ref = source["ref"]; + this.subDir = source["subDir"]; + } +} +export class DeploymentItemConfig { + path?: string; + include?: string; + git?: GitProject; + tags?: string[]; + barrier?: boolean; + message?: string; + waitReadiness?: boolean; + vars?: VarsSource[]; + skipDeleteIfTags?: boolean; + onlyRender?: boolean; + alwaysDeploy?: boolean; + deleteObjects?: DeleteObjectItemConfig[]; + when?: string; + renderedHelmChartConfig?: HelmChartConfig; + renderedObjects?: ObjectRef[]; + renderedInclude?: DeploymentProjectConfig; + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.path = source["path"]; + this.include = source["include"]; + this.git = this.convertValues(source["git"], GitProject); + this.tags = source["tags"]; + this.barrier = source["barrier"]; + this.message = source["message"]; + this.waitReadiness = source["waitReadiness"]; + this.vars = this.convertValues(source["vars"], VarsSource); + this.skipDeleteIfTags = source["skipDeleteIfTags"]; + this.onlyRender = source["onlyRender"]; + this.alwaysDeploy = source["alwaysDeploy"]; + this.deleteObjects = this.convertValues(source["deleteObjects"], DeleteObjectItemConfig); + this.when = source["when"]; + this.renderedHelmChartConfig = this.convertValues(source["renderedHelmChartConfig"], HelmChartConfig); + this.renderedObjects = this.convertValues(source["renderedObjects"], ObjectRef); + this.renderedInclude = this.convertValues(source["renderedInclude"], DeploymentProjectConfig); + } + + convertValues(a: any, classs: any, asMap: boolean = false): any { + if (!a) { + return a; + } + if (a.slice) { + return (a as any[]).map(elem => this.convertValues(elem, classs)); + } else if ("object" === typeof a) { + if (asMap) { + for (const key of Object.keys(a)) { + a[key] = new classs(a[key]); + } + return a; + } + return new classs(a); + } + return a; + } +} +export class SealedSecretsConfig { + outputPattern?: string; + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.outputPattern = source["outputPattern"]; + } +} +export class VarsSourceVault { + address: string; + path: string; + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.address = source["address"]; + this.path = source["path"]; + } +} +export class VarsSourceAwsSecretsManager { + secretName: string; + region?: string; + profile?: string; + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.secretName = source["secretName"]; + this.region = source["region"]; + this.profile = source["profile"]; + } +} +export class VarsSourceHttp { + url?: string; + method?: string; + body?: string; + headers?: {[key: string]: string}; + jsonPath?: string; + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.url = source["url"]; + this.method = source["method"]; + this.body = source["body"]; + this.headers = source["headers"]; + this.jsonPath = source["jsonPath"]; + } +} +export class VarsSourceClusterConfigMapOrSecret { + name?: string; + labels?: {[key: string]: string}; + namespace: string; + key: string; + targetPath?: string; + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.name = source["name"]; + this.labels = source["labels"]; + this.namespace = source["namespace"]; + this.key = source["key"]; + this.targetPath = source["targetPath"]; + } +} +export class VarsSourceGit { + url: string; + ref?: string; + path: string; + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.url = source["url"]; + this.ref = source["ref"]; + this.path = source["path"]; + } +} +export class VarsSource { + ignoreMissing?: boolean; + noOverride?: boolean; + values?: any; + file?: string; + git?: VarsSourceGit; + clusterConfigMap?: VarsSourceClusterConfigMapOrSecret; + clusterSecret?: VarsSourceClusterConfigMapOrSecret; + systemEnvVars?: any; + http?: VarsSourceHttp; + awsSecretsManager?: VarsSourceAwsSecretsManager; + vault?: VarsSourceVault; + when?: string; + renderedVars?: any; + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.ignoreMissing = source["ignoreMissing"]; + this.noOverride = source["noOverride"]; + this.values = source["values"]; + this.file = source["file"]; + this.git = this.convertValues(source["git"], VarsSourceGit); + this.clusterConfigMap = this.convertValues(source["clusterConfigMap"], VarsSourceClusterConfigMapOrSecret); + this.clusterSecret = this.convertValues(source["clusterSecret"], VarsSourceClusterConfigMapOrSecret); + this.systemEnvVars = source["systemEnvVars"]; + this.http = this.convertValues(source["http"], VarsSourceHttp); + this.awsSecretsManager = this.convertValues(source["awsSecretsManager"], VarsSourceAwsSecretsManager); + this.vault = this.convertValues(source["vault"], VarsSourceVault); + this.when = source["when"]; + this.renderedVars = source["renderedVars"]; + } + + convertValues(a: any, classs: any, asMap: boolean = false): any { + if (!a) { + return a; + } + if (a.slice) { + return (a as any[]).map(elem => this.convertValues(elem, classs)); + } else if ("object" === typeof a) { + if (asMap) { + for (const key of Object.keys(a)) { + a[key] = new classs(a[key]); + } + return a; + } + return new classs(a); + } + return a; + } +} +export class DeploymentProjectConfig { + vars?: VarsSource[]; + sealedSecrets?: SealedSecretsConfig; + when?: string; + deployments?: DeploymentItemConfig[]; + commonLabels?: {[key: string]: string}; + commonAnnotations?: {[key: string]: string}; + overrideNamespace?: string; + tags?: string[]; + ignoreForDiff?: IgnoreForDiffItemConfig[]; + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.vars = this.convertValues(source["vars"], VarsSource); + this.sealedSecrets = this.convertValues(source["sealedSecrets"], SealedSecretsConfig); + this.when = source["when"]; + this.deployments = this.convertValues(source["deployments"], DeploymentItemConfig); + this.commonLabels = source["commonLabels"]; + this.commonAnnotations = source["commonAnnotations"]; + this.overrideNamespace = source["overrideNamespace"]; + this.tags = source["tags"]; + this.ignoreForDiff = this.convertValues(source["ignoreForDiff"], IgnoreForDiffItemConfig); + } + + convertValues(a: any, classs: any, asMap: boolean = false): any { + if (!a) { + return a; + } + if (a.slice) { + return (a as any[]).map(elem => this.convertValues(elem, classs)); + } else if ("object" === typeof a) { + if (asMap) { + for (const key of Object.keys(a)) { + a[key] = new classs(a[key]); + } + return a; + } + return new classs(a); + } + return a; + } +} +export class ClusterInfo { + clusterId: string; + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.clusterId = source["clusterId"]; + } +} +export class GitInfo { + url?: string; + ref: string; + subDir: string; + commit: string; + dirty: boolean; + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.url = source["url"]; + this.ref = source["ref"]; + this.subDir = source["subDir"]; + this.commit = source["commit"]; + this.dirty = source["dirty"]; + } +} +export class KluctlDeploymentInfo { + name: string; + namespace: string; + gitUrl: string; + gitRef: string; + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.name = source["name"]; + this.namespace = source["namespace"]; + this.gitUrl = source["gitUrl"]; + this.gitRef = source["gitRef"]; + } +} +export class CommandInfo { + initiator: string; + startTime: string; + endTime: string; + kluctlDeployment?: KluctlDeploymentInfo; + command?: string; + target?: string; + targetNameOverride?: string; + contextOverride?: string; + args?: any; + images?: FixedImage[]; + dryRun?: boolean; + noWait?: boolean; + forceApply?: boolean; + replaceOnError?: boolean; + forceReplaceOnError?: boolean; + abortOnError?: boolean; + includeTags?: string[]; + excludeTags?: string[]; + includeDeploymentDirs?: string[]; + excludeDeploymentDirs?: string[]; + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.initiator = source["initiator"]; + this.startTime = source["startTime"]; + this.endTime = source["endTime"]; + this.kluctlDeployment = this.convertValues(source["kluctlDeployment"], KluctlDeploymentInfo); + this.command = source["command"]; + this.target = source["target"]; + this.targetNameOverride = source["targetNameOverride"]; + this.contextOverride = source["contextOverride"]; + this.args = source["args"]; + this.images = this.convertValues(source["images"], FixedImage); + this.dryRun = source["dryRun"]; + this.noWait = source["noWait"]; + this.forceApply = source["forceApply"]; + this.replaceOnError = source["replaceOnError"]; + this.forceReplaceOnError = source["forceReplaceOnError"]; + this.abortOnError = source["abortOnError"]; + this.includeTags = source["includeTags"]; + this.excludeTags = source["excludeTags"]; + this.includeDeploymentDirs = source["includeDeploymentDirs"]; + this.excludeDeploymentDirs = source["excludeDeploymentDirs"]; + } + + convertValues(a: any, classs: any, asMap: boolean = false): any { + if (!a) { + return a; + } + if (a.slice) { + return (a as any[]).map(elem => this.convertValues(elem, classs)); + } else if ("object" === typeof a) { + if (asMap) { + for (const key of Object.keys(a)) { + a[key] = new classs(a[key]); + } + return a; + } + return new classs(a); + } + return a; + } +} +export class ObjectRef { + group?: string; + version?: string; + kind: string; + name: string; + namespace?: string; + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.group = source["group"]; + this.version = source["version"]; + this.kind = source["kind"]; + this.name = source["name"]; + this.namespace = source["namespace"]; + } +} +export class FixedImage { + image: string; + resultImage: string; + deployedImage?: string; + namespace?: string; + object?: ObjectRef; + deployment?: string; + container?: string; + deployTags?: string[]; + deploymentDir?: string; + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.image = source["image"]; + this.resultImage = source["resultImage"]; + this.deployedImage = source["deployedImage"]; + this.namespace = source["namespace"]; + this.object = this.convertValues(source["object"], ObjectRef); + this.deployment = source["deployment"]; + this.container = source["container"]; + this.deployTags = source["deployTags"]; + this.deploymentDir = source["deploymentDir"]; + } + + convertValues(a: any, classs: any, asMap: boolean = false): any { + if (!a) { + return a; + } + if (a.slice) { + return (a as any[]).map(elem => this.convertValues(elem, classs)); + } else if ("object" === typeof a) { + if (asMap) { + for (const key of Object.keys(a)) { + a[key] = new classs(a[key]); + } + return a; + } + return new classs(a); + } + return a; + } +} +export class SealingConfig { + args?: any; + secretSets?: string[]; + certFile?: string; + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.args = source["args"]; + this.secretSets = source["secretSets"]; + this.certFile = source["certFile"]; + } +} +export class Target { + name: string; + context?: string; + args?: any; + sealingConfig?: SealingConfig; + images?: FixedImage[]; + discriminator?: string; + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.name = source["name"]; + this.context = source["context"]; + this.args = source["args"]; + this.sealingConfig = this.convertValues(source["sealingConfig"], SealingConfig); + this.images = this.convertValues(source["images"], FixedImage); + this.discriminator = source["discriminator"]; + } + + convertValues(a: any, classs: any, asMap: boolean = false): any { + if (!a) { + return a; + } + if (a.slice) { + return (a as any[]).map(elem => this.convertValues(elem, classs)); + } else if ("object" === typeof a) { + if (asMap) { + for (const key of Object.keys(a)) { + a[key] = new classs(a[key]); + } + return a; + } + return new classs(a); + } + return a; + } +} +export class TargetKey { + targetName?: string; + clusterId: string; + discriminator?: string; + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.targetName = source["targetName"]; + this.clusterId = source["clusterId"]; + this.discriminator = source["discriminator"]; + } +} +export class ProjectKey { + gitRepoKey?: string; + subDir?: string; + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.gitRepoKey = source["gitRepoKey"]; + this.subDir = source["subDir"]; + } +} +export class CommandResult { + id: string; + projectKey: ProjectKey; + targetKey: TargetKey; + target: Target; + command?: CommandInfo; + gitInfo?: GitInfo; + clusterInfo: ClusterInfo; + deployment?: DeploymentProjectConfig; + objects?: ResultObject[]; + errors?: DeploymentError[]; + warnings?: DeploymentError[]; + seenImages?: FixedImage[]; + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.id = source["id"]; + this.projectKey = this.convertValues(source["projectKey"], ProjectKey); + this.targetKey = this.convertValues(source["targetKey"], TargetKey); + this.target = this.convertValues(source["target"], Target); + this.command = this.convertValues(source["command"], CommandInfo); + this.gitInfo = this.convertValues(source["gitInfo"], GitInfo); + this.clusterInfo = this.convertValues(source["clusterInfo"], ClusterInfo); + this.deployment = this.convertValues(source["deployment"], DeploymentProjectConfig); + this.objects = this.convertValues(source["objects"], ResultObject); + this.errors = this.convertValues(source["errors"], DeploymentError); + this.warnings = this.convertValues(source["warnings"], DeploymentError); + this.seenImages = this.convertValues(source["seenImages"], FixedImage); + } + + convertValues(a: any, classs: any, asMap: boolean = false): any { + if (!a) { + return a; + } + if (a.slice) { + return (a as any[]).map(elem => this.convertValues(elem, classs)); + } else if ("object" === typeof a) { + if (asMap) { + for (const key of Object.keys(a)) { + a[key] = new classs(a[key]); + } + return a; + } + return new classs(a); + } + return a; + } +} +export class CommandResultSummary { + id: string; + projectKey: ProjectKey; + targetKey: TargetKey; + target: Target; + commandInfo: CommandInfo; + gitInfo?: GitInfo; + clusterInfo?: ClusterInfo; + renderedObjects: number; + remoteObjects: number; + appliedObjects: number; + appliedHookObjects: number; + newObjects: number; + changedObjects: number; + orphanObjects: number; + deletedObjects: number; + errors: number; + warnings: number; + totalChanges: number; + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.id = source["id"]; + this.projectKey = this.convertValues(source["projectKey"], ProjectKey); + this.targetKey = this.convertValues(source["targetKey"], TargetKey); + this.target = this.convertValues(source["target"], Target); + this.commandInfo = this.convertValues(source["commandInfo"], CommandInfo); + this.gitInfo = this.convertValues(source["gitInfo"], GitInfo); + this.clusterInfo = this.convertValues(source["clusterInfo"], ClusterInfo); + this.renderedObjects = source["renderedObjects"]; + this.remoteObjects = source["remoteObjects"]; + this.appliedObjects = source["appliedObjects"]; + this.appliedHookObjects = source["appliedHookObjects"]; + this.newObjects = source["newObjects"]; + this.changedObjects = source["changedObjects"]; + this.orphanObjects = source["orphanObjects"]; + this.deletedObjects = source["deletedObjects"]; + this.errors = source["errors"]; + this.warnings = source["warnings"]; + this.totalChanges = source["totalChanges"]; + } + + convertValues(a: any, classs: any, asMap: boolean = false): any { + if (!a) { + return a; + } + if (a.slice) { + return (a as any[]).map(elem => this.convertValues(elem, classs)); + } else if ("object" === typeof a) { + if (asMap) { + for (const key of Object.keys(a)) { + a[key] = new classs(a[key]); + } + return a; + } + return new classs(a); + } + return a; + } +} +export class ChangedObject { + ref: ObjectRef; + changes?: Change[]; + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.ref = this.convertValues(source["ref"], ObjectRef); + this.changes = this.convertValues(source["changes"], Change); + } + + convertValues(a: any, classs: any, asMap: boolean = false): any { + if (!a) { + return a; + } + if (a.slice) { + return (a as any[]).map(elem => this.convertValues(elem, classs)); + } else if ("object" === typeof a) { + if (asMap) { + for (const key of Object.keys(a)) { + a[key] = new classs(a[key]); + } + return a; + } + return new classs(a); + } + return a; + } +} +export class ValidateResultEntry { + ref: ObjectRef; + annotation: string; + message: string; + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.ref = this.convertValues(source["ref"], ObjectRef); + this.annotation = source["annotation"]; + this.message = source["message"]; + } + + convertValues(a: any, classs: any, asMap: boolean = false): any { + if (!a) { + return a; + } + if (a.slice) { + return (a as any[]).map(elem => this.convertValues(elem, classs)); + } else if ("object" === typeof a) { + if (asMap) { + for (const key of Object.keys(a)) { + a[key] = new classs(a[key]); + } + return a; + } + return new classs(a); + } + return a; + } +} +export class ValidateResult { + id: string; + startTime: string; + endTime: string; + ready: boolean; + warnings?: DeploymentError[]; + errors?: DeploymentError[]; + results?: ValidateResultEntry[]; + drift?: ChangedObject[]; + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.id = source["id"]; + this.startTime = source["startTime"]; + this.endTime = source["endTime"]; + this.ready = source["ready"]; + this.warnings = this.convertValues(source["warnings"], DeploymentError); + this.errors = this.convertValues(source["errors"], DeploymentError); + this.results = this.convertValues(source["results"], ValidateResultEntry); + this.drift = this.convertValues(source["drift"], ChangedObject); + } + + convertValues(a: any, classs: any, asMap: boolean = false): any { + if (!a) { + return a; + } + if (a.slice) { + return (a as any[]).map(elem => this.convertValues(elem, classs)); + } else if ("object" === typeof a) { + if (asMap) { + for (const key of Object.keys(a)) { + a[key] = new classs(a[key]); + } + return a; + } + return new classs(a); + } + return a; + } +} +export class TargetSummary { + target: TargetKey; + lastValidateResult?: ValidateResult; + commandResults?: CommandResultSummary[]; + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.target = this.convertValues(source["target"], TargetKey); + this.lastValidateResult = this.convertValues(source["lastValidateResult"], ValidateResult); + this.commandResults = this.convertValues(source["commandResults"], CommandResultSummary); + } + + convertValues(a: any, classs: any, asMap: boolean = false): any { + if (!a) { + return a; + } + if (a.slice) { + return (a as any[]).map(elem => this.convertValues(elem, classs)); + } else if ("object" === typeof a) { + if (asMap) { + for (const key of Object.keys(a)) { + a[key] = new classs(a[key]); + } + return a; + } + return new classs(a); + } + return a; + } +} +export class ProjectSummary { + project: ProjectKey; + targets: TargetSummary[]; + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.project = this.convertValues(source["project"], ProjectKey); + this.targets = this.convertValues(source["targets"], TargetSummary); + } + + convertValues(a: any, classs: any, asMap: boolean = false): any { + if (!a) { + return a; + } + if (a.slice) { + return (a as any[]).map(elem => this.convertValues(elem, classs)); + } else if ("object" === typeof a) { + if (asMap) { + for (const key of Object.keys(a)) { + a[key] = new classs(a[key]); + } + return a; + } + return new classs(a); + } + return a; + } +} + +export class ShortName { + group?: string; + kind: string; + shortName: string; + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.group = source["group"]; + this.kind = source["kind"]; + this.shortName = source["shortName"]; + } +} +export class UnstructuredObject { + + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + + } +} \ No newline at end of file diff --git a/pkg/webui/ui/src/react-app-env.d.ts b/pkg/webui/ui/src/react-app-env.d.ts new file mode 100644 index 000000000..6431bc5fc --- /dev/null +++ b/pkg/webui/ui/src/react-app-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/pkg/webui/ui/src/reportWebVitals.ts b/pkg/webui/ui/src/reportWebVitals.ts new file mode 100644 index 000000000..ffeb31f00 --- /dev/null +++ b/pkg/webui/ui/src/reportWebVitals.ts @@ -0,0 +1,15 @@ +import { ReportHandler } from 'web-vitals'; + +const reportWebVitals = (onPerfEntry?: ReportHandler) => { + if (onPerfEntry && onPerfEntry instanceof Function) { + import('web-vitals').then(({getCLS, getFID, getFCP, getLCP, getTTFB}) => { + getCLS(onPerfEntry); + getFID(onPerfEntry); + getFCP(onPerfEntry); + getLCP(onPerfEntry); + getTTFB(onPerfEntry); + }); + } +}; + +export default reportWebVitals; diff --git a/pkg/webui/ui/src/setupTests.ts b/pkg/webui/ui/src/setupTests.ts new file mode 100644 index 000000000..b28b91057 --- /dev/null +++ b/pkg/webui/ui/src/setupTests.ts @@ -0,0 +1,5 @@ +// jest-dom adds custom jest matchers for asserting on DOM nodes. +// allows you to do things like: +// expect(element).toHaveTextContent(/react/i) +// learn more: https://github.com/testing-library/jest-dom +import '@testing-library/jest-dom'; \ No newline at end of file diff --git a/pkg/webui/ui/src/staticbuild.d.ts b/pkg/webui/ui/src/staticbuild.d.ts new file mode 100644 index 000000000..511b2c96a --- /dev/null +++ b/pkg/webui/ui/src/staticbuild.d.ts @@ -0,0 +1,7 @@ +declare var isStaticBuild: bool; + +declare const staticResults: Map; + +declare const staticShortNames: any[]; +declare const staticProjects: any[]; +declare const staticSummaries: any[]; diff --git a/pkg/webui/ui/src/utils/duration.ts b/pkg/webui/ui/src/utils/duration.ts new file mode 100644 index 000000000..5dd95c4dc --- /dev/null +++ b/pkg/webui/ui/src/utils/duration.ts @@ -0,0 +1,30 @@ +export function formatDuration(ms: number, withMs?: boolean) { + if (ms < 0) ms = -ms; + const time = { + day: Math.floor(ms / 86400000), + hour: Math.floor(ms / 3600000) % 24, + minute: Math.floor(ms / 60000) % 60, + second: Math.floor(ms / 1000) % 60, + millisecond: withMs ? Math.floor(ms) % 1000 : 0 + }; + return Object.entries(time) + .filter(val => val[1] !== 0) + .map(val => val[1] + ' ' + (val[1] !== 1 ? val[0] + 's' : val[0])) + .join(', '); +} + +export function formatDurationShort(ms: number) { + if (ms < 0) ms = -ms; + const time = { + d: Math.floor(ms / 86400000), + h: Math.floor(ms / 3600000) % 24, + m: Math.floor(ms / 60000) % 60, + s: Math.floor(ms / 1000) % 60, + ms: Math.floor(ms) % 1000 + }; + const f = Object.entries(time).find(val => val[1] > 0) + if (f === undefined) { + return "0s" + } + return f[1] + f[0] +} \ No newline at end of file diff --git a/pkg/webui/ui/src/utils/misc.ts b/pkg/webui/ui/src/utils/misc.ts new file mode 100644 index 000000000..5543c1436 --- /dev/null +++ b/pkg/webui/ui/src/utils/misc.ts @@ -0,0 +1,14 @@ +export function getLastPathElement(url?: string): string | undefined { + if (url === undefined) { + return undefined + } + if (!url) { + return "" + } + const s= url.split("/") + return s[s.length - 1] +} + +export function sleep(ms: number) { + return new Promise(resolve => setTimeout(resolve, ms)); +} diff --git a/pkg/webui/ui/tsconfig.json b/pkg/webui/ui/tsconfig.json new file mode 100644 index 000000000..a273b0cfc --- /dev/null +++ b/pkg/webui/ui/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": [ + "src" + ] +} diff --git a/pkg/webui/validator.go b/pkg/webui/validator.go new file mode 100644 index 000000000..78ba366bf --- /dev/null +++ b/pkg/webui/validator.go @@ -0,0 +1,223 @@ +package webui + +import ( + "context" + "github.com/kluctl/kluctl/v2/pkg/deployment/commands" + "github.com/kluctl/kluctl/v2/pkg/results" + "github.com/kluctl/kluctl/v2/pkg/status" + "github.com/kluctl/kluctl/v2/pkg/types/result" + "sync" + "time" +) + +const shortValidationInterval = time.Second * 15 +const longValidationInterval = time.Minute * 5 + +type validatorManager struct { + ctx context.Context + + store results.ResultStore + cam *clusterAccessorManager + + validators map[projectTargetKey]*validatorEntry + mutex sync.Mutex +} + +type projectTargetKey struct { + Project result.ProjectKey `json:"project"` + Target result.TargetKey `json:"target"` +} + +type validatorEntry struct { + vm *validatorManager + key projectTargetKey + ch chan bool + validateResult *result.ValidateResult + err error + mutex sync.Mutex +} + +func newValidatorManager(ctx context.Context, store results.ResultStore, cam *clusterAccessorManager) *validatorManager { + return &validatorManager{ + ctx: ctx, + store: store, + cam: cam, + validators: map[projectTargetKey]*validatorEntry{}, + } +} + +func (vm *validatorManager) start() { + runOnce := func() { + summaries, err := vm.store.ListCommandResultSummaries(results.ListCommandResultSummariesOptions{}) + if err != nil { + return + } + + projects := result.BuildProjectSummaries(summaries) + + found := map[projectTargetKey]bool{} + for _, project := range projects { + for _, target := range project.Targets { + k := projectTargetKey{ + Project: project.Project, + Target: target.Target, + } + + vm.mutex.Lock() + _, ok := vm.validators[k] + if !ok { + v := &validatorEntry{ + vm: vm, + key: k, + ch: make(chan bool), + } + vm.validators[k] = v + go v.run() + } + vm.mutex.Unlock() + + found[k] = true + } + } + + vm.mutex.Lock() + for k, v := range vm.validators { + if _, ok := found[k]; !ok { + delete(vm.validators, k) + close(v.ch) + } + } + vm.mutex.Unlock() + } + + go func() { + for { + runOnce() + time.Sleep(5 * time.Second) + } + }() +} + +func (vm *validatorManager) getValidateResult(key projectTargetKey) (*result.ValidateResult, error) { + vm.mutex.Lock() + defer vm.mutex.Unlock() + v := vm.validators[key] + if v == nil { + return nil, nil + } + return v.validateResult, v.err +} + +func (vm *validatorManager) validateNow(key projectTargetKey) bool { + vm.mutex.Lock() + defer vm.mutex.Unlock() + v := vm.validators[key] + if v == nil { + return false + } + v.ch <- true + return true +} + +func (v *validatorEntry) run() { + status.Info(v.vm.ctx, "Started validator for: %v", v.key) + defer status.Info(v.vm.ctx, "Stopped validator for: %v", v.key) + + for { + summaries, err := v.vm.store.ListCommandResultSummaries(results.ListCommandResultSummariesOptions{ + ProjectFilter: &v.key.Project, + }) + if err != nil { + return + } + projects := result.BuildProjectSummaries(summaries) + + longWait := false + outer: + for _, p := range projects { + if p.Project == v.key.Project { + for _, t := range p.Targets { + if t.Target == v.key.Target { + longWait = v.runOnce(t) + break outer + } + } + } + } + + waitTime := shortValidationInterval + if longWait { + waitTime = longValidationInterval + } + + select { + case _, ok := <-v.ch: + if !ok { + break + } + continue + case <-time.After(waitTime): + } + } +} + +func (v *validatorEntry) findFullProjectResult(summaries []result.CommandResultSummary) *result.CommandResultSummary { + for _, summary := range summaries { + if len(summary.Command.IncludeTags) != 0 || len(summary.Command.ExcludeTags) != 0 { + continue + } + if summary.Command.Command == "deploy" || summary.Command.Command == "diff" { + return &summary + } + } + return nil +} + +func (v *validatorEntry) runOnce(target *result.TargetSummary) bool { + s := status.Start(v.vm.ctx, "Validating: %v", v.key) + defer s.Failed() + + summary := v.findFullProjectResult(target.CommandResults) + if summary == nil { + s.FailedWithMessage("No result summaries for %v", v.key) + return false + } + + ca := v.vm.cam.getForClusterId(summary.ClusterInfo.ClusterId) + if ca == nil { + s.FailedWithMessage("No cluster accessor for %v", v.key) + return false + } + k := ca.getK() + if k == nil { + return false + } + + cr, err := v.vm.store.GetCommandResult(results.GetCommandResultOptions{ + Id: summary.Id, + Reduced: false, + }) + if err != nil { + s.FailedWithMessage("Failed to get command result for %v: %v", v.key, err) + return false + } + + discriminator := summary.Target.Discriminator + + cmd := commands.NewValidateCommand(v.vm.ctx, discriminator, nil, cr) + vr, err := cmd.Run(v.vm.ctx, k) + + if err != nil { + s.UpdateAndInfoFallback("Finished validation with error: %v", v.key) + } else { + s.UpdateAndInfoFallback("Finished validation: %v", v.key) + } + s.Success() + + v.mutex.Lock() + defer v.mutex.Unlock() + v.validateResult = vr + v.err = err + + return true +} From 5970e0cacb2499e5c89d355b84a234ef31c6116c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 30 May 2023 13:31:25 +0200 Subject: [PATCH 1775/2916] fix: Server ui from / --- pkg/webui/server.go | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/pkg/webui/server.go b/pkg/webui/server.go index 746f3b0da..8d854718b 100644 --- a/pkg/webui/server.go +++ b/pkg/webui/server.go @@ -10,6 +10,7 @@ import ( "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/types/result" "github.com/kluctl/kluctl/v2/pkg/utils/uo" + "io/fs" "k8s.io/client-go/rest" "net" "net/http" @@ -52,7 +53,30 @@ func (s *CommandResultsServer) Run(port int) error { router := gin.Default() - router.StaticFS("/webui", http.FS(uiFS)) + dis, err := fs.ReadDir(uiFS, ".") + if err != nil { + return err + } + + for _, di := range dis { + if di.IsDir() { + x, err := fs.Sub(uiFS, di.Name()) + if err != nil { + return err + } + router.StaticFS("/"+di.Name(), http.FS(x)) + } else { + router.StaticFileFS("/"+di.Name(), di.Name(), http.FS(uiFS)) + } + } + router.GET("/", func(c *gin.Context) { + b, err := fs.ReadFile(uiFS, "index.html") + if err != nil { + _ = c.AbortWithError(http.StatusInternalServerError, err) + return + } + c.Data(http.StatusOK, "text/html; charset=utf-8", b) + }) api := router.Group("/api") api.GET("/getShortNames", s.getShortNames) From 6dd85526abcf77f468445649715487c35cbba107 Mon Sep 17 00:00:00 2001 From: Alexey Vagarenko Date: Sat, 27 May 2023 00:12:05 +0600 Subject: [PATCH 1776/2916] WIP Main Page. --- pkg/webui/ui/public/staticbuild.js | 2 +- pkg/webui/ui/src/components/App.tsx | 13 +- pkg/webui/ui/src/components/LeftDrawer.tsx | 115 +++++++++--------- .../components/targets-view/TargetsView.tsx | 2 +- pkg/webui/ui/src/components/theme.ts | 72 +++++++++++ pkg/webui/ui/src/icons/KluctlLogo.tsx | 2 +- pkg/webui/ui/src/icons/KluctlText.tsx | 6 + pkg/webui/ui/src/icons/Targets.tsx | 5 + pkg/webui/ui/src/icons/kluctl-logo.svg | 4 +- pkg/webui/ui/src/icons/kluctl-text.svg | 7 ++ pkg/webui/ui/src/icons/targets.svg | 6 + pkg/webui/ui/src/index.css | 2 + 12 files changed, 172 insertions(+), 64 deletions(-) create mode 100644 pkg/webui/ui/src/components/theme.ts create mode 100644 pkg/webui/ui/src/icons/KluctlText.tsx create mode 100644 pkg/webui/ui/src/icons/Targets.tsx create mode 100644 pkg/webui/ui/src/icons/kluctl-text.svg create mode 100644 pkg/webui/ui/src/icons/targets.svg diff --git a/pkg/webui/ui/public/staticbuild.js b/pkg/webui/ui/public/staticbuild.js index 58271d740..a653e44b6 100644 --- a/pkg/webui/ui/public/staticbuild.js +++ b/pkg/webui/ui/public/staticbuild.js @@ -1,2 +1,2 @@ -let isStaticBuild = false; +let isStaticBuild = true; const staticResults = new Map() diff --git a/pkg/webui/ui/src/components/App.tsx b/pkg/webui/ui/src/components/App.tsx index ce3a71aa9..ba36ddc4a 100644 --- a/pkg/webui/ui/src/components/App.tsx +++ b/pkg/webui/ui/src/components/App.tsx @@ -1,9 +1,10 @@ import React, { useState } from 'react'; import '../index.css'; -import { Box } from "@mui/material"; +import { Box, ThemeProvider } from "@mui/material"; import { Outlet, useOutletContext } from "react-router-dom"; import LeftDrawer from "./LeftDrawer"; +import { light } from './theme'; export interface AppOutletContext { filters?: React.ReactNode @@ -22,9 +23,13 @@ const App = () => { setFilters: setFilters, } - return - } context={context}/> - + return ( + + + } context={context} /> + + + ); }; export default App; diff --git a/pkg/webui/ui/src/components/LeftDrawer.tsx b/pkg/webui/ui/src/components/LeftDrawer.tsx index 7c23a7be3..cd712c4b5 100644 --- a/pkg/webui/ui/src/components/LeftDrawer.tsx +++ b/pkg/webui/ui/src/components/LeftDrawer.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { useState } from 'react'; -import { CSSObject, styled, Theme, useTheme } from '@mui/material/styles'; +import { CSSObject, styled, Theme, ThemeProvider, useTheme } from '@mui/material/styles'; import Box from '@mui/material/Box'; import MuiDrawer from '@mui/material/Drawer'; import MuiAppBar, { AppBarProps as MuiAppBarProps } from '@mui/material/AppBar'; @@ -15,15 +15,19 @@ import ListItem from '@mui/material/ListItem'; import ListItemButton from '@mui/material/ListItemButton'; import ListItemIcon from '@mui/material/ListItemIcon'; import ListItemText from '@mui/material/ListItemText'; -import { Adjust } from "@mui/icons-material"; import { Link } from "react-router-dom"; import { AppOutletContext } from "./App"; import { KluctlLogo } from "../icons/KluctlLogo"; +import { Targets } from '../icons/Targets'; +import { dark } from './theme'; +import { KluctlText } from '../icons/KluctlText'; +import { Typography } from '@mui/material'; -const drawerWidth = 240; +const drawerWidthOpen = 224; +const drawerWidthClosed = 96; const openedMixin = (theme: Theme): CSSObject => ({ - width: drawerWidth, + width: drawerWidthOpen, transition: theme.transitions.create('width', { easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.enteringScreen, @@ -37,17 +41,12 @@ const closedMixin = (theme: Theme): CSSObject => ({ duration: theme.transitions.duration.leavingScreen, }), overflowX: 'hidden', - width: `calc(${theme.spacing(7)} + 1px)`, - [theme.breakpoints.up('sm')]: { - width: `calc(${theme.spacing(8)} + 1px)`, - }, + width: drawerWidthClosed, }); const DrawerHeader = styled('div')(({ theme }) => ({ - display: 'flex', - alignItems: 'center', - justifyContent: 'flex-end', - padding: theme.spacing(0, 1), + height: '106px', + padding: '31px 23px 0 23px', // necessary for content to be below app bar ...theme.mixins.toolbar, })); @@ -59,14 +58,22 @@ interface AppBarProps extends MuiAppBarProps { const AppBar = styled(MuiAppBar, { shouldForwardProp: (prop) => prop !== 'open', })(({ theme, open }) => ({ + height: 106, + border: 'none', + boxShadow: 'none', + background: 'transparent', + padding: '40px 40px 0 40px', + marginLeft: drawerWidthClosed, + justifyContent: 'space-between', + width: `calc(100% - ${drawerWidthClosed}px)`, zIndex: theme.zIndex.drawer + 1, transition: theme.transitions.create(['width', 'margin'], { easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.leavingScreen, }), ...(open && { - marginLeft: drawerWidth, - width: `calc(100% - ${drawerWidth}px)`, + marginLeft: drawerWidthOpen, + width: `calc(100% - ${drawerWidthOpen}px)`, transition: theme.transitions.create(['width', 'margin'], { easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.enteringScreen, @@ -76,17 +83,24 @@ const AppBar = styled(MuiAppBar, { const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => prop !== 'open' })( ({ theme, open }) => ({ - width: drawerWidth, + width: drawerWidthOpen, flexShrink: 0, whiteSpace: 'nowrap', boxSizing: 'border-box', + borderRadius: '0px 20px 20px 0px', ...(open && { ...openedMixin(theme), - '& .MuiDrawer-paper': openedMixin(theme), + '& .MuiDrawer-paper': { + ...openedMixin(theme), + borderRadius: '0px 20px 20px 0px', + } }), ...(!open && { ...closedMixin(theme), - '& .MuiDrawer-paper': closedMixin(theme), + '& .MuiDrawer-paper': { + ...closedMixin(theme), + borderRadius: '0px 20px 20px 0px', + } }), }), ); @@ -97,7 +111,7 @@ function Item(props: { text: string, open: boolean, icon: React.ReactNode, to: s sx={{ minHeight: 48, justifyContent: props.open ? 'initial' : 'center', - px: 2.5, + px: '24px', }} > {props.icon} - + } @@ -117,52 +131,41 @@ function Item(props: { text: string, open: boolean, icon: React.ReactNode, to: s export default function LeftDrawer(props: { content: React.ReactNode, context: AppOutletContext }) { const context = props.context - const theme = useTheme(); const [open, setOpen] = useState(true); - const handleDrawerOpen = () => { - setOpen(true); - }; - - const handleDrawerClose = () => { - setOpen(false); + const toggleDrawer = () => { + setOpen(o => !o); }; return ( - - - - - - + + Dashboard + + - - - - {theme.direction === 'rtl' ? : } - - - - - } to={"targets"}/> - - {context.filters} - - - + + + + + + {open && } + + + + + } to={"targets"} /> + + {context.filters} + + {context.filters && } + + - + {props.content} diff --git a/pkg/webui/ui/src/components/targets-view/TargetsView.tsx b/pkg/webui/ui/src/components/targets-view/TargetsView.tsx index 581d8ce82..3f44afc91 100644 --- a/pkg/webui/ui/src/components/targets-view/TargetsView.tsx +++ b/pkg/webui/ui/src/components/targets-view/TargetsView.tsx @@ -39,7 +39,7 @@ export const TargetsView = () => { setSelectedTargetSummary(ts) } - return + return setSelectedCommandResult(undefined)}/> setSelectedTargetSummary(undefined)}/> diff --git a/pkg/webui/ui/src/components/theme.ts b/pkg/webui/ui/src/components/theme.ts new file mode 100644 index 000000000..a552fab79 --- /dev/null +++ b/pkg/webui/ui/src/components/theme.ts @@ -0,0 +1,72 @@ +import { PaletteOptions, createTheme } from '@mui/material/styles'; + +const paletteDark = { + primary: { main: '#DFEBE9' }, + background: { default: '#222222', paper: '#222222' } +} satisfies PaletteOptions; + +const paletteLight = { + primary: { main: '#222222' }, + secondary: { main: '#39403E'} +} satisfies PaletteOptions; + +export const light = createTheme({ + palette: paletteLight, + components: { + MuiDivider: { + styleOverrides: { + root: { + borderColor: 'rgba(0, 0, 0, 0.5)' + } + } + }, + MuiAppBar: { + styleOverrides: { + root: { + color: paletteLight.primary.main + } + } + } + } +}); + +export const dark = createTheme({ + palette: paletteDark, + components: { + MuiListItem: { + styleOverrides: { + root: { + color: paletteDark.primary.main, + background: paletteDark.background.default + } + } + }, + MuiButtonBase: { + styleOverrides: { + root: { + color: paletteDark.primary.main, + background: paletteDark.background.default + } + } + }, + MuiDrawer: { + styleOverrides: { + root: { + border: 'none' + }, + paper: { + border: 'none' + }, + } + }, + MuiDivider: { + styleOverrides: { + root: { + background: paletteDark.primary.main, + opacity: 0.2, + margin: '0 13px' + } + } + } + } +}) \ No newline at end of file diff --git a/pkg/webui/ui/src/icons/KluctlLogo.tsx b/pkg/webui/ui/src/icons/KluctlLogo.tsx index b89f71f95..f7c3d0665 100644 --- a/pkg/webui/ui/src/icons/KluctlLogo.tsx +++ b/pkg/webui/ui/src/icons/KluctlLogo.tsx @@ -2,5 +2,5 @@ import { ReactComponent as KluctlLogoSvg } from './kluctl-logo.svg'; import React from "react"; export const KluctlLogo = () => { - return + return } diff --git a/pkg/webui/ui/src/icons/KluctlText.tsx b/pkg/webui/ui/src/icons/KluctlText.tsx new file mode 100644 index 000000000..6122532ab --- /dev/null +++ b/pkg/webui/ui/src/icons/KluctlText.tsx @@ -0,0 +1,6 @@ +import { ReactComponent as KluctlTextSvg } from './kluctl-text.svg'; +import React from "react"; + +export const KluctlText = () => { + return +} diff --git a/pkg/webui/ui/src/icons/Targets.tsx b/pkg/webui/ui/src/icons/Targets.tsx new file mode 100644 index 000000000..d1ba0a421 --- /dev/null +++ b/pkg/webui/ui/src/icons/Targets.tsx @@ -0,0 +1,5 @@ +import { ReactComponent as TargetsSvg } from './targets.svg'; + +export const Targets = () => { + return +} diff --git a/pkg/webui/ui/src/icons/kluctl-logo.svg b/pkg/webui/ui/src/icons/kluctl-logo.svg index d61023e05..2248dffe2 100644 --- a/pkg/webui/ui/src/icons/kluctl-logo.svg +++ b/pkg/webui/ui/src/icons/kluctl-logo.svg @@ -1 +1,3 @@ - \ No newline at end of file + + + diff --git a/pkg/webui/ui/src/icons/kluctl-text.svg b/pkg/webui/ui/src/icons/kluctl-text.svg new file mode 100644 index 000000000..ec60048e9 --- /dev/null +++ b/pkg/webui/ui/src/icons/kluctl-text.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/pkg/webui/ui/src/icons/targets.svg b/pkg/webui/ui/src/icons/targets.svg new file mode 100644 index 000000000..e6a6ad8c9 --- /dev/null +++ b/pkg/webui/ui/src/icons/targets.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/pkg/webui/ui/src/index.css b/pkg/webui/ui/src/index.css index 63775a2e6..738f22103 100644 --- a/pkg/webui/ui/src/index.css +++ b/pkg/webui/ui/src/index.css @@ -5,6 +5,8 @@ body { sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; + background: linear-gradient(180deg, #59A588 0%, #404846 100%); + background-attachment: fixed; } code { From 5b463de1026cdd4866ab3c38bfc7df8dd2544cab Mon Sep 17 00:00:00 2001 From: Alexey Vagarenko Date: Tue, 30 May 2023 01:57:55 +0600 Subject: [PATCH 1777/2916] WIP Main Page. --- pkg/webui/ui/package-lock.json | 10 +- pkg/webui/ui/package.json | 2 +- pkg/webui/ui/src/components/ActionsMenu.tsx | 5 +- pkg/webui/ui/src/components/LeftDrawer.tsx | 64 ++++++--- pkg/webui/ui/src/components/Router.tsx | 2 +- .../result-view/CommandResultStatusLine.tsx | 36 +++-- .../nodes/DeploymentItemIncludeNode.tsx | 2 +- .../result-view/nodes/VarsSourceNode.tsx | 2 +- .../targets-view/CommandResultItem.tsx | 88 +++++++++---- .../src/components/targets-view/Projects.tsx | 54 +++++--- .../src/components/targets-view/Targets.tsx | 123 ++++++++++-------- .../components/targets-view/TargetsView.tsx | 120 +++++++++++------ pkg/webui/ui/src/components/theme.ts | 18 ++- pkg/webui/ui/src/icons/GitIcon.tsx | 6 - pkg/webui/ui/src/icons/Icons.tsx | 64 +++++++++ pkg/webui/ui/src/icons/KluctlLogo.tsx | 6 - pkg/webui/ui/src/icons/KluctlText.tsx | 6 - pkg/webui/ui/src/icons/Targets.tsx | 5 - pkg/webui/ui/src/icons/cpu.svg | 16 +++ pkg/webui/ui/src/icons/finger-scan.svg | 8 ++ pkg/webui/ui/src/icons/project.svg | 5 + pkg/webui/ui/src/icons/relation-hline.svg | 5 + pkg/webui/ui/src/icons/search.svg | 4 + pkg/webui/ui/src/icons/target.svg | 5 + pkg/webui/ui/src/icons/tree-view.svg | 7 + pkg/webui/ui/src/index.css | 22 ++-- pkg/webui/ui/src/index.tsx | 5 +- 27 files changed, 465 insertions(+), 225 deletions(-) delete mode 100644 pkg/webui/ui/src/icons/GitIcon.tsx create mode 100644 pkg/webui/ui/src/icons/Icons.tsx delete mode 100644 pkg/webui/ui/src/icons/KluctlLogo.tsx delete mode 100644 pkg/webui/ui/src/icons/KluctlText.tsx delete mode 100644 pkg/webui/ui/src/icons/Targets.tsx create mode 100644 pkg/webui/ui/src/icons/cpu.svg create mode 100644 pkg/webui/ui/src/icons/finger-scan.svg create mode 100644 pkg/webui/ui/src/icons/project.svg create mode 100644 pkg/webui/ui/src/icons/relation-hline.svg create mode 100644 pkg/webui/ui/src/icons/search.svg create mode 100644 pkg/webui/ui/src/icons/target.svg create mode 100644 pkg/webui/ui/src/icons/tree-view.svg diff --git a/pkg/webui/ui/package-lock.json b/pkg/webui/ui/package-lock.json index 014cd172d..a31048de4 100644 --- a/pkg/webui/ui/package-lock.json +++ b/pkg/webui/ui/package-lock.json @@ -10,7 +10,7 @@ "dependencies": { "@emotion/react": "^11.10.6", "@emotion/styled": "^11.10.6", - "@fontsource/roboto": "^4.5.8", + "@fontsource-variable/nunito": "^5.0.2", "@mui/icons-material": "^5.11.11", "@mui/lab": "^5.0.0-alpha.124", "@mui/material": "^5.11.14", @@ -2386,10 +2386,10 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@fontsource/roboto": { - "version": "4.5.8", - "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-4.5.8.tgz", - "integrity": "sha512-CnD7zLItIzt86q4Sj3kZUiLcBk1dSk81qcqgMGaZe7SQ1P8hFNxhMl5AZthK1zrDM5m74VVhaOpuMGIL4gagaA==" + "node_modules/@fontsource-variable/nunito": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@fontsource-variable/nunito/-/nunito-5.0.2.tgz", + "integrity": "sha512-ztW98DGVZbq77ITkqg5+C5O8CI/SoaiqWOFBfbyE7CHMkU8XaBOXFe2l6H7YREE15mMIKCGA/icztQuoLi21lA==" }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.8", diff --git a/pkg/webui/ui/package.json b/pkg/webui/ui/package.json index eea5b6cc0..e658c5ed9 100644 --- a/pkg/webui/ui/package.json +++ b/pkg/webui/ui/package.json @@ -7,7 +7,7 @@ "dependencies": { "@emotion/react": "^11.10.6", "@emotion/styled": "^11.10.6", - "@fontsource/roboto": "^4.5.8", + "@fontsource-variable/nunito": "^5.0.2", "@mui/icons-material": "^5.11.11", "@mui/lab": "^5.0.0-alpha.124", "@mui/material": "^5.11.14", diff --git a/pkg/webui/ui/src/components/ActionsMenu.tsx b/pkg/webui/ui/src/components/ActionsMenu.tsx index 8247b7ed8..4b297e5ca 100644 --- a/pkg/webui/ui/src/components/ActionsMenu.tsx +++ b/pkg/webui/ui/src/components/ActionsMenu.tsx @@ -22,7 +22,7 @@ export const ActionsMenu = (props: { icon?: React.ReactNode, menuItems: ActionMe setAnchorEl(null); }; - const icon = props.icon || + const icon = props.icon || return {icon} @@ -39,7 +40,7 @@ export const ActionsMenu = (props: { icon?: React.ReactNode, menuItems: ActionMe id="account-menu" open={menuOpen} onClose={handleMenuClose} - onClick={ e => {e.stopPropagation(); handleMenuClose()}} + onClick={e => { e.stopPropagation(); handleMenuClose() }} PaperProps={{ elevation: 0, sx: { diff --git a/pkg/webui/ui/src/components/LeftDrawer.tsx b/pkg/webui/ui/src/components/LeftDrawer.tsx index cd712c4b5..5c1c61859 100644 --- a/pkg/webui/ui/src/components/LeftDrawer.tsx +++ b/pkg/webui/ui/src/components/LeftDrawer.tsx @@ -4,27 +4,22 @@ import { CSSObject, styled, Theme, ThemeProvider, useTheme } from '@mui/material import Box from '@mui/material/Box'; import MuiDrawer from '@mui/material/Drawer'; import MuiAppBar, { AppBarProps as MuiAppBarProps } from '@mui/material/AppBar'; -import Toolbar from '@mui/material/Toolbar'; import List from '@mui/material/List'; import Divider from '@mui/material/Divider'; import IconButton from '@mui/material/IconButton'; -import MenuIcon from '@mui/icons-material/Menu'; -import ChevronLeftIcon from '@mui/icons-material/ChevronLeft'; -import ChevronRightIcon from '@mui/icons-material/ChevronRight'; import ListItem from '@mui/material/ListItem'; import ListItemButton from '@mui/material/ListItemButton'; import ListItemIcon from '@mui/material/ListItemIcon'; import ListItemText from '@mui/material/ListItemText'; import { Link } from "react-router-dom"; import { AppOutletContext } from "./App"; -import { KluctlLogo } from "../icons/KluctlLogo"; -import { Targets } from '../icons/Targets'; +import { KluctlLogo, TargetsIcon, KluctlText, SearchIcon } from '../icons/Icons'; import { dark } from './theme'; -import { KluctlText } from '../icons/KluctlText'; import { Typography } from '@mui/material'; const drawerWidthOpen = 224; const drawerWidthClosed = 96; +const appBarHeight = 106; const openedMixin = (theme: Theme): CSSObject => ({ width: drawerWidthOpen, @@ -45,7 +40,7 @@ const closedMixin = (theme: Theme): CSSObject => ({ }); const DrawerHeader = styled('div')(({ theme }) => ({ - height: '106px', + height: appBarHeight, padding: '31px 23px 0 23px', // necessary for content to be below app bar ...theme.mixins.toolbar, @@ -58,7 +53,7 @@ interface AppBarProps extends MuiAppBarProps { const AppBar = styled(MuiAppBar, { shouldForwardProp: (prop) => prop !== 'open', })(({ theme, open }) => ({ - height: 106, + height: appBarHeight, border: 'none', boxShadow: 'none', background: 'transparent', @@ -133,16 +128,47 @@ export default function LeftDrawer(props: { content: React.ReactNode, context: A const [open, setOpen] = useState(true); + const theme = useTheme(); + const toggleDrawer = () => { setOpen(o => !o); }; return ( - - - - Dashboard - + + + + + Dashboard + + + + + + + + @@ -156,17 +182,19 @@ export default function LeftDrawer(props: { content: React.ReactNode, context: A - - } to={"targets"} /> + + } to={"targets"} /> {context.filters} {context.filters && } - + - {props.content} + + {props.content} + ); diff --git a/pkg/webui/ui/src/components/Router.tsx b/pkg/webui/ui/src/components/Router.tsx index ee3417a1a..0c908fdb7 100644 --- a/pkg/webui/ui/src/components/Router.tsx +++ b/pkg/webui/ui/src/components/Router.tsx @@ -1,4 +1,4 @@ -import { createBrowserRouter, createHashRouter, useRouteError } from "react-router-dom"; +import { createHashRouter, useRouteError } from "react-router-dom"; import React from "react"; import App from "./App"; import { projectsLoader, TargetsView } from "./targets-view/TargetsView"; diff --git a/pkg/webui/ui/src/components/result-view/CommandResultStatusLine.tsx b/pkg/webui/ui/src/components/result-view/CommandResultStatusLine.tsx index d5a48a05b..11b9721d6 100644 --- a/pkg/webui/ui/src/components/result-view/CommandResultStatusLine.tsx +++ b/pkg/webui/ui/src/components/result-view/CommandResultStatusLine.tsx @@ -20,7 +20,15 @@ export const StatusLine = (props: StatusLineProps) => { children.push( - {icon} + + {icon} + {n} @@ -28,12 +36,12 @@ export const StatusLine = (props: StatusLineProps) => { } } - doPush(props.errors, "total errors.", ) - doPush(props.warnings, "total warnings.", ) - doPush(props.newObjects, "new objects.", ) - doPush(props.deletedObjects, "deleted objects.", ) - doPush(props.orphanObjects, "orphan objects.", ) - doPush(props.changedObjects, "changed objects.", ) + doPush(props.errors, "total errors.", ) + doPush(props.warnings, "total warnings.", ) + doPush(props.newObjects, "new objects.", ) + doPush(props.deletedObjects, "deleted objects.", ) + doPush(props.orphanObjects, "orphan objects.", ) + doPush(props.changedObjects, "changed objects.", ) return {children} @@ -42,18 +50,18 @@ export const StatusLine = (props: StatusLineProps) => { export const CommandResultStatusLine = (props: { rs: CommandResultSummary }) => { return } export const ValidateResultStatusLine = (props: { vr?: ValidateResult }) => { return } \ No newline at end of file diff --git a/pkg/webui/ui/src/components/result-view/nodes/DeploymentItemIncludeNode.tsx b/pkg/webui/ui/src/components/result-view/nodes/DeploymentItemIncludeNode.tsx index 8d5ecc36c..1d33bf8b6 100644 --- a/pkg/webui/ui/src/components/result-view/nodes/DeploymentItemIncludeNode.tsx +++ b/pkg/webui/ui/src/components/result-view/nodes/DeploymentItemIncludeNode.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { DeploymentItemConfig, DeploymentProjectConfig } from "../../../models"; import { NodeData } from "./NodeData"; import { FolderZip } from "@mui/icons-material"; -import { GitIcon } from "../../../icons/GitIcon"; +import { GitIcon } from "../../../icons/Icons"; import { CommandResultProps } from "../CommandResultView"; import { PropertiesTable } from "../../PropertiesTable"; import { buildDeploymentItemSummaryProps } from "./DeploymentItemNode"; diff --git a/pkg/webui/ui/src/components/result-view/nodes/VarsSourceNode.tsx b/pkg/webui/ui/src/components/result-view/nodes/VarsSourceNode.tsx index d53838eeb..3b5674e5c 100644 --- a/pkg/webui/ui/src/components/result-view/nodes/VarsSourceNode.tsx +++ b/pkg/webui/ui/src/components/result-view/nodes/VarsSourceNode.tsx @@ -2,7 +2,7 @@ import { VarsSource } from "../../../models"; import { NodeData } from "./NodeData"; import React from "react"; import { Category, Cloud, Dvr, Http, Lock, Settings, Source } from "@mui/icons-material"; -import { GitIcon } from "../../../icons/GitIcon"; +import { GitIcon } from "../../../icons/Icons"; import { PropertiesTable } from "../../PropertiesTable"; import { CodeViewer } from "../../CodeViewer"; import { Box } from "@mui/material"; diff --git a/pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx b/pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx index 8c23fb84e..6eb2dec6e 100644 --- a/pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx +++ b/pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx @@ -1,6 +1,6 @@ import { CommandResultSummary, ProjectSummary, TargetSummary } from "../../models"; -import { AccountTree, CleaningServices, CloudSync, Delete, Difference } from "@mui/icons-material"; -import React, { useEffect, useMemo, useState } from "react"; +import { CleaningServices, CloudSync, Delete, Difference } from "@mui/icons-material"; +import { useEffect, useMemo, useState } from "react"; import * as yaml from "js-yaml"; import { CodeViewer } from "../CodeViewer"; import Paper from "@mui/material/Paper"; @@ -8,6 +8,7 @@ import { Box, IconButton, Tooltip, Typography } from "@mui/material"; import { CommandResultStatusLine } from "../result-view/CommandResultStatusLine"; import { useNavigate } from "react-router"; import { formatDurationShort } from "../../utils/duration"; +import { TreeViewIcon } from "../../icons/Icons"; export const CommandResultItem = (props: { ps: ProjectSummary, ts: TargetSummary, rs: CommandResultSummary, onSelectCommandResult: (rs?: CommandResultSummary) => void }) => { const calcAgo = () => { @@ -42,7 +43,7 @@ export const CommandResultItem = (props: { ps: ProjectSummary, ts: TargetSummary const cmdInfoYaml = useMemo(() => { return yaml.dump(props.rs.commandInfo) }, [props.rs]) - let iconTooltip = + let iconTooltip = useEffect(() => { const interval = setInterval(() => setAgo(calcAgo()), 5000); @@ -51,39 +52,70 @@ export const CommandResultItem = (props: { ps: ProjectSummary, ts: TargetSummary return props.onSelectCommandResult(props.rs)} > - - - + + + - + + + + + {props.rs.commandInfo?.command} + - {ago} + {ago} - - {props.rs.commandInfo?.command} - - - - - - - { - e.stopPropagation(); - navigate(`/results/${props.rs.id}`) - - }}> - - - - - - + + + + + + { + e.stopPropagation(); + navigate(`/results/${props.rs.id}`); + }} + sx={{ + padding: 0, + width: 26, + height: 26 + }} + > + + + + diff --git a/pkg/webui/ui/src/components/targets-view/Projects.tsx b/pkg/webui/ui/src/components/targets-view/Projects.tsx index 390fb9ee1..4756cf046 100644 --- a/pkg/webui/ui/src/components/targets-view/Projects.tsx +++ b/pkg/webui/ui/src/components/targets-view/Projects.tsx @@ -4,41 +4,57 @@ import Paper from "@mui/material/Paper"; import { Box, Typography } from "@mui/material"; import React from "react"; import Tooltip from "@mui/material/Tooltip"; +import { ProjectIcon } from "../../icons/Icons"; export const ProjectItem = (props: { ps: ProjectSummary }) => { const name = getLastPathElement(props.ps.project.gitRepoKey) const subDir = props.ps.project.subDir const projectInfo = - {props.ps.project.gitRepoKey}
    + {props.ps.project.gitRepoKey}
    {props.ps.project.subDir ? <> - SubDir: {props.ps.project.subDir}
    + SubDir: {props.ps.project.subDir}
    : <>}
    - return - - - {name ? - - - {name} - - - : <>} + return + + + {name && ( + <> + + + + {name} + + + + )} {subDir ? {subDir} : <>} - + {/**/} diff --git a/pkg/webui/ui/src/components/targets-view/Targets.tsx b/pkg/webui/ui/src/components/targets-view/Targets.tsx index b7b99849f..729cf98ff 100644 --- a/pkg/webui/ui/src/components/targets-view/Targets.tsx +++ b/pkg/webui/ui/src/components/targets-view/Targets.tsx @@ -1,28 +1,29 @@ import { ProjectSummary, TargetSummary } from "../../models"; import { ActionMenuItem, ActionsMenu } from "../ActionsMenu"; import Paper from "@mui/material/Paper"; -import { Box, Typography } from "@mui/material"; +import { Box, Typography, useTheme } from "@mui/material"; import React from "react"; -import { Jdenticon } from "../Jdenticon"; import Tooltip from "@mui/material/Tooltip"; -import { Favorite, Fingerprint, HeartBroken, PublishedWithChanges, QuestionMark } from "@mui/icons-material"; +import { Favorite, HeartBroken, PublishedWithChanges } from "@mui/icons-material"; import { api } from "../../api"; +import { CpuIcon, FingerScanIcon, MessageQuestionIcon, TargetIcon } from "../../icons/Icons"; const StatusIcon = (props: { ps: ProjectSummary, ts: TargetSummary }) => { let icon: React.ReactElement + const theme = useTheme(); if (props.ts.lastValidateResult === undefined) { - icon = + icon = } else if (props.ts.lastValidateResult.ready && !props.ts.lastValidateResult.errors) { if (props.ts.lastValidateResult.warnings?.length) { - icon = + icon = } else if (props.ts.lastValidateResult.drift?.length) { - icon = + icon = } else { - icon = + icon = } } else { - icon = + icon = } const tooltip: string[] = [] @@ -46,7 +47,7 @@ const StatusIcon = (props: { ps: ProjectSummary, ts: TargetSummary }) => { } return {t})}> - {icon} + {icon} } @@ -54,7 +55,7 @@ export const TargetItem = (props: { ps: ProjectSummary, ts: TargetSummary, onSel const actionMenuItems: ActionMenuItem[] = [] actionMenuItems.push({ - icon: , + icon: , text: "Validate now", handler: () => { api.validateNow(props.ps.project, props.ts.target) @@ -79,9 +80,9 @@ export const TargetItem = (props: { ps: ProjectSummary, ts: TargetSummary, onSel }) const contextTooltip = - All known contexts: + All known contexts: {allContexts.map((context, i) => ( - {context} + {context} ))} @@ -92,54 +93,62 @@ export const TargetItem = (props: { ps: ProjectSummary, ts: TargetSummary, onSel return props.onSelectTarget(props.ts)} > - - - - {targetName} - - {allContexts.length ? - - - {allContexts[0]} - - - - : <>} - - - - - -
    - -
    + + + + + + {targetName} + + {allContexts.length ? + + + {allContexts[0]} + - -
    - -
    -
    -
    - - - - + : <>} +
    +
    + + + + + + + + + + + +
    diff --git a/pkg/webui/ui/src/components/targets-view/TargetsView.tsx b/pkg/webui/ui/src/components/targets-view/TargetsView.tsx index 3f44afc91..991546840 100644 --- a/pkg/webui/ui/src/components/targets-view/TargetsView.tsx +++ b/pkg/webui/ui/src/components/targets-view/TargetsView.tsx @@ -1,6 +1,6 @@ import { useLoaderData } from "react-router-dom"; import { CommandResultSummary, ProjectSummary, TargetSummary } from "../../models"; -import { Box, Typography } from "@mui/material"; +import { Box, BoxProps, Typography } from "@mui/material"; import React, { useEffect, useState } from "react"; import { useAppOutletContext } from "../App"; import { api } from "../../api"; @@ -10,15 +10,56 @@ import Divider from "@mui/material/Divider"; import { CommandResultItem } from "./CommandResultItem"; import { CommandResultDetailsDrawer } from "./CommandResultDetailsDrawer"; import { TargetDetailsDrawer } from "./TargetDetailsDrawer"; +import { RelationHLine } from "../../icons/Icons"; -const targetWidth = "220px" -const targetHeight = "110px" +const colWidth = 433; +const cardWidth = 247; +const projectCardHeight = 80; +const cardHeight = 126; export async function projectsLoader() { const projects = await api.listProjects() return projects } +function ColHeader({ children }: { children: React.ReactNode }) { + return + {children} + +} + +function Card({ children, ...rest }: BoxProps) { + return + {children} + +} + +function CardCol({ children, ...rest }: BoxProps) { + return + {children} + +} + +function CardRow({ children, ...rest }: BoxProps) { + return + {children} + +} + export const TargetsView = () => { const context = useAppOutletContext() const [selectedCommandResult, setSelectedCommandResult] = useState() @@ -39,57 +80,58 @@ export const TargetsView = () => { setSelectedTargetSummary(ts) } + useEffect(() => { + if (projects[2].targets.length < 3) { + projects[2].targets.push(projects[2].targets[0]) + } + }) + return - setSelectedCommandResult(undefined)}/> - setSelectedTargetSummary(undefined)}/> - - - Projects - - - - Targets - - - - History - + setSelectedCommandResult(undefined)} /> + setSelectedTargetSummary(undefined)} /> + + Projects + Targets + History - + {projects.map((ps, i) => { return - - - - - - - + + + + + + - + {ps.targets.map((ts, i) => { - return - doSetSelectedTargetSummary(ts)}/> + return + + doSetSelectedTargetSummary(ts)} /> + + + + })} - - + - + {ps.targets.map((ts, i) => { - return + return {ts.commandResults?.map((rs, i) => { - return + return doSetSelectedCommandResult(rs)}/> - + onSelectCommandResult={(rs) => doSetSelectedCommandResult(rs)} /> + })} - + })} - + - + })} diff --git a/pkg/webui/ui/src/components/theme.ts b/pkg/webui/ui/src/components/theme.ts index a552fab79..bc1c18469 100644 --- a/pkg/webui/ui/src/components/theme.ts +++ b/pkg/webui/ui/src/components/theme.ts @@ -7,11 +7,20 @@ const paletteDark = { const paletteLight = { primary: { main: '#222222' }, - secondary: { main: '#39403E'} + secondary: { main: '#39403E' }, + background: { default: '#DFEBE9', paper: '#DFEBE9' }, + text: { primary: '#222222' } } satisfies PaletteOptions; export const light = createTheme({ palette: paletteLight, + typography: { + fontFamily: 'Nunito Variable', + h1: { color: '#222222' }, + h6: { color: '#39403E' }, + subtitle1: { color: '#39403E' }, + subtitle2: { fontSize: '14px', lineHeight: 1.2 } + }, components: { MuiDivider: { styleOverrides: { @@ -32,6 +41,9 @@ export const light = createTheme({ export const dark = createTheme({ palette: paletteDark, + typography: { + fontFamily: 'Nunito Variable' + }, components: { MuiListItem: { styleOverrides: { @@ -53,10 +65,10 @@ export const dark = createTheme({ styleOverrides: { root: { border: 'none' - }, + }, paper: { border: 'none' - }, + }, } }, MuiDivider: { diff --git a/pkg/webui/ui/src/icons/GitIcon.tsx b/pkg/webui/ui/src/icons/GitIcon.tsx deleted file mode 100644 index 6948ffe5a..000000000 --- a/pkg/webui/ui/src/icons/GitIcon.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { ReactComponent as GitIconSvg } from './git.svg'; -import React from "react"; - -export const GitIcon = () => { - return -} diff --git a/pkg/webui/ui/src/icons/Icons.tsx b/pkg/webui/ui/src/icons/Icons.tsx new file mode 100644 index 000000000..18921d8bc --- /dev/null +++ b/pkg/webui/ui/src/icons/Icons.tsx @@ -0,0 +1,64 @@ +import { ReactComponent as KluctlTextSvg } from './kluctl-text.svg'; +import { ReactComponent as GitIconSvg } from './git.svg'; +import { ReactComponent as KluctlLogoSvg } from './kluctl-logo.svg'; +import { ReactComponent as SearchIconSvg } from './search.svg'; +import { ReactComponent as TargetsIconSvg } from './targets.svg'; +import { ReactComponent as TargetIconSvg } from './target.svg'; +import { ReactComponent as RelationHLineSvg } from './relation-hline.svg'; +import { ReactComponent as ProjectIconSvg } from './project.svg'; +import { ReactComponent as CpuIconSvg } from './cpu.svg'; +import { ReactComponent as FingerScanIconSvg } from './finger-scan.svg'; +import { ReactComponent as TreeViewIconSvg } from './tree-view.svg'; + +export const KluctlText = () => { + return +} + +export const GitIcon = () => { + return +} + +export const KluctlLogo = () => { + return +} + +export const SearchIcon = () => { + return +} + +export const TargetsIcon = () => { + return +} + +export const TargetIcon = () => { + return +} +export const RelationHLine = () => { + return +} + +export const ProjectIcon = () => { + return +} + +export const CpuIcon = () => { + return +} + +export const FingerScanIcon = () => { + return +} + +export const MessageQuestionIcon = (props: { color: string }) => { + return + + + +} + +export const TreeViewIcon = () => { + return +} diff --git a/pkg/webui/ui/src/icons/KluctlLogo.tsx b/pkg/webui/ui/src/icons/KluctlLogo.tsx deleted file mode 100644 index f7c3d0665..000000000 --- a/pkg/webui/ui/src/icons/KluctlLogo.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { ReactComponent as KluctlLogoSvg } from './kluctl-logo.svg'; -import React from "react"; - -export const KluctlLogo = () => { - return -} diff --git a/pkg/webui/ui/src/icons/KluctlText.tsx b/pkg/webui/ui/src/icons/KluctlText.tsx deleted file mode 100644 index 6122532ab..000000000 --- a/pkg/webui/ui/src/icons/KluctlText.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { ReactComponent as KluctlTextSvg } from './kluctl-text.svg'; -import React from "react"; - -export const KluctlText = () => { - return -} diff --git a/pkg/webui/ui/src/icons/Targets.tsx b/pkg/webui/ui/src/icons/Targets.tsx deleted file mode 100644 index d1ba0a421..000000000 --- a/pkg/webui/ui/src/icons/Targets.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { ReactComponent as TargetsSvg } from './targets.svg'; - -export const Targets = () => { - return -} diff --git a/pkg/webui/ui/src/icons/cpu.svg b/pkg/webui/ui/src/icons/cpu.svg new file mode 100644 index 000000000..b30baa518 --- /dev/null +++ b/pkg/webui/ui/src/icons/cpu.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/pkg/webui/ui/src/icons/finger-scan.svg b/pkg/webui/ui/src/icons/finger-scan.svg new file mode 100644 index 000000000..8f7fd351f --- /dev/null +++ b/pkg/webui/ui/src/icons/finger-scan.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/pkg/webui/ui/src/icons/project.svg b/pkg/webui/ui/src/icons/project.svg new file mode 100644 index 000000000..4a0c505c9 --- /dev/null +++ b/pkg/webui/ui/src/icons/project.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/pkg/webui/ui/src/icons/relation-hline.svg b/pkg/webui/ui/src/icons/relation-hline.svg new file mode 100644 index 000000000..0835a6c9f --- /dev/null +++ b/pkg/webui/ui/src/icons/relation-hline.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/pkg/webui/ui/src/icons/search.svg b/pkg/webui/ui/src/icons/search.svg new file mode 100644 index 000000000..1a9508cbd --- /dev/null +++ b/pkg/webui/ui/src/icons/search.svg @@ -0,0 +1,4 @@ + + + + diff --git a/pkg/webui/ui/src/icons/target.svg b/pkg/webui/ui/src/icons/target.svg new file mode 100644 index 000000000..eb93030dc --- /dev/null +++ b/pkg/webui/ui/src/icons/target.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/pkg/webui/ui/src/icons/tree-view.svg b/pkg/webui/ui/src/icons/tree-view.svg new file mode 100644 index 000000000..225712f63 --- /dev/null +++ b/pkg/webui/ui/src/icons/tree-view.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/pkg/webui/ui/src/index.css b/pkg/webui/ui/src/index.css index 738f22103..e1ba40f42 100644 --- a/pkg/webui/ui/src/index.css +++ b/pkg/webui/ui/src/index.css @@ -1,27 +1,31 @@ body { margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; background: linear-gradient(180deg, #59A588 0%, #404846 100%); background-attachment: fixed; } +body, input { + font-family: 'Nunito Variable', 'Segoe UI', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', -apple-system, BlinkMacSystemFont, + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + code { font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; + monospace; } * { box-sizing: border-box; } -html, body, #root { +html, +body, +#root { width: 100%; height: 100%; margin: 0; padding: 0; - font-family: sans-serif; -} +} \ No newline at end of file diff --git a/pkg/webui/ui/src/index.tsx b/pkg/webui/ui/src/index.tsx index f7e132268..391fce39b 100644 --- a/pkg/webui/ui/src/index.tsx +++ b/pkg/webui/ui/src/index.tsx @@ -5,10 +5,7 @@ import { RouterProvider } from "react-router-dom"; import './index.css'; import reportWebVitals from './reportWebVitals'; -import '@fontsource/roboto/300.css'; -import '@fontsource/roboto/400.css'; -import '@fontsource/roboto/500.css'; -import '@fontsource/roboto/700.css'; +import '@fontsource-variable/nunito'; import { Router } from "./components/Router"; From 776729c327d7907227d4131abe0067ab9f6bef4a Mon Sep 17 00:00:00 2001 From: Alexey Vagarenko Date: Tue, 30 May 2023 22:47:24 +0600 Subject: [PATCH 1778/2916] Main Page: Added lines from projects to targets. --- .../components/targets-view/TargetsView.tsx | 111 ++++++++++++++++-- 1 file changed, 100 insertions(+), 11 deletions(-) diff --git a/pkg/webui/ui/src/components/targets-view/TargetsView.tsx b/pkg/webui/ui/src/components/targets-view/TargetsView.tsx index 991546840..8a88ca741 100644 --- a/pkg/webui/ui/src/components/targets-view/TargetsView.tsx +++ b/pkg/webui/ui/src/components/targets-view/TargetsView.tsx @@ -1,6 +1,6 @@ import { useLoaderData } from "react-router-dom"; import { CommandResultSummary, ProjectSummary, TargetSummary } from "../../models"; -import { Box, BoxProps, Typography } from "@mui/material"; +import { Box, BoxProps, Typography, useTheme } from "@mui/material"; import React, { useEffect, useState } from "react"; import { useAppOutletContext } from "../App"; import { api } from "../../api"; @@ -16,6 +16,7 @@ const colWidth = 433; const cardWidth = 247; const projectCardHeight = 80; const cardHeight = 126; +const cardGap = 20; export async function projectsLoader() { const projects = await api.listProjects() @@ -49,17 +50,102 @@ function Card({ children, ...rest }: BoxProps) { } function CardCol({ children, ...rest }: BoxProps) { - return + return {children} } function CardRow({ children, ...rest }: BoxProps) { - return + return {children} } +const RelationTree = React.memo(({ targetCount }: { targetCount: number }): JSX.Element | null => { + const theme = useTheme(); + const height = targetCount * cardHeight + (targetCount - 1) * cardGap + const width = 169; + const curveRadius = 12; + const circleRadius = 5; + const strokeWidth = 2; + + if (targetCount <= 0) { + return null; + } + + const targetsCenterYs = Array(targetCount).fill(0).map((_, i) => + cardHeight / 2 + i * (cardHeight + cardGap) + ); + const rootCenterY = height / 2; + + return + {targetsCenterYs.map((cy, i) => { + let d: React.SVGAttributes['d']; + if (targetCount % 2 === 1 && i === Math.floor(targetCount / 2)) { + // target is in the middle. + d = ` + M ${circleRadius},${rootCenterY} + h ${width - circleRadius} + `; + } else if (i < targetCount / 2) { + // target is higher than root. + d = ` + M ${circleRadius},${rootCenterY} + h ${width / 2 - curveRadius - circleRadius} + a ${curveRadius} ${curveRadius} 90 0 0 ${curveRadius} -${curveRadius} + v ${cy - rootCenterY + curveRadius * 2} + a ${curveRadius} ${curveRadius} 90 0 1 ${curveRadius} -${curveRadius} + h ${width / 2 - curveRadius - circleRadius} + `; + } else { + // target is lower than root. + d = ` + M ${circleRadius},${rootCenterY} + h ${width / 2 - curveRadius - circleRadius} + a ${curveRadius} ${curveRadius} 90 0 1 ${curveRadius} ${curveRadius} + v ${cy - rootCenterY - curveRadius * 2} + a ${curveRadius} ${curveRadius} 90 0 0 ${curveRadius} ${curveRadius} + h ${width / 2 - curveRadius - circleRadius} + `; + } + + return [ + , + + ] + })} + + +}); + export const TargetsView = () => { const context = useAppOutletContext() const [selectedCommandResult, setSelectedCommandResult] = useState() @@ -80,12 +166,6 @@ export const TargetsView = () => { setSelectedTargetSummary(ts) } - useEffect(() => { - if (projects[2].targets.length < 3) { - projects[2].targets.push(projects[2].targets[0]) - } - }) - return setSelectedCommandResult(undefined)} /> setSelectedTargetSummary(undefined)} /> @@ -98,11 +178,20 @@ export const TargetsView = () => { {projects.map((ps, i) => { return - + - + + + + {ps.targets.map((ts, i) => { From f17ee24b4b8c12e96dca3fba120adf7d93d9cf3e Mon Sep 17 00:00:00 2001 From: Alexey Vagarenko Date: Wed, 31 May 2023 01:55:39 +0600 Subject: [PATCH 1779/2916] Main Page: added side panel for target nodes. --- pkg/webui/ui/src/components/LeftDrawer.tsx | 11 +- .../ui/src/components/PropertiesTable.tsx | 2 +- .../src/components/result-view/SidePanel.tsx | 55 +++++--- .../targets-view/CommandResultItem.tsx | 3 - .../src/components/targets-view/Projects.tsx | 3 - .../targets-view/TargetDetailsDrawer.tsx | 58 ++++---- .../src/components/targets-view/Targets.tsx | 3 - .../components/targets-view/TargetsView.tsx | 6 +- pkg/webui/ui/src/components/theme.ts | 126 ++++++++++++++++-- pkg/webui/ui/src/icons/Icons.tsx | 5 + pkg/webui/ui/src/icons/close.svg | 3 + 11 files changed, 200 insertions(+), 75 deletions(-) create mode 100644 pkg/webui/ui/src/icons/close.svg diff --git a/pkg/webui/ui/src/components/LeftDrawer.tsx b/pkg/webui/ui/src/components/LeftDrawer.tsx index 5c1c61859..a5bd4e5fe 100644 --- a/pkg/webui/ui/src/components/LeftDrawer.tsx +++ b/pkg/webui/ui/src/components/LeftDrawer.tsx @@ -19,7 +19,6 @@ import { Typography } from '@mui/material'; const drawerWidthOpen = 224; const drawerWidthClosed = 96; -const appBarHeight = 106; const openedMixin = (theme: Theme): CSSObject => ({ width: drawerWidthOpen, @@ -40,7 +39,7 @@ const closedMixin = (theme: Theme): CSSObject => ({ }); const DrawerHeader = styled('div')(({ theme }) => ({ - height: appBarHeight, + height: theme.consts.appBarHeight, padding: '31px 23px 0 23px', // necessary for content to be below app bar ...theme.mixins.toolbar, @@ -53,7 +52,7 @@ interface AppBarProps extends MuiAppBarProps { const AppBar = styled(MuiAppBar, { shouldForwardProp: (prop) => prop !== 'open', })(({ theme, open }) => ({ - height: appBarHeight, + height: theme.consts.appBarHeight, border: 'none', boxShadow: 'none', background: 'transparent', @@ -138,7 +137,7 @@ export default function LeftDrawer(props: { content: React.ReactNode, context: A - + Dashboard - @@ -192,7 +190,8 @@ export default function LeftDrawer(props: { content: React.ReactNode, context: A - + + {props.content} diff --git a/pkg/webui/ui/src/components/PropertiesTable.tsx b/pkg/webui/ui/src/components/PropertiesTable.tsx index 20cdd967e..61237aefb 100644 --- a/pkg/webui/ui/src/components/PropertiesTable.tsx +++ b/pkg/webui/ui/src/components/PropertiesTable.tsx @@ -9,7 +9,7 @@ import Paper from '@mui/material/Paper'; export function PropertiesTable(props: {properties: {name: string, value: React.ReactNode}[]}) { return ( - + diff --git a/pkg/webui/ui/src/components/result-view/SidePanel.tsx b/pkg/webui/ui/src/components/result-view/SidePanel.tsx index da68288f9..9a2047a7c 100644 --- a/pkg/webui/ui/src/components/result-view/SidePanel.tsx +++ b/pkg/webui/ui/src/components/result-view/SidePanel.tsx @@ -1,6 +1,8 @@ -import { Box, Tab, Typography } from "@mui/material"; +import { Box, Divider, IconButton, Tab, ThemeProvider, Typography, useTheme } from "@mui/material"; import React, { useEffect, useState } from "react"; import { TabContext, TabList, TabPanel } from "@mui/lab"; +import { CloseIcon } from "../../icons/Icons"; +import { light } from "../theme"; export interface SidePanelTab { label: string @@ -14,10 +16,12 @@ export interface SidePanelProvider { export interface SidePanelProps { provider?: SidePanelProvider; + onClose?: () => void; } export const SidePanel = (props: SidePanelProps) => { - let [selectedTab, setSelectedTab] = useState(); + const [selectedTab, setSelectedTab] = useState(); + const theme = useTheme(); function handleTabChange(_e: React.SyntheticEvent, value: string) { setSelectedTab(value); @@ -55,24 +59,39 @@ export const SidePanel = (props: SidePanelProps) => { return <> } - return - - {props.provider.buildSidePanelTitle()} - - + return - - - {tabs.map((tab, i) => { - return - })} - + + + + + {props.provider.buildSidePanelTitle()} + + + + + + + + + + + {tabs.map((tab, i) => { + return + })} + + + + + + {tabs.map((tab, index) => { + return + + {tab.content} + + + })} - {tabs.map((tab, index) => { - return - {tab.content} - - })} } diff --git a/pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx b/pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx index 6eb2dec6e..3c5a1a5d2 100644 --- a/pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx +++ b/pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx @@ -75,10 +75,7 @@ export const CommandResultItem = (props: { ps: ProjectSummary, ts: TargetSummary textAlign='left' textOverflow='ellipsis' overflow='hidden' - lineHeight='27.28px' flexGrow={1} - fontSize='20px' - fontWeight={800} > {props.rs.commandInfo?.command} diff --git a/pkg/webui/ui/src/components/targets-view/Projects.tsx b/pkg/webui/ui/src/components/targets-view/Projects.tsx index 4756cf046..c5ebb34f9 100644 --- a/pkg/webui/ui/src/components/targets-view/Projects.tsx +++ b/pkg/webui/ui/src/components/targets-view/Projects.tsx @@ -38,10 +38,7 @@ export const ProjectItem = (props: { ps: ProjectSummary }) => { textAlign='left' textOverflow='ellipsis' overflow='hidden' - lineHeight={1.2} flexGrow={1} - fontSize='20px' - fontWeight={800} > {name} diff --git a/pkg/webui/ui/src/components/targets-view/TargetDetailsDrawer.tsx b/pkg/webui/ui/src/components/targets-view/TargetDetailsDrawer.tsx index 6c76ebbc4..874779b74 100644 --- a/pkg/webui/ui/src/components/targets-view/TargetDetailsDrawer.tsx +++ b/pkg/webui/ui/src/components/targets-view/TargetDetailsDrawer.tsx @@ -1,11 +1,12 @@ import { TargetSummary } from "../../models"; -import { Box, Drawer } from "@mui/material"; +import { Box, Drawer, ThemeProvider } from "@mui/material"; import { SidePanel, SidePanelProvider, SidePanelTab } from "../result-view/SidePanel"; import React from "react"; import { PropertiesTable } from "../PropertiesTable"; import { DiffStatus } from "../result-view/nodes/NodeData"; import { ChangesTable } from "../result-view/ChangesTable"; import { ErrorsTable } from "../ErrorsTable"; +import { dark } from "../theme"; class MyProvider implements SidePanelProvider { private ts?: TargetSummary; @@ -26,27 +27,27 @@ class MyProvider implements SidePanelProvider { } const tabs = [ - {label: "Summary", content: this.buildSummaryTab()} + { label: "Summary", content: this.buildSummaryTab() } ] if (this.ts.target) - if (this.diffStatus.changedObjects.length) { - tabs.push({ - label: "Drift", - content: - }) - } + if (this.diffStatus.changedObjects.length) { + tabs.push({ + label: "Drift", + content: + }) + } if (this.ts.lastValidateResult?.errors?.length) { tabs.push({ label: "Errors", - content: + content: }) } if (this.ts.lastValidateResult?.warnings?.length) { tabs.push({ label: "Warnings", - content: + content: }) } @@ -55,25 +56,25 @@ class MyProvider implements SidePanelProvider { buildSummaryTab(): React.ReactNode { const props = [ - {name: "Target Name", value: this.getTargetName()}, - {name: "Discriminator", value: this.ts?.target.discriminator}, + { name: "Target Name", value: this.getTargetName() }, + { name: "Discriminator", value: this.ts?.target.discriminator }, ] if (this.ts?.lastValidateResult) { - props.push({name: "Ready", value: this.ts.lastValidateResult.ready + ""}) + props.push({ name: "Ready", value: this.ts.lastValidateResult.ready + "" }) } if (this.ts?.lastValidateResult?.errors?.length) { - props.push({name: "Errors", value: this.ts.lastValidateResult.errors.length + ""}) + props.push({ name: "Errors", value: this.ts.lastValidateResult.errors.length + "" }) } if (this.ts?.lastValidateResult?.warnings?.length) { - props.push({name: "Warnings", value: this.ts.lastValidateResult.warnings.length + ""}) + props.push({ name: "Warnings", value: this.ts.lastValidateResult.warnings.length + "" }) } if (this.ts?.lastValidateResult?.drift?.length) { - props.push({name: "Drifted Objects", value: this.ts.lastValidateResult.drift.length + ""}) + props.push({ name: "Drifted Objects", value: this.ts.lastValidateResult.drift.length + "" }) } return <> - + } @@ -95,14 +96,17 @@ class MyProvider implements SidePanelProvider { } export const TargetDetailsDrawer = (props: { ts?: TargetSummary, onClose: () => void }) => { - return props.onClose()} - > - - - - + return + + + + + + ; } \ No newline at end of file diff --git a/pkg/webui/ui/src/components/targets-view/Targets.tsx b/pkg/webui/ui/src/components/targets-view/Targets.tsx index 729cf98ff..d6f891c37 100644 --- a/pkg/webui/ui/src/components/targets-view/Targets.tsx +++ b/pkg/webui/ui/src/components/targets-view/Targets.tsx @@ -112,10 +112,7 @@ export const TargetItem = (props: { ps: ProjectSummary, ts: TargetSummary, onSel textAlign='left' textOverflow='ellipsis' overflow='hidden' - lineHeight='27.28px' flexGrow={1} - fontSize='20px' - fontWeight={800} > {targetName} diff --git a/pkg/webui/ui/src/components/targets-view/TargetsView.tsx b/pkg/webui/ui/src/components/targets-view/TargetsView.tsx index 8a88ca741..52178ed7f 100644 --- a/pkg/webui/ui/src/components/targets-view/TargetsView.tsx +++ b/pkg/webui/ui/src/components/targets-view/TargetsView.tsx @@ -39,7 +39,7 @@ function ColHeader({ children }: { children: React.ReactNode }) { } }} > - {children} + {children} } @@ -79,9 +79,9 @@ const RelationTree = React.memo(({ targetCount }: { targetCount: number }): JSX. const rootCenterY = height / 2; return {targetsCenterYs.map((cy, i) => { diff --git a/pkg/webui/ui/src/components/theme.ts b/pkg/webui/ui/src/components/theme.ts index bc1c18469..c599bf1cd 100644 --- a/pkg/webui/ui/src/components/theme.ts +++ b/pkg/webui/ui/src/components/theme.ts @@ -1,8 +1,9 @@ -import { PaletteOptions, createTheme } from '@mui/material/styles'; +import { PaletteOptions, ThemeOptions, createTheme } from '@mui/material/styles'; const paletteDark = { primary: { main: '#DFEBE9' }, - background: { default: '#222222', paper: '#222222' } + background: { default: '#222222', paper: '#222222' }, + text: { primary: '#DFEBE9' } } satisfies PaletteOptions; const paletteLight = { @@ -12,13 +13,53 @@ const paletteLight = { text: { primary: '#222222' } } satisfies PaletteOptions; -export const light = createTheme({ - palette: paletteLight, +declare module '@mui/material/styles' { + interface Theme { + consts: { + appBarHeight: number; + }; + } + // allow configuration using `createTheme` + interface ThemeOptions { + consts?: { + appBarHeight?: number; + }; + } +} + +export const common = createTheme({ + consts: { + appBarHeight: 106 + }, typography: { fontFamily: 'Nunito Variable', - h1: { color: '#222222' }, - h6: { color: '#39403E' }, - subtitle1: { color: '#39403E' }, + } +}); + +export const light = createTheme(common, { + palette: paletteLight, + typography: { + h1: { + color: paletteLight.text.primary, + fontWeight: 700, + fontSize: '32px', + lineHeight: '44px', + letterSpacing: '1px', + }, + h2: { + color: paletteLight.text.primary, + fontWeight: 700, + fontSize: '20px', + lineHeight: '27px', + letterSpacing: '1px', + }, + h6: { + color: paletteLight.secondary.main, + fontWeight: 800, + fontSize: '20px', + lineHeight: '27px' + }, + subtitle1: { color: paletteLight.secondary.main }, subtitle2: { fontSize: '14px', lineHeight: 1.2 } }, components: { @@ -35,14 +76,51 @@ export const light = createTheme({ color: paletteLight.primary.main } } + }, + MuiTableCell: { + styleOverrides: { + root: { + border: 'none', + borderTop: `0.5px solid ${paletteLight.secondary.main}`, + fontWeight: 400, + fontSize: '16px', + lineHeight: '22px', + letterSpacing: '1px', + position: 'relative', + ':after': { + content: '""', + position: 'absolute', + top: '10px', + right: 0, + bottom: '10px', + display: 'block', + borderRight: '0.5px solid #39403E', + }, + ':last-of-type:after': { + content: 'none' + } + }, + head: { + border: 'none', + } + } } } -}); +} satisfies ThemeOptions); -export const dark = createTheme({ +export const dark = createTheme(common, { palette: paletteDark, typography: { - fontFamily: 'Nunito Variable' + allVariants: { + color: paletteDark.text.primary + }, + h4: { + color: paletteDark.text.primary, + fontWeight: 700, + fontSize: '24px', + lineHeight: '33px', + letterSpacing: '1px', + } }, components: { MuiListItem: { @@ -79,6 +157,32 @@ export const dark = createTheme({ margin: '0 13px' } } + }, + MuiTabs: { + styleOverrides: { + root: { + height: '36px', + minHeight: 0, + textTransform: 'none' + }, + indicator: { + backgroundColor: '#59A588' + } + } + }, + MuiTab: { + styleOverrides: { + root: { + height: '36px', + minHeight: 0, + fontWeight: 400, + fontSize: '16px', + lineHeight: '22px', + letterSpacing: '1px', + textTransform: 'none', + padding: '7px 5px' + } + } } } -}) \ No newline at end of file +} satisfies ThemeOptions) \ No newline at end of file diff --git a/pkg/webui/ui/src/icons/Icons.tsx b/pkg/webui/ui/src/icons/Icons.tsx index 18921d8bc..156c0233f 100644 --- a/pkg/webui/ui/src/icons/Icons.tsx +++ b/pkg/webui/ui/src/icons/Icons.tsx @@ -9,6 +9,7 @@ import { ReactComponent as ProjectIconSvg } from './project.svg'; import { ReactComponent as CpuIconSvg } from './cpu.svg'; import { ReactComponent as FingerScanIconSvg } from './finger-scan.svg'; import { ReactComponent as TreeViewIconSvg } from './tree-view.svg'; +import { ReactComponent as CloseIconSvg } from './close.svg'; export const KluctlText = () => { return @@ -62,3 +63,7 @@ export const MessageQuestionIcon = (props: { color: string }) => { export const TreeViewIcon = () => { return } + +export const CloseIcon = () => { + return +} \ No newline at end of file diff --git a/pkg/webui/ui/src/icons/close.svg b/pkg/webui/ui/src/icons/close.svg new file mode 100644 index 000000000..f3ce4ed70 --- /dev/null +++ b/pkg/webui/ui/src/icons/close.svg @@ -0,0 +1,3 @@ + + + From 4eed4f3b6d5252fea1de39fcaa0599b919d87fd8 Mon Sep 17 00:00:00 2001 From: Alexey Vagarenko Date: Thu, 1 Jun 2023 01:27:26 +0600 Subject: [PATCH 1780/2916] Main Page: added side panel for command result nodes. --- pkg/webui/ui/src/components/ErrorsTable.tsx | 5 ++- .../ui/src/components/PropertiesTable.tsx | 3 +- .../components/result-view/ChangesTable.tsx | 36 ++++++++++--------- .../src/components/result-view/SidePanel.tsx | 14 ++++---- .../CommandResultDetailsDrawer.tsx | 34 ++++++++++-------- pkg/webui/ui/src/components/theme.ts | 20 +++++++++-- 6 files changed, 67 insertions(+), 45 deletions(-) diff --git a/pkg/webui/ui/src/components/ErrorsTable.tsx b/pkg/webui/ui/src/components/ErrorsTable.tsx index f15abf73a..a7875b858 100644 --- a/pkg/webui/ui/src/components/ErrorsTable.tsx +++ b/pkg/webui/ui/src/components/ErrorsTable.tsx @@ -5,7 +5,6 @@ import TableCell from '@mui/material/TableCell'; import TableContainer from '@mui/material/TableContainer'; import TableHead from '@mui/material/TableHead'; import TableRow from '@mui/material/TableRow'; -import Paper from '@mui/material/Paper'; import { DeploymentError } from "../models"; import { Box, Divider, List, ListItem, ListItemText } from "@mui/material"; import { buildRefKindElement } from "../api"; @@ -13,7 +12,7 @@ import { buildRefKindElement } from "../api"; export function ErrorsTable(props: { errors: DeploymentError[] }) { return <> - +
    @@ -25,7 +24,7 @@ export function ErrorsTable(props: { errors: DeploymentError[] }) { {props.errors?.map((e, i) => ( - + {buildRefKindElement(e.ref, <> diff --git a/pkg/webui/ui/src/components/PropertiesTable.tsx b/pkg/webui/ui/src/components/PropertiesTable.tsx index 61237aefb..0f189fab6 100644 --- a/pkg/webui/ui/src/components/PropertiesTable.tsx +++ b/pkg/webui/ui/src/components/PropertiesTable.tsx @@ -5,11 +5,10 @@ import TableCell from '@mui/material/TableCell'; import TableContainer from '@mui/material/TableContainer'; import TableHead from '@mui/material/TableHead'; import TableRow from '@mui/material/TableRow'; -import Paper from '@mui/material/Paper'; export function PropertiesTable(props: {properties: {name: string, value: React.ReactNode}[]}) { return ( - +
    diff --git a/pkg/webui/ui/src/components/result-view/ChangesTable.tsx b/pkg/webui/ui/src/components/result-view/ChangesTable.tsx index b802201f3..0082446be 100644 --- a/pkg/webui/ui/src/components/result-view/ChangesTable.tsx +++ b/pkg/webui/ui/src/components/result-view/ChangesTable.tsx @@ -5,18 +5,16 @@ import TableCell from '@mui/material/TableCell'; import TableContainer from '@mui/material/TableContainer'; import TableHead from '@mui/material/TableHead'; import TableRow from '@mui/material/TableRow'; -import Paper from '@mui/material/Paper'; import { buildRefKindElement, buildRefString } from "../../api"; import { Box, Typography } from "@mui/material"; import { CodeViewer } from "../CodeViewer"; import { DiffStatus } from "./nodes/NodeData"; -import Divider from "@mui/material/Divider"; import { ObjectRef } from "../../models"; const RefList = (props: { title: string, refs: ObjectRef[] }) => { - return + return {props.title} - +
    @@ -48,7 +46,6 @@ const RefList = (props: { title: string, refs: ObjectRef[] }) => {
    -
    } @@ -56,15 +53,22 @@ export function ChangesTable(props: { diffStatus: DiffStatus }) { let changedObjects: React.ReactElement | undefined if (props.diffStatus.changedObjects.length) { - changedObjects = + changedObjects = Changed Objects {props.diffStatus.changedObjects.map((co, i) => ( - + - - {buildRefString(co.ref)} + + {buildRefString(co.ref)} @@ -81,25 +85,25 @@ export function ChangesTable(props: { diffStatus: DiffStatus }) { - + ))}
    - ))} - -
    + )) + } +
    } return {props.diffStatus.newObjects.length ? - : <>} + : <>} {props.diffStatus.deletedObjects.length ? - : <>} + : <>} {props.diffStatus.orphanObjects.length ? - : <>} + : <>} {changedObjects} } \ No newline at end of file diff --git a/pkg/webui/ui/src/components/result-view/SidePanel.tsx b/pkg/webui/ui/src/components/result-view/SidePanel.tsx index 9a2047a7c..bfb5c17b0 100644 --- a/pkg/webui/ui/src/components/result-view/SidePanel.tsx +++ b/pkg/webui/ui/src/components/result-view/SidePanel.tsx @@ -1,4 +1,4 @@ -import { Box, Divider, IconButton, Tab, ThemeProvider, Typography, useTheme } from "@mui/material"; +import { Box, Divider, IconButton, Paper, Tab, ThemeProvider, Typography, useTheme } from "@mui/material"; import React, { useEffect, useState } from "react"; import { TabContext, TabList, TabPanel } from "@mui/lab"; import { CloseIcon } from "../../icons/Icons"; @@ -59,7 +59,7 @@ export const SidePanel = (props: SidePanelProps) => { return <> } - return + return @@ -83,11 +83,13 @@ export const SidePanel = (props: SidePanelProps) => { - + {tabs.map((tab, index) => { - return - - {tab.content} + return + + + {tab.content} + })} diff --git a/pkg/webui/ui/src/components/targets-view/CommandResultDetailsDrawer.tsx b/pkg/webui/ui/src/components/targets-view/CommandResultDetailsDrawer.tsx index 298fbe66a..5820d9dab 100644 --- a/pkg/webui/ui/src/components/targets-view/CommandResultDetailsDrawer.tsx +++ b/pkg/webui/ui/src/components/targets-view/CommandResultDetailsDrawer.tsx @@ -4,8 +4,9 @@ import { NodeBuilder } from "../result-view/nodes/NodeBuilder"; import React, { Suspense, useEffect, useState } from "react"; import { NodeData } from "../result-view/nodes/NodeData"; import { SidePanel } from "../result-view/SidePanel"; -import { Box, Drawer } from "@mui/material"; +import { Box, Drawer, ThemeProvider } from "@mui/material"; import { Loading } from "../Loading"; +import { dark } from "../theme"; async function doGetRootNode(rs: CommandResultSummary) { const shortNames = api.getShortNames() @@ -34,21 +35,24 @@ export const CommandResultDetailsDrawer = (props: { rs?: CommandResultSummary, o setPromise(doGetRootNode(props.rs)) }, [props.rs]) - const Content = () => { + const Content = (props: { onClose: () => void }) => { const node = usePromise(promise) - return + return } - return props.onClose()} - > - - }> - - - - + return + + + }> + + + + + } \ No newline at end of file diff --git a/pkg/webui/ui/src/components/theme.ts b/pkg/webui/ui/src/components/theme.ts index c599bf1cd..86d328802 100644 --- a/pkg/webui/ui/src/components/theme.ts +++ b/pkg/webui/ui/src/components/theme.ts @@ -53,6 +53,13 @@ export const light = createTheme(common, { lineHeight: '27px', letterSpacing: '1px', }, + h5: { + color: paletteLight.secondary.main, + fontWeight: 700, + fontSize: '22px', + lineHeight: '30px', + letterSpacing: '1px', + }, h6: { color: paletteLight.secondary.main, fontWeight: 800, @@ -94,10 +101,13 @@ export const light = createTheme(common, { right: 0, bottom: '10px', display: 'block', - borderRight: '0.5px solid #39403E', + borderRight: `0.5px solid ${paletteLight.secondary.main}`, }, ':last-of-type:after': { content: 'none' + }, + ':last-of-type': { + overflowWrap: 'anywhere' } }, head: { @@ -114,7 +124,7 @@ export const dark = createTheme(common, { allVariants: { color: paletteDark.text.primary }, - h4: { + h4: { color: paletteDark.text.primary, fontWeight: 700, fontSize: '24px', @@ -180,7 +190,11 @@ export const dark = createTheme(common, { lineHeight: '22px', letterSpacing: '1px', textTransform: 'none', - padding: '7px 5px' + padding: '7px 5px', + color: '#8A8E91', + '&.Mui-selected': { + color: paletteDark.text.primary + } } } } From 333507ebad348f8aa111e5c7a18432f0d89e9181 Mon Sep 17 00:00:00 2001 From: Alexey Vagarenko Date: Thu, 1 Jun 2023 03:25:52 +0600 Subject: [PATCH 1781/2916] Main Page: replaced all icons. --- .../result-view/CommandResultStatusLine.tsx | 14 +++--- .../targets-view/CommandResultItem.tsx | 17 ++++--- pkg/webui/ui/src/icons/Icons.tsx | 47 ++++++++++++++++++- pkg/webui/ui/src/icons/added.svg | 4 ++ pkg/webui/ui/src/icons/changed.svg | 5 ++ pkg/webui/ui/src/icons/deploy.svg | 8 ++++ pkg/webui/ui/src/icons/diff.svg | 8 ++++ pkg/webui/ui/src/icons/error.svg | 4 ++ pkg/webui/ui/src/icons/orphan.svg | 6 +++ pkg/webui/ui/src/icons/prune.svg | 8 ++++ pkg/webui/ui/src/icons/trash.svg | 6 +++ pkg/webui/ui/src/icons/warning.svg | 5 ++ 12 files changed, 115 insertions(+), 17 deletions(-) create mode 100644 pkg/webui/ui/src/icons/added.svg create mode 100644 pkg/webui/ui/src/icons/changed.svg create mode 100644 pkg/webui/ui/src/icons/deploy.svg create mode 100644 pkg/webui/ui/src/icons/diff.svg create mode 100644 pkg/webui/ui/src/icons/error.svg create mode 100644 pkg/webui/ui/src/icons/orphan.svg create mode 100644 pkg/webui/ui/src/icons/prune.svg create mode 100644 pkg/webui/ui/src/icons/trash.svg create mode 100644 pkg/webui/ui/src/icons/warning.svg diff --git a/pkg/webui/ui/src/components/result-view/CommandResultStatusLine.tsx b/pkg/webui/ui/src/components/result-view/CommandResultStatusLine.tsx index 11b9721d6..4794f31ae 100644 --- a/pkg/webui/ui/src/components/result-view/CommandResultStatusLine.tsx +++ b/pkg/webui/ui/src/components/result-view/CommandResultStatusLine.tsx @@ -1,7 +1,7 @@ import { CommandResultSummary, ValidateResult } from "../../models"; import { Box, Tooltip, Typography } from "@mui/material"; -import { Dangerous, Delete, Difference, LibraryAdd, LinkOff, Warning } from "@mui/icons-material"; import React from "react"; +import { AddedIcon, ChangedIcon, ErrorIcon, OrphanIcon, TrashIcon, WarningIcon } from "../../icons/Icons"; export interface StatusLineProps { errors?: number @@ -36,12 +36,12 @@ export const StatusLine = (props: StatusLineProps) => { } } - doPush(props.errors, "total errors.", ) - doPush(props.warnings, "total warnings.", ) - doPush(props.newObjects, "new objects.", ) - doPush(props.deletedObjects, "deleted objects.", ) - doPush(props.orphanObjects, "orphan objects.", ) - doPush(props.changedObjects, "changed objects.", ) + doPush(props.errors, "total errors.", ) + doPush(props.warnings, "total warnings.", ) + doPush(props.newObjects, "new objects.", ) + doPush(props.deletedObjects, "deleted objects.", ) + doPush(props.orphanObjects, "orphan objects.", ) + doPush(props.changedObjects, "changed objects.", ) return {children} diff --git a/pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx b/pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx index 3c5a1a5d2..3e305c9b2 100644 --- a/pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx +++ b/pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx @@ -1,5 +1,4 @@ import { CommandResultSummary, ProjectSummary, TargetSummary } from "../../models"; -import { CleaningServices, CloudSync, Delete, Difference } from "@mui/icons-material"; import { useEffect, useMemo, useState } from "react"; import * as yaml from "js-yaml"; import { CodeViewer } from "../CodeViewer"; @@ -8,7 +7,7 @@ import { Box, IconButton, Tooltip, Typography } from "@mui/material"; import { CommandResultStatusLine } from "../result-view/CommandResultStatusLine"; import { useNavigate } from "react-router"; import { formatDurationShort } from "../../utils/duration"; -import { TreeViewIcon } from "../../icons/Icons"; +import { DeployIcon, DiffIcon, PruneIcon, TreeViewIcon } from "../../icons/Icons"; export const CommandResultItem = (props: { ps: ProjectSummary, ts: TargetSummary, rs: CommandResultSummary, onSelectCommandResult: (rs?: CommandResultSummary) => void }) => { const calcAgo = () => { @@ -21,22 +20,22 @@ export const CommandResultItem = (props: { ps: ProjectSummary, ts: TargetSummary const navigate = useNavigate() const [ago, setAgo] = useState(calcAgo()) - let Icon = Difference + let Icon: () => JSX.Element = DiffIcon switch (props.rs.commandInfo?.command) { case "delete": - Icon = Delete + Icon = PruneIcon break case "deploy": - Icon = CloudSync + Icon = DeployIcon break case "diff": - Icon = Difference + Icon = DiffIcon break case "poke-images": - Icon = CloudSync + Icon = DeployIcon break case "prune": - Icon = CleaningServices + Icon = PruneIcon break } @@ -66,7 +65,7 @@ export const CommandResultItem = (props: { ps: ProjectSummary, ts: TargetSummary - + diff --git a/pkg/webui/ui/src/icons/Icons.tsx b/pkg/webui/ui/src/icons/Icons.tsx index 156c0233f..34c9fca56 100644 --- a/pkg/webui/ui/src/icons/Icons.tsx +++ b/pkg/webui/ui/src/icons/Icons.tsx @@ -10,6 +10,15 @@ import { ReactComponent as CpuIconSvg } from './cpu.svg'; import { ReactComponent as FingerScanIconSvg } from './finger-scan.svg'; import { ReactComponent as TreeViewIconSvg } from './tree-view.svg'; import { ReactComponent as CloseIconSvg } from './close.svg'; +import { ReactComponent as DeployIconSvg } from './deploy.svg'; +import { ReactComponent as PruneIconSvg } from './prune.svg'; +import { ReactComponent as DiffIconSvg } from './diff.svg'; +import { ReactComponent as WarningIconSvg } from './warning.svg'; +import { ReactComponent as ErrorIconSvg } from './error.svg'; +import { ReactComponent as TrashIconSvg } from './trash.svg'; +import { ReactComponent as OrphanIconSvg } from './orphan.svg'; +import { ReactComponent as AddedIconSvg } from './added.svg'; +import { ReactComponent as ChangedIconSvg } from './changed.svg'; export const KluctlText = () => { return @@ -42,6 +51,18 @@ export const ProjectIcon = () => { return } +export const DeployIcon = () => { + return +} + +export const PruneIcon = () => { + return +} + +export const DiffIcon = () => { + return +} + export const CpuIcon = () => { return } @@ -66,4 +87,28 @@ export const TreeViewIcon = () => { export const CloseIcon = () => { return -} \ No newline at end of file +} + +export const WarningIcon = () => { + return +} + +export const ErrorIcon = () => { + return +} + +export const TrashIcon = () => { + return +} + +export const OrphanIcon = () => { + return +} + +export const AddedIcon = () => { + return +} + +export const ChangedIcon = () => { + return +} diff --git a/pkg/webui/ui/src/icons/added.svg b/pkg/webui/ui/src/icons/added.svg new file mode 100644 index 000000000..1086507eb --- /dev/null +++ b/pkg/webui/ui/src/icons/added.svg @@ -0,0 +1,4 @@ + + + + diff --git a/pkg/webui/ui/src/icons/changed.svg b/pkg/webui/ui/src/icons/changed.svg new file mode 100644 index 000000000..5ee26bbe6 --- /dev/null +++ b/pkg/webui/ui/src/icons/changed.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/pkg/webui/ui/src/icons/deploy.svg b/pkg/webui/ui/src/icons/deploy.svg new file mode 100644 index 000000000..a3be467e3 --- /dev/null +++ b/pkg/webui/ui/src/icons/deploy.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/pkg/webui/ui/src/icons/diff.svg b/pkg/webui/ui/src/icons/diff.svg new file mode 100644 index 000000000..ad1b92774 --- /dev/null +++ b/pkg/webui/ui/src/icons/diff.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/pkg/webui/ui/src/icons/error.svg b/pkg/webui/ui/src/icons/error.svg new file mode 100644 index 000000000..2f9a8cd4b --- /dev/null +++ b/pkg/webui/ui/src/icons/error.svg @@ -0,0 +1,4 @@ + + + + diff --git a/pkg/webui/ui/src/icons/orphan.svg b/pkg/webui/ui/src/icons/orphan.svg new file mode 100644 index 000000000..43e777661 --- /dev/null +++ b/pkg/webui/ui/src/icons/orphan.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/pkg/webui/ui/src/icons/prune.svg b/pkg/webui/ui/src/icons/prune.svg new file mode 100644 index 000000000..7c4784206 --- /dev/null +++ b/pkg/webui/ui/src/icons/prune.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/pkg/webui/ui/src/icons/trash.svg b/pkg/webui/ui/src/icons/trash.svg new file mode 100644 index 000000000..426eec953 --- /dev/null +++ b/pkg/webui/ui/src/icons/trash.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/pkg/webui/ui/src/icons/warning.svg b/pkg/webui/ui/src/icons/warning.svg new file mode 100644 index 000000000..788515486 --- /dev/null +++ b/pkg/webui/ui/src/icons/warning.svg @@ -0,0 +1,5 @@ + + + + + From 378b76bdecb62e3477a190ff9aa91fc74da75bdb Mon Sep 17 00:00:00 2001 From: Alexey Vagarenko Date: Thu, 1 Jun 2023 21:22:17 +0600 Subject: [PATCH 1782/2916] Updated left drawer styles. --- pkg/webui/ui/src/components/LeftDrawer.tsx | 29 +++++++++++++++------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/pkg/webui/ui/src/components/LeftDrawer.tsx b/pkg/webui/ui/src/components/LeftDrawer.tsx index a5bd4e5fe..85df4d530 100644 --- a/pkg/webui/ui/src/components/LeftDrawer.tsx +++ b/pkg/webui/ui/src/components/LeftDrawer.tsx @@ -100,24 +100,36 @@ const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => prop !== 'open' ); function Item(props: { text: string, open: boolean, icon: React.ReactNode, to: string }) { - return + return {props.icon} - + } @@ -180,9 +192,8 @@ export default function LeftDrawer(props: { content: React.ReactNode, context: A - + } to={"targets"} /> - {context.filters} {context.filters && } @@ -190,7 +201,7 @@ export default function LeftDrawer(props: { content: React.ReactNode, context: A - + {props.content} From ecdf0c49a94fabe31d646661635dfd7c09dee8f2 Mon Sep 17 00:00:00 2001 From: Alexey Vagarenko Date: Thu, 1 Jun 2023 22:00:50 +0600 Subject: [PATCH 1783/2916] Fix tooltips for icons of Command result nodes. --- .../ui/src/components/targets-view/CommandResultItem.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx b/pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx index 3e305c9b2..8812d8181 100644 --- a/pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx +++ b/pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx @@ -63,11 +63,11 @@ export const CommandResultItem = (props: { ps: ProjectSummary, ts: TargetSummary > - - + + - - + + Date: Fri, 2 Jun 2023 01:26:42 +0600 Subject: [PATCH 1784/2916] Result tree page: added filters. --- pkg/webui/ui/src/components/App.tsx | 9 +- pkg/webui/ui/src/components/LeftDrawer.tsx | 96 ++++++++------ .../result-view/CommandResultView.tsx | 123 +++++++++++++++--- pkg/webui/ui/src/icons/Icons.tsx | 35 +++++ pkg/webui/ui/src/icons/arrow-left.svg | 4 + pkg/webui/ui/src/icons/changes.svg | 5 + pkg/webui/ui/src/icons/checkbox-checked.svg | 5 + pkg/webui/ui/src/icons/checkbox-disabled.svg | 4 + pkg/webui/ui/src/icons/checkbox.svg | 3 + pkg/webui/ui/src/icons/star.svg | 4 + pkg/webui/ui/src/icons/warning-sign.svg | 5 + 11 files changed, 236 insertions(+), 57 deletions(-) create mode 100644 pkg/webui/ui/src/icons/arrow-left.svg create mode 100644 pkg/webui/ui/src/icons/changes.svg create mode 100644 pkg/webui/ui/src/icons/checkbox-checked.svg create mode 100644 pkg/webui/ui/src/icons/checkbox-disabled.svg create mode 100644 pkg/webui/ui/src/icons/checkbox.svg create mode 100644 pkg/webui/ui/src/icons/star.svg create mode 100644 pkg/webui/ui/src/icons/warning-sign.svg diff --git a/pkg/webui/ui/src/components/App.tsx b/pkg/webui/ui/src/components/App.tsx index ba36ddc4a..7c326111b 100644 --- a/pkg/webui/ui/src/components/App.tsx +++ b/pkg/webui/ui/src/components/App.tsx @@ -1,14 +1,15 @@ -import React, { useState } from 'react'; +import React, { Dispatch, SetStateAction, useState } from 'react'; import '../index.css'; import { Box, ThemeProvider } from "@mui/material"; import { Outlet, useOutletContext } from "react-router-dom"; import LeftDrawer from "./LeftDrawer"; import { light } from './theme'; +import { ActiveFilters } from './result-view/NodeStatusFilter'; export interface AppOutletContext { - filters?: React.ReactNode - setFilters: (filter: React.ReactNode) => void + filters?: ActiveFilters + setFilters: Dispatch> } export function useAppOutletContext(): AppOutletContext { @@ -16,7 +17,7 @@ export function useAppOutletContext(): AppOutletContext { } const App = () => { - const [filters, setFilters] = useState() + const [filters, setFilters] = useState() const context: AppOutletContext = { filters: filters, diff --git a/pkg/webui/ui/src/components/LeftDrawer.tsx b/pkg/webui/ui/src/components/LeftDrawer.tsx index 85df4d530..62cacf4a5 100644 --- a/pkg/webui/ui/src/components/LeftDrawer.tsx +++ b/pkg/webui/ui/src/components/LeftDrawer.tsx @@ -11,9 +11,9 @@ import ListItem from '@mui/material/ListItem'; import ListItemButton from '@mui/material/ListItemButton'; import ListItemIcon from '@mui/material/ListItemIcon'; import ListItemText from '@mui/material/ListItemText'; -import { Link } from "react-router-dom"; +import { Link, useLocation, useNavigate } from "react-router-dom"; import { AppOutletContext } from "./App"; -import { KluctlLogo, TargetsIcon, KluctlText, SearchIcon } from '../icons/Icons'; +import { KluctlLogo, TargetsIcon, KluctlText, SearchIcon, ArrowLeftIcon } from '../icons/Icons'; import { dark } from './theme'; import { Typography } from '@mui/material'; @@ -135,50 +135,74 @@ function Item(props: { text: string, open: boolean, icon: React.ReactNode, to: s } export default function LeftDrawer(props: { content: React.ReactNode, context: AppOutletContext }) { - const context = props.context - const [open, setOpen] = useState(true); - + const location = useLocation() + const navigate = useNavigate(); const theme = useTheme(); const toggleDrawer = () => { setOpen(o => !o); }; + const path = location.pathname.split('/')[1]; + let header: JSX.Element | null = null; + switch (path) { + case 'targets': + header = <> + + Dashboard + + + + + + + + + break; + case 'results': + header = + navigate('/targets')}> + + + + Result Tree + + + break; + } + return ( - - Dashboard - - - - - - - + {header} @@ -194,9 +218,7 @@ export default function LeftDrawer(props: { content: React.ReactNode, context: A } to={"targets"} /> - {context.filters} - {context.filters && } diff --git a/pkg/webui/ui/src/components/result-view/CommandResultView.tsx b/pkg/webui/ui/src/components/result-view/CommandResultView.tsx index 607bc925b..a09a061f5 100644 --- a/pkg/webui/ui/src/components/result-view/CommandResultView.tsx +++ b/pkg/webui/ui/src/components/result-view/CommandResultView.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { useEffect, useState } from 'react'; -import { Box, Divider } from "@mui/material"; +import { Box, Checkbox, CheckboxProps, Divider, FormControlLabel, FormLabel, Typography } from "@mui/material"; import { CommandResult, CommandResultSummary, ShortName } from "../../models"; import { NodeData } from "./nodes/NodeData"; import { SidePanel } from "./SidePanel"; @@ -9,6 +9,7 @@ import CommandResultTree from "./CommandResultTree"; import { useLoaderData } from "react-router-dom"; import { api } from "../../api"; import { useAppOutletContext } from "../App"; +import { ChangesIcon, CheckboxCheckedIcon, CheckboxIcon, StarIcon, WarningSignIcon } from '../../icons/Icons'; export interface CommandResultProps { shortNames: ShortName[] @@ -28,26 +29,116 @@ export async function commandResultLoader({ params }: any) { } } +const FilterCheckbox = (props: { + text: string, + checked: boolean, + Icon: () => JSX.Element, + onChange: CheckboxProps['onChange'] +}) => { + const { text, checked, Icon, onChange } = props; + return } + checkedIcon={} + /> + } + slotProps={{ + typography: { + sx: { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + gap: '10px', + } + } + }} + label={ + <> + {text} + + + + + } + /> +} + +const defaultFilters = { + onlyImportant: false, + onlyChanged: false, + onlyWithErrorsOrWarnings: false +} + export const CommandResultView = () => { - const context = useAppOutletContext() - const commandResultProps = useLoaderData() as CommandResultProps - const [activeFilters, setActiveFilters] = useState() - const [sidePanelNode, setSidePanelNode] = useState() - - useEffect(() => { - context.setFilters(<> - - ) - }, []) - - return + const context = useAppOutletContext(); + const commandResultProps = useLoaderData() as CommandResultProps; + const [sidePanelNode, setSidePanelNode] = useState(); + + const divider = ; + + const handleFilterChange = (filter: keyof ActiveFilters) => (_: React.ChangeEvent, checked: boolean) => { + context.setFilters(fs => ({ + ...(fs || defaultFilters), + [filter]: checked + })); + } + + return + + + {divider} + + {divider} + + + + activeFilters={context.filters} /> - - + } diff --git a/pkg/webui/ui/src/icons/Icons.tsx b/pkg/webui/ui/src/icons/Icons.tsx index 34c9fca56..19227081e 100644 --- a/pkg/webui/ui/src/icons/Icons.tsx +++ b/pkg/webui/ui/src/icons/Icons.tsx @@ -19,6 +19,13 @@ import { ReactComponent as TrashIconSvg } from './trash.svg'; import { ReactComponent as OrphanIconSvg } from './orphan.svg'; import { ReactComponent as AddedIconSvg } from './added.svg'; import { ReactComponent as ChangedIconSvg } from './changed.svg'; +import { ReactComponent as CheckboxIconSvg } from './checkbox.svg'; +import { ReactComponent as CheckboxCheckedIconSvg } from './checkbox-checked.svg'; +import { ReactComponent as CheckboxDisabledIconSvg } from './checkbox-disabled.svg'; +import { ReactComponent as ArrowLeftIconSvg } from './arrow-left.svg'; +import { ReactComponent as WarningSignIconSvg } from './warning-sign.svg'; +import { ReactComponent as ChangesIconSvg } from './changes.svg'; +import { ReactComponent as StarIconSvg } from './star.svg'; export const KluctlText = () => { return @@ -112,3 +119,31 @@ export const AddedIcon = () => { export const ChangedIcon = () => { return } + +export const CheckboxIcon = () => { + return +} + +export const CheckboxCheckedIcon = () => { + return +} + +export const CheckboxDisabledIcon = () => { + return +} + +export const ArrowLeftIcon = () => { + return +} + +export const WarningSignIcon = () => { + return +} + +export const ChangesIcon = () => { + return +} + +export const StarIcon = () => { + return +} diff --git a/pkg/webui/ui/src/icons/arrow-left.svg b/pkg/webui/ui/src/icons/arrow-left.svg new file mode 100644 index 000000000..5f8ccdd11 --- /dev/null +++ b/pkg/webui/ui/src/icons/arrow-left.svg @@ -0,0 +1,4 @@ + + + + diff --git a/pkg/webui/ui/src/icons/changes.svg b/pkg/webui/ui/src/icons/changes.svg new file mode 100644 index 000000000..fe25e1e1f --- /dev/null +++ b/pkg/webui/ui/src/icons/changes.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/pkg/webui/ui/src/icons/checkbox-checked.svg b/pkg/webui/ui/src/icons/checkbox-checked.svg new file mode 100644 index 000000000..f9e6a1557 --- /dev/null +++ b/pkg/webui/ui/src/icons/checkbox-checked.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/pkg/webui/ui/src/icons/checkbox-disabled.svg b/pkg/webui/ui/src/icons/checkbox-disabled.svg new file mode 100644 index 000000000..9922728ac --- /dev/null +++ b/pkg/webui/ui/src/icons/checkbox-disabled.svg @@ -0,0 +1,4 @@ + + + + diff --git a/pkg/webui/ui/src/icons/checkbox.svg b/pkg/webui/ui/src/icons/checkbox.svg new file mode 100644 index 000000000..f9f5f7b78 --- /dev/null +++ b/pkg/webui/ui/src/icons/checkbox.svg @@ -0,0 +1,3 @@ + + + diff --git a/pkg/webui/ui/src/icons/star.svg b/pkg/webui/ui/src/icons/star.svg new file mode 100644 index 000000000..0fda929ba --- /dev/null +++ b/pkg/webui/ui/src/icons/star.svg @@ -0,0 +1,4 @@ + + + + diff --git a/pkg/webui/ui/src/icons/warning-sign.svg b/pkg/webui/ui/src/icons/warning-sign.svg new file mode 100644 index 000000000..e38fa71b2 --- /dev/null +++ b/pkg/webui/ui/src/icons/warning-sign.svg @@ -0,0 +1,5 @@ + + + + + From 97975b31430abc2cee5021fe6c21d492ef81b031 Mon Sep 17 00:00:00 2001 From: Alexey Vagarenko Date: Fri, 2 Jun 2023 05:08:05 +0600 Subject: [PATCH 1785/2916] WIP Result tree page. --- .../result-view/CommandResultTree.tsx | 70 +++++++++++++++---- .../result-view/CommandResultView.tsx | 29 +++++--- pkg/webui/ui/src/components/theme.ts | 2 +- 3 files changed, 78 insertions(+), 23 deletions(-) diff --git a/pkg/webui/ui/src/components/result-view/CommandResultTree.tsx b/pkg/webui/ui/src/components/result-view/CommandResultTree.tsx index d1986046a..f9c00216d 100644 --- a/pkg/webui/ui/src/components/result-view/CommandResultTree.tsx +++ b/pkg/webui/ui/src/components/result-view/CommandResultTree.tsx @@ -9,6 +9,7 @@ import { NodeData } from "./nodes/NodeData"; import { ActiveFilters, FilterNode } from "./NodeStatusFilter"; import { CommandResultProps } from "./CommandResultView"; import { Loading } from "../Loading"; +import { Box, Divider, Paper, useTheme } from '@mui/material'; export interface CommandResultTreeProps { commandResultProps?: CommandResultProps @@ -18,6 +19,7 @@ export interface CommandResultTreeProps { } const CommandResultTree = (props: CommandResultTreeProps) => { + const theme = useTheme(); const [expanded, setExpanded] = useState(["root"]); const [selectedNodeId, setSelectedNodeId] = useState() @@ -67,8 +69,50 @@ const CommandResultTree = (props: CommandResultTreeProps) => { return handleItemClick(e, nodes)}>{nodes.buildTreeItem()}} - sx={{ marginBottom: "5px", marginTop: "5px" }} + label={ + handleItemClick(e, nodes)} + pl='22px' + position='relative' + > + {nodes.children.length !== 0 && + + } + {nodes.buildTreeItem()} + + } + sx={{ + '& .MuiTreeItem-content': { + height: '78px', + borderBottom: `0.5px solid ${theme.palette.secondary.main}`, + padding: 0, + '& .MuiTreeItem-iconContainer': { + width: '50px', + height: '50px', + margin: 0, + padding: 0, + display: nodes.children.length !== 0 ? 'flex' : 'none', + justifyContent: 'center', + alignItems: 'center', + }, + '& .MuiTreeItem-label': { + margin: 0, + padding: 0 + } + }, + '& .MuiTreeItem-group': { + margin: '0 0 0 38px' + }, + }} onDoubleClick={(e: React.SyntheticEvent) => handleDoubleClick(e, nodes)} > {Array.isArray(nodes.children) @@ -78,18 +122,20 @@ const CommandResultTree = (props: CommandResultTreeProps) => { }; if (!rootNode) { - return + return } - return } - defaultExpandIcon={} - sx={{ width: "100%" }} - > - {renderTree(rootNode)} - + return + } + defaultExpandIcon={} + sx={{ width: "100%" }} + > + {renderTree(rootNode)} + + } export default CommandResultTree; diff --git a/pkg/webui/ui/src/components/result-view/CommandResultView.tsx b/pkg/webui/ui/src/components/result-view/CommandResultView.tsx index a09a061f5..b9186b71e 100644 --- a/pkg/webui/ui/src/components/result-view/CommandResultView.tsx +++ b/pkg/webui/ui/src/components/result-view/CommandResultView.tsx @@ -1,10 +1,10 @@ import * as React from 'react'; -import { useEffect, useState } from 'react'; -import { Box, Checkbox, CheckboxProps, Divider, FormControlLabel, FormLabel, Typography } from "@mui/material"; +import { useState } from 'react'; +import { Box, Checkbox, CheckboxProps, Divider, FormControlLabel, Typography } from "@mui/material"; import { CommandResult, CommandResultSummary, ShortName } from "../../models"; import { NodeData } from "./nodes/NodeData"; import { SidePanel } from "./SidePanel"; -import { ActiveFilters, NodeStatusFilter } from "./NodeStatusFilter"; +import { ActiveFilters } from "./NodeStatusFilter"; import CommandResultTree from "./CommandResultTree"; import { useLoaderData } from "react-router-dom"; import { api } from "../../api"; @@ -109,8 +109,14 @@ export const CommandResultView = () => { })); } - return - + return + { onChange={handleFilterChange('onlyWithErrorsOrWarnings')} /> - - - + + + - + diff --git a/pkg/webui/ui/src/components/theme.ts b/pkg/webui/ui/src/components/theme.ts index 86d328802..14680cd0a 100644 --- a/pkg/webui/ui/src/components/theme.ts +++ b/pkg/webui/ui/src/components/theme.ts @@ -73,7 +73,7 @@ export const light = createTheme(common, { MuiDivider: { styleOverrides: { root: { - borderColor: 'rgba(0, 0, 0, 0.5)' + borderColor: paletteLight.secondary.main, } } }, From 876f49e34c60ae317c62629c5349e24c06bfaa78 Mon Sep 17 00:00:00 2001 From: Alexey Vagarenko Date: Sat, 3 Jun 2023 02:19:11 +0600 Subject: [PATCH 1786/2916] Result tree styling. --- .../result-view/CommandResultTree.tsx | 19 ++- .../result-view/nodes/CommandResultNode.tsx | 4 +- .../nodes/DeploymentItemIncludeNode.tsx | 5 +- .../components/result-view/nodes/NodeData.tsx | 108 ++++++++++++++---- .../result-view/nodes/ObjectNode.tsx | 38 +++--- .../nodes/VarsSourceCollectionNode.tsx | 4 +- .../result-view/nodes/VarsSourceNode.tsx | 6 +- pkg/webui/ui/src/icons/Icons.tsx | 35 ++++++ pkg/webui/ui/src/icons/brackets-curly.svg | 3 + pkg/webui/ui/src/icons/brackets-square.svg | 3 + pkg/webui/ui/src/icons/file.svg | 5 + pkg/webui/ui/src/icons/include.svg | 7 ++ pkg/webui/ui/src/icons/result.svg | 8 ++ pkg/webui/ui/src/icons/triangle-down.svg | 4 + pkg/webui/ui/src/icons/triangle-right.svg | 4 + 15 files changed, 194 insertions(+), 59 deletions(-) create mode 100644 pkg/webui/ui/src/icons/brackets-curly.svg create mode 100644 pkg/webui/ui/src/icons/brackets-square.svg create mode 100644 pkg/webui/ui/src/icons/file.svg create mode 100644 pkg/webui/ui/src/icons/include.svg create mode 100644 pkg/webui/ui/src/icons/result.svg create mode 100644 pkg/webui/ui/src/icons/triangle-down.svg create mode 100644 pkg/webui/ui/src/icons/triangle-right.svg diff --git a/pkg/webui/ui/src/components/result-view/CommandResultTree.tsx b/pkg/webui/ui/src/components/result-view/CommandResultTree.tsx index f9c00216d..56c3174b7 100644 --- a/pkg/webui/ui/src/components/result-view/CommandResultTree.tsx +++ b/pkg/webui/ui/src/components/result-view/CommandResultTree.tsx @@ -2,14 +2,13 @@ import * as React from 'react'; import { useEffect, useMemo, useState } from 'react'; import TreeView from '@mui/lab/TreeView'; import { TreeItem } from "@mui/lab"; -import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; -import ChevronRightIcon from '@mui/icons-material/ChevronRight'; import { NodeBuilder } from "./nodes/NodeBuilder"; import { NodeData } from "./nodes/NodeData"; import { ActiveFilters, FilterNode } from "./NodeStatusFilter"; import { CommandResultProps } from "./CommandResultView"; import { Loading } from "../Loading"; import { Box, Divider, Paper, useTheme } from '@mui/material'; +import { TriangleDownIcon, TriangleRightIcon } from '../../icons/Icons'; export interface CommandResultTreeProps { commandResultProps?: CommandResultProps @@ -75,6 +74,8 @@ const CommandResultTree = (props: CommandResultTreeProps) => { alignItems='center' onClick={(e: React.SyntheticEvent) => handleItemClick(e, nodes)} pl='22px' + height='100%' + flex='1 1 auto' position='relative' > {nodes.children.length !== 0 && @@ -87,7 +88,7 @@ const CommandResultTree = (props: CommandResultTreeProps) => { }} /> } - {nodes.buildTreeItem()} + {nodes.buildTreeItem(nodes.children.length !== 0)} } sx={{ @@ -95,9 +96,11 @@ const CommandResultTree = (props: CommandResultTreeProps) => { height: '78px', borderBottom: `0.5px solid ${theme.palette.secondary.main}`, padding: 0, + overflow: 'hidden', '& .MuiTreeItem-iconContainer': { width: '50px', height: '50px', + flex: '0 0 auto', margin: 0, padding: 0, display: nodes.children.length !== 0 ? 'flex' : 'none', @@ -105,8 +108,12 @@ const CommandResultTree = (props: CommandResultTreeProps) => { alignItems: 'center', }, '& .MuiTreeItem-label': { + height: '100%', margin: 0, - padding: 0 + padding: 0, + flex: '1 1 auto', + display: 'flex', + alignItems: 'center' } }, '& .MuiTreeItem-group': { @@ -129,8 +136,8 @@ const CommandResultTree = (props: CommandResultTreeProps) => { } - defaultExpandIcon={} + defaultCollapseIcon={} + defaultExpandIcon={} sx={{ width: "100%" }} > {renderTree(rootNode)} diff --git a/pkg/webui/ui/src/components/result-view/nodes/CommandResultNode.tsx b/pkg/webui/ui/src/components/result-view/nodes/CommandResultNode.tsx index 170b653af..9e5e37266 100644 --- a/pkg/webui/ui/src/components/result-view/nodes/CommandResultNode.tsx +++ b/pkg/webui/ui/src/components/result-view/nodes/CommandResultNode.tsx @@ -1,13 +1,13 @@ import React from 'react'; import { NodeData } from "./NodeData"; -import { CloudSync } from "@mui/icons-material"; import { PropertiesTable } from "../../PropertiesTable"; import { CodeViewer } from "../../CodeViewer"; import { CommandResultProps } from "../CommandResultView"; import * as yaml from 'js-yaml'; import { SidePanelTab } from "../SidePanel"; +import { DeployIcon } from '../../../icons/Icons'; export class CommandResultNodeData extends NodeData { dumpedTargetYaml?: string @@ -25,7 +25,7 @@ export class CommandResultNodeData extends NodeData { } buildIcon(): [React.ReactNode, string] { - return [, "result"] + return [, "result"] } buildSidePanelTabs(): SidePanelTab[] { diff --git a/pkg/webui/ui/src/components/result-view/nodes/DeploymentItemIncludeNode.tsx b/pkg/webui/ui/src/components/result-view/nodes/DeploymentItemIncludeNode.tsx index 1d33bf8b6..096917f17 100644 --- a/pkg/webui/ui/src/components/result-view/nodes/DeploymentItemIncludeNode.tsx +++ b/pkg/webui/ui/src/components/result-view/nodes/DeploymentItemIncludeNode.tsx @@ -2,8 +2,7 @@ import React from 'react'; import { DeploymentItemConfig, DeploymentProjectConfig } from "../../../models"; import { NodeData } from "./NodeData"; -import { FolderZip } from "@mui/icons-material"; -import { GitIcon } from "../../../icons/Icons"; +import { GitIcon, IncludeIcon } from "../../../icons/Icons"; import { CommandResultProps } from "../CommandResultView"; import { PropertiesTable } from "../../PropertiesTable"; import { buildDeploymentItemSummaryProps } from "./DeploymentItemNode"; @@ -39,7 +38,7 @@ export class DeploymentItemIncludeNodeData extends NodeData { if (this.deploymentItem.git) { return [, "git"] } - return [, "include"] + return [, "include"] } buildSidePanelTabs(): SidePanelTab[] { diff --git a/pkg/webui/ui/src/components/result-view/nodes/NodeData.tsx b/pkg/webui/ui/src/components/result-view/nodes/NodeData.tsx index 55c3d66d4..771c5fdce 100644 --- a/pkg/webui/ui/src/components/result-view/nodes/NodeData.tsx +++ b/pkg/webui/ui/src/components/result-view/nodes/NodeData.tsx @@ -1,6 +1,6 @@ import { ChangedObject, DeploymentError, ObjectRef, ResultObject } from "../../../models"; import React from "react"; -import { Box, Typography } from "@mui/material"; +import { Box, Divider, Typography } from "@mui/material"; import { CommandResultProps } from "../CommandResultView"; import { ChangesTable } from "../ChangesTable"; import { ErrorsTable } from "../../ErrorsTable"; @@ -123,26 +123,86 @@ export abstract class NodeData implements SidePanelProvider { /> } - buildTreeItem(): React.ReactNode { - const [icon, iconText] = this.buildIcon() - - return - - - - {icon} - {iconText} - - - - - {this.buildSidePanelTitle()}
    -
    - - {this.buildStatusLine()} - -
    + buildTreeItem(hasChildren?: boolean): React.ReactNode { + const [icon, iconText] = this.buildIcon(); + + const hasStatusLine = [ + this.healthStatus?.errors.length, + this.healthStatus?.warnings.length, + this.diffStatus?.changedObjects.length, + this.diffStatus?.newObjects.length, + this.diffStatus?.deletedObjects.length, + this.diffStatus?.orphanObjects.length, + ].some(x => (x || 0) > 0); + + return + + {icon} + + {iconText} + + + + {this.buildSidePanelTitle()} + + + {hasStatusLine && + + {this.buildStatusLine()} + } } @@ -153,7 +213,7 @@ export abstract class NodeData implements SidePanelProvider { } buildObjectPage(ref: ObjectRef, objectType: ObjectType): React.ReactNode { - return + return } buildChangesPage(tabs: { label: string, content: React.ReactNode }[]) { @@ -162,7 +222,7 @@ export abstract class NodeData implements SidePanelProvider { } tabs.push({ label: "Changes", - content: + content: }) } @@ -172,7 +232,7 @@ export abstract class NodeData implements SidePanelProvider { } tabs.push({ label: "Errors", - content: + content: }) } @@ -182,7 +242,7 @@ export abstract class NodeData implements SidePanelProvider { } tabs.push({ label: "Warnings", - content: + content: }) } } diff --git a/pkg/webui/ui/src/components/result-view/nodes/ObjectNode.tsx b/pkg/webui/ui/src/components/result-view/nodes/ObjectNode.tsx index 62a5b133f..ec851d53f 100644 --- a/pkg/webui/ui/src/components/result-view/nodes/ObjectNode.tsx +++ b/pkg/webui/ui/src/components/result-view/nodes/ObjectNode.tsx @@ -3,7 +3,6 @@ import React from 'react'; import { ObjectRef } from "../../../models"; import { NodeData } from "./NodeData"; import { - DataObject, PublishedWithChanges, Settings, SettingsEthernet, @@ -14,12 +13,13 @@ import { PropertiesTable } from "../../PropertiesTable"; import { findObjectByRef, ObjectType } from "../../../api"; import { CommandResultProps } from "../CommandResultView"; import { SidePanelTab } from "../SidePanel"; +import { BracketsCurlyIcon } from '../../../icons/Icons'; -const kindMapping: {[key: string]: {icon: SvgIconComponent}} = { - "/ConfigMap": {icon: Settings}, - "apps/Deployment": {icon: PublishedWithChanges}, - "Service": {icon: SettingsEthernet}, - "ServiceAccount": {icon: SmartToy} +const kindMapping: { [key: string]: { icon: SvgIconComponent } } = { + "/ConfigMap": { icon: Settings }, + "apps/Deployment": { icon: PublishedWithChanges }, + "Service": { icon: SettingsEthernet }, + "ServiceAccount": { icon: SmartToy } } export class ObjectNodeData extends NodeData { @@ -40,15 +40,15 @@ export class ObjectNodeData extends NodeData { const m = kindMapping[this.objectRef.group + "/" + this.objectRef.kind] if (m !== undefined) { - return [React.createElement(m.icon, {fontSize: "large"}), snStr] + return [React.createElement(m.icon, { fontSize: "large" }), snStr] } - return [, snStr] + return [, snStr] } buildSidePanelTabs(): SidePanelTab[] { const tabs = [ - {label: "Summary", content: this.buildSummaryPage()} + { label: "Summary", content: this.buildSummaryPage() } ] this.buildDiffAndHealthPages(tabs) @@ -58,10 +58,10 @@ export class ObjectNodeData extends NodeData { } if (findObjectByRef(this.props.commandResult.objects, this.objectRef)?.remote) { - tabs.push({label: "Remote", content: this.buildObjectPage(this.objectRef, ObjectType.Remote)}) + tabs.push({ label: "Remote", content: this.buildObjectPage(this.objectRef, ObjectType.Remote) }) } if (findObjectByRef(this.props.commandResult.objects, this.objectRef)?.applied) { - tabs.push({label: "Applied", content: this.buildObjectPage(this.objectRef, ObjectType.Applied)}) + tabs.push({ label: "Applied", content: this.buildObjectPage(this.objectRef, ObjectType.Applied) }) } return tabs @@ -74,27 +74,27 @@ export class ObjectNodeData extends NodeData { if (this.objectRef.group) { apiVersion = this.objectRef.group + "/" + this.objectRef.version } - props.push({name: "ApiVersion", value: apiVersion}) - props.push({name: "Kind", value: this.objectRef.kind}) + props.push({ name: "ApiVersion", value: apiVersion }) + props.push({ name: "Kind", value: this.objectRef.kind }) - props.push({name: "Name", value: this.objectRef.name}) + props.push({ name: "Name", value: this.objectRef.name }) if (this.objectRef.namespace) { props.push({ name: "Namespace", value: this.objectRef.namespace }) } const o = findObjectByRef(this.props.commandResult.objects, this.objectRef) - const annotations: {[key: string]: string} = o?.rendered?.metadata.annotations + const annotations: { [key: string]: string } = o?.rendered?.metadata.annotations if (annotations) { Object.keys(annotations).forEach(k => { - if (k.indexOf("kluctl.io/") !== -1) { - props.push({ name: k, value: annotations[k] }) - } + if (k.indexOf("kluctl.io/") !== -1) { + props.push({ name: k, value: annotations[k] }) } + } ) } return <> - + } } diff --git a/pkg/webui/ui/src/components/result-view/nodes/VarsSourceCollectionNode.tsx b/pkg/webui/ui/src/components/result-view/nodes/VarsSourceCollectionNode.tsx index 20af76274..dfa9b5e4d 100644 --- a/pkg/webui/ui/src/components/result-view/nodes/VarsSourceCollectionNode.tsx +++ b/pkg/webui/ui/src/components/result-view/nodes/VarsSourceCollectionNode.tsx @@ -1,9 +1,9 @@ import { VarsSource } from "../../../models"; import { NodeData } from "./NodeData"; import React from "react"; -import { DataArray } from "@mui/icons-material"; import { CommandResultProps } from "../CommandResultView"; import { SidePanelTab } from "../SidePanel"; +import { BracketsSquareIcon } from "../../../icons/Icons"; export class VarsSourceCollectionNodeData extends NodeData { varsSources: VarsSource[] = [] @@ -17,7 +17,7 @@ export class VarsSourceCollectionNodeData extends NodeData { } buildIcon(): [React.ReactNode, string] { - return [, "vars"] + return [, "vars"] } buildSidePanelTabs(): SidePanelTab[] { diff --git a/pkg/webui/ui/src/components/result-view/nodes/VarsSourceNode.tsx b/pkg/webui/ui/src/components/result-view/nodes/VarsSourceNode.tsx index 3b5674e5c..e337cd4fd 100644 --- a/pkg/webui/ui/src/components/result-view/nodes/VarsSourceNode.tsx +++ b/pkg/webui/ui/src/components/result-view/nodes/VarsSourceNode.tsx @@ -1,8 +1,8 @@ import { VarsSource } from "../../../models"; import { NodeData } from "./NodeData"; import React from "react"; -import { Category, Cloud, Dvr, Http, Lock, Settings, Source } from "@mui/icons-material"; -import { GitIcon } from "../../../icons/Icons"; +import { Category, Cloud, Dvr, Http, Lock, Settings } from "@mui/icons-material"; +import { FileIcon, GitIcon } from "../../../icons/Icons"; import { PropertiesTable } from "../../PropertiesTable"; import { CodeViewer } from "../../CodeViewer"; import { Box } from "@mui/material"; @@ -59,7 +59,7 @@ export class VarsSourceNodeData extends NodeData { label: () => { return this.varsSource.file }, - icon: () => , + icon: () => , sourceProps: () => [ {name: "File", value: this.varsSource.file} ] diff --git a/pkg/webui/ui/src/icons/Icons.tsx b/pkg/webui/ui/src/icons/Icons.tsx index 19227081e..7abba98ce 100644 --- a/pkg/webui/ui/src/icons/Icons.tsx +++ b/pkg/webui/ui/src/icons/Icons.tsx @@ -26,6 +26,13 @@ import { ReactComponent as ArrowLeftIconSvg } from './arrow-left.svg'; import { ReactComponent as WarningSignIconSvg } from './warning-sign.svg'; import { ReactComponent as ChangesIconSvg } from './changes.svg'; import { ReactComponent as StarIconSvg } from './star.svg'; +import { ReactComponent as TriangleDownIconSvg } from './triangle-down.svg'; +import { ReactComponent as TriangleRightIconSvg } from './triangle-right.svg'; +import { ReactComponent as BracketsCurlyIconSvg } from './brackets-curly.svg'; +import { ReactComponent as BracketsSquareIconSvg } from './brackets-square.svg'; +import { ReactComponent as FileIconSvg } from './file.svg'; +import { ReactComponent as ResultIconSvg } from './result.svg'; +import { ReactComponent as IncludeIconSvg } from './include.svg'; export const KluctlText = () => { return @@ -147,3 +154,31 @@ export const ChangesIcon = () => { export const StarIcon = () => { return } + +export const TriangleDownIcon = () => { + return +} + +export const TriangleRightIcon = () => { + return +} + +export const BracketsCurlyIcon = () => { + return +} + +export const BracketsSquareIcon = () => { + return +} + +export const FileIcon = () => { + return +} + +export const ResultIcon = () => { + return +} + +export const IncludeIcon = () => { + return +} diff --git a/pkg/webui/ui/src/icons/brackets-curly.svg b/pkg/webui/ui/src/icons/brackets-curly.svg new file mode 100644 index 000000000..c0ead2b85 --- /dev/null +++ b/pkg/webui/ui/src/icons/brackets-curly.svg @@ -0,0 +1,3 @@ + + + diff --git a/pkg/webui/ui/src/icons/brackets-square.svg b/pkg/webui/ui/src/icons/brackets-square.svg new file mode 100644 index 000000000..ab82bf02c --- /dev/null +++ b/pkg/webui/ui/src/icons/brackets-square.svg @@ -0,0 +1,3 @@ + + + diff --git a/pkg/webui/ui/src/icons/file.svg b/pkg/webui/ui/src/icons/file.svg new file mode 100644 index 000000000..698087505 --- /dev/null +++ b/pkg/webui/ui/src/icons/file.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/pkg/webui/ui/src/icons/include.svg b/pkg/webui/ui/src/icons/include.svg new file mode 100644 index 000000000..f17e9d604 --- /dev/null +++ b/pkg/webui/ui/src/icons/include.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/pkg/webui/ui/src/icons/result.svg b/pkg/webui/ui/src/icons/result.svg new file mode 100644 index 000000000..013fab63e --- /dev/null +++ b/pkg/webui/ui/src/icons/result.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/pkg/webui/ui/src/icons/triangle-down.svg b/pkg/webui/ui/src/icons/triangle-down.svg new file mode 100644 index 000000000..d9c2677ca --- /dev/null +++ b/pkg/webui/ui/src/icons/triangle-down.svg @@ -0,0 +1,4 @@ + + + + diff --git a/pkg/webui/ui/src/icons/triangle-right.svg b/pkg/webui/ui/src/icons/triangle-right.svg new file mode 100644 index 000000000..ad10f4ccc --- /dev/null +++ b/pkg/webui/ui/src/icons/triangle-right.svg @@ -0,0 +1,4 @@ + + + + From 653cea0f4f8c214788f07db2d72c99f603103feb Mon Sep 17 00:00:00 2001 From: Alexey Vagarenko Date: Sat, 3 Jun 2023 03:17:50 +0600 Subject: [PATCH 1787/2916] Result Tree page: Added right drawer. --- .../result-view/CommandResultTree.tsx | 17 ++--------- .../result-view/CommandResultView.tsx | 28 +++++++++++++++---- .../src/components/result-view/SidePanel.tsx | 2 +- 3 files changed, 27 insertions(+), 20 deletions(-) diff --git a/pkg/webui/ui/src/components/result-view/CommandResultTree.tsx b/pkg/webui/ui/src/components/result-view/CommandResultTree.tsx index 56c3174b7..58bd404b6 100644 --- a/pkg/webui/ui/src/components/result-view/CommandResultTree.tsx +++ b/pkg/webui/ui/src/components/result-view/CommandResultTree.tsx @@ -45,22 +45,11 @@ const CommandResultTree = (props: CommandResultTreeProps) => { }; const handleItemClick = (e: React.SyntheticEvent, node: NodeData) => { - setSelectedNodeId(node.id) - e.stopPropagation() + setSelectedNodeId(node.id); + props.onSelectNode(node); + e.stopPropagation(); } - const onSelectNode = props.onSelectNode - useEffect(() => { - if (!nodeMap || !selectedNodeId) { - return - } - const node = nodeMap.get(selectedNodeId) - if (!node) { - setSelectedNodeId(undefined) - } - onSelectNode(node) - }, [nodeMap, selectedNodeId, onSelectNode]) - const renderTree = (nodes: NodeData) => { if (!FilterNode(nodes, props.activeFilters)) { return null diff --git a/pkg/webui/ui/src/components/result-view/CommandResultView.tsx b/pkg/webui/ui/src/components/result-view/CommandResultView.tsx index b9186b71e..0790dfe13 100644 --- a/pkg/webui/ui/src/components/result-view/CommandResultView.tsx +++ b/pkg/webui/ui/src/components/result-view/CommandResultView.tsx @@ -1,15 +1,16 @@ import * as React from 'react'; import { useState } from 'react'; -import { Box, Checkbox, CheckboxProps, Divider, FormControlLabel, Typography } from "@mui/material"; +import { Box, Checkbox, CheckboxProps, Divider, Drawer, FormControlLabel, ThemeProvider, Typography } from "@mui/material"; import { CommandResult, CommandResultSummary, ShortName } from "../../models"; import { NodeData } from "./nodes/NodeData"; -import { SidePanel } from "./SidePanel"; +import { SidePanel, SidePanelProvider } from "./SidePanel"; import { ActiveFilters } from "./NodeStatusFilter"; import CommandResultTree from "./CommandResultTree"; import { useLoaderData } from "react-router-dom"; import { api } from "../../api"; import { useAppOutletContext } from "../App"; import { ChangesIcon, CheckboxCheckedIcon, CheckboxIcon, StarIcon, WarningSignIcon } from '../../icons/Icons'; +import { dark } from '../theme'; export interface CommandResultProps { shortNames: ShortName[] @@ -83,6 +84,22 @@ const FilterCheckbox = (props: { /> } +function DetailsDrawer(props: { nodeData?: NodeData, onClose?: () => void }) { + return + + + + + + ; +} + const defaultFilters = { onlyImportant: false, onlyChanged: false, @@ -116,6 +133,10 @@ export const CommandResultView = () => { flexDirection='column' overflow='hidden' > + setSidePanelNode(undefined)} + /> { activeFilters={context.filters} /> - - -
    } diff --git a/pkg/webui/ui/src/components/result-view/SidePanel.tsx b/pkg/webui/ui/src/components/result-view/SidePanel.tsx index bfb5c17b0..049c9ddf1 100644 --- a/pkg/webui/ui/src/components/result-view/SidePanel.tsx +++ b/pkg/webui/ui/src/components/result-view/SidePanel.tsx @@ -61,7 +61,7 @@ export const SidePanel = (props: SidePanelProps) => { return - + From bdf318a6a795b244cf3735ce4b10d2917ab80ba4 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 5 Jun 2023 21:43:35 +0200 Subject: [PATCH 1788/2916] fix: Fix crash in buildDeploymentProjectChildren --- pkg/webui/ui/src/components/result-view/nodes/NodeBuilder.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/webui/ui/src/components/result-view/nodes/NodeBuilder.ts b/pkg/webui/ui/src/components/result-view/nodes/NodeBuilder.ts index c3ab9769b..b5bbe43e3 100644 --- a/pkg/webui/ui/src/components/result-view/nodes/NodeBuilder.ts +++ b/pkg/webui/ui/src/components/result-view/nodes/NodeBuilder.ts @@ -68,7 +68,9 @@ export class NodeBuilder { buildRoot(): [CommandResultNodeData, Map] { const rootNode = new CommandResultNodeData(this.props, "root") - this.buildDeploymentProjectChildren(rootNode, this.props.commandResult.deployment!) + if (this.props.commandResult.deployment) { + this.buildDeploymentProjectChildren(rootNode, this.props.commandResult.deployment) + } if (this.deletedObjectsMap.size) { this.buildDeletedOrOrphanNode(rootNode, true, Array.from(this.deletedObjectsMap.values())) From c6fd95dd9d9746d8870e4bb9cfff55e9d4d9c6a3 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 5 Jun 2023 21:46:05 +0200 Subject: [PATCH 1789/2916] fix: Use ./ as homepage --- pkg/webui/staticbuilder.go | 11 ----------- pkg/webui/ui/package.json | 2 +- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/pkg/webui/staticbuilder.go b/pkg/webui/staticbuilder.go index fd7f95726..7cb3aad78 100644 --- a/pkg/webui/staticbuilder.go +++ b/pkg/webui/staticbuilder.go @@ -1,7 +1,6 @@ package webui import ( - "bytes" "context" "fmt" "github.com/kluctl/kluctl/v2/pkg/results" @@ -115,16 +114,6 @@ func (swb *StaticWebuiBuilder) Build(path string) error { return err } - indexHtml, err := os.ReadFile(filepath.Join(tmpDir, "index.html")) - if err != nil { - return err - } - indexHtml = bytes.ReplaceAll(indexHtml, []byte("/webui/"), []byte("./")) - err = os.WriteFile(filepath.Join(tmpDir, "index.html"), indexHtml, 0) - if err != nil { - return err - } - staticbuildJsBytes, err := os.ReadFile(filepath.Join(tmpDir, "staticbuild.js")) if err != nil { return err diff --git a/pkg/webui/ui/package.json b/pkg/webui/ui/package.json index e658c5ed9..68d21f982 100644 --- a/pkg/webui/ui/package.json +++ b/pkg/webui/ui/package.json @@ -1,7 +1,7 @@ { "name": "react-demo", "version": "0.1.0", - "homepage": "/", + "homepage": "./", "private": true, "proxy": "http://localhost:8080", "dependencies": { From 3ebb6c0e87780b11f1967ddad42b76f29c33da3e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 5 Jun 2023 23:41:10 +0200 Subject: [PATCH 1790/2916] fix: update models.ts --- .../components/result-view/CommandResultStatusLine.tsx | 4 ++-- pkg/webui/ui/src/models.ts | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/webui/ui/src/components/result-view/CommandResultStatusLine.tsx b/pkg/webui/ui/src/components/result-view/CommandResultStatusLine.tsx index 4794f31ae..8187e6f9e 100644 --- a/pkg/webui/ui/src/components/result-view/CommandResultStatusLine.tsx +++ b/pkg/webui/ui/src/components/result-view/CommandResultStatusLine.tsx @@ -49,8 +49,8 @@ export const StatusLine = (props: StatusLineProps) => { } export const CommandResultStatusLine = (props: { rs: CommandResultSummary }) => { - return Date: Wed, 7 Jun 2023 16:39:43 +0200 Subject: [PATCH 1791/2916] feat: Add support for reconcile/deploy now --- pkg/webui/clusteraccessor.go | 18 +++++- pkg/webui/server.go | 62 +++++++++++++++++++ pkg/webui/ui/src/api.tsx | 42 ++++++++++--- .../src/components/targets-view/Targets.tsx | 34 +++++++++- 4 files changed, 145 insertions(+), 11 deletions(-) diff --git a/pkg/webui/clusteraccessor.go b/pkg/webui/clusteraccessor.go index fe83386f2..1e44dcdbb 100644 --- a/pkg/webui/clusteraccessor.go +++ b/pkg/webui/clusteraccessor.go @@ -2,8 +2,11 @@ package webui import ( "context" + kluctlv1 "github.com/kluctl/kluctl/v2/api/v1beta1" k8s2 "github.com/kluctl/kluctl/v2/pkg/k8s" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" "sync" @@ -61,8 +64,19 @@ func (ca *clusterAccessor) initClient() { } func (ca *clusterAccessor) tryInitClient() error { - var err error - c, err := client.New(ca.config, client.Options{}) + scheme := runtime.NewScheme() + err := clientgoscheme.AddToScheme(scheme) + if err != nil { + return err + } + err = kluctlv1.AddToScheme(scheme) + if err != nil { + return err + } + + c, err := client.New(ca.config, client.Options{ + Scheme: scheme, + }) if err != nil { return err } diff --git a/pkg/webui/server.go b/pkg/webui/server.go index 8d854718b..90523fba0 100644 --- a/pkg/webui/server.go +++ b/pkg/webui/server.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "github.com/gin-gonic/gin" + kluctlv1 "github.com/kluctl/kluctl/v2/api/v1beta1" "github.com/kluctl/kluctl/v2/pkg/results" "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/types" @@ -11,11 +12,17 @@ import ( "github.com/kluctl/kluctl/v2/pkg/types/result" "github.com/kluctl/kluctl/v2/pkg/utils/uo" "io/fs" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/rest" "net" "net/http" + "sigs.k8s.io/controller-runtime/pkg/client" + "time" ) +const webuiManager = "kluctl-webui" + type CommandResultsServer struct { ctx context.Context collector *results.ResultsCollector @@ -85,6 +92,8 @@ func (s *CommandResultsServer) Run(port int) error { api.GET("/getResult", s.getResult) api.GET("/getResultObject", s.getResultObject) api.POST("/validateNow", s.validateNow) + api.POST("/reconcileNow", s.reconcileNow) + api.POST("/deployNow", s.deployNow) address := fmt.Sprintf(":%d", port) listener, err := net.Listen("tcp", address) @@ -302,3 +311,56 @@ func (s *CommandResultsServer) validateNow(c *gin.Context) { c.Status(http.StatusOK) } + +type kluctlDeploymentParam struct { + Cluster string `json:"cluster"` + Name string `json:"name"` + Namespace string `json:"namespace"` +} + +func (s *CommandResultsServer) doSetAnnotation(c *gin.Context, aname string, avalue string) { + var params kluctlDeploymentParam + err := c.Bind(¶ms) + if err != nil { + _ = c.AbortWithError(http.StatusBadRequest, err) + return + } + + ca := s.cam.getForClusterId(params.Cluster) + if ca == nil { + _ = c.AbortWithError(http.StatusNotFound, err) + return + } + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + defer cancel() + + var kd kluctlv1.KluctlDeployment + err = ca.getClient().Get(ctx, client.ObjectKey{Name: params.Name, Namespace: params.Namespace}, &kd) + if err != nil { + if errors.IsNotFound(err) { + _ = c.AbortWithError(http.StatusNotFound, err) + return + } + _ = c.AbortWithError(http.StatusInternalServerError, err) + return + } + + patch := client.MergeFrom(kd.DeepCopy()) + metav1.SetMetaDataAnnotation(&kd.ObjectMeta, aname, avalue) + err = ca.getClient().Patch(ctx, &kd, patch, client.FieldOwner(webuiManager)) + if err != nil { + _ = c.AbortWithError(http.StatusInternalServerError, err) + return + } + + c.Status(http.StatusOK) +} + +func (s *CommandResultsServer) reconcileNow(c *gin.Context) { + s.doSetAnnotation(c, kluctlv1.KluctlRequestReconcileAnnotation, time.Now().Format(time.RFC3339Nano)) +} + +func (s *CommandResultsServer) deployNow(c *gin.Context) { + s.doSetAnnotation(c, kluctlv1.KluctlRequestDeployAnnotation, time.Now().Format(time.RFC3339Nano)) +} diff --git a/pkg/webui/ui/src/api.tsx b/pkg/webui/ui/src/api.tsx index 173f2a42d..5b4ea4678 100644 --- a/pkg/webui/ui/src/api.tsx +++ b/pkg/webui/ui/src/api.tsx @@ -31,6 +31,8 @@ export interface Api { getResult(resultId: string): Promise getResultObject(resultId: string, ref: ObjectRef, objectType: string): Promise validateNow(project: ProjectKey, target: TargetKey): Promise + reconcileNow(cluster: string, name: string, namespace: string): Promise + deployNow(cluster: string, name: string, namespace: string): Promise } class RealApi implements Api { @@ -80,22 +82,40 @@ class RealApi implements Api { .then(response => response.json()); } - async validateNow(project: ProjectKey, target: TargetKey) { - const key = { - "project": project, - "target": target, - } - - let url = `${apiUrl}/validateNow` + async doPost(f: string, body: any) { + let url = `${apiUrl}/${f}` return fetch(url, { method: "POST", - body: JSON.stringify(key), + body: JSON.stringify(body), headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, }).then(handleErrors); } + + async validateNow(project: ProjectKey, target: TargetKey) { + return this.doPost("validateNow", { + "project": project, + "target": target, + }) + } + + async deployNow(cluster: string, name: string, namespace: string): Promise { + return this.doPost("deployNow", { + "cluster": cluster, + "name": name, + "namespace": namespace, + }) + } + + async reconcileNow(cluster: string, name: string, namespace: string): Promise { + return this.doPost("reconcileNow", { + "cluster": cluster, + "name": name, + "namespace": namespace, + }) + } } class StaticApi implements Api { @@ -143,6 +163,12 @@ class StaticApi implements Api { validateNow(project: ProjectKey, target: TargetKey): Promise { throw new Error("not implemented") } + reconcileNow(cluster: string, name: string, namespace: string): Promise { + throw new Error("not implemented") + } + deployNow(cluster: string, name: string, namespace: string): Promise { + throw new Error("not implemented") + } } export let api: Api diff --git a/pkg/webui/ui/src/components/targets-view/Targets.tsx b/pkg/webui/ui/src/components/targets-view/Targets.tsx index d6f891c37..607b1ade6 100644 --- a/pkg/webui/ui/src/components/targets-view/Targets.tsx +++ b/pkg/webui/ui/src/components/targets-view/Targets.tsx @@ -1,4 +1,4 @@ -import { ProjectSummary, TargetSummary } from "../../models"; +import { KluctlDeploymentInfo, ProjectSummary, TargetSummary } from "../../models"; import { ActionMenuItem, ActionsMenu } from "../ActionsMenu"; import Paper from "@mui/material/Paper"; import { Box, Typography, useTheme } from "@mui/material"; @@ -62,6 +62,38 @@ export const TargetItem = (props: { ps: ProjectSummary, ts: TargetSummary, onSel } }) + let kd: KluctlDeploymentInfo | undefined + let allKdEqual = true + props.ts.commandResults?.forEach(rs => { + console.log(rs) + if (rs.commandInfo.kluctlDeployment) { + if (!kd) { + kd = rs.commandInfo.kluctlDeployment + } else { + if (kd.name !== rs.commandInfo.kluctlDeployment.name || kd.namespace !== rs.commandInfo.kluctlDeployment.namespace) { + allKdEqual = false + } + } + } + }) + + if (kd && allKdEqual) { + actionMenuItems.push({ + icon: , + text: "Reconcile", + handler: () => { + api.reconcileNow(props.ts.target.clusterId, kd!.name, kd!.namespace) + } + }) + actionMenuItems.push({ + icon: , + text: "Deploy", + handler: () => { + api.deployNow(props.ts.target.clusterId, kd!.name, kd!.namespace) + } + }) + } + const allContexts: string[] = [] const handleContext = (c?: string) => { From a253a129720e0ebfa8405c090acd5c0c9d559557 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 7 Jun 2023 16:39:49 +0200 Subject: [PATCH 1792/2916] fix: Update models.ts --- pkg/webui/ui/src/models.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkg/webui/ui/src/models.ts b/pkg/webui/ui/src/models.ts index 8e420682d..d0eed1881 100644 --- a/pkg/webui/ui/src/models.ts +++ b/pkg/webui/ui/src/models.ts @@ -410,15 +410,11 @@ export class GitInfo { export class KluctlDeploymentInfo { name: string; namespace: string; - gitUrl: string; - gitRef: string; constructor(source: any = {}) { if ('string' === typeof source) source = JSON.parse(source); this.name = source["name"]; this.namespace = source["namespace"]; - this.gitUrl = source["gitUrl"]; - this.gitRef = source["gitRef"]; } } export class CommandInfo { From 29196a8a397449db892b69fc27b581e20dbd5973 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 7 Jun 2023 21:49:50 +0200 Subject: [PATCH 1793/2916] feat: Determine static build by looking for existence of projects.js --- pkg/webui/ui/public/staticbuild.js | 1 - pkg/webui/ui/src/api.tsx | 23 +++++++++++++------ pkg/webui/ui/src/components/ObjectYaml.tsx | 12 ++++++---- .../result-view/CommandResultView.tsx | 3 ++- .../CommandResultDetailsDrawer.tsx | 3 ++- .../src/components/targets-view/Targets.tsx | 12 ++++++---- .../components/targets-view/TargetsView.tsx | 3 ++- pkg/webui/ui/src/staticbuild.d.ts | 2 -- 8 files changed, 37 insertions(+), 22 deletions(-) diff --git a/pkg/webui/ui/public/staticbuild.js b/pkg/webui/ui/public/staticbuild.js index a653e44b6..af1b72ec3 100644 --- a/pkg/webui/ui/public/staticbuild.js +++ b/pkg/webui/ui/public/staticbuild.js @@ -1,2 +1 @@ -let isStaticBuild = true; const staticResults = new Map() diff --git a/pkg/webui/ui/src/api.tsx b/pkg/webui/ui/src/api.tsx index 5b4ea4678..6e29ca97c 100644 --- a/pkg/webui/ui/src/api.tsx +++ b/pkg/webui/ui/src/api.tsx @@ -35,6 +35,22 @@ export interface Api { deployNow(cluster: string, name: string, namespace: string): Promise } +let apiPromise: Promise | undefined = undefined +export async function getApi(): Promise { + if (!apiPromise) { + apiPromise = loadScript(staticPath + "/projects.js") + } + + try { + await apiPromise + return new StaticApi() + } catch (error) { + return new RealApi() + } +} + +export let api = getApi() + class RealApi implements Api { async getShortNames(): Promise { let url = `${apiUrl}/getShortNames` @@ -171,13 +187,6 @@ class StaticApi implements Api { } } -export let api: Api -if (isStaticBuild) { - api = new StaticApi() -} else { - api = new RealApi() -} - function handleErrors(response: Response) { if (!response.ok) { throw Error(response.statusText) diff --git a/pkg/webui/ui/src/components/ObjectYaml.tsx b/pkg/webui/ui/src/components/ObjectYaml.tsx index 11f50f428..2d3fee8fe 100644 --- a/pkg/webui/ui/src/components/ObjectYaml.tsx +++ b/pkg/webui/ui/src/components/ObjectYaml.tsx @@ -1,6 +1,6 @@ import { CommandResultProps } from "./result-view/CommandResultView"; import { ObjectRef } from "../models"; -import { api, ObjectType, usePromise } from "../api"; +import { getApi, ObjectType, usePromise } from "../api"; import React, { Suspense, useEffect, useState } from "react"; import { CodeViewer } from "./CodeViewer"; @@ -10,10 +10,14 @@ import { Loading } from "./Loading"; export const ObjectYaml = (props: {treeProps: CommandResultProps, objectRef: ObjectRef, objectType: ObjectType}) => { const [promise, setPromise] = useState>() + const getData = async () => { + const api = await getApi() + const o = await api.getResultObject(props.treeProps.summary.id, props.objectRef, props.objectType) + return yaml.dump(o) + } + useEffect(() => { - const p = api.getResultObject(props.treeProps.summary.id, props.objectRef, props.objectType) - .then(yaml.dump) - setPromise(p) + setPromise(getData()) }, [props.treeProps, props.objectRef, props.objectType]) const Content = () => { diff --git a/pkg/webui/ui/src/components/result-view/CommandResultView.tsx b/pkg/webui/ui/src/components/result-view/CommandResultView.tsx index 0790dfe13..c1e8ffd96 100644 --- a/pkg/webui/ui/src/components/result-view/CommandResultView.tsx +++ b/pkg/webui/ui/src/components/result-view/CommandResultView.tsx @@ -7,10 +7,10 @@ import { SidePanel, SidePanelProvider } from "./SidePanel"; import { ActiveFilters } from "./NodeStatusFilter"; import CommandResultTree from "./CommandResultTree"; import { useLoaderData } from "react-router-dom"; -import { api } from "../../api"; import { useAppOutletContext } from "../App"; import { ChangesIcon, CheckboxCheckedIcon, CheckboxIcon, StarIcon, WarningSignIcon } from '../../icons/Icons'; import { dark } from '../theme'; +import { getApi } from "../../api"; export interface CommandResultProps { shortNames: ShortName[] @@ -19,6 +19,7 @@ export interface CommandResultProps { } export async function commandResultLoader({ params }: any) { + const api = await getApi() const result = api.getResult(params.id) const shortNames = api.getShortNames() const summaries = api.listResults() diff --git a/pkg/webui/ui/src/components/targets-view/CommandResultDetailsDrawer.tsx b/pkg/webui/ui/src/components/targets-view/CommandResultDetailsDrawer.tsx index 5820d9dab..a7c7f7705 100644 --- a/pkg/webui/ui/src/components/targets-view/CommandResultDetailsDrawer.tsx +++ b/pkg/webui/ui/src/components/targets-view/CommandResultDetailsDrawer.tsx @@ -1,5 +1,5 @@ import { CommandResultSummary } from "../../models"; -import { api, usePromise } from "../../api"; +import { getApi, usePromise } from "../../api"; import { NodeBuilder } from "../result-view/nodes/NodeBuilder"; import React, { Suspense, useEffect, useState } from "react"; import { NodeData } from "../result-view/nodes/NodeData"; @@ -9,6 +9,7 @@ import { Loading } from "../Loading"; import { dark } from "../theme"; async function doGetRootNode(rs: CommandResultSummary) { + const api = await getApi() const shortNames = api.getShortNames() const r = api.getResult(rs.id) const builder = new NodeBuilder({ diff --git a/pkg/webui/ui/src/components/targets-view/Targets.tsx b/pkg/webui/ui/src/components/targets-view/Targets.tsx index 607b1ade6..530874065 100644 --- a/pkg/webui/ui/src/components/targets-view/Targets.tsx +++ b/pkg/webui/ui/src/components/targets-view/Targets.tsx @@ -5,7 +5,7 @@ import { Box, Typography, useTheme } from "@mui/material"; import React from "react"; import Tooltip from "@mui/material/Tooltip"; import { Favorite, HeartBroken, PublishedWithChanges } from "@mui/icons-material"; -import { api } from "../../api"; +import { getApi } from "../../api"; import { CpuIcon, FingerScanIcon, MessageQuestionIcon, TargetIcon } from "../../icons/Icons"; const StatusIcon = (props: { ps: ProjectSummary, ts: TargetSummary }) => { @@ -57,7 +57,8 @@ export const TargetItem = (props: { ps: ProjectSummary, ts: TargetSummary, onSel actionMenuItems.push({ icon: , text: "Validate now", - handler: () => { + handler: async () => { + const api = await getApi() api.validateNow(props.ps.project, props.ts.target) } }) @@ -65,7 +66,6 @@ export const TargetItem = (props: { ps: ProjectSummary, ts: TargetSummary, onSel let kd: KluctlDeploymentInfo | undefined let allKdEqual = true props.ts.commandResults?.forEach(rs => { - console.log(rs) if (rs.commandInfo.kluctlDeployment) { if (!kd) { kd = rs.commandInfo.kluctlDeployment @@ -81,14 +81,16 @@ export const TargetItem = (props: { ps: ProjectSummary, ts: TargetSummary, onSel actionMenuItems.push({ icon: , text: "Reconcile", - handler: () => { + handler: async () => { + const api = await getApi() api.reconcileNow(props.ts.target.clusterId, kd!.name, kd!.namespace) } }) actionMenuItems.push({ icon: , text: "Deploy", - handler: () => { + handler: async () => { + const api = await getApi() api.deployNow(props.ts.target.clusterId, kd!.name, kd!.namespace) } }) diff --git a/pkg/webui/ui/src/components/targets-view/TargetsView.tsx b/pkg/webui/ui/src/components/targets-view/TargetsView.tsx index 52178ed7f..3a71b9fa8 100644 --- a/pkg/webui/ui/src/components/targets-view/TargetsView.tsx +++ b/pkg/webui/ui/src/components/targets-view/TargetsView.tsx @@ -3,7 +3,7 @@ import { CommandResultSummary, ProjectSummary, TargetSummary } from "../../model import { Box, BoxProps, Typography, useTheme } from "@mui/material"; import React, { useEffect, useState } from "react"; import { useAppOutletContext } from "../App"; -import { api } from "../../api"; +import { getApi } from "../../api"; import { ProjectItem } from "./Projects"; import { TargetItem } from "./Targets"; import Divider from "@mui/material/Divider"; @@ -19,6 +19,7 @@ const cardHeight = 126; const cardGap = 20; export async function projectsLoader() { + const api = await getApi() const projects = await api.listProjects() return projects } diff --git a/pkg/webui/ui/src/staticbuild.d.ts b/pkg/webui/ui/src/staticbuild.d.ts index 511b2c96a..f8d60218d 100644 --- a/pkg/webui/ui/src/staticbuild.d.ts +++ b/pkg/webui/ui/src/staticbuild.d.ts @@ -1,5 +1,3 @@ -declare var isStaticBuild: bool; - declare const staticResults: Map; declare const staticShortNames: any[]; From 5ce0efa13822f8392ff3e79e2b596d4dc3ddb019 Mon Sep 17 00:00:00 2001 From: Alexey Vagarenko Date: Wed, 7 Jun 2023 06:20:57 +0600 Subject: [PATCH 1794/2916] WIP expanding/collapsing nodes. --- .../ui/src/components/targets-view/Card.tsx | 24 +++ .../CommandResultDetailsDrawer.tsx | 47 +++++- .../targets-view/CommandResultItem.tsx | 2 +- .../components/targets-view/TargetsView.tsx | 151 +++++++++++------- 4 files changed, 159 insertions(+), 65 deletions(-) create mode 100644 pkg/webui/ui/src/components/targets-view/Card.tsx diff --git a/pkg/webui/ui/src/components/targets-view/Card.tsx b/pkg/webui/ui/src/components/targets-view/Card.tsx new file mode 100644 index 000000000..5c550fc9e --- /dev/null +++ b/pkg/webui/ui/src/components/targets-view/Card.tsx @@ -0,0 +1,24 @@ +import { Box, BoxProps } from "@mui/material" + +export const cardWidth = 247; +export const projectCardHeight = 80; +export const cardHeight = 126; +export const cardGap = 20; + +export function Card({ children, ...rest }: BoxProps) { + return + {children} + +} + +export function CardCol({ children, ...rest }: BoxProps) { + return + {children} + +} + +export function CardRow({ children, ...rest }: BoxProps) { + return + {children} + +} diff --git a/pkg/webui/ui/src/components/targets-view/CommandResultDetailsDrawer.tsx b/pkg/webui/ui/src/components/targets-view/CommandResultDetailsDrawer.tsx index a7c7f7705..e5d997038 100644 --- a/pkg/webui/ui/src/components/targets-view/CommandResultDetailsDrawer.tsx +++ b/pkg/webui/ui/src/components/targets-view/CommandResultDetailsDrawer.tsx @@ -1,12 +1,16 @@ -import { CommandResultSummary } from "../../models"; +import { CommandResultSummary, ProjectSummary, TargetSummary } from "../../models"; import { getApi, usePromise } from "../../api"; import { NodeBuilder } from "../result-view/nodes/NodeBuilder"; -import React, { Suspense, useEffect, useState } from "react"; +import { Suspense, useEffect, useState } from "react"; import { NodeData } from "../result-view/nodes/NodeData"; import { SidePanel } from "../result-view/SidePanel"; import { Box, Drawer, ThemeProvider } from "@mui/material"; import { Loading } from "../Loading"; -import { dark } from "../theme"; +import { dark, light } from "../theme"; +import { Card, CardCol } from "./Card"; +import { CommandResultItem } from "./CommandResultItem"; + +const sidePanelWidth = 720; async function doGetRootNode(rs: CommandResultSummary) { const api = await getApi() @@ -21,7 +25,7 @@ async function doGetRootNode(rs: CommandResultSummary) { return node } -export const CommandResultDetailsDrawer = (props: { rs?: CommandResultSummary, onClose: () => void }) => { +export const CommandResultDetailsDrawer = (props: { rs?: CommandResultSummary, ts?: TargetSummary, ps?: ProjectSummary, onClose: () => void }) => { const [prevId, setPrevId] = useState() const [promise, setPromise] = useState>(new Promise(() => undefined)) @@ -34,26 +38,55 @@ export const CommandResultDetailsDrawer = (props: { rs?: CommandResultSummary, o } setPrevId(props.rs.id) setPromise(doGetRootNode(props.rs)) - }, [props.rs]) + }, [props.rs, prevId]) const Content = (props: { onClose: () => void }) => { const node = usePromise(promise) return } + const { ps, ts } = props; + return - + }> + + + + {ps && ts?.commandResults?.map((rs, i) => { + return + { }} + /> + + })} + + + } \ No newline at end of file diff --git a/pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx b/pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx index 8812d8181..937768958 100644 --- a/pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx +++ b/pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx @@ -9,7 +9,7 @@ import { useNavigate } from "react-router"; import { formatDurationShort } from "../../utils/duration"; import { DeployIcon, DiffIcon, PruneIcon, TreeViewIcon } from "../../icons/Icons"; -export const CommandResultItem = (props: { ps: ProjectSummary, ts: TargetSummary, rs: CommandResultSummary, onSelectCommandResult: (rs?: CommandResultSummary) => void }) => { +export const CommandResultItem = (props: { ps: ProjectSummary, ts: TargetSummary, rs: CommandResultSummary, onSelectCommandResult: (rs: CommandResultSummary) => void }) => { const calcAgo = () => { const t1 = new Date(props.rs.commandInfo.startTime) const t2 = new Date() diff --git a/pkg/webui/ui/src/components/targets-view/TargetsView.tsx b/pkg/webui/ui/src/components/targets-view/TargetsView.tsx index 3a71b9fa8..2d37a3f7a 100644 --- a/pkg/webui/ui/src/components/targets-view/TargetsView.tsx +++ b/pkg/webui/ui/src/components/targets-view/TargetsView.tsx @@ -1,6 +1,6 @@ import { useLoaderData } from "react-router-dom"; import { CommandResultSummary, ProjectSummary, TargetSummary } from "../../models"; -import { Box, BoxProps, Typography, useTheme } from "@mui/material"; +import { Box, Typography, useTheme } from "@mui/material"; import React, { useEffect, useState } from "react"; import { useAppOutletContext } from "../App"; import { getApi } from "../../api"; @@ -10,13 +10,12 @@ import Divider from "@mui/material/Divider"; import { CommandResultItem } from "./CommandResultItem"; import { CommandResultDetailsDrawer } from "./CommandResultDetailsDrawer"; import { TargetDetailsDrawer } from "./TargetDetailsDrawer"; -import { RelationHLine } from "../../icons/Icons"; +import { Card, CardCol, CardRow, cardGap, cardHeight, cardWidth, projectCardHeight } from "./Card"; -const colWidth = 433; -const cardWidth = 247; -const projectCardHeight = 80; -const cardHeight = 126; -const cardGap = 20; +const colWidth = 416; +const curveRadius = 12; +const circleRadius = 5; +const strokeWidth = 2; export async function projectsLoader() { const api = await getApi() @@ -44,31 +43,21 @@ function ColHeader({ children }: { children: React.ReactNode }) { } -function Card({ children, ...rest }: BoxProps) { - return - {children} - -} - -function CardCol({ children, ...rest }: BoxProps) { - return - {children} - -} - -function CardRow({ children, ...rest }: BoxProps) { - return - {children} - -} +const Circle = React.memo((props: React.SVGProps) => { + const theme = useTheme(); + return +}) const RelationTree = React.memo(({ targetCount }: { targetCount: number }): JSX.Element | null => { const theme = useTheme(); const height = targetCount * cardHeight + (targetCount - 1) * cardGap - const width = 169; - const curveRadius = 12; - const circleRadius = 5; - const strokeWidth = 2; + const width = 152; if (targetCount <= 0) { return null; @@ -124,33 +113,26 @@ const RelationTree = React.memo(({ targetCount }: { targetCount: number }): JSX. strokeLinecap='round' strokeLinejoin='round' />, - ] })} - }); export const TargetsView = () => { + const theme = useTheme(); const context = useAppOutletContext() - const [selectedCommandResult, setSelectedCommandResult] = useState() - const [selectedTargetSummary, setSelectedTargetSummary] = useState() + const [selectedCommandResult, setSelectedCommandResult] = useState<{rs: CommandResultSummary, ts: TargetSummary, ps: ProjectSummary} | undefined>(); + const [selectedTargetSummary, setSelectedTargetSummary] = useState(); const projects = useLoaderData() as ProjectSummary[]; @@ -158,18 +140,26 @@ export const TargetsView = () => { context.setFilters(undefined) }) - const doSetSelectedCommandResult = (rs?: CommandResultSummary) => { - setSelectedCommandResult(rs) - setSelectedTargetSummary(undefined) + const doSetSelectedCommandResult = (o?: {rs: CommandResultSummary, ts: TargetSummary, ps: ProjectSummary}) => { + setSelectedCommandResult(o); + setSelectedTargetSummary(undefined); } const doSetSelectedTargetSummary = (ts?: TargetSummary) => { - setSelectedCommandResult(undefined) - setSelectedTargetSummary(ts) + setSelectedCommandResult(undefined); + setSelectedTargetSummary(ts); } - return - setSelectedCommandResult(undefined)} /> - setSelectedTargetSummary(undefined)} /> + return + doSetSelectedCommandResult(undefined)} + /> + setSelectedTargetSummary(undefined)} + /> Projects Targets @@ -179,7 +169,7 @@ export const TargetsView = () => { {projects.map((ps, i) => { return - + @@ -194,27 +184,74 @@ export const TargetsView = () => { - + {ps.targets.map((ts, i) => { return doSetSelectedTargetSummary(ts)} /> - - + + + + + + + + + + + + })} - + {ps.targets.map((ts, i) => { - return + return {ts.commandResults?.map((rs, i) => { - return - doSetSelectedCommandResult(rs)} /> + return + doSetSelectedCommandResult({rs, ts, ps})} + /> })} From 1c5419fc5405a4a26b421ce5999b24f825becdbe Mon Sep 17 00:00:00 2001 From: Alexey Vagarenko Date: Thu, 8 Jun 2023 05:06:24 +0600 Subject: [PATCH 1795/2916] Added card selection when card stack is expanded. --- pkg/webui/ui/src/components/LeftDrawer.tsx | 17 +-- .../CommandResultDetailsDrawer.tsx | 124 ++++++++++-------- .../targets-view/CommandResultItem.tsx | 32 +++-- .../targets-view/TargetDetailsDrawer.tsx | 8 +- .../components/targets-view/TargetsView.tsx | 23 +++- pkg/webui/ui/src/components/theme.ts | 20 ++- 6 files changed, 135 insertions(+), 89 deletions(-) diff --git a/pkg/webui/ui/src/components/LeftDrawer.tsx b/pkg/webui/ui/src/components/LeftDrawer.tsx index 62cacf4a5..5bd0db728 100644 --- a/pkg/webui/ui/src/components/LeftDrawer.tsx +++ b/pkg/webui/ui/src/components/LeftDrawer.tsx @@ -17,11 +17,8 @@ import { KluctlLogo, TargetsIcon, KluctlText, SearchIcon, ArrowLeftIcon } from ' import { dark } from './theme'; import { Typography } from '@mui/material'; -const drawerWidthOpen = 224; -const drawerWidthClosed = 96; - const openedMixin = (theme: Theme): CSSObject => ({ - width: drawerWidthOpen, + width: theme.consts.leftDrawerWidthOpen, transition: theme.transitions.create('width', { easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.enteringScreen, @@ -35,7 +32,7 @@ const closedMixin = (theme: Theme): CSSObject => ({ duration: theme.transitions.duration.leavingScreen, }), overflowX: 'hidden', - width: drawerWidthClosed, + width: theme.consts.leftDrawerWidthClosed, }); const DrawerHeader = styled('div')(({ theme }) => ({ @@ -57,17 +54,17 @@ const AppBar = styled(MuiAppBar, { boxShadow: 'none', background: 'transparent', padding: '40px 40px 0 40px', - marginLeft: drawerWidthClosed, + marginLeft: theme.consts.leftDrawerWidthClosed, justifyContent: 'space-between', - width: `calc(100% - ${drawerWidthClosed}px)`, + width: `calc(100% - ${theme.consts.leftDrawerWidthClosed}px)`, zIndex: theme.zIndex.drawer + 1, transition: theme.transitions.create(['width', 'margin'], { easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.leavingScreen, }), ...(open && { - marginLeft: drawerWidthOpen, - width: `calc(100% - ${drawerWidthOpen}px)`, + marginLeft: theme.consts.leftDrawerWidthOpen, + width: `calc(100% - ${theme.consts.leftDrawerWidthOpen}px)`, transition: theme.transitions.create(['width', 'margin'], { easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.enteringScreen, @@ -77,7 +74,7 @@ const AppBar = styled(MuiAppBar, { const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => prop !== 'open' })( ({ theme, open }) => ({ - width: drawerWidthOpen, + width: theme.consts.leftDrawerWidthOpen, flexShrink: 0, whiteSpace: 'nowrap', boxSizing: 'border-box', diff --git a/pkg/webui/ui/src/components/targets-view/CommandResultDetailsDrawer.tsx b/pkg/webui/ui/src/components/targets-view/CommandResultDetailsDrawer.tsx index e5d997038..37c3e11a9 100644 --- a/pkg/webui/ui/src/components/targets-view/CommandResultDetailsDrawer.tsx +++ b/pkg/webui/ui/src/components/targets-view/CommandResultDetailsDrawer.tsx @@ -4,11 +4,12 @@ import { NodeBuilder } from "../result-view/nodes/NodeBuilder"; import { Suspense, useEffect, useState } from "react"; import { NodeData } from "../result-view/nodes/NodeData"; import { SidePanel } from "../result-view/SidePanel"; -import { Box, Drawer, ThemeProvider } from "@mui/material"; +import { Box, Drawer, ThemeProvider, useTheme } from "@mui/material"; import { Loading } from "../Loading"; -import { dark, light } from "../theme"; +import { dark } from "../theme"; import { Card, CardCol } from "./Card"; import { CommandResultItem } from "./CommandResultItem"; +import React from "react"; const sidePanelWidth = 720; @@ -25,68 +26,81 @@ async function doGetRootNode(rs: CommandResultSummary) { return node } -export const CommandResultDetailsDrawer = (props: { rs?: CommandResultSummary, ts?: TargetSummary, ps?: ProjectSummary, onClose: () => void }) => { - const [prevId, setPrevId] = useState() - const [promise, setPromise] = useState>(new Promise(() => undefined)) +export const CommandResultDetailsDrawer = React.memo((props: { + rs?: CommandResultSummary, + ts?: TargetSummary, + ps?: ProjectSummary, + onClose: () => void +}) => { + const { ps, ts } = props; + const theme = useTheme(); + const [promise, setPromise] = useState>(new Promise(() => undefined)); + const [selectedCommandResult, setSelectedCommandResult] = useState(); + const [prevTargetSummary, setPrevTargetSummary] = useState(ts); + + if (prevTargetSummary !== ts) { + setPrevTargetSummary(ts); + setSelectedCommandResult(ts?.commandResults?.[0]); + } useEffect(() => { - if (props.rs === undefined) { - return - } - if (props.rs.id === prevId) { + if (selectedCommandResult === undefined) { return } - setPrevId(props.rs.id) - setPromise(doGetRootNode(props.rs)) - }, [props.rs, prevId]) + setPromise(doGetRootNode(selectedCommandResult)); + }, [selectedCommandResult]) const Content = (props: { onClose: () => void }) => { const node = usePromise(promise) return } - const { ps, ts } = props; - - return - - - }> - - + return <> + {ps && ts && + + e.stopPropagation()} flexGrow={1} justifyContent='center'> + {ts.commandResults?.map((rs, i) => { + return + + + })} + - - - - {ps && ts?.commandResults?.map((rs, i) => { - return - { }} - /> - - })} - + } + + + + }> + + - - - -} \ No newline at end of file + + + +}); diff --git a/pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx b/pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx index 937768958..bf3ff6a52 100644 --- a/pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx +++ b/pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx @@ -1,3 +1,4 @@ +import React from "react"; import { CommandResultSummary, ProjectSummary, TargetSummary } from "../../models"; import { useEffect, useMemo, useState } from "react"; import * as yaml from "js-yaml"; @@ -9,16 +10,22 @@ import { useNavigate } from "react-router"; import { formatDurationShort } from "../../utils/duration"; import { DeployIcon, DiffIcon, PruneIcon, TreeViewIcon } from "../../icons/Icons"; -export const CommandResultItem = (props: { ps: ProjectSummary, ts: TargetSummary, rs: CommandResultSummary, onSelectCommandResult: (rs: CommandResultSummary) => void }) => { - const calcAgo = () => { - const t1 = new Date(props.rs.commandInfo.startTime) - const t2 = new Date() - const d = t2.getTime() - t1.getTime() - return formatDurationShort(d) - } +const calcAgo = (startTime: string) => { + const t1 = new Date(startTime) + const t2 = new Date() + const d = t2.getTime() - t1.getTime() + return formatDurationShort(d) +} +export const CommandResultItem = React.memo((props: { + ps: ProjectSummary, + ts: TargetSummary, + rs: CommandResultSummary, + onSelectCommandResult: (rs: CommandResultSummary) => void, + selected?: boolean; +}) => { const navigate = useNavigate() - const [ago, setAgo] = useState(calcAgo()) + const [ago, setAgo] = useState(calcAgo(props.rs.commandInfo.startTime)) let Icon: () => JSX.Element = DiffIcon switch (props.rs.commandInfo?.command) { @@ -45,9 +52,9 @@ export const CommandResultItem = (props: { ps: ProjectSummary, ts: TargetSummary let iconTooltip = useEffect(() => { - const interval = setInterval(() => setAgo(calcAgo()), 5000); + const interval = setInterval(() => setAgo(calcAgo(props.rs.commandInfo.startTime)), 5000); return () => clearInterval(interval); - }, []) + }, [props.rs.commandInfo.startTime]) return props.onSelectCommandResult(props.rs)} > @@ -116,4 +124,4 @@ export const CommandResultItem = (props: { ps: ProjectSummary, ts: TargetSummary
    -} \ No newline at end of file +}); diff --git a/pkg/webui/ui/src/components/targets-view/TargetDetailsDrawer.tsx b/pkg/webui/ui/src/components/targets-view/TargetDetailsDrawer.tsx index 874779b74..b3d45a96b 100644 --- a/pkg/webui/ui/src/components/targets-view/TargetDetailsDrawer.tsx +++ b/pkg/webui/ui/src/components/targets-view/TargetDetailsDrawer.tsx @@ -95,18 +95,18 @@ class MyProvider implements SidePanelProvider { } } -export const TargetDetailsDrawer = (props: { ts?: TargetSummary, onClose: () => void }) => { +export const TargetDetailsDrawer = React.memo((props: { ts?: TargetSummary, onClose: () => void }) => { return - + ; -} \ No newline at end of file +}); diff --git a/pkg/webui/ui/src/components/targets-view/TargetsView.tsx b/pkg/webui/ui/src/components/targets-view/TargetsView.tsx index 2d37a3f7a..f480f39f9 100644 --- a/pkg/webui/ui/src/components/targets-view/TargetsView.tsx +++ b/pkg/webui/ui/src/components/targets-view/TargetsView.tsx @@ -1,7 +1,7 @@ import { useLoaderData } from "react-router-dom"; import { CommandResultSummary, ProjectSummary, TargetSummary } from "../../models"; import { Box, Typography, useTheme } from "@mui/material"; -import React, { useEffect, useState } from "react"; +import React, { useCallback, useEffect, useState } from "react"; import { useAppOutletContext } from "../App"; import { getApi } from "../../api"; import { ProjectItem } from "./Projects"; @@ -140,25 +140,34 @@ export const TargetsView = () => { context.setFilters(undefined) }) - const doSetSelectedCommandResult = (o?: {rs: CommandResultSummary, ts: TargetSummary, ps: ProjectSummary}) => { + const doSetSelectedCommandResult = useCallback((o?: {rs: CommandResultSummary, ts: TargetSummary, ps: ProjectSummary}) => { setSelectedCommandResult(o); setSelectedTargetSummary(undefined); - } - const doSetSelectedTargetSummary = (ts?: TargetSummary) => { + }, []); + + const doSetSelectedTargetSummary = useCallback((ts?: TargetSummary) => { setSelectedCommandResult(undefined); setSelectedTargetSummary(ts); - } + }, []); + + const onCommandResultDetailsDrawerClose = useCallback(() => { + doSetSelectedCommandResult(undefined); + }, [doSetSelectedCommandResult]); + + const onTargetDetailsDrawerClose = useCallback(() => { + setSelectedTargetSummary(undefined); + }, [setSelectedTargetSummary]); return doSetSelectedCommandResult(undefined)} + onClose={onCommandResultDetailsDrawerClose} /> setSelectedTargetSummary(undefined)} + onClose={onTargetDetailsDrawerClose} /> Projects diff --git a/pkg/webui/ui/src/components/theme.ts b/pkg/webui/ui/src/components/theme.ts index 14680cd0a..701d20a15 100644 --- a/pkg/webui/ui/src/components/theme.ts +++ b/pkg/webui/ui/src/components/theme.ts @@ -17,22 +17,40 @@ declare module '@mui/material/styles' { interface Theme { consts: { appBarHeight: number; + leftDrawerWidthOpen: number; + leftDrawerWidthClosed: number; }; } // allow configuration using `createTheme` interface ThemeOptions { consts?: { appBarHeight?: number; + leftDrawerWidthOpen?: number; + leftDrawerWidthClosed?: number; }; } } export const common = createTheme({ consts: { - appBarHeight: 106 + appBarHeight: 106, + leftDrawerWidthOpen: 224, + leftDrawerWidthClosed: 96 }, typography: { fontFamily: 'Nunito Variable', + }, + components: { + MuiBackdrop: { + styleOverrides: { + root: { + backgroundColor: 'rgba(0, 0, 0, 0.65)' + }, + invisible: { + backgroundColor: 'transparent' + } + } + } } }); From 12662156a12448467d0eacacf4c712e571e2200f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 8 Jun 2023 22:20:06 +0200 Subject: [PATCH 1796/2916] refactor: Remove unused TargetSummary/ProjectSummary --- pkg/types/result/summary.go | 55 ----------------------- pkg/types/result/zz_generated.deepcopy.go | 55 ----------------------- 2 files changed, 110 deletions(-) diff --git a/pkg/types/result/summary.go b/pkg/types/result/summary.go index dc4c38b74..3ad691bcd 100644 --- a/pkg/types/result/summary.go +++ b/pkg/types/result/summary.go @@ -2,7 +2,6 @@ package result import ( "github.com/kluctl/kluctl/v2/pkg/types" - "sort" ) type CommandResultSummary struct { @@ -30,19 +29,6 @@ type CommandResultSummary struct { TotalChanges int `json:"totalChanges"` } -type TargetSummary struct { - Target TargetKey `json:"target"` - - LastValidateResult *ValidateResult `json:"lastValidateResult,omitempty"` - CommandResults []CommandResultSummary `json:"commandResults,omitempty"` -} - -type ProjectSummary struct { - Project ProjectKey `json:"project"` - - Targets []*TargetSummary `json:"targets"` -} - func (cr *CommandResult) BuildSummary() *CommandResultSummary { if cr == nil { return nil @@ -82,44 +68,3 @@ func (cr *CommandResult) BuildSummary() *CommandResultSummary { } return ret } - -func BuildProjectSummaries(summaries []CommandResultSummary) []*ProjectSummary { - m := map[ProjectKey]*ProjectSummary{} - for _, rs := range summaries { - p, ok := m[rs.ProjectKey] - if !ok { - p = &ProjectSummary{Project: rs.ProjectKey} - m[rs.ProjectKey] = p - } - - var target *TargetSummary - for i, t := range p.Targets { - if t.Target == rs.TargetKey { - target = p.Targets[i] - break - } - } - if target == nil { - target = &TargetSummary{ - Target: rs.TargetKey, - } - p.Targets = append(p.Targets, target) - } - - target.CommandResults = append(target.CommandResults, rs) - } - - ret := make([]*ProjectSummary, 0, len(m)) - for _, p := range m { - sort.Slice(p.Targets, func(i, j int) bool { - return p.Targets[i].Target.Less(p.Targets[j].Target) - }) - ret = append(ret, p) - } - - sort.Slice(ret, func(i, j int) bool { - return ret[i].Project.Less(ret[j].Project) - }) - - return ret -} diff --git a/pkg/types/result/zz_generated.deepcopy.go b/pkg/types/result/zz_generated.deepcopy.go index a461c0278..3c35c8174 100644 --- a/pkg/types/result/zz_generated.deepcopy.go +++ b/pkg/types/result/zz_generated.deepcopy.go @@ -372,33 +372,6 @@ func (in *ProjectKey) DeepCopy() *ProjectKey { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ProjectSummary) DeepCopyInto(out *ProjectSummary) { - *out = *in - out.Project = in.Project - if in.Targets != nil { - in, out := &in.Targets, &out.Targets - *out = make([]*TargetSummary, len(*in)) - for i := range *in { - if (*in)[i] != nil { - in, out := &(*in)[i], &(*out)[i] - *out = new(TargetSummary) - (*in).DeepCopyInto(*out) - } - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProjectSummary. -func (in *ProjectSummary) DeepCopy() *ProjectSummary { - if in == nil { - return nil - } - out := new(ProjectSummary) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ResultObject) DeepCopyInto(out *ResultObject) { *out = *in @@ -442,34 +415,6 @@ func (in *TargetKey) DeepCopy() *TargetKey { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TargetSummary) DeepCopyInto(out *TargetSummary) { - *out = *in - out.Target = in.Target - if in.LastValidateResult != nil { - in, out := &in.LastValidateResult, &out.LastValidateResult - *out = new(ValidateResult) - (*in).DeepCopyInto(*out) - } - if in.CommandResults != nil { - in, out := &in.CommandResults, &out.CommandResults - *out = make([]CommandResultSummary, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TargetSummary. -func (in *TargetSummary) DeepCopy() *TargetSummary { - if in == nil { - return nil - } - out := new(TargetSummary) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ValidateResult) DeepCopyInto(out *ValidateResult) { *out = *in From da2c7bc518fb0b443a32a8c08c7d7c6fe4769fb3 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 8 Jun 2023 22:21:51 +0200 Subject: [PATCH 1797/2916] fix: Use proper keys --- pkg/webui/ui/src/components/targets-view/TargetsView.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/webui/ui/src/components/targets-view/TargetsView.tsx b/pkg/webui/ui/src/components/targets-view/TargetsView.tsx index f480f39f9..f0d2a073c 100644 --- a/pkg/webui/ui/src/components/targets-view/TargetsView.tsx +++ b/pkg/webui/ui/src/components/targets-view/TargetsView.tsx @@ -176,8 +176,8 @@ export const TargetsView = () => { {projects.map((ps, i) => { - return - + return + @@ -195,7 +195,7 @@ export const TargetsView = () => { {ps.targets.map((ts, i) => { - return + return doSetSelectedTargetSummary(ts)} /> @@ -245,7 +245,7 @@ export const TargetsView = () => { {ps.targets.map((ts, i) => { - return + return {ts.commandResults?.map((rs, i) => { return Date: Thu, 8 Jun 2023 22:26:54 +0200 Subject: [PATCH 1798/2916] feat: Use websockets to get updates for summaries and validation results --- go.mod | 1 + go.sum | 16 ++ pkg/webui/generate-ts/main.go | 3 +- pkg/webui/server.go | 92 +++------- pkg/webui/staticbuilder.go | 13 -- pkg/webui/ui/src/api.tsx | 160 ++++++++++++++---- pkg/webui/ui/src/components/App.tsx | 85 +++++++++- pkg/webui/ui/src/components/ObjectYaml.tsx | 3 +- pkg/webui/ui/src/components/Router.tsx | 3 +- .../result-view/CommandResultTree.tsx | 2 +- .../result-view/CommandResultView.tsx | 7 +- .../CommandResultDetailsDrawer.tsx | 6 +- .../targets-view/CommandResultItem.tsx | 7 +- .../src/components/targets-view/Projects.tsx | 2 +- .../targets-view/TargetDetailsDrawer.tsx | 2 +- .../src/components/targets-view/Targets.tsx | 14 +- .../components/targets-view/TargetsView.tsx | 24 +-- pkg/webui/ui/src/models.ts | 65 ++----- pkg/webui/ui/src/project-summaries.ts | 77 +++++++++ pkg/webui/validator.go | 118 ++++++++----- pkg/webui/websocket.go | 108 ++++++++++++ 21 files changed, 549 insertions(+), 259 deletions(-) create mode 100644 pkg/webui/ui/src/project-summaries.ts create mode 100644 pkg/webui/websocket.go diff --git a/go.mod b/go.mod index 4fa6e6eff..d79fdb2ba 100644 --- a/go.mod +++ b/go.mod @@ -259,6 +259,7 @@ require ( k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect k8s.io/kubectl v0.27.1 // indirect k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 // indirect + nhooyr.io/websocket v1.8.7 // indirect oras.land/oras-go v1.2.3 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect ) diff --git a/go.sum b/go.sum index 992fed9f8..7e2749e3d 100644 --- a/go.sum +++ b/go.sum @@ -280,6 +280,7 @@ github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8= github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k= github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= @@ -318,11 +319,15 @@ github.com/go-openapi/jsonreference v0.20.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTr github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k= github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= @@ -338,6 +343,9 @@ github.com/gobuffalo/packr/v2 v2.8.3 h1:xE1yzvnO56cUC0sTpKR3DIbxZgB54AftTFMhB2XE github.com/gobuffalo/packr/v2 v2.8.3/go.mod h1:0SahksCVcx4IMnigTjiFuyldmTrdTctXsOdiU5KwbKc= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= +github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -447,6 +455,7 @@ github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= @@ -532,6 +541,7 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= @@ -546,6 +556,7 @@ github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI= github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= @@ -574,6 +585,7 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -829,6 +841,8 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1 github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/urfave/cli v1.22.12 h1:igJgVw1JdKH+trcLWLeLwZjU9fEfPesQ+9/e4MQ44S8= @@ -1390,6 +1404,8 @@ k8s.io/kubectl v0.27.1 h1:9T5c5KdpburYiW8XKQSH0Uly1kMNE90aGSnbYUZNdcA= k8s.io/kubectl v0.27.1/go.mod h1:QsAkSmrRsKTPlAFzF8kODGDl4p35BIwQnc9XFhkcsy8= k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 h1:kmDqav+P+/5e1i9tFfHq1qcF3sOrDp+YEkVDAHu7Jwk= k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= +nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= oras.land/oras-go v1.2.3 h1:v8PJl+gEAntI1pJ/LCrDgsuk+1PKVavVEPsYIHFE5uY= oras.land/oras-go v1.2.3/go.mod h1:M/uaPdYklze0Vf3AakfarnpoEckvw0ESbRdN8Z1vdJg= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/pkg/webui/generate-ts/main.go b/pkg/webui/generate-ts/main.go index 23a26e7ee..591a4b819 100644 --- a/pkg/webui/generate-ts/main.go +++ b/pkg/webui/generate-ts/main.go @@ -14,10 +14,11 @@ func main() { converter := typescriptify.New(). WithBackupDir(""). Add(result.CommandResult{}). - Add(result.ProjectSummary{}). Add(result.CommandResultSummary{}). + Add(result.ValidateResult{}). Add(webui.ShortName{}). Add(uo.UnstructuredObject{}). + Add(webui.ProjectTargetKey{}). ManageType(types.GitUrl{}, typescriptify.TypeOptions{TSType: "string"}). ManageType(types.GitRepoKey{}, typescriptify.TypeOptions{TSType: "string"}). ManageType(types.YamlUrl{}, typescriptify.TypeOptions{TSType: "string"}). diff --git a/pkg/webui/server.go b/pkg/webui/server.go index 90523fba0..bd49f200d 100644 --- a/pkg/webui/server.go +++ b/pkg/webui/server.go @@ -7,7 +7,6 @@ import ( kluctlv1 "github.com/kluctl/kluctl/v2/api/v1beta1" "github.com/kluctl/kluctl/v2/pkg/results" "github.com/kluctl/kluctl/v2/pkg/status" - "github.com/kluctl/kluctl/v2/pkg/types" "github.com/kluctl/kluctl/v2/pkg/types/k8s" "github.com/kluctl/kluctl/v2/pkg/types/result" "github.com/kluctl/kluctl/v2/pkg/utils/uo" @@ -87,13 +86,13 @@ func (s *CommandResultsServer) Run(port int) error { api := router.Group("/api") api.GET("/getShortNames", s.getShortNames) - api.GET("/listProjects", s.listProjects) - api.GET("/listResults", s.listResults) api.GET("/getResult", s.getResult) + api.GET("/getResultSummary", s.getResultSummary) api.GET("/getResultObject", s.getResultObject) api.POST("/validateNow", s.validateNow) api.POST("/reconcileNow", s.reconcileNow) api.POST("/deployNow", s.deployNow) + api.Any("/ws", s.ws) address := fmt.Sprintf(":%d", port) listener, err := net.Listen("tcp", address) @@ -116,67 +115,6 @@ func (s *CommandResultsServer) getShortNames(c *gin.Context) { c.JSON(http.StatusOK, GetShortNames()) } -func (s *CommandResultsServer) listResults(c *gin.Context) { - args := struct { - FilterProject string `form:"filterProject"` - FilterSubDir string `form:"filterSubDir"` - }{} - err := c.BindQuery(&args) - if err != nil { - _ = c.AbortWithError(http.StatusBadRequest, err) - return - } - - repoKey, err := types.ParseGitRepoKey(args.FilterProject) - if err != nil { - _ = c.AbortWithError(http.StatusBadRequest, err) - return - } - - var filter *result.ProjectKey - if args.FilterProject != "" { - filter = &result.ProjectKey{ - GitRepoKey: repoKey, - SubDir: args.FilterSubDir, - } - } - - summaries, err := s.collector.ListCommandResultSummaries(results.ListCommandResultSummariesOptions{ - ProjectFilter: filter, - }) - if err != nil { - _ = c.AbortWithError(http.StatusBadRequest, err) - return - } - - c.JSON(http.StatusOK, summaries) -} - -func (s *CommandResultsServer) listProjects(c *gin.Context) { - summaries, err := s.collector.ListCommandResultSummaries(results.ListCommandResultSummariesOptions{}) - if err != nil { - _ = c.AbortWithError(http.StatusBadRequest, err) - return - } - - projects := result.BuildProjectSummaries(summaries) - - for _, p := range projects { - for _, t := range p.Targets { - key := projectTargetKey{ - Project: p.Project, - Target: t.Target, - } - vr, err := s.vam.getValidateResult(key) - if err == nil { - t.LastValidateResult = vr - } - } - } - - c.JSON(http.StatusOK, projects) -} - type resultIdParam struct { ResultId string `form:"resultId"` } @@ -226,6 +164,28 @@ func (s *CommandResultsServer) getResult(c *gin.Context) { c.JSON(http.StatusOK, sr) } +func (s *CommandResultsServer) getResultSummary(c *gin.Context) { + var params resultIdParam + + err := c.Bind(¶ms) + if err != nil { + _ = c.AbortWithError(http.StatusBadRequest, err) + return + } + + sr, err := s.collector.GetCommandResultSummary(params.ResultId) + if err != nil { + _ = c.AbortWithError(http.StatusBadRequest, err) + return + } + if sr == nil { + c.AbortWithStatus(http.StatusNotFound) + return + } + + c.JSON(http.StatusOK, sr) +} + func (s *CommandResultsServer) getResultObject(c *gin.Context) { var params resultIdParam var ref refParam @@ -292,14 +252,14 @@ func (s *CommandResultsServer) getResultObject(c *gin.Context) { } func (s *CommandResultsServer) validateNow(c *gin.Context) { - var params projectTargetKey + var params ProjectTargetKey err := c.Bind(¶ms) if err != nil { _ = c.AbortWithError(http.StatusBadRequest, err) return } - key := projectTargetKey{ + key := ProjectTargetKey{ Project: params.Project, Target: params.Target, } diff --git a/pkg/webui/staticbuilder.go b/pkg/webui/staticbuilder.go index 7cb3aad78..142b5f148 100644 --- a/pkg/webui/staticbuilder.go +++ b/pkg/webui/staticbuilder.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "github.com/kluctl/kluctl/v2/pkg/results" - "github.com/kluctl/kluctl/v2/pkg/types/result" "github.com/kluctl/kluctl/v2/pkg/utils" "github.com/kluctl/kluctl/v2/pkg/yaml" cp "github.com/otiai10/copy" @@ -35,8 +34,6 @@ func (swb *StaticWebuiBuilder) Build(path string) error { return err } - projects := result.BuildProjectSummaries(summaries) - err = os.MkdirAll(filepath.Join(tmpDir, "staticdata"), 0o700) if err != nil { return err @@ -89,16 +86,6 @@ func (swb *StaticWebuiBuilder) Build(path string) error { return err } - j, err = yaml.WriteJsonString(projects) - if err != nil { - return err - } - j = `const staticProjects=` + j - err = os.WriteFile(filepath.Join(tmpDir, "staticdata/projects.js"), []byte(j), 0o600) - if err != nil { - return err - } - j, err = yaml.WriteJsonString(GetShortNames()) if err != nil { return err diff --git a/pkg/webui/ui/src/api.tsx b/pkg/webui/ui/src/api.tsx index 6e29ca97c..150c4223f 100644 --- a/pkg/webui/ui/src/api.tsx +++ b/pkg/webui/ui/src/api.tsx @@ -3,7 +3,6 @@ import { CommandResultSummary, ObjectRef, ProjectKey, - ProjectSummary, ResultObject, ShortName, TargetKey @@ -14,6 +13,7 @@ import { Box, Typography } from "@mui/material"; import Tooltip from "@mui/material/Tooltip"; import "./staticbuild.d.ts" import { loadScript } from "./loadscript"; +import { sleep } from "./utils/misc"; const apiUrl = "/api" const staticPath = "./staticdata" @@ -26,30 +26,66 @@ export enum ObjectType { export interface Api { getShortNames(): Promise - listProjects(): Promise - listResults(filterProject?: string, filterSubDir?: string): Promise + listenUpdates(filterProject: string | undefined, filterSubDir: string | undefined, handle: (msg: any) => void): Promise<() => void> getResult(resultId: string): Promise + getResultSummary(resultId: string): Promise getResultObject(resultId: string, ref: ObjectRef, objectType: string): Promise validateNow(project: ProjectKey, target: TargetKey): Promise reconcileNow(cluster: string, name: string, namespace: string): Promise deployNow(cluster: string, name: string, namespace: string): Promise } -let apiPromise: Promise | undefined = undefined -export async function getApi(): Promise { - if (!apiPromise) { - apiPromise = loadScript(staticPath + "/projects.js") +class RealOrStaticApi implements Api { + api: Promise + + constructor() { + this.api = this.buildApi() + } + + async buildApi(): Promise { + const p = loadScript(staticPath + "/summaries.js") + try { + await p + return new StaticApi() + } catch (error) { + return new RealApi() + } + } + + async deployNow(cluster: string, name: string, namespace: string): Promise { + return (await this.api).deployNow(cluster, name, namespace) + } + + async getResult(resultId: string): Promise { + return (await this.api).getResult(resultId) + } + + async getResultObject(resultId: string, ref: ObjectRef, objectType: string): Promise { + return (await this.api).getResultObject(resultId, ref, objectType) + } + + async getResultSummary(resultId: string): Promise { + return (await this.api).getResultSummary(resultId) } - try { - await apiPromise - return new StaticApi() - } catch (error) { - return new RealApi() + async getShortNames(): Promise { + return (await this.api).getShortNames() + } + + async listenUpdates(filterProject: string | undefined, filterSubDir: string | undefined, handle: (msg: any) => void): Promise<() => void> { + return (await this.api).listenUpdates(filterProject, filterSubDir, handle) + } + + async reconcileNow(cluster: string, name: string, namespace: string): Promise { + return (await this.api).reconcileNow(cluster, name, namespace) + } + + async validateNow(project: ProjectKey, target: TargetKey): Promise { + return (await this.api).validateNow(project, target) } } -export let api = getApi() +export const api = new RealOrStaticApi() class RealApi implements Api { async getShortNames(): Promise { @@ -59,15 +95,12 @@ class RealApi implements Api { .then((response) => response.json()); } - async listProjects(): Promise { - let url = `${apiUrl}/listProjects` - return fetch(url) - .then(handleErrors) - .then((response) => response.json()); - } - - async listResults(filterProject?: string, filterSubDir?: string): Promise { - let url = `${apiUrl}/listResults` + async listenUpdates(filterProject: string | undefined, filterSubDir: string | undefined, handle: (msg: any) => void): Promise<() => void> { + let host = window.location.host + if (process.env.NODE_ENV === 'development') { + host = "localhost:9090" + } + let url = `ws://${host}${apiUrl}/ws` const params = new URLSearchParams() if (filterProject) { params.set("filterProject", filterProject) @@ -76,9 +109,44 @@ class RealApi implements Api { params.set("filterSubDir", filterSubDir) } url += "?" + params.toString() - return fetch(url) - .then(handleErrors) - .then((response) => response.json()); + + let ws: WebSocket | undefined; + let cancelled = false + + const connect = () => { + if (cancelled) { + return + } + + console.log("ws connect: " + url) + ws = new WebSocket(url); + ws.onopen = function () { + console.log("ws connected") + } + ws.onclose = function (event) { + console.log("ws close") + if (!cancelled) { + sleep(5000).then(connect) + } + } + ws.onmessage = function (event: MessageEvent) { + if (cancelled) { + return + } + const msg = JSON.parse(event.data) + handle(msg) + } + } + + connect() + + return () => { + console.log("ws cancel") + cancelled = true + if (ws) { + ws.close() + } + } } async getResult(resultId: string) { @@ -91,6 +159,16 @@ class RealApi implements Api { }); } + async getResultSummary(resultId: string) { + let url = `${apiUrl}/getResultSummary?resultId=${resultId}` + return fetch(url) + .then(handleErrors) + .then(response => response.text()) + .then(json => { + return new CommandResultSummary(json) + }); + } + async getResultObject(resultId: string, ref: ObjectRef, objectType: string) { let url = `${apiUrl}/getResultObject?resultId=${resultId}&${buildRefParams(ref)}&objectType=${objectType}` return fetch(url) @@ -139,26 +217,36 @@ class StaticApi implements Api { await loadScript(staticPath + "/shortnames.js") return staticShortNames } - async listProjects(): Promise { - await loadScript(staticPath + "/projects.js") - return staticProjects - } - async listResults(filterProject?: string, filterSubDir?: string): Promise { + + async listenUpdates(filterProject: string | undefined, filterSubDir: string | undefined, handle: (msg: any) => void): Promise<() => void> { await loadScript(staticPath + "/summaries.js") - return staticSummaries.filter(s => { - if (filterProject && filterProject != s.project.normalizedGitUrl) { - return false + + staticSummaries.forEach(rs => { + if (filterProject && filterProject != rs.project.normalizedGitUrl) { + return } - if (filterSubDir && filterSubDir != s.project.subDir) { - return false + if (filterSubDir && filterSubDir != rs.project.subDir) { + return } - return true + handle({ + "type": "update_summary", + "summary": rs, + }) }) + return () => { + } } + async getResult(resultId: string): Promise { await loadScript(staticPath + `/result-${resultId}.js`) return staticResults.get(resultId) } + async getResultSummary(resultId: string): Promise { + await loadScript(staticPath + "/summaries.js") + return staticSummaries.filter(s => { + return s.id == resultId + }).at(0) + } async getResultObject(resultId: string, ref: ObjectRef, objectType: string): Promise { const result = await this.getResult(resultId) const object = result.objects?.find(x => _.isEqual(x.ref, ref)) diff --git a/pkg/webui/ui/src/components/App.tsx b/pkg/webui/ui/src/components/App.tsx index 7c326111b..3c6fa4a20 100644 --- a/pkg/webui/ui/src/components/App.tsx +++ b/pkg/webui/ui/src/components/App.tsx @@ -1,4 +1,4 @@ -import React, { Dispatch, SetStateAction, useState } from 'react'; +import React, { createContext, Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from 'react'; import '../index.css'; import { Box, ThemeProvider } from "@mui/material"; @@ -6,30 +6,99 @@ import { Outlet, useOutletContext } from "react-router-dom"; import LeftDrawer from "./LeftDrawer"; import { light } from './theme'; import { ActiveFilters } from './result-view/NodeStatusFilter'; +import { CommandResultSummary, ProjectTargetKey, ValidateResult } from "../models"; +import { api } from "../api"; +import { buildProjectSummaries, ProjectSummary } from "../project-summaries"; export interface AppOutletContext { filters?: ActiveFilters setFilters: Dispatch> } - export function useAppOutletContext(): AppOutletContext { return useOutletContext() } +export interface AppContextProps { + summaries: Map + projects: ProjectSummary[] + validateResults: Map +} +export const AppContext = createContext({ + summaries: new Map(), + projects: [], + validateResults: new Map(), +}); + const App = () => { + let [summaries, setSummaries] = useState>(new Map()) + let [validateResults, setValidateResults] = useState>(new Map()) const [filters, setFilters] = useState() - const context: AppOutletContext = { + const updateSummary = (rs: CommandResultSummary) => { + console.log("update_summary", rs.id, rs.commandInfo.startTime) + summaries.set(rs.id, rs) + summaries = new Map(summaries) + setSummaries(summaries) + } + + const deleteSummary = (id: string) => { + console.log("delete_summary", id) + summaries.delete(id) + summaries = new Map(summaries) + setSummaries(summaries) + } + + const updateValidateResult = (key: ProjectTargetKey, vr: ValidateResult) => { + console.log("validate_result", key) + validateResults.set(JSON.stringify(key), vr) + validateResults = new Map(validateResults) + setValidateResults(validateResults) + } + + useEffect(() => { + console.log("starting listenResults") + const cancel = api.listenUpdates(undefined, undefined, msg => { + switch(msg.type) { + case "update_summary": + updateSummary(msg.summary) + break + case "delete_summary": + deleteSummary(msg.id) + break + case "validate_result": + updateValidateResult(msg.key, msg.result) + break + } + }) + return () => { + console.log("cancel listenResults") + cancel.then(c => c()) + } + }, []) + + const projects = useMemo(() => { + return buildProjectSummaries(summaries, validateResults) + }, [summaries, validateResults]) + + const appContext = { + summaries: summaries, + projects: projects, + validateResults: validateResults, + } + + const outletContext: AppOutletContext = { filters: filters, setFilters: setFilters, } return ( - - - } context={context} /> - - + + + + } context={outletContext}/> + + + ); }; diff --git a/pkg/webui/ui/src/components/ObjectYaml.tsx b/pkg/webui/ui/src/components/ObjectYaml.tsx index 2d3fee8fe..cf4c9ebc1 100644 --- a/pkg/webui/ui/src/components/ObjectYaml.tsx +++ b/pkg/webui/ui/src/components/ObjectYaml.tsx @@ -1,6 +1,6 @@ import { CommandResultProps } from "./result-view/CommandResultView"; import { ObjectRef } from "../models"; -import { getApi, ObjectType, usePromise } from "../api"; +import { api, ObjectType, usePromise } from "../api"; import React, { Suspense, useEffect, useState } from "react"; import { CodeViewer } from "./CodeViewer"; @@ -11,7 +11,6 @@ export const ObjectYaml = (props: {treeProps: CommandResultProps, objectRef: Obj const [promise, setPromise] = useState>() const getData = async () => { - const api = await getApi() const o = await api.getResultObject(props.treeProps.summary.id, props.objectRef, props.objectType) return yaml.dump(o) } diff --git a/pkg/webui/ui/src/components/Router.tsx b/pkg/webui/ui/src/components/Router.tsx index 0c908fdb7..a08c8abd5 100644 --- a/pkg/webui/ui/src/components/Router.tsx +++ b/pkg/webui/ui/src/components/Router.tsx @@ -1,7 +1,7 @@ import { createHashRouter, useRouteError } from "react-router-dom"; import React from "react"; import App from "./App"; -import { projectsLoader, TargetsView } from "./targets-view/TargetsView"; +import { TargetsView } from "./targets-view/TargetsView"; import { commandResultLoader, CommandResultView } from "./result-view/CommandResultView"; function ErrorPage() { @@ -27,7 +27,6 @@ export const Router = createHashRouter([ { path: "targets", element: , - loader: projectsLoader, errorElement: , }, { diff --git a/pkg/webui/ui/src/components/result-view/CommandResultTree.tsx b/pkg/webui/ui/src/components/result-view/CommandResultTree.tsx index 58bd404b6..eead2b7fa 100644 --- a/pkg/webui/ui/src/components/result-view/CommandResultTree.tsx +++ b/pkg/webui/ui/src/components/result-view/CommandResultTree.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { useEffect, useMemo, useState } from 'react'; +import { useMemo, useState } from 'react'; import TreeView from '@mui/lab/TreeView'; import { TreeItem } from "@mui/lab"; import { NodeBuilder } from "./nodes/NodeBuilder"; diff --git a/pkg/webui/ui/src/components/result-view/CommandResultView.tsx b/pkg/webui/ui/src/components/result-view/CommandResultView.tsx index c1e8ffd96..d5a02d6c7 100644 --- a/pkg/webui/ui/src/components/result-view/CommandResultView.tsx +++ b/pkg/webui/ui/src/components/result-view/CommandResultView.tsx @@ -10,7 +10,7 @@ import { useLoaderData } from "react-router-dom"; import { useAppOutletContext } from "../App"; import { ChangesIcon, CheckboxCheckedIcon, CheckboxIcon, StarIcon, WarningSignIcon } from '../../icons/Icons'; import { dark } from '../theme'; -import { getApi } from "../../api"; +import { api } from "../../api"; export interface CommandResultProps { shortNames: ShortName[] @@ -19,14 +19,13 @@ export interface CommandResultProps { } export async function commandResultLoader({ params }: any) { - const api = await getApi() const result = api.getResult(params.id) const shortNames = api.getShortNames() - const summaries = api.listResults() + const rs = api.getResult(params.id) return { shortNames: await shortNames, - summary: (await summaries).find(x => x.id === params.id), + summary: await rs, commandResult: await result, } } diff --git a/pkg/webui/ui/src/components/targets-view/CommandResultDetailsDrawer.tsx b/pkg/webui/ui/src/components/targets-view/CommandResultDetailsDrawer.tsx index 37c3e11a9..ee83051a1 100644 --- a/pkg/webui/ui/src/components/targets-view/CommandResultDetailsDrawer.tsx +++ b/pkg/webui/ui/src/components/targets-view/CommandResultDetailsDrawer.tsx @@ -1,5 +1,5 @@ -import { CommandResultSummary, ProjectSummary, TargetSummary } from "../../models"; -import { getApi, usePromise } from "../../api"; +import { CommandResultSummary } from "../../models"; +import { api, usePromise } from "../../api"; import { NodeBuilder } from "../result-view/nodes/NodeBuilder"; import { Suspense, useEffect, useState } from "react"; import { NodeData } from "../result-view/nodes/NodeData"; @@ -10,11 +10,11 @@ import { dark } from "../theme"; import { Card, CardCol } from "./Card"; import { CommandResultItem } from "./CommandResultItem"; import React from "react"; +import { ProjectSummary, TargetSummary } from "../../project-summaries"; const sidePanelWidth = 720; async function doGetRootNode(rs: CommandResultSummary) { - const api = await getApi() const shortNames = api.getShortNames() const r = api.getResult(rs.id) const builder = new NodeBuilder({ diff --git a/pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx b/pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx index bf3ff6a52..f4b400e7f 100644 --- a/pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx +++ b/pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { CommandResultSummary, ProjectSummary, TargetSummary } from "../../models"; +import { CommandResultSummary } from "../../models"; import { useEffect, useMemo, useState } from "react"; import * as yaml from "js-yaml"; import { CodeViewer } from "../CodeViewer"; @@ -9,6 +9,7 @@ import { CommandResultStatusLine } from "../result-view/CommandResultStatusLine" import { useNavigate } from "react-router"; import { formatDurationShort } from "../../utils/duration"; import { DeployIcon, DiffIcon, PruneIcon, TreeViewIcon } from "../../icons/Icons"; +import { ProjectSummary, TargetSummary } from "../../project-summaries"; const calcAgo = (startTime: string) => { const t1 = new Date(startTime) @@ -18,8 +19,8 @@ const calcAgo = (startTime: string) => { } export const CommandResultItem = React.memo((props: { - ps: ProjectSummary, - ts: TargetSummary, + ps: ProjectSummary, + ts: TargetSummary, rs: CommandResultSummary, onSelectCommandResult: (rs: CommandResultSummary) => void, selected?: boolean; diff --git a/pkg/webui/ui/src/components/targets-view/Projects.tsx b/pkg/webui/ui/src/components/targets-view/Projects.tsx index c5ebb34f9..1ead486b3 100644 --- a/pkg/webui/ui/src/components/targets-view/Projects.tsx +++ b/pkg/webui/ui/src/components/targets-view/Projects.tsx @@ -1,10 +1,10 @@ -import { ProjectSummary } from "../../models"; import { getLastPathElement } from "../../utils/misc"; import Paper from "@mui/material/Paper"; import { Box, Typography } from "@mui/material"; import React from "react"; import Tooltip from "@mui/material/Tooltip"; import { ProjectIcon } from "../../icons/Icons"; +import { ProjectSummary } from "../../project-summaries"; export const ProjectItem = (props: { ps: ProjectSummary }) => { const name = getLastPathElement(props.ps.project.gitRepoKey) diff --git a/pkg/webui/ui/src/components/targets-view/TargetDetailsDrawer.tsx b/pkg/webui/ui/src/components/targets-view/TargetDetailsDrawer.tsx index b3d45a96b..848db14bc 100644 --- a/pkg/webui/ui/src/components/targets-view/TargetDetailsDrawer.tsx +++ b/pkg/webui/ui/src/components/targets-view/TargetDetailsDrawer.tsx @@ -1,4 +1,3 @@ -import { TargetSummary } from "../../models"; import { Box, Drawer, ThemeProvider } from "@mui/material"; import { SidePanel, SidePanelProvider, SidePanelTab } from "../result-view/SidePanel"; import React from "react"; @@ -7,6 +6,7 @@ import { DiffStatus } from "../result-view/nodes/NodeData"; import { ChangesTable } from "../result-view/ChangesTable"; import { ErrorsTable } from "../ErrorsTable"; import { dark } from "../theme"; +import { TargetSummary } from "../../project-summaries"; class MyProvider implements SidePanelProvider { private ts?: TargetSummary; diff --git a/pkg/webui/ui/src/components/targets-view/Targets.tsx b/pkg/webui/ui/src/components/targets-view/Targets.tsx index 530874065..281622fd0 100644 --- a/pkg/webui/ui/src/components/targets-view/Targets.tsx +++ b/pkg/webui/ui/src/components/targets-view/Targets.tsx @@ -1,12 +1,13 @@ -import { KluctlDeploymentInfo, ProjectSummary, TargetSummary } from "../../models"; +import { KluctlDeploymentInfo } from "../../models"; import { ActionMenuItem, ActionsMenu } from "../ActionsMenu"; import Paper from "@mui/material/Paper"; import { Box, Typography, useTheme } from "@mui/material"; import React from "react"; import Tooltip from "@mui/material/Tooltip"; import { Favorite, HeartBroken, PublishedWithChanges } from "@mui/icons-material"; -import { getApi } from "../../api"; +import { api } from "../../api"; import { CpuIcon, FingerScanIcon, MessageQuestionIcon, TargetIcon } from "../../icons/Icons"; +import { ProjectSummary, TargetSummary } from "../../project-summaries"; const StatusIcon = (props: { ps: ProjectSummary, ts: TargetSummary }) => { let icon: React.ReactElement @@ -57,8 +58,7 @@ export const TargetItem = (props: { ps: ProjectSummary, ts: TargetSummary, onSel actionMenuItems.push({ icon: , text: "Validate now", - handler: async () => { - const api = await getApi() + handler: () => { api.validateNow(props.ps.project, props.ts.target) } }) @@ -81,16 +81,14 @@ export const TargetItem = (props: { ps: ProjectSummary, ts: TargetSummary, onSel actionMenuItems.push({ icon: , text: "Reconcile", - handler: async () => { - const api = await getApi() + handler: () => { api.reconcileNow(props.ts.target.clusterId, kd!.name, kd!.namespace) } }) actionMenuItems.push({ icon: , text: "Deploy", - handler: async () => { - const api = await getApi() + handler: () => { api.deployNow(props.ts.target.clusterId, kd!.name, kd!.namespace) } }) diff --git a/pkg/webui/ui/src/components/targets-view/TargetsView.tsx b/pkg/webui/ui/src/components/targets-view/TargetsView.tsx index f0d2a073c..5eb732de9 100644 --- a/pkg/webui/ui/src/components/targets-view/TargetsView.tsx +++ b/pkg/webui/ui/src/components/targets-view/TargetsView.tsx @@ -1,9 +1,8 @@ -import { useLoaderData } from "react-router-dom"; -import { CommandResultSummary, ProjectSummary, TargetSummary } from "../../models"; +import { CommandResultSummary } from "../../models"; import { Box, Typography, useTheme } from "@mui/material"; -import React, { useCallback, useEffect, useState } from "react"; -import { useAppOutletContext } from "../App"; -import { getApi } from "../../api"; +import React, { useCallback, useContext, useEffect, useState } from "react"; +import { AppContext, useAppOutletContext } from "../App"; +import { api } from "../../api"; import { ProjectItem } from "./Projects"; import { TargetItem } from "./Targets"; import Divider from "@mui/material/Divider"; @@ -11,18 +10,14 @@ import { CommandResultItem } from "./CommandResultItem"; import { CommandResultDetailsDrawer } from "./CommandResultDetailsDrawer"; import { TargetDetailsDrawer } from "./TargetDetailsDrawer"; import { Card, CardCol, CardRow, cardGap, cardHeight, cardWidth, projectCardHeight } from "./Card"; +import { buildProjectSummaries, ProjectSummary, TargetSummary } from "../../project-summaries"; +import { sum } from "lodash"; const colWidth = 416; const curveRadius = 12; const circleRadius = 5; const strokeWidth = 2; -export async function projectsLoader() { - const api = await getApi() - const projects = await api.listProjects() - return projects -} - function ColHeader({ children }: { children: React.ReactNode }) { return { const [selectedCommandResult, setSelectedCommandResult] = useState<{rs: CommandResultSummary, ts: TargetSummary, ps: ProjectSummary} | undefined>(); const [selectedTargetSummary, setSelectedTargetSummary] = useState(); - const projects = useLoaderData() as ProjectSummary[]; - - useEffect(() => { - context.setFilters(undefined) - }) + const appContext = useContext(AppContext) + const projects = appContext.projects const doSetSelectedCommandResult = useCallback((o?: {rs: CommandResultSummary, ts: TargetSummary, ps: ProjectSummary}) => { setSelectedCommandResult(o); diff --git a/pkg/webui/ui/src/models.ts b/pkg/webui/ui/src/models.ts index d0eed1881..333a71ee5 100644 --- a/pkg/webui/ui/src/models.ts +++ b/pkg/webui/ui/src/models.ts @@ -815,44 +815,34 @@ export class ValidateResult { return a; } } -export class TargetSummary { - target: TargetKey; - lastValidateResult?: ValidateResult; - commandResults?: CommandResultSummary[]; +export class ShortName { + group?: string; + kind: string; + shortName: string; constructor(source: any = {}) { if ('string' === typeof source) source = JSON.parse(source); - this.target = this.convertValues(source["target"], TargetKey); - this.lastValidateResult = this.convertValues(source["lastValidateResult"], ValidateResult); - this.commandResults = this.convertValues(source["commandResults"], CommandResultSummary); + this.group = source["group"]; + this.kind = source["kind"]; + this.shortName = source["shortName"]; } +} +export class UnstructuredObject { - convertValues(a: any, classs: any, asMap: boolean = false): any { - if (!a) { - return a; - } - if (a.slice) { - return (a as any[]).map(elem => this.convertValues(elem, classs)); - } else if ("object" === typeof a) { - if (asMap) { - for (const key of Object.keys(a)) { - a[key] = new classs(a[key]); - } - return a; - } - return new classs(a); - } - return a; - } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + + } } -export class ProjectSummary { +export class ProjectTargetKey { project: ProjectKey; - targets: TargetSummary[]; + target: TargetKey; constructor(source: any = {}) { if ('string' === typeof source) source = JSON.parse(source); this.project = this.convertValues(source["project"], ProjectKey); - this.targets = this.convertValues(source["targets"], TargetSummary); + this.target = this.convertValues(source["target"], TargetKey); } convertValues(a: any, classs: any, asMap: boolean = false): any { @@ -872,25 +862,4 @@ export class ProjectSummary { } return a; } -} - -export class ShortName { - group?: string; - kind: string; - shortName: string; - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.group = source["group"]; - this.kind = source["kind"]; - this.shortName = source["shortName"]; - } -} -export class UnstructuredObject { - - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - - } } \ No newline at end of file diff --git a/pkg/webui/ui/src/project-summaries.ts b/pkg/webui/ui/src/project-summaries.ts new file mode 100644 index 000000000..17d4a526c --- /dev/null +++ b/pkg/webui/ui/src/project-summaries.ts @@ -0,0 +1,77 @@ +import { + CommandResultSummary, + ProjectKey, + ProjectTargetKey, TargetKey, ValidateResult, +} from "./models"; +import _ from "lodash"; + +export interface TargetSummary { + target: TargetKey; + lastValidateResult?: ValidateResult; + commandResults: CommandResultSummary[]; +} + +export interface ProjectSummary { + project: ProjectKey; + targets: TargetSummary[]; +} + +export function compareSummaries(a: CommandResultSummary, b: CommandResultSummary) { + return b.commandInfo.startTime.localeCompare(a.commandInfo.startTime) || + b.commandInfo.endTime.localeCompare(b.commandInfo.endTime) || + (b.commandInfo.command || "").localeCompare(a.commandInfo.command || "") +} + +export function buildProjectSummaries(summaries: Map, validateResults: Map) { + const sorted = Array.from(summaries.values()) + sorted.sort(compareSummaries) + + const m = new Map() + sorted.forEach(rs => { + const projectKey = JSON.stringify(rs.projectKey) + + let p = m.get(projectKey) + if (!p) { + p = { + project: rs.projectKey, + targets: [] + } + m.set(projectKey, p) + } + + const ptKey = new ProjectTargetKey() + ptKey.project = rs.projectKey + ptKey.target = rs.targetKey + + const vr = validateResults.get(JSON.stringify(ptKey)) + + let target = p.targets.find(t => _.isEqual(t.target, rs.targetKey)) + if (!target) { + target = { + target: rs.targetKey, + lastValidateResult: vr, + commandResults: [] + } + p.targets.push(target) + } + + target.commandResults.push(rs) + }) + + const ret: ProjectSummary[] = [] + m.forEach(p => { + p.targets.sort((a, b) => { + return (a.target.targetName || "").localeCompare(b.target.targetName || "") || + a.target.clusterId.localeCompare(b.target.clusterId) || + (a.target.discriminator || "")?.localeCompare(b.target.discriminator || "") + }) + ret.push(p) + }) + + ret.sort((a, b) => { + return (a.project.gitRepoKey || "").localeCompare(b.project.gitRepoKey || "") || + (a.project.subDir || "").localeCompare(b.project.subDir || "") + }) + + return ret +} \ No newline at end of file diff --git a/pkg/webui/validator.go b/pkg/webui/validator.go index 78ba366bf..6b524ca76 100644 --- a/pkg/webui/validator.go +++ b/pkg/webui/validator.go @@ -11,7 +11,9 @@ import ( ) const shortValidationInterval = time.Second * 15 -const longValidationInterval = time.Minute * 5 +const longValidationInterval = time.Minute * 1 + +type validateResultHandler func(key ProjectTargetKey, r *result.ValidateResult) type validatorManager struct { ctx context.Context @@ -19,22 +21,23 @@ type validatorManager struct { store results.ResultStore cam *clusterAccessorManager - validators map[projectTargetKey]*validatorEntry + validators map[ProjectTargetKey]*validatorEntry + handlers map[int]validateResultHandler + nextId int mutex sync.Mutex } -type projectTargetKey struct { +type ProjectTargetKey struct { Project result.ProjectKey `json:"project"` Target result.TargetKey `json:"target"` } type validatorEntry struct { vm *validatorManager - key projectTargetKey + key ProjectTargetKey ch chan bool validateResult *result.ValidateResult err error - mutex sync.Mutex } func newValidatorManager(ctx context.Context, store results.ResultStore, cam *clusterAccessorManager) *validatorManager { @@ -42,7 +45,8 @@ func newValidatorManager(ctx context.Context, store results.ResultStore, cam *cl ctx: ctx, store: store, cam: cam, - validators: map[projectTargetKey]*validatorEntry{}, + validators: map[ProjectTargetKey]*validatorEntry{}, + handlers: map[int]validateResultHandler{}, } } @@ -53,31 +57,29 @@ func (vm *validatorManager) start() { return } - projects := result.BuildProjectSummaries(summaries) - - found := map[projectTargetKey]bool{} - for _, project := range projects { - for _, target := range project.Targets { - k := projectTargetKey{ - Project: project.Project, - Target: target.Target, - } - - vm.mutex.Lock() - _, ok := vm.validators[k] - if !ok { - v := &validatorEntry{ - vm: vm, - key: k, - ch: make(chan bool), - } - vm.validators[k] = v - go v.run() + found := map[ProjectTargetKey]bool{} + for _, rs := range summaries { + k := ProjectTargetKey{ + Project: rs.ProjectKey, + Target: rs.TargetKey, + } + if _, ok := found[k]; ok { + continue + } + vm.mutex.Lock() + _, ok := vm.validators[k] + if !ok { + v := &validatorEntry{ + vm: vm, + key: k, + ch: make(chan bool), } - vm.mutex.Unlock() - - found[k] = true + vm.validators[k] = v + go v.run() } + vm.mutex.Unlock() + + found[k] = true } vm.mutex.Lock() @@ -98,7 +100,29 @@ func (vm *validatorManager) start() { }() } -func (vm *validatorManager) getValidateResult(key projectTargetKey) (*result.ValidateResult, error) { +func (vm *validatorManager) addHandler(h validateResultHandler) func() { + vm.mutex.Lock() + defer vm.mutex.Unlock() + + id := vm.nextId + vm.nextId++ + + vm.handlers[id] = h + + for _, v := range vm.validators { + if v.validateResult != nil { + h(v.key, v.validateResult) + } + } + + return func() { + vm.mutex.Lock() + defer vm.mutex.Unlock() + delete(vm.handlers, id) + } +} + +func (vm *validatorManager) getValidateResult(key ProjectTargetKey) (*result.ValidateResult, error) { vm.mutex.Lock() defer vm.mutex.Unlock() v := vm.validators[key] @@ -108,7 +132,7 @@ func (vm *validatorManager) getValidateResult(key projectTargetKey) (*result.Val return v.validateResult, v.err } -func (vm *validatorManager) validateNow(key projectTargetKey) bool { +func (vm *validatorManager) validateNow(key ProjectTargetKey) bool { vm.mutex.Lock() defer vm.mutex.Unlock() v := vm.validators[key] @@ -130,21 +154,19 @@ func (v *validatorEntry) run() { if err != nil { return } - projects := result.BuildProjectSummaries(summaries) - longWait := false - outer: - for _, p := range projects { - if p.Project == v.key.Project { - for _, t := range p.Targets { - if t.Target == v.key.Target { - longWait = v.runOnce(t) - break outer - } - } + var targetSummaries []result.CommandResultSummary + for _, rs := range summaries { + if rs.TargetKey == v.key.Target { + targetSummaries = append(targetSummaries, rs) } } + longWait := false + if len(targetSummaries) != 0 { + longWait = v.runOnce(targetSummaries) + } + waitTime := shortValidationInterval if longWait { waitTime = longValidationInterval @@ -173,11 +195,11 @@ func (v *validatorEntry) findFullProjectResult(summaries []result.CommandResultS return nil } -func (v *validatorEntry) runOnce(target *result.TargetSummary) bool { +func (v *validatorEntry) runOnce(summaries []result.CommandResultSummary) bool { s := status.Start(v.vm.ctx, "Validating: %v", v.key) defer s.Failed() - summary := v.findFullProjectResult(target.CommandResults) + summary := v.findFullProjectResult(summaries) if summary == nil { s.FailedWithMessage("No result summaries for %v", v.key) return false @@ -214,10 +236,14 @@ func (v *validatorEntry) runOnce(target *result.TargetSummary) bool { } s.Success() - v.mutex.Lock() - defer v.mutex.Unlock() + v.vm.mutex.Lock() + defer v.vm.mutex.Unlock() v.validateResult = vr v.err = err + for _, h := range v.vm.handlers { + h(v.key, vr) + } + return true } diff --git a/pkg/webui/websocket.go b/pkg/webui/websocket.go new file mode 100644 index 000000000..978642ffb --- /dev/null +++ b/pkg/webui/websocket.go @@ -0,0 +1,108 @@ +package webui + +import ( + "context" + "github.com/gin-gonic/gin" + "github.com/kluctl/kluctl/v2/pkg/results" + "github.com/kluctl/kluctl/v2/pkg/types" + "github.com/kluctl/kluctl/v2/pkg/types/result" + "github.com/kluctl/kluctl/v2/pkg/yaml" + "net/http" + "nhooyr.io/websocket" + "time" +) + +func (s *CommandResultsServer) ws(c *gin.Context) { + args := struct { + FilterProject string `form:"filterProject"` + FilterSubDir string `form:"filterSubDir"` + }{} + err := c.BindQuery(&args) + if err != nil { + _ = c.AbortWithError(http.StatusBadRequest, err) + return + } + + conn, err := websocket.Accept(c.Writer, c.Request, &websocket.AcceptOptions{ + InsecureSkipVerify: true, + }) + if err != nil { + //s.logf("%v", err) + return + } + defer conn.Close(websocket.StatusInternalError, "the sky is falling") + + repoKey, err := types.ParseGitRepoKey(args.FilterProject) + if err != nil { + _ = c.AbortWithError(http.StatusBadRequest, err) + return + } + + var filter *result.ProjectKey + if args.FilterProject != "" { + filter = &result.ProjectKey{ + GitRepoKey: repoKey, + SubDir: args.FilterSubDir, + } + } + + sendMessage := func(msg string) error { + ctx, cancel := context.WithTimeout(s.ctx, 500*time.Millisecond) + defer cancel() + w, err := conn.Writer(ctx, websocket.MessageText) + if err != nil { + return err + } + _, err = w.Write([]byte(msg)) + if err != nil { + _ = w.Close() + return err + } + err = w.Close() + if err != nil { + ctx = nil + } + return err + } + + cancel, err := s.collector.WatchCommandResultSummaries(results.ListCommandResultSummariesOptions{ProjectFilter: filter}, + func(summary *result.CommandResultSummary) { + x := yaml.WriteJsonStringMust(map[string]any{ + "type": "update_summary", + "summary": summary, + }) + _ = sendMessage(x) + }, func(id string) { + x := yaml.WriteJsonStringMust(map[string]any{ + "type": "delete_summary", + "id": id, + }) + _ = sendMessage(x) + }, + ) + if err != nil { + _ = c.AbortWithError(http.StatusInternalServerError, err) + return + } + defer cancel() + + h := s.vam.addHandler(func(key ProjectTargetKey, r *result.ValidateResult) { + x := yaml.WriteJsonStringMust(map[string]any{ + "type": "validate_result", + "key": key, + "result": r, + }) + _ = sendMessage(x) + }) + defer h() + + _, _, err = conn.Read(s.ctx) + if err != nil { + cs := websocket.CloseStatus(err) + if cs == websocket.StatusNormalClosure || cs == websocket.StatusGoingAway { + return + } + _ = c.AbortWithError(http.StatusInternalServerError, err) + return + } +} From 64758af361ce666712744198fb9bf93e5db233a5 Mon Sep 17 00:00:00 2001 From: Alexey Vagarenko Date: Fri, 9 Jun 2023 06:51:52 +0600 Subject: [PATCH 1799/2916] Added animations. --- .../result-view/CommandResultView.tsx | 2 +- .../CommandResultDetailsDrawer.tsx | 117 ++++++++++++++---- .../targets-view/CommandResultItem.tsx | 33 ++--- .../components/targets-view/TargetsView.tsx | 29 +++-- 4 files changed, 132 insertions(+), 49 deletions(-) diff --git a/pkg/webui/ui/src/components/result-view/CommandResultView.tsx b/pkg/webui/ui/src/components/result-view/CommandResultView.tsx index d5a02d6c7..79fa85254 100644 --- a/pkg/webui/ui/src/components/result-view/CommandResultView.tsx +++ b/pkg/webui/ui/src/components/result-view/CommandResultView.tsx @@ -3,7 +3,7 @@ import { useState } from 'react'; import { Box, Checkbox, CheckboxProps, Divider, Drawer, FormControlLabel, ThemeProvider, Typography } from "@mui/material"; import { CommandResult, CommandResultSummary, ShortName } from "../../models"; import { NodeData } from "./nodes/NodeData"; -import { SidePanel, SidePanelProvider } from "./SidePanel"; +import { SidePanel } from "./SidePanel"; import { ActiveFilters } from "./NodeStatusFilter"; import CommandResultTree from "./CommandResultTree"; import { useLoaderData } from "react-router-dom"; diff --git a/pkg/webui/ui/src/components/targets-view/CommandResultDetailsDrawer.tsx b/pkg/webui/ui/src/components/targets-view/CommandResultDetailsDrawer.tsx index ee83051a1..be88c7ecf 100644 --- a/pkg/webui/ui/src/components/targets-view/CommandResultDetailsDrawer.tsx +++ b/pkg/webui/ui/src/components/targets-view/CommandResultDetailsDrawer.tsx @@ -1,16 +1,17 @@ import { CommandResultSummary } from "../../models"; import { api, usePromise } from "../../api"; import { NodeBuilder } from "../result-view/nodes/NodeBuilder"; -import { Suspense, useEffect, useState } from "react"; +import { Suspense, useEffect, useMemo, useRef, useState } from "react"; import { NodeData } from "../result-view/nodes/NodeData"; import { SidePanel } from "../result-view/SidePanel"; import { Box, Drawer, ThemeProvider, useTheme } from "@mui/material"; import { Loading } from "../Loading"; import { dark } from "../theme"; -import { Card, CardCol } from "./Card"; +import { Card, cardGap, cardHeight } from "./Card"; import { CommandResultItem } from "./CommandResultItem"; import React from "react"; import { ProjectSummary, TargetSummary } from "../../project-summaries"; +import { cardWidth } from "./Card"; const sidePanelWidth = 720; @@ -22,7 +23,7 @@ async function doGetRootNode(rs: CommandResultSummary) { summary: rs, commandResult: await r, }) - const [node, nodeMap] = builder.buildRoot() + const [node] = builder.buildRoot() return node } @@ -30,13 +31,16 @@ export const CommandResultDetailsDrawer = React.memo((props: { rs?: CommandResultSummary, ts?: TargetSummary, ps?: ProjectSummary, - onClose: () => void + onClose: () => void, + selectedCardRect?: DOMRect }) => { - const { ps, ts } = props; + const { ps, ts, selectedCardRect } = props; const theme = useTheme(); const [promise, setPromise] = useState>(new Promise(() => undefined)); const [selectedCommandResult, setSelectedCommandResult] = useState(); const [prevTargetSummary, setPrevTargetSummary] = useState(ts); + const [cardsCoords, setCardsCoords] = useState<{ left: number, top: number }[]>([]); + const [transitionRunning, setTransitionRunning] = useState(false); if (prevTargetSummary !== ts) { setPrevTargetSummary(ts); @@ -55,8 +59,81 @@ export const CommandResultDetailsDrawer = React.memo((props: { return } + const cardsContainerElem = useRef(); + + useEffect(() => { + const rect = cardsContainerElem.current?.getBoundingClientRect(); + if (!rect || !selectedCardRect || !ts?.commandResults) { + setCardsCoords([]); + return; + } + + const initialCoords = ts.commandResults.map(() => ({ + left: selectedCardRect.left - rect.left, + top: selectedCardRect.top - rect.top + })); + + setCardsCoords(initialCoords); + setTransitionRunning(true); + }, [selectedCardRect, ts?.commandResults]); + + useEffect(() => { + if (cardsCoords.length > 0) { + const targetCoords = cardsCoords.map((_, i) => ({ + left: 0, + top: i * (cardHeight + cardGap) + })); + + if (cardsCoords.length === targetCoords.length + && cardsCoords.every(({ left, top }, i) => + targetCoords[i].left === left && targetCoords[i].top === top + ) + ) { + return; + } + + setTimeout(() => { + setCardsCoords(targetCoords); + setTimeout(() => { + setTransitionRunning(false); + }, theme.transitions.duration.enteringScreen) + }, 10); + } + }, [cardsCoords, theme.transitions.duration.enteringScreen]) + + const zIndex = theme.zIndex.modal + 1; + + const cards = useMemo(() => { + if (!(ps && ts && ts.commandResults && ts.commandResults.length > 0 && cardsCoords.length > 0)) { + return null; + } + + return ts.commandResults.map((rs, i) => { + return + + + }) + }, [ps, ts, cardsCoords, zIndex, theme.transitions, selectedCommandResult]) + return <> - {ps && ts && + {ps && ts && ts.commandResults && ts.commandResults.length > 0 && - e.stopPropagation()} flexGrow={1} justifyContent='center'> - {ts.commandResults?.map((rs, i) => { - return - - - })} - + e.stopPropagation()} + position='relative' + width={cardWidth} + height={cardHeight * ts.commandResults.length + cardGap * (ts.commandResults.length - 1)} + ref={cardsContainerElem} + > + {cards} + } diff --git a/pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx b/pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx index f4b400e7f..0dee155bd 100644 --- a/pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx +++ b/pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx @@ -18,18 +18,19 @@ const calcAgo = (startTime: string) => { return formatDurationShort(d) } -export const CommandResultItem = React.memo((props: { +export const CommandResultItem = React.memo((props: { ps: ProjectSummary, ts: TargetSummary, - rs: CommandResultSummary, + rs: CommandResultSummary, onSelectCommandResult: (rs: CommandResultSummary) => void, selected?: boolean; }) => { + const { rs, onSelectCommandResult, selected } = props; const navigate = useNavigate() - const [ago, setAgo] = useState(calcAgo(props.rs.commandInfo.startTime)) + const [ago, setAgo] = useState(calcAgo(rs.commandInfo.startTime)) let Icon: () => JSX.Element = DiffIcon - switch (props.rs.commandInfo?.command) { + switch (rs.commandInfo?.command) { case "delete": Icon = PruneIcon break @@ -47,15 +48,15 @@ export const CommandResultItem = React.memo((props: { break } - const cmdInfoYaml = useMemo(() => { - return yaml.dump(props.rs.commandInfo) - }, [props.rs]) - let iconTooltip = + const iconTooltip = useMemo(() => { + const cmdInfoYaml = yaml.dump(rs.commandInfo); + return + }, [rs.commandInfo]); useEffect(() => { - const interval = setInterval(() => setAgo(calcAgo(props.rs.commandInfo.startTime)), 5000); + const interval = setInterval(() => setAgo(calcAgo(rs.commandInfo.startTime)), 5000); return () => clearInterval(interval); - }, [props.rs.commandInfo.startTime]) + }, [rs.commandInfo.startTime]); return props.onSelectCommandResult(props.rs)} + onClick={() => onSelectCommandResult(rs)} > @@ -85,9 +86,9 @@ export const CommandResultItem = React.memo((props: { overflow='hidden' flexGrow={1} > - {props.rs.commandInfo?.command} + {rs.commandInfo?.command} - + - + { e.stopPropagation(); - navigate(`/results/${props.rs.id}`); + navigate(`/results/${rs.id}`); }} sx={{ padding: 0, diff --git a/pkg/webui/ui/src/components/targets-view/TargetsView.tsx b/pkg/webui/ui/src/components/targets-view/TargetsView.tsx index 5eb732de9..2ed8c3982 100644 --- a/pkg/webui/ui/src/components/targets-view/TargetsView.tsx +++ b/pkg/webui/ui/src/components/targets-view/TargetsView.tsx @@ -128,27 +128,29 @@ export const TargetsView = () => { const context = useAppOutletContext() const [selectedCommandResult, setSelectedCommandResult] = useState<{rs: CommandResultSummary, ts: TargetSummary, ps: ProjectSummary} | undefined>(); const [selectedTargetSummary, setSelectedTargetSummary] = useState(); + const [selectedCardRect, setSelectedCardRect] = useState(); const appContext = useContext(AppContext) const projects = appContext.projects - const doSetSelectedCommandResult = useCallback((o?: {rs: CommandResultSummary, ts: TargetSummary, ps: ProjectSummary}) => { - setSelectedCommandResult(o); + const onTargetDetailsDrawerClose = useCallback(() => { setSelectedTargetSummary(undefined); }, []); - const doSetSelectedTargetSummary = useCallback((ts?: TargetSummary) => { + const onCommandResultDetailsDrawerClose = useCallback(() => { setSelectedCommandResult(undefined); - setSelectedTargetSummary(ts); + setSelectedCardRect(undefined); }, []); - const onCommandResultDetailsDrawerClose = useCallback(() => { - doSetSelectedCommandResult(undefined); - }, [doSetSelectedCommandResult]); - - const onTargetDetailsDrawerClose = useCallback(() => { - setSelectedTargetSummary(undefined); - }, [setSelectedTargetSummary]); + const doSetSelectedCommandResult = useCallback((o?: {rs: CommandResultSummary, ts: TargetSummary, ps: ProjectSummary}) => { + onTargetDetailsDrawerClose(); + setSelectedCommandResult(o); + }, [onTargetDetailsDrawerClose]); + + const doSetSelectedTargetSummary = useCallback((ts?: TargetSummary) => { + onCommandResultDetailsDrawerClose(); + setSelectedTargetSummary(ts); + }, [onCommandResultDetailsDrawerClose]); return { ts={selectedCommandResult?.ts} ps={selectedCommandResult?.ps} onClose={onCommandResultDetailsDrawerClose} + selectedCardRect={selectedCardRect} /> { zIndex: -i, display: i < 4 ? 'flex' : 'none' }} + onClick={(e) => { + const rect = e.currentTarget.getBoundingClientRect(); + setSelectedCardRect(rect); + }} > Date: Fri, 9 Jun 2023 10:08:59 +0200 Subject: [PATCH 1800/2916] fix: Enlarge TargetDetailsDrawer --- .../ui/src/components/targets-view/TargetDetailsDrawer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/webui/ui/src/components/targets-view/TargetDetailsDrawer.tsx b/pkg/webui/ui/src/components/targets-view/TargetDetailsDrawer.tsx index 848db14bc..7b60afdea 100644 --- a/pkg/webui/ui/src/components/targets-view/TargetDetailsDrawer.tsx +++ b/pkg/webui/ui/src/components/targets-view/TargetDetailsDrawer.tsx @@ -104,7 +104,7 @@ export const TargetDetailsDrawer = React.memo((props: { ts?: TargetSummary, onCl onClose={props.onClose} ModalProps={{ BackdropProps: { invisible: true } }} > - + From 5574285f7f57d3c30b8b75c064b21a93ab712149 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 9 Jun 2023 11:33:22 +0200 Subject: [PATCH 1801/2916] chore: Optimize imports --- pkg/webui/ui/src/components/App.tsx | 2 +- pkg/webui/ui/src/components/LeftDrawer.tsx | 2 +- .../src/components/result-view/CommandResultView.tsx | 11 ++++++++++- .../src/components/result-view/nodes/ObjectNode.tsx | 8 +------- .../targets-view/CommandResultDetailsDrawer.tsx | 6 ++---- .../src/components/targets-view/CommandResultItem.tsx | 3 +-- .../ui/src/components/targets-view/TargetsView.tsx | 8 +++----- pkg/webui/ui/src/components/theme.ts | 2 +- pkg/webui/ui/src/project-summaries.ts | 6 +----- 9 files changed, 21 insertions(+), 27 deletions(-) diff --git a/pkg/webui/ui/src/components/App.tsx b/pkg/webui/ui/src/components/App.tsx index 3c6fa4a20..e41af9bd3 100644 --- a/pkg/webui/ui/src/components/App.tsx +++ b/pkg/webui/ui/src/components/App.tsx @@ -1,4 +1,4 @@ -import React, { createContext, Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from 'react'; +import React, { createContext, Dispatch, SetStateAction, useEffect, useMemo, useState } from 'react'; import '../index.css'; import { Box, ThemeProvider } from "@mui/material"; diff --git a/pkg/webui/ui/src/components/LeftDrawer.tsx b/pkg/webui/ui/src/components/LeftDrawer.tsx index 5bd0db728..75e4d18bb 100644 --- a/pkg/webui/ui/src/components/LeftDrawer.tsx +++ b/pkg/webui/ui/src/components/LeftDrawer.tsx @@ -13,7 +13,7 @@ import ListItemIcon from '@mui/material/ListItemIcon'; import ListItemText from '@mui/material/ListItemText'; import { Link, useLocation, useNavigate } from "react-router-dom"; import { AppOutletContext } from "./App"; -import { KluctlLogo, TargetsIcon, KluctlText, SearchIcon, ArrowLeftIcon } from '../icons/Icons'; +import { ArrowLeftIcon, KluctlLogo, KluctlText, SearchIcon, TargetsIcon } from '../icons/Icons'; import { dark } from './theme'; import { Typography } from '@mui/material'; diff --git a/pkg/webui/ui/src/components/result-view/CommandResultView.tsx b/pkg/webui/ui/src/components/result-view/CommandResultView.tsx index 79fa85254..70c16cb4b 100644 --- a/pkg/webui/ui/src/components/result-view/CommandResultView.tsx +++ b/pkg/webui/ui/src/components/result-view/CommandResultView.tsx @@ -1,6 +1,15 @@ import * as React from 'react'; import { useState } from 'react'; -import { Box, Checkbox, CheckboxProps, Divider, Drawer, FormControlLabel, ThemeProvider, Typography } from "@mui/material"; +import { + Box, + Checkbox, + CheckboxProps, + Divider, + Drawer, + FormControlLabel, + ThemeProvider, + Typography +} from "@mui/material"; import { CommandResult, CommandResultSummary, ShortName } from "../../models"; import { NodeData } from "./nodes/NodeData"; import { SidePanel } from "./SidePanel"; diff --git a/pkg/webui/ui/src/components/result-view/nodes/ObjectNode.tsx b/pkg/webui/ui/src/components/result-view/nodes/ObjectNode.tsx index ec851d53f..24b14c524 100644 --- a/pkg/webui/ui/src/components/result-view/nodes/ObjectNode.tsx +++ b/pkg/webui/ui/src/components/result-view/nodes/ObjectNode.tsx @@ -2,13 +2,7 @@ import React from 'react'; import { ObjectRef } from "../../../models"; import { NodeData } from "./NodeData"; -import { - PublishedWithChanges, - Settings, - SettingsEthernet, - SmartToy, - SvgIconComponent -} from "@mui/icons-material"; +import { PublishedWithChanges, Settings, SettingsEthernet, SmartToy, SvgIconComponent } from "@mui/icons-material"; import { PropertiesTable } from "../../PropertiesTable"; import { findObjectByRef, ObjectType } from "../../../api"; import { CommandResultProps } from "../CommandResultView"; diff --git a/pkg/webui/ui/src/components/targets-view/CommandResultDetailsDrawer.tsx b/pkg/webui/ui/src/components/targets-view/CommandResultDetailsDrawer.tsx index be88c7ecf..d4c7aee8c 100644 --- a/pkg/webui/ui/src/components/targets-view/CommandResultDetailsDrawer.tsx +++ b/pkg/webui/ui/src/components/targets-view/CommandResultDetailsDrawer.tsx @@ -1,17 +1,15 @@ import { CommandResultSummary } from "../../models"; import { api, usePromise } from "../../api"; import { NodeBuilder } from "../result-view/nodes/NodeBuilder"; -import { Suspense, useEffect, useMemo, useRef, useState } from "react"; +import React, { Suspense, useEffect, useMemo, useRef, useState } from "react"; import { NodeData } from "../result-view/nodes/NodeData"; import { SidePanel } from "../result-view/SidePanel"; import { Box, Drawer, ThemeProvider, useTheme } from "@mui/material"; import { Loading } from "../Loading"; import { dark } from "../theme"; -import { Card, cardGap, cardHeight } from "./Card"; +import { Card, cardGap, cardHeight, cardWidth } from "./Card"; import { CommandResultItem } from "./CommandResultItem"; -import React from "react"; import { ProjectSummary, TargetSummary } from "../../project-summaries"; -import { cardWidth } from "./Card"; const sidePanelWidth = 720; diff --git a/pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx b/pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx index 0dee155bd..76c86de0c 100644 --- a/pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx +++ b/pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx @@ -1,6 +1,5 @@ -import React from "react"; +import React, { useEffect, useMemo, useState } from "react"; import { CommandResultSummary } from "../../models"; -import { useEffect, useMemo, useState } from "react"; import * as yaml from "js-yaml"; import { CodeViewer } from "../CodeViewer"; import Paper from "@mui/material/Paper"; diff --git a/pkg/webui/ui/src/components/targets-view/TargetsView.tsx b/pkg/webui/ui/src/components/targets-view/TargetsView.tsx index 2ed8c3982..ef04d8c9c 100644 --- a/pkg/webui/ui/src/components/targets-view/TargetsView.tsx +++ b/pkg/webui/ui/src/components/targets-view/TargetsView.tsx @@ -1,17 +1,15 @@ import { CommandResultSummary } from "../../models"; import { Box, Typography, useTheme } from "@mui/material"; -import React, { useCallback, useContext, useEffect, useState } from "react"; +import React, { useCallback, useContext, useState } from "react"; import { AppContext, useAppOutletContext } from "../App"; -import { api } from "../../api"; import { ProjectItem } from "./Projects"; import { TargetItem } from "./Targets"; import Divider from "@mui/material/Divider"; import { CommandResultItem } from "./CommandResultItem"; import { CommandResultDetailsDrawer } from "./CommandResultDetailsDrawer"; import { TargetDetailsDrawer } from "./TargetDetailsDrawer"; -import { Card, CardCol, CardRow, cardGap, cardHeight, cardWidth, projectCardHeight } from "./Card"; -import { buildProjectSummaries, ProjectSummary, TargetSummary } from "../../project-summaries"; -import { sum } from "lodash"; +import { Card, CardCol, cardGap, cardHeight, CardRow, cardWidth, projectCardHeight } from "./Card"; +import { ProjectSummary, TargetSummary } from "../../project-summaries"; const colWidth = 416; const curveRadius = 12; diff --git a/pkg/webui/ui/src/components/theme.ts b/pkg/webui/ui/src/components/theme.ts index 701d20a15..85c0250e6 100644 --- a/pkg/webui/ui/src/components/theme.ts +++ b/pkg/webui/ui/src/components/theme.ts @@ -1,4 +1,4 @@ -import { PaletteOptions, ThemeOptions, createTheme } from '@mui/material/styles'; +import { createTheme, PaletteOptions, ThemeOptions } from '@mui/material/styles'; const paletteDark = { primary: { main: '#DFEBE9' }, diff --git a/pkg/webui/ui/src/project-summaries.ts b/pkg/webui/ui/src/project-summaries.ts index 17d4a526c..62a6a5011 100644 --- a/pkg/webui/ui/src/project-summaries.ts +++ b/pkg/webui/ui/src/project-summaries.ts @@ -1,8 +1,4 @@ -import { - CommandResultSummary, - ProjectKey, - ProjectTargetKey, TargetKey, ValidateResult, -} from "./models"; +import { CommandResultSummary, ProjectKey, ProjectTargetKey, TargetKey, ValidateResult, } from "./models"; import _ from "lodash"; export interface TargetSummary { From 9fd6b036b40bb511c9d868e9d754b5ee1fb28d64 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 9 Jun 2023 12:42:10 +0200 Subject: [PATCH 1802/2916] fix: Rename react app to kluctl-webui --- pkg/webui/ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/webui/ui/package.json b/pkg/webui/ui/package.json index 68d21f982..2efb3d9b1 100644 --- a/pkg/webui/ui/package.json +++ b/pkg/webui/ui/package.json @@ -1,5 +1,5 @@ { - "name": "react-demo", + "name": "kluctl-webui", "version": "0.1.0", "homepage": "./", "private": true, From 5faeb119a8807c535c5eb8fecf3ab66ebab14ae0 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 9 Jun 2023 12:43:25 +0200 Subject: [PATCH 1803/2916] feat: Introduce fix-assets --- pkg/webui/ui/fix-assets/main.go | 119 ++++++++++++++++++++++++++++++++ pkg/webui/ui/package.json | 5 +- 2 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 pkg/webui/ui/fix-assets/main.go diff --git a/pkg/webui/ui/fix-assets/main.go b/pkg/webui/ui/fix-assets/main.go new file mode 100644 index 000000000..76132b6a9 --- /dev/null +++ b/pkg/webui/ui/fix-assets/main.go @@ -0,0 +1,119 @@ +package main + +import ( + "encoding/json" + "io/fs" + "k8s.io/apimachinery/pkg/util/rand" + "os" + "path" + "path/filepath" + "regexp" + "strings" +) + +type assetManifest struct { + Files map[string]string `json:"files"` + Entrypoints []string `json:"entrypoints"` +} + +func main() { + doError := func(err error) { + if err == nil { + return + } + os.Stderr.WriteString(err.Error() + "\n") + os.Exit(1) + } + + err := os.Chdir("build") + doError(err) + + s, err := os.ReadFile("asset-manifest.json") + doError(err) + + var manifest assetManifest + err = json.Unmarshal(s, &manifest) + doError(err) + + hashRegex := regexp.MustCompile(`([a-z-0-9-_]*)(\.[a-f0-9]*)(\.chunk)?\.(js|css)(\.map)?`) + + newEntries := map[string]string{} + for p1, p2 := range manifest.Files { + oldName := path.Base(p2) + newName := path.Base(p1) + oldPath := path.Join(path.Dir(p2), oldName) + newPath := path.Join(path.Dir(p2), newName) + + m := hashRegex.FindSubmatch([]byte(newName)) + if m != nil { + s := strings.Split(newName, ".") + // get rid of hash + s = append(s[0:1], s[2:]...) + newName = strings.Join(s, ".") + newPath = path.Join(path.Dir(p2), newName) + + delete(manifest.Files, p1) + newEntries[newName] = newPath + } else { + manifest.Files[p1] = newPath + } + + // we assume that .map files are implicitly replaced by the non-.map versions + if !strings.HasSuffix(oldName, ".map") { + err = replaceInFiles(".", oldName, newName+"?f="+oldName) + doError(err) + } + + err = os.Rename(oldPath, newPath) + doError(err) + + for i, e := range manifest.Entrypoints { + if path.Base(e) == oldName { + manifest.Entrypoints[i] = path.Join(path.Dir(e), newName) + } + } + } + for k, v := range newEntries { + manifest.Files[k] = v + } + + s, err = json.MarshalIndent(&manifest, "", " ") + doError(err) + err = os.WriteFile("asset-manifest.json", s, 0o600) + doError(err) +} + +func replaceInFiles(dir string, s string, r string) error { + return filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { + if d.IsDir() { + return nil + } + f, err := os.ReadFile(path) + if err != nil { + return err + } + + dummy1 := rand.String(32) + dummy2 := rand.String(32) + + f2 := string(f) + + f2 = strings.ReplaceAll(f2, "f="+s, dummy1) + f2 = strings.ReplaceAll(f2, s+".map", dummy2) + + f2 = strings.ReplaceAll(f2, s, r) + + f2 = strings.ReplaceAll(f2, dummy1, "f="+s) + f2 = strings.ReplaceAll(f2, dummy2, s+".map") + + if strings.Compare(string(f), f2) == 0 { + return nil + } + + err = os.WriteFile(path, []byte(f2), 0o600) + if err != nil { + return err + } + return nil + }) +} diff --git a/pkg/webui/ui/package.json b/pkg/webui/ui/package.json index 2efb3d9b1..610d3ae1a 100644 --- a/pkg/webui/ui/package.json +++ b/pkg/webui/ui/package.json @@ -36,9 +36,10 @@ }, "scripts": { "start": "react-scripts start", - "build": "node build", + "build": "node build && npm run fix-assets", "test": "react-scripts test", - "eject": "react-scripts eject" + "eject": "react-scripts eject", + "fix-assets": "go run ./fix-assets" }, "eslintConfig": { "extends": [ From 7c6388774d4f6d745bbbf3cd9fad7a27b94d94b8 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 9 Jun 2023 13:58:05 +0200 Subject: [PATCH 1804/2916] ci: Check for up-to-date webui build --- .github/workflows/tests.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2a4ffa48f..cb0239ea7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -19,6 +19,11 @@ jobs: - uses: actions/setup-go@v4 with: go-version: '1.19' + - uses: actions/setup-node@v3 + with: + node-version: 20 + cache: 'npm' + cache-dependency-path: pkg/webui/ui/package-lock.json - uses: actions/cache@v3 with: path: | @@ -66,6 +71,17 @@ jobs: git diff exit 1 fi + - name: Verify webui build is up-to-date + run: | + cd pkg/webui/ui + npm install + npm run build + if [ ! -z "$(git status --porcelain)" ]; then + echo "npm run build must be invoked and the result committed" + git status + git diff + exit 1 + fi tests: strategy: From 52b6f6e84948d94861beb0258ce5934554d780db Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 9 Jun 2023 14:25:17 +0200 Subject: [PATCH 1805/2916] chore: Update all npm packages --- pkg/webui/ui/package-lock.json | 5917 ++++++++++++-------------------- pkg/webui/ui/package.json | 23 +- 2 files changed, 2152 insertions(+), 3788 deletions(-) diff --git a/pkg/webui/ui/package-lock.json b/pkg/webui/ui/package-lock.json index a31048de4..fa6d08424 100644 --- a/pkg/webui/ui/package-lock.json +++ b/pkg/webui/ui/package-lock.json @@ -1,26 +1,27 @@ { - "name": "react-demo", + "name": "kluctl-webui", "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "react-demo", + "name": "kluctl-webui", "version": "0.1.0", "dependencies": { - "@emotion/react": "^11.10.6", - "@emotion/styled": "^11.10.6", - "@fontsource-variable/nunito": "^5.0.2", - "@mui/icons-material": "^5.11.11", + "@babel/plugin-proposal-private-property-in-object": "^7.21.11", + "@emotion/react": "^11.11.1", + "@emotion/styled": "^11.11.0", + "@fontsource-variable/nunito": "^5.0.3", + "@mui/icons-material": "^5.11.16", "@mui/lab": "^5.0.0-alpha.124", - "@mui/material": "^5.11.14", + "@mui/material": "^5.13.4", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "@types/jest": "^27.5.2", - "@types/node": "^16.18.18", - "@types/react": "^18.0.28", - "@types/react-dom": "^18.0.11", + "@types/node": "^16.18.34", + "@types/react": "^18.2.9", + "@types/react-dom": "^18.2.4", "jdenticon": "^3.2.0", "js-sha256": "^0.9.0", "js-yaml": "^4.1.0", @@ -30,7 +31,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-router": "^6.10.0", - "react-router-dom": "^6.10.0", + "react-router-dom": "^6.12.1", "react-scripts": "5.0.1", "react-syntax-highlighter": "^15.5.0", "sort-by": "^1.2.0", @@ -39,8 +40,8 @@ }, "devDependencies": { "@types/js-yaml": "^4.0.5", - "@types/lodash": "^4.14.192", - "@types/react-syntax-highlighter": "^15.5.6", + "@types/lodash": "^4.14.195", + "@types/react-syntax-highlighter": "^15.5.7", "rewire": "^6.0.0" } }, @@ -49,12 +50,23 @@ "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.2.0.tgz", "integrity": "sha512-E09FiIft46CmH5Qnjb0wsW54/YQd69LsxeKUOWawmws1XWvyFGURnAChH0mlr7YPFR1ofwvUQfcL0J3lMxXqPA==" }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@ampproject/remapping": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", "dependencies": { - "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" }, "engines": { @@ -62,39 +74,39 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", + "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", "dependencies": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.0.tgz", - "integrity": "sha512-gMuZsmsgxk/ENC3O/fRw5QY8A9/uxQbbCEypnLIiYYc/qVJtEV7ouxC3EllIIwNzMqAQee5tanFabWsUOutS7g==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.5.tgz", + "integrity": "sha512-4Jc/YuIaYqKnDDz892kPIledykKg12Aw1PYX5i/TY28anJtacvM1Rrr8wbieB9GfEJwlzqT0hUEao0CxEebiDA==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.3.tgz", - "integrity": "sha512-qIJONzoa/qiHghnm0l1n4i/6IIziDpzqc36FBs4pzMhDUraHqponwJLiAKm1hGLP3OSB/TVNz6rMwVGpwxxySw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.5.tgz", + "integrity": "sha512-SBuTAjg91A3eKOvD+bPEz3LlhHZRNu1nFOVts9lzDJTXshHTjII0BAtDS3Y2DAkdZdDKWVZGVwkDfc4Clxn1dg==", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.21.3", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-module-transforms": "^7.21.2", - "@babel/helpers": "^7.21.0", - "@babel/parser": "^7.21.3", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.3", - "@babel/types": "^7.21.3", + "@babel/code-frame": "^7.22.5", + "@babel/generator": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helpers": "^7.22.5", + "@babel/parser": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -118,9 +130,9 @@ } }, "node_modules/@babel/eslint-parser": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.21.3.tgz", - "integrity": "sha512-kfhmPimwo6k4P8zxNs8+T7yR44q1LdpsZdE1NkCsVlfiuTPRfnGgjaF8Qgug9q9Pou17u6wneYF0lDCZJATMFg==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.22.5.tgz", + "integrity": "sha512-C69RWYNYtrgIRE5CmTd77ZiLDXqgBipahJc/jHP3sLcAGj6AJzxNIuKNpVnICqbyK7X3pFUfEvL++rvtbQpZkQ==", "dependencies": { "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", "eslint-visitor-keys": "^2.1.0", @@ -151,11 +163,11 @@ } }, "node_modules/@babel/generator": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.3.tgz", - "integrity": "sha512-QS3iR1GYC/YGUnW7IdggFeN5c1poPUurnGttOV/bZgPGV+izC/D8HnD6DLwod0fsatNyVn1G3EVWMYIF0nHbeA==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.5.tgz", + "integrity": "sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==", "dependencies": { - "@babel/types": "^7.21.3", + "@babel/types": "^7.22.5", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -164,49 +176,35 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", - "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", - "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.5.tgz", + "integrity": "sha512-m1EP3lVOPptR+2DwD125gziZNcmoNSHGmJROKoy87loWUQyJaVXDgpmruWqDARZSmtYQ+Dl25okU8+qhVzuykw==", "dependencies": { - "@babel/helper-explode-assignable-expression": "^7.18.6", - "@babel/types": "^7.18.9" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", - "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.5.tgz", + "integrity": "sha512-Ji+ywpHeuqxB8WDxraCiqR0xfhYjiDE/e6k7FuIaANnoOFxAHskHChz4vA1mJC9Lbm01s1PVAGhQY4FUKSkGZw==", "dependencies": { - "@babel/compat-data": "^7.20.5", - "@babel/helper-validator-option": "^7.18.6", + "@babel/compat-data": "^7.22.5", + "@babel/helper-validator-option": "^7.22.5", "browserslist": "^4.21.3", "lru-cache": "^5.1.1", "semver": "^6.3.0" @@ -227,18 +225,19 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.0.tgz", - "integrity": "sha512-Q8wNiMIdwsv5la5SPxNYzzkPnjgC0Sy0i7jLkVOCdllu/xcVNkr3TeZzbHBJrj+XXRqzX5uCyCoV9eu6xUG7KQ==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-member-expression-to-functions": "^7.21.0", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-replace-supers": "^7.20.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", - "@babel/helper-split-export-declaration": "^7.18.6" + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.5.tgz", + "integrity": "sha512-xkb58MyOYIslxu3gKmVXmjTtUPvBU4odYzbiIQbWwLKIHCsx6UGZGX6F1IznMFVnDdirseUZopzN+ZRt8Xb33Q==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-member-expression-to-functions": "^7.22.5", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.5", + "semver": "^6.3.0" }, "engines": { "node": ">=6.9.0" @@ -247,13 +246,22 @@ "@babel/core": "^7.0.0" } }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.21.0.tgz", - "integrity": "sha512-N+LaFW/auRSWdx7SHD/HiARwXQju1vXTW4fKr4u5SgBUTm51OKEjKgj+cs00ggW3kEvNqwErnlwuq7Y3xBe4eg==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.5.tgz", + "integrity": "sha512-1VpEFOIbMRaXyDeUwUfmTIxExLwQ+zkW+Bh5zXpApA3oQedBx9v/updixWxnx/bZpKw7u8VxWjb/qWpIcmPq8A==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "regexpu-core": "^5.3.1" + "@babel/helper-annotate-as-pure": "^7.22.5", + "regexpu-core": "^5.3.1", + "semver": "^6.3.0" }, "engines": { "node": ">=6.9.0" @@ -262,10 +270,18 @@ "@babel/core": "^7.0.0" } }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", - "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.0.tgz", + "integrity": "sha512-RnanLx5ETe6aybRi1cO/edaRH+bNYWaryCEmjDDYyNr4wnSzyOp8T0dWipmqVHKEY3AbVKUom50AKSlj1zmKbg==", "dependencies": { "@babel/helper-compilation-targets": "^7.17.7", "@babel/helper-plugin-utils": "^7.16.7", @@ -287,115 +303,104 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-explode-assignable-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", - "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", - "dependencies": { - "@babel/types": "^7.18.6" - }, + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", + "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", - "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", + "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", "dependencies": { - "@babel/template": "^7.20.7", - "@babel/types": "^7.21.0" + "@babel/template": "^7.22.5", + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.21.0.tgz", - "integrity": "sha512-Muu8cdZwNN6mRRNG6lAYErJ5X3bRevgYR2O8wN0yn7jJSnGDu6eG59RfT29JHxGUovyfrh6Pj0XzmR7drNVL3Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz", + "integrity": "sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ==", "dependencies": { - "@babel/types": "^7.21.0" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz", + "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==", "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.21.2", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz", - "integrity": "sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.5.tgz", + "integrity": "sha512-+hGKDt/Ze8GFExiVHno/2dvG5IdstpzCq0y4Qc9OJ25D4q3pKfiIP/4Vp3/JvhDkLKsDK2api3q3fpIgiIF5bw==", "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.20.2", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.19.1", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.2", - "@babel/types": "^7.21.2" + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", - "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", + "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", - "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", - "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.5.tgz", + "integrity": "sha512-cU0Sq1Rf4Z55fgz7haOakIyM7+x/uCFwXpLPaeRzfoUtAEAuUZjZvFPjL/rk5rW693dIgn2hng1W7xbT7lWT4g==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-wrap-function": "^7.18.9", - "@babel/types": "^7.18.9" + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-wrap-function": "^7.22.5", + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -405,111 +410,111 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.20.7.tgz", - "integrity": "sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A==", - "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-member-expression-to-functions": "^7.20.7", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.20.7", - "@babel/types": "^7.20.7" + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.5.tgz", + "integrity": "sha512-aLdNM5I3kdI/V9xGNyKSF3X/gTyMUBohTZ+/3QdQKAA9vxIiy12E+8E2HoOP1/DjeqU+g6as35QHJNMDDYpuCg==", + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-member-expression-to-functions": "^7.22.5", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-simple-access": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", - "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", "dependencies": { - "@babel/types": "^7.20.2" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz", - "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", + "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", "dependencies": { - "@babel/types": "^7.20.0" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz", + "integrity": "sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ==", "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", - "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", + "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz", - "integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz", + "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.20.5.tgz", - "integrity": "sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.5.tgz", + "integrity": "sha512-bYqLIBSEshYcYQyfks8ewYA8S30yaGSeRslcvKMvoUk6HHPySbxHq9YRi6ghhzEU+yhQv9bP/jXnygkStOcqZw==", "dependencies": { - "@babel/helper-function-name": "^7.19.0", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.20.5", - "@babel/types": "^7.20.5" + "@babel/helper-function-name": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.0.tgz", - "integrity": "sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.5.tgz", + "integrity": "sha512-pSXRmfE1vzcUIDFQcSGA5Mr+GxBV9oiRKDuDxXvWQQBCh8HoIjs/2DlDB7H8smac1IVrB9/xdXj2N3Wol9Cr+Q==", "dependencies": { - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.0", - "@babel/types": "^7.21.0" + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", + "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", + "@babel/helper-validator-identifier": "^7.22.5", "chalk": "^2.0.0", "js-tokens": "^4.0.0" }, @@ -517,150 +522,118 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/parser": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.3.tgz", - "integrity": "sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ==", - "bin": { - "parser": "bin/babel-parser.js" + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" }, "engines": { - "node": ">=6.0.0" + "node": ">=4" } }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz", - "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">=4" } }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.20.7.tgz", - "integrity": "sha512-sbr9+wNE5aXMBBFBICk01tt7sBf2Oc9ikRFEcem/ZORup9IMUdNhW7/wVLEbbtlWOsEubJet46mHAL2C8+2jKQ==", + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", - "@babel/plugin-proposal-optional-chaining": "^7.20.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.13.0" + "color-name": "1.1.3" } }, - "node_modules/@babel/plugin-proposal-async-generator-functions": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz", - "integrity": "sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==", - "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-remap-async-to-generator": "^7.18.9", - "@babel/plugin-syntax-async-generators": "^7.8.4" - }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=0.8.0" } }, - "node_modules/@babel/plugin-proposal-class-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", - "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=4" } }, - "node_modules/@babel/plugin-proposal-class-static-block": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.21.0.tgz", - "integrity": "sha512-XP5G9MWNUskFuP30IfFSEFB0Z6HzLIUcjYM4bYOPHXl7eiJ9HFv8tWj6TXTN5QODiEhDZAeI4hLok2iHFFV4hw==", + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.21.0", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-class-static-block": "^7.14.5" + "has-flag": "^3.0.0" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" + "node": ">=4" } }, - "node_modules/@babel/plugin-proposal-decorators": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.21.0.tgz", - "integrity": "sha512-MfgX49uRrFUTL/HvWtmx3zmpyzMMr4MTj3d527MLlr/4RTT9G/ytFFP7qet2uM2Ve03b+BkpWUpK+lRXnQ+v9w==", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.21.0", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-replace-supers": "^7.20.7", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/plugin-syntax-decorators": "^7.21.0" + "node_modules/@babel/parser": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz", + "integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==", + "bin": { + "parser": "bin/babel-parser.js" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-proposal-dynamic-import": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", - "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.5.tgz", + "integrity": "sha512-NP1M5Rf+u2Gw9qfSO4ihjcTGW5zXTi36ITLd4/EoAcEhIZ0yjMqmftDNl3QC19CX7olhrjpyU454g/2W7X0jvQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-proposal-export-namespace-from": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", - "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.5.tgz", + "integrity": "sha512-31Bb65aZaUwqCbWMnZPduIZxCBngHFlzyN6Dq6KAJjtx+lx6ohKHubc61OomYi7XwVD4Ol0XCVz4h+pYFR048g==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-transform-optional-chaining": "^7.22.5" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.13.0" } }, - "node_modules/@babel/plugin-proposal-json-strings": { + "node_modules/@babel/plugin-proposal-class-properties": { "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", - "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-json-strings": "^7.8.3" + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -669,13 +642,16 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz", - "integrity": "sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==", + "node_modules/@babel/plugin-proposal-decorators": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.22.5.tgz", + "integrity": "sha512-h8hlezQ4dl6ixodgXkH8lUfcD7x+WAuIqPUjwGoItynrXOAv4a4Tci1zA/qjzQjjcl0v3QpLdc2LM6ZACQuY7A==", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.5", + "@babel/plugin-syntax-decorators": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -714,39 +690,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-object-rest-spread": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", - "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", - "dependencies": { - "@babel/compat-data": "^7.20.5", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.20.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-optional-catch-binding": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", - "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-proposal-optional-chaining": { "version": "7.21.0", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", @@ -779,9 +722,9 @@ } }, "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0.tgz", - "integrity": "sha512-ha4zfehbJjc5MmXBlHec1igel5TJXXLDDRbuJ4+XT2TJcyD9/V1919BA8gMvsdHcNMBy4WBUBiRb3nw/EQUtBw==", + "version": "7.21.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.11.tgz", + "integrity": "sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw==", "dependencies": { "@babel/helper-annotate-as-pure": "^7.18.6", "@babel/helper-create-class-features-plugin": "^7.21.0", @@ -858,11 +801,11 @@ } }, "node_modules/@babel/plugin-syntax-decorators": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.21.0.tgz", - "integrity": "sha512-tIoPpGBR8UuM4++ccWN3gifhVvQu7ZizuR1fklhRJrd5ewgbkUS+0KVFeWWxELtn18NTLoW32XV7zyOgIAiz+w==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.22.5.tgz", + "integrity": "sha512-avpUOBS7IU6al8MmF1XpAyj9QYeLPuSDJI5D4pVMSMdL7xQokKqJPYQC67RCT0aCTashUXPiGwMJ0DEXXCEmMA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -894,11 +837,11 @@ } }, "node_modules/@babel/plugin-syntax-flow": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.18.6.tgz", - "integrity": "sha512-LUbR+KNTBWCUAqRG9ex5Gnzu2IOkt8jRJbHHXFT9q+L9zm7M/QQbEqXyw1n1pohYvOyWC8CjeyjrSaIwiYjK7A==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.22.5.tgz", + "integrity": "sha512-9RdCl0i+q0QExayk2nOS7853w08yLucnnPML6EN9S8fgMPVtdLDCdx/cOQ/i44Lb9UeQX9A35yaqBBOMMZxPxQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -908,11 +851,25 @@ } }, "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz", - "integrity": "sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz", + "integrity": "sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.5.tgz", + "integrity": "sha512-KwvoWDeNKPETmozyFE0P2rOLqh39EoQHNjqizrI5B8Vt0ZNS7M56s7dAiAqbYfiAYOuIzIh96z3iR2ktgu3tEg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -944,11 +901,11 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", - "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", + "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1052,11 +1009,11 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz", - "integrity": "sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz", + "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1065,28 +1022,27 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.20.7.tgz", - "integrity": "sha512-3poA5E7dzDomxj9WXWwuD6A5F3kc7VXwIJO+E+J8qtDtS+pXPAhrgEyh+9GBwBgPq1Z+bB+/JD60lp5jsN7JPQ==", + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.20.7.tgz", - "integrity": "sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q==", + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz", + "integrity": "sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw==", "dependencies": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-remap-async-to-generator": "^7.18.9" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1095,12 +1051,15 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", - "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.5.tgz", + "integrity": "sha512-gGOEvFzm3fWoyD5uZq7vVTD57pPJ3PczPUD/xCFGjzBpUosnklmXyKnGQbbbGs1NPNPskFex0j93yKbHt0cHyg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.5", + "@babel/plugin-syntax-async-generators": "^7.8.4" }, "engines": { "node": ">=6.9.0" @@ -1109,12 +1068,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.21.0.tgz", - "integrity": "sha512-Mdrbunoh9SxwFZapeHVrwFmri16+oYotcZysSzhNIVDwIAb1UV+kvnxULSYq9J3/q5MDG+4X6w8QVgD1zhBXNQ==", + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz", + "integrity": "sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" + "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1123,20 +1084,12 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.21.0.tgz", - "integrity": "sha512-RZhbYTCEUAe6ntPehC4hlslPWosNHDox+vAs4On/mCLRLfoDVHf6hVEd7kuxr1RnHwJmxFfUM3cZiZRmPxJPXQ==", + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz", + "integrity": "sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-replace-supers": "^7.20.7", - "@babel/helper-split-export-declaration": "^7.18.6", - "globals": "^11.1.0" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1145,13 +1098,12 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.20.7.tgz", - "integrity": "sha512-Lz7MvBK6DTjElHAmfu6bfANzKcxpyNPeYBGEafyA6E5HtRpjpZwU+u7Qrgz/2OR0z+5TvKYbPdphfSaAcZBrYQ==", + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.5.tgz", + "integrity": "sha512-EcACl1i5fSQ6bt+YGuU/XGCeZKStLmyVGytWkpyhCLeQVA0eu6Wtiw92V+I1T/hnezUv7j74dA/Ro69gWcU+hg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/template": "^7.20.7" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1160,12 +1112,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.21.3.tgz", - "integrity": "sha512-bp6hwMFzuiE4HqYEyoGJ/V2LeIWn+hLVKc4pnj++E5XQptwhtcGmSayM029d/j2X1bPKGTlsyPwAubuU22KhMA==", + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.5.tgz", + "integrity": "sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" + "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1174,28 +1127,110 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", - "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.5.tgz", + "integrity": "sha512-SPToJ5eYZLxlnp1UzdARpOGeC2GbHvr9d/UV0EukuVx8atktg194oe+C5BqQ8jRTkgLRVOPYeXRSBg1IlMoVRA==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-class-static-block": "^7.14.5" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.12.0" } }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", - "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.5.tgz", + "integrity": "sha512-2edQhLfibpWpsVBx2n/GKOz6JdGQvLruZQfGr9l1qes2KQaWswjBzhQF7UDUZMNaMMQeYnQzxwOMPsbYF7wqPQ==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.5", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz", + "integrity": "sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/template": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.5.tgz", + "integrity": "sha512-GfqcFuGW8vnEqTUBM7UtPd5A4q797LTvvwKxXTgRsFjoqaJiEg9deBG6kWeQYkVEL569NpnmpC0Pkr/8BLKGnQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz", + "integrity": "sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz", + "integrity": "sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.5.tgz", + "integrity": "sha512-0MC3ppTB1AMxd8fXjSrbPa7LT9hrImt+/fcj+Pg5YMD7UQyWp/02+JWpdnCymmsXwIx5Z+sYn1bwCn4ZJNvhqQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, "engines": { "node": ">=6.9.0" }, @@ -1204,12 +1239,27 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", - "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz", + "integrity": "sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g==", "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.5.tgz", + "integrity": "sha512-X4hhm7FRnPgd4nDA4b/5V280xCx6oL7Oob5+9qVS5C13Zq4bh1qq7LU0GgRU6b5dBWBvhGaXYVB4AcN6+ol6vg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" }, "engines": { "node": ">=6.9.0" @@ -1219,12 +1269,12 @@ } }, "node_modules/@babel/plugin-transform-flow-strip-types": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.21.0.tgz", - "integrity": "sha512-FlFA2Mj87a6sDkW4gfGrQQqwY/dLlBAyJa2dJEZ+FHXUVHBflO2wyKvg+OOEzXfrKYIa4HWl0mgmbCzt0cMb7w==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.22.5.tgz", + "integrity": "sha512-tujNbZdxdG0/54g/oua8ISToaXTFBf8EnSb5PgQSciIXWOWKX3S4+JR7ZE9ol8FZwf9kxitzkGQ+QWeov/mCiA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-flow": "^7.18.6" + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-flow": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1234,11 +1284,11 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.21.0.tgz", - "integrity": "sha512-LlUYlydgDkKpIY7mcBWvyPPmMcOphEyYA27Ef4xpbh1IiDNLr0kZsos2nf92vz3IccvJI25QUwp86Eo5s6HmBQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.5.tgz", + "integrity": "sha512-3kxQjX1dU9uudwSshyLeEipvrLjBCVthCgeTp6CzE/9JYrlAIaeekVxRpCWsDDfYTfRZRoCeZatCQvwo+wvK8A==", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1248,13 +1298,28 @@ } }, "node_modules/@babel/plugin-transform-function-name": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", - "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz", + "integrity": "sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg==", + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.5.tgz", + "integrity": "sha512-DuCRB7fu8MyTLbEQd1ew3R85nx/88yMoqo2uPSjevMj3yoN7CDM8jkgrY0wmVxfJZyJ/B9fE1iq7EQppWQmR5A==", "dependencies": { - "@babel/helper-compilation-targets": "^7.18.9", - "@babel/helper-function-name": "^7.18.9", - "@babel/helper-plugin-utils": "^7.18.9" + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-json-strings": "^7.8.3" }, "engines": { "node": ">=6.9.0" @@ -1264,11 +1329,26 @@ } }, "node_modules/@babel/plugin-transform-literals": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", - "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz", + "integrity": "sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.5.tgz", + "integrity": "sha512-MQQOUW1KL8X0cDWfbwYP+TbVbZm16QmQXJQ+vndPtH/BoO0lOKpVoEDMI7+PskYxH+IiE0tS8xZye0qr1lGzSA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" }, "engines": { "node": ">=6.9.0" @@ -1278,11 +1358,11 @@ } }, "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", - "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz", + "integrity": "sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1292,12 +1372,12 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.20.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.20.11.tgz", - "integrity": "sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.22.5.tgz", + "integrity": "sha512-R+PTfLTcYEmb1+kK7FNkhQ1gP4KgjpSO6HfH9+f8/yfp2Nt3ggBjiVpRwmwTlfqZLafYKJACy36yDXlEmI9HjQ==", "dependencies": { - "@babel/helper-module-transforms": "^7.20.11", - "@babel/helper-plugin-utils": "^7.20.2" + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1307,13 +1387,13 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.21.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.21.2.tgz", - "integrity": "sha512-Cln+Yy04Gxua7iPdj6nOV96smLGjpElir5YwzF0LBPKoPlLDNJePNlrGGaybAJkd0zKRnOVXOgizSqPYMNYkzA==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.5.tgz", + "integrity": "sha512-B4pzOXj+ONRmuaQTg05b3y/4DuFz3WcCNAXPLb2Q0GT0TrGKGxNKV4jwsXts+StaM0LQczZbOpj8o1DLPDJIiA==", "dependencies": { - "@babel/helper-module-transforms": "^7.21.2", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-simple-access": "^7.20.2" + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1323,14 +1403,14 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.20.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.20.11.tgz", - "integrity": "sha512-vVu5g9BPQKSFEmvt2TA4Da5N+QVS66EX21d8uoOihC+OCpUoGvzVsXeqFdtAEfVa5BILAeFt+U7yVmLbQnAJmw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.5.tgz", + "integrity": "sha512-emtEpoaTMsOs6Tzz+nbmcePl6AKVtS1yC4YNAeMun9U8YCsgadPNxnOPQ8GhHFB2qdx+LZu9LgoC0Lthuu05DQ==", "dependencies": { - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-module-transforms": "^7.20.11", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-validator-identifier": "^7.19.1" + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1340,12 +1420,12 @@ } }, "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", - "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz", + "integrity": "sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ==", "dependencies": { - "@babel/helper-module-transforms": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1355,12 +1435,12 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.20.5.tgz", - "integrity": "sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", + "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.20.5", - "@babel/helper-plugin-utils": "^7.20.2" + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1370,11 +1450,59 @@ } }, "node_modules/@babel/plugin-transform-new-target": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", - "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz", + "integrity": "sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.5.tgz", + "integrity": "sha512-6CF8g6z1dNYZ/VXok5uYkkBBICHZPiGEl7oDnAx2Mt1hlHVHOSIKWJaXHjQJA5VB43KZnXZDIexMchY4y2PGdA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.5.tgz", + "integrity": "sha512-NbslED1/6M+sXiwwtcAB/nieypGw02Ejf4KtDeMkCEpP6gWFMX1wI9WKYua+4oBneCCEmulOkRpwywypVZzs/g==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.5.tgz", + "integrity": "sha512-Kk3lyDmEslH9DnvCDA1s1kkd3YWQITiBOHngOtDL9Pt6BZjzqb6hiOlb8VfjiiQJ2unmegBqZu0rx5RxJb5vmQ==", + "dependencies": { + "@babel/compat-data": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1384,12 +1512,43 @@ } }, "node_modules/@babel/plugin-transform-object-super": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", - "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz", + "integrity": "sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-replace-supers": "^7.18.6" + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.5.tgz", + "integrity": "sha512-pH8orJahy+hzZje5b8e2QIlBWQvGpelS76C63Z+jhZKsmzfNaPQ+LaW6dcJ9bxTpo1mtXbgHwy765Ro3jftmUg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.5.tgz", + "integrity": "sha512-AconbMKOMkyG+xCng2JogMCDcqW8wedQAqpVIL4cOSescZ7+iW8utC6YDZLMCSUIReEA733gzRSaOSXMAt/4WQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" }, "engines": { "node": ">=6.9.0" @@ -1399,11 +1558,43 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.21.3.tgz", - "integrity": "sha512-Wxc+TvppQG9xWFYatvCGPvZ6+SIUxQ2ZdiBP+PHYMIjnPXD+uThCshaz4NZOnODAtBjjcVQQ/3OKs9LW28purQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.5.tgz", + "integrity": "sha512-AVkFUBurORBREOmHRKo06FjHYgjrabpdqRSwq6+C7R5iTCZOsM4QbcB27St0a4U6fffyAOqh3s/qEfybAhfivg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.5.tgz", + "integrity": "sha512-PPjh4gyrQnGe97JTalgRGMuU4icsZFnWkzicB/fUtzlKUqvsWBKEpPPfr5a2JiyirZkHxnAqkQMO5Z5B2kK3fA==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.5.tgz", + "integrity": "sha512-/9xnaTTJcVoBtSSmrVyhtSvO3kbqS2ODoh2juEU72c3aYonNF0OMGiaz2gjukyKM2wBBYJP38S4JiE0Wfb5VMQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" }, "engines": { "node": ">=6.9.0" @@ -1413,11 +1604,11 @@ } }, "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", - "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz", + "integrity": "sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1427,11 +1618,11 @@ } }, "node_modules/@babel/plugin-transform-react-constant-elements": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.21.3.tgz", - "integrity": "sha512-4DVcFeWe/yDYBLp0kBmOGFJ6N2UYg7coGid1gdxb4co62dy/xISDMaYBXBVXEDhfgMk7qkbcYiGtwd5Q/hwDDQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.22.5.tgz", + "integrity": "sha512-BF5SXoO+nX3h5OhlN78XbbDrBOffv+AxPP2ENaJOVqjWCgBDeOY3WcaUcddutGSfoap+5NEQ/q/4I3WZIvgkXA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1441,11 +1632,11 @@ } }, "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.18.6.tgz", - "integrity": "sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.22.5.tgz", + "integrity": "sha512-PVk3WPYudRF5z4GKMEYUrLjPl38fJSKNaEOkFuoprioowGuWN6w2RKznuFNSlJx7pzzXXStPUnNSOEO0jL5EVw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1455,15 +1646,15 @@ } }, "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.21.0.tgz", - "integrity": "sha512-6OAWljMvQrZjR2DaNhVfRz6dkCAVV+ymcLUmaf8bccGOHn2v5rHJK3tTpij0BuhdYWP4LLaqj5lwcdlpAAPuvg==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.22.5.tgz", + "integrity": "sha512-rog5gZaVbUip5iWDMTYbVM15XQq+RkUKhET/IHR6oizR+JEoN6CAfTTuHcK4vwUyzca30qqHqEpzBOnaRMWYMA==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-jsx": "^7.18.6", - "@babel/types": "^7.21.0" + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-jsx": "^7.22.5", + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1473,11 +1664,11 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-development": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.18.6.tgz", - "integrity": "sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.22.5.tgz", + "integrity": "sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A==", "dependencies": { - "@babel/plugin-transform-react-jsx": "^7.18.6" + "@babel/plugin-transform-react-jsx": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1487,12 +1678,12 @@ } }, "node_modules/@babel/plugin-transform-react-pure-annotations": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.18.6.tgz", - "integrity": "sha512-I8VfEPg9r2TRDdvnHgPepTKvuRomzA8+u+nhY7qSI1fR2hRNebasZEETLyM5mAUr0Ku56OkXJ0I7NHJnO6cJiQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.22.5.tgz", + "integrity": "sha512-gP4k85wx09q+brArVinTXhWiyzLl9UpmGva0+mWyKxk6JZequ05x3eUcIUE+FyttPKJFRRVtAvQaJ6YF9h1ZpA==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1502,11 +1693,11 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.20.5.tgz", - "integrity": "sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.5.tgz", + "integrity": "sha512-rR7KePOE7gfEtNTh9Qw+iO3Q/e4DEsoQ+hdvM6QUDH7JRJ5qxq5AA52ZzBWbI5i9lfNuvySgOGP8ZN7LAmaiPw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-plugin-utils": "^7.22.5", "regenerator-transform": "^0.15.1" }, "engines": { @@ -1517,11 +1708,11 @@ } }, "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", - "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz", + "integrity": "sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1531,15 +1722,15 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.21.0.tgz", - "integrity": "sha512-ReY6pxwSzEU0b3r2/T/VhqMKg/AkceBT19X0UptA3/tYi5Pe2eXgEUH+NNMC5nok6c6XQz5tyVTUpuezRfSMSg==", - "dependencies": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.20.2", - "babel-plugin-polyfill-corejs2": "^0.3.3", - "babel-plugin-polyfill-corejs3": "^0.6.0", - "babel-plugin-polyfill-regenerator": "^0.4.1", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.22.5.tgz", + "integrity": "sha512-bg4Wxd1FWeFx3daHFTWk1pkSWK/AyQuiyAoeZAOkAOUBjnZPH6KT7eMxouV47tQ6hl6ax2zyAWBdWZXbrvXlaw==", + "dependencies": { + "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "babel-plugin-polyfill-corejs2": "^0.4.3", + "babel-plugin-polyfill-corejs3": "^0.8.1", + "babel-plugin-polyfill-regenerator": "^0.5.0", "semver": "^6.3.0" }, "engines": { @@ -1558,11 +1749,11 @@ } }, "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", - "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz", + "integrity": "sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1572,12 +1763,12 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.20.7.tgz", - "integrity": "sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz", + "integrity": "sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0" + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1587,11 +1778,11 @@ } }, "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", - "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz", + "integrity": "sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1601,11 +1792,11 @@ } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", - "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz", + "integrity": "sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1615,11 +1806,11 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", - "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz", + "integrity": "sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1629,14 +1820,14 @@ } }, "node_modules/@babel/plugin-transform-typescript": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.21.3.tgz", - "integrity": "sha512-RQxPz6Iqt8T0uw/WsJNReuBpWpBqs/n7mNo18sKLoTbMp+UrEekhH+pKSVC7gWz+DNjo9gryfV8YzCiT45RgMw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.22.5.tgz", + "integrity": "sha512-SMubA9S7Cb5sGSFFUlqxyClTA9zWJ8qGQrppNUm05LtFuN1ELRFNndkix4zUJrC9F+YivWwa1dHMSyo0e0N9dA==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-create-class-features-plugin": "^7.21.0", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-typescript": "^7.20.0" + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-typescript": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1646,11 +1837,26 @@ } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", - "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.5.tgz", + "integrity": "sha512-biEmVg1IYB/raUO5wT1tgfacCef15Fbzhkx493D3urBI++6hpJ+RFG4SrWMn0NEZLfvilqKf3QDrRVZHo08FYg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.5.tgz", + "integrity": "sha512-HCCIb+CbJIAE6sXn5CjFQXMwkCClcOfPCzTlilJ8cUatfzwHlWQkbtV0zD338u9dZskwvuOYTuuaMaA8J5EI5A==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1660,12 +1866,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", - "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz", + "integrity": "sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1674,38 +1880,41 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/preset-env": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.20.2.tgz", - "integrity": "sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg==", + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.5.tgz", + "integrity": "sha512-lhMfi4FC15j13eKrh3DnYHjpGj6UKQHtNKTbtc1igvAhRy4+kLhV07OpLcsN0VgDEw/MjAvJO4BdMJsHwMhzCg==", "dependencies": { - "@babel/compat-data": "^7.20.1", - "@babel/helper-compilation-targets": "^7.20.0", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-async-generator-functions": "^7.20.1", - "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/plugin-proposal-class-static-block": "^7.18.6", - "@babel/plugin-proposal-dynamic-import": "^7.18.6", - "@babel/plugin-proposal-export-namespace-from": "^7.18.9", - "@babel/plugin-proposal-json-strings": "^7.18.6", - "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", - "@babel/plugin-proposal-numeric-separator": "^7.18.6", - "@babel/plugin-proposal-object-rest-spread": "^7.20.2", - "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", - "@babel/plugin-proposal-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-private-methods": "^7.18.6", - "@babel/plugin-proposal-private-property-in-object": "^7.18.6", - "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.22.5.tgz", + "integrity": "sha512-fj06hw89dpiZzGZtxn+QybifF07nNiZjZ7sazs2aVDcysAZVGjW7+7iFYxg6GLNM47R/thYfLdrXc+2f11Vi9A==", + "dependencies": { + "@babel/compat-data": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.5", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.22.5", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.22.5", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.20.0", + "@babel/plugin-syntax-import-assertions": "^7.22.5", + "@babel/plugin-syntax-import-attributes": "^7.22.5", + "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", @@ -1715,44 +1924,61 @@ "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-transform-arrow-functions": "^7.18.6", - "@babel/plugin-transform-async-to-generator": "^7.18.6", - "@babel/plugin-transform-block-scoped-functions": "^7.18.6", - "@babel/plugin-transform-block-scoping": "^7.20.2", - "@babel/plugin-transform-classes": "^7.20.2", - "@babel/plugin-transform-computed-properties": "^7.18.9", - "@babel/plugin-transform-destructuring": "^7.20.2", - "@babel/plugin-transform-dotall-regex": "^7.18.6", - "@babel/plugin-transform-duplicate-keys": "^7.18.9", - "@babel/plugin-transform-exponentiation-operator": "^7.18.6", - "@babel/plugin-transform-for-of": "^7.18.8", - "@babel/plugin-transform-function-name": "^7.18.9", - "@babel/plugin-transform-literals": "^7.18.9", - "@babel/plugin-transform-member-expression-literals": "^7.18.6", - "@babel/plugin-transform-modules-amd": "^7.19.6", - "@babel/plugin-transform-modules-commonjs": "^7.19.6", - "@babel/plugin-transform-modules-systemjs": "^7.19.6", - "@babel/plugin-transform-modules-umd": "^7.18.6", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.19.1", - "@babel/plugin-transform-new-target": "^7.18.6", - "@babel/plugin-transform-object-super": "^7.18.6", - "@babel/plugin-transform-parameters": "^7.20.1", - "@babel/plugin-transform-property-literals": "^7.18.6", - "@babel/plugin-transform-regenerator": "^7.18.6", - "@babel/plugin-transform-reserved-words": "^7.18.6", - "@babel/plugin-transform-shorthand-properties": "^7.18.6", - "@babel/plugin-transform-spread": "^7.19.0", - "@babel/plugin-transform-sticky-regex": "^7.18.6", - "@babel/plugin-transform-template-literals": "^7.18.9", - "@babel/plugin-transform-typeof-symbol": "^7.18.9", - "@babel/plugin-transform-unicode-escapes": "^7.18.10", - "@babel/plugin-transform-unicode-regex": "^7.18.6", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.22.5", + "@babel/plugin-transform-async-generator-functions": "^7.22.5", + "@babel/plugin-transform-async-to-generator": "^7.22.5", + "@babel/plugin-transform-block-scoped-functions": "^7.22.5", + "@babel/plugin-transform-block-scoping": "^7.22.5", + "@babel/plugin-transform-class-properties": "^7.22.5", + "@babel/plugin-transform-class-static-block": "^7.22.5", + "@babel/plugin-transform-classes": "^7.22.5", + "@babel/plugin-transform-computed-properties": "^7.22.5", + "@babel/plugin-transform-destructuring": "^7.22.5", + "@babel/plugin-transform-dotall-regex": "^7.22.5", + "@babel/plugin-transform-duplicate-keys": "^7.22.5", + "@babel/plugin-transform-dynamic-import": "^7.22.5", + "@babel/plugin-transform-exponentiation-operator": "^7.22.5", + "@babel/plugin-transform-export-namespace-from": "^7.22.5", + "@babel/plugin-transform-for-of": "^7.22.5", + "@babel/plugin-transform-function-name": "^7.22.5", + "@babel/plugin-transform-json-strings": "^7.22.5", + "@babel/plugin-transform-literals": "^7.22.5", + "@babel/plugin-transform-logical-assignment-operators": "^7.22.5", + "@babel/plugin-transform-member-expression-literals": "^7.22.5", + "@babel/plugin-transform-modules-amd": "^7.22.5", + "@babel/plugin-transform-modules-commonjs": "^7.22.5", + "@babel/plugin-transform-modules-systemjs": "^7.22.5", + "@babel/plugin-transform-modules-umd": "^7.22.5", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", + "@babel/plugin-transform-new-target": "^7.22.5", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.22.5", + "@babel/plugin-transform-numeric-separator": "^7.22.5", + "@babel/plugin-transform-object-rest-spread": "^7.22.5", + "@babel/plugin-transform-object-super": "^7.22.5", + "@babel/plugin-transform-optional-catch-binding": "^7.22.5", + "@babel/plugin-transform-optional-chaining": "^7.22.5", + "@babel/plugin-transform-parameters": "^7.22.5", + "@babel/plugin-transform-private-methods": "^7.22.5", + "@babel/plugin-transform-private-property-in-object": "^7.22.5", + "@babel/plugin-transform-property-literals": "^7.22.5", + "@babel/plugin-transform-regenerator": "^7.22.5", + "@babel/plugin-transform-reserved-words": "^7.22.5", + "@babel/plugin-transform-shorthand-properties": "^7.22.5", + "@babel/plugin-transform-spread": "^7.22.5", + "@babel/plugin-transform-sticky-regex": "^7.22.5", + "@babel/plugin-transform-template-literals": "^7.22.5", + "@babel/plugin-transform-typeof-symbol": "^7.22.5", + "@babel/plugin-transform-unicode-escapes": "^7.22.5", + "@babel/plugin-transform-unicode-property-regex": "^7.22.5", + "@babel/plugin-transform-unicode-regex": "^7.22.5", + "@babel/plugin-transform-unicode-sets-regex": "^7.22.5", "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.20.2", - "babel-plugin-polyfill-corejs2": "^0.3.3", - "babel-plugin-polyfill-corejs3": "^0.6.0", - "babel-plugin-polyfill-regenerator": "^0.4.1", - "core-js-compat": "^3.25.1", + "@babel/types": "^7.22.5", + "babel-plugin-polyfill-corejs2": "^0.4.3", + "babel-plugin-polyfill-corejs3": "^0.8.1", + "babel-plugin-polyfill-regenerator": "^0.5.0", + "core-js-compat": "^3.30.2", "semver": "^6.3.0" }, "engines": { @@ -1762,6 +1988,17 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/preset-env/node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/preset-env/node_modules/semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -1786,16 +2023,16 @@ } }, "node_modules/@babel/preset-react": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.18.6.tgz", - "integrity": "sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.22.5.tgz", + "integrity": "sha512-M+Is3WikOpEJHgR385HbuCITPTaPRaNkibTEa9oiofmJvIsrceb4yp9RL9Kb+TE8LznmeyZqpP+Lopwcx59xPQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-transform-react-display-name": "^7.18.6", - "@babel/plugin-transform-react-jsx": "^7.18.6", - "@babel/plugin-transform-react-jsx-development": "^7.18.6", - "@babel/plugin-transform-react-pure-annotations": "^7.18.6" + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.5", + "@babel/plugin-transform-react-display-name": "^7.22.5", + "@babel/plugin-transform-react-jsx": "^7.22.5", + "@babel/plugin-transform-react-jsx-development": "^7.22.5", + "@babel/plugin-transform-react-pure-annotations": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1805,13 +2042,15 @@ } }, "node_modules/@babel/preset-typescript": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.21.0.tgz", - "integrity": "sha512-myc9mpoVA5m1rF8K8DgLEatOYFDpwC+RkMkjZ0Du6uI62YvDe8uxIEYVs/VCdSJ097nlALiU/yBC7//3nI+hNg==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.22.5.tgz", + "integrity": "sha512-YbPaal9LxztSGhmndR46FmAbkJ/1fAsw293tSU+I5E5h+cnJ3d4GTwyUgGYmOXJYdGA+uNePle4qbaRzj2NISQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-validator-option": "^7.21.0", - "@babel/plugin-transform-typescript": "^7.21.0" + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.5", + "@babel/plugin-syntax-jsx": "^7.22.5", + "@babel/plugin-transform-modules-commonjs": "^7.22.5", + "@babel/plugin-transform-typescript": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1826,9 +2065,9 @@ "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" }, "node_modules/@babel/runtime": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", - "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz", + "integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==", "dependencies": { "regenerator-runtime": "^0.13.11" }, @@ -1837,31 +2076,31 @@ } }, "node_modules/@babel/template": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", - "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", + "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7" + "@babel/code-frame": "^7.22.5", + "@babel/parser": "^7.22.5", + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.3.tgz", - "integrity": "sha512-XLyopNeaTancVitYZe2MlUEvgKb6YVVPXzofHgqHijCImG33b/uTurMS488ht/Hbsb2XK3U2BnSTxKVNGV3nGQ==", - "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.21.3", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.21.3", - "@babel/types": "^7.21.3", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.5.tgz", + "integrity": "sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==", + "dependencies": { + "@babel/code-frame": "^7.22.5", + "@babel/generator": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.5", + "@babel/parser": "^7.22.5", + "@babel/types": "^7.22.5", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -1870,12 +2109,12 @@ } }, "node_modules/@babel/types": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.3.tgz", - "integrity": "sha512-sBGdETxC+/M4o/zKC0sl6sjWv62WFR/uzxrJ6uYyMLZOUlPnwzw0tKgVHOXxaAd5l2g8pEDM5RZ495GPQI77kg==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", + "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", "dependencies": { - "@babel/helper-string-parser": "^7.19.4", - "@babel/helper-validator-identifier": "^7.19.1", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5", "to-fast-properties": "^2.0.0" }, "engines": { @@ -2158,84 +2397,65 @@ } }, "node_modules/@emotion/babel-plugin": { - "version": "11.10.6", - "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.10.6.tgz", - "integrity": "sha512-p2dAqtVrkhSa7xz1u/m9eHYdLi+en8NowrmXeF/dKtJpU8lCWli8RUAati7NcSl0afsBott48pdnANuD0wh9QQ==", + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", + "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==", "dependencies": { "@babel/helper-module-imports": "^7.16.7", "@babel/runtime": "^7.18.3", - "@emotion/hash": "^0.9.0", - "@emotion/memoize": "^0.8.0", - "@emotion/serialize": "^1.1.1", + "@emotion/hash": "^0.9.1", + "@emotion/memoize": "^0.8.1", + "@emotion/serialize": "^1.1.2", "babel-plugin-macros": "^3.1.0", "convert-source-map": "^1.5.0", "escape-string-regexp": "^4.0.0", "find-root": "^1.1.0", "source-map": "^0.5.7", - "stylis": "4.1.3" - } - }, - "node_modules/@emotion/babel-plugin/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@emotion/babel-plugin/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "engines": { - "node": ">=0.10.0" + "stylis": "4.2.0" } }, "node_modules/@emotion/cache": { - "version": "11.10.5", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.10.5.tgz", - "integrity": "sha512-dGYHWyzTdmK+f2+EnIGBpkz1lKc4Zbj2KHd4cX3Wi8/OWr5pKslNjc3yABKH4adRGCvSX4VDC0i04mrrq0aiRA==", + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", + "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", "dependencies": { - "@emotion/memoize": "^0.8.0", - "@emotion/sheet": "^1.2.1", - "@emotion/utils": "^1.2.0", - "@emotion/weak-memoize": "^0.3.0", - "stylis": "4.1.3" + "@emotion/memoize": "^0.8.1", + "@emotion/sheet": "^1.2.2", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", + "stylis": "4.2.0" } }, "node_modules/@emotion/hash": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.0.tgz", - "integrity": "sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ==" + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", + "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==" }, "node_modules/@emotion/is-prop-valid": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", - "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz", + "integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==", "dependencies": { - "@emotion/memoize": "^0.8.0" + "@emotion/memoize": "^0.8.1" } }, "node_modules/@emotion/memoize": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", - "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" }, "node_modules/@emotion/react": { - "version": "11.10.6", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.10.6.tgz", - "integrity": "sha512-6HT8jBmcSkfzO7mc+N1L9uwvOnlcGoix8Zn7srt+9ga0MjREo6lRpuVX0kzo6Jp6oTqDhREOFsygN6Ew4fEQbw==", + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.1.tgz", + "integrity": "sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA==", "dependencies": { "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.10.6", - "@emotion/cache": "^11.10.5", - "@emotion/serialize": "^1.1.1", - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", - "@emotion/utils": "^1.2.0", - "@emotion/weak-memoize": "^0.3.0", + "@emotion/babel-plugin": "^11.11.0", + "@emotion/cache": "^11.11.0", + "@emotion/serialize": "^1.1.2", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", "hoist-non-react-statics": "^3.3.1" }, "peerDependencies": { @@ -2248,33 +2468,33 @@ } }, "node_modules/@emotion/serialize": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.1.tgz", - "integrity": "sha512-Zl/0LFggN7+L1liljxXdsVSVlg6E/Z/olVWpfxUTxOAmi8NU7YoeWeLfi1RmnB2TATHoaWwIBRoL+FvAJiTUQA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.2.tgz", + "integrity": "sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA==", "dependencies": { - "@emotion/hash": "^0.9.0", - "@emotion/memoize": "^0.8.0", - "@emotion/unitless": "^0.8.0", - "@emotion/utils": "^1.2.0", + "@emotion/hash": "^0.9.1", + "@emotion/memoize": "^0.8.1", + "@emotion/unitless": "^0.8.1", + "@emotion/utils": "^1.2.1", "csstype": "^3.0.2" } }, "node_modules/@emotion/sheet": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.1.tgz", - "integrity": "sha512-zxRBwl93sHMsOj4zs+OslQKg/uhF38MB+OMKoCrVuS0nyTkqnau+BM3WGEoOptg9Oz45T/aIGs1qbVAsEFo3nA==" + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", + "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==" }, "node_modules/@emotion/styled": { - "version": "11.10.6", - "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.10.6.tgz", - "integrity": "sha512-OXtBzOmDSJo5Q0AFemHCfl+bUueT8BIcPSxu0EGTpGk6DmI5dnhSzQANm1e1ze0YZL7TDyAyy6s/b/zmGOS3Og==", + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.11.0.tgz", + "integrity": "sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng==", "dependencies": { "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.10.6", - "@emotion/is-prop-valid": "^1.2.0", - "@emotion/serialize": "^1.1.1", - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", - "@emotion/utils": "^1.2.0" + "@emotion/babel-plugin": "^11.11.0", + "@emotion/is-prop-valid": "^1.2.1", + "@emotion/serialize": "^1.1.2", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", + "@emotion/utils": "^1.2.1" }, "peerDependencies": { "@emotion/react": "^11.0.0-rc.0", @@ -2287,32 +2507,32 @@ } }, "node_modules/@emotion/unitless": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz", - "integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==" + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" }, "node_modules/@emotion/use-insertion-effect-with-fallbacks": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.0.tgz", - "integrity": "sha512-1eEgUGmkaljiBnRMTdksDV1W4kUnmwgp7X9G8B++9GYwl1lUdqSndSriIrTJ0N7LQaoauY9JJ2yhiOYK5+NI4A==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz", + "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==", "peerDependencies": { "react": ">=16.8.0" } }, "node_modules/@emotion/utils": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.0.tgz", - "integrity": "sha512-sn3WH53Kzpw8oQ5mgMmIzzyAaH2ZqFEbozVVBSYp538E06OSE6ytOp7pRAjNQR+Q/orwqdQYJSe2m3hCOeznkw==" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", + "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==" }, "node_modules/@emotion/weak-memoize": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz", - "integrity": "sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg==" + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", + "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.3.0.tgz", - "integrity": "sha512-v3oplH6FYCULtFuCeqyuTd9D2WKO937Dxdq+GmHOLL72TTRriLxz2VLlNfkZRsvj6PKnOPAtuT6dwrs/pA5DvA==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", "dependencies": { "eslint-visitor-keys": "^3.3.0" }, @@ -2324,21 +2544,21 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.4.0.tgz", - "integrity": "sha512-A9983Q0LnDGdLPjxyXQ00sbV+K+O+ko2Dr+CZigbHWtX9pNfxlaBkMR8X1CztI73zuEyEBXTVjx7CE+/VSwDiQ==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", + "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/eslintrc": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.1.tgz", - "integrity": "sha512-eFRmABvW2E5Ho6f5fHLqgena46rOj7r7OKHYfLElqcBfGFHHpjBhivyi5+jOEQuSpdc/1phIZJlbC2te+tZNIw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz", + "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.5.0", + "espree": "^9.5.2", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -2379,22 +2599,22 @@ } }, "node_modules/@eslint/js": { - "version": "8.36.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.36.0.tgz", - "integrity": "sha512-lxJ9R5ygVm8ZWgYdUweoq5ownDlJ4upvoWmO4eLxBYHdMo+vZ/Rx0EN6MbKWDJOSUGrqJy2Gt+Dyv/VKml0fjg==", + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.42.0.tgz", + "integrity": "sha512-6SWlXpWU5AvId8Ac7zjzmIOqMOba/JWY8XZ4A7q7Gn1Vlfg/SFFIlrtHXt9nPn4op9ZPAkl91Jao+QQv3r/ukw==", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@fontsource-variable/nunito": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@fontsource-variable/nunito/-/nunito-5.0.2.tgz", - "integrity": "sha512-ztW98DGVZbq77ITkqg5+C5O8CI/SoaiqWOFBfbyE7CHMkU8XaBOXFe2l6H7YREE15mMIKCGA/icztQuoLi21lA==" + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@fontsource-variable/nunito/-/nunito-5.0.3.tgz", + "integrity": "sha512-yxQ+IPeqA43DXNigCdU4rAziyeeKqm7VkAaJ9/o/h/qBA6w/dKsAH/YWmNB04PFJ6n84+3IAS7AKiOFs+4yZsw==" }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", - "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", + "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", "dependencies": { "@humanwhocodes/object-schema": "^1.2.1", "debug": "^4.1.1", @@ -2512,6 +2732,14 @@ "node": ">=8" } }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "engines": { + "node": ">=8" + } + }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", @@ -2536,70 +2764,6 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/@jest/console/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/console/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/console/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/console/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@jest/console/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/core": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.5.1.tgz", @@ -2646,70 +2810,6 @@ } } }, - "node_modules/@jest/core/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/core/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/core/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/core/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@jest/core/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/core/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/environment": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.5.1.tgz", @@ -2796,59 +2896,6 @@ } } }, - "node_modules/@jest/reporters/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/reporters/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/reporters/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/reporters/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@jest/reporters/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/reporters/node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -2857,17 +2904,6 @@ "node": ">=0.10.0" } }, - "node_modules/@jest/reporters/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/schemas": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", @@ -2953,59 +2989,6 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/@jest/transform/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/transform/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/transform/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/transform/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@jest/transform/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/transform/node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -3014,17 +2997,6 @@ "node": ">=0.10.0" } }, - "node_modules/@jest/transform/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/types": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", @@ -3040,77 +3012,14 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/@jest/types/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/types/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/types/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/types/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@jest/types/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/types/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", "dependencies": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" }, "engines": { "node": ">=6.0.0" @@ -3133,56 +3042,48 @@ } }, "node_modules/@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz", + "integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==", "dependencies": { "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" } }, - "node_modules/@jridgewell/source-map/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.17", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", - "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", "dependencies": { "@jridgewell/resolve-uri": "3.1.0", "@jridgewell/sourcemap-codec": "1.4.14" } }, + "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + }, "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" }, "node_modules/@mui/base": { - "version": "5.0.0-alpha.122", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.122.tgz", - "integrity": "sha512-IgZEFQyHa39J1+Q3tekVdhPuUm1fr3icddaNLmiAIeYTVXmR7KR5FhBAIL0P+4shlPq0liUPGlXryoTm0iCeFg==", + "version": "5.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.4.tgz", + "integrity": "sha512-ejhtqYJpjDgHGEljjMBQWZ22yEK0OzIXNa7toJmmXsP4TT3W7xVy8bTJ0TniPDf+JNjrsgfgiFTDGdlEhV1E+g==", "dependencies": { "@babel/runtime": "^7.21.0", - "@emotion/is-prop-valid": "^1.2.0", - "@mui/types": "^7.2.3", - "@mui/utils": "^5.11.13", - "@popperjs/core": "^2.11.6", + "@emotion/is-prop-valid": "^1.2.1", + "@mui/types": "^7.2.4", + "@mui/utils": "^5.13.1", + "@popperjs/core": "^2.11.8", "clsx": "^1.2.1", "prop-types": "^15.8.1", "react-is": "^18.2.0" @@ -3205,24 +3106,19 @@ } } }, - "node_modules/@mui/base/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" - }, "node_modules/@mui/core-downloads-tracker": { - "version": "5.11.14", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.11.14.tgz", - "integrity": "sha512-rfc08z6+3Fif+Gopx2/qmk+MEQlwYeA+gOcSK048BHkTty/ol/boHuVeL2BNC/cf9OVRjJLYHtVb/DeW791LSQ==", + "version": "5.13.4", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.13.4.tgz", + "integrity": "sha512-yFrMWcrlI0TqRN5jpb6Ma9iI7sGTHpytdzzL33oskFHNQ8UgrtPas33Y1K7sWAMwCrr1qbWDrOHLAQG4tAzuSw==", "funding": { "type": "opencollective", "url": "https://opencollective.com/mui" } }, "node_modules/@mui/icons-material": { - "version": "5.11.11", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.11.11.tgz", - "integrity": "sha512-Eell3ADmQVE8HOpt/LZ3zIma8JSvPh3XgnhwZLT0k5HRqZcd6F/QDHc7xsWtgz09t+UEFvOYJXjtrwKmLdwwpw==", + "version": "5.11.16", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.11.16.tgz", + "integrity": "sha512-oKkx9z9Kwg40NtcIajF9uOXhxiyTZrrm9nmIJ4UjkU2IdHpd4QVLbCc/5hZN/y0C6qzi2Zlxyr9TGddQx2vx2A==", "dependencies": { "@babel/runtime": "^7.21.0" }, @@ -3245,15 +3141,15 @@ } }, "node_modules/@mui/lab": { - "version": "5.0.0-alpha.124", - "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-5.0.0-alpha.124.tgz", - "integrity": "sha512-K/XEv1zYyLi3tS63tyDta1mqWql+at5w7rWp5Yd63Jx1NjPUtgopAvyRZG2SVYPs/yBwfyDPW1iqQXpRw8h9Xw==", + "version": "5.0.0-alpha.133", + "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-5.0.0-alpha.133.tgz", + "integrity": "sha512-pPL5/f6si8eCXlsnOZrO+/zg5Yv6qKa9OpI6nGP77Mpn7iYHm9qrcsWFIBs6YjgxqJf6dA2IqtHaSNOSndrXDw==", "dependencies": { "@babel/runtime": "^7.21.0", - "@mui/base": "5.0.0-alpha.122", - "@mui/system": "^5.11.14", - "@mui/types": "^7.2.3", - "@mui/utils": "^5.11.13", + "@mui/base": "5.0.0-beta.4", + "@mui/system": "^5.13.2", + "@mui/types": "^7.2.4", + "@mui/utils": "^5.13.1", "clsx": "^1.2.1", "prop-types": "^15.8.1", "react-is": "^18.2.0" @@ -3285,25 +3181,20 @@ } } }, - "node_modules/@mui/lab/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" - }, "node_modules/@mui/material": { - "version": "5.11.14", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.11.14.tgz", - "integrity": "sha512-uoiUyybmo+M+nYARBygmbXgX6s/hH0NKD56LCAv9XvmdGVoXhEGjOvxI5/Bng6FS3NNybnA8V+rgZW1Z/9OJtA==", + "version": "5.13.4", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.13.4.tgz", + "integrity": "sha512-Yq+4f1KLPa/Szd3xqra2hbOAf2Usl8GbubncArM6LIp40mBLtXIdPE29MNtHsbtuzz4g+eidrETgoi3wdbEYfQ==", "dependencies": { "@babel/runtime": "^7.21.0", - "@mui/base": "5.0.0-alpha.122", - "@mui/core-downloads-tracker": "^5.11.14", - "@mui/system": "^5.11.14", - "@mui/types": "^7.2.3", - "@mui/utils": "^5.11.13", - "@types/react-transition-group": "^4.4.5", + "@mui/base": "5.0.0-beta.4", + "@mui/core-downloads-tracker": "^5.13.4", + "@mui/system": "^5.13.2", + "@mui/types": "^7.2.4", + "@mui/utils": "^5.13.1", + "@types/react-transition-group": "^4.4.6", "clsx": "^1.2.1", - "csstype": "^3.1.1", + "csstype": "^3.1.2", "prop-types": "^15.8.1", "react-is": "^18.2.0", "react-transition-group": "^4.4.5" @@ -3334,18 +3225,13 @@ } } }, - "node_modules/@mui/material/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" - }, "node_modules/@mui/private-theming": { - "version": "5.11.13", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.11.13.tgz", - "integrity": "sha512-PJnYNKzW5LIx3R+Zsp6WZVPs6w5sEKJ7mgLNnUXuYB1zo5aX71FVLtV7geyPXRcaN2tsoRNK7h444ED0t7cIjA==", + "version": "5.13.1", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.13.1.tgz", + "integrity": "sha512-HW4npLUD9BAkVppOUZHeO1FOKUJWAwbpy0VQoGe3McUYTlck1HezGHQCfBQ5S/Nszi7EViqiimECVl9xi+/WjQ==", "dependencies": { "@babel/runtime": "^7.21.0", - "@mui/utils": "^5.11.13", + "@mui/utils": "^5.13.1", "prop-types": "^15.8.1" }, "engines": { @@ -3366,13 +3252,13 @@ } }, "node_modules/@mui/styled-engine": { - "version": "5.11.11", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.11.11.tgz", - "integrity": "sha512-wV0UgW4lN5FkDBXefN8eTYeuE9sjyQdg5h94vtwZCUamGQEzmCOtir4AakgmbWMy0x8OLjdEUESn9wnf5J9MOg==", + "version": "5.13.2", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.13.2.tgz", + "integrity": "sha512-VCYCU6xVtXOrIN8lcbuPmoG+u7FYuOERG++fpY74hPpEWkyFQG97F+/XfTQVYzlR2m7nPjnwVUgATcTCMEaMvw==", "dependencies": { "@babel/runtime": "^7.21.0", - "@emotion/cache": "^11.10.5", - "csstype": "^3.1.1", + "@emotion/cache": "^11.11.0", + "csstype": "^3.1.2", "prop-types": "^15.8.1" }, "engines": { @@ -3397,17 +3283,17 @@ } }, "node_modules/@mui/system": { - "version": "5.11.14", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.11.14.tgz", - "integrity": "sha512-/MBv5dUoijJNEKEGi5tppIszGN0o2uejmeISi5vl0CLcaQsI1cd+uBgK+JYUP1VWvI/MtkWRLVSWtF2FWhu5Nw==", + "version": "5.13.2", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.13.2.tgz", + "integrity": "sha512-TPyWmRJPt0JPVxacZISI4o070xEJ7ftxpVtu6LWuYVOUOINlhoGOclam4iV8PDT3EMQEHuUrwU49po34UdWLlw==", "dependencies": { "@babel/runtime": "^7.21.0", - "@mui/private-theming": "^5.11.13", - "@mui/styled-engine": "^5.11.11", - "@mui/types": "^7.2.3", - "@mui/utils": "^5.11.13", + "@mui/private-theming": "^5.13.1", + "@mui/styled-engine": "^5.13.2", + "@mui/types": "^7.2.4", + "@mui/utils": "^5.13.1", "clsx": "^1.2.1", - "csstype": "^3.1.1", + "csstype": "^3.1.2", "prop-types": "^15.8.1" }, "engines": { @@ -3436,9 +3322,9 @@ } }, "node_modules/@mui/types": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.3.tgz", - "integrity": "sha512-tZ+CQggbe9Ol7e/Fs5RcKwg/woU+o8DCtOnccX6KmbBc7YrfqMYEYuaIcXHuhpT880QwNkZZ3wQwvtlDFA2yOw==", + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.4.tgz", + "integrity": "sha512-LBcwa8rN84bKF+f5sDyku42w1NTxaPgPyYKODsh01U1fVstTClbUoSA96oyRBnSNyEiAVjKm6Gwx9vjR+xyqHA==", "peerDependencies": { "@types/react": "*" }, @@ -3449,13 +3335,13 @@ } }, "node_modules/@mui/utils": { - "version": "5.11.13", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.11.13.tgz", - "integrity": "sha512-5ltA58MM9euOuUcnvwFJqpLdEugc9XFsRR8Gt4zZNb31XzMfSKJPR4eumulyhsOTK1rWf7K4D63NKFPfX0AxqA==", + "version": "5.13.1", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.13.1.tgz", + "integrity": "sha512-6lXdWwmlUbEU2jUI8blw38Kt+3ly7xkmV9ljzY4Q20WhsJMWiNry9CX8M+TaP/HbtuyR8XKsdMgQW7h7MM3n3A==", "dependencies": { "@babel/runtime": "^7.21.0", "@types/prop-types": "^15.7.5", - "@types/react-is": "^16.7.1 || ^17.0.0", + "@types/react-is": "^18.2.0", "prop-types": "^15.8.1", "react-is": "^18.2.0" }, @@ -3470,11 +3356,6 @@ "react": "^17.0.0 || ^18.0.0" } }, - "node_modules/@mui/utils/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" - }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -3584,19 +3465,27 @@ } } }, + "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "engines": { + "node": ">= 8" + } + }, "node_modules/@popperjs/core": { - "version": "2.11.6", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", - "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==", + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" } }, "node_modules/@remix-run/router": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.5.0.tgz", - "integrity": "sha512-bkUDCp8o1MvFO+qxkODcbhSqRa6P2GXgrGZVpt0dCXNW2HCSCqYI0ZoAqEOSAjRWmmlKcYgFvN4B4S+zo/f8kg==", + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.6.3.tgz", + "integrity": "sha512-EXJysQ7J3veRECd0kZFQwYYd5sJMcq2O/m60zu1W2l3oVQ9xtub8jTOtYRE0+M2iomyG/W3Ps7+vp2kna0C27Q==", "engines": { "node": ">=14" } @@ -3676,9 +3565,9 @@ "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==" }, "node_modules/@rushstack/eslint-patch": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz", - "integrity": "sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==" + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.3.1.tgz", + "integrity": "sha512-RkmuBcqiNioeeBKbgzMlOdreUkJfYaSjwgx9XDgGGpjvWgyaxWvDmZVSN9CS6LjEASadhgPv2BcFp+SeouWXXA==" }, "node_modules/@sinclair/typebox": { "version": "0.24.51", @@ -3920,9 +3809,9 @@ } }, "node_modules/@testing-library/dom": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.0.1.tgz", - "integrity": "sha512-fTOVsMY9QLFCCXRHG3Ese6cMH5qIWwSbgxZsgeF5TNsy81HKaZ4kgehnSF8FsR3OF+numlIV2YcU79MzbnhSig==", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.0.tgz", + "integrity": "sha512-Dffe68pGwI6WlLRYR2I0piIkyole9cSBH5jGQKCGMRpHW5RHCqAUaqc2Kv0tUyd4dU4DLPKhJIjyKOnjv4tuUw==", "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", @@ -3938,76 +3827,6 @@ "node": ">=14" } }, - "node_modules/@testing-library/dom/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@testing-library/dom/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@testing-library/dom/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@testing-library/dom/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "peer": true - }, - "node_modules/@testing-library/dom/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@testing-library/dom/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@testing-library/jest-dom": { "version": "5.16.5", "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz", @@ -4029,20 +3848,6 @@ "yarn": ">=1" } }, - "node_modules/@testing-library/jest-dom/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/@testing-library/jest-dom/node_modules/chalk": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", @@ -4055,41 +3860,6 @@ "node": ">=8" } }, - "node_modules/@testing-library/jest-dom/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@testing-library/jest-dom/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@testing-library/jest-dom/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@testing-library/jest-dom/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@testing-library/react": { "version": "13.4.0", "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-13.4.0.tgz", @@ -4125,99 +3895,35 @@ "node": ">=12" } }, - "node_modules/@testing-library/react/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@testing-library/user-event": { + "version": "13.5.0", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-13.5.0.tgz", + "integrity": "sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg==", "dependencies": { - "color-convert": "^2.0.1" + "@babel/runtime": "^7.12.5" }, "engines": { - "node": ">=8" + "node": ">=10", + "npm": ">=6" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" } }, - "node_modules/@testing-library/react/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "node_modules/@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">= 6" } }, - "node_modules/@testing-library/react/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, + "node_modules/@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@testing-library/react/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@testing-library/react/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@testing-library/react/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@testing-library/user-event": { - "version": "13.5.0", - "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-13.5.0.tgz", - "integrity": "sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg==", - "dependencies": { - "@babel/runtime": "^7.12.5" - }, - "engines": { - "node": ">=10", - "npm": ">=6" - }, - "peerDependencies": { - "@testing-library/dom": ">=7.21.4" - } - }, - "node_modules/@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/@trysound/sax": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", - "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", - "engines": { - "node": ">=10.13.0" + "node": ">=10.13.0" } }, "node_modules/@types/aria-query": { @@ -4226,9 +3932,9 @@ "integrity": "sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q==" }, "node_modules/@types/babel__core": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz", - "integrity": "sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==", + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.1.tgz", + "integrity": "sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==", "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", @@ -4255,11 +3961,11 @@ } }, "node_modules/@types/babel__traverse": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.3.tgz", - "integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==", + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.1.tgz", + "integrity": "sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg==", "dependencies": { - "@babel/types": "^7.3.0" + "@babel/types": "^7.20.7" } }, "node_modules/@types/body-parser": { @@ -4288,18 +3994,18 @@ } }, "node_modules/@types/connect-history-api-fallback": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz", - "integrity": "sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz", + "integrity": "sha512-4x5FkPpLipqwthjPsF7ZRbOv3uoLUFkTA9G9v583qi4pACvq0uTELrB8OLUzPWUI4IJIyvM85vzkV1nyiI2Lig==", "dependencies": { "@types/express-serve-static-core": "*", "@types/node": "*" } }, "node_modules/@types/eslint": { - "version": "8.21.3", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.21.3.tgz", - "integrity": "sha512-fa7GkppZVEByMWGbTtE5MbmXWJTVbrjjaS8K6uQj+XtuuUv1fsuPAxhygfqLmsb/Ufb3CV8deFCpiMfAgi00Sw==", + "version": "8.40.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.40.1.tgz", + "integrity": "sha512-vRb792M4mF1FBT+eoLecmkpLXwxsBHvWWRGJjzbYANBM6DtiJc6yETyv4rqDA6QNjF1pkj1U7LMA6dGb3VYlHw==", "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -4315,9 +4021,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", - "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", + "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==" }, "node_modules/@types/express": { "version": "4.17.17", @@ -4331,13 +4037,14 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "4.17.33", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.33.tgz", - "integrity": "sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==", + "version": "4.17.35", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz", + "integrity": "sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg==", "dependencies": { "@types/node": "*", "@types/qs": "*", - "@types/range-parser": "*" + "@types/range-parser": "*", + "@types/send": "*" } }, "node_modules/@types/graceful-fs": { @@ -4362,9 +4069,9 @@ "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==" }, "node_modules/@types/http-proxy": { - "version": "1.17.10", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.10.tgz", - "integrity": "sha512-Qs5aULi+zV1bwKAg5z1PWnDXWmsn+LxIvUGv6E2+OOMYhclZMO+OXd9pYVf2gLykf2I7IV2u7oTHwChPNsvJ7g==", + "version": "1.17.11", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.11.tgz", + "integrity": "sha512-HC8G7c1WmaF2ekqpnFq626xd3Zz0uvaqFmBJNRZCGEZCXkvSdJoNFn/8Ygbd9fKNQj8UzLdCETaI0UWPAjK7IA==", "dependencies": { "@types/node": "*" } @@ -4406,9 +4113,9 @@ "dev": true }, "node_modules/@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", + "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==" }, "node_modules/@types/json5": { "version": "0.0.29", @@ -4416,20 +4123,20 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" }, "node_modules/@types/lodash": { - "version": "4.14.192", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.192.tgz", - "integrity": "sha512-km+Vyn3BYm5ytMO13k9KTp27O75rbQ0NFw+U//g+PX7VZyjCioXaRFisqSIJRECljcTv73G3i6BpglNGHgUQ5A==", + "version": "4.14.195", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.195.tgz", + "integrity": "sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg==", "dev": true }, "node_modules/@types/mime": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", - "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==" + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" }, "node_modules/@types/node": { - "version": "16.18.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.18.tgz", - "integrity": "sha512-fwGw1uvQAzabxL1pyoknPlJIF2t7+K90uTqynleKRx24n3lYcxWa3+KByLhgkF8GEAK2c7hC8Ki0RkNM5H15jQ==" + "version": "16.18.34", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.34.tgz", + "integrity": "sha512-VmVm7gXwhkUimRfBwVI1CHhwp86jDWR04B5FGebMMyxV90SlCmFujwUHrxTD4oO+SOYU86SoxvhgeRQJY7iXFg==" }, "node_modules/@types/parse-json": { "version": "4.0.0", @@ -4437,9 +4144,9 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" }, "node_modules/@types/prettier": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", - "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==" + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", + "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==" }, "node_modules/@types/prop-types": { "version": "15.7.5", @@ -4462,9 +4169,9 @@ "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" }, "node_modules/@types/react": { - "version": "18.0.28", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.28.tgz", - "integrity": "sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==", + "version": "18.2.9", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.9.tgz", + "integrity": "sha512-pL3JAesUkF7PEQGxh5XOwdXGV907te6m1/Qe1ERJLgomojS6Ne790QiA7GUl434JEkFA2aAaB6qJ5z4e1zJn/w==", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -4472,34 +4179,34 @@ } }, "node_modules/@types/react-dom": { - "version": "18.0.11", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.11.tgz", - "integrity": "sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==", + "version": "18.2.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.4.tgz", + "integrity": "sha512-G2mHoTMTL4yoydITgOGwWdWMVd8sNgyEP85xVmMKAPUBwQWm9wBPQUmvbeF4V3WBY1P7mmL4BkjQ0SqUpf1snw==", "dependencies": { "@types/react": "*" } }, "node_modules/@types/react-is": { - "version": "17.0.3", - "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-17.0.3.tgz", - "integrity": "sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-1vz2yObaQkLL7YFe/pme2cpvDsCwI1WXIfL+5eLz0MI9gFG24Re16RzUsI8t9XZn9ZWvgLNDrJBmrqXJO7GNQQ==", "dependencies": { "@types/react": "*" } }, "node_modules/@types/react-syntax-highlighter": { - "version": "15.5.6", - "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.6.tgz", - "integrity": "sha512-i7wFuLbIAFlabTeD2I1cLjEOrG/xdMa/rpx2zwzAoGHuXJDhSqp9BSfDlMHSh9JSuNfxHk9eEmMX6D55GiyjGg==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.7.tgz", + "integrity": "sha512-bo5fEO5toQeyCp0zVHBeggclqf5SQ/Z5blfFmjwO5dkMVGPgmiwZsJh9nu/Bo5L7IHTuGWrja6LxJVE2uB5ZrQ==", "dev": true, "dependencies": { "@types/react": "*" } }, "node_modules/@types/react-transition-group": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz", - "integrity": "sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==", + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.6.tgz", + "integrity": "sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew==", "dependencies": { "@types/react": "*" } @@ -4518,14 +4225,23 @@ "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" }, "node_modules/@types/scheduler": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", - "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" + "version": "0.16.3", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", + "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==" }, "node_modules/@types/semver": { - "version": "7.3.13", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", - "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==" + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==" + }, + "node_modules/@types/send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", + "integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } }, "node_modules/@types/serve-index": { "version": "1.9.1", @@ -4558,9 +4274,9 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==" }, "node_modules/@types/testing-library__jest-dom": { - "version": "5.14.5", - "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.5.tgz", - "integrity": "sha512-SBwbxYoyPIvxHbeHxTZX2Pe/74F/tX2/D3mMvzabdeJ25bBojfW0TyB8BHrbq/9zaaKICJZjLP+8r6AeZMFCuQ==", + "version": "5.14.6", + "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.6.tgz", + "integrity": "sha512-FkHXCb+ikSoUP4Y4rOslzTdX5sqYwMxfefKh1GmZ8ce1GOkEHntSp6b5cGadmNfp5e4BMEWOMx+WSKd5/MqlDA==", "dependencies": { "@types/jest": "*" } @@ -4576,9 +4292,9 @@ "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==" }, "node_modules/@types/ws": { - "version": "8.5.4", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", - "integrity": "sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==", + "version": "8.5.5", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.5.tgz", + "integrity": "sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==", "dependencies": { "@types/node": "*" } @@ -4597,14 +4313,14 @@ "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.56.0.tgz", - "integrity": "sha512-ZNW37Ccl3oMZkzxrYDUX4o7cnuPgU+YrcaYXzsRtLB16I1FR5SHMqga3zGsaSliZADCWo2v8qHWqAYIj8nWCCg==", + "version": "5.59.9", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.9.tgz", + "integrity": "sha512-4uQIBq1ffXd2YvF7MAvehWKW3zVv/w+mSfRAu+8cKbfj3nwzyqJLNcZJpQ/WZ1HLbJDiowwmQ6NO+63nCA+fqA==", "dependencies": { "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.56.0", - "@typescript-eslint/type-utils": "5.56.0", - "@typescript-eslint/utils": "5.56.0", + "@typescript-eslint/scope-manager": "5.59.9", + "@typescript-eslint/type-utils": "5.59.9", + "@typescript-eslint/utils": "5.59.9", "debug": "^4.3.4", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", @@ -4630,11 +4346,11 @@ } }, "node_modules/@typescript-eslint/experimental-utils": { - "version": "5.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.56.0.tgz", - "integrity": "sha512-sxWuj0eO5nItmKgZmsBbChVt90EhfkuncDCPbLAVeEJ+SCjXMcZN3AhhNbxed7IeGJ4XwsdL3/FMvD4r+FLqqA==", + "version": "5.59.9", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.59.9.tgz", + "integrity": "sha512-eZTK/Ci0QAqNc/q2MqMwI2+QI5ZI9HM12FcfGwbEvKif5ev/CIIYLmrlckvgPrC8XSbl39HtErR5NJiQkRkvWg==", "dependencies": { - "@typescript-eslint/utils": "5.56.0" + "@typescript-eslint/utils": "5.59.9" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -4648,13 +4364,13 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "5.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.56.0.tgz", - "integrity": "sha512-sn1OZmBxUsgxMmR8a8U5QM/Wl+tyqlH//jTqCg8daTAmhAk26L2PFhcqPLlYBhYUJMZJK276qLXlHN3a83o2cg==", + "version": "5.59.9", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.9.tgz", + "integrity": "sha512-FsPkRvBtcLQ/eVK1ivDiNYBjn3TGJdXy2fhXX+rc7czWl4ARwnpArwbihSOHI2Peg9WbtGHrbThfBUkZZGTtvQ==", "dependencies": { - "@typescript-eslint/scope-manager": "5.56.0", - "@typescript-eslint/types": "5.56.0", - "@typescript-eslint/typescript-estree": "5.56.0", + "@typescript-eslint/scope-manager": "5.59.9", + "@typescript-eslint/types": "5.59.9", + "@typescript-eslint/typescript-estree": "5.59.9", "debug": "^4.3.4" }, "engines": { @@ -4674,12 +4390,12 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.56.0.tgz", - "integrity": "sha512-jGYKyt+iBakD0SA5Ww8vFqGpoV2asSjwt60Gl6YcO8ksQ8s2HlUEyHBMSa38bdLopYqGf7EYQMUIGdT/Luw+sw==", + "version": "5.59.9", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.9.tgz", + "integrity": "sha512-8RA+E+w78z1+2dzvK/tGZ2cpGigBZ58VMEHDZtpE1v+LLjzrYGc8mMaTONSxKyEkz3IuXFM0IqYiGHlCsmlZxQ==", "dependencies": { - "@typescript-eslint/types": "5.56.0", - "@typescript-eslint/visitor-keys": "5.56.0" + "@typescript-eslint/types": "5.59.9", + "@typescript-eslint/visitor-keys": "5.59.9" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -4690,12 +4406,12 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.56.0.tgz", - "integrity": "sha512-8WxgOgJjWRy6m4xg9KoSHPzBNZeQbGlQOH7l2QEhQID/+YseaFxg5J/DLwWSsi9Axj4e/cCiKx7PVzOq38tY4A==", + "version": "5.59.9", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.9.tgz", + "integrity": "sha512-ksEsT0/mEHg9e3qZu98AlSrONAQtrSTljL3ow9CGej8eRo7pe+yaC/mvTjptp23Xo/xIf2mLZKC6KPv4Sji26Q==", "dependencies": { - "@typescript-eslint/typescript-estree": "5.56.0", - "@typescript-eslint/utils": "5.56.0", + "@typescript-eslint/typescript-estree": "5.59.9", + "@typescript-eslint/utils": "5.59.9", "debug": "^4.3.4", "tsutils": "^3.21.0" }, @@ -4716,9 +4432,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "5.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.56.0.tgz", - "integrity": "sha512-JyAzbTJcIyhuUhogmiu+t79AkdnqgPUEsxMTMc/dCZczGMJQh1MK2wgrju++yMN6AWroVAy2jxyPcPr3SWCq5w==", + "version": "5.59.9", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.9.tgz", + "integrity": "sha512-uW8H5NRgTVneSVTfiCVffBb8AbwWSKg7qcA4Ot3JI3MPCJGsB4Db4BhvAODIIYE5mNj7Q+VJkK7JxmRhk2Lyjw==", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -4728,12 +4444,12 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.56.0.tgz", - "integrity": "sha512-41CH/GncsLXOJi0jb74SnC7jVPWeVJ0pxQj8bOjH1h2O26jXN3YHKDT1ejkVz5YeTEQPeLCCRY0U2r68tfNOcg==", + "version": "5.59.9", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.9.tgz", + "integrity": "sha512-pmM0/VQ7kUhd1QyIxgS+aRvMgw+ZljB3eDb+jYyp6d2bC0mQWLzUDF+DLwCTkQ3tlNyVsvZRXjFyV0LkU/aXjA==", "dependencies": { - "@typescript-eslint/types": "5.56.0", - "@typescript-eslint/visitor-keys": "5.56.0", + "@typescript-eslint/types": "5.59.9", + "@typescript-eslint/visitor-keys": "5.59.9", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -4754,16 +4470,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "5.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.56.0.tgz", - "integrity": "sha512-XhZDVdLnUJNtbzaJeDSCIYaM+Tgr59gZGbFuELgF7m0IY03PlciidS7UQNKLE0+WpUTn1GlycEr6Ivb/afjbhA==", + "version": "5.59.9", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.9.tgz", + "integrity": "sha512-1PuMYsju/38I5Ggblaeb98TOoUvjhRvLpLa1DoTOFaLWqaXl/1iQ1eGurTXgBY58NUdtfTXKP5xBq7q9NDaLKg==", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.56.0", - "@typescript-eslint/types": "5.56.0", - "@typescript-eslint/typescript-estree": "5.56.0", + "@typescript-eslint/scope-manager": "5.59.9", + "@typescript-eslint/types": "5.59.9", + "@typescript-eslint/typescript-estree": "5.59.9", "eslint-scope": "^5.1.1", "semver": "^7.3.7" }, @@ -4799,11 +4515,11 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.56.0.tgz", - "integrity": "sha512-1mFdED7u5bZpX6Xxf5N9U2c18sb+8EvU3tyOIj6LQZ5OOvnmj8BVeNNP603OFPm5KkS1a7IvCIcwrdHXaEMG/Q==", + "version": "5.59.9", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.9.tgz", + "integrity": "sha512-bT7s0td97KMaLwpEBckbzj/YohnvXtqbe2XgqNvTl6RJVakY5mvENOTPvw5u66nljfZxthESpDozs86U+oLY8Q==", "dependencies": { - "@typescript-eslint/types": "5.56.0", + "@typescript-eslint/types": "5.59.9", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -4815,133 +4531,133 @@ } }, "node_modules/@webassemblyjs/ast": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", - "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", + "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" } }, "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==" + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==" }, "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==" + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==" }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==" + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", + "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==" }, "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", - "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==" + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==" }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", - "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", + "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6" } }, "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", - "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", "dependencies": { "@xtuc/ieee754": "^1.2.0" } }, "node_modules/@webassemblyjs/leb128": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", "dependencies": { "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/utf8": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==" + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==" }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", - "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", + "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/helper-wasm-section": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-opt": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "@webassemblyjs/wast-printer": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-opt": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6", + "@webassemblyjs/wast-printer": "1.11.6" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", - "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", + "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", - "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", + "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", - "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", + "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", - "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", + "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", "dependencies": { - "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/ast": "1.11.6", "@xtuc/long": "4.2.2" } }, @@ -5004,9 +4720,9 @@ } }, "node_modules/acorn-import-assertions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", "peerDependencies": { "acorn": "^8" } @@ -5019,27 +4735,6 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/acorn-node": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", - "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", - "dependencies": { - "acorn": "^7.0.0", - "acorn-walk": "^7.0.0", - "xtend": "^4.0.2" - } - }, - "node_modules/acorn-node/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/acorn-walk": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", @@ -5181,16 +4876,24 @@ } }, "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dependencies": { - "color-convert": "^1.9.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -5409,9 +5112,9 @@ } }, "node_modules/axe-core": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.6.3.tgz", - "integrity": "sha512-/BQzOX780JhsxDnPpH4ZiyrJAzcd8AfzFPkv+89veFSr1rcMjuq2JDCwypKaPeB6ljHp9KjXhPpjgCvQlWYuqg==", + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.7.2.tgz", + "integrity": "sha512-zIURGIS1E1Q4pcrMjp+nnEh+16G56eG/MUllJH8yEvw7asDo7Ac9uhC9KIH5jzpITueEZolfYglnCGIuSBz39g==", "engines": { "node": ">=4" } @@ -5445,70 +5148,6 @@ "@babel/core": "^7.8.0" } }, - "node_modules/babel-jest/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/babel-jest/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/babel-jest/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/babel-jest/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/babel-jest/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-jest/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/babel-loader": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.3.0.tgz", @@ -5596,12 +5235,12 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", - "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.3.tgz", + "integrity": "sha512-bM3gHc337Dta490gg+/AseNB9L4YLHxq1nGKZZSHbhXv4aTYU2MD2cjza1Ru4S6975YLTaL1K8uJf6ukJhhmtw==", "dependencies": { "@babel/compat-data": "^7.17.7", - "@babel/helper-define-polyfill-provider": "^0.3.3", + "@babel/helper-define-polyfill-provider": "^0.4.0", "semver": "^6.1.1" }, "peerDependencies": { @@ -5617,23 +5256,23 @@ } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz", - "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==", + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.1.tgz", + "integrity": "sha512-ikFrZITKg1xH6pLND8zT14UPgjKHiGLqex7rGEZCH2EvhsneJaJPemmpQaIZV5AL03II+lXylw3UmddDK8RU5Q==", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.3.3", - "core-js-compat": "^3.25.1" + "@babel/helper-define-polyfill-provider": "^0.4.0", + "core-js-compat": "^3.30.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", - "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.0.tgz", + "integrity": "sha512-hDJtKjMLVa7Z+LwnTCxoDLQj6wdc+B8dun7ayF2fYieI6OzfuvcLMB32ihJZ4UhCBwNYGl5bg/x/P9cMdnkc2g==", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.3.3" + "@babel/helper-define-polyfill-provider": "^0.4.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" @@ -5846,9 +5485,9 @@ "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" }, "node_modules/browserslist": { - "version": "4.21.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", - "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "version": "4.21.7", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.7.tgz", + "integrity": "sha512-BauCXrQ7I2ftSqd2mvKHGo85XR0u7Ru3C/Hxsy/0TkfCtjrmAbPdzLGasmoiBxplpDXlPvdjX9u7srIMfgasNA==", "funding": [ { "type": "opencollective", @@ -5857,13 +5496,17 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "caniuse-lite": "^1.0.30001449", - "electron-to-chromium": "^1.4.284", - "node-releases": "^2.0.8", - "update-browserslist-db": "^1.0.10" + "caniuse-lite": "^1.0.30001489", + "electron-to-chromium": "^1.4.411", + "node-releases": "^2.0.12", + "update-browserslist-db": "^1.0.11" }, "bin": { "browserslist": "cli.js" @@ -5964,9 +5607,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001469", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001469.tgz", - "integrity": "sha512-Rcp7221ScNqQPP3W+lVOYDyjdR6dC+neEQCttoNr5bAyz54AboB4iwpnWgyi8P4YUsPybVzT4LgWiBbI3drL4g==", + "version": "1.0.30001497", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001497.tgz", + "integrity": "sha512-I4/duVK4wL6rAK/aKZl3HXB4g+lIZvaT4VLAn2rCgJ38jVLb0lv2Xug6QuqmxXFVRJMF74SPPWPJ/1Sdm3vCzw==", "funding": [ { "type": "opencollective", @@ -5975,6 +5618,10 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ] }, @@ -5995,16 +5642,18 @@ } }, "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/char-regex": { @@ -6170,12 +5819,31 @@ "node": ">= 4.0" } }, - "node_modules/collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==" + "node_modules/coa/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } }, - "node_modules/color-convert": { + "node_modules/coa/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/coa/node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", @@ -6183,20 +5851,68 @@ "color-name": "1.1.3" } }, - "node_modules/color-name": { + "node_modules/coa/node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" }, + "node_modules/coa/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/coa/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/coa/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, "node_modules/colord": { "version": "2.9.3", "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==" }, "node_modules/colorette": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", - "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==" + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" }, "node_modules/combined-stream": { "version": "1.0.8", @@ -6346,9 +6062,9 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, "node_modules/core-js": { - "version": "3.29.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.29.1.tgz", - "integrity": "sha512-+jwgnhg6cQxKYIIjGtAHq2nwUOolo9eoFZ4sHfUH09BLXBgxnH4gA0zEd+t+BO2cNB8idaBtZFcFTRjQJRJmAw==", + "version": "3.30.2", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.30.2.tgz", + "integrity": "sha512-uBJiDmwqsbJCWHAwjrx3cvjbMXP7xD72Dmsn5LOJpiRmE3WbBbN5rCqQ2Qh6Ek6/eOrjlWngEynBWo4VxerQhg==", "hasInstallScript": true, "funding": { "type": "opencollective", @@ -6356,9 +6072,9 @@ } }, "node_modules/core-js-compat": { - "version": "3.29.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.29.1.tgz", - "integrity": "sha512-QmchCua884D8wWskMX8tW5ydINzd8oSJVx38lx/pVkFGqztxt73GYre3pm/hyYq8bPf+MW5In4I/uRShFDsbrA==", + "version": "3.30.2", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.30.2.tgz", + "integrity": "sha512-nriW1nuJjUgvkEjIot1Spwakz52V9YkYHZAQG6A1eCgC8AA1p0zngrQEP9R0+V6hji5XilWKG1Bd0YRppmGimA==", "dependencies": { "browserslist": "^4.21.5" }, @@ -6368,9 +6084,9 @@ } }, "node_modules/core-js-pure": { - "version": "3.29.1", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.29.1.tgz", - "integrity": "sha512-4En6zYVi0i0XlXHVz/bi6l1XDjCqkKRq765NXuX+SnaIatlE96Odt5lMLjdxUiNI1v9OXI5DSLWYPlmTfkTktg==", + "version": "3.30.2", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.30.2.tgz", + "integrity": "sha512-p/npFUJXXBkCCTIlEGBdghofn00jWG6ZOtdoIXSJmAu2QBvN0IqpZXWweOytcwE6cfx8ZvVUy1vw8zxhe4Y2vg==", "hasInstallScript": true, "funding": { "type": "opencollective", @@ -6436,9 +6152,9 @@ } }, "node_modules/css-declaration-sorter": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.3.1.tgz", - "integrity": "sha512-fBffmak0bPAnyqc/HO8C3n2sHrp9wcqQz6ES9koRF2/mLOVAx9zIQ3Y7R29sYCteTPqMCwns4WYQoCX91Xl3+w==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.0.tgz", + "integrity": "sha512-jDfsatwWMWN0MODAFuHszfjphEXfNw9JUAhmY4pLu3TyTU+ohUpsbVtbU+1MZn4a47D9kqh03i4eyOm+74+zew==", "engines": { "node": "^10 || ^12 || >=14" }, @@ -6464,14 +6180,14 @@ } }, "node_modules/css-loader": { - "version": "6.7.3", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.3.tgz", - "integrity": "sha512-qhOH1KlBMnZP8FzRO6YCH9UHXQhVMcEGLyNdb7Hv2cpcmJbW0YrddO+tG1ab5nT41KpHIYGsbeHqxB9xPu1pKQ==", + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.8.1.tgz", + "integrity": "sha512-xDAXtEVGlD0gJ07iclwWVkLoZOpEvAWaSyf6W18S2pOC//K8+qUDIx8IIT3D+HjnmkJPQeesOPv5aiUaJsCM2g==", "dependencies": { "icss-utils": "^5.1.0", - "postcss": "^8.4.19", + "postcss": "^8.4.21", "postcss-modules-extract-imports": "^3.0.0", - "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-local-by-default": "^4.0.3", "postcss-modules-scope": "^3.0.0", "postcss-modules-values": "^4.0.0", "postcss-value-parser": "^4.2.0", @@ -6557,14 +6273,14 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "node_modules/css-minimizer-webpack-plugin/node_modules/schema-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.1.0.tgz", + "integrity": "sha512-Jw+GZVbP5IggB2WAn6UHI02LBwGmsIeYN/lNbSMZyDziQ7jmtAUrqKqDja+W89YHVs+KL/3IkIMltAklqB1vAw==", "dependencies": { "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", + "ajv": "^8.9.0", "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" + "ajv-keywords": "^5.1.0" }, "engines": { "node": ">= 12.13.0" @@ -6653,13 +6369,19 @@ "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==" }, "node_modules/cssdb": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-7.4.1.tgz", - "integrity": "sha512-0Q8NOMpXJ3iTDDbUv9grcmQAfdDx4qz+fN/+Md2FGbevT+6+bJNQ2LjB2YIUlLbpBTM32idU1Sb+tb/uGt6/XQ==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-7.6.0.tgz", + "integrity": "sha512-Nna7rph8V0jC6+JBY4Vk4ndErUmfJfV6NJCaZdurL0omggabiy+QB2HCQtu5c/ACLZ0I7REv7A4QyPIoYzZx0w==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + } + ] }, "node_modules/cssesc": { "version": "3.0.0", @@ -6803,9 +6525,9 @@ "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" }, "node_modules/csstype": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", - "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" }, "node_modules/damerau-levenshtein": { "version": "1.0.8", @@ -6852,15 +6574,16 @@ "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==" }, "node_modules/deep-equal": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.0.tgz", - "integrity": "sha512-RdpzE0Hv4lhowpIUKKMJfeH6C1pXdtT1/it80ubgWqwI3qpuxUBpC1S4hnHg+zjnuOoDkzUtUCEEkG+XG5l3Mw==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.1.tgz", + "integrity": "sha512-lKdkdV6EOGoVn65XaOsPdH4rMxTZOnmFyuIkMjM1i5HHCbfjC97dawgTAy0deYNfuqUqW+Q5VrVaQYtUpSd6yQ==", "dependencies": { + "array-buffer-byte-length": "^1.0.0", "call-bind": "^1.0.2", - "es-get-iterator": "^1.1.2", - "get-intrinsic": "^1.1.3", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.0", "is-arguments": "^1.1.1", - "is-array-buffer": "^3.0.1", + "is-array-buffer": "^3.0.2", "is-date-object": "^1.0.5", "is-regex": "^1.1.4", "is-shared-array-buffer": "^1.0.2", @@ -6868,7 +6591,7 @@ "object-is": "^1.1.5", "object-keys": "^1.1.1", "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", + "regexp.prototype.flags": "^1.5.0", "side-channel": "^1.0.4", "which-boxed-primitive": "^1.0.2", "which-collection": "^1.0.1", @@ -6925,14 +6648,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/defined": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.1.tgz", - "integrity": "sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -7000,22 +6715,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, - "node_modules/detective": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.1.tgz", - "integrity": "sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==", - "dependencies": { - "acorn-node": "^1.8.2", - "defined": "^1.0.0", - "minimist": "^1.2.6" - }, - "bin": { - "detective": "bin/detective.js" - }, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -7051,9 +6750,9 @@ "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==" }, "node_modules/dns-packet": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.4.0.tgz", - "integrity": "sha512-EgqGeaBB8hLiHLZtp/IbaDQTL8pZ0+IvwzSHA6d7VyMDM+B9hgddEMa9xjK5oYnw0ci0JQ6g2XCD7/f6cafU6g==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.0.tgz", + "integrity": "sha512-rza3UH1LwdHh9qyPXp8lkwpjSNk/AMD3dPytUoRoqnypDUhY0xvbdmVhWOfxO68frEfV9BU8V12Ez7ZsHGZpCQ==", "dependencies": { "@leichtgewicht/ip-codec": "^2.0.1" }, @@ -7211,9 +6910,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.335", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.335.tgz", - "integrity": "sha512-l/eowQqTnrq3gu+WSrdfkhfNHnPgYqlKAwxz7MTOj6mom19vpEDHNXl6dxDxyTiYuhemydprKr/HCrHfgk+OfQ==" + "version": "1.4.425", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.425.tgz", + "integrity": "sha512-wv1NufHxu11zfDbY4fglYQApMswleE9FL/DSeyOyauVXDZ+Kco96JK/tPfBUaDqfRarYp2WH2hJ/5UnVywp9Jg==" }, "node_modules/emittery": { "version": "0.8.1", @@ -7248,9 +6947,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.12.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", - "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==", + "version": "5.14.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.14.1.tgz", + "integrity": "sha512-Vklwq2vDKtl0y/vtwjSesgJ5MYS7Etuk5txS8VdKL4AOS1aUlD96zqIfsOSLQsdv3xgMRbtkWM8eG9XDfKUPow==", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -7367,9 +7066,9 @@ } }, "node_modules/es-module-lexer": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.2.1.tgz", + "integrity": "sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg==" }, "node_modules/es-set-tostringtag": { "version": "2.0.1", @@ -7422,11 +7121,14 @@ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "engines": { - "node": ">=0.8.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/escodegen": { @@ -7507,15 +7209,15 @@ } }, "node_modules/eslint": { - "version": "8.36.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.36.0.tgz", - "integrity": "sha512-Y956lmS7vDqomxlaaQAHVmeb4tNMp2FWIvU/RnU5BD3IKMD/MJPr76xdyr68P8tV1iNMvN2mRK0yy3c+UjL+bw==", + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.42.0.tgz", + "integrity": "sha512-ulg9Ms6E1WPf67PHaEY4/6E2tEn5/f7FXGzr3t9cBMugOmf1INYvuUwwh1aXQN4MfJ6a5K2iNwP3w4AColvI9A==", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.1", - "@eslint/js": "8.36.0", - "@humanwhocodes/config-array": "^0.11.8", + "@eslint/eslintrc": "^2.0.3", + "@eslint/js": "8.42.0", + "@humanwhocodes/config-array": "^0.11.10", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.10.0", @@ -7524,9 +7226,9 @@ "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.5.0", + "eslint-scope": "^7.2.0", + "eslint-visitor-keys": "^3.4.1", + "espree": "^9.5.2", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -7534,13 +7236,12 @@ "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", - "grapheme-splitter": "^1.0.4", + "graphemer": "^1.4.0", "ignore": "^5.2.0", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", @@ -7608,9 +7309,9 @@ } }, "node_modules/eslint-module-utils": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz", - "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", + "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", "dependencies": { "debug": "^3.2.7" }, @@ -7838,11 +7539,11 @@ } }, "node_modules/eslint-plugin-testing-library": { - "version": "5.10.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-5.10.2.tgz", - "integrity": "sha512-f1DmDWcz5SDM+IpCkEX0lbFqrrTs8HRsEElzDEqN/EBI0hpRj8Cns5+IVANXswE8/LeybIJqPAOQIFu2j5Y5sw==", + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-5.11.0.tgz", + "integrity": "sha512-ELY7Gefo+61OfXKlQeXNIDVVLPcvKTeiQOoMZG9TeuWa7Ln4dUNRv8JdRWBQI9Mbb427XGlVB1aa1QPZxBJM8Q==", "dependencies": { - "@typescript-eslint/utils": "^5.43.0" + "@typescript-eslint/utils": "^5.58.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0", @@ -7853,15 +7554,18 @@ } }, "node_modules/eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", + "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-utils": { @@ -7889,11 +7593,14 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", + "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-webpack-plugin": { @@ -7945,14 +7652,6 @@ "ajv": "^8.8.2" } }, - "node_modules/eslint-webpack-plugin/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, "node_modules/eslint-webpack-plugin/node_modules/jest-worker": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz", @@ -7972,14 +7671,14 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "node_modules/eslint-webpack-plugin/node_modules/schema-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.1.0.tgz", + "integrity": "sha512-Jw+GZVbP5IggB2WAn6UHI02LBwGmsIeYN/lNbSMZyDziQ7jmtAUrqKqDja+W89YHVs+KL/3IkIMltAklqB1vAw==", "dependencies": { "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", + "ajv": "^8.9.0", "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" + "ajv-keywords": "^5.1.0" }, "engines": { "node": ">= 12.13.0" @@ -8003,62 +7702,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/eslint/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/eslint/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/eslint/node_modules/globals": { "version": "13.20.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", @@ -8073,25 +7716,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/eslint/node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -8104,13 +7728,13 @@ } }, "node_modules/espree": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.0.tgz", - "integrity": "sha512-JPbJGhKc47++oo4JkEoTe2wjy4fmMwvFpgJT9cQzmfXKp22Dr6Hf1tdCteLz1h0P3t+mGvWZ+4Uankvh8+c6zw==", + "version": "9.5.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", + "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", "dependencies": { "acorn": "^8.8.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.1" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -8602,51 +8226,6 @@ } } }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, "node_modules/fork-ts-checker-webpack-plugin/node_modules/cosmiconfig": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", @@ -8676,14 +8255,6 @@ "node": ">=10" } }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, "node_modules/fork-ts-checker-webpack-plugin/node_modules/schema-utils": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", @@ -8701,17 +8272,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/fork-ts-checker-webpack-plugin/node_modules/tapable": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", @@ -8783,9 +8343,9 @@ } }, "node_modules/fs-monkey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz", - "integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==" + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.4.tgz", + "integrity": "sha512-INM/fWAxMICjttnD0DX1rBvinKskj5G1w+oy/pnm9u/tSlnBrzFonJMcalKJ30P8RRsPzKcCG7Q8l0jx5Fh9YQ==" }, "node_modules/fs.realpath": { "version": "1.0.0", @@ -8858,12 +8418,13 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", - "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", "dependencies": { "function-bind": "^1.1.1", "has": "^1.0.3", + "has-proto": "^1.0.1", "has-symbols": "^1.0.3" }, "funding": { @@ -9041,6 +8602,11 @@ "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==" }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" + }, "node_modules/gzip-size": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", @@ -9085,11 +8651,11 @@ } }, "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/has-property-descriptors": { @@ -9256,9 +8822,9 @@ } }, "node_modules/html-entities": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", - "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==" + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.5.tgz", + "integrity": "sha512-72TJlcMkYsEJASa/3HnX7VT59htM7iSHbH59NSZbtc+22Ap0Txnlx91sfeB+/A7wNZg7UxtZdhAW4y+/jimrdg==" }, "node_modules/html-escaper": { "version": "2.0.2", @@ -9286,9 +8852,9 @@ } }, "node_modules/html-webpack-plugin": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz", - "integrity": "sha512-sy88PC2cRTVxvETRgUHFrL4No3UxvcH8G1NepGhqaTT+GXN2kTamqasot0inS5hXeg1cMbFDt27zzo9p35lZVw==", + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.1.tgz", + "integrity": "sha512-cTUzZ1+NqjGEKjmVgZKLMdiFg3m9MdRXkZW2OEe69WYVi5ONLMmlnSZdXzGGMOq0C8jGDrL6EWyEDDUioHO/pA==", "dependencies": { "@types/html-minifier-terser": "^6.0.0", "html-minifier-terser": "^6.0.2", @@ -9471,9 +9037,9 @@ "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" }, "node_modules/immer": { - "version": "9.0.19", - "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.19.tgz", - "integrity": "sha512-eY+Y0qcsB4TZKwgQzLaE/lqYMlKhv5J9dyd2RhhtGhNo2njPXDqU9XPfcNfa3MIDsdtZt5KlkIsirlo4dHsWdQ==", + "version": "9.0.21", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", + "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", "funding": { "type": "opencollective", "url": "https://opencollective.com/immer" @@ -9494,14 +9060,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/import-fresh/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "engines": { - "node": ">=4" - } - }, "node_modules/import-local": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", @@ -9569,9 +9127,9 @@ } }, "node_modules/ipaddr.js": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", - "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz", + "integrity": "sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==", "engines": { "node": ">= 10" } @@ -9680,9 +9238,9 @@ } }, "node_modules/is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", + "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", "dependencies": { "has": "^1.0.3" }, @@ -10057,25 +9615,6 @@ "node": ">=8" } }, - "node_modules/istanbul-lib-report/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/istanbul-lib-source-maps": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", @@ -10110,14 +9649,14 @@ } }, "node_modules/jake": { - "version": "10.8.5", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz", - "integrity": "sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==", + "version": "10.8.7", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz", + "integrity": "sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==", "dependencies": { "async": "^3.2.3", "chalk": "^4.0.2", - "filelist": "^1.0.1", - "minimatch": "^3.0.4" + "filelist": "^1.0.4", + "minimatch": "^3.1.2" }, "bin": { "jake": "bin/cli.js" @@ -10126,79 +9665,15 @@ "node": ">=10" } }, - "node_modules/jake/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/jdenticon": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jdenticon/-/jdenticon-3.2.0.tgz", + "integrity": "sha512-z6Iq3fTODUMSOiR2nNYrqigS6Y0GvdXfyQWrUby7htDHvX7GNEwaWR4hcaL+FmhEgBe08Xkup/BKxXQhDJByPA==", "dependencies": { - "color-convert": "^2.0.1" + "canvas-renderer": "~2.2.0" }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jake/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jake/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jake/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jake/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jake/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jdenticon": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jdenticon/-/jdenticon-3.2.0.tgz", - "integrity": "sha512-z6Iq3fTODUMSOiR2nNYrqigS6Y0GvdXfyQWrUby7htDHvX7GNEwaWR4hcaL+FmhEgBe08Xkup/BKxXQhDJByPA==", - "dependencies": { - "canvas-renderer": "~2.2.0" - }, - "bin": { - "jdenticon": "bin/jdenticon.js" + "bin": { + "jdenticon": "bin/jdenticon.js" }, "engines": { "node": ">=6.4.0" @@ -10270,70 +9745,6 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/jest-circus/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-circus/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-circus/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-circus/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-circus/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-circus/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-cli": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.5.1.tgz", @@ -10367,70 +9778,6 @@ } } }, - "node_modules/jest-cli/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-cli/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-cli/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-cli/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-cli/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-cli/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-config": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-27.5.1.tgz", @@ -10473,259 +9820,67 @@ } } }, - "node_modules/jest-config/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/jest-diff": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", + "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", "dependencies": { - "color-convert": "^2.0.1" + "chalk": "^4.0.0", + "diff-sequences": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/jest-config/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/jest-docblock": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.5.1.tgz", + "integrity": "sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ==", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "detect-newline": "^3.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/jest-config/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/jest-each": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.5.1.tgz", + "integrity": "sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ==", "dependencies": { - "color-name": "~1.1.4" + "@jest/types": "^27.5.1", + "chalk": "^4.0.0", + "jest-get-type": "^27.5.1", + "jest-util": "^27.5.1", + "pretty-format": "^27.5.1" }, "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-config/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-config/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/jest-config/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/jest-environment-jsdom": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz", + "integrity": "sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw==", "dependencies": { - "has-flag": "^4.0.0" + "@jest/environment": "^27.5.1", + "@jest/fake-timers": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "jest-mock": "^27.5.1", + "jest-util": "^27.5.1", + "jsdom": "^16.6.0" }, "engines": { - "node": ">=8" + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/jest-diff": { + "node_modules/jest-environment-node": { "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", - "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-diff/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-diff/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-diff/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-diff/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-diff/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-diff/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-docblock": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.5.1.tgz", - "integrity": "sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ==", - "dependencies": { - "detect-newline": "^3.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-each": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.5.1.tgz", - "integrity": "sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ==", - "dependencies": { - "@jest/types": "^27.5.1", - "chalk": "^4.0.0", - "jest-get-type": "^27.5.1", - "jest-util": "^27.5.1", - "pretty-format": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-each/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-each/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-each/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-each/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-each/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-each/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-environment-jsdom": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz", - "integrity": "sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw==", - "dependencies": { - "@jest/environment": "^27.5.1", - "@jest/fake-timers": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "jest-mock": "^27.5.1", - "jest-util": "^27.5.1", - "jsdom": "^16.6.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-environment-node": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.5.1.tgz", - "integrity": "sha512-Jt4ZUnxdOsTGwSRAfKEnE6BcwsSPNOijjwifq5sDFSA2kesnXTvNqKHYgM0hDq3549Uf/KzdXNYn4wMZJPlFLw==", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.5.1.tgz", + "integrity": "sha512-Jt4ZUnxdOsTGwSRAfKEnE6BcwsSPNOijjwifq5sDFSA2kesnXTvNqKHYgM0hDq3549Uf/KzdXNYn4wMZJPlFLw==", "dependencies": { "@jest/environment": "^27.5.1", "@jest/fake-timers": "^27.5.1", @@ -10798,70 +9953,6 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/jest-jasmine2/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-jasmine2/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-jasmine2/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-jasmine2/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-jasmine2/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-jasmine2/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-leak-detector": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz", @@ -10882,157 +9973,29 @@ "chalk": "^4.0.0", "jest-diff": "^27.5.1", "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-matcher-utils/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-matcher-utils/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-message-util/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-message-util/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-message-util/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-message-util/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-message-util/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "pretty-format": "^27.5.1" + }, "engines": { - "node": ">=8" + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/jest-message-util/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/jest-message-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", + "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", "dependencies": { - "has-flag": "^4.0.0" + "@babel/code-frame": "^7.12.13", + "@jest/types": "^27.5.1", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^27.5.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, "engines": { - "node": ">=8" + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, "node_modules/jest-mock": { @@ -11104,70 +10067,6 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/jest-resolve/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-resolve/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-resolve/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-resolve/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-resolve/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-resolve/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-runner": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-27.5.1.tgz", @@ -11199,70 +10098,6 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/jest-runner/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-runner/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-runner/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-runner/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-runner/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runner/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-runtime": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.5.1.tgz", @@ -11295,70 +10130,6 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/jest-runtime/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-runtime/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-runtime/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-runtime/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-runtime/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runtime/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-serializer": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.5.1.tgz", @@ -11397,74 +10168,10 @@ "jest-util": "^27.5.1", "natural-compare": "^1.4.0", "pretty-format": "^27.5.1", - "semver": "^7.3.2" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-snapshot/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-snapshot/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-snapshot/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-snapshot/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" + "semver": "^7.3.2" }, "engines": { - "node": ">=8" + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, "node_modules/jest-util": { @@ -11483,70 +10190,6 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/jest-util/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-util/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-util/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-util/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-util/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-util/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-validate": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.5.1.tgz", @@ -11563,70 +10206,6 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/jest-validate/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-validate/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-validate/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-validate/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-validate/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-validate/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-watch-typeahead": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/jest-watch-typeahead/-/jest-watch-typeahead-1.1.0.tgz", @@ -11702,58 +10281,24 @@ } }, "node_modules/jest-watch-typeahead/node_modules/@types/yargs": { - "version": "17.0.23", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.23.tgz", - "integrity": "sha512-yuogunc04OnzGQCrfHx+Kk883Q4X0aSwmYZhKjI21m+SVYzjIbrWl8dOOwSv5hf2Um2pdCOXWo9isteZTNXUZQ==", + "version": "17.0.24", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", + "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", "dependencies": { "@types/yargs-parser": "*" } }, "node_modules/jest-watch-typeahead/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-watch-typeahead/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-watch-typeahead/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-watch-typeahead/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, "node_modules/jest-watch-typeahead/node_modules/emittery": { "version": "0.10.2", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", @@ -11765,14 +10310,6 @@ "url": "https://github.com/sindresorhus/emittery?sponsor=1" } }, - "node_modules/jest-watch-typeahead/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, "node_modules/jest-watch-typeahead/node_modules/jest-message-util": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", @@ -11879,22 +10416,6 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-watch-typeahead/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-watch-typeahead/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" - }, "node_modules/jest-watch-typeahead/node_modules/slash": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", @@ -11930,9 +10451,9 @@ } }, "node_modules/jest-watch-typeahead/node_modules/strip-ansi": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", - "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dependencies": { "ansi-regex": "^6.0.1" }, @@ -11943,107 +10464,32 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/jest-watch-typeahead/node_modules/strip-ansi/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/jest-watch-typeahead/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-watcher": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.5.1.tgz", - "integrity": "sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw==", - "dependencies": { - "@jest/test-result": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "jest-util": "^27.5.1", - "string-length": "^4.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-watcher/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-watcher/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-watcher/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-watcher/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-watcher/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/jest-watch-typeahead/node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/jest-watcher/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/jest-watcher": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.5.1.tgz", + "integrity": "sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw==", "dependencies": { - "has-flag": "^4.0.0" + "@jest/test-result": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "jest-util": "^27.5.1", + "string-length": "^4.0.1" }, "engines": { - "node": ">=8" + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, "node_modules/jest-worker": { @@ -12059,14 +10505,6 @@ "node": ">= 10.13.0" } }, - "node_modules/jest-worker/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, "node_modules/jest-worker/node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -12081,13 +10519,12 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/js-sdsl": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", - "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" + "node_modules/jiti": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz", + "integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==", + "bin": { + "jiti": "bin/jiti.js" } }, "node_modules/js-sha256": { @@ -12509,11 +10946,11 @@ } }, "node_modules/memfs": { - "version": "3.4.13", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.13.tgz", - "integrity": "sha512-omTM41g3Skpvx5dSYeZIbXKcXoAVc/AoMNwn9TKx++L/gaen/+4TTttmu8ZSch5vfVJ8uJvGbroTsIlslRg6lg==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", "dependencies": { - "fs-monkey": "^1.0.3" + "fs-monkey": "^1.0.4" }, "engines": { "node": ">= 4.0.0" @@ -12604,9 +11041,9 @@ } }, "node_modules/mini-css-extract-plugin": { - "version": "2.7.5", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.5.tgz", - "integrity": "sha512-9HaR++0mlgom81s95vvNjxkg52n2b5s//3ZTI1EtzFb98awsLSivs2LMsVqnQ3ay0PVhqWcGNyDaTE961FOcjQ==", + "version": "2.7.6", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.6.tgz", + "integrity": "sha512-Qk7HcgaPkGG6eD77mLvZS1nmxlao3j+9PkrT9Uc7HAE1id3F41+DdBRYRYkbyfNRGzm8/YWtzhw7nVPmwhqTQw==", "dependencies": { "schema-utils": "^4.0.0" }, @@ -12653,14 +11090,14 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "node_modules/mini-css-extract-plugin/node_modules/schema-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.1.0.tgz", + "integrity": "sha512-Jw+GZVbP5IggB2WAn6UHI02LBwGmsIeYN/lNbSMZyDziQ7jmtAUrqKqDja+W89YHVs+KL/3IkIMltAklqB1vAw==", "dependencies": { "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", + "ajv": "^8.9.0", "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" + "ajv-keywords": "^5.1.0" }, "engines": { "node": ">= 12.13.0" @@ -12722,10 +11159,26 @@ "multicast-dns": "cli.js" } }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, "node_modules/nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -12779,9 +11232,9 @@ "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==" }, "node_modules/node-releases": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", - "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==" + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.12.tgz", + "integrity": "sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==" }, "node_modules/normalize-path": { "version": "3.0.0", @@ -12833,9 +11286,9 @@ } }, "node_modules/nwsapi": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz", - "integrity": "sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==" + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.5.tgz", + "integrity": "sha512-6xpotnECFy/og7tKSBVmUNft7J3jyXAka4XvG6AUhFWRz+Q/Ljus7znJAA3bxColfQLdS+XsjoodtJfCgeTEFQ==" }, "node_modules/object-assign": { "version": "4.1.1", @@ -12939,14 +11392,15 @@ } }, "node_modules/object.getownpropertydescriptors": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.5.tgz", - "integrity": "sha512-yDNzckpM6ntyQiGTik1fKV1DcVDRS+w8bvpWNCBanvH5LfRX9O8WTHqQzG4RZwRAM4I0oU7TV11Lj5v0g20ibw==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.6.tgz", + "integrity": "sha512-lq+61g26E/BgHv0ZTFgRvi7NMEPuAxLkFU7rukXjc/AlwH4Am5xXVnIXy3un1bg/JPbXHrixRkK1itUzzPiIjQ==", "dependencies": { "array.prototype.reduce": "^1.0.5", "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "define-properties": "^1.2.0", + "es-abstract": "^1.21.2", + "safe-array-concat": "^1.0.0" }, "engines": { "node": ">= 0.8" @@ -13391,9 +11845,9 @@ } }, "node_modules/postcss": { - "version": "8.4.21", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", - "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", + "version": "8.4.24", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz", + "integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==", "funding": [ { "type": "opencollective", @@ -13402,10 +11856,14 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "nanoid": "^3.3.4", + "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" }, @@ -13782,16 +12240,16 @@ } }, "node_modules/postcss-import": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-14.1.0.tgz", - "integrity": "sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==", + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", "dependencies": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", "resolve": "^1.1.7" }, "engines": { - "node": ">=10.0.0" + "node": ">=14.0.0" }, "peerDependencies": { "postcss": "^8.0.0" @@ -13843,15 +12301,15 @@ } }, "node_modules/postcss-load-config": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", - "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz", + "integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==", "dependencies": { "lilconfig": "^2.0.5", - "yaml": "^1.10.2" + "yaml": "^2.1.1" }, "engines": { - "node": ">= 10" + "node": ">= 14" }, "funding": { "type": "opencollective", @@ -13870,6 +12328,14 @@ } } }, + "node_modules/postcss-load-config/node_modules/yaml": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", + "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==", + "engines": { + "node": ">= 14" + } + }, "node_modules/postcss-loader": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-6.2.1.tgz", @@ -14017,9 +12483,9 @@ } }, "node_modules/postcss-modules-local-by-default": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", - "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz", + "integrity": "sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA==", "dependencies": { "icss-utils": "^5.0.0", "postcss-selector-parser": "^6.0.2", @@ -14061,11 +12527,11 @@ } }, "node_modules/postcss-nested": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.0.tgz", - "integrity": "sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", + "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", "dependencies": { - "postcss-selector-parser": "^6.0.10" + "postcss-selector-parser": "^6.0.11" }, "engines": { "node": ">=12.0" @@ -14459,9 +12925,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.0.11", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz", - "integrity": "sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==", + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", + "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -14609,6 +13075,11 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/pretty-format/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + }, "node_modules/prismjs": { "version": "1.29.0", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", @@ -14758,17 +13229,6 @@ } ] }, - "node_modules/quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/raf": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", @@ -14887,70 +13347,6 @@ "node": ">=14" } }, - "node_modules/react-dev-utils/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/react-dev-utils/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/react-dev-utils/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/react-dev-utils/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/react-dev-utils/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/react-dev-utils/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, "node_modules/react-dev-utils/node_modules/loader-utils": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz", @@ -14959,17 +13355,6 @@ "node": ">= 12.13.0" } }, - "node_modules/react-dev-utils/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", @@ -14988,9 +13373,9 @@ "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" }, "node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, "node_modules/react-refresh": { "version": "0.11.0", @@ -15001,11 +13386,11 @@ } }, "node_modules/react-router": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.10.0.tgz", - "integrity": "sha512-Nrg0BWpQqrC3ZFFkyewrflCud9dio9ME3ojHCF/WLsprJVzkq3q3UeEhMCAW1dobjeGbWgjNn/PVF6m46ANxXQ==", + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.12.1.tgz", + "integrity": "sha512-evd/GrKJOeOypD0JB9e1r7pQh2gWCsTbUfq059Wm1AFT/K2MNZuDo19lFtAgIhlBrp0MmpgpqtvZC7LPAs7vSw==", "dependencies": { - "@remix-run/router": "1.5.0" + "@remix-run/router": "1.6.3" }, "engines": { "node": ">=14" @@ -15015,12 +13400,12 @@ } }, "node_modules/react-router-dom": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.10.0.tgz", - "integrity": "sha512-E5dfxRPuXKJqzwSe/qGcqdwa18QiWC6f3H3cWXM24qj4N0/beCIf/CWTipop2xm7mR0RCS99NnaqPNjHtrAzCg==", + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.12.1.tgz", + "integrity": "sha512-POIZN9UDKWwEDga054LvYr2KnK8V+0HR4Ny4Bwv8V7/FZCPxJgsCjYxXGxqxzHs7VBxMKZfgvtKhafuJkJSPGA==", "dependencies": { - "@remix-run/router": "1.5.0", - "react-router": "6.10.0" + "@remix-run/router": "1.6.3", + "react-router": "6.12.1" }, "engines": { "node": ">=14" @@ -15244,13 +13629,13 @@ "integrity": "sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==" }, "node_modules/regexp.prototype.flags": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", - "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", + "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "functions-have-names": "^1.2.2" + "define-properties": "^1.2.0", + "functions-have-names": "^1.2.3" }, "engines": { "node": ">= 0.4" @@ -15353,11 +13738,11 @@ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" }, "node_modules/resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", "dependencies": { - "is-core-module": "^2.9.0", + "is-core-module": "^2.11.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -15379,7 +13764,7 @@ "node": ">=8" } }, - "node_modules/resolve-from": { + "node_modules/resolve-cwd/node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", @@ -15387,6 +13772,14 @@ "node": ">=8" } }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "engines": { + "node": ">=4" + } + }, "node_modules/resolve-url-loader": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-4.0.0.tgz", @@ -15532,21 +13925,6 @@ "node": ">=0.4.0" } }, - "node_modules/rewire/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/rewire/node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -15556,52 +13934,6 @@ "sprintf-js": "~1.0.2" } }, - "node_modules/rewire/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/rewire/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/rewire/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/rewire/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/rewire/node_modules/eslint": { "version": "7.32.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", @@ -15740,15 +14072,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/rewire/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/rewire/node_modules/ignore": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", @@ -15764,23 +14087,11 @@ "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/rewire/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" + "argparse": "^1.0.7", + "esprima": "^4.0.0" }, - "engines": { - "node": ">=8" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, "node_modules/rewire/node_modules/type-fest": { @@ -15838,14 +14149,6 @@ "rollup": "^2.0.0" } }, - "node_modules/rollup-plugin-terser/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, "node_modules/rollup-plugin-terser/node_modules/jest-worker": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", @@ -15867,17 +14170,6 @@ "randombytes": "^2.1.0" } }, - "node_modules/rollup-plugin-terser/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -15900,6 +14192,23 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/safe-array-concat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.0.tgz", + "integrity": "sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -16004,9 +14313,9 @@ } }, "node_modules/schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.2.0.tgz", + "integrity": "sha512-0zTyLGyDJYd/MBxG1AhJkKa6fpEBds4OQO2ut0w7OYG+ZGhGea09lijvzsqegYSik88zc7cUtIlnnO+/BvD6gQ==", "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -16037,9 +14346,9 @@ } }, "node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -16224,9 +14533,9 @@ } }, "node_modules/shell-quote": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.0.tgz", - "integrity": "sha512-QHsz8GgQIGKlRi24yFc6a6lN69Idnx634w49ay6+jA5yFh7a1UY+4Rp6HPx/L/1zcEDPEij8cIsiqR6bQsE5VQ==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -16279,39 +14588,6 @@ "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/slice-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/sockjs": { "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", @@ -16336,11 +14612,11 @@ "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==" }, "node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", "engines": { - "node": ">= 8" + "node": ">=0.10.0" } }, "node_modules/source-map-js": { @@ -16659,9 +14935,9 @@ } }, "node_modules/style-loader": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.2.tgz", - "integrity": "sha512-RHs/vcrKdQK8wZliteNK4NKzxvLBzpuHMqYmUVWeKa6MkaIQ97ZTOS0b+zapZhy6GcrgWnvWYCMHRirC3FsUmw==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.3.tgz", + "integrity": "sha512-53BiGLXAcll9maCYtZi2RCQZKa8NQQai5C4horqKyRmHj9H7QmcUyucrH+4KW/gBQbXM2AsB0axoEcFZPlfPcw==", "engines": { "node": ">= 12.13.0" }, @@ -16689,42 +14965,59 @@ } }, "node_modules/stylis": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.1.3.tgz", - "integrity": "sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/sucrase": { + "version": "3.32.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.32.0.tgz", + "integrity": "sha512-ydQOU34rpSyj2TGyz4D2p8rbktIOZ8QY9s+DGLvFU1i5pWJE8vkpruCjGCMHsdXwnD7JDcS+noSwM/a7zyNFDQ==", "dependencies": { - "has-flag": "^3.0.0" + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "7.1.6", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" }, "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/supports-hyperlinks": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", - "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", - "dependencies": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", "engines": { - "node": ">=8" + "node": ">= 6" } }, - "node_modules/supports-hyperlinks/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/sucrase/node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, "engines": { - "node": ">=8" + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/supports-hyperlinks/node_modules/supports-color": { + "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", @@ -16735,6 +15028,18 @@ "node": ">=8" } }, + "node_modules/supports-hyperlinks": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", + "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -16778,6 +15083,17 @@ "node": ">=4.0.0" } }, + "node_modules/svgo/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/svgo/node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -16786,6 +15102,32 @@ "sprintf-js": "~1.0.2" } }, + "node_modules/svgo/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/svgo/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/svgo/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, "node_modules/svgo/node_modules/css-select": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", @@ -16831,6 +15173,22 @@ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" }, + "node_modules/svgo/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/svgo/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, "node_modules/svgo/node_modules/js-yaml": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", @@ -16851,6 +15209,17 @@ "boolbase": "~1.0.0" } }, + "node_modules/svgo/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -16895,50 +15264,42 @@ "dev": true }, "node_modules/tailwindcss": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.2.7.tgz", - "integrity": "sha512-B6DLqJzc21x7wntlH/GsZwEXTBttVSl1FtCzC8WP4oBc/NKef7kaax5jeihkkCEWc831/5NDJ9gRNDK6NEioQQ==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.2.tgz", + "integrity": "sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w==", "dependencies": { + "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.5.3", - "color-name": "^1.1.4", - "detective": "^5.2.1", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.2.12", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", - "lilconfig": "^2.0.6", + "jiti": "^1.18.2", + "lilconfig": "^2.1.0", "micromatch": "^4.0.5", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.0.0", - "postcss": "^8.0.9", - "postcss-import": "^14.1.0", - "postcss-js": "^4.0.0", - "postcss-load-config": "^3.1.4", - "postcss-nested": "6.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", "postcss-selector-parser": "^6.0.11", "postcss-value-parser": "^4.2.0", - "quick-lru": "^5.1.1", - "resolve": "^1.22.1" + "resolve": "^1.22.2", + "sucrase": "^3.32.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" }, "engines": { - "node": ">=12.13.0" - }, - "peerDependencies": { - "postcss": "^8.0.9" + "node": ">=14.0.0" } }, - "node_modules/tailwindcss/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -16999,12 +15360,12 @@ } }, "node_modules/terser": { - "version": "5.16.6", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.6.tgz", - "integrity": "sha512-IBZ+ZQIA9sMaXmRZCUMDjNH0D5AQQfdn4WUjHL0+1lF4TP1IHRJbrhb6fNaXWikrYQTSkb7SLxkeXAiy1p7mbg==", + "version": "5.17.7", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.17.7.tgz", + "integrity": "sha512-/bi0Zm2C6VAexlGgLlVxA0P2lru/sdLyfCVaRMfKVo9nWxbmz7f/sD8VPybPeSUJaJcwmCJis9pBIhcVcG1QcQ==", "dependencies": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, @@ -17016,15 +15377,15 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.7", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.7.tgz", - "integrity": "sha512-AfKwIktyP7Cu50xNjXF/6Qb5lBNzYaWpU6YfoX3uZicTx0zTy0stDDCsvjDapKsSDvOeWo5MEq4TmdBy2cNoHw==", + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", + "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", "dependencies": { "@jridgewell/trace-mapping": "^0.3.17", "jest-worker": "^27.4.5", "schema-utils": "^3.1.1", "serialize-javascript": "^6.0.1", - "terser": "^5.16.5" + "terser": "^5.16.8" }, "engines": { "node": ">= 10.13.0" @@ -17071,6 +15432,25 @@ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/throat": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.2.tgz", @@ -17114,9 +15494,9 @@ } }, "node_modules/tough-cookie": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", - "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", + "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", @@ -17151,6 +15531,11 @@ "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==" }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" + }, "node_modules/tsconfig-paths": { "version": "3.14.2", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", @@ -17182,9 +15567,9 @@ } }, "node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", + "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==" }, "node_modules/tsutils": { "version": "3.21.0", @@ -17372,9 +15757,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", - "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", + "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", "funding": [ { "type": "opencollective", @@ -17383,6 +15768,10 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { @@ -17390,7 +15779,7 @@ "picocolors": "^1.0.0" }, "bin": { - "browserslist-lint": "cli.js" + "update-browserslist-db": "cli.js" }, "peerDependencies": { "browserslist": ">= 4.21.0" @@ -17472,6 +15861,14 @@ "node": ">=10.12.0" } }, + "node_modules/v8-to-istanbul/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "engines": { + "node": ">= 8" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -17542,21 +15939,21 @@ } }, "node_modules/webpack": { - "version": "5.76.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.2.tgz", - "integrity": "sha512-Th05ggRm23rVzEOlX8y67NkYCHa9nTNcwHPBhdg+lKG+mtiW7XgggjAeeLnADAe7mLjJ6LUNfgHAuRRh+Z6J7w==", + "version": "5.86.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.86.0.tgz", + "integrity": "sha512-3BOvworZ8SO/D4GVP+GoRC3fVeg5MO4vzmq8TJJEkdmopxyazGDxN8ClqN12uzrZW9Tv8EED8v5VSb6Sqyi0pg==", "dependencies": { "@types/eslint-scope": "^3.7.3", - "@types/estree": "^0.0.51", - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/wasm-edit": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", + "@types/estree": "^1.0.0", + "@webassemblyjs/ast": "^1.11.5", + "@webassemblyjs/wasm-edit": "^1.11.5", + "@webassemblyjs/wasm-parser": "^1.11.5", "acorn": "^8.7.1", - "acorn-import-assertions": "^1.7.6", + "acorn-import-assertions": "^1.9.0", "browserslist": "^4.14.5", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.10.0", - "es-module-lexer": "^0.9.0", + "enhanced-resolve": "^5.14.1", + "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", @@ -17565,9 +15962,9 @@ "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^3.1.0", + "schema-utils": "^3.1.2", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", + "terser-webpack-plugin": "^5.3.7", "watchpack": "^2.4.0", "webpack-sources": "^3.2.3" }, @@ -17641,14 +16038,14 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "node_modules/webpack-dev-middleware/node_modules/schema-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.1.0.tgz", + "integrity": "sha512-Jw+GZVbP5IggB2WAn6UHI02LBwGmsIeYN/lNbSMZyDziQ7jmtAUrqKqDja+W89YHVs+KL/3IkIMltAklqB1vAw==", "dependencies": { "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", + "ajv": "^8.9.0", "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" + "ajv-keywords": "^5.1.0" }, "engines": { "node": ">= 12.13.0" @@ -17659,9 +16056,9 @@ } }, "node_modules/webpack-dev-server": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.13.1.tgz", - "integrity": "sha512-5tWg00bnWbYgkN+pd5yISQKDejRBYGEw15RaEEslH+zdbNDxxaZvEAO2WulaSaFKb5n3YG8JXsGaDsut1D0xdA==", + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.0.tgz", + "integrity": "sha512-HmNB5QeSl1KpulTBQ8UT4FPrByYyaLxpJoQ0+s7EvUrMc16m0ZS1sgb1XGqzmgCPk0c9y+aaXxn11tbLzuM7NQ==", "dependencies": { "@types/bonjour": "^3.5.9", "@types/connect-history-api-fallback": "^1.3.5", @@ -17748,14 +16145,14 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "node_modules/webpack-dev-server/node_modules/schema-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.1.0.tgz", + "integrity": "sha512-Jw+GZVbP5IggB2WAn6UHI02LBwGmsIeYN/lNbSMZyDziQ7jmtAUrqKqDja+W89YHVs+KL/3IkIMltAklqB1vAw==", "dependencies": { "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", + "ajv": "^8.9.0", "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" + "ajv-keywords": "^5.1.0" }, "engines": { "node": ">= 12.13.0" @@ -17828,11 +16225,6 @@ "node": ">=10.13.0" } }, - "node_modules/webpack/node_modules/@types/estree": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==" - }, "node_modules/webpack/node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -17987,26 +16379,26 @@ } }, "node_modules/workbox-background-sync": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-6.5.4.tgz", - "integrity": "sha512-0r4INQZMyPky/lj4Ou98qxcThrETucOde+7mRGJl13MPJugQNKeZQOdIJe/1AchOP23cTqHcN/YVpD6r8E6I8g==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-6.6.0.tgz", + "integrity": "sha512-jkf4ZdgOJxC9u2vztxLuPT/UjlH7m/nWRQ/MgGL0v8BJHoZdVGJd18Kck+a0e55wGXdqyHO+4IQTk0685g4MUw==", "dependencies": { "idb": "^7.0.1", - "workbox-core": "6.5.4" + "workbox-core": "6.6.0" } }, "node_modules/workbox-broadcast-update": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-6.5.4.tgz", - "integrity": "sha512-I/lBERoH1u3zyBosnpPEtcAVe5lwykx9Yg1k6f8/BGEPGaMMgZrwVrqL1uA9QZ1NGGFoyE6t9i7lBjOlDhFEEw==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-6.6.0.tgz", + "integrity": "sha512-nm+v6QmrIFaB/yokJmQ/93qIJ7n72NICxIwQwe5xsZiV2aI93MGGyEyzOzDPVz5THEr5rC3FJSsO3346cId64Q==", "dependencies": { - "workbox-core": "6.5.4" + "workbox-core": "6.6.0" } }, "node_modules/workbox-build": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-6.5.4.tgz", - "integrity": "sha512-kgRevLXEYvUW9WS4XoziYqZ8Q9j/2ziJYEtTrjdz5/L/cTUa2XfyMP2i7c3p34lgqJ03+mTiz13SdFef2POwbA==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-6.6.0.tgz", + "integrity": "sha512-Tjf+gBwOTuGyZwMz2Nk/B13Fuyeo0Q84W++bebbVsfr9iLkDSo6j6PST8tET9HYA58mlRXwlMGpyWO8ETJiXdQ==", "dependencies": { "@apideck/better-ajv-errors": "^0.3.1", "@babel/core": "^7.11.1", @@ -18030,21 +16422,21 @@ "strip-comments": "^2.0.1", "tempy": "^0.6.0", "upath": "^1.2.0", - "workbox-background-sync": "6.5.4", - "workbox-broadcast-update": "6.5.4", - "workbox-cacheable-response": "6.5.4", - "workbox-core": "6.5.4", - "workbox-expiration": "6.5.4", - "workbox-google-analytics": "6.5.4", - "workbox-navigation-preload": "6.5.4", - "workbox-precaching": "6.5.4", - "workbox-range-requests": "6.5.4", - "workbox-recipes": "6.5.4", - "workbox-routing": "6.5.4", - "workbox-strategies": "6.5.4", - "workbox-streams": "6.5.4", - "workbox-sw": "6.5.4", - "workbox-window": "6.5.4" + "workbox-background-sync": "6.6.0", + "workbox-broadcast-update": "6.6.0", + "workbox-cacheable-response": "6.6.0", + "workbox-core": "6.6.0", + "workbox-expiration": "6.6.0", + "workbox-google-analytics": "6.6.0", + "workbox-navigation-preload": "6.6.0", + "workbox-precaching": "6.6.0", + "workbox-range-requests": "6.6.0", + "workbox-recipes": "6.6.0", + "workbox-routing": "6.6.0", + "workbox-strategies": "6.6.0", + "workbox-streams": "6.6.0", + "workbox-sw": "6.6.0", + "workbox-window": "6.6.0" }, "engines": { "node": ">=10.0.0" @@ -18135,117 +16527,118 @@ } }, "node_modules/workbox-cacheable-response": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-6.5.4.tgz", - "integrity": "sha512-DCR9uD0Fqj8oB2TSWQEm1hbFs/85hXXoayVwFKLVuIuxwJaihBsLsp4y7J9bvZbqtPJ1KlCkmYVGQKrBU4KAug==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-6.6.0.tgz", + "integrity": "sha512-JfhJUSQDwsF1Xv3EV1vWzSsCOZn4mQ38bWEBR3LdvOxSPgB65gAM6cS2CX8rkkKHRgiLrN7Wxoyu+TuH67kHrw==", + "deprecated": "workbox-background-sync@6.6.0", "dependencies": { - "workbox-core": "6.5.4" + "workbox-core": "6.6.0" } }, "node_modules/workbox-core": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-6.5.4.tgz", - "integrity": "sha512-OXYb+m9wZm8GrORlV2vBbE5EC1FKu71GGp0H4rjmxmF4/HLbMCoTFws87M3dFwgpmg0v00K++PImpNQ6J5NQ6Q==" + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-6.6.0.tgz", + "integrity": "sha512-GDtFRF7Yg3DD859PMbPAYPeJyg5gJYXuBQAC+wyrWuuXgpfoOrIQIvFRZnQ7+czTIQjIr1DhLEGFzZanAT/3bQ==" }, "node_modules/workbox-expiration": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-6.5.4.tgz", - "integrity": "sha512-jUP5qPOpH1nXtjGGh1fRBa1wJL2QlIb5mGpct3NzepjGG2uFFBn4iiEBiI9GUmfAFR2ApuRhDydjcRmYXddiEQ==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-6.6.0.tgz", + "integrity": "sha512-baplYXcDHbe8vAo7GYvyAmlS4f6998Jff513L4XvlzAOxcl8F620O91guoJ5EOf5qeXG4cGdNZHkkVAPouFCpw==", "dependencies": { "idb": "^7.0.1", - "workbox-core": "6.5.4" + "workbox-core": "6.6.0" } }, "node_modules/workbox-google-analytics": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-6.5.4.tgz", - "integrity": "sha512-8AU1WuaXsD49249Wq0B2zn4a/vvFfHkpcFfqAFHNHwln3jK9QUYmzdkKXGIZl9wyKNP+RRX30vcgcyWMcZ9VAg==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-6.6.0.tgz", + "integrity": "sha512-p4DJa6OldXWd6M9zRl0H6vB9lkrmqYFkRQ2xEiNdBFp9U0LhsGO7hsBscVEyH9H2/3eZZt8c97NB2FD9U2NJ+Q==", "dependencies": { - "workbox-background-sync": "6.5.4", - "workbox-core": "6.5.4", - "workbox-routing": "6.5.4", - "workbox-strategies": "6.5.4" + "workbox-background-sync": "6.6.0", + "workbox-core": "6.6.0", + "workbox-routing": "6.6.0", + "workbox-strategies": "6.6.0" } }, "node_modules/workbox-navigation-preload": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-6.5.4.tgz", - "integrity": "sha512-IIwf80eO3cr8h6XSQJF+Hxj26rg2RPFVUmJLUlM0+A2GzB4HFbQyKkrgD5y2d84g2IbJzP4B4j5dPBRzamHrng==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-6.6.0.tgz", + "integrity": "sha512-utNEWG+uOfXdaZmvhshrh7KzhDu/1iMHyQOV6Aqup8Mm78D286ugu5k9MFD9SzBT5TcwgwSORVvInaXWbvKz9Q==", "dependencies": { - "workbox-core": "6.5.4" + "workbox-core": "6.6.0" } }, "node_modules/workbox-precaching": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-6.5.4.tgz", - "integrity": "sha512-hSMezMsW6btKnxHB4bFy2Qfwey/8SYdGWvVIKFaUm8vJ4E53JAY+U2JwLTRD8wbLWoP6OVUdFlXsTdKu9yoLTg==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-6.6.0.tgz", + "integrity": "sha512-eYu/7MqtRZN1IDttl/UQcSZFkHP7dnvr/X3Vn6Iw6OsPMruQHiVjjomDFCNtd8k2RdjLs0xiz9nq+t3YVBcWPw==", "dependencies": { - "workbox-core": "6.5.4", - "workbox-routing": "6.5.4", - "workbox-strategies": "6.5.4" + "workbox-core": "6.6.0", + "workbox-routing": "6.6.0", + "workbox-strategies": "6.6.0" } }, "node_modules/workbox-range-requests": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-6.5.4.tgz", - "integrity": "sha512-Je2qR1NXCFC8xVJ/Lux6saH6IrQGhMpDrPXWZWWS8n/RD+WZfKa6dSZwU+/QksfEadJEr/NfY+aP/CXFFK5JFg==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-6.6.0.tgz", + "integrity": "sha512-V3aICz5fLGq5DpSYEU8LxeXvsT//mRWzKrfBOIxzIdQnV/Wj7R+LyJVTczi4CQ4NwKhAaBVaSujI1cEjXW+hTw==", "dependencies": { - "workbox-core": "6.5.4" + "workbox-core": "6.6.0" } }, "node_modules/workbox-recipes": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-6.5.4.tgz", - "integrity": "sha512-QZNO8Ez708NNwzLNEXTG4QYSKQ1ochzEtRLGaq+mr2PyoEIC1xFW7MrWxrONUxBFOByksds9Z4//lKAX8tHyUA==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-6.6.0.tgz", + "integrity": "sha512-TFi3kTgYw73t5tg73yPVqQC8QQjxJSeqjXRO4ouE/CeypmP2O/xqmB/ZFBBQazLTPxILUQ0b8aeh0IuxVn9a6A==", "dependencies": { - "workbox-cacheable-response": "6.5.4", - "workbox-core": "6.5.4", - "workbox-expiration": "6.5.4", - "workbox-precaching": "6.5.4", - "workbox-routing": "6.5.4", - "workbox-strategies": "6.5.4" + "workbox-cacheable-response": "6.6.0", + "workbox-core": "6.6.0", + "workbox-expiration": "6.6.0", + "workbox-precaching": "6.6.0", + "workbox-routing": "6.6.0", + "workbox-strategies": "6.6.0" } }, "node_modules/workbox-routing": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-6.5.4.tgz", - "integrity": "sha512-apQswLsbrrOsBUWtr9Lf80F+P1sHnQdYodRo32SjiByYi36IDyL2r7BH1lJtFX8fwNHDa1QOVY74WKLLS6o5Pg==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-6.6.0.tgz", + "integrity": "sha512-x8gdN7VDBiLC03izAZRfU+WKUXJnbqt6PG9Uh0XuPRzJPpZGLKce/FkOX95dWHRpOHWLEq8RXzjW0O+POSkKvw==", "dependencies": { - "workbox-core": "6.5.4" + "workbox-core": "6.6.0" } }, "node_modules/workbox-strategies": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-6.5.4.tgz", - "integrity": "sha512-DEtsxhx0LIYWkJBTQolRxG4EI0setTJkqR4m7r4YpBdxtWJH1Mbg01Cj8ZjNOO8etqfA3IZaOPHUxCs8cBsKLw==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-6.6.0.tgz", + "integrity": "sha512-eC07XGuINAKUWDnZeIPdRdVja4JQtTuc35TZ8SwMb1ztjp7Ddq2CJ4yqLvWzFWGlYI7CG/YGqaETntTxBGdKgQ==", "dependencies": { - "workbox-core": "6.5.4" + "workbox-core": "6.6.0" } }, "node_modules/workbox-streams": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-6.5.4.tgz", - "integrity": "sha512-FXKVh87d2RFXkliAIheBojBELIPnWbQdyDvsH3t74Cwhg0fDheL1T8BqSM86hZvC0ZESLsznSYWw+Va+KVbUzg==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-6.6.0.tgz", + "integrity": "sha512-rfMJLVvwuED09CnH1RnIep7L9+mj4ufkTyDPVaXPKlhi9+0czCu+SJggWCIFbPpJaAZmp2iyVGLqS3RUmY3fxg==", "dependencies": { - "workbox-core": "6.5.4", - "workbox-routing": "6.5.4" + "workbox-core": "6.6.0", + "workbox-routing": "6.6.0" } }, "node_modules/workbox-sw": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-6.5.4.tgz", - "integrity": "sha512-vo2RQo7DILVRoH5LjGqw3nphavEjK4Qk+FenXeUsknKn14eCNedHOXWbmnvP4ipKhlE35pvJ4yl4YYf6YsJArA==" + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-6.6.0.tgz", + "integrity": "sha512-R2IkwDokbtHUE4Kus8pKO5+VkPHD2oqTgl+XJwh4zbF1HyjAbgNmK/FneZHVU7p03XUt9ICfuGDYISWG9qV/CQ==" }, "node_modules/workbox-webpack-plugin": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-webpack-plugin/-/workbox-webpack-plugin-6.5.4.tgz", - "integrity": "sha512-LmWm/zoaahe0EGmMTrSLUi+BjyR3cdGEfU3fS6PN1zKFYbqAKuQ+Oy/27e4VSXsyIwAw8+QDfk1XHNGtZu9nQg==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-webpack-plugin/-/workbox-webpack-plugin-6.6.0.tgz", + "integrity": "sha512-xNZIZHalboZU66Wa7x1YkjIqEy1gTR+zPM+kjrYJzqN7iurYZBctBLISyScjhkJKYuRrZUP0iqViZTh8rS0+3A==", "dependencies": { "fast-json-stable-stringify": "^2.1.0", "pretty-bytes": "^5.4.1", "upath": "^1.2.0", "webpack-sources": "^1.4.3", - "workbox-build": "6.5.4" + "workbox-build": "6.6.0" }, "engines": { "node": ">=10.0.0" @@ -18272,12 +16665,12 @@ } }, "node_modules/workbox-window": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-6.5.4.tgz", - "integrity": "sha512-HnLZJDwYBE+hpG25AQBO8RUWBJRaCsI9ksQJEp3aCOFCaG5kqaToAYXFRAHxzRluM2cQbGzdQF5rjKPWPA1fug==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-6.6.0.tgz", + "integrity": "sha512-L4N9+vka17d16geaJXXRjENLFldvkWy7JyGxElRD0JvBxvFEd8LOhr+uXCcar/NzAmIBRv9EZ+M+Qr4mOoBITw==", "dependencies": { "@types/trusted-types": "^2.0.2", - "workbox-core": "6.5.4" + "workbox-core": "6.6.0" } }, "node_modules/wrap-ansi": { @@ -18296,36 +16689,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/pkg/webui/ui/package.json b/pkg/webui/ui/package.json index 610d3ae1a..22e326feb 100644 --- a/pkg/webui/ui/package.json +++ b/pkg/webui/ui/package.json @@ -5,19 +5,20 @@ "private": true, "proxy": "http://localhost:8080", "dependencies": { - "@emotion/react": "^11.10.6", - "@emotion/styled": "^11.10.6", - "@fontsource-variable/nunito": "^5.0.2", - "@mui/icons-material": "^5.11.11", + "@babel/plugin-proposal-private-property-in-object": "^7.21.11", + "@emotion/react": "^11.11.1", + "@emotion/styled": "^11.11.0", + "@fontsource-variable/nunito": "^5.0.3", + "@mui/icons-material": "^5.11.16", "@mui/lab": "^5.0.0-alpha.124", - "@mui/material": "^5.11.14", + "@mui/material": "^5.13.4", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "@types/jest": "^27.5.2", - "@types/node": "^16.18.18", - "@types/react": "^18.0.28", - "@types/react-dom": "^18.0.11", + "@types/node": "^16.18.34", + "@types/react": "^18.2.9", + "@types/react-dom": "^18.2.4", "jdenticon": "^3.2.0", "js-sha256": "^0.9.0", "js-yaml": "^4.1.0", @@ -27,7 +28,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-router": "^6.10.0", - "react-router-dom": "^6.10.0", + "react-router-dom": "^6.12.1", "react-scripts": "5.0.1", "react-syntax-highlighter": "^15.5.0", "sort-by": "^1.2.0", @@ -61,8 +62,8 @@ }, "devDependencies": { "@types/js-yaml": "^4.0.5", - "@types/lodash": "^4.14.192", - "@types/react-syntax-highlighter": "^15.5.6", + "@types/lodash": "^4.14.195", + "@types/react-syntax-highlighter": "^15.5.7", "rewire": "^6.0.0" } } From 46911515d786f7a27b409e9c50d224bbde59805e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 9 Jun 2023 14:28:22 +0200 Subject: [PATCH 1806/2916] fix: Fix all build warnings --- pkg/webui/ui/src/api.tsx | 6 +-- pkg/webui/ui/src/components/App.tsx | 46 +++++++++---------- pkg/webui/ui/src/components/ObjectYaml.tsx | 9 ++-- .../result-view/CommandResultTree.tsx | 4 +- .../components/targets-view/TargetsView.tsx | 3 +- 5 files changed, 30 insertions(+), 38 deletions(-) diff --git a/pkg/webui/ui/src/api.tsx b/pkg/webui/ui/src/api.tsx index 150c4223f..4145b0f5f 100644 --- a/pkg/webui/ui/src/api.tsx +++ b/pkg/webui/ui/src/api.tsx @@ -222,10 +222,10 @@ class StaticApi implements Api { await loadScript(staticPath + "/summaries.js") staticSummaries.forEach(rs => { - if (filterProject && filterProject != rs.project.normalizedGitUrl) { + if (filterProject && filterProject !== rs.project.normalizedGitUrl) { return } - if (filterSubDir && filterSubDir != rs.project.subDir) { + if (filterSubDir && filterSubDir !== rs.project.subDir) { return } handle({ @@ -244,7 +244,7 @@ class StaticApi implements Api { async getResultSummary(resultId: string): Promise { await loadScript(staticPath + "/summaries.js") return staticSummaries.filter(s => { - return s.id == resultId + return s.id === resultId }).at(0) } async getResultObject(resultId: string, ref: ObjectRef, objectType: string): Promise { diff --git a/pkg/webui/ui/src/components/App.tsx b/pkg/webui/ui/src/components/App.tsx index e41af9bd3..e4f6fa347 100644 --- a/pkg/webui/ui/src/components/App.tsx +++ b/pkg/webui/ui/src/components/App.tsx @@ -1,4 +1,4 @@ -import React, { createContext, Dispatch, SetStateAction, useEffect, useMemo, useState } from 'react'; +import React, { createContext, Dispatch, SetStateAction, useEffect, useMemo, useRef, useState } from 'react'; import '../index.css'; import { Box, ThemeProvider } from "@mui/material"; @@ -30,32 +30,27 @@ export const AppContext = createContext({ }); const App = () => { - let [summaries, setSummaries] = useState>(new Map()) - let [validateResults, setValidateResults] = useState>(new Map()) const [filters, setFilters] = useState() - const updateSummary = (rs: CommandResultSummary) => { - console.log("update_summary", rs.id, rs.commandInfo.startTime) - summaries.set(rs.id, rs) - summaries = new Map(summaries) - setSummaries(summaries) - } + const summaries = useRef>(new Map()) + const validateResults = useRef>(new Map()) - const deleteSummary = (id: string) => { - console.log("delete_summary", id) - summaries.delete(id) - summaries = new Map(summaries) - setSummaries(summaries) - } + useEffect(() => { + const updateSummary = (rs: CommandResultSummary) => { + console.log("update_summary", rs.id, rs.commandInfo.startTime) + summaries.current.set(rs.id, rs) + } - const updateValidateResult = (key: ProjectTargetKey, vr: ValidateResult) => { - console.log("validate_result", key) - validateResults.set(JSON.stringify(key), vr) - validateResults = new Map(validateResults) - setValidateResults(validateResults) - } + const deleteSummary = (id: string) => { + console.log("delete_summary", id) + summaries.current.delete(id) + } + + const updateValidateResult = (key: ProjectTargetKey, vr: ValidateResult) => { + console.log("validate_result", key) + validateResults.current.set(JSON.stringify(key), vr) + } - useEffect(() => { console.log("starting listenResults") const cancel = api.listenUpdates(undefined, undefined, msg => { switch(msg.type) { @@ -77,13 +72,14 @@ const App = () => { }, []) const projects = useMemo(() => { - return buildProjectSummaries(summaries, validateResults) + console.log("buildProjectSummaries") + return buildProjectSummaries(summaries.current, validateResults.current) }, [summaries, validateResults]) const appContext = { - summaries: summaries, + summaries: summaries.current, projects: projects, - validateResults: validateResults, + validateResults: validateResults.current, } const outletContext: AppOutletContext = { diff --git a/pkg/webui/ui/src/components/ObjectYaml.tsx b/pkg/webui/ui/src/components/ObjectYaml.tsx index cf4c9ebc1..80a71a2b8 100644 --- a/pkg/webui/ui/src/components/ObjectYaml.tsx +++ b/pkg/webui/ui/src/components/ObjectYaml.tsx @@ -10,12 +10,11 @@ import { Loading } from "./Loading"; export const ObjectYaml = (props: {treeProps: CommandResultProps, objectRef: ObjectRef, objectType: ObjectType}) => { const [promise, setPromise] = useState>() - const getData = async () => { - const o = await api.getResultObject(props.treeProps.summary.id, props.objectRef, props.objectType) - return yaml.dump(o) - } - useEffect(() => { + const getData = async () => { + const o = await api.getResultObject(props.treeProps.summary.id, props.objectRef, props.objectType) + return yaml.dump(o) + } setPromise(getData()) }, [props.treeProps, props.objectRef, props.objectType]) diff --git a/pkg/webui/ui/src/components/result-view/CommandResultTree.tsx b/pkg/webui/ui/src/components/result-view/CommandResultTree.tsx index eead2b7fa..396cd16fa 100644 --- a/pkg/webui/ui/src/components/result-view/CommandResultTree.tsx +++ b/pkg/webui/ui/src/components/result-view/CommandResultTree.tsx @@ -20,9 +20,8 @@ export interface CommandResultTreeProps { const CommandResultTree = (props: CommandResultTreeProps) => { const theme = useTheme(); const [expanded, setExpanded] = useState(["root"]); - const [selectedNodeId, setSelectedNodeId] = useState() - const [rootNode, nodeMap] = useMemo(() => { + const [rootNode] = useMemo(() => { if (!props.commandResultProps) { return [undefined, undefined] } @@ -45,7 +44,6 @@ const CommandResultTree = (props: CommandResultTreeProps) => { }; const handleItemClick = (e: React.SyntheticEvent, node: NodeData) => { - setSelectedNodeId(node.id); props.onSelectNode(node); e.stopPropagation(); } diff --git a/pkg/webui/ui/src/components/targets-view/TargetsView.tsx b/pkg/webui/ui/src/components/targets-view/TargetsView.tsx index ef04d8c9c..7cb5a8fd9 100644 --- a/pkg/webui/ui/src/components/targets-view/TargetsView.tsx +++ b/pkg/webui/ui/src/components/targets-view/TargetsView.tsx @@ -1,7 +1,7 @@ import { CommandResultSummary } from "../../models"; import { Box, Typography, useTheme } from "@mui/material"; import React, { useCallback, useContext, useState } from "react"; -import { AppContext, useAppOutletContext } from "../App"; +import { AppContext } from "../App"; import { ProjectItem } from "./Projects"; import { TargetItem } from "./Targets"; import Divider from "@mui/material/Divider"; @@ -123,7 +123,6 @@ const RelationTree = React.memo(({ targetCount }: { targetCount: number }): JSX. export const TargetsView = () => { const theme = useTheme(); - const context = useAppOutletContext() const [selectedCommandResult, setSelectedCommandResult] = useState<{rs: CommandResultSummary, ts: TargetSummary, ps: ProjectSummary} | undefined>(); const [selectedTargetSummary, setSelectedTargetSummary] = useState(); const [selectedCardRect, setSelectedCardRect] = useState(); From 4a493e2026ab33abee5f697821f7e9c0815437fb Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 9 Jun 2023 11:37:39 +0200 Subject: [PATCH 1807/2916] chore: Add current/initial version of built webui --- pkg/webui/ui/.gitignore | 6 +- pkg/webui/ui/build.js | 2 + pkg/webui/ui/build/.gitignore | 1 + pkg/webui/ui/build/asset-manifest.json | 55 + pkg/webui/ui/build/favicon.ico | Bin 0 -> 15406 bytes pkg/webui/ui/build/index.html | 1 + pkg/webui/ui/build/logo192.png | Bin 0 -> 13703 bytes pkg/webui/ui/build/logo512.png | Bin 0 -> 26403 bytes pkg/webui/ui/build/manifest.json | 25 + pkg/webui/ui/build/robots.txt | 3 + pkg/webui/ui/build/static/css/main.css | 87 + pkg/webui/ui/build/static/js/787.chunk.js | 239 + pkg/webui/ui/build/static/js/main.js | 60971 ++++++++++++++++ pkg/webui/ui/build/static/media/added.svg | 4 + .../ui/build/static/media/arrow-left.svg | 4 + .../ui/build/static/media/brackets-curly.svg | 3 + .../ui/build/static/media/brackets-square.svg | 3 + pkg/webui/ui/build/static/media/changed.svg | 5 + pkg/webui/ui/build/static/media/changes.svg | 5 + .../build/static/media/checkbox-checked.svg | 5 + .../build/static/media/checkbox-disabled.svg | 4 + pkg/webui/ui/build/static/media/checkbox.svg | 3 + pkg/webui/ui/build/static/media/close.svg | 3 + pkg/webui/ui/build/static/media/cpu.svg | 16 + pkg/webui/ui/build/static/media/deploy.svg | 8 + pkg/webui/ui/build/static/media/diff.svg | 8 + pkg/webui/ui/build/static/media/error.svg | 4 + pkg/webui/ui/build/static/media/file.svg | 5 + .../ui/build/static/media/finger-scan.svg | 8 + pkg/webui/ui/build/static/media/git.svg | 1 + pkg/webui/ui/build/static/media/include.svg | 7 + .../ui/build/static/media/kluctl-logo.svg | 3 + .../ui/build/static/media/kluctl-text.svg | 7 + .../nunito-cyrillic-ext-wght-normal.woff2 | Bin 0 -> 28960 bytes .../media/nunito-cyrillic-wght-normal.woff2 | Bin 0 -> 20824 bytes .../media/nunito-latin-ext-wght-normal.woff2 | Bin 0 -> 32720 bytes .../media/nunito-latin-wght-normal.woff2 | Bin 0 -> 35904 bytes .../media/nunito-vietnamese-wght-normal.woff2 | Bin 0 -> 10632 bytes pkg/webui/ui/build/static/media/orphan.svg | 6 + pkg/webui/ui/build/static/media/project.svg | 5 + pkg/webui/ui/build/static/media/prune.svg | 8 + .../ui/build/static/media/relation-hline.svg | 5 + pkg/webui/ui/build/static/media/result.svg | 8 + pkg/webui/ui/build/static/media/search.svg | 4 + pkg/webui/ui/build/static/media/star.svg | 4 + pkg/webui/ui/build/static/media/target.svg | 5 + pkg/webui/ui/build/static/media/targets.svg | 6 + pkg/webui/ui/build/static/media/trash.svg | 6 + pkg/webui/ui/build/static/media/tree-view.svg | 7 + .../ui/build/static/media/triangle-down.svg | 4 + .../ui/build/static/media/triangle-right.svg | 4 + .../ui/build/static/media/warning-sign.svg | 5 + pkg/webui/ui/build/static/media/warning.svg | 5 + pkg/webui/ui/build/staticbuild.js | 1 + 54 files changed, 61576 insertions(+), 3 deletions(-) create mode 100644 pkg/webui/ui/build/.gitignore create mode 100644 pkg/webui/ui/build/asset-manifest.json create mode 100644 pkg/webui/ui/build/favicon.ico create mode 100644 pkg/webui/ui/build/index.html create mode 100644 pkg/webui/ui/build/logo192.png create mode 100644 pkg/webui/ui/build/logo512.png create mode 100644 pkg/webui/ui/build/manifest.json create mode 100644 pkg/webui/ui/build/robots.txt create mode 100644 pkg/webui/ui/build/static/css/main.css create mode 100644 pkg/webui/ui/build/static/js/787.chunk.js create mode 100644 pkg/webui/ui/build/static/js/main.js create mode 100644 pkg/webui/ui/build/static/media/added.svg create mode 100644 pkg/webui/ui/build/static/media/arrow-left.svg create mode 100644 pkg/webui/ui/build/static/media/brackets-curly.svg create mode 100644 pkg/webui/ui/build/static/media/brackets-square.svg create mode 100644 pkg/webui/ui/build/static/media/changed.svg create mode 100644 pkg/webui/ui/build/static/media/changes.svg create mode 100644 pkg/webui/ui/build/static/media/checkbox-checked.svg create mode 100644 pkg/webui/ui/build/static/media/checkbox-disabled.svg create mode 100644 pkg/webui/ui/build/static/media/checkbox.svg create mode 100644 pkg/webui/ui/build/static/media/close.svg create mode 100644 pkg/webui/ui/build/static/media/cpu.svg create mode 100644 pkg/webui/ui/build/static/media/deploy.svg create mode 100644 pkg/webui/ui/build/static/media/diff.svg create mode 100644 pkg/webui/ui/build/static/media/error.svg create mode 100644 pkg/webui/ui/build/static/media/file.svg create mode 100644 pkg/webui/ui/build/static/media/finger-scan.svg create mode 100644 pkg/webui/ui/build/static/media/git.svg create mode 100644 pkg/webui/ui/build/static/media/include.svg create mode 100644 pkg/webui/ui/build/static/media/kluctl-logo.svg create mode 100644 pkg/webui/ui/build/static/media/kluctl-text.svg create mode 100644 pkg/webui/ui/build/static/media/nunito-cyrillic-ext-wght-normal.woff2 create mode 100644 pkg/webui/ui/build/static/media/nunito-cyrillic-wght-normal.woff2 create mode 100644 pkg/webui/ui/build/static/media/nunito-latin-ext-wght-normal.woff2 create mode 100644 pkg/webui/ui/build/static/media/nunito-latin-wght-normal.woff2 create mode 100644 pkg/webui/ui/build/static/media/nunito-vietnamese-wght-normal.woff2 create mode 100644 pkg/webui/ui/build/static/media/orphan.svg create mode 100644 pkg/webui/ui/build/static/media/project.svg create mode 100644 pkg/webui/ui/build/static/media/prune.svg create mode 100644 pkg/webui/ui/build/static/media/relation-hline.svg create mode 100644 pkg/webui/ui/build/static/media/result.svg create mode 100644 pkg/webui/ui/build/static/media/search.svg create mode 100644 pkg/webui/ui/build/static/media/star.svg create mode 100644 pkg/webui/ui/build/static/media/target.svg create mode 100644 pkg/webui/ui/build/static/media/targets.svg create mode 100644 pkg/webui/ui/build/static/media/trash.svg create mode 100644 pkg/webui/ui/build/static/media/tree-view.svg create mode 100644 pkg/webui/ui/build/static/media/triangle-down.svg create mode 100644 pkg/webui/ui/build/static/media/triangle-right.svg create mode 100644 pkg/webui/ui/build/static/media/warning-sign.svg create mode 100644 pkg/webui/ui/build/static/media/warning.svg create mode 100644 pkg/webui/ui/build/staticbuild.js diff --git a/pkg/webui/ui/.gitignore b/pkg/webui/ui/.gitignore index b2ba12633..f471c1a59 100644 --- a/pkg/webui/ui/.gitignore +++ b/pkg/webui/ui/.gitignore @@ -8,9 +8,6 @@ # testing /coverage -# production -/build - # misc .DS_Store .env.local @@ -22,4 +19,7 @@ npm-debug.log* yarn-debug.log* yarn-error.log* +*.css.map +*.js.map + /public/staticdata diff --git a/pkg/webui/ui/build.js b/pkg/webui/ui/build.js index 0386a1a3c..17db0e576 100644 --- a/pkg/webui/ui/build.js +++ b/pkg/webui/ui/build.js @@ -2,4 +2,6 @@ const rewire = require('rewire'); const defaults = rewire('react-scripts/scripts/build.js'); const config = defaults.__get__('config'); +// we disable minimize as we're actually committing the build folder into git, so that people who don't want to deal +// with the UI while working on Go code don't have to use npm and friends config.optimization.minimize = false diff --git a/pkg/webui/ui/build/.gitignore b/pkg/webui/ui/build/.gitignore new file mode 100644 index 000000000..c9e9f1beb --- /dev/null +++ b/pkg/webui/ui/build/.gitignore @@ -0,0 +1 @@ +staticdata \ No newline at end of file diff --git a/pkg/webui/ui/build/asset-manifest.json b/pkg/webui/ui/build/asset-manifest.json new file mode 100644 index 000000000..351d6bcf7 --- /dev/null +++ b/pkg/webui/ui/build/asset-manifest.json @@ -0,0 +1,55 @@ +{ + "files": { + "787.chunk.js": "static/js/787.chunk.js", + "787.chunk.js.map": "static/js/787.chunk.js.map", + "index.html": "index.html", + "main.css": "static/css/main.css", + "main.css.map": "static/css/main.css.map", + "main.js": "static/js/main.js", + "main.js.map": "static/js/main.js.map", + "static/media/added.svg": "static/media/added.svg", + "static/media/arrow-left.svg": "static/media/arrow-left.svg", + "static/media/brackets-curly.svg": "static/media/brackets-curly.svg", + "static/media/brackets-square.svg": "static/media/brackets-square.svg", + "static/media/changed.svg": "static/media/changed.svg", + "static/media/changes.svg": "static/media/changes.svg", + "static/media/checkbox-checked.svg": "static/media/checkbox-checked.svg", + "static/media/checkbox-disabled.svg": "static/media/checkbox-disabled.svg", + "static/media/checkbox.svg": "static/media/checkbox.svg", + "static/media/close.svg": "static/media/close.svg", + "static/media/cpu.svg": "static/media/cpu.svg", + "static/media/deploy.svg": "static/media/deploy.svg", + "static/media/diff.svg": "static/media/diff.svg", + "static/media/error.svg": "static/media/error.svg", + "static/media/file.svg": "static/media/file.svg", + "static/media/finger-scan.svg": "static/media/finger-scan.svg", + "static/media/git.svg": "static/media/git.svg", + "static/media/include.svg": "static/media/include.svg", + "static/media/kluctl-logo.svg": "static/media/kluctl-logo.svg", + "static/media/kluctl-text.svg": "static/media/kluctl-text.svg", + "static/media/nunito-cyrillic-ext-wght-normal.woff2": "static/media/nunito-cyrillic-ext-wght-normal.woff2", + "static/media/nunito-cyrillic-wght-normal.woff2": "static/media/nunito-cyrillic-wght-normal.woff2", + "static/media/nunito-latin-ext-wght-normal.woff2": "static/media/nunito-latin-ext-wght-normal.woff2", + "static/media/nunito-latin-wght-normal.woff2": "static/media/nunito-latin-wght-normal.woff2", + "static/media/nunito-vietnamese-wght-normal.woff2": "static/media/nunito-vietnamese-wght-normal.woff2", + "static/media/orphan.svg": "static/media/orphan.svg", + "static/media/project.svg": "static/media/project.svg", + "static/media/prune.svg": "static/media/prune.svg", + "static/media/relation-hline.svg": "static/media/relation-hline.svg", + "static/media/result.svg": "static/media/result.svg", + "static/media/search.svg": "static/media/search.svg", + "static/media/star.svg": "static/media/star.svg", + "static/media/target.svg": "static/media/target.svg", + "static/media/targets.svg": "static/media/targets.svg", + "static/media/trash.svg": "static/media/trash.svg", + "static/media/tree-view.svg": "static/media/tree-view.svg", + "static/media/triangle-down.svg": "static/media/triangle-down.svg", + "static/media/triangle-right.svg": "static/media/triangle-right.svg", + "static/media/warning-sign.svg": "static/media/warning-sign.svg", + "static/media/warning.svg": "static/media/warning.svg" + }, + "entrypoints": [ + "static/css/main.css", + "static/js/main.js" + ] +} \ No newline at end of file diff --git a/pkg/webui/ui/build/favicon.ico b/pkg/webui/ui/build/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..496bbc69cabfce158be2fbc7e709e8a81a5bc95e GIT binary patch literal 15406 zcmeHO3v^V)8D6vxTWwWZwYJ*gQCn+UYisN2sUD@R)x%@&y$Qy*0*Z(SJr#k_~K1sI8X6j^_Pc$c$$5q)hSqBj@C_1{Q@LcIGU`Zm8@bQIo zzk~u!KkI6i3>TnrqpY`Mdm0cH{5ED!1ok3XvY zW7L#BgJX;QCBv(4O7+QDKo8fdFm~@AkLM(R#N7+HyD*LmAlGiZ?+HY- zc^H?EeNm&YXF+FJYug{k6#Xs)?1m)Yk%WMrMqw<+B;NJnM4Pr5_Tcl>8Z>Y72i#J& zjhWh4JU^xP3;{i}0X$=T_|7`en?|_eNjHn}vj>i2J%7HiG2+>pyTts}6$ge+?Ece4 zE$|~+BX~8)Z}jQ>XKU^}Fnh&|A~b)<2w7h;v&*-^=MFrV$+|$5erZBpT?C?en3ahi zVF*hcS!Y)UqQ?Eiub4X+_{RNqFq!H+5u?-db-fzv>Yv->jk_<%1bZxP^fR6CbjCyf z)ZU*zYCMSkHp6BPKtGj5@#{`^qQy4AhA=*#(N_`I2{xB{)o;l$gA5TTT-Z@K1&1-h z_zKui4=4C+^_Z8{D!t_U=MR>o+X(#Bx6poAg0adXs{m4@{jy8b(8wMcrh^hUe=(gyZ2+0Js#n=gsj!on8Pu)-eA zcOb0w1id3F?LbJ&x6*(QxLLxCs=G(TUh}pD!uowyxb}SeBJMjuyHyWieYlkl+DA(M z$+Eu{=2*UA+ujuYu(sMt3+v0dJUo7jbWnpFa-(pUU z16(C}vK>78NZHLY%sH$Lby^3wCMDtaVQs(5w2iR7FG0fySb6fBw7enB0~k~G1T5kJ zlQ-5)^e6q%8hsJtPOB~pqwYVZ!zS>95$$&>f3E425&dbj#fj?V-Nt97!)co_kDj9L zO&bX5qtjs${1H!#B1~eOw4s1==$mfFNoYR=9n5l~XH$RBrp~H6TNuWvm9IW7%I6Fc zQ|q4-4O{9N8%_&iJqArPR!q_+KILXiw+YY3c&t2f`%c(J6Y!gc$8Xv?{*?iTYF;lE zQ{EUUiYMQ^YFJhNc^ogsK=i5JsBxjk<2p$lL%t8I)4o?&rCkMGt;{A1#$XlW% z+B(g#ArJDh<74G({l?_jg$Fj?*{e#PQkq!!MM%l5ys7;_LE6v>$`-^lZFS z?O|<>FO>hYW95&1%y+Oaco_ZOD`}&DlNr;XTqx^ME`krtV{DYFGvhVgmu+8WC!;@_ z_iOr8JRhd$nR7rJD`R-tkZj{#EJ>#WY_vqyn`M-~Q5#LaWyedc!y526;Bd~R%`dEP zRqD9X&r3MQ^a`C!cCyhMcK=m@O}#>Q=YZZPlBO)jPP-4XX!VTmQowkDkGodFSZS7) zUhz$|+3ocN)}MWzioCN`T6_omZIYfWm#mG8aWfv+>kD;Fvn4NL8wm%ReXR5_cITsg zPYMP)9Y1S;Sg^VhYp45GD?InVD(u2F5mCRsmg6wM3d?WU-%Apne%~I({g8xVIrpuX z#p1Ow5eS?6@ir&}XzLMuBjIXae@1NDzEX^=zJ0w_?-B`iCikTD#Ya=|?wFw`~omgRFE7H>B#5KGoh65fdBIUclgp}}?B7uAN1p4w;k=&ASaX1s5g zzsZHx|2howB;0WH!gDNn5}>#Fc`Mu(aExH5+hDJUkq=qmuhhDE%6pyC1~9`1VPRJq zXmH>4IqYsbXbmUr3~0yPl#Jh($vLK!pJI(3;^dQ6#tGkR48hv@nSw8U)>3b!_6rsM zc)iEDPYr8zHatzK-yW z?wc_FU*LHHbUI|9)d4x@#~@>yyb1dZg9c`w6gXTbN@!6G*dP%7pl5F7usE{1{)u2fU`r|IFlD#mbGdaaQGT zDT?-(X?N!2M6W&d>;qy{?#I4Rb${21o?6d50_JmTJ~OH}$Q&f$=o3Mk zc~8^!;b%XjA5&>Ky>m~EHT#hy{+5J{TBTg$$OrV)oRyqk^zk(uk6iXJIIDgD@Y1&& z#NPU84{66KyP0E8=6?;u+{Qgl^2&PhByXJM@~kgyy`jvM9p@6v-)CA=kz+T8Fuy8u zWRizkQr5P4T%46g^%dZ=0`w7ICv*?lm%+!{<{wCpJc4oUBH%p*zTlnaucUqraK;gMm+7#AP(Fcj^)yCP-GR<6NbXDD$=13qPEuDHYE1!k@=w$RI* zCG%nZbH#OlLmXt-#X?#7VB%!Ngq>|4b98N&OKY0KLxIKH*Fvp zk6HJloYV9f`_a#RfZ5N7dc^8DyHsHG%WSQ{MGQ!0C^%tU_6XwwE}!z2?{$y@JT=qW(7c;hrWO@>Iw< zjzXVm@%=vh40*RhX7ip?9enK=(*|)@34D8z4a;@cJ;0*|yeMOTtcT}N z_S6r4i+d0DiEBnCU&(ww?^>8^Vj|z(e6;dI&&^naI@53Q95xd_&ULP(dV`FuGH~v~mvWE7_#C-e?r9F-k-GnscaDZt z=by*e4D}(UjW8|1`v`ur+8^$bx|8T6N3QkMb)Y8i2k^C*fqpTb)OoSOpX*sUV;M=8 zevUd#!G!gxs*MyBAeS07&-tutCi`*_{31mW{m=CAr7QY6=JO;!Yf0bEJHF6a5HimO zuiYtGT6IcZhhRr9QeKtDAjGI>E0;LPSyk{3unJRDT5vkrN8@4tNz3vuKdmi>9)@GQ z-MQKp?__{hIdCX*TN|2c77G`7$%tjeBLm>9?IpJnN4(XO3G^F6=xRj{ zF$nGIpQ@dv@r5oS{{km{RJ{6_n7M2$`f$re*w>$|`10G_GvY2?D;;e$>twsN3?hdD zTlse@d>j3X=WBb3@B)v(S^7NQ*|*}$Z{7h${M2Yir)vI_$Zt1@QB&{X9oj*e_Qc*$ zkBgJ`m$VCQN3c@6Nb<3k)G6Y%f#`$Xd^UCV!R{u3xU(_S{T1deA-g3XrHj&;4~VV1 z-W4;KzW{lRWr(F5bF`H0=_+>@s}6X_-72^G?TO#e5p&g=-!kv|u56qmcJAFGo~`Yf zF~7=r2OEp2yyLhdK-stKSSOyV>4|ko>!$Lxd&k|Jwz(e8gT&d49eApaYZ%~;aE@`G z6^!NMuB=b&JFrX4Uo{alv{$&}t=>H0czpS5GpX58;66T>Inw2QjS6lY7K@Ev)k3}O7Q z1^g)N9eWg`q$G!Y8Sf}rW#>2i%aKfTSn?(h@&XR>Xa;@wPV{LVWs)$tl*Ki9XM3aF zuje|(`M-@rS4#uGAGvn zzGDpD{;FijRb~|q|Ci7O`bQj=u6yQeQ2&vzn5;E5tX&w~m> k-LAl1ECQML7Eiu)AL7+G?;m^__cBhDe*a&N|4t432jt`J!vFvP literal 0 HcmV?d00001 diff --git a/pkg/webui/ui/build/index.html b/pkg/webui/ui/build/index.html new file mode 100644 index 000000000..56667fb71 --- /dev/null +++ b/pkg/webui/ui/build/index.html @@ -0,0 +1 @@ +React App
    \ No newline at end of file diff --git a/pkg/webui/ui/build/logo192.png b/pkg/webui/ui/build/logo192.png new file mode 100644 index 0000000000000000000000000000000000000000..11f22322c00fe673d7aa1530ecfd53fbc82b2373 GIT binary patch literal 13703 zcmXY&V|3tLw1!h#Q@c}RYE3(}+o|oTt*LE$YTLGL+qP}@_Ph7~NY2X2TFFkQfeI4yI5rx8rLi^(Sp1sbHxjepRrplYlV8kRo&iEf>^m3#fc#ghbythAIJiJ0* z>$!qgq+BcBVwf31A&eY@uAqnJq`?n}A(sV(yTRNw=zPe@nKFXYR?bm7(X~u8FWMib zG=Z4yoU_+I_inoKbhj9P%0gHeZ+!#W{aBvhn0Zu!F-&Mn4idJIq3x$MWdH_-Vr* z!Ei6Y#99^_k!g1t{Pzr;-3$RGyO^)gYQSyDgR9F7CMq1#>oDS9#iNoG+T=x_3E%$kma_H-6hmO+$xl*)UM* z2hrhMjH05Jr{OmL5C~;rwYj1Z`dQu^B6jJ8($aXmS8F7P#!^e}jgy37vD^s9-0=*I zvVVqyWsLA4je@w^_ij8)dT@BUw^K4^iA#wH;{lNN_3PzO-_?Z3O?vrEjZr1(<%FtAqKfWUifaF~P@;i|3&Key|ro#mw}>!428ftwh~ z!R73g@Wsa{K0H7-GI$PD1qanAs2Gxa)m~tNOn;iE*brZd_j1jDkS8r-J^o`AByX#G zr#iAT(7csubPxntAR5st<2de7{8rHPNp#wcf9k} z`r>u#r@IakGd6wXM!=j74Ar5;MAqVDf7eERF!tn)$7`O# z`8E{^?2~7_fX-=5JT({lTvTBS6Uuu+n?QZfBC$x#7-&*242A5U;M_Lgax^tc-(%QHcgDK;(K**8=E`ZVj8crXt0mXmcN5r`9bjsC^g+$#J!=QoJL<>P>6}t#(i-bW zC!GB0j|k4sUvQ(1FHsKN>Yp`ZqMgiB?y+*{Um;dV(>cG{2h7ED_D4}A7*uaR_^52h zWEk^ZkT1rCe7b;bUOuhM@F05Y2iA7O{soT0)0cs0t9z4mlql_($cORBGX%|FuQ1MP zeIQx2o1pz)abORL0emeB?Mk9~nSW8F&E`SuR6Kn9I~U$(LOMb09rfOR)_rsd!vd@X z)I`uW?TCXQS)GKt)R$J2mPy@WOf&V+Nm9rVOPtu-4qx8 zzmwW;ugCO|>P+if0gl#=I(lZ)R$y90i~p`b2Xr5ZP8VNQt5zwHX5?LxrG&hsmG)2J zamZu}X9s;3U@(DSg1Z^;`?-}?!#{GWAXr37N!5Rhy2&~%5i5)D1%@>>o(p>`jV|C0 zo@2IwdOIo~WhUi8bDlkZkjmLX%K{6;jHZo$8?_t<6KwrMS9oUD>E;@Kz5a9o=|;=U z&q55^D~!xw*o8B>D_uDIsi+Aa9;DxMY)?(&y0W4klQp-XuwB06!K?GAS6I6caA@-b zf*zL+m`31Y#w7R;Ywl~^il+rrF4q!;LhPr3- z_YD4{Kc>uWo*`acNoK3Fk~JHMQ=ooG$N|+9!AV`=tZOY#m=p-#&FF-Y+3!tv2fyG=y9LZnD(>9IJ_#aN+eYtGqFi4nrXe3`c%7BF4P1f+H;UW-;`A0`x1 zUONOTjo|H>2bT{ewv8vD?*#zSe7Z(gmH|Ku55phvZNN%7t~0JcCXOpjPNiZZb7eFq zm{7|8EGPzkkkA{F4l>E)C)PYwfI_WUuoqqgj`qpFysq)G`Q>axSFSC6c4JgRSG z;)RJj>JpqLF8>jZ#H(KgOKtw~)hwzV zEnbr@{b1z)!L_=M`o+GTdtX}hlo;RP4pR@uOdY&cSDaydn8#7-8?iclf(_}7_BdAy zv*Q7L6wCNt-T2(b{JkD_;KknW^7<@9ANqU{RCInH&)tM3)I!b^OE2BKtR-Vw#r@~A z196g8OEw4}Tk`pd?_iCVjep+qBS;0&VK3v!zGe&9Zapiuv9OIOrG{>N(50+g?Z(g(*5 z_-u5|*cqpPva1J-iq+E(W13Puj{xZJ<9?A=iCEyq;;s#EZQ&^~F_$fIXm9LO*$Xh^ zQ^hX0WvkDxjD?Lc5Y{@0+E^>E7VsAfh0?3amahn2?{!$eKsUgJ+@1cqMoJ1{DZ}6b zB%5RB1uJn*0FPEB1%_}x#8=>r2raO*mJT!K&vO!?S8vuBxz6{8AEl7tWNcx^!E&4a zuB$exk;(F~T2ZKb->Mh`pnpN^zRbW63h1*0RtkH*W-iqX1`(wZcCck>$-{*cvcMl^ zIG1E{MnFiY&_fe1cssy;=vz${iH?~9@zM4*ku{AAyImIW(dgONWo zoUZw}91TqVm4fbyzGlNVhTMAq)}5Oe-al;lc1to;*lAadTk{#kWl|;MJ(y^C&?H!| z(CVoQL?=&Kx;+fTlQ{+K3Ry0L&xg{uWd?+QU@mSCCEja)zHrBt!6fN_>oino+datR zc#k#3-d7BEJ*!Q7&r*+Qr5;vQiut$Bs}6WNe9SeJ_J0 zmp|iuzPxaMb~1O$&KJysg^8>wP{eLABH))GV*oL585dcyhTpwM{aCRixtr-3n#>8g zY`0k*N=D&#V3flSST#8A1Q5LFwi7ddi$nqJi zSr|v{WU^^5!A-Y6CuI7Cx!(j;j_--=E@r2eF3CPSbYxuB3ThWhzl)KG07UsFCscW+ zzX7IwVCk9SzQ6sDoMd_@Q=!q|-q_i$9}2nNm)}2Lx!>+1jir!FY5l1G!f_ZT<-4`W zcZQB1qm1p-xDZ{9JB9dfcv&At&$EplDw6Fxki7Cbrsgk+ms{oao>2Z?kFfhWUkk}= z=Ja=xHDHGwvwtXS^ciO3uxUp`F~TZbfzbM);!o)GTsWXtU@XC0`zd415FYpY>zgI& zpDc2OcE(ZuW43t(T5J16%%PQ>c?(`;a(eFY0mYZPq$*9l`EyI5A@{`NsLKoIYtMF+;<6cygab=K${n{PBIQMwTWwE@u=~WeeL(QvY4qGTujk$QpQUy(Ll&VmWlMLcX@nS%Rt;z0 zuh#pqIRf21{de@&DGE*<0QSIKurc~Lg99w4MCie&J>ND(;qAo;%?FDi!enHFwPZnt z6P8cfz&}pb{q5GH=#M1*r`_0am5Va)7#IMtI0_y9`9cXdxh4ZF_X=LK9ufQCLD>rn zLqaQBPk+RC65B}n;kT_b8%Tn#V=`gjZ`W_7jS!q8_S3nCvnj*&V4t8Eu7(ZLk+Ja)9c*gwYbb(LI^&+bi9-7)4gGaoF}k5wFcdE-zV%q`Zg8J!899@yx4Sb!-(oBK^zVc} zX=xi(t(CnCyMM+M4x0$xADO2vfuerIg?i$*>U$|c9C&?VA9!AV78q}#GE`8F+dt7m z1#bOO1%mhbD66EHVq54#)`s6bjD<3^c59++dG;YpU7a7&$B6&cmv8(O-k~5Z1W=k!TTSx7MK#; z2oPHVLXhiVOE6X0E8f&HsZ%Fm5m?BZ9+kfTk=hOKsZe~iq2eCPoI})PHlQ$`sz&oU zoP~Sm@NK+ero`%xeL8c_?-oIAC-MgADoIGWIjv4EJkgwc0UeW{o1gM3OTg7SlvXQA zW|#h*Lk*+6?Rk^TDxFE{93J6k1E-E3e0-mcDy_!{hN*hIV&mv=mjQRVfsK`E%kH!o zp>J*LZ3TpC1$)E7u9J{K(ye6zI=lZUE*cwPpf5UZbC0P!L}NBc%o8eRal$UpS?gOj z16>m-&MUuOAr9{fzZI=d8-*8V^phaWU<$EZbC}(AQfvibdO=&6*6W9qo3m7~twTxB z8WjSzB$bU6sQ}&9a8`nBy*DP^9Iu(Y^QWS&O2 znD}3r(~_=NYOr1sJ=shY7ml}&82F$I+~E3g97w=LWGyFju}f@zu_9QUcDlq_vWY#! z_nvK@+c4N$K2IOj6ceo@+rT2y9Vboos500f^A*Z8S-FN@xFk=Gz!ahVuS&B z;t42WANW9EI>U2!&Nkyq;j=0&Su<5cjyl*IN>&Ig!fwv}A<2?K|0ABq?xawWy^t0r8KNE^^_fIcG-jb~c=hj{y&0 za;0J}%m590D8p7cfImyOWyqi!w#RaGY3JD-7#N~)#c!uGet7Sh3Ne`4oTvNno;4` z=WO7FIT;p4&q~kXx1Qd}WWNBv0w`glOvr8K1`G@y&kiyrDWyQMJ-T;J(CK+Mf{eg- zE6w&aKRC0z7Jy_8m`HTT>B7o!`iWq7<7G1^iK4D{b>SPyM;?Iz%30|t+gX{y8QCIo2ea^ zASx%^I}q=VTSopQ*@_IteU>n#e`V!6&@X(;f0~znTrD~>5ChQR z!S3 wqli$aKQ2SbKZFKg{E9g@WEz#e7APR8O^xSmtxq&tn!vCgCj&k^n$_Wq;zA zAM`Dv;VH~GnZEgN+f{tP6GI?QJ%CAnC)TK!g(l-IRitx%djDsAT3SP8(raAI-V-XJPO#E7uu)q zC5$n<^Wi}vP<#<2(vH+LMoTN9q}D1A)V(1`*&-=i){4-*;cZeF2-e!U<;2#`V;balnGP<`|Sh9?U$#nvTrMq z2ZvdG(bpSzWSX=I^9<1%KE*Fq^dfkU9l%0~;KTQi(2=GY9xApw<-xM+u)7awFaN$5>r(HwEOb^^?O96vocq-^A~aW7ganJsxg>my^}3eei%8 zyRcD;3X-a|zRxQ5k>SmA{5kCO)4khDq6fAfq+9E22pk~A(|!L<{M-Wi!yUC)@1i*f2chw_?%TD5bN zOzu;LoAxIo4*TOPVjnwdRe;6}{8+QQ$A!4o$~Fj;p&j~dsOJ(z)R(`Qm7=T_@|~jk z5`=4&X3i;>%1#Qp9Qsp4m2zOLh$ho9^HGo@wMrANpx9y~Il-2JS7p8+{?^2PEe5;A z*Gp0%DHEub!<9;f9=fP&6#S^}C{$Rh7zQ5_S~(vJe*B(3nne_Z=$at%l2SZv8G6jl zr^Ddx^bPGAek=6{ZhRVX7d&mi;x8Xs7+&(C6ys0NEMd4Tyq*G+BLPXoXj3QRy*}Ge z*=lh=`GAs=5utG-2&LjOb!j7E7hr4Jn~FBgzR|P=iceE;# zWQp4yeu~XCS9UAPWKQDu_zkTa8P;zF#U@4EukzvPioqG3@f`eGnW0`+fEdKqZnW$p zu8zsN#22ntJHfK---fqVE$G70I3CaGvof2*<-SfEcn*nXXvBq@B!?_wPbr4fn}?Or zM2py;i727GNDW{Pe9~!tQu&!FNhIk}4K1W1G!5a8fLXvIHK)953L3E>LudT@@oaZFbR$=kpCmi^sIyRT5P)U2grZ$FJrs(Gq_r<-BImTq<%Y9Do)7J5mJ976Nn{_OJnA z1QRk=jM_Pc>KvnyR+Dv{<1q^kR9AKv{)VuT0e&M(^K@%?rCBu7!eU&hg&VI*M`YsOvIU%mlhOy zPqT>jJ-Tmcu1Jh@N>?pUKxmKU0_!KP(L{!$Gx6d(8dK1l?q2a1N4 zi@i0ryR@muM9prolt>*JqW8-NaIqGPbw?NA`foj$fRP^x43{-qvyH(#N4IQ*ylI(| zdKW07IMWZk;ZzQYU#pONxuF!nrC-*r6DFR@{0Zy;!4HgPC0<`ofx00V;8F;CFqz9K zu4&Rsd=`F^og9|F@^uQzn|Lu^ptrSpHe&EW5m_WI{}%>+tI=!;3KU=aXjXHj_tC#gi7&1t=g#kJ2RBkgZ*@lO4|Q!(^KX;i zw_;3i+eDsvy9P1FV;L_w40Fn6I!<*p{u zDRy4^94Jzbsayb}ai#7xu@7BX!35(dCTM~)*G__?m?rP-3k=j)4Jw?g14vd%wHk-u zt-QPr3A&ELag(@iVHG=#^Ub$#!@~>a|22b9#Rq{!40 z90vwj$rMoEfG&aMNa$&I`*uzRBl`9S!QuFzn#{Eoa{hJMmyMXp2&N<6Qf#L0ElM3q z?MlJ36MFJj9m~w*G3BrN{*xRKX-{6%CcV-5HU!7q?JJpdl|6IhiQwI#;4CP3aVIyZjp`&Z18iJg6N#@t@0jvSjwi;#x$ldZ8n8 zO0H><4Eir>;Y1*%;s-Rn?~$ZQir z?j&`+5jT3B$5lfB5Hg(E+Rn?@{aN}!nM9yM0SBDVEc?_EV+ib7H8ASAAUHm&@7+Qk z1sSk3?n%!_$TKNnt{Oz+o?gjy`5ej8NKq`>iHI11F3QVFrUk)}pJw`wqTKIGN4t`x zP^-M8)<3TC@wPW58x1O^+GXq2D5Z_o zeWIr-+llKMr=^WSe4mTOGOyV6YL{<7H0Q5J9E>}oG`6cIJbk&vB8HI^;g&8~z} zOtyJn@Gzx`=jbAJk58m}i;Ye=IL{~0%a10!Z&)CPi6X=v{4aUEQ20n-I!`|iLtsko zMIkM+aUvF@MER`2Asvn*{u(3jzZ#WK^`so(zWE4y&M7*V$dX0En?U#VQKrge^qU2K*a1K=TB+>@jgv&B00@0uC&s92(-|`t`InVCjikxiAFu* zMIwj<(Q@t1!Q;rPvOTSIK8bx^QgE4)p-160RgxYrP<)(=#op{&abA%n5fCKf$LnqguuA+WF&kHo%0R5ymcI7K{=Md0wyZ|!l==nO*D*h5p1|YO~;X&O-oQJ;IJo7sTFytPlWlRe}#XfKj_;{kzsS^7#NaJ#$r%DPk9M== zte+1BLBz~ut1eVMuIY#ePzXXAH8N=b4_LyBZ7hV2*Re3lS;nY*d30Ii#zQGVYF#B$ zBl&<8S~p#O?`x|4Im<}%jl|Yq8g-pk`!7A}A2_~^t;)P7z}NBI_+tHzrVD1GO*q2;rM zph_6QHE=rQhQ3l@+_^`ROgd943qz{lZ1Sj{RXc1eT|N(12N>9(i+V?x2(MQs8GaGS ze?}vvB`BxHau$bTk^3ir$xezbAuK||WeDZM7N(J_su9#zR-GWEkHfcRD^J_qS`Q__ zLq;g)U;tg0INzJAQMF%fX`K7PIy_KC4kRfFBh>#Ke+ zyE)`Wyr>5%+}I69`>KGLp(A|EJ@Iw1W$RcRs}4dD}M-VL>B?p z61HQJN!tucMRW0_H?afZ*z3rh?6QFm$+B5KBZ>6)zIb>>gzygFgaI}MGtQOub3&(8 z636-JBYB;BABNB6XHVoDTVo0|J+x2GjO1W~$Xzb~>@Y<0BbCr(;6AQCAu5joRw+2# zW<6Jt1e_iZSU3n#iQqe?Z@aB`tpt!77#<1Cf0hE5rA_+BmL_y}Jp7AJosDTlZ|M^DPB zW}0PZ;HO4Ejb+K>8xDb7C+-lH@a#*>L3}LbZQ~1e4D~g|_owcfGQ8(!^m^f%Jxi#-XB-{^lL+Z1#$1@NG$Pa}`qL9ZF=SAS*oWLB zl_9N_L+Dd8d5&LG9A82B9PbhqR+)*e@P-r{42BgxV+J-7K_|D_je=MVGzc&;z{PR0 z4sQ*Rm^Jg0BcmVG+!g`ln2cjw?dy4cy6<)`s)e3LeWb=aO{K0)5?N zmS(%TJ>H&rSwb9sQd<3asByS$DIe||>6B7Geu}_uBvp;!$%uX6+j4l%TKP6Car5>& zzKRR!pQ|4k|wI4Aea7dw*}sny}2&>^XS9D!Mp_jQex z8;li1sLOaEfwQGo4A~1+Jo+IOz7F0}{FAv=HOlh!j7o)%(h9)0ANr4592EZAYa=5c z8Oj-6SE{`KL-Z2MNF$u*^Q;lk_YTKIPFq0_ElSpYZ)pU*FO4h$c2vL{`o_)JzNG2A zF@|(4UbJcS6w@1a!@1Jd2JO$^QpcDen{O;sgsEU=F>Id9HZN}*e8FviiA6~@ZZoty z%BWBhTd8X88`t z>2UgORu$8DMlGOc1d0T;ciodhquxal7;&MUG;pf9O zd`mcy>*y}Psu;?241vZ8_}D^vNl7wWDo;X9*M#BP14v4ev?-jytaK2nn+HVtPT~c* zmPL-IrPfY|mQbojwbKfoeiFSBGd225_obS|R@Kw4AthI;(Y7GW6evv)qv1Hwic_Rf z{iE&i4r6cpIr+<@*<-YVpnT^?uL~88q!}02&hF`Dl%)RiH=|oh=(uUpg&7v#T{#!g zur~{X^gOVI^g$LdNeYI^l9!`!w{;|Z%t2A^=*(RK*XlL>akkT(XcS9$2`J`>jQB-0 zKgUo=`TZmXrPGFIeL{5vVy!~RI|nP-jRC9t6EJHQu=~AS*cv6<N=f{{5!2ag-gR9wzPO=x(ao@jiTUvf_62EYbOO ze)3jqsT5$0>}*iQiKX_Yh%?yZ zvNp)f$7s4R>Y3eMTh+-DE4~U}Y^63daLW-^!2{}DiOG1`!_Zu*Bv-m@S#xHO|2}w-;=v``}5K_lHe`w*Oe1+l?wxwy~)0$FSwpLxk~4f zUzZAynS>cu>h!AE(^x7Ly|=4)$4|I`x2BW!#-j4jR#oyQ+E&6dIk&FDTC)@KkuX?b zk3j!!v&+Z*gzJ|;n#F6R*arf1X>W}{=;~p$Mh0=FH7WfvA59jd06*AgG~mm7Ggtp@ zc7Sj!Dd!i6?L|LqKZG|W)xu&53{iY07LvouWi|J<12mhfLpKM+pLy13`pk)fo zjU)Bg`%G_dIf+-@Y=oiM_@V|Rp=s&mDGqdiU&pdb-Lm=lSE|Q`mE-@Ju1zvJwuus= z5ndqA($gq*Y%&9UiHHYGI_#7v15qVgqlbq3Z)Qe^WRpoV3f0s%2Kr8ZXJrk=;$VUt zJUY?qMY6MBgyWLS;$3(3U6N(INLxoBBxdStAmuJqW`jKFc6cWD6%JtWk6nhi05<=DX}|U0T~9eHM`}(IxS^3J5LC$HEctYWcjdLC=d3c z!XP_zq&e_ZLqM)Z&NM4nfFRw|5@Rn)ID1DZT)lo?!oZ<0emK@NUKGY1fs(7g%w+{) zvwJ$xrv=k6Oyo(F#(XyjOO*77c6hA$Jn3GtDEh>4+=yr#e4drsFz}hIBxrC97jv0C zEyVIh!9x?YYtSIup-B{EkG-bY(g0#tVo-#fUS%umYj)gVbqKoaCdR5ec)vYuRyA6E zpg{7-p<3jU)Q_M$>I=x~oNPgNBlVWNSUHfXk$;QLV`gmiAPDGF6WvW`EYrQG#8HcO z>X3LVMq#TsbHEVEbmYR4@%UMa|p&VRWxv6Jv#guMf0wSm@-qKb>3=2OLLkb zbU$&#TMJ6;bZ_n8@FC^VC{VgSQuRXZV*%u0H7Ss{xltz`z?n0MQdbqVf@0@HSz3X~~CQHd+=<2E>cOim{~vQpeU)fQM{sf8z5x@teQZXx{D=fB%)iWBHeA z%XLr-dD4agmsnIwpF2M!iSk#))OgAR$RxE4JIoQy81bJJ)!YT~!Z9rrDE@Yk!6wc^ zLz$YU1cOC^bUa2F6xJuAGBI3M>L8@2b!+88b`GX8?m|+M8QfXlNdoVhf1c3Bs7dgd znk3pirBjs^@K^H&6qEkiWJ29D)_+A?dwsNnboyT=TdPV`(IN;o4B^K$7uN z4_8|Cyfp`5u0$VGp&v?7oP_lCiD2bdk_7GgvToQ{+#GKP2XuUC4s0*l{6ps)Lh;1S zk_a-t%;eBC*K&ESu$>`NG@`t9ZlEBZXmtjYe5mrVBSi(R_(jtul7|L_&KXn?|EXBw-KfsGVbA3I>Y6B ze)@Pd(X!uhD_Ac}B<@4zV?7p^1m^uE-$8Hb${}xyN-*ICR!jKLW}r2ivn9Jxqa{9a z&9V~~QT=9r^FBjIG}Zv>qGii=F-Z`YB{)G$JL|Ff39s93RxK`k`S14B2FVc1l{ zAZu=v|MJB##q8vFEIC?DL%JkJ3AW~)Ggze8S9MJ|5f8GURs!YYpFj=IK-Ls2?n_@4xVeafvB`EGtJ+6Y^D`h@3#!H}ufpNP!jW>o{SEv{7B8XunL&=)!wi}6O%t{TRw zBy<=GKow?_6~r@nmpDq4qIdUCz+2e7vv2gzi`H)*u`aEC>`3?EN9I7f3rxlFBSq$mlE&h0u}oNX>CVvX<|CNC zRP!PmWdhU8AR1s0uCU(R56`p*n|0CM^CtAA-G8?yZSjCcmZY_jG7!j+djY-wGP|Mm zzQXsEly~kNljV2BX8Qy{bSkFA+ZC$uBxSQ70MVLT5(?0@iFNZ2hybbHx{XpyjVx% z3?L=qgeD{UbJK2w%;w_$gI3=ZS4&0mT<4dI48JNlJNpr~6xA~jK2J#WJ?-B4*ln>y zKlqkP7LT1$q7n~yrWwz<+b4*8j05e#E;yh*xK0qqrr#GnXtTj4&tpoTo*d*W;Iph~ zGQupHh!%JPgR2Dj0Atl_sp%Io(MY$P_Bng)->rm{qS;Q(yPaz$?T&e(%B>^LDb zrBHET=!p!oC6ESh+KxXJz(J=q|3rw${y)1&Jczr&;K&@v;3`aId#>hZDW&2S%Vs&qyqp!r2tp$v(? zn_DEh4-&j1CL~i~q+KERzO^&khghwKqdNt`cPuIkhcgTMmFP*$%F3gqT8**CQe_N` z50a1Pnrp+4B&les3r`0(xxXAHA)RnS+^vCYjgLLBE&O-DqfUvy9h$cl6(#nK{aoE?2*tU&}2< z=l+HlFyHC(7gCHOo>P|{E3=4g4dd9|#B~)veC;m;u&lEg7_HYDjt&nNMgyFnj~Qfs zsU%fNXmoVqqiO)`&qH)cmfR-kdyN{V_-ri%VsQ}sv2sqMfk~ksi;tMgilbhKdE7M= zp{dw4=7acv88O42gR$PP)O~RJqQsmjlfa+x{E5iXHbs$=;94W$_p+vu{WNUO4{nX| z5l#f~9HG@Dd|!U(@lkFwOt|S}c+G(~fqly^; literal 0 HcmV?d00001 diff --git a/pkg/webui/ui/build/logo512.png b/pkg/webui/ui/build/logo512.png new file mode 100644 index 0000000000000000000000000000000000000000..2bc3b0b03aa840a3beb5879dafeaa47c65dd1ce7 GIT binary patch literal 26403 zcmb@tbySpV8#nsQFvt+XP?EwBItWTBEi#0pgft={NOw0gC@~-UG=g*qBB3JP zUDDkR=V9;te&0F&pS4)RTHencSI6%PRa23>PDD=x0Kjzxc^P#8fP#NQ0r)lW*Pdtp z3HS@*qAn)|6m>E#0{{|Gkdf5%G+cX6=*ehy-iFz~at5Z`v?_OuL~udNT{`B}ZzZ>fR!~15M%|Isaeo4hYkDnOKLs54Xux3l<%4D)+XpVYbHMm`#Vy<`U6?O1LIc zEv@UOp2=C4Gj?`p%)7nv?@xR^EC3h21wf|bs0IVDFj!f|ILYi80mY|Y9iq*V8b0;j z@#q_HY@;j;3Gh-N(}94R3OyQU!hb{Q%U7 z6@VbJ?K2W|5tf#hozX?Jf+Z(Se`Pk^W-qY1e^6mKG6)=dv_}!&jK!KKeIf6$=i|ug zqOhH4IDr#=V4qs zsgcZmUk6MuSwBNNm--`4Tg(Vm!JEc|BBIF}h#`P{hsjK>PrXf&4R5;S=<=L)_jdnf z^1{`+cjCu_fcfih3&^`h!cfQnVIZbo?LF-d0fZU$p#3hz+vvVMuj^BpE3WZTw$1Mb z%jcVgSy>;V(6oxe(10+Ishq4$Py|96UHXg}X@WZ4KPbnG%ttw3*_M7#OOv)R95r4_*O#yW<3hoC?*i_o1q=`OL>1rqyGIhZURtw z>9HbfF#U@afU~CpFmw|EFdrg~-gCo9=#eStJZ^2!*EMjAuQ6OM1Njq*+NewdhYZ*3{tU(olg*lgd5Kb%T$ z$6%s((yFMYiJ8MC(^)||3&RxyusX_|TDDqz=I8azwIN$~Etn90gbq@WLR7MpD2WfMu{E`vw~{t_>zlWw8vrPUU8Hoam))UmovdczD{x zjkR#)g@J@C##19Fzk$UHLEY?1RTk>@utKYrg~3__-4hlD-0YzZ=Zhlz%&U7VtN8jt zvOWUAH{n?HB}i}Hn@9u#Lfv&CB>VK|1LBYO=Y%TzN}0WUED`RIz%oDuZA=LYIxP_b z{J`Lw)?)ZPp5x7$b{~dsa#~O(Vb_ImJB~J;{R^9QKbT_3fsgh_$&qF=Wi?a;?71c52R@!AOk_Dj_2WoIeg!HI01#Wkw;inR$!T&!Hs=gI`k-y*!!(Y4#s9u|QE;93SsEYSD_iv^(N+N-tU!}5@#IG64AQHS0e(jh*- zPdnQ%k8gf?v_5)62wEe14G%FK2*7|~C=7;>pWVNl9Io4do~_njb{{f1Uk<*60}yFq zn*Sp?t60RxGc!I_ZZgL>i?L~{sKH3hXGk#PVw1l~eup#ZwH;)>nH z^~+m%r%j2BQQw0OuJ2v^o{t>*VuZU14E;0)pu>9j2vUhoP8c5|2jj#@-K_r3ijWrs zEr#-`M1vD&mPW2WLFPw4LAS*5`s;{9Am&^|df(*AW6Tpe5P~=B_LvJ9Ao*C^hOvmn zoL#*3=(*4QZQkpdBVprxQQB9H7FWVaf<$6Ln`ed_yG|#or1K?xAj@RFg<2a89Ld57 zkXR3h@ijF1iVWmb?@zISVhP?@3&Wx{v^3&fTz~B|S z0F|SK4O3T4`r6UHMc~0^pMa-yPCT~zNYQA9b#yd6m0;||QDDH+w>SAXNkYLF2&c0E zO*7#f+o174Try=gMT5c^h(`}a{(qNZ&F42*XDvdgxIex)(x{kpR+D-oH*&>O7*;LS zG2taN+Ys|BJ=spKNRj__x{Ye$kv;M4iaI7&#v_6NcF>*5OC>%vwS+m+A$9wQFUS&j z>QHaM3w%9^u*SQcuD4*qI{Iju_m^u|?wCElb&TG_ylaqckTA{ngW6z}!ly+xg8JGM z`wFkfVLIM#{l0(bKkJ%?Hr|J`|8E;T>OKe? zP(+`~)R;9FyqX-3`#Jmc0UQRJ6C|?OF~t@-jE@+@q@KS4WdNym8zj645CDLbfCv7z ztbWfIOuPrz0p$v`!4L#I80|N@vHGRHgF|=n1JC16)hw`_~p4^dP4SW0+iQosN zG&+Fo`B%6xO`}nR)4^sGbAjalG5uH8(4#8+C<4D8Um|-i5|M!9MP6AeI2%+OKUTyWa?t)hQ|NXyqoqs6r#Ge$(?aCz2tVz7Jlj~sttq+_|xan3b;0{SWd{5p` zo?iLH-98{5B#t3SFVBzH7!ZyM0JKqrH}zc1A}Ag?9Rl8)*}E7Y5&|nb7`NoyS-i=u zy2lU4%2YD9gvf(DkQs)@U-2LZG&(#?Zbp6K4Vs3b)Zm`}pkf}~g8LvG!FB2U9I59z z=7H2mWEi+7N6Z!;lo}qM)>mW0*K&cUc7A0WU9o6Skn<0}4i=2-$@tzTUtjIP=gS}y z?h&uBf4(q4u-4BCI=PEet+iRP2@m`BF>5!f5${`SL-tHR-?)mSMB~V>A51SA$+*@R zGDz$j6H{jFcpxrP5jkCpk@vDZ1BC5nUe*r@)HUmip(>snT9b)Y6Iv!z$Kit8-o`v?=}6+^aiu8 z%T2fEf;mjs%z0tfg=>qnT61?jH0m5iYf6-O`)33=q5F1)&q7P3sJ7{PGG5uI-gs=Q zWg-Ra_2kxwEVzEi;L5UMUEGX1*6AW0v8`ZNR*`SM4|#rVn94xe`^}48`iAXLiyYp# zFzn#q$z7+4hPw(`y=ys$MnFEfMDz#dfHCduP|hQe18J0maK2HTWmeU$1(f{5H- zL0&K)(4l;KUHCi-sJI+R3nq4`&N``|VRrxZoKOi&0Rk7~-PvFMg+kCW<=PuI^KDVT zhtn^dn;;rl_IB@V-V4|WsbmfH7ot5EzKqqBq&R+Yq`d+8toK>HHuhHMqlV(#hlir!uyPO+OfswwypRCj_(KN=^rTmQY0+<|fFq)sDpBeM92=-#=%$(|sJiZh15 z6iz}lP4KY^LTN}@0s>x*kF`%u5Fw<`(P%hedr3^2DR0nh9xQ_Cwzn3!qyCN@U-c7! z>zF>Er*BK$-Bkv$@s$o}45o-8JD?ZnIH@N*+u43}58wv3nJB8!{Z!YrFXMc$P zR@i?s(bi~jrnm*H>{hcWg)QoGR8{Is z{fcaeo@mPq0%n1hk*7D6e;t>jO`FypIjK9(0U)-+0?Z?0ITojNlbSjD+qxVik<^$gpF z6~Immq@Me{Pj*}@^C=on>s_{Z9i|USAM9C*4QlP_voLy1s=q?adk zQD*lvp}uEJqh1@$-b(KF7jSbaAscM^K4`HE<^NK@p7+Amvg<2$ynQucn``|^`{BGY z??_VUWK!PL93XmF+$SCITO_-5(MTz65AJ8;eb-2vS`4C=nC3-jk-I9L^ngY()%Vz8 zZDSRTa5jF=b7zp$QL%!_*~ds3X4|0KG|{DeJZ{PG$q`J(MkI$d@nPaX( z^SRRtz`v}pviUd^*W?*8FR#5#|K!}4M&H5fKJC=jN)Q$&r;)h1;^2@vd&}2SaH56D z`khDll;>l8A(QQPtS%f2Rya^DhTe@1I@D1dk33c*D>^!|$ud)jPwJa+JRs0USLzhRtOmTcTxkAnE|4`VIw!mv{_*T04) zGI~Pa7jMoqNuR9*N%R$VJzQ(sRM!`(*qQ?>X5p3z$3*1ocRDlcJ|Ka=D-q^N^QEEd z_2ya)hb*oe-%*D)7d>I~4>ofNta?&-sz{xFNuqkTPZ_1|V&w+=D?3ygMP@>e zyJLFWjO182RWEXd{2Uk}v@tt4Sy>1vqV=o5*7B9io|75$O=l(d}wcGUwP;N#M{F z&+6AcR86griIX_mmTT<#V|E}O&QI{6>UMyOsJ~3Rc8H71+1s&A6^+QgbLz?V(I~ju z`B82euywz#>2q5^CK4Yla>i`r7hL3b=1-7hTf^S+M$YX)#}>w>AbW{-MHwCMbseB`>7v;8C7X-f(n3j8s*b9SCc14m40 zze&}bEhWvL%?CZwe&PkzM}Eyk1aOiSlfCRsvQF?~BlnZ8X@=bn)T4y=W#e9(IGq#LTTCyV+Z$%6YVAyj_&H`3hdLJnj2x8z1i z`{?kalp4iJ`w%s)Va_|i*Ad&5+@}MNy;{4xZo72Vq+Zh+5EgPQ`Krm;bL8%zTBq)Y zHJE;Cl55>Gc)dT_)%%TCgvM)Mx0DoDih99FmqET4qsc=Ar^mbA6k4&9wbq8Fna)!R z7na8?>puiZe{e~a90YCcJeJ7TN$?(tFp(Sf-ZkL7RU`! z9+QyK)WP;UEk&Wi5$GnK7>OoDL2DJ!c7Gugu#yD1ho{q9IYT;owN2Y{vxM6imBEvMR7oP!Js#51-eAU^?gy=_w}21?{E$Z82`FnR_Aq}FZr%la#63wdgD@_ z@Z%e8PjyKHCpX6k>lr2V!fsRDrdG7A#{5SF1u9iOFt(+As{YRW>)E}j6DGb21NQIu1X|1C@{Y^uRc~V7?Lqf&HxEAL zwyA8HD00>6#6G$dO|1CGCG=%IWp9rs2Nal1Bv_~0FsZ6!4f`?KkRRX2Cm#Ve<+!${}N1E~#W;acOheW0V}7OY5ECB^qkwxV`*8#$eTAP2AU} zX5qo6P;l+d*{YUOVjOi0abqKVnf zlDyzRPg)Idt}K#~5IJrqp-R5tzap7@**o{aR*TMDwMD^}iU87xGpBMj{oMS{H02rN zf}Exczmae<|0(n8uL(PhfjqI%X$Y6TR%8-bEE%OgpRFxZvO0?H5WhA1_*Vj+Q^u|L zH6`)c>AX8nkfa2TT2nC#)mAaI)?T}Z%}c@xy>D5se@TQqdR7w7y;RqubsgNrQOl4q z7 z{1IhJ>&2RWlEetZW>@$K(kFJ=$C?Z^)CT{Ah365LTl&9}7FgRBwjpz0MYxT6 zyEg6YdI#p1P)Bf;#zloqEG7K#A>EIk)L@6_kXq%8rYM|UkK`k*A=}r7Dc*U7qhl3@ zo>sE-x%T}pTqoaAiq8X(X~Gm*wsAbQB-8WBknU=NwC7$gzEInT(ANS*TSWQ=E8Jjm zTTGs_BtJlxtNX4Izt*+_-#}u+M3rfRiAyWV?-n=YLD{9FypGd6k?MP;=%;;4nK|2U zOmFJc`R)^m*3Pv>4mp$+w+%*Ee`zgtQ9%#9uH__a_wQ9d z00EF6J{@>%JUm<^?8|tw{`4a9#|M|q^0I=qu{Wbfs0UwnZNRcSZADv3TE23{)5JW-U z<(_R>lhlZ%Co(^ZjC87^+}a^<=%sF`9W%fh=CeQ_X1cu0(W@3@-B|bg9Lb6^3QR(D zxtd0md+idnF0D+nwV+iAK*WV`jNRJ{BK8Bx0&rF#fR*t^?XuUg~lM{WTdIY~};l-Pa zvLk`YBqx2LI8Ullelg(Rc1MpLs`$fF^xImEcHMf`FpH^Up$^ZN_APFH+{WQ!N#?hL zC-nn*p5?kXh5>Lon=?h&&bh{uozjAp~vgQ+S%|8YAv>@|sgkQx@o-ik~RbG>8c4 z@=VQpFUTw@^kF+9Z1BT1|MxnwuKtoj{(5}y8&$W{-vJz>HBaYjgCd#P)TjXm4&_$c>}I7jMOLZ9S?bt;_gd!EuB2jG&|@ye0gAC zJ7DdtVRyg+^rzmR(L3}|NMD|`u?lMQz>ka7T*qT!EI@~VHR2bXvx=pEcDl}gOPu~l zJA=jfNG5wjrw<0-Tfu##FZ9%SejJo?XD8<+D^nc0+`-Bh+L-WrD3|^jkSViz{a3N( z<*FxPG%jHpg<<6gUNl|)xv>%w)qW62(}Q83hXLGpM-C@wrV6Gyx(V&W!s`i+ow@!S zce?`UvfO!=`Yz^1AS6QwwZAWP)4*Dk$g_nsET`C`3$BO|0%{L27GRdorzyY6bX>@? zy+hV_;jjlIp*(cHuJd6o=V)f@xh*=le6WBoToFU|v!0_;qTFpRB_s9LK$LkB7tT^% zeO0H>UiLv|I>UYHKlT=cm4-1f-WqA>poA!9y;|_3$hqFvv zvk3ZMc*1I}oEHT)*PBAQ4$Wfv{o_})_hE9sZesZzN5}%5PI$vd%q>nXLM^`B`4t5> zmg%B)HN6xrA*{VIvS>Jf_hQ&@jmL1}=E%LHX={h5^w=z1;QaT{Nw7t0pmkSY4+4-Q zE~?fm=jE-%=b=WdSPp^7!ECp{G{1+eQb2QN)VUUm4`xnzO5ct1jbRo(`n}l=5%Nj& zqMbckX`TW`*wt_@N?SGo+Z14S`AY5Bn08G}Nd}MS=j!IG% zUKBFIgtJRQf9Lb}r|iYu&Tp&99SK?$4U5S>OD?|M-PWL9cm*(?|Rzi1Q zl;RzxVA7483*=V%K^r-1qu%99YG5g?@1opLvDtstRzUlg4ATBSlNk6SFfz#hIhAN- z8U4l-S{3>S!U+cmqS+1jxiRa4cPdWs6#Ar};UREiiJ0pCVM451Euew36#T5G0975X zamtBF3W<9FL(8W>4w<=1#p`~^JQ(6V%UWtbfXxN)N(IreB|wb};g$yI=9M3Tp}Mc& zj#nrFjp)kZ>bLtZ&`uAfDMTKAB@%io(Ar0{S-hXH?No5D0-8Qv@T}c8nB0#qGUAPo z@*QpZiML4?Qsb+8>S=@K+-xd8>V~gb;X%434DLDH2%RaJm+Uk`d=uF|728j^|7z>r z>n>eltQ%{3Bklp6F&lvgY%8c&h^K>p>UKs~btc`J^ z@$1dL%BOuj4yy-rQBZDUnaW?+n2$AYV|n70=>{t2KPL#MX(bs}adqb_d)Xv-F_XV% zvPU60cp%ytyTp_cMko7hwSKHHbTy%y-TlMA5BI~?u1or-P&CrC2R~tdZ+7;|G_z-5 zXF(^lr)xV&I|7($8PuQDyl2C6d|MVM2zkxmR2QzWCy0PB9H47N%*)0*X@6v3~?I&!73-lXz{je?#V0+?M3k*f1jGQB6}{kQHl=kYqA>bD)j9!zU(7NQu)~r>Mw_& z9=dDRU1M2pM*)`K-t%=*sHgr`xr%S{YI2+j723tEEY9msQ* zr|2Yiwj5hUu-Ve2*FI|z0WuCRY-lWcQ33OVHHLd${mHVDAWoQ zDIALp`5oW`YW#F}lpkpQ7!KQzK$P%3 z8gJAD0?NSd=-^sG`!Sktka3Y$6rSkfKI3@1cEGHzk+A! zzO^;ksn1n5PU@{u?2WJl!~K}I4~O1Uwmh0TVKudlD`O7yL}{DI7~NUg#;9Ou?E(RK z^jGuwNw(QBd|GIc5C{d}Vg1+4Mwh(c`Q!*Do5G2Tm32m(2N z@!Upx!rg@pA_LuOZ7g|bBxnWo;JKdB( z^CU8g9zu$&PrjT^)R1Je1kBWPHl^3I?bZ7znKgAi;kMgkDp?Oy*XxaE%XCaNVqXQJ z0r}yw(Yk7JQ%!%086;t!26vXQzsQUQ_IuQe_h2eP=_h_9uMpNqqVLdoE7v5+CJ2bG z7MnK+5Ztw%bq)G!rI=JLXs?{-L;(Rciq*S+Fhchsi$>{_6r8nIio?i`SvdE-m*EUA+li0Qu7F^# z`qXU7FJj2WbiVhqsfv)YrgvJ`2T-1x|K$}@M3UOt>=t(MwSGe~=m+gAe|5u4ue{%J$0S^i;yGH4uDZ~58=q|EAAPucj`bx(NA zQ|MLq2Ps9LgNCBy59-$WrzjP!P1!r!C^6M)9FtvR-{POqubf&Z9HVz%MsyYx?=RSx{ZT3BxvS>^`Cs9b z&gZU@O1NSh_1-dBdkonQJQ9>3N879e-%06_?@BjxzQl?Cg2$N3G~8Qi{Y4(QA@m6> z{qPjTJrNQ4^;nR+cw>lvLd98iHaXdKpA>9=Rtkrfqsq)jEPoByI zE$a_U5M9yLyd+QOyd8~K=_XAyqmI#WuT^*3_hGWe)hpz^vFmfP-^G|3aS)CFgQYRK z9&ms0&v-2={)EQSR~EU0O182yCveKss79ss<#m3xY|&g#9rsL_-PKy4mDyOPn6mrc zoUo(Rww0&`LI?=$d@*KGHZ}w5snl4rdjUBWB?6%1$9!P&VhrIGwrdNr^u@QZaG#)ggk+k~2$tjiBdw2)E_PYPLa*YlYyFfPsMJ#e z4YfuRP9>^Nf)$y$$7D{q1a3#**VAb)3dAk#s;4LxQLQ!dsFrwj)Cgs^_|nVVApy4p zFD6vYkNkQH_f-3ml<+sNIt7YYe-l?yEiSq}C?8*Lxgod>!Xglx7ex6grGHdIEpvE7 z!bFKoxM~NJ1%jV0Z-%jMzY{!_(0B0f&va@qPc~CwVLfwyj}zWb^H{k@6J}0=*=7NE zNHYvpC+sZvO`!cukfZ)kdzYTU@BD-B^G6=GVcRm7lQqpF!$HuBtkqHT4}ZK?0@NPC zb_l!%2P5$Qs#4O)DuY_MLdrpFRErWy?mhFu*P!{pk4hHdINu{%5a@Jazt(B#Fu`Z= z(t%Kw{TiuF@AAC`0Ts71Z_pR6E#$u$VCK#E&c|USLkMOx0Yb0BkVyz;kSA8I2s6+b zF4AT8NFHeqqB5CgHE-a}Uw#@AZ_Iu+;+>&X7lNz7Jw4|Co@q0Q{pod+uY!&|U{ThO z@bp^yQY-ZBV-Q$@P`Ll`o2H+-fz(*gSJTJXO_SAo4y}3YRFw1}wxe6EO-${-cKEEp ztvo!40-RT{qw~R>xtzl2M{9Znkms(_Teo_5*Iwm;Lhl|3?&VOy);p|rjBy#ldJ{qM z!?H)<^yxu9f0;@65->*wL!*HJr2i{UWCHJKk?ldR&T;A-1doz2Gd5` z5*H%x-H9EGFcK{`0FwT!jp~mMXlm*qwRhf$s$UD>AgnW4ZJm628$0|Tn-#TgGO@C2 ziA9Gu!-^+(oLo%e=wayaQLo|Ubp^C2NNLNj1-HPGrniY}Xu&ZH4+8!g9ZGYk zKl}hMYLFC9(8LxEqQmjX?||)>f9jDts@Jv&pbBhT4xrCaeAbhr!vreP&K_$|l4XJy zB{}}Zha(wj+3jA0yxWlNlv5}5rcx||LV+EmxJ-q?WX84<#LMUPpFo6X(_n;jkmoB( z#MTu;1%-b129>T?EkU`>*5VT^hrmUmm1}&u>soDfT_0#LHtf}E+Ne^(&y zkimp|+QAZS6oJM&EIJ*uXwVkoMSsHS7_8B&;;0z(5bnimn)mK^{CDWhC=sCB~Bm^=-7#G&^ z9IbDXi|;|2nQe7IHJziD{gc<>I)+M(8(mSME?$+46gRurbVXXcZ!jTI$i z(?AS^*e~ESt02r#0V!A*SA!T|Lb>@wrxJr_avQ(MJ%b^ke>dO$rkiayn%`hKjJSgO z-Zqf~Sds4c;xmxG$4Wid*jcV%P3U)yx5ywkz{{IF-hEBV!1WC{qU)zJlG44zvXHGgXfpu&}2qKlhyWkA-mTVGQr3-ed}9o$m4lBT8b z>X}dFLBt?drc6)wEdpp#YkJ#!b&x%&I;C9tAit5hj8Et;T$S&(+E+KWn{y{>^zZ`E zq+%uIVF9I^HRh#e0|sgA8M;){LvBl@9nJ&TtNwhymot;BC9T6*tk9&n~9lu_2 zX*y{fcSr|bhD6!~3=P;n4}2pE`Ulub1j1O=a`fZ}uJG)bvlx@>kmxv-OcfB^j{g5) zsaQZw9SDwat@j`+enJ9iT^vhFD5cQgAyU2zzP-8PkzSBZf?ksgPt1S|^MJFykou#w z7er7+5Y48Rp)B9a1F^k4z4h*{&+}{Y+6}SK?!nONVrj)SEC9uzW7@nFa0G+9+ybA( z(Q})9IRq{3<5QD=#BWHjMqtuptF)XfenZf3`&rX(*=jca7pioswCi*<#3hW!&%W9Ya-r(8*fc zug}<(IVn(AxZgkVym@_HCu)t;y&=VsghK)bt&?fqv&Sm2D3Tjb<{D%Z zTvJ#2+AI*ZQZU|^dITZ=jO|8l(FI%qWlBfxHglLu-2o+9v7Bq<*Z(qORY81Bp8El= zcgh!Eh4vZ7gTS?G(A6u0bK}!(5a^>yp3Ot?RB=V-EhF4k(O6(E>&ZteZ-^rrEelmA zx+<*Egy#g{t{+0yB?@HrgF;*T9{DytV28T7JZt!+VarLrPOB~-2npbTyOQ_xg~ulo zB`se@vO=0&RY28LwU7}a@9FF;6ny?mq|hY%LOa&~q{=H|H_Y%H_5u|I!M6nG|f^F}Ml!?i2^SL`TAKFxv=@ z%KH{TK0w)X5Flr0j* z2ZSP6bT6Uf>=(Wag3#d~Ev_d~hXN{E%feg*es1ffq}=;)krv@9EE$x6BBZ~b{tssH z$&o4y$Vut*rd2Iz2-ebk=OnzCuYSiJQ_{ScOK{^6rk* zd1C7|UYSWm3Z(|-HU&L6w0p!PX+G9qRL*jpuisHT zuRohg4YRrM$dZs<=Cc{QF0Z-=I zvmZeu;QSB>rTv5|{xcXsF8T7#^DAm*$*iFy*@@Hs{SX-~15M3yU+d3?PdugJ1Xk{r zzM<4Ym5AifU40AwKyPm`gOw>7YH1uGystAvmZw+a8=v!On&7_$=yV5Yg+2Uu<6!+0 zx5O?*r}|Ll)Bkn{pfiXZ1rM_1p?CKr^m=#GcpxC^c#1M}?x9==3mgRTUj1NE&^y`! zHEj3i@l{)8Ce!dh6t;~_S;|mjF&+c~+64d-4=2gb`#4cC90{VziAQaQ2g`PGc8`=U zJ3s^xSNo;XrtcZ#e0A=yi7P+*2y-xUpJ|_>yN?tsgnu)-Sg%gR7DN8qDhZ4fGczSB zh5QbcEC>~Li%|Z`H@}?KpZ!TNz6fjK8I3hR`%&mJ1O=1eM4TmaWk500e@}hM#%t28 zR%6^?x-?u*MtG}S&d@)`|L8AXld0Q(Gh_>`C0ti;H0m3*xO){A|3kz$mg1oW{hqiI zO3Sg1$~e}=`TR#&rNk!8NplB7kVt%VYT6=C%L63gzpw3rxsUlOiQ4QvExRQ3>0Njo ztS%bAv&Qx02hG$enX(@T&)rW9a&RaVeiq0gA}o)ps30Hc-0|-Hv~L2d%**E zB6Mm%c)LZVLWwoRhvM4`mhV3v7qB=EY*SkGyVtgUXuZE#_hbt5^1~NVt_d>Ubf9st zf#-nt+CTh(;~Mv~pQCGSMfj1M&Q<4IvA6aHPVINzl3>0M>dPK3rW=7M(&{$c(aa;( zA5{O*7m9M+CycfHV(0#O2haEH%#Fs!@^~eFZ`h`*xA5}lwW-dc-wcPS+f=5dnp=b^ zLpE`m@9wSS}K`3`;jG{pO(#RQg{YfavjQoZZl5>%lOALsi*cg|Gjd z=0^$Xvq3+hp4GIw+X*C3e+@>s8nyZn0Ui98N;3Lh5!b+Z%YU9BFB;HJCLR$vvWsS+ zT^V)$aK2Yb+?@tAjx<>GU(JABEep6L{~6T~9z1yD6Z~CFNh19X&G|T&w*6}BZB^M( z8KD2!!YK*Q$u<$4`+xdWOc+o|a6M~mrD6R|_>uuu83t^e9S$(lzW!H}z=@>=A(fA= zd0k37)VcL1QV3lT^kR5p5sN;6SI9 zmPITd4BE(NfA@bBoraG){>CpW{o%m?PN2;b4H{k3EyYWpcZm(%PI=9B{evVp-5EaY zYWNBN;L`%;AX4+H$kLmh_aj$c2w}R73DPZoM>+HUK4$}Dzt>f{V^gfvXxo1?qa2(V z(EhZYdh|!85&iZf#Xl&Q;oDuSBkTkvAg<8|_j8PlQo7}}F|zmr&@efu|7|`Lqo6 zspMZ+LNUdq^9z&qxeR`~DRU;xzk8oJFk6|(0L>M4pQbW`$7V%()jZ_eK{5}d;#7|m zm;w}88@~V?Q!ris2fQ%!j=54+*roMqE4d`9KyM*JW-lUruCYNAwo_sMghVt`wB0v` z@+K8H7!ySkaL?vi-_1Fm)k+Qs)VSjckg=W2u%AD(hn;xoD-tGEOnC!;f6J6~&wmeD z+OxBe7TWVxvK6~|gb=p<%hdl+uv9^k*{>#!TlYv~DyWDYNFI2m+I-{a;2HsPIv&Ut zto_wppZun*Z^xX5FYm@O<+o2P-J3o=KRVUgQyNgRZ9z9_w)k z2q`#8MHmRoofSxFw4b3&T$}TA{=w^knN&71Q!fjR**rQCKoiYmKIf6$tQdZbEAH4b6nJkc?q3~zI_+vRN;3b%Y zAob5Z*>hW>hI=Hn(CHC1xYB$ZqXh{}-aPiB-isoK2RH1$U>U;2Z9_|WX z{|YSE=PwEf-iX!QYb`gSK}T$$me(4-L_R39u&D_fm|v`&CWBqOn&pHnCjzE>u~Mx^ zf{4Of>GS%VEUi$C|3gn7RJ4ihbGgsQk2aZR&Gc-NFfV1~+VVLy+5Vl%;oFIwJyW`= zd=C@;RG~@nJ2#d$EUO7GWDv~#&^whFEN zws{Q%iZMKU@n!yvT|K^P4+G${ezF1`{2vxzqV&K-D=YEG2Fa{~0``7khwoeAh!}ox zcItJ{{o|KR4LuS1}@A$*1^bBgf zkUYH_{&kJo;?VG=P5cRA-TFV7U9`18&tdu(lH&8tLLRWx#+*==FJz;etB8#cTP$_n z5l+t?l_K+U@ou)~kK8vVjCF%;dA)U^LIP*|k4yhxD0&vpy#mTPVS{y~v`}=s`|+RH zl2`AoiVm0Y7Z87XJ3(f=8|>FxkPzvynrT|QNUap6C*A4GiOrG8Q$W6IHA_h>9 z0@nkHJm+Gsxiud+bgrK6gL554gA zspx)!leNxA*@L5kpF^lMAaIDX!}?r|oPp9%MutM!`23t70c89NUXSYQbB_%mGsTUw z08)YLyi}%Z4&bRTNE1zy0O+$x-w@gvI##G|@OlOhWDA_wqH<@&EhaUFRM^|Dj?f2x z0P{FcS(+Rce|!YTr2l&xFBdvK`WwowGH%M)o;LT^;jgxbBc$kK%yi=dy&2UtaMDXt zj+~Znh@x>(rc(h-go}9ogCZMzOJc;tP%fQ|1OU->di8zz3S@MmU$(!-6J;^hY6`U5 zvSx^saNWIzFA2y@HQyiA_jp1xqTgfjisXci7YkU{hvE!iu3yPHF479SJT)r{V%~vp zmVDgSnJb-78n^a(opCRf@3DM0W8poaB;f|VSDMjH!7wHe`*JyWX~eJw82e~^)q8P9 zF4p&|?Le{rhV7+Nf4H)S*U(@@m%{rOO)-51gBS|FhlaW_-yG_cDslnK`-$Xj*Ka;O zZ11GKW73@2iMM?zhfXp5Aa&@ipgAQCgR9TW=! zLlt*O#u|cLf1uuM=4`F4$2?%kj~on$&Ro9JHGI0?=bU+#bel)I!+IhKSO2!|_k2zj zkJES>a1@9WR3mbZLrmQjUepjxc~%tZ5M-?r{5nze{0BF7ygsk|4zs;S70}TJ2{Z%dqpn91k{wo@ol6>4QZ8Kg}NUAv$o*`5cS0n*)Iv z$1L4x98B`S^mnvBPOP|4LBc@bixX2(gorWqe9Zmb(uVFIVO@`t@Jwpium*rfUB=7TE09H5l>MZ(#Hb(8!H;%>0fszu{^s>qtJW#~z@YdhT-JPn{L{eyaa$ z7HvyES}I`Bva(}iOg8!dGAlScb}O#@60*p7ke!Igf%H-c8%LmI4=Z&%Yua{ zsO%LJ8S;V|wRSwh!Xf*LiI3iKD3PZSJtGLkXKkUm1_`C7XW6k}FHVvYoZ~fJ#gc#~ zD6&~k#_q_dPBQoBKnU8v6TS|E%ipM#ExE17+K+o~f-fFMgKdUf1_Ho@i#To4Dyt-8 zWU&0eDLp<)N6dXyYkKD|&Jqi``p&wf@w*Av)})^%lwqsGKA*nY{rIIUcS<-(osYZD zKHn42B@KPR>aVSQ(d9;z&b~s&{SO}#cUMaTdLN}D4nGay=$CFDHEn-|&S!?(=$1x> zs=?bl5D)dkRk>7bi1!n2PE6`^Xcq4OX5J-f<8a3a%)Iet0I*KtNH-W!RmXBJf@g2A zT4*;aJrt=U%DPB2$|+0MOUKWJmyLF8%Qa>tIAPp)Gr4RH#Y_+R=qCEKi^}JaW$vq; z>C%1ha~$Q_g@)2`dxaNzC)R(DU8T_#KTKZr?)&Qry4-|DHekHWS*J?GJ6p)q}M#aqrS^w2n0;boVsKMkjhl!+!Ev+ z-X*KfJw7+H$d>tSS@3~#Wc+X1uR9)9^9<+%O51q-X)F5e&cKzTw|93hsvT2FZE@gy z!N9--Fx1}VTj9Cz4TkYP)p}(v1F(UyFP*&AvDp2l+NIC-O1wG~8FhTa=Hu;O&Zb=6 z@1Ob%J+352XFRiS(TK;aXb&n>dA8<<+iY+cY?&fT+23nn&VClzot}X&M14c{t)##a zSQTSph4YkEnxP)-sWFZ!b~g1{ENzj3?GHL%#XfWOV7{JXMdLg9nzuI@fR2!>235Qg z3TS=(nIB~|&Y0}FVJZ(4*BCi)NcQj)hpV@idgLzBNzP@_%uWnVj3B*HLkwD*Yvy4)08qKwp;u<4cl|DwLED1F-se1>$q2569}~)>vLP zVYl2MjTXLAiXZt|BM@K^@W*q)Di5Aq0q`_I`t+Y*XWfT%%iUEs+7q1mU(8W1uX63EN)rN zjUFYu(tlk)*qCH)B1ABLnLPbP@J-23I0OfqQypVa8D6l{3!$z)r$jmg1`;(t2b0(< zL+;LeN&b^d{vbK;%7o7w)B5M&bowTD6a7R_*q2Wls*k-&$ldMmhV`ui$Zff|;pX>c zNg>Oz{DGv;mO4e+O#-s@lBj(HfO@bPKULsVhbg7za(f<@C2lVR;VX>cu1LZgbZ^@36HbF{XhHSWeqAiRcMe0+eu>U&! z>5*y#yJZ6QyBPm6O#k-Bd?Y}elzp6mzb0tG33vnl>h!LPE2i_r(#$Xriekr9= z8YLo|?v`3-e8;QMnKGp6Cs_`&k0D_oQ}`FLl@OIHKFpOx7CTJQ(038r{*&zGX*#%2 znsXmObvV~vE+W<2^a2>z&F69c+iJP-*&K23xfy9@aR3X-%nd>gX69>5Ff2JhyPZ^hHzH(36|`DO<)t60fowE}CMuS{ zGC(N2;f_`b(>FarFjRnkPL358?swAVcjkW)nkh;4@^Q3M7D!BQ)?e)KG&!Qyo``o` z7Cp686z$R0fpTavMq+G}O8(fhSR+<|K?StLs*9z4@tg(a;QsDIR}t+6g_iZ_>9#QA zf~O_QrmLlXA}Jdh0v-#5|5?1WhoC>1UJ7__l>KIrDVrqw>Wx5v0#Hg^%QN!YUrQ@g zVSHme$Ns9=kEvzie4MU&`imWw`J3qf^W>ny4yYIh+0kK1~t-fkM0Z!eKht8TZ}7KYgx0pKwxwRV$nb zu$V*?G_NIZDE%_ts;h``a7bQ>#IAQ~w)c7!qxQ1t;1iko=qpm50-Dqn;M63-%z3p< z0FwsT6+D*D7gtH@pycYN(^c1^pj6$Pbj_pmBemY98!P2sMd6?Y30m+4&a?o(73>kd z(7|TnYJsOJxxb4cz7x1#I?w!vc_D{M=;qkJSoU))?W^vYD|97}p;MDy9S&UOv0YCe zYkxkz8*LKLBng7n8|x5Y!YynfiE^@L>kQ+qY&d#lk?i$j1;=fVdxZEJAmn~(3Ef#l z8kr|1@MQrFye_JAV0V+vAJKk!?lfN!s-;Fd6ox%@Yz$?SBrr1rUn~IfI@3`6D~rg? zJb(RHxMRhKfp$Lmj`{Z+#Uq0LUqex`_atk)9R71%&^>C^b|mQZwghX<`MciYmdU7< z$v4p{vV@|Fi{>XB^Hl`urGytzJskI@Ur0gi4b2q=j_b1HMWhjwe#1^o#x2yyFW3-L zikDcTf*%HBKD@2l+)|syKvv=7&{!vZ(H~m;UyI#^F?ZXoxrEMIAqFoN3U~-+WX22d zGJPv*QXS>AoSp^e!)C7Xe|50lpOxSL;J3E*^ZZb;=<=6bE-(zV38KGM7i(0-3*UbF zUW3hWSs$2E^l|%%N!YC+o74R7mkSzQzmvO&f11iX@11Q5J6&q5CB#F~ zgPrgIBLKKMV-P5RWkZZswkEqBWqL&c+8@!m15&DInVo!IxQmhyDP<&9>}ZUDRuB2A zle!Lin0~j$$=~m9v__VJ+<)LZML7(s?V4&tY>jkLxTe%ancX8!D6akp6j6_-20807 zT&N&R-BcwZv4g)tNY@}q#5MppTL|urVaa6n3og56?F5J5$2VFV!Q zw?!o!glWy`XBEz4k-cB*`D7{B{U+J4!K}Fau++Y{HnB@stbFP0(aoBV!y8^Q^zWt- ztJzgL&~whxNvjt?UMQ*mO^msXuhmOBDXCJW46=*j~r?byZWN#R5!?Q3yk!?9=Q|r85d?? zxJLMA9hXss^4=3yhelENt-J6~v<4~Ts;NbRkt``rD3!J06fsLc)@)AzGxPP~v9z3! zs65Jd+tKHGN5ILIdeMMeyl7}mYOXb9SmZF&Smg8CG%dsUqdVm{6G{*^MQ+^eUhEEb zHIw(@kki+ccKy~bo+*@%y+F@wHj3_}IHCn(3bP6;<>m_$W*C|HK1%{1)Cz9rgeY&e zrWym<)Qr-znTz4H$s~PcA~AWtJltv&h;fk*!S|ZrH&3MA1l1l56@Ko`5XdU?Im&Fh zwIqMR_}vI#61`Jl(vFIkyIuOJR~V?%bF+~10fhMebqDSzDGdhwmx`!0kJ>x3b~7yY z!nC%{yiZ8}JGZf9K*TY!DjiBkXr)Y9tOP))P%Sum;6~&3PPQO&!GpOtJPZWmT062W z;c7y6{xnQsaSF+BS`LAlXe2!_10oe_}(fixD`#Z~%l5{9#=+ zFn!CIw~4P^pFY+3vSMoZlO?^9G|>>Ch=w-dR&;}}e;W$N&40nrL5xlu>a;|wiZnT_ zb)ufGv?<44AK-OoFgs8rS+?6IN+pCh`~iB99=~vE7P(Ud^s;)+wAwK;HcMJTMj!0K z_i9vn`{(5*9j!2%CTq6MhrGQv)20oNur6sOp2BT3pN3NIJV-LJ{9{(D-k+NiY&$|sWQwaTq+@^?ndQC|NaGezX`c% zD4hAUvV=}vUKau50W3eMzlU-uzG;^3E?KKKmC-^EO0Oa|l#Tc>tLSyqUa#+-+;dj? zf}CHD{~Wc|JTqP&V5)T7nJA8nfV$V}qQikum6UWTpD7su$#kv@ak!8|ql}VahZBwo z1FPKe;cixEIxw>xYWqgZDoEZrh8ZS;<|r7?Qi|$neN6H8;rXP~S|=^Wsaonn^^5FB!EDE& zz#*pCOc{6wy!IuzPYf+BeS;oH!sAetgyDf z8wSWp$o9049m$^8FQ11$gC2DaX7PL*Hh(aLAarB%a7q37ug1H{pK7e(TxZJoeUg6( zq9sv_g(l7ehLPz@a6yge3M%sF-jukLHACe#Z*de~?ySUITDiKQDU~#|n$DN(%#TF> z^lGgSxPEzkW&U_eMdg)VUkBetvr|WBd4IiMWH$yQ`VQbLE=H zd+Y^_WdAN1cf3u&W#X!b&pcSre&ARp@P-`00u$Mh1-d2{Tm7|-YBpqEV z*yIc~CBk4%w>4#acc*zY&6#+kZUgluAYIghin6ZR^zexv1K{Y(z|mt>KlE7uVO|L- zn^w}vEsW(1y^!rWn*jEKf@$Z+TZ4niEN`Uaw(vM-=w_Emgw|iDdsl% zWu|^f0$D|dh+&kDBVG>9FZ&sR%kZPAi6@Od*S@F^*d{s}bL**4OcYg3GLm8#kt=zD zjz%AW9Afj@fKN*^yi9pQUu$fmO<&8ieQkH?t!TE>D{7553QxkJ-b#82k_QBEbS9qi ztZ7e8KWitB-$^Z&S5YdAIC1VpS-+s#ERrOIS|eTINf?CMe+`K7JZg*I09h3QNvp^8 za`ayYIe~^^D@;>5RHM3>Pcm(GvUt1!|`D)Zc&pNyE&a|N3-QY0#P}8W{AS}{pj6yzM1>q zdm|XI(A3YS9bzAjQZqTzr=6d$F<|a{YcVfP2t2(-Edz0m-(ObbxRCmr`o1|%ME!Jr zdE|zx>cWy{%Z!PCdT;ZXqX4;D!r_K}>Se5*EDfhLxJttAdns#HK6fH>Ssoxipu*8j zWdtO5a}3t&f)VLx!eX=;*=39doh6;Q7gRBAAg@KRZ$z8VG1HPc_gnu~z5P=rJmd~C zc-%w`$91(?HHGN;p~Y65aj}Bww(RI$(A4eGZ|?;FLorWhlT6~^aI*TItO%pZJaJ0? zuhd-j7gl+FI~Ig&YFN*1@4b9fRl+f!NLyD&F>6W9{EszQpMMzb{r9p+z&2#S1f&K< zWX4-tFU`1)0-vM6^ozIJiXGy#w`aU11>6mKd)&0s=`gnEIFwh4kERyBIhD!rI7~TL z_sxz7jmQJ(7PIDgD|Q!^t+T!GVdU)}+mFlMYKvK0#pi0ykVt;=B?T0#`kJAWvV$+{ zFtg6ZwT940W(cYNeh^TLJXKp;1;j_z*na5?bv;*-pzyO0Wvd4g_IP3A`ZD`AFe+q|gO)T&!!U3poUfLNj z#4qi1nCQAC1`RY`2!0lSFhJ?qTgPLhbUoE9)y>%UfFTC{=3*DKedWOviKWQ~h|{pX zkq|9sJ=zq$T@y? zkVHTI@$JAeveL@$mRzA}`$yiwo&YE`PvW1U=}(tmplq6ik{#KeT^pc+F8v2JP~#4q zeBo@PAfpE6oXPRBP%tkP`YEk`C&;Egsz4g7A3X3`@WZu_M(O0EY$_}+A6TF)TTv{j zS{ORi&vvczWXO$9BM7V_0_zw2l@a94Xk zR+2wZZyWc*4cGO?>*vlt2RSyHzx+=GWG~exxW$~c7Z?TGY6N#0Qt0|K*e&|5)>&GQqY`4FF>|+Y)1^4peQLAA>mwwF?`%DjxAWmI0#v$`(Zq{(V0D}W z2(!!%c3(I`N;J0CCk_AgP~Y4zn@f-a-g&-ZdnbMu?XA;&L}=9`6ImYbdgFwsG``Vq zIl{n?vq^4~JQ#8w=q<+PiAQm%Bwx~%9hUFOLzPYO8FU!4mS@fMn6Q?akKb*;C1tQd;oPnEM<~&iNmWd?i#QyvYB7(vhfAe$x}Esa7vHe( z)`M2wsrdipF8L$kDx>(_AT7yfpz%oJNc{5Q)*kq$-x6P*9v2I+FgFf{S|-xX7MwK* z^9-Z8jVOW&@=9@h(hB1EAsmT|d17?EL=PSw2KpUcc2#8=MStEr`Iqu4IbYH>BnI}vt(&EG?aR}nT8Wd>1Bh_`Z zxu8$775f&dSyTX5bkg1J57#Ej{d;Hp!wC?LBXNtf@h& z3T)7xu~YSYnwJsT#^Y&;Wkiv!UGu{J-I2GRJjaGX)oGSZ_Kd%xA@R!}^=Q4q785;g z4xT$$kgj{dE%Uf3t(!JQ2HKg%>JYV@$yv(+gvceA`4O))?REbENGX0x)_dbB%pi66<;TVwIA7Z*s=b#EJrWKsZ2qj*_eXk|VYw1Z z3nx0(>#{&At431O@ykt0$N_9#;fnQOC$XQZ^hQTXw+AFtkd-eo*!V63SC918sY%*@ zk-Uu61ILgT$y00e@9aM1H{=?71};Wnjng?iv8Xez{NnCkkK@UJZMWuAi(>QHwz0ff%9C?i5^>k+G?fvHXk!O_`(+N$ew?7keD8UB|G(EYZx37_f<%JrQ`lKHZz5cBbFJ%_n3pzFS10H1Vd&}FOG3}ljfE@PafE` zJNMY}3`WWu-rkf*MQ-Kz{P`+ri~DE8$q^oX=G$toFek zN?zu9wnHF(A{9(fX1{UtXS0BG_fT>Y7}iTTCuKck=3-S+av@A?#Jb3AJ3JtE4712# z-NO-HJmZgLpAvssCh`3@QF(+Rc)@BZM+;1o)f7IvK3_WPA!xRi14*tgmm{OMXqZ5L(DFT3#ktOVJO%XT=?TFexCNb0M}f zpe>f?dw!fO+2=;8C0q;v#eVU=a9w-I;&U{cGPq_TC*|vyA(w{qN^GU;Z zVc&*r?Z*_uDIXvhn)V@2nr40%?11Z6w+}T%R~$ZchmE2fM_IX0mC#Or*RM~FAf+}2 z3C%8rKJw)qkdQ%oIOjwGcA~N_VzsJ%kRDfVcH_DykS-nikd3XwXi&Ul6|%R}nCoU@l2%*%CaYe+jRjeI5J z;7r*#M(a09DrV9FnfMDKN^};F_DMAi)g$rNZvM)R-ijQ1)%E@lj1d341Z+pO%tw5S z#vj})?L9a7LjrklFr~HQ0{dfgjGH#Z8F&=$2EkY;zj<#uO7Fu-jg^s6rIoQ*X)KCN kC)Jgl7IyDt1M(=32|CI*DI7{v>LBo^D2GCp%9sWI59Y;SdH?_b literal 0 HcmV?d00001 diff --git a/pkg/webui/ui/build/manifest.json b/pkg/webui/ui/build/manifest.json new file mode 100644 index 000000000..080d6c77a --- /dev/null +++ b/pkg/webui/ui/build/manifest.json @@ -0,0 +1,25 @@ +{ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "logo192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "logo512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/pkg/webui/ui/build/robots.txt b/pkg/webui/ui/build/robots.txt new file mode 100644 index 000000000..e9e57dc4d --- /dev/null +++ b/pkg/webui/ui/build/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/pkg/webui/ui/build/static/css/main.css b/pkg/webui/ui/build/static/css/main.css new file mode 100644 index 000000000..a8874843f --- /dev/null +++ b/pkg/webui/ui/build/static/css/main.css @@ -0,0 +1,87 @@ +body { + margin: 0; + background: linear-gradient(180deg, #59A588 0%, #404846 100%); + background-attachment: fixed; +} + +body, input { + font-family: 'Nunito Variable', 'Segoe UI', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', -apple-system, BlinkMacSystemFont, + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; +} + +* { + box-sizing: border-box; +} + +html, +body, +#root { + width: 100%; + height: 100%; + margin: 0; + padding: 0; +} +/* nunito-cyrillic-ext-wght-normal */ +@font-face { + font-family: 'Nunito Variable'; + font-style: normal; + font-display: swap; + font-display: var(--fontsource-display, swap); + font-weight: 200 1000; + src: url(../../static/media/nunito-cyrillic-ext-wght-normal.woff2?f=nunito-cyrillic-ext-wght-normal.c18a3502d6473272bdc3.woff2) format('woff2-variations'); + unicode-range: U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F; +} + +/* nunito-cyrillic-wght-normal */ +@font-face { + font-family: 'Nunito Variable'; + font-style: normal; + font-display: swap; + font-display: var(--fontsource-display, swap); + font-weight: 200 1000; + src: url(../../static/media/nunito-cyrillic-wght-normal.woff2?f=nunito-cyrillic-wght-normal.ab3faa41df1758ee5b88.woff2) format('woff2-variations'); + unicode-range: U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116; +} + +/* nunito-vietnamese-wght-normal */ +@font-face { + font-family: 'Nunito Variable'; + font-style: normal; + font-display: swap; + font-display: var(--fontsource-display, swap); + font-weight: 200 1000; + src: url(../../static/media/nunito-vietnamese-wght-normal.woff2?f=nunito-vietnamese-wght-normal.864aa311404227008fae.woff2) format('woff2-variations'); + unicode-range: U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB; +} + +/* nunito-latin-ext-wght-normal */ +@font-face { + font-family: 'Nunito Variable'; + font-style: normal; + font-display: swap; + font-display: var(--fontsource-display, swap); + font-weight: 200 1000; + src: url(../../static/media/nunito-latin-ext-wght-normal.woff2?f=nunito-latin-ext-wght-normal.b2120f41c4a82a277229.woff2) format('woff2-variations'); + unicode-range: U+0100-02AF,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF; +} + +/* nunito-latin-wght-normal */ +@font-face { + font-family: 'Nunito Variable'; + font-style: normal; + font-display: swap; + font-display: var(--fontsource-display, swap); + font-weight: 200 1000; + src: url(../../static/media/nunito-latin-wght-normal.woff2?f=nunito-latin-wght-normal.07adf0b2f5cb89c62ba7.woff2) format('woff2-variations'); + unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; +} + +/*# sourceMappingURL=main.9c2b54a7.css.map*/ \ No newline at end of file diff --git a/pkg/webui/ui/build/static/js/787.chunk.js b/pkg/webui/ui/build/static/js/787.chunk.js new file mode 100644 index 000000000..748939666 --- /dev/null +++ b/pkg/webui/ui/build/static/js/787.chunk.js @@ -0,0 +1,239 @@ +"use strict"; +(self["webpackChunkkluctl_webui"] = self["webpackChunkkluctl_webui"] || []).push([[787],{ + +/***/ 787: +/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { + +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ getCLS: function() { return /* binding */ h; }, +/* harmony export */ getFCP: function() { return /* binding */ d; }, +/* harmony export */ getFID: function() { return /* binding */ L; }, +/* harmony export */ getLCP: function() { return /* binding */ F; }, +/* harmony export */ getTTFB: function() { return /* binding */ P; } +/* harmony export */ }); +var e, + t, + n, + i, + r = function r(e, t) { + return { + name: e, + value: void 0 === t ? -1 : t, + delta: 0, + entries: [], + id: "v2-".concat(Date.now(), "-").concat(Math.floor(8999999999999 * Math.random()) + 1e12) + }; + }, + a = function a(e, t) { + try { + if (PerformanceObserver.supportedEntryTypes.includes(e)) { + if ("first-input" === e && !("PerformanceEventTiming" in self)) return; + var n = new PerformanceObserver(function (e) { + return e.getEntries().map(t); + }); + return n.observe({ + type: e, + buffered: !0 + }), n; + } + } catch (e) {} + }, + o = function o(e, t) { + var n = function n(i) { + "pagehide" !== i.type && "hidden" !== document.visibilityState || (e(i), t && (removeEventListener("visibilitychange", n, !0), removeEventListener("pagehide", n, !0))); + }; + addEventListener("visibilitychange", n, !0), addEventListener("pagehide", n, !0); + }, + u = function u(e) { + addEventListener("pageshow", function (t) { + t.persisted && e(t); + }, !0); + }, + c = function c(e, t, n) { + var i; + return function (r) { + t.value >= 0 && (r || n) && (t.delta = t.value - (i || 0), (t.delta || void 0 === i) && (i = t.value, e(t))); + }; + }, + f = -1, + s = function s() { + return "hidden" === document.visibilityState ? 0 : 1 / 0; + }, + m = function m() { + o(function (e) { + var t = e.timeStamp; + f = t; + }, !0); + }, + v = function v() { + return f < 0 && (f = s(), m(), u(function () { + setTimeout(function () { + f = s(), m(); + }, 0); + })), { + get firstHiddenTime() { + return f; + } + }; + }, + d = function d(e, t) { + var n, + i = v(), + o = r("FCP"), + f = function f(e) { + "first-contentful-paint" === e.name && (m && m.disconnect(), e.startTime < i.firstHiddenTime && (o.value = e.startTime, o.entries.push(e), n(!0))); + }, + s = window.performance && performance.getEntriesByName && performance.getEntriesByName("first-contentful-paint")[0], + m = s ? null : a("paint", f); + (s || m) && (n = c(e, o, t), s && f(s), u(function (i) { + o = r("FCP"), n = c(e, o, t), requestAnimationFrame(function () { + requestAnimationFrame(function () { + o.value = performance.now() - i.timeStamp, n(!0); + }); + }); + })); + }, + p = !1, + l = -1, + h = function h(e, t) { + p || (d(function (e) { + l = e.value; + }), p = !0); + var n, + i = function i(t) { + l > -1 && e(t); + }, + f = r("CLS", 0), + s = 0, + m = [], + v = function v(e) { + if (!e.hadRecentInput) { + var t = m[0], + i = m[m.length - 1]; + s && e.startTime - i.startTime < 1e3 && e.startTime - t.startTime < 5e3 ? (s += e.value, m.push(e)) : (s = e.value, m = [e]), s > f.value && (f.value = s, f.entries = m, n()); + } + }, + h = a("layout-shift", v); + h && (n = c(i, f, t), o(function () { + h.takeRecords().map(v), n(!0); + }), u(function () { + s = 0, l = -1, f = r("CLS", 0), n = c(i, f, t); + })); + }, + T = { + passive: !0, + capture: !0 + }, + y = new Date(), + g = function g(i, r) { + e || (e = r, t = i, n = new Date(), w(removeEventListener), E()); + }, + E = function E() { + if (t >= 0 && t < n - y) { + var r = { + entryType: "first-input", + name: e.type, + target: e.target, + cancelable: e.cancelable, + startTime: e.timeStamp, + processingStart: e.timeStamp + t + }; + i.forEach(function (e) { + e(r); + }), i = []; + } + }, + S = function S(e) { + if (e.cancelable) { + var t = (e.timeStamp > 1e12 ? new Date() : performance.now()) - e.timeStamp; + "pointerdown" == e.type ? function (e, t) { + var n = function n() { + g(e, t), r(); + }, + i = function i() { + r(); + }, + r = function r() { + removeEventListener("pointerup", n, T), removeEventListener("pointercancel", i, T); + }; + addEventListener("pointerup", n, T), addEventListener("pointercancel", i, T); + }(t, e) : g(t, e); + } + }, + w = function w(e) { + ["mousedown", "keydown", "touchstart", "pointerdown"].forEach(function (t) { + return e(t, S, T); + }); + }, + L = function L(n, f) { + var s, + m = v(), + d = r("FID"), + p = function p(e) { + e.startTime < m.firstHiddenTime && (d.value = e.processingStart - e.startTime, d.entries.push(e), s(!0)); + }, + l = a("first-input", p); + s = c(n, d, f), l && o(function () { + l.takeRecords().map(p), l.disconnect(); + }, !0), l && u(function () { + var a; + d = r("FID"), s = c(n, d, f), i = [], t = -1, e = null, w(addEventListener), a = p, i.push(a), E(); + }); + }, + b = {}, + F = function F(e, t) { + var n, + i = v(), + f = r("LCP"), + s = function s(e) { + var t = e.startTime; + t < i.firstHiddenTime && (f.value = t, f.entries.push(e), n()); + }, + m = a("largest-contentful-paint", s); + if (m) { + n = c(e, f, t); + var d = function d() { + b[f.id] || (m.takeRecords().map(s), m.disconnect(), b[f.id] = !0, n(!0)); + }; + ["keydown", "click"].forEach(function (e) { + addEventListener(e, d, { + once: !0, + capture: !0 + }); + }), o(d, !0), u(function (i) { + f = r("LCP"), n = c(e, f, t), requestAnimationFrame(function () { + requestAnimationFrame(function () { + f.value = performance.now() - i.timeStamp, b[f.id] = !0, n(!0); + }); + }); + }); + } + }, + P = function P(e) { + var t, + n = r("TTFB"); + t = function t() { + try { + var t = performance.getEntriesByType("navigation")[0] || function () { + var e = performance.timing, + t = { + entryType: "navigation", + startTime: 0 + }; + for (var n in e) "navigationStart" !== n && "toJSON" !== n && (t[n] = Math.max(e[n] - e.navigationStart, 0)); + return t; + }(); + if (n.value = n.delta = t.responseStart, n.value < 0 || n.value > performance.now()) return; + n.entries = [t], e(n); + } catch (e) {} + }, "complete" === document.readyState ? setTimeout(t, 0) : addEventListener("load", function () { + return setTimeout(t, 0); + }); + }; + + +/***/ }) + +}]); +//# sourceMappingURL=787.98a1313e.chunk.js.map \ No newline at end of file diff --git a/pkg/webui/ui/build/static/js/main.js b/pkg/webui/ui/build/static/js/main.js new file mode 100644 index 000000000..d5043f933 --- /dev/null +++ b/pkg/webui/ui/build/static/js/main.js @@ -0,0 +1,60971 @@ +/******/ (function() { // webpackBootstrap +/******/ var __webpack_modules__ = ({ + +/***/ 867: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +"use strict"; + + +var formatter = __webpack_require__(707); +var fault = create(Error); +module.exports = fault; +fault.eval = create(EvalError); +fault.range = create(RangeError); +fault.reference = create(ReferenceError); +fault.syntax = create(SyntaxError); +fault.type = create(TypeError); +fault.uri = create(URIError); +fault.create = create; + +// Create a new `EConstructor`, with the formatted `format` as a first argument. +function create(EConstructor) { + FormattedError.displayName = EConstructor.displayName || EConstructor.name; + return FormattedError; + function FormattedError(format) { + if (format) { + format = formatter.apply(null, arguments); + } + return new EConstructor(format); + } +} + +/***/ }), + +/***/ 707: +/***/ (function(module) { + +// +// format - printf-like string formatting for JavaScript +// github.com/samsonjs/format +// @_sjs +// +// Copyright 2010 - 2013 Sami Samhuri +// +// MIT License +// http://sjs.mit-license.org +// + +; +(function () { + //// Export the API + var namespace; + + // CommonJS / Node module + if (true) { + namespace = module.exports = format; + } + + // Browsers and other environments + else {} + namespace.format = format; + namespace.vsprintf = vsprintf; + if (typeof console !== 'undefined' && typeof console.log === 'function') { + namespace.printf = printf; + } + function printf( /* ... */ + ) { + console.log(format.apply(null, arguments)); + } + function vsprintf(fmt, replacements) { + return format.apply(null, [fmt].concat(replacements)); + } + function format(fmt) { + var argIndex = 1 // skip initial format argument + , + args = [].slice.call(arguments), + i = 0, + n = fmt.length, + result = '', + c, + escaped = false, + arg, + tmp, + leadingZero = false, + precision, + nextArg = function nextArg() { + return args[argIndex++]; + }, + slurpNumber = function slurpNumber() { + var digits = ''; + while (/\d/.test(fmt[i])) { + digits += fmt[i++]; + c = fmt[i]; + } + return digits.length > 0 ? parseInt(digits) : null; + }; + for (; i < n; ++i) { + c = fmt[i]; + if (escaped) { + escaped = false; + if (c == '.') { + leadingZero = false; + c = fmt[++i]; + } else if (c == '0' && fmt[i + 1] == '.') { + leadingZero = true; + i += 2; + c = fmt[i]; + } else { + leadingZero = true; + } + precision = slurpNumber(); + switch (c) { + case 'b': + // number in binary + result += parseInt(nextArg(), 10).toString(2); + break; + case 'c': + // character + arg = nextArg(); + if (typeof arg === 'string' || arg instanceof String) result += arg;else result += String.fromCharCode(parseInt(arg, 10)); + break; + case 'd': + // number in decimal + result += parseInt(nextArg(), 10); + break; + case 'f': + // floating point number + tmp = String(parseFloat(nextArg()).toFixed(precision || 6)); + result += leadingZero ? tmp : tmp.replace(/^0/, ''); + break; + case 'j': + // JSON + result += JSON.stringify(nextArg()); + break; + case 'o': + // number in octal + result += '0' + parseInt(nextArg(), 10).toString(8); + break; + case 's': + // string + result += nextArg(); + break; + case 'x': + // lowercase hexadecimal + result += '0x' + parseInt(nextArg(), 10).toString(16); + break; + case 'X': + // uppercase hexadecimal + result += '0x' + parseInt(nextArg(), 10).toString(16).toUpperCase(); + break; + default: + result += c; + break; + } + } else if (c === '%') { + escaped = true; + } else { + result += c; + } + } + return result; + } +})(); + +/***/ }), + +/***/ 478: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var _slicedToArray = (__webpack_require__(424)["default"]); +var _toConsumableArray = (__webpack_require__(861)["default"]); +var _inherits = (__webpack_require__(655)["default"]); +var _createSuper = (__webpack_require__(389)["default"]); +var _classCallCheck = (__webpack_require__(690)["default"]); +var _createClass = (__webpack_require__(728)["default"]); +function deepFreeze(obj) { + if (obj instanceof Map) { + obj.clear = obj.delete = obj.set = function () { + throw new Error('map is read-only'); + }; + } else if (obj instanceof Set) { + obj.add = obj.clear = obj.delete = function () { + throw new Error('set is read-only'); + }; + } + + // Freeze self + Object.freeze(obj); + Object.getOwnPropertyNames(obj).forEach(function (name) { + var prop = obj[name]; + + // Freeze prop if it is an object + if (typeof prop == 'object' && !Object.isFrozen(prop)) { + deepFreeze(prop); + } + }); + return obj; +} +var deepFreezeEs6 = deepFreeze; +var _default = deepFreeze; +deepFreezeEs6.default = _default; + +/** @implements CallbackResponse */ +var Response = /*#__PURE__*/function () { + "use strict"; + + /** + * @param {CompiledMode} mode + */ + function Response(mode) { + _classCallCheck(this, Response); + // eslint-disable-next-line no-undefined + if (mode.data === undefined) mode.data = {}; + this.data = mode.data; + this.isMatchIgnored = false; + } + _createClass(Response, [{ + key: "ignoreMatch", + value: function ignoreMatch() { + this.isMatchIgnored = true; + } + }]); + return Response; +}(); +/** + * @param {string} value + * @returns {string} + */ +function escapeHTML(value) { + return value.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, '''); +} + +/** + * performs a shallow merge of multiple objects into one + * + * @template T + * @param {T} original + * @param {Record[]} objects + * @returns {T} a single new object + */ +function inherit(original) { + /** @type Record */ + var result = Object.create(null); + for (var key in original) { + result[key] = original[key]; + } + for (var _len = arguments.length, objects = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + objects[_key - 1] = arguments[_key]; + } + objects.forEach(function (obj) { + for (var _key2 in obj) { + result[_key2] = obj[_key2]; + } + }); + return (/** @type {T} */result + ); +} + +/** + * @typedef {object} Renderer + * @property {(text: string) => void} addText + * @property {(node: Node) => void} openNode + * @property {(node: Node) => void} closeNode + * @property {() => string} value + */ + +/** @typedef {{kind?: string, sublanguage?: boolean}} Node */ +/** @typedef {{walk: (r: Renderer) => void}} Tree */ +/** */ + +var SPAN_CLOSE = ''; + +/** + * Determines if a node needs to be wrapped in + * + * @param {Node} node */ +var emitsWrappingTags = function emitsWrappingTags(node) { + return !!node.kind; +}; + +/** @type {Renderer} */ +var HTMLRenderer = /*#__PURE__*/function () { + "use strict"; + + /** + * Creates a new HTMLRenderer + * + * @param {Tree} parseTree - the parse tree (must support `walk` API) + * @param {{classPrefix: string}} options + */ + function HTMLRenderer(parseTree, options) { + _classCallCheck(this, HTMLRenderer); + this.buffer = ""; + this.classPrefix = options.classPrefix; + parseTree.walk(this); + } + + /** + * Adds texts to the output stream + * + * @param {string} text */ + _createClass(HTMLRenderer, [{ + key: "addText", + value: function addText(text) { + this.buffer += escapeHTML(text); + } + + /** + * Adds a node open to the output stream (if needed) + * + * @param {Node} node */ + }, { + key: "openNode", + value: function openNode(node) { + if (!emitsWrappingTags(node)) return; + var className = node.kind; + if (!node.sublanguage) { + className = "".concat(this.classPrefix).concat(className); + } + this.span(className); + } + + /** + * Adds a node close to the output stream (if needed) + * + * @param {Node} node */ + }, { + key: "closeNode", + value: function closeNode(node) { + if (!emitsWrappingTags(node)) return; + this.buffer += SPAN_CLOSE; + } + + /** + * returns the accumulated buffer + */ + }, { + key: "value", + value: function value() { + return this.buffer; + } + + // helpers + + /** + * Builds a span element + * + * @param {string} className */ + }, { + key: "span", + value: function span(className) { + this.buffer += ""); + } + }]); + return HTMLRenderer; +}(); +/** @typedef {{kind?: string, sublanguage?: boolean, children: Node[]} | string} Node */ +/** @typedef {{kind?: string, sublanguage?: boolean, children: Node[]} } DataNode */ +/** */ +var TokenTree = /*#__PURE__*/function () { + "use strict"; + + function TokenTree() { + _classCallCheck(this, TokenTree); + /** @type DataNode */ + this.rootNode = { + children: [] + }; + this.stack = [this.rootNode]; + } + _createClass(TokenTree, [{ + key: "top", + get: function get() { + return this.stack[this.stack.length - 1]; + } + }, { + key: "root", + get: function get() { + return this.rootNode; + } + + /** @param {Node} node */ + }, { + key: "add", + value: function add(node) { + this.top.children.push(node); + } + + /** @param {string} kind */ + }, { + key: "openNode", + value: function openNode(kind) { + /** @type Node */ + var node = { + kind: kind, + children: [] + }; + this.add(node); + this.stack.push(node); + } + }, { + key: "closeNode", + value: function closeNode() { + if (this.stack.length > 1) { + return this.stack.pop(); + } + // eslint-disable-next-line no-undefined + return undefined; + } + }, { + key: "closeAllNodes", + value: function closeAllNodes() { + while (this.closeNode()); + } + }, { + key: "toJSON", + value: function toJSON() { + return JSON.stringify(this.rootNode, null, 4); + } + + /** + * @typedef { import("./html_renderer").Renderer } Renderer + * @param {Renderer} builder + */ + }, { + key: "walk", + value: function walk(builder) { + // this does not + return this.constructor._walk(builder, this.rootNode); + // this works + // return TokenTree._walk(builder, this.rootNode); + } + + /** + * @param {Renderer} builder + * @param {Node} node + */ + }], [{ + key: "_walk", + value: function _walk(builder, node) { + var _this = this; + if (typeof node === "string") { + builder.addText(node); + } else if (node.children) { + builder.openNode(node); + node.children.forEach(function (child) { + return _this._walk(builder, child); + }); + builder.closeNode(node); + } + return builder; + } + + /** + * @param {Node} node + */ + }, { + key: "_collapse", + value: function _collapse(node) { + if (typeof node === "string") return; + if (!node.children) return; + if (node.children.every(function (el) { + return typeof el === "string"; + })) { + // node.text = node.children.join(""); + // delete node.children; + node.children = [node.children.join("")]; + } else { + node.children.forEach(function (child) { + TokenTree._collapse(child); + }); + } + } + }]); + return TokenTree; +}(); +/** + Currently this is all private API, but this is the minimal API necessary + that an Emitter must implement to fully support the parser. + + Minimal interface: + + - addKeyword(text, kind) + - addText(text) + - addSublanguage(emitter, subLanguageName) + - finalize() + - openNode(kind) + - closeNode() + - closeAllNodes() + - toHTML() + +*/ +/** + * @implements {Emitter} + */ +var TokenTreeEmitter = /*#__PURE__*/function (_TokenTree) { + "use strict"; + + _inherits(TokenTreeEmitter, _TokenTree); + var _super = _createSuper(TokenTreeEmitter); + /** + * @param {*} options + */ + function TokenTreeEmitter(options) { + var _this2; + _classCallCheck(this, TokenTreeEmitter); + _this2 = _super.call(this); + _this2.options = options; + return _this2; + } + + /** + * @param {string} text + * @param {string} kind + */ + _createClass(TokenTreeEmitter, [{ + key: "addKeyword", + value: function addKeyword(text, kind) { + if (text === "") { + return; + } + this.openNode(kind); + this.addText(text); + this.closeNode(); + } + + /** + * @param {string} text + */ + }, { + key: "addText", + value: function addText(text) { + if (text === "") { + return; + } + this.add(text); + } + + /** + * @param {Emitter & {root: DataNode}} emitter + * @param {string} name + */ + }, { + key: "addSublanguage", + value: function addSublanguage(emitter, name) { + /** @type DataNode */ + var node = emitter.root; + node.kind = name; + node.sublanguage = true; + this.add(node); + } + }, { + key: "toHTML", + value: function toHTML() { + var renderer = new HTMLRenderer(this, this.options); + return renderer.value(); + } + }, { + key: "finalize", + value: function finalize() { + return true; + } + }]); + return TokenTreeEmitter; +}(TokenTree); +/** + * @param {string} value + * @returns {RegExp} + * */ +function escape(value) { + return new RegExp(value.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'), 'm'); +} + +/** + * @param {RegExp | string } re + * @returns {string} + */ +function source(re) { + if (!re) return null; + if (typeof re === "string") return re; + return re.source; +} + +/** + * @param {...(RegExp | string) } args + * @returns {string} + */ +function concat() { + for (var _len2 = arguments.length, args = new Array(_len2), _key3 = 0; _key3 < _len2; _key3++) { + args[_key3] = arguments[_key3]; + } + var joined = args.map(function (x) { + return source(x); + }).join(""); + return joined; +} + +/** + * Any of the passed expresssions may match + * + * Creates a huge this | this | that | that match + * @param {(RegExp | string)[] } args + * @returns {string} + */ +function either() { + for (var _len3 = arguments.length, args = new Array(_len3), _key4 = 0; _key4 < _len3; _key4++) { + args[_key4] = arguments[_key4]; + } + var joined = '(' + args.map(function (x) { + return source(x); + }).join("|") + ")"; + return joined; +} + +/** + * @param {RegExp} re + * @returns {number} + */ +function countMatchGroups(re) { + return new RegExp(re.toString() + '|').exec('').length - 1; +} + +/** + * Does lexeme start with a regular expression match at the beginning + * @param {RegExp} re + * @param {string} lexeme + */ +function startsWith(re, lexeme) { + var match = re && re.exec(lexeme); + return match && match.index === 0; +} + +// BACKREF_RE matches an open parenthesis or backreference. To avoid +// an incorrect parse, it additionally matches the following: +// - [...] elements, where the meaning of parentheses and escapes change +// - other escape sequences, so we do not misparse escape sequences as +// interesting elements +// - non-matching or lookahead parentheses, which do not capture. These +// follow the '(' with a '?'. +var BACKREF_RE = /\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./; + +// join logically computes regexps.join(separator), but fixes the +// backreferences so they continue to match. +// it also places each individual regular expression into it's own +// match group, keeping track of the sequencing of those match groups +// is currently an exercise for the caller. :-) +/** + * @param {(string | RegExp)[]} regexps + * @param {string} separator + * @returns {string} + */ +function join(regexps) { + var separator = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "|"; + var numCaptures = 0; + return regexps.map(function (regex) { + numCaptures += 1; + var offset = numCaptures; + var re = source(regex); + var out = ''; + while (re.length > 0) { + var match = BACKREF_RE.exec(re); + if (!match) { + out += re; + break; + } + out += re.substring(0, match.index); + re = re.substring(match.index + match[0].length); + if (match[0][0] === '\\' && match[1]) { + // Adjust the backreference. + out += '\\' + String(Number(match[1]) + offset); + } else { + out += match[0]; + if (match[0] === '(') { + numCaptures++; + } + } + } + return out; + }).map(function (re) { + return "(".concat(re, ")"); + }).join(separator); +} + +// Common regexps +var MATCH_NOTHING_RE = /\b\B/; +var IDENT_RE = '[a-zA-Z]\\w*'; +var UNDERSCORE_IDENT_RE = '[a-zA-Z_]\\w*'; +var NUMBER_RE = '\\b\\d+(\\.\\d+)?'; +var C_NUMBER_RE = '(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)'; // 0x..., 0..., decimal, float +var BINARY_NUMBER_RE = '\\b(0b[01]+)'; // 0b... +var RE_STARTERS_RE = '!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~'; + +/** +* @param { Partial & {binary?: string | RegExp} } opts +*/ +var SHEBANG = function SHEBANG() { + var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + var beginShebang = /^#![ ]*\//; + if (opts.binary) { + opts.begin = concat(beginShebang, /.*\b/, opts.binary, /\b.*/); + } + return inherit({ + className: 'meta', + begin: beginShebang, + end: /$/, + relevance: 0, + /** @type {ModeCallback} */ + "on:begin": function onBegin(m, resp) { + if (m.index !== 0) resp.ignoreMatch(); + } + }, opts); +}; + +// Common modes +var BACKSLASH_ESCAPE = { + begin: '\\\\[\\s\\S]', + relevance: 0 +}; +var APOS_STRING_MODE = { + className: 'string', + begin: '\'', + end: '\'', + illegal: '\\n', + contains: [BACKSLASH_ESCAPE] +}; +var QUOTE_STRING_MODE = { + className: 'string', + begin: '"', + end: '"', + illegal: '\\n', + contains: [BACKSLASH_ESCAPE] +}; +var PHRASAL_WORDS_MODE = { + begin: /\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/ +}; +/** + * Creates a comment mode + * + * @param {string | RegExp} begin + * @param {string | RegExp} end + * @param {Mode | {}} [modeOptions] + * @returns {Partial} + */ +var COMMENT = function COMMENT(begin, end) { + var modeOptions = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; + var mode = inherit({ + className: 'comment', + begin: begin, + end: end, + contains: [] + }, modeOptions); + mode.contains.push(PHRASAL_WORDS_MODE); + mode.contains.push({ + className: 'doctag', + begin: '(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):', + relevance: 0 + }); + return mode; +}; +var C_LINE_COMMENT_MODE = COMMENT('//', '$'); +var C_BLOCK_COMMENT_MODE = COMMENT('/\\*', '\\*/'); +var HASH_COMMENT_MODE = COMMENT('#', '$'); +var NUMBER_MODE = { + className: 'number', + begin: NUMBER_RE, + relevance: 0 +}; +var C_NUMBER_MODE = { + className: 'number', + begin: C_NUMBER_RE, + relevance: 0 +}; +var BINARY_NUMBER_MODE = { + className: 'number', + begin: BINARY_NUMBER_RE, + relevance: 0 +}; +var CSS_NUMBER_MODE = { + className: 'number', + begin: NUMBER_RE + '(' + '%|em|ex|ch|rem' + '|vw|vh|vmin|vmax' + '|cm|mm|in|pt|pc|px' + '|deg|grad|rad|turn' + '|s|ms' + '|Hz|kHz' + '|dpi|dpcm|dppx' + ')?', + relevance: 0 +}; +var REGEXP_MODE = { + // this outer rule makes sure we actually have a WHOLE regex and not simply + // an expression such as: + // + // 3 / something + // + // (which will then blow up when regex's `illegal` sees the newline) + begin: /(?=\/[^/\n]*\/)/, + contains: [{ + className: 'regexp', + begin: /\//, + end: /\/[gimuy]*/, + illegal: /\n/, + contains: [BACKSLASH_ESCAPE, { + begin: /\[/, + end: /\]/, + relevance: 0, + contains: [BACKSLASH_ESCAPE] + }] + }] +}; +var TITLE_MODE = { + className: 'title', + begin: IDENT_RE, + relevance: 0 +}; +var UNDERSCORE_TITLE_MODE = { + className: 'title', + begin: UNDERSCORE_IDENT_RE, + relevance: 0 +}; +var METHOD_GUARD = { + // excludes method names from keyword processing + begin: '\\.\\s*' + UNDERSCORE_IDENT_RE, + relevance: 0 +}; + +/** + * Adds end same as begin mechanics to a mode + * + * Your mode must include at least a single () match group as that first match + * group is what is used for comparison + * @param {Partial} mode + */ +var END_SAME_AS_BEGIN = function END_SAME_AS_BEGIN(mode) { + return Object.assign(mode, { + /** @type {ModeCallback} */ + 'on:begin': function onBegin(m, resp) { + resp.data._beginMatch = m[1]; + }, + /** @type {ModeCallback} */ + 'on:end': function onEnd(m, resp) { + if (resp.data._beginMatch !== m[1]) resp.ignoreMatch(); + } + }); +}; +var MODES = /*#__PURE__*/Object.freeze({ + __proto__: null, + MATCH_NOTHING_RE: MATCH_NOTHING_RE, + IDENT_RE: IDENT_RE, + UNDERSCORE_IDENT_RE: UNDERSCORE_IDENT_RE, + NUMBER_RE: NUMBER_RE, + C_NUMBER_RE: C_NUMBER_RE, + BINARY_NUMBER_RE: BINARY_NUMBER_RE, + RE_STARTERS_RE: RE_STARTERS_RE, + SHEBANG: SHEBANG, + BACKSLASH_ESCAPE: BACKSLASH_ESCAPE, + APOS_STRING_MODE: APOS_STRING_MODE, + QUOTE_STRING_MODE: QUOTE_STRING_MODE, + PHRASAL_WORDS_MODE: PHRASAL_WORDS_MODE, + COMMENT: COMMENT, + C_LINE_COMMENT_MODE: C_LINE_COMMENT_MODE, + C_BLOCK_COMMENT_MODE: C_BLOCK_COMMENT_MODE, + HASH_COMMENT_MODE: HASH_COMMENT_MODE, + NUMBER_MODE: NUMBER_MODE, + C_NUMBER_MODE: C_NUMBER_MODE, + BINARY_NUMBER_MODE: BINARY_NUMBER_MODE, + CSS_NUMBER_MODE: CSS_NUMBER_MODE, + REGEXP_MODE: REGEXP_MODE, + TITLE_MODE: TITLE_MODE, + UNDERSCORE_TITLE_MODE: UNDERSCORE_TITLE_MODE, + METHOD_GUARD: METHOD_GUARD, + END_SAME_AS_BEGIN: END_SAME_AS_BEGIN +}); + +// Grammar extensions / plugins +// See: https://github.com/highlightjs/highlight.js/issues/2833 + +// Grammar extensions allow "syntactic sugar" to be added to the grammar modes +// without requiring any underlying changes to the compiler internals. + +// `compileMatch` being the perfect small example of now allowing a grammar +// author to write `match` when they desire to match a single expression rather +// than being forced to use `begin`. The extension then just moves `match` into +// `begin` when it runs. Ie, no features have been added, but we've just made +// the experience of writing (and reading grammars) a little bit nicer. + +// ------ + +// TODO: We need negative look-behind support to do this properly +/** + * Skip a match if it has a preceding dot + * + * This is used for `beginKeywords` to prevent matching expressions such as + * `bob.keyword.do()`. The mode compiler automatically wires this up as a + * special _internal_ 'on:begin' callback for modes with `beginKeywords` + * @param {RegExpMatchArray} match + * @param {CallbackResponse} response + */ +function skipIfhasPrecedingDot(match, response) { + var before = match.input[match.index - 1]; + if (before === ".") { + response.ignoreMatch(); + } +} + +/** + * `beginKeywords` syntactic sugar + * @type {CompilerExt} + */ +function beginKeywords(mode, parent) { + if (!parent) return; + if (!mode.beginKeywords) return; + + // for languages with keywords that include non-word characters checking for + // a word boundary is not sufficient, so instead we check for a word boundary + // or whitespace - this does no harm in any case since our keyword engine + // doesn't allow spaces in keywords anyways and we still check for the boundary + // first + mode.begin = '\\b(' + mode.beginKeywords.split(' ').join('|') + ')(?!\\.)(?=\\b|\\s)'; + mode.__beforeBegin = skipIfhasPrecedingDot; + mode.keywords = mode.keywords || mode.beginKeywords; + delete mode.beginKeywords; + + // prevents double relevance, the keywords themselves provide + // relevance, the mode doesn't need to double it + // eslint-disable-next-line no-undefined + if (mode.relevance === undefined) mode.relevance = 0; +} + +/** + * Allow `illegal` to contain an array of illegal values + * @type {CompilerExt} + */ +function compileIllegal(mode, _parent) { + if (!Array.isArray(mode.illegal)) return; + mode.illegal = either.apply(void 0, _toConsumableArray(mode.illegal)); +} + +/** + * `match` to match a single expression for readability + * @type {CompilerExt} + */ +function compileMatch(mode, _parent) { + if (!mode.match) return; + if (mode.begin || mode.end) throw new Error("begin & end are not supported with match"); + mode.begin = mode.match; + delete mode.match; +} + +/** + * provides the default 1 relevance to all modes + * @type {CompilerExt} + */ +function compileRelevance(mode, _parent) { + // eslint-disable-next-line no-undefined + if (mode.relevance === undefined) mode.relevance = 1; +} + +// keywords that should have no default relevance value +var COMMON_KEYWORDS = ['of', 'and', 'for', 'in', 'not', 'or', 'if', 'then', 'parent', +// common variable name +'list', +// common variable name +'value' // common variable name +]; + +var DEFAULT_KEYWORD_CLASSNAME = "keyword"; + +/** + * Given raw keywords from a language definition, compile them. + * + * @param {string | Record | Array} rawKeywords + * @param {boolean} caseInsensitive + */ +function compileKeywords(rawKeywords, caseInsensitive) { + var className = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : DEFAULT_KEYWORD_CLASSNAME; + /** @type KeywordDict */ + var compiledKeywords = {}; + + // input can be a string of keywords, an array of keywords, or a object with + // named keys representing className (which can then point to a string or array) + if (typeof rawKeywords === 'string') { + compileList(className, rawKeywords.split(" ")); + } else if (Array.isArray(rawKeywords)) { + compileList(className, rawKeywords); + } else { + Object.keys(rawKeywords).forEach(function (className) { + // collapse all our objects back into the parent object + Object.assign(compiledKeywords, compileKeywords(rawKeywords[className], caseInsensitive, className)); + }); + } + return compiledKeywords; + + // --- + + /** + * Compiles an individual list of keywords + * + * Ex: "for if when while|5" + * + * @param {string} className + * @param {Array} keywordList + */ + function compileList(className, keywordList) { + if (caseInsensitive) { + keywordList = keywordList.map(function (x) { + return x.toLowerCase(); + }); + } + keywordList.forEach(function (keyword) { + var pair = keyword.split('|'); + compiledKeywords[pair[0]] = [className, scoreForKeyword(pair[0], pair[1])]; + }); + } +} + +/** + * Returns the proper score for a given keyword + * + * Also takes into account comment keywords, which will be scored 0 UNLESS + * another score has been manually assigned. + * @param {string} keyword + * @param {string} [providedScore] + */ +function scoreForKeyword(keyword, providedScore) { + // manual scores always win over common keywords + // so you can force a score of 1 if you really insist + if (providedScore) { + return Number(providedScore); + } + return commonKeyword(keyword) ? 0 : 1; +} + +/** + * Determines if a given keyword is common or not + * + * @param {string} keyword */ +function commonKeyword(keyword) { + return COMMON_KEYWORDS.includes(keyword.toLowerCase()); +} + +// compilation + +/** + * Compiles a language definition result + * + * Given the raw result of a language definition (Language), compiles this so + * that it is ready for highlighting code. + * @param {Language} language + * @param {{plugins: HLJSPlugin[]}} opts + * @returns {CompiledLanguage} + */ +function compileLanguage(language, _ref) { + var plugins = _ref.plugins; + /** + * Builds a regex with the case sensativility of the current language + * + * @param {RegExp | string} value + * @param {boolean} [global] + */ + function langRe(value, global) { + return new RegExp(source(value), 'm' + (language.case_insensitive ? 'i' : '') + (global ? 'g' : '')); + } + + /** + Stores multiple regular expressions and allows you to quickly search for + them all in a string simultaneously - returning the first match. It does + this by creating a huge (a|b|c) regex - each individual item wrapped with () + and joined by `|` - using match groups to track position. When a match is + found checking which position in the array has content allows us to figure + out which of the original regexes / match groups triggered the match. + The match object itself (the result of `Regex.exec`) is returned but also + enhanced by merging in any meta-data that was registered with the regex. + This is how we keep track of which mode matched, and what type of rule + (`illegal`, `begin`, end, etc). + */ + var MultiRegex = /*#__PURE__*/function () { + "use strict"; + + function MultiRegex() { + _classCallCheck(this, MultiRegex); + this.matchIndexes = {}; + // @ts-ignore + this.regexes = []; + this.matchAt = 1; + this.position = 0; + } + + // @ts-ignore + _createClass(MultiRegex, [{ + key: "addRule", + value: function addRule(re, opts) { + opts.position = this.position++; + // @ts-ignore + this.matchIndexes[this.matchAt] = opts; + this.regexes.push([opts, re]); + this.matchAt += countMatchGroups(re) + 1; + } + }, { + key: "compile", + value: function compile() { + if (this.regexes.length === 0) { + // avoids the need to check length every time exec is called + // @ts-ignore + this.exec = function () { + return null; + }; + } + var terminators = this.regexes.map(function (el) { + return el[1]; + }); + this.matcherRe = langRe(join(terminators), true); + this.lastIndex = 0; + } + + /** @param {string} s */ + }, { + key: "exec", + value: function exec(s) { + this.matcherRe.lastIndex = this.lastIndex; + var match = this.matcherRe.exec(s); + if (!match) { + return null; + } + + // eslint-disable-next-line no-undefined + var i = match.findIndex(function (el, i) { + return i > 0 && el !== undefined; + }); + // @ts-ignore + var matchData = this.matchIndexes[i]; + // trim off any earlier non-relevant match groups (ie, the other regex + // match groups that make up the multi-matcher) + match.splice(0, i); + return Object.assign(match, matchData); + } + }]); + return MultiRegex; + }(); + /* + Created to solve the key deficiently with MultiRegex - there is no way to + test for multiple matches at a single location. Why would we need to do + that? In the future a more dynamic engine will allow certain matches to be + ignored. An example: if we matched say the 3rd regex in a large group but + decided to ignore it - we'd need to started testing again at the 4th + regex... but MultiRegex itself gives us no real way to do that. + So what this class creates MultiRegexs on the fly for whatever search + position they are needed. + NOTE: These additional MultiRegex objects are created dynamically. For most + grammars most of the time we will never actually need anything more than the + first MultiRegex - so this shouldn't have too much overhead. + Say this is our search group, and we match regex3, but wish to ignore it. + regex1 | regex2 | regex3 | regex4 | regex5 ' ie, startAt = 0 + What we need is a new MultiRegex that only includes the remaining + possibilities: + regex4 | regex5 ' ie, startAt = 3 + This class wraps all that complexity up in a simple API... `startAt` decides + where in the array of expressions to start doing the matching. It + auto-increments, so if a match is found at position 2, then startAt will be + set to 3. If the end is reached startAt will return to 0. + MOST of the time the parser will be setting startAt manually to 0. + */ + var ResumableMultiRegex = /*#__PURE__*/function () { + "use strict"; + + function ResumableMultiRegex() { + _classCallCheck(this, ResumableMultiRegex); + // @ts-ignore + this.rules = []; + // @ts-ignore + this.multiRegexes = []; + this.count = 0; + this.lastIndex = 0; + this.regexIndex = 0; + } + + // @ts-ignore + _createClass(ResumableMultiRegex, [{ + key: "getMatcher", + value: function getMatcher(index) { + if (this.multiRegexes[index]) return this.multiRegexes[index]; + var matcher = new MultiRegex(); + this.rules.slice(index).forEach(function (_ref2) { + var _ref3 = _slicedToArray(_ref2, 2), + re = _ref3[0], + opts = _ref3[1]; + return matcher.addRule(re, opts); + }); + matcher.compile(); + this.multiRegexes[index] = matcher; + return matcher; + } + }, { + key: "resumingScanAtSamePosition", + value: function resumingScanAtSamePosition() { + return this.regexIndex !== 0; + } + }, { + key: "considerAll", + value: function considerAll() { + this.regexIndex = 0; + } + + // @ts-ignore + }, { + key: "addRule", + value: function addRule(re, opts) { + this.rules.push([re, opts]); + if (opts.type === "begin") this.count++; + } + + /** @param {string} s */ + }, { + key: "exec", + value: function exec(s) { + var m = this.getMatcher(this.regexIndex); + m.lastIndex = this.lastIndex; + var result = m.exec(s); + + // The following is because we have no easy way to say "resume scanning at the + // existing position but also skip the current rule ONLY". What happens is + // all prior rules are also skipped which can result in matching the wrong + // thing. Example of matching "booger": + + // our matcher is [string, "booger", number] + // + // ....booger.... + + // if "booger" is ignored then we'd really need a regex to scan from the + // SAME position for only: [string, number] but ignoring "booger" (if it + // was the first match), a simple resume would scan ahead who knows how + // far looking only for "number", ignoring potential string matches (or + // future "booger" matches that might be valid.) + + // So what we do: We execute two matchers, one resuming at the same + // position, but the second full matcher starting at the position after: + + // /--- resume first regex match here (for [number]) + // |/---- full match here for [string, "booger", number] + // vv + // ....booger.... + + // Which ever results in a match first is then used. So this 3-4 step + // process essentially allows us to say "match at this position, excluding + // a prior rule that was ignored". + // + // 1. Match "booger" first, ignore. Also proves that [string] does non match. + // 2. Resume matching for [number] + // 3. Match at index + 1 for [string, "booger", number] + // 4. If #2 and #3 result in matches, which came first? + if (this.resumingScanAtSamePosition()) { + if (result && result.index === this.lastIndex) ;else { + // use the second matcher result + var m2 = this.getMatcher(0); + m2.lastIndex = this.lastIndex + 1; + result = m2.exec(s); + } + } + if (result) { + this.regexIndex += result.position + 1; + if (this.regexIndex === this.count) { + // wrap-around to considering all matches again + this.considerAll(); + } + } + return result; + } + }]); + return ResumableMultiRegex; + }(); + /** + * Given a mode, builds a huge ResumableMultiRegex that can be used to walk + * the content and find matches. + * + * @param {CompiledMode} mode + * @returns {ResumableMultiRegex} + */ + function buildModeRegex(mode) { + var mm = new ResumableMultiRegex(); + mode.contains.forEach(function (term) { + return mm.addRule(term.begin, { + rule: term, + type: "begin" + }); + }); + if (mode.terminatorEnd) { + mm.addRule(mode.terminatorEnd, { + type: "end" + }); + } + if (mode.illegal) { + mm.addRule(mode.illegal, { + type: "illegal" + }); + } + return mm; + } + + /** skip vs abort vs ignore + * + * @skip - The mode is still entered and exited normally (and contains rules apply), + * but all content is held and added to the parent buffer rather than being + * output when the mode ends. Mostly used with `sublanguage` to build up + * a single large buffer than can be parsed by sublanguage. + * + * - The mode begin ands ends normally. + * - Content matched is added to the parent mode buffer. + * - The parser cursor is moved forward normally. + * + * @abort - A hack placeholder until we have ignore. Aborts the mode (as if it + * never matched) but DOES NOT continue to match subsequent `contains` + * modes. Abort is bad/suboptimal because it can result in modes + * farther down not getting applied because an earlier rule eats the + * content but then aborts. + * + * - The mode does not begin. + * - Content matched by `begin` is added to the mode buffer. + * - The parser cursor is moved forward accordingly. + * + * @ignore - Ignores the mode (as if it never matched) and continues to match any + * subsequent `contains` modes. Ignore isn't technically possible with + * the current parser implementation. + * + * - The mode does not begin. + * - Content matched by `begin` is ignored. + * - The parser cursor is not moved forward. + */ + + /** + * Compiles an individual mode + * + * This can raise an error if the mode contains certain detectable known logic + * issues. + * @param {Mode} mode + * @param {CompiledMode | null} [parent] + * @returns {CompiledMode | never} + */ + function compileMode(mode, parent) { + var _ref4; + var cmode = /** @type CompiledMode */mode; + if (mode.isCompiled) return cmode; + [ + // do this early so compiler extensions generally don't have to worry about + // the distinction between match/begin + compileMatch].forEach(function (ext) { + return ext(mode, parent); + }); + language.compilerExtensions.forEach(function (ext) { + return ext(mode, parent); + }); + + // __beforeBegin is considered private API, internal use only + mode.__beforeBegin = null; + [beginKeywords, + // do this later so compiler extensions that come earlier have access to the + // raw array if they wanted to perhaps manipulate it, etc. + compileIllegal, + // default to 1 relevance if not specified + compileRelevance].forEach(function (ext) { + return ext(mode, parent); + }); + mode.isCompiled = true; + var keywordPattern = null; + if (typeof mode.keywords === "object") { + keywordPattern = mode.keywords.$pattern; + delete mode.keywords.$pattern; + } + if (mode.keywords) { + mode.keywords = compileKeywords(mode.keywords, language.case_insensitive); + } + + // both are not allowed + if (mode.lexemes && keywordPattern) { + throw new Error("ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) "); + } + + // `mode.lexemes` was the old standard before we added and now recommend + // using `keywords.$pattern` to pass the keyword pattern + keywordPattern = keywordPattern || mode.lexemes || /\w+/; + cmode.keywordPatternRe = langRe(keywordPattern, true); + if (parent) { + if (!mode.begin) mode.begin = /\B|\b/; + cmode.beginRe = langRe(mode.begin); + if (mode.endSameAsBegin) mode.end = mode.begin; + if (!mode.end && !mode.endsWithParent) mode.end = /\B|\b/; + if (mode.end) cmode.endRe = langRe(mode.end); + cmode.terminatorEnd = source(mode.end) || ''; + if (mode.endsWithParent && parent.terminatorEnd) { + cmode.terminatorEnd += (mode.end ? '|' : '') + parent.terminatorEnd; + } + } + if (mode.illegal) cmode.illegalRe = langRe( /** @type {RegExp | string} */mode.illegal); + if (!mode.contains) mode.contains = []; + mode.contains = (_ref4 = []).concat.apply(_ref4, _toConsumableArray(mode.contains.map(function (c) { + return expandOrCloneMode(c === 'self' ? mode : c); + }))); + mode.contains.forEach(function (c) { + compileMode( /** @type Mode */c, cmode); + }); + if (mode.starts) { + compileMode(mode.starts, parent); + } + cmode.matcher = buildModeRegex(cmode); + return cmode; + } + if (!language.compilerExtensions) language.compilerExtensions = []; + + // self is not valid at the top-level + if (language.contains && language.contains.includes('self')) { + throw new Error("ERR: contains `self` is not supported at the top-level of a language. See documentation."); + } + + // we need a null object, which inherit will guarantee + language.classNameAliases = inherit(language.classNameAliases || {}); + return compileMode( /** @type Mode */language); +} + +/** + * Determines if a mode has a dependency on it's parent or not + * + * If a mode does have a parent dependency then often we need to clone it if + * it's used in multiple places so that each copy points to the correct parent, + * where-as modes without a parent can often safely be re-used at the bottom of + * a mode chain. + * + * @param {Mode | null} mode + * @returns {boolean} - is there a dependency on the parent? + * */ +function dependencyOnParent(mode) { + if (!mode) return false; + return mode.endsWithParent || dependencyOnParent(mode.starts); +} + +/** + * Expands a mode or clones it if necessary + * + * This is necessary for modes with parental dependenceis (see notes on + * `dependencyOnParent`) and for nodes that have `variants` - which must then be + * exploded into their own individual modes at compile time. + * + * @param {Mode} mode + * @returns {Mode | Mode[]} + * */ +function expandOrCloneMode(mode) { + if (mode.variants && !mode.cachedVariants) { + mode.cachedVariants = mode.variants.map(function (variant) { + return inherit(mode, { + variants: null + }, variant); + }); + } + + // EXPAND + // if we have variants then essentially "replace" the mode with the variants + // this happens in compileMode, where this function is called from + if (mode.cachedVariants) { + return mode.cachedVariants; + } + + // CLONE + // if we have dependencies on parents then we need a unique + // instance of ourselves, so we can be reused with many + // different parents without issue + if (dependencyOnParent(mode)) { + return inherit(mode, { + starts: mode.starts ? inherit(mode.starts) : null + }); + } + if (Object.isFrozen(mode)) { + return inherit(mode); + } + + // no special dependency issues, just return ourselves + return mode; +} +var version = "10.7.3"; + +// @ts-nocheck + +function hasValueOrEmptyAttribute(value) { + return Boolean(value || value === ""); +} +function BuildVuePlugin(hljs) { + var Component = { + props: ["language", "code", "autodetect"], + data: function data() { + return { + detectedLanguage: "", + unknownLanguage: false + }; + }, + computed: { + className: function className() { + if (this.unknownLanguage) return ""; + return "hljs " + this.detectedLanguage; + }, + highlighted: function highlighted() { + // no idea what language to use, return raw code + if (!this.autoDetect && !hljs.getLanguage(this.language)) { + console.warn("The language \"".concat(this.language, "\" you specified could not be found.")); + this.unknownLanguage = true; + return escapeHTML(this.code); + } + var result = {}; + if (this.autoDetect) { + result = hljs.highlightAuto(this.code); + this.detectedLanguage = result.language; + } else { + result = hljs.highlight(this.language, this.code, this.ignoreIllegals); + this.detectedLanguage = this.language; + } + return result.value; + }, + autoDetect: function autoDetect() { + return !this.language || hasValueOrEmptyAttribute(this.autodetect); + }, + ignoreIllegals: function ignoreIllegals() { + return true; + } + }, + // this avoids needing to use a whole Vue compilation pipeline just + // to build Highlight.js + render: function render(createElement) { + return createElement("pre", {}, [createElement("code", { + class: this.className, + domProps: { + innerHTML: this.highlighted + } + })]); + } // template: `
    ` + }; + var VuePlugin = { + install: function install(Vue) { + Vue.component('highlightjs', Component); + } + }; + return { + Component: Component, + VuePlugin: VuePlugin + }; +} + +/* plugin itself */ + +/** @type {HLJSPlugin} */ +var mergeHTMLPlugin = { + "after:highlightElement": function afterHighlightElement(_ref5) { + var el = _ref5.el, + result = _ref5.result, + text = _ref5.text; + var originalStream = nodeStream(el); + if (!originalStream.length) return; + var resultNode = document.createElement('div'); + resultNode.innerHTML = result.value; + result.value = mergeStreams(originalStream, nodeStream(resultNode), text); + } +}; + +/* Stream merging support functions */ + +/** + * @typedef Event + * @property {'start'|'stop'} event + * @property {number} offset + * @property {Node} node + */ + +/** + * @param {Node} node + */ +function tag(node) { + return node.nodeName.toLowerCase(); +} + +/** + * @param {Node} node + */ +function nodeStream(node) { + /** @type Event[] */ + var result = []; + (function _nodeStream(node, offset) { + for (var child = node.firstChild; child; child = child.nextSibling) { + if (child.nodeType === 3) { + offset += child.nodeValue.length; + } else if (child.nodeType === 1) { + result.push({ + event: 'start', + offset: offset, + node: child + }); + offset = _nodeStream(child, offset); + // Prevent void elements from having an end tag that would actually + // double them in the output. There are more void elements in HTML + // but we list only those realistically expected in code display. + if (!tag(child).match(/br|hr|img|input/)) { + result.push({ + event: 'stop', + offset: offset, + node: child + }); + } + } + } + return offset; + })(node, 0); + return result; +} + +/** + * @param {any} original - the original stream + * @param {any} highlighted - stream of the highlighted source + * @param {string} value - the original source itself + */ +function mergeStreams(original, highlighted, value) { + var processed = 0; + var result = ''; + var nodeStack = []; + function selectStream() { + if (!original.length || !highlighted.length) { + return original.length ? original : highlighted; + } + if (original[0].offset !== highlighted[0].offset) { + return original[0].offset < highlighted[0].offset ? original : highlighted; + } + + /* + To avoid starting the stream just before it should stop the order is + ensured that original always starts first and closes last: + if (event1 == 'start' && event2 == 'start') + return original; + if (event1 == 'start' && event2 == 'stop') + return highlighted; + if (event1 == 'stop' && event2 == 'start') + return original; + if (event1 == 'stop' && event2 == 'stop') + return highlighted; + ... which is collapsed to: + */ + return highlighted[0].event === 'start' ? original : highlighted; + } + + /** + * @param {Node} node + */ + function open(node) { + /** @param {Attr} attr */ + function attributeString(attr) { + return ' ' + attr.nodeName + '="' + escapeHTML(attr.value) + '"'; + } + // @ts-ignore + result += '<' + tag(node) + [].map.call(node.attributes, attributeString).join('') + '>'; + } + + /** + * @param {Node} node + */ + function close(node) { + result += ''; + } + + /** + * @param {Event} event + */ + function render(event) { + (event.event === 'start' ? open : close)(event.node); + } + while (original.length || highlighted.length) { + var stream = selectStream(); + result += escapeHTML(value.substring(processed, stream[0].offset)); + processed = stream[0].offset; + if (stream === original) { + /* + On any opening or closing tag of the original markup we first close + the entire highlighted node stack, then render the original tag along + with all the following original tags at the same offset and then + reopen all the tags on the highlighted stack. + */ + nodeStack.reverse().forEach(close); + do { + render(stream.splice(0, 1)[0]); + stream = selectStream(); + } while (stream === original && stream.length && stream[0].offset === processed); + nodeStack.reverse().forEach(open); + } else { + if (stream[0].event === 'start') { + nodeStack.push(stream[0].node); + } else { + nodeStack.pop(); + } + render(stream.splice(0, 1)[0]); + } + } + return result + escapeHTML(value.substr(processed)); +} + +/* + +For the reasoning behind this please see: +https://github.com/highlightjs/highlight.js/issues/2880#issuecomment-747275419 + +*/ + +/** + * @type {Record} + */ +var seenDeprecations = {}; + +/** + * @param {string} message + */ +var error = function error(message) { + console.error(message); +}; + +/** + * @param {string} message + * @param {any} args + */ +var warn = function warn(message) { + var _console; + for (var _len4 = arguments.length, args = new Array(_len4 > 1 ? _len4 - 1 : 0), _key5 = 1; _key5 < _len4; _key5++) { + args[_key5 - 1] = arguments[_key5]; + } + (_console = console).log.apply(_console, ["WARN: ".concat(message)].concat(args)); +}; + +/** + * @param {string} version + * @param {string} message + */ +var deprecated = function deprecated(version, message) { + if (seenDeprecations["".concat(version, "/").concat(message)]) return; + console.log("Deprecated as of ".concat(version, ". ").concat(message)); + seenDeprecations["".concat(version, "/").concat(message)] = true; +}; + +/* +Syntax highlighting with language autodetection. +https://highlightjs.org/ +*/ + +var escape$1 = escapeHTML; +var inherit$1 = inherit; +var NO_MATCH = Symbol("nomatch"); + +/** + * @param {any} hljs - object that is extended (legacy) + * @returns {HLJSApi} + */ +var HLJS = function HLJS(hljs) { + // Global internal variables used within the highlight.js library. + /** @type {Record} */ + var languages = Object.create(null); + /** @type {Record} */ + var aliases = Object.create(null); + /** @type {HLJSPlugin[]} */ + var plugins = []; + + // safe/production mode - swallows more errors, tries to keep running + // even if a single syntax or parse hits a fatal error + var SAFE_MODE = true; + var fixMarkupRe = /(^(<[^>]+>|\t|)+|\n)/gm; + var LANGUAGE_NOT_FOUND = "Could not find the language '{}', did you forget to load/include a language module?"; + /** @type {Language} */ + var PLAINTEXT_LANGUAGE = { + disableAutodetect: true, + name: 'Plain text', + contains: [] + }; + + // Global options used when within external APIs. This is modified when + // calling the `hljs.configure` function. + /** @type HLJSOptions */ + var options = { + noHighlightRe: /^(no-?highlight)$/i, + languageDetectRe: /\blang(?:uage)?-([\w-]+)\b/i, + classPrefix: 'hljs-', + tabReplace: null, + useBR: false, + languages: null, + // beta configuration options, subject to change, welcome to discuss + // https://github.com/highlightjs/highlight.js/issues/1086 + __emitter: TokenTreeEmitter + }; + + /* Utility functions */ + + /** + * Tests a language name to see if highlighting should be skipped + * @param {string} languageName + */ + function shouldNotHighlight(languageName) { + return options.noHighlightRe.test(languageName); + } + + /** + * @param {HighlightedHTMLElement} block - the HTML element to determine language for + */ + function blockLanguage(block) { + var classes = block.className + ' '; + classes += block.parentNode ? block.parentNode.className : ''; + + // language-* takes precedence over non-prefixed class names. + var match = options.languageDetectRe.exec(classes); + if (match) { + var language = getLanguage(match[1]); + if (!language) { + warn(LANGUAGE_NOT_FOUND.replace("{}", match[1])); + warn("Falling back to no-highlight mode for this block.", block); + } + return language ? match[1] : 'no-highlight'; + } + return classes.split(/\s+/).find(function (_class) { + return shouldNotHighlight(_class) || getLanguage(_class); + }); + } + + /** + * Core highlighting function. + * + * OLD API + * highlight(lang, code, ignoreIllegals, continuation) + * + * NEW API + * highlight(code, {lang, ignoreIllegals}) + * + * @param {string} codeOrlanguageName - the language to use for highlighting + * @param {string | HighlightOptions} optionsOrCode - the code to highlight + * @param {boolean} [ignoreIllegals] - whether to ignore illegal matches, default is to bail + * @param {CompiledMode} [continuation] - current continuation mode, if any + * + * @returns {HighlightResult} Result - an object that represents the result + * @property {string} language - the language name + * @property {number} relevance - the relevance score + * @property {string} value - the highlighted HTML code + * @property {string} code - the original raw code + * @property {CompiledMode} top - top of the current mode stack + * @property {boolean} illegal - indicates whether any illegal matches were found + */ + function highlight(codeOrlanguageName, optionsOrCode, ignoreIllegals, continuation) { + var code = ""; + var languageName = ""; + if (typeof optionsOrCode === "object") { + code = codeOrlanguageName; + ignoreIllegals = optionsOrCode.ignoreIllegals; + languageName = optionsOrCode.language; + // continuation not supported at all via the new API + // eslint-disable-next-line no-undefined + continuation = undefined; + } else { + // old API + deprecated("10.7.0", "highlight(lang, code, ...args) has been deprecated."); + deprecated("10.7.0", "Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277"); + languageName = codeOrlanguageName; + code = optionsOrCode; + } + + /** @type {BeforeHighlightContext} */ + var context = { + code: code, + language: languageName + }; + // the plugin can change the desired language or the code to be highlighted + // just be changing the object it was passed + fire("before:highlight", context); + + // a before plugin can usurp the result completely by providing it's own + // in which case we don't even need to call highlight + var result = context.result ? context.result : _highlight(context.language, context.code, ignoreIllegals, continuation); + result.code = context.code; + // the plugin can change anything in result to suite it + fire("after:highlight", result); + return result; + } + + /** + * private highlight that's used internally and does not fire callbacks + * + * @param {string} languageName - the language to use for highlighting + * @param {string} codeToHighlight - the code to highlight + * @param {boolean?} [ignoreIllegals] - whether to ignore illegal matches, default is to bail + * @param {CompiledMode?} [continuation] - current continuation mode, if any + * @returns {HighlightResult} - result of the highlight operation + */ + function _highlight(languageName, codeToHighlight, ignoreIllegals, continuation) { + /** + * Return keyword data if a match is a keyword + * @param {CompiledMode} mode - current mode + * @param {RegExpMatchArray} match - regexp match data + * @returns {KeywordData | false} + */ + function keywordData(mode, match) { + var matchText = language.case_insensitive ? match[0].toLowerCase() : match[0]; + return Object.prototype.hasOwnProperty.call(mode.keywords, matchText) && mode.keywords[matchText]; + } + function processKeywords() { + if (!top.keywords) { + emitter.addText(modeBuffer); + return; + } + var lastIndex = 0; + top.keywordPatternRe.lastIndex = 0; + var match = top.keywordPatternRe.exec(modeBuffer); + var buf = ""; + while (match) { + buf += modeBuffer.substring(lastIndex, match.index); + var data = keywordData(top, match); + if (data) { + var _data = _slicedToArray(data, 2), + kind = _data[0], + keywordRelevance = _data[1]; + emitter.addText(buf); + buf = ""; + relevance += keywordRelevance; + if (kind.startsWith("_")) { + // _ implied for relevance only, do not highlight + // by applying a class name + buf += match[0]; + } else { + var cssClass = language.classNameAliases[kind] || kind; + emitter.addKeyword(match[0], cssClass); + } + } else { + buf += match[0]; + } + lastIndex = top.keywordPatternRe.lastIndex; + match = top.keywordPatternRe.exec(modeBuffer); + } + buf += modeBuffer.substr(lastIndex); + emitter.addText(buf); + } + function processSubLanguage() { + if (modeBuffer === "") return; + /** @type HighlightResult */ + var result = null; + if (typeof top.subLanguage === 'string') { + if (!languages[top.subLanguage]) { + emitter.addText(modeBuffer); + return; + } + result = _highlight(top.subLanguage, modeBuffer, true, continuations[top.subLanguage]); + continuations[top.subLanguage] = /** @type {CompiledMode} */result.top; + } else { + result = highlightAuto(modeBuffer, top.subLanguage.length ? top.subLanguage : null); + } + + // Counting embedded language score towards the host language may be disabled + // with zeroing the containing mode relevance. Use case in point is Markdown that + // allows XML everywhere and makes every XML snippet to have a much larger Markdown + // score. + if (top.relevance > 0) { + relevance += result.relevance; + } + emitter.addSublanguage(result.emitter, result.language); + } + function processBuffer() { + if (top.subLanguage != null) { + processSubLanguage(); + } else { + processKeywords(); + } + modeBuffer = ''; + } + + /** + * @param {Mode} mode - new mode to start + */ + function startNewMode(mode) { + if (mode.className) { + emitter.openNode(language.classNameAliases[mode.className] || mode.className); + } + top = Object.create(mode, { + parent: { + value: top + } + }); + return top; + } + + /** + * @param {CompiledMode } mode - the mode to potentially end + * @param {RegExpMatchArray} match - the latest match + * @param {string} matchPlusRemainder - match plus remainder of content + * @returns {CompiledMode | void} - the next mode, or if void continue on in current mode + */ + function endOfMode(mode, match, matchPlusRemainder) { + var matched = startsWith(mode.endRe, matchPlusRemainder); + if (matched) { + if (mode["on:end"]) { + var resp = new Response(mode); + mode["on:end"](match, resp); + if (resp.isMatchIgnored) matched = false; + } + if (matched) { + while (mode.endsParent && mode.parent) { + mode = mode.parent; + } + return mode; + } + } + // even if on:end fires an `ignore` it's still possible + // that we might trigger the end node because of a parent mode + if (mode.endsWithParent) { + return endOfMode(mode.parent, match, matchPlusRemainder); + } + } + + /** + * Handle matching but then ignoring a sequence of text + * + * @param {string} lexeme - string containing full match text + */ + function doIgnore(lexeme) { + if (top.matcher.regexIndex === 0) { + // no more regexs to potentially match here, so we move the cursor forward one + // space + modeBuffer += lexeme[0]; + return 1; + } else { + // no need to move the cursor, we still have additional regexes to try and + // match at this very spot + resumeScanAtSamePosition = true; + return 0; + } + } + + /** + * Handle the start of a new potential mode match + * + * @param {EnhancedMatch} match - the current match + * @returns {number} how far to advance the parse cursor + */ + function doBeginMatch(match) { + var lexeme = match[0]; + var newMode = match.rule; + var resp = new Response(newMode); + // first internal before callbacks, then the public ones + var beforeCallbacks = [newMode.__beforeBegin, newMode["on:begin"]]; + for (var _i = 0, _beforeCallbacks = beforeCallbacks; _i < _beforeCallbacks.length; _i++) { + var cb = _beforeCallbacks[_i]; + if (!cb) continue; + cb(match, resp); + if (resp.isMatchIgnored) return doIgnore(lexeme); + } + if (newMode && newMode.endSameAsBegin) { + newMode.endRe = escape(lexeme); + } + if (newMode.skip) { + modeBuffer += lexeme; + } else { + if (newMode.excludeBegin) { + modeBuffer += lexeme; + } + processBuffer(); + if (!newMode.returnBegin && !newMode.excludeBegin) { + modeBuffer = lexeme; + } + } + startNewMode(newMode); + // if (mode["after:begin"]) { + // let resp = new Response(mode); + // mode["after:begin"](match, resp); + // } + return newMode.returnBegin ? 0 : lexeme.length; + } + + /** + * Handle the potential end of mode + * + * @param {RegExpMatchArray} match - the current match + */ + function doEndMatch(match) { + var lexeme = match[0]; + var matchPlusRemainder = codeToHighlight.substr(match.index); + var endMode = endOfMode(top, match, matchPlusRemainder); + if (!endMode) { + return NO_MATCH; + } + var origin = top; + if (origin.skip) { + modeBuffer += lexeme; + } else { + if (!(origin.returnEnd || origin.excludeEnd)) { + modeBuffer += lexeme; + } + processBuffer(); + if (origin.excludeEnd) { + modeBuffer = lexeme; + } + } + do { + if (top.className) { + emitter.closeNode(); + } + if (!top.skip && !top.subLanguage) { + relevance += top.relevance; + } + top = top.parent; + } while (top !== endMode.parent); + if (endMode.starts) { + if (endMode.endSameAsBegin) { + endMode.starts.endRe = endMode.endRe; + } + startNewMode(endMode.starts); + } + return origin.returnEnd ? 0 : lexeme.length; + } + function processContinuations() { + var list = []; + for (var current = top; current !== language; current = current.parent) { + if (current.className) { + list.unshift(current.className); + } + } + list.forEach(function (item) { + return emitter.openNode(item); + }); + } + + /** @type {{type?: MatchType, index?: number, rule?: Mode}}} */ + var lastMatch = {}; + + /** + * Process an individual match + * + * @param {string} textBeforeMatch - text preceeding the match (since the last match) + * @param {EnhancedMatch} [match] - the match itself + */ + function processLexeme(textBeforeMatch, match) { + var lexeme = match && match[0]; + + // add non-matched text to the current mode buffer + modeBuffer += textBeforeMatch; + if (lexeme == null) { + processBuffer(); + return 0; + } + + // we've found a 0 width match and we're stuck, so we need to advance + // this happens when we have badly behaved rules that have optional matchers to the degree that + // sometimes they can end up matching nothing at all + // Ref: https://github.com/highlightjs/highlight.js/issues/2140 + if (lastMatch.type === "begin" && match.type === "end" && lastMatch.index === match.index && lexeme === "") { + // spit the "skipped" character that our regex choked on back into the output sequence + modeBuffer += codeToHighlight.slice(match.index, match.index + 1); + if (!SAFE_MODE) { + /** @type {AnnotatedError} */ + var err = new Error('0 width match regex'); + err.languageName = languageName; + err.badRule = lastMatch.rule; + throw err; + } + return 1; + } + lastMatch = match; + if (match.type === "begin") { + return doBeginMatch(match); + } else if (match.type === "illegal" && !ignoreIllegals) { + // illegal match, we do not continue processing + /** @type {AnnotatedError} */ + var _err = new Error('Illegal lexeme "' + lexeme + '" for mode "' + (top.className || '') + '"'); + _err.mode = top; + throw _err; + } else if (match.type === "end") { + var processed = doEndMatch(match); + if (processed !== NO_MATCH) { + return processed; + } + } + + // edge case for when illegal matches $ (end of line) which is technically + // a 0 width match but not a begin/end match so it's not caught by the + // first handler (when ignoreIllegals is true) + if (match.type === "illegal" && lexeme === "") { + // advance so we aren't stuck in an infinite loop + return 1; + } + + // infinite loops are BAD, this is a last ditch catch all. if we have a + // decent number of iterations yet our index (cursor position in our + // parsing) still 3x behind our index then something is very wrong + // so we bail + if (iterations > 100000 && iterations > match.index * 3) { + var _err2 = new Error('potential infinite loop, way more iterations than matches'); + throw _err2; + } + + /* + Why might be find ourselves here? Only one occasion now. An end match that was + triggered but could not be completed. When might this happen? When an `endSameasBegin` + rule sets the end rule to a specific match. Since the overall mode termination rule that's + being used to scan the text isn't recompiled that means that any match that LOOKS like + the end (but is not, because it is not an exact match to the beginning) will + end up here. A definite end match, but when `doEndMatch` tries to "reapply" + the end rule and fails to match, we wind up here, and just silently ignore the end. + This causes no real harm other than stopping a few times too many. + */ + + modeBuffer += lexeme; + return lexeme.length; + } + var language = getLanguage(languageName); + if (!language) { + error(LANGUAGE_NOT_FOUND.replace("{}", languageName)); + throw new Error('Unknown language: "' + languageName + '"'); + } + var md = compileLanguage(language, { + plugins: plugins + }); + var result = ''; + /** @type {CompiledMode} */ + var top = continuation || md; + /** @type Record */ + var continuations = {}; // keep continuations for sub-languages + var emitter = new options.__emitter(options); + processContinuations(); + var modeBuffer = ''; + var relevance = 0; + var index = 0; + var iterations = 0; + var resumeScanAtSamePosition = false; + try { + top.matcher.considerAll(); + for (;;) { + iterations++; + if (resumeScanAtSamePosition) { + // only regexes not matched previously will now be + // considered for a potential match + resumeScanAtSamePosition = false; + } else { + top.matcher.considerAll(); + } + top.matcher.lastIndex = index; + var match = top.matcher.exec(codeToHighlight); + // console.log("match", match[0], match.rule && match.rule.begin) + + if (!match) break; + var beforeMatch = codeToHighlight.substring(index, match.index); + var processedCount = processLexeme(beforeMatch, match); + index = match.index + processedCount; + } + processLexeme(codeToHighlight.substr(index)); + emitter.closeAllNodes(); + emitter.finalize(); + result = emitter.toHTML(); + return { + // avoid possible breakage with v10 clients expecting + // this to always be an integer + relevance: Math.floor(relevance), + value: result, + language: languageName, + illegal: false, + emitter: emitter, + top: top + }; + } catch (err) { + if (err.message && err.message.includes('Illegal')) { + return { + illegal: true, + illegalBy: { + msg: err.message, + context: codeToHighlight.slice(index - 100, index + 100), + mode: err.mode + }, + sofar: result, + relevance: 0, + value: escape$1(codeToHighlight), + emitter: emitter + }; + } else if (SAFE_MODE) { + return { + illegal: false, + relevance: 0, + value: escape$1(codeToHighlight), + emitter: emitter, + language: languageName, + top: top, + errorRaised: err + }; + } else { + throw err; + } + } + } + + /** + * returns a valid highlight result, without actually doing any actual work, + * auto highlight starts with this and it's possible for small snippets that + * auto-detection may not find a better match + * @param {string} code + * @returns {HighlightResult} + */ + function justTextHighlightResult(code) { + var result = { + relevance: 0, + emitter: new options.__emitter(options), + value: escape$1(code), + illegal: false, + top: PLAINTEXT_LANGUAGE + }; + result.emitter.addText(code); + return result; + } + + /** + Highlighting with language detection. Accepts a string with the code to + highlight. Returns an object with the following properties: + - language (detected language) + - relevance (int) + - value (an HTML string with highlighting markup) + - second_best (object with the same structure for second-best heuristically + detected language, may be absent) + @param {string} code + @param {Array} [languageSubset] + @returns {AutoHighlightResult} + */ + function highlightAuto(code, languageSubset) { + languageSubset = languageSubset || options.languages || Object.keys(languages); + var plaintext = justTextHighlightResult(code); + var results = languageSubset.filter(getLanguage).filter(autoDetection).map(function (name) { + return _highlight(name, code, false); + }); + results.unshift(plaintext); // plaintext is always an option + + var sorted = results.sort(function (a, b) { + // sort base on relevance + if (a.relevance !== b.relevance) return b.relevance - a.relevance; + + // always award the tie to the base language + // ie if C++ and Arduino are tied, it's more likely to be C++ + if (a.language && b.language) { + if (getLanguage(a.language).supersetOf === b.language) { + return 1; + } else if (getLanguage(b.language).supersetOf === a.language) { + return -1; + } + } + + // otherwise say they are equal, which has the effect of sorting on + // relevance while preserving the original ordering - which is how ties + // have historically been settled, ie the language that comes first always + // wins in the case of a tie + return 0; + }); + var _sorted = _slicedToArray(sorted, 2), + best = _sorted[0], + secondBest = _sorted[1]; + + /** @type {AutoHighlightResult} */ + var result = best; + result.second_best = secondBest; + return result; + } + + /** + Post-processing of the highlighted markup: + - replace TABs with something more useful + - replace real line-breaks with '
    ' for non-pre containers + @param {string} html + @returns {string} + */ + function fixMarkup(html) { + if (!(options.tabReplace || options.useBR)) { + return html; + } + return html.replace(fixMarkupRe, function (match) { + if (match === '\n') { + return options.useBR ? '
    ' : match; + } else if (options.tabReplace) { + return match.replace(/\t/g, options.tabReplace); + } + return match; + }); + } + + /** + * Builds new class name for block given the language name + * + * @param {HTMLElement} element + * @param {string} [currentLang] + * @param {string} [resultLang] + */ + function updateClassName(element, currentLang, resultLang) { + var language = currentLang ? aliases[currentLang] : resultLang; + element.classList.add("hljs"); + if (language) element.classList.add(language); + } + + /** @type {HLJSPlugin} */ + var brPlugin = { + "before:highlightElement": function beforeHighlightElement(_ref6) { + var el = _ref6.el; + if (options.useBR) { + el.innerHTML = el.innerHTML.replace(/\n/g, '').replace(//g, '\n'); + } + }, + "after:highlightElement": function afterHighlightElement(_ref7) { + var result = _ref7.result; + if (options.useBR) { + result.value = result.value.replace(/\n/g, "
    "); + } + } + }; + var TAB_REPLACE_RE = /^(<[^>]+>|\t)+/gm; + /** @type {HLJSPlugin} */ + var tabReplacePlugin = { + "after:highlightElement": function afterHighlightElement(_ref8) { + var result = _ref8.result; + if (options.tabReplace) { + result.value = result.value.replace(TAB_REPLACE_RE, function (m) { + return m.replace(/\t/g, options.tabReplace); + }); + } + } + }; + + /** + * Applies highlighting to a DOM node containing code. Accepts a DOM node and + * two optional parameters for fixMarkup. + * + * @param {HighlightedHTMLElement} element - the HTML element to highlight + */ + function highlightElement(element) { + /** @type HTMLElement */ + var node = null; + var language = blockLanguage(element); + if (shouldNotHighlight(language)) return; + + // support for v10 API + fire("before:highlightElement", { + el: element, + language: language + }); + node = element; + var text = node.textContent; + var result = language ? highlight(text, { + language: language, + ignoreIllegals: true + }) : highlightAuto(text); + + // support for v10 API + fire("after:highlightElement", { + el: element, + result: result, + text: text + }); + element.innerHTML = result.value; + updateClassName(element, language, result.language); + element.result = { + language: result.language, + // TODO: remove with version 11.0 + re: result.relevance, + relavance: result.relevance + }; + if (result.second_best) { + element.second_best = { + language: result.second_best.language, + // TODO: remove with version 11.0 + re: result.second_best.relevance, + relavance: result.second_best.relevance + }; + } + } + + /** + * Updates highlight.js global options with the passed options + * + * @param {Partial} userOptions + */ + function configure(userOptions) { + if (userOptions.useBR) { + deprecated("10.3.0", "'useBR' will be removed entirely in v11.0"); + deprecated("10.3.0", "Please see https://github.com/highlightjs/highlight.js/issues/2559"); + } + options = inherit$1(options, userOptions); + } + + /** + * Highlights to all
     blocks on a page
    +   *
    +   * @type {Function & {called?: boolean}}
    +   */
    +  // TODO: remove v12, deprecated
    +  var initHighlighting = function initHighlighting() {
    +    if (initHighlighting.called) return;
    +    initHighlighting.called = true;
    +    deprecated("10.6.0", "initHighlighting() is deprecated.  Use highlightAll() instead.");
    +    var blocks = document.querySelectorAll('pre code');
    +    blocks.forEach(highlightElement);
    +  };
    +
    +  // Higlights all when DOMContentLoaded fires
    +  // TODO: remove v12, deprecated
    +  function initHighlightingOnLoad() {
    +    deprecated("10.6.0", "initHighlightingOnLoad() is deprecated.  Use highlightAll() instead.");
    +    wantsHighlight = true;
    +  }
    +  var wantsHighlight = false;
    +
    +  /**
    +   * auto-highlights all pre>code elements on the page
    +   */
    +  function highlightAll() {
    +    // if we are called too early in the loading process
    +    if (document.readyState === "loading") {
    +      wantsHighlight = true;
    +      return;
    +    }
    +    var blocks = document.querySelectorAll('pre code');
    +    blocks.forEach(highlightElement);
    +  }
    +  function boot() {
    +    // if a highlight was requested before DOM was loaded, do now
    +    if (wantsHighlight) highlightAll();
    +  }
    +
    +  // make sure we are in the browser environment
    +  if (typeof window !== 'undefined' && window.addEventListener) {
    +    window.addEventListener('DOMContentLoaded', boot, false);
    +  }
    +
    +  /**
    +   * Register a language grammar module
    +   *
    +   * @param {string} languageName
    +   * @param {LanguageFn} languageDefinition
    +   */
    +  function registerLanguage(languageName, languageDefinition) {
    +    var lang = null;
    +    try {
    +      lang = languageDefinition(hljs);
    +    } catch (error$1) {
    +      error("Language definition for '{}' could not be registered.".replace("{}", languageName));
    +      // hard or soft error
    +      if (!SAFE_MODE) {
    +        throw error$1;
    +      } else {
    +        error(error$1);
    +      }
    +      // languages that have serious errors are replaced with essentially a
    +      // "plaintext" stand-in so that the code blocks will still get normal
    +      // css classes applied to them - and one bad language won't break the
    +      // entire highlighter
    +      lang = PLAINTEXT_LANGUAGE;
    +    }
    +    // give it a temporary name if it doesn't have one in the meta-data
    +    if (!lang.name) lang.name = languageName;
    +    languages[languageName] = lang;
    +    lang.rawDefinition = languageDefinition.bind(null, hljs);
    +    if (lang.aliases) {
    +      registerAliases(lang.aliases, {
    +        languageName: languageName
    +      });
    +    }
    +  }
    +
    +  /**
    +   * Remove a language grammar module
    +   *
    +   * @param {string} languageName
    +   */
    +  function unregisterLanguage(languageName) {
    +    delete languages[languageName];
    +    for (var _i2 = 0, _Object$keys = Object.keys(aliases); _i2 < _Object$keys.length; _i2++) {
    +      var alias = _Object$keys[_i2];
    +      if (aliases[alias] === languageName) {
    +        delete aliases[alias];
    +      }
    +    }
    +  }
    +
    +  /**
    +   * @returns {string[]} List of language internal names
    +   */
    +  function listLanguages() {
    +    return Object.keys(languages);
    +  }
    +
    +  /**
    +    intended usage: When one language truly requires another
    +     Unlike `getLanguage`, this will throw when the requested language
    +    is not available.
    +     @param {string} name - name of the language to fetch/require
    +    @returns {Language | never}
    +  */
    +  function requireLanguage(name) {
    +    deprecated("10.4.0", "requireLanguage will be removed entirely in v11.");
    +    deprecated("10.4.0", "Please see https://github.com/highlightjs/highlight.js/pull/2844");
    +    var lang = getLanguage(name);
    +    if (lang) {
    +      return lang;
    +    }
    +    var err = new Error('The \'{}\' language is required, but not loaded.'.replace('{}', name));
    +    throw err;
    +  }
    +
    +  /**
    +   * @param {string} name - name of the language to retrieve
    +   * @returns {Language | undefined}
    +   */
    +  function getLanguage(name) {
    +    name = (name || '').toLowerCase();
    +    return languages[name] || languages[aliases[name]];
    +  }
    +
    +  /**
    +   *
    +   * @param {string|string[]} aliasList - single alias or list of aliases
    +   * @param {{languageName: string}} opts
    +   */
    +  function registerAliases(aliasList, _ref9) {
    +    var languageName = _ref9.languageName;
    +    if (typeof aliasList === 'string') {
    +      aliasList = [aliasList];
    +    }
    +    aliasList.forEach(function (alias) {
    +      aliases[alias.toLowerCase()] = languageName;
    +    });
    +  }
    +
    +  /**
    +   * Determines if a given language has auto-detection enabled
    +   * @param {string} name - name of the language
    +   */
    +  function autoDetection(name) {
    +    var lang = getLanguage(name);
    +    return lang && !lang.disableAutodetect;
    +  }
    +
    +  /**
    +   * Upgrades the old highlightBlock plugins to the new
    +   * highlightElement API
    +   * @param {HLJSPlugin} plugin
    +   */
    +  function upgradePluginAPI(plugin) {
    +    // TODO: remove with v12
    +    if (plugin["before:highlightBlock"] && !plugin["before:highlightElement"]) {
    +      plugin["before:highlightElement"] = function (data) {
    +        plugin["before:highlightBlock"](Object.assign({
    +          block: data.el
    +        }, data));
    +      };
    +    }
    +    if (plugin["after:highlightBlock"] && !plugin["after:highlightElement"]) {
    +      plugin["after:highlightElement"] = function (data) {
    +        plugin["after:highlightBlock"](Object.assign({
    +          block: data.el
    +        }, data));
    +      };
    +    }
    +  }
    +
    +  /**
    +   * @param {HLJSPlugin} plugin
    +   */
    +  function addPlugin(plugin) {
    +    upgradePluginAPI(plugin);
    +    plugins.push(plugin);
    +  }
    +
    +  /**
    +   *
    +   * @param {PluginEvent} event
    +   * @param {any} args
    +   */
    +  function fire(event, args) {
    +    var cb = event;
    +    plugins.forEach(function (plugin) {
    +      if (plugin[cb]) {
    +        plugin[cb](args);
    +      }
    +    });
    +  }
    +
    +  /**
    +  Note: fixMarkup is deprecated and will be removed entirely in v11
    +   @param {string} arg
    +  @returns {string}
    +  */
    +  function deprecateFixMarkup(arg) {
    +    deprecated("10.2.0", "fixMarkup will be removed entirely in v11.0");
    +    deprecated("10.2.0", "Please see https://github.com/highlightjs/highlight.js/issues/2534");
    +    return fixMarkup(arg);
    +  }
    +
    +  /**
    +   *
    +   * @param {HighlightedHTMLElement} el
    +   */
    +  function deprecateHighlightBlock(el) {
    +    deprecated("10.7.0", "highlightBlock will be removed entirely in v12.0");
    +    deprecated("10.7.0", "Please use highlightElement now.");
    +    return highlightElement(el);
    +  }
    +
    +  /* Interface definition */
    +  Object.assign(hljs, {
    +    highlight: highlight,
    +    highlightAuto: highlightAuto,
    +    highlightAll: highlightAll,
    +    fixMarkup: deprecateFixMarkup,
    +    highlightElement: highlightElement,
    +    // TODO: Remove with v12 API
    +    highlightBlock: deprecateHighlightBlock,
    +    configure: configure,
    +    initHighlighting: initHighlighting,
    +    initHighlightingOnLoad: initHighlightingOnLoad,
    +    registerLanguage: registerLanguage,
    +    unregisterLanguage: unregisterLanguage,
    +    listLanguages: listLanguages,
    +    getLanguage: getLanguage,
    +    registerAliases: registerAliases,
    +    requireLanguage: requireLanguage,
    +    autoDetection: autoDetection,
    +    inherit: inherit$1,
    +    addPlugin: addPlugin,
    +    // plugins for frameworks
    +    vuePlugin: BuildVuePlugin(hljs).VuePlugin
    +  });
    +  hljs.debugMode = function () {
    +    SAFE_MODE = false;
    +  };
    +  hljs.safeMode = function () {
    +    SAFE_MODE = true;
    +  };
    +  hljs.versionString = version;
    +  for (var key in MODES) {
    +    // @ts-ignore
    +    if (typeof MODES[key] === "object") {
    +      // @ts-ignore
    +      deepFreezeEs6(MODES[key]);
    +    }
    +  }
    +
    +  // merge all the modes/regexs into our main object
    +  Object.assign(hljs, MODES);
    +
    +  // built-in plugins, likely to be moved out of core in the future
    +  hljs.addPlugin(brPlugin); // slated to be removed in v11
    +  hljs.addPlugin(mergeHTMLPlugin);
    +  hljs.addPlugin(tabReplacePlugin);
    +  return hljs;
    +};
    +
    +// export an "instance" of the highlighter
    +var highlight = HLJS({});
    +module.exports = highlight;
    +
    +/***/ }),
    +
    +/***/ 682:
    +/***/ (function(module) {
    +
    +/*
    +Language: Diff
    +Description: Unified and context diff
    +Author: Vasily Polovnyov 
    +Website: https://www.gnu.org/software/diffutils/
    +Category: common
    +*/
    +
    +/** @type LanguageFn */
    +function diff(hljs) {
    +  return {
    +    name: 'Diff',
    +    aliases: ['patch'],
    +    contains: [{
    +      className: 'meta',
    +      relevance: 10,
    +      variants: [{
    +        begin: /^@@ +-\d+,\d+ +\+\d+,\d+ +@@/
    +      }, {
    +        begin: /^\*\*\* +\d+,\d+ +\*\*\*\*$/
    +      }, {
    +        begin: /^--- +\d+,\d+ +----$/
    +      }]
    +    }, {
    +      className: 'comment',
    +      variants: [{
    +        begin: /Index: /,
    +        end: /$/
    +      }, {
    +        begin: /^index/,
    +        end: /$/
    +      }, {
    +        begin: /={3,}/,
    +        end: /$/
    +      }, {
    +        begin: /^-{3}/,
    +        end: /$/
    +      }, {
    +        begin: /^\*{3} /,
    +        end: /$/
    +      }, {
    +        begin: /^\+{3}/,
    +        end: /$/
    +      }, {
    +        begin: /^\*{15}$/
    +      }, {
    +        begin: /^diff --git/,
    +        end: /$/
    +      }]
    +    }, {
    +      className: 'addition',
    +      begin: /^\+/,
    +      end: /$/
    +    }, {
    +      className: 'deletion',
    +      begin: /^-/,
    +      end: /$/
    +    }, {
    +      className: 'addition',
    +      begin: /^!/,
    +      end: /$/
    +    }]
    +  };
    +}
    +module.exports = diff;
    +
    +/***/ }),
    +
    +/***/ 712:
    +/***/ (function(module) {
    +
    +/*
    +Language: YAML
    +Description: Yet Another Markdown Language
    +Author: Stefan Wienert 
    +Contributors: Carl Baxter 
    +Requires: ruby.js
    +Website: https://yaml.org
    +Category: common, config
    +*/
    +function yaml(hljs) {
    +  var LITERALS = 'true false yes no null';
    +
    +  // YAML spec allows non-reserved URI characters in tags.
    +  var URI_CHARACTERS = '[\\w#;/?:@&=+$,.~*\'()[\\]]+';
    +
    +  // Define keys as starting with a word character
    +  // ...containing word chars, spaces, colons, forward-slashes, hyphens and periods
    +  // ...and ending with a colon followed immediately by a space, tab or newline.
    +  // The YAML spec allows for much more than this, but this covers most use-cases.
    +  var KEY = {
    +    className: 'attr',
    +    variants: [{
    +      begin: '\\w[\\w :\\/.-]*:(?=[ \t]|$)'
    +    }, {
    +      begin: '"\\w[\\w :\\/.-]*":(?=[ \t]|$)'
    +    },
    +    // double quoted keys
    +    {
    +      begin: '\'\\w[\\w :\\/.-]*\':(?=[ \t]|$)'
    +    } // single quoted keys
    +    ]
    +  };
    +
    +  var TEMPLATE_VARIABLES = {
    +    className: 'template-variable',
    +    variants: [{
    +      begin: /\{\{/,
    +      end: /\}\}/
    +    },
    +    // jinja templates Ansible
    +    {
    +      begin: /%\{/,
    +      end: /\}/
    +    } // Ruby i18n
    +    ]
    +  };
    +
    +  var STRING = {
    +    className: 'string',
    +    relevance: 0,
    +    variants: [{
    +      begin: /'/,
    +      end: /'/
    +    }, {
    +      begin: /"/,
    +      end: /"/
    +    }, {
    +      begin: /\S+/
    +    }],
    +    contains: [hljs.BACKSLASH_ESCAPE, TEMPLATE_VARIABLES]
    +  };
    +
    +  // Strings inside of value containers (objects) can't contain braces,
    +  // brackets, or commas
    +  var CONTAINER_STRING = hljs.inherit(STRING, {
    +    variants: [{
    +      begin: /'/,
    +      end: /'/
    +    }, {
    +      begin: /"/,
    +      end: /"/
    +    }, {
    +      begin: /[^\s,{}[\]]+/
    +    }]
    +  });
    +  var DATE_RE = '[0-9]{4}(-[0-9][0-9]){0,2}';
    +  var TIME_RE = '([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?';
    +  var FRACTION_RE = '(\\.[0-9]*)?';
    +  var ZONE_RE = '([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?';
    +  var TIMESTAMP = {
    +    className: 'number',
    +    begin: '\\b' + DATE_RE + TIME_RE + FRACTION_RE + ZONE_RE + '\\b'
    +  };
    +  var VALUE_CONTAINER = {
    +    end: ',',
    +    endsWithParent: true,
    +    excludeEnd: true,
    +    keywords: LITERALS,
    +    relevance: 0
    +  };
    +  var OBJECT = {
    +    begin: /\{/,
    +    end: /\}/,
    +    contains: [VALUE_CONTAINER],
    +    illegal: '\\n',
    +    relevance: 0
    +  };
    +  var ARRAY = {
    +    begin: '\\[',
    +    end: '\\]',
    +    contains: [VALUE_CONTAINER],
    +    illegal: '\\n',
    +    relevance: 0
    +  };
    +  var MODES = [KEY, {
    +    className: 'meta',
    +    begin: '^---\\s*$',
    +    relevance: 10
    +  }, {
    +    // multi line string
    +    // Blocks start with a | or > followed by a newline
    +    //
    +    // Indentation of subsequent lines must be the same to
    +    // be considered part of the block
    +    className: 'string',
    +    begin: '[\\|>]([1-9]?[+-])?[ ]*\\n( +)[^ ][^\\n]*\\n(\\2[^\\n]+\\n?)*'
    +  }, {
    +    // Ruby/Rails erb
    +    begin: '<%[%=-]?',
    +    end: '[%-]?%>',
    +    subLanguage: 'ruby',
    +    excludeBegin: true,
    +    excludeEnd: true,
    +    relevance: 0
    +  }, {
    +    // named tags
    +    className: 'type',
    +    begin: '!\\w+!' + URI_CHARACTERS
    +  },
    +  // https://yaml.org/spec/1.2/spec.html#id2784064
    +  {
    +    // verbatim tags
    +    className: 'type',
    +    begin: '!<' + URI_CHARACTERS + ">"
    +  }, {
    +    // primary tags
    +    className: 'type',
    +    begin: '!' + URI_CHARACTERS
    +  }, {
    +    // secondary tags
    +    className: 'type',
    +    begin: '!!' + URI_CHARACTERS
    +  }, {
    +    // fragment id &ref
    +    className: 'meta',
    +    begin: '&' + hljs.UNDERSCORE_IDENT_RE + '$'
    +  }, {
    +    // fragment reference *ref
    +    className: 'meta',
    +    begin: '\\*' + hljs.UNDERSCORE_IDENT_RE + '$'
    +  }, {
    +    // array listing
    +    className: 'bullet',
    +    // TODO: remove |$ hack when we have proper look-ahead support
    +    begin: '-(?=[ ]|$)',
    +    relevance: 0
    +  }, hljs.HASH_COMMENT_MODE, {
    +    beginKeywords: LITERALS,
    +    keywords: {
    +      literal: LITERALS
    +    }
    +  }, TIMESTAMP,
    +  // numbers are any valid C-style number that
    +  // sit isolated from other words
    +  {
    +    className: 'number',
    +    begin: hljs.C_NUMBER_RE + '\\b',
    +    relevance: 0
    +  }, OBJECT, ARRAY, STRING];
    +  var VALUE_MODES = [].concat(MODES);
    +  VALUE_MODES.pop();
    +  VALUE_MODES.push(CONTAINER_STRING);
    +  VALUE_CONTAINER.contains = VALUE_MODES;
    +  return {
    +    name: 'YAML',
    +    case_insensitive: true,
    +    aliases: ['yml'],
    +    contains: MODES
    +  };
    +}
    +module.exports = yaml;
    +
    +/***/ }),
    +
    +/***/ 110:
    +/***/ (function(module, __unused_webpack_exports, __webpack_require__) {
    +
    +"use strict";
    +
    +
    +var reactIs = __webpack_require__(309);
    +
    +/**
    + * Copyright 2015, Yahoo! Inc.
    + * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
    + */
    +var REACT_STATICS = {
    +  childContextTypes: true,
    +  contextType: true,
    +  contextTypes: true,
    +  defaultProps: true,
    +  displayName: true,
    +  getDefaultProps: true,
    +  getDerivedStateFromError: true,
    +  getDerivedStateFromProps: true,
    +  mixins: true,
    +  propTypes: true,
    +  type: true
    +};
    +var KNOWN_STATICS = {
    +  name: true,
    +  length: true,
    +  prototype: true,
    +  caller: true,
    +  callee: true,
    +  arguments: true,
    +  arity: true
    +};
    +var FORWARD_REF_STATICS = {
    +  '$$typeof': true,
    +  render: true,
    +  defaultProps: true,
    +  displayName: true,
    +  propTypes: true
    +};
    +var MEMO_STATICS = {
    +  '$$typeof': true,
    +  compare: true,
    +  defaultProps: true,
    +  displayName: true,
    +  propTypes: true,
    +  type: true
    +};
    +var TYPE_STATICS = {};
    +TYPE_STATICS[reactIs.ForwardRef] = FORWARD_REF_STATICS;
    +TYPE_STATICS[reactIs.Memo] = MEMO_STATICS;
    +function getStatics(component) {
    +  // React v16.11 and below
    +  if (reactIs.isMemo(component)) {
    +    return MEMO_STATICS;
    +  } // React v16.12 and above
    +
    +  return TYPE_STATICS[component['$$typeof']] || REACT_STATICS;
    +}
    +var defineProperty = Object.defineProperty;
    +var getOwnPropertyNames = Object.getOwnPropertyNames;
    +var getOwnPropertySymbols = Object.getOwnPropertySymbols;
    +var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
    +var getPrototypeOf = Object.getPrototypeOf;
    +var objectPrototype = Object.prototype;
    +function hoistNonReactStatics(targetComponent, sourceComponent, blacklist) {
    +  if (typeof sourceComponent !== 'string') {
    +    // don't hoist over string (html) components
    +    if (objectPrototype) {
    +      var inheritedComponent = getPrototypeOf(sourceComponent);
    +      if (inheritedComponent && inheritedComponent !== objectPrototype) {
    +        hoistNonReactStatics(targetComponent, inheritedComponent, blacklist);
    +      }
    +    }
    +    var keys = getOwnPropertyNames(sourceComponent);
    +    if (getOwnPropertySymbols) {
    +      keys = keys.concat(getOwnPropertySymbols(sourceComponent));
    +    }
    +    var targetStatics = getStatics(targetComponent);
    +    var sourceStatics = getStatics(sourceComponent);
    +    for (var i = 0; i < keys.length; ++i) {
    +      var key = keys[i];
    +      if (!KNOWN_STATICS[key] && !(blacklist && blacklist[key]) && !(sourceStatics && sourceStatics[key]) && !(targetStatics && targetStatics[key])) {
    +        var descriptor = getOwnPropertyDescriptor(sourceComponent, key);
    +        try {
    +          // Avoid failures from read-only properties
    +          defineProperty(targetComponent, key, descriptor);
    +        } catch (e) {}
    +      }
    +    }
    +  }
    +  return targetComponent;
    +}
    +module.exports = hoistNonReactStatics;
    +
    +/***/ }),
    +
    +/***/ 746:
    +/***/ (function(__unused_webpack_module, exports) {
    +
    +"use strict";
    +/** @license React v16.13.1
    + * react-is.production.min.js
    + *
    + * Copyright (c) Facebook, Inc. and its affiliates.
    + *
    + * This source code is licensed under the MIT license found in the
    + * LICENSE file in the root directory of this source tree.
    + */
    +
    +
    +
    +var b = "function" === typeof Symbol && Symbol.for,
    +  c = b ? Symbol.for("react.element") : 60103,
    +  d = b ? Symbol.for("react.portal") : 60106,
    +  e = b ? Symbol.for("react.fragment") : 60107,
    +  f = b ? Symbol.for("react.strict_mode") : 60108,
    +  g = b ? Symbol.for("react.profiler") : 60114,
    +  h = b ? Symbol.for("react.provider") : 60109,
    +  k = b ? Symbol.for("react.context") : 60110,
    +  l = b ? Symbol.for("react.async_mode") : 60111,
    +  m = b ? Symbol.for("react.concurrent_mode") : 60111,
    +  n = b ? Symbol.for("react.forward_ref") : 60112,
    +  p = b ? Symbol.for("react.suspense") : 60113,
    +  q = b ? Symbol.for("react.suspense_list") : 60120,
    +  r = b ? Symbol.for("react.memo") : 60115,
    +  t = b ? Symbol.for("react.lazy") : 60116,
    +  v = b ? Symbol.for("react.block") : 60121,
    +  w = b ? Symbol.for("react.fundamental") : 60117,
    +  x = b ? Symbol.for("react.responder") : 60118,
    +  y = b ? Symbol.for("react.scope") : 60119;
    +function z(a) {
    +  if ("object" === typeof a && null !== a) {
    +    var u = a.$$typeof;
    +    switch (u) {
    +      case c:
    +        switch (a = a.type, a) {
    +          case l:
    +          case m:
    +          case e:
    +          case g:
    +          case f:
    +          case p:
    +            return a;
    +          default:
    +            switch (a = a && a.$$typeof, a) {
    +              case k:
    +              case n:
    +              case t:
    +              case r:
    +              case h:
    +                return a;
    +              default:
    +                return u;
    +            }
    +        }
    +      case d:
    +        return u;
    +    }
    +  }
    +}
    +function A(a) {
    +  return z(a) === m;
    +}
    +exports.AsyncMode = l;
    +exports.ConcurrentMode = m;
    +exports.ContextConsumer = k;
    +exports.ContextProvider = h;
    +exports.Element = c;
    +exports.ForwardRef = n;
    +exports.Fragment = e;
    +exports.Lazy = t;
    +exports.Memo = r;
    +exports.Portal = d;
    +exports.Profiler = g;
    +exports.StrictMode = f;
    +exports.Suspense = p;
    +exports.isAsyncMode = function (a) {
    +  return A(a) || z(a) === l;
    +};
    +exports.isConcurrentMode = A;
    +exports.isContextConsumer = function (a) {
    +  return z(a) === k;
    +};
    +exports.isContextProvider = function (a) {
    +  return z(a) === h;
    +};
    +exports.isElement = function (a) {
    +  return "object" === typeof a && null !== a && a.$$typeof === c;
    +};
    +exports.isForwardRef = function (a) {
    +  return z(a) === n;
    +};
    +exports.isFragment = function (a) {
    +  return z(a) === e;
    +};
    +exports.isLazy = function (a) {
    +  return z(a) === t;
    +};
    +exports.isMemo = function (a) {
    +  return z(a) === r;
    +};
    +exports.isPortal = function (a) {
    +  return z(a) === d;
    +};
    +exports.isProfiler = function (a) {
    +  return z(a) === g;
    +};
    +exports.isStrictMode = function (a) {
    +  return z(a) === f;
    +};
    +exports.isSuspense = function (a) {
    +  return z(a) === p;
    +};
    +exports.isValidElementType = function (a) {
    +  return "string" === typeof a || "function" === typeof a || a === e || a === m || a === g || a === f || a === p || a === q || "object" === typeof a && null !== a && (a.$$typeof === t || a.$$typeof === r || a.$$typeof === h || a.$$typeof === k || a.$$typeof === n || a.$$typeof === w || a.$$typeof === x || a.$$typeof === y || a.$$typeof === v);
    +};
    +exports.typeOf = z;
    +
    +/***/ }),
    +
    +/***/ 309:
    +/***/ (function(module, __unused_webpack_exports, __webpack_require__) {
    +
    +"use strict";
    +
    +
    +if (true) {
    +  module.exports = __webpack_require__(746);
    +} else {}
    +
    +/***/ }),
    +
    +/***/ 763:
    +/***/ (function(module, exports, __webpack_require__) {
    +
    +/* module decorator */ module = __webpack_require__.nmd(module);
    +var __WEBPACK_AMD_DEFINE_RESULT__;/**
    + * @license
    + * Lodash 
    + * Copyright OpenJS Foundation and other contributors 
    + * Released under MIT license 
    + * Based on Underscore.js 1.8.3 
    + * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
    + */
    +;
    +(function () {
    +  /** Used as a safe reference for `undefined` in pre-ES5 environments. */
    +  var undefined;
    +
    +  /** Used as the semantic version number. */
    +  var VERSION = '4.17.21';
    +
    +  /** Used as the size to enable large array optimizations. */
    +  var LARGE_ARRAY_SIZE = 200;
    +
    +  /** Error message constants. */
    +  var CORE_ERROR_TEXT = 'Unsupported core-js use. Try https://npms.io/search?q=ponyfill.',
    +    FUNC_ERROR_TEXT = 'Expected a function',
    +    INVALID_TEMPL_VAR_ERROR_TEXT = 'Invalid `variable` option passed into `_.template`';
    +
    +  /** Used to stand-in for `undefined` hash values. */
    +  var HASH_UNDEFINED = '__lodash_hash_undefined__';
    +
    +  /** Used as the maximum memoize cache size. */
    +  var MAX_MEMOIZE_SIZE = 500;
    +
    +  /** Used as the internal argument placeholder. */
    +  var PLACEHOLDER = '__lodash_placeholder__';
    +
    +  /** Used to compose bitmasks for cloning. */
    +  var CLONE_DEEP_FLAG = 1,
    +    CLONE_FLAT_FLAG = 2,
    +    CLONE_SYMBOLS_FLAG = 4;
    +
    +  /** Used to compose bitmasks for value comparisons. */
    +  var COMPARE_PARTIAL_FLAG = 1,
    +    COMPARE_UNORDERED_FLAG = 2;
    +
    +  /** Used to compose bitmasks for function metadata. */
    +  var WRAP_BIND_FLAG = 1,
    +    WRAP_BIND_KEY_FLAG = 2,
    +    WRAP_CURRY_BOUND_FLAG = 4,
    +    WRAP_CURRY_FLAG = 8,
    +    WRAP_CURRY_RIGHT_FLAG = 16,
    +    WRAP_PARTIAL_FLAG = 32,
    +    WRAP_PARTIAL_RIGHT_FLAG = 64,
    +    WRAP_ARY_FLAG = 128,
    +    WRAP_REARG_FLAG = 256,
    +    WRAP_FLIP_FLAG = 512;
    +
    +  /** Used as default options for `_.truncate`. */
    +  var DEFAULT_TRUNC_LENGTH = 30,
    +    DEFAULT_TRUNC_OMISSION = '...';
    +
    +  /** Used to detect hot functions by number of calls within a span of milliseconds. */
    +  var HOT_COUNT = 800,
    +    HOT_SPAN = 16;
    +
    +  /** Used to indicate the type of lazy iteratees. */
    +  var LAZY_FILTER_FLAG = 1,
    +    LAZY_MAP_FLAG = 2,
    +    LAZY_WHILE_FLAG = 3;
    +
    +  /** Used as references for various `Number` constants. */
    +  var INFINITY = 1 / 0,
    +    MAX_SAFE_INTEGER = 9007199254740991,
    +    MAX_INTEGER = 1.7976931348623157e+308,
    +    NAN = 0 / 0;
    +
    +  /** Used as references for the maximum length and index of an array. */
    +  var MAX_ARRAY_LENGTH = 4294967295,
    +    MAX_ARRAY_INDEX = MAX_ARRAY_LENGTH - 1,
    +    HALF_MAX_ARRAY_LENGTH = MAX_ARRAY_LENGTH >>> 1;
    +
    +  /** Used to associate wrap methods with their bit flags. */
    +  var wrapFlags = [['ary', WRAP_ARY_FLAG], ['bind', WRAP_BIND_FLAG], ['bindKey', WRAP_BIND_KEY_FLAG], ['curry', WRAP_CURRY_FLAG], ['curryRight', WRAP_CURRY_RIGHT_FLAG], ['flip', WRAP_FLIP_FLAG], ['partial', WRAP_PARTIAL_FLAG], ['partialRight', WRAP_PARTIAL_RIGHT_FLAG], ['rearg', WRAP_REARG_FLAG]];
    +
    +  /** `Object#toString` result references. */
    +  var argsTag = '[object Arguments]',
    +    arrayTag = '[object Array]',
    +    asyncTag = '[object AsyncFunction]',
    +    boolTag = '[object Boolean]',
    +    dateTag = '[object Date]',
    +    domExcTag = '[object DOMException]',
    +    errorTag = '[object Error]',
    +    funcTag = '[object Function]',
    +    genTag = '[object GeneratorFunction]',
    +    mapTag = '[object Map]',
    +    numberTag = '[object Number]',
    +    nullTag = '[object Null]',
    +    objectTag = '[object Object]',
    +    promiseTag = '[object Promise]',
    +    proxyTag = '[object Proxy]',
    +    regexpTag = '[object RegExp]',
    +    setTag = '[object Set]',
    +    stringTag = '[object String]',
    +    symbolTag = '[object Symbol]',
    +    undefinedTag = '[object Undefined]',
    +    weakMapTag = '[object WeakMap]',
    +    weakSetTag = '[object WeakSet]';
    +  var arrayBufferTag = '[object ArrayBuffer]',
    +    dataViewTag = '[object DataView]',
    +    float32Tag = '[object Float32Array]',
    +    float64Tag = '[object Float64Array]',
    +    int8Tag = '[object Int8Array]',
    +    int16Tag = '[object Int16Array]',
    +    int32Tag = '[object Int32Array]',
    +    uint8Tag = '[object Uint8Array]',
    +    uint8ClampedTag = '[object Uint8ClampedArray]',
    +    uint16Tag = '[object Uint16Array]',
    +    uint32Tag = '[object Uint32Array]';
    +
    +  /** Used to match empty string literals in compiled template source. */
    +  var reEmptyStringLeading = /\b__p \+= '';/g,
    +    reEmptyStringMiddle = /\b(__p \+=) '' \+/g,
    +    reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g;
    +
    +  /** Used to match HTML entities and HTML characters. */
    +  var reEscapedHtml = /&(?:amp|lt|gt|quot|#39);/g,
    +    reUnescapedHtml = /[&<>"']/g,
    +    reHasEscapedHtml = RegExp(reEscapedHtml.source),
    +    reHasUnescapedHtml = RegExp(reUnescapedHtml.source);
    +
    +  /** Used to match template delimiters. */
    +  var reEscape = /<%-([\s\S]+?)%>/g,
    +    reEvaluate = /<%([\s\S]+?)%>/g,
    +    reInterpolate = /<%=([\s\S]+?)%>/g;
    +
    +  /** Used to match property names within property paths. */
    +  var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,
    +    reIsPlainProp = /^\w*$/,
    +    rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g;
    +
    +  /**
    +   * Used to match `RegExp`
    +   * [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns).
    +   */
    +  var reRegExpChar = /[\\^$.*+?()[\]{}|]/g,
    +    reHasRegExpChar = RegExp(reRegExpChar.source);
    +
    +  /** Used to match leading whitespace. */
    +  var reTrimStart = /^\s+/;
    +
    +  /** Used to match a single whitespace character. */
    +  var reWhitespace = /\s/;
    +
    +  /** Used to match wrap detail comments. */
    +  var reWrapComment = /\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/,
    +    reWrapDetails = /\{\n\/\* \[wrapped with (.+)\] \*/,
    +    reSplitDetails = /,? & /;
    +
    +  /** Used to match words composed of alphanumeric characters. */
    +  var reAsciiWord = /[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g;
    +
    +  /**
    +   * Used to validate the `validate` option in `_.template` variable.
    +   *
    +   * Forbids characters which could potentially change the meaning of the function argument definition:
    +   * - "()," (modification of function parameters)
    +   * - "=" (default value)
    +   * - "[]{}" (destructuring of function parameters)
    +   * - "/" (beginning of a comment)
    +   * - whitespace
    +   */
    +  var reForbiddenIdentifierChars = /[()=,{}\[\]\/\s]/;
    +
    +  /** Used to match backslashes in property paths. */
    +  var reEscapeChar = /\\(\\)?/g;
    +
    +  /**
    +   * Used to match
    +   * [ES template delimiters](http://ecma-international.org/ecma-262/7.0/#sec-template-literal-lexical-components).
    +   */
    +  var reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g;
    +
    +  /** Used to match `RegExp` flags from their coerced string values. */
    +  var reFlags = /\w*$/;
    +
    +  /** Used to detect bad signed hexadecimal string values. */
    +  var reIsBadHex = /^[-+]0x[0-9a-f]+$/i;
    +
    +  /** Used to detect binary string values. */
    +  var reIsBinary = /^0b[01]+$/i;
    +
    +  /** Used to detect host constructors (Safari). */
    +  var reIsHostCtor = /^\[object .+?Constructor\]$/;
    +
    +  /** Used to detect octal string values. */
    +  var reIsOctal = /^0o[0-7]+$/i;
    +
    +  /** Used to detect unsigned integer values. */
    +  var reIsUint = /^(?:0|[1-9]\d*)$/;
    +
    +  /** Used to match Latin Unicode letters (excluding mathematical operators). */
    +  var reLatin = /[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g;
    +
    +  /** Used to ensure capturing order of template delimiters. */
    +  var reNoMatch = /($^)/;
    +
    +  /** Used to match unescaped characters in compiled string literals. */
    +  var reUnescapedString = /['\n\r\u2028\u2029\\]/g;
    +
    +  /** Used to compose unicode character classes. */
    +  var rsAstralRange = "\\ud800-\\udfff",
    +    rsComboMarksRange = "\\u0300-\\u036f",
    +    reComboHalfMarksRange = "\\ufe20-\\ufe2f",
    +    rsComboSymbolsRange = "\\u20d0-\\u20ff",
    +    rsComboRange = rsComboMarksRange + reComboHalfMarksRange + rsComboSymbolsRange,
    +    rsDingbatRange = "\\u2700-\\u27bf",
    +    rsLowerRange = 'a-z\\xdf-\\xf6\\xf8-\\xff',
    +    rsMathOpRange = '\\xac\\xb1\\xd7\\xf7',
    +    rsNonCharRange = '\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf',
    +    rsPunctuationRange = "\\u2000-\\u206f",
    +    rsSpaceRange = " \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000",
    +    rsUpperRange = 'A-Z\\xc0-\\xd6\\xd8-\\xde',
    +    rsVarRange = "\\ufe0e\\ufe0f",
    +    rsBreakRange = rsMathOpRange + rsNonCharRange + rsPunctuationRange + rsSpaceRange;
    +
    +  /** Used to compose unicode capture groups. */
    +  var rsApos = "['\u2019]",
    +    rsAstral = '[' + rsAstralRange + ']',
    +    rsBreak = '[' + rsBreakRange + ']',
    +    rsCombo = '[' + rsComboRange + ']',
    +    rsDigits = '\\d+',
    +    rsDingbat = '[' + rsDingbatRange + ']',
    +    rsLower = '[' + rsLowerRange + ']',
    +    rsMisc = '[^' + rsAstralRange + rsBreakRange + rsDigits + rsDingbatRange + rsLowerRange + rsUpperRange + ']',
    +    rsFitz = "\\ud83c[\\udffb-\\udfff]",
    +    rsModifier = '(?:' + rsCombo + '|' + rsFitz + ')',
    +    rsNonAstral = '[^' + rsAstralRange + ']',
    +    rsRegional = "(?:\\ud83c[\\udde6-\\uddff]){2}",
    +    rsSurrPair = "[\\ud800-\\udbff][\\udc00-\\udfff]",
    +    rsUpper = '[' + rsUpperRange + ']',
    +    rsZWJ = "\\u200d";
    +
    +  /** Used to compose unicode regexes. */
    +  var rsMiscLower = '(?:' + rsLower + '|' + rsMisc + ')',
    +    rsMiscUpper = '(?:' + rsUpper + '|' + rsMisc + ')',
    +    rsOptContrLower = '(?:' + rsApos + '(?:d|ll|m|re|s|t|ve))?',
    +    rsOptContrUpper = '(?:' + rsApos + '(?:D|LL|M|RE|S|T|VE))?',
    +    reOptMod = rsModifier + '?',
    +    rsOptVar = '[' + rsVarRange + ']?',
    +    rsOptJoin = '(?:' + rsZWJ + '(?:' + [rsNonAstral, rsRegional, rsSurrPair].join('|') + ')' + rsOptVar + reOptMod + ')*',
    +    rsOrdLower = '\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])',
    +    rsOrdUpper = '\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])',
    +    rsSeq = rsOptVar + reOptMod + rsOptJoin,
    +    rsEmoji = '(?:' + [rsDingbat, rsRegional, rsSurrPair].join('|') + ')' + rsSeq,
    +    rsSymbol = '(?:' + [rsNonAstral + rsCombo + '?', rsCombo, rsRegional, rsSurrPair, rsAstral].join('|') + ')';
    +
    +  /** Used to match apostrophes. */
    +  var reApos = RegExp(rsApos, 'g');
    +
    +  /**
    +   * Used to match [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks) and
    +   * [combining diacritical marks for symbols](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks_for_Symbols).
    +   */
    +  var reComboMark = RegExp(rsCombo, 'g');
    +
    +  /** Used to match [string symbols](https://mathiasbynens.be/notes/javascript-unicode). */
    +  var reUnicode = RegExp(rsFitz + '(?=' + rsFitz + ')|' + rsSymbol + rsSeq, 'g');
    +
    +  /** Used to match complex or compound words. */
    +  var reUnicodeWord = RegExp([rsUpper + '?' + rsLower + '+' + rsOptContrLower + '(?=' + [rsBreak, rsUpper, '$'].join('|') + ')', rsMiscUpper + '+' + rsOptContrUpper + '(?=' + [rsBreak, rsUpper + rsMiscLower, '$'].join('|') + ')', rsUpper + '?' + rsMiscLower + '+' + rsOptContrLower, rsUpper + '+' + rsOptContrUpper, rsOrdUpper, rsOrdLower, rsDigits, rsEmoji].join('|'), 'g');
    +
    +  /** Used to detect strings with [zero-width joiners or code points from the astral planes](http://eev.ee/blog/2015/09/12/dark-corners-of-unicode/). */
    +  var reHasUnicode = RegExp('[' + rsZWJ + rsAstralRange + rsComboRange + rsVarRange + ']');
    +
    +  /** Used to detect strings that need a more robust regexp to match words. */
    +  var reHasUnicodeWord = /[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/;
    +
    +  /** Used to assign default `context` object properties. */
    +  var contextProps = ['Array', 'Buffer', 'DataView', 'Date', 'Error', 'Float32Array', 'Float64Array', 'Function', 'Int8Array', 'Int16Array', 'Int32Array', 'Map', 'Math', 'Object', 'Promise', 'RegExp', 'Set', 'String', 'Symbol', 'TypeError', 'Uint8Array', 'Uint8ClampedArray', 'Uint16Array', 'Uint32Array', 'WeakMap', '_', 'clearTimeout', 'isFinite', 'parseInt', 'setTimeout'];
    +
    +  /** Used to make template sourceURLs easier to identify. */
    +  var templateCounter = -1;
    +
    +  /** Used to identify `toStringTag` values of typed arrays. */
    +  var typedArrayTags = {};
    +  typedArrayTags[float32Tag] = typedArrayTags[float64Tag] = typedArrayTags[int8Tag] = typedArrayTags[int16Tag] = typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] = typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] = typedArrayTags[uint32Tag] = true;
    +  typedArrayTags[argsTag] = typedArrayTags[arrayTag] = typedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] = typedArrayTags[dataViewTag] = typedArrayTags[dateTag] = typedArrayTags[errorTag] = typedArrayTags[funcTag] = typedArrayTags[mapTag] = typedArrayTags[numberTag] = typedArrayTags[objectTag] = typedArrayTags[regexpTag] = typedArrayTags[setTag] = typedArrayTags[stringTag] = typedArrayTags[weakMapTag] = false;
    +
    +  /** Used to identify `toStringTag` values supported by `_.clone`. */
    +  var cloneableTags = {};
    +  cloneableTags[argsTag] = cloneableTags[arrayTag] = cloneableTags[arrayBufferTag] = cloneableTags[dataViewTag] = cloneableTags[boolTag] = cloneableTags[dateTag] = cloneableTags[float32Tag] = cloneableTags[float64Tag] = cloneableTags[int8Tag] = cloneableTags[int16Tag] = cloneableTags[int32Tag] = cloneableTags[mapTag] = cloneableTags[numberTag] = cloneableTags[objectTag] = cloneableTags[regexpTag] = cloneableTags[setTag] = cloneableTags[stringTag] = cloneableTags[symbolTag] = cloneableTags[uint8Tag] = cloneableTags[uint8ClampedTag] = cloneableTags[uint16Tag] = cloneableTags[uint32Tag] = true;
    +  cloneableTags[errorTag] = cloneableTags[funcTag] = cloneableTags[weakMapTag] = false;
    +
    +  /** Used to map Latin Unicode letters to basic Latin letters. */
    +  var deburredLetters = {
    +    // Latin-1 Supplement block.
    +    '\xc0': 'A',
    +    '\xc1': 'A',
    +    '\xc2': 'A',
    +    '\xc3': 'A',
    +    '\xc4': 'A',
    +    '\xc5': 'A',
    +    '\xe0': 'a',
    +    '\xe1': 'a',
    +    '\xe2': 'a',
    +    '\xe3': 'a',
    +    '\xe4': 'a',
    +    '\xe5': 'a',
    +    '\xc7': 'C',
    +    '\xe7': 'c',
    +    '\xd0': 'D',
    +    '\xf0': 'd',
    +    '\xc8': 'E',
    +    '\xc9': 'E',
    +    '\xca': 'E',
    +    '\xcb': 'E',
    +    '\xe8': 'e',
    +    '\xe9': 'e',
    +    '\xea': 'e',
    +    '\xeb': 'e',
    +    '\xcc': 'I',
    +    '\xcd': 'I',
    +    '\xce': 'I',
    +    '\xcf': 'I',
    +    '\xec': 'i',
    +    '\xed': 'i',
    +    '\xee': 'i',
    +    '\xef': 'i',
    +    '\xd1': 'N',
    +    '\xf1': 'n',
    +    '\xd2': 'O',
    +    '\xd3': 'O',
    +    '\xd4': 'O',
    +    '\xd5': 'O',
    +    '\xd6': 'O',
    +    '\xd8': 'O',
    +    '\xf2': 'o',
    +    '\xf3': 'o',
    +    '\xf4': 'o',
    +    '\xf5': 'o',
    +    '\xf6': 'o',
    +    '\xf8': 'o',
    +    '\xd9': 'U',
    +    '\xda': 'U',
    +    '\xdb': 'U',
    +    '\xdc': 'U',
    +    '\xf9': 'u',
    +    '\xfa': 'u',
    +    '\xfb': 'u',
    +    '\xfc': 'u',
    +    '\xdd': 'Y',
    +    '\xfd': 'y',
    +    '\xff': 'y',
    +    '\xc6': 'Ae',
    +    '\xe6': 'ae',
    +    '\xde': 'Th',
    +    '\xfe': 'th',
    +    '\xdf': 'ss',
    +    // Latin Extended-A block.
    +    "\u0100": 'A',
    +    "\u0102": 'A',
    +    "\u0104": 'A',
    +    "\u0101": 'a',
    +    "\u0103": 'a',
    +    "\u0105": 'a',
    +    "\u0106": 'C',
    +    "\u0108": 'C',
    +    "\u010A": 'C',
    +    "\u010C": 'C',
    +    "\u0107": 'c',
    +    "\u0109": 'c',
    +    "\u010B": 'c',
    +    "\u010D": 'c',
    +    "\u010E": 'D',
    +    "\u0110": 'D',
    +    "\u010F": 'd',
    +    "\u0111": 'd',
    +    "\u0112": 'E',
    +    "\u0114": 'E',
    +    "\u0116": 'E',
    +    "\u0118": 'E',
    +    "\u011A": 'E',
    +    "\u0113": 'e',
    +    "\u0115": 'e',
    +    "\u0117": 'e',
    +    "\u0119": 'e',
    +    "\u011B": 'e',
    +    "\u011C": 'G',
    +    "\u011E": 'G',
    +    "\u0120": 'G',
    +    "\u0122": 'G',
    +    "\u011D": 'g',
    +    "\u011F": 'g',
    +    "\u0121": 'g',
    +    "\u0123": 'g',
    +    "\u0124": 'H',
    +    "\u0126": 'H',
    +    "\u0125": 'h',
    +    "\u0127": 'h',
    +    "\u0128": 'I',
    +    "\u012A": 'I',
    +    "\u012C": 'I',
    +    "\u012E": 'I',
    +    "\u0130": 'I',
    +    "\u0129": 'i',
    +    "\u012B": 'i',
    +    "\u012D": 'i',
    +    "\u012F": 'i',
    +    "\u0131": 'i',
    +    "\u0134": 'J',
    +    "\u0135": 'j',
    +    "\u0136": 'K',
    +    "\u0137": 'k',
    +    "\u0138": 'k',
    +    "\u0139": 'L',
    +    "\u013B": 'L',
    +    "\u013D": 'L',
    +    "\u013F": 'L',
    +    "\u0141": 'L',
    +    "\u013A": 'l',
    +    "\u013C": 'l',
    +    "\u013E": 'l',
    +    "\u0140": 'l',
    +    "\u0142": 'l',
    +    "\u0143": 'N',
    +    "\u0145": 'N',
    +    "\u0147": 'N',
    +    "\u014A": 'N',
    +    "\u0144": 'n',
    +    "\u0146": 'n',
    +    "\u0148": 'n',
    +    "\u014B": 'n',
    +    "\u014C": 'O',
    +    "\u014E": 'O',
    +    "\u0150": 'O',
    +    "\u014D": 'o',
    +    "\u014F": 'o',
    +    "\u0151": 'o',
    +    "\u0154": 'R',
    +    "\u0156": 'R',
    +    "\u0158": 'R',
    +    "\u0155": 'r',
    +    "\u0157": 'r',
    +    "\u0159": 'r',
    +    "\u015A": 'S',
    +    "\u015C": 'S',
    +    "\u015E": 'S',
    +    "\u0160": 'S',
    +    "\u015B": 's',
    +    "\u015D": 's',
    +    "\u015F": 's',
    +    "\u0161": 's',
    +    "\u0162": 'T',
    +    "\u0164": 'T',
    +    "\u0166": 'T',
    +    "\u0163": 't',
    +    "\u0165": 't',
    +    "\u0167": 't',
    +    "\u0168": 'U',
    +    "\u016A": 'U',
    +    "\u016C": 'U',
    +    "\u016E": 'U',
    +    "\u0170": 'U',
    +    "\u0172": 'U',
    +    "\u0169": 'u',
    +    "\u016B": 'u',
    +    "\u016D": 'u',
    +    "\u016F": 'u',
    +    "\u0171": 'u',
    +    "\u0173": 'u',
    +    "\u0174": 'W',
    +    "\u0175": 'w',
    +    "\u0176": 'Y',
    +    "\u0177": 'y',
    +    "\u0178": 'Y',
    +    "\u0179": 'Z',
    +    "\u017B": 'Z',
    +    "\u017D": 'Z',
    +    "\u017A": 'z',
    +    "\u017C": 'z',
    +    "\u017E": 'z',
    +    "\u0132": 'IJ',
    +    "\u0133": 'ij',
    +    "\u0152": 'Oe',
    +    "\u0153": 'oe',
    +    "\u0149": "'n",
    +    "\u017F": 's'
    +  };
    +
    +  /** Used to map characters to HTML entities. */
    +  var htmlEscapes = {
    +    '&': '&',
    +    '<': '<',
    +    '>': '>',
    +    '"': '"',
    +    "'": '''
    +  };
    +
    +  /** Used to map HTML entities to characters. */
    +  var htmlUnescapes = {
    +    '&': '&',
    +    '<': '<',
    +    '>': '>',
    +    '"': '"',
    +    ''': "'"
    +  };
    +
    +  /** Used to escape characters for inclusion in compiled string literals. */
    +  var stringEscapes = {
    +    '\\': '\\',
    +    "'": "'",
    +    '\n': 'n',
    +    '\r': 'r',
    +    "\u2028": 'u2028',
    +    "\u2029": 'u2029'
    +  };
    +
    +  /** Built-in method references without a dependency on `root`. */
    +  var freeParseFloat = parseFloat,
    +    freeParseInt = parseInt;
    +
    +  /** Detect free variable `global` from Node.js. */
    +  var freeGlobal = typeof __webpack_require__.g == 'object' && __webpack_require__.g && __webpack_require__.g.Object === Object && __webpack_require__.g;
    +
    +  /** Detect free variable `self`. */
    +  var freeSelf = typeof self == 'object' && self && self.Object === Object && self;
    +
    +  /** Used as a reference to the global object. */
    +  var root = freeGlobal || freeSelf || Function('return this')();
    +
    +  /** Detect free variable `exports`. */
    +  var freeExports =  true && exports && !exports.nodeType && exports;
    +
    +  /** Detect free variable `module`. */
    +  var freeModule = freeExports && "object" == 'object' && module && !module.nodeType && module;
    +
    +  /** Detect the popular CommonJS extension `module.exports`. */
    +  var moduleExports = freeModule && freeModule.exports === freeExports;
    +
    +  /** Detect free variable `process` from Node.js. */
    +  var freeProcess = moduleExports && freeGlobal.process;
    +
    +  /** Used to access faster Node.js helpers. */
    +  var nodeUtil = function () {
    +    try {
    +      // Use `util.types` for Node.js 10+.
    +      var types = freeModule && freeModule.require && freeModule.require('util').types;
    +      if (types) {
    +        return types;
    +      }
    +
    +      // Legacy `process.binding('util')` for Node.js < 10.
    +      return freeProcess && freeProcess.binding && freeProcess.binding('util');
    +    } catch (e) {}
    +  }();
    +
    +  /* Node.js helper references. */
    +  var nodeIsArrayBuffer = nodeUtil && nodeUtil.isArrayBuffer,
    +    nodeIsDate = nodeUtil && nodeUtil.isDate,
    +    nodeIsMap = nodeUtil && nodeUtil.isMap,
    +    nodeIsRegExp = nodeUtil && nodeUtil.isRegExp,
    +    nodeIsSet = nodeUtil && nodeUtil.isSet,
    +    nodeIsTypedArray = nodeUtil && nodeUtil.isTypedArray;
    +
    +  /*--------------------------------------------------------------------------*/
    +
    +  /**
    +   * A faster alternative to `Function#apply`, this function invokes `func`
    +   * with the `this` binding of `thisArg` and the arguments of `args`.
    +   *
    +   * @private
    +   * @param {Function} func The function to invoke.
    +   * @param {*} thisArg The `this` binding of `func`.
    +   * @param {Array} args The arguments to invoke `func` with.
    +   * @returns {*} Returns the result of `func`.
    +   */
    +  function apply(func, thisArg, args) {
    +    switch (args.length) {
    +      case 0:
    +        return func.call(thisArg);
    +      case 1:
    +        return func.call(thisArg, args[0]);
    +      case 2:
    +        return func.call(thisArg, args[0], args[1]);
    +      case 3:
    +        return func.call(thisArg, args[0], args[1], args[2]);
    +    }
    +    return func.apply(thisArg, args);
    +  }
    +
    +  /**
    +   * A specialized version of `baseAggregator` for arrays.
    +   *
    +   * @private
    +   * @param {Array} [array] The array to iterate over.
    +   * @param {Function} setter The function to set `accumulator` values.
    +   * @param {Function} iteratee The iteratee to transform keys.
    +   * @param {Object} accumulator The initial aggregated object.
    +   * @returns {Function} Returns `accumulator`.
    +   */
    +  function arrayAggregator(array, setter, iteratee, accumulator) {
    +    var index = -1,
    +      length = array == null ? 0 : array.length;
    +    while (++index < length) {
    +      var value = array[index];
    +      setter(accumulator, value, iteratee(value), array);
    +    }
    +    return accumulator;
    +  }
    +
    +  /**
    +   * A specialized version of `_.forEach` for arrays without support for
    +   * iteratee shorthands.
    +   *
    +   * @private
    +   * @param {Array} [array] The array to iterate over.
    +   * @param {Function} iteratee The function invoked per iteration.
    +   * @returns {Array} Returns `array`.
    +   */
    +  function arrayEach(array, iteratee) {
    +    var index = -1,
    +      length = array == null ? 0 : array.length;
    +    while (++index < length) {
    +      if (iteratee(array[index], index, array) === false) {
    +        break;
    +      }
    +    }
    +    return array;
    +  }
    +
    +  /**
    +   * A specialized version of `_.forEachRight` for arrays without support for
    +   * iteratee shorthands.
    +   *
    +   * @private
    +   * @param {Array} [array] The array to iterate over.
    +   * @param {Function} iteratee The function invoked per iteration.
    +   * @returns {Array} Returns `array`.
    +   */
    +  function arrayEachRight(array, iteratee) {
    +    var length = array == null ? 0 : array.length;
    +    while (length--) {
    +      if (iteratee(array[length], length, array) === false) {
    +        break;
    +      }
    +    }
    +    return array;
    +  }
    +
    +  /**
    +   * A specialized version of `_.every` for arrays without support for
    +   * iteratee shorthands.
    +   *
    +   * @private
    +   * @param {Array} [array] The array to iterate over.
    +   * @param {Function} predicate The function invoked per iteration.
    +   * @returns {boolean} Returns `true` if all elements pass the predicate check,
    +   *  else `false`.
    +   */
    +  function arrayEvery(array, predicate) {
    +    var index = -1,
    +      length = array == null ? 0 : array.length;
    +    while (++index < length) {
    +      if (!predicate(array[index], index, array)) {
    +        return false;
    +      }
    +    }
    +    return true;
    +  }
    +
    +  /**
    +   * A specialized version of `_.filter` for arrays without support for
    +   * iteratee shorthands.
    +   *
    +   * @private
    +   * @param {Array} [array] The array to iterate over.
    +   * @param {Function} predicate The function invoked per iteration.
    +   * @returns {Array} Returns the new filtered array.
    +   */
    +  function arrayFilter(array, predicate) {
    +    var index = -1,
    +      length = array == null ? 0 : array.length,
    +      resIndex = 0,
    +      result = [];
    +    while (++index < length) {
    +      var value = array[index];
    +      if (predicate(value, index, array)) {
    +        result[resIndex++] = value;
    +      }
    +    }
    +    return result;
    +  }
    +
    +  /**
    +   * A specialized version of `_.includes` for arrays without support for
    +   * specifying an index to search from.
    +   *
    +   * @private
    +   * @param {Array} [array] The array to inspect.
    +   * @param {*} target The value to search for.
    +   * @returns {boolean} Returns `true` if `target` is found, else `false`.
    +   */
    +  function arrayIncludes(array, value) {
    +    var length = array == null ? 0 : array.length;
    +    return !!length && baseIndexOf(array, value, 0) > -1;
    +  }
    +
    +  /**
    +   * This function is like `arrayIncludes` except that it accepts a comparator.
    +   *
    +   * @private
    +   * @param {Array} [array] The array to inspect.
    +   * @param {*} target The value to search for.
    +   * @param {Function} comparator The comparator invoked per element.
    +   * @returns {boolean} Returns `true` if `target` is found, else `false`.
    +   */
    +  function arrayIncludesWith(array, value, comparator) {
    +    var index = -1,
    +      length = array == null ? 0 : array.length;
    +    while (++index < length) {
    +      if (comparator(value, array[index])) {
    +        return true;
    +      }
    +    }
    +    return false;
    +  }
    +
    +  /**
    +   * A specialized version of `_.map` for arrays without support for iteratee
    +   * shorthands.
    +   *
    +   * @private
    +   * @param {Array} [array] The array to iterate over.
    +   * @param {Function} iteratee The function invoked per iteration.
    +   * @returns {Array} Returns the new mapped array.
    +   */
    +  function arrayMap(array, iteratee) {
    +    var index = -1,
    +      length = array == null ? 0 : array.length,
    +      result = Array(length);
    +    while (++index < length) {
    +      result[index] = iteratee(array[index], index, array);
    +    }
    +    return result;
    +  }
    +
    +  /**
    +   * Appends the elements of `values` to `array`.
    +   *
    +   * @private
    +   * @param {Array} array The array to modify.
    +   * @param {Array} values The values to append.
    +   * @returns {Array} Returns `array`.
    +   */
    +  function arrayPush(array, values) {
    +    var index = -1,
    +      length = values.length,
    +      offset = array.length;
    +    while (++index < length) {
    +      array[offset + index] = values[index];
    +    }
    +    return array;
    +  }
    +
    +  /**
    +   * A specialized version of `_.reduce` for arrays without support for
    +   * iteratee shorthands.
    +   *
    +   * @private
    +   * @param {Array} [array] The array to iterate over.
    +   * @param {Function} iteratee The function invoked per iteration.
    +   * @param {*} [accumulator] The initial value.
    +   * @param {boolean} [initAccum] Specify using the first element of `array` as
    +   *  the initial value.
    +   * @returns {*} Returns the accumulated value.
    +   */
    +  function arrayReduce(array, iteratee, accumulator, initAccum) {
    +    var index = -1,
    +      length = array == null ? 0 : array.length;
    +    if (initAccum && length) {
    +      accumulator = array[++index];
    +    }
    +    while (++index < length) {
    +      accumulator = iteratee(accumulator, array[index], index, array);
    +    }
    +    return accumulator;
    +  }
    +
    +  /**
    +   * A specialized version of `_.reduceRight` for arrays without support for
    +   * iteratee shorthands.
    +   *
    +   * @private
    +   * @param {Array} [array] The array to iterate over.
    +   * @param {Function} iteratee The function invoked per iteration.
    +   * @param {*} [accumulator] The initial value.
    +   * @param {boolean} [initAccum] Specify using the last element of `array` as
    +   *  the initial value.
    +   * @returns {*} Returns the accumulated value.
    +   */
    +  function arrayReduceRight(array, iteratee, accumulator, initAccum) {
    +    var length = array == null ? 0 : array.length;
    +    if (initAccum && length) {
    +      accumulator = array[--length];
    +    }
    +    while (length--) {
    +      accumulator = iteratee(accumulator, array[length], length, array);
    +    }
    +    return accumulator;
    +  }
    +
    +  /**
    +   * A specialized version of `_.some` for arrays without support for iteratee
    +   * shorthands.
    +   *
    +   * @private
    +   * @param {Array} [array] The array to iterate over.
    +   * @param {Function} predicate The function invoked per iteration.
    +   * @returns {boolean} Returns `true` if any element passes the predicate check,
    +   *  else `false`.
    +   */
    +  function arraySome(array, predicate) {
    +    var index = -1,
    +      length = array == null ? 0 : array.length;
    +    while (++index < length) {
    +      if (predicate(array[index], index, array)) {
    +        return true;
    +      }
    +    }
    +    return false;
    +  }
    +
    +  /**
    +   * Gets the size of an ASCII `string`.
    +   *
    +   * @private
    +   * @param {string} string The string inspect.
    +   * @returns {number} Returns the string size.
    +   */
    +  var asciiSize = baseProperty('length');
    +
    +  /**
    +   * Converts an ASCII `string` to an array.
    +   *
    +   * @private
    +   * @param {string} string The string to convert.
    +   * @returns {Array} Returns the converted array.
    +   */
    +  function asciiToArray(string) {
    +    return string.split('');
    +  }
    +
    +  /**
    +   * Splits an ASCII `string` into an array of its words.
    +   *
    +   * @private
    +   * @param {string} The string to inspect.
    +   * @returns {Array} Returns the words of `string`.
    +   */
    +  function asciiWords(string) {
    +    return string.match(reAsciiWord) || [];
    +  }
    +
    +  /**
    +   * The base implementation of methods like `_.findKey` and `_.findLastKey`,
    +   * without support for iteratee shorthands, which iterates over `collection`
    +   * using `eachFunc`.
    +   *
    +   * @private
    +   * @param {Array|Object} collection The collection to inspect.
    +   * @param {Function} predicate The function invoked per iteration.
    +   * @param {Function} eachFunc The function to iterate over `collection`.
    +   * @returns {*} Returns the found element or its key, else `undefined`.
    +   */
    +  function baseFindKey(collection, predicate, eachFunc) {
    +    var result;
    +    eachFunc(collection, function (value, key, collection) {
    +      if (predicate(value, key, collection)) {
    +        result = key;
    +        return false;
    +      }
    +    });
    +    return result;
    +  }
    +
    +  /**
    +   * The base implementation of `_.findIndex` and `_.findLastIndex` without
    +   * support for iteratee shorthands.
    +   *
    +   * @private
    +   * @param {Array} array The array to inspect.
    +   * @param {Function} predicate The function invoked per iteration.
    +   * @param {number} fromIndex The index to search from.
    +   * @param {boolean} [fromRight] Specify iterating from right to left.
    +   * @returns {number} Returns the index of the matched value, else `-1`.
    +   */
    +  function baseFindIndex(array, predicate, fromIndex, fromRight) {
    +    var length = array.length,
    +      index = fromIndex + (fromRight ? 1 : -1);
    +    while (fromRight ? index-- : ++index < length) {
    +      if (predicate(array[index], index, array)) {
    +        return index;
    +      }
    +    }
    +    return -1;
    +  }
    +
    +  /**
    +   * The base implementation of `_.indexOf` without `fromIndex` bounds checks.
    +   *
    +   * @private
    +   * @param {Array} array The array to inspect.
    +   * @param {*} value The value to search for.
    +   * @param {number} fromIndex The index to search from.
    +   * @returns {number} Returns the index of the matched value, else `-1`.
    +   */
    +  function baseIndexOf(array, value, fromIndex) {
    +    return value === value ? strictIndexOf(array, value, fromIndex) : baseFindIndex(array, baseIsNaN, fromIndex);
    +  }
    +
    +  /**
    +   * This function is like `baseIndexOf` except that it accepts a comparator.
    +   *
    +   * @private
    +   * @param {Array} array The array to inspect.
    +   * @param {*} value The value to search for.
    +   * @param {number} fromIndex The index to search from.
    +   * @param {Function} comparator The comparator invoked per element.
    +   * @returns {number} Returns the index of the matched value, else `-1`.
    +   */
    +  function baseIndexOfWith(array, value, fromIndex, comparator) {
    +    var index = fromIndex - 1,
    +      length = array.length;
    +    while (++index < length) {
    +      if (comparator(array[index], value)) {
    +        return index;
    +      }
    +    }
    +    return -1;
    +  }
    +
    +  /**
    +   * The base implementation of `_.isNaN` without support for number objects.
    +   *
    +   * @private
    +   * @param {*} value The value to check.
    +   * @returns {boolean} Returns `true` if `value` is `NaN`, else `false`.
    +   */
    +  function baseIsNaN(value) {
    +    return value !== value;
    +  }
    +
    +  /**
    +   * The base implementation of `_.mean` and `_.meanBy` without support for
    +   * iteratee shorthands.
    +   *
    +   * @private
    +   * @param {Array} array The array to iterate over.
    +   * @param {Function} iteratee The function invoked per iteration.
    +   * @returns {number} Returns the mean.
    +   */
    +  function baseMean(array, iteratee) {
    +    var length = array == null ? 0 : array.length;
    +    return length ? baseSum(array, iteratee) / length : NAN;
    +  }
    +
    +  /**
    +   * The base implementation of `_.property` without support for deep paths.
    +   *
    +   * @private
    +   * @param {string} key The key of the property to get.
    +   * @returns {Function} Returns the new accessor function.
    +   */
    +  function baseProperty(key) {
    +    return function (object) {
    +      return object == null ? undefined : object[key];
    +    };
    +  }
    +
    +  /**
    +   * The base implementation of `_.propertyOf` without support for deep paths.
    +   *
    +   * @private
    +   * @param {Object} object The object to query.
    +   * @returns {Function} Returns the new accessor function.
    +   */
    +  function basePropertyOf(object) {
    +    return function (key) {
    +      return object == null ? undefined : object[key];
    +    };
    +  }
    +
    +  /**
    +   * The base implementation of `_.reduce` and `_.reduceRight`, without support
    +   * for iteratee shorthands, which iterates over `collection` using `eachFunc`.
    +   *
    +   * @private
    +   * @param {Array|Object} collection The collection to iterate over.
    +   * @param {Function} iteratee The function invoked per iteration.
    +   * @param {*} accumulator The initial value.
    +   * @param {boolean} initAccum Specify using the first or last element of
    +   *  `collection` as the initial value.
    +   * @param {Function} eachFunc The function to iterate over `collection`.
    +   * @returns {*} Returns the accumulated value.
    +   */
    +  function baseReduce(collection, iteratee, accumulator, initAccum, eachFunc) {
    +    eachFunc(collection, function (value, index, collection) {
    +      accumulator = initAccum ? (initAccum = false, value) : iteratee(accumulator, value, index, collection);
    +    });
    +    return accumulator;
    +  }
    +
    +  /**
    +   * The base implementation of `_.sortBy` which uses `comparer` to define the
    +   * sort order of `array` and replaces criteria objects with their corresponding
    +   * values.
    +   *
    +   * @private
    +   * @param {Array} array The array to sort.
    +   * @param {Function} comparer The function to define sort order.
    +   * @returns {Array} Returns `array`.
    +   */
    +  function baseSortBy(array, comparer) {
    +    var length = array.length;
    +    array.sort(comparer);
    +    while (length--) {
    +      array[length] = array[length].value;
    +    }
    +    return array;
    +  }
    +
    +  /**
    +   * The base implementation of `_.sum` and `_.sumBy` without support for
    +   * iteratee shorthands.
    +   *
    +   * @private
    +   * @param {Array} array The array to iterate over.
    +   * @param {Function} iteratee The function invoked per iteration.
    +   * @returns {number} Returns the sum.
    +   */
    +  function baseSum(array, iteratee) {
    +    var result,
    +      index = -1,
    +      length = array.length;
    +    while (++index < length) {
    +      var current = iteratee(array[index]);
    +      if (current !== undefined) {
    +        result = result === undefined ? current : result + current;
    +      }
    +    }
    +    return result;
    +  }
    +
    +  /**
    +   * The base implementation of `_.times` without support for iteratee shorthands
    +   * or max array length checks.
    +   *
    +   * @private
    +   * @param {number} n The number of times to invoke `iteratee`.
    +   * @param {Function} iteratee The function invoked per iteration.
    +   * @returns {Array} Returns the array of results.
    +   */
    +  function baseTimes(n, iteratee) {
    +    var index = -1,
    +      result = Array(n);
    +    while (++index < n) {
    +      result[index] = iteratee(index);
    +    }
    +    return result;
    +  }
    +
    +  /**
    +   * The base implementation of `_.toPairs` and `_.toPairsIn` which creates an array
    +   * of key-value pairs for `object` corresponding to the property names of `props`.
    +   *
    +   * @private
    +   * @param {Object} object The object to query.
    +   * @param {Array} props The property names to get values for.
    +   * @returns {Object} Returns the key-value pairs.
    +   */
    +  function baseToPairs(object, props) {
    +    return arrayMap(props, function (key) {
    +      return [key, object[key]];
    +    });
    +  }
    +
    +  /**
    +   * The base implementation of `_.trim`.
    +   *
    +   * @private
    +   * @param {string} string The string to trim.
    +   * @returns {string} Returns the trimmed string.
    +   */
    +  function baseTrim(string) {
    +    return string ? string.slice(0, trimmedEndIndex(string) + 1).replace(reTrimStart, '') : string;
    +  }
    +
    +  /**
    +   * The base implementation of `_.unary` without support for storing metadata.
    +   *
    +   * @private
    +   * @param {Function} func The function to cap arguments for.
    +   * @returns {Function} Returns the new capped function.
    +   */
    +  function baseUnary(func) {
    +    return function (value) {
    +      return func(value);
    +    };
    +  }
    +
    +  /**
    +   * The base implementation of `_.values` and `_.valuesIn` which creates an
    +   * array of `object` property values corresponding to the property names
    +   * of `props`.
    +   *
    +   * @private
    +   * @param {Object} object The object to query.
    +   * @param {Array} props The property names to get values for.
    +   * @returns {Object} Returns the array of property values.
    +   */
    +  function baseValues(object, props) {
    +    return arrayMap(props, function (key) {
    +      return object[key];
    +    });
    +  }
    +
    +  /**
    +   * Checks if a `cache` value for `key` exists.
    +   *
    +   * @private
    +   * @param {Object} cache The cache to query.
    +   * @param {string} key The key of the entry to check.
    +   * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
    +   */
    +  function cacheHas(cache, key) {
    +    return cache.has(key);
    +  }
    +
    +  /**
    +   * Used by `_.trim` and `_.trimStart` to get the index of the first string symbol
    +   * that is not found in the character symbols.
    +   *
    +   * @private
    +   * @param {Array} strSymbols The string symbols to inspect.
    +   * @param {Array} chrSymbols The character symbols to find.
    +   * @returns {number} Returns the index of the first unmatched string symbol.
    +   */
    +  function charsStartIndex(strSymbols, chrSymbols) {
    +    var index = -1,
    +      length = strSymbols.length;
    +    while (++index < length && baseIndexOf(chrSymbols, strSymbols[index], 0) > -1) {}
    +    return index;
    +  }
    +
    +  /**
    +   * Used by `_.trim` and `_.trimEnd` to get the index of the last string symbol
    +   * that is not found in the character symbols.
    +   *
    +   * @private
    +   * @param {Array} strSymbols The string symbols to inspect.
    +   * @param {Array} chrSymbols The character symbols to find.
    +   * @returns {number} Returns the index of the last unmatched string symbol.
    +   */
    +  function charsEndIndex(strSymbols, chrSymbols) {
    +    var index = strSymbols.length;
    +    while (index-- && baseIndexOf(chrSymbols, strSymbols[index], 0) > -1) {}
    +    return index;
    +  }
    +
    +  /**
    +   * Gets the number of `placeholder` occurrences in `array`.
    +   *
    +   * @private
    +   * @param {Array} array The array to inspect.
    +   * @param {*} placeholder The placeholder to search for.
    +   * @returns {number} Returns the placeholder count.
    +   */
    +  function countHolders(array, placeholder) {
    +    var length = array.length,
    +      result = 0;
    +    while (length--) {
    +      if (array[length] === placeholder) {
    +        ++result;
    +      }
    +    }
    +    return result;
    +  }
    +
    +  /**
    +   * Used by `_.deburr` to convert Latin-1 Supplement and Latin Extended-A
    +   * letters to basic Latin letters.
    +   *
    +   * @private
    +   * @param {string} letter The matched letter to deburr.
    +   * @returns {string} Returns the deburred letter.
    +   */
    +  var deburrLetter = basePropertyOf(deburredLetters);
    +
    +  /**
    +   * Used by `_.escape` to convert characters to HTML entities.
    +   *
    +   * @private
    +   * @param {string} chr The matched character to escape.
    +   * @returns {string} Returns the escaped character.
    +   */
    +  var escapeHtmlChar = basePropertyOf(htmlEscapes);
    +
    +  /**
    +   * Used by `_.template` to escape characters for inclusion in compiled string literals.
    +   *
    +   * @private
    +   * @param {string} chr The matched character to escape.
    +   * @returns {string} Returns the escaped character.
    +   */
    +  function escapeStringChar(chr) {
    +    return '\\' + stringEscapes[chr];
    +  }
    +
    +  /**
    +   * Gets the value at `key` of `object`.
    +   *
    +   * @private
    +   * @param {Object} [object] The object to query.
    +   * @param {string} key The key of the property to get.
    +   * @returns {*} Returns the property value.
    +   */
    +  function getValue(object, key) {
    +    return object == null ? undefined : object[key];
    +  }
    +
    +  /**
    +   * Checks if `string` contains Unicode symbols.
    +   *
    +   * @private
    +   * @param {string} string The string to inspect.
    +   * @returns {boolean} Returns `true` if a symbol is found, else `false`.
    +   */
    +  function hasUnicode(string) {
    +    return reHasUnicode.test(string);
    +  }
    +
    +  /**
    +   * Checks if `string` contains a word composed of Unicode symbols.
    +   *
    +   * @private
    +   * @param {string} string The string to inspect.
    +   * @returns {boolean} Returns `true` if a word is found, else `false`.
    +   */
    +  function hasUnicodeWord(string) {
    +    return reHasUnicodeWord.test(string);
    +  }
    +
    +  /**
    +   * Converts `iterator` to an array.
    +   *
    +   * @private
    +   * @param {Object} iterator The iterator to convert.
    +   * @returns {Array} Returns the converted array.
    +   */
    +  function iteratorToArray(iterator) {
    +    var data,
    +      result = [];
    +    while (!(data = iterator.next()).done) {
    +      result.push(data.value);
    +    }
    +    return result;
    +  }
    +
    +  /**
    +   * Converts `map` to its key-value pairs.
    +   *
    +   * @private
    +   * @param {Object} map The map to convert.
    +   * @returns {Array} Returns the key-value pairs.
    +   */
    +  function mapToArray(map) {
    +    var index = -1,
    +      result = Array(map.size);
    +    map.forEach(function (value, key) {
    +      result[++index] = [key, value];
    +    });
    +    return result;
    +  }
    +
    +  /**
    +   * Creates a unary function that invokes `func` with its argument transformed.
    +   *
    +   * @private
    +   * @param {Function} func The function to wrap.
    +   * @param {Function} transform The argument transform.
    +   * @returns {Function} Returns the new function.
    +   */
    +  function overArg(func, transform) {
    +    return function (arg) {
    +      return func(transform(arg));
    +    };
    +  }
    +
    +  /**
    +   * Replaces all `placeholder` elements in `array` with an internal placeholder
    +   * and returns an array of their indexes.
    +   *
    +   * @private
    +   * @param {Array} array The array to modify.
    +   * @param {*} placeholder The placeholder to replace.
    +   * @returns {Array} Returns the new array of placeholder indexes.
    +   */
    +  function replaceHolders(array, placeholder) {
    +    var index = -1,
    +      length = array.length,
    +      resIndex = 0,
    +      result = [];
    +    while (++index < length) {
    +      var value = array[index];
    +      if (value === placeholder || value === PLACEHOLDER) {
    +        array[index] = PLACEHOLDER;
    +        result[resIndex++] = index;
    +      }
    +    }
    +    return result;
    +  }
    +
    +  /**
    +   * Converts `set` to an array of its values.
    +   *
    +   * @private
    +   * @param {Object} set The set to convert.
    +   * @returns {Array} Returns the values.
    +   */
    +  function setToArray(set) {
    +    var index = -1,
    +      result = Array(set.size);
    +    set.forEach(function (value) {
    +      result[++index] = value;
    +    });
    +    return result;
    +  }
    +
    +  /**
    +   * Converts `set` to its value-value pairs.
    +   *
    +   * @private
    +   * @param {Object} set The set to convert.
    +   * @returns {Array} Returns the value-value pairs.
    +   */
    +  function setToPairs(set) {
    +    var index = -1,
    +      result = Array(set.size);
    +    set.forEach(function (value) {
    +      result[++index] = [value, value];
    +    });
    +    return result;
    +  }
    +
    +  /**
    +   * A specialized version of `_.indexOf` which performs strict equality
    +   * comparisons of values, i.e. `===`.
    +   *
    +   * @private
    +   * @param {Array} array The array to inspect.
    +   * @param {*} value The value to search for.
    +   * @param {number} fromIndex The index to search from.
    +   * @returns {number} Returns the index of the matched value, else `-1`.
    +   */
    +  function strictIndexOf(array, value, fromIndex) {
    +    var index = fromIndex - 1,
    +      length = array.length;
    +    while (++index < length) {
    +      if (array[index] === value) {
    +        return index;
    +      }
    +    }
    +    return -1;
    +  }
    +
    +  /**
    +   * A specialized version of `_.lastIndexOf` which performs strict equality
    +   * comparisons of values, i.e. `===`.
    +   *
    +   * @private
    +   * @param {Array} array The array to inspect.
    +   * @param {*} value The value to search for.
    +   * @param {number} fromIndex The index to search from.
    +   * @returns {number} Returns the index of the matched value, else `-1`.
    +   */
    +  function strictLastIndexOf(array, value, fromIndex) {
    +    var index = fromIndex + 1;
    +    while (index--) {
    +      if (array[index] === value) {
    +        return index;
    +      }
    +    }
    +    return index;
    +  }
    +
    +  /**
    +   * Gets the number of symbols in `string`.
    +   *
    +   * @private
    +   * @param {string} string The string to inspect.
    +   * @returns {number} Returns the string size.
    +   */
    +  function stringSize(string) {
    +    return hasUnicode(string) ? unicodeSize(string) : asciiSize(string);
    +  }
    +
    +  /**
    +   * Converts `string` to an array.
    +   *
    +   * @private
    +   * @param {string} string The string to convert.
    +   * @returns {Array} Returns the converted array.
    +   */
    +  function stringToArray(string) {
    +    return hasUnicode(string) ? unicodeToArray(string) : asciiToArray(string);
    +  }
    +
    +  /**
    +   * Used by `_.trim` and `_.trimEnd` to get the index of the last non-whitespace
    +   * character of `string`.
    +   *
    +   * @private
    +   * @param {string} string The string to inspect.
    +   * @returns {number} Returns the index of the last non-whitespace character.
    +   */
    +  function trimmedEndIndex(string) {
    +    var index = string.length;
    +    while (index-- && reWhitespace.test(string.charAt(index))) {}
    +    return index;
    +  }
    +
    +  /**
    +   * Used by `_.unescape` to convert HTML entities to characters.
    +   *
    +   * @private
    +   * @param {string} chr The matched character to unescape.
    +   * @returns {string} Returns the unescaped character.
    +   */
    +  var unescapeHtmlChar = basePropertyOf(htmlUnescapes);
    +
    +  /**
    +   * Gets the size of a Unicode `string`.
    +   *
    +   * @private
    +   * @param {string} string The string inspect.
    +   * @returns {number} Returns the string size.
    +   */
    +  function unicodeSize(string) {
    +    var result = reUnicode.lastIndex = 0;
    +    while (reUnicode.test(string)) {
    +      ++result;
    +    }
    +    return result;
    +  }
    +
    +  /**
    +   * Converts a Unicode `string` to an array.
    +   *
    +   * @private
    +   * @param {string} string The string to convert.
    +   * @returns {Array} Returns the converted array.
    +   */
    +  function unicodeToArray(string) {
    +    return string.match(reUnicode) || [];
    +  }
    +
    +  /**
    +   * Splits a Unicode `string` into an array of its words.
    +   *
    +   * @private
    +   * @param {string} The string to inspect.
    +   * @returns {Array} Returns the words of `string`.
    +   */
    +  function unicodeWords(string) {
    +    return string.match(reUnicodeWord) || [];
    +  }
    +
    +  /*--------------------------------------------------------------------------*/
    +
    +  /**
    +   * Create a new pristine `lodash` function using the `context` object.
    +   *
    +   * @static
    +   * @memberOf _
    +   * @since 1.1.0
    +   * @category Util
    +   * @param {Object} [context=root] The context object.
    +   * @returns {Function} Returns a new `lodash` function.
    +   * @example
    +   *
    +   * _.mixin({ 'foo': _.constant('foo') });
    +   *
    +   * var lodash = _.runInContext();
    +   * lodash.mixin({ 'bar': lodash.constant('bar') });
    +   *
    +   * _.isFunction(_.foo);
    +   * // => true
    +   * _.isFunction(_.bar);
    +   * // => false
    +   *
    +   * lodash.isFunction(lodash.foo);
    +   * // => false
    +   * lodash.isFunction(lodash.bar);
    +   * // => true
    +   *
    +   * // Create a suped-up `defer` in Node.js.
    +   * var defer = _.runInContext({ 'setTimeout': setImmediate }).defer;
    +   */
    +  var runInContext = function runInContext(context) {
    +    context = context == null ? root : _.defaults(root.Object(), context, _.pick(root, contextProps));
    +
    +    /** Built-in constructor references. */
    +    var Array = context.Array,
    +      Date = context.Date,
    +      Error = context.Error,
    +      Function = context.Function,
    +      Math = context.Math,
    +      Object = context.Object,
    +      RegExp = context.RegExp,
    +      String = context.String,
    +      TypeError = context.TypeError;
    +
    +    /** Used for built-in method references. */
    +    var arrayProto = Array.prototype,
    +      funcProto = Function.prototype,
    +      objectProto = Object.prototype;
    +
    +    /** Used to detect overreaching core-js shims. */
    +    var coreJsData = context['__core-js_shared__'];
    +
    +    /** Used to resolve the decompiled source of functions. */
    +    var funcToString = funcProto.toString;
    +
    +    /** Used to check objects for own properties. */
    +    var hasOwnProperty = objectProto.hasOwnProperty;
    +
    +    /** Used to generate unique IDs. */
    +    var idCounter = 0;
    +
    +    /** Used to detect methods masquerading as native. */
    +    var maskSrcKey = function () {
    +      var uid = /[^.]+$/.exec(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO || '');
    +      return uid ? 'Symbol(src)_1.' + uid : '';
    +    }();
    +
    +    /**
    +     * Used to resolve the
    +     * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
    +     * of values.
    +     */
    +    var nativeObjectToString = objectProto.toString;
    +
    +    /** Used to infer the `Object` constructor. */
    +    var objectCtorString = funcToString.call(Object);
    +
    +    /** Used to restore the original `_` reference in `_.noConflict`. */
    +    var oldDash = root._;
    +
    +    /** Used to detect if a method is native. */
    +    var reIsNative = RegExp('^' + funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\$&').replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$');
    +
    +    /** Built-in value references. */
    +    var Buffer = moduleExports ? context.Buffer : undefined,
    +      Symbol = context.Symbol,
    +      Uint8Array = context.Uint8Array,
    +      allocUnsafe = Buffer ? Buffer.allocUnsafe : undefined,
    +      getPrototype = overArg(Object.getPrototypeOf, Object),
    +      objectCreate = Object.create,
    +      propertyIsEnumerable = objectProto.propertyIsEnumerable,
    +      splice = arrayProto.splice,
    +      spreadableSymbol = Symbol ? Symbol.isConcatSpreadable : undefined,
    +      symIterator = Symbol ? Symbol.iterator : undefined,
    +      symToStringTag = Symbol ? Symbol.toStringTag : undefined;
    +    var defineProperty = function () {
    +      try {
    +        var func = getNative(Object, 'defineProperty');
    +        func({}, '', {});
    +        return func;
    +      } catch (e) {}
    +    }();
    +
    +    /** Mocked built-ins. */
    +    var ctxClearTimeout = context.clearTimeout !== root.clearTimeout && context.clearTimeout,
    +      ctxNow = Date && Date.now !== root.Date.now && Date.now,
    +      ctxSetTimeout = context.setTimeout !== root.setTimeout && context.setTimeout;
    +
    +    /* Built-in method references for those with the same name as other `lodash` methods. */
    +    var nativeCeil = Math.ceil,
    +      nativeFloor = Math.floor,
    +      nativeGetSymbols = Object.getOwnPropertySymbols,
    +      nativeIsBuffer = Buffer ? Buffer.isBuffer : undefined,
    +      nativeIsFinite = context.isFinite,
    +      nativeJoin = arrayProto.join,
    +      nativeKeys = overArg(Object.keys, Object),
    +      nativeMax = Math.max,
    +      nativeMin = Math.min,
    +      nativeNow = Date.now,
    +      nativeParseInt = context.parseInt,
    +      nativeRandom = Math.random,
    +      nativeReverse = arrayProto.reverse;
    +
    +    /* Built-in method references that are verified to be native. */
    +    var DataView = getNative(context, 'DataView'),
    +      Map = getNative(context, 'Map'),
    +      Promise = getNative(context, 'Promise'),
    +      Set = getNative(context, 'Set'),
    +      WeakMap = getNative(context, 'WeakMap'),
    +      nativeCreate = getNative(Object, 'create');
    +
    +    /** Used to store function metadata. */
    +    var metaMap = WeakMap && new WeakMap();
    +
    +    /** Used to lookup unminified function names. */
    +    var realNames = {};
    +
    +    /** Used to detect maps, sets, and weakmaps. */
    +    var dataViewCtorString = toSource(DataView),
    +      mapCtorString = toSource(Map),
    +      promiseCtorString = toSource(Promise),
    +      setCtorString = toSource(Set),
    +      weakMapCtorString = toSource(WeakMap);
    +
    +    /** Used to convert symbols to primitives and strings. */
    +    var symbolProto = Symbol ? Symbol.prototype : undefined,
    +      symbolValueOf = symbolProto ? symbolProto.valueOf : undefined,
    +      symbolToString = symbolProto ? symbolProto.toString : undefined;
    +
    +    /*------------------------------------------------------------------------*/
    +
    +    /**
    +     * Creates a `lodash` object which wraps `value` to enable implicit method
    +     * chain sequences. Methods that operate on and return arrays, collections,
    +     * and functions can be chained together. Methods that retrieve a single value
    +     * or may return a primitive value will automatically end the chain sequence
    +     * and return the unwrapped value. Otherwise, the value must be unwrapped
    +     * with `_#value`.
    +     *
    +     * Explicit chain sequences, which must be unwrapped with `_#value`, may be
    +     * enabled using `_.chain`.
    +     *
    +     * The execution of chained methods is lazy, that is, it's deferred until
    +     * `_#value` is implicitly or explicitly called.
    +     *
    +     * Lazy evaluation allows several methods to support shortcut fusion.
    +     * Shortcut fusion is an optimization to merge iteratee calls; this avoids
    +     * the creation of intermediate arrays and can greatly reduce the number of
    +     * iteratee executions. Sections of a chain sequence qualify for shortcut
    +     * fusion if the section is applied to an array and iteratees accept only
    +     * one argument. The heuristic for whether a section qualifies for shortcut
    +     * fusion is subject to change.
    +     *
    +     * Chaining is supported in custom builds as long as the `_#value` method is
    +     * directly or indirectly included in the build.
    +     *
    +     * In addition to lodash methods, wrappers have `Array` and `String` methods.
    +     *
    +     * The wrapper `Array` methods are:
    +     * `concat`, `join`, `pop`, `push`, `shift`, `sort`, `splice`, and `unshift`
    +     *
    +     * The wrapper `String` methods are:
    +     * `replace` and `split`
    +     *
    +     * The wrapper methods that support shortcut fusion are:
    +     * `at`, `compact`, `drop`, `dropRight`, `dropWhile`, `filter`, `find`,
    +     * `findLast`, `head`, `initial`, `last`, `map`, `reject`, `reverse`, `slice`,
    +     * `tail`, `take`, `takeRight`, `takeRightWhile`, `takeWhile`, and `toArray`
    +     *
    +     * The chainable wrapper methods are:
    +     * `after`, `ary`, `assign`, `assignIn`, `assignInWith`, `assignWith`, `at`,
    +     * `before`, `bind`, `bindAll`, `bindKey`, `castArray`, `chain`, `chunk`,
    +     * `commit`, `compact`, `concat`, `conforms`, `constant`, `countBy`, `create`,
    +     * `curry`, `debounce`, `defaults`, `defaultsDeep`, `defer`, `delay`,
    +     * `difference`, `differenceBy`, `differenceWith`, `drop`, `dropRight`,
    +     * `dropRightWhile`, `dropWhile`, `extend`, `extendWith`, `fill`, `filter`,
    +     * `flatMap`, `flatMapDeep`, `flatMapDepth`, `flatten`, `flattenDeep`,
    +     * `flattenDepth`, `flip`, `flow`, `flowRight`, `fromPairs`, `functions`,
    +     * `functionsIn`, `groupBy`, `initial`, `intersection`, `intersectionBy`,
    +     * `intersectionWith`, `invert`, `invertBy`, `invokeMap`, `iteratee`, `keyBy`,
    +     * `keys`, `keysIn`, `map`, `mapKeys`, `mapValues`, `matches`, `matchesProperty`,
    +     * `memoize`, `merge`, `mergeWith`, `method`, `methodOf`, `mixin`, `negate`,
    +     * `nthArg`, `omit`, `omitBy`, `once`, `orderBy`, `over`, `overArgs`,
    +     * `overEvery`, `overSome`, `partial`, `partialRight`, `partition`, `pick`,
    +     * `pickBy`, `plant`, `property`, `propertyOf`, `pull`, `pullAll`, `pullAllBy`,
    +     * `pullAllWith`, `pullAt`, `push`, `range`, `rangeRight`, `rearg`, `reject`,
    +     * `remove`, `rest`, `reverse`, `sampleSize`, `set`, `setWith`, `shuffle`,
    +     * `slice`, `sort`, `sortBy`, `splice`, `spread`, `tail`, `take`, `takeRight`,
    +     * `takeRightWhile`, `takeWhile`, `tap`, `throttle`, `thru`, `toArray`,
    +     * `toPairs`, `toPairsIn`, `toPath`, `toPlainObject`, `transform`, `unary`,
    +     * `union`, `unionBy`, `unionWith`, `uniq`, `uniqBy`, `uniqWith`, `unset`,
    +     * `unshift`, `unzip`, `unzipWith`, `update`, `updateWith`, `values`,
    +     * `valuesIn`, `without`, `wrap`, `xor`, `xorBy`, `xorWith`, `zip`,
    +     * `zipObject`, `zipObjectDeep`, and `zipWith`
    +     *
    +     * The wrapper methods that are **not** chainable by default are:
    +     * `add`, `attempt`, `camelCase`, `capitalize`, `ceil`, `clamp`, `clone`,
    +     * `cloneDeep`, `cloneDeepWith`, `cloneWith`, `conformsTo`, `deburr`,
    +     * `defaultTo`, `divide`, `each`, `eachRight`, `endsWith`, `eq`, `escape`,
    +     * `escapeRegExp`, `every`, `find`, `findIndex`, `findKey`, `findLast`,
    +     * `findLastIndex`, `findLastKey`, `first`, `floor`, `forEach`, `forEachRight`,
    +     * `forIn`, `forInRight`, `forOwn`, `forOwnRight`, `get`, `gt`, `gte`, `has`,
    +     * `hasIn`, `head`, `identity`, `includes`, `indexOf`, `inRange`, `invoke`,
    +     * `isArguments`, `isArray`, `isArrayBuffer`, `isArrayLike`, `isArrayLikeObject`,
    +     * `isBoolean`, `isBuffer`, `isDate`, `isElement`, `isEmpty`, `isEqual`,
    +     * `isEqualWith`, `isError`, `isFinite`, `isFunction`, `isInteger`, `isLength`,
    +     * `isMap`, `isMatch`, `isMatchWith`, `isNaN`, `isNative`, `isNil`, `isNull`,
    +     * `isNumber`, `isObject`, `isObjectLike`, `isPlainObject`, `isRegExp`,
    +     * `isSafeInteger`, `isSet`, `isString`, `isUndefined`, `isTypedArray`,
    +     * `isWeakMap`, `isWeakSet`, `join`, `kebabCase`, `last`, `lastIndexOf`,
    +     * `lowerCase`, `lowerFirst`, `lt`, `lte`, `max`, `maxBy`, `mean`, `meanBy`,
    +     * `min`, `minBy`, `multiply`, `noConflict`, `noop`, `now`, `nth`, `pad`,
    +     * `padEnd`, `padStart`, `parseInt`, `pop`, `random`, `reduce`, `reduceRight`,
    +     * `repeat`, `result`, `round`, `runInContext`, `sample`, `shift`, `size`,
    +     * `snakeCase`, `some`, `sortedIndex`, `sortedIndexBy`, `sortedLastIndex`,
    +     * `sortedLastIndexBy`, `startCase`, `startsWith`, `stubArray`, `stubFalse`,
    +     * `stubObject`, `stubString`, `stubTrue`, `subtract`, `sum`, `sumBy`,
    +     * `template`, `times`, `toFinite`, `toInteger`, `toJSON`, `toLength`,
    +     * `toLower`, `toNumber`, `toSafeInteger`, `toString`, `toUpper`, `trim`,
    +     * `trimEnd`, `trimStart`, `truncate`, `unescape`, `uniqueId`, `upperCase`,
    +     * `upperFirst`, `value`, and `words`
    +     *
    +     * @name _
    +     * @constructor
    +     * @category Seq
    +     * @param {*} value The value to wrap in a `lodash` instance.
    +     * @returns {Object} Returns the new `lodash` wrapper instance.
    +     * @example
    +     *
    +     * function square(n) {
    +     *   return n * n;
    +     * }
    +     *
    +     * var wrapped = _([1, 2, 3]);
    +     *
    +     * // Returns an unwrapped value.
    +     * wrapped.reduce(_.add);
    +     * // => 6
    +     *
    +     * // Returns a wrapped value.
    +     * var squares = wrapped.map(square);
    +     *
    +     * _.isArray(squares);
    +     * // => false
    +     *
    +     * _.isArray(squares.value());
    +     * // => true
    +     */
    +    function lodash(value) {
    +      if (isObjectLike(value) && !isArray(value) && !(value instanceof LazyWrapper)) {
    +        if (value instanceof LodashWrapper) {
    +          return value;
    +        }
    +        if (hasOwnProperty.call(value, '__wrapped__')) {
    +          return wrapperClone(value);
    +        }
    +      }
    +      return new LodashWrapper(value);
    +    }
    +
    +    /**
    +     * The base implementation of `_.create` without support for assigning
    +     * properties to the created object.
    +     *
    +     * @private
    +     * @param {Object} proto The object to inherit from.
    +     * @returns {Object} Returns the new object.
    +     */
    +    var baseCreate = function () {
    +      function object() {}
    +      return function (proto) {
    +        if (!isObject(proto)) {
    +          return {};
    +        }
    +        if (objectCreate) {
    +          return objectCreate(proto);
    +        }
    +        object.prototype = proto;
    +        var result = new object();
    +        object.prototype = undefined;
    +        return result;
    +      };
    +    }();
    +
    +    /**
    +     * The function whose prototype chain sequence wrappers inherit from.
    +     *
    +     * @private
    +     */
    +    function baseLodash() {
    +      // No operation performed.
    +    }
    +
    +    /**
    +     * The base constructor for creating `lodash` wrapper objects.
    +     *
    +     * @private
    +     * @param {*} value The value to wrap.
    +     * @param {boolean} [chainAll] Enable explicit method chain sequences.
    +     */
    +    function LodashWrapper(value, chainAll) {
    +      this.__wrapped__ = value;
    +      this.__actions__ = [];
    +      this.__chain__ = !!chainAll;
    +      this.__index__ = 0;
    +      this.__values__ = undefined;
    +    }
    +
    +    /**
    +     * By default, the template delimiters used by lodash are like those in
    +     * embedded Ruby (ERB) as well as ES2015 template strings. Change the
    +     * following template settings to use alternative delimiters.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @type {Object}
    +     */
    +    lodash.templateSettings = {
    +      /**
    +       * Used to detect `data` property values to be HTML-escaped.
    +       *
    +       * @memberOf _.templateSettings
    +       * @type {RegExp}
    +       */
    +      'escape': reEscape,
    +      /**
    +       * Used to detect code to be evaluated.
    +       *
    +       * @memberOf _.templateSettings
    +       * @type {RegExp}
    +       */
    +      'evaluate': reEvaluate,
    +      /**
    +       * Used to detect `data` property values to inject.
    +       *
    +       * @memberOf _.templateSettings
    +       * @type {RegExp}
    +       */
    +      'interpolate': reInterpolate,
    +      /**
    +       * Used to reference the data object in the template text.
    +       *
    +       * @memberOf _.templateSettings
    +       * @type {string}
    +       */
    +      'variable': '',
    +      /**
    +       * Used to import variables into the compiled template.
    +       *
    +       * @memberOf _.templateSettings
    +       * @type {Object}
    +       */
    +      'imports': {
    +        /**
    +         * A reference to the `lodash` function.
    +         *
    +         * @memberOf _.templateSettings.imports
    +         * @type {Function}
    +         */
    +        '_': lodash
    +      }
    +    };
    +
    +    // Ensure wrappers are instances of `baseLodash`.
    +    lodash.prototype = baseLodash.prototype;
    +    lodash.prototype.constructor = lodash;
    +    LodashWrapper.prototype = baseCreate(baseLodash.prototype);
    +    LodashWrapper.prototype.constructor = LodashWrapper;
    +
    +    /*------------------------------------------------------------------------*/
    +
    +    /**
    +     * Creates a lazy wrapper object which wraps `value` to enable lazy evaluation.
    +     *
    +     * @private
    +     * @constructor
    +     * @param {*} value The value to wrap.
    +     */
    +    function LazyWrapper(value) {
    +      this.__wrapped__ = value;
    +      this.__actions__ = [];
    +      this.__dir__ = 1;
    +      this.__filtered__ = false;
    +      this.__iteratees__ = [];
    +      this.__takeCount__ = MAX_ARRAY_LENGTH;
    +      this.__views__ = [];
    +    }
    +
    +    /**
    +     * Creates a clone of the lazy wrapper object.
    +     *
    +     * @private
    +     * @name clone
    +     * @memberOf LazyWrapper
    +     * @returns {Object} Returns the cloned `LazyWrapper` object.
    +     */
    +    function lazyClone() {
    +      var result = new LazyWrapper(this.__wrapped__);
    +      result.__actions__ = copyArray(this.__actions__);
    +      result.__dir__ = this.__dir__;
    +      result.__filtered__ = this.__filtered__;
    +      result.__iteratees__ = copyArray(this.__iteratees__);
    +      result.__takeCount__ = this.__takeCount__;
    +      result.__views__ = copyArray(this.__views__);
    +      return result;
    +    }
    +
    +    /**
    +     * Reverses the direction of lazy iteration.
    +     *
    +     * @private
    +     * @name reverse
    +     * @memberOf LazyWrapper
    +     * @returns {Object} Returns the new reversed `LazyWrapper` object.
    +     */
    +    function lazyReverse() {
    +      if (this.__filtered__) {
    +        var result = new LazyWrapper(this);
    +        result.__dir__ = -1;
    +        result.__filtered__ = true;
    +      } else {
    +        result = this.clone();
    +        result.__dir__ *= -1;
    +      }
    +      return result;
    +    }
    +
    +    /**
    +     * Extracts the unwrapped value from its lazy wrapper.
    +     *
    +     * @private
    +     * @name value
    +     * @memberOf LazyWrapper
    +     * @returns {*} Returns the unwrapped value.
    +     */
    +    function lazyValue() {
    +      var array = this.__wrapped__.value(),
    +        dir = this.__dir__,
    +        isArr = isArray(array),
    +        isRight = dir < 0,
    +        arrLength = isArr ? array.length : 0,
    +        view = getView(0, arrLength, this.__views__),
    +        start = view.start,
    +        end = view.end,
    +        length = end - start,
    +        index = isRight ? end : start - 1,
    +        iteratees = this.__iteratees__,
    +        iterLength = iteratees.length,
    +        resIndex = 0,
    +        takeCount = nativeMin(length, this.__takeCount__);
    +      if (!isArr || !isRight && arrLength == length && takeCount == length) {
    +        return baseWrapperValue(array, this.__actions__);
    +      }
    +      var result = [];
    +      outer: while (length-- && resIndex < takeCount) {
    +        index += dir;
    +        var iterIndex = -1,
    +          value = array[index];
    +        while (++iterIndex < iterLength) {
    +          var data = iteratees[iterIndex],
    +            iteratee = data.iteratee,
    +            type = data.type,
    +            computed = iteratee(value);
    +          if (type == LAZY_MAP_FLAG) {
    +            value = computed;
    +          } else if (!computed) {
    +            if (type == LAZY_FILTER_FLAG) {
    +              continue outer;
    +            } else {
    +              break outer;
    +            }
    +          }
    +        }
    +        result[resIndex++] = value;
    +      }
    +      return result;
    +    }
    +
    +    // Ensure `LazyWrapper` is an instance of `baseLodash`.
    +    LazyWrapper.prototype = baseCreate(baseLodash.prototype);
    +    LazyWrapper.prototype.constructor = LazyWrapper;
    +
    +    /*------------------------------------------------------------------------*/
    +
    +    /**
    +     * Creates a hash object.
    +     *
    +     * @private
    +     * @constructor
    +     * @param {Array} [entries] The key-value pairs to cache.
    +     */
    +    function Hash(entries) {
    +      var index = -1,
    +        length = entries == null ? 0 : entries.length;
    +      this.clear();
    +      while (++index < length) {
    +        var entry = entries[index];
    +        this.set(entry[0], entry[1]);
    +      }
    +    }
    +
    +    /**
    +     * Removes all key-value entries from the hash.
    +     *
    +     * @private
    +     * @name clear
    +     * @memberOf Hash
    +     */
    +    function hashClear() {
    +      this.__data__ = nativeCreate ? nativeCreate(null) : {};
    +      this.size = 0;
    +    }
    +
    +    /**
    +     * Removes `key` and its value from the hash.
    +     *
    +     * @private
    +     * @name delete
    +     * @memberOf Hash
    +     * @param {Object} hash The hash to modify.
    +     * @param {string} key The key of the value to remove.
    +     * @returns {boolean} Returns `true` if the entry was removed, else `false`.
    +     */
    +    function hashDelete(key) {
    +      var result = this.has(key) && delete this.__data__[key];
    +      this.size -= result ? 1 : 0;
    +      return result;
    +    }
    +
    +    /**
    +     * Gets the hash value for `key`.
    +     *
    +     * @private
    +     * @name get
    +     * @memberOf Hash
    +     * @param {string} key The key of the value to get.
    +     * @returns {*} Returns the entry value.
    +     */
    +    function hashGet(key) {
    +      var data = this.__data__;
    +      if (nativeCreate) {
    +        var result = data[key];
    +        return result === HASH_UNDEFINED ? undefined : result;
    +      }
    +      return hasOwnProperty.call(data, key) ? data[key] : undefined;
    +    }
    +
    +    /**
    +     * Checks if a hash value for `key` exists.
    +     *
    +     * @private
    +     * @name has
    +     * @memberOf Hash
    +     * @param {string} key The key of the entry to check.
    +     * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
    +     */
    +    function hashHas(key) {
    +      var data = this.__data__;
    +      return nativeCreate ? data[key] !== undefined : hasOwnProperty.call(data, key);
    +    }
    +
    +    /**
    +     * Sets the hash `key` to `value`.
    +     *
    +     * @private
    +     * @name set
    +     * @memberOf Hash
    +     * @param {string} key The key of the value to set.
    +     * @param {*} value The value to set.
    +     * @returns {Object} Returns the hash instance.
    +     */
    +    function hashSet(key, value) {
    +      var data = this.__data__;
    +      this.size += this.has(key) ? 0 : 1;
    +      data[key] = nativeCreate && value === undefined ? HASH_UNDEFINED : value;
    +      return this;
    +    }
    +
    +    // Add methods to `Hash`.
    +    Hash.prototype.clear = hashClear;
    +    Hash.prototype['delete'] = hashDelete;
    +    Hash.prototype.get = hashGet;
    +    Hash.prototype.has = hashHas;
    +    Hash.prototype.set = hashSet;
    +
    +    /*------------------------------------------------------------------------*/
    +
    +    /**
    +     * Creates an list cache object.
    +     *
    +     * @private
    +     * @constructor
    +     * @param {Array} [entries] The key-value pairs to cache.
    +     */
    +    function ListCache(entries) {
    +      var index = -1,
    +        length = entries == null ? 0 : entries.length;
    +      this.clear();
    +      while (++index < length) {
    +        var entry = entries[index];
    +        this.set(entry[0], entry[1]);
    +      }
    +    }
    +
    +    /**
    +     * Removes all key-value entries from the list cache.
    +     *
    +     * @private
    +     * @name clear
    +     * @memberOf ListCache
    +     */
    +    function listCacheClear() {
    +      this.__data__ = [];
    +      this.size = 0;
    +    }
    +
    +    /**
    +     * Removes `key` and its value from the list cache.
    +     *
    +     * @private
    +     * @name delete
    +     * @memberOf ListCache
    +     * @param {string} key The key of the value to remove.
    +     * @returns {boolean} Returns `true` if the entry was removed, else `false`.
    +     */
    +    function listCacheDelete(key) {
    +      var data = this.__data__,
    +        index = assocIndexOf(data, key);
    +      if (index < 0) {
    +        return false;
    +      }
    +      var lastIndex = data.length - 1;
    +      if (index == lastIndex) {
    +        data.pop();
    +      } else {
    +        splice.call(data, index, 1);
    +      }
    +      --this.size;
    +      return true;
    +    }
    +
    +    /**
    +     * Gets the list cache value for `key`.
    +     *
    +     * @private
    +     * @name get
    +     * @memberOf ListCache
    +     * @param {string} key The key of the value to get.
    +     * @returns {*} Returns the entry value.
    +     */
    +    function listCacheGet(key) {
    +      var data = this.__data__,
    +        index = assocIndexOf(data, key);
    +      return index < 0 ? undefined : data[index][1];
    +    }
    +
    +    /**
    +     * Checks if a list cache value for `key` exists.
    +     *
    +     * @private
    +     * @name has
    +     * @memberOf ListCache
    +     * @param {string} key The key of the entry to check.
    +     * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
    +     */
    +    function listCacheHas(key) {
    +      return assocIndexOf(this.__data__, key) > -1;
    +    }
    +
    +    /**
    +     * Sets the list cache `key` to `value`.
    +     *
    +     * @private
    +     * @name set
    +     * @memberOf ListCache
    +     * @param {string} key The key of the value to set.
    +     * @param {*} value The value to set.
    +     * @returns {Object} Returns the list cache instance.
    +     */
    +    function listCacheSet(key, value) {
    +      var data = this.__data__,
    +        index = assocIndexOf(data, key);
    +      if (index < 0) {
    +        ++this.size;
    +        data.push([key, value]);
    +      } else {
    +        data[index][1] = value;
    +      }
    +      return this;
    +    }
    +
    +    // Add methods to `ListCache`.
    +    ListCache.prototype.clear = listCacheClear;
    +    ListCache.prototype['delete'] = listCacheDelete;
    +    ListCache.prototype.get = listCacheGet;
    +    ListCache.prototype.has = listCacheHas;
    +    ListCache.prototype.set = listCacheSet;
    +
    +    /*------------------------------------------------------------------------*/
    +
    +    /**
    +     * Creates a map cache object to store key-value pairs.
    +     *
    +     * @private
    +     * @constructor
    +     * @param {Array} [entries] The key-value pairs to cache.
    +     */
    +    function MapCache(entries) {
    +      var index = -1,
    +        length = entries == null ? 0 : entries.length;
    +      this.clear();
    +      while (++index < length) {
    +        var entry = entries[index];
    +        this.set(entry[0], entry[1]);
    +      }
    +    }
    +
    +    /**
    +     * Removes all key-value entries from the map.
    +     *
    +     * @private
    +     * @name clear
    +     * @memberOf MapCache
    +     */
    +    function mapCacheClear() {
    +      this.size = 0;
    +      this.__data__ = {
    +        'hash': new Hash(),
    +        'map': new (Map || ListCache)(),
    +        'string': new Hash()
    +      };
    +    }
    +
    +    /**
    +     * Removes `key` and its value from the map.
    +     *
    +     * @private
    +     * @name delete
    +     * @memberOf MapCache
    +     * @param {string} key The key of the value to remove.
    +     * @returns {boolean} Returns `true` if the entry was removed, else `false`.
    +     */
    +    function mapCacheDelete(key) {
    +      var result = getMapData(this, key)['delete'](key);
    +      this.size -= result ? 1 : 0;
    +      return result;
    +    }
    +
    +    /**
    +     * Gets the map value for `key`.
    +     *
    +     * @private
    +     * @name get
    +     * @memberOf MapCache
    +     * @param {string} key The key of the value to get.
    +     * @returns {*} Returns the entry value.
    +     */
    +    function mapCacheGet(key) {
    +      return getMapData(this, key).get(key);
    +    }
    +
    +    /**
    +     * Checks if a map value for `key` exists.
    +     *
    +     * @private
    +     * @name has
    +     * @memberOf MapCache
    +     * @param {string} key The key of the entry to check.
    +     * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
    +     */
    +    function mapCacheHas(key) {
    +      return getMapData(this, key).has(key);
    +    }
    +
    +    /**
    +     * Sets the map `key` to `value`.
    +     *
    +     * @private
    +     * @name set
    +     * @memberOf MapCache
    +     * @param {string} key The key of the value to set.
    +     * @param {*} value The value to set.
    +     * @returns {Object} Returns the map cache instance.
    +     */
    +    function mapCacheSet(key, value) {
    +      var data = getMapData(this, key),
    +        size = data.size;
    +      data.set(key, value);
    +      this.size += data.size == size ? 0 : 1;
    +      return this;
    +    }
    +
    +    // Add methods to `MapCache`.
    +    MapCache.prototype.clear = mapCacheClear;
    +    MapCache.prototype['delete'] = mapCacheDelete;
    +    MapCache.prototype.get = mapCacheGet;
    +    MapCache.prototype.has = mapCacheHas;
    +    MapCache.prototype.set = mapCacheSet;
    +
    +    /*------------------------------------------------------------------------*/
    +
    +    /**
    +     *
    +     * Creates an array cache object to store unique values.
    +     *
    +     * @private
    +     * @constructor
    +     * @param {Array} [values] The values to cache.
    +     */
    +    function SetCache(values) {
    +      var index = -1,
    +        length = values == null ? 0 : values.length;
    +      this.__data__ = new MapCache();
    +      while (++index < length) {
    +        this.add(values[index]);
    +      }
    +    }
    +
    +    /**
    +     * Adds `value` to the array cache.
    +     *
    +     * @private
    +     * @name add
    +     * @memberOf SetCache
    +     * @alias push
    +     * @param {*} value The value to cache.
    +     * @returns {Object} Returns the cache instance.
    +     */
    +    function setCacheAdd(value) {
    +      this.__data__.set(value, HASH_UNDEFINED);
    +      return this;
    +    }
    +
    +    /**
    +     * Checks if `value` is in the array cache.
    +     *
    +     * @private
    +     * @name has
    +     * @memberOf SetCache
    +     * @param {*} value The value to search for.
    +     * @returns {number} Returns `true` if `value` is found, else `false`.
    +     */
    +    function setCacheHas(value) {
    +      return this.__data__.has(value);
    +    }
    +
    +    // Add methods to `SetCache`.
    +    SetCache.prototype.add = SetCache.prototype.push = setCacheAdd;
    +    SetCache.prototype.has = setCacheHas;
    +
    +    /*------------------------------------------------------------------------*/
    +
    +    /**
    +     * Creates a stack cache object to store key-value pairs.
    +     *
    +     * @private
    +     * @constructor
    +     * @param {Array} [entries] The key-value pairs to cache.
    +     */
    +    function Stack(entries) {
    +      var data = this.__data__ = new ListCache(entries);
    +      this.size = data.size;
    +    }
    +
    +    /**
    +     * Removes all key-value entries from the stack.
    +     *
    +     * @private
    +     * @name clear
    +     * @memberOf Stack
    +     */
    +    function stackClear() {
    +      this.__data__ = new ListCache();
    +      this.size = 0;
    +    }
    +
    +    /**
    +     * Removes `key` and its value from the stack.
    +     *
    +     * @private
    +     * @name delete
    +     * @memberOf Stack
    +     * @param {string} key The key of the value to remove.
    +     * @returns {boolean} Returns `true` if the entry was removed, else `false`.
    +     */
    +    function stackDelete(key) {
    +      var data = this.__data__,
    +        result = data['delete'](key);
    +      this.size = data.size;
    +      return result;
    +    }
    +
    +    /**
    +     * Gets the stack value for `key`.
    +     *
    +     * @private
    +     * @name get
    +     * @memberOf Stack
    +     * @param {string} key The key of the value to get.
    +     * @returns {*} Returns the entry value.
    +     */
    +    function stackGet(key) {
    +      return this.__data__.get(key);
    +    }
    +
    +    /**
    +     * Checks if a stack value for `key` exists.
    +     *
    +     * @private
    +     * @name has
    +     * @memberOf Stack
    +     * @param {string} key The key of the entry to check.
    +     * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
    +     */
    +    function stackHas(key) {
    +      return this.__data__.has(key);
    +    }
    +
    +    /**
    +     * Sets the stack `key` to `value`.
    +     *
    +     * @private
    +     * @name set
    +     * @memberOf Stack
    +     * @param {string} key The key of the value to set.
    +     * @param {*} value The value to set.
    +     * @returns {Object} Returns the stack cache instance.
    +     */
    +    function stackSet(key, value) {
    +      var data = this.__data__;
    +      if (data instanceof ListCache) {
    +        var pairs = data.__data__;
    +        if (!Map || pairs.length < LARGE_ARRAY_SIZE - 1) {
    +          pairs.push([key, value]);
    +          this.size = ++data.size;
    +          return this;
    +        }
    +        data = this.__data__ = new MapCache(pairs);
    +      }
    +      data.set(key, value);
    +      this.size = data.size;
    +      return this;
    +    }
    +
    +    // Add methods to `Stack`.
    +    Stack.prototype.clear = stackClear;
    +    Stack.prototype['delete'] = stackDelete;
    +    Stack.prototype.get = stackGet;
    +    Stack.prototype.has = stackHas;
    +    Stack.prototype.set = stackSet;
    +
    +    /*------------------------------------------------------------------------*/
    +
    +    /**
    +     * Creates an array of the enumerable property names of the array-like `value`.
    +     *
    +     * @private
    +     * @param {*} value The value to query.
    +     * @param {boolean} inherited Specify returning inherited property names.
    +     * @returns {Array} Returns the array of property names.
    +     */
    +    function arrayLikeKeys(value, inherited) {
    +      var isArr = isArray(value),
    +        isArg = !isArr && isArguments(value),
    +        isBuff = !isArr && !isArg && isBuffer(value),
    +        isType = !isArr && !isArg && !isBuff && isTypedArray(value),
    +        skipIndexes = isArr || isArg || isBuff || isType,
    +        result = skipIndexes ? baseTimes(value.length, String) : [],
    +        length = result.length;
    +      for (var key in value) {
    +        if ((inherited || hasOwnProperty.call(value, key)) && !(skipIndexes && (
    +        // Safari 9 has enumerable `arguments.length` in strict mode.
    +        key == 'length' ||
    +        // Node.js 0.10 has enumerable non-index properties on buffers.
    +        isBuff && (key == 'offset' || key == 'parent') ||
    +        // PhantomJS 2 has enumerable non-index properties on typed arrays.
    +        isType && (key == 'buffer' || key == 'byteLength' || key == 'byteOffset') ||
    +        // Skip index properties.
    +        isIndex(key, length)))) {
    +          result.push(key);
    +        }
    +      }
    +      return result;
    +    }
    +
    +    /**
    +     * A specialized version of `_.sample` for arrays.
    +     *
    +     * @private
    +     * @param {Array} array The array to sample.
    +     * @returns {*} Returns the random element.
    +     */
    +    function arraySample(array) {
    +      var length = array.length;
    +      return length ? array[baseRandom(0, length - 1)] : undefined;
    +    }
    +
    +    /**
    +     * A specialized version of `_.sampleSize` for arrays.
    +     *
    +     * @private
    +     * @param {Array} array The array to sample.
    +     * @param {number} n The number of elements to sample.
    +     * @returns {Array} Returns the random elements.
    +     */
    +    function arraySampleSize(array, n) {
    +      return shuffleSelf(copyArray(array), baseClamp(n, 0, array.length));
    +    }
    +
    +    /**
    +     * A specialized version of `_.shuffle` for arrays.
    +     *
    +     * @private
    +     * @param {Array} array The array to shuffle.
    +     * @returns {Array} Returns the new shuffled array.
    +     */
    +    function arrayShuffle(array) {
    +      return shuffleSelf(copyArray(array));
    +    }
    +
    +    /**
    +     * This function is like `assignValue` except that it doesn't assign
    +     * `undefined` values.
    +     *
    +     * @private
    +     * @param {Object} object The object to modify.
    +     * @param {string} key The key of the property to assign.
    +     * @param {*} value The value to assign.
    +     */
    +    function assignMergeValue(object, key, value) {
    +      if (value !== undefined && !eq(object[key], value) || value === undefined && !(key in object)) {
    +        baseAssignValue(object, key, value);
    +      }
    +    }
    +
    +    /**
    +     * Assigns `value` to `key` of `object` if the existing value is not equivalent
    +     * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
    +     * for equality comparisons.
    +     *
    +     * @private
    +     * @param {Object} object The object to modify.
    +     * @param {string} key The key of the property to assign.
    +     * @param {*} value The value to assign.
    +     */
    +    function assignValue(object, key, value) {
    +      var objValue = object[key];
    +      if (!(hasOwnProperty.call(object, key) && eq(objValue, value)) || value === undefined && !(key in object)) {
    +        baseAssignValue(object, key, value);
    +      }
    +    }
    +
    +    /**
    +     * Gets the index at which the `key` is found in `array` of key-value pairs.
    +     *
    +     * @private
    +     * @param {Array} array The array to inspect.
    +     * @param {*} key The key to search for.
    +     * @returns {number} Returns the index of the matched value, else `-1`.
    +     */
    +    function assocIndexOf(array, key) {
    +      var length = array.length;
    +      while (length--) {
    +        if (eq(array[length][0], key)) {
    +          return length;
    +        }
    +      }
    +      return -1;
    +    }
    +
    +    /**
    +     * Aggregates elements of `collection` on `accumulator` with keys transformed
    +     * by `iteratee` and values set by `setter`.
    +     *
    +     * @private
    +     * @param {Array|Object} collection The collection to iterate over.
    +     * @param {Function} setter The function to set `accumulator` values.
    +     * @param {Function} iteratee The iteratee to transform keys.
    +     * @param {Object} accumulator The initial aggregated object.
    +     * @returns {Function} Returns `accumulator`.
    +     */
    +    function baseAggregator(collection, setter, iteratee, accumulator) {
    +      baseEach(collection, function (value, key, collection) {
    +        setter(accumulator, value, iteratee(value), collection);
    +      });
    +      return accumulator;
    +    }
    +
    +    /**
    +     * The base implementation of `_.assign` without support for multiple sources
    +     * or `customizer` functions.
    +     *
    +     * @private
    +     * @param {Object} object The destination object.
    +     * @param {Object} source The source object.
    +     * @returns {Object} Returns `object`.
    +     */
    +    function baseAssign(object, source) {
    +      return object && copyObject(source, keys(source), object);
    +    }
    +
    +    /**
    +     * The base implementation of `_.assignIn` without support for multiple sources
    +     * or `customizer` functions.
    +     *
    +     * @private
    +     * @param {Object} object The destination object.
    +     * @param {Object} source The source object.
    +     * @returns {Object} Returns `object`.
    +     */
    +    function baseAssignIn(object, source) {
    +      return object && copyObject(source, keysIn(source), object);
    +    }
    +
    +    /**
    +     * The base implementation of `assignValue` and `assignMergeValue` without
    +     * value checks.
    +     *
    +     * @private
    +     * @param {Object} object The object to modify.
    +     * @param {string} key The key of the property to assign.
    +     * @param {*} value The value to assign.
    +     */
    +    function baseAssignValue(object, key, value) {
    +      if (key == '__proto__' && defineProperty) {
    +        defineProperty(object, key, {
    +          'configurable': true,
    +          'enumerable': true,
    +          'value': value,
    +          'writable': true
    +        });
    +      } else {
    +        object[key] = value;
    +      }
    +    }
    +
    +    /**
    +     * The base implementation of `_.at` without support for individual paths.
    +     *
    +     * @private
    +     * @param {Object} object The object to iterate over.
    +     * @param {string[]} paths The property paths to pick.
    +     * @returns {Array} Returns the picked elements.
    +     */
    +    function baseAt(object, paths) {
    +      var index = -1,
    +        length = paths.length,
    +        result = Array(length),
    +        skip = object == null;
    +      while (++index < length) {
    +        result[index] = skip ? undefined : get(object, paths[index]);
    +      }
    +      return result;
    +    }
    +
    +    /**
    +     * The base implementation of `_.clamp` which doesn't coerce arguments.
    +     *
    +     * @private
    +     * @param {number} number The number to clamp.
    +     * @param {number} [lower] The lower bound.
    +     * @param {number} upper The upper bound.
    +     * @returns {number} Returns the clamped number.
    +     */
    +    function baseClamp(number, lower, upper) {
    +      if (number === number) {
    +        if (upper !== undefined) {
    +          number = number <= upper ? number : upper;
    +        }
    +        if (lower !== undefined) {
    +          number = number >= lower ? number : lower;
    +        }
    +      }
    +      return number;
    +    }
    +
    +    /**
    +     * The base implementation of `_.clone` and `_.cloneDeep` which tracks
    +     * traversed objects.
    +     *
    +     * @private
    +     * @param {*} value The value to clone.
    +     * @param {boolean} bitmask The bitmask flags.
    +     *  1 - Deep clone
    +     *  2 - Flatten inherited properties
    +     *  4 - Clone symbols
    +     * @param {Function} [customizer] The function to customize cloning.
    +     * @param {string} [key] The key of `value`.
    +     * @param {Object} [object] The parent object of `value`.
    +     * @param {Object} [stack] Tracks traversed objects and their clone counterparts.
    +     * @returns {*} Returns the cloned value.
    +     */
    +    function baseClone(value, bitmask, customizer, key, object, stack) {
    +      var result,
    +        isDeep = bitmask & CLONE_DEEP_FLAG,
    +        isFlat = bitmask & CLONE_FLAT_FLAG,
    +        isFull = bitmask & CLONE_SYMBOLS_FLAG;
    +      if (customizer) {
    +        result = object ? customizer(value, key, object, stack) : customizer(value);
    +      }
    +      if (result !== undefined) {
    +        return result;
    +      }
    +      if (!isObject(value)) {
    +        return value;
    +      }
    +      var isArr = isArray(value);
    +      if (isArr) {
    +        result = initCloneArray(value);
    +        if (!isDeep) {
    +          return copyArray(value, result);
    +        }
    +      } else {
    +        var tag = getTag(value),
    +          isFunc = tag == funcTag || tag == genTag;
    +        if (isBuffer(value)) {
    +          return cloneBuffer(value, isDeep);
    +        }
    +        if (tag == objectTag || tag == argsTag || isFunc && !object) {
    +          result = isFlat || isFunc ? {} : initCloneObject(value);
    +          if (!isDeep) {
    +            return isFlat ? copySymbolsIn(value, baseAssignIn(result, value)) : copySymbols(value, baseAssign(result, value));
    +          }
    +        } else {
    +          if (!cloneableTags[tag]) {
    +            return object ? value : {};
    +          }
    +          result = initCloneByTag(value, tag, isDeep);
    +        }
    +      }
    +      // Check for circular references and return its corresponding clone.
    +      stack || (stack = new Stack());
    +      var stacked = stack.get(value);
    +      if (stacked) {
    +        return stacked;
    +      }
    +      stack.set(value, result);
    +      if (isSet(value)) {
    +        value.forEach(function (subValue) {
    +          result.add(baseClone(subValue, bitmask, customizer, subValue, value, stack));
    +        });
    +      } else if (isMap(value)) {
    +        value.forEach(function (subValue, key) {
    +          result.set(key, baseClone(subValue, bitmask, customizer, key, value, stack));
    +        });
    +      }
    +      var keysFunc = isFull ? isFlat ? getAllKeysIn : getAllKeys : isFlat ? keysIn : keys;
    +      var props = isArr ? undefined : keysFunc(value);
    +      arrayEach(props || value, function (subValue, key) {
    +        if (props) {
    +          key = subValue;
    +          subValue = value[key];
    +        }
    +        // Recursively populate clone (susceptible to call stack limits).
    +        assignValue(result, key, baseClone(subValue, bitmask, customizer, key, value, stack));
    +      });
    +      return result;
    +    }
    +
    +    /**
    +     * The base implementation of `_.conforms` which doesn't clone `source`.
    +     *
    +     * @private
    +     * @param {Object} source The object of property predicates to conform to.
    +     * @returns {Function} Returns the new spec function.
    +     */
    +    function baseConforms(source) {
    +      var props = keys(source);
    +      return function (object) {
    +        return baseConformsTo(object, source, props);
    +      };
    +    }
    +
    +    /**
    +     * The base implementation of `_.conformsTo` which accepts `props` to check.
    +     *
    +     * @private
    +     * @param {Object} object The object to inspect.
    +     * @param {Object} source The object of property predicates to conform to.
    +     * @returns {boolean} Returns `true` if `object` conforms, else `false`.
    +     */
    +    function baseConformsTo(object, source, props) {
    +      var length = props.length;
    +      if (object == null) {
    +        return !length;
    +      }
    +      object = Object(object);
    +      while (length--) {
    +        var key = props[length],
    +          predicate = source[key],
    +          value = object[key];
    +        if (value === undefined && !(key in object) || !predicate(value)) {
    +          return false;
    +        }
    +      }
    +      return true;
    +    }
    +
    +    /**
    +     * The base implementation of `_.delay` and `_.defer` which accepts `args`
    +     * to provide to `func`.
    +     *
    +     * @private
    +     * @param {Function} func The function to delay.
    +     * @param {number} wait The number of milliseconds to delay invocation.
    +     * @param {Array} args The arguments to provide to `func`.
    +     * @returns {number|Object} Returns the timer id or timeout object.
    +     */
    +    function baseDelay(func, wait, args) {
    +      if (typeof func != 'function') {
    +        throw new TypeError(FUNC_ERROR_TEXT);
    +      }
    +      return setTimeout(function () {
    +        func.apply(undefined, args);
    +      }, wait);
    +    }
    +
    +    /**
    +     * The base implementation of methods like `_.difference` without support
    +     * for excluding multiple arrays or iteratee shorthands.
    +     *
    +     * @private
    +     * @param {Array} array The array to inspect.
    +     * @param {Array} values The values to exclude.
    +     * @param {Function} [iteratee] The iteratee invoked per element.
    +     * @param {Function} [comparator] The comparator invoked per element.
    +     * @returns {Array} Returns the new array of filtered values.
    +     */
    +    function baseDifference(array, values, iteratee, comparator) {
    +      var index = -1,
    +        includes = arrayIncludes,
    +        isCommon = true,
    +        length = array.length,
    +        result = [],
    +        valuesLength = values.length;
    +      if (!length) {
    +        return result;
    +      }
    +      if (iteratee) {
    +        values = arrayMap(values, baseUnary(iteratee));
    +      }
    +      if (comparator) {
    +        includes = arrayIncludesWith;
    +        isCommon = false;
    +      } else if (values.length >= LARGE_ARRAY_SIZE) {
    +        includes = cacheHas;
    +        isCommon = false;
    +        values = new SetCache(values);
    +      }
    +      outer: while (++index < length) {
    +        var value = array[index],
    +          computed = iteratee == null ? value : iteratee(value);
    +        value = comparator || value !== 0 ? value : 0;
    +        if (isCommon && computed === computed) {
    +          var valuesIndex = valuesLength;
    +          while (valuesIndex--) {
    +            if (values[valuesIndex] === computed) {
    +              continue outer;
    +            }
    +          }
    +          result.push(value);
    +        } else if (!includes(values, computed, comparator)) {
    +          result.push(value);
    +        }
    +      }
    +      return result;
    +    }
    +
    +    /**
    +     * The base implementation of `_.forEach` without support for iteratee shorthands.
    +     *
    +     * @private
    +     * @param {Array|Object} collection The collection to iterate over.
    +     * @param {Function} iteratee The function invoked per iteration.
    +     * @returns {Array|Object} Returns `collection`.
    +     */
    +    var baseEach = createBaseEach(baseForOwn);
    +
    +    /**
    +     * The base implementation of `_.forEachRight` without support for iteratee shorthands.
    +     *
    +     * @private
    +     * @param {Array|Object} collection The collection to iterate over.
    +     * @param {Function} iteratee The function invoked per iteration.
    +     * @returns {Array|Object} Returns `collection`.
    +     */
    +    var baseEachRight = createBaseEach(baseForOwnRight, true);
    +
    +    /**
    +     * The base implementation of `_.every` without support for iteratee shorthands.
    +     *
    +     * @private
    +     * @param {Array|Object} collection The collection to iterate over.
    +     * @param {Function} predicate The function invoked per iteration.
    +     * @returns {boolean} Returns `true` if all elements pass the predicate check,
    +     *  else `false`
    +     */
    +    function baseEvery(collection, predicate) {
    +      var result = true;
    +      baseEach(collection, function (value, index, collection) {
    +        result = !!predicate(value, index, collection);
    +        return result;
    +      });
    +      return result;
    +    }
    +
    +    /**
    +     * The base implementation of methods like `_.max` and `_.min` which accepts a
    +     * `comparator` to determine the extremum value.
    +     *
    +     * @private
    +     * @param {Array} array The array to iterate over.
    +     * @param {Function} iteratee The iteratee invoked per iteration.
    +     * @param {Function} comparator The comparator used to compare values.
    +     * @returns {*} Returns the extremum value.
    +     */
    +    function baseExtremum(array, iteratee, comparator) {
    +      var index = -1,
    +        length = array.length;
    +      while (++index < length) {
    +        var value = array[index],
    +          current = iteratee(value);
    +        if (current != null && (computed === undefined ? current === current && !isSymbol(current) : comparator(current, computed))) {
    +          var computed = current,
    +            result = value;
    +        }
    +      }
    +      return result;
    +    }
    +
    +    /**
    +     * The base implementation of `_.fill` without an iteratee call guard.
    +     *
    +     * @private
    +     * @param {Array} array The array to fill.
    +     * @param {*} value The value to fill `array` with.
    +     * @param {number} [start=0] The start position.
    +     * @param {number} [end=array.length] The end position.
    +     * @returns {Array} Returns `array`.
    +     */
    +    function baseFill(array, value, start, end) {
    +      var length = array.length;
    +      start = toInteger(start);
    +      if (start < 0) {
    +        start = -start > length ? 0 : length + start;
    +      }
    +      end = end === undefined || end > length ? length : toInteger(end);
    +      if (end < 0) {
    +        end += length;
    +      }
    +      end = start > end ? 0 : toLength(end);
    +      while (start < end) {
    +        array[start++] = value;
    +      }
    +      return array;
    +    }
    +
    +    /**
    +     * The base implementation of `_.filter` without support for iteratee shorthands.
    +     *
    +     * @private
    +     * @param {Array|Object} collection The collection to iterate over.
    +     * @param {Function} predicate The function invoked per iteration.
    +     * @returns {Array} Returns the new filtered array.
    +     */
    +    function baseFilter(collection, predicate) {
    +      var result = [];
    +      baseEach(collection, function (value, index, collection) {
    +        if (predicate(value, index, collection)) {
    +          result.push(value);
    +        }
    +      });
    +      return result;
    +    }
    +
    +    /**
    +     * The base implementation of `_.flatten` with support for restricting flattening.
    +     *
    +     * @private
    +     * @param {Array} array The array to flatten.
    +     * @param {number} depth The maximum recursion depth.
    +     * @param {boolean} [predicate=isFlattenable] The function invoked per iteration.
    +     * @param {boolean} [isStrict] Restrict to values that pass `predicate` checks.
    +     * @param {Array} [result=[]] The initial result value.
    +     * @returns {Array} Returns the new flattened array.
    +     */
    +    function baseFlatten(array, depth, predicate, isStrict, result) {
    +      var index = -1,
    +        length = array.length;
    +      predicate || (predicate = isFlattenable);
    +      result || (result = []);
    +      while (++index < length) {
    +        var value = array[index];
    +        if (depth > 0 && predicate(value)) {
    +          if (depth > 1) {
    +            // Recursively flatten arrays (susceptible to call stack limits).
    +            baseFlatten(value, depth - 1, predicate, isStrict, result);
    +          } else {
    +            arrayPush(result, value);
    +          }
    +        } else if (!isStrict) {
    +          result[result.length] = value;
    +        }
    +      }
    +      return result;
    +    }
    +
    +    /**
    +     * The base implementation of `baseForOwn` which iterates over `object`
    +     * properties returned by `keysFunc` and invokes `iteratee` for each property.
    +     * Iteratee functions may exit iteration early by explicitly returning `false`.
    +     *
    +     * @private
    +     * @param {Object} object The object to iterate over.
    +     * @param {Function} iteratee The function invoked per iteration.
    +     * @param {Function} keysFunc The function to get the keys of `object`.
    +     * @returns {Object} Returns `object`.
    +     */
    +    var baseFor = createBaseFor();
    +
    +    /**
    +     * This function is like `baseFor` except that it iterates over properties
    +     * in the opposite order.
    +     *
    +     * @private
    +     * @param {Object} object The object to iterate over.
    +     * @param {Function} iteratee The function invoked per iteration.
    +     * @param {Function} keysFunc The function to get the keys of `object`.
    +     * @returns {Object} Returns `object`.
    +     */
    +    var baseForRight = createBaseFor(true);
    +
    +    /**
    +     * The base implementation of `_.forOwn` without support for iteratee shorthands.
    +     *
    +     * @private
    +     * @param {Object} object The object to iterate over.
    +     * @param {Function} iteratee The function invoked per iteration.
    +     * @returns {Object} Returns `object`.
    +     */
    +    function baseForOwn(object, iteratee) {
    +      return object && baseFor(object, iteratee, keys);
    +    }
    +
    +    /**
    +     * The base implementation of `_.forOwnRight` without support for iteratee shorthands.
    +     *
    +     * @private
    +     * @param {Object} object The object to iterate over.
    +     * @param {Function} iteratee The function invoked per iteration.
    +     * @returns {Object} Returns `object`.
    +     */
    +    function baseForOwnRight(object, iteratee) {
    +      return object && baseForRight(object, iteratee, keys);
    +    }
    +
    +    /**
    +     * The base implementation of `_.functions` which creates an array of
    +     * `object` function property names filtered from `props`.
    +     *
    +     * @private
    +     * @param {Object} object The object to inspect.
    +     * @param {Array} props The property names to filter.
    +     * @returns {Array} Returns the function names.
    +     */
    +    function baseFunctions(object, props) {
    +      return arrayFilter(props, function (key) {
    +        return isFunction(object[key]);
    +      });
    +    }
    +
    +    /**
    +     * The base implementation of `_.get` without support for default values.
    +     *
    +     * @private
    +     * @param {Object} object The object to query.
    +     * @param {Array|string} path The path of the property to get.
    +     * @returns {*} Returns the resolved value.
    +     */
    +    function baseGet(object, path) {
    +      path = castPath(path, object);
    +      var index = 0,
    +        length = path.length;
    +      while (object != null && index < length) {
    +        object = object[toKey(path[index++])];
    +      }
    +      return index && index == length ? object : undefined;
    +    }
    +
    +    /**
    +     * The base implementation of `getAllKeys` and `getAllKeysIn` which uses
    +     * `keysFunc` and `symbolsFunc` to get the enumerable property names and
    +     * symbols of `object`.
    +     *
    +     * @private
    +     * @param {Object} object The object to query.
    +     * @param {Function} keysFunc The function to get the keys of `object`.
    +     * @param {Function} symbolsFunc The function to get the symbols of `object`.
    +     * @returns {Array} Returns the array of property names and symbols.
    +     */
    +    function baseGetAllKeys(object, keysFunc, symbolsFunc) {
    +      var result = keysFunc(object);
    +      return isArray(object) ? result : arrayPush(result, symbolsFunc(object));
    +    }
    +
    +    /**
    +     * The base implementation of `getTag` without fallbacks for buggy environments.
    +     *
    +     * @private
    +     * @param {*} value The value to query.
    +     * @returns {string} Returns the `toStringTag`.
    +     */
    +    function baseGetTag(value) {
    +      if (value == null) {
    +        return value === undefined ? undefinedTag : nullTag;
    +      }
    +      return symToStringTag && symToStringTag in Object(value) ? getRawTag(value) : objectToString(value);
    +    }
    +
    +    /**
    +     * The base implementation of `_.gt` which doesn't coerce arguments.
    +     *
    +     * @private
    +     * @param {*} value The value to compare.
    +     * @param {*} other The other value to compare.
    +     * @returns {boolean} Returns `true` if `value` is greater than `other`,
    +     *  else `false`.
    +     */
    +    function baseGt(value, other) {
    +      return value > other;
    +    }
    +
    +    /**
    +     * The base implementation of `_.has` without support for deep paths.
    +     *
    +     * @private
    +     * @param {Object} [object] The object to query.
    +     * @param {Array|string} key The key to check.
    +     * @returns {boolean} Returns `true` if `key` exists, else `false`.
    +     */
    +    function baseHas(object, key) {
    +      return object != null && hasOwnProperty.call(object, key);
    +    }
    +
    +    /**
    +     * The base implementation of `_.hasIn` without support for deep paths.
    +     *
    +     * @private
    +     * @param {Object} [object] The object to query.
    +     * @param {Array|string} key The key to check.
    +     * @returns {boolean} Returns `true` if `key` exists, else `false`.
    +     */
    +    function baseHasIn(object, key) {
    +      return object != null && key in Object(object);
    +    }
    +
    +    /**
    +     * The base implementation of `_.inRange` which doesn't coerce arguments.
    +     *
    +     * @private
    +     * @param {number} number The number to check.
    +     * @param {number} start The start of the range.
    +     * @param {number} end The end of the range.
    +     * @returns {boolean} Returns `true` if `number` is in the range, else `false`.
    +     */
    +    function baseInRange(number, start, end) {
    +      return number >= nativeMin(start, end) && number < nativeMax(start, end);
    +    }
    +
    +    /**
    +     * The base implementation of methods like `_.intersection`, without support
    +     * for iteratee shorthands, that accepts an array of arrays to inspect.
    +     *
    +     * @private
    +     * @param {Array} arrays The arrays to inspect.
    +     * @param {Function} [iteratee] The iteratee invoked per element.
    +     * @param {Function} [comparator] The comparator invoked per element.
    +     * @returns {Array} Returns the new array of shared values.
    +     */
    +    function baseIntersection(arrays, iteratee, comparator) {
    +      var includes = comparator ? arrayIncludesWith : arrayIncludes,
    +        length = arrays[0].length,
    +        othLength = arrays.length,
    +        othIndex = othLength,
    +        caches = Array(othLength),
    +        maxLength = Infinity,
    +        result = [];
    +      while (othIndex--) {
    +        var array = arrays[othIndex];
    +        if (othIndex && iteratee) {
    +          array = arrayMap(array, baseUnary(iteratee));
    +        }
    +        maxLength = nativeMin(array.length, maxLength);
    +        caches[othIndex] = !comparator && (iteratee || length >= 120 && array.length >= 120) ? new SetCache(othIndex && array) : undefined;
    +      }
    +      array = arrays[0];
    +      var index = -1,
    +        seen = caches[0];
    +      outer: while (++index < length && result.length < maxLength) {
    +        var value = array[index],
    +          computed = iteratee ? iteratee(value) : value;
    +        value = comparator || value !== 0 ? value : 0;
    +        if (!(seen ? cacheHas(seen, computed) : includes(result, computed, comparator))) {
    +          othIndex = othLength;
    +          while (--othIndex) {
    +            var cache = caches[othIndex];
    +            if (!(cache ? cacheHas(cache, computed) : includes(arrays[othIndex], computed, comparator))) {
    +              continue outer;
    +            }
    +          }
    +          if (seen) {
    +            seen.push(computed);
    +          }
    +          result.push(value);
    +        }
    +      }
    +      return result;
    +    }
    +
    +    /**
    +     * The base implementation of `_.invert` and `_.invertBy` which inverts
    +     * `object` with values transformed by `iteratee` and set by `setter`.
    +     *
    +     * @private
    +     * @param {Object} object The object to iterate over.
    +     * @param {Function} setter The function to set `accumulator` values.
    +     * @param {Function} iteratee The iteratee to transform values.
    +     * @param {Object} accumulator The initial inverted object.
    +     * @returns {Function} Returns `accumulator`.
    +     */
    +    function baseInverter(object, setter, iteratee, accumulator) {
    +      baseForOwn(object, function (value, key, object) {
    +        setter(accumulator, iteratee(value), key, object);
    +      });
    +      return accumulator;
    +    }
    +
    +    /**
    +     * The base implementation of `_.invoke` without support for individual
    +     * method arguments.
    +     *
    +     * @private
    +     * @param {Object} object The object to query.
    +     * @param {Array|string} path The path of the method to invoke.
    +     * @param {Array} args The arguments to invoke the method with.
    +     * @returns {*} Returns the result of the invoked method.
    +     */
    +    function baseInvoke(object, path, args) {
    +      path = castPath(path, object);
    +      object = parent(object, path);
    +      var func = object == null ? object : object[toKey(last(path))];
    +      return func == null ? undefined : apply(func, object, args);
    +    }
    +
    +    /**
    +     * The base implementation of `_.isArguments`.
    +     *
    +     * @private
    +     * @param {*} value The value to check.
    +     * @returns {boolean} Returns `true` if `value` is an `arguments` object,
    +     */
    +    function baseIsArguments(value) {
    +      return isObjectLike(value) && baseGetTag(value) == argsTag;
    +    }
    +
    +    /**
    +     * The base implementation of `_.isArrayBuffer` without Node.js optimizations.
    +     *
    +     * @private
    +     * @param {*} value The value to check.
    +     * @returns {boolean} Returns `true` if `value` is an array buffer, else `false`.
    +     */
    +    function baseIsArrayBuffer(value) {
    +      return isObjectLike(value) && baseGetTag(value) == arrayBufferTag;
    +    }
    +
    +    /**
    +     * The base implementation of `_.isDate` without Node.js optimizations.
    +     *
    +     * @private
    +     * @param {*} value The value to check.
    +     * @returns {boolean} Returns `true` if `value` is a date object, else `false`.
    +     */
    +    function baseIsDate(value) {
    +      return isObjectLike(value) && baseGetTag(value) == dateTag;
    +    }
    +
    +    /**
    +     * The base implementation of `_.isEqual` which supports partial comparisons
    +     * and tracks traversed objects.
    +     *
    +     * @private
    +     * @param {*} value The value to compare.
    +     * @param {*} other The other value to compare.
    +     * @param {boolean} bitmask The bitmask flags.
    +     *  1 - Unordered comparison
    +     *  2 - Partial comparison
    +     * @param {Function} [customizer] The function to customize comparisons.
    +     * @param {Object} [stack] Tracks traversed `value` and `other` objects.
    +     * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
    +     */
    +    function baseIsEqual(value, other, bitmask, customizer, stack) {
    +      if (value === other) {
    +        return true;
    +      }
    +      if (value == null || other == null || !isObjectLike(value) && !isObjectLike(other)) {
    +        return value !== value && other !== other;
    +      }
    +      return baseIsEqualDeep(value, other, bitmask, customizer, baseIsEqual, stack);
    +    }
    +
    +    /**
    +     * A specialized version of `baseIsEqual` for arrays and objects which performs
    +     * deep comparisons and tracks traversed objects enabling objects with circular
    +     * references to be compared.
    +     *
    +     * @private
    +     * @param {Object} object The object to compare.
    +     * @param {Object} other The other object to compare.
    +     * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.
    +     * @param {Function} customizer The function to customize comparisons.
    +     * @param {Function} equalFunc The function to determine equivalents of values.
    +     * @param {Object} [stack] Tracks traversed `object` and `other` objects.
    +     * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
    +     */
    +    function baseIsEqualDeep(object, other, bitmask, customizer, equalFunc, stack) {
    +      var objIsArr = isArray(object),
    +        othIsArr = isArray(other),
    +        objTag = objIsArr ? arrayTag : getTag(object),
    +        othTag = othIsArr ? arrayTag : getTag(other);
    +      objTag = objTag == argsTag ? objectTag : objTag;
    +      othTag = othTag == argsTag ? objectTag : othTag;
    +      var objIsObj = objTag == objectTag,
    +        othIsObj = othTag == objectTag,
    +        isSameTag = objTag == othTag;
    +      if (isSameTag && isBuffer(object)) {
    +        if (!isBuffer(other)) {
    +          return false;
    +        }
    +        objIsArr = true;
    +        objIsObj = false;
    +      }
    +      if (isSameTag && !objIsObj) {
    +        stack || (stack = new Stack());
    +        return objIsArr || isTypedArray(object) ? equalArrays(object, other, bitmask, customizer, equalFunc, stack) : equalByTag(object, other, objTag, bitmask, customizer, equalFunc, stack);
    +      }
    +      if (!(bitmask & COMPARE_PARTIAL_FLAG)) {
    +        var objIsWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__'),
    +          othIsWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__');
    +        if (objIsWrapped || othIsWrapped) {
    +          var objUnwrapped = objIsWrapped ? object.value() : object,
    +            othUnwrapped = othIsWrapped ? other.value() : other;
    +          stack || (stack = new Stack());
    +          return equalFunc(objUnwrapped, othUnwrapped, bitmask, customizer, stack);
    +        }
    +      }
    +      if (!isSameTag) {
    +        return false;
    +      }
    +      stack || (stack = new Stack());
    +      return equalObjects(object, other, bitmask, customizer, equalFunc, stack);
    +    }
    +
    +    /**
    +     * The base implementation of `_.isMap` without Node.js optimizations.
    +     *
    +     * @private
    +     * @param {*} value The value to check.
    +     * @returns {boolean} Returns `true` if `value` is a map, else `false`.
    +     */
    +    function baseIsMap(value) {
    +      return isObjectLike(value) && getTag(value) == mapTag;
    +    }
    +
    +    /**
    +     * The base implementation of `_.isMatch` without support for iteratee shorthands.
    +     *
    +     * @private
    +     * @param {Object} object The object to inspect.
    +     * @param {Object} source The object of property values to match.
    +     * @param {Array} matchData The property names, values, and compare flags to match.
    +     * @param {Function} [customizer] The function to customize comparisons.
    +     * @returns {boolean} Returns `true` if `object` is a match, else `false`.
    +     */
    +    function baseIsMatch(object, source, matchData, customizer) {
    +      var index = matchData.length,
    +        length = index,
    +        noCustomizer = !customizer;
    +      if (object == null) {
    +        return !length;
    +      }
    +      object = Object(object);
    +      while (index--) {
    +        var data = matchData[index];
    +        if (noCustomizer && data[2] ? data[1] !== object[data[0]] : !(data[0] in object)) {
    +          return false;
    +        }
    +      }
    +      while (++index < length) {
    +        data = matchData[index];
    +        var key = data[0],
    +          objValue = object[key],
    +          srcValue = data[1];
    +        if (noCustomizer && data[2]) {
    +          if (objValue === undefined && !(key in object)) {
    +            return false;
    +          }
    +        } else {
    +          var stack = new Stack();
    +          if (customizer) {
    +            var result = customizer(objValue, srcValue, key, object, source, stack);
    +          }
    +          if (!(result === undefined ? baseIsEqual(srcValue, objValue, COMPARE_PARTIAL_FLAG | COMPARE_UNORDERED_FLAG, customizer, stack) : result)) {
    +            return false;
    +          }
    +        }
    +      }
    +      return true;
    +    }
    +
    +    /**
    +     * The base implementation of `_.isNative` without bad shim checks.
    +     *
    +     * @private
    +     * @param {*} value The value to check.
    +     * @returns {boolean} Returns `true` if `value` is a native function,
    +     *  else `false`.
    +     */
    +    function baseIsNative(value) {
    +      if (!isObject(value) || isMasked(value)) {
    +        return false;
    +      }
    +      var pattern = isFunction(value) ? reIsNative : reIsHostCtor;
    +      return pattern.test(toSource(value));
    +    }
    +
    +    /**
    +     * The base implementation of `_.isRegExp` without Node.js optimizations.
    +     *
    +     * @private
    +     * @param {*} value The value to check.
    +     * @returns {boolean} Returns `true` if `value` is a regexp, else `false`.
    +     */
    +    function baseIsRegExp(value) {
    +      return isObjectLike(value) && baseGetTag(value) == regexpTag;
    +    }
    +
    +    /**
    +     * The base implementation of `_.isSet` without Node.js optimizations.
    +     *
    +     * @private
    +     * @param {*} value The value to check.
    +     * @returns {boolean} Returns `true` if `value` is a set, else `false`.
    +     */
    +    function baseIsSet(value) {
    +      return isObjectLike(value) && getTag(value) == setTag;
    +    }
    +
    +    /**
    +     * The base implementation of `_.isTypedArray` without Node.js optimizations.
    +     *
    +     * @private
    +     * @param {*} value The value to check.
    +     * @returns {boolean} Returns `true` if `value` is a typed array, else `false`.
    +     */
    +    function baseIsTypedArray(value) {
    +      return isObjectLike(value) && isLength(value.length) && !!typedArrayTags[baseGetTag(value)];
    +    }
    +
    +    /**
    +     * The base implementation of `_.iteratee`.
    +     *
    +     * @private
    +     * @param {*} [value=_.identity] The value to convert to an iteratee.
    +     * @returns {Function} Returns the iteratee.
    +     */
    +    function baseIteratee(value) {
    +      // Don't store the `typeof` result in a variable to avoid a JIT bug in Safari 9.
    +      // See https://bugs.webkit.org/show_bug.cgi?id=156034 for more details.
    +      if (typeof value == 'function') {
    +        return value;
    +      }
    +      if (value == null) {
    +        return identity;
    +      }
    +      if (typeof value == 'object') {
    +        return isArray(value) ? baseMatchesProperty(value[0], value[1]) : baseMatches(value);
    +      }
    +      return property(value);
    +    }
    +
    +    /**
    +     * The base implementation of `_.keys` which doesn't treat sparse arrays as dense.
    +     *
    +     * @private
    +     * @param {Object} object The object to query.
    +     * @returns {Array} Returns the array of property names.
    +     */
    +    function baseKeys(object) {
    +      if (!isPrototype(object)) {
    +        return nativeKeys(object);
    +      }
    +      var result = [];
    +      for (var key in Object(object)) {
    +        if (hasOwnProperty.call(object, key) && key != 'constructor') {
    +          result.push(key);
    +        }
    +      }
    +      return result;
    +    }
    +
    +    /**
    +     * The base implementation of `_.keysIn` which doesn't treat sparse arrays as dense.
    +     *
    +     * @private
    +     * @param {Object} object The object to query.
    +     * @returns {Array} Returns the array of property names.
    +     */
    +    function baseKeysIn(object) {
    +      if (!isObject(object)) {
    +        return nativeKeysIn(object);
    +      }
    +      var isProto = isPrototype(object),
    +        result = [];
    +      for (var key in object) {
    +        if (!(key == 'constructor' && (isProto || !hasOwnProperty.call(object, key)))) {
    +          result.push(key);
    +        }
    +      }
    +      return result;
    +    }
    +
    +    /**
    +     * The base implementation of `_.lt` which doesn't coerce arguments.
    +     *
    +     * @private
    +     * @param {*} value The value to compare.
    +     * @param {*} other The other value to compare.
    +     * @returns {boolean} Returns `true` if `value` is less than `other`,
    +     *  else `false`.
    +     */
    +    function baseLt(value, other) {
    +      return value < other;
    +    }
    +
    +    /**
    +     * The base implementation of `_.map` without support for iteratee shorthands.
    +     *
    +     * @private
    +     * @param {Array|Object} collection The collection to iterate over.
    +     * @param {Function} iteratee The function invoked per iteration.
    +     * @returns {Array} Returns the new mapped array.
    +     */
    +    function baseMap(collection, iteratee) {
    +      var index = -1,
    +        result = isArrayLike(collection) ? Array(collection.length) : [];
    +      baseEach(collection, function (value, key, collection) {
    +        result[++index] = iteratee(value, key, collection);
    +      });
    +      return result;
    +    }
    +
    +    /**
    +     * The base implementation of `_.matches` which doesn't clone `source`.
    +     *
    +     * @private
    +     * @param {Object} source The object of property values to match.
    +     * @returns {Function} Returns the new spec function.
    +     */
    +    function baseMatches(source) {
    +      var matchData = getMatchData(source);
    +      if (matchData.length == 1 && matchData[0][2]) {
    +        return matchesStrictComparable(matchData[0][0], matchData[0][1]);
    +      }
    +      return function (object) {
    +        return object === source || baseIsMatch(object, source, matchData);
    +      };
    +    }
    +
    +    /**
    +     * The base implementation of `_.matchesProperty` which doesn't clone `srcValue`.
    +     *
    +     * @private
    +     * @param {string} path The path of the property to get.
    +     * @param {*} srcValue The value to match.
    +     * @returns {Function} Returns the new spec function.
    +     */
    +    function baseMatchesProperty(path, srcValue) {
    +      if (isKey(path) && isStrictComparable(srcValue)) {
    +        return matchesStrictComparable(toKey(path), srcValue);
    +      }
    +      return function (object) {
    +        var objValue = get(object, path);
    +        return objValue === undefined && objValue === srcValue ? hasIn(object, path) : baseIsEqual(srcValue, objValue, COMPARE_PARTIAL_FLAG | COMPARE_UNORDERED_FLAG);
    +      };
    +    }
    +
    +    /**
    +     * The base implementation of `_.merge` without support for multiple sources.
    +     *
    +     * @private
    +     * @param {Object} object The destination object.
    +     * @param {Object} source The source object.
    +     * @param {number} srcIndex The index of `source`.
    +     * @param {Function} [customizer] The function to customize merged values.
    +     * @param {Object} [stack] Tracks traversed source values and their merged
    +     *  counterparts.
    +     */
    +    function baseMerge(object, source, srcIndex, customizer, stack) {
    +      if (object === source) {
    +        return;
    +      }
    +      baseFor(source, function (srcValue, key) {
    +        stack || (stack = new Stack());
    +        if (isObject(srcValue)) {
    +          baseMergeDeep(object, source, key, srcIndex, baseMerge, customizer, stack);
    +        } else {
    +          var newValue = customizer ? customizer(safeGet(object, key), srcValue, key + '', object, source, stack) : undefined;
    +          if (newValue === undefined) {
    +            newValue = srcValue;
    +          }
    +          assignMergeValue(object, key, newValue);
    +        }
    +      }, keysIn);
    +    }
    +
    +    /**
    +     * A specialized version of `baseMerge` for arrays and objects which performs
    +     * deep merges and tracks traversed objects enabling objects with circular
    +     * references to be merged.
    +     *
    +     * @private
    +     * @param {Object} object The destination object.
    +     * @param {Object} source The source object.
    +     * @param {string} key The key of the value to merge.
    +     * @param {number} srcIndex The index of `source`.
    +     * @param {Function} mergeFunc The function to merge values.
    +     * @param {Function} [customizer] The function to customize assigned values.
    +     * @param {Object} [stack] Tracks traversed source values and their merged
    +     *  counterparts.
    +     */
    +    function baseMergeDeep(object, source, key, srcIndex, mergeFunc, customizer, stack) {
    +      var objValue = safeGet(object, key),
    +        srcValue = safeGet(source, key),
    +        stacked = stack.get(srcValue);
    +      if (stacked) {
    +        assignMergeValue(object, key, stacked);
    +        return;
    +      }
    +      var newValue = customizer ? customizer(objValue, srcValue, key + '', object, source, stack) : undefined;
    +      var isCommon = newValue === undefined;
    +      if (isCommon) {
    +        var isArr = isArray(srcValue),
    +          isBuff = !isArr && isBuffer(srcValue),
    +          isTyped = !isArr && !isBuff && isTypedArray(srcValue);
    +        newValue = srcValue;
    +        if (isArr || isBuff || isTyped) {
    +          if (isArray(objValue)) {
    +            newValue = objValue;
    +          } else if (isArrayLikeObject(objValue)) {
    +            newValue = copyArray(objValue);
    +          } else if (isBuff) {
    +            isCommon = false;
    +            newValue = cloneBuffer(srcValue, true);
    +          } else if (isTyped) {
    +            isCommon = false;
    +            newValue = cloneTypedArray(srcValue, true);
    +          } else {
    +            newValue = [];
    +          }
    +        } else if (isPlainObject(srcValue) || isArguments(srcValue)) {
    +          newValue = objValue;
    +          if (isArguments(objValue)) {
    +            newValue = toPlainObject(objValue);
    +          } else if (!isObject(objValue) || isFunction(objValue)) {
    +            newValue = initCloneObject(srcValue);
    +          }
    +        } else {
    +          isCommon = false;
    +        }
    +      }
    +      if (isCommon) {
    +        // Recursively merge objects and arrays (susceptible to call stack limits).
    +        stack.set(srcValue, newValue);
    +        mergeFunc(newValue, srcValue, srcIndex, customizer, stack);
    +        stack['delete'](srcValue);
    +      }
    +      assignMergeValue(object, key, newValue);
    +    }
    +
    +    /**
    +     * The base implementation of `_.nth` which doesn't coerce arguments.
    +     *
    +     * @private
    +     * @param {Array} array The array to query.
    +     * @param {number} n The index of the element to return.
    +     * @returns {*} Returns the nth element of `array`.
    +     */
    +    function baseNth(array, n) {
    +      var length = array.length;
    +      if (!length) {
    +        return;
    +      }
    +      n += n < 0 ? length : 0;
    +      return isIndex(n, length) ? array[n] : undefined;
    +    }
    +
    +    /**
    +     * The base implementation of `_.orderBy` without param guards.
    +     *
    +     * @private
    +     * @param {Array|Object} collection The collection to iterate over.
    +     * @param {Function[]|Object[]|string[]} iteratees The iteratees to sort by.
    +     * @param {string[]} orders The sort orders of `iteratees`.
    +     * @returns {Array} Returns the new sorted array.
    +     */
    +    function baseOrderBy(collection, iteratees, orders) {
    +      if (iteratees.length) {
    +        iteratees = arrayMap(iteratees, function (iteratee) {
    +          if (isArray(iteratee)) {
    +            return function (value) {
    +              return baseGet(value, iteratee.length === 1 ? iteratee[0] : iteratee);
    +            };
    +          }
    +          return iteratee;
    +        });
    +      } else {
    +        iteratees = [identity];
    +      }
    +      var index = -1;
    +      iteratees = arrayMap(iteratees, baseUnary(getIteratee()));
    +      var result = baseMap(collection, function (value, key, collection) {
    +        var criteria = arrayMap(iteratees, function (iteratee) {
    +          return iteratee(value);
    +        });
    +        return {
    +          'criteria': criteria,
    +          'index': ++index,
    +          'value': value
    +        };
    +      });
    +      return baseSortBy(result, function (object, other) {
    +        return compareMultiple(object, other, orders);
    +      });
    +    }
    +
    +    /**
    +     * The base implementation of `_.pick` without support for individual
    +     * property identifiers.
    +     *
    +     * @private
    +     * @param {Object} object The source object.
    +     * @param {string[]} paths The property paths to pick.
    +     * @returns {Object} Returns the new object.
    +     */
    +    function basePick(object, paths) {
    +      return basePickBy(object, paths, function (value, path) {
    +        return hasIn(object, path);
    +      });
    +    }
    +
    +    /**
    +     * The base implementation of  `_.pickBy` without support for iteratee shorthands.
    +     *
    +     * @private
    +     * @param {Object} object The source object.
    +     * @param {string[]} paths The property paths to pick.
    +     * @param {Function} predicate The function invoked per property.
    +     * @returns {Object} Returns the new object.
    +     */
    +    function basePickBy(object, paths, predicate) {
    +      var index = -1,
    +        length = paths.length,
    +        result = {};
    +      while (++index < length) {
    +        var path = paths[index],
    +          value = baseGet(object, path);
    +        if (predicate(value, path)) {
    +          baseSet(result, castPath(path, object), value);
    +        }
    +      }
    +      return result;
    +    }
    +
    +    /**
    +     * A specialized version of `baseProperty` which supports deep paths.
    +     *
    +     * @private
    +     * @param {Array|string} path The path of the property to get.
    +     * @returns {Function} Returns the new accessor function.
    +     */
    +    function basePropertyDeep(path) {
    +      return function (object) {
    +        return baseGet(object, path);
    +      };
    +    }
    +
    +    /**
    +     * The base implementation of `_.pullAllBy` without support for iteratee
    +     * shorthands.
    +     *
    +     * @private
    +     * @param {Array} array The array to modify.
    +     * @param {Array} values The values to remove.
    +     * @param {Function} [iteratee] The iteratee invoked per element.
    +     * @param {Function} [comparator] The comparator invoked per element.
    +     * @returns {Array} Returns `array`.
    +     */
    +    function basePullAll(array, values, iteratee, comparator) {
    +      var indexOf = comparator ? baseIndexOfWith : baseIndexOf,
    +        index = -1,
    +        length = values.length,
    +        seen = array;
    +      if (array === values) {
    +        values = copyArray(values);
    +      }
    +      if (iteratee) {
    +        seen = arrayMap(array, baseUnary(iteratee));
    +      }
    +      while (++index < length) {
    +        var fromIndex = 0,
    +          value = values[index],
    +          computed = iteratee ? iteratee(value) : value;
    +        while ((fromIndex = indexOf(seen, computed, fromIndex, comparator)) > -1) {
    +          if (seen !== array) {
    +            splice.call(seen, fromIndex, 1);
    +          }
    +          splice.call(array, fromIndex, 1);
    +        }
    +      }
    +      return array;
    +    }
    +
    +    /**
    +     * The base implementation of `_.pullAt` without support for individual
    +     * indexes or capturing the removed elements.
    +     *
    +     * @private
    +     * @param {Array} array The array to modify.
    +     * @param {number[]} indexes The indexes of elements to remove.
    +     * @returns {Array} Returns `array`.
    +     */
    +    function basePullAt(array, indexes) {
    +      var length = array ? indexes.length : 0,
    +        lastIndex = length - 1;
    +      while (length--) {
    +        var index = indexes[length];
    +        if (length == lastIndex || index !== previous) {
    +          var previous = index;
    +          if (isIndex(index)) {
    +            splice.call(array, index, 1);
    +          } else {
    +            baseUnset(array, index);
    +          }
    +        }
    +      }
    +      return array;
    +    }
    +
    +    /**
    +     * The base implementation of `_.random` without support for returning
    +     * floating-point numbers.
    +     *
    +     * @private
    +     * @param {number} lower The lower bound.
    +     * @param {number} upper The upper bound.
    +     * @returns {number} Returns the random number.
    +     */
    +    function baseRandom(lower, upper) {
    +      return lower + nativeFloor(nativeRandom() * (upper - lower + 1));
    +    }
    +
    +    /**
    +     * The base implementation of `_.range` and `_.rangeRight` which doesn't
    +     * coerce arguments.
    +     *
    +     * @private
    +     * @param {number} start The start of the range.
    +     * @param {number} end The end of the range.
    +     * @param {number} step The value to increment or decrement by.
    +     * @param {boolean} [fromRight] Specify iterating from right to left.
    +     * @returns {Array} Returns the range of numbers.
    +     */
    +    function baseRange(start, end, step, fromRight) {
    +      var index = -1,
    +        length = nativeMax(nativeCeil((end - start) / (step || 1)), 0),
    +        result = Array(length);
    +      while (length--) {
    +        result[fromRight ? length : ++index] = start;
    +        start += step;
    +      }
    +      return result;
    +    }
    +
    +    /**
    +     * The base implementation of `_.repeat` which doesn't coerce arguments.
    +     *
    +     * @private
    +     * @param {string} string The string to repeat.
    +     * @param {number} n The number of times to repeat the string.
    +     * @returns {string} Returns the repeated string.
    +     */
    +    function baseRepeat(string, n) {
    +      var result = '';
    +      if (!string || n < 1 || n > MAX_SAFE_INTEGER) {
    +        return result;
    +      }
    +      // Leverage the exponentiation by squaring algorithm for a faster repeat.
    +      // See https://en.wikipedia.org/wiki/Exponentiation_by_squaring for more details.
    +      do {
    +        if (n % 2) {
    +          result += string;
    +        }
    +        n = nativeFloor(n / 2);
    +        if (n) {
    +          string += string;
    +        }
    +      } while (n);
    +      return result;
    +    }
    +
    +    /**
    +     * The base implementation of `_.rest` which doesn't validate or coerce arguments.
    +     *
    +     * @private
    +     * @param {Function} func The function to apply a rest parameter to.
    +     * @param {number} [start=func.length-1] The start position of the rest parameter.
    +     * @returns {Function} Returns the new function.
    +     */
    +    function baseRest(func, start) {
    +      return setToString(overRest(func, start, identity), func + '');
    +    }
    +
    +    /**
    +     * The base implementation of `_.sample`.
    +     *
    +     * @private
    +     * @param {Array|Object} collection The collection to sample.
    +     * @returns {*} Returns the random element.
    +     */
    +    function baseSample(collection) {
    +      return arraySample(values(collection));
    +    }
    +
    +    /**
    +     * The base implementation of `_.sampleSize` without param guards.
    +     *
    +     * @private
    +     * @param {Array|Object} collection The collection to sample.
    +     * @param {number} n The number of elements to sample.
    +     * @returns {Array} Returns the random elements.
    +     */
    +    function baseSampleSize(collection, n) {
    +      var array = values(collection);
    +      return shuffleSelf(array, baseClamp(n, 0, array.length));
    +    }
    +
    +    /**
    +     * The base implementation of `_.set`.
    +     *
    +     * @private
    +     * @param {Object} object The object to modify.
    +     * @param {Array|string} path The path of the property to set.
    +     * @param {*} value The value to set.
    +     * @param {Function} [customizer] The function to customize path creation.
    +     * @returns {Object} Returns `object`.
    +     */
    +    function baseSet(object, path, value, customizer) {
    +      if (!isObject(object)) {
    +        return object;
    +      }
    +      path = castPath(path, object);
    +      var index = -1,
    +        length = path.length,
    +        lastIndex = length - 1,
    +        nested = object;
    +      while (nested != null && ++index < length) {
    +        var key = toKey(path[index]),
    +          newValue = value;
    +        if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
    +          return object;
    +        }
    +        if (index != lastIndex) {
    +          var objValue = nested[key];
    +          newValue = customizer ? customizer(objValue, key, nested) : undefined;
    +          if (newValue === undefined) {
    +            newValue = isObject(objValue) ? objValue : isIndex(path[index + 1]) ? [] : {};
    +          }
    +        }
    +        assignValue(nested, key, newValue);
    +        nested = nested[key];
    +      }
    +      return object;
    +    }
    +
    +    /**
    +     * The base implementation of `setData` without support for hot loop shorting.
    +     *
    +     * @private
    +     * @param {Function} func The function to associate metadata with.
    +     * @param {*} data The metadata.
    +     * @returns {Function} Returns `func`.
    +     */
    +    var baseSetData = !metaMap ? identity : function (func, data) {
    +      metaMap.set(func, data);
    +      return func;
    +    };
    +
    +    /**
    +     * The base implementation of `setToString` without support for hot loop shorting.
    +     *
    +     * @private
    +     * @param {Function} func The function to modify.
    +     * @param {Function} string The `toString` result.
    +     * @returns {Function} Returns `func`.
    +     */
    +    var baseSetToString = !defineProperty ? identity : function (func, string) {
    +      return defineProperty(func, 'toString', {
    +        'configurable': true,
    +        'enumerable': false,
    +        'value': constant(string),
    +        'writable': true
    +      });
    +    };
    +
    +    /**
    +     * The base implementation of `_.shuffle`.
    +     *
    +     * @private
    +     * @param {Array|Object} collection The collection to shuffle.
    +     * @returns {Array} Returns the new shuffled array.
    +     */
    +    function baseShuffle(collection) {
    +      return shuffleSelf(values(collection));
    +    }
    +
    +    /**
    +     * The base implementation of `_.slice` without an iteratee call guard.
    +     *
    +     * @private
    +     * @param {Array} array The array to slice.
    +     * @param {number} [start=0] The start position.
    +     * @param {number} [end=array.length] The end position.
    +     * @returns {Array} Returns the slice of `array`.
    +     */
    +    function baseSlice(array, start, end) {
    +      var index = -1,
    +        length = array.length;
    +      if (start < 0) {
    +        start = -start > length ? 0 : length + start;
    +      }
    +      end = end > length ? length : end;
    +      if (end < 0) {
    +        end += length;
    +      }
    +      length = start > end ? 0 : end - start >>> 0;
    +      start >>>= 0;
    +      var result = Array(length);
    +      while (++index < length) {
    +        result[index] = array[index + start];
    +      }
    +      return result;
    +    }
    +
    +    /**
    +     * The base implementation of `_.some` without support for iteratee shorthands.
    +     *
    +     * @private
    +     * @param {Array|Object} collection The collection to iterate over.
    +     * @param {Function} predicate The function invoked per iteration.
    +     * @returns {boolean} Returns `true` if any element passes the predicate check,
    +     *  else `false`.
    +     */
    +    function baseSome(collection, predicate) {
    +      var result;
    +      baseEach(collection, function (value, index, collection) {
    +        result = predicate(value, index, collection);
    +        return !result;
    +      });
    +      return !!result;
    +    }
    +
    +    /**
    +     * The base implementation of `_.sortedIndex` and `_.sortedLastIndex` which
    +     * performs a binary search of `array` to determine the index at which `value`
    +     * should be inserted into `array` in order to maintain its sort order.
    +     *
    +     * @private
    +     * @param {Array} array The sorted array to inspect.
    +     * @param {*} value The value to evaluate.
    +     * @param {boolean} [retHighest] Specify returning the highest qualified index.
    +     * @returns {number} Returns the index at which `value` should be inserted
    +     *  into `array`.
    +     */
    +    function baseSortedIndex(array, value, retHighest) {
    +      var low = 0,
    +        high = array == null ? low : array.length;
    +      if (typeof value == 'number' && value === value && high <= HALF_MAX_ARRAY_LENGTH) {
    +        while (low < high) {
    +          var mid = low + high >>> 1,
    +            computed = array[mid];
    +          if (computed !== null && !isSymbol(computed) && (retHighest ? computed <= value : computed < value)) {
    +            low = mid + 1;
    +          } else {
    +            high = mid;
    +          }
    +        }
    +        return high;
    +      }
    +      return baseSortedIndexBy(array, value, identity, retHighest);
    +    }
    +
    +    /**
    +     * The base implementation of `_.sortedIndexBy` and `_.sortedLastIndexBy`
    +     * which invokes `iteratee` for `value` and each element of `array` to compute
    +     * their sort ranking. The iteratee is invoked with one argument; (value).
    +     *
    +     * @private
    +     * @param {Array} array The sorted array to inspect.
    +     * @param {*} value The value to evaluate.
    +     * @param {Function} iteratee The iteratee invoked per element.
    +     * @param {boolean} [retHighest] Specify returning the highest qualified index.
    +     * @returns {number} Returns the index at which `value` should be inserted
    +     *  into `array`.
    +     */
    +    function baseSortedIndexBy(array, value, iteratee, retHighest) {
    +      var low = 0,
    +        high = array == null ? 0 : array.length;
    +      if (high === 0) {
    +        return 0;
    +      }
    +      value = iteratee(value);
    +      var valIsNaN = value !== value,
    +        valIsNull = value === null,
    +        valIsSymbol = isSymbol(value),
    +        valIsUndefined = value === undefined;
    +      while (low < high) {
    +        var mid = nativeFloor((low + high) / 2),
    +          computed = iteratee(array[mid]),
    +          othIsDefined = computed !== undefined,
    +          othIsNull = computed === null,
    +          othIsReflexive = computed === computed,
    +          othIsSymbol = isSymbol(computed);
    +        if (valIsNaN) {
    +          var setLow = retHighest || othIsReflexive;
    +        } else if (valIsUndefined) {
    +          setLow = othIsReflexive && (retHighest || othIsDefined);
    +        } else if (valIsNull) {
    +          setLow = othIsReflexive && othIsDefined && (retHighest || !othIsNull);
    +        } else if (valIsSymbol) {
    +          setLow = othIsReflexive && othIsDefined && !othIsNull && (retHighest || !othIsSymbol);
    +        } else if (othIsNull || othIsSymbol) {
    +          setLow = false;
    +        } else {
    +          setLow = retHighest ? computed <= value : computed < value;
    +        }
    +        if (setLow) {
    +          low = mid + 1;
    +        } else {
    +          high = mid;
    +        }
    +      }
    +      return nativeMin(high, MAX_ARRAY_INDEX);
    +    }
    +
    +    /**
    +     * The base implementation of `_.sortedUniq` and `_.sortedUniqBy` without
    +     * support for iteratee shorthands.
    +     *
    +     * @private
    +     * @param {Array} array The array to inspect.
    +     * @param {Function} [iteratee] The iteratee invoked per element.
    +     * @returns {Array} Returns the new duplicate free array.
    +     */
    +    function baseSortedUniq(array, iteratee) {
    +      var index = -1,
    +        length = array.length,
    +        resIndex = 0,
    +        result = [];
    +      while (++index < length) {
    +        var value = array[index],
    +          computed = iteratee ? iteratee(value) : value;
    +        if (!index || !eq(computed, seen)) {
    +          var seen = computed;
    +          result[resIndex++] = value === 0 ? 0 : value;
    +        }
    +      }
    +      return result;
    +    }
    +
    +    /**
    +     * The base implementation of `_.toNumber` which doesn't ensure correct
    +     * conversions of binary, hexadecimal, or octal string values.
    +     *
    +     * @private
    +     * @param {*} value The value to process.
    +     * @returns {number} Returns the number.
    +     */
    +    function baseToNumber(value) {
    +      if (typeof value == 'number') {
    +        return value;
    +      }
    +      if (isSymbol(value)) {
    +        return NAN;
    +      }
    +      return +value;
    +    }
    +
    +    /**
    +     * The base implementation of `_.toString` which doesn't convert nullish
    +     * values to empty strings.
    +     *
    +     * @private
    +     * @param {*} value The value to process.
    +     * @returns {string} Returns the string.
    +     */
    +    function baseToString(value) {
    +      // Exit early for strings to avoid a performance hit in some environments.
    +      if (typeof value == 'string') {
    +        return value;
    +      }
    +      if (isArray(value)) {
    +        // Recursively convert values (susceptible to call stack limits).
    +        return arrayMap(value, baseToString) + '';
    +      }
    +      if (isSymbol(value)) {
    +        return symbolToString ? symbolToString.call(value) : '';
    +      }
    +      var result = value + '';
    +      return result == '0' && 1 / value == -INFINITY ? '-0' : result;
    +    }
    +
    +    /**
    +     * The base implementation of `_.uniqBy` without support for iteratee shorthands.
    +     *
    +     * @private
    +     * @param {Array} array The array to inspect.
    +     * @param {Function} [iteratee] The iteratee invoked per element.
    +     * @param {Function} [comparator] The comparator invoked per element.
    +     * @returns {Array} Returns the new duplicate free array.
    +     */
    +    function baseUniq(array, iteratee, comparator) {
    +      var index = -1,
    +        includes = arrayIncludes,
    +        length = array.length,
    +        isCommon = true,
    +        result = [],
    +        seen = result;
    +      if (comparator) {
    +        isCommon = false;
    +        includes = arrayIncludesWith;
    +      } else if (length >= LARGE_ARRAY_SIZE) {
    +        var set = iteratee ? null : createSet(array);
    +        if (set) {
    +          return setToArray(set);
    +        }
    +        isCommon = false;
    +        includes = cacheHas;
    +        seen = new SetCache();
    +      } else {
    +        seen = iteratee ? [] : result;
    +      }
    +      outer: while (++index < length) {
    +        var value = array[index],
    +          computed = iteratee ? iteratee(value) : value;
    +        value = comparator || value !== 0 ? value : 0;
    +        if (isCommon && computed === computed) {
    +          var seenIndex = seen.length;
    +          while (seenIndex--) {
    +            if (seen[seenIndex] === computed) {
    +              continue outer;
    +            }
    +          }
    +          if (iteratee) {
    +            seen.push(computed);
    +          }
    +          result.push(value);
    +        } else if (!includes(seen, computed, comparator)) {
    +          if (seen !== result) {
    +            seen.push(computed);
    +          }
    +          result.push(value);
    +        }
    +      }
    +      return result;
    +    }
    +
    +    /**
    +     * The base implementation of `_.unset`.
    +     *
    +     * @private
    +     * @param {Object} object The object to modify.
    +     * @param {Array|string} path The property path to unset.
    +     * @returns {boolean} Returns `true` if the property is deleted, else `false`.
    +     */
    +    function baseUnset(object, path) {
    +      path = castPath(path, object);
    +      object = parent(object, path);
    +      return object == null || delete object[toKey(last(path))];
    +    }
    +
    +    /**
    +     * The base implementation of `_.update`.
    +     *
    +     * @private
    +     * @param {Object} object The object to modify.
    +     * @param {Array|string} path The path of the property to update.
    +     * @param {Function} updater The function to produce the updated value.
    +     * @param {Function} [customizer] The function to customize path creation.
    +     * @returns {Object} Returns `object`.
    +     */
    +    function baseUpdate(object, path, updater, customizer) {
    +      return baseSet(object, path, updater(baseGet(object, path)), customizer);
    +    }
    +
    +    /**
    +     * The base implementation of methods like `_.dropWhile` and `_.takeWhile`
    +     * without support for iteratee shorthands.
    +     *
    +     * @private
    +     * @param {Array} array The array to query.
    +     * @param {Function} predicate The function invoked per iteration.
    +     * @param {boolean} [isDrop] Specify dropping elements instead of taking them.
    +     * @param {boolean} [fromRight] Specify iterating from right to left.
    +     * @returns {Array} Returns the slice of `array`.
    +     */
    +    function baseWhile(array, predicate, isDrop, fromRight) {
    +      var length = array.length,
    +        index = fromRight ? length : -1;
    +      while ((fromRight ? index-- : ++index < length) && predicate(array[index], index, array)) {}
    +      return isDrop ? baseSlice(array, fromRight ? 0 : index, fromRight ? index + 1 : length) : baseSlice(array, fromRight ? index + 1 : 0, fromRight ? length : index);
    +    }
    +
    +    /**
    +     * The base implementation of `wrapperValue` which returns the result of
    +     * performing a sequence of actions on the unwrapped `value`, where each
    +     * successive action is supplied the return value of the previous.
    +     *
    +     * @private
    +     * @param {*} value The unwrapped value.
    +     * @param {Array} actions Actions to perform to resolve the unwrapped value.
    +     * @returns {*} Returns the resolved value.
    +     */
    +    function baseWrapperValue(value, actions) {
    +      var result = value;
    +      if (result instanceof LazyWrapper) {
    +        result = result.value();
    +      }
    +      return arrayReduce(actions, function (result, action) {
    +        return action.func.apply(action.thisArg, arrayPush([result], action.args));
    +      }, result);
    +    }
    +
    +    /**
    +     * The base implementation of methods like `_.xor`, without support for
    +     * iteratee shorthands, that accepts an array of arrays to inspect.
    +     *
    +     * @private
    +     * @param {Array} arrays The arrays to inspect.
    +     * @param {Function} [iteratee] The iteratee invoked per element.
    +     * @param {Function} [comparator] The comparator invoked per element.
    +     * @returns {Array} Returns the new array of values.
    +     */
    +    function baseXor(arrays, iteratee, comparator) {
    +      var length = arrays.length;
    +      if (length < 2) {
    +        return length ? baseUniq(arrays[0]) : [];
    +      }
    +      var index = -1,
    +        result = Array(length);
    +      while (++index < length) {
    +        var array = arrays[index],
    +          othIndex = -1;
    +        while (++othIndex < length) {
    +          if (othIndex != index) {
    +            result[index] = baseDifference(result[index] || array, arrays[othIndex], iteratee, comparator);
    +          }
    +        }
    +      }
    +      return baseUniq(baseFlatten(result, 1), iteratee, comparator);
    +    }
    +
    +    /**
    +     * This base implementation of `_.zipObject` which assigns values using `assignFunc`.
    +     *
    +     * @private
    +     * @param {Array} props The property identifiers.
    +     * @param {Array} values The property values.
    +     * @param {Function} assignFunc The function to assign values.
    +     * @returns {Object} Returns the new object.
    +     */
    +    function baseZipObject(props, values, assignFunc) {
    +      var index = -1,
    +        length = props.length,
    +        valsLength = values.length,
    +        result = {};
    +      while (++index < length) {
    +        var value = index < valsLength ? values[index] : undefined;
    +        assignFunc(result, props[index], value);
    +      }
    +      return result;
    +    }
    +
    +    /**
    +     * Casts `value` to an empty array if it's not an array like object.
    +     *
    +     * @private
    +     * @param {*} value The value to inspect.
    +     * @returns {Array|Object} Returns the cast array-like object.
    +     */
    +    function castArrayLikeObject(value) {
    +      return isArrayLikeObject(value) ? value : [];
    +    }
    +
    +    /**
    +     * Casts `value` to `identity` if it's not a function.
    +     *
    +     * @private
    +     * @param {*} value The value to inspect.
    +     * @returns {Function} Returns cast function.
    +     */
    +    function castFunction(value) {
    +      return typeof value == 'function' ? value : identity;
    +    }
    +
    +    /**
    +     * Casts `value` to a path array if it's not one.
    +     *
    +     * @private
    +     * @param {*} value The value to inspect.
    +     * @param {Object} [object] The object to query keys on.
    +     * @returns {Array} Returns the cast property path array.
    +     */
    +    function castPath(value, object) {
    +      if (isArray(value)) {
    +        return value;
    +      }
    +      return isKey(value, object) ? [value] : stringToPath(toString(value));
    +    }
    +
    +    /**
    +     * A `baseRest` alias which can be replaced with `identity` by module
    +     * replacement plugins.
    +     *
    +     * @private
    +     * @type {Function}
    +     * @param {Function} func The function to apply a rest parameter to.
    +     * @returns {Function} Returns the new function.
    +     */
    +    var castRest = baseRest;
    +
    +    /**
    +     * Casts `array` to a slice if it's needed.
    +     *
    +     * @private
    +     * @param {Array} array The array to inspect.
    +     * @param {number} start The start position.
    +     * @param {number} [end=array.length] The end position.
    +     * @returns {Array} Returns the cast slice.
    +     */
    +    function castSlice(array, start, end) {
    +      var length = array.length;
    +      end = end === undefined ? length : end;
    +      return !start && end >= length ? array : baseSlice(array, start, end);
    +    }
    +
    +    /**
    +     * A simple wrapper around the global [`clearTimeout`](https://mdn.io/clearTimeout).
    +     *
    +     * @private
    +     * @param {number|Object} id The timer id or timeout object of the timer to clear.
    +     */
    +    var clearTimeout = ctxClearTimeout || function (id) {
    +      return root.clearTimeout(id);
    +    };
    +
    +    /**
    +     * Creates a clone of  `buffer`.
    +     *
    +     * @private
    +     * @param {Buffer} buffer The buffer to clone.
    +     * @param {boolean} [isDeep] Specify a deep clone.
    +     * @returns {Buffer} Returns the cloned buffer.
    +     */
    +    function cloneBuffer(buffer, isDeep) {
    +      if (isDeep) {
    +        return buffer.slice();
    +      }
    +      var length = buffer.length,
    +        result = allocUnsafe ? allocUnsafe(length) : new buffer.constructor(length);
    +      buffer.copy(result);
    +      return result;
    +    }
    +
    +    /**
    +     * Creates a clone of `arrayBuffer`.
    +     *
    +     * @private
    +     * @param {ArrayBuffer} arrayBuffer The array buffer to clone.
    +     * @returns {ArrayBuffer} Returns the cloned array buffer.
    +     */
    +    function cloneArrayBuffer(arrayBuffer) {
    +      var result = new arrayBuffer.constructor(arrayBuffer.byteLength);
    +      new Uint8Array(result).set(new Uint8Array(arrayBuffer));
    +      return result;
    +    }
    +
    +    /**
    +     * Creates a clone of `dataView`.
    +     *
    +     * @private
    +     * @param {Object} dataView The data view to clone.
    +     * @param {boolean} [isDeep] Specify a deep clone.
    +     * @returns {Object} Returns the cloned data view.
    +     */
    +    function cloneDataView(dataView, isDeep) {
    +      var buffer = isDeep ? cloneArrayBuffer(dataView.buffer) : dataView.buffer;
    +      return new dataView.constructor(buffer, dataView.byteOffset, dataView.byteLength);
    +    }
    +
    +    /**
    +     * Creates a clone of `regexp`.
    +     *
    +     * @private
    +     * @param {Object} regexp The regexp to clone.
    +     * @returns {Object} Returns the cloned regexp.
    +     */
    +    function cloneRegExp(regexp) {
    +      var result = new regexp.constructor(regexp.source, reFlags.exec(regexp));
    +      result.lastIndex = regexp.lastIndex;
    +      return result;
    +    }
    +
    +    /**
    +     * Creates a clone of the `symbol` object.
    +     *
    +     * @private
    +     * @param {Object} symbol The symbol object to clone.
    +     * @returns {Object} Returns the cloned symbol object.
    +     */
    +    function cloneSymbol(symbol) {
    +      return symbolValueOf ? Object(symbolValueOf.call(symbol)) : {};
    +    }
    +
    +    /**
    +     * Creates a clone of `typedArray`.
    +     *
    +     * @private
    +     * @param {Object} typedArray The typed array to clone.
    +     * @param {boolean} [isDeep] Specify a deep clone.
    +     * @returns {Object} Returns the cloned typed array.
    +     */
    +    function cloneTypedArray(typedArray, isDeep) {
    +      var buffer = isDeep ? cloneArrayBuffer(typedArray.buffer) : typedArray.buffer;
    +      return new typedArray.constructor(buffer, typedArray.byteOffset, typedArray.length);
    +    }
    +
    +    /**
    +     * Compares values to sort them in ascending order.
    +     *
    +     * @private
    +     * @param {*} value The value to compare.
    +     * @param {*} other The other value to compare.
    +     * @returns {number} Returns the sort order indicator for `value`.
    +     */
    +    function compareAscending(value, other) {
    +      if (value !== other) {
    +        var valIsDefined = value !== undefined,
    +          valIsNull = value === null,
    +          valIsReflexive = value === value,
    +          valIsSymbol = isSymbol(value);
    +        var othIsDefined = other !== undefined,
    +          othIsNull = other === null,
    +          othIsReflexive = other === other,
    +          othIsSymbol = isSymbol(other);
    +        if (!othIsNull && !othIsSymbol && !valIsSymbol && value > other || valIsSymbol && othIsDefined && othIsReflexive && !othIsNull && !othIsSymbol || valIsNull && othIsDefined && othIsReflexive || !valIsDefined && othIsReflexive || !valIsReflexive) {
    +          return 1;
    +        }
    +        if (!valIsNull && !valIsSymbol && !othIsSymbol && value < other || othIsSymbol && valIsDefined && valIsReflexive && !valIsNull && !valIsSymbol || othIsNull && valIsDefined && valIsReflexive || !othIsDefined && valIsReflexive || !othIsReflexive) {
    +          return -1;
    +        }
    +      }
    +      return 0;
    +    }
    +
    +    /**
    +     * Used by `_.orderBy` to compare multiple properties of a value to another
    +     * and stable sort them.
    +     *
    +     * If `orders` is unspecified, all values are sorted in ascending order. Otherwise,
    +     * specify an order of "desc" for descending or "asc" for ascending sort order
    +     * of corresponding values.
    +     *
    +     * @private
    +     * @param {Object} object The object to compare.
    +     * @param {Object} other The other object to compare.
    +     * @param {boolean[]|string[]} orders The order to sort by for each property.
    +     * @returns {number} Returns the sort order indicator for `object`.
    +     */
    +    function compareMultiple(object, other, orders) {
    +      var index = -1,
    +        objCriteria = object.criteria,
    +        othCriteria = other.criteria,
    +        length = objCriteria.length,
    +        ordersLength = orders.length;
    +      while (++index < length) {
    +        var result = compareAscending(objCriteria[index], othCriteria[index]);
    +        if (result) {
    +          if (index >= ordersLength) {
    +            return result;
    +          }
    +          var order = orders[index];
    +          return result * (order == 'desc' ? -1 : 1);
    +        }
    +      }
    +      // Fixes an `Array#sort` bug in the JS engine embedded in Adobe applications
    +      // that causes it, under certain circumstances, to provide the same value for
    +      // `object` and `other`. See https://github.com/jashkenas/underscore/pull/1247
    +      // for more details.
    +      //
    +      // This also ensures a stable sort in V8 and other engines.
    +      // See https://bugs.chromium.org/p/v8/issues/detail?id=90 for more details.
    +      return object.index - other.index;
    +    }
    +
    +    /**
    +     * Creates an array that is the composition of partially applied arguments,
    +     * placeholders, and provided arguments into a single array of arguments.
    +     *
    +     * @private
    +     * @param {Array} args The provided arguments.
    +     * @param {Array} partials The arguments to prepend to those provided.
    +     * @param {Array} holders The `partials` placeholder indexes.
    +     * @params {boolean} [isCurried] Specify composing for a curried function.
    +     * @returns {Array} Returns the new array of composed arguments.
    +     */
    +    function composeArgs(args, partials, holders, isCurried) {
    +      var argsIndex = -1,
    +        argsLength = args.length,
    +        holdersLength = holders.length,
    +        leftIndex = -1,
    +        leftLength = partials.length,
    +        rangeLength = nativeMax(argsLength - holdersLength, 0),
    +        result = Array(leftLength + rangeLength),
    +        isUncurried = !isCurried;
    +      while (++leftIndex < leftLength) {
    +        result[leftIndex] = partials[leftIndex];
    +      }
    +      while (++argsIndex < holdersLength) {
    +        if (isUncurried || argsIndex < argsLength) {
    +          result[holders[argsIndex]] = args[argsIndex];
    +        }
    +      }
    +      while (rangeLength--) {
    +        result[leftIndex++] = args[argsIndex++];
    +      }
    +      return result;
    +    }
    +
    +    /**
    +     * This function is like `composeArgs` except that the arguments composition
    +     * is tailored for `_.partialRight`.
    +     *
    +     * @private
    +     * @param {Array} args The provided arguments.
    +     * @param {Array} partials The arguments to append to those provided.
    +     * @param {Array} holders The `partials` placeholder indexes.
    +     * @params {boolean} [isCurried] Specify composing for a curried function.
    +     * @returns {Array} Returns the new array of composed arguments.
    +     */
    +    function composeArgsRight(args, partials, holders, isCurried) {
    +      var argsIndex = -1,
    +        argsLength = args.length,
    +        holdersIndex = -1,
    +        holdersLength = holders.length,
    +        rightIndex = -1,
    +        rightLength = partials.length,
    +        rangeLength = nativeMax(argsLength - holdersLength, 0),
    +        result = Array(rangeLength + rightLength),
    +        isUncurried = !isCurried;
    +      while (++argsIndex < rangeLength) {
    +        result[argsIndex] = args[argsIndex];
    +      }
    +      var offset = argsIndex;
    +      while (++rightIndex < rightLength) {
    +        result[offset + rightIndex] = partials[rightIndex];
    +      }
    +      while (++holdersIndex < holdersLength) {
    +        if (isUncurried || argsIndex < argsLength) {
    +          result[offset + holders[holdersIndex]] = args[argsIndex++];
    +        }
    +      }
    +      return result;
    +    }
    +
    +    /**
    +     * Copies the values of `source` to `array`.
    +     *
    +     * @private
    +     * @param {Array} source The array to copy values from.
    +     * @param {Array} [array=[]] The array to copy values to.
    +     * @returns {Array} Returns `array`.
    +     */
    +    function copyArray(source, array) {
    +      var index = -1,
    +        length = source.length;
    +      array || (array = Array(length));
    +      while (++index < length) {
    +        array[index] = source[index];
    +      }
    +      return array;
    +    }
    +
    +    /**
    +     * Copies properties of `source` to `object`.
    +     *
    +     * @private
    +     * @param {Object} source The object to copy properties from.
    +     * @param {Array} props The property identifiers to copy.
    +     * @param {Object} [object={}] The object to copy properties to.
    +     * @param {Function} [customizer] The function to customize copied values.
    +     * @returns {Object} Returns `object`.
    +     */
    +    function copyObject(source, props, object, customizer) {
    +      var isNew = !object;
    +      object || (object = {});
    +      var index = -1,
    +        length = props.length;
    +      while (++index < length) {
    +        var key = props[index];
    +        var newValue = customizer ? customizer(object[key], source[key], key, object, source) : undefined;
    +        if (newValue === undefined) {
    +          newValue = source[key];
    +        }
    +        if (isNew) {
    +          baseAssignValue(object, key, newValue);
    +        } else {
    +          assignValue(object, key, newValue);
    +        }
    +      }
    +      return object;
    +    }
    +
    +    /**
    +     * Copies own symbols of `source` to `object`.
    +     *
    +     * @private
    +     * @param {Object} source The object to copy symbols from.
    +     * @param {Object} [object={}] The object to copy symbols to.
    +     * @returns {Object} Returns `object`.
    +     */
    +    function copySymbols(source, object) {
    +      return copyObject(source, getSymbols(source), object);
    +    }
    +
    +    /**
    +     * Copies own and inherited symbols of `source` to `object`.
    +     *
    +     * @private
    +     * @param {Object} source The object to copy symbols from.
    +     * @param {Object} [object={}] The object to copy symbols to.
    +     * @returns {Object} Returns `object`.
    +     */
    +    function copySymbolsIn(source, object) {
    +      return copyObject(source, getSymbolsIn(source), object);
    +    }
    +
    +    /**
    +     * Creates a function like `_.groupBy`.
    +     *
    +     * @private
    +     * @param {Function} setter The function to set accumulator values.
    +     * @param {Function} [initializer] The accumulator object initializer.
    +     * @returns {Function} Returns the new aggregator function.
    +     */
    +    function createAggregator(setter, initializer) {
    +      return function (collection, iteratee) {
    +        var func = isArray(collection) ? arrayAggregator : baseAggregator,
    +          accumulator = initializer ? initializer() : {};
    +        return func(collection, setter, getIteratee(iteratee, 2), accumulator);
    +      };
    +    }
    +
    +    /**
    +     * Creates a function like `_.assign`.
    +     *
    +     * @private
    +     * @param {Function} assigner The function to assign values.
    +     * @returns {Function} Returns the new assigner function.
    +     */
    +    function createAssigner(assigner) {
    +      return baseRest(function (object, sources) {
    +        var index = -1,
    +          length = sources.length,
    +          customizer = length > 1 ? sources[length - 1] : undefined,
    +          guard = length > 2 ? sources[2] : undefined;
    +        customizer = assigner.length > 3 && typeof customizer == 'function' ? (length--, customizer) : undefined;
    +        if (guard && isIterateeCall(sources[0], sources[1], guard)) {
    +          customizer = length < 3 ? undefined : customizer;
    +          length = 1;
    +        }
    +        object = Object(object);
    +        while (++index < length) {
    +          var source = sources[index];
    +          if (source) {
    +            assigner(object, source, index, customizer);
    +          }
    +        }
    +        return object;
    +      });
    +    }
    +
    +    /**
    +     * Creates a `baseEach` or `baseEachRight` function.
    +     *
    +     * @private
    +     * @param {Function} eachFunc The function to iterate over a collection.
    +     * @param {boolean} [fromRight] Specify iterating from right to left.
    +     * @returns {Function} Returns the new base function.
    +     */
    +    function createBaseEach(eachFunc, fromRight) {
    +      return function (collection, iteratee) {
    +        if (collection == null) {
    +          return collection;
    +        }
    +        if (!isArrayLike(collection)) {
    +          return eachFunc(collection, iteratee);
    +        }
    +        var length = collection.length,
    +          index = fromRight ? length : -1,
    +          iterable = Object(collection);
    +        while (fromRight ? index-- : ++index < length) {
    +          if (iteratee(iterable[index], index, iterable) === false) {
    +            break;
    +          }
    +        }
    +        return collection;
    +      };
    +    }
    +
    +    /**
    +     * Creates a base function for methods like `_.forIn` and `_.forOwn`.
    +     *
    +     * @private
    +     * @param {boolean} [fromRight] Specify iterating from right to left.
    +     * @returns {Function} Returns the new base function.
    +     */
    +    function createBaseFor(fromRight) {
    +      return function (object, iteratee, keysFunc) {
    +        var index = -1,
    +          iterable = Object(object),
    +          props = keysFunc(object),
    +          length = props.length;
    +        while (length--) {
    +          var key = props[fromRight ? length : ++index];
    +          if (iteratee(iterable[key], key, iterable) === false) {
    +            break;
    +          }
    +        }
    +        return object;
    +      };
    +    }
    +
    +    /**
    +     * Creates a function that wraps `func` to invoke it with the optional `this`
    +     * binding of `thisArg`.
    +     *
    +     * @private
    +     * @param {Function} func The function to wrap.
    +     * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
    +     * @param {*} [thisArg] The `this` binding of `func`.
    +     * @returns {Function} Returns the new wrapped function.
    +     */
    +    function createBind(func, bitmask, thisArg) {
    +      var isBind = bitmask & WRAP_BIND_FLAG,
    +        Ctor = createCtor(func);
    +      function wrapper() {
    +        var fn = this && this !== root && this instanceof wrapper ? Ctor : func;
    +        return fn.apply(isBind ? thisArg : this, arguments);
    +      }
    +      return wrapper;
    +    }
    +
    +    /**
    +     * Creates a function like `_.lowerFirst`.
    +     *
    +     * @private
    +     * @param {string} methodName The name of the `String` case method to use.
    +     * @returns {Function} Returns the new case function.
    +     */
    +    function createCaseFirst(methodName) {
    +      return function (string) {
    +        string = toString(string);
    +        var strSymbols = hasUnicode(string) ? stringToArray(string) : undefined;
    +        var chr = strSymbols ? strSymbols[0] : string.charAt(0);
    +        var trailing = strSymbols ? castSlice(strSymbols, 1).join('') : string.slice(1);
    +        return chr[methodName]() + trailing;
    +      };
    +    }
    +
    +    /**
    +     * Creates a function like `_.camelCase`.
    +     *
    +     * @private
    +     * @param {Function} callback The function to combine each word.
    +     * @returns {Function} Returns the new compounder function.
    +     */
    +    function createCompounder(callback) {
    +      return function (string) {
    +        return arrayReduce(words(deburr(string).replace(reApos, '')), callback, '');
    +      };
    +    }
    +
    +    /**
    +     * Creates a function that produces an instance of `Ctor` regardless of
    +     * whether it was invoked as part of a `new` expression or by `call` or `apply`.
    +     *
    +     * @private
    +     * @param {Function} Ctor The constructor to wrap.
    +     * @returns {Function} Returns the new wrapped function.
    +     */
    +    function createCtor(Ctor) {
    +      return function () {
    +        // Use a `switch` statement to work with class constructors. See
    +        // http://ecma-international.org/ecma-262/7.0/#sec-ecmascript-function-objects-call-thisargument-argumentslist
    +        // for more details.
    +        var args = arguments;
    +        switch (args.length) {
    +          case 0:
    +            return new Ctor();
    +          case 1:
    +            return new Ctor(args[0]);
    +          case 2:
    +            return new Ctor(args[0], args[1]);
    +          case 3:
    +            return new Ctor(args[0], args[1], args[2]);
    +          case 4:
    +            return new Ctor(args[0], args[1], args[2], args[3]);
    +          case 5:
    +            return new Ctor(args[0], args[1], args[2], args[3], args[4]);
    +          case 6:
    +            return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5]);
    +          case 7:
    +            return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
    +        }
    +        var thisBinding = baseCreate(Ctor.prototype),
    +          result = Ctor.apply(thisBinding, args);
    +
    +        // Mimic the constructor's `return` behavior.
    +        // See https://es5.github.io/#x13.2.2 for more details.
    +        return isObject(result) ? result : thisBinding;
    +      };
    +    }
    +
    +    /**
    +     * Creates a function that wraps `func` to enable currying.
    +     *
    +     * @private
    +     * @param {Function} func The function to wrap.
    +     * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
    +     * @param {number} arity The arity of `func`.
    +     * @returns {Function} Returns the new wrapped function.
    +     */
    +    function createCurry(func, bitmask, arity) {
    +      var Ctor = createCtor(func);
    +      function wrapper() {
    +        var length = arguments.length,
    +          args = Array(length),
    +          index = length,
    +          placeholder = getHolder(wrapper);
    +        while (index--) {
    +          args[index] = arguments[index];
    +        }
    +        var holders = length < 3 && args[0] !== placeholder && args[length - 1] !== placeholder ? [] : replaceHolders(args, placeholder);
    +        length -= holders.length;
    +        if (length < arity) {
    +          return createRecurry(func, bitmask, createHybrid, wrapper.placeholder, undefined, args, holders, undefined, undefined, arity - length);
    +        }
    +        var fn = this && this !== root && this instanceof wrapper ? Ctor : func;
    +        return apply(fn, this, args);
    +      }
    +      return wrapper;
    +    }
    +
    +    /**
    +     * Creates a `_.find` or `_.findLast` function.
    +     *
    +     * @private
    +     * @param {Function} findIndexFunc The function to find the collection index.
    +     * @returns {Function} Returns the new find function.
    +     */
    +    function createFind(findIndexFunc) {
    +      return function (collection, predicate, fromIndex) {
    +        var iterable = Object(collection);
    +        if (!isArrayLike(collection)) {
    +          var iteratee = getIteratee(predicate, 3);
    +          collection = keys(collection);
    +          predicate = function predicate(key) {
    +            return iteratee(iterable[key], key, iterable);
    +          };
    +        }
    +        var index = findIndexFunc(collection, predicate, fromIndex);
    +        return index > -1 ? iterable[iteratee ? collection[index] : index] : undefined;
    +      };
    +    }
    +
    +    /**
    +     * Creates a `_.flow` or `_.flowRight` function.
    +     *
    +     * @private
    +     * @param {boolean} [fromRight] Specify iterating from right to left.
    +     * @returns {Function} Returns the new flow function.
    +     */
    +    function createFlow(fromRight) {
    +      return flatRest(function (funcs) {
    +        var length = funcs.length,
    +          index = length,
    +          prereq = LodashWrapper.prototype.thru;
    +        if (fromRight) {
    +          funcs.reverse();
    +        }
    +        while (index--) {
    +          var func = funcs[index];
    +          if (typeof func != 'function') {
    +            throw new TypeError(FUNC_ERROR_TEXT);
    +          }
    +          if (prereq && !wrapper && getFuncName(func) == 'wrapper') {
    +            var wrapper = new LodashWrapper([], true);
    +          }
    +        }
    +        index = wrapper ? index : length;
    +        while (++index < length) {
    +          func = funcs[index];
    +          var funcName = getFuncName(func),
    +            data = funcName == 'wrapper' ? getData(func) : undefined;
    +          if (data && isLaziable(data[0]) && data[1] == (WRAP_ARY_FLAG | WRAP_CURRY_FLAG | WRAP_PARTIAL_FLAG | WRAP_REARG_FLAG) && !data[4].length && data[9] == 1) {
    +            wrapper = wrapper[getFuncName(data[0])].apply(wrapper, data[3]);
    +          } else {
    +            wrapper = func.length == 1 && isLaziable(func) ? wrapper[funcName]() : wrapper.thru(func);
    +          }
    +        }
    +        return function () {
    +          var args = arguments,
    +            value = args[0];
    +          if (wrapper && args.length == 1 && isArray(value)) {
    +            return wrapper.plant(value).value();
    +          }
    +          var index = 0,
    +            result = length ? funcs[index].apply(this, args) : value;
    +          while (++index < length) {
    +            result = funcs[index].call(this, result);
    +          }
    +          return result;
    +        };
    +      });
    +    }
    +
    +    /**
    +     * Creates a function that wraps `func` to invoke it with optional `this`
    +     * binding of `thisArg`, partial application, and currying.
    +     *
    +     * @private
    +     * @param {Function|string} func The function or method name to wrap.
    +     * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
    +     * @param {*} [thisArg] The `this` binding of `func`.
    +     * @param {Array} [partials] The arguments to prepend to those provided to
    +     *  the new function.
    +     * @param {Array} [holders] The `partials` placeholder indexes.
    +     * @param {Array} [partialsRight] The arguments to append to those provided
    +     *  to the new function.
    +     * @param {Array} [holdersRight] The `partialsRight` placeholder indexes.
    +     * @param {Array} [argPos] The argument positions of the new function.
    +     * @param {number} [ary] The arity cap of `func`.
    +     * @param {number} [arity] The arity of `func`.
    +     * @returns {Function} Returns the new wrapped function.
    +     */
    +    function createHybrid(func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, argPos, ary, arity) {
    +      var isAry = bitmask & WRAP_ARY_FLAG,
    +        isBind = bitmask & WRAP_BIND_FLAG,
    +        isBindKey = bitmask & WRAP_BIND_KEY_FLAG,
    +        isCurried = bitmask & (WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG),
    +        isFlip = bitmask & WRAP_FLIP_FLAG,
    +        Ctor = isBindKey ? undefined : createCtor(func);
    +      function wrapper() {
    +        var length = arguments.length,
    +          args = Array(length),
    +          index = length;
    +        while (index--) {
    +          args[index] = arguments[index];
    +        }
    +        if (isCurried) {
    +          var placeholder = getHolder(wrapper),
    +            holdersCount = countHolders(args, placeholder);
    +        }
    +        if (partials) {
    +          args = composeArgs(args, partials, holders, isCurried);
    +        }
    +        if (partialsRight) {
    +          args = composeArgsRight(args, partialsRight, holdersRight, isCurried);
    +        }
    +        length -= holdersCount;
    +        if (isCurried && length < arity) {
    +          var newHolders = replaceHolders(args, placeholder);
    +          return createRecurry(func, bitmask, createHybrid, wrapper.placeholder, thisArg, args, newHolders, argPos, ary, arity - length);
    +        }
    +        var thisBinding = isBind ? thisArg : this,
    +          fn = isBindKey ? thisBinding[func] : func;
    +        length = args.length;
    +        if (argPos) {
    +          args = reorder(args, argPos);
    +        } else if (isFlip && length > 1) {
    +          args.reverse();
    +        }
    +        if (isAry && ary < length) {
    +          args.length = ary;
    +        }
    +        if (this && this !== root && this instanceof wrapper) {
    +          fn = Ctor || createCtor(fn);
    +        }
    +        return fn.apply(thisBinding, args);
    +      }
    +      return wrapper;
    +    }
    +
    +    /**
    +     * Creates a function like `_.invertBy`.
    +     *
    +     * @private
    +     * @param {Function} setter The function to set accumulator values.
    +     * @param {Function} toIteratee The function to resolve iteratees.
    +     * @returns {Function} Returns the new inverter function.
    +     */
    +    function createInverter(setter, toIteratee) {
    +      return function (object, iteratee) {
    +        return baseInverter(object, setter, toIteratee(iteratee), {});
    +      };
    +    }
    +
    +    /**
    +     * Creates a function that performs a mathematical operation on two values.
    +     *
    +     * @private
    +     * @param {Function} operator The function to perform the operation.
    +     * @param {number} [defaultValue] The value used for `undefined` arguments.
    +     * @returns {Function} Returns the new mathematical operation function.
    +     */
    +    function createMathOperation(operator, defaultValue) {
    +      return function (value, other) {
    +        var result;
    +        if (value === undefined && other === undefined) {
    +          return defaultValue;
    +        }
    +        if (value !== undefined) {
    +          result = value;
    +        }
    +        if (other !== undefined) {
    +          if (result === undefined) {
    +            return other;
    +          }
    +          if (typeof value == 'string' || typeof other == 'string') {
    +            value = baseToString(value);
    +            other = baseToString(other);
    +          } else {
    +            value = baseToNumber(value);
    +            other = baseToNumber(other);
    +          }
    +          result = operator(value, other);
    +        }
    +        return result;
    +      };
    +    }
    +
    +    /**
    +     * Creates a function like `_.over`.
    +     *
    +     * @private
    +     * @param {Function} arrayFunc The function to iterate over iteratees.
    +     * @returns {Function} Returns the new over function.
    +     */
    +    function createOver(arrayFunc) {
    +      return flatRest(function (iteratees) {
    +        iteratees = arrayMap(iteratees, baseUnary(getIteratee()));
    +        return baseRest(function (args) {
    +          var thisArg = this;
    +          return arrayFunc(iteratees, function (iteratee) {
    +            return apply(iteratee, thisArg, args);
    +          });
    +        });
    +      });
    +    }
    +
    +    /**
    +     * Creates the padding for `string` based on `length`. The `chars` string
    +     * is truncated if the number of characters exceeds `length`.
    +     *
    +     * @private
    +     * @param {number} length The padding length.
    +     * @param {string} [chars=' '] The string used as padding.
    +     * @returns {string} Returns the padding for `string`.
    +     */
    +    function createPadding(length, chars) {
    +      chars = chars === undefined ? ' ' : baseToString(chars);
    +      var charsLength = chars.length;
    +      if (charsLength < 2) {
    +        return charsLength ? baseRepeat(chars, length) : chars;
    +      }
    +      var result = baseRepeat(chars, nativeCeil(length / stringSize(chars)));
    +      return hasUnicode(chars) ? castSlice(stringToArray(result), 0, length).join('') : result.slice(0, length);
    +    }
    +
    +    /**
    +     * Creates a function that wraps `func` to invoke it with the `this` binding
    +     * of `thisArg` and `partials` prepended to the arguments it receives.
    +     *
    +     * @private
    +     * @param {Function} func The function to wrap.
    +     * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
    +     * @param {*} thisArg The `this` binding of `func`.
    +     * @param {Array} partials The arguments to prepend to those provided to
    +     *  the new function.
    +     * @returns {Function} Returns the new wrapped function.
    +     */
    +    function createPartial(func, bitmask, thisArg, partials) {
    +      var isBind = bitmask & WRAP_BIND_FLAG,
    +        Ctor = createCtor(func);
    +      function wrapper() {
    +        var argsIndex = -1,
    +          argsLength = arguments.length,
    +          leftIndex = -1,
    +          leftLength = partials.length,
    +          args = Array(leftLength + argsLength),
    +          fn = this && this !== root && this instanceof wrapper ? Ctor : func;
    +        while (++leftIndex < leftLength) {
    +          args[leftIndex] = partials[leftIndex];
    +        }
    +        while (argsLength--) {
    +          args[leftIndex++] = arguments[++argsIndex];
    +        }
    +        return apply(fn, isBind ? thisArg : this, args);
    +      }
    +      return wrapper;
    +    }
    +
    +    /**
    +     * Creates a `_.range` or `_.rangeRight` function.
    +     *
    +     * @private
    +     * @param {boolean} [fromRight] Specify iterating from right to left.
    +     * @returns {Function} Returns the new range function.
    +     */
    +    function createRange(fromRight) {
    +      return function (start, end, step) {
    +        if (step && typeof step != 'number' && isIterateeCall(start, end, step)) {
    +          end = step = undefined;
    +        }
    +        // Ensure the sign of `-0` is preserved.
    +        start = toFinite(start);
    +        if (end === undefined) {
    +          end = start;
    +          start = 0;
    +        } else {
    +          end = toFinite(end);
    +        }
    +        step = step === undefined ? start < end ? 1 : -1 : toFinite(step);
    +        return baseRange(start, end, step, fromRight);
    +      };
    +    }
    +
    +    /**
    +     * Creates a function that performs a relational operation on two values.
    +     *
    +     * @private
    +     * @param {Function} operator The function to perform the operation.
    +     * @returns {Function} Returns the new relational operation function.
    +     */
    +    function createRelationalOperation(operator) {
    +      return function (value, other) {
    +        if (!(typeof value == 'string' && typeof other == 'string')) {
    +          value = toNumber(value);
    +          other = toNumber(other);
    +        }
    +        return operator(value, other);
    +      };
    +    }
    +
    +    /**
    +     * Creates a function that wraps `func` to continue currying.
    +     *
    +     * @private
    +     * @param {Function} func The function to wrap.
    +     * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
    +     * @param {Function} wrapFunc The function to create the `func` wrapper.
    +     * @param {*} placeholder The placeholder value.
    +     * @param {*} [thisArg] The `this` binding of `func`.
    +     * @param {Array} [partials] The arguments to prepend to those provided to
    +     *  the new function.
    +     * @param {Array} [holders] The `partials` placeholder indexes.
    +     * @param {Array} [argPos] The argument positions of the new function.
    +     * @param {number} [ary] The arity cap of `func`.
    +     * @param {number} [arity] The arity of `func`.
    +     * @returns {Function} Returns the new wrapped function.
    +     */
    +    function createRecurry(func, bitmask, wrapFunc, placeholder, thisArg, partials, holders, argPos, ary, arity) {
    +      var isCurry = bitmask & WRAP_CURRY_FLAG,
    +        newHolders = isCurry ? holders : undefined,
    +        newHoldersRight = isCurry ? undefined : holders,
    +        newPartials = isCurry ? partials : undefined,
    +        newPartialsRight = isCurry ? undefined : partials;
    +      bitmask |= isCurry ? WRAP_PARTIAL_FLAG : WRAP_PARTIAL_RIGHT_FLAG;
    +      bitmask &= ~(isCurry ? WRAP_PARTIAL_RIGHT_FLAG : WRAP_PARTIAL_FLAG);
    +      if (!(bitmask & WRAP_CURRY_BOUND_FLAG)) {
    +        bitmask &= ~(WRAP_BIND_FLAG | WRAP_BIND_KEY_FLAG);
    +      }
    +      var newData = [func, bitmask, thisArg, newPartials, newHolders, newPartialsRight, newHoldersRight, argPos, ary, arity];
    +      var result = wrapFunc.apply(undefined, newData);
    +      if (isLaziable(func)) {
    +        setData(result, newData);
    +      }
    +      result.placeholder = placeholder;
    +      return setWrapToString(result, func, bitmask);
    +    }
    +
    +    /**
    +     * Creates a function like `_.round`.
    +     *
    +     * @private
    +     * @param {string} methodName The name of the `Math` method to use when rounding.
    +     * @returns {Function} Returns the new round function.
    +     */
    +    function createRound(methodName) {
    +      var func = Math[methodName];
    +      return function (number, precision) {
    +        number = toNumber(number);
    +        precision = precision == null ? 0 : nativeMin(toInteger(precision), 292);
    +        if (precision && nativeIsFinite(number)) {
    +          // Shift with exponential notation to avoid floating-point issues.
    +          // See [MDN](https://mdn.io/round#Examples) for more details.
    +          var pair = (toString(number) + 'e').split('e'),
    +            value = func(pair[0] + 'e' + (+pair[1] + precision));
    +          pair = (toString(value) + 'e').split('e');
    +          return +(pair[0] + 'e' + (+pair[1] - precision));
    +        }
    +        return func(number);
    +      };
    +    }
    +
    +    /**
    +     * Creates a set object of `values`.
    +     *
    +     * @private
    +     * @param {Array} values The values to add to the set.
    +     * @returns {Object} Returns the new set.
    +     */
    +    var createSet = !(Set && 1 / setToArray(new Set([, -0]))[1] == INFINITY) ? noop : function (values) {
    +      return new Set(values);
    +    };
    +
    +    /**
    +     * Creates a `_.toPairs` or `_.toPairsIn` function.
    +     *
    +     * @private
    +     * @param {Function} keysFunc The function to get the keys of a given object.
    +     * @returns {Function} Returns the new pairs function.
    +     */
    +    function createToPairs(keysFunc) {
    +      return function (object) {
    +        var tag = getTag(object);
    +        if (tag == mapTag) {
    +          return mapToArray(object);
    +        }
    +        if (tag == setTag) {
    +          return setToPairs(object);
    +        }
    +        return baseToPairs(object, keysFunc(object));
    +      };
    +    }
    +
    +    /**
    +     * Creates a function that either curries or invokes `func` with optional
    +     * `this` binding and partially applied arguments.
    +     *
    +     * @private
    +     * @param {Function|string} func The function or method name to wrap.
    +     * @param {number} bitmask The bitmask flags.
    +     *    1 - `_.bind`
    +     *    2 - `_.bindKey`
    +     *    4 - `_.curry` or `_.curryRight` of a bound function
    +     *    8 - `_.curry`
    +     *   16 - `_.curryRight`
    +     *   32 - `_.partial`
    +     *   64 - `_.partialRight`
    +     *  128 - `_.rearg`
    +     *  256 - `_.ary`
    +     *  512 - `_.flip`
    +     * @param {*} [thisArg] The `this` binding of `func`.
    +     * @param {Array} [partials] The arguments to be partially applied.
    +     * @param {Array} [holders] The `partials` placeholder indexes.
    +     * @param {Array} [argPos] The argument positions of the new function.
    +     * @param {number} [ary] The arity cap of `func`.
    +     * @param {number} [arity] The arity of `func`.
    +     * @returns {Function} Returns the new wrapped function.
    +     */
    +    function createWrap(func, bitmask, thisArg, partials, holders, argPos, ary, arity) {
    +      var isBindKey = bitmask & WRAP_BIND_KEY_FLAG;
    +      if (!isBindKey && typeof func != 'function') {
    +        throw new TypeError(FUNC_ERROR_TEXT);
    +      }
    +      var length = partials ? partials.length : 0;
    +      if (!length) {
    +        bitmask &= ~(WRAP_PARTIAL_FLAG | WRAP_PARTIAL_RIGHT_FLAG);
    +        partials = holders = undefined;
    +      }
    +      ary = ary === undefined ? ary : nativeMax(toInteger(ary), 0);
    +      arity = arity === undefined ? arity : toInteger(arity);
    +      length -= holders ? holders.length : 0;
    +      if (bitmask & WRAP_PARTIAL_RIGHT_FLAG) {
    +        var partialsRight = partials,
    +          holdersRight = holders;
    +        partials = holders = undefined;
    +      }
    +      var data = isBindKey ? undefined : getData(func);
    +      var newData = [func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, argPos, ary, arity];
    +      if (data) {
    +        mergeData(newData, data);
    +      }
    +      func = newData[0];
    +      bitmask = newData[1];
    +      thisArg = newData[2];
    +      partials = newData[3];
    +      holders = newData[4];
    +      arity = newData[9] = newData[9] === undefined ? isBindKey ? 0 : func.length : nativeMax(newData[9] - length, 0);
    +      if (!arity && bitmask & (WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG)) {
    +        bitmask &= ~(WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG);
    +      }
    +      if (!bitmask || bitmask == WRAP_BIND_FLAG) {
    +        var result = createBind(func, bitmask, thisArg);
    +      } else if (bitmask == WRAP_CURRY_FLAG || bitmask == WRAP_CURRY_RIGHT_FLAG) {
    +        result = createCurry(func, bitmask, arity);
    +      } else if ((bitmask == WRAP_PARTIAL_FLAG || bitmask == (WRAP_BIND_FLAG | WRAP_PARTIAL_FLAG)) && !holders.length) {
    +        result = createPartial(func, bitmask, thisArg, partials);
    +      } else {
    +        result = createHybrid.apply(undefined, newData);
    +      }
    +      var setter = data ? baseSetData : setData;
    +      return setWrapToString(setter(result, newData), func, bitmask);
    +    }
    +
    +    /**
    +     * Used by `_.defaults` to customize its `_.assignIn` use to assign properties
    +     * of source objects to the destination object for all destination properties
    +     * that resolve to `undefined`.
    +     *
    +     * @private
    +     * @param {*} objValue The destination value.
    +     * @param {*} srcValue The source value.
    +     * @param {string} key The key of the property to assign.
    +     * @param {Object} object The parent object of `objValue`.
    +     * @returns {*} Returns the value to assign.
    +     */
    +    function customDefaultsAssignIn(objValue, srcValue, key, object) {
    +      if (objValue === undefined || eq(objValue, objectProto[key]) && !hasOwnProperty.call(object, key)) {
    +        return srcValue;
    +      }
    +      return objValue;
    +    }
    +
    +    /**
    +     * Used by `_.defaultsDeep` to customize its `_.merge` use to merge source
    +     * objects into destination objects that are passed thru.
    +     *
    +     * @private
    +     * @param {*} objValue The destination value.
    +     * @param {*} srcValue The source value.
    +     * @param {string} key The key of the property to merge.
    +     * @param {Object} object The parent object of `objValue`.
    +     * @param {Object} source The parent object of `srcValue`.
    +     * @param {Object} [stack] Tracks traversed source values and their merged
    +     *  counterparts.
    +     * @returns {*} Returns the value to assign.
    +     */
    +    function customDefaultsMerge(objValue, srcValue, key, object, source, stack) {
    +      if (isObject(objValue) && isObject(srcValue)) {
    +        // Recursively merge objects and arrays (susceptible to call stack limits).
    +        stack.set(srcValue, objValue);
    +        baseMerge(objValue, srcValue, undefined, customDefaultsMerge, stack);
    +        stack['delete'](srcValue);
    +      }
    +      return objValue;
    +    }
    +
    +    /**
    +     * Used by `_.omit` to customize its `_.cloneDeep` use to only clone plain
    +     * objects.
    +     *
    +     * @private
    +     * @param {*} value The value to inspect.
    +     * @param {string} key The key of the property to inspect.
    +     * @returns {*} Returns the uncloned value or `undefined` to defer cloning to `_.cloneDeep`.
    +     */
    +    function customOmitClone(value) {
    +      return isPlainObject(value) ? undefined : value;
    +    }
    +
    +    /**
    +     * A specialized version of `baseIsEqualDeep` for arrays with support for
    +     * partial deep comparisons.
    +     *
    +     * @private
    +     * @param {Array} array The array to compare.
    +     * @param {Array} other The other array to compare.
    +     * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.
    +     * @param {Function} customizer The function to customize comparisons.
    +     * @param {Function} equalFunc The function to determine equivalents of values.
    +     * @param {Object} stack Tracks traversed `array` and `other` objects.
    +     * @returns {boolean} Returns `true` if the arrays are equivalent, else `false`.
    +     */
    +    function equalArrays(array, other, bitmask, customizer, equalFunc, stack) {
    +      var isPartial = bitmask & COMPARE_PARTIAL_FLAG,
    +        arrLength = array.length,
    +        othLength = other.length;
    +      if (arrLength != othLength && !(isPartial && othLength > arrLength)) {
    +        return false;
    +      }
    +      // Check that cyclic values are equal.
    +      var arrStacked = stack.get(array);
    +      var othStacked = stack.get(other);
    +      if (arrStacked && othStacked) {
    +        return arrStacked == other && othStacked == array;
    +      }
    +      var index = -1,
    +        result = true,
    +        seen = bitmask & COMPARE_UNORDERED_FLAG ? new SetCache() : undefined;
    +      stack.set(array, other);
    +      stack.set(other, array);
    +
    +      // Ignore non-index properties.
    +      while (++index < arrLength) {
    +        var arrValue = array[index],
    +          othValue = other[index];
    +        if (customizer) {
    +          var compared = isPartial ? customizer(othValue, arrValue, index, other, array, stack) : customizer(arrValue, othValue, index, array, other, stack);
    +        }
    +        if (compared !== undefined) {
    +          if (compared) {
    +            continue;
    +          }
    +          result = false;
    +          break;
    +        }
    +        // Recursively compare arrays (susceptible to call stack limits).
    +        if (seen) {
    +          if (!arraySome(other, function (othValue, othIndex) {
    +            if (!cacheHas(seen, othIndex) && (arrValue === othValue || equalFunc(arrValue, othValue, bitmask, customizer, stack))) {
    +              return seen.push(othIndex);
    +            }
    +          })) {
    +            result = false;
    +            break;
    +          }
    +        } else if (!(arrValue === othValue || equalFunc(arrValue, othValue, bitmask, customizer, stack))) {
    +          result = false;
    +          break;
    +        }
    +      }
    +      stack['delete'](array);
    +      stack['delete'](other);
    +      return result;
    +    }
    +
    +    /**
    +     * A specialized version of `baseIsEqualDeep` for comparing objects of
    +     * the same `toStringTag`.
    +     *
    +     * **Note:** This function only supports comparing values with tags of
    +     * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`.
    +     *
    +     * @private
    +     * @param {Object} object The object to compare.
    +     * @param {Object} other The other object to compare.
    +     * @param {string} tag The `toStringTag` of the objects to compare.
    +     * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.
    +     * @param {Function} customizer The function to customize comparisons.
    +     * @param {Function} equalFunc The function to determine equivalents of values.
    +     * @param {Object} stack Tracks traversed `object` and `other` objects.
    +     * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
    +     */
    +    function equalByTag(object, other, tag, bitmask, customizer, equalFunc, stack) {
    +      switch (tag) {
    +        case dataViewTag:
    +          if (object.byteLength != other.byteLength || object.byteOffset != other.byteOffset) {
    +            return false;
    +          }
    +          object = object.buffer;
    +          other = other.buffer;
    +        case arrayBufferTag:
    +          if (object.byteLength != other.byteLength || !equalFunc(new Uint8Array(object), new Uint8Array(other))) {
    +            return false;
    +          }
    +          return true;
    +        case boolTag:
    +        case dateTag:
    +        case numberTag:
    +          // Coerce booleans to `1` or `0` and dates to milliseconds.
    +          // Invalid dates are coerced to `NaN`.
    +          return eq(+object, +other);
    +        case errorTag:
    +          return object.name == other.name && object.message == other.message;
    +        case regexpTag:
    +        case stringTag:
    +          // Coerce regexes to strings and treat strings, primitives and objects,
    +          // as equal. See http://www.ecma-international.org/ecma-262/7.0/#sec-regexp.prototype.tostring
    +          // for more details.
    +          return object == other + '';
    +        case mapTag:
    +          var convert = mapToArray;
    +        case setTag:
    +          var isPartial = bitmask & COMPARE_PARTIAL_FLAG;
    +          convert || (convert = setToArray);
    +          if (object.size != other.size && !isPartial) {
    +            return false;
    +          }
    +          // Assume cyclic values are equal.
    +          var stacked = stack.get(object);
    +          if (stacked) {
    +            return stacked == other;
    +          }
    +          bitmask |= COMPARE_UNORDERED_FLAG;
    +
    +          // Recursively compare objects (susceptible to call stack limits).
    +          stack.set(object, other);
    +          var result = equalArrays(convert(object), convert(other), bitmask, customizer, equalFunc, stack);
    +          stack['delete'](object);
    +          return result;
    +        case symbolTag:
    +          if (symbolValueOf) {
    +            return symbolValueOf.call(object) == symbolValueOf.call(other);
    +          }
    +      }
    +      return false;
    +    }
    +
    +    /**
    +     * A specialized version of `baseIsEqualDeep` for objects with support for
    +     * partial deep comparisons.
    +     *
    +     * @private
    +     * @param {Object} object The object to compare.
    +     * @param {Object} other The other object to compare.
    +     * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.
    +     * @param {Function} customizer The function to customize comparisons.
    +     * @param {Function} equalFunc The function to determine equivalents of values.
    +     * @param {Object} stack Tracks traversed `object` and `other` objects.
    +     * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
    +     */
    +    function equalObjects(object, other, bitmask, customizer, equalFunc, stack) {
    +      var isPartial = bitmask & COMPARE_PARTIAL_FLAG,
    +        objProps = getAllKeys(object),
    +        objLength = objProps.length,
    +        othProps = getAllKeys(other),
    +        othLength = othProps.length;
    +      if (objLength != othLength && !isPartial) {
    +        return false;
    +      }
    +      var index = objLength;
    +      while (index--) {
    +        var key = objProps[index];
    +        if (!(isPartial ? key in other : hasOwnProperty.call(other, key))) {
    +          return false;
    +        }
    +      }
    +      // Check that cyclic values are equal.
    +      var objStacked = stack.get(object);
    +      var othStacked = stack.get(other);
    +      if (objStacked && othStacked) {
    +        return objStacked == other && othStacked == object;
    +      }
    +      var result = true;
    +      stack.set(object, other);
    +      stack.set(other, object);
    +      var skipCtor = isPartial;
    +      while (++index < objLength) {
    +        key = objProps[index];
    +        var objValue = object[key],
    +          othValue = other[key];
    +        if (customizer) {
    +          var compared = isPartial ? customizer(othValue, objValue, key, other, object, stack) : customizer(objValue, othValue, key, object, other, stack);
    +        }
    +        // Recursively compare objects (susceptible to call stack limits).
    +        if (!(compared === undefined ? objValue === othValue || equalFunc(objValue, othValue, bitmask, customizer, stack) : compared)) {
    +          result = false;
    +          break;
    +        }
    +        skipCtor || (skipCtor = key == 'constructor');
    +      }
    +      if (result && !skipCtor) {
    +        var objCtor = object.constructor,
    +          othCtor = other.constructor;
    +
    +        // Non `Object` object instances with different constructors are not equal.
    +        if (objCtor != othCtor && 'constructor' in object && 'constructor' in other && !(typeof objCtor == 'function' && objCtor instanceof objCtor && typeof othCtor == 'function' && othCtor instanceof othCtor)) {
    +          result = false;
    +        }
    +      }
    +      stack['delete'](object);
    +      stack['delete'](other);
    +      return result;
    +    }
    +
    +    /**
    +     * A specialized version of `baseRest` which flattens the rest array.
    +     *
    +     * @private
    +     * @param {Function} func The function to apply a rest parameter to.
    +     * @returns {Function} Returns the new function.
    +     */
    +    function flatRest(func) {
    +      return setToString(overRest(func, undefined, flatten), func + '');
    +    }
    +
    +    /**
    +     * Creates an array of own enumerable property names and symbols of `object`.
    +     *
    +     * @private
    +     * @param {Object} object The object to query.
    +     * @returns {Array} Returns the array of property names and symbols.
    +     */
    +    function getAllKeys(object) {
    +      return baseGetAllKeys(object, keys, getSymbols);
    +    }
    +
    +    /**
    +     * Creates an array of own and inherited enumerable property names and
    +     * symbols of `object`.
    +     *
    +     * @private
    +     * @param {Object} object The object to query.
    +     * @returns {Array} Returns the array of property names and symbols.
    +     */
    +    function getAllKeysIn(object) {
    +      return baseGetAllKeys(object, keysIn, getSymbolsIn);
    +    }
    +
    +    /**
    +     * Gets metadata for `func`.
    +     *
    +     * @private
    +     * @param {Function} func The function to query.
    +     * @returns {*} Returns the metadata for `func`.
    +     */
    +    var getData = !metaMap ? noop : function (func) {
    +      return metaMap.get(func);
    +    };
    +
    +    /**
    +     * Gets the name of `func`.
    +     *
    +     * @private
    +     * @param {Function} func The function to query.
    +     * @returns {string} Returns the function name.
    +     */
    +    function getFuncName(func) {
    +      var result = func.name + '',
    +        array = realNames[result],
    +        length = hasOwnProperty.call(realNames, result) ? array.length : 0;
    +      while (length--) {
    +        var data = array[length],
    +          otherFunc = data.func;
    +        if (otherFunc == null || otherFunc == func) {
    +          return data.name;
    +        }
    +      }
    +      return result;
    +    }
    +
    +    /**
    +     * Gets the argument placeholder value for `func`.
    +     *
    +     * @private
    +     * @param {Function} func The function to inspect.
    +     * @returns {*} Returns the placeholder value.
    +     */
    +    function getHolder(func) {
    +      var object = hasOwnProperty.call(lodash, 'placeholder') ? lodash : func;
    +      return object.placeholder;
    +    }
    +
    +    /**
    +     * Gets the appropriate "iteratee" function. If `_.iteratee` is customized,
    +     * this function returns the custom method, otherwise it returns `baseIteratee`.
    +     * If arguments are provided, the chosen function is invoked with them and
    +     * its result is returned.
    +     *
    +     * @private
    +     * @param {*} [value] The value to convert to an iteratee.
    +     * @param {number} [arity] The arity of the created iteratee.
    +     * @returns {Function} Returns the chosen function or its result.
    +     */
    +    function getIteratee() {
    +      var result = lodash.iteratee || iteratee;
    +      result = result === iteratee ? baseIteratee : result;
    +      return arguments.length ? result(arguments[0], arguments[1]) : result;
    +    }
    +
    +    /**
    +     * Gets the data for `map`.
    +     *
    +     * @private
    +     * @param {Object} map The map to query.
    +     * @param {string} key The reference key.
    +     * @returns {*} Returns the map data.
    +     */
    +    function getMapData(map, key) {
    +      var data = map.__data__;
    +      return isKeyable(key) ? data[typeof key == 'string' ? 'string' : 'hash'] : data.map;
    +    }
    +
    +    /**
    +     * Gets the property names, values, and compare flags of `object`.
    +     *
    +     * @private
    +     * @param {Object} object The object to query.
    +     * @returns {Array} Returns the match data of `object`.
    +     */
    +    function getMatchData(object) {
    +      var result = keys(object),
    +        length = result.length;
    +      while (length--) {
    +        var key = result[length],
    +          value = object[key];
    +        result[length] = [key, value, isStrictComparable(value)];
    +      }
    +      return result;
    +    }
    +
    +    /**
    +     * Gets the native function at `key` of `object`.
    +     *
    +     * @private
    +     * @param {Object} object The object to query.
    +     * @param {string} key The key of the method to get.
    +     * @returns {*} Returns the function if it's native, else `undefined`.
    +     */
    +    function getNative(object, key) {
    +      var value = getValue(object, key);
    +      return baseIsNative(value) ? value : undefined;
    +    }
    +
    +    /**
    +     * A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values.
    +     *
    +     * @private
    +     * @param {*} value The value to query.
    +     * @returns {string} Returns the raw `toStringTag`.
    +     */
    +    function getRawTag(value) {
    +      var isOwn = hasOwnProperty.call(value, symToStringTag),
    +        tag = value[symToStringTag];
    +      try {
    +        value[symToStringTag] = undefined;
    +        var unmasked = true;
    +      } catch (e) {}
    +      var result = nativeObjectToString.call(value);
    +      if (unmasked) {
    +        if (isOwn) {
    +          value[symToStringTag] = tag;
    +        } else {
    +          delete value[symToStringTag];
    +        }
    +      }
    +      return result;
    +    }
    +
    +    /**
    +     * Creates an array of the own enumerable symbols of `object`.
    +     *
    +     * @private
    +     * @param {Object} object The object to query.
    +     * @returns {Array} Returns the array of symbols.
    +     */
    +    var getSymbols = !nativeGetSymbols ? stubArray : function (object) {
    +      if (object == null) {
    +        return [];
    +      }
    +      object = Object(object);
    +      return arrayFilter(nativeGetSymbols(object), function (symbol) {
    +        return propertyIsEnumerable.call(object, symbol);
    +      });
    +    };
    +
    +    /**
    +     * Creates an array of the own and inherited enumerable symbols of `object`.
    +     *
    +     * @private
    +     * @param {Object} object The object to query.
    +     * @returns {Array} Returns the array of symbols.
    +     */
    +    var getSymbolsIn = !nativeGetSymbols ? stubArray : function (object) {
    +      var result = [];
    +      while (object) {
    +        arrayPush(result, getSymbols(object));
    +        object = getPrototype(object);
    +      }
    +      return result;
    +    };
    +
    +    /**
    +     * Gets the `toStringTag` of `value`.
    +     *
    +     * @private
    +     * @param {*} value The value to query.
    +     * @returns {string} Returns the `toStringTag`.
    +     */
    +    var getTag = baseGetTag;
    +
    +    // Fallback for data views, maps, sets, and weak maps in IE 11 and promises in Node.js < 6.
    +    if (DataView && getTag(new DataView(new ArrayBuffer(1))) != dataViewTag || Map && getTag(new Map()) != mapTag || Promise && getTag(Promise.resolve()) != promiseTag || Set && getTag(new Set()) != setTag || WeakMap && getTag(new WeakMap()) != weakMapTag) {
    +      getTag = function getTag(value) {
    +        var result = baseGetTag(value),
    +          Ctor = result == objectTag ? value.constructor : undefined,
    +          ctorString = Ctor ? toSource(Ctor) : '';
    +        if (ctorString) {
    +          switch (ctorString) {
    +            case dataViewCtorString:
    +              return dataViewTag;
    +            case mapCtorString:
    +              return mapTag;
    +            case promiseCtorString:
    +              return promiseTag;
    +            case setCtorString:
    +              return setTag;
    +            case weakMapCtorString:
    +              return weakMapTag;
    +          }
    +        }
    +        return result;
    +      };
    +    }
    +
    +    /**
    +     * Gets the view, applying any `transforms` to the `start` and `end` positions.
    +     *
    +     * @private
    +     * @param {number} start The start of the view.
    +     * @param {number} end The end of the view.
    +     * @param {Array} transforms The transformations to apply to the view.
    +     * @returns {Object} Returns an object containing the `start` and `end`
    +     *  positions of the view.
    +     */
    +    function getView(start, end, transforms) {
    +      var index = -1,
    +        length = transforms.length;
    +      while (++index < length) {
    +        var data = transforms[index],
    +          size = data.size;
    +        switch (data.type) {
    +          case 'drop':
    +            start += size;
    +            break;
    +          case 'dropRight':
    +            end -= size;
    +            break;
    +          case 'take':
    +            end = nativeMin(end, start + size);
    +            break;
    +          case 'takeRight':
    +            start = nativeMax(start, end - size);
    +            break;
    +        }
    +      }
    +      return {
    +        'start': start,
    +        'end': end
    +      };
    +    }
    +
    +    /**
    +     * Extracts wrapper details from the `source` body comment.
    +     *
    +     * @private
    +     * @param {string} source The source to inspect.
    +     * @returns {Array} Returns the wrapper details.
    +     */
    +    function getWrapDetails(source) {
    +      var match = source.match(reWrapDetails);
    +      return match ? match[1].split(reSplitDetails) : [];
    +    }
    +
    +    /**
    +     * Checks if `path` exists on `object`.
    +     *
    +     * @private
    +     * @param {Object} object The object to query.
    +     * @param {Array|string} path The path to check.
    +     * @param {Function} hasFunc The function to check properties.
    +     * @returns {boolean} Returns `true` if `path` exists, else `false`.
    +     */
    +    function hasPath(object, path, hasFunc) {
    +      path = castPath(path, object);
    +      var index = -1,
    +        length = path.length,
    +        result = false;
    +      while (++index < length) {
    +        var key = toKey(path[index]);
    +        if (!(result = object != null && hasFunc(object, key))) {
    +          break;
    +        }
    +        object = object[key];
    +      }
    +      if (result || ++index != length) {
    +        return result;
    +      }
    +      length = object == null ? 0 : object.length;
    +      return !!length && isLength(length) && isIndex(key, length) && (isArray(object) || isArguments(object));
    +    }
    +
    +    /**
    +     * Initializes an array clone.
    +     *
    +     * @private
    +     * @param {Array} array The array to clone.
    +     * @returns {Array} Returns the initialized clone.
    +     */
    +    function initCloneArray(array) {
    +      var length = array.length,
    +        result = new array.constructor(length);
    +
    +      // Add properties assigned by `RegExp#exec`.
    +      if (length && typeof array[0] == 'string' && hasOwnProperty.call(array, 'index')) {
    +        result.index = array.index;
    +        result.input = array.input;
    +      }
    +      return result;
    +    }
    +
    +    /**
    +     * Initializes an object clone.
    +     *
    +     * @private
    +     * @param {Object} object The object to clone.
    +     * @returns {Object} Returns the initialized clone.
    +     */
    +    function initCloneObject(object) {
    +      return typeof object.constructor == 'function' && !isPrototype(object) ? baseCreate(getPrototype(object)) : {};
    +    }
    +
    +    /**
    +     * Initializes an object clone based on its `toStringTag`.
    +     *
    +     * **Note:** This function only supports cloning values with tags of
    +     * `Boolean`, `Date`, `Error`, `Map`, `Number`, `RegExp`, `Set`, or `String`.
    +     *
    +     * @private
    +     * @param {Object} object The object to clone.
    +     * @param {string} tag The `toStringTag` of the object to clone.
    +     * @param {boolean} [isDeep] Specify a deep clone.
    +     * @returns {Object} Returns the initialized clone.
    +     */
    +    function initCloneByTag(object, tag, isDeep) {
    +      var Ctor = object.constructor;
    +      switch (tag) {
    +        case arrayBufferTag:
    +          return cloneArrayBuffer(object);
    +        case boolTag:
    +        case dateTag:
    +          return new Ctor(+object);
    +        case dataViewTag:
    +          return cloneDataView(object, isDeep);
    +        case float32Tag:
    +        case float64Tag:
    +        case int8Tag:
    +        case int16Tag:
    +        case int32Tag:
    +        case uint8Tag:
    +        case uint8ClampedTag:
    +        case uint16Tag:
    +        case uint32Tag:
    +          return cloneTypedArray(object, isDeep);
    +        case mapTag:
    +          return new Ctor();
    +        case numberTag:
    +        case stringTag:
    +          return new Ctor(object);
    +        case regexpTag:
    +          return cloneRegExp(object);
    +        case setTag:
    +          return new Ctor();
    +        case symbolTag:
    +          return cloneSymbol(object);
    +      }
    +    }
    +
    +    /**
    +     * Inserts wrapper `details` in a comment at the top of the `source` body.
    +     *
    +     * @private
    +     * @param {string} source The source to modify.
    +     * @returns {Array} details The details to insert.
    +     * @returns {string} Returns the modified source.
    +     */
    +    function insertWrapDetails(source, details) {
    +      var length = details.length;
    +      if (!length) {
    +        return source;
    +      }
    +      var lastIndex = length - 1;
    +      details[lastIndex] = (length > 1 ? '& ' : '') + details[lastIndex];
    +      details = details.join(length > 2 ? ', ' : ' ');
    +      return source.replace(reWrapComment, '{\n/* [wrapped with ' + details + '] */\n');
    +    }
    +
    +    /**
    +     * Checks if `value` is a flattenable `arguments` object or array.
    +     *
    +     * @private
    +     * @param {*} value The value to check.
    +     * @returns {boolean} Returns `true` if `value` is flattenable, else `false`.
    +     */
    +    function isFlattenable(value) {
    +      return isArray(value) || isArguments(value) || !!(spreadableSymbol && value && value[spreadableSymbol]);
    +    }
    +
    +    /**
    +     * Checks if `value` is a valid array-like index.
    +     *
    +     * @private
    +     * @param {*} value The value to check.
    +     * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index.
    +     * @returns {boolean} Returns `true` if `value` is a valid index, else `false`.
    +     */
    +    function isIndex(value, length) {
    +      var type = typeof value;
    +      length = length == null ? MAX_SAFE_INTEGER : length;
    +      return !!length && (type == 'number' || type != 'symbol' && reIsUint.test(value)) && value > -1 && value % 1 == 0 && value < length;
    +    }
    +
    +    /**
    +     * Checks if the given arguments are from an iteratee call.
    +     *
    +     * @private
    +     * @param {*} value The potential iteratee value argument.
    +     * @param {*} index The potential iteratee index or key argument.
    +     * @param {*} object The potential iteratee object argument.
    +     * @returns {boolean} Returns `true` if the arguments are from an iteratee call,
    +     *  else `false`.
    +     */
    +    function isIterateeCall(value, index, object) {
    +      if (!isObject(object)) {
    +        return false;
    +      }
    +      var type = typeof index;
    +      if (type == 'number' ? isArrayLike(object) && isIndex(index, object.length) : type == 'string' && index in object) {
    +        return eq(object[index], value);
    +      }
    +      return false;
    +    }
    +
    +    /**
    +     * Checks if `value` is a property name and not a property path.
    +     *
    +     * @private
    +     * @param {*} value The value to check.
    +     * @param {Object} [object] The object to query keys on.
    +     * @returns {boolean} Returns `true` if `value` is a property name, else `false`.
    +     */
    +    function isKey(value, object) {
    +      if (isArray(value)) {
    +        return false;
    +      }
    +      var type = typeof value;
    +      if (type == 'number' || type == 'symbol' || type == 'boolean' || value == null || isSymbol(value)) {
    +        return true;
    +      }
    +      return reIsPlainProp.test(value) || !reIsDeepProp.test(value) || object != null && value in Object(object);
    +    }
    +
    +    /**
    +     * Checks if `value` is suitable for use as unique object key.
    +     *
    +     * @private
    +     * @param {*} value The value to check.
    +     * @returns {boolean} Returns `true` if `value` is suitable, else `false`.
    +     */
    +    function isKeyable(value) {
    +      var type = typeof value;
    +      return type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean' ? value !== '__proto__' : value === null;
    +    }
    +
    +    /**
    +     * Checks if `func` has a lazy counterpart.
    +     *
    +     * @private
    +     * @param {Function} func The function to check.
    +     * @returns {boolean} Returns `true` if `func` has a lazy counterpart,
    +     *  else `false`.
    +     */
    +    function isLaziable(func) {
    +      var funcName = getFuncName(func),
    +        other = lodash[funcName];
    +      if (typeof other != 'function' || !(funcName in LazyWrapper.prototype)) {
    +        return false;
    +      }
    +      if (func === other) {
    +        return true;
    +      }
    +      var data = getData(other);
    +      return !!data && func === data[0];
    +    }
    +
    +    /**
    +     * Checks if `func` has its source masked.
    +     *
    +     * @private
    +     * @param {Function} func The function to check.
    +     * @returns {boolean} Returns `true` if `func` is masked, else `false`.
    +     */
    +    function isMasked(func) {
    +      return !!maskSrcKey && maskSrcKey in func;
    +    }
    +
    +    /**
    +     * Checks if `func` is capable of being masked.
    +     *
    +     * @private
    +     * @param {*} value The value to check.
    +     * @returns {boolean} Returns `true` if `func` is maskable, else `false`.
    +     */
    +    var isMaskable = coreJsData ? isFunction : stubFalse;
    +
    +    /**
    +     * Checks if `value` is likely a prototype object.
    +     *
    +     * @private
    +     * @param {*} value The value to check.
    +     * @returns {boolean} Returns `true` if `value` is a prototype, else `false`.
    +     */
    +    function isPrototype(value) {
    +      var Ctor = value && value.constructor,
    +        proto = typeof Ctor == 'function' && Ctor.prototype || objectProto;
    +      return value === proto;
    +    }
    +
    +    /**
    +     * Checks if `value` is suitable for strict equality comparisons, i.e. `===`.
    +     *
    +     * @private
    +     * @param {*} value The value to check.
    +     * @returns {boolean} Returns `true` if `value` if suitable for strict
    +     *  equality comparisons, else `false`.
    +     */
    +    function isStrictComparable(value) {
    +      return value === value && !isObject(value);
    +    }
    +
    +    /**
    +     * A specialized version of `matchesProperty` for source values suitable
    +     * for strict equality comparisons, i.e. `===`.
    +     *
    +     * @private
    +     * @param {string} key The key of the property to get.
    +     * @param {*} srcValue The value to match.
    +     * @returns {Function} Returns the new spec function.
    +     */
    +    function matchesStrictComparable(key, srcValue) {
    +      return function (object) {
    +        if (object == null) {
    +          return false;
    +        }
    +        return object[key] === srcValue && (srcValue !== undefined || key in Object(object));
    +      };
    +    }
    +
    +    /**
    +     * A specialized version of `_.memoize` which clears the memoized function's
    +     * cache when it exceeds `MAX_MEMOIZE_SIZE`.
    +     *
    +     * @private
    +     * @param {Function} func The function to have its output memoized.
    +     * @returns {Function} Returns the new memoized function.
    +     */
    +    function memoizeCapped(func) {
    +      var result = memoize(func, function (key) {
    +        if (cache.size === MAX_MEMOIZE_SIZE) {
    +          cache.clear();
    +        }
    +        return key;
    +      });
    +      var cache = result.cache;
    +      return result;
    +    }
    +
    +    /**
    +     * Merges the function metadata of `source` into `data`.
    +     *
    +     * Merging metadata reduces the number of wrappers used to invoke a function.
    +     * This is possible because methods like `_.bind`, `_.curry`, and `_.partial`
    +     * may be applied regardless of execution order. Methods like `_.ary` and
    +     * `_.rearg` modify function arguments, making the order in which they are
    +     * executed important, preventing the merging of metadata. However, we make
    +     * an exception for a safe combined case where curried functions have `_.ary`
    +     * and or `_.rearg` applied.
    +     *
    +     * @private
    +     * @param {Array} data The destination metadata.
    +     * @param {Array} source The source metadata.
    +     * @returns {Array} Returns `data`.
    +     */
    +    function mergeData(data, source) {
    +      var bitmask = data[1],
    +        srcBitmask = source[1],
    +        newBitmask = bitmask | srcBitmask,
    +        isCommon = newBitmask < (WRAP_BIND_FLAG | WRAP_BIND_KEY_FLAG | WRAP_ARY_FLAG);
    +      var isCombo = srcBitmask == WRAP_ARY_FLAG && bitmask == WRAP_CURRY_FLAG || srcBitmask == WRAP_ARY_FLAG && bitmask == WRAP_REARG_FLAG && data[7].length <= source[8] || srcBitmask == (WRAP_ARY_FLAG | WRAP_REARG_FLAG) && source[7].length <= source[8] && bitmask == WRAP_CURRY_FLAG;
    +
    +      // Exit early if metadata can't be merged.
    +      if (!(isCommon || isCombo)) {
    +        return data;
    +      }
    +      // Use source `thisArg` if available.
    +      if (srcBitmask & WRAP_BIND_FLAG) {
    +        data[2] = source[2];
    +        // Set when currying a bound function.
    +        newBitmask |= bitmask & WRAP_BIND_FLAG ? 0 : WRAP_CURRY_BOUND_FLAG;
    +      }
    +      // Compose partial arguments.
    +      var value = source[3];
    +      if (value) {
    +        var partials = data[3];
    +        data[3] = partials ? composeArgs(partials, value, source[4]) : value;
    +        data[4] = partials ? replaceHolders(data[3], PLACEHOLDER) : source[4];
    +      }
    +      // Compose partial right arguments.
    +      value = source[5];
    +      if (value) {
    +        partials = data[5];
    +        data[5] = partials ? composeArgsRight(partials, value, source[6]) : value;
    +        data[6] = partials ? replaceHolders(data[5], PLACEHOLDER) : source[6];
    +      }
    +      // Use source `argPos` if available.
    +      value = source[7];
    +      if (value) {
    +        data[7] = value;
    +      }
    +      // Use source `ary` if it's smaller.
    +      if (srcBitmask & WRAP_ARY_FLAG) {
    +        data[8] = data[8] == null ? source[8] : nativeMin(data[8], source[8]);
    +      }
    +      // Use source `arity` if one is not provided.
    +      if (data[9] == null) {
    +        data[9] = source[9];
    +      }
    +      // Use source `func` and merge bitmasks.
    +      data[0] = source[0];
    +      data[1] = newBitmask;
    +      return data;
    +    }
    +
    +    /**
    +     * This function is like
    +     * [`Object.keys`](http://ecma-international.org/ecma-262/7.0/#sec-object.keys)
    +     * except that it includes inherited enumerable properties.
    +     *
    +     * @private
    +     * @param {Object} object The object to query.
    +     * @returns {Array} Returns the array of property names.
    +     */
    +    function nativeKeysIn(object) {
    +      var result = [];
    +      if (object != null) {
    +        for (var key in Object(object)) {
    +          result.push(key);
    +        }
    +      }
    +      return result;
    +    }
    +
    +    /**
    +     * Converts `value` to a string using `Object.prototype.toString`.
    +     *
    +     * @private
    +     * @param {*} value The value to convert.
    +     * @returns {string} Returns the converted string.
    +     */
    +    function objectToString(value) {
    +      return nativeObjectToString.call(value);
    +    }
    +
    +    /**
    +     * A specialized version of `baseRest` which transforms the rest array.
    +     *
    +     * @private
    +     * @param {Function} func The function to apply a rest parameter to.
    +     * @param {number} [start=func.length-1] The start position of the rest parameter.
    +     * @param {Function} transform The rest array transform.
    +     * @returns {Function} Returns the new function.
    +     */
    +    function overRest(func, start, transform) {
    +      start = nativeMax(start === undefined ? func.length - 1 : start, 0);
    +      return function () {
    +        var args = arguments,
    +          index = -1,
    +          length = nativeMax(args.length - start, 0),
    +          array = Array(length);
    +        while (++index < length) {
    +          array[index] = args[start + index];
    +        }
    +        index = -1;
    +        var otherArgs = Array(start + 1);
    +        while (++index < start) {
    +          otherArgs[index] = args[index];
    +        }
    +        otherArgs[start] = transform(array);
    +        return apply(func, this, otherArgs);
    +      };
    +    }
    +
    +    /**
    +     * Gets the parent value at `path` of `object`.
    +     *
    +     * @private
    +     * @param {Object} object The object to query.
    +     * @param {Array} path The path to get the parent value of.
    +     * @returns {*} Returns the parent value.
    +     */
    +    function parent(object, path) {
    +      return path.length < 2 ? object : baseGet(object, baseSlice(path, 0, -1));
    +    }
    +
    +    /**
    +     * Reorder `array` according to the specified indexes where the element at
    +     * the first index is assigned as the first element, the element at
    +     * the second index is assigned as the second element, and so on.
    +     *
    +     * @private
    +     * @param {Array} array The array to reorder.
    +     * @param {Array} indexes The arranged array indexes.
    +     * @returns {Array} Returns `array`.
    +     */
    +    function reorder(array, indexes) {
    +      var arrLength = array.length,
    +        length = nativeMin(indexes.length, arrLength),
    +        oldArray = copyArray(array);
    +      while (length--) {
    +        var index = indexes[length];
    +        array[length] = isIndex(index, arrLength) ? oldArray[index] : undefined;
    +      }
    +      return array;
    +    }
    +
    +    /**
    +     * Gets the value at `key`, unless `key` is "__proto__" or "constructor".
    +     *
    +     * @private
    +     * @param {Object} object The object to query.
    +     * @param {string} key The key of the property to get.
    +     * @returns {*} Returns the property value.
    +     */
    +    function safeGet(object, key) {
    +      if (key === 'constructor' && typeof object[key] === 'function') {
    +        return;
    +      }
    +      if (key == '__proto__') {
    +        return;
    +      }
    +      return object[key];
    +    }
    +
    +    /**
    +     * Sets metadata for `func`.
    +     *
    +     * **Note:** If this function becomes hot, i.e. is invoked a lot in a short
    +     * period of time, it will trip its breaker and transition to an identity
    +     * function to avoid garbage collection pauses in V8. See
    +     * [V8 issue 2070](https://bugs.chromium.org/p/v8/issues/detail?id=2070)
    +     * for more details.
    +     *
    +     * @private
    +     * @param {Function} func The function to associate metadata with.
    +     * @param {*} data The metadata.
    +     * @returns {Function} Returns `func`.
    +     */
    +    var setData = shortOut(baseSetData);
    +
    +    /**
    +     * A simple wrapper around the global [`setTimeout`](https://mdn.io/setTimeout).
    +     *
    +     * @private
    +     * @param {Function} func The function to delay.
    +     * @param {number} wait The number of milliseconds to delay invocation.
    +     * @returns {number|Object} Returns the timer id or timeout object.
    +     */
    +    var setTimeout = ctxSetTimeout || function (func, wait) {
    +      return root.setTimeout(func, wait);
    +    };
    +
    +    /**
    +     * Sets the `toString` method of `func` to return `string`.
    +     *
    +     * @private
    +     * @param {Function} func The function to modify.
    +     * @param {Function} string The `toString` result.
    +     * @returns {Function} Returns `func`.
    +     */
    +    var setToString = shortOut(baseSetToString);
    +
    +    /**
    +     * Sets the `toString` method of `wrapper` to mimic the source of `reference`
    +     * with wrapper details in a comment at the top of the source body.
    +     *
    +     * @private
    +     * @param {Function} wrapper The function to modify.
    +     * @param {Function} reference The reference function.
    +     * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
    +     * @returns {Function} Returns `wrapper`.
    +     */
    +    function setWrapToString(wrapper, reference, bitmask) {
    +      var source = reference + '';
    +      return setToString(wrapper, insertWrapDetails(source, updateWrapDetails(getWrapDetails(source), bitmask)));
    +    }
    +
    +    /**
    +     * Creates a function that'll short out and invoke `identity` instead
    +     * of `func` when it's called `HOT_COUNT` or more times in `HOT_SPAN`
    +     * milliseconds.
    +     *
    +     * @private
    +     * @param {Function} func The function to restrict.
    +     * @returns {Function} Returns the new shortable function.
    +     */
    +    function shortOut(func) {
    +      var count = 0,
    +        lastCalled = 0;
    +      return function () {
    +        var stamp = nativeNow(),
    +          remaining = HOT_SPAN - (stamp - lastCalled);
    +        lastCalled = stamp;
    +        if (remaining > 0) {
    +          if (++count >= HOT_COUNT) {
    +            return arguments[0];
    +          }
    +        } else {
    +          count = 0;
    +        }
    +        return func.apply(undefined, arguments);
    +      };
    +    }
    +
    +    /**
    +     * A specialized version of `_.shuffle` which mutates and sets the size of `array`.
    +     *
    +     * @private
    +     * @param {Array} array The array to shuffle.
    +     * @param {number} [size=array.length] The size of `array`.
    +     * @returns {Array} Returns `array`.
    +     */
    +    function shuffleSelf(array, size) {
    +      var index = -1,
    +        length = array.length,
    +        lastIndex = length - 1;
    +      size = size === undefined ? length : size;
    +      while (++index < size) {
    +        var rand = baseRandom(index, lastIndex),
    +          value = array[rand];
    +        array[rand] = array[index];
    +        array[index] = value;
    +      }
    +      array.length = size;
    +      return array;
    +    }
    +
    +    /**
    +     * Converts `string` to a property path array.
    +     *
    +     * @private
    +     * @param {string} string The string to convert.
    +     * @returns {Array} Returns the property path array.
    +     */
    +    var stringToPath = memoizeCapped(function (string) {
    +      var result = [];
    +      if (string.charCodeAt(0) === 46 /* . */) {
    +        result.push('');
    +      }
    +      string.replace(rePropName, function (match, number, quote, subString) {
    +        result.push(quote ? subString.replace(reEscapeChar, '$1') : number || match);
    +      });
    +      return result;
    +    });
    +
    +    /**
    +     * Converts `value` to a string key if it's not a string or symbol.
    +     *
    +     * @private
    +     * @param {*} value The value to inspect.
    +     * @returns {string|symbol} Returns the key.
    +     */
    +    function toKey(value) {
    +      if (typeof value == 'string' || isSymbol(value)) {
    +        return value;
    +      }
    +      var result = value + '';
    +      return result == '0' && 1 / value == -INFINITY ? '-0' : result;
    +    }
    +
    +    /**
    +     * Converts `func` to its source code.
    +     *
    +     * @private
    +     * @param {Function} func The function to convert.
    +     * @returns {string} Returns the source code.
    +     */
    +    function toSource(func) {
    +      if (func != null) {
    +        try {
    +          return funcToString.call(func);
    +        } catch (e) {}
    +        try {
    +          return func + '';
    +        } catch (e) {}
    +      }
    +      return '';
    +    }
    +
    +    /**
    +     * Updates wrapper `details` based on `bitmask` flags.
    +     *
    +     * @private
    +     * @returns {Array} details The details to modify.
    +     * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
    +     * @returns {Array} Returns `details`.
    +     */
    +    function updateWrapDetails(details, bitmask) {
    +      arrayEach(wrapFlags, function (pair) {
    +        var value = '_.' + pair[0];
    +        if (bitmask & pair[1] && !arrayIncludes(details, value)) {
    +          details.push(value);
    +        }
    +      });
    +      return details.sort();
    +    }
    +
    +    /**
    +     * Creates a clone of `wrapper`.
    +     *
    +     * @private
    +     * @param {Object} wrapper The wrapper to clone.
    +     * @returns {Object} Returns the cloned wrapper.
    +     */
    +    function wrapperClone(wrapper) {
    +      if (wrapper instanceof LazyWrapper) {
    +        return wrapper.clone();
    +      }
    +      var result = new LodashWrapper(wrapper.__wrapped__, wrapper.__chain__);
    +      result.__actions__ = copyArray(wrapper.__actions__);
    +      result.__index__ = wrapper.__index__;
    +      result.__values__ = wrapper.__values__;
    +      return result;
    +    }
    +
    +    /*------------------------------------------------------------------------*/
    +
    +    /**
    +     * Creates an array of elements split into groups the length of `size`.
    +     * If `array` can't be split evenly, the final chunk will be the remaining
    +     * elements.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 3.0.0
    +     * @category Array
    +     * @param {Array} array The array to process.
    +     * @param {number} [size=1] The length of each chunk
    +     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
    +     * @returns {Array} Returns the new array of chunks.
    +     * @example
    +     *
    +     * _.chunk(['a', 'b', 'c', 'd'], 2);
    +     * // => [['a', 'b'], ['c', 'd']]
    +     *
    +     * _.chunk(['a', 'b', 'c', 'd'], 3);
    +     * // => [['a', 'b', 'c'], ['d']]
    +     */
    +    function chunk(array, size, guard) {
    +      if (guard ? isIterateeCall(array, size, guard) : size === undefined) {
    +        size = 1;
    +      } else {
    +        size = nativeMax(toInteger(size), 0);
    +      }
    +      var length = array == null ? 0 : array.length;
    +      if (!length || size < 1) {
    +        return [];
    +      }
    +      var index = 0,
    +        resIndex = 0,
    +        result = Array(nativeCeil(length / size));
    +      while (index < length) {
    +        result[resIndex++] = baseSlice(array, index, index += size);
    +      }
    +      return result;
    +    }
    +
    +    /**
    +     * Creates an array with all falsey values removed. The values `false`, `null`,
    +     * `0`, `""`, `undefined`, and `NaN` are falsey.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.1.0
    +     * @category Array
    +     * @param {Array} array The array to compact.
    +     * @returns {Array} Returns the new array of filtered values.
    +     * @example
    +     *
    +     * _.compact([0, 1, false, 2, '', 3]);
    +     * // => [1, 2, 3]
    +     */
    +    function compact(array) {
    +      var index = -1,
    +        length = array == null ? 0 : array.length,
    +        resIndex = 0,
    +        result = [];
    +      while (++index < length) {
    +        var value = array[index];
    +        if (value) {
    +          result[resIndex++] = value;
    +        }
    +      }
    +      return result;
    +    }
    +
    +    /**
    +     * Creates a new array concatenating `array` with any additional arrays
    +     * and/or values.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 4.0.0
    +     * @category Array
    +     * @param {Array} array The array to concatenate.
    +     * @param {...*} [values] The values to concatenate.
    +     * @returns {Array} Returns the new concatenated array.
    +     * @example
    +     *
    +     * var array = [1];
    +     * var other = _.concat(array, 2, [3], [[4]]);
    +     *
    +     * console.log(other);
    +     * // => [1, 2, 3, [4]]
    +     *
    +     * console.log(array);
    +     * // => [1]
    +     */
    +    function concat() {
    +      var length = arguments.length;
    +      if (!length) {
    +        return [];
    +      }
    +      var args = Array(length - 1),
    +        array = arguments[0],
    +        index = length;
    +      while (index--) {
    +        args[index - 1] = arguments[index];
    +      }
    +      return arrayPush(isArray(array) ? copyArray(array) : [array], baseFlatten(args, 1));
    +    }
    +
    +    /**
    +     * Creates an array of `array` values not included in the other given arrays
    +     * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
    +     * for equality comparisons. The order and references of result values are
    +     * determined by the first array.
    +     *
    +     * **Note:** Unlike `_.pullAll`, this method returns a new array.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.1.0
    +     * @category Array
    +     * @param {Array} array The array to inspect.
    +     * @param {...Array} [values] The values to exclude.
    +     * @returns {Array} Returns the new array of filtered values.
    +     * @see _.without, _.xor
    +     * @example
    +     *
    +     * _.difference([2, 1], [2, 3]);
    +     * // => [1]
    +     */
    +    var difference = baseRest(function (array, values) {
    +      return isArrayLikeObject(array) ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true)) : [];
    +    });
    +
    +    /**
    +     * This method is like `_.difference` except that it accepts `iteratee` which
    +     * is invoked for each element of `array` and `values` to generate the criterion
    +     * by which they're compared. The order and references of result values are
    +     * determined by the first array. The iteratee is invoked with one argument:
    +     * (value).
    +     *
    +     * **Note:** Unlike `_.pullAllBy`, this method returns a new array.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 4.0.0
    +     * @category Array
    +     * @param {Array} array The array to inspect.
    +     * @param {...Array} [values] The values to exclude.
    +     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
    +     * @returns {Array} Returns the new array of filtered values.
    +     * @example
    +     *
    +     * _.differenceBy([2.1, 1.2], [2.3, 3.4], Math.floor);
    +     * // => [1.2]
    +     *
    +     * // The `_.property` iteratee shorthand.
    +     * _.differenceBy([{ 'x': 2 }, { 'x': 1 }], [{ 'x': 1 }], 'x');
    +     * // => [{ 'x': 2 }]
    +     */
    +    var differenceBy = baseRest(function (array, values) {
    +      var iteratee = last(values);
    +      if (isArrayLikeObject(iteratee)) {
    +        iteratee = undefined;
    +      }
    +      return isArrayLikeObject(array) ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true), getIteratee(iteratee, 2)) : [];
    +    });
    +
    +    /**
    +     * This method is like `_.difference` except that it accepts `comparator`
    +     * which is invoked to compare elements of `array` to `values`. The order and
    +     * references of result values are determined by the first array. The comparator
    +     * is invoked with two arguments: (arrVal, othVal).
    +     *
    +     * **Note:** Unlike `_.pullAllWith`, this method returns a new array.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 4.0.0
    +     * @category Array
    +     * @param {Array} array The array to inspect.
    +     * @param {...Array} [values] The values to exclude.
    +     * @param {Function} [comparator] The comparator invoked per element.
    +     * @returns {Array} Returns the new array of filtered values.
    +     * @example
    +     *
    +     * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
    +     *
    +     * _.differenceWith(objects, [{ 'x': 1, 'y': 2 }], _.isEqual);
    +     * // => [{ 'x': 2, 'y': 1 }]
    +     */
    +    var differenceWith = baseRest(function (array, values) {
    +      var comparator = last(values);
    +      if (isArrayLikeObject(comparator)) {
    +        comparator = undefined;
    +      }
    +      return isArrayLikeObject(array) ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true), undefined, comparator) : [];
    +    });
    +
    +    /**
    +     * Creates a slice of `array` with `n` elements dropped from the beginning.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.5.0
    +     * @category Array
    +     * @param {Array} array The array to query.
    +     * @param {number} [n=1] The number of elements to drop.
    +     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
    +     * @returns {Array} Returns the slice of `array`.
    +     * @example
    +     *
    +     * _.drop([1, 2, 3]);
    +     * // => [2, 3]
    +     *
    +     * _.drop([1, 2, 3], 2);
    +     * // => [3]
    +     *
    +     * _.drop([1, 2, 3], 5);
    +     * // => []
    +     *
    +     * _.drop([1, 2, 3], 0);
    +     * // => [1, 2, 3]
    +     */
    +    function drop(array, n, guard) {
    +      var length = array == null ? 0 : array.length;
    +      if (!length) {
    +        return [];
    +      }
    +      n = guard || n === undefined ? 1 : toInteger(n);
    +      return baseSlice(array, n < 0 ? 0 : n, length);
    +    }
    +
    +    /**
    +     * Creates a slice of `array` with `n` elements dropped from the end.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 3.0.0
    +     * @category Array
    +     * @param {Array} array The array to query.
    +     * @param {number} [n=1] The number of elements to drop.
    +     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
    +     * @returns {Array} Returns the slice of `array`.
    +     * @example
    +     *
    +     * _.dropRight([1, 2, 3]);
    +     * // => [1, 2]
    +     *
    +     * _.dropRight([1, 2, 3], 2);
    +     * // => [1]
    +     *
    +     * _.dropRight([1, 2, 3], 5);
    +     * // => []
    +     *
    +     * _.dropRight([1, 2, 3], 0);
    +     * // => [1, 2, 3]
    +     */
    +    function dropRight(array, n, guard) {
    +      var length = array == null ? 0 : array.length;
    +      if (!length) {
    +        return [];
    +      }
    +      n = guard || n === undefined ? 1 : toInteger(n);
    +      n = length - n;
    +      return baseSlice(array, 0, n < 0 ? 0 : n);
    +    }
    +
    +    /**
    +     * Creates a slice of `array` excluding elements dropped from the end.
    +     * Elements are dropped until `predicate` returns falsey. The predicate is
    +     * invoked with three arguments: (value, index, array).
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 3.0.0
    +     * @category Array
    +     * @param {Array} array The array to query.
    +     * @param {Function} [predicate=_.identity] The function invoked per iteration.
    +     * @returns {Array} Returns the slice of `array`.
    +     * @example
    +     *
    +     * var users = [
    +     *   { 'user': 'barney',  'active': true },
    +     *   { 'user': 'fred',    'active': false },
    +     *   { 'user': 'pebbles', 'active': false }
    +     * ];
    +     *
    +     * _.dropRightWhile(users, function(o) { return !o.active; });
    +     * // => objects for ['barney']
    +     *
    +     * // The `_.matches` iteratee shorthand.
    +     * _.dropRightWhile(users, { 'user': 'pebbles', 'active': false });
    +     * // => objects for ['barney', 'fred']
    +     *
    +     * // The `_.matchesProperty` iteratee shorthand.
    +     * _.dropRightWhile(users, ['active', false]);
    +     * // => objects for ['barney']
    +     *
    +     * // The `_.property` iteratee shorthand.
    +     * _.dropRightWhile(users, 'active');
    +     * // => objects for ['barney', 'fred', 'pebbles']
    +     */
    +    function dropRightWhile(array, predicate) {
    +      return array && array.length ? baseWhile(array, getIteratee(predicate, 3), true, true) : [];
    +    }
    +
    +    /**
    +     * Creates a slice of `array` excluding elements dropped from the beginning.
    +     * Elements are dropped until `predicate` returns falsey. The predicate is
    +     * invoked with three arguments: (value, index, array).
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 3.0.0
    +     * @category Array
    +     * @param {Array} array The array to query.
    +     * @param {Function} [predicate=_.identity] The function invoked per iteration.
    +     * @returns {Array} Returns the slice of `array`.
    +     * @example
    +     *
    +     * var users = [
    +     *   { 'user': 'barney',  'active': false },
    +     *   { 'user': 'fred',    'active': false },
    +     *   { 'user': 'pebbles', 'active': true }
    +     * ];
    +     *
    +     * _.dropWhile(users, function(o) { return !o.active; });
    +     * // => objects for ['pebbles']
    +     *
    +     * // The `_.matches` iteratee shorthand.
    +     * _.dropWhile(users, { 'user': 'barney', 'active': false });
    +     * // => objects for ['fred', 'pebbles']
    +     *
    +     * // The `_.matchesProperty` iteratee shorthand.
    +     * _.dropWhile(users, ['active', false]);
    +     * // => objects for ['pebbles']
    +     *
    +     * // The `_.property` iteratee shorthand.
    +     * _.dropWhile(users, 'active');
    +     * // => objects for ['barney', 'fred', 'pebbles']
    +     */
    +    function dropWhile(array, predicate) {
    +      return array && array.length ? baseWhile(array, getIteratee(predicate, 3), true) : [];
    +    }
    +
    +    /**
    +     * Fills elements of `array` with `value` from `start` up to, but not
    +     * including, `end`.
    +     *
    +     * **Note:** This method mutates `array`.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 3.2.0
    +     * @category Array
    +     * @param {Array} array The array to fill.
    +     * @param {*} value The value to fill `array` with.
    +     * @param {number} [start=0] The start position.
    +     * @param {number} [end=array.length] The end position.
    +     * @returns {Array} Returns `array`.
    +     * @example
    +     *
    +     * var array = [1, 2, 3];
    +     *
    +     * _.fill(array, 'a');
    +     * console.log(array);
    +     * // => ['a', 'a', 'a']
    +     *
    +     * _.fill(Array(3), 2);
    +     * // => [2, 2, 2]
    +     *
    +     * _.fill([4, 6, 8, 10], '*', 1, 3);
    +     * // => [4, '*', '*', 10]
    +     */
    +    function fill(array, value, start, end) {
    +      var length = array == null ? 0 : array.length;
    +      if (!length) {
    +        return [];
    +      }
    +      if (start && typeof start != 'number' && isIterateeCall(array, value, start)) {
    +        start = 0;
    +        end = length;
    +      }
    +      return baseFill(array, value, start, end);
    +    }
    +
    +    /**
    +     * This method is like `_.find` except that it returns the index of the first
    +     * element `predicate` returns truthy for instead of the element itself.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 1.1.0
    +     * @category Array
    +     * @param {Array} array The array to inspect.
    +     * @param {Function} [predicate=_.identity] The function invoked per iteration.
    +     * @param {number} [fromIndex=0] The index to search from.
    +     * @returns {number} Returns the index of the found element, else `-1`.
    +     * @example
    +     *
    +     * var users = [
    +     *   { 'user': 'barney',  'active': false },
    +     *   { 'user': 'fred',    'active': false },
    +     *   { 'user': 'pebbles', 'active': true }
    +     * ];
    +     *
    +     * _.findIndex(users, function(o) { return o.user == 'barney'; });
    +     * // => 0
    +     *
    +     * // The `_.matches` iteratee shorthand.
    +     * _.findIndex(users, { 'user': 'fred', 'active': false });
    +     * // => 1
    +     *
    +     * // The `_.matchesProperty` iteratee shorthand.
    +     * _.findIndex(users, ['active', false]);
    +     * // => 0
    +     *
    +     * // The `_.property` iteratee shorthand.
    +     * _.findIndex(users, 'active');
    +     * // => 2
    +     */
    +    function findIndex(array, predicate, fromIndex) {
    +      var length = array == null ? 0 : array.length;
    +      if (!length) {
    +        return -1;
    +      }
    +      var index = fromIndex == null ? 0 : toInteger(fromIndex);
    +      if (index < 0) {
    +        index = nativeMax(length + index, 0);
    +      }
    +      return baseFindIndex(array, getIteratee(predicate, 3), index);
    +    }
    +
    +    /**
    +     * This method is like `_.findIndex` except that it iterates over elements
    +     * of `collection` from right to left.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 2.0.0
    +     * @category Array
    +     * @param {Array} array The array to inspect.
    +     * @param {Function} [predicate=_.identity] The function invoked per iteration.
    +     * @param {number} [fromIndex=array.length-1] The index to search from.
    +     * @returns {number} Returns the index of the found element, else `-1`.
    +     * @example
    +     *
    +     * var users = [
    +     *   { 'user': 'barney',  'active': true },
    +     *   { 'user': 'fred',    'active': false },
    +     *   { 'user': 'pebbles', 'active': false }
    +     * ];
    +     *
    +     * _.findLastIndex(users, function(o) { return o.user == 'pebbles'; });
    +     * // => 2
    +     *
    +     * // The `_.matches` iteratee shorthand.
    +     * _.findLastIndex(users, { 'user': 'barney', 'active': true });
    +     * // => 0
    +     *
    +     * // The `_.matchesProperty` iteratee shorthand.
    +     * _.findLastIndex(users, ['active', false]);
    +     * // => 2
    +     *
    +     * // The `_.property` iteratee shorthand.
    +     * _.findLastIndex(users, 'active');
    +     * // => 0
    +     */
    +    function findLastIndex(array, predicate, fromIndex) {
    +      var length = array == null ? 0 : array.length;
    +      if (!length) {
    +        return -1;
    +      }
    +      var index = length - 1;
    +      if (fromIndex !== undefined) {
    +        index = toInteger(fromIndex);
    +        index = fromIndex < 0 ? nativeMax(length + index, 0) : nativeMin(index, length - 1);
    +      }
    +      return baseFindIndex(array, getIteratee(predicate, 3), index, true);
    +    }
    +
    +    /**
    +     * Flattens `array` a single level deep.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.1.0
    +     * @category Array
    +     * @param {Array} array The array to flatten.
    +     * @returns {Array} Returns the new flattened array.
    +     * @example
    +     *
    +     * _.flatten([1, [2, [3, [4]], 5]]);
    +     * // => [1, 2, [3, [4]], 5]
    +     */
    +    function flatten(array) {
    +      var length = array == null ? 0 : array.length;
    +      return length ? baseFlatten(array, 1) : [];
    +    }
    +
    +    /**
    +     * Recursively flattens `array`.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 3.0.0
    +     * @category Array
    +     * @param {Array} array The array to flatten.
    +     * @returns {Array} Returns the new flattened array.
    +     * @example
    +     *
    +     * _.flattenDeep([1, [2, [3, [4]], 5]]);
    +     * // => [1, 2, 3, 4, 5]
    +     */
    +    function flattenDeep(array) {
    +      var length = array == null ? 0 : array.length;
    +      return length ? baseFlatten(array, INFINITY) : [];
    +    }
    +
    +    /**
    +     * Recursively flatten `array` up to `depth` times.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 4.4.0
    +     * @category Array
    +     * @param {Array} array The array to flatten.
    +     * @param {number} [depth=1] The maximum recursion depth.
    +     * @returns {Array} Returns the new flattened array.
    +     * @example
    +     *
    +     * var array = [1, [2, [3, [4]], 5]];
    +     *
    +     * _.flattenDepth(array, 1);
    +     * // => [1, 2, [3, [4]], 5]
    +     *
    +     * _.flattenDepth(array, 2);
    +     * // => [1, 2, 3, [4], 5]
    +     */
    +    function flattenDepth(array, depth) {
    +      var length = array == null ? 0 : array.length;
    +      if (!length) {
    +        return [];
    +      }
    +      depth = depth === undefined ? 1 : toInteger(depth);
    +      return baseFlatten(array, depth);
    +    }
    +
    +    /**
    +     * The inverse of `_.toPairs`; this method returns an object composed
    +     * from key-value `pairs`.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 4.0.0
    +     * @category Array
    +     * @param {Array} pairs The key-value pairs.
    +     * @returns {Object} Returns the new object.
    +     * @example
    +     *
    +     * _.fromPairs([['a', 1], ['b', 2]]);
    +     * // => { 'a': 1, 'b': 2 }
    +     */
    +    function fromPairs(pairs) {
    +      var index = -1,
    +        length = pairs == null ? 0 : pairs.length,
    +        result = {};
    +      while (++index < length) {
    +        var pair = pairs[index];
    +        result[pair[0]] = pair[1];
    +      }
    +      return result;
    +    }
    +
    +    /**
    +     * Gets the first element of `array`.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.1.0
    +     * @alias first
    +     * @category Array
    +     * @param {Array} array The array to query.
    +     * @returns {*} Returns the first element of `array`.
    +     * @example
    +     *
    +     * _.head([1, 2, 3]);
    +     * // => 1
    +     *
    +     * _.head([]);
    +     * // => undefined
    +     */
    +    function head(array) {
    +      return array && array.length ? array[0] : undefined;
    +    }
    +
    +    /**
    +     * Gets the index at which the first occurrence of `value` is found in `array`
    +     * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
    +     * for equality comparisons. If `fromIndex` is negative, it's used as the
    +     * offset from the end of `array`.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.1.0
    +     * @category Array
    +     * @param {Array} array The array to inspect.
    +     * @param {*} value The value to search for.
    +     * @param {number} [fromIndex=0] The index to search from.
    +     * @returns {number} Returns the index of the matched value, else `-1`.
    +     * @example
    +     *
    +     * _.indexOf([1, 2, 1, 2], 2);
    +     * // => 1
    +     *
    +     * // Search from the `fromIndex`.
    +     * _.indexOf([1, 2, 1, 2], 2, 2);
    +     * // => 3
    +     */
    +    function indexOf(array, value, fromIndex) {
    +      var length = array == null ? 0 : array.length;
    +      if (!length) {
    +        return -1;
    +      }
    +      var index = fromIndex == null ? 0 : toInteger(fromIndex);
    +      if (index < 0) {
    +        index = nativeMax(length + index, 0);
    +      }
    +      return baseIndexOf(array, value, index);
    +    }
    +
    +    /**
    +     * Gets all but the last element of `array`.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.1.0
    +     * @category Array
    +     * @param {Array} array The array to query.
    +     * @returns {Array} Returns the slice of `array`.
    +     * @example
    +     *
    +     * _.initial([1, 2, 3]);
    +     * // => [1, 2]
    +     */
    +    function initial(array) {
    +      var length = array == null ? 0 : array.length;
    +      return length ? baseSlice(array, 0, -1) : [];
    +    }
    +
    +    /**
    +     * Creates an array of unique values that are included in all given arrays
    +     * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
    +     * for equality comparisons. The order and references of result values are
    +     * determined by the first array.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.1.0
    +     * @category Array
    +     * @param {...Array} [arrays] The arrays to inspect.
    +     * @returns {Array} Returns the new array of intersecting values.
    +     * @example
    +     *
    +     * _.intersection([2, 1], [2, 3]);
    +     * // => [2]
    +     */
    +    var intersection = baseRest(function (arrays) {
    +      var mapped = arrayMap(arrays, castArrayLikeObject);
    +      return mapped.length && mapped[0] === arrays[0] ? baseIntersection(mapped) : [];
    +    });
    +
    +    /**
    +     * This method is like `_.intersection` except that it accepts `iteratee`
    +     * which is invoked for each element of each `arrays` to generate the criterion
    +     * by which they're compared. The order and references of result values are
    +     * determined by the first array. The iteratee is invoked with one argument:
    +     * (value).
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 4.0.0
    +     * @category Array
    +     * @param {...Array} [arrays] The arrays to inspect.
    +     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
    +     * @returns {Array} Returns the new array of intersecting values.
    +     * @example
    +     *
    +     * _.intersectionBy([2.1, 1.2], [2.3, 3.4], Math.floor);
    +     * // => [2.1]
    +     *
    +     * // The `_.property` iteratee shorthand.
    +     * _.intersectionBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');
    +     * // => [{ 'x': 1 }]
    +     */
    +    var intersectionBy = baseRest(function (arrays) {
    +      var iteratee = last(arrays),
    +        mapped = arrayMap(arrays, castArrayLikeObject);
    +      if (iteratee === last(mapped)) {
    +        iteratee = undefined;
    +      } else {
    +        mapped.pop();
    +      }
    +      return mapped.length && mapped[0] === arrays[0] ? baseIntersection(mapped, getIteratee(iteratee, 2)) : [];
    +    });
    +
    +    /**
    +     * This method is like `_.intersection` except that it accepts `comparator`
    +     * which is invoked to compare elements of `arrays`. The order and references
    +     * of result values are determined by the first array. The comparator is
    +     * invoked with two arguments: (arrVal, othVal).
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 4.0.0
    +     * @category Array
    +     * @param {...Array} [arrays] The arrays to inspect.
    +     * @param {Function} [comparator] The comparator invoked per element.
    +     * @returns {Array} Returns the new array of intersecting values.
    +     * @example
    +     *
    +     * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
    +     * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];
    +     *
    +     * _.intersectionWith(objects, others, _.isEqual);
    +     * // => [{ 'x': 1, 'y': 2 }]
    +     */
    +    var intersectionWith = baseRest(function (arrays) {
    +      var comparator = last(arrays),
    +        mapped = arrayMap(arrays, castArrayLikeObject);
    +      comparator = typeof comparator == 'function' ? comparator : undefined;
    +      if (comparator) {
    +        mapped.pop();
    +      }
    +      return mapped.length && mapped[0] === arrays[0] ? baseIntersection(mapped, undefined, comparator) : [];
    +    });
    +
    +    /**
    +     * Converts all elements in `array` into a string separated by `separator`.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 4.0.0
    +     * @category Array
    +     * @param {Array} array The array to convert.
    +     * @param {string} [separator=','] The element separator.
    +     * @returns {string} Returns the joined string.
    +     * @example
    +     *
    +     * _.join(['a', 'b', 'c'], '~');
    +     * // => 'a~b~c'
    +     */
    +    function join(array, separator) {
    +      return array == null ? '' : nativeJoin.call(array, separator);
    +    }
    +
    +    /**
    +     * Gets the last element of `array`.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.1.0
    +     * @category Array
    +     * @param {Array} array The array to query.
    +     * @returns {*} Returns the last element of `array`.
    +     * @example
    +     *
    +     * _.last([1, 2, 3]);
    +     * // => 3
    +     */
    +    function last(array) {
    +      var length = array == null ? 0 : array.length;
    +      return length ? array[length - 1] : undefined;
    +    }
    +
    +    /**
    +     * This method is like `_.indexOf` except that it iterates over elements of
    +     * `array` from right to left.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.1.0
    +     * @category Array
    +     * @param {Array} array The array to inspect.
    +     * @param {*} value The value to search for.
    +     * @param {number} [fromIndex=array.length-1] The index to search from.
    +     * @returns {number} Returns the index of the matched value, else `-1`.
    +     * @example
    +     *
    +     * _.lastIndexOf([1, 2, 1, 2], 2);
    +     * // => 3
    +     *
    +     * // Search from the `fromIndex`.
    +     * _.lastIndexOf([1, 2, 1, 2], 2, 2);
    +     * // => 1
    +     */
    +    function lastIndexOf(array, value, fromIndex) {
    +      var length = array == null ? 0 : array.length;
    +      if (!length) {
    +        return -1;
    +      }
    +      var index = length;
    +      if (fromIndex !== undefined) {
    +        index = toInteger(fromIndex);
    +        index = index < 0 ? nativeMax(length + index, 0) : nativeMin(index, length - 1);
    +      }
    +      return value === value ? strictLastIndexOf(array, value, index) : baseFindIndex(array, baseIsNaN, index, true);
    +    }
    +
    +    /**
    +     * Gets the element at index `n` of `array`. If `n` is negative, the nth
    +     * element from the end is returned.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 4.11.0
    +     * @category Array
    +     * @param {Array} array The array to query.
    +     * @param {number} [n=0] The index of the element to return.
    +     * @returns {*} Returns the nth element of `array`.
    +     * @example
    +     *
    +     * var array = ['a', 'b', 'c', 'd'];
    +     *
    +     * _.nth(array, 1);
    +     * // => 'b'
    +     *
    +     * _.nth(array, -2);
    +     * // => 'c';
    +     */
    +    function nth(array, n) {
    +      return array && array.length ? baseNth(array, toInteger(n)) : undefined;
    +    }
    +
    +    /**
    +     * Removes all given values from `array` using
    +     * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
    +     * for equality comparisons.
    +     *
    +     * **Note:** Unlike `_.without`, this method mutates `array`. Use `_.remove`
    +     * to remove elements from an array by predicate.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 2.0.0
    +     * @category Array
    +     * @param {Array} array The array to modify.
    +     * @param {...*} [values] The values to remove.
    +     * @returns {Array} Returns `array`.
    +     * @example
    +     *
    +     * var array = ['a', 'b', 'c', 'a', 'b', 'c'];
    +     *
    +     * _.pull(array, 'a', 'c');
    +     * console.log(array);
    +     * // => ['b', 'b']
    +     */
    +    var pull = baseRest(pullAll);
    +
    +    /**
    +     * This method is like `_.pull` except that it accepts an array of values to remove.
    +     *
    +     * **Note:** Unlike `_.difference`, this method mutates `array`.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 4.0.0
    +     * @category Array
    +     * @param {Array} array The array to modify.
    +     * @param {Array} values The values to remove.
    +     * @returns {Array} Returns `array`.
    +     * @example
    +     *
    +     * var array = ['a', 'b', 'c', 'a', 'b', 'c'];
    +     *
    +     * _.pullAll(array, ['a', 'c']);
    +     * console.log(array);
    +     * // => ['b', 'b']
    +     */
    +    function pullAll(array, values) {
    +      return array && array.length && values && values.length ? basePullAll(array, values) : array;
    +    }
    +
    +    /**
    +     * This method is like `_.pullAll` except that it accepts `iteratee` which is
    +     * invoked for each element of `array` and `values` to generate the criterion
    +     * by which they're compared. The iteratee is invoked with one argument: (value).
    +     *
    +     * **Note:** Unlike `_.differenceBy`, this method mutates `array`.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 4.0.0
    +     * @category Array
    +     * @param {Array} array The array to modify.
    +     * @param {Array} values The values to remove.
    +     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
    +     * @returns {Array} Returns `array`.
    +     * @example
    +     *
    +     * var array = [{ 'x': 1 }, { 'x': 2 }, { 'x': 3 }, { 'x': 1 }];
    +     *
    +     * _.pullAllBy(array, [{ 'x': 1 }, { 'x': 3 }], 'x');
    +     * console.log(array);
    +     * // => [{ 'x': 2 }]
    +     */
    +    function pullAllBy(array, values, iteratee) {
    +      return array && array.length && values && values.length ? basePullAll(array, values, getIteratee(iteratee, 2)) : array;
    +    }
    +
    +    /**
    +     * This method is like `_.pullAll` except that it accepts `comparator` which
    +     * is invoked to compare elements of `array` to `values`. The comparator is
    +     * invoked with two arguments: (arrVal, othVal).
    +     *
    +     * **Note:** Unlike `_.differenceWith`, this method mutates `array`.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 4.6.0
    +     * @category Array
    +     * @param {Array} array The array to modify.
    +     * @param {Array} values The values to remove.
    +     * @param {Function} [comparator] The comparator invoked per element.
    +     * @returns {Array} Returns `array`.
    +     * @example
    +     *
    +     * var array = [{ 'x': 1, 'y': 2 }, { 'x': 3, 'y': 4 }, { 'x': 5, 'y': 6 }];
    +     *
    +     * _.pullAllWith(array, [{ 'x': 3, 'y': 4 }], _.isEqual);
    +     * console.log(array);
    +     * // => [{ 'x': 1, 'y': 2 }, { 'x': 5, 'y': 6 }]
    +     */
    +    function pullAllWith(array, values, comparator) {
    +      return array && array.length && values && values.length ? basePullAll(array, values, undefined, comparator) : array;
    +    }
    +
    +    /**
    +     * Removes elements from `array` corresponding to `indexes` and returns an
    +     * array of removed elements.
    +     *
    +     * **Note:** Unlike `_.at`, this method mutates `array`.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 3.0.0
    +     * @category Array
    +     * @param {Array} array The array to modify.
    +     * @param {...(number|number[])} [indexes] The indexes of elements to remove.
    +     * @returns {Array} Returns the new array of removed elements.
    +     * @example
    +     *
    +     * var array = ['a', 'b', 'c', 'd'];
    +     * var pulled = _.pullAt(array, [1, 3]);
    +     *
    +     * console.log(array);
    +     * // => ['a', 'c']
    +     *
    +     * console.log(pulled);
    +     * // => ['b', 'd']
    +     */
    +    var pullAt = flatRest(function (array, indexes) {
    +      var length = array == null ? 0 : array.length,
    +        result = baseAt(array, indexes);
    +      basePullAt(array, arrayMap(indexes, function (index) {
    +        return isIndex(index, length) ? +index : index;
    +      }).sort(compareAscending));
    +      return result;
    +    });
    +
    +    /**
    +     * Removes all elements from `array` that `predicate` returns truthy for
    +     * and returns an array of the removed elements. The predicate is invoked
    +     * with three arguments: (value, index, array).
    +     *
    +     * **Note:** Unlike `_.filter`, this method mutates `array`. Use `_.pull`
    +     * to pull elements from an array by value.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 2.0.0
    +     * @category Array
    +     * @param {Array} array The array to modify.
    +     * @param {Function} [predicate=_.identity] The function invoked per iteration.
    +     * @returns {Array} Returns the new array of removed elements.
    +     * @example
    +     *
    +     * var array = [1, 2, 3, 4];
    +     * var evens = _.remove(array, function(n) {
    +     *   return n % 2 == 0;
    +     * });
    +     *
    +     * console.log(array);
    +     * // => [1, 3]
    +     *
    +     * console.log(evens);
    +     * // => [2, 4]
    +     */
    +    function remove(array, predicate) {
    +      var result = [];
    +      if (!(array && array.length)) {
    +        return result;
    +      }
    +      var index = -1,
    +        indexes = [],
    +        length = array.length;
    +      predicate = getIteratee(predicate, 3);
    +      while (++index < length) {
    +        var value = array[index];
    +        if (predicate(value, index, array)) {
    +          result.push(value);
    +          indexes.push(index);
    +        }
    +      }
    +      basePullAt(array, indexes);
    +      return result;
    +    }
    +
    +    /**
    +     * Reverses `array` so that the first element becomes the last, the second
    +     * element becomes the second to last, and so on.
    +     *
    +     * **Note:** This method mutates `array` and is based on
    +     * [`Array#reverse`](https://mdn.io/Array/reverse).
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 4.0.0
    +     * @category Array
    +     * @param {Array} array The array to modify.
    +     * @returns {Array} Returns `array`.
    +     * @example
    +     *
    +     * var array = [1, 2, 3];
    +     *
    +     * _.reverse(array);
    +     * // => [3, 2, 1]
    +     *
    +     * console.log(array);
    +     * // => [3, 2, 1]
    +     */
    +    function reverse(array) {
    +      return array == null ? array : nativeReverse.call(array);
    +    }
    +
    +    /**
    +     * Creates a slice of `array` from `start` up to, but not including, `end`.
    +     *
    +     * **Note:** This method is used instead of
    +     * [`Array#slice`](https://mdn.io/Array/slice) to ensure dense arrays are
    +     * returned.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 3.0.0
    +     * @category Array
    +     * @param {Array} array The array to slice.
    +     * @param {number} [start=0] The start position.
    +     * @param {number} [end=array.length] The end position.
    +     * @returns {Array} Returns the slice of `array`.
    +     */
    +    function slice(array, start, end) {
    +      var length = array == null ? 0 : array.length;
    +      if (!length) {
    +        return [];
    +      }
    +      if (end && typeof end != 'number' && isIterateeCall(array, start, end)) {
    +        start = 0;
    +        end = length;
    +      } else {
    +        start = start == null ? 0 : toInteger(start);
    +        end = end === undefined ? length : toInteger(end);
    +      }
    +      return baseSlice(array, start, end);
    +    }
    +
    +    /**
    +     * Uses a binary search to determine the lowest index at which `value`
    +     * should be inserted into `array` in order to maintain its sort order.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.1.0
    +     * @category Array
    +     * @param {Array} array The sorted array to inspect.
    +     * @param {*} value The value to evaluate.
    +     * @returns {number} Returns the index at which `value` should be inserted
    +     *  into `array`.
    +     * @example
    +     *
    +     * _.sortedIndex([30, 50], 40);
    +     * // => 1
    +     */
    +    function sortedIndex(array, value) {
    +      return baseSortedIndex(array, value);
    +    }
    +
    +    /**
    +     * This method is like `_.sortedIndex` except that it accepts `iteratee`
    +     * which is invoked for `value` and each element of `array` to compute their
    +     * sort ranking. The iteratee is invoked with one argument: (value).
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 4.0.0
    +     * @category Array
    +     * @param {Array} array The sorted array to inspect.
    +     * @param {*} value The value to evaluate.
    +     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
    +     * @returns {number} Returns the index at which `value` should be inserted
    +     *  into `array`.
    +     * @example
    +     *
    +     * var objects = [{ 'x': 4 }, { 'x': 5 }];
    +     *
    +     * _.sortedIndexBy(objects, { 'x': 4 }, function(o) { return o.x; });
    +     * // => 0
    +     *
    +     * // The `_.property` iteratee shorthand.
    +     * _.sortedIndexBy(objects, { 'x': 4 }, 'x');
    +     * // => 0
    +     */
    +    function sortedIndexBy(array, value, iteratee) {
    +      return baseSortedIndexBy(array, value, getIteratee(iteratee, 2));
    +    }
    +
    +    /**
    +     * This method is like `_.indexOf` except that it performs a binary
    +     * search on a sorted `array`.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 4.0.0
    +     * @category Array
    +     * @param {Array} array The array to inspect.
    +     * @param {*} value The value to search for.
    +     * @returns {number} Returns the index of the matched value, else `-1`.
    +     * @example
    +     *
    +     * _.sortedIndexOf([4, 5, 5, 5, 6], 5);
    +     * // => 1
    +     */
    +    function sortedIndexOf(array, value) {
    +      var length = array == null ? 0 : array.length;
    +      if (length) {
    +        var index = baseSortedIndex(array, value);
    +        if (index < length && eq(array[index], value)) {
    +          return index;
    +        }
    +      }
    +      return -1;
    +    }
    +
    +    /**
    +     * This method is like `_.sortedIndex` except that it returns the highest
    +     * index at which `value` should be inserted into `array` in order to
    +     * maintain its sort order.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 3.0.0
    +     * @category Array
    +     * @param {Array} array The sorted array to inspect.
    +     * @param {*} value The value to evaluate.
    +     * @returns {number} Returns the index at which `value` should be inserted
    +     *  into `array`.
    +     * @example
    +     *
    +     * _.sortedLastIndex([4, 5, 5, 5, 6], 5);
    +     * // => 4
    +     */
    +    function sortedLastIndex(array, value) {
    +      return baseSortedIndex(array, value, true);
    +    }
    +
    +    /**
    +     * This method is like `_.sortedLastIndex` except that it accepts `iteratee`
    +     * which is invoked for `value` and each element of `array` to compute their
    +     * sort ranking. The iteratee is invoked with one argument: (value).
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 4.0.0
    +     * @category Array
    +     * @param {Array} array The sorted array to inspect.
    +     * @param {*} value The value to evaluate.
    +     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
    +     * @returns {number} Returns the index at which `value` should be inserted
    +     *  into `array`.
    +     * @example
    +     *
    +     * var objects = [{ 'x': 4 }, { 'x': 5 }];
    +     *
    +     * _.sortedLastIndexBy(objects, { 'x': 4 }, function(o) { return o.x; });
    +     * // => 1
    +     *
    +     * // The `_.property` iteratee shorthand.
    +     * _.sortedLastIndexBy(objects, { 'x': 4 }, 'x');
    +     * // => 1
    +     */
    +    function sortedLastIndexBy(array, value, iteratee) {
    +      return baseSortedIndexBy(array, value, getIteratee(iteratee, 2), true);
    +    }
    +
    +    /**
    +     * This method is like `_.lastIndexOf` except that it performs a binary
    +     * search on a sorted `array`.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 4.0.0
    +     * @category Array
    +     * @param {Array} array The array to inspect.
    +     * @param {*} value The value to search for.
    +     * @returns {number} Returns the index of the matched value, else `-1`.
    +     * @example
    +     *
    +     * _.sortedLastIndexOf([4, 5, 5, 5, 6], 5);
    +     * // => 3
    +     */
    +    function sortedLastIndexOf(array, value) {
    +      var length = array == null ? 0 : array.length;
    +      if (length) {
    +        var index = baseSortedIndex(array, value, true) - 1;
    +        if (eq(array[index], value)) {
    +          return index;
    +        }
    +      }
    +      return -1;
    +    }
    +
    +    /**
    +     * This method is like `_.uniq` except that it's designed and optimized
    +     * for sorted arrays.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 4.0.0
    +     * @category Array
    +     * @param {Array} array The array to inspect.
    +     * @returns {Array} Returns the new duplicate free array.
    +     * @example
    +     *
    +     * _.sortedUniq([1, 1, 2]);
    +     * // => [1, 2]
    +     */
    +    function sortedUniq(array) {
    +      return array && array.length ? baseSortedUniq(array) : [];
    +    }
    +
    +    /**
    +     * This method is like `_.uniqBy` except that it's designed and optimized
    +     * for sorted arrays.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 4.0.0
    +     * @category Array
    +     * @param {Array} array The array to inspect.
    +     * @param {Function} [iteratee] The iteratee invoked per element.
    +     * @returns {Array} Returns the new duplicate free array.
    +     * @example
    +     *
    +     * _.sortedUniqBy([1.1, 1.2, 2.3, 2.4], Math.floor);
    +     * // => [1.1, 2.3]
    +     */
    +    function sortedUniqBy(array, iteratee) {
    +      return array && array.length ? baseSortedUniq(array, getIteratee(iteratee, 2)) : [];
    +    }
    +
    +    /**
    +     * Gets all but the first element of `array`.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 4.0.0
    +     * @category Array
    +     * @param {Array} array The array to query.
    +     * @returns {Array} Returns the slice of `array`.
    +     * @example
    +     *
    +     * _.tail([1, 2, 3]);
    +     * // => [2, 3]
    +     */
    +    function tail(array) {
    +      var length = array == null ? 0 : array.length;
    +      return length ? baseSlice(array, 1, length) : [];
    +    }
    +
    +    /**
    +     * Creates a slice of `array` with `n` elements taken from the beginning.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.1.0
    +     * @category Array
    +     * @param {Array} array The array to query.
    +     * @param {number} [n=1] The number of elements to take.
    +     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
    +     * @returns {Array} Returns the slice of `array`.
    +     * @example
    +     *
    +     * _.take([1, 2, 3]);
    +     * // => [1]
    +     *
    +     * _.take([1, 2, 3], 2);
    +     * // => [1, 2]
    +     *
    +     * _.take([1, 2, 3], 5);
    +     * // => [1, 2, 3]
    +     *
    +     * _.take([1, 2, 3], 0);
    +     * // => []
    +     */
    +    function take(array, n, guard) {
    +      if (!(array && array.length)) {
    +        return [];
    +      }
    +      n = guard || n === undefined ? 1 : toInteger(n);
    +      return baseSlice(array, 0, n < 0 ? 0 : n);
    +    }
    +
    +    /**
    +     * Creates a slice of `array` with `n` elements taken from the end.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 3.0.0
    +     * @category Array
    +     * @param {Array} array The array to query.
    +     * @param {number} [n=1] The number of elements to take.
    +     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
    +     * @returns {Array} Returns the slice of `array`.
    +     * @example
    +     *
    +     * _.takeRight([1, 2, 3]);
    +     * // => [3]
    +     *
    +     * _.takeRight([1, 2, 3], 2);
    +     * // => [2, 3]
    +     *
    +     * _.takeRight([1, 2, 3], 5);
    +     * // => [1, 2, 3]
    +     *
    +     * _.takeRight([1, 2, 3], 0);
    +     * // => []
    +     */
    +    function takeRight(array, n, guard) {
    +      var length = array == null ? 0 : array.length;
    +      if (!length) {
    +        return [];
    +      }
    +      n = guard || n === undefined ? 1 : toInteger(n);
    +      n = length - n;
    +      return baseSlice(array, n < 0 ? 0 : n, length);
    +    }
    +
    +    /**
    +     * Creates a slice of `array` with elements taken from the end. Elements are
    +     * taken until `predicate` returns falsey. The predicate is invoked with
    +     * three arguments: (value, index, array).
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 3.0.0
    +     * @category Array
    +     * @param {Array} array The array to query.
    +     * @param {Function} [predicate=_.identity] The function invoked per iteration.
    +     * @returns {Array} Returns the slice of `array`.
    +     * @example
    +     *
    +     * var users = [
    +     *   { 'user': 'barney',  'active': true },
    +     *   { 'user': 'fred',    'active': false },
    +     *   { 'user': 'pebbles', 'active': false }
    +     * ];
    +     *
    +     * _.takeRightWhile(users, function(o) { return !o.active; });
    +     * // => objects for ['fred', 'pebbles']
    +     *
    +     * // The `_.matches` iteratee shorthand.
    +     * _.takeRightWhile(users, { 'user': 'pebbles', 'active': false });
    +     * // => objects for ['pebbles']
    +     *
    +     * // The `_.matchesProperty` iteratee shorthand.
    +     * _.takeRightWhile(users, ['active', false]);
    +     * // => objects for ['fred', 'pebbles']
    +     *
    +     * // The `_.property` iteratee shorthand.
    +     * _.takeRightWhile(users, 'active');
    +     * // => []
    +     */
    +    function takeRightWhile(array, predicate) {
    +      return array && array.length ? baseWhile(array, getIteratee(predicate, 3), false, true) : [];
    +    }
    +
    +    /**
    +     * Creates a slice of `array` with elements taken from the beginning. Elements
    +     * are taken until `predicate` returns falsey. The predicate is invoked with
    +     * three arguments: (value, index, array).
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 3.0.0
    +     * @category Array
    +     * @param {Array} array The array to query.
    +     * @param {Function} [predicate=_.identity] The function invoked per iteration.
    +     * @returns {Array} Returns the slice of `array`.
    +     * @example
    +     *
    +     * var users = [
    +     *   { 'user': 'barney',  'active': false },
    +     *   { 'user': 'fred',    'active': false },
    +     *   { 'user': 'pebbles', 'active': true }
    +     * ];
    +     *
    +     * _.takeWhile(users, function(o) { return !o.active; });
    +     * // => objects for ['barney', 'fred']
    +     *
    +     * // The `_.matches` iteratee shorthand.
    +     * _.takeWhile(users, { 'user': 'barney', 'active': false });
    +     * // => objects for ['barney']
    +     *
    +     * // The `_.matchesProperty` iteratee shorthand.
    +     * _.takeWhile(users, ['active', false]);
    +     * // => objects for ['barney', 'fred']
    +     *
    +     * // The `_.property` iteratee shorthand.
    +     * _.takeWhile(users, 'active');
    +     * // => []
    +     */
    +    function takeWhile(array, predicate) {
    +      return array && array.length ? baseWhile(array, getIteratee(predicate, 3)) : [];
    +    }
    +
    +    /**
    +     * Creates an array of unique values, in order, from all given arrays using
    +     * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
    +     * for equality comparisons.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.1.0
    +     * @category Array
    +     * @param {...Array} [arrays] The arrays to inspect.
    +     * @returns {Array} Returns the new array of combined values.
    +     * @example
    +     *
    +     * _.union([2], [1, 2]);
    +     * // => [2, 1]
    +     */
    +    var union = baseRest(function (arrays) {
    +      return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true));
    +    });
    +
    +    /**
    +     * This method is like `_.union` except that it accepts `iteratee` which is
    +     * invoked for each element of each `arrays` to generate the criterion by
    +     * which uniqueness is computed. Result values are chosen from the first
    +     * array in which the value occurs. The iteratee is invoked with one argument:
    +     * (value).
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 4.0.0
    +     * @category Array
    +     * @param {...Array} [arrays] The arrays to inspect.
    +     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
    +     * @returns {Array} Returns the new array of combined values.
    +     * @example
    +     *
    +     * _.unionBy([2.1], [1.2, 2.3], Math.floor);
    +     * // => [2.1, 1.2]
    +     *
    +     * // The `_.property` iteratee shorthand.
    +     * _.unionBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');
    +     * // => [{ 'x': 1 }, { 'x': 2 }]
    +     */
    +    var unionBy = baseRest(function (arrays) {
    +      var iteratee = last(arrays);
    +      if (isArrayLikeObject(iteratee)) {
    +        iteratee = undefined;
    +      }
    +      return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true), getIteratee(iteratee, 2));
    +    });
    +
    +    /**
    +     * This method is like `_.union` except that it accepts `comparator` which
    +     * is invoked to compare elements of `arrays`. Result values are chosen from
    +     * the first array in which the value occurs. The comparator is invoked
    +     * with two arguments: (arrVal, othVal).
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 4.0.0
    +     * @category Array
    +     * @param {...Array} [arrays] The arrays to inspect.
    +     * @param {Function} [comparator] The comparator invoked per element.
    +     * @returns {Array} Returns the new array of combined values.
    +     * @example
    +     *
    +     * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
    +     * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];
    +     *
    +     * _.unionWith(objects, others, _.isEqual);
    +     * // => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }]
    +     */
    +    var unionWith = baseRest(function (arrays) {
    +      var comparator = last(arrays);
    +      comparator = typeof comparator == 'function' ? comparator : undefined;
    +      return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true), undefined, comparator);
    +    });
    +
    +    /**
    +     * Creates a duplicate-free version of an array, using
    +     * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
    +     * for equality comparisons, in which only the first occurrence of each element
    +     * is kept. The order of result values is determined by the order they occur
    +     * in the array.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.1.0
    +     * @category Array
    +     * @param {Array} array The array to inspect.
    +     * @returns {Array} Returns the new duplicate free array.
    +     * @example
    +     *
    +     * _.uniq([2, 1, 2]);
    +     * // => [2, 1]
    +     */
    +    function uniq(array) {
    +      return array && array.length ? baseUniq(array) : [];
    +    }
    +
    +    /**
    +     * This method is like `_.uniq` except that it accepts `iteratee` which is
    +     * invoked for each element in `array` to generate the criterion by which
    +     * uniqueness is computed. The order of result values is determined by the
    +     * order they occur in the array. The iteratee is invoked with one argument:
    +     * (value).
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 4.0.0
    +     * @category Array
    +     * @param {Array} array The array to inspect.
    +     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
    +     * @returns {Array} Returns the new duplicate free array.
    +     * @example
    +     *
    +     * _.uniqBy([2.1, 1.2, 2.3], Math.floor);
    +     * // => [2.1, 1.2]
    +     *
    +     * // The `_.property` iteratee shorthand.
    +     * _.uniqBy([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x');
    +     * // => [{ 'x': 1 }, { 'x': 2 }]
    +     */
    +    function uniqBy(array, iteratee) {
    +      return array && array.length ? baseUniq(array, getIteratee(iteratee, 2)) : [];
    +    }
    +
    +    /**
    +     * This method is like `_.uniq` except that it accepts `comparator` which
    +     * is invoked to compare elements of `array`. The order of result values is
    +     * determined by the order they occur in the array.The comparator is invoked
    +     * with two arguments: (arrVal, othVal).
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 4.0.0
    +     * @category Array
    +     * @param {Array} array The array to inspect.
    +     * @param {Function} [comparator] The comparator invoked per element.
    +     * @returns {Array} Returns the new duplicate free array.
    +     * @example
    +     *
    +     * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 2 }];
    +     *
    +     * _.uniqWith(objects, _.isEqual);
    +     * // => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]
    +     */
    +    function uniqWith(array, comparator) {
    +      comparator = typeof comparator == 'function' ? comparator : undefined;
    +      return array && array.length ? baseUniq(array, undefined, comparator) : [];
    +    }
    +
    +    /**
    +     * This method is like `_.zip` except that it accepts an array of grouped
    +     * elements and creates an array regrouping the elements to their pre-zip
    +     * configuration.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 1.2.0
    +     * @category Array
    +     * @param {Array} array The array of grouped elements to process.
    +     * @returns {Array} Returns the new array of regrouped elements.
    +     * @example
    +     *
    +     * var zipped = _.zip(['a', 'b'], [1, 2], [true, false]);
    +     * // => [['a', 1, true], ['b', 2, false]]
    +     *
    +     * _.unzip(zipped);
    +     * // => [['a', 'b'], [1, 2], [true, false]]
    +     */
    +    function unzip(array) {
    +      if (!(array && array.length)) {
    +        return [];
    +      }
    +      var length = 0;
    +      array = arrayFilter(array, function (group) {
    +        if (isArrayLikeObject(group)) {
    +          length = nativeMax(group.length, length);
    +          return true;
    +        }
    +      });
    +      return baseTimes(length, function (index) {
    +        return arrayMap(array, baseProperty(index));
    +      });
    +    }
    +
    +    /**
    +     * This method is like `_.unzip` except that it accepts `iteratee` to specify
    +     * how regrouped values should be combined. The iteratee is invoked with the
    +     * elements of each group: (...group).
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 3.8.0
    +     * @category Array
    +     * @param {Array} array The array of grouped elements to process.
    +     * @param {Function} [iteratee=_.identity] The function to combine
    +     *  regrouped values.
    +     * @returns {Array} Returns the new array of regrouped elements.
    +     * @example
    +     *
    +     * var zipped = _.zip([1, 2], [10, 20], [100, 200]);
    +     * // => [[1, 10, 100], [2, 20, 200]]
    +     *
    +     * _.unzipWith(zipped, _.add);
    +     * // => [3, 30, 300]
    +     */
    +    function unzipWith(array, iteratee) {
    +      if (!(array && array.length)) {
    +        return [];
    +      }
    +      var result = unzip(array);
    +      if (iteratee == null) {
    +        return result;
    +      }
    +      return arrayMap(result, function (group) {
    +        return apply(iteratee, undefined, group);
    +      });
    +    }
    +
    +    /**
    +     * Creates an array excluding all given values using
    +     * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
    +     * for equality comparisons.
    +     *
    +     * **Note:** Unlike `_.pull`, this method returns a new array.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.1.0
    +     * @category Array
    +     * @param {Array} array The array to inspect.
    +     * @param {...*} [values] The values to exclude.
    +     * @returns {Array} Returns the new array of filtered values.
    +     * @see _.difference, _.xor
    +     * @example
    +     *
    +     * _.without([2, 1, 2, 3], 1, 2);
    +     * // => [3]
    +     */
    +    var without = baseRest(function (array, values) {
    +      return isArrayLikeObject(array) ? baseDifference(array, values) : [];
    +    });
    +
    +    /**
    +     * Creates an array of unique values that is the
    +     * [symmetric difference](https://en.wikipedia.org/wiki/Symmetric_difference)
    +     * of the given arrays. The order of result values is determined by the order
    +     * they occur in the arrays.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 2.4.0
    +     * @category Array
    +     * @param {...Array} [arrays] The arrays to inspect.
    +     * @returns {Array} Returns the new array of filtered values.
    +     * @see _.difference, _.without
    +     * @example
    +     *
    +     * _.xor([2, 1], [2, 3]);
    +     * // => [1, 3]
    +     */
    +    var xor = baseRest(function (arrays) {
    +      return baseXor(arrayFilter(arrays, isArrayLikeObject));
    +    });
    +
    +    /**
    +     * This method is like `_.xor` except that it accepts `iteratee` which is
    +     * invoked for each element of each `arrays` to generate the criterion by
    +     * which by which they're compared. The order of result values is determined
    +     * by the order they occur in the arrays. The iteratee is invoked with one
    +     * argument: (value).
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 4.0.0
    +     * @category Array
    +     * @param {...Array} [arrays] The arrays to inspect.
    +     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
    +     * @returns {Array} Returns the new array of filtered values.
    +     * @example
    +     *
    +     * _.xorBy([2.1, 1.2], [2.3, 3.4], Math.floor);
    +     * // => [1.2, 3.4]
    +     *
    +     * // The `_.property` iteratee shorthand.
    +     * _.xorBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');
    +     * // => [{ 'x': 2 }]
    +     */
    +    var xorBy = baseRest(function (arrays) {
    +      var iteratee = last(arrays);
    +      if (isArrayLikeObject(iteratee)) {
    +        iteratee = undefined;
    +      }
    +      return baseXor(arrayFilter(arrays, isArrayLikeObject), getIteratee(iteratee, 2));
    +    });
    +
    +    /**
    +     * This method is like `_.xor` except that it accepts `comparator` which is
    +     * invoked to compare elements of `arrays`. The order of result values is
    +     * determined by the order they occur in the arrays. The comparator is invoked
    +     * with two arguments: (arrVal, othVal).
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 4.0.0
    +     * @category Array
    +     * @param {...Array} [arrays] The arrays to inspect.
    +     * @param {Function} [comparator] The comparator invoked per element.
    +     * @returns {Array} Returns the new array of filtered values.
    +     * @example
    +     *
    +     * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
    +     * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];
    +     *
    +     * _.xorWith(objects, others, _.isEqual);
    +     * // => [{ 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }]
    +     */
    +    var xorWith = baseRest(function (arrays) {
    +      var comparator = last(arrays);
    +      comparator = typeof comparator == 'function' ? comparator : undefined;
    +      return baseXor(arrayFilter(arrays, isArrayLikeObject), undefined, comparator);
    +    });
    +
    +    /**
    +     * Creates an array of grouped elements, the first of which contains the
    +     * first elements of the given arrays, the second of which contains the
    +     * second elements of the given arrays, and so on.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.1.0
    +     * @category Array
    +     * @param {...Array} [arrays] The arrays to process.
    +     * @returns {Array} Returns the new array of grouped elements.
    +     * @example
    +     *
    +     * _.zip(['a', 'b'], [1, 2], [true, false]);
    +     * // => [['a', 1, true], ['b', 2, false]]
    +     */
    +    var zip = baseRest(unzip);
    +
    +    /**
    +     * This method is like `_.fromPairs` except that it accepts two arrays,
    +     * one of property identifiers and one of corresponding values.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.4.0
    +     * @category Array
    +     * @param {Array} [props=[]] The property identifiers.
    +     * @param {Array} [values=[]] The property values.
    +     * @returns {Object} Returns the new object.
    +     * @example
    +     *
    +     * _.zipObject(['a', 'b'], [1, 2]);
    +     * // => { 'a': 1, 'b': 2 }
    +     */
    +    function zipObject(props, values) {
    +      return baseZipObject(props || [], values || [], assignValue);
    +    }
    +
    +    /**
    +     * This method is like `_.zipObject` except that it supports property paths.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 4.1.0
    +     * @category Array
    +     * @param {Array} [props=[]] The property identifiers.
    +     * @param {Array} [values=[]] The property values.
    +     * @returns {Object} Returns the new object.
    +     * @example
    +     *
    +     * _.zipObjectDeep(['a.b[0].c', 'a.b[1].d'], [1, 2]);
    +     * // => { 'a': { 'b': [{ 'c': 1 }, { 'd': 2 }] } }
    +     */
    +    function zipObjectDeep(props, values) {
    +      return baseZipObject(props || [], values || [], baseSet);
    +    }
    +
    +    /**
    +     * This method is like `_.zip` except that it accepts `iteratee` to specify
    +     * how grouped values should be combined. The iteratee is invoked with the
    +     * elements of each group: (...group).
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 3.8.0
    +     * @category Array
    +     * @param {...Array} [arrays] The arrays to process.
    +     * @param {Function} [iteratee=_.identity] The function to combine
    +     *  grouped values.
    +     * @returns {Array} Returns the new array of grouped elements.
    +     * @example
    +     *
    +     * _.zipWith([1, 2], [10, 20], [100, 200], function(a, b, c) {
    +     *   return a + b + c;
    +     * });
    +     * // => [111, 222]
    +     */
    +    var zipWith = baseRest(function (arrays) {
    +      var length = arrays.length,
    +        iteratee = length > 1 ? arrays[length - 1] : undefined;
    +      iteratee = typeof iteratee == 'function' ? (arrays.pop(), iteratee) : undefined;
    +      return unzipWith(arrays, iteratee);
    +    });
    +
    +    /*------------------------------------------------------------------------*/
    +
    +    /**
    +     * Creates a `lodash` wrapper instance that wraps `value` with explicit method
    +     * chain sequences enabled. The result of such sequences must be unwrapped
    +     * with `_#value`.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 1.3.0
    +     * @category Seq
    +     * @param {*} value The value to wrap.
    +     * @returns {Object} Returns the new `lodash` wrapper instance.
    +     * @example
    +     *
    +     * var users = [
    +     *   { 'user': 'barney',  'age': 36 },
    +     *   { 'user': 'fred',    'age': 40 },
    +     *   { 'user': 'pebbles', 'age': 1 }
    +     * ];
    +     *
    +     * var youngest = _
    +     *   .chain(users)
    +     *   .sortBy('age')
    +     *   .map(function(o) {
    +     *     return o.user + ' is ' + o.age;
    +     *   })
    +     *   .head()
    +     *   .value();
    +     * // => 'pebbles is 1'
    +     */
    +    function chain(value) {
    +      var result = lodash(value);
    +      result.__chain__ = true;
    +      return result;
    +    }
    +
    +    /**
    +     * This method invokes `interceptor` and returns `value`. The interceptor
    +     * is invoked with one argument; (value). The purpose of this method is to
    +     * "tap into" a method chain sequence in order to modify intermediate results.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.1.0
    +     * @category Seq
    +     * @param {*} value The value to provide to `interceptor`.
    +     * @param {Function} interceptor The function to invoke.
    +     * @returns {*} Returns `value`.
    +     * @example
    +     *
    +     * _([1, 2, 3])
    +     *  .tap(function(array) {
    +     *    // Mutate input array.
    +     *    array.pop();
    +     *  })
    +     *  .reverse()
    +     *  .value();
    +     * // => [2, 1]
    +     */
    +    function tap(value, interceptor) {
    +      interceptor(value);
    +      return value;
    +    }
    +
    +    /**
    +     * This method is like `_.tap` except that it returns the result of `interceptor`.
    +     * The purpose of this method is to "pass thru" values replacing intermediate
    +     * results in a method chain sequence.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 3.0.0
    +     * @category Seq
    +     * @param {*} value The value to provide to `interceptor`.
    +     * @param {Function} interceptor The function to invoke.
    +     * @returns {*} Returns the result of `interceptor`.
    +     * @example
    +     *
    +     * _('  abc  ')
    +     *  .chain()
    +     *  .trim()
    +     *  .thru(function(value) {
    +     *    return [value];
    +     *  })
    +     *  .value();
    +     * // => ['abc']
    +     */
    +    function thru(value, interceptor) {
    +      return interceptor(value);
    +    }
    +
    +    /**
    +     * This method is the wrapper version of `_.at`.
    +     *
    +     * @name at
    +     * @memberOf _
    +     * @since 1.0.0
    +     * @category Seq
    +     * @param {...(string|string[])} [paths] The property paths to pick.
    +     * @returns {Object} Returns the new `lodash` wrapper instance.
    +     * @example
    +     *
    +     * var object = { 'a': [{ 'b': { 'c': 3 } }, 4] };
    +     *
    +     * _(object).at(['a[0].b.c', 'a[1]']).value();
    +     * // => [3, 4]
    +     */
    +    var wrapperAt = flatRest(function (paths) {
    +      var length = paths.length,
    +        start = length ? paths[0] : 0,
    +        value = this.__wrapped__,
    +        interceptor = function interceptor(object) {
    +          return baseAt(object, paths);
    +        };
    +      if (length > 1 || this.__actions__.length || !(value instanceof LazyWrapper) || !isIndex(start)) {
    +        return this.thru(interceptor);
    +      }
    +      value = value.slice(start, +start + (length ? 1 : 0));
    +      value.__actions__.push({
    +        'func': thru,
    +        'args': [interceptor],
    +        'thisArg': undefined
    +      });
    +      return new LodashWrapper(value, this.__chain__).thru(function (array) {
    +        if (length && !array.length) {
    +          array.push(undefined);
    +        }
    +        return array;
    +      });
    +    });
    +
    +    /**
    +     * Creates a `lodash` wrapper instance with explicit method chain sequences enabled.
    +     *
    +     * @name chain
    +     * @memberOf _
    +     * @since 0.1.0
    +     * @category Seq
    +     * @returns {Object} Returns the new `lodash` wrapper instance.
    +     * @example
    +     *
    +     * var users = [
    +     *   { 'user': 'barney', 'age': 36 },
    +     *   { 'user': 'fred',   'age': 40 }
    +     * ];
    +     *
    +     * // A sequence without explicit chaining.
    +     * _(users).head();
    +     * // => { 'user': 'barney', 'age': 36 }
    +     *
    +     * // A sequence with explicit chaining.
    +     * _(users)
    +     *   .chain()
    +     *   .head()
    +     *   .pick('user')
    +     *   .value();
    +     * // => { 'user': 'barney' }
    +     */
    +    function wrapperChain() {
    +      return chain(this);
    +    }
    +
    +    /**
    +     * Executes the chain sequence and returns the wrapped result.
    +     *
    +     * @name commit
    +     * @memberOf _
    +     * @since 3.2.0
    +     * @category Seq
    +     * @returns {Object} Returns the new `lodash` wrapper instance.
    +     * @example
    +     *
    +     * var array = [1, 2];
    +     * var wrapped = _(array).push(3);
    +     *
    +     * console.log(array);
    +     * // => [1, 2]
    +     *
    +     * wrapped = wrapped.commit();
    +     * console.log(array);
    +     * // => [1, 2, 3]
    +     *
    +     * wrapped.last();
    +     * // => 3
    +     *
    +     * console.log(array);
    +     * // => [1, 2, 3]
    +     */
    +    function wrapperCommit() {
    +      return new LodashWrapper(this.value(), this.__chain__);
    +    }
    +
    +    /**
    +     * Gets the next value on a wrapped object following the
    +     * [iterator protocol](https://mdn.io/iteration_protocols#iterator).
    +     *
    +     * @name next
    +     * @memberOf _
    +     * @since 4.0.0
    +     * @category Seq
    +     * @returns {Object} Returns the next iterator value.
    +     * @example
    +     *
    +     * var wrapped = _([1, 2]);
    +     *
    +     * wrapped.next();
    +     * // => { 'done': false, 'value': 1 }
    +     *
    +     * wrapped.next();
    +     * // => { 'done': false, 'value': 2 }
    +     *
    +     * wrapped.next();
    +     * // => { 'done': true, 'value': undefined }
    +     */
    +    function wrapperNext() {
    +      if (this.__values__ === undefined) {
    +        this.__values__ = toArray(this.value());
    +      }
    +      var done = this.__index__ >= this.__values__.length,
    +        value = done ? undefined : this.__values__[this.__index__++];
    +      return {
    +        'done': done,
    +        'value': value
    +      };
    +    }
    +
    +    /**
    +     * Enables the wrapper to be iterable.
    +     *
    +     * @name Symbol.iterator
    +     * @memberOf _
    +     * @since 4.0.0
    +     * @category Seq
    +     * @returns {Object} Returns the wrapper object.
    +     * @example
    +     *
    +     * var wrapped = _([1, 2]);
    +     *
    +     * wrapped[Symbol.iterator]() === wrapped;
    +     * // => true
    +     *
    +     * Array.from(wrapped);
    +     * // => [1, 2]
    +     */
    +    function wrapperToIterator() {
    +      return this;
    +    }
    +
    +    /**
    +     * Creates a clone of the chain sequence planting `value` as the wrapped value.
    +     *
    +     * @name plant
    +     * @memberOf _
    +     * @since 3.2.0
    +     * @category Seq
    +     * @param {*} value The value to plant.
    +     * @returns {Object} Returns the new `lodash` wrapper instance.
    +     * @example
    +     *
    +     * function square(n) {
    +     *   return n * n;
    +     * }
    +     *
    +     * var wrapped = _([1, 2]).map(square);
    +     * var other = wrapped.plant([3, 4]);
    +     *
    +     * other.value();
    +     * // => [9, 16]
    +     *
    +     * wrapped.value();
    +     * // => [1, 4]
    +     */
    +    function wrapperPlant(value) {
    +      var result,
    +        parent = this;
    +      while (parent instanceof baseLodash) {
    +        var clone = wrapperClone(parent);
    +        clone.__index__ = 0;
    +        clone.__values__ = undefined;
    +        if (result) {
    +          previous.__wrapped__ = clone;
    +        } else {
    +          result = clone;
    +        }
    +        var previous = clone;
    +        parent = parent.__wrapped__;
    +      }
    +      previous.__wrapped__ = value;
    +      return result;
    +    }
    +
    +    /**
    +     * This method is the wrapper version of `_.reverse`.
    +     *
    +     * **Note:** This method mutates the wrapped array.
    +     *
    +     * @name reverse
    +     * @memberOf _
    +     * @since 0.1.0
    +     * @category Seq
    +     * @returns {Object} Returns the new `lodash` wrapper instance.
    +     * @example
    +     *
    +     * var array = [1, 2, 3];
    +     *
    +     * _(array).reverse().value()
    +     * // => [3, 2, 1]
    +     *
    +     * console.log(array);
    +     * // => [3, 2, 1]
    +     */
    +    function wrapperReverse() {
    +      var value = this.__wrapped__;
    +      if (value instanceof LazyWrapper) {
    +        var wrapped = value;
    +        if (this.__actions__.length) {
    +          wrapped = new LazyWrapper(this);
    +        }
    +        wrapped = wrapped.reverse();
    +        wrapped.__actions__.push({
    +          'func': thru,
    +          'args': [reverse],
    +          'thisArg': undefined
    +        });
    +        return new LodashWrapper(wrapped, this.__chain__);
    +      }
    +      return this.thru(reverse);
    +    }
    +
    +    /**
    +     * Executes the chain sequence to resolve the unwrapped value.
    +     *
    +     * @name value
    +     * @memberOf _
    +     * @since 0.1.0
    +     * @alias toJSON, valueOf
    +     * @category Seq
    +     * @returns {*} Returns the resolved unwrapped value.
    +     * @example
    +     *
    +     * _([1, 2, 3]).value();
    +     * // => [1, 2, 3]
    +     */
    +    function wrapperValue() {
    +      return baseWrapperValue(this.__wrapped__, this.__actions__);
    +    }
    +
    +    /*------------------------------------------------------------------------*/
    +
    +    /**
    +     * Creates an object composed of keys generated from the results of running
    +     * each element of `collection` thru `iteratee`. The corresponding value of
    +     * each key is the number of times the key was returned by `iteratee`. The
    +     * iteratee is invoked with one argument: (value).
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.5.0
    +     * @category Collection
    +     * @param {Array|Object} collection The collection to iterate over.
    +     * @param {Function} [iteratee=_.identity] The iteratee to transform keys.
    +     * @returns {Object} Returns the composed aggregate object.
    +     * @example
    +     *
    +     * _.countBy([6.1, 4.2, 6.3], Math.floor);
    +     * // => { '4': 1, '6': 2 }
    +     *
    +     * // The `_.property` iteratee shorthand.
    +     * _.countBy(['one', 'two', 'three'], 'length');
    +     * // => { '3': 2, '5': 1 }
    +     */
    +    var countBy = createAggregator(function (result, value, key) {
    +      if (hasOwnProperty.call(result, key)) {
    +        ++result[key];
    +      } else {
    +        baseAssignValue(result, key, 1);
    +      }
    +    });
    +
    +    /**
    +     * Checks if `predicate` returns truthy for **all** elements of `collection`.
    +     * Iteration is stopped once `predicate` returns falsey. The predicate is
    +     * invoked with three arguments: (value, index|key, collection).
    +     *
    +     * **Note:** This method returns `true` for
    +     * [empty collections](https://en.wikipedia.org/wiki/Empty_set) because
    +     * [everything is true](https://en.wikipedia.org/wiki/Vacuous_truth) of
    +     * elements of empty collections.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.1.0
    +     * @category Collection
    +     * @param {Array|Object} collection The collection to iterate over.
    +     * @param {Function} [predicate=_.identity] The function invoked per iteration.
    +     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
    +     * @returns {boolean} Returns `true` if all elements pass the predicate check,
    +     *  else `false`.
    +     * @example
    +     *
    +     * _.every([true, 1, null, 'yes'], Boolean);
    +     * // => false
    +     *
    +     * var users = [
    +     *   { 'user': 'barney', 'age': 36, 'active': false },
    +     *   { 'user': 'fred',   'age': 40, 'active': false }
    +     * ];
    +     *
    +     * // The `_.matches` iteratee shorthand.
    +     * _.every(users, { 'user': 'barney', 'active': false });
    +     * // => false
    +     *
    +     * // The `_.matchesProperty` iteratee shorthand.
    +     * _.every(users, ['active', false]);
    +     * // => true
    +     *
    +     * // The `_.property` iteratee shorthand.
    +     * _.every(users, 'active');
    +     * // => false
    +     */
    +    function every(collection, predicate, guard) {
    +      var func = isArray(collection) ? arrayEvery : baseEvery;
    +      if (guard && isIterateeCall(collection, predicate, guard)) {
    +        predicate = undefined;
    +      }
    +      return func(collection, getIteratee(predicate, 3));
    +    }
    +
    +    /**
    +     * Iterates over elements of `collection`, returning an array of all elements
    +     * `predicate` returns truthy for. The predicate is invoked with three
    +     * arguments: (value, index|key, collection).
    +     *
    +     * **Note:** Unlike `_.remove`, this method returns a new array.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.1.0
    +     * @category Collection
    +     * @param {Array|Object} collection The collection to iterate over.
    +     * @param {Function} [predicate=_.identity] The function invoked per iteration.
    +     * @returns {Array} Returns the new filtered array.
    +     * @see _.reject
    +     * @example
    +     *
    +     * var users = [
    +     *   { 'user': 'barney', 'age': 36, 'active': true },
    +     *   { 'user': 'fred',   'age': 40, 'active': false }
    +     * ];
    +     *
    +     * _.filter(users, function(o) { return !o.active; });
    +     * // => objects for ['fred']
    +     *
    +     * // The `_.matches` iteratee shorthand.
    +     * _.filter(users, { 'age': 36, 'active': true });
    +     * // => objects for ['barney']
    +     *
    +     * // The `_.matchesProperty` iteratee shorthand.
    +     * _.filter(users, ['active', false]);
    +     * // => objects for ['fred']
    +     *
    +     * // The `_.property` iteratee shorthand.
    +     * _.filter(users, 'active');
    +     * // => objects for ['barney']
    +     *
    +     * // Combining several predicates using `_.overEvery` or `_.overSome`.
    +     * _.filter(users, _.overSome([{ 'age': 36 }, ['age', 40]]));
    +     * // => objects for ['fred', 'barney']
    +     */
    +    function filter(collection, predicate) {
    +      var func = isArray(collection) ? arrayFilter : baseFilter;
    +      return func(collection, getIteratee(predicate, 3));
    +    }
    +
    +    /**
    +     * Iterates over elements of `collection`, returning the first element
    +     * `predicate` returns truthy for. The predicate is invoked with three
    +     * arguments: (value, index|key, collection).
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.1.0
    +     * @category Collection
    +     * @param {Array|Object} collection The collection to inspect.
    +     * @param {Function} [predicate=_.identity] The function invoked per iteration.
    +     * @param {number} [fromIndex=0] The index to search from.
    +     * @returns {*} Returns the matched element, else `undefined`.
    +     * @example
    +     *
    +     * var users = [
    +     *   { 'user': 'barney',  'age': 36, 'active': true },
    +     *   { 'user': 'fred',    'age': 40, 'active': false },
    +     *   { 'user': 'pebbles', 'age': 1,  'active': true }
    +     * ];
    +     *
    +     * _.find(users, function(o) { return o.age < 40; });
    +     * // => object for 'barney'
    +     *
    +     * // The `_.matches` iteratee shorthand.
    +     * _.find(users, { 'age': 1, 'active': true });
    +     * // => object for 'pebbles'
    +     *
    +     * // The `_.matchesProperty` iteratee shorthand.
    +     * _.find(users, ['active', false]);
    +     * // => object for 'fred'
    +     *
    +     * // The `_.property` iteratee shorthand.
    +     * _.find(users, 'active');
    +     * // => object for 'barney'
    +     */
    +    var find = createFind(findIndex);
    +
    +    /**
    +     * This method is like `_.find` except that it iterates over elements of
    +     * `collection` from right to left.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 2.0.0
    +     * @category Collection
    +     * @param {Array|Object} collection The collection to inspect.
    +     * @param {Function} [predicate=_.identity] The function invoked per iteration.
    +     * @param {number} [fromIndex=collection.length-1] The index to search from.
    +     * @returns {*} Returns the matched element, else `undefined`.
    +     * @example
    +     *
    +     * _.findLast([1, 2, 3, 4], function(n) {
    +     *   return n % 2 == 1;
    +     * });
    +     * // => 3
    +     */
    +    var findLast = createFind(findLastIndex);
    +
    +    /**
    +     * Creates a flattened array of values by running each element in `collection`
    +     * thru `iteratee` and flattening the mapped results. The iteratee is invoked
    +     * with three arguments: (value, index|key, collection).
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 4.0.0
    +     * @category Collection
    +     * @param {Array|Object} collection The collection to iterate over.
    +     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
    +     * @returns {Array} Returns the new flattened array.
    +     * @example
    +     *
    +     * function duplicate(n) {
    +     *   return [n, n];
    +     * }
    +     *
    +     * _.flatMap([1, 2], duplicate);
    +     * // => [1, 1, 2, 2]
    +     */
    +    function flatMap(collection, iteratee) {
    +      return baseFlatten(map(collection, iteratee), 1);
    +    }
    +
    +    /**
    +     * This method is like `_.flatMap` except that it recursively flattens the
    +     * mapped results.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 4.7.0
    +     * @category Collection
    +     * @param {Array|Object} collection The collection to iterate over.
    +     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
    +     * @returns {Array} Returns the new flattened array.
    +     * @example
    +     *
    +     * function duplicate(n) {
    +     *   return [[[n, n]]];
    +     * }
    +     *
    +     * _.flatMapDeep([1, 2], duplicate);
    +     * // => [1, 1, 2, 2]
    +     */
    +    function flatMapDeep(collection, iteratee) {
    +      return baseFlatten(map(collection, iteratee), INFINITY);
    +    }
    +
    +    /**
    +     * This method is like `_.flatMap` except that it recursively flattens the
    +     * mapped results up to `depth` times.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 4.7.0
    +     * @category Collection
    +     * @param {Array|Object} collection The collection to iterate over.
    +     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
    +     * @param {number} [depth=1] The maximum recursion depth.
    +     * @returns {Array} Returns the new flattened array.
    +     * @example
    +     *
    +     * function duplicate(n) {
    +     *   return [[[n, n]]];
    +     * }
    +     *
    +     * _.flatMapDepth([1, 2], duplicate, 2);
    +     * // => [[1, 1], [2, 2]]
    +     */
    +    function flatMapDepth(collection, iteratee, depth) {
    +      depth = depth === undefined ? 1 : toInteger(depth);
    +      return baseFlatten(map(collection, iteratee), depth);
    +    }
    +
    +    /**
    +     * Iterates over elements of `collection` and invokes `iteratee` for each element.
    +     * The iteratee is invoked with three arguments: (value, index|key, collection).
    +     * Iteratee functions may exit iteration early by explicitly returning `false`.
    +     *
    +     * **Note:** As with other "Collections" methods, objects with a "length"
    +     * property are iterated like arrays. To avoid this behavior use `_.forIn`
    +     * or `_.forOwn` for object iteration.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.1.0
    +     * @alias each
    +     * @category Collection
    +     * @param {Array|Object} collection The collection to iterate over.
    +     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
    +     * @returns {Array|Object} Returns `collection`.
    +     * @see _.forEachRight
    +     * @example
    +     *
    +     * _.forEach([1, 2], function(value) {
    +     *   console.log(value);
    +     * });
    +     * // => Logs `1` then `2`.
    +     *
    +     * _.forEach({ 'a': 1, 'b': 2 }, function(value, key) {
    +     *   console.log(key);
    +     * });
    +     * // => Logs 'a' then 'b' (iteration order is not guaranteed).
    +     */
    +    function forEach(collection, iteratee) {
    +      var func = isArray(collection) ? arrayEach : baseEach;
    +      return func(collection, getIteratee(iteratee, 3));
    +    }
    +
    +    /**
    +     * This method is like `_.forEach` except that it iterates over elements of
    +     * `collection` from right to left.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 2.0.0
    +     * @alias eachRight
    +     * @category Collection
    +     * @param {Array|Object} collection The collection to iterate over.
    +     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
    +     * @returns {Array|Object} Returns `collection`.
    +     * @see _.forEach
    +     * @example
    +     *
    +     * _.forEachRight([1, 2], function(value) {
    +     *   console.log(value);
    +     * });
    +     * // => Logs `2` then `1`.
    +     */
    +    function forEachRight(collection, iteratee) {
    +      var func = isArray(collection) ? arrayEachRight : baseEachRight;
    +      return func(collection, getIteratee(iteratee, 3));
    +    }
    +
    +    /**
    +     * Creates an object composed of keys generated from the results of running
    +     * each element of `collection` thru `iteratee`. The order of grouped values
    +     * is determined by the order they occur in `collection`. The corresponding
    +     * value of each key is an array of elements responsible for generating the
    +     * key. The iteratee is invoked with one argument: (value).
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.1.0
    +     * @category Collection
    +     * @param {Array|Object} collection The collection to iterate over.
    +     * @param {Function} [iteratee=_.identity] The iteratee to transform keys.
    +     * @returns {Object} Returns the composed aggregate object.
    +     * @example
    +     *
    +     * _.groupBy([6.1, 4.2, 6.3], Math.floor);
    +     * // => { '4': [4.2], '6': [6.1, 6.3] }
    +     *
    +     * // The `_.property` iteratee shorthand.
    +     * _.groupBy(['one', 'two', 'three'], 'length');
    +     * // => { '3': ['one', 'two'], '5': ['three'] }
    +     */
    +    var groupBy = createAggregator(function (result, value, key) {
    +      if (hasOwnProperty.call(result, key)) {
    +        result[key].push(value);
    +      } else {
    +        baseAssignValue(result, key, [value]);
    +      }
    +    });
    +
    +    /**
    +     * Checks if `value` is in `collection`. If `collection` is a string, it's
    +     * checked for a substring of `value`, otherwise
    +     * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
    +     * is used for equality comparisons. If `fromIndex` is negative, it's used as
    +     * the offset from the end of `collection`.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.1.0
    +     * @category Collection
    +     * @param {Array|Object|string} collection The collection to inspect.
    +     * @param {*} value The value to search for.
    +     * @param {number} [fromIndex=0] The index to search from.
    +     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.reduce`.
    +     * @returns {boolean} Returns `true` if `value` is found, else `false`.
    +     * @example
    +     *
    +     * _.includes([1, 2, 3], 1);
    +     * // => true
    +     *
    +     * _.includes([1, 2, 3], 1, 2);
    +     * // => false
    +     *
    +     * _.includes({ 'a': 1, 'b': 2 }, 1);
    +     * // => true
    +     *
    +     * _.includes('abcd', 'bc');
    +     * // => true
    +     */
    +    function includes(collection, value, fromIndex, guard) {
    +      collection = isArrayLike(collection) ? collection : values(collection);
    +      fromIndex = fromIndex && !guard ? toInteger(fromIndex) : 0;
    +      var length = collection.length;
    +      if (fromIndex < 0) {
    +        fromIndex = nativeMax(length + fromIndex, 0);
    +      }
    +      return isString(collection) ? fromIndex <= length && collection.indexOf(value, fromIndex) > -1 : !!length && baseIndexOf(collection, value, fromIndex) > -1;
    +    }
    +
    +    /**
    +     * Invokes the method at `path` of each element in `collection`, returning
    +     * an array of the results of each invoked method. Any additional arguments
    +     * are provided to each invoked method. If `path` is a function, it's invoked
    +     * for, and `this` bound to, each element in `collection`.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 4.0.0
    +     * @category Collection
    +     * @param {Array|Object} collection The collection to iterate over.
    +     * @param {Array|Function|string} path The path of the method to invoke or
    +     *  the function invoked per iteration.
    +     * @param {...*} [args] The arguments to invoke each method with.
    +     * @returns {Array} Returns the array of results.
    +     * @example
    +     *
    +     * _.invokeMap([[5, 1, 7], [3, 2, 1]], 'sort');
    +     * // => [[1, 5, 7], [1, 2, 3]]
    +     *
    +     * _.invokeMap([123, 456], String.prototype.split, '');
    +     * // => [['1', '2', '3'], ['4', '5', '6']]
    +     */
    +    var invokeMap = baseRest(function (collection, path, args) {
    +      var index = -1,
    +        isFunc = typeof path == 'function',
    +        result = isArrayLike(collection) ? Array(collection.length) : [];
    +      baseEach(collection, function (value) {
    +        result[++index] = isFunc ? apply(path, value, args) : baseInvoke(value, path, args);
    +      });
    +      return result;
    +    });
    +
    +    /**
    +     * Creates an object composed of keys generated from the results of running
    +     * each element of `collection` thru `iteratee`. The corresponding value of
    +     * each key is the last element responsible for generating the key. The
    +     * iteratee is invoked with one argument: (value).
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 4.0.0
    +     * @category Collection
    +     * @param {Array|Object} collection The collection to iterate over.
    +     * @param {Function} [iteratee=_.identity] The iteratee to transform keys.
    +     * @returns {Object} Returns the composed aggregate object.
    +     * @example
    +     *
    +     * var array = [
    +     *   { 'dir': 'left', 'code': 97 },
    +     *   { 'dir': 'right', 'code': 100 }
    +     * ];
    +     *
    +     * _.keyBy(array, function(o) {
    +     *   return String.fromCharCode(o.code);
    +     * });
    +     * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } }
    +     *
    +     * _.keyBy(array, 'dir');
    +     * // => { 'left': { 'dir': 'left', 'code': 97 }, 'right': { 'dir': 'right', 'code': 100 } }
    +     */
    +    var keyBy = createAggregator(function (result, value, key) {
    +      baseAssignValue(result, key, value);
    +    });
    +
    +    /**
    +     * Creates an array of values by running each element in `collection` thru
    +     * `iteratee`. The iteratee is invoked with three arguments:
    +     * (value, index|key, collection).
    +     *
    +     * Many lodash methods are guarded to work as iteratees for methods like
    +     * `_.every`, `_.filter`, `_.map`, `_.mapValues`, `_.reject`, and `_.some`.
    +     *
    +     * The guarded methods are:
    +     * `ary`, `chunk`, `curry`, `curryRight`, `drop`, `dropRight`, `every`,
    +     * `fill`, `invert`, `parseInt`, `random`, `range`, `rangeRight`, `repeat`,
    +     * `sampleSize`, `slice`, `some`, `sortBy`, `split`, `take`, `takeRight`,
    +     * `template`, `trim`, `trimEnd`, `trimStart`, and `words`
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.1.0
    +     * @category Collection
    +     * @param {Array|Object} collection The collection to iterate over.
    +     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
    +     * @returns {Array} Returns the new mapped array.
    +     * @example
    +     *
    +     * function square(n) {
    +     *   return n * n;
    +     * }
    +     *
    +     * _.map([4, 8], square);
    +     * // => [16, 64]
    +     *
    +     * _.map({ 'a': 4, 'b': 8 }, square);
    +     * // => [16, 64] (iteration order is not guaranteed)
    +     *
    +     * var users = [
    +     *   { 'user': 'barney' },
    +     *   { 'user': 'fred' }
    +     * ];
    +     *
    +     * // The `_.property` iteratee shorthand.
    +     * _.map(users, 'user');
    +     * // => ['barney', 'fred']
    +     */
    +    function map(collection, iteratee) {
    +      var func = isArray(collection) ? arrayMap : baseMap;
    +      return func(collection, getIteratee(iteratee, 3));
    +    }
    +
    +    /**
    +     * This method is like `_.sortBy` except that it allows specifying the sort
    +     * orders of the iteratees to sort by. If `orders` is unspecified, all values
    +     * are sorted in ascending order. Otherwise, specify an order of "desc" for
    +     * descending or "asc" for ascending sort order of corresponding values.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 4.0.0
    +     * @category Collection
    +     * @param {Array|Object} collection The collection to iterate over.
    +     * @param {Array[]|Function[]|Object[]|string[]} [iteratees=[_.identity]]
    +     *  The iteratees to sort by.
    +     * @param {string[]} [orders] The sort orders of `iteratees`.
    +     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.reduce`.
    +     * @returns {Array} Returns the new sorted array.
    +     * @example
    +     *
    +     * var users = [
    +     *   { 'user': 'fred',   'age': 48 },
    +     *   { 'user': 'barney', 'age': 34 },
    +     *   { 'user': 'fred',   'age': 40 },
    +     *   { 'user': 'barney', 'age': 36 }
    +     * ];
    +     *
    +     * // Sort by `user` in ascending order and by `age` in descending order.
    +     * _.orderBy(users, ['user', 'age'], ['asc', 'desc']);
    +     * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]
    +     */
    +    function orderBy(collection, iteratees, orders, guard) {
    +      if (collection == null) {
    +        return [];
    +      }
    +      if (!isArray(iteratees)) {
    +        iteratees = iteratees == null ? [] : [iteratees];
    +      }
    +      orders = guard ? undefined : orders;
    +      if (!isArray(orders)) {
    +        orders = orders == null ? [] : [orders];
    +      }
    +      return baseOrderBy(collection, iteratees, orders);
    +    }
    +
    +    /**
    +     * Creates an array of elements split into two groups, the first of which
    +     * contains elements `predicate` returns truthy for, the second of which
    +     * contains elements `predicate` returns falsey for. The predicate is
    +     * invoked with one argument: (value).
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 3.0.0
    +     * @category Collection
    +     * @param {Array|Object} collection The collection to iterate over.
    +     * @param {Function} [predicate=_.identity] The function invoked per iteration.
    +     * @returns {Array} Returns the array of grouped elements.
    +     * @example
    +     *
    +     * var users = [
    +     *   { 'user': 'barney',  'age': 36, 'active': false },
    +     *   { 'user': 'fred',    'age': 40, 'active': true },
    +     *   { 'user': 'pebbles', 'age': 1,  'active': false }
    +     * ];
    +     *
    +     * _.partition(users, function(o) { return o.active; });
    +     * // => objects for [['fred'], ['barney', 'pebbles']]
    +     *
    +     * // The `_.matches` iteratee shorthand.
    +     * _.partition(users, { 'age': 1, 'active': false });
    +     * // => objects for [['pebbles'], ['barney', 'fred']]
    +     *
    +     * // The `_.matchesProperty` iteratee shorthand.
    +     * _.partition(users, ['active', false]);
    +     * // => objects for [['barney', 'pebbles'], ['fred']]
    +     *
    +     * // The `_.property` iteratee shorthand.
    +     * _.partition(users, 'active');
    +     * // => objects for [['fred'], ['barney', 'pebbles']]
    +     */
    +    var partition = createAggregator(function (result, value, key) {
    +      result[key ? 0 : 1].push(value);
    +    }, function () {
    +      return [[], []];
    +    });
    +
    +    /**
    +     * Reduces `collection` to a value which is the accumulated result of running
    +     * each element in `collection` thru `iteratee`, where each successive
    +     * invocation is supplied the return value of the previous. If `accumulator`
    +     * is not given, the first element of `collection` is used as the initial
    +     * value. The iteratee is invoked with four arguments:
    +     * (accumulator, value, index|key, collection).
    +     *
    +     * Many lodash methods are guarded to work as iteratees for methods like
    +     * `_.reduce`, `_.reduceRight`, and `_.transform`.
    +     *
    +     * The guarded methods are:
    +     * `assign`, `defaults`, `defaultsDeep`, `includes`, `merge`, `orderBy`,
    +     * and `sortBy`
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.1.0
    +     * @category Collection
    +     * @param {Array|Object} collection The collection to iterate over.
    +     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
    +     * @param {*} [accumulator] The initial value.
    +     * @returns {*} Returns the accumulated value.
    +     * @see _.reduceRight
    +     * @example
    +     *
    +     * _.reduce([1, 2], function(sum, n) {
    +     *   return sum + n;
    +     * }, 0);
    +     * // => 3
    +     *
    +     * _.reduce({ 'a': 1, 'b': 2, 'c': 1 }, function(result, value, key) {
    +     *   (result[value] || (result[value] = [])).push(key);
    +     *   return result;
    +     * }, {});
    +     * // => { '1': ['a', 'c'], '2': ['b'] } (iteration order is not guaranteed)
    +     */
    +    function reduce(collection, iteratee, accumulator) {
    +      var func = isArray(collection) ? arrayReduce : baseReduce,
    +        initAccum = arguments.length < 3;
    +      return func(collection, getIteratee(iteratee, 4), accumulator, initAccum, baseEach);
    +    }
    +
    +    /**
    +     * This method is like `_.reduce` except that it iterates over elements of
    +     * `collection` from right to left.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.1.0
    +     * @category Collection
    +     * @param {Array|Object} collection The collection to iterate over.
    +     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
    +     * @param {*} [accumulator] The initial value.
    +     * @returns {*} Returns the accumulated value.
    +     * @see _.reduce
    +     * @example
    +     *
    +     * var array = [[0, 1], [2, 3], [4, 5]];
    +     *
    +     * _.reduceRight(array, function(flattened, other) {
    +     *   return flattened.concat(other);
    +     * }, []);
    +     * // => [4, 5, 2, 3, 0, 1]
    +     */
    +    function reduceRight(collection, iteratee, accumulator) {
    +      var func = isArray(collection) ? arrayReduceRight : baseReduce,
    +        initAccum = arguments.length < 3;
    +      return func(collection, getIteratee(iteratee, 4), accumulator, initAccum, baseEachRight);
    +    }
    +
    +    /**
    +     * The opposite of `_.filter`; this method returns the elements of `collection`
    +     * that `predicate` does **not** return truthy for.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.1.0
    +     * @category Collection
    +     * @param {Array|Object} collection The collection to iterate over.
    +     * @param {Function} [predicate=_.identity] The function invoked per iteration.
    +     * @returns {Array} Returns the new filtered array.
    +     * @see _.filter
    +     * @example
    +     *
    +     * var users = [
    +     *   { 'user': 'barney', 'age': 36, 'active': false },
    +     *   { 'user': 'fred',   'age': 40, 'active': true }
    +     * ];
    +     *
    +     * _.reject(users, function(o) { return !o.active; });
    +     * // => objects for ['fred']
    +     *
    +     * // The `_.matches` iteratee shorthand.
    +     * _.reject(users, { 'age': 40, 'active': true });
    +     * // => objects for ['barney']
    +     *
    +     * // The `_.matchesProperty` iteratee shorthand.
    +     * _.reject(users, ['active', false]);
    +     * // => objects for ['fred']
    +     *
    +     * // The `_.property` iteratee shorthand.
    +     * _.reject(users, 'active');
    +     * // => objects for ['barney']
    +     */
    +    function reject(collection, predicate) {
    +      var func = isArray(collection) ? arrayFilter : baseFilter;
    +      return func(collection, negate(getIteratee(predicate, 3)));
    +    }
    +
    +    /**
    +     * Gets a random element from `collection`.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 2.0.0
    +     * @category Collection
    +     * @param {Array|Object} collection The collection to sample.
    +     * @returns {*} Returns the random element.
    +     * @example
    +     *
    +     * _.sample([1, 2, 3, 4]);
    +     * // => 2
    +     */
    +    function sample(collection) {
    +      var func = isArray(collection) ? arraySample : baseSample;
    +      return func(collection);
    +    }
    +
    +    /**
    +     * Gets `n` random elements at unique keys from `collection` up to the
    +     * size of `collection`.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 4.0.0
    +     * @category Collection
    +     * @param {Array|Object} collection The collection to sample.
    +     * @param {number} [n=1] The number of elements to sample.
    +     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
    +     * @returns {Array} Returns the random elements.
    +     * @example
    +     *
    +     * _.sampleSize([1, 2, 3], 2);
    +     * // => [3, 1]
    +     *
    +     * _.sampleSize([1, 2, 3], 4);
    +     * // => [2, 3, 1]
    +     */
    +    function sampleSize(collection, n, guard) {
    +      if (guard ? isIterateeCall(collection, n, guard) : n === undefined) {
    +        n = 1;
    +      } else {
    +        n = toInteger(n);
    +      }
    +      var func = isArray(collection) ? arraySampleSize : baseSampleSize;
    +      return func(collection, n);
    +    }
    +
    +    /**
    +     * Creates an array of shuffled values, using a version of the
    +     * [Fisher-Yates shuffle](https://en.wikipedia.org/wiki/Fisher-Yates_shuffle).
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.1.0
    +     * @category Collection
    +     * @param {Array|Object} collection The collection to shuffle.
    +     * @returns {Array} Returns the new shuffled array.
    +     * @example
    +     *
    +     * _.shuffle([1, 2, 3, 4]);
    +     * // => [4, 1, 3, 2]
    +     */
    +    function shuffle(collection) {
    +      var func = isArray(collection) ? arrayShuffle : baseShuffle;
    +      return func(collection);
    +    }
    +
    +    /**
    +     * Gets the size of `collection` by returning its length for array-like
    +     * values or the number of own enumerable string keyed properties for objects.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.1.0
    +     * @category Collection
    +     * @param {Array|Object|string} collection The collection to inspect.
    +     * @returns {number} Returns the collection size.
    +     * @example
    +     *
    +     * _.size([1, 2, 3]);
    +     * // => 3
    +     *
    +     * _.size({ 'a': 1, 'b': 2 });
    +     * // => 2
    +     *
    +     * _.size('pebbles');
    +     * // => 7
    +     */
    +    function size(collection) {
    +      if (collection == null) {
    +        return 0;
    +      }
    +      if (isArrayLike(collection)) {
    +        return isString(collection) ? stringSize(collection) : collection.length;
    +      }
    +      var tag = getTag(collection);
    +      if (tag == mapTag || tag == setTag) {
    +        return collection.size;
    +      }
    +      return baseKeys(collection).length;
    +    }
    +
    +    /**
    +     * Checks if `predicate` returns truthy for **any** element of `collection`.
    +     * Iteration is stopped once `predicate` returns truthy. The predicate is
    +     * invoked with three arguments: (value, index|key, collection).
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.1.0
    +     * @category Collection
    +     * @param {Array|Object} collection The collection to iterate over.
    +     * @param {Function} [predicate=_.identity] The function invoked per iteration.
    +     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
    +     * @returns {boolean} Returns `true` if any element passes the predicate check,
    +     *  else `false`.
    +     * @example
    +     *
    +     * _.some([null, 0, 'yes', false], Boolean);
    +     * // => true
    +     *
    +     * var users = [
    +     *   { 'user': 'barney', 'active': true },
    +     *   { 'user': 'fred',   'active': false }
    +     * ];
    +     *
    +     * // The `_.matches` iteratee shorthand.
    +     * _.some(users, { 'user': 'barney', 'active': false });
    +     * // => false
    +     *
    +     * // The `_.matchesProperty` iteratee shorthand.
    +     * _.some(users, ['active', false]);
    +     * // => true
    +     *
    +     * // The `_.property` iteratee shorthand.
    +     * _.some(users, 'active');
    +     * // => true
    +     */
    +    function some(collection, predicate, guard) {
    +      var func = isArray(collection) ? arraySome : baseSome;
    +      if (guard && isIterateeCall(collection, predicate, guard)) {
    +        predicate = undefined;
    +      }
    +      return func(collection, getIteratee(predicate, 3));
    +    }
    +
    +    /**
    +     * Creates an array of elements, sorted in ascending order by the results of
    +     * running each element in a collection thru each iteratee. This method
    +     * performs a stable sort, that is, it preserves the original sort order of
    +     * equal elements. The iteratees are invoked with one argument: (value).
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.1.0
    +     * @category Collection
    +     * @param {Array|Object} collection The collection to iterate over.
    +     * @param {...(Function|Function[])} [iteratees=[_.identity]]
    +     *  The iteratees to sort by.
    +     * @returns {Array} Returns the new sorted array.
    +     * @example
    +     *
    +     * var users = [
    +     *   { 'user': 'fred',   'age': 48 },
    +     *   { 'user': 'barney', 'age': 36 },
    +     *   { 'user': 'fred',   'age': 30 },
    +     *   { 'user': 'barney', 'age': 34 }
    +     * ];
    +     *
    +     * _.sortBy(users, [function(o) { return o.user; }]);
    +     * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 30]]
    +     *
    +     * _.sortBy(users, ['user', 'age']);
    +     * // => objects for [['barney', 34], ['barney', 36], ['fred', 30], ['fred', 48]]
    +     */
    +    var sortBy = baseRest(function (collection, iteratees) {
    +      if (collection == null) {
    +        return [];
    +      }
    +      var length = iteratees.length;
    +      if (length > 1 && isIterateeCall(collection, iteratees[0], iteratees[1])) {
    +        iteratees = [];
    +      } else if (length > 2 && isIterateeCall(iteratees[0], iteratees[1], iteratees[2])) {
    +        iteratees = [iteratees[0]];
    +      }
    +      return baseOrderBy(collection, baseFlatten(iteratees, 1), []);
    +    });
    +
    +    /*------------------------------------------------------------------------*/
    +
    +    /**
    +     * Gets the timestamp of the number of milliseconds that have elapsed since
    +     * the Unix epoch (1 January 1970 00:00:00 UTC).
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 2.4.0
    +     * @category Date
    +     * @returns {number} Returns the timestamp.
    +     * @example
    +     *
    +     * _.defer(function(stamp) {
    +     *   console.log(_.now() - stamp);
    +     * }, _.now());
    +     * // => Logs the number of milliseconds it took for the deferred invocation.
    +     */
    +    var now = ctxNow || function () {
    +      return root.Date.now();
    +    };
    +
    +    /*------------------------------------------------------------------------*/
    +
    +    /**
    +     * The opposite of `_.before`; this method creates a function that invokes
    +     * `func` once it's called `n` or more times.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.1.0
    +     * @category Function
    +     * @param {number} n The number of calls before `func` is invoked.
    +     * @param {Function} func The function to restrict.
    +     * @returns {Function} Returns the new restricted function.
    +     * @example
    +     *
    +     * var saves = ['profile', 'settings'];
    +     *
    +     * var done = _.after(saves.length, function() {
    +     *   console.log('done saving!');
    +     * });
    +     *
    +     * _.forEach(saves, function(type) {
    +     *   asyncSave({ 'type': type, 'complete': done });
    +     * });
    +     * // => Logs 'done saving!' after the two async saves have completed.
    +     */
    +    function after(n, func) {
    +      if (typeof func != 'function') {
    +        throw new TypeError(FUNC_ERROR_TEXT);
    +      }
    +      n = toInteger(n);
    +      return function () {
    +        if (--n < 1) {
    +          return func.apply(this, arguments);
    +        }
    +      };
    +    }
    +
    +    /**
    +     * Creates a function that invokes `func`, with up to `n` arguments,
    +     * ignoring any additional arguments.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 3.0.0
    +     * @category Function
    +     * @param {Function} func The function to cap arguments for.
    +     * @param {number} [n=func.length] The arity cap.
    +     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
    +     * @returns {Function} Returns the new capped function.
    +     * @example
    +     *
    +     * _.map(['6', '8', '10'], _.ary(parseInt, 1));
    +     * // => [6, 8, 10]
    +     */
    +    function ary(func, n, guard) {
    +      n = guard ? undefined : n;
    +      n = func && n == null ? func.length : n;
    +      return createWrap(func, WRAP_ARY_FLAG, undefined, undefined, undefined, undefined, n);
    +    }
    +
    +    /**
    +     * Creates a function that invokes `func`, with the `this` binding and arguments
    +     * of the created function, while it's called less than `n` times. Subsequent
    +     * calls to the created function return the result of the last `func` invocation.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 3.0.0
    +     * @category Function
    +     * @param {number} n The number of calls at which `func` is no longer invoked.
    +     * @param {Function} func The function to restrict.
    +     * @returns {Function} Returns the new restricted function.
    +     * @example
    +     *
    +     * jQuery(element).on('click', _.before(5, addContactToList));
    +     * // => Allows adding up to 4 contacts to the list.
    +     */
    +    function before(n, func) {
    +      var result;
    +      if (typeof func != 'function') {
    +        throw new TypeError(FUNC_ERROR_TEXT);
    +      }
    +      n = toInteger(n);
    +      return function () {
    +        if (--n > 0) {
    +          result = func.apply(this, arguments);
    +        }
    +        if (n <= 1) {
    +          func = undefined;
    +        }
    +        return result;
    +      };
    +    }
    +
    +    /**
    +     * Creates a function that invokes `func` with the `this` binding of `thisArg`
    +     * and `partials` prepended to the arguments it receives.
    +     *
    +     * The `_.bind.placeholder` value, which defaults to `_` in monolithic builds,
    +     * may be used as a placeholder for partially applied arguments.
    +     *
    +     * **Note:** Unlike native `Function#bind`, this method doesn't set the "length"
    +     * property of bound functions.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.1.0
    +     * @category Function
    +     * @param {Function} func The function to bind.
    +     * @param {*} thisArg The `this` binding of `func`.
    +     * @param {...*} [partials] The arguments to be partially applied.
    +     * @returns {Function} Returns the new bound function.
    +     * @example
    +     *
    +     * function greet(greeting, punctuation) {
    +     *   return greeting + ' ' + this.user + punctuation;
    +     * }
    +     *
    +     * var object = { 'user': 'fred' };
    +     *
    +     * var bound = _.bind(greet, object, 'hi');
    +     * bound('!');
    +     * // => 'hi fred!'
    +     *
    +     * // Bound with placeholders.
    +     * var bound = _.bind(greet, object, _, '!');
    +     * bound('hi');
    +     * // => 'hi fred!'
    +     */
    +    var bind = baseRest(function (func, thisArg, partials) {
    +      var bitmask = WRAP_BIND_FLAG;
    +      if (partials.length) {
    +        var holders = replaceHolders(partials, getHolder(bind));
    +        bitmask |= WRAP_PARTIAL_FLAG;
    +      }
    +      return createWrap(func, bitmask, thisArg, partials, holders);
    +    });
    +
    +    /**
    +     * Creates a function that invokes the method at `object[key]` with `partials`
    +     * prepended to the arguments it receives.
    +     *
    +     * This method differs from `_.bind` by allowing bound functions to reference
    +     * methods that may be redefined or don't yet exist. See
    +     * [Peter Michaux's article](http://peter.michaux.ca/articles/lazy-function-definition-pattern)
    +     * for more details.
    +     *
    +     * The `_.bindKey.placeholder` value, which defaults to `_` in monolithic
    +     * builds, may be used as a placeholder for partially applied arguments.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.10.0
    +     * @category Function
    +     * @param {Object} object The object to invoke the method on.
    +     * @param {string} key The key of the method.
    +     * @param {...*} [partials] The arguments to be partially applied.
    +     * @returns {Function} Returns the new bound function.
    +     * @example
    +     *
    +     * var object = {
    +     *   'user': 'fred',
    +     *   'greet': function(greeting, punctuation) {
    +     *     return greeting + ' ' + this.user + punctuation;
    +     *   }
    +     * };
    +     *
    +     * var bound = _.bindKey(object, 'greet', 'hi');
    +     * bound('!');
    +     * // => 'hi fred!'
    +     *
    +     * object.greet = function(greeting, punctuation) {
    +     *   return greeting + 'ya ' + this.user + punctuation;
    +     * };
    +     *
    +     * bound('!');
    +     * // => 'hiya fred!'
    +     *
    +     * // Bound with placeholders.
    +     * var bound = _.bindKey(object, 'greet', _, '!');
    +     * bound('hi');
    +     * // => 'hiya fred!'
    +     */
    +    var bindKey = baseRest(function (object, key, partials) {
    +      var bitmask = WRAP_BIND_FLAG | WRAP_BIND_KEY_FLAG;
    +      if (partials.length) {
    +        var holders = replaceHolders(partials, getHolder(bindKey));
    +        bitmask |= WRAP_PARTIAL_FLAG;
    +      }
    +      return createWrap(key, bitmask, object, partials, holders);
    +    });
    +
    +    /**
    +     * Creates a function that accepts arguments of `func` and either invokes
    +     * `func` returning its result, if at least `arity` number of arguments have
    +     * been provided, or returns a function that accepts the remaining `func`
    +     * arguments, and so on. The arity of `func` may be specified if `func.length`
    +     * is not sufficient.
    +     *
    +     * The `_.curry.placeholder` value, which defaults to `_` in monolithic builds,
    +     * may be used as a placeholder for provided arguments.
    +     *
    +     * **Note:** This method doesn't set the "length" property of curried functions.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 2.0.0
    +     * @category Function
    +     * @param {Function} func The function to curry.
    +     * @param {number} [arity=func.length] The arity of `func`.
    +     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
    +     * @returns {Function} Returns the new curried function.
    +     * @example
    +     *
    +     * var abc = function(a, b, c) {
    +     *   return [a, b, c];
    +     * };
    +     *
    +     * var curried = _.curry(abc);
    +     *
    +     * curried(1)(2)(3);
    +     * // => [1, 2, 3]
    +     *
    +     * curried(1, 2)(3);
    +     * // => [1, 2, 3]
    +     *
    +     * curried(1, 2, 3);
    +     * // => [1, 2, 3]
    +     *
    +     * // Curried with placeholders.
    +     * curried(1)(_, 3)(2);
    +     * // => [1, 2, 3]
    +     */
    +    function curry(func, arity, guard) {
    +      arity = guard ? undefined : arity;
    +      var result = createWrap(func, WRAP_CURRY_FLAG, undefined, undefined, undefined, undefined, undefined, arity);
    +      result.placeholder = curry.placeholder;
    +      return result;
    +    }
    +
    +    /**
    +     * This method is like `_.curry` except that arguments are applied to `func`
    +     * in the manner of `_.partialRight` instead of `_.partial`.
    +     *
    +     * The `_.curryRight.placeholder` value, which defaults to `_` in monolithic
    +     * builds, may be used as a placeholder for provided arguments.
    +     *
    +     * **Note:** This method doesn't set the "length" property of curried functions.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 3.0.0
    +     * @category Function
    +     * @param {Function} func The function to curry.
    +     * @param {number} [arity=func.length] The arity of `func`.
    +     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
    +     * @returns {Function} Returns the new curried function.
    +     * @example
    +     *
    +     * var abc = function(a, b, c) {
    +     *   return [a, b, c];
    +     * };
    +     *
    +     * var curried = _.curryRight(abc);
    +     *
    +     * curried(3)(2)(1);
    +     * // => [1, 2, 3]
    +     *
    +     * curried(2, 3)(1);
    +     * // => [1, 2, 3]
    +     *
    +     * curried(1, 2, 3);
    +     * // => [1, 2, 3]
    +     *
    +     * // Curried with placeholders.
    +     * curried(3)(1, _)(2);
    +     * // => [1, 2, 3]
    +     */
    +    function curryRight(func, arity, guard) {
    +      arity = guard ? undefined : arity;
    +      var result = createWrap(func, WRAP_CURRY_RIGHT_FLAG, undefined, undefined, undefined, undefined, undefined, arity);
    +      result.placeholder = curryRight.placeholder;
    +      return result;
    +    }
    +
    +    /**
    +     * Creates a debounced function that delays invoking `func` until after `wait`
    +     * milliseconds have elapsed since the last time the debounced function was
    +     * invoked. The debounced function comes with a `cancel` method to cancel
    +     * delayed `func` invocations and a `flush` method to immediately invoke them.
    +     * Provide `options` to indicate whether `func` should be invoked on the
    +     * leading and/or trailing edge of the `wait` timeout. The `func` is invoked
    +     * with the last arguments provided to the debounced function. Subsequent
    +     * calls to the debounced function return the result of the last `func`
    +     * invocation.
    +     *
    +     * **Note:** If `leading` and `trailing` options are `true`, `func` is
    +     * invoked on the trailing edge of the timeout only if the debounced function
    +     * is invoked more than once during the `wait` timeout.
    +     *
    +     * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
    +     * until to the next tick, similar to `setTimeout` with a timeout of `0`.
    +     *
    +     * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
    +     * for details over the differences between `_.debounce` and `_.throttle`.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.1.0
    +     * @category Function
    +     * @param {Function} func The function to debounce.
    +     * @param {number} [wait=0] The number of milliseconds to delay.
    +     * @param {Object} [options={}] The options object.
    +     * @param {boolean} [options.leading=false]
    +     *  Specify invoking on the leading edge of the timeout.
    +     * @param {number} [options.maxWait]
    +     *  The maximum time `func` is allowed to be delayed before it's invoked.
    +     * @param {boolean} [options.trailing=true]
    +     *  Specify invoking on the trailing edge of the timeout.
    +     * @returns {Function} Returns the new debounced function.
    +     * @example
    +     *
    +     * // Avoid costly calculations while the window size is in flux.
    +     * jQuery(window).on('resize', _.debounce(calculateLayout, 150));
    +     *
    +     * // Invoke `sendMail` when clicked, debouncing subsequent calls.
    +     * jQuery(element).on('click', _.debounce(sendMail, 300, {
    +     *   'leading': true,
    +     *   'trailing': false
    +     * }));
    +     *
    +     * // Ensure `batchLog` is invoked once after 1 second of debounced calls.
    +     * var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 });
    +     * var source = new EventSource('/stream');
    +     * jQuery(source).on('message', debounced);
    +     *
    +     * // Cancel the trailing debounced invocation.
    +     * jQuery(window).on('popstate', debounced.cancel);
    +     */
    +    function debounce(func, wait, options) {
    +      var lastArgs,
    +        lastThis,
    +        maxWait,
    +        result,
    +        timerId,
    +        lastCallTime,
    +        lastInvokeTime = 0,
    +        leading = false,
    +        maxing = false,
    +        trailing = true;
    +      if (typeof func != 'function') {
    +        throw new TypeError(FUNC_ERROR_TEXT);
    +      }
    +      wait = toNumber(wait) || 0;
    +      if (isObject(options)) {
    +        leading = !!options.leading;
    +        maxing = 'maxWait' in options;
    +        maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait;
    +        trailing = 'trailing' in options ? !!options.trailing : trailing;
    +      }
    +      function invokeFunc(time) {
    +        var args = lastArgs,
    +          thisArg = lastThis;
    +        lastArgs = lastThis = undefined;
    +        lastInvokeTime = time;
    +        result = func.apply(thisArg, args);
    +        return result;
    +      }
    +      function leadingEdge(time) {
    +        // Reset any `maxWait` timer.
    +        lastInvokeTime = time;
    +        // Start the timer for the trailing edge.
    +        timerId = setTimeout(timerExpired, wait);
    +        // Invoke the leading edge.
    +        return leading ? invokeFunc(time) : result;
    +      }
    +      function remainingWait(time) {
    +        var timeSinceLastCall = time - lastCallTime,
    +          timeSinceLastInvoke = time - lastInvokeTime,
    +          timeWaiting = wait - timeSinceLastCall;
    +        return maxing ? nativeMin(timeWaiting, maxWait - timeSinceLastInvoke) : timeWaiting;
    +      }
    +      function shouldInvoke(time) {
    +        var timeSinceLastCall = time - lastCallTime,
    +          timeSinceLastInvoke = time - lastInvokeTime;
    +
    +        // Either this is the first call, activity has stopped and we're at the
    +        // trailing edge, the system time has gone backwards and we're treating
    +        // it as the trailing edge, or we've hit the `maxWait` limit.
    +        return lastCallTime === undefined || timeSinceLastCall >= wait || timeSinceLastCall < 0 || maxing && timeSinceLastInvoke >= maxWait;
    +      }
    +      function timerExpired() {
    +        var time = now();
    +        if (shouldInvoke(time)) {
    +          return trailingEdge(time);
    +        }
    +        // Restart the timer.
    +        timerId = setTimeout(timerExpired, remainingWait(time));
    +      }
    +      function trailingEdge(time) {
    +        timerId = undefined;
    +
    +        // Only invoke if we have `lastArgs` which means `func` has been
    +        // debounced at least once.
    +        if (trailing && lastArgs) {
    +          return invokeFunc(time);
    +        }
    +        lastArgs = lastThis = undefined;
    +        return result;
    +      }
    +      function cancel() {
    +        if (timerId !== undefined) {
    +          clearTimeout(timerId);
    +        }
    +        lastInvokeTime = 0;
    +        lastArgs = lastCallTime = lastThis = timerId = undefined;
    +      }
    +      function flush() {
    +        return timerId === undefined ? result : trailingEdge(now());
    +      }
    +      function debounced() {
    +        var time = now(),
    +          isInvoking = shouldInvoke(time);
    +        lastArgs = arguments;
    +        lastThis = this;
    +        lastCallTime = time;
    +        if (isInvoking) {
    +          if (timerId === undefined) {
    +            return leadingEdge(lastCallTime);
    +          }
    +          if (maxing) {
    +            // Handle invocations in a tight loop.
    +            clearTimeout(timerId);
    +            timerId = setTimeout(timerExpired, wait);
    +            return invokeFunc(lastCallTime);
    +          }
    +        }
    +        if (timerId === undefined) {
    +          timerId = setTimeout(timerExpired, wait);
    +        }
    +        return result;
    +      }
    +      debounced.cancel = cancel;
    +      debounced.flush = flush;
    +      return debounced;
    +    }
    +
    +    /**
    +     * Defers invoking the `func` until the current call stack has cleared. Any
    +     * additional arguments are provided to `func` when it's invoked.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.1.0
    +     * @category Function
    +     * @param {Function} func The function to defer.
    +     * @param {...*} [args] The arguments to invoke `func` with.
    +     * @returns {number} Returns the timer id.
    +     * @example
    +     *
    +     * _.defer(function(text) {
    +     *   console.log(text);
    +     * }, 'deferred');
    +     * // => Logs 'deferred' after one millisecond.
    +     */
    +    var defer = baseRest(function (func, args) {
    +      return baseDelay(func, 1, args);
    +    });
    +
    +    /**
    +     * Invokes `func` after `wait` milliseconds. Any additional arguments are
    +     * provided to `func` when it's invoked.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.1.0
    +     * @category Function
    +     * @param {Function} func The function to delay.
    +     * @param {number} wait The number of milliseconds to delay invocation.
    +     * @param {...*} [args] The arguments to invoke `func` with.
    +     * @returns {number} Returns the timer id.
    +     * @example
    +     *
    +     * _.delay(function(text) {
    +     *   console.log(text);
    +     * }, 1000, 'later');
    +     * // => Logs 'later' after one second.
    +     */
    +    var delay = baseRest(function (func, wait, args) {
    +      return baseDelay(func, toNumber(wait) || 0, args);
    +    });
    +
    +    /**
    +     * Creates a function that invokes `func` with arguments reversed.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 4.0.0
    +     * @category Function
    +     * @param {Function} func The function to flip arguments for.
    +     * @returns {Function} Returns the new flipped function.
    +     * @example
    +     *
    +     * var flipped = _.flip(function() {
    +     *   return _.toArray(arguments);
    +     * });
    +     *
    +     * flipped('a', 'b', 'c', 'd');
    +     * // => ['d', 'c', 'b', 'a']
    +     */
    +    function flip(func) {
    +      return createWrap(func, WRAP_FLIP_FLAG);
    +    }
    +
    +    /**
    +     * Creates a function that memoizes the result of `func`. If `resolver` is
    +     * provided, it determines the cache key for storing the result based on the
    +     * arguments provided to the memoized function. By default, the first argument
    +     * provided to the memoized function is used as the map cache key. The `func`
    +     * is invoked with the `this` binding of the memoized function.
    +     *
    +     * **Note:** The cache is exposed as the `cache` property on the memoized
    +     * function. Its creation may be customized by replacing the `_.memoize.Cache`
    +     * constructor with one whose instances implement the
    +     * [`Map`](http://ecma-international.org/ecma-262/7.0/#sec-properties-of-the-map-prototype-object)
    +     * method interface of `clear`, `delete`, `get`, `has`, and `set`.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.1.0
    +     * @category Function
    +     * @param {Function} func The function to have its output memoized.
    +     * @param {Function} [resolver] The function to resolve the cache key.
    +     * @returns {Function} Returns the new memoized function.
    +     * @example
    +     *
    +     * var object = { 'a': 1, 'b': 2 };
    +     * var other = { 'c': 3, 'd': 4 };
    +     *
    +     * var values = _.memoize(_.values);
    +     * values(object);
    +     * // => [1, 2]
    +     *
    +     * values(other);
    +     * // => [3, 4]
    +     *
    +     * object.a = 2;
    +     * values(object);
    +     * // => [1, 2]
    +     *
    +     * // Modify the result cache.
    +     * values.cache.set(object, ['a', 'b']);
    +     * values(object);
    +     * // => ['a', 'b']
    +     *
    +     * // Replace `_.memoize.Cache`.
    +     * _.memoize.Cache = WeakMap;
    +     */
    +    function memoize(func, resolver) {
    +      if (typeof func != 'function' || resolver != null && typeof resolver != 'function') {
    +        throw new TypeError(FUNC_ERROR_TEXT);
    +      }
    +      var memoized = function memoized() {
    +        var args = arguments,
    +          key = resolver ? resolver.apply(this, args) : args[0],
    +          cache = memoized.cache;
    +        if (cache.has(key)) {
    +          return cache.get(key);
    +        }
    +        var result = func.apply(this, args);
    +        memoized.cache = cache.set(key, result) || cache;
    +        return result;
    +      };
    +      memoized.cache = new (memoize.Cache || MapCache)();
    +      return memoized;
    +    }
    +
    +    // Expose `MapCache`.
    +    memoize.Cache = MapCache;
    +
    +    /**
    +     * Creates a function that negates the result of the predicate `func`. The
    +     * `func` predicate is invoked with the `this` binding and arguments of the
    +     * created function.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 3.0.0
    +     * @category Function
    +     * @param {Function} predicate The predicate to negate.
    +     * @returns {Function} Returns the new negated function.
    +     * @example
    +     *
    +     * function isEven(n) {
    +     *   return n % 2 == 0;
    +     * }
    +     *
    +     * _.filter([1, 2, 3, 4, 5, 6], _.negate(isEven));
    +     * // => [1, 3, 5]
    +     */
    +    function negate(predicate) {
    +      if (typeof predicate != 'function') {
    +        throw new TypeError(FUNC_ERROR_TEXT);
    +      }
    +      return function () {
    +        var args = arguments;
    +        switch (args.length) {
    +          case 0:
    +            return !predicate.call(this);
    +          case 1:
    +            return !predicate.call(this, args[0]);
    +          case 2:
    +            return !predicate.call(this, args[0], args[1]);
    +          case 3:
    +            return !predicate.call(this, args[0], args[1], args[2]);
    +        }
    +        return !predicate.apply(this, args);
    +      };
    +    }
    +
    +    /**
    +     * Creates a function that is restricted to invoking `func` once. Repeat calls
    +     * to the function return the value of the first invocation. The `func` is
    +     * invoked with the `this` binding and arguments of the created function.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.1.0
    +     * @category Function
    +     * @param {Function} func The function to restrict.
    +     * @returns {Function} Returns the new restricted function.
    +     * @example
    +     *
    +     * var initialize = _.once(createApplication);
    +     * initialize();
    +     * initialize();
    +     * // => `createApplication` is invoked once
    +     */
    +    function once(func) {
    +      return before(2, func);
    +    }
    +
    +    /**
    +     * Creates a function that invokes `func` with its arguments transformed.
    +     *
    +     * @static
    +     * @since 4.0.0
    +     * @memberOf _
    +     * @category Function
    +     * @param {Function} func The function to wrap.
    +     * @param {...(Function|Function[])} [transforms=[_.identity]]
    +     *  The argument transforms.
    +     * @returns {Function} Returns the new function.
    +     * @example
    +     *
    +     * function doubled(n) {
    +     *   return n * 2;
    +     * }
    +     *
    +     * function square(n) {
    +     *   return n * n;
    +     * }
    +     *
    +     * var func = _.overArgs(function(x, y) {
    +     *   return [x, y];
    +     * }, [square, doubled]);
    +     *
    +     * func(9, 3);
    +     * // => [81, 6]
    +     *
    +     * func(10, 5);
    +     * // => [100, 10]
    +     */
    +    var overArgs = castRest(function (func, transforms) {
    +      transforms = transforms.length == 1 && isArray(transforms[0]) ? arrayMap(transforms[0], baseUnary(getIteratee())) : arrayMap(baseFlatten(transforms, 1), baseUnary(getIteratee()));
    +      var funcsLength = transforms.length;
    +      return baseRest(function (args) {
    +        var index = -1,
    +          length = nativeMin(args.length, funcsLength);
    +        while (++index < length) {
    +          args[index] = transforms[index].call(this, args[index]);
    +        }
    +        return apply(func, this, args);
    +      });
    +    });
    +
    +    /**
    +     * Creates a function that invokes `func` with `partials` prepended to the
    +     * arguments it receives. This method is like `_.bind` except it does **not**
    +     * alter the `this` binding.
    +     *
    +     * The `_.partial.placeholder` value, which defaults to `_` in monolithic
    +     * builds, may be used as a placeholder for partially applied arguments.
    +     *
    +     * **Note:** This method doesn't set the "length" property of partially
    +     * applied functions.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.2.0
    +     * @category Function
    +     * @param {Function} func The function to partially apply arguments to.
    +     * @param {...*} [partials] The arguments to be partially applied.
    +     * @returns {Function} Returns the new partially applied function.
    +     * @example
    +     *
    +     * function greet(greeting, name) {
    +     *   return greeting + ' ' + name;
    +     * }
    +     *
    +     * var sayHelloTo = _.partial(greet, 'hello');
    +     * sayHelloTo('fred');
    +     * // => 'hello fred'
    +     *
    +     * // Partially applied with placeholders.
    +     * var greetFred = _.partial(greet, _, 'fred');
    +     * greetFred('hi');
    +     * // => 'hi fred'
    +     */
    +    var partial = baseRest(function (func, partials) {
    +      var holders = replaceHolders(partials, getHolder(partial));
    +      return createWrap(func, WRAP_PARTIAL_FLAG, undefined, partials, holders);
    +    });
    +
    +    /**
    +     * This method is like `_.partial` except that partially applied arguments
    +     * are appended to the arguments it receives.
    +     *
    +     * The `_.partialRight.placeholder` value, which defaults to `_` in monolithic
    +     * builds, may be used as a placeholder for partially applied arguments.
    +     *
    +     * **Note:** This method doesn't set the "length" property of partially
    +     * applied functions.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 1.0.0
    +     * @category Function
    +     * @param {Function} func The function to partially apply arguments to.
    +     * @param {...*} [partials] The arguments to be partially applied.
    +     * @returns {Function} Returns the new partially applied function.
    +     * @example
    +     *
    +     * function greet(greeting, name) {
    +     *   return greeting + ' ' + name;
    +     * }
    +     *
    +     * var greetFred = _.partialRight(greet, 'fred');
    +     * greetFred('hi');
    +     * // => 'hi fred'
    +     *
    +     * // Partially applied with placeholders.
    +     * var sayHelloTo = _.partialRight(greet, 'hello', _);
    +     * sayHelloTo('fred');
    +     * // => 'hello fred'
    +     */
    +    var partialRight = baseRest(function (func, partials) {
    +      var holders = replaceHolders(partials, getHolder(partialRight));
    +      return createWrap(func, WRAP_PARTIAL_RIGHT_FLAG, undefined, partials, holders);
    +    });
    +
    +    /**
    +     * Creates a function that invokes `func` with arguments arranged according
    +     * to the specified `indexes` where the argument value at the first index is
    +     * provided as the first argument, the argument value at the second index is
    +     * provided as the second argument, and so on.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 3.0.0
    +     * @category Function
    +     * @param {Function} func The function to rearrange arguments for.
    +     * @param {...(number|number[])} indexes The arranged argument indexes.
    +     * @returns {Function} Returns the new function.
    +     * @example
    +     *
    +     * var rearged = _.rearg(function(a, b, c) {
    +     *   return [a, b, c];
    +     * }, [2, 0, 1]);
    +     *
    +     * rearged('b', 'c', 'a')
    +     * // => ['a', 'b', 'c']
    +     */
    +    var rearg = flatRest(function (func, indexes) {
    +      return createWrap(func, WRAP_REARG_FLAG, undefined, undefined, undefined, indexes);
    +    });
    +
    +    /**
    +     * Creates a function that invokes `func` with the `this` binding of the
    +     * created function and arguments from `start` and beyond provided as
    +     * an array.
    +     *
    +     * **Note:** This method is based on the
    +     * [rest parameter](https://mdn.io/rest_parameters).
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 4.0.0
    +     * @category Function
    +     * @param {Function} func The function to apply a rest parameter to.
    +     * @param {number} [start=func.length-1] The start position of the rest parameter.
    +     * @returns {Function} Returns the new function.
    +     * @example
    +     *
    +     * var say = _.rest(function(what, names) {
    +     *   return what + ' ' + _.initial(names).join(', ') +
    +     *     (_.size(names) > 1 ? ', & ' : '') + _.last(names);
    +     * });
    +     *
    +     * say('hello', 'fred', 'barney', 'pebbles');
    +     * // => 'hello fred, barney, & pebbles'
    +     */
    +    function rest(func, start) {
    +      if (typeof func != 'function') {
    +        throw new TypeError(FUNC_ERROR_TEXT);
    +      }
    +      start = start === undefined ? start : toInteger(start);
    +      return baseRest(func, start);
    +    }
    +
    +    /**
    +     * Creates a function that invokes `func` with the `this` binding of the
    +     * create function and an array of arguments much like
    +     * [`Function#apply`](http://www.ecma-international.org/ecma-262/7.0/#sec-function.prototype.apply).
    +     *
    +     * **Note:** This method is based on the
    +     * [spread operator](https://mdn.io/spread_operator).
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 3.2.0
    +     * @category Function
    +     * @param {Function} func The function to spread arguments over.
    +     * @param {number} [start=0] The start position of the spread.
    +     * @returns {Function} Returns the new function.
    +     * @example
    +     *
    +     * var say = _.spread(function(who, what) {
    +     *   return who + ' says ' + what;
    +     * });
    +     *
    +     * say(['fred', 'hello']);
    +     * // => 'fred says hello'
    +     *
    +     * var numbers = Promise.all([
    +     *   Promise.resolve(40),
    +     *   Promise.resolve(36)
    +     * ]);
    +     *
    +     * numbers.then(_.spread(function(x, y) {
    +     *   return x + y;
    +     * }));
    +     * // => a Promise of 76
    +     */
    +    function spread(func, start) {
    +      if (typeof func != 'function') {
    +        throw new TypeError(FUNC_ERROR_TEXT);
    +      }
    +      start = start == null ? 0 : nativeMax(toInteger(start), 0);
    +      return baseRest(function (args) {
    +        var array = args[start],
    +          otherArgs = castSlice(args, 0, start);
    +        if (array) {
    +          arrayPush(otherArgs, array);
    +        }
    +        return apply(func, this, otherArgs);
    +      });
    +    }
    +
    +    /**
    +     * Creates a throttled function that only invokes `func` at most once per
    +     * every `wait` milliseconds. The throttled function comes with a `cancel`
    +     * method to cancel delayed `func` invocations and a `flush` method to
    +     * immediately invoke them. Provide `options` to indicate whether `func`
    +     * should be invoked on the leading and/or trailing edge of the `wait`
    +     * timeout. The `func` is invoked with the last arguments provided to the
    +     * throttled function. Subsequent calls to the throttled function return the
    +     * result of the last `func` invocation.
    +     *
    +     * **Note:** If `leading` and `trailing` options are `true`, `func` is
    +     * invoked on the trailing edge of the timeout only if the throttled function
    +     * is invoked more than once during the `wait` timeout.
    +     *
    +     * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
    +     * until to the next tick, similar to `setTimeout` with a timeout of `0`.
    +     *
    +     * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
    +     * for details over the differences between `_.throttle` and `_.debounce`.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.1.0
    +     * @category Function
    +     * @param {Function} func The function to throttle.
    +     * @param {number} [wait=0] The number of milliseconds to throttle invocations to.
    +     * @param {Object} [options={}] The options object.
    +     * @param {boolean} [options.leading=true]
    +     *  Specify invoking on the leading edge of the timeout.
    +     * @param {boolean} [options.trailing=true]
    +     *  Specify invoking on the trailing edge of the timeout.
    +     * @returns {Function} Returns the new throttled function.
    +     * @example
    +     *
    +     * // Avoid excessively updating the position while scrolling.
    +     * jQuery(window).on('scroll', _.throttle(updatePosition, 100));
    +     *
    +     * // Invoke `renewToken` when the click event is fired, but not more than once every 5 minutes.
    +     * var throttled = _.throttle(renewToken, 300000, { 'trailing': false });
    +     * jQuery(element).on('click', throttled);
    +     *
    +     * // Cancel the trailing throttled invocation.
    +     * jQuery(window).on('popstate', throttled.cancel);
    +     */
    +    function throttle(func, wait, options) {
    +      var leading = true,
    +        trailing = true;
    +      if (typeof func != 'function') {
    +        throw new TypeError(FUNC_ERROR_TEXT);
    +      }
    +      if (isObject(options)) {
    +        leading = 'leading' in options ? !!options.leading : leading;
    +        trailing = 'trailing' in options ? !!options.trailing : trailing;
    +      }
    +      return debounce(func, wait, {
    +        'leading': leading,
    +        'maxWait': wait,
    +        'trailing': trailing
    +      });
    +    }
    +
    +    /**
    +     * Creates a function that accepts up to one argument, ignoring any
    +     * additional arguments.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 4.0.0
    +     * @category Function
    +     * @param {Function} func The function to cap arguments for.
    +     * @returns {Function} Returns the new capped function.
    +     * @example
    +     *
    +     * _.map(['6', '8', '10'], _.unary(parseInt));
    +     * // => [6, 8, 10]
    +     */
    +    function unary(func) {
    +      return ary(func, 1);
    +    }
    +
    +    /**
    +     * Creates a function that provides `value` to `wrapper` as its first
    +     * argument. Any additional arguments provided to the function are appended
    +     * to those provided to the `wrapper`. The wrapper is invoked with the `this`
    +     * binding of the created function.
    +     *
    +     * @static
    +     * @memberOf _
    +     * @since 0.1.0
    +     * @category Function
    +     * @param {*} value The value to wrap.
    +     * @param {Function} [wrapper=identity] The wrapper function.
    +     * @returns {Function} Returns the new function.
    +     * @example
    +     *
    +     * var p = _.wrap(_.escape, function(func, text) {
    +     *   return '

    ' + func(text) + '

    '; + * }); + * + * p('fred, barney, & pebbles'); + * // => '

    fred, barney, & pebbles

    ' + */ + function wrap(value, wrapper) { + return partial(castFunction(wrapper), value); + } + + /*------------------------------------------------------------------------*/ + + /** + * Casts `value` as an array if it's not one. + * + * @static + * @memberOf _ + * @since 4.4.0 + * @category Lang + * @param {*} value The value to inspect. + * @returns {Array} Returns the cast array. + * @example + * + * _.castArray(1); + * // => [1] + * + * _.castArray({ 'a': 1 }); + * // => [{ 'a': 1 }] + * + * _.castArray('abc'); + * // => ['abc'] + * + * _.castArray(null); + * // => [null] + * + * _.castArray(undefined); + * // => [undefined] + * + * _.castArray(); + * // => [] + * + * var array = [1, 2, 3]; + * console.log(_.castArray(array) === array); + * // => true + */ + function castArray() { + if (!arguments.length) { + return []; + } + var value = arguments[0]; + return isArray(value) ? value : [value]; + } + + /** + * Creates a shallow clone of `value`. + * + * **Note:** This method is loosely based on the + * [structured clone algorithm](https://mdn.io/Structured_clone_algorithm) + * and supports cloning arrays, array buffers, booleans, date objects, maps, + * numbers, `Object` objects, regexes, sets, strings, symbols, and typed + * arrays. The own enumerable properties of `arguments` objects are cloned + * as plain objects. An empty object is returned for uncloneable values such + * as error objects, functions, DOM nodes, and WeakMaps. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to clone. + * @returns {*} Returns the cloned value. + * @see _.cloneDeep + * @example + * + * var objects = [{ 'a': 1 }, { 'b': 2 }]; + * + * var shallow = _.clone(objects); + * console.log(shallow[0] === objects[0]); + * // => true + */ + function clone(value) { + return baseClone(value, CLONE_SYMBOLS_FLAG); + } + + /** + * This method is like `_.clone` except that it accepts `customizer` which + * is invoked to produce the cloned value. If `customizer` returns `undefined`, + * cloning is handled by the method instead. The `customizer` is invoked with + * up to four arguments; (value [, index|key, object, stack]). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to clone. + * @param {Function} [customizer] The function to customize cloning. + * @returns {*} Returns the cloned value. + * @see _.cloneDeepWith + * @example + * + * function customizer(value) { + * if (_.isElement(value)) { + * return value.cloneNode(false); + * } + * } + * + * var el = _.cloneWith(document.body, customizer); + * + * console.log(el === document.body); + * // => false + * console.log(el.nodeName); + * // => 'BODY' + * console.log(el.childNodes.length); + * // => 0 + */ + function cloneWith(value, customizer) { + customizer = typeof customizer == 'function' ? customizer : undefined; + return baseClone(value, CLONE_SYMBOLS_FLAG, customizer); + } + + /** + * This method is like `_.clone` except that it recursively clones `value`. + * + * @static + * @memberOf _ + * @since 1.0.0 + * @category Lang + * @param {*} value The value to recursively clone. + * @returns {*} Returns the deep cloned value. + * @see _.clone + * @example + * + * var objects = [{ 'a': 1 }, { 'b': 2 }]; + * + * var deep = _.cloneDeep(objects); + * console.log(deep[0] === objects[0]); + * // => false + */ + function cloneDeep(value) { + return baseClone(value, CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG); + } + + /** + * This method is like `_.cloneWith` except that it recursively clones `value`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to recursively clone. + * @param {Function} [customizer] The function to customize cloning. + * @returns {*} Returns the deep cloned value. + * @see _.cloneWith + * @example + * + * function customizer(value) { + * if (_.isElement(value)) { + * return value.cloneNode(true); + * } + * } + * + * var el = _.cloneDeepWith(document.body, customizer); + * + * console.log(el === document.body); + * // => false + * console.log(el.nodeName); + * // => 'BODY' + * console.log(el.childNodes.length); + * // => 20 + */ + function cloneDeepWith(value, customizer) { + customizer = typeof customizer == 'function' ? customizer : undefined; + return baseClone(value, CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG, customizer); + } + + /** + * Checks if `object` conforms to `source` by invoking the predicate + * properties of `source` with the corresponding property values of `object`. + * + * **Note:** This method is equivalent to `_.conforms` when `source` is + * partially applied. + * + * @static + * @memberOf _ + * @since 4.14.0 + * @category Lang + * @param {Object} object The object to inspect. + * @param {Object} source The object of property predicates to conform to. + * @returns {boolean} Returns `true` if `object` conforms, else `false`. + * @example + * + * var object = { 'a': 1, 'b': 2 }; + * + * _.conformsTo(object, { 'b': function(n) { return n > 1; } }); + * // => true + * + * _.conformsTo(object, { 'b': function(n) { return n > 2; } }); + * // => false + */ + function conformsTo(object, source) { + return source == null || baseConformsTo(object, source, keys(source)); + } + + /** + * Performs a + * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) + * comparison between two values to determine if they are equivalent. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {boolean} Returns `true` if the values are equivalent, else `false`. + * @example + * + * var object = { 'a': 1 }; + * var other = { 'a': 1 }; + * + * _.eq(object, object); + * // => true + * + * _.eq(object, other); + * // => false + * + * _.eq('a', 'a'); + * // => true + * + * _.eq('a', Object('a')); + * // => false + * + * _.eq(NaN, NaN); + * // => true + */ + function eq(value, other) { + return value === other || value !== value && other !== other; + } + + /** + * Checks if `value` is greater than `other`. + * + * @static + * @memberOf _ + * @since 3.9.0 + * @category Lang + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {boolean} Returns `true` if `value` is greater than `other`, + * else `false`. + * @see _.lt + * @example + * + * _.gt(3, 1); + * // => true + * + * _.gt(3, 3); + * // => false + * + * _.gt(1, 3); + * // => false + */ + var gt = createRelationalOperation(baseGt); + + /** + * Checks if `value` is greater than or equal to `other`. + * + * @static + * @memberOf _ + * @since 3.9.0 + * @category Lang + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {boolean} Returns `true` if `value` is greater than or equal to + * `other`, else `false`. + * @see _.lte + * @example + * + * _.gte(3, 1); + * // => true + * + * _.gte(3, 3); + * // => true + * + * _.gte(1, 3); + * // => false + */ + var gte = createRelationalOperation(function (value, other) { + return value >= other; + }); + + /** + * Checks if `value` is likely an `arguments` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an `arguments` object, + * else `false`. + * @example + * + * _.isArguments(function() { return arguments; }()); + * // => true + * + * _.isArguments([1, 2, 3]); + * // => false + */ + var isArguments = baseIsArguments(function () { + return arguments; + }()) ? baseIsArguments : function (value) { + return isObjectLike(value) && hasOwnProperty.call(value, 'callee') && !propertyIsEnumerable.call(value, 'callee'); + }; + + /** + * Checks if `value` is classified as an `Array` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an array, else `false`. + * @example + * + * _.isArray([1, 2, 3]); + * // => true + * + * _.isArray(document.body.children); + * // => false + * + * _.isArray('abc'); + * // => false + * + * _.isArray(_.noop); + * // => false + */ + var isArray = Array.isArray; + + /** + * Checks if `value` is classified as an `ArrayBuffer` object. + * + * @static + * @memberOf _ + * @since 4.3.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an array buffer, else `false`. + * @example + * + * _.isArrayBuffer(new ArrayBuffer(2)); + * // => true + * + * _.isArrayBuffer(new Array(2)); + * // => false + */ + var isArrayBuffer = nodeIsArrayBuffer ? baseUnary(nodeIsArrayBuffer) : baseIsArrayBuffer; + + /** + * Checks if `value` is array-like. A value is considered array-like if it's + * not a function and has a `value.length` that's an integer greater than or + * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is array-like, else `false`. + * @example + * + * _.isArrayLike([1, 2, 3]); + * // => true + * + * _.isArrayLike(document.body.children); + * // => true + * + * _.isArrayLike('abc'); + * // => true + * + * _.isArrayLike(_.noop); + * // => false + */ + function isArrayLike(value) { + return value != null && isLength(value.length) && !isFunction(value); + } + + /** + * This method is like `_.isArrayLike` except that it also checks if `value` + * is an object. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an array-like object, + * else `false`. + * @example + * + * _.isArrayLikeObject([1, 2, 3]); + * // => true + * + * _.isArrayLikeObject(document.body.children); + * // => true + * + * _.isArrayLikeObject('abc'); + * // => false + * + * _.isArrayLikeObject(_.noop); + * // => false + */ + function isArrayLikeObject(value) { + return isObjectLike(value) && isArrayLike(value); + } + + /** + * Checks if `value` is classified as a boolean primitive or object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a boolean, else `false`. + * @example + * + * _.isBoolean(false); + * // => true + * + * _.isBoolean(null); + * // => false + */ + function isBoolean(value) { + return value === true || value === false || isObjectLike(value) && baseGetTag(value) == boolTag; + } + + /** + * Checks if `value` is a buffer. + * + * @static + * @memberOf _ + * @since 4.3.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a buffer, else `false`. + * @example + * + * _.isBuffer(new Buffer(2)); + * // => true + * + * _.isBuffer(new Uint8Array(2)); + * // => false + */ + var isBuffer = nativeIsBuffer || stubFalse; + + /** + * Checks if `value` is classified as a `Date` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a date object, else `false`. + * @example + * + * _.isDate(new Date); + * // => true + * + * _.isDate('Mon April 23 2012'); + * // => false + */ + var isDate = nodeIsDate ? baseUnary(nodeIsDate) : baseIsDate; + + /** + * Checks if `value` is likely a DOM element. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a DOM element, else `false`. + * @example + * + * _.isElement(document.body); + * // => true + * + * _.isElement(''); + * // => false + */ + function isElement(value) { + return isObjectLike(value) && value.nodeType === 1 && !isPlainObject(value); + } + + /** + * Checks if `value` is an empty object, collection, map, or set. + * + * Objects are considered empty if they have no own enumerable string keyed + * properties. + * + * Array-like values such as `arguments` objects, arrays, buffers, strings, or + * jQuery-like collections are considered empty if they have a `length` of `0`. + * Similarly, maps and sets are considered empty if they have a `size` of `0`. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is empty, else `false`. + * @example + * + * _.isEmpty(null); + * // => true + * + * _.isEmpty(true); + * // => true + * + * _.isEmpty(1); + * // => true + * + * _.isEmpty([1, 2, 3]); + * // => false + * + * _.isEmpty({ 'a': 1 }); + * // => false + */ + function isEmpty(value) { + if (value == null) { + return true; + } + if (isArrayLike(value) && (isArray(value) || typeof value == 'string' || typeof value.splice == 'function' || isBuffer(value) || isTypedArray(value) || isArguments(value))) { + return !value.length; + } + var tag = getTag(value); + if (tag == mapTag || tag == setTag) { + return !value.size; + } + if (isPrototype(value)) { + return !baseKeys(value).length; + } + for (var key in value) { + if (hasOwnProperty.call(value, key)) { + return false; + } + } + return true; + } + + /** + * Performs a deep comparison between two values to determine if they are + * equivalent. + * + * **Note:** This method supports comparing arrays, array buffers, booleans, + * date objects, error objects, maps, numbers, `Object` objects, regexes, + * sets, strings, symbols, and typed arrays. `Object` objects are compared + * by their own, not inherited, enumerable properties. Functions and DOM + * nodes are compared by strict equality, i.e. `===`. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {boolean} Returns `true` if the values are equivalent, else `false`. + * @example + * + * var object = { 'a': 1 }; + * var other = { 'a': 1 }; + * + * _.isEqual(object, other); + * // => true + * + * object === other; + * // => false + */ + function isEqual(value, other) { + return baseIsEqual(value, other); + } + + /** + * This method is like `_.isEqual` except that it accepts `customizer` which + * is invoked to compare values. If `customizer` returns `undefined`, comparisons + * are handled by the method instead. The `customizer` is invoked with up to + * six arguments: (objValue, othValue [, index|key, object, other, stack]). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @param {Function} [customizer] The function to customize comparisons. + * @returns {boolean} Returns `true` if the values are equivalent, else `false`. + * @example + * + * function isGreeting(value) { + * return /^h(?:i|ello)$/.test(value); + * } + * + * function customizer(objValue, othValue) { + * if (isGreeting(objValue) && isGreeting(othValue)) { + * return true; + * } + * } + * + * var array = ['hello', 'goodbye']; + * var other = ['hi', 'goodbye']; + * + * _.isEqualWith(array, other, customizer); + * // => true + */ + function isEqualWith(value, other, customizer) { + customizer = typeof customizer == 'function' ? customizer : undefined; + var result = customizer ? customizer(value, other) : undefined; + return result === undefined ? baseIsEqual(value, other, undefined, customizer) : !!result; + } + + /** + * Checks if `value` is an `Error`, `EvalError`, `RangeError`, `ReferenceError`, + * `SyntaxError`, `TypeError`, or `URIError` object. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an error object, else `false`. + * @example + * + * _.isError(new Error); + * // => true + * + * _.isError(Error); + * // => false + */ + function isError(value) { + if (!isObjectLike(value)) { + return false; + } + var tag = baseGetTag(value); + return tag == errorTag || tag == domExcTag || typeof value.message == 'string' && typeof value.name == 'string' && !isPlainObject(value); + } + + /** + * Checks if `value` is a finite primitive number. + * + * **Note:** This method is based on + * [`Number.isFinite`](https://mdn.io/Number/isFinite). + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a finite number, else `false`. + * @example + * + * _.isFinite(3); + * // => true + * + * _.isFinite(Number.MIN_VALUE); + * // => true + * + * _.isFinite(Infinity); + * // => false + * + * _.isFinite('3'); + * // => false + */ + function isFinite(value) { + return typeof value == 'number' && nativeIsFinite(value); + } + + /** + * Checks if `value` is classified as a `Function` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a function, else `false`. + * @example + * + * _.isFunction(_); + * // => true + * + * _.isFunction(/abc/); + * // => false + */ + function isFunction(value) { + if (!isObject(value)) { + return false; + } + // The use of `Object#toString` avoids issues with the `typeof` operator + // in Safari 9 which returns 'object' for typed arrays and other constructors. + var tag = baseGetTag(value); + return tag == funcTag || tag == genTag || tag == asyncTag || tag == proxyTag; + } + + /** + * Checks if `value` is an integer. + * + * **Note:** This method is based on + * [`Number.isInteger`](https://mdn.io/Number/isInteger). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an integer, else `false`. + * @example + * + * _.isInteger(3); + * // => true + * + * _.isInteger(Number.MIN_VALUE); + * // => false + * + * _.isInteger(Infinity); + * // => false + * + * _.isInteger('3'); + * // => false + */ + function isInteger(value) { + return typeof value == 'number' && value == toInteger(value); + } + + /** + * Checks if `value` is a valid array-like length. + * + * **Note:** This method is loosely based on + * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. + * @example + * + * _.isLength(3); + * // => true + * + * _.isLength(Number.MIN_VALUE); + * // => false + * + * _.isLength(Infinity); + * // => false + * + * _.isLength('3'); + * // => false + */ + function isLength(value) { + return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; + } + + /** + * Checks if `value` is the + * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types) + * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an object, else `false`. + * @example + * + * _.isObject({}); + * // => true + * + * _.isObject([1, 2, 3]); + * // => true + * + * _.isObject(_.noop); + * // => true + * + * _.isObject(null); + * // => false + */ + function isObject(value) { + var type = typeof value; + return value != null && (type == 'object' || type == 'function'); + } + + /** + * Checks if `value` is object-like. A value is object-like if it's not `null` + * and has a `typeof` result of "object". + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is object-like, else `false`. + * @example + * + * _.isObjectLike({}); + * // => true + * + * _.isObjectLike([1, 2, 3]); + * // => true + * + * _.isObjectLike(_.noop); + * // => false + * + * _.isObjectLike(null); + * // => false + */ + function isObjectLike(value) { + return value != null && typeof value == 'object'; + } + + /** + * Checks if `value` is classified as a `Map` object. + * + * @static + * @memberOf _ + * @since 4.3.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a map, else `false`. + * @example + * + * _.isMap(new Map); + * // => true + * + * _.isMap(new WeakMap); + * // => false + */ + var isMap = nodeIsMap ? baseUnary(nodeIsMap) : baseIsMap; + + /** + * Performs a partial deep comparison between `object` and `source` to + * determine if `object` contains equivalent property values. + * + * **Note:** This method is equivalent to `_.matches` when `source` is + * partially applied. + * + * Partial comparisons will match empty array and empty object `source` + * values against any array or object value, respectively. See `_.isEqual` + * for a list of supported value comparisons. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Lang + * @param {Object} object The object to inspect. + * @param {Object} source The object of property values to match. + * @returns {boolean} Returns `true` if `object` is a match, else `false`. + * @example + * + * var object = { 'a': 1, 'b': 2 }; + * + * _.isMatch(object, { 'b': 2 }); + * // => true + * + * _.isMatch(object, { 'b': 1 }); + * // => false + */ + function isMatch(object, source) { + return object === source || baseIsMatch(object, source, getMatchData(source)); + } + + /** + * This method is like `_.isMatch` except that it accepts `customizer` which + * is invoked to compare values. If `customizer` returns `undefined`, comparisons + * are handled by the method instead. The `customizer` is invoked with five + * arguments: (objValue, srcValue, index|key, object, source). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {Object} object The object to inspect. + * @param {Object} source The object of property values to match. + * @param {Function} [customizer] The function to customize comparisons. + * @returns {boolean} Returns `true` if `object` is a match, else `false`. + * @example + * + * function isGreeting(value) { + * return /^h(?:i|ello)$/.test(value); + * } + * + * function customizer(objValue, srcValue) { + * if (isGreeting(objValue) && isGreeting(srcValue)) { + * return true; + * } + * } + * + * var object = { 'greeting': 'hello' }; + * var source = { 'greeting': 'hi' }; + * + * _.isMatchWith(object, source, customizer); + * // => true + */ + function isMatchWith(object, source, customizer) { + customizer = typeof customizer == 'function' ? customizer : undefined; + return baseIsMatch(object, source, getMatchData(source), customizer); + } + + /** + * Checks if `value` is `NaN`. + * + * **Note:** This method is based on + * [`Number.isNaN`](https://mdn.io/Number/isNaN) and is not the same as + * global [`isNaN`](https://mdn.io/isNaN) which returns `true` for + * `undefined` and other non-number values. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is `NaN`, else `false`. + * @example + * + * _.isNaN(NaN); + * // => true + * + * _.isNaN(new Number(NaN)); + * // => true + * + * isNaN(undefined); + * // => true + * + * _.isNaN(undefined); + * // => false + */ + function isNaN(value) { + // An `NaN` primitive is the only value that is not equal to itself. + // Perform the `toStringTag` check first to avoid errors with some + // ActiveX objects in IE. + return isNumber(value) && value != +value; + } + + /** + * Checks if `value` is a pristine native function. + * + * **Note:** This method can't reliably detect native functions in the presence + * of the core-js package because core-js circumvents this kind of detection. + * Despite multiple requests, the core-js maintainer has made it clear: any + * attempt to fix the detection will be obstructed. As a result, we're left + * with little choice but to throw an error. Unfortunately, this also affects + * packages, like [babel-polyfill](https://www.npmjs.com/package/babel-polyfill), + * which rely on core-js. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a native function, + * else `false`. + * @example + * + * _.isNative(Array.prototype.push); + * // => true + * + * _.isNative(_); + * // => false + */ + function isNative(value) { + if (isMaskable(value)) { + throw new Error(CORE_ERROR_TEXT); + } + return baseIsNative(value); + } + + /** + * Checks if `value` is `null`. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is `null`, else `false`. + * @example + * + * _.isNull(null); + * // => true + * + * _.isNull(void 0); + * // => false + */ + function isNull(value) { + return value === null; + } + + /** + * Checks if `value` is `null` or `undefined`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is nullish, else `false`. + * @example + * + * _.isNil(null); + * // => true + * + * _.isNil(void 0); + * // => true + * + * _.isNil(NaN); + * // => false + */ + function isNil(value) { + return value == null; + } + + /** + * Checks if `value` is classified as a `Number` primitive or object. + * + * **Note:** To exclude `Infinity`, `-Infinity`, and `NaN`, which are + * classified as numbers, use the `_.isFinite` method. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a number, else `false`. + * @example + * + * _.isNumber(3); + * // => true + * + * _.isNumber(Number.MIN_VALUE); + * // => true + * + * _.isNumber(Infinity); + * // => true + * + * _.isNumber('3'); + * // => false + */ + function isNumber(value) { + return typeof value == 'number' || isObjectLike(value) && baseGetTag(value) == numberTag; + } + + /** + * Checks if `value` is a plain object, that is, an object created by the + * `Object` constructor or one with a `[[Prototype]]` of `null`. + * + * @static + * @memberOf _ + * @since 0.8.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a plain object, else `false`. + * @example + * + * function Foo() { + * this.a = 1; + * } + * + * _.isPlainObject(new Foo); + * // => false + * + * _.isPlainObject([1, 2, 3]); + * // => false + * + * _.isPlainObject({ 'x': 0, 'y': 0 }); + * // => true + * + * _.isPlainObject(Object.create(null)); + * // => true + */ + function isPlainObject(value) { + if (!isObjectLike(value) || baseGetTag(value) != objectTag) { + return false; + } + var proto = getPrototype(value); + if (proto === null) { + return true; + } + var Ctor = hasOwnProperty.call(proto, 'constructor') && proto.constructor; + return typeof Ctor == 'function' && Ctor instanceof Ctor && funcToString.call(Ctor) == objectCtorString; + } + + /** + * Checks if `value` is classified as a `RegExp` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a regexp, else `false`. + * @example + * + * _.isRegExp(/abc/); + * // => true + * + * _.isRegExp('/abc/'); + * // => false + */ + var isRegExp = nodeIsRegExp ? baseUnary(nodeIsRegExp) : baseIsRegExp; + + /** + * Checks if `value` is a safe integer. An integer is safe if it's an IEEE-754 + * double precision number which isn't the result of a rounded unsafe integer. + * + * **Note:** This method is based on + * [`Number.isSafeInteger`](https://mdn.io/Number/isSafeInteger). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a safe integer, else `false`. + * @example + * + * _.isSafeInteger(3); + * // => true + * + * _.isSafeInteger(Number.MIN_VALUE); + * // => false + * + * _.isSafeInteger(Infinity); + * // => false + * + * _.isSafeInteger('3'); + * // => false + */ + function isSafeInteger(value) { + return isInteger(value) && value >= -MAX_SAFE_INTEGER && value <= MAX_SAFE_INTEGER; + } + + /** + * Checks if `value` is classified as a `Set` object. + * + * @static + * @memberOf _ + * @since 4.3.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a set, else `false`. + * @example + * + * _.isSet(new Set); + * // => true + * + * _.isSet(new WeakSet); + * // => false + */ + var isSet = nodeIsSet ? baseUnary(nodeIsSet) : baseIsSet; + + /** + * Checks if `value` is classified as a `String` primitive or object. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a string, else `false`. + * @example + * + * _.isString('abc'); + * // => true + * + * _.isString(1); + * // => false + */ + function isString(value) { + return typeof value == 'string' || !isArray(value) && isObjectLike(value) && baseGetTag(value) == stringTag; + } + + /** + * Checks if `value` is classified as a `Symbol` primitive or object. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a symbol, else `false`. + * @example + * + * _.isSymbol(Symbol.iterator); + * // => true + * + * _.isSymbol('abc'); + * // => false + */ + function isSymbol(value) { + return typeof value == 'symbol' || isObjectLike(value) && baseGetTag(value) == symbolTag; + } + + /** + * Checks if `value` is classified as a typed array. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a typed array, else `false`. + * @example + * + * _.isTypedArray(new Uint8Array); + * // => true + * + * _.isTypedArray([]); + * // => false + */ + var isTypedArray = nodeIsTypedArray ? baseUnary(nodeIsTypedArray) : baseIsTypedArray; + + /** + * Checks if `value` is `undefined`. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is `undefined`, else `false`. + * @example + * + * _.isUndefined(void 0); + * // => true + * + * _.isUndefined(null); + * // => false + */ + function isUndefined(value) { + return value === undefined; + } + + /** + * Checks if `value` is classified as a `WeakMap` object. + * + * @static + * @memberOf _ + * @since 4.3.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a weak map, else `false`. + * @example + * + * _.isWeakMap(new WeakMap); + * // => true + * + * _.isWeakMap(new Map); + * // => false + */ + function isWeakMap(value) { + return isObjectLike(value) && getTag(value) == weakMapTag; + } + + /** + * Checks if `value` is classified as a `WeakSet` object. + * + * @static + * @memberOf _ + * @since 4.3.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a weak set, else `false`. + * @example + * + * _.isWeakSet(new WeakSet); + * // => true + * + * _.isWeakSet(new Set); + * // => false + */ + function isWeakSet(value) { + return isObjectLike(value) && baseGetTag(value) == weakSetTag; + } + + /** + * Checks if `value` is less than `other`. + * + * @static + * @memberOf _ + * @since 3.9.0 + * @category Lang + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {boolean} Returns `true` if `value` is less than `other`, + * else `false`. + * @see _.gt + * @example + * + * _.lt(1, 3); + * // => true + * + * _.lt(3, 3); + * // => false + * + * _.lt(3, 1); + * // => false + */ + var lt = createRelationalOperation(baseLt); + + /** + * Checks if `value` is less than or equal to `other`. + * + * @static + * @memberOf _ + * @since 3.9.0 + * @category Lang + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {boolean} Returns `true` if `value` is less than or equal to + * `other`, else `false`. + * @see _.gte + * @example + * + * _.lte(1, 3); + * // => true + * + * _.lte(3, 3); + * // => true + * + * _.lte(3, 1); + * // => false + */ + var lte = createRelationalOperation(function (value, other) { + return value <= other; + }); + + /** + * Converts `value` to an array. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Lang + * @param {*} value The value to convert. + * @returns {Array} Returns the converted array. + * @example + * + * _.toArray({ 'a': 1, 'b': 2 }); + * // => [1, 2] + * + * _.toArray('abc'); + * // => ['a', 'b', 'c'] + * + * _.toArray(1); + * // => [] + * + * _.toArray(null); + * // => [] + */ + function toArray(value) { + if (!value) { + return []; + } + if (isArrayLike(value)) { + return isString(value) ? stringToArray(value) : copyArray(value); + } + if (symIterator && value[symIterator]) { + return iteratorToArray(value[symIterator]()); + } + var tag = getTag(value), + func = tag == mapTag ? mapToArray : tag == setTag ? setToArray : values; + return func(value); + } + + /** + * Converts `value` to a finite number. + * + * @static + * @memberOf _ + * @since 4.12.0 + * @category Lang + * @param {*} value The value to convert. + * @returns {number} Returns the converted number. + * @example + * + * _.toFinite(3.2); + * // => 3.2 + * + * _.toFinite(Number.MIN_VALUE); + * // => 5e-324 + * + * _.toFinite(Infinity); + * // => 1.7976931348623157e+308 + * + * _.toFinite('3.2'); + * // => 3.2 + */ + function toFinite(value) { + if (!value) { + return value === 0 ? value : 0; + } + value = toNumber(value); + if (value === INFINITY || value === -INFINITY) { + var sign = value < 0 ? -1 : 1; + return sign * MAX_INTEGER; + } + return value === value ? value : 0; + } + + /** + * Converts `value` to an integer. + * + * **Note:** This method is loosely based on + * [`ToInteger`](http://www.ecma-international.org/ecma-262/7.0/#sec-tointeger). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to convert. + * @returns {number} Returns the converted integer. + * @example + * + * _.toInteger(3.2); + * // => 3 + * + * _.toInteger(Number.MIN_VALUE); + * // => 0 + * + * _.toInteger(Infinity); + * // => 1.7976931348623157e+308 + * + * _.toInteger('3.2'); + * // => 3 + */ + function toInteger(value) { + var result = toFinite(value), + remainder = result % 1; + return result === result ? remainder ? result - remainder : result : 0; + } + + /** + * Converts `value` to an integer suitable for use as the length of an + * array-like object. + * + * **Note:** This method is based on + * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to convert. + * @returns {number} Returns the converted integer. + * @example + * + * _.toLength(3.2); + * // => 3 + * + * _.toLength(Number.MIN_VALUE); + * // => 0 + * + * _.toLength(Infinity); + * // => 4294967295 + * + * _.toLength('3.2'); + * // => 3 + */ + function toLength(value) { + return value ? baseClamp(toInteger(value), 0, MAX_ARRAY_LENGTH) : 0; + } + + /** + * Converts `value` to a number. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to process. + * @returns {number} Returns the number. + * @example + * + * _.toNumber(3.2); + * // => 3.2 + * + * _.toNumber(Number.MIN_VALUE); + * // => 5e-324 + * + * _.toNumber(Infinity); + * // => Infinity + * + * _.toNumber('3.2'); + * // => 3.2 + */ + function toNumber(value) { + if (typeof value == 'number') { + return value; + } + if (isSymbol(value)) { + return NAN; + } + if (isObject(value)) { + var other = typeof value.valueOf == 'function' ? value.valueOf() : value; + value = isObject(other) ? other + '' : other; + } + if (typeof value != 'string') { + return value === 0 ? value : +value; + } + value = baseTrim(value); + var isBinary = reIsBinary.test(value); + return isBinary || reIsOctal.test(value) ? freeParseInt(value.slice(2), isBinary ? 2 : 8) : reIsBadHex.test(value) ? NAN : +value; + } + + /** + * Converts `value` to a plain object flattening inherited enumerable string + * keyed properties of `value` to own properties of the plain object. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Lang + * @param {*} value The value to convert. + * @returns {Object} Returns the converted plain object. + * @example + * + * function Foo() { + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.assign({ 'a': 1 }, new Foo); + * // => { 'a': 1, 'b': 2 } + * + * _.assign({ 'a': 1 }, _.toPlainObject(new Foo)); + * // => { 'a': 1, 'b': 2, 'c': 3 } + */ + function toPlainObject(value) { + return copyObject(value, keysIn(value)); + } + + /** + * Converts `value` to a safe integer. A safe integer can be compared and + * represented correctly. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to convert. + * @returns {number} Returns the converted integer. + * @example + * + * _.toSafeInteger(3.2); + * // => 3 + * + * _.toSafeInteger(Number.MIN_VALUE); + * // => 0 + * + * _.toSafeInteger(Infinity); + * // => 9007199254740991 + * + * _.toSafeInteger('3.2'); + * // => 3 + */ + function toSafeInteger(value) { + return value ? baseClamp(toInteger(value), -MAX_SAFE_INTEGER, MAX_SAFE_INTEGER) : value === 0 ? value : 0; + } + + /** + * Converts `value` to a string. An empty string is returned for `null` + * and `undefined` values. The sign of `-0` is preserved. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to convert. + * @returns {string} Returns the converted string. + * @example + * + * _.toString(null); + * // => '' + * + * _.toString(-0); + * // => '-0' + * + * _.toString([1, 2, 3]); + * // => '1,2,3' + */ + function toString(value) { + return value == null ? '' : baseToString(value); + } + + /*------------------------------------------------------------------------*/ + + /** + * Assigns own enumerable string keyed properties of source objects to the + * destination object. Source objects are applied from left to right. + * Subsequent sources overwrite property assignments of previous sources. + * + * **Note:** This method mutates `object` and is loosely based on + * [`Object.assign`](https://mdn.io/Object/assign). + * + * @static + * @memberOf _ + * @since 0.10.0 + * @category Object + * @param {Object} object The destination object. + * @param {...Object} [sources] The source objects. + * @returns {Object} Returns `object`. + * @see _.assignIn + * @example + * + * function Foo() { + * this.a = 1; + * } + * + * function Bar() { + * this.c = 3; + * } + * + * Foo.prototype.b = 2; + * Bar.prototype.d = 4; + * + * _.assign({ 'a': 0 }, new Foo, new Bar); + * // => { 'a': 1, 'c': 3 } + */ + var assign = createAssigner(function (object, source) { + if (isPrototype(source) || isArrayLike(source)) { + copyObject(source, keys(source), object); + return; + } + for (var key in source) { + if (hasOwnProperty.call(source, key)) { + assignValue(object, key, source[key]); + } + } + }); + + /** + * This method is like `_.assign` except that it iterates over own and + * inherited source properties. + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @alias extend + * @category Object + * @param {Object} object The destination object. + * @param {...Object} [sources] The source objects. + * @returns {Object} Returns `object`. + * @see _.assign + * @example + * + * function Foo() { + * this.a = 1; + * } + * + * function Bar() { + * this.c = 3; + * } + * + * Foo.prototype.b = 2; + * Bar.prototype.d = 4; + * + * _.assignIn({ 'a': 0 }, new Foo, new Bar); + * // => { 'a': 1, 'b': 2, 'c': 3, 'd': 4 } + */ + var assignIn = createAssigner(function (object, source) { + copyObject(source, keysIn(source), object); + }); + + /** + * This method is like `_.assignIn` except that it accepts `customizer` + * which is invoked to produce the assigned values. If `customizer` returns + * `undefined`, assignment is handled by the method instead. The `customizer` + * is invoked with five arguments: (objValue, srcValue, key, object, source). + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @alias extendWith + * @category Object + * @param {Object} object The destination object. + * @param {...Object} sources The source objects. + * @param {Function} [customizer] The function to customize assigned values. + * @returns {Object} Returns `object`. + * @see _.assignWith + * @example + * + * function customizer(objValue, srcValue) { + * return _.isUndefined(objValue) ? srcValue : objValue; + * } + * + * var defaults = _.partialRight(_.assignInWith, customizer); + * + * defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 }); + * // => { 'a': 1, 'b': 2 } + */ + var assignInWith = createAssigner(function (object, source, srcIndex, customizer) { + copyObject(source, keysIn(source), object, customizer); + }); + + /** + * This method is like `_.assign` except that it accepts `customizer` + * which is invoked to produce the assigned values. If `customizer` returns + * `undefined`, assignment is handled by the method instead. The `customizer` + * is invoked with five arguments: (objValue, srcValue, key, object, source). + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Object + * @param {Object} object The destination object. + * @param {...Object} sources The source objects. + * @param {Function} [customizer] The function to customize assigned values. + * @returns {Object} Returns `object`. + * @see _.assignInWith + * @example + * + * function customizer(objValue, srcValue) { + * return _.isUndefined(objValue) ? srcValue : objValue; + * } + * + * var defaults = _.partialRight(_.assignWith, customizer); + * + * defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 }); + * // => { 'a': 1, 'b': 2 } + */ + var assignWith = createAssigner(function (object, source, srcIndex, customizer) { + copyObject(source, keys(source), object, customizer); + }); + + /** + * Creates an array of values corresponding to `paths` of `object`. + * + * @static + * @memberOf _ + * @since 1.0.0 + * @category Object + * @param {Object} object The object to iterate over. + * @param {...(string|string[])} [paths] The property paths to pick. + * @returns {Array} Returns the picked values. + * @example + * + * var object = { 'a': [{ 'b': { 'c': 3 } }, 4] }; + * + * _.at(object, ['a[0].b.c', 'a[1]']); + * // => [3, 4] + */ + var at = flatRest(baseAt); + + /** + * Creates an object that inherits from the `prototype` object. If a + * `properties` object is given, its own enumerable string keyed properties + * are assigned to the created object. + * + * @static + * @memberOf _ + * @since 2.3.0 + * @category Object + * @param {Object} prototype The object to inherit from. + * @param {Object} [properties] The properties to assign to the object. + * @returns {Object} Returns the new object. + * @example + * + * function Shape() { + * this.x = 0; + * this.y = 0; + * } + * + * function Circle() { + * Shape.call(this); + * } + * + * Circle.prototype = _.create(Shape.prototype, { + * 'constructor': Circle + * }); + * + * var circle = new Circle; + * circle instanceof Circle; + * // => true + * + * circle instanceof Shape; + * // => true + */ + function create(prototype, properties) { + var result = baseCreate(prototype); + return properties == null ? result : baseAssign(result, properties); + } + + /** + * Assigns own and inherited enumerable string keyed properties of source + * objects to the destination object for all destination properties that + * resolve to `undefined`. Source objects are applied from left to right. + * Once a property is set, additional values of the same property are ignored. + * + * **Note:** This method mutates `object`. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Object + * @param {Object} object The destination object. + * @param {...Object} [sources] The source objects. + * @returns {Object} Returns `object`. + * @see _.defaultsDeep + * @example + * + * _.defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 }); + * // => { 'a': 1, 'b': 2 } + */ + var defaults = baseRest(function (object, sources) { + object = Object(object); + var index = -1; + var length = sources.length; + var guard = length > 2 ? sources[2] : undefined; + if (guard && isIterateeCall(sources[0], sources[1], guard)) { + length = 1; + } + while (++index < length) { + var source = sources[index]; + var props = keysIn(source); + var propsIndex = -1; + var propsLength = props.length; + while (++propsIndex < propsLength) { + var key = props[propsIndex]; + var value = object[key]; + if (value === undefined || eq(value, objectProto[key]) && !hasOwnProperty.call(object, key)) { + object[key] = source[key]; + } + } + } + return object; + }); + + /** + * This method is like `_.defaults` except that it recursively assigns + * default properties. + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 3.10.0 + * @category Object + * @param {Object} object The destination object. + * @param {...Object} [sources] The source objects. + * @returns {Object} Returns `object`. + * @see _.defaults + * @example + * + * _.defaultsDeep({ 'a': { 'b': 2 } }, { 'a': { 'b': 1, 'c': 3 } }); + * // => { 'a': { 'b': 2, 'c': 3 } } + */ + var defaultsDeep = baseRest(function (args) { + args.push(undefined, customDefaultsMerge); + return apply(mergeWith, undefined, args); + }); + + /** + * This method is like `_.find` except that it returns the key of the first + * element `predicate` returns truthy for instead of the element itself. + * + * @static + * @memberOf _ + * @since 1.1.0 + * @category Object + * @param {Object} object The object to inspect. + * @param {Function} [predicate=_.identity] The function invoked per iteration. + * @returns {string|undefined} Returns the key of the matched element, + * else `undefined`. + * @example + * + * var users = { + * 'barney': { 'age': 36, 'active': true }, + * 'fred': { 'age': 40, 'active': false }, + * 'pebbles': { 'age': 1, 'active': true } + * }; + * + * _.findKey(users, function(o) { return o.age < 40; }); + * // => 'barney' (iteration order is not guaranteed) + * + * // The `_.matches` iteratee shorthand. + * _.findKey(users, { 'age': 1, 'active': true }); + * // => 'pebbles' + * + * // The `_.matchesProperty` iteratee shorthand. + * _.findKey(users, ['active', false]); + * // => 'fred' + * + * // The `_.property` iteratee shorthand. + * _.findKey(users, 'active'); + * // => 'barney' + */ + function findKey(object, predicate) { + return baseFindKey(object, getIteratee(predicate, 3), baseForOwn); + } + + /** + * This method is like `_.findKey` except that it iterates over elements of + * a collection in the opposite order. + * + * @static + * @memberOf _ + * @since 2.0.0 + * @category Object + * @param {Object} object The object to inspect. + * @param {Function} [predicate=_.identity] The function invoked per iteration. + * @returns {string|undefined} Returns the key of the matched element, + * else `undefined`. + * @example + * + * var users = { + * 'barney': { 'age': 36, 'active': true }, + * 'fred': { 'age': 40, 'active': false }, + * 'pebbles': { 'age': 1, 'active': true } + * }; + * + * _.findLastKey(users, function(o) { return o.age < 40; }); + * // => returns 'pebbles' assuming `_.findKey` returns 'barney' + * + * // The `_.matches` iteratee shorthand. + * _.findLastKey(users, { 'age': 36, 'active': true }); + * // => 'barney' + * + * // The `_.matchesProperty` iteratee shorthand. + * _.findLastKey(users, ['active', false]); + * // => 'fred' + * + * // The `_.property` iteratee shorthand. + * _.findLastKey(users, 'active'); + * // => 'pebbles' + */ + function findLastKey(object, predicate) { + return baseFindKey(object, getIteratee(predicate, 3), baseForOwnRight); + } + + /** + * Iterates over own and inherited enumerable string keyed properties of an + * object and invokes `iteratee` for each property. The iteratee is invoked + * with three arguments: (value, key, object). Iteratee functions may exit + * iteration early by explicitly returning `false`. + * + * @static + * @memberOf _ + * @since 0.3.0 + * @category Object + * @param {Object} object The object to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @returns {Object} Returns `object`. + * @see _.forInRight + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.forIn(new Foo, function(value, key) { + * console.log(key); + * }); + * // => Logs 'a', 'b', then 'c' (iteration order is not guaranteed). + */ + function forIn(object, iteratee) { + return object == null ? object : baseFor(object, getIteratee(iteratee, 3), keysIn); + } + + /** + * This method is like `_.forIn` except that it iterates over properties of + * `object` in the opposite order. + * + * @static + * @memberOf _ + * @since 2.0.0 + * @category Object + * @param {Object} object The object to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @returns {Object} Returns `object`. + * @see _.forIn + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.forInRight(new Foo, function(value, key) { + * console.log(key); + * }); + * // => Logs 'c', 'b', then 'a' assuming `_.forIn` logs 'a', 'b', then 'c'. + */ + function forInRight(object, iteratee) { + return object == null ? object : baseForRight(object, getIteratee(iteratee, 3), keysIn); + } + + /** + * Iterates over own enumerable string keyed properties of an object and + * invokes `iteratee` for each property. The iteratee is invoked with three + * arguments: (value, key, object). Iteratee functions may exit iteration + * early by explicitly returning `false`. + * + * @static + * @memberOf _ + * @since 0.3.0 + * @category Object + * @param {Object} object The object to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @returns {Object} Returns `object`. + * @see _.forOwnRight + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.forOwn(new Foo, function(value, key) { + * console.log(key); + * }); + * // => Logs 'a' then 'b' (iteration order is not guaranteed). + */ + function forOwn(object, iteratee) { + return object && baseForOwn(object, getIteratee(iteratee, 3)); + } + + /** + * This method is like `_.forOwn` except that it iterates over properties of + * `object` in the opposite order. + * + * @static + * @memberOf _ + * @since 2.0.0 + * @category Object + * @param {Object} object The object to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @returns {Object} Returns `object`. + * @see _.forOwn + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.forOwnRight(new Foo, function(value, key) { + * console.log(key); + * }); + * // => Logs 'b' then 'a' assuming `_.forOwn` logs 'a' then 'b'. + */ + function forOwnRight(object, iteratee) { + return object && baseForOwnRight(object, getIteratee(iteratee, 3)); + } + + /** + * Creates an array of function property names from own enumerable properties + * of `object`. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Object + * @param {Object} object The object to inspect. + * @returns {Array} Returns the function names. + * @see _.functionsIn + * @example + * + * function Foo() { + * this.a = _.constant('a'); + * this.b = _.constant('b'); + * } + * + * Foo.prototype.c = _.constant('c'); + * + * _.functions(new Foo); + * // => ['a', 'b'] + */ + function functions(object) { + return object == null ? [] : baseFunctions(object, keys(object)); + } + + /** + * Creates an array of function property names from own and inherited + * enumerable properties of `object`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Object + * @param {Object} object The object to inspect. + * @returns {Array} Returns the function names. + * @see _.functions + * @example + * + * function Foo() { + * this.a = _.constant('a'); + * this.b = _.constant('b'); + * } + * + * Foo.prototype.c = _.constant('c'); + * + * _.functionsIn(new Foo); + * // => ['a', 'b', 'c'] + */ + function functionsIn(object) { + return object == null ? [] : baseFunctions(object, keysIn(object)); + } + + /** + * Gets the value at `path` of `object`. If the resolved value is + * `undefined`, the `defaultValue` is returned in its place. + * + * @static + * @memberOf _ + * @since 3.7.0 + * @category Object + * @param {Object} object The object to query. + * @param {Array|string} path The path of the property to get. + * @param {*} [defaultValue] The value returned for `undefined` resolved values. + * @returns {*} Returns the resolved value. + * @example + * + * var object = { 'a': [{ 'b': { 'c': 3 } }] }; + * + * _.get(object, 'a[0].b.c'); + * // => 3 + * + * _.get(object, ['a', '0', 'b', 'c']); + * // => 3 + * + * _.get(object, 'a.b.c', 'default'); + * // => 'default' + */ + function get(object, path, defaultValue) { + var result = object == null ? undefined : baseGet(object, path); + return result === undefined ? defaultValue : result; + } + + /** + * Checks if `path` is a direct property of `object`. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Object + * @param {Object} object The object to query. + * @param {Array|string} path The path to check. + * @returns {boolean} Returns `true` if `path` exists, else `false`. + * @example + * + * var object = { 'a': { 'b': 2 } }; + * var other = _.create({ 'a': _.create({ 'b': 2 }) }); + * + * _.has(object, 'a'); + * // => true + * + * _.has(object, 'a.b'); + * // => true + * + * _.has(object, ['a', 'b']); + * // => true + * + * _.has(other, 'a'); + * // => false + */ + function has(object, path) { + return object != null && hasPath(object, path, baseHas); + } + + /** + * Checks if `path` is a direct or inherited property of `object`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Object + * @param {Object} object The object to query. + * @param {Array|string} path The path to check. + * @returns {boolean} Returns `true` if `path` exists, else `false`. + * @example + * + * var object = _.create({ 'a': _.create({ 'b': 2 }) }); + * + * _.hasIn(object, 'a'); + * // => true + * + * _.hasIn(object, 'a.b'); + * // => true + * + * _.hasIn(object, ['a', 'b']); + * // => true + * + * _.hasIn(object, 'b'); + * // => false + */ + function hasIn(object, path) { + return object != null && hasPath(object, path, baseHasIn); + } + + /** + * Creates an object composed of the inverted keys and values of `object`. + * If `object` contains duplicate values, subsequent values overwrite + * property assignments of previous values. + * + * @static + * @memberOf _ + * @since 0.7.0 + * @category Object + * @param {Object} object The object to invert. + * @returns {Object} Returns the new inverted object. + * @example + * + * var object = { 'a': 1, 'b': 2, 'c': 1 }; + * + * _.invert(object); + * // => { '1': 'c', '2': 'b' } + */ + var invert = createInverter(function (result, value, key) { + if (value != null && typeof value.toString != 'function') { + value = nativeObjectToString.call(value); + } + result[value] = key; + }, constant(identity)); + + /** + * This method is like `_.invert` except that the inverted object is generated + * from the results of running each element of `object` thru `iteratee`. The + * corresponding inverted value of each inverted key is an array of keys + * responsible for generating the inverted value. The iteratee is invoked + * with one argument: (value). + * + * @static + * @memberOf _ + * @since 4.1.0 + * @category Object + * @param {Object} object The object to invert. + * @param {Function} [iteratee=_.identity] The iteratee invoked per element. + * @returns {Object} Returns the new inverted object. + * @example + * + * var object = { 'a': 1, 'b': 2, 'c': 1 }; + * + * _.invertBy(object); + * // => { '1': ['a', 'c'], '2': ['b'] } + * + * _.invertBy(object, function(value) { + * return 'group' + value; + * }); + * // => { 'group1': ['a', 'c'], 'group2': ['b'] } + */ + var invertBy = createInverter(function (result, value, key) { + if (value != null && typeof value.toString != 'function') { + value = nativeObjectToString.call(value); + } + if (hasOwnProperty.call(result, value)) { + result[value].push(key); + } else { + result[value] = [key]; + } + }, getIteratee); + + /** + * Invokes the method at `path` of `object`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Object + * @param {Object} object The object to query. + * @param {Array|string} path The path of the method to invoke. + * @param {...*} [args] The arguments to invoke the method with. + * @returns {*} Returns the result of the invoked method. + * @example + * + * var object = { 'a': [{ 'b': { 'c': [1, 2, 3, 4] } }] }; + * + * _.invoke(object, 'a[0].b.c.slice', 1, 3); + * // => [2, 3] + */ + var invoke = baseRest(baseInvoke); + + /** + * Creates an array of the own enumerable property names of `object`. + * + * **Note:** Non-object values are coerced to objects. See the + * [ES spec](http://ecma-international.org/ecma-262/7.0/#sec-object.keys) + * for more details. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.keys(new Foo); + * // => ['a', 'b'] (iteration order is not guaranteed) + * + * _.keys('hi'); + * // => ['0', '1'] + */ + function keys(object) { + return isArrayLike(object) ? arrayLikeKeys(object) : baseKeys(object); + } + + /** + * Creates an array of the own and inherited enumerable property names of `object`. + * + * **Note:** Non-object values are coerced to objects. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.keysIn(new Foo); + * // => ['a', 'b', 'c'] (iteration order is not guaranteed) + */ + function keysIn(object) { + return isArrayLike(object) ? arrayLikeKeys(object, true) : baseKeysIn(object); + } + + /** + * The opposite of `_.mapValues`; this method creates an object with the + * same values as `object` and keys generated by running each own enumerable + * string keyed property of `object` thru `iteratee`. The iteratee is invoked + * with three arguments: (value, key, object). + * + * @static + * @memberOf _ + * @since 3.8.0 + * @category Object + * @param {Object} object The object to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @returns {Object} Returns the new mapped object. + * @see _.mapValues + * @example + * + * _.mapKeys({ 'a': 1, 'b': 2 }, function(value, key) { + * return key + value; + * }); + * // => { 'a1': 1, 'b2': 2 } + */ + function mapKeys(object, iteratee) { + var result = {}; + iteratee = getIteratee(iteratee, 3); + baseForOwn(object, function (value, key, object) { + baseAssignValue(result, iteratee(value, key, object), value); + }); + return result; + } + + /** + * Creates an object with the same keys as `object` and values generated + * by running each own enumerable string keyed property of `object` thru + * `iteratee`. The iteratee is invoked with three arguments: + * (value, key, object). + * + * @static + * @memberOf _ + * @since 2.4.0 + * @category Object + * @param {Object} object The object to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @returns {Object} Returns the new mapped object. + * @see _.mapKeys + * @example + * + * var users = { + * 'fred': { 'user': 'fred', 'age': 40 }, + * 'pebbles': { 'user': 'pebbles', 'age': 1 } + * }; + * + * _.mapValues(users, function(o) { return o.age; }); + * // => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed) + * + * // The `_.property` iteratee shorthand. + * _.mapValues(users, 'age'); + * // => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed) + */ + function mapValues(object, iteratee) { + var result = {}; + iteratee = getIteratee(iteratee, 3); + baseForOwn(object, function (value, key, object) { + baseAssignValue(result, key, iteratee(value, key, object)); + }); + return result; + } + + /** + * This method is like `_.assign` except that it recursively merges own and + * inherited enumerable string keyed properties of source objects into the + * destination object. Source properties that resolve to `undefined` are + * skipped if a destination value exists. Array and plain object properties + * are merged recursively. Other objects and value types are overridden by + * assignment. Source objects are applied from left to right. Subsequent + * sources overwrite property assignments of previous sources. + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 0.5.0 + * @category Object + * @param {Object} object The destination object. + * @param {...Object} [sources] The source objects. + * @returns {Object} Returns `object`. + * @example + * + * var object = { + * 'a': [{ 'b': 2 }, { 'd': 4 }] + * }; + * + * var other = { + * 'a': [{ 'c': 3 }, { 'e': 5 }] + * }; + * + * _.merge(object, other); + * // => { 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] } + */ + var merge = createAssigner(function (object, source, srcIndex) { + baseMerge(object, source, srcIndex); + }); + + /** + * This method is like `_.merge` except that it accepts `customizer` which + * is invoked to produce the merged values of the destination and source + * properties. If `customizer` returns `undefined`, merging is handled by the + * method instead. The `customizer` is invoked with six arguments: + * (objValue, srcValue, key, object, source, stack). + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Object + * @param {Object} object The destination object. + * @param {...Object} sources The source objects. + * @param {Function} customizer The function to customize assigned values. + * @returns {Object} Returns `object`. + * @example + * + * function customizer(objValue, srcValue) { + * if (_.isArray(objValue)) { + * return objValue.concat(srcValue); + * } + * } + * + * var object = { 'a': [1], 'b': [2] }; + * var other = { 'a': [3], 'b': [4] }; + * + * _.mergeWith(object, other, customizer); + * // => { 'a': [1, 3], 'b': [2, 4] } + */ + var mergeWith = createAssigner(function (object, source, srcIndex, customizer) { + baseMerge(object, source, srcIndex, customizer); + }); + + /** + * The opposite of `_.pick`; this method creates an object composed of the + * own and inherited enumerable property paths of `object` that are not omitted. + * + * **Note:** This method is considerably slower than `_.pick`. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Object + * @param {Object} object The source object. + * @param {...(string|string[])} [paths] The property paths to omit. + * @returns {Object} Returns the new object. + * @example + * + * var object = { 'a': 1, 'b': '2', 'c': 3 }; + * + * _.omit(object, ['a', 'c']); + * // => { 'b': '2' } + */ + var omit = flatRest(function (object, paths) { + var result = {}; + if (object == null) { + return result; + } + var isDeep = false; + paths = arrayMap(paths, function (path) { + path = castPath(path, object); + isDeep || (isDeep = path.length > 1); + return path; + }); + copyObject(object, getAllKeysIn(object), result); + if (isDeep) { + result = baseClone(result, CLONE_DEEP_FLAG | CLONE_FLAT_FLAG | CLONE_SYMBOLS_FLAG, customOmitClone); + } + var length = paths.length; + while (length--) { + baseUnset(result, paths[length]); + } + return result; + }); + + /** + * The opposite of `_.pickBy`; this method creates an object composed of + * the own and inherited enumerable string keyed properties of `object` that + * `predicate` doesn't return truthy for. The predicate is invoked with two + * arguments: (value, key). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Object + * @param {Object} object The source object. + * @param {Function} [predicate=_.identity] The function invoked per property. + * @returns {Object} Returns the new object. + * @example + * + * var object = { 'a': 1, 'b': '2', 'c': 3 }; + * + * _.omitBy(object, _.isNumber); + * // => { 'b': '2' } + */ + function omitBy(object, predicate) { + return pickBy(object, negate(getIteratee(predicate))); + } + + /** + * Creates an object composed of the picked `object` properties. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Object + * @param {Object} object The source object. + * @param {...(string|string[])} [paths] The property paths to pick. + * @returns {Object} Returns the new object. + * @example + * + * var object = { 'a': 1, 'b': '2', 'c': 3 }; + * + * _.pick(object, ['a', 'c']); + * // => { 'a': 1, 'c': 3 } + */ + var pick = flatRest(function (object, paths) { + return object == null ? {} : basePick(object, paths); + }); + + /** + * Creates an object composed of the `object` properties `predicate` returns + * truthy for. The predicate is invoked with two arguments: (value, key). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Object + * @param {Object} object The source object. + * @param {Function} [predicate=_.identity] The function invoked per property. + * @returns {Object} Returns the new object. + * @example + * + * var object = { 'a': 1, 'b': '2', 'c': 3 }; + * + * _.pickBy(object, _.isNumber); + * // => { 'a': 1, 'c': 3 } + */ + function pickBy(object, predicate) { + if (object == null) { + return {}; + } + var props = arrayMap(getAllKeysIn(object), function (prop) { + return [prop]; + }); + predicate = getIteratee(predicate); + return basePickBy(object, props, function (value, path) { + return predicate(value, path[0]); + }); + } + + /** + * This method is like `_.get` except that if the resolved value is a + * function it's invoked with the `this` binding of its parent object and + * its result is returned. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Object + * @param {Object} object The object to query. + * @param {Array|string} path The path of the property to resolve. + * @param {*} [defaultValue] The value returned for `undefined` resolved values. + * @returns {*} Returns the resolved value. + * @example + * + * var object = { 'a': [{ 'b': { 'c1': 3, 'c2': _.constant(4) } }] }; + * + * _.result(object, 'a[0].b.c1'); + * // => 3 + * + * _.result(object, 'a[0].b.c2'); + * // => 4 + * + * _.result(object, 'a[0].b.c3', 'default'); + * // => 'default' + * + * _.result(object, 'a[0].b.c3', _.constant('default')); + * // => 'default' + */ + function result(object, path, defaultValue) { + path = castPath(path, object); + var index = -1, + length = path.length; + + // Ensure the loop is entered when path is empty. + if (!length) { + length = 1; + object = undefined; + } + while (++index < length) { + var value = object == null ? undefined : object[toKey(path[index])]; + if (value === undefined) { + index = length; + value = defaultValue; + } + object = isFunction(value) ? value.call(object) : value; + } + return object; + } + + /** + * Sets the value at `path` of `object`. If a portion of `path` doesn't exist, + * it's created. Arrays are created for missing index properties while objects + * are created for all other missing properties. Use `_.setWith` to customize + * `path` creation. + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 3.7.0 + * @category Object + * @param {Object} object The object to modify. + * @param {Array|string} path The path of the property to set. + * @param {*} value The value to set. + * @returns {Object} Returns `object`. + * @example + * + * var object = { 'a': [{ 'b': { 'c': 3 } }] }; + * + * _.set(object, 'a[0].b.c', 4); + * console.log(object.a[0].b.c); + * // => 4 + * + * _.set(object, ['x', '0', 'y', 'z'], 5); + * console.log(object.x[0].y.z); + * // => 5 + */ + function set(object, path, value) { + return object == null ? object : baseSet(object, path, value); + } + + /** + * This method is like `_.set` except that it accepts `customizer` which is + * invoked to produce the objects of `path`. If `customizer` returns `undefined` + * path creation is handled by the method instead. The `customizer` is invoked + * with three arguments: (nsValue, key, nsObject). + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Object + * @param {Object} object The object to modify. + * @param {Array|string} path The path of the property to set. + * @param {*} value The value to set. + * @param {Function} [customizer] The function to customize assigned values. + * @returns {Object} Returns `object`. + * @example + * + * var object = {}; + * + * _.setWith(object, '[0][1]', 'a', Object); + * // => { '0': { '1': 'a' } } + */ + function setWith(object, path, value, customizer) { + customizer = typeof customizer == 'function' ? customizer : undefined; + return object == null ? object : baseSet(object, path, value, customizer); + } + + /** + * Creates an array of own enumerable string keyed-value pairs for `object` + * which can be consumed by `_.fromPairs`. If `object` is a map or set, its + * entries are returned. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @alias entries + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the key-value pairs. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.toPairs(new Foo); + * // => [['a', 1], ['b', 2]] (iteration order is not guaranteed) + */ + var toPairs = createToPairs(keys); + + /** + * Creates an array of own and inherited enumerable string keyed-value pairs + * for `object` which can be consumed by `_.fromPairs`. If `object` is a map + * or set, its entries are returned. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @alias entriesIn + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the key-value pairs. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.toPairsIn(new Foo); + * // => [['a', 1], ['b', 2], ['c', 3]] (iteration order is not guaranteed) + */ + var toPairsIn = createToPairs(keysIn); + + /** + * An alternative to `_.reduce`; this method transforms `object` to a new + * `accumulator` object which is the result of running each of its own + * enumerable string keyed properties thru `iteratee`, with each invocation + * potentially mutating the `accumulator` object. If `accumulator` is not + * provided, a new object with the same `[[Prototype]]` will be used. The + * iteratee is invoked with four arguments: (accumulator, value, key, object). + * Iteratee functions may exit iteration early by explicitly returning `false`. + * + * @static + * @memberOf _ + * @since 1.3.0 + * @category Object + * @param {Object} object The object to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @param {*} [accumulator] The custom accumulator value. + * @returns {*} Returns the accumulated value. + * @example + * + * _.transform([2, 3, 4], function(result, n) { + * result.push(n *= n); + * return n % 2 == 0; + * }, []); + * // => [4, 9] + * + * _.transform({ 'a': 1, 'b': 2, 'c': 1 }, function(result, value, key) { + * (result[value] || (result[value] = [])).push(key); + * }, {}); + * // => { '1': ['a', 'c'], '2': ['b'] } + */ + function transform(object, iteratee, accumulator) { + var isArr = isArray(object), + isArrLike = isArr || isBuffer(object) || isTypedArray(object); + iteratee = getIteratee(iteratee, 4); + if (accumulator == null) { + var Ctor = object && object.constructor; + if (isArrLike) { + accumulator = isArr ? new Ctor() : []; + } else if (isObject(object)) { + accumulator = isFunction(Ctor) ? baseCreate(getPrototype(object)) : {}; + } else { + accumulator = {}; + } + } + (isArrLike ? arrayEach : baseForOwn)(object, function (value, index, object) { + return iteratee(accumulator, value, index, object); + }); + return accumulator; + } + + /** + * Removes the property at `path` of `object`. + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Object + * @param {Object} object The object to modify. + * @param {Array|string} path The path of the property to unset. + * @returns {boolean} Returns `true` if the property is deleted, else `false`. + * @example + * + * var object = { 'a': [{ 'b': { 'c': 7 } }] }; + * _.unset(object, 'a[0].b.c'); + * // => true + * + * console.log(object); + * // => { 'a': [{ 'b': {} }] }; + * + * _.unset(object, ['a', '0', 'b', 'c']); + * // => true + * + * console.log(object); + * // => { 'a': [{ 'b': {} }] }; + */ + function unset(object, path) { + return object == null ? true : baseUnset(object, path); + } + + /** + * This method is like `_.set` except that accepts `updater` to produce the + * value to set. Use `_.updateWith` to customize `path` creation. The `updater` + * is invoked with one argument: (value). + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 4.6.0 + * @category Object + * @param {Object} object The object to modify. + * @param {Array|string} path The path of the property to set. + * @param {Function} updater The function to produce the updated value. + * @returns {Object} Returns `object`. + * @example + * + * var object = { 'a': [{ 'b': { 'c': 3 } }] }; + * + * _.update(object, 'a[0].b.c', function(n) { return n * n; }); + * console.log(object.a[0].b.c); + * // => 9 + * + * _.update(object, 'x[0].y.z', function(n) { return n ? n + 1 : 0; }); + * console.log(object.x[0].y.z); + * // => 0 + */ + function update(object, path, updater) { + return object == null ? object : baseUpdate(object, path, castFunction(updater)); + } + + /** + * This method is like `_.update` except that it accepts `customizer` which is + * invoked to produce the objects of `path`. If `customizer` returns `undefined` + * path creation is handled by the method instead. The `customizer` is invoked + * with three arguments: (nsValue, key, nsObject). + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 4.6.0 + * @category Object + * @param {Object} object The object to modify. + * @param {Array|string} path The path of the property to set. + * @param {Function} updater The function to produce the updated value. + * @param {Function} [customizer] The function to customize assigned values. + * @returns {Object} Returns `object`. + * @example + * + * var object = {}; + * + * _.updateWith(object, '[0][1]', _.constant('a'), Object); + * // => { '0': { '1': 'a' } } + */ + function updateWith(object, path, updater, customizer) { + customizer = typeof customizer == 'function' ? customizer : undefined; + return object == null ? object : baseUpdate(object, path, castFunction(updater), customizer); + } + + /** + * Creates an array of the own enumerable string keyed property values of `object`. + * + * **Note:** Non-object values are coerced to objects. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property values. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.values(new Foo); + * // => [1, 2] (iteration order is not guaranteed) + * + * _.values('hi'); + * // => ['h', 'i'] + */ + function values(object) { + return object == null ? [] : baseValues(object, keys(object)); + } + + /** + * Creates an array of the own and inherited enumerable string keyed property + * values of `object`. + * + * **Note:** Non-object values are coerced to objects. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property values. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.valuesIn(new Foo); + * // => [1, 2, 3] (iteration order is not guaranteed) + */ + function valuesIn(object) { + return object == null ? [] : baseValues(object, keysIn(object)); + } + + /*------------------------------------------------------------------------*/ + + /** + * Clamps `number` within the inclusive `lower` and `upper` bounds. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Number + * @param {number} number The number to clamp. + * @param {number} [lower] The lower bound. + * @param {number} upper The upper bound. + * @returns {number} Returns the clamped number. + * @example + * + * _.clamp(-10, -5, 5); + * // => -5 + * + * _.clamp(10, -5, 5); + * // => 5 + */ + function clamp(number, lower, upper) { + if (upper === undefined) { + upper = lower; + lower = undefined; + } + if (upper !== undefined) { + upper = toNumber(upper); + upper = upper === upper ? upper : 0; + } + if (lower !== undefined) { + lower = toNumber(lower); + lower = lower === lower ? lower : 0; + } + return baseClamp(toNumber(number), lower, upper); + } + + /** + * Checks if `n` is between `start` and up to, but not including, `end`. If + * `end` is not specified, it's set to `start` with `start` then set to `0`. + * If `start` is greater than `end` the params are swapped to support + * negative ranges. + * + * @static + * @memberOf _ + * @since 3.3.0 + * @category Number + * @param {number} number The number to check. + * @param {number} [start=0] The start of the range. + * @param {number} end The end of the range. + * @returns {boolean} Returns `true` if `number` is in the range, else `false`. + * @see _.range, _.rangeRight + * @example + * + * _.inRange(3, 2, 4); + * // => true + * + * _.inRange(4, 8); + * // => true + * + * _.inRange(4, 2); + * // => false + * + * _.inRange(2, 2); + * // => false + * + * _.inRange(1.2, 2); + * // => true + * + * _.inRange(5.2, 4); + * // => false + * + * _.inRange(-3, -2, -6); + * // => true + */ + function inRange(number, start, end) { + start = toFinite(start); + if (end === undefined) { + end = start; + start = 0; + } else { + end = toFinite(end); + } + number = toNumber(number); + return baseInRange(number, start, end); + } + + /** + * Produces a random number between the inclusive `lower` and `upper` bounds. + * If only one argument is provided a number between `0` and the given number + * is returned. If `floating` is `true`, or either `lower` or `upper` are + * floats, a floating-point number is returned instead of an integer. + * + * **Note:** JavaScript follows the IEEE-754 standard for resolving + * floating-point values which can produce unexpected results. + * + * @static + * @memberOf _ + * @since 0.7.0 + * @category Number + * @param {number} [lower=0] The lower bound. + * @param {number} [upper=1] The upper bound. + * @param {boolean} [floating] Specify returning a floating-point number. + * @returns {number} Returns the random number. + * @example + * + * _.random(0, 5); + * // => an integer between 0 and 5 + * + * _.random(5); + * // => also an integer between 0 and 5 + * + * _.random(5, true); + * // => a floating-point number between 0 and 5 + * + * _.random(1.2, 5.2); + * // => a floating-point number between 1.2 and 5.2 + */ + function random(lower, upper, floating) { + if (floating && typeof floating != 'boolean' && isIterateeCall(lower, upper, floating)) { + upper = floating = undefined; + } + if (floating === undefined) { + if (typeof upper == 'boolean') { + floating = upper; + upper = undefined; + } else if (typeof lower == 'boolean') { + floating = lower; + lower = undefined; + } + } + if (lower === undefined && upper === undefined) { + lower = 0; + upper = 1; + } else { + lower = toFinite(lower); + if (upper === undefined) { + upper = lower; + lower = 0; + } else { + upper = toFinite(upper); + } + } + if (lower > upper) { + var temp = lower; + lower = upper; + upper = temp; + } + if (floating || lower % 1 || upper % 1) { + var rand = nativeRandom(); + return nativeMin(lower + rand * (upper - lower + freeParseFloat('1e-' + ((rand + '').length - 1))), upper); + } + return baseRandom(lower, upper); + } + + /*------------------------------------------------------------------------*/ + + /** + * Converts `string` to [camel case](https://en.wikipedia.org/wiki/CamelCase). + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category String + * @param {string} [string=''] The string to convert. + * @returns {string} Returns the camel cased string. + * @example + * + * _.camelCase('Foo Bar'); + * // => 'fooBar' + * + * _.camelCase('--foo-bar--'); + * // => 'fooBar' + * + * _.camelCase('__FOO_BAR__'); + * // => 'fooBar' + */ + var camelCase = createCompounder(function (result, word, index) { + word = word.toLowerCase(); + return result + (index ? capitalize(word) : word); + }); + + /** + * Converts the first character of `string` to upper case and the remaining + * to lower case. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category String + * @param {string} [string=''] The string to capitalize. + * @returns {string} Returns the capitalized string. + * @example + * + * _.capitalize('FRED'); + * // => 'Fred' + */ + function capitalize(string) { + return upperFirst(toString(string).toLowerCase()); + } + + /** + * Deburrs `string` by converting + * [Latin-1 Supplement](https://en.wikipedia.org/wiki/Latin-1_Supplement_(Unicode_block)#Character_table) + * and [Latin Extended-A](https://en.wikipedia.org/wiki/Latin_Extended-A) + * letters to basic Latin letters and removing + * [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks). + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category String + * @param {string} [string=''] The string to deburr. + * @returns {string} Returns the deburred string. + * @example + * + * _.deburr('déjà vu'); + * // => 'deja vu' + */ + function deburr(string) { + string = toString(string); + return string && string.replace(reLatin, deburrLetter).replace(reComboMark, ''); + } + + /** + * Checks if `string` ends with the given target string. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category String + * @param {string} [string=''] The string to inspect. + * @param {string} [target] The string to search for. + * @param {number} [position=string.length] The position to search up to. + * @returns {boolean} Returns `true` if `string` ends with `target`, + * else `false`. + * @example + * + * _.endsWith('abc', 'c'); + * // => true + * + * _.endsWith('abc', 'b'); + * // => false + * + * _.endsWith('abc', 'b', 2); + * // => true + */ + function endsWith(string, target, position) { + string = toString(string); + target = baseToString(target); + var length = string.length; + position = position === undefined ? length : baseClamp(toInteger(position), 0, length); + var end = position; + position -= target.length; + return position >= 0 && string.slice(position, end) == target; + } + + /** + * Converts the characters "&", "<", ">", '"', and "'" in `string` to their + * corresponding HTML entities. + * + * **Note:** No other characters are escaped. To escape additional + * characters use a third-party library like [_he_](https://mths.be/he). + * + * Though the ">" character is escaped for symmetry, characters like + * ">" and "/" don't need escaping in HTML and have no special meaning + * unless they're part of a tag or unquoted attribute value. See + * [Mathias Bynens's article](https://mathiasbynens.be/notes/ambiguous-ampersands) + * (under "semi-related fun fact") for more details. + * + * When working with HTML you should always + * [quote attribute values](http://wonko.com/post/html-escaping) to reduce + * XSS vectors. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category String + * @param {string} [string=''] The string to escape. + * @returns {string} Returns the escaped string. + * @example + * + * _.escape('fred, barney, & pebbles'); + * // => 'fred, barney, & pebbles' + */ + function escape(string) { + string = toString(string); + return string && reHasUnescapedHtml.test(string) ? string.replace(reUnescapedHtml, escapeHtmlChar) : string; + } + + /** + * Escapes the `RegExp` special characters "^", "$", "\", ".", "*", "+", + * "?", "(", ")", "[", "]", "{", "}", and "|" in `string`. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category String + * @param {string} [string=''] The string to escape. + * @returns {string} Returns the escaped string. + * @example + * + * _.escapeRegExp('[lodash](https://lodash.com/)'); + * // => '\[lodash\]\(https://lodash\.com/\)' + */ + function escapeRegExp(string) { + string = toString(string); + return string && reHasRegExpChar.test(string) ? string.replace(reRegExpChar, '\\$&') : string; + } + + /** + * Converts `string` to + * [kebab case](https://en.wikipedia.org/wiki/Letter_case#Special_case_styles). + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category String + * @param {string} [string=''] The string to convert. + * @returns {string} Returns the kebab cased string. + * @example + * + * _.kebabCase('Foo Bar'); + * // => 'foo-bar' + * + * _.kebabCase('fooBar'); + * // => 'foo-bar' + * + * _.kebabCase('__FOO_BAR__'); + * // => 'foo-bar' + */ + var kebabCase = createCompounder(function (result, word, index) { + return result + (index ? '-' : '') + word.toLowerCase(); + }); + + /** + * Converts `string`, as space separated words, to lower case. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category String + * @param {string} [string=''] The string to convert. + * @returns {string} Returns the lower cased string. + * @example + * + * _.lowerCase('--Foo-Bar--'); + * // => 'foo bar' + * + * _.lowerCase('fooBar'); + * // => 'foo bar' + * + * _.lowerCase('__FOO_BAR__'); + * // => 'foo bar' + */ + var lowerCase = createCompounder(function (result, word, index) { + return result + (index ? ' ' : '') + word.toLowerCase(); + }); + + /** + * Converts the first character of `string` to lower case. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category String + * @param {string} [string=''] The string to convert. + * @returns {string} Returns the converted string. + * @example + * + * _.lowerFirst('Fred'); + * // => 'fred' + * + * _.lowerFirst('FRED'); + * // => 'fRED' + */ + var lowerFirst = createCaseFirst('toLowerCase'); + + /** + * Pads `string` on the left and right sides if it's shorter than `length`. + * Padding characters are truncated if they can't be evenly divided by `length`. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category String + * @param {string} [string=''] The string to pad. + * @param {number} [length=0] The padding length. + * @param {string} [chars=' '] The string used as padding. + * @returns {string} Returns the padded string. + * @example + * + * _.pad('abc', 8); + * // => ' abc ' + * + * _.pad('abc', 8, '_-'); + * // => '_-abc_-_' + * + * _.pad('abc', 3); + * // => 'abc' + */ + function pad(string, length, chars) { + string = toString(string); + length = toInteger(length); + var strLength = length ? stringSize(string) : 0; + if (!length || strLength >= length) { + return string; + } + var mid = (length - strLength) / 2; + return createPadding(nativeFloor(mid), chars) + string + createPadding(nativeCeil(mid), chars); + } + + /** + * Pads `string` on the right side if it's shorter than `length`. Padding + * characters are truncated if they exceed `length`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category String + * @param {string} [string=''] The string to pad. + * @param {number} [length=0] The padding length. + * @param {string} [chars=' '] The string used as padding. + * @returns {string} Returns the padded string. + * @example + * + * _.padEnd('abc', 6); + * // => 'abc ' + * + * _.padEnd('abc', 6, '_-'); + * // => 'abc_-_' + * + * _.padEnd('abc', 3); + * // => 'abc' + */ + function padEnd(string, length, chars) { + string = toString(string); + length = toInteger(length); + var strLength = length ? stringSize(string) : 0; + return length && strLength < length ? string + createPadding(length - strLength, chars) : string; + } + + /** + * Pads `string` on the left side if it's shorter than `length`. Padding + * characters are truncated if they exceed `length`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category String + * @param {string} [string=''] The string to pad. + * @param {number} [length=0] The padding length. + * @param {string} [chars=' '] The string used as padding. + * @returns {string} Returns the padded string. + * @example + * + * _.padStart('abc', 6); + * // => ' abc' + * + * _.padStart('abc', 6, '_-'); + * // => '_-_abc' + * + * _.padStart('abc', 3); + * // => 'abc' + */ + function padStart(string, length, chars) { + string = toString(string); + length = toInteger(length); + var strLength = length ? stringSize(string) : 0; + return length && strLength < length ? createPadding(length - strLength, chars) + string : string; + } + + /** + * Converts `string` to an integer of the specified radix. If `radix` is + * `undefined` or `0`, a `radix` of `10` is used unless `value` is a + * hexadecimal, in which case a `radix` of `16` is used. + * + * **Note:** This method aligns with the + * [ES5 implementation](https://es5.github.io/#x15.1.2.2) of `parseInt`. + * + * @static + * @memberOf _ + * @since 1.1.0 + * @category String + * @param {string} string The string to convert. + * @param {number} [radix=10] The radix to interpret `value` by. + * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. + * @returns {number} Returns the converted integer. + * @example + * + * _.parseInt('08'); + * // => 8 + * + * _.map(['6', '08', '10'], _.parseInt); + * // => [6, 8, 10] + */ + function parseInt(string, radix, guard) { + if (guard || radix == null) { + radix = 0; + } else if (radix) { + radix = +radix; + } + return nativeParseInt(toString(string).replace(reTrimStart, ''), radix || 0); + } + + /** + * Repeats the given string `n` times. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category String + * @param {string} [string=''] The string to repeat. + * @param {number} [n=1] The number of times to repeat the string. + * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. + * @returns {string} Returns the repeated string. + * @example + * + * _.repeat('*', 3); + * // => '***' + * + * _.repeat('abc', 2); + * // => 'abcabc' + * + * _.repeat('abc', 0); + * // => '' + */ + function repeat(string, n, guard) { + if (guard ? isIterateeCall(string, n, guard) : n === undefined) { + n = 1; + } else { + n = toInteger(n); + } + return baseRepeat(toString(string), n); + } + + /** + * Replaces matches for `pattern` in `string` with `replacement`. + * + * **Note:** This method is based on + * [`String#replace`](https://mdn.io/String/replace). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category String + * @param {string} [string=''] The string to modify. + * @param {RegExp|string} pattern The pattern to replace. + * @param {Function|string} replacement The match replacement. + * @returns {string} Returns the modified string. + * @example + * + * _.replace('Hi Fred', 'Fred', 'Barney'); + * // => 'Hi Barney' + */ + function replace() { + var args = arguments, + string = toString(args[0]); + return args.length < 3 ? string : string.replace(args[1], args[2]); + } + + /** + * Converts `string` to + * [snake case](https://en.wikipedia.org/wiki/Snake_case). + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category String + * @param {string} [string=''] The string to convert. + * @returns {string} Returns the snake cased string. + * @example + * + * _.snakeCase('Foo Bar'); + * // => 'foo_bar' + * + * _.snakeCase('fooBar'); + * // => 'foo_bar' + * + * _.snakeCase('--FOO-BAR--'); + * // => 'foo_bar' + */ + var snakeCase = createCompounder(function (result, word, index) { + return result + (index ? '_' : '') + word.toLowerCase(); + }); + + /** + * Splits `string` by `separator`. + * + * **Note:** This method is based on + * [`String#split`](https://mdn.io/String/split). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category String + * @param {string} [string=''] The string to split. + * @param {RegExp|string} separator The separator pattern to split by. + * @param {number} [limit] The length to truncate results to. + * @returns {Array} Returns the string segments. + * @example + * + * _.split('a-b-c', '-', 2); + * // => ['a', 'b'] + */ + function split(string, separator, limit) { + if (limit && typeof limit != 'number' && isIterateeCall(string, separator, limit)) { + separator = limit = undefined; + } + limit = limit === undefined ? MAX_ARRAY_LENGTH : limit >>> 0; + if (!limit) { + return []; + } + string = toString(string); + if (string && (typeof separator == 'string' || separator != null && !isRegExp(separator))) { + separator = baseToString(separator); + if (!separator && hasUnicode(string)) { + return castSlice(stringToArray(string), 0, limit); + } + } + return string.split(separator, limit); + } + + /** + * Converts `string` to + * [start case](https://en.wikipedia.org/wiki/Letter_case#Stylistic_or_specialised_usage). + * + * @static + * @memberOf _ + * @since 3.1.0 + * @category String + * @param {string} [string=''] The string to convert. + * @returns {string} Returns the start cased string. + * @example + * + * _.startCase('--foo-bar--'); + * // => 'Foo Bar' + * + * _.startCase('fooBar'); + * // => 'Foo Bar' + * + * _.startCase('__FOO_BAR__'); + * // => 'FOO BAR' + */ + var startCase = createCompounder(function (result, word, index) { + return result + (index ? ' ' : '') + upperFirst(word); + }); + + /** + * Checks if `string` starts with the given target string. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category String + * @param {string} [string=''] The string to inspect. + * @param {string} [target] The string to search for. + * @param {number} [position=0] The position to search from. + * @returns {boolean} Returns `true` if `string` starts with `target`, + * else `false`. + * @example + * + * _.startsWith('abc', 'a'); + * // => true + * + * _.startsWith('abc', 'b'); + * // => false + * + * _.startsWith('abc', 'b', 1); + * // => true + */ + function startsWith(string, target, position) { + string = toString(string); + position = position == null ? 0 : baseClamp(toInteger(position), 0, string.length); + target = baseToString(target); + return string.slice(position, position + target.length) == target; + } + + /** + * Creates a compiled template function that can interpolate data properties + * in "interpolate" delimiters, HTML-escape interpolated data properties in + * "escape" delimiters, and execute JavaScript in "evaluate" delimiters. Data + * properties may be accessed as free variables in the template. If a setting + * object is given, it takes precedence over `_.templateSettings` values. + * + * **Note:** In the development build `_.template` utilizes + * [sourceURLs](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl) + * for easier debugging. + * + * For more information on precompiling templates see + * [lodash's custom builds documentation](https://lodash.com/custom-builds). + * + * For more information on Chrome extension sandboxes see + * [Chrome's extensions documentation](https://developer.chrome.com/extensions/sandboxingEval). + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category String + * @param {string} [string=''] The template string. + * @param {Object} [options={}] The options object. + * @param {RegExp} [options.escape=_.templateSettings.escape] + * The HTML "escape" delimiter. + * @param {RegExp} [options.evaluate=_.templateSettings.evaluate] + * The "evaluate" delimiter. + * @param {Object} [options.imports=_.templateSettings.imports] + * An object to import into the template as free variables. + * @param {RegExp} [options.interpolate=_.templateSettings.interpolate] + * The "interpolate" delimiter. + * @param {string} [options.sourceURL='lodash.templateSources[n]'] + * The sourceURL of the compiled template. + * @param {string} [options.variable='obj'] + * The data object variable name. + * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. + * @returns {Function} Returns the compiled template function. + * @example + * + * // Use the "interpolate" delimiter to create a compiled template. + * var compiled = _.template('hello <%= user %>!'); + * compiled({ 'user': 'fred' }); + * // => 'hello fred!' + * + * // Use the HTML "escape" delimiter to escape data property values. + * var compiled = _.template('<%- value %>'); + * compiled({ 'value': '
    \ No newline at end of file +React App
    \ No newline at end of file diff --git a/pkg/webui/ui/build/static/js/main.js b/pkg/webui/ui/build/static/js/main.js index d5043f933..844adb822 100644 --- a/pkg/webui/ui/build/static/js/main.js +++ b/pkg/webui/ui/build/static/js/main.js @@ -3233,6 +3233,478 @@ if (true) { /***/ }), +/***/ 981: +/***/ (function(module, exports, __webpack_require__) { + +var __WEBPACK_AMD_DEFINE_RESULT__;/** + * [js-sha256]{@link https://github.com/emn178/js-sha256} + * + * @version 0.9.0 + * @author Chen, Yi-Cyuan [emn178@gmail.com] + * @copyright Chen, Yi-Cyuan 2014-2017 + * @license MIT + */ +/*jslint bitwise: true */ +(function () { + 'use strict'; + + var ERROR = 'input is invalid type'; + var WINDOW = typeof window === 'object'; + var root = WINDOW ? window : {}; + if (root.JS_SHA256_NO_WINDOW) { + WINDOW = false; + } + var WEB_WORKER = !WINDOW && typeof self === 'object'; + var NODE_JS = !root.JS_SHA256_NO_NODE_JS && typeof process === 'object' && process.versions && process.versions.node; + if (NODE_JS) { + root = __webpack_require__.g; + } else if (WEB_WORKER) { + root = self; + } + var COMMON_JS = !root.JS_SHA256_NO_COMMON_JS && "object" === 'object' && module.exports; + var AMD = true && __webpack_require__.amdO; + var ARRAY_BUFFER = !root.JS_SHA256_NO_ARRAY_BUFFER && typeof ArrayBuffer !== 'undefined'; + var HEX_CHARS = '0123456789abcdef'.split(''); + var EXTRA = [-2147483648, 8388608, 32768, 128]; + var SHIFT = [24, 16, 8, 0]; + var K = [0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2]; + var OUTPUT_TYPES = ['hex', 'array', 'digest', 'arrayBuffer']; + var blocks = []; + if (root.JS_SHA256_NO_NODE_JS || !Array.isArray) { + Array.isArray = function (obj) { + return Object.prototype.toString.call(obj) === '[object Array]'; + }; + } + if (ARRAY_BUFFER && (root.JS_SHA256_NO_ARRAY_BUFFER_IS_VIEW || !ArrayBuffer.isView)) { + ArrayBuffer.isView = function (obj) { + return typeof obj === 'object' && obj.buffer && obj.buffer.constructor === ArrayBuffer; + }; + } + var createOutputMethod = function createOutputMethod(outputType, is224) { + return function (message) { + return new Sha256(is224, true).update(message)[outputType](); + }; + }; + var createMethod = function createMethod(is224) { + var method = createOutputMethod('hex', is224); + if (NODE_JS) { + method = nodeWrap(method, is224); + } + method.create = function () { + return new Sha256(is224); + }; + method.update = function (message) { + return method.create().update(message); + }; + for (var i = 0; i < OUTPUT_TYPES.length; ++i) { + var type = OUTPUT_TYPES[i]; + method[type] = createOutputMethod(type, is224); + } + return method; + }; + var nodeWrap = function nodeWrap(method, is224) { + var crypto = eval("require('crypto')"); + var Buffer = eval("require('buffer').Buffer"); + var algorithm = is224 ? 'sha224' : 'sha256'; + var nodeMethod = function nodeMethod(message) { + if (typeof message === 'string') { + return crypto.createHash(algorithm).update(message, 'utf8').digest('hex'); + } else { + if (message === null || message === undefined) { + throw new Error(ERROR); + } else if (message.constructor === ArrayBuffer) { + message = new Uint8Array(message); + } + } + if (Array.isArray(message) || ArrayBuffer.isView(message) || message.constructor === Buffer) { + return crypto.createHash(algorithm).update(new Buffer(message)).digest('hex'); + } else { + return method(message); + } + }; + return nodeMethod; + }; + var createHmacOutputMethod = function createHmacOutputMethod(outputType, is224) { + return function (key, message) { + return new HmacSha256(key, is224, true).update(message)[outputType](); + }; + }; + var createHmacMethod = function createHmacMethod(is224) { + var method = createHmacOutputMethod('hex', is224); + method.create = function (key) { + return new HmacSha256(key, is224); + }; + method.update = function (key, message) { + return method.create(key).update(message); + }; + for (var i = 0; i < OUTPUT_TYPES.length; ++i) { + var type = OUTPUT_TYPES[i]; + method[type] = createHmacOutputMethod(type, is224); + } + return method; + }; + function Sha256(is224, sharedMemory) { + if (sharedMemory) { + blocks[0] = blocks[16] = blocks[1] = blocks[2] = blocks[3] = blocks[4] = blocks[5] = blocks[6] = blocks[7] = blocks[8] = blocks[9] = blocks[10] = blocks[11] = blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; + this.blocks = blocks; + } else { + this.blocks = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + } + if (is224) { + this.h0 = 0xc1059ed8; + this.h1 = 0x367cd507; + this.h2 = 0x3070dd17; + this.h3 = 0xf70e5939; + this.h4 = 0xffc00b31; + this.h5 = 0x68581511; + this.h6 = 0x64f98fa7; + this.h7 = 0xbefa4fa4; + } else { + // 256 + this.h0 = 0x6a09e667; + this.h1 = 0xbb67ae85; + this.h2 = 0x3c6ef372; + this.h3 = 0xa54ff53a; + this.h4 = 0x510e527f; + this.h5 = 0x9b05688c; + this.h6 = 0x1f83d9ab; + this.h7 = 0x5be0cd19; + } + this.block = this.start = this.bytes = this.hBytes = 0; + this.finalized = this.hashed = false; + this.first = true; + this.is224 = is224; + } + Sha256.prototype.update = function (message) { + if (this.finalized) { + return; + } + var notString, + type = typeof message; + if (type !== 'string') { + if (type === 'object') { + if (message === null) { + throw new Error(ERROR); + } else if (ARRAY_BUFFER && message.constructor === ArrayBuffer) { + message = new Uint8Array(message); + } else if (!Array.isArray(message)) { + if (!ARRAY_BUFFER || !ArrayBuffer.isView(message)) { + throw new Error(ERROR); + } + } + } else { + throw new Error(ERROR); + } + notString = true; + } + var code, + index = 0, + i, + length = message.length, + blocks = this.blocks; + while (index < length) { + if (this.hashed) { + this.hashed = false; + blocks[0] = this.block; + blocks[16] = blocks[1] = blocks[2] = blocks[3] = blocks[4] = blocks[5] = blocks[6] = blocks[7] = blocks[8] = blocks[9] = blocks[10] = blocks[11] = blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; + } + if (notString) { + for (i = this.start; index < length && i < 64; ++index) { + blocks[i >> 2] |= message[index] << SHIFT[i++ & 3]; + } + } else { + for (i = this.start; index < length && i < 64; ++index) { + code = message.charCodeAt(index); + if (code < 0x80) { + blocks[i >> 2] |= code << SHIFT[i++ & 3]; + } else if (code < 0x800) { + blocks[i >> 2] |= (0xc0 | code >> 6) << SHIFT[i++ & 3]; + blocks[i >> 2] |= (0x80 | code & 0x3f) << SHIFT[i++ & 3]; + } else if (code < 0xd800 || code >= 0xe000) { + blocks[i >> 2] |= (0xe0 | code >> 12) << SHIFT[i++ & 3]; + blocks[i >> 2] |= (0x80 | code >> 6 & 0x3f) << SHIFT[i++ & 3]; + blocks[i >> 2] |= (0x80 | code & 0x3f) << SHIFT[i++ & 3]; + } else { + code = 0x10000 + ((code & 0x3ff) << 10 | message.charCodeAt(++index) & 0x3ff); + blocks[i >> 2] |= (0xf0 | code >> 18) << SHIFT[i++ & 3]; + blocks[i >> 2] |= (0x80 | code >> 12 & 0x3f) << SHIFT[i++ & 3]; + blocks[i >> 2] |= (0x80 | code >> 6 & 0x3f) << SHIFT[i++ & 3]; + blocks[i >> 2] |= (0x80 | code & 0x3f) << SHIFT[i++ & 3]; + } + } + } + this.lastByteIndex = i; + this.bytes += i - this.start; + if (i >= 64) { + this.block = blocks[16]; + this.start = i - 64; + this.hash(); + this.hashed = true; + } else { + this.start = i; + } + } + if (this.bytes > 4294967295) { + this.hBytes += this.bytes / 4294967296 << 0; + this.bytes = this.bytes % 4294967296; + } + return this; + }; + Sha256.prototype.finalize = function () { + if (this.finalized) { + return; + } + this.finalized = true; + var blocks = this.blocks, + i = this.lastByteIndex; + blocks[16] = this.block; + blocks[i >> 2] |= EXTRA[i & 3]; + this.block = blocks[16]; + if (i >= 56) { + if (!this.hashed) { + this.hash(); + } + blocks[0] = this.block; + blocks[16] = blocks[1] = blocks[2] = blocks[3] = blocks[4] = blocks[5] = blocks[6] = blocks[7] = blocks[8] = blocks[9] = blocks[10] = blocks[11] = blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; + } + blocks[14] = this.hBytes << 3 | this.bytes >>> 29; + blocks[15] = this.bytes << 3; + this.hash(); + }; + Sha256.prototype.hash = function () { + var a = this.h0, + b = this.h1, + c = this.h2, + d = this.h3, + e = this.h4, + f = this.h5, + g = this.h6, + h = this.h7, + blocks = this.blocks, + j, + s0, + s1, + maj, + t1, + t2, + ch, + ab, + da, + cd, + bc; + for (j = 16; j < 64; ++j) { + // rightrotate + t1 = blocks[j - 15]; + s0 = (t1 >>> 7 | t1 << 25) ^ (t1 >>> 18 | t1 << 14) ^ t1 >>> 3; + t1 = blocks[j - 2]; + s1 = (t1 >>> 17 | t1 << 15) ^ (t1 >>> 19 | t1 << 13) ^ t1 >>> 10; + blocks[j] = blocks[j - 16] + s0 + blocks[j - 7] + s1 << 0; + } + bc = b & c; + for (j = 0; j < 64; j += 4) { + if (this.first) { + if (this.is224) { + ab = 300032; + t1 = blocks[0] - 1413257819; + h = t1 - 150054599 << 0; + d = t1 + 24177077 << 0; + } else { + ab = 704751109; + t1 = blocks[0] - 210244248; + h = t1 - 1521486534 << 0; + d = t1 + 143694565 << 0; + } + this.first = false; + } else { + s0 = (a >>> 2 | a << 30) ^ (a >>> 13 | a << 19) ^ (a >>> 22 | a << 10); + s1 = (e >>> 6 | e << 26) ^ (e >>> 11 | e << 21) ^ (e >>> 25 | e << 7); + ab = a & b; + maj = ab ^ a & c ^ bc; + ch = e & f ^ ~e & g; + t1 = h + s1 + ch + K[j] + blocks[j]; + t2 = s0 + maj; + h = d + t1 << 0; + d = t1 + t2 << 0; + } + s0 = (d >>> 2 | d << 30) ^ (d >>> 13 | d << 19) ^ (d >>> 22 | d << 10); + s1 = (h >>> 6 | h << 26) ^ (h >>> 11 | h << 21) ^ (h >>> 25 | h << 7); + da = d & a; + maj = da ^ d & b ^ ab; + ch = h & e ^ ~h & f; + t1 = g + s1 + ch + K[j + 1] + blocks[j + 1]; + t2 = s0 + maj; + g = c + t1 << 0; + c = t1 + t2 << 0; + s0 = (c >>> 2 | c << 30) ^ (c >>> 13 | c << 19) ^ (c >>> 22 | c << 10); + s1 = (g >>> 6 | g << 26) ^ (g >>> 11 | g << 21) ^ (g >>> 25 | g << 7); + cd = c & d; + maj = cd ^ c & a ^ da; + ch = g & h ^ ~g & e; + t1 = f + s1 + ch + K[j + 2] + blocks[j + 2]; + t2 = s0 + maj; + f = b + t1 << 0; + b = t1 + t2 << 0; + s0 = (b >>> 2 | b << 30) ^ (b >>> 13 | b << 19) ^ (b >>> 22 | b << 10); + s1 = (f >>> 6 | f << 26) ^ (f >>> 11 | f << 21) ^ (f >>> 25 | f << 7); + bc = b & c; + maj = bc ^ b & d ^ cd; + ch = f & g ^ ~f & h; + t1 = e + s1 + ch + K[j + 3] + blocks[j + 3]; + t2 = s0 + maj; + e = a + t1 << 0; + a = t1 + t2 << 0; + } + this.h0 = this.h0 + a << 0; + this.h1 = this.h1 + b << 0; + this.h2 = this.h2 + c << 0; + this.h3 = this.h3 + d << 0; + this.h4 = this.h4 + e << 0; + this.h5 = this.h5 + f << 0; + this.h6 = this.h6 + g << 0; + this.h7 = this.h7 + h << 0; + }; + Sha256.prototype.hex = function () { + this.finalize(); + var h0 = this.h0, + h1 = this.h1, + h2 = this.h2, + h3 = this.h3, + h4 = this.h4, + h5 = this.h5, + h6 = this.h6, + h7 = this.h7; + var hex = HEX_CHARS[h0 >> 28 & 0x0F] + HEX_CHARS[h0 >> 24 & 0x0F] + HEX_CHARS[h0 >> 20 & 0x0F] + HEX_CHARS[h0 >> 16 & 0x0F] + HEX_CHARS[h0 >> 12 & 0x0F] + HEX_CHARS[h0 >> 8 & 0x0F] + HEX_CHARS[h0 >> 4 & 0x0F] + HEX_CHARS[h0 & 0x0F] + HEX_CHARS[h1 >> 28 & 0x0F] + HEX_CHARS[h1 >> 24 & 0x0F] + HEX_CHARS[h1 >> 20 & 0x0F] + HEX_CHARS[h1 >> 16 & 0x0F] + HEX_CHARS[h1 >> 12 & 0x0F] + HEX_CHARS[h1 >> 8 & 0x0F] + HEX_CHARS[h1 >> 4 & 0x0F] + HEX_CHARS[h1 & 0x0F] + HEX_CHARS[h2 >> 28 & 0x0F] + HEX_CHARS[h2 >> 24 & 0x0F] + HEX_CHARS[h2 >> 20 & 0x0F] + HEX_CHARS[h2 >> 16 & 0x0F] + HEX_CHARS[h2 >> 12 & 0x0F] + HEX_CHARS[h2 >> 8 & 0x0F] + HEX_CHARS[h2 >> 4 & 0x0F] + HEX_CHARS[h2 & 0x0F] + HEX_CHARS[h3 >> 28 & 0x0F] + HEX_CHARS[h3 >> 24 & 0x0F] + HEX_CHARS[h3 >> 20 & 0x0F] + HEX_CHARS[h3 >> 16 & 0x0F] + HEX_CHARS[h3 >> 12 & 0x0F] + HEX_CHARS[h3 >> 8 & 0x0F] + HEX_CHARS[h3 >> 4 & 0x0F] + HEX_CHARS[h3 & 0x0F] + HEX_CHARS[h4 >> 28 & 0x0F] + HEX_CHARS[h4 >> 24 & 0x0F] + HEX_CHARS[h4 >> 20 & 0x0F] + HEX_CHARS[h4 >> 16 & 0x0F] + HEX_CHARS[h4 >> 12 & 0x0F] + HEX_CHARS[h4 >> 8 & 0x0F] + HEX_CHARS[h4 >> 4 & 0x0F] + HEX_CHARS[h4 & 0x0F] + HEX_CHARS[h5 >> 28 & 0x0F] + HEX_CHARS[h5 >> 24 & 0x0F] + HEX_CHARS[h5 >> 20 & 0x0F] + HEX_CHARS[h5 >> 16 & 0x0F] + HEX_CHARS[h5 >> 12 & 0x0F] + HEX_CHARS[h5 >> 8 & 0x0F] + HEX_CHARS[h5 >> 4 & 0x0F] + HEX_CHARS[h5 & 0x0F] + HEX_CHARS[h6 >> 28 & 0x0F] + HEX_CHARS[h6 >> 24 & 0x0F] + HEX_CHARS[h6 >> 20 & 0x0F] + HEX_CHARS[h6 >> 16 & 0x0F] + HEX_CHARS[h6 >> 12 & 0x0F] + HEX_CHARS[h6 >> 8 & 0x0F] + HEX_CHARS[h6 >> 4 & 0x0F] + HEX_CHARS[h6 & 0x0F]; + if (!this.is224) { + hex += HEX_CHARS[h7 >> 28 & 0x0F] + HEX_CHARS[h7 >> 24 & 0x0F] + HEX_CHARS[h7 >> 20 & 0x0F] + HEX_CHARS[h7 >> 16 & 0x0F] + HEX_CHARS[h7 >> 12 & 0x0F] + HEX_CHARS[h7 >> 8 & 0x0F] + HEX_CHARS[h7 >> 4 & 0x0F] + HEX_CHARS[h7 & 0x0F]; + } + return hex; + }; + Sha256.prototype.toString = Sha256.prototype.hex; + Sha256.prototype.digest = function () { + this.finalize(); + var h0 = this.h0, + h1 = this.h1, + h2 = this.h2, + h3 = this.h3, + h4 = this.h4, + h5 = this.h5, + h6 = this.h6, + h7 = this.h7; + var arr = [h0 >> 24 & 0xFF, h0 >> 16 & 0xFF, h0 >> 8 & 0xFF, h0 & 0xFF, h1 >> 24 & 0xFF, h1 >> 16 & 0xFF, h1 >> 8 & 0xFF, h1 & 0xFF, h2 >> 24 & 0xFF, h2 >> 16 & 0xFF, h2 >> 8 & 0xFF, h2 & 0xFF, h3 >> 24 & 0xFF, h3 >> 16 & 0xFF, h3 >> 8 & 0xFF, h3 & 0xFF, h4 >> 24 & 0xFF, h4 >> 16 & 0xFF, h4 >> 8 & 0xFF, h4 & 0xFF, h5 >> 24 & 0xFF, h5 >> 16 & 0xFF, h5 >> 8 & 0xFF, h5 & 0xFF, h6 >> 24 & 0xFF, h6 >> 16 & 0xFF, h6 >> 8 & 0xFF, h6 & 0xFF]; + if (!this.is224) { + arr.push(h7 >> 24 & 0xFF, h7 >> 16 & 0xFF, h7 >> 8 & 0xFF, h7 & 0xFF); + } + return arr; + }; + Sha256.prototype.array = Sha256.prototype.digest; + Sha256.prototype.arrayBuffer = function () { + this.finalize(); + var buffer = new ArrayBuffer(this.is224 ? 28 : 32); + var dataView = new DataView(buffer); + dataView.setUint32(0, this.h0); + dataView.setUint32(4, this.h1); + dataView.setUint32(8, this.h2); + dataView.setUint32(12, this.h3); + dataView.setUint32(16, this.h4); + dataView.setUint32(20, this.h5); + dataView.setUint32(24, this.h6); + if (!this.is224) { + dataView.setUint32(28, this.h7); + } + return buffer; + }; + function HmacSha256(key, is224, sharedMemory) { + var i, + type = typeof key; + if (type === 'string') { + var bytes = [], + length = key.length, + index = 0, + code; + for (i = 0; i < length; ++i) { + code = key.charCodeAt(i); + if (code < 0x80) { + bytes[index++] = code; + } else if (code < 0x800) { + bytes[index++] = 0xc0 | code >> 6; + bytes[index++] = 0x80 | code & 0x3f; + } else if (code < 0xd800 || code >= 0xe000) { + bytes[index++] = 0xe0 | code >> 12; + bytes[index++] = 0x80 | code >> 6 & 0x3f; + bytes[index++] = 0x80 | code & 0x3f; + } else { + code = 0x10000 + ((code & 0x3ff) << 10 | key.charCodeAt(++i) & 0x3ff); + bytes[index++] = 0xf0 | code >> 18; + bytes[index++] = 0x80 | code >> 12 & 0x3f; + bytes[index++] = 0x80 | code >> 6 & 0x3f; + bytes[index++] = 0x80 | code & 0x3f; + } + } + key = bytes; + } else { + if (type === 'object') { + if (key === null) { + throw new Error(ERROR); + } else if (ARRAY_BUFFER && key.constructor === ArrayBuffer) { + key = new Uint8Array(key); + } else if (!Array.isArray(key)) { + if (!ARRAY_BUFFER || !ArrayBuffer.isView(key)) { + throw new Error(ERROR); + } + } + } else { + throw new Error(ERROR); + } + } + if (key.length > 64) { + key = new Sha256(is224, true).update(key).array(); + } + var oKeyPad = [], + iKeyPad = []; + for (i = 0; i < 64; ++i) { + var b = key[i] || 0; + oKeyPad[i] = 0x5c ^ b; + iKeyPad[i] = 0x36 ^ b; + } + Sha256.call(this, is224, sharedMemory); + this.update(iKeyPad); + this.oKeyPad = oKeyPad; + this.inner = true; + this.sharedMemory = sharedMemory; + } + HmacSha256.prototype = new Sha256(); + HmacSha256.prototype.finalize = function () { + Sha256.prototype.finalize.call(this); + if (this.inner) { + this.inner = false; + var innerHash = this.array(); + Sha256.call(this, this.is224, this.sharedMemory); + this.update(this.oKeyPad); + this.update(innerHash); + Sha256.prototype.finalize.call(this); + } + }; + var exports = createMethod(); + exports.sha256 = exports; + exports.sha224 = createMethod(true); + exports.sha256.hmac = createHmacMethod(); + exports.sha224.hmac = createHmacMethod(true); + if (COMMON_JS) { + module.exports = exports; + } else { + root.sha256 = exports.sha256; + root.sha224 = exports.sha224; + if (AMD) { + !(__WEBPACK_AMD_DEFINE_RESULT__ = (function () { + return exports; + }).call(exports, __webpack_require__, exports, module), + __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); + } + } +})(); + +/***/ }), + /***/ 763: /***/ (function(module, exports, __webpack_require__) { @@ -28614,6 +29086,11 @@ module.exports = _unsupportedIterableToArray, module.exports.__esModule = true, /******/ __webpack_require__.m = __webpack_modules__; /******/ /************************************************************************/ +/******/ /* webpack/runtime/amd options */ +/******/ !function() { +/******/ __webpack_require__.amdO = {}; +/******/ }(); +/******/ /******/ /* webpack/runtime/compat get default export */ /******/ !function() { /******/ // getDefaultExport function for compatibility with non-harmony modules @@ -51555,11 +52032,11 @@ var loadedScripts=new Map();var loadScript=function loadScript(fileUrl){var asyn ;// CONCATENATED MODULE: ./src/utils/misc.ts function getLastPathElement(url){if(url===undefined){return undefined;}if(!url){return"";}var s=url.split("/");return s[s.length-1];}function sleep(ms){return new Promise(function(resolve){return setTimeout(resolve,ms);});} ;// CONCATENATED MODULE: ./src/api.tsx -var apiUrl="/api";var staticPath="./staticdata";var ObjectType=/*#__PURE__*/function(ObjectType){ObjectType["Rendered"]="rendered";ObjectType["Remote"]="remote";ObjectType["Applied"]="applied";return ObjectType;}({});var RealOrStaticApi=/*#__PURE__*/function(){function RealOrStaticApi(){classCallCheck_classCallCheck(this,RealOrStaticApi);this.api=void 0;this.api=this.buildApi();}createClass_createClass(RealOrStaticApi,[{key:"buildApi",value:function(){var _buildApi=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee(){var p;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee$(_context){while(1)switch(_context.prev=_context.next){case 0:p=loadScript(staticPath+"/summaries.js");_context.prev=1;_context.next=4;return p;case 4:return _context.abrupt("return",new StaticApi());case 7:_context.prev=7;_context.t0=_context["catch"](1);return _context.abrupt("return",new RealApi());case 10:case"end":return _context.stop();}},_callee,null,[[1,7]]);}));function buildApi(){return _buildApi.apply(this,arguments);}return buildApi;}()},{key:"deployNow",value:function(){var _deployNow=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee2(cluster,name,namespace){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee2$(_context2){while(1)switch(_context2.prev=_context2.next){case 0:_context2.next=2;return this.api;case 2:return _context2.abrupt("return",_context2.sent.deployNow(cluster,name,namespace));case 3:case"end":return _context2.stop();}},_callee2,this);}));function deployNow(_x,_x2,_x3){return _deployNow.apply(this,arguments);}return deployNow;}()},{key:"getResult",value:function(){var _getResult=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee3(resultId){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee3$(_context3){while(1)switch(_context3.prev=_context3.next){case 0:_context3.next=2;return this.api;case 2:return _context3.abrupt("return",_context3.sent.getResult(resultId));case 3:case"end":return _context3.stop();}},_callee3,this);}));function getResult(_x4){return _getResult.apply(this,arguments);}return getResult;}()},{key:"getResultObject",value:function(){var _getResultObject=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee4(resultId,ref,objectType){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee4$(_context4){while(1)switch(_context4.prev=_context4.next){case 0:_context4.next=2;return this.api;case 2:return _context4.abrupt("return",_context4.sent.getResultObject(resultId,ref,objectType));case 3:case"end":return _context4.stop();}},_callee4,this);}));function getResultObject(_x5,_x6,_x7){return _getResultObject.apply(this,arguments);}return getResultObject;}()},{key:"getResultSummary",value:function(){var _getResultSummary=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee5(resultId){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee5$(_context5){while(1)switch(_context5.prev=_context5.next){case 0:_context5.next=2;return this.api;case 2:return _context5.abrupt("return",_context5.sent.getResultSummary(resultId));case 3:case"end":return _context5.stop();}},_callee5,this);}));function getResultSummary(_x8){return _getResultSummary.apply(this,arguments);}return getResultSummary;}()},{key:"getShortNames",value:function(){var _getShortNames=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee6(){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee6$(_context6){while(1)switch(_context6.prev=_context6.next){case 0:_context6.next=2;return this.api;case 2:return _context6.abrupt("return",_context6.sent.getShortNames());case 3:case"end":return _context6.stop();}},_callee6,this);}));function getShortNames(){return _getShortNames.apply(this,arguments);}return getShortNames;}()},{key:"listenUpdates",value:function(){var _listenUpdates=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee7(filterProject,filterSubDir,handle){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee7$(_context7){while(1)switch(_context7.prev=_context7.next){case 0:_context7.next=2;return this.api;case 2:return _context7.abrupt("return",_context7.sent.listenUpdates(filterProject,filterSubDir,handle));case 3:case"end":return _context7.stop();}},_callee7,this);}));function listenUpdates(_x9,_x10,_x11){return _listenUpdates.apply(this,arguments);}return listenUpdates;}()},{key:"reconcileNow",value:function(){var _reconcileNow=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee8(cluster,name,namespace){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee8$(_context8){while(1)switch(_context8.prev=_context8.next){case 0:_context8.next=2;return this.api;case 2:return _context8.abrupt("return",_context8.sent.reconcileNow(cluster,name,namespace));case 3:case"end":return _context8.stop();}},_callee8,this);}));function reconcileNow(_x12,_x13,_x14){return _reconcileNow.apply(this,arguments);}return reconcileNow;}()},{key:"validateNow",value:function(){var _validateNow=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee9(project,target){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee9$(_context9){while(1)switch(_context9.prev=_context9.next){case 0:_context9.next=2;return this.api;case 2:return _context9.abrupt("return",_context9.sent.validateNow(project,target));case 3:case"end":return _context9.stop();}},_callee9,this);}));function validateNow(_x15,_x16){return _validateNow.apply(this,arguments);}return validateNow;}()}]);return RealOrStaticApi;}();var api=new RealOrStaticApi();var RealApi=/*#__PURE__*/function(){function RealApi(){classCallCheck_classCallCheck(this,RealApi);}createClass_createClass(RealApi,[{key:"getShortNames",value:function(){var _getShortNames2=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee10(){var url;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee10$(_context10){while(1)switch(_context10.prev=_context10.next){case 0:url="".concat(apiUrl,"/getShortNames");return _context10.abrupt("return",fetch(url).then(handleErrors).then(function(response){return response.json();}));case 2:case"end":return _context10.stop();}},_callee10);}));function getShortNames(){return _getShortNames2.apply(this,arguments);}return getShortNames;}()},{key:"listenUpdates",value:function(){var _listenUpdates2=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee11(filterProject,filterSubDir,handle){var host,url,params,ws,cancelled,connect;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee11$(_context11){while(1)switch(_context11.prev=_context11.next){case 0:host=window.location.host;if(false){}url="ws://".concat(host).concat(apiUrl,"/ws");params=new URLSearchParams();if(filterProject){params.set("filterProject",filterProject);}if(filterSubDir){params.set("filterSubDir",filterSubDir);}url+="?"+params.toString();cancelled=false;connect=function connect(){if(cancelled){return;}console.log("ws connect: "+url);ws=new WebSocket(url);ws.onopen=function(){console.log("ws connected");};ws.onclose=function(event){console.log("ws close");if(!cancelled){sleep(5000).then(connect);}};ws.onmessage=function(event){if(cancelled){return;}var msg=JSON.parse(event.data);handle(msg);};};connect();return _context11.abrupt("return",function(){console.log("ws cancel");cancelled=true;if(ws){ws.close();}});case 11:case"end":return _context11.stop();}},_callee11);}));function listenUpdates(_x17,_x18,_x19){return _listenUpdates2.apply(this,arguments);}return listenUpdates;}()},{key:"getResult",value:function(){var _getResult2=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee12(resultId){var url;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee12$(_context12){while(1)switch(_context12.prev=_context12.next){case 0:url="".concat(apiUrl,"/getResult?resultId=").concat(resultId);return _context12.abrupt("return",fetch(url).then(handleErrors).then(function(response){return response.text();}).then(function(json){return new CommandResult(json);}));case 2:case"end":return _context12.stop();}},_callee12);}));function getResult(_x20){return _getResult2.apply(this,arguments);}return getResult;}()},{key:"getResultSummary",value:function(){var _getResultSummary2=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee13(resultId){var url;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee13$(_context13){while(1)switch(_context13.prev=_context13.next){case 0:url="".concat(apiUrl,"/getResultSummary?resultId=").concat(resultId);return _context13.abrupt("return",fetch(url).then(handleErrors).then(function(response){return response.text();}).then(function(json){return new CommandResultSummary(json);}));case 2:case"end":return _context13.stop();}},_callee13);}));function getResultSummary(_x21){return _getResultSummary2.apply(this,arguments);}return getResultSummary;}()},{key:"getResultObject",value:function(){var _getResultObject2=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee14(resultId,ref,objectType){var url;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee14$(_context14){while(1)switch(_context14.prev=_context14.next){case 0:url="".concat(apiUrl,"/getResultObject?resultId=").concat(resultId,"&").concat(buildRefParams(ref),"&objectType=").concat(objectType);return _context14.abrupt("return",fetch(url).then(handleErrors).then(function(response){return response.json();}));case 2:case"end":return _context14.stop();}},_callee14);}));function getResultObject(_x22,_x23,_x24){return _getResultObject2.apply(this,arguments);}return getResultObject;}()},{key:"doPost",value:function(){var _doPost=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee15(f,body){var url;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee15$(_context15){while(1)switch(_context15.prev=_context15.next){case 0:url="".concat(apiUrl,"/").concat(f);return _context15.abrupt("return",fetch(url,{method:"POST",body:JSON.stringify(body),headers:{'Accept':'application/json','Content-Type':'application/json'}}).then(handleErrors));case 2:case"end":return _context15.stop();}},_callee15);}));function doPost(_x25,_x26){return _doPost.apply(this,arguments);}return doPost;}()},{key:"validateNow",value:function(){var _validateNow2=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee16(project,target){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee16$(_context16){while(1)switch(_context16.prev=_context16.next){case 0:return _context16.abrupt("return",this.doPost("validateNow",{"project":project,"target":target}));case 1:case"end":return _context16.stop();}},_callee16,this);}));function validateNow(_x27,_x28){return _validateNow2.apply(this,arguments);}return validateNow;}()},{key:"deployNow",value:function(){var _deployNow2=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee17(cluster,name,namespace){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee17$(_context17){while(1)switch(_context17.prev=_context17.next){case 0:return _context17.abrupt("return",this.doPost("deployNow",{"cluster":cluster,"name":name,"namespace":namespace}));case 1:case"end":return _context17.stop();}},_callee17,this);}));function deployNow(_x29,_x30,_x31){return _deployNow2.apply(this,arguments);}return deployNow;}()},{key:"reconcileNow",value:function(){var _reconcileNow2=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee18(cluster,name,namespace){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee18$(_context18){while(1)switch(_context18.prev=_context18.next){case 0:return _context18.abrupt("return",this.doPost("reconcileNow",{"cluster":cluster,"name":name,"namespace":namespace}));case 1:case"end":return _context18.stop();}},_callee18,this);}));function reconcileNow(_x32,_x33,_x34){return _reconcileNow2.apply(this,arguments);}return reconcileNow;}()}]);return RealApi;}();var StaticApi=/*#__PURE__*/function(){function StaticApi(){classCallCheck_classCallCheck(this,StaticApi);}createClass_createClass(StaticApi,[{key:"getShortNames",value:function(){var _getShortNames3=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee19(){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee19$(_context19){while(1)switch(_context19.prev=_context19.next){case 0:_context19.next=2;return loadScript(staticPath+"/shortnames.js");case 2:return _context19.abrupt("return",staticShortNames);case 3:case"end":return _context19.stop();}},_callee19);}));function getShortNames(){return _getShortNames3.apply(this,arguments);}return getShortNames;}()},{key:"listenUpdates",value:function(){var _listenUpdates3=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee20(filterProject,filterSubDir,handle){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee20$(_context20){while(1)switch(_context20.prev=_context20.next){case 0:_context20.next=2;return loadScript(staticPath+"/summaries.js");case 2:staticSummaries.forEach(function(rs){if(filterProject&&filterProject!==rs.project.normalizedGitUrl){return;}if(filterSubDir&&filterSubDir!==rs.project.subDir){return;}handle({"type":"update_summary","summary":rs});});return _context20.abrupt("return",function(){});case 4:case"end":return _context20.stop();}},_callee20);}));function listenUpdates(_x35,_x36,_x37){return _listenUpdates3.apply(this,arguments);}return listenUpdates;}()},{key:"getResult",value:function(){var _getResult3=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee21(resultId){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee21$(_context21){while(1)switch(_context21.prev=_context21.next){case 0:_context21.next=2;return loadScript(staticPath+"/result-".concat(resultId,".js"));case 2:return _context21.abrupt("return",staticResults.get(resultId));case 3:case"end":return _context21.stop();}},_callee21);}));function getResult(_x38){return _getResult3.apply(this,arguments);}return getResult;}()},{key:"getResultSummary",value:function(){var _getResultSummary3=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee22(resultId){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee22$(_context22){while(1)switch(_context22.prev=_context22.next){case 0:_context22.next=2;return loadScript(staticPath+"/summaries.js");case 2:return _context22.abrupt("return",staticSummaries.filter(function(s){return s.id===resultId;}).at(0));case 3:case"end":return _context22.stop();}},_callee22);}));function getResultSummary(_x39){return _getResultSummary3.apply(this,arguments);}return getResultSummary;}()},{key:"getResultObject",value:function(){var _getResultObject3=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee23(resultId,ref,objectType){var _result$objects;var result,object;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee23$(_context23){while(1)switch(_context23.prev=_context23.next){case 0:_context23.next=2;return this.getResult(resultId);case 2:result=_context23.sent;object=(_result$objects=result.objects)===null||_result$objects===void 0?void 0:_result$objects.find(function(x){return lodash_default().isEqual(x.ref,ref);});if(object){_context23.next=6;break;}throw new Error("object not found");case 6:_context23.t0=objectType;_context23.next=_context23.t0===ObjectType.Rendered?9:_context23.t0===ObjectType.Remote?10:_context23.t0===ObjectType.Applied?11:12;break;case 9:return _context23.abrupt("return",object.rendered);case 10:return _context23.abrupt("return",object.remote);case 11:return _context23.abrupt("return",object.applied);case 12:throw new Error("unknown object type "+objectType);case 13:case"end":return _context23.stop();}},_callee23,this);}));function getResultObject(_x40,_x41,_x42){return _getResultObject3.apply(this,arguments);}return getResultObject;}()},{key:"validateNow",value:function validateNow(project,target){throw new Error("not implemented");}},{key:"reconcileNow",value:function reconcileNow(cluster,name,namespace){throw new Error("not implemented");}},{key:"deployNow",value:function deployNow(cluster,name,namespace){throw new Error("not implemented");}}]);return StaticApi;}();function handleErrors(response){if(!response.ok){throw Error(response.statusText);}return response;}function buildRefParams(ref){var params=new URLSearchParams();params.set("kind",ref.kind);params.set("name",ref.name);if(ref.group){params.set("group",ref.group);}if(ref.version){params.set("version",ref.version);}if(ref.namespace){params.set("namespace",ref.namespace);}return params.toString();}function buildRefString(ref){if(ref.namespace){return"".concat(ref.namespace,"/").concat(ref.kind,"/").concat(ref.name);}else{if(ref.name){return"".concat(ref.kind,"/").concat(ref.name);}else{return ref.kind;}}}function buildRefKindElement(ref,element){var tooltip=/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{zIndex:1000,children:[/*#__PURE__*/(0,jsx_runtime.jsxs)(Typography_Typography,{children:["ApiVersion: ",[ref.group,ref.version].filter(function(x){return x;}).join("/")]}),/*#__PURE__*/(0,jsx_runtime.jsx)("br",{}),/*#__PURE__*/(0,jsx_runtime.jsxs)(Typography_Typography,{children:["Kind: ",ref.kind]})]});return/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:tooltip,children:element?element:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{children:ref.kind})});}function buildObjectRefFromObject(obj){var apiVersion=obj.apiVersion;var s=apiVersion.split("/",2);var ref=new ObjectRef();if(s.length===1){ref.version=s[0];}else{ref.group=s[0];ref.version=s[1];}ref.kind=obj.kind;ref.namespace=obj.metadata.namespace;ref.name=obj.metadata.name;return ref;}function findObjectByRef(l,ref,filter){return l===null||l===void 0?void 0:l.find(function(x){if(filter&&!filter(x)){return false;}return lodash_default().isEqual(x.ref,ref);});}function usePromise(p){if(p===undefined){throw new Promise(function(){return undefined;});}var promise=p;if(promise.status==='fulfilled'){return promise.value;}else if(promise.status==='rejected'){throw promise.reason;}else if(promise.status==='pending'){throw promise;}else{promise.status='pending';p.then(function(result){promise.status='fulfilled';promise.value=result;},function(reason){promise.status='rejected';promise.reason=reason;});throw promise;}} +var apiUrl="/api";var staticPath="./staticdata";var ObjectType=/*#__PURE__*/function(ObjectType){ObjectType["Rendered"]="rendered";ObjectType["Remote"]="remote";ObjectType["Applied"]="applied";return ObjectType;}({});var RealOrStaticApi=/*#__PURE__*/function(){function RealOrStaticApi(){classCallCheck_classCallCheck(this,RealOrStaticApi);this.api=void 0;this.api=this.buildApi();}createClass_createClass(RealOrStaticApi,[{key:"buildApi",value:function(){var _buildApi=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee(){var p;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee$(_context){while(1)switch(_context.prev=_context.next){case 0:p=loadScript(staticPath+"/summaries.js");_context.prev=1;_context.next=4;return p;case 4:return _context.abrupt("return",new StaticApi());case 7:_context.prev=7;_context.t0=_context["catch"](1);return _context.abrupt("return",new RealApi());case 10:case"end":return _context.stop();}},_callee,null,[[1,7]]);}));function buildApi(){return _buildApi.apply(this,arguments);}return buildApi;}()},{key:"deployNow",value:function(){var _deployNow=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee2(cluster,name,namespace){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee2$(_context2){while(1)switch(_context2.prev=_context2.next){case 0:_context2.next=2;return this.api;case 2:return _context2.abrupt("return",_context2.sent.deployNow(cluster,name,namespace));case 3:case"end":return _context2.stop();}},_callee2,this);}));function deployNow(_x,_x2,_x3){return _deployNow.apply(this,arguments);}return deployNow;}()},{key:"getResult",value:function(){var _getResult=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee3(resultId){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee3$(_context3){while(1)switch(_context3.prev=_context3.next){case 0:_context3.next=2;return this.api;case 2:return _context3.abrupt("return",_context3.sent.getResult(resultId));case 3:case"end":return _context3.stop();}},_callee3,this);}));function getResult(_x4){return _getResult.apply(this,arguments);}return getResult;}()},{key:"getResultObject",value:function(){var _getResultObject=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee4(resultId,ref,objectType){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee4$(_context4){while(1)switch(_context4.prev=_context4.next){case 0:_context4.next=2;return this.api;case 2:return _context4.abrupt("return",_context4.sent.getResultObject(resultId,ref,objectType));case 3:case"end":return _context4.stop();}},_callee4,this);}));function getResultObject(_x5,_x6,_x7){return _getResultObject.apply(this,arguments);}return getResultObject;}()},{key:"getResultSummary",value:function(){var _getResultSummary=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee5(resultId){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee5$(_context5){while(1)switch(_context5.prev=_context5.next){case 0:_context5.next=2;return this.api;case 2:return _context5.abrupt("return",_context5.sent.getResultSummary(resultId));case 3:case"end":return _context5.stop();}},_callee5,this);}));function getResultSummary(_x8){return _getResultSummary.apply(this,arguments);}return getResultSummary;}()},{key:"getShortNames",value:function(){var _getShortNames=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee6(){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee6$(_context6){while(1)switch(_context6.prev=_context6.next){case 0:_context6.next=2;return this.api;case 2:return _context6.abrupt("return",_context6.sent.getShortNames());case 3:case"end":return _context6.stop();}},_callee6,this);}));function getShortNames(){return _getShortNames.apply(this,arguments);}return getShortNames;}()},{key:"listenUpdates",value:function(){var _listenUpdates=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee7(filterProject,filterSubDir,handle){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee7$(_context7){while(1)switch(_context7.prev=_context7.next){case 0:_context7.next=2;return this.api;case 2:return _context7.abrupt("return",_context7.sent.listenUpdates(filterProject,filterSubDir,handle));case 3:case"end":return _context7.stop();}},_callee7,this);}));function listenUpdates(_x9,_x10,_x11){return _listenUpdates.apply(this,arguments);}return listenUpdates;}()},{key:"reconcileNow",value:function(){var _reconcileNow=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee8(cluster,name,namespace){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee8$(_context8){while(1)switch(_context8.prev=_context8.next){case 0:_context8.next=2;return this.api;case 2:return _context8.abrupt("return",_context8.sent.reconcileNow(cluster,name,namespace));case 3:case"end":return _context8.stop();}},_callee8,this);}));function reconcileNow(_x12,_x13,_x14){return _reconcileNow.apply(this,arguments);}return reconcileNow;}()},{key:"validateNow",value:function(){var _validateNow=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee9(project,target){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee9$(_context9){while(1)switch(_context9.prev=_context9.next){case 0:_context9.next=2;return this.api;case 2:return _context9.abrupt("return",_context9.sent.validateNow(project,target));case 3:case"end":return _context9.stop();}},_callee9,this);}));function validateNow(_x15,_x16){return _validateNow.apply(this,arguments);}return validateNow;}()}]);return RealOrStaticApi;}();var api=new RealOrStaticApi();var RealApi=/*#__PURE__*/function(){function RealApi(){classCallCheck_classCallCheck(this,RealApi);}createClass_createClass(RealApi,[{key:"getShortNames",value:function(){var _getShortNames2=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee10(){var url;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee10$(_context10){while(1)switch(_context10.prev=_context10.next){case 0:url="".concat(apiUrl,"/getShortNames");return _context10.abrupt("return",fetch(url).then(handleErrors).then(function(response){return response.json();}));case 2:case"end":return _context10.stop();}},_callee10);}));function getShortNames(){return _getShortNames2.apply(this,arguments);}return getShortNames;}()},{key:"listenUpdates",value:function(){var _listenUpdates2=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee11(filterProject,filterSubDir,handle){var host,url,params,ws,cancelled,connect;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee11$(_context11){while(1)switch(_context11.prev=_context11.next){case 0:host=window.location.host;if(false){}url="ws://".concat(host).concat(apiUrl,"/ws");params=new URLSearchParams();if(filterProject){params.set("filterProject",filterProject);}if(filterSubDir){params.set("filterSubDir",filterSubDir);}url+="?"+params.toString();cancelled=false;connect=function connect(){if(cancelled){return;}console.log("ws connect: "+url);ws=new WebSocket(url);ws.onopen=function(){console.log("ws connected");};ws.onclose=function(event){console.log("ws close");if(!cancelled){sleep(5000).then(connect);}};ws.onerror=function(event){console.log("ws error",event);};ws.onmessage=function(event){if(cancelled){return;}var msg=JSON.parse(event.data);handle(msg);};};connect();return _context11.abrupt("return",function(){console.log("ws cancel");cancelled=true;if(ws){ws.close();}});case 11:case"end":return _context11.stop();}},_callee11);}));function listenUpdates(_x17,_x18,_x19){return _listenUpdates2.apply(this,arguments);}return listenUpdates;}()},{key:"getResult",value:function(){var _getResult2=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee12(resultId){var url;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee12$(_context12){while(1)switch(_context12.prev=_context12.next){case 0:url="".concat(apiUrl,"/getResult?resultId=").concat(resultId);return _context12.abrupt("return",fetch(url).then(handleErrors).then(function(response){return response.text();}).then(function(json){return new CommandResult(json);}));case 2:case"end":return _context12.stop();}},_callee12);}));function getResult(_x20){return _getResult2.apply(this,arguments);}return getResult;}()},{key:"getResultSummary",value:function(){var _getResultSummary2=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee13(resultId){var url;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee13$(_context13){while(1)switch(_context13.prev=_context13.next){case 0:url="".concat(apiUrl,"/getResultSummary?resultId=").concat(resultId);return _context13.abrupt("return",fetch(url).then(handleErrors).then(function(response){return response.text();}).then(function(json){return new CommandResultSummary(json);}));case 2:case"end":return _context13.stop();}},_callee13);}));function getResultSummary(_x21){return _getResultSummary2.apply(this,arguments);}return getResultSummary;}()},{key:"getResultObject",value:function(){var _getResultObject2=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee14(resultId,ref,objectType){var url;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee14$(_context14){while(1)switch(_context14.prev=_context14.next){case 0:url="".concat(apiUrl,"/getResultObject?resultId=").concat(resultId,"&").concat(buildRefParams(ref),"&objectType=").concat(objectType);return _context14.abrupt("return",fetch(url).then(handleErrors).then(function(response){return response.json();}));case 2:case"end":return _context14.stop();}},_callee14);}));function getResultObject(_x22,_x23,_x24){return _getResultObject2.apply(this,arguments);}return getResultObject;}()},{key:"doPost",value:function(){var _doPost=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee15(f,body){var url;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee15$(_context15){while(1)switch(_context15.prev=_context15.next){case 0:url="".concat(apiUrl,"/").concat(f);return _context15.abrupt("return",fetch(url,{method:"POST",body:JSON.stringify(body),headers:{'Accept':'application/json','Content-Type':'application/json'}}).then(handleErrors));case 2:case"end":return _context15.stop();}},_callee15);}));function doPost(_x25,_x26){return _doPost.apply(this,arguments);}return doPost;}()},{key:"validateNow",value:function(){var _validateNow2=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee16(project,target){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee16$(_context16){while(1)switch(_context16.prev=_context16.next){case 0:return _context16.abrupt("return",this.doPost("validateNow",{"project":project,"target":target}));case 1:case"end":return _context16.stop();}},_callee16,this);}));function validateNow(_x27,_x28){return _validateNow2.apply(this,arguments);}return validateNow;}()},{key:"deployNow",value:function(){var _deployNow2=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee17(cluster,name,namespace){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee17$(_context17){while(1)switch(_context17.prev=_context17.next){case 0:return _context17.abrupt("return",this.doPost("deployNow",{"cluster":cluster,"name":name,"namespace":namespace}));case 1:case"end":return _context17.stop();}},_callee17,this);}));function deployNow(_x29,_x30,_x31){return _deployNow2.apply(this,arguments);}return deployNow;}()},{key:"reconcileNow",value:function(){var _reconcileNow2=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee18(cluster,name,namespace){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee18$(_context18){while(1)switch(_context18.prev=_context18.next){case 0:return _context18.abrupt("return",this.doPost("reconcileNow",{"cluster":cluster,"name":name,"namespace":namespace}));case 1:case"end":return _context18.stop();}},_callee18,this);}));function reconcileNow(_x32,_x33,_x34){return _reconcileNow2.apply(this,arguments);}return reconcileNow;}()}]);return RealApi;}();var StaticApi=/*#__PURE__*/function(){function StaticApi(){classCallCheck_classCallCheck(this,StaticApi);}createClass_createClass(StaticApi,[{key:"getShortNames",value:function(){var _getShortNames3=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee19(){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee19$(_context19){while(1)switch(_context19.prev=_context19.next){case 0:_context19.next=2;return loadScript(staticPath+"/shortnames.js");case 2:return _context19.abrupt("return",staticShortNames);case 3:case"end":return _context19.stop();}},_callee19);}));function getShortNames(){return _getShortNames3.apply(this,arguments);}return getShortNames;}()},{key:"listenUpdates",value:function(){var _listenUpdates3=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee20(filterProject,filterSubDir,handle){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee20$(_context20){while(1)switch(_context20.prev=_context20.next){case 0:_context20.next=2;return loadScript(staticPath+"/summaries.js");case 2:staticSummaries.forEach(function(rs){if(filterProject&&filterProject!==rs.project.normalizedGitUrl){return;}if(filterSubDir&&filterSubDir!==rs.project.subDir){return;}handle({"type":"update_summary","summary":rs});});return _context20.abrupt("return",function(){});case 4:case"end":return _context20.stop();}},_callee20);}));function listenUpdates(_x35,_x36,_x37){return _listenUpdates3.apply(this,arguments);}return listenUpdates;}()},{key:"getResult",value:function(){var _getResult3=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee21(resultId){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee21$(_context21){while(1)switch(_context21.prev=_context21.next){case 0:_context21.next=2;return loadScript(staticPath+"/result-".concat(resultId,".js"));case 2:return _context21.abrupt("return",staticResults.get(resultId));case 3:case"end":return _context21.stop();}},_callee21);}));function getResult(_x38){return _getResult3.apply(this,arguments);}return getResult;}()},{key:"getResultSummary",value:function(){var _getResultSummary3=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee22(resultId){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee22$(_context22){while(1)switch(_context22.prev=_context22.next){case 0:_context22.next=2;return loadScript(staticPath+"/summaries.js");case 2:return _context22.abrupt("return",staticSummaries.filter(function(s){return s.id===resultId;}).at(0));case 3:case"end":return _context22.stop();}},_callee22);}));function getResultSummary(_x39){return _getResultSummary3.apply(this,arguments);}return getResultSummary;}()},{key:"getResultObject",value:function(){var _getResultObject3=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee23(resultId,ref,objectType){var _result$objects;var result,object;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee23$(_context23){while(1)switch(_context23.prev=_context23.next){case 0:_context23.next=2;return this.getResult(resultId);case 2:result=_context23.sent;object=(_result$objects=result.objects)===null||_result$objects===void 0?void 0:_result$objects.find(function(x){return lodash_default().isEqual(x.ref,ref);});if(object){_context23.next=6;break;}throw new Error("object not found");case 6:_context23.t0=objectType;_context23.next=_context23.t0===ObjectType.Rendered?9:_context23.t0===ObjectType.Remote?10:_context23.t0===ObjectType.Applied?11:12;break;case 9:return _context23.abrupt("return",object.rendered);case 10:return _context23.abrupt("return",object.remote);case 11:return _context23.abrupt("return",object.applied);case 12:throw new Error("unknown object type "+objectType);case 13:case"end":return _context23.stop();}},_callee23,this);}));function getResultObject(_x40,_x41,_x42){return _getResultObject3.apply(this,arguments);}return getResultObject;}()},{key:"validateNow",value:function validateNow(project,target){throw new Error("not implemented");}},{key:"reconcileNow",value:function reconcileNow(cluster,name,namespace){throw new Error("not implemented");}},{key:"deployNow",value:function deployNow(cluster,name,namespace){throw new Error("not implemented");}}]);return StaticApi;}();function handleErrors(response){if(!response.ok){throw Error(response.statusText);}return response;}function buildRefParams(ref){var params=new URLSearchParams();params.set("kind",ref.kind);params.set("name",ref.name);if(ref.group){params.set("group",ref.group);}if(ref.version){params.set("version",ref.version);}if(ref.namespace){params.set("namespace",ref.namespace);}return params.toString();}function buildRefString(ref){if(ref.namespace){return"".concat(ref.namespace,"/").concat(ref.kind,"/").concat(ref.name);}else{if(ref.name){return"".concat(ref.kind,"/").concat(ref.name);}else{return ref.kind;}}}function buildRefKindElement(ref,element){var tooltip=/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{zIndex:1000,children:[/*#__PURE__*/(0,jsx_runtime.jsxs)(Typography_Typography,{children:["ApiVersion: ",[ref.group,ref.version].filter(function(x){return x;}).join("/")]}),/*#__PURE__*/(0,jsx_runtime.jsx)("br",{}),/*#__PURE__*/(0,jsx_runtime.jsxs)(Typography_Typography,{children:["Kind: ",ref.kind]})]});return/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:tooltip,children:element?element:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{children:ref.kind})});}function buildObjectRefFromObject(obj){var apiVersion=obj.apiVersion;var s=apiVersion.split("/",2);var ref=new ObjectRef();if(s.length===1){ref.version=s[0];}else{ref.group=s[0];ref.version=s[1];}ref.kind=obj.kind;ref.namespace=obj.metadata.namespace;ref.name=obj.metadata.name;return ref;}function findObjectByRef(l,ref,filter){return l===null||l===void 0?void 0:l.find(function(x){if(filter&&!filter(x)){return false;}return lodash_default().isEqual(x.ref,ref);});}function usePromise(p){if(p===undefined){throw new Promise(function(){return undefined;});}var promise=p;if(promise.status==='fulfilled'){return promise.value;}else if(promise.status==='rejected'){throw promise.reason;}else if(promise.status==='pending'){throw promise;}else{promise.status='pending';p.then(function(result){promise.status='fulfilled';promise.value=result;},function(reason){promise.status='rejected';promise.reason=reason;});throw promise;}} ;// CONCATENATED MODULE: ./src/project-summaries.ts function compareSummaries(a,b){return b.commandInfo.startTime.localeCompare(a.commandInfo.startTime)||b.commandInfo.endTime.localeCompare(b.commandInfo.endTime)||(b.commandInfo.command||"").localeCompare(a.commandInfo.command||"");}function buildProjectSummaries(summaries,validateResults){var sorted=Array.from(summaries.values());sorted.sort(compareSummaries);var m=new Map();sorted.forEach(function(rs){var projectKey=JSON.stringify(rs.projectKey);var p=m.get(projectKey);if(!p){p={project:rs.projectKey,targets:[]};m.set(projectKey,p);}var ptKey=new ProjectTargetKey();ptKey.project=rs.projectKey;ptKey.target=rs.targetKey;var vr=validateResults.get(JSON.stringify(ptKey));var target=p.targets.find(function(t){return lodash_default().isEqual(t.target,rs.targetKey);});if(!target){target={target:rs.targetKey,lastValidateResult:vr,commandResults:[]};p.targets.push(target);}target.commandResults.push(rs);});var ret=[];m.forEach(function(p){p.targets.sort(function(a,b){var _ref;return(a.target.targetName||"").localeCompare(b.target.targetName||"")||a.target.clusterId.localeCompare(b.target.clusterId)||((_ref=a.target.discriminator||"")===null||_ref===void 0?void 0:_ref.localeCompare(b.target.discriminator||""));});ret.push(p);});ret.sort(function(a,b){return(a.project.gitRepoKey||"").localeCompare(b.project.gitRepoKey||"")||(a.project.subDir||"").localeCompare(b.project.subDir||"");});return ret;} ;// CONCATENATED MODULE: ./src/components/App.tsx -function useAppOutletContext(){return useOutletContext();}var AppContext=/*#__PURE__*/(0,react.createContext)({summaries:new Map(),projects:[],validateResults:new Map()});var App=function App(){var _useState=(0,react.useState)(),_useState2=slicedToArray_slicedToArray(_useState,2),filters=_useState2[0],setFilters=_useState2[1];var summaries=(0,react.useRef)(new Map());var validateResults=(0,react.useRef)(new Map());(0,react.useEffect)(function(){var updateSummary=function updateSummary(rs){console.log("update_summary",rs.id,rs.commandInfo.startTime);summaries.current.set(rs.id,rs);};var deleteSummary=function deleteSummary(id){console.log("delete_summary",id);summaries.current.delete(id);};var updateValidateResult=function updateValidateResult(key,vr){console.log("validate_result",key);validateResults.current.set(JSON.stringify(key),vr);};console.log("starting listenResults");var cancel=api.listenUpdates(undefined,undefined,function(msg){switch(msg.type){case"update_summary":updateSummary(msg.summary);break;case"delete_summary":deleteSummary(msg.id);break;case"validate_result":updateValidateResult(msg.key,msg.result);break;}});return function(){console.log("cancel listenResults");cancel.then(function(c){return c();});};},[]);var projects=(0,react.useMemo)(function(){console.log("buildProjectSummaries");return buildProjectSummaries(summaries.current,validateResults.current);},[summaries,validateResults]);var appContext={summaries:summaries.current,projects:projects,validateResults:validateResults.current};var outletContext={filters:filters,setFilters:setFilters};return/*#__PURE__*/(0,jsx_runtime.jsx)(AppContext.Provider,{value:appContext,children:/*#__PURE__*/(0,jsx_runtime.jsx)(styles_ThemeProvider_ThemeProvider,{theme:theme_light,children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{width:"100%",height:"100%",children:/*#__PURE__*/(0,jsx_runtime.jsx)(LeftDrawer,{content:/*#__PURE__*/(0,jsx_runtime.jsx)(Outlet,{context:outletContext}),context:outletContext})})})});};/* harmony default export */ var components_App = (App); +function useAppOutletContext(){return useOutletContext();}var AppContext=/*#__PURE__*/(0,react.createContext)({summaries:new Map(),projects:[],validateResults:new Map()});var App=function App(){var _useState=(0,react.useState)(),_useState2=slicedToArray_slicedToArray(_useState,2),filters=_useState2[0],setFilters=_useState2[1];var summariesRef=(0,react.useRef)(new Map());var validateResultsRef=(0,react.useRef)(new Map());var _useState3=(0,react.useState)(summariesRef.current),_useState4=slicedToArray_slicedToArray(_useState3,2),summaries=_useState4[0],setSummaries=_useState4[1];var _useState5=(0,react.useState)(validateResultsRef.current),_useState6=slicedToArray_slicedToArray(_useState5,2),validateResults=_useState6[0],setValidateResults=_useState6[1];(0,react.useEffect)(function(){var updateSummary=function updateSummary(rs){console.log("update_summary",rs.id,rs.commandInfo.startTime);summariesRef.current.set(rs.id,rs);setSummaries(new Map(summariesRef.current));};var deleteSummary=function deleteSummary(id){console.log("delete_summary",id);summariesRef.current.delete(id);setSummaries(new Map(summariesRef.current));};var updateValidateResult=function updateValidateResult(key,vr){console.log("validate_result",key);validateResultsRef.current.set(JSON.stringify(key),vr);setValidateResults(new Map(validateResultsRef.current));};console.log("starting listenResults");var cancel=api.listenUpdates(undefined,undefined,function(msg){switch(msg.type){case"update_summary":updateSummary(msg.summary);break;case"delete_summary":deleteSummary(msg.id);break;case"validate_result":updateValidateResult(msg.key,msg.result);break;}});return function(){console.log("cancel listenResults");cancel.then(function(c){return c();});};},[]);var projects=(0,react.useMemo)(function(){return buildProjectSummaries(summaries,validateResults);},[summaries,validateResults]);var appContext={summaries:summariesRef.current,projects:projects,validateResults:validateResults};var outletContext={filters:filters,setFilters:setFilters};return/*#__PURE__*/(0,jsx_runtime.jsx)(AppContext.Provider,{value:appContext,children:/*#__PURE__*/(0,jsx_runtime.jsx)(styles_ThemeProvider_ThemeProvider,{theme:theme_light,children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{width:"100%",height:"100%",children:/*#__PURE__*/(0,jsx_runtime.jsx)(LeftDrawer,{content:/*#__PURE__*/(0,jsx_runtime.jsx)(Outlet,{context:outletContext}),context:outletContext})})})});};/* harmony default export */ var components_App = (App); ;// CONCATENATED MODULE: ./src/components/targets-view/Projects.tsx var ProjectItem=function ProjectItem(props){var name=getLastPathElement(props.ps.project.gitRepoKey);var subDir=props.ps.project.subDir;var projectInfo=/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{children:[props.ps.project.gitRepoKey,/*#__PURE__*/(0,jsx_runtime.jsx)("br",{}),props.ps.project.subDir?/*#__PURE__*/(0,jsx_runtime.jsxs)(jsx_runtime.Fragment,{children:["SubDir: ",props.ps.project.subDir,/*#__PURE__*/(0,jsx_runtime.jsx)("br",{})]}):/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{})]});return/*#__PURE__*/(0,jsx_runtime.jsx)(Paper_Paper,{elevation:5,sx:{width:"100%",height:"100%",borderRadius:'12px',border:'1px solid #59A588',boxShadow:'4px 4px 10px #1E617A',padding:'5px 16px'},children:/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",flexDirection:"column",justifyContent:"center",height:"100%",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",alignItems:"center",gap:"15px",children:name&&/*#__PURE__*/(0,jsx_runtime.jsxs)(jsx_runtime.Fragment,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{flexShrink:0,children:/*#__PURE__*/(0,jsx_runtime.jsx)(ProjectIcon,{})}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:projectInfo,children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"h6",textAlign:"left",textOverflow:"ellipsis",overflow:"hidden",flexGrow:1,children:name})})]})}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{children:subDir?/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{textAlign:"center",children:subDir}):/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{})}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{marginLeft:"auto"})})})]})});}; ;// CONCATENATED MODULE: ./node_modules/@mui/material/SvgIcon/svgIconClasses.js @@ -52708,8 +53185,10 @@ var ActionsMenu=function ActionsMenu(props){var _React$useState=react.useState(n /* harmony default export */ var PublishedWithChanges = (createSvgIcon( /*#__PURE__*/(0,jsx_runtime.jsx)("path", { d: "m17.66 9.53-7.07 7.07-4.24-4.24 1.41-1.41 2.83 2.83 5.66-5.66 1.41 1.41zM4 12c0-2.33 1.02-4.42 2.62-5.88L9 8.5v-6H3l2.2 2.2C3.24 6.52 2 9.11 2 12c0 5.19 3.95 9.45 9 9.95v-2.02c-3.94-.49-7-3.86-7-7.93zm18 0c0-5.19-3.95-9.45-9-9.95v2.02c3.94.49 7 3.86 7 7.93 0 2.33-1.02 4.42-2.62 5.88L15 15.5v6h6l-2.2-2.2c1.96-1.82 3.2-4.41 3.2-7.3z" }), 'PublishedWithChanges')); +;// CONCATENATED MODULE: ./src/utils/duration.ts +function formatDuration(ms,withMs){if(ms<0)ms=-ms;var time={day:Math.floor(ms/86400000),hour:Math.floor(ms/3600000)%24,minute:Math.floor(ms/60000)%60,second:Math.floor(ms/1000)%60,millisecond:withMs?Math.floor(ms)%1000:0};return Object.entries(time).filter(function(val){return val[1]!==0;}).map(function(val){return val[1]+' '+(val[1]!==1?val[0]+'s':val[0]);}).join(', ');}function formatDurationShort(ms){if(ms<0)ms=-ms;var time={d:Math.floor(ms/86400000),h:Math.floor(ms/3600000)%24,m:Math.floor(ms/60000)%60,s:Math.floor(ms/1000)%60,ms:Math.floor(ms)%1000};var f=Object.entries(time).find(function(val){return val[1]>0;});if(f===undefined){return"0s";}return f[1]+f[0];}var calcAgo=function calcAgo(startTime){var t1=new Date(startTime);var t2=new Date();var d=t2.getTime()-t1.getTime();return formatDurationShort(d);}; ;// CONCATENATED MODULE: ./src/components/targets-view/Targets.tsx -var StatusIcon=function StatusIcon(props){var icon;var theme=styles_useTheme_useTheme();if(props.ts.lastValidateResult===undefined){icon=/*#__PURE__*/(0,jsx_runtime.jsx)(MessageQuestionIcon,{color:theme.palette.error.main});}else if(props.ts.lastValidateResult.ready&&!props.ts.lastValidateResult.errors){var _props$ts$lastValidat,_props$ts$lastValidat2;if((_props$ts$lastValidat=props.ts.lastValidateResult.warnings)!==null&&_props$ts$lastValidat!==void 0&&_props$ts$lastValidat.length){icon=/*#__PURE__*/(0,jsx_runtime.jsx)(Favorite,{color:"warning"});}else if((_props$ts$lastValidat2=props.ts.lastValidateResult.drift)!==null&&_props$ts$lastValidat2!==void 0&&_props$ts$lastValidat2.length){icon=/*#__PURE__*/(0,jsx_runtime.jsx)(Favorite,{color:"primary"});}else{icon=/*#__PURE__*/(0,jsx_runtime.jsx)(Favorite,{color:"success"});}}else{icon=/*#__PURE__*/(0,jsx_runtime.jsx)(HeartBroken,{color:"error"});}var tooltip=[];if(props.ts.lastValidateResult===undefined){tooltip.push("No validation result available.");}else{var _props$ts$lastValidat3,_props$ts$lastValidat4,_props$ts$lastValidat5,_props$ts$lastValidat6;if(props.ts.lastValidateResult.ready&&!((_props$ts$lastValidat3=props.ts.lastValidateResult.errors)!==null&&_props$ts$lastValidat3!==void 0&&_props$ts$lastValidat3.length)){tooltip.push("Target is ready.");}else{tooltip.push("Target is not ready.");}if((_props$ts$lastValidat4=props.ts.lastValidateResult.errors)!==null&&_props$ts$lastValidat4!==void 0&&_props$ts$lastValidat4.length){tooltip.push("Target has ".concat(props.ts.lastValidateResult.errors.length," validation errors."));}if((_props$ts$lastValidat5=props.ts.lastValidateResult.warnings)!==null&&_props$ts$lastValidat5!==void 0&&_props$ts$lastValidat5.length){tooltip.push("Target has ".concat(props.ts.lastValidateResult.warnings.length," validation warnings."));}if((_props$ts$lastValidat6=props.ts.lastValidateResult.drift)!==null&&_props$ts$lastValidat6!==void 0&&_props$ts$lastValidat6.length){tooltip.push("Target has ".concat(props.ts.lastValidateResult.drift.length," drifted objects."));}}return/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:tooltip.map(function(t,i){return/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{children:t},i);}),children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",children:icon})});};var TargetItem=function TargetItem(props){var _props$ts$commandResu,_props$ts$commandResu2;var actionMenuItems=[];actionMenuItems.push({icon:/*#__PURE__*/(0,jsx_runtime.jsx)(PublishedWithChanges,{}),text:"Validate now",handler:function handler(){api.validateNow(props.ps.project,props.ts.target);}});var kd;var allKdEqual=true;(_props$ts$commandResu=props.ts.commandResults)===null||_props$ts$commandResu===void 0?void 0:_props$ts$commandResu.forEach(function(rs){if(rs.commandInfo.kluctlDeployment){if(!kd){kd=rs.commandInfo.kluctlDeployment;}else{if(kd.name!==rs.commandInfo.kluctlDeployment.name||kd.namespace!==rs.commandInfo.kluctlDeployment.namespace){allKdEqual=false;}}}});if(kd&&allKdEqual){actionMenuItems.push({icon:/*#__PURE__*/(0,jsx_runtime.jsx)(PublishedWithChanges,{}),text:"Reconcile",handler:function handler(){api.reconcileNow(props.ts.target.clusterId,kd.name,kd.namespace);}});actionMenuItems.push({icon:/*#__PURE__*/(0,jsx_runtime.jsx)(PublishedWithChanges,{}),text:"Deploy",handler:function handler(){api.deployNow(props.ts.target.clusterId,kd.name,kd.namespace);}});}var allContexts=[];var handleContext=function handleContext(c){if(!c){return;}if(allContexts.find(function(x){return x===c;})){return;}allContexts.push(c);};(_props$ts$commandResu2=props.ts.commandResults)===null||_props$ts$commandResu2===void 0?void 0:_props$ts$commandResu2.forEach(function(rs){handleContext(rs.commandInfo.contextOverride);handleContext(rs.target.context);});var contextTooltip=/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{textAlign:"center",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"subtitle2",children:"All known contexts:"}),allContexts.map(function(context,i){return/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"subtitle2",children:context},i);})]});var targetName=props.ts.target.targetName;if(!targetName){targetName="";}return/*#__PURE__*/(0,jsx_runtime.jsx)(Paper_Paper,{elevation:5,sx:{width:"100%",height:"100%",borderRadius:'12px',border:'1px solid #59A588',boxShadow:'4px 4px 10px #1E617A',padding:'20px 16px 12px 16px'},onClick:function onClick(e){return props.onSelectTarget(props.ts);},children:/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",flexDirection:"column",justifyContent:"space-between",height:"100%",children:[/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",gap:"15px",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{flexShrink:0,children:/*#__PURE__*/(0,jsx_runtime.jsx)(TargetIcon,{})}),/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{flexGrow:1,children:[/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"h6",textAlign:"left",textOverflow:"ellipsis",overflow:"hidden",flexGrow:1,children:targetName}),allContexts.length?/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:contextTooltip,children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"subtitle1",textAlign:"left",textOverflow:"ellipsis",overflow:"hidden",whiteSpace:"nowrap",fontSize:"14px",fontWeight:500,lineHeight:"19px",children:allContexts[0]})}):/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{})]})]}),/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",alignItems:"center",justifyContent:"space-between",children:[/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",gap:"6px",alignItems:"center",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:"Cluster ID: "+props.ts.target.clusterId,children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",children:/*#__PURE__*/(0,jsx_runtime.jsx)(CpuIcon,{})})}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:"Discriminator: "+props.ts.target.discriminator,children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",children:/*#__PURE__*/(0,jsx_runtime.jsx)(FingerScanIcon,{})})})]}),/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",gap:"6px",alignItems:"center",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(StatusIcon,_objectSpread2({},props)),/*#__PURE__*/(0,jsx_runtime.jsx)(ActionsMenu,{menuItems:actionMenuItems})]})]})]})});}; +var StatusIcon=function StatusIcon(props){var icon;var theme=styles_useTheme_useTheme();if(props.ts.lastValidateResult===undefined){icon=/*#__PURE__*/(0,jsx_runtime.jsx)(MessageQuestionIcon,{color:theme.palette.error.main});}else if(props.ts.lastValidateResult.ready&&!props.ts.lastValidateResult.errors){var _props$ts$lastValidat,_props$ts$lastValidat2;if((_props$ts$lastValidat=props.ts.lastValidateResult.warnings)!==null&&_props$ts$lastValidat!==void 0&&_props$ts$lastValidat.length){icon=/*#__PURE__*/(0,jsx_runtime.jsx)(Favorite,{color:"warning"});}else if((_props$ts$lastValidat2=props.ts.lastValidateResult.drift)!==null&&_props$ts$lastValidat2!==void 0&&_props$ts$lastValidat2.length){icon=/*#__PURE__*/(0,jsx_runtime.jsx)(Favorite,{color:"primary"});}else{icon=/*#__PURE__*/(0,jsx_runtime.jsx)(Favorite,{color:"success"});}}else{icon=/*#__PURE__*/(0,jsx_runtime.jsx)(HeartBroken,{color:"error"});}var tooltip=[];if(props.ts.lastValidateResult===undefined){tooltip.push("No validation result available.");}else{var _props$ts$lastValidat3,_props$ts$lastValidat4,_props$ts$lastValidat5,_props$ts$lastValidat6;if(props.ts.lastValidateResult.ready&&!((_props$ts$lastValidat3=props.ts.lastValidateResult.errors)!==null&&_props$ts$lastValidat3!==void 0&&_props$ts$lastValidat3.length)){tooltip.push("Target is ready.");}else{tooltip.push("Target is not ready.");}if((_props$ts$lastValidat4=props.ts.lastValidateResult.errors)!==null&&_props$ts$lastValidat4!==void 0&&_props$ts$lastValidat4.length){tooltip.push("Target has ".concat(props.ts.lastValidateResult.errors.length," validation errors."));}if((_props$ts$lastValidat5=props.ts.lastValidateResult.warnings)!==null&&_props$ts$lastValidat5!==void 0&&_props$ts$lastValidat5.length){tooltip.push("Target has ".concat(props.ts.lastValidateResult.warnings.length," validation warnings."));}if((_props$ts$lastValidat6=props.ts.lastValidateResult.drift)!==null&&_props$ts$lastValidat6!==void 0&&_props$ts$lastValidat6.length){tooltip.push("Target has ".concat(props.ts.lastValidateResult.drift.length," drifted objects."));}tooltip.push("Validation performed "+calcAgo(props.ts.lastValidateResult.startTime)+" ago");}return/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:tooltip.map(function(t){return/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{children:t},t);}),children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",children:icon})});};var TargetItem=function TargetItem(props){var _props$ts$commandResu,_props$ts$commandResu2;var actionMenuItems=[];actionMenuItems.push({icon:/*#__PURE__*/(0,jsx_runtime.jsx)(PublishedWithChanges,{}),text:"Validate now",handler:function handler(){api.validateNow(props.ps.project,props.ts.target);}});var kd;var allKdEqual=true;(_props$ts$commandResu=props.ts.commandResults)===null||_props$ts$commandResu===void 0?void 0:_props$ts$commandResu.forEach(function(rs){if(rs.commandInfo.kluctlDeployment){if(!kd){kd=rs.commandInfo.kluctlDeployment;}else{if(kd.name!==rs.commandInfo.kluctlDeployment.name||kd.namespace!==rs.commandInfo.kluctlDeployment.namespace){allKdEqual=false;}}}});if(kd&&allKdEqual){actionMenuItems.push({icon:/*#__PURE__*/(0,jsx_runtime.jsx)(PublishedWithChanges,{}),text:"Reconcile",handler:function handler(){api.reconcileNow(props.ts.target.clusterId,kd.name,kd.namespace);}});actionMenuItems.push({icon:/*#__PURE__*/(0,jsx_runtime.jsx)(PublishedWithChanges,{}),text:"Deploy",handler:function handler(){api.deployNow(props.ts.target.clusterId,kd.name,kd.namespace);}});}var allContexts=[];var handleContext=function handleContext(c){if(!c){return;}if(allContexts.find(function(x){return x===c;})){return;}allContexts.push(c);};(_props$ts$commandResu2=props.ts.commandResults)===null||_props$ts$commandResu2===void 0?void 0:_props$ts$commandResu2.forEach(function(rs){handleContext(rs.commandInfo.contextOverride);handleContext(rs.target.context);});var contextTooltip=/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{textAlign:"center",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"subtitle2",children:"All known contexts:"}),allContexts.map(function(context){return/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"subtitle2",children:context},context);})]});var targetName=props.ts.target.targetName;if(!targetName){targetName="";}return/*#__PURE__*/(0,jsx_runtime.jsx)(Paper_Paper,{elevation:5,sx:{width:"100%",height:"100%",borderRadius:'12px',border:'1px solid #59A588',boxShadow:'4px 4px 10px #1E617A',padding:'20px 16px 12px 16px'},onClick:function onClick(e){return props.onSelectTarget(props.ts);},children:/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",flexDirection:"column",justifyContent:"space-between",height:"100%",children:[/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",gap:"15px",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{flexShrink:0,children:/*#__PURE__*/(0,jsx_runtime.jsx)(TargetIcon,{})}),/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{flexGrow:1,children:[/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"h6",textAlign:"left",textOverflow:"ellipsis",overflow:"hidden",flexGrow:1,children:targetName}),allContexts.length?/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:contextTooltip,children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"subtitle1",textAlign:"left",textOverflow:"ellipsis",overflow:"hidden",whiteSpace:"nowrap",fontSize:"14px",fontWeight:500,lineHeight:"19px",children:allContexts[0]})}):/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{})]})]}),/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",alignItems:"center",justifyContent:"space-between",children:[/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",gap:"6px",alignItems:"center",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:"Cluster ID: "+props.ts.target.clusterId,children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",children:/*#__PURE__*/(0,jsx_runtime.jsx)(CpuIcon,{})})}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:"Discriminator: "+props.ts.target.discriminator,children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",children:/*#__PURE__*/(0,jsx_runtime.jsx)(FingerScanIcon,{})})})]}),/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",gap:"6px",alignItems:"center",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(StatusIcon,_objectSpread2({},props)),/*#__PURE__*/(0,jsx_runtime.jsx)(ActionsMenu,{menuItems:actionMenuItems})]})]})]})});}; ;// CONCATENATED MODULE: ./node_modules/js-yaml/dist/js-yaml.mjs /*! js-yaml 4.1.0 https://github.com/nodeca/js-yaml @license MIT */ function isNothing(subject) { @@ -56501,11 +56980,9 @@ esm_light.registerLanguage('yaml',hljs_yaml);esm_light.registerLanguage('diff',h */} ;// CONCATENATED MODULE: ./src/components/result-view/CommandResultStatusLine.tsx -var StatusLine=function StatusLine(props){var children=[];var doPush=function doPush(n,t,icon){if(n){children.push(/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",flexDirection:"column",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:n+" "+t,children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",alignItems:"center",justifyContent:"center",width:"24px",height:"24px",children:icon})}),/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{fontSize:"10px",align:"center",children:n})]},children.length));}};doPush(props.errors,"total errors.",/*#__PURE__*/(0,jsx_runtime.jsx)(ErrorIcon,{}));doPush(props.warnings,"total warnings.",/*#__PURE__*/(0,jsx_runtime.jsx)(WarningIcon,{}));doPush(props.newObjects,"new objects.",/*#__PURE__*/(0,jsx_runtime.jsx)(AddedIcon,{}));doPush(props.deletedObjects,"deleted objects.",/*#__PURE__*/(0,jsx_runtime.jsx)(TrashIcon,{}));doPush(props.orphanObjects,"orphan objects.",/*#__PURE__*/(0,jsx_runtime.jsx)(OrphanIcon,{}));doPush(props.changedObjects,"changed objects.",/*#__PURE__*/(0,jsx_runtime.jsx)(ChangedIcon,{}));return/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",width:"100%",children:children});};var CommandResultStatusLine=function CommandResultStatusLine(props){var _props$rs$errors,_props$rs$warnings;return/*#__PURE__*/(0,jsx_runtime.jsx)(StatusLine,{errors:(_props$rs$errors=props.rs.errors)===null||_props$rs$errors===void 0?void 0:_props$rs$errors.length,warnings:(_props$rs$warnings=props.rs.warnings)===null||_props$rs$warnings===void 0?void 0:_props$rs$warnings.length,changedObjects:props.rs.changedObjects,newObjects:props.rs.newObjects,deletedObjects:props.rs.deletedObjects,orphanObjects:props.rs.orphanObjects});};var ValidateResultStatusLine=function ValidateResultStatusLine(props){var _props$vr,_props$vr$errors,_props$vr2,_props$vr2$warnings,_props$vr3,_props$vr3$drift;return/*#__PURE__*/_jsx(StatusLine,{errors:(_props$vr=props.vr)===null||_props$vr===void 0?void 0:(_props$vr$errors=_props$vr.errors)===null||_props$vr$errors===void 0?void 0:_props$vr$errors.length,warnings:(_props$vr2=props.vr)===null||_props$vr2===void 0?void 0:(_props$vr2$warnings=_props$vr2.warnings)===null||_props$vr2$warnings===void 0?void 0:_props$vr2$warnings.length,changedObjects:(_props$vr3=props.vr)===null||_props$vr3===void 0?void 0:(_props$vr3$drift=_props$vr3.drift)===null||_props$vr3$drift===void 0?void 0:_props$vr3$drift.length});}; -;// CONCATENATED MODULE: ./src/utils/duration.ts -function formatDuration(ms,withMs){if(ms<0)ms=-ms;var time={day:Math.floor(ms/86400000),hour:Math.floor(ms/3600000)%24,minute:Math.floor(ms/60000)%60,second:Math.floor(ms/1000)%60,millisecond:withMs?Math.floor(ms)%1000:0};return Object.entries(time).filter(function(val){return val[1]!==0;}).map(function(val){return val[1]+' '+(val[1]!==1?val[0]+'s':val[0]);}).join(', ');}function formatDurationShort(ms){if(ms<0)ms=-ms;var time={d:Math.floor(ms/86400000),h:Math.floor(ms/3600000)%24,m:Math.floor(ms/60000)%60,s:Math.floor(ms/1000)%60,ms:Math.floor(ms)%1000};var f=Object.entries(time).find(function(val){return val[1]>0;});if(f===undefined){return"0s";}return f[1]+f[0];} +var StatusLine=function StatusLine(props){var children=[];var doPush=function doPush(n,t,icon){if(n){children.push(/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",flexDirection:"column",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:n+" "+t,children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",alignItems:"center",justifyContent:"center",width:"24px",height:"24px",children:icon})}),/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{fontSize:"10px",align:"center",children:n})]},t));}};doPush(props.errors,"total errors.",/*#__PURE__*/(0,jsx_runtime.jsx)(ErrorIcon,{}));doPush(props.warnings,"total warnings.",/*#__PURE__*/(0,jsx_runtime.jsx)(WarningIcon,{}));doPush(props.newObjects,"new objects.",/*#__PURE__*/(0,jsx_runtime.jsx)(AddedIcon,{}));doPush(props.deletedObjects,"deleted objects.",/*#__PURE__*/(0,jsx_runtime.jsx)(TrashIcon,{}));doPush(props.orphanObjects,"orphan objects.",/*#__PURE__*/(0,jsx_runtime.jsx)(OrphanIcon,{}));doPush(props.changedObjects,"changed objects.",/*#__PURE__*/(0,jsx_runtime.jsx)(ChangedIcon,{}));return/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",width:"100%",children:children});};var CommandResultStatusLine=function CommandResultStatusLine(props){var _props$rs$errors,_props$rs$warnings;return/*#__PURE__*/(0,jsx_runtime.jsx)(StatusLine,{errors:(_props$rs$errors=props.rs.errors)===null||_props$rs$errors===void 0?void 0:_props$rs$errors.length,warnings:(_props$rs$warnings=props.rs.warnings)===null||_props$rs$warnings===void 0?void 0:_props$rs$warnings.length,changedObjects:props.rs.changedObjects,newObjects:props.rs.newObjects,deletedObjects:props.rs.deletedObjects,orphanObjects:props.rs.orphanObjects});};var ValidateResultStatusLine=function ValidateResultStatusLine(props){var _props$vr,_props$vr$errors,_props$vr2,_props$vr2$warnings,_props$vr3,_props$vr3$drift;return/*#__PURE__*/_jsx(StatusLine,{errors:(_props$vr=props.vr)===null||_props$vr===void 0?void 0:(_props$vr$errors=_props$vr.errors)===null||_props$vr$errors===void 0?void 0:_props$vr$errors.length,warnings:(_props$vr2=props.vr)===null||_props$vr2===void 0?void 0:(_props$vr2$warnings=_props$vr2.warnings)===null||_props$vr2$warnings===void 0?void 0:_props$vr2$warnings.length,changedObjects:(_props$vr3=props.vr)===null||_props$vr3===void 0?void 0:(_props$vr3$drift=_props$vr3.drift)===null||_props$vr3$drift===void 0?void 0:_props$vr3$drift.length});}; ;// CONCATENATED MODULE: ./src/components/targets-view/CommandResultItem.tsx -var calcAgo=function calcAgo(startTime){var t1=new Date(startTime);var t2=new Date();var d=t2.getTime()-t1.getTime();return formatDurationShort(d);};var CommandResultItem=/*#__PURE__*/react.memo(function(props){var _rs$commandInfo,_rs$commandInfo2;var rs=props.rs,onSelectCommandResult=props.onSelectCommandResult,selected=props.selected;var navigate=dist_useNavigate();var _useState=(0,react.useState)(calcAgo(rs.commandInfo.startTime)),_useState2=slicedToArray_slicedToArray(_useState,2),ago=_useState2[0],setAgo=_useState2[1];var Icon=DiffIcon;switch((_rs$commandInfo=rs.commandInfo)===null||_rs$commandInfo===void 0?void 0:_rs$commandInfo.command){case"delete":Icon=PruneIcon;break;case"deploy":Icon=DeployIcon;break;case"diff":Icon=DiffIcon;break;case"poke-images":Icon=DeployIcon;break;case"prune":Icon=PruneIcon;break;}var iconTooltip=(0,react.useMemo)(function(){var cmdInfoYaml=dump(rs.commandInfo);return/*#__PURE__*/(0,jsx_runtime.jsx)(CodeViewer,{code:cmdInfoYaml,language:"yaml"});},[rs.commandInfo]);(0,react.useEffect)(function(){var interval=setInterval(function(){return setAgo(calcAgo(rs.commandInfo.startTime));},5000);return function(){return clearInterval(interval);};},[rs.commandInfo.startTime]);return/*#__PURE__*/(0,jsx_runtime.jsx)(Paper_Paper,{elevation:5,sx:{width:"100%",height:"100%",borderRadius:'12px',border:'1px solid #59A588',boxShadow:'4px 4px 10px #1E617A',padding:'20px 16px 5px 16px',outline:selected?'8px solid #59A588':'none'},onClick:function onClick(){return onSelectCommandResult(rs);},children:/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",flexDirection:"column",justifyContent:"space-between",height:"100%",children:[/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",gap:"15px",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:iconTooltip,children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{width:"45px",height:"45px",flex:"0 0 auto",justifyContent:"center",alignItems:"center",children:/*#__PURE__*/(0,jsx_runtime.jsx)(Icon,{})})}),/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{flexGrow:1,children:[/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"h6",textAlign:"left",textOverflow:"ellipsis",overflow:"hidden",flexGrow:1,children:(_rs$commandInfo2=rs.commandInfo)===null||_rs$commandInfo2===void 0?void 0:_rs$commandInfo2.command}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:rs.commandInfo.startTime,children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"subtitle1",textAlign:"left",textOverflow:"ellipsis",overflow:"hidden",whiteSpace:"nowrap",fontSize:"14px",fontWeight:500,lineHeight:"19px",children:ago})})]})]}),/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",alignItems:"center",justifyContent:"space-between",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",gap:"6px",alignItems:"center",children:/*#__PURE__*/(0,jsx_runtime.jsx)(CommandResultStatusLine,{rs:rs})}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",gap:"6px",alignItems:"center",height:"39px",children:/*#__PURE__*/(0,jsx_runtime.jsx)(IconButton_IconButton,{onClick:function onClick(e){e.stopPropagation();navigate("/results/".concat(rs.id));},sx:{padding:0,width:26,height:26},children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:"Open Result Tree",children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",children:/*#__PURE__*/(0,jsx_runtime.jsx)(TreeViewIcon,{})})})})})]})]})});}); +var CommandResultItem=/*#__PURE__*/react.memo(function(props){var _rs$commandInfo,_rs$commandInfo2;var rs=props.rs,onSelectCommandResult=props.onSelectCommandResult,selected=props.selected;var navigate=dist_useNavigate();var _useState=(0,react.useState)(calcAgo(rs.commandInfo.startTime)),_useState2=slicedToArray_slicedToArray(_useState,2),ago=_useState2[0],setAgo=_useState2[1];var Icon=DiffIcon;switch((_rs$commandInfo=rs.commandInfo)===null||_rs$commandInfo===void 0?void 0:_rs$commandInfo.command){case"delete":Icon=PruneIcon;break;case"deploy":Icon=DeployIcon;break;case"diff":Icon=DiffIcon;break;case"poke-images":Icon=DeployIcon;break;case"prune":Icon=PruneIcon;break;}var iconTooltip=(0,react.useMemo)(function(){var cmdInfoYaml=dump(rs.commandInfo);return/*#__PURE__*/(0,jsx_runtime.jsx)(CodeViewer,{code:cmdInfoYaml,language:"yaml"});},[rs.commandInfo]);(0,react.useEffect)(function(){var interval=setInterval(function(){return setAgo(calcAgo(rs.commandInfo.startTime));},5000);return function(){return clearInterval(interval);};},[rs.commandInfo.startTime]);return/*#__PURE__*/(0,jsx_runtime.jsx)(Paper_Paper,{elevation:5,sx:{width:"100%",height:"100%",borderRadius:'12px',border:'1px solid #59A588',boxShadow:'4px 4px 10px #1E617A',padding:'20px 16px 5px 16px',outline:selected?'8px solid #59A588':'none'},onClick:function onClick(){return onSelectCommandResult(rs);},children:/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",flexDirection:"column",justifyContent:"space-between",height:"100%",children:[/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",gap:"15px",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:iconTooltip,children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{width:"45px",height:"45px",flex:"0 0 auto",justifyContent:"center",alignItems:"center",children:/*#__PURE__*/(0,jsx_runtime.jsx)(Icon,{})})}),/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{flexGrow:1,children:[/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"h6",textAlign:"left",textOverflow:"ellipsis",overflow:"hidden",flexGrow:1,children:(_rs$commandInfo2=rs.commandInfo)===null||_rs$commandInfo2===void 0?void 0:_rs$commandInfo2.command}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:rs.commandInfo.startTime,children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"subtitle1",textAlign:"left",textOverflow:"ellipsis",overflow:"hidden",whiteSpace:"nowrap",fontSize:"14px",fontWeight:500,lineHeight:"19px",children:ago})})]})]}),/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",alignItems:"center",justifyContent:"space-between",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",gap:"6px",alignItems:"center",children:/*#__PURE__*/(0,jsx_runtime.jsx)(CommandResultStatusLine,{rs:rs})}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",gap:"6px",alignItems:"center",height:"39px",children:/*#__PURE__*/(0,jsx_runtime.jsx)(IconButton_IconButton,{onClick:function onClick(e){e.stopPropagation();navigate("/results/".concat(rs.id));},sx:{padding:0,width:26,height:26},children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:"Open Result Tree",children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",children:/*#__PURE__*/(0,jsx_runtime.jsx)(TreeViewIcon,{})})})})})]})]})});}); ;// CONCATENATED MODULE: ./node_modules/@mui/material/Table/TableContext.js @@ -57069,10 +57546,14 @@ var TableRow = /*#__PURE__*/react.forwardRef(function TableRow(inProps, ref) { }); false ? 0 : void 0; /* harmony default export */ var TableRow_TableRow = (TableRow); +// EXTERNAL MODULE: ./node_modules/js-sha256/src/sha256.js +var sha256 = __webpack_require__(981); +;// CONCATENATED MODULE: ./src/utils/listKey.ts +function buildListKey(o){var j=JSON.stringify(o);return (0,sha256.sha256)(j);} ;// CONCATENATED MODULE: ./src/components/result-view/ChangesTable.tsx -var RefList=function RefList(props){return/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{py:"10px",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{align:"center",variant:"h5",children:props.title}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableContainer_TableContainer,{children:/*#__PURE__*/(0,jsx_runtime.jsxs)(Table_Table,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableHead_TableHead,{children:/*#__PURE__*/(0,jsx_runtime.jsxs)(TableRow_TableRow,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{align:"left",children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{children:"Kind"})}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{align:"left",children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{children:"Namespace"})}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{align:"left",children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{children:"Name"})})]})}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableBody_TableBody,{children:props.refs.map(function(ref,i){return/*#__PURE__*/(0,jsx_runtime.jsxs)(TableRow_TableRow,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:buildRefKindElement(ref)}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{children:ref.namespace})}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{children:ref.name})})]},i);})})]})})]});};function ChangesTable(props){var changedObjects;if(props.diffStatus.changedObjects.length){changedObjects=/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{py:"10px",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{align:"center",variant:"h5",children:"Changed Objects"}),props.diffStatus.changedObjects.map(function(co,i){var _co$changes;return/*#__PURE__*/(0,jsx_runtime.jsx)(TableContainer_TableContainer,{children:/*#__PURE__*/(0,jsx_runtime.jsxs)(Table_Table,{children:[/*#__PURE__*/(0,jsx_runtime.jsxs)(TableHead_TableHead,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableRow_TableRow,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{align:"left",colSpan:2,sx:{padding:'24px 16px 5px 16px'},children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{sx:{fontWeight:500,fontSize:'20px',lineHeight:'27px',letterSpacing:'1px'},children:buildRefString(co.ref)})})}),/*#__PURE__*/(0,jsx_runtime.jsxs)(TableRow_TableRow,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:"Path"}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:"Changes"})]})]}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableBody_TableBody,{children:(_co$changes=co.changes)===null||_co$changes===void 0?void 0:_co$changes.map(function(c,i){return/*#__PURE__*/(0,jsx_runtime.jsxs)(TableRow_TableRow,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{minWidth:"100px",sx:{overflowWrap:"anywhere"},children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{children:c.jsonPath})})}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(CodeViewer,{code:c.unifiedDiff||"",language:"diff"})})]},i);})})]})},i);})]});}return/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{width:"100%",display:"flex",flexDirection:"column",children:[props.diffStatus.newObjects.length?/*#__PURE__*/(0,jsx_runtime.jsx)(RefList,{title:"New Objects",refs:props.diffStatus.newObjects}):/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{}),props.diffStatus.deletedObjects.length?/*#__PURE__*/(0,jsx_runtime.jsx)(RefList,{title:"Deleted Objects",refs:props.diffStatus.deletedObjects}):/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{}),props.diffStatus.orphanObjects.length?/*#__PURE__*/(0,jsx_runtime.jsx)(RefList,{title:"Orphan Objects",refs:props.diffStatus.orphanObjects}):/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{}),changedObjects]});} +var RefList=function RefList(props){return/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{py:"10px",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{align:"center",variant:"h5",children:props.title}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableContainer_TableContainer,{children:/*#__PURE__*/(0,jsx_runtime.jsxs)(Table_Table,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableHead_TableHead,{children:/*#__PURE__*/(0,jsx_runtime.jsxs)(TableRow_TableRow,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{align:"left",children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{children:"Kind"})}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{align:"left",children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{children:"Namespace"})}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{align:"left",children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{children:"Name"})})]})}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableBody_TableBody,{children:props.refs.map(function(ref){return/*#__PURE__*/(0,jsx_runtime.jsxs)(TableRow_TableRow,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:buildRefKindElement(ref)}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{children:ref.namespace})}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{children:ref.name})})]},buildRefString(ref));})})]})})]});};function ChangesTable(props){var changedObjects;if(props.diffStatus.changedObjects.length){changedObjects=/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{py:"10px",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{align:"center",variant:"h5",children:"Changed Objects"}),props.diffStatus.changedObjects.map(function(co){var _co$changes;return/*#__PURE__*/(0,jsx_runtime.jsx)(TableContainer_TableContainer,{children:/*#__PURE__*/(0,jsx_runtime.jsxs)(Table_Table,{children:[/*#__PURE__*/(0,jsx_runtime.jsxs)(TableHead_TableHead,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableRow_TableRow,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{align:"left",colSpan:2,sx:{padding:'24px 16px 5px 16px'},children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{sx:{fontWeight:500,fontSize:'20px',lineHeight:'27px',letterSpacing:'1px'},children:buildRefString(co.ref)})})}),/*#__PURE__*/(0,jsx_runtime.jsxs)(TableRow_TableRow,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:"Path"}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:"Changes"})]})]}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableBody_TableBody,{children:(_co$changes=co.changes)===null||_co$changes===void 0?void 0:_co$changes.map(function(c){return/*#__PURE__*/(0,jsx_runtime.jsxs)(TableRow_TableRow,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{minWidth:"100px",sx:{overflowWrap:"anywhere"},children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{children:c.jsonPath})})}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(CodeViewer,{code:c.unifiedDiff||"",language:"diff"})})]},buildListKey(c));})})]})},buildRefString(co.ref));})]});}return/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{width:"100%",display:"flex",flexDirection:"column",children:[props.diffStatus.newObjects.length?/*#__PURE__*/(0,jsx_runtime.jsx)(RefList,{title:"New Objects",refs:props.diffStatus.newObjects}):/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{}),props.diffStatus.deletedObjects.length?/*#__PURE__*/(0,jsx_runtime.jsx)(RefList,{title:"Deleted Objects",refs:props.diffStatus.deletedObjects}):/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{}),props.diffStatus.orphanObjects.length?/*#__PURE__*/(0,jsx_runtime.jsx)(RefList,{title:"Orphan Objects",refs:props.diffStatus.orphanObjects}):/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{}),changedObjects]});} ;// CONCATENATED MODULE: ./src/components/ErrorsTable.tsx -function ErrorsTable(props){var _props$errors;return/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{height:"100%",children:/*#__PURE__*/(0,jsx_runtime.jsx)(TableContainer_TableContainer,{children:/*#__PURE__*/(0,jsx_runtime.jsxs)(Table_Table,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableHead_TableHead,{children:/*#__PURE__*/(0,jsx_runtime.jsxs)(TableRow_TableRow,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:"Ref"}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:"Message"})]})}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableBody_TableBody,{children:(_props$errors=props.errors)===null||_props$errors===void 0?void 0:_props$errors.map(function(e,i){return/*#__PURE__*/(0,jsx_runtime.jsxs)(TableRow_TableRow,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{sx:{minWidth:"150px"},children:/*#__PURE__*/(0,jsx_runtime.jsxs)(List_List,{disablePadding:true,children:[buildRefKindElement(e.ref,/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(ListItem_ListItem,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(ListItemText_ListItemText,{primary:e.ref.kind})})})),/*#__PURE__*/(0,jsx_runtime.jsx)(Divider_Divider,{}),/*#__PURE__*/(0,jsx_runtime.jsx)(ListItem_ListItem,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(ListItemText_ListItemText,{primary:e.ref.name})}),e.ref.namespace&&/*#__PURE__*/(0,jsx_runtime.jsxs)(jsx_runtime.Fragment,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(Divider_Divider,{}),/*#__PURE__*/(0,jsx_runtime.jsx)(ListItem_ListItem,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(ListItemText_ListItemText,{primary:e.ref.namespace})})]})]})}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:e.message})]},i);})})]})})})});} +function ErrorsTable(props){var _props$errors;return/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{height:"100%",children:/*#__PURE__*/(0,jsx_runtime.jsx)(TableContainer_TableContainer,{children:/*#__PURE__*/(0,jsx_runtime.jsxs)(Table_Table,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableHead_TableHead,{children:/*#__PURE__*/(0,jsx_runtime.jsxs)(TableRow_TableRow,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:"Ref"}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:"Message"})]})}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableBody_TableBody,{children:(_props$errors=props.errors)===null||_props$errors===void 0?void 0:_props$errors.map(function(e){return/*#__PURE__*/(0,jsx_runtime.jsxs)(TableRow_TableRow,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{sx:{minWidth:"150px"},children:/*#__PURE__*/(0,jsx_runtime.jsxs)(List_List,{disablePadding:true,children:[buildRefKindElement(e.ref,/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(ListItem_ListItem,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(ListItemText_ListItemText,{primary:e.ref.kind})})})),/*#__PURE__*/(0,jsx_runtime.jsx)(Divider_Divider,{}),/*#__PURE__*/(0,jsx_runtime.jsx)(ListItem_ListItem,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(ListItemText_ListItemText,{primary:e.ref.name})}),e.ref.namespace&&/*#__PURE__*/(0,jsx_runtime.jsxs)(jsx_runtime.Fragment,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(Divider_Divider,{}),/*#__PURE__*/(0,jsx_runtime.jsx)(ListItem_ListItem,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(ListItemText_ListItemText,{primary:e.ref.namespace})})]})]})}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:e.message})]},buildListKey(e));})})]})})})});} ;// CONCATENATED MODULE: ./node_modules/@mui/material/CircularProgress/circularProgressClasses.js @@ -58672,18 +59153,18 @@ var TabPanel = /*#__PURE__*/react.forwardRef(function TabPanel(inProps, ref) { var SidePanel=function SidePanel(props){var _props$provider;var _useState=(0,react.useState)(),_useState2=slicedToArray_slicedToArray(_useState,2),selectedTab=_useState2[0],setSelectedTab=_useState2[1];var theme=styles_useTheme_useTheme();function handleTabChange(_e,value){setSelectedTab(value);}var tabs=(_props$provider=props.provider)===null||_props$provider===void 0?void 0:_props$provider.buildSidePanelTabs();if(!tabs){tabs=[];}(0,react.useEffect)(function(){var _tabs;if(!((_tabs=tabs)!==null&&_tabs!==void 0&&_tabs.length)){setSelectedTab("");return;}if(!selectedTab){setSelectedTab(tabs[0].label);return;}if(!tabs.find(function(x){return x.label===selectedTab;})){// reset it after the type of selected item has changed setSelectedTab(tabs[0].label);}// ignore that it wants us to add selectedTab to the deps (it would cause and endless loop) // eslint-disable-next-line -},[props.provider]);if(!selectedTab||!tabs.find(function(x){return x.label===selectedTab;})){return/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{});}if(!props.provider){return/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{});}return/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{width:"100%",height:"100%",display:"flex",flexDirection:"column",overflow:"hidden",children:/*#__PURE__*/(0,jsx_runtime.jsxs)(TabContext,{value:selectedTab,children:[/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{minHeight:theme.consts.appBarHeight,display:"flex",flexDirection:"column",flex:"0 0 auto",justifyContent:"space-between",children:[/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{flex:"1 1 auto",display:"flex",justifyContent:"space-between",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{flex:"1 1 auto",pt:"25px",pl:"35px",children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"h4",children:props.provider.buildSidePanelTitle()})}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{flex:"0 0 auto",pt:"10px",pr:"10px",children:/*#__PURE__*/(0,jsx_runtime.jsx)(IconButton_IconButton,{onClick:props.onClose,children:/*#__PURE__*/(0,jsx_runtime.jsx)(CloseIcon,{})})})]}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{height:"36px",flex:"0 0 auto",p:"0 30px",children:/*#__PURE__*/(0,jsx_runtime.jsx)(TabList_TabList,{onChange:handleTabChange,children:tabs.map(function(tab,i){return/*#__PURE__*/(0,jsx_runtime.jsx)(Tab_Tab,{label:tab.label,value:tab.label},tab.label);})})})]}),/*#__PURE__*/(0,jsx_runtime.jsx)(Divider_Divider,{sx:{margin:0}}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{overflow:"auto",p:"30px",children:tabs.map(function(tab,index){return/*#__PURE__*/(0,jsx_runtime.jsx)(styles_ThemeProvider_ThemeProvider,{theme:theme_light,children:/*#__PURE__*/(0,jsx_runtime.jsx)(TabPanel_TabPanel,{value:tab.label,sx:{padding:0},children:/*#__PURE__*/(0,jsx_runtime.jsx)(Paper_Paper,{sx:{padding:'10px 0'},children:tab.content})})},index);})})]})});}; +},[props.provider]);if(!selectedTab||!tabs.find(function(x){return x.label===selectedTab;})){return/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{});}if(!props.provider){return/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{});}return/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{width:"100%",height:"100%",display:"flex",flexDirection:"column",overflow:"hidden",children:/*#__PURE__*/(0,jsx_runtime.jsxs)(TabContext,{value:selectedTab,children:[/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{minHeight:theme.consts.appBarHeight,display:"flex",flexDirection:"column",flex:"0 0 auto",justifyContent:"space-between",children:[/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{flex:"1 1 auto",display:"flex",justifyContent:"space-between",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{flex:"1 1 auto",pt:"25px",pl:"35px",children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"h4",children:props.provider.buildSidePanelTitle()})}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{flex:"0 0 auto",pt:"10px",pr:"10px",children:/*#__PURE__*/(0,jsx_runtime.jsx)(IconButton_IconButton,{onClick:props.onClose,children:/*#__PURE__*/(0,jsx_runtime.jsx)(CloseIcon,{})})})]}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{height:"36px",flex:"0 0 auto",p:"0 30px",children:/*#__PURE__*/(0,jsx_runtime.jsx)(TabList_TabList,{onChange:handleTabChange,children:tabs.map(function(tab,i){return/*#__PURE__*/(0,jsx_runtime.jsx)(Tab_Tab,{label:tab.label,value:tab.label},tab.label);})})})]}),/*#__PURE__*/(0,jsx_runtime.jsx)(Divider_Divider,{sx:{margin:0}}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{overflow:"auto",p:"30px",children:tabs.map(function(tab){return/*#__PURE__*/(0,jsx_runtime.jsx)(styles_ThemeProvider_ThemeProvider,{theme:theme_light,children:/*#__PURE__*/(0,jsx_runtime.jsx)(TabPanel_TabPanel,{value:tab.label,sx:{padding:0},children:/*#__PURE__*/(0,jsx_runtime.jsx)(Paper_Paper,{sx:{padding:'10px 0'},children:tab.content})})},tab.label);})})]})});}; ;// CONCATENATED MODULE: ./src/components/targets-view/Card.tsx var Card_excluded=["children"],Card_excluded2=["children"],Card_excluded3=["children"];var cardWidth=247;var projectCardHeight=80;var cardHeight=126;var cardGap=20;function Card(_ref){var children=_ref.children,rest=objectWithoutProperties_objectWithoutProperties(_ref,Card_excluded);return/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,_objectSpread2(_objectSpread2({display:"flex",flexShrink:0,width:cardWidth,height:cardHeight},rest),{},{children:children}));}function CardCol(_ref2){var children=_ref2.children,rest=objectWithoutProperties_objectWithoutProperties(_ref2,Card_excluded2);return/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,_objectSpread2(_objectSpread2({display:"flex",flexDirection:"column",gap:"".concat(cardGap,"px")},rest),{},{children:children}));}function CardRow(_ref3){var children=_ref3.children,rest=objectWithoutProperties_objectWithoutProperties(_ref3,Card_excluded3);return/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,_objectSpread2(_objectSpread2({display:"flex",gap:"".concat(cardGap,"px")},rest),{},{children:children}));} ;// CONCATENATED MODULE: ./src/components/targets-view/CommandResultDetailsDrawer.tsx -var sidePanelWidth=720;function doGetRootNode(_x){return _doGetRootNode.apply(this,arguments);}function _doGetRootNode(){_doGetRootNode=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee(rs){var shortNames,r,builder,_builder$buildRoot,_builder$buildRoot2,node;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee$(_context){while(1)switch(_context.prev=_context.next){case 0:shortNames=api.getShortNames();r=api.getResult(rs.id);_context.t0=NodeBuilder;_context.next=5;return shortNames;case 5:_context.t1=_context.sent;_context.t2=rs;_context.next=9;return r;case 9:_context.t3=_context.sent;_context.t4={shortNames:_context.t1,summary:_context.t2,commandResult:_context.t3};builder=new _context.t0(_context.t4);_builder$buildRoot=builder.buildRoot(),_builder$buildRoot2=slicedToArray_slicedToArray(_builder$buildRoot,1),node=_builder$buildRoot2[0];return _context.abrupt("return",node);case 14:case"end":return _context.stop();}},_callee);}));return _doGetRootNode.apply(this,arguments);}var CommandResultDetailsDrawer=/*#__PURE__*/react.memo(function(props){var ps=props.ps,ts=props.ts,selectedCardRect=props.selectedCardRect;var theme=styles_useTheme_useTheme();var _useState=(0,react.useState)(new Promise(function(){return undefined;})),_useState2=slicedToArray_slicedToArray(_useState,2),promise=_useState2[0],setPromise=_useState2[1];var _useState3=(0,react.useState)(),_useState4=slicedToArray_slicedToArray(_useState3,2),selectedCommandResult=_useState4[0],setSelectedCommandResult=_useState4[1];var _useState5=(0,react.useState)(ts),_useState6=slicedToArray_slicedToArray(_useState5,2),prevTargetSummary=_useState6[0],setPrevTargetSummary=_useState6[1];var _useState7=(0,react.useState)([]),_useState8=slicedToArray_slicedToArray(_useState7,2),cardsCoords=_useState8[0],setCardsCoords=_useState8[1];var _useState9=(0,react.useState)(false),_useState10=slicedToArray_slicedToArray(_useState9,2),transitionRunning=_useState10[0],setTransitionRunning=_useState10[1];if(prevTargetSummary!==ts){var _ts$commandResults;setPrevTargetSummary(ts);setSelectedCommandResult(ts===null||ts===void 0?void 0:(_ts$commandResults=ts.commandResults)===null||_ts$commandResults===void 0?void 0:_ts$commandResults[0]);}(0,react.useEffect)(function(){if(selectedCommandResult===undefined){return;}setPromise(doGetRootNode(selectedCommandResult));},[selectedCommandResult]);var Content=function Content(props){var node=usePromise(promise);return/*#__PURE__*/(0,jsx_runtime.jsx)(SidePanel,{provider:node,onClose:props.onClose});};var cardsContainerElem=(0,react.useRef)();(0,react.useEffect)(function(){var _cardsContainerElem$c;var rect=(_cardsContainerElem$c=cardsContainerElem.current)===null||_cardsContainerElem$c===void 0?void 0:_cardsContainerElem$c.getBoundingClientRect();if(!rect||!selectedCardRect||!(ts!==null&&ts!==void 0&&ts.commandResults)){setCardsCoords([]);return;}var initialCoords=ts.commandResults.map(function(){return{left:selectedCardRect.left-rect.left,top:selectedCardRect.top-rect.top};});setCardsCoords(initialCoords);setTransitionRunning(true);},[selectedCardRect,ts===null||ts===void 0?void 0:ts.commandResults]);(0,react.useEffect)(function(){if(cardsCoords.length>0){var targetCoords=cardsCoords.map(function(_,i){return{left:0,top:i*(cardHeight+cardGap)};});if(cardsCoords.length===targetCoords.length&&cardsCoords.every(function(_ref,i){var left=_ref.left,top=_ref.top;return targetCoords[i].left===left&&targetCoords[i].top===top;})){return;}setTimeout(function(){setCardsCoords(targetCoords);setTimeout(function(){setTransitionRunning(false);},theme.transitions.duration.enteringScreen);},10);}},[cardsCoords,theme.transitions.duration.enteringScreen]);var zIndex=theme.zIndex.modal+1;var cards=(0,react.useMemo)(function(){if(!(ps&&ts&&ts.commandResults&&ts.commandResults.length>0&&cardsCoords.length>0)){return null;}return ts.commandResults.map(function(rs,i){return/*#__PURE__*/(0,jsx_runtime.jsx)(Card,{sx:{position:'absolute',translate:"".concat(cardsCoords[i].left,"px ").concat(cardsCoords[i].top,"px"),zIndex:zIndex,transition:theme.transitions.create(['translate'],{easing:theme.transitions.easing.sharp,duration:theme.transitions.duration.enteringScreen})},children:/*#__PURE__*/(0,jsx_runtime.jsx)(CommandResultItem,{ps:ps,ts:ts,rs:rs,onSelectCommandResult:setSelectedCommandResult,selected:rs===selectedCommandResult})},i);});},[ps,ts,cardsCoords,zIndex,theme.transitions,selectedCommandResult]);return/*#__PURE__*/(0,jsx_runtime.jsxs)(jsx_runtime.Fragment,{children:[ps&&ts&&ts.commandResults&&ts.commandResults.length>0&&/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{sx:{position:'fixed',top:0,bottom:0,right:sidePanelWidth,width:"calc(100% - ".concat(sidePanelWidth,"px)"),overflowX:'visible',overflowY:transitionRunning?'visible':'auto',display:'flex',flexDirection:'column',alignItems:'center',justifyContent:'center',padding:'25px 0',zIndex:zIndex},onClick:props.onClose,children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{onClick:function onClick(e){return e.stopPropagation();},position:"relative",width:cardWidth,height:cardHeight*ts.commandResults.length+cardGap*(ts.commandResults.length-1),ref:cardsContainerElem,children:cards})}),/*#__PURE__*/(0,jsx_runtime.jsx)(styles_ThemeProvider_ThemeProvider,{theme:theme_dark,children:/*#__PURE__*/(0,jsx_runtime.jsx)(Drawer_Drawer,{sx:{zIndex:1300},anchor:"right",open:props.rs!==undefined,onClose:props.onClose,children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{width:sidePanelWidth,height:"100%",children:/*#__PURE__*/(0,jsx_runtime.jsx)(react.Suspense,{fallback:/*#__PURE__*/(0,jsx_runtime.jsx)(Loading,{}),children:/*#__PURE__*/(0,jsx_runtime.jsx)(Content,{onClose:props.onClose})})})})})]});}); +var sidePanelWidth=720;function doGetRootNode(_x){return _doGetRootNode.apply(this,arguments);}function _doGetRootNode(){_doGetRootNode=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee(rs){var shortNames,r,builder,_builder$buildRoot,_builder$buildRoot2,node;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee$(_context){while(1)switch(_context.prev=_context.next){case 0:shortNames=api.getShortNames();r=api.getResult(rs.id);_context.t0=NodeBuilder;_context.next=5;return shortNames;case 5:_context.t1=_context.sent;_context.t2=rs;_context.next=9;return r;case 9:_context.t3=_context.sent;_context.t4={shortNames:_context.t1,summary:_context.t2,commandResult:_context.t3};builder=new _context.t0(_context.t4);_builder$buildRoot=builder.buildRoot(),_builder$buildRoot2=slicedToArray_slicedToArray(_builder$buildRoot,1),node=_builder$buildRoot2[0];return _context.abrupt("return",node);case 14:case"end":return _context.stop();}},_callee);}));return _doGetRootNode.apply(this,arguments);}var CommandResultDetailsDrawer=/*#__PURE__*/react.memo(function(props){var ps=props.ps,ts=props.ts,selectedCardRect=props.selectedCardRect;var theme=styles_useTheme_useTheme();var _useState=(0,react.useState)(new Promise(function(){return undefined;})),_useState2=slicedToArray_slicedToArray(_useState,2),promise=_useState2[0],setPromise=_useState2[1];var _useState3=(0,react.useState)(),_useState4=slicedToArray_slicedToArray(_useState3,2),selectedCommandResult=_useState4[0],setSelectedCommandResult=_useState4[1];var _useState5=(0,react.useState)(ts),_useState6=slicedToArray_slicedToArray(_useState5,2),prevTargetSummary=_useState6[0],setPrevTargetSummary=_useState6[1];var _useState7=(0,react.useState)([]),_useState8=slicedToArray_slicedToArray(_useState7,2),cardsCoords=_useState8[0],setCardsCoords=_useState8[1];var _useState9=(0,react.useState)(false),_useState10=slicedToArray_slicedToArray(_useState9,2),transitionRunning=_useState10[0],setTransitionRunning=_useState10[1];if(prevTargetSummary!==ts){var _ts$commandResults;setPrevTargetSummary(ts);setSelectedCommandResult(ts===null||ts===void 0?void 0:(_ts$commandResults=ts.commandResults)===null||_ts$commandResults===void 0?void 0:_ts$commandResults[0]);}(0,react.useEffect)(function(){if(selectedCommandResult===undefined){return;}setPromise(doGetRootNode(selectedCommandResult));},[selectedCommandResult]);var Content=function Content(props){var node=usePromise(promise);return/*#__PURE__*/(0,jsx_runtime.jsx)(SidePanel,{provider:node,onClose:props.onClose});};var cardsContainerElem=(0,react.useRef)();(0,react.useEffect)(function(){var _cardsContainerElem$c;var rect=(_cardsContainerElem$c=cardsContainerElem.current)===null||_cardsContainerElem$c===void 0?void 0:_cardsContainerElem$c.getBoundingClientRect();if(!rect||!selectedCardRect||!(ts!==null&&ts!==void 0&&ts.commandResults)){setCardsCoords([]);return;}var initialCoords=ts.commandResults.map(function(){return{left:selectedCardRect.left-rect.left,top:selectedCardRect.top-rect.top};});setCardsCoords(initialCoords);setTransitionRunning(true);},[selectedCardRect,ts===null||ts===void 0?void 0:ts.commandResults]);(0,react.useEffect)(function(){if(cardsCoords.length>0){var targetCoords=cardsCoords.map(function(_,i){return{left:0,top:i*(cardHeight+cardGap)};});if(cardsCoords.length===targetCoords.length&&cardsCoords.every(function(_ref,i){var left=_ref.left,top=_ref.top;return targetCoords[i].left===left&&targetCoords[i].top===top;})){return;}setTimeout(function(){setCardsCoords(targetCoords);setTimeout(function(){setTransitionRunning(false);},theme.transitions.duration.enteringScreen);},10);}},[cardsCoords,theme.transitions.duration.enteringScreen]);var zIndex=theme.zIndex.modal+1;var cards=(0,react.useMemo)(function(){if(!(ps&&ts&&ts.commandResults&&ts.commandResults.length>0&&cardsCoords.length>0)){return null;}return ts.commandResults.map(function(rs,i){return/*#__PURE__*/(0,jsx_runtime.jsx)(Card,{sx:{position:'absolute',translate:"".concat(cardsCoords[i].left,"px ").concat(cardsCoords[i].top,"px"),zIndex:zIndex,transition:theme.transitions.create(['translate'],{easing:theme.transitions.easing.sharp,duration:theme.transitions.duration.enteringScreen})},children:/*#__PURE__*/(0,jsx_runtime.jsx)(CommandResultItem,{ps:ps,ts:ts,rs:rs,onSelectCommandResult:setSelectedCommandResult,selected:rs===selectedCommandResult})},rs.id);});},[ps,ts,cardsCoords,zIndex,theme.transitions,selectedCommandResult]);return/*#__PURE__*/(0,jsx_runtime.jsxs)(jsx_runtime.Fragment,{children:[ps&&ts&&ts.commandResults&&ts.commandResults.length>0&&/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{sx:{position:'fixed',top:0,bottom:0,right:sidePanelWidth,width:"calc(100% - ".concat(sidePanelWidth,"px)"),overflowX:'visible',overflowY:transitionRunning?'visible':'auto',display:'flex',flexDirection:'column',alignItems:'center',justifyContent:'center',padding:'25px 0',zIndex:zIndex},onClick:props.onClose,children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{onClick:function onClick(e){return e.stopPropagation();},position:"relative",width:cardWidth,height:cardHeight*ts.commandResults.length+cardGap*(ts.commandResults.length-1),ref:cardsContainerElem,children:cards})}),/*#__PURE__*/(0,jsx_runtime.jsx)(styles_ThemeProvider_ThemeProvider,{theme:theme_dark,children:/*#__PURE__*/(0,jsx_runtime.jsx)(Drawer_Drawer,{sx:{zIndex:1300},anchor:"right",open:props.rs!==undefined,onClose:props.onClose,children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{width:sidePanelWidth,height:"100%",children:/*#__PURE__*/(0,jsx_runtime.jsx)(react.Suspense,{fallback:/*#__PURE__*/(0,jsx_runtime.jsx)(Loading,{}),children:/*#__PURE__*/(0,jsx_runtime.jsx)(Content,{onClose:props.onClose})})})})})]});}); ;// CONCATENATED MODULE: ./src/components/targets-view/TargetDetailsDrawer.tsx var MyProvider=/*#__PURE__*/function(){function MyProvider(ts){var _this$ts,_this$ts$lastValidate,_this$ts$lastValidate2,_this=this;classCallCheck_classCallCheck(this,MyProvider);this.ts=void 0;this.diffStatus=void 0;this.ts=ts;this.diffStatus=new DiffStatus();(_this$ts=this.ts)===null||_this$ts===void 0?void 0:(_this$ts$lastValidate=_this$ts.lastValidateResult)===null||_this$ts$lastValidate===void 0?void 0:(_this$ts$lastValidate2=_this$ts$lastValidate.drift)===null||_this$ts$lastValidate2===void 0?void 0:_this$ts$lastValidate2.forEach(function(co){_this.diffStatus.addChangedObject(co);});}createClass_createClass(MyProvider,[{key:"buildSidePanelTabs",value:function buildSidePanelTabs(){var _this$ts$lastValidate3,_this$ts$lastValidate4,_this$ts$lastValidate5,_this$ts$lastValidate6;if(!this.ts){return[];}var tabs=[{label:"Summary",content:this.buildSummaryTab()}];if(this.ts.target)if(this.diffStatus.changedObjects.length){tabs.push({label:"Drift",content:/*#__PURE__*/(0,jsx_runtime.jsx)(ChangesTable,{diffStatus:this.diffStatus})});}if((_this$ts$lastValidate3=this.ts.lastValidateResult)!==null&&_this$ts$lastValidate3!==void 0&&(_this$ts$lastValidate4=_this$ts$lastValidate3.errors)!==null&&_this$ts$lastValidate4!==void 0&&_this$ts$lastValidate4.length){tabs.push({label:"Errors",content:/*#__PURE__*/(0,jsx_runtime.jsx)(ErrorsTable,{errors:this.ts.lastValidateResult.errors})});}if((_this$ts$lastValidate5=this.ts.lastValidateResult)!==null&&_this$ts$lastValidate5!==void 0&&(_this$ts$lastValidate6=_this$ts$lastValidate5.warnings)!==null&&_this$ts$lastValidate6!==void 0&&_this$ts$lastValidate6.length){tabs.push({label:"Warnings",content:/*#__PURE__*/(0,jsx_runtime.jsx)(ErrorsTable,{errors:this.ts.lastValidateResult.warnings})});}return tabs;}},{key:"buildSummaryTab",value:function buildSummaryTab(){var _this$ts2,_this$ts3,_this$ts4,_this$ts4$lastValidat,_this$ts4$lastValidat2,_this$ts5,_this$ts5$lastValidat,_this$ts5$lastValidat2,_this$ts6,_this$ts6$lastValidat,_this$ts6$lastValidat2;var props=[{name:"Target Name",value:this.getTargetName()},{name:"Discriminator",value:(_this$ts2=this.ts)===null||_this$ts2===void 0?void 0:_this$ts2.target.discriminator}];if((_this$ts3=this.ts)!==null&&_this$ts3!==void 0&&_this$ts3.lastValidateResult){props.push({name:"Ready",value:this.ts.lastValidateResult.ready+""});}if((_this$ts4=this.ts)!==null&&_this$ts4!==void 0&&(_this$ts4$lastValidat=_this$ts4.lastValidateResult)!==null&&_this$ts4$lastValidat!==void 0&&(_this$ts4$lastValidat2=_this$ts4$lastValidat.errors)!==null&&_this$ts4$lastValidat2!==void 0&&_this$ts4$lastValidat2.length){props.push({name:"Errors",value:this.ts.lastValidateResult.errors.length+""});}if((_this$ts5=this.ts)!==null&&_this$ts5!==void 0&&(_this$ts5$lastValidat=_this$ts5.lastValidateResult)!==null&&_this$ts5$lastValidat!==void 0&&(_this$ts5$lastValidat2=_this$ts5$lastValidat.warnings)!==null&&_this$ts5$lastValidat2!==void 0&&_this$ts5$lastValidat2.length){props.push({name:"Warnings",value:this.ts.lastValidateResult.warnings.length+""});}if((_this$ts6=this.ts)!==null&&_this$ts6!==void 0&&(_this$ts6$lastValidat=_this$ts6.lastValidateResult)!==null&&_this$ts6$lastValidat!==void 0&&(_this$ts6$lastValidat2=_this$ts6$lastValidat.drift)!==null&&_this$ts6$lastValidat2!==void 0&&_this$ts6$lastValidat2.length){props.push({name:"Drifted Objects",value:this.ts.lastValidateResult.drift.length+""});}return/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(PropertiesTable,{properties:props})});}},{key:"getTargetName",value:function getTargetName(){if(!this.ts){return"";}var name="";if(this.ts.target.targetName){name=this.ts.target.targetName;}return name;}},{key:"buildSidePanelTitle",value:function buildSidePanelTitle(){return this.getTargetName();}}]);return MyProvider;}();var TargetDetailsDrawer=/*#__PURE__*/react.memo(function(props){return/*#__PURE__*/(0,jsx_runtime.jsx)(styles_ThemeProvider_ThemeProvider,{theme:theme_dark,children:/*#__PURE__*/(0,jsx_runtime.jsx)(Drawer_Drawer,{sx:{zIndex:1300},anchor:"right",open:props.ts!==undefined,onClose:props.onClose,ModalProps:{BackdropProps:{invisible:true}},children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{width:"600px",height:"100%",children:/*#__PURE__*/(0,jsx_runtime.jsx)(SidePanel,{provider:new MyProvider(props.ts),onClose:props.onClose})})})});}); ;// CONCATENATED MODULE: ./src/components/targets-view/TargetsView.tsx var colWidth=416;var curveRadius=12;var circleRadius=5;var strokeWidth=2;function ColHeader(_ref){var children=_ref.children;return/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{minWidth:colWidth,width:colWidth,height:"42px",display:"flex",alignItems:"center",sx:{borderLeft:'0.8px solid rgba(0,0,0,0.5)',paddingLeft:'15px','&:first-of-type':{borderLeft:'none',paddingLeft:0}},children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"h2",textAlign:"left",children:children})});}var Circle=/*#__PURE__*/react.memo(function(props){var theme=styles_useTheme_useTheme();return/*#__PURE__*/(0,jsx_runtime.jsx)("circle",_objectSpread2({r:circleRadius,fill:theme.palette.background.default,stroke:theme.palette.secondary.main,strokeWidth:strokeWidth},props));});var RelationTree=/*#__PURE__*/react.memo(function(_ref2){var targetCount=_ref2.targetCount;var theme=styles_useTheme_useTheme();var height=targetCount*cardHeight+(targetCount-1)*cardGap;var width=152;if(targetCount<=0){return null;}var targetsCenterYs=Array(targetCount).fill(0).map(function(_,i){return cardHeight/2+i*(cardHeight+cardGap);});var rootCenterY=height/2;return/*#__PURE__*/(0,jsx_runtime.jsxs)("svg",{width:width,height:height,viewBox:"0 0 ".concat(width," ").concat(height),fill:"none",children:[targetsCenterYs.map(function(cy,i){var d;if(targetCount%2===1&&i===Math.floor(targetCount/2)){// target is in the middle. d="\n M ".concat(circleRadius,",").concat(rootCenterY,"\n h ").concat(width-circleRadius,"\n ");}else if(i Date: Sat, 10 Jun 2023 01:04:22 +0200 Subject: [PATCH 1814/2916] fix: Fix tests and controller client creation --- cmd/kluctl/commands/cmd_controller_run.go | 8 ++++---- e2e/gitops_errors_test.go | 2 +- e2e/results_test.go | 2 +- e2e/test-utils/envtest_cluster.go | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cmd/kluctl/commands/cmd_controller_run.go b/cmd/kluctl/commands/cmd_controller_run.go index a08a09016..dc9bfb130 100644 --- a/cmd/kluctl/commands/cmd_controller_run.go +++ b/cmd/kluctl/commands/cmd_controller_run.go @@ -131,11 +131,11 @@ func (cmd *controllerRunCmd) Run(ctx context.Context) error { } if cmd.WriteCommandResult { - wc, ok := mgr.GetClient().(client.WithWatch) - if !ok { - return fmt.Errorf("client does not implement WithWatch") + c, err := client.NewWithWatch(restConfig, client.Options{}) + if err != nil { + return err } - resultStore, err := results.NewResultStoreSecrets(ctx, wc, cmd.CommandResultNamespace, cmd.KeepCommandResultsCount) + resultStore, err := results.NewResultStoreSecrets(ctx, c, cmd.CommandResultNamespace, cmd.KeepCommandResultsCount) if err != nil { return err } diff --git a/e2e/gitops_errors_test.go b/e2e/gitops_errors_test.go index c2df3e467..2d015ffaa 100644 --- a/e2e/gitops_errors_test.go +++ b/e2e/gitops_errors_test.go @@ -41,7 +41,7 @@ func (suite *GitopsTestSuite) assertErrors(key client.ObjectKey, rstatus metav1. g.Expect(readinessCondition.Reason).To(Equal(rreason)) g.Expect(readinessCondition.Message).To(ContainSubstring(rmessage)) - rs, err := results.NewResultStoreSecrets(context.TODO(), suite.k.Client, nil, "", 0) + rs, err := results.NewResultStoreSecrets(context.TODO(), suite.k.Client, "", 0) g.Expect(err).To(Succeed()) lastDeployResult, err := kd.Status.GetLastDeployResult() diff --git a/e2e/results_test.go b/e2e/results_test.go index 000265647..ae859d7c8 100644 --- a/e2e/results_test.go +++ b/e2e/results_test.go @@ -42,7 +42,7 @@ func TestWriteResult(t *testing.T) { p.KluctlMust("deploy", "--yes", "-t", "test", "--write-command-result") assertConfigMapExists(t, k, p.TestSlug(), "cm") - rs, err := results.NewResultStoreSecrets(context.Background(), k.Client, nil, "kluctl-results", 0) + rs, err := results.NewResultStoreSecrets(context.Background(), k.Client, "kluctl-results", 0) assert.NoError(t, err) opts := results.ListCommandResultSummariesOptions{ diff --git a/e2e/test-utils/envtest_cluster.go b/e2e/test-utils/envtest_cluster.go index c6e6d9fd6..4afba9da3 100644 --- a/e2e/test-utils/envtest_cluster.go +++ b/e2e/test-utils/envtest_cluster.go @@ -36,7 +36,7 @@ type EnvTestCluster struct { HttpClient *http.Client DynamicClient dynamic.Interface - Client client.Client + Client client.WithWatch ServerVersion *version.Info callbackServer webhook.Server @@ -103,7 +103,7 @@ func (k *EnvTestCluster) Start() error { } k.DynamicClient = dynamicClient - c, err := client.New(k.config, client.Options{ + c, err := client.NewWithWatch(k.config, client.Options{ HTTPClient: httpClient, Scheme: k.env.Scheme, }) From 3fd345bcbde9e3d8fe62fe046c262a8f76d5b05e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sun, 11 Jun 2023 22:05:36 +0200 Subject: [PATCH 1815/2916] ci: Remove cross-compile workflow (#559) It's redundant as the goreleaser workflows already perform cross compilation. --- .github/workflows/cross-compile.yaml | 36 ---------------------------- 1 file changed, 36 deletions(-) delete mode 100644 .github/workflows/cross-compile.yaml diff --git a/.github/workflows/cross-compile.yaml b/.github/workflows/cross-compile.yaml deleted file mode 100644 index e9651b820..000000000 --- a/.github/workflows/cross-compile.yaml +++ /dev/null @@ -1,36 +0,0 @@ -name: cross-compile - -on: - push: - branches: - - main - -jobs: - cross-compile: - runs-on: ubuntu-20.04 - if: github.event_name != 'pull_request' - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - uses: actions/setup-go@v4 - with: - go-version: '1.19' - - uses: actions/cache@v3 - with: - path: | - ~/go/pkg/mod - ~/.cache/go-build - key: cross-compile-go-${{ runner.os }}-${{ hashFiles('**/go.sum') }} - restore-keys: | - cross-compile-go-${{ runner.os }}- - - name: Build kluctl (linux) - run: | - make build GOARCH=amd64 GOOS=linux - - name: Build kluctl (darwin) - run: | - make build GOARCH=amd64 GOOS=darwin - - name: Build kluctl (windows) - run: | - make build GOARCH=amd64 GOOS=windows From c9f389344a933acfe49c5681588ace0a08660bb5 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 12 Jun 2023 23:19:01 +0200 Subject: [PATCH 1816/2916] ci: Also run tests workflow on releaser branch (#561) --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index cb0239ea7..74553224f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -4,6 +4,7 @@ on: push: branches: - main + - release-v* pull_request: branches: - main From d78fc8783696b2bfc306acbaec8b0e1c7362a013 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 13 Jun 2023 08:51:42 +0200 Subject: [PATCH 1817/2916] refactor: Use ResultStore interface instead of ResultsCollector --- pkg/webui/server.go | 24 ++++++++++++------------ pkg/webui/websocket.go | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/pkg/webui/server.go b/pkg/webui/server.go index 63db414c0..55fe6abeb 100644 --- a/pkg/webui/server.go +++ b/pkg/webui/server.go @@ -23,16 +23,16 @@ import ( const webuiManager = "kluctl-webui" type CommandResultsServer struct { - ctx context.Context - collector *results.ResultsCollector - cam *clusterAccessorManager - vam *validatorManager + ctx context.Context + store results.ResultStore + cam *clusterAccessorManager + vam *validatorManager } -func NewCommandResultsServer(ctx context.Context, collector *results.ResultsCollector, configs []*rest.Config) *CommandResultsServer { +func NewCommandResultsServer(ctx context.Context, store *results.ResultsCollector, configs []*rest.Config) *CommandResultsServer { ret := &CommandResultsServer{ - ctx: ctx, - collector: collector, + ctx: ctx, + store: store, cam: &clusterAccessorManager{ ctx: ctx, }, @@ -42,13 +42,13 @@ func NewCommandResultsServer(ctx context.Context, collector *results.ResultsColl ret.cam.add(config) } - ret.vam = newValidatorManager(ctx, collector, ret.cam) + ret.vam = newValidatorManager(ctx, store, ret.cam) return ret } func (s *CommandResultsServer) Run(port int) error { - l, ch, cancel, err := s.collector.WatchCommandResultSummaries(results.ListCommandResultSummariesOptions{}) + l, ch, cancel, err := s.store.WatchCommandResultSummaries(results.ListCommandResultSummariesOptions{}) if err != nil { return err } @@ -165,7 +165,7 @@ func (s *CommandResultsServer) getResult(c *gin.Context) { return } - sr, err := s.collector.GetCommandResult(results.GetCommandResultOptions{ + sr, err := s.store.GetCommandResult(results.GetCommandResultOptions{ Id: params.ResultId, Reduced: true, }) @@ -190,7 +190,7 @@ func (s *CommandResultsServer) getResultSummary(c *gin.Context) { return } - sr, err := s.collector.GetCommandResultSummary(params.ResultId) + sr, err := s.store.GetCommandResultSummary(params.ResultId) if err != nil { _ = c.AbortWithError(http.StatusBadRequest, err) return @@ -224,7 +224,7 @@ func (s *CommandResultsServer) getResultObject(c *gin.Context) { return } - sr, err := s.collector.GetCommandResult(results.GetCommandResultOptions{ + sr, err := s.store.GetCommandResult(results.GetCommandResultOptions{ Id: params.ResultId, Reduced: false, }) diff --git a/pkg/webui/websocket.go b/pkg/webui/websocket.go index a0690c752..2a21cb462 100644 --- a/pkg/webui/websocket.go +++ b/pkg/webui/websocket.go @@ -68,7 +68,7 @@ func (s *CommandResultsServer) ws(c *gin.Context) { } func (s *CommandResultsServer) wsHandle(c *websocket.Conn, filter *result.ProjectKey) error { - initial, ch, cancel, err := s.collector.WatchCommandResultSummaries(results.ListCommandResultSummariesOptions{ProjectFilter: filter}) + initial, ch, cancel, err := s.store.WatchCommandResultSummaries(results.ListCommandResultSummariesOptions{ProjectFilter: filter}) if err != nil { return err } From 335923f122ab2812d7f69bea9da1e79cf40b0880 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 13 Jun 2023 08:59:49 +0200 Subject: [PATCH 1818/2916] refactor: Move static routes setup into own function --- pkg/webui/server.go | 50 ++++++++++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/pkg/webui/server.go b/pkg/webui/server.go index 55fe6abeb..6aad01bd9 100644 --- a/pkg/webui/server.go +++ b/pkg/webui/server.go @@ -76,31 +76,11 @@ func (s *CommandResultsServer) Run(port int) error { router := gin.Default() - dis, err := fs.ReadDir(uiFS, ".") + err = s.setupStaticRoutes(router) if err != nil { return err } - for _, di := range dis { - if di.IsDir() { - x, err := fs.Sub(uiFS, di.Name()) - if err != nil { - return err - } - router.StaticFS("/"+di.Name(), http.FS(x)) - } else { - router.StaticFileFS("/"+di.Name(), di.Name(), http.FS(uiFS)) - } - } - router.GET("/", func(c *gin.Context) { - b, err := fs.ReadFile(uiFS, "index.html") - if err != nil { - _ = c.AbortWithError(http.StatusInternalServerError, err) - return - } - c.Data(http.StatusOK, "text/html; charset=utf-8", b) - }) - api := router.Group("/api") api.GET("/getShortNames", s.getShortNames) api.GET("/getResult", s.getResult) @@ -128,6 +108,34 @@ func (s *CommandResultsServer) Run(port int) error { return httpServer.Serve(listener) } +func (s *CommandResultsServer) setupStaticRoutes(router gin.IRouter) error { + dis, err := fs.ReadDir(uiFS, ".") + if err != nil { + return err + } + + for _, di := range dis { + if di.IsDir() { + x, err := fs.Sub(uiFS, di.Name()) + if err != nil { + return err + } + router.StaticFS("/"+di.Name(), http.FS(x)) + } else { + router.StaticFileFS("/"+di.Name(), di.Name(), http.FS(uiFS)) + } + } + router.GET("/", func(c *gin.Context) { + b, err := fs.ReadFile(uiFS, "index.html") + if err != nil { + _ = c.AbortWithError(http.StatusInternalServerError, err) + return + } + c.Data(http.StatusOK, "text/html; charset=utf-8", b) + }) + return nil +} + func (s *CommandResultsServer) getShortNames(c *gin.Context) { c.JSON(http.StatusOK, GetShortNames()) } From 52db53c5bee29d38d2392ef181cb21bee9d9168e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 14 Jun 2023 14:40:47 +0200 Subject: [PATCH 1819/2916] refactor: Move conversion of watch events into own function --- pkg/results/result-store-secrets.go | 55 +++++++++++++++++------------ 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/pkg/results/result-store-secrets.go b/pkg/results/result-store-secrets.go index 7f3e5cfa5..28a61cccb 100644 --- a/pkg/results/result-store-secrets.go +++ b/pkg/results/result-store-secrets.go @@ -251,6 +251,35 @@ func (s *ResultStoreSecrets) filterSummary(summary *result.CommandResultSummary, return true } +func (s *ResultStoreSecrets) convertWatchEvent(event watch.Event, filter *result.ProjectKey) *WatchCommandResultSummaryEvent { + if event.Object == nil { + return nil + } + x2, ok := event.Object.(client.Object) + if !ok { + return nil + } + summary, err := s.parseSummary(x2.GetAnnotations()) + if err != nil { + return nil + } + if !s.filterSummary(summary, filter) { + return nil + } + switch event.Type { + case watch.Deleted: + return &WatchCommandResultSummaryEvent{ + Delete: true, + Summary: summary, + } + case watch.Added, watch.Modified: + return &WatchCommandResultSummaryEvent{ + Summary: summary, + } + } + return nil +} + func (s *ResultStoreSecrets) WatchCommandResultSummaries(options ListCommandResultSummariesOptions) ([]*result.CommandResultSummary, <-chan WatchCommandResultSummaryEvent, context.CancelFunc, error) { var l metav1.PartialObjectMetadataList l.SetGroupVersionKind(schema.GroupVersionKind{Version: "v1", Kind: "SecretList"}) @@ -275,31 +304,11 @@ func (s *ResultStoreSecrets) WatchCommandResultSummaries(options ListCommandResu go func() { for x := range w.ResultChan() { - if x.Object == nil { + we := s.convertWatchEvent(x, options.ProjectFilter) + if we == nil { continue } - x2, ok := x.Object.(client.Object) - if !ok { - continue - } - summary, err := s.parseSummary(x2.GetAnnotations()) - if err != nil { - continue - } - if !s.filterSummary(summary, options.ProjectFilter) { - continue - } - switch x.Type { - case watch.Deleted: - ch <- WatchCommandResultSummaryEvent{ - Delete: true, - Summary: summary, - } - case watch.Added, watch.Modified: - ch <- WatchCommandResultSummaryEvent{ - Summary: summary, - } - } + ch <- *we } close(ch) }() From 9ad61feb931e5ec68068c80078bc28b260ac8d20 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 14 Jun 2023 14:42:42 +0200 Subject: [PATCH 1820/2916] fix: Actually delete temporary files --- pkg/webui/staticbuilder.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/webui/staticbuilder.go b/pkg/webui/staticbuilder.go index 142b5f148..70b209c3f 100644 --- a/pkg/webui/staticbuilder.go +++ b/pkg/webui/staticbuilder.go @@ -27,7 +27,7 @@ func (swb *StaticWebuiBuilder) Build(path string) error { if err != nil { return err } - //defer os.RemoveAll(tmpDir) + defer os.RemoveAll(tmpDir) summaries, err := swb.store.ListCommandResultSummaries(results.ListCommandResultSummariesOptions{}) if err != nil { From ffb3ffd9bc3cc74f276b464d35c37daf4ff10693 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 14 Jun 2023 15:06:03 +0200 Subject: [PATCH 1821/2916] fix: More reliable WaitForResults --- cmd/kluctl/commands/cmd_webui.go | 8 +++++++- pkg/results/results-collector.go | 32 ++++++++++++++++++++------------ 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/cmd/kluctl/commands/cmd_webui.go b/cmd/kluctl/commands/cmd_webui.go index 2ae1a520c..e3614cb85 100644 --- a/cmd/kluctl/commands/cmd_webui.go +++ b/cmd/kluctl/commands/cmd_webui.go @@ -32,7 +32,13 @@ func (cmd *webuiCmd) Run(ctx context.Context) error { collector.Start() if cmd.StaticPath != "" { - collector.WaitForInitialSync() + st := status.Start(ctx, "Collecting results") + defer st.Failed() + err = collector.WaitForResults(time.Second, time.Second*30) + if err != nil { + return err + } + st.Success() sbw := webui.NewStaticWebuiBuilder(collector) return sbw.Build(cmd.StaticPath) } else { diff --git a/pkg/results/results-collector.go b/pkg/results/results-collector.go index 2237c33a8..f053406d5 100644 --- a/pkg/results/results-collector.go +++ b/pkg/results/results-collector.go @@ -17,8 +17,7 @@ type ResultsCollector struct { resultSummaries map[string]summaryEntry watches []*watchEntry - initialWG sync.WaitGroup - mutex sync.Mutex + mutex sync.Mutex } type summaryEntry struct { @@ -40,8 +39,6 @@ func NewResultsCollector(ctx context.Context, stores []ResultStore) *ResultsColl resultSummaries: map[string]summaryEntry{}, } - ret.initialWG.Add(len(stores)) - return ret } @@ -49,8 +46,25 @@ func (rc *ResultsCollector) Start() { rc.startWatchResults() } -func (rc *ResultsCollector) WaitForInitialSync() { - rc.initialWG.Wait() +func (rc *ResultsCollector) WaitForResults(idleTimeout time.Duration, totalTimeout time.Duration) error { + _, ch, cancel, err := rc.WatchCommandResultSummaries(ListCommandResultSummariesOptions{}) + if err != nil { + return err + } + defer cancel() + + totalCh := time.After(totalTimeout) + for { + idleCh := time.After(idleTimeout) + select { + case <-ch: + continue + case <-idleCh: + return nil + case <-totalCh: + return fmt.Errorf("result collector kept receiving results even after the total timeout") + } + } } func (rc *ResultsCollector) startWatchResults() { @@ -60,7 +74,6 @@ func (rc *ResultsCollector) startWatchResults() { } func (rc *ResultsCollector) runWatchResults(store ResultStore) { - initial := true for { l, ch, _, err := store.WatchCommandResultSummaries(ListCommandResultSummariesOptions{}) if err != nil { @@ -68,11 +81,6 @@ func (rc *ResultsCollector) runWatchResults(store ResultStore) { continue } - if initial { - rc.initialWG.Done() - initial = false - } - for _, rs := range l { rc.handleUpdate(store, WatchCommandResultSummaryEvent{ Summary: rs, From 2f512ceeab0e6348f364d8c2051c0d59d3437141 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 14 Jun 2023 15:22:17 +0200 Subject: [PATCH 1822/2916] feat: Implement webui auth based on a simple admin account --- cmd/kluctl/commands/cmd_webui.go | 23 +- go.mod | 5 +- go.sum | 13 +- pkg/webui/auth.go | 274 ++++++++++++++++++ pkg/webui/server.go | 45 ++- pkg/webui/ui/src/api.tsx | 244 ++++++++-------- pkg/webui/ui/src/components/App.tsx | 113 +++++++- pkg/webui/ui/src/components/Loading.tsx | 26 +- pkg/webui/ui/src/components/Login.tsx | 56 ++++ pkg/webui/ui/src/components/ObjectYaml.tsx | 35 +-- pkg/webui/ui/src/components/Router.tsx | 3 +- .../result-view/CommandResultView.tsx | 39 ++- .../CommandResultDetailsDrawer.tsx | 43 +-- .../src/components/targets-view/Targets.tsx | 5 +- pkg/webui/websocket.go | 62 +++- 15 files changed, 789 insertions(+), 197 deletions(-) create mode 100644 pkg/webui/auth.go create mode 100644 pkg/webui/ui/src/components/Login.tsx diff --git a/cmd/kluctl/commands/cmd_webui.go b/cmd/kluctl/commands/cmd_webui.go index e3614cb85..344882505 100644 --- a/cmd/kluctl/commands/cmd_webui.go +++ b/cmd/kluctl/commands/cmd_webui.go @@ -4,10 +4,12 @@ import ( "context" "fmt" "github.com/kluctl/kluctl/v2/pkg/results" + "github.com/kluctl/kluctl/v2/pkg/status" "github.com/kluctl/kluctl/v2/pkg/webui" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" "sigs.k8s.io/controller-runtime/pkg/client" + "time" ) type webuiCmd struct { @@ -15,6 +17,9 @@ type webuiCmd struct { Context []string `group:"misc" help:"List of kubernetes contexts to use."` AllContexts bool `group:"misc" help:"Use all Kubernetes contexts found in the kubeconfig."` StaticPath string `group:"misc" help:"Build static webui."` + + InCluster bool `group:"misc" help:"This enables in-cluster functionality."` + InClusterContext string `group:"misc" help:"The context to use fo in-cluster functionality."` } func (cmd *webuiCmd) Help() string { @@ -22,6 +27,22 @@ func (cmd *webuiCmd) Help() string { } func (cmd *webuiCmd) Run(ctx context.Context) error { + var inClusterClient client.Client + if cmd.InCluster { + configOverrides := &clientcmd.ConfigOverrides{ + CurrentContext: cmd.InClusterContext, + } + config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( + clientcmd.NewDefaultClientConfigLoadingRules(), + configOverrides).ClientConfig() + if err != nil { + return err + } + inClusterClient, err = client.NewWithWatch(config, client.Options{}) + if err != nil { + return err + } + } stores, configs, err := cmd.createResultStores(ctx) if err != nil { @@ -42,7 +63,7 @@ func (cmd *webuiCmd) Run(ctx context.Context) error { sbw := webui.NewStaticWebuiBuilder(collector) return sbw.Build(cmd.StaticPath) } else { - server := webui.NewCommandResultsServer(ctx, collector, configs) + server := webui.NewCommandResultsServer(ctx, collector, configs, inClusterClient, inClusterClient != nil) return server.Run(cmd.Port) } } diff --git a/go.mod b/go.mod index d79fdb2ba..07201c049 100644 --- a/go.mod +++ b/go.mod @@ -74,6 +74,7 @@ require ( github.com/sergi/go-diff v1.3.1 github.com/tkrajina/typescriptify-golang-structs v0.1.10 go.mozilla.org/sops/v3 v3.7.4-0.20220901181616-9124783930b1 + nhooyr.io/websocket v1.8.7 sigs.k8s.io/cli-utils v0.34.0 sigs.k8s.io/controller-runtime v0.15.0 sigs.k8s.io/kustomize/api v0.13.4 @@ -176,6 +177,7 @@ require ( github.com/jmoiron/sqlx v1.3.5 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/karrick/godirwalk v1.17.0 // indirect github.com/klauspost/compress v1.16.5 // indirect github.com/klauspost/cpuid/v2 v2.0.9 // indirect github.com/kylelemons/godebug v1.1.0 // indirect @@ -186,6 +188,7 @@ require ( github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect @@ -201,6 +204,7 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc3 // indirect + github.com/opencontainers/runc v1.1.6 // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect @@ -259,7 +263,6 @@ require ( k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect k8s.io/kubectl v0.27.1 // indirect k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 // indirect - nhooyr.io/websocket v1.8.7 // indirect oras.land/oras-go v1.2.3 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect ) diff --git a/go.sum b/go.sum index 7e2749e3d..ff2d7461d 100644 --- a/go.sum +++ b/go.sum @@ -343,8 +343,11 @@ github.com/gobuffalo/packr/v2 v2.8.3 h1:xE1yzvnO56cUC0sTpKR3DIbxZgB54AftTFMhB2XE github.com/gobuffalo/packr/v2 v2.8.3/go.mod h1:0SahksCVcx4IMnigTjiFuyldmTrdTctXsOdiU5KwbKc= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= +github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= @@ -456,6 +459,7 @@ github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= @@ -549,8 +553,9 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw= github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= +github.com/karrick/godirwalk v1.17.0 h1:b4kY7nqDdioR/6qnbHQyDvmA17u5G1cZ6J+CZXwSWoI= +github.com/karrick/godirwalk v1.17.0/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= @@ -627,8 +632,9 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= +github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= @@ -691,7 +697,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc3 h1:fzg1mXZFj8YdPeNkRXMg+zb88BFV0Ys52cJydRwBkb8= github.com/opencontainers/image-spec v1.1.0-rc3/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= -github.com/opencontainers/runc v1.1.4 h1:nRCz/8sKg6K6jgYAFLDlXzPeITBZJyX28DBVhWD+5dg= +github.com/opencontainers/runc v1.1.6 h1:XbhB8IfG/EsnhNvZtNdLB0GBw92GYEFvKlhaJk9jUgA= +github.com/opencontainers/runc v1.1.6/go.mod h1:CbUumNnWCuTGFukNXahoo/RFBZvDAgRh/smNYNOhA50= github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= github.com/otiai10/copy v1.11.0 h1:OKBD80J/mLBrwnzXqGtFCzprFSGioo30JcmR4APsNwc= github.com/otiai10/copy v1.11.0/go.mod h1:rSaLseMUsZFFbsFGc7wCJnnkTAvdc5L6VWxPE4308Ww= diff --git a/pkg/webui/auth.go b/pkg/webui/auth.go new file mode 100644 index 000000000..fb9cc97e1 --- /dev/null +++ b/pkg/webui/auth.go @@ -0,0 +1,274 @@ +package webui + +import ( + "context" + "fmt" + "github.com/gin-gonic/gin" + "github.com/golang-jwt/jwt/v4" + corev1 "k8s.io/api/core/v1" + "net/http" + "sigs.k8s.io/controller-runtime/pkg/client" + "strings" + "time" +) + +const ( + tokenValidTime = 1 * time.Hour + tokenMaxRefreshTime = 2 * time.Hour +) + +type login struct { + Username string `form:"username" json:"username" binding:"required"` + Password string `form:"password" json:"password" binding:"required"` +} + +type authHandler struct { + ctx context.Context + + adminEnabled bool + + serverClient client.Client + webuiSecretName string +} + +type User struct { + Username string `json:"username"` + IsAdmin bool `json:"isAdmin"` +} + +func (s *authHandler) setupAuth(r gin.IRouter) error { + r.POST("/auth/login", s.loginHandler) + r.POST("/auth/refresh", s.refreshTokenHandler) + r.GET("/auth/user", s.authHandler, s.userHandler) + + return nil +} + +func (s *authHandler) loginHandler(c *gin.Context) { + if !s.adminEnabled { + c.AbortWithStatus(http.StatusUnauthorized) + return + } + + var loginVals login + if err := c.ShouldBind(&loginVals); err != nil { + c.AbortWithStatus(http.StatusUnauthorized) + return + } + userID := loginVals.Username + password := loginVals.Password + + as, err := s.getAdminSecrets() + if err != nil { + c.AbortWithStatus(http.StatusUnauthorized) + return + } + + if userID != "admin" || password != as.adminPassword { + // we only support the admin account at the moment + c.AbortWithStatus(http.StatusUnauthorized) + return + } + + token, err := s.createAdminToken(userID, as) + if err != nil { + c.AbortWithStatus(http.StatusUnauthorized) + return + } + + c.JSON(http.StatusOK, map[string]any{ + "token": token, + }) +} + +func (s *authHandler) refreshTokenHandler(c *gin.Context) { + if !s.adminEnabled { + c.AbortWithStatus(http.StatusUnauthorized) + return + } + + oldToken := s.getBearerToken(c) + if oldToken == "" { + c.AbortWithStatus(http.StatusUnauthorized) + return + } + + as, err := s.getAdminSecrets() + if err != nil { + c.AbortWithStatus(http.StatusUnauthorized) + return + } + + claims, err := s.checkIfAdminTokenExpired(oldToken, as) + if err != nil { + c.AbortWithStatus(http.StatusUnauthorized) + return + } + user := s.getAdminUserFromClaims(claims) + + newToken, err := s.createAdminToken(user.Username, as) + if err != nil { + c.AbortWithStatus(http.StatusUnauthorized) + return + } + + c.JSON(http.StatusOK, map[string]any{ + "token": newToken, + }) +} + +func (s *authHandler) createAdminToken(username string, as adminSecrets) (string, error) { + // Create the token + token := jwt.New(jwt.SigningMethodHS256) + claims := token.Claims.(jwt.MapClaims) + claims["id"] = username + + expire := time.Now().Add(tokenValidTime) + claims["exp"] = expire.Unix() + claims["orig_iat"] = time.Now().Unix() + tokenString, err := token.SignedString(as.secret) + if err != nil { + return "", err + } + return tokenString, nil +} + +func (s *authHandler) checkIfAdminTokenExpired(token string, as adminSecrets) (jwt.MapClaims, error) { + jt, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { + return as.secret, nil + }, jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Name})) + if err != nil { + validationErr, ok := err.(*jwt.ValidationError) + if !ok || validationErr.Errors != jwt.ValidationErrorExpired { + return nil, err + } + } + + claims := jt.Claims.(jwt.MapClaims) + + origIat := int64(claims["orig_iat"].(float64)) + + if origIat < time.Now().Add(-tokenMaxRefreshTime).Unix() { + return nil, fmt.Errorf("token expired") + } + + return claims, nil +} + +func (s *authHandler) getBearerToken(c *gin.Context) string { + authHeader := c.GetHeader("Authorization") + if authHeader == "" { + return "" + } + + parts := strings.SplitN(authHeader, " ", 2) + if len(parts) != 2 || parts[0] != "Bearer" { + c.AbortWithStatus(http.StatusUnauthorized) + return "" + } + + return parts[1] +} + +func (s *authHandler) getUser(c *gin.Context) *User { + if s == nil { + return nil + } + token := s.getBearerToken(c) + if token == "" { + return nil + } + return s.getUserFromToken(token) +} + +func (s *authHandler) getUserFromToken(token string) *User { + user := s.getAdminUser(token) + if user != nil { + return user + } + return nil +} + +func (s *authHandler) getAdminUser(token string) *User { + as, err := s.getAdminSecrets() + if err != nil { + return nil + } + + jt, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { + return as.secret, nil + }, jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Name})) + if err != nil { + return nil + } + + claims, ok := jt.Claims.(jwt.MapClaims) + if !ok { + return nil + } + + return s.getAdminUserFromClaims(claims) +} + +func (s *authHandler) getAdminUserFromClaims(claims jwt.MapClaims) *User { + x, ok := claims["id"] + if !ok { + return nil + } + id, ok := x.(string) + if !ok { + return nil + } + + return &User{ + Username: id, + IsAdmin: true, + } +} + +func (s *authHandler) authHandler(c *gin.Context) { + if s == nil { + return + } + if s.getUser(c) == nil { + c.AbortWithStatus(http.StatusUnauthorized) + } +} + +func (s *authHandler) userHandler(c *gin.Context) { + user := s.getUser(c) + c.JSON(http.StatusOK, user) +} + +type adminSecrets struct { + secret []byte + adminPassword string +} + +func (s *authHandler) getAdminSecrets() (adminSecrets, error) { + if s.serverClient == nil { + return adminSecrets{}, fmt.Errorf("no serverClient set") + } + + var secret corev1.Secret + err := s.serverClient.Get(s.ctx, client.ObjectKey{Name: s.webuiSecretName, Namespace: "kluctl-system"}, &secret) + if err != nil { + return adminSecrets{}, err + } + + var ret adminSecrets + + x, ok := secret.Data["secret"] + if !ok { + return adminSecrets{}, fmt.Errorf("admin secret %s has no 'secret' key", s.webuiSecretName) + } + ret.secret = x + + x, ok = secret.Data["adminPassword"] + if !ok { + return adminSecrets{}, fmt.Errorf("admin secret %s has no adminPassword", s.webuiSecretName) + } + ret.adminPassword = string(x) + + return ret, nil +} diff --git a/pkg/webui/server.go b/pkg/webui/server.go index 6aad01bd9..31b45181c 100644 --- a/pkg/webui/server.go +++ b/pkg/webui/server.go @@ -27,15 +27,35 @@ type CommandResultsServer struct { store results.ResultStore cam *clusterAccessorManager vam *validatorManager + + // this is the client for the k8s cluster where the server runs on + serverClient client.Client + + auth *authHandler } -func NewCommandResultsServer(ctx context.Context, store *results.ResultsCollector, configs []*rest.Config) *CommandResultsServer { +func NewCommandResultsServer(ctx context.Context, store *results.ResultsCollector, configs []*rest.Config, serverClient client.Client, authEnabled bool) *CommandResultsServer { ret := &CommandResultsServer{ ctx: ctx, store: store, cam: &clusterAccessorManager{ ctx: ctx, }, + serverClient: serverClient, + } + + adminEnabled := false + if serverClient != nil { + adminEnabled = true + } + + if authEnabled { + ret.auth = &authHandler{ + ctx: ctx, + adminEnabled: adminEnabled, + serverClient: serverClient, + webuiSecretName: "admin-secret", + } } for _, config := range configs { @@ -76,19 +96,28 @@ func (s *CommandResultsServer) Run(port int) error { router := gin.Default() + if s.auth != nil { + err = s.auth.setupAuth(router) + if err != nil { + return err + } + } + err = s.setupStaticRoutes(router) if err != nil { return err } api := router.Group("/api") - api.GET("/getShortNames", s.getShortNames) - api.GET("/getResult", s.getResult) - api.GET("/getResultSummary", s.getResultSummary) - api.GET("/getResultObject", s.getResultObject) - api.POST("/validateNow", s.validateNow) - api.POST("/reconcileNow", s.reconcileNow) - api.POST("/deployNow", s.deployNow) + api.GET("/getShortNames", s.auth.authHandler, s.getShortNames) + api.GET("/getResult", s.auth.authHandler, s.getResult) + api.GET("/getResultSummary", s.auth.authHandler, s.getResultSummary) + api.GET("/getResultObject", s.auth.authHandler, s.getResultObject) + api.POST("/validateNow", s.auth.authHandler, s.validateNow) + api.POST("/reconcileNow", s.auth.authHandler, s.reconcileNow) + api.POST("/deployNow", s.auth.authHandler, s.deployNow) + + // handles authentication via the first message api.Any("/ws", s.ws) address := fmt.Sprintf(":%d", port) diff --git a/pkg/webui/ui/src/api.tsx b/pkg/webui/ui/src/api.tsx index f175bbd9a..b70f1d8bc 100644 --- a/pkg/webui/ui/src/api.tsx +++ b/pkg/webui/ui/src/api.tsx @@ -15,7 +15,6 @@ import "./staticbuild.d.ts" import { loadScript } from "./loadscript"; import { sleep } from "./utils/misc"; -const apiUrl = "/api" const staticPath = "./staticdata" export enum ObjectType { @@ -24,6 +23,11 @@ export enum ObjectType { Applied = "applied", } +export interface User { + username: string + isAdmin: boolean +} + export interface Api { getShortNames(): Promise listenUpdates(filterProject: string | undefined, filterSubDir: string | undefined, handle: (msg: any) => void): Promise<() => void> @@ -35,64 +39,109 @@ export interface Api { deployNow(cluster: string, name: string, namespace: string): Promise } -class RealOrStaticApi implements Api { - api: Promise - - constructor() { - this.api = this.buildApi() - } - - async buildApi(): Promise { - const p = loadScript(staticPath + "/summaries.js") - try { - await p - return new StaticApi() - } catch (error) { - return new RealApi() - } - } - - async deployNow(cluster: string, name: string, namespace: string): Promise { - return (await this.api).deployNow(cluster, name, namespace) - } - - async getResult(resultId: string): Promise { - return (await this.api).getResult(resultId) +export async function checkStaticBuild() { + const p = loadScript(staticPath + "/summaries.js") + try { + await p + return true + } catch (error) { + return false } +} - async getResultObject(resultId: string, ref: ObjectRef, objectType: string): Promise { - return (await this.api).getResultObject(resultId, ref, objectType) - } +export class RealApi implements Api { + getToken: (() => string) | undefined; + onUnauthorized: () => void; + onTokenRefresh: (newToken: string) => void; - async getResultSummary(resultId: string): Promise { - return (await this.api).getResultSummary(resultId) + constructor(getToken: (() => string) | undefined, onUnauthorized: () => void, onTokenRefresh: (newToken: string) => void) { + this.getToken = getToken + this.onUnauthorized = onUnauthorized + this.onTokenRefresh = onTokenRefresh } - async getShortNames(): Promise { - return (await this.api).getShortNames() + setAuthorizationHeader(headers: any) { + if (!this.getToken) { + return + } + headers['Authorization'] = "Bearer " + this.getToken() } - async listenUpdates(filterProject: string | undefined, filterSubDir: string | undefined, handle: (msg: any) => void): Promise<() => void> { - return (await this.api).listenUpdates(filterProject, filterSubDir, handle) + async refreshToken() { + const headers = { + 'Accept': 'application/json', + } + this.setAuthorizationHeader(headers) + const resp = await fetch("/auth/refresh", { + method: "POST", + headers: headers, + }) + if (resp.status === 401) { + this.onUnauthorized() + throw Error(resp.statusText) + } + const j = await resp.json() + this.onTokenRefresh(j.token) + } + + async handleErrors(response: Response, retry?: () => Promise): Promise { + if (!response.ok) { + if (response.status === 401) { + if (retry) { + console.log("retrying with token refresh") + await this.refreshToken() + const newResp = await retry() + return await this.handleErrors(newResp) + } else { + this.onUnauthorized() + } + } + throw Error(response.statusText) + } + return response } - async reconcileNow(cluster: string, name: string, namespace: string): Promise { - return (await this.api).reconcileNow(cluster, name, namespace) + async doGet(path: string, params?: URLSearchParams) { + let url = path + if (params) { + url += "?" + params.toString() + } + const doFetch = () => { + const headers = { + 'Accept': 'application/json', + } + this.setAuthorizationHeader(headers) + return fetch(url, { + method: "GET", + headers: headers, + }) + } + let resp = await doFetch() + resp = await this.handleErrors(resp, doFetch) + return resp.json() } - async validateNow(project: ProjectKey, target: TargetKey): Promise { - return (await this.api).validateNow(project, target) + async doPost(path: string, body: any) { + let url = path + const doFetch = () => { + const headers = { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + } + this.setAuthorizationHeader(headers) + return fetch(url, { + method: "POST", + body: JSON.stringify(body), + headers: headers, + }) + } + let resp = await doFetch() + resp = await this.handleErrors(resp, doFetch) + return resp } -} - -export const api = new RealOrStaticApi() -class RealApi implements Api { async getShortNames(): Promise { - let url = `${apiUrl}/getShortNames` - return fetch(url) - .then(handleErrors) - .then((response) => response.json()); + return this.doGet("/api/getShortNames") } async listenUpdates(filterProject: string | undefined, filterSubDir: string | undefined, handle: (msg: any) => void): Promise<() => void> { @@ -100,7 +149,7 @@ class RealApi implements Api { if (process.env.NODE_ENV === 'development') { host = "localhost:9090" } - let url = `ws://${host}${apiUrl}/ws` + let url = `ws://${host}/api/ws` const params = new URLSearchParams() if (filterProject) { params.set("filterProject", filterProject) @@ -110,10 +159,11 @@ class RealApi implements Api { } url += "?" + params.toString() + const getToken = this.getToken let ws: WebSocket | undefined; let cancelled = false - const connect = () => { + const connect = async () => { if (cancelled) { return } @@ -122,6 +172,9 @@ class RealApi implements Api { ws = new WebSocket(url); ws.onopen = function () { console.log("ws connected") + if (getToken) { + ws!.send(JSON.stringify({ "type": "auth", "token": getToken() })) + } } ws.onclose = function (event) { console.log("ws close") @@ -141,7 +194,7 @@ class RealApi implements Api { } } - connect() + await connect() return () => { console.log("ws cancel") @@ -153,53 +206,36 @@ class RealApi implements Api { } async getResult(resultId: string) { - let url = `${apiUrl}/getResult?resultId=${resultId}` - return fetch(url) - .then(handleErrors) - .then(response => response.text()) - .then(json => { - return new CommandResult(json) - }); + const params = new URLSearchParams() + params.set("resultId", resultId) + const json = await this.doGet("/api/getResult", params) + return new CommandResult(json) } async getResultSummary(resultId: string) { - let url = `${apiUrl}/getResultSummary?resultId=${resultId}` - return fetch(url) - .then(handleErrors) - .then(response => response.text()) - .then(json => { - return new CommandResultSummary(json) - }); + const params = new URLSearchParams() + params.set("resultId", resultId) + const json = await this.doGet("/api/getResultSummary", params) + return new CommandResultSummary(json) } async getResultObject(resultId: string, ref: ObjectRef, objectType: string) { - let url = `${apiUrl}/getResultObject?resultId=${resultId}&${buildRefParams(ref)}&objectType=${objectType}` - return fetch(url) - .then(handleErrors) - .then(response => response.json()); - } - - async doPost(f: string, body: any) { - let url = `${apiUrl}/${f}` - return fetch(url, { - method: "POST", - body: JSON.stringify(body), - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - }, - }).then(handleErrors); + const params = new URLSearchParams() + params.set("resultId", resultId) + params.set("objectType", objectType) + buildRefParams(ref, params) + return await this.doGet("/api/getResultObject", params) } async validateNow(project: ProjectKey, target: TargetKey) { - return this.doPost("validateNow", { + return this.doPost("/api/validateNow", { "project": project, "target": target, }) } async deployNow(cluster: string, name: string, namespace: string): Promise { - return this.doPost("deployNow", { + return this.doPost("/api/deployNow", { "cluster": cluster, "name": name, "namespace": namespace, @@ -207,7 +243,7 @@ class RealApi implements Api { } async reconcileNow(cluster: string, name: string, namespace: string): Promise { - return this.doPost("reconcileNow", { + return this.doPost("/api/reconcileNow", { "cluster": cluster, "name": name, "namespace": namespace, @@ -215,7 +251,7 @@ class RealApi implements Api { } } -class StaticApi implements Api { +export class StaticApi implements Api { async getShortNames(): Promise { await loadScript(staticPath + "/shortnames.js") return staticShortNames @@ -244,12 +280,14 @@ class StaticApi implements Api { await loadScript(staticPath + `/result-${resultId}.js`) return staticResults.get(resultId) } + async getResultSummary(resultId: string): Promise { await loadScript(staticPath + "/summaries.js") return staticSummaries.filter(s => { return s.id === resultId }).at(0) } + async getResultObject(resultId: string, ref: ObjectRef, objectType: string): Promise { const result = await this.getResult(resultId) const object = result.objects?.find(x => _.isEqual(x.ref, ref)) @@ -267,26 +305,21 @@ class StaticApi implements Api { throw new Error("unknown object type " + objectType) } } + validateNow(project: ProjectKey, target: TargetKey): Promise { throw new Error("not implemented") } + reconcileNow(cluster: string, name: string, namespace: string): Promise { throw new Error("not implemented") } + deployNow(cluster: string, name: string, namespace: string): Promise { throw new Error("not implemented") } } -function handleErrors(response: Response) { - if (!response.ok) { - throw Error(response.statusText) - } - return response -} - -function buildRefParams(ref: ObjectRef): string { - const params = new URLSearchParams() +function buildRefParams(ref: ObjectRef, params: URLSearchParams) { params.set("kind", ref.kind) params.set("name", ref.name) if (ref.group) { @@ -298,7 +331,6 @@ function buildRefParams(ref: ObjectRef): string { if (ref.namespace) { params.set("namespace", ref.namespace) } - return params.toString() } export function buildRefString(ref: ObjectRef): string { @@ -347,31 +379,3 @@ export function findObjectByRef(l: ResultObject[] | undefined, ref: ObjectRef, f return _.isEqual(x.ref, ref) }) } - -export function usePromise(p?: Promise): T { - if (p === undefined) { - throw new Promise(() => undefined) - } - - const promise = p as any - if (promise.status === 'fulfilled') { - return promise.value; - } else if (promise.status === 'rejected') { - throw promise.reason; - } else if (promise.status === 'pending') { - throw promise; - } else { - promise.status = 'pending'; - p.then( - result => { - promise.status = 'fulfilled'; - promise.value = result; - }, - reason => { - promise.status = 'rejected'; - promise.reason = reason; - }, - ); - throw promise; - } -} \ No newline at end of file diff --git a/pkg/webui/ui/src/components/App.tsx b/pkg/webui/ui/src/components/App.tsx index 82f605ddb..e7c100e0f 100644 --- a/pkg/webui/ui/src/components/App.tsx +++ b/pkg/webui/ui/src/components/App.tsx @@ -1,4 +1,13 @@ -import React, { createContext, Dispatch, SetStateAction, useEffect, useMemo, useRef, useState } from 'react'; +import React, { + createContext, + Dispatch, + SetStateAction, + useContext, + useEffect, + useMemo, + useRef, + useState +} from 'react'; import '../index.css'; import { Box, ThemeProvider } from "@mui/material"; @@ -7,8 +16,10 @@ import LeftDrawer from "./LeftDrawer"; import { light } from './theme'; import { ActiveFilters } from './result-view/NodeStatusFilter'; import { CommandResultSummary, ProjectTargetKey, ValidateResult } from "../models"; -import { api } from "../api"; +import { Api, checkStaticBuild, RealApi, StaticApi } from "../api"; import { buildProjectSummaries, ProjectSummary } from "../project-summaries"; +import Login from "./Login"; +import { Loading } from "./Loading"; export interface AppOutletContext { filters?: ActiveFilters @@ -29,7 +40,10 @@ export const AppContext = createContext({ validateResults: new Map(), }); -const App = () => { +export const ApiContext = createContext(new StaticApi()) + +const LoggedInApp = (props: {onUnauthorized: () => void}) => { + const api = useContext(ApiContext) const [filters, setFilters] = useState() const summariesRef = useRef>(new Map()) @@ -37,6 +51,8 @@ const App = () => { const [summaries, setSummaries] = useState(summariesRef.current) const [validateResults, setValidateResults] = useState(validateResultsRef.current) + const onUnauthorized = props.onUnauthorized + useEffect(() => { const updateSummary = (rs: CommandResultSummary) => { console.log("update_summary", rs.id, rs.commandInfo.startTime) @@ -57,7 +73,8 @@ const App = () => { } console.log("starting listenResults") - const cancel = api.listenUpdates(undefined, undefined, msg => { + let cancel: Promise<() => void> + cancel = api.listenUpdates(undefined, undefined, msg => { switch(msg.type) { case "update_summary": updateSummary(msg.summary) @@ -68,13 +85,18 @@ const App = () => { case "validate_result": updateValidateResult(msg.key, msg.result) break + case "auth_result": + if (!msg.success) { + cancel.then(c => c()) + onUnauthorized() + } } }) return () => { console.log("cancel listenResults") cancel.then(c => c()) } - }, []) + }, [api, onUnauthorized]) const projects = useMemo(() => { return buildProjectSummaries(summaries, validateResults) @@ -102,4 +124,85 @@ const App = () => { ); }; +const App = () => { + const [api, setApi] = useState() + const [needToken, setNeedToken] = useState(false) + + const storage = localStorage + + const getToken = () => { + const token = storage.getItem("token") + if (!token) { + return "" + } + return JSON.parse(token) + } + const setToken = (token?: string) => { + if (!token) { + storage.removeItem("token") + } else { + storage.setItem("token", JSON.stringify(token)) + } + } + + const onUnauthorized = () => { + console.log("handle onUnauthorized") + setToken(undefined) + setApi(undefined) + setNeedToken(true) + } + const onTokenRefresh = (newToken: string) => { + console.log("handle onTokenRefresh") + setToken(newToken) + } + + const handleLoginSucceeded = (token: string) => { + console.log("handle saveToken") + setToken(token); + setApi(new RealApi(getToken, onUnauthorized, onTokenRefresh)) + }; + + useEffect(() => { + if (api) { + return + } + + const doInit = async () => { + const isStatic = await checkStaticBuild() + if (isStatic) { + setApi(new StaticApi()) + } else { + // check if we don't need auth (running locally?) + const noAuthApi = new RealApi(undefined, onUnauthorized, onTokenRefresh) + try { + await noAuthApi.getShortNames() + setToken(undefined) + setNeedToken(false) + setApi(noAuthApi) + } catch (error) { + if (!getToken()) { + setNeedToken(true) + } else { + setApi(new RealApi(getToken, onUnauthorized, onTokenRefresh)) + } + } + } + } + doInit() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + if (needToken && !getToken()) { + return + } + + if (!api) { + return + } + + return + + +} + export default App; diff --git a/pkg/webui/ui/src/components/Loading.tsx b/pkg/webui/ui/src/components/Loading.tsx index 9b26bf95e..307f9d34b 100644 --- a/pkg/webui/ui/src/components/Loading.tsx +++ b/pkg/webui/ui/src/components/Loading.tsx @@ -1,4 +1,5 @@ import { Box, CircularProgress } from "@mui/material"; +import { DependencyList, useEffect, useState } from "react"; export const Loading = () => { return @@ -12,4 +13,27 @@ export const Loading = () => { -} \ No newline at end of file +} + +export function useLoadingHelper(load: () => Promise, deps: DependencyList): [boolean, any, T | undefined] { + const [loading, setLoading] = useState(true) + const [error, setError] = useState() + const [content, setContent] = useState() + + useEffect(() => { + const doStartLoading = async () => { + try { + const c = await load() + setContent(c) + setLoading(false) + } catch (error) { + setError(error) + setLoading(false) + } + } + doStartLoading() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [...deps, load]) + + return [loading, error, content] +} diff --git a/pkg/webui/ui/src/components/Login.tsx b/pkg/webui/ui/src/components/Login.tsx new file mode 100644 index 000000000..b1fd66064 --- /dev/null +++ b/pkg/webui/ui/src/components/Login.tsx @@ -0,0 +1,56 @@ +import React, { SyntheticEvent, useState } from 'react'; + +//import './Login.css'; + +interface loginCredentials { + username: string + password: string +} + +async function loginUser(creds: loginCredentials) { + const url = `/auth/login` + return fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(creds) + }).then(data => data.json()) + .then(token => token.token) +} + +export default function Login(props: { setToken: (token: string) => void}) { + const [username, setUserName] = useState(); + const [password, setPassword] = useState(); + + const handleSubmit = async (e: SyntheticEvent) => { + e.preventDefault(); + if (!username || !password) { + return + } + const token = await loginUser({ + username: username, + password: password, + }); + props.setToken(token); + } + + return( +
    +

    Please Log In

    +
    + + +
    + +
    +
    +
    + ) +} diff --git a/pkg/webui/ui/src/components/ObjectYaml.tsx b/pkg/webui/ui/src/components/ObjectYaml.tsx index 80a71a2b8..388931dcd 100644 --- a/pkg/webui/ui/src/components/ObjectYaml.tsx +++ b/pkg/webui/ui/src/components/ObjectYaml.tsx @@ -1,28 +1,25 @@ import { CommandResultProps } from "./result-view/CommandResultView"; import { ObjectRef } from "../models"; -import { api, ObjectType, usePromise } from "../api"; -import React, { Suspense, useEffect, useState } from "react"; +import { ObjectType } from "../api"; +import React, { useContext } from "react"; import { CodeViewer } from "./CodeViewer"; +import { Loading, useLoadingHelper } from "./Loading"; +import { ApiContext } from "./App"; import * as yaml from 'js-yaml'; -import { Loading } from "./Loading"; export const ObjectYaml = (props: {treeProps: CommandResultProps, objectRef: ObjectRef, objectType: ObjectType}) => { - const [promise, setPromise] = useState>() + const api = useContext(ApiContext) + const [loading, error, content] = useLoadingHelper(async () => { + const o = await api.getResultObject(props.treeProps.summary.id, props.objectRef, props.objectType) + return yaml.dump(o) + }, [props.treeProps.summary.id, props.objectRef, props.objectType]) - useEffect(() => { - const getData = async () => { - const o = await api.getResultObject(props.treeProps.summary.id, props.objectRef, props.objectType) - return yaml.dump(o) - } - setPromise(getData()) - }, [props.treeProps, props.objectRef, props.objectType]) - - const Content = () => { - return + if (loading) { + return + } else if (error) { + return <>Error + } else { + return } - - return }> - - -} \ No newline at end of file +} diff --git a/pkg/webui/ui/src/components/Router.tsx b/pkg/webui/ui/src/components/Router.tsx index a08c8abd5..54af5c317 100644 --- a/pkg/webui/ui/src/components/Router.tsx +++ b/pkg/webui/ui/src/components/Router.tsx @@ -2,7 +2,7 @@ import { createHashRouter, useRouteError } from "react-router-dom"; import React from "react"; import App from "./App"; import { TargetsView } from "./targets-view/TargetsView"; -import { commandResultLoader, CommandResultView } from "./result-view/CommandResultView"; +import { CommandResultView } from "./result-view/CommandResultView"; function ErrorPage() { const error = useRouteError() as any; @@ -32,7 +32,6 @@ export const Router = createHashRouter([ { path: "results/:id", element: , - loader: commandResultLoader, errorElement: , }, ], diff --git a/pkg/webui/ui/src/components/result-view/CommandResultView.tsx b/pkg/webui/ui/src/components/result-view/CommandResultView.tsx index 70c16cb4b..3bb0b0edc 100644 --- a/pkg/webui/ui/src/components/result-view/CommandResultView.tsx +++ b/pkg/webui/ui/src/components/result-view/CommandResultView.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { useState } from 'react'; +import { useContext, useState } from 'react'; import { Box, Checkbox, @@ -15,11 +15,12 @@ import { NodeData } from "./nodes/NodeData"; import { SidePanel } from "./SidePanel"; import { ActiveFilters } from "./NodeStatusFilter"; import CommandResultTree from "./CommandResultTree"; -import { useLoaderData } from "react-router-dom"; -import { useAppOutletContext } from "../App"; +import { useParams } from "react-router-dom"; +import { ApiContext, useAppOutletContext } from "../App"; import { ChangesIcon, CheckboxCheckedIcon, CheckboxIcon, StarIcon, WarningSignIcon } from '../../icons/Icons'; import { dark } from '../theme'; -import { api } from "../../api"; +import { Api } from "../../api"; +import { Loading, useLoadingHelper } from "../Loading"; export interface CommandResultProps { shortNames: ShortName[] @@ -27,15 +28,15 @@ export interface CommandResultProps { commandResult: CommandResult } -export async function commandResultLoader({ params }: any) { - const result = api.getResult(params.id) - const shortNames = api.getShortNames() - const rs = api.getResult(params.id) +async function doLoadCommandResult(api: Api, resultId: string): Promise { + const shortNames = await api.getShortNames() + const rs = await api.getResultSummary(resultId) + const result = await api.getResult(resultId) return { - shortNames: await shortNames, - summary: await rs, - commandResult: await result, + shortNames: shortNames, + summary: rs, + commandResult: result, } } @@ -117,9 +118,17 @@ const defaultFilters = { export const CommandResultView = () => { const context = useAppOutletContext(); - const commandResultProps = useLoaderData() as CommandResultProps; + const api = useContext(ApiContext) const [sidePanelNode, setSidePanelNode] = useState(); + const {id} = useParams() + const [loading, loadingError, commandResultProps] = useLoadingHelper(() => { + if (!id) { + return Promise.resolve(undefined) + } + return doLoadCommandResult(api, id) + }, [id]) + const divider = { })); } + if (loading) { + return + } else if (loadingError) { + return <>Error + } + return { const { ps, ts, selectedCardRect } = props; + const api = useContext(ApiContext) const theme = useTheme(); - const [promise, setPromise] = useState>(new Promise(() => undefined)); const [selectedCommandResult, setSelectedCommandResult] = useState(); const [prevTargetSummary, setPrevTargetSummary] = useState(ts); const [cardsCoords, setCardsCoords] = useState<{ left: number, top: number }[]>([]); @@ -45,18 +47,13 @@ export const CommandResultDetailsDrawer = React.memo((props: { setSelectedCommandResult(ts?.commandResults?.[0]); } - useEffect(() => { + const [loading, loadingError, nodeData] = useLoadingHelper(() => { if (selectedCommandResult === undefined) { - return + return Promise.resolve(undefined) } - setPromise(doGetRootNode(selectedCommandResult)); + return doGetRootNode(api, selectedCommandResult) }, [selectedCommandResult]) - const Content = (props: { onClose: () => void }) => { - const node = usePromise(promise) - return - } - const cardsContainerElem = useRef(); useEffect(() => { @@ -130,6 +127,10 @@ export const CommandResultDetailsDrawer = React.memo((props: { }) }, [ps, ts, cardsCoords, zIndex, theme.transitions, selectedCommandResult]) + if (loadingError) { + return <>Error + } + return <> {ps && ts && ts.commandResults && ts.commandResults.length > 0 && - }> - - + {loading ? : + + } diff --git a/pkg/webui/ui/src/components/targets-view/Targets.tsx b/pkg/webui/ui/src/components/targets-view/Targets.tsx index 4a7bf1f4a..b12ffa5e1 100644 --- a/pkg/webui/ui/src/components/targets-view/Targets.tsx +++ b/pkg/webui/ui/src/components/targets-view/Targets.tsx @@ -2,13 +2,13 @@ import { KluctlDeploymentInfo } from "../../models"; import { ActionMenuItem, ActionsMenu } from "../ActionsMenu"; import Paper from "@mui/material/Paper"; import { Box, Typography, useTheme } from "@mui/material"; -import React from "react"; +import React, { useContext } from "react"; import Tooltip from "@mui/material/Tooltip"; import { Favorite, HeartBroken, PublishedWithChanges } from "@mui/icons-material"; -import { api } from "../../api"; import { CpuIcon, FingerScanIcon, MessageQuestionIcon, TargetIcon } from "../../icons/Icons"; import { ProjectSummary, TargetSummary } from "../../project-summaries"; import { calcAgo } from "../../utils/duration"; +import { ApiContext } from "../App"; const StatusIcon = (props: { ps: ProjectSummary, ts: TargetSummary }) => { let icon: React.ReactElement @@ -55,6 +55,7 @@ const StatusIcon = (props: { ps: ProjectSummary, ts: TargetSummary }) => { } export const TargetItem = (props: { ps: ProjectSummary, ts: TargetSummary, onSelectTarget: (ts?: TargetSummary) => void }) => { + const api = useContext(ApiContext) const actionMenuItems: ActionMenuItem[] = [] actionMenuItems.push({ diff --git a/pkg/webui/websocket.go b/pkg/webui/websocket.go index 2a21cb462..c01bd3ff0 100644 --- a/pkg/webui/websocket.go +++ b/pkg/webui/websocket.go @@ -43,6 +43,13 @@ func (s *CommandResultsServer) ws(c *gin.Context) { } defer conn.Close(websocket.StatusInternalError, "the sky is falling") + // don't try anything else before we get the auth message + user, err := s.wsHandleAuth(conn) + if err != nil { + c.AbortWithStatus(http.StatusUnauthorized) + return + } + repoKey, err := types.ParseGitRepoKey(args.FilterProject) if err != nil { _ = c.AbortWithError(http.StatusBadRequest, err) @@ -57,7 +64,7 @@ func (s *CommandResultsServer) ws(c *gin.Context) { } } - err = s.wsHandle(conn, filter) + err = s.wsHandle(conn, user, filter) if err != nil { cs := websocket.CloseStatus(err) if cs == websocket.StatusNormalClosure || cs == websocket.StatusGoingAway { @@ -67,7 +74,58 @@ func (s *CommandResultsServer) ws(c *gin.Context) { } } -func (s *CommandResultsServer) wsHandle(c *websocket.Conn, filter *result.ProjectKey) error { +func (s *CommandResultsServer) wsHandleAuth(c *websocket.Conn) (*User, error) { + if s.auth == nil { + return nil, nil + } + + t, msg, err := c.Read(s.ctx) + if err != nil { + return nil, err + } + if t != websocket.MessageText { + return nil, fmt.Errorf("unexpected message type") + } + + type authMsg struct { + Type string `json:"type"` + Token string `json:"token"` + } + type authResult struct { + Type string `json:"type"` + Success bool `json:"success"` + } + var am authMsg + err = yaml.ReadYamlString(string(msg), &am) + if err != nil { + return nil, err + } + if am.Type != "auth" { + return nil, fmt.Errorf("unexpected message type") + } + + user := s.auth.getUserFromToken(am.Token) + if user == nil { + msg, _ := yaml.WriteJsonString(authResult{ + Type: "auth_result", + Success: false, + }) + _ = c.Write(s.ctx, websocket.MessageText, []byte(msg)) + return nil, fmt.Errorf("invalid token") + } else { + msg, _ := yaml.WriteJsonString(authResult{ + Type: "auth_result", + Success: true, + }) + err = c.Write(s.ctx, websocket.MessageText, []byte(msg)) + if err != nil { + return nil, err + } + return user, nil + } +} + +func (s *CommandResultsServer) wsHandle(c *websocket.Conn, user *User, filter *result.ProjectKey) error { initial, ch, cancel, err := s.store.WatchCommandResultSummaries(results.ListCommandResultSummariesOptions{ProjectFilter: filter}) if err != nil { return err From 20ab3bf15d126383135e3d3b0ea62b3ce2867dd1 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 14 Jun 2023 15:58:24 +0200 Subject: [PATCH 1823/2916] fix: Don't try refresh/logout when doing the initial creds check --- pkg/webui/ui/src/api.tsx | 22 ++++++++++++++-------- pkg/webui/ui/src/components/App.tsx | 2 +- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/pkg/webui/ui/src/api.tsx b/pkg/webui/ui/src/api.tsx index b70f1d8bc..2bd092be2 100644 --- a/pkg/webui/ui/src/api.tsx +++ b/pkg/webui/ui/src/api.tsx @@ -50,11 +50,11 @@ export async function checkStaticBuild() { } export class RealApi implements Api { - getToken: (() => string) | undefined; - onUnauthorized: () => void; - onTokenRefresh: (newToken: string) => void; + getToken?: (() => string); + onUnauthorized?: () => void; + onTokenRefresh?: (newToken: string) => void; - constructor(getToken: (() => string) | undefined, onUnauthorized: () => void, onTokenRefresh: (newToken: string) => void) { + constructor(getToken?: (() => string), onUnauthorized?: () => void, onTokenRefresh?: (newToken: string) => void) { this.getToken = getToken this.onUnauthorized = onUnauthorized this.onTokenRefresh = onTokenRefresh @@ -77,23 +77,29 @@ export class RealApi implements Api { headers: headers, }) if (resp.status === 401) { - this.onUnauthorized() + if (this.onUnauthorized) { + this.onUnauthorized() + } throw Error(resp.statusText) } const j = await resp.json() - this.onTokenRefresh(j.token) + if (this.onTokenRefresh) { + this.onTokenRefresh(j.token) + } } async handleErrors(response: Response, retry?: () => Promise): Promise { if (!response.ok) { if (response.status === 401) { - if (retry) { + if (this.getToken && retry) { console.log("retrying with token refresh") await this.refreshToken() const newResp = await retry() return await this.handleErrors(newResp) } else { - this.onUnauthorized() + if (this.onUnauthorized) { + this.onUnauthorized() + } } } throw Error(response.statusText) diff --git a/pkg/webui/ui/src/components/App.tsx b/pkg/webui/ui/src/components/App.tsx index e7c100e0f..c330fd775 100644 --- a/pkg/webui/ui/src/components/App.tsx +++ b/pkg/webui/ui/src/components/App.tsx @@ -173,7 +173,7 @@ const App = () => { setApi(new StaticApi()) } else { // check if we don't need auth (running locally?) - const noAuthApi = new RealApi(undefined, onUnauthorized, onTokenRefresh) + const noAuthApi = new RealApi(undefined, undefined, undefined) try { await noAuthApi.getShortNames() setToken(undefined) From d8c2c696e85bc0ec122e6251e5a1f82925869976 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 14 Jun 2023 15:58:40 +0200 Subject: [PATCH 1824/2916] fix: Don't use load as dep in useLoadingHelper --- pkg/webui/ui/src/components/Loading.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/webui/ui/src/components/Loading.tsx b/pkg/webui/ui/src/components/Loading.tsx index 307f9d34b..4cf7b8189 100644 --- a/pkg/webui/ui/src/components/Loading.tsx +++ b/pkg/webui/ui/src/components/Loading.tsx @@ -33,7 +33,7 @@ export function useLoadingHelper(load: () => Promise, deps: DependencyList } doStartLoading() // eslint-disable-next-line react-hooks/exhaustive-deps - }, [...deps, load]) + }, deps) return [loading, error, content] } From 61bcb9cb7597898b0f63108352b2d08018cd0545 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 14 Jun 2023 15:22:36 +0200 Subject: [PATCH 1825/2916] chore: Run npm run build --- pkg/webui/ui/build/index.html | 2 +- pkg/webui/ui/build/static/js/main.js | 382 ++++++++++++++------------- 2 files changed, 195 insertions(+), 189 deletions(-) diff --git a/pkg/webui/ui/build/index.html b/pkg/webui/ui/build/index.html index 5e73bd915..08726c8bf 100644 --- a/pkg/webui/ui/build/index.html +++ b/pkg/webui/ui/build/index.html @@ -1 +1 @@ -React App
    \ No newline at end of file +React App
    \ No newline at end of file diff --git a/pkg/webui/ui/build/static/js/main.js b/pkg/webui/ui/build/static/js/main.js index 844adb822..4a9d496ae 100644 --- a/pkg/webui/ui/build/static/js/main.js +++ b/pkg/webui/ui/build/static/js/main.js @@ -34880,7 +34880,7 @@ function useOutlet(context) { * @see https://reactrouter.com/hooks/use-params */ function useParams() { - var _React$useContext5 = React.useContext(RouteContext), + var _React$useContext5 = react.useContext(RouteContext), matches = _React$useContext5.matches; var routeMatch = matches[matches.length - 1]; return routeMatch ? routeMatch.params : {}; @@ -52032,11 +52032,196 @@ var loadedScripts=new Map();var loadScript=function loadScript(fileUrl){var asyn ;// CONCATENATED MODULE: ./src/utils/misc.ts function getLastPathElement(url){if(url===undefined){return undefined;}if(!url){return"";}var s=url.split("/");return s[s.length-1];}function sleep(ms){return new Promise(function(resolve){return setTimeout(resolve,ms);});} ;// CONCATENATED MODULE: ./src/api.tsx -var apiUrl="/api";var staticPath="./staticdata";var ObjectType=/*#__PURE__*/function(ObjectType){ObjectType["Rendered"]="rendered";ObjectType["Remote"]="remote";ObjectType["Applied"]="applied";return ObjectType;}({});var RealOrStaticApi=/*#__PURE__*/function(){function RealOrStaticApi(){classCallCheck_classCallCheck(this,RealOrStaticApi);this.api=void 0;this.api=this.buildApi();}createClass_createClass(RealOrStaticApi,[{key:"buildApi",value:function(){var _buildApi=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee(){var p;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee$(_context){while(1)switch(_context.prev=_context.next){case 0:p=loadScript(staticPath+"/summaries.js");_context.prev=1;_context.next=4;return p;case 4:return _context.abrupt("return",new StaticApi());case 7:_context.prev=7;_context.t0=_context["catch"](1);return _context.abrupt("return",new RealApi());case 10:case"end":return _context.stop();}},_callee,null,[[1,7]]);}));function buildApi(){return _buildApi.apply(this,arguments);}return buildApi;}()},{key:"deployNow",value:function(){var _deployNow=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee2(cluster,name,namespace){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee2$(_context2){while(1)switch(_context2.prev=_context2.next){case 0:_context2.next=2;return this.api;case 2:return _context2.abrupt("return",_context2.sent.deployNow(cluster,name,namespace));case 3:case"end":return _context2.stop();}},_callee2,this);}));function deployNow(_x,_x2,_x3){return _deployNow.apply(this,arguments);}return deployNow;}()},{key:"getResult",value:function(){var _getResult=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee3(resultId){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee3$(_context3){while(1)switch(_context3.prev=_context3.next){case 0:_context3.next=2;return this.api;case 2:return _context3.abrupt("return",_context3.sent.getResult(resultId));case 3:case"end":return _context3.stop();}},_callee3,this);}));function getResult(_x4){return _getResult.apply(this,arguments);}return getResult;}()},{key:"getResultObject",value:function(){var _getResultObject=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee4(resultId,ref,objectType){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee4$(_context4){while(1)switch(_context4.prev=_context4.next){case 0:_context4.next=2;return this.api;case 2:return _context4.abrupt("return",_context4.sent.getResultObject(resultId,ref,objectType));case 3:case"end":return _context4.stop();}},_callee4,this);}));function getResultObject(_x5,_x6,_x7){return _getResultObject.apply(this,arguments);}return getResultObject;}()},{key:"getResultSummary",value:function(){var _getResultSummary=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee5(resultId){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee5$(_context5){while(1)switch(_context5.prev=_context5.next){case 0:_context5.next=2;return this.api;case 2:return _context5.abrupt("return",_context5.sent.getResultSummary(resultId));case 3:case"end":return _context5.stop();}},_callee5,this);}));function getResultSummary(_x8){return _getResultSummary.apply(this,arguments);}return getResultSummary;}()},{key:"getShortNames",value:function(){var _getShortNames=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee6(){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee6$(_context6){while(1)switch(_context6.prev=_context6.next){case 0:_context6.next=2;return this.api;case 2:return _context6.abrupt("return",_context6.sent.getShortNames());case 3:case"end":return _context6.stop();}},_callee6,this);}));function getShortNames(){return _getShortNames.apply(this,arguments);}return getShortNames;}()},{key:"listenUpdates",value:function(){var _listenUpdates=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee7(filterProject,filterSubDir,handle){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee7$(_context7){while(1)switch(_context7.prev=_context7.next){case 0:_context7.next=2;return this.api;case 2:return _context7.abrupt("return",_context7.sent.listenUpdates(filterProject,filterSubDir,handle));case 3:case"end":return _context7.stop();}},_callee7,this);}));function listenUpdates(_x9,_x10,_x11){return _listenUpdates.apply(this,arguments);}return listenUpdates;}()},{key:"reconcileNow",value:function(){var _reconcileNow=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee8(cluster,name,namespace){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee8$(_context8){while(1)switch(_context8.prev=_context8.next){case 0:_context8.next=2;return this.api;case 2:return _context8.abrupt("return",_context8.sent.reconcileNow(cluster,name,namespace));case 3:case"end":return _context8.stop();}},_callee8,this);}));function reconcileNow(_x12,_x13,_x14){return _reconcileNow.apply(this,arguments);}return reconcileNow;}()},{key:"validateNow",value:function(){var _validateNow=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee9(project,target){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee9$(_context9){while(1)switch(_context9.prev=_context9.next){case 0:_context9.next=2;return this.api;case 2:return _context9.abrupt("return",_context9.sent.validateNow(project,target));case 3:case"end":return _context9.stop();}},_callee9,this);}));function validateNow(_x15,_x16){return _validateNow.apply(this,arguments);}return validateNow;}()}]);return RealOrStaticApi;}();var api=new RealOrStaticApi();var RealApi=/*#__PURE__*/function(){function RealApi(){classCallCheck_classCallCheck(this,RealApi);}createClass_createClass(RealApi,[{key:"getShortNames",value:function(){var _getShortNames2=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee10(){var url;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee10$(_context10){while(1)switch(_context10.prev=_context10.next){case 0:url="".concat(apiUrl,"/getShortNames");return _context10.abrupt("return",fetch(url).then(handleErrors).then(function(response){return response.json();}));case 2:case"end":return _context10.stop();}},_callee10);}));function getShortNames(){return _getShortNames2.apply(this,arguments);}return getShortNames;}()},{key:"listenUpdates",value:function(){var _listenUpdates2=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee11(filterProject,filterSubDir,handle){var host,url,params,ws,cancelled,connect;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee11$(_context11){while(1)switch(_context11.prev=_context11.next){case 0:host=window.location.host;if(false){}url="ws://".concat(host).concat(apiUrl,"/ws");params=new URLSearchParams();if(filterProject){params.set("filterProject",filterProject);}if(filterSubDir){params.set("filterSubDir",filterSubDir);}url+="?"+params.toString();cancelled=false;connect=function connect(){if(cancelled){return;}console.log("ws connect: "+url);ws=new WebSocket(url);ws.onopen=function(){console.log("ws connected");};ws.onclose=function(event){console.log("ws close");if(!cancelled){sleep(5000).then(connect);}};ws.onerror=function(event){console.log("ws error",event);};ws.onmessage=function(event){if(cancelled){return;}var msg=JSON.parse(event.data);handle(msg);};};connect();return _context11.abrupt("return",function(){console.log("ws cancel");cancelled=true;if(ws){ws.close();}});case 11:case"end":return _context11.stop();}},_callee11);}));function listenUpdates(_x17,_x18,_x19){return _listenUpdates2.apply(this,arguments);}return listenUpdates;}()},{key:"getResult",value:function(){var _getResult2=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee12(resultId){var url;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee12$(_context12){while(1)switch(_context12.prev=_context12.next){case 0:url="".concat(apiUrl,"/getResult?resultId=").concat(resultId);return _context12.abrupt("return",fetch(url).then(handleErrors).then(function(response){return response.text();}).then(function(json){return new CommandResult(json);}));case 2:case"end":return _context12.stop();}},_callee12);}));function getResult(_x20){return _getResult2.apply(this,arguments);}return getResult;}()},{key:"getResultSummary",value:function(){var _getResultSummary2=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee13(resultId){var url;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee13$(_context13){while(1)switch(_context13.prev=_context13.next){case 0:url="".concat(apiUrl,"/getResultSummary?resultId=").concat(resultId);return _context13.abrupt("return",fetch(url).then(handleErrors).then(function(response){return response.text();}).then(function(json){return new CommandResultSummary(json);}));case 2:case"end":return _context13.stop();}},_callee13);}));function getResultSummary(_x21){return _getResultSummary2.apply(this,arguments);}return getResultSummary;}()},{key:"getResultObject",value:function(){var _getResultObject2=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee14(resultId,ref,objectType){var url;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee14$(_context14){while(1)switch(_context14.prev=_context14.next){case 0:url="".concat(apiUrl,"/getResultObject?resultId=").concat(resultId,"&").concat(buildRefParams(ref),"&objectType=").concat(objectType);return _context14.abrupt("return",fetch(url).then(handleErrors).then(function(response){return response.json();}));case 2:case"end":return _context14.stop();}},_callee14);}));function getResultObject(_x22,_x23,_x24){return _getResultObject2.apply(this,arguments);}return getResultObject;}()},{key:"doPost",value:function(){var _doPost=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee15(f,body){var url;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee15$(_context15){while(1)switch(_context15.prev=_context15.next){case 0:url="".concat(apiUrl,"/").concat(f);return _context15.abrupt("return",fetch(url,{method:"POST",body:JSON.stringify(body),headers:{'Accept':'application/json','Content-Type':'application/json'}}).then(handleErrors));case 2:case"end":return _context15.stop();}},_callee15);}));function doPost(_x25,_x26){return _doPost.apply(this,arguments);}return doPost;}()},{key:"validateNow",value:function(){var _validateNow2=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee16(project,target){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee16$(_context16){while(1)switch(_context16.prev=_context16.next){case 0:return _context16.abrupt("return",this.doPost("validateNow",{"project":project,"target":target}));case 1:case"end":return _context16.stop();}},_callee16,this);}));function validateNow(_x27,_x28){return _validateNow2.apply(this,arguments);}return validateNow;}()},{key:"deployNow",value:function(){var _deployNow2=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee17(cluster,name,namespace){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee17$(_context17){while(1)switch(_context17.prev=_context17.next){case 0:return _context17.abrupt("return",this.doPost("deployNow",{"cluster":cluster,"name":name,"namespace":namespace}));case 1:case"end":return _context17.stop();}},_callee17,this);}));function deployNow(_x29,_x30,_x31){return _deployNow2.apply(this,arguments);}return deployNow;}()},{key:"reconcileNow",value:function(){var _reconcileNow2=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee18(cluster,name,namespace){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee18$(_context18){while(1)switch(_context18.prev=_context18.next){case 0:return _context18.abrupt("return",this.doPost("reconcileNow",{"cluster":cluster,"name":name,"namespace":namespace}));case 1:case"end":return _context18.stop();}},_callee18,this);}));function reconcileNow(_x32,_x33,_x34){return _reconcileNow2.apply(this,arguments);}return reconcileNow;}()}]);return RealApi;}();var StaticApi=/*#__PURE__*/function(){function StaticApi(){classCallCheck_classCallCheck(this,StaticApi);}createClass_createClass(StaticApi,[{key:"getShortNames",value:function(){var _getShortNames3=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee19(){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee19$(_context19){while(1)switch(_context19.prev=_context19.next){case 0:_context19.next=2;return loadScript(staticPath+"/shortnames.js");case 2:return _context19.abrupt("return",staticShortNames);case 3:case"end":return _context19.stop();}},_callee19);}));function getShortNames(){return _getShortNames3.apply(this,arguments);}return getShortNames;}()},{key:"listenUpdates",value:function(){var _listenUpdates3=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee20(filterProject,filterSubDir,handle){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee20$(_context20){while(1)switch(_context20.prev=_context20.next){case 0:_context20.next=2;return loadScript(staticPath+"/summaries.js");case 2:staticSummaries.forEach(function(rs){if(filterProject&&filterProject!==rs.project.normalizedGitUrl){return;}if(filterSubDir&&filterSubDir!==rs.project.subDir){return;}handle({"type":"update_summary","summary":rs});});return _context20.abrupt("return",function(){});case 4:case"end":return _context20.stop();}},_callee20);}));function listenUpdates(_x35,_x36,_x37){return _listenUpdates3.apply(this,arguments);}return listenUpdates;}()},{key:"getResult",value:function(){var _getResult3=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee21(resultId){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee21$(_context21){while(1)switch(_context21.prev=_context21.next){case 0:_context21.next=2;return loadScript(staticPath+"/result-".concat(resultId,".js"));case 2:return _context21.abrupt("return",staticResults.get(resultId));case 3:case"end":return _context21.stop();}},_callee21);}));function getResult(_x38){return _getResult3.apply(this,arguments);}return getResult;}()},{key:"getResultSummary",value:function(){var _getResultSummary3=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee22(resultId){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee22$(_context22){while(1)switch(_context22.prev=_context22.next){case 0:_context22.next=2;return loadScript(staticPath+"/summaries.js");case 2:return _context22.abrupt("return",staticSummaries.filter(function(s){return s.id===resultId;}).at(0));case 3:case"end":return _context22.stop();}},_callee22);}));function getResultSummary(_x39){return _getResultSummary3.apply(this,arguments);}return getResultSummary;}()},{key:"getResultObject",value:function(){var _getResultObject3=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee23(resultId,ref,objectType){var _result$objects;var result,object;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee23$(_context23){while(1)switch(_context23.prev=_context23.next){case 0:_context23.next=2;return this.getResult(resultId);case 2:result=_context23.sent;object=(_result$objects=result.objects)===null||_result$objects===void 0?void 0:_result$objects.find(function(x){return lodash_default().isEqual(x.ref,ref);});if(object){_context23.next=6;break;}throw new Error("object not found");case 6:_context23.t0=objectType;_context23.next=_context23.t0===ObjectType.Rendered?9:_context23.t0===ObjectType.Remote?10:_context23.t0===ObjectType.Applied?11:12;break;case 9:return _context23.abrupt("return",object.rendered);case 10:return _context23.abrupt("return",object.remote);case 11:return _context23.abrupt("return",object.applied);case 12:throw new Error("unknown object type "+objectType);case 13:case"end":return _context23.stop();}},_callee23,this);}));function getResultObject(_x40,_x41,_x42){return _getResultObject3.apply(this,arguments);}return getResultObject;}()},{key:"validateNow",value:function validateNow(project,target){throw new Error("not implemented");}},{key:"reconcileNow",value:function reconcileNow(cluster,name,namespace){throw new Error("not implemented");}},{key:"deployNow",value:function deployNow(cluster,name,namespace){throw new Error("not implemented");}}]);return StaticApi;}();function handleErrors(response){if(!response.ok){throw Error(response.statusText);}return response;}function buildRefParams(ref){var params=new URLSearchParams();params.set("kind",ref.kind);params.set("name",ref.name);if(ref.group){params.set("group",ref.group);}if(ref.version){params.set("version",ref.version);}if(ref.namespace){params.set("namespace",ref.namespace);}return params.toString();}function buildRefString(ref){if(ref.namespace){return"".concat(ref.namespace,"/").concat(ref.kind,"/").concat(ref.name);}else{if(ref.name){return"".concat(ref.kind,"/").concat(ref.name);}else{return ref.kind;}}}function buildRefKindElement(ref,element){var tooltip=/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{zIndex:1000,children:[/*#__PURE__*/(0,jsx_runtime.jsxs)(Typography_Typography,{children:["ApiVersion: ",[ref.group,ref.version].filter(function(x){return x;}).join("/")]}),/*#__PURE__*/(0,jsx_runtime.jsx)("br",{}),/*#__PURE__*/(0,jsx_runtime.jsxs)(Typography_Typography,{children:["Kind: ",ref.kind]})]});return/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:tooltip,children:element?element:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{children:ref.kind})});}function buildObjectRefFromObject(obj){var apiVersion=obj.apiVersion;var s=apiVersion.split("/",2);var ref=new ObjectRef();if(s.length===1){ref.version=s[0];}else{ref.group=s[0];ref.version=s[1];}ref.kind=obj.kind;ref.namespace=obj.metadata.namespace;ref.name=obj.metadata.name;return ref;}function findObjectByRef(l,ref,filter){return l===null||l===void 0?void 0:l.find(function(x){if(filter&&!filter(x)){return false;}return lodash_default().isEqual(x.ref,ref);});}function usePromise(p){if(p===undefined){throw new Promise(function(){return undefined;});}var promise=p;if(promise.status==='fulfilled'){return promise.value;}else if(promise.status==='rejected'){throw promise.reason;}else if(promise.status==='pending'){throw promise;}else{promise.status='pending';p.then(function(result){promise.status='fulfilled';promise.value=result;},function(reason){promise.status='rejected';promise.reason=reason;});throw promise;}} +var staticPath="./staticdata";var ObjectType=/*#__PURE__*/function(ObjectType){ObjectType["Rendered"]="rendered";ObjectType["Remote"]="remote";ObjectType["Applied"]="applied";return ObjectType;}({});function checkStaticBuild(){return _checkStaticBuild.apply(this,arguments);}function _checkStaticBuild(){_checkStaticBuild=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee19(){var p;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee19$(_context19){while(1)switch(_context19.prev=_context19.next){case 0:p=loadScript(staticPath+"/summaries.js");_context19.prev=1;_context19.next=4;return p;case 4:return _context19.abrupt("return",true);case 7:_context19.prev=7;_context19.t0=_context19["catch"](1);return _context19.abrupt("return",false);case 10:case"end":return _context19.stop();}},_callee19,null,[[1,7]]);}));return _checkStaticBuild.apply(this,arguments);}var RealApi=/*#__PURE__*/function(){function RealApi(getToken,onUnauthorized,onTokenRefresh){classCallCheck_classCallCheck(this,RealApi);this.getToken=void 0;this.onUnauthorized=void 0;this.onTokenRefresh=void 0;this.getToken=getToken;this.onUnauthorized=onUnauthorized;this.onTokenRefresh=onTokenRefresh;}createClass_createClass(RealApi,[{key:"setAuthorizationHeader",value:function setAuthorizationHeader(headers){if(!this.getToken){return;}headers['Authorization']="Bearer "+this.getToken();}},{key:"refreshToken",value:function(){var _refreshToken=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee(){var headers,resp,j;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee$(_context){while(1)switch(_context.prev=_context.next){case 0:headers={'Accept':'application/json'};this.setAuthorizationHeader(headers);_context.next=4;return fetch("/auth/refresh",{method:"POST",headers:headers});case 4:resp=_context.sent;if(!(resp.status===401)){_context.next=8;break;}if(this.onUnauthorized){this.onUnauthorized();}throw Error(resp.statusText);case 8:_context.next=10;return resp.json();case 10:j=_context.sent;if(this.onTokenRefresh){this.onTokenRefresh(j.token);}case 12:case"end":return _context.stop();}},_callee,this);}));function refreshToken(){return _refreshToken.apply(this,arguments);}return refreshToken;}()},{key:"handleErrors",value:function(){var _handleErrors=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee2(response,retry){var newResp;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee2$(_context2){while(1)switch(_context2.prev=_context2.next){case 0:if(response.ok){_context2.next=16;break;}if(!(response.status===401)){_context2.next=15;break;}if(!(this.getToken&&retry)){_context2.next=14;break;}console.log("retrying with token refresh");_context2.next=6;return this.refreshToken();case 6:_context2.next=8;return retry();case 8:newResp=_context2.sent;_context2.next=11;return this.handleErrors(newResp);case 11:return _context2.abrupt("return",_context2.sent);case 14:if(this.onUnauthorized){this.onUnauthorized();}case 15:throw Error(response.statusText);case 16:return _context2.abrupt("return",response);case 17:case"end":return _context2.stop();}},_callee2,this);}));function handleErrors(_x,_x2){return _handleErrors.apply(this,arguments);}return handleErrors;}()},{key:"doGet",value:function(){var _doGet=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee3(path,params){var _this=this;var url,doFetch,resp;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee3$(_context3){while(1)switch(_context3.prev=_context3.next){case 0:url=path;if(params){url+="?"+params.toString();}doFetch=function doFetch(){var headers={'Accept':'application/json'};_this.setAuthorizationHeader(headers);return fetch(url,{method:"GET",headers:headers});};_context3.next=5;return doFetch();case 5:resp=_context3.sent;_context3.next=8;return this.handleErrors(resp,doFetch);case 8:resp=_context3.sent;return _context3.abrupt("return",resp.json());case 10:case"end":return _context3.stop();}},_callee3,this);}));function doGet(_x3,_x4){return _doGet.apply(this,arguments);}return doGet;}()},{key:"doPost",value:function(){var _doPost=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee4(path,body){var _this2=this;var url,doFetch,resp;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee4$(_context4){while(1)switch(_context4.prev=_context4.next){case 0:url=path;doFetch=function doFetch(){var headers={'Accept':'application/json','Content-Type':'application/json'};_this2.setAuthorizationHeader(headers);return fetch(url,{method:"POST",body:JSON.stringify(body),headers:headers});};_context4.next=4;return doFetch();case 4:resp=_context4.sent;_context4.next=7;return this.handleErrors(resp,doFetch);case 7:resp=_context4.sent;return _context4.abrupt("return",resp);case 9:case"end":return _context4.stop();}},_callee4,this);}));function doPost(_x5,_x6){return _doPost.apply(this,arguments);}return doPost;}()},{key:"getShortNames",value:function(){var _getShortNames=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee5(){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee5$(_context5){while(1)switch(_context5.prev=_context5.next){case 0:return _context5.abrupt("return",this.doGet("/api/getShortNames"));case 1:case"end":return _context5.stop();}},_callee5,this);}));function getShortNames(){return _getShortNames.apply(this,arguments);}return getShortNames;}()},{key:"listenUpdates",value:function(){var _listenUpdates=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee7(filterProject,filterSubDir,handle){var host,url,params,getToken,ws,cancelled,connect;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee7$(_context7){while(1)switch(_context7.prev=_context7.next){case 0:host=window.location.host;if(false){}url="ws://".concat(host,"/api/ws");params=new URLSearchParams();if(filterProject){params.set("filterProject",filterProject);}if(filterSubDir){params.set("filterSubDir",filterSubDir);}url+="?"+params.toString();getToken=this.getToken;cancelled=false;connect=/*#__PURE__*/function(){var _ref=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee6(){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee6$(_context6){while(1)switch(_context6.prev=_context6.next){case 0:if(!cancelled){_context6.next=2;break;}return _context6.abrupt("return");case 2:console.log("ws connect: "+url);ws=new WebSocket(url);ws.onopen=function(){console.log("ws connected");if(getToken){ws.send(JSON.stringify({"type":"auth","token":getToken()}));}};ws.onclose=function(event){console.log("ws close");if(!cancelled){sleep(5000).then(connect);}};ws.onerror=function(event){console.log("ws error",event);};ws.onmessage=function(event){if(cancelled){return;}var msg=JSON.parse(event.data);handle(msg);};case 8:case"end":return _context6.stop();}},_callee6);}));return function connect(){return _ref.apply(this,arguments);};}();_context7.next=12;return connect();case 12:return _context7.abrupt("return",function(){console.log("ws cancel");cancelled=true;if(ws){ws.close();}});case 13:case"end":return _context7.stop();}},_callee7,this);}));function listenUpdates(_x7,_x8,_x9){return _listenUpdates.apply(this,arguments);}return listenUpdates;}()},{key:"getResult",value:function(){var _getResult=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee8(resultId){var params,json;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee8$(_context8){while(1)switch(_context8.prev=_context8.next){case 0:params=new URLSearchParams();params.set("resultId",resultId);_context8.next=4;return this.doGet("/api/getResult",params);case 4:json=_context8.sent;return _context8.abrupt("return",new CommandResult(json));case 6:case"end":return _context8.stop();}},_callee8,this);}));function getResult(_x10){return _getResult.apply(this,arguments);}return getResult;}()},{key:"getResultSummary",value:function(){var _getResultSummary=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee9(resultId){var params,json;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee9$(_context9){while(1)switch(_context9.prev=_context9.next){case 0:params=new URLSearchParams();params.set("resultId",resultId);_context9.next=4;return this.doGet("/api/getResultSummary",params);case 4:json=_context9.sent;return _context9.abrupt("return",new CommandResultSummary(json));case 6:case"end":return _context9.stop();}},_callee9,this);}));function getResultSummary(_x11){return _getResultSummary.apply(this,arguments);}return getResultSummary;}()},{key:"getResultObject",value:function(){var _getResultObject=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee10(resultId,ref,objectType){var params;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee10$(_context10){while(1)switch(_context10.prev=_context10.next){case 0:params=new URLSearchParams();params.set("resultId",resultId);params.set("objectType",objectType);buildRefParams(ref,params);_context10.next=6;return this.doGet("/api/getResultObject",params);case 6:return _context10.abrupt("return",_context10.sent);case 7:case"end":return _context10.stop();}},_callee10,this);}));function getResultObject(_x12,_x13,_x14){return _getResultObject.apply(this,arguments);}return getResultObject;}()},{key:"validateNow",value:function(){var _validateNow=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee11(project,target){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee11$(_context11){while(1)switch(_context11.prev=_context11.next){case 0:return _context11.abrupt("return",this.doPost("/api/validateNow",{"project":project,"target":target}));case 1:case"end":return _context11.stop();}},_callee11,this);}));function validateNow(_x15,_x16){return _validateNow.apply(this,arguments);}return validateNow;}()},{key:"deployNow",value:function(){var _deployNow=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee12(cluster,name,namespace){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee12$(_context12){while(1)switch(_context12.prev=_context12.next){case 0:return _context12.abrupt("return",this.doPost("/api/deployNow",{"cluster":cluster,"name":name,"namespace":namespace}));case 1:case"end":return _context12.stop();}},_callee12,this);}));function deployNow(_x17,_x18,_x19){return _deployNow.apply(this,arguments);}return deployNow;}()},{key:"reconcileNow",value:function(){var _reconcileNow=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee13(cluster,name,namespace){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee13$(_context13){while(1)switch(_context13.prev=_context13.next){case 0:return _context13.abrupt("return",this.doPost("/api/reconcileNow",{"cluster":cluster,"name":name,"namespace":namespace}));case 1:case"end":return _context13.stop();}},_callee13,this);}));function reconcileNow(_x20,_x21,_x22){return _reconcileNow.apply(this,arguments);}return reconcileNow;}()}]);return RealApi;}();var StaticApi=/*#__PURE__*/function(){function StaticApi(){classCallCheck_classCallCheck(this,StaticApi);}createClass_createClass(StaticApi,[{key:"getShortNames",value:function(){var _getShortNames2=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee14(){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee14$(_context14){while(1)switch(_context14.prev=_context14.next){case 0:_context14.next=2;return loadScript(staticPath+"/shortnames.js");case 2:return _context14.abrupt("return",staticShortNames);case 3:case"end":return _context14.stop();}},_callee14);}));function getShortNames(){return _getShortNames2.apply(this,arguments);}return getShortNames;}()},{key:"listenUpdates",value:function(){var _listenUpdates2=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee15(filterProject,filterSubDir,handle){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee15$(_context15){while(1)switch(_context15.prev=_context15.next){case 0:_context15.next=2;return loadScript(staticPath+"/summaries.js");case 2:staticSummaries.forEach(function(rs){if(filterProject&&filterProject!==rs.project.normalizedGitUrl){return;}if(filterSubDir&&filterSubDir!==rs.project.subDir){return;}handle({"type":"update_summary","summary":rs});});return _context15.abrupt("return",function(){});case 4:case"end":return _context15.stop();}},_callee15);}));function listenUpdates(_x23,_x24,_x25){return _listenUpdates2.apply(this,arguments);}return listenUpdates;}()},{key:"getResult",value:function(){var _getResult2=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee16(resultId){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee16$(_context16){while(1)switch(_context16.prev=_context16.next){case 0:_context16.next=2;return loadScript(staticPath+"/result-".concat(resultId,".js"));case 2:return _context16.abrupt("return",staticResults.get(resultId));case 3:case"end":return _context16.stop();}},_callee16);}));function getResult(_x26){return _getResult2.apply(this,arguments);}return getResult;}()},{key:"getResultSummary",value:function(){var _getResultSummary2=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee17(resultId){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee17$(_context17){while(1)switch(_context17.prev=_context17.next){case 0:_context17.next=2;return loadScript(staticPath+"/summaries.js");case 2:return _context17.abrupt("return",staticSummaries.filter(function(s){return s.id===resultId;}).at(0));case 3:case"end":return _context17.stop();}},_callee17);}));function getResultSummary(_x27){return _getResultSummary2.apply(this,arguments);}return getResultSummary;}()},{key:"getResultObject",value:function(){var _getResultObject2=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee18(resultId,ref,objectType){var _result$objects;var result,object;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee18$(_context18){while(1)switch(_context18.prev=_context18.next){case 0:_context18.next=2;return this.getResult(resultId);case 2:result=_context18.sent;object=(_result$objects=result.objects)===null||_result$objects===void 0?void 0:_result$objects.find(function(x){return lodash_default().isEqual(x.ref,ref);});if(object){_context18.next=6;break;}throw new Error("object not found");case 6:_context18.t0=objectType;_context18.next=_context18.t0===ObjectType.Rendered?9:_context18.t0===ObjectType.Remote?10:_context18.t0===ObjectType.Applied?11:12;break;case 9:return _context18.abrupt("return",object.rendered);case 10:return _context18.abrupt("return",object.remote);case 11:return _context18.abrupt("return",object.applied);case 12:throw new Error("unknown object type "+objectType);case 13:case"end":return _context18.stop();}},_callee18,this);}));function getResultObject(_x28,_x29,_x30){return _getResultObject2.apply(this,arguments);}return getResultObject;}()},{key:"validateNow",value:function validateNow(project,target){throw new Error("not implemented");}},{key:"reconcileNow",value:function reconcileNow(cluster,name,namespace){throw new Error("not implemented");}},{key:"deployNow",value:function deployNow(cluster,name,namespace){throw new Error("not implemented");}}]);return StaticApi;}();function buildRefParams(ref,params){params.set("kind",ref.kind);params.set("name",ref.name);if(ref.group){params.set("group",ref.group);}if(ref.version){params.set("version",ref.version);}if(ref.namespace){params.set("namespace",ref.namespace);}}function buildRefString(ref){if(ref.namespace){return"".concat(ref.namespace,"/").concat(ref.kind,"/").concat(ref.name);}else{if(ref.name){return"".concat(ref.kind,"/").concat(ref.name);}else{return ref.kind;}}}function buildRefKindElement(ref,element){var tooltip=/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{zIndex:1000,children:[/*#__PURE__*/(0,jsx_runtime.jsxs)(Typography_Typography,{children:["ApiVersion: ",[ref.group,ref.version].filter(function(x){return x;}).join("/")]}),/*#__PURE__*/(0,jsx_runtime.jsx)("br",{}),/*#__PURE__*/(0,jsx_runtime.jsxs)(Typography_Typography,{children:["Kind: ",ref.kind]})]});return/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:tooltip,children:element?element:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{children:ref.kind})});}function buildObjectRefFromObject(obj){var apiVersion=obj.apiVersion;var s=apiVersion.split("/",2);var ref=new ObjectRef();if(s.length===1){ref.version=s[0];}else{ref.group=s[0];ref.version=s[1];}ref.kind=obj.kind;ref.namespace=obj.metadata.namespace;ref.name=obj.metadata.name;return ref;}function findObjectByRef(l,ref,filter){return l===null||l===void 0?void 0:l.find(function(x){if(filter&&!filter(x)){return false;}return lodash_default().isEqual(x.ref,ref);});} ;// CONCATENATED MODULE: ./src/project-summaries.ts function compareSummaries(a,b){return b.commandInfo.startTime.localeCompare(a.commandInfo.startTime)||b.commandInfo.endTime.localeCompare(b.commandInfo.endTime)||(b.commandInfo.command||"").localeCompare(a.commandInfo.command||"");}function buildProjectSummaries(summaries,validateResults){var sorted=Array.from(summaries.values());sorted.sort(compareSummaries);var m=new Map();sorted.forEach(function(rs){var projectKey=JSON.stringify(rs.projectKey);var p=m.get(projectKey);if(!p){p={project:rs.projectKey,targets:[]};m.set(projectKey,p);}var ptKey=new ProjectTargetKey();ptKey.project=rs.projectKey;ptKey.target=rs.targetKey;var vr=validateResults.get(JSON.stringify(ptKey));var target=p.targets.find(function(t){return lodash_default().isEqual(t.target,rs.targetKey);});if(!target){target={target:rs.targetKey,lastValidateResult:vr,commandResults:[]};p.targets.push(target);}target.commandResults.push(rs);});var ret=[];m.forEach(function(p){p.targets.sort(function(a,b){var _ref;return(a.target.targetName||"").localeCompare(b.target.targetName||"")||a.target.clusterId.localeCompare(b.target.clusterId)||((_ref=a.target.discriminator||"")===null||_ref===void 0?void 0:_ref.localeCompare(b.target.discriminator||""));});ret.push(p);});ret.sort(function(a,b){return(a.project.gitRepoKey||"").localeCompare(b.project.gitRepoKey||"")||(a.project.subDir||"").localeCompare(b.project.subDir||"");});return ret;} +;// CONCATENATED MODULE: ./src/components/Login.tsx +//import './Login.css'; +function loginUser(_x){return _loginUser.apply(this,arguments);}function _loginUser(){_loginUser=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee2(creds){var url;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee2$(_context2){while(1)switch(_context2.prev=_context2.next){case 0:url="/auth/login";return _context2.abrupt("return",fetch(url,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(creds)}).then(function(data){return data.json();}).then(function(token){return token.token;}));case 2:case"end":return _context2.stop();}},_callee2);}));return _loginUser.apply(this,arguments);}function Login(props){var _useState=(0,react.useState)(),_useState2=slicedToArray_slicedToArray(_useState,2),username=_useState2[0],setUserName=_useState2[1];var _useState3=(0,react.useState)(),_useState4=slicedToArray_slicedToArray(_useState3,2),password=_useState4[0],setPassword=_useState4[1];var handleSubmit=/*#__PURE__*/function(){var _ref=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee(e){var token;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee$(_context){while(1)switch(_context.prev=_context.next){case 0:e.preventDefault();if(!(!username||!password)){_context.next=3;break;}return _context.abrupt("return");case 3:_context.next=5;return loginUser({username:username,password:password});case 5:token=_context.sent;props.setToken(token);case 7:case"end":return _context.stop();}},_callee);}));return function handleSubmit(_x2){return _ref.apply(this,arguments);};}();return/*#__PURE__*/(0,jsx_runtime.jsxs)("div",{className:"login-wrapper",children:[/*#__PURE__*/(0,jsx_runtime.jsx)("h1",{children:"Please Log In"}),/*#__PURE__*/(0,jsx_runtime.jsxs)("form",{onSubmit:handleSubmit,children:[/*#__PURE__*/(0,jsx_runtime.jsxs)("label",{children:[/*#__PURE__*/(0,jsx_runtime.jsx)("p",{children:"Username"}),/*#__PURE__*/(0,jsx_runtime.jsx)("input",{type:"text",onChange:function onChange(e){return setUserName(e.target.value);}})]}),/*#__PURE__*/(0,jsx_runtime.jsxs)("label",{children:[/*#__PURE__*/(0,jsx_runtime.jsx)("p",{children:"Password"}),/*#__PURE__*/(0,jsx_runtime.jsx)("input",{type:"password",onChange:function onChange(e){return setPassword(e.target.value);}})]}),/*#__PURE__*/(0,jsx_runtime.jsx)("div",{children:/*#__PURE__*/(0,jsx_runtime.jsx)("button",{type:"submit",children:"Submit"})})]})]});} +;// CONCATENATED MODULE: ./node_modules/@mui/material/CircularProgress/circularProgressClasses.js + + +function getCircularProgressUtilityClass(slot) { + return generateUtilityClass_generateUtilityClass('MuiCircularProgress', slot); +} +var circularProgressClasses = generateUtilityClasses('MuiCircularProgress', ['root', 'determinate', 'indeterminate', 'colorPrimary', 'colorSecondary', 'svg', 'circle', 'circleDeterminate', 'circleIndeterminate', 'circleDisableShrink']); +/* harmony default export */ var CircularProgress_circularProgressClasses = ((/* unused pure expression or super */ null && (circularProgressClasses))); +;// CONCATENATED MODULE: ./node_modules/@mui/material/CircularProgress/CircularProgress.js + +var CircularProgress_templateObject, CircularProgress_templateObject2, CircularProgress_templateObject3, CircularProgress_templateObject4; + + +var CircularProgress_excluded = ["className", "color", "disableShrink", "size", "style", "thickness", "value", "variant"]; +var CircularProgress_ = function _(t) { + return t; + }, + CircularProgress_t, + CircularProgress_t2, + CircularProgress_t3, + CircularProgress_t4; + + + + + + + + + + + +var SIZE = 44; +var circularRotateKeyframe = keyframes(CircularProgress_t || (CircularProgress_t = CircularProgress_(CircularProgress_templateObject || (CircularProgress_templateObject = _taggedTemplateLiteral(["\n 0% {\n transform: rotate(0deg);\n }\n\n 100% {\n transform: rotate(360deg);\n }\n"]))))); +var circularDashKeyframe = keyframes(CircularProgress_t2 || (CircularProgress_t2 = CircularProgress_(CircularProgress_templateObject2 || (CircularProgress_templateObject2 = _taggedTemplateLiteral(["\n 0% {\n stroke-dasharray: 1px, 200px;\n stroke-dashoffset: 0;\n }\n\n 50% {\n stroke-dasharray: 100px, 200px;\n stroke-dashoffset: -15px;\n }\n\n 100% {\n stroke-dasharray: 100px, 200px;\n stroke-dashoffset: -125px;\n }\n"]))))); +var CircularProgress_useUtilityClasses = function useUtilityClasses(ownerState) { + var classes = ownerState.classes, + variant = ownerState.variant, + color = ownerState.color, + disableShrink = ownerState.disableShrink; + var slots = { + root: ['root', variant, "color".concat(utils_capitalize(color))], + svg: ['svg'], + circle: ['circle', "circle".concat(utils_capitalize(variant)), disableShrink && 'circleDisableShrink'] + }; + return composeClasses(slots, getCircularProgressUtilityClass, classes); +}; +var CircularProgressRoot = styles_styled('span', { + name: 'MuiCircularProgress', + slot: 'Root', + overridesResolver: function overridesResolver(props, styles) { + var ownerState = props.ownerState; + return [styles.root, styles[ownerState.variant], styles["color".concat(utils_capitalize(ownerState.color))]]; + } +})(function (_ref) { + var ownerState = _ref.ownerState, + theme = _ref.theme; + return extends_extends({ + display: 'inline-block' + }, ownerState.variant === 'determinate' && { + transition: theme.transitions.create('transform') + }, ownerState.color !== 'inherit' && { + color: (theme.vars || theme).palette[ownerState.color].main + }); +}, function (_ref2) { + var ownerState = _ref2.ownerState; + return ownerState.variant === 'indeterminate' && css(CircularProgress_t3 || (CircularProgress_t3 = CircularProgress_(CircularProgress_templateObject3 || (CircularProgress_templateObject3 = _taggedTemplateLiteral(["\n animation: ", " 1.4s linear infinite;\n "])), 0)), circularRotateKeyframe); +}); +var CircularProgressSVG = styles_styled('svg', { + name: 'MuiCircularProgress', + slot: 'Svg', + overridesResolver: function overridesResolver(props, styles) { + return styles.svg; + } +})({ + display: 'block' // Keeps the progress centered +}); + +var CircularProgressCircle = styles_styled('circle', { + name: 'MuiCircularProgress', + slot: 'Circle', + overridesResolver: function overridesResolver(props, styles) { + var ownerState = props.ownerState; + return [styles.circle, styles["circle".concat(utils_capitalize(ownerState.variant))], ownerState.disableShrink && styles.circleDisableShrink]; + } +})(function (_ref3) { + var ownerState = _ref3.ownerState, + theme = _ref3.theme; + return extends_extends({ + stroke: 'currentColor' + }, ownerState.variant === 'determinate' && { + transition: theme.transitions.create('stroke-dashoffset') + }, ownerState.variant === 'indeterminate' && { + // Some default value that looks fine waiting for the animation to kicks in. + strokeDasharray: '80px, 200px', + strokeDashoffset: 0 // Add the unit to fix a Edge 16 and below bug. + }); +}, function (_ref4) { + var ownerState = _ref4.ownerState; + return ownerState.variant === 'indeterminate' && !ownerState.disableShrink && css(CircularProgress_t4 || (CircularProgress_t4 = CircularProgress_(CircularProgress_templateObject4 || (CircularProgress_templateObject4 = _taggedTemplateLiteral(["\n animation: ", " 1.4s ease-in-out infinite;\n "])), 0)), circularDashKeyframe); +}); + +/** + * ## ARIA + * + * If the progress bar is describing the loading progress of a particular region of a page, + * you should use `aria-describedby` to point to the progress bar, and set the `aria-busy` + * attribute to `true` on that region until it has finished loading. + */ +var CircularProgress = /*#__PURE__*/react.forwardRef(function CircularProgress(inProps, ref) { + var props = useThemeProps_useThemeProps({ + props: inProps, + name: 'MuiCircularProgress' + }); + var className = props.className, + _props$color = props.color, + color = _props$color === void 0 ? 'primary' : _props$color, + _props$disableShrink = props.disableShrink, + disableShrink = _props$disableShrink === void 0 ? false : _props$disableShrink, + _props$size = props.size, + size = _props$size === void 0 ? 40 : _props$size, + style = props.style, + _props$thickness = props.thickness, + thickness = _props$thickness === void 0 ? 3.6 : _props$thickness, + _props$value = props.value, + value = _props$value === void 0 ? 0 : _props$value, + _props$variant = props.variant, + variant = _props$variant === void 0 ? 'indeterminate' : _props$variant, + other = objectWithoutPropertiesLoose_objectWithoutPropertiesLoose(props, CircularProgress_excluded); + var ownerState = extends_extends({}, props, { + color: color, + disableShrink: disableShrink, + size: size, + thickness: thickness, + value: value, + variant: variant + }); + var classes = CircularProgress_useUtilityClasses(ownerState); + var circleStyle = {}; + var rootStyle = {}; + var rootProps = {}; + if (variant === 'determinate') { + var circumference = 2 * Math.PI * ((SIZE - thickness) / 2); + circleStyle.strokeDasharray = circumference.toFixed(3); + rootProps['aria-valuenow'] = Math.round(value); + circleStyle.strokeDashoffset = "".concat(((100 - value) / 100 * circumference).toFixed(3), "px"); + rootStyle.transform = 'rotate(-90deg)'; + } + return /*#__PURE__*/(0,jsx_runtime.jsx)(CircularProgressRoot, extends_extends({ + className: clsx_m(classes.root, className), + style: extends_extends({ + width: size, + height: size + }, rootStyle, style), + ownerState: ownerState, + ref: ref, + role: "progressbar" + }, rootProps, other, { + children: /*#__PURE__*/(0,jsx_runtime.jsx)(CircularProgressSVG, { + className: classes.svg, + ownerState: ownerState, + viewBox: "".concat(SIZE / 2, " ").concat(SIZE / 2, " ").concat(SIZE, " ").concat(SIZE), + children: /*#__PURE__*/(0,jsx_runtime.jsx)(CircularProgressCircle, { + className: classes.circle, + style: circleStyle, + ownerState: ownerState, + cx: SIZE, + cy: SIZE, + r: (SIZE - thickness) / 2, + fill: "none", + strokeWidth: thickness + }) + }) + })); +}); + false ? 0 : void 0; +/* harmony default export */ var CircularProgress_CircularProgress = (CircularProgress); +;// CONCATENATED MODULE: ./src/components/Loading.tsx +var Loading=function Loading(){return/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",height:"100%",children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",width:"100%",height:"100%",alignItems:"center",justifyContent:"center",children:/*#__PURE__*/(0,jsx_runtime.jsx)(CircularProgress_CircularProgress,{})})});};function useLoadingHelper(load,deps){var _useState=(0,react.useState)(true),_useState2=slicedToArray_slicedToArray(_useState,2),loading=_useState2[0],setLoading=_useState2[1];var _useState3=(0,react.useState)(),_useState4=slicedToArray_slicedToArray(_useState3,2),error=_useState4[0],setError=_useState4[1];var _useState5=(0,react.useState)(),_useState6=slicedToArray_slicedToArray(_useState5,2),content=_useState6[0],setContent=_useState6[1];(0,react.useEffect)(function(){var doStartLoading=/*#__PURE__*/function(){var _ref=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee(){var c;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee$(_context){while(1)switch(_context.prev=_context.next){case 0:_context.prev=0;_context.next=3;return load();case 3:c=_context.sent;setContent(c);setLoading(false);_context.next=12;break;case 8:_context.prev=8;_context.t0=_context["catch"](0);setError(_context.t0);setLoading(false);case 12:case"end":return _context.stop();}},_callee,null,[[0,8]]);}));return function doStartLoading(){return _ref.apply(this,arguments);};}();doStartLoading();// eslint-disable-next-line react-hooks/exhaustive-deps +},deps);return[loading,error,content];} ;// CONCATENATED MODULE: ./src/components/App.tsx -function useAppOutletContext(){return useOutletContext();}var AppContext=/*#__PURE__*/(0,react.createContext)({summaries:new Map(),projects:[],validateResults:new Map()});var App=function App(){var _useState=(0,react.useState)(),_useState2=slicedToArray_slicedToArray(_useState,2),filters=_useState2[0],setFilters=_useState2[1];var summariesRef=(0,react.useRef)(new Map());var validateResultsRef=(0,react.useRef)(new Map());var _useState3=(0,react.useState)(summariesRef.current),_useState4=slicedToArray_slicedToArray(_useState3,2),summaries=_useState4[0],setSummaries=_useState4[1];var _useState5=(0,react.useState)(validateResultsRef.current),_useState6=slicedToArray_slicedToArray(_useState5,2),validateResults=_useState6[0],setValidateResults=_useState6[1];(0,react.useEffect)(function(){var updateSummary=function updateSummary(rs){console.log("update_summary",rs.id,rs.commandInfo.startTime);summariesRef.current.set(rs.id,rs);setSummaries(new Map(summariesRef.current));};var deleteSummary=function deleteSummary(id){console.log("delete_summary",id);summariesRef.current.delete(id);setSummaries(new Map(summariesRef.current));};var updateValidateResult=function updateValidateResult(key,vr){console.log("validate_result",key);validateResultsRef.current.set(JSON.stringify(key),vr);setValidateResults(new Map(validateResultsRef.current));};console.log("starting listenResults");var cancel=api.listenUpdates(undefined,undefined,function(msg){switch(msg.type){case"update_summary":updateSummary(msg.summary);break;case"delete_summary":deleteSummary(msg.id);break;case"validate_result":updateValidateResult(msg.key,msg.result);break;}});return function(){console.log("cancel listenResults");cancel.then(function(c){return c();});};},[]);var projects=(0,react.useMemo)(function(){return buildProjectSummaries(summaries,validateResults);},[summaries,validateResults]);var appContext={summaries:summariesRef.current,projects:projects,validateResults:validateResults};var outletContext={filters:filters,setFilters:setFilters};return/*#__PURE__*/(0,jsx_runtime.jsx)(AppContext.Provider,{value:appContext,children:/*#__PURE__*/(0,jsx_runtime.jsx)(styles_ThemeProvider_ThemeProvider,{theme:theme_light,children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{width:"100%",height:"100%",children:/*#__PURE__*/(0,jsx_runtime.jsx)(LeftDrawer,{content:/*#__PURE__*/(0,jsx_runtime.jsx)(Outlet,{context:outletContext}),context:outletContext})})})});};/* harmony default export */ var components_App = (App); +function useAppOutletContext(){return useOutletContext();}var AppContext=/*#__PURE__*/(0,react.createContext)({summaries:new Map(),projects:[],validateResults:new Map()});var ApiContext=/*#__PURE__*/(0,react.createContext)(new StaticApi());var LoggedInApp=function LoggedInApp(props){var api=(0,react.useContext)(ApiContext);var _useState=(0,react.useState)(),_useState2=slicedToArray_slicedToArray(_useState,2),filters=_useState2[0],setFilters=_useState2[1];var summariesRef=(0,react.useRef)(new Map());var validateResultsRef=(0,react.useRef)(new Map());var _useState3=(0,react.useState)(summariesRef.current),_useState4=slicedToArray_slicedToArray(_useState3,2),summaries=_useState4[0],setSummaries=_useState4[1];var _useState5=(0,react.useState)(validateResultsRef.current),_useState6=slicedToArray_slicedToArray(_useState5,2),validateResults=_useState6[0],setValidateResults=_useState6[1];var onUnauthorized=props.onUnauthorized;(0,react.useEffect)(function(){var updateSummary=function updateSummary(rs){console.log("update_summary",rs.id,rs.commandInfo.startTime);summariesRef.current.set(rs.id,rs);setSummaries(new Map(summariesRef.current));};var deleteSummary=function deleteSummary(id){console.log("delete_summary",id);summariesRef.current.delete(id);setSummaries(new Map(summariesRef.current));};var updateValidateResult=function updateValidateResult(key,vr){console.log("validate_result",key);validateResultsRef.current.set(JSON.stringify(key),vr);setValidateResults(new Map(validateResultsRef.current));};console.log("starting listenResults");var cancel;cancel=api.listenUpdates(undefined,undefined,function(msg){switch(msg.type){case"update_summary":updateSummary(msg.summary);break;case"delete_summary":deleteSummary(msg.id);break;case"validate_result":updateValidateResult(msg.key,msg.result);break;case"auth_result":if(!msg.success){cancel.then(function(c){return c();});onUnauthorized();}}});return function(){console.log("cancel listenResults");cancel.then(function(c){return c();});};},[api,onUnauthorized]);var projects=(0,react.useMemo)(function(){return buildProjectSummaries(summaries,validateResults);},[summaries,validateResults]);var appContext={summaries:summariesRef.current,projects:projects,validateResults:validateResults};var outletContext={filters:filters,setFilters:setFilters};return/*#__PURE__*/(0,jsx_runtime.jsx)(AppContext.Provider,{value:appContext,children:/*#__PURE__*/(0,jsx_runtime.jsx)(styles_ThemeProvider_ThemeProvider,{theme:theme_light,children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{width:"100%",height:"100%",children:/*#__PURE__*/(0,jsx_runtime.jsx)(LeftDrawer,{content:/*#__PURE__*/(0,jsx_runtime.jsx)(Outlet,{context:outletContext}),context:outletContext})})})});};var App=function App(){var _useState7=(0,react.useState)(),_useState8=slicedToArray_slicedToArray(_useState7,2),api=_useState8[0],setApi=_useState8[1];var _useState9=(0,react.useState)(false),_useState10=slicedToArray_slicedToArray(_useState9,2),needToken=_useState10[0],setNeedToken=_useState10[1];var storage=localStorage;var getToken=function getToken(){var token=storage.getItem("token");if(!token){return"";}return JSON.parse(token);};var setToken=function setToken(token){if(!token){storage.removeItem("token");}else{storage.setItem("token",JSON.stringify(token));}};var onUnauthorized=function onUnauthorized(){console.log("handle onUnauthorized");setToken(undefined);setApi(undefined);setNeedToken(true);};var onTokenRefresh=function onTokenRefresh(newToken){console.log("handle onTokenRefresh");setToken(newToken);};var handleLoginSucceeded=function handleLoginSucceeded(token){console.log("handle saveToken");setToken(token);setApi(new RealApi(getToken,onUnauthorized,onTokenRefresh));};(0,react.useEffect)(function(){if(api){return;}var doInit=/*#__PURE__*/function(){var _ref=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee(){var isStatic,noAuthApi;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee$(_context){while(1)switch(_context.prev=_context.next){case 0:_context.next=2;return checkStaticBuild();case 2:isStatic=_context.sent;if(!isStatic){_context.next=7;break;}setApi(new StaticApi());_context.next=19;break;case 7:// check if we don't need auth (running locally?) +noAuthApi=new RealApi(undefined,undefined,undefined);_context.prev=8;_context.next=11;return noAuthApi.getShortNames();case 11:setToken(undefined);setNeedToken(false);setApi(noAuthApi);_context.next=19;break;case 16:_context.prev=16;_context.t0=_context["catch"](8);if(!getToken()){setNeedToken(true);}else{setApi(new RealApi(getToken,onUnauthorized,onTokenRefresh));}case 19:case"end":return _context.stop();}},_callee,null,[[8,16]]);}));return function doInit(){return _ref.apply(this,arguments);};}();doInit();// eslint-disable-next-line react-hooks/exhaustive-deps +},[]);if(needToken&&!getToken()){return/*#__PURE__*/(0,jsx_runtime.jsx)(Login,{setToken:handleLoginSucceeded});}if(!api){return/*#__PURE__*/(0,jsx_runtime.jsx)(Loading,{});}return/*#__PURE__*/(0,jsx_runtime.jsx)(ApiContext.Provider,{value:api,children:/*#__PURE__*/(0,jsx_runtime.jsx)(LoggedInApp,{onUnauthorized:onUnauthorized})});};/* harmony default export */ var components_App = (App); ;// CONCATENATED MODULE: ./src/components/targets-view/Projects.tsx var ProjectItem=function ProjectItem(props){var name=getLastPathElement(props.ps.project.gitRepoKey);var subDir=props.ps.project.subDir;var projectInfo=/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{children:[props.ps.project.gitRepoKey,/*#__PURE__*/(0,jsx_runtime.jsx)("br",{}),props.ps.project.subDir?/*#__PURE__*/(0,jsx_runtime.jsxs)(jsx_runtime.Fragment,{children:["SubDir: ",props.ps.project.subDir,/*#__PURE__*/(0,jsx_runtime.jsx)("br",{})]}):/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{})]});return/*#__PURE__*/(0,jsx_runtime.jsx)(Paper_Paper,{elevation:5,sx:{width:"100%",height:"100%",borderRadius:'12px',border:'1px solid #59A588',boxShadow:'4px 4px 10px #1E617A',padding:'5px 16px'},children:/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",flexDirection:"column",justifyContent:"center",height:"100%",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",alignItems:"center",gap:"15px",children:name&&/*#__PURE__*/(0,jsx_runtime.jsxs)(jsx_runtime.Fragment,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{flexShrink:0,children:/*#__PURE__*/(0,jsx_runtime.jsx)(ProjectIcon,{})}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:projectInfo,children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"h6",textAlign:"left",textOverflow:"ellipsis",overflow:"hidden",flexGrow:1,children:name})})]})}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{children:subDir?/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{textAlign:"center",children:subDir}):/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{})}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{marginLeft:"auto"})})})]})});}; ;// CONCATENATED MODULE: ./node_modules/@mui/material/SvgIcon/svgIconClasses.js @@ -53188,7 +53373,7 @@ var ActionsMenu=function ActionsMenu(props){var _React$useState=react.useState(n ;// CONCATENATED MODULE: ./src/utils/duration.ts function formatDuration(ms,withMs){if(ms<0)ms=-ms;var time={day:Math.floor(ms/86400000),hour:Math.floor(ms/3600000)%24,minute:Math.floor(ms/60000)%60,second:Math.floor(ms/1000)%60,millisecond:withMs?Math.floor(ms)%1000:0};return Object.entries(time).filter(function(val){return val[1]!==0;}).map(function(val){return val[1]+' '+(val[1]!==1?val[0]+'s':val[0]);}).join(', ');}function formatDurationShort(ms){if(ms<0)ms=-ms;var time={d:Math.floor(ms/86400000),h:Math.floor(ms/3600000)%24,m:Math.floor(ms/60000)%60,s:Math.floor(ms/1000)%60,ms:Math.floor(ms)%1000};var f=Object.entries(time).find(function(val){return val[1]>0;});if(f===undefined){return"0s";}return f[1]+f[0];}var calcAgo=function calcAgo(startTime){var t1=new Date(startTime);var t2=new Date();var d=t2.getTime()-t1.getTime();return formatDurationShort(d);}; ;// CONCATENATED MODULE: ./src/components/targets-view/Targets.tsx -var StatusIcon=function StatusIcon(props){var icon;var theme=styles_useTheme_useTheme();if(props.ts.lastValidateResult===undefined){icon=/*#__PURE__*/(0,jsx_runtime.jsx)(MessageQuestionIcon,{color:theme.palette.error.main});}else if(props.ts.lastValidateResult.ready&&!props.ts.lastValidateResult.errors){var _props$ts$lastValidat,_props$ts$lastValidat2;if((_props$ts$lastValidat=props.ts.lastValidateResult.warnings)!==null&&_props$ts$lastValidat!==void 0&&_props$ts$lastValidat.length){icon=/*#__PURE__*/(0,jsx_runtime.jsx)(Favorite,{color:"warning"});}else if((_props$ts$lastValidat2=props.ts.lastValidateResult.drift)!==null&&_props$ts$lastValidat2!==void 0&&_props$ts$lastValidat2.length){icon=/*#__PURE__*/(0,jsx_runtime.jsx)(Favorite,{color:"primary"});}else{icon=/*#__PURE__*/(0,jsx_runtime.jsx)(Favorite,{color:"success"});}}else{icon=/*#__PURE__*/(0,jsx_runtime.jsx)(HeartBroken,{color:"error"});}var tooltip=[];if(props.ts.lastValidateResult===undefined){tooltip.push("No validation result available.");}else{var _props$ts$lastValidat3,_props$ts$lastValidat4,_props$ts$lastValidat5,_props$ts$lastValidat6;if(props.ts.lastValidateResult.ready&&!((_props$ts$lastValidat3=props.ts.lastValidateResult.errors)!==null&&_props$ts$lastValidat3!==void 0&&_props$ts$lastValidat3.length)){tooltip.push("Target is ready.");}else{tooltip.push("Target is not ready.");}if((_props$ts$lastValidat4=props.ts.lastValidateResult.errors)!==null&&_props$ts$lastValidat4!==void 0&&_props$ts$lastValidat4.length){tooltip.push("Target has ".concat(props.ts.lastValidateResult.errors.length," validation errors."));}if((_props$ts$lastValidat5=props.ts.lastValidateResult.warnings)!==null&&_props$ts$lastValidat5!==void 0&&_props$ts$lastValidat5.length){tooltip.push("Target has ".concat(props.ts.lastValidateResult.warnings.length," validation warnings."));}if((_props$ts$lastValidat6=props.ts.lastValidateResult.drift)!==null&&_props$ts$lastValidat6!==void 0&&_props$ts$lastValidat6.length){tooltip.push("Target has ".concat(props.ts.lastValidateResult.drift.length," drifted objects."));}tooltip.push("Validation performed "+calcAgo(props.ts.lastValidateResult.startTime)+" ago");}return/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:tooltip.map(function(t){return/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{children:t},t);}),children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",children:icon})});};var TargetItem=function TargetItem(props){var _props$ts$commandResu,_props$ts$commandResu2;var actionMenuItems=[];actionMenuItems.push({icon:/*#__PURE__*/(0,jsx_runtime.jsx)(PublishedWithChanges,{}),text:"Validate now",handler:function handler(){api.validateNow(props.ps.project,props.ts.target);}});var kd;var allKdEqual=true;(_props$ts$commandResu=props.ts.commandResults)===null||_props$ts$commandResu===void 0?void 0:_props$ts$commandResu.forEach(function(rs){if(rs.commandInfo.kluctlDeployment){if(!kd){kd=rs.commandInfo.kluctlDeployment;}else{if(kd.name!==rs.commandInfo.kluctlDeployment.name||kd.namespace!==rs.commandInfo.kluctlDeployment.namespace){allKdEqual=false;}}}});if(kd&&allKdEqual){actionMenuItems.push({icon:/*#__PURE__*/(0,jsx_runtime.jsx)(PublishedWithChanges,{}),text:"Reconcile",handler:function handler(){api.reconcileNow(props.ts.target.clusterId,kd.name,kd.namespace);}});actionMenuItems.push({icon:/*#__PURE__*/(0,jsx_runtime.jsx)(PublishedWithChanges,{}),text:"Deploy",handler:function handler(){api.deployNow(props.ts.target.clusterId,kd.name,kd.namespace);}});}var allContexts=[];var handleContext=function handleContext(c){if(!c){return;}if(allContexts.find(function(x){return x===c;})){return;}allContexts.push(c);};(_props$ts$commandResu2=props.ts.commandResults)===null||_props$ts$commandResu2===void 0?void 0:_props$ts$commandResu2.forEach(function(rs){handleContext(rs.commandInfo.contextOverride);handleContext(rs.target.context);});var contextTooltip=/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{textAlign:"center",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"subtitle2",children:"All known contexts:"}),allContexts.map(function(context){return/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"subtitle2",children:context},context);})]});var targetName=props.ts.target.targetName;if(!targetName){targetName="";}return/*#__PURE__*/(0,jsx_runtime.jsx)(Paper_Paper,{elevation:5,sx:{width:"100%",height:"100%",borderRadius:'12px',border:'1px solid #59A588',boxShadow:'4px 4px 10px #1E617A',padding:'20px 16px 12px 16px'},onClick:function onClick(e){return props.onSelectTarget(props.ts);},children:/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",flexDirection:"column",justifyContent:"space-between",height:"100%",children:[/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",gap:"15px",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{flexShrink:0,children:/*#__PURE__*/(0,jsx_runtime.jsx)(TargetIcon,{})}),/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{flexGrow:1,children:[/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"h6",textAlign:"left",textOverflow:"ellipsis",overflow:"hidden",flexGrow:1,children:targetName}),allContexts.length?/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:contextTooltip,children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"subtitle1",textAlign:"left",textOverflow:"ellipsis",overflow:"hidden",whiteSpace:"nowrap",fontSize:"14px",fontWeight:500,lineHeight:"19px",children:allContexts[0]})}):/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{})]})]}),/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",alignItems:"center",justifyContent:"space-between",children:[/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",gap:"6px",alignItems:"center",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:"Cluster ID: "+props.ts.target.clusterId,children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",children:/*#__PURE__*/(0,jsx_runtime.jsx)(CpuIcon,{})})}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:"Discriminator: "+props.ts.target.discriminator,children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",children:/*#__PURE__*/(0,jsx_runtime.jsx)(FingerScanIcon,{})})})]}),/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",gap:"6px",alignItems:"center",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(StatusIcon,_objectSpread2({},props)),/*#__PURE__*/(0,jsx_runtime.jsx)(ActionsMenu,{menuItems:actionMenuItems})]})]})]})});}; +var StatusIcon=function StatusIcon(props){var icon;var theme=styles_useTheme_useTheme();if(props.ts.lastValidateResult===undefined){icon=/*#__PURE__*/(0,jsx_runtime.jsx)(MessageQuestionIcon,{color:theme.palette.error.main});}else if(props.ts.lastValidateResult.ready&&!props.ts.lastValidateResult.errors){var _props$ts$lastValidat,_props$ts$lastValidat2;if((_props$ts$lastValidat=props.ts.lastValidateResult.warnings)!==null&&_props$ts$lastValidat!==void 0&&_props$ts$lastValidat.length){icon=/*#__PURE__*/(0,jsx_runtime.jsx)(Favorite,{color:"warning"});}else if((_props$ts$lastValidat2=props.ts.lastValidateResult.drift)!==null&&_props$ts$lastValidat2!==void 0&&_props$ts$lastValidat2.length){icon=/*#__PURE__*/(0,jsx_runtime.jsx)(Favorite,{color:"primary"});}else{icon=/*#__PURE__*/(0,jsx_runtime.jsx)(Favorite,{color:"success"});}}else{icon=/*#__PURE__*/(0,jsx_runtime.jsx)(HeartBroken,{color:"error"});}var tooltip=[];if(props.ts.lastValidateResult===undefined){tooltip.push("No validation result available.");}else{var _props$ts$lastValidat3,_props$ts$lastValidat4,_props$ts$lastValidat5,_props$ts$lastValidat6;if(props.ts.lastValidateResult.ready&&!((_props$ts$lastValidat3=props.ts.lastValidateResult.errors)!==null&&_props$ts$lastValidat3!==void 0&&_props$ts$lastValidat3.length)){tooltip.push("Target is ready.");}else{tooltip.push("Target is not ready.");}if((_props$ts$lastValidat4=props.ts.lastValidateResult.errors)!==null&&_props$ts$lastValidat4!==void 0&&_props$ts$lastValidat4.length){tooltip.push("Target has ".concat(props.ts.lastValidateResult.errors.length," validation errors."));}if((_props$ts$lastValidat5=props.ts.lastValidateResult.warnings)!==null&&_props$ts$lastValidat5!==void 0&&_props$ts$lastValidat5.length){tooltip.push("Target has ".concat(props.ts.lastValidateResult.warnings.length," validation warnings."));}if((_props$ts$lastValidat6=props.ts.lastValidateResult.drift)!==null&&_props$ts$lastValidat6!==void 0&&_props$ts$lastValidat6.length){tooltip.push("Target has ".concat(props.ts.lastValidateResult.drift.length," drifted objects."));}tooltip.push("Validation performed "+calcAgo(props.ts.lastValidateResult.startTime)+" ago");}return/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:tooltip.map(function(t){return/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{children:t},t);}),children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",children:icon})});};var TargetItem=function TargetItem(props){var _props$ts$commandResu,_props$ts$commandResu2;var api=(0,react.useContext)(ApiContext);var actionMenuItems=[];actionMenuItems.push({icon:/*#__PURE__*/(0,jsx_runtime.jsx)(PublishedWithChanges,{}),text:"Validate now",handler:function handler(){api.validateNow(props.ps.project,props.ts.target);}});var kd;var allKdEqual=true;(_props$ts$commandResu=props.ts.commandResults)===null||_props$ts$commandResu===void 0?void 0:_props$ts$commandResu.forEach(function(rs){if(rs.commandInfo.kluctlDeployment){if(!kd){kd=rs.commandInfo.kluctlDeployment;}else{if(kd.name!==rs.commandInfo.kluctlDeployment.name||kd.namespace!==rs.commandInfo.kluctlDeployment.namespace){allKdEqual=false;}}}});if(kd&&allKdEqual){actionMenuItems.push({icon:/*#__PURE__*/(0,jsx_runtime.jsx)(PublishedWithChanges,{}),text:"Reconcile",handler:function handler(){api.reconcileNow(props.ts.target.clusterId,kd.name,kd.namespace);}});actionMenuItems.push({icon:/*#__PURE__*/(0,jsx_runtime.jsx)(PublishedWithChanges,{}),text:"Deploy",handler:function handler(){api.deployNow(props.ts.target.clusterId,kd.name,kd.namespace);}});}var allContexts=[];var handleContext=function handleContext(c){if(!c){return;}if(allContexts.find(function(x){return x===c;})){return;}allContexts.push(c);};(_props$ts$commandResu2=props.ts.commandResults)===null||_props$ts$commandResu2===void 0?void 0:_props$ts$commandResu2.forEach(function(rs){handleContext(rs.commandInfo.contextOverride);handleContext(rs.target.context);});var contextTooltip=/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{textAlign:"center",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"subtitle2",children:"All known contexts:"}),allContexts.map(function(context){return/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"subtitle2",children:context},context);})]});var targetName=props.ts.target.targetName;if(!targetName){targetName="";}return/*#__PURE__*/(0,jsx_runtime.jsx)(Paper_Paper,{elevation:5,sx:{width:"100%",height:"100%",borderRadius:'12px',border:'1px solid #59A588',boxShadow:'4px 4px 10px #1E617A',padding:'20px 16px 12px 16px'},onClick:function onClick(e){return props.onSelectTarget(props.ts);},children:/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",flexDirection:"column",justifyContent:"space-between",height:"100%",children:[/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",gap:"15px",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{flexShrink:0,children:/*#__PURE__*/(0,jsx_runtime.jsx)(TargetIcon,{})}),/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{flexGrow:1,children:[/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"h6",textAlign:"left",textOverflow:"ellipsis",overflow:"hidden",flexGrow:1,children:targetName}),allContexts.length?/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:contextTooltip,children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"subtitle1",textAlign:"left",textOverflow:"ellipsis",overflow:"hidden",whiteSpace:"nowrap",fontSize:"14px",fontWeight:500,lineHeight:"19px",children:allContexts[0]})}):/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{})]})]}),/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",alignItems:"center",justifyContent:"space-between",children:[/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",gap:"6px",alignItems:"center",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:"Cluster ID: "+props.ts.target.clusterId,children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",children:/*#__PURE__*/(0,jsx_runtime.jsx)(CpuIcon,{})})}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:"Discriminator: "+props.ts.target.discriminator,children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",children:/*#__PURE__*/(0,jsx_runtime.jsx)(FingerScanIcon,{})})})]}),/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",gap:"6px",alignItems:"center",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(StatusIcon,_objectSpread2({},props)),/*#__PURE__*/(0,jsx_runtime.jsx)(ActionsMenu,{menuItems:actionMenuItems})]})]})]})});}; ;// CONCATENATED MODULE: ./node_modules/js-yaml/dist/js-yaml.mjs /*! js-yaml 4.1.0 https://github.com/nodeca/js-yaml @license MIT */ function isNothing(subject) { @@ -57554,187 +57739,8 @@ function buildListKey(o){var j=JSON.stringify(o);return (0,sha256.sha256)(j);} var RefList=function RefList(props){return/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{py:"10px",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{align:"center",variant:"h5",children:props.title}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableContainer_TableContainer,{children:/*#__PURE__*/(0,jsx_runtime.jsxs)(Table_Table,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableHead_TableHead,{children:/*#__PURE__*/(0,jsx_runtime.jsxs)(TableRow_TableRow,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{align:"left",children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{children:"Kind"})}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{align:"left",children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{children:"Namespace"})}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{align:"left",children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{children:"Name"})})]})}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableBody_TableBody,{children:props.refs.map(function(ref){return/*#__PURE__*/(0,jsx_runtime.jsxs)(TableRow_TableRow,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:buildRefKindElement(ref)}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{children:ref.namespace})}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{children:ref.name})})]},buildRefString(ref));})})]})})]});};function ChangesTable(props){var changedObjects;if(props.diffStatus.changedObjects.length){changedObjects=/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{py:"10px",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{align:"center",variant:"h5",children:"Changed Objects"}),props.diffStatus.changedObjects.map(function(co){var _co$changes;return/*#__PURE__*/(0,jsx_runtime.jsx)(TableContainer_TableContainer,{children:/*#__PURE__*/(0,jsx_runtime.jsxs)(Table_Table,{children:[/*#__PURE__*/(0,jsx_runtime.jsxs)(TableHead_TableHead,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableRow_TableRow,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{align:"left",colSpan:2,sx:{padding:'24px 16px 5px 16px'},children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{sx:{fontWeight:500,fontSize:'20px',lineHeight:'27px',letterSpacing:'1px'},children:buildRefString(co.ref)})})}),/*#__PURE__*/(0,jsx_runtime.jsxs)(TableRow_TableRow,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:"Path"}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:"Changes"})]})]}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableBody_TableBody,{children:(_co$changes=co.changes)===null||_co$changes===void 0?void 0:_co$changes.map(function(c){return/*#__PURE__*/(0,jsx_runtime.jsxs)(TableRow_TableRow,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{minWidth:"100px",sx:{overflowWrap:"anywhere"},children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{children:c.jsonPath})})}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(CodeViewer,{code:c.unifiedDiff||"",language:"diff"})})]},buildListKey(c));})})]})},buildRefString(co.ref));})]});}return/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{width:"100%",display:"flex",flexDirection:"column",children:[props.diffStatus.newObjects.length?/*#__PURE__*/(0,jsx_runtime.jsx)(RefList,{title:"New Objects",refs:props.diffStatus.newObjects}):/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{}),props.diffStatus.deletedObjects.length?/*#__PURE__*/(0,jsx_runtime.jsx)(RefList,{title:"Deleted Objects",refs:props.diffStatus.deletedObjects}):/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{}),props.diffStatus.orphanObjects.length?/*#__PURE__*/(0,jsx_runtime.jsx)(RefList,{title:"Orphan Objects",refs:props.diffStatus.orphanObjects}):/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{}),changedObjects]});} ;// CONCATENATED MODULE: ./src/components/ErrorsTable.tsx function ErrorsTable(props){var _props$errors;return/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{height:"100%",children:/*#__PURE__*/(0,jsx_runtime.jsx)(TableContainer_TableContainer,{children:/*#__PURE__*/(0,jsx_runtime.jsxs)(Table_Table,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableHead_TableHead,{children:/*#__PURE__*/(0,jsx_runtime.jsxs)(TableRow_TableRow,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:"Ref"}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:"Message"})]})}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableBody_TableBody,{children:(_props$errors=props.errors)===null||_props$errors===void 0?void 0:_props$errors.map(function(e){return/*#__PURE__*/(0,jsx_runtime.jsxs)(TableRow_TableRow,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{sx:{minWidth:"150px"},children:/*#__PURE__*/(0,jsx_runtime.jsxs)(List_List,{disablePadding:true,children:[buildRefKindElement(e.ref,/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(ListItem_ListItem,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(ListItemText_ListItemText,{primary:e.ref.kind})})})),/*#__PURE__*/(0,jsx_runtime.jsx)(Divider_Divider,{}),/*#__PURE__*/(0,jsx_runtime.jsx)(ListItem_ListItem,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(ListItemText_ListItemText,{primary:e.ref.name})}),e.ref.namespace&&/*#__PURE__*/(0,jsx_runtime.jsxs)(jsx_runtime.Fragment,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(Divider_Divider,{}),/*#__PURE__*/(0,jsx_runtime.jsx)(ListItem_ListItem,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(ListItemText_ListItemText,{primary:e.ref.namespace})})]})]})}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:e.message})]},buildListKey(e));})})]})})})});} -;// CONCATENATED MODULE: ./node_modules/@mui/material/CircularProgress/circularProgressClasses.js - - -function getCircularProgressUtilityClass(slot) { - return generateUtilityClass_generateUtilityClass('MuiCircularProgress', slot); -} -var circularProgressClasses = generateUtilityClasses('MuiCircularProgress', ['root', 'determinate', 'indeterminate', 'colorPrimary', 'colorSecondary', 'svg', 'circle', 'circleDeterminate', 'circleIndeterminate', 'circleDisableShrink']); -/* harmony default export */ var CircularProgress_circularProgressClasses = ((/* unused pure expression or super */ null && (circularProgressClasses))); -;// CONCATENATED MODULE: ./node_modules/@mui/material/CircularProgress/CircularProgress.js - -var CircularProgress_templateObject, CircularProgress_templateObject2, CircularProgress_templateObject3, CircularProgress_templateObject4; - - -var CircularProgress_excluded = ["className", "color", "disableShrink", "size", "style", "thickness", "value", "variant"]; -var CircularProgress_ = function _(t) { - return t; - }, - CircularProgress_t, - CircularProgress_t2, - CircularProgress_t3, - CircularProgress_t4; - - - - - - - - - - - -var SIZE = 44; -var circularRotateKeyframe = keyframes(CircularProgress_t || (CircularProgress_t = CircularProgress_(CircularProgress_templateObject || (CircularProgress_templateObject = _taggedTemplateLiteral(["\n 0% {\n transform: rotate(0deg);\n }\n\n 100% {\n transform: rotate(360deg);\n }\n"]))))); -var circularDashKeyframe = keyframes(CircularProgress_t2 || (CircularProgress_t2 = CircularProgress_(CircularProgress_templateObject2 || (CircularProgress_templateObject2 = _taggedTemplateLiteral(["\n 0% {\n stroke-dasharray: 1px, 200px;\n stroke-dashoffset: 0;\n }\n\n 50% {\n stroke-dasharray: 100px, 200px;\n stroke-dashoffset: -15px;\n }\n\n 100% {\n stroke-dasharray: 100px, 200px;\n stroke-dashoffset: -125px;\n }\n"]))))); -var CircularProgress_useUtilityClasses = function useUtilityClasses(ownerState) { - var classes = ownerState.classes, - variant = ownerState.variant, - color = ownerState.color, - disableShrink = ownerState.disableShrink; - var slots = { - root: ['root', variant, "color".concat(utils_capitalize(color))], - svg: ['svg'], - circle: ['circle', "circle".concat(utils_capitalize(variant)), disableShrink && 'circleDisableShrink'] - }; - return composeClasses(slots, getCircularProgressUtilityClass, classes); -}; -var CircularProgressRoot = styles_styled('span', { - name: 'MuiCircularProgress', - slot: 'Root', - overridesResolver: function overridesResolver(props, styles) { - var ownerState = props.ownerState; - return [styles.root, styles[ownerState.variant], styles["color".concat(utils_capitalize(ownerState.color))]]; - } -})(function (_ref) { - var ownerState = _ref.ownerState, - theme = _ref.theme; - return extends_extends({ - display: 'inline-block' - }, ownerState.variant === 'determinate' && { - transition: theme.transitions.create('transform') - }, ownerState.color !== 'inherit' && { - color: (theme.vars || theme).palette[ownerState.color].main - }); -}, function (_ref2) { - var ownerState = _ref2.ownerState; - return ownerState.variant === 'indeterminate' && css(CircularProgress_t3 || (CircularProgress_t3 = CircularProgress_(CircularProgress_templateObject3 || (CircularProgress_templateObject3 = _taggedTemplateLiteral(["\n animation: ", " 1.4s linear infinite;\n "])), 0)), circularRotateKeyframe); -}); -var CircularProgressSVG = styles_styled('svg', { - name: 'MuiCircularProgress', - slot: 'Svg', - overridesResolver: function overridesResolver(props, styles) { - return styles.svg; - } -})({ - display: 'block' // Keeps the progress centered -}); - -var CircularProgressCircle = styles_styled('circle', { - name: 'MuiCircularProgress', - slot: 'Circle', - overridesResolver: function overridesResolver(props, styles) { - var ownerState = props.ownerState; - return [styles.circle, styles["circle".concat(utils_capitalize(ownerState.variant))], ownerState.disableShrink && styles.circleDisableShrink]; - } -})(function (_ref3) { - var ownerState = _ref3.ownerState, - theme = _ref3.theme; - return extends_extends({ - stroke: 'currentColor' - }, ownerState.variant === 'determinate' && { - transition: theme.transitions.create('stroke-dashoffset') - }, ownerState.variant === 'indeterminate' && { - // Some default value that looks fine waiting for the animation to kicks in. - strokeDasharray: '80px, 200px', - strokeDashoffset: 0 // Add the unit to fix a Edge 16 and below bug. - }); -}, function (_ref4) { - var ownerState = _ref4.ownerState; - return ownerState.variant === 'indeterminate' && !ownerState.disableShrink && css(CircularProgress_t4 || (CircularProgress_t4 = CircularProgress_(CircularProgress_templateObject4 || (CircularProgress_templateObject4 = _taggedTemplateLiteral(["\n animation: ", " 1.4s ease-in-out infinite;\n "])), 0)), circularDashKeyframe); -}); - -/** - * ## ARIA - * - * If the progress bar is describing the loading progress of a particular region of a page, - * you should use `aria-describedby` to point to the progress bar, and set the `aria-busy` - * attribute to `true` on that region until it has finished loading. - */ -var CircularProgress = /*#__PURE__*/react.forwardRef(function CircularProgress(inProps, ref) { - var props = useThemeProps_useThemeProps({ - props: inProps, - name: 'MuiCircularProgress' - }); - var className = props.className, - _props$color = props.color, - color = _props$color === void 0 ? 'primary' : _props$color, - _props$disableShrink = props.disableShrink, - disableShrink = _props$disableShrink === void 0 ? false : _props$disableShrink, - _props$size = props.size, - size = _props$size === void 0 ? 40 : _props$size, - style = props.style, - _props$thickness = props.thickness, - thickness = _props$thickness === void 0 ? 3.6 : _props$thickness, - _props$value = props.value, - value = _props$value === void 0 ? 0 : _props$value, - _props$variant = props.variant, - variant = _props$variant === void 0 ? 'indeterminate' : _props$variant, - other = objectWithoutPropertiesLoose_objectWithoutPropertiesLoose(props, CircularProgress_excluded); - var ownerState = extends_extends({}, props, { - color: color, - disableShrink: disableShrink, - size: size, - thickness: thickness, - value: value, - variant: variant - }); - var classes = CircularProgress_useUtilityClasses(ownerState); - var circleStyle = {}; - var rootStyle = {}; - var rootProps = {}; - if (variant === 'determinate') { - var circumference = 2 * Math.PI * ((SIZE - thickness) / 2); - circleStyle.strokeDasharray = circumference.toFixed(3); - rootProps['aria-valuenow'] = Math.round(value); - circleStyle.strokeDashoffset = "".concat(((100 - value) / 100 * circumference).toFixed(3), "px"); - rootStyle.transform = 'rotate(-90deg)'; - } - return /*#__PURE__*/(0,jsx_runtime.jsx)(CircularProgressRoot, extends_extends({ - className: clsx_m(classes.root, className), - style: extends_extends({ - width: size, - height: size - }, rootStyle, style), - ownerState: ownerState, - ref: ref, - role: "progressbar" - }, rootProps, other, { - children: /*#__PURE__*/(0,jsx_runtime.jsx)(CircularProgressSVG, { - className: classes.svg, - ownerState: ownerState, - viewBox: "".concat(SIZE / 2, " ").concat(SIZE / 2, " ").concat(SIZE, " ").concat(SIZE), - children: /*#__PURE__*/(0,jsx_runtime.jsx)(CircularProgressCircle, { - className: classes.circle, - style: circleStyle, - ownerState: ownerState, - cx: SIZE, - cy: SIZE, - r: (SIZE - thickness) / 2, - fill: "none", - strokeWidth: thickness - }) - }) - })); -}); - false ? 0 : void 0; -/* harmony default export */ var CircularProgress_CircularProgress = (CircularProgress); -;// CONCATENATED MODULE: ./src/components/Loading.tsx -var Loading=function Loading(){return/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",height:"100%",children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",width:"100%",height:"100%",alignItems:"center",justifyContent:"center",children:/*#__PURE__*/(0,jsx_runtime.jsx)(CircularProgress_CircularProgress,{})})});}; ;// CONCATENATED MODULE: ./src/components/ObjectYaml.tsx -var ObjectYaml=function ObjectYaml(props){var _useState=(0,react.useState)(),_useState2=slicedToArray_slicedToArray(_useState,2),promise=_useState2[0],setPromise=_useState2[1];(0,react.useEffect)(function(){var getData=/*#__PURE__*/function(){var _ref=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee(){var o;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee$(_context){while(1)switch(_context.prev=_context.next){case 0:_context.next=2;return api.getResultObject(props.treeProps.summary.id,props.objectRef,props.objectType);case 2:o=_context.sent;return _context.abrupt("return",dump(o));case 4:case"end":return _context.stop();}},_callee);}));return function getData(){return _ref.apply(this,arguments);};}();setPromise(getData());},[props.treeProps,props.objectRef,props.objectType]);var Content=function Content(){return/*#__PURE__*/(0,jsx_runtime.jsx)(CodeViewer,{code:usePromise(promise),language:"yaml"});};return/*#__PURE__*/(0,jsx_runtime.jsx)(react.Suspense,{fallback:/*#__PURE__*/(0,jsx_runtime.jsx)(Loading,{}),children:/*#__PURE__*/(0,jsx_runtime.jsx)(Content,{})});}; +var ObjectYaml=function ObjectYaml(props){var api=(0,react.useContext)(ApiContext);var _useLoadingHelper=useLoadingHelper(/*#__PURE__*/asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee(){var o;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee$(_context){while(1)switch(_context.prev=_context.next){case 0:_context.next=2;return api.getResultObject(props.treeProps.summary.id,props.objectRef,props.objectType);case 2:o=_context.sent;return _context.abrupt("return",dump(o));case 4:case"end":return _context.stop();}},_callee);})),[props.treeProps.summary.id,props.objectRef,props.objectType]),_useLoadingHelper2=slicedToArray_slicedToArray(_useLoadingHelper,3),loading=_useLoadingHelper2[0],error=_useLoadingHelper2[1],content=_useLoadingHelper2[2];if(loading){return/*#__PURE__*/(0,jsx_runtime.jsx)(Loading,{});}else if(error){return/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{children:"Error"});}else{return/*#__PURE__*/(0,jsx_runtime.jsx)(CodeViewer,{code:content,language:"yaml"});}}; ;// CONCATENATED MODULE: ./src/components/result-view/nodes/NodeData.tsx var DiffStatus=/*#__PURE__*/function(){function DiffStatus(){classCallCheck_classCallCheck(this,DiffStatus);this.newObjects=[];this.deletedObjects=[];this.orphanObjects=[];this.changedObjects=[];this.totalInsertions=0;this.totalDeletions=0;this.totalUpdates=0;}createClass_createClass(DiffStatus,[{key:"addChangedObject",value:function addChangedObject(co){var _co$changes,_this=this;this.changedObjects.push(co);(_co$changes=co.changes)===null||_co$changes===void 0?void 0:_co$changes.forEach(function(x){switch(x.type){case"insert":_this.totalInsertions++;break;case"delete":_this.totalDeletions++;break;case"update":_this.totalUpdates++;break;}});}},{key:"merge",value:function merge(other){this.newObjects=this.newObjects.concat(other.newObjects);this.deletedObjects=this.deletedObjects.concat(other.deletedObjects);this.orphanObjects=this.orphanObjects.concat(other.orphanObjects);this.changedObjects=this.changedObjects.concat(other.changedObjects);this.totalInsertions+=other.totalInsertions;this.totalDeletions+=other.totalDeletions;this.totalUpdates+=other.totalUpdates;}},{key:"hasDiffs",value:function hasDiffs(){if(this.newObjects.length||this.deletedObjects.length||this.orphanObjects.length){return true;}if(this.changedObjects.find(function(co){var _co$changes2;return((_co$changes2=co.changes)===null||_co$changes2===void 0?void 0:_co$changes2.length)!==0;})){return true;}return false;}}]);return DiffStatus;}();var HealthStatus=/*#__PURE__*/function(){function HealthStatus(){classCallCheck_classCallCheck(this,HealthStatus);this.errors=[];this.warnings=[];}createClass_createClass(HealthStatus,[{key:"merge",value:function merge(other){this.errors=this.errors.concat(other.errors);this.warnings=this.warnings.concat(other.warnings);}}]);return HealthStatus;}();var NodeData=/*#__PURE__*/function(){function NodeData(props,id,hasHealthStatus,hasDiffStatus){classCallCheck_classCallCheck(this,NodeData);this.props=void 0;this.id=void 0;this.children=[];this.healthStatus=void 0;this.diffStatus=void 0;this.props=props;this.id=id;if(hasHealthStatus){this.healthStatus=new HealthStatus();}if(hasDiffStatus){this.diffStatus=new DiffStatus();}}createClass_createClass(NodeData,[{key:"pushChild",value:function pushChild(child,front){if(front){this.children.unshift(child);}else{this.children.push(child);}this.merge(child);}},{key:"merge",value:function merge(other){if(this.diffStatus&&other.diffStatus){this.diffStatus.merge(other.diffStatus);}if(this.healthStatus&&other.healthStatus){this.healthStatus.merge(other.healthStatus);}}},{key:"buildStatusLine",value:function buildStatusLine(){var _this$healthStatus,_this$healthStatus2,_this$diffStatus,_this$diffStatus2,_this$diffStatus3,_this$diffStatus4;return/*#__PURE__*/(0,jsx_runtime.jsx)(StatusLine,{errors:(_this$healthStatus=this.healthStatus)===null||_this$healthStatus===void 0?void 0:_this$healthStatus.errors.length,warnings:(_this$healthStatus2=this.healthStatus)===null||_this$healthStatus2===void 0?void 0:_this$healthStatus2.warnings.length,changedObjects:(_this$diffStatus=this.diffStatus)===null||_this$diffStatus===void 0?void 0:_this$diffStatus.changedObjects.length,newObjects:(_this$diffStatus2=this.diffStatus)===null||_this$diffStatus2===void 0?void 0:_this$diffStatus2.newObjects.length,deletedObjects:(_this$diffStatus3=this.diffStatus)===null||_this$diffStatus3===void 0?void 0:_this$diffStatus3.deletedObjects.length,orphanObjects:(_this$diffStatus4=this.diffStatus)===null||_this$diffStatus4===void 0?void 0:_this$diffStatus4.orphanObjects.length});}},{key:"buildTreeItem",value:function buildTreeItem(hasChildren){var _this$healthStatus3,_this$healthStatus4,_this$diffStatus5,_this$diffStatus6,_this$diffStatus7,_this$diffStatus8;var _this$buildIcon=this.buildIcon(),_this$buildIcon2=slicedToArray_slicedToArray(_this$buildIcon,2),icon=_this$buildIcon2[0],iconText=_this$buildIcon2[1];var hasStatusLine=[(_this$healthStatus3=this.healthStatus)===null||_this$healthStatus3===void 0?void 0:_this$healthStatus3.errors.length,(_this$healthStatus4=this.healthStatus)===null||_this$healthStatus4===void 0?void 0:_this$healthStatus4.warnings.length,(_this$diffStatus5=this.diffStatus)===null||_this$diffStatus5===void 0?void 0:_this$diffStatus5.changedObjects.length,(_this$diffStatus6=this.diffStatus)===null||_this$diffStatus6===void 0?void 0:_this$diffStatus6.newObjects.length,(_this$diffStatus7=this.diffStatus)===null||_this$diffStatus7===void 0?void 0:_this$diffStatus7.deletedObjects.length,(_this$diffStatus8=this.diffStatus)===null||_this$diffStatus8===void 0?void 0:_this$diffStatus8.orphanObjects.length].some(function(x){return(x||0)>0;});return/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",height:"100%",flex:"1 1 auto",children:[/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",width:"30px",height:"100%",flex:"0 0 auto",mr:"13px",sx:{'& svg':{width:'30px',height:'30px'}},children:[icon,/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"subtitle1",component:"div",fontSize:"12px",fontWeight:400,lineHeight:"16px",height:"16px",children:iconText})]}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",height:"100%",flex:"1 1 auto",py:"15px",children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,_objectSpread2(_objectSpread2({variant:"h6",component:"div",sx:{wordBreak:'break-all'}},hasChildren?{}:{fontSize:'16px',lineHeight:'22px'}),{},{children:this.buildSidePanelTitle()}))}),hasStatusLine&&/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{height:"100%",width:"172px",flex:"0 0 auto",display:"flex",alignItems:"center",px:"14px",ml:"14px",position:"relative",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(Divider_Divider,{orientation:"vertical",sx:{height:'40px',position:'absolute',left:0}}),this.buildStatusLine()]})]});}},{key:"buildDiffAndHealthPages",value:function buildDiffAndHealthPages(tabs){this.buildChangesPage(tabs);this.buildErrorsPage(tabs);this.buildWarningsPage(tabs);}},{key:"buildObjectPage",value:function buildObjectPage(ref,objectType){return/*#__PURE__*/(0,jsx_runtime.jsx)(ObjectYaml,{treeProps:this.props,objectRef:ref,objectType:objectType});}},{key:"buildChangesPage",value:function buildChangesPage(tabs){var _this$diffStatus9;if(!((_this$diffStatus9=this.diffStatus)!==null&&_this$diffStatus9!==void 0&&_this$diffStatus9.hasDiffs())){return undefined;}tabs.push({label:"Changes",content:/*#__PURE__*/(0,jsx_runtime.jsx)(ChangesTable,{diffStatus:this.diffStatus})});}},{key:"buildErrorsPage",value:function buildErrorsPage(tabs){var _this$healthStatus5;if(!((_this$healthStatus5=this.healthStatus)!==null&&_this$healthStatus5!==void 0&&_this$healthStatus5.errors.length)){return undefined;}tabs.push({label:"Errors",content:/*#__PURE__*/(0,jsx_runtime.jsx)(ErrorsTable,{errors:this.healthStatus.errors})});}},{key:"buildWarningsPage",value:function buildWarningsPage(tabs){var _this$healthStatus6;if(!((_this$healthStatus6=this.healthStatus)!==null&&_this$healthStatus6!==void 0&&_this$healthStatus6.warnings.length)){return undefined;}tabs.push({label:"Warnings",content:/*#__PURE__*/(0,jsx_runtime.jsx)(ErrorsTable,{errors:this.healthStatus.warnings})});}}]);return NodeData;}(); ;// CONCATENATED MODULE: ./node_modules/@mui/icons-material/esm/Category.js @@ -59157,7 +59163,7 @@ setSelectedTab(tabs[0].label);}// ignore that it wants us to add selectedTab to ;// CONCATENATED MODULE: ./src/components/targets-view/Card.tsx var Card_excluded=["children"],Card_excluded2=["children"],Card_excluded3=["children"];var cardWidth=247;var projectCardHeight=80;var cardHeight=126;var cardGap=20;function Card(_ref){var children=_ref.children,rest=objectWithoutProperties_objectWithoutProperties(_ref,Card_excluded);return/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,_objectSpread2(_objectSpread2({display:"flex",flexShrink:0,width:cardWidth,height:cardHeight},rest),{},{children:children}));}function CardCol(_ref2){var children=_ref2.children,rest=objectWithoutProperties_objectWithoutProperties(_ref2,Card_excluded2);return/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,_objectSpread2(_objectSpread2({display:"flex",flexDirection:"column",gap:"".concat(cardGap,"px")},rest),{},{children:children}));}function CardRow(_ref3){var children=_ref3.children,rest=objectWithoutProperties_objectWithoutProperties(_ref3,Card_excluded3);return/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,_objectSpread2(_objectSpread2({display:"flex",gap:"".concat(cardGap,"px")},rest),{},{children:children}));} ;// CONCATENATED MODULE: ./src/components/targets-view/CommandResultDetailsDrawer.tsx -var sidePanelWidth=720;function doGetRootNode(_x){return _doGetRootNode.apply(this,arguments);}function _doGetRootNode(){_doGetRootNode=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee(rs){var shortNames,r,builder,_builder$buildRoot,_builder$buildRoot2,node;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee$(_context){while(1)switch(_context.prev=_context.next){case 0:shortNames=api.getShortNames();r=api.getResult(rs.id);_context.t0=NodeBuilder;_context.next=5;return shortNames;case 5:_context.t1=_context.sent;_context.t2=rs;_context.next=9;return r;case 9:_context.t3=_context.sent;_context.t4={shortNames:_context.t1,summary:_context.t2,commandResult:_context.t3};builder=new _context.t0(_context.t4);_builder$buildRoot=builder.buildRoot(),_builder$buildRoot2=slicedToArray_slicedToArray(_builder$buildRoot,1),node=_builder$buildRoot2[0];return _context.abrupt("return",node);case 14:case"end":return _context.stop();}},_callee);}));return _doGetRootNode.apply(this,arguments);}var CommandResultDetailsDrawer=/*#__PURE__*/react.memo(function(props){var ps=props.ps,ts=props.ts,selectedCardRect=props.selectedCardRect;var theme=styles_useTheme_useTheme();var _useState=(0,react.useState)(new Promise(function(){return undefined;})),_useState2=slicedToArray_slicedToArray(_useState,2),promise=_useState2[0],setPromise=_useState2[1];var _useState3=(0,react.useState)(),_useState4=slicedToArray_slicedToArray(_useState3,2),selectedCommandResult=_useState4[0],setSelectedCommandResult=_useState4[1];var _useState5=(0,react.useState)(ts),_useState6=slicedToArray_slicedToArray(_useState5,2),prevTargetSummary=_useState6[0],setPrevTargetSummary=_useState6[1];var _useState7=(0,react.useState)([]),_useState8=slicedToArray_slicedToArray(_useState7,2),cardsCoords=_useState8[0],setCardsCoords=_useState8[1];var _useState9=(0,react.useState)(false),_useState10=slicedToArray_slicedToArray(_useState9,2),transitionRunning=_useState10[0],setTransitionRunning=_useState10[1];if(prevTargetSummary!==ts){var _ts$commandResults;setPrevTargetSummary(ts);setSelectedCommandResult(ts===null||ts===void 0?void 0:(_ts$commandResults=ts.commandResults)===null||_ts$commandResults===void 0?void 0:_ts$commandResults[0]);}(0,react.useEffect)(function(){if(selectedCommandResult===undefined){return;}setPromise(doGetRootNode(selectedCommandResult));},[selectedCommandResult]);var Content=function Content(props){var node=usePromise(promise);return/*#__PURE__*/(0,jsx_runtime.jsx)(SidePanel,{provider:node,onClose:props.onClose});};var cardsContainerElem=(0,react.useRef)();(0,react.useEffect)(function(){var _cardsContainerElem$c;var rect=(_cardsContainerElem$c=cardsContainerElem.current)===null||_cardsContainerElem$c===void 0?void 0:_cardsContainerElem$c.getBoundingClientRect();if(!rect||!selectedCardRect||!(ts!==null&&ts!==void 0&&ts.commandResults)){setCardsCoords([]);return;}var initialCoords=ts.commandResults.map(function(){return{left:selectedCardRect.left-rect.left,top:selectedCardRect.top-rect.top};});setCardsCoords(initialCoords);setTransitionRunning(true);},[selectedCardRect,ts===null||ts===void 0?void 0:ts.commandResults]);(0,react.useEffect)(function(){if(cardsCoords.length>0){var targetCoords=cardsCoords.map(function(_,i){return{left:0,top:i*(cardHeight+cardGap)};});if(cardsCoords.length===targetCoords.length&&cardsCoords.every(function(_ref,i){var left=_ref.left,top=_ref.top;return targetCoords[i].left===left&&targetCoords[i].top===top;})){return;}setTimeout(function(){setCardsCoords(targetCoords);setTimeout(function(){setTransitionRunning(false);},theme.transitions.duration.enteringScreen);},10);}},[cardsCoords,theme.transitions.duration.enteringScreen]);var zIndex=theme.zIndex.modal+1;var cards=(0,react.useMemo)(function(){if(!(ps&&ts&&ts.commandResults&&ts.commandResults.length>0&&cardsCoords.length>0)){return null;}return ts.commandResults.map(function(rs,i){return/*#__PURE__*/(0,jsx_runtime.jsx)(Card,{sx:{position:'absolute',translate:"".concat(cardsCoords[i].left,"px ").concat(cardsCoords[i].top,"px"),zIndex:zIndex,transition:theme.transitions.create(['translate'],{easing:theme.transitions.easing.sharp,duration:theme.transitions.duration.enteringScreen})},children:/*#__PURE__*/(0,jsx_runtime.jsx)(CommandResultItem,{ps:ps,ts:ts,rs:rs,onSelectCommandResult:setSelectedCommandResult,selected:rs===selectedCommandResult})},rs.id);});},[ps,ts,cardsCoords,zIndex,theme.transitions,selectedCommandResult]);return/*#__PURE__*/(0,jsx_runtime.jsxs)(jsx_runtime.Fragment,{children:[ps&&ts&&ts.commandResults&&ts.commandResults.length>0&&/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{sx:{position:'fixed',top:0,bottom:0,right:sidePanelWidth,width:"calc(100% - ".concat(sidePanelWidth,"px)"),overflowX:'visible',overflowY:transitionRunning?'visible':'auto',display:'flex',flexDirection:'column',alignItems:'center',justifyContent:'center',padding:'25px 0',zIndex:zIndex},onClick:props.onClose,children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{onClick:function onClick(e){return e.stopPropagation();},position:"relative",width:cardWidth,height:cardHeight*ts.commandResults.length+cardGap*(ts.commandResults.length-1),ref:cardsContainerElem,children:cards})}),/*#__PURE__*/(0,jsx_runtime.jsx)(styles_ThemeProvider_ThemeProvider,{theme:theme_dark,children:/*#__PURE__*/(0,jsx_runtime.jsx)(Drawer_Drawer,{sx:{zIndex:1300},anchor:"right",open:props.rs!==undefined,onClose:props.onClose,children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{width:sidePanelWidth,height:"100%",children:/*#__PURE__*/(0,jsx_runtime.jsx)(react.Suspense,{fallback:/*#__PURE__*/(0,jsx_runtime.jsx)(Loading,{}),children:/*#__PURE__*/(0,jsx_runtime.jsx)(Content,{onClose:props.onClose})})})})})]});}); +var sidePanelWidth=720;function doGetRootNode(_x,_x2){return _doGetRootNode.apply(this,arguments);}function _doGetRootNode(){_doGetRootNode=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee(api,rs){var shortNames,r,builder,_builder$buildRoot,_builder$buildRoot2,node;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee$(_context){while(1)switch(_context.prev=_context.next){case 0:_context.next=2;return api.getShortNames();case 2:shortNames=_context.sent;_context.next=5;return api.getResult(rs.id);case 5:r=_context.sent;builder=new NodeBuilder({shortNames:shortNames,summary:rs,commandResult:r});_builder$buildRoot=builder.buildRoot(),_builder$buildRoot2=slicedToArray_slicedToArray(_builder$buildRoot,1),node=_builder$buildRoot2[0];return _context.abrupt("return",node);case 9:case"end":return _context.stop();}},_callee);}));return _doGetRootNode.apply(this,arguments);}var CommandResultDetailsDrawer=/*#__PURE__*/react.memo(function(props){var ps=props.ps,ts=props.ts,selectedCardRect=props.selectedCardRect;var api=(0,react.useContext)(ApiContext);var theme=styles_useTheme_useTheme();var _useState=(0,react.useState)(),_useState2=slicedToArray_slicedToArray(_useState,2),selectedCommandResult=_useState2[0],setSelectedCommandResult=_useState2[1];var _useState3=(0,react.useState)(ts),_useState4=slicedToArray_slicedToArray(_useState3,2),prevTargetSummary=_useState4[0],setPrevTargetSummary=_useState4[1];var _useState5=(0,react.useState)([]),_useState6=slicedToArray_slicedToArray(_useState5,2),cardsCoords=_useState6[0],setCardsCoords=_useState6[1];var _useState7=(0,react.useState)(false),_useState8=slicedToArray_slicedToArray(_useState7,2),transitionRunning=_useState8[0],setTransitionRunning=_useState8[1];if(prevTargetSummary!==ts){var _ts$commandResults;setPrevTargetSummary(ts);setSelectedCommandResult(ts===null||ts===void 0?void 0:(_ts$commandResults=ts.commandResults)===null||_ts$commandResults===void 0?void 0:_ts$commandResults[0]);}var _useLoadingHelper=useLoadingHelper(function(){if(selectedCommandResult===undefined){return Promise.resolve(undefined);}return doGetRootNode(api,selectedCommandResult);},[selectedCommandResult]),_useLoadingHelper2=slicedToArray_slicedToArray(_useLoadingHelper,3),loading=_useLoadingHelper2[0],loadingError=_useLoadingHelper2[1],nodeData=_useLoadingHelper2[2];var cardsContainerElem=(0,react.useRef)();(0,react.useEffect)(function(){var _cardsContainerElem$c;var rect=(_cardsContainerElem$c=cardsContainerElem.current)===null||_cardsContainerElem$c===void 0?void 0:_cardsContainerElem$c.getBoundingClientRect();if(!rect||!selectedCardRect||!(ts!==null&&ts!==void 0&&ts.commandResults)){setCardsCoords([]);return;}var initialCoords=ts.commandResults.map(function(){return{left:selectedCardRect.left-rect.left,top:selectedCardRect.top-rect.top};});setCardsCoords(initialCoords);setTransitionRunning(true);},[selectedCardRect,ts===null||ts===void 0?void 0:ts.commandResults]);(0,react.useEffect)(function(){if(cardsCoords.length>0){var targetCoords=cardsCoords.map(function(_,i){return{left:0,top:i*(cardHeight+cardGap)};});if(cardsCoords.length===targetCoords.length&&cardsCoords.every(function(_ref,i){var left=_ref.left,top=_ref.top;return targetCoords[i].left===left&&targetCoords[i].top===top;})){return;}setTimeout(function(){setCardsCoords(targetCoords);setTimeout(function(){setTransitionRunning(false);},theme.transitions.duration.enteringScreen);},10);}},[cardsCoords,theme.transitions.duration.enteringScreen]);var zIndex=theme.zIndex.modal+1;var cards=(0,react.useMemo)(function(){if(!(ps&&ts&&ts.commandResults&&ts.commandResults.length>0&&cardsCoords.length>0)){return null;}return ts.commandResults.map(function(rs,i){return/*#__PURE__*/(0,jsx_runtime.jsx)(Card,{sx:{position:'absolute',translate:"".concat(cardsCoords[i].left,"px ").concat(cardsCoords[i].top,"px"),zIndex:zIndex,transition:theme.transitions.create(['translate'],{easing:theme.transitions.easing.sharp,duration:theme.transitions.duration.enteringScreen})},children:/*#__PURE__*/(0,jsx_runtime.jsx)(CommandResultItem,{ps:ps,ts:ts,rs:rs,onSelectCommandResult:setSelectedCommandResult,selected:rs===selectedCommandResult})},rs.id);});},[ps,ts,cardsCoords,zIndex,theme.transitions,selectedCommandResult]);if(loadingError){return/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{children:"Error"});}return/*#__PURE__*/(0,jsx_runtime.jsxs)(jsx_runtime.Fragment,{children:[ps&&ts&&ts.commandResults&&ts.commandResults.length>0&&/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{sx:{position:'fixed',top:0,bottom:0,right:sidePanelWidth,width:"calc(100% - ".concat(sidePanelWidth,"px)"),overflowX:'visible',overflowY:transitionRunning?'visible':'auto',display:'flex',flexDirection:'column',alignItems:'center',justifyContent:'center',padding:'25px 0',zIndex:zIndex},onClick:props.onClose,children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{onClick:function onClick(e){return e.stopPropagation();},position:"relative",width:cardWidth,height:cardHeight*ts.commandResults.length+cardGap*(ts.commandResults.length-1),ref:cardsContainerElem,children:cards})}),/*#__PURE__*/(0,jsx_runtime.jsx)(styles_ThemeProvider_ThemeProvider,{theme:theme_dark,children:/*#__PURE__*/(0,jsx_runtime.jsx)(Drawer_Drawer,{sx:{zIndex:1300},anchor:"right",open:props.rs!==undefined,onClose:props.onClose,children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{width:sidePanelWidth,height:"100%",children:loading?/*#__PURE__*/(0,jsx_runtime.jsx)(Loading,{}):/*#__PURE__*/(0,jsx_runtime.jsx)(SidePanel,{provider:nodeData,onClose:props.onClose})})})})]});}); ;// CONCATENATED MODULE: ./src/components/targets-view/TargetDetailsDrawer.tsx var MyProvider=/*#__PURE__*/function(){function MyProvider(ts){var _this$ts,_this$ts$lastValidate,_this$ts$lastValidate2,_this=this;classCallCheck_classCallCheck(this,MyProvider);this.ts=void 0;this.diffStatus=void 0;this.ts=ts;this.diffStatus=new DiffStatus();(_this$ts=this.ts)===null||_this$ts===void 0?void 0:(_this$ts$lastValidate=_this$ts.lastValidateResult)===null||_this$ts$lastValidate===void 0?void 0:(_this$ts$lastValidate2=_this$ts$lastValidate.drift)===null||_this$ts$lastValidate2===void 0?void 0:_this$ts$lastValidate2.forEach(function(co){_this.diffStatus.addChangedObject(co);});}createClass_createClass(MyProvider,[{key:"buildSidePanelTabs",value:function buildSidePanelTabs(){var _this$ts$lastValidate3,_this$ts$lastValidate4,_this$ts$lastValidate5,_this$ts$lastValidate6;if(!this.ts){return[];}var tabs=[{label:"Summary",content:this.buildSummaryTab()}];if(this.ts.target)if(this.diffStatus.changedObjects.length){tabs.push({label:"Drift",content:/*#__PURE__*/(0,jsx_runtime.jsx)(ChangesTable,{diffStatus:this.diffStatus})});}if((_this$ts$lastValidate3=this.ts.lastValidateResult)!==null&&_this$ts$lastValidate3!==void 0&&(_this$ts$lastValidate4=_this$ts$lastValidate3.errors)!==null&&_this$ts$lastValidate4!==void 0&&_this$ts$lastValidate4.length){tabs.push({label:"Errors",content:/*#__PURE__*/(0,jsx_runtime.jsx)(ErrorsTable,{errors:this.ts.lastValidateResult.errors})});}if((_this$ts$lastValidate5=this.ts.lastValidateResult)!==null&&_this$ts$lastValidate5!==void 0&&(_this$ts$lastValidate6=_this$ts$lastValidate5.warnings)!==null&&_this$ts$lastValidate6!==void 0&&_this$ts$lastValidate6.length){tabs.push({label:"Warnings",content:/*#__PURE__*/(0,jsx_runtime.jsx)(ErrorsTable,{errors:this.ts.lastValidateResult.warnings})});}return tabs;}},{key:"buildSummaryTab",value:function buildSummaryTab(){var _this$ts2,_this$ts3,_this$ts4,_this$ts4$lastValidat,_this$ts4$lastValidat2,_this$ts5,_this$ts5$lastValidat,_this$ts5$lastValidat2,_this$ts6,_this$ts6$lastValidat,_this$ts6$lastValidat2;var props=[{name:"Target Name",value:this.getTargetName()},{name:"Discriminator",value:(_this$ts2=this.ts)===null||_this$ts2===void 0?void 0:_this$ts2.target.discriminator}];if((_this$ts3=this.ts)!==null&&_this$ts3!==void 0&&_this$ts3.lastValidateResult){props.push({name:"Ready",value:this.ts.lastValidateResult.ready+""});}if((_this$ts4=this.ts)!==null&&_this$ts4!==void 0&&(_this$ts4$lastValidat=_this$ts4.lastValidateResult)!==null&&_this$ts4$lastValidat!==void 0&&(_this$ts4$lastValidat2=_this$ts4$lastValidat.errors)!==null&&_this$ts4$lastValidat2!==void 0&&_this$ts4$lastValidat2.length){props.push({name:"Errors",value:this.ts.lastValidateResult.errors.length+""});}if((_this$ts5=this.ts)!==null&&_this$ts5!==void 0&&(_this$ts5$lastValidat=_this$ts5.lastValidateResult)!==null&&_this$ts5$lastValidat!==void 0&&(_this$ts5$lastValidat2=_this$ts5$lastValidat.warnings)!==null&&_this$ts5$lastValidat2!==void 0&&_this$ts5$lastValidat2.length){props.push({name:"Warnings",value:this.ts.lastValidateResult.warnings.length+""});}if((_this$ts6=this.ts)!==null&&_this$ts6!==void 0&&(_this$ts6$lastValidat=_this$ts6.lastValidateResult)!==null&&_this$ts6$lastValidat!==void 0&&(_this$ts6$lastValidat2=_this$ts6$lastValidat.drift)!==null&&_this$ts6$lastValidat2!==void 0&&_this$ts6$lastValidat2.length){props.push({name:"Drifted Objects",value:this.ts.lastValidateResult.drift.length+""});}return/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(PropertiesTable,{properties:props})});}},{key:"getTargetName",value:function getTargetName(){if(!this.ts){return"";}var name="";if(this.ts.target.targetName){name=this.ts.target.targetName;}return name;}},{key:"buildSidePanelTitle",value:function buildSidePanelTitle(){return this.getTargetName();}}]);return MyProvider;}();var TargetDetailsDrawer=/*#__PURE__*/react.memo(function(props){return/*#__PURE__*/(0,jsx_runtime.jsx)(styles_ThemeProvider_ThemeProvider,{theme:theme_dark,children:/*#__PURE__*/(0,jsx_runtime.jsx)(Drawer_Drawer,{sx:{zIndex:1300},anchor:"right",open:props.ts!==undefined,onClose:props.onClose,ModalProps:{BackdropProps:{invisible:true}},children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{width:"600px",height:"100%",children:/*#__PURE__*/(0,jsx_runtime.jsx)(SidePanel,{provider:new MyProvider(props.ts),onClose:props.onClose})})})});}); ;// CONCATENATED MODULE: ./src/components/targets-view/TargetsView.tsx @@ -61438,9 +61444,9 @@ function FilterNode(node,activeFilters){var _node$diffStatus,_node$healthStatus, ;// CONCATENATED MODULE: ./src/components/result-view/CommandResultTree.tsx var CommandResultTree=function CommandResultTree(props){var theme=styles_useTheme_useTheme();var _useState=(0,react.useState)(["root"]),_useState2=slicedToArray_slicedToArray(_useState,2),expanded=_useState2[0],setExpanded=_useState2[1];var _useMemo=(0,react.useMemo)(function(){if(!props.commandResultProps){return[undefined,undefined];}var builder=new NodeBuilder(props.commandResultProps);return builder.buildRoot();},[props.commandResultProps]),_useMemo2=slicedToArray_slicedToArray(_useMemo,1),rootNode=_useMemo2[0];var handleToggle=function handleToggle(event,nodeIds){setExpanded(nodeIds);};var handleDoubleClick=function handleDoubleClick(e,node){if(expanded.includes(node.id)){setExpanded(expanded.filter(function(item){return item!==node.id;}));}else{setExpanded([].concat(toConsumableArray_toConsumableArray(expanded),[node.id]));}e.stopPropagation();};var handleItemClick=function handleItemClick(e,node){props.onSelectNode(node);e.stopPropagation();};var renderTree=function renderTree(nodes){if(!FilterNode(nodes,props.activeFilters)){return null;}return/*#__PURE__*/(0,jsx_runtime.jsx)(TreeItem_TreeItem,{nodeId:nodes.id,label:/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",alignItems:"center",onClick:function onClick(e){return handleItemClick(e,nodes);},pl:"22px",height:"100%",flex:"1 1 auto",position:"relative",children:[nodes.children.length!==0&&/*#__PURE__*/(0,jsx_runtime.jsx)(Divider_Divider,{orientation:"vertical",sx:{height:'40px',position:'absolute',left:0}}),nodes.buildTreeItem(nodes.children.length!==0)]}),sx:{'& .MuiTreeItem-content':{height:'78px',borderBottom:"0.5px solid ".concat(theme.palette.secondary.main),padding:0,overflow:'hidden','& .MuiTreeItem-iconContainer':{width:'50px',height:'50px',flex:'0 0 auto',margin:0,padding:0,display:nodes.children.length!==0?'flex':'none',justifyContent:'center',alignItems:'center'},'& .MuiTreeItem-label':{height:'100%',margin:0,padding:0,flex:'1 1 auto',display:'flex',alignItems:'center'}},'& .MuiTreeItem-group':{margin:'0 0 0 38px'}},onDoubleClick:function onDoubleClick(e){return handleDoubleClick(e,nodes);},children:Array.isArray(nodes.children)?nodes.children.map(function(node){return renderTree(node);}):null},nodes.id);};if(!rootNode){return/*#__PURE__*/(0,jsx_runtime.jsx)(Loading,{});}return/*#__PURE__*/(0,jsx_runtime.jsx)(Paper_Paper,{sx:{padding:'20px 40px'},children:/*#__PURE__*/(0,jsx_runtime.jsx)(TreeView_TreeView,{expanded:expanded,onNodeToggle:handleToggle,"aria-label":"rich object",defaultCollapseIcon:/*#__PURE__*/(0,jsx_runtime.jsx)(TriangleDownIcon,{}),defaultExpandIcon:/*#__PURE__*/(0,jsx_runtime.jsx)(TriangleRightIcon,{}),sx:{width:"100%"},children:renderTree(rootNode)})});};/* harmony default export */ var result_view_CommandResultTree = (CommandResultTree); ;// CONCATENATED MODULE: ./src/components/result-view/CommandResultView.tsx -function commandResultLoader(_x){return _commandResultLoader.apply(this,arguments);}function _commandResultLoader(){_commandResultLoader=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee(_ref){var params,result,shortNames,rs;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee$(_context){while(1)switch(_context.prev=_context.next){case 0:params=_ref.params;result=api.getResult(params.id);shortNames=api.getShortNames();rs=api.getResult(params.id);_context.next=6;return shortNames;case 6:_context.t0=_context.sent;_context.next=9;return rs;case 9:_context.t1=_context.sent;_context.next=12;return result;case 12:_context.t2=_context.sent;return _context.abrupt("return",{shortNames:_context.t0,summary:_context.t1,commandResult:_context.t2});case 14:case"end":return _context.stop();}},_callee);}));return _commandResultLoader.apply(this,arguments);}var FilterCheckbox=function FilterCheckbox(props){var text=props.text,checked=props.checked,Icon=props.Icon,onChange=props.onChange;return/*#__PURE__*/(0,jsx_runtime.jsx)(FormControlLabel_FormControlLabel,{sx:{display:'flex',justifyContent:'center',alignItems:'center',margin:0,padding:0},control:/*#__PURE__*/(0,jsx_runtime.jsx)(Checkbox_Checkbox,{checked:checked,sx:{display:'flex',justifyContent:'center',alignItems:'center'},onChange:onChange,icon:/*#__PURE__*/(0,jsx_runtime.jsx)(CheckboxIcon,{}),checkedIcon:/*#__PURE__*/(0,jsx_runtime.jsx)(CheckboxCheckedIcon,{})}),slotProps:{typography:{sx:{display:'flex',justifyContent:'center',alignItems:'center',gap:'10px'}}},label:/*#__PURE__*/(0,jsx_runtime.jsxs)(jsx_runtime.Fragment,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"h2",children:text}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{flex:"0 0 auto",display:"flex",alignItems:"center",justifyContent:"center",children:/*#__PURE__*/(0,jsx_runtime.jsx)(Icon,{})})]})});};function DetailsDrawer(props){return/*#__PURE__*/(0,jsx_runtime.jsx)(styles_ThemeProvider_ThemeProvider,{theme:theme_dark,children:/*#__PURE__*/(0,jsx_runtime.jsx)(Drawer_Drawer,{sx:{zIndex:1300},anchor:"right",open:props.nodeData!==undefined,onClose:props.onClose,ModalProps:{BackdropProps:{invisible:true}},children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{width:"720px",height:"100%",children:/*#__PURE__*/(0,jsx_runtime.jsx)(SidePanel,{provider:props.nodeData,onClose:props.onClose})})})});}var defaultFilters={onlyImportant:false,onlyChanged:false,onlyWithErrorsOrWarnings:false};var CommandResultView=function CommandResultView(){var _context$filters,_context$filters2,_context$filters3;var context=useAppOutletContext();var commandResultProps=useLoaderData();var _useState=(0,react.useState)(),_useState2=slicedToArray_slicedToArray(_useState,2),sidePanelNode=_useState2[0],setSidePanelNode=_useState2[1];var divider=/*#__PURE__*/(0,jsx_runtime.jsx)(Divider_Divider,{orientation:"vertical",sx:{height:'40px',margin:'0 20px 0 30px'}});var handleFilterChange=function handleFilterChange(filter){return function(_,checked){context.setFilters(function(fs){return _objectSpread2(_objectSpread2({},fs||defaultFilters),{},defineProperty_defineProperty({},filter,checked));});};};return/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{width:"100%",height:"100%",display:"flex",flexDirection:"column",overflow:"hidden",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(DetailsDrawer,{nodeData:sidePanelNode,onClose:function onClose(){return setSidePanelNode(undefined);}}),/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",alignItems:"center",minHeight:"70px",p:"0 40px",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(FilterCheckbox,{text:"Only important",checked:!!((_context$filters=context.filters)!==null&&_context$filters!==void 0&&_context$filters.onlyImportant),Icon:StarIcon,onChange:handleFilterChange('onlyImportant')}),divider,/*#__PURE__*/(0,jsx_runtime.jsx)(FilterCheckbox,{text:"Only with changes",checked:!!((_context$filters2=context.filters)!==null&&_context$filters2!==void 0&&_context$filters2.onlyChanged),Icon:ChangesIcon,onChange:handleFilterChange('onlyChanged')}),divider,/*#__PURE__*/(0,jsx_runtime.jsx)(FilterCheckbox,{text:"Only with errors and warnings",checked:!!((_context$filters3=context.filters)!==null&&_context$filters3!==void 0&&_context$filters3.onlyWithErrorsOrWarnings),Icon:WarningSignIcon,onChange:handleFilterChange('onlyWithErrorsOrWarnings')})]}),/*#__PURE__*/(0,jsx_runtime.jsx)(Divider_Divider,{sx:{margin:'0 40px'}}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{p:"25px 40px",overflow:"auto",children:/*#__PURE__*/(0,jsx_runtime.jsx)(result_view_CommandResultTree,{commandResultProps:commandResultProps,onSelectNode:setSidePanelNode,activeFilters:context.filters})})]});}; +function doLoadCommandResult(_x,_x2){return _doLoadCommandResult.apply(this,arguments);}function _doLoadCommandResult(){_doLoadCommandResult=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee(api,resultId){var shortNames,rs,result;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee$(_context){while(1)switch(_context.prev=_context.next){case 0:_context.next=2;return api.getShortNames();case 2:shortNames=_context.sent;_context.next=5;return api.getResultSummary(resultId);case 5:rs=_context.sent;_context.next=8;return api.getResult(resultId);case 8:result=_context.sent;return _context.abrupt("return",{shortNames:shortNames,summary:rs,commandResult:result});case 10:case"end":return _context.stop();}},_callee);}));return _doLoadCommandResult.apply(this,arguments);}var FilterCheckbox=function FilterCheckbox(props){var text=props.text,checked=props.checked,Icon=props.Icon,onChange=props.onChange;return/*#__PURE__*/(0,jsx_runtime.jsx)(FormControlLabel_FormControlLabel,{sx:{display:'flex',justifyContent:'center',alignItems:'center',margin:0,padding:0},control:/*#__PURE__*/(0,jsx_runtime.jsx)(Checkbox_Checkbox,{checked:checked,sx:{display:'flex',justifyContent:'center',alignItems:'center'},onChange:onChange,icon:/*#__PURE__*/(0,jsx_runtime.jsx)(CheckboxIcon,{}),checkedIcon:/*#__PURE__*/(0,jsx_runtime.jsx)(CheckboxCheckedIcon,{})}),slotProps:{typography:{sx:{display:'flex',justifyContent:'center',alignItems:'center',gap:'10px'}}},label:/*#__PURE__*/(0,jsx_runtime.jsxs)(jsx_runtime.Fragment,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"h2",children:text}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{flex:"0 0 auto",display:"flex",alignItems:"center",justifyContent:"center",children:/*#__PURE__*/(0,jsx_runtime.jsx)(Icon,{})})]})});};function DetailsDrawer(props){return/*#__PURE__*/(0,jsx_runtime.jsx)(styles_ThemeProvider_ThemeProvider,{theme:theme_dark,children:/*#__PURE__*/(0,jsx_runtime.jsx)(Drawer_Drawer,{sx:{zIndex:1300},anchor:"right",open:props.nodeData!==undefined,onClose:props.onClose,ModalProps:{BackdropProps:{invisible:true}},children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{width:"720px",height:"100%",children:/*#__PURE__*/(0,jsx_runtime.jsx)(SidePanel,{provider:props.nodeData,onClose:props.onClose})})})});}var defaultFilters={onlyImportant:false,onlyChanged:false,onlyWithErrorsOrWarnings:false};var CommandResultView=function CommandResultView(){var _context$filters,_context$filters2,_context$filters3;var context=useAppOutletContext();var api=(0,react.useContext)(ApiContext);var _useState=(0,react.useState)(),_useState2=slicedToArray_slicedToArray(_useState,2),sidePanelNode=_useState2[0],setSidePanelNode=_useState2[1];var _useParams=useParams(),id=_useParams.id;var _useLoadingHelper=useLoadingHelper(function(){if(!id){return Promise.resolve(undefined);}return doLoadCommandResult(api,id);},[id]),_useLoadingHelper2=slicedToArray_slicedToArray(_useLoadingHelper,3),loading=_useLoadingHelper2[0],loadingError=_useLoadingHelper2[1],commandResultProps=_useLoadingHelper2[2];var divider=/*#__PURE__*/(0,jsx_runtime.jsx)(Divider_Divider,{orientation:"vertical",sx:{height:'40px',margin:'0 20px 0 30px'}});var handleFilterChange=function handleFilterChange(filter){return function(_,checked){context.setFilters(function(fs){return _objectSpread2(_objectSpread2({},fs||defaultFilters),{},defineProperty_defineProperty({},filter,checked));});};};if(loading){return/*#__PURE__*/(0,jsx_runtime.jsx)(Loading,{});}else if(loadingError){return/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{children:"Error"});}return/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{width:"100%",height:"100%",display:"flex",flexDirection:"column",overflow:"hidden",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(DetailsDrawer,{nodeData:sidePanelNode,onClose:function onClose(){return setSidePanelNode(undefined);}}),/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",alignItems:"center",minHeight:"70px",p:"0 40px",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(FilterCheckbox,{text:"Only important",checked:!!((_context$filters=context.filters)!==null&&_context$filters!==void 0&&_context$filters.onlyImportant),Icon:StarIcon,onChange:handleFilterChange('onlyImportant')}),divider,/*#__PURE__*/(0,jsx_runtime.jsx)(FilterCheckbox,{text:"Only with changes",checked:!!((_context$filters2=context.filters)!==null&&_context$filters2!==void 0&&_context$filters2.onlyChanged),Icon:ChangesIcon,onChange:handleFilterChange('onlyChanged')}),divider,/*#__PURE__*/(0,jsx_runtime.jsx)(FilterCheckbox,{text:"Only with errors and warnings",checked:!!((_context$filters3=context.filters)!==null&&_context$filters3!==void 0&&_context$filters3.onlyWithErrorsOrWarnings),Icon:WarningSignIcon,onChange:handleFilterChange('onlyWithErrorsOrWarnings')})]}),/*#__PURE__*/(0,jsx_runtime.jsx)(Divider_Divider,{sx:{margin:'0 40px'}}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{p:"25px 40px",overflow:"auto",children:/*#__PURE__*/(0,jsx_runtime.jsx)(result_view_CommandResultTree,{commandResultProps:commandResultProps,onSelectNode:setSidePanelNode,activeFilters:context.filters})})]});}; ;// CONCATENATED MODULE: ./src/components/Router.tsx -function ErrorPage(){var error=useRouteError();return/*#__PURE__*/(0,jsx_runtime.jsxs)("div",{id:"error-page",children:[/*#__PURE__*/(0,jsx_runtime.jsx)("h1",{children:"Oops!"}),/*#__PURE__*/(0,jsx_runtime.jsx)("p",{children:"Sorry, an unexpected error has occurred."}),/*#__PURE__*/(0,jsx_runtime.jsx)("p",{children:/*#__PURE__*/(0,jsx_runtime.jsx)("i",{children:error.statusText||error.message})})]});}var Router_Router=createHashRouter([{path:"/",element:/*#__PURE__*/(0,jsx_runtime.jsx)(components_App,{}),errorElement:/*#__PURE__*/(0,jsx_runtime.jsx)(ErrorPage,{}),children:[{path:"targets",element:/*#__PURE__*/(0,jsx_runtime.jsx)(TargetsView,{}),errorElement:/*#__PURE__*/(0,jsx_runtime.jsx)(ErrorPage,{})},{path:"results/:id",element:/*#__PURE__*/(0,jsx_runtime.jsx)(CommandResultView,{}),loader:commandResultLoader,errorElement:/*#__PURE__*/(0,jsx_runtime.jsx)(ErrorPage,{})}]}]); +function ErrorPage(){var error=useRouteError();return/*#__PURE__*/(0,jsx_runtime.jsxs)("div",{id:"error-page",children:[/*#__PURE__*/(0,jsx_runtime.jsx)("h1",{children:"Oops!"}),/*#__PURE__*/(0,jsx_runtime.jsx)("p",{children:"Sorry, an unexpected error has occurred."}),/*#__PURE__*/(0,jsx_runtime.jsx)("p",{children:/*#__PURE__*/(0,jsx_runtime.jsx)("i",{children:error.statusText||error.message})})]});}var Router_Router=createHashRouter([{path:"/",element:/*#__PURE__*/(0,jsx_runtime.jsx)(components_App,{}),errorElement:/*#__PURE__*/(0,jsx_runtime.jsx)(ErrorPage,{}),children:[{path:"targets",element:/*#__PURE__*/(0,jsx_runtime.jsx)(TargetsView,{}),errorElement:/*#__PURE__*/(0,jsx_runtime.jsx)(ErrorPage,{})},{path:"results/:id",element:/*#__PURE__*/(0,jsx_runtime.jsx)(CommandResultView,{}),errorElement:/*#__PURE__*/(0,jsx_runtime.jsx)(ErrorPage,{})}]}]); ;// CONCATENATED MODULE: ./src/index.tsx var root=client.createRoot(document.getElementById('root'));root.render(/*#__PURE__*/(0,jsx_runtime.jsx)(react.StrictMode,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(RouterProvider,{router:Router_Router})}));// If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) @@ -61449,4 +61455,4 @@ src_reportWebVitals(); }(); /******/ })() ; -//# sourceMappingURL=main.82849393.js.map \ No newline at end of file +//# sourceMappingURL=main.1b77f1d8.js.map \ No newline at end of file From 01ad4e06194ad4309409d76fbd854a7aeea66d52 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Jun 2023 16:23:52 +0200 Subject: [PATCH 1826/2916] chore(deps): Bump github.com/gin-gonic/gin from 1.9.0 to 1.9.1 (#562) Bumps [github.com/gin-gonic/gin](https://github.com/gin-gonic/gin) from 1.9.0 to 1.9.1. - [Release notes](https://github.com/gin-gonic/gin/releases) - [Changelog](https://github.com/gin-gonic/gin/blob/master/CHANGELOG.md) - [Commits](https://github.com/gin-gonic/gin/compare/v1.9.0...v1.9.1) --- updated-dependencies: - dependency-name: github.com/gin-gonic/gin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 10 +++++----- go.sum | 19 +++++++++++-------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/go.mod b/go.mod index 07201c049..5833c1fdf 100644 --- a/go.mod +++ b/go.mod @@ -61,7 +61,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/sts v1.19.0 github.com/aws/smithy-go v1.13.5 github.com/dimchansky/utfbom v1.1.1 - github.com/gin-gonic/gin v1.9.0 + github.com/gin-gonic/gin v1.9.1 github.com/go-git/go-git/v5 v5.7.0 github.com/go-logr/logr v1.2.4 github.com/google/uuid v1.3.0 @@ -111,7 +111,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.10 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver v3.5.1+incompatible // indirect - github.com/bytedance/sonic v1.8.0 // indirect + github.com/bytedance/sonic v1.9.1 // indirect github.com/cenkalti/backoff/v3 v3.2.2 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect @@ -148,7 +148,7 @@ require ( github.com/go-openapi/swag v0.22.3 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/goccy/go-json v0.10.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect @@ -179,7 +179,7 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/karrick/godirwalk v1.17.0 // indirect github.com/klauspost/compress v1.16.5 // indirect - github.com/klauspost/cpuid/v2 v2.0.9 // indirect + github.com/klauspost/cpuid/v2 v2.2.4 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect @@ -241,7 +241,7 @@ require ( go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.8.0 // indirect go.uber.org/zap v1.24.0 // indirect - golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect + golang.org/x/arch v0.3.0 // indirect golang.org/x/mod v0.10.0 // indirect golang.org/x/oauth2 v0.7.0 // indirect golang.org/x/time v0.3.0 // indirect diff --git a/go.sum b/go.sum index ff2d7461d..7f7709829 100644 --- a/go.sum +++ b/go.sum @@ -165,8 +165,8 @@ github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembj github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA= -github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= +github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= @@ -281,8 +281,8 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= -github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8= -github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k= +github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= +github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= @@ -349,8 +349,8 @@ github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= -github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= -github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godror/godror v0.24.2/go.mod h1:wZv/9vPiUib6tkoDl+AZ/QLf5YZgMravZ7jxH2eQWAE= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -564,8 +564,9 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI= github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= +github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/kluctl/go-embed-python v0.0.0-3.10.9-20230206-2 h1:K96SwIr8MzBQ3kFFz2H/pA2y+EEk04vZ4fWj/YZghBU= github.com/kluctl/go-embed-python v0.0.0-3.10.9-20230206-2/go.mod h1:vzngsPshNKUtq0gxkYQKNJafrcH7Qy7Qt6yGNt7JmQI= github.com/kluctl/go-jinja2 v0.0.0-20230428103343-a832225dc94c h1:qAIvhYamCEU/tY6NaENEIQCynGV5sdON7zgZKnbrhhw= @@ -919,8 +920,9 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= +golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -1122,6 +1124,7 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= From 4214363906da18c36e89631c74b15287362c7e2b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Jun 2023 16:24:31 +0200 Subject: [PATCH 1827/2916] chore(deps): Bump golang.org/x/sys from 0.8.0 to 0.9.0 (#563) Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.8.0 to 0.9.0. - [Commits](https://github.com/golang/sys/compare/v0.8.0...v0.9.0) --- updated-dependencies: - dependency-name: golang.org/x/sys dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 5833c1fdf..72a25c589 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,7 @@ require ( golang.org/x/crypto v0.9.0 golang.org/x/net v0.10.0 golang.org/x/sync v0.2.0 - golang.org/x/sys v0.8.0 + golang.org/x/sys v0.9.0 golang.org/x/term v0.8.0 golang.org/x/text v0.9.0 helm.sh/helm/v3 v3.12.0 diff --git a/go.sum b/go.sum index 7f7709829..6754581c7 100644 --- a/go.sum +++ b/go.sum @@ -1136,8 +1136,8 @@ golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= +golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= From dbcca4081ae58a542bedcfb0049621d9a1b6ea0f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Jun 2023 16:24:46 +0200 Subject: [PATCH 1828/2916] chore(deps): Bump golang.org/x/text from 0.9.0 to 0.10.0 (#564) Bumps [golang.org/x/text](https://github.com/golang/text) from 0.9.0 to 0.10.0. - [Release notes](https://github.com/golang/text/releases) - [Commits](https://github.com/golang/text/compare/v0.9.0...v0.10.0) --- updated-dependencies: - dependency-name: golang.org/x/text dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 72a25c589..5bc40aef5 100644 --- a/go.mod +++ b/go.mod @@ -39,7 +39,7 @@ require ( golang.org/x/sync v0.2.0 golang.org/x/sys v0.9.0 golang.org/x/term v0.8.0 - golang.org/x/text v0.9.0 + golang.org/x/text v0.10.0 helm.sh/helm/v3 v3.12.0 k8s.io/api v0.27.2 k8s.io/apiextensions-apiserver v0.27.2 diff --git a/go.sum b/go.sum index 6754581c7..13ae2b76b 100644 --- a/go.sum +++ b/go.sum @@ -1161,8 +1161,8 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= +golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From a779e4aeea0ad49662a53c49bda81973dabd8b2b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Jun 2023 16:25:04 +0200 Subject: [PATCH 1829/2916] chore(deps): Bump github.com/aws/aws-sdk-go-v2/config (#567) Bumps [github.com/aws/aws-sdk-go-v2/config](https://github.com/aws/aws-sdk-go-v2) from 1.18.25 to 1.18.26. - [Release notes](https://github.com/aws/aws-sdk-go-v2/releases) - [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go-v2/compare/config/v1.18.25...config/v1.18.26) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go-v2/config dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 22 +++++++++++----------- go.sum | 41 ++++++++++++++++++++++------------------- 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/go.mod b/go.mod index 5bc40aef5..26ad793f4 100644 --- a/go.mod +++ b/go.mod @@ -54,11 +54,11 @@ require ( filippo.io/age v1.1.1 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.1 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 - github.com/aws/aws-sdk-go-v2 v1.18.0 - github.com/aws/aws-sdk-go-v2/config v1.18.25 - github.com/aws/aws-sdk-go-v2/credentials v1.13.24 + github.com/aws/aws-sdk-go-v2 v1.18.1 + github.com/aws/aws-sdk-go-v2/config v1.18.26 + github.com/aws/aws-sdk-go-v2/credentials v1.13.25 github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.8 - github.com/aws/aws-sdk-go-v2/service/sts v1.19.0 + github.com/aws/aws-sdk-go-v2/service/sts v1.19.1 github.com/aws/smithy-go v1.13.5 github.com/dimchansky/utfbom v1.1.1 github.com/gin-gonic/gin v1.9.1 @@ -101,14 +101,14 @@ require ( github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903 // indirect github.com/acomagu/bufpipe v1.0.4 // indirect github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.3.34 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.4 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.34 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.28 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.3.35 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.28 // indirect github.com/aws/aws-sdk-go-v2/service/kms v1.17.5 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.12.10 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.10 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.12.11 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.11 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver v3.5.1+incompatible // indirect github.com/bytedance/sonic v1.9.1 // indirect diff --git a/go.sum b/go.sum index 13ae2b76b..9a4d94557 100644 --- a/go.sum +++ b/go.sum @@ -115,34 +115,37 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go-v2 v1.16.7/go.mod h1:6CpKuLXg2w7If3ABZCl/qZ6rEgwtjZTn4eAf4RcEyuw= -github.com/aws/aws-sdk-go-v2 v1.18.0 h1:882kkTpSFhdgYRKVZ/VCgf7sd0ru57p2JCxz4/oN5RY= github.com/aws/aws-sdk-go-v2 v1.18.0/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= -github.com/aws/aws-sdk-go-v2/config v1.18.25 h1:JuYyZcnMPBiFqn87L2cRppo+rNwgah6YwD3VuyvaW6Q= -github.com/aws/aws-sdk-go-v2/config v1.18.25/go.mod h1:dZnYpD5wTW/dQF0rRNLVypB396zWCcPiBIvdvSWHEg4= -github.com/aws/aws-sdk-go-v2/credentials v1.13.24 h1:PjiYyls3QdCrzqUN35jMWtUK1vqVZ+zLfdOa/UPFDp0= -github.com/aws/aws-sdk-go-v2/credentials v1.13.24/go.mod h1:jYPYi99wUOPIFi0rhiOvXeSEReVOzBqFNOX5bXYoG2o= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3 h1:jJPgroehGvjrde3XufFIJUZVK5A2L9a3KwSFgKy9n8w= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3/go.mod h1:4Q0UFP0YJf0NrsEuEYHpM9fTSEVnD16Z3uyEF7J9JGM= +github.com/aws/aws-sdk-go-v2 v1.18.1 h1:+tefE750oAb7ZQGzla6bLkOwfcQCEtC5y2RqoqCeqKo= +github.com/aws/aws-sdk-go-v2 v1.18.1/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= +github.com/aws/aws-sdk-go-v2/config v1.18.26 h1:ivCHcSmKd1+9rBlqVsxZHB35eCW88KWbMdG2VL3BuBw= +github.com/aws/aws-sdk-go-v2/config v1.18.26/go.mod h1:NVmd//z/PNl7U+ZU2EnuffxOA060JWzgbH3BnqQrUoY= +github.com/aws/aws-sdk-go-v2/credentials v1.13.25 h1:5wROoMcUC7nAE66e0b3IIht6Tos76M4HC+GQw8MeqxU= +github.com/aws/aws-sdk-go-v2/credentials v1.13.25/go.mod h1:W9I2660WXSwZQ23mM1Ks72+UGeyirIxuU7/KzN7daeA= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.4 h1:LxK/bitrAr4lnh9LnIS6i7zWbCOdMsfzKFBI6LUCS0I= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.4/go.mod h1:E1hLXN/BL2e6YizK1zFlYd8vsfi2GTjbjBazinMmeaM= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.14/go.mod h1:kdjrMwHwrC3+FsKhNcCMJ7tUVj/8uSD5CZXeQ4wV6fM= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33 h1:kG5eQilShqmJbv11XL1VpyDbaEJzWxd4zRiCG30GSn4= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33/go.mod h1:7i0PF1ME/2eUPFcjkVIwq+DOygHEoK92t5cDqNgYbIw= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.34 h1:A5UqQEmPaCFpedKouS4v+dHCTUo2sKqhoKO9U5kxyWo= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.34/go.mod h1:wZpTEecJe0Btj3IYnDx/VlUzor9wm3fJHyvLpQF0VwY= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.8/go.mod h1:ZIV8GYoC6WLBW5KGs+o4rsc65/ozd+eQ0L31XF5VDwk= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27 h1:vFQlirhuM8lLlpI7imKOMsjdQLuN9CPi+k44F/OFVsk= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27/go.mod h1:UrHnn3QV/d0pBZ6QBAEQcqFLf8FAzLmoUfPVIueOvoM= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.34 h1:gGLG7yKaXG02/jBlg210R7VgQIotiQntNhsCFejawx8= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.34/go.mod h1:Etz2dj6UHYuw+Xw830KfzCfWGMzqvUTCjUj5b76GVDc= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27 h1:0iKliEXAcCa2qVtRs7Ot5hItA2MsufrphbRFlz1Owxo= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27/go.mod h1:EOwBD4J4S5qYszS5/3DpkejfuK+Z5/1uzICfPaZLtqw= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.28 h1:srIVS45eQuewqz6fKKu6ZGXaq6FuFg5NzgQBAM6g8Y4= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.28/go.mod h1:7VRpKQQedkfIEXb4k52I7swUnZP0wohVajJMRn3vsUw= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.35 h1:LWA+3kDM8ly001vJ1X1waCuLJdtTl48gwkPKWy9sosI= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.35/go.mod h1:0Eg1YjxE0Bhn56lx+SHJwCzhW+2JGtizsrx+lCqrfm0= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.28 h1:bkRyG4a929RCnpVSTvLM2j/T4ls015ZhhYApbmYs15s= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.28/go.mod h1:jj7znCIg05jXlaGBlFMGP8+7UN3VtCkRBG2spnmRQkU= github.com/aws/aws-sdk-go-v2/service/kms v1.17.5 h1:DkubF+BSEy0uX59+pYySzWFReN3fCcXobIO8L5Phh24= github.com/aws/aws-sdk-go-v2/service/kms v1.17.5/go.mod h1:ubAtMGRUMVv5kX8lpbeDguxZ64pR4kXTGApY4sCM0io= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.8 h1:eB91eEYUlh8+O2dXr189W8GJJd+/T8N/c5HocH2KzVo= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.8/go.mod h1:3ARttS6G6U3auEdKfaN4GlnfS9UxYE9nqub1+0YGycA= -github.com/aws/aws-sdk-go-v2/service/sso v1.12.10 h1:UBQjaMTCKwyUYwiVnUt6toEJwGXsLBI6al083tpjJzY= -github.com/aws/aws-sdk-go-v2/service/sso v1.12.10/go.mod h1:ouy2P4z6sJN70fR3ka3wD3Ro3KezSxU6eKGQI2+2fjI= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.10 h1:PkHIIJs8qvq0e5QybnZoG1K/9QTrLr9OsqCIo59jOBA= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.10/go.mod h1:AFvkxc8xfBe8XA+5St5XIHHrQQtkxqrRincx4hmMHOk= -github.com/aws/aws-sdk-go-v2/service/sts v1.19.0 h1:2DQLAKDteoEDI8zpCzqBMaZlJuoE9iTYD0gFmXVax9E= -github.com/aws/aws-sdk-go-v2/service/sts v1.19.0/go.mod h1:BgQOMsg8av8jset59jelyPW7NoZcZXLVpDsXunGDrk8= +github.com/aws/aws-sdk-go-v2/service/sso v1.12.11 h1:cNrMc266RsZJ8V1u1OQQONKcf9HmfxQFqgcpY7ZJBhY= +github.com/aws/aws-sdk-go-v2/service/sso v1.12.11/go.mod h1:HuCOxYsF21eKrerARYO6HapNeh9GBNq7fius2AcwodY= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.11 h1:h2VhtCE5PBiJefmlVCjJRSzBfFcQeAE10SXIGkXw1jQ= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.11/go.mod h1:E4VrHCPzmVB/KFXtqBGKb3c8zpbNBgKe3fisDNLAW5w= +github.com/aws/aws-sdk-go-v2/service/sts v1.19.1 h1:ehPTnLR/es8TL1fpBfq8qw9cAwOpQr47fLmZD9yhHjk= +github.com/aws/aws-sdk-go-v2/service/sts v1.19.1/go.mod h1:dp0yLPsLBOi++WTxzCjA/oZqi6NPIhoR+uF7GeMU9eg= github.com/aws/smithy-go v1.12.0/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8= github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= From b96e275f620b9ebc59985f876065e038b9629656 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Jun 2023 16:27:02 +0200 Subject: [PATCH 1830/2916] chore(deps): Bump golang.org/x/term from 0.8.0 to 0.9.0 (#565) Bumps [golang.org/x/term](https://github.com/golang/term) from 0.8.0 to 0.9.0. - [Commits](https://github.com/golang/term/compare/v0.8.0...v0.9.0) --- updated-dependencies: - dependency-name: golang.org/x/term dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 26ad793f4..e0219a7be 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( golang.org/x/net v0.10.0 golang.org/x/sync v0.2.0 golang.org/x/sys v0.9.0 - golang.org/x/term v0.8.0 + golang.org/x/term v0.9.0 golang.org/x/text v0.10.0 helm.sh/helm/v3 v3.12.0 k8s.io/api v0.27.2 diff --git a/go.sum b/go.sum index 9a4d94557..e55b9ac74 100644 --- a/go.sum +++ b/go.sum @@ -1148,8 +1148,8 @@ golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28= +golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From 755680b0d82022a3464ce407e54478167bc926f2 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 14 Jun 2023 17:16:47 +0200 Subject: [PATCH 1831/2916] fix: Fix DEPLOYED printer column (#570) --- api/v1beta1/kluctldeployment_types.go | 3 +-- config/crd/bases/gitops.kluctl.io_kluctldeployments.yaml | 5 +---- install/controller/controller/crd.yaml | 5 +---- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/api/v1beta1/kluctldeployment_types.go b/api/v1beta1/kluctldeployment_types.go index 3150f4130..4d961bb07 100644 --- a/api/v1beta1/kluctldeployment_types.go +++ b/api/v1beta1/kluctldeployment_types.go @@ -395,8 +395,7 @@ func (s *KluctlDeploymentStatus) GetLastValidateResult() (*result.ValidateResult //+kubebuilder:object:root=true //+kubebuilder:subresource:status //+kubebuilder:printcolumn:name="DryRun",type="boolean",JSONPath=".spec.dryRun",description="" -//+kubebuilder:printcolumn:name="Deployed",type="date",JSONPath=".status.lastDeployResult.command.endTime",description="" -//+kubebuilder:printcolumn:name="Pruned",type="date",JSONPath=".status.lastPruneResult.command.endTime",description="" +//+kubebuilder:printcolumn:name="Deployed",type="date",JSONPath=".status.lastDeployResult.commandInfo.endTime",description="" //+kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].status",description="" //+kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].message",description="" //+kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="" diff --git a/config/crd/bases/gitops.kluctl.io_kluctldeployments.yaml b/config/crd/bases/gitops.kluctl.io_kluctldeployments.yaml index 535e56610..7b18591d0 100644 --- a/config/crd/bases/gitops.kluctl.io_kluctldeployments.yaml +++ b/config/crd/bases/gitops.kluctl.io_kluctldeployments.yaml @@ -18,12 +18,9 @@ spec: - jsonPath: .spec.dryRun name: DryRun type: boolean - - jsonPath: .status.lastDeployResult.command.endTime + - jsonPath: .status.lastDeployResult.commandInfo.endTime name: Deployed type: date - - jsonPath: .status.lastPruneResult.command.endTime - name: Pruned - type: date - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string diff --git a/install/controller/controller/crd.yaml b/install/controller/controller/crd.yaml index b8c794f09..dd1e01c15 100644 --- a/install/controller/controller/crd.yaml +++ b/install/controller/controller/crd.yaml @@ -17,12 +17,9 @@ spec: - jsonPath: .spec.dryRun name: DryRun type: boolean - - jsonPath: .status.lastDeployResult.command.endTime + - jsonPath: .status.lastDeployResult.commandInfo.endTime name: Deployed type: date - - jsonPath: .status.lastPruneResult.command.endTime - name: Pruned - type: date - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string From 9b004cf674cddf5489f18da5e03295114dcbbde3 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 15 Jun 2023 00:18:36 +0200 Subject: [PATCH 1832/2916] fix: Set controller-runtime logger --- cmd/kluctl/commands/root.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/kluctl/commands/root.go b/cmd/kluctl/commands/root.go index 67dbc8cfe..54c1750c9 100644 --- a/cmd/kluctl/commands/root.go +++ b/cmd/kluctl/commands/root.go @@ -25,6 +25,7 @@ import ( "os" "path/filepath" "runtime/pprof" + ctrl "sigs.k8s.io/controller-runtime" "strings" "time" @@ -110,7 +111,7 @@ func redirectLogsAndStderr(ctxGetter func() context.Context) { klog.LogToStderr(false) klog.SetOutput(lr1) log.SetOutput(lr2) - //ctrl.SetLogger(klog.NewKlogr()) + ctrl.SetLogger(klog.NewKlogr()) pr, pw, err := os.Pipe() if err != nil { From 72567a230176d87a97a31b78e37749aef787d3b6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 15 Jun 2023 00:19:15 +0200 Subject: [PATCH 1833/2916] feat: Implement impersonation support in the webui --- pkg/controllers/kluctl_project.go | 2 +- pkg/k8s/client_factory.go | 23 +++++--- pkg/k8s/k8s_cluster.go | 20 ------- pkg/kluctl_project/target_context.go | 2 +- pkg/webui/auth.go | 3 ++ pkg/webui/clusteraccessor.go | 81 ++++++++++++++++++++-------- pkg/webui/server.go | 17 ++++-- pkg/webui/validator.go | 14 +++-- 8 files changed, 106 insertions(+), 56 deletions(-) diff --git a/pkg/controllers/kluctl_project.go b/pkg/controllers/kluctl_project.go index 4b426668b..45a214962 100644 --- a/pkg/controllers/kluctl_project.go +++ b/pkg/controllers/kluctl_project.go @@ -760,7 +760,7 @@ func (pt *preparedTarget) kluctlDelete(ctx context.Context, discriminator string if err != nil { return nil, err } - clientFactory, err := k8s2.NewClientFactory(ctx, restConfig) + clientFactory, err := k8s2.NewClientFactoryFromConfig(ctx, restConfig) if err != nil { return nil, err } diff --git a/pkg/k8s/client_factory.go b/pkg/k8s/client_factory.go index 255f35fb7..c530186d9 100644 --- a/pkg/k8s/client_factory.go +++ b/pkg/k8s/client_factory.go @@ -16,6 +16,7 @@ import ( "net/url" "path/filepath" "sigs.k8s.io/controller-runtime/pkg/client" + "strings" "time" ) @@ -38,7 +39,7 @@ type realClientFactory struct { httpClient *http.Client discoveryClient discovery.DiscoveryInterface - mapper meta.ResettableRESTMapper + mapper meta.RESTMapper } func (r *realClientFactory) RESTConfig() *rest.Config { @@ -91,12 +92,12 @@ func (r *realClientFactory) CloseIdleConnections() { r.httpClient.CloseIdleConnections() } -func initDiscoveryClient(ctx context.Context, config *rest.Config) (discovery.CachedDiscoveryInterface, error) { +func CreateDiscoveryClient(ctx context.Context, config *rest.Config) (discovery.CachedDiscoveryInterface, error) { apiHost, err := url.Parse(config.Host) if err != nil { return nil, err } - discoveryCacheDir := filepath.Join(utils.GetTmpBaseDir(ctx), "kube-cache/discovery", apiHost.Hostname()) + discoveryCacheDir := filepath.Join(utils.GetTmpBaseDir(ctx), "kube-cache/discovery", strings.ReplaceAll(apiHost.Host, ":", "-")) discovery2, err := disk.NewCachedDiscoveryClientForConfig(dynamic.ConfigFor(config), discoveryCacheDir, "", time.Hour*24) if err != nil { return nil, err @@ -104,7 +105,7 @@ func initDiscoveryClient(ctx context.Context, config *rest.Config) (discovery.Ca return discovery2, nil } -func NewClientFactory(ctx context.Context, configIn *rest.Config) (ClientFactory, error) { +func NewClientFactoryFromConfig(ctx context.Context, configIn *rest.Config) (ClientFactory, error) { restConfig := rest.CopyConfig(configIn) restConfig.QPS = 10 restConfig.Burst = 20 @@ -114,7 +115,7 @@ func NewClientFactory(ctx context.Context, configIn *rest.Config) (ClientFactory return nil, err } - dc, err := initDiscoveryClient(ctx, restConfig) + dc, err := CreateDiscoveryClient(ctx, restConfig) if err != nil { return nil, err } @@ -143,5 +144,15 @@ func NewClientFactoryFromDefaultConfig(ctx context.Context, context *string) (Cl return nil, err } - return NewClientFactory(ctx, config) + return NewClientFactoryFromConfig(ctx, config) +} + +func NewClientFactory(ctx context.Context, config *rest.Config, httpClient *http.Client, dc discovery.DiscoveryInterface, mapper meta.RESTMapper) (ClientFactory, error) { + return &realClientFactory{ + ctx: ctx, + config: config, + httpClient: httpClient, + discoveryClient: dc, + mapper: mapper, + }, nil } diff --git a/pkg/k8s/k8s_cluster.go b/pkg/k8s/k8s_cluster.go index 082224646..8e1ff12da 100644 --- a/pkg/k8s/k8s_cluster.go +++ b/pkg/k8s/k8s_cluster.go @@ -78,26 +78,6 @@ func (k *K8sCluster) ReadWrite() *K8sCluster { return &k2 } -func (k *K8sCluster) GetCA() []byte { - return k.clientFactory.GetCA() -} - -func (k *K8sCluster) buildLabelSelector(labels map[string]string) string { - ret := "" - - for k, v := range labels { - if len(ret) != 0 { - ret += "," - } - if v == "" { - ret += k - } else { - ret += fmt.Sprintf("%s=%s", k, v) - } - } - return ret -} - func (k *K8sCluster) doList(l client.ObjectList, namespace string, labels map[string]string) ([]*uo.UnstructuredObject, []ApiWarning, error) { apiWarnings, err := k.clients.withCClientFromPool(true, func(c client.Client) error { return c.List(k.ctx, l, client.InNamespace(namespace), client.MatchingLabels(labels)) diff --git a/pkg/kluctl_project/target_context.go b/pkg/kluctl_project/target_context.go index 4761c9a0d..57c0b0a27 100644 --- a/pkg/kluctl_project/target_context.go +++ b/pkg/kluctl_project/target_context.go @@ -98,7 +98,7 @@ func (p *LoadedKluctlProject) NewTargetContext(ctx context.Context, params Targe var k *k8s.K8sCluster if clientConfig != nil { s := status.Start(ctx, fmt.Sprintf("Initializing k8s client")) - clientFactory, err := k8s.NewClientFactory(ctx, clientConfig) + clientFactory, err := k8s.NewClientFactoryFromConfig(ctx, clientConfig) if err != nil { return nil, err } diff --git a/pkg/webui/auth.go b/pkg/webui/auth.go index fb9cc97e1..8428482cb 100644 --- a/pkg/webui/auth.go +++ b/pkg/webui/auth.go @@ -29,11 +29,13 @@ type authHandler struct { serverClient client.Client webuiSecretName string + adminRbacUser string } type User struct { Username string `json:"username"` IsAdmin bool `json:"isAdmin"` + RbacUser string `json:"RbacUser"` } func (s *authHandler) setupAuth(r gin.IRouter) error { @@ -223,6 +225,7 @@ func (s *authHandler) getAdminUserFromClaims(claims jwt.MapClaims) *User { return &User{ Username: id, IsAdmin: true, + RbacUser: s.adminRbacUser, } } diff --git a/pkg/webui/clusteraccessor.go b/pkg/webui/clusteraccessor.go index 1e44dcdbb..e83a75dec 100644 --- a/pkg/webui/clusteraccessor.go +++ b/pkg/webui/clusteraccessor.go @@ -5,10 +5,14 @@ import ( kluctlv1 "github.com/kluctl/kluctl/v2/api/v1beta1" k8s2 "github.com/kluctl/kluctl/v2/pkg/k8s" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/discovery" clientgoscheme "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" + "net/http" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/apiutil" "sync" "time" ) @@ -19,12 +23,14 @@ type clusterAccessorManager struct { } type clusterAccessor struct { - ctx context.Context - config *rest.Config - client client.Client - k *k8s2.K8sCluster - clusterId string - mutex sync.Mutex + ctx context.Context + config *rest.Config + httpClient *http.Client + scheme *runtime.Scheme + discovery discovery.DiscoveryInterface + mapper meta.RESTMapper + clusterId string + mutex sync.Mutex } func (cam *clusterAccessorManager) add(config *rest.Config) { @@ -64,6 +70,9 @@ func (ca *clusterAccessor) initClient() { } func (ca *clusterAccessor) tryInitClient() error { + ca.mutex.Lock() + defer ca.mutex.Unlock() + scheme := runtime.NewScheme() err := clientgoscheme.AddToScheme(scheme) if err != nil { @@ -73,33 +82,37 @@ func (ca *clusterAccessor) tryInitClient() error { if err != nil { return err } + ca.scheme = scheme - c, err := client.New(ca.config, client.Options{ - Scheme: scheme, - }) + httpClient, err := rest.HTTPClientFor(ca.config) if err != nil { return err } - var ns corev1.Namespace - err = c.Get(context.Background(), client.ObjectKey{Name: "kube-system"}, &ns) + ca.httpClient = httpClient + + dc, err := k8s2.CreateDiscoveryClient(context.Background(), ca.config) if err != nil { return err } + ca.discovery = dc - cf, err := k8s2.NewClientFactory(context.Background(), ca.config) + mapper, err := apiutil.NewDynamicRESTMapper(ca.config, ca.httpClient) if err != nil { return err } + ca.mapper = mapper - k, err := k8s2.NewK8sCluster(context.Background(), cf, true) + c, err := ca.getClientLocked("", nil) + if err != nil { + return err + } + + var ns corev1.Namespace + err = c.Get(context.Background(), client.ObjectKey{Name: "kube-system"}, &ns) if err != nil { return err } - ca.mutex.Lock() - defer ca.mutex.Unlock() - ca.client = c - ca.k = k ca.clusterId = string(ns.UID) return nil @@ -111,14 +124,40 @@ func (ca *clusterAccessor) getClusterId() string { return ca.clusterId } -func (ca *clusterAccessor) getClient() client.Client { +func (ca *clusterAccessor) getClient(asUser string, asGroups []string) (client.Client, error) { ca.mutex.Lock() defer ca.mutex.Unlock() - return ca.client + return ca.getClientLocked(asUser, asGroups) } -func (ca *clusterAccessor) getK() *k8s2.K8sCluster { +func (ca *clusterAccessor) getClientLocked(asUser string, asGroups []string) (client.Client, error) { + config := rest.CopyConfig(ca.config) + config.Impersonate.UserName = asUser + config.Impersonate.Groups = asGroups + + c, err := client.New(config, client.Options{ + HTTPClient: ca.httpClient, + Scheme: ca.scheme, + Mapper: ca.mapper, + }) + if err != nil { + return nil, err + } + return c, nil +} + +func (ca *clusterAccessor) getK(ctx context.Context, asUser string, asGroups []string) (*k8s2.K8sCluster, error) { ca.mutex.Lock() defer ca.mutex.Unlock() - return ca.k + + config := rest.CopyConfig(ca.config) + config.Impersonate.UserName = asUser + config.Impersonate.Groups = asGroups + + cf, err := k8s2.NewClientFactory(ctx, config, ca.httpClient, ca.discovery, ca.mapper) + if err != nil { + return nil, err + } + + return k8s2.NewK8sCluster(ctx, cf, false) } diff --git a/pkg/webui/server.go b/pkg/webui/server.go index 31b45181c..6f0fa5bde 100644 --- a/pkg/webui/server.go +++ b/pkg/webui/server.go @@ -44,6 +44,8 @@ func NewCommandResultsServer(ctx context.Context, store *results.ResultsCollecto serverClient: serverClient, } + adminUser := "kluctl-webui-admin" + adminEnabled := false if serverClient != nil { adminEnabled = true @@ -55,6 +57,7 @@ func NewCommandResultsServer(ctx context.Context, store *results.ResultsCollecto adminEnabled: adminEnabled, serverClient: serverClient, webuiSecretName: "admin-secret", + adminRbacUser: adminUser, } } @@ -62,7 +65,7 @@ func NewCommandResultsServer(ctx context.Context, store *results.ResultsCollecto ret.cam.add(config) } - ret.vam = newValidatorManager(ctx, store, ret.cam) + ret.vam = newValidatorManager(ctx, store, ret.cam, adminUser) return ret } @@ -340,17 +343,25 @@ func (s *CommandResultsServer) doSetAnnotation(c *gin.Context, aname string, ava return } + user := s.auth.getUser(c) + ca := s.cam.getForClusterId(params.Cluster) if ca == nil { _ = c.AbortWithError(http.StatusNotFound, err) return } + kc, err := ca.getClient(user.RbacUser, nil) + if err != nil { + _ = c.AbortWithError(http.StatusInternalServerError, err) + return + } + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) defer cancel() var kd kluctlv1.KluctlDeployment - err = ca.getClient().Get(ctx, client.ObjectKey{Name: params.Name, Namespace: params.Namespace}, &kd) + err = kc.Get(ctx, client.ObjectKey{Name: params.Name, Namespace: params.Namespace}, &kd) if err != nil { if errors.IsNotFound(err) { _ = c.AbortWithError(http.StatusNotFound, err) @@ -362,7 +373,7 @@ func (s *CommandResultsServer) doSetAnnotation(c *gin.Context, aname string, ava patch := client.MergeFrom(kd.DeepCopy()) metav1.SetMetaDataAnnotation(&kd.ObjectMeta, aname, avalue) - err = ca.getClient().Patch(ctx, &kd, patch, client.FieldOwner(webuiManager)) + err = kc.Patch(ctx, &kd, patch, client.FieldOwner(webuiManager)) if err != nil { _ = c.AbortWithError(http.StatusInternalServerError, err) return diff --git a/pkg/webui/validator.go b/pkg/webui/validator.go index 277d9c77b..76e7e7ff1 100644 --- a/pkg/webui/validator.go +++ b/pkg/webui/validator.go @@ -27,8 +27,9 @@ type validationWatch struct { type validatorManager struct { ctx context.Context - store results.ResultStore - cam *clusterAccessorManager + store results.ResultStore + cam *clusterAccessorManager + adminUser string validators map[ProjectTargetKey]*validatorEntry watches []*validationWatch @@ -49,11 +50,12 @@ type validatorEntry struct { mutex sync.Mutex } -func newValidatorManager(ctx context.Context, store results.ResultStore, cam *clusterAccessorManager) *validatorManager { +func newValidatorManager(ctx context.Context, store results.ResultStore, cam *clusterAccessorManager, adminUser string) *validatorManager { return &validatorManager{ ctx: ctx, store: store, cam: cam, + adminUser: adminUser, validators: map[ProjectTargetKey]*validatorEntry{}, } } @@ -244,7 +246,11 @@ func (v *validatorEntry) runOnce(summaries []result.CommandResultSummary) bool { s.FailedWithMessage("No cluster accessor for %v", v.key) return false } - k := ca.getK() + k, err := ca.getK(context.Background(), v.vm.adminUser, nil) + if err != nil { + s.FailedWithMessage("Failed to create K8sCluster: %v", err) + return false + } if k == nil { return false } From 9d5072973730eba0957c119e7a99adb05ce35ee2 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 15 Jun 2023 00:19:41 +0200 Subject: [PATCH 1834/2916] fix: Use default context when using --in-cluster --- cmd/kluctl/commands/cmd_webui.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/kluctl/commands/cmd_webui.go b/cmd/kluctl/commands/cmd_webui.go index 344882505..1c7ee15f4 100644 --- a/cmd/kluctl/commands/cmd_webui.go +++ b/cmd/kluctl/commands/cmd_webui.go @@ -84,6 +84,9 @@ func (cmd *webuiCmd) createResultStores(ctx context.Context) ([]results.ResultSt for name, _ := range kcfg.Contexts { contexts = append(contexts, name) } + } else if cmd.InCluster { + // placeholder for current context + contexts = append(contexts, "") } else { if len(cmd.Context) == 0 { // placeholder for current context From ae78653ca96fb587169b15aeec1f6c8863ed2fd5 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 15 Jun 2023 00:22:19 +0200 Subject: [PATCH 1835/2916] feat: Add webui deployment --- hack/prepare-release.sh | 6 ++- install/webui/.kluctl.yaml | 9 ++++ install/webui/deployment.yaml | 3 ++ install/webui/webui/admin-rbac.yaml | 41 ++++++++++++++++++ install/webui/webui/deployment.yaml | 65 +++++++++++++++++++++++++++++ install/webui/webui/webui-rbac.yaml | 48 +++++++++++++++++++++ 6 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 install/webui/.kluctl.yaml create mode 100644 install/webui/deployment.yaml create mode 100644 install/webui/webui/admin-rbac.yaml create mode 100644 install/webui/webui/deployment.yaml create mode 100644 install/webui/webui/webui-rbac.yaml diff --git a/hack/prepare-release.sh b/hack/prepare-release.sh index f63be7543..b306fb781 100755 --- a/hack/prepare-release.sh +++ b/hack/prepare-release.sh @@ -24,7 +24,11 @@ fi echo VERSION=$VERSION -FILES="install/controller/.kluctl.yaml install/controller/controller/kustomization.yaml docs/installation.md" +FILES="" +FILES="$FILES install/controller/.kluctl.yaml" +FILES="$FILES install/controller/controller/kustomization.yaml" +FILES="$FILES install/webui/.kluctl.yaml" +FILES="$FILES docs/installation.md" for f in $FILES; do cat $f | sed "s/$VERSION_REGEX_SED/$VERSION/g" > $f.tmp diff --git a/install/webui/.kluctl.yaml b/install/webui/.kluctl.yaml new file mode 100644 index 000000000..961fb60c4 --- /dev/null +++ b/install/webui/.kluctl.yaml @@ -0,0 +1,9 @@ +discriminator: kluctl.io-webui + +args: + - name: kluctl_version + default: v2.20.4 + - name: webui_args + default: [] + - name: webui_envs + default: [] diff --git a/install/webui/deployment.yaml b/install/webui/deployment.yaml new file mode 100644 index 000000000..cf765cdb9 --- /dev/null +++ b/install/webui/deployment.yaml @@ -0,0 +1,3 @@ + +deployments: + - path: webui diff --git a/install/webui/webui/admin-rbac.yaml b/install/webui/webui/admin-rbac.yaml new file mode 100644 index 000000000..5215ec007 --- /dev/null +++ b/install/webui/webui/admin-rbac.yaml @@ -0,0 +1,41 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: kluctl-webui-admin-role +rules: + - apiGroups: + - gitops.kluctl.io + resources: + - kluctldeployments + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + # Read access for all other Kubernetes objects + - apiGroups: ["*"] + resources: ["*"] + verbs: ["get", "list", "watch"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: controller + app.kubernetes.io/instance: kluctl-webui-rolebinding + app.kubernetes.io/managed-by: kluctl + app.kubernetes.io/name: clusterrolebinding + app.kubernetes.io/part-of: controller + name: kluctl-webui-admin-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: kluctl-webui-admin-role +subjects: + - kind: User + apiGroup: rbac.authorization.k8s.io + name: kluctl-webui-admin diff --git a/install/webui/webui/deployment.yaml b/install/webui/webui/deployment.yaml new file mode 100644 index 000000000..109a6eea4 --- /dev/null +++ b/install/webui/webui/deployment.yaml @@ -0,0 +1,65 @@ +{% set kluctl_version = get_var("args.kluctl_version", "v2.20.4") %} +{% set pull_policy = "Always" if "-devel" in kluctl_version or "-snapshot" in kluctl_version else "IfNotPresent" %} + +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app.kubernetes.io/component: kluctl-webui + app.kubernetes.io/instance: kluctl-controller + app.kubernetes.io/managed-by: kluctl + app.kubernetes.io/name: kluctl-webui + control-plane: kluctl-webui + name: kluctl-webui + namespace: kluctl-system +spec: + replicas: 1 + selector: + matchLabels: + control-plane: kluctl-controller + template: + metadata: + labels: + control-plane: kluctl-controller + spec: + containers: + - name: webui + image: ghcr.io/kluctl/kluctl:{{ kluctl_version }} + imagePullPolicy: {{ pull_policy }} + command: + - kluctl + - webui + - --in-cluster + args: {{ get_var(["args.webui_args", "webui_args"], []) | to_json }} + env: {{ get_var(["args.webui_envs", "webui_envs"], []) | to_json }} + ports: + - containerPort: 8080 + name: http + livenessProbe: + httpGet: + path: / + port: 8080 + initialDelaySeconds: 15 + periodSeconds: 20 + readinessProbe: + httpGet: + path: / + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 10 + resources: + limits: + cpu: 2000m + memory: 512Mi + requests: + cpu: 500m + memory: 512Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + securityContext: + runAsNonRoot: true + serviceAccountName: kluctl-webui + terminationGracePeriodSeconds: 10 diff --git a/install/webui/webui/webui-rbac.yaml b/install/webui/webui/webui-rbac.yaml new file mode 100644 index 000000000..1e7622111 --- /dev/null +++ b/install/webui/webui/webui-rbac.yaml @@ -0,0 +1,48 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app.kubernetes.io/component: rbac + app.kubernetes.io/instance: kluctl-webui-sa + app.kubernetes.io/managed-by: kluctl + app.kubernetes.io/name: serviceaccount + app.kubernetes.io/part-of: controller + name: kluctl-webui + namespace: kluctl-system +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: kluctl-webui-cluster-role +rules: + - apiGroups: [""] + resources: ["namespaces"] + verbs: ["get", "list"] + # allow access to results + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "list", "watch"] + # allow to impersonate other users, groups and serviceaccounts + - apiGroups: [""] + resources: ["users", "groups", "serviceaccounts"] + verbs: ["impersonate"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: controller + app.kubernetes.io/instance: kluctl-webui-rolebinding + app.kubernetes.io/managed-by: kluctl + app.kubernetes.io/name: clusterrolebinding + app.kubernetes.io/part-of: controller + name: kluctl-webui-cluster-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: kluctl-webui-cluster-role +subjects: + - kind: ServiceAccount + name: kluctl-webui + namespace: kluctl-system From 9064f420dd79664a440d7aef8927fd5dd9661875 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 15 Jun 2023 07:51:39 +0200 Subject: [PATCH 1836/2916] fix: Use new http clients to fix impersonation --- pkg/webui/clusteraccessor.go | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/pkg/webui/clusteraccessor.go b/pkg/webui/clusteraccessor.go index e83a75dec..65bd1853d 100644 --- a/pkg/webui/clusteraccessor.go +++ b/pkg/webui/clusteraccessor.go @@ -10,7 +10,6 @@ import ( "k8s.io/client-go/discovery" clientgoscheme "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" - "net/http" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/apiutil" "sync" @@ -23,14 +22,13 @@ type clusterAccessorManager struct { } type clusterAccessor struct { - ctx context.Context - config *rest.Config - httpClient *http.Client - scheme *runtime.Scheme - discovery discovery.DiscoveryInterface - mapper meta.RESTMapper - clusterId string - mutex sync.Mutex + ctx context.Context + config *rest.Config + scheme *runtime.Scheme + discovery discovery.DiscoveryInterface + mapper meta.RESTMapper + clusterId string + mutex sync.Mutex } func (cam *clusterAccessorManager) add(config *rest.Config) { @@ -88,7 +86,6 @@ func (ca *clusterAccessor) tryInitClient() error { if err != nil { return err } - ca.httpClient = httpClient dc, err := k8s2.CreateDiscoveryClient(context.Background(), ca.config) if err != nil { @@ -96,7 +93,7 @@ func (ca *clusterAccessor) tryInitClient() error { } ca.discovery = dc - mapper, err := apiutil.NewDynamicRESTMapper(ca.config, ca.httpClient) + mapper, err := apiutil.NewDynamicRESTMapper(ca.config, httpClient) if err != nil { return err } @@ -135,10 +132,9 @@ func (ca *clusterAccessor) getClientLocked(asUser string, asGroups []string) (cl config.Impersonate.UserName = asUser config.Impersonate.Groups = asGroups - c, err := client.New(config, client.Options{ - HTTPClient: ca.httpClient, - Scheme: ca.scheme, - Mapper: ca.mapper, + c, err := client.NewWithWatch(config, client.Options{ + Scheme: ca.scheme, + Mapper: ca.mapper, }) if err != nil { return nil, err @@ -154,7 +150,12 @@ func (ca *clusterAccessor) getK(ctx context.Context, asUser string, asGroups []s config.Impersonate.UserName = asUser config.Impersonate.Groups = asGroups - cf, err := k8s2.NewClientFactory(ctx, config, ca.httpClient, ca.discovery, ca.mapper) + httpClient, err := rest.HTTPClientFor(config) + if err != nil { + return nil, err + } + + cf, err := k8s2.NewClientFactory(ctx, config, httpClient, ca.discovery, ca.mapper) if err != nil { return nil, err } From 79fc4999336c1a1ae7db3a265fa10f67bd158e3e Mon Sep 17 00:00:00 2001 From: Alexey Vagarenko Date: Wed, 14 Jun 2023 00:12:55 +0600 Subject: [PATCH 1837/2916] WIP history cards. --- .../src/components/result-view/SidePanel.tsx | 34 ++-- .../ui/src/components/targets-view/Card.tsx | 36 ++-- .../CommandResultDetailsDrawer.tsx | 3 +- .../targets-view/CommandResultItem.tsx | 100 +++++----- .../components/targets-view/HistoryCards.tsx | 172 ++++++++++++++++++ .../src/components/targets-view/Projects.tsx | 17 +- .../src/components/targets-view/Targets.tsx | 18 +- .../components/targets-view/TargetsView.tsx | 20 +- 8 files changed, 288 insertions(+), 112 deletions(-) create mode 100644 pkg/webui/ui/src/components/targets-view/HistoryCards.tsx diff --git a/pkg/webui/ui/src/components/result-view/SidePanel.tsx b/pkg/webui/ui/src/components/result-view/SidePanel.tsx index c2d6a420a..a2ea88709 100644 --- a/pkg/webui/ui/src/components/result-view/SidePanel.tsx +++ b/pkg/webui/ui/src/components/result-view/SidePanel.tsx @@ -1,5 +1,5 @@ import { Box, Divider, IconButton, Paper, Tab, ThemeProvider, Typography, useTheme } from "@mui/material"; -import React, { useEffect, useState } from "react"; +import React, { useCallback, useEffect, useMemo, useState } from "react"; import { TabContext, TabList, TabPanel } from "@mui/lab"; import { CloseIcon } from "../../icons/Icons"; import { light } from "../theme"; @@ -19,37 +19,41 @@ export interface SidePanelProps { onClose?: () => void; } -export const SidePanel = (props: SidePanelProps) => { +export function useSidePanelTabs(provider?: SidePanelProvider) { const [selectedTab, setSelectedTab] = useState(); - const theme = useTheme(); - function handleTabChange(_e: React.SyntheticEvent, value: string) { + const handleTabChange = useCallback((_e: React.SyntheticEvent, value: string) => { setSelectedTab(value); - } + }, []); - let tabs = props.provider?.buildSidePanelTabs() - if (!tabs) { - tabs = [] - } + const tabs = useMemo( + () => provider?.buildSidePanelTabs() || [], + [provider] + ); useEffect(() => { if (!tabs?.length) { - setSelectedTab("") + setSelectedTab(""); return } if (!selectedTab) { - setSelectedTab(tabs[0].label) + setSelectedTab(tabs[0].label); return } if (!tabs.find(x => x.label === selectedTab)) { // reset it after the type of selected item has changed - setSelectedTab(tabs[0].label) + setSelectedTab(tabs[0].label); } - // ignore that it wants us to add selectedTab to the deps (it would cause and endless loop) - // eslint-disable-next-line - }, [props.provider]) + }, [selectedTab, tabs]); + + return { tabs, selectedTab, handleTabChange } +} + +export const SidePanel = (props: SidePanelProps) => { + const theme = useTheme(); + const { tabs, selectedTab, handleTabChange } = useSidePanelTabs(props.provider) if (!selectedTab || !tabs.find(x => x.label === selectedTab)) { return <> diff --git a/pkg/webui/ui/src/components/targets-view/Card.tsx b/pkg/webui/ui/src/components/targets-view/Card.tsx index 5c550fc9e..6e2cf2226 100644 --- a/pkg/webui/ui/src/components/targets-view/Card.tsx +++ b/pkg/webui/ui/src/components/targets-view/Card.tsx @@ -1,24 +1,34 @@ -import { Box, BoxProps } from "@mui/material" +import { Box, BoxProps, Paper, PaperProps } from "@mui/material" export const cardWidth = 247; export const projectCardHeight = 80; export const cardHeight = 126; export const cardGap = 20; -export function Card({ children, ...rest }: BoxProps) { - return - {children} - +export function CardPaper(props: PaperProps) { + const { sx, ...rest } = props; + return } -export function CardCol({ children, ...rest }: BoxProps) { - return - {children} - +export function Card(props: BoxProps) { + return } -export function CardRow({ children, ...rest }: BoxProps) { - return - {children} - +export function CardCol(props: BoxProps) { + return +} + +export function CardRow(props: BoxProps) { + return } diff --git a/pkg/webui/ui/src/components/targets-view/CommandResultDetailsDrawer.tsx b/pkg/webui/ui/src/components/targets-view/CommandResultDetailsDrawer.tsx index 705bb623f..a6b6bb6a2 100644 --- a/pkg/webui/ui/src/components/targets-view/CommandResultDetailsDrawer.tsx +++ b/pkg/webui/ui/src/components/targets-view/CommandResultDetailsDrawer.tsx @@ -121,11 +121,10 @@ export const CommandResultDetailsDrawer = React.memo((props: { ts={ts} rs={rs} onSelectCommandResult={setSelectedCommandResult} - selected={rs === selectedCommandResult} /> }) - }, [ps, ts, cardsCoords, zIndex, theme.transitions, selectedCommandResult]) + }, [ps, ts, cardsCoords, zIndex, theme.transitions]) if (loadingError) { return <>Error diff --git a/pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx b/pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx index 21c6c12c6..f2f7bf2eb 100644 --- a/pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx +++ b/pkg/webui/ui/src/components/targets-view/CommandResultItem.tsx @@ -2,23 +2,16 @@ import React, { useEffect, useMemo, useState } from "react"; import { CommandResultSummary } from "../../models"; import * as yaml from "js-yaml"; import { CodeViewer } from "../CodeViewer"; -import Paper from "@mui/material/Paper"; import { Box, IconButton, Tooltip, Typography } from "@mui/material"; import { CommandResultStatusLine } from "../result-view/CommandResultStatusLine"; import { useNavigate } from "react-router"; import { DeployIcon, DiffIcon, PruneIcon, TreeViewIcon } from "../../icons/Icons"; import { ProjectSummary, TargetSummary } from "../../project-summaries"; import { calcAgo } from "../../utils/duration"; +import { CardPaper } from "./Card"; -export const CommandResultItem = React.memo((props: { - ps: ProjectSummary, - ts: TargetSummary, - rs: CommandResultSummary, - onSelectCommandResult: (rs: CommandResultSummary) => void, - selected?: boolean; -}) => { - const { rs, onSelectCommandResult, selected } = props; - const navigate = useNavigate() +export const CommandResultItemHeader = React.memo((props: { rs: CommandResultSummary }) => { + const { rs } = props; const [ago, setAgo] = useState(calcAgo(rs.commandInfo.startTime)) let Icon: () => JSX.Element = DiffIcon @@ -50,50 +43,53 @@ export const CommandResultItem = React.memo((props: { return () => clearInterval(interval); }, [rs.commandInfo.startTime]); - return + + + + + + + + {rs.commandInfo?.command} + + + {ago} + + + +}); + +export const CommandResultItem = React.memo((props: { + ps: ProjectSummary, + ts: TargetSummary, + rs: CommandResultSummary, + onSelectCommandResult: (rs: CommandResultSummary) => void, +}) => { + const { rs, onSelectCommandResult } = props; + const navigate = useNavigate() + + return onSelectCommandResult(rs)} > - - - - - - - - - {rs.commandInfo?.command} - - - {ago} - - - + @@ -117,5 +113,5 @@ export const CommandResultItem = React.memo((props: { - + }); diff --git a/pkg/webui/ui/src/components/targets-view/HistoryCards.tsx b/pkg/webui/ui/src/components/targets-view/HistoryCards.tsx new file mode 100644 index 000000000..63baafce3 --- /dev/null +++ b/pkg/webui/ui/src/components/targets-view/HistoryCards.tsx @@ -0,0 +1,172 @@ +import React, { Suspense, useEffect, useRef, useState } from "react"; +import { Box, Tab, useTheme } from "@mui/material"; +import { CommandResultSummary } from "../../models"; +import { ProjectSummary, TargetSummary } from "../../project-summaries"; +import { CardPaper, cardHeight, cardWidth } from "./Card"; +import { CommandResultItemHeader } from "./CommandResultItem"; +import { Loading } from "../Loading"; +import { NodeData } from "../result-view/nodes/NodeData"; +import { api, usePromise } from "../../api"; +import { NodeBuilder } from "../result-view/nodes/NodeBuilder"; +import { SidePanelProvider, useSidePanelTabs } from "../result-view/SidePanel"; +import { TabContext, TabList, TabPanel } from "@mui/lab"; + +async function doGetRootNode(rs: CommandResultSummary) { + const shortNames = api.getShortNames() + const r = api.getResult(rs.id) + const builder = new NodeBuilder({ + shortNames: await shortNames, + summary: rs, + commandResult: await r, + }) + const [node] = builder.buildRoot() + return node +} + +export interface HistoryCardsProps { + rs: CommandResultSummary, + ts: TargetSummary, + ps: ProjectSummary, + initialCardRect: DOMRect +} + +const paddingY = 25; +const paddingX = 120; + +interface Rect { + left: number, + top: number, + width: number | string, + height: number | string +} + +type TransitionStatus = 'not-started' | 'running' | 'finished' + +function CardContent(props: { provider: SidePanelProvider }) { + const { tabs, selectedTab, handleTabChange } = useSidePanelTabs(props.provider) + + if (!props.provider + || !selectedTab + || !tabs.find(x => x.label === selectedTab) + ) { + return null; + } + + return + + {tabs.map((tab, i) => { + return + })} + + + {tabs.map(tab => { + return + {tab.content} + + })} + + +} + +export const HistoryCards = React.memo((props: HistoryCardsProps) => { + const theme = useTheme(); + const containerElem = useRef(); + const [cardRect, setCardRect] = useState(); + const [transitionStatus, setTransitionStatus] = useState('not-started'); + const [promise, setPromise] = useState>(new Promise(() => undefined)); + + const Content = () => { + const node = usePromise(promise) + return ; + } + + useEffect(() => { + if (props.rs === undefined) { + return + } + setPromise(doGetRootNode(props.rs)); + }, [props.rs]); + + useEffect(() => { + const rect = containerElem.current?.getBoundingClientRect(); + if (!rect) { + setCardRect(undefined); + return; + } + + const initialRect = { + left: props.initialCardRect.left - rect.left - paddingX, + top: props.initialCardRect.top - rect.top - paddingY, + width: cardWidth, + height: cardHeight + }; + + setCardRect(initialRect); + }, [props.initialCardRect]); + + useEffect(() => { + if (!cardRect) { + return; + } + + const targetRect = { + left: 0, + top: 0, + width: '100%', + height: '100%' + }; + + if (cardRect.left === targetRect.left + && cardRect.top === targetRect.top + && cardRect.width === targetRect.width + && cardRect.height === targetRect.height + ) { + return; + } + + setTimeout(() => { + setCardRect(targetRect); + setTransitionStatus('running'); + setTimeout(() => { + setTransitionStatus('finished'); + }, theme.transitions.duration.enteringScreen) + }, 10); + }, [cardRect, theme.transitions.duration.enteringScreen]); + + return + {cardRect && + + + + {transitionStatus === 'finished' && + }> + + + } + + + } + ; +}); diff --git a/pkg/webui/ui/src/components/targets-view/Projects.tsx b/pkg/webui/ui/src/components/targets-view/Projects.tsx index 1ead486b3..cbf643318 100644 --- a/pkg/webui/ui/src/components/targets-view/Projects.tsx +++ b/pkg/webui/ui/src/components/targets-view/Projects.tsx @@ -1,10 +1,10 @@ import { getLastPathElement } from "../../utils/misc"; -import Paper from "@mui/material/Paper"; import { Box, Typography } from "@mui/material"; import React from "react"; import Tooltip from "@mui/material/Tooltip"; import { ProjectIcon } from "../../icons/Icons"; import { ProjectSummary } from "../../project-summaries"; +import { CardPaper } from "./Card"; export const ProjectItem = (props: { ps: ProjectSummary }) => { const name = getLastPathElement(props.ps.project.gitRepoKey) @@ -17,16 +17,9 @@ export const ProjectItem = (props: { ps: ProjectSummary }) => { : <>}
    - return + return {name && ( @@ -59,5 +52,5 @@ export const ProjectItem = (props: { ps: ProjectSummary }) => {
    - + } diff --git a/pkg/webui/ui/src/components/targets-view/Targets.tsx b/pkg/webui/ui/src/components/targets-view/Targets.tsx index b12ffa5e1..5127a4b09 100644 --- a/pkg/webui/ui/src/components/targets-view/Targets.tsx +++ b/pkg/webui/ui/src/components/targets-view/Targets.tsx @@ -1,6 +1,5 @@ import { KluctlDeploymentInfo } from "../../models"; import { ActionMenuItem, ActionsMenu } from "../ActionsMenu"; -import Paper from "@mui/material/Paper"; import { Box, Typography, useTheme } from "@mui/material"; import React, { useContext } from "react"; import Tooltip from "@mui/material/Tooltip"; @@ -9,6 +8,7 @@ import { CpuIcon, FingerScanIcon, MessageQuestionIcon, TargetIcon } from "../../ import { ProjectSummary, TargetSummary } from "../../project-summaries"; import { calcAgo } from "../../utils/duration"; import { ApiContext } from "../App"; +import { CardPaper } from "./Card"; const StatusIcon = (props: { ps: ProjectSummary, ts: TargetSummary }) => { let icon: React.ReactElement @@ -116,7 +116,7 @@ export const TargetItem = (props: { ps: ProjectSummary, ts: TargetSummary, onSel const contextTooltip = All known contexts: - {allContexts.map(context=> ( + {allContexts.map(context => ( {context} ))} @@ -126,16 +126,8 @@ export const TargetItem = (props: { ps: ProjectSummary, ts: TargetSummary, onSel targetName = "" } - return props.onSelectTarget(props.ts)} > @@ -184,5 +176,5 @@ export const TargetItem = (props: { ps: ProjectSummary, ts: TargetSummary, onSel - + } diff --git a/pkg/webui/ui/src/components/targets-view/TargetsView.tsx b/pkg/webui/ui/src/components/targets-view/TargetsView.tsx index 228519b0d..e4dfe1b66 100644 --- a/pkg/webui/ui/src/components/targets-view/TargetsView.tsx +++ b/pkg/webui/ui/src/components/targets-view/TargetsView.tsx @@ -11,6 +11,7 @@ import { TargetDetailsDrawer } from "./TargetDetailsDrawer"; import { Card, CardCol, cardGap, cardHeight, CardRow, cardWidth, projectCardHeight } from "./Card"; import { ProjectSummary, TargetSummary } from "../../project-summaries"; import { buildListKey } from "../../utils/listKey"; +import { HistoryCards } from "./HistoryCards"; const colWidth = 416; const curveRadius = 12; @@ -140,7 +141,7 @@ export const TargetsView = () => { setSelectedCardRect(undefined); }, []); - const doSetSelectedCommandResult = useCallback((o?: {rs: CommandResultSummary, ts: TargetSummary, ps: ProjectSummary}) => { + const onSelectCommandResult = useCallback((o?: {rs: CommandResultSummary, ts: TargetSummary, ps: ProjectSummary}) => { onTargetDetailsDrawerClose(); setSelectedCommandResult(o); }, [onTargetDetailsDrawerClose]); @@ -150,14 +151,23 @@ export const TargetsView = () => { setSelectedTargetSummary(ts); }, [onCommandResultDetailsDrawerClose]); + if (selectedCommandResult && selectedCardRect) { + return ; + } + return - + /> */} { { ps={ps} ts={ts} rs={rs} - onSelectCommandResult={(rs) => doSetSelectedCommandResult({rs, ts, ps})} + onSelectCommandResult={(rs) => onSelectCommandResult({rs, ts, ps})} /> })} From cdd437fcf11e536514dae46361b5d57a70cc15a6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 14 Jun 2023 15:57:42 +0200 Subject: [PATCH 1838/2916] fix: Migrate to new way of loading --- .../components/targets-view/HistoryCards.tsx | 42 ++++++++----------- .../components/targets-view/TargetsView.tsx | 1 - 2 files changed, 18 insertions(+), 25 deletions(-) diff --git a/pkg/webui/ui/src/components/targets-view/HistoryCards.tsx b/pkg/webui/ui/src/components/targets-view/HistoryCards.tsx index 63baafce3..7b74b1be9 100644 --- a/pkg/webui/ui/src/components/targets-view/HistoryCards.tsx +++ b/pkg/webui/ui/src/components/targets-view/HistoryCards.tsx @@ -1,23 +1,23 @@ -import React, { Suspense, useEffect, useRef, useState } from "react"; +import React, { useContext, useEffect, useRef, useState } from "react"; import { Box, Tab, useTheme } from "@mui/material"; import { CommandResultSummary } from "../../models"; import { ProjectSummary, TargetSummary } from "../../project-summaries"; import { CardPaper, cardHeight, cardWidth } from "./Card"; import { CommandResultItemHeader } from "./CommandResultItem"; -import { Loading } from "../Loading"; -import { NodeData } from "../result-view/nodes/NodeData"; -import { api, usePromise } from "../../api"; +import { Loading, useLoadingHelper } from "../Loading"; import { NodeBuilder } from "../result-view/nodes/NodeBuilder"; import { SidePanelProvider, useSidePanelTabs } from "../result-view/SidePanel"; import { TabContext, TabList, TabPanel } from "@mui/lab"; +import { Api } from "../../api"; +import { ApiContext } from "../App"; -async function doGetRootNode(rs: CommandResultSummary) { - const shortNames = api.getShortNames() - const r = api.getResult(rs.id) +async function doGetRootNode(api: Api, rs: CommandResultSummary) { + const shortNames = await api.getShortNames() + const r = await api.getResult(rs.id) const builder = new NodeBuilder({ - shortNames: await shortNames, + shortNames: shortNames, summary: rs, - commandResult: await r, + commandResult: r, }) const [node] = builder.buildRoot() return node @@ -69,23 +69,15 @@ function CardContent(props: { provider: SidePanelProvider }) { } export const HistoryCards = React.memo((props: HistoryCardsProps) => { + const api = useContext(ApiContext) const theme = useTheme(); const containerElem = useRef(); const [cardRect, setCardRect] = useState(); const [transitionStatus, setTransitionStatus] = useState('not-started'); - const [promise, setPromise] = useState>(new Promise(() => undefined)); - const Content = () => { - const node = usePromise(promise) - return ; - } - - useEffect(() => { - if (props.rs === undefined) { - return - } - setPromise(doGetRootNode(props.rs)); - }, [props.rs]); + const [loading, loadingError, node] = useLoadingHelper(() => { + return doGetRootNode(api, props.rs) + }, [api, props.rs]) useEffect(() => { const rect = containerElem.current?.getBoundingClientRect(); @@ -133,6 +125,10 @@ export const HistoryCards = React.memo((props: HistoryCardsProps) => { }, 10); }, [cardRect, theme.transitions.duration.enteringScreen]); + if (loadingError) { + return <>Error + } + return { {transitionStatus === 'finished' && - }> - - + (loading ? : ) } diff --git a/pkg/webui/ui/src/components/targets-view/TargetsView.tsx b/pkg/webui/ui/src/components/targets-view/TargetsView.tsx index e4dfe1b66..77683560d 100644 --- a/pkg/webui/ui/src/components/targets-view/TargetsView.tsx +++ b/pkg/webui/ui/src/components/targets-view/TargetsView.tsx @@ -6,7 +6,6 @@ import { ProjectItem } from "./Projects"; import { TargetItem } from "./Targets"; import Divider from "@mui/material/Divider"; import { CommandResultItem } from "./CommandResultItem"; -import { CommandResultDetailsDrawer } from "./CommandResultDetailsDrawer"; import { TargetDetailsDrawer } from "./TargetDetailsDrawer"; import { Card, CardCol, cardGap, cardHeight, CardRow, cardWidth, projectCardHeight } from "./Card"; import { ProjectSummary, TargetSummary } from "../../project-summaries"; From 4fb00ad1ab11d5d62f4e9d809e9df9e7652fa50f Mon Sep 17 00:00:00 2001 From: Alexey Vagarenko Date: Thu, 15 Jun 2023 05:22:16 +0600 Subject: [PATCH 1839/2916] Added new history cards view. --- .../components/targets-view/HistoryCards.tsx | 251 ++++++++++++++---- .../components/targets-view/TargetsView.tsx | 38 +-- pkg/webui/ui/src/components/theme.ts | 30 +++ pkg/webui/ui/src/icons/Icons.tsx | 16 +- pkg/webui/ui/src/icons/close-light.svg | 3 + .../ui/src/icons/triangle-left-light.svg | 4 + .../ui/src/icons/triangle-right-light.svg | 4 + 7 files changed, 266 insertions(+), 80 deletions(-) create mode 100644 pkg/webui/ui/src/icons/close-light.svg create mode 100644 pkg/webui/ui/src/icons/triangle-left-light.svg create mode 100644 pkg/webui/ui/src/icons/triangle-right-light.svg diff --git a/pkg/webui/ui/src/components/targets-view/HistoryCards.tsx b/pkg/webui/ui/src/components/targets-view/HistoryCards.tsx index 7b74b1be9..2570361f8 100644 --- a/pkg/webui/ui/src/components/targets-view/HistoryCards.tsx +++ b/pkg/webui/ui/src/components/targets-view/HistoryCards.tsx @@ -1,7 +1,7 @@ -import React, { useContext, useEffect, useRef, useState } from "react"; -import { Box, Tab, useTheme } from "@mui/material"; +import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react"; +import { Box, Divider, IconButton, SxProps, Tab, Tooltip, useTheme } from "@mui/material"; import { CommandResultSummary } from "../../models"; -import { ProjectSummary, TargetSummary } from "../../project-summaries"; +import { TargetSummary } from "../../project-summaries"; import { CardPaper, cardHeight, cardWidth } from "./Card"; import { CommandResultItemHeader } from "./CommandResultItem"; import { Loading, useLoadingHelper } from "../Loading"; @@ -10,6 +10,8 @@ import { SidePanelProvider, useSidePanelTabs } from "../result-view/SidePanel"; import { TabContext, TabList, TabPanel } from "@mui/lab"; import { Api } from "../../api"; import { ApiContext } from "../App"; +import { useNavigate } from "react-router"; +import { CloseLightIcon, TreeViewIcon, TriangleLeftLightIcon, TriangleRightLightIcon } from "../../icons/Icons"; async function doGetRootNode(api: Api, rs: CommandResultSummary) { const shortNames = await api.getShortNames() @@ -26,13 +28,10 @@ async function doGetRootNode(api: Api, rs: CommandResultSummary) { export interface HistoryCardsProps { rs: CommandResultSummary, ts: TargetSummary, - ps: ProjectSummary, - initialCardRect: DOMRect + initialCardRect: DOMRect, + onClose: () => void } -const paddingY = 25; -const paddingX = 120; - interface Rect { left: number, top: number, @@ -42,7 +41,7 @@ interface Rect { type TransitionStatus = 'not-started' | 'running' | 'finished' -function CardContent(props: { provider: SidePanelProvider }) { +const CardContent = React.memo((props: { provider: SidePanelProvider }) => { const { tabs, selectedTab, handleTabChange } = useSidePanelTabs(props.provider) if (!props.provider @@ -53,31 +52,126 @@ function CardContent(props: { provider: SidePanelProvider }) { } return - - {tabs.map((tab, i) => { - return - })} - + + + {tabs.map((tab, i) => { + return + })} + + + {tabs.map(tab => { - return + return {tab.content} })} -} +}); + +const ArrowButton = React.memo((props: { + direction: 'left' | 'right', + onClick: () => void, + hidden: boolean +}) => { + const Icon = { + left: TriangleLeftLightIcon, + right: TriangleRightLightIcon + }[props.direction]; + + return + {!props.hidden && + + + + } + +}); + +const HistoryCard = React.memo((props: { + rs: CommandResultSummary, + sx?: SxProps + transitionFinished?: boolean, + onClose?: () => void; +}) => { + const navigate = useNavigate(); + const api = useContext(ApiContext); + const [loading, loadingError, node] = useLoadingHelper(() => { + return doGetRootNode(api, props.rs) + }, [api, props.rs]); + + if (loadingError) { + return <>Error + } + + return + + {props.transitionFinished && ( + + + + )} + + + + + + + {props.transitionFinished && ( + loading + ? + : + )} + + + { + e.stopPropagation(); + navigate(`/results/${props.rs.id}`); + }} + sx={{ + padding: 0, + width: 32, + height: 32 + }} + > + + + + + + + +}); export const HistoryCards = React.memo((props: HistoryCardsProps) => { - const api = useContext(ApiContext) const theme = useTheme(); const containerElem = useRef(); const [cardRect, setCardRect] = useState(); const [transitionStatus, setTransitionStatus] = useState('not-started'); - - const [loading, loadingError, node] = useLoadingHelper(() => { - return doGetRootNode(api, props.rs) - }, [api, props.rs]) + const [currentRS, setCurrentRS] = useState(props.rs); useEffect(() => { const rect = containerElem.current?.getBoundingClientRect(); @@ -87,8 +181,8 @@ export const HistoryCards = React.memo((props: HistoryCardsProps) => { } const initialRect = { - left: props.initialCardRect.left - rect.left - paddingX, - top: props.initialCardRect.top - rect.top - paddingY, + left: props.initialCardRect.left - rect.left, + top: props.initialCardRect.top - rect.top, width: cardWidth, height: cardHeight }; @@ -121,46 +215,95 @@ export const HistoryCards = React.memo((props: HistoryCardsProps) => { setTransitionStatus('running'); setTimeout(() => { setTransitionStatus('finished'); - }, theme.transitions.duration.enteringScreen) + }, theme.transitions.duration.enteringScreen); }, 10); }, [cardRect, theme.transitions.duration.enteringScreen]); - if (loadingError) { - return <>Error - } + const currentRSIndex = useMemo( + () => props.ts.commandResults.indexOf(currentRS), + [currentRS, props.ts.commandResults] + ) + + const onLeftArrowClick = useCallback(() => { + if (currentRSIndex > 0) { + setCurrentRS(props.ts.commandResults[currentRSIndex - 1]); + } + }, [currentRSIndex, props.ts.commandResults]); + + const onRightArrowClick = useCallback(() => { + if (currentRSIndex < props.ts.commandResults.length - 1) { + setCurrentRS(props.ts.commandResults[currentRSIndex + 1]); + } + }, [currentRSIndex, props.ts.commandResults]); return - {cardRect && + - - - - {transitionStatus === 'finished' && - (loading ? : ) - } + {transitionStatus !== 'finished' && cardRect && } + {transitionStatus === 'finished' && + + {props.ts.commandResults.map((rs) => + + )} - - } + } + + ; }); diff --git a/pkg/webui/ui/src/components/targets-view/TargetsView.tsx b/pkg/webui/ui/src/components/targets-view/TargetsView.tsx index 77683560d..b46d9a909 100644 --- a/pkg/webui/ui/src/components/targets-view/TargetsView.tsx +++ b/pkg/webui/ui/src/components/targets-view/TargetsView.tsx @@ -8,7 +8,7 @@ import Divider from "@mui/material/Divider"; import { CommandResultItem } from "./CommandResultItem"; import { TargetDetailsDrawer } from "./TargetDetailsDrawer"; import { Card, CardCol, cardGap, cardHeight, CardRow, cardWidth, projectCardHeight } from "./Card"; -import { ProjectSummary, TargetSummary } from "../../project-summaries"; +import { TargetSummary } from "../../project-summaries"; import { buildListKey } from "../../utils/listKey"; import { HistoryCards } from "./HistoryCards"; @@ -124,7 +124,7 @@ const RelationTree = React.memo(({ targetCount }: { targetCount: number }): JSX. export const TargetsView = () => { const theme = useTheme(); - const [selectedCommandResult, setSelectedCommandResult] = useState<{rs: CommandResultSummary, ts: TargetSummary, ps: ProjectSummary} | undefined>(); + const [selectedCommandResult, setSelectedCommandResult] = useState<{ rs: CommandResultSummary, ts: TargetSummary } | undefined>(); const [selectedTargetSummary, setSelectedTargetSummary] = useState(); const [selectedCardRect, setSelectedCardRect] = useState(); @@ -135,38 +135,26 @@ export const TargetsView = () => { setSelectedTargetSummary(undefined); }, []); - const onCommandResultDetailsDrawerClose = useCallback(() => { - setSelectedCommandResult(undefined); - setSelectedCardRect(undefined); - }, []); - - const onSelectCommandResult = useCallback((o?: {rs: CommandResultSummary, ts: TargetSummary, ps: ProjectSummary}) => { + const onSelectCommandResult = useCallback((o?: { rs: CommandResultSummary, ts: TargetSummary }) => { onTargetDetailsDrawerClose(); setSelectedCommandResult(o); }, [onTargetDetailsDrawerClose]); - - const doSetSelectedTargetSummary = useCallback((ts?: TargetSummary) => { - onCommandResultDetailsDrawerClose(); - setSelectedTargetSummary(ts); - }, [onCommandResultDetailsDrawerClose]); + + const onHistoryCardsClose = useCallback(() => { + setSelectedCommandResult(undefined); + setSelectedCardRect(undefined); + }, []); if (selectedCommandResult && selectedCardRect) { - return ; } return - {/* */} { return doSetSelectedTargetSummary(ts)} /> + onSelectTarget={setSelectedTargetSummary} /> { ps={ps} ts={ts} rs={rs} - onSelectCommandResult={(rs) => onSelectCommandResult({rs, ts, ps})} + onSelectCommandResult={(rs) => onSelectCommandResult({ rs, ts })} /> })} diff --git a/pkg/webui/ui/src/components/theme.ts b/pkg/webui/ui/src/components/theme.ts index 85c0250e6..639a52406 100644 --- a/pkg/webui/ui/src/components/theme.ts +++ b/pkg/webui/ui/src/components/theme.ts @@ -95,6 +95,36 @@ export const light = createTheme(common, { } } }, + MuiTabs: { + styleOverrides: { + root: { + height: '36px', + minHeight: 0, + textTransform: 'none' + }, + indicator: { + backgroundColor: '#59A588' + } + } + }, + MuiTab: { + styleOverrides: { + root: { + height: '36px', + minHeight: 0, + fontWeight: 400, + fontSize: '16px', + lineHeight: '22px', + letterSpacing: '1px', + textTransform: 'none', + padding: '7px 5px', + color: '#8A8E91', + '&.Mui-selected': { + color: paletteLight.text.primary + } + } + } + }, MuiAppBar: { styleOverrides: { root: { diff --git a/pkg/webui/ui/src/icons/Icons.tsx b/pkg/webui/ui/src/icons/Icons.tsx index 7abba98ce..967aa08ca 100644 --- a/pkg/webui/ui/src/icons/Icons.tsx +++ b/pkg/webui/ui/src/icons/Icons.tsx @@ -10,6 +10,7 @@ import { ReactComponent as CpuIconSvg } from './cpu.svg'; import { ReactComponent as FingerScanIconSvg } from './finger-scan.svg'; import { ReactComponent as TreeViewIconSvg } from './tree-view.svg'; import { ReactComponent as CloseIconSvg } from './close.svg'; +import { ReactComponent as CloseLightIconSvg } from './close-light.svg'; import { ReactComponent as DeployIconSvg } from './deploy.svg'; import { ReactComponent as PruneIconSvg } from './prune.svg'; import { ReactComponent as DiffIconSvg } from './diff.svg'; @@ -27,6 +28,8 @@ import { ReactComponent as WarningSignIconSvg } from './warning-sign.svg'; import { ReactComponent as ChangesIconSvg } from './changes.svg'; import { ReactComponent as StarIconSvg } from './star.svg'; import { ReactComponent as TriangleDownIconSvg } from './triangle-down.svg'; +import { ReactComponent as TriangleLeftLightIconSvg } from './triangle-left-light.svg'; +import { ReactComponent as TriangleRightLightIconSvg } from './triangle-right-light.svg'; import { ReactComponent as TriangleRightIconSvg } from './triangle-right.svg'; import { ReactComponent as BracketsCurlyIconSvg } from './brackets-curly.svg'; import { ReactComponent as BracketsSquareIconSvg } from './brackets-square.svg'; @@ -103,6 +106,10 @@ export const CloseIcon = () => { return } +export const CloseLightIcon = () => { + return +} + export const WarningIcon = () => { return } @@ -159,10 +166,17 @@ export const TriangleDownIcon = () => { return } +export const TriangleLeftLightIcon = () => { + return +} + +export const TriangleRightLightIcon = () => { + return +} + export const TriangleRightIcon = () => { return } - export const BracketsCurlyIcon = () => { return } diff --git a/pkg/webui/ui/src/icons/close-light.svg b/pkg/webui/ui/src/icons/close-light.svg new file mode 100644 index 000000000..a00259719 --- /dev/null +++ b/pkg/webui/ui/src/icons/close-light.svg @@ -0,0 +1,3 @@ + + + diff --git a/pkg/webui/ui/src/icons/triangle-left-light.svg b/pkg/webui/ui/src/icons/triangle-left-light.svg new file mode 100644 index 000000000..6731d308d --- /dev/null +++ b/pkg/webui/ui/src/icons/triangle-left-light.svg @@ -0,0 +1,4 @@ + + + + diff --git a/pkg/webui/ui/src/icons/triangle-right-light.svg b/pkg/webui/ui/src/icons/triangle-right-light.svg new file mode 100644 index 000000000..500b3f1e8 --- /dev/null +++ b/pkg/webui/ui/src/icons/triangle-right-light.svg @@ -0,0 +1,4 @@ + + + + From 5abe6caee130d332b40ae18255cb138cb111f9c3 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 15 Jun 2023 08:23:17 +0200 Subject: [PATCH 1840/2916] fix: Make sure it's clear that search is not implemneted yet --- pkg/webui/ui/src/components/LeftDrawer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/webui/ui/src/components/LeftDrawer.tsx b/pkg/webui/ui/src/components/LeftDrawer.tsx index 75e4d18bb..b3dab16ae 100644 --- a/pkg/webui/ui/src/components/LeftDrawer.tsx +++ b/pkg/webui/ui/src/components/LeftDrawer.tsx @@ -170,7 +170,7 @@ export default function LeftDrawer(props: { content: React.ReactNode, context: A lineHeight: '20px', fontSize: '18px' }} - placeholder='Search' + placeholder='Search (not impl. yet)' /> From 863f46b5661c68f3aeea0bf977bada8e3d9b9619 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 15 Jun 2023 08:23:46 +0200 Subject: [PATCH 1841/2916] chore: Run npm run build --- pkg/webui/ui/build/asset-manifest.json | 3 + pkg/webui/ui/build/index.html | 2 +- pkg/webui/ui/build/static/js/main.js | 3612 +++++++++-------- .../ui/build/static/media/close-light.svg | 3 + .../static/media/triangle-left-light.svg | 4 + .../static/media/triangle-right-light.svg | 4 + 6 files changed, 1868 insertions(+), 1760 deletions(-) create mode 100644 pkg/webui/ui/build/static/media/close-light.svg create mode 100644 pkg/webui/ui/build/static/media/triangle-left-light.svg create mode 100644 pkg/webui/ui/build/static/media/triangle-right-light.svg diff --git a/pkg/webui/ui/build/asset-manifest.json b/pkg/webui/ui/build/asset-manifest.json index 351d6bcf7..dfdfa5fab 100644 --- a/pkg/webui/ui/build/asset-manifest.json +++ b/pkg/webui/ui/build/asset-manifest.json @@ -16,6 +16,7 @@ "static/media/checkbox-checked.svg": "static/media/checkbox-checked.svg", "static/media/checkbox-disabled.svg": "static/media/checkbox-disabled.svg", "static/media/checkbox.svg": "static/media/checkbox.svg", + "static/media/close-light.svg": "static/media/close-light.svg", "static/media/close.svg": "static/media/close.svg", "static/media/cpu.svg": "static/media/cpu.svg", "static/media/deploy.svg": "static/media/deploy.svg", @@ -44,6 +45,8 @@ "static/media/trash.svg": "static/media/trash.svg", "static/media/tree-view.svg": "static/media/tree-view.svg", "static/media/triangle-down.svg": "static/media/triangle-down.svg", + "static/media/triangle-left-light.svg": "static/media/triangle-left-light.svg", + "static/media/triangle-right-light.svg": "static/media/triangle-right-light.svg", "static/media/triangle-right.svg": "static/media/triangle-right.svg", "static/media/warning-sign.svg": "static/media/warning-sign.svg", "static/media/warning.svg": "static/media/warning.svg" diff --git a/pkg/webui/ui/build/index.html b/pkg/webui/ui/build/index.html index 08726c8bf..95134f6bf 100644 --- a/pkg/webui/ui/build/index.html +++ b/pkg/webui/ui/build/index.html @@ -1 +1 @@ -React App
    \ No newline at end of file +React App
    \ No newline at end of file diff --git a/pkg/webui/ui/build/static/js/main.js b/pkg/webui/ui/build/static/js/main.js index 4a9d496ae..361e5411f 100644 --- a/pkg/webui/ui/build/static/js/main.js +++ b/pkg/webui/ui/build/static/js/main.js @@ -48107,6 +48107,35 @@ function SvgClose(_ref, svgRef) { var close_ForwardRef = /*#__PURE__*/react.forwardRef(SvgClose); /* harmony default export */ var icons_close = (__webpack_require__.p + "static/media/close.svg?f=close.76fdca7f91598011401cb97d4b490d52.svg"); +;// CONCATENATED MODULE: ./src/icons/close-light.svg +var close_light_path; +var close_light_excluded = ["title", "titleId"]; +function close_light_extends() { close_light_extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return close_light_extends.apply(this, arguments); } +function close_light_objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = close_light_objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } +function close_light_objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } + +function SvgCloseLight(_ref, svgRef) { + var title = _ref.title, + titleId = _ref.titleId, + props = close_light_objectWithoutProperties(_ref, close_light_excluded); + return /*#__PURE__*/react.createElement("svg", close_light_extends({ + width: 24, + height: 24, + viewBox: "0 0 24 24", + fill: "none", + xmlns: "http://www.w3.org/2000/svg", + ref: svgRef, + "aria-labelledby": titleId + }, props), title ? /*#__PURE__*/react.createElement("title", { + id: titleId + }, title) : null, close_light_path || (close_light_path = /*#__PURE__*/react.createElement("path", { + d: "M6.4 19L5 17.6L10.6 12L5 6.4L6.4 5L12 10.6L17.6 5L19 6.4L13.4 12L19 17.6L17.6 19L12 13.4L6.4 19Z", + fill: "#39403E" + }))); +} +var close_light_ForwardRef = /*#__PURE__*/react.forwardRef(SvgCloseLight); +/* harmony default export */ var close_light = (__webpack_require__.p + "static/media/close-light.svg?f=close-light.eb8a7d21cff89473a30809cdaacf6446.svg"); + ;// CONCATENATED MODULE: ./src/icons/deploy.svg var deploy_circle, deploy_path, _g; var deploy_excluded = ["title", "titleId"]; @@ -48789,6 +48818,73 @@ function SvgTriangleDown(_ref, svgRef) { var triangle_down_ForwardRef = /*#__PURE__*/react.forwardRef(SvgTriangleDown); /* harmony default export */ var triangle_down = (__webpack_require__.p + "static/media/triangle-down.svg?f=triangle-down.cb859bf44378ddfd76730eb4a9f90a89.svg"); +;// CONCATENATED MODULE: ./src/icons/triangle-left-light.svg +var triangle_left_light_path, triangle_left_light_path2; +var triangle_left_light_excluded = ["title", "titleId"]; +function triangle_left_light_extends() { triangle_left_light_extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return triangle_left_light_extends.apply(this, arguments); } +function triangle_left_light_objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = triangle_left_light_objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } +function triangle_left_light_objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } + +function SvgTriangleLeftLight(_ref, svgRef) { + var title = _ref.title, + titleId = _ref.titleId, + props = triangle_left_light_objectWithoutProperties(_ref, triangle_left_light_excluded); + return /*#__PURE__*/react.createElement("svg", triangle_left_light_extends({ + width: 50, + height: 50, + viewBox: "0 0 50 50", + fill: "none", + xmlns: "http://www.w3.org/2000/svg", + ref: svgRef, + "aria-labelledby": titleId + }, props), title ? /*#__PURE__*/react.createElement("title", { + id: titleId + }, title) : null, triangle_left_light_path || (triangle_left_light_path = /*#__PURE__*/react.createElement("path", { + opacity: 0.4, + d: "M22.4377 32.25L32.9585 24.3542V12.6667C32.9585 10.6667 30.5418 9.66667 29.1252 11.0833L18.3335 21.875C16.6043 23.6042 16.6043 26.4167 18.3335 28.1458L22.4377 32.25Z", + fill: "#DFEBE9" + })), triangle_left_light_path2 || (triangle_left_light_path2 = /*#__PURE__*/react.createElement("path", { + d: "M32.4585 25.3546V37.3333C32.4585 38.8863 30.5801 39.6671 29.4773 38.5826C29.4769 38.5822 29.4765 38.5817 29.4761 38.5813L23.1987 32.304L32.4585 25.3546Z", + fill: "#DFEBE9", + stroke: "#DFEBE9" + }))); +} +var triangle_left_light_ForwardRef = /*#__PURE__*/react.forwardRef(SvgTriangleLeftLight); +/* harmony default export */ var triangle_left_light = (__webpack_require__.p + "static/media/triangle-left-light.svg?f=triangle-left-light.723aa352c7195ed6c2c94748badb018c.svg"); + +;// CONCATENATED MODULE: ./src/icons/triangle-right-light.svg +var triangle_right_light_path, triangle_right_light_path2; +var triangle_right_light_excluded = ["title", "titleId"]; +function triangle_right_light_extends() { triangle_right_light_extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return triangle_right_light_extends.apply(this, arguments); } +function triangle_right_light_objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = triangle_right_light_objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } +function triangle_right_light_objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } + +function SvgTriangleRightLight(_ref, svgRef) { + var title = _ref.title, + titleId = _ref.titleId, + props = triangle_right_light_objectWithoutProperties(_ref, triangle_right_light_excluded); + return /*#__PURE__*/react.createElement("svg", triangle_right_light_extends({ + width: 50, + height: 50, + viewBox: "0 0 50 50", + fill: "none", + xmlns: "http://www.w3.org/2000/svg", + ref: svgRef, + "aria-labelledby": titleId + }, props), title ? /*#__PURE__*/react.createElement("title", { + id: titleId + }, title) : null, triangle_right_light_path || (triangle_right_light_path = /*#__PURE__*/react.createElement("path", { + opacity: 0.4, + d: "M27.5623 17.75L17.0415 25.6458V37.3333C17.0415 39.3333 19.4582 40.3333 20.8748 38.9167L31.6665 28.125C33.3957 26.3958 33.3957 23.5833 31.6665 21.8542L27.5623 17.75Z", + fill: "#DFEBE9" + })), triangle_right_light_path2 || (triangle_right_light_path2 = /*#__PURE__*/react.createElement("path", { + d: "M17.0415 12.6667V25.6458L27.5623 17.75L20.8748 11.0625C19.4582 9.66667 17.0415 10.6667 17.0415 12.6667Z", + fill: "#DFEBE9" + }))); +} +var triangle_right_light_ForwardRef = /*#__PURE__*/react.forwardRef(SvgTriangleRightLight); +/* harmony default export */ var triangle_right_light = (__webpack_require__.p + "static/media/triangle-right-light.svg?f=triangle-right-light.22d8903e553c49bf9b0100b61fe7254f.svg"); + ;// CONCATENATED MODULE: ./src/icons/triangle-right.svg var triangle_right_path, triangle_right_path2; var triangle_right_excluded = ["title", "titleId"]; @@ -49032,11 +49128,11 @@ var include_ForwardRef = /*#__PURE__*/react.forwardRef(SvgInclude); /* harmony default export */ var include = (__webpack_require__.p + "static/media/include.svg?f=include.6bf4795a2f5623b297789116effd0bec.svg"); ;// CONCATENATED MODULE: ./src/icons/Icons.tsx -var KluctlText=function KluctlText(){return/*#__PURE__*/(0,jsx_runtime.jsx)(ForwardRef,{width:"115px",height:"33px"});};var GitIcon=function GitIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(git_ForwardRef,{width:"35px",height:"35px"});};var KluctlLogo=function KluctlLogo(){return/*#__PURE__*/(0,jsx_runtime.jsx)(kluctl_logo_ForwardRef,{width:"50px",height:"50px"});};var SearchIcon=function SearchIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(search_ForwardRef,{width:"27px",height:"27px"});};var TargetsIcon=function TargetsIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(targets_ForwardRef,{width:"48px",height:"48px"});};var TargetIcon=function TargetIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(target_ForwardRef,{width:"45px",height:"45px"});};var RelationHLine=function RelationHLine(){return/*#__PURE__*/_jsx(RelationHLineSvg,{width:"169px",height:"12px"});};var ProjectIcon=function ProjectIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(project_ForwardRef,{width:"45px",height:"45px"});};var DeployIcon=function DeployIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(deploy_ForwardRef,{width:"45px",height:"45px"});};var PruneIcon=function PruneIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(prune_ForwardRef,{width:"45px",height:"45px"});};var DiffIcon=function DiffIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(diff_ForwardRef,{width:"45px",height:"45px"});};var CpuIcon=function CpuIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(cpu_ForwardRef,{width:"24px",height:"24px"});};var FingerScanIcon=function FingerScanIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(finger_scan_ForwardRef,{width:"24px",height:"24px"});};var MessageQuestionIcon=function MessageQuestionIcon(props){return/*#__PURE__*/(0,jsx_runtime.jsx)("svg",{width:"24",height:"24",viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg",children:/*#__PURE__*/(0,jsx_runtime.jsx)("path",{d:"M17 2.42999H7C4 2.42999 2 4.42999 2 7.42999V13.43C2 16.43 4 18.43 7 18.43V20.56C7 21.36 7.89 21.84 8.55 21.39L13 18.43H17C20 18.43 22 16.43 22 13.43V7.42999C22 4.42999 20 2.42999 17 2.42999ZM12 14.6C11.58 14.6 11.25 14.26 11.25 13.85C11.25 13.44 11.58 13.1 12 13.1C12.42 13.1 12.75 13.44 12.75 13.85C12.75 14.26 12.42 14.6 12 14.6ZM13.26 10.45C12.87 10.71 12.75 10.88 12.75 11.16V11.37C12.75 11.78 12.41 12.12 12 12.12C11.59 12.12 11.25 11.78 11.25 11.37V11.16C11.25 9.99999 12.1 9.42999 12.42 9.20999C12.79 8.95999 12.91 8.78999 12.91 8.52999C12.91 8.02999 12.5 7.61999 12 7.61999C11.5 7.61999 11.09 8.02999 11.09 8.52999C11.09 8.93999 10.75 9.27999 10.34 9.27999C9.93 9.27999 9.59 8.93999 9.59 8.52999C9.59 7.19999 10.67 6.11999 12 6.11999C13.33 6.11999 14.41 7.19999 14.41 8.52999C14.41 9.66999 13.57 10.24 13.26 10.45Z",fill:props.color})});};var TreeViewIcon=function TreeViewIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(tree_view_ForwardRef,{width:"26px",height:"26px"});};var CloseIcon=function CloseIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(close_ForwardRef,{width:"24px",height:"24px"});};var WarningIcon=function WarningIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(warning_ForwardRef,{width:"24px",height:"24px"});};var ErrorIcon=function ErrorIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(error_ForwardRef,{width:"24px",height:"24px"});};var TrashIcon=function TrashIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(trash_ForwardRef,{width:"24px",height:"24px"});};var OrphanIcon=function OrphanIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(orphan_ForwardRef,{width:"24px",height:"24px"});};var AddedIcon=function AddedIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(added_ForwardRef,{width:"24px",height:"24px"});};var ChangedIcon=function ChangedIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(changed_ForwardRef,{width:"24px",height:"24px"});};var CheckboxIcon=function CheckboxIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(checkbox_ForwardRef,{width:"24px",height:"24px"});};var CheckboxCheckedIcon=function CheckboxCheckedIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(checkbox_checked_ForwardRef,{width:"24px",height:"24px"});};var CheckboxDisabledIcon=function CheckboxDisabledIcon(){return/*#__PURE__*/_jsx(CheckboxDisabledIconSvg,{width:"24px",height:"24px"});};var ArrowLeftIcon=function ArrowLeftIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(arrow_left_ForwardRef,{width:"40px",height:"40px"});};var WarningSignIcon=function WarningSignIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(warning_sign_ForwardRef,{width:"21px",height:"21px"});};var ChangesIcon=function ChangesIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(changes_ForwardRef,{width:"21px",height:"21px"});};var StarIcon=function StarIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(star_ForwardRef,{width:"21px",height:"21px"});};var TriangleDownIcon=function TriangleDownIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(triangle_down_ForwardRef,{width:"50px",height:"50px"});};var TriangleRightIcon=function TriangleRightIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(triangle_right_ForwardRef,{width:"50px",height:"50px"});};var BracketsCurlyIcon=function BracketsCurlyIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(brackets_curly_ForwardRef,{width:"22px",height:"18px"});};var BracketsSquareIcon=function BracketsSquareIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(brackets_square_ForwardRef,{width:"22px",height:"18px"});};var FileIcon=function FileIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(file_ForwardRef,{width:"40px",height:"40px"});};var ResultIcon=function ResultIcon(){return/*#__PURE__*/_jsx(ResultIconSvg,{width:"30px",height:"30px"});};var IncludeIcon=function IncludeIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(include_ForwardRef,{width:"30px",height:"30px"});}; +var KluctlText=function KluctlText(){return/*#__PURE__*/(0,jsx_runtime.jsx)(ForwardRef,{width:"115px",height:"33px"});};var GitIcon=function GitIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(git_ForwardRef,{width:"35px",height:"35px"});};var KluctlLogo=function KluctlLogo(){return/*#__PURE__*/(0,jsx_runtime.jsx)(kluctl_logo_ForwardRef,{width:"50px",height:"50px"});};var SearchIcon=function SearchIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(search_ForwardRef,{width:"27px",height:"27px"});};var TargetsIcon=function TargetsIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(targets_ForwardRef,{width:"48px",height:"48px"});};var TargetIcon=function TargetIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(target_ForwardRef,{width:"45px",height:"45px"});};var RelationHLine=function RelationHLine(){return/*#__PURE__*/_jsx(RelationHLineSvg,{width:"169px",height:"12px"});};var ProjectIcon=function ProjectIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(project_ForwardRef,{width:"45px",height:"45px"});};var DeployIcon=function DeployIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(deploy_ForwardRef,{width:"45px",height:"45px"});};var PruneIcon=function PruneIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(prune_ForwardRef,{width:"45px",height:"45px"});};var DiffIcon=function DiffIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(diff_ForwardRef,{width:"45px",height:"45px"});};var CpuIcon=function CpuIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(cpu_ForwardRef,{width:"24px",height:"24px"});};var FingerScanIcon=function FingerScanIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(finger_scan_ForwardRef,{width:"24px",height:"24px"});};var MessageQuestionIcon=function MessageQuestionIcon(props){return/*#__PURE__*/(0,jsx_runtime.jsx)("svg",{width:"24",height:"24",viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg",children:/*#__PURE__*/(0,jsx_runtime.jsx)("path",{d:"M17 2.42999H7C4 2.42999 2 4.42999 2 7.42999V13.43C2 16.43 4 18.43 7 18.43V20.56C7 21.36 7.89 21.84 8.55 21.39L13 18.43H17C20 18.43 22 16.43 22 13.43V7.42999C22 4.42999 20 2.42999 17 2.42999ZM12 14.6C11.58 14.6 11.25 14.26 11.25 13.85C11.25 13.44 11.58 13.1 12 13.1C12.42 13.1 12.75 13.44 12.75 13.85C12.75 14.26 12.42 14.6 12 14.6ZM13.26 10.45C12.87 10.71 12.75 10.88 12.75 11.16V11.37C12.75 11.78 12.41 12.12 12 12.12C11.59 12.12 11.25 11.78 11.25 11.37V11.16C11.25 9.99999 12.1 9.42999 12.42 9.20999C12.79 8.95999 12.91 8.78999 12.91 8.52999C12.91 8.02999 12.5 7.61999 12 7.61999C11.5 7.61999 11.09 8.02999 11.09 8.52999C11.09 8.93999 10.75 9.27999 10.34 9.27999C9.93 9.27999 9.59 8.93999 9.59 8.52999C9.59 7.19999 10.67 6.11999 12 6.11999C13.33 6.11999 14.41 7.19999 14.41 8.52999C14.41 9.66999 13.57 10.24 13.26 10.45Z",fill:props.color})});};var TreeViewIcon=function TreeViewIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(tree_view_ForwardRef,{width:"26px",height:"26px"});};var CloseIcon=function CloseIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(close_ForwardRef,{width:"24px",height:"24px"});};var CloseLightIcon=function CloseLightIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(close_light_ForwardRef,{width:"24px",height:"24px"});};var WarningIcon=function WarningIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(warning_ForwardRef,{width:"24px",height:"24px"});};var ErrorIcon=function ErrorIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(error_ForwardRef,{width:"24px",height:"24px"});};var TrashIcon=function TrashIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(trash_ForwardRef,{width:"24px",height:"24px"});};var OrphanIcon=function OrphanIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(orphan_ForwardRef,{width:"24px",height:"24px"});};var AddedIcon=function AddedIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(added_ForwardRef,{width:"24px",height:"24px"});};var ChangedIcon=function ChangedIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(changed_ForwardRef,{width:"24px",height:"24px"});};var CheckboxIcon=function CheckboxIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(checkbox_ForwardRef,{width:"24px",height:"24px"});};var CheckboxCheckedIcon=function CheckboxCheckedIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(checkbox_checked_ForwardRef,{width:"24px",height:"24px"});};var CheckboxDisabledIcon=function CheckboxDisabledIcon(){return/*#__PURE__*/_jsx(CheckboxDisabledIconSvg,{width:"24px",height:"24px"});};var ArrowLeftIcon=function ArrowLeftIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(arrow_left_ForwardRef,{width:"40px",height:"40px"});};var WarningSignIcon=function WarningSignIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(warning_sign_ForwardRef,{width:"21px",height:"21px"});};var ChangesIcon=function ChangesIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(changes_ForwardRef,{width:"21px",height:"21px"});};var StarIcon=function StarIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(star_ForwardRef,{width:"21px",height:"21px"});};var TriangleDownIcon=function TriangleDownIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(triangle_down_ForwardRef,{width:"50px",height:"50px"});};var TriangleLeftLightIcon=function TriangleLeftLightIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(triangle_left_light_ForwardRef,{width:"50px",height:"50px"});};var TriangleRightLightIcon=function TriangleRightLightIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(triangle_right_light_ForwardRef,{width:"50px",height:"50px"});};var TriangleRightIcon=function TriangleRightIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(triangle_right_ForwardRef,{width:"50px",height:"50px"});};var BracketsCurlyIcon=function BracketsCurlyIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(brackets_curly_ForwardRef,{width:"22px",height:"18px"});};var BracketsSquareIcon=function BracketsSquareIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(brackets_square_ForwardRef,{width:"22px",height:"18px"});};var FileIcon=function FileIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(file_ForwardRef,{width:"40px",height:"40px"});};var ResultIcon=function ResultIcon(){return/*#__PURE__*/_jsx(ResultIconSvg,{width:"30px",height:"30px"});};var IncludeIcon=function IncludeIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(include_ForwardRef,{width:"30px",height:"30px"});}; ;// CONCATENATED MODULE: ./src/components/theme.ts -var paletteDark={primary:{main:'#DFEBE9'},background:{default:'#222222',paper:'#222222'},text:{primary:'#DFEBE9'}};var paletteLight={primary:{main:'#222222'},secondary:{main:'#39403E'},background:{default:'#DFEBE9',paper:'#DFEBE9'},text:{primary:'#222222'}};var theme_common=styles_createTheme({consts:{appBarHeight:106,leftDrawerWidthOpen:224,leftDrawerWidthClosed:96},typography:{fontFamily:'Nunito Variable'},components:{MuiBackdrop:{styleOverrides:{root:{backgroundColor:'rgba(0, 0, 0, 0.65)'},invisible:{backgroundColor:'transparent'}}}}});var theme_light=styles_createTheme(theme_common,{palette:paletteLight,typography:{h1:{color:paletteLight.text.primary,fontWeight:700,fontSize:'32px',lineHeight:'44px',letterSpacing:'1px'},h2:{color:paletteLight.text.primary,fontWeight:700,fontSize:'20px',lineHeight:'27px',letterSpacing:'1px'},h5:{color:paletteLight.secondary.main,fontWeight:700,fontSize:'22px',lineHeight:'30px',letterSpacing:'1px'},h6:{color:paletteLight.secondary.main,fontWeight:800,fontSize:'20px',lineHeight:'27px'},subtitle1:{color:paletteLight.secondary.main},subtitle2:{fontSize:'14px',lineHeight:1.2}},components:{MuiDivider:{styleOverrides:{root:{borderColor:paletteLight.secondary.main}}},MuiAppBar:{styleOverrides:{root:{color:paletteLight.primary.main}}},MuiTableCell:{styleOverrides:{root:{border:'none',borderTop:"0.5px solid ".concat(paletteLight.secondary.main),fontWeight:400,fontSize:'16px',lineHeight:'22px',letterSpacing:'1px',position:'relative',':after':{content:'""',position:'absolute',top:'10px',right:0,bottom:'10px',display:'block',borderRight:"0.5px solid ".concat(paletteLight.secondary.main)},':last-of-type:after':{content:'none'},':last-of-type':{overflowWrap:'anywhere'}},head:{border:'none'}}}}});var theme_dark=styles_createTheme(theme_common,{palette:paletteDark,typography:{allVariants:{color:paletteDark.text.primary},h4:{color:paletteDark.text.primary,fontWeight:700,fontSize:'24px',lineHeight:'33px',letterSpacing:'1px'}},components:{MuiListItem:{styleOverrides:{root:{color:paletteDark.primary.main,background:paletteDark.background.default}}},MuiButtonBase:{styleOverrides:{root:{color:paletteDark.primary.main,background:paletteDark.background.default}}},MuiDrawer:{styleOverrides:{root:{border:'none'},paper:{border:'none'}}},MuiDivider:{styleOverrides:{root:{background:paletteDark.primary.main,opacity:0.2,margin:'0 13px'}}},MuiTabs:{styleOverrides:{root:{height:'36px',minHeight:0,textTransform:'none'},indicator:{backgroundColor:'#59A588'}}},MuiTab:{styleOverrides:{root:{height:'36px',minHeight:0,fontWeight:400,fontSize:'16px',lineHeight:'22px',letterSpacing:'1px',textTransform:'none',padding:'7px 5px',color:'#8A8E91','&.Mui-selected':{color:paletteDark.text.primary}}}}}}); +var paletteDark={primary:{main:'#DFEBE9'},background:{default:'#222222',paper:'#222222'},text:{primary:'#DFEBE9'}};var paletteLight={primary:{main:'#222222'},secondary:{main:'#39403E'},background:{default:'#DFEBE9',paper:'#DFEBE9'},text:{primary:'#222222'}};var theme_common=styles_createTheme({consts:{appBarHeight:106,leftDrawerWidthOpen:224,leftDrawerWidthClosed:96},typography:{fontFamily:'Nunito Variable'},components:{MuiBackdrop:{styleOverrides:{root:{backgroundColor:'rgba(0, 0, 0, 0.65)'},invisible:{backgroundColor:'transparent'}}}}});var theme_light=styles_createTheme(theme_common,{palette:paletteLight,typography:{h1:{color:paletteLight.text.primary,fontWeight:700,fontSize:'32px',lineHeight:'44px',letterSpacing:'1px'},h2:{color:paletteLight.text.primary,fontWeight:700,fontSize:'20px',lineHeight:'27px',letterSpacing:'1px'},h5:{color:paletteLight.secondary.main,fontWeight:700,fontSize:'22px',lineHeight:'30px',letterSpacing:'1px'},h6:{color:paletteLight.secondary.main,fontWeight:800,fontSize:'20px',lineHeight:'27px'},subtitle1:{color:paletteLight.secondary.main},subtitle2:{fontSize:'14px',lineHeight:1.2}},components:{MuiDivider:{styleOverrides:{root:{borderColor:paletteLight.secondary.main}}},MuiTabs:{styleOverrides:{root:{height:'36px',minHeight:0,textTransform:'none'},indicator:{backgroundColor:'#59A588'}}},MuiTab:{styleOverrides:{root:{height:'36px',minHeight:0,fontWeight:400,fontSize:'16px',lineHeight:'22px',letterSpacing:'1px',textTransform:'none',padding:'7px 5px',color:'#8A8E91','&.Mui-selected':{color:paletteLight.text.primary}}}},MuiAppBar:{styleOverrides:{root:{color:paletteLight.primary.main}}},MuiTableCell:{styleOverrides:{root:{border:'none',borderTop:"0.5px solid ".concat(paletteLight.secondary.main),fontWeight:400,fontSize:'16px',lineHeight:'22px',letterSpacing:'1px',position:'relative',':after':{content:'""',position:'absolute',top:'10px',right:0,bottom:'10px',display:'block',borderRight:"0.5px solid ".concat(paletteLight.secondary.main)},':last-of-type:after':{content:'none'},':last-of-type':{overflowWrap:'anywhere'}},head:{border:'none'}}}}});var theme_dark=styles_createTheme(theme_common,{palette:paletteDark,typography:{allVariants:{color:paletteDark.text.primary},h4:{color:paletteDark.text.primary,fontWeight:700,fontSize:'24px',lineHeight:'33px',letterSpacing:'1px'}},components:{MuiListItem:{styleOverrides:{root:{color:paletteDark.primary.main,background:paletteDark.background.default}}},MuiButtonBase:{styleOverrides:{root:{color:paletteDark.primary.main,background:paletteDark.background.default}}},MuiDrawer:{styleOverrides:{root:{border:'none'},paper:{border:'none'}}},MuiDivider:{styleOverrides:{root:{background:paletteDark.primary.main,opacity:0.2,margin:'0 13px'}}},MuiTabs:{styleOverrides:{root:{height:'36px',minHeight:0,textTransform:'none'},indicator:{backgroundColor:'#59A588'}}},MuiTab:{styleOverrides:{root:{height:'36px',minHeight:0,fontWeight:400,fontSize:'16px',lineHeight:'22px',letterSpacing:'1px',textTransform:'none',padding:'7px 5px',color:'#8A8E91','&.Mui-selected':{color:paletteDark.text.primary}}}}}}); ;// CONCATENATED MODULE: ./src/components/LeftDrawer.tsx -var openedMixin=function openedMixin(theme){return{width:theme.consts.leftDrawerWidthOpen,transition:theme.transitions.create('width',{easing:theme.transitions.easing.sharp,duration:theme.transitions.duration.enteringScreen}),overflowX:'hidden'};};var closedMixin=function closedMixin(theme){return{transition:theme.transitions.create('width',{easing:theme.transitions.easing.sharp,duration:theme.transitions.duration.leavingScreen}),overflowX:'hidden',width:theme.consts.leftDrawerWidthClosed};};var DrawerHeader=styles_styled('div')(function(_ref){var theme=_ref.theme;return _objectSpread2({height:theme.consts.appBarHeight,padding:'31px 23px 0 23px'},theme.mixins.toolbar);});var LeftDrawer_AppBar=styles_styled(AppBar_AppBar,{shouldForwardProp:function shouldForwardProp(prop){return prop!=='open';}})(function(_ref2){var theme=_ref2.theme,open=_ref2.open;return _objectSpread2({height:theme.consts.appBarHeight,border:'none',boxShadow:'none',background:'transparent',padding:'40px 40px 0 40px',marginLeft:theme.consts.leftDrawerWidthClosed,justifyContent:'space-between',width:"calc(100% - ".concat(theme.consts.leftDrawerWidthClosed,"px)"),zIndex:theme.zIndex.drawer+1,transition:theme.transitions.create(['width','margin'],{easing:theme.transitions.easing.sharp,duration:theme.transitions.duration.leavingScreen})},open&&{marginLeft:theme.consts.leftDrawerWidthOpen,width:"calc(100% - ".concat(theme.consts.leftDrawerWidthOpen,"px)"),transition:theme.transitions.create(['width','margin'],{easing:theme.transitions.easing.sharp,duration:theme.transitions.duration.enteringScreen})});});var LeftDrawer_Drawer=styles_styled(Drawer_Drawer,{shouldForwardProp:function shouldForwardProp(prop){return prop!=='open';}})(function(_ref3){var theme=_ref3.theme,open=_ref3.open;return _objectSpread2(_objectSpread2({width:theme.consts.leftDrawerWidthOpen,flexShrink:0,whiteSpace:'nowrap',boxSizing:'border-box',borderRadius:'0px 20px 20px 0px'},open&&_objectSpread2(_objectSpread2({},openedMixin(theme)),{},{'& .MuiDrawer-paper':_objectSpread2(_objectSpread2({},openedMixin(theme)),{},{borderRadius:'0px 20px 20px 0px'})})),!open&&_objectSpread2(_objectSpread2({},closedMixin(theme)),{},{'& .MuiDrawer-paper':_objectSpread2(_objectSpread2({},closedMixin(theme)),{},{borderRadius:'0px 20px 20px 0px'})}));});function Item(props){return/*#__PURE__*/(0,jsx_runtime.jsx)(ListItem_ListItem,{component:Link,to:props.to,disablePadding:true,sx:{display:'block',margin:'14px 0'},children:/*#__PURE__*/(0,jsx_runtime.jsxs)(ListItemButton_ListItemButton,{sx:{height:'60px',justifyContent:'start',alignItems:'center',gap:'15px',padding:'0 24px'},children:[/*#__PURE__*/(0,jsx_runtime.jsx)(ListItemIcon_ListItemIcon,{sx:{minWidth:0,flex:'0 0 auto',justifyContent:'center',alignItems:'center'},children:props.icon}),/*#__PURE__*/(0,jsx_runtime.jsx)(ListItemText_ListItemText,{primary:props.text,sx:{display:props.open?'block':'none',margin:0},primaryTypographyProps:{fontWeight:500,fontSize:'24px',lineHeight:'33px',letterSpacing:'1px'}})]})},props.text);}function LeftDrawer(props){var _useState=(0,react.useState)(true),_useState2=slicedToArray_slicedToArray(_useState,2),open=_useState2[0],setOpen=_useState2[1];var location=dist_useLocation();var navigate=dist_useNavigate();var theme=styles_useTheme_useTheme();var toggleDrawer=function toggleDrawer(){setOpen(function(o){return!o;});};var path=location.pathname.split('/')[1];var header=null;switch(path){case'targets':header=/*#__PURE__*/(0,jsx_runtime.jsxs)(jsx_runtime.Fragment,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"h1",children:"Dashboard"}),/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{height:"40px",maxWidth:"314px",flexGrow:1,borderRadius:"10px",display:"flex",justifyContent:"space-between",alignItems:"center",padding:"0 9px 0 15px",sx:{background:theme.palette.background.default},children:[/*#__PURE__*/(0,jsx_runtime.jsx)("input",{type:"text",style:{background:'none',border:'none',outline:'none',height:'20px',lineHeight:'20px',fontSize:'18px'},placeholder:"Search"}),/*#__PURE__*/(0,jsx_runtime.jsx)(IconButton_IconButton,{sx:{padding:0,height:40,width:40},children:/*#__PURE__*/(0,jsx_runtime.jsx)(SearchIcon,{})})]})]});break;case'results':header=/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",alignItems:"center",justifyContent:"start",gap:"12px",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(IconButton_IconButton,{sx:{padding:0},onClick:function onClick(){return navigate('/targets');},children:/*#__PURE__*/(0,jsx_runtime.jsx)(ArrowLeftIcon,{})}),/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"h1",children:"Result Tree"})]});break;}return/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",height:"100%",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(LeftDrawer_AppBar,{position:"fixed",open:open,children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",justifyContent:"space-between",children:header})}),/*#__PURE__*/(0,jsx_runtime.jsx)(styles_ThemeProvider_ThemeProvider,{theme:theme_dark,children:/*#__PURE__*/(0,jsx_runtime.jsxs)(LeftDrawer_Drawer,{variant:"permanent",open:open,children:[/*#__PURE__*/(0,jsx_runtime.jsx)(DrawerHeader,{children:/*#__PURE__*/(0,jsx_runtime.jsxs)(IconButton_IconButton,{onClick:toggleDrawer,sx:{gap:'13px',padding:0},children:[/*#__PURE__*/(0,jsx_runtime.jsx)(KluctlLogo,{}),open&&/*#__PURE__*/(0,jsx_runtime.jsx)(KluctlText,{})]})}),/*#__PURE__*/(0,jsx_runtime.jsx)(Divider_Divider,{}),/*#__PURE__*/(0,jsx_runtime.jsx)(List_List,{sx:{padding:'10px 0 0 0'},children:/*#__PURE__*/(0,jsx_runtime.jsx)(Item,{text:"Targets",open:open,icon:/*#__PURE__*/(0,jsx_runtime.jsx)(TargetsIcon,{}),to:"targets"})})]})}),/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{component:"main",sx:{flexGrow:1,height:'100%',overflow:'hidden'},minWidth:0,children:[/*#__PURE__*/(0,jsx_runtime.jsx)(DrawerHeader,{}),/*#__PURE__*/(0,jsx_runtime.jsx)(Divider_Divider,{sx:{margin:'0 40px'}}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{width:"100%",height:"calc(100% - ".concat(theme.consts.appBarHeight,"px)"),overflow:"auto",children:props.content})]})]});} +var openedMixin=function openedMixin(theme){return{width:theme.consts.leftDrawerWidthOpen,transition:theme.transitions.create('width',{easing:theme.transitions.easing.sharp,duration:theme.transitions.duration.enteringScreen}),overflowX:'hidden'};};var closedMixin=function closedMixin(theme){return{transition:theme.transitions.create('width',{easing:theme.transitions.easing.sharp,duration:theme.transitions.duration.leavingScreen}),overflowX:'hidden',width:theme.consts.leftDrawerWidthClosed};};var DrawerHeader=styles_styled('div')(function(_ref){var theme=_ref.theme;return _objectSpread2({height:theme.consts.appBarHeight,padding:'31px 23px 0 23px'},theme.mixins.toolbar);});var LeftDrawer_AppBar=styles_styled(AppBar_AppBar,{shouldForwardProp:function shouldForwardProp(prop){return prop!=='open';}})(function(_ref2){var theme=_ref2.theme,open=_ref2.open;return _objectSpread2({height:theme.consts.appBarHeight,border:'none',boxShadow:'none',background:'transparent',padding:'40px 40px 0 40px',marginLeft:theme.consts.leftDrawerWidthClosed,justifyContent:'space-between',width:"calc(100% - ".concat(theme.consts.leftDrawerWidthClosed,"px)"),zIndex:theme.zIndex.drawer+1,transition:theme.transitions.create(['width','margin'],{easing:theme.transitions.easing.sharp,duration:theme.transitions.duration.leavingScreen})},open&&{marginLeft:theme.consts.leftDrawerWidthOpen,width:"calc(100% - ".concat(theme.consts.leftDrawerWidthOpen,"px)"),transition:theme.transitions.create(['width','margin'],{easing:theme.transitions.easing.sharp,duration:theme.transitions.duration.enteringScreen})});});var LeftDrawer_Drawer=styles_styled(Drawer_Drawer,{shouldForwardProp:function shouldForwardProp(prop){return prop!=='open';}})(function(_ref3){var theme=_ref3.theme,open=_ref3.open;return _objectSpread2(_objectSpread2({width:theme.consts.leftDrawerWidthOpen,flexShrink:0,whiteSpace:'nowrap',boxSizing:'border-box',borderRadius:'0px 20px 20px 0px'},open&&_objectSpread2(_objectSpread2({},openedMixin(theme)),{},{'& .MuiDrawer-paper':_objectSpread2(_objectSpread2({},openedMixin(theme)),{},{borderRadius:'0px 20px 20px 0px'})})),!open&&_objectSpread2(_objectSpread2({},closedMixin(theme)),{},{'& .MuiDrawer-paper':_objectSpread2(_objectSpread2({},closedMixin(theme)),{},{borderRadius:'0px 20px 20px 0px'})}));});function Item(props){return/*#__PURE__*/(0,jsx_runtime.jsx)(ListItem_ListItem,{component:Link,to:props.to,disablePadding:true,sx:{display:'block',margin:'14px 0'},children:/*#__PURE__*/(0,jsx_runtime.jsxs)(ListItemButton_ListItemButton,{sx:{height:'60px',justifyContent:'start',alignItems:'center',gap:'15px',padding:'0 24px'},children:[/*#__PURE__*/(0,jsx_runtime.jsx)(ListItemIcon_ListItemIcon,{sx:{minWidth:0,flex:'0 0 auto',justifyContent:'center',alignItems:'center'},children:props.icon}),/*#__PURE__*/(0,jsx_runtime.jsx)(ListItemText_ListItemText,{primary:props.text,sx:{display:props.open?'block':'none',margin:0},primaryTypographyProps:{fontWeight:500,fontSize:'24px',lineHeight:'33px',letterSpacing:'1px'}})]})},props.text);}function LeftDrawer(props){var _useState=(0,react.useState)(true),_useState2=slicedToArray_slicedToArray(_useState,2),open=_useState2[0],setOpen=_useState2[1];var location=dist_useLocation();var navigate=dist_useNavigate();var theme=styles_useTheme_useTheme();var toggleDrawer=function toggleDrawer(){setOpen(function(o){return!o;});};var path=location.pathname.split('/')[1];var header=null;switch(path){case'targets':header=/*#__PURE__*/(0,jsx_runtime.jsxs)(jsx_runtime.Fragment,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"h1",children:"Dashboard"}),/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{height:"40px",maxWidth:"314px",flexGrow:1,borderRadius:"10px",display:"flex",justifyContent:"space-between",alignItems:"center",padding:"0 9px 0 15px",sx:{background:theme.palette.background.default},children:[/*#__PURE__*/(0,jsx_runtime.jsx)("input",{type:"text",style:{background:'none',border:'none',outline:'none',height:'20px',lineHeight:'20px',fontSize:'18px'},placeholder:"Search (not impl. yet)"}),/*#__PURE__*/(0,jsx_runtime.jsx)(IconButton_IconButton,{sx:{padding:0,height:40,width:40},children:/*#__PURE__*/(0,jsx_runtime.jsx)(SearchIcon,{})})]})]});break;case'results':header=/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",alignItems:"center",justifyContent:"start",gap:"12px",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(IconButton_IconButton,{sx:{padding:0},onClick:function onClick(){return navigate('/targets');},children:/*#__PURE__*/(0,jsx_runtime.jsx)(ArrowLeftIcon,{})}),/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"h1",children:"Result Tree"})]});break;}return/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",height:"100%",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(LeftDrawer_AppBar,{position:"fixed",open:open,children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",justifyContent:"space-between",children:header})}),/*#__PURE__*/(0,jsx_runtime.jsx)(styles_ThemeProvider_ThemeProvider,{theme:theme_dark,children:/*#__PURE__*/(0,jsx_runtime.jsxs)(LeftDrawer_Drawer,{variant:"permanent",open:open,children:[/*#__PURE__*/(0,jsx_runtime.jsx)(DrawerHeader,{children:/*#__PURE__*/(0,jsx_runtime.jsxs)(IconButton_IconButton,{onClick:toggleDrawer,sx:{gap:'13px',padding:0},children:[/*#__PURE__*/(0,jsx_runtime.jsx)(KluctlLogo,{}),open&&/*#__PURE__*/(0,jsx_runtime.jsx)(KluctlText,{})]})}),/*#__PURE__*/(0,jsx_runtime.jsx)(Divider_Divider,{}),/*#__PURE__*/(0,jsx_runtime.jsx)(List_List,{sx:{padding:'10px 0 0 0'},children:/*#__PURE__*/(0,jsx_runtime.jsx)(Item,{text:"Targets",open:open,icon:/*#__PURE__*/(0,jsx_runtime.jsx)(TargetsIcon,{}),to:"targets"})})]})}),/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{component:"main",sx:{flexGrow:1,height:'100%',overflow:'hidden'},minWidth:0,children:[/*#__PURE__*/(0,jsx_runtime.jsx)(DrawerHeader,{}),/*#__PURE__*/(0,jsx_runtime.jsx)(Divider_Divider,{sx:{margin:'0 40px'}}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{width:"100%",height:"calc(100% - ".concat(theme.consts.appBarHeight,"px)"),overflow:"auto",children:props.content})]})]});} ;// CONCATENATED MODULE: ./src/models.ts /* Do not change, this code is generated from Golang structs */var DeploymentError=/*#__PURE__*/function(){function DeploymentError(){var source=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{};classCallCheck_classCallCheck(this,DeploymentError);this.ref=void 0;this.message=void 0;if('string'===typeof source)source=JSON.parse(source);this.ref=this.convertValues(source["ref"],models_ObjectRef);this.message=source["message"];}createClass_createClass(DeploymentError,[{key:"convertValues",value:function convertValues(a,classs){var _this=this;var asMap=arguments.length>2&&arguments[2]!==undefined?arguments[2]:false;if(!a){return a;}if(a.slice){return a.map(function(elem){return _this.convertValues(elem,classs);});}else if("object"===typeof a){if(asMap){for(var _i=0,_Object$keys=Object.keys(a);_i<_Object$keys.length;_i++){var _key=_Object$keys[_i];a[_key]=new classs(a[_key]);}return a;}return new classs(a);}return a;}}]);return DeploymentError;}();var Change=/*#__PURE__*/createClass_createClass(function Change(){var source=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{};classCallCheck_classCallCheck(this,Change);this.type=void 0;this.jsonPath=void 0;this.oldValue=void 0;this.newValue=void 0;this.unifiedDiff=void 0;if('string'===typeof source)source=JSON.parse(source);this.type=source["type"];this.jsonPath=source["jsonPath"];this.oldValue=source["oldValue"];this.newValue=source["newValue"];this.unifiedDiff=source["unifiedDiff"];});var ResultObject=/*#__PURE__*/function(){function ResultObject(){var source=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{};classCallCheck_classCallCheck(this,ResultObject);this.ref=void 0;this.changes=void 0;this.new=void 0;this.orphan=void 0;this.deleted=void 0;this.hook=void 0;this.rendered=void 0;this.remote=void 0;this.applied=void 0;if('string'===typeof source)source=JSON.parse(source);this.ref=this.convertValues(source["ref"],models_ObjectRef);this.changes=this.convertValues(source["changes"],Change);this.new=source["new"];this.orphan=source["orphan"];this.deleted=source["deleted"];this.hook=source["hook"];this.rendered=source["rendered"];this.remote=source["remote"];this.applied=source["applied"];}createClass_createClass(ResultObject,[{key:"convertValues",value:function convertValues(a,classs){var _this2=this;var asMap=arguments.length>2&&arguments[2]!==undefined?arguments[2]:false;if(!a){return a;}if(a.slice){return a.map(function(elem){return _this2.convertValues(elem,classs);});}else if("object"===typeof a){if(asMap){for(var _i2=0,_Object$keys2=Object.keys(a);_i2<_Object$keys2.length;_i2++){var _key2=_Object$keys2[_i2];a[_key2]=new classs(a[_key2]);}return a;}return new classs(a);}return a;}}]);return ResultObject;}();var IgnoreForDiffItemConfig=/*#__PURE__*/createClass_createClass(function IgnoreForDiffItemConfig(){var source=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{};classCallCheck_classCallCheck(this,IgnoreForDiffItemConfig);this.fieldPath=void 0;this.fieldPathRegex=void 0;this.group=void 0;this.kind=void 0;this.name=void 0;this.namespace=void 0;if('string'===typeof source)source=JSON.parse(source);this.fieldPath=source["fieldPath"];this.fieldPathRegex=source["fieldPathRegex"];this.group=source["group"];this.kind=source["kind"];this.name=source["name"];this.namespace=source["namespace"];});var HelmChartConfig=/*#__PURE__*/createClass_createClass(function HelmChartConfig(){var source=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{};classCallCheck_classCallCheck(this,HelmChartConfig);this.repo=void 0;this.path=void 0;this.credentialsId=void 0;this.chartName=void 0;this.chartVersion=void 0;this.updateConstraints=void 0;this.releaseName=void 0;this.namespace=void 0;this.output=void 0;this.skipCRDs=void 0;this.skipUpdate=void 0;this.skipPrePull=void 0;if('string'===typeof source)source=JSON.parse(source);this.repo=source["repo"];this.path=source["path"];this.credentialsId=source["credentialsId"];this.chartName=source["chartName"];this.chartVersion=source["chartVersion"];this.updateConstraints=source["updateConstraints"];this.releaseName=source["releaseName"];this.namespace=source["namespace"];this.output=source["output"];this.skipCRDs=source["skipCRDs"];this.skipUpdate=source["skipUpdate"];this.skipPrePull=source["skipPrePull"];});var DeleteObjectItemConfig=/*#__PURE__*/createClass_createClass(function DeleteObjectItemConfig(){var source=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{};classCallCheck_classCallCheck(this,DeleteObjectItemConfig);this.group=void 0;this.kind=void 0;this.name=void 0;this.namespace=void 0;if('string'===typeof source)source=JSON.parse(source);this.group=source["group"];this.kind=source["kind"];this.name=source["name"];this.namespace=source["namespace"];});var GitProject=/*#__PURE__*/createClass_createClass(function GitProject(){var source=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{};classCallCheck_classCallCheck(this,GitProject);this.url=void 0;this.ref=void 0;this.subDir=void 0;if('string'===typeof source)source=JSON.parse(source);this.url=source["url"];this.ref=source["ref"];this.subDir=source["subDir"];});var DeploymentItemConfig=/*#__PURE__*/function(){function DeploymentItemConfig(){var source=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{};classCallCheck_classCallCheck(this,DeploymentItemConfig);this.path=void 0;this.include=void 0;this.git=void 0;this.tags=void 0;this.barrier=void 0;this.message=void 0;this.waitReadiness=void 0;this.vars=void 0;this.skipDeleteIfTags=void 0;this.onlyRender=void 0;this.alwaysDeploy=void 0;this.deleteObjects=void 0;this.when=void 0;this.renderedHelmChartConfig=void 0;this.renderedObjects=void 0;this.renderedInclude=void 0;if('string'===typeof source)source=JSON.parse(source);this.path=source["path"];this.include=source["include"];this.git=this.convertValues(source["git"],GitProject);this.tags=source["tags"];this.barrier=source["barrier"];this.message=source["message"];this.waitReadiness=source["waitReadiness"];this.vars=this.convertValues(source["vars"],VarsSource);this.skipDeleteIfTags=source["skipDeleteIfTags"];this.onlyRender=source["onlyRender"];this.alwaysDeploy=source["alwaysDeploy"];this.deleteObjects=this.convertValues(source["deleteObjects"],DeleteObjectItemConfig);this.when=source["when"];this.renderedHelmChartConfig=this.convertValues(source["renderedHelmChartConfig"],HelmChartConfig);this.renderedObjects=this.convertValues(source["renderedObjects"],models_ObjectRef);this.renderedInclude=this.convertValues(source["renderedInclude"],DeploymentProjectConfig);}createClass_createClass(DeploymentItemConfig,[{key:"convertValues",value:function convertValues(a,classs){var _this3=this;var asMap=arguments.length>2&&arguments[2]!==undefined?arguments[2]:false;if(!a){return a;}if(a.slice){return a.map(function(elem){return _this3.convertValues(elem,classs);});}else if("object"===typeof a){if(asMap){for(var _i3=0,_Object$keys3=Object.keys(a);_i3<_Object$keys3.length;_i3++){var _key3=_Object$keys3[_i3];a[_key3]=new classs(a[_key3]);}return a;}return new classs(a);}return a;}}]);return DeploymentItemConfig;}();var SealedSecretsConfig=/*#__PURE__*/createClass_createClass(function SealedSecretsConfig(){var source=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{};classCallCheck_classCallCheck(this,SealedSecretsConfig);this.outputPattern=void 0;if('string'===typeof source)source=JSON.parse(source);this.outputPattern=source["outputPattern"];});var VarsSourceVault=/*#__PURE__*/createClass_createClass(function VarsSourceVault(){var source=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{};classCallCheck_classCallCheck(this,VarsSourceVault);this.address=void 0;this.path=void 0;if('string'===typeof source)source=JSON.parse(source);this.address=source["address"];this.path=source["path"];});var VarsSourceAwsSecretsManager=/*#__PURE__*/createClass_createClass(function VarsSourceAwsSecretsManager(){var source=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{};classCallCheck_classCallCheck(this,VarsSourceAwsSecretsManager);this.secretName=void 0;this.region=void 0;this.profile=void 0;if('string'===typeof source)source=JSON.parse(source);this.secretName=source["secretName"];this.region=source["region"];this.profile=source["profile"];});var VarsSourceHttp=/*#__PURE__*/createClass_createClass(function VarsSourceHttp(){var source=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{};classCallCheck_classCallCheck(this,VarsSourceHttp);this.url=void 0;this.method=void 0;this.body=void 0;this.headers=void 0;this.jsonPath=void 0;if('string'===typeof source)source=JSON.parse(source);this.url=source["url"];this.method=source["method"];this.body=source["body"];this.headers=source["headers"];this.jsonPath=source["jsonPath"];});var VarsSourceClusterConfigMapOrSecret=/*#__PURE__*/createClass_createClass(function VarsSourceClusterConfigMapOrSecret(){var source=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{};classCallCheck_classCallCheck(this,VarsSourceClusterConfigMapOrSecret);this.name=void 0;this.labels=void 0;this.namespace=void 0;this.key=void 0;this.targetPath=void 0;if('string'===typeof source)source=JSON.parse(source);this.name=source["name"];this.labels=source["labels"];this.namespace=source["namespace"];this.key=source["key"];this.targetPath=source["targetPath"];});var VarsSourceGit=/*#__PURE__*/createClass_createClass(function VarsSourceGit(){var source=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{};classCallCheck_classCallCheck(this,VarsSourceGit);this.url=void 0;this.ref=void 0;this.path=void 0;if('string'===typeof source)source=JSON.parse(source);this.url=source["url"];this.ref=source["ref"];this.path=source["path"];});var VarsSource=/*#__PURE__*/function(){function VarsSource(){var source=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{};classCallCheck_classCallCheck(this,VarsSource);this.ignoreMissing=void 0;this.noOverride=void 0;this.values=void 0;this.file=void 0;this.git=void 0;this.clusterConfigMap=void 0;this.clusterSecret=void 0;this.systemEnvVars=void 0;this.http=void 0;this.awsSecretsManager=void 0;this.vault=void 0;this.when=void 0;this.renderedVars=void 0;if('string'===typeof source)source=JSON.parse(source);this.ignoreMissing=source["ignoreMissing"];this.noOverride=source["noOverride"];this.values=source["values"];this.file=source["file"];this.git=this.convertValues(source["git"],VarsSourceGit);this.clusterConfigMap=this.convertValues(source["clusterConfigMap"],VarsSourceClusterConfigMapOrSecret);this.clusterSecret=this.convertValues(source["clusterSecret"],VarsSourceClusterConfigMapOrSecret);this.systemEnvVars=source["systemEnvVars"];this.http=this.convertValues(source["http"],VarsSourceHttp);this.awsSecretsManager=this.convertValues(source["awsSecretsManager"],VarsSourceAwsSecretsManager);this.vault=this.convertValues(source["vault"],VarsSourceVault);this.when=source["when"];this.renderedVars=source["renderedVars"];}createClass_createClass(VarsSource,[{key:"convertValues",value:function convertValues(a,classs){var _this4=this;var asMap=arguments.length>2&&arguments[2]!==undefined?arguments[2]:false;if(!a){return a;}if(a.slice){return a.map(function(elem){return _this4.convertValues(elem,classs);});}else if("object"===typeof a){if(asMap){for(var _i4=0,_Object$keys4=Object.keys(a);_i4<_Object$keys4.length;_i4++){var _key4=_Object$keys4[_i4];a[_key4]=new classs(a[_key4]);}return a;}return new classs(a);}return a;}}]);return VarsSource;}();var DeploymentProjectConfig=/*#__PURE__*/function(){function DeploymentProjectConfig(){var source=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{};classCallCheck_classCallCheck(this,DeploymentProjectConfig);this.vars=void 0;this.sealedSecrets=void 0;this.when=void 0;this.deployments=void 0;this.commonLabels=void 0;this.commonAnnotations=void 0;this.overrideNamespace=void 0;this.tags=void 0;this.ignoreForDiff=void 0;if('string'===typeof source)source=JSON.parse(source);this.vars=this.convertValues(source["vars"],VarsSource);this.sealedSecrets=this.convertValues(source["sealedSecrets"],SealedSecretsConfig);this.when=source["when"];this.deployments=this.convertValues(source["deployments"],DeploymentItemConfig);this.commonLabels=source["commonLabels"];this.commonAnnotations=source["commonAnnotations"];this.overrideNamespace=source["overrideNamespace"];this.tags=source["tags"];this.ignoreForDiff=this.convertValues(source["ignoreForDiff"],IgnoreForDiffItemConfig);}createClass_createClass(DeploymentProjectConfig,[{key:"convertValues",value:function convertValues(a,classs){var _this5=this;var asMap=arguments.length>2&&arguments[2]!==undefined?arguments[2]:false;if(!a){return a;}if(a.slice){return a.map(function(elem){return _this5.convertValues(elem,classs);});}else if("object"===typeof a){if(asMap){for(var _i5=0,_Object$keys5=Object.keys(a);_i5<_Object$keys5.length;_i5++){var _key5=_Object$keys5[_i5];a[_key5]=new classs(a[_key5]);}return a;}return new classs(a);}return a;}}]);return DeploymentProjectConfig;}();var ClusterInfo=/*#__PURE__*/createClass_createClass(function ClusterInfo(){var source=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{};classCallCheck_classCallCheck(this,ClusterInfo);this.clusterId=void 0;if('string'===typeof source)source=JSON.parse(source);this.clusterId=source["clusterId"];});var GitInfo=/*#__PURE__*/createClass_createClass(function GitInfo(){var source=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{};classCallCheck_classCallCheck(this,GitInfo);this.url=void 0;this.ref=void 0;this.subDir=void 0;this.commit=void 0;this.dirty=void 0;if('string'===typeof source)source=JSON.parse(source);this.url=source["url"];this.ref=source["ref"];this.subDir=source["subDir"];this.commit=source["commit"];this.dirty=source["dirty"];});var KluctlDeploymentInfo=/*#__PURE__*/createClass_createClass(function KluctlDeploymentInfo(){var source=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{};classCallCheck_classCallCheck(this,KluctlDeploymentInfo);this.name=void 0;this.namespace=void 0;if('string'===typeof source)source=JSON.parse(source);this.name=source["name"];this.namespace=source["namespace"];});var CommandInfo=/*#__PURE__*/function(){function CommandInfo(){var source=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{};classCallCheck_classCallCheck(this,CommandInfo);this.initiator=void 0;this.startTime=void 0;this.endTime=void 0;this.kluctlDeployment=void 0;this.command=void 0;this.target=void 0;this.targetNameOverride=void 0;this.contextOverride=void 0;this.args=void 0;this.images=void 0;this.dryRun=void 0;this.noWait=void 0;this.forceApply=void 0;this.replaceOnError=void 0;this.forceReplaceOnError=void 0;this.abortOnError=void 0;this.includeTags=void 0;this.excludeTags=void 0;this.includeDeploymentDirs=void 0;this.excludeDeploymentDirs=void 0;if('string'===typeof source)source=JSON.parse(source);this.initiator=source["initiator"];this.startTime=source["startTime"];this.endTime=source["endTime"];this.kluctlDeployment=this.convertValues(source["kluctlDeployment"],KluctlDeploymentInfo);this.command=source["command"];this.target=source["target"];this.targetNameOverride=source["targetNameOverride"];this.contextOverride=source["contextOverride"];this.args=source["args"];this.images=this.convertValues(source["images"],FixedImage);this.dryRun=source["dryRun"];this.noWait=source["noWait"];this.forceApply=source["forceApply"];this.replaceOnError=source["replaceOnError"];this.forceReplaceOnError=source["forceReplaceOnError"];this.abortOnError=source["abortOnError"];this.includeTags=source["includeTags"];this.excludeTags=source["excludeTags"];this.includeDeploymentDirs=source["includeDeploymentDirs"];this.excludeDeploymentDirs=source["excludeDeploymentDirs"];}createClass_createClass(CommandInfo,[{key:"convertValues",value:function convertValues(a,classs){var _this6=this;var asMap=arguments.length>2&&arguments[2]!==undefined?arguments[2]:false;if(!a){return a;}if(a.slice){return a.map(function(elem){return _this6.convertValues(elem,classs);});}else if("object"===typeof a){if(asMap){for(var _i6=0,_Object$keys6=Object.keys(a);_i6<_Object$keys6.length;_i6++){var _key6=_Object$keys6[_i6];a[_key6]=new classs(a[_key6]);}return a;}return new classs(a);}return a;}}]);return CommandInfo;}();var models_ObjectRef=/*#__PURE__*/createClass_createClass(function ObjectRef(){var source=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{};classCallCheck_classCallCheck(this,ObjectRef);this.group=void 0;this.version=void 0;this.kind=void 0;this.name=void 0;this.namespace=void 0;if('string'===typeof source)source=JSON.parse(source);this.group=source["group"];this.version=source["version"];this.kind=source["kind"];this.name=source["name"];this.namespace=source["namespace"];});var FixedImage=/*#__PURE__*/function(){function FixedImage(){var source=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{};classCallCheck_classCallCheck(this,FixedImage);this.image=void 0;this.resultImage=void 0;this.deployedImage=void 0;this.namespace=void 0;this.object=void 0;this.deployment=void 0;this.container=void 0;this.deployTags=void 0;this.deploymentDir=void 0;if('string'===typeof source)source=JSON.parse(source);this.image=source["image"];this.resultImage=source["resultImage"];this.deployedImage=source["deployedImage"];this.namespace=source["namespace"];this.object=this.convertValues(source["object"],models_ObjectRef);this.deployment=source["deployment"];this.container=source["container"];this.deployTags=source["deployTags"];this.deploymentDir=source["deploymentDir"];}createClass_createClass(FixedImage,[{key:"convertValues",value:function convertValues(a,classs){var _this7=this;var asMap=arguments.length>2&&arguments[2]!==undefined?arguments[2]:false;if(!a){return a;}if(a.slice){return a.map(function(elem){return _this7.convertValues(elem,classs);});}else if("object"===typeof a){if(asMap){for(var _i7=0,_Object$keys7=Object.keys(a);_i7<_Object$keys7.length;_i7++){var _key7=_Object$keys7[_i7];a[_key7]=new classs(a[_key7]);}return a;}return new classs(a);}return a;}}]);return FixedImage;}();var SealingConfig=/*#__PURE__*/createClass_createClass(function SealingConfig(){var source=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{};classCallCheck_classCallCheck(this,SealingConfig);this.args=void 0;this.secretSets=void 0;this.certFile=void 0;if('string'===typeof source)source=JSON.parse(source);this.args=source["args"];this.secretSets=source["secretSets"];this.certFile=source["certFile"];});var Target=/*#__PURE__*/function(){function Target(){var source=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{};classCallCheck_classCallCheck(this,Target);this.name=void 0;this.context=void 0;this.args=void 0;this.sealingConfig=void 0;this.images=void 0;this.discriminator=void 0;if('string'===typeof source)source=JSON.parse(source);this.name=source["name"];this.context=source["context"];this.args=source["args"];this.sealingConfig=this.convertValues(source["sealingConfig"],SealingConfig);this.images=this.convertValues(source["images"],FixedImage);this.discriminator=source["discriminator"];}createClass_createClass(Target,[{key:"convertValues",value:function convertValues(a,classs){var _this8=this;var asMap=arguments.length>2&&arguments[2]!==undefined?arguments[2]:false;if(!a){return a;}if(a.slice){return a.map(function(elem){return _this8.convertValues(elem,classs);});}else if("object"===typeof a){if(asMap){for(var _i8=0,_Object$keys8=Object.keys(a);_i8<_Object$keys8.length;_i8++){var _key8=_Object$keys8[_i8];a[_key8]=new classs(a[_key8]);}return a;}return new classs(a);}return a;}}]);return Target;}();var TargetKey=/*#__PURE__*/createClass_createClass(function TargetKey(){var source=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{};classCallCheck_classCallCheck(this,TargetKey);this.targetName=void 0;this.clusterId=void 0;this.discriminator=void 0;if('string'===typeof source)source=JSON.parse(source);this.targetName=source["targetName"];this.clusterId=source["clusterId"];this.discriminator=source["discriminator"];});var ProjectKey=/*#__PURE__*/createClass_createClass(function ProjectKey(){var source=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{};classCallCheck_classCallCheck(this,ProjectKey);this.gitRepoKey=void 0;this.subDir=void 0;if('string'===typeof source)source=JSON.parse(source);this.gitRepoKey=source["gitRepoKey"];this.subDir=source["subDir"];});var CommandResult=/*#__PURE__*/function(){function CommandResult(){var source=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{};classCallCheck_classCallCheck(this,CommandResult);this.id=void 0;this.projectKey=void 0;this.targetKey=void 0;this.target=void 0;this.command=void 0;this.gitInfo=void 0;this.clusterInfo=void 0;this.deployment=void 0;this.objects=void 0;this.errors=void 0;this.warnings=void 0;this.seenImages=void 0;if('string'===typeof source)source=JSON.parse(source);this.id=source["id"];this.projectKey=this.convertValues(source["projectKey"],ProjectKey);this.targetKey=this.convertValues(source["targetKey"],TargetKey);this.target=this.convertValues(source["target"],Target);this.command=this.convertValues(source["command"],CommandInfo);this.gitInfo=this.convertValues(source["gitInfo"],GitInfo);this.clusterInfo=this.convertValues(source["clusterInfo"],ClusterInfo);this.deployment=this.convertValues(source["deployment"],DeploymentProjectConfig);this.objects=this.convertValues(source["objects"],ResultObject);this.errors=this.convertValues(source["errors"],DeploymentError);this.warnings=this.convertValues(source["warnings"],DeploymentError);this.seenImages=this.convertValues(source["seenImages"],FixedImage);}createClass_createClass(CommandResult,[{key:"convertValues",value:function convertValues(a,classs){var _this9=this;var asMap=arguments.length>2&&arguments[2]!==undefined?arguments[2]:false;if(!a){return a;}if(a.slice){return a.map(function(elem){return _this9.convertValues(elem,classs);});}else if("object"===typeof a){if(asMap){for(var _i9=0,_Object$keys9=Object.keys(a);_i9<_Object$keys9.length;_i9++){var _key9=_Object$keys9[_i9];a[_key9]=new classs(a[_key9]);}return a;}return new classs(a);}return a;}}]);return CommandResult;}();var CommandResultSummary=/*#__PURE__*/function(){function CommandResultSummary(){var source=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{};classCallCheck_classCallCheck(this,CommandResultSummary);this.id=void 0;this.projectKey=void 0;this.targetKey=void 0;this.target=void 0;this.commandInfo=void 0;this.gitInfo=void 0;this.clusterInfo=void 0;this.renderedObjects=void 0;this.remoteObjects=void 0;this.appliedObjects=void 0;this.appliedHookObjects=void 0;this.newObjects=void 0;this.changedObjects=void 0;this.orphanObjects=void 0;this.deletedObjects=void 0;this.errors=void 0;this.warnings=void 0;this.totalChanges=void 0;if('string'===typeof source)source=JSON.parse(source);this.id=source["id"];this.projectKey=this.convertValues(source["projectKey"],ProjectKey);this.targetKey=this.convertValues(source["targetKey"],TargetKey);this.target=this.convertValues(source["target"],Target);this.commandInfo=this.convertValues(source["commandInfo"],CommandInfo);this.gitInfo=this.convertValues(source["gitInfo"],GitInfo);this.clusterInfo=this.convertValues(source["clusterInfo"],ClusterInfo);this.renderedObjects=source["renderedObjects"];this.remoteObjects=source["remoteObjects"];this.appliedObjects=source["appliedObjects"];this.appliedHookObjects=source["appliedHookObjects"];this.newObjects=source["newObjects"];this.changedObjects=source["changedObjects"];this.orphanObjects=source["orphanObjects"];this.deletedObjects=source["deletedObjects"];this.errors=this.convertValues(source["errors"],DeploymentError);this.warnings=this.convertValues(source["warnings"],DeploymentError);this.totalChanges=source["totalChanges"];}createClass_createClass(CommandResultSummary,[{key:"convertValues",value:function convertValues(a,classs){var _this10=this;var asMap=arguments.length>2&&arguments[2]!==undefined?arguments[2]:false;if(!a){return a;}if(a.slice){return a.map(function(elem){return _this10.convertValues(elem,classs);});}else if("object"===typeof a){if(asMap){for(var _i10=0,_Object$keys10=Object.keys(a);_i10<_Object$keys10.length;_i10++){var _key10=_Object$keys10[_i10];a[_key10]=new classs(a[_key10]);}return a;}return new classs(a);}return a;}}]);return CommandResultSummary;}();var ChangedObject=/*#__PURE__*/(/* unused pure expression or super */ null && (function(){function ChangedObject(){var source=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{};_classCallCheck(this,ChangedObject);this.ref=void 0;this.changes=void 0;if('string'===typeof source)source=JSON.parse(source);this.ref=this.convertValues(source["ref"],models_ObjectRef);this.changes=this.convertValues(source["changes"],Change);}_createClass(ChangedObject,[{key:"convertValues",value:function convertValues(a,classs){var _this11=this;var asMap=arguments.length>2&&arguments[2]!==undefined?arguments[2]:false;if(!a){return a;}if(a.slice){return a.map(function(elem){return _this11.convertValues(elem,classs);});}else if("object"===typeof a){if(asMap){for(var _i11=0,_Object$keys11=Object.keys(a);_i11<_Object$keys11.length;_i11++){var _key11=_Object$keys11[_i11];a[_key11]=new classs(a[_key11]);}return a;}return new classs(a);}return a;}}]);return ChangedObject;}()));var ValidateResultEntry=/*#__PURE__*/(/* unused pure expression or super */ null && (function(){function ValidateResultEntry(){var source=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{};_classCallCheck(this,ValidateResultEntry);this.ref=void 0;this.annotation=void 0;this.message=void 0;if('string'===typeof source)source=JSON.parse(source);this.ref=this.convertValues(source["ref"],models_ObjectRef);this.annotation=source["annotation"];this.message=source["message"];}_createClass(ValidateResultEntry,[{key:"convertValues",value:function convertValues(a,classs){var _this12=this;var asMap=arguments.length>2&&arguments[2]!==undefined?arguments[2]:false;if(!a){return a;}if(a.slice){return a.map(function(elem){return _this12.convertValues(elem,classs);});}else if("object"===typeof a){if(asMap){for(var _i12=0,_Object$keys12=Object.keys(a);_i12<_Object$keys12.length;_i12++){var _key12=_Object$keys12[_i12];a[_key12]=new classs(a[_key12]);}return a;}return new classs(a);}return a;}}]);return ValidateResultEntry;}()));var ValidateResult=/*#__PURE__*/(/* unused pure expression or super */ null && (function(){function ValidateResult(){var source=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{};_classCallCheck(this,ValidateResult);this.id=void 0;this.startTime=void 0;this.endTime=void 0;this.ready=void 0;this.warnings=void 0;this.errors=void 0;this.results=void 0;this.drift=void 0;if('string'===typeof source)source=JSON.parse(source);this.id=source["id"];this.startTime=source["startTime"];this.endTime=source["endTime"];this.ready=source["ready"];this.warnings=this.convertValues(source["warnings"],DeploymentError);this.errors=this.convertValues(source["errors"],DeploymentError);this.results=this.convertValues(source["results"],ValidateResultEntry);this.drift=this.convertValues(source["drift"],ChangedObject);}_createClass(ValidateResult,[{key:"convertValues",value:function convertValues(a,classs){var _this13=this;var asMap=arguments.length>2&&arguments[2]!==undefined?arguments[2]:false;if(!a){return a;}if(a.slice){return a.map(function(elem){return _this13.convertValues(elem,classs);});}else if("object"===typeof a){if(asMap){for(var _i13=0,_Object$keys13=Object.keys(a);_i13<_Object$keys13.length;_i13++){var _key13=_Object$keys13[_i13];a[_key13]=new classs(a[_key13]);}return a;}return new classs(a);}return a;}}]);return ValidateResult;}()));var ShortName=/*#__PURE__*/(/* unused pure expression or super */ null && (_createClass(function ShortName(){var source=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{};_classCallCheck(this,ShortName);this.group=void 0;this.kind=void 0;this.shortName=void 0;if('string'===typeof source)source=JSON.parse(source);this.group=source["group"];this.kind=source["kind"];this.shortName=source["shortName"];})));var UnstructuredObject=/*#__PURE__*/(/* unused pure expression or super */ null && (_createClass(function UnstructuredObject(){var source=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{};_classCallCheck(this,UnstructuredObject);if('string'===typeof source)source=JSON.parse(source);})));var ProjectTargetKey=/*#__PURE__*/function(){function ProjectTargetKey(){var source=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{};classCallCheck_classCallCheck(this,ProjectTargetKey);this.project=void 0;this.target=void 0;if('string'===typeof source)source=JSON.parse(source);this.project=this.convertValues(source["project"],ProjectKey);this.target=this.convertValues(source["target"],TargetKey);}createClass_createClass(ProjectTargetKey,[{key:"convertValues",value:function convertValues(a,classs){var _this14=this;var asMap=arguments.length>2&&arguments[2]!==undefined?arguments[2]:false;if(!a){return a;}if(a.slice){return a.map(function(elem){return _this14.convertValues(elem,classs);});}else if("object"===typeof a){if(asMap){for(var _i14=0,_Object$keys14=Object.keys(a);_i14<_Object$keys14.length;_i14++){var _key14=_Object$keys14[_i14];a[_key14]=new classs(a[_key14]);}return a;}return new classs(a);}return a;}}]);return ProjectTargetKey;}(); // EXTERNAL MODULE: ./node_modules/lodash/lodash.js @@ -52222,8 +52318,27 @@ var Loading=function Loading(){return/*#__PURE__*/(0,jsx_runtime.jsx)(material_B function useAppOutletContext(){return useOutletContext();}var AppContext=/*#__PURE__*/(0,react.createContext)({summaries:new Map(),projects:[],validateResults:new Map()});var ApiContext=/*#__PURE__*/(0,react.createContext)(new StaticApi());var LoggedInApp=function LoggedInApp(props){var api=(0,react.useContext)(ApiContext);var _useState=(0,react.useState)(),_useState2=slicedToArray_slicedToArray(_useState,2),filters=_useState2[0],setFilters=_useState2[1];var summariesRef=(0,react.useRef)(new Map());var validateResultsRef=(0,react.useRef)(new Map());var _useState3=(0,react.useState)(summariesRef.current),_useState4=slicedToArray_slicedToArray(_useState3,2),summaries=_useState4[0],setSummaries=_useState4[1];var _useState5=(0,react.useState)(validateResultsRef.current),_useState6=slicedToArray_slicedToArray(_useState5,2),validateResults=_useState6[0],setValidateResults=_useState6[1];var onUnauthorized=props.onUnauthorized;(0,react.useEffect)(function(){var updateSummary=function updateSummary(rs){console.log("update_summary",rs.id,rs.commandInfo.startTime);summariesRef.current.set(rs.id,rs);setSummaries(new Map(summariesRef.current));};var deleteSummary=function deleteSummary(id){console.log("delete_summary",id);summariesRef.current.delete(id);setSummaries(new Map(summariesRef.current));};var updateValidateResult=function updateValidateResult(key,vr){console.log("validate_result",key);validateResultsRef.current.set(JSON.stringify(key),vr);setValidateResults(new Map(validateResultsRef.current));};console.log("starting listenResults");var cancel;cancel=api.listenUpdates(undefined,undefined,function(msg){switch(msg.type){case"update_summary":updateSummary(msg.summary);break;case"delete_summary":deleteSummary(msg.id);break;case"validate_result":updateValidateResult(msg.key,msg.result);break;case"auth_result":if(!msg.success){cancel.then(function(c){return c();});onUnauthorized();}}});return function(){console.log("cancel listenResults");cancel.then(function(c){return c();});};},[api,onUnauthorized]);var projects=(0,react.useMemo)(function(){return buildProjectSummaries(summaries,validateResults);},[summaries,validateResults]);var appContext={summaries:summariesRef.current,projects:projects,validateResults:validateResults};var outletContext={filters:filters,setFilters:setFilters};return/*#__PURE__*/(0,jsx_runtime.jsx)(AppContext.Provider,{value:appContext,children:/*#__PURE__*/(0,jsx_runtime.jsx)(styles_ThemeProvider_ThemeProvider,{theme:theme_light,children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{width:"100%",height:"100%",children:/*#__PURE__*/(0,jsx_runtime.jsx)(LeftDrawer,{content:/*#__PURE__*/(0,jsx_runtime.jsx)(Outlet,{context:outletContext}),context:outletContext})})})});};var App=function App(){var _useState7=(0,react.useState)(),_useState8=slicedToArray_slicedToArray(_useState7,2),api=_useState8[0],setApi=_useState8[1];var _useState9=(0,react.useState)(false),_useState10=slicedToArray_slicedToArray(_useState9,2),needToken=_useState10[0],setNeedToken=_useState10[1];var storage=localStorage;var getToken=function getToken(){var token=storage.getItem("token");if(!token){return"";}return JSON.parse(token);};var setToken=function setToken(token){if(!token){storage.removeItem("token");}else{storage.setItem("token",JSON.stringify(token));}};var onUnauthorized=function onUnauthorized(){console.log("handle onUnauthorized");setToken(undefined);setApi(undefined);setNeedToken(true);};var onTokenRefresh=function onTokenRefresh(newToken){console.log("handle onTokenRefresh");setToken(newToken);};var handleLoginSucceeded=function handleLoginSucceeded(token){console.log("handle saveToken");setToken(token);setApi(new RealApi(getToken,onUnauthorized,onTokenRefresh));};(0,react.useEffect)(function(){if(api){return;}var doInit=/*#__PURE__*/function(){var _ref=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee(){var isStatic,noAuthApi;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee$(_context){while(1)switch(_context.prev=_context.next){case 0:_context.next=2;return checkStaticBuild();case 2:isStatic=_context.sent;if(!isStatic){_context.next=7;break;}setApi(new StaticApi());_context.next=19;break;case 7:// check if we don't need auth (running locally?) noAuthApi=new RealApi(undefined,undefined,undefined);_context.prev=8;_context.next=11;return noAuthApi.getShortNames();case 11:setToken(undefined);setNeedToken(false);setApi(noAuthApi);_context.next=19;break;case 16:_context.prev=16;_context.t0=_context["catch"](8);if(!getToken()){setNeedToken(true);}else{setApi(new RealApi(getToken,onUnauthorized,onTokenRefresh));}case 19:case"end":return _context.stop();}},_callee,null,[[8,16]]);}));return function doInit(){return _ref.apply(this,arguments);};}();doInit();// eslint-disable-next-line react-hooks/exhaustive-deps },[]);if(needToken&&!getToken()){return/*#__PURE__*/(0,jsx_runtime.jsx)(Login,{setToken:handleLoginSucceeded});}if(!api){return/*#__PURE__*/(0,jsx_runtime.jsx)(Loading,{});}return/*#__PURE__*/(0,jsx_runtime.jsx)(ApiContext.Provider,{value:api,children:/*#__PURE__*/(0,jsx_runtime.jsx)(LoggedInApp,{onUnauthorized:onUnauthorized})});};/* harmony default export */ var components_App = (App); +;// CONCATENATED MODULE: ./node_modules/@babel/runtime/helpers/esm/objectWithoutProperties.js + +function objectWithoutProperties_objectWithoutProperties(source, excluded) { + if (source == null) return {}; + var target = objectWithoutPropertiesLoose_objectWithoutPropertiesLoose(source, excluded); + var key, i; + if (Object.getOwnPropertySymbols) { + var sourceSymbolKeys = Object.getOwnPropertySymbols(source); + for (i = 0; i < sourceSymbolKeys.length; i++) { + key = sourceSymbolKeys[i]; + if (excluded.indexOf(key) >= 0) continue; + if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; + target[key] = source[key]; + } + } + return target; +} +;// CONCATENATED MODULE: ./src/components/targets-view/Card.tsx +var Card_excluded=["sx"];var cardWidth=247;var projectCardHeight=80;var cardHeight=126;var cardGap=20;function CardPaper(props){var sx=props.sx,rest=objectWithoutProperties_objectWithoutProperties(props,Card_excluded);return/*#__PURE__*/(0,jsx_runtime.jsx)(Paper_Paper,_objectSpread2({elevation:5,sx:_objectSpread2({width:"100%",height:"100%",borderRadius:'12px',border:'1px solid #59A588',boxShadow:'4px 4px 10px #1E617A'},sx)},rest));}function Card(props){return/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,_objectSpread2({display:"flex",flexShrink:0,width:cardWidth,height:cardHeight},props));}function CardCol(props){return/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,_objectSpread2({display:"flex",flexDirection:"column",gap:"".concat(cardGap,"px")},props));}function CardRow(props){return/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,_objectSpread2({display:"flex",gap:"".concat(cardGap,"px")},props));} ;// CONCATENATED MODULE: ./src/components/targets-view/Projects.tsx -var ProjectItem=function ProjectItem(props){var name=getLastPathElement(props.ps.project.gitRepoKey);var subDir=props.ps.project.subDir;var projectInfo=/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{children:[props.ps.project.gitRepoKey,/*#__PURE__*/(0,jsx_runtime.jsx)("br",{}),props.ps.project.subDir?/*#__PURE__*/(0,jsx_runtime.jsxs)(jsx_runtime.Fragment,{children:["SubDir: ",props.ps.project.subDir,/*#__PURE__*/(0,jsx_runtime.jsx)("br",{})]}):/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{})]});return/*#__PURE__*/(0,jsx_runtime.jsx)(Paper_Paper,{elevation:5,sx:{width:"100%",height:"100%",borderRadius:'12px',border:'1px solid #59A588',boxShadow:'4px 4px 10px #1E617A',padding:'5px 16px'},children:/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",flexDirection:"column",justifyContent:"center",height:"100%",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",alignItems:"center",gap:"15px",children:name&&/*#__PURE__*/(0,jsx_runtime.jsxs)(jsx_runtime.Fragment,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{flexShrink:0,children:/*#__PURE__*/(0,jsx_runtime.jsx)(ProjectIcon,{})}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:projectInfo,children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"h6",textAlign:"left",textOverflow:"ellipsis",overflow:"hidden",flexGrow:1,children:name})})]})}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{children:subDir?/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{textAlign:"center",children:subDir}):/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{})}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{marginLeft:"auto"})})})]})});}; +var ProjectItem=function ProjectItem(props){var name=getLastPathElement(props.ps.project.gitRepoKey);var subDir=props.ps.project.subDir;var projectInfo=/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{children:[props.ps.project.gitRepoKey,/*#__PURE__*/(0,jsx_runtime.jsx)("br",{}),props.ps.project.subDir?/*#__PURE__*/(0,jsx_runtime.jsxs)(jsx_runtime.Fragment,{children:["SubDir: ",props.ps.project.subDir,/*#__PURE__*/(0,jsx_runtime.jsx)("br",{})]}):/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{})]});return/*#__PURE__*/(0,jsx_runtime.jsx)(CardPaper,{sx:{padding:'5px 16px'},children:/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",flexDirection:"column",justifyContent:"center",height:"100%",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",alignItems:"center",gap:"15px",children:name&&/*#__PURE__*/(0,jsx_runtime.jsxs)(jsx_runtime.Fragment,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{flexShrink:0,children:/*#__PURE__*/(0,jsx_runtime.jsx)(ProjectIcon,{})}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:projectInfo,children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"h6",textAlign:"left",textOverflow:"ellipsis",overflow:"hidden",flexGrow:1,children:name})})]})}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{children:subDir?/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{textAlign:"center",children:subDir}):/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{})}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{marginLeft:"auto"})})})]})});}; ;// CONCATENATED MODULE: ./node_modules/@mui/material/SvgIcon/svgIconClasses.js @@ -53373,7 +53488,7 @@ var ActionsMenu=function ActionsMenu(props){var _React$useState=react.useState(n ;// CONCATENATED MODULE: ./src/utils/duration.ts function formatDuration(ms,withMs){if(ms<0)ms=-ms;var time={day:Math.floor(ms/86400000),hour:Math.floor(ms/3600000)%24,minute:Math.floor(ms/60000)%60,second:Math.floor(ms/1000)%60,millisecond:withMs?Math.floor(ms)%1000:0};return Object.entries(time).filter(function(val){return val[1]!==0;}).map(function(val){return val[1]+' '+(val[1]!==1?val[0]+'s':val[0]);}).join(', ');}function formatDurationShort(ms){if(ms<0)ms=-ms;var time={d:Math.floor(ms/86400000),h:Math.floor(ms/3600000)%24,m:Math.floor(ms/60000)%60,s:Math.floor(ms/1000)%60,ms:Math.floor(ms)%1000};var f=Object.entries(time).find(function(val){return val[1]>0;});if(f===undefined){return"0s";}return f[1]+f[0];}var calcAgo=function calcAgo(startTime){var t1=new Date(startTime);var t2=new Date();var d=t2.getTime()-t1.getTime();return formatDurationShort(d);}; ;// CONCATENATED MODULE: ./src/components/targets-view/Targets.tsx -var StatusIcon=function StatusIcon(props){var icon;var theme=styles_useTheme_useTheme();if(props.ts.lastValidateResult===undefined){icon=/*#__PURE__*/(0,jsx_runtime.jsx)(MessageQuestionIcon,{color:theme.palette.error.main});}else if(props.ts.lastValidateResult.ready&&!props.ts.lastValidateResult.errors){var _props$ts$lastValidat,_props$ts$lastValidat2;if((_props$ts$lastValidat=props.ts.lastValidateResult.warnings)!==null&&_props$ts$lastValidat!==void 0&&_props$ts$lastValidat.length){icon=/*#__PURE__*/(0,jsx_runtime.jsx)(Favorite,{color:"warning"});}else if((_props$ts$lastValidat2=props.ts.lastValidateResult.drift)!==null&&_props$ts$lastValidat2!==void 0&&_props$ts$lastValidat2.length){icon=/*#__PURE__*/(0,jsx_runtime.jsx)(Favorite,{color:"primary"});}else{icon=/*#__PURE__*/(0,jsx_runtime.jsx)(Favorite,{color:"success"});}}else{icon=/*#__PURE__*/(0,jsx_runtime.jsx)(HeartBroken,{color:"error"});}var tooltip=[];if(props.ts.lastValidateResult===undefined){tooltip.push("No validation result available.");}else{var _props$ts$lastValidat3,_props$ts$lastValidat4,_props$ts$lastValidat5,_props$ts$lastValidat6;if(props.ts.lastValidateResult.ready&&!((_props$ts$lastValidat3=props.ts.lastValidateResult.errors)!==null&&_props$ts$lastValidat3!==void 0&&_props$ts$lastValidat3.length)){tooltip.push("Target is ready.");}else{tooltip.push("Target is not ready.");}if((_props$ts$lastValidat4=props.ts.lastValidateResult.errors)!==null&&_props$ts$lastValidat4!==void 0&&_props$ts$lastValidat4.length){tooltip.push("Target has ".concat(props.ts.lastValidateResult.errors.length," validation errors."));}if((_props$ts$lastValidat5=props.ts.lastValidateResult.warnings)!==null&&_props$ts$lastValidat5!==void 0&&_props$ts$lastValidat5.length){tooltip.push("Target has ".concat(props.ts.lastValidateResult.warnings.length," validation warnings."));}if((_props$ts$lastValidat6=props.ts.lastValidateResult.drift)!==null&&_props$ts$lastValidat6!==void 0&&_props$ts$lastValidat6.length){tooltip.push("Target has ".concat(props.ts.lastValidateResult.drift.length," drifted objects."));}tooltip.push("Validation performed "+calcAgo(props.ts.lastValidateResult.startTime)+" ago");}return/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:tooltip.map(function(t){return/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{children:t},t);}),children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",children:icon})});};var TargetItem=function TargetItem(props){var _props$ts$commandResu,_props$ts$commandResu2;var api=(0,react.useContext)(ApiContext);var actionMenuItems=[];actionMenuItems.push({icon:/*#__PURE__*/(0,jsx_runtime.jsx)(PublishedWithChanges,{}),text:"Validate now",handler:function handler(){api.validateNow(props.ps.project,props.ts.target);}});var kd;var allKdEqual=true;(_props$ts$commandResu=props.ts.commandResults)===null||_props$ts$commandResu===void 0?void 0:_props$ts$commandResu.forEach(function(rs){if(rs.commandInfo.kluctlDeployment){if(!kd){kd=rs.commandInfo.kluctlDeployment;}else{if(kd.name!==rs.commandInfo.kluctlDeployment.name||kd.namespace!==rs.commandInfo.kluctlDeployment.namespace){allKdEqual=false;}}}});if(kd&&allKdEqual){actionMenuItems.push({icon:/*#__PURE__*/(0,jsx_runtime.jsx)(PublishedWithChanges,{}),text:"Reconcile",handler:function handler(){api.reconcileNow(props.ts.target.clusterId,kd.name,kd.namespace);}});actionMenuItems.push({icon:/*#__PURE__*/(0,jsx_runtime.jsx)(PublishedWithChanges,{}),text:"Deploy",handler:function handler(){api.deployNow(props.ts.target.clusterId,kd.name,kd.namespace);}});}var allContexts=[];var handleContext=function handleContext(c){if(!c){return;}if(allContexts.find(function(x){return x===c;})){return;}allContexts.push(c);};(_props$ts$commandResu2=props.ts.commandResults)===null||_props$ts$commandResu2===void 0?void 0:_props$ts$commandResu2.forEach(function(rs){handleContext(rs.commandInfo.contextOverride);handleContext(rs.target.context);});var contextTooltip=/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{textAlign:"center",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"subtitle2",children:"All known contexts:"}),allContexts.map(function(context){return/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"subtitle2",children:context},context);})]});var targetName=props.ts.target.targetName;if(!targetName){targetName="";}return/*#__PURE__*/(0,jsx_runtime.jsx)(Paper_Paper,{elevation:5,sx:{width:"100%",height:"100%",borderRadius:'12px',border:'1px solid #59A588',boxShadow:'4px 4px 10px #1E617A',padding:'20px 16px 12px 16px'},onClick:function onClick(e){return props.onSelectTarget(props.ts);},children:/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",flexDirection:"column",justifyContent:"space-between",height:"100%",children:[/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",gap:"15px",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{flexShrink:0,children:/*#__PURE__*/(0,jsx_runtime.jsx)(TargetIcon,{})}),/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{flexGrow:1,children:[/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"h6",textAlign:"left",textOverflow:"ellipsis",overflow:"hidden",flexGrow:1,children:targetName}),allContexts.length?/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:contextTooltip,children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"subtitle1",textAlign:"left",textOverflow:"ellipsis",overflow:"hidden",whiteSpace:"nowrap",fontSize:"14px",fontWeight:500,lineHeight:"19px",children:allContexts[0]})}):/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{})]})]}),/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",alignItems:"center",justifyContent:"space-between",children:[/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",gap:"6px",alignItems:"center",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:"Cluster ID: "+props.ts.target.clusterId,children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",children:/*#__PURE__*/(0,jsx_runtime.jsx)(CpuIcon,{})})}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:"Discriminator: "+props.ts.target.discriminator,children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",children:/*#__PURE__*/(0,jsx_runtime.jsx)(FingerScanIcon,{})})})]}),/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",gap:"6px",alignItems:"center",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(StatusIcon,_objectSpread2({},props)),/*#__PURE__*/(0,jsx_runtime.jsx)(ActionsMenu,{menuItems:actionMenuItems})]})]})]})});}; +var StatusIcon=function StatusIcon(props){var icon;var theme=styles_useTheme_useTheme();if(props.ts.lastValidateResult===undefined){icon=/*#__PURE__*/(0,jsx_runtime.jsx)(MessageQuestionIcon,{color:theme.palette.error.main});}else if(props.ts.lastValidateResult.ready&&!props.ts.lastValidateResult.errors){var _props$ts$lastValidat,_props$ts$lastValidat2;if((_props$ts$lastValidat=props.ts.lastValidateResult.warnings)!==null&&_props$ts$lastValidat!==void 0&&_props$ts$lastValidat.length){icon=/*#__PURE__*/(0,jsx_runtime.jsx)(Favorite,{color:"warning"});}else if((_props$ts$lastValidat2=props.ts.lastValidateResult.drift)!==null&&_props$ts$lastValidat2!==void 0&&_props$ts$lastValidat2.length){icon=/*#__PURE__*/(0,jsx_runtime.jsx)(Favorite,{color:"primary"});}else{icon=/*#__PURE__*/(0,jsx_runtime.jsx)(Favorite,{color:"success"});}}else{icon=/*#__PURE__*/(0,jsx_runtime.jsx)(HeartBroken,{color:"error"});}var tooltip=[];if(props.ts.lastValidateResult===undefined){tooltip.push("No validation result available.");}else{var _props$ts$lastValidat3,_props$ts$lastValidat4,_props$ts$lastValidat5,_props$ts$lastValidat6;if(props.ts.lastValidateResult.ready&&!((_props$ts$lastValidat3=props.ts.lastValidateResult.errors)!==null&&_props$ts$lastValidat3!==void 0&&_props$ts$lastValidat3.length)){tooltip.push("Target is ready.");}else{tooltip.push("Target is not ready.");}if((_props$ts$lastValidat4=props.ts.lastValidateResult.errors)!==null&&_props$ts$lastValidat4!==void 0&&_props$ts$lastValidat4.length){tooltip.push("Target has ".concat(props.ts.lastValidateResult.errors.length," validation errors."));}if((_props$ts$lastValidat5=props.ts.lastValidateResult.warnings)!==null&&_props$ts$lastValidat5!==void 0&&_props$ts$lastValidat5.length){tooltip.push("Target has ".concat(props.ts.lastValidateResult.warnings.length," validation warnings."));}if((_props$ts$lastValidat6=props.ts.lastValidateResult.drift)!==null&&_props$ts$lastValidat6!==void 0&&_props$ts$lastValidat6.length){tooltip.push("Target has ".concat(props.ts.lastValidateResult.drift.length," drifted objects."));}tooltip.push("Validation performed "+calcAgo(props.ts.lastValidateResult.startTime)+" ago");}return/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:tooltip.map(function(t){return/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{children:t},t);}),children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",children:icon})});};var TargetItem=function TargetItem(props){var _props$ts$commandResu,_props$ts$commandResu2;var api=(0,react.useContext)(ApiContext);var actionMenuItems=[];actionMenuItems.push({icon:/*#__PURE__*/(0,jsx_runtime.jsx)(PublishedWithChanges,{}),text:"Validate now",handler:function handler(){api.validateNow(props.ps.project,props.ts.target);}});var kd;var allKdEqual=true;(_props$ts$commandResu=props.ts.commandResults)===null||_props$ts$commandResu===void 0?void 0:_props$ts$commandResu.forEach(function(rs){if(rs.commandInfo.kluctlDeployment){if(!kd){kd=rs.commandInfo.kluctlDeployment;}else{if(kd.name!==rs.commandInfo.kluctlDeployment.name||kd.namespace!==rs.commandInfo.kluctlDeployment.namespace){allKdEqual=false;}}}});if(kd&&allKdEqual){actionMenuItems.push({icon:/*#__PURE__*/(0,jsx_runtime.jsx)(PublishedWithChanges,{}),text:"Reconcile",handler:function handler(){api.reconcileNow(props.ts.target.clusterId,kd.name,kd.namespace);}});actionMenuItems.push({icon:/*#__PURE__*/(0,jsx_runtime.jsx)(PublishedWithChanges,{}),text:"Deploy",handler:function handler(){api.deployNow(props.ts.target.clusterId,kd.name,kd.namespace);}});}var allContexts=[];var handleContext=function handleContext(c){if(!c){return;}if(allContexts.find(function(x){return x===c;})){return;}allContexts.push(c);};(_props$ts$commandResu2=props.ts.commandResults)===null||_props$ts$commandResu2===void 0?void 0:_props$ts$commandResu2.forEach(function(rs){handleContext(rs.commandInfo.contextOverride);handleContext(rs.target.context);});var contextTooltip=/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{textAlign:"center",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"subtitle2",children:"All known contexts:"}),allContexts.map(function(context){return/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"subtitle2",children:context},context);})]});var targetName=props.ts.target.targetName;if(!targetName){targetName="";}return/*#__PURE__*/(0,jsx_runtime.jsx)(CardPaper,{sx:{padding:'20px 16px 12px 16px'},onClick:function onClick(e){return props.onSelectTarget(props.ts);},children:/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",flexDirection:"column",justifyContent:"space-between",height:"100%",children:[/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",gap:"15px",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{flexShrink:0,children:/*#__PURE__*/(0,jsx_runtime.jsx)(TargetIcon,{})}),/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{flexGrow:1,children:[/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"h6",textAlign:"left",textOverflow:"ellipsis",overflow:"hidden",flexGrow:1,children:targetName}),allContexts.length?/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:contextTooltip,children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"subtitle1",textAlign:"left",textOverflow:"ellipsis",overflow:"hidden",whiteSpace:"nowrap",fontSize:"14px",fontWeight:500,lineHeight:"19px",children:allContexts[0]})}):/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{})]})]}),/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",alignItems:"center",justifyContent:"space-between",children:[/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",gap:"6px",alignItems:"center",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:"Cluster ID: "+props.ts.target.clusterId,children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",children:/*#__PURE__*/(0,jsx_runtime.jsx)(CpuIcon,{})})}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:"Discriminator: "+props.ts.target.discriminator,children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",children:/*#__PURE__*/(0,jsx_runtime.jsx)(FingerScanIcon,{})})})]}),/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",gap:"6px",alignItems:"center",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(StatusIcon,_objectSpread2({},props)),/*#__PURE__*/(0,jsx_runtime.jsx)(ActionsMenu,{menuItems:actionMenuItems})]})]})]})});}; ;// CONCATENATED MODULE: ./node_modules/js-yaml/dist/js-yaml.mjs /*! js-yaml 4.1.0 https://github.com/nodeca/js-yaml @license MIT */ function isNothing(subject) { @@ -56487,23 +56602,6 @@ var jsYaml = { }; /* harmony default export */ var js_yaml = ((/* unused pure expression or super */ null && (jsYaml))); -;// CONCATENATED MODULE: ./node_modules/@babel/runtime/helpers/esm/objectWithoutProperties.js - -function objectWithoutProperties_objectWithoutProperties(source, excluded) { - if (source == null) return {}; - var target = objectWithoutPropertiesLoose_objectWithoutPropertiesLoose(source, excluded); - var key, i; - if (Object.getOwnPropertySymbols) { - var sourceSymbolKeys = Object.getOwnPropertySymbols(source); - for (i = 0; i < sourceSymbolKeys.length; i++) { - key = sourceSymbolKeys[i]; - if (excluded.indexOf(key) >= 0) continue; - if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; - target[key] = source[key]; - } - } - return target; -} ;// CONCATENATED MODULE: ./node_modules/react-syntax-highlighter/dist/esm/create-element.js @@ -57167,28 +57265,21 @@ esm_light.registerLanguage('yaml',hljs_yaml);esm_light.registerLanguage('diff',h ;// CONCATENATED MODULE: ./src/components/result-view/CommandResultStatusLine.tsx var StatusLine=function StatusLine(props){var children=[];var doPush=function doPush(n,t,icon){if(n){children.push(/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",flexDirection:"column",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:n+" "+t,children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",alignItems:"center",justifyContent:"center",width:"24px",height:"24px",children:icon})}),/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{fontSize:"10px",align:"center",children:n})]},t));}};doPush(props.errors,"total errors.",/*#__PURE__*/(0,jsx_runtime.jsx)(ErrorIcon,{}));doPush(props.warnings,"total warnings.",/*#__PURE__*/(0,jsx_runtime.jsx)(WarningIcon,{}));doPush(props.newObjects,"new objects.",/*#__PURE__*/(0,jsx_runtime.jsx)(AddedIcon,{}));doPush(props.deletedObjects,"deleted objects.",/*#__PURE__*/(0,jsx_runtime.jsx)(TrashIcon,{}));doPush(props.orphanObjects,"orphan objects.",/*#__PURE__*/(0,jsx_runtime.jsx)(OrphanIcon,{}));doPush(props.changedObjects,"changed objects.",/*#__PURE__*/(0,jsx_runtime.jsx)(ChangedIcon,{}));return/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",width:"100%",children:children});};var CommandResultStatusLine=function CommandResultStatusLine(props){var _props$rs$errors,_props$rs$warnings;return/*#__PURE__*/(0,jsx_runtime.jsx)(StatusLine,{errors:(_props$rs$errors=props.rs.errors)===null||_props$rs$errors===void 0?void 0:_props$rs$errors.length,warnings:(_props$rs$warnings=props.rs.warnings)===null||_props$rs$warnings===void 0?void 0:_props$rs$warnings.length,changedObjects:props.rs.changedObjects,newObjects:props.rs.newObjects,deletedObjects:props.rs.deletedObjects,orphanObjects:props.rs.orphanObjects});};var ValidateResultStatusLine=function ValidateResultStatusLine(props){var _props$vr,_props$vr$errors,_props$vr2,_props$vr2$warnings,_props$vr3,_props$vr3$drift;return/*#__PURE__*/_jsx(StatusLine,{errors:(_props$vr=props.vr)===null||_props$vr===void 0?void 0:(_props$vr$errors=_props$vr.errors)===null||_props$vr$errors===void 0?void 0:_props$vr$errors.length,warnings:(_props$vr2=props.vr)===null||_props$vr2===void 0?void 0:(_props$vr2$warnings=_props$vr2.warnings)===null||_props$vr2$warnings===void 0?void 0:_props$vr2$warnings.length,changedObjects:(_props$vr3=props.vr)===null||_props$vr3===void 0?void 0:(_props$vr3$drift=_props$vr3.drift)===null||_props$vr3$drift===void 0?void 0:_props$vr3$drift.length});}; ;// CONCATENATED MODULE: ./src/components/targets-view/CommandResultItem.tsx -var CommandResultItem=/*#__PURE__*/react.memo(function(props){var _rs$commandInfo,_rs$commandInfo2;var rs=props.rs,onSelectCommandResult=props.onSelectCommandResult,selected=props.selected;var navigate=dist_useNavigate();var _useState=(0,react.useState)(calcAgo(rs.commandInfo.startTime)),_useState2=slicedToArray_slicedToArray(_useState,2),ago=_useState2[0],setAgo=_useState2[1];var Icon=DiffIcon;switch((_rs$commandInfo=rs.commandInfo)===null||_rs$commandInfo===void 0?void 0:_rs$commandInfo.command){case"delete":Icon=PruneIcon;break;case"deploy":Icon=DeployIcon;break;case"diff":Icon=DiffIcon;break;case"poke-images":Icon=DeployIcon;break;case"prune":Icon=PruneIcon;break;}var iconTooltip=(0,react.useMemo)(function(){var cmdInfoYaml=dump(rs.commandInfo);return/*#__PURE__*/(0,jsx_runtime.jsx)(CodeViewer,{code:cmdInfoYaml,language:"yaml"});},[rs.commandInfo]);(0,react.useEffect)(function(){var interval=setInterval(function(){return setAgo(calcAgo(rs.commandInfo.startTime));},5000);return function(){return clearInterval(interval);};},[rs.commandInfo.startTime]);return/*#__PURE__*/(0,jsx_runtime.jsx)(Paper_Paper,{elevation:5,sx:{width:"100%",height:"100%",borderRadius:'12px',border:'1px solid #59A588',boxShadow:'4px 4px 10px #1E617A',padding:'20px 16px 5px 16px',outline:selected?'8px solid #59A588':'none'},onClick:function onClick(){return onSelectCommandResult(rs);},children:/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",flexDirection:"column",justifyContent:"space-between",height:"100%",children:[/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",gap:"15px",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:iconTooltip,children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{width:"45px",height:"45px",flex:"0 0 auto",justifyContent:"center",alignItems:"center",children:/*#__PURE__*/(0,jsx_runtime.jsx)(Icon,{})})}),/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{flexGrow:1,children:[/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"h6",textAlign:"left",textOverflow:"ellipsis",overflow:"hidden",flexGrow:1,children:(_rs$commandInfo2=rs.commandInfo)===null||_rs$commandInfo2===void 0?void 0:_rs$commandInfo2.command}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:rs.commandInfo.startTime,children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"subtitle1",textAlign:"left",textOverflow:"ellipsis",overflow:"hidden",whiteSpace:"nowrap",fontSize:"14px",fontWeight:500,lineHeight:"19px",children:ago})})]})]}),/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",alignItems:"center",justifyContent:"space-between",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",gap:"6px",alignItems:"center",children:/*#__PURE__*/(0,jsx_runtime.jsx)(CommandResultStatusLine,{rs:rs})}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",gap:"6px",alignItems:"center",height:"39px",children:/*#__PURE__*/(0,jsx_runtime.jsx)(IconButton_IconButton,{onClick:function onClick(e){e.stopPropagation();navigate("/results/".concat(rs.id));},sx:{padding:0,width:26,height:26},children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:"Open Result Tree",children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",children:/*#__PURE__*/(0,jsx_runtime.jsx)(TreeViewIcon,{})})})})})]})]})});}); -;// CONCATENATED MODULE: ./node_modules/@mui/material/Table/TableContext.js +var CommandResultItemHeader=/*#__PURE__*/react.memo(function(props){var _rs$commandInfo,_rs$commandInfo2;var rs=props.rs;var _useState=(0,react.useState)(calcAgo(rs.commandInfo.startTime)),_useState2=slicedToArray_slicedToArray(_useState,2),ago=_useState2[0],setAgo=_useState2[1];var Icon=DiffIcon;switch((_rs$commandInfo=rs.commandInfo)===null||_rs$commandInfo===void 0?void 0:_rs$commandInfo.command){case"delete":Icon=PruneIcon;break;case"deploy":Icon=DeployIcon;break;case"diff":Icon=DiffIcon;break;case"poke-images":Icon=DeployIcon;break;case"prune":Icon=PruneIcon;break;}var iconTooltip=(0,react.useMemo)(function(){var cmdInfoYaml=dump(rs.commandInfo);return/*#__PURE__*/(0,jsx_runtime.jsx)(CodeViewer,{code:cmdInfoYaml,language:"yaml"});},[rs.commandInfo]);(0,react.useEffect)(function(){var interval=setInterval(function(){return setAgo(calcAgo(rs.commandInfo.startTime));},5000);return function(){return clearInterval(interval);};},[rs.commandInfo.startTime]);return/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",gap:"15px",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:iconTooltip,children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{width:"45px",height:"45px",flex:"0 0 auto",justifyContent:"center",alignItems:"center",children:/*#__PURE__*/(0,jsx_runtime.jsx)(Icon,{})})}),/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"h6",textAlign:"left",textOverflow:"ellipsis",overflow:"hidden",children:(_rs$commandInfo2=rs.commandInfo)===null||_rs$commandInfo2===void 0?void 0:_rs$commandInfo2.command}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:rs.commandInfo.startTime,children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"subtitle1",textAlign:"left",textOverflow:"ellipsis",overflow:"hidden",whiteSpace:"nowrap",fontSize:"14px",fontWeight:500,lineHeight:"19px",children:ago})})]})]});});var CommandResultItem=/*#__PURE__*/react.memo(function(props){var rs=props.rs,onSelectCommandResult=props.onSelectCommandResult;var navigate=dist_useNavigate();return/*#__PURE__*/(0,jsx_runtime.jsx)(CardPaper,{sx:{padding:'20px 16px 5px 16px'},onClick:function onClick(){return onSelectCommandResult(rs);},children:/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",flexDirection:"column",justifyContent:"space-between",height:"100%",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(CommandResultItemHeader,{rs:rs}),/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",alignItems:"center",justifyContent:"space-between",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",gap:"6px",alignItems:"center",children:/*#__PURE__*/(0,jsx_runtime.jsx)(CommandResultStatusLine,{rs:rs})}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",gap:"6px",alignItems:"center",height:"39px",children:/*#__PURE__*/(0,jsx_runtime.jsx)(IconButton_IconButton,{onClick:function onClick(e){e.stopPropagation();navigate("/results/".concat(rs.id));},sx:{padding:0,width:26,height:26},children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:"Open Result Tree",children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",children:/*#__PURE__*/(0,jsx_runtime.jsx)(TreeViewIcon,{})})})})})]})]})});}); +;// CONCATENATED MODULE: ./node_modules/@mui/material/Tab/tabClasses.js -/** - * @ignore - internal component. - */ -var TableContext = /*#__PURE__*/react.createContext(); -if (false) {} -/* harmony default export */ var Table_TableContext = (TableContext); -;// CONCATENATED MODULE: ./node_modules/@mui/material/Table/tableClasses.js +function getTabUtilityClass(slot) { + return generateUtilityClass_generateUtilityClass('MuiTab', slot); +} +var tabClasses = generateUtilityClasses('MuiTab', ['root', 'labelIcon', 'textColorInherit', 'textColorPrimary', 'textColorSecondary', 'selected', 'disabled', 'fullWidth', 'wrapped', 'iconWrapper']); +/* harmony default export */ var Tab_tabClasses = (tabClasses); +;// CONCATENATED MODULE: ./node_modules/@mui/material/Tab/Tab.js -function getTableUtilityClass(slot) { - return generateUtilityClass_generateUtilityClass('MuiTable', slot); -} -var tableClasses = generateUtilityClasses('MuiTable', ['root', 'stickyHeader']); -/* harmony default export */ var Table_tableClasses = ((/* unused pure expression or super */ null && (tableClasses))); -;// CONCATENATED MODULE: ./node_modules/@mui/material/Table/Table.js +var Tab_excluded = ["className", "disabled", "disableFocusRipple", "fullWidth", "icon", "iconPosition", "indicator", "label", "onChange", "onClick", "onFocus", "selected", "selectionFollowsFocus", "textColor", "value", "wrapped"]; -var Table_excluded = ["className", "component", "padding", "size", "stickyHeader"]; @@ -57198,171 +57289,428 @@ var Table_excluded = ["className", "component", "padding", "size", "stickyHeader -var Table_useUtilityClasses = function useUtilityClasses(ownerState) { + +var Tab_useUtilityClasses = function useUtilityClasses(ownerState) { var classes = ownerState.classes, - stickyHeader = ownerState.stickyHeader; + textColor = ownerState.textColor, + fullWidth = ownerState.fullWidth, + wrapped = ownerState.wrapped, + icon = ownerState.icon, + label = ownerState.label, + selected = ownerState.selected, + disabled = ownerState.disabled; var slots = { - root: ['root', stickyHeader && 'stickyHeader'] + root: ['root', icon && label && 'labelIcon', "textColor".concat(utils_capitalize(textColor)), fullWidth && 'fullWidth', wrapped && 'wrapped', selected && 'selected', disabled && 'disabled'], + iconWrapper: ['iconWrapper'] }; - return composeClasses(slots, getTableUtilityClass, classes); + return composeClasses(slots, getTabUtilityClass, classes); }; -var TableRoot = styles_styled('table', { - name: 'MuiTable', +var TabRoot = styles_styled(ButtonBase_ButtonBase, { + name: 'MuiTab', slot: 'Root', overridesResolver: function overridesResolver(props, styles) { var ownerState = props.ownerState; - return [styles.root, ownerState.stickyHeader && styles.stickyHeader]; + return [styles.root, ownerState.label && ownerState.icon && styles.labelIcon, styles["textColor".concat(utils_capitalize(ownerState.textColor))], ownerState.fullWidth && styles.fullWidth, ownerState.wrapped && styles.wrapped]; } })(function (_ref) { + var _ref3, _ref4, _ref5; var theme = _ref.theme, ownerState = _ref.ownerState; - return extends_extends({ - display: 'table', - width: '100%', - borderCollapse: 'collapse', - borderSpacing: 0, - '& caption': extends_extends({}, theme.typography.body2, { - padding: theme.spacing(2), - color: (theme.vars || theme).palette.text.secondary, - textAlign: 'left', - captionSide: 'bottom' - }) - }, ownerState.stickyHeader && { - borderCollapse: 'separate' + return extends_extends({}, theme.typography.button, { + maxWidth: 360, + minWidth: 90, + position: 'relative', + minHeight: 48, + flexShrink: 0, + padding: '12px 16px', + overflow: 'hidden', + whiteSpace: 'normal', + textAlign: 'center' + }, ownerState.label && { + flexDirection: ownerState.iconPosition === 'top' || ownerState.iconPosition === 'bottom' ? 'column' : 'row' + }, { + lineHeight: 1.25 + }, ownerState.icon && ownerState.label && defineProperty_defineProperty({ + minHeight: 72, + paddingTop: 9, + paddingBottom: 9 + }, "& > .".concat(Tab_tabClasses.iconWrapper), extends_extends({}, ownerState.iconPosition === 'top' && { + marginBottom: 6 + }, ownerState.iconPosition === 'bottom' && { + marginTop: 6 + }, ownerState.iconPosition === 'start' && { + marginRight: theme.spacing(1) + }, ownerState.iconPosition === 'end' && { + marginLeft: theme.spacing(1) + })), ownerState.textColor === 'inherit' && (_ref3 = { + color: 'inherit', + opacity: 0.6 + }, defineProperty_defineProperty(_ref3, "&.".concat(Tab_tabClasses.selected), { + opacity: 1 + }), defineProperty_defineProperty(_ref3, "&.".concat(Tab_tabClasses.disabled), { + opacity: (theme.vars || theme).palette.action.disabledOpacity + }), _ref3), ownerState.textColor === 'primary' && (_ref4 = { + color: (theme.vars || theme).palette.text.secondary + }, defineProperty_defineProperty(_ref4, "&.".concat(Tab_tabClasses.selected), { + color: (theme.vars || theme).palette.primary.main + }), defineProperty_defineProperty(_ref4, "&.".concat(Tab_tabClasses.disabled), { + color: (theme.vars || theme).palette.text.disabled + }), _ref4), ownerState.textColor === 'secondary' && (_ref5 = { + color: (theme.vars || theme).palette.text.secondary + }, defineProperty_defineProperty(_ref5, "&.".concat(Tab_tabClasses.selected), { + color: (theme.vars || theme).palette.secondary.main + }), defineProperty_defineProperty(_ref5, "&.".concat(Tab_tabClasses.disabled), { + color: (theme.vars || theme).palette.text.disabled + }), _ref5), ownerState.fullWidth && { + flexShrink: 1, + flexGrow: 1, + flexBasis: 0, + maxWidth: 'none' + }, ownerState.wrapped && { + fontSize: theme.typography.pxToRem(12) }); }); -var defaultComponent = 'table'; -var Table = /*#__PURE__*/react.forwardRef(function Table(inProps, ref) { +var Tab = /*#__PURE__*/react.forwardRef(function Tab(inProps, ref) { var props = useThemeProps_useThemeProps({ props: inProps, - name: 'MuiTable' + name: 'MuiTab' }); var className = props.className, - _props$component = props.component, - component = _props$component === void 0 ? defaultComponent : _props$component, - _props$padding = props.padding, - padding = _props$padding === void 0 ? 'normal' : _props$padding, - _props$size = props.size, - size = _props$size === void 0 ? 'medium' : _props$size, - _props$stickyHeader = props.stickyHeader, - stickyHeader = _props$stickyHeader === void 0 ? false : _props$stickyHeader, - other = objectWithoutPropertiesLoose_objectWithoutPropertiesLoose(props, Table_excluded); + _props$disabled = props.disabled, + disabled = _props$disabled === void 0 ? false : _props$disabled, + _props$disableFocusRi = props.disableFocusRipple, + disableFocusRipple = _props$disableFocusRi === void 0 ? false : _props$disableFocusRi, + fullWidth = props.fullWidth, + iconProp = props.icon, + _props$iconPosition = props.iconPosition, + iconPosition = _props$iconPosition === void 0 ? 'top' : _props$iconPosition, + indicator = props.indicator, + label = props.label, + onChange = props.onChange, + onClick = props.onClick, + onFocus = props.onFocus, + selected = props.selected, + selectionFollowsFocus = props.selectionFollowsFocus, + _props$textColor = props.textColor, + textColor = _props$textColor === void 0 ? 'inherit' : _props$textColor, + value = props.value, + _props$wrapped = props.wrapped, + wrapped = _props$wrapped === void 0 ? false : _props$wrapped, + other = objectWithoutPropertiesLoose_objectWithoutPropertiesLoose(props, Tab_excluded); var ownerState = extends_extends({}, props, { - component: component, - padding: padding, - size: size, - stickyHeader: stickyHeader - }); - var classes = Table_useUtilityClasses(ownerState); - var table = react.useMemo(function () { - return { - padding: padding, - size: size, - stickyHeader: stickyHeader - }; - }, [padding, size, stickyHeader]); - return /*#__PURE__*/(0,jsx_runtime.jsx)(Table_TableContext.Provider, { - value: table, - children: /*#__PURE__*/(0,jsx_runtime.jsx)(TableRoot, extends_extends({ - as: component, - role: component === defaultComponent ? null : 'table', - ref: ref, - className: clsx_m(classes.root, className), - ownerState: ownerState - }, other)) + disabled: disabled, + disableFocusRipple: disableFocusRipple, + selected: selected, + icon: !!iconProp, + iconPosition: iconPosition, + label: !!label, + fullWidth: fullWidth, + textColor: textColor, + wrapped: wrapped }); + var classes = Tab_useUtilityClasses(ownerState); + var icon = iconProp && label && /*#__PURE__*/react.isValidElement(iconProp) ? /*#__PURE__*/react.cloneElement(iconProp, { + className: clsx_m(classes.iconWrapper, iconProp.props.className) + }) : iconProp; + var handleClick = function handleClick(event) { + if (!selected && onChange) { + onChange(event, value); + } + if (onClick) { + onClick(event); + } + }; + var handleFocus = function handleFocus(event) { + if (selectionFollowsFocus && !selected && onChange) { + onChange(event, value); + } + if (onFocus) { + onFocus(event); + } + }; + return /*#__PURE__*/(0,jsx_runtime.jsxs)(TabRoot, extends_extends({ + focusRipple: !disableFocusRipple, + className: clsx_m(classes.root, className), + ref: ref, + role: "tab", + "aria-selected": selected, + disabled: disabled, + onClick: handleClick, + onFocus: handleFocus, + ownerState: ownerState, + tabIndex: selected ? 0 : -1 + }, other, { + children: [iconPosition === 'top' || iconPosition === 'start' ? /*#__PURE__*/(0,jsx_runtime.jsxs)(react.Fragment, { + children: [icon, label] + }) : /*#__PURE__*/(0,jsx_runtime.jsxs)(react.Fragment, { + children: [label, icon] + }), indicator] + })); }); false ? 0 : void 0; -/* harmony default export */ var Table_Table = (Table); -;// CONCATENATED MODULE: ./node_modules/@mui/material/Table/Tablelvl2Context.js +/* harmony default export */ var Tab_Tab = (Tab); +;// CONCATENATED MODULE: ./node_modules/@mui/lab/TabContext/TabContext.js + + /** - * @ignore - internal component. + * @type {React.Context<{ idPrefix: string; value: string } | null>} */ -var Tablelvl2Context = /*#__PURE__*/react.createContext(); -if (false) {} -/* harmony default export */ var Table_Tablelvl2Context = (Tablelvl2Context); -;// CONCATENATED MODULE: ./node_modules/@mui/material/TableBody/tableBodyClasses.js +var Context = /*#__PURE__*/react.createContext(null); +if (false) {} +function useUniquePrefix() { + var _React$useState = react.useState(null), + _React$useState2 = slicedToArray_slicedToArray(_React$useState, 2), + id = _React$useState2[0], + setId = _React$useState2[1]; + react.useEffect(function () { + setId("mui-p-".concat(Math.round(Math.random() * 1e5))); + }, []); + return id; +} +function TabContext(props) { + var children = props.children, + value = props.value; + var idPrefix = useUniquePrefix(); + var context = react.useMemo(function () { + return { + idPrefix: idPrefix, + value: value + }; + }, [idPrefix, value]); + return /*#__PURE__*/(0,jsx_runtime.jsx)(Context.Provider, { + value: context, + children: children + }); +} + false ? 0 : void 0; -function getTableBodyUtilityClass(slot) { - return generateUtilityClass_generateUtilityClass('MuiTableBody', slot); +/** + * @returns {unknown} + */ +function useTabContext() { + return react.useContext(Context); } -var tableBodyClasses = generateUtilityClasses('MuiTableBody', ['root']); -/* harmony default export */ var TableBody_tableBodyClasses = ((/* unused pure expression or super */ null && (tableBodyClasses))); -;// CONCATENATED MODULE: ./node_modules/@mui/material/TableBody/TableBody.js +function getPanelId(context, value) { + var idPrefix = context.idPrefix; + if (idPrefix === null) { + return null; + } + return "".concat(context.idPrefix, "-P-").concat(value); +} +function getTabId(context, value) { + var idPrefix = context.idPrefix; + if (idPrefix === null) { + return null; + } + return "".concat(context.idPrefix, "-T-").concat(value); +} +;// CONCATENATED MODULE: ./node_modules/@mui/utils/esm/scrollLeft.js +// Source from https://github.com/alitaheri/normalize-scroll-left +var cachedType; +/** + * Based on the jquery plugin https://github.com/othree/jquery.rtl-scroll-type + * + * Types of scrollLeft, assuming scrollWidth=100 and direction is rtl. + * + * Type | <- Most Left | Most Right -> | Initial + * ---------------- | ------------ | ------------- | ------- + * default | 0 | 100 | 100 + * negative (spec*) | -100 | 0 | 0 + * reverse | 100 | 0 | 0 + * + * Edge 85: default + * Safari 14: negative + * Chrome 85: negative + * Firefox 81: negative + * IE11: reverse + * + * spec* https://drafts.csswg.org/cssom-view/#dom-window-scroll + */ +function detectScrollType() { + if (cachedType) { + return cachedType; + } + var dummy = document.createElement('div'); + var container = document.createElement('div'); + container.style.width = '10px'; + container.style.height = '1px'; + dummy.appendChild(container); + dummy.dir = 'rtl'; + dummy.style.fontSize = '14px'; + dummy.style.width = '4px'; + dummy.style.height = '1px'; + dummy.style.position = 'absolute'; + dummy.style.top = '-1000px'; + dummy.style.overflow = 'scroll'; + document.body.appendChild(dummy); + cachedType = 'reverse'; + if (dummy.scrollLeft > 0) { + cachedType = 'default'; + } else { + dummy.scrollLeft = 1; + if (dummy.scrollLeft === 0) { + cachedType = 'negative'; + } + } + document.body.removeChild(dummy); + return cachedType; +} -var TableBody_excluded = ["className", "component"]; +// Based on https://stackoverflow.com/a/24394376 +function getNormalizedScrollLeft(element, direction) { + var scrollLeft = element.scrollLeft; + // Perform the calculations only when direction is rtl to avoid messing up the ltr behavior + if (direction !== 'rtl') { + return scrollLeft; + } + var type = detectScrollType(); + switch (type) { + case 'negative': + return element.scrollWidth - element.clientWidth + scrollLeft; + case 'reverse': + return element.scrollWidth - element.clientWidth - scrollLeft; + default: + return scrollLeft; + } +} +;// CONCATENATED MODULE: ./node_modules/@mui/material/internal/animate.js +function easeInOutSin(time) { + return (1 + Math.sin(Math.PI * time - Math.PI / 2)) / 2; +} +function animate(property, element, to) { + var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; + var cb = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : function () {}; + var _options$ease = options.ease, + ease = _options$ease === void 0 ? easeInOutSin : _options$ease, + _options$duration = options.duration, + duration = _options$duration === void 0 ? 300 : _options$duration; + var start = null; + var from = element[property]; + var cancelled = false; + var cancel = function cancel() { + cancelled = true; + }; + var step = function step(timestamp) { + if (cancelled) { + cb(new Error('Animation cancelled')); + return; + } + if (start === null) { + start = timestamp; + } + var time = Math.min(1, (timestamp - start) / duration); + element[property] = ease(time) * (to - from) + from; + if (time >= 1) { + requestAnimationFrame(function () { + cb(null); + }); + return; + } + requestAnimationFrame(step); + }; + if (from === to) { + cb(new Error('Element already at target position')); + return cancel; + } + requestAnimationFrame(step); + return cancel; +} +;// CONCATENATED MODULE: ./node_modules/@mui/material/Tabs/ScrollbarSize.js +var ScrollbarSize_excluded = ["onChange"]; +var ScrollbarSize_styles = { + width: 99, + height: 99, + position: 'absolute', + top: -9999, + overflow: 'scroll' +}; -var TableBody_useUtilityClasses = function useUtilityClasses(ownerState) { - var classes = ownerState.classes; - var slots = { - root: ['root'] +/** + * @ignore - internal component. + * The component originates from https://github.com/STORIS/react-scrollbar-size. + * It has been moved into the core in order to minimize the bundle size. + */ +function ScrollbarSize(props) { + var onChange = props.onChange, + other = objectWithoutPropertiesLoose_objectWithoutPropertiesLoose(props, ScrollbarSize_excluded); + var scrollbarHeight = react.useRef(); + var nodeRef = react.useRef(null); + var setMeasurements = function setMeasurements() { + scrollbarHeight.current = nodeRef.current.offsetHeight - nodeRef.current.clientHeight; }; - return composeClasses(slots, getTableBodyUtilityClass, classes); -}; -var TableBodyRoot = styles_styled('tbody', { - name: 'MuiTableBody', - slot: 'Root', - overridesResolver: function overridesResolver(props, styles) { - return styles.root; - } -})({ - display: 'table-row-group' -}); -var tablelvl2 = { - variant: 'body' -}; -var TableBody_defaultComponent = 'tbody'; -var TableBody = /*#__PURE__*/react.forwardRef(function TableBody(inProps, ref) { - var props = useThemeProps_useThemeProps({ - props: inProps, - name: 'MuiTableBody' - }); - var className = props.className, - _props$component = props.component, - component = _props$component === void 0 ? TableBody_defaultComponent : _props$component, - other = objectWithoutPropertiesLoose_objectWithoutPropertiesLoose(props, TableBody_excluded); - var ownerState = extends_extends({}, props, { - component: component - }); - var classes = TableBody_useUtilityClasses(ownerState); - return /*#__PURE__*/(0,jsx_runtime.jsx)(Table_Tablelvl2Context.Provider, { - value: tablelvl2, - children: /*#__PURE__*/(0,jsx_runtime.jsx)(TableBodyRoot, extends_extends({ - className: clsx_m(classes.root, className), - as: component, - ref: ref, - role: component === TableBody_defaultComponent ? null : 'rowgroup', - ownerState: ownerState - }, other)) - }); -}); + utils_useEnhancedEffect(function () { + var handleResize = utils_debounce(function () { + var prevHeight = scrollbarHeight.current; + setMeasurements(); + if (prevHeight !== scrollbarHeight.current) { + onChange(scrollbarHeight.current); + } + }); + var containerWindow = utils_ownerWindow(nodeRef.current); + containerWindow.addEventListener('resize', handleResize); + return function () { + handleResize.clear(); + containerWindow.removeEventListener('resize', handleResize); + }; + }, [onChange]); + react.useEffect(function () { + setMeasurements(); + onChange(scrollbarHeight.current); + }, [onChange]); + return /*#__PURE__*/(0,jsx_runtime.jsx)("div", extends_extends({ + style: ScrollbarSize_styles, + ref: nodeRef + }, other)); +} false ? 0 : void 0; -/* harmony default export */ var TableBody_TableBody = (TableBody); -;// CONCATENATED MODULE: ./node_modules/@mui/material/TableCell/tableCellClasses.js +;// CONCATENATED MODULE: ./node_modules/@mui/material/internal/svg-icons/KeyboardArrowLeft.js -function getTableCellUtilityClass(slot) { - return generateUtilityClass_generateUtilityClass('MuiTableCell', slot); + +/** + * @ignore - internal component. + */ + +/* harmony default export */ var KeyboardArrowLeft = (createSvgIcon( /*#__PURE__*/(0,jsx_runtime.jsx)("path", { + d: "M15.41 16.09l-4.58-4.59 4.58-4.59L14 5.5l-6 6 6 6z" +}), 'KeyboardArrowLeft')); +;// CONCATENATED MODULE: ./node_modules/@mui/material/internal/svg-icons/KeyboardArrowRight.js + + + +/** + * @ignore - internal component. + */ + +/* harmony default export */ var KeyboardArrowRight = (createSvgIcon( /*#__PURE__*/(0,jsx_runtime.jsx)("path", { + d: "M8.59 16.34l4.58-4.59-4.58-4.59L10 5.75l6 6-6 6z" +}), 'KeyboardArrowRight')); +;// CONCATENATED MODULE: ./node_modules/@mui/material/TabScrollButton/tabScrollButtonClasses.js + + +function getTabScrollButtonUtilityClass(slot) { + return generateUtilityClass_generateUtilityClass('MuiTabScrollButton', slot); } -var tableCellClasses = generateUtilityClasses('MuiTableCell', ['root', 'head', 'body', 'footer', 'sizeSmall', 'sizeMedium', 'paddingCheckbox', 'paddingNone', 'alignLeft', 'alignCenter', 'alignRight', 'alignJustify', 'stickyHeader']); -/* harmony default export */ var TableCell_tableCellClasses = (tableCellClasses); -;// CONCATENATED MODULE: ./node_modules/@mui/material/TableCell/TableCell.js +var tabScrollButtonClasses = generateUtilityClasses('MuiTabScrollButton', ['root', 'vertical', 'horizontal', 'disabled']); +/* harmony default export */ var TabScrollButton_tabScrollButtonClasses = (tabScrollButtonClasses); +;// CONCATENATED MODULE: ./node_modules/@mui/material/TabScrollButton/TabScrollButton.js -var TableCell_excluded = ["align", "className", "component", "padding", "scope", "size", "sortDirection", "variant"]; +var TabScrollButton_excluded = ["className", "slots", "slotProps", "direction", "orientation", "disabled"]; +/* eslint-disable jsx-a11y/aria-role */ @@ -57375,279 +57723,113 @@ var TableCell_excluded = ["align", "className", "component", "padding", "scope", -var TableCell_useUtilityClasses = function useUtilityClasses(ownerState) { +var TabScrollButton_useUtilityClasses = function useUtilityClasses(ownerState) { var classes = ownerState.classes, - variant = ownerState.variant, - align = ownerState.align, - padding = ownerState.padding, - size = ownerState.size, - stickyHeader = ownerState.stickyHeader; + orientation = ownerState.orientation, + disabled = ownerState.disabled; var slots = { - root: ['root', variant, stickyHeader && 'stickyHeader', align !== 'inherit' && "align".concat(utils_capitalize(align)), padding !== 'normal' && "padding".concat(utils_capitalize(padding)), "size".concat(utils_capitalize(size))] + root: ['root', orientation, disabled && 'disabled'] }; - return composeClasses(slots, getTableCellUtilityClass, classes); + return composeClasses(slots, getTabScrollButtonUtilityClass, classes); }; -var TableCellRoot = styles_styled('td', { - name: 'MuiTableCell', +var TabScrollButtonRoot = styles_styled(ButtonBase_ButtonBase, { + name: 'MuiTabScrollButton', slot: 'Root', overridesResolver: function overridesResolver(props, styles) { var ownerState = props.ownerState; - return [styles.root, styles[ownerState.variant], styles["size".concat(utils_capitalize(ownerState.size))], ownerState.padding !== 'normal' && styles["padding".concat(utils_capitalize(ownerState.padding))], ownerState.align !== 'inherit' && styles["align".concat(utils_capitalize(ownerState.align))], ownerState.stickyHeader && styles.stickyHeader]; + return [styles.root, ownerState.orientation && styles[ownerState.orientation]]; } })(function (_ref) { - var theme = _ref.theme, - ownerState = _ref.ownerState; - return extends_extends({}, theme.typography.body2, { - display: 'table-cell', - verticalAlign: 'inherit', - // Workaround for a rendering bug with spanned columns in Chrome 62.0. - // Removes the alpha (sets it to 1), and lightens or darkens the theme color. - borderBottom: theme.vars ? "1px solid ".concat(theme.vars.palette.TableCell.border) : "1px solid\n ".concat(theme.palette.mode === 'light' ? lighten(alpha(theme.palette.divider, 1), 0.88) : darken(alpha(theme.palette.divider, 1), 0.68)), - textAlign: 'left', - padding: 16 - }, ownerState.variant === 'head' && { - color: (theme.vars || theme).palette.text.primary, - lineHeight: theme.typography.pxToRem(24), - fontWeight: theme.typography.fontWeightMedium - }, ownerState.variant === 'body' && { - color: (theme.vars || theme).palette.text.primary - }, ownerState.variant === 'footer' && { - color: (theme.vars || theme).palette.text.secondary, - lineHeight: theme.typography.pxToRem(21), - fontSize: theme.typography.pxToRem(12) - }, ownerState.size === 'small' && defineProperty_defineProperty({ - padding: '6px 16px' - }, "&.".concat(TableCell_tableCellClasses.paddingCheckbox), { - width: 24, - // prevent the checkbox column from growing - padding: '0 12px 0 16px', - '& > *': { - padding: 0 + var ownerState = _ref.ownerState; + return extends_extends(defineProperty_defineProperty({ + width: 40, + flexShrink: 0, + opacity: 0.8 + }, "&.".concat(TabScrollButton_tabScrollButtonClasses.disabled), { + opacity: 0 + }), ownerState.orientation === 'vertical' && { + width: '100%', + height: 40, + '& svg': { + transform: "rotate(".concat(ownerState.isRtl ? -90 : 90, "deg)") } - }), ownerState.padding === 'checkbox' && { - width: 48, - // prevent the checkbox column from growing - padding: '0 0 0 4px' - }, ownerState.padding === 'none' && { - padding: 0 - }, ownerState.align === 'left' && { - textAlign: 'left' - }, ownerState.align === 'center' && { - textAlign: 'center' - }, ownerState.align === 'right' && { - textAlign: 'right', - flexDirection: 'row-reverse' - }, ownerState.align === 'justify' && { - textAlign: 'justify' - }, ownerState.stickyHeader && { - position: 'sticky', - top: 0, - zIndex: 2, - backgroundColor: (theme.vars || theme).palette.background.default }); }); - -/** - * The component renders a `` element when the parent context is a header - * or otherwise a `` element. - */ -var TableCell = /*#__PURE__*/react.forwardRef(function TableCell(inProps, ref) { +var TabScrollButton = /*#__PURE__*/react.forwardRef(function TabScrollButton(inProps, ref) { + var _slots$StartScrollBut, _slots$EndScrollButto; var props = useThemeProps_useThemeProps({ props: inProps, - name: 'MuiTableCell' + name: 'MuiTabScrollButton' }); - var _props$align = props.align, - align = _props$align === void 0 ? 'inherit' : _props$align, - className = props.className, - componentProp = props.component, - paddingProp = props.padding, - scopeProp = props.scope, - sizeProp = props.size, - sortDirection = props.sortDirection, - variantProp = props.variant, - other = objectWithoutPropertiesLoose_objectWithoutPropertiesLoose(props, TableCell_excluded); - var table = react.useContext(Table_TableContext); - var tablelvl2 = react.useContext(Table_Tablelvl2Context); - var isHeadCell = tablelvl2 && tablelvl2.variant === 'head'; - var component; - if (componentProp) { - component = componentProp; - } else { - component = isHeadCell ? 'th' : 'td'; - } - var scope = scopeProp; - // scope is not a valid attribute for elements. - // source: https://html.spec.whatwg.org/multipage/tables.html#the-td-element - if (component === 'td') { - scope = undefined; - } else if (!scope && isHeadCell) { - scope = 'col'; - } - var variant = variantProp || tablelvl2 && tablelvl2.variant; - var ownerState = extends_extends({}, props, { - align: align, - component: component, - padding: paddingProp || (table && table.padding ? table.padding : 'normal'), - size: sizeProp || (table && table.size ? table.size : 'medium'), - sortDirection: sortDirection, - stickyHeader: variant === 'head' && table && table.stickyHeader, - variant: variant + var className = props.className, + _props$slots = props.slots, + slots = _props$slots === void 0 ? {} : _props$slots, + _props$slotProps = props.slotProps, + slotProps = _props$slotProps === void 0 ? {} : _props$slotProps, + direction = props.direction, + other = objectWithoutPropertiesLoose_objectWithoutPropertiesLoose(props, TabScrollButton_excluded); + var theme = styles_useTheme_useTheme(); + var isRtl = theme.direction === 'rtl'; + var ownerState = extends_extends({ + isRtl: isRtl + }, props); + var classes = TabScrollButton_useUtilityClasses(ownerState); + var StartButtonIcon = (_slots$StartScrollBut = slots.StartScrollButtonIcon) != null ? _slots$StartScrollBut : KeyboardArrowLeft; + var EndButtonIcon = (_slots$EndScrollButto = slots.EndScrollButtonIcon) != null ? _slots$EndScrollButto : KeyboardArrowRight; + var startButtonIconProps = useSlotProps({ + elementType: StartButtonIcon, + externalSlotProps: slotProps.startScrollButtonIcon, + additionalProps: { + fontSize: 'small' + }, + ownerState: ownerState }); - var classes = TableCell_useUtilityClasses(ownerState); - var ariaSort = null; - if (sortDirection) { - ariaSort = sortDirection === 'asc' ? 'ascending' : 'descending'; - } - return /*#__PURE__*/(0,jsx_runtime.jsx)(TableCellRoot, extends_extends({ - as: component, - ref: ref, - className: clsx_m(classes.root, className), - "aria-sort": ariaSort, - scope: scope, + var endButtonIconProps = useSlotProps({ + elementType: EndButtonIcon, + externalSlotProps: slotProps.endScrollButtonIcon, + additionalProps: { + fontSize: 'small' + }, ownerState: ownerState - }, other)); -}); - false ? 0 : void 0; -/* harmony default export */ var TableCell_TableCell = (TableCell); -;// CONCATENATED MODULE: ./node_modules/@mui/material/TableContainer/tableContainerClasses.js - - -function getTableContainerUtilityClass(slot) { - return generateUtilityClass_generateUtilityClass('MuiTableContainer', slot); -} -var tableContainerClasses = generateUtilityClasses('MuiTableContainer', ['root']); -/* harmony default export */ var TableContainer_tableContainerClasses = ((/* unused pure expression or super */ null && (tableContainerClasses))); -;// CONCATENATED MODULE: ./node_modules/@mui/material/TableContainer/TableContainer.js - - -var TableContainer_excluded = ["className", "component"]; - - - - - - - - -var TableContainer_useUtilityClasses = function useUtilityClasses(ownerState) { - var classes = ownerState.classes; - var slots = { - root: ['root'] - }; - return composeClasses(slots, getTableContainerUtilityClass, classes); -}; -var TableContainerRoot = styles_styled('div', { - name: 'MuiTableContainer', - slot: 'Root', - overridesResolver: function overridesResolver(props, styles) { - return styles.root; - } -})({ - width: '100%', - overflowX: 'auto' -}); -var TableContainer = /*#__PURE__*/react.forwardRef(function TableContainer(inProps, ref) { - var props = useThemeProps_useThemeProps({ - props: inProps, - name: 'MuiTableContainer' - }); - var className = props.className, - _props$component = props.component, - component = _props$component === void 0 ? 'div' : _props$component, - other = objectWithoutPropertiesLoose_objectWithoutPropertiesLoose(props, TableContainer_excluded); - var ownerState = extends_extends({}, props, { - component: component }); - var classes = TableContainer_useUtilityClasses(ownerState); - return /*#__PURE__*/(0,jsx_runtime.jsx)(TableContainerRoot, extends_extends({ - ref: ref, - as: component, + return /*#__PURE__*/(0,jsx_runtime.jsx)(TabScrollButtonRoot, extends_extends({ + component: "div", className: clsx_m(classes.root, className), - ownerState: ownerState - }, other)); + ref: ref, + role: null, + ownerState: ownerState, + tabIndex: null + }, other, { + children: direction === 'left' ? /*#__PURE__*/(0,jsx_runtime.jsx)(StartButtonIcon, extends_extends({}, startButtonIconProps)) : /*#__PURE__*/(0,jsx_runtime.jsx)(EndButtonIcon, extends_extends({}, endButtonIconProps)) + })); }); false ? 0 : void 0; -/* harmony default export */ var TableContainer_TableContainer = (TableContainer); -;// CONCATENATED MODULE: ./node_modules/@mui/material/TableHead/tableHeadClasses.js +/* harmony default export */ var TabScrollButton_TabScrollButton = (TabScrollButton); +;// CONCATENATED MODULE: ./node_modules/@mui/material/Tabs/tabsClasses.js -function getTableHeadUtilityClass(slot) { - return generateUtilityClass_generateUtilityClass('MuiTableHead', slot); +function getTabsUtilityClass(slot) { + return generateUtilityClass_generateUtilityClass('MuiTabs', slot); } -var tableHeadClasses = generateUtilityClasses('MuiTableHead', ['root']); -/* harmony default export */ var TableHead_tableHeadClasses = ((/* unused pure expression or super */ null && (tableHeadClasses))); -;// CONCATENATED MODULE: ./node_modules/@mui/material/TableHead/TableHead.js - +var tabsClasses = generateUtilityClasses('MuiTabs', ['root', 'vertical', 'flexContainer', 'flexContainerVertical', 'centered', 'scroller', 'fixed', 'scrollableX', 'scrollableY', 'hideScrollbar', 'scrollButtons', 'scrollButtonsHideMobile', 'indicator']); +/* harmony default export */ var Tabs_tabsClasses = (tabsClasses); +;// CONCATENATED MODULE: ./node_modules/@mui/material/Tabs/Tabs.js -var TableHead_excluded = ["className", "component"]; +var Tabs_excluded = ["aria-label", "aria-labelledby", "action", "centered", "children", "className", "component", "allowScrollButtonsMobile", "indicatorColor", "onChange", "orientation", "ScrollButtonComponent", "scrollButtons", "selectionFollowsFocus", "slots", "slotProps", "TabIndicatorProps", "TabScrollButtonProps", "textColor", "value", "variant", "visibleScrollbar"]; -var TableHead_useUtilityClasses = function useUtilityClasses(ownerState) { - var classes = ownerState.classes; - var slots = { - root: ['root'] - }; - return composeClasses(slots, getTableHeadUtilityClass, classes); -}; -var TableHeadRoot = styles_styled('thead', { - name: 'MuiTableHead', - slot: 'Root', - overridesResolver: function overridesResolver(props, styles) { - return styles.root; - } -})({ - display: 'table-header-group' -}); -var TableHead_tablelvl2 = { - variant: 'head' -}; -var TableHead_defaultComponent = 'thead'; -var TableHead = /*#__PURE__*/react.forwardRef(function TableHead(inProps, ref) { - var props = useThemeProps_useThemeProps({ - props: inProps, - name: 'MuiTableHead' - }); - var className = props.className, - _props$component = props.component, - component = _props$component === void 0 ? TableHead_defaultComponent : _props$component, - other = objectWithoutPropertiesLoose_objectWithoutPropertiesLoose(props, TableHead_excluded); - var ownerState = extends_extends({}, props, { - component: component - }); - var classes = TableHead_useUtilityClasses(ownerState); - return /*#__PURE__*/(0,jsx_runtime.jsx)(Table_Tablelvl2Context.Provider, { - value: TableHead_tablelvl2, - children: /*#__PURE__*/(0,jsx_runtime.jsx)(TableHeadRoot, extends_extends({ - as: component, - className: clsx_m(classes.root, className), - ref: ref, - role: component === TableHead_defaultComponent ? null : 'rowgroup', - ownerState: ownerState - }, other)) - }); -}); - false ? 0 : void 0; -/* harmony default export */ var TableHead_TableHead = (TableHead); -;// CONCATENATED MODULE: ./node_modules/@mui/material/TableRow/tableRowClasses.js -function getTableRowUtilityClass(slot) { - return generateUtilityClass_generateUtilityClass('MuiTableRow', slot); -} -var tableRowClasses = generateUtilityClasses('MuiTableRow', ['root', 'selected', 'hover', 'head', 'footer']); -/* harmony default export */ var TableRow_tableRowClasses = (tableRowClasses); -;// CONCATENATED MODULE: ./node_modules/@mui/material/TableRow/TableRow.js -var TableRow_excluded = ["className", "component", "hover", "selected"]; @@ -57657,752 +57839,773 @@ var TableRow_excluded = ["className", "component", "hover", "selected"]; +var Tabs_nextItem = function nextItem(list, item) { + if (list === item) { + return list.firstChild; + } + if (item && item.nextElementSibling) { + return item.nextElementSibling; + } + return list.firstChild; +}; +var Tabs_previousItem = function previousItem(list, item) { + if (list === item) { + return list.lastChild; + } + if (item && item.previousElementSibling) { + return item.previousElementSibling; + } + return list.lastChild; +}; +var Tabs_moveFocus = function moveFocus(list, currentFocus, traversalFunction) { + var wrappedOnce = false; + var nextFocus = traversalFunction(list, currentFocus); + while (nextFocus) { + // Prevent infinite loop. + if (nextFocus === list.firstChild) { + if (wrappedOnce) { + return; + } + wrappedOnce = true; + } -var TableRow_useUtilityClasses = function useUtilityClasses(ownerState) { - var classes = ownerState.classes, - selected = ownerState.selected, - hover = ownerState.hover, - head = ownerState.head, - footer = ownerState.footer; + // Same logic as useAutocomplete.js + var nextFocusDisabled = nextFocus.disabled || nextFocus.getAttribute('aria-disabled') === 'true'; + if (!nextFocus.hasAttribute('tabindex') || nextFocusDisabled) { + // Move to the next element. + nextFocus = traversalFunction(list, nextFocus); + } else { + nextFocus.focus(); + return; + } + } +}; +var Tabs_useUtilityClasses = function useUtilityClasses(ownerState) { + var vertical = ownerState.vertical, + fixed = ownerState.fixed, + hideScrollbar = ownerState.hideScrollbar, + scrollableX = ownerState.scrollableX, + scrollableY = ownerState.scrollableY, + centered = ownerState.centered, + scrollButtonsHideMobile = ownerState.scrollButtonsHideMobile, + classes = ownerState.classes; var slots = { - root: ['root', selected && 'selected', hover && 'hover', head && 'head', footer && 'footer'] + root: ['root', vertical && 'vertical'], + scroller: ['scroller', fixed && 'fixed', hideScrollbar && 'hideScrollbar', scrollableX && 'scrollableX', scrollableY && 'scrollableY'], + flexContainer: ['flexContainer', vertical && 'flexContainerVertical', centered && 'centered'], + indicator: ['indicator'], + scrollButtons: ['scrollButtons', scrollButtonsHideMobile && 'scrollButtonsHideMobile'], + scrollableX: [scrollableX && 'scrollableX'], + hideScrollbar: [hideScrollbar && 'hideScrollbar'] }; - return composeClasses(slots, getTableRowUtilityClass, classes); + return composeClasses(slots, getTabsUtilityClass, classes); }; -var TableRowRoot = styles_styled('tr', { - name: 'MuiTableRow', +var TabsRoot = styles_styled('div', { + name: 'MuiTabs', slot: 'Root', overridesResolver: function overridesResolver(props, styles) { var ownerState = props.ownerState; - return [styles.root, ownerState.head && styles.head, ownerState.footer && styles.footer]; + return [defineProperty_defineProperty({}, "& .".concat(Tabs_tabsClasses.scrollButtons), styles.scrollButtons), defineProperty_defineProperty({}, "& .".concat(Tabs_tabsClasses.scrollButtons), ownerState.scrollButtonsHideMobile && styles.scrollButtonsHideMobile), styles.root, ownerState.vertical && styles.vertical]; } -})(function (_ref) { - var _ref2; - var theme = _ref.theme; - return _ref2 = { - color: 'inherit', - display: 'table-row', - verticalAlign: 'middle', - // We disable the focus ring for mouse, touch and keyboard users. - outline: 0 - }, defineProperty_defineProperty(_ref2, "&.".concat(TableRow_tableRowClasses.hover, ":hover"), { - backgroundColor: (theme.vars || theme).palette.action.hover - }), defineProperty_defineProperty(_ref2, "&.".concat(TableRow_tableRowClasses.selected), { - backgroundColor: theme.vars ? "rgba(".concat(theme.vars.palette.primary.mainChannel, " / ").concat(theme.vars.palette.action.selectedOpacity, ")") : alpha(theme.palette.primary.main, theme.palette.action.selectedOpacity), - '&:hover': { - backgroundColor: theme.vars ? "rgba(".concat(theme.vars.palette.primary.mainChannel, " / calc(").concat(theme.vars.palette.action.selectedOpacity, " + ").concat(theme.vars.palette.action.hoverOpacity, "))") : alpha(theme.palette.primary.main, theme.palette.action.selectedOpacity + theme.palette.action.hoverOpacity) - } - }), _ref2; +})(function (_ref3) { + var ownerState = _ref3.ownerState, + theme = _ref3.theme; + return extends_extends({ + overflow: 'hidden', + minHeight: 48, + // Add iOS momentum scrolling for iOS < 13.0 + WebkitOverflowScrolling: 'touch', + display: 'flex' + }, ownerState.vertical && { + flexDirection: 'column' + }, ownerState.scrollButtonsHideMobile && defineProperty_defineProperty({}, "& .".concat(Tabs_tabsClasses.scrollButtons), defineProperty_defineProperty({}, theme.breakpoints.down('sm'), { + display: 'none' + }))); }); -var TableRow_defaultComponent = 'tr'; -/** - * Will automatically set dynamic row height - * based on the material table element parent (head, body, etc). - */ -var TableRow = /*#__PURE__*/react.forwardRef(function TableRow(inProps, ref) { - var props = useThemeProps_useThemeProps({ - props: inProps, - name: 'MuiTableRow' - }); - var className = props.className, - _props$component = props.component, - component = _props$component === void 0 ? TableRow_defaultComponent : _props$component, - _props$hover = props.hover, - hover = _props$hover === void 0 ? false : _props$hover, - _props$selected = props.selected, - selected = _props$selected === void 0 ? false : _props$selected, - other = objectWithoutPropertiesLoose_objectWithoutPropertiesLoose(props, TableRow_excluded); - var tablelvl2 = react.useContext(Table_Tablelvl2Context); - var ownerState = extends_extends({}, props, { - component: component, - hover: hover, - selected: selected, - head: tablelvl2 && tablelvl2.variant === 'head', - footer: tablelvl2 && tablelvl2.variant === 'footer' - }); - var classes = TableRow_useUtilityClasses(ownerState); - return /*#__PURE__*/(0,jsx_runtime.jsx)(TableRowRoot, extends_extends({ - as: component, - ref: ref, - className: clsx_m(classes.root, className), - role: component === TableRow_defaultComponent ? null : 'row', - ownerState: ownerState - }, other)); -}); - false ? 0 : void 0; -/* harmony default export */ var TableRow_TableRow = (TableRow); -// EXTERNAL MODULE: ./node_modules/js-sha256/src/sha256.js -var sha256 = __webpack_require__(981); -;// CONCATENATED MODULE: ./src/utils/listKey.ts -function buildListKey(o){var j=JSON.stringify(o);return (0,sha256.sha256)(j);} -;// CONCATENATED MODULE: ./src/components/result-view/ChangesTable.tsx -var RefList=function RefList(props){return/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{py:"10px",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{align:"center",variant:"h5",children:props.title}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableContainer_TableContainer,{children:/*#__PURE__*/(0,jsx_runtime.jsxs)(Table_Table,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableHead_TableHead,{children:/*#__PURE__*/(0,jsx_runtime.jsxs)(TableRow_TableRow,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{align:"left",children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{children:"Kind"})}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{align:"left",children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{children:"Namespace"})}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{align:"left",children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{children:"Name"})})]})}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableBody_TableBody,{children:props.refs.map(function(ref){return/*#__PURE__*/(0,jsx_runtime.jsxs)(TableRow_TableRow,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:buildRefKindElement(ref)}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{children:ref.namespace})}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{children:ref.name})})]},buildRefString(ref));})})]})})]});};function ChangesTable(props){var changedObjects;if(props.diffStatus.changedObjects.length){changedObjects=/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{py:"10px",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{align:"center",variant:"h5",children:"Changed Objects"}),props.diffStatus.changedObjects.map(function(co){var _co$changes;return/*#__PURE__*/(0,jsx_runtime.jsx)(TableContainer_TableContainer,{children:/*#__PURE__*/(0,jsx_runtime.jsxs)(Table_Table,{children:[/*#__PURE__*/(0,jsx_runtime.jsxs)(TableHead_TableHead,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableRow_TableRow,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{align:"left",colSpan:2,sx:{padding:'24px 16px 5px 16px'},children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{sx:{fontWeight:500,fontSize:'20px',lineHeight:'27px',letterSpacing:'1px'},children:buildRefString(co.ref)})})}),/*#__PURE__*/(0,jsx_runtime.jsxs)(TableRow_TableRow,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:"Path"}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:"Changes"})]})]}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableBody_TableBody,{children:(_co$changes=co.changes)===null||_co$changes===void 0?void 0:_co$changes.map(function(c){return/*#__PURE__*/(0,jsx_runtime.jsxs)(TableRow_TableRow,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{minWidth:"100px",sx:{overflowWrap:"anywhere"},children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{children:c.jsonPath})})}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(CodeViewer,{code:c.unifiedDiff||"",language:"diff"})})]},buildListKey(c));})})]})},buildRefString(co.ref));})]});}return/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{width:"100%",display:"flex",flexDirection:"column",children:[props.diffStatus.newObjects.length?/*#__PURE__*/(0,jsx_runtime.jsx)(RefList,{title:"New Objects",refs:props.diffStatus.newObjects}):/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{}),props.diffStatus.deletedObjects.length?/*#__PURE__*/(0,jsx_runtime.jsx)(RefList,{title:"Deleted Objects",refs:props.diffStatus.deletedObjects}):/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{}),props.diffStatus.orphanObjects.length?/*#__PURE__*/(0,jsx_runtime.jsx)(RefList,{title:"Orphan Objects",refs:props.diffStatus.orphanObjects}):/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{}),changedObjects]});} -;// CONCATENATED MODULE: ./src/components/ErrorsTable.tsx -function ErrorsTable(props){var _props$errors;return/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{height:"100%",children:/*#__PURE__*/(0,jsx_runtime.jsx)(TableContainer_TableContainer,{children:/*#__PURE__*/(0,jsx_runtime.jsxs)(Table_Table,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableHead_TableHead,{children:/*#__PURE__*/(0,jsx_runtime.jsxs)(TableRow_TableRow,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:"Ref"}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:"Message"})]})}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableBody_TableBody,{children:(_props$errors=props.errors)===null||_props$errors===void 0?void 0:_props$errors.map(function(e){return/*#__PURE__*/(0,jsx_runtime.jsxs)(TableRow_TableRow,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{sx:{minWidth:"150px"},children:/*#__PURE__*/(0,jsx_runtime.jsxs)(List_List,{disablePadding:true,children:[buildRefKindElement(e.ref,/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(ListItem_ListItem,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(ListItemText_ListItemText,{primary:e.ref.kind})})})),/*#__PURE__*/(0,jsx_runtime.jsx)(Divider_Divider,{}),/*#__PURE__*/(0,jsx_runtime.jsx)(ListItem_ListItem,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(ListItemText_ListItemText,{primary:e.ref.name})}),e.ref.namespace&&/*#__PURE__*/(0,jsx_runtime.jsxs)(jsx_runtime.Fragment,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(Divider_Divider,{}),/*#__PURE__*/(0,jsx_runtime.jsx)(ListItem_ListItem,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(ListItemText_ListItemText,{primary:e.ref.namespace})})]})]})}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:e.message})]},buildListKey(e));})})]})})})});} -;// CONCATENATED MODULE: ./src/components/ObjectYaml.tsx -var ObjectYaml=function ObjectYaml(props){var api=(0,react.useContext)(ApiContext);var _useLoadingHelper=useLoadingHelper(/*#__PURE__*/asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee(){var o;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee$(_context){while(1)switch(_context.prev=_context.next){case 0:_context.next=2;return api.getResultObject(props.treeProps.summary.id,props.objectRef,props.objectType);case 2:o=_context.sent;return _context.abrupt("return",dump(o));case 4:case"end":return _context.stop();}},_callee);})),[props.treeProps.summary.id,props.objectRef,props.objectType]),_useLoadingHelper2=slicedToArray_slicedToArray(_useLoadingHelper,3),loading=_useLoadingHelper2[0],error=_useLoadingHelper2[1],content=_useLoadingHelper2[2];if(loading){return/*#__PURE__*/(0,jsx_runtime.jsx)(Loading,{});}else if(error){return/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{children:"Error"});}else{return/*#__PURE__*/(0,jsx_runtime.jsx)(CodeViewer,{code:content,language:"yaml"});}}; -;// CONCATENATED MODULE: ./src/components/result-view/nodes/NodeData.tsx -var DiffStatus=/*#__PURE__*/function(){function DiffStatus(){classCallCheck_classCallCheck(this,DiffStatus);this.newObjects=[];this.deletedObjects=[];this.orphanObjects=[];this.changedObjects=[];this.totalInsertions=0;this.totalDeletions=0;this.totalUpdates=0;}createClass_createClass(DiffStatus,[{key:"addChangedObject",value:function addChangedObject(co){var _co$changes,_this=this;this.changedObjects.push(co);(_co$changes=co.changes)===null||_co$changes===void 0?void 0:_co$changes.forEach(function(x){switch(x.type){case"insert":_this.totalInsertions++;break;case"delete":_this.totalDeletions++;break;case"update":_this.totalUpdates++;break;}});}},{key:"merge",value:function merge(other){this.newObjects=this.newObjects.concat(other.newObjects);this.deletedObjects=this.deletedObjects.concat(other.deletedObjects);this.orphanObjects=this.orphanObjects.concat(other.orphanObjects);this.changedObjects=this.changedObjects.concat(other.changedObjects);this.totalInsertions+=other.totalInsertions;this.totalDeletions+=other.totalDeletions;this.totalUpdates+=other.totalUpdates;}},{key:"hasDiffs",value:function hasDiffs(){if(this.newObjects.length||this.deletedObjects.length||this.orphanObjects.length){return true;}if(this.changedObjects.find(function(co){var _co$changes2;return((_co$changes2=co.changes)===null||_co$changes2===void 0?void 0:_co$changes2.length)!==0;})){return true;}return false;}}]);return DiffStatus;}();var HealthStatus=/*#__PURE__*/function(){function HealthStatus(){classCallCheck_classCallCheck(this,HealthStatus);this.errors=[];this.warnings=[];}createClass_createClass(HealthStatus,[{key:"merge",value:function merge(other){this.errors=this.errors.concat(other.errors);this.warnings=this.warnings.concat(other.warnings);}}]);return HealthStatus;}();var NodeData=/*#__PURE__*/function(){function NodeData(props,id,hasHealthStatus,hasDiffStatus){classCallCheck_classCallCheck(this,NodeData);this.props=void 0;this.id=void 0;this.children=[];this.healthStatus=void 0;this.diffStatus=void 0;this.props=props;this.id=id;if(hasHealthStatus){this.healthStatus=new HealthStatus();}if(hasDiffStatus){this.diffStatus=new DiffStatus();}}createClass_createClass(NodeData,[{key:"pushChild",value:function pushChild(child,front){if(front){this.children.unshift(child);}else{this.children.push(child);}this.merge(child);}},{key:"merge",value:function merge(other){if(this.diffStatus&&other.diffStatus){this.diffStatus.merge(other.diffStatus);}if(this.healthStatus&&other.healthStatus){this.healthStatus.merge(other.healthStatus);}}},{key:"buildStatusLine",value:function buildStatusLine(){var _this$healthStatus,_this$healthStatus2,_this$diffStatus,_this$diffStatus2,_this$diffStatus3,_this$diffStatus4;return/*#__PURE__*/(0,jsx_runtime.jsx)(StatusLine,{errors:(_this$healthStatus=this.healthStatus)===null||_this$healthStatus===void 0?void 0:_this$healthStatus.errors.length,warnings:(_this$healthStatus2=this.healthStatus)===null||_this$healthStatus2===void 0?void 0:_this$healthStatus2.warnings.length,changedObjects:(_this$diffStatus=this.diffStatus)===null||_this$diffStatus===void 0?void 0:_this$diffStatus.changedObjects.length,newObjects:(_this$diffStatus2=this.diffStatus)===null||_this$diffStatus2===void 0?void 0:_this$diffStatus2.newObjects.length,deletedObjects:(_this$diffStatus3=this.diffStatus)===null||_this$diffStatus3===void 0?void 0:_this$diffStatus3.deletedObjects.length,orphanObjects:(_this$diffStatus4=this.diffStatus)===null||_this$diffStatus4===void 0?void 0:_this$diffStatus4.orphanObjects.length});}},{key:"buildTreeItem",value:function buildTreeItem(hasChildren){var _this$healthStatus3,_this$healthStatus4,_this$diffStatus5,_this$diffStatus6,_this$diffStatus7,_this$diffStatus8;var _this$buildIcon=this.buildIcon(),_this$buildIcon2=slicedToArray_slicedToArray(_this$buildIcon,2),icon=_this$buildIcon2[0],iconText=_this$buildIcon2[1];var hasStatusLine=[(_this$healthStatus3=this.healthStatus)===null||_this$healthStatus3===void 0?void 0:_this$healthStatus3.errors.length,(_this$healthStatus4=this.healthStatus)===null||_this$healthStatus4===void 0?void 0:_this$healthStatus4.warnings.length,(_this$diffStatus5=this.diffStatus)===null||_this$diffStatus5===void 0?void 0:_this$diffStatus5.changedObjects.length,(_this$diffStatus6=this.diffStatus)===null||_this$diffStatus6===void 0?void 0:_this$diffStatus6.newObjects.length,(_this$diffStatus7=this.diffStatus)===null||_this$diffStatus7===void 0?void 0:_this$diffStatus7.deletedObjects.length,(_this$diffStatus8=this.diffStatus)===null||_this$diffStatus8===void 0?void 0:_this$diffStatus8.orphanObjects.length].some(function(x){return(x||0)>0;});return/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",height:"100%",flex:"1 1 auto",children:[/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",width:"30px",height:"100%",flex:"0 0 auto",mr:"13px",sx:{'& svg':{width:'30px',height:'30px'}},children:[icon,/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"subtitle1",component:"div",fontSize:"12px",fontWeight:400,lineHeight:"16px",height:"16px",children:iconText})]}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",height:"100%",flex:"1 1 auto",py:"15px",children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,_objectSpread2(_objectSpread2({variant:"h6",component:"div",sx:{wordBreak:'break-all'}},hasChildren?{}:{fontSize:'16px',lineHeight:'22px'}),{},{children:this.buildSidePanelTitle()}))}),hasStatusLine&&/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{height:"100%",width:"172px",flex:"0 0 auto",display:"flex",alignItems:"center",px:"14px",ml:"14px",position:"relative",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(Divider_Divider,{orientation:"vertical",sx:{height:'40px',position:'absolute',left:0}}),this.buildStatusLine()]})]});}},{key:"buildDiffAndHealthPages",value:function buildDiffAndHealthPages(tabs){this.buildChangesPage(tabs);this.buildErrorsPage(tabs);this.buildWarningsPage(tabs);}},{key:"buildObjectPage",value:function buildObjectPage(ref,objectType){return/*#__PURE__*/(0,jsx_runtime.jsx)(ObjectYaml,{treeProps:this.props,objectRef:ref,objectType:objectType});}},{key:"buildChangesPage",value:function buildChangesPage(tabs){var _this$diffStatus9;if(!((_this$diffStatus9=this.diffStatus)!==null&&_this$diffStatus9!==void 0&&_this$diffStatus9.hasDiffs())){return undefined;}tabs.push({label:"Changes",content:/*#__PURE__*/(0,jsx_runtime.jsx)(ChangesTable,{diffStatus:this.diffStatus})});}},{key:"buildErrorsPage",value:function buildErrorsPage(tabs){var _this$healthStatus5;if(!((_this$healthStatus5=this.healthStatus)!==null&&_this$healthStatus5!==void 0&&_this$healthStatus5.errors.length)){return undefined;}tabs.push({label:"Errors",content:/*#__PURE__*/(0,jsx_runtime.jsx)(ErrorsTable,{errors:this.healthStatus.errors})});}},{key:"buildWarningsPage",value:function buildWarningsPage(tabs){var _this$healthStatus6;if(!((_this$healthStatus6=this.healthStatus)!==null&&_this$healthStatus6!==void 0&&_this$healthStatus6.warnings.length)){return undefined;}tabs.push({label:"Warnings",content:/*#__PURE__*/(0,jsx_runtime.jsx)(ErrorsTable,{errors:this.healthStatus.warnings})});}}]);return NodeData;}(); -;// CONCATENATED MODULE: ./node_modules/@mui/icons-material/esm/Category.js - - -/* harmony default export */ var Category = (createSvgIcon([/*#__PURE__*/(0,jsx_runtime.jsx)("path", { - d: "m12 2-5.5 9h11z" -}, "0"), /*#__PURE__*/(0,jsx_runtime.jsx)("circle", { - cx: "17.5", - cy: "17.5", - r: "4.5" -}, "1"), /*#__PURE__*/(0,jsx_runtime.jsx)("path", { - d: "M3 13.5h8v8H3z" -}, "2")], 'Category')); -;// CONCATENATED MODULE: ./node_modules/@mui/icons-material/esm/Settings.js - - -/* harmony default export */ var Settings = (createSvgIcon( /*#__PURE__*/(0,jsx_runtime.jsx)("path", { - d: "M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z" -}), 'Settings')); -;// CONCATENATED MODULE: ./node_modules/@mui/icons-material/esm/Lock.js - - -/* harmony default export */ var Lock = (createSvgIcon( /*#__PURE__*/(0,jsx_runtime.jsx)("path", { - d: "M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z" -}), 'Lock')); -;// CONCATENATED MODULE: ./node_modules/@mui/icons-material/esm/Dvr.js - - -/* harmony default export */ var Dvr = (createSvgIcon( /*#__PURE__*/(0,jsx_runtime.jsx)("path", { - d: "M21 3H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h5v2h8v-2h5c1.1 0 1.99-.9 1.99-2L23 5c0-1.1-.9-2-2-2zm0 14H3V5h18v12zm-2-9H8v2h11V8zm0 4H8v2h11v-2zM7 8H5v2h2V8zm0 4H5v2h2v-2z" -}), 'Dvr')); -;// CONCATENATED MODULE: ./node_modules/@mui/icons-material/esm/Http.js - - -/* harmony default export */ var Http = (createSvgIcon( /*#__PURE__*/(0,jsx_runtime.jsx)("path", { - d: "M4.5 11h-2V9H1v6h1.5v-2.5h2V15H6V9H4.5v2zm2.5-.5h1.5V15H10v-4.5h1.5V9H7v1.5zm5.5 0H14V15h1.5v-4.5H17V9h-4.5v1.5zm9-1.5H18v6h1.5v-2h2c.8 0 1.5-.7 1.5-1.5v-1c0-.8-.7-1.5-1.5-1.5zm0 2.5h-2v-1h2v1z" -}), 'Http')); -;// CONCATENATED MODULE: ./node_modules/@mui/icons-material/esm/Cloud.js - - -/* harmony default export */ var Cloud = (createSvgIcon( /*#__PURE__*/(0,jsx_runtime.jsx)("path", { - d: "M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96z" -}), 'Cloud')); -;// CONCATENATED MODULE: ./src/components/PropertiesTable.tsx -function PropertiesTable(props){return/*#__PURE__*/(0,jsx_runtime.jsx)(TableContainer_TableContainer,{children:/*#__PURE__*/(0,jsx_runtime.jsxs)(Table_Table,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableHead_TableHead,{children:/*#__PURE__*/(0,jsx_runtime.jsxs)(TableRow_TableRow,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:"Name"}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:"Value"})]})}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableBody_TableBody,{children:props.properties.map(function(row){return/*#__PURE__*/(0,jsx_runtime.jsxs)(TableRow_TableRow,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:row.name}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:row.value})]},row.name);})})]})});} -;// CONCATENATED MODULE: ./src/components/result-view/nodes/VarsSourceNode.tsx -var VarsSourceNodeData=/*#__PURE__*/function(_NodeData){_inherits(VarsSourceNodeData,_NodeData);var _super=_createSuper(VarsSourceNodeData);function VarsSourceNodeData(props,id,varsSource){var _this$varsSource$clus;var _this;classCallCheck_classCallCheck(this,VarsSourceNodeData);_this=_super.call(this,props,id,false,false);_this.varsSource=void 0;_this.labelsYaml=void 0;_this.renderedVarsYaml=void 0;_this.varsSource=varsSource;var labels=(_this$varsSource$clus=_this.varsSource.clusterConfigMap)===null||_this$varsSource$clus===void 0?void 0:_this$varsSource$clus.labels;if(!labels){var _this$varsSource$clus2;labels=(_this$varsSource$clus2=_this.varsSource.clusterSecret)===null||_this$varsSource$clus2===void 0?void 0:_this$varsSource$clus2.labels;}if(labels){_this.labelsYaml=dump(labels);}_this.renderedVarsYaml=dump(_this.varsSource.renderedVars);return _this;}createClass_createClass(VarsSourceNodeData,[{key:"getVarsSourceHandler",value:function getVarsSourceHandler(){var _this2=this;if(this.varsSource.values){return{type:"values",label:function label(){if(_this2.varsSource.renderedVars){return"values: "+Object.keys(_this2.varsSource.renderedVars).length;}else{return"empty values";}},icon:function icon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(Category,{fontSize:"large"});},sourceProps:function sourceProps(){return[];}};}else if(this.varsSource.file){return{type:"file",label:function label(){return _this2.varsSource.file;},icon:function icon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(FileIcon,{});},sourceProps:function sourceProps(){return[{name:"File",value:_this2.varsSource.file}];}};}else if(this.varsSource.git){return{type:"git",label:function label(){var s=_this2.varsSource.git.url.split("/");var name=s[s.length-1];return/*#__PURE__*/(0,jsx_runtime.jsxs)(jsx_runtime.Fragment,{children:[name,/*#__PURE__*/(0,jsx_runtime.jsx)("br",{}),_this2.varsSource.git.path]});},icon:function icon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(GitIcon,{});},sourceProps:function sourceProps(){var sourceProps=[];sourceProps.push({name:"Url",value:_this2.varsSource.git.url});sourceProps.push({name:"Path",value:_this2.varsSource.git.path});var ref="HEAD";if(_this2.varsSource.git.ref){ref=_this2.varsSource.git.ref;}sourceProps.push({name:"Ref",value:ref});return sourceProps;}};}else if(this.varsSource.clusterConfigMap||this.varsSource.clusterSecret){var vs=this.varsSource.clusterConfigMap?this.varsSource.clusterConfigMap:this.varsSource.clusterSecret;var type=this.varsSource.clusterConfigMap?"cm":"secret";var _icon=this.varsSource.clusterConfigMap?/*#__PURE__*/(0,jsx_runtime.jsx)(Settings,{fontSize:"large"}):/*#__PURE__*/(0,jsx_runtime.jsx)(Lock,{fontSize:"large"});return{type:type,label:function label(){var _this2$varsSource$clu;return(_this2$varsSource$clu=_this2.varsSource.clusterConfigMap)===null||_this2$varsSource$clu===void 0?void 0:_this2$varsSource$clu.name;},icon:function icon(){return _icon;},sourceProps:function sourceProps(){var sourceProps=[];sourceProps.push({name:"Name",value:vs.name});sourceProps.push({name:"Namespace",value:vs.namespace});sourceProps.push({name:"Key",value:vs.key});if(vs.labels){sourceProps.push({name:"Labels",value:/*#__PURE__*/(0,jsx_runtime.jsx)(CodeViewer,{code:_this2.labelsYaml,language:"yaml"})});}return sourceProps;}};}else if(this.varsSource.systemEnvVars){return{type:"systemEnvVar",label:function label(){return"systemEnvVars";},icon:function icon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(Dvr,{fontSize:"large"});},sourceProps:function sourceProps(){// TODO -return[];}};}else if(this.varsSource.http){return{type:"http",label:function label(){return _this2.varsSource.http.url;},icon:function icon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(Http,{fontSize:"large"});},sourceProps:function sourceProps(){var sourceProps=[];sourceProps.push({name:"Url",value:_this2.varsSource.http.url});sourceProps.push({name:"Method",value:_this2.varsSource.http.method||"GET"});if(_this2.varsSource.http.jsonPath){sourceProps.push({name:"JsonPath",value:_this2.varsSource.http.jsonPath});}return sourceProps;}};}else if(this.varsSource.awsSecretsManager){return{type:"awsSecretsManager",label:function label(){return _this2.varsSource.awsSecretsManager.secretName;},icon:function icon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(Cloud,{fontSize:"large"});},sourceProps:function sourceProps(){var sourceProps=[];sourceProps.push({name:"SecretName",value:_this2.varsSource.awsSecretsManager.secretName});if(_this2.varsSource.awsSecretsManager.region){sourceProps.push({name:"Region",value:_this2.varsSource.awsSecretsManager.region});}if(_this2.varsSource.awsSecretsManager.profile){sourceProps.push({name:"Profile",value:_this2.varsSource.awsSecretsManager.profile});}return sourceProps;}};}else if(this.varsSource.vault){return{type:"vault",label:function label(){return/*#__PURE__*/(0,jsx_runtime.jsxs)(jsx_runtime.Fragment,{children:[_this2.varsSource.vault.address,/*#__PURE__*/(0,jsx_runtime.jsx)("br",{}),_this2.varsSource.vault.path]});},icon:function icon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(Cloud,{fontSize:"large"});},sourceProps:function sourceProps(){var sourceProps=[];sourceProps.push({name:"Address",value:_this2.varsSource.vault.address});sourceProps.push({name:"Path",value:_this2.varsSource.vault.path});return sourceProps;}};}else{return{type:"unknown",label:function label(){return"values: "+Object.keys(_this2.varsSource.renderedVars).length;},icon:function icon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(Category,{fontSize:"large"});},sourceProps:function sourceProps(){return[];}};}}},{key:"buildSidePanelTitle",value:function buildSidePanelTitle(){return this.getVarsSourceHandler().label();}},{key:"buildIcon",value:function buildIcon(){var h=this.getVarsSourceHandler();return[h.icon(),h.type];}},{key:"buildSidePanelTabs",value:function buildSidePanelTabs(){var tabs=[{label:"Summary",content:this.buildSummaryPage()},{label:"Vars",content:this.buildVarsPage()}];this.buildDiffAndHealthPages(tabs);return tabs;}},{key:"buildSummaryPage",value:function buildSummaryPage(){var h=this.getVarsSourceHandler();var props=[{name:"Type",value:h.type}].concat(toConsumableArray_toConsumableArray(h.sourceProps()),[{name:"IgnoreMissing",value:!!this.varsSource.ignoreMissing+""},{name:"NoOverride",value:!!this.varsSource.noOverride+""}]);if(this.varsSource.when){props.push({name:"When",value:this.varsSource.when});}return/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(PropertiesTable,{properties:props})});}},{key:"buildVarsPage",value:function buildVarsPage(){return/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{sx:{overflowY:'scroll',// Enable vertical scrolling -//maxHeight: '400px', // Set a fixed height -border:'1px solid #ccc',// Optional border -padding:'10px'// Optional padding -},children:/*#__PURE__*/(0,jsx_runtime.jsx)("pre",{children:/*#__PURE__*/(0,jsx_runtime.jsx)(CodeViewer,{code:this.renderedVarsYaml,language:"yaml"})})});}}]);return VarsSourceNodeData;}(NodeData); -;// CONCATENATED MODULE: ./node_modules/@mui/icons-material/esm/Source.js - - -/* harmony default export */ var Source = (createSvgIcon( /*#__PURE__*/(0,jsx_runtime.jsx)("path", { - d: "M20 6h-8l-2-2H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm-6 10H6v-2h8v2zm4-4H6v-2h12v2z" -}), 'Source')); -;// CONCATENATED MODULE: ./src/components/result-view/nodes/DeploymentItemNode.tsx -var DeploymentItemNodeData=/*#__PURE__*/function(_NodeData){_inherits(DeploymentItemNodeData,_NodeData);var _super=_createSuper(DeploymentItemNodeData);function DeploymentItemNodeData(props,id,deploymentItem){var _this;classCallCheck_classCallCheck(this,DeploymentItemNodeData);_this=_super.call(this,props,id,true,true);_this.deploymentItem=void 0;_this.deploymentItem=deploymentItem;return _this;}createClass_createClass(DeploymentItemNodeData,[{key:"buildSidePanelTitle",value:function buildSidePanelTitle(){return this.deploymentItem.path;}},{key:"buildIcon",value:function buildIcon(){var iconText="di";return[/*#__PURE__*/(0,jsx_runtime.jsx)(Source,{fontSize:"large"}),iconText];}},{key:"buildSidePanelTabs",value:function buildSidePanelTabs(){var tabs=[{label:"Summary",content:this.buildSummaryPage()}];this.buildDiffAndHealthPages(tabs);return tabs;}},{key:"buildSummaryPage",value:function buildSummaryPage(){var props=[];props.push({name:"Path",value:this.deploymentItem.path});buildDeploymentItemSummaryProps(this.deploymentItem,props);return/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(PropertiesTable,{properties:props})});}}]);return DeploymentItemNodeData;}(NodeData);function buildDeploymentItemSummaryProps(di,props){if(di.barrier!==undefined){props.push({name:"Barrier",value:di.barrier+""});}if(di.waitReadiness!==undefined){props.push({name:"WaitReadiness",value:di.waitReadiness+""});}if(di.skipDeleteIfTags!==undefined){props.push({name:"SkipDeleteIfTags",value:di.skipDeleteIfTags+""});}if(di.onlyRender!==undefined){props.push({name:"OnlyRender",value:di.onlyRender+""});}if(di.alwaysDeploy!==undefined){props.push({name:"AlwaysDeploy",value:di.alwaysDeploy+""});}if(di.deleteObjects){// TODO this is ugly -props.push({name:"DeleteObjects",value:JSON.stringify(di.deleteObjects)});}if(di.when){props.push({name:"When",value:di.when});}} -;// CONCATENATED MODULE: ./node_modules/@mui/icons-material/esm/SettingsEthernet.js - - -/* harmony default export */ var SettingsEthernet = (createSvgIcon( /*#__PURE__*/(0,jsx_runtime.jsx)("path", { - d: "M7.77 6.76 6.23 5.48.82 12l5.41 6.52 1.54-1.28L3.42 12l4.35-5.24zM7 13h2v-2H7v2zm10-2h-2v2h2v-2zm-6 2h2v-2h-2v2zm6.77-7.52-1.54 1.28L20.58 12l-4.35 5.24 1.54 1.28L23.18 12l-5.41-6.52z" -}), 'SettingsEthernet')); -;// CONCATENATED MODULE: ./node_modules/@mui/icons-material/esm/SmartToy.js - - -/* harmony default export */ var SmartToy = (createSvgIcon( /*#__PURE__*/(0,jsx_runtime.jsx)("path", { - d: "M20 9V7c0-1.1-.9-2-2-2h-3c0-1.66-1.34-3-3-3S9 3.34 9 5H6c-1.1 0-2 .9-2 2v2c-1.66 0-3 1.34-3 3s1.34 3 3 3v4c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2v-4c1.66 0 3-1.34 3-3s-1.34-3-3-3zM7.5 11.5c0-.83.67-1.5 1.5-1.5s1.5.67 1.5 1.5S9.83 13 9 13s-1.5-.67-1.5-1.5zM16 17H8v-2h8v2zm-1-4c-.83 0-1.5-.67-1.5-1.5S14.17 10 15 10s1.5.67 1.5 1.5S15.83 13 15 13z" -}), 'SmartToy')); -;// CONCATENATED MODULE: ./src/components/result-view/nodes/ObjectNode.tsx -var kindMapping={"/ConfigMap":{icon:Settings},"apps/Deployment":{icon:PublishedWithChanges},"Service":{icon:SettingsEthernet},"ServiceAccount":{icon:SmartToy}};var ObjectNodeData=/*#__PURE__*/function(_NodeData){_inherits(ObjectNodeData,_NodeData);var _super=_createSuper(ObjectNodeData);function ObjectNodeData(props,id,objectRef){var _this;classCallCheck_classCallCheck(this,ObjectNodeData);_this=_super.call(this,props,id,true,true);_this.objectRef=void 0;_this.objectRef=objectRef;return _this;}createClass_createClass(ObjectNodeData,[{key:"buildSidePanelTitle",value:function buildSidePanelTitle(){return this.objectRef.name;}},{key:"buildIcon",value:function buildIcon(){var _this2=this;var sn=this.props.shortNames.find(function(sn){return sn.group===_this2.objectRef.group&&sn.kind===_this2.objectRef.kind;});var snStr=(sn===null||sn===void 0?void 0:sn.shortName)||"";var m=kindMapping[this.objectRef.group+"/"+this.objectRef.kind];if(m!==undefined){return[/*#__PURE__*/react.createElement(m.icon,{fontSize:"large"}),snStr];}return[/*#__PURE__*/(0,jsx_runtime.jsx)(BracketsCurlyIcon,{}),snStr];}},{key:"buildSidePanelTabs",value:function buildSidePanelTabs(){var _findObjectByRef,_findObjectByRef2,_findObjectByRef3;var tabs=[{label:"Summary",content:this.buildSummaryPage()}];this.buildDiffAndHealthPages(tabs);if((_findObjectByRef=findObjectByRef(this.props.commandResult.objects,this.objectRef))!==null&&_findObjectByRef!==void 0&&_findObjectByRef.rendered){tabs.push({label:"Rendered",content:this.buildObjectPage(this.objectRef,ObjectType.Rendered)});}if((_findObjectByRef2=findObjectByRef(this.props.commandResult.objects,this.objectRef))!==null&&_findObjectByRef2!==void 0&&_findObjectByRef2.remote){tabs.push({label:"Remote",content:this.buildObjectPage(this.objectRef,ObjectType.Remote)});}if((_findObjectByRef3=findObjectByRef(this.props.commandResult.objects,this.objectRef))!==null&&_findObjectByRef3!==void 0&&_findObjectByRef3.applied){tabs.push({label:"Applied",content:this.buildObjectPage(this.objectRef,ObjectType.Applied)});}return tabs;}},{key:"buildSummaryPage",value:function buildSummaryPage(){var _o$rendered;var props=[];var apiVersion=this.objectRef.version;if(this.objectRef.group){apiVersion=this.objectRef.group+"/"+this.objectRef.version;}props.push({name:"ApiVersion",value:apiVersion});props.push({name:"Kind",value:this.objectRef.kind});props.push({name:"Name",value:this.objectRef.name});if(this.objectRef.namespace){props.push({name:"Namespace",value:this.objectRef.namespace});}var o=findObjectByRef(this.props.commandResult.objects,this.objectRef);var annotations=o===null||o===void 0?void 0:(_o$rendered=o.rendered)===null||_o$rendered===void 0?void 0:_o$rendered.metadata.annotations;if(annotations){Object.keys(annotations).forEach(function(k){if(k.indexOf("kluctl.io/")!==-1){props.push({name:k,value:annotations[k]});}});}return/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(PropertiesTable,{properties:props})});}}]);return ObjectNodeData;}(NodeData); -;// CONCATENATED MODULE: ./src/components/result-view/nodes/CommandResultNode.tsx -var CommandResultNodeData=/*#__PURE__*/function(_NodeData){_inherits(CommandResultNodeData,_NodeData);var _super=_createSuper(CommandResultNodeData);function CommandResultNodeData(props,id){var _this$props$commandRe;var _this;classCallCheck_classCallCheck(this,CommandResultNodeData);_this=_super.call(this,props,id,true,true);_this.dumpedTargetYaml=void 0;if((_this$props$commandRe=_this.props.commandResult.command)!==null&&_this$props$commandRe!==void 0&&_this$props$commandRe.target){_this.dumpedTargetYaml=dump(_this.props.commandResult.command.target);}return _this;}createClass_createClass(CommandResultNodeData,[{key:"buildSidePanelTitle",value:function buildSidePanelTitle(){return"CommandResult";}},{key:"buildIcon",value:function buildIcon(){return[/*#__PURE__*/(0,jsx_runtime.jsx)(DeployIcon,{}),"result"];}},{key:"buildSidePanelTabs",value:function buildSidePanelTabs(){var tabs=[{label:"Summary",content:this.buildSummaryPage()}];if(this.dumpedTargetYaml){var page=this.buildTargetPage();tabs.push({label:"Target",content:page});}this.buildDiffAndHealthPages(tabs);return tabs;}},{key:"buildSummaryPage",value:function buildSummaryPage(){var _this$props$commandRe2,_this$props$commandRe3;var props=[{name:"Initiator",value:(_this$props$commandRe2=this.props.commandResult.command)===null||_this$props$commandRe2===void 0?void 0:_this$props$commandRe2.initiator},{name:"Command",value:(_this$props$commandRe3=this.props.commandResult.command)===null||_this$props$commandRe3===void 0?void 0:_this$props$commandRe3.command}];return/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(PropertiesTable,{properties:props})});}},{key:"buildTargetPage",value:function buildTargetPage(){return/*#__PURE__*/(0,jsx_runtime.jsx)(CodeViewer,{code:this.dumpedTargetYaml,language:"yaml"});}}]);return CommandResultNodeData;}(NodeData); -;// CONCATENATED MODULE: ./src/components/result-view/nodes/VarsSourceCollectionNode.tsx -var VarsSourceCollectionNodeData=/*#__PURE__*/function(_NodeData){_inherits(VarsSourceCollectionNodeData,_NodeData);var _super=_createSuper(VarsSourceCollectionNodeData);function VarsSourceCollectionNodeData(props,id){var _this;classCallCheck_classCallCheck(this,VarsSourceCollectionNodeData);_this=_super.call(this,props,id,false,false);_this.varsSources=[];return _this;}createClass_createClass(VarsSourceCollectionNodeData,[{key:"buildSidePanelTitle",value:function buildSidePanelTitle(){return"vars: "+this.varsSources.length;}},{key:"buildIcon",value:function buildIcon(){return[/*#__PURE__*/(0,jsx_runtime.jsx)(BracketsSquareIcon,{}),"vars"];}},{key:"buildSidePanelTabs",value:function buildSidePanelTabs(){return[];}}]);return VarsSourceCollectionNodeData;}(NodeData); -;// CONCATENATED MODULE: ./src/components/result-view/nodes/DeploymentItemIncludeNode.tsx -var DeploymentItemIncludeNodeData=/*#__PURE__*/function(_NodeData){_inherits(DeploymentItemIncludeNodeData,_NodeData);var _super=_createSuper(DeploymentItemIncludeNodeData);function DeploymentItemIncludeNodeData(props,id,deploymentItem,includedDeployment){var _this;classCallCheck_classCallCheck(this,DeploymentItemIncludeNodeData);_this=_super.call(this,props,id,true,true);_this.deploymentItem=void 0;_this.includedDeployment=void 0;_this.deploymentItem=deploymentItem;_this.includedDeployment=includedDeployment;return _this;}createClass_createClass(DeploymentItemIncludeNodeData,[{key:"buildSidePanelTitle",value:function buildSidePanelTitle(){if(this.deploymentItem.include){return this.deploymentItem.include;}else if(this.deploymentItem.git){var s=this.deploymentItem.git.url.split("/");var name=s[s.length-1];return/*#__PURE__*/(0,jsx_runtime.jsxs)(jsx_runtime.Fragment,{children:[name,this.deploymentItem.git.subDir&&/*#__PURE__*/(0,jsx_runtime.jsxs)(jsx_runtime.Fragment,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)("br",{}),this.deploymentItem.git.subDir]})]});}else{return"unknown include";}}},{key:"buildIcon",value:function buildIcon(){if(this.deploymentItem.git){return[/*#__PURE__*/(0,jsx_runtime.jsx)(GitIcon,{}),"git"];}return[/*#__PURE__*/(0,jsx_runtime.jsx)(IncludeIcon,{}),"include"];}},{key:"buildSidePanelTabs",value:function buildSidePanelTabs(){var tabs=[{label:"Summary",content:this.buildSummaryPage()}];this.buildDiffAndHealthPages(tabs);return tabs;}},{key:"buildSummaryPage",value:function buildSummaryPage(){var props=[];if(this.deploymentItem.include){props.push({name:"Type",value:"LocalInclude"});props.push({name:"Path",value:this.deploymentItem.include});}else if(this.deploymentItem.git){props.push({name:"Type",value:"GitInclude"});props.push({name:"Url",value:this.deploymentItem.git.url});props.push({name:"SubDir",value:this.deploymentItem.git.subDir});var ref="HEAD";if(this.deploymentItem.git.ref){ref=this.deploymentItem.git.ref;}props.push({name:"Ref",value:ref});}else{props.push({name:"Type",value:"Unknown"});}buildDeploymentItemSummaryProps(this.deploymentItem,props);return/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(PropertiesTable,{properties:props})});}}]);return DeploymentItemIncludeNodeData;}(NodeData); -;// CONCATENATED MODULE: ./node_modules/@mui/icons-material/esm/Delete.js - - -/* harmony default export */ var Delete = (createSvgIcon( /*#__PURE__*/(0,jsx_runtime.jsx)("path", { - d: "M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z" -}), 'Delete')); -;// CONCATENATED MODULE: ./node_modules/@mui/icons-material/esm/LinkOff.js - - -/* harmony default export */ var LinkOff = (createSvgIcon( /*#__PURE__*/(0,jsx_runtime.jsx)("path", { - d: "M17 7h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1 0 1.43-.98 2.63-2.31 2.98l1.46 1.46C20.88 15.61 22 13.95 22 12c0-2.76-2.24-5-5-5zm-1 4h-2.19l2 2H16zM2 4.27l3.11 3.11C3.29 8.12 2 9.91 2 12c0 2.76 2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1 0-1.59 1.21-2.9 2.76-3.07L8.73 11H8v2h2.73L13 15.27V17h1.73l4.01 4L20 19.74 3.27 3 2 4.27z" -}), 'LinkOff')); -;// CONCATENATED MODULE: ./src/components/result-view/nodes/DeletedOrOrphanObjectsCollectionNode.tsx -var DeletedOrOrphanObjectsCollectionNode=/*#__PURE__*/function(_NodeData){_inherits(DeletedOrOrphanObjectsCollectionNode,_NodeData);var _super=_createSuper(DeletedOrOrphanObjectsCollectionNode);function DeletedOrOrphanObjectsCollectionNode(props,id,deleted){var _this;classCallCheck_classCallCheck(this,DeletedOrOrphanObjectsCollectionNode);_this=_super.call(this,props,id,false,true);_this.deleted=void 0;_this.deleted=deleted;return _this;}createClass_createClass(DeletedOrOrphanObjectsCollectionNode,[{key:"buildSidePanelTitle",value:function buildSidePanelTitle(){if(this.deleted){var _this$diffStatus;return"".concat((_this$diffStatus=this.diffStatus)===null||_this$diffStatus===void 0?void 0:_this$diffStatus.deletedObjects.length," objects deleted");}else{var _this$diffStatus2;return"".concat((_this$diffStatus2=this.diffStatus)===null||_this$diffStatus2===void 0?void 0:_this$diffStatus2.orphanObjects.length," objects orphaned");}}},{key:"buildIcon",value:function buildIcon(){if(this.deleted){return[/*#__PURE__*/(0,jsx_runtime.jsx)(Delete,{fontSize:"large"}),"deleted"];}else{return[/*#__PURE__*/(0,jsx_runtime.jsx)(LinkOff,{fontSize:"large"}),"orphans"];}}},{key:"buildSidePanelTabs",value:function buildSidePanelTabs(){var tabs=[{label:"Summary",content:this.buildSummaryPage()}];return tabs;}},{key:"buildSummaryPage",value:function buildSummaryPage(){var _this$diffStatus3,_this$diffStatus5;var props=[];if((_this$diffStatus3=this.diffStatus)!==null&&_this$diffStatus3!==void 0&&_this$diffStatus3.deletedObjects.length){var _this$diffStatus4;props.push({name:"Deleted",value:(_this$diffStatus4=this.diffStatus)===null||_this$diffStatus4===void 0?void 0:_this$diffStatus4.deletedObjects.length});}if((_this$diffStatus5=this.diffStatus)!==null&&_this$diffStatus5!==void 0&&_this$diffStatus5.orphanObjects.length){var _this$diffStatus6;props.push({name:"Orphaned",value:(_this$diffStatus6=this.diffStatus)===null||_this$diffStatus6===void 0?void 0:_this$diffStatus6.orphanObjects.length});}return/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(PropertiesTable,{properties:props})});}}]);return DeletedOrOrphanObjectsCollectionNode;}(NodeData); -;// CONCATENATED MODULE: ./src/components/result-view/nodes/NodeBuilder.ts -var NodeBuilder=/*#__PURE__*/function(){function NodeBuilder(props){var _props$commandResult$,_this=this,_props$commandResult$2,_props$commandResult$3;classCallCheck_classCallCheck(this,NodeBuilder);this.props=void 0;this.changedObjectsMap=new Map();this.newObjectsMap=new Map();this.orphanObjectsMap=new Map();this.deletedObjectsMap=new Map();this.errorsMap=new Map();this.warningsMap=new Map();this.props=props;(_props$commandResult$=props.commandResult.objects)===null||_props$commandResult$===void 0?void 0:_props$commandResult$.forEach(function(o){var _o$changes;var key=buildObjectRefKey(o.ref);if((_o$changes=o.changes)!==null&&_o$changes!==void 0&&_o$changes.length){_this.changedObjectsMap.set(key,o);}if(o.new){_this.newObjectsMap.set(key,o.ref);}if(o.orphan){_this.orphanObjectsMap.set(key,o.ref);}if(o.deleted){_this.deletedObjectsMap.set(key,o.ref);}});(_props$commandResult$2=props.commandResult.errors)===null||_props$commandResult$2===void 0?void 0:_props$commandResult$2.forEach(function(e){var key=buildObjectRefKey(e.ref);var l=_this.errorsMap.get(key);if(!l){l=[e];_this.errorsMap.set(key,l);}else{l.push(e);}});(_props$commandResult$3=props.commandResult.warnings)===null||_props$commandResult$3===void 0?void 0:_props$commandResult$3.forEach(function(e){var key=buildObjectRefKey(e.ref);var l=_this.warningsMap.get(key);if(!l){l=[e];_this.warningsMap.set(key,l);}else{l.push(e);}});}createClass_createClass(NodeBuilder,[{key:"buildRoot",value:function buildRoot(){var rootNode=new CommandResultNodeData(this.props,"root");if(this.props.commandResult.deployment){this.buildDeploymentProjectChildren(rootNode,this.props.commandResult.deployment);}if(this.deletedObjectsMap.size){this.buildDeletedOrOrphanNode(rootNode,true,Array.from(this.deletedObjectsMap.values()));}if(this.orphanObjectsMap.size){this.buildDeletedOrOrphanNode(rootNode,false,Array.from(this.orphanObjectsMap.values()));}var nodeMap=new Map();function collect(n){nodeMap.set(n.id,n);n.children.forEach(function(c){collect(c);});}collect(rootNode);return[rootNode,nodeMap];}},{key:"buildDeploymentProjectChildren",value:function buildDeploymentProjectChildren(node,deploymentProject){var _deploymentProject$de,_this2=this;if(deploymentProject.vars){this.buildVarsSourceCollectionNode(node,deploymentProject.vars);}(_deploymentProject$de=deploymentProject.deployments)===null||_deploymentProject$de===void 0?void 0:_deploymentProject$de.forEach(function(deploymentItem,i){_this2.buildDeploymentItemNode(node,deploymentItem,i);});}},{key:"buildVarsSourceCollectionNode",value:function buildVarsSourceCollectionNode(parentNode,varsSources){var _node$varsSources,_this3=this;if(varsSources===undefined){return;}var newId="".concat(parentNode.id,"/(vars)");var node=new VarsSourceCollectionNodeData(this.props,newId);(_node$varsSources=node.varsSources).push.apply(_node$varsSources,toConsumableArray_toConsumableArray(varsSources));varsSources.forEach(function(vs,i){_this3.buildVarsSourceNode(node,vs,i);});parentNode.pushChild(node);return node;}},{key:"buildVarsSourceNode",value:function buildVarsSourceNode(parentNode,varsSource,index){var newId="".concat(parentNode.id,"/").concat(index,"}");var node=new VarsSourceNodeData(this.props,newId,varsSource);parentNode.pushChild(node);return node;}},{key:"buildDeploymentItemNode",value:function buildDeploymentItemNode(parentNode,deploymentItem,index){var _this4=this;var node;var newId="".concat(parentNode.id,"/(dis)/").concat(index);if(deploymentItem.path){var _deploymentItem$rende;node=new DeploymentItemNodeData(this.props,newId,deploymentItem);this.buildVarsSourceCollectionNode(node,deploymentItem.vars);(_deploymentItem$rende=deploymentItem.renderedObjects)===null||_deploymentItem$rende===void 0?void 0:_deploymentItem$rende.forEach(function(renderedObject){_this4.buildObjectNode(node,renderedObject);});}else if(deploymentItem.include||deploymentItem.git){node=new DeploymentItemIncludeNodeData(this.props,newId,deploymentItem,deploymentItem.renderedInclude);this.buildVarsSourceCollectionNode(node,deploymentItem.vars);if(deploymentItem.renderedInclude){this.buildDeploymentProjectChildren(node,deploymentItem.renderedInclude);}}else{return node;}parentNode.pushChild(node);return node;}},{key:"buildObjectNode",value:function buildObjectNode(parentNode,objectRef){var _this$errorsMap$get,_this$warningsMap$get;var newId="".concat(parentNode.id,"/(obj)/").concat(buildObjectRefKey(objectRef));var node=new ObjectNodeData(this.props,newId,objectRef);var key=buildObjectRefKey(objectRef);if(this.changedObjectsMap.has(key)){var _node$diffStatus;(_node$diffStatus=node.diffStatus)===null||_node$diffStatus===void 0?void 0:_node$diffStatus.addChangedObject(this.changedObjectsMap.get(key));}if(this.newObjectsMap.has(key)){var _node$diffStatus2;(_node$diffStatus2=node.diffStatus)===null||_node$diffStatus2===void 0?void 0:_node$diffStatus2.newObjects.push(objectRef);}if(this.deletedObjectsMap.has(key)){var _node$diffStatus3;(_node$diffStatus3=node.diffStatus)===null||_node$diffStatus3===void 0?void 0:_node$diffStatus3.deletedObjects.push(objectRef);}if(this.orphanObjectsMap.has(key)){var _node$diffStatus4;(_node$diffStatus4=node.diffStatus)===null||_node$diffStatus4===void 0?void 0:_node$diffStatus4.orphanObjects.push(objectRef);}(_this$errorsMap$get=this.errorsMap.get(key))===null||_this$errorsMap$get===void 0?void 0:_this$errorsMap$get.forEach(function(e){var _node$healthStatus;(_node$healthStatus=node.healthStatus)===null||_node$healthStatus===void 0?void 0:_node$healthStatus.errors.push(e);});(_this$warningsMap$get=this.warningsMap.get(key))===null||_this$warningsMap$get===void 0?void 0:_this$warningsMap$get.forEach(function(e){var _node$healthStatus2;(_node$healthStatus2=node.healthStatus)===null||_node$healthStatus2===void 0?void 0:_node$healthStatus2.warnings.push(e);});parentNode.pushChild(node);return node;}},{key:"buildDeletedOrOrphanNode",value:function buildDeletedOrOrphanNode(parentNode,deleted,refs){var _this5=this;var idType=deleted?"deleted":"orphaned";var newId="".concat(parentNode.id,"/(").concat(idType,")");var node=new DeletedOrOrphanObjectsCollectionNode(this.props,newId,deleted);refs.forEach(function(ref){_this5.buildObjectNode(node,ref);});parentNode.pushChild(node,true);return node;}}]);return NodeBuilder;}();function buildObjectRefKey(ref){return[ref.group,ref.version,ref.kind,ref.namespace,ref.name].join("+");} -;// CONCATENATED MODULE: ./node_modules/@mui/material/Tab/tabClasses.js - - -function getTabUtilityClass(slot) { - return generateUtilityClass_generateUtilityClass('MuiTab', slot); -} -var tabClasses = generateUtilityClasses('MuiTab', ['root', 'labelIcon', 'textColorInherit', 'textColorPrimary', 'textColorSecondary', 'selected', 'disabled', 'fullWidth', 'wrapped', 'iconWrapper']); -/* harmony default export */ var Tab_tabClasses = (tabClasses); -;// CONCATENATED MODULE: ./node_modules/@mui/material/Tab/Tab.js - - - -var Tab_excluded = ["className", "disabled", "disableFocusRipple", "fullWidth", "icon", "iconPosition", "indicator", "label", "onChange", "onClick", "onFocus", "selected", "selectionFollowsFocus", "textColor", "value", "wrapped"]; - - - - - - - - - - - -var Tab_useUtilityClasses = function useUtilityClasses(ownerState) { - var classes = ownerState.classes, - textColor = ownerState.textColor, - fullWidth = ownerState.fullWidth, - wrapped = ownerState.wrapped, - icon = ownerState.icon, - label = ownerState.label, - selected = ownerState.selected, - disabled = ownerState.disabled; - var slots = { - root: ['root', icon && label && 'labelIcon', "textColor".concat(utils_capitalize(textColor)), fullWidth && 'fullWidth', wrapped && 'wrapped', selected && 'selected', disabled && 'disabled'], - iconWrapper: ['iconWrapper'] - }; - return composeClasses(slots, getTabUtilityClass, classes); -}; -var TabRoot = styles_styled(ButtonBase_ButtonBase, { - name: 'MuiTab', - slot: 'Root', +var TabsScroller = styles_styled('div', { + name: 'MuiTabs', + slot: 'Scroller', overridesResolver: function overridesResolver(props, styles) { var ownerState = props.ownerState; - return [styles.root, ownerState.label && ownerState.icon && styles.labelIcon, styles["textColor".concat(utils_capitalize(ownerState.textColor))], ownerState.fullWidth && styles.fullWidth, ownerState.wrapped && styles.wrapped]; + return [styles.scroller, ownerState.fixed && styles.fixed, ownerState.hideScrollbar && styles.hideScrollbar, ownerState.scrollableX && styles.scrollableX, ownerState.scrollableY && styles.scrollableY]; } -})(function (_ref) { - var _ref3, _ref4, _ref5; - var theme = _ref.theme, - ownerState = _ref.ownerState; - return extends_extends({}, theme.typography.button, { - maxWidth: 360, - minWidth: 90, +})(function (_ref5) { + var ownerState = _ref5.ownerState; + return extends_extends({ position: 'relative', - minHeight: 48, - flexShrink: 0, - padding: '12px 16px', - overflow: 'hidden', - whiteSpace: 'normal', - textAlign: 'center' - }, ownerState.label && { - flexDirection: ownerState.iconPosition === 'top' || ownerState.iconPosition === 'bottom' ? 'column' : 'row' - }, { - lineHeight: 1.25 - }, ownerState.icon && ownerState.label && defineProperty_defineProperty({ - minHeight: 72, - paddingTop: 9, - paddingBottom: 9 - }, "& > .".concat(Tab_tabClasses.iconWrapper), extends_extends({}, ownerState.iconPosition === 'top' && { - marginBottom: 6 - }, ownerState.iconPosition === 'bottom' && { - marginTop: 6 - }, ownerState.iconPosition === 'start' && { - marginRight: theme.spacing(1) - }, ownerState.iconPosition === 'end' && { - marginLeft: theme.spacing(1) - })), ownerState.textColor === 'inherit' && (_ref3 = { - color: 'inherit', - opacity: 0.6 - }, defineProperty_defineProperty(_ref3, "&.".concat(Tab_tabClasses.selected), { - opacity: 1 - }), defineProperty_defineProperty(_ref3, "&.".concat(Tab_tabClasses.disabled), { - opacity: (theme.vars || theme).palette.action.disabledOpacity - }), _ref3), ownerState.textColor === 'primary' && (_ref4 = { - color: (theme.vars || theme).palette.text.secondary - }, defineProperty_defineProperty(_ref4, "&.".concat(Tab_tabClasses.selected), { - color: (theme.vars || theme).palette.primary.main - }), defineProperty_defineProperty(_ref4, "&.".concat(Tab_tabClasses.disabled), { - color: (theme.vars || theme).palette.text.disabled - }), _ref4), ownerState.textColor === 'secondary' && (_ref5 = { - color: (theme.vars || theme).palette.text.secondary - }, defineProperty_defineProperty(_ref5, "&.".concat(Tab_tabClasses.selected), { - color: (theme.vars || theme).palette.secondary.main - }), defineProperty_defineProperty(_ref5, "&.".concat(Tab_tabClasses.disabled), { - color: (theme.vars || theme).palette.text.disabled - }), _ref5), ownerState.fullWidth && { - flexShrink: 1, - flexGrow: 1, - flexBasis: 0, - maxWidth: 'none' - }, ownerState.wrapped && { - fontSize: theme.typography.pxToRem(12) + display: 'inline-block', + flex: '1 1 auto', + whiteSpace: 'nowrap' + }, ownerState.fixed && { + overflowX: 'hidden', + width: '100%' + }, ownerState.hideScrollbar && { + // Hide dimensionless scrollbar on macOS + scrollbarWidth: 'none', + // Firefox + '&::-webkit-scrollbar': { + display: 'none' // Safari + Chrome + } + }, ownerState.scrollableX && { + overflowX: 'auto', + overflowY: 'hidden' + }, ownerState.scrollableY && { + overflowY: 'auto', + overflowX: 'hidden' }); }); -var Tab = /*#__PURE__*/react.forwardRef(function Tab(inProps, ref) { - var props = useThemeProps_useThemeProps({ - props: inProps, - name: 'MuiTab' +var FlexContainer = styles_styled('div', { + name: 'MuiTabs', + slot: 'FlexContainer', + overridesResolver: function overridesResolver(props, styles) { + var ownerState = props.ownerState; + return [styles.flexContainer, ownerState.vertical && styles.flexContainerVertical, ownerState.centered && styles.centered]; + } +})(function (_ref6) { + var ownerState = _ref6.ownerState; + return extends_extends({ + display: 'flex' + }, ownerState.vertical && { + flexDirection: 'column' + }, ownerState.centered && { + justifyContent: 'center' }); - var className = props.className, - _props$disabled = props.disabled, - disabled = _props$disabled === void 0 ? false : _props$disabled, - _props$disableFocusRi = props.disableFocusRipple, - disableFocusRipple = _props$disableFocusRi === void 0 ? false : _props$disableFocusRi, - fullWidth = props.fullWidth, - iconProp = props.icon, - _props$iconPosition = props.iconPosition, - iconPosition = _props$iconPosition === void 0 ? 'top' : _props$iconPosition, - indicator = props.indicator, - label = props.label, +}); +var TabsIndicator = styles_styled('span', { + name: 'MuiTabs', + slot: 'Indicator', + overridesResolver: function overridesResolver(props, styles) { + return styles.indicator; + } +})(function (_ref7) { + var ownerState = _ref7.ownerState, + theme = _ref7.theme; + return extends_extends({ + position: 'absolute', + height: 2, + bottom: 0, + width: '100%', + transition: theme.transitions.create() + }, ownerState.indicatorColor === 'primary' && { + backgroundColor: (theme.vars || theme).palette.primary.main + }, ownerState.indicatorColor === 'secondary' && { + backgroundColor: (theme.vars || theme).palette.secondary.main + }, ownerState.vertical && { + height: '100%', + width: 2, + right: 0 + }); +}); +var TabsScrollbarSize = styles_styled(ScrollbarSize, { + name: 'MuiTabs', + slot: 'ScrollbarSize' +})({ + overflowX: 'auto', + overflowY: 'hidden', + // Hide dimensionless scrollbar on macOS + scrollbarWidth: 'none', + // Firefox + '&::-webkit-scrollbar': { + display: 'none' // Safari + Chrome + } +}); + +var defaultIndicatorStyle = {}; +var warnedOnceTabPresent = false; +var Tabs = /*#__PURE__*/react.forwardRef(function Tabs(inProps, ref) { + var props = useThemeProps_useThemeProps({ + props: inProps, + name: 'MuiTabs' + }); + var theme = styles_useTheme_useTheme(); + var isRtl = theme.direction === 'rtl'; + var ariaLabel = props['aria-label'], + ariaLabelledBy = props['aria-labelledby'], + action = props.action, + _props$centered = props.centered, + centered = _props$centered === void 0 ? false : _props$centered, + childrenProp = props.children, + className = props.className, + _props$component = props.component, + component = _props$component === void 0 ? 'div' : _props$component, + _props$allowScrollBut = props.allowScrollButtonsMobile, + allowScrollButtonsMobile = _props$allowScrollBut === void 0 ? false : _props$allowScrollBut, + _props$indicatorColor = props.indicatorColor, + indicatorColor = _props$indicatorColor === void 0 ? 'primary' : _props$indicatorColor, onChange = props.onChange, - onClick = props.onClick, - onFocus = props.onFocus, - selected = props.selected, + _props$orientation = props.orientation, + orientation = _props$orientation === void 0 ? 'horizontal' : _props$orientation, + _props$ScrollButtonCo = props.ScrollButtonComponent, + ScrollButtonComponent = _props$ScrollButtonCo === void 0 ? TabScrollButton_TabScrollButton : _props$ScrollButtonCo, + _props$scrollButtons = props.scrollButtons, + scrollButtons = _props$scrollButtons === void 0 ? 'auto' : _props$scrollButtons, selectionFollowsFocus = props.selectionFollowsFocus, + _props$slots = props.slots, + slots = _props$slots === void 0 ? {} : _props$slots, + _props$slotProps = props.slotProps, + slotProps = _props$slotProps === void 0 ? {} : _props$slotProps, + _props$TabIndicatorPr = props.TabIndicatorProps, + TabIndicatorProps = _props$TabIndicatorPr === void 0 ? {} : _props$TabIndicatorPr, + _props$TabScrollButto = props.TabScrollButtonProps, + TabScrollButtonProps = _props$TabScrollButto === void 0 ? {} : _props$TabScrollButto, _props$textColor = props.textColor, - textColor = _props$textColor === void 0 ? 'inherit' : _props$textColor, + textColor = _props$textColor === void 0 ? 'primary' : _props$textColor, value = props.value, - _props$wrapped = props.wrapped, - wrapped = _props$wrapped === void 0 ? false : _props$wrapped, - other = objectWithoutPropertiesLoose_objectWithoutPropertiesLoose(props, Tab_excluded); + _props$variant = props.variant, + variant = _props$variant === void 0 ? 'standard' : _props$variant, + _props$visibleScrollb = props.visibleScrollbar, + visibleScrollbar = _props$visibleScrollb === void 0 ? false : _props$visibleScrollb, + other = objectWithoutPropertiesLoose_objectWithoutPropertiesLoose(props, Tabs_excluded); + var scrollable = variant === 'scrollable'; + var vertical = orientation === 'vertical'; + var scrollStart = vertical ? 'scrollTop' : 'scrollLeft'; + var start = vertical ? 'top' : 'left'; + var end = vertical ? 'bottom' : 'right'; + var clientSize = vertical ? 'clientHeight' : 'clientWidth'; + var size = vertical ? 'height' : 'width'; var ownerState = extends_extends({}, props, { - disabled: disabled, - disableFocusRipple: disableFocusRipple, - selected: selected, - icon: !!iconProp, - iconPosition: iconPosition, - label: !!label, - fullWidth: fullWidth, + component: component, + allowScrollButtonsMobile: allowScrollButtonsMobile, + indicatorColor: indicatorColor, + orientation: orientation, + vertical: vertical, + scrollButtons: scrollButtons, textColor: textColor, - wrapped: wrapped + variant: variant, + visibleScrollbar: visibleScrollbar, + fixed: !scrollable, + hideScrollbar: scrollable && !visibleScrollbar, + scrollableX: scrollable && !vertical, + scrollableY: scrollable && vertical, + centered: centered && !scrollable, + scrollButtonsHideMobile: !allowScrollButtonsMobile }); - var classes = Tab_useUtilityClasses(ownerState); - var icon = iconProp && label && /*#__PURE__*/react.isValidElement(iconProp) ? /*#__PURE__*/react.cloneElement(iconProp, { - className: clsx_m(classes.iconWrapper, iconProp.props.className) - }) : iconProp; - var handleClick = function handleClick(event) { - if (!selected && onChange) { - onChange(event, value); + var classes = Tabs_useUtilityClasses(ownerState); + var startScrollButtonIconProps = useSlotProps({ + elementType: slots.StartScrollButtonIcon, + externalSlotProps: slotProps.startScrollButtonIcon, + ownerState: ownerState + }); + var endScrollButtonIconProps = useSlotProps({ + elementType: slots.EndScrollButtonIcon, + externalSlotProps: slotProps.endScrollButtonIcon, + ownerState: ownerState + }); + if (false) {} + var _React$useState = react.useState(false), + _React$useState2 = slicedToArray_slicedToArray(_React$useState, 2), + mounted = _React$useState2[0], + setMounted = _React$useState2[1]; + var _React$useState3 = react.useState(defaultIndicatorStyle), + _React$useState4 = slicedToArray_slicedToArray(_React$useState3, 2), + indicatorStyle = _React$useState4[0], + setIndicatorStyle = _React$useState4[1]; + var _React$useState5 = react.useState({ + start: false, + end: false + }), + _React$useState6 = slicedToArray_slicedToArray(_React$useState5, 2), + displayScroll = _React$useState6[0], + setDisplayScroll = _React$useState6[1]; + var _React$useState7 = react.useState({ + overflow: 'hidden', + scrollbarWidth: 0 + }), + _React$useState8 = slicedToArray_slicedToArray(_React$useState7, 2), + scrollerStyle = _React$useState8[0], + setScrollerStyle = _React$useState8[1]; + var valueToIndex = new Map(); + var tabsRef = react.useRef(null); + var tabListRef = react.useRef(null); + var getTabsMeta = function getTabsMeta() { + var tabsNode = tabsRef.current; + var tabsMeta; + if (tabsNode) { + var rect = tabsNode.getBoundingClientRect(); + // create a new object with ClientRect class props + scrollLeft + tabsMeta = { + clientWidth: tabsNode.clientWidth, + scrollLeft: tabsNode.scrollLeft, + scrollTop: tabsNode.scrollTop, + scrollLeftNormalized: getNormalizedScrollLeft(tabsNode, theme.direction), + scrollWidth: tabsNode.scrollWidth, + top: rect.top, + bottom: rect.bottom, + left: rect.left, + right: rect.right + }; } - if (onClick) { - onClick(event); + var tabMeta; + if (tabsNode && value !== false) { + var _children = tabListRef.current.children; + if (_children.length > 0) { + var tab = _children[valueToIndex.get(value)]; + if (false) {} + tabMeta = tab ? tab.getBoundingClientRect() : null; + if (false) {} + } } + return { + tabsMeta: tabsMeta, + tabMeta: tabMeta + }; }; - var handleFocus = function handleFocus(event) { - if (selectionFollowsFocus && !selected && onChange) { - onChange(event, value); + var updateIndicatorState = utils_useEventCallback(function () { + var _newIndicatorStyle; + var _getTabsMeta = getTabsMeta(), + tabsMeta = _getTabsMeta.tabsMeta, + tabMeta = _getTabsMeta.tabMeta; + var startValue = 0; + var startIndicator; + if (vertical) { + startIndicator = 'top'; + if (tabMeta && tabsMeta) { + startValue = tabMeta.top - tabsMeta.top + tabsMeta.scrollTop; + } + } else { + startIndicator = isRtl ? 'right' : 'left'; + if (tabMeta && tabsMeta) { + var correction = isRtl ? tabsMeta.scrollLeftNormalized + tabsMeta.clientWidth - tabsMeta.scrollWidth : tabsMeta.scrollLeft; + startValue = (isRtl ? -1 : 1) * (tabMeta[startIndicator] - tabsMeta[startIndicator] + correction); + } } - if (onFocus) { - onFocus(event); + var newIndicatorStyle = (_newIndicatorStyle = {}, defineProperty_defineProperty(_newIndicatorStyle, startIndicator, startValue), defineProperty_defineProperty(_newIndicatorStyle, size, tabMeta ? tabMeta[size] : 0), _newIndicatorStyle); + + // IE11 support, replace with Number.isNaN + // eslint-disable-next-line no-restricted-globals + if (isNaN(indicatorStyle[startIndicator]) || isNaN(indicatorStyle[size])) { + setIndicatorStyle(newIndicatorStyle); + } else { + var dStart = Math.abs(indicatorStyle[startIndicator] - newIndicatorStyle[startIndicator]); + var dSize = Math.abs(indicatorStyle[size] - newIndicatorStyle[size]); + if (dStart >= 1 || dSize >= 1) { + setIndicatorStyle(newIndicatorStyle); + } } - }; - return /*#__PURE__*/(0,jsx_runtime.jsxs)(TabRoot, extends_extends({ - focusRipple: !disableFocusRipple, - className: clsx_m(classes.root, className), - ref: ref, - role: "tab", - "aria-selected": selected, - disabled: disabled, - onClick: handleClick, - onFocus: handleFocus, - ownerState: ownerState, - tabIndex: selected ? 0 : -1 - }, other, { - children: [iconPosition === 'top' || iconPosition === 'start' ? /*#__PURE__*/(0,jsx_runtime.jsxs)(react.Fragment, { - children: [icon, label] - }) : /*#__PURE__*/(0,jsx_runtime.jsxs)(react.Fragment, { - children: [label, icon] - }), indicator] - })); -}); - false ? 0 : void 0; -/* harmony default export */ var Tab_Tab = (Tab); -;// CONCATENATED MODULE: ./node_modules/@mui/lab/TabContext/TabContext.js - - - - -/** - * @type {React.Context<{ idPrefix: string; value: string } | null>} - */ - -var Context = /*#__PURE__*/react.createContext(null); -if (false) {} -function useUniquePrefix() { - var _React$useState = react.useState(null), - _React$useState2 = slicedToArray_slicedToArray(_React$useState, 2), - id = _React$useState2[0], - setId = _React$useState2[1]; - react.useEffect(function () { - setId("mui-p-".concat(Math.round(Math.random() * 1e5))); - }, []); - return id; -} -function TabContext(props) { - var children = props.children, - value = props.value; - var idPrefix = useUniquePrefix(); - var context = react.useMemo(function () { - return { - idPrefix: idPrefix, - value: value - }; - }, [idPrefix, value]); - return /*#__PURE__*/(0,jsx_runtime.jsx)(Context.Provider, { - value: context, - children: children }); -} - false ? 0 : void 0; - -/** - * @returns {unknown} - */ -function useTabContext() { - return react.useContext(Context); -} -function getPanelId(context, value) { - var idPrefix = context.idPrefix; - if (idPrefix === null) { - return null; - } - return "".concat(context.idPrefix, "-P-").concat(value); -} -function getTabId(context, value) { - var idPrefix = context.idPrefix; - if (idPrefix === null) { - return null; - } - return "".concat(context.idPrefix, "-T-").concat(value); -} -;// CONCATENATED MODULE: ./node_modules/@mui/utils/esm/scrollLeft.js -// Source from https://github.com/alitaheri/normalize-scroll-left -var cachedType; - -/** - * Based on the jquery plugin https://github.com/othree/jquery.rtl-scroll-type - * - * Types of scrollLeft, assuming scrollWidth=100 and direction is rtl. - * - * Type | <- Most Left | Most Right -> | Initial - * ---------------- | ------------ | ------------- | ------- - * default | 0 | 100 | 100 - * negative (spec*) | -100 | 0 | 0 - * reverse | 100 | 0 | 0 - * - * Edge 85: default - * Safari 14: negative - * Chrome 85: negative - * Firefox 81: negative - * IE11: reverse - * - * spec* https://drafts.csswg.org/cssom-view/#dom-window-scroll - */ -function detectScrollType() { - if (cachedType) { - return cachedType; - } - var dummy = document.createElement('div'); - var container = document.createElement('div'); - container.style.width = '10px'; - container.style.height = '1px'; - dummy.appendChild(container); - dummy.dir = 'rtl'; - dummy.style.fontSize = '14px'; - dummy.style.width = '4px'; - dummy.style.height = '1px'; - dummy.style.position = 'absolute'; - dummy.style.top = '-1000px'; - dummy.style.overflow = 'scroll'; - document.body.appendChild(dummy); - cachedType = 'reverse'; - if (dummy.scrollLeft > 0) { - cachedType = 'default'; - } else { - dummy.scrollLeft = 1; - if (dummy.scrollLeft === 0) { - cachedType = 'negative'; + var scroll = function scroll(scrollValue) { + var _ref8 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, + _ref8$animation = _ref8.animation, + animation = _ref8$animation === void 0 ? true : _ref8$animation; + if (animation) { + animate(scrollStart, tabsRef.current, scrollValue, { + duration: theme.transitions.duration.standard + }); + } else { + tabsRef.current[scrollStart] = scrollValue; } - } - document.body.removeChild(dummy); - return cachedType; -} - -// Based on https://stackoverflow.com/a/24394376 -function getNormalizedScrollLeft(element, direction) { - var scrollLeft = element.scrollLeft; - - // Perform the calculations only when direction is rtl to avoid messing up the ltr behavior - if (direction !== 'rtl') { - return scrollLeft; - } - var type = detectScrollType(); - switch (type) { - case 'negative': - return element.scrollWidth - element.clientWidth + scrollLeft; - case 'reverse': - return element.scrollWidth - element.clientWidth - scrollLeft; - default: - return scrollLeft; - } -} -;// CONCATENATED MODULE: ./node_modules/@mui/material/internal/animate.js -function easeInOutSin(time) { - return (1 + Math.sin(Math.PI * time - Math.PI / 2)) / 2; -} -function animate(property, element, to) { - var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; - var cb = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : function () {}; - var _options$ease = options.ease, - ease = _options$ease === void 0 ? easeInOutSin : _options$ease, - _options$duration = options.duration, - duration = _options$duration === void 0 ? 300 : _options$duration; - var start = null; - var from = element[property]; - var cancelled = false; - var cancel = function cancel() { - cancelled = true; }; - var step = function step(timestamp) { - if (cancelled) { - cb(new Error('Animation cancelled')); - return; - } - if (start === null) { - start = timestamp; + var moveTabsScroll = function moveTabsScroll(delta) { + var scrollValue = tabsRef.current[scrollStart]; + if (vertical) { + scrollValue += delta; + } else { + scrollValue += delta * (isRtl ? -1 : 1); + // Fix for Edge + scrollValue *= isRtl && detectScrollType() === 'reverse' ? -1 : 1; } - var time = Math.min(1, (timestamp - start) / duration); - element[property] = ease(time) * (to - from) + from; - if (time >= 1) { - requestAnimationFrame(function () { - cb(null); - }); - return; + scroll(scrollValue); + }; + var getScrollSize = function getScrollSize() { + var containerSize = tabsRef.current[clientSize]; + var totalSize = 0; + var children = Array.from(tabListRef.current.children); + for (var i = 0; i < children.length; i += 1) { + var tab = children[i]; + if (totalSize + tab[clientSize] > containerSize) { + // If the first item is longer than the container size, then only scroll + // by the container size. + if (i === 0) { + totalSize = containerSize; + } + break; + } + totalSize += tab[clientSize]; } - requestAnimationFrame(step); + return totalSize; }; - if (from === to) { - cb(new Error('Element already at target position')); - return cancel; - } - requestAnimationFrame(step); - return cancel; -} -;// CONCATENATED MODULE: ./node_modules/@mui/material/Tabs/ScrollbarSize.js - - -var ScrollbarSize_excluded = ["onChange"]; - - - - - -var ScrollbarSize_styles = { - width: 99, - height: 99, - position: 'absolute', - top: -9999, - overflow: 'scroll' -}; - -/** - * @ignore - internal component. - * The component originates from https://github.com/STORIS/react-scrollbar-size. - * It has been moved into the core in order to minimize the bundle size. - */ -function ScrollbarSize(props) { - var onChange = props.onChange, - other = objectWithoutPropertiesLoose_objectWithoutPropertiesLoose(props, ScrollbarSize_excluded); - var scrollbarHeight = react.useRef(); - var nodeRef = react.useRef(null); - var setMeasurements = function setMeasurements() { - scrollbarHeight.current = nodeRef.current.offsetHeight - nodeRef.current.clientHeight; + var handleStartScrollClick = function handleStartScrollClick() { + moveTabsScroll(-1 * getScrollSize()); }; - utils_useEnhancedEffect(function () { - var handleResize = utils_debounce(function () { - var prevHeight = scrollbarHeight.current; - setMeasurements(); - if (prevHeight !== scrollbarHeight.current) { - onChange(scrollbarHeight.current); + var handleEndScrollClick = function handleEndScrollClick() { + moveTabsScroll(getScrollSize()); + }; + + // TODO Remove as browser support for hiding the scrollbar + // with CSS improves. + var handleScrollbarSizeChange = react.useCallback(function (scrollbarWidth) { + setScrollerStyle({ + overflow: null, + scrollbarWidth: scrollbarWidth + }); + }, []); + var getConditionalElements = function getConditionalElements() { + var conditionalElements = {}; + conditionalElements.scrollbarSizeListener = scrollable ? /*#__PURE__*/(0,jsx_runtime.jsx)(TabsScrollbarSize, { + onChange: handleScrollbarSizeChange, + className: clsx_m(classes.scrollableX, classes.hideScrollbar) + }) : null; + var scrollButtonsActive = displayScroll.start || displayScroll.end; + var showScrollButtons = scrollable && (scrollButtons === 'auto' && scrollButtonsActive || scrollButtons === true); + conditionalElements.scrollButtonStart = showScrollButtons ? /*#__PURE__*/(0,jsx_runtime.jsx)(ScrollButtonComponent, extends_extends({ + slots: { + StartScrollButtonIcon: slots.StartScrollButtonIcon + }, + slotProps: { + startScrollButtonIcon: startScrollButtonIconProps + }, + orientation: orientation, + direction: isRtl ? 'right' : 'left', + onClick: handleStartScrollClick, + disabled: !displayScroll.start + }, TabScrollButtonProps, { + className: clsx_m(classes.scrollButtons, TabScrollButtonProps.className) + })) : null; + conditionalElements.scrollButtonEnd = showScrollButtons ? /*#__PURE__*/(0,jsx_runtime.jsx)(ScrollButtonComponent, extends_extends({ + slots: { + EndScrollButtonIcon: slots.EndScrollButtonIcon + }, + slotProps: { + endScrollButtonIcon: endScrollButtonIconProps + }, + orientation: orientation, + direction: isRtl ? 'left' : 'right', + onClick: handleEndScrollClick, + disabled: !displayScroll.end + }, TabScrollButtonProps, { + className: clsx_m(classes.scrollButtons, TabScrollButtonProps.className) + })) : null; + return conditionalElements; + }; + var scrollSelectedIntoView = utils_useEventCallback(function (animation) { + var _getTabsMeta2 = getTabsMeta(), + tabsMeta = _getTabsMeta2.tabsMeta, + tabMeta = _getTabsMeta2.tabMeta; + if (!tabMeta || !tabsMeta) { + return; + } + if (tabMeta[start] < tabsMeta[start]) { + // left side of button is out of view + var nextScrollStart = tabsMeta[scrollStart] + (tabMeta[start] - tabsMeta[start]); + scroll(nextScrollStart, { + animation: animation + }); + } else if (tabMeta[end] > tabsMeta[end]) { + // right side of button is out of view + var _nextScrollStart = tabsMeta[scrollStart] + (tabMeta[end] - tabsMeta[end]); + scroll(_nextScrollStart, { + animation: animation + }); + } + }); + var updateScrollButtonState = utils_useEventCallback(function () { + if (scrollable && scrollButtons !== false) { + var _tabsRef$current = tabsRef.current, + scrollTop = _tabsRef$current.scrollTop, + scrollHeight = _tabsRef$current.scrollHeight, + clientHeight = _tabsRef$current.clientHeight, + scrollWidth = _tabsRef$current.scrollWidth, + clientWidth = _tabsRef$current.clientWidth; + var showStartScroll; + var showEndScroll; + if (vertical) { + showStartScroll = scrollTop > 1; + showEndScroll = scrollTop < scrollHeight - clientHeight - 1; + } else { + var scrollLeft = getNormalizedScrollLeft(tabsRef.current, theme.direction); + // use 1 for the potential rounding error with browser zooms. + showStartScroll = isRtl ? scrollLeft < scrollWidth - clientWidth - 1 : scrollLeft > 1; + showEndScroll = !isRtl ? scrollLeft < scrollWidth - clientWidth - 1 : scrollLeft > 1; + } + if (showStartScroll !== displayScroll.start || showEndScroll !== displayScroll.end) { + setDisplayScroll({ + start: showStartScroll, + end: showEndScroll + }); + } + } + }); + react.useEffect(function () { + var handleResize = utils_debounce(function () { + // If the Tabs component is replaced by Suspense with a fallback, the last + // ResizeObserver's handler that runs because of the change in the layout is trying to + // access a dom node that is no longer there (as the fallback component is being shown instead). + // See https://github.com/mui/material-ui/issues/33276 + // TODO: Add tests that will ensure the component is not failing when + // replaced by Suspense with a fallback, once React is updated to version 18 + if (tabsRef.current) { + updateIndicatorState(); + updateScrollButtonState(); } }); - var containerWindow = utils_ownerWindow(nodeRef.current); - containerWindow.addEventListener('resize', handleResize); + var win = utils_ownerWindow(tabsRef.current); + win.addEventListener('resize', handleResize); + var resizeObserver; + if (typeof ResizeObserver !== 'undefined') { + resizeObserver = new ResizeObserver(handleResize); + Array.from(tabListRef.current.children).forEach(function (child) { + resizeObserver.observe(child); + }); + } return function () { handleResize.clear(); - containerWindow.removeEventListener('resize', handleResize); + win.removeEventListener('resize', handleResize); + if (resizeObserver) { + resizeObserver.disconnect(); + } }; - }, [onChange]); + }, [updateIndicatorState, updateScrollButtonState]); + var handleTabsScroll = react.useMemo(function () { + return utils_debounce(function () { + updateScrollButtonState(); + }); + }, [updateScrollButtonState]); react.useEffect(function () { - setMeasurements(); - onChange(scrollbarHeight.current); - }, [onChange]); - return /*#__PURE__*/(0,jsx_runtime.jsx)("div", extends_extends({ - style: ScrollbarSize_styles, - ref: nodeRef - }, other)); -} - false ? 0 : void 0; -;// CONCATENATED MODULE: ./node_modules/@mui/material/internal/svg-icons/KeyboardArrowLeft.js - - - -/** - * @ignore - internal component. - */ - -/* harmony default export */ var KeyboardArrowLeft = (createSvgIcon( /*#__PURE__*/(0,jsx_runtime.jsx)("path", { - d: "M15.41 16.09l-4.58-4.59 4.58-4.59L14 5.5l-6 6 6 6z" -}), 'KeyboardArrowLeft')); -;// CONCATENATED MODULE: ./node_modules/@mui/material/internal/svg-icons/KeyboardArrowRight.js - - - -/** - * @ignore - internal component. - */ - -/* harmony default export */ var KeyboardArrowRight = (createSvgIcon( /*#__PURE__*/(0,jsx_runtime.jsx)("path", { - d: "M8.59 16.34l4.58-4.59-4.58-4.59L10 5.75l6 6-6 6z" -}), 'KeyboardArrowRight')); -;// CONCATENATED MODULE: ./node_modules/@mui/material/TabScrollButton/tabScrollButtonClasses.js - - -function getTabScrollButtonUtilityClass(slot) { - return generateUtilityClass_generateUtilityClass('MuiTabScrollButton', slot); -} -var tabScrollButtonClasses = generateUtilityClasses('MuiTabScrollButton', ['root', 'vertical', 'horizontal', 'disabled']); -/* harmony default export */ var TabScrollButton_tabScrollButtonClasses = (tabScrollButtonClasses); -;// CONCATENATED MODULE: ./node_modules/@mui/material/TabScrollButton/TabScrollButton.js - - - -var TabScrollButton_excluded = ["className", "slots", "slotProps", "direction", "orientation", "disabled"]; -/* eslint-disable jsx-a11y/aria-role */ - - - - - - - - - - - - -var TabScrollButton_useUtilityClasses = function useUtilityClasses(ownerState) { - var classes = ownerState.classes, - orientation = ownerState.orientation, - disabled = ownerState.disabled; - var slots = { - root: ['root', orientation, disabled && 'disabled'] - }; - return composeClasses(slots, getTabScrollButtonUtilityClass, classes); -}; -var TabScrollButtonRoot = styles_styled(ButtonBase_ButtonBase, { - name: 'MuiTabScrollButton', - slot: 'Root', - overridesResolver: function overridesResolver(props, styles) { - var ownerState = props.ownerState; - return [styles.root, ownerState.orientation && styles[ownerState.orientation]]; - } -})(function (_ref) { - var ownerState = _ref.ownerState; - return extends_extends(defineProperty_defineProperty({ - width: 40, - flexShrink: 0, - opacity: 0.8 - }, "&.".concat(TabScrollButton_tabScrollButtonClasses.disabled), { - opacity: 0 - }), ownerState.orientation === 'vertical' && { - width: '100%', - height: 40, - '& svg': { - transform: "rotate(".concat(ownerState.isRtl ? -90 : 90, "deg)") - } - }); -}); -var TabScrollButton = /*#__PURE__*/react.forwardRef(function TabScrollButton(inProps, ref) { - var _slots$StartScrollBut, _slots$EndScrollButto; - var props = useThemeProps_useThemeProps({ - props: inProps, - name: 'MuiTabScrollButton' + return function () { + handleTabsScroll.clear(); + }; + }, [handleTabsScroll]); + react.useEffect(function () { + setMounted(true); + }, []); + react.useEffect(function () { + updateIndicatorState(); + updateScrollButtonState(); }); - var className = props.className, - _props$slots = props.slots, - slots = _props$slots === void 0 ? {} : _props$slots, - _props$slotProps = props.slotProps, - slotProps = _props$slotProps === void 0 ? {} : _props$slotProps, - direction = props.direction, - other = objectWithoutPropertiesLoose_objectWithoutPropertiesLoose(props, TabScrollButton_excluded); - var theme = styles_useTheme_useTheme(); - var isRtl = theme.direction === 'rtl'; - var ownerState = extends_extends({ - isRtl: isRtl - }, props); - var classes = TabScrollButton_useUtilityClasses(ownerState); - var StartButtonIcon = (_slots$StartScrollBut = slots.StartScrollButtonIcon) != null ? _slots$StartScrollBut : KeyboardArrowLeft; - var EndButtonIcon = (_slots$EndScrollButto = slots.EndScrollButtonIcon) != null ? _slots$EndScrollButto : KeyboardArrowRight; - var startButtonIconProps = useSlotProps({ - elementType: StartButtonIcon, - externalSlotProps: slotProps.startScrollButtonIcon, - additionalProps: { - fontSize: 'small' - }, - ownerState: ownerState - }); - var endButtonIconProps = useSlotProps({ - elementType: EndButtonIcon, - externalSlotProps: slotProps.endScrollButtonIcon, - additionalProps: { - fontSize: 'small' - }, - ownerState: ownerState + react.useEffect(function () { + // Don't animate on the first render. + scrollSelectedIntoView(defaultIndicatorStyle !== indicatorStyle); + }, [scrollSelectedIntoView, indicatorStyle]); + react.useImperativeHandle(action, function () { + return { + updateIndicator: updateIndicatorState, + updateScrollButtons: updateScrollButtonState + }; + }, [updateIndicatorState, updateScrollButtonState]); + var indicator = /*#__PURE__*/(0,jsx_runtime.jsx)(TabsIndicator, extends_extends({}, TabIndicatorProps, { + className: clsx_m(classes.indicator, TabIndicatorProps.className), + ownerState: ownerState, + style: extends_extends({}, indicatorStyle, TabIndicatorProps.style) + })); + var childIndex = 0; + var children = react.Children.map(childrenProp, function (child) { + if (! /*#__PURE__*/react.isValidElement(child)) { + return null; + } + if (false) {} + var childValue = child.props.value === undefined ? childIndex : child.props.value; + valueToIndex.set(childValue, childIndex); + var selected = childValue === value; + childIndex += 1; + return /*#__PURE__*/react.cloneElement(child, extends_extends({ + fullWidth: variant === 'fullWidth', + indicator: selected && !mounted && indicator, + selected: selected, + selectionFollowsFocus: selectionFollowsFocus, + onChange: onChange, + textColor: textColor, + value: childValue + }, childIndex === 1 && value === false && !child.props.tabIndex ? { + tabIndex: 0 + } : {})); }); - return /*#__PURE__*/(0,jsx_runtime.jsx)(TabScrollButtonRoot, extends_extends({ - component: "div", + var handleKeyDown = function handleKeyDown(event) { + var list = tabListRef.current; + var currentFocus = utils_ownerDocument(list).activeElement; + // Keyboard navigation assumes that [role="tab"] are siblings + // though we might warn in the future about nested, interactive elements + // as a a11y violation + var role = currentFocus.getAttribute('role'); + if (role !== 'tab') { + return; + } + var previousItemKey = orientation === 'horizontal' ? 'ArrowLeft' : 'ArrowUp'; + var nextItemKey = orientation === 'horizontal' ? 'ArrowRight' : 'ArrowDown'; + if (orientation === 'horizontal' && isRtl) { + // swap previousItemKey with nextItemKey + previousItemKey = 'ArrowRight'; + nextItemKey = 'ArrowLeft'; + } + switch (event.key) { + case previousItemKey: + event.preventDefault(); + Tabs_moveFocus(list, currentFocus, Tabs_previousItem); + break; + case nextItemKey: + event.preventDefault(); + Tabs_moveFocus(list, currentFocus, Tabs_nextItem); + break; + case 'Home': + event.preventDefault(); + Tabs_moveFocus(list, null, Tabs_nextItem); + break; + case 'End': + event.preventDefault(); + Tabs_moveFocus(list, null, Tabs_previousItem); + break; + default: + break; + } + }; + var conditionalElements = getConditionalElements(); + return /*#__PURE__*/(0,jsx_runtime.jsxs)(TabsRoot, extends_extends({ className: clsx_m(classes.root, className), - ref: ref, - role: null, ownerState: ownerState, - tabIndex: null + ref: ref, + as: component }, other, { - children: direction === 'left' ? /*#__PURE__*/(0,jsx_runtime.jsx)(StartButtonIcon, extends_extends({}, startButtonIconProps)) : /*#__PURE__*/(0,jsx_runtime.jsx)(EndButtonIcon, extends_extends({}, endButtonIconProps)) + children: [conditionalElements.scrollButtonStart, conditionalElements.scrollbarSizeListener, /*#__PURE__*/(0,jsx_runtime.jsxs)(TabsScroller, { + className: classes.scroller, + ownerState: ownerState, + style: defineProperty_defineProperty({ + overflow: scrollerStyle.overflow + }, vertical ? "margin".concat(isRtl ? 'Left' : 'Right') : 'marginBottom', visibleScrollbar ? undefined : -scrollerStyle.scrollbarWidth), + ref: tabsRef, + onScroll: handleTabsScroll, + children: [/*#__PURE__*/(0,jsx_runtime.jsx)(FlexContainer, { + "aria-label": ariaLabel, + "aria-labelledby": ariaLabelledBy, + "aria-orientation": orientation === 'vertical' ? 'vertical' : null, + className: classes.flexContainer, + ownerState: ownerState, + onKeyDown: handleKeyDown, + ref: tabListRef, + role: "tablist", + children: children + }), mounted && indicator] + }), conditionalElements.scrollButtonEnd] })); }); false ? 0 : void 0; -/* harmony default export */ var TabScrollButton_TabScrollButton = (TabScrollButton); -;// CONCATENATED MODULE: ./node_modules/@mui/material/Tabs/tabsClasses.js +/* harmony default export */ var Tabs_Tabs = (Tabs); +;// CONCATENATED MODULE: ./node_modules/@mui/lab/TabList/TabList.js -function getTabsUtilityClass(slot) { - return generateUtilityClass_generateUtilityClass('MuiTabs', slot); +var TabList_excluded = ["children"]; + + + + + +var TabList = /*#__PURE__*/react.forwardRef(function TabList(props, ref) { + var childrenProp = props.children, + other = objectWithoutPropertiesLoose_objectWithoutPropertiesLoose(props, TabList_excluded); + var context = useTabContext(); + if (context === null) { + throw new TypeError('No TabContext provided'); + } + var children = react.Children.map(childrenProp, function (child) { + if (! /*#__PURE__*/react.isValidElement(child)) { + return null; + } + return /*#__PURE__*/react.cloneElement(child, { + // SOMEDAY: `Tabs` will set those themselves + 'aria-controls': getPanelId(context, child.props.value), + id: getTabId(context, child.props.value) + }); + }); + return /*#__PURE__*/(0,jsx_runtime.jsx)(Tabs_Tabs, extends_extends({}, other, { + ref: ref, + value: context.value, + children: children + })); +}); + false ? 0 : void 0; +/* harmony default export */ var TabList_TabList = (TabList); +;// CONCATENATED MODULE: ./node_modules/@mui/lab/TabPanel/tabPanelClasses.js + + +function getTabPanelUtilityClass(slot) { + return generateUtilityClass_generateUtilityClass('MuiTabPanel', slot); } -var tabsClasses = generateUtilityClasses('MuiTabs', ['root', 'vertical', 'flexContainer', 'flexContainerVertical', 'centered', 'scroller', 'fixed', 'scrollableX', 'scrollableY', 'hideScrollbar', 'scrollButtons', 'scrollButtonsHideMobile', 'indicator']); -/* harmony default export */ var Tabs_tabsClasses = (tabsClasses); -;// CONCATENATED MODULE: ./node_modules/@mui/material/Tabs/Tabs.js +var tabPanelClasses = generateUtilityClasses('MuiTabPanel', ['root']); +/* harmony default export */ var TabPanel_tabPanelClasses = ((/* unused pure expression or super */ null && (tabPanelClasses))); +;// CONCATENATED MODULE: ./node_modules/@mui/lab/TabPanel/TabPanel.js + +var TabPanel_excluded = ["children", "className", "value"]; -var Tabs_excluded = ["aria-label", "aria-labelledby", "action", "centered", "children", "className", "component", "allowScrollButtonsMobile", "indicatorColor", "onChange", "orientation", "ScrollButtonComponent", "scrollButtons", "selectionFollowsFocus", "slots", "slotProps", "TabIndicatorProps", "TabScrollButtonProps", "textColor", "value", "variant", "visibleScrollbar"]; +var TabPanel_useUtilityClasses = function useUtilityClasses(ownerState) { + var classes = ownerState.classes; + var slots = { + root: ['root'] + }; + return composeClasses(slots, getTabPanelUtilityClass, classes); +}; +var TabPanelRoot = styles_styled('div', { + name: 'MuiTabPanel', + slot: 'Root', + overridesResolver: function overridesResolver(props, styles) { + return styles.root; + } +})(function (_ref) { + var theme = _ref.theme; + return { + padding: theme.spacing(3) + }; +}); +var TabPanel = /*#__PURE__*/react.forwardRef(function TabPanel(inProps, ref) { + var props = useThemeProps_useThemeProps({ + props: inProps, + name: 'MuiTabPanel' + }); + var children = props.children, + className = props.className, + value = props.value, + other = objectWithoutPropertiesLoose_objectWithoutPropertiesLoose(props, TabPanel_excluded); + var ownerState = extends_extends({}, props); + var classes = TabPanel_useUtilityClasses(ownerState); + var context = useTabContext(); + if (context === null) { + throw new TypeError('No TabContext provided'); + } + var id = getPanelId(context, value); + var tabId = getTabId(context, value); + return /*#__PURE__*/(0,jsx_runtime.jsx)(TabPanelRoot, extends_extends({ + "aria-labelledby": tabId, + className: clsx_m(classes.root, className), + hidden: value !== context.value, + id: id, + ref: ref, + role: "tabpanel", + ownerState: ownerState + }, other, { + children: value === context.value && children + })); +}); + false ? 0 : void 0; +/* harmony default export */ var TabPanel_TabPanel = (TabPanel); +;// CONCATENATED MODULE: ./src/components/result-view/SidePanel.tsx +function useSidePanelTabs(provider){var _useState=(0,react.useState)(),_useState2=slicedToArray_slicedToArray(_useState,2),selectedTab=_useState2[0],setSelectedTab=_useState2[1];var handleTabChange=(0,react.useCallback)(function(_e,value){setSelectedTab(value);},[]);var tabs=(0,react.useMemo)(function(){return(provider===null||provider===void 0?void 0:provider.buildSidePanelTabs())||[];},[provider]);(0,react.useEffect)(function(){if(!(tabs!==null&&tabs!==void 0&&tabs.length)){setSelectedTab("");return;}if(!selectedTab){setSelectedTab(tabs[0].label);return;}if(!tabs.find(function(x){return x.label===selectedTab;})){// reset it after the type of selected item has changed +setSelectedTab(tabs[0].label);}},[selectedTab,tabs]);return{tabs:tabs,selectedTab:selectedTab,handleTabChange:handleTabChange};}var SidePanel=function SidePanel(props){var theme=styles_useTheme_useTheme();var _useSidePanelTabs=useSidePanelTabs(props.provider),tabs=_useSidePanelTabs.tabs,selectedTab=_useSidePanelTabs.selectedTab,handleTabChange=_useSidePanelTabs.handleTabChange;if(!selectedTab||!tabs.find(function(x){return x.label===selectedTab;})){return/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{});}if(!props.provider){return/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{});}return/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{width:"100%",height:"100%",display:"flex",flexDirection:"column",overflow:"hidden",children:/*#__PURE__*/(0,jsx_runtime.jsxs)(TabContext,{value:selectedTab,children:[/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{minHeight:theme.consts.appBarHeight,display:"flex",flexDirection:"column",flex:"0 0 auto",justifyContent:"space-between",children:[/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{flex:"1 1 auto",display:"flex",justifyContent:"space-between",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{flex:"1 1 auto",pt:"25px",pl:"35px",children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"h4",children:props.provider.buildSidePanelTitle()})}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{flex:"0 0 auto",pt:"10px",pr:"10px",children:/*#__PURE__*/(0,jsx_runtime.jsx)(IconButton_IconButton,{onClick:props.onClose,children:/*#__PURE__*/(0,jsx_runtime.jsx)(CloseIcon,{})})})]}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{height:"36px",flex:"0 0 auto",p:"0 30px",children:/*#__PURE__*/(0,jsx_runtime.jsx)(TabList_TabList,{onChange:handleTabChange,children:tabs.map(function(tab,i){return/*#__PURE__*/(0,jsx_runtime.jsx)(Tab_Tab,{label:tab.label,value:tab.label},tab.label);})})})]}),/*#__PURE__*/(0,jsx_runtime.jsx)(Divider_Divider,{sx:{margin:0}}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{overflow:"auto",p:"30px",children:tabs.map(function(tab){return/*#__PURE__*/(0,jsx_runtime.jsx)(styles_ThemeProvider_ThemeProvider,{theme:theme_light,children:/*#__PURE__*/(0,jsx_runtime.jsx)(TabPanel_TabPanel,{value:tab.label,sx:{padding:0},children:/*#__PURE__*/(0,jsx_runtime.jsx)(Paper_Paper,{sx:{padding:'10px 0'},children:tab.content})})},tab.label);})})]})});}; +;// CONCATENATED MODULE: ./node_modules/@mui/material/Table/TableContext.js +/** + * @ignore - internal component. + */ +var TableContext = /*#__PURE__*/react.createContext(); +if (false) {} +/* harmony default export */ var Table_TableContext = (TableContext); +;// CONCATENATED MODULE: ./node_modules/@mui/material/Table/tableClasses.js +function getTableUtilityClass(slot) { + return generateUtilityClass_generateUtilityClass('MuiTable', slot); +} +var tableClasses = generateUtilityClasses('MuiTable', ['root', 'stickyHeader']); +/* harmony default export */ var Table_tableClasses = ((/* unused pure expression or super */ null && (tableClasses))); +;// CONCATENATED MODULE: ./node_modules/@mui/material/Table/Table.js +var Table_excluded = ["className", "component", "padding", "size", "stickyHeader"]; @@ -58412,691 +58615,458 @@ var Tabs_excluded = ["aria-label", "aria-labelledby", "action", "centered", "chi -var Tabs_nextItem = function nextItem(list, item) { - if (list === item) { - return list.firstChild; - } - if (item && item.nextElementSibling) { - return item.nextElementSibling; - } - return list.firstChild; +var Table_useUtilityClasses = function useUtilityClasses(ownerState) { + var classes = ownerState.classes, + stickyHeader = ownerState.stickyHeader; + var slots = { + root: ['root', stickyHeader && 'stickyHeader'] + }; + return composeClasses(slots, getTableUtilityClass, classes); }; -var Tabs_previousItem = function previousItem(list, item) { - if (list === item) { - return list.lastChild; - } - if (item && item.previousElementSibling) { - return item.previousElementSibling; - } - return list.lastChild; -}; -var Tabs_moveFocus = function moveFocus(list, currentFocus, traversalFunction) { - var wrappedOnce = false; - var nextFocus = traversalFunction(list, currentFocus); - while (nextFocus) { - // Prevent infinite loop. - if (nextFocus === list.firstChild) { - if (wrappedOnce) { - return; - } - wrappedOnce = true; - } - - // Same logic as useAutocomplete.js - var nextFocusDisabled = nextFocus.disabled || nextFocus.getAttribute('aria-disabled') === 'true'; - if (!nextFocus.hasAttribute('tabindex') || nextFocusDisabled) { - // Move to the next element. - nextFocus = traversalFunction(list, nextFocus); - } else { - nextFocus.focus(); - return; - } - } -}; -var Tabs_useUtilityClasses = function useUtilityClasses(ownerState) { - var vertical = ownerState.vertical, - fixed = ownerState.fixed, - hideScrollbar = ownerState.hideScrollbar, - scrollableX = ownerState.scrollableX, - scrollableY = ownerState.scrollableY, - centered = ownerState.centered, - scrollButtonsHideMobile = ownerState.scrollButtonsHideMobile, - classes = ownerState.classes; - var slots = { - root: ['root', vertical && 'vertical'], - scroller: ['scroller', fixed && 'fixed', hideScrollbar && 'hideScrollbar', scrollableX && 'scrollableX', scrollableY && 'scrollableY'], - flexContainer: ['flexContainer', vertical && 'flexContainerVertical', centered && 'centered'], - indicator: ['indicator'], - scrollButtons: ['scrollButtons', scrollButtonsHideMobile && 'scrollButtonsHideMobile'], - scrollableX: [scrollableX && 'scrollableX'], - hideScrollbar: [hideScrollbar && 'hideScrollbar'] - }; - return composeClasses(slots, getTabsUtilityClass, classes); -}; -var TabsRoot = styles_styled('div', { - name: 'MuiTabs', +var TableRoot = styles_styled('table', { + name: 'MuiTable', slot: 'Root', overridesResolver: function overridesResolver(props, styles) { var ownerState = props.ownerState; - return [defineProperty_defineProperty({}, "& .".concat(Tabs_tabsClasses.scrollButtons), styles.scrollButtons), defineProperty_defineProperty({}, "& .".concat(Tabs_tabsClasses.scrollButtons), ownerState.scrollButtonsHideMobile && styles.scrollButtonsHideMobile), styles.root, ownerState.vertical && styles.vertical]; - } -})(function (_ref3) { - var ownerState = _ref3.ownerState, - theme = _ref3.theme; - return extends_extends({ - overflow: 'hidden', - minHeight: 48, - // Add iOS momentum scrolling for iOS < 13.0 - WebkitOverflowScrolling: 'touch', - display: 'flex' - }, ownerState.vertical && { - flexDirection: 'column' - }, ownerState.scrollButtonsHideMobile && defineProperty_defineProperty({}, "& .".concat(Tabs_tabsClasses.scrollButtons), defineProperty_defineProperty({}, theme.breakpoints.down('sm'), { - display: 'none' - }))); -}); -var TabsScroller = styles_styled('div', { - name: 'MuiTabs', - slot: 'Scroller', - overridesResolver: function overridesResolver(props, styles) { - var ownerState = props.ownerState; - return [styles.scroller, ownerState.fixed && styles.fixed, ownerState.hideScrollbar && styles.hideScrollbar, ownerState.scrollableX && styles.scrollableX, ownerState.scrollableY && styles.scrollableY]; + return [styles.root, ownerState.stickyHeader && styles.stickyHeader]; } -})(function (_ref5) { - var ownerState = _ref5.ownerState; +})(function (_ref) { + var theme = _ref.theme, + ownerState = _ref.ownerState; return extends_extends({ - position: 'relative', - display: 'inline-block', - flex: '1 1 auto', - whiteSpace: 'nowrap' - }, ownerState.fixed && { - overflowX: 'hidden', - width: '100%' - }, ownerState.hideScrollbar && { - // Hide dimensionless scrollbar on macOS - scrollbarWidth: 'none', - // Firefox - '&::-webkit-scrollbar': { - display: 'none' // Safari + Chrome - } - }, ownerState.scrollableX && { - overflowX: 'auto', - overflowY: 'hidden' - }, ownerState.scrollableY && { - overflowY: 'auto', - overflowX: 'hidden' + display: 'table', + width: '100%', + borderCollapse: 'collapse', + borderSpacing: 0, + '& caption': extends_extends({}, theme.typography.body2, { + padding: theme.spacing(2), + color: (theme.vars || theme).palette.text.secondary, + textAlign: 'left', + captionSide: 'bottom' + }) + }, ownerState.stickyHeader && { + borderCollapse: 'separate' }); }); -var FlexContainer = styles_styled('div', { - name: 'MuiTabs', - slot: 'FlexContainer', - overridesResolver: function overridesResolver(props, styles) { - var ownerState = props.ownerState; - return [styles.flexContainer, ownerState.vertical && styles.flexContainerVertical, ownerState.centered && styles.centered]; - } -})(function (_ref6) { - var ownerState = _ref6.ownerState; - return extends_extends({ - display: 'flex' - }, ownerState.vertical && { - flexDirection: 'column' - }, ownerState.centered && { - justifyContent: 'center' +var defaultComponent = 'table'; +var Table = /*#__PURE__*/react.forwardRef(function Table(inProps, ref) { + var props = useThemeProps_useThemeProps({ + props: inProps, + name: 'MuiTable' + }); + var className = props.className, + _props$component = props.component, + component = _props$component === void 0 ? defaultComponent : _props$component, + _props$padding = props.padding, + padding = _props$padding === void 0 ? 'normal' : _props$padding, + _props$size = props.size, + size = _props$size === void 0 ? 'medium' : _props$size, + _props$stickyHeader = props.stickyHeader, + stickyHeader = _props$stickyHeader === void 0 ? false : _props$stickyHeader, + other = objectWithoutPropertiesLoose_objectWithoutPropertiesLoose(props, Table_excluded); + var ownerState = extends_extends({}, props, { + component: component, + padding: padding, + size: size, + stickyHeader: stickyHeader + }); + var classes = Table_useUtilityClasses(ownerState); + var table = react.useMemo(function () { + return { + padding: padding, + size: size, + stickyHeader: stickyHeader + }; + }, [padding, size, stickyHeader]); + return /*#__PURE__*/(0,jsx_runtime.jsx)(Table_TableContext.Provider, { + value: table, + children: /*#__PURE__*/(0,jsx_runtime.jsx)(TableRoot, extends_extends({ + as: component, + role: component === defaultComponent ? null : 'table', + ref: ref, + className: clsx_m(classes.root, className), + ownerState: ownerState + }, other)) }); }); -var TabsIndicator = styles_styled('span', { - name: 'MuiTabs', - slot: 'Indicator', + false ? 0 : void 0; +/* harmony default export */ var Table_Table = (Table); +;// CONCATENATED MODULE: ./node_modules/@mui/material/Table/Tablelvl2Context.js + + +/** + * @ignore - internal component. + */ +var Tablelvl2Context = /*#__PURE__*/react.createContext(); +if (false) {} +/* harmony default export */ var Table_Tablelvl2Context = (Tablelvl2Context); +;// CONCATENATED MODULE: ./node_modules/@mui/material/TableBody/tableBodyClasses.js + + +function getTableBodyUtilityClass(slot) { + return generateUtilityClass_generateUtilityClass('MuiTableBody', slot); +} +var tableBodyClasses = generateUtilityClasses('MuiTableBody', ['root']); +/* harmony default export */ var TableBody_tableBodyClasses = ((/* unused pure expression or super */ null && (tableBodyClasses))); +;// CONCATENATED MODULE: ./node_modules/@mui/material/TableBody/TableBody.js + + +var TableBody_excluded = ["className", "component"]; + + + + + + + + + +var TableBody_useUtilityClasses = function useUtilityClasses(ownerState) { + var classes = ownerState.classes; + var slots = { + root: ['root'] + }; + return composeClasses(slots, getTableBodyUtilityClass, classes); +}; +var TableBodyRoot = styles_styled('tbody', { + name: 'MuiTableBody', + slot: 'Root', overridesResolver: function overridesResolver(props, styles) { - return styles.indicator; + return styles.root; } -})(function (_ref7) { - var ownerState = _ref7.ownerState, - theme = _ref7.theme; - return extends_extends({ - position: 'absolute', - height: 2, - bottom: 0, - width: '100%', - transition: theme.transitions.create() - }, ownerState.indicatorColor === 'primary' && { - backgroundColor: (theme.vars || theme).palette.primary.main - }, ownerState.indicatorColor === 'secondary' && { - backgroundColor: (theme.vars || theme).palette.secondary.main - }, ownerState.vertical && { - height: '100%', - width: 2, - right: 0 - }); -}); -var TabsScrollbarSize = styles_styled(ScrollbarSize, { - name: 'MuiTabs', - slot: 'ScrollbarSize' })({ - overflowX: 'auto', - overflowY: 'hidden', - // Hide dimensionless scrollbar on macOS - scrollbarWidth: 'none', - // Firefox - '&::-webkit-scrollbar': { - display: 'none' // Safari + Chrome - } + display: 'table-row-group' }); - -var defaultIndicatorStyle = {}; -var warnedOnceTabPresent = false; -var Tabs = /*#__PURE__*/react.forwardRef(function Tabs(inProps, ref) { +var tablelvl2 = { + variant: 'body' +}; +var TableBody_defaultComponent = 'tbody'; +var TableBody = /*#__PURE__*/react.forwardRef(function TableBody(inProps, ref) { var props = useThemeProps_useThemeProps({ props: inProps, - name: 'MuiTabs' + name: 'MuiTableBody' }); - var theme = styles_useTheme_useTheme(); - var isRtl = theme.direction === 'rtl'; - var ariaLabel = props['aria-label'], - ariaLabelledBy = props['aria-labelledby'], - action = props.action, - _props$centered = props.centered, - centered = _props$centered === void 0 ? false : _props$centered, - childrenProp = props.children, - className = props.className, + var className = props.className, _props$component = props.component, - component = _props$component === void 0 ? 'div' : _props$component, - _props$allowScrollBut = props.allowScrollButtonsMobile, - allowScrollButtonsMobile = _props$allowScrollBut === void 0 ? false : _props$allowScrollBut, - _props$indicatorColor = props.indicatorColor, - indicatorColor = _props$indicatorColor === void 0 ? 'primary' : _props$indicatorColor, - onChange = props.onChange, - _props$orientation = props.orientation, - orientation = _props$orientation === void 0 ? 'horizontal' : _props$orientation, - _props$ScrollButtonCo = props.ScrollButtonComponent, - ScrollButtonComponent = _props$ScrollButtonCo === void 0 ? TabScrollButton_TabScrollButton : _props$ScrollButtonCo, - _props$scrollButtons = props.scrollButtons, - scrollButtons = _props$scrollButtons === void 0 ? 'auto' : _props$scrollButtons, - selectionFollowsFocus = props.selectionFollowsFocus, - _props$slots = props.slots, - slots = _props$slots === void 0 ? {} : _props$slots, - _props$slotProps = props.slotProps, - slotProps = _props$slotProps === void 0 ? {} : _props$slotProps, - _props$TabIndicatorPr = props.TabIndicatorProps, - TabIndicatorProps = _props$TabIndicatorPr === void 0 ? {} : _props$TabIndicatorPr, - _props$TabScrollButto = props.TabScrollButtonProps, - TabScrollButtonProps = _props$TabScrollButto === void 0 ? {} : _props$TabScrollButto, - _props$textColor = props.textColor, - textColor = _props$textColor === void 0 ? 'primary' : _props$textColor, - value = props.value, - _props$variant = props.variant, - variant = _props$variant === void 0 ? 'standard' : _props$variant, - _props$visibleScrollb = props.visibleScrollbar, - visibleScrollbar = _props$visibleScrollb === void 0 ? false : _props$visibleScrollb, - other = objectWithoutPropertiesLoose_objectWithoutPropertiesLoose(props, Tabs_excluded); - var scrollable = variant === 'scrollable'; - var vertical = orientation === 'vertical'; - var scrollStart = vertical ? 'scrollTop' : 'scrollLeft'; - var start = vertical ? 'top' : 'left'; - var end = vertical ? 'bottom' : 'right'; - var clientSize = vertical ? 'clientHeight' : 'clientWidth'; - var size = vertical ? 'height' : 'width'; + component = _props$component === void 0 ? TableBody_defaultComponent : _props$component, + other = objectWithoutPropertiesLoose_objectWithoutPropertiesLoose(props, TableBody_excluded); var ownerState = extends_extends({}, props, { - component: component, - allowScrollButtonsMobile: allowScrollButtonsMobile, - indicatorColor: indicatorColor, - orientation: orientation, - vertical: vertical, - scrollButtons: scrollButtons, - textColor: textColor, - variant: variant, - visibleScrollbar: visibleScrollbar, - fixed: !scrollable, - hideScrollbar: scrollable && !visibleScrollbar, - scrollableX: scrollable && !vertical, - scrollableY: scrollable && vertical, - centered: centered && !scrollable, - scrollButtonsHideMobile: !allowScrollButtonsMobile - }); - var classes = Tabs_useUtilityClasses(ownerState); - var startScrollButtonIconProps = useSlotProps({ - elementType: slots.StartScrollButtonIcon, - externalSlotProps: slotProps.startScrollButtonIcon, - ownerState: ownerState + component: component }); - var endScrollButtonIconProps = useSlotProps({ - elementType: slots.EndScrollButtonIcon, - externalSlotProps: slotProps.endScrollButtonIcon, - ownerState: ownerState + var classes = TableBody_useUtilityClasses(ownerState); + return /*#__PURE__*/(0,jsx_runtime.jsx)(Table_Tablelvl2Context.Provider, { + value: tablelvl2, + children: /*#__PURE__*/(0,jsx_runtime.jsx)(TableBodyRoot, extends_extends({ + className: clsx_m(classes.root, className), + as: component, + ref: ref, + role: component === TableBody_defaultComponent ? null : 'rowgroup', + ownerState: ownerState + }, other)) }); - if (false) {} - var _React$useState = react.useState(false), - _React$useState2 = slicedToArray_slicedToArray(_React$useState, 2), - mounted = _React$useState2[0], - setMounted = _React$useState2[1]; - var _React$useState3 = react.useState(defaultIndicatorStyle), - _React$useState4 = slicedToArray_slicedToArray(_React$useState3, 2), - indicatorStyle = _React$useState4[0], - setIndicatorStyle = _React$useState4[1]; - var _React$useState5 = react.useState({ - start: false, - end: false - }), - _React$useState6 = slicedToArray_slicedToArray(_React$useState5, 2), - displayScroll = _React$useState6[0], - setDisplayScroll = _React$useState6[1]; - var _React$useState7 = react.useState({ - overflow: 'hidden', - scrollbarWidth: 0 - }), - _React$useState8 = slicedToArray_slicedToArray(_React$useState7, 2), - scrollerStyle = _React$useState8[0], - setScrollerStyle = _React$useState8[1]; - var valueToIndex = new Map(); - var tabsRef = react.useRef(null); - var tabListRef = react.useRef(null); - var getTabsMeta = function getTabsMeta() { - var tabsNode = tabsRef.current; - var tabsMeta; - if (tabsNode) { - var rect = tabsNode.getBoundingClientRect(); - // create a new object with ClientRect class props + scrollLeft - tabsMeta = { - clientWidth: tabsNode.clientWidth, - scrollLeft: tabsNode.scrollLeft, - scrollTop: tabsNode.scrollTop, - scrollLeftNormalized: getNormalizedScrollLeft(tabsNode, theme.direction), - scrollWidth: tabsNode.scrollWidth, - top: rect.top, - bottom: rect.bottom, - left: rect.left, - right: rect.right - }; - } - var tabMeta; - if (tabsNode && value !== false) { - var _children = tabListRef.current.children; - if (_children.length > 0) { - var tab = _children[valueToIndex.get(value)]; - if (false) {} - tabMeta = tab ? tab.getBoundingClientRect() : null; - if (false) {} - } - } - return { - tabsMeta: tabsMeta, - tabMeta: tabMeta - }; +}); + false ? 0 : void 0; +/* harmony default export */ var TableBody_TableBody = (TableBody); +;// CONCATENATED MODULE: ./node_modules/@mui/material/TableCell/tableCellClasses.js + + +function getTableCellUtilityClass(slot) { + return generateUtilityClass_generateUtilityClass('MuiTableCell', slot); +} +var tableCellClasses = generateUtilityClasses('MuiTableCell', ['root', 'head', 'body', 'footer', 'sizeSmall', 'sizeMedium', 'paddingCheckbox', 'paddingNone', 'alignLeft', 'alignCenter', 'alignRight', 'alignJustify', 'stickyHeader']); +/* harmony default export */ var TableCell_tableCellClasses = (tableCellClasses); +;// CONCATENATED MODULE: ./node_modules/@mui/material/TableCell/TableCell.js + + + +var TableCell_excluded = ["align", "className", "component", "padding", "scope", "size", "sortDirection", "variant"]; + + + + + + + + + + + + +var TableCell_useUtilityClasses = function useUtilityClasses(ownerState) { + var classes = ownerState.classes, + variant = ownerState.variant, + align = ownerState.align, + padding = ownerState.padding, + size = ownerState.size, + stickyHeader = ownerState.stickyHeader; + var slots = { + root: ['root', variant, stickyHeader && 'stickyHeader', align !== 'inherit' && "align".concat(utils_capitalize(align)), padding !== 'normal' && "padding".concat(utils_capitalize(padding)), "size".concat(utils_capitalize(size))] }; - var updateIndicatorState = utils_useEventCallback(function () { - var _newIndicatorStyle; - var _getTabsMeta = getTabsMeta(), - tabsMeta = _getTabsMeta.tabsMeta, - tabMeta = _getTabsMeta.tabMeta; - var startValue = 0; - var startIndicator; - if (vertical) { - startIndicator = 'top'; - if (tabMeta && tabsMeta) { - startValue = tabMeta.top - tabsMeta.top + tabsMeta.scrollTop; - } - } else { - startIndicator = isRtl ? 'right' : 'left'; - if (tabMeta && tabsMeta) { - var correction = isRtl ? tabsMeta.scrollLeftNormalized + tabsMeta.clientWidth - tabsMeta.scrollWidth : tabsMeta.scrollLeft; - startValue = (isRtl ? -1 : 1) * (tabMeta[startIndicator] - tabsMeta[startIndicator] + correction); - } + return composeClasses(slots, getTableCellUtilityClass, classes); +}; +var TableCellRoot = styles_styled('td', { + name: 'MuiTableCell', + slot: 'Root', + overridesResolver: function overridesResolver(props, styles) { + var ownerState = props.ownerState; + return [styles.root, styles[ownerState.variant], styles["size".concat(utils_capitalize(ownerState.size))], ownerState.padding !== 'normal' && styles["padding".concat(utils_capitalize(ownerState.padding))], ownerState.align !== 'inherit' && styles["align".concat(utils_capitalize(ownerState.align))], ownerState.stickyHeader && styles.stickyHeader]; + } +})(function (_ref) { + var theme = _ref.theme, + ownerState = _ref.ownerState; + return extends_extends({}, theme.typography.body2, { + display: 'table-cell', + verticalAlign: 'inherit', + // Workaround for a rendering bug with spanned columns in Chrome 62.0. + // Removes the alpha (sets it to 1), and lightens or darkens the theme color. + borderBottom: theme.vars ? "1px solid ".concat(theme.vars.palette.TableCell.border) : "1px solid\n ".concat(theme.palette.mode === 'light' ? lighten(alpha(theme.palette.divider, 1), 0.88) : darken(alpha(theme.palette.divider, 1), 0.68)), + textAlign: 'left', + padding: 16 + }, ownerState.variant === 'head' && { + color: (theme.vars || theme).palette.text.primary, + lineHeight: theme.typography.pxToRem(24), + fontWeight: theme.typography.fontWeightMedium + }, ownerState.variant === 'body' && { + color: (theme.vars || theme).palette.text.primary + }, ownerState.variant === 'footer' && { + color: (theme.vars || theme).palette.text.secondary, + lineHeight: theme.typography.pxToRem(21), + fontSize: theme.typography.pxToRem(12) + }, ownerState.size === 'small' && defineProperty_defineProperty({ + padding: '6px 16px' + }, "&.".concat(TableCell_tableCellClasses.paddingCheckbox), { + width: 24, + // prevent the checkbox column from growing + padding: '0 12px 0 16px', + '& > *': { + padding: 0 } - var newIndicatorStyle = (_newIndicatorStyle = {}, defineProperty_defineProperty(_newIndicatorStyle, startIndicator, startValue), defineProperty_defineProperty(_newIndicatorStyle, size, tabMeta ? tabMeta[size] : 0), _newIndicatorStyle); + }), ownerState.padding === 'checkbox' && { + width: 48, + // prevent the checkbox column from growing + padding: '0 0 0 4px' + }, ownerState.padding === 'none' && { + padding: 0 + }, ownerState.align === 'left' && { + textAlign: 'left' + }, ownerState.align === 'center' && { + textAlign: 'center' + }, ownerState.align === 'right' && { + textAlign: 'right', + flexDirection: 'row-reverse' + }, ownerState.align === 'justify' && { + textAlign: 'justify' + }, ownerState.stickyHeader && { + position: 'sticky', + top: 0, + zIndex: 2, + backgroundColor: (theme.vars || theme).palette.background.default + }); +}); - // IE11 support, replace with Number.isNaN - // eslint-disable-next-line no-restricted-globals - if (isNaN(indicatorStyle[startIndicator]) || isNaN(indicatorStyle[size])) { - setIndicatorStyle(newIndicatorStyle); - } else { - var dStart = Math.abs(indicatorStyle[startIndicator] - newIndicatorStyle[startIndicator]); - var dSize = Math.abs(indicatorStyle[size] - newIndicatorStyle[size]); - if (dStart >= 1 || dSize >= 1) { - setIndicatorStyle(newIndicatorStyle); - } - } +/** + * The component renders a `` element when the parent context is a header + * or otherwise a `` element. + */ +var TableCell = /*#__PURE__*/react.forwardRef(function TableCell(inProps, ref) { + var props = useThemeProps_useThemeProps({ + props: inProps, + name: 'MuiTableCell' }); - var scroll = function scroll(scrollValue) { - var _ref8 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, - _ref8$animation = _ref8.animation, - animation = _ref8$animation === void 0 ? true : _ref8$animation; - if (animation) { - animate(scrollStart, tabsRef.current, scrollValue, { - duration: theme.transitions.duration.standard - }); - } else { - tabsRef.current[scrollStart] = scrollValue; - } - }; - var moveTabsScroll = function moveTabsScroll(delta) { - var scrollValue = tabsRef.current[scrollStart]; - if (vertical) { - scrollValue += delta; - } else { - scrollValue += delta * (isRtl ? -1 : 1); - // Fix for Edge - scrollValue *= isRtl && detectScrollType() === 'reverse' ? -1 : 1; - } - scroll(scrollValue); - }; - var getScrollSize = function getScrollSize() { - var containerSize = tabsRef.current[clientSize]; - var totalSize = 0; - var children = Array.from(tabListRef.current.children); - for (var i = 0; i < children.length; i += 1) { - var tab = children[i]; - if (totalSize + tab[clientSize] > containerSize) { - // If the first item is longer than the container size, then only scroll - // by the container size. - if (i === 0) { - totalSize = containerSize; - } - break; - } - totalSize += tab[clientSize]; - } - return totalSize; - }; - var handleStartScrollClick = function handleStartScrollClick() { - moveTabsScroll(-1 * getScrollSize()); - }; - var handleEndScrollClick = function handleEndScrollClick() { - moveTabsScroll(getScrollSize()); - }; + var _props$align = props.align, + align = _props$align === void 0 ? 'inherit' : _props$align, + className = props.className, + componentProp = props.component, + paddingProp = props.padding, + scopeProp = props.scope, + sizeProp = props.size, + sortDirection = props.sortDirection, + variantProp = props.variant, + other = objectWithoutPropertiesLoose_objectWithoutPropertiesLoose(props, TableCell_excluded); + var table = react.useContext(Table_TableContext); + var tablelvl2 = react.useContext(Table_Tablelvl2Context); + var isHeadCell = tablelvl2 && tablelvl2.variant === 'head'; + var component; + if (componentProp) { + component = componentProp; + } else { + component = isHeadCell ? 'th' : 'td'; + } + var scope = scopeProp; + // scope is not a valid attribute for elements. + // source: https://html.spec.whatwg.org/multipage/tables.html#the-td-element + if (component === 'td') { + scope = undefined; + } else if (!scope && isHeadCell) { + scope = 'col'; + } + var variant = variantProp || tablelvl2 && tablelvl2.variant; + var ownerState = extends_extends({}, props, { + align: align, + component: component, + padding: paddingProp || (table && table.padding ? table.padding : 'normal'), + size: sizeProp || (table && table.size ? table.size : 'medium'), + sortDirection: sortDirection, + stickyHeader: variant === 'head' && table && table.stickyHeader, + variant: variant + }); + var classes = TableCell_useUtilityClasses(ownerState); + var ariaSort = null; + if (sortDirection) { + ariaSort = sortDirection === 'asc' ? 'ascending' : 'descending'; + } + return /*#__PURE__*/(0,jsx_runtime.jsx)(TableCellRoot, extends_extends({ + as: component, + ref: ref, + className: clsx_m(classes.root, className), + "aria-sort": ariaSort, + scope: scope, + ownerState: ownerState + }, other)); +}); + false ? 0 : void 0; +/* harmony default export */ var TableCell_TableCell = (TableCell); +;// CONCATENATED MODULE: ./node_modules/@mui/material/TableContainer/tableContainerClasses.js - // TODO Remove as browser support for hiding the scrollbar - // with CSS improves. - var handleScrollbarSizeChange = react.useCallback(function (scrollbarWidth) { - setScrollerStyle({ - overflow: null, - scrollbarWidth: scrollbarWidth - }); - }, []); - var getConditionalElements = function getConditionalElements() { - var conditionalElements = {}; - conditionalElements.scrollbarSizeListener = scrollable ? /*#__PURE__*/(0,jsx_runtime.jsx)(TabsScrollbarSize, { - onChange: handleScrollbarSizeChange, - className: clsx_m(classes.scrollableX, classes.hideScrollbar) - }) : null; - var scrollButtonsActive = displayScroll.start || displayScroll.end; - var showScrollButtons = scrollable && (scrollButtons === 'auto' && scrollButtonsActive || scrollButtons === true); - conditionalElements.scrollButtonStart = showScrollButtons ? /*#__PURE__*/(0,jsx_runtime.jsx)(ScrollButtonComponent, extends_extends({ - slots: { - StartScrollButtonIcon: slots.StartScrollButtonIcon - }, - slotProps: { - startScrollButtonIcon: startScrollButtonIconProps - }, - orientation: orientation, - direction: isRtl ? 'right' : 'left', - onClick: handleStartScrollClick, - disabled: !displayScroll.start - }, TabScrollButtonProps, { - className: clsx_m(classes.scrollButtons, TabScrollButtonProps.className) - })) : null; - conditionalElements.scrollButtonEnd = showScrollButtons ? /*#__PURE__*/(0,jsx_runtime.jsx)(ScrollButtonComponent, extends_extends({ - slots: { - EndScrollButtonIcon: slots.EndScrollButtonIcon - }, - slotProps: { - endScrollButtonIcon: endScrollButtonIconProps - }, - orientation: orientation, - direction: isRtl ? 'left' : 'right', - onClick: handleEndScrollClick, - disabled: !displayScroll.end - }, TabScrollButtonProps, { - className: clsx_m(classes.scrollButtons, TabScrollButtonProps.className) - })) : null; - return conditionalElements; + +function getTableContainerUtilityClass(slot) { + return generateUtilityClass_generateUtilityClass('MuiTableContainer', slot); +} +var tableContainerClasses = generateUtilityClasses('MuiTableContainer', ['root']); +/* harmony default export */ var TableContainer_tableContainerClasses = ((/* unused pure expression or super */ null && (tableContainerClasses))); +;// CONCATENATED MODULE: ./node_modules/@mui/material/TableContainer/TableContainer.js + + +var TableContainer_excluded = ["className", "component"]; + + + + + + + + +var TableContainer_useUtilityClasses = function useUtilityClasses(ownerState) { + var classes = ownerState.classes; + var slots = { + root: ['root'] }; - var scrollSelectedIntoView = utils_useEventCallback(function (animation) { - var _getTabsMeta2 = getTabsMeta(), - tabsMeta = _getTabsMeta2.tabsMeta, - tabMeta = _getTabsMeta2.tabMeta; - if (!tabMeta || !tabsMeta) { - return; - } - if (tabMeta[start] < tabsMeta[start]) { - // left side of button is out of view - var nextScrollStart = tabsMeta[scrollStart] + (tabMeta[start] - tabsMeta[start]); - scroll(nextScrollStart, { - animation: animation - }); - } else if (tabMeta[end] > tabsMeta[end]) { - // right side of button is out of view - var _nextScrollStart = tabsMeta[scrollStart] + (tabMeta[end] - tabsMeta[end]); - scroll(_nextScrollStart, { - animation: animation - }); - } + return composeClasses(slots, getTableContainerUtilityClass, classes); +}; +var TableContainerRoot = styles_styled('div', { + name: 'MuiTableContainer', + slot: 'Root', + overridesResolver: function overridesResolver(props, styles) { + return styles.root; + } +})({ + width: '100%', + overflowX: 'auto' +}); +var TableContainer = /*#__PURE__*/react.forwardRef(function TableContainer(inProps, ref) { + var props = useThemeProps_useThemeProps({ + props: inProps, + name: 'MuiTableContainer' }); - var updateScrollButtonState = utils_useEventCallback(function () { - if (scrollable && scrollButtons !== false) { - var _tabsRef$current = tabsRef.current, - scrollTop = _tabsRef$current.scrollTop, - scrollHeight = _tabsRef$current.scrollHeight, - clientHeight = _tabsRef$current.clientHeight, - scrollWidth = _tabsRef$current.scrollWidth, - clientWidth = _tabsRef$current.clientWidth; - var showStartScroll; - var showEndScroll; - if (vertical) { - showStartScroll = scrollTop > 1; - showEndScroll = scrollTop < scrollHeight - clientHeight - 1; - } else { - var scrollLeft = getNormalizedScrollLeft(tabsRef.current, theme.direction); - // use 1 for the potential rounding error with browser zooms. - showStartScroll = isRtl ? scrollLeft < scrollWidth - clientWidth - 1 : scrollLeft > 1; - showEndScroll = !isRtl ? scrollLeft < scrollWidth - clientWidth - 1 : scrollLeft > 1; - } - if (showStartScroll !== displayScroll.start || showEndScroll !== displayScroll.end) { - setDisplayScroll({ - start: showStartScroll, - end: showEndScroll - }); - } - } + var className = props.className, + _props$component = props.component, + component = _props$component === void 0 ? 'div' : _props$component, + other = objectWithoutPropertiesLoose_objectWithoutPropertiesLoose(props, TableContainer_excluded); + var ownerState = extends_extends({}, props, { + component: component }); - react.useEffect(function () { - var handleResize = utils_debounce(function () { - // If the Tabs component is replaced by Suspense with a fallback, the last - // ResizeObserver's handler that runs because of the change in the layout is trying to - // access a dom node that is no longer there (as the fallback component is being shown instead). - // See https://github.com/mui/material-ui/issues/33276 - // TODO: Add tests that will ensure the component is not failing when - // replaced by Suspense with a fallback, once React is updated to version 18 - if (tabsRef.current) { - updateIndicatorState(); - updateScrollButtonState(); - } - }); - var win = utils_ownerWindow(tabsRef.current); - win.addEventListener('resize', handleResize); - var resizeObserver; - if (typeof ResizeObserver !== 'undefined') { - resizeObserver = new ResizeObserver(handleResize); - Array.from(tabListRef.current.children).forEach(function (child) { - resizeObserver.observe(child); - }); - } - return function () { - handleResize.clear(); - win.removeEventListener('resize', handleResize); - if (resizeObserver) { - resizeObserver.disconnect(); - } - }; - }, [updateIndicatorState, updateScrollButtonState]); - var handleTabsScroll = react.useMemo(function () { - return utils_debounce(function () { - updateScrollButtonState(); - }); - }, [updateScrollButtonState]); - react.useEffect(function () { - return function () { - handleTabsScroll.clear(); - }; - }, [handleTabsScroll]); - react.useEffect(function () { - setMounted(true); - }, []); - react.useEffect(function () { - updateIndicatorState(); - updateScrollButtonState(); + var classes = TableContainer_useUtilityClasses(ownerState); + return /*#__PURE__*/(0,jsx_runtime.jsx)(TableContainerRoot, extends_extends({ + ref: ref, + as: component, + className: clsx_m(classes.root, className), + ownerState: ownerState + }, other)); +}); + false ? 0 : void 0; +/* harmony default export */ var TableContainer_TableContainer = (TableContainer); +;// CONCATENATED MODULE: ./node_modules/@mui/material/TableHead/tableHeadClasses.js + + +function getTableHeadUtilityClass(slot) { + return generateUtilityClass_generateUtilityClass('MuiTableHead', slot); +} +var tableHeadClasses = generateUtilityClasses('MuiTableHead', ['root']); +/* harmony default export */ var TableHead_tableHeadClasses = ((/* unused pure expression or super */ null && (tableHeadClasses))); +;// CONCATENATED MODULE: ./node_modules/@mui/material/TableHead/TableHead.js + + +var TableHead_excluded = ["className", "component"]; + + + + + + + + + +var TableHead_useUtilityClasses = function useUtilityClasses(ownerState) { + var classes = ownerState.classes; + var slots = { + root: ['root'] + }; + return composeClasses(slots, getTableHeadUtilityClass, classes); +}; +var TableHeadRoot = styles_styled('thead', { + name: 'MuiTableHead', + slot: 'Root', + overridesResolver: function overridesResolver(props, styles) { + return styles.root; + } +})({ + display: 'table-header-group' +}); +var TableHead_tablelvl2 = { + variant: 'head' +}; +var TableHead_defaultComponent = 'thead'; +var TableHead = /*#__PURE__*/react.forwardRef(function TableHead(inProps, ref) { + var props = useThemeProps_useThemeProps({ + props: inProps, + name: 'MuiTableHead' }); - react.useEffect(function () { - // Don't animate on the first render. - scrollSelectedIntoView(defaultIndicatorStyle !== indicatorStyle); - }, [scrollSelectedIntoView, indicatorStyle]); - react.useImperativeHandle(action, function () { - return { - updateIndicator: updateIndicatorState, - updateScrollButtons: updateScrollButtonState - }; - }, [updateIndicatorState, updateScrollButtonState]); - var indicator = /*#__PURE__*/(0,jsx_runtime.jsx)(TabsIndicator, extends_extends({}, TabIndicatorProps, { - className: clsx_m(classes.indicator, TabIndicatorProps.className), - ownerState: ownerState, - style: extends_extends({}, indicatorStyle, TabIndicatorProps.style) - })); - var childIndex = 0; - var children = react.Children.map(childrenProp, function (child) { - if (! /*#__PURE__*/react.isValidElement(child)) { - return null; - } - if (false) {} - var childValue = child.props.value === undefined ? childIndex : child.props.value; - valueToIndex.set(childValue, childIndex); - var selected = childValue === value; - childIndex += 1; - return /*#__PURE__*/react.cloneElement(child, extends_extends({ - fullWidth: variant === 'fullWidth', - indicator: selected && !mounted && indicator, - selected: selected, - selectionFollowsFocus: selectionFollowsFocus, - onChange: onChange, - textColor: textColor, - value: childValue - }, childIndex === 1 && value === false && !child.props.tabIndex ? { - tabIndex: 0 - } : {})); + var className = props.className, + _props$component = props.component, + component = _props$component === void 0 ? TableHead_defaultComponent : _props$component, + other = objectWithoutPropertiesLoose_objectWithoutPropertiesLoose(props, TableHead_excluded); + var ownerState = extends_extends({}, props, { + component: component + }); + var classes = TableHead_useUtilityClasses(ownerState); + return /*#__PURE__*/(0,jsx_runtime.jsx)(Table_Tablelvl2Context.Provider, { + value: TableHead_tablelvl2, + children: /*#__PURE__*/(0,jsx_runtime.jsx)(TableHeadRoot, extends_extends({ + as: component, + className: clsx_m(classes.root, className), + ref: ref, + role: component === TableHead_defaultComponent ? null : 'rowgroup', + ownerState: ownerState + }, other)) }); - var handleKeyDown = function handleKeyDown(event) { - var list = tabListRef.current; - var currentFocus = utils_ownerDocument(list).activeElement; - // Keyboard navigation assumes that [role="tab"] are siblings - // though we might warn in the future about nested, interactive elements - // as a a11y violation - var role = currentFocus.getAttribute('role'); - if (role !== 'tab') { - return; - } - var previousItemKey = orientation === 'horizontal' ? 'ArrowLeft' : 'ArrowUp'; - var nextItemKey = orientation === 'horizontal' ? 'ArrowRight' : 'ArrowDown'; - if (orientation === 'horizontal' && isRtl) { - // swap previousItemKey with nextItemKey - previousItemKey = 'ArrowRight'; - nextItemKey = 'ArrowLeft'; - } - switch (event.key) { - case previousItemKey: - event.preventDefault(); - Tabs_moveFocus(list, currentFocus, Tabs_previousItem); - break; - case nextItemKey: - event.preventDefault(); - Tabs_moveFocus(list, currentFocus, Tabs_nextItem); - break; - case 'Home': - event.preventDefault(); - Tabs_moveFocus(list, null, Tabs_nextItem); - break; - case 'End': - event.preventDefault(); - Tabs_moveFocus(list, null, Tabs_previousItem); - break; - default: - break; - } - }; - var conditionalElements = getConditionalElements(); - return /*#__PURE__*/(0,jsx_runtime.jsxs)(TabsRoot, extends_extends({ - className: clsx_m(classes.root, className), - ownerState: ownerState, - ref: ref, - as: component - }, other, { - children: [conditionalElements.scrollButtonStart, conditionalElements.scrollbarSizeListener, /*#__PURE__*/(0,jsx_runtime.jsxs)(TabsScroller, { - className: classes.scroller, - ownerState: ownerState, - style: defineProperty_defineProperty({ - overflow: scrollerStyle.overflow - }, vertical ? "margin".concat(isRtl ? 'Left' : 'Right') : 'marginBottom', visibleScrollbar ? undefined : -scrollerStyle.scrollbarWidth), - ref: tabsRef, - onScroll: handleTabsScroll, - children: [/*#__PURE__*/(0,jsx_runtime.jsx)(FlexContainer, { - "aria-label": ariaLabel, - "aria-labelledby": ariaLabelledBy, - "aria-orientation": orientation === 'vertical' ? 'vertical' : null, - className: classes.flexContainer, - ownerState: ownerState, - onKeyDown: handleKeyDown, - ref: tabListRef, - role: "tablist", - children: children - }), mounted && indicator] - }), conditionalElements.scrollButtonEnd] - })); }); false ? 0 : void 0; -/* harmony default export */ var Tabs_Tabs = (Tabs); -;// CONCATENATED MODULE: ./node_modules/@mui/lab/TabList/TabList.js - - -var TabList_excluded = ["children"]; - - +/* harmony default export */ var TableHead_TableHead = (TableHead); +;// CONCATENATED MODULE: ./node_modules/@mui/material/TableRow/tableRowClasses.js +function getTableRowUtilityClass(slot) { + return generateUtilityClass_generateUtilityClass('MuiTableRow', slot); +} +var tableRowClasses = generateUtilityClasses('MuiTableRow', ['root', 'selected', 'hover', 'head', 'footer']); +/* harmony default export */ var TableRow_tableRowClasses = (tableRowClasses); +;// CONCATENATED MODULE: ./node_modules/@mui/material/TableRow/TableRow.js -var TabList = /*#__PURE__*/react.forwardRef(function TabList(props, ref) { - var childrenProp = props.children, - other = objectWithoutPropertiesLoose_objectWithoutPropertiesLoose(props, TabList_excluded); - var context = useTabContext(); - if (context === null) { - throw new TypeError('No TabContext provided'); - } - var children = react.Children.map(childrenProp, function (child) { - if (! /*#__PURE__*/react.isValidElement(child)) { - return null; - } - return /*#__PURE__*/react.cloneElement(child, { - // SOMEDAY: `Tabs` will set those themselves - 'aria-controls': getPanelId(context, child.props.value), - id: getTabId(context, child.props.value) - }); - }); - return /*#__PURE__*/(0,jsx_runtime.jsx)(Tabs_Tabs, extends_extends({}, other, { - ref: ref, - value: context.value, - children: children - })); -}); - false ? 0 : void 0; -/* harmony default export */ var TabList_TabList = (TabList); -;// CONCATENATED MODULE: ./node_modules/@mui/lab/TabPanel/tabPanelClasses.js -function getTabPanelUtilityClass(slot) { - return generateUtilityClass_generateUtilityClass('MuiTabPanel', slot); -} -var tabPanelClasses = generateUtilityClasses('MuiTabPanel', ['root']); -/* harmony default export */ var TabPanel_tabPanelClasses = ((/* unused pure expression or super */ null && (tabPanelClasses))); -;// CONCATENATED MODULE: ./node_modules/@mui/lab/TabPanel/TabPanel.js +var TableRow_excluded = ["className", "component", "hover", "selected"]; -var TabPanel_excluded = ["children", "className", "value"]; @@ -59105,72 +59075,196 @@ var TabPanel_excluded = ["children", "className", "value"]; -var TabPanel_useUtilityClasses = function useUtilityClasses(ownerState) { - var classes = ownerState.classes; +var TableRow_useUtilityClasses = function useUtilityClasses(ownerState) { + var classes = ownerState.classes, + selected = ownerState.selected, + hover = ownerState.hover, + head = ownerState.head, + footer = ownerState.footer; var slots = { - root: ['root'] + root: ['root', selected && 'selected', hover && 'hover', head && 'head', footer && 'footer'] }; - return composeClasses(slots, getTabPanelUtilityClass, classes); + return composeClasses(slots, getTableRowUtilityClass, classes); }; -var TabPanelRoot = styles_styled('div', { - name: 'MuiTabPanel', +var TableRowRoot = styles_styled('tr', { + name: 'MuiTableRow', slot: 'Root', overridesResolver: function overridesResolver(props, styles) { - return styles.root; + var ownerState = props.ownerState; + return [styles.root, ownerState.head && styles.head, ownerState.footer && styles.footer]; } })(function (_ref) { + var _ref2; var theme = _ref.theme; - return { - padding: theme.spacing(3) - }; + return _ref2 = { + color: 'inherit', + display: 'table-row', + verticalAlign: 'middle', + // We disable the focus ring for mouse, touch and keyboard users. + outline: 0 + }, defineProperty_defineProperty(_ref2, "&.".concat(TableRow_tableRowClasses.hover, ":hover"), { + backgroundColor: (theme.vars || theme).palette.action.hover + }), defineProperty_defineProperty(_ref2, "&.".concat(TableRow_tableRowClasses.selected), { + backgroundColor: theme.vars ? "rgba(".concat(theme.vars.palette.primary.mainChannel, " / ").concat(theme.vars.palette.action.selectedOpacity, ")") : alpha(theme.palette.primary.main, theme.palette.action.selectedOpacity), + '&:hover': { + backgroundColor: theme.vars ? "rgba(".concat(theme.vars.palette.primary.mainChannel, " / calc(").concat(theme.vars.palette.action.selectedOpacity, " + ").concat(theme.vars.palette.action.hoverOpacity, "))") : alpha(theme.palette.primary.main, theme.palette.action.selectedOpacity + theme.palette.action.hoverOpacity) + } + }), _ref2; }); -var TabPanel = /*#__PURE__*/react.forwardRef(function TabPanel(inProps, ref) { +var TableRow_defaultComponent = 'tr'; +/** + * Will automatically set dynamic row height + * based on the material table element parent (head, body, etc). + */ +var TableRow = /*#__PURE__*/react.forwardRef(function TableRow(inProps, ref) { var props = useThemeProps_useThemeProps({ props: inProps, - name: 'MuiTabPanel' + name: 'MuiTableRow' }); - var children = props.children, - className = props.className, - value = props.value, - other = objectWithoutPropertiesLoose_objectWithoutPropertiesLoose(props, TabPanel_excluded); - var ownerState = extends_extends({}, props); - var classes = TabPanel_useUtilityClasses(ownerState); - var context = useTabContext(); - if (context === null) { - throw new TypeError('No TabContext provided'); - } - var id = getPanelId(context, value); - var tabId = getTabId(context, value); - return /*#__PURE__*/(0,jsx_runtime.jsx)(TabPanelRoot, extends_extends({ - "aria-labelledby": tabId, - className: clsx_m(classes.root, className), - hidden: value !== context.value, - id: id, + var className = props.className, + _props$component = props.component, + component = _props$component === void 0 ? TableRow_defaultComponent : _props$component, + _props$hover = props.hover, + hover = _props$hover === void 0 ? false : _props$hover, + _props$selected = props.selected, + selected = _props$selected === void 0 ? false : _props$selected, + other = objectWithoutPropertiesLoose_objectWithoutPropertiesLoose(props, TableRow_excluded); + var tablelvl2 = react.useContext(Table_Tablelvl2Context); + var ownerState = extends_extends({}, props, { + component: component, + hover: hover, + selected: selected, + head: tablelvl2 && tablelvl2.variant === 'head', + footer: tablelvl2 && tablelvl2.variant === 'footer' + }); + var classes = TableRow_useUtilityClasses(ownerState); + return /*#__PURE__*/(0,jsx_runtime.jsx)(TableRowRoot, extends_extends({ + as: component, ref: ref, - role: "tabpanel", + className: clsx_m(classes.root, className), + role: component === TableRow_defaultComponent ? null : 'row', ownerState: ownerState - }, other, { - children: value === context.value && children - })); + }, other)); }); false ? 0 : void 0; -/* harmony default export */ var TabPanel_TabPanel = (TabPanel); -;// CONCATENATED MODULE: ./src/components/result-view/SidePanel.tsx -var SidePanel=function SidePanel(props){var _props$provider;var _useState=(0,react.useState)(),_useState2=slicedToArray_slicedToArray(_useState,2),selectedTab=_useState2[0],setSelectedTab=_useState2[1];var theme=styles_useTheme_useTheme();function handleTabChange(_e,value){setSelectedTab(value);}var tabs=(_props$provider=props.provider)===null||_props$provider===void 0?void 0:_props$provider.buildSidePanelTabs();if(!tabs){tabs=[];}(0,react.useEffect)(function(){var _tabs;if(!((_tabs=tabs)!==null&&_tabs!==void 0&&_tabs.length)){setSelectedTab("");return;}if(!selectedTab){setSelectedTab(tabs[0].label);return;}if(!tabs.find(function(x){return x.label===selectedTab;})){// reset it after the type of selected item has changed -setSelectedTab(tabs[0].label);}// ignore that it wants us to add selectedTab to the deps (it would cause and endless loop) -// eslint-disable-next-line -},[props.provider]);if(!selectedTab||!tabs.find(function(x){return x.label===selectedTab;})){return/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{});}if(!props.provider){return/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{});}return/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{width:"100%",height:"100%",display:"flex",flexDirection:"column",overflow:"hidden",children:/*#__PURE__*/(0,jsx_runtime.jsxs)(TabContext,{value:selectedTab,children:[/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{minHeight:theme.consts.appBarHeight,display:"flex",flexDirection:"column",flex:"0 0 auto",justifyContent:"space-between",children:[/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{flex:"1 1 auto",display:"flex",justifyContent:"space-between",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{flex:"1 1 auto",pt:"25px",pl:"35px",children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"h4",children:props.provider.buildSidePanelTitle()})}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{flex:"0 0 auto",pt:"10px",pr:"10px",children:/*#__PURE__*/(0,jsx_runtime.jsx)(IconButton_IconButton,{onClick:props.onClose,children:/*#__PURE__*/(0,jsx_runtime.jsx)(CloseIcon,{})})})]}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{height:"36px",flex:"0 0 auto",p:"0 30px",children:/*#__PURE__*/(0,jsx_runtime.jsx)(TabList_TabList,{onChange:handleTabChange,children:tabs.map(function(tab,i){return/*#__PURE__*/(0,jsx_runtime.jsx)(Tab_Tab,{label:tab.label,value:tab.label},tab.label);})})})]}),/*#__PURE__*/(0,jsx_runtime.jsx)(Divider_Divider,{sx:{margin:0}}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{overflow:"auto",p:"30px",children:tabs.map(function(tab){return/*#__PURE__*/(0,jsx_runtime.jsx)(styles_ThemeProvider_ThemeProvider,{theme:theme_light,children:/*#__PURE__*/(0,jsx_runtime.jsx)(TabPanel_TabPanel,{value:tab.label,sx:{padding:0},children:/*#__PURE__*/(0,jsx_runtime.jsx)(Paper_Paper,{sx:{padding:'10px 0'},children:tab.content})})},tab.label);})})]})});}; -;// CONCATENATED MODULE: ./src/components/targets-view/Card.tsx -var Card_excluded=["children"],Card_excluded2=["children"],Card_excluded3=["children"];var cardWidth=247;var projectCardHeight=80;var cardHeight=126;var cardGap=20;function Card(_ref){var children=_ref.children,rest=objectWithoutProperties_objectWithoutProperties(_ref,Card_excluded);return/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,_objectSpread2(_objectSpread2({display:"flex",flexShrink:0,width:cardWidth,height:cardHeight},rest),{},{children:children}));}function CardCol(_ref2){var children=_ref2.children,rest=objectWithoutProperties_objectWithoutProperties(_ref2,Card_excluded2);return/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,_objectSpread2(_objectSpread2({display:"flex",flexDirection:"column",gap:"".concat(cardGap,"px")},rest),{},{children:children}));}function CardRow(_ref3){var children=_ref3.children,rest=objectWithoutProperties_objectWithoutProperties(_ref3,Card_excluded3);return/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,_objectSpread2(_objectSpread2({display:"flex",gap:"".concat(cardGap,"px")},rest),{},{children:children}));} -;// CONCATENATED MODULE: ./src/components/targets-view/CommandResultDetailsDrawer.tsx -var sidePanelWidth=720;function doGetRootNode(_x,_x2){return _doGetRootNode.apply(this,arguments);}function _doGetRootNode(){_doGetRootNode=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee(api,rs){var shortNames,r,builder,_builder$buildRoot,_builder$buildRoot2,node;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee$(_context){while(1)switch(_context.prev=_context.next){case 0:_context.next=2;return api.getShortNames();case 2:shortNames=_context.sent;_context.next=5;return api.getResult(rs.id);case 5:r=_context.sent;builder=new NodeBuilder({shortNames:shortNames,summary:rs,commandResult:r});_builder$buildRoot=builder.buildRoot(),_builder$buildRoot2=slicedToArray_slicedToArray(_builder$buildRoot,1),node=_builder$buildRoot2[0];return _context.abrupt("return",node);case 9:case"end":return _context.stop();}},_callee);}));return _doGetRootNode.apply(this,arguments);}var CommandResultDetailsDrawer=/*#__PURE__*/react.memo(function(props){var ps=props.ps,ts=props.ts,selectedCardRect=props.selectedCardRect;var api=(0,react.useContext)(ApiContext);var theme=styles_useTheme_useTheme();var _useState=(0,react.useState)(),_useState2=slicedToArray_slicedToArray(_useState,2),selectedCommandResult=_useState2[0],setSelectedCommandResult=_useState2[1];var _useState3=(0,react.useState)(ts),_useState4=slicedToArray_slicedToArray(_useState3,2),prevTargetSummary=_useState4[0],setPrevTargetSummary=_useState4[1];var _useState5=(0,react.useState)([]),_useState6=slicedToArray_slicedToArray(_useState5,2),cardsCoords=_useState6[0],setCardsCoords=_useState6[1];var _useState7=(0,react.useState)(false),_useState8=slicedToArray_slicedToArray(_useState7,2),transitionRunning=_useState8[0],setTransitionRunning=_useState8[1];if(prevTargetSummary!==ts){var _ts$commandResults;setPrevTargetSummary(ts);setSelectedCommandResult(ts===null||ts===void 0?void 0:(_ts$commandResults=ts.commandResults)===null||_ts$commandResults===void 0?void 0:_ts$commandResults[0]);}var _useLoadingHelper=useLoadingHelper(function(){if(selectedCommandResult===undefined){return Promise.resolve(undefined);}return doGetRootNode(api,selectedCommandResult);},[selectedCommandResult]),_useLoadingHelper2=slicedToArray_slicedToArray(_useLoadingHelper,3),loading=_useLoadingHelper2[0],loadingError=_useLoadingHelper2[1],nodeData=_useLoadingHelper2[2];var cardsContainerElem=(0,react.useRef)();(0,react.useEffect)(function(){var _cardsContainerElem$c;var rect=(_cardsContainerElem$c=cardsContainerElem.current)===null||_cardsContainerElem$c===void 0?void 0:_cardsContainerElem$c.getBoundingClientRect();if(!rect||!selectedCardRect||!(ts!==null&&ts!==void 0&&ts.commandResults)){setCardsCoords([]);return;}var initialCoords=ts.commandResults.map(function(){return{left:selectedCardRect.left-rect.left,top:selectedCardRect.top-rect.top};});setCardsCoords(initialCoords);setTransitionRunning(true);},[selectedCardRect,ts===null||ts===void 0?void 0:ts.commandResults]);(0,react.useEffect)(function(){if(cardsCoords.length>0){var targetCoords=cardsCoords.map(function(_,i){return{left:0,top:i*(cardHeight+cardGap)};});if(cardsCoords.length===targetCoords.length&&cardsCoords.every(function(_ref,i){var left=_ref.left,top=_ref.top;return targetCoords[i].left===left&&targetCoords[i].top===top;})){return;}setTimeout(function(){setCardsCoords(targetCoords);setTimeout(function(){setTransitionRunning(false);},theme.transitions.duration.enteringScreen);},10);}},[cardsCoords,theme.transitions.duration.enteringScreen]);var zIndex=theme.zIndex.modal+1;var cards=(0,react.useMemo)(function(){if(!(ps&&ts&&ts.commandResults&&ts.commandResults.length>0&&cardsCoords.length>0)){return null;}return ts.commandResults.map(function(rs,i){return/*#__PURE__*/(0,jsx_runtime.jsx)(Card,{sx:{position:'absolute',translate:"".concat(cardsCoords[i].left,"px ").concat(cardsCoords[i].top,"px"),zIndex:zIndex,transition:theme.transitions.create(['translate'],{easing:theme.transitions.easing.sharp,duration:theme.transitions.duration.enteringScreen})},children:/*#__PURE__*/(0,jsx_runtime.jsx)(CommandResultItem,{ps:ps,ts:ts,rs:rs,onSelectCommandResult:setSelectedCommandResult,selected:rs===selectedCommandResult})},rs.id);});},[ps,ts,cardsCoords,zIndex,theme.transitions,selectedCommandResult]);if(loadingError){return/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{children:"Error"});}return/*#__PURE__*/(0,jsx_runtime.jsxs)(jsx_runtime.Fragment,{children:[ps&&ts&&ts.commandResults&&ts.commandResults.length>0&&/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{sx:{position:'fixed',top:0,bottom:0,right:sidePanelWidth,width:"calc(100% - ".concat(sidePanelWidth,"px)"),overflowX:'visible',overflowY:transitionRunning?'visible':'auto',display:'flex',flexDirection:'column',alignItems:'center',justifyContent:'center',padding:'25px 0',zIndex:zIndex},onClick:props.onClose,children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{onClick:function onClick(e){return e.stopPropagation();},position:"relative",width:cardWidth,height:cardHeight*ts.commandResults.length+cardGap*(ts.commandResults.length-1),ref:cardsContainerElem,children:cards})}),/*#__PURE__*/(0,jsx_runtime.jsx)(styles_ThemeProvider_ThemeProvider,{theme:theme_dark,children:/*#__PURE__*/(0,jsx_runtime.jsx)(Drawer_Drawer,{sx:{zIndex:1300},anchor:"right",open:props.rs!==undefined,onClose:props.onClose,children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{width:sidePanelWidth,height:"100%",children:loading?/*#__PURE__*/(0,jsx_runtime.jsx)(Loading,{}):/*#__PURE__*/(0,jsx_runtime.jsx)(SidePanel,{provider:nodeData,onClose:props.onClose})})})})]});}); +/* harmony default export */ var TableRow_TableRow = (TableRow); +;// CONCATENATED MODULE: ./src/components/PropertiesTable.tsx +function PropertiesTable(props){return/*#__PURE__*/(0,jsx_runtime.jsx)(TableContainer_TableContainer,{children:/*#__PURE__*/(0,jsx_runtime.jsxs)(Table_Table,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableHead_TableHead,{children:/*#__PURE__*/(0,jsx_runtime.jsxs)(TableRow_TableRow,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:"Name"}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:"Value"})]})}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableBody_TableBody,{children:props.properties.map(function(row){return/*#__PURE__*/(0,jsx_runtime.jsxs)(TableRow_TableRow,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:row.name}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:row.value})]},row.name);})})]})});} +// EXTERNAL MODULE: ./node_modules/js-sha256/src/sha256.js +var sha256 = __webpack_require__(981); +;// CONCATENATED MODULE: ./src/utils/listKey.ts +function buildListKey(o){var j=JSON.stringify(o);return (0,sha256.sha256)(j);} +;// CONCATENATED MODULE: ./src/components/result-view/ChangesTable.tsx +var RefList=function RefList(props){return/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{py:"10px",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{align:"center",variant:"h5",children:props.title}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableContainer_TableContainer,{children:/*#__PURE__*/(0,jsx_runtime.jsxs)(Table_Table,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableHead_TableHead,{children:/*#__PURE__*/(0,jsx_runtime.jsxs)(TableRow_TableRow,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{align:"left",children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{children:"Kind"})}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{align:"left",children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{children:"Namespace"})}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{align:"left",children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{children:"Name"})})]})}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableBody_TableBody,{children:props.refs.map(function(ref){return/*#__PURE__*/(0,jsx_runtime.jsxs)(TableRow_TableRow,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:buildRefKindElement(ref)}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{children:ref.namespace})}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{children:ref.name})})]},buildRefString(ref));})})]})})]});};function ChangesTable(props){var changedObjects;if(props.diffStatus.changedObjects.length){changedObjects=/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{py:"10px",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{align:"center",variant:"h5",children:"Changed Objects"}),props.diffStatus.changedObjects.map(function(co){var _co$changes;return/*#__PURE__*/(0,jsx_runtime.jsx)(TableContainer_TableContainer,{children:/*#__PURE__*/(0,jsx_runtime.jsxs)(Table_Table,{children:[/*#__PURE__*/(0,jsx_runtime.jsxs)(TableHead_TableHead,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableRow_TableRow,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{align:"left",colSpan:2,sx:{padding:'24px 16px 5px 16px'},children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{sx:{fontWeight:500,fontSize:'20px',lineHeight:'27px',letterSpacing:'1px'},children:buildRefString(co.ref)})})}),/*#__PURE__*/(0,jsx_runtime.jsxs)(TableRow_TableRow,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:"Path"}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:"Changes"})]})]}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableBody_TableBody,{children:(_co$changes=co.changes)===null||_co$changes===void 0?void 0:_co$changes.map(function(c){return/*#__PURE__*/(0,jsx_runtime.jsxs)(TableRow_TableRow,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{minWidth:"100px",sx:{overflowWrap:"anywhere"},children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{children:c.jsonPath})})}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(CodeViewer,{code:c.unifiedDiff||"",language:"diff"})})]},buildListKey(c));})})]})},buildRefString(co.ref));})]});}return/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{width:"100%",display:"flex",flexDirection:"column",children:[props.diffStatus.newObjects.length?/*#__PURE__*/(0,jsx_runtime.jsx)(RefList,{title:"New Objects",refs:props.diffStatus.newObjects}):/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{}),props.diffStatus.deletedObjects.length?/*#__PURE__*/(0,jsx_runtime.jsx)(RefList,{title:"Deleted Objects",refs:props.diffStatus.deletedObjects}):/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{}),props.diffStatus.orphanObjects.length?/*#__PURE__*/(0,jsx_runtime.jsx)(RefList,{title:"Orphan Objects",refs:props.diffStatus.orphanObjects}):/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{}),changedObjects]});} +;// CONCATENATED MODULE: ./src/components/ErrorsTable.tsx +function ErrorsTable(props){var _props$errors;return/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{height:"100%",children:/*#__PURE__*/(0,jsx_runtime.jsx)(TableContainer_TableContainer,{children:/*#__PURE__*/(0,jsx_runtime.jsxs)(Table_Table,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableHead_TableHead,{children:/*#__PURE__*/(0,jsx_runtime.jsxs)(TableRow_TableRow,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:"Ref"}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:"Message"})]})}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableBody_TableBody,{children:(_props$errors=props.errors)===null||_props$errors===void 0?void 0:_props$errors.map(function(e){return/*#__PURE__*/(0,jsx_runtime.jsxs)(TableRow_TableRow,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{sx:{minWidth:"150px"},children:/*#__PURE__*/(0,jsx_runtime.jsxs)(List_List,{disablePadding:true,children:[buildRefKindElement(e.ref,/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(ListItem_ListItem,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(ListItemText_ListItemText,{primary:e.ref.kind})})})),/*#__PURE__*/(0,jsx_runtime.jsx)(Divider_Divider,{}),/*#__PURE__*/(0,jsx_runtime.jsx)(ListItem_ListItem,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(ListItemText_ListItemText,{primary:e.ref.name})}),e.ref.namespace&&/*#__PURE__*/(0,jsx_runtime.jsxs)(jsx_runtime.Fragment,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)(Divider_Divider,{}),/*#__PURE__*/(0,jsx_runtime.jsx)(ListItem_ListItem,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(ListItemText_ListItemText,{primary:e.ref.namespace})})]})]})}),/*#__PURE__*/(0,jsx_runtime.jsx)(TableCell_TableCell,{children:e.message})]},buildListKey(e));})})]})})})});} +;// CONCATENATED MODULE: ./src/components/ObjectYaml.tsx +var ObjectYaml=function ObjectYaml(props){var api=(0,react.useContext)(ApiContext);var _useLoadingHelper=useLoadingHelper(/*#__PURE__*/asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee(){var o;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee$(_context){while(1)switch(_context.prev=_context.next){case 0:_context.next=2;return api.getResultObject(props.treeProps.summary.id,props.objectRef,props.objectType);case 2:o=_context.sent;return _context.abrupt("return",dump(o));case 4:case"end":return _context.stop();}},_callee);})),[props.treeProps.summary.id,props.objectRef,props.objectType]),_useLoadingHelper2=slicedToArray_slicedToArray(_useLoadingHelper,3),loading=_useLoadingHelper2[0],error=_useLoadingHelper2[1],content=_useLoadingHelper2[2];if(loading){return/*#__PURE__*/(0,jsx_runtime.jsx)(Loading,{});}else if(error){return/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{children:"Error"});}else{return/*#__PURE__*/(0,jsx_runtime.jsx)(CodeViewer,{code:content,language:"yaml"});}}; +;// CONCATENATED MODULE: ./src/components/result-view/nodes/NodeData.tsx +var DiffStatus=/*#__PURE__*/function(){function DiffStatus(){classCallCheck_classCallCheck(this,DiffStatus);this.newObjects=[];this.deletedObjects=[];this.orphanObjects=[];this.changedObjects=[];this.totalInsertions=0;this.totalDeletions=0;this.totalUpdates=0;}createClass_createClass(DiffStatus,[{key:"addChangedObject",value:function addChangedObject(co){var _co$changes,_this=this;this.changedObjects.push(co);(_co$changes=co.changes)===null||_co$changes===void 0?void 0:_co$changes.forEach(function(x){switch(x.type){case"insert":_this.totalInsertions++;break;case"delete":_this.totalDeletions++;break;case"update":_this.totalUpdates++;break;}});}},{key:"merge",value:function merge(other){this.newObjects=this.newObjects.concat(other.newObjects);this.deletedObjects=this.deletedObjects.concat(other.deletedObjects);this.orphanObjects=this.orphanObjects.concat(other.orphanObjects);this.changedObjects=this.changedObjects.concat(other.changedObjects);this.totalInsertions+=other.totalInsertions;this.totalDeletions+=other.totalDeletions;this.totalUpdates+=other.totalUpdates;}},{key:"hasDiffs",value:function hasDiffs(){if(this.newObjects.length||this.deletedObjects.length||this.orphanObjects.length){return true;}if(this.changedObjects.find(function(co){var _co$changes2;return((_co$changes2=co.changes)===null||_co$changes2===void 0?void 0:_co$changes2.length)!==0;})){return true;}return false;}}]);return DiffStatus;}();var HealthStatus=/*#__PURE__*/function(){function HealthStatus(){classCallCheck_classCallCheck(this,HealthStatus);this.errors=[];this.warnings=[];}createClass_createClass(HealthStatus,[{key:"merge",value:function merge(other){this.errors=this.errors.concat(other.errors);this.warnings=this.warnings.concat(other.warnings);}}]);return HealthStatus;}();var NodeData=/*#__PURE__*/function(){function NodeData(props,id,hasHealthStatus,hasDiffStatus){classCallCheck_classCallCheck(this,NodeData);this.props=void 0;this.id=void 0;this.children=[];this.healthStatus=void 0;this.diffStatus=void 0;this.props=props;this.id=id;if(hasHealthStatus){this.healthStatus=new HealthStatus();}if(hasDiffStatus){this.diffStatus=new DiffStatus();}}createClass_createClass(NodeData,[{key:"pushChild",value:function pushChild(child,front){if(front){this.children.unshift(child);}else{this.children.push(child);}this.merge(child);}},{key:"merge",value:function merge(other){if(this.diffStatus&&other.diffStatus){this.diffStatus.merge(other.diffStatus);}if(this.healthStatus&&other.healthStatus){this.healthStatus.merge(other.healthStatus);}}},{key:"buildStatusLine",value:function buildStatusLine(){var _this$healthStatus,_this$healthStatus2,_this$diffStatus,_this$diffStatus2,_this$diffStatus3,_this$diffStatus4;return/*#__PURE__*/(0,jsx_runtime.jsx)(StatusLine,{errors:(_this$healthStatus=this.healthStatus)===null||_this$healthStatus===void 0?void 0:_this$healthStatus.errors.length,warnings:(_this$healthStatus2=this.healthStatus)===null||_this$healthStatus2===void 0?void 0:_this$healthStatus2.warnings.length,changedObjects:(_this$diffStatus=this.diffStatus)===null||_this$diffStatus===void 0?void 0:_this$diffStatus.changedObjects.length,newObjects:(_this$diffStatus2=this.diffStatus)===null||_this$diffStatus2===void 0?void 0:_this$diffStatus2.newObjects.length,deletedObjects:(_this$diffStatus3=this.diffStatus)===null||_this$diffStatus3===void 0?void 0:_this$diffStatus3.deletedObjects.length,orphanObjects:(_this$diffStatus4=this.diffStatus)===null||_this$diffStatus4===void 0?void 0:_this$diffStatus4.orphanObjects.length});}},{key:"buildTreeItem",value:function buildTreeItem(hasChildren){var _this$healthStatus3,_this$healthStatus4,_this$diffStatus5,_this$diffStatus6,_this$diffStatus7,_this$diffStatus8;var _this$buildIcon=this.buildIcon(),_this$buildIcon2=slicedToArray_slicedToArray(_this$buildIcon,2),icon=_this$buildIcon2[0],iconText=_this$buildIcon2[1];var hasStatusLine=[(_this$healthStatus3=this.healthStatus)===null||_this$healthStatus3===void 0?void 0:_this$healthStatus3.errors.length,(_this$healthStatus4=this.healthStatus)===null||_this$healthStatus4===void 0?void 0:_this$healthStatus4.warnings.length,(_this$diffStatus5=this.diffStatus)===null||_this$diffStatus5===void 0?void 0:_this$diffStatus5.changedObjects.length,(_this$diffStatus6=this.diffStatus)===null||_this$diffStatus6===void 0?void 0:_this$diffStatus6.newObjects.length,(_this$diffStatus7=this.diffStatus)===null||_this$diffStatus7===void 0?void 0:_this$diffStatus7.deletedObjects.length,(_this$diffStatus8=this.diffStatus)===null||_this$diffStatus8===void 0?void 0:_this$diffStatus8.orphanObjects.length].some(function(x){return(x||0)>0;});return/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",height:"100%",flex:"1 1 auto",children:[/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",width:"30px",height:"100%",flex:"0 0 auto",mr:"13px",sx:{'& svg':{width:'30px',height:'30px'}},children:[icon,/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"subtitle1",component:"div",fontSize:"12px",fontWeight:400,lineHeight:"16px",height:"16px",children:iconText})]}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",height:"100%",flex:"1 1 auto",py:"15px",children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,_objectSpread2(_objectSpread2({variant:"h6",component:"div",sx:{wordBreak:'break-all'}},hasChildren?{}:{fontSize:'16px',lineHeight:'22px'}),{},{children:this.buildSidePanelTitle()}))}),hasStatusLine&&/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{height:"100%",width:"172px",flex:"0 0 auto",display:"flex",alignItems:"center",px:"14px",ml:"14px",position:"relative",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(Divider_Divider,{orientation:"vertical",sx:{height:'40px',position:'absolute',left:0}}),this.buildStatusLine()]})]});}},{key:"buildDiffAndHealthPages",value:function buildDiffAndHealthPages(tabs){this.buildChangesPage(tabs);this.buildErrorsPage(tabs);this.buildWarningsPage(tabs);}},{key:"buildObjectPage",value:function buildObjectPage(ref,objectType){return/*#__PURE__*/(0,jsx_runtime.jsx)(ObjectYaml,{treeProps:this.props,objectRef:ref,objectType:objectType});}},{key:"buildChangesPage",value:function buildChangesPage(tabs){var _this$diffStatus9;if(!((_this$diffStatus9=this.diffStatus)!==null&&_this$diffStatus9!==void 0&&_this$diffStatus9.hasDiffs())){return undefined;}tabs.push({label:"Changes",content:/*#__PURE__*/(0,jsx_runtime.jsx)(ChangesTable,{diffStatus:this.diffStatus})});}},{key:"buildErrorsPage",value:function buildErrorsPage(tabs){var _this$healthStatus5;if(!((_this$healthStatus5=this.healthStatus)!==null&&_this$healthStatus5!==void 0&&_this$healthStatus5.errors.length)){return undefined;}tabs.push({label:"Errors",content:/*#__PURE__*/(0,jsx_runtime.jsx)(ErrorsTable,{errors:this.healthStatus.errors})});}},{key:"buildWarningsPage",value:function buildWarningsPage(tabs){var _this$healthStatus6;if(!((_this$healthStatus6=this.healthStatus)!==null&&_this$healthStatus6!==void 0&&_this$healthStatus6.warnings.length)){return undefined;}tabs.push({label:"Warnings",content:/*#__PURE__*/(0,jsx_runtime.jsx)(ErrorsTable,{errors:this.healthStatus.warnings})});}}]);return NodeData;}(); ;// CONCATENATED MODULE: ./src/components/targets-view/TargetDetailsDrawer.tsx var MyProvider=/*#__PURE__*/function(){function MyProvider(ts){var _this$ts,_this$ts$lastValidate,_this$ts$lastValidate2,_this=this;classCallCheck_classCallCheck(this,MyProvider);this.ts=void 0;this.diffStatus=void 0;this.ts=ts;this.diffStatus=new DiffStatus();(_this$ts=this.ts)===null||_this$ts===void 0?void 0:(_this$ts$lastValidate=_this$ts.lastValidateResult)===null||_this$ts$lastValidate===void 0?void 0:(_this$ts$lastValidate2=_this$ts$lastValidate.drift)===null||_this$ts$lastValidate2===void 0?void 0:_this$ts$lastValidate2.forEach(function(co){_this.diffStatus.addChangedObject(co);});}createClass_createClass(MyProvider,[{key:"buildSidePanelTabs",value:function buildSidePanelTabs(){var _this$ts$lastValidate3,_this$ts$lastValidate4,_this$ts$lastValidate5,_this$ts$lastValidate6;if(!this.ts){return[];}var tabs=[{label:"Summary",content:this.buildSummaryTab()}];if(this.ts.target)if(this.diffStatus.changedObjects.length){tabs.push({label:"Drift",content:/*#__PURE__*/(0,jsx_runtime.jsx)(ChangesTable,{diffStatus:this.diffStatus})});}if((_this$ts$lastValidate3=this.ts.lastValidateResult)!==null&&_this$ts$lastValidate3!==void 0&&(_this$ts$lastValidate4=_this$ts$lastValidate3.errors)!==null&&_this$ts$lastValidate4!==void 0&&_this$ts$lastValidate4.length){tabs.push({label:"Errors",content:/*#__PURE__*/(0,jsx_runtime.jsx)(ErrorsTable,{errors:this.ts.lastValidateResult.errors})});}if((_this$ts$lastValidate5=this.ts.lastValidateResult)!==null&&_this$ts$lastValidate5!==void 0&&(_this$ts$lastValidate6=_this$ts$lastValidate5.warnings)!==null&&_this$ts$lastValidate6!==void 0&&_this$ts$lastValidate6.length){tabs.push({label:"Warnings",content:/*#__PURE__*/(0,jsx_runtime.jsx)(ErrorsTable,{errors:this.ts.lastValidateResult.warnings})});}return tabs;}},{key:"buildSummaryTab",value:function buildSummaryTab(){var _this$ts2,_this$ts3,_this$ts4,_this$ts4$lastValidat,_this$ts4$lastValidat2,_this$ts5,_this$ts5$lastValidat,_this$ts5$lastValidat2,_this$ts6,_this$ts6$lastValidat,_this$ts6$lastValidat2;var props=[{name:"Target Name",value:this.getTargetName()},{name:"Discriminator",value:(_this$ts2=this.ts)===null||_this$ts2===void 0?void 0:_this$ts2.target.discriminator}];if((_this$ts3=this.ts)!==null&&_this$ts3!==void 0&&_this$ts3.lastValidateResult){props.push({name:"Ready",value:this.ts.lastValidateResult.ready+""});}if((_this$ts4=this.ts)!==null&&_this$ts4!==void 0&&(_this$ts4$lastValidat=_this$ts4.lastValidateResult)!==null&&_this$ts4$lastValidat!==void 0&&(_this$ts4$lastValidat2=_this$ts4$lastValidat.errors)!==null&&_this$ts4$lastValidat2!==void 0&&_this$ts4$lastValidat2.length){props.push({name:"Errors",value:this.ts.lastValidateResult.errors.length+""});}if((_this$ts5=this.ts)!==null&&_this$ts5!==void 0&&(_this$ts5$lastValidat=_this$ts5.lastValidateResult)!==null&&_this$ts5$lastValidat!==void 0&&(_this$ts5$lastValidat2=_this$ts5$lastValidat.warnings)!==null&&_this$ts5$lastValidat2!==void 0&&_this$ts5$lastValidat2.length){props.push({name:"Warnings",value:this.ts.lastValidateResult.warnings.length+""});}if((_this$ts6=this.ts)!==null&&_this$ts6!==void 0&&(_this$ts6$lastValidat=_this$ts6.lastValidateResult)!==null&&_this$ts6$lastValidat!==void 0&&(_this$ts6$lastValidat2=_this$ts6$lastValidat.drift)!==null&&_this$ts6$lastValidat2!==void 0&&_this$ts6$lastValidat2.length){props.push({name:"Drifted Objects",value:this.ts.lastValidateResult.drift.length+""});}return/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(PropertiesTable,{properties:props})});}},{key:"getTargetName",value:function getTargetName(){if(!this.ts){return"";}var name="";if(this.ts.target.targetName){name=this.ts.target.targetName;}return name;}},{key:"buildSidePanelTitle",value:function buildSidePanelTitle(){return this.getTargetName();}}]);return MyProvider;}();var TargetDetailsDrawer=/*#__PURE__*/react.memo(function(props){return/*#__PURE__*/(0,jsx_runtime.jsx)(styles_ThemeProvider_ThemeProvider,{theme:theme_dark,children:/*#__PURE__*/(0,jsx_runtime.jsx)(Drawer_Drawer,{sx:{zIndex:1300},anchor:"right",open:props.ts!==undefined,onClose:props.onClose,ModalProps:{BackdropProps:{invisible:true}},children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{width:"600px",height:"100%",children:/*#__PURE__*/(0,jsx_runtime.jsx)(SidePanel,{provider:new MyProvider(props.ts),onClose:props.onClose})})})});}); +;// CONCATENATED MODULE: ./node_modules/@mui/icons-material/esm/Category.js + + +/* harmony default export */ var Category = (createSvgIcon([/*#__PURE__*/(0,jsx_runtime.jsx)("path", { + d: "m12 2-5.5 9h11z" +}, "0"), /*#__PURE__*/(0,jsx_runtime.jsx)("circle", { + cx: "17.5", + cy: "17.5", + r: "4.5" +}, "1"), /*#__PURE__*/(0,jsx_runtime.jsx)("path", { + d: "M3 13.5h8v8H3z" +}, "2")], 'Category')); +;// CONCATENATED MODULE: ./node_modules/@mui/icons-material/esm/Settings.js + + +/* harmony default export */ var Settings = (createSvgIcon( /*#__PURE__*/(0,jsx_runtime.jsx)("path", { + d: "M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z" +}), 'Settings')); +;// CONCATENATED MODULE: ./node_modules/@mui/icons-material/esm/Lock.js + + +/* harmony default export */ var Lock = (createSvgIcon( /*#__PURE__*/(0,jsx_runtime.jsx)("path", { + d: "M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z" +}), 'Lock')); +;// CONCATENATED MODULE: ./node_modules/@mui/icons-material/esm/Dvr.js + + +/* harmony default export */ var Dvr = (createSvgIcon( /*#__PURE__*/(0,jsx_runtime.jsx)("path", { + d: "M21 3H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h5v2h8v-2h5c1.1 0 1.99-.9 1.99-2L23 5c0-1.1-.9-2-2-2zm0 14H3V5h18v12zm-2-9H8v2h11V8zm0 4H8v2h11v-2zM7 8H5v2h2V8zm0 4H5v2h2v-2z" +}), 'Dvr')); +;// CONCATENATED MODULE: ./node_modules/@mui/icons-material/esm/Http.js + + +/* harmony default export */ var Http = (createSvgIcon( /*#__PURE__*/(0,jsx_runtime.jsx)("path", { + d: "M4.5 11h-2V9H1v6h1.5v-2.5h2V15H6V9H4.5v2zm2.5-.5h1.5V15H10v-4.5h1.5V9H7v1.5zm5.5 0H14V15h1.5v-4.5H17V9h-4.5v1.5zm9-1.5H18v6h1.5v-2h2c.8 0 1.5-.7 1.5-1.5v-1c0-.8-.7-1.5-1.5-1.5zm0 2.5h-2v-1h2v1z" +}), 'Http')); +;// CONCATENATED MODULE: ./node_modules/@mui/icons-material/esm/Cloud.js + + +/* harmony default export */ var Cloud = (createSvgIcon( /*#__PURE__*/(0,jsx_runtime.jsx)("path", { + d: "M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96z" +}), 'Cloud')); +;// CONCATENATED MODULE: ./src/components/result-view/nodes/VarsSourceNode.tsx +var VarsSourceNodeData=/*#__PURE__*/function(_NodeData){_inherits(VarsSourceNodeData,_NodeData);var _super=_createSuper(VarsSourceNodeData);function VarsSourceNodeData(props,id,varsSource){var _this$varsSource$clus;var _this;classCallCheck_classCallCheck(this,VarsSourceNodeData);_this=_super.call(this,props,id,false,false);_this.varsSource=void 0;_this.labelsYaml=void 0;_this.renderedVarsYaml=void 0;_this.varsSource=varsSource;var labels=(_this$varsSource$clus=_this.varsSource.clusterConfigMap)===null||_this$varsSource$clus===void 0?void 0:_this$varsSource$clus.labels;if(!labels){var _this$varsSource$clus2;labels=(_this$varsSource$clus2=_this.varsSource.clusterSecret)===null||_this$varsSource$clus2===void 0?void 0:_this$varsSource$clus2.labels;}if(labels){_this.labelsYaml=dump(labels);}_this.renderedVarsYaml=dump(_this.varsSource.renderedVars);return _this;}createClass_createClass(VarsSourceNodeData,[{key:"getVarsSourceHandler",value:function getVarsSourceHandler(){var _this2=this;if(this.varsSource.values){return{type:"values",label:function label(){if(_this2.varsSource.renderedVars){return"values: "+Object.keys(_this2.varsSource.renderedVars).length;}else{return"empty values";}},icon:function icon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(Category,{fontSize:"large"});},sourceProps:function sourceProps(){return[];}};}else if(this.varsSource.file){return{type:"file",label:function label(){return _this2.varsSource.file;},icon:function icon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(FileIcon,{});},sourceProps:function sourceProps(){return[{name:"File",value:_this2.varsSource.file}];}};}else if(this.varsSource.git){return{type:"git",label:function label(){var s=_this2.varsSource.git.url.split("/");var name=s[s.length-1];return/*#__PURE__*/(0,jsx_runtime.jsxs)(jsx_runtime.Fragment,{children:[name,/*#__PURE__*/(0,jsx_runtime.jsx)("br",{}),_this2.varsSource.git.path]});},icon:function icon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(GitIcon,{});},sourceProps:function sourceProps(){var sourceProps=[];sourceProps.push({name:"Url",value:_this2.varsSource.git.url});sourceProps.push({name:"Path",value:_this2.varsSource.git.path});var ref="HEAD";if(_this2.varsSource.git.ref){ref=_this2.varsSource.git.ref;}sourceProps.push({name:"Ref",value:ref});return sourceProps;}};}else if(this.varsSource.clusterConfigMap||this.varsSource.clusterSecret){var vs=this.varsSource.clusterConfigMap?this.varsSource.clusterConfigMap:this.varsSource.clusterSecret;var type=this.varsSource.clusterConfigMap?"cm":"secret";var _icon=this.varsSource.clusterConfigMap?/*#__PURE__*/(0,jsx_runtime.jsx)(Settings,{fontSize:"large"}):/*#__PURE__*/(0,jsx_runtime.jsx)(Lock,{fontSize:"large"});return{type:type,label:function label(){var _this2$varsSource$clu;return(_this2$varsSource$clu=_this2.varsSource.clusterConfigMap)===null||_this2$varsSource$clu===void 0?void 0:_this2$varsSource$clu.name;},icon:function icon(){return _icon;},sourceProps:function sourceProps(){var sourceProps=[];sourceProps.push({name:"Name",value:vs.name});sourceProps.push({name:"Namespace",value:vs.namespace});sourceProps.push({name:"Key",value:vs.key});if(vs.labels){sourceProps.push({name:"Labels",value:/*#__PURE__*/(0,jsx_runtime.jsx)(CodeViewer,{code:_this2.labelsYaml,language:"yaml"})});}return sourceProps;}};}else if(this.varsSource.systemEnvVars){return{type:"systemEnvVar",label:function label(){return"systemEnvVars";},icon:function icon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(Dvr,{fontSize:"large"});},sourceProps:function sourceProps(){// TODO +return[];}};}else if(this.varsSource.http){return{type:"http",label:function label(){return _this2.varsSource.http.url;},icon:function icon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(Http,{fontSize:"large"});},sourceProps:function sourceProps(){var sourceProps=[];sourceProps.push({name:"Url",value:_this2.varsSource.http.url});sourceProps.push({name:"Method",value:_this2.varsSource.http.method||"GET"});if(_this2.varsSource.http.jsonPath){sourceProps.push({name:"JsonPath",value:_this2.varsSource.http.jsonPath});}return sourceProps;}};}else if(this.varsSource.awsSecretsManager){return{type:"awsSecretsManager",label:function label(){return _this2.varsSource.awsSecretsManager.secretName;},icon:function icon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(Cloud,{fontSize:"large"});},sourceProps:function sourceProps(){var sourceProps=[];sourceProps.push({name:"SecretName",value:_this2.varsSource.awsSecretsManager.secretName});if(_this2.varsSource.awsSecretsManager.region){sourceProps.push({name:"Region",value:_this2.varsSource.awsSecretsManager.region});}if(_this2.varsSource.awsSecretsManager.profile){sourceProps.push({name:"Profile",value:_this2.varsSource.awsSecretsManager.profile});}return sourceProps;}};}else if(this.varsSource.vault){return{type:"vault",label:function label(){return/*#__PURE__*/(0,jsx_runtime.jsxs)(jsx_runtime.Fragment,{children:[_this2.varsSource.vault.address,/*#__PURE__*/(0,jsx_runtime.jsx)("br",{}),_this2.varsSource.vault.path]});},icon:function icon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(Cloud,{fontSize:"large"});},sourceProps:function sourceProps(){var sourceProps=[];sourceProps.push({name:"Address",value:_this2.varsSource.vault.address});sourceProps.push({name:"Path",value:_this2.varsSource.vault.path});return sourceProps;}};}else{return{type:"unknown",label:function label(){return"values: "+Object.keys(_this2.varsSource.renderedVars).length;},icon:function icon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(Category,{fontSize:"large"});},sourceProps:function sourceProps(){return[];}};}}},{key:"buildSidePanelTitle",value:function buildSidePanelTitle(){return this.getVarsSourceHandler().label();}},{key:"buildIcon",value:function buildIcon(){var h=this.getVarsSourceHandler();return[h.icon(),h.type];}},{key:"buildSidePanelTabs",value:function buildSidePanelTabs(){var tabs=[{label:"Summary",content:this.buildSummaryPage()},{label:"Vars",content:this.buildVarsPage()}];this.buildDiffAndHealthPages(tabs);return tabs;}},{key:"buildSummaryPage",value:function buildSummaryPage(){var h=this.getVarsSourceHandler();var props=[{name:"Type",value:h.type}].concat(toConsumableArray_toConsumableArray(h.sourceProps()),[{name:"IgnoreMissing",value:!!this.varsSource.ignoreMissing+""},{name:"NoOverride",value:!!this.varsSource.noOverride+""}]);if(this.varsSource.when){props.push({name:"When",value:this.varsSource.when});}return/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(PropertiesTable,{properties:props})});}},{key:"buildVarsPage",value:function buildVarsPage(){return/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{sx:{overflowY:'scroll',// Enable vertical scrolling +//maxHeight: '400px', // Set a fixed height +border:'1px solid #ccc',// Optional border +padding:'10px'// Optional padding +},children:/*#__PURE__*/(0,jsx_runtime.jsx)("pre",{children:/*#__PURE__*/(0,jsx_runtime.jsx)(CodeViewer,{code:this.renderedVarsYaml,language:"yaml"})})});}}]);return VarsSourceNodeData;}(NodeData); +;// CONCATENATED MODULE: ./node_modules/@mui/icons-material/esm/Source.js + + +/* harmony default export */ var Source = (createSvgIcon( /*#__PURE__*/(0,jsx_runtime.jsx)("path", { + d: "M20 6h-8l-2-2H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm-6 10H6v-2h8v2zm4-4H6v-2h12v2z" +}), 'Source')); +;// CONCATENATED MODULE: ./src/components/result-view/nodes/DeploymentItemNode.tsx +var DeploymentItemNodeData=/*#__PURE__*/function(_NodeData){_inherits(DeploymentItemNodeData,_NodeData);var _super=_createSuper(DeploymentItemNodeData);function DeploymentItemNodeData(props,id,deploymentItem){var _this;classCallCheck_classCallCheck(this,DeploymentItemNodeData);_this=_super.call(this,props,id,true,true);_this.deploymentItem=void 0;_this.deploymentItem=deploymentItem;return _this;}createClass_createClass(DeploymentItemNodeData,[{key:"buildSidePanelTitle",value:function buildSidePanelTitle(){return this.deploymentItem.path;}},{key:"buildIcon",value:function buildIcon(){var iconText="di";return[/*#__PURE__*/(0,jsx_runtime.jsx)(Source,{fontSize:"large"}),iconText];}},{key:"buildSidePanelTabs",value:function buildSidePanelTabs(){var tabs=[{label:"Summary",content:this.buildSummaryPage()}];this.buildDiffAndHealthPages(tabs);return tabs;}},{key:"buildSummaryPage",value:function buildSummaryPage(){var props=[];props.push({name:"Path",value:this.deploymentItem.path});buildDeploymentItemSummaryProps(this.deploymentItem,props);return/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(PropertiesTable,{properties:props})});}}]);return DeploymentItemNodeData;}(NodeData);function buildDeploymentItemSummaryProps(di,props){if(di.barrier!==undefined){props.push({name:"Barrier",value:di.barrier+""});}if(di.waitReadiness!==undefined){props.push({name:"WaitReadiness",value:di.waitReadiness+""});}if(di.skipDeleteIfTags!==undefined){props.push({name:"SkipDeleteIfTags",value:di.skipDeleteIfTags+""});}if(di.onlyRender!==undefined){props.push({name:"OnlyRender",value:di.onlyRender+""});}if(di.alwaysDeploy!==undefined){props.push({name:"AlwaysDeploy",value:di.alwaysDeploy+""});}if(di.deleteObjects){// TODO this is ugly +props.push({name:"DeleteObjects",value:JSON.stringify(di.deleteObjects)});}if(di.when){props.push({name:"When",value:di.when});}} +;// CONCATENATED MODULE: ./node_modules/@mui/icons-material/esm/SettingsEthernet.js + + +/* harmony default export */ var SettingsEthernet = (createSvgIcon( /*#__PURE__*/(0,jsx_runtime.jsx)("path", { + d: "M7.77 6.76 6.23 5.48.82 12l5.41 6.52 1.54-1.28L3.42 12l4.35-5.24zM7 13h2v-2H7v2zm10-2h-2v2h2v-2zm-6 2h2v-2h-2v2zm6.77-7.52-1.54 1.28L20.58 12l-4.35 5.24 1.54 1.28L23.18 12l-5.41-6.52z" +}), 'SettingsEthernet')); +;// CONCATENATED MODULE: ./node_modules/@mui/icons-material/esm/SmartToy.js + + +/* harmony default export */ var SmartToy = (createSvgIcon( /*#__PURE__*/(0,jsx_runtime.jsx)("path", { + d: "M20 9V7c0-1.1-.9-2-2-2h-3c0-1.66-1.34-3-3-3S9 3.34 9 5H6c-1.1 0-2 .9-2 2v2c-1.66 0-3 1.34-3 3s1.34 3 3 3v4c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2v-4c1.66 0 3-1.34 3-3s-1.34-3-3-3zM7.5 11.5c0-.83.67-1.5 1.5-1.5s1.5.67 1.5 1.5S9.83 13 9 13s-1.5-.67-1.5-1.5zM16 17H8v-2h8v2zm-1-4c-.83 0-1.5-.67-1.5-1.5S14.17 10 15 10s1.5.67 1.5 1.5S15.83 13 15 13z" +}), 'SmartToy')); +;// CONCATENATED MODULE: ./src/components/result-view/nodes/ObjectNode.tsx +var kindMapping={"/ConfigMap":{icon:Settings},"apps/Deployment":{icon:PublishedWithChanges},"Service":{icon:SettingsEthernet},"ServiceAccount":{icon:SmartToy}};var ObjectNodeData=/*#__PURE__*/function(_NodeData){_inherits(ObjectNodeData,_NodeData);var _super=_createSuper(ObjectNodeData);function ObjectNodeData(props,id,objectRef){var _this;classCallCheck_classCallCheck(this,ObjectNodeData);_this=_super.call(this,props,id,true,true);_this.objectRef=void 0;_this.objectRef=objectRef;return _this;}createClass_createClass(ObjectNodeData,[{key:"buildSidePanelTitle",value:function buildSidePanelTitle(){return this.objectRef.name;}},{key:"buildIcon",value:function buildIcon(){var _this2=this;var sn=this.props.shortNames.find(function(sn){return sn.group===_this2.objectRef.group&&sn.kind===_this2.objectRef.kind;});var snStr=(sn===null||sn===void 0?void 0:sn.shortName)||"";var m=kindMapping[this.objectRef.group+"/"+this.objectRef.kind];if(m!==undefined){return[/*#__PURE__*/react.createElement(m.icon,{fontSize:"large"}),snStr];}return[/*#__PURE__*/(0,jsx_runtime.jsx)(BracketsCurlyIcon,{}),snStr];}},{key:"buildSidePanelTabs",value:function buildSidePanelTabs(){var _findObjectByRef,_findObjectByRef2,_findObjectByRef3;var tabs=[{label:"Summary",content:this.buildSummaryPage()}];this.buildDiffAndHealthPages(tabs);if((_findObjectByRef=findObjectByRef(this.props.commandResult.objects,this.objectRef))!==null&&_findObjectByRef!==void 0&&_findObjectByRef.rendered){tabs.push({label:"Rendered",content:this.buildObjectPage(this.objectRef,ObjectType.Rendered)});}if((_findObjectByRef2=findObjectByRef(this.props.commandResult.objects,this.objectRef))!==null&&_findObjectByRef2!==void 0&&_findObjectByRef2.remote){tabs.push({label:"Remote",content:this.buildObjectPage(this.objectRef,ObjectType.Remote)});}if((_findObjectByRef3=findObjectByRef(this.props.commandResult.objects,this.objectRef))!==null&&_findObjectByRef3!==void 0&&_findObjectByRef3.applied){tabs.push({label:"Applied",content:this.buildObjectPage(this.objectRef,ObjectType.Applied)});}return tabs;}},{key:"buildSummaryPage",value:function buildSummaryPage(){var _o$rendered;var props=[];var apiVersion=this.objectRef.version;if(this.objectRef.group){apiVersion=this.objectRef.group+"/"+this.objectRef.version;}props.push({name:"ApiVersion",value:apiVersion});props.push({name:"Kind",value:this.objectRef.kind});props.push({name:"Name",value:this.objectRef.name});if(this.objectRef.namespace){props.push({name:"Namespace",value:this.objectRef.namespace});}var o=findObjectByRef(this.props.commandResult.objects,this.objectRef);var annotations=o===null||o===void 0?void 0:(_o$rendered=o.rendered)===null||_o$rendered===void 0?void 0:_o$rendered.metadata.annotations;if(annotations){Object.keys(annotations).forEach(function(k){if(k.indexOf("kluctl.io/")!==-1){props.push({name:k,value:annotations[k]});}});}return/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(PropertiesTable,{properties:props})});}}]);return ObjectNodeData;}(NodeData); +;// CONCATENATED MODULE: ./src/components/result-view/nodes/CommandResultNode.tsx +var CommandResultNodeData=/*#__PURE__*/function(_NodeData){_inherits(CommandResultNodeData,_NodeData);var _super=_createSuper(CommandResultNodeData);function CommandResultNodeData(props,id){var _this$props$commandRe;var _this;classCallCheck_classCallCheck(this,CommandResultNodeData);_this=_super.call(this,props,id,true,true);_this.dumpedTargetYaml=void 0;if((_this$props$commandRe=_this.props.commandResult.command)!==null&&_this$props$commandRe!==void 0&&_this$props$commandRe.target){_this.dumpedTargetYaml=dump(_this.props.commandResult.command.target);}return _this;}createClass_createClass(CommandResultNodeData,[{key:"buildSidePanelTitle",value:function buildSidePanelTitle(){return"CommandResult";}},{key:"buildIcon",value:function buildIcon(){return[/*#__PURE__*/(0,jsx_runtime.jsx)(DeployIcon,{}),"result"];}},{key:"buildSidePanelTabs",value:function buildSidePanelTabs(){var tabs=[{label:"Summary",content:this.buildSummaryPage()}];if(this.dumpedTargetYaml){var page=this.buildTargetPage();tabs.push({label:"Target",content:page});}this.buildDiffAndHealthPages(tabs);return tabs;}},{key:"buildSummaryPage",value:function buildSummaryPage(){var _this$props$commandRe2,_this$props$commandRe3;var props=[{name:"Initiator",value:(_this$props$commandRe2=this.props.commandResult.command)===null||_this$props$commandRe2===void 0?void 0:_this$props$commandRe2.initiator},{name:"Command",value:(_this$props$commandRe3=this.props.commandResult.command)===null||_this$props$commandRe3===void 0?void 0:_this$props$commandRe3.command}];return/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(PropertiesTable,{properties:props})});}},{key:"buildTargetPage",value:function buildTargetPage(){return/*#__PURE__*/(0,jsx_runtime.jsx)(CodeViewer,{code:this.dumpedTargetYaml,language:"yaml"});}}]);return CommandResultNodeData;}(NodeData); +;// CONCATENATED MODULE: ./src/components/result-view/nodes/VarsSourceCollectionNode.tsx +var VarsSourceCollectionNodeData=/*#__PURE__*/function(_NodeData){_inherits(VarsSourceCollectionNodeData,_NodeData);var _super=_createSuper(VarsSourceCollectionNodeData);function VarsSourceCollectionNodeData(props,id){var _this;classCallCheck_classCallCheck(this,VarsSourceCollectionNodeData);_this=_super.call(this,props,id,false,false);_this.varsSources=[];return _this;}createClass_createClass(VarsSourceCollectionNodeData,[{key:"buildSidePanelTitle",value:function buildSidePanelTitle(){return"vars: "+this.varsSources.length;}},{key:"buildIcon",value:function buildIcon(){return[/*#__PURE__*/(0,jsx_runtime.jsx)(BracketsSquareIcon,{}),"vars"];}},{key:"buildSidePanelTabs",value:function buildSidePanelTabs(){return[];}}]);return VarsSourceCollectionNodeData;}(NodeData); +;// CONCATENATED MODULE: ./src/components/result-view/nodes/DeploymentItemIncludeNode.tsx +var DeploymentItemIncludeNodeData=/*#__PURE__*/function(_NodeData){_inherits(DeploymentItemIncludeNodeData,_NodeData);var _super=_createSuper(DeploymentItemIncludeNodeData);function DeploymentItemIncludeNodeData(props,id,deploymentItem,includedDeployment){var _this;classCallCheck_classCallCheck(this,DeploymentItemIncludeNodeData);_this=_super.call(this,props,id,true,true);_this.deploymentItem=void 0;_this.includedDeployment=void 0;_this.deploymentItem=deploymentItem;_this.includedDeployment=includedDeployment;return _this;}createClass_createClass(DeploymentItemIncludeNodeData,[{key:"buildSidePanelTitle",value:function buildSidePanelTitle(){if(this.deploymentItem.include){return this.deploymentItem.include;}else if(this.deploymentItem.git){var s=this.deploymentItem.git.url.split("/");var name=s[s.length-1];return/*#__PURE__*/(0,jsx_runtime.jsxs)(jsx_runtime.Fragment,{children:[name,this.deploymentItem.git.subDir&&/*#__PURE__*/(0,jsx_runtime.jsxs)(jsx_runtime.Fragment,{children:[/*#__PURE__*/(0,jsx_runtime.jsx)("br",{}),this.deploymentItem.git.subDir]})]});}else{return"unknown include";}}},{key:"buildIcon",value:function buildIcon(){if(this.deploymentItem.git){return[/*#__PURE__*/(0,jsx_runtime.jsx)(GitIcon,{}),"git"];}return[/*#__PURE__*/(0,jsx_runtime.jsx)(IncludeIcon,{}),"include"];}},{key:"buildSidePanelTabs",value:function buildSidePanelTabs(){var tabs=[{label:"Summary",content:this.buildSummaryPage()}];this.buildDiffAndHealthPages(tabs);return tabs;}},{key:"buildSummaryPage",value:function buildSummaryPage(){var props=[];if(this.deploymentItem.include){props.push({name:"Type",value:"LocalInclude"});props.push({name:"Path",value:this.deploymentItem.include});}else if(this.deploymentItem.git){props.push({name:"Type",value:"GitInclude"});props.push({name:"Url",value:this.deploymentItem.git.url});props.push({name:"SubDir",value:this.deploymentItem.git.subDir});var ref="HEAD";if(this.deploymentItem.git.ref){ref=this.deploymentItem.git.ref;}props.push({name:"Ref",value:ref});}else{props.push({name:"Type",value:"Unknown"});}buildDeploymentItemSummaryProps(this.deploymentItem,props);return/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(PropertiesTable,{properties:props})});}}]);return DeploymentItemIncludeNodeData;}(NodeData); +;// CONCATENATED MODULE: ./node_modules/@mui/icons-material/esm/Delete.js + + +/* harmony default export */ var Delete = (createSvgIcon( /*#__PURE__*/(0,jsx_runtime.jsx)("path", { + d: "M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z" +}), 'Delete')); +;// CONCATENATED MODULE: ./node_modules/@mui/icons-material/esm/LinkOff.js + + +/* harmony default export */ var LinkOff = (createSvgIcon( /*#__PURE__*/(0,jsx_runtime.jsx)("path", { + d: "M17 7h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1 0 1.43-.98 2.63-2.31 2.98l1.46 1.46C20.88 15.61 22 13.95 22 12c0-2.76-2.24-5-5-5zm-1 4h-2.19l2 2H16zM2 4.27l3.11 3.11C3.29 8.12 2 9.91 2 12c0 2.76 2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1 0-1.59 1.21-2.9 2.76-3.07L8.73 11H8v2h2.73L13 15.27V17h1.73l4.01 4L20 19.74 3.27 3 2 4.27z" +}), 'LinkOff')); +;// CONCATENATED MODULE: ./src/components/result-view/nodes/DeletedOrOrphanObjectsCollectionNode.tsx +var DeletedOrOrphanObjectsCollectionNode=/*#__PURE__*/function(_NodeData){_inherits(DeletedOrOrphanObjectsCollectionNode,_NodeData);var _super=_createSuper(DeletedOrOrphanObjectsCollectionNode);function DeletedOrOrphanObjectsCollectionNode(props,id,deleted){var _this;classCallCheck_classCallCheck(this,DeletedOrOrphanObjectsCollectionNode);_this=_super.call(this,props,id,false,true);_this.deleted=void 0;_this.deleted=deleted;return _this;}createClass_createClass(DeletedOrOrphanObjectsCollectionNode,[{key:"buildSidePanelTitle",value:function buildSidePanelTitle(){if(this.deleted){var _this$diffStatus;return"".concat((_this$diffStatus=this.diffStatus)===null||_this$diffStatus===void 0?void 0:_this$diffStatus.deletedObjects.length," objects deleted");}else{var _this$diffStatus2;return"".concat((_this$diffStatus2=this.diffStatus)===null||_this$diffStatus2===void 0?void 0:_this$diffStatus2.orphanObjects.length," objects orphaned");}}},{key:"buildIcon",value:function buildIcon(){if(this.deleted){return[/*#__PURE__*/(0,jsx_runtime.jsx)(Delete,{fontSize:"large"}),"deleted"];}else{return[/*#__PURE__*/(0,jsx_runtime.jsx)(LinkOff,{fontSize:"large"}),"orphans"];}}},{key:"buildSidePanelTabs",value:function buildSidePanelTabs(){var tabs=[{label:"Summary",content:this.buildSummaryPage()}];return tabs;}},{key:"buildSummaryPage",value:function buildSummaryPage(){var _this$diffStatus3,_this$diffStatus5;var props=[];if((_this$diffStatus3=this.diffStatus)!==null&&_this$diffStatus3!==void 0&&_this$diffStatus3.deletedObjects.length){var _this$diffStatus4;props.push({name:"Deleted",value:(_this$diffStatus4=this.diffStatus)===null||_this$diffStatus4===void 0?void 0:_this$diffStatus4.deletedObjects.length});}if((_this$diffStatus5=this.diffStatus)!==null&&_this$diffStatus5!==void 0&&_this$diffStatus5.orphanObjects.length){var _this$diffStatus6;props.push({name:"Orphaned",value:(_this$diffStatus6=this.diffStatus)===null||_this$diffStatus6===void 0?void 0:_this$diffStatus6.orphanObjects.length});}return/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{children:/*#__PURE__*/(0,jsx_runtime.jsx)(PropertiesTable,{properties:props})});}}]);return DeletedOrOrphanObjectsCollectionNode;}(NodeData); +;// CONCATENATED MODULE: ./src/components/result-view/nodes/NodeBuilder.ts +var NodeBuilder=/*#__PURE__*/function(){function NodeBuilder(props){var _props$commandResult$,_this=this,_props$commandResult$2,_props$commandResult$3;classCallCheck_classCallCheck(this,NodeBuilder);this.props=void 0;this.changedObjectsMap=new Map();this.newObjectsMap=new Map();this.orphanObjectsMap=new Map();this.deletedObjectsMap=new Map();this.errorsMap=new Map();this.warningsMap=new Map();this.props=props;(_props$commandResult$=props.commandResult.objects)===null||_props$commandResult$===void 0?void 0:_props$commandResult$.forEach(function(o){var _o$changes;var key=buildObjectRefKey(o.ref);if((_o$changes=o.changes)!==null&&_o$changes!==void 0&&_o$changes.length){_this.changedObjectsMap.set(key,o);}if(o.new){_this.newObjectsMap.set(key,o.ref);}if(o.orphan){_this.orphanObjectsMap.set(key,o.ref);}if(o.deleted){_this.deletedObjectsMap.set(key,o.ref);}});(_props$commandResult$2=props.commandResult.errors)===null||_props$commandResult$2===void 0?void 0:_props$commandResult$2.forEach(function(e){var key=buildObjectRefKey(e.ref);var l=_this.errorsMap.get(key);if(!l){l=[e];_this.errorsMap.set(key,l);}else{l.push(e);}});(_props$commandResult$3=props.commandResult.warnings)===null||_props$commandResult$3===void 0?void 0:_props$commandResult$3.forEach(function(e){var key=buildObjectRefKey(e.ref);var l=_this.warningsMap.get(key);if(!l){l=[e];_this.warningsMap.set(key,l);}else{l.push(e);}});}createClass_createClass(NodeBuilder,[{key:"buildRoot",value:function buildRoot(){var rootNode=new CommandResultNodeData(this.props,"root");if(this.props.commandResult.deployment){this.buildDeploymentProjectChildren(rootNode,this.props.commandResult.deployment);}if(this.deletedObjectsMap.size){this.buildDeletedOrOrphanNode(rootNode,true,Array.from(this.deletedObjectsMap.values()));}if(this.orphanObjectsMap.size){this.buildDeletedOrOrphanNode(rootNode,false,Array.from(this.orphanObjectsMap.values()));}var nodeMap=new Map();function collect(n){nodeMap.set(n.id,n);n.children.forEach(function(c){collect(c);});}collect(rootNode);return[rootNode,nodeMap];}},{key:"buildDeploymentProjectChildren",value:function buildDeploymentProjectChildren(node,deploymentProject){var _deploymentProject$de,_this2=this;if(deploymentProject.vars){this.buildVarsSourceCollectionNode(node,deploymentProject.vars);}(_deploymentProject$de=deploymentProject.deployments)===null||_deploymentProject$de===void 0?void 0:_deploymentProject$de.forEach(function(deploymentItem,i){_this2.buildDeploymentItemNode(node,deploymentItem,i);});}},{key:"buildVarsSourceCollectionNode",value:function buildVarsSourceCollectionNode(parentNode,varsSources){var _node$varsSources,_this3=this;if(varsSources===undefined){return;}var newId="".concat(parentNode.id,"/(vars)");var node=new VarsSourceCollectionNodeData(this.props,newId);(_node$varsSources=node.varsSources).push.apply(_node$varsSources,toConsumableArray_toConsumableArray(varsSources));varsSources.forEach(function(vs,i){_this3.buildVarsSourceNode(node,vs,i);});parentNode.pushChild(node);return node;}},{key:"buildVarsSourceNode",value:function buildVarsSourceNode(parentNode,varsSource,index){var newId="".concat(parentNode.id,"/").concat(index,"}");var node=new VarsSourceNodeData(this.props,newId,varsSource);parentNode.pushChild(node);return node;}},{key:"buildDeploymentItemNode",value:function buildDeploymentItemNode(parentNode,deploymentItem,index){var _this4=this;var node;var newId="".concat(parentNode.id,"/(dis)/").concat(index);if(deploymentItem.path){var _deploymentItem$rende;node=new DeploymentItemNodeData(this.props,newId,deploymentItem);this.buildVarsSourceCollectionNode(node,deploymentItem.vars);(_deploymentItem$rende=deploymentItem.renderedObjects)===null||_deploymentItem$rende===void 0?void 0:_deploymentItem$rende.forEach(function(renderedObject){_this4.buildObjectNode(node,renderedObject);});}else if(deploymentItem.include||deploymentItem.git){node=new DeploymentItemIncludeNodeData(this.props,newId,deploymentItem,deploymentItem.renderedInclude);this.buildVarsSourceCollectionNode(node,deploymentItem.vars);if(deploymentItem.renderedInclude){this.buildDeploymentProjectChildren(node,deploymentItem.renderedInclude);}}else{return node;}parentNode.pushChild(node);return node;}},{key:"buildObjectNode",value:function buildObjectNode(parentNode,objectRef){var _this$errorsMap$get,_this$warningsMap$get;var newId="".concat(parentNode.id,"/(obj)/").concat(buildObjectRefKey(objectRef));var node=new ObjectNodeData(this.props,newId,objectRef);var key=buildObjectRefKey(objectRef);if(this.changedObjectsMap.has(key)){var _node$diffStatus;(_node$diffStatus=node.diffStatus)===null||_node$diffStatus===void 0?void 0:_node$diffStatus.addChangedObject(this.changedObjectsMap.get(key));}if(this.newObjectsMap.has(key)){var _node$diffStatus2;(_node$diffStatus2=node.diffStatus)===null||_node$diffStatus2===void 0?void 0:_node$diffStatus2.newObjects.push(objectRef);}if(this.deletedObjectsMap.has(key)){var _node$diffStatus3;(_node$diffStatus3=node.diffStatus)===null||_node$diffStatus3===void 0?void 0:_node$diffStatus3.deletedObjects.push(objectRef);}if(this.orphanObjectsMap.has(key)){var _node$diffStatus4;(_node$diffStatus4=node.diffStatus)===null||_node$diffStatus4===void 0?void 0:_node$diffStatus4.orphanObjects.push(objectRef);}(_this$errorsMap$get=this.errorsMap.get(key))===null||_this$errorsMap$get===void 0?void 0:_this$errorsMap$get.forEach(function(e){var _node$healthStatus;(_node$healthStatus=node.healthStatus)===null||_node$healthStatus===void 0?void 0:_node$healthStatus.errors.push(e);});(_this$warningsMap$get=this.warningsMap.get(key))===null||_this$warningsMap$get===void 0?void 0:_this$warningsMap$get.forEach(function(e){var _node$healthStatus2;(_node$healthStatus2=node.healthStatus)===null||_node$healthStatus2===void 0?void 0:_node$healthStatus2.warnings.push(e);});parentNode.pushChild(node);return node;}},{key:"buildDeletedOrOrphanNode",value:function buildDeletedOrOrphanNode(parentNode,deleted,refs){var _this5=this;var idType=deleted?"deleted":"orphaned";var newId="".concat(parentNode.id,"/(").concat(idType,")");var node=new DeletedOrOrphanObjectsCollectionNode(this.props,newId,deleted);refs.forEach(function(ref){_this5.buildObjectNode(node,ref);});parentNode.pushChild(node,true);return node;}}]);return NodeBuilder;}();function buildObjectRefKey(ref){return[ref.group,ref.version,ref.kind,ref.namespace,ref.name].join("+");} +;// CONCATENATED MODULE: ./src/components/targets-view/HistoryCards.tsx +function doGetRootNode(_x,_x2){return _doGetRootNode.apply(this,arguments);}function _doGetRootNode(){_doGetRootNode=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee(api,rs){var shortNames,r,builder,_builder$buildRoot,_builder$buildRoot2,node;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee$(_context){while(1)switch(_context.prev=_context.next){case 0:_context.next=2;return api.getShortNames();case 2:shortNames=_context.sent;_context.next=5;return api.getResult(rs.id);case 5:r=_context.sent;builder=new NodeBuilder({shortNames:shortNames,summary:rs,commandResult:r});_builder$buildRoot=builder.buildRoot(),_builder$buildRoot2=slicedToArray_slicedToArray(_builder$buildRoot,1),node=_builder$buildRoot2[0];return _context.abrupt("return",node);case 9:case"end":return _context.stop();}},_callee);}));return _doGetRootNode.apply(this,arguments);}var CardContent=/*#__PURE__*/react.memo(function(props){var _useSidePanelTabs=useSidePanelTabs(props.provider),tabs=_useSidePanelTabs.tabs,selectedTab=_useSidePanelTabs.selectedTab,handleTabChange=_useSidePanelTabs.handleTabChange;if(!props.provider||!selectedTab||!tabs.find(function(x){return x.label===selectedTab;})){return null;}return/*#__PURE__*/(0,jsx_runtime.jsxs)(TabContext,{value:selectedTab,children:[/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{height:"36px",flex:"0 0 auto",p:"0 30px",mt:"12px",children:/*#__PURE__*/(0,jsx_runtime.jsx)(TabList_TabList,{onChange:handleTabChange,children:tabs.map(function(tab,i){return/*#__PURE__*/(0,jsx_runtime.jsx)(Tab_Tab,{label:tab.label,value:tab.label},tab.label);})})}),/*#__PURE__*/(0,jsx_runtime.jsx)(Divider_Divider,{sx:{margin:0}}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{overflow:"auto",p:"30px",children:tabs.map(function(tab){return/*#__PURE__*/(0,jsx_runtime.jsx)(TabPanel_TabPanel,{value:tab.label,sx:{padding:0},children:tab.content},tab.label);})})]});});var ArrowButton=/*#__PURE__*/react.memo(function(props){var Icon={left:TriangleLeftLightIcon,right:TriangleRightLightIcon}[props.direction];return/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{flex:"0 0 auto",height:"100%",width:"80px",display:"flex",justifyContent:"center",alignItems:"center",children:!props.hidden&&/*#__PURE__*/(0,jsx_runtime.jsx)(IconButton_IconButton,{onClick:props.onClick,children:/*#__PURE__*/(0,jsx_runtime.jsx)(Icon,{})})});});var HistoryCard=/*#__PURE__*/react.memo(function(props){var navigate=dist_useNavigate();var api=(0,react.useContext)(ApiContext);var _useLoadingHelper=useLoadingHelper(function(){return doGetRootNode(api,props.rs);},[api,props.rs]),_useLoadingHelper2=slicedToArray_slicedToArray(_useLoadingHelper,3),loading=_useLoadingHelper2[0],loadingError=_useLoadingHelper2[1],node=_useLoadingHelper2[2];if(loadingError){return/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{children:"Error"});}return/*#__PURE__*/(0,jsx_runtime.jsxs)(CardPaper,{sx:_objectSpread2({position:'relative'},props.sx),children:[/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{position:"absolute",right:"10px",top:"10px",children:props.transitionFinished&&/*#__PURE__*/(0,jsx_runtime.jsx)(IconButton_IconButton,{onClick:props.onClose,children:/*#__PURE__*/(0,jsx_runtime.jsx)(CloseLightIcon,{})})}),/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",flexDirection:"column",height:"100%",justifyContent:"space-between",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{p:"0 16px",flex:"0 0 auto",children:/*#__PURE__*/(0,jsx_runtime.jsx)(CommandResultItemHeader,{rs:props.rs})}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{width:"100%",flex:"1 1 auto",overflow:"hidden",display:"flex",flexDirection:"column",children:props.transitionFinished&&(loading?/*#__PURE__*/(0,jsx_runtime.jsx)(Loading,{}):/*#__PURE__*/(0,jsx_runtime.jsx)(CardContent,{provider:node}))}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{flex:"0 0 auto",height:"39px",display:"flex",alignItems:"center",justifyContent:"end",p:"0 30px",children:/*#__PURE__*/(0,jsx_runtime.jsx)(IconButton_IconButton,{onClick:function onClick(e){e.stopPropagation();navigate("/results/".concat(props.rs.id));},sx:{padding:0,width:32,height:32},children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:"Open Result Tree",children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",children:/*#__PURE__*/(0,jsx_runtime.jsx)(TreeViewIcon,{})})})})})]})]});});var HistoryCards=/*#__PURE__*/react.memo(function(props){var theme=styles_useTheme_useTheme();var containerElem=(0,react.useRef)();var _useState=(0,react.useState)(),_useState2=slicedToArray_slicedToArray(_useState,2),cardRect=_useState2[0],setCardRect=_useState2[1];var _useState3=(0,react.useState)('not-started'),_useState4=slicedToArray_slicedToArray(_useState3,2),transitionStatus=_useState4[0],setTransitionStatus=_useState4[1];var _useState5=(0,react.useState)(props.rs),_useState6=slicedToArray_slicedToArray(_useState5,2),currentRS=_useState6[0],setCurrentRS=_useState6[1];(0,react.useEffect)(function(){var _containerElem$curren;var rect=(_containerElem$curren=containerElem.current)===null||_containerElem$curren===void 0?void 0:_containerElem$curren.getBoundingClientRect();if(!rect){setCardRect(undefined);return;}var initialRect={left:props.initialCardRect.left-rect.left,top:props.initialCardRect.top-rect.top,width:cardWidth,height:cardHeight};setCardRect(initialRect);},[props.initialCardRect]);(0,react.useEffect)(function(){if(!cardRect){return;}var targetRect={left:0,top:0,width:'100%',height:'100%'};if(cardRect.left===targetRect.left&&cardRect.top===targetRect.top&&cardRect.width===targetRect.width&&cardRect.height===targetRect.height){return;}setTimeout(function(){setCardRect(targetRect);setTransitionStatus('running');setTimeout(function(){setTransitionStatus('finished');},theme.transitions.duration.enteringScreen);},10);},[cardRect,theme.transitions.duration.enteringScreen]);var currentRSIndex=(0,react.useMemo)(function(){return props.ts.commandResults.indexOf(currentRS);},[currentRS,props.ts.commandResults]);var onLeftArrowClick=(0,react.useCallback)(function(){if(currentRSIndex>0){setCurrentRS(props.ts.commandResults[currentRSIndex-1]);}},[currentRSIndex,props.ts.commandResults]);var onRightArrowClick=(0,react.useCallback)(function(){if(currentRSIndex + + diff --git a/pkg/webui/ui/build/static/media/triangle-left-light.svg b/pkg/webui/ui/build/static/media/triangle-left-light.svg new file mode 100644 index 000000000..6731d308d --- /dev/null +++ b/pkg/webui/ui/build/static/media/triangle-left-light.svg @@ -0,0 +1,4 @@ + + + + diff --git a/pkg/webui/ui/build/static/media/triangle-right-light.svg b/pkg/webui/ui/build/static/media/triangle-right-light.svg new file mode 100644 index 000000000..500b3f1e8 --- /dev/null +++ b/pkg/webui/ui/build/static/media/triangle-right-light.svg @@ -0,0 +1,4 @@ + + + + From 1b14e82beac14795a65aa4d58ada0543a84e0150 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 15 Jun 2023 09:12:35 +0200 Subject: [PATCH 1842/2916] feat: Enable writing of command results by default (#580) * feat: Enable writing of command results by default * tests: Don't pass --write-command-result --- cmd/kluctl/args/project.go | 2 +- docs/reference/commands/common-arguments.md | 3 ++- e2e/gitops_test.go | 1 - e2e/results_test.go | 8 ++++---- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cmd/kluctl/args/project.go b/cmd/kluctl/args/project.go index 880426843..b09afefbb 100644 --- a/cmd/kluctl/args/project.go +++ b/cmd/kluctl/args/project.go @@ -44,7 +44,7 @@ type TargetFlags struct { } type CommandResultFlags struct { - WriteCommandResult bool `group:"results" help:"Enable writing of command results into the cluster."` + WriteCommandResult bool `group:"results" help:"Enable writing of command results into the cluster. This is enabled by default." default:"true"` ForceWriteCommandResult bool `group:"results" help:"Force writing of command results, even if the command is run in dry-run mode."` CommandResultNamespace string `group:"results" help:"Override the namespace to be used when writing command results." default:"kluctl-results"` KeepCommandResultsCount int `group:"results" help:"Configure how many old command results to keep." default:"10"` diff --git a/docs/reference/commands/common-arguments.md b/docs/reference/commands/common-arguments.md index 192cf6433..8a25add90 100644 --- a/docs/reference/commands/common-arguments.md +++ b/docs/reference/commands/common-arguments.md @@ -138,7 +138,8 @@ Command Results: "kluctl-results") --force-write-command-result Force writing of command results, even if the command is run in dry-run mode. --keep-command-results-count int Configure how many old command results to keep. (default 10) - --write-command-result Enable writing of command results into the cluster. + --write-command-result Enable writing of command results into the cluster. This is enabled by + default. (default true) ``` diff --git a/e2e/gitops_test.go b/e2e/gitops_test.go index 72d4dc89f..a9c5d1c10 100644 --- a/e2e/gitops_test.go +++ b/e2e/gitops_test.go @@ -101,7 +101,6 @@ func (suite *GitopsTestSuite) startController() { tmpKubeconfig, "--context", "context1", - "--write-command-result", } done := make(chan struct{}) go func() { diff --git a/e2e/results_test.go b/e2e/results_test.go index ae859d7c8..2bcd243fb 100644 --- a/e2e/results_test.go +++ b/e2e/results_test.go @@ -39,7 +39,7 @@ func TestWriteResult(t *testing.T) { name: "cm", namespace: p.TestSlug(), }) - p.KluctlMust("deploy", "--yes", "-t", "test", "--write-command-result") + p.KluctlMust("deploy", "--yes", "-t", "test") assertConfigMapExists(t, k, p.TestSlug(), "cm") rs, err := results.NewResultStoreSecrets(context.Background(), k.Client, "kluctl-results", 0) @@ -67,7 +67,7 @@ func TestWriteResult(t *testing.T) { _ = o.SetNestedField("v2", "data", "d1") return nil }, "") - p.KluctlMust("deploy", "--yes", "-t", "test", "--write-command-result") + p.KluctlMust("deploy", "--yes", "-t", "test") assertConfigMapExists(t, k, p.TestSlug(), "cm2") summaries, err = rs.ListCommandResultSummaries(opts) @@ -84,7 +84,7 @@ func TestWriteResult(t *testing.T) { _ = o.RemoveNestedField("deployments", 1) return nil }) - p.KluctlMust("deploy", "--yes", "-t", "test", "--write-command-result") + p.KluctlMust("deploy", "--yes", "-t", "test") assertConfigMapExists(t, k, p.TestSlug(), "cm2") summaries, err = rs.ListCommandResultSummaries(opts) @@ -95,7 +95,7 @@ func TestWriteResult(t *testing.T) { OrphanObjects: 1, }, summaries[0]) - p.KluctlMust("prune", "--yes", "-t", "test", "--write-command-result") + p.KluctlMust("prune", "--yes", "-t", "test") assertConfigMapNotExists(t, k, p.TestSlug(), "cm2") summaries, err = rs.ListCommandResultSummaries(opts) From b31683b6b0609840a32251dad2355d0fa04264dc Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 15 Jun 2023 11:18:58 +0200 Subject: [PATCH 1843/2916] fix: Add missing service to webui deployment --- install/webui/webui/deployment.yaml | 14 +++++++++----- install/webui/webui/service.yaml | 13 +++++++++++++ 2 files changed, 22 insertions(+), 5 deletions(-) create mode 100644 install/webui/webui/service.yaml diff --git a/install/webui/webui/deployment.yaml b/install/webui/webui/deployment.yaml index 109a6eea4..dedf908b5 100644 --- a/install/webui/webui/deployment.yaml +++ b/install/webui/webui/deployment.yaml @@ -5,10 +5,10 @@ apiVersion: apps/v1 kind: Deployment metadata: labels: - app.kubernetes.io/component: kluctl-webui - app.kubernetes.io/instance: kluctl-controller - app.kubernetes.io/managed-by: kluctl + app.kubernetes.io/component: deployment + app.kubernetes.io/instance: kluctl-webui app.kubernetes.io/name: kluctl-webui + app.kubernetes.io/managed-by: kluctl control-plane: kluctl-webui name: kluctl-webui namespace: kluctl-system @@ -16,11 +16,15 @@ spec: replicas: 1 selector: matchLabels: - control-plane: kluctl-controller + app.kubernetes.io/component: deployment + app.kubernetes.io/instance: kluctl-webui + app.kubernetes.io/name: kluctl-webui template: metadata: labels: - control-plane: kluctl-controller + app.kubernetes.io/component: deployment + app.kubernetes.io/instance: kluctl-webui + app.kubernetes.io/name: kluctl-webui spec: containers: - name: webui diff --git a/install/webui/webui/service.yaml b/install/webui/webui/service.yaml new file mode 100644 index 000000000..4c28ef2c4 --- /dev/null +++ b/install/webui/webui/service.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Service +metadata: + name: kluctl-webui + namespace: kluctl-system +spec: + ports: + - port: 8080 + targetPort: 8080 + selector: + app.kubernetes.io/component: deployment + app.kubernetes.io/instance: kluctl-webui + app.kubernetes.io/name: kluctl-webui From fc33f588ac83e8329a6febca97699796c2342a0d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 15 Jun 2023 11:50:24 +0200 Subject: [PATCH 1844/2916] fix: Smaller initialDelaySeconds in webui deployment --- install/webui/webui/deployment.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/webui/webui/deployment.yaml b/install/webui/webui/deployment.yaml index dedf908b5..f50c31b72 100644 --- a/install/webui/webui/deployment.yaml +++ b/install/webui/webui/deployment.yaml @@ -43,7 +43,7 @@ spec: httpGet: path: / port: 8080 - initialDelaySeconds: 15 + initialDelaySeconds: 5 periodSeconds: 20 readinessProbe: httpGet: From ac4d71d3c289ef1078b8c49a3c4929b156958234 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 15 Jun 2023 11:25:25 +0200 Subject: [PATCH 1845/2916] fix: Use wss protocol when needed --- pkg/webui/ui/build/index.html | 2 +- pkg/webui/ui/build/static/js/main.js | 4 ++-- pkg/webui/ui/src/api.tsx | 6 +++++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/pkg/webui/ui/build/index.html b/pkg/webui/ui/build/index.html index 95134f6bf..31e223a49 100644 --- a/pkg/webui/ui/build/index.html +++ b/pkg/webui/ui/build/index.html @@ -1 +1 @@ -React App
    \ No newline at end of file +React App
    \ No newline at end of file diff --git a/pkg/webui/ui/build/static/js/main.js b/pkg/webui/ui/build/static/js/main.js index 361e5411f..882a0ead2 100644 --- a/pkg/webui/ui/build/static/js/main.js +++ b/pkg/webui/ui/build/static/js/main.js @@ -52128,7 +52128,7 @@ var loadedScripts=new Map();var loadScript=function loadScript(fileUrl){var asyn ;// CONCATENATED MODULE: ./src/utils/misc.ts function getLastPathElement(url){if(url===undefined){return undefined;}if(!url){return"";}var s=url.split("/");return s[s.length-1];}function sleep(ms){return new Promise(function(resolve){return setTimeout(resolve,ms);});} ;// CONCATENATED MODULE: ./src/api.tsx -var staticPath="./staticdata";var ObjectType=/*#__PURE__*/function(ObjectType){ObjectType["Rendered"]="rendered";ObjectType["Remote"]="remote";ObjectType["Applied"]="applied";return ObjectType;}({});function checkStaticBuild(){return _checkStaticBuild.apply(this,arguments);}function _checkStaticBuild(){_checkStaticBuild=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee19(){var p;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee19$(_context19){while(1)switch(_context19.prev=_context19.next){case 0:p=loadScript(staticPath+"/summaries.js");_context19.prev=1;_context19.next=4;return p;case 4:return _context19.abrupt("return",true);case 7:_context19.prev=7;_context19.t0=_context19["catch"](1);return _context19.abrupt("return",false);case 10:case"end":return _context19.stop();}},_callee19,null,[[1,7]]);}));return _checkStaticBuild.apply(this,arguments);}var RealApi=/*#__PURE__*/function(){function RealApi(getToken,onUnauthorized,onTokenRefresh){classCallCheck_classCallCheck(this,RealApi);this.getToken=void 0;this.onUnauthorized=void 0;this.onTokenRefresh=void 0;this.getToken=getToken;this.onUnauthorized=onUnauthorized;this.onTokenRefresh=onTokenRefresh;}createClass_createClass(RealApi,[{key:"setAuthorizationHeader",value:function setAuthorizationHeader(headers){if(!this.getToken){return;}headers['Authorization']="Bearer "+this.getToken();}},{key:"refreshToken",value:function(){var _refreshToken=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee(){var headers,resp,j;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee$(_context){while(1)switch(_context.prev=_context.next){case 0:headers={'Accept':'application/json'};this.setAuthorizationHeader(headers);_context.next=4;return fetch("/auth/refresh",{method:"POST",headers:headers});case 4:resp=_context.sent;if(!(resp.status===401)){_context.next=8;break;}if(this.onUnauthorized){this.onUnauthorized();}throw Error(resp.statusText);case 8:_context.next=10;return resp.json();case 10:j=_context.sent;if(this.onTokenRefresh){this.onTokenRefresh(j.token);}case 12:case"end":return _context.stop();}},_callee,this);}));function refreshToken(){return _refreshToken.apply(this,arguments);}return refreshToken;}()},{key:"handleErrors",value:function(){var _handleErrors=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee2(response,retry){var newResp;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee2$(_context2){while(1)switch(_context2.prev=_context2.next){case 0:if(response.ok){_context2.next=16;break;}if(!(response.status===401)){_context2.next=15;break;}if(!(this.getToken&&retry)){_context2.next=14;break;}console.log("retrying with token refresh");_context2.next=6;return this.refreshToken();case 6:_context2.next=8;return retry();case 8:newResp=_context2.sent;_context2.next=11;return this.handleErrors(newResp);case 11:return _context2.abrupt("return",_context2.sent);case 14:if(this.onUnauthorized){this.onUnauthorized();}case 15:throw Error(response.statusText);case 16:return _context2.abrupt("return",response);case 17:case"end":return _context2.stop();}},_callee2,this);}));function handleErrors(_x,_x2){return _handleErrors.apply(this,arguments);}return handleErrors;}()},{key:"doGet",value:function(){var _doGet=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee3(path,params){var _this=this;var url,doFetch,resp;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee3$(_context3){while(1)switch(_context3.prev=_context3.next){case 0:url=path;if(params){url+="?"+params.toString();}doFetch=function doFetch(){var headers={'Accept':'application/json'};_this.setAuthorizationHeader(headers);return fetch(url,{method:"GET",headers:headers});};_context3.next=5;return doFetch();case 5:resp=_context3.sent;_context3.next=8;return this.handleErrors(resp,doFetch);case 8:resp=_context3.sent;return _context3.abrupt("return",resp.json());case 10:case"end":return _context3.stop();}},_callee3,this);}));function doGet(_x3,_x4){return _doGet.apply(this,arguments);}return doGet;}()},{key:"doPost",value:function(){var _doPost=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee4(path,body){var _this2=this;var url,doFetch,resp;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee4$(_context4){while(1)switch(_context4.prev=_context4.next){case 0:url=path;doFetch=function doFetch(){var headers={'Accept':'application/json','Content-Type':'application/json'};_this2.setAuthorizationHeader(headers);return fetch(url,{method:"POST",body:JSON.stringify(body),headers:headers});};_context4.next=4;return doFetch();case 4:resp=_context4.sent;_context4.next=7;return this.handleErrors(resp,doFetch);case 7:resp=_context4.sent;return _context4.abrupt("return",resp);case 9:case"end":return _context4.stop();}},_callee4,this);}));function doPost(_x5,_x6){return _doPost.apply(this,arguments);}return doPost;}()},{key:"getShortNames",value:function(){var _getShortNames=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee5(){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee5$(_context5){while(1)switch(_context5.prev=_context5.next){case 0:return _context5.abrupt("return",this.doGet("/api/getShortNames"));case 1:case"end":return _context5.stop();}},_callee5,this);}));function getShortNames(){return _getShortNames.apply(this,arguments);}return getShortNames;}()},{key:"listenUpdates",value:function(){var _listenUpdates=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee7(filterProject,filterSubDir,handle){var host,url,params,getToken,ws,cancelled,connect;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee7$(_context7){while(1)switch(_context7.prev=_context7.next){case 0:host=window.location.host;if(false){}url="ws://".concat(host,"/api/ws");params=new URLSearchParams();if(filterProject){params.set("filterProject",filterProject);}if(filterSubDir){params.set("filterSubDir",filterSubDir);}url+="?"+params.toString();getToken=this.getToken;cancelled=false;connect=/*#__PURE__*/function(){var _ref=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee6(){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee6$(_context6){while(1)switch(_context6.prev=_context6.next){case 0:if(!cancelled){_context6.next=2;break;}return _context6.abrupt("return");case 2:console.log("ws connect: "+url);ws=new WebSocket(url);ws.onopen=function(){console.log("ws connected");if(getToken){ws.send(JSON.stringify({"type":"auth","token":getToken()}));}};ws.onclose=function(event){console.log("ws close");if(!cancelled){sleep(5000).then(connect);}};ws.onerror=function(event){console.log("ws error",event);};ws.onmessage=function(event){if(cancelled){return;}var msg=JSON.parse(event.data);handle(msg);};case 8:case"end":return _context6.stop();}},_callee6);}));return function connect(){return _ref.apply(this,arguments);};}();_context7.next=12;return connect();case 12:return _context7.abrupt("return",function(){console.log("ws cancel");cancelled=true;if(ws){ws.close();}});case 13:case"end":return _context7.stop();}},_callee7,this);}));function listenUpdates(_x7,_x8,_x9){return _listenUpdates.apply(this,arguments);}return listenUpdates;}()},{key:"getResult",value:function(){var _getResult=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee8(resultId){var params,json;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee8$(_context8){while(1)switch(_context8.prev=_context8.next){case 0:params=new URLSearchParams();params.set("resultId",resultId);_context8.next=4;return this.doGet("/api/getResult",params);case 4:json=_context8.sent;return _context8.abrupt("return",new CommandResult(json));case 6:case"end":return _context8.stop();}},_callee8,this);}));function getResult(_x10){return _getResult.apply(this,arguments);}return getResult;}()},{key:"getResultSummary",value:function(){var _getResultSummary=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee9(resultId){var params,json;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee9$(_context9){while(1)switch(_context9.prev=_context9.next){case 0:params=new URLSearchParams();params.set("resultId",resultId);_context9.next=4;return this.doGet("/api/getResultSummary",params);case 4:json=_context9.sent;return _context9.abrupt("return",new CommandResultSummary(json));case 6:case"end":return _context9.stop();}},_callee9,this);}));function getResultSummary(_x11){return _getResultSummary.apply(this,arguments);}return getResultSummary;}()},{key:"getResultObject",value:function(){var _getResultObject=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee10(resultId,ref,objectType){var params;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee10$(_context10){while(1)switch(_context10.prev=_context10.next){case 0:params=new URLSearchParams();params.set("resultId",resultId);params.set("objectType",objectType);buildRefParams(ref,params);_context10.next=6;return this.doGet("/api/getResultObject",params);case 6:return _context10.abrupt("return",_context10.sent);case 7:case"end":return _context10.stop();}},_callee10,this);}));function getResultObject(_x12,_x13,_x14){return _getResultObject.apply(this,arguments);}return getResultObject;}()},{key:"validateNow",value:function(){var _validateNow=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee11(project,target){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee11$(_context11){while(1)switch(_context11.prev=_context11.next){case 0:return _context11.abrupt("return",this.doPost("/api/validateNow",{"project":project,"target":target}));case 1:case"end":return _context11.stop();}},_callee11,this);}));function validateNow(_x15,_x16){return _validateNow.apply(this,arguments);}return validateNow;}()},{key:"deployNow",value:function(){var _deployNow=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee12(cluster,name,namespace){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee12$(_context12){while(1)switch(_context12.prev=_context12.next){case 0:return _context12.abrupt("return",this.doPost("/api/deployNow",{"cluster":cluster,"name":name,"namespace":namespace}));case 1:case"end":return _context12.stop();}},_callee12,this);}));function deployNow(_x17,_x18,_x19){return _deployNow.apply(this,arguments);}return deployNow;}()},{key:"reconcileNow",value:function(){var _reconcileNow=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee13(cluster,name,namespace){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee13$(_context13){while(1)switch(_context13.prev=_context13.next){case 0:return _context13.abrupt("return",this.doPost("/api/reconcileNow",{"cluster":cluster,"name":name,"namespace":namespace}));case 1:case"end":return _context13.stop();}},_callee13,this);}));function reconcileNow(_x20,_x21,_x22){return _reconcileNow.apply(this,arguments);}return reconcileNow;}()}]);return RealApi;}();var StaticApi=/*#__PURE__*/function(){function StaticApi(){classCallCheck_classCallCheck(this,StaticApi);}createClass_createClass(StaticApi,[{key:"getShortNames",value:function(){var _getShortNames2=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee14(){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee14$(_context14){while(1)switch(_context14.prev=_context14.next){case 0:_context14.next=2;return loadScript(staticPath+"/shortnames.js");case 2:return _context14.abrupt("return",staticShortNames);case 3:case"end":return _context14.stop();}},_callee14);}));function getShortNames(){return _getShortNames2.apply(this,arguments);}return getShortNames;}()},{key:"listenUpdates",value:function(){var _listenUpdates2=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee15(filterProject,filterSubDir,handle){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee15$(_context15){while(1)switch(_context15.prev=_context15.next){case 0:_context15.next=2;return loadScript(staticPath+"/summaries.js");case 2:staticSummaries.forEach(function(rs){if(filterProject&&filterProject!==rs.project.normalizedGitUrl){return;}if(filterSubDir&&filterSubDir!==rs.project.subDir){return;}handle({"type":"update_summary","summary":rs});});return _context15.abrupt("return",function(){});case 4:case"end":return _context15.stop();}},_callee15);}));function listenUpdates(_x23,_x24,_x25){return _listenUpdates2.apply(this,arguments);}return listenUpdates;}()},{key:"getResult",value:function(){var _getResult2=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee16(resultId){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee16$(_context16){while(1)switch(_context16.prev=_context16.next){case 0:_context16.next=2;return loadScript(staticPath+"/result-".concat(resultId,".js"));case 2:return _context16.abrupt("return",staticResults.get(resultId));case 3:case"end":return _context16.stop();}},_callee16);}));function getResult(_x26){return _getResult2.apply(this,arguments);}return getResult;}()},{key:"getResultSummary",value:function(){var _getResultSummary2=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee17(resultId){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee17$(_context17){while(1)switch(_context17.prev=_context17.next){case 0:_context17.next=2;return loadScript(staticPath+"/summaries.js");case 2:return _context17.abrupt("return",staticSummaries.filter(function(s){return s.id===resultId;}).at(0));case 3:case"end":return _context17.stop();}},_callee17);}));function getResultSummary(_x27){return _getResultSummary2.apply(this,arguments);}return getResultSummary;}()},{key:"getResultObject",value:function(){var _getResultObject2=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee18(resultId,ref,objectType){var _result$objects;var result,object;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee18$(_context18){while(1)switch(_context18.prev=_context18.next){case 0:_context18.next=2;return this.getResult(resultId);case 2:result=_context18.sent;object=(_result$objects=result.objects)===null||_result$objects===void 0?void 0:_result$objects.find(function(x){return lodash_default().isEqual(x.ref,ref);});if(object){_context18.next=6;break;}throw new Error("object not found");case 6:_context18.t0=objectType;_context18.next=_context18.t0===ObjectType.Rendered?9:_context18.t0===ObjectType.Remote?10:_context18.t0===ObjectType.Applied?11:12;break;case 9:return _context18.abrupt("return",object.rendered);case 10:return _context18.abrupt("return",object.remote);case 11:return _context18.abrupt("return",object.applied);case 12:throw new Error("unknown object type "+objectType);case 13:case"end":return _context18.stop();}},_callee18,this);}));function getResultObject(_x28,_x29,_x30){return _getResultObject2.apply(this,arguments);}return getResultObject;}()},{key:"validateNow",value:function validateNow(project,target){throw new Error("not implemented");}},{key:"reconcileNow",value:function reconcileNow(cluster,name,namespace){throw new Error("not implemented");}},{key:"deployNow",value:function deployNow(cluster,name,namespace){throw new Error("not implemented");}}]);return StaticApi;}();function buildRefParams(ref,params){params.set("kind",ref.kind);params.set("name",ref.name);if(ref.group){params.set("group",ref.group);}if(ref.version){params.set("version",ref.version);}if(ref.namespace){params.set("namespace",ref.namespace);}}function buildRefString(ref){if(ref.namespace){return"".concat(ref.namespace,"/").concat(ref.kind,"/").concat(ref.name);}else{if(ref.name){return"".concat(ref.kind,"/").concat(ref.name);}else{return ref.kind;}}}function buildRefKindElement(ref,element){var tooltip=/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{zIndex:1000,children:[/*#__PURE__*/(0,jsx_runtime.jsxs)(Typography_Typography,{children:["ApiVersion: ",[ref.group,ref.version].filter(function(x){return x;}).join("/")]}),/*#__PURE__*/(0,jsx_runtime.jsx)("br",{}),/*#__PURE__*/(0,jsx_runtime.jsxs)(Typography_Typography,{children:["Kind: ",ref.kind]})]});return/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:tooltip,children:element?element:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{children:ref.kind})});}function buildObjectRefFromObject(obj){var apiVersion=obj.apiVersion;var s=apiVersion.split("/",2);var ref=new ObjectRef();if(s.length===1){ref.version=s[0];}else{ref.group=s[0];ref.version=s[1];}ref.kind=obj.kind;ref.namespace=obj.metadata.namespace;ref.name=obj.metadata.name;return ref;}function findObjectByRef(l,ref,filter){return l===null||l===void 0?void 0:l.find(function(x){if(filter&&!filter(x)){return false;}return lodash_default().isEqual(x.ref,ref);});} +var staticPath="./staticdata";var ObjectType=/*#__PURE__*/function(ObjectType){ObjectType["Rendered"]="rendered";ObjectType["Remote"]="remote";ObjectType["Applied"]="applied";return ObjectType;}({});function checkStaticBuild(){return _checkStaticBuild.apply(this,arguments);}function _checkStaticBuild(){_checkStaticBuild=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee19(){var p;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee19$(_context19){while(1)switch(_context19.prev=_context19.next){case 0:p=loadScript(staticPath+"/summaries.js");_context19.prev=1;_context19.next=4;return p;case 4:return _context19.abrupt("return",true);case 7:_context19.prev=7;_context19.t0=_context19["catch"](1);return _context19.abrupt("return",false);case 10:case"end":return _context19.stop();}},_callee19,null,[[1,7]]);}));return _checkStaticBuild.apply(this,arguments);}var RealApi=/*#__PURE__*/function(){function RealApi(getToken,onUnauthorized,onTokenRefresh){classCallCheck_classCallCheck(this,RealApi);this.getToken=void 0;this.onUnauthorized=void 0;this.onTokenRefresh=void 0;this.getToken=getToken;this.onUnauthorized=onUnauthorized;this.onTokenRefresh=onTokenRefresh;}createClass_createClass(RealApi,[{key:"setAuthorizationHeader",value:function setAuthorizationHeader(headers){if(!this.getToken){return;}headers['Authorization']="Bearer "+this.getToken();}},{key:"refreshToken",value:function(){var _refreshToken=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee(){var headers,resp,j;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee$(_context){while(1)switch(_context.prev=_context.next){case 0:headers={'Accept':'application/json'};this.setAuthorizationHeader(headers);_context.next=4;return fetch("/auth/refresh",{method:"POST",headers:headers});case 4:resp=_context.sent;if(!(resp.status===401)){_context.next=8;break;}if(this.onUnauthorized){this.onUnauthorized();}throw Error(resp.statusText);case 8:_context.next=10;return resp.json();case 10:j=_context.sent;if(this.onTokenRefresh){this.onTokenRefresh(j.token);}case 12:case"end":return _context.stop();}},_callee,this);}));function refreshToken(){return _refreshToken.apply(this,arguments);}return refreshToken;}()},{key:"handleErrors",value:function(){var _handleErrors=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee2(response,retry){var newResp;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee2$(_context2){while(1)switch(_context2.prev=_context2.next){case 0:if(response.ok){_context2.next=16;break;}if(!(response.status===401)){_context2.next=15;break;}if(!(this.getToken&&retry)){_context2.next=14;break;}console.log("retrying with token refresh");_context2.next=6;return this.refreshToken();case 6:_context2.next=8;return retry();case 8:newResp=_context2.sent;_context2.next=11;return this.handleErrors(newResp);case 11:return _context2.abrupt("return",_context2.sent);case 14:if(this.onUnauthorized){this.onUnauthorized();}case 15:throw Error(response.statusText);case 16:return _context2.abrupt("return",response);case 17:case"end":return _context2.stop();}},_callee2,this);}));function handleErrors(_x,_x2){return _handleErrors.apply(this,arguments);}return handleErrors;}()},{key:"doGet",value:function(){var _doGet=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee3(path,params){var _this=this;var url,doFetch,resp;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee3$(_context3){while(1)switch(_context3.prev=_context3.next){case 0:url=path;if(params){url+="?"+params.toString();}doFetch=function doFetch(){var headers={'Accept':'application/json'};_this.setAuthorizationHeader(headers);return fetch(url,{method:"GET",headers:headers});};_context3.next=5;return doFetch();case 5:resp=_context3.sent;_context3.next=8;return this.handleErrors(resp,doFetch);case 8:resp=_context3.sent;return _context3.abrupt("return",resp.json());case 10:case"end":return _context3.stop();}},_callee3,this);}));function doGet(_x3,_x4){return _doGet.apply(this,arguments);}return doGet;}()},{key:"doPost",value:function(){var _doPost=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee4(path,body){var _this2=this;var url,doFetch,resp;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee4$(_context4){while(1)switch(_context4.prev=_context4.next){case 0:url=path;doFetch=function doFetch(){var headers={'Accept':'application/json','Content-Type':'application/json'};_this2.setAuthorizationHeader(headers);return fetch(url,{method:"POST",body:JSON.stringify(body),headers:headers});};_context4.next=4;return doFetch();case 4:resp=_context4.sent;_context4.next=7;return this.handleErrors(resp,doFetch);case 7:resp=_context4.sent;return _context4.abrupt("return",resp);case 9:case"end":return _context4.stop();}},_callee4,this);}));function doPost(_x5,_x6){return _doPost.apply(this,arguments);}return doPost;}()},{key:"getShortNames",value:function(){var _getShortNames=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee5(){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee5$(_context5){while(1)switch(_context5.prev=_context5.next){case 0:return _context5.abrupt("return",this.doGet("/api/getShortNames"));case 1:case"end":return _context5.stop();}},_callee5,this);}));function getShortNames(){return _getShortNames.apply(this,arguments);}return getShortNames;}()},{key:"listenUpdates",value:function(){var _listenUpdates=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee7(filterProject,filterSubDir,handle){var host,proto,url,params,getToken,ws,cancelled,connect;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee7$(_context7){while(1)switch(_context7.prev=_context7.next){case 0:host=window.location.host;proto="wss";if(false){}if(window.location.protocol!=="https:"){proto="ws";}url="".concat(proto,"://").concat(host,"/api/ws");params=new URLSearchParams();if(filterProject){params.set("filterProject",filterProject);}if(filterSubDir){params.set("filterSubDir",filterSubDir);}url+="?"+params.toString();getToken=this.getToken;cancelled=false;connect=/*#__PURE__*/function(){var _ref=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee6(){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee6$(_context6){while(1)switch(_context6.prev=_context6.next){case 0:if(!cancelled){_context6.next=2;break;}return _context6.abrupt("return");case 2:console.log("ws connect: "+url);ws=new WebSocket(url);ws.onopen=function(){console.log("ws connected");if(getToken){ws.send(JSON.stringify({"type":"auth","token":getToken()}));}};ws.onclose=function(event){console.log("ws close");if(!cancelled){sleep(5000).then(connect);}};ws.onerror=function(event){console.log("ws error",event);};ws.onmessage=function(event){if(cancelled){return;}var msg=JSON.parse(event.data);handle(msg);};case 8:case"end":return _context6.stop();}},_callee6);}));return function connect(){return _ref.apply(this,arguments);};}();_context7.next=14;return connect();case 14:return _context7.abrupt("return",function(){console.log("ws cancel");cancelled=true;if(ws){ws.close();}});case 15:case"end":return _context7.stop();}},_callee7,this);}));function listenUpdates(_x7,_x8,_x9){return _listenUpdates.apply(this,arguments);}return listenUpdates;}()},{key:"getResult",value:function(){var _getResult=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee8(resultId){var params,json;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee8$(_context8){while(1)switch(_context8.prev=_context8.next){case 0:params=new URLSearchParams();params.set("resultId",resultId);_context8.next=4;return this.doGet("/api/getResult",params);case 4:json=_context8.sent;return _context8.abrupt("return",new CommandResult(json));case 6:case"end":return _context8.stop();}},_callee8,this);}));function getResult(_x10){return _getResult.apply(this,arguments);}return getResult;}()},{key:"getResultSummary",value:function(){var _getResultSummary=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee9(resultId){var params,json;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee9$(_context9){while(1)switch(_context9.prev=_context9.next){case 0:params=new URLSearchParams();params.set("resultId",resultId);_context9.next=4;return this.doGet("/api/getResultSummary",params);case 4:json=_context9.sent;return _context9.abrupt("return",new CommandResultSummary(json));case 6:case"end":return _context9.stop();}},_callee9,this);}));function getResultSummary(_x11){return _getResultSummary.apply(this,arguments);}return getResultSummary;}()},{key:"getResultObject",value:function(){var _getResultObject=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee10(resultId,ref,objectType){var params;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee10$(_context10){while(1)switch(_context10.prev=_context10.next){case 0:params=new URLSearchParams();params.set("resultId",resultId);params.set("objectType",objectType);buildRefParams(ref,params);_context10.next=6;return this.doGet("/api/getResultObject",params);case 6:return _context10.abrupt("return",_context10.sent);case 7:case"end":return _context10.stop();}},_callee10,this);}));function getResultObject(_x12,_x13,_x14){return _getResultObject.apply(this,arguments);}return getResultObject;}()},{key:"validateNow",value:function(){var _validateNow=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee11(project,target){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee11$(_context11){while(1)switch(_context11.prev=_context11.next){case 0:return _context11.abrupt("return",this.doPost("/api/validateNow",{"project":project,"target":target}));case 1:case"end":return _context11.stop();}},_callee11,this);}));function validateNow(_x15,_x16){return _validateNow.apply(this,arguments);}return validateNow;}()},{key:"deployNow",value:function(){var _deployNow=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee12(cluster,name,namespace){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee12$(_context12){while(1)switch(_context12.prev=_context12.next){case 0:return _context12.abrupt("return",this.doPost("/api/deployNow",{"cluster":cluster,"name":name,"namespace":namespace}));case 1:case"end":return _context12.stop();}},_callee12,this);}));function deployNow(_x17,_x18,_x19){return _deployNow.apply(this,arguments);}return deployNow;}()},{key:"reconcileNow",value:function(){var _reconcileNow=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee13(cluster,name,namespace){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee13$(_context13){while(1)switch(_context13.prev=_context13.next){case 0:return _context13.abrupt("return",this.doPost("/api/reconcileNow",{"cluster":cluster,"name":name,"namespace":namespace}));case 1:case"end":return _context13.stop();}},_callee13,this);}));function reconcileNow(_x20,_x21,_x22){return _reconcileNow.apply(this,arguments);}return reconcileNow;}()}]);return RealApi;}();var StaticApi=/*#__PURE__*/function(){function StaticApi(){classCallCheck_classCallCheck(this,StaticApi);}createClass_createClass(StaticApi,[{key:"getShortNames",value:function(){var _getShortNames2=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee14(){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee14$(_context14){while(1)switch(_context14.prev=_context14.next){case 0:_context14.next=2;return loadScript(staticPath+"/shortnames.js");case 2:return _context14.abrupt("return",staticShortNames);case 3:case"end":return _context14.stop();}},_callee14);}));function getShortNames(){return _getShortNames2.apply(this,arguments);}return getShortNames;}()},{key:"listenUpdates",value:function(){var _listenUpdates2=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee15(filterProject,filterSubDir,handle){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee15$(_context15){while(1)switch(_context15.prev=_context15.next){case 0:_context15.next=2;return loadScript(staticPath+"/summaries.js");case 2:staticSummaries.forEach(function(rs){if(filterProject&&filterProject!==rs.project.normalizedGitUrl){return;}if(filterSubDir&&filterSubDir!==rs.project.subDir){return;}handle({"type":"update_summary","summary":rs});});return _context15.abrupt("return",function(){});case 4:case"end":return _context15.stop();}},_callee15);}));function listenUpdates(_x23,_x24,_x25){return _listenUpdates2.apply(this,arguments);}return listenUpdates;}()},{key:"getResult",value:function(){var _getResult2=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee16(resultId){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee16$(_context16){while(1)switch(_context16.prev=_context16.next){case 0:_context16.next=2;return loadScript(staticPath+"/result-".concat(resultId,".js"));case 2:return _context16.abrupt("return",staticResults.get(resultId));case 3:case"end":return _context16.stop();}},_callee16);}));function getResult(_x26){return _getResult2.apply(this,arguments);}return getResult;}()},{key:"getResultSummary",value:function(){var _getResultSummary2=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee17(resultId){return regeneratorRuntime_regeneratorRuntime().wrap(function _callee17$(_context17){while(1)switch(_context17.prev=_context17.next){case 0:_context17.next=2;return loadScript(staticPath+"/summaries.js");case 2:return _context17.abrupt("return",staticSummaries.filter(function(s){return s.id===resultId;}).at(0));case 3:case"end":return _context17.stop();}},_callee17);}));function getResultSummary(_x27){return _getResultSummary2.apply(this,arguments);}return getResultSummary;}()},{key:"getResultObject",value:function(){var _getResultObject2=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee18(resultId,ref,objectType){var _result$objects;var result,object;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee18$(_context18){while(1)switch(_context18.prev=_context18.next){case 0:_context18.next=2;return this.getResult(resultId);case 2:result=_context18.sent;object=(_result$objects=result.objects)===null||_result$objects===void 0?void 0:_result$objects.find(function(x){return lodash_default().isEqual(x.ref,ref);});if(object){_context18.next=6;break;}throw new Error("object not found");case 6:_context18.t0=objectType;_context18.next=_context18.t0===ObjectType.Rendered?9:_context18.t0===ObjectType.Remote?10:_context18.t0===ObjectType.Applied?11:12;break;case 9:return _context18.abrupt("return",object.rendered);case 10:return _context18.abrupt("return",object.remote);case 11:return _context18.abrupt("return",object.applied);case 12:throw new Error("unknown object type "+objectType);case 13:case"end":return _context18.stop();}},_callee18,this);}));function getResultObject(_x28,_x29,_x30){return _getResultObject2.apply(this,arguments);}return getResultObject;}()},{key:"validateNow",value:function validateNow(project,target){throw new Error("not implemented");}},{key:"reconcileNow",value:function reconcileNow(cluster,name,namespace){throw new Error("not implemented");}},{key:"deployNow",value:function deployNow(cluster,name,namespace){throw new Error("not implemented");}}]);return StaticApi;}();function buildRefParams(ref,params){params.set("kind",ref.kind);params.set("name",ref.name);if(ref.group){params.set("group",ref.group);}if(ref.version){params.set("version",ref.version);}if(ref.namespace){params.set("namespace",ref.namespace);}}function buildRefString(ref){if(ref.namespace){return"".concat(ref.namespace,"/").concat(ref.kind,"/").concat(ref.name);}else{if(ref.name){return"".concat(ref.kind,"/").concat(ref.name);}else{return ref.kind;}}}function buildRefKindElement(ref,element){var tooltip=/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{zIndex:1000,children:[/*#__PURE__*/(0,jsx_runtime.jsxs)(Typography_Typography,{children:["ApiVersion: ",[ref.group,ref.version].filter(function(x){return x;}).join("/")]}),/*#__PURE__*/(0,jsx_runtime.jsx)("br",{}),/*#__PURE__*/(0,jsx_runtime.jsxs)(Typography_Typography,{children:["Kind: ",ref.kind]})]});return/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:tooltip,children:element?element:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{children:ref.kind})});}function buildObjectRefFromObject(obj){var apiVersion=obj.apiVersion;var s=apiVersion.split("/",2);var ref=new ObjectRef();if(s.length===1){ref.version=s[0];}else{ref.group=s[0];ref.version=s[1];}ref.kind=obj.kind;ref.namespace=obj.metadata.namespace;ref.name=obj.metadata.name;return ref;}function findObjectByRef(l,ref,filter){return l===null||l===void 0?void 0:l.find(function(x){if(filter&&!filter(x)){return false;}return lodash_default().isEqual(x.ref,ref);});} ;// CONCATENATED MODULE: ./src/project-summaries.ts function compareSummaries(a,b){return b.commandInfo.startTime.localeCompare(a.commandInfo.startTime)||b.commandInfo.endTime.localeCompare(b.commandInfo.endTime)||(b.commandInfo.command||"").localeCompare(a.commandInfo.command||"");}function buildProjectSummaries(summaries,validateResults){var sorted=Array.from(summaries.values());sorted.sort(compareSummaries);var m=new Map();sorted.forEach(function(rs){var projectKey=JSON.stringify(rs.projectKey);var p=m.get(projectKey);if(!p){p={project:rs.projectKey,targets:[]};m.set(projectKey,p);}var ptKey=new ProjectTargetKey();ptKey.project=rs.projectKey;ptKey.target=rs.targetKey;var vr=validateResults.get(JSON.stringify(ptKey));var target=p.targets.find(function(t){return lodash_default().isEqual(t.target,rs.targetKey);});if(!target){target={target:rs.targetKey,lastValidateResult:vr,commandResults:[]};p.targets.push(target);}target.commandResults.push(rs);});var ret=[];m.forEach(function(p){p.targets.sort(function(a,b){var _ref;return(a.target.targetName||"").localeCompare(b.target.targetName||"")||a.target.clusterId.localeCompare(b.target.clusterId)||((_ref=a.target.discriminator||"")===null||_ref===void 0?void 0:_ref.localeCompare(b.target.discriminator||""));});ret.push(p);});ret.sort(function(a,b){return(a.project.gitRepoKey||"").localeCompare(b.project.gitRepoKey||"")||(a.project.subDir||"").localeCompare(b.project.subDir||"");});return ret;} ;// CONCATENATED MODULE: ./src/components/Login.tsx @@ -61549,4 +61549,4 @@ src_reportWebVitals(); }(); /******/ })() ; -//# sourceMappingURL=main.4093ded8.js.map \ No newline at end of file +//# sourceMappingURL=main.7c75d535.js.map \ No newline at end of file diff --git a/pkg/webui/ui/src/api.tsx b/pkg/webui/ui/src/api.tsx index 2bd092be2..9c325ec86 100644 --- a/pkg/webui/ui/src/api.tsx +++ b/pkg/webui/ui/src/api.tsx @@ -152,10 +152,14 @@ export class RealApi implements Api { async listenUpdates(filterProject: string | undefined, filterSubDir: string | undefined, handle: (msg: any) => void): Promise<() => void> { let host = window.location.host + let proto = "wss" if (process.env.NODE_ENV === 'development') { host = "localhost:9090" } - let url = `ws://${host}/api/ws` + if (window.location.protocol !== "https:") { + proto = "ws" + } + let url = `${proto}://${host}/api/ws` const params = new URLSearchParams() if (filterProject) { params.set("filterProject", filterProject) From 0add46776f2f5ba5f897a6cc67af9ac03638474a Mon Sep 17 00:00:00 2001 From: nett_hier <66856670+netthier@users.noreply.github.com> Date: Thu, 15 Jun 2023 11:52:01 +0200 Subject: [PATCH 1846/2916] fix: Set seccompProfile for controller deployment (#571) Signed-off-by: netthier --- config/manager/manager.yaml | 9 ++------- install/controller/controller/manager.yaml | 2 ++ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index 4700ace09..5115bc02c 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -58,13 +58,8 @@ spec: # - linux securityContext: runAsNonRoot: true - # TODO(user): For common cases that do not require escalating privileges - # it is recommended to ensure that all your Pods/Containers are restrictive. - # More info: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted - # Please uncomment the following code if your project does NOT have to work on old Kubernetes - # versions < 1.19 or on vendors versions which do NOT support this field by default (i.e. Openshift < 4.11 ). - # seccompProfile: - # type: RuntimeDefault + seccompProfile: + type: RuntimeDefault containers: - name: controller image: ghcr.io/kluctl/kluctl:latest diff --git a/install/controller/controller/manager.yaml b/install/controller/controller/manager.yaml index 8ef9d3e81..6fe0ebe3a 100644 --- a/install/controller/controller/manager.yaml +++ b/install/controller/controller/manager.yaml @@ -73,5 +73,7 @@ spec: - ALL securityContext: runAsNonRoot: true + seccompProfile: + type: RuntimeDefault serviceAccountName: kluctl-controller terminationGracePeriodSeconds: 10 From d4fba8fe720de3aa9b08f57ede2df2211efd75e0 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 15 Jun 2023 14:25:20 +0200 Subject: [PATCH 1847/2916] ci: Add warning header to generated manifests (#572) --- Makefile | 9 ++++++--- install/controller/controller/crd.yaml | 1 + install/controller/controller/manager.yaml | 1 + install/controller/controller/rbac.yaml | 1 + 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index c7bea97f0..4023149a4 100644 --- a/Makefile +++ b/Makefile @@ -60,9 +60,12 @@ help: ## Display this help. .PHONY: manifests manifests: controller-gen kustomize ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. $(CONTROLLER_GEN) rbac:roleName=kluctl-controller-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases - $(KUSTOMIZE) build config/crd > install/controller/controller/crd.yaml - $(KUSTOMIZE) build config/rbac > install/controller/controller/rbac.yaml - $(KUSTOMIZE) build config/manager > install/controller/controller/manager.yaml + @echo "# Warning, this file is generated via \"make manifests\", don't edit it directly but instead change the files in config/crd" > install/controller/controller/crd.yaml + $(KUSTOMIZE) build config/crd >> install/controller/controller/crd.yaml + @echo "# Warning, this file is generated via \"make manifests\", don't edit it directly but instead change the files in config/rbac" > install/controller/controller/rbac.yaml + $(KUSTOMIZE) build config/rbac >> install/controller/controller/rbac.yaml + @echo "# Warning, this file is generated via \"make manifests\", don't edit it directly but instead change the files in config/manager" > install/controller/controller/manager.yaml + $(KUSTOMIZE) build config/manager >> install/controller/controller/manager.yaml # Generate API reference documentation api-docs: gen-crd-api-reference-docs diff --git a/install/controller/controller/crd.yaml b/install/controller/controller/crd.yaml index dd1e01c15..0159cc23b 100644 --- a/install/controller/controller/crd.yaml +++ b/install/controller/controller/crd.yaml @@ -1,3 +1,4 @@ +# Warning, this file is generated via "make manifests", don't edit it directly but instead change the files in config/crd apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: diff --git a/install/controller/controller/manager.yaml b/install/controller/controller/manager.yaml index 6fe0ebe3a..acec1201a 100644 --- a/install/controller/controller/manager.yaml +++ b/install/controller/controller/manager.yaml @@ -1,3 +1,4 @@ +# Warning, this file is generated via "make manifests", don't edit it directly but instead change the files in config/manager apiVersion: v1 kind: Namespace metadata: diff --git a/install/controller/controller/rbac.yaml b/install/controller/controller/rbac.yaml index aa7d7bccb..9072415fc 100644 --- a/install/controller/controller/rbac.yaml +++ b/install/controller/controller/rbac.yaml @@ -1,3 +1,4 @@ +# Warning, this file is generated via "make manifests", don't edit it directly but instead change the files in config/rbac apiVersion: v1 kind: ServiceAccount metadata: From 87110b16c399ca4df37d900b84c6d009b78a68da Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 15 Jun 2023 14:51:19 +0200 Subject: [PATCH 1848/2916] fix: Don't treat deleted objects still as orphan (#582) --- pkg/deployment/commands/utils.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/pkg/deployment/commands/utils.go b/pkg/deployment/commands/utils.go index ebb2b08b2..44ca0359c 100644 --- a/pkg/deployment/commands/utils.go +++ b/pkg/deployment/commands/utils.go @@ -86,13 +86,22 @@ func collectObjects(c *deployment.DeploymentCollection, ru *utils.RemoteObjectUt o.New = true } } - for _, x := range orphans { - o := getOrCreate(x) - o.Orphan = true - } + + deletedMap := map[k8s.ObjectRef]bool{} for _, x := range deleted { o := getOrCreate(x) o.Deleted = true + deletedMap[o.Ref] = true + } + for _, x := range orphans { + if _, ok := deletedMap[x]; ok { + // orphan object also got deleted? This can only mean that deletion did not wait for the object to disappear, + // so we should treat this object not as orphan anymore + continue + } + o := getOrCreate(x) + o.Orphan = true + } for ref, o := range m { From acc6d97ee650512078bbf214aac1cf4a895ae241 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 15 Jun 2023 16:50:14 +0200 Subject: [PATCH 1849/2916] fix: Fix snapshot detection and version replacement via prepare-release.sh (#583) --- hack/prepare-release.sh | 1 + install/webui/webui/deployment.yaml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/hack/prepare-release.sh b/hack/prepare-release.sh index b306fb781..380832889 100755 --- a/hack/prepare-release.sh +++ b/hack/prepare-release.sh @@ -28,6 +28,7 @@ FILES="" FILES="$FILES install/controller/.kluctl.yaml" FILES="$FILES install/controller/controller/kustomization.yaml" FILES="$FILES install/webui/.kluctl.yaml" +FILES="$FILES install/webui/webui/deployment.yaml" FILES="$FILES docs/installation.md" for f in $FILES; do diff --git a/install/webui/webui/deployment.yaml b/install/webui/webui/deployment.yaml index f50c31b72..d832b2ef4 100644 --- a/install/webui/webui/deployment.yaml +++ b/install/webui/webui/deployment.yaml @@ -1,5 +1,5 @@ {% set kluctl_version = get_var("args.kluctl_version", "v2.20.4") %} -{% set pull_policy = "Always" if "-devel" in kluctl_version or "-snapshot" in kluctl_version else "IfNotPresent" %} +{% set pull_policy = "Always" if ("-devel" in kluctl_version or "-snapshot" in kluctl_version) else "IfNotPresent" %} apiVersion: apps/v1 kind: Deployment From 3d8a94c9c1f1890f810fe568a7a3e87c6de3808f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 15 Jun 2023 16:59:11 +0200 Subject: [PATCH 1850/2916] fix: Rename controller_version to kluctl_version and fix snapshot detection (#585) --- cmd/kluctl/commands/cmd_controller_install.go | 8 ++++---- install/controller/.kluctl.yaml | 2 +- install/controller/controller/kustomization.yaml | 13 +++++-------- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/cmd/kluctl/commands/cmd_controller_install.go b/cmd/kluctl/commands/cmd_controller_install.go index d5e3db490..1e70e16a8 100644 --- a/cmd/kluctl/commands/cmd_controller_install.go +++ b/cmd/kluctl/commands/cmd_controller_install.go @@ -14,8 +14,8 @@ type controllerInstallCmd struct { args.DryRunFlags args.CommandResultFlags - Context string `group:"misc" help:"Override the context to use."` - ControllerVersion string `group:"misc" help:"Specify the controller version to install."` + Context string `group:"misc" help:"Override the context to use."` + KluctlVersion string `group:"misc" help:"Specify the controller version to install."` } func (cmd *controllerInstallCmd) Help() string { @@ -29,8 +29,8 @@ func (cmd *controllerInstallCmd) Run(ctx context.Context) error { } var deployArgs []string - if cmd.ControllerVersion != "" { - deployArgs = append(deployArgs, fmt.Sprintf("controller_version=%s", cmd.ControllerVersion)) + if cmd.KluctlVersion != "" { + deployArgs = append(deployArgs, fmt.Sprintf("kluctl_version=%s", cmd.KluctlVersion)) } cmd2 := deployCmd{ diff --git a/install/controller/.kluctl.yaml b/install/controller/.kluctl.yaml index 61be49c62..f85272bec 100644 --- a/install/controller/.kluctl.yaml +++ b/install/controller/.kluctl.yaml @@ -1,7 +1,7 @@ discriminator: kluctl.io-controller args: - - name: controller_version + - name: kluctl_version default: v2.20.4 - name: controller_args default: [] diff --git a/install/controller/controller/kustomization.yaml b/install/controller/controller/kustomization.yaml index be62c808a..a7551b296 100644 --- a/install/controller/controller/kustomization.yaml +++ b/install/controller/controller/kustomization.yaml @@ -1,4 +1,6 @@ -{% set controller_version = get_var("args.controller_version", "v2.20.4") %} +# TODO remove controller_version +{% set kluctl_version = get_var(["args.kluctl_version", "args.controller_version"], "v2.20.4") %} +{% set pull_policy = "Always" if ("-devel" in kluctl_version or "-snapshot" in kluctl_version) else "IfNotPresent" %} resources: - crd.yaml @@ -12,19 +14,14 @@ patches: patch: |- - op: add path: /spec/template/spec/containers/0/image - value: ghcr.io/kluctl/kluctl:{{ controller_version }} + value: ghcr.io/kluctl/kluctl:{{ kluctl_version }} - target: kind: Deployment name: kluctl-controller patch: |- - - op: test - path: /kind - value: Deployment # this is just a dummy test to avoid empty patches -{% if "-devel" in controller_version %} - op: add path: /spec/template/spec/containers/0/imagePullPolicy - value: Always -{% endif %} + value: {{ pull_policy }} {% for a in get_var("args.controller_args", []) %} - op: add path: /spec/template/spec/containers/0/args/- From d0fb711aefaf89a0226b86b85fd605fb765e47a5 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 15 Jun 2023 17:11:10 +0200 Subject: [PATCH 1851/2916] chore: Run make replace-commands-help --- docs/reference/commands/controller-install.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/reference/commands/controller-install.md b/docs/reference/commands/controller-install.md index 516e9ee91..fcaabfd83 100644 --- a/docs/reference/commands/controller-install.md +++ b/docs/reference/commands/controller-install.md @@ -28,10 +28,10 @@ In addition, the following arguments are available: Misc arguments: Command specific arguments. - --context string Override the context to use. - --controller-version string Specify the controller version to install. - --dry-run Performs all kubernetes API calls in dry-run mode. - -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. + --context string Override the context to use. + --dry-run Performs all kubernetes API calls in dry-run mode. + --kluctl-version string Specify the controller version to install. + -y, --yes Suppresses 'Are you sure?' questions and proceeds as if you would answer 'yes'. ``` From 2087c139f9d9cb7de70f5a04c35ffe9ddf51d142 Mon Sep 17 00:00:00 2001 From: Aleksei Vagarenko Date: Thu, 15 Jun 2023 21:12:45 +0600 Subject: [PATCH 1852/2916] fix: Fix cropping of sliding history card animations (#584) * Update sliding animations. * Update build. --- pkg/webui/ui/build/index.html | 2 +- pkg/webui/ui/build/static/css/main.css | 2 +- pkg/webui/ui/build/static/js/main.js | 78 +++++++++---------- .../components/targets-view/HistoryCards.tsx | 28 +++++-- 4 files changed, 63 insertions(+), 47 deletions(-) diff --git a/pkg/webui/ui/build/index.html b/pkg/webui/ui/build/index.html index 31e223a49..d31444bc3 100644 --- a/pkg/webui/ui/build/index.html +++ b/pkg/webui/ui/build/index.html @@ -1 +1 @@ -React App
    \ No newline at end of file +React App
    \ No newline at end of file diff --git a/pkg/webui/ui/build/static/css/main.css b/pkg/webui/ui/build/static/css/main.css index a8874843f..38a0dc2e1 100644 --- a/pkg/webui/ui/build/static/css/main.css +++ b/pkg/webui/ui/build/static/css/main.css @@ -84,4 +84,4 @@ body, unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; } -/*# sourceMappingURL=main.9c2b54a7.css.map*/ \ No newline at end of file +/*# sourceMappingURL=main.94bdb1a0.css.map*/ \ No newline at end of file diff --git a/pkg/webui/ui/build/static/js/main.js b/pkg/webui/ui/build/static/js/main.js index 882a0ead2..bb1335e47 100644 --- a/pkg/webui/ui/build/static/js/main.js +++ b/pkg/webui/ui/build/static/js/main.js @@ -47615,7 +47615,7 @@ function SvgKluctlText(_ref, svgRef) { }))); } var ForwardRef = /*#__PURE__*/react.forwardRef(SvgKluctlText); -/* harmony default export */ var kluctl_text = (__webpack_require__.p + "static/media/kluctl-text.svg?f=kluctl-text.f97d4b1ffe2002c0802e2e1ad2044bab.svg"); +/* harmony default export */ var kluctl_text = (__webpack_require__.p + "static/media/kluctl-text.svg?f=kluctl-text.1392c9ce908d475e5086961a5b3a3072.svg"); ;// CONCATENATED MODULE: ./src/icons/git.svg var _defs; @@ -47683,7 +47683,7 @@ function SvgKluctlLogo(_ref, svgRef) { }))); } var kluctl_logo_ForwardRef = /*#__PURE__*/react.forwardRef(SvgKluctlLogo); -/* harmony default export */ var kluctl_logo = (__webpack_require__.p + "static/media/kluctl-logo.svg?f=kluctl-logo.252d0acc30f14f58acd1b5faac05c17b.svg"); +/* harmony default export */ var kluctl_logo = (__webpack_require__.p + "static/media/kluctl-logo.svg?f=kluctl-logo.928b5d24c522acfce937ce451da78cfd.svg"); ;// CONCATENATED MODULE: ./src/icons/search.svg var search_path, search_path2; @@ -47716,7 +47716,7 @@ function SvgSearch(_ref, svgRef) { }))); } var search_ForwardRef = /*#__PURE__*/react.forwardRef(SvgSearch); -/* harmony default export */ var search = (__webpack_require__.p + "static/media/search.svg?f=search.10cfa92f61b9621ea7fe3d64cac12eee.svg"); +/* harmony default export */ var search = (__webpack_require__.p + "static/media/search.svg?f=search.301f22f9e2228136d62b0edb7d97c86e.svg"); ;// CONCATENATED MODULE: ./src/icons/targets.svg var _circle, targets_path, targets_path2, targets_path3; @@ -47763,7 +47763,7 @@ function SvgTargets(_ref, svgRef) { }))); } var targets_ForwardRef = /*#__PURE__*/react.forwardRef(SvgTargets); -/* harmony default export */ var targets = (__webpack_require__.p + "static/media/targets.svg?f=targets.2b39a3a176679d8e9ed00f0b3bfd3ba5.svg"); +/* harmony default export */ var targets = (__webpack_require__.p + "static/media/targets.svg?f=targets.ddda17cc51371fc22f6fc66cb7986073.svg"); ;// CONCATENATED MODULE: ./src/icons/target.svg var _ellipse, target_path, target_path2; @@ -47808,7 +47808,7 @@ function SvgTarget(_ref, svgRef) { }))); } var target_ForwardRef = /*#__PURE__*/react.forwardRef(SvgTarget); -/* harmony default export */ var target = (__webpack_require__.p + "static/media/target.svg?f=target.1dd672dc23cacf59e90fbb277c9bdd9d.svg"); +/* harmony default export */ var target = (__webpack_require__.p + "static/media/target.svg?f=target.0750f7a6c362d82cc7fb678f9260c46d.svg"); ;// CONCATENATED MODULE: ./src/icons/relation-hline.svg var relation_hline_path, relation_hline_circle, _circle2; @@ -47852,7 +47852,7 @@ function SvgRelationHline(_ref, svgRef) { }))); } var relation_hline_ForwardRef = /*#__PURE__*/(/* unused pure expression or super */ null && (React.forwardRef(SvgRelationHline))); -/* harmony default export */ var relation_hline = (__webpack_require__.p + "static/media/relation-hline.svg?f=relation-hline.91c9012405859f512c983b2985999ad7.svg"); +/* harmony default export */ var relation_hline = (__webpack_require__.p + "static/media/relation-hline.svg?f=relation-hline.871cfae94c944beb7d05364a2cee37f1.svg"); ;// CONCATENATED MODULE: ./src/icons/project.svg var project_ellipse, project_path, project_path2; @@ -47899,7 +47899,7 @@ function SvgProject(_ref, svgRef) { }))); } var project_ForwardRef = /*#__PURE__*/react.forwardRef(SvgProject); -/* harmony default export */ var project = (__webpack_require__.p + "static/media/project.svg?f=project.66e77c7ad83b85f0325c2e0ffed64b8c.svg"); +/* harmony default export */ var project = (__webpack_require__.p + "static/media/project.svg?f=project.637cf85e6cadcedc900412158dfa7372.svg"); ;// CONCATENATED MODULE: ./src/icons/cpu.svg var cpu_path, cpu_path2, cpu_path3, cpu_path4, cpu_path5, _path6, _path7, _path8, _path9, _path10, _path11, _path12, _path13, _path14; @@ -47968,7 +47968,7 @@ function SvgCpu(_ref, svgRef) { }))); } var cpu_ForwardRef = /*#__PURE__*/react.forwardRef(SvgCpu); -/* harmony default export */ var cpu = (__webpack_require__.p + "static/media/cpu.svg?f=cpu.a3fa0b9152cb81606a7da57c1b07cc85.svg"); +/* harmony default export */ var cpu = (__webpack_require__.p + "static/media/cpu.svg?f=cpu.b47d73a3f55d2fa8fa37d8ed73d178c4.svg"); ;// CONCATENATED MODULE: ./src/icons/finger-scan.svg var finger_scan_path, finger_scan_path2, finger_scan_path3, finger_scan_path4, finger_scan_path5, finger_scan_path6; @@ -48034,7 +48034,7 @@ function SvgFingerScan(_ref, svgRef) { }))); } var finger_scan_ForwardRef = /*#__PURE__*/react.forwardRef(SvgFingerScan); -/* harmony default export */ var finger_scan = (__webpack_require__.p + "static/media/finger-scan.svg?f=finger-scan.ab3c987f3fd693b908ef65df4c2cd433.svg"); +/* harmony default export */ var finger_scan = (__webpack_require__.p + "static/media/finger-scan.svg?f=finger-scan.2c034cf5598ca4927432b008dc2304c4.svg"); ;// CONCATENATED MODULE: ./src/icons/tree-view.svg var tree_view_path, tree_view_path2, tree_view_path3, tree_view_path4, tree_view_path5; @@ -48076,7 +48076,7 @@ function SvgTreeView(_ref, svgRef) { }))); } var tree_view_ForwardRef = /*#__PURE__*/react.forwardRef(SvgTreeView); -/* harmony default export */ var tree_view = (__webpack_require__.p + "static/media/tree-view.svg?f=tree-view.a2f07b1cebd3235db357cc2b6e4c8c0d.svg"); +/* harmony default export */ var tree_view = (__webpack_require__.p + "static/media/tree-view.svg?f=tree-view.3c49a19936c3ecef1167c9ae1aad9102.svg"); ;// CONCATENATED MODULE: ./src/icons/close.svg var close_path; @@ -48105,7 +48105,7 @@ function SvgClose(_ref, svgRef) { }))); } var close_ForwardRef = /*#__PURE__*/react.forwardRef(SvgClose); -/* harmony default export */ var icons_close = (__webpack_require__.p + "static/media/close.svg?f=close.76fdca7f91598011401cb97d4b490d52.svg"); +/* harmony default export */ var icons_close = (__webpack_require__.p + "static/media/close.svg?f=close.06c69ef83805e9ea03cc487136b57b43.svg"); ;// CONCATENATED MODULE: ./src/icons/close-light.svg var close_light_path; @@ -48134,7 +48134,7 @@ function SvgCloseLight(_ref, svgRef) { }))); } var close_light_ForwardRef = /*#__PURE__*/react.forwardRef(SvgCloseLight); -/* harmony default export */ var close_light = (__webpack_require__.p + "static/media/close-light.svg?f=close-light.eb8a7d21cff89473a30809cdaacf6446.svg"); +/* harmony default export */ var close_light = (__webpack_require__.p + "static/media/close-light.svg?f=close-light.3f339905970f9885aae8cc76977a1abb.svg"); ;// CONCATENATED MODULE: ./src/icons/deploy.svg var deploy_circle, deploy_path, _g; @@ -48186,7 +48186,7 @@ function SvgDeploy(_ref, svgRef) { })))); } var deploy_ForwardRef = /*#__PURE__*/react.forwardRef(SvgDeploy); -/* harmony default export */ var deploy = (__webpack_require__.p + "static/media/deploy.svg?f=deploy.2d28f597e81eb12910b9816b567b11ce.svg"); +/* harmony default export */ var deploy = (__webpack_require__.p + "static/media/deploy.svg?f=deploy.62f738f9b93ba10869f740e158192270.svg"); ;// CONCATENATED MODULE: ./src/icons/prune.svg var prune_circle, prune_path, prune_path2, prune_path3, prune_path4, prune_path5; @@ -48250,7 +48250,7 @@ function SvgPrune(_ref, svgRef) { }))); } var prune_ForwardRef = /*#__PURE__*/react.forwardRef(SvgPrune); -/* harmony default export */ var prune = (__webpack_require__.p + "static/media/prune.svg?f=prune.a5f592f5851c37276e764af73b2a8a56.svg"); +/* harmony default export */ var prune = (__webpack_require__.p + "static/media/prune.svg?f=prune.ac5564f5a55a590b9d82c5e6aac239fd.svg"); ;// CONCATENATED MODULE: ./src/icons/diff.svg var diff_circle, diff_g, diff_path; @@ -48301,7 +48301,7 @@ function SvgDiff(_ref, svgRef) { }))); } var diff_ForwardRef = /*#__PURE__*/react.forwardRef(SvgDiff); -/* harmony default export */ var diff = (__webpack_require__.p + "static/media/diff.svg?f=diff.37d81f4e01925715c51f42684bf21554.svg"); +/* harmony default export */ var diff = (__webpack_require__.p + "static/media/diff.svg?f=diff.e79db65b5d7c3fb43104133da0dc8368.svg"); ;// CONCATENATED MODULE: ./src/icons/warning.svg var warning_path, warning_path2, warning_path3; @@ -48336,7 +48336,7 @@ function SvgWarning(_ref, svgRef) { }))); } var warning_ForwardRef = /*#__PURE__*/react.forwardRef(SvgWarning); -/* harmony default export */ var icons_warning = (__webpack_require__.p + "static/media/warning.svg?f=warning.dd1c59340347be553c83108e04d6be04.svg"); +/* harmony default export */ var icons_warning = (__webpack_require__.p + "static/media/warning.svg?f=warning.ddf48b11eb0bb3fbca783743bfd0ae08.svg"); ;// CONCATENATED MODULE: ./src/icons/error.svg var error_path, error_path2; @@ -48368,7 +48368,7 @@ function SvgError(_ref, svgRef) { }))); } var error_ForwardRef = /*#__PURE__*/react.forwardRef(SvgError); -/* harmony default export */ var error = (__webpack_require__.p + "static/media/error.svg?f=error.c59bc8c7d7f86a588a16bc6ec27d448a.svg"); +/* harmony default export */ var error = (__webpack_require__.p + "static/media/error.svg?f=error.ee4b3e05eeb403dd3e8a8f65cc782fd4.svg"); ;// CONCATENATED MODULE: ./src/icons/trash.svg var trash_path, trash_path2, trash_path3, trash_path4; @@ -48411,7 +48411,7 @@ function SvgTrash(_ref, svgRef) { }))); } var trash_ForwardRef = /*#__PURE__*/react.forwardRef(SvgTrash); -/* harmony default export */ var trash = (__webpack_require__.p + "static/media/trash.svg?f=trash.b782f36f610aae639750393e0793c538.svg"); +/* harmony default export */ var trash = (__webpack_require__.p + "static/media/trash.svg?f=trash.025b2d3120e905c307efe6236bb4d3a2.svg"); ;// CONCATENATED MODULE: ./src/icons/orphan.svg var orphan_path, orphan_path2, orphan_path3, orphan_path4; @@ -48453,7 +48453,7 @@ function SvgOrphan(_ref, svgRef) { }))); } var orphan_ForwardRef = /*#__PURE__*/react.forwardRef(SvgOrphan); -/* harmony default export */ var orphan = (__webpack_require__.p + "static/media/orphan.svg?f=orphan.6fde4d55f3fb9bbd2738b0b61fcee146.svg"); +/* harmony default export */ var orphan = (__webpack_require__.p + "static/media/orphan.svg?f=orphan.05f9bb90537f7ff1ab68bc2c4806352c.svg"); ;// CONCATENATED MODULE: ./src/icons/added.svg var added_path, added_path2; @@ -48486,7 +48486,7 @@ function SvgAdded(_ref, svgRef) { }))); } var added_ForwardRef = /*#__PURE__*/react.forwardRef(SvgAdded); -/* harmony default export */ var added = (__webpack_require__.p + "static/media/added.svg?f=added.a633ace445f83e4ab52d3c80cc788e1e.svg"); +/* harmony default export */ var added = (__webpack_require__.p + "static/media/added.svg?f=added.54530b6cceb257eddeb8f0016f3c9821.svg"); ;// CONCATENATED MODULE: ./src/icons/changed.svg var changed_path, changed_path2, changed_path3; @@ -48522,7 +48522,7 @@ function SvgChanged(_ref, svgRef) { }))); } var changed_ForwardRef = /*#__PURE__*/react.forwardRef(SvgChanged); -/* harmony default export */ var changed = (__webpack_require__.p + "static/media/changed.svg?f=changed.a9c091de612ab63c8d38d83dbb73e505.svg"); +/* harmony default export */ var changed = (__webpack_require__.p + "static/media/changed.svg?f=changed.27c5207a7397f2557ac197a3636da39d.svg"); ;// CONCATENATED MODULE: ./src/icons/checkbox.svg var _rect; @@ -48557,7 +48557,7 @@ function SvgCheckbox(_ref, svgRef) { }))); } var checkbox_ForwardRef = /*#__PURE__*/react.forwardRef(SvgCheckbox); -/* harmony default export */ var icons_checkbox = (__webpack_require__.p + "static/media/checkbox.svg?f=checkbox.90148614b6ab0451aac6f71db92028fd.svg"); +/* harmony default export */ var icons_checkbox = (__webpack_require__.p + "static/media/checkbox.svg?f=checkbox.bcc364182a1a03dc753e7402a023eee2.svg"); ;// CONCATENATED MODULE: ./src/icons/checkbox-checked.svg var checkbox_checked_rect, checkbox_checked_path, _rect2; @@ -48601,7 +48601,7 @@ function SvgCheckboxChecked(_ref, svgRef) { }))); } var checkbox_checked_ForwardRef = /*#__PURE__*/react.forwardRef(SvgCheckboxChecked); -/* harmony default export */ var checkbox_checked = (__webpack_require__.p + "static/media/checkbox-checked.svg?f=checkbox-checked.1c552519ffded79bb8ad25822c2573bb.svg"); +/* harmony default export */ var checkbox_checked = (__webpack_require__.p + "static/media/checkbox-checked.svg?f=checkbox-checked.57f61c3bde08a33c0bd9136714a3d3b3.svg"); ;// CONCATENATED MODULE: ./src/icons/checkbox-disabled.svg var checkbox_disabled_rect, checkbox_disabled_rect2; @@ -48644,7 +48644,7 @@ function SvgCheckboxDisabled(_ref, svgRef) { }))); } var checkbox_disabled_ForwardRef = /*#__PURE__*/(/* unused pure expression or super */ null && (React.forwardRef(SvgCheckboxDisabled))); -/* harmony default export */ var checkbox_disabled = (__webpack_require__.p + "static/media/checkbox-disabled.svg?f=checkbox-disabled.f7de3b059a1f9fc60f809e7d3fd601e2.svg"); +/* harmony default export */ var checkbox_disabled = (__webpack_require__.p + "static/media/checkbox-disabled.svg?f=checkbox-disabled.83ae46d3015ca4b4114b8a785857ef00.svg"); ;// CONCATENATED MODULE: ./src/icons/arrow-left.svg var arrow_left_path, arrow_left_path2; @@ -48677,7 +48677,7 @@ function SvgArrowLeft(_ref, svgRef) { }))); } var arrow_left_ForwardRef = /*#__PURE__*/react.forwardRef(SvgArrowLeft); -/* harmony default export */ var arrow_left = (__webpack_require__.p + "static/media/arrow-left.svg?f=arrow-left.0e69d1eb25254a957b660d86b58b7594.svg"); +/* harmony default export */ var arrow_left = (__webpack_require__.p + "static/media/arrow-left.svg?f=arrow-left.55965a5b737ce5b390f6ce9053ef61f4.svg"); ;// CONCATENATED MODULE: ./src/icons/warning-sign.svg var warning_sign_path, warning_sign_path2, warning_sign_path3; @@ -48713,7 +48713,7 @@ function SvgWarningSign(_ref, svgRef) { }))); } var warning_sign_ForwardRef = /*#__PURE__*/react.forwardRef(SvgWarningSign); -/* harmony default export */ var warning_sign = (__webpack_require__.p + "static/media/warning-sign.svg?f=warning-sign.fb9ebdb743ce95ba858869fcc9772754.svg"); +/* harmony default export */ var warning_sign = (__webpack_require__.p + "static/media/warning-sign.svg?f=warning-sign.a4dd71daaeea7e2f3212daa2159c2c55.svg"); ;// CONCATENATED MODULE: ./src/icons/changes.svg var changes_path, changes_path2, changes_path3; @@ -48750,7 +48750,7 @@ function SvgChanges(_ref, svgRef) { }))); } var changes_ForwardRef = /*#__PURE__*/react.forwardRef(SvgChanges); -/* harmony default export */ var changes = (__webpack_require__.p + "static/media/changes.svg?f=changes.b1e048d4a195559cfe9376069cd93bea.svg"); +/* harmony default export */ var changes = (__webpack_require__.p + "static/media/changes.svg?f=changes.ceb3f50f60cb8242ab5c9a5bfecb20f8.svg"); ;// CONCATENATED MODULE: ./src/icons/star.svg var star_path, star_path2; @@ -48783,7 +48783,7 @@ function SvgStar(_ref, svgRef) { }))); } var star_ForwardRef = /*#__PURE__*/react.forwardRef(SvgStar); -/* harmony default export */ var star = (__webpack_require__.p + "static/media/star.svg?f=star.0c433d1c6ccb36781955621fc40f0540.svg"); +/* harmony default export */ var star = (__webpack_require__.p + "static/media/star.svg?f=star.231e6e122fdf32a9487349a189a2a100.svg"); ;// CONCATENATED MODULE: ./src/icons/triangle-down.svg var triangle_down_path, triangle_down_path2; @@ -48816,7 +48816,7 @@ function SvgTriangleDown(_ref, svgRef) { }))); } var triangle_down_ForwardRef = /*#__PURE__*/react.forwardRef(SvgTriangleDown); -/* harmony default export */ var triangle_down = (__webpack_require__.p + "static/media/triangle-down.svg?f=triangle-down.cb859bf44378ddfd76730eb4a9f90a89.svg"); +/* harmony default export */ var triangle_down = (__webpack_require__.p + "static/media/triangle-down.svg?f=triangle-down.60eb89e2ef7264593e9b4daf2377cc6c.svg"); ;// CONCATENATED MODULE: ./src/icons/triangle-left-light.svg var triangle_left_light_path, triangle_left_light_path2; @@ -48850,7 +48850,7 @@ function SvgTriangleLeftLight(_ref, svgRef) { }))); } var triangle_left_light_ForwardRef = /*#__PURE__*/react.forwardRef(SvgTriangleLeftLight); -/* harmony default export */ var triangle_left_light = (__webpack_require__.p + "static/media/triangle-left-light.svg?f=triangle-left-light.723aa352c7195ed6c2c94748badb018c.svg"); +/* harmony default export */ var triangle_left_light = (__webpack_require__.p + "static/media/triangle-left-light.svg?f=triangle-left-light.65fea1fe6bee135dd5ccdcfd6ee5ba02.svg"); ;// CONCATENATED MODULE: ./src/icons/triangle-right-light.svg var triangle_right_light_path, triangle_right_light_path2; @@ -48883,7 +48883,7 @@ function SvgTriangleRightLight(_ref, svgRef) { }))); } var triangle_right_light_ForwardRef = /*#__PURE__*/react.forwardRef(SvgTriangleRightLight); -/* harmony default export */ var triangle_right_light = (__webpack_require__.p + "static/media/triangle-right-light.svg?f=triangle-right-light.22d8903e553c49bf9b0100b61fe7254f.svg"); +/* harmony default export */ var triangle_right_light = (__webpack_require__.p + "static/media/triangle-right-light.svg?f=triangle-right-light.0436dfced580e6b8bf6a64dbbb6b6ebd.svg"); ;// CONCATENATED MODULE: ./src/icons/triangle-right.svg var triangle_right_path, triangle_right_path2; @@ -48917,7 +48917,7 @@ function SvgTriangleRight(_ref, svgRef) { }))); } var triangle_right_ForwardRef = /*#__PURE__*/react.forwardRef(SvgTriangleRight); -/* harmony default export */ var triangle_right = (__webpack_require__.p + "static/media/triangle-right.svg?f=triangle-right.795bcf3f22906edc57cecb48328c9f89.svg"); +/* harmony default export */ var triangle_right = (__webpack_require__.p + "static/media/triangle-right.svg?f=triangle-right.4ec8dee9a1ea90ac8936f504d81720ca.svg"); ;// CONCATENATED MODULE: ./src/icons/brackets-curly.svg var brackets_curly_path; @@ -48946,7 +48946,7 @@ function SvgBracketsCurly(_ref, svgRef) { }))); } var brackets_curly_ForwardRef = /*#__PURE__*/react.forwardRef(SvgBracketsCurly); -/* harmony default export */ var brackets_curly = (__webpack_require__.p + "static/media/brackets-curly.svg?f=brackets-curly.99e04241d2e10a7cfb60d2c695401adc.svg"); +/* harmony default export */ var brackets_curly = (__webpack_require__.p + "static/media/brackets-curly.svg?f=brackets-curly.d831d285bb85a307a76da8c13f76fa9b.svg"); ;// CONCATENATED MODULE: ./src/icons/brackets-square.svg var brackets_square_path; @@ -48975,7 +48975,7 @@ function SvgBracketsSquare(_ref, svgRef) { }))); } var brackets_square_ForwardRef = /*#__PURE__*/react.forwardRef(SvgBracketsSquare); -/* harmony default export */ var brackets_square = (__webpack_require__.p + "static/media/brackets-square.svg?f=brackets-square.3daecb55d1cd657d5682c02cfe8563d5.svg"); +/* harmony default export */ var brackets_square = (__webpack_require__.p + "static/media/brackets-square.svg?f=brackets-square.c743c4f98f9a2cdddff2e1ec7843d74c.svg"); ;// CONCATENATED MODULE: ./src/icons/file.svg var file_path, file_path2, file_path3; @@ -49011,7 +49011,7 @@ function SvgFile(_ref, svgRef) { }))); } var file_ForwardRef = /*#__PURE__*/react.forwardRef(SvgFile); -/* harmony default export */ var file = (__webpack_require__.p + "static/media/file.svg?f=file.f2670ef3fe4fae6122bb957ea2390db0.svg"); +/* harmony default export */ var file = (__webpack_require__.p + "static/media/file.svg?f=file.82db4ab2b22fca5dc01ef7cc944e4cae.svg"); ;// CONCATENATED MODULE: ./src/icons/result.svg var result_circle, result_path, result_g; @@ -49063,7 +49063,7 @@ function SvgResult(_ref, svgRef) { })))); } var result_ForwardRef = /*#__PURE__*/(/* unused pure expression or super */ null && (React.forwardRef(SvgResult))); -/* harmony default export */ var result = (__webpack_require__.p + "static/media/result.svg?f=result.cc7a9a9173a072950aee17843ff145ba.svg"); +/* harmony default export */ var result = (__webpack_require__.p + "static/media/result.svg?f=result.f37cb0485efb4bdd26bde5626dcc236b.svg"); ;// CONCATENATED MODULE: ./src/icons/include.svg var include_circle, include_path, include_path2, include_path3, include_path4; @@ -49125,7 +49125,7 @@ function SvgInclude(_ref, svgRef) { }))); } var include_ForwardRef = /*#__PURE__*/react.forwardRef(SvgInclude); -/* harmony default export */ var include = (__webpack_require__.p + "static/media/include.svg?f=include.6bf4795a2f5623b297789116effd0bec.svg"); +/* harmony default export */ var include = (__webpack_require__.p + "static/media/include.svg?f=include.758427058cde3728cc49a2a93848150c.svg"); ;// CONCATENATED MODULE: ./src/icons/Icons.tsx var KluctlText=function KluctlText(){return/*#__PURE__*/(0,jsx_runtime.jsx)(ForwardRef,{width:"115px",height:"33px"});};var GitIcon=function GitIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(git_ForwardRef,{width:"35px",height:"35px"});};var KluctlLogo=function KluctlLogo(){return/*#__PURE__*/(0,jsx_runtime.jsx)(kluctl_logo_ForwardRef,{width:"50px",height:"50px"});};var SearchIcon=function SearchIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(search_ForwardRef,{width:"27px",height:"27px"});};var TargetsIcon=function TargetsIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(targets_ForwardRef,{width:"48px",height:"48px"});};var TargetIcon=function TargetIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(target_ForwardRef,{width:"45px",height:"45px"});};var RelationHLine=function RelationHLine(){return/*#__PURE__*/_jsx(RelationHLineSvg,{width:"169px",height:"12px"});};var ProjectIcon=function ProjectIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(project_ForwardRef,{width:"45px",height:"45px"});};var DeployIcon=function DeployIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(deploy_ForwardRef,{width:"45px",height:"45px"});};var PruneIcon=function PruneIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(prune_ForwardRef,{width:"45px",height:"45px"});};var DiffIcon=function DiffIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(diff_ForwardRef,{width:"45px",height:"45px"});};var CpuIcon=function CpuIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(cpu_ForwardRef,{width:"24px",height:"24px"});};var FingerScanIcon=function FingerScanIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(finger_scan_ForwardRef,{width:"24px",height:"24px"});};var MessageQuestionIcon=function MessageQuestionIcon(props){return/*#__PURE__*/(0,jsx_runtime.jsx)("svg",{width:"24",height:"24",viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg",children:/*#__PURE__*/(0,jsx_runtime.jsx)("path",{d:"M17 2.42999H7C4 2.42999 2 4.42999 2 7.42999V13.43C2 16.43 4 18.43 7 18.43V20.56C7 21.36 7.89 21.84 8.55 21.39L13 18.43H17C20 18.43 22 16.43 22 13.43V7.42999C22 4.42999 20 2.42999 17 2.42999ZM12 14.6C11.58 14.6 11.25 14.26 11.25 13.85C11.25 13.44 11.58 13.1 12 13.1C12.42 13.1 12.75 13.44 12.75 13.85C12.75 14.26 12.42 14.6 12 14.6ZM13.26 10.45C12.87 10.71 12.75 10.88 12.75 11.16V11.37C12.75 11.78 12.41 12.12 12 12.12C11.59 12.12 11.25 11.78 11.25 11.37V11.16C11.25 9.99999 12.1 9.42999 12.42 9.20999C12.79 8.95999 12.91 8.78999 12.91 8.52999C12.91 8.02999 12.5 7.61999 12 7.61999C11.5 7.61999 11.09 8.02999 11.09 8.52999C11.09 8.93999 10.75 9.27999 10.34 9.27999C9.93 9.27999 9.59 8.93999 9.59 8.52999C9.59 7.19999 10.67 6.11999 12 6.11999C13.33 6.11999 14.41 7.19999 14.41 8.52999C14.41 9.66999 13.57 10.24 13.26 10.45Z",fill:props.color})});};var TreeViewIcon=function TreeViewIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(tree_view_ForwardRef,{width:"26px",height:"26px"});};var CloseIcon=function CloseIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(close_ForwardRef,{width:"24px",height:"24px"});};var CloseLightIcon=function CloseLightIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(close_light_ForwardRef,{width:"24px",height:"24px"});};var WarningIcon=function WarningIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(warning_ForwardRef,{width:"24px",height:"24px"});};var ErrorIcon=function ErrorIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(error_ForwardRef,{width:"24px",height:"24px"});};var TrashIcon=function TrashIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(trash_ForwardRef,{width:"24px",height:"24px"});};var OrphanIcon=function OrphanIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(orphan_ForwardRef,{width:"24px",height:"24px"});};var AddedIcon=function AddedIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(added_ForwardRef,{width:"24px",height:"24px"});};var ChangedIcon=function ChangedIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(changed_ForwardRef,{width:"24px",height:"24px"});};var CheckboxIcon=function CheckboxIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(checkbox_ForwardRef,{width:"24px",height:"24px"});};var CheckboxCheckedIcon=function CheckboxCheckedIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(checkbox_checked_ForwardRef,{width:"24px",height:"24px"});};var CheckboxDisabledIcon=function CheckboxDisabledIcon(){return/*#__PURE__*/_jsx(CheckboxDisabledIconSvg,{width:"24px",height:"24px"});};var ArrowLeftIcon=function ArrowLeftIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(arrow_left_ForwardRef,{width:"40px",height:"40px"});};var WarningSignIcon=function WarningSignIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(warning_sign_ForwardRef,{width:"21px",height:"21px"});};var ChangesIcon=function ChangesIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(changes_ForwardRef,{width:"21px",height:"21px"});};var StarIcon=function StarIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(star_ForwardRef,{width:"21px",height:"21px"});};var TriangleDownIcon=function TriangleDownIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(triangle_down_ForwardRef,{width:"50px",height:"50px"});};var TriangleLeftLightIcon=function TriangleLeftLightIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(triangle_left_light_ForwardRef,{width:"50px",height:"50px"});};var TriangleRightLightIcon=function TriangleRightLightIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(triangle_right_light_ForwardRef,{width:"50px",height:"50px"});};var TriangleRightIcon=function TriangleRightIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(triangle_right_ForwardRef,{width:"50px",height:"50px"});};var BracketsCurlyIcon=function BracketsCurlyIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(brackets_curly_ForwardRef,{width:"22px",height:"18px"});};var BracketsSquareIcon=function BracketsSquareIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(brackets_square_ForwardRef,{width:"22px",height:"18px"});};var FileIcon=function FileIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(file_ForwardRef,{width:"40px",height:"40px"});};var ResultIcon=function ResultIcon(){return/*#__PURE__*/_jsx(ResultIconSvg,{width:"30px",height:"30px"});};var IncludeIcon=function IncludeIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(include_ForwardRef,{width:"30px",height:"30px"});}; @@ -59259,7 +59259,7 @@ var DeletedOrOrphanObjectsCollectionNode=/*#__PURE__*/function(_NodeData){_inher ;// CONCATENATED MODULE: ./src/components/result-view/nodes/NodeBuilder.ts var NodeBuilder=/*#__PURE__*/function(){function NodeBuilder(props){var _props$commandResult$,_this=this,_props$commandResult$2,_props$commandResult$3;classCallCheck_classCallCheck(this,NodeBuilder);this.props=void 0;this.changedObjectsMap=new Map();this.newObjectsMap=new Map();this.orphanObjectsMap=new Map();this.deletedObjectsMap=new Map();this.errorsMap=new Map();this.warningsMap=new Map();this.props=props;(_props$commandResult$=props.commandResult.objects)===null||_props$commandResult$===void 0?void 0:_props$commandResult$.forEach(function(o){var _o$changes;var key=buildObjectRefKey(o.ref);if((_o$changes=o.changes)!==null&&_o$changes!==void 0&&_o$changes.length){_this.changedObjectsMap.set(key,o);}if(o.new){_this.newObjectsMap.set(key,o.ref);}if(o.orphan){_this.orphanObjectsMap.set(key,o.ref);}if(o.deleted){_this.deletedObjectsMap.set(key,o.ref);}});(_props$commandResult$2=props.commandResult.errors)===null||_props$commandResult$2===void 0?void 0:_props$commandResult$2.forEach(function(e){var key=buildObjectRefKey(e.ref);var l=_this.errorsMap.get(key);if(!l){l=[e];_this.errorsMap.set(key,l);}else{l.push(e);}});(_props$commandResult$3=props.commandResult.warnings)===null||_props$commandResult$3===void 0?void 0:_props$commandResult$3.forEach(function(e){var key=buildObjectRefKey(e.ref);var l=_this.warningsMap.get(key);if(!l){l=[e];_this.warningsMap.set(key,l);}else{l.push(e);}});}createClass_createClass(NodeBuilder,[{key:"buildRoot",value:function buildRoot(){var rootNode=new CommandResultNodeData(this.props,"root");if(this.props.commandResult.deployment){this.buildDeploymentProjectChildren(rootNode,this.props.commandResult.deployment);}if(this.deletedObjectsMap.size){this.buildDeletedOrOrphanNode(rootNode,true,Array.from(this.deletedObjectsMap.values()));}if(this.orphanObjectsMap.size){this.buildDeletedOrOrphanNode(rootNode,false,Array.from(this.orphanObjectsMap.values()));}var nodeMap=new Map();function collect(n){nodeMap.set(n.id,n);n.children.forEach(function(c){collect(c);});}collect(rootNode);return[rootNode,nodeMap];}},{key:"buildDeploymentProjectChildren",value:function buildDeploymentProjectChildren(node,deploymentProject){var _deploymentProject$de,_this2=this;if(deploymentProject.vars){this.buildVarsSourceCollectionNode(node,deploymentProject.vars);}(_deploymentProject$de=deploymentProject.deployments)===null||_deploymentProject$de===void 0?void 0:_deploymentProject$de.forEach(function(deploymentItem,i){_this2.buildDeploymentItemNode(node,deploymentItem,i);});}},{key:"buildVarsSourceCollectionNode",value:function buildVarsSourceCollectionNode(parentNode,varsSources){var _node$varsSources,_this3=this;if(varsSources===undefined){return;}var newId="".concat(parentNode.id,"/(vars)");var node=new VarsSourceCollectionNodeData(this.props,newId);(_node$varsSources=node.varsSources).push.apply(_node$varsSources,toConsumableArray_toConsumableArray(varsSources));varsSources.forEach(function(vs,i){_this3.buildVarsSourceNode(node,vs,i);});parentNode.pushChild(node);return node;}},{key:"buildVarsSourceNode",value:function buildVarsSourceNode(parentNode,varsSource,index){var newId="".concat(parentNode.id,"/").concat(index,"}");var node=new VarsSourceNodeData(this.props,newId,varsSource);parentNode.pushChild(node);return node;}},{key:"buildDeploymentItemNode",value:function buildDeploymentItemNode(parentNode,deploymentItem,index){var _this4=this;var node;var newId="".concat(parentNode.id,"/(dis)/").concat(index);if(deploymentItem.path){var _deploymentItem$rende;node=new DeploymentItemNodeData(this.props,newId,deploymentItem);this.buildVarsSourceCollectionNode(node,deploymentItem.vars);(_deploymentItem$rende=deploymentItem.renderedObjects)===null||_deploymentItem$rende===void 0?void 0:_deploymentItem$rende.forEach(function(renderedObject){_this4.buildObjectNode(node,renderedObject);});}else if(deploymentItem.include||deploymentItem.git){node=new DeploymentItemIncludeNodeData(this.props,newId,deploymentItem,deploymentItem.renderedInclude);this.buildVarsSourceCollectionNode(node,deploymentItem.vars);if(deploymentItem.renderedInclude){this.buildDeploymentProjectChildren(node,deploymentItem.renderedInclude);}}else{return node;}parentNode.pushChild(node);return node;}},{key:"buildObjectNode",value:function buildObjectNode(parentNode,objectRef){var _this$errorsMap$get,_this$warningsMap$get;var newId="".concat(parentNode.id,"/(obj)/").concat(buildObjectRefKey(objectRef));var node=new ObjectNodeData(this.props,newId,objectRef);var key=buildObjectRefKey(objectRef);if(this.changedObjectsMap.has(key)){var _node$diffStatus;(_node$diffStatus=node.diffStatus)===null||_node$diffStatus===void 0?void 0:_node$diffStatus.addChangedObject(this.changedObjectsMap.get(key));}if(this.newObjectsMap.has(key)){var _node$diffStatus2;(_node$diffStatus2=node.diffStatus)===null||_node$diffStatus2===void 0?void 0:_node$diffStatus2.newObjects.push(objectRef);}if(this.deletedObjectsMap.has(key)){var _node$diffStatus3;(_node$diffStatus3=node.diffStatus)===null||_node$diffStatus3===void 0?void 0:_node$diffStatus3.deletedObjects.push(objectRef);}if(this.orphanObjectsMap.has(key)){var _node$diffStatus4;(_node$diffStatus4=node.diffStatus)===null||_node$diffStatus4===void 0?void 0:_node$diffStatus4.orphanObjects.push(objectRef);}(_this$errorsMap$get=this.errorsMap.get(key))===null||_this$errorsMap$get===void 0?void 0:_this$errorsMap$get.forEach(function(e){var _node$healthStatus;(_node$healthStatus=node.healthStatus)===null||_node$healthStatus===void 0?void 0:_node$healthStatus.errors.push(e);});(_this$warningsMap$get=this.warningsMap.get(key))===null||_this$warningsMap$get===void 0?void 0:_this$warningsMap$get.forEach(function(e){var _node$healthStatus2;(_node$healthStatus2=node.healthStatus)===null||_node$healthStatus2===void 0?void 0:_node$healthStatus2.warnings.push(e);});parentNode.pushChild(node);return node;}},{key:"buildDeletedOrOrphanNode",value:function buildDeletedOrOrphanNode(parentNode,deleted,refs){var _this5=this;var idType=deleted?"deleted":"orphaned";var newId="".concat(parentNode.id,"/(").concat(idType,")");var node=new DeletedOrOrphanObjectsCollectionNode(this.props,newId,deleted);refs.forEach(function(ref){_this5.buildObjectNode(node,ref);});parentNode.pushChild(node,true);return node;}}]);return NodeBuilder;}();function buildObjectRefKey(ref){return[ref.group,ref.version,ref.kind,ref.namespace,ref.name].join("+");} ;// CONCATENATED MODULE: ./src/components/targets-view/HistoryCards.tsx -function doGetRootNode(_x,_x2){return _doGetRootNode.apply(this,arguments);}function _doGetRootNode(){_doGetRootNode=asyncToGenerator_asyncToGenerator(/*#__PURE__*/regeneratorRuntime_regeneratorRuntime().mark(function _callee(api,rs){var shortNames,r,builder,_builder$buildRoot,_builder$buildRoot2,node;return regeneratorRuntime_regeneratorRuntime().wrap(function _callee$(_context){while(1)switch(_context.prev=_context.next){case 0:_context.next=2;return api.getShortNames();case 2:shortNames=_context.sent;_context.next=5;return api.getResult(rs.id);case 5:r=_context.sent;builder=new NodeBuilder({shortNames:shortNames,summary:rs,commandResult:r});_builder$buildRoot=builder.buildRoot(),_builder$buildRoot2=slicedToArray_slicedToArray(_builder$buildRoot,1),node=_builder$buildRoot2[0];return _context.abrupt("return",node);case 9:case"end":return _context.stop();}},_callee);}));return _doGetRootNode.apply(this,arguments);}var CardContent=/*#__PURE__*/react.memo(function(props){var _useSidePanelTabs=useSidePanelTabs(props.provider),tabs=_useSidePanelTabs.tabs,selectedTab=_useSidePanelTabs.selectedTab,handleTabChange=_useSidePanelTabs.handleTabChange;if(!props.provider||!selectedTab||!tabs.find(function(x){return x.label===selectedTab;})){return null;}return/*#__PURE__*/(0,jsx_runtime.jsxs)(TabContext,{value:selectedTab,children:[/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{height:"36px",flex:"0 0 auto",p:"0 30px",mt:"12px",children:/*#__PURE__*/(0,jsx_runtime.jsx)(TabList_TabList,{onChange:handleTabChange,children:tabs.map(function(tab,i){return/*#__PURE__*/(0,jsx_runtime.jsx)(Tab_Tab,{label:tab.label,value:tab.label},tab.label);})})}),/*#__PURE__*/(0,jsx_runtime.jsx)(Divider_Divider,{sx:{margin:0}}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{overflow:"auto",p:"30px",children:tabs.map(function(tab){return/*#__PURE__*/(0,jsx_runtime.jsx)(TabPanel_TabPanel,{value:tab.label,sx:{padding:0},children:tab.content},tab.label);})})]});});var ArrowButton=/*#__PURE__*/react.memo(function(props){var Icon={left:TriangleLeftLightIcon,right:TriangleRightLightIcon}[props.direction];return/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{flex:"0 0 auto",height:"100%",width:"80px",display:"flex",justifyContent:"center",alignItems:"center",children:!props.hidden&&/*#__PURE__*/(0,jsx_runtime.jsx)(IconButton_IconButton,{onClick:props.onClick,children:/*#__PURE__*/(0,jsx_runtime.jsx)(Icon,{})})});});var HistoryCard=/*#__PURE__*/react.memo(function(props){var navigate=dist_useNavigate();var api=(0,react.useContext)(ApiContext);var _useLoadingHelper=useLoadingHelper(function(){return doGetRootNode(api,props.rs);},[api,props.rs]),_useLoadingHelper2=slicedToArray_slicedToArray(_useLoadingHelper,3),loading=_useLoadingHelper2[0],loadingError=_useLoadingHelper2[1],node=_useLoadingHelper2[2];if(loadingError){return/*#__PURE__*/(0,jsx_runtime.jsx)(jsx_runtime.Fragment,{children:"Error"});}return/*#__PURE__*/(0,jsx_runtime.jsxs)(CardPaper,{sx:_objectSpread2({position:'relative'},props.sx),children:[/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{position:"absolute",right:"10px",top:"10px",children:props.transitionFinished&&/*#__PURE__*/(0,jsx_runtime.jsx)(IconButton_IconButton,{onClick:props.onClose,children:/*#__PURE__*/(0,jsx_runtime.jsx)(CloseLightIcon,{})})}),/*#__PURE__*/(0,jsx_runtime.jsxs)(material_Box_Box,{display:"flex",flexDirection:"column",height:"100%",justifyContent:"space-between",children:[/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{p:"0 16px",flex:"0 0 auto",children:/*#__PURE__*/(0,jsx_runtime.jsx)(CommandResultItemHeader,{rs:props.rs})}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{width:"100%",flex:"1 1 auto",overflow:"hidden",display:"flex",flexDirection:"column",children:props.transitionFinished&&(loading?/*#__PURE__*/(0,jsx_runtime.jsx)(Loading,{}):/*#__PURE__*/(0,jsx_runtime.jsx)(CardContent,{provider:node}))}),/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{flex:"0 0 auto",height:"39px",display:"flex",alignItems:"center",justifyContent:"end",p:"0 30px",children:/*#__PURE__*/(0,jsx_runtime.jsx)(IconButton_IconButton,{onClick:function onClick(e){e.stopPropagation();navigate("/results/".concat(props.rs.id));},sx:{padding:0,width:32,height:32},children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Tooltip_Tooltip,{title:"Open Result Tree",children:/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{display:"flex",children:/*#__PURE__*/(0,jsx_runtime.jsx)(TreeViewIcon,{})})})})})]})]});});var HistoryCards=/*#__PURE__*/react.memo(function(props){var theme=styles_useTheme_useTheme();var containerElem=(0,react.useRef)();var _useState=(0,react.useState)(),_useState2=slicedToArray_slicedToArray(_useState,2),cardRect=_useState2[0],setCardRect=_useState2[1];var _useState3=(0,react.useState)('not-started'),_useState4=slicedToArray_slicedToArray(_useState3,2),transitionStatus=_useState4[0],setTransitionStatus=_useState4[1];var _useState5=(0,react.useState)(props.rs),_useState6=slicedToArray_slicedToArray(_useState5,2),currentRS=_useState6[0],setCurrentRS=_useState6[1];(0,react.useEffect)(function(){var _containerElem$curren;var rect=(_containerElem$curren=containerElem.current)===null||_containerElem$curren===void 0?void 0:_containerElem$curren.getBoundingClientRect();if(!rect){setCardRect(undefined);return;}var initialRect={left:props.initialCardRect.left-rect.left,top:props.initialCardRect.top-rect.top,width:cardWidth,height:cardHeight};setCardRect(initialRect);},[props.initialCardRect]);(0,react.useEffect)(function(){if(!cardRect){return;}var targetRect={left:0,top:0,width:'100%',height:'100%'};if(cardRect.left===targetRect.left&&cardRect.top===targetRect.top&&cardRect.width===targetRect.width&&cardRect.height===targetRect.height){return;}setTimeout(function(){setCardRect(targetRect);setTransitionStatus('running');setTimeout(function(){setTransitionStatus('finished');},theme.transitions.duration.enteringScreen);},10);},[cardRect,theme.transitions.duration.enteringScreen]);var currentRSIndex=(0,react.useMemo)(function(){return props.ts.commandResults.indexOf(currentRS);},[currentRS,props.ts.commandResults]);var onLeftArrowClick=(0,react.useCallback)(function(){if(currentRSIndex>0){setCurrentRS(props.ts.commandResults[currentRSIndex-1]);}},[currentRSIndex,props.ts.commandResults]);var onRightArrowClick=(0,react.useCallback)(function(){if(currentRSIndex0){setCurrentRS(props.ts.commandResults[currentRSIndex-1]);}},[currentRSIndex,props.ts.commandResults]);var onRightArrowClick=(0,react.useCallback)(function(){if(currentRSIndex { }); +const arrowButtonWidth = 80; + const ArrowButton = React.memo((props: { direction: 'left' | 'right', onClick: () => void, @@ -80,7 +82,16 @@ const ArrowButton = React.memo((props: { right: TriangleRightLightIcon }[props.direction]; - return + return {!props.hidden && @@ -236,11 +247,16 @@ export const HistoryCards = React.memo((props: HistoryCardsProps) => { } }, [currentRSIndex, props.ts.commandResults]); + const paddingX = 40; + const gap = 2 * (paddingX + arrowButtonWidth); + return { hidden={currentRSIndex === 0 || transitionStatus !== 'finished'} /> {transitionStatus !== 'finished' && cardRect && { width='100%' height='100%' display='flex' - gap='20px' + gap={`${gap}px`} sx={{ - translate: `calc((-100% - 20px) * ${currentRSIndex})`, + translate: `calc((-100% - ${gap}px) * ${currentRSIndex})`, transition: theme.transitions.create(['translate'], { easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.enteringScreen, From ca1908e0f47d2c2865dc0debda39e20a8ef12851 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 15 Jun 2023 17:21:31 +0200 Subject: [PATCH 1853/2916] chore: Re-run npm build --- pkg/webui/ui/build/index.html | 2 +- pkg/webui/ui/build/static/css/main.css | 2 +- pkg/webui/ui/build/static/js/main.js | 76 +++++++++++++------------- 3 files changed, 40 insertions(+), 40 deletions(-) diff --git a/pkg/webui/ui/build/index.html b/pkg/webui/ui/build/index.html index d31444bc3..7b9e36bcf 100644 --- a/pkg/webui/ui/build/index.html +++ b/pkg/webui/ui/build/index.html @@ -1 +1 @@ -React App
    \ No newline at end of file +React App
    \ No newline at end of file diff --git a/pkg/webui/ui/build/static/css/main.css b/pkg/webui/ui/build/static/css/main.css index 38a0dc2e1..a8874843f 100644 --- a/pkg/webui/ui/build/static/css/main.css +++ b/pkg/webui/ui/build/static/css/main.css @@ -84,4 +84,4 @@ body, unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; } -/*# sourceMappingURL=main.94bdb1a0.css.map*/ \ No newline at end of file +/*# sourceMappingURL=main.9c2b54a7.css.map*/ \ No newline at end of file diff --git a/pkg/webui/ui/build/static/js/main.js b/pkg/webui/ui/build/static/js/main.js index bb1335e47..7fc426284 100644 --- a/pkg/webui/ui/build/static/js/main.js +++ b/pkg/webui/ui/build/static/js/main.js @@ -47615,7 +47615,7 @@ function SvgKluctlText(_ref, svgRef) { }))); } var ForwardRef = /*#__PURE__*/react.forwardRef(SvgKluctlText); -/* harmony default export */ var kluctl_text = (__webpack_require__.p + "static/media/kluctl-text.svg?f=kluctl-text.1392c9ce908d475e5086961a5b3a3072.svg"); +/* harmony default export */ var kluctl_text = (__webpack_require__.p + "static/media/kluctl-text.svg?f=kluctl-text.f97d4b1ffe2002c0802e2e1ad2044bab.svg"); ;// CONCATENATED MODULE: ./src/icons/git.svg var _defs; @@ -47683,7 +47683,7 @@ function SvgKluctlLogo(_ref, svgRef) { }))); } var kluctl_logo_ForwardRef = /*#__PURE__*/react.forwardRef(SvgKluctlLogo); -/* harmony default export */ var kluctl_logo = (__webpack_require__.p + "static/media/kluctl-logo.svg?f=kluctl-logo.928b5d24c522acfce937ce451da78cfd.svg"); +/* harmony default export */ var kluctl_logo = (__webpack_require__.p + "static/media/kluctl-logo.svg?f=kluctl-logo.252d0acc30f14f58acd1b5faac05c17b.svg"); ;// CONCATENATED MODULE: ./src/icons/search.svg var search_path, search_path2; @@ -47716,7 +47716,7 @@ function SvgSearch(_ref, svgRef) { }))); } var search_ForwardRef = /*#__PURE__*/react.forwardRef(SvgSearch); -/* harmony default export */ var search = (__webpack_require__.p + "static/media/search.svg?f=search.301f22f9e2228136d62b0edb7d97c86e.svg"); +/* harmony default export */ var search = (__webpack_require__.p + "static/media/search.svg?f=search.10cfa92f61b9621ea7fe3d64cac12eee.svg"); ;// CONCATENATED MODULE: ./src/icons/targets.svg var _circle, targets_path, targets_path2, targets_path3; @@ -47763,7 +47763,7 @@ function SvgTargets(_ref, svgRef) { }))); } var targets_ForwardRef = /*#__PURE__*/react.forwardRef(SvgTargets); -/* harmony default export */ var targets = (__webpack_require__.p + "static/media/targets.svg?f=targets.ddda17cc51371fc22f6fc66cb7986073.svg"); +/* harmony default export */ var targets = (__webpack_require__.p + "static/media/targets.svg?f=targets.2b39a3a176679d8e9ed00f0b3bfd3ba5.svg"); ;// CONCATENATED MODULE: ./src/icons/target.svg var _ellipse, target_path, target_path2; @@ -47808,7 +47808,7 @@ function SvgTarget(_ref, svgRef) { }))); } var target_ForwardRef = /*#__PURE__*/react.forwardRef(SvgTarget); -/* harmony default export */ var target = (__webpack_require__.p + "static/media/target.svg?f=target.0750f7a6c362d82cc7fb678f9260c46d.svg"); +/* harmony default export */ var target = (__webpack_require__.p + "static/media/target.svg?f=target.1dd672dc23cacf59e90fbb277c9bdd9d.svg"); ;// CONCATENATED MODULE: ./src/icons/relation-hline.svg var relation_hline_path, relation_hline_circle, _circle2; @@ -47852,7 +47852,7 @@ function SvgRelationHline(_ref, svgRef) { }))); } var relation_hline_ForwardRef = /*#__PURE__*/(/* unused pure expression or super */ null && (React.forwardRef(SvgRelationHline))); -/* harmony default export */ var relation_hline = (__webpack_require__.p + "static/media/relation-hline.svg?f=relation-hline.871cfae94c944beb7d05364a2cee37f1.svg"); +/* harmony default export */ var relation_hline = (__webpack_require__.p + "static/media/relation-hline.svg?f=relation-hline.91c9012405859f512c983b2985999ad7.svg"); ;// CONCATENATED MODULE: ./src/icons/project.svg var project_ellipse, project_path, project_path2; @@ -47899,7 +47899,7 @@ function SvgProject(_ref, svgRef) { }))); } var project_ForwardRef = /*#__PURE__*/react.forwardRef(SvgProject); -/* harmony default export */ var project = (__webpack_require__.p + "static/media/project.svg?f=project.637cf85e6cadcedc900412158dfa7372.svg"); +/* harmony default export */ var project = (__webpack_require__.p + "static/media/project.svg?f=project.66e77c7ad83b85f0325c2e0ffed64b8c.svg"); ;// CONCATENATED MODULE: ./src/icons/cpu.svg var cpu_path, cpu_path2, cpu_path3, cpu_path4, cpu_path5, _path6, _path7, _path8, _path9, _path10, _path11, _path12, _path13, _path14; @@ -47968,7 +47968,7 @@ function SvgCpu(_ref, svgRef) { }))); } var cpu_ForwardRef = /*#__PURE__*/react.forwardRef(SvgCpu); -/* harmony default export */ var cpu = (__webpack_require__.p + "static/media/cpu.svg?f=cpu.b47d73a3f55d2fa8fa37d8ed73d178c4.svg"); +/* harmony default export */ var cpu = (__webpack_require__.p + "static/media/cpu.svg?f=cpu.a3fa0b9152cb81606a7da57c1b07cc85.svg"); ;// CONCATENATED MODULE: ./src/icons/finger-scan.svg var finger_scan_path, finger_scan_path2, finger_scan_path3, finger_scan_path4, finger_scan_path5, finger_scan_path6; @@ -48034,7 +48034,7 @@ function SvgFingerScan(_ref, svgRef) { }))); } var finger_scan_ForwardRef = /*#__PURE__*/react.forwardRef(SvgFingerScan); -/* harmony default export */ var finger_scan = (__webpack_require__.p + "static/media/finger-scan.svg?f=finger-scan.2c034cf5598ca4927432b008dc2304c4.svg"); +/* harmony default export */ var finger_scan = (__webpack_require__.p + "static/media/finger-scan.svg?f=finger-scan.ab3c987f3fd693b908ef65df4c2cd433.svg"); ;// CONCATENATED MODULE: ./src/icons/tree-view.svg var tree_view_path, tree_view_path2, tree_view_path3, tree_view_path4, tree_view_path5; @@ -48076,7 +48076,7 @@ function SvgTreeView(_ref, svgRef) { }))); } var tree_view_ForwardRef = /*#__PURE__*/react.forwardRef(SvgTreeView); -/* harmony default export */ var tree_view = (__webpack_require__.p + "static/media/tree-view.svg?f=tree-view.3c49a19936c3ecef1167c9ae1aad9102.svg"); +/* harmony default export */ var tree_view = (__webpack_require__.p + "static/media/tree-view.svg?f=tree-view.a2f07b1cebd3235db357cc2b6e4c8c0d.svg"); ;// CONCATENATED MODULE: ./src/icons/close.svg var close_path; @@ -48105,7 +48105,7 @@ function SvgClose(_ref, svgRef) { }))); } var close_ForwardRef = /*#__PURE__*/react.forwardRef(SvgClose); -/* harmony default export */ var icons_close = (__webpack_require__.p + "static/media/close.svg?f=close.06c69ef83805e9ea03cc487136b57b43.svg"); +/* harmony default export */ var icons_close = (__webpack_require__.p + "static/media/close.svg?f=close.76fdca7f91598011401cb97d4b490d52.svg"); ;// CONCATENATED MODULE: ./src/icons/close-light.svg var close_light_path; @@ -48134,7 +48134,7 @@ function SvgCloseLight(_ref, svgRef) { }))); } var close_light_ForwardRef = /*#__PURE__*/react.forwardRef(SvgCloseLight); -/* harmony default export */ var close_light = (__webpack_require__.p + "static/media/close-light.svg?f=close-light.3f339905970f9885aae8cc76977a1abb.svg"); +/* harmony default export */ var close_light = (__webpack_require__.p + "static/media/close-light.svg?f=close-light.eb8a7d21cff89473a30809cdaacf6446.svg"); ;// CONCATENATED MODULE: ./src/icons/deploy.svg var deploy_circle, deploy_path, _g; @@ -48186,7 +48186,7 @@ function SvgDeploy(_ref, svgRef) { })))); } var deploy_ForwardRef = /*#__PURE__*/react.forwardRef(SvgDeploy); -/* harmony default export */ var deploy = (__webpack_require__.p + "static/media/deploy.svg?f=deploy.62f738f9b93ba10869f740e158192270.svg"); +/* harmony default export */ var deploy = (__webpack_require__.p + "static/media/deploy.svg?f=deploy.2d28f597e81eb12910b9816b567b11ce.svg"); ;// CONCATENATED MODULE: ./src/icons/prune.svg var prune_circle, prune_path, prune_path2, prune_path3, prune_path4, prune_path5; @@ -48250,7 +48250,7 @@ function SvgPrune(_ref, svgRef) { }))); } var prune_ForwardRef = /*#__PURE__*/react.forwardRef(SvgPrune); -/* harmony default export */ var prune = (__webpack_require__.p + "static/media/prune.svg?f=prune.ac5564f5a55a590b9d82c5e6aac239fd.svg"); +/* harmony default export */ var prune = (__webpack_require__.p + "static/media/prune.svg?f=prune.a5f592f5851c37276e764af73b2a8a56.svg"); ;// CONCATENATED MODULE: ./src/icons/diff.svg var diff_circle, diff_g, diff_path; @@ -48301,7 +48301,7 @@ function SvgDiff(_ref, svgRef) { }))); } var diff_ForwardRef = /*#__PURE__*/react.forwardRef(SvgDiff); -/* harmony default export */ var diff = (__webpack_require__.p + "static/media/diff.svg?f=diff.e79db65b5d7c3fb43104133da0dc8368.svg"); +/* harmony default export */ var diff = (__webpack_require__.p + "static/media/diff.svg?f=diff.37d81f4e01925715c51f42684bf21554.svg"); ;// CONCATENATED MODULE: ./src/icons/warning.svg var warning_path, warning_path2, warning_path3; @@ -48336,7 +48336,7 @@ function SvgWarning(_ref, svgRef) { }))); } var warning_ForwardRef = /*#__PURE__*/react.forwardRef(SvgWarning); -/* harmony default export */ var icons_warning = (__webpack_require__.p + "static/media/warning.svg?f=warning.ddf48b11eb0bb3fbca783743bfd0ae08.svg"); +/* harmony default export */ var icons_warning = (__webpack_require__.p + "static/media/warning.svg?f=warning.dd1c59340347be553c83108e04d6be04.svg"); ;// CONCATENATED MODULE: ./src/icons/error.svg var error_path, error_path2; @@ -48368,7 +48368,7 @@ function SvgError(_ref, svgRef) { }))); } var error_ForwardRef = /*#__PURE__*/react.forwardRef(SvgError); -/* harmony default export */ var error = (__webpack_require__.p + "static/media/error.svg?f=error.ee4b3e05eeb403dd3e8a8f65cc782fd4.svg"); +/* harmony default export */ var error = (__webpack_require__.p + "static/media/error.svg?f=error.c59bc8c7d7f86a588a16bc6ec27d448a.svg"); ;// CONCATENATED MODULE: ./src/icons/trash.svg var trash_path, trash_path2, trash_path3, trash_path4; @@ -48411,7 +48411,7 @@ function SvgTrash(_ref, svgRef) { }))); } var trash_ForwardRef = /*#__PURE__*/react.forwardRef(SvgTrash); -/* harmony default export */ var trash = (__webpack_require__.p + "static/media/trash.svg?f=trash.025b2d3120e905c307efe6236bb4d3a2.svg"); +/* harmony default export */ var trash = (__webpack_require__.p + "static/media/trash.svg?f=trash.b782f36f610aae639750393e0793c538.svg"); ;// CONCATENATED MODULE: ./src/icons/orphan.svg var orphan_path, orphan_path2, orphan_path3, orphan_path4; @@ -48453,7 +48453,7 @@ function SvgOrphan(_ref, svgRef) { }))); } var orphan_ForwardRef = /*#__PURE__*/react.forwardRef(SvgOrphan); -/* harmony default export */ var orphan = (__webpack_require__.p + "static/media/orphan.svg?f=orphan.05f9bb90537f7ff1ab68bc2c4806352c.svg"); +/* harmony default export */ var orphan = (__webpack_require__.p + "static/media/orphan.svg?f=orphan.6fde4d55f3fb9bbd2738b0b61fcee146.svg"); ;// CONCATENATED MODULE: ./src/icons/added.svg var added_path, added_path2; @@ -48486,7 +48486,7 @@ function SvgAdded(_ref, svgRef) { }))); } var added_ForwardRef = /*#__PURE__*/react.forwardRef(SvgAdded); -/* harmony default export */ var added = (__webpack_require__.p + "static/media/added.svg?f=added.54530b6cceb257eddeb8f0016f3c9821.svg"); +/* harmony default export */ var added = (__webpack_require__.p + "static/media/added.svg?f=added.a633ace445f83e4ab52d3c80cc788e1e.svg"); ;// CONCATENATED MODULE: ./src/icons/changed.svg var changed_path, changed_path2, changed_path3; @@ -48522,7 +48522,7 @@ function SvgChanged(_ref, svgRef) { }))); } var changed_ForwardRef = /*#__PURE__*/react.forwardRef(SvgChanged); -/* harmony default export */ var changed = (__webpack_require__.p + "static/media/changed.svg?f=changed.27c5207a7397f2557ac197a3636da39d.svg"); +/* harmony default export */ var changed = (__webpack_require__.p + "static/media/changed.svg?f=changed.a9c091de612ab63c8d38d83dbb73e505.svg"); ;// CONCATENATED MODULE: ./src/icons/checkbox.svg var _rect; @@ -48557,7 +48557,7 @@ function SvgCheckbox(_ref, svgRef) { }))); } var checkbox_ForwardRef = /*#__PURE__*/react.forwardRef(SvgCheckbox); -/* harmony default export */ var icons_checkbox = (__webpack_require__.p + "static/media/checkbox.svg?f=checkbox.bcc364182a1a03dc753e7402a023eee2.svg"); +/* harmony default export */ var icons_checkbox = (__webpack_require__.p + "static/media/checkbox.svg?f=checkbox.90148614b6ab0451aac6f71db92028fd.svg"); ;// CONCATENATED MODULE: ./src/icons/checkbox-checked.svg var checkbox_checked_rect, checkbox_checked_path, _rect2; @@ -48601,7 +48601,7 @@ function SvgCheckboxChecked(_ref, svgRef) { }))); } var checkbox_checked_ForwardRef = /*#__PURE__*/react.forwardRef(SvgCheckboxChecked); -/* harmony default export */ var checkbox_checked = (__webpack_require__.p + "static/media/checkbox-checked.svg?f=checkbox-checked.57f61c3bde08a33c0bd9136714a3d3b3.svg"); +/* harmony default export */ var checkbox_checked = (__webpack_require__.p + "static/media/checkbox-checked.svg?f=checkbox-checked.1c552519ffded79bb8ad25822c2573bb.svg"); ;// CONCATENATED MODULE: ./src/icons/checkbox-disabled.svg var checkbox_disabled_rect, checkbox_disabled_rect2; @@ -48644,7 +48644,7 @@ function SvgCheckboxDisabled(_ref, svgRef) { }))); } var checkbox_disabled_ForwardRef = /*#__PURE__*/(/* unused pure expression or super */ null && (React.forwardRef(SvgCheckboxDisabled))); -/* harmony default export */ var checkbox_disabled = (__webpack_require__.p + "static/media/checkbox-disabled.svg?f=checkbox-disabled.83ae46d3015ca4b4114b8a785857ef00.svg"); +/* harmony default export */ var checkbox_disabled = (__webpack_require__.p + "static/media/checkbox-disabled.svg?f=checkbox-disabled.f7de3b059a1f9fc60f809e7d3fd601e2.svg"); ;// CONCATENATED MODULE: ./src/icons/arrow-left.svg var arrow_left_path, arrow_left_path2; @@ -48677,7 +48677,7 @@ function SvgArrowLeft(_ref, svgRef) { }))); } var arrow_left_ForwardRef = /*#__PURE__*/react.forwardRef(SvgArrowLeft); -/* harmony default export */ var arrow_left = (__webpack_require__.p + "static/media/arrow-left.svg?f=arrow-left.55965a5b737ce5b390f6ce9053ef61f4.svg"); +/* harmony default export */ var arrow_left = (__webpack_require__.p + "static/media/arrow-left.svg?f=arrow-left.0e69d1eb25254a957b660d86b58b7594.svg"); ;// CONCATENATED MODULE: ./src/icons/warning-sign.svg var warning_sign_path, warning_sign_path2, warning_sign_path3; @@ -48713,7 +48713,7 @@ function SvgWarningSign(_ref, svgRef) { }))); } var warning_sign_ForwardRef = /*#__PURE__*/react.forwardRef(SvgWarningSign); -/* harmony default export */ var warning_sign = (__webpack_require__.p + "static/media/warning-sign.svg?f=warning-sign.a4dd71daaeea7e2f3212daa2159c2c55.svg"); +/* harmony default export */ var warning_sign = (__webpack_require__.p + "static/media/warning-sign.svg?f=warning-sign.fb9ebdb743ce95ba858869fcc9772754.svg"); ;// CONCATENATED MODULE: ./src/icons/changes.svg var changes_path, changes_path2, changes_path3; @@ -48750,7 +48750,7 @@ function SvgChanges(_ref, svgRef) { }))); } var changes_ForwardRef = /*#__PURE__*/react.forwardRef(SvgChanges); -/* harmony default export */ var changes = (__webpack_require__.p + "static/media/changes.svg?f=changes.ceb3f50f60cb8242ab5c9a5bfecb20f8.svg"); +/* harmony default export */ var changes = (__webpack_require__.p + "static/media/changes.svg?f=changes.b1e048d4a195559cfe9376069cd93bea.svg"); ;// CONCATENATED MODULE: ./src/icons/star.svg var star_path, star_path2; @@ -48783,7 +48783,7 @@ function SvgStar(_ref, svgRef) { }))); } var star_ForwardRef = /*#__PURE__*/react.forwardRef(SvgStar); -/* harmony default export */ var star = (__webpack_require__.p + "static/media/star.svg?f=star.231e6e122fdf32a9487349a189a2a100.svg"); +/* harmony default export */ var star = (__webpack_require__.p + "static/media/star.svg?f=star.0c433d1c6ccb36781955621fc40f0540.svg"); ;// CONCATENATED MODULE: ./src/icons/triangle-down.svg var triangle_down_path, triangle_down_path2; @@ -48816,7 +48816,7 @@ function SvgTriangleDown(_ref, svgRef) { }))); } var triangle_down_ForwardRef = /*#__PURE__*/react.forwardRef(SvgTriangleDown); -/* harmony default export */ var triangle_down = (__webpack_require__.p + "static/media/triangle-down.svg?f=triangle-down.60eb89e2ef7264593e9b4daf2377cc6c.svg"); +/* harmony default export */ var triangle_down = (__webpack_require__.p + "static/media/triangle-down.svg?f=triangle-down.cb859bf44378ddfd76730eb4a9f90a89.svg"); ;// CONCATENATED MODULE: ./src/icons/triangle-left-light.svg var triangle_left_light_path, triangle_left_light_path2; @@ -48850,7 +48850,7 @@ function SvgTriangleLeftLight(_ref, svgRef) { }))); } var triangle_left_light_ForwardRef = /*#__PURE__*/react.forwardRef(SvgTriangleLeftLight); -/* harmony default export */ var triangle_left_light = (__webpack_require__.p + "static/media/triangle-left-light.svg?f=triangle-left-light.65fea1fe6bee135dd5ccdcfd6ee5ba02.svg"); +/* harmony default export */ var triangle_left_light = (__webpack_require__.p + "static/media/triangle-left-light.svg?f=triangle-left-light.723aa352c7195ed6c2c94748badb018c.svg"); ;// CONCATENATED MODULE: ./src/icons/triangle-right-light.svg var triangle_right_light_path, triangle_right_light_path2; @@ -48883,7 +48883,7 @@ function SvgTriangleRightLight(_ref, svgRef) { }))); } var triangle_right_light_ForwardRef = /*#__PURE__*/react.forwardRef(SvgTriangleRightLight); -/* harmony default export */ var triangle_right_light = (__webpack_require__.p + "static/media/triangle-right-light.svg?f=triangle-right-light.0436dfced580e6b8bf6a64dbbb6b6ebd.svg"); +/* harmony default export */ var triangle_right_light = (__webpack_require__.p + "static/media/triangle-right-light.svg?f=triangle-right-light.22d8903e553c49bf9b0100b61fe7254f.svg"); ;// CONCATENATED MODULE: ./src/icons/triangle-right.svg var triangle_right_path, triangle_right_path2; @@ -48917,7 +48917,7 @@ function SvgTriangleRight(_ref, svgRef) { }))); } var triangle_right_ForwardRef = /*#__PURE__*/react.forwardRef(SvgTriangleRight); -/* harmony default export */ var triangle_right = (__webpack_require__.p + "static/media/triangle-right.svg?f=triangle-right.4ec8dee9a1ea90ac8936f504d81720ca.svg"); +/* harmony default export */ var triangle_right = (__webpack_require__.p + "static/media/triangle-right.svg?f=triangle-right.795bcf3f22906edc57cecb48328c9f89.svg"); ;// CONCATENATED MODULE: ./src/icons/brackets-curly.svg var brackets_curly_path; @@ -48946,7 +48946,7 @@ function SvgBracketsCurly(_ref, svgRef) { }))); } var brackets_curly_ForwardRef = /*#__PURE__*/react.forwardRef(SvgBracketsCurly); -/* harmony default export */ var brackets_curly = (__webpack_require__.p + "static/media/brackets-curly.svg?f=brackets-curly.d831d285bb85a307a76da8c13f76fa9b.svg"); +/* harmony default export */ var brackets_curly = (__webpack_require__.p + "static/media/brackets-curly.svg?f=brackets-curly.99e04241d2e10a7cfb60d2c695401adc.svg"); ;// CONCATENATED MODULE: ./src/icons/brackets-square.svg var brackets_square_path; @@ -48975,7 +48975,7 @@ function SvgBracketsSquare(_ref, svgRef) { }))); } var brackets_square_ForwardRef = /*#__PURE__*/react.forwardRef(SvgBracketsSquare); -/* harmony default export */ var brackets_square = (__webpack_require__.p + "static/media/brackets-square.svg?f=brackets-square.c743c4f98f9a2cdddff2e1ec7843d74c.svg"); +/* harmony default export */ var brackets_square = (__webpack_require__.p + "static/media/brackets-square.svg?f=brackets-square.3daecb55d1cd657d5682c02cfe8563d5.svg"); ;// CONCATENATED MODULE: ./src/icons/file.svg var file_path, file_path2, file_path3; @@ -49011,7 +49011,7 @@ function SvgFile(_ref, svgRef) { }))); } var file_ForwardRef = /*#__PURE__*/react.forwardRef(SvgFile); -/* harmony default export */ var file = (__webpack_require__.p + "static/media/file.svg?f=file.82db4ab2b22fca5dc01ef7cc944e4cae.svg"); +/* harmony default export */ var file = (__webpack_require__.p + "static/media/file.svg?f=file.f2670ef3fe4fae6122bb957ea2390db0.svg"); ;// CONCATENATED MODULE: ./src/icons/result.svg var result_circle, result_path, result_g; @@ -49063,7 +49063,7 @@ function SvgResult(_ref, svgRef) { })))); } var result_ForwardRef = /*#__PURE__*/(/* unused pure expression or super */ null && (React.forwardRef(SvgResult))); -/* harmony default export */ var result = (__webpack_require__.p + "static/media/result.svg?f=result.f37cb0485efb4bdd26bde5626dcc236b.svg"); +/* harmony default export */ var result = (__webpack_require__.p + "static/media/result.svg?f=result.cc7a9a9173a072950aee17843ff145ba.svg"); ;// CONCATENATED MODULE: ./src/icons/include.svg var include_circle, include_path, include_path2, include_path3, include_path4; @@ -49125,7 +49125,7 @@ function SvgInclude(_ref, svgRef) { }))); } var include_ForwardRef = /*#__PURE__*/react.forwardRef(SvgInclude); -/* harmony default export */ var include = (__webpack_require__.p + "static/media/include.svg?f=include.758427058cde3728cc49a2a93848150c.svg"); +/* harmony default export */ var include = (__webpack_require__.p + "static/media/include.svg?f=include.6bf4795a2f5623b297789116effd0bec.svg"); ;// CONCATENATED MODULE: ./src/icons/Icons.tsx var KluctlText=function KluctlText(){return/*#__PURE__*/(0,jsx_runtime.jsx)(ForwardRef,{width:"115px",height:"33px"});};var GitIcon=function GitIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(git_ForwardRef,{width:"35px",height:"35px"});};var KluctlLogo=function KluctlLogo(){return/*#__PURE__*/(0,jsx_runtime.jsx)(kluctl_logo_ForwardRef,{width:"50px",height:"50px"});};var SearchIcon=function SearchIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(search_ForwardRef,{width:"27px",height:"27px"});};var TargetsIcon=function TargetsIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(targets_ForwardRef,{width:"48px",height:"48px"});};var TargetIcon=function TargetIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(target_ForwardRef,{width:"45px",height:"45px"});};var RelationHLine=function RelationHLine(){return/*#__PURE__*/_jsx(RelationHLineSvg,{width:"169px",height:"12px"});};var ProjectIcon=function ProjectIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(project_ForwardRef,{width:"45px",height:"45px"});};var DeployIcon=function DeployIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(deploy_ForwardRef,{width:"45px",height:"45px"});};var PruneIcon=function PruneIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(prune_ForwardRef,{width:"45px",height:"45px"});};var DiffIcon=function DiffIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(diff_ForwardRef,{width:"45px",height:"45px"});};var CpuIcon=function CpuIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(cpu_ForwardRef,{width:"24px",height:"24px"});};var FingerScanIcon=function FingerScanIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(finger_scan_ForwardRef,{width:"24px",height:"24px"});};var MessageQuestionIcon=function MessageQuestionIcon(props){return/*#__PURE__*/(0,jsx_runtime.jsx)("svg",{width:"24",height:"24",viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg",children:/*#__PURE__*/(0,jsx_runtime.jsx)("path",{d:"M17 2.42999H7C4 2.42999 2 4.42999 2 7.42999V13.43C2 16.43 4 18.43 7 18.43V20.56C7 21.36 7.89 21.84 8.55 21.39L13 18.43H17C20 18.43 22 16.43 22 13.43V7.42999C22 4.42999 20 2.42999 17 2.42999ZM12 14.6C11.58 14.6 11.25 14.26 11.25 13.85C11.25 13.44 11.58 13.1 12 13.1C12.42 13.1 12.75 13.44 12.75 13.85C12.75 14.26 12.42 14.6 12 14.6ZM13.26 10.45C12.87 10.71 12.75 10.88 12.75 11.16V11.37C12.75 11.78 12.41 12.12 12 12.12C11.59 12.12 11.25 11.78 11.25 11.37V11.16C11.25 9.99999 12.1 9.42999 12.42 9.20999C12.79 8.95999 12.91 8.78999 12.91 8.52999C12.91 8.02999 12.5 7.61999 12 7.61999C11.5 7.61999 11.09 8.02999 11.09 8.52999C11.09 8.93999 10.75 9.27999 10.34 9.27999C9.93 9.27999 9.59 8.93999 9.59 8.52999C9.59 7.19999 10.67 6.11999 12 6.11999C13.33 6.11999 14.41 7.19999 14.41 8.52999C14.41 9.66999 13.57 10.24 13.26 10.45Z",fill:props.color})});};var TreeViewIcon=function TreeViewIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(tree_view_ForwardRef,{width:"26px",height:"26px"});};var CloseIcon=function CloseIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(close_ForwardRef,{width:"24px",height:"24px"});};var CloseLightIcon=function CloseLightIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(close_light_ForwardRef,{width:"24px",height:"24px"});};var WarningIcon=function WarningIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(warning_ForwardRef,{width:"24px",height:"24px"});};var ErrorIcon=function ErrorIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(error_ForwardRef,{width:"24px",height:"24px"});};var TrashIcon=function TrashIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(trash_ForwardRef,{width:"24px",height:"24px"});};var OrphanIcon=function OrphanIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(orphan_ForwardRef,{width:"24px",height:"24px"});};var AddedIcon=function AddedIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(added_ForwardRef,{width:"24px",height:"24px"});};var ChangedIcon=function ChangedIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(changed_ForwardRef,{width:"24px",height:"24px"});};var CheckboxIcon=function CheckboxIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(checkbox_ForwardRef,{width:"24px",height:"24px"});};var CheckboxCheckedIcon=function CheckboxCheckedIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(checkbox_checked_ForwardRef,{width:"24px",height:"24px"});};var CheckboxDisabledIcon=function CheckboxDisabledIcon(){return/*#__PURE__*/_jsx(CheckboxDisabledIconSvg,{width:"24px",height:"24px"});};var ArrowLeftIcon=function ArrowLeftIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(arrow_left_ForwardRef,{width:"40px",height:"40px"});};var WarningSignIcon=function WarningSignIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(warning_sign_ForwardRef,{width:"21px",height:"21px"});};var ChangesIcon=function ChangesIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(changes_ForwardRef,{width:"21px",height:"21px"});};var StarIcon=function StarIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(star_ForwardRef,{width:"21px",height:"21px"});};var TriangleDownIcon=function TriangleDownIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(triangle_down_ForwardRef,{width:"50px",height:"50px"});};var TriangleLeftLightIcon=function TriangleLeftLightIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(triangle_left_light_ForwardRef,{width:"50px",height:"50px"});};var TriangleRightLightIcon=function TriangleRightLightIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(triangle_right_light_ForwardRef,{width:"50px",height:"50px"});};var TriangleRightIcon=function TriangleRightIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(triangle_right_ForwardRef,{width:"50px",height:"50px"});};var BracketsCurlyIcon=function BracketsCurlyIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(brackets_curly_ForwardRef,{width:"22px",height:"18px"});};var BracketsSquareIcon=function BracketsSquareIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(brackets_square_ForwardRef,{width:"22px",height:"18px"});};var FileIcon=function FileIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(file_ForwardRef,{width:"40px",height:"40px"});};var ResultIcon=function ResultIcon(){return/*#__PURE__*/_jsx(ResultIconSvg,{width:"30px",height:"30px"});};var IncludeIcon=function IncludeIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(include_ForwardRef,{width:"30px",height:"30px"});}; @@ -61549,4 +61549,4 @@ src_reportWebVitals(); }(); /******/ })() ; -//# sourceMappingURL=main.5e199973.js.map \ No newline at end of file +//# sourceMappingURL=main.45a7295d.js.map \ No newline at end of file From fdbc61129ae175a3abf4b77e233c6bb86eebcf84 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 15 Jun 2023 22:06:57 +0200 Subject: [PATCH 1854/2916] fix: Enforce LF line-ending for .svg files (#586) --- pkg/webui/ui/.gitattributes | 1 + 1 file changed, 1 insertion(+) create mode 100644 pkg/webui/ui/.gitattributes diff --git a/pkg/webui/ui/.gitattributes b/pkg/webui/ui/.gitattributes new file mode 100644 index 000000000..0c34d559f --- /dev/null +++ b/pkg/webui/ui/.gitattributes @@ -0,0 +1 @@ +*.svg text eol=lf \ No newline at end of file From eda6ac8f62107e3949c4d1a303d8c050100d1fec Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 15 Jun 2023 22:12:07 +0200 Subject: [PATCH 1855/2916] fix: Also treat *.map as eol=lf (#588) --- pkg/webui/ui/.gitattributes | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/webui/ui/.gitattributes b/pkg/webui/ui/.gitattributes index 0c34d559f..447692d7d 100644 --- a/pkg/webui/ui/.gitattributes +++ b/pkg/webui/ui/.gitattributes @@ -1 +1,2 @@ -*.svg text eol=lf \ No newline at end of file +*.svg text eol=lf +*.map text eol=lf From 121660189e0fac13a89b026060c0290fd3d331f7 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 16 Jun 2023 00:34:09 +0200 Subject: [PATCH 1856/2916] fix: Instead of using .gitattributes to fix CRLF issues, implement it via fix-assets (#589) --- pkg/webui/ui/.gitattributes | 2 - pkg/webui/ui/build/index.html | 2 +- pkg/webui/ui/build/static/css/main.css | 12 ++-- pkg/webui/ui/build/static/js/787.chunk.js | 2 +- pkg/webui/ui/build/static/js/main.js | 78 +++++++++++------------ pkg/webui/ui/fix-assets/main.go | 63 ++++++++++++++++-- 6 files changed, 105 insertions(+), 54 deletions(-) delete mode 100644 pkg/webui/ui/.gitattributes diff --git a/pkg/webui/ui/.gitattributes b/pkg/webui/ui/.gitattributes deleted file mode 100644 index 447692d7d..000000000 --- a/pkg/webui/ui/.gitattributes +++ /dev/null @@ -1,2 +0,0 @@ -*.svg text eol=lf -*.map text eol=lf diff --git a/pkg/webui/ui/build/index.html b/pkg/webui/ui/build/index.html index 7b9e36bcf..044a50d48 100644 --- a/pkg/webui/ui/build/index.html +++ b/pkg/webui/ui/build/index.html @@ -1 +1 @@ -React App
    \ No newline at end of file +React App
    \ No newline at end of file diff --git a/pkg/webui/ui/build/static/css/main.css b/pkg/webui/ui/build/static/css/main.css index a8874843f..86c1ca612 100644 --- a/pkg/webui/ui/build/static/css/main.css +++ b/pkg/webui/ui/build/static/css/main.css @@ -36,7 +36,7 @@ body, font-display: swap; font-display: var(--fontsource-display, swap); font-weight: 200 1000; - src: url(../../static/media/nunito-cyrillic-ext-wght-normal.woff2?f=nunito-cyrillic-ext-wght-normal.c18a3502d6473272bdc3.woff2) format('woff2-variations'); + src: url(../../static/media/nunito-cyrillic-ext-wght-normal.woff2?h=b2611fa3f916d23df9b0735ba668944500ff23b73f9da4fbb10818c875d482a0) format('woff2-variations'); unicode-range: U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F; } @@ -47,7 +47,7 @@ body, font-display: swap; font-display: var(--fontsource-display, swap); font-weight: 200 1000; - src: url(../../static/media/nunito-cyrillic-wght-normal.woff2?f=nunito-cyrillic-wght-normal.ab3faa41df1758ee5b88.woff2) format('woff2-variations'); + src: url(../../static/media/nunito-cyrillic-wght-normal.woff2?h=7ca4b4bb8be6840990cc92b2dee938f142df99c93ce85063b391a09369b63b17) format('woff2-variations'); unicode-range: U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116; } @@ -58,7 +58,7 @@ body, font-display: swap; font-display: var(--fontsource-display, swap); font-weight: 200 1000; - src: url(../../static/media/nunito-vietnamese-wght-normal.woff2?f=nunito-vietnamese-wght-normal.864aa311404227008fae.woff2) format('woff2-variations'); + src: url(../../static/media/nunito-vietnamese-wght-normal.woff2?h=0ef9726dbc36b5871efa4b0cfdc43fd1bfed5dd48aeb70dc8210e8cb9bc9247b) format('woff2-variations'); unicode-range: U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB; } @@ -69,7 +69,7 @@ body, font-display: swap; font-display: var(--fontsource-display, swap); font-weight: 200 1000; - src: url(../../static/media/nunito-latin-ext-wght-normal.woff2?f=nunito-latin-ext-wght-normal.b2120f41c4a82a277229.woff2) format('woff2-variations'); + src: url(../../static/media/nunito-latin-ext-wght-normal.woff2?h=89def7428656f40331c1430ee1dc1846ed1e30d7001707b548f9f816d27264a5) format('woff2-variations'); unicode-range: U+0100-02AF,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF; } @@ -80,8 +80,8 @@ body, font-display: swap; font-display: var(--fontsource-display, swap); font-weight: 200 1000; - src: url(../../static/media/nunito-latin-wght-normal.woff2?f=nunito-latin-wght-normal.07adf0b2f5cb89c62ba7.woff2) format('woff2-variations'); + src: url(../../static/media/nunito-latin-wght-normal.woff2?h=96217f1d27fb909f92b4a6b35a0d3d6775f2f0b4d136d27aee88547d3ed87357) format('woff2-variations'); unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; } -/*# sourceMappingURL=main.9c2b54a7.css.map*/ \ No newline at end of file +/*# sourceMappingURL=main.css.map?h=9212af710a168fca4ce287f416924bf456fbc497b2cc289b74211c435224c397*/ \ No newline at end of file diff --git a/pkg/webui/ui/build/static/js/787.chunk.js b/pkg/webui/ui/build/static/js/787.chunk.js index 748939666..15b544b3a 100644 --- a/pkg/webui/ui/build/static/js/787.chunk.js +++ b/pkg/webui/ui/build/static/js/787.chunk.js @@ -236,4 +236,4 @@ var e, /***/ }) }]); -//# sourceMappingURL=787.98a1313e.chunk.js.map \ No newline at end of file +//# sourceMappingURL=787.chunk.js.map?h=47820fdbf26705ec6b47ae95042786f8669103d43ced5b19492db0ef8a1a30eb \ No newline at end of file diff --git a/pkg/webui/ui/build/static/js/main.js b/pkg/webui/ui/build/static/js/main.js index 7fc426284..7f430b626 100644 --- a/pkg/webui/ui/build/static/js/main.js +++ b/pkg/webui/ui/build/static/js/main.js @@ -47615,7 +47615,7 @@ function SvgKluctlText(_ref, svgRef) { }))); } var ForwardRef = /*#__PURE__*/react.forwardRef(SvgKluctlText); -/* harmony default export */ var kluctl_text = (__webpack_require__.p + "static/media/kluctl-text.svg?f=kluctl-text.f97d4b1ffe2002c0802e2e1ad2044bab.svg"); +/* harmony default export */ var kluctl_text = (__webpack_require__.p + "static/media/kluctl-text.svg?h=5599a99d8e965c1475a0712854d4c20b8f920a28267ed65213cbdcba720a1a33"); ;// CONCATENATED MODULE: ./src/icons/git.svg var _defs; @@ -47654,7 +47654,7 @@ function SvgGit(_ref, svgRef) { }))); } var git_ForwardRef = /*#__PURE__*/react.forwardRef(SvgGit); -/* harmony default export */ var git = (__webpack_require__.p + "static/media/git.svg?f=git.53474401b92b10a2c44571348999e714.svg"); +/* harmony default export */ var git = (__webpack_require__.p + "static/media/git.svg?h=2989537d0badbe5ef04e33d0c26fed9dd7f949310e0eb0f85dcbf9ff43e1e231"); ;// CONCATENATED MODULE: ./src/icons/kluctl-logo.svg var kluctl_logo_path; @@ -47683,7 +47683,7 @@ function SvgKluctlLogo(_ref, svgRef) { }))); } var kluctl_logo_ForwardRef = /*#__PURE__*/react.forwardRef(SvgKluctlLogo); -/* harmony default export */ var kluctl_logo = (__webpack_require__.p + "static/media/kluctl-logo.svg?f=kluctl-logo.252d0acc30f14f58acd1b5faac05c17b.svg"); +/* harmony default export */ var kluctl_logo = (__webpack_require__.p + "static/media/kluctl-logo.svg?h=7d059c061a18c3766a797697efc167ad38bfd4871a68abd35614a49684ea83cb"); ;// CONCATENATED MODULE: ./src/icons/search.svg var search_path, search_path2; @@ -47716,7 +47716,7 @@ function SvgSearch(_ref, svgRef) { }))); } var search_ForwardRef = /*#__PURE__*/react.forwardRef(SvgSearch); -/* harmony default export */ var search = (__webpack_require__.p + "static/media/search.svg?f=search.10cfa92f61b9621ea7fe3d64cac12eee.svg"); +/* harmony default export */ var search = (__webpack_require__.p + "static/media/search.svg?h=7e9ab6fee66c4ecdfb04a958152201a6c415cea8e98368f513f7e38bbcb59265"); ;// CONCATENATED MODULE: ./src/icons/targets.svg var _circle, targets_path, targets_path2, targets_path3; @@ -47763,7 +47763,7 @@ function SvgTargets(_ref, svgRef) { }))); } var targets_ForwardRef = /*#__PURE__*/react.forwardRef(SvgTargets); -/* harmony default export */ var targets = (__webpack_require__.p + "static/media/targets.svg?f=targets.2b39a3a176679d8e9ed00f0b3bfd3ba5.svg"); +/* harmony default export */ var targets = (__webpack_require__.p + "static/media/targets.svg?h=a44bb94e204eb47bd105d1a987b6d514bb4b84d4e57c6597d7339a9dbc926224"); ;// CONCATENATED MODULE: ./src/icons/target.svg var _ellipse, target_path, target_path2; @@ -47808,7 +47808,7 @@ function SvgTarget(_ref, svgRef) { }))); } var target_ForwardRef = /*#__PURE__*/react.forwardRef(SvgTarget); -/* harmony default export */ var target = (__webpack_require__.p + "static/media/target.svg?f=target.1dd672dc23cacf59e90fbb277c9bdd9d.svg"); +/* harmony default export */ var target = (__webpack_require__.p + "static/media/target.svg?h=49b4146149b8d455ad856589b7c88f795a8a43c6ddba7d0014be1550fb552723"); ;// CONCATENATED MODULE: ./src/icons/relation-hline.svg var relation_hline_path, relation_hline_circle, _circle2; @@ -47852,7 +47852,7 @@ function SvgRelationHline(_ref, svgRef) { }))); } var relation_hline_ForwardRef = /*#__PURE__*/(/* unused pure expression or super */ null && (React.forwardRef(SvgRelationHline))); -/* harmony default export */ var relation_hline = (__webpack_require__.p + "static/media/relation-hline.svg?f=relation-hline.91c9012405859f512c983b2985999ad7.svg"); +/* harmony default export */ var relation_hline = (__webpack_require__.p + "static/media/relation-hline.svg?h=d8e3ff7b79ea251ab37f8117527bd452a5ca962eb1ca21c6e7b8e285ee57468c"); ;// CONCATENATED MODULE: ./src/icons/project.svg var project_ellipse, project_path, project_path2; @@ -47899,7 +47899,7 @@ function SvgProject(_ref, svgRef) { }))); } var project_ForwardRef = /*#__PURE__*/react.forwardRef(SvgProject); -/* harmony default export */ var project = (__webpack_require__.p + "static/media/project.svg?f=project.66e77c7ad83b85f0325c2e0ffed64b8c.svg"); +/* harmony default export */ var project = (__webpack_require__.p + "static/media/project.svg?h=44199fa94e808397355a59b736af8b3c2bf87f2f6a5205c31b415a873a4d4d8f"); ;// CONCATENATED MODULE: ./src/icons/cpu.svg var cpu_path, cpu_path2, cpu_path3, cpu_path4, cpu_path5, _path6, _path7, _path8, _path9, _path10, _path11, _path12, _path13, _path14; @@ -47968,7 +47968,7 @@ function SvgCpu(_ref, svgRef) { }))); } var cpu_ForwardRef = /*#__PURE__*/react.forwardRef(SvgCpu); -/* harmony default export */ var cpu = (__webpack_require__.p + "static/media/cpu.svg?f=cpu.a3fa0b9152cb81606a7da57c1b07cc85.svg"); +/* harmony default export */ var cpu = (__webpack_require__.p + "static/media/cpu.svg?h=4f20d8f5d919bccd9f172faa8762929f37ddd85a6e458fe84b53e349a1d1a125"); ;// CONCATENATED MODULE: ./src/icons/finger-scan.svg var finger_scan_path, finger_scan_path2, finger_scan_path3, finger_scan_path4, finger_scan_path5, finger_scan_path6; @@ -48034,7 +48034,7 @@ function SvgFingerScan(_ref, svgRef) { }))); } var finger_scan_ForwardRef = /*#__PURE__*/react.forwardRef(SvgFingerScan); -/* harmony default export */ var finger_scan = (__webpack_require__.p + "static/media/finger-scan.svg?f=finger-scan.ab3c987f3fd693b908ef65df4c2cd433.svg"); +/* harmony default export */ var finger_scan = (__webpack_require__.p + "static/media/finger-scan.svg?h=415c47592edb926869c3c59c6ef803d1faf7ab06b4d0110031222a21a61abc7f"); ;// CONCATENATED MODULE: ./src/icons/tree-view.svg var tree_view_path, tree_view_path2, tree_view_path3, tree_view_path4, tree_view_path5; @@ -48076,7 +48076,7 @@ function SvgTreeView(_ref, svgRef) { }))); } var tree_view_ForwardRef = /*#__PURE__*/react.forwardRef(SvgTreeView); -/* harmony default export */ var tree_view = (__webpack_require__.p + "static/media/tree-view.svg?f=tree-view.a2f07b1cebd3235db357cc2b6e4c8c0d.svg"); +/* harmony default export */ var tree_view = (__webpack_require__.p + "static/media/tree-view.svg?h=9ef6fdcafc96d72a91f43a39e2cd00344b439cd3f3ff289363ad0a8baada77a6"); ;// CONCATENATED MODULE: ./src/icons/close.svg var close_path; @@ -48105,7 +48105,7 @@ function SvgClose(_ref, svgRef) { }))); } var close_ForwardRef = /*#__PURE__*/react.forwardRef(SvgClose); -/* harmony default export */ var icons_close = (__webpack_require__.p + "static/media/close.svg?f=close.76fdca7f91598011401cb97d4b490d52.svg"); +/* harmony default export */ var icons_close = (__webpack_require__.p + "static/media/close.svg?h=f8d9dcdf085e62ba04b182f66ef408c71c13691bd83ed431aac09c981b3b3a8d"); ;// CONCATENATED MODULE: ./src/icons/close-light.svg var close_light_path; @@ -48134,7 +48134,7 @@ function SvgCloseLight(_ref, svgRef) { }))); } var close_light_ForwardRef = /*#__PURE__*/react.forwardRef(SvgCloseLight); -/* harmony default export */ var close_light = (__webpack_require__.p + "static/media/close-light.svg?f=close-light.eb8a7d21cff89473a30809cdaacf6446.svg"); +/* harmony default export */ var close_light = (__webpack_require__.p + "static/media/close-light.svg?h=19c3aa7cdc6bf35102d6f9437c6a0d4412e6e349c926b591122af8a8df11ee7e"); ;// CONCATENATED MODULE: ./src/icons/deploy.svg var deploy_circle, deploy_path, _g; @@ -48186,7 +48186,7 @@ function SvgDeploy(_ref, svgRef) { })))); } var deploy_ForwardRef = /*#__PURE__*/react.forwardRef(SvgDeploy); -/* harmony default export */ var deploy = (__webpack_require__.p + "static/media/deploy.svg?f=deploy.2d28f597e81eb12910b9816b567b11ce.svg"); +/* harmony default export */ var deploy = (__webpack_require__.p + "static/media/deploy.svg?h=5ef5e7631c6b158f7ed8e25d00e145bb139937a3def2af691a45ee930a906bcc"); ;// CONCATENATED MODULE: ./src/icons/prune.svg var prune_circle, prune_path, prune_path2, prune_path3, prune_path4, prune_path5; @@ -48250,7 +48250,7 @@ function SvgPrune(_ref, svgRef) { }))); } var prune_ForwardRef = /*#__PURE__*/react.forwardRef(SvgPrune); -/* harmony default export */ var prune = (__webpack_require__.p + "static/media/prune.svg?f=prune.a5f592f5851c37276e764af73b2a8a56.svg"); +/* harmony default export */ var prune = (__webpack_require__.p + "static/media/prune.svg?h=782022127a83a36b114977736d9e36c4101fce39ad822abee1a474eff352a604"); ;// CONCATENATED MODULE: ./src/icons/diff.svg var diff_circle, diff_g, diff_path; @@ -48301,7 +48301,7 @@ function SvgDiff(_ref, svgRef) { }))); } var diff_ForwardRef = /*#__PURE__*/react.forwardRef(SvgDiff); -/* harmony default export */ var diff = (__webpack_require__.p + "static/media/diff.svg?f=diff.37d81f4e01925715c51f42684bf21554.svg"); +/* harmony default export */ var diff = (__webpack_require__.p + "static/media/diff.svg?h=e5b9cc64086c22cbd40424e67cf80f9696e32fe5640c5cedce8774233059b146"); ;// CONCATENATED MODULE: ./src/icons/warning.svg var warning_path, warning_path2, warning_path3; @@ -48336,7 +48336,7 @@ function SvgWarning(_ref, svgRef) { }))); } var warning_ForwardRef = /*#__PURE__*/react.forwardRef(SvgWarning); -/* harmony default export */ var icons_warning = (__webpack_require__.p + "static/media/warning.svg?f=warning.dd1c59340347be553c83108e04d6be04.svg"); +/* harmony default export */ var icons_warning = (__webpack_require__.p + "static/media/warning.svg?h=3f478d508e7172505f66b7dadbd81d0e156360ff8bc0de0d12ec851c42ac864f"); ;// CONCATENATED MODULE: ./src/icons/error.svg var error_path, error_path2; @@ -48368,7 +48368,7 @@ function SvgError(_ref, svgRef) { }))); } var error_ForwardRef = /*#__PURE__*/react.forwardRef(SvgError); -/* harmony default export */ var error = (__webpack_require__.p + "static/media/error.svg?f=error.c59bc8c7d7f86a588a16bc6ec27d448a.svg"); +/* harmony default export */ var error = (__webpack_require__.p + "static/media/error.svg?h=9a026f75493e261450266b437a9e4a5c1cb049ba2ee56efa107230a3d057c468"); ;// CONCATENATED MODULE: ./src/icons/trash.svg var trash_path, trash_path2, trash_path3, trash_path4; @@ -48411,7 +48411,7 @@ function SvgTrash(_ref, svgRef) { }))); } var trash_ForwardRef = /*#__PURE__*/react.forwardRef(SvgTrash); -/* harmony default export */ var trash = (__webpack_require__.p + "static/media/trash.svg?f=trash.b782f36f610aae639750393e0793c538.svg"); +/* harmony default export */ var trash = (__webpack_require__.p + "static/media/trash.svg?h=414c070724e5b79d4382fb146751a613df58166e88df2baa42fffa71aee32f8a"); ;// CONCATENATED MODULE: ./src/icons/orphan.svg var orphan_path, orphan_path2, orphan_path3, orphan_path4; @@ -48453,7 +48453,7 @@ function SvgOrphan(_ref, svgRef) { }))); } var orphan_ForwardRef = /*#__PURE__*/react.forwardRef(SvgOrphan); -/* harmony default export */ var orphan = (__webpack_require__.p + "static/media/orphan.svg?f=orphan.6fde4d55f3fb9bbd2738b0b61fcee146.svg"); +/* harmony default export */ var orphan = (__webpack_require__.p + "static/media/orphan.svg?h=75637a27d6cf40bee4022eff91c852ecca4f0bcc7c8c78bf3cd02cd76a5fba66"); ;// CONCATENATED MODULE: ./src/icons/added.svg var added_path, added_path2; @@ -48486,7 +48486,7 @@ function SvgAdded(_ref, svgRef) { }))); } var added_ForwardRef = /*#__PURE__*/react.forwardRef(SvgAdded); -/* harmony default export */ var added = (__webpack_require__.p + "static/media/added.svg?f=added.a633ace445f83e4ab52d3c80cc788e1e.svg"); +/* harmony default export */ var added = (__webpack_require__.p + "static/media/added.svg?h=af4dd83591778905da5dcbc1833893ff89a96495158cab04e1b7b415284b0388"); ;// CONCATENATED MODULE: ./src/icons/changed.svg var changed_path, changed_path2, changed_path3; @@ -48522,7 +48522,7 @@ function SvgChanged(_ref, svgRef) { }))); } var changed_ForwardRef = /*#__PURE__*/react.forwardRef(SvgChanged); -/* harmony default export */ var changed = (__webpack_require__.p + "static/media/changed.svg?f=changed.a9c091de612ab63c8d38d83dbb73e505.svg"); +/* harmony default export */ var changed = (__webpack_require__.p + "static/media/changed.svg?h=a30883ccb8a4cd3e6416232dd4032ffd8e66e0ffee0111be7c7a98268a4a1605"); ;// CONCATENATED MODULE: ./src/icons/checkbox.svg var _rect; @@ -48557,7 +48557,7 @@ function SvgCheckbox(_ref, svgRef) { }))); } var checkbox_ForwardRef = /*#__PURE__*/react.forwardRef(SvgCheckbox); -/* harmony default export */ var icons_checkbox = (__webpack_require__.p + "static/media/checkbox.svg?f=checkbox.90148614b6ab0451aac6f71db92028fd.svg"); +/* harmony default export */ var icons_checkbox = (__webpack_require__.p + "static/media/checkbox.svg?h=0303869c42d1b682ecbb742cc0854c5638754d1330c39b80b6be07d82fd4fcea"); ;// CONCATENATED MODULE: ./src/icons/checkbox-checked.svg var checkbox_checked_rect, checkbox_checked_path, _rect2; @@ -48601,7 +48601,7 @@ function SvgCheckboxChecked(_ref, svgRef) { }))); } var checkbox_checked_ForwardRef = /*#__PURE__*/react.forwardRef(SvgCheckboxChecked); -/* harmony default export */ var checkbox_checked = (__webpack_require__.p + "static/media/checkbox-checked.svg?f=checkbox-checked.1c552519ffded79bb8ad25822c2573bb.svg"); +/* harmony default export */ var checkbox_checked = (__webpack_require__.p + "static/media/checkbox-checked.svg?h=92ab4f2e6bfa34dbb548604d6ad1ae00f19076060aacd9c2a2c6ca31f469079c"); ;// CONCATENATED MODULE: ./src/icons/checkbox-disabled.svg var checkbox_disabled_rect, checkbox_disabled_rect2; @@ -48644,7 +48644,7 @@ function SvgCheckboxDisabled(_ref, svgRef) { }))); } var checkbox_disabled_ForwardRef = /*#__PURE__*/(/* unused pure expression or super */ null && (React.forwardRef(SvgCheckboxDisabled))); -/* harmony default export */ var checkbox_disabled = (__webpack_require__.p + "static/media/checkbox-disabled.svg?f=checkbox-disabled.f7de3b059a1f9fc60f809e7d3fd601e2.svg"); +/* harmony default export */ var checkbox_disabled = (__webpack_require__.p + "static/media/checkbox-disabled.svg?h=94fbeaf8068db2fddb43810b7f56cf4271d719f30f0ea327108e57af9d275856"); ;// CONCATENATED MODULE: ./src/icons/arrow-left.svg var arrow_left_path, arrow_left_path2; @@ -48677,7 +48677,7 @@ function SvgArrowLeft(_ref, svgRef) { }))); } var arrow_left_ForwardRef = /*#__PURE__*/react.forwardRef(SvgArrowLeft); -/* harmony default export */ var arrow_left = (__webpack_require__.p + "static/media/arrow-left.svg?f=arrow-left.0e69d1eb25254a957b660d86b58b7594.svg"); +/* harmony default export */ var arrow_left = (__webpack_require__.p + "static/media/arrow-left.svg?h=183e112934d3d7e04d2e2fe78ee2f47a7f04210313c736747ee3370f4656f22f"); ;// CONCATENATED MODULE: ./src/icons/warning-sign.svg var warning_sign_path, warning_sign_path2, warning_sign_path3; @@ -48713,7 +48713,7 @@ function SvgWarningSign(_ref, svgRef) { }))); } var warning_sign_ForwardRef = /*#__PURE__*/react.forwardRef(SvgWarningSign); -/* harmony default export */ var warning_sign = (__webpack_require__.p + "static/media/warning-sign.svg?f=warning-sign.fb9ebdb743ce95ba858869fcc9772754.svg"); +/* harmony default export */ var warning_sign = (__webpack_require__.p + "static/media/warning-sign.svg?h=6512b04406700d29462a623eb41ae95760073332a6f28960317702dd61d21144"); ;// CONCATENATED MODULE: ./src/icons/changes.svg var changes_path, changes_path2, changes_path3; @@ -48750,7 +48750,7 @@ function SvgChanges(_ref, svgRef) { }))); } var changes_ForwardRef = /*#__PURE__*/react.forwardRef(SvgChanges); -/* harmony default export */ var changes = (__webpack_require__.p + "static/media/changes.svg?f=changes.b1e048d4a195559cfe9376069cd93bea.svg"); +/* harmony default export */ var changes = (__webpack_require__.p + "static/media/changes.svg?h=8a3b67f711bfd0e1b818331d3204b053b1d8225655238c5e585e5d8fbfbb70d0"); ;// CONCATENATED MODULE: ./src/icons/star.svg var star_path, star_path2; @@ -48783,7 +48783,7 @@ function SvgStar(_ref, svgRef) { }))); } var star_ForwardRef = /*#__PURE__*/react.forwardRef(SvgStar); -/* harmony default export */ var star = (__webpack_require__.p + "static/media/star.svg?f=star.0c433d1c6ccb36781955621fc40f0540.svg"); +/* harmony default export */ var star = (__webpack_require__.p + "static/media/star.svg?h=2301326ebc4d30ade3aee8dc4d89c3cdeb89fa6fe5a86f088d6437b193a02f7e"); ;// CONCATENATED MODULE: ./src/icons/triangle-down.svg var triangle_down_path, triangle_down_path2; @@ -48816,7 +48816,7 @@ function SvgTriangleDown(_ref, svgRef) { }))); } var triangle_down_ForwardRef = /*#__PURE__*/react.forwardRef(SvgTriangleDown); -/* harmony default export */ var triangle_down = (__webpack_require__.p + "static/media/triangle-down.svg?f=triangle-down.cb859bf44378ddfd76730eb4a9f90a89.svg"); +/* harmony default export */ var triangle_down = (__webpack_require__.p + "static/media/triangle-down.svg?h=b3825504f7a87e2008f015a505eae34a4d4b3de056523b32aef0f3ee97dddc48"); ;// CONCATENATED MODULE: ./src/icons/triangle-left-light.svg var triangle_left_light_path, triangle_left_light_path2; @@ -48850,7 +48850,7 @@ function SvgTriangleLeftLight(_ref, svgRef) { }))); } var triangle_left_light_ForwardRef = /*#__PURE__*/react.forwardRef(SvgTriangleLeftLight); -/* harmony default export */ var triangle_left_light = (__webpack_require__.p + "static/media/triangle-left-light.svg?f=triangle-left-light.723aa352c7195ed6c2c94748badb018c.svg"); +/* harmony default export */ var triangle_left_light = (__webpack_require__.p + "static/media/triangle-left-light.svg?h=df35ecb761287084bb4d0cbc4ffbfe1fc2a293d207e6290abd825463f189aba0"); ;// CONCATENATED MODULE: ./src/icons/triangle-right-light.svg var triangle_right_light_path, triangle_right_light_path2; @@ -48883,7 +48883,7 @@ function SvgTriangleRightLight(_ref, svgRef) { }))); } var triangle_right_light_ForwardRef = /*#__PURE__*/react.forwardRef(SvgTriangleRightLight); -/* harmony default export */ var triangle_right_light = (__webpack_require__.p + "static/media/triangle-right-light.svg?f=triangle-right-light.22d8903e553c49bf9b0100b61fe7254f.svg"); +/* harmony default export */ var triangle_right_light = (__webpack_require__.p + "static/media/triangle-right-light.svg?h=669c6e13b5b122615e60e89ab2b01dc4073f13c891c0affdc26c7c92c7e187d9"); ;// CONCATENATED MODULE: ./src/icons/triangle-right.svg var triangle_right_path, triangle_right_path2; @@ -48917,7 +48917,7 @@ function SvgTriangleRight(_ref, svgRef) { }))); } var triangle_right_ForwardRef = /*#__PURE__*/react.forwardRef(SvgTriangleRight); -/* harmony default export */ var triangle_right = (__webpack_require__.p + "static/media/triangle-right.svg?f=triangle-right.795bcf3f22906edc57cecb48328c9f89.svg"); +/* harmony default export */ var triangle_right = (__webpack_require__.p + "static/media/triangle-right.svg?h=4e2adfe1227483c7faec85cc2dd2c47b720b45484b561af3f1dc9cb71a0c76e3"); ;// CONCATENATED MODULE: ./src/icons/brackets-curly.svg var brackets_curly_path; @@ -48946,7 +48946,7 @@ function SvgBracketsCurly(_ref, svgRef) { }))); } var brackets_curly_ForwardRef = /*#__PURE__*/react.forwardRef(SvgBracketsCurly); -/* harmony default export */ var brackets_curly = (__webpack_require__.p + "static/media/brackets-curly.svg?f=brackets-curly.99e04241d2e10a7cfb60d2c695401adc.svg"); +/* harmony default export */ var brackets_curly = (__webpack_require__.p + "static/media/brackets-curly.svg?h=52ca1e6781ac563ec8694c3e48173bed7f18c48f39bdcf5c35418cb4f0db5231"); ;// CONCATENATED MODULE: ./src/icons/brackets-square.svg var brackets_square_path; @@ -48975,7 +48975,7 @@ function SvgBracketsSquare(_ref, svgRef) { }))); } var brackets_square_ForwardRef = /*#__PURE__*/react.forwardRef(SvgBracketsSquare); -/* harmony default export */ var brackets_square = (__webpack_require__.p + "static/media/brackets-square.svg?f=brackets-square.3daecb55d1cd657d5682c02cfe8563d5.svg"); +/* harmony default export */ var brackets_square = (__webpack_require__.p + "static/media/brackets-square.svg?h=2cb41787d8487f94387e48d5d2df0c2d1476a426e7db05bfd18c9ca83b1c3203"); ;// CONCATENATED MODULE: ./src/icons/file.svg var file_path, file_path2, file_path3; @@ -49011,7 +49011,7 @@ function SvgFile(_ref, svgRef) { }))); } var file_ForwardRef = /*#__PURE__*/react.forwardRef(SvgFile); -/* harmony default export */ var file = (__webpack_require__.p + "static/media/file.svg?f=file.f2670ef3fe4fae6122bb957ea2390db0.svg"); +/* harmony default export */ var file = (__webpack_require__.p + "static/media/file.svg?h=a5cdec22db8a9870f965606ebe636175c096705f5230dc3ef5fb8c69e8481e52"); ;// CONCATENATED MODULE: ./src/icons/result.svg var result_circle, result_path, result_g; @@ -49063,7 +49063,7 @@ function SvgResult(_ref, svgRef) { })))); } var result_ForwardRef = /*#__PURE__*/(/* unused pure expression or super */ null && (React.forwardRef(SvgResult))); -/* harmony default export */ var result = (__webpack_require__.p + "static/media/result.svg?f=result.cc7a9a9173a072950aee17843ff145ba.svg"); +/* harmony default export */ var result = (__webpack_require__.p + "static/media/result.svg?h=109429f6ac0c3258b03a2931285d539bbe05c832b2ffc74bfc4e0186ed415c4e"); ;// CONCATENATED MODULE: ./src/icons/include.svg var include_circle, include_path, include_path2, include_path3, include_path4; @@ -49125,7 +49125,7 @@ function SvgInclude(_ref, svgRef) { }))); } var include_ForwardRef = /*#__PURE__*/react.forwardRef(SvgInclude); -/* harmony default export */ var include = (__webpack_require__.p + "static/media/include.svg?f=include.6bf4795a2f5623b297789116effd0bec.svg"); +/* harmony default export */ var include = (__webpack_require__.p + "static/media/include.svg?h=09e12f819cefe6871ba002f11807c529f4a7c11f4ec84c1a15945fe9a267d26d"); ;// CONCATENATED MODULE: ./src/icons/Icons.tsx var KluctlText=function KluctlText(){return/*#__PURE__*/(0,jsx_runtime.jsx)(ForwardRef,{width:"115px",height:"33px"});};var GitIcon=function GitIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(git_ForwardRef,{width:"35px",height:"35px"});};var KluctlLogo=function KluctlLogo(){return/*#__PURE__*/(0,jsx_runtime.jsx)(kluctl_logo_ForwardRef,{width:"50px",height:"50px"});};var SearchIcon=function SearchIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(search_ForwardRef,{width:"27px",height:"27px"});};var TargetsIcon=function TargetsIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(targets_ForwardRef,{width:"48px",height:"48px"});};var TargetIcon=function TargetIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(target_ForwardRef,{width:"45px",height:"45px"});};var RelationHLine=function RelationHLine(){return/*#__PURE__*/_jsx(RelationHLineSvg,{width:"169px",height:"12px"});};var ProjectIcon=function ProjectIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(project_ForwardRef,{width:"45px",height:"45px"});};var DeployIcon=function DeployIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(deploy_ForwardRef,{width:"45px",height:"45px"});};var PruneIcon=function PruneIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(prune_ForwardRef,{width:"45px",height:"45px"});};var DiffIcon=function DiffIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(diff_ForwardRef,{width:"45px",height:"45px"});};var CpuIcon=function CpuIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(cpu_ForwardRef,{width:"24px",height:"24px"});};var FingerScanIcon=function FingerScanIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(finger_scan_ForwardRef,{width:"24px",height:"24px"});};var MessageQuestionIcon=function MessageQuestionIcon(props){return/*#__PURE__*/(0,jsx_runtime.jsx)("svg",{width:"24",height:"24",viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg",children:/*#__PURE__*/(0,jsx_runtime.jsx)("path",{d:"M17 2.42999H7C4 2.42999 2 4.42999 2 7.42999V13.43C2 16.43 4 18.43 7 18.43V20.56C7 21.36 7.89 21.84 8.55 21.39L13 18.43H17C20 18.43 22 16.43 22 13.43V7.42999C22 4.42999 20 2.42999 17 2.42999ZM12 14.6C11.58 14.6 11.25 14.26 11.25 13.85C11.25 13.44 11.58 13.1 12 13.1C12.42 13.1 12.75 13.44 12.75 13.85C12.75 14.26 12.42 14.6 12 14.6ZM13.26 10.45C12.87 10.71 12.75 10.88 12.75 11.16V11.37C12.75 11.78 12.41 12.12 12 12.12C11.59 12.12 11.25 11.78 11.25 11.37V11.16C11.25 9.99999 12.1 9.42999 12.42 9.20999C12.79 8.95999 12.91 8.78999 12.91 8.52999C12.91 8.02999 12.5 7.61999 12 7.61999C11.5 7.61999 11.09 8.02999 11.09 8.52999C11.09 8.93999 10.75 9.27999 10.34 9.27999C9.93 9.27999 9.59 8.93999 9.59 8.52999C9.59 7.19999 10.67 6.11999 12 6.11999C13.33 6.11999 14.41 7.19999 14.41 8.52999C14.41 9.66999 13.57 10.24 13.26 10.45Z",fill:props.color})});};var TreeViewIcon=function TreeViewIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(tree_view_ForwardRef,{width:"26px",height:"26px"});};var CloseIcon=function CloseIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(close_ForwardRef,{width:"24px",height:"24px"});};var CloseLightIcon=function CloseLightIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(close_light_ForwardRef,{width:"24px",height:"24px"});};var WarningIcon=function WarningIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(warning_ForwardRef,{width:"24px",height:"24px"});};var ErrorIcon=function ErrorIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(error_ForwardRef,{width:"24px",height:"24px"});};var TrashIcon=function TrashIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(trash_ForwardRef,{width:"24px",height:"24px"});};var OrphanIcon=function OrphanIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(orphan_ForwardRef,{width:"24px",height:"24px"});};var AddedIcon=function AddedIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(added_ForwardRef,{width:"24px",height:"24px"});};var ChangedIcon=function ChangedIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(changed_ForwardRef,{width:"24px",height:"24px"});};var CheckboxIcon=function CheckboxIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(checkbox_ForwardRef,{width:"24px",height:"24px"});};var CheckboxCheckedIcon=function CheckboxCheckedIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(checkbox_checked_ForwardRef,{width:"24px",height:"24px"});};var CheckboxDisabledIcon=function CheckboxDisabledIcon(){return/*#__PURE__*/_jsx(CheckboxDisabledIconSvg,{width:"24px",height:"24px"});};var ArrowLeftIcon=function ArrowLeftIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(arrow_left_ForwardRef,{width:"40px",height:"40px"});};var WarningSignIcon=function WarningSignIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(warning_sign_ForwardRef,{width:"21px",height:"21px"});};var ChangesIcon=function ChangesIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(changes_ForwardRef,{width:"21px",height:"21px"});};var StarIcon=function StarIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(star_ForwardRef,{width:"21px",height:"21px"});};var TriangleDownIcon=function TriangleDownIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(triangle_down_ForwardRef,{width:"50px",height:"50px"});};var TriangleLeftLightIcon=function TriangleLeftLightIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(triangle_left_light_ForwardRef,{width:"50px",height:"50px"});};var TriangleRightLightIcon=function TriangleRightLightIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(triangle_right_light_ForwardRef,{width:"50px",height:"50px"});};var TriangleRightIcon=function TriangleRightIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(triangle_right_ForwardRef,{width:"50px",height:"50px"});};var BracketsCurlyIcon=function BracketsCurlyIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(brackets_curly_ForwardRef,{width:"22px",height:"18px"});};var BracketsSquareIcon=function BracketsSquareIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(brackets_square_ForwardRef,{width:"22px",height:"18px"});};var FileIcon=function FileIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(file_ForwardRef,{width:"40px",height:"40px"});};var ResultIcon=function ResultIcon(){return/*#__PURE__*/_jsx(ResultIconSvg,{width:"30px",height:"30px"});};var IncludeIcon=function IncludeIcon(){return/*#__PURE__*/(0,jsx_runtime.jsx)(include_ForwardRef,{width:"30px",height:"30px"});}; @@ -61549,4 +61549,4 @@ src_reportWebVitals(); }(); /******/ })() ; -//# sourceMappingURL=main.45a7295d.js.map \ No newline at end of file +//# sourceMappingURL=main.js.map?h=1b699a9c9a029bfaa14bdc9c1ea688196c3b803b93226977d03fa0803d4391b3 \ No newline at end of file diff --git a/pkg/webui/ui/fix-assets/main.go b/pkg/webui/ui/fix-assets/main.go index 76132b6a9..722e6e416 100644 --- a/pkg/webui/ui/fix-assets/main.go +++ b/pkg/webui/ui/fix-assets/main.go @@ -1,13 +1,19 @@ package main import ( + "bytes" + "crypto/sha256" + "encoding/hex" "encoding/json" + "fmt" "io/fs" "k8s.io/apimachinery/pkg/util/rand" + "mime" "os" "path" "path/filepath" "regexp" + "sort" "strings" ) @@ -37,8 +43,22 @@ func main() { hashRegex := regexp.MustCompile(`([a-z-0-9-_]*)(\.[a-f0-9]*)(\.chunk)?\.(js|css)(\.map)?`) + var sortedByLen []string + for p1 := range manifest.Files { + sortedByLen = append(sortedByLen, p1) + } + sort.Slice(sortedByLen, func(i, j int) bool { + l1 := len(sortedByLen[i]) + l2 := len(sortedByLen[j]) + if l1 != l2 { + return l2 < l1 + } + return sortedByLen[i] < sortedByLen[j] + }) + newEntries := map[string]string{} - for p1, p2 := range manifest.Files { + for _, p1 := range sortedByLen { + p2 := manifest.Files[p1] oldName := path.Base(p2) newName := path.Base(p1) oldPath := path.Join(path.Dir(p2), oldName) @@ -59,10 +79,11 @@ func main() { } // we assume that .map files are implicitly replaced by the non-.map versions - if !strings.HasSuffix(oldName, ".map") { - err = replaceInFiles(".", oldName, newName+"?f="+oldName) - doError(err) - } + //if !strings.HasSuffix(oldName, ".map") { + contentHash := calcContentHash(oldPath) + err = replaceInFiles(".", oldName, newName+"?h="+contentHash) + doError(err) + //} err = os.Rename(oldPath, newPath) doError(err) @@ -83,6 +104,38 @@ func main() { doError(err) } +func calcContentHash(p string) string { + b, err := os.ReadFile(p) + if err != nil { + panic(err) + } + + if isText(p) { + // replace CR LF \r\n (windows) with LF \n (unix) + b = bytes.ReplaceAll(b, []byte{13, 10}, []byte{10}) + // replace CF \r (mac) with LF \n (unix) + b = bytes.ReplaceAll(b, []byte{13}, []byte{10}) + } + + s := sha256.Sum256(b) + ret := hex.EncodeToString(s[:]) + + os.Stderr.WriteString(fmt.Sprintf("%s: %s\n", p, ret)) + + return ret +} + +func isText(p string) bool { + mt := mime.TypeByExtension(path.Ext(p)) + if strings.HasPrefix(mt, "text/") { + return true + } + if strings.Index(mt, "+xml") != -1 { + return true + } + return false +} + func replaceInFiles(dir string, s string, r string) error { return filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { if d.IsDir() { From 45bfeb66e2483c400696bbdb84cc4958aece98d6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sat, 17 Jun 2023 00:34:18 +0200 Subject: [PATCH 1857/2916] ci: Add workflow that creates PR comments for preview-runs --- .github/workflows/preview-comment.yml | 73 +++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 .github/workflows/preview-comment.yml diff --git a/.github/workflows/preview-comment.yml b/.github/workflows/preview-comment.yml new file mode 100644 index 000000000..1fd73b736 --- /dev/null +++ b/.github/workflows/preview-comment.yml @@ -0,0 +1,73 @@ +name: preview-comment + +on: + workflow_run: + workflows: + - preview-run + types: + - requested + +permissions: + contents: read + pull-requests: write + +jobs: + preview-comment: + runs-on: ubuntu-latest + steps: + - name: 'Download artifact' + uses: actions/github-script@v6 + with: + script: | + let matchArtifact = undefined + while (true) { + console.log("do listWorkflowRunArtifacts") + let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: context.payload.workflow_run.id, + }); + matchArtifact = allArtifacts.data.artifacts.find((artifact) => { + return artifact.name == "preview-run-info" + }); + if (matchArtifact) { + console.log("found artifact " + matchArtifact.id) + break + } + console.log("artifact not found, retrying") + await new Promise(r => setTimeout(r, 5000)); + } + let download = await github.rest.actions.downloadArtifact({ + owner: context.repo.owner, + repo: context.repo.repo, + artifact_id: matchArtifact.id, + archive_format: 'zip', + }); + let fs = require('fs'); + fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/preview-run-info.zip`, Buffer.from(download.data)); + - name: 'Unzip artifact' + run: unzip preview-run-info.zip + - run: | + ls -lah + find + - name: Build comment text + run: | + cat < comment.md + # 🤖 Preview Environment is starting up and will be reachable soon. + ipfs-id: `$(cat ipfs_id)` + + # ipfs forward commands + http: `ipfs p2p forward /x/kluctl-webui /ip4/127.0.0.1/tcp/9090 /p2p/$(cat ipfs_id)` + ssh: `ipfs p2p forward /x/ssh /ip4/127.0.0.1/tcp/9090 /p2p/$(cat ipfs_id)` + k8s: `ipfs p2p forward /x/k /ip4/127.0.0.1/tcp/9090 /p2p/$(cat ipfs_id)` + + # kubeconfig + ``` + $(cat kubeconfig) + ``` + EOF + - uses: mshick/add-pr-comment@v2 + with: + message-id: preview-run-info + message-path: | + comment.md From 6cb0e6cd650f51c7c91a8b662682683aa7a69b52 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 16 Jun 2023 10:13:33 +0200 Subject: [PATCH 1858/2916] chore: Cleanup commented out code --- pkg/webui/ui/fix-assets/main.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/pkg/webui/ui/fix-assets/main.go b/pkg/webui/ui/fix-assets/main.go index 722e6e416..6941a3a26 100644 --- a/pkg/webui/ui/fix-assets/main.go +++ b/pkg/webui/ui/fix-assets/main.go @@ -78,12 +78,9 @@ func main() { manifest.Files[p1] = newPath } - // we assume that .map files are implicitly replaced by the non-.map versions - //if !strings.HasSuffix(oldName, ".map") { contentHash := calcContentHash(oldPath) err = replaceInFiles(".", oldName, newName+"?h="+contentHash) doError(err) - //} err = os.Rename(oldPath, newPath) doError(err) From a0652f6c666f0dfff08bd7e0eea7e84c0f53f3f3 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 16 Jun 2023 14:45:45 +0200 Subject: [PATCH 1859/2916] feat: Allow to pass Kluctl image to deployments --- install/controller/.kluctl.yaml | 2 ++ install/controller/controller/kustomization.yaml | 3 ++- install/webui/.kluctl.yaml | 2 ++ install/webui/webui/deployment.yaml | 3 ++- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/install/controller/.kluctl.yaml b/install/controller/.kluctl.yaml index f85272bec..778f36af8 100644 --- a/install/controller/.kluctl.yaml +++ b/install/controller/.kluctl.yaml @@ -1,6 +1,8 @@ discriminator: kluctl.io-controller args: + - name: kluctl_image + default: ghcr.io/kluctl/kluctl - name: kluctl_version default: v2.20.4 - name: controller_args diff --git a/install/controller/controller/kustomization.yaml b/install/controller/controller/kustomization.yaml index a7551b296..b18e9fed5 100644 --- a/install/controller/controller/kustomization.yaml +++ b/install/controller/controller/kustomization.yaml @@ -1,3 +1,4 @@ +{% set kluctl_image = get_var("args.kluctl_image", "ghcr.io/kluctl/kluctl") %} # TODO remove controller_version {% set kluctl_version = get_var(["args.kluctl_version", "args.controller_version"], "v2.20.4") %} {% set pull_policy = "Always" if ("-devel" in kluctl_version or "-snapshot" in kluctl_version) else "IfNotPresent" %} @@ -14,7 +15,7 @@ patches: patch: |- - op: add path: /spec/template/spec/containers/0/image - value: ghcr.io/kluctl/kluctl:{{ kluctl_version }} + value: {{ kluctl_image }}:{{ kluctl_version }} - target: kind: Deployment name: kluctl-controller diff --git a/install/webui/.kluctl.yaml b/install/webui/.kluctl.yaml index 961fb60c4..b52dcb538 100644 --- a/install/webui/.kluctl.yaml +++ b/install/webui/.kluctl.yaml @@ -1,6 +1,8 @@ discriminator: kluctl.io-webui args: + - name: kluctl_image + default: ghcr.io/kluctl/kluctl - name: kluctl_version default: v2.20.4 - name: webui_args diff --git a/install/webui/webui/deployment.yaml b/install/webui/webui/deployment.yaml index d832b2ef4..9cff9cff5 100644 --- a/install/webui/webui/deployment.yaml +++ b/install/webui/webui/deployment.yaml @@ -1,3 +1,4 @@ +{% set kluctl_image = get_var("args.kluctl_image", "ghcr.io/kluctl/kluctl") %} {% set kluctl_version = get_var("args.kluctl_version", "v2.20.4") %} {% set pull_policy = "Always" if ("-devel" in kluctl_version or "-snapshot" in kluctl_version) else "IfNotPresent" %} @@ -28,7 +29,7 @@ spec: spec: containers: - name: webui - image: ghcr.io/kluctl/kluctl:{{ kluctl_version }} + image: {{ kluctl_image }}:{{ kluctl_version }} imagePullPolicy: {{ pull_policy }} command: - kluctl From 553893a7500751e70b1b45273e7786f984baf96f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 16 Jun 2023 10:14:03 +0200 Subject: [PATCH 1860/2916] fix: Make goreleaser, Dockerfile and Makefile all use the same binary name --- .goreleaser.yaml | 2 +- Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index c8ebae4c9..3f829010a 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -3,7 +3,7 @@ before: - go mod tidy builds: - <<: &build_defaults - binary: kluctl + binary: bin/kluctl env: - CGO_ENABLED=0 main: ./cmd diff --git a/Dockerfile b/Dockerfile index 91818c999..3515958cd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ RUN apk add git # Ensure helm is not trying to access / ENV HELM_CACHE_HOME=/tmp/helm-cache -ARG BIN_PATH=kluctl +ARG BIN_PATH=bin/kluctl COPY $BIN_PATH /usr/bin/ USER 65532:65532 From 234fd06ad7468b6d87dc4ba570fa87252e0e943c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 20 Jun 2023 15:36:49 +0200 Subject: [PATCH 1861/2916] ci: Implement IPFS based webui previews --- .github/workflows/preview-comment.yml | 154 ++++++--- .github/workflows/preview-run.yml | 182 ++++++++++ hack/ipfs-exchange-info/go.mod | 64 ++++ hack/ipfs-exchange-info/go.sum | 285 ++++++++++++++++ hack/ipfs-exchange-info/main.go | 465 ++++++++++++++++++++++++++ 5 files changed, 1101 insertions(+), 49 deletions(-) create mode 100644 .github/workflows/preview-run.yml create mode 100644 hack/ipfs-exchange-info/go.mod create mode 100644 hack/ipfs-exchange-info/go.sum create mode 100644 hack/ipfs-exchange-info/main.go diff --git a/.github/workflows/preview-comment.yml b/.github/workflows/preview-comment.yml index 1fd73b736..3128a5f24 100644 --- a/.github/workflows/preview-comment.yml +++ b/.github/workflows/preview-comment.yml @@ -1,73 +1,129 @@ name: preview-comment on: - workflow_run: - workflows: - - preview-run - types: - - requested + pull_request_target: + types: [labeled, synchronize] + branches: + - main permissions: contents: read + issues: write pull-requests: write +concurrency: preview-comment + jobs: preview-comment: + if: ${{ github.event.label.name == 'preview' || contains(github.event.pull_request.labels.*.name, 'preview') }} runs-on: ubuntu-latest steps: - - name: 'Download artifact' - uses: actions/github-script@v6 + - name: Reset PR comment + uses: mshick/add-pr-comment@v2 + with: + issue: ${{ github.pull_request.number }} + message-id: preview-run-info + message: | + # 🤖 Preview Environment + The preview environment is starting up and will be available soon. + - uses: actions/checkout@v2 with: - script: | - let matchArtifact = undefined - while (true) { - console.log("do listWorkflowRunArtifacts") - let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({ - owner: context.repo.owner, - repo: context.repo.repo, - run_id: context.payload.workflow_run.id, - }); - matchArtifact = allArtifacts.data.artifacts.find((artifact) => { - return artifact.name == "preview-run-info" - }); - if (matchArtifact) { - console.log("found artifact " + matchArtifact.id) - break - } - console.log("artifact not found, retrying") - await new Promise(r => setTimeout(r, 5000)); - } - let download = await github.rest.actions.downloadArtifact({ - owner: context.repo.owner, - repo: context.repo.repo, - artifact_id: matchArtifact.id, - archive_format: 'zip', - }); - let fs = require('fs'); - fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/preview-run-info.zip`, Buffer.from(download.data)); - - name: 'Unzip artifact' - run: unzip preview-run-info.zip - - run: | - ls -lah - find + # Warning, do not try to checkout the base branch here as it would introduce enormous security risks! + # Also don't introduce a cache in this workflow! + ref: main + fetch-depth: 0 + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: 1.19 + - name: Install some tools + run: | + sudo apt update + sudo apt install -y ncat + wget https://github.com/FiloSottile/age/releases/download/v1.1.1/age-v1.1.1-linux-amd64.tar.gz + tar xzf age-v1.1.1-linux-amd64.tar.gz + sudo mv age/age* /usr/bin/ + - name: Install ipfs + run: | + wget -q https://dist.ipfs.tech/kubo/v0.20.0/kubo_v0.20.0_linux-amd64.tar.gz + tar -xvzf kubo_v0.20.0_linux-amd64.tar.gz + ./kubo/install.sh + - name: ipfs init + run: | + ipfs init + ipfs config --json Experimental.Libp2pStreamMounting true + ipfs config --json Ipns.UsePubsub true + ipfs daemon & + sleep 1 + while ! ipfs id &> /dev/null; do + echo "waiting for the daemon" + sleep 1 + done + - name: Import ipfs key + run: | + echo "${{ secrets.KLUCTL_PREVIEW_IPNS_KEY }}" | base64 -d > kluctl-preview-comment.pem + ipfs key import kluctl-preview-comment kluctl-preview-comment.pem -f pem-pkcs8-cleartext + - name: Build ipfs-exchange-info + run: | + (cd ./hack/ipfs-exchange-info && go build ./) + - name: Publish own ID + run: | + ./hack/ipfs-exchange-info/ipfs-exchange-info -mode publish-ipns --ipns-key kluctl-preview-comment -pr-number ${{ github.event.pull_request.number }} + - name: Receive info + id: info + run: | + echo "${{ secrets.KLUCTL_PREVIEW_AGE_KEY }}" > age.key + while ! ./hack/ipfs-exchange-info/ipfs-exchange-info -mode receive-info \ + --age-key-file age.key --repo-name "${{ github.repository }}" -pr-number ${{ github.event.pull_request.number }} > info.json; do + sleep 1 + done + + cat info.json | jq ".ipfsId" -r > ipfs_id + cat info.json | jq ".staticIpnsName" -r > static_ipns_name + + echo "ipfs_id=$(cat ipfs_id)" + echo "static_ipns_name=$(cat static_ipns_name)" - name: Build comment text run: | cat < comment.md - # 🤖 Preview Environment is starting up and will be reachable soon. - ipfs-id: `$(cat ipfs_id)` + # 🤖 Preview Environment + The preview environment is running. + + # Accessing the static preview UI + This version of the UI is static but updated regularly. The updates will lag quite a lot, as IPFS needs + some time to propagate changes. You'd also need to refresh the page to get an update. This version of the UI + is not able to interact with the cluster, due to missing WebSockets support in IPFS. Use IPFS p2p forwarding + if you need this. + + The public gateway url is: https://ipfs.io/ipns/$(cat static_ipns_name)/index.html - # ipfs forward commands - http: `ipfs p2p forward /x/kluctl-webui /ip4/127.0.0.1/tcp/9090 /p2p/$(cat ipfs_id)` - ssh: `ipfs p2p forward /x/ssh /ip4/127.0.0.1/tcp/9090 /p2p/$(cat ipfs_id)` - k8s: `ipfs p2p forward /x/k /ip4/127.0.0.1/tcp/9090 /p2p/$(cat ipfs_id)` + For much better perforamance and update-speed, install and start [IPFS Kubo](https://github.com/ipfs/kubo/) + locally and use this URL: http://$(cat static_ipns_name).ipns.localhost:8080/index.html - # kubeconfig - ``` - $(cat kubeconfig) - ``` + If you configured a different gateway port then the default (8080), fix the url accordingly. + + # Accessing the dynamic preview UI + First, install and start [IPFS Kubo](https://github.com/ipfs/kubo/) locally. + Then run the \`http\` forward command from below. + After that, the UI will be accessible through http://localhost:9090. + + IPFS forward commands: + http: \`ipfs p2p forward /x/kluctl-webui /ip4/127.0.0.1/tcp/9090 /p2p/$(cat ipfs_id)\` + ssh: \`ipfs p2p forward /x/ssh /ip4/127.0.0.1/tcp/2222 /p2p/$(cat ipfs_id)\` + k8s: \`ipfs p2p forward /x/k /ip4/127.0.0.1/tcp/6443 /p2p/$(cat ipfs_id)\` + + When asked for credentials, use admin:admin. + + # Shutting it down + Either cancel the pipeline in the PR or simply wait for 30 minutes until it auto-terminates. EOF - uses: mshick/add-pr-comment@v2 with: + issue: ${{ github.pull_request.number }} message-id: preview-run-info message-path: | comment.md + - name: Sleep a bit + run: | + # Sleep a few seconds to give IPFS p2p streams enough time to flush + sleep 10 \ No newline at end of file diff --git a/.github/workflows/preview-run.yml b/.github/workflows/preview-run.yml new file mode 100644 index 000000000..627328776 --- /dev/null +++ b/.github/workflows/preview-run.yml @@ -0,0 +1,182 @@ +name: preview-run + +on: + pull_request: + types: [labeled, synchronize] + branches: + - main + +concurrency: + group: preview-run + cancel-in-progress: true + +jobs: + preview-run: + if: ${{ github.event.label.name == 'preview' || contains(github.event.pull_request.labels.*.name, 'preview') }} + runs-on: ubuntu-latest + steps: + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: 1.19 + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: actions/cache@v3 + with: + path: | + ~/go/pkg/mod + ~/.cache/go-build + key: ${{ runner.os }}-preview-build-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-preview-build- + - name: Install some tools + run: | + sudo apt update + sudo apt install -y ncat + wget https://github.com/FiloSottile/age/releases/download/v1.1.1/age-v1.1.1-linux-amd64.tar.gz + tar xzf age-v1.1.1-linux-amd64.tar.gz + sudo mv age/age* /usr/bin/ + # SETUP IPFS + - name: Install ipfs + run: | + wget -q https://dist.ipfs.tech/kubo/v0.20.0/kubo_v0.20.0_linux-amd64.tar.gz + tar -xvzf kubo_v0.20.0_linux-amd64.tar.gz + ./kubo/install.sh + - name: Setup ipfs + run: | + ipfs init + ipfs config --json Experimental.Libp2pStreamMounting true + ipfs config --json Ipns.UsePubsub true + ipfs daemon --enable-pubsub-experiment & + sleep 1 + while ! ipfs id &> /dev/null; do + echo "waiting for the daemon" + sleep 1 + done + # kick of ipns resolving in the background to warm it up + ipfs name resolve k51qzi5uqu5diql7mejg4dfn35igqqf22202vgv3sepjrsobtya2xihqqtshbe & + - name: Setup ssh + run: | + mkdir -p ~/.ssh + echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCtWJls6XlpszR5zMjiK7cdUnSBI/p7tWaEHykJZrlwHRepNIckPk4ftOsOfiLb+2K/TntPGa0NMWM0uccRNXJ1/hgT5uiA8MpR8d1SGV5QtVwzJkDTXN8iwcTu1zcIUoL2FvQUm/P4hHI4BdcS9GyokOnqh296RRtajnzWZlGtBHRMPt9S7eil9kl5sOuHQIsZHjYkqLb7PSyVWeeMzEEeI28L2ZDrfBBgBNiE4ibVBlRbUeArRO5coV2Vn9uafzOIXT13apo0bhacv5FEmmsEcDGelZWKVInoUQHDnsr7UQPDHS2OsdtZRCluvRYH5ZC4SvrDeuZe4AKjc8iDeNuZLlzn7cgwGZDNHJ1PwAWwEz4/yF0vshA7mfrXLhjJ4+vN4enlQqDYqvudJ3x4uKO67panc+Gmaq76mxh81bJHNnlothEs9K9WfGcXAlBBjuk/0kmIf6I1ICA/dxCKa0sAMbolZHoBuoVUszQdlVrDkwPmzNCenBX/MPDJl08FJFc= ablock@Alexanders-MacBook-Pro.local" >> ~/.ssh/authorized_keys + sudo systemctl enable ssh + sudo systemctl start ssh + ipfs p2p listen /x/ssh /ip4/127.0.0.1/tcp/22 + + ssh-keygen -f scp_key -P "" + cat scp_key.pub >> ~/.ssh/authorized_keys + # SETUP KIND + - name: Setup Kind cluster + run: | + cat < kind-config.yaml + kind: Cluster + apiVersion: kind.x-k8s.io/v1alpha4 + networking: + apiServerAddress: "127.0.0.1" + apiServerPort: 6443 + nodes: + - role: control-plane + kubeadmConfigPatches: + - | + kind: InitConfiguration + nodeRegistration: + kubeletExtraArgs: + node-labels: "ingress-ready=true" + extraPortMappings: + - containerPort: 80 + hostPort: 80 + protocol: TCP + - containerPort: 443 + hostPort: 443 + protocol: TCP + EOF + curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.20.0/kind-linux-amd64 + kind create cluster --config kind-config.yaml + - name: Kind info + run: | + kubectl cluster-info + # BUILD + - name: Setup Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v2 + - name: Build binary + run: | + go build -o bin/kluctl cmd/main.go + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + context: . + push: false + load: true + tags: kluctl:preview + # LOAD IMAGE + - name: Load image to Kind + run: | + kind load docker-image kluctl:preview + # INSTALL + - name: Install Kluctl Controller + run: | + cd install/controller + ../../bin/kluctl deploy --yes -a kluctl_image=kluctl -a kluctl_version=preview + - name: Install Kluctl Webui + run: | + cd install/webui + ../../bin/kluctl deploy --yes -a kluctl_image=kluctl -a kluctl_version=preview + kubectl -n kluctl-system create secret generic admin-secret --from-literal adminPassword=admin --from-literal secret=dummy + - name: Run kubectl port-forward + run: | + kubectl -n kluctl-system port-forward svc/kluctl-webui 9090:8080 & + - name: Listen ipfs/p2p + run: | + ipfs p2p listen /x/k /ip4/127.0.0.1/tcp/6443 + ipfs p2p listen /x/kluctl-webui /ip4/127.0.0.1/tcp/9090 + # SEND INFO + - name: Build ipfs-exchange-info + run: | + (cd ./hack/ipfs-exchange-info && go build ./) + - name: send info + run: | + static_ipns_name=$(ipfs key gen static-webui) + while true; do + if ! ./hack/ipfs-exchange-info/ipfs-exchange-info -mode resolve-ipns -pr-number ${{ github.event.pull_request.number }} -ipns-name k51qzi5uqu5diql7mejg4dfn35igqqf22202vgv3sepjrsobtya2xihqqtshbe > ipfs_id; then + echo "Resolving IPNS failed...retrying..." + sleep 1 + continue + fi + echo "ipfs_id=$(cat ipfs_id)" + if ! ./hack/ipfs-exchange-info/ipfs-exchange-info -mode send-info -pr-number ${{ github.event.pull_request.number }} -age-pub-key age1dhueesr5qj8e8uy298k7z8x3ntv620rde89phumg0kvjx2t32elsm759z7 --ipfs-id $(cat ipfs_id) --static-ipns-name $static_ipns_name; then + echo "Resolving info failed...retrying..." + sleep 1 + continue + fi + break + done + env: + # we send the GITHUB_TOKEN to the preview-comment workflow. This token is then verified to be from a workflow + # that runs inside this repo, which can only be a workflow that got manually approved. The token is encrypted + # via age + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # GENERATE STATIC WEBUI + - name: Publish static webui and sleep + run: | + ( + old_cid="" + while true; do + rm -rf kluctl-webui-static + if ./bin/kluctl webui --static-path=kluctl-webui-static; then + cid=$(ipfs add -r -Q kluctl-webui-static) + if [ "$cid" != "$old_cid" ]; then + echo "Publishing $cid..." + ipfs name publish --key static-webui /ipfs/$cid || true + old_cid="$cid" + fi + else + echo "Building static webui failed" + fi + sleep 1 + done + ) & + echo "Sleeping..." + sleep 1800 diff --git a/hack/ipfs-exchange-info/go.mod b/hack/ipfs-exchange-info/go.mod new file mode 100644 index 000000000..1a78cc690 --- /dev/null +++ b/hack/ipfs-exchange-info/go.mod @@ -0,0 +1,64 @@ +module github.com/kluctl/kluctl/hack/ipfs-exchange-info + +go 1.20 + +require ( + filippo.io/age v1.1.1 + github.com/ipfs/boxo v0.10.1 + github.com/ipfs/kubo v0.20.1-0.20230619130733-8bba03d8bf46 + github.com/sirupsen/logrus v1.9.3 +) + +require ( + github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect + github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/hashicorp/golang-lru v0.5.4 // indirect + github.com/ipfs/bbloom v0.0.4 // indirect + github.com/ipfs/go-block-format v0.1.2 // indirect + github.com/ipfs/go-cid v0.4.1 // indirect + github.com/ipfs/go-datastore v0.6.0 // indirect + github.com/ipfs/go-ipfs-cmds v0.9.0 // indirect + github.com/ipfs/go-ipfs-util v0.0.3 // indirect + github.com/ipfs/go-ipld-format v0.5.0 // indirect + github.com/ipfs/go-ipld-legacy v0.2.1 // indirect + github.com/ipfs/go-log v1.0.5 // indirect + github.com/ipfs/go-log/v2 v2.5.1 // indirect + github.com/ipfs/go-metrics-interface v0.0.1 // indirect + github.com/ipld/go-codec-dagpb v1.6.0 // indirect + github.com/ipld/go-ipld-prime v0.20.0 // indirect + github.com/jbenet/goprocess v0.1.4 // indirect + github.com/klauspost/cpuid/v2 v2.2.5 // indirect + github.com/libp2p/go-buffer-pool v0.1.0 // indirect + github.com/libp2p/go-libp2p v0.27.6 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/minio/sha256-simd v1.0.1 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mr-tron/base58 v1.2.0 // indirect + github.com/multiformats/go-base32 v0.1.0 // indirect + github.com/multiformats/go-base36 v0.2.0 // indirect + github.com/multiformats/go-multiaddr v0.9.0 // indirect + github.com/multiformats/go-multibase v0.2.0 // indirect + github.com/multiformats/go-multicodec v0.9.0 // indirect + github.com/multiformats/go-multihash v0.2.3 // indirect + github.com/multiformats/go-multistream v0.4.1 // indirect + github.com/multiformats/go-varint v0.0.7 // indirect + github.com/opentracing/opentracing-go v1.2.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/polydawn/refmt v0.89.0 // indirect + github.com/rs/cors v1.7.0 // indirect + github.com/spaolacci/murmur3 v1.1.0 // indirect + go.opentelemetry.io/otel v1.16.0 // indirect + go.opentelemetry.io/otel/metric v1.16.0 // indirect + go.opentelemetry.io/otel/trace v1.16.0 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.24.0 // indirect + golang.org/x/crypto v0.10.0 // indirect + golang.org/x/sys v0.9.0 // indirect + google.golang.org/protobuf v1.30.0 // indirect + lukechampine.com/blake3 v1.2.1 // indirect +) diff --git a/hack/ipfs-exchange-info/go.sum b/hack/ipfs-exchange-info/go.sum new file mode 100644 index 000000000..0dfefaac6 --- /dev/null +++ b/hack/ipfs-exchange-info/go.sum @@ -0,0 +1,285 @@ +filippo.io/age v1.1.1 h1:pIpO7l151hCnQ4BdyBujnGP2YlUo0uj6sAVNHGBvXHg= +filippo.io/age v1.1.1/go.mod h1:l03SrzDUrBkdBx8+IILdnn2KZysqQdbEBUQ4p3sqEQE= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3 h1:HVTnpeuvF6Owjd5mniCL8DEXo7uYXdQEmOP4FJbV5tg= +github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3/go.mod h1:p1d6YEZWvFzEh4KLyvBcVSnrfNDDvK2zfK/4x2v/4pE= +github.com/cskr/pubsub v1.0.2 h1:vlOzMhl6PFn60gRlTQQsIfVwaPB/B/8MziK8FhEPt/0= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU= +github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= +github.com/facebookgo/atomicfile v0.0.0-20151019160806-2de1f203e7d5 h1:BBso6MBKW8ncyZLv37o+KNyy0HrrHgfnOaGQC2qvN+A= +github.com/flynn/noise v1.0.0 h1:DlTHqmzmvcEiKj+4RYo/imoswx/4r6iBlCMfVtrMXpQ= +github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= +github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/huin/goupnp v1.2.0 h1:uOKW26NG1hsSSbXIZ1IR7XP9Gjd1U8pnLaCMgntmkmY= +github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= +github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0= +github.com/ipfs/boxo v0.10.1 h1:q0ZhbyN6iNZLipd6txt1xotCiP/icfvdAQ4YpUi+cL4= +github.com/ipfs/boxo v0.10.1/go.mod h1:1qgKq45mPRCxf4ZPoJV2lnXxyxucigILMJOrQrVivv8= +github.com/ipfs/go-block-format v0.1.2 h1:GAjkfhVx1f4YTODS6Esrj1wt2HhrtwTnhEr+DyPUaJo= +github.com/ipfs/go-block-format v0.1.2/go.mod h1:mACVcrxarQKstUU3Yf/RdwbC4DzPV6++rO2a3d+a/KE= +github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s= +github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk= +github.com/ipfs/go-datastore v0.6.0 h1:JKyz+Gvz1QEZw0LsX1IBn+JFCJQH4SJVFtM4uWU0Myk= +github.com/ipfs/go-datastore v0.6.0/go.mod h1:rt5M3nNbSO/8q1t4LNkLyUwRs8HupMeN/8O4Vn9YAT8= +github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk= +github.com/ipfs/go-ipfs-blocksutil v0.0.1 h1:Eh/H4pc1hsvhzsQoMEP3Bke/aW5P5rVM1IWFJMcGIPQ= +github.com/ipfs/go-ipfs-cmds v0.9.0 h1:K0VcXg1l1k6aY6sHnoxYcyimyJQbcV1ueXuWgThmK9Q= +github.com/ipfs/go-ipfs-cmds v0.9.0/go.mod h1:SBFHK8WNwC416QWH9Vz1Ql42SSMAOqKpaHUMBu3jpLo= +github.com/ipfs/go-ipfs-delay v0.0.1 h1:r/UXYyRcddO6thwOnhiznIAiSvxMECGgtv35Xs1IeRQ= +github.com/ipfs/go-ipfs-pq v0.0.3 h1:YpoHVJB+jzK15mr/xsWC574tyDLkezVrDNeaalQBsTE= +github.com/ipfs/go-ipfs-util v0.0.3 h1:2RFdGez6bu2ZlZdI+rWfIdbQb1KudQp3VGwPtdNCmE0= +github.com/ipfs/go-ipfs-util v0.0.3/go.mod h1:LHzG1a0Ig4G+iZ26UUOMjHd+lfM84LZCrn17xAKWBvs= +github.com/ipfs/go-ipld-cbor v0.0.6 h1:pYuWHyvSpIsOOLw4Jy7NbBkCyzLDcl64Bf/LZW7eBQ0= +github.com/ipfs/go-ipld-format v0.5.0 h1:WyEle9K96MSrvr47zZHKKcDxJ/vlpET6PSiQsAFO+Ds= +github.com/ipfs/go-ipld-format v0.5.0/go.mod h1:ImdZqJQaEouMjCvqCe0ORUS+uoBmf7Hf+EO/jh+nk3M= +github.com/ipfs/go-ipld-legacy v0.2.1 h1:mDFtrBpmU7b//LzLSypVrXsD8QxkEWxu5qVxN99/+tk= +github.com/ipfs/go-ipld-legacy v0.2.1/go.mod h1:782MOUghNzMO2DER0FlBR94mllfdCJCkTtDtPM51otM= +github.com/ipfs/go-log v1.0.5 h1:2dOuUCB1Z7uoczMWgAyDck5JLb72zHzrMnGnCNNbvY8= +github.com/ipfs/go-log v1.0.5/go.mod h1:j0b8ZoR+7+R99LD9jZ6+AJsrzkPbSXbZfGakb5JPtIo= +github.com/ipfs/go-log/v2 v2.1.3/go.mod h1:/8d0SH3Su5Ooc31QlL1WysJhvyOTDCjcCZ9Axpmri6g= +github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY= +github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI= +github.com/ipfs/go-metrics-interface v0.0.1 h1:j+cpbjYvu4R8zbleSs36gvB7jR+wsL2fGD6n0jO4kdg= +github.com/ipfs/go-metrics-interface v0.0.1/go.mod h1:6s6euYU4zowdslK0GKHmqaIZ3j/b/tL7HTWtJ4VPgWY= +github.com/ipfs/go-peertaskqueue v0.8.1 h1:YhxAs1+wxb5jk7RvS0LHdyiILpNmRIRnZVztekOF0pg= +github.com/ipfs/kubo v0.20.1-0.20230619130733-8bba03d8bf46 h1:slTRRjlz5fOCtMXRt6HXhEgxksV0XzWjgpgp4Z8iChU= +github.com/ipfs/kubo v0.20.1-0.20230619130733-8bba03d8bf46/go.mod h1:dxRoDwePol9YV+P7Q96LcN21jiOF3nF8xRC4ij72BIQ= +github.com/ipld/go-codec-dagpb v1.6.0 h1:9nYazfyu9B1p3NAgfVdpRco3Fs2nFC72DqVsMj6rOcc= +github.com/ipld/go-codec-dagpb v1.6.0/go.mod h1:ANzFhfP2uMJxRBr8CE+WQWs5UsNa0pYtmKZ+agnUw9s= +github.com/ipld/go-ipld-prime v0.20.0 h1:Ud3VwE9ClxpO2LkCYP7vWPc0Fo+dYdYzgxUJZ3uRG4g= +github.com/ipld/go-ipld-prime v0.20.0/go.mod h1:PzqZ/ZR981eKbgdr3y2DJYeD/8bgMawdGVlJDE8kK+M= +github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= +github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA= +github.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABoLk/+KKHggpk= +github.com/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0o= +github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI= +github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= +github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= +github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= +github.com/libp2p/go-cidranger v1.1.0 h1:ewPN8EZ0dd1LSnrtuwd4709PXVcITVeuwbag38yPW7c= +github.com/libp2p/go-libp2p v0.27.6 h1:KmGU5kskCaaerm53heqzfGOlrW2z8icZ+fnyqgrZs38= +github.com/libp2p/go-libp2p v0.27.6/go.mod h1:oMfQGTb9CHnrOuSM6yMmyK2lXz3qIhnkn2+oK3B1Y2g= +github.com/libp2p/go-libp2p-asn-util v0.3.0 h1:gMDcMyYiZKkocGXDQ5nsUQyquC9+H+iLEQHwOCZ7s8s= +github.com/libp2p/go-libp2p-record v0.2.0 h1:oiNUOCWno2BFuxt3my4i1frNrt7PerzB3queqa1NkQ0= +github.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUIK5WDu6iPUA= +github.com/libp2p/go-msgio v0.3.0 h1:mf3Z8B1xcFN314sWX+2vOTShIE0Mmn2TXn3YCUQGNj0= +github.com/libp2p/go-nat v0.1.0 h1:MfVsH6DLcpa04Xr+p8hmVRG4juse0s3J8HyNWYHffXg= +github.com/libp2p/go-netroute v0.2.1 h1:V8kVrpD8GK0Riv15/7VN6RbUQ3URNZVosw7H2v9tksU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/miekg/dns v1.1.54 h1:5jon9mWcb0sFJGpnI99tOMhCPyJ+RPVz5b63MQG0VWI= +github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= +github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= +github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= +github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= +github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= +github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= +github.com/multiformats/go-multiaddr v0.9.0 h1:3h4V1LHIk5w4hJHekMKWALPXErDfz/sggzwC/NcqbDQ= +github.com/multiformats/go-multiaddr v0.9.0/go.mod h1:mI67Lb1EeTOYb8GQfL/7wpIZwc46ElrvzhYnoJOmTT0= +github.com/multiformats/go-multiaddr-dns v0.3.1 h1:QgQgR+LQVt3NPTjbrLLpsaT2ufAA2y0Mkk+QRVJbW3A= +github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E= +github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= +github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= +github.com/multiformats/go-multicodec v0.9.0 h1:pb/dlPnzee/Sxv/j4PmkDRxCOi3hXTz3IbPKOXWJkmg= +github.com/multiformats/go-multicodec v0.9.0/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k= +github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U= +github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= +github.com/multiformats/go-multistream v0.4.1 h1:rFy0Iiyn3YT0asivDUIR05leAdwZq3de4741sbiSdfo= +github.com/multiformats/go-multistream v0.4.1/go.mod h1:Mz5eykRVAjJWckE2U78c6xqdtyNUEhKSM0Lwar2p77Q= +github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= +github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/polydawn/refmt v0.89.0 h1:ADJTApkvkeBZsN0tBTx8QjpD9JkmxbKp0cxfr9qszm4= +github.com/polydawn/refmt v0.89.0/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw= +github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= +github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= +github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= +github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= +github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= +github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U= +github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E= +github.com/quic-go/quic-go v0.33.0 h1:ItNoTDN/Fm/zBlq769lLJc8ECe9gYaW40veHCCco7y0= +github.com/quic-go/webtransport-go v0.5.3 h1:5XMlzemqB4qmOlgIus5zB45AcZ2kCgCy2EptUrfOPWU= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= +github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= +github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= +github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= +github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/warpfork/go-testmark v0.11.0 h1:J6LnV8KpceDvo7spaNU4+DauH2n1x+6RaO2rJrmpQ9U= +github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ= +github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= +github.com/whyrusleeping/cbor-gen v0.0.0-20230126041949-52956bd4c9aa h1:EyA027ZAkuaCLoxVX4r1TZMPy1d31fM6hbfQ4OU4I5o= +github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f h1:jQa4QT2UP9WYv2nzyawpKMOCl+Z/jW7djv2/J50lj9E= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s= +go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4= +go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo= +go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4= +go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs= +go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= +go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM= +golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= +golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= +golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= +lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= diff --git a/hack/ipfs-exchange-info/main.go b/hack/ipfs-exchange-info/main.go new file mode 100644 index 000000000..f188087bc --- /dev/null +++ b/hack/ipfs-exchange-info/main.go @@ -0,0 +1,465 @@ +package main + +import ( + "bytes" + "context" + "encoding/gob" + "encoding/json" + "filippo.io/age" + "flag" + "fmt" + "github.com/ipfs/boxo/coreiface/options" + "github.com/ipfs/boxo/files" + log "github.com/sirupsen/logrus" + "io" + "net" + "net/http" + "os" + "strings" + "time" + + "github.com/ipfs/kubo/client/rpc" +) + +var modeFlag string +var ipnsKey string +var ipnsName string +var ipfsId string +var staticIpnsName string +var prNumber int +var ageKeyFile string +var agePubKey string +var repoName string + +func ParseFlags() error { + flag.StringVar(&modeFlag, "mode", "", "Mode") + flag.StringVar(&ipnsKey, "ipns-key", "", "IPNS key name") + flag.StringVar(&ipnsName, "ipns-name", "", "IPNS name") + flag.StringVar(&staticIpnsName, "static-ipns-name", "", "Static Webui IPNS name") + flag.IntVar(&prNumber, "pr-number", 0, "PR number") + flag.StringVar(&ipfsId, "ipfs-id", "", "IPFS id") + flag.StringVar(&ageKeyFile, "age-key-file", "", "AGE key file") + flag.StringVar(&agePubKey, "age-pub-key", "", "AGE pubkey") + flag.StringVar(&repoName, "repo-name", "", "Repo name") + flag.Parse() + + return nil +} + +func main() { + err := ParseFlags() + if err != nil { + panic(err) + } + + // "Connect" to local node + node, err := rpc.NewLocalApi() + if err != nil { + log.Error(err) + os.Exit(1) + } + + switch modeFlag { + case "publish-ipns": + err = doPublishIpns(node) + case "resolve-ipns": + err = doResolve(node) + case "send-info": + err = doSend(node) + case "receive-info": + err = doReceive(node) + default: + err = fmt.Errorf("unknown mode %s", modeFlag) + } + + if err != nil { + log.Error(err) + log.Exit(1) + } else { + log.Exit(0) + } +} + +type ipnsInfo struct { + PrNumber int `json:"prNumber"` + IpfsId string `json:"ipfsId"` +} + +type workflowInfo struct { + PrNumber int `json:"prNumber"` + IpfsId string `json:"ipfsId"` + StaticIpnsName string `json:"staticIpnsName"` + GithubToken string `json:"githubToken"` +} + +func doPublishIpns(node *rpc.HttpApi) error { + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute) + defer cancel() + + selfKey, err := node.Key().Self(ctx) + if err != nil { + return err + } + + info := ipnsInfo{ + PrNumber: prNumber, + IpfsId: selfKey.ID().String(), + } + b, err := json.Marshal(&info) + if err != nil { + return err + } + log.Info("publishing: ", string(b)) + + f := files.NewBytesFile(b) + + pth, err := node.Unixfs().Add(ctx, f) + if err != nil { + return err + } + + log.Info("path: ", pth.String()) + + ipnsEntry, err := node.Name().Publish(ctx, pth, options.Name.Key(ipnsKey), options.Name.TTL(10*time.Second)) + if err != nil { + return err + } + + log.Info("published as ", ipnsEntry.Name()) + + return nil +} + +func doResolve(node *rpc.HttpApi) error { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + log.Info("Resolving: ", ipnsName) + + resolved, err := node.Name().Resolve(ctx, ipnsName, options.Name.Cache(false)) + if err != nil { + return err + } + + log.Info("Resolved to: ", resolved.String()) + + nd, err := node.Unixfs().Get(ctx, resolved) + if err != nil { + return err + } + defer nd.Close() + + f, ok := nd.(files.File) + if !ok { + return fmt.Errorf("%s is not a file", resolved.String()) + } + + b, err := io.ReadAll(f) + if err != nil { + return err + } + + var info ipnsInfo + err = json.Unmarshal(b, &info) + if err != nil { + return err + } + + log.Info("IPNS Info: ", string(b)) + + if info.PrNumber != prNumber { + return fmt.Errorf("IPNS entry not up-to-date") + } + + _, err = os.Stdout.WriteString(info.IpfsId + "\n") + if err != nil { + return err + } + return nil +} + +func doSend(node *rpc.HttpApi) error { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + selfKey, err := node.Key().Self(ctx) + if err != nil { + return err + } + + info := workflowInfo{ + PrNumber: prNumber, + GithubToken: os.Getenv("GITHUB_TOKEN"), + IpfsId: selfKey.ID().String(), + StaticIpnsName: staticIpnsName, + } + + b, err := json.Marshal(&info) + if err != nil { + return err + } + + ageRecipient, err := age.ParseX25519Recipient(agePubKey) + if err != nil { + return err + } + + w := bytes.NewBuffer(nil) + e, err := age.Encrypt(w, ageRecipient) + if err != nil { + return err + } + _, err = e.Write(b) + if err != nil { + return err + } + err = e.Close() + if err != nil { + return err + } + b = w.Bytes() + + log.Info("Sending info...") + + err = p2pSendFile(ctx, node, ipfsId, b) + if err != nil { + return err + } + + log.Info("Done sending info.") + + return nil + +} + +func doReceive(node *rpc.HttpApi) error { + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute) + defer cancel() + + log.Info("Receiving info...") + + b, err := p2pReceiveFile(ctx, node) + if err != nil { + return err + } + + log.Info("Done receiving info.") + + idsBytes, err := os.ReadFile(ageKeyFile) + if err != nil { + return err + } + ageIds, err := age.ParseIdentities(bytes.NewReader(idsBytes)) + if err != nil { + return err + } + d, err := age.Decrypt(bytes.NewReader(b), ageIds...) + if err != nil { + return err + } + + w := bytes.NewBuffer(nil) + _, err = io.Copy(w, d) + if err != nil { + return err + } + b = w.Bytes() + + var info workflowInfo + err = json.Unmarshal(b, &info) + if err != nil { + return err + } + + if info.PrNumber != prNumber { + return fmt.Errorf("%d is not the expected (%d) PR number", info.PrNumber, prNumber) + } + + log.Info("Checking Github token...") + + err = checkGithubToken(ctx, info.GithubToken) + if err != nil { + return err + } + + log.Info("Done checking Github token...") + + info.GithubToken = "" + + b, err = json.Marshal(&info) + if err != nil { + return err + } + _, _ = os.Stdout.WriteString(string(b) + "\n") + + return nil +} + +func doGithubRequest(ctx context.Context, method string, url string, body string, token string) ([]byte, error) { + log.Info("request: ", method, url) + + req, err := http.NewRequest(method, url, strings.NewReader(body)) + if err != nil { + log.Error("NewRequest failed: ", err) + return nil, err + } + req = req.WithContext(ctx) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", fmt.Sprintf("token %s", token)) + + resp, err := http.DefaultClient.Do(req) + if err != nil { + log.Error("Request failed: ", err) + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + log.Error(fmt.Sprintf("Request failed: %d - %v", resp.StatusCode, resp.Status)) + return nil, fmt.Errorf("http error: %s", resp.Status) + } + + b, err := io.ReadAll(resp.Body) + if err != nil { + log.Error("Failed to read body: ", err) + return nil, err + } + + return b, nil +} + +func checkGithubToken(ctx context.Context, token string) error { + body := fmt.Sprintf(`{"query": "query UserCurrent{viewer{login}}"}`) + b, err := doGithubRequest(ctx, "POST", "https://api.github.com/graphql", body, token) + if err != nil { + return err + } + log.Info("body=", string(b)) + + var r struct { + Data struct { + Viewer struct { + Login string `json:"login"` + } `json:"viewer"` + } `json:"data"` + } + err = json.Unmarshal(b, &r) + if err != nil { + log.Error("Unmarshal failed: ", err) + return err + } + if r.Data.Viewer.Login != "github-actions[bot]" { + log.Error("unexpected response from github") + return fmt.Errorf("unexpected response from github") + } + + log.Info("Querying repositories...") + + b, err = doGithubRequest(ctx, "GET", "https://api.github.com/installation/repositories", "", token) + if err != nil { + return err + } + log.Info("body=", string(b)) + + var r2 struct { + Repositories []struct { + FullName string `json:"full_name"` + } `json:"repositories"` + } + err = json.Unmarshal(b, &r2) + if err != nil { + return err + } + if len(r2.Repositories) != 1 { + return fmt.Errorf("unexpected repositories count %d", len(r2.Repositories)) + } + if r2.Repositories[0].FullName != repoName { + return fmt.Errorf("%s is not the expected repo name", r2.Repositories[0].FullName) + } + + return nil +} + +func p2pSendFile(ctx context.Context, node *rpc.HttpApi, ipfsId string, data []byte) error { + // close the old one + _, _ = node.Request("p2p/close"). + Option("protocol", "/x/kluctl-preview-info"). + Option("listen-address", "/ip4/127.0.0.1/tcp/10001"). + Send(ctx) + + _, err := node.Request("p2p/forward", "/x/kluctl-preview-info", "/ip4/127.0.0.1/tcp/10001", fmt.Sprintf("/p2p/%s", ipfsId)). + Send(ctx) + if err != nil { + return err + } + + c, err := net.Dial("tcp", "127.0.0.1:10001") + if err != nil { + return err + } + defer c.Close() + + e := gob.NewEncoder(c) + d := gob.NewDecoder(c) + + err = e.Encode(data) + if err != nil { + return err + } + + var ok string + err = d.Decode(&ok) + if err != nil { + return err + } + if ok != "ok" { + return fmt.Errorf("did not receive ok") + } + + return nil +} + +func p2pReceiveFile(ctx context.Context, node *rpc.HttpApi) ([]byte, error) { + l, err := net.Listen("tcp", "127.0.0.1:10002") + if err != nil { + return nil, err + } + defer l.Close() + addr := l.Addr().(*net.TCPAddr) + + targetAddr := fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", addr.Port) + + // close the old one + _, _ = node.Request("p2p/close"). + Option("protocol", "/x/kluctl-preview-info"). + Option("target-address", targetAddr). + Send(ctx) + _, err = node.Request("p2p/listen", "/x/kluctl-preview-info", targetAddr). + Send(ctx) + if err != nil { + return nil, err + } + + c, err := l.Accept() + if err != nil { + return nil, err + } + defer c.Close() + + d := gob.NewDecoder(c) + e := gob.NewEncoder(c) + + var data []byte + err = d.Decode(&data) + if err != nil { + return nil, err + } + + ok := "ok" + err = e.Encode(&ok) + if err != nil { + return nil, err + } + + return data, nil +} From 02a0977795f011fd9b95d2ef689b1a09b7afc2b3 Mon Sep 17 00:00:00 2001 From: Aleksei Vagarenko Date: Wed, 21 Jun 2023 03:59:16 +0600 Subject: [PATCH 1862/2916] Fix visual bug on the Targets page in Firefox browser. (#593) --- pkg/webui/ui/build/index.html | 2 +- pkg/webui/ui/build/static/css/main.css | 2 +- pkg/webui/ui/build/static/js/main.js | 4 ++-- .../components/targets-view/TargetsView.tsx | 20 +++++++++---------- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/pkg/webui/ui/build/index.html b/pkg/webui/ui/build/index.html index 044a50d48..0ef404b56 100644 --- a/pkg/webui/ui/build/index.html +++ b/pkg/webui/ui/build/index.html @@ -1 +1 @@ -React App
    \ No newline at end of file +React App
    \ No newline at end of file diff --git a/pkg/webui/ui/build/static/css/main.css b/pkg/webui/ui/build/static/css/main.css index 86c1ca612..70ec57003 100644 --- a/pkg/webui/ui/build/static/css/main.css +++ b/pkg/webui/ui/build/static/css/main.css @@ -84,4 +84,4 @@ body, unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; } -/*# sourceMappingURL=main.css.map?h=9212af710a168fca4ce287f416924bf456fbc497b2cc289b74211c435224c397*/ \ No newline at end of file +/*# sourceMappingURL=main.css.map?h=b04a21858ecfe46c93b33bc95788b6861c50ee3a7852bdb7721e314a3ee95b80*/ \ No newline at end of file diff --git a/pkg/webui/ui/build/static/js/main.js b/pkg/webui/ui/build/static/js/main.js index 7f430b626..a7625bb71 100644 --- a/pkg/webui/ui/build/static/js/main.js +++ b/pkg/webui/ui/build/static/js/main.js @@ -59264,7 +59264,7 @@ function doGetRootNode(_x,_x2){return _doGetRootNode.apply(this,arguments);}func var colWidth=416;var curveRadius=12;var circleRadius=5;var strokeWidth=2;function ColHeader(_ref){var children=_ref.children;return/*#__PURE__*/(0,jsx_runtime.jsx)(material_Box_Box,{minWidth:colWidth,width:colWidth,height:"42px",display:"flex",alignItems:"center",sx:{borderLeft:'0.8px solid rgba(0,0,0,0.5)',paddingLeft:'15px','&:first-of-type':{borderLeft:'none',paddingLeft:0}},children:/*#__PURE__*/(0,jsx_runtime.jsx)(Typography_Typography,{variant:"h2",textAlign:"left",children:children})});}var Circle=/*#__PURE__*/react.memo(function(props){var theme=styles_useTheme_useTheme();return/*#__PURE__*/(0,jsx_runtime.jsx)("circle",_objectSpread2({r:circleRadius,fill:theme.palette.background.default,stroke:theme.palette.secondary.main,strokeWidth:strokeWidth},props));});var RelationTree=/*#__PURE__*/react.memo(function(_ref2){var targetCount=_ref2.targetCount;var theme=styles_useTheme_useTheme();var height=targetCount*cardHeight+(targetCount-1)*cardGap;var width=152;if(targetCount<=0){return null;}var targetsCenterYs=Array(targetCount).fill(0).map(function(_,i){return cardHeight/2+i*(cardHeight+cardGap);});var rootCenterY=height/2;return/*#__PURE__*/(0,jsx_runtime.jsxs)("svg",{width:width,height:height,viewBox:"0 0 ".concat(width," ").concat(height),fill:"none",children:[targetsCenterYs.map(function(cy,i){var d;if(targetCount%2===1&&i===Math.floor(targetCount/2)){// target is in the middle. d="\n M ".concat(circleRadius,",").concat(rootCenterY,"\n h ").concat(width-circleRadius,"\n ");}else if(i { { /> - + - +
    From 636469345cf6c08b8f8ccb49dc997858aee76267 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 21 Jun 2023 09:10:49 +0200 Subject: [PATCH 1863/2916] ci: Remove npm build folder from git --- .github/workflows/tests.yml | 11 - pkg/webui/ui/.gitignore | 3 + pkg/webui/ui/build.js | 7 - pkg/webui/ui/build/.gitignore | 1 - pkg/webui/ui/build/.gitkeep | 0 pkg/webui/ui/build/asset-manifest.json | 58 - pkg/webui/ui/build/favicon.ico | Bin 15406 -> 0 bytes pkg/webui/ui/build/index.html | 1 - pkg/webui/ui/build/logo192.png | Bin 13703 -> 0 bytes pkg/webui/ui/build/logo512.png | Bin 26403 -> 0 bytes pkg/webui/ui/build/manifest.json | 25 - pkg/webui/ui/build/robots.txt | 3 - pkg/webui/ui/build/static/css/main.css | 87 - pkg/webui/ui/build/static/js/787.chunk.js | 239 - pkg/webui/ui/build/static/js/main.js | 61552 ---------------- pkg/webui/ui/build/static/media/added.svg | 4 - .../ui/build/static/media/arrow-left.svg | 4 - .../ui/build/static/media/brackets-curly.svg | 3 - .../ui/build/static/media/brackets-square.svg | 3 - pkg/webui/ui/build/static/media/changed.svg | 5 - pkg/webui/ui/build/static/media/changes.svg | 5 - .../build/static/media/checkbox-checked.svg | 5 - .../build/static/media/checkbox-disabled.svg | 4 - pkg/webui/ui/build/static/media/checkbox.svg | 3 - .../ui/build/static/media/close-light.svg | 3 - pkg/webui/ui/build/static/media/close.svg | 3 - pkg/webui/ui/build/static/media/cpu.svg | 16 - pkg/webui/ui/build/static/media/deploy.svg | 8 - pkg/webui/ui/build/static/media/diff.svg | 8 - pkg/webui/ui/build/static/media/error.svg | 4 - pkg/webui/ui/build/static/media/file.svg | 5 - .../ui/build/static/media/finger-scan.svg | 8 - pkg/webui/ui/build/static/media/git.svg | 1 - pkg/webui/ui/build/static/media/include.svg | 7 - .../ui/build/static/media/kluctl-logo.svg | 3 - .../ui/build/static/media/kluctl-text.svg | 7 - .../nunito-cyrillic-ext-wght-normal.woff2 | Bin 28960 -> 0 bytes .../media/nunito-cyrillic-wght-normal.woff2 | Bin 20824 -> 0 bytes .../media/nunito-latin-ext-wght-normal.woff2 | Bin 32720 -> 0 bytes .../media/nunito-latin-wght-normal.woff2 | Bin 35904 -> 0 bytes .../media/nunito-vietnamese-wght-normal.woff2 | Bin 10632 -> 0 bytes pkg/webui/ui/build/static/media/orphan.svg | 6 - pkg/webui/ui/build/static/media/project.svg | 5 - pkg/webui/ui/build/static/media/prune.svg | 8 - .../ui/build/static/media/relation-hline.svg | 5 - pkg/webui/ui/build/static/media/result.svg | 8 - pkg/webui/ui/build/static/media/search.svg | 4 - pkg/webui/ui/build/static/media/star.svg | 4 - pkg/webui/ui/build/static/media/target.svg | 5 - pkg/webui/ui/build/static/media/targets.svg | 6 - pkg/webui/ui/build/static/media/trash.svg | 6 - pkg/webui/ui/build/static/media/tree-view.svg | 7 - .../ui/build/static/media/triangle-down.svg | 4 - .../static/media/triangle-left-light.svg | 4 - .../static/media/triangle-right-light.svg | 4 - .../ui/build/static/media/triangle-right.svg | 4 - .../ui/build/static/media/warning-sign.svg | 5 - pkg/webui/ui/build/static/media/warning.svg | 5 - pkg/webui/ui/build/staticbuild.js | 1 - pkg/webui/ui/fix-assets/main.go | 169 - pkg/webui/ui/package-lock.json | 396 +- pkg/webui/ui/package.json | 8 +- 62 files changed, 7 insertions(+), 62753 deletions(-) delete mode 100644 pkg/webui/ui/build.js delete mode 100644 pkg/webui/ui/build/.gitignore create mode 100644 pkg/webui/ui/build/.gitkeep delete mode 100644 pkg/webui/ui/build/asset-manifest.json delete mode 100644 pkg/webui/ui/build/favicon.ico delete mode 100644 pkg/webui/ui/build/index.html delete mode 100644 pkg/webui/ui/build/logo192.png delete mode 100644 pkg/webui/ui/build/logo512.png delete mode 100644 pkg/webui/ui/build/manifest.json delete mode 100644 pkg/webui/ui/build/robots.txt delete mode 100644 pkg/webui/ui/build/static/css/main.css delete mode 100644 pkg/webui/ui/build/static/js/787.chunk.js delete mode 100644 pkg/webui/ui/build/static/js/main.js delete mode 100644 pkg/webui/ui/build/static/media/added.svg delete mode 100644 pkg/webui/ui/build/static/media/arrow-left.svg delete mode 100644 pkg/webui/ui/build/static/media/brackets-curly.svg delete mode 100644 pkg/webui/ui/build/static/media/brackets-square.svg delete mode 100644 pkg/webui/ui/build/static/media/changed.svg delete mode 100644 pkg/webui/ui/build/static/media/changes.svg delete mode 100644 pkg/webui/ui/build/static/media/checkbox-checked.svg delete mode 100644 pkg/webui/ui/build/static/media/checkbox-disabled.svg delete mode 100644 pkg/webui/ui/build/static/media/checkbox.svg delete mode 100644 pkg/webui/ui/build/static/media/close-light.svg delete mode 100644 pkg/webui/ui/build/static/media/close.svg delete mode 100644 pkg/webui/ui/build/static/media/cpu.svg delete mode 100644 pkg/webui/ui/build/static/media/deploy.svg delete mode 100644 pkg/webui/ui/build/static/media/diff.svg delete mode 100644 pkg/webui/ui/build/static/media/error.svg delete mode 100644 pkg/webui/ui/build/static/media/file.svg delete mode 100644 pkg/webui/ui/build/static/media/finger-scan.svg delete mode 100644 pkg/webui/ui/build/static/media/git.svg delete mode 100644 pkg/webui/ui/build/static/media/include.svg delete mode 100644 pkg/webui/ui/build/static/media/kluctl-logo.svg delete mode 100644 pkg/webui/ui/build/static/media/kluctl-text.svg delete mode 100644 pkg/webui/ui/build/static/media/nunito-cyrillic-ext-wght-normal.woff2 delete mode 100644 pkg/webui/ui/build/static/media/nunito-cyrillic-wght-normal.woff2 delete mode 100644 pkg/webui/ui/build/static/media/nunito-latin-ext-wght-normal.woff2 delete mode 100644 pkg/webui/ui/build/static/media/nunito-latin-wght-normal.woff2 delete mode 100644 pkg/webui/ui/build/static/media/nunito-vietnamese-wght-normal.woff2 delete mode 100644 pkg/webui/ui/build/static/media/orphan.svg delete mode 100644 pkg/webui/ui/build/static/media/project.svg delete mode 100644 pkg/webui/ui/build/static/media/prune.svg delete mode 100644 pkg/webui/ui/build/static/media/relation-hline.svg delete mode 100644 pkg/webui/ui/build/static/media/result.svg delete mode 100644 pkg/webui/ui/build/static/media/search.svg delete mode 100644 pkg/webui/ui/build/static/media/star.svg delete mode 100644 pkg/webui/ui/build/static/media/target.svg delete mode 100644 pkg/webui/ui/build/static/media/targets.svg delete mode 100644 pkg/webui/ui/build/static/media/trash.svg delete mode 100644 pkg/webui/ui/build/static/media/tree-view.svg delete mode 100644 pkg/webui/ui/build/static/media/triangle-down.svg delete mode 100644 pkg/webui/ui/build/static/media/triangle-left-light.svg delete mode 100644 pkg/webui/ui/build/static/media/triangle-right-light.svg delete mode 100644 pkg/webui/ui/build/static/media/triangle-right.svg delete mode 100644 pkg/webui/ui/build/static/media/warning-sign.svg delete mode 100644 pkg/webui/ui/build/static/media/warning.svg delete mode 100644 pkg/webui/ui/build/staticbuild.js delete mode 100644 pkg/webui/ui/fix-assets/main.go diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 74553224f..c2674e099 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -72,17 +72,6 @@ jobs: git diff exit 1 fi - - name: Verify webui build is up-to-date - run: | - cd pkg/webui/ui - npm install - npm run build - if [ ! -z "$(git status --porcelain)" ]; then - echo "npm run build must be invoked and the result committed" - git status - git diff - exit 1 - fi tests: strategy: diff --git a/pkg/webui/ui/.gitignore b/pkg/webui/ui/.gitignore index f471c1a59..3b5e29e26 100644 --- a/pkg/webui/ui/.gitignore +++ b/pkg/webui/ui/.gitignore @@ -23,3 +23,6 @@ yarn-error.log* *.js.map /public/staticdata + +/build/* +!/build/.gitkeep diff --git a/pkg/webui/ui/build.js b/pkg/webui/ui/build.js deleted file mode 100644 index 17db0e576..000000000 --- a/pkg/webui/ui/build.js +++ /dev/null @@ -1,7 +0,0 @@ -const rewire = require('rewire'); -const defaults = rewire('react-scripts/scripts/build.js'); -const config = defaults.__get__('config'); - -// we disable minimize as we're actually committing the build folder into git, so that people who don't want to deal -// with the UI while working on Go code don't have to use npm and friends -config.optimization.minimize = false diff --git a/pkg/webui/ui/build/.gitignore b/pkg/webui/ui/build/.gitignore deleted file mode 100644 index c9e9f1beb..000000000 --- a/pkg/webui/ui/build/.gitignore +++ /dev/null @@ -1 +0,0 @@ -staticdata \ No newline at end of file diff --git a/pkg/webui/ui/build/.gitkeep b/pkg/webui/ui/build/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/pkg/webui/ui/build/asset-manifest.json b/pkg/webui/ui/build/asset-manifest.json deleted file mode 100644 index dfdfa5fab..000000000 --- a/pkg/webui/ui/build/asset-manifest.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "files": { - "787.chunk.js": "static/js/787.chunk.js", - "787.chunk.js.map": "static/js/787.chunk.js.map", - "index.html": "index.html", - "main.css": "static/css/main.css", - "main.css.map": "static/css/main.css.map", - "main.js": "static/js/main.js", - "main.js.map": "static/js/main.js.map", - "static/media/added.svg": "static/media/added.svg", - "static/media/arrow-left.svg": "static/media/arrow-left.svg", - "static/media/brackets-curly.svg": "static/media/brackets-curly.svg", - "static/media/brackets-square.svg": "static/media/brackets-square.svg", - "static/media/changed.svg": "static/media/changed.svg", - "static/media/changes.svg": "static/media/changes.svg", - "static/media/checkbox-checked.svg": "static/media/checkbox-checked.svg", - "static/media/checkbox-disabled.svg": "static/media/checkbox-disabled.svg", - "static/media/checkbox.svg": "static/media/checkbox.svg", - "static/media/close-light.svg": "static/media/close-light.svg", - "static/media/close.svg": "static/media/close.svg", - "static/media/cpu.svg": "static/media/cpu.svg", - "static/media/deploy.svg": "static/media/deploy.svg", - "static/media/diff.svg": "static/media/diff.svg", - "static/media/error.svg": "static/media/error.svg", - "static/media/file.svg": "static/media/file.svg", - "static/media/finger-scan.svg": "static/media/finger-scan.svg", - "static/media/git.svg": "static/media/git.svg", - "static/media/include.svg": "static/media/include.svg", - "static/media/kluctl-logo.svg": "static/media/kluctl-logo.svg", - "static/media/kluctl-text.svg": "static/media/kluctl-text.svg", - "static/media/nunito-cyrillic-ext-wght-normal.woff2": "static/media/nunito-cyrillic-ext-wght-normal.woff2", - "static/media/nunito-cyrillic-wght-normal.woff2": "static/media/nunito-cyrillic-wght-normal.woff2", - "static/media/nunito-latin-ext-wght-normal.woff2": "static/media/nunito-latin-ext-wght-normal.woff2", - "static/media/nunito-latin-wght-normal.woff2": "static/media/nunito-latin-wght-normal.woff2", - "static/media/nunito-vietnamese-wght-normal.woff2": "static/media/nunito-vietnamese-wght-normal.woff2", - "static/media/orphan.svg": "static/media/orphan.svg", - "static/media/project.svg": "static/media/project.svg", - "static/media/prune.svg": "static/media/prune.svg", - "static/media/relation-hline.svg": "static/media/relation-hline.svg", - "static/media/result.svg": "static/media/result.svg", - "static/media/search.svg": "static/media/search.svg", - "static/media/star.svg": "static/media/star.svg", - "static/media/target.svg": "static/media/target.svg", - "static/media/targets.svg": "static/media/targets.svg", - "static/media/trash.svg": "static/media/trash.svg", - "static/media/tree-view.svg": "static/media/tree-view.svg", - "static/media/triangle-down.svg": "static/media/triangle-down.svg", - "static/media/triangle-left-light.svg": "static/media/triangle-left-light.svg", - "static/media/triangle-right-light.svg": "static/media/triangle-right-light.svg", - "static/media/triangle-right.svg": "static/media/triangle-right.svg", - "static/media/warning-sign.svg": "static/media/warning-sign.svg", - "static/media/warning.svg": "static/media/warning.svg" - }, - "entrypoints": [ - "static/css/main.css", - "static/js/main.js" - ] -} \ No newline at end of file diff --git a/pkg/webui/ui/build/favicon.ico b/pkg/webui/ui/build/favicon.ico deleted file mode 100644 index 496bbc69cabfce158be2fbc7e709e8a81a5bc95e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15406 zcmeHO3v^V)8D6vxTWwWZwYJ*gQCn+UYisN2sUD@R)x%@&y$Qy*0*Z(SJr#k_~K1sI8X6j^_Pc$c$$5q)hSqBj@C_1{Q@LcIGU`Zm8@bQIo zzk~u!KkI6i3>TnrqpY`Mdm0cH{5ED!1ok3XvY zW7L#BgJX;QCBv(4O7+QDKo8fdFm~@AkLM(R#N7+HyD*LmAlGiZ?+HY- zc^H?EeNm&YXF+FJYug{k6#Xs)?1m)Yk%WMrMqw<+B;NJnM4Pr5_Tcl>8Z>Y72i#J& zjhWh4JU^xP3;{i}0X$=T_|7`en?|_eNjHn}vj>i2J%7HiG2+>pyTts}6$ge+?Ece4 zE$|~+BX~8)Z}jQ>XKU^}Fnh&|A~b)<2w7h;v&*-^=MFrV$+|$5erZBpT?C?en3ahi zVF*hcS!Y)UqQ?Eiub4X+_{RNqFq!H+5u?-db-fzv>Yv->jk_<%1bZxP^fR6CbjCyf z)ZU*zYCMSkHp6BPKtGj5@#{`^qQy4AhA=*#(N_`I2{xB{)o;l$gA5TTT-Z@K1&1-h z_zKui4=4C+^_Z8{D!t_U=MR>o+X(#Bx6poAg0adXs{m4@{jy8b(8wMcrh^hUe=(gyZ2+0Js#n=gsj!on8Pu)-eA zcOb0w1id3F?LbJ&x6*(QxLLxCs=G(TUh}pD!uowyxb}SeBJMjuyHyWieYlkl+DA(M z$+Eu{=2*UA+ujuYu(sMt3+v0dJUo7jbWnpFa-(pUU z16(C}vK>78NZHLY%sH$Lby^3wCMDtaVQs(5w2iR7FG0fySb6fBw7enB0~k~G1T5kJ zlQ-5)^e6q%8hsJtPOB~pqwYVZ!zS>95$$&>f3E425&dbj#fj?V-Nt97!)co_kDj9L zO&bX5qtjs${1H!#B1~eOw4s1==$mfFNoYR=9n5l~XH$RBrp~H6TNuWvm9IW7%I6Fc zQ|q4-4O{9N8%_&iJqArPR!q_+KILXiw+YY3c&t2f`%c(J6Y!gc$8Xv?{*?iTYF;lE zQ{EUUiYMQ^YFJhNc^ogsK=i5JsBxjk<2p$lL%t8I)4o?&rCkMGt;{A1#$XlW% z+B(g#ArJDh<74G({l?_jg$Fj?*{e#PQkq!!MM%l5ys7;_LE6v>$`-^lZFS z?O|<>FO>hYW95&1%y+Oaco_ZOD`}&DlNr;XTqx^ME`krtV{DYFGvhVgmu+8WC!;@_ z_iOr8JRhd$nR7rJD`R-tkZj{#EJ>#WY_vqyn`M-~Q5#LaWyedc!y526;Bd~R%`dEP zRqD9X&r3MQ^a`C!cCyhMcK=m@O}#>Q=YZZPlBO)jPP-4XX!VTmQowkDkGodFSZS7) zUhz$|+3ocN)}MWzioCN`T6_omZIYfWm#mG8aWfv+>kD;Fvn4NL8wm%ReXR5_cITsg zPYMP)9Y1S;Sg^VhYp45GD?InVD(u2F5mCRsmg6wM3d?WU-%Apne%~I({g8xVIrpuX z#p1Ow5eS?6@ir&}XzLMuBjIXae@1NDzEX^=zJ0w_?-B`iCikTD#Ya=|?wFw`~omgRFE7H>B#5KGoh65fdBIUclgp}}?B7uAN1p4w;k=&ASaX1s5g zzsZHx|2howB;0WH!gDNn5}>#Fc`Mu(aExH5+hDJUkq=qmuhhDE%6pyC1~9`1VPRJq zXmH>4IqYsbXbmUr3~0yPl#Jh($vLK!pJI(3;^dQ6#tGkR48hv@nSw8U)>3b!_6rsM zc)iEDPYr8zHatzK-yW z?wc_FU*LHHbUI|9)d4x@#~@>yyb1dZg9c`w6gXTbN@!6G*dP%7pl5F7usE{1{)u2fU`r|IFlD#mbGdaaQGT zDT?-(X?N!2M6W&d>;qy{?#I4Rb${21o?6d50_JmTJ~OH}$Q&f$=o3Mk zc~8^!;b%XjA5&>Ky>m~EHT#hy{+5J{TBTg$$OrV)oRyqk^zk(uk6iXJIIDgD@Y1&& z#NPU84{66KyP0E8=6?;u+{Qgl^2&PhByXJM@~kgyy`jvM9p@6v-)CA=kz+T8Fuy8u zWRizkQr5P4T%46g^%dZ=0`w7ICv*?lm%+!{<{wCpJc4oUBH%p*zTlnaucUqraK;gMm+7#AP(Fcj^)yCP-GR<6NbXDD$=13qPEuDHYE1!k@=w$RI* zCG%nZbH#OlLmXt-#X?#7VB%!Ngq>|4b98N&OKY0KLxIKH*Fvp zk6HJloYV9f`_a#RfZ5N7dc^8DyHsHG%WSQ{MGQ!0C^%tU_6XwwE}!z2?{$y@JT=qW(7c;hrWO@>Iw< zjzXVm@%=vh40*RhX7ip?9enK=(*|)@34D8z4a;@cJ;0*|yeMOTtcT}N z_S6r4i+d0DiEBnCU&(ww?^>8^Vj|z(e6;dI&&^naI@53Q95xd_&ULP(dV`FuGH~v~mvWE7_#C-e?r9F-k-GnscaDZt z=by*e4D}(UjW8|1`v`ur+8^$bx|8T6N3QkMb)Y8i2k^C*fqpTb)OoSOpX*sUV;M=8 zevUd#!G!gxs*MyBAeS07&-tutCi`*_{31mW{m=CAr7QY6=JO;!Yf0bEJHF6a5HimO zuiYtGT6IcZhhRr9QeKtDAjGI>E0;LPSyk{3unJRDT5vkrN8@4tNz3vuKdmi>9)@GQ z-MQKp?__{hIdCX*TN|2c77G`7$%tjeBLm>9?IpJnN4(XO3G^F6=xRj{ zF$nGIpQ@dv@r5oS{{km{RJ{6_n7M2$`f$re*w>$|`10G_GvY2?D;;e$>twsN3?hdD zTlse@d>j3X=WBb3@B)v(S^7NQ*|*}$Z{7h${M2Yir)vI_$Zt1@QB&{X9oj*e_Qc*$ zkBgJ`m$VCQN3c@6Nb<3k)G6Y%f#`$Xd^UCV!R{u3xU(_S{T1deA-g3XrHj&;4~VV1 z-W4;KzW{lRWr(F5bF`H0=_+>@s}6X_-72^G?TO#e5p&g=-!kv|u56qmcJAFGo~`Yf zF~7=r2OEp2yyLhdK-stKSSOyV>4|ko>!$Lxd&k|Jwz(e8gT&d49eApaYZ%~;aE@`G z6^!NMuB=b&JFrX4Uo{alv{$&}t=>H0czpS5GpX58;66T>Inw2QjS6lY7K@Ev)k3}O7Q z1^g)N9eWg`q$G!Y8Sf}rW#>2i%aKfTSn?(h@&XR>Xa;@wPV{LVWs)$tl*Ki9XM3aF zuje|(`M-@rS4#uGAGvn zzGDpD{;FijRb~|q|Ci7O`bQj=u6yQeQ2&vzn5;E5tX&w~m> k-LAl1ECQML7Eiu)AL7+G?;m^__cBhDe*a&N|4t432jt`J!vFvP diff --git a/pkg/webui/ui/build/index.html b/pkg/webui/ui/build/index.html deleted file mode 100644 index 0ef404b56..000000000 --- a/pkg/webui/ui/build/index.html +++ /dev/null @@ -1 +0,0 @@ -React App
    \ No newline at end of file diff --git a/pkg/webui/ui/build/logo192.png b/pkg/webui/ui/build/logo192.png deleted file mode 100644 index 11f22322c00fe673d7aa1530ecfd53fbc82b2373..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13703 zcmXY&V|3tLw1!h#Q@c}RYE3(}+o|oTt*LE$YTLGL+qP}@_Ph7~NY2X2TFFkQfeI4yI5rx8rLi^(Sp1sbHxjepRrplYlV8kRo&iEf>^m3#fc#ghbythAIJiJ0* z>$!qgq+BcBVwf31A&eY@uAqnJq`?n}A(sV(yTRNw=zPe@nKFXYR?bm7(X~u8FWMib zG=Z4yoU_+I_inoKbhj9P%0gHeZ+!#W{aBvhn0Zu!F-&Mn4idJIq3x$MWdH_-Vr* z!Ei6Y#99^_k!g1t{Pzr;-3$RGyO^)gYQSyDgR9F7CMq1#>oDS9#iNoG+T=x_3E%$kma_H-6hmO+$xl*)UM* z2hrhMjH05Jr{OmL5C~;rwYj1Z`dQu^B6jJ8($aXmS8F7P#!^e}jgy37vD^s9-0=*I zvVVqyWsLA4je@w^_ij8)dT@BUw^K4^iA#wH;{lNN_3PzO-_?Z3O?vrEjZr1(<%FtAqKfWUifaF~P@;i|3&Key|ro#mw}>!428ftwh~ z!R73g@Wsa{K0H7-GI$PD1qanAs2Gxa)m~tNOn;iE*brZd_j1jDkS8r-J^o`AByX#G zr#iAT(7csubPxntAR5st<2de7{8rHPNp#wcf9k} z`r>u#r@IakGd6wXM!=j74Ar5;MAqVDf7eERF!tn)$7`O# z`8E{^?2~7_fX-=5JT({lTvTBS6Uuu+n?QZfBC$x#7-&*242A5U;M_Lgax^tc-(%QHcgDK;(K**8=E`ZVj8crXt0mXmcN5r`9bjsC^g+$#J!=QoJL<>P>6}t#(i-bW zC!GB0j|k4sUvQ(1FHsKN>Yp`ZqMgiB?y+*{Um;dV(>cG{2h7ED_D4}A7*uaR_^52h zWEk^ZkT1rCe7b;bUOuhM@F05Y2iA7O{soT0)0cs0t9z4mlql_($cORBGX%|FuQ1MP zeIQx2o1pz)abORL0emeB?Mk9~nSW8F&E`SuR6Kn9I~U$(LOMb09rfOR)_rsd!vd@X z)I`uW?TCXQS)GKt)R$J2mPy@WOf&V+Nm9rVOPtu-4qx8 zzmwW;ugCO|>P+if0gl#=I(lZ)R$y90i~p`b2Xr5ZP8VNQt5zwHX5?LxrG&hsmG)2J zamZu}X9s;3U@(DSg1Z^;`?-}?!#{GWAXr37N!5Rhy2&~%5i5)D1%@>>o(p>`jV|C0 zo@2IwdOIo~WhUi8bDlkZkjmLX%K{6;jHZo$8?_t<6KwrMS9oUD>E;@Kz5a9o=|;=U z&q55^D~!xw*o8B>D_uDIsi+Aa9;DxMY)?(&y0W4klQp-XuwB06!K?GAS6I6caA@-b zf*zL+m`31Y#w7R;Ywl~^il+rrF4q!;LhPr3- z_YD4{Kc>uWo*`acNoK3Fk~JHMQ=ooG$N|+9!AV`=tZOY#m=p-#&FF-Y+3!tv2fyG=y9LZnD(>9IJ_#aN+eYtGqFi4nrXe3`c%7BF4P1f+H;UW-;`A0`x1 zUONOTjo|H>2bT{ewv8vD?*#zSe7Z(gmH|Ku55phvZNN%7t~0JcCXOpjPNiZZb7eFq zm{7|8EGPzkkkA{F4l>E)C)PYwfI_WUuoqqgj`qpFysq)G`Q>axSFSC6c4JgRSG z;)RJj>JpqLF8>jZ#H(KgOKtw~)hwzV zEnbr@{b1z)!L_=M`o+GTdtX}hlo;RP4pR@uOdY&cSDaydn8#7-8?iclf(_}7_BdAy zv*Q7L6wCNt-T2(b{JkD_;KknW^7<@9ANqU{RCInH&)tM3)I!b^OE2BKtR-Vw#r@~A z196g8OEw4}Tk`pd?_iCVjep+qBS;0&VK3v!zGe&9Zapiuv9OIOrG{>N(50+g?Z(g(*5 z_-u5|*cqpPva1J-iq+E(W13Puj{xZJ<9?A=iCEyq;;s#EZQ&^~F_$fIXm9LO*$Xh^ zQ^hX0WvkDxjD?Lc5Y{@0+E^>E7VsAfh0?3amahn2?{!$eKsUgJ+@1cqMoJ1{DZ}6b zB%5RB1uJn*0FPEB1%_}x#8=>r2raO*mJT!K&vO!?S8vuBxz6{8AEl7tWNcx^!E&4a zuB$exk;(F~T2ZKb->Mh`pnpN^zRbW63h1*0RtkH*W-iqX1`(wZcCck>$-{*cvcMl^ zIG1E{MnFiY&_fe1cssy;=vz${iH?~9@zM4*ku{AAyImIW(dgONWo zoUZw}91TqVm4fbyzGlNVhTMAq)}5Oe-al;lc1to;*lAadTk{#kWl|;MJ(y^C&?H!| z(CVoQL?=&Kx;+fTlQ{+K3Ry0L&xg{uWd?+QU@mSCCEja)zHrBt!6fN_>oino+datR zc#k#3-d7BEJ*!Q7&r*+Qr5;vQiut$Bs}6WNe9SeJ_J0 zmp|iuzPxaMb~1O$&KJysg^8>wP{eLABH))GV*oL585dcyhTpwM{aCRixtr-3n#>8g zY`0k*N=D&#V3flSST#8A1Q5LFwi7ddi$nqJi zSr|v{WU^^5!A-Y6CuI7Cx!(j;j_--=E@r2eF3CPSbYxuB3ThWhzl)KG07UsFCscW+ zzX7IwVCk9SzQ6sDoMd_@Q=!q|-q_i$9}2nNm)}2Lx!>+1jir!FY5l1G!f_ZT<-4`W zcZQB1qm1p-xDZ{9JB9dfcv&At&$EplDw6Fxki7Cbrsgk+ms{oao>2Z?kFfhWUkk}= z=Ja=xHDHGwvwtXS^ciO3uxUp`F~TZbfzbM);!o)GTsWXtU@XC0`zd415FYpY>zgI& zpDc2OcE(ZuW43t(T5J16%%PQ>c?(`;a(eFY0mYZPq$*9l`EyI5A@{`NsLKoIYtMF+;<6cygab=K${n{PBIQMwTWwE@u=~WeeL(QvY4qGTujk$QpQUy(Ll&VmWlMLcX@nS%Rt;z0 zuh#pqIRf21{de@&DGE*<0QSIKurc~Lg99w4MCie&J>ND(;qAo;%?FDi!enHFwPZnt z6P8cfz&}pb{q5GH=#M1*r`_0am5Va)7#IMtI0_y9`9cXdxh4ZF_X=LK9ufQCLD>rn zLqaQBPk+RC65B}n;kT_b8%Tn#V=`gjZ`W_7jS!q8_S3nCvnj*&V4t8Eu7(ZLk+Ja)9c*gwYbb(LI^&+bi9-7)4gGaoF}k5wFcdE-zV%q`Zg8J!899@yx4Sb!-(oBK^zVc} zX=xi(t(CnCyMM+M4x0$xADO2vfuerIg?i$*>U$|c9C&?VA9!AV78q}#GE`8F+dt7m z1#bOO1%mhbD66EHVq54#)`s6bjD<3^c59++dG;YpU7a7&$B6&cmv8(O-k~5Z1W=k!TTSx7MK#; z2oPHVLXhiVOE6X0E8f&HsZ%Fm5m?BZ9+kfTk=hOKsZe~iq2eCPoI})PHlQ$`sz&oU zoP~Sm@NK+ero`%xeL8c_?-oIAC-MgADoIGWIjv4EJkgwc0UeW{o1gM3OTg7SlvXQA zW|#h*Lk*+6?Rk^TDxFE{93J6k1E-E3e0-mcDy_!{hN*hIV&mv=mjQRVfsK`E%kH!o zp>J*LZ3TpC1$)E7u9J{K(ye6zI=lZUE*cwPpf5UZbC0P!L}NBc%o8eRal$UpS?gOj z16>m-&MUuOAr9{fzZI=d8-*8V^phaWU<$EZbC}(AQfvibdO=&6*6W9qo3m7~twTxB z8WjSzB$bU6sQ}&9a8`nBy*DP^9Iu(Y^QWS&O2 znD}3r(~_=NYOr1sJ=shY7ml}&82F$I+~E3g97w=LWGyFju}f@zu_9QUcDlq_vWY#! z_nvK@+c4N$K2IOj6ceo@+rT2y9Vboos500f^A*Z8S-FN@xFk=Gz!ahVuS&B z;t42WANW9EI>U2!&Nkyq;j=0&Su<5cjyl*IN>&Ig!fwv}A<2?K|0ABq?xawWy^t0r8KNE^^_fIcG-jb~c=hj{y&0 za;0J}%m590D8p7cfImyOWyqi!w#RaGY3JD-7#N~)#c!uGet7Sh3Ne`4oTvNno;4` z=WO7FIT;p4&q~kXx1Qd}WWNBv0w`glOvr8K1`G@y&kiyrDWyQMJ-T;J(CK+Mf{eg- zE6w&aKRC0z7Jy_8m`HTT>B7o!`iWq7<7G1^iK4D{b>SPyM;?Iz%30|t+gX{y8QCIo2ea^ zASx%^I}q=VTSopQ*@_IteU>n#e`V!6&@X(;f0~znTrD~>5ChQR z!S3 wqli$aKQ2SbKZFKg{E9g@WEz#e7APR8O^xSmtxq&tn!vCgCj&k^n$_Wq;zA zAM`Dv;VH~GnZEgN+f{tP6GI?QJ%CAnC)TK!g(l-IRitx%djDsAT3SP8(raAI-V-XJPO#E7uu)q zC5$n<^Wi}vP<#<2(vH+LMoTN9q}D1A)V(1`*&-=i){4-*;cZeF2-e!U<;2#`V;balnGP<`|Sh9?U$#nvTrMq z2ZvdG(bpSzWSX=I^9<1%KE*Fq^dfkU9l%0~;KTQi(2=GY9xApw<-xM+u)7awFaN$5>r(HwEOb^^?O96vocq-^A~aW7ganJsxg>my^}3eei%8 zyRcD;3X-a|zRxQ5k>SmA{5kCO)4khDq6fAfq+9E22pk~A(|!L<{M-Wi!yUC)@1i*f2chw_?%TD5bN zOzu;LoAxIo4*TOPVjnwdRe;6}{8+QQ$A!4o$~Fj;p&j~dsOJ(z)R(`Qm7=T_@|~jk z5`=4&X3i;>%1#Qp9Qsp4m2zOLh$ho9^HGo@wMrANpx9y~Il-2JS7p8+{?^2PEe5;A z*Gp0%DHEub!<9;f9=fP&6#S^}C{$Rh7zQ5_S~(vJe*B(3nne_Z=$at%l2SZv8G6jl zr^Ddx^bPGAek=6{ZhRVX7d&mi;x8Xs7+&(C6ys0NEMd4Tyq*G+BLPXoXj3QRy*}Ge z*=lh=`GAs=5utG-2&LjOb!j7E7hr4Jn~FBgzR|P=iceE;# zWQp4yeu~XCS9UAPWKQDu_zkTa8P;zF#U@4EukzvPioqG3@f`eGnW0`+fEdKqZnW$p zu8zsN#22ntJHfK---fqVE$G70I3CaGvof2*<-SfEcn*nXXvBq@B!?_wPbr4fn}?Or zM2py;i727GNDW{Pe9~!tQu&!FNhIk}4K1W1G!5a8fLXvIHK)953L3E>LudT@@oaZFbR$=kpCmi^sIyRT5P)U2grZ$FJrs(Gq_r<-BImTq<%Y9Do)7J5mJ976Nn{_OJnA z1QRk=jM_Pc>KvnyR+Dv{<1q^kR9AKv{)VuT0e&M(^K@%?rCBu7!eU&hg&VI*M`YsOvIU%mlhOy zPqT>jJ-Tmcu1Jh@N>?pUKxmKU0_!KP(L{!$Gx6d(8dK1l?q2a1N4 zi@i0ryR@muM9prolt>*JqW8-NaIqGPbw?NA`foj$fRP^x43{-qvyH(#N4IQ*ylI(| zdKW07IMWZk;ZzQYU#pONxuF!nrC-*r6DFR@{0Zy;!4HgPC0<`ofx00V;8F;CFqz9K zu4&Rsd=`F^og9|F@^uQzn|Lu^ptrSpHe&EW5m_WI{}%>+tI=!;3KU=aXjXHj_tC#gi7&1t=g#kJ2RBkgZ*@lO4|Q!(^KX;i zw_;3i+eDsvy9P1FV;L_w40Fn6I!<*p{u zDRy4^94Jzbsayb}ai#7xu@7BX!35(dCTM~)*G__?m?rP-3k=j)4Jw?g14vd%wHk-u zt-QPr3A&ELag(@iVHG=#^Ub$#!@~>a|22b9#Rq{!40 z90vwj$rMoEfG&aMNa$&I`*uzRBl`9S!QuFzn#{Eoa{hJMmyMXp2&N<6Qf#L0ElM3q z?MlJ36MFJj9m~w*G3BrN{*xRKX-{6%CcV-5HU!7q?JJpdl|6IhiQwI#;4CP3aVIyZjp`&Z18iJg6N#@t@0jvSjwi;#x$ldZ8n8 zO0H><4Eir>;Y1*%;s-Rn?~$ZQir z?j&`+5jT3B$5lfB5Hg(E+Rn?@{aN}!nM9yM0SBDVEc?_EV+ib7H8ASAAUHm&@7+Qk z1sSk3?n%!_$TKNnt{Oz+o?gjy`5ej8NKq`>iHI11F3QVFrUk)}pJw`wqTKIGN4t`x zP^-M8)<3TC@wPW58x1O^+GXq2D5Z_o zeWIr-+llKMr=^WSe4mTOGOyV6YL{<7H0Q5J9E>}oG`6cIJbk&vB8HI^;g&8~z} zOtyJn@Gzx`=jbAJk58m}i;Ye=IL{~0%a10!Z&)CPi6X=v{4aUEQ20n-I!`|iLtsko zMIkM+aUvF@MER`2Asvn*{u(3jzZ#WK^`so(zWE4y&M7*V$dX0En?U#VQKrge^qU2K*a1K=TB+>@jgv&B00@0uC&s92(-|`t`InVCjikxiAFu* zMIwj<(Q@t1!Q;rPvOTSIK8bx^QgE4)p-160RgxYrP<)(=#op{&abA%n5fCKf$LnqguuA+WF&kHo%0R5ymcI7K{=Md0wyZ|!l==nO*D*h5p1|YO~;X&O-oQJ;IJo7sTFytPlWlRe}#XfKj_;{kzsS^7#NaJ#$r%DPk9M== zte+1BLBz~ut1eVMuIY#ePzXXAH8N=b4_LyBZ7hV2*Re3lS;nY*d30Ii#zQGVYF#B$ zBl&<8S~p#O?`x|4Im<}%jl|Yq8g-pk`!7A}A2_~^t;)P7z}NBI_+tHzrVD1GO*q2;rM zph_6QHE=rQhQ3l@+_^`ROgd943qz{lZ1Sj{RXc1eT|N(12N>9(i+V?x2(MQs8GaGS ze?}vvB`BxHau$bTk^3ir$xezbAuK||WeDZM7N(J_su9#zR-GWEkHfcRD^J_qS`Q__ zLq;g)U;tg0INzJAQMF%fX`K7PIy_KC4kRfFBh>#Ke+ zyE)`Wyr>5%+}I69`>KGLp(A|EJ@Iw1W$RcRs}4dD}M-VL>B?p z61HQJN!tucMRW0_H?afZ*z3rh?6QFm$+B5KBZ>6)zIb>>gzygFgaI}MGtQOub3&(8 z636-JBYB;BABNB6XHVoDTVo0|J+x2GjO1W~$Xzb~>@Y<0BbCr(;6AQCAu5joRw+2# zW<6Jt1e_iZSU3n#iQqe?Z@aB`tpt!77#<1Cf0hE5rA_+BmL_y}Jp7AJosDTlZ|M^DPB zW}0PZ;HO4Ejb+K>8xDb7C+-lH@a#*>L3}LbZQ~1e4D~g|_owcfGQ8(!^m^f%Jxi#-XB-{^lL+Z1#$1@NG$Pa}`qL9ZF=SAS*oWLB zl_9N_L+Dd8d5&LG9A82B9PbhqR+)*e@P-r{42BgxV+J-7K_|D_je=MVGzc&;z{PR0 z4sQ*Rm^Jg0BcmVG+!g`ln2cjw?dy4cy6<)`s)e3LeWb=aO{K0)5?N zmS(%TJ>H&rSwb9sQd<3asByS$DIe||>6B7Geu}_uBvp;!$%uX6+j4l%TKP6Car5>& zzKRR!pQ|4k|wI4Aea7dw*}sny}2&>^XS9D!Mp_jQex z8;li1sLOaEfwQGo4A~1+Jo+IOz7F0}{FAv=HOlh!j7o)%(h9)0ANr4592EZAYa=5c z8Oj-6SE{`KL-Z2MNF$u*^Q;lk_YTKIPFq0_ElSpYZ)pU*FO4h$c2vL{`o_)JzNG2A zF@|(4UbJcS6w@1a!@1Jd2JO$^QpcDen{O;sgsEU=F>Id9HZN}*e8FviiA6~@ZZoty z%BWBhTd8X88`t z>2UgORu$8DMlGOc1d0T;ciodhquxal7;&MUG;pf9O zd`mcy>*y}Psu;?241vZ8_}D^vNl7wWDo;X9*M#BP14v4ev?-jytaK2nn+HVtPT~c* zmPL-IrPfY|mQbojwbKfoeiFSBGd225_obS|R@Kw4AthI;(Y7GW6evv)qv1Hwic_Rf z{iE&i4r6cpIr+<@*<-YVpnT^?uL~88q!}02&hF`Dl%)RiH=|oh=(uUpg&7v#T{#!g zur~{X^gOVI^g$LdNeYI^l9!`!w{;|Z%t2A^=*(RK*XlL>akkT(XcS9$2`J`>jQB-0 zKgUo=`TZmXrPGFIeL{5vVy!~RI|nP-jRC9t6EJHQu=~AS*cv6<N=f{{5!2ag-gR9wzPO=x(ao@jiTUvf_62EYbOO ze)3jqsT5$0>}*iQiKX_Yh%?yZ zvNp)f$7s4R>Y3eMTh+-DE4~U}Y^63daLW-^!2{}DiOG1`!_Zu*Bv-m@S#xHO|2}w-;=v``}5K_lHe`w*Oe1+l?wxwy~)0$FSwpLxk~4f zUzZAynS>cu>h!AE(^x7Ly|=4)$4|I`x2BW!#-j4jR#oyQ+E&6dIk&FDTC)@KkuX?b zk3j!!v&+Z*gzJ|;n#F6R*arf1X>W}{=;~p$Mh0=FH7WfvA59jd06*AgG~mm7Ggtp@ zc7Sj!Dd!i6?L|LqKZG|W)xu&53{iY07LvouWi|J<12mhfLpKM+pLy13`pk)fo zjU)Bg`%G_dIf+-@Y=oiM_@V|Rp=s&mDGqdiU&pdb-Lm=lSE|Q`mE-@Ju1zvJwuus= z5ndqA($gq*Y%&9UiHHYGI_#7v15qVgqlbq3Z)Qe^WRpoV3f0s%2Kr8ZXJrk=;$VUt zJUY?qMY6MBgyWLS;$3(3U6N(INLxoBBxdStAmuJqW`jKFc6cWD6%JtWk6nhi05<=DX}|U0T~9eHM`}(IxS^3J5LC$HEctYWcjdLC=d3c z!XP_zq&e_ZLqM)Z&NM4nfFRw|5@Rn)ID1DZT)lo?!oZ<0emK@NUKGY1fs(7g%w+{) zvwJ$xrv=k6Oyo(F#(XyjOO*77c6hA$Jn3GtDEh>4+=yr#e4drsFz}hIBxrC97jv0C zEyVIh!9x?YYtSIup-B{EkG-bY(g0#tVo-#fUS%umYj)gVbqKoaCdR5ec)vYuRyA6E zpg{7-p<3jU)Q_M$>I=x~oNPgNBlVWNSUHfXk$;QLV`gmiAPDGF6WvW`EYrQG#8HcO z>X3LVMq#TsbHEVEbmYR4@%UMa|p&VRWxv6Jv#guMf0wSm@-qKb>3=2OLLkb zbU$&#TMJ6;bZ_n8@FC^VC{VgSQuRXZV*%u0H7Ss{xltz`z?n0MQdbqVf@0@HSz3X~~CQHd+=<2E>cOim{~vQpeU)fQM{sf8z5x@teQZXx{D=fB%)iWBHeA z%XLr-dD4agmsnIwpF2M!iSk#))OgAR$RxE4JIoQy81bJJ)!YT~!Z9rrDE@Yk!6wc^ zLz$YU1cOC^bUa2F6xJuAGBI3M>L8@2b!+88b`GX8?m|+M8QfXlNdoVhf1c3Bs7dgd znk3pirBjs^@K^H&6qEkiWJ29D)_+A?dwsNnboyT=TdPV`(IN;o4B^K$7uN z4_8|Cyfp`5u0$VGp&v?7oP_lCiD2bdk_7GgvToQ{+#GKP2XuUC4s0*l{6ps)Lh;1S zk_a-t%;eBC*K&ESu$>`NG@`t9ZlEBZXmtjYe5mrVBSi(R_(jtul7|L_&KXn?|EXBw-KfsGVbA3I>Y6B ze)@Pd(X!uhD_Ac}B<@4zV?7p^1m^uE-$8Hb${}xyN-*ICR!jKLW}r2ivn9Jxqa{9a z&9V~~QT=9r^FBjIG}Zv>qGii=F-Z`YB{)G$JL|Ff39s93RxK`k`S14B2FVc1l{ zAZu=v|MJB##q8vFEIC?DL%JkJ3AW~)Ggze8S9MJ|5f8GURs!YYpFj=IK-Ls2?n_@4xVeafvB`EGtJ+6Y^D`h@3#!H}ufpNP!jW>o{SEv{7B8XunL&=)!wi}6O%t{TRw zBy<=GKow?_6~r@nmpDq4qIdUCz+2e7vv2gzi`H)*u`aEC>`3?EN9I7f3rxlFBSq$mlE&h0u}oNX>CVvX<|CNC zRP!PmWdhU8AR1s0uCU(R56`p*n|0CM^CtAA-G8?yZSjCcmZY_jG7!j+djY-wGP|Mm zzQXsEly~kNljV2BX8Qy{bSkFA+ZC$uBxSQ70MVLT5(?0@iFNZ2hybbHx{XpyjVx% z3?L=qgeD{UbJK2w%;w_$gI3=ZS4&0mT<4dI48JNlJNpr~6xA~jK2J#WJ?-B4*ln>y zKlqkP7LT1$q7n~yrWwz<+b4*8j05e#E;yh*xK0qqrr#GnXtTj4&tpoTo*d*W;Iph~ zGQupHh!%JPgR2Dj0Atl_sp%Io(MY$P_Bng)->rm{qS;Q(yPaz$?T&e(%B>^LDb zrBHET=!p!oC6ESh+KxXJz(J=q|3rw${y)1&Jczr&;K&@v;3`aId#>hZDW&2S%Vs&qyqp!r2tp$v(? zn_DEh4-&j1CL~i~q+KERzO^&khghwKqdNt`cPuIkhcgTMmFP*$%F3gqT8**CQe_N` z50a1Pnrp+4B&les3r`0(xxXAHA)RnS+^vCYjgLLBE&O-DqfUvy9h$cl6(#nK{aoE?2*tU&}2< z=l+HlFyHC(7gCHOo>P|{E3=4g4dd9|#B~)veC;m;u&lEg7_HYDjt&nNMgyFnj~Qfs zsU%fNXmoVqqiO)`&qH)cmfR-kdyN{V_-ri%VsQ}sv2sqMfk~ksi;tMgilbhKdE7M= zp{dw4=7acv88O42gR$PP)O~RJqQsmjlfa+x{E5iXHbs$=;94W$_p+vu{WNUO4{nX| z5l#f~9HG@Dd|!U(@lkFwOt|S}c+G(~fqly^; diff --git a/pkg/webui/ui/build/logo512.png b/pkg/webui/ui/build/logo512.png deleted file mode 100644 index 2bc3b0b03aa840a3beb5879dafeaa47c65dd1ce7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26403 zcmb@tbySpV8#nsQFvt+XP?EwBItWTBEi#0pgft={NOw0gC@~-UG=g*qBB3JP zUDDkR=V9;te&0F&pS4)RTHencSI6%PRa23>PDD=x0Kjzxc^P#8fP#NQ0r)lW*Pdtp z3HS@*qAn)|6m>E#0{{|Gkdf5%G+cX6=*ehy-iFz~at5Z`v?_OuL~udNT{`B}ZzZ>fR!~15M%|Isaeo4hYkDnOKLs54Xux3l<%4D)+XpVYbHMm`#Vy<`U6?O1LIc zEv@UOp2=C4Gj?`p%)7nv?@xR^EC3h21wf|bs0IVDFj!f|ILYi80mY|Y9iq*V8b0;j z@#q_HY@;j;3Gh-N(}94R3OyQU!hb{Q%U7 z6@VbJ?K2W|5tf#hozX?Jf+Z(Se`Pk^W-qY1e^6mKG6)=dv_}!&jK!KKeIf6$=i|ug zqOhH4IDr#=V4qs zsgcZmUk6MuSwBNNm--`4Tg(Vm!JEc|BBIF}h#`P{hsjK>PrXf&4R5;S=<=L)_jdnf z^1{`+cjCu_fcfih3&^`h!cfQnVIZbo?LF-d0fZU$p#3hz+vvVMuj^BpE3WZTw$1Mb z%jcVgSy>;V(6oxe(10+Ishq4$Py|96UHXg}X@WZ4KPbnG%ttw3*_M7#OOv)R95r4_*O#yW<3hoC?*i_o1q=`OL>1rqyGIhZURtw z>9HbfF#U@afU~CpFmw|EFdrg~-gCo9=#eStJZ^2!*EMjAuQ6OM1Njq*+NewdhYZ*3{tU(olg*lgd5Kb%T$ z$6%s((yFMYiJ8MC(^)||3&RxyusX_|TDDqz=I8azwIN$~Etn90gbq@WLR7MpD2WfMu{E`vw~{t_>zlWw8vrPUU8Hoam))UmovdczD{x zjkR#)g@J@C##19Fzk$UHLEY?1RTk>@utKYrg~3__-4hlD-0YzZ=Zhlz%&U7VtN8jt zvOWUAH{n?HB}i}Hn@9u#Lfv&CB>VK|1LBYO=Y%TzN}0WUED`RIz%oDuZA=LYIxP_b z{J`Lw)?)ZPp5x7$b{~dsa#~O(Vb_ImJB~J;{R^9QKbT_3fsgh_$&qF=Wi?a;?71c52R@!AOk_Dj_2WoIeg!HI01#Wkw;inR$!T&!Hs=gI`k-y*!!(Y4#s9u|QE;93SsEYSD_iv^(N+N-tU!}5@#IG64AQHS0e(jh*- zPdnQ%k8gf?v_5)62wEe14G%FK2*7|~C=7;>pWVNl9Io4do~_njb{{f1Uk<*60}yFq zn*Sp?t60RxGc!I_ZZgL>i?L~{sKH3hXGk#PVw1l~eup#ZwH;)>nH z^~+m%r%j2BQQw0OuJ2v^o{t>*VuZU14E;0)pu>9j2vUhoP8c5|2jj#@-K_r3ijWrs zEr#-`M1vD&mPW2WLFPw4LAS*5`s;{9Am&^|df(*AW6Tpe5P~=B_LvJ9Ao*C^hOvmn zoL#*3=(*4QZQkpdBVprxQQB9H7FWVaf<$6Ln`ed_yG|#or1K?xAj@RFg<2a89Ld57 zkXR3h@ijF1iVWmb?@zISVhP?@3&Wx{v^3&fTz~B|S z0F|SK4O3T4`r6UHMc~0^pMa-yPCT~zNYQA9b#yd6m0;||QDDH+w>SAXNkYLF2&c0E zO*7#f+o174Try=gMT5c^h(`}a{(qNZ&F42*XDvdgxIex)(x{kpR+D-oH*&>O7*;LS zG2taN+Ys|BJ=spKNRj__x{Ye$kv;M4iaI7&#v_6NcF>*5OC>%vwS+m+A$9wQFUS&j z>QHaM3w%9^u*SQcuD4*qI{Iju_m^u|?wCElb&TG_ylaqckTA{ngW6z}!ly+xg8JGM z`wFkfVLIM#{l0(bKkJ%?Hr|J`|8E;T>OKe? zP(+`~)R;9FyqX-3`#Jmc0UQRJ6C|?OF~t@-jE@+@q@KS4WdNym8zj645CDLbfCv7z ztbWfIOuPrz0p$v`!4L#I80|N@vHGRHgF|=n1JC16)hw`_~p4^dP4SW0+iQosN zG&+Fo`B%6xO`}nR)4^sGbAjalG5uH8(4#8+C<4D8Um|-i5|M!9MP6AeI2%+OKUTyWa?t)hQ|NXyqoqs6r#Ge$(?aCz2tVz7Jlj~sttq+_|xan3b;0{SWd{5p` zo?iLH-98{5B#t3SFVBzH7!ZyM0JKqrH}zc1A}Ag?9Rl8)*}E7Y5&|nb7`NoyS-i=u zy2lU4%2YD9gvf(DkQs)@U-2LZG&(#?Zbp6K4Vs3b)Zm`}pkf}~g8LvG!FB2U9I59z z=7H2mWEi+7N6Z!;lo}qM)>mW0*K&cUc7A0WU9o6Skn<0}4i=2-$@tzTUtjIP=gS}y z?h&uBf4(q4u-4BCI=PEet+iRP2@m`BF>5!f5${`SL-tHR-?)mSMB~V>A51SA$+*@R zGDz$j6H{jFcpxrP5jkCpk@vDZ1BC5nUe*r@)HUmip(>snT9b)Y6Iv!z$Kit8-o`v?=}6+^aiu8 z%T2fEf;mjs%z0tfg=>qnT61?jH0m5iYf6-O`)33=q5F1)&q7P3sJ7{PGG5uI-gs=Q zWg-Ra_2kxwEVzEi;L5UMUEGX1*6AW0v8`ZNR*`SM4|#rVn94xe`^}48`iAXLiyYp# zFzn#q$z7+4hPw(`y=ys$MnFEfMDz#dfHCduP|hQe18J0maK2HTWmeU$1(f{5H- zL0&K)(4l;KUHCi-sJI+R3nq4`&N``|VRrxZoKOi&0Rk7~-PvFMg+kCW<=PuI^KDVT zhtn^dn;;rl_IB@V-V4|WsbmfH7ot5EzKqqBq&R+Yq`d+8toK>HHuhHMqlV(#hlir!uyPO+OfswwypRCj_(KN=^rTmQY0+<|fFq)sDpBeM92=-#=%$(|sJiZh15 z6iz}lP4KY^LTN}@0s>x*kF`%u5Fw<`(P%hedr3^2DR0nh9xQ_Cwzn3!qyCN@U-c7! z>zF>Er*BK$-Bkv$@s$o}45o-8JD?ZnIH@N*+u43}58wv3nJB8!{Z!YrFXMc$P zR@i?s(bi~jrnm*H>{hcWg)QoGR8{Is z{fcaeo@mPq0%n1hk*7D6e;t>jO`FypIjK9(0U)-+0?Z?0ITojNlbSjD+qxVik<^$gpF z6~Immq@Me{Pj*}@^C=on>s_{Z9i|USAM9C*4QlP_voLy1s=q?adk zQD*lvp}uEJqh1@$-b(KF7jSbaAscM^K4`HE<^NK@p7+Amvg<2$ynQucn``|^`{BGY z??_VUWK!PL93XmF+$SCITO_-5(MTz65AJ8;eb-2vS`4C=nC3-jk-I9L^ngY()%Vz8 zZDSRTa5jF=b7zp$QL%!_*~ds3X4|0KG|{DeJZ{PG$q`J(MkI$d@nPaX( z^SRRtz`v}pviUd^*W?*8FR#5#|K!}4M&H5fKJC=jN)Q$&r;)h1;^2@vd&}2SaH56D z`khDll;>l8A(QQPtS%f2Rya^DhTe@1I@D1dk33c*D>^!|$ud)jPwJa+JRs0USLzhRtOmTcTxkAnE|4`VIw!mv{_*T04) zGI~Pa7jMoqNuR9*N%R$VJzQ(sRM!`(*qQ?>X5p3z$3*1ocRDlcJ|Ka=D-q^N^QEEd z_2ya)hb*oe-%*D)7d>I~4>ofNta?&-sz{xFNuqkTPZ_1|V&w+=D?3ygMP@>e zyJLFWjO182RWEXd{2Uk}v@tt4Sy>1vqV=o5*7B9io|75$O=l(d}wcGUwP;N#M{F z&+6AcR86griIX_mmTT<#V|E}O&QI{6>UMyOsJ~3Rc8H71+1s&A6^+QgbLz?V(I~ju z`B82euywz#>2q5^CK4Yla>i`r7hL3b=1-7hTf^S+M$YX)#}>w>AbW{-MHwCMbseB`>7v;8C7X-f(n3j8s*b9SCc14m40 zze&}bEhWvL%?CZwe&PkzM}Eyk1aOiSlfCRsvQF?~BlnZ8X@=bn)T4y=W#e9(IGq#LTTCyV+Z$%6YVAyj_&H`3hdLJnj2x8z1i z`{?kalp4iJ`w%s)Va_|i*Ad&5+@}MNy;{4xZo72Vq+Zh+5EgPQ`Krm;bL8%zTBq)Y zHJE;Cl55>Gc)dT_)%%TCgvM)Mx0DoDih99FmqET4qsc=Ar^mbA6k4&9wbq8Fna)!R z7na8?>puiZe{e~a90YCcJeJ7TN$?(tFp(Sf-ZkL7RU`! z9+QyK)WP;UEk&Wi5$GnK7>OoDL2DJ!c7Gugu#yD1ho{q9IYT;owN2Y{vxM6imBEvMR7oP!Js#51-eAU^?gy=_w}21?{E$Z82`FnR_Aq}FZr%la#63wdgD@_ z@Z%e8PjyKHCpX6k>lr2V!fsRDrdG7A#{5SF1u9iOFt(+As{YRW>)E}j6DGb21NQIu1X|1C@{Y^uRc~V7?Lqf&HxEAL zwyA8HD00>6#6G$dO|1CGCG=%IWp9rs2Nal1Bv_~0FsZ6!4f`?KkRRX2Cm#Ve<+!${}N1E~#W;acOheW0V}7OY5ECB^qkwxV`*8#$eTAP2AU} zX5qo6P;l+d*{YUOVjOi0abqKVnf zlDyzRPg)Idt}K#~5IJrqp-R5tzap7@**o{aR*TMDwMD^}iU87xGpBMj{oMS{H02rN zf}Exczmae<|0(n8uL(PhfjqI%X$Y6TR%8-bEE%OgpRFxZvO0?H5WhA1_*Vj+Q^u|L zH6`)c>AX8nkfa2TT2nC#)mAaI)?T}Z%}c@xy>D5se@TQqdR7w7y;RqubsgNrQOl4q z7 z{1IhJ>&2RWlEetZW>@$K(kFJ=$C?Z^)CT{Ah365LTl&9}7FgRBwjpz0MYxT6 zyEg6YdI#p1P)Bf;#zloqEG7K#A>EIk)L@6_kXq%8rYM|UkK`k*A=}r7Dc*U7qhl3@ zo>sE-x%T}pTqoaAiq8X(X~Gm*wsAbQB-8WBknU=NwC7$gzEInT(ANS*TSWQ=E8Jjm zTTGs_BtJlxtNX4Izt*+_-#}u+M3rfRiAyWV?-n=YLD{9FypGd6k?MP;=%;;4nK|2U zOmFJc`R)^m*3Pv>4mp$+w+%*Ee`zgtQ9%#9uH__a_wQ9d z00EF6J{@>%JUm<^?8|tw{`4a9#|M|q^0I=qu{Wbfs0UwnZNRcSZADv3TE23{)5JW-U z<(_R>lhlZ%Co(^ZjC87^+}a^<=%sF`9W%fh=CeQ_X1cu0(W@3@-B|bg9Lb6^3QR(D zxtd0md+idnF0D+nwV+iAK*WV`jNRJ{BK8Bx0&rF#fR*t^?XuUg~lM{WTdIY~};l-Pa zvLk`YBqx2LI8Ullelg(Rc1MpLs`$fF^xImEcHMf`FpH^Up$^ZN_APFH+{WQ!N#?hL zC-nn*p5?kXh5>Lon=?h&&bh{uozjAp~vgQ+S%|8YAv>@|sgkQx@o-ik~RbG>8c4 z@=VQpFUTw@^kF+9Z1BT1|MxnwuKtoj{(5}y8&$W{-vJz>HBaYjgCd#P)TjXm4&_$c>}I7jMOLZ9S?bt;_gd!EuB2jG&|@ye0gAC zJ7DdtVRyg+^rzmR(L3}|NMD|`u?lMQz>ka7T*qT!EI@~VHR2bXvx=pEcDl}gOPu~l zJA=jfNG5wjrw<0-Tfu##FZ9%SejJo?XD8<+D^nc0+`-Bh+L-WrD3|^jkSViz{a3N( z<*FxPG%jHpg<<6gUNl|)xv>%w)qW62(}Q83hXLGpM-C@wrV6Gyx(V&W!s`i+ow@!S zce?`UvfO!=`Yz^1AS6QwwZAWP)4*Dk$g_nsET`C`3$BO|0%{L27GRdorzyY6bX>@? zy+hV_;jjlIp*(cHuJd6o=V)f@xh*=le6WBoToFU|v!0_;qTFpRB_s9LK$LkB7tT^% zeO0H>UiLv|I>UYHKlT=cm4-1f-WqA>poA!9y;|_3$hqFvv zvk3ZMc*1I}oEHT)*PBAQ4$Wfv{o_})_hE9sZesZzN5}%5PI$vd%q>nXLM^`B`4t5> zmg%B)HN6xrA*{VIvS>Jf_hQ&@jmL1}=E%LHX={h5^w=z1;QaT{Nw7t0pmkSY4+4-Q zE~?fm=jE-%=b=WdSPp^7!ECp{G{1+eQb2QN)VUUm4`xnzO5ct1jbRo(`n}l=5%Nj& zqMbckX`TW`*wt_@N?SGo+Z14S`AY5Bn08G}Nd}MS=j!IG% zUKBFIgtJRQf9Lb}r|iYu&Tp&99SK?$4U5S>OD?|M-PWL9cm*(?|Rzi1Q zl;RzxVA7483*=V%K^r-1qu%99YG5g?@1opLvDtstRzUlg4ATBSlNk6SFfz#hIhAN- z8U4l-S{3>S!U+cmqS+1jxiRa4cPdWs6#Ar};UREiiJ0pCVM451Euew36#T5G0975X zamtBF3W<9FL(8W>4w<=1#p`~^JQ(6V%UWtbfXxN)N(IreB|wb};g$yI=9M3Tp}Mc& zj#nrFjp)kZ>bLtZ&`uAfDMTKAB@%io(Ar0{S-hXH?No5D0-8Qv@T}c8nB0#qGUAPo z@*QpZiML4?Qsb+8>S=@K+-xd8>V~gb;X%434DLDH2%RaJm+Uk`d=uF|728j^|7z>r z>n>eltQ%{3Bklp6F&lvgY%8c&h^K>p>UKs~btc`J^ z@$1dL%BOuj4yy-rQBZDUnaW?+n2$AYV|n70=>{t2KPL#MX(bs}adqb_d)Xv-F_XV% zvPU60cp%ytyTp_cMko7hwSKHHbTy%y-TlMA5BI~?u1or-P&CrC2R~tdZ+7;|G_z-5 zXF(^lr)xV&I|7($8PuQDyl2C6d|MVM2zkxmR2QzWCy0PB9H47N%*)0*X@6v3~?I&!73-lXz{je?#V0+?M3k*f1jGQB6}{kQHl=kYqA>bD)j9!zU(7NQu)~r>Mw_& z9=dDRU1M2pM*)`K-t%=*sHgr`xr%S{YI2+j723tEEY9msQ* zr|2Yiwj5hUu-Ve2*FI|z0WuCRY-lWcQ33OVHHLd${mHVDAWoQ zDIALp`5oW`YW#F}lpkpQ7!KQzK$P%3 z8gJAD0?NSd=-^sG`!Sktka3Y$6rSkfKI3@1cEGHzk+A! zzO^;ksn1n5PU@{u?2WJl!~K}I4~O1Uwmh0TVKudlD`O7yL}{DI7~NUg#;9Ou?E(RK z^jGuwNw(QBd|GIc5C{d}Vg1+4Mwh(c`Q!*Do5G2Tm32m(2N z@!Upx!rg@pA_LuOZ7g|bBxnWo;JKdB( z^CU8g9zu$&PrjT^)R1Je1kBWPHl^3I?bZ7znKgAi;kMgkDp?Oy*XxaE%XCaNVqXQJ z0r}yw(Yk7JQ%!%086;t!26vXQzsQUQ_IuQe_h2eP=_h_9uMpNqqVLdoE7v5+CJ2bG z7MnK+5Ztw%bq)G!rI=JLXs?{-L;(Rciq*S+Fhchsi$>{_6r8nIio?i`SvdE-m*EUA+li0Qu7F^# z`qXU7FJj2WbiVhqsfv)YrgvJ`2T-1x|K$}@M3UOt>=t(MwSGe~=m+gAe|5u4ue{%J$0S^i;yGH4uDZ~58=q|EAAPucj`bx(NA zQ|MLq2Ps9LgNCBy59-$WrzjP!P1!r!C^6M)9FtvR-{POqubf&Z9HVz%MsyYx?=RSx{ZT3BxvS>^`Cs9b z&gZU@O1NSh_1-dBdkonQJQ9>3N879e-%06_?@BjxzQl?Cg2$N3G~8Qi{Y4(QA@m6> z{qPjTJrNQ4^;nR+cw>lvLd98iHaXdKpA>9=Rtkrfqsq)jEPoByI zE$a_U5M9yLyd+QOyd8~K=_XAyqmI#WuT^*3_hGWe)hpz^vFmfP-^G|3aS)CFgQYRK z9&ms0&v-2={)EQSR~EU0O182yCveKss79ss<#m3xY|&g#9rsL_-PKy4mDyOPn6mrc zoUo(Rww0&`LI?=$d@*KGHZ}w5snl4rdjUBWB?6%1$9!P&VhrIGwrdNr^u@QZaG#)ggk+k~2$tjiBdw2)E_PYPLa*YlYyFfPsMJ#e z4YfuRP9>^Nf)$y$$7D{q1a3#**VAb)3dAk#s;4LxQLQ!dsFrwj)Cgs^_|nVVApy4p zFD6vYkNkQH_f-3ml<+sNIt7YYe-l?yEiSq}C?8*Lxgod>!Xglx7ex6grGHdIEpvE7 z!bFKoxM~NJ1%jV0Z-%jMzY{!_(0B0f&va@qPc~CwVLfwyj}zWb^H{k@6J}0=*=7NE zNHYvpC+sZvO`!cukfZ)kdzYTU@BD-B^G6=GVcRm7lQqpF!$HuBtkqHT4}ZK?0@NPC zb_l!%2P5$Qs#4O)DuY_MLdrpFRErWy?mhFu*P!{pk4hHdINu{%5a@Jazt(B#Fu`Z= z(t%Kw{TiuF@AAC`0Ts71Z_pR6E#$u$VCK#E&c|USLkMOx0Yb0BkVyz;kSA8I2s6+b zF4AT8NFHeqqB5CgHE-a}Uw#@AZ_Iu+;+>&X7lNz7Jw4|Co@q0Q{pod+uY!&|U{ThO z@bp^yQY-ZBV-Q$@P`Ll`o2H+-fz(*gSJTJXO_SAo4y}3YRFw1}wxe6EO-${-cKEEp ztvo!40-RT{qw~R>xtzl2M{9Znkms(_Teo_5*Iwm;Lhl|3?&VOy);p|rjBy#ldJ{qM z!?H)<^yxu9f0;@65->*wL!*HJr2i{UWCHJKk?ldR&T;A-1doz2Gd5` z5*H%x-H9EGFcK{`0FwT!jp~mMXlm*qwRhf$s$UD>AgnW4ZJm628$0|Tn-#TgGO@C2 ziA9Gu!-^+(oLo%e=wayaQLo|Ubp^C2NNLNj1-HPGrniY}Xu&ZH4+8!g9ZGYk zKl}hMYLFC9(8LxEqQmjX?||)>f9jDts@Jv&pbBhT4xrCaeAbhr!vreP&K_$|l4XJy zB{}}Zha(wj+3jA0yxWlNlv5}5rcx||LV+EmxJ-q?WX84<#LMUPpFo6X(_n;jkmoB( z#MTu;1%-b129>T?EkU`>*5VT^hrmUmm1}&u>soDfT_0#LHtf}E+Ne^(&y zkimp|+QAZS6oJM&EIJ*uXwVkoMSsHS7_8B&;;0z(5bnimn)mK^{CDWhC=sCB~Bm^=-7#G&^ z9IbDXi|;|2nQe7IHJziD{gc<>I)+M(8(mSME?$+46gRurbVXXcZ!jTI$i z(?AS^*e~ESt02r#0V!A*SA!T|Lb>@wrxJr_avQ(MJ%b^ke>dO$rkiayn%`hKjJSgO z-Zqf~Sds4c;xmxG$4Wid*jcV%P3U)yx5ywkz{{IF-hEBV!1WC{qU)zJlG44zvXHGgXfpu&}2qKlhyWkA-mTVGQr3-ed}9o$m4lBT8b z>X}dFLBt?drc6)wEdpp#YkJ#!b&x%&I;C9tAit5hj8Et;T$S&(+E+KWn{y{>^zZ`E zq+%uIVF9I^HRh#e0|sgA8M;){LvBl@9nJ&TtNwhymot;BC9T6*tk9&n~9lu_2 zX*y{fcSr|bhD6!~3=P;n4}2pE`Ulub1j1O=a`fZ}uJG)bvlx@>kmxv-OcfB^j{g5) zsaQZw9SDwat@j`+enJ9iT^vhFD5cQgAyU2zzP-8PkzSBZf?ksgPt1S|^MJFykou#w z7er7+5Y48Rp)B9a1F^k4z4h*{&+}{Y+6}SK?!nONVrj)SEC9uzW7@nFa0G+9+ybA( z(Q})9IRq{3<5QD=#BWHjMqtuptF)XfenZf3`&rX(*=jca7pioswCi*<#3hW!&%W9Ya-r(8*fc zug}<(IVn(AxZgkVym@_HCu)t;y&=VsghK)bt&?fqv&Sm2D3Tjb<{D%Z zTvJ#2+AI*ZQZU|^dITZ=jO|8l(FI%qWlBfxHglLu-2o+9v7Bq<*Z(qORY81Bp8El= zcgh!Eh4vZ7gTS?G(A6u0bK}!(5a^>yp3Ot?RB=V-EhF4k(O6(E>&ZteZ-^rrEelmA zx+<*Egy#g{t{+0yB?@HrgF;*T9{DytV28T7JZt!+VarLrPOB~-2npbTyOQ_xg~ulo zB`se@vO=0&RY28LwU7}a@9FF;6ny?mq|hY%LOa&~q{=H|H_Y%H_5u|I!M6nG|f^F}Ml!?i2^SL`TAKFxv=@ z%KH{TK0w)X5Flr0j* z2ZSP6bT6Uf>=(Wag3#d~Ev_d~hXN{E%feg*es1ffq}=;)krv@9EE$x6BBZ~b{tssH z$&o4y$Vut*rd2Iz2-ebk=OnzCuYSiJQ_{ScOK{^6rk* zd1C7|UYSWm3Z(|-HU&L6w0p!PX+G9qRL*jpuisHT zuRohg4YRrM$dZs<=Cc{QF0Z-=I zvmZeu;QSB>rTv5|{xcXsF8T7#^DAm*$*iFy*@@Hs{SX-~15M3yU+d3?PdugJ1Xk{r zzM<4Ym5AifU40AwKyPm`gOw>7YH1uGystAvmZw+a8=v!On&7_$=yV5Yg+2Uu<6!+0 zx5O?*r}|Ll)Bkn{pfiXZ1rM_1p?CKr^m=#GcpxC^c#1M}?x9==3mgRTUj1NE&^y`! zHEj3i@l{)8Ce!dh6t;~_S;|mjF&+c~+64d-4=2gb`#4cC90{VziAQaQ2g`PGc8`=U zJ3s^xSNo;XrtcZ#e0A=yi7P+*2y-xUpJ|_>yN?tsgnu)-Sg%gR7DN8qDhZ4fGczSB zh5QbcEC>~Li%|Z`H@}?KpZ!TNz6fjK8I3hR`%&mJ1O=1eM4TmaWk500e@}hM#%t28 zR%6^?x-?u*MtG}S&d@)`|L8AXld0Q(Gh_>`C0ti;H0m3*xO){A|3kz$mg1oW{hqiI zO3Sg1$~e}=`TR#&rNk!8NplB7kVt%VYT6=C%L63gzpw3rxsUlOiQ4QvExRQ3>0Njo ztS%bAv&Qx02hG$enX(@T&)rW9a&RaVeiq0gA}o)ps30Hc-0|-Hv~L2d%**E zB6Mm%c)LZVLWwoRhvM4`mhV3v7qB=EY*SkGyVtgUXuZE#_hbt5^1~NVt_d>Ubf9st zf#-nt+CTh(;~Mv~pQCGSMfj1M&Q<4IvA6aHPVINzl3>0M>dPK3rW=7M(&{$c(aa;( zA5{O*7m9M+CycfHV(0#O2haEH%#Fs!@^~eFZ`h`*xA5}lwW-dc-wcPS+f=5dnp=b^ zLpE`m@9wSS}K`3`;jG{pO(#RQg{YfavjQoZZl5>%lOALsi*cg|Gjd z=0^$Xvq3+hp4GIw+X*C3e+@>s8nyZn0Ui98N;3Lh5!b+Z%YU9BFB;HJCLR$vvWsS+ zT^V)$aK2Yb+?@tAjx<>GU(JABEep6L{~6T~9z1yD6Z~CFNh19X&G|T&w*6}BZB^M( z8KD2!!YK*Q$u<$4`+xdWOc+o|a6M~mrD6R|_>uuu83t^e9S$(lzW!H}z=@>=A(fA= zd0k37)VcL1QV3lT^kR5p5sN;6SI9 zmPITd4BE(NfA@bBoraG){>CpW{o%m?PN2;b4H{k3EyYWpcZm(%PI=9B{evVp-5EaY zYWNBN;L`%;AX4+H$kLmh_aj$c2w}R73DPZoM>+HUK4$}Dzt>f{V^gfvXxo1?qa2(V z(EhZYdh|!85&iZf#Xl&Q;oDuSBkTkvAg<8|_j8PlQo7}}F|zmr&@efu|7|`Lqo6 zspMZ+LNUdq^9z&qxeR`~DRU;xzk8oJFk6|(0L>M4pQbW`$7V%()jZ_eK{5}d;#7|m zm;w}88@~V?Q!ris2fQ%!j=54+*roMqE4d`9KyM*JW-lUruCYNAwo_sMghVt`wB0v` z@+K8H7!ySkaL?vi-_1Fm)k+Qs)VSjckg=W2u%AD(hn;xoD-tGEOnC!;f6J6~&wmeD z+OxBe7TWVxvK6~|gb=p<%hdl+uv9^k*{>#!TlYv~DyWDYNFI2m+I-{a;2HsPIv&Ut zto_wppZun*Z^xX5FYm@O<+o2P-J3o=KRVUgQyNgRZ9z9_w)k z2q`#8MHmRoofSxFw4b3&T$}TA{=w^knN&71Q!fjR**rQCKoiYmKIf6$tQdZbEAH4b6nJkc?q3~zI_+vRN;3b%Y zAob5Z*>hW>hI=Hn(CHC1xYB$ZqXh{}-aPiB-isoK2RH1$U>U;2Z9_|WX z{|YSE=PwEf-iX!QYb`gSK}T$$me(4-L_R39u&D_fm|v`&CWBqOn&pHnCjzE>u~Mx^ zf{4Of>GS%VEUi$C|3gn7RJ4ihbGgsQk2aZR&Gc-NFfV1~+VVLy+5Vl%;oFIwJyW`= zd=C@;RG~@nJ2#d$EUO7GWDv~#&^whFEN zws{Q%iZMKU@n!yvT|K^P4+G${ezF1`{2vxzqV&K-D=YEG2Fa{~0``7khwoeAh!}ox zcItJ{{o|KR4LuS1}@A$*1^bBgf zkUYH_{&kJo;?VG=P5cRA-TFV7U9`18&tdu(lH&8tLLRWx#+*==FJz;etB8#cTP$_n z5l+t?l_K+U@ou)~kK8vVjCF%;dA)U^LIP*|k4yhxD0&vpy#mTPVS{y~v`}=s`|+RH zl2`AoiVm0Y7Z87XJ3(f=8|>FxkPzvynrT|QNUap6C*A4GiOrG8Q$W6IHA_h>9 z0@nkHJm+Gsxiud+bgrK6gL554gA zspx)!leNxA*@L5kpF^lMAaIDX!}?r|oPp9%MutM!`23t70c89NUXSYQbB_%mGsTUw z08)YLyi}%Z4&bRTNE1zy0O+$x-w@gvI##G|@OlOhWDA_wqH<@&EhaUFRM^|Dj?f2x z0P{FcS(+Rce|!YTr2l&xFBdvK`WwowGH%M)o;LT^;jgxbBc$kK%yi=dy&2UtaMDXt zj+~Znh@x>(rc(h-go}9ogCZMzOJc;tP%fQ|1OU->di8zz3S@MmU$(!-6J;^hY6`U5 zvSx^saNWIzFA2y@HQyiA_jp1xqTgfjisXci7YkU{hvE!iu3yPHF479SJT)r{V%~vp zmVDgSnJb-78n^a(opCRf@3DM0W8poaB;f|VSDMjH!7wHe`*JyWX~eJw82e~^)q8P9 zF4p&|?Le{rhV7+Nf4H)S*U(@@m%{rOO)-51gBS|FhlaW_-yG_cDslnK`-$Xj*Ka;O zZ11GKW73@2iMM?zhfXp5Aa&@ipgAQCgR9TW=! zLlt*O#u|cLf1uuM=4`F4$2?%kj~on$&Ro9JHGI0?=bU+#bel)I!+IhKSO2!|_k2zj zkJES>a1@9WR3mbZLrmQjUepjxc~%tZ5M-?r{5nze{0BF7ygsk|4zs;S70}TJ2{Z%dqpn91k{wo@ol6>4QZ8Kg}NUAv$o*`5cS0n*)Iv z$1L4x98B`S^mnvBPOP|4LBc@bixX2(gorWqe9Zmb(uVFIVO@`t@Jwpium*rfUB=7TE09H5l>MZ(#Hb(8!H;%>0fszu{^s>qtJW#~z@YdhT-JPn{L{eyaa$ z7HvyES}I`Bva(}iOg8!dGAlScb}O#@60*p7ke!Igf%H-c8%LmI4=Z&%Yua{ zsO%LJ8S;V|wRSwh!Xf*LiI3iKD3PZSJtGLkXKkUm1_`C7XW6k}FHVvYoZ~fJ#gc#~ zD6&~k#_q_dPBQoBKnU8v6TS|E%ipM#ExE17+K+o~f-fFMgKdUf1_Ho@i#To4Dyt-8 zWU&0eDLp<)N6dXyYkKD|&Jqi``p&wf@w*Av)})^%lwqsGKA*nY{rIIUcS<-(osYZD zKHn42B@KPR>aVSQ(d9;z&b~s&{SO}#cUMaTdLN}D4nGay=$CFDHEn-|&S!?(=$1x> zs=?bl5D)dkRk>7bi1!n2PE6`^Xcq4OX5J-f<8a3a%)Iet0I*KtNH-W!RmXBJf@g2A zT4*;aJrt=U%DPB2$|+0MOUKWJmyLF8%Qa>tIAPp)Gr4RH#Y_+R=qCEKi^}JaW$vq; z>C%1ha~$Q_g@)2`dxaNzC)R(DU8T_#KTKZr?)&Qry4-|DHekHWS*J?GJ6p)q}M#aqrS^w2n0;boVsKMkjhl!+!Ev+ z-X*KfJw7+H$d>tSS@3~#Wc+X1uR9)9^9<+%O51q-X)F5e&cKzTw|93hsvT2FZE@gy z!N9--Fx1}VTj9Cz4TkYP)p}(v1F(UyFP*&AvDp2l+NIC-O1wG~8FhTa=Hu;O&Zb=6 z@1Ob%J+352XFRiS(TK;aXb&n>dA8<<+iY+cY?&fT+23nn&VClzot}X&M14c{t)##a zSQTSph4YkEnxP)-sWFZ!b~g1{ENzj3?GHL%#XfWOV7{JXMdLg9nzuI@fR2!>235Qg z3TS=(nIB~|&Y0}FVJZ(4*BCi)NcQj)hpV@idgLzBNzP@_%uWnVj3B*HLkwD*Yvy4)08qKwp;u<4cl|DwLED1F-se1>$q2569}~)>vLP zVYl2MjTXLAiXZt|BM@K^@W*q)Di5Aq0q`_I`t+Y*XWfT%%iUEs+7q1mU(8W1uX63EN)rN zjUFYu(tlk)*qCH)B1ABLnLPbP@J-23I0OfqQypVa8D6l{3!$z)r$jmg1`;(t2b0(< zL+;LeN&b^d{vbK;%7o7w)B5M&bowTD6a7R_*q2Wls*k-&$ldMmhV`ui$Zff|;pX>c zNg>Oz{DGv;mO4e+O#-s@lBj(HfO@bPKULsVhbg7za(f<@C2lVR;VX>cu1LZgbZ^@36HbF{XhHSWeqAiRcMe0+eu>U&! z>5*y#yJZ6QyBPm6O#k-Bd?Y}elzp6mzb0tG33vnl>h!LPE2i_r(#$Xriekr9= z8YLo|?v`3-e8;QMnKGp6Cs_`&k0D_oQ}`FLl@OIHKFpOx7CTJQ(038r{*&zGX*#%2 znsXmObvV~vE+W<2^a2>z&F69c+iJP-*&K23xfy9@aR3X-%nd>gX69>5Ff2JhyPZ^hHzH(36|`DO<)t60fowE}CMuS{ zGC(N2;f_`b(>FarFjRnkPL358?swAVcjkW)nkh;4@^Q3M7D!BQ)?e)KG&!Qyo``o` z7Cp686z$R0fpTavMq+G}O8(fhSR+<|K?StLs*9z4@tg(a;QsDIR}t+6g_iZ_>9#QA zf~O_QrmLlXA}Jdh0v-#5|5?1WhoC>1UJ7__l>KIrDVrqw>Wx5v0#Hg^%QN!YUrQ@g zVSHme$Ns9=kEvzie4MU&`imWw`J3qf^W>ny4yYIh+0kK1~t-fkM0Z!eKht8TZ}7KYgx0pKwxwRV$nb zu$V*?G_NIZDE%_ts;h``a7bQ>#IAQ~w)c7!qxQ1t;1iko=qpm50-Dqn;M63-%z3p< z0FwsT6+D*D7gtH@pycYN(^c1^pj6$Pbj_pmBemY98!P2sMd6?Y30m+4&a?o(73>kd z(7|TnYJsOJxxb4cz7x1#I?w!vc_D{M=;qkJSoU))?W^vYD|97}p;MDy9S&UOv0YCe zYkxkz8*LKLBng7n8|x5Y!YynfiE^@L>kQ+qY&d#lk?i$j1;=fVdxZEJAmn~(3Ef#l z8kr|1@MQrFye_JAV0V+vAJKk!?lfN!s-;Fd6ox%@Yz$?SBrr1rUn~IfI@3`6D~rg? zJb(RHxMRhKfp$Lmj`{Z+#Uq0LUqex`_atk)9R71%&^>C^b|mQZwghX<`MciYmdU7< z$v4p{vV@|Fi{>XB^Hl`urGytzJskI@Ur0gi4b2q=j_b1HMWhjwe#1^o#x2yyFW3-L zikDcTf*%HBKD@2l+)|syKvv=7&{!vZ(H~m;UyI#^F?ZXoxrEMIAqFoN3U~-+WX22d zGJPv*QXS>AoSp^e!)C7Xe|50lpOxSL;J3E*^ZZb;=<=6bE-(zV38KGM7i(0-3*UbF zUW3hWSs$2E^l|%%N!YC+o74R7mkSzQzmvO&f11iX@11Q5J6&q5CB#F~ zgPrgIBLKKMV-P5RWkZZswkEqBWqL&c+8@!m15&DInVo!IxQmhyDP<&9>}ZUDRuB2A zle!Lin0~j$$=~m9v__VJ+<)LZML7(s?V4&tY>jkLxTe%ancX8!D6akp6j6_-20807 zT&N&R-BcwZv4g)tNY@}q#5MppTL|urVaa6n3og56?F5J5$2VFV!Q zw?!o!glWy`XBEz4k-cB*`D7{B{U+J4!K}Fau++Y{HnB@stbFP0(aoBV!y8^Q^zWt- ztJzgL&~whxNvjt?UMQ*mO^msXuhmOBDXCJW46=*j~r?byZWN#R5!?Q3yk!?9=Q|r85d?? zxJLMA9hXss^4=3yhelENt-J6~v<4~Ts;NbRkt``rD3!J06fsLc)@)AzGxPP~v9z3! zs65Jd+tKHGN5ILIdeMMeyl7}mYOXb9SmZF&Smg8CG%dsUqdVm{6G{*^MQ+^eUhEEb zHIw(@kki+ccKy~bo+*@%y+F@wHj3_}IHCn(3bP6;<>m_$W*C|HK1%{1)Cz9rgeY&e zrWym<)Qr-znTz4H$s~PcA~AWtJltv&h;fk*!S|ZrH&3MA1l1l56@Ko`5XdU?Im&Fh zwIqMR_}vI#61`Jl(vFIkyIuOJR~V?%bF+~10fhMebqDSzDGdhwmx`!0kJ>x3b~7yY z!nC%{yiZ8}JGZf9K*TY!DjiBkXr)Y9tOP))P%Sum;6~&3PPQO&!GpOtJPZWmT062W z;c7y6{xnQsaSF+BS`LAlXe2!_10oe_}(fixD`#Z~%l5{9#=+ zFn!CIw~4P^pFY+3vSMoZlO?^9G|>>Ch=w-dR&;}}e;W$N&40nrL5xlu>a;|wiZnT_ zb)ufGv?<44AK-OoFgs8rS+?6IN+pCh`~iB99=~vE7P(Ud^s;)+wAwK;HcMJTMj!0K z_i9vn`{(5*9j!2%CTq6MhrGQv)20oNur6sOp2BT3pN3NIJV-LJ{9{(D-k+NiY&$|sWQwaTq+@^?ndQC|NaGezX`c% zD4hAUvV=}vUKau50W3eMzlU-uzG;^3E?KKKmC-^EO0Oa|l#Tc>tLSyqUa#+-+;dj? zf}CHD{~Wc|JTqP&V5)T7nJA8nfV$V}qQikum6UWTpD7su$#kv@ak!8|ql}VahZBwo z1FPKe;cixEIxw>xYWqgZDoEZrh8ZS;<|r7?Qi|$neN6H8;rXP~S|=^Wsaonn^^5FB!EDE& zz#*pCOc{6wy!IuzPYf+BeS;oH!sAetgyDf z8wSWp$o9049m$^8FQ11$gC2DaX7PL*Hh(aLAarB%a7q37ug1H{pK7e(TxZJoeUg6( zq9sv_g(l7ehLPz@a6yge3M%sF-jukLHACe#Z*de~?ySUITDiKQDU~#|n$DN(%#TF> z^lGgSxPEzkW&U_eMdg)VUkBetvr|WBd4IiMWH$yQ`VQbLE=H zd+Y^_WdAN1cf3u&W#X!b&pcSre&ARp@P-`00u$Mh1-d2{Tm7|-YBpqEV z*yIc~CBk4%w>4#acc*zY&6#+kZUgluAYIghin6ZR^zexv1K{Y(z|mt>KlE7uVO|L- zn^w}vEsW(1y^!rWn*jEKf@$Z+TZ4niEN`Uaw(vM-=w_Emgw|iDdsl% zWu|^f0$D|dh+&kDBVG>9FZ&sR%kZPAi6@Od*S@F^*d{s}bL**4OcYg3GLm8#kt=zD zjz%AW9Afj@fKN*^yi9pQUu$fmO<&8ieQkH?t!TE>D{7553QxkJ-b#82k_QBEbS9qi ztZ7e8KWitB-$^Z&S5YdAIC1VpS-+s#ERrOIS|eTINf?CMe+`K7JZg*I09h3QNvp^8 za`ayYIe~^^D@;>5RHM3>Pcm(GvUt1!|`D)Zc&pNyE&a|N3-QY0#P}8W{AS}{pj6yzM1>q zdm|XI(A3YS9bzAjQZqTzr=6d$F<|a{YcVfP2t2(-Edz0m-(ObbxRCmr`o1|%ME!Jr zdE|zx>cWy{%Z!PCdT;ZXqX4;D!r_K}>Se5*EDfhLxJttAdns#HK6fH>Ssoxipu*8j zWdtO5a}3t&f)VLx!eX=;*=39doh6;Q7gRBAAg@KRZ$z8VG1HPc_gnu~z5P=rJmd~C zc-%w`$91(?HHGN;p~Y65aj}Bww(RI$(A4eGZ|?;FLorWhlT6~^aI*TItO%pZJaJ0? zuhd-j7gl+FI~Ig&YFN*1@4b9fRl+f!NLyD&F>6W9{EszQpMMzb{r9p+z&2#S1f&K< zWX4-tFU`1)0-vM6^ozIJiXGy#w`aU11>6mKd)&0s=`gnEIFwh4kERyBIhD!rI7~TL z_sxz7jmQJ(7PIDgD|Q!^t+T!GVdU)}+mFlMYKvK0#pi0ykVt;=B?T0#`kJAWvV$+{ zFtg6ZwT940W(cYNeh^TLJXKp;1;j_z*na5?bv;*-pzyO0Wvd4g_IP3A`ZD`AFe+q|gO)T&!!U3poUfLNj z#4qi1nCQAC1`RY`2!0lSFhJ?qTgPLhbUoE9)y>%UfFTC{=3*DKedWOviKWQ~h|{pX zkq|9sJ=zq$T@y? zkVHTI@$JAeveL@$mRzA}`$yiwo&YE`PvW1U=}(tmplq6ik{#KeT^pc+F8v2JP~#4q zeBo@PAfpE6oXPRBP%tkP`YEk`C&;Egsz4g7A3X3`@WZu_M(O0EY$_}+A6TF)TTv{j zS{ORi&vvczWXO$9BM7V_0_zw2l@a94Xk zR+2wZZyWc*4cGO?>*vlt2RSyHzx+=GWG~exxW$~c7Z?TGY6N#0Qt0|K*e&|5)>&GQqY`4FF>|+Y)1^4peQLAA>mwwF?`%DjxAWmI0#v$`(Zq{(V0D}W z2(!!%c3(I`N;J0CCk_AgP~Y4zn@f-a-g&-ZdnbMu?XA;&L}=9`6ImYbdgFwsG``Vq zIl{n?vq^4~JQ#8w=q<+PiAQm%Bwx~%9hUFOLzPYO8FU!4mS@fMn6Q?akKb*;C1tQd;oPnEM<~&iNmWd?i#QyvYB7(vhfAe$x}Esa7vHe( z)`M2wsrdipF8L$kDx>(_AT7yfpz%oJNc{5Q)*kq$-x6P*9v2I+FgFf{S|-xX7MwK* z^9-Z8jVOW&@=9@h(hB1EAsmT|d17?EL=PSw2KpUcc2#8=MStEr`Iqu4IbYH>BnI}vt(&EG?aR}nT8Wd>1Bh_`Z zxu8$775f&dSyTX5bkg1J57#Ej{d;Hp!wC?LBXNtf@h& z3T)7xu~YSYnwJsT#^Y&;Wkiv!UGu{J-I2GRJjaGX)oGSZ_Kd%xA@R!}^=Q4q785;g z4xT$$kgj{dE%Uf3t(!JQ2HKg%>JYV@$yv(+gvceA`4O))?REbENGX0x)_dbB%pi66<;TVwIA7Z*s=b#EJrWKsZ2qj*_eXk|VYw1Z z3nx0(>#{&At431O@ykt0$N_9#;fnQOC$XQZ^hQTXw+AFtkd-eo*!V63SC918sY%*@ zk-Uu61ILgT$y00e@9aM1H{=?71};Wnjng?iv8Xez{NnCkkK@UJZMWuAi(>QHwz0ff%9C?i5^>k+G?fvHXk!O_`(+N$ew?7keD8UB|G(EYZx37_f<%JrQ`lKHZz5cBbFJ%_n3pzFS10H1Vd&}FOG3}ljfE@PafE` zJNMY}3`WWu-rkf*MQ-Kz{P`+ri~DE8$q^oX=G$toFek zN?zu9wnHF(A{9(fX1{UtXS0BG_fT>Y7}iTTCuKck=3-S+av@A?#Jb3AJ3JtE4712# z-NO-HJmZgLpAvssCh`3@QF(+Rc)@BZM+;1o)f7IvK3_WPA!xRi14*tgmm{OMXqZ5L(DFT3#ktOVJO%XT=?TFexCNb0M}f zpe>f?dw!fO+2=;8C0q;v#eVU=a9w-I;&U{cGPq_TC*|vyA(w{qN^GU;Z zVc&*r?Z*_uDIXvhn)V@2nr40%?11Z6w+}T%R~$ZchmE2fM_IX0mC#Or*RM~FAf+}2 z3C%8rKJw)qkdQ%oIOjwGcA~N_VzsJ%kRDfVcH_DykS-nikd3XwXi&Ul6|%R}nCoU@l2%*%CaYe+jRjeI5J z;7r*#M(a09DrV9FnfMDKN^};F_DMAi)g$rNZvM)R-ijQ1)%E@lj1d341Z+pO%tw5S z#vj})?L9a7LjrklFr~HQ0{dfgjGH#Z8F&=$2EkY;zj<#uO7Fu-jg^s6rIoQ*X)KCN kC)Jgl7IyDt1M(=32|CI*DI7{v>LBo^D2GCp%9sWI59Y;SdH?_b diff --git a/pkg/webui/ui/build/manifest.json b/pkg/webui/ui/build/manifest.json deleted file mode 100644 index 080d6c77a..000000000 --- a/pkg/webui/ui/build/manifest.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "short_name": "React App", - "name": "Create React App Sample", - "icons": [ - { - "src": "favicon.ico", - "sizes": "64x64 32x32 24x24 16x16", - "type": "image/x-icon" - }, - { - "src": "logo192.png", - "type": "image/png", - "sizes": "192x192" - }, - { - "src": "logo512.png", - "type": "image/png", - "sizes": "512x512" - } - ], - "start_url": ".", - "display": "standalone", - "theme_color": "#000000", - "background_color": "#ffffff" -} diff --git a/pkg/webui/ui/build/robots.txt b/pkg/webui/ui/build/robots.txt deleted file mode 100644 index e9e57dc4d..000000000 --- a/pkg/webui/ui/build/robots.txt +++ /dev/null @@ -1,3 +0,0 @@ -# https://www.robotstxt.org/robotstxt.html -User-agent: * -Disallow: diff --git a/pkg/webui/ui/build/static/css/main.css b/pkg/webui/ui/build/static/css/main.css deleted file mode 100644 index 70ec57003..000000000 --- a/pkg/webui/ui/build/static/css/main.css +++ /dev/null @@ -1,87 +0,0 @@ -body { - margin: 0; - background: linear-gradient(180deg, #59A588 0%, #404846 100%); - background-attachment: fixed; -} - -body, input { - font-family: 'Nunito Variable', 'Segoe UI', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', -apple-system, BlinkMacSystemFont, - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; -} - -* { - box-sizing: border-box; -} - -html, -body, -#root { - width: 100%; - height: 100%; - margin: 0; - padding: 0; -} -/* nunito-cyrillic-ext-wght-normal */ -@font-face { - font-family: 'Nunito Variable'; - font-style: normal; - font-display: swap; - font-display: var(--fontsource-display, swap); - font-weight: 200 1000; - src: url(../../static/media/nunito-cyrillic-ext-wght-normal.woff2?h=b2611fa3f916d23df9b0735ba668944500ff23b73f9da4fbb10818c875d482a0) format('woff2-variations'); - unicode-range: U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F; -} - -/* nunito-cyrillic-wght-normal */ -@font-face { - font-family: 'Nunito Variable'; - font-style: normal; - font-display: swap; - font-display: var(--fontsource-display, swap); - font-weight: 200 1000; - src: url(../../static/media/nunito-cyrillic-wght-normal.woff2?h=7ca4b4bb8be6840990cc92b2dee938f142df99c93ce85063b391a09369b63b17) format('woff2-variations'); - unicode-range: U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116; -} - -/* nunito-vietnamese-wght-normal */ -@font-face { - font-family: 'Nunito Variable'; - font-style: normal; - font-display: swap; - font-display: var(--fontsource-display, swap); - font-weight: 200 1000; - src: url(../../static/media/nunito-vietnamese-wght-normal.woff2?h=0ef9726dbc36b5871efa4b0cfdc43fd1bfed5dd48aeb70dc8210e8cb9bc9247b) format('woff2-variations'); - unicode-range: U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB; -} - -/* nunito-latin-ext-wght-normal */ -@font-face { - font-family: 'Nunito Variable'; - font-style: normal; - font-display: swap; - font-display: var(--fontsource-display, swap); - font-weight: 200 1000; - src: url(../../static/media/nunito-latin-ext-wght-normal.woff2?h=89def7428656f40331c1430ee1dc1846ed1e30d7001707b548f9f816d27264a5) format('woff2-variations'); - unicode-range: U+0100-02AF,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF; -} - -/* nunito-latin-wght-normal */ -@font-face { - font-family: 'Nunito Variable'; - font-style: normal; - font-display: swap; - font-display: var(--fontsource-display, swap); - font-weight: 200 1000; - src: url(../../static/media/nunito-latin-wght-normal.woff2?h=96217f1d27fb909f92b4a6b35a0d3d6775f2f0b4d136d27aee88547d3ed87357) format('woff2-variations'); - unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; -} - -/*# sourceMappingURL=main.css.map?h=b04a21858ecfe46c93b33bc95788b6861c50ee3a7852bdb7721e314a3ee95b80*/ \ No newline at end of file diff --git a/pkg/webui/ui/build/static/js/787.chunk.js b/pkg/webui/ui/build/static/js/787.chunk.js deleted file mode 100644 index 15b544b3a..000000000 --- a/pkg/webui/ui/build/static/js/787.chunk.js +++ /dev/null @@ -1,239 +0,0 @@ -"use strict"; -(self["webpackChunkkluctl_webui"] = self["webpackChunkkluctl_webui"] || []).push([[787],{ - -/***/ 787: -/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - -__webpack_require__.r(__webpack_exports__); -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ getCLS: function() { return /* binding */ h; }, -/* harmony export */ getFCP: function() { return /* binding */ d; }, -/* harmony export */ getFID: function() { return /* binding */ L; }, -/* harmony export */ getLCP: function() { return /* binding */ F; }, -/* harmony export */ getTTFB: function() { return /* binding */ P; } -/* harmony export */ }); -var e, - t, - n, - i, - r = function r(e, t) { - return { - name: e, - value: void 0 === t ? -1 : t, - delta: 0, - entries: [], - id: "v2-".concat(Date.now(), "-").concat(Math.floor(8999999999999 * Math.random()) + 1e12) - }; - }, - a = function a(e, t) { - try { - if (PerformanceObserver.supportedEntryTypes.includes(e)) { - if ("first-input" === e && !("PerformanceEventTiming" in self)) return; - var n = new PerformanceObserver(function (e) { - return e.getEntries().map(t); - }); - return n.observe({ - type: e, - buffered: !0 - }), n; - } - } catch (e) {} - }, - o = function o(e, t) { - var n = function n(i) { - "pagehide" !== i.type && "hidden" !== document.visibilityState || (e(i), t && (removeEventListener("visibilitychange", n, !0), removeEventListener("pagehide", n, !0))); - }; - addEventListener("visibilitychange", n, !0), addEventListener("pagehide", n, !0); - }, - u = function u(e) { - addEventListener("pageshow", function (t) { - t.persisted && e(t); - }, !0); - }, - c = function c(e, t, n) { - var i; - return function (r) { - t.value >= 0 && (r || n) && (t.delta = t.value - (i || 0), (t.delta || void 0 === i) && (i = t.value, e(t))); - }; - }, - f = -1, - s = function s() { - return "hidden" === document.visibilityState ? 0 : 1 / 0; - }, - m = function m() { - o(function (e) { - var t = e.timeStamp; - f = t; - }, !0); - }, - v = function v() { - return f < 0 && (f = s(), m(), u(function () { - setTimeout(function () { - f = s(), m(); - }, 0); - })), { - get firstHiddenTime() { - return f; - } - }; - }, - d = function d(e, t) { - var n, - i = v(), - o = r("FCP"), - f = function f(e) { - "first-contentful-paint" === e.name && (m && m.disconnect(), e.startTime < i.firstHiddenTime && (o.value = e.startTime, o.entries.push(e), n(!0))); - }, - s = window.performance && performance.getEntriesByName && performance.getEntriesByName("first-contentful-paint")[0], - m = s ? null : a("paint", f); - (s || m) && (n = c(e, o, t), s && f(s), u(function (i) { - o = r("FCP"), n = c(e, o, t), requestAnimationFrame(function () { - requestAnimationFrame(function () { - o.value = performance.now() - i.timeStamp, n(!0); - }); - }); - })); - }, - p = !1, - l = -1, - h = function h(e, t) { - p || (d(function (e) { - l = e.value; - }), p = !0); - var n, - i = function i(t) { - l > -1 && e(t); - }, - f = r("CLS", 0), - s = 0, - m = [], - v = function v(e) { - if (!e.hadRecentInput) { - var t = m[0], - i = m[m.length - 1]; - s && e.startTime - i.startTime < 1e3 && e.startTime - t.startTime < 5e3 ? (s += e.value, m.push(e)) : (s = e.value, m = [e]), s > f.value && (f.value = s, f.entries = m, n()); - } - }, - h = a("layout-shift", v); - h && (n = c(i, f, t), o(function () { - h.takeRecords().map(v), n(!0); - }), u(function () { - s = 0, l = -1, f = r("CLS", 0), n = c(i, f, t); - })); - }, - T = { - passive: !0, - capture: !0 - }, - y = new Date(), - g = function g(i, r) { - e || (e = r, t = i, n = new Date(), w(removeEventListener), E()); - }, - E = function E() { - if (t >= 0 && t < n - y) { - var r = { - entryType: "first-input", - name: e.type, - target: e.target, - cancelable: e.cancelable, - startTime: e.timeStamp, - processingStart: e.timeStamp + t - }; - i.forEach(function (e) { - e(r); - }), i = []; - } - }, - S = function S(e) { - if (e.cancelable) { - var t = (e.timeStamp > 1e12 ? new Date() : performance.now()) - e.timeStamp; - "pointerdown" == e.type ? function (e, t) { - var n = function n() { - g(e, t), r(); - }, - i = function i() { - r(); - }, - r = function r() { - removeEventListener("pointerup", n, T), removeEventListener("pointercancel", i, T); - }; - addEventListener("pointerup", n, T), addEventListener("pointercancel", i, T); - }(t, e) : g(t, e); - } - }, - w = function w(e) { - ["mousedown", "keydown", "touchstart", "pointerdown"].forEach(function (t) { - return e(t, S, T); - }); - }, - L = function L(n, f) { - var s, - m = v(), - d = r("FID"), - p = function p(e) { - e.startTime < m.firstHiddenTime && (d.value = e.processingStart - e.startTime, d.entries.push(e), s(!0)); - }, - l = a("first-input", p); - s = c(n, d, f), l && o(function () { - l.takeRecords().map(p), l.disconnect(); - }, !0), l && u(function () { - var a; - d = r("FID"), s = c(n, d, f), i = [], t = -1, e = null, w(addEventListener), a = p, i.push(a), E(); - }); - }, - b = {}, - F = function F(e, t) { - var n, - i = v(), - f = r("LCP"), - s = function s(e) { - var t = e.startTime; - t < i.firstHiddenTime && (f.value = t, f.entries.push(e), n()); - }, - m = a("largest-contentful-paint", s); - if (m) { - n = c(e, f, t); - var d = function d() { - b[f.id] || (m.takeRecords().map(s), m.disconnect(), b[f.id] = !0, n(!0)); - }; - ["keydown", "click"].forEach(function (e) { - addEventListener(e, d, { - once: !0, - capture: !0 - }); - }), o(d, !0), u(function (i) { - f = r("LCP"), n = c(e, f, t), requestAnimationFrame(function () { - requestAnimationFrame(function () { - f.value = performance.now() - i.timeStamp, b[f.id] = !0, n(!0); - }); - }); - }); - } - }, - P = function P(e) { - var t, - n = r("TTFB"); - t = function t() { - try { - var t = performance.getEntriesByType("navigation")[0] || function () { - var e = performance.timing, - t = { - entryType: "navigation", - startTime: 0 - }; - for (var n in e) "navigationStart" !== n && "toJSON" !== n && (t[n] = Math.max(e[n] - e.navigationStart, 0)); - return t; - }(); - if (n.value = n.delta = t.responseStart, n.value < 0 || n.value > performance.now()) return; - n.entries = [t], e(n); - } catch (e) {} - }, "complete" === document.readyState ? setTimeout(t, 0) : addEventListener("load", function () { - return setTimeout(t, 0); - }); - }; - - -/***/ }) - -}]); -//# sourceMappingURL=787.chunk.js.map?h=47820fdbf26705ec6b47ae95042786f8669103d43ced5b19492db0ef8a1a30eb \ No newline at end of file diff --git a/pkg/webui/ui/build/static/js/main.js b/pkg/webui/ui/build/static/js/main.js deleted file mode 100644 index a7625bb71..000000000 --- a/pkg/webui/ui/build/static/js/main.js +++ /dev/null @@ -1,61552 +0,0 @@ -/******/ (function() { // webpackBootstrap -/******/ var __webpack_modules__ = ({ - -/***/ 867: -/***/ (function(module, __unused_webpack_exports, __webpack_require__) { - -"use strict"; - - -var formatter = __webpack_require__(707); -var fault = create(Error); -module.exports = fault; -fault.eval = create(EvalError); -fault.range = create(RangeError); -fault.reference = create(ReferenceError); -fault.syntax = create(SyntaxError); -fault.type = create(TypeError); -fault.uri = create(URIError); -fault.create = create; - -// Create a new `EConstructor`, with the formatted `format` as a first argument. -function create(EConstructor) { - FormattedError.displayName = EConstructor.displayName || EConstructor.name; - return FormattedError; - function FormattedError(format) { - if (format) { - format = formatter.apply(null, arguments); - } - return new EConstructor(format); - } -} - -/***/ }), - -/***/ 707: -/***/ (function(module) { - -// -// format - printf-like string formatting for JavaScript -// github.com/samsonjs/format -// @_sjs -// -// Copyright 2010 - 2013 Sami Samhuri -// -// MIT License -// http://sjs.mit-license.org -// - -; -(function () { - //// Export the API - var namespace; - - // CommonJS / Node module - if (true) { - namespace = module.exports = format; - } - - // Browsers and other environments - else {} - namespace.format = format; - namespace.vsprintf = vsprintf; - if (typeof console !== 'undefined' && typeof console.log === 'function') { - namespace.printf = printf; - } - function printf( /* ... */ - ) { - console.log(format.apply(null, arguments)); - } - function vsprintf(fmt, replacements) { - return format.apply(null, [fmt].concat(replacements)); - } - function format(fmt) { - var argIndex = 1 // skip initial format argument - , - args = [].slice.call(arguments), - i = 0, - n = fmt.length, - result = '', - c, - escaped = false, - arg, - tmp, - leadingZero = false, - precision, - nextArg = function nextArg() { - return args[argIndex++]; - }, - slurpNumber = function slurpNumber() { - var digits = ''; - while (/\d/.test(fmt[i])) { - digits += fmt[i++]; - c = fmt[i]; - } - return digits.length > 0 ? parseInt(digits) : null; - }; - for (; i < n; ++i) { - c = fmt[i]; - if (escaped) { - escaped = false; - if (c == '.') { - leadingZero = false; - c = fmt[++i]; - } else if (c == '0' && fmt[i + 1] == '.') { - leadingZero = true; - i += 2; - c = fmt[i]; - } else { - leadingZero = true; - } - precision = slurpNumber(); - switch (c) { - case 'b': - // number in binary - result += parseInt(nextArg(), 10).toString(2); - break; - case 'c': - // character - arg = nextArg(); - if (typeof arg === 'string' || arg instanceof String) result += arg;else result += String.fromCharCode(parseInt(arg, 10)); - break; - case 'd': - // number in decimal - result += parseInt(nextArg(), 10); - break; - case 'f': - // floating point number - tmp = String(parseFloat(nextArg()).toFixed(precision || 6)); - result += leadingZero ? tmp : tmp.replace(/^0/, ''); - break; - case 'j': - // JSON - result += JSON.stringify(nextArg()); - break; - case 'o': - // number in octal - result += '0' + parseInt(nextArg(), 10).toString(8); - break; - case 's': - // string - result += nextArg(); - break; - case 'x': - // lowercase hexadecimal - result += '0x' + parseInt(nextArg(), 10).toString(16); - break; - case 'X': - // uppercase hexadecimal - result += '0x' + parseInt(nextArg(), 10).toString(16).toUpperCase(); - break; - default: - result += c; - break; - } - } else if (c === '%') { - escaped = true; - } else { - result += c; - } - } - return result; - } -})(); - -/***/ }), - -/***/ 478: -/***/ (function(module, __unused_webpack_exports, __webpack_require__) { - -var _slicedToArray = (__webpack_require__(424)["default"]); -var _toConsumableArray = (__webpack_require__(861)["default"]); -var _inherits = (__webpack_require__(655)["default"]); -var _createSuper = (__webpack_require__(389)["default"]); -var _classCallCheck = (__webpack_require__(690)["default"]); -var _createClass = (__webpack_require__(728)["default"]); -function deepFreeze(obj) { - if (obj instanceof Map) { - obj.clear = obj.delete = obj.set = function () { - throw new Error('map is read-only'); - }; - } else if (obj instanceof Set) { - obj.add = obj.clear = obj.delete = function () { - throw new Error('set is read-only'); - }; - } - - // Freeze self - Object.freeze(obj); - Object.getOwnPropertyNames(obj).forEach(function (name) { - var prop = obj[name]; - - // Freeze prop if it is an object - if (typeof prop == 'object' && !Object.isFrozen(prop)) { - deepFreeze(prop); - } - }); - return obj; -} -var deepFreezeEs6 = deepFreeze; -var _default = deepFreeze; -deepFreezeEs6.default = _default; - -/** @implements CallbackResponse */ -var Response = /*#__PURE__*/function () { - "use strict"; - - /** - * @param {CompiledMode} mode - */ - function Response(mode) { - _classCallCheck(this, Response); - // eslint-disable-next-line no-undefined - if (mode.data === undefined) mode.data = {}; - this.data = mode.data; - this.isMatchIgnored = false; - } - _createClass(Response, [{ - key: "ignoreMatch", - value: function ignoreMatch() { - this.isMatchIgnored = true; - } - }]); - return Response; -}(); -/** - * @param {string} value - * @returns {string} - */ -function escapeHTML(value) { - return value.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, '''); -} - -/** - * performs a shallow merge of multiple objects into one - * - * @template T - * @param {T} original - * @param {Record[]} objects - * @returns {T} a single new object - */ -function inherit(original) { - /** @type Record */ - var result = Object.create(null); - for (var key in original) { - result[key] = original[key]; - } - for (var _len = arguments.length, objects = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { - objects[_key - 1] = arguments[_key]; - } - objects.forEach(function (obj) { - for (var _key2 in obj) { - result[_key2] = obj[_key2]; - } - }); - return (/** @type {T} */result - ); -} - -/** - * @typedef {object} Renderer - * @property {(text: string) => void} addText - * @property {(node: Node) => void} openNode - * @property {(node: Node) => void} closeNode - * @property {() => string} value - */ - -/** @typedef {{kind?: string, sublanguage?: boolean}} Node */ -/** @typedef {{walk: (r: Renderer) => void}} Tree */ -/** */ - -var SPAN_CLOSE = ''; - -/** - * Determines if a node needs to be wrapped in - * - * @param {Node} node */ -var emitsWrappingTags = function emitsWrappingTags(node) { - return !!node.kind; -}; - -/** @type {Renderer} */ -var HTMLRenderer = /*#__PURE__*/function () { - "use strict"; - - /** - * Creates a new HTMLRenderer - * - * @param {Tree} parseTree - the parse tree (must support `walk` API) - * @param {{classPrefix: string}} options - */ - function HTMLRenderer(parseTree, options) { - _classCallCheck(this, HTMLRenderer); - this.buffer = ""; - this.classPrefix = options.classPrefix; - parseTree.walk(this); - } - - /** - * Adds texts to the output stream - * - * @param {string} text */ - _createClass(HTMLRenderer, [{ - key: "addText", - value: function addText(text) { - this.buffer += escapeHTML(text); - } - - /** - * Adds a node open to the output stream (if needed) - * - * @param {Node} node */ - }, { - key: "openNode", - value: function openNode(node) { - if (!emitsWrappingTags(node)) return; - var className = node.kind; - if (!node.sublanguage) { - className = "".concat(this.classPrefix).concat(className); - } - this.span(className); - } - - /** - * Adds a node close to the output stream (if needed) - * - * @param {Node} node */ - }, { - key: "closeNode", - value: function closeNode(node) { - if (!emitsWrappingTags(node)) return; - this.buffer += SPAN_CLOSE; - } - - /** - * returns the accumulated buffer - */ - }, { - key: "value", - value: function value() { - return this.buffer; - } - - // helpers - - /** - * Builds a span element - * - * @param {string} className */ - }, { - key: "span", - value: function span(className) { - this.buffer += ""); - } - }]); - return HTMLRenderer; -}(); -/** @typedef {{kind?: string, sublanguage?: boolean, children: Node[]} | string} Node */ -/** @typedef {{kind?: string, sublanguage?: boolean, children: Node[]} } DataNode */ -/** */ -var TokenTree = /*#__PURE__*/function () { - "use strict"; - - function TokenTree() { - _classCallCheck(this, TokenTree); - /** @type DataNode */ - this.rootNode = { - children: [] - }; - this.stack = [this.rootNode]; - } - _createClass(TokenTree, [{ - key: "top", - get: function get() { - return this.stack[this.stack.length - 1]; - } - }, { - key: "root", - get: function get() { - return this.rootNode; - } - - /** @param {Node} node */ - }, { - key: "add", - value: function add(node) { - this.top.children.push(node); - } - - /** @param {string} kind */ - }, { - key: "openNode", - value: function openNode(kind) { - /** @type Node */ - var node = { - kind: kind, - children: [] - }; - this.add(node); - this.stack.push(node); - } - }, { - key: "closeNode", - value: function closeNode() { - if (this.stack.length > 1) { - return this.stack.pop(); - } - // eslint-disable-next-line no-undefined - return undefined; - } - }, { - key: "closeAllNodes", - value: function closeAllNodes() { - while (this.closeNode()); - } - }, { - key: "toJSON", - value: function toJSON() { - return JSON.stringify(this.rootNode, null, 4); - } - - /** - * @typedef { import("./html_renderer").Renderer } Renderer - * @param {Renderer} builder - */ - }, { - key: "walk", - value: function walk(builder) { - // this does not - return this.constructor._walk(builder, this.rootNode); - // this works - // return TokenTree._walk(builder, this.rootNode); - } - - /** - * @param {Renderer} builder - * @param {Node} node - */ - }], [{ - key: "_walk", - value: function _walk(builder, node) { - var _this = this; - if (typeof node === "string") { - builder.addText(node); - } else if (node.children) { - builder.openNode(node); - node.children.forEach(function (child) { - return _this._walk(builder, child); - }); - builder.closeNode(node); - } - return builder; - } - - /** - * @param {Node} node - */ - }, { - key: "_collapse", - value: function _collapse(node) { - if (typeof node === "string") return; - if (!node.children) return; - if (node.children.every(function (el) { - return typeof el === "string"; - })) { - // node.text = node.children.join(""); - // delete node.children; - node.children = [node.children.join("")]; - } else { - node.children.forEach(function (child) { - TokenTree._collapse(child); - }); - } - } - }]); - return TokenTree; -}(); -/** - Currently this is all private API, but this is the minimal API necessary - that an Emitter must implement to fully support the parser. - - Minimal interface: - - - addKeyword(text, kind) - - addText(text) - - addSublanguage(emitter, subLanguageName) - - finalize() - - openNode(kind) - - closeNode() - - closeAllNodes() - - toHTML() - -*/ -/** - * @implements {Emitter} - */ -var TokenTreeEmitter = /*#__PURE__*/function (_TokenTree) { - "use strict"; - - _inherits(TokenTreeEmitter, _TokenTree); - var _super = _createSuper(TokenTreeEmitter); - /** - * @param {*} options - */ - function TokenTreeEmitter(options) { - var _this2; - _classCallCheck(this, TokenTreeEmitter); - _this2 = _super.call(this); - _this2.options = options; - return _this2; - } - - /** - * @param {string} text - * @param {string} kind - */ - _createClass(TokenTreeEmitter, [{ - key: "addKeyword", - value: function addKeyword(text, kind) { - if (text === "") { - return; - } - this.openNode(kind); - this.addText(text); - this.closeNode(); - } - - /** - * @param {string} text - */ - }, { - key: "addText", - value: function addText(text) { - if (text === "") { - return; - } - this.add(text); - } - - /** - * @param {Emitter & {root: DataNode}} emitter - * @param {string} name - */ - }, { - key: "addSublanguage", - value: function addSublanguage(emitter, name) { - /** @type DataNode */ - var node = emitter.root; - node.kind = name; - node.sublanguage = true; - this.add(node); - } - }, { - key: "toHTML", - value: function toHTML() { - var renderer = new HTMLRenderer(this, this.options); - return renderer.value(); - } - }, { - key: "finalize", - value: function finalize() { - return true; - } - }]); - return TokenTreeEmitter; -}(TokenTree); -/** - * @param {string} value - * @returns {RegExp} - * */ -function escape(value) { - return new RegExp(value.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'), 'm'); -} - -/** - * @param {RegExp | string } re - * @returns {string} - */ -function source(re) { - if (!re) return null; - if (typeof re === "string") return re; - return re.source; -} - -/** - * @param {...(RegExp | string) } args - * @returns {string} - */ -function concat() { - for (var _len2 = arguments.length, args = new Array(_len2), _key3 = 0; _key3 < _len2; _key3++) { - args[_key3] = arguments[_key3]; - } - var joined = args.map(function (x) { - return source(x); - }).join(""); - return joined; -} - -/** - * Any of the passed expresssions may match - * - * Creates a huge this | this | that | that match - * @param {(RegExp | string)[] } args - * @returns {string} - */ -function either() { - for (var _len3 = arguments.length, args = new Array(_len3), _key4 = 0; _key4 < _len3; _key4++) { - args[_key4] = arguments[_key4]; - } - var joined = '(' + args.map(function (x) { - return source(x); - }).join("|") + ")"; - return joined; -} - -/** - * @param {RegExp} re - * @returns {number} - */ -function countMatchGroups(re) { - return new RegExp(re.toString() + '|').exec('').length - 1; -} - -/** - * Does lexeme start with a regular expression match at the beginning - * @param {RegExp} re - * @param {string} lexeme - */ -function startsWith(re, lexeme) { - var match = re && re.exec(lexeme); - return match && match.index === 0; -} - -// BACKREF_RE matches an open parenthesis or backreference. To avoid -// an incorrect parse, it additionally matches the following: -// - [...] elements, where the meaning of parentheses and escapes change -// - other escape sequences, so we do not misparse escape sequences as -// interesting elements -// - non-matching or lookahead parentheses, which do not capture. These -// follow the '(' with a '?'. -var BACKREF_RE = /\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./; - -// join logically computes regexps.join(separator), but fixes the -// backreferences so they continue to match. -// it also places each individual regular expression into it's own -// match group, keeping track of the sequencing of those match groups -// is currently an exercise for the caller. :-) -/** - * @param {(string | RegExp)[]} regexps - * @param {string} separator - * @returns {string} - */ -function join(regexps) { - var separator = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "|"; - var numCaptures = 0; - return regexps.map(function (regex) { - numCaptures += 1; - var offset = numCaptures; - var re = source(regex); - var out = ''; - while (re.length > 0) { - var match = BACKREF_RE.exec(re); - if (!match) { - out += re; - break; - } - out += re.substring(0, match.index); - re = re.substring(match.index + match[0].length); - if (match[0][0] === '\\' && match[1]) { - // Adjust the backreference. - out += '\\' + String(Number(match[1]) + offset); - } else { - out += match[0]; - if (match[0] === '(') { - numCaptures++; - } - } - } - return out; - }).map(function (re) { - return "(".concat(re, ")"); - }).join(separator); -} - -// Common regexps -var MATCH_NOTHING_RE = /\b\B/; -var IDENT_RE = '[a-zA-Z]\\w*'; -var UNDERSCORE_IDENT_RE = '[a-zA-Z_]\\w*'; -var NUMBER_RE = '\\b\\d+(\\.\\d+)?'; -var C_NUMBER_RE = '(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)'; // 0x..., 0..., decimal, float -var BINARY_NUMBER_RE = '\\b(0b[01]+)'; // 0b... -var RE_STARTERS_RE = '!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~'; - -/** -* @param { Partial & {binary?: string | RegExp} } opts -*/ -var SHEBANG = function SHEBANG() { - var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - var beginShebang = /^#![ ]*\//; - if (opts.binary) { - opts.begin = concat(beginShebang, /.*\b/, opts.binary, /\b.*/); - } - return inherit({ - className: 'meta', - begin: beginShebang, - end: /$/, - relevance: 0, - /** @type {ModeCallback} */ - "on:begin": function onBegin(m, resp) { - if (m.index !== 0) resp.ignoreMatch(); - } - }, opts); -}; - -// Common modes -var BACKSLASH_ESCAPE = { - begin: '\\\\[\\s\\S]', - relevance: 0 -}; -var APOS_STRING_MODE = { - className: 'string', - begin: '\'', - end: '\'', - illegal: '\\n', - contains: [BACKSLASH_ESCAPE] -}; -var QUOTE_STRING_MODE = { - className: 'string', - begin: '"', - end: '"', - illegal: '\\n', - contains: [BACKSLASH_ESCAPE] -}; -var PHRASAL_WORDS_MODE = { - begin: /\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/ -}; -/** - * Creates a comment mode - * - * @param {string | RegExp} begin - * @param {string | RegExp} end - * @param {Mode | {}} [modeOptions] - * @returns {Partial} - */ -var COMMENT = function COMMENT(begin, end) { - var modeOptions = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; - var mode = inherit({ - className: 'comment', - begin: begin, - end: end, - contains: [] - }, modeOptions); - mode.contains.push(PHRASAL_WORDS_MODE); - mode.contains.push({ - className: 'doctag', - begin: '(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):', - relevance: 0 - }); - return mode; -}; -var C_LINE_COMMENT_MODE = COMMENT('//', '$'); -var C_BLOCK_COMMENT_MODE = COMMENT('/\\*', '\\*/'); -var HASH_COMMENT_MODE = COMMENT('#', '$'); -var NUMBER_MODE = { - className: 'number', - begin: NUMBER_RE, - relevance: 0 -}; -var C_NUMBER_MODE = { - className: 'number', - begin: C_NUMBER_RE, - relevance: 0 -}; -var BINARY_NUMBER_MODE = { - className: 'number', - begin: BINARY_NUMBER_RE, - relevance: 0 -}; -var CSS_NUMBER_MODE = { - className: 'number', - begin: NUMBER_RE + '(' + '%|em|ex|ch|rem' + '|vw|vh|vmin|vmax' + '|cm|mm|in|pt|pc|px' + '|deg|grad|rad|turn' + '|s|ms' + '|Hz|kHz' + '|dpi|dpcm|dppx' + ')?', - relevance: 0 -}; -var REGEXP_MODE = { - // this outer rule makes sure we actually have a WHOLE regex and not simply - // an expression such as: - // - // 3 / something - // - // (which will then blow up when regex's `illegal` sees the newline) - begin: /(?=\/[^/\n]*\/)/, - contains: [{ - className: 'regexp', - begin: /\//, - end: /\/[gimuy]*/, - illegal: /\n/, - contains: [BACKSLASH_ESCAPE, { - begin: /\[/, - end: /\]/, - relevance: 0, - contains: [BACKSLASH_ESCAPE] - }] - }] -}; -var TITLE_MODE = { - className: 'title', - begin: IDENT_RE, - relevance: 0 -}; -var UNDERSCORE_TITLE_MODE = { - className: 'title', - begin: UNDERSCORE_IDENT_RE, - relevance: 0 -}; -var METHOD_GUARD = { - // excludes method names from keyword processing - begin: '\\.\\s*' + UNDERSCORE_IDENT_RE, - relevance: 0 -}; - -/** - * Adds end same as begin mechanics to a mode - * - * Your mode must include at least a single () match group as that first match - * group is what is used for comparison - * @param {Partial} mode - */ -var END_SAME_AS_BEGIN = function END_SAME_AS_BEGIN(mode) { - return Object.assign(mode, { - /** @type {ModeCallback} */ - 'on:begin': function onBegin(m, resp) { - resp.data._beginMatch = m[1]; - }, - /** @type {ModeCallback} */ - 'on:end': function onEnd(m, resp) { - if (resp.data._beginMatch !== m[1]) resp.ignoreMatch(); - } - }); -}; -var MODES = /*#__PURE__*/Object.freeze({ - __proto__: null, - MATCH_NOTHING_RE: MATCH_NOTHING_RE, - IDENT_RE: IDENT_RE, - UNDERSCORE_IDENT_RE: UNDERSCORE_IDENT_RE, - NUMBER_RE: NUMBER_RE, - C_NUMBER_RE: C_NUMBER_RE, - BINARY_NUMBER_RE: BINARY_NUMBER_RE, - RE_STARTERS_RE: RE_STARTERS_RE, - SHEBANG: SHEBANG, - BACKSLASH_ESCAPE: BACKSLASH_ESCAPE, - APOS_STRING_MODE: APOS_STRING_MODE, - QUOTE_STRING_MODE: QUOTE_STRING_MODE, - PHRASAL_WORDS_MODE: PHRASAL_WORDS_MODE, - COMMENT: COMMENT, - C_LINE_COMMENT_MODE: C_LINE_COMMENT_MODE, - C_BLOCK_COMMENT_MODE: C_BLOCK_COMMENT_MODE, - HASH_COMMENT_MODE: HASH_COMMENT_MODE, - NUMBER_MODE: NUMBER_MODE, - C_NUMBER_MODE: C_NUMBER_MODE, - BINARY_NUMBER_MODE: BINARY_NUMBER_MODE, - CSS_NUMBER_MODE: CSS_NUMBER_MODE, - REGEXP_MODE: REGEXP_MODE, - TITLE_MODE: TITLE_MODE, - UNDERSCORE_TITLE_MODE: UNDERSCORE_TITLE_MODE, - METHOD_GUARD: METHOD_GUARD, - END_SAME_AS_BEGIN: END_SAME_AS_BEGIN -}); - -// Grammar extensions / plugins -// See: https://github.com/highlightjs/highlight.js/issues/2833 - -// Grammar extensions allow "syntactic sugar" to be added to the grammar modes -// without requiring any underlying changes to the compiler internals. - -// `compileMatch` being the perfect small example of now allowing a grammar -// author to write `match` when they desire to match a single expression rather -// than being forced to use `begin`. The extension then just moves `match` into -// `begin` when it runs. Ie, no features have been added, but we've just made -// the experience of writing (and reading grammars) a little bit nicer. - -// ------ - -// TODO: We need negative look-behind support to do this properly -/** - * Skip a match if it has a preceding dot - * - * This is used for `beginKeywords` to prevent matching expressions such as - * `bob.keyword.do()`. The mode compiler automatically wires this up as a - * special _internal_ 'on:begin' callback for modes with `beginKeywords` - * @param {RegExpMatchArray} match - * @param {CallbackResponse} response - */ -function skipIfhasPrecedingDot(match, response) { - var before = match.input[match.index - 1]; - if (before === ".") { - response.ignoreMatch(); - } -} - -/** - * `beginKeywords` syntactic sugar - * @type {CompilerExt} - */ -function beginKeywords(mode, parent) { - if (!parent) return; - if (!mode.beginKeywords) return; - - // for languages with keywords that include non-word characters checking for - // a word boundary is not sufficient, so instead we check for a word boundary - // or whitespace - this does no harm in any case since our keyword engine - // doesn't allow spaces in keywords anyways and we still check for the boundary - // first - mode.begin = '\\b(' + mode.beginKeywords.split(' ').join('|') + ')(?!\\.)(?=\\b|\\s)'; - mode.__beforeBegin = skipIfhasPrecedingDot; - mode.keywords = mode.keywords || mode.beginKeywords; - delete mode.beginKeywords; - - // prevents double relevance, the keywords themselves provide - // relevance, the mode doesn't need to double it - // eslint-disable-next-line no-undefined - if (mode.relevance === undefined) mode.relevance = 0; -} - -/** - * Allow `illegal` to contain an array of illegal values - * @type {CompilerExt} - */ -function compileIllegal(mode, _parent) { - if (!Array.isArray(mode.illegal)) return; - mode.illegal = either.apply(void 0, _toConsumableArray(mode.illegal)); -} - -/** - * `match` to match a single expression for readability - * @type {CompilerExt} - */ -function compileMatch(mode, _parent) { - if (!mode.match) return; - if (mode.begin || mode.end) throw new Error("begin & end are not supported with match"); - mode.begin = mode.match; - delete mode.match; -} - -/** - * provides the default 1 relevance to all modes - * @type {CompilerExt} - */ -function compileRelevance(mode, _parent) { - // eslint-disable-next-line no-undefined - if (mode.relevance === undefined) mode.relevance = 1; -} - -// keywords that should have no default relevance value -var COMMON_KEYWORDS = ['of', 'and', 'for', 'in', 'not', 'or', 'if', 'then', 'parent', -// common variable name -'list', -// common variable name -'value' // common variable name -]; - -var DEFAULT_KEYWORD_CLASSNAME = "keyword"; - -/** - * Given raw keywords from a language definition, compile them. - * - * @param {string | Record | Array} rawKeywords - * @param {boolean} caseInsensitive - */ -function compileKeywords(rawKeywords, caseInsensitive) { - var className = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : DEFAULT_KEYWORD_CLASSNAME; - /** @type KeywordDict */ - var compiledKeywords = {}; - - // input can be a string of keywords, an array of keywords, or a object with - // named keys representing className (which can then point to a string or array) - if (typeof rawKeywords === 'string') { - compileList(className, rawKeywords.split(" ")); - } else if (Array.isArray(rawKeywords)) { - compileList(className, rawKeywords); - } else { - Object.keys(rawKeywords).forEach(function (className) { - // collapse all our objects back into the parent object - Object.assign(compiledKeywords, compileKeywords(rawKeywords[className], caseInsensitive, className)); - }); - } - return compiledKeywords; - - // --- - - /** - * Compiles an individual list of keywords - * - * Ex: "for if when while|5" - * - * @param {string} className - * @param {Array} keywordList - */ - function compileList(className, keywordList) { - if (caseInsensitive) { - keywordList = keywordList.map(function (x) { - return x.toLowerCase(); - }); - } - keywordList.forEach(function (keyword) { - var pair = keyword.split('|'); - compiledKeywords[pair[0]] = [className, scoreForKeyword(pair[0], pair[1])]; - }); - } -} - -/** - * Returns the proper score for a given keyword - * - * Also takes into account comment keywords, which will be scored 0 UNLESS - * another score has been manually assigned. - * @param {string} keyword - * @param {string} [providedScore] - */ -function scoreForKeyword(keyword, providedScore) { - // manual scores always win over common keywords - // so you can force a score of 1 if you really insist - if (providedScore) { - return Number(providedScore); - } - return commonKeyword(keyword) ? 0 : 1; -} - -/** - * Determines if a given keyword is common or not - * - * @param {string} keyword */ -function commonKeyword(keyword) { - return COMMON_KEYWORDS.includes(keyword.toLowerCase()); -} - -// compilation - -/** - * Compiles a language definition result - * - * Given the raw result of a language definition (Language), compiles this so - * that it is ready for highlighting code. - * @param {Language} language - * @param {{plugins: HLJSPlugin[]}} opts - * @returns {CompiledLanguage} - */ -function compileLanguage(language, _ref) { - var plugins = _ref.plugins; - /** - * Builds a regex with the case sensativility of the current language - * - * @param {RegExp | string} value - * @param {boolean} [global] - */ - function langRe(value, global) { - return new RegExp(source(value), 'm' + (language.case_insensitive ? 'i' : '') + (global ? 'g' : '')); - } - - /** - Stores multiple regular expressions and allows you to quickly search for - them all in a string simultaneously - returning the first match. It does - this by creating a huge (a|b|c) regex - each individual item wrapped with () - and joined by `|` - using match groups to track position. When a match is - found checking which position in the array has content allows us to figure - out which of the original regexes / match groups triggered the match. - The match object itself (the result of `Regex.exec`) is returned but also - enhanced by merging in any meta-data that was registered with the regex. - This is how we keep track of which mode matched, and what type of rule - (`illegal`, `begin`, end, etc). - */ - var MultiRegex = /*#__PURE__*/function () { - "use strict"; - - function MultiRegex() { - _classCallCheck(this, MultiRegex); - this.matchIndexes = {}; - // @ts-ignore - this.regexes = []; - this.matchAt = 1; - this.position = 0; - } - - // @ts-ignore - _createClass(MultiRegex, [{ - key: "addRule", - value: function addRule(re, opts) { - opts.position = this.position++; - // @ts-ignore - this.matchIndexes[this.matchAt] = opts; - this.regexes.push([opts, re]); - this.matchAt += countMatchGroups(re) + 1; - } - }, { - key: "compile", - value: function compile() { - if (this.regexes.length === 0) { - // avoids the need to check length every time exec is called - // @ts-ignore - this.exec = function () { - return null; - }; - } - var terminators = this.regexes.map(function (el) { - return el[1]; - }); - this.matcherRe = langRe(join(terminators), true); - this.lastIndex = 0; - } - - /** @param {string} s */ - }, { - key: "exec", - value: function exec(s) { - this.matcherRe.lastIndex = this.lastIndex; - var match = this.matcherRe.exec(s); - if (!match) { - return null; - } - - // eslint-disable-next-line no-undefined - var i = match.findIndex(function (el, i) { - return i > 0 && el !== undefined; - }); - // @ts-ignore - var matchData = this.matchIndexes[i]; - // trim off any earlier non-relevant match groups (ie, the other regex - // match groups that make up the multi-matcher) - match.splice(0, i); - return Object.assign(match, matchData); - } - }]); - return MultiRegex; - }(); - /* - Created to solve the key deficiently with MultiRegex - there is no way to - test for multiple matches at a single location. Why would we need to do - that? In the future a more dynamic engine will allow certain matches to be - ignored. An example: if we matched say the 3rd regex in a large group but - decided to ignore it - we'd need to started testing again at the 4th - regex... but MultiRegex itself gives us no real way to do that. - So what this class creates MultiRegexs on the fly for whatever search - position they are needed. - NOTE: These additional MultiRegex objects are created dynamically. For most - grammars most of the time we will never actually need anything more than the - first MultiRegex - so this shouldn't have too much overhead. - Say this is our search group, and we match regex3, but wish to ignore it. - regex1 | regex2 | regex3 | regex4 | regex5 ' ie, startAt = 0 - What we need is a new MultiRegex that only includes the remaining - possibilities: - regex4 | regex5 ' ie, startAt = 3 - This class wraps all that complexity up in a simple API... `startAt` decides - where in the array of expressions to start doing the matching. It - auto-increments, so if a match is found at position 2, then startAt will be - set to 3. If the end is reached startAt will return to 0. - MOST of the time the parser will be setting startAt manually to 0. - */ - var ResumableMultiRegex = /*#__PURE__*/function () { - "use strict"; - - function ResumableMultiRegex() { - _classCallCheck(this, ResumableMultiRegex); - // @ts-ignore - this.rules = []; - // @ts-ignore - this.multiRegexes = []; - this.count = 0; - this.lastIndex = 0; - this.regexIndex = 0; - } - - // @ts-ignore - _createClass(ResumableMultiRegex, [{ - key: "getMatcher", - value: function getMatcher(index) { - if (this.multiRegexes[index]) return this.multiRegexes[index]; - var matcher = new MultiRegex(); - this.rules.slice(index).forEach(function (_ref2) { - var _ref3 = _slicedToArray(_ref2, 2), - re = _ref3[0], - opts = _ref3[1]; - return matcher.addRule(re, opts); - }); - matcher.compile(); - this.multiRegexes[index] = matcher; - return matcher; - } - }, { - key: "resumingScanAtSamePosition", - value: function resumingScanAtSamePosition() { - return this.regexIndex !== 0; - } - }, { - key: "considerAll", - value: function considerAll() { - this.regexIndex = 0; - } - - // @ts-ignore - }, { - key: "addRule", - value: function addRule(re, opts) { - this.rules.push([re, opts]); - if (opts.type === "begin") this.count++; - } - - /** @param {string} s */ - }, { - key: "exec", - value: function exec(s) { - var m = this.getMatcher(this.regexIndex); - m.lastIndex = this.lastIndex; - var result = m.exec(s); - - // The following is because we have no easy way to say "resume scanning at the - // existing position but also skip the current rule ONLY". What happens is - // all prior rules are also skipped which can result in matching the wrong - // thing. Example of matching "booger": - - // our matcher is [string, "booger", number] - // - // ....booger.... - - // if "booger" is ignored then we'd really need a regex to scan from the - // SAME position for only: [string, number] but ignoring "booger" (if it - // was the first match), a simple resume would scan ahead who knows how - // far looking only for "number", ignoring potential string matches (or - // future "booger" matches that might be valid.) - - // So what we do: We execute two matchers, one resuming at the same - // position, but the second full matcher starting at the position after: - - // /--- resume first regex match here (for [number]) - // |/---- full match here for [string, "booger", number] - // vv - // ....booger.... - - // Which ever results in a match first is then used. So this 3-4 step - // process essentially allows us to say "match at this position, excluding - // a prior rule that was ignored". - // - // 1. Match "booger" first, ignore. Also proves that [string] does non match. - // 2. Resume matching for [number] - // 3. Match at index + 1 for [string, "booger", number] - // 4. If #2 and #3 result in matches, which came first? - if (this.resumingScanAtSamePosition()) { - if (result && result.index === this.lastIndex) ;else { - // use the second matcher result - var m2 = this.getMatcher(0); - m2.lastIndex = this.lastIndex + 1; - result = m2.exec(s); - } - } - if (result) { - this.regexIndex += result.position + 1; - if (this.regexIndex === this.count) { - // wrap-around to considering all matches again - this.considerAll(); - } - } - return result; - } - }]); - return ResumableMultiRegex; - }(); - /** - * Given a mode, builds a huge ResumableMultiRegex that can be used to walk - * the content and find matches. - * - * @param {CompiledMode} mode - * @returns {ResumableMultiRegex} - */ - function buildModeRegex(mode) { - var mm = new ResumableMultiRegex(); - mode.contains.forEach(function (term) { - return mm.addRule(term.begin, { - rule: term, - type: "begin" - }); - }); - if (mode.terminatorEnd) { - mm.addRule(mode.terminatorEnd, { - type: "end" - }); - } - if (mode.illegal) { - mm.addRule(mode.illegal, { - type: "illegal" - }); - } - return mm; - } - - /** skip vs abort vs ignore - * - * @skip - The mode is still entered and exited normally (and contains rules apply), - * but all content is held and added to the parent buffer rather than being - * output when the mode ends. Mostly used with `sublanguage` to build up - * a single large buffer than can be parsed by sublanguage. - * - * - The mode begin ands ends normally. - * - Content matched is added to the parent mode buffer. - * - The parser cursor is moved forward normally. - * - * @abort - A hack placeholder until we have ignore. Aborts the mode (as if it - * never matched) but DOES NOT continue to match subsequent `contains` - * modes. Abort is bad/suboptimal because it can result in modes - * farther down not getting applied because an earlier rule eats the - * content but then aborts. - * - * - The mode does not begin. - * - Content matched by `begin` is added to the mode buffer. - * - The parser cursor is moved forward accordingly. - * - * @ignore - Ignores the mode (as if it never matched) and continues to match any - * subsequent `contains` modes. Ignore isn't technically possible with - * the current parser implementation. - * - * - The mode does not begin. - * - Content matched by `begin` is ignored. - * - The parser cursor is not moved forward. - */ - - /** - * Compiles an individual mode - * - * This can raise an error if the mode contains certain detectable known logic - * issues. - * @param {Mode} mode - * @param {CompiledMode | null} [parent] - * @returns {CompiledMode | never} - */ - function compileMode(mode, parent) { - var _ref4; - var cmode = /** @type CompiledMode */mode; - if (mode.isCompiled) return cmode; - [ - // do this early so compiler extensions generally don't have to worry about - // the distinction between match/begin - compileMatch].forEach(function (ext) { - return ext(mode, parent); - }); - language.compilerExtensions.forEach(function (ext) { - return ext(mode, parent); - }); - - // __beforeBegin is considered private API, internal use only - mode.__beforeBegin = null; - [beginKeywords, - // do this later so compiler extensions that come earlier have access to the - // raw array if they wanted to perhaps manipulate it, etc. - compileIllegal, - // default to 1 relevance if not specified - compileRelevance].forEach(function (ext) { - return ext(mode, parent); - }); - mode.isCompiled = true; - var keywordPattern = null; - if (typeof mode.keywords === "object") { - keywordPattern = mode.keywords.$pattern; - delete mode.keywords.$pattern; - } - if (mode.keywords) { - mode.keywords = compileKeywords(mode.keywords, language.case_insensitive); - } - - // both are not allowed - if (mode.lexemes && keywordPattern) { - throw new Error("ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) "); - } - - // `mode.lexemes` was the old standard before we added and now recommend - // using `keywords.$pattern` to pass the keyword pattern - keywordPattern = keywordPattern || mode.lexemes || /\w+/; - cmode.keywordPatternRe = langRe(keywordPattern, true); - if (parent) { - if (!mode.begin) mode.begin = /\B|\b/; - cmode.beginRe = langRe(mode.begin); - if (mode.endSameAsBegin) mode.end = mode.begin; - if (!mode.end && !mode.endsWithParent) mode.end = /\B|\b/; - if (mode.end) cmode.endRe = langRe(mode.end); - cmode.terminatorEnd = source(mode.end) || ''; - if (mode.endsWithParent && parent.terminatorEnd) { - cmode.terminatorEnd += (mode.end ? '|' : '') + parent.terminatorEnd; - } - } - if (mode.illegal) cmode.illegalRe = langRe( /** @type {RegExp | string} */mode.illegal); - if (!mode.contains) mode.contains = []; - mode.contains = (_ref4 = []).concat.apply(_ref4, _toConsumableArray(mode.contains.map(function (c) { - return expandOrCloneMode(c === 'self' ? mode : c); - }))); - mode.contains.forEach(function (c) { - compileMode( /** @type Mode */c, cmode); - }); - if (mode.starts) { - compileMode(mode.starts, parent); - } - cmode.matcher = buildModeRegex(cmode); - return cmode; - } - if (!language.compilerExtensions) language.compilerExtensions = []; - - // self is not valid at the top-level - if (language.contains && language.contains.includes('self')) { - throw new Error("ERR: contains `self` is not supported at the top-level of a language. See documentation."); - } - - // we need a null object, which inherit will guarantee - language.classNameAliases = inherit(language.classNameAliases || {}); - return compileMode( /** @type Mode */language); -} - -/** - * Determines if a mode has a dependency on it's parent or not - * - * If a mode does have a parent dependency then often we need to clone it if - * it's used in multiple places so that each copy points to the correct parent, - * where-as modes without a parent can often safely be re-used at the bottom of - * a mode chain. - * - * @param {Mode | null} mode - * @returns {boolean} - is there a dependency on the parent? - * */ -function dependencyOnParent(mode) { - if (!mode) return false; - return mode.endsWithParent || dependencyOnParent(mode.starts); -} - -/** - * Expands a mode or clones it if necessary - * - * This is necessary for modes with parental dependenceis (see notes on - * `dependencyOnParent`) and for nodes that have `variants` - which must then be - * exploded into their own individual modes at compile time. - * - * @param {Mode} mode - * @returns {Mode | Mode[]} - * */ -function expandOrCloneMode(mode) { - if (mode.variants && !mode.cachedVariants) { - mode.cachedVariants = mode.variants.map(function (variant) { - return inherit(mode, { - variants: null - }, variant); - }); - } - - // EXPAND - // if we have variants then essentially "replace" the mode with the variants - // this happens in compileMode, where this function is called from - if (mode.cachedVariants) { - return mode.cachedVariants; - } - - // CLONE - // if we have dependencies on parents then we need a unique - // instance of ourselves, so we can be reused with many - // different parents without issue - if (dependencyOnParent(mode)) { - return inherit(mode, { - starts: mode.starts ? inherit(mode.starts) : null - }); - } - if (Object.isFrozen(mode)) { - return inherit(mode); - } - - // no special dependency issues, just return ourselves - return mode; -} -var version = "10.7.3"; - -// @ts-nocheck - -function hasValueOrEmptyAttribute(value) { - return Boolean(value || value === ""); -} -function BuildVuePlugin(hljs) { - var Component = { - props: ["language", "code", "autodetect"], - data: function data() { - return { - detectedLanguage: "", - unknownLanguage: false - }; - }, - computed: { - className: function className() { - if (this.unknownLanguage) return ""; - return "hljs " + this.detectedLanguage; - }, - highlighted: function highlighted() { - // no idea what language to use, return raw code - if (!this.autoDetect && !hljs.getLanguage(this.language)) { - console.warn("The language \"".concat(this.language, "\" you specified could not be found.")); - this.unknownLanguage = true; - return escapeHTML(this.code); - } - var result = {}; - if (this.autoDetect) { - result = hljs.highlightAuto(this.code); - this.detectedLanguage = result.language; - } else { - result = hljs.highlight(this.language, this.code, this.ignoreIllegals); - this.detectedLanguage = this.language; - } - return result.value; - }, - autoDetect: function autoDetect() { - return !this.language || hasValueOrEmptyAttribute(this.autodetect); - }, - ignoreIllegals: function ignoreIllegals() { - return true; - } - }, - // this avoids needing to use a whole Vue compilation pipeline just - // to build Highlight.js - render: function render(createElement) { - return createElement("pre", {}, [createElement("code", { - class: this.className, - domProps: { - innerHTML: this.highlighted - } - })]); - } // template: `
    ` - }; - var VuePlugin = { - install: function install(Vue) { - Vue.component('highlightjs', Component); - } - }; - return { - Component: Component, - VuePlugin: VuePlugin - }; -} - -/* plugin itself */ - -/** @type {HLJSPlugin} */ -var mergeHTMLPlugin = { - "after:highlightElement": function afterHighlightElement(_ref5) { - var el = _ref5.el, - result = _ref5.result, - text = _ref5.text; - var originalStream = nodeStream(el); - if (!originalStream.length) return; - var resultNode = document.createElement('div'); - resultNode.innerHTML = result.value; - result.value = mergeStreams(originalStream, nodeStream(resultNode), text); - } -}; - -/* Stream merging support functions */ - -/** - * @typedef Event - * @property {'start'|'stop'} event - * @property {number} offset - * @property {Node} node - */ - -/** - * @param {Node} node - */ -function tag(node) { - return node.nodeName.toLowerCase(); -} - -/** - * @param {Node} node - */ -function nodeStream(node) { - /** @type Event[] */ - var result = []; - (function _nodeStream(node, offset) { - for (var child = node.firstChild; child; child = child.nextSibling) { - if (child.nodeType === 3) { - offset += child.nodeValue.length; - } else if (child.nodeType === 1) { - result.push({ - event: 'start', - offset: offset, - node: child - }); - offset = _nodeStream(child, offset); - // Prevent void elements from having an end tag that would actually - // double them in the output. There are more void elements in HTML - // but we list only those realistically expected in code display. - if (!tag(child).match(/br|hr|img|input/)) { - result.push({ - event: 'stop', - offset: offset, - node: child - }); - } - } - } - return offset; - })(node, 0); - return result; -} - -/** - * @param {any} original - the original stream - * @param {any} highlighted - stream of the highlighted source - * @param {string} value - the original source itself - */ -function mergeStreams(original, highlighted, value) { - var processed = 0; - var result = ''; - var nodeStack = []; - function selectStream() { - if (!original.length || !highlighted.length) { - return original.length ? original : highlighted; - } - if (original[0].offset !== highlighted[0].offset) { - return original[0].offset < highlighted[0].offset ? original : highlighted; - } - - /* - To avoid starting the stream just before it should stop the order is - ensured that original always starts first and closes last: - if (event1 == 'start' && event2 == 'start') - return original; - if (event1 == 'start' && event2 == 'stop') - return highlighted; - if (event1 == 'stop' && event2 == 'start') - return original; - if (event1 == 'stop' && event2 == 'stop') - return highlighted; - ... which is collapsed to: - */ - return highlighted[0].event === 'start' ? original : highlighted; - } - - /** - * @param {Node} node - */ - function open(node) { - /** @param {Attr} attr */ - function attributeString(attr) { - return ' ' + attr.nodeName + '="' + escapeHTML(attr.value) + '"'; - } - // @ts-ignore - result += '<' + tag(node) + [].map.call(node.attributes, attributeString).join('') + '>'; - } - - /** - * @param {Node} node - */ - function close(node) { - result += ''; - } - - /** - * @param {Event} event - */ - function render(event) { - (event.event === 'start' ? open : close)(event.node); - } - while (original.length || highlighted.length) { - var stream = selectStream(); - result += escapeHTML(value.substring(processed, stream[0].offset)); - processed = stream[0].offset; - if (stream === original) { - /* - On any opening or closing tag of the original markup we first close - the entire highlighted node stack, then render the original tag along - with all the following original tags at the same offset and then - reopen all the tags on the highlighted stack. - */ - nodeStack.reverse().forEach(close); - do { - render(stream.splice(0, 1)[0]); - stream = selectStream(); - } while (stream === original && stream.length && stream[0].offset === processed); - nodeStack.reverse().forEach(open); - } else { - if (stream[0].event === 'start') { - nodeStack.push(stream[0].node); - } else { - nodeStack.pop(); - } - render(stream.splice(0, 1)[0]); - } - } - return result + escapeHTML(value.substr(processed)); -} - -/* - -For the reasoning behind this please see: -https://github.com/highlightjs/highlight.js/issues/2880#issuecomment-747275419 - -*/ - -/** - * @type {Record} - */ -var seenDeprecations = {}; - -/** - * @param {string} message - */ -var error = function error(message) { - console.error(message); -}; - -/** - * @param {string} message - * @param {any} args - */ -var warn = function warn(message) { - var _console; - for (var _len4 = arguments.length, args = new Array(_len4 > 1 ? _len4 - 1 : 0), _key5 = 1; _key5 < _len4; _key5++) { - args[_key5 - 1] = arguments[_key5]; - } - (_console = console).log.apply(_console, ["WARN: ".concat(message)].concat(args)); -}; - -/** - * @param {string} version - * @param {string} message - */ -var deprecated = function deprecated(version, message) { - if (seenDeprecations["".concat(version, "/").concat(message)]) return; - console.log("Deprecated as of ".concat(version, ". ").concat(message)); - seenDeprecations["".concat(version, "/").concat(message)] = true; -}; - -/* -Syntax highlighting with language autodetection. -https://highlightjs.org/ -*/ - -var escape$1 = escapeHTML; -var inherit$1 = inherit; -var NO_MATCH = Symbol("nomatch"); - -/** - * @param {any} hljs - object that is extended (legacy) - * @returns {HLJSApi} - */ -var HLJS = function HLJS(hljs) { - // Global internal variables used within the highlight.js library. - /** @type {Record} */ - var languages = Object.create(null); - /** @type {Record} */ - var aliases = Object.create(null); - /** @type {HLJSPlugin[]} */ - var plugins = []; - - // safe/production mode - swallows more errors, tries to keep running - // even if a single syntax or parse hits a fatal error - var SAFE_MODE = true; - var fixMarkupRe = /(^(<[^>]+>|\t|)+|\n)/gm; - var LANGUAGE_NOT_FOUND = "Could not find the language '{}', did you forget to load/include a language module?"; - /** @type {Language} */ - var PLAINTEXT_LANGUAGE = { - disableAutodetect: true, - name: 'Plain text', - contains: [] - }; - - // Global options used when within external APIs. This is modified when - // calling the `hljs.configure` function. - /** @type HLJSOptions */ - var options = { - noHighlightRe: /^(no-?highlight)$/i, - languageDetectRe: /\blang(?:uage)?-([\w-]+)\b/i, - classPrefix: 'hljs-', - tabReplace: null, - useBR: false, - languages: null, - // beta configuration options, subject to change, welcome to discuss - // https://github.com/highlightjs/highlight.js/issues/1086 - __emitter: TokenTreeEmitter - }; - - /* Utility functions */ - - /** - * Tests a language name to see if highlighting should be skipped - * @param {string} languageName - */ - function shouldNotHighlight(languageName) { - return options.noHighlightRe.test(languageName); - } - - /** - * @param {HighlightedHTMLElement} block - the HTML element to determine language for - */ - function blockLanguage(block) { - var classes = block.className + ' '; - classes += block.parentNode ? block.parentNode.className : ''; - - // language-* takes precedence over non-prefixed class names. - var match = options.languageDetectRe.exec(classes); - if (match) { - var language = getLanguage(match[1]); - if (!language) { - warn(LANGUAGE_NOT_FOUND.replace("{}", match[1])); - warn("Falling back to no-highlight mode for this block.", block); - } - return language ? match[1] : 'no-highlight'; - } - return classes.split(/\s+/).find(function (_class) { - return shouldNotHighlight(_class) || getLanguage(_class); - }); - } - - /** - * Core highlighting function. - * - * OLD API - * highlight(lang, code, ignoreIllegals, continuation) - * - * NEW API - * highlight(code, {lang, ignoreIllegals}) - * - * @param {string} codeOrlanguageName - the language to use for highlighting - * @param {string | HighlightOptions} optionsOrCode - the code to highlight - * @param {boolean} [ignoreIllegals] - whether to ignore illegal matches, default is to bail - * @param {CompiledMode} [continuation] - current continuation mode, if any - * - * @returns {HighlightResult} Result - an object that represents the result - * @property {string} language - the language name - * @property {number} relevance - the relevance score - * @property {string} value - the highlighted HTML code - * @property {string} code - the original raw code - * @property {CompiledMode} top - top of the current mode stack - * @property {boolean} illegal - indicates whether any illegal matches were found - */ - function highlight(codeOrlanguageName, optionsOrCode, ignoreIllegals, continuation) { - var code = ""; - var languageName = ""; - if (typeof optionsOrCode === "object") { - code = codeOrlanguageName; - ignoreIllegals = optionsOrCode.ignoreIllegals; - languageName = optionsOrCode.language; - // continuation not supported at all via the new API - // eslint-disable-next-line no-undefined - continuation = undefined; - } else { - // old API - deprecated("10.7.0", "highlight(lang, code, ...args) has been deprecated."); - deprecated("10.7.0", "Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277"); - languageName = codeOrlanguageName; - code = optionsOrCode; - } - - /** @type {BeforeHighlightContext} */ - var context = { - code: code, - language: languageName - }; - // the plugin can change the desired language or the code to be highlighted - // just be changing the object it was passed - fire("before:highlight", context); - - // a before plugin can usurp the result completely by providing it's own - // in which case we don't even need to call highlight - var result = context.result ? context.result : _highlight(context.language, context.code, ignoreIllegals, continuation); - result.code = context.code; - // the plugin can change anything in result to suite it - fire("after:highlight", result); - return result; - } - - /** - * private highlight that's used internally and does not fire callbacks - * - * @param {string} languageName - the language to use for highlighting - * @param {string} codeToHighlight - the code to highlight - * @param {boolean?} [ignoreIllegals] - whether to ignore illegal matches, default is to bail - * @param {CompiledMode?} [continuation] - current continuation mode, if any - * @returns {HighlightResult} - result of the highlight operation - */ - function _highlight(languageName, codeToHighlight, ignoreIllegals, continuation) { - /** - * Return keyword data if a match is a keyword - * @param {CompiledMode} mode - current mode - * @param {RegExpMatchArray} match - regexp match data - * @returns {KeywordData | false} - */ - function keywordData(mode, match) { - var matchText = language.case_insensitive ? match[0].toLowerCase() : match[0]; - return Object.prototype.hasOwnProperty.call(mode.keywords, matchText) && mode.keywords[matchText]; - } - function processKeywords() { - if (!top.keywords) { - emitter.addText(modeBuffer); - return; - } - var lastIndex = 0; - top.keywordPatternRe.lastIndex = 0; - var match = top.keywordPatternRe.exec(modeBuffer); - var buf = ""; - while (match) { - buf += modeBuffer.substring(lastIndex, match.index); - var data = keywordData(top, match); - if (data) { - var _data = _slicedToArray(data, 2), - kind = _data[0], - keywordRelevance = _data[1]; - emitter.addText(buf); - buf = ""; - relevance += keywordRelevance; - if (kind.startsWith("_")) { - // _ implied for relevance only, do not highlight - // by applying a class name - buf += match[0]; - } else { - var cssClass = language.classNameAliases[kind] || kind; - emitter.addKeyword(match[0], cssClass); - } - } else { - buf += match[0]; - } - lastIndex = top.keywordPatternRe.lastIndex; - match = top.keywordPatternRe.exec(modeBuffer); - } - buf += modeBuffer.substr(lastIndex); - emitter.addText(buf); - } - function processSubLanguage() { - if (modeBuffer === "") return; - /** @type HighlightResult */ - var result = null; - if (typeof top.subLanguage === 'string') { - if (!languages[top.subLanguage]) { - emitter.addText(modeBuffer); - return; - } - result = _highlight(top.subLanguage, modeBuffer, true, continuations[top.subLanguage]); - continuations[top.subLanguage] = /** @type {CompiledMode} */result.top; - } else { - result = highlightAuto(modeBuffer, top.subLanguage.length ? top.subLanguage : null); - } - - // Counting embedded language score towards the host language may be disabled - // with zeroing the containing mode relevance. Use case in point is Markdown that - // allows XML everywhere and makes every XML snippet to have a much larger Markdown - // score. - if (top.relevance > 0) { - relevance += result.relevance; - } - emitter.addSublanguage(result.emitter, result.language); - } - function processBuffer() { - if (top.subLanguage != null) { - processSubLanguage(); - } else { - processKeywords(); - } - modeBuffer = ''; - } - - /** - * @param {Mode} mode - new mode to start - */ - function startNewMode(mode) { - if (mode.className) { - emitter.openNode(language.classNameAliases[mode.className] || mode.className); - } - top = Object.create(mode, { - parent: { - value: top - } - }); - return top; - } - - /** - * @param {CompiledMode } mode - the mode to potentially end - * @param {RegExpMatchArray} match - the latest match - * @param {string} matchPlusRemainder - match plus remainder of content - * @returns {CompiledMode | void} - the next mode, or if void continue on in current mode - */ - function endOfMode(mode, match, matchPlusRemainder) { - var matched = startsWith(mode.endRe, matchPlusRemainder); - if (matched) { - if (mode["on:end"]) { - var resp = new Response(mode); - mode["on:end"](match, resp); - if (resp.isMatchIgnored) matched = false; - } - if (matched) { - while (mode.endsParent && mode.parent) { - mode = mode.parent; - } - return mode; - } - } - // even if on:end fires an `ignore` it's still possible - // that we might trigger the end node because of a parent mode - if (mode.endsWithParent) { - return endOfMode(mode.parent, match, matchPlusRemainder); - } - } - - /** - * Handle matching but then ignoring a sequence of text - * - * @param {string} lexeme - string containing full match text - */ - function doIgnore(lexeme) { - if (top.matcher.regexIndex === 0) { - // no more regexs to potentially match here, so we move the cursor forward one - // space - modeBuffer += lexeme[0]; - return 1; - } else { - // no need to move the cursor, we still have additional regexes to try and - // match at this very spot - resumeScanAtSamePosition = true; - return 0; - } - } - - /** - * Handle the start of a new potential mode match - * - * @param {EnhancedMatch} match - the current match - * @returns {number} how far to advance the parse cursor - */ - function doBeginMatch(match) { - var lexeme = match[0]; - var newMode = match.rule; - var resp = new Response(newMode); - // first internal before callbacks, then the public ones - var beforeCallbacks = [newMode.__beforeBegin, newMode["on:begin"]]; - for (var _i = 0, _beforeCallbacks = beforeCallbacks; _i < _beforeCallbacks.length; _i++) { - var cb = _beforeCallbacks[_i]; - if (!cb) continue; - cb(match, resp); - if (resp.isMatchIgnored) return doIgnore(lexeme); - } - if (newMode && newMode.endSameAsBegin) { - newMode.endRe = escape(lexeme); - } - if (newMode.skip) { - modeBuffer += lexeme; - } else { - if (newMode.excludeBegin) { - modeBuffer += lexeme; - } - processBuffer(); - if (!newMode.returnBegin && !newMode.excludeBegin) { - modeBuffer = lexeme; - } - } - startNewMode(newMode); - // if (mode["after:begin"]) { - // let resp = new Response(mode); - // mode["after:begin"](match, resp); - // } - return newMode.returnBegin ? 0 : lexeme.length; - } - - /** - * Handle the potential end of mode - * - * @param {RegExpMatchArray} match - the current match - */ - function doEndMatch(match) { - var lexeme = match[0]; - var matchPlusRemainder = codeToHighlight.substr(match.index); - var endMode = endOfMode(top, match, matchPlusRemainder); - if (!endMode) { - return NO_MATCH; - } - var origin = top; - if (origin.skip) { - modeBuffer += lexeme; - } else { - if (!(origin.returnEnd || origin.excludeEnd)) { - modeBuffer += lexeme; - } - processBuffer(); - if (origin.excludeEnd) { - modeBuffer = lexeme; - } - } - do { - if (top.className) { - emitter.closeNode(); - } - if (!top.skip && !top.subLanguage) { - relevance += top.relevance; - } - top = top.parent; - } while (top !== endMode.parent); - if (endMode.starts) { - if (endMode.endSameAsBegin) { - endMode.starts.endRe = endMode.endRe; - } - startNewMode(endMode.starts); - } - return origin.returnEnd ? 0 : lexeme.length; - } - function processContinuations() { - var list = []; - for (var current = top; current !== language; current = current.parent) { - if (current.className) { - list.unshift(current.className); - } - } - list.forEach(function (item) { - return emitter.openNode(item); - }); - } - - /** @type {{type?: MatchType, index?: number, rule?: Mode}}} */ - var lastMatch = {}; - - /** - * Process an individual match - * - * @param {string} textBeforeMatch - text preceeding the match (since the last match) - * @param {EnhancedMatch} [match] - the match itself - */ - function processLexeme(textBeforeMatch, match) { - var lexeme = match && match[0]; - - // add non-matched text to the current mode buffer - modeBuffer += textBeforeMatch; - if (lexeme == null) { - processBuffer(); - return 0; - } - - // we've found a 0 width match and we're stuck, so we need to advance - // this happens when we have badly behaved rules that have optional matchers to the degree that - // sometimes they can end up matching nothing at all - // Ref: https://github.com/highlightjs/highlight.js/issues/2140 - if (lastMatch.type === "begin" && match.type === "end" && lastMatch.index === match.index && lexeme === "") { - // spit the "skipped" character that our regex choked on back into the output sequence - modeBuffer += codeToHighlight.slice(match.index, match.index + 1); - if (!SAFE_MODE) { - /** @type {AnnotatedError} */ - var err = new Error('0 width match regex'); - err.languageName = languageName; - err.badRule = lastMatch.rule; - throw err; - } - return 1; - } - lastMatch = match; - if (match.type === "begin") { - return doBeginMatch(match); - } else if (match.type === "illegal" && !ignoreIllegals) { - // illegal match, we do not continue processing - /** @type {AnnotatedError} */ - var _err = new Error('Illegal lexeme "' + lexeme + '" for mode "' + (top.className || '') + '"'); - _err.mode = top; - throw _err; - } else if (match.type === "end") { - var processed = doEndMatch(match); - if (processed !== NO_MATCH) { - return processed; - } - } - - // edge case for when illegal matches $ (end of line) which is technically - // a 0 width match but not a begin/end match so it's not caught by the - // first handler (when ignoreIllegals is true) - if (match.type === "illegal" && lexeme === "") { - // advance so we aren't stuck in an infinite loop - return 1; - } - - // infinite loops are BAD, this is a last ditch catch all. if we have a - // decent number of iterations yet our index (cursor position in our - // parsing) still 3x behind our index then something is very wrong - // so we bail - if (iterations > 100000 && iterations > match.index * 3) { - var _err2 = new Error('potential infinite loop, way more iterations than matches'); - throw _err2; - } - - /* - Why might be find ourselves here? Only one occasion now. An end match that was - triggered but could not be completed. When might this happen? When an `endSameasBegin` - rule sets the end rule to a specific match. Since the overall mode termination rule that's - being used to scan the text isn't recompiled that means that any match that LOOKS like - the end (but is not, because it is not an exact match to the beginning) will - end up here. A definite end match, but when `doEndMatch` tries to "reapply" - the end rule and fails to match, we wind up here, and just silently ignore the end. - This causes no real harm other than stopping a few times too many. - */ - - modeBuffer += lexeme; - return lexeme.length; - } - var language = getLanguage(languageName); - if (!language) { - error(LANGUAGE_NOT_FOUND.replace("{}", languageName)); - throw new Error('Unknown language: "' + languageName + '"'); - } - var md = compileLanguage(language, { - plugins: plugins - }); - var result = ''; - /** @type {CompiledMode} */ - var top = continuation || md; - /** @type Record */ - var continuations = {}; // keep continuations for sub-languages - var emitter = new options.__emitter(options); - processContinuations(); - var modeBuffer = ''; - var relevance = 0; - var index = 0; - var iterations = 0; - var resumeScanAtSamePosition = false; - try { - top.matcher.considerAll(); - for (;;) { - iterations++; - if (resumeScanAtSamePosition) { - // only regexes not matched previously will now be - // considered for a potential match - resumeScanAtSamePosition = false; - } else { - top.matcher.considerAll(); - } - top.matcher.lastIndex = index; - var match = top.matcher.exec(codeToHighlight); - // console.log("match", match[0], match.rule && match.rule.begin) - - if (!match) break; - var beforeMatch = codeToHighlight.substring(index, match.index); - var processedCount = processLexeme(beforeMatch, match); - index = match.index + processedCount; - } - processLexeme(codeToHighlight.substr(index)); - emitter.closeAllNodes(); - emitter.finalize(); - result = emitter.toHTML(); - return { - // avoid possible breakage with v10 clients expecting - // this to always be an integer - relevance: Math.floor(relevance), - value: result, - language: languageName, - illegal: false, - emitter: emitter, - top: top - }; - } catch (err) { - if (err.message && err.message.includes('Illegal')) { - return { - illegal: true, - illegalBy: { - msg: err.message, - context: codeToHighlight.slice(index - 100, index + 100), - mode: err.mode - }, - sofar: result, - relevance: 0, - value: escape$1(codeToHighlight), - emitter: emitter - }; - } else if (SAFE_MODE) { - return { - illegal: false, - relevance: 0, - value: escape$1(codeToHighlight), - emitter: emitter, - language: languageName, - top: top, - errorRaised: err - }; - } else { - throw err; - } - } - } - - /** - * returns a valid highlight result, without actually doing any actual work, - * auto highlight starts with this and it's possible for small snippets that - * auto-detection may not find a better match - * @param {string} code - * @returns {HighlightResult} - */ - function justTextHighlightResult(code) { - var result = { - relevance: 0, - emitter: new options.__emitter(options), - value: escape$1(code), - illegal: false, - top: PLAINTEXT_LANGUAGE - }; - result.emitter.addText(code); - return result; - } - - /** - Highlighting with language detection. Accepts a string with the code to - highlight. Returns an object with the following properties: - - language (detected language) - - relevance (int) - - value (an HTML string with highlighting markup) - - second_best (object with the same structure for second-best heuristically - detected language, may be absent) - @param {string} code - @param {Array} [languageSubset] - @returns {AutoHighlightResult} - */ - function highlightAuto(code, languageSubset) { - languageSubset = languageSubset || options.languages || Object.keys(languages); - var plaintext = justTextHighlightResult(code); - var results = languageSubset.filter(getLanguage).filter(autoDetection).map(function (name) { - return _highlight(name, code, false); - }); - results.unshift(plaintext); // plaintext is always an option - - var sorted = results.sort(function (a, b) { - // sort base on relevance - if (a.relevance !== b.relevance) return b.relevance - a.relevance; - - // always award the tie to the base language - // ie if C++ and Arduino are tied, it's more likely to be C++ - if (a.language && b.language) { - if (getLanguage(a.language).supersetOf === b.language) { - return 1; - } else if (getLanguage(b.language).supersetOf === a.language) { - return -1; - } - } - - // otherwise say they are equal, which has the effect of sorting on - // relevance while preserving the original ordering - which is how ties - // have historically been settled, ie the language that comes first always - // wins in the case of a tie - return 0; - }); - var _sorted = _slicedToArray(sorted, 2), - best = _sorted[0], - secondBest = _sorted[1]; - - /** @type {AutoHighlightResult} */ - var result = best; - result.second_best = secondBest; - return result; - } - - /** - Post-processing of the highlighted markup: - - replace TABs with something more useful - - replace real line-breaks with '
    ' for non-pre containers - @param {string} html - @returns {string} - */ - function fixMarkup(html) { - if (!(options.tabReplace || options.useBR)) { - return html; - } - return html.replace(fixMarkupRe, function (match) { - if (match === '\n') { - return options.useBR ? '
    ' : match; - } else if (options.tabReplace) { - return match.replace(/\t/g, options.tabReplace); - } - return match; - }); - } - - /** - * Builds new class name for block given the language name - * - * @param {HTMLElement} element - * @param {string} [currentLang] - * @param {string} [resultLang] - */ - function updateClassName(element, currentLang, resultLang) { - var language = currentLang ? aliases[currentLang] : resultLang; - element.classList.add("hljs"); - if (language) element.classList.add(language); - } - - /** @type {HLJSPlugin} */ - var brPlugin = { - "before:highlightElement": function beforeHighlightElement(_ref6) { - var el = _ref6.el; - if (options.useBR) { - el.innerHTML = el.innerHTML.replace(/\n/g, '').replace(//g, '\n'); - } - }, - "after:highlightElement": function afterHighlightElement(_ref7) { - var result = _ref7.result; - if (options.useBR) { - result.value = result.value.replace(/\n/g, "
    "); - } - } - }; - var TAB_REPLACE_RE = /^(<[^>]+>|\t)+/gm; - /** @type {HLJSPlugin} */ - var tabReplacePlugin = { - "after:highlightElement": function afterHighlightElement(_ref8) { - var result = _ref8.result; - if (options.tabReplace) { - result.value = result.value.replace(TAB_REPLACE_RE, function (m) { - return m.replace(/\t/g, options.tabReplace); - }); - } - } - }; - - /** - * Applies highlighting to a DOM node containing code. Accepts a DOM node and - * two optional parameters for fixMarkup. - * - * @param {HighlightedHTMLElement} element - the HTML element to highlight - */ - function highlightElement(element) { - /** @type HTMLElement */ - var node = null; - var language = blockLanguage(element); - if (shouldNotHighlight(language)) return; - - // support for v10 API - fire("before:highlightElement", { - el: element, - language: language - }); - node = element; - var text = node.textContent; - var result = language ? highlight(text, { - language: language, - ignoreIllegals: true - }) : highlightAuto(text); - - // support for v10 API - fire("after:highlightElement", { - el: element, - result: result, - text: text - }); - element.innerHTML = result.value; - updateClassName(element, language, result.language); - element.result = { - language: result.language, - // TODO: remove with version 11.0 - re: result.relevance, - relavance: result.relevance - }; - if (result.second_best) { - element.second_best = { - language: result.second_best.language, - // TODO: remove with version 11.0 - re: result.second_best.relevance, - relavance: result.second_best.relevance - }; - } - } - - /** - * Updates highlight.js global options with the passed options - * - * @param {Partial} userOptions - */ - function configure(userOptions) { - if (userOptions.useBR) { - deprecated("10.3.0", "'useBR' will be removed entirely in v11.0"); - deprecated("10.3.0", "Please see https://github.com/highlightjs/highlight.js/issues/2559"); - } - options = inherit$1(options, userOptions); - } - - /** - * Highlights to all
     blocks on a page
    -   *
    -   * @type {Function & {called?: boolean}}
    -   */
    -  // TODO: remove v12, deprecated
    -  var initHighlighting = function initHighlighting() {
    -    if (initHighlighting.called) return;
    -    initHighlighting.called = true;
    -    deprecated("10.6.0", "initHighlighting() is deprecated.  Use highlightAll() instead.");
    -    var blocks = document.querySelectorAll('pre code');
    -    blocks.forEach(highlightElement);
    -  };
    -
    -  // Higlights all when DOMContentLoaded fires
    -  // TODO: remove v12, deprecated
    -  function initHighlightingOnLoad() {
    -    deprecated("10.6.0", "initHighlightingOnLoad() is deprecated.  Use highlightAll() instead.");
    -    wantsHighlight = true;
    -  }
    -  var wantsHighlight = false;
    -
    -  /**
    -   * auto-highlights all pre>code elements on the page
    -   */
    -  function highlightAll() {
    -    // if we are called too early in the loading process
    -    if (document.readyState === "loading") {
    -      wantsHighlight = true;
    -      return;
    -    }
    -    var blocks = document.querySelectorAll('pre code');
    -    blocks.forEach(highlightElement);
    -  }
    -  function boot() {
    -    // if a highlight was requested before DOM was loaded, do now
    -    if (wantsHighlight) highlightAll();
    -  }
    -
    -  // make sure we are in the browser environment
    -  if (typeof window !== 'undefined' && window.addEventListener) {
    -    window.addEventListener('DOMContentLoaded', boot, false);
    -  }
    -
    -  /**
    -   * Register a language grammar module
    -   *
    -   * @param {string} languageName
    -   * @param {LanguageFn} languageDefinition
    -   */
    -  function registerLanguage(languageName, languageDefinition) {
    -    var lang = null;
    -    try {
    -      lang = languageDefinition(hljs);
    -    } catch (error$1) {
    -      error("Language definition for '{}' could not be registered.".replace("{}", languageName));
    -      // hard or soft error
    -      if (!SAFE_MODE) {
    -        throw error$1;
    -      } else {
    -        error(error$1);
    -      }
    -      // languages that have serious errors are replaced with essentially a
    -      // "plaintext" stand-in so that the code blocks will still get normal
    -      // css classes applied to them - and one bad language won't break the
    -      // entire highlighter
    -      lang = PLAINTEXT_LANGUAGE;
    -    }
    -    // give it a temporary name if it doesn't have one in the meta-data
    -    if (!lang.name) lang.name = languageName;
    -    languages[languageName] = lang;
    -    lang.rawDefinition = languageDefinition.bind(null, hljs);
    -    if (lang.aliases) {
    -      registerAliases(lang.aliases, {
    -        languageName: languageName
    -      });
    -    }
    -  }
    -
    -  /**
    -   * Remove a language grammar module
    -   *
    -   * @param {string} languageName
    -   */
    -  function unregisterLanguage(languageName) {
    -    delete languages[languageName];
    -    for (var _i2 = 0, _Object$keys = Object.keys(aliases); _i2 < _Object$keys.length; _i2++) {
    -      var alias = _Object$keys[_i2];
    -      if (aliases[alias] === languageName) {
    -        delete aliases[alias];
    -      }
    -    }
    -  }
    -
    -  /**
    -   * @returns {string[]} List of language internal names
    -   */
    -  function listLanguages() {
    -    return Object.keys(languages);
    -  }
    -
    -  /**
    -    intended usage: When one language truly requires another
    -     Unlike `getLanguage`, this will throw when the requested language
    -    is not available.
    -     @param {string} name - name of the language to fetch/require
    -    @returns {Language | never}
    -  */
    -  function requireLanguage(name) {
    -    deprecated("10.4.0", "requireLanguage will be removed entirely in v11.");
    -    deprecated("10.4.0", "Please see https://github.com/highlightjs/highlight.js/pull/2844");
    -    var lang = getLanguage(name);
    -    if (lang) {
    -      return lang;
    -    }
    -    var err = new Error('The \'{}\' language is required, but not loaded.'.replace('{}', name));
    -    throw err;
    -  }
    -
    -  /**
    -   * @param {string} name - name of the language to retrieve
    -   * @returns {Language | undefined}
    -   */
    -  function getLanguage(name) {
    -    name = (name || '').toLowerCase();
    -    return languages[name] || languages[aliases[name]];
    -  }
    -
    -  /**
    -   *
    -   * @param {string|string[]} aliasList - single alias or list of aliases
    -   * @param {{languageName: string}} opts
    -   */
    -  function registerAliases(aliasList, _ref9) {
    -    var languageName = _ref9.languageName;
    -    if (typeof aliasList === 'string') {
    -      aliasList = [aliasList];
    -    }
    -    aliasList.forEach(function (alias) {
    -      aliases[alias.toLowerCase()] = languageName;
    -    });
    -  }
    -
    -  /**
    -   * Determines if a given language has auto-detection enabled
    -   * @param {string} name - name of the language
    -   */
    -  function autoDetection(name) {
    -    var lang = getLanguage(name);
    -    return lang && !lang.disableAutodetect;
    -  }
    -
    -  /**
    -   * Upgrades the old highlightBlock plugins to the new
    -   * highlightElement API
    -   * @param {HLJSPlugin} plugin
    -   */
    -  function upgradePluginAPI(plugin) {
    -    // TODO: remove with v12
    -    if (plugin["before:highlightBlock"] && !plugin["before:highlightElement"]) {
    -      plugin["before:highlightElement"] = function (data) {
    -        plugin["before:highlightBlock"](Object.assign({
    -          block: data.el
    -        }, data));
    -      };
    -    }
    -    if (plugin["after:highlightBlock"] && !plugin["after:highlightElement"]) {
    -      plugin["after:highlightElement"] = function (data) {
    -        plugin["after:highlightBlock"](Object.assign({
    -          block: data.el
    -        }, data));
    -      };
    -    }
    -  }
    -
    -  /**
    -   * @param {HLJSPlugin} plugin
    -   */
    -  function addPlugin(plugin) {
    -    upgradePluginAPI(plugin);
    -    plugins.push(plugin);
    -  }
    -
    -  /**
    -   *
    -   * @param {PluginEvent} event
    -   * @param {any} args
    -   */
    -  function fire(event, args) {
    -    var cb = event;
    -    plugins.forEach(function (plugin) {
    -      if (plugin[cb]) {
    -        plugin[cb](args);
    -      }
    -    });
    -  }
    -
    -  /**
    -  Note: fixMarkup is deprecated and will be removed entirely in v11
    -   @param {string} arg
    -  @returns {string}
    -  */
    -  function deprecateFixMarkup(arg) {
    -    deprecated("10.2.0", "fixMarkup will be removed entirely in v11.0");
    -    deprecated("10.2.0", "Please see https://github.com/highlightjs/highlight.js/issues/2534");
    -    return fixMarkup(arg);
    -  }
    -
    -  /**
    -   *
    -   * @param {HighlightedHTMLElement} el
    -   */
    -  function deprecateHighlightBlock(el) {
    -    deprecated("10.7.0", "highlightBlock will be removed entirely in v12.0");
    -    deprecated("10.7.0", "Please use highlightElement now.");
    -    return highlightElement(el);
    -  }
    -
    -  /* Interface definition */
    -  Object.assign(hljs, {
    -    highlight: highlight,
    -    highlightAuto: highlightAuto,
    -    highlightAll: highlightAll,
    -    fixMarkup: deprecateFixMarkup,
    -    highlightElement: highlightElement,
    -    // TODO: Remove with v12 API
    -    highlightBlock: deprecateHighlightBlock,
    -    configure: configure,
    -    initHighlighting: initHighlighting,
    -    initHighlightingOnLoad: initHighlightingOnLoad,
    -    registerLanguage: registerLanguage,
    -    unregisterLanguage: unregisterLanguage,
    -    listLanguages: listLanguages,
    -    getLanguage: getLanguage,
    -    registerAliases: registerAliases,
    -    requireLanguage: requireLanguage,
    -    autoDetection: autoDetection,
    -    inherit: inherit$1,
    -    addPlugin: addPlugin,
    -    // plugins for frameworks
    -    vuePlugin: BuildVuePlugin(hljs).VuePlugin
    -  });
    -  hljs.debugMode = function () {
    -    SAFE_MODE = false;
    -  };
    -  hljs.safeMode = function () {
    -    SAFE_MODE = true;
    -  };
    -  hljs.versionString = version;
    -  for (var key in MODES) {
    -    // @ts-ignore
    -    if (typeof MODES[key] === "object") {
    -      // @ts-ignore
    -      deepFreezeEs6(MODES[key]);
    -    }
    -  }
    -
    -  // merge all the modes/regexs into our main object
    -  Object.assign(hljs, MODES);
    -
    -  // built-in plugins, likely to be moved out of core in the future
    -  hljs.addPlugin(brPlugin); // slated to be removed in v11
    -  hljs.addPlugin(mergeHTMLPlugin);
    -  hljs.addPlugin(tabReplacePlugin);
    -  return hljs;
    -};
    -
    -// export an "instance" of the highlighter
    -var highlight = HLJS({});
    -module.exports = highlight;
    -
    -/***/ }),
    -
    -/***/ 682:
    -/***/ (function(module) {
    -
    -/*
    -Language: Diff
    -Description: Unified and context diff
    -Author: Vasily Polovnyov 
    -Website: https://www.gnu.org/software/diffutils/
    -Category: common
    -*/
    -
    -/** @type LanguageFn */
    -function diff(hljs) {
    -  return {
    -    name: 'Diff',
    -    aliases: ['patch'],
    -    contains: [{
    -      className: 'meta',
    -      relevance: 10,
    -      variants: [{
    -        begin: /^@@ +-\d+,\d+ +\+\d+,\d+ +@@/
    -      }, {
    -        begin: /^\*\*\* +\d+,\d+ +\*\*\*\*$/
    -      }, {
    -        begin: /^--- +\d+,\d+ +----$/
    -      }]
    -    }, {
    -      className: 'comment',
    -      variants: [{
    -        begin: /Index: /,
    -        end: /$/
    -      }, {
    -        begin: /^index/,
    -        end: /$/
    -      }, {
    -        begin: /={3,}/,
    -        end: /$/
    -      }, {
    -        begin: /^-{3}/,
    -        end: /$/
    -      }, {
    -        begin: /^\*{3} /,
    -        end: /$/
    -      }, {
    -        begin: /^\+{3}/,
    -        end: /$/
    -      }, {
    -        begin: /^\*{15}$/
    -      }, {
    -        begin: /^diff --git/,
    -        end: /$/
    -      }]
    -    }, {
    -      className: 'addition',
    -      begin: /^\+/,
    -      end: /$/
    -    }, {
    -      className: 'deletion',
    -      begin: /^-/,
    -      end: /$/
    -    }, {
    -      className: 'addition',
    -      begin: /^!/,
    -      end: /$/
    -    }]
    -  };
    -}
    -module.exports = diff;
    -
    -/***/ }),
    -
    -/***/ 712:
    -/***/ (function(module) {
    -
    -/*
    -Language: YAML
    -Description: Yet Another Markdown Language
    -Author: Stefan Wienert 
    -Contributors: Carl Baxter 
    -Requires: ruby.js
    -Website: https://yaml.org
    -Category: common, config
    -*/
    -function yaml(hljs) {
    -  var LITERALS = 'true false yes no null';
    -
    -  // YAML spec allows non-reserved URI characters in tags.
    -  var URI_CHARACTERS = '[\\w#;/?:@&=+$,.~*\'()[\\]]+';
    -
    -  // Define keys as starting with a word character
    -  // ...containing word chars, spaces, colons, forward-slashes, hyphens and periods
    -  // ...and ending with a colon followed immediately by a space, tab or newline.
    -  // The YAML spec allows for much more than this, but this covers most use-cases.
    -  var KEY = {
    -    className: 'attr',
    -    variants: [{
    -      begin: '\\w[\\w :\\/.-]*:(?=[ \t]|$)'
    -    }, {
    -      begin: '"\\w[\\w :\\/.-]*":(?=[ \t]|$)'
    -    },
    -    // double quoted keys
    -    {
    -      begin: '\'\\w[\\w :\\/.-]*\':(?=[ \t]|$)'
    -    } // single quoted keys
    -    ]
    -  };
    -
    -  var TEMPLATE_VARIABLES = {
    -    className: 'template-variable',
    -    variants: [{
    -      begin: /\{\{/,
    -      end: /\}\}/
    -    },
    -    // jinja templates Ansible
    -    {
    -      begin: /%\{/,
    -      end: /\}/
    -    } // Ruby i18n
    -    ]
    -  };
    -
    -  var STRING = {
    -    className: 'string',
    -    relevance: 0,
    -    variants: [{
    -      begin: /'/,
    -      end: /'/
    -    }, {
    -      begin: /"/,
    -      end: /"/
    -    }, {
    -      begin: /\S+/
    -    }],
    -    contains: [hljs.BACKSLASH_ESCAPE, TEMPLATE_VARIABLES]
    -  };
    -
    -  // Strings inside of value containers (objects) can't contain braces,
    -  // brackets, or commas
    -  var CONTAINER_STRING = hljs.inherit(STRING, {
    -    variants: [{
    -      begin: /'/,
    -      end: /'/
    -    }, {
    -      begin: /"/,
    -      end: /"/
    -    }, {
    -      begin: /[^\s,{}[\]]+/
    -    }]
    -  });
    -  var DATE_RE = '[0-9]{4}(-[0-9][0-9]){0,2}';
    -  var TIME_RE = '([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?';
    -  var FRACTION_RE = '(\\.[0-9]*)?';
    -  var ZONE_RE = '([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?';
    -  var TIMESTAMP = {
    -    className: 'number',
    -    begin: '\\b' + DATE_RE + TIME_RE + FRACTION_RE + ZONE_RE + '\\b'
    -  };
    -  var VALUE_CONTAINER = {
    -    end: ',',
    -    endsWithParent: true,
    -    excludeEnd: true,
    -    keywords: LITERALS,
    -    relevance: 0
    -  };
    -  var OBJECT = {
    -    begin: /\{/,
    -    end: /\}/,
    -    contains: [VALUE_CONTAINER],
    -    illegal: '\\n',
    -    relevance: 0
    -  };
    -  var ARRAY = {
    -    begin: '\\[',
    -    end: '\\]',
    -    contains: [VALUE_CONTAINER],
    -    illegal: '\\n',
    -    relevance: 0
    -  };
    -  var MODES = [KEY, {
    -    className: 'meta',
    -    begin: '^---\\s*$',
    -    relevance: 10
    -  }, {
    -    // multi line string
    -    // Blocks start with a | or > followed by a newline
    -    //
    -    // Indentation of subsequent lines must be the same to
    -    // be considered part of the block
    -    className: 'string',
    -    begin: '[\\|>]([1-9]?[+-])?[ ]*\\n( +)[^ ][^\\n]*\\n(\\2[^\\n]+\\n?)*'
    -  }, {
    -    // Ruby/Rails erb
    -    begin: '<%[%=-]?',
    -    end: '[%-]?%>',
    -    subLanguage: 'ruby',
    -    excludeBegin: true,
    -    excludeEnd: true,
    -    relevance: 0
    -  }, {
    -    // named tags
    -    className: 'type',
    -    begin: '!\\w+!' + URI_CHARACTERS
    -  },
    -  // https://yaml.org/spec/1.2/spec.html#id2784064
    -  {
    -    // verbatim tags
    -    className: 'type',
    -    begin: '!<' + URI_CHARACTERS + ">"
    -  }, {
    -    // primary tags
    -    className: 'type',
    -    begin: '!' + URI_CHARACTERS
    -  }, {
    -    // secondary tags
    -    className: 'type',
    -    begin: '!!' + URI_CHARACTERS
    -  }, {
    -    // fragment id &ref
    -    className: 'meta',
    -    begin: '&' + hljs.UNDERSCORE_IDENT_RE + '$'
    -  }, {
    -    // fragment reference *ref
    -    className: 'meta',
    -    begin: '\\*' + hljs.UNDERSCORE_IDENT_RE + '$'
    -  }, {
    -    // array listing
    -    className: 'bullet',
    -    // TODO: remove |$ hack when we have proper look-ahead support
    -    begin: '-(?=[ ]|$)',
    -    relevance: 0
    -  }, hljs.HASH_COMMENT_MODE, {
    -    beginKeywords: LITERALS,
    -    keywords: {
    -      literal: LITERALS
    -    }
    -  }, TIMESTAMP,
    -  // numbers are any valid C-style number that
    -  // sit isolated from other words
    -  {
    -    className: 'number',
    -    begin: hljs.C_NUMBER_RE + '\\b',
    -    relevance: 0
    -  }, OBJECT, ARRAY, STRING];
    -  var VALUE_MODES = [].concat(MODES);
    -  VALUE_MODES.pop();
    -  VALUE_MODES.push(CONTAINER_STRING);
    -  VALUE_CONTAINER.contains = VALUE_MODES;
    -  return {
    -    name: 'YAML',
    -    case_insensitive: true,
    -    aliases: ['yml'],
    -    contains: MODES
    -  };
    -}
    -module.exports = yaml;
    -
    -/***/ }),
    -
    -/***/ 110:
    -/***/ (function(module, __unused_webpack_exports, __webpack_require__) {
    -
    -"use strict";
    -
    -
    -var reactIs = __webpack_require__(309);
    -
    -/**
    - * Copyright 2015, Yahoo! Inc.
    - * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
    - */
    -var REACT_STATICS = {
    -  childContextTypes: true,
    -  contextType: true,
    -  contextTypes: true,
    -  defaultProps: true,
    -  displayName: true,
    -  getDefaultProps: true,
    -  getDerivedStateFromError: true,
    -  getDerivedStateFromProps: true,
    -  mixins: true,
    -  propTypes: true,
    -  type: true
    -};
    -var KNOWN_STATICS = {
    -  name: true,
    -  length: true,
    -  prototype: true,
    -  caller: true,
    -  callee: true,
    -  arguments: true,
    -  arity: true
    -};
    -var FORWARD_REF_STATICS = {
    -  '$$typeof': true,
    -  render: true,
    -  defaultProps: true,
    -  displayName: true,
    -  propTypes: true
    -};
    -var MEMO_STATICS = {
    -  '$$typeof': true,
    -  compare: true,
    -  defaultProps: true,
    -  displayName: true,
    -  propTypes: true,
    -  type: true
    -};
    -var TYPE_STATICS = {};
    -TYPE_STATICS[reactIs.ForwardRef] = FORWARD_REF_STATICS;
    -TYPE_STATICS[reactIs.Memo] = MEMO_STATICS;
    -function getStatics(component) {
    -  // React v16.11 and below
    -  if (reactIs.isMemo(component)) {
    -    return MEMO_STATICS;
    -  } // React v16.12 and above
    -
    -  return TYPE_STATICS[component['$$typeof']] || REACT_STATICS;
    -}
    -var defineProperty = Object.defineProperty;
    -var getOwnPropertyNames = Object.getOwnPropertyNames;
    -var getOwnPropertySymbols = Object.getOwnPropertySymbols;
    -var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
    -var getPrototypeOf = Object.getPrototypeOf;
    -var objectPrototype = Object.prototype;
    -function hoistNonReactStatics(targetComponent, sourceComponent, blacklist) {
    -  if (typeof sourceComponent !== 'string') {
    -    // don't hoist over string (html) components
    -    if (objectPrototype) {
    -      var inheritedComponent = getPrototypeOf(sourceComponent);
    -      if (inheritedComponent && inheritedComponent !== objectPrototype) {
    -        hoistNonReactStatics(targetComponent, inheritedComponent, blacklist);
    -      }
    -    }
    -    var keys = getOwnPropertyNames(sourceComponent);
    -    if (getOwnPropertySymbols) {
    -      keys = keys.concat(getOwnPropertySymbols(sourceComponent));
    -    }
    -    var targetStatics = getStatics(targetComponent);
    -    var sourceStatics = getStatics(sourceComponent);
    -    for (var i = 0; i < keys.length; ++i) {
    -      var key = keys[i];
    -      if (!KNOWN_STATICS[key] && !(blacklist && blacklist[key]) && !(sourceStatics && sourceStatics[key]) && !(targetStatics && targetStatics[key])) {
    -        var descriptor = getOwnPropertyDescriptor(sourceComponent, key);
    -        try {
    -          // Avoid failures from read-only properties
    -          defineProperty(targetComponent, key, descriptor);
    -        } catch (e) {}
    -      }
    -    }
    -  }
    -  return targetComponent;
    -}
    -module.exports = hoistNonReactStatics;
    -
    -/***/ }),
    -
    -/***/ 746:
    -/***/ (function(__unused_webpack_module, exports) {
    -
    -"use strict";
    -/** @license React v16.13.1
    - * react-is.production.min.js
    - *
    - * Copyright (c) Facebook, Inc. and its affiliates.
    - *
    - * This source code is licensed under the MIT license found in the
    - * LICENSE file in the root directory of this source tree.
    - */
    -
    -
    -
    -var b = "function" === typeof Symbol && Symbol.for,
    -  c = b ? Symbol.for("react.element") : 60103,
    -  d = b ? Symbol.for("react.portal") : 60106,
    -  e = b ? Symbol.for("react.fragment") : 60107,
    -  f = b ? Symbol.for("react.strict_mode") : 60108,
    -  g = b ? Symbol.for("react.profiler") : 60114,
    -  h = b ? Symbol.for("react.provider") : 60109,
    -  k = b ? Symbol.for("react.context") : 60110,
    -  l = b ? Symbol.for("react.async_mode") : 60111,
    -  m = b ? Symbol.for("react.concurrent_mode") : 60111,
    -  n = b ? Symbol.for("react.forward_ref") : 60112,
    -  p = b ? Symbol.for("react.suspense") : 60113,
    -  q = b ? Symbol.for("react.suspense_list") : 60120,
    -  r = b ? Symbol.for("react.memo") : 60115,
    -  t = b ? Symbol.for("react.lazy") : 60116,
    -  v = b ? Symbol.for("react.block") : 60121,
    -  w = b ? Symbol.for("react.fundamental") : 60117,
    -  x = b ? Symbol.for("react.responder") : 60118,
    -  y = b ? Symbol.for("react.scope") : 60119;
    -function z(a) {
    -  if ("object" === typeof a && null !== a) {
    -    var u = a.$$typeof;
    -    switch (u) {
    -      case c:
    -        switch (a = a.type, a) {
    -          case l:
    -          case m:
    -          case e:
    -          case g:
    -          case f:
    -          case p:
    -            return a;
    -          default:
    -            switch (a = a && a.$$typeof, a) {
    -              case k:
    -              case n:
    -              case t:
    -              case r:
    -              case h:
    -                return a;
    -              default:
    -                return u;
    -            }
    -        }
    -      case d:
    -        return u;
    -    }
    -  }
    -}
    -function A(a) {
    -  return z(a) === m;
    -}
    -exports.AsyncMode = l;
    -exports.ConcurrentMode = m;
    -exports.ContextConsumer = k;
    -exports.ContextProvider = h;
    -exports.Element = c;
    -exports.ForwardRef = n;
    -exports.Fragment = e;
    -exports.Lazy = t;
    -exports.Memo = r;
    -exports.Portal = d;
    -exports.Profiler = g;
    -exports.StrictMode = f;
    -exports.Suspense = p;
    -exports.isAsyncMode = function (a) {
    -  return A(a) || z(a) === l;
    -};
    -exports.isConcurrentMode = A;
    -exports.isContextConsumer = function (a) {
    -  return z(a) === k;
    -};
    -exports.isContextProvider = function (a) {
    -  return z(a) === h;
    -};
    -exports.isElement = function (a) {
    -  return "object" === typeof a && null !== a && a.$$typeof === c;
    -};
    -exports.isForwardRef = function (a) {
    -  return z(a) === n;
    -};
    -exports.isFragment = function (a) {
    -  return z(a) === e;
    -};
    -exports.isLazy = function (a) {
    -  return z(a) === t;
    -};
    -exports.isMemo = function (a) {
    -  return z(a) === r;
    -};
    -exports.isPortal = function (a) {
    -  return z(a) === d;
    -};
    -exports.isProfiler = function (a) {
    -  return z(a) === g;
    -};
    -exports.isStrictMode = function (a) {
    -  return z(a) === f;
    -};
    -exports.isSuspense = function (a) {
    -  return z(a) === p;
    -};
    -exports.isValidElementType = function (a) {
    -  return "string" === typeof a || "function" === typeof a || a === e || a === m || a === g || a === f || a === p || a === q || "object" === typeof a && null !== a && (a.$$typeof === t || a.$$typeof === r || a.$$typeof === h || a.$$typeof === k || a.$$typeof === n || a.$$typeof === w || a.$$typeof === x || a.$$typeof === y || a.$$typeof === v);
    -};
    -exports.typeOf = z;
    -
    -/***/ }),
    -
    -/***/ 309:
    -/***/ (function(module, __unused_webpack_exports, __webpack_require__) {
    -
    -"use strict";
    -
    -
    -if (true) {
    -  module.exports = __webpack_require__(746);
    -} else {}
    -
    -/***/ }),
    -
    -/***/ 981:
    -/***/ (function(module, exports, __webpack_require__) {
    -
    -var __WEBPACK_AMD_DEFINE_RESULT__;/**
    - * [js-sha256]{@link https://github.com/emn178/js-sha256}
    - *
    - * @version 0.9.0
    - * @author Chen, Yi-Cyuan [emn178@gmail.com]
    - * @copyright Chen, Yi-Cyuan 2014-2017
    - * @license MIT
    - */
    -/*jslint bitwise: true */
    -(function () {
    -  'use strict';
    -
    -  var ERROR = 'input is invalid type';
    -  var WINDOW = typeof window === 'object';
    -  var root = WINDOW ? window : {};
    -  if (root.JS_SHA256_NO_WINDOW) {
    -    WINDOW = false;
    -  }
    -  var WEB_WORKER = !WINDOW && typeof self === 'object';
    -  var NODE_JS = !root.JS_SHA256_NO_NODE_JS && typeof process === 'object' && process.versions && process.versions.node;
    -  if (NODE_JS) {
    -    root = __webpack_require__.g;
    -  } else if (WEB_WORKER) {
    -    root = self;
    -  }
    -  var COMMON_JS = !root.JS_SHA256_NO_COMMON_JS && "object" === 'object' && module.exports;
    -  var AMD =  true && __webpack_require__.amdO;
    -  var ARRAY_BUFFER = !root.JS_SHA256_NO_ARRAY_BUFFER && typeof ArrayBuffer !== 'undefined';
    -  var HEX_CHARS = '0123456789abcdef'.split('');
    -  var EXTRA = [-2147483648, 8388608, 32768, 128];
    -  var SHIFT = [24, 16, 8, 0];
    -  var K = [0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2];
    -  var OUTPUT_TYPES = ['hex', 'array', 'digest', 'arrayBuffer'];
    -  var blocks = [];
    -  if (root.JS_SHA256_NO_NODE_JS || !Array.isArray) {
    -    Array.isArray = function (obj) {
    -      return Object.prototype.toString.call(obj) === '[object Array]';
    -    };
    -  }
    -  if (ARRAY_BUFFER && (root.JS_SHA256_NO_ARRAY_BUFFER_IS_VIEW || !ArrayBuffer.isView)) {
    -    ArrayBuffer.isView = function (obj) {
    -      return typeof obj === 'object' && obj.buffer && obj.buffer.constructor === ArrayBuffer;
    -    };
    -  }
    -  var createOutputMethod = function createOutputMethod(outputType, is224) {
    -    return function (message) {
    -      return new Sha256(is224, true).update(message)[outputType]();
    -    };
    -  };
    -  var createMethod = function createMethod(is224) {
    -    var method = createOutputMethod('hex', is224);
    -    if (NODE_JS) {
    -      method = nodeWrap(method, is224);
    -    }
    -    method.create = function () {
    -      return new Sha256(is224);
    -    };
    -    method.update = function (message) {
    -      return method.create().update(message);
    -    };
    -    for (var i = 0; i < OUTPUT_TYPES.length; ++i) {
    -      var type = OUTPUT_TYPES[i];
    -      method[type] = createOutputMethod(type, is224);
    -    }
    -    return method;
    -  };
    -  var nodeWrap = function nodeWrap(method, is224) {
    -    var crypto = eval("require('crypto')");
    -    var Buffer = eval("require('buffer').Buffer");
    -    var algorithm = is224 ? 'sha224' : 'sha256';
    -    var nodeMethod = function nodeMethod(message) {
    -      if (typeof message === 'string') {
    -        return crypto.createHash(algorithm).update(message, 'utf8').digest('hex');
    -      } else {
    -        if (message === null || message === undefined) {
    -          throw new Error(ERROR);
    -        } else if (message.constructor === ArrayBuffer) {
    -          message = new Uint8Array(message);
    -        }
    -      }
    -      if (Array.isArray(message) || ArrayBuffer.isView(message) || message.constructor === Buffer) {
    -        return crypto.createHash(algorithm).update(new Buffer(message)).digest('hex');
    -      } else {
    -        return method(message);
    -      }
    -    };
    -    return nodeMethod;
    -  };
    -  var createHmacOutputMethod = function createHmacOutputMethod(outputType, is224) {
    -    return function (key, message) {
    -      return new HmacSha256(key, is224, true).update(message)[outputType]();
    -    };
    -  };
    -  var createHmacMethod = function createHmacMethod(is224) {
    -    var method = createHmacOutputMethod('hex', is224);
    -    method.create = function (key) {
    -      return new HmacSha256(key, is224);
    -    };
    -    method.update = function (key, message) {
    -      return method.create(key).update(message);
    -    };
    -    for (var i = 0; i < OUTPUT_TYPES.length; ++i) {
    -      var type = OUTPUT_TYPES[i];
    -      method[type] = createHmacOutputMethod(type, is224);
    -    }
    -    return method;
    -  };
    -  function Sha256(is224, sharedMemory) {
    -    if (sharedMemory) {
    -      blocks[0] = blocks[16] = blocks[1] = blocks[2] = blocks[3] = blocks[4] = blocks[5] = blocks[6] = blocks[7] = blocks[8] = blocks[9] = blocks[10] = blocks[11] = blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0;
    -      this.blocks = blocks;
    -    } else {
    -      this.blocks = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
    -    }
    -    if (is224) {
    -      this.h0 = 0xc1059ed8;
    -      this.h1 = 0x367cd507;
    -      this.h2 = 0x3070dd17;
    -      this.h3 = 0xf70e5939;
    -      this.h4 = 0xffc00b31;
    -      this.h5 = 0x68581511;
    -      this.h6 = 0x64f98fa7;
    -      this.h7 = 0xbefa4fa4;
    -    } else {
    -      // 256
    -      this.h0 = 0x6a09e667;
    -      this.h1 = 0xbb67ae85;
    -      this.h2 = 0x3c6ef372;
    -      this.h3 = 0xa54ff53a;
    -      this.h4 = 0x510e527f;
    -      this.h5 = 0x9b05688c;
    -      this.h6 = 0x1f83d9ab;
    -      this.h7 = 0x5be0cd19;
    -    }
    -    this.block = this.start = this.bytes = this.hBytes = 0;
    -    this.finalized = this.hashed = false;
    -    this.first = true;
    -    this.is224 = is224;
    -  }
    -  Sha256.prototype.update = function (message) {
    -    if (this.finalized) {
    -      return;
    -    }
    -    var notString,
    -      type = typeof message;
    -    if (type !== 'string') {
    -      if (type === 'object') {
    -        if (message === null) {
    -          throw new Error(ERROR);
    -        } else if (ARRAY_BUFFER && message.constructor === ArrayBuffer) {
    -          message = new Uint8Array(message);
    -        } else if (!Array.isArray(message)) {
    -          if (!ARRAY_BUFFER || !ArrayBuffer.isView(message)) {
    -            throw new Error(ERROR);
    -          }
    -        }
    -      } else {
    -        throw new Error(ERROR);
    -      }
    -      notString = true;
    -    }
    -    var code,
    -      index = 0,
    -      i,
    -      length = message.length,
    -      blocks = this.blocks;
    -    while (index < length) {
    -      if (this.hashed) {
    -        this.hashed = false;
    -        blocks[0] = this.block;
    -        blocks[16] = blocks[1] = blocks[2] = blocks[3] = blocks[4] = blocks[5] = blocks[6] = blocks[7] = blocks[8] = blocks[9] = blocks[10] = blocks[11] = blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0;
    -      }
    -      if (notString) {
    -        for (i = this.start; index < length && i < 64; ++index) {
    -          blocks[i >> 2] |= message[index] << SHIFT[i++ & 3];
    -        }
    -      } else {
    -        for (i = this.start; index < length && i < 64; ++index) {
    -          code = message.charCodeAt(index);
    -          if (code < 0x80) {
    -            blocks[i >> 2] |= code << SHIFT[i++ & 3];
    -          } else if (code < 0x800) {
    -            blocks[i >> 2] |= (0xc0 | code >> 6) << SHIFT[i++ & 3];
    -            blocks[i >> 2] |= (0x80 | code & 0x3f) << SHIFT[i++ & 3];
    -          } else if (code < 0xd800 || code >= 0xe000) {
    -            blocks[i >> 2] |= (0xe0 | code >> 12) << SHIFT[i++ & 3];
    -            blocks[i >> 2] |= (0x80 | code >> 6 & 0x3f) << SHIFT[i++ & 3];
    -            blocks[i >> 2] |= (0x80 | code & 0x3f) << SHIFT[i++ & 3];
    -          } else {
    -            code = 0x10000 + ((code & 0x3ff) << 10 | message.charCodeAt(++index) & 0x3ff);
    -            blocks[i >> 2] |= (0xf0 | code >> 18) << SHIFT[i++ & 3];
    -            blocks[i >> 2] |= (0x80 | code >> 12 & 0x3f) << SHIFT[i++ & 3];
    -            blocks[i >> 2] |= (0x80 | code >> 6 & 0x3f) << SHIFT[i++ & 3];
    -            blocks[i >> 2] |= (0x80 | code & 0x3f) << SHIFT[i++ & 3];
    -          }
    -        }
    -      }
    -      this.lastByteIndex = i;
    -      this.bytes += i - this.start;
    -      if (i >= 64) {
    -        this.block = blocks[16];
    -        this.start = i - 64;
    -        this.hash();
    -        this.hashed = true;
    -      } else {
    -        this.start = i;
    -      }
    -    }
    -    if (this.bytes > 4294967295) {
    -      this.hBytes += this.bytes / 4294967296 << 0;
    -      this.bytes = this.bytes % 4294967296;
    -    }
    -    return this;
    -  };
    -  Sha256.prototype.finalize = function () {
    -    if (this.finalized) {
    -      return;
    -    }
    -    this.finalized = true;
    -    var blocks = this.blocks,
    -      i = this.lastByteIndex;
    -    blocks[16] = this.block;
    -    blocks[i >> 2] |= EXTRA[i & 3];
    -    this.block = blocks[16];
    -    if (i >= 56) {
    -      if (!this.hashed) {
    -        this.hash();
    -      }
    -      blocks[0] = this.block;
    -      blocks[16] = blocks[1] = blocks[2] = blocks[3] = blocks[4] = blocks[5] = blocks[6] = blocks[7] = blocks[8] = blocks[9] = blocks[10] = blocks[11] = blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0;
    -    }
    -    blocks[14] = this.hBytes << 3 | this.bytes >>> 29;
    -    blocks[15] = this.bytes << 3;
    -    this.hash();
    -  };
    -  Sha256.prototype.hash = function () {
    -    var a = this.h0,
    -      b = this.h1,
    -      c = this.h2,
    -      d = this.h3,
    -      e = this.h4,
    -      f = this.h5,
    -      g = this.h6,
    -      h = this.h7,
    -      blocks = this.blocks,
    -      j,
    -      s0,
    -      s1,
    -      maj,
    -      t1,
    -      t2,
    -      ch,
    -      ab,
    -      da,
    -      cd,
    -      bc;
    -    for (j = 16; j < 64; ++j) {
    -      // rightrotate
    -      t1 = blocks[j - 15];
    -      s0 = (t1 >>> 7 | t1 << 25) ^ (t1 >>> 18 | t1 << 14) ^ t1 >>> 3;
    -      t1 = blocks[j - 2];
    -      s1 = (t1 >>> 17 | t1 << 15) ^ (t1 >>> 19 | t1 << 13) ^ t1 >>> 10;
    -      blocks[j] = blocks[j - 16] + s0 + blocks[j - 7] + s1 << 0;
    -    }
    -    bc = b & c;
    -    for (j = 0; j < 64; j += 4) {
    -      if (this.first) {
    -        if (this.is224) {
    -          ab = 300032;
    -          t1 = blocks[0] - 1413257819;
    -          h = t1 - 150054599 << 0;
    -          d = t1 + 24177077 << 0;
    -        } else {
    -          ab = 704751109;
    -          t1 = blocks[0] - 210244248;
    -          h = t1 - 1521486534 << 0;
    -          d = t1 + 143694565 << 0;
    -        }
    -        this.first = false;
    -      } else {
    -        s0 = (a >>> 2 | a << 30) ^ (a >>> 13 | a << 19) ^ (a >>> 22 | a << 10);
    -        s1 = (e >>> 6 | e << 26) ^ (e >>> 11 | e << 21) ^ (e >>> 25 | e << 7);
    -        ab = a & b;
    -        maj = ab ^ a & c ^ bc;
    -        ch = e & f ^ ~e & g;
    -        t1 = h + s1 + ch + K[j] + blocks[j];
    -        t2 = s0 + maj;
    -        h = d + t1 << 0;
    -        d = t1 + t2 << 0;
    -      }
    -      s0 = (d >>> 2 | d << 30) ^ (d >>> 13 | d << 19) ^ (d >>> 22 | d << 10);
    -      s1 = (h >>> 6 | h << 26) ^ (h >>> 11 | h << 21) ^ (h >>> 25 | h << 7);
    -      da = d & a;
    -      maj = da ^ d & b ^ ab;
    -      ch = h & e ^ ~h & f;
    -      t1 = g + s1 + ch + K[j + 1] + blocks[j + 1];
    -      t2 = s0 + maj;
    -      g = c + t1 << 0;
    -      c = t1 + t2 << 0;
    -      s0 = (c >>> 2 | c << 30) ^ (c >>> 13 | c << 19) ^ (c >>> 22 | c << 10);
    -      s1 = (g >>> 6 | g << 26) ^ (g >>> 11 | g << 21) ^ (g >>> 25 | g << 7);
    -      cd = c & d;
    -      maj = cd ^ c & a ^ da;
    -      ch = g & h ^ ~g & e;
    -      t1 = f + s1 + ch + K[j + 2] + blocks[j + 2];
    -      t2 = s0 + maj;
    -      f = b + t1 << 0;
    -      b = t1 + t2 << 0;
    -      s0 = (b >>> 2 | b << 30) ^ (b >>> 13 | b << 19) ^ (b >>> 22 | b << 10);
    -      s1 = (f >>> 6 | f << 26) ^ (f >>> 11 | f << 21) ^ (f >>> 25 | f << 7);
    -      bc = b & c;
    -      maj = bc ^ b & d ^ cd;
    -      ch = f & g ^ ~f & h;
    -      t1 = e + s1 + ch + K[j + 3] + blocks[j + 3];
    -      t2 = s0 + maj;
    -      e = a + t1 << 0;
    -      a = t1 + t2 << 0;
    -    }
    -    this.h0 = this.h0 + a << 0;
    -    this.h1 = this.h1 + b << 0;
    -    this.h2 = this.h2 + c << 0;
    -    this.h3 = this.h3 + d << 0;
    -    this.h4 = this.h4 + e << 0;
    -    this.h5 = this.h5 + f << 0;
    -    this.h6 = this.h6 + g << 0;
    -    this.h7 = this.h7 + h << 0;
    -  };
    -  Sha256.prototype.hex = function () {
    -    this.finalize();
    -    var h0 = this.h0,
    -      h1 = this.h1,
    -      h2 = this.h2,
    -      h3 = this.h3,
    -      h4 = this.h4,
    -      h5 = this.h5,
    -      h6 = this.h6,
    -      h7 = this.h7;
    -    var hex = HEX_CHARS[h0 >> 28 & 0x0F] + HEX_CHARS[h0 >> 24 & 0x0F] + HEX_CHARS[h0 >> 20 & 0x0F] + HEX_CHARS[h0 >> 16 & 0x0F] + HEX_CHARS[h0 >> 12 & 0x0F] + HEX_CHARS[h0 >> 8 & 0x0F] + HEX_CHARS[h0 >> 4 & 0x0F] + HEX_CHARS[h0 & 0x0F] + HEX_CHARS[h1 >> 28 & 0x0F] + HEX_CHARS[h1 >> 24 & 0x0F] + HEX_CHARS[h1 >> 20 & 0x0F] + HEX_CHARS[h1 >> 16 & 0x0F] + HEX_CHARS[h1 >> 12 & 0x0F] + HEX_CHARS[h1 >> 8 & 0x0F] + HEX_CHARS[h1 >> 4 & 0x0F] + HEX_CHARS[h1 & 0x0F] + HEX_CHARS[h2 >> 28 & 0x0F] + HEX_CHARS[h2 >> 24 & 0x0F] + HEX_CHARS[h2 >> 20 & 0x0F] + HEX_CHARS[h2 >> 16 & 0x0F] + HEX_CHARS[h2 >> 12 & 0x0F] + HEX_CHARS[h2 >> 8 & 0x0F] + HEX_CHARS[h2 >> 4 & 0x0F] + HEX_CHARS[h2 & 0x0F] + HEX_CHARS[h3 >> 28 & 0x0F] + HEX_CHARS[h3 >> 24 & 0x0F] + HEX_CHARS[h3 >> 20 & 0x0F] + HEX_CHARS[h3 >> 16 & 0x0F] + HEX_CHARS[h3 >> 12 & 0x0F] + HEX_CHARS[h3 >> 8 & 0x0F] + HEX_CHARS[h3 >> 4 & 0x0F] + HEX_CHARS[h3 & 0x0F] + HEX_CHARS[h4 >> 28 & 0x0F] + HEX_CHARS[h4 >> 24 & 0x0F] + HEX_CHARS[h4 >> 20 & 0x0F] + HEX_CHARS[h4 >> 16 & 0x0F] + HEX_CHARS[h4 >> 12 & 0x0F] + HEX_CHARS[h4 >> 8 & 0x0F] + HEX_CHARS[h4 >> 4 & 0x0F] + HEX_CHARS[h4 & 0x0F] + HEX_CHARS[h5 >> 28 & 0x0F] + HEX_CHARS[h5 >> 24 & 0x0F] + HEX_CHARS[h5 >> 20 & 0x0F] + HEX_CHARS[h5 >> 16 & 0x0F] + HEX_CHARS[h5 >> 12 & 0x0F] + HEX_CHARS[h5 >> 8 & 0x0F] + HEX_CHARS[h5 >> 4 & 0x0F] + HEX_CHARS[h5 & 0x0F] + HEX_CHARS[h6 >> 28 & 0x0F] + HEX_CHARS[h6 >> 24 & 0x0F] + HEX_CHARS[h6 >> 20 & 0x0F] + HEX_CHARS[h6 >> 16 & 0x0F] + HEX_CHARS[h6 >> 12 & 0x0F] + HEX_CHARS[h6 >> 8 & 0x0F] + HEX_CHARS[h6 >> 4 & 0x0F] + HEX_CHARS[h6 & 0x0F];
    -    if (!this.is224) {
    -      hex += HEX_CHARS[h7 >> 28 & 0x0F] + HEX_CHARS[h7 >> 24 & 0x0F] + HEX_CHARS[h7 >> 20 & 0x0F] + HEX_CHARS[h7 >> 16 & 0x0F] + HEX_CHARS[h7 >> 12 & 0x0F] + HEX_CHARS[h7 >> 8 & 0x0F] + HEX_CHARS[h7 >> 4 & 0x0F] + HEX_CHARS[h7 & 0x0F];
    -    }
    -    return hex;
    -  };
    -  Sha256.prototype.toString = Sha256.prototype.hex;
    -  Sha256.prototype.digest = function () {
    -    this.finalize();
    -    var h0 = this.h0,
    -      h1 = this.h1,
    -      h2 = this.h2,
    -      h3 = this.h3,
    -      h4 = this.h4,
    -      h5 = this.h5,
    -      h6 = this.h6,
    -      h7 = this.h7;
    -    var arr = [h0 >> 24 & 0xFF, h0 >> 16 & 0xFF, h0 >> 8 & 0xFF, h0 & 0xFF, h1 >> 24 & 0xFF, h1 >> 16 & 0xFF, h1 >> 8 & 0xFF, h1 & 0xFF, h2 >> 24 & 0xFF, h2 >> 16 & 0xFF, h2 >> 8 & 0xFF, h2 & 0xFF, h3 >> 24 & 0xFF, h3 >> 16 & 0xFF, h3 >> 8 & 0xFF, h3 & 0xFF, h4 >> 24 & 0xFF, h4 >> 16 & 0xFF, h4 >> 8 & 0xFF, h4 & 0xFF, h5 >> 24 & 0xFF, h5 >> 16 & 0xFF, h5 >> 8 & 0xFF, h5 & 0xFF, h6 >> 24 & 0xFF, h6 >> 16 & 0xFF, h6 >> 8 & 0xFF, h6 & 0xFF];
    -    if (!this.is224) {
    -      arr.push(h7 >> 24 & 0xFF, h7 >> 16 & 0xFF, h7 >> 8 & 0xFF, h7 & 0xFF);
    -    }
    -    return arr;
    -  };
    -  Sha256.prototype.array = Sha256.prototype.digest;
    -  Sha256.prototype.arrayBuffer = function () {
    -    this.finalize();
    -    var buffer = new ArrayBuffer(this.is224 ? 28 : 32);
    -    var dataView = new DataView(buffer);
    -    dataView.setUint32(0, this.h0);
    -    dataView.setUint32(4, this.h1);
    -    dataView.setUint32(8, this.h2);
    -    dataView.setUint32(12, this.h3);
    -    dataView.setUint32(16, this.h4);
    -    dataView.setUint32(20, this.h5);
    -    dataView.setUint32(24, this.h6);
    -    if (!this.is224) {
    -      dataView.setUint32(28, this.h7);
    -    }
    -    return buffer;
    -  };
    -  function HmacSha256(key, is224, sharedMemory) {
    -    var i,
    -      type = typeof key;
    -    if (type === 'string') {
    -      var bytes = [],
    -        length = key.length,
    -        index = 0,
    -        code;
    -      for (i = 0; i < length; ++i) {
    -        code = key.charCodeAt(i);
    -        if (code < 0x80) {
    -          bytes[index++] = code;
    -        } else if (code < 0x800) {
    -          bytes[index++] = 0xc0 | code >> 6;
    -          bytes[index++] = 0x80 | code & 0x3f;
    -        } else if (code < 0xd800 || code >= 0xe000) {
    -          bytes[index++] = 0xe0 | code >> 12;
    -          bytes[index++] = 0x80 | code >> 6 & 0x3f;
    -          bytes[index++] = 0x80 | code & 0x3f;
    -        } else {
    -          code = 0x10000 + ((code & 0x3ff) << 10 | key.charCodeAt(++i) & 0x3ff);
    -          bytes[index++] = 0xf0 | code >> 18;
    -          bytes[index++] = 0x80 | code >> 12 & 0x3f;
    -          bytes[index++] = 0x80 | code >> 6 & 0x3f;
    -          bytes[index++] = 0x80 | code & 0x3f;
    -        }
    -      }
    -      key = bytes;
    -    } else {
    -      if (type === 'object') {
    -        if (key === null) {
    -          throw new Error(ERROR);
    -        } else if (ARRAY_BUFFER && key.constructor === ArrayBuffer) {
    -          key = new Uint8Array(key);
    -        } else if (!Array.isArray(key)) {
    -          if (!ARRAY_BUFFER || !ArrayBuffer.isView(key)) {
    -            throw new Error(ERROR);
    -          }
    -        }
    -      } else {
    -        throw new Error(ERROR);
    -      }
    -    }
    -    if (key.length > 64) {
    -      key = new Sha256(is224, true).update(key).array();
    -    }
    -    var oKeyPad = [],
    -      iKeyPad = [];
    -    for (i = 0; i < 64; ++i) {
    -      var b = key[i] || 0;
    -      oKeyPad[i] = 0x5c ^ b;
    -      iKeyPad[i] = 0x36 ^ b;
    -    }
    -    Sha256.call(this, is224, sharedMemory);
    -    this.update(iKeyPad);
    -    this.oKeyPad = oKeyPad;
    -    this.inner = true;
    -    this.sharedMemory = sharedMemory;
    -  }
    -  HmacSha256.prototype = new Sha256();
    -  HmacSha256.prototype.finalize = function () {
    -    Sha256.prototype.finalize.call(this);
    -    if (this.inner) {
    -      this.inner = false;
    -      var innerHash = this.array();
    -      Sha256.call(this, this.is224, this.sharedMemory);
    -      this.update(this.oKeyPad);
    -      this.update(innerHash);
    -      Sha256.prototype.finalize.call(this);
    -    }
    -  };
    -  var exports = createMethod();
    -  exports.sha256 = exports;
    -  exports.sha224 = createMethod(true);
    -  exports.sha256.hmac = createHmacMethod();
    -  exports.sha224.hmac = createHmacMethod(true);
    -  if (COMMON_JS) {
    -    module.exports = exports;
    -  } else {
    -    root.sha256 = exports.sha256;
    -    root.sha224 = exports.sha224;
    -    if (AMD) {
    -      !(__WEBPACK_AMD_DEFINE_RESULT__ = (function () {
    -        return exports;
    -      }).call(exports, __webpack_require__, exports, module),
    -		__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
    -    }
    -  }
    -})();
    -
    -/***/ }),
    -
    -/***/ 763:
    -/***/ (function(module, exports, __webpack_require__) {
    -
    -/* module decorator */ module = __webpack_require__.nmd(module);
    -var __WEBPACK_AMD_DEFINE_RESULT__;/**
    - * @license
    - * Lodash 
    - * Copyright OpenJS Foundation and other contributors 
    - * Released under MIT license 
    - * Based on Underscore.js 1.8.3 
    - * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
    - */
    -;
    -(function () {
    -  /** Used as a safe reference for `undefined` in pre-ES5 environments. */
    -  var undefined;
    -
    -  /** Used as the semantic version number. */
    -  var VERSION = '4.17.21';
    -
    -  /** Used as the size to enable large array optimizations. */
    -  var LARGE_ARRAY_SIZE = 200;
    -
    -  /** Error message constants. */
    -  var CORE_ERROR_TEXT = 'Unsupported core-js use. Try https://npms.io/search?q=ponyfill.',
    -    FUNC_ERROR_TEXT = 'Expected a function',
    -    INVALID_TEMPL_VAR_ERROR_TEXT = 'Invalid `variable` option passed into `_.template`';
    -
    -  /** Used to stand-in for `undefined` hash values. */
    -  var HASH_UNDEFINED = '__lodash_hash_undefined__';
    -
    -  /** Used as the maximum memoize cache size. */
    -  var MAX_MEMOIZE_SIZE = 500;
    -
    -  /** Used as the internal argument placeholder. */
    -  var PLACEHOLDER = '__lodash_placeholder__';
    -
    -  /** Used to compose bitmasks for cloning. */
    -  var CLONE_DEEP_FLAG = 1,
    -    CLONE_FLAT_FLAG = 2,
    -    CLONE_SYMBOLS_FLAG = 4;
    -
    -  /** Used to compose bitmasks for value comparisons. */
    -  var COMPARE_PARTIAL_FLAG = 1,
    -    COMPARE_UNORDERED_FLAG = 2;
    -
    -  /** Used to compose bitmasks for function metadata. */
    -  var WRAP_BIND_FLAG = 1,
    -    WRAP_BIND_KEY_FLAG = 2,
    -    WRAP_CURRY_BOUND_FLAG = 4,
    -    WRAP_CURRY_FLAG = 8,
    -    WRAP_CURRY_RIGHT_FLAG = 16,
    -    WRAP_PARTIAL_FLAG = 32,
    -    WRAP_PARTIAL_RIGHT_FLAG = 64,
    -    WRAP_ARY_FLAG = 128,
    -    WRAP_REARG_FLAG = 256,
    -    WRAP_FLIP_FLAG = 512;
    -
    -  /** Used as default options for `_.truncate`. */
    -  var DEFAULT_TRUNC_LENGTH = 30,
    -    DEFAULT_TRUNC_OMISSION = '...';
    -
    -  /** Used to detect hot functions by number of calls within a span of milliseconds. */
    -  var HOT_COUNT = 800,
    -    HOT_SPAN = 16;
    -
    -  /** Used to indicate the type of lazy iteratees. */
    -  var LAZY_FILTER_FLAG = 1,
    -    LAZY_MAP_FLAG = 2,
    -    LAZY_WHILE_FLAG = 3;
    -
    -  /** Used as references for various `Number` constants. */
    -  var INFINITY = 1 / 0,
    -    MAX_SAFE_INTEGER = 9007199254740991,
    -    MAX_INTEGER = 1.7976931348623157e+308,
    -    NAN = 0 / 0;
    -
    -  /** Used as references for the maximum length and index of an array. */
    -  var MAX_ARRAY_LENGTH = 4294967295,
    -    MAX_ARRAY_INDEX = MAX_ARRAY_LENGTH - 1,
    -    HALF_MAX_ARRAY_LENGTH = MAX_ARRAY_LENGTH >>> 1;
    -
    -  /** Used to associate wrap methods with their bit flags. */
    -  var wrapFlags = [['ary', WRAP_ARY_FLAG], ['bind', WRAP_BIND_FLAG], ['bindKey', WRAP_BIND_KEY_FLAG], ['curry', WRAP_CURRY_FLAG], ['curryRight', WRAP_CURRY_RIGHT_FLAG], ['flip', WRAP_FLIP_FLAG], ['partial', WRAP_PARTIAL_FLAG], ['partialRight', WRAP_PARTIAL_RIGHT_FLAG], ['rearg', WRAP_REARG_FLAG]];
    -
    -  /** `Object#toString` result references. */
    -  var argsTag = '[object Arguments]',
    -    arrayTag = '[object Array]',
    -    asyncTag = '[object AsyncFunction]',
    -    boolTag = '[object Boolean]',
    -    dateTag = '[object Date]',
    -    domExcTag = '[object DOMException]',
    -    errorTag = '[object Error]',
    -    funcTag = '[object Function]',
    -    genTag = '[object GeneratorFunction]',
    -    mapTag = '[object Map]',
    -    numberTag = '[object Number]',
    -    nullTag = '[object Null]',
    -    objectTag = '[object Object]',
    -    promiseTag = '[object Promise]',
    -    proxyTag = '[object Proxy]',
    -    regexpTag = '[object RegExp]',
    -    setTag = '[object Set]',
    -    stringTag = '[object String]',
    -    symbolTag = '[object Symbol]',
    -    undefinedTag = '[object Undefined]',
    -    weakMapTag = '[object WeakMap]',
    -    weakSetTag = '[object WeakSet]';
    -  var arrayBufferTag = '[object ArrayBuffer]',
    -    dataViewTag = '[object DataView]',
    -    float32Tag = '[object Float32Array]',
    -    float64Tag = '[object Float64Array]',
    -    int8Tag = '[object Int8Array]',
    -    int16Tag = '[object Int16Array]',
    -    int32Tag = '[object Int32Array]',
    -    uint8Tag = '[object Uint8Array]',
    -    uint8ClampedTag = '[object Uint8ClampedArray]',
    -    uint16Tag = '[object Uint16Array]',
    -    uint32Tag = '[object Uint32Array]';
    -
    -  /** Used to match empty string literals in compiled template source. */
    -  var reEmptyStringLeading = /\b__p \+= '';/g,
    -    reEmptyStringMiddle = /\b(__p \+=) '' \+/g,
    -    reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g;
    -
    -  /** Used to match HTML entities and HTML characters. */
    -  var reEscapedHtml = /&(?:amp|lt|gt|quot|#39);/g,
    -    reUnescapedHtml = /[&<>"']/g,
    -    reHasEscapedHtml = RegExp(reEscapedHtml.source),
    -    reHasUnescapedHtml = RegExp(reUnescapedHtml.source);
    -
    -  /** Used to match template delimiters. */
    -  var reEscape = /<%-([\s\S]+?)%>/g,
    -    reEvaluate = /<%([\s\S]+?)%>/g,
    -    reInterpolate = /<%=([\s\S]+?)%>/g;
    -
    -  /** Used to match property names within property paths. */
    -  var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,
    -    reIsPlainProp = /^\w*$/,
    -    rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g;
    -
    -  /**
    -   * Used to match `RegExp`
    -   * [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns).
    -   */
    -  var reRegExpChar = /[\\^$.*+?()[\]{}|]/g,
    -    reHasRegExpChar = RegExp(reRegExpChar.source);
    -
    -  /** Used to match leading whitespace. */
    -  var reTrimStart = /^\s+/;
    -
    -  /** Used to match a single whitespace character. */
    -  var reWhitespace = /\s/;
    -
    -  /** Used to match wrap detail comments. */
    -  var reWrapComment = /\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/,
    -    reWrapDetails = /\{\n\/\* \[wrapped with (.+)\] \*/,
    -    reSplitDetails = /,? & /;
    -
    -  /** Used to match words composed of alphanumeric characters. */
    -  var reAsciiWord = /[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g;
    -
    -  /**
    -   * Used to validate the `validate` option in `_.template` variable.
    -   *
    -   * Forbids characters which could potentially change the meaning of the function argument definition:
    -   * - "()," (modification of function parameters)
    -   * - "=" (default value)
    -   * - "[]{}" (destructuring of function parameters)
    -   * - "/" (beginning of a comment)
    -   * - whitespace
    -   */
    -  var reForbiddenIdentifierChars = /[()=,{}\[\]\/\s]/;
    -
    -  /** Used to match backslashes in property paths. */
    -  var reEscapeChar = /\\(\\)?/g;
    -
    -  /**
    -   * Used to match
    -   * [ES template delimiters](http://ecma-international.org/ecma-262/7.0/#sec-template-literal-lexical-components).
    -   */
    -  var reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g;
    -
    -  /** Used to match `RegExp` flags from their coerced string values. */
    -  var reFlags = /\w*$/;
    -
    -  /** Used to detect bad signed hexadecimal string values. */
    -  var reIsBadHex = /^[-+]0x[0-9a-f]+$/i;
    -
    -  /** Used to detect binary string values. */
    -  var reIsBinary = /^0b[01]+$/i;
    -
    -  /** Used to detect host constructors (Safari). */
    -  var reIsHostCtor = /^\[object .+?Constructor\]$/;
    -
    -  /** Used to detect octal string values. */
    -  var reIsOctal = /^0o[0-7]+$/i;
    -
    -  /** Used to detect unsigned integer values. */
    -  var reIsUint = /^(?:0|[1-9]\d*)$/;
    -
    -  /** Used to match Latin Unicode letters (excluding mathematical operators). */
    -  var reLatin = /[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g;
    -
    -  /** Used to ensure capturing order of template delimiters. */
    -  var reNoMatch = /($^)/;
    -
    -  /** Used to match unescaped characters in compiled string literals. */
    -  var reUnescapedString = /['\n\r\u2028\u2029\\]/g;
    -
    -  /** Used to compose unicode character classes. */
    -  var rsAstralRange = "\\ud800-\\udfff",
    -    rsComboMarksRange = "\\u0300-\\u036f",
    -    reComboHalfMarksRange = "\\ufe20-\\ufe2f",
    -    rsComboSymbolsRange = "\\u20d0-\\u20ff",
    -    rsComboRange = rsComboMarksRange + reComboHalfMarksRange + rsComboSymbolsRange,
    -    rsDingbatRange = "\\u2700-\\u27bf",
    -    rsLowerRange = 'a-z\\xdf-\\xf6\\xf8-\\xff',
    -    rsMathOpRange = '\\xac\\xb1\\xd7\\xf7',
    -    rsNonCharRange = '\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf',
    -    rsPunctuationRange = "\\u2000-\\u206f",
    -    rsSpaceRange = " \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000",
    -    rsUpperRange = 'A-Z\\xc0-\\xd6\\xd8-\\xde',
    -    rsVarRange = "\\ufe0e\\ufe0f",
    -    rsBreakRange = rsMathOpRange + rsNonCharRange + rsPunctuationRange + rsSpaceRange;
    -
    -  /** Used to compose unicode capture groups. */
    -  var rsApos = "['\u2019]",
    -    rsAstral = '[' + rsAstralRange + ']',
    -    rsBreak = '[' + rsBreakRange + ']',
    -    rsCombo = '[' + rsComboRange + ']',
    -    rsDigits = '\\d+',
    -    rsDingbat = '[' + rsDingbatRange + ']',
    -    rsLower = '[' + rsLowerRange + ']',
    -    rsMisc = '[^' + rsAstralRange + rsBreakRange + rsDigits + rsDingbatRange + rsLowerRange + rsUpperRange + ']',
    -    rsFitz = "\\ud83c[\\udffb-\\udfff]",
    -    rsModifier = '(?:' + rsCombo + '|' + rsFitz + ')',
    -    rsNonAstral = '[^' + rsAstralRange + ']',
    -    rsRegional = "(?:\\ud83c[\\udde6-\\uddff]){2}",
    -    rsSurrPair = "[\\ud800-\\udbff][\\udc00-\\udfff]",
    -    rsUpper = '[' + rsUpperRange + ']',
    -    rsZWJ = "\\u200d";
    -
    -  /** Used to compose unicode regexes. */
    -  var rsMiscLower = '(?:' + rsLower + '|' + rsMisc + ')',
    -    rsMiscUpper = '(?:' + rsUpper + '|' + rsMisc + ')',
    -    rsOptContrLower = '(?:' + rsApos + '(?:d|ll|m|re|s|t|ve))?',
    -    rsOptContrUpper = '(?:' + rsApos + '(?:D|LL|M|RE|S|T|VE))?',
    -    reOptMod = rsModifier + '?',
    -    rsOptVar = '[' + rsVarRange + ']?',
    -    rsOptJoin = '(?:' + rsZWJ + '(?:' + [rsNonAstral, rsRegional, rsSurrPair].join('|') + ')' + rsOptVar + reOptMod + ')*',
    -    rsOrdLower = '\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])',
    -    rsOrdUpper = '\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])',
    -    rsSeq = rsOptVar + reOptMod + rsOptJoin,
    -    rsEmoji = '(?:' + [rsDingbat, rsRegional, rsSurrPair].join('|') + ')' + rsSeq,
    -    rsSymbol = '(?:' + [rsNonAstral + rsCombo + '?', rsCombo, rsRegional, rsSurrPair, rsAstral].join('|') + ')';
    -
    -  /** Used to match apostrophes. */
    -  var reApos = RegExp(rsApos, 'g');
    -
    -  /**
    -   * Used to match [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks) and
    -   * [combining diacritical marks for symbols](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks_for_Symbols).
    -   */
    -  var reComboMark = RegExp(rsCombo, 'g');
    -
    -  /** Used to match [string symbols](https://mathiasbynens.be/notes/javascript-unicode). */
    -  var reUnicode = RegExp(rsFitz + '(?=' + rsFitz + ')|' + rsSymbol + rsSeq, 'g');
    -
    -  /** Used to match complex or compound words. */
    -  var reUnicodeWord = RegExp([rsUpper + '?' + rsLower + '+' + rsOptContrLower + '(?=' + [rsBreak, rsUpper, '$'].join('|') + ')', rsMiscUpper + '+' + rsOptContrUpper + '(?=' + [rsBreak, rsUpper + rsMiscLower, '$'].join('|') + ')', rsUpper + '?' + rsMiscLower + '+' + rsOptContrLower, rsUpper + '+' + rsOptContrUpper, rsOrdUpper, rsOrdLower, rsDigits, rsEmoji].join('|'), 'g');
    -
    -  /** Used to detect strings with [zero-width joiners or code points from the astral planes](http://eev.ee/blog/2015/09/12/dark-corners-of-unicode/). */
    -  var reHasUnicode = RegExp('[' + rsZWJ + rsAstralRange + rsComboRange + rsVarRange + ']');
    -
    -  /** Used to detect strings that need a more robust regexp to match words. */
    -  var reHasUnicodeWord = /[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/;
    -
    -  /** Used to assign default `context` object properties. */
    -  var contextProps = ['Array', 'Buffer', 'DataView', 'Date', 'Error', 'Float32Array', 'Float64Array', 'Function', 'Int8Array', 'Int16Array', 'Int32Array', 'Map', 'Math', 'Object', 'Promise', 'RegExp', 'Set', 'String', 'Symbol', 'TypeError', 'Uint8Array', 'Uint8ClampedArray', 'Uint16Array', 'Uint32Array', 'WeakMap', '_', 'clearTimeout', 'isFinite', 'parseInt', 'setTimeout'];
    -
    -  /** Used to make template sourceURLs easier to identify. */
    -  var templateCounter = -1;
    -
    -  /** Used to identify `toStringTag` values of typed arrays. */
    -  var typedArrayTags = {};
    -  typedArrayTags[float32Tag] = typedArrayTags[float64Tag] = typedArrayTags[int8Tag] = typedArrayTags[int16Tag] = typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] = typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] = typedArrayTags[uint32Tag] = true;
    -  typedArrayTags[argsTag] = typedArrayTags[arrayTag] = typedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] = typedArrayTags[dataViewTag] = typedArrayTags[dateTag] = typedArrayTags[errorTag] = typedArrayTags[funcTag] = typedArrayTags[mapTag] = typedArrayTags[numberTag] = typedArrayTags[objectTag] = typedArrayTags[regexpTag] = typedArrayTags[setTag] = typedArrayTags[stringTag] = typedArrayTags[weakMapTag] = false;
    -
    -  /** Used to identify `toStringTag` values supported by `_.clone`. */
    -  var cloneableTags = {};
    -  cloneableTags[argsTag] = cloneableTags[arrayTag] = cloneableTags[arrayBufferTag] = cloneableTags[dataViewTag] = cloneableTags[boolTag] = cloneableTags[dateTag] = cloneableTags[float32Tag] = cloneableTags[float64Tag] = cloneableTags[int8Tag] = cloneableTags[int16Tag] = cloneableTags[int32Tag] = cloneableTags[mapTag] = cloneableTags[numberTag] = cloneableTags[objectTag] = cloneableTags[regexpTag] = cloneableTags[setTag] = cloneableTags[stringTag] = cloneableTags[symbolTag] = cloneableTags[uint8Tag] = cloneableTags[uint8ClampedTag] = cloneableTags[uint16Tag] = cloneableTags[uint32Tag] = true;
    -  cloneableTags[errorTag] = cloneableTags[funcTag] = cloneableTags[weakMapTag] = false;
    -
    -  /** Used to map Latin Unicode letters to basic Latin letters. */
    -  var deburredLetters = {
    -    // Latin-1 Supplement block.
    -    '\xc0': 'A',
    -    '\xc1': 'A',
    -    '\xc2': 'A',
    -    '\xc3': 'A',
    -    '\xc4': 'A',
    -    '\xc5': 'A',
    -    '\xe0': 'a',
    -    '\xe1': 'a',
    -    '\xe2': 'a',
    -    '\xe3': 'a',
    -    '\xe4': 'a',
    -    '\xe5': 'a',
    -    '\xc7': 'C',
    -    '\xe7': 'c',
    -    '\xd0': 'D',
    -    '\xf0': 'd',
    -    '\xc8': 'E',
    -    '\xc9': 'E',
    -    '\xca': 'E',
    -    '\xcb': 'E',
    -    '\xe8': 'e',
    -    '\xe9': 'e',
    -    '\xea': 'e',
    -    '\xeb': 'e',
    -    '\xcc': 'I',
    -    '\xcd': 'I',
    -    '\xce': 'I',
    -    '\xcf': 'I',
    -    '\xec': 'i',
    -    '\xed': 'i',
    -    '\xee': 'i',
    -    '\xef': 'i',
    -    '\xd1': 'N',
    -    '\xf1': 'n',
    -    '\xd2': 'O',
    -    '\xd3': 'O',
    -    '\xd4': 'O',
    -    '\xd5': 'O',
    -    '\xd6': 'O',
    -    '\xd8': 'O',
    -    '\xf2': 'o',
    -    '\xf3': 'o',
    -    '\xf4': 'o',
    -    '\xf5': 'o',
    -    '\xf6': 'o',
    -    '\xf8': 'o',
    -    '\xd9': 'U',
    -    '\xda': 'U',
    -    '\xdb': 'U',
    -    '\xdc': 'U',
    -    '\xf9': 'u',
    -    '\xfa': 'u',
    -    '\xfb': 'u',
    -    '\xfc': 'u',
    -    '\xdd': 'Y',
    -    '\xfd': 'y',
    -    '\xff': 'y',
    -    '\xc6': 'Ae',
    -    '\xe6': 'ae',
    -    '\xde': 'Th',
    -    '\xfe': 'th',
    -    '\xdf': 'ss',
    -    // Latin Extended-A block.
    -    "\u0100": 'A',
    -    "\u0102": 'A',
    -    "\u0104": 'A',
    -    "\u0101": 'a',
    -    "\u0103": 'a',
    -    "\u0105": 'a',
    -    "\u0106": 'C',
    -    "\u0108": 'C',
    -    "\u010A": 'C',
    -    "\u010C": 'C',
    -    "\u0107": 'c',
    -    "\u0109": 'c',
    -    "\u010B": 'c',
    -    "\u010D": 'c',
    -    "\u010E": 'D',
    -    "\u0110": 'D',
    -    "\u010F": 'd',
    -    "\u0111": 'd',
    -    "\u0112": 'E',
    -    "\u0114": 'E',
    -    "\u0116": 'E',
    -    "\u0118": 'E',
    -    "\u011A": 'E',
    -    "\u0113": 'e',
    -    "\u0115": 'e',
    -    "\u0117": 'e',
    -    "\u0119": 'e',
    -    "\u011B": 'e',
    -    "\u011C": 'G',
    -    "\u011E": 'G',
    -    "\u0120": 'G',
    -    "\u0122": 'G',
    -    "\u011D": 'g',
    -    "\u011F": 'g',
    -    "\u0121": 'g',
    -    "\u0123": 'g',
    -    "\u0124": 'H',
    -    "\u0126": 'H',
    -    "\u0125": 'h',
    -    "\u0127": 'h',
    -    "\u0128": 'I',
    -    "\u012A": 'I',
    -    "\u012C": 'I',
    -    "\u012E": 'I',
    -    "\u0130": 'I',
    -    "\u0129": 'i',
    -    "\u012B": 'i',
    -    "\u012D": 'i',
    -    "\u012F": 'i',
    -    "\u0131": 'i',
    -    "\u0134": 'J',
    -    "\u0135": 'j',
    -    "\u0136": 'K',
    -    "\u0137": 'k',
    -    "\u0138": 'k',
    -    "\u0139": 'L',
    -    "\u013B": 'L',
    -    "\u013D": 'L',
    -    "\u013F": 'L',
    -    "\u0141": 'L',
    -    "\u013A": 'l',
    -    "\u013C": 'l',
    -    "\u013E": 'l',
    -    "\u0140": 'l',
    -    "\u0142": 'l',
    -    "\u0143": 'N',
    -    "\u0145": 'N',
    -    "\u0147": 'N',
    -    "\u014A": 'N',
    -    "\u0144": 'n',
    -    "\u0146": 'n',
    -    "\u0148": 'n',
    -    "\u014B": 'n',
    -    "\u014C": 'O',
    -    "\u014E": 'O',
    -    "\u0150": 'O',
    -    "\u014D": 'o',
    -    "\u014F": 'o',
    -    "\u0151": 'o',
    -    "\u0154": 'R',
    -    "\u0156": 'R',
    -    "\u0158": 'R',
    -    "\u0155": 'r',
    -    "\u0157": 'r',
    -    "\u0159": 'r',
    -    "\u015A": 'S',
    -    "\u015C": 'S',
    -    "\u015E": 'S',
    -    "\u0160": 'S',
    -    "\u015B": 's',
    -    "\u015D": 's',
    -    "\u015F": 's',
    -    "\u0161": 's',
    -    "\u0162": 'T',
    -    "\u0164": 'T',
    -    "\u0166": 'T',
    -    "\u0163": 't',
    -    "\u0165": 't',
    -    "\u0167": 't',
    -    "\u0168": 'U',
    -    "\u016A": 'U',
    -    "\u016C": 'U',
    -    "\u016E": 'U',
    -    "\u0170": 'U',
    -    "\u0172": 'U',
    -    "\u0169": 'u',
    -    "\u016B": 'u',
    -    "\u016D": 'u',
    -    "\u016F": 'u',
    -    "\u0171": 'u',
    -    "\u0173": 'u',
    -    "\u0174": 'W',
    -    "\u0175": 'w',
    -    "\u0176": 'Y',
    -    "\u0177": 'y',
    -    "\u0178": 'Y',
    -    "\u0179": 'Z',
    -    "\u017B": 'Z',
    -    "\u017D": 'Z',
    -    "\u017A": 'z',
    -    "\u017C": 'z',
    -    "\u017E": 'z',
    -    "\u0132": 'IJ',
    -    "\u0133": 'ij',
    -    "\u0152": 'Oe',
    -    "\u0153": 'oe',
    -    "\u0149": "'n",
    -    "\u017F": 's'
    -  };
    -
    -  /** Used to map characters to HTML entities. */
    -  var htmlEscapes = {
    -    '&': '&',
    -    '<': '<',
    -    '>': '>',
    -    '"': '"',
    -    "'": '''
    -  };
    -
    -  /** Used to map HTML entities to characters. */
    -  var htmlUnescapes = {
    -    '&': '&',
    -    '<': '<',
    -    '>': '>',
    -    '"': '"',
    -    ''': "'"
    -  };
    -
    -  /** Used to escape characters for inclusion in compiled string literals. */
    -  var stringEscapes = {
    -    '\\': '\\',
    -    "'": "'",
    -    '\n': 'n',
    -    '\r': 'r',
    -    "\u2028": 'u2028',
    -    "\u2029": 'u2029'
    -  };
    -
    -  /** Built-in method references without a dependency on `root`. */
    -  var freeParseFloat = parseFloat,
    -    freeParseInt = parseInt;
    -
    -  /** Detect free variable `global` from Node.js. */
    -  var freeGlobal = typeof __webpack_require__.g == 'object' && __webpack_require__.g && __webpack_require__.g.Object === Object && __webpack_require__.g;
    -
    -  /** Detect free variable `self`. */
    -  var freeSelf = typeof self == 'object' && self && self.Object === Object && self;
    -
    -  /** Used as a reference to the global object. */
    -  var root = freeGlobal || freeSelf || Function('return this')();
    -
    -  /** Detect free variable `exports`. */
    -  var freeExports =  true && exports && !exports.nodeType && exports;
    -
    -  /** Detect free variable `module`. */
    -  var freeModule = freeExports && "object" == 'object' && module && !module.nodeType && module;
    -
    -  /** Detect the popular CommonJS extension `module.exports`. */
    -  var moduleExports = freeModule && freeModule.exports === freeExports;
    -
    -  /** Detect free variable `process` from Node.js. */
    -  var freeProcess = moduleExports && freeGlobal.process;
    -
    -  /** Used to access faster Node.js helpers. */
    -  var nodeUtil = function () {
    -    try {
    -      // Use `util.types` for Node.js 10+.
    -      var types = freeModule && freeModule.require && freeModule.require('util').types;
    -      if (types) {
    -        return types;
    -      }
    -
    -      // Legacy `process.binding('util')` for Node.js < 10.
    -      return freeProcess && freeProcess.binding && freeProcess.binding('util');
    -    } catch (e) {}
    -  }();
    -
    -  /* Node.js helper references. */
    -  var nodeIsArrayBuffer = nodeUtil && nodeUtil.isArrayBuffer,
    -    nodeIsDate = nodeUtil && nodeUtil.isDate,
    -    nodeIsMap = nodeUtil && nodeUtil.isMap,
    -    nodeIsRegExp = nodeUtil && nodeUtil.isRegExp,
    -    nodeIsSet = nodeUtil && nodeUtil.isSet,
    -    nodeIsTypedArray = nodeUtil && nodeUtil.isTypedArray;
    -
    -  /*--------------------------------------------------------------------------*/
    -
    -  /**
    -   * A faster alternative to `Function#apply`, this function invokes `func`
    -   * with the `this` binding of `thisArg` and the arguments of `args`.
    -   *
    -   * @private
    -   * @param {Function} func The function to invoke.
    -   * @param {*} thisArg The `this` binding of `func`.
    -   * @param {Array} args The arguments to invoke `func` with.
    -   * @returns {*} Returns the result of `func`.
    -   */
    -  function apply(func, thisArg, args) {
    -    switch (args.length) {
    -      case 0:
    -        return func.call(thisArg);
    -      case 1:
    -        return func.call(thisArg, args[0]);
    -      case 2:
    -        return func.call(thisArg, args[0], args[1]);
    -      case 3:
    -        return func.call(thisArg, args[0], args[1], args[2]);
    -    }
    -    return func.apply(thisArg, args);
    -  }
    -
    -  /**
    -   * A specialized version of `baseAggregator` for arrays.
    -   *
    -   * @private
    -   * @param {Array} [array] The array to iterate over.
    -   * @param {Function} setter The function to set `accumulator` values.
    -   * @param {Function} iteratee The iteratee to transform keys.
    -   * @param {Object} accumulator The initial aggregated object.
    -   * @returns {Function} Returns `accumulator`.
    -   */
    -  function arrayAggregator(array, setter, iteratee, accumulator) {
    -    var index = -1,
    -      length = array == null ? 0 : array.length;
    -    while (++index < length) {
    -      var value = array[index];
    -      setter(accumulator, value, iteratee(value), array);
    -    }
    -    return accumulator;
    -  }
    -
    -  /**
    -   * A specialized version of `_.forEach` for arrays without support for
    -   * iteratee shorthands.
    -   *
    -   * @private
    -   * @param {Array} [array] The array to iterate over.
    -   * @param {Function} iteratee The function invoked per iteration.
    -   * @returns {Array} Returns `array`.
    -   */
    -  function arrayEach(array, iteratee) {
    -    var index = -1,
    -      length = array == null ? 0 : array.length;
    -    while (++index < length) {
    -      if (iteratee(array[index], index, array) === false) {
    -        break;
    -      }
    -    }
    -    return array;
    -  }
    -
    -  /**
    -   * A specialized version of `_.forEachRight` for arrays without support for
    -   * iteratee shorthands.
    -   *
    -   * @private
    -   * @param {Array} [array] The array to iterate over.
    -   * @param {Function} iteratee The function invoked per iteration.
    -   * @returns {Array} Returns `array`.
    -   */
    -  function arrayEachRight(array, iteratee) {
    -    var length = array == null ? 0 : array.length;
    -    while (length--) {
    -      if (iteratee(array[length], length, array) === false) {
    -        break;
    -      }
    -    }
    -    return array;
    -  }
    -
    -  /**
    -   * A specialized version of `_.every` for arrays without support for
    -   * iteratee shorthands.
    -   *
    -   * @private
    -   * @param {Array} [array] The array to iterate over.
    -   * @param {Function} predicate The function invoked per iteration.
    -   * @returns {boolean} Returns `true` if all elements pass the predicate check,
    -   *  else `false`.
    -   */
    -  function arrayEvery(array, predicate) {
    -    var index = -1,
    -      length = array == null ? 0 : array.length;
    -    while (++index < length) {
    -      if (!predicate(array[index], index, array)) {
    -        return false;
    -      }
    -    }
    -    return true;
    -  }
    -
    -  /**
    -   * A specialized version of `_.filter` for arrays without support for
    -   * iteratee shorthands.
    -   *
    -   * @private
    -   * @param {Array} [array] The array to iterate over.
    -   * @param {Function} predicate The function invoked per iteration.
    -   * @returns {Array} Returns the new filtered array.
    -   */
    -  function arrayFilter(array, predicate) {
    -    var index = -1,
    -      length = array == null ? 0 : array.length,
    -      resIndex = 0,
    -      result = [];
    -    while (++index < length) {
    -      var value = array[index];
    -      if (predicate(value, index, array)) {
    -        result[resIndex++] = value;
    -      }
    -    }
    -    return result;
    -  }
    -
    -  /**
    -   * A specialized version of `_.includes` for arrays without support for
    -   * specifying an index to search from.
    -   *
    -   * @private
    -   * @param {Array} [array] The array to inspect.
    -   * @param {*} target The value to search for.
    -   * @returns {boolean} Returns `true` if `target` is found, else `false`.
    -   */
    -  function arrayIncludes(array, value) {
    -    var length = array == null ? 0 : array.length;
    -    return !!length && baseIndexOf(array, value, 0) > -1;
    -  }
    -
    -  /**
    -   * This function is like `arrayIncludes` except that it accepts a comparator.
    -   *
    -   * @private
    -   * @param {Array} [array] The array to inspect.
    -   * @param {*} target The value to search for.
    -   * @param {Function} comparator The comparator invoked per element.
    -   * @returns {boolean} Returns `true` if `target` is found, else `false`.
    -   */
    -  function arrayIncludesWith(array, value, comparator) {
    -    var index = -1,
    -      length = array == null ? 0 : array.length;
    -    while (++index < length) {
    -      if (comparator(value, array[index])) {
    -        return true;
    -      }
    -    }
    -    return false;
    -  }
    -
    -  /**
    -   * A specialized version of `_.map` for arrays without support for iteratee
    -   * shorthands.
    -   *
    -   * @private
    -   * @param {Array} [array] The array to iterate over.
    -   * @param {Function} iteratee The function invoked per iteration.
    -   * @returns {Array} Returns the new mapped array.
    -   */
    -  function arrayMap(array, iteratee) {
    -    var index = -1,
    -      length = array == null ? 0 : array.length,
    -      result = Array(length);
    -    while (++index < length) {
    -      result[index] = iteratee(array[index], index, array);
    -    }
    -    return result;
    -  }
    -
    -  /**
    -   * Appends the elements of `values` to `array`.
    -   *
    -   * @private
    -   * @param {Array} array The array to modify.
    -   * @param {Array} values The values to append.
    -   * @returns {Array} Returns `array`.
    -   */
    -  function arrayPush(array, values) {
    -    var index = -1,
    -      length = values.length,
    -      offset = array.length;
    -    while (++index < length) {
    -      array[offset + index] = values[index];
    -    }
    -    return array;
    -  }
    -
    -  /**
    -   * A specialized version of `_.reduce` for arrays without support for
    -   * iteratee shorthands.
    -   *
    -   * @private
    -   * @param {Array} [array] The array to iterate over.
    -   * @param {Function} iteratee The function invoked per iteration.
    -   * @param {*} [accumulator] The initial value.
    -   * @param {boolean} [initAccum] Specify using the first element of `array` as
    -   *  the initial value.
    -   * @returns {*} Returns the accumulated value.
    -   */
    -  function arrayReduce(array, iteratee, accumulator, initAccum) {
    -    var index = -1,
    -      length = array == null ? 0 : array.length;
    -    if (initAccum && length) {
    -      accumulator = array[++index];
    -    }
    -    while (++index < length) {
    -      accumulator = iteratee(accumulator, array[index], index, array);
    -    }
    -    return accumulator;
    -  }
    -
    -  /**
    -   * A specialized version of `_.reduceRight` for arrays without support for
    -   * iteratee shorthands.
    -   *
    -   * @private
    -   * @param {Array} [array] The array to iterate over.
    -   * @param {Function} iteratee The function invoked per iteration.
    -   * @param {*} [accumulator] The initial value.
    -   * @param {boolean} [initAccum] Specify using the last element of `array` as
    -   *  the initial value.
    -   * @returns {*} Returns the accumulated value.
    -   */
    -  function arrayReduceRight(array, iteratee, accumulator, initAccum) {
    -    var length = array == null ? 0 : array.length;
    -    if (initAccum && length) {
    -      accumulator = array[--length];
    -    }
    -    while (length--) {
    -      accumulator = iteratee(accumulator, array[length], length, array);
    -    }
    -    return accumulator;
    -  }
    -
    -  /**
    -   * A specialized version of `_.some` for arrays without support for iteratee
    -   * shorthands.
    -   *
    -   * @private
    -   * @param {Array} [array] The array to iterate over.
    -   * @param {Function} predicate The function invoked per iteration.
    -   * @returns {boolean} Returns `true` if any element passes the predicate check,
    -   *  else `false`.
    -   */
    -  function arraySome(array, predicate) {
    -    var index = -1,
    -      length = array == null ? 0 : array.length;
    -    while (++index < length) {
    -      if (predicate(array[index], index, array)) {
    -        return true;
    -      }
    -    }
    -    return false;
    -  }
    -
    -  /**
    -   * Gets the size of an ASCII `string`.
    -   *
    -   * @private
    -   * @param {string} string The string inspect.
    -   * @returns {number} Returns the string size.
    -   */
    -  var asciiSize = baseProperty('length');
    -
    -  /**
    -   * Converts an ASCII `string` to an array.
    -   *
    -   * @private
    -   * @param {string} string The string to convert.
    -   * @returns {Array} Returns the converted array.
    -   */
    -  function asciiToArray(string) {
    -    return string.split('');
    -  }
    -
    -  /**
    -   * Splits an ASCII `string` into an array of its words.
    -   *
    -   * @private
    -   * @param {string} The string to inspect.
    -   * @returns {Array} Returns the words of `string`.
    -   */
    -  function asciiWords(string) {
    -    return string.match(reAsciiWord) || [];
    -  }
    -
    -  /**
    -   * The base implementation of methods like `_.findKey` and `_.findLastKey`,
    -   * without support for iteratee shorthands, which iterates over `collection`
    -   * using `eachFunc`.
    -   *
    -   * @private
    -   * @param {Array|Object} collection The collection to inspect.
    -   * @param {Function} predicate The function invoked per iteration.
    -   * @param {Function} eachFunc The function to iterate over `collection`.
    -   * @returns {*} Returns the found element or its key, else `undefined`.
    -   */
    -  function baseFindKey(collection, predicate, eachFunc) {
    -    var result;
    -    eachFunc(collection, function (value, key, collection) {
    -      if (predicate(value, key, collection)) {
    -        result = key;
    -        return false;
    -      }
    -    });
    -    return result;
    -  }
    -
    -  /**
    -   * The base implementation of `_.findIndex` and `_.findLastIndex` without
    -   * support for iteratee shorthands.
    -   *
    -   * @private
    -   * @param {Array} array The array to inspect.
    -   * @param {Function} predicate The function invoked per iteration.
    -   * @param {number} fromIndex The index to search from.
    -   * @param {boolean} [fromRight] Specify iterating from right to left.
    -   * @returns {number} Returns the index of the matched value, else `-1`.
    -   */
    -  function baseFindIndex(array, predicate, fromIndex, fromRight) {
    -    var length = array.length,
    -      index = fromIndex + (fromRight ? 1 : -1);
    -    while (fromRight ? index-- : ++index < length) {
    -      if (predicate(array[index], index, array)) {
    -        return index;
    -      }
    -    }
    -    return -1;
    -  }
    -
    -  /**
    -   * The base implementation of `_.indexOf` without `fromIndex` bounds checks.
    -   *
    -   * @private
    -   * @param {Array} array The array to inspect.
    -   * @param {*} value The value to search for.
    -   * @param {number} fromIndex The index to search from.
    -   * @returns {number} Returns the index of the matched value, else `-1`.
    -   */
    -  function baseIndexOf(array, value, fromIndex) {
    -    return value === value ? strictIndexOf(array, value, fromIndex) : baseFindIndex(array, baseIsNaN, fromIndex);
    -  }
    -
    -  /**
    -   * This function is like `baseIndexOf` except that it accepts a comparator.
    -   *
    -   * @private
    -   * @param {Array} array The array to inspect.
    -   * @param {*} value The value to search for.
    -   * @param {number} fromIndex The index to search from.
    -   * @param {Function} comparator The comparator invoked per element.
    -   * @returns {number} Returns the index of the matched value, else `-1`.
    -   */
    -  function baseIndexOfWith(array, value, fromIndex, comparator) {
    -    var index = fromIndex - 1,
    -      length = array.length;
    -    while (++index < length) {
    -      if (comparator(array[index], value)) {
    -        return index;
    -      }
    -    }
    -    return -1;
    -  }
    -
    -  /**
    -   * The base implementation of `_.isNaN` without support for number objects.
    -   *
    -   * @private
    -   * @param {*} value The value to check.
    -   * @returns {boolean} Returns `true` if `value` is `NaN`, else `false`.
    -   */
    -  function baseIsNaN(value) {
    -    return value !== value;
    -  }
    -
    -  /**
    -   * The base implementation of `_.mean` and `_.meanBy` without support for
    -   * iteratee shorthands.
    -   *
    -   * @private
    -   * @param {Array} array The array to iterate over.
    -   * @param {Function} iteratee The function invoked per iteration.
    -   * @returns {number} Returns the mean.
    -   */
    -  function baseMean(array, iteratee) {
    -    var length = array == null ? 0 : array.length;
    -    return length ? baseSum(array, iteratee) / length : NAN;
    -  }
    -
    -  /**
    -   * The base implementation of `_.property` without support for deep paths.
    -   *
    -   * @private
    -   * @param {string} key The key of the property to get.
    -   * @returns {Function} Returns the new accessor function.
    -   */
    -  function baseProperty(key) {
    -    return function (object) {
    -      return object == null ? undefined : object[key];
    -    };
    -  }
    -
    -  /**
    -   * The base implementation of `_.propertyOf` without support for deep paths.
    -   *
    -   * @private
    -   * @param {Object} object The object to query.
    -   * @returns {Function} Returns the new accessor function.
    -   */
    -  function basePropertyOf(object) {
    -    return function (key) {
    -      return object == null ? undefined : object[key];
    -    };
    -  }
    -
    -  /**
    -   * The base implementation of `_.reduce` and `_.reduceRight`, without support
    -   * for iteratee shorthands, which iterates over `collection` using `eachFunc`.
    -   *
    -   * @private
    -   * @param {Array|Object} collection The collection to iterate over.
    -   * @param {Function} iteratee The function invoked per iteration.
    -   * @param {*} accumulator The initial value.
    -   * @param {boolean} initAccum Specify using the first or last element of
    -   *  `collection` as the initial value.
    -   * @param {Function} eachFunc The function to iterate over `collection`.
    -   * @returns {*} Returns the accumulated value.
    -   */
    -  function baseReduce(collection, iteratee, accumulator, initAccum, eachFunc) {
    -    eachFunc(collection, function (value, index, collection) {
    -      accumulator = initAccum ? (initAccum = false, value) : iteratee(accumulator, value, index, collection);
    -    });
    -    return accumulator;
    -  }
    -
    -  /**
    -   * The base implementation of `_.sortBy` which uses `comparer` to define the
    -   * sort order of `array` and replaces criteria objects with their corresponding
    -   * values.
    -   *
    -   * @private
    -   * @param {Array} array The array to sort.
    -   * @param {Function} comparer The function to define sort order.
    -   * @returns {Array} Returns `array`.
    -   */
    -  function baseSortBy(array, comparer) {
    -    var length = array.length;
    -    array.sort(comparer);
    -    while (length--) {
    -      array[length] = array[length].value;
    -    }
    -    return array;
    -  }
    -
    -  /**
    -   * The base implementation of `_.sum` and `_.sumBy` without support for
    -   * iteratee shorthands.
    -   *
    -   * @private
    -   * @param {Array} array The array to iterate over.
    -   * @param {Function} iteratee The function invoked per iteration.
    -   * @returns {number} Returns the sum.
    -   */
    -  function baseSum(array, iteratee) {
    -    var result,
    -      index = -1,
    -      length = array.length;
    -    while (++index < length) {
    -      var current = iteratee(array[index]);
    -      if (current !== undefined) {
    -        result = result === undefined ? current : result + current;
    -      }
    -    }
    -    return result;
    -  }
    -
    -  /**
    -   * The base implementation of `_.times` without support for iteratee shorthands
    -   * or max array length checks.
    -   *
    -   * @private
    -   * @param {number} n The number of times to invoke `iteratee`.
    -   * @param {Function} iteratee The function invoked per iteration.
    -   * @returns {Array} Returns the array of results.
    -   */
    -  function baseTimes(n, iteratee) {
    -    var index = -1,
    -      result = Array(n);
    -    while (++index < n) {
    -      result[index] = iteratee(index);
    -    }
    -    return result;
    -  }
    -
    -  /**
    -   * The base implementation of `_.toPairs` and `_.toPairsIn` which creates an array
    -   * of key-value pairs for `object` corresponding to the property names of `props`.
    -   *
    -   * @private
    -   * @param {Object} object The object to query.
    -   * @param {Array} props The property names to get values for.
    -   * @returns {Object} Returns the key-value pairs.
    -   */
    -  function baseToPairs(object, props) {
    -    return arrayMap(props, function (key) {
    -      return [key, object[key]];
    -    });
    -  }
    -
    -  /**
    -   * The base implementation of `_.trim`.
    -   *
    -   * @private
    -   * @param {string} string The string to trim.
    -   * @returns {string} Returns the trimmed string.
    -   */
    -  function baseTrim(string) {
    -    return string ? string.slice(0, trimmedEndIndex(string) + 1).replace(reTrimStart, '') : string;
    -  }
    -
    -  /**
    -   * The base implementation of `_.unary` without support for storing metadata.
    -   *
    -   * @private
    -   * @param {Function} func The function to cap arguments for.
    -   * @returns {Function} Returns the new capped function.
    -   */
    -  function baseUnary(func) {
    -    return function (value) {
    -      return func(value);
    -    };
    -  }
    -
    -  /**
    -   * The base implementation of `_.values` and `_.valuesIn` which creates an
    -   * array of `object` property values corresponding to the property names
    -   * of `props`.
    -   *
    -   * @private
    -   * @param {Object} object The object to query.
    -   * @param {Array} props The property names to get values for.
    -   * @returns {Object} Returns the array of property values.
    -   */
    -  function baseValues(object, props) {
    -    return arrayMap(props, function (key) {
    -      return object[key];
    -    });
    -  }
    -
    -  /**
    -   * Checks if a `cache` value for `key` exists.
    -   *
    -   * @private
    -   * @param {Object} cache The cache to query.
    -   * @param {string} key The key of the entry to check.
    -   * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
    -   */
    -  function cacheHas(cache, key) {
    -    return cache.has(key);
    -  }
    -
    -  /**
    -   * Used by `_.trim` and `_.trimStart` to get the index of the first string symbol
    -   * that is not found in the character symbols.
    -   *
    -   * @private
    -   * @param {Array} strSymbols The string symbols to inspect.
    -   * @param {Array} chrSymbols The character symbols to find.
    -   * @returns {number} Returns the index of the first unmatched string symbol.
    -   */
    -  function charsStartIndex(strSymbols, chrSymbols) {
    -    var index = -1,
    -      length = strSymbols.length;
    -    while (++index < length && baseIndexOf(chrSymbols, strSymbols[index], 0) > -1) {}
    -    return index;
    -  }
    -
    -  /**
    -   * Used by `_.trim` and `_.trimEnd` to get the index of the last string symbol
    -   * that is not found in the character symbols.
    -   *
    -   * @private
    -   * @param {Array} strSymbols The string symbols to inspect.
    -   * @param {Array} chrSymbols The character symbols to find.
    -   * @returns {number} Returns the index of the last unmatched string symbol.
    -   */
    -  function charsEndIndex(strSymbols, chrSymbols) {
    -    var index = strSymbols.length;
    -    while (index-- && baseIndexOf(chrSymbols, strSymbols[index], 0) > -1) {}
    -    return index;
    -  }
    -
    -  /**
    -   * Gets the number of `placeholder` occurrences in `array`.
    -   *
    -   * @private
    -   * @param {Array} array The array to inspect.
    -   * @param {*} placeholder The placeholder to search for.
    -   * @returns {number} Returns the placeholder count.
    -   */
    -  function countHolders(array, placeholder) {
    -    var length = array.length,
    -      result = 0;
    -    while (length--) {
    -      if (array[length] === placeholder) {
    -        ++result;
    -      }
    -    }
    -    return result;
    -  }
    -
    -  /**
    -   * Used by `_.deburr` to convert Latin-1 Supplement and Latin Extended-A
    -   * letters to basic Latin letters.
    -   *
    -   * @private
    -   * @param {string} letter The matched letter to deburr.
    -   * @returns {string} Returns the deburred letter.
    -   */
    -  var deburrLetter = basePropertyOf(deburredLetters);
    -
    -  /**
    -   * Used by `_.escape` to convert characters to HTML entities.
    -   *
    -   * @private
    -   * @param {string} chr The matched character to escape.
    -   * @returns {string} Returns the escaped character.
    -   */
    -  var escapeHtmlChar = basePropertyOf(htmlEscapes);
    -
    -  /**
    -   * Used by `_.template` to escape characters for inclusion in compiled string literals.
    -   *
    -   * @private
    -   * @param {string} chr The matched character to escape.
    -   * @returns {string} Returns the escaped character.
    -   */
    -  function escapeStringChar(chr) {
    -    return '\\' + stringEscapes[chr];
    -  }
    -
    -  /**
    -   * Gets the value at `key` of `object`.
    -   *
    -   * @private
    -   * @param {Object} [object] The object to query.
    -   * @param {string} key The key of the property to get.
    -   * @returns {*} Returns the property value.
    -   */
    -  function getValue(object, key) {
    -    return object == null ? undefined : object[key];
    -  }
    -
    -  /**
    -   * Checks if `string` contains Unicode symbols.
    -   *
    -   * @private
    -   * @param {string} string The string to inspect.
    -   * @returns {boolean} Returns `true` if a symbol is found, else `false`.
    -   */
    -  function hasUnicode(string) {
    -    return reHasUnicode.test(string);
    -  }
    -
    -  /**
    -   * Checks if `string` contains a word composed of Unicode symbols.
    -   *
    -   * @private
    -   * @param {string} string The string to inspect.
    -   * @returns {boolean} Returns `true` if a word is found, else `false`.
    -   */
    -  function hasUnicodeWord(string) {
    -    return reHasUnicodeWord.test(string);
    -  }
    -
    -  /**
    -   * Converts `iterator` to an array.
    -   *
    -   * @private
    -   * @param {Object} iterator The iterator to convert.
    -   * @returns {Array} Returns the converted array.
    -   */
    -  function iteratorToArray(iterator) {
    -    var data,
    -      result = [];
    -    while (!(data = iterator.next()).done) {
    -      result.push(data.value);
    -    }
    -    return result;
    -  }
    -
    -  /**
    -   * Converts `map` to its key-value pairs.
    -   *
    -   * @private
    -   * @param {Object} map The map to convert.
    -   * @returns {Array} Returns the key-value pairs.
    -   */
    -  function mapToArray(map) {
    -    var index = -1,
    -      result = Array(map.size);
    -    map.forEach(function (value, key) {
    -      result[++index] = [key, value];
    -    });
    -    return result;
    -  }
    -
    -  /**
    -   * Creates a unary function that invokes `func` with its argument transformed.
    -   *
    -   * @private
    -   * @param {Function} func The function to wrap.
    -   * @param {Function} transform The argument transform.
    -   * @returns {Function} Returns the new function.
    -   */
    -  function overArg(func, transform) {
    -    return function (arg) {
    -      return func(transform(arg));
    -    };
    -  }
    -
    -  /**
    -   * Replaces all `placeholder` elements in `array` with an internal placeholder
    -   * and returns an array of their indexes.
    -   *
    -   * @private
    -   * @param {Array} array The array to modify.
    -   * @param {*} placeholder The placeholder to replace.
    -   * @returns {Array} Returns the new array of placeholder indexes.
    -   */
    -  function replaceHolders(array, placeholder) {
    -    var index = -1,
    -      length = array.length,
    -      resIndex = 0,
    -      result = [];
    -    while (++index < length) {
    -      var value = array[index];
    -      if (value === placeholder || value === PLACEHOLDER) {
    -        array[index] = PLACEHOLDER;
    -        result[resIndex++] = index;
    -      }
    -    }
    -    return result;
    -  }
    -
    -  /**
    -   * Converts `set` to an array of its values.
    -   *
    -   * @private
    -   * @param {Object} set The set to convert.
    -   * @returns {Array} Returns the values.
    -   */
    -  function setToArray(set) {
    -    var index = -1,
    -      result = Array(set.size);
    -    set.forEach(function (value) {
    -      result[++index] = value;
    -    });
    -    return result;
    -  }
    -
    -  /**
    -   * Converts `set` to its value-value pairs.
    -   *
    -   * @private
    -   * @param {Object} set The set to convert.
    -   * @returns {Array} Returns the value-value pairs.
    -   */
    -  function setToPairs(set) {
    -    var index = -1,
    -      result = Array(set.size);
    -    set.forEach(function (value) {
    -      result[++index] = [value, value];
    -    });
    -    return result;
    -  }
    -
    -  /**
    -   * A specialized version of `_.indexOf` which performs strict equality
    -   * comparisons of values, i.e. `===`.
    -   *
    -   * @private
    -   * @param {Array} array The array to inspect.
    -   * @param {*} value The value to search for.
    -   * @param {number} fromIndex The index to search from.
    -   * @returns {number} Returns the index of the matched value, else `-1`.
    -   */
    -  function strictIndexOf(array, value, fromIndex) {
    -    var index = fromIndex - 1,
    -      length = array.length;
    -    while (++index < length) {
    -      if (array[index] === value) {
    -        return index;
    -      }
    -    }
    -    return -1;
    -  }
    -
    -  /**
    -   * A specialized version of `_.lastIndexOf` which performs strict equality
    -   * comparisons of values, i.e. `===`.
    -   *
    -   * @private
    -   * @param {Array} array The array to inspect.
    -   * @param {*} value The value to search for.
    -   * @param {number} fromIndex The index to search from.
    -   * @returns {number} Returns the index of the matched value, else `-1`.
    -   */
    -  function strictLastIndexOf(array, value, fromIndex) {
    -    var index = fromIndex + 1;
    -    while (index--) {
    -      if (array[index] === value) {
    -        return index;
    -      }
    -    }
    -    return index;
    -  }
    -
    -  /**
    -   * Gets the number of symbols in `string`.
    -   *
    -   * @private
    -   * @param {string} string The string to inspect.
    -   * @returns {number} Returns the string size.
    -   */
    -  function stringSize(string) {
    -    return hasUnicode(string) ? unicodeSize(string) : asciiSize(string);
    -  }
    -
    -  /**
    -   * Converts `string` to an array.
    -   *
    -   * @private
    -   * @param {string} string The string to convert.
    -   * @returns {Array} Returns the converted array.
    -   */
    -  function stringToArray(string) {
    -    return hasUnicode(string) ? unicodeToArray(string) : asciiToArray(string);
    -  }
    -
    -  /**
    -   * Used by `_.trim` and `_.trimEnd` to get the index of the last non-whitespace
    -   * character of `string`.
    -   *
    -   * @private
    -   * @param {string} string The string to inspect.
    -   * @returns {number} Returns the index of the last non-whitespace character.
    -   */
    -  function trimmedEndIndex(string) {
    -    var index = string.length;
    -    while (index-- && reWhitespace.test(string.charAt(index))) {}
    -    return index;
    -  }
    -
    -  /**
    -   * Used by `_.unescape` to convert HTML entities to characters.
    -   *
    -   * @private
    -   * @param {string} chr The matched character to unescape.
    -   * @returns {string} Returns the unescaped character.
    -   */
    -  var unescapeHtmlChar = basePropertyOf(htmlUnescapes);
    -
    -  /**
    -   * Gets the size of a Unicode `string`.
    -   *
    -   * @private
    -   * @param {string} string The string inspect.
    -   * @returns {number} Returns the string size.
    -   */
    -  function unicodeSize(string) {
    -    var result = reUnicode.lastIndex = 0;
    -    while (reUnicode.test(string)) {
    -      ++result;
    -    }
    -    return result;
    -  }
    -
    -  /**
    -   * Converts a Unicode `string` to an array.
    -   *
    -   * @private
    -   * @param {string} string The string to convert.
    -   * @returns {Array} Returns the converted array.
    -   */
    -  function unicodeToArray(string) {
    -    return string.match(reUnicode) || [];
    -  }
    -
    -  /**
    -   * Splits a Unicode `string` into an array of its words.
    -   *
    -   * @private
    -   * @param {string} The string to inspect.
    -   * @returns {Array} Returns the words of `string`.
    -   */
    -  function unicodeWords(string) {
    -    return string.match(reUnicodeWord) || [];
    -  }
    -
    -  /*--------------------------------------------------------------------------*/
    -
    -  /**
    -   * Create a new pristine `lodash` function using the `context` object.
    -   *
    -   * @static
    -   * @memberOf _
    -   * @since 1.1.0
    -   * @category Util
    -   * @param {Object} [context=root] The context object.
    -   * @returns {Function} Returns a new `lodash` function.
    -   * @example
    -   *
    -   * _.mixin({ 'foo': _.constant('foo') });
    -   *
    -   * var lodash = _.runInContext();
    -   * lodash.mixin({ 'bar': lodash.constant('bar') });
    -   *
    -   * _.isFunction(_.foo);
    -   * // => true
    -   * _.isFunction(_.bar);
    -   * // => false
    -   *
    -   * lodash.isFunction(lodash.foo);
    -   * // => false
    -   * lodash.isFunction(lodash.bar);
    -   * // => true
    -   *
    -   * // Create a suped-up `defer` in Node.js.
    -   * var defer = _.runInContext({ 'setTimeout': setImmediate }).defer;
    -   */
    -  var runInContext = function runInContext(context) {
    -    context = context == null ? root : _.defaults(root.Object(), context, _.pick(root, contextProps));
    -
    -    /** Built-in constructor references. */
    -    var Array = context.Array,
    -      Date = context.Date,
    -      Error = context.Error,
    -      Function = context.Function,
    -      Math = context.Math,
    -      Object = context.Object,
    -      RegExp = context.RegExp,
    -      String = context.String,
    -      TypeError = context.TypeError;
    -
    -    /** Used for built-in method references. */
    -    var arrayProto = Array.prototype,
    -      funcProto = Function.prototype,
    -      objectProto = Object.prototype;
    -
    -    /** Used to detect overreaching core-js shims. */
    -    var coreJsData = context['__core-js_shared__'];
    -
    -    /** Used to resolve the decompiled source of functions. */
    -    var funcToString = funcProto.toString;
    -
    -    /** Used to check objects for own properties. */
    -    var hasOwnProperty = objectProto.hasOwnProperty;
    -
    -    /** Used to generate unique IDs. */
    -    var idCounter = 0;
    -
    -    /** Used to detect methods masquerading as native. */
    -    var maskSrcKey = function () {
    -      var uid = /[^.]+$/.exec(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO || '');
    -      return uid ? 'Symbol(src)_1.' + uid : '';
    -    }();
    -
    -    /**
    -     * Used to resolve the
    -     * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
    -     * of values.
    -     */
    -    var nativeObjectToString = objectProto.toString;
    -
    -    /** Used to infer the `Object` constructor. */
    -    var objectCtorString = funcToString.call(Object);
    -
    -    /** Used to restore the original `_` reference in `_.noConflict`. */
    -    var oldDash = root._;
    -
    -    /** Used to detect if a method is native. */
    -    var reIsNative = RegExp('^' + funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\$&').replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$');
    -
    -    /** Built-in value references. */
    -    var Buffer = moduleExports ? context.Buffer : undefined,
    -      Symbol = context.Symbol,
    -      Uint8Array = context.Uint8Array,
    -      allocUnsafe = Buffer ? Buffer.allocUnsafe : undefined,
    -      getPrototype = overArg(Object.getPrototypeOf, Object),
    -      objectCreate = Object.create,
    -      propertyIsEnumerable = objectProto.propertyIsEnumerable,
    -      splice = arrayProto.splice,
    -      spreadableSymbol = Symbol ? Symbol.isConcatSpreadable : undefined,
    -      symIterator = Symbol ? Symbol.iterator : undefined,
    -      symToStringTag = Symbol ? Symbol.toStringTag : undefined;
    -    var defineProperty = function () {
    -      try {
    -        var func = getNative(Object, 'defineProperty');
    -        func({}, '', {});
    -        return func;
    -      } catch (e) {}
    -    }();
    -
    -    /** Mocked built-ins. */
    -    var ctxClearTimeout = context.clearTimeout !== root.clearTimeout && context.clearTimeout,
    -      ctxNow = Date && Date.now !== root.Date.now && Date.now,
    -      ctxSetTimeout = context.setTimeout !== root.setTimeout && context.setTimeout;
    -
    -    /* Built-in method references for those with the same name as other `lodash` methods. */
    -    var nativeCeil = Math.ceil,
    -      nativeFloor = Math.floor,
    -      nativeGetSymbols = Object.getOwnPropertySymbols,
    -      nativeIsBuffer = Buffer ? Buffer.isBuffer : undefined,
    -      nativeIsFinite = context.isFinite,
    -      nativeJoin = arrayProto.join,
    -      nativeKeys = overArg(Object.keys, Object),
    -      nativeMax = Math.max,
    -      nativeMin = Math.min,
    -      nativeNow = Date.now,
    -      nativeParseInt = context.parseInt,
    -      nativeRandom = Math.random,
    -      nativeReverse = arrayProto.reverse;
    -
    -    /* Built-in method references that are verified to be native. */
    -    var DataView = getNative(context, 'DataView'),
    -      Map = getNative(context, 'Map'),
    -      Promise = getNative(context, 'Promise'),
    -      Set = getNative(context, 'Set'),
    -      WeakMap = getNative(context, 'WeakMap'),
    -      nativeCreate = getNative(Object, 'create');
    -
    -    /** Used to store function metadata. */
    -    var metaMap = WeakMap && new WeakMap();
    -
    -    /** Used to lookup unminified function names. */
    -    var realNames = {};
    -
    -    /** Used to detect maps, sets, and weakmaps. */
    -    var dataViewCtorString = toSource(DataView),
    -      mapCtorString = toSource(Map),
    -      promiseCtorString = toSource(Promise),
    -      setCtorString = toSource(Set),
    -      weakMapCtorString = toSource(WeakMap);
    -
    -    /** Used to convert symbols to primitives and strings. */
    -    var symbolProto = Symbol ? Symbol.prototype : undefined,
    -      symbolValueOf = symbolProto ? symbolProto.valueOf : undefined,
    -      symbolToString = symbolProto ? symbolProto.toString : undefined;
    -
    -    /*------------------------------------------------------------------------*/
    -
    -    /**
    -     * Creates a `lodash` object which wraps `value` to enable implicit method
    -     * chain sequences. Methods that operate on and return arrays, collections,
    -     * and functions can be chained together. Methods that retrieve a single value
    -     * or may return a primitive value will automatically end the chain sequence
    -     * and return the unwrapped value. Otherwise, the value must be unwrapped
    -     * with `_#value`.
    -     *
    -     * Explicit chain sequences, which must be unwrapped with `_#value`, may be
    -     * enabled using `_.chain`.
    -     *
    -     * The execution of chained methods is lazy, that is, it's deferred until
    -     * `_#value` is implicitly or explicitly called.
    -     *
    -     * Lazy evaluation allows several methods to support shortcut fusion.
    -     * Shortcut fusion is an optimization to merge iteratee calls; this avoids
    -     * the creation of intermediate arrays and can greatly reduce the number of
    -     * iteratee executions. Sections of a chain sequence qualify for shortcut
    -     * fusion if the section is applied to an array and iteratees accept only
    -     * one argument. The heuristic for whether a section qualifies for shortcut
    -     * fusion is subject to change.
    -     *
    -     * Chaining is supported in custom builds as long as the `_#value` method is
    -     * directly or indirectly included in the build.
    -     *
    -     * In addition to lodash methods, wrappers have `Array` and `String` methods.
    -     *
    -     * The wrapper `Array` methods are:
    -     * `concat`, `join`, `pop`, `push`, `shift`, `sort`, `splice`, and `unshift`
    -     *
    -     * The wrapper `String` methods are:
    -     * `replace` and `split`
    -     *
    -     * The wrapper methods that support shortcut fusion are:
    -     * `at`, `compact`, `drop`, `dropRight`, `dropWhile`, `filter`, `find`,
    -     * `findLast`, `head`, `initial`, `last`, `map`, `reject`, `reverse`, `slice`,
    -     * `tail`, `take`, `takeRight`, `takeRightWhile`, `takeWhile`, and `toArray`
    -     *
    -     * The chainable wrapper methods are:
    -     * `after`, `ary`, `assign`, `assignIn`, `assignInWith`, `assignWith`, `at`,
    -     * `before`, `bind`, `bindAll`, `bindKey`, `castArray`, `chain`, `chunk`,
    -     * `commit`, `compact`, `concat`, `conforms`, `constant`, `countBy`, `create`,
    -     * `curry`, `debounce`, `defaults`, `defaultsDeep`, `defer`, `delay`,
    -     * `difference`, `differenceBy`, `differenceWith`, `drop`, `dropRight`,
    -     * `dropRightWhile`, `dropWhile`, `extend`, `extendWith`, `fill`, `filter`,
    -     * `flatMap`, `flatMapDeep`, `flatMapDepth`, `flatten`, `flattenDeep`,
    -     * `flattenDepth`, `flip`, `flow`, `flowRight`, `fromPairs`, `functions`,
    -     * `functionsIn`, `groupBy`, `initial`, `intersection`, `intersectionBy`,
    -     * `intersectionWith`, `invert`, `invertBy`, `invokeMap`, `iteratee`, `keyBy`,
    -     * `keys`, `keysIn`, `map`, `mapKeys`, `mapValues`, `matches`, `matchesProperty`,
    -     * `memoize`, `merge`, `mergeWith`, `method`, `methodOf`, `mixin`, `negate`,
    -     * `nthArg`, `omit`, `omitBy`, `once`, `orderBy`, `over`, `overArgs`,
    -     * `overEvery`, `overSome`, `partial`, `partialRight`, `partition`, `pick`,
    -     * `pickBy`, `plant`, `property`, `propertyOf`, `pull`, `pullAll`, `pullAllBy`,
    -     * `pullAllWith`, `pullAt`, `push`, `range`, `rangeRight`, `rearg`, `reject`,
    -     * `remove`, `rest`, `reverse`, `sampleSize`, `set`, `setWith`, `shuffle`,
    -     * `slice`, `sort`, `sortBy`, `splice`, `spread`, `tail`, `take`, `takeRight`,
    -     * `takeRightWhile`, `takeWhile`, `tap`, `throttle`, `thru`, `toArray`,
    -     * `toPairs`, `toPairsIn`, `toPath`, `toPlainObject`, `transform`, `unary`,
    -     * `union`, `unionBy`, `unionWith`, `uniq`, `uniqBy`, `uniqWith`, `unset`,
    -     * `unshift`, `unzip`, `unzipWith`, `update`, `updateWith`, `values`,
    -     * `valuesIn`, `without`, `wrap`, `xor`, `xorBy`, `xorWith`, `zip`,
    -     * `zipObject`, `zipObjectDeep`, and `zipWith`
    -     *
    -     * The wrapper methods that are **not** chainable by default are:
    -     * `add`, `attempt`, `camelCase`, `capitalize`, `ceil`, `clamp`, `clone`,
    -     * `cloneDeep`, `cloneDeepWith`, `cloneWith`, `conformsTo`, `deburr`,
    -     * `defaultTo`, `divide`, `each`, `eachRight`, `endsWith`, `eq`, `escape`,
    -     * `escapeRegExp`, `every`, `find`, `findIndex`, `findKey`, `findLast`,
    -     * `findLastIndex`, `findLastKey`, `first`, `floor`, `forEach`, `forEachRight`,
    -     * `forIn`, `forInRight`, `forOwn`, `forOwnRight`, `get`, `gt`, `gte`, `has`,
    -     * `hasIn`, `head`, `identity`, `includes`, `indexOf`, `inRange`, `invoke`,
    -     * `isArguments`, `isArray`, `isArrayBuffer`, `isArrayLike`, `isArrayLikeObject`,
    -     * `isBoolean`, `isBuffer`, `isDate`, `isElement`, `isEmpty`, `isEqual`,
    -     * `isEqualWith`, `isError`, `isFinite`, `isFunction`, `isInteger`, `isLength`,
    -     * `isMap`, `isMatch`, `isMatchWith`, `isNaN`, `isNative`, `isNil`, `isNull`,
    -     * `isNumber`, `isObject`, `isObjectLike`, `isPlainObject`, `isRegExp`,
    -     * `isSafeInteger`, `isSet`, `isString`, `isUndefined`, `isTypedArray`,
    -     * `isWeakMap`, `isWeakSet`, `join`, `kebabCase`, `last`, `lastIndexOf`,
    -     * `lowerCase`, `lowerFirst`, `lt`, `lte`, `max`, `maxBy`, `mean`, `meanBy`,
    -     * `min`, `minBy`, `multiply`, `noConflict`, `noop`, `now`, `nth`, `pad`,
    -     * `padEnd`, `padStart`, `parseInt`, `pop`, `random`, `reduce`, `reduceRight`,
    -     * `repeat`, `result`, `round`, `runInContext`, `sample`, `shift`, `size`,
    -     * `snakeCase`, `some`, `sortedIndex`, `sortedIndexBy`, `sortedLastIndex`,
    -     * `sortedLastIndexBy`, `startCase`, `startsWith`, `stubArray`, `stubFalse`,
    -     * `stubObject`, `stubString`, `stubTrue`, `subtract`, `sum`, `sumBy`,
    -     * `template`, `times`, `toFinite`, `toInteger`, `toJSON`, `toLength`,
    -     * `toLower`, `toNumber`, `toSafeInteger`, `toString`, `toUpper`, `trim`,
    -     * `trimEnd`, `trimStart`, `truncate`, `unescape`, `uniqueId`, `upperCase`,
    -     * `upperFirst`, `value`, and `words`
    -     *
    -     * @name _
    -     * @constructor
    -     * @category Seq
    -     * @param {*} value The value to wrap in a `lodash` instance.
    -     * @returns {Object} Returns the new `lodash` wrapper instance.
    -     * @example
    -     *
    -     * function square(n) {
    -     *   return n * n;
    -     * }
    -     *
    -     * var wrapped = _([1, 2, 3]);
    -     *
    -     * // Returns an unwrapped value.
    -     * wrapped.reduce(_.add);
    -     * // => 6
    -     *
    -     * // Returns a wrapped value.
    -     * var squares = wrapped.map(square);
    -     *
    -     * _.isArray(squares);
    -     * // => false
    -     *
    -     * _.isArray(squares.value());
    -     * // => true
    -     */
    -    function lodash(value) {
    -      if (isObjectLike(value) && !isArray(value) && !(value instanceof LazyWrapper)) {
    -        if (value instanceof LodashWrapper) {
    -          return value;
    -        }
    -        if (hasOwnProperty.call(value, '__wrapped__')) {
    -          return wrapperClone(value);
    -        }
    -      }
    -      return new LodashWrapper(value);
    -    }
    -
    -    /**
    -     * The base implementation of `_.create` without support for assigning
    -     * properties to the created object.
    -     *
    -     * @private
    -     * @param {Object} proto The object to inherit from.
    -     * @returns {Object} Returns the new object.
    -     */
    -    var baseCreate = function () {
    -      function object() {}
    -      return function (proto) {
    -        if (!isObject(proto)) {
    -          return {};
    -        }
    -        if (objectCreate) {
    -          return objectCreate(proto);
    -        }
    -        object.prototype = proto;
    -        var result = new object();
    -        object.prototype = undefined;
    -        return result;
    -      };
    -    }();
    -
    -    /**
    -     * The function whose prototype chain sequence wrappers inherit from.
    -     *
    -     * @private
    -     */
    -    function baseLodash() {
    -      // No operation performed.
    -    }
    -
    -    /**
    -     * The base constructor for creating `lodash` wrapper objects.
    -     *
    -     * @private
    -     * @param {*} value The value to wrap.
    -     * @param {boolean} [chainAll] Enable explicit method chain sequences.
    -     */
    -    function LodashWrapper(value, chainAll) {
    -      this.__wrapped__ = value;
    -      this.__actions__ = [];
    -      this.__chain__ = !!chainAll;
    -      this.__index__ = 0;
    -      this.__values__ = undefined;
    -    }
    -
    -    /**
    -     * By default, the template delimiters used by lodash are like those in
    -     * embedded Ruby (ERB) as well as ES2015 template strings. Change the
    -     * following template settings to use alternative delimiters.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @type {Object}
    -     */
    -    lodash.templateSettings = {
    -      /**
    -       * Used to detect `data` property values to be HTML-escaped.
    -       *
    -       * @memberOf _.templateSettings
    -       * @type {RegExp}
    -       */
    -      'escape': reEscape,
    -      /**
    -       * Used to detect code to be evaluated.
    -       *
    -       * @memberOf _.templateSettings
    -       * @type {RegExp}
    -       */
    -      'evaluate': reEvaluate,
    -      /**
    -       * Used to detect `data` property values to inject.
    -       *
    -       * @memberOf _.templateSettings
    -       * @type {RegExp}
    -       */
    -      'interpolate': reInterpolate,
    -      /**
    -       * Used to reference the data object in the template text.
    -       *
    -       * @memberOf _.templateSettings
    -       * @type {string}
    -       */
    -      'variable': '',
    -      /**
    -       * Used to import variables into the compiled template.
    -       *
    -       * @memberOf _.templateSettings
    -       * @type {Object}
    -       */
    -      'imports': {
    -        /**
    -         * A reference to the `lodash` function.
    -         *
    -         * @memberOf _.templateSettings.imports
    -         * @type {Function}
    -         */
    -        '_': lodash
    -      }
    -    };
    -
    -    // Ensure wrappers are instances of `baseLodash`.
    -    lodash.prototype = baseLodash.prototype;
    -    lodash.prototype.constructor = lodash;
    -    LodashWrapper.prototype = baseCreate(baseLodash.prototype);
    -    LodashWrapper.prototype.constructor = LodashWrapper;
    -
    -    /*------------------------------------------------------------------------*/
    -
    -    /**
    -     * Creates a lazy wrapper object which wraps `value` to enable lazy evaluation.
    -     *
    -     * @private
    -     * @constructor
    -     * @param {*} value The value to wrap.
    -     */
    -    function LazyWrapper(value) {
    -      this.__wrapped__ = value;
    -      this.__actions__ = [];
    -      this.__dir__ = 1;
    -      this.__filtered__ = false;
    -      this.__iteratees__ = [];
    -      this.__takeCount__ = MAX_ARRAY_LENGTH;
    -      this.__views__ = [];
    -    }
    -
    -    /**
    -     * Creates a clone of the lazy wrapper object.
    -     *
    -     * @private
    -     * @name clone
    -     * @memberOf LazyWrapper
    -     * @returns {Object} Returns the cloned `LazyWrapper` object.
    -     */
    -    function lazyClone() {
    -      var result = new LazyWrapper(this.__wrapped__);
    -      result.__actions__ = copyArray(this.__actions__);
    -      result.__dir__ = this.__dir__;
    -      result.__filtered__ = this.__filtered__;
    -      result.__iteratees__ = copyArray(this.__iteratees__);
    -      result.__takeCount__ = this.__takeCount__;
    -      result.__views__ = copyArray(this.__views__);
    -      return result;
    -    }
    -
    -    /**
    -     * Reverses the direction of lazy iteration.
    -     *
    -     * @private
    -     * @name reverse
    -     * @memberOf LazyWrapper
    -     * @returns {Object} Returns the new reversed `LazyWrapper` object.
    -     */
    -    function lazyReverse() {
    -      if (this.__filtered__) {
    -        var result = new LazyWrapper(this);
    -        result.__dir__ = -1;
    -        result.__filtered__ = true;
    -      } else {
    -        result = this.clone();
    -        result.__dir__ *= -1;
    -      }
    -      return result;
    -    }
    -
    -    /**
    -     * Extracts the unwrapped value from its lazy wrapper.
    -     *
    -     * @private
    -     * @name value
    -     * @memberOf LazyWrapper
    -     * @returns {*} Returns the unwrapped value.
    -     */
    -    function lazyValue() {
    -      var array = this.__wrapped__.value(),
    -        dir = this.__dir__,
    -        isArr = isArray(array),
    -        isRight = dir < 0,
    -        arrLength = isArr ? array.length : 0,
    -        view = getView(0, arrLength, this.__views__),
    -        start = view.start,
    -        end = view.end,
    -        length = end - start,
    -        index = isRight ? end : start - 1,
    -        iteratees = this.__iteratees__,
    -        iterLength = iteratees.length,
    -        resIndex = 0,
    -        takeCount = nativeMin(length, this.__takeCount__);
    -      if (!isArr || !isRight && arrLength == length && takeCount == length) {
    -        return baseWrapperValue(array, this.__actions__);
    -      }
    -      var result = [];
    -      outer: while (length-- && resIndex < takeCount) {
    -        index += dir;
    -        var iterIndex = -1,
    -          value = array[index];
    -        while (++iterIndex < iterLength) {
    -          var data = iteratees[iterIndex],
    -            iteratee = data.iteratee,
    -            type = data.type,
    -            computed = iteratee(value);
    -          if (type == LAZY_MAP_FLAG) {
    -            value = computed;
    -          } else if (!computed) {
    -            if (type == LAZY_FILTER_FLAG) {
    -              continue outer;
    -            } else {
    -              break outer;
    -            }
    -          }
    -        }
    -        result[resIndex++] = value;
    -      }
    -      return result;
    -    }
    -
    -    // Ensure `LazyWrapper` is an instance of `baseLodash`.
    -    LazyWrapper.prototype = baseCreate(baseLodash.prototype);
    -    LazyWrapper.prototype.constructor = LazyWrapper;
    -
    -    /*------------------------------------------------------------------------*/
    -
    -    /**
    -     * Creates a hash object.
    -     *
    -     * @private
    -     * @constructor
    -     * @param {Array} [entries] The key-value pairs to cache.
    -     */
    -    function Hash(entries) {
    -      var index = -1,
    -        length = entries == null ? 0 : entries.length;
    -      this.clear();
    -      while (++index < length) {
    -        var entry = entries[index];
    -        this.set(entry[0], entry[1]);
    -      }
    -    }
    -
    -    /**
    -     * Removes all key-value entries from the hash.
    -     *
    -     * @private
    -     * @name clear
    -     * @memberOf Hash
    -     */
    -    function hashClear() {
    -      this.__data__ = nativeCreate ? nativeCreate(null) : {};
    -      this.size = 0;
    -    }
    -
    -    /**
    -     * Removes `key` and its value from the hash.
    -     *
    -     * @private
    -     * @name delete
    -     * @memberOf Hash
    -     * @param {Object} hash The hash to modify.
    -     * @param {string} key The key of the value to remove.
    -     * @returns {boolean} Returns `true` if the entry was removed, else `false`.
    -     */
    -    function hashDelete(key) {
    -      var result = this.has(key) && delete this.__data__[key];
    -      this.size -= result ? 1 : 0;
    -      return result;
    -    }
    -
    -    /**
    -     * Gets the hash value for `key`.
    -     *
    -     * @private
    -     * @name get
    -     * @memberOf Hash
    -     * @param {string} key The key of the value to get.
    -     * @returns {*} Returns the entry value.
    -     */
    -    function hashGet(key) {
    -      var data = this.__data__;
    -      if (nativeCreate) {
    -        var result = data[key];
    -        return result === HASH_UNDEFINED ? undefined : result;
    -      }
    -      return hasOwnProperty.call(data, key) ? data[key] : undefined;
    -    }
    -
    -    /**
    -     * Checks if a hash value for `key` exists.
    -     *
    -     * @private
    -     * @name has
    -     * @memberOf Hash
    -     * @param {string} key The key of the entry to check.
    -     * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
    -     */
    -    function hashHas(key) {
    -      var data = this.__data__;
    -      return nativeCreate ? data[key] !== undefined : hasOwnProperty.call(data, key);
    -    }
    -
    -    /**
    -     * Sets the hash `key` to `value`.
    -     *
    -     * @private
    -     * @name set
    -     * @memberOf Hash
    -     * @param {string} key The key of the value to set.
    -     * @param {*} value The value to set.
    -     * @returns {Object} Returns the hash instance.
    -     */
    -    function hashSet(key, value) {
    -      var data = this.__data__;
    -      this.size += this.has(key) ? 0 : 1;
    -      data[key] = nativeCreate && value === undefined ? HASH_UNDEFINED : value;
    -      return this;
    -    }
    -
    -    // Add methods to `Hash`.
    -    Hash.prototype.clear = hashClear;
    -    Hash.prototype['delete'] = hashDelete;
    -    Hash.prototype.get = hashGet;
    -    Hash.prototype.has = hashHas;
    -    Hash.prototype.set = hashSet;
    -
    -    /*------------------------------------------------------------------------*/
    -
    -    /**
    -     * Creates an list cache object.
    -     *
    -     * @private
    -     * @constructor
    -     * @param {Array} [entries] The key-value pairs to cache.
    -     */
    -    function ListCache(entries) {
    -      var index = -1,
    -        length = entries == null ? 0 : entries.length;
    -      this.clear();
    -      while (++index < length) {
    -        var entry = entries[index];
    -        this.set(entry[0], entry[1]);
    -      }
    -    }
    -
    -    /**
    -     * Removes all key-value entries from the list cache.
    -     *
    -     * @private
    -     * @name clear
    -     * @memberOf ListCache
    -     */
    -    function listCacheClear() {
    -      this.__data__ = [];
    -      this.size = 0;
    -    }
    -
    -    /**
    -     * Removes `key` and its value from the list cache.
    -     *
    -     * @private
    -     * @name delete
    -     * @memberOf ListCache
    -     * @param {string} key The key of the value to remove.
    -     * @returns {boolean} Returns `true` if the entry was removed, else `false`.
    -     */
    -    function listCacheDelete(key) {
    -      var data = this.__data__,
    -        index = assocIndexOf(data, key);
    -      if (index < 0) {
    -        return false;
    -      }
    -      var lastIndex = data.length - 1;
    -      if (index == lastIndex) {
    -        data.pop();
    -      } else {
    -        splice.call(data, index, 1);
    -      }
    -      --this.size;
    -      return true;
    -    }
    -
    -    /**
    -     * Gets the list cache value for `key`.
    -     *
    -     * @private
    -     * @name get
    -     * @memberOf ListCache
    -     * @param {string} key The key of the value to get.
    -     * @returns {*} Returns the entry value.
    -     */
    -    function listCacheGet(key) {
    -      var data = this.__data__,
    -        index = assocIndexOf(data, key);
    -      return index < 0 ? undefined : data[index][1];
    -    }
    -
    -    /**
    -     * Checks if a list cache value for `key` exists.
    -     *
    -     * @private
    -     * @name has
    -     * @memberOf ListCache
    -     * @param {string} key The key of the entry to check.
    -     * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
    -     */
    -    function listCacheHas(key) {
    -      return assocIndexOf(this.__data__, key) > -1;
    -    }
    -
    -    /**
    -     * Sets the list cache `key` to `value`.
    -     *
    -     * @private
    -     * @name set
    -     * @memberOf ListCache
    -     * @param {string} key The key of the value to set.
    -     * @param {*} value The value to set.
    -     * @returns {Object} Returns the list cache instance.
    -     */
    -    function listCacheSet(key, value) {
    -      var data = this.__data__,
    -        index = assocIndexOf(data, key);
    -      if (index < 0) {
    -        ++this.size;
    -        data.push([key, value]);
    -      } else {
    -        data[index][1] = value;
    -      }
    -      return this;
    -    }
    -
    -    // Add methods to `ListCache`.
    -    ListCache.prototype.clear = listCacheClear;
    -    ListCache.prototype['delete'] = listCacheDelete;
    -    ListCache.prototype.get = listCacheGet;
    -    ListCache.prototype.has = listCacheHas;
    -    ListCache.prototype.set = listCacheSet;
    -
    -    /*------------------------------------------------------------------------*/
    -
    -    /**
    -     * Creates a map cache object to store key-value pairs.
    -     *
    -     * @private
    -     * @constructor
    -     * @param {Array} [entries] The key-value pairs to cache.
    -     */
    -    function MapCache(entries) {
    -      var index = -1,
    -        length = entries == null ? 0 : entries.length;
    -      this.clear();
    -      while (++index < length) {
    -        var entry = entries[index];
    -        this.set(entry[0], entry[1]);
    -      }
    -    }
    -
    -    /**
    -     * Removes all key-value entries from the map.
    -     *
    -     * @private
    -     * @name clear
    -     * @memberOf MapCache
    -     */
    -    function mapCacheClear() {
    -      this.size = 0;
    -      this.__data__ = {
    -        'hash': new Hash(),
    -        'map': new (Map || ListCache)(),
    -        'string': new Hash()
    -      };
    -    }
    -
    -    /**
    -     * Removes `key` and its value from the map.
    -     *
    -     * @private
    -     * @name delete
    -     * @memberOf MapCache
    -     * @param {string} key The key of the value to remove.
    -     * @returns {boolean} Returns `true` if the entry was removed, else `false`.
    -     */
    -    function mapCacheDelete(key) {
    -      var result = getMapData(this, key)['delete'](key);
    -      this.size -= result ? 1 : 0;
    -      return result;
    -    }
    -
    -    /**
    -     * Gets the map value for `key`.
    -     *
    -     * @private
    -     * @name get
    -     * @memberOf MapCache
    -     * @param {string} key The key of the value to get.
    -     * @returns {*} Returns the entry value.
    -     */
    -    function mapCacheGet(key) {
    -      return getMapData(this, key).get(key);
    -    }
    -
    -    /**
    -     * Checks if a map value for `key` exists.
    -     *
    -     * @private
    -     * @name has
    -     * @memberOf MapCache
    -     * @param {string} key The key of the entry to check.
    -     * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
    -     */
    -    function mapCacheHas(key) {
    -      return getMapData(this, key).has(key);
    -    }
    -
    -    /**
    -     * Sets the map `key` to `value`.
    -     *
    -     * @private
    -     * @name set
    -     * @memberOf MapCache
    -     * @param {string} key The key of the value to set.
    -     * @param {*} value The value to set.
    -     * @returns {Object} Returns the map cache instance.
    -     */
    -    function mapCacheSet(key, value) {
    -      var data = getMapData(this, key),
    -        size = data.size;
    -      data.set(key, value);
    -      this.size += data.size == size ? 0 : 1;
    -      return this;
    -    }
    -
    -    // Add methods to `MapCache`.
    -    MapCache.prototype.clear = mapCacheClear;
    -    MapCache.prototype['delete'] = mapCacheDelete;
    -    MapCache.prototype.get = mapCacheGet;
    -    MapCache.prototype.has = mapCacheHas;
    -    MapCache.prototype.set = mapCacheSet;
    -
    -    /*------------------------------------------------------------------------*/
    -
    -    /**
    -     *
    -     * Creates an array cache object to store unique values.
    -     *
    -     * @private
    -     * @constructor
    -     * @param {Array} [values] The values to cache.
    -     */
    -    function SetCache(values) {
    -      var index = -1,
    -        length = values == null ? 0 : values.length;
    -      this.__data__ = new MapCache();
    -      while (++index < length) {
    -        this.add(values[index]);
    -      }
    -    }
    -
    -    /**
    -     * Adds `value` to the array cache.
    -     *
    -     * @private
    -     * @name add
    -     * @memberOf SetCache
    -     * @alias push
    -     * @param {*} value The value to cache.
    -     * @returns {Object} Returns the cache instance.
    -     */
    -    function setCacheAdd(value) {
    -      this.__data__.set(value, HASH_UNDEFINED);
    -      return this;
    -    }
    -
    -    /**
    -     * Checks if `value` is in the array cache.
    -     *
    -     * @private
    -     * @name has
    -     * @memberOf SetCache
    -     * @param {*} value The value to search for.
    -     * @returns {number} Returns `true` if `value` is found, else `false`.
    -     */
    -    function setCacheHas(value) {
    -      return this.__data__.has(value);
    -    }
    -
    -    // Add methods to `SetCache`.
    -    SetCache.prototype.add = SetCache.prototype.push = setCacheAdd;
    -    SetCache.prototype.has = setCacheHas;
    -
    -    /*------------------------------------------------------------------------*/
    -
    -    /**
    -     * Creates a stack cache object to store key-value pairs.
    -     *
    -     * @private
    -     * @constructor
    -     * @param {Array} [entries] The key-value pairs to cache.
    -     */
    -    function Stack(entries) {
    -      var data = this.__data__ = new ListCache(entries);
    -      this.size = data.size;
    -    }
    -
    -    /**
    -     * Removes all key-value entries from the stack.
    -     *
    -     * @private
    -     * @name clear
    -     * @memberOf Stack
    -     */
    -    function stackClear() {
    -      this.__data__ = new ListCache();
    -      this.size = 0;
    -    }
    -
    -    /**
    -     * Removes `key` and its value from the stack.
    -     *
    -     * @private
    -     * @name delete
    -     * @memberOf Stack
    -     * @param {string} key The key of the value to remove.
    -     * @returns {boolean} Returns `true` if the entry was removed, else `false`.
    -     */
    -    function stackDelete(key) {
    -      var data = this.__data__,
    -        result = data['delete'](key);
    -      this.size = data.size;
    -      return result;
    -    }
    -
    -    /**
    -     * Gets the stack value for `key`.
    -     *
    -     * @private
    -     * @name get
    -     * @memberOf Stack
    -     * @param {string} key The key of the value to get.
    -     * @returns {*} Returns the entry value.
    -     */
    -    function stackGet(key) {
    -      return this.__data__.get(key);
    -    }
    -
    -    /**
    -     * Checks if a stack value for `key` exists.
    -     *
    -     * @private
    -     * @name has
    -     * @memberOf Stack
    -     * @param {string} key The key of the entry to check.
    -     * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
    -     */
    -    function stackHas(key) {
    -      return this.__data__.has(key);
    -    }
    -
    -    /**
    -     * Sets the stack `key` to `value`.
    -     *
    -     * @private
    -     * @name set
    -     * @memberOf Stack
    -     * @param {string} key The key of the value to set.
    -     * @param {*} value The value to set.
    -     * @returns {Object} Returns the stack cache instance.
    -     */
    -    function stackSet(key, value) {
    -      var data = this.__data__;
    -      if (data instanceof ListCache) {
    -        var pairs = data.__data__;
    -        if (!Map || pairs.length < LARGE_ARRAY_SIZE - 1) {
    -          pairs.push([key, value]);
    -          this.size = ++data.size;
    -          return this;
    -        }
    -        data = this.__data__ = new MapCache(pairs);
    -      }
    -      data.set(key, value);
    -      this.size = data.size;
    -      return this;
    -    }
    -
    -    // Add methods to `Stack`.
    -    Stack.prototype.clear = stackClear;
    -    Stack.prototype['delete'] = stackDelete;
    -    Stack.prototype.get = stackGet;
    -    Stack.prototype.has = stackHas;
    -    Stack.prototype.set = stackSet;
    -
    -    /*------------------------------------------------------------------------*/
    -
    -    /**
    -     * Creates an array of the enumerable property names of the array-like `value`.
    -     *
    -     * @private
    -     * @param {*} value The value to query.
    -     * @param {boolean} inherited Specify returning inherited property names.
    -     * @returns {Array} Returns the array of property names.
    -     */
    -    function arrayLikeKeys(value, inherited) {
    -      var isArr = isArray(value),
    -        isArg = !isArr && isArguments(value),
    -        isBuff = !isArr && !isArg && isBuffer(value),
    -        isType = !isArr && !isArg && !isBuff && isTypedArray(value),
    -        skipIndexes = isArr || isArg || isBuff || isType,
    -        result = skipIndexes ? baseTimes(value.length, String) : [],
    -        length = result.length;
    -      for (var key in value) {
    -        if ((inherited || hasOwnProperty.call(value, key)) && !(skipIndexes && (
    -        // Safari 9 has enumerable `arguments.length` in strict mode.
    -        key == 'length' ||
    -        // Node.js 0.10 has enumerable non-index properties on buffers.
    -        isBuff && (key == 'offset' || key == 'parent') ||
    -        // PhantomJS 2 has enumerable non-index properties on typed arrays.
    -        isType && (key == 'buffer' || key == 'byteLength' || key == 'byteOffset') ||
    -        // Skip index properties.
    -        isIndex(key, length)))) {
    -          result.push(key);
    -        }
    -      }
    -      return result;
    -    }
    -
    -    /**
    -     * A specialized version of `_.sample` for arrays.
    -     *
    -     * @private
    -     * @param {Array} array The array to sample.
    -     * @returns {*} Returns the random element.
    -     */
    -    function arraySample(array) {
    -      var length = array.length;
    -      return length ? array[baseRandom(0, length - 1)] : undefined;
    -    }
    -
    -    /**
    -     * A specialized version of `_.sampleSize` for arrays.
    -     *
    -     * @private
    -     * @param {Array} array The array to sample.
    -     * @param {number} n The number of elements to sample.
    -     * @returns {Array} Returns the random elements.
    -     */
    -    function arraySampleSize(array, n) {
    -      return shuffleSelf(copyArray(array), baseClamp(n, 0, array.length));
    -    }
    -
    -    /**
    -     * A specialized version of `_.shuffle` for arrays.
    -     *
    -     * @private
    -     * @param {Array} array The array to shuffle.
    -     * @returns {Array} Returns the new shuffled array.
    -     */
    -    function arrayShuffle(array) {
    -      return shuffleSelf(copyArray(array));
    -    }
    -
    -    /**
    -     * This function is like `assignValue` except that it doesn't assign
    -     * `undefined` values.
    -     *
    -     * @private
    -     * @param {Object} object The object to modify.
    -     * @param {string} key The key of the property to assign.
    -     * @param {*} value The value to assign.
    -     */
    -    function assignMergeValue(object, key, value) {
    -      if (value !== undefined && !eq(object[key], value) || value === undefined && !(key in object)) {
    -        baseAssignValue(object, key, value);
    -      }
    -    }
    -
    -    /**
    -     * Assigns `value` to `key` of `object` if the existing value is not equivalent
    -     * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
    -     * for equality comparisons.
    -     *
    -     * @private
    -     * @param {Object} object The object to modify.
    -     * @param {string} key The key of the property to assign.
    -     * @param {*} value The value to assign.
    -     */
    -    function assignValue(object, key, value) {
    -      var objValue = object[key];
    -      if (!(hasOwnProperty.call(object, key) && eq(objValue, value)) || value === undefined && !(key in object)) {
    -        baseAssignValue(object, key, value);
    -      }
    -    }
    -
    -    /**
    -     * Gets the index at which the `key` is found in `array` of key-value pairs.
    -     *
    -     * @private
    -     * @param {Array} array The array to inspect.
    -     * @param {*} key The key to search for.
    -     * @returns {number} Returns the index of the matched value, else `-1`.
    -     */
    -    function assocIndexOf(array, key) {
    -      var length = array.length;
    -      while (length--) {
    -        if (eq(array[length][0], key)) {
    -          return length;
    -        }
    -      }
    -      return -1;
    -    }
    -
    -    /**
    -     * Aggregates elements of `collection` on `accumulator` with keys transformed
    -     * by `iteratee` and values set by `setter`.
    -     *
    -     * @private
    -     * @param {Array|Object} collection The collection to iterate over.
    -     * @param {Function} setter The function to set `accumulator` values.
    -     * @param {Function} iteratee The iteratee to transform keys.
    -     * @param {Object} accumulator The initial aggregated object.
    -     * @returns {Function} Returns `accumulator`.
    -     */
    -    function baseAggregator(collection, setter, iteratee, accumulator) {
    -      baseEach(collection, function (value, key, collection) {
    -        setter(accumulator, value, iteratee(value), collection);
    -      });
    -      return accumulator;
    -    }
    -
    -    /**
    -     * The base implementation of `_.assign` without support for multiple sources
    -     * or `customizer` functions.
    -     *
    -     * @private
    -     * @param {Object} object The destination object.
    -     * @param {Object} source The source object.
    -     * @returns {Object} Returns `object`.
    -     */
    -    function baseAssign(object, source) {
    -      return object && copyObject(source, keys(source), object);
    -    }
    -
    -    /**
    -     * The base implementation of `_.assignIn` without support for multiple sources
    -     * or `customizer` functions.
    -     *
    -     * @private
    -     * @param {Object} object The destination object.
    -     * @param {Object} source The source object.
    -     * @returns {Object} Returns `object`.
    -     */
    -    function baseAssignIn(object, source) {
    -      return object && copyObject(source, keysIn(source), object);
    -    }
    -
    -    /**
    -     * The base implementation of `assignValue` and `assignMergeValue` without
    -     * value checks.
    -     *
    -     * @private
    -     * @param {Object} object The object to modify.
    -     * @param {string} key The key of the property to assign.
    -     * @param {*} value The value to assign.
    -     */
    -    function baseAssignValue(object, key, value) {
    -      if (key == '__proto__' && defineProperty) {
    -        defineProperty(object, key, {
    -          'configurable': true,
    -          'enumerable': true,
    -          'value': value,
    -          'writable': true
    -        });
    -      } else {
    -        object[key] = value;
    -      }
    -    }
    -
    -    /**
    -     * The base implementation of `_.at` without support for individual paths.
    -     *
    -     * @private
    -     * @param {Object} object The object to iterate over.
    -     * @param {string[]} paths The property paths to pick.
    -     * @returns {Array} Returns the picked elements.
    -     */
    -    function baseAt(object, paths) {
    -      var index = -1,
    -        length = paths.length,
    -        result = Array(length),
    -        skip = object == null;
    -      while (++index < length) {
    -        result[index] = skip ? undefined : get(object, paths[index]);
    -      }
    -      return result;
    -    }
    -
    -    /**
    -     * The base implementation of `_.clamp` which doesn't coerce arguments.
    -     *
    -     * @private
    -     * @param {number} number The number to clamp.
    -     * @param {number} [lower] The lower bound.
    -     * @param {number} upper The upper bound.
    -     * @returns {number} Returns the clamped number.
    -     */
    -    function baseClamp(number, lower, upper) {
    -      if (number === number) {
    -        if (upper !== undefined) {
    -          number = number <= upper ? number : upper;
    -        }
    -        if (lower !== undefined) {
    -          number = number >= lower ? number : lower;
    -        }
    -      }
    -      return number;
    -    }
    -
    -    /**
    -     * The base implementation of `_.clone` and `_.cloneDeep` which tracks
    -     * traversed objects.
    -     *
    -     * @private
    -     * @param {*} value The value to clone.
    -     * @param {boolean} bitmask The bitmask flags.
    -     *  1 - Deep clone
    -     *  2 - Flatten inherited properties
    -     *  4 - Clone symbols
    -     * @param {Function} [customizer] The function to customize cloning.
    -     * @param {string} [key] The key of `value`.
    -     * @param {Object} [object] The parent object of `value`.
    -     * @param {Object} [stack] Tracks traversed objects and their clone counterparts.
    -     * @returns {*} Returns the cloned value.
    -     */
    -    function baseClone(value, bitmask, customizer, key, object, stack) {
    -      var result,
    -        isDeep = bitmask & CLONE_DEEP_FLAG,
    -        isFlat = bitmask & CLONE_FLAT_FLAG,
    -        isFull = bitmask & CLONE_SYMBOLS_FLAG;
    -      if (customizer) {
    -        result = object ? customizer(value, key, object, stack) : customizer(value);
    -      }
    -      if (result !== undefined) {
    -        return result;
    -      }
    -      if (!isObject(value)) {
    -        return value;
    -      }
    -      var isArr = isArray(value);
    -      if (isArr) {
    -        result = initCloneArray(value);
    -        if (!isDeep) {
    -          return copyArray(value, result);
    -        }
    -      } else {
    -        var tag = getTag(value),
    -          isFunc = tag == funcTag || tag == genTag;
    -        if (isBuffer(value)) {
    -          return cloneBuffer(value, isDeep);
    -        }
    -        if (tag == objectTag || tag == argsTag || isFunc && !object) {
    -          result = isFlat || isFunc ? {} : initCloneObject(value);
    -          if (!isDeep) {
    -            return isFlat ? copySymbolsIn(value, baseAssignIn(result, value)) : copySymbols(value, baseAssign(result, value));
    -          }
    -        } else {
    -          if (!cloneableTags[tag]) {
    -            return object ? value : {};
    -          }
    -          result = initCloneByTag(value, tag, isDeep);
    -        }
    -      }
    -      // Check for circular references and return its corresponding clone.
    -      stack || (stack = new Stack());
    -      var stacked = stack.get(value);
    -      if (stacked) {
    -        return stacked;
    -      }
    -      stack.set(value, result);
    -      if (isSet(value)) {
    -        value.forEach(function (subValue) {
    -          result.add(baseClone(subValue, bitmask, customizer, subValue, value, stack));
    -        });
    -      } else if (isMap(value)) {
    -        value.forEach(function (subValue, key) {
    -          result.set(key, baseClone(subValue, bitmask, customizer, key, value, stack));
    -        });
    -      }
    -      var keysFunc = isFull ? isFlat ? getAllKeysIn : getAllKeys : isFlat ? keysIn : keys;
    -      var props = isArr ? undefined : keysFunc(value);
    -      arrayEach(props || value, function (subValue, key) {
    -        if (props) {
    -          key = subValue;
    -          subValue = value[key];
    -        }
    -        // Recursively populate clone (susceptible to call stack limits).
    -        assignValue(result, key, baseClone(subValue, bitmask, customizer, key, value, stack));
    -      });
    -      return result;
    -    }
    -
    -    /**
    -     * The base implementation of `_.conforms` which doesn't clone `source`.
    -     *
    -     * @private
    -     * @param {Object} source The object of property predicates to conform to.
    -     * @returns {Function} Returns the new spec function.
    -     */
    -    function baseConforms(source) {
    -      var props = keys(source);
    -      return function (object) {
    -        return baseConformsTo(object, source, props);
    -      };
    -    }
    -
    -    /**
    -     * The base implementation of `_.conformsTo` which accepts `props` to check.
    -     *
    -     * @private
    -     * @param {Object} object The object to inspect.
    -     * @param {Object} source The object of property predicates to conform to.
    -     * @returns {boolean} Returns `true` if `object` conforms, else `false`.
    -     */
    -    function baseConformsTo(object, source, props) {
    -      var length = props.length;
    -      if (object == null) {
    -        return !length;
    -      }
    -      object = Object(object);
    -      while (length--) {
    -        var key = props[length],
    -          predicate = source[key],
    -          value = object[key];
    -        if (value === undefined && !(key in object) || !predicate(value)) {
    -          return false;
    -        }
    -      }
    -      return true;
    -    }
    -
    -    /**
    -     * The base implementation of `_.delay` and `_.defer` which accepts `args`
    -     * to provide to `func`.
    -     *
    -     * @private
    -     * @param {Function} func The function to delay.
    -     * @param {number} wait The number of milliseconds to delay invocation.
    -     * @param {Array} args The arguments to provide to `func`.
    -     * @returns {number|Object} Returns the timer id or timeout object.
    -     */
    -    function baseDelay(func, wait, args) {
    -      if (typeof func != 'function') {
    -        throw new TypeError(FUNC_ERROR_TEXT);
    -      }
    -      return setTimeout(function () {
    -        func.apply(undefined, args);
    -      }, wait);
    -    }
    -
    -    /**
    -     * The base implementation of methods like `_.difference` without support
    -     * for excluding multiple arrays or iteratee shorthands.
    -     *
    -     * @private
    -     * @param {Array} array The array to inspect.
    -     * @param {Array} values The values to exclude.
    -     * @param {Function} [iteratee] The iteratee invoked per element.
    -     * @param {Function} [comparator] The comparator invoked per element.
    -     * @returns {Array} Returns the new array of filtered values.
    -     */
    -    function baseDifference(array, values, iteratee, comparator) {
    -      var index = -1,
    -        includes = arrayIncludes,
    -        isCommon = true,
    -        length = array.length,
    -        result = [],
    -        valuesLength = values.length;
    -      if (!length) {
    -        return result;
    -      }
    -      if (iteratee) {
    -        values = arrayMap(values, baseUnary(iteratee));
    -      }
    -      if (comparator) {
    -        includes = arrayIncludesWith;
    -        isCommon = false;
    -      } else if (values.length >= LARGE_ARRAY_SIZE) {
    -        includes = cacheHas;
    -        isCommon = false;
    -        values = new SetCache(values);
    -      }
    -      outer: while (++index < length) {
    -        var value = array[index],
    -          computed = iteratee == null ? value : iteratee(value);
    -        value = comparator || value !== 0 ? value : 0;
    -        if (isCommon && computed === computed) {
    -          var valuesIndex = valuesLength;
    -          while (valuesIndex--) {
    -            if (values[valuesIndex] === computed) {
    -              continue outer;
    -            }
    -          }
    -          result.push(value);
    -        } else if (!includes(values, computed, comparator)) {
    -          result.push(value);
    -        }
    -      }
    -      return result;
    -    }
    -
    -    /**
    -     * The base implementation of `_.forEach` without support for iteratee shorthands.
    -     *
    -     * @private
    -     * @param {Array|Object} collection The collection to iterate over.
    -     * @param {Function} iteratee The function invoked per iteration.
    -     * @returns {Array|Object} Returns `collection`.
    -     */
    -    var baseEach = createBaseEach(baseForOwn);
    -
    -    /**
    -     * The base implementation of `_.forEachRight` without support for iteratee shorthands.
    -     *
    -     * @private
    -     * @param {Array|Object} collection The collection to iterate over.
    -     * @param {Function} iteratee The function invoked per iteration.
    -     * @returns {Array|Object} Returns `collection`.
    -     */
    -    var baseEachRight = createBaseEach(baseForOwnRight, true);
    -
    -    /**
    -     * The base implementation of `_.every` without support for iteratee shorthands.
    -     *
    -     * @private
    -     * @param {Array|Object} collection The collection to iterate over.
    -     * @param {Function} predicate The function invoked per iteration.
    -     * @returns {boolean} Returns `true` if all elements pass the predicate check,
    -     *  else `false`
    -     */
    -    function baseEvery(collection, predicate) {
    -      var result = true;
    -      baseEach(collection, function (value, index, collection) {
    -        result = !!predicate(value, index, collection);
    -        return result;
    -      });
    -      return result;
    -    }
    -
    -    /**
    -     * The base implementation of methods like `_.max` and `_.min` which accepts a
    -     * `comparator` to determine the extremum value.
    -     *
    -     * @private
    -     * @param {Array} array The array to iterate over.
    -     * @param {Function} iteratee The iteratee invoked per iteration.
    -     * @param {Function} comparator The comparator used to compare values.
    -     * @returns {*} Returns the extremum value.
    -     */
    -    function baseExtremum(array, iteratee, comparator) {
    -      var index = -1,
    -        length = array.length;
    -      while (++index < length) {
    -        var value = array[index],
    -          current = iteratee(value);
    -        if (current != null && (computed === undefined ? current === current && !isSymbol(current) : comparator(current, computed))) {
    -          var computed = current,
    -            result = value;
    -        }
    -      }
    -      return result;
    -    }
    -
    -    /**
    -     * The base implementation of `_.fill` without an iteratee call guard.
    -     *
    -     * @private
    -     * @param {Array} array The array to fill.
    -     * @param {*} value The value to fill `array` with.
    -     * @param {number} [start=0] The start position.
    -     * @param {number} [end=array.length] The end position.
    -     * @returns {Array} Returns `array`.
    -     */
    -    function baseFill(array, value, start, end) {
    -      var length = array.length;
    -      start = toInteger(start);
    -      if (start < 0) {
    -        start = -start > length ? 0 : length + start;
    -      }
    -      end = end === undefined || end > length ? length : toInteger(end);
    -      if (end < 0) {
    -        end += length;
    -      }
    -      end = start > end ? 0 : toLength(end);
    -      while (start < end) {
    -        array[start++] = value;
    -      }
    -      return array;
    -    }
    -
    -    /**
    -     * The base implementation of `_.filter` without support for iteratee shorthands.
    -     *
    -     * @private
    -     * @param {Array|Object} collection The collection to iterate over.
    -     * @param {Function} predicate The function invoked per iteration.
    -     * @returns {Array} Returns the new filtered array.
    -     */
    -    function baseFilter(collection, predicate) {
    -      var result = [];
    -      baseEach(collection, function (value, index, collection) {
    -        if (predicate(value, index, collection)) {
    -          result.push(value);
    -        }
    -      });
    -      return result;
    -    }
    -
    -    /**
    -     * The base implementation of `_.flatten` with support for restricting flattening.
    -     *
    -     * @private
    -     * @param {Array} array The array to flatten.
    -     * @param {number} depth The maximum recursion depth.
    -     * @param {boolean} [predicate=isFlattenable] The function invoked per iteration.
    -     * @param {boolean} [isStrict] Restrict to values that pass `predicate` checks.
    -     * @param {Array} [result=[]] The initial result value.
    -     * @returns {Array} Returns the new flattened array.
    -     */
    -    function baseFlatten(array, depth, predicate, isStrict, result) {
    -      var index = -1,
    -        length = array.length;
    -      predicate || (predicate = isFlattenable);
    -      result || (result = []);
    -      while (++index < length) {
    -        var value = array[index];
    -        if (depth > 0 && predicate(value)) {
    -          if (depth > 1) {
    -            // Recursively flatten arrays (susceptible to call stack limits).
    -            baseFlatten(value, depth - 1, predicate, isStrict, result);
    -          } else {
    -            arrayPush(result, value);
    -          }
    -        } else if (!isStrict) {
    -          result[result.length] = value;
    -        }
    -      }
    -      return result;
    -    }
    -
    -    /**
    -     * The base implementation of `baseForOwn` which iterates over `object`
    -     * properties returned by `keysFunc` and invokes `iteratee` for each property.
    -     * Iteratee functions may exit iteration early by explicitly returning `false`.
    -     *
    -     * @private
    -     * @param {Object} object The object to iterate over.
    -     * @param {Function} iteratee The function invoked per iteration.
    -     * @param {Function} keysFunc The function to get the keys of `object`.
    -     * @returns {Object} Returns `object`.
    -     */
    -    var baseFor = createBaseFor();
    -
    -    /**
    -     * This function is like `baseFor` except that it iterates over properties
    -     * in the opposite order.
    -     *
    -     * @private
    -     * @param {Object} object The object to iterate over.
    -     * @param {Function} iteratee The function invoked per iteration.
    -     * @param {Function} keysFunc The function to get the keys of `object`.
    -     * @returns {Object} Returns `object`.
    -     */
    -    var baseForRight = createBaseFor(true);
    -
    -    /**
    -     * The base implementation of `_.forOwn` without support for iteratee shorthands.
    -     *
    -     * @private
    -     * @param {Object} object The object to iterate over.
    -     * @param {Function} iteratee The function invoked per iteration.
    -     * @returns {Object} Returns `object`.
    -     */
    -    function baseForOwn(object, iteratee) {
    -      return object && baseFor(object, iteratee, keys);
    -    }
    -
    -    /**
    -     * The base implementation of `_.forOwnRight` without support for iteratee shorthands.
    -     *
    -     * @private
    -     * @param {Object} object The object to iterate over.
    -     * @param {Function} iteratee The function invoked per iteration.
    -     * @returns {Object} Returns `object`.
    -     */
    -    function baseForOwnRight(object, iteratee) {
    -      return object && baseForRight(object, iteratee, keys);
    -    }
    -
    -    /**
    -     * The base implementation of `_.functions` which creates an array of
    -     * `object` function property names filtered from `props`.
    -     *
    -     * @private
    -     * @param {Object} object The object to inspect.
    -     * @param {Array} props The property names to filter.
    -     * @returns {Array} Returns the function names.
    -     */
    -    function baseFunctions(object, props) {
    -      return arrayFilter(props, function (key) {
    -        return isFunction(object[key]);
    -      });
    -    }
    -
    -    /**
    -     * The base implementation of `_.get` without support for default values.
    -     *
    -     * @private
    -     * @param {Object} object The object to query.
    -     * @param {Array|string} path The path of the property to get.
    -     * @returns {*} Returns the resolved value.
    -     */
    -    function baseGet(object, path) {
    -      path = castPath(path, object);
    -      var index = 0,
    -        length = path.length;
    -      while (object != null && index < length) {
    -        object = object[toKey(path[index++])];
    -      }
    -      return index && index == length ? object : undefined;
    -    }
    -
    -    /**
    -     * The base implementation of `getAllKeys` and `getAllKeysIn` which uses
    -     * `keysFunc` and `symbolsFunc` to get the enumerable property names and
    -     * symbols of `object`.
    -     *
    -     * @private
    -     * @param {Object} object The object to query.
    -     * @param {Function} keysFunc The function to get the keys of `object`.
    -     * @param {Function} symbolsFunc The function to get the symbols of `object`.
    -     * @returns {Array} Returns the array of property names and symbols.
    -     */
    -    function baseGetAllKeys(object, keysFunc, symbolsFunc) {
    -      var result = keysFunc(object);
    -      return isArray(object) ? result : arrayPush(result, symbolsFunc(object));
    -    }
    -
    -    /**
    -     * The base implementation of `getTag` without fallbacks for buggy environments.
    -     *
    -     * @private
    -     * @param {*} value The value to query.
    -     * @returns {string} Returns the `toStringTag`.
    -     */
    -    function baseGetTag(value) {
    -      if (value == null) {
    -        return value === undefined ? undefinedTag : nullTag;
    -      }
    -      return symToStringTag && symToStringTag in Object(value) ? getRawTag(value) : objectToString(value);
    -    }
    -
    -    /**
    -     * The base implementation of `_.gt` which doesn't coerce arguments.
    -     *
    -     * @private
    -     * @param {*} value The value to compare.
    -     * @param {*} other The other value to compare.
    -     * @returns {boolean} Returns `true` if `value` is greater than `other`,
    -     *  else `false`.
    -     */
    -    function baseGt(value, other) {
    -      return value > other;
    -    }
    -
    -    /**
    -     * The base implementation of `_.has` without support for deep paths.
    -     *
    -     * @private
    -     * @param {Object} [object] The object to query.
    -     * @param {Array|string} key The key to check.
    -     * @returns {boolean} Returns `true` if `key` exists, else `false`.
    -     */
    -    function baseHas(object, key) {
    -      return object != null && hasOwnProperty.call(object, key);
    -    }
    -
    -    /**
    -     * The base implementation of `_.hasIn` without support for deep paths.
    -     *
    -     * @private
    -     * @param {Object} [object] The object to query.
    -     * @param {Array|string} key The key to check.
    -     * @returns {boolean} Returns `true` if `key` exists, else `false`.
    -     */
    -    function baseHasIn(object, key) {
    -      return object != null && key in Object(object);
    -    }
    -
    -    /**
    -     * The base implementation of `_.inRange` which doesn't coerce arguments.
    -     *
    -     * @private
    -     * @param {number} number The number to check.
    -     * @param {number} start The start of the range.
    -     * @param {number} end The end of the range.
    -     * @returns {boolean} Returns `true` if `number` is in the range, else `false`.
    -     */
    -    function baseInRange(number, start, end) {
    -      return number >= nativeMin(start, end) && number < nativeMax(start, end);
    -    }
    -
    -    /**
    -     * The base implementation of methods like `_.intersection`, without support
    -     * for iteratee shorthands, that accepts an array of arrays to inspect.
    -     *
    -     * @private
    -     * @param {Array} arrays The arrays to inspect.
    -     * @param {Function} [iteratee] The iteratee invoked per element.
    -     * @param {Function} [comparator] The comparator invoked per element.
    -     * @returns {Array} Returns the new array of shared values.
    -     */
    -    function baseIntersection(arrays, iteratee, comparator) {
    -      var includes = comparator ? arrayIncludesWith : arrayIncludes,
    -        length = arrays[0].length,
    -        othLength = arrays.length,
    -        othIndex = othLength,
    -        caches = Array(othLength),
    -        maxLength = Infinity,
    -        result = [];
    -      while (othIndex--) {
    -        var array = arrays[othIndex];
    -        if (othIndex && iteratee) {
    -          array = arrayMap(array, baseUnary(iteratee));
    -        }
    -        maxLength = nativeMin(array.length, maxLength);
    -        caches[othIndex] = !comparator && (iteratee || length >= 120 && array.length >= 120) ? new SetCache(othIndex && array) : undefined;
    -      }
    -      array = arrays[0];
    -      var index = -1,
    -        seen = caches[0];
    -      outer: while (++index < length && result.length < maxLength) {
    -        var value = array[index],
    -          computed = iteratee ? iteratee(value) : value;
    -        value = comparator || value !== 0 ? value : 0;
    -        if (!(seen ? cacheHas(seen, computed) : includes(result, computed, comparator))) {
    -          othIndex = othLength;
    -          while (--othIndex) {
    -            var cache = caches[othIndex];
    -            if (!(cache ? cacheHas(cache, computed) : includes(arrays[othIndex], computed, comparator))) {
    -              continue outer;
    -            }
    -          }
    -          if (seen) {
    -            seen.push(computed);
    -          }
    -          result.push(value);
    -        }
    -      }
    -      return result;
    -    }
    -
    -    /**
    -     * The base implementation of `_.invert` and `_.invertBy` which inverts
    -     * `object` with values transformed by `iteratee` and set by `setter`.
    -     *
    -     * @private
    -     * @param {Object} object The object to iterate over.
    -     * @param {Function} setter The function to set `accumulator` values.
    -     * @param {Function} iteratee The iteratee to transform values.
    -     * @param {Object} accumulator The initial inverted object.
    -     * @returns {Function} Returns `accumulator`.
    -     */
    -    function baseInverter(object, setter, iteratee, accumulator) {
    -      baseForOwn(object, function (value, key, object) {
    -        setter(accumulator, iteratee(value), key, object);
    -      });
    -      return accumulator;
    -    }
    -
    -    /**
    -     * The base implementation of `_.invoke` without support for individual
    -     * method arguments.
    -     *
    -     * @private
    -     * @param {Object} object The object to query.
    -     * @param {Array|string} path The path of the method to invoke.
    -     * @param {Array} args The arguments to invoke the method with.
    -     * @returns {*} Returns the result of the invoked method.
    -     */
    -    function baseInvoke(object, path, args) {
    -      path = castPath(path, object);
    -      object = parent(object, path);
    -      var func = object == null ? object : object[toKey(last(path))];
    -      return func == null ? undefined : apply(func, object, args);
    -    }
    -
    -    /**
    -     * The base implementation of `_.isArguments`.
    -     *
    -     * @private
    -     * @param {*} value The value to check.
    -     * @returns {boolean} Returns `true` if `value` is an `arguments` object,
    -     */
    -    function baseIsArguments(value) {
    -      return isObjectLike(value) && baseGetTag(value) == argsTag;
    -    }
    -
    -    /**
    -     * The base implementation of `_.isArrayBuffer` without Node.js optimizations.
    -     *
    -     * @private
    -     * @param {*} value The value to check.
    -     * @returns {boolean} Returns `true` if `value` is an array buffer, else `false`.
    -     */
    -    function baseIsArrayBuffer(value) {
    -      return isObjectLike(value) && baseGetTag(value) == arrayBufferTag;
    -    }
    -
    -    /**
    -     * The base implementation of `_.isDate` without Node.js optimizations.
    -     *
    -     * @private
    -     * @param {*} value The value to check.
    -     * @returns {boolean} Returns `true` if `value` is a date object, else `false`.
    -     */
    -    function baseIsDate(value) {
    -      return isObjectLike(value) && baseGetTag(value) == dateTag;
    -    }
    -
    -    /**
    -     * The base implementation of `_.isEqual` which supports partial comparisons
    -     * and tracks traversed objects.
    -     *
    -     * @private
    -     * @param {*} value The value to compare.
    -     * @param {*} other The other value to compare.
    -     * @param {boolean} bitmask The bitmask flags.
    -     *  1 - Unordered comparison
    -     *  2 - Partial comparison
    -     * @param {Function} [customizer] The function to customize comparisons.
    -     * @param {Object} [stack] Tracks traversed `value` and `other` objects.
    -     * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
    -     */
    -    function baseIsEqual(value, other, bitmask, customizer, stack) {
    -      if (value === other) {
    -        return true;
    -      }
    -      if (value == null || other == null || !isObjectLike(value) && !isObjectLike(other)) {
    -        return value !== value && other !== other;
    -      }
    -      return baseIsEqualDeep(value, other, bitmask, customizer, baseIsEqual, stack);
    -    }
    -
    -    /**
    -     * A specialized version of `baseIsEqual` for arrays and objects which performs
    -     * deep comparisons and tracks traversed objects enabling objects with circular
    -     * references to be compared.
    -     *
    -     * @private
    -     * @param {Object} object The object to compare.
    -     * @param {Object} other The other object to compare.
    -     * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.
    -     * @param {Function} customizer The function to customize comparisons.
    -     * @param {Function} equalFunc The function to determine equivalents of values.
    -     * @param {Object} [stack] Tracks traversed `object` and `other` objects.
    -     * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
    -     */
    -    function baseIsEqualDeep(object, other, bitmask, customizer, equalFunc, stack) {
    -      var objIsArr = isArray(object),
    -        othIsArr = isArray(other),
    -        objTag = objIsArr ? arrayTag : getTag(object),
    -        othTag = othIsArr ? arrayTag : getTag(other);
    -      objTag = objTag == argsTag ? objectTag : objTag;
    -      othTag = othTag == argsTag ? objectTag : othTag;
    -      var objIsObj = objTag == objectTag,
    -        othIsObj = othTag == objectTag,
    -        isSameTag = objTag == othTag;
    -      if (isSameTag && isBuffer(object)) {
    -        if (!isBuffer(other)) {
    -          return false;
    -        }
    -        objIsArr = true;
    -        objIsObj = false;
    -      }
    -      if (isSameTag && !objIsObj) {
    -        stack || (stack = new Stack());
    -        return objIsArr || isTypedArray(object) ? equalArrays(object, other, bitmask, customizer, equalFunc, stack) : equalByTag(object, other, objTag, bitmask, customizer, equalFunc, stack);
    -      }
    -      if (!(bitmask & COMPARE_PARTIAL_FLAG)) {
    -        var objIsWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__'),
    -          othIsWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__');
    -        if (objIsWrapped || othIsWrapped) {
    -          var objUnwrapped = objIsWrapped ? object.value() : object,
    -            othUnwrapped = othIsWrapped ? other.value() : other;
    -          stack || (stack = new Stack());
    -          return equalFunc(objUnwrapped, othUnwrapped, bitmask, customizer, stack);
    -        }
    -      }
    -      if (!isSameTag) {
    -        return false;
    -      }
    -      stack || (stack = new Stack());
    -      return equalObjects(object, other, bitmask, customizer, equalFunc, stack);
    -    }
    -
    -    /**
    -     * The base implementation of `_.isMap` without Node.js optimizations.
    -     *
    -     * @private
    -     * @param {*} value The value to check.
    -     * @returns {boolean} Returns `true` if `value` is a map, else `false`.
    -     */
    -    function baseIsMap(value) {
    -      return isObjectLike(value) && getTag(value) == mapTag;
    -    }
    -
    -    /**
    -     * The base implementation of `_.isMatch` without support for iteratee shorthands.
    -     *
    -     * @private
    -     * @param {Object} object The object to inspect.
    -     * @param {Object} source The object of property values to match.
    -     * @param {Array} matchData The property names, values, and compare flags to match.
    -     * @param {Function} [customizer] The function to customize comparisons.
    -     * @returns {boolean} Returns `true` if `object` is a match, else `false`.
    -     */
    -    function baseIsMatch(object, source, matchData, customizer) {
    -      var index = matchData.length,
    -        length = index,
    -        noCustomizer = !customizer;
    -      if (object == null) {
    -        return !length;
    -      }
    -      object = Object(object);
    -      while (index--) {
    -        var data = matchData[index];
    -        if (noCustomizer && data[2] ? data[1] !== object[data[0]] : !(data[0] in object)) {
    -          return false;
    -        }
    -      }
    -      while (++index < length) {
    -        data = matchData[index];
    -        var key = data[0],
    -          objValue = object[key],
    -          srcValue = data[1];
    -        if (noCustomizer && data[2]) {
    -          if (objValue === undefined && !(key in object)) {
    -            return false;
    -          }
    -        } else {
    -          var stack = new Stack();
    -          if (customizer) {
    -            var result = customizer(objValue, srcValue, key, object, source, stack);
    -          }
    -          if (!(result === undefined ? baseIsEqual(srcValue, objValue, COMPARE_PARTIAL_FLAG | COMPARE_UNORDERED_FLAG, customizer, stack) : result)) {
    -            return false;
    -          }
    -        }
    -      }
    -      return true;
    -    }
    -
    -    /**
    -     * The base implementation of `_.isNative` without bad shim checks.
    -     *
    -     * @private
    -     * @param {*} value The value to check.
    -     * @returns {boolean} Returns `true` if `value` is a native function,
    -     *  else `false`.
    -     */
    -    function baseIsNative(value) {
    -      if (!isObject(value) || isMasked(value)) {
    -        return false;
    -      }
    -      var pattern = isFunction(value) ? reIsNative : reIsHostCtor;
    -      return pattern.test(toSource(value));
    -    }
    -
    -    /**
    -     * The base implementation of `_.isRegExp` without Node.js optimizations.
    -     *
    -     * @private
    -     * @param {*} value The value to check.
    -     * @returns {boolean} Returns `true` if `value` is a regexp, else `false`.
    -     */
    -    function baseIsRegExp(value) {
    -      return isObjectLike(value) && baseGetTag(value) == regexpTag;
    -    }
    -
    -    /**
    -     * The base implementation of `_.isSet` without Node.js optimizations.
    -     *
    -     * @private
    -     * @param {*} value The value to check.
    -     * @returns {boolean} Returns `true` if `value` is a set, else `false`.
    -     */
    -    function baseIsSet(value) {
    -      return isObjectLike(value) && getTag(value) == setTag;
    -    }
    -
    -    /**
    -     * The base implementation of `_.isTypedArray` without Node.js optimizations.
    -     *
    -     * @private
    -     * @param {*} value The value to check.
    -     * @returns {boolean} Returns `true` if `value` is a typed array, else `false`.
    -     */
    -    function baseIsTypedArray(value) {
    -      return isObjectLike(value) && isLength(value.length) && !!typedArrayTags[baseGetTag(value)];
    -    }
    -
    -    /**
    -     * The base implementation of `_.iteratee`.
    -     *
    -     * @private
    -     * @param {*} [value=_.identity] The value to convert to an iteratee.
    -     * @returns {Function} Returns the iteratee.
    -     */
    -    function baseIteratee(value) {
    -      // Don't store the `typeof` result in a variable to avoid a JIT bug in Safari 9.
    -      // See https://bugs.webkit.org/show_bug.cgi?id=156034 for more details.
    -      if (typeof value == 'function') {
    -        return value;
    -      }
    -      if (value == null) {
    -        return identity;
    -      }
    -      if (typeof value == 'object') {
    -        return isArray(value) ? baseMatchesProperty(value[0], value[1]) : baseMatches(value);
    -      }
    -      return property(value);
    -    }
    -
    -    /**
    -     * The base implementation of `_.keys` which doesn't treat sparse arrays as dense.
    -     *
    -     * @private
    -     * @param {Object} object The object to query.
    -     * @returns {Array} Returns the array of property names.
    -     */
    -    function baseKeys(object) {
    -      if (!isPrototype(object)) {
    -        return nativeKeys(object);
    -      }
    -      var result = [];
    -      for (var key in Object(object)) {
    -        if (hasOwnProperty.call(object, key) && key != 'constructor') {
    -          result.push(key);
    -        }
    -      }
    -      return result;
    -    }
    -
    -    /**
    -     * The base implementation of `_.keysIn` which doesn't treat sparse arrays as dense.
    -     *
    -     * @private
    -     * @param {Object} object The object to query.
    -     * @returns {Array} Returns the array of property names.
    -     */
    -    function baseKeysIn(object) {
    -      if (!isObject(object)) {
    -        return nativeKeysIn(object);
    -      }
    -      var isProto = isPrototype(object),
    -        result = [];
    -      for (var key in object) {
    -        if (!(key == 'constructor' && (isProto || !hasOwnProperty.call(object, key)))) {
    -          result.push(key);
    -        }
    -      }
    -      return result;
    -    }
    -
    -    /**
    -     * The base implementation of `_.lt` which doesn't coerce arguments.
    -     *
    -     * @private
    -     * @param {*} value The value to compare.
    -     * @param {*} other The other value to compare.
    -     * @returns {boolean} Returns `true` if `value` is less than `other`,
    -     *  else `false`.
    -     */
    -    function baseLt(value, other) {
    -      return value < other;
    -    }
    -
    -    /**
    -     * The base implementation of `_.map` without support for iteratee shorthands.
    -     *
    -     * @private
    -     * @param {Array|Object} collection The collection to iterate over.
    -     * @param {Function} iteratee The function invoked per iteration.
    -     * @returns {Array} Returns the new mapped array.
    -     */
    -    function baseMap(collection, iteratee) {
    -      var index = -1,
    -        result = isArrayLike(collection) ? Array(collection.length) : [];
    -      baseEach(collection, function (value, key, collection) {
    -        result[++index] = iteratee(value, key, collection);
    -      });
    -      return result;
    -    }
    -
    -    /**
    -     * The base implementation of `_.matches` which doesn't clone `source`.
    -     *
    -     * @private
    -     * @param {Object} source The object of property values to match.
    -     * @returns {Function} Returns the new spec function.
    -     */
    -    function baseMatches(source) {
    -      var matchData = getMatchData(source);
    -      if (matchData.length == 1 && matchData[0][2]) {
    -        return matchesStrictComparable(matchData[0][0], matchData[0][1]);
    -      }
    -      return function (object) {
    -        return object === source || baseIsMatch(object, source, matchData);
    -      };
    -    }
    -
    -    /**
    -     * The base implementation of `_.matchesProperty` which doesn't clone `srcValue`.
    -     *
    -     * @private
    -     * @param {string} path The path of the property to get.
    -     * @param {*} srcValue The value to match.
    -     * @returns {Function} Returns the new spec function.
    -     */
    -    function baseMatchesProperty(path, srcValue) {
    -      if (isKey(path) && isStrictComparable(srcValue)) {
    -        return matchesStrictComparable(toKey(path), srcValue);
    -      }
    -      return function (object) {
    -        var objValue = get(object, path);
    -        return objValue === undefined && objValue === srcValue ? hasIn(object, path) : baseIsEqual(srcValue, objValue, COMPARE_PARTIAL_FLAG | COMPARE_UNORDERED_FLAG);
    -      };
    -    }
    -
    -    /**
    -     * The base implementation of `_.merge` without support for multiple sources.
    -     *
    -     * @private
    -     * @param {Object} object The destination object.
    -     * @param {Object} source The source object.
    -     * @param {number} srcIndex The index of `source`.
    -     * @param {Function} [customizer] The function to customize merged values.
    -     * @param {Object} [stack] Tracks traversed source values and their merged
    -     *  counterparts.
    -     */
    -    function baseMerge(object, source, srcIndex, customizer, stack) {
    -      if (object === source) {
    -        return;
    -      }
    -      baseFor(source, function (srcValue, key) {
    -        stack || (stack = new Stack());
    -        if (isObject(srcValue)) {
    -          baseMergeDeep(object, source, key, srcIndex, baseMerge, customizer, stack);
    -        } else {
    -          var newValue = customizer ? customizer(safeGet(object, key), srcValue, key + '', object, source, stack) : undefined;
    -          if (newValue === undefined) {
    -            newValue = srcValue;
    -          }
    -          assignMergeValue(object, key, newValue);
    -        }
    -      }, keysIn);
    -    }
    -
    -    /**
    -     * A specialized version of `baseMerge` for arrays and objects which performs
    -     * deep merges and tracks traversed objects enabling objects with circular
    -     * references to be merged.
    -     *
    -     * @private
    -     * @param {Object} object The destination object.
    -     * @param {Object} source The source object.
    -     * @param {string} key The key of the value to merge.
    -     * @param {number} srcIndex The index of `source`.
    -     * @param {Function} mergeFunc The function to merge values.
    -     * @param {Function} [customizer] The function to customize assigned values.
    -     * @param {Object} [stack] Tracks traversed source values and their merged
    -     *  counterparts.
    -     */
    -    function baseMergeDeep(object, source, key, srcIndex, mergeFunc, customizer, stack) {
    -      var objValue = safeGet(object, key),
    -        srcValue = safeGet(source, key),
    -        stacked = stack.get(srcValue);
    -      if (stacked) {
    -        assignMergeValue(object, key, stacked);
    -        return;
    -      }
    -      var newValue = customizer ? customizer(objValue, srcValue, key + '', object, source, stack) : undefined;
    -      var isCommon = newValue === undefined;
    -      if (isCommon) {
    -        var isArr = isArray(srcValue),
    -          isBuff = !isArr && isBuffer(srcValue),
    -          isTyped = !isArr && !isBuff && isTypedArray(srcValue);
    -        newValue = srcValue;
    -        if (isArr || isBuff || isTyped) {
    -          if (isArray(objValue)) {
    -            newValue = objValue;
    -          } else if (isArrayLikeObject(objValue)) {
    -            newValue = copyArray(objValue);
    -          } else if (isBuff) {
    -            isCommon = false;
    -            newValue = cloneBuffer(srcValue, true);
    -          } else if (isTyped) {
    -            isCommon = false;
    -            newValue = cloneTypedArray(srcValue, true);
    -          } else {
    -            newValue = [];
    -          }
    -        } else if (isPlainObject(srcValue) || isArguments(srcValue)) {
    -          newValue = objValue;
    -          if (isArguments(objValue)) {
    -            newValue = toPlainObject(objValue);
    -          } else if (!isObject(objValue) || isFunction(objValue)) {
    -            newValue = initCloneObject(srcValue);
    -          }
    -        } else {
    -          isCommon = false;
    -        }
    -      }
    -      if (isCommon) {
    -        // Recursively merge objects and arrays (susceptible to call stack limits).
    -        stack.set(srcValue, newValue);
    -        mergeFunc(newValue, srcValue, srcIndex, customizer, stack);
    -        stack['delete'](srcValue);
    -      }
    -      assignMergeValue(object, key, newValue);
    -    }
    -
    -    /**
    -     * The base implementation of `_.nth` which doesn't coerce arguments.
    -     *
    -     * @private
    -     * @param {Array} array The array to query.
    -     * @param {number} n The index of the element to return.
    -     * @returns {*} Returns the nth element of `array`.
    -     */
    -    function baseNth(array, n) {
    -      var length = array.length;
    -      if (!length) {
    -        return;
    -      }
    -      n += n < 0 ? length : 0;
    -      return isIndex(n, length) ? array[n] : undefined;
    -    }
    -
    -    /**
    -     * The base implementation of `_.orderBy` without param guards.
    -     *
    -     * @private
    -     * @param {Array|Object} collection The collection to iterate over.
    -     * @param {Function[]|Object[]|string[]} iteratees The iteratees to sort by.
    -     * @param {string[]} orders The sort orders of `iteratees`.
    -     * @returns {Array} Returns the new sorted array.
    -     */
    -    function baseOrderBy(collection, iteratees, orders) {
    -      if (iteratees.length) {
    -        iteratees = arrayMap(iteratees, function (iteratee) {
    -          if (isArray(iteratee)) {
    -            return function (value) {
    -              return baseGet(value, iteratee.length === 1 ? iteratee[0] : iteratee);
    -            };
    -          }
    -          return iteratee;
    -        });
    -      } else {
    -        iteratees = [identity];
    -      }
    -      var index = -1;
    -      iteratees = arrayMap(iteratees, baseUnary(getIteratee()));
    -      var result = baseMap(collection, function (value, key, collection) {
    -        var criteria = arrayMap(iteratees, function (iteratee) {
    -          return iteratee(value);
    -        });
    -        return {
    -          'criteria': criteria,
    -          'index': ++index,
    -          'value': value
    -        };
    -      });
    -      return baseSortBy(result, function (object, other) {
    -        return compareMultiple(object, other, orders);
    -      });
    -    }
    -
    -    /**
    -     * The base implementation of `_.pick` without support for individual
    -     * property identifiers.
    -     *
    -     * @private
    -     * @param {Object} object The source object.
    -     * @param {string[]} paths The property paths to pick.
    -     * @returns {Object} Returns the new object.
    -     */
    -    function basePick(object, paths) {
    -      return basePickBy(object, paths, function (value, path) {
    -        return hasIn(object, path);
    -      });
    -    }
    -
    -    /**
    -     * The base implementation of  `_.pickBy` without support for iteratee shorthands.
    -     *
    -     * @private
    -     * @param {Object} object The source object.
    -     * @param {string[]} paths The property paths to pick.
    -     * @param {Function} predicate The function invoked per property.
    -     * @returns {Object} Returns the new object.
    -     */
    -    function basePickBy(object, paths, predicate) {
    -      var index = -1,
    -        length = paths.length,
    -        result = {};
    -      while (++index < length) {
    -        var path = paths[index],
    -          value = baseGet(object, path);
    -        if (predicate(value, path)) {
    -          baseSet(result, castPath(path, object), value);
    -        }
    -      }
    -      return result;
    -    }
    -
    -    /**
    -     * A specialized version of `baseProperty` which supports deep paths.
    -     *
    -     * @private
    -     * @param {Array|string} path The path of the property to get.
    -     * @returns {Function} Returns the new accessor function.
    -     */
    -    function basePropertyDeep(path) {
    -      return function (object) {
    -        return baseGet(object, path);
    -      };
    -    }
    -
    -    /**
    -     * The base implementation of `_.pullAllBy` without support for iteratee
    -     * shorthands.
    -     *
    -     * @private
    -     * @param {Array} array The array to modify.
    -     * @param {Array} values The values to remove.
    -     * @param {Function} [iteratee] The iteratee invoked per element.
    -     * @param {Function} [comparator] The comparator invoked per element.
    -     * @returns {Array} Returns `array`.
    -     */
    -    function basePullAll(array, values, iteratee, comparator) {
    -      var indexOf = comparator ? baseIndexOfWith : baseIndexOf,
    -        index = -1,
    -        length = values.length,
    -        seen = array;
    -      if (array === values) {
    -        values = copyArray(values);
    -      }
    -      if (iteratee) {
    -        seen = arrayMap(array, baseUnary(iteratee));
    -      }
    -      while (++index < length) {
    -        var fromIndex = 0,
    -          value = values[index],
    -          computed = iteratee ? iteratee(value) : value;
    -        while ((fromIndex = indexOf(seen, computed, fromIndex, comparator)) > -1) {
    -          if (seen !== array) {
    -            splice.call(seen, fromIndex, 1);
    -          }
    -          splice.call(array, fromIndex, 1);
    -        }
    -      }
    -      return array;
    -    }
    -
    -    /**
    -     * The base implementation of `_.pullAt` without support for individual
    -     * indexes or capturing the removed elements.
    -     *
    -     * @private
    -     * @param {Array} array The array to modify.
    -     * @param {number[]} indexes The indexes of elements to remove.
    -     * @returns {Array} Returns `array`.
    -     */
    -    function basePullAt(array, indexes) {
    -      var length = array ? indexes.length : 0,
    -        lastIndex = length - 1;
    -      while (length--) {
    -        var index = indexes[length];
    -        if (length == lastIndex || index !== previous) {
    -          var previous = index;
    -          if (isIndex(index)) {
    -            splice.call(array, index, 1);
    -          } else {
    -            baseUnset(array, index);
    -          }
    -        }
    -      }
    -      return array;
    -    }
    -
    -    /**
    -     * The base implementation of `_.random` without support for returning
    -     * floating-point numbers.
    -     *
    -     * @private
    -     * @param {number} lower The lower bound.
    -     * @param {number} upper The upper bound.
    -     * @returns {number} Returns the random number.
    -     */
    -    function baseRandom(lower, upper) {
    -      return lower + nativeFloor(nativeRandom() * (upper - lower + 1));
    -    }
    -
    -    /**
    -     * The base implementation of `_.range` and `_.rangeRight` which doesn't
    -     * coerce arguments.
    -     *
    -     * @private
    -     * @param {number} start The start of the range.
    -     * @param {number} end The end of the range.
    -     * @param {number} step The value to increment or decrement by.
    -     * @param {boolean} [fromRight] Specify iterating from right to left.
    -     * @returns {Array} Returns the range of numbers.
    -     */
    -    function baseRange(start, end, step, fromRight) {
    -      var index = -1,
    -        length = nativeMax(nativeCeil((end - start) / (step || 1)), 0),
    -        result = Array(length);
    -      while (length--) {
    -        result[fromRight ? length : ++index] = start;
    -        start += step;
    -      }
    -      return result;
    -    }
    -
    -    /**
    -     * The base implementation of `_.repeat` which doesn't coerce arguments.
    -     *
    -     * @private
    -     * @param {string} string The string to repeat.
    -     * @param {number} n The number of times to repeat the string.
    -     * @returns {string} Returns the repeated string.
    -     */
    -    function baseRepeat(string, n) {
    -      var result = '';
    -      if (!string || n < 1 || n > MAX_SAFE_INTEGER) {
    -        return result;
    -      }
    -      // Leverage the exponentiation by squaring algorithm for a faster repeat.
    -      // See https://en.wikipedia.org/wiki/Exponentiation_by_squaring for more details.
    -      do {
    -        if (n % 2) {
    -          result += string;
    -        }
    -        n = nativeFloor(n / 2);
    -        if (n) {
    -          string += string;
    -        }
    -      } while (n);
    -      return result;
    -    }
    -
    -    /**
    -     * The base implementation of `_.rest` which doesn't validate or coerce arguments.
    -     *
    -     * @private
    -     * @param {Function} func The function to apply a rest parameter to.
    -     * @param {number} [start=func.length-1] The start position of the rest parameter.
    -     * @returns {Function} Returns the new function.
    -     */
    -    function baseRest(func, start) {
    -      return setToString(overRest(func, start, identity), func + '');
    -    }
    -
    -    /**
    -     * The base implementation of `_.sample`.
    -     *
    -     * @private
    -     * @param {Array|Object} collection The collection to sample.
    -     * @returns {*} Returns the random element.
    -     */
    -    function baseSample(collection) {
    -      return arraySample(values(collection));
    -    }
    -
    -    /**
    -     * The base implementation of `_.sampleSize` without param guards.
    -     *
    -     * @private
    -     * @param {Array|Object} collection The collection to sample.
    -     * @param {number} n The number of elements to sample.
    -     * @returns {Array} Returns the random elements.
    -     */
    -    function baseSampleSize(collection, n) {
    -      var array = values(collection);
    -      return shuffleSelf(array, baseClamp(n, 0, array.length));
    -    }
    -
    -    /**
    -     * The base implementation of `_.set`.
    -     *
    -     * @private
    -     * @param {Object} object The object to modify.
    -     * @param {Array|string} path The path of the property to set.
    -     * @param {*} value The value to set.
    -     * @param {Function} [customizer] The function to customize path creation.
    -     * @returns {Object} Returns `object`.
    -     */
    -    function baseSet(object, path, value, customizer) {
    -      if (!isObject(object)) {
    -        return object;
    -      }
    -      path = castPath(path, object);
    -      var index = -1,
    -        length = path.length,
    -        lastIndex = length - 1,
    -        nested = object;
    -      while (nested != null && ++index < length) {
    -        var key = toKey(path[index]),
    -          newValue = value;
    -        if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
    -          return object;
    -        }
    -        if (index != lastIndex) {
    -          var objValue = nested[key];
    -          newValue = customizer ? customizer(objValue, key, nested) : undefined;
    -          if (newValue === undefined) {
    -            newValue = isObject(objValue) ? objValue : isIndex(path[index + 1]) ? [] : {};
    -          }
    -        }
    -        assignValue(nested, key, newValue);
    -        nested = nested[key];
    -      }
    -      return object;
    -    }
    -
    -    /**
    -     * The base implementation of `setData` without support for hot loop shorting.
    -     *
    -     * @private
    -     * @param {Function} func The function to associate metadata with.
    -     * @param {*} data The metadata.
    -     * @returns {Function} Returns `func`.
    -     */
    -    var baseSetData = !metaMap ? identity : function (func, data) {
    -      metaMap.set(func, data);
    -      return func;
    -    };
    -
    -    /**
    -     * The base implementation of `setToString` without support for hot loop shorting.
    -     *
    -     * @private
    -     * @param {Function} func The function to modify.
    -     * @param {Function} string The `toString` result.
    -     * @returns {Function} Returns `func`.
    -     */
    -    var baseSetToString = !defineProperty ? identity : function (func, string) {
    -      return defineProperty(func, 'toString', {
    -        'configurable': true,
    -        'enumerable': false,
    -        'value': constant(string),
    -        'writable': true
    -      });
    -    };
    -
    -    /**
    -     * The base implementation of `_.shuffle`.
    -     *
    -     * @private
    -     * @param {Array|Object} collection The collection to shuffle.
    -     * @returns {Array} Returns the new shuffled array.
    -     */
    -    function baseShuffle(collection) {
    -      return shuffleSelf(values(collection));
    -    }
    -
    -    /**
    -     * The base implementation of `_.slice` without an iteratee call guard.
    -     *
    -     * @private
    -     * @param {Array} array The array to slice.
    -     * @param {number} [start=0] The start position.
    -     * @param {number} [end=array.length] The end position.
    -     * @returns {Array} Returns the slice of `array`.
    -     */
    -    function baseSlice(array, start, end) {
    -      var index = -1,
    -        length = array.length;
    -      if (start < 0) {
    -        start = -start > length ? 0 : length + start;
    -      }
    -      end = end > length ? length : end;
    -      if (end < 0) {
    -        end += length;
    -      }
    -      length = start > end ? 0 : end - start >>> 0;
    -      start >>>= 0;
    -      var result = Array(length);
    -      while (++index < length) {
    -        result[index] = array[index + start];
    -      }
    -      return result;
    -    }
    -
    -    /**
    -     * The base implementation of `_.some` without support for iteratee shorthands.
    -     *
    -     * @private
    -     * @param {Array|Object} collection The collection to iterate over.
    -     * @param {Function} predicate The function invoked per iteration.
    -     * @returns {boolean} Returns `true` if any element passes the predicate check,
    -     *  else `false`.
    -     */
    -    function baseSome(collection, predicate) {
    -      var result;
    -      baseEach(collection, function (value, index, collection) {
    -        result = predicate(value, index, collection);
    -        return !result;
    -      });
    -      return !!result;
    -    }
    -
    -    /**
    -     * The base implementation of `_.sortedIndex` and `_.sortedLastIndex` which
    -     * performs a binary search of `array` to determine the index at which `value`
    -     * should be inserted into `array` in order to maintain its sort order.
    -     *
    -     * @private
    -     * @param {Array} array The sorted array to inspect.
    -     * @param {*} value The value to evaluate.
    -     * @param {boolean} [retHighest] Specify returning the highest qualified index.
    -     * @returns {number} Returns the index at which `value` should be inserted
    -     *  into `array`.
    -     */
    -    function baseSortedIndex(array, value, retHighest) {
    -      var low = 0,
    -        high = array == null ? low : array.length;
    -      if (typeof value == 'number' && value === value && high <= HALF_MAX_ARRAY_LENGTH) {
    -        while (low < high) {
    -          var mid = low + high >>> 1,
    -            computed = array[mid];
    -          if (computed !== null && !isSymbol(computed) && (retHighest ? computed <= value : computed < value)) {
    -            low = mid + 1;
    -          } else {
    -            high = mid;
    -          }
    -        }
    -        return high;
    -      }
    -      return baseSortedIndexBy(array, value, identity, retHighest);
    -    }
    -
    -    /**
    -     * The base implementation of `_.sortedIndexBy` and `_.sortedLastIndexBy`
    -     * which invokes `iteratee` for `value` and each element of `array` to compute
    -     * their sort ranking. The iteratee is invoked with one argument; (value).
    -     *
    -     * @private
    -     * @param {Array} array The sorted array to inspect.
    -     * @param {*} value The value to evaluate.
    -     * @param {Function} iteratee The iteratee invoked per element.
    -     * @param {boolean} [retHighest] Specify returning the highest qualified index.
    -     * @returns {number} Returns the index at which `value` should be inserted
    -     *  into `array`.
    -     */
    -    function baseSortedIndexBy(array, value, iteratee, retHighest) {
    -      var low = 0,
    -        high = array == null ? 0 : array.length;
    -      if (high === 0) {
    -        return 0;
    -      }
    -      value = iteratee(value);
    -      var valIsNaN = value !== value,
    -        valIsNull = value === null,
    -        valIsSymbol = isSymbol(value),
    -        valIsUndefined = value === undefined;
    -      while (low < high) {
    -        var mid = nativeFloor((low + high) / 2),
    -          computed = iteratee(array[mid]),
    -          othIsDefined = computed !== undefined,
    -          othIsNull = computed === null,
    -          othIsReflexive = computed === computed,
    -          othIsSymbol = isSymbol(computed);
    -        if (valIsNaN) {
    -          var setLow = retHighest || othIsReflexive;
    -        } else if (valIsUndefined) {
    -          setLow = othIsReflexive && (retHighest || othIsDefined);
    -        } else if (valIsNull) {
    -          setLow = othIsReflexive && othIsDefined && (retHighest || !othIsNull);
    -        } else if (valIsSymbol) {
    -          setLow = othIsReflexive && othIsDefined && !othIsNull && (retHighest || !othIsSymbol);
    -        } else if (othIsNull || othIsSymbol) {
    -          setLow = false;
    -        } else {
    -          setLow = retHighest ? computed <= value : computed < value;
    -        }
    -        if (setLow) {
    -          low = mid + 1;
    -        } else {
    -          high = mid;
    -        }
    -      }
    -      return nativeMin(high, MAX_ARRAY_INDEX);
    -    }
    -
    -    /**
    -     * The base implementation of `_.sortedUniq` and `_.sortedUniqBy` without
    -     * support for iteratee shorthands.
    -     *
    -     * @private
    -     * @param {Array} array The array to inspect.
    -     * @param {Function} [iteratee] The iteratee invoked per element.
    -     * @returns {Array} Returns the new duplicate free array.
    -     */
    -    function baseSortedUniq(array, iteratee) {
    -      var index = -1,
    -        length = array.length,
    -        resIndex = 0,
    -        result = [];
    -      while (++index < length) {
    -        var value = array[index],
    -          computed = iteratee ? iteratee(value) : value;
    -        if (!index || !eq(computed, seen)) {
    -          var seen = computed;
    -          result[resIndex++] = value === 0 ? 0 : value;
    -        }
    -      }
    -      return result;
    -    }
    -
    -    /**
    -     * The base implementation of `_.toNumber` which doesn't ensure correct
    -     * conversions of binary, hexadecimal, or octal string values.
    -     *
    -     * @private
    -     * @param {*} value The value to process.
    -     * @returns {number} Returns the number.
    -     */
    -    function baseToNumber(value) {
    -      if (typeof value == 'number') {
    -        return value;
    -      }
    -      if (isSymbol(value)) {
    -        return NAN;
    -      }
    -      return +value;
    -    }
    -
    -    /**
    -     * The base implementation of `_.toString` which doesn't convert nullish
    -     * values to empty strings.
    -     *
    -     * @private
    -     * @param {*} value The value to process.
    -     * @returns {string} Returns the string.
    -     */
    -    function baseToString(value) {
    -      // Exit early for strings to avoid a performance hit in some environments.
    -      if (typeof value == 'string') {
    -        return value;
    -      }
    -      if (isArray(value)) {
    -        // Recursively convert values (susceptible to call stack limits).
    -        return arrayMap(value, baseToString) + '';
    -      }
    -      if (isSymbol(value)) {
    -        return symbolToString ? symbolToString.call(value) : '';
    -      }
    -      var result = value + '';
    -      return result == '0' && 1 / value == -INFINITY ? '-0' : result;
    -    }
    -
    -    /**
    -     * The base implementation of `_.uniqBy` without support for iteratee shorthands.
    -     *
    -     * @private
    -     * @param {Array} array The array to inspect.
    -     * @param {Function} [iteratee] The iteratee invoked per element.
    -     * @param {Function} [comparator] The comparator invoked per element.
    -     * @returns {Array} Returns the new duplicate free array.
    -     */
    -    function baseUniq(array, iteratee, comparator) {
    -      var index = -1,
    -        includes = arrayIncludes,
    -        length = array.length,
    -        isCommon = true,
    -        result = [],
    -        seen = result;
    -      if (comparator) {
    -        isCommon = false;
    -        includes = arrayIncludesWith;
    -      } else if (length >= LARGE_ARRAY_SIZE) {
    -        var set = iteratee ? null : createSet(array);
    -        if (set) {
    -          return setToArray(set);
    -        }
    -        isCommon = false;
    -        includes = cacheHas;
    -        seen = new SetCache();
    -      } else {
    -        seen = iteratee ? [] : result;
    -      }
    -      outer: while (++index < length) {
    -        var value = array[index],
    -          computed = iteratee ? iteratee(value) : value;
    -        value = comparator || value !== 0 ? value : 0;
    -        if (isCommon && computed === computed) {
    -          var seenIndex = seen.length;
    -          while (seenIndex--) {
    -            if (seen[seenIndex] === computed) {
    -              continue outer;
    -            }
    -          }
    -          if (iteratee) {
    -            seen.push(computed);
    -          }
    -          result.push(value);
    -        } else if (!includes(seen, computed, comparator)) {
    -          if (seen !== result) {
    -            seen.push(computed);
    -          }
    -          result.push(value);
    -        }
    -      }
    -      return result;
    -    }
    -
    -    /**
    -     * The base implementation of `_.unset`.
    -     *
    -     * @private
    -     * @param {Object} object The object to modify.
    -     * @param {Array|string} path The property path to unset.
    -     * @returns {boolean} Returns `true` if the property is deleted, else `false`.
    -     */
    -    function baseUnset(object, path) {
    -      path = castPath(path, object);
    -      object = parent(object, path);
    -      return object == null || delete object[toKey(last(path))];
    -    }
    -
    -    /**
    -     * The base implementation of `_.update`.
    -     *
    -     * @private
    -     * @param {Object} object The object to modify.
    -     * @param {Array|string} path The path of the property to update.
    -     * @param {Function} updater The function to produce the updated value.
    -     * @param {Function} [customizer] The function to customize path creation.
    -     * @returns {Object} Returns `object`.
    -     */
    -    function baseUpdate(object, path, updater, customizer) {
    -      return baseSet(object, path, updater(baseGet(object, path)), customizer);
    -    }
    -
    -    /**
    -     * The base implementation of methods like `_.dropWhile` and `_.takeWhile`
    -     * without support for iteratee shorthands.
    -     *
    -     * @private
    -     * @param {Array} array The array to query.
    -     * @param {Function} predicate The function invoked per iteration.
    -     * @param {boolean} [isDrop] Specify dropping elements instead of taking them.
    -     * @param {boolean} [fromRight] Specify iterating from right to left.
    -     * @returns {Array} Returns the slice of `array`.
    -     */
    -    function baseWhile(array, predicate, isDrop, fromRight) {
    -      var length = array.length,
    -        index = fromRight ? length : -1;
    -      while ((fromRight ? index-- : ++index < length) && predicate(array[index], index, array)) {}
    -      return isDrop ? baseSlice(array, fromRight ? 0 : index, fromRight ? index + 1 : length) : baseSlice(array, fromRight ? index + 1 : 0, fromRight ? length : index);
    -    }
    -
    -    /**
    -     * The base implementation of `wrapperValue` which returns the result of
    -     * performing a sequence of actions on the unwrapped `value`, where each
    -     * successive action is supplied the return value of the previous.
    -     *
    -     * @private
    -     * @param {*} value The unwrapped value.
    -     * @param {Array} actions Actions to perform to resolve the unwrapped value.
    -     * @returns {*} Returns the resolved value.
    -     */
    -    function baseWrapperValue(value, actions) {
    -      var result = value;
    -      if (result instanceof LazyWrapper) {
    -        result = result.value();
    -      }
    -      return arrayReduce(actions, function (result, action) {
    -        return action.func.apply(action.thisArg, arrayPush([result], action.args));
    -      }, result);
    -    }
    -
    -    /**
    -     * The base implementation of methods like `_.xor`, without support for
    -     * iteratee shorthands, that accepts an array of arrays to inspect.
    -     *
    -     * @private
    -     * @param {Array} arrays The arrays to inspect.
    -     * @param {Function} [iteratee] The iteratee invoked per element.
    -     * @param {Function} [comparator] The comparator invoked per element.
    -     * @returns {Array} Returns the new array of values.
    -     */
    -    function baseXor(arrays, iteratee, comparator) {
    -      var length = arrays.length;
    -      if (length < 2) {
    -        return length ? baseUniq(arrays[0]) : [];
    -      }
    -      var index = -1,
    -        result = Array(length);
    -      while (++index < length) {
    -        var array = arrays[index],
    -          othIndex = -1;
    -        while (++othIndex < length) {
    -          if (othIndex != index) {
    -            result[index] = baseDifference(result[index] || array, arrays[othIndex], iteratee, comparator);
    -          }
    -        }
    -      }
    -      return baseUniq(baseFlatten(result, 1), iteratee, comparator);
    -    }
    -
    -    /**
    -     * This base implementation of `_.zipObject` which assigns values using `assignFunc`.
    -     *
    -     * @private
    -     * @param {Array} props The property identifiers.
    -     * @param {Array} values The property values.
    -     * @param {Function} assignFunc The function to assign values.
    -     * @returns {Object} Returns the new object.
    -     */
    -    function baseZipObject(props, values, assignFunc) {
    -      var index = -1,
    -        length = props.length,
    -        valsLength = values.length,
    -        result = {};
    -      while (++index < length) {
    -        var value = index < valsLength ? values[index] : undefined;
    -        assignFunc(result, props[index], value);
    -      }
    -      return result;
    -    }
    -
    -    /**
    -     * Casts `value` to an empty array if it's not an array like object.
    -     *
    -     * @private
    -     * @param {*} value The value to inspect.
    -     * @returns {Array|Object} Returns the cast array-like object.
    -     */
    -    function castArrayLikeObject(value) {
    -      return isArrayLikeObject(value) ? value : [];
    -    }
    -
    -    /**
    -     * Casts `value` to `identity` if it's not a function.
    -     *
    -     * @private
    -     * @param {*} value The value to inspect.
    -     * @returns {Function} Returns cast function.
    -     */
    -    function castFunction(value) {
    -      return typeof value == 'function' ? value : identity;
    -    }
    -
    -    /**
    -     * Casts `value` to a path array if it's not one.
    -     *
    -     * @private
    -     * @param {*} value The value to inspect.
    -     * @param {Object} [object] The object to query keys on.
    -     * @returns {Array} Returns the cast property path array.
    -     */
    -    function castPath(value, object) {
    -      if (isArray(value)) {
    -        return value;
    -      }
    -      return isKey(value, object) ? [value] : stringToPath(toString(value));
    -    }
    -
    -    /**
    -     * A `baseRest` alias which can be replaced with `identity` by module
    -     * replacement plugins.
    -     *
    -     * @private
    -     * @type {Function}
    -     * @param {Function} func The function to apply a rest parameter to.
    -     * @returns {Function} Returns the new function.
    -     */
    -    var castRest = baseRest;
    -
    -    /**
    -     * Casts `array` to a slice if it's needed.
    -     *
    -     * @private
    -     * @param {Array} array The array to inspect.
    -     * @param {number} start The start position.
    -     * @param {number} [end=array.length] The end position.
    -     * @returns {Array} Returns the cast slice.
    -     */
    -    function castSlice(array, start, end) {
    -      var length = array.length;
    -      end = end === undefined ? length : end;
    -      return !start && end >= length ? array : baseSlice(array, start, end);
    -    }
    -
    -    /**
    -     * A simple wrapper around the global [`clearTimeout`](https://mdn.io/clearTimeout).
    -     *
    -     * @private
    -     * @param {number|Object} id The timer id or timeout object of the timer to clear.
    -     */
    -    var clearTimeout = ctxClearTimeout || function (id) {
    -      return root.clearTimeout(id);
    -    };
    -
    -    /**
    -     * Creates a clone of  `buffer`.
    -     *
    -     * @private
    -     * @param {Buffer} buffer The buffer to clone.
    -     * @param {boolean} [isDeep] Specify a deep clone.
    -     * @returns {Buffer} Returns the cloned buffer.
    -     */
    -    function cloneBuffer(buffer, isDeep) {
    -      if (isDeep) {
    -        return buffer.slice();
    -      }
    -      var length = buffer.length,
    -        result = allocUnsafe ? allocUnsafe(length) : new buffer.constructor(length);
    -      buffer.copy(result);
    -      return result;
    -    }
    -
    -    /**
    -     * Creates a clone of `arrayBuffer`.
    -     *
    -     * @private
    -     * @param {ArrayBuffer} arrayBuffer The array buffer to clone.
    -     * @returns {ArrayBuffer} Returns the cloned array buffer.
    -     */
    -    function cloneArrayBuffer(arrayBuffer) {
    -      var result = new arrayBuffer.constructor(arrayBuffer.byteLength);
    -      new Uint8Array(result).set(new Uint8Array(arrayBuffer));
    -      return result;
    -    }
    -
    -    /**
    -     * Creates a clone of `dataView`.
    -     *
    -     * @private
    -     * @param {Object} dataView The data view to clone.
    -     * @param {boolean} [isDeep] Specify a deep clone.
    -     * @returns {Object} Returns the cloned data view.
    -     */
    -    function cloneDataView(dataView, isDeep) {
    -      var buffer = isDeep ? cloneArrayBuffer(dataView.buffer) : dataView.buffer;
    -      return new dataView.constructor(buffer, dataView.byteOffset, dataView.byteLength);
    -    }
    -
    -    /**
    -     * Creates a clone of `regexp`.
    -     *
    -     * @private
    -     * @param {Object} regexp The regexp to clone.
    -     * @returns {Object} Returns the cloned regexp.
    -     */
    -    function cloneRegExp(regexp) {
    -      var result = new regexp.constructor(regexp.source, reFlags.exec(regexp));
    -      result.lastIndex = regexp.lastIndex;
    -      return result;
    -    }
    -
    -    /**
    -     * Creates a clone of the `symbol` object.
    -     *
    -     * @private
    -     * @param {Object} symbol The symbol object to clone.
    -     * @returns {Object} Returns the cloned symbol object.
    -     */
    -    function cloneSymbol(symbol) {
    -      return symbolValueOf ? Object(symbolValueOf.call(symbol)) : {};
    -    }
    -
    -    /**
    -     * Creates a clone of `typedArray`.
    -     *
    -     * @private
    -     * @param {Object} typedArray The typed array to clone.
    -     * @param {boolean} [isDeep] Specify a deep clone.
    -     * @returns {Object} Returns the cloned typed array.
    -     */
    -    function cloneTypedArray(typedArray, isDeep) {
    -      var buffer = isDeep ? cloneArrayBuffer(typedArray.buffer) : typedArray.buffer;
    -      return new typedArray.constructor(buffer, typedArray.byteOffset, typedArray.length);
    -    }
    -
    -    /**
    -     * Compares values to sort them in ascending order.
    -     *
    -     * @private
    -     * @param {*} value The value to compare.
    -     * @param {*} other The other value to compare.
    -     * @returns {number} Returns the sort order indicator for `value`.
    -     */
    -    function compareAscending(value, other) {
    -      if (value !== other) {
    -        var valIsDefined = value !== undefined,
    -          valIsNull = value === null,
    -          valIsReflexive = value === value,
    -          valIsSymbol = isSymbol(value);
    -        var othIsDefined = other !== undefined,
    -          othIsNull = other === null,
    -          othIsReflexive = other === other,
    -          othIsSymbol = isSymbol(other);
    -        if (!othIsNull && !othIsSymbol && !valIsSymbol && value > other || valIsSymbol && othIsDefined && othIsReflexive && !othIsNull && !othIsSymbol || valIsNull && othIsDefined && othIsReflexive || !valIsDefined && othIsReflexive || !valIsReflexive) {
    -          return 1;
    -        }
    -        if (!valIsNull && !valIsSymbol && !othIsSymbol && value < other || othIsSymbol && valIsDefined && valIsReflexive && !valIsNull && !valIsSymbol || othIsNull && valIsDefined && valIsReflexive || !othIsDefined && valIsReflexive || !othIsReflexive) {
    -          return -1;
    -        }
    -      }
    -      return 0;
    -    }
    -
    -    /**
    -     * Used by `_.orderBy` to compare multiple properties of a value to another
    -     * and stable sort them.
    -     *
    -     * If `orders` is unspecified, all values are sorted in ascending order. Otherwise,
    -     * specify an order of "desc" for descending or "asc" for ascending sort order
    -     * of corresponding values.
    -     *
    -     * @private
    -     * @param {Object} object The object to compare.
    -     * @param {Object} other The other object to compare.
    -     * @param {boolean[]|string[]} orders The order to sort by for each property.
    -     * @returns {number} Returns the sort order indicator for `object`.
    -     */
    -    function compareMultiple(object, other, orders) {
    -      var index = -1,
    -        objCriteria = object.criteria,
    -        othCriteria = other.criteria,
    -        length = objCriteria.length,
    -        ordersLength = orders.length;
    -      while (++index < length) {
    -        var result = compareAscending(objCriteria[index], othCriteria[index]);
    -        if (result) {
    -          if (index >= ordersLength) {
    -            return result;
    -          }
    -          var order = orders[index];
    -          return result * (order == 'desc' ? -1 : 1);
    -        }
    -      }
    -      // Fixes an `Array#sort` bug in the JS engine embedded in Adobe applications
    -      // that causes it, under certain circumstances, to provide the same value for
    -      // `object` and `other`. See https://github.com/jashkenas/underscore/pull/1247
    -      // for more details.
    -      //
    -      // This also ensures a stable sort in V8 and other engines.
    -      // See https://bugs.chromium.org/p/v8/issues/detail?id=90 for more details.
    -      return object.index - other.index;
    -    }
    -
    -    /**
    -     * Creates an array that is the composition of partially applied arguments,
    -     * placeholders, and provided arguments into a single array of arguments.
    -     *
    -     * @private
    -     * @param {Array} args The provided arguments.
    -     * @param {Array} partials The arguments to prepend to those provided.
    -     * @param {Array} holders The `partials` placeholder indexes.
    -     * @params {boolean} [isCurried] Specify composing for a curried function.
    -     * @returns {Array} Returns the new array of composed arguments.
    -     */
    -    function composeArgs(args, partials, holders, isCurried) {
    -      var argsIndex = -1,
    -        argsLength = args.length,
    -        holdersLength = holders.length,
    -        leftIndex = -1,
    -        leftLength = partials.length,
    -        rangeLength = nativeMax(argsLength - holdersLength, 0),
    -        result = Array(leftLength + rangeLength),
    -        isUncurried = !isCurried;
    -      while (++leftIndex < leftLength) {
    -        result[leftIndex] = partials[leftIndex];
    -      }
    -      while (++argsIndex < holdersLength) {
    -        if (isUncurried || argsIndex < argsLength) {
    -          result[holders[argsIndex]] = args[argsIndex];
    -        }
    -      }
    -      while (rangeLength--) {
    -        result[leftIndex++] = args[argsIndex++];
    -      }
    -      return result;
    -    }
    -
    -    /**
    -     * This function is like `composeArgs` except that the arguments composition
    -     * is tailored for `_.partialRight`.
    -     *
    -     * @private
    -     * @param {Array} args The provided arguments.
    -     * @param {Array} partials The arguments to append to those provided.
    -     * @param {Array} holders The `partials` placeholder indexes.
    -     * @params {boolean} [isCurried] Specify composing for a curried function.
    -     * @returns {Array} Returns the new array of composed arguments.
    -     */
    -    function composeArgsRight(args, partials, holders, isCurried) {
    -      var argsIndex = -1,
    -        argsLength = args.length,
    -        holdersIndex = -1,
    -        holdersLength = holders.length,
    -        rightIndex = -1,
    -        rightLength = partials.length,
    -        rangeLength = nativeMax(argsLength - holdersLength, 0),
    -        result = Array(rangeLength + rightLength),
    -        isUncurried = !isCurried;
    -      while (++argsIndex < rangeLength) {
    -        result[argsIndex] = args[argsIndex];
    -      }
    -      var offset = argsIndex;
    -      while (++rightIndex < rightLength) {
    -        result[offset + rightIndex] = partials[rightIndex];
    -      }
    -      while (++holdersIndex < holdersLength) {
    -        if (isUncurried || argsIndex < argsLength) {
    -          result[offset + holders[holdersIndex]] = args[argsIndex++];
    -        }
    -      }
    -      return result;
    -    }
    -
    -    /**
    -     * Copies the values of `source` to `array`.
    -     *
    -     * @private
    -     * @param {Array} source The array to copy values from.
    -     * @param {Array} [array=[]] The array to copy values to.
    -     * @returns {Array} Returns `array`.
    -     */
    -    function copyArray(source, array) {
    -      var index = -1,
    -        length = source.length;
    -      array || (array = Array(length));
    -      while (++index < length) {
    -        array[index] = source[index];
    -      }
    -      return array;
    -    }
    -
    -    /**
    -     * Copies properties of `source` to `object`.
    -     *
    -     * @private
    -     * @param {Object} source The object to copy properties from.
    -     * @param {Array} props The property identifiers to copy.
    -     * @param {Object} [object={}] The object to copy properties to.
    -     * @param {Function} [customizer] The function to customize copied values.
    -     * @returns {Object} Returns `object`.
    -     */
    -    function copyObject(source, props, object, customizer) {
    -      var isNew = !object;
    -      object || (object = {});
    -      var index = -1,
    -        length = props.length;
    -      while (++index < length) {
    -        var key = props[index];
    -        var newValue = customizer ? customizer(object[key], source[key], key, object, source) : undefined;
    -        if (newValue === undefined) {
    -          newValue = source[key];
    -        }
    -        if (isNew) {
    -          baseAssignValue(object, key, newValue);
    -        } else {
    -          assignValue(object, key, newValue);
    -        }
    -      }
    -      return object;
    -    }
    -
    -    /**
    -     * Copies own symbols of `source` to `object`.
    -     *
    -     * @private
    -     * @param {Object} source The object to copy symbols from.
    -     * @param {Object} [object={}] The object to copy symbols to.
    -     * @returns {Object} Returns `object`.
    -     */
    -    function copySymbols(source, object) {
    -      return copyObject(source, getSymbols(source), object);
    -    }
    -
    -    /**
    -     * Copies own and inherited symbols of `source` to `object`.
    -     *
    -     * @private
    -     * @param {Object} source The object to copy symbols from.
    -     * @param {Object} [object={}] The object to copy symbols to.
    -     * @returns {Object} Returns `object`.
    -     */
    -    function copySymbolsIn(source, object) {
    -      return copyObject(source, getSymbolsIn(source), object);
    -    }
    -
    -    /**
    -     * Creates a function like `_.groupBy`.
    -     *
    -     * @private
    -     * @param {Function} setter The function to set accumulator values.
    -     * @param {Function} [initializer] The accumulator object initializer.
    -     * @returns {Function} Returns the new aggregator function.
    -     */
    -    function createAggregator(setter, initializer) {
    -      return function (collection, iteratee) {
    -        var func = isArray(collection) ? arrayAggregator : baseAggregator,
    -          accumulator = initializer ? initializer() : {};
    -        return func(collection, setter, getIteratee(iteratee, 2), accumulator);
    -      };
    -    }
    -
    -    /**
    -     * Creates a function like `_.assign`.
    -     *
    -     * @private
    -     * @param {Function} assigner The function to assign values.
    -     * @returns {Function} Returns the new assigner function.
    -     */
    -    function createAssigner(assigner) {
    -      return baseRest(function (object, sources) {
    -        var index = -1,
    -          length = sources.length,
    -          customizer = length > 1 ? sources[length - 1] : undefined,
    -          guard = length > 2 ? sources[2] : undefined;
    -        customizer = assigner.length > 3 && typeof customizer == 'function' ? (length--, customizer) : undefined;
    -        if (guard && isIterateeCall(sources[0], sources[1], guard)) {
    -          customizer = length < 3 ? undefined : customizer;
    -          length = 1;
    -        }
    -        object = Object(object);
    -        while (++index < length) {
    -          var source = sources[index];
    -          if (source) {
    -            assigner(object, source, index, customizer);
    -          }
    -        }
    -        return object;
    -      });
    -    }
    -
    -    /**
    -     * Creates a `baseEach` or `baseEachRight` function.
    -     *
    -     * @private
    -     * @param {Function} eachFunc The function to iterate over a collection.
    -     * @param {boolean} [fromRight] Specify iterating from right to left.
    -     * @returns {Function} Returns the new base function.
    -     */
    -    function createBaseEach(eachFunc, fromRight) {
    -      return function (collection, iteratee) {
    -        if (collection == null) {
    -          return collection;
    -        }
    -        if (!isArrayLike(collection)) {
    -          return eachFunc(collection, iteratee);
    -        }
    -        var length = collection.length,
    -          index = fromRight ? length : -1,
    -          iterable = Object(collection);
    -        while (fromRight ? index-- : ++index < length) {
    -          if (iteratee(iterable[index], index, iterable) === false) {
    -            break;
    -          }
    -        }
    -        return collection;
    -      };
    -    }
    -
    -    /**
    -     * Creates a base function for methods like `_.forIn` and `_.forOwn`.
    -     *
    -     * @private
    -     * @param {boolean} [fromRight] Specify iterating from right to left.
    -     * @returns {Function} Returns the new base function.
    -     */
    -    function createBaseFor(fromRight) {
    -      return function (object, iteratee, keysFunc) {
    -        var index = -1,
    -          iterable = Object(object),
    -          props = keysFunc(object),
    -          length = props.length;
    -        while (length--) {
    -          var key = props[fromRight ? length : ++index];
    -          if (iteratee(iterable[key], key, iterable) === false) {
    -            break;
    -          }
    -        }
    -        return object;
    -      };
    -    }
    -
    -    /**
    -     * Creates a function that wraps `func` to invoke it with the optional `this`
    -     * binding of `thisArg`.
    -     *
    -     * @private
    -     * @param {Function} func The function to wrap.
    -     * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
    -     * @param {*} [thisArg] The `this` binding of `func`.
    -     * @returns {Function} Returns the new wrapped function.
    -     */
    -    function createBind(func, bitmask, thisArg) {
    -      var isBind = bitmask & WRAP_BIND_FLAG,
    -        Ctor = createCtor(func);
    -      function wrapper() {
    -        var fn = this && this !== root && this instanceof wrapper ? Ctor : func;
    -        return fn.apply(isBind ? thisArg : this, arguments);
    -      }
    -      return wrapper;
    -    }
    -
    -    /**
    -     * Creates a function like `_.lowerFirst`.
    -     *
    -     * @private
    -     * @param {string} methodName The name of the `String` case method to use.
    -     * @returns {Function} Returns the new case function.
    -     */
    -    function createCaseFirst(methodName) {
    -      return function (string) {
    -        string = toString(string);
    -        var strSymbols = hasUnicode(string) ? stringToArray(string) : undefined;
    -        var chr = strSymbols ? strSymbols[0] : string.charAt(0);
    -        var trailing = strSymbols ? castSlice(strSymbols, 1).join('') : string.slice(1);
    -        return chr[methodName]() + trailing;
    -      };
    -    }
    -
    -    /**
    -     * Creates a function like `_.camelCase`.
    -     *
    -     * @private
    -     * @param {Function} callback The function to combine each word.
    -     * @returns {Function} Returns the new compounder function.
    -     */
    -    function createCompounder(callback) {
    -      return function (string) {
    -        return arrayReduce(words(deburr(string).replace(reApos, '')), callback, '');
    -      };
    -    }
    -
    -    /**
    -     * Creates a function that produces an instance of `Ctor` regardless of
    -     * whether it was invoked as part of a `new` expression or by `call` or `apply`.
    -     *
    -     * @private
    -     * @param {Function} Ctor The constructor to wrap.
    -     * @returns {Function} Returns the new wrapped function.
    -     */
    -    function createCtor(Ctor) {
    -      return function () {
    -        // Use a `switch` statement to work with class constructors. See
    -        // http://ecma-international.org/ecma-262/7.0/#sec-ecmascript-function-objects-call-thisargument-argumentslist
    -        // for more details.
    -        var args = arguments;
    -        switch (args.length) {
    -          case 0:
    -            return new Ctor();
    -          case 1:
    -            return new Ctor(args[0]);
    -          case 2:
    -            return new Ctor(args[0], args[1]);
    -          case 3:
    -            return new Ctor(args[0], args[1], args[2]);
    -          case 4:
    -            return new Ctor(args[0], args[1], args[2], args[3]);
    -          case 5:
    -            return new Ctor(args[0], args[1], args[2], args[3], args[4]);
    -          case 6:
    -            return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5]);
    -          case 7:
    -            return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
    -        }
    -        var thisBinding = baseCreate(Ctor.prototype),
    -          result = Ctor.apply(thisBinding, args);
    -
    -        // Mimic the constructor's `return` behavior.
    -        // See https://es5.github.io/#x13.2.2 for more details.
    -        return isObject(result) ? result : thisBinding;
    -      };
    -    }
    -
    -    /**
    -     * Creates a function that wraps `func` to enable currying.
    -     *
    -     * @private
    -     * @param {Function} func The function to wrap.
    -     * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
    -     * @param {number} arity The arity of `func`.
    -     * @returns {Function} Returns the new wrapped function.
    -     */
    -    function createCurry(func, bitmask, arity) {
    -      var Ctor = createCtor(func);
    -      function wrapper() {
    -        var length = arguments.length,
    -          args = Array(length),
    -          index = length,
    -          placeholder = getHolder(wrapper);
    -        while (index--) {
    -          args[index] = arguments[index];
    -        }
    -        var holders = length < 3 && args[0] !== placeholder && args[length - 1] !== placeholder ? [] : replaceHolders(args, placeholder);
    -        length -= holders.length;
    -        if (length < arity) {
    -          return createRecurry(func, bitmask, createHybrid, wrapper.placeholder, undefined, args, holders, undefined, undefined, arity - length);
    -        }
    -        var fn = this && this !== root && this instanceof wrapper ? Ctor : func;
    -        return apply(fn, this, args);
    -      }
    -      return wrapper;
    -    }
    -
    -    /**
    -     * Creates a `_.find` or `_.findLast` function.
    -     *
    -     * @private
    -     * @param {Function} findIndexFunc The function to find the collection index.
    -     * @returns {Function} Returns the new find function.
    -     */
    -    function createFind(findIndexFunc) {
    -      return function (collection, predicate, fromIndex) {
    -        var iterable = Object(collection);
    -        if (!isArrayLike(collection)) {
    -          var iteratee = getIteratee(predicate, 3);
    -          collection = keys(collection);
    -          predicate = function predicate(key) {
    -            return iteratee(iterable[key], key, iterable);
    -          };
    -        }
    -        var index = findIndexFunc(collection, predicate, fromIndex);
    -        return index > -1 ? iterable[iteratee ? collection[index] : index] : undefined;
    -      };
    -    }
    -
    -    /**
    -     * Creates a `_.flow` or `_.flowRight` function.
    -     *
    -     * @private
    -     * @param {boolean} [fromRight] Specify iterating from right to left.
    -     * @returns {Function} Returns the new flow function.
    -     */
    -    function createFlow(fromRight) {
    -      return flatRest(function (funcs) {
    -        var length = funcs.length,
    -          index = length,
    -          prereq = LodashWrapper.prototype.thru;
    -        if (fromRight) {
    -          funcs.reverse();
    -        }
    -        while (index--) {
    -          var func = funcs[index];
    -          if (typeof func != 'function') {
    -            throw new TypeError(FUNC_ERROR_TEXT);
    -          }
    -          if (prereq && !wrapper && getFuncName(func) == 'wrapper') {
    -            var wrapper = new LodashWrapper([], true);
    -          }
    -        }
    -        index = wrapper ? index : length;
    -        while (++index < length) {
    -          func = funcs[index];
    -          var funcName = getFuncName(func),
    -            data = funcName == 'wrapper' ? getData(func) : undefined;
    -          if (data && isLaziable(data[0]) && data[1] == (WRAP_ARY_FLAG | WRAP_CURRY_FLAG | WRAP_PARTIAL_FLAG | WRAP_REARG_FLAG) && !data[4].length && data[9] == 1) {
    -            wrapper = wrapper[getFuncName(data[0])].apply(wrapper, data[3]);
    -          } else {
    -            wrapper = func.length == 1 && isLaziable(func) ? wrapper[funcName]() : wrapper.thru(func);
    -          }
    -        }
    -        return function () {
    -          var args = arguments,
    -            value = args[0];
    -          if (wrapper && args.length == 1 && isArray(value)) {
    -            return wrapper.plant(value).value();
    -          }
    -          var index = 0,
    -            result = length ? funcs[index].apply(this, args) : value;
    -          while (++index < length) {
    -            result = funcs[index].call(this, result);
    -          }
    -          return result;
    -        };
    -      });
    -    }
    -
    -    /**
    -     * Creates a function that wraps `func` to invoke it with optional `this`
    -     * binding of `thisArg`, partial application, and currying.
    -     *
    -     * @private
    -     * @param {Function|string} func The function or method name to wrap.
    -     * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
    -     * @param {*} [thisArg] The `this` binding of `func`.
    -     * @param {Array} [partials] The arguments to prepend to those provided to
    -     *  the new function.
    -     * @param {Array} [holders] The `partials` placeholder indexes.
    -     * @param {Array} [partialsRight] The arguments to append to those provided
    -     *  to the new function.
    -     * @param {Array} [holdersRight] The `partialsRight` placeholder indexes.
    -     * @param {Array} [argPos] The argument positions of the new function.
    -     * @param {number} [ary] The arity cap of `func`.
    -     * @param {number} [arity] The arity of `func`.
    -     * @returns {Function} Returns the new wrapped function.
    -     */
    -    function createHybrid(func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, argPos, ary, arity) {
    -      var isAry = bitmask & WRAP_ARY_FLAG,
    -        isBind = bitmask & WRAP_BIND_FLAG,
    -        isBindKey = bitmask & WRAP_BIND_KEY_FLAG,
    -        isCurried = bitmask & (WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG),
    -        isFlip = bitmask & WRAP_FLIP_FLAG,
    -        Ctor = isBindKey ? undefined : createCtor(func);
    -      function wrapper() {
    -        var length = arguments.length,
    -          args = Array(length),
    -          index = length;
    -        while (index--) {
    -          args[index] = arguments[index];
    -        }
    -        if (isCurried) {
    -          var placeholder = getHolder(wrapper),
    -            holdersCount = countHolders(args, placeholder);
    -        }
    -        if (partials) {
    -          args = composeArgs(args, partials, holders, isCurried);
    -        }
    -        if (partialsRight) {
    -          args = composeArgsRight(args, partialsRight, holdersRight, isCurried);
    -        }
    -        length -= holdersCount;
    -        if (isCurried && length < arity) {
    -          var newHolders = replaceHolders(args, placeholder);
    -          return createRecurry(func, bitmask, createHybrid, wrapper.placeholder, thisArg, args, newHolders, argPos, ary, arity - length);
    -        }
    -        var thisBinding = isBind ? thisArg : this,
    -          fn = isBindKey ? thisBinding[func] : func;
    -        length = args.length;
    -        if (argPos) {
    -          args = reorder(args, argPos);
    -        } else if (isFlip && length > 1) {
    -          args.reverse();
    -        }
    -        if (isAry && ary < length) {
    -          args.length = ary;
    -        }
    -        if (this && this !== root && this instanceof wrapper) {
    -          fn = Ctor || createCtor(fn);
    -        }
    -        return fn.apply(thisBinding, args);
    -      }
    -      return wrapper;
    -    }
    -
    -    /**
    -     * Creates a function like `_.invertBy`.
    -     *
    -     * @private
    -     * @param {Function} setter The function to set accumulator values.
    -     * @param {Function} toIteratee The function to resolve iteratees.
    -     * @returns {Function} Returns the new inverter function.
    -     */
    -    function createInverter(setter, toIteratee) {
    -      return function (object, iteratee) {
    -        return baseInverter(object, setter, toIteratee(iteratee), {});
    -      };
    -    }
    -
    -    /**
    -     * Creates a function that performs a mathematical operation on two values.
    -     *
    -     * @private
    -     * @param {Function} operator The function to perform the operation.
    -     * @param {number} [defaultValue] The value used for `undefined` arguments.
    -     * @returns {Function} Returns the new mathematical operation function.
    -     */
    -    function createMathOperation(operator, defaultValue) {
    -      return function (value, other) {
    -        var result;
    -        if (value === undefined && other === undefined) {
    -          return defaultValue;
    -        }
    -        if (value !== undefined) {
    -          result = value;
    -        }
    -        if (other !== undefined) {
    -          if (result === undefined) {
    -            return other;
    -          }
    -          if (typeof value == 'string' || typeof other == 'string') {
    -            value = baseToString(value);
    -            other = baseToString(other);
    -          } else {
    -            value = baseToNumber(value);
    -            other = baseToNumber(other);
    -          }
    -          result = operator(value, other);
    -        }
    -        return result;
    -      };
    -    }
    -
    -    /**
    -     * Creates a function like `_.over`.
    -     *
    -     * @private
    -     * @param {Function} arrayFunc The function to iterate over iteratees.
    -     * @returns {Function} Returns the new over function.
    -     */
    -    function createOver(arrayFunc) {
    -      return flatRest(function (iteratees) {
    -        iteratees = arrayMap(iteratees, baseUnary(getIteratee()));
    -        return baseRest(function (args) {
    -          var thisArg = this;
    -          return arrayFunc(iteratees, function (iteratee) {
    -            return apply(iteratee, thisArg, args);
    -          });
    -        });
    -      });
    -    }
    -
    -    /**
    -     * Creates the padding for `string` based on `length`. The `chars` string
    -     * is truncated if the number of characters exceeds `length`.
    -     *
    -     * @private
    -     * @param {number} length The padding length.
    -     * @param {string} [chars=' '] The string used as padding.
    -     * @returns {string} Returns the padding for `string`.
    -     */
    -    function createPadding(length, chars) {
    -      chars = chars === undefined ? ' ' : baseToString(chars);
    -      var charsLength = chars.length;
    -      if (charsLength < 2) {
    -        return charsLength ? baseRepeat(chars, length) : chars;
    -      }
    -      var result = baseRepeat(chars, nativeCeil(length / stringSize(chars)));
    -      return hasUnicode(chars) ? castSlice(stringToArray(result), 0, length).join('') : result.slice(0, length);
    -    }
    -
    -    /**
    -     * Creates a function that wraps `func` to invoke it with the `this` binding
    -     * of `thisArg` and `partials` prepended to the arguments it receives.
    -     *
    -     * @private
    -     * @param {Function} func The function to wrap.
    -     * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
    -     * @param {*} thisArg The `this` binding of `func`.
    -     * @param {Array} partials The arguments to prepend to those provided to
    -     *  the new function.
    -     * @returns {Function} Returns the new wrapped function.
    -     */
    -    function createPartial(func, bitmask, thisArg, partials) {
    -      var isBind = bitmask & WRAP_BIND_FLAG,
    -        Ctor = createCtor(func);
    -      function wrapper() {
    -        var argsIndex = -1,
    -          argsLength = arguments.length,
    -          leftIndex = -1,
    -          leftLength = partials.length,
    -          args = Array(leftLength + argsLength),
    -          fn = this && this !== root && this instanceof wrapper ? Ctor : func;
    -        while (++leftIndex < leftLength) {
    -          args[leftIndex] = partials[leftIndex];
    -        }
    -        while (argsLength--) {
    -          args[leftIndex++] = arguments[++argsIndex];
    -        }
    -        return apply(fn, isBind ? thisArg : this, args);
    -      }
    -      return wrapper;
    -    }
    -
    -    /**
    -     * Creates a `_.range` or `_.rangeRight` function.
    -     *
    -     * @private
    -     * @param {boolean} [fromRight] Specify iterating from right to left.
    -     * @returns {Function} Returns the new range function.
    -     */
    -    function createRange(fromRight) {
    -      return function (start, end, step) {
    -        if (step && typeof step != 'number' && isIterateeCall(start, end, step)) {
    -          end = step = undefined;
    -        }
    -        // Ensure the sign of `-0` is preserved.
    -        start = toFinite(start);
    -        if (end === undefined) {
    -          end = start;
    -          start = 0;
    -        } else {
    -          end = toFinite(end);
    -        }
    -        step = step === undefined ? start < end ? 1 : -1 : toFinite(step);
    -        return baseRange(start, end, step, fromRight);
    -      };
    -    }
    -
    -    /**
    -     * Creates a function that performs a relational operation on two values.
    -     *
    -     * @private
    -     * @param {Function} operator The function to perform the operation.
    -     * @returns {Function} Returns the new relational operation function.
    -     */
    -    function createRelationalOperation(operator) {
    -      return function (value, other) {
    -        if (!(typeof value == 'string' && typeof other == 'string')) {
    -          value = toNumber(value);
    -          other = toNumber(other);
    -        }
    -        return operator(value, other);
    -      };
    -    }
    -
    -    /**
    -     * Creates a function that wraps `func` to continue currying.
    -     *
    -     * @private
    -     * @param {Function} func The function to wrap.
    -     * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
    -     * @param {Function} wrapFunc The function to create the `func` wrapper.
    -     * @param {*} placeholder The placeholder value.
    -     * @param {*} [thisArg] The `this` binding of `func`.
    -     * @param {Array} [partials] The arguments to prepend to those provided to
    -     *  the new function.
    -     * @param {Array} [holders] The `partials` placeholder indexes.
    -     * @param {Array} [argPos] The argument positions of the new function.
    -     * @param {number} [ary] The arity cap of `func`.
    -     * @param {number} [arity] The arity of `func`.
    -     * @returns {Function} Returns the new wrapped function.
    -     */
    -    function createRecurry(func, bitmask, wrapFunc, placeholder, thisArg, partials, holders, argPos, ary, arity) {
    -      var isCurry = bitmask & WRAP_CURRY_FLAG,
    -        newHolders = isCurry ? holders : undefined,
    -        newHoldersRight = isCurry ? undefined : holders,
    -        newPartials = isCurry ? partials : undefined,
    -        newPartialsRight = isCurry ? undefined : partials;
    -      bitmask |= isCurry ? WRAP_PARTIAL_FLAG : WRAP_PARTIAL_RIGHT_FLAG;
    -      bitmask &= ~(isCurry ? WRAP_PARTIAL_RIGHT_FLAG : WRAP_PARTIAL_FLAG);
    -      if (!(bitmask & WRAP_CURRY_BOUND_FLAG)) {
    -        bitmask &= ~(WRAP_BIND_FLAG | WRAP_BIND_KEY_FLAG);
    -      }
    -      var newData = [func, bitmask, thisArg, newPartials, newHolders, newPartialsRight, newHoldersRight, argPos, ary, arity];
    -      var result = wrapFunc.apply(undefined, newData);
    -      if (isLaziable(func)) {
    -        setData(result, newData);
    -      }
    -      result.placeholder = placeholder;
    -      return setWrapToString(result, func, bitmask);
    -    }
    -
    -    /**
    -     * Creates a function like `_.round`.
    -     *
    -     * @private
    -     * @param {string} methodName The name of the `Math` method to use when rounding.
    -     * @returns {Function} Returns the new round function.
    -     */
    -    function createRound(methodName) {
    -      var func = Math[methodName];
    -      return function (number, precision) {
    -        number = toNumber(number);
    -        precision = precision == null ? 0 : nativeMin(toInteger(precision), 292);
    -        if (precision && nativeIsFinite(number)) {
    -          // Shift with exponential notation to avoid floating-point issues.
    -          // See [MDN](https://mdn.io/round#Examples) for more details.
    -          var pair = (toString(number) + 'e').split('e'),
    -            value = func(pair[0] + 'e' + (+pair[1] + precision));
    -          pair = (toString(value) + 'e').split('e');
    -          return +(pair[0] + 'e' + (+pair[1] - precision));
    -        }
    -        return func(number);
    -      };
    -    }
    -
    -    /**
    -     * Creates a set object of `values`.
    -     *
    -     * @private
    -     * @param {Array} values The values to add to the set.
    -     * @returns {Object} Returns the new set.
    -     */
    -    var createSet = !(Set && 1 / setToArray(new Set([, -0]))[1] == INFINITY) ? noop : function (values) {
    -      return new Set(values);
    -    };
    -
    -    /**
    -     * Creates a `_.toPairs` or `_.toPairsIn` function.
    -     *
    -     * @private
    -     * @param {Function} keysFunc The function to get the keys of a given object.
    -     * @returns {Function} Returns the new pairs function.
    -     */
    -    function createToPairs(keysFunc) {
    -      return function (object) {
    -        var tag = getTag(object);
    -        if (tag == mapTag) {
    -          return mapToArray(object);
    -        }
    -        if (tag == setTag) {
    -          return setToPairs(object);
    -        }
    -        return baseToPairs(object, keysFunc(object));
    -      };
    -    }
    -
    -    /**
    -     * Creates a function that either curries or invokes `func` with optional
    -     * `this` binding and partially applied arguments.
    -     *
    -     * @private
    -     * @param {Function|string} func The function or method name to wrap.
    -     * @param {number} bitmask The bitmask flags.
    -     *    1 - `_.bind`
    -     *    2 - `_.bindKey`
    -     *    4 - `_.curry` or `_.curryRight` of a bound function
    -     *    8 - `_.curry`
    -     *   16 - `_.curryRight`
    -     *   32 - `_.partial`
    -     *   64 - `_.partialRight`
    -     *  128 - `_.rearg`
    -     *  256 - `_.ary`
    -     *  512 - `_.flip`
    -     * @param {*} [thisArg] The `this` binding of `func`.
    -     * @param {Array} [partials] The arguments to be partially applied.
    -     * @param {Array} [holders] The `partials` placeholder indexes.
    -     * @param {Array} [argPos] The argument positions of the new function.
    -     * @param {number} [ary] The arity cap of `func`.
    -     * @param {number} [arity] The arity of `func`.
    -     * @returns {Function} Returns the new wrapped function.
    -     */
    -    function createWrap(func, bitmask, thisArg, partials, holders, argPos, ary, arity) {
    -      var isBindKey = bitmask & WRAP_BIND_KEY_FLAG;
    -      if (!isBindKey && typeof func != 'function') {
    -        throw new TypeError(FUNC_ERROR_TEXT);
    -      }
    -      var length = partials ? partials.length : 0;
    -      if (!length) {
    -        bitmask &= ~(WRAP_PARTIAL_FLAG | WRAP_PARTIAL_RIGHT_FLAG);
    -        partials = holders = undefined;
    -      }
    -      ary = ary === undefined ? ary : nativeMax(toInteger(ary), 0);
    -      arity = arity === undefined ? arity : toInteger(arity);
    -      length -= holders ? holders.length : 0;
    -      if (bitmask & WRAP_PARTIAL_RIGHT_FLAG) {
    -        var partialsRight = partials,
    -          holdersRight = holders;
    -        partials = holders = undefined;
    -      }
    -      var data = isBindKey ? undefined : getData(func);
    -      var newData = [func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, argPos, ary, arity];
    -      if (data) {
    -        mergeData(newData, data);
    -      }
    -      func = newData[0];
    -      bitmask = newData[1];
    -      thisArg = newData[2];
    -      partials = newData[3];
    -      holders = newData[4];
    -      arity = newData[9] = newData[9] === undefined ? isBindKey ? 0 : func.length : nativeMax(newData[9] - length, 0);
    -      if (!arity && bitmask & (WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG)) {
    -        bitmask &= ~(WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG);
    -      }
    -      if (!bitmask || bitmask == WRAP_BIND_FLAG) {
    -        var result = createBind(func, bitmask, thisArg);
    -      } else if (bitmask == WRAP_CURRY_FLAG || bitmask == WRAP_CURRY_RIGHT_FLAG) {
    -        result = createCurry(func, bitmask, arity);
    -      } else if ((bitmask == WRAP_PARTIAL_FLAG || bitmask == (WRAP_BIND_FLAG | WRAP_PARTIAL_FLAG)) && !holders.length) {
    -        result = createPartial(func, bitmask, thisArg, partials);
    -      } else {
    -        result = createHybrid.apply(undefined, newData);
    -      }
    -      var setter = data ? baseSetData : setData;
    -      return setWrapToString(setter(result, newData), func, bitmask);
    -    }
    -
    -    /**
    -     * Used by `_.defaults` to customize its `_.assignIn` use to assign properties
    -     * of source objects to the destination object for all destination properties
    -     * that resolve to `undefined`.
    -     *
    -     * @private
    -     * @param {*} objValue The destination value.
    -     * @param {*} srcValue The source value.
    -     * @param {string} key The key of the property to assign.
    -     * @param {Object} object The parent object of `objValue`.
    -     * @returns {*} Returns the value to assign.
    -     */
    -    function customDefaultsAssignIn(objValue, srcValue, key, object) {
    -      if (objValue === undefined || eq(objValue, objectProto[key]) && !hasOwnProperty.call(object, key)) {
    -        return srcValue;
    -      }
    -      return objValue;
    -    }
    -
    -    /**
    -     * Used by `_.defaultsDeep` to customize its `_.merge` use to merge source
    -     * objects into destination objects that are passed thru.
    -     *
    -     * @private
    -     * @param {*} objValue The destination value.
    -     * @param {*} srcValue The source value.
    -     * @param {string} key The key of the property to merge.
    -     * @param {Object} object The parent object of `objValue`.
    -     * @param {Object} source The parent object of `srcValue`.
    -     * @param {Object} [stack] Tracks traversed source values and their merged
    -     *  counterparts.
    -     * @returns {*} Returns the value to assign.
    -     */
    -    function customDefaultsMerge(objValue, srcValue, key, object, source, stack) {
    -      if (isObject(objValue) && isObject(srcValue)) {
    -        // Recursively merge objects and arrays (susceptible to call stack limits).
    -        stack.set(srcValue, objValue);
    -        baseMerge(objValue, srcValue, undefined, customDefaultsMerge, stack);
    -        stack['delete'](srcValue);
    -      }
    -      return objValue;
    -    }
    -
    -    /**
    -     * Used by `_.omit` to customize its `_.cloneDeep` use to only clone plain
    -     * objects.
    -     *
    -     * @private
    -     * @param {*} value The value to inspect.
    -     * @param {string} key The key of the property to inspect.
    -     * @returns {*} Returns the uncloned value or `undefined` to defer cloning to `_.cloneDeep`.
    -     */
    -    function customOmitClone(value) {
    -      return isPlainObject(value) ? undefined : value;
    -    }
    -
    -    /**
    -     * A specialized version of `baseIsEqualDeep` for arrays with support for
    -     * partial deep comparisons.
    -     *
    -     * @private
    -     * @param {Array} array The array to compare.
    -     * @param {Array} other The other array to compare.
    -     * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.
    -     * @param {Function} customizer The function to customize comparisons.
    -     * @param {Function} equalFunc The function to determine equivalents of values.
    -     * @param {Object} stack Tracks traversed `array` and `other` objects.
    -     * @returns {boolean} Returns `true` if the arrays are equivalent, else `false`.
    -     */
    -    function equalArrays(array, other, bitmask, customizer, equalFunc, stack) {
    -      var isPartial = bitmask & COMPARE_PARTIAL_FLAG,
    -        arrLength = array.length,
    -        othLength = other.length;
    -      if (arrLength != othLength && !(isPartial && othLength > arrLength)) {
    -        return false;
    -      }
    -      // Check that cyclic values are equal.
    -      var arrStacked = stack.get(array);
    -      var othStacked = stack.get(other);
    -      if (arrStacked && othStacked) {
    -        return arrStacked == other && othStacked == array;
    -      }
    -      var index = -1,
    -        result = true,
    -        seen = bitmask & COMPARE_UNORDERED_FLAG ? new SetCache() : undefined;
    -      stack.set(array, other);
    -      stack.set(other, array);
    -
    -      // Ignore non-index properties.
    -      while (++index < arrLength) {
    -        var arrValue = array[index],
    -          othValue = other[index];
    -        if (customizer) {
    -          var compared = isPartial ? customizer(othValue, arrValue, index, other, array, stack) : customizer(arrValue, othValue, index, array, other, stack);
    -        }
    -        if (compared !== undefined) {
    -          if (compared) {
    -            continue;
    -          }
    -          result = false;
    -          break;
    -        }
    -        // Recursively compare arrays (susceptible to call stack limits).
    -        if (seen) {
    -          if (!arraySome(other, function (othValue, othIndex) {
    -            if (!cacheHas(seen, othIndex) && (arrValue === othValue || equalFunc(arrValue, othValue, bitmask, customizer, stack))) {
    -              return seen.push(othIndex);
    -            }
    -          })) {
    -            result = false;
    -            break;
    -          }
    -        } else if (!(arrValue === othValue || equalFunc(arrValue, othValue, bitmask, customizer, stack))) {
    -          result = false;
    -          break;
    -        }
    -      }
    -      stack['delete'](array);
    -      stack['delete'](other);
    -      return result;
    -    }
    -
    -    /**
    -     * A specialized version of `baseIsEqualDeep` for comparing objects of
    -     * the same `toStringTag`.
    -     *
    -     * **Note:** This function only supports comparing values with tags of
    -     * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`.
    -     *
    -     * @private
    -     * @param {Object} object The object to compare.
    -     * @param {Object} other The other object to compare.
    -     * @param {string} tag The `toStringTag` of the objects to compare.
    -     * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.
    -     * @param {Function} customizer The function to customize comparisons.
    -     * @param {Function} equalFunc The function to determine equivalents of values.
    -     * @param {Object} stack Tracks traversed `object` and `other` objects.
    -     * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
    -     */
    -    function equalByTag(object, other, tag, bitmask, customizer, equalFunc, stack) {
    -      switch (tag) {
    -        case dataViewTag:
    -          if (object.byteLength != other.byteLength || object.byteOffset != other.byteOffset) {
    -            return false;
    -          }
    -          object = object.buffer;
    -          other = other.buffer;
    -        case arrayBufferTag:
    -          if (object.byteLength != other.byteLength || !equalFunc(new Uint8Array(object), new Uint8Array(other))) {
    -            return false;
    -          }
    -          return true;
    -        case boolTag:
    -        case dateTag:
    -        case numberTag:
    -          // Coerce booleans to `1` or `0` and dates to milliseconds.
    -          // Invalid dates are coerced to `NaN`.
    -          return eq(+object, +other);
    -        case errorTag:
    -          return object.name == other.name && object.message == other.message;
    -        case regexpTag:
    -        case stringTag:
    -          // Coerce regexes to strings and treat strings, primitives and objects,
    -          // as equal. See http://www.ecma-international.org/ecma-262/7.0/#sec-regexp.prototype.tostring
    -          // for more details.
    -          return object == other + '';
    -        case mapTag:
    -          var convert = mapToArray;
    -        case setTag:
    -          var isPartial = bitmask & COMPARE_PARTIAL_FLAG;
    -          convert || (convert = setToArray);
    -          if (object.size != other.size && !isPartial) {
    -            return false;
    -          }
    -          // Assume cyclic values are equal.
    -          var stacked = stack.get(object);
    -          if (stacked) {
    -            return stacked == other;
    -          }
    -          bitmask |= COMPARE_UNORDERED_FLAG;
    -
    -          // Recursively compare objects (susceptible to call stack limits).
    -          stack.set(object, other);
    -          var result = equalArrays(convert(object), convert(other), bitmask, customizer, equalFunc, stack);
    -          stack['delete'](object);
    -          return result;
    -        case symbolTag:
    -          if (symbolValueOf) {
    -            return symbolValueOf.call(object) == symbolValueOf.call(other);
    -          }
    -      }
    -      return false;
    -    }
    -
    -    /**
    -     * A specialized version of `baseIsEqualDeep` for objects with support for
    -     * partial deep comparisons.
    -     *
    -     * @private
    -     * @param {Object} object The object to compare.
    -     * @param {Object} other The other object to compare.
    -     * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.
    -     * @param {Function} customizer The function to customize comparisons.
    -     * @param {Function} equalFunc The function to determine equivalents of values.
    -     * @param {Object} stack Tracks traversed `object` and `other` objects.
    -     * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
    -     */
    -    function equalObjects(object, other, bitmask, customizer, equalFunc, stack) {
    -      var isPartial = bitmask & COMPARE_PARTIAL_FLAG,
    -        objProps = getAllKeys(object),
    -        objLength = objProps.length,
    -        othProps = getAllKeys(other),
    -        othLength = othProps.length;
    -      if (objLength != othLength && !isPartial) {
    -        return false;
    -      }
    -      var index = objLength;
    -      while (index--) {
    -        var key = objProps[index];
    -        if (!(isPartial ? key in other : hasOwnProperty.call(other, key))) {
    -          return false;
    -        }
    -      }
    -      // Check that cyclic values are equal.
    -      var objStacked = stack.get(object);
    -      var othStacked = stack.get(other);
    -      if (objStacked && othStacked) {
    -        return objStacked == other && othStacked == object;
    -      }
    -      var result = true;
    -      stack.set(object, other);
    -      stack.set(other, object);
    -      var skipCtor = isPartial;
    -      while (++index < objLength) {
    -        key = objProps[index];
    -        var objValue = object[key],
    -          othValue = other[key];
    -        if (customizer) {
    -          var compared = isPartial ? customizer(othValue, objValue, key, other, object, stack) : customizer(objValue, othValue, key, object, other, stack);
    -        }
    -        // Recursively compare objects (susceptible to call stack limits).
    -        if (!(compared === undefined ? objValue === othValue || equalFunc(objValue, othValue, bitmask, customizer, stack) : compared)) {
    -          result = false;
    -          break;
    -        }
    -        skipCtor || (skipCtor = key == 'constructor');
    -      }
    -      if (result && !skipCtor) {
    -        var objCtor = object.constructor,
    -          othCtor = other.constructor;
    -
    -        // Non `Object` object instances with different constructors are not equal.
    -        if (objCtor != othCtor && 'constructor' in object && 'constructor' in other && !(typeof objCtor == 'function' && objCtor instanceof objCtor && typeof othCtor == 'function' && othCtor instanceof othCtor)) {
    -          result = false;
    -        }
    -      }
    -      stack['delete'](object);
    -      stack['delete'](other);
    -      return result;
    -    }
    -
    -    /**
    -     * A specialized version of `baseRest` which flattens the rest array.
    -     *
    -     * @private
    -     * @param {Function} func The function to apply a rest parameter to.
    -     * @returns {Function} Returns the new function.
    -     */
    -    function flatRest(func) {
    -      return setToString(overRest(func, undefined, flatten), func + '');
    -    }
    -
    -    /**
    -     * Creates an array of own enumerable property names and symbols of `object`.
    -     *
    -     * @private
    -     * @param {Object} object The object to query.
    -     * @returns {Array} Returns the array of property names and symbols.
    -     */
    -    function getAllKeys(object) {
    -      return baseGetAllKeys(object, keys, getSymbols);
    -    }
    -
    -    /**
    -     * Creates an array of own and inherited enumerable property names and
    -     * symbols of `object`.
    -     *
    -     * @private
    -     * @param {Object} object The object to query.
    -     * @returns {Array} Returns the array of property names and symbols.
    -     */
    -    function getAllKeysIn(object) {
    -      return baseGetAllKeys(object, keysIn, getSymbolsIn);
    -    }
    -
    -    /**
    -     * Gets metadata for `func`.
    -     *
    -     * @private
    -     * @param {Function} func The function to query.
    -     * @returns {*} Returns the metadata for `func`.
    -     */
    -    var getData = !metaMap ? noop : function (func) {
    -      return metaMap.get(func);
    -    };
    -
    -    /**
    -     * Gets the name of `func`.
    -     *
    -     * @private
    -     * @param {Function} func The function to query.
    -     * @returns {string} Returns the function name.
    -     */
    -    function getFuncName(func) {
    -      var result = func.name + '',
    -        array = realNames[result],
    -        length = hasOwnProperty.call(realNames, result) ? array.length : 0;
    -      while (length--) {
    -        var data = array[length],
    -          otherFunc = data.func;
    -        if (otherFunc == null || otherFunc == func) {
    -          return data.name;
    -        }
    -      }
    -      return result;
    -    }
    -
    -    /**
    -     * Gets the argument placeholder value for `func`.
    -     *
    -     * @private
    -     * @param {Function} func The function to inspect.
    -     * @returns {*} Returns the placeholder value.
    -     */
    -    function getHolder(func) {
    -      var object = hasOwnProperty.call(lodash, 'placeholder') ? lodash : func;
    -      return object.placeholder;
    -    }
    -
    -    /**
    -     * Gets the appropriate "iteratee" function. If `_.iteratee` is customized,
    -     * this function returns the custom method, otherwise it returns `baseIteratee`.
    -     * If arguments are provided, the chosen function is invoked with them and
    -     * its result is returned.
    -     *
    -     * @private
    -     * @param {*} [value] The value to convert to an iteratee.
    -     * @param {number} [arity] The arity of the created iteratee.
    -     * @returns {Function} Returns the chosen function or its result.
    -     */
    -    function getIteratee() {
    -      var result = lodash.iteratee || iteratee;
    -      result = result === iteratee ? baseIteratee : result;
    -      return arguments.length ? result(arguments[0], arguments[1]) : result;
    -    }
    -
    -    /**
    -     * Gets the data for `map`.
    -     *
    -     * @private
    -     * @param {Object} map The map to query.
    -     * @param {string} key The reference key.
    -     * @returns {*} Returns the map data.
    -     */
    -    function getMapData(map, key) {
    -      var data = map.__data__;
    -      return isKeyable(key) ? data[typeof key == 'string' ? 'string' : 'hash'] : data.map;
    -    }
    -
    -    /**
    -     * Gets the property names, values, and compare flags of `object`.
    -     *
    -     * @private
    -     * @param {Object} object The object to query.
    -     * @returns {Array} Returns the match data of `object`.
    -     */
    -    function getMatchData(object) {
    -      var result = keys(object),
    -        length = result.length;
    -      while (length--) {
    -        var key = result[length],
    -          value = object[key];
    -        result[length] = [key, value, isStrictComparable(value)];
    -      }
    -      return result;
    -    }
    -
    -    /**
    -     * Gets the native function at `key` of `object`.
    -     *
    -     * @private
    -     * @param {Object} object The object to query.
    -     * @param {string} key The key of the method to get.
    -     * @returns {*} Returns the function if it's native, else `undefined`.
    -     */
    -    function getNative(object, key) {
    -      var value = getValue(object, key);
    -      return baseIsNative(value) ? value : undefined;
    -    }
    -
    -    /**
    -     * A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values.
    -     *
    -     * @private
    -     * @param {*} value The value to query.
    -     * @returns {string} Returns the raw `toStringTag`.
    -     */
    -    function getRawTag(value) {
    -      var isOwn = hasOwnProperty.call(value, symToStringTag),
    -        tag = value[symToStringTag];
    -      try {
    -        value[symToStringTag] = undefined;
    -        var unmasked = true;
    -      } catch (e) {}
    -      var result = nativeObjectToString.call(value);
    -      if (unmasked) {
    -        if (isOwn) {
    -          value[symToStringTag] = tag;
    -        } else {
    -          delete value[symToStringTag];
    -        }
    -      }
    -      return result;
    -    }
    -
    -    /**
    -     * Creates an array of the own enumerable symbols of `object`.
    -     *
    -     * @private
    -     * @param {Object} object The object to query.
    -     * @returns {Array} Returns the array of symbols.
    -     */
    -    var getSymbols = !nativeGetSymbols ? stubArray : function (object) {
    -      if (object == null) {
    -        return [];
    -      }
    -      object = Object(object);
    -      return arrayFilter(nativeGetSymbols(object), function (symbol) {
    -        return propertyIsEnumerable.call(object, symbol);
    -      });
    -    };
    -
    -    /**
    -     * Creates an array of the own and inherited enumerable symbols of `object`.
    -     *
    -     * @private
    -     * @param {Object} object The object to query.
    -     * @returns {Array} Returns the array of symbols.
    -     */
    -    var getSymbolsIn = !nativeGetSymbols ? stubArray : function (object) {
    -      var result = [];
    -      while (object) {
    -        arrayPush(result, getSymbols(object));
    -        object = getPrototype(object);
    -      }
    -      return result;
    -    };
    -
    -    /**
    -     * Gets the `toStringTag` of `value`.
    -     *
    -     * @private
    -     * @param {*} value The value to query.
    -     * @returns {string} Returns the `toStringTag`.
    -     */
    -    var getTag = baseGetTag;
    -
    -    // Fallback for data views, maps, sets, and weak maps in IE 11 and promises in Node.js < 6.
    -    if (DataView && getTag(new DataView(new ArrayBuffer(1))) != dataViewTag || Map && getTag(new Map()) != mapTag || Promise && getTag(Promise.resolve()) != promiseTag || Set && getTag(new Set()) != setTag || WeakMap && getTag(new WeakMap()) != weakMapTag) {
    -      getTag = function getTag(value) {
    -        var result = baseGetTag(value),
    -          Ctor = result == objectTag ? value.constructor : undefined,
    -          ctorString = Ctor ? toSource(Ctor) : '';
    -        if (ctorString) {
    -          switch (ctorString) {
    -            case dataViewCtorString:
    -              return dataViewTag;
    -            case mapCtorString:
    -              return mapTag;
    -            case promiseCtorString:
    -              return promiseTag;
    -            case setCtorString:
    -              return setTag;
    -            case weakMapCtorString:
    -              return weakMapTag;
    -          }
    -        }
    -        return result;
    -      };
    -    }
    -
    -    /**
    -     * Gets the view, applying any `transforms` to the `start` and `end` positions.
    -     *
    -     * @private
    -     * @param {number} start The start of the view.
    -     * @param {number} end The end of the view.
    -     * @param {Array} transforms The transformations to apply to the view.
    -     * @returns {Object} Returns an object containing the `start` and `end`
    -     *  positions of the view.
    -     */
    -    function getView(start, end, transforms) {
    -      var index = -1,
    -        length = transforms.length;
    -      while (++index < length) {
    -        var data = transforms[index],
    -          size = data.size;
    -        switch (data.type) {
    -          case 'drop':
    -            start += size;
    -            break;
    -          case 'dropRight':
    -            end -= size;
    -            break;
    -          case 'take':
    -            end = nativeMin(end, start + size);
    -            break;
    -          case 'takeRight':
    -            start = nativeMax(start, end - size);
    -            break;
    -        }
    -      }
    -      return {
    -        'start': start,
    -        'end': end
    -      };
    -    }
    -
    -    /**
    -     * Extracts wrapper details from the `source` body comment.
    -     *
    -     * @private
    -     * @param {string} source The source to inspect.
    -     * @returns {Array} Returns the wrapper details.
    -     */
    -    function getWrapDetails(source) {
    -      var match = source.match(reWrapDetails);
    -      return match ? match[1].split(reSplitDetails) : [];
    -    }
    -
    -    /**
    -     * Checks if `path` exists on `object`.
    -     *
    -     * @private
    -     * @param {Object} object The object to query.
    -     * @param {Array|string} path The path to check.
    -     * @param {Function} hasFunc The function to check properties.
    -     * @returns {boolean} Returns `true` if `path` exists, else `false`.
    -     */
    -    function hasPath(object, path, hasFunc) {
    -      path = castPath(path, object);
    -      var index = -1,
    -        length = path.length,
    -        result = false;
    -      while (++index < length) {
    -        var key = toKey(path[index]);
    -        if (!(result = object != null && hasFunc(object, key))) {
    -          break;
    -        }
    -        object = object[key];
    -      }
    -      if (result || ++index != length) {
    -        return result;
    -      }
    -      length = object == null ? 0 : object.length;
    -      return !!length && isLength(length) && isIndex(key, length) && (isArray(object) || isArguments(object));
    -    }
    -
    -    /**
    -     * Initializes an array clone.
    -     *
    -     * @private
    -     * @param {Array} array The array to clone.
    -     * @returns {Array} Returns the initialized clone.
    -     */
    -    function initCloneArray(array) {
    -      var length = array.length,
    -        result = new array.constructor(length);
    -
    -      // Add properties assigned by `RegExp#exec`.
    -      if (length && typeof array[0] == 'string' && hasOwnProperty.call(array, 'index')) {
    -        result.index = array.index;
    -        result.input = array.input;
    -      }
    -      return result;
    -    }
    -
    -    /**
    -     * Initializes an object clone.
    -     *
    -     * @private
    -     * @param {Object} object The object to clone.
    -     * @returns {Object} Returns the initialized clone.
    -     */
    -    function initCloneObject(object) {
    -      return typeof object.constructor == 'function' && !isPrototype(object) ? baseCreate(getPrototype(object)) : {};
    -    }
    -
    -    /**
    -     * Initializes an object clone based on its `toStringTag`.
    -     *
    -     * **Note:** This function only supports cloning values with tags of
    -     * `Boolean`, `Date`, `Error`, `Map`, `Number`, `RegExp`, `Set`, or `String`.
    -     *
    -     * @private
    -     * @param {Object} object The object to clone.
    -     * @param {string} tag The `toStringTag` of the object to clone.
    -     * @param {boolean} [isDeep] Specify a deep clone.
    -     * @returns {Object} Returns the initialized clone.
    -     */
    -    function initCloneByTag(object, tag, isDeep) {
    -      var Ctor = object.constructor;
    -      switch (tag) {
    -        case arrayBufferTag:
    -          return cloneArrayBuffer(object);
    -        case boolTag:
    -        case dateTag:
    -          return new Ctor(+object);
    -        case dataViewTag:
    -          return cloneDataView(object, isDeep);
    -        case float32Tag:
    -        case float64Tag:
    -        case int8Tag:
    -        case int16Tag:
    -        case int32Tag:
    -        case uint8Tag:
    -        case uint8ClampedTag:
    -        case uint16Tag:
    -        case uint32Tag:
    -          return cloneTypedArray(object, isDeep);
    -        case mapTag:
    -          return new Ctor();
    -        case numberTag:
    -        case stringTag:
    -          return new Ctor(object);
    -        case regexpTag:
    -          return cloneRegExp(object);
    -        case setTag:
    -          return new Ctor();
    -        case symbolTag:
    -          return cloneSymbol(object);
    -      }
    -    }
    -
    -    /**
    -     * Inserts wrapper `details` in a comment at the top of the `source` body.
    -     *
    -     * @private
    -     * @param {string} source The source to modify.
    -     * @returns {Array} details The details to insert.
    -     * @returns {string} Returns the modified source.
    -     */
    -    function insertWrapDetails(source, details) {
    -      var length = details.length;
    -      if (!length) {
    -        return source;
    -      }
    -      var lastIndex = length - 1;
    -      details[lastIndex] = (length > 1 ? '& ' : '') + details[lastIndex];
    -      details = details.join(length > 2 ? ', ' : ' ');
    -      return source.replace(reWrapComment, '{\n/* [wrapped with ' + details + '] */\n');
    -    }
    -
    -    /**
    -     * Checks if `value` is a flattenable `arguments` object or array.
    -     *
    -     * @private
    -     * @param {*} value The value to check.
    -     * @returns {boolean} Returns `true` if `value` is flattenable, else `false`.
    -     */
    -    function isFlattenable(value) {
    -      return isArray(value) || isArguments(value) || !!(spreadableSymbol && value && value[spreadableSymbol]);
    -    }
    -
    -    /**
    -     * Checks if `value` is a valid array-like index.
    -     *
    -     * @private
    -     * @param {*} value The value to check.
    -     * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index.
    -     * @returns {boolean} Returns `true` if `value` is a valid index, else `false`.
    -     */
    -    function isIndex(value, length) {
    -      var type = typeof value;
    -      length = length == null ? MAX_SAFE_INTEGER : length;
    -      return !!length && (type == 'number' || type != 'symbol' && reIsUint.test(value)) && value > -1 && value % 1 == 0 && value < length;
    -    }
    -
    -    /**
    -     * Checks if the given arguments are from an iteratee call.
    -     *
    -     * @private
    -     * @param {*} value The potential iteratee value argument.
    -     * @param {*} index The potential iteratee index or key argument.
    -     * @param {*} object The potential iteratee object argument.
    -     * @returns {boolean} Returns `true` if the arguments are from an iteratee call,
    -     *  else `false`.
    -     */
    -    function isIterateeCall(value, index, object) {
    -      if (!isObject(object)) {
    -        return false;
    -      }
    -      var type = typeof index;
    -      if (type == 'number' ? isArrayLike(object) && isIndex(index, object.length) : type == 'string' && index in object) {
    -        return eq(object[index], value);
    -      }
    -      return false;
    -    }
    -
    -    /**
    -     * Checks if `value` is a property name and not a property path.
    -     *
    -     * @private
    -     * @param {*} value The value to check.
    -     * @param {Object} [object] The object to query keys on.
    -     * @returns {boolean} Returns `true` if `value` is a property name, else `false`.
    -     */
    -    function isKey(value, object) {
    -      if (isArray(value)) {
    -        return false;
    -      }
    -      var type = typeof value;
    -      if (type == 'number' || type == 'symbol' || type == 'boolean' || value == null || isSymbol(value)) {
    -        return true;
    -      }
    -      return reIsPlainProp.test(value) || !reIsDeepProp.test(value) || object != null && value in Object(object);
    -    }
    -
    -    /**
    -     * Checks if `value` is suitable for use as unique object key.
    -     *
    -     * @private
    -     * @param {*} value The value to check.
    -     * @returns {boolean} Returns `true` if `value` is suitable, else `false`.
    -     */
    -    function isKeyable(value) {
    -      var type = typeof value;
    -      return type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean' ? value !== '__proto__' : value === null;
    -    }
    -
    -    /**
    -     * Checks if `func` has a lazy counterpart.
    -     *
    -     * @private
    -     * @param {Function} func The function to check.
    -     * @returns {boolean} Returns `true` if `func` has a lazy counterpart,
    -     *  else `false`.
    -     */
    -    function isLaziable(func) {
    -      var funcName = getFuncName(func),
    -        other = lodash[funcName];
    -      if (typeof other != 'function' || !(funcName in LazyWrapper.prototype)) {
    -        return false;
    -      }
    -      if (func === other) {
    -        return true;
    -      }
    -      var data = getData(other);
    -      return !!data && func === data[0];
    -    }
    -
    -    /**
    -     * Checks if `func` has its source masked.
    -     *
    -     * @private
    -     * @param {Function} func The function to check.
    -     * @returns {boolean} Returns `true` if `func` is masked, else `false`.
    -     */
    -    function isMasked(func) {
    -      return !!maskSrcKey && maskSrcKey in func;
    -    }
    -
    -    /**
    -     * Checks if `func` is capable of being masked.
    -     *
    -     * @private
    -     * @param {*} value The value to check.
    -     * @returns {boolean} Returns `true` if `func` is maskable, else `false`.
    -     */
    -    var isMaskable = coreJsData ? isFunction : stubFalse;
    -
    -    /**
    -     * Checks if `value` is likely a prototype object.
    -     *
    -     * @private
    -     * @param {*} value The value to check.
    -     * @returns {boolean} Returns `true` if `value` is a prototype, else `false`.
    -     */
    -    function isPrototype(value) {
    -      var Ctor = value && value.constructor,
    -        proto = typeof Ctor == 'function' && Ctor.prototype || objectProto;
    -      return value === proto;
    -    }
    -
    -    /**
    -     * Checks if `value` is suitable for strict equality comparisons, i.e. `===`.
    -     *
    -     * @private
    -     * @param {*} value The value to check.
    -     * @returns {boolean} Returns `true` if `value` if suitable for strict
    -     *  equality comparisons, else `false`.
    -     */
    -    function isStrictComparable(value) {
    -      return value === value && !isObject(value);
    -    }
    -
    -    /**
    -     * A specialized version of `matchesProperty` for source values suitable
    -     * for strict equality comparisons, i.e. `===`.
    -     *
    -     * @private
    -     * @param {string} key The key of the property to get.
    -     * @param {*} srcValue The value to match.
    -     * @returns {Function} Returns the new spec function.
    -     */
    -    function matchesStrictComparable(key, srcValue) {
    -      return function (object) {
    -        if (object == null) {
    -          return false;
    -        }
    -        return object[key] === srcValue && (srcValue !== undefined || key in Object(object));
    -      };
    -    }
    -
    -    /**
    -     * A specialized version of `_.memoize` which clears the memoized function's
    -     * cache when it exceeds `MAX_MEMOIZE_SIZE`.
    -     *
    -     * @private
    -     * @param {Function} func The function to have its output memoized.
    -     * @returns {Function} Returns the new memoized function.
    -     */
    -    function memoizeCapped(func) {
    -      var result = memoize(func, function (key) {
    -        if (cache.size === MAX_MEMOIZE_SIZE) {
    -          cache.clear();
    -        }
    -        return key;
    -      });
    -      var cache = result.cache;
    -      return result;
    -    }
    -
    -    /**
    -     * Merges the function metadata of `source` into `data`.
    -     *
    -     * Merging metadata reduces the number of wrappers used to invoke a function.
    -     * This is possible because methods like `_.bind`, `_.curry`, and `_.partial`
    -     * may be applied regardless of execution order. Methods like `_.ary` and
    -     * `_.rearg` modify function arguments, making the order in which they are
    -     * executed important, preventing the merging of metadata. However, we make
    -     * an exception for a safe combined case where curried functions have `_.ary`
    -     * and or `_.rearg` applied.
    -     *
    -     * @private
    -     * @param {Array} data The destination metadata.
    -     * @param {Array} source The source metadata.
    -     * @returns {Array} Returns `data`.
    -     */
    -    function mergeData(data, source) {
    -      var bitmask = data[1],
    -        srcBitmask = source[1],
    -        newBitmask = bitmask | srcBitmask,
    -        isCommon = newBitmask < (WRAP_BIND_FLAG | WRAP_BIND_KEY_FLAG | WRAP_ARY_FLAG);
    -      var isCombo = srcBitmask == WRAP_ARY_FLAG && bitmask == WRAP_CURRY_FLAG || srcBitmask == WRAP_ARY_FLAG && bitmask == WRAP_REARG_FLAG && data[7].length <= source[8] || srcBitmask == (WRAP_ARY_FLAG | WRAP_REARG_FLAG) && source[7].length <= source[8] && bitmask == WRAP_CURRY_FLAG;
    -
    -      // Exit early if metadata can't be merged.
    -      if (!(isCommon || isCombo)) {
    -        return data;
    -      }
    -      // Use source `thisArg` if available.
    -      if (srcBitmask & WRAP_BIND_FLAG) {
    -        data[2] = source[2];
    -        // Set when currying a bound function.
    -        newBitmask |= bitmask & WRAP_BIND_FLAG ? 0 : WRAP_CURRY_BOUND_FLAG;
    -      }
    -      // Compose partial arguments.
    -      var value = source[3];
    -      if (value) {
    -        var partials = data[3];
    -        data[3] = partials ? composeArgs(partials, value, source[4]) : value;
    -        data[4] = partials ? replaceHolders(data[3], PLACEHOLDER) : source[4];
    -      }
    -      // Compose partial right arguments.
    -      value = source[5];
    -      if (value) {
    -        partials = data[5];
    -        data[5] = partials ? composeArgsRight(partials, value, source[6]) : value;
    -        data[6] = partials ? replaceHolders(data[5], PLACEHOLDER) : source[6];
    -      }
    -      // Use source `argPos` if available.
    -      value = source[7];
    -      if (value) {
    -        data[7] = value;
    -      }
    -      // Use source `ary` if it's smaller.
    -      if (srcBitmask & WRAP_ARY_FLAG) {
    -        data[8] = data[8] == null ? source[8] : nativeMin(data[8], source[8]);
    -      }
    -      // Use source `arity` if one is not provided.
    -      if (data[9] == null) {
    -        data[9] = source[9];
    -      }
    -      // Use source `func` and merge bitmasks.
    -      data[0] = source[0];
    -      data[1] = newBitmask;
    -      return data;
    -    }
    -
    -    /**
    -     * This function is like
    -     * [`Object.keys`](http://ecma-international.org/ecma-262/7.0/#sec-object.keys)
    -     * except that it includes inherited enumerable properties.
    -     *
    -     * @private
    -     * @param {Object} object The object to query.
    -     * @returns {Array} Returns the array of property names.
    -     */
    -    function nativeKeysIn(object) {
    -      var result = [];
    -      if (object != null) {
    -        for (var key in Object(object)) {
    -          result.push(key);
    -        }
    -      }
    -      return result;
    -    }
    -
    -    /**
    -     * Converts `value` to a string using `Object.prototype.toString`.
    -     *
    -     * @private
    -     * @param {*} value The value to convert.
    -     * @returns {string} Returns the converted string.
    -     */
    -    function objectToString(value) {
    -      return nativeObjectToString.call(value);
    -    }
    -
    -    /**
    -     * A specialized version of `baseRest` which transforms the rest array.
    -     *
    -     * @private
    -     * @param {Function} func The function to apply a rest parameter to.
    -     * @param {number} [start=func.length-1] The start position of the rest parameter.
    -     * @param {Function} transform The rest array transform.
    -     * @returns {Function} Returns the new function.
    -     */
    -    function overRest(func, start, transform) {
    -      start = nativeMax(start === undefined ? func.length - 1 : start, 0);
    -      return function () {
    -        var args = arguments,
    -          index = -1,
    -          length = nativeMax(args.length - start, 0),
    -          array = Array(length);
    -        while (++index < length) {
    -          array[index] = args[start + index];
    -        }
    -        index = -1;
    -        var otherArgs = Array(start + 1);
    -        while (++index < start) {
    -          otherArgs[index] = args[index];
    -        }
    -        otherArgs[start] = transform(array);
    -        return apply(func, this, otherArgs);
    -      };
    -    }
    -
    -    /**
    -     * Gets the parent value at `path` of `object`.
    -     *
    -     * @private
    -     * @param {Object} object The object to query.
    -     * @param {Array} path The path to get the parent value of.
    -     * @returns {*} Returns the parent value.
    -     */
    -    function parent(object, path) {
    -      return path.length < 2 ? object : baseGet(object, baseSlice(path, 0, -1));
    -    }
    -
    -    /**
    -     * Reorder `array` according to the specified indexes where the element at
    -     * the first index is assigned as the first element, the element at
    -     * the second index is assigned as the second element, and so on.
    -     *
    -     * @private
    -     * @param {Array} array The array to reorder.
    -     * @param {Array} indexes The arranged array indexes.
    -     * @returns {Array} Returns `array`.
    -     */
    -    function reorder(array, indexes) {
    -      var arrLength = array.length,
    -        length = nativeMin(indexes.length, arrLength),
    -        oldArray = copyArray(array);
    -      while (length--) {
    -        var index = indexes[length];
    -        array[length] = isIndex(index, arrLength) ? oldArray[index] : undefined;
    -      }
    -      return array;
    -    }
    -
    -    /**
    -     * Gets the value at `key`, unless `key` is "__proto__" or "constructor".
    -     *
    -     * @private
    -     * @param {Object} object The object to query.
    -     * @param {string} key The key of the property to get.
    -     * @returns {*} Returns the property value.
    -     */
    -    function safeGet(object, key) {
    -      if (key === 'constructor' && typeof object[key] === 'function') {
    -        return;
    -      }
    -      if (key == '__proto__') {
    -        return;
    -      }
    -      return object[key];
    -    }
    -
    -    /**
    -     * Sets metadata for `func`.
    -     *
    -     * **Note:** If this function becomes hot, i.e. is invoked a lot in a short
    -     * period of time, it will trip its breaker and transition to an identity
    -     * function to avoid garbage collection pauses in V8. See
    -     * [V8 issue 2070](https://bugs.chromium.org/p/v8/issues/detail?id=2070)
    -     * for more details.
    -     *
    -     * @private
    -     * @param {Function} func The function to associate metadata with.
    -     * @param {*} data The metadata.
    -     * @returns {Function} Returns `func`.
    -     */
    -    var setData = shortOut(baseSetData);
    -
    -    /**
    -     * A simple wrapper around the global [`setTimeout`](https://mdn.io/setTimeout).
    -     *
    -     * @private
    -     * @param {Function} func The function to delay.
    -     * @param {number} wait The number of milliseconds to delay invocation.
    -     * @returns {number|Object} Returns the timer id or timeout object.
    -     */
    -    var setTimeout = ctxSetTimeout || function (func, wait) {
    -      return root.setTimeout(func, wait);
    -    };
    -
    -    /**
    -     * Sets the `toString` method of `func` to return `string`.
    -     *
    -     * @private
    -     * @param {Function} func The function to modify.
    -     * @param {Function} string The `toString` result.
    -     * @returns {Function} Returns `func`.
    -     */
    -    var setToString = shortOut(baseSetToString);
    -
    -    /**
    -     * Sets the `toString` method of `wrapper` to mimic the source of `reference`
    -     * with wrapper details in a comment at the top of the source body.
    -     *
    -     * @private
    -     * @param {Function} wrapper The function to modify.
    -     * @param {Function} reference The reference function.
    -     * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
    -     * @returns {Function} Returns `wrapper`.
    -     */
    -    function setWrapToString(wrapper, reference, bitmask) {
    -      var source = reference + '';
    -      return setToString(wrapper, insertWrapDetails(source, updateWrapDetails(getWrapDetails(source), bitmask)));
    -    }
    -
    -    /**
    -     * Creates a function that'll short out and invoke `identity` instead
    -     * of `func` when it's called `HOT_COUNT` or more times in `HOT_SPAN`
    -     * milliseconds.
    -     *
    -     * @private
    -     * @param {Function} func The function to restrict.
    -     * @returns {Function} Returns the new shortable function.
    -     */
    -    function shortOut(func) {
    -      var count = 0,
    -        lastCalled = 0;
    -      return function () {
    -        var stamp = nativeNow(),
    -          remaining = HOT_SPAN - (stamp - lastCalled);
    -        lastCalled = stamp;
    -        if (remaining > 0) {
    -          if (++count >= HOT_COUNT) {
    -            return arguments[0];
    -          }
    -        } else {
    -          count = 0;
    -        }
    -        return func.apply(undefined, arguments);
    -      };
    -    }
    -
    -    /**
    -     * A specialized version of `_.shuffle` which mutates and sets the size of `array`.
    -     *
    -     * @private
    -     * @param {Array} array The array to shuffle.
    -     * @param {number} [size=array.length] The size of `array`.
    -     * @returns {Array} Returns `array`.
    -     */
    -    function shuffleSelf(array, size) {
    -      var index = -1,
    -        length = array.length,
    -        lastIndex = length - 1;
    -      size = size === undefined ? length : size;
    -      while (++index < size) {
    -        var rand = baseRandom(index, lastIndex),
    -          value = array[rand];
    -        array[rand] = array[index];
    -        array[index] = value;
    -      }
    -      array.length = size;
    -      return array;
    -    }
    -
    -    /**
    -     * Converts `string` to a property path array.
    -     *
    -     * @private
    -     * @param {string} string The string to convert.
    -     * @returns {Array} Returns the property path array.
    -     */
    -    var stringToPath = memoizeCapped(function (string) {
    -      var result = [];
    -      if (string.charCodeAt(0) === 46 /* . */) {
    -        result.push('');
    -      }
    -      string.replace(rePropName, function (match, number, quote, subString) {
    -        result.push(quote ? subString.replace(reEscapeChar, '$1') : number || match);
    -      });
    -      return result;
    -    });
    -
    -    /**
    -     * Converts `value` to a string key if it's not a string or symbol.
    -     *
    -     * @private
    -     * @param {*} value The value to inspect.
    -     * @returns {string|symbol} Returns the key.
    -     */
    -    function toKey(value) {
    -      if (typeof value == 'string' || isSymbol(value)) {
    -        return value;
    -      }
    -      var result = value + '';
    -      return result == '0' && 1 / value == -INFINITY ? '-0' : result;
    -    }
    -
    -    /**
    -     * Converts `func` to its source code.
    -     *
    -     * @private
    -     * @param {Function} func The function to convert.
    -     * @returns {string} Returns the source code.
    -     */
    -    function toSource(func) {
    -      if (func != null) {
    -        try {
    -          return funcToString.call(func);
    -        } catch (e) {}
    -        try {
    -          return func + '';
    -        } catch (e) {}
    -      }
    -      return '';
    -    }
    -
    -    /**
    -     * Updates wrapper `details` based on `bitmask` flags.
    -     *
    -     * @private
    -     * @returns {Array} details The details to modify.
    -     * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
    -     * @returns {Array} Returns `details`.
    -     */
    -    function updateWrapDetails(details, bitmask) {
    -      arrayEach(wrapFlags, function (pair) {
    -        var value = '_.' + pair[0];
    -        if (bitmask & pair[1] && !arrayIncludes(details, value)) {
    -          details.push(value);
    -        }
    -      });
    -      return details.sort();
    -    }
    -
    -    /**
    -     * Creates a clone of `wrapper`.
    -     *
    -     * @private
    -     * @param {Object} wrapper The wrapper to clone.
    -     * @returns {Object} Returns the cloned wrapper.
    -     */
    -    function wrapperClone(wrapper) {
    -      if (wrapper instanceof LazyWrapper) {
    -        return wrapper.clone();
    -      }
    -      var result = new LodashWrapper(wrapper.__wrapped__, wrapper.__chain__);
    -      result.__actions__ = copyArray(wrapper.__actions__);
    -      result.__index__ = wrapper.__index__;
    -      result.__values__ = wrapper.__values__;
    -      return result;
    -    }
    -
    -    /*------------------------------------------------------------------------*/
    -
    -    /**
    -     * Creates an array of elements split into groups the length of `size`.
    -     * If `array` can't be split evenly, the final chunk will be the remaining
    -     * elements.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 3.0.0
    -     * @category Array
    -     * @param {Array} array The array to process.
    -     * @param {number} [size=1] The length of each chunk
    -     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
    -     * @returns {Array} Returns the new array of chunks.
    -     * @example
    -     *
    -     * _.chunk(['a', 'b', 'c', 'd'], 2);
    -     * // => [['a', 'b'], ['c', 'd']]
    -     *
    -     * _.chunk(['a', 'b', 'c', 'd'], 3);
    -     * // => [['a', 'b', 'c'], ['d']]
    -     */
    -    function chunk(array, size, guard) {
    -      if (guard ? isIterateeCall(array, size, guard) : size === undefined) {
    -        size = 1;
    -      } else {
    -        size = nativeMax(toInteger(size), 0);
    -      }
    -      var length = array == null ? 0 : array.length;
    -      if (!length || size < 1) {
    -        return [];
    -      }
    -      var index = 0,
    -        resIndex = 0,
    -        result = Array(nativeCeil(length / size));
    -      while (index < length) {
    -        result[resIndex++] = baseSlice(array, index, index += size);
    -      }
    -      return result;
    -    }
    -
    -    /**
    -     * Creates an array with all falsey values removed. The values `false`, `null`,
    -     * `0`, `""`, `undefined`, and `NaN` are falsey.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.1.0
    -     * @category Array
    -     * @param {Array} array The array to compact.
    -     * @returns {Array} Returns the new array of filtered values.
    -     * @example
    -     *
    -     * _.compact([0, 1, false, 2, '', 3]);
    -     * // => [1, 2, 3]
    -     */
    -    function compact(array) {
    -      var index = -1,
    -        length = array == null ? 0 : array.length,
    -        resIndex = 0,
    -        result = [];
    -      while (++index < length) {
    -        var value = array[index];
    -        if (value) {
    -          result[resIndex++] = value;
    -        }
    -      }
    -      return result;
    -    }
    -
    -    /**
    -     * Creates a new array concatenating `array` with any additional arrays
    -     * and/or values.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 4.0.0
    -     * @category Array
    -     * @param {Array} array The array to concatenate.
    -     * @param {...*} [values] The values to concatenate.
    -     * @returns {Array} Returns the new concatenated array.
    -     * @example
    -     *
    -     * var array = [1];
    -     * var other = _.concat(array, 2, [3], [[4]]);
    -     *
    -     * console.log(other);
    -     * // => [1, 2, 3, [4]]
    -     *
    -     * console.log(array);
    -     * // => [1]
    -     */
    -    function concat() {
    -      var length = arguments.length;
    -      if (!length) {
    -        return [];
    -      }
    -      var args = Array(length - 1),
    -        array = arguments[0],
    -        index = length;
    -      while (index--) {
    -        args[index - 1] = arguments[index];
    -      }
    -      return arrayPush(isArray(array) ? copyArray(array) : [array], baseFlatten(args, 1));
    -    }
    -
    -    /**
    -     * Creates an array of `array` values not included in the other given arrays
    -     * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
    -     * for equality comparisons. The order and references of result values are
    -     * determined by the first array.
    -     *
    -     * **Note:** Unlike `_.pullAll`, this method returns a new array.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.1.0
    -     * @category Array
    -     * @param {Array} array The array to inspect.
    -     * @param {...Array} [values] The values to exclude.
    -     * @returns {Array} Returns the new array of filtered values.
    -     * @see _.without, _.xor
    -     * @example
    -     *
    -     * _.difference([2, 1], [2, 3]);
    -     * // => [1]
    -     */
    -    var difference = baseRest(function (array, values) {
    -      return isArrayLikeObject(array) ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true)) : [];
    -    });
    -
    -    /**
    -     * This method is like `_.difference` except that it accepts `iteratee` which
    -     * is invoked for each element of `array` and `values` to generate the criterion
    -     * by which they're compared. The order and references of result values are
    -     * determined by the first array. The iteratee is invoked with one argument:
    -     * (value).
    -     *
    -     * **Note:** Unlike `_.pullAllBy`, this method returns a new array.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 4.0.0
    -     * @category Array
    -     * @param {Array} array The array to inspect.
    -     * @param {...Array} [values] The values to exclude.
    -     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
    -     * @returns {Array} Returns the new array of filtered values.
    -     * @example
    -     *
    -     * _.differenceBy([2.1, 1.2], [2.3, 3.4], Math.floor);
    -     * // => [1.2]
    -     *
    -     * // The `_.property` iteratee shorthand.
    -     * _.differenceBy([{ 'x': 2 }, { 'x': 1 }], [{ 'x': 1 }], 'x');
    -     * // => [{ 'x': 2 }]
    -     */
    -    var differenceBy = baseRest(function (array, values) {
    -      var iteratee = last(values);
    -      if (isArrayLikeObject(iteratee)) {
    -        iteratee = undefined;
    -      }
    -      return isArrayLikeObject(array) ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true), getIteratee(iteratee, 2)) : [];
    -    });
    -
    -    /**
    -     * This method is like `_.difference` except that it accepts `comparator`
    -     * which is invoked to compare elements of `array` to `values`. The order and
    -     * references of result values are determined by the first array. The comparator
    -     * is invoked with two arguments: (arrVal, othVal).
    -     *
    -     * **Note:** Unlike `_.pullAllWith`, this method returns a new array.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 4.0.0
    -     * @category Array
    -     * @param {Array} array The array to inspect.
    -     * @param {...Array} [values] The values to exclude.
    -     * @param {Function} [comparator] The comparator invoked per element.
    -     * @returns {Array} Returns the new array of filtered values.
    -     * @example
    -     *
    -     * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
    -     *
    -     * _.differenceWith(objects, [{ 'x': 1, 'y': 2 }], _.isEqual);
    -     * // => [{ 'x': 2, 'y': 1 }]
    -     */
    -    var differenceWith = baseRest(function (array, values) {
    -      var comparator = last(values);
    -      if (isArrayLikeObject(comparator)) {
    -        comparator = undefined;
    -      }
    -      return isArrayLikeObject(array) ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true), undefined, comparator) : [];
    -    });
    -
    -    /**
    -     * Creates a slice of `array` with `n` elements dropped from the beginning.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.5.0
    -     * @category Array
    -     * @param {Array} array The array to query.
    -     * @param {number} [n=1] The number of elements to drop.
    -     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
    -     * @returns {Array} Returns the slice of `array`.
    -     * @example
    -     *
    -     * _.drop([1, 2, 3]);
    -     * // => [2, 3]
    -     *
    -     * _.drop([1, 2, 3], 2);
    -     * // => [3]
    -     *
    -     * _.drop([1, 2, 3], 5);
    -     * // => []
    -     *
    -     * _.drop([1, 2, 3], 0);
    -     * // => [1, 2, 3]
    -     */
    -    function drop(array, n, guard) {
    -      var length = array == null ? 0 : array.length;
    -      if (!length) {
    -        return [];
    -      }
    -      n = guard || n === undefined ? 1 : toInteger(n);
    -      return baseSlice(array, n < 0 ? 0 : n, length);
    -    }
    -
    -    /**
    -     * Creates a slice of `array` with `n` elements dropped from the end.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 3.0.0
    -     * @category Array
    -     * @param {Array} array The array to query.
    -     * @param {number} [n=1] The number of elements to drop.
    -     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
    -     * @returns {Array} Returns the slice of `array`.
    -     * @example
    -     *
    -     * _.dropRight([1, 2, 3]);
    -     * // => [1, 2]
    -     *
    -     * _.dropRight([1, 2, 3], 2);
    -     * // => [1]
    -     *
    -     * _.dropRight([1, 2, 3], 5);
    -     * // => []
    -     *
    -     * _.dropRight([1, 2, 3], 0);
    -     * // => [1, 2, 3]
    -     */
    -    function dropRight(array, n, guard) {
    -      var length = array == null ? 0 : array.length;
    -      if (!length) {
    -        return [];
    -      }
    -      n = guard || n === undefined ? 1 : toInteger(n);
    -      n = length - n;
    -      return baseSlice(array, 0, n < 0 ? 0 : n);
    -    }
    -
    -    /**
    -     * Creates a slice of `array` excluding elements dropped from the end.
    -     * Elements are dropped until `predicate` returns falsey. The predicate is
    -     * invoked with three arguments: (value, index, array).
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 3.0.0
    -     * @category Array
    -     * @param {Array} array The array to query.
    -     * @param {Function} [predicate=_.identity] The function invoked per iteration.
    -     * @returns {Array} Returns the slice of `array`.
    -     * @example
    -     *
    -     * var users = [
    -     *   { 'user': 'barney',  'active': true },
    -     *   { 'user': 'fred',    'active': false },
    -     *   { 'user': 'pebbles', 'active': false }
    -     * ];
    -     *
    -     * _.dropRightWhile(users, function(o) { return !o.active; });
    -     * // => objects for ['barney']
    -     *
    -     * // The `_.matches` iteratee shorthand.
    -     * _.dropRightWhile(users, { 'user': 'pebbles', 'active': false });
    -     * // => objects for ['barney', 'fred']
    -     *
    -     * // The `_.matchesProperty` iteratee shorthand.
    -     * _.dropRightWhile(users, ['active', false]);
    -     * // => objects for ['barney']
    -     *
    -     * // The `_.property` iteratee shorthand.
    -     * _.dropRightWhile(users, 'active');
    -     * // => objects for ['barney', 'fred', 'pebbles']
    -     */
    -    function dropRightWhile(array, predicate) {
    -      return array && array.length ? baseWhile(array, getIteratee(predicate, 3), true, true) : [];
    -    }
    -
    -    /**
    -     * Creates a slice of `array` excluding elements dropped from the beginning.
    -     * Elements are dropped until `predicate` returns falsey. The predicate is
    -     * invoked with three arguments: (value, index, array).
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 3.0.0
    -     * @category Array
    -     * @param {Array} array The array to query.
    -     * @param {Function} [predicate=_.identity] The function invoked per iteration.
    -     * @returns {Array} Returns the slice of `array`.
    -     * @example
    -     *
    -     * var users = [
    -     *   { 'user': 'barney',  'active': false },
    -     *   { 'user': 'fred',    'active': false },
    -     *   { 'user': 'pebbles', 'active': true }
    -     * ];
    -     *
    -     * _.dropWhile(users, function(o) { return !o.active; });
    -     * // => objects for ['pebbles']
    -     *
    -     * // The `_.matches` iteratee shorthand.
    -     * _.dropWhile(users, { 'user': 'barney', 'active': false });
    -     * // => objects for ['fred', 'pebbles']
    -     *
    -     * // The `_.matchesProperty` iteratee shorthand.
    -     * _.dropWhile(users, ['active', false]);
    -     * // => objects for ['pebbles']
    -     *
    -     * // The `_.property` iteratee shorthand.
    -     * _.dropWhile(users, 'active');
    -     * // => objects for ['barney', 'fred', 'pebbles']
    -     */
    -    function dropWhile(array, predicate) {
    -      return array && array.length ? baseWhile(array, getIteratee(predicate, 3), true) : [];
    -    }
    -
    -    /**
    -     * Fills elements of `array` with `value` from `start` up to, but not
    -     * including, `end`.
    -     *
    -     * **Note:** This method mutates `array`.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 3.2.0
    -     * @category Array
    -     * @param {Array} array The array to fill.
    -     * @param {*} value The value to fill `array` with.
    -     * @param {number} [start=0] The start position.
    -     * @param {number} [end=array.length] The end position.
    -     * @returns {Array} Returns `array`.
    -     * @example
    -     *
    -     * var array = [1, 2, 3];
    -     *
    -     * _.fill(array, 'a');
    -     * console.log(array);
    -     * // => ['a', 'a', 'a']
    -     *
    -     * _.fill(Array(3), 2);
    -     * // => [2, 2, 2]
    -     *
    -     * _.fill([4, 6, 8, 10], '*', 1, 3);
    -     * // => [4, '*', '*', 10]
    -     */
    -    function fill(array, value, start, end) {
    -      var length = array == null ? 0 : array.length;
    -      if (!length) {
    -        return [];
    -      }
    -      if (start && typeof start != 'number' && isIterateeCall(array, value, start)) {
    -        start = 0;
    -        end = length;
    -      }
    -      return baseFill(array, value, start, end);
    -    }
    -
    -    /**
    -     * This method is like `_.find` except that it returns the index of the first
    -     * element `predicate` returns truthy for instead of the element itself.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 1.1.0
    -     * @category Array
    -     * @param {Array} array The array to inspect.
    -     * @param {Function} [predicate=_.identity] The function invoked per iteration.
    -     * @param {number} [fromIndex=0] The index to search from.
    -     * @returns {number} Returns the index of the found element, else `-1`.
    -     * @example
    -     *
    -     * var users = [
    -     *   { 'user': 'barney',  'active': false },
    -     *   { 'user': 'fred',    'active': false },
    -     *   { 'user': 'pebbles', 'active': true }
    -     * ];
    -     *
    -     * _.findIndex(users, function(o) { return o.user == 'barney'; });
    -     * // => 0
    -     *
    -     * // The `_.matches` iteratee shorthand.
    -     * _.findIndex(users, { 'user': 'fred', 'active': false });
    -     * // => 1
    -     *
    -     * // The `_.matchesProperty` iteratee shorthand.
    -     * _.findIndex(users, ['active', false]);
    -     * // => 0
    -     *
    -     * // The `_.property` iteratee shorthand.
    -     * _.findIndex(users, 'active');
    -     * // => 2
    -     */
    -    function findIndex(array, predicate, fromIndex) {
    -      var length = array == null ? 0 : array.length;
    -      if (!length) {
    -        return -1;
    -      }
    -      var index = fromIndex == null ? 0 : toInteger(fromIndex);
    -      if (index < 0) {
    -        index = nativeMax(length + index, 0);
    -      }
    -      return baseFindIndex(array, getIteratee(predicate, 3), index);
    -    }
    -
    -    /**
    -     * This method is like `_.findIndex` except that it iterates over elements
    -     * of `collection` from right to left.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 2.0.0
    -     * @category Array
    -     * @param {Array} array The array to inspect.
    -     * @param {Function} [predicate=_.identity] The function invoked per iteration.
    -     * @param {number} [fromIndex=array.length-1] The index to search from.
    -     * @returns {number} Returns the index of the found element, else `-1`.
    -     * @example
    -     *
    -     * var users = [
    -     *   { 'user': 'barney',  'active': true },
    -     *   { 'user': 'fred',    'active': false },
    -     *   { 'user': 'pebbles', 'active': false }
    -     * ];
    -     *
    -     * _.findLastIndex(users, function(o) { return o.user == 'pebbles'; });
    -     * // => 2
    -     *
    -     * // The `_.matches` iteratee shorthand.
    -     * _.findLastIndex(users, { 'user': 'barney', 'active': true });
    -     * // => 0
    -     *
    -     * // The `_.matchesProperty` iteratee shorthand.
    -     * _.findLastIndex(users, ['active', false]);
    -     * // => 2
    -     *
    -     * // The `_.property` iteratee shorthand.
    -     * _.findLastIndex(users, 'active');
    -     * // => 0
    -     */
    -    function findLastIndex(array, predicate, fromIndex) {
    -      var length = array == null ? 0 : array.length;
    -      if (!length) {
    -        return -1;
    -      }
    -      var index = length - 1;
    -      if (fromIndex !== undefined) {
    -        index = toInteger(fromIndex);
    -        index = fromIndex < 0 ? nativeMax(length + index, 0) : nativeMin(index, length - 1);
    -      }
    -      return baseFindIndex(array, getIteratee(predicate, 3), index, true);
    -    }
    -
    -    /**
    -     * Flattens `array` a single level deep.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.1.0
    -     * @category Array
    -     * @param {Array} array The array to flatten.
    -     * @returns {Array} Returns the new flattened array.
    -     * @example
    -     *
    -     * _.flatten([1, [2, [3, [4]], 5]]);
    -     * // => [1, 2, [3, [4]], 5]
    -     */
    -    function flatten(array) {
    -      var length = array == null ? 0 : array.length;
    -      return length ? baseFlatten(array, 1) : [];
    -    }
    -
    -    /**
    -     * Recursively flattens `array`.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 3.0.0
    -     * @category Array
    -     * @param {Array} array The array to flatten.
    -     * @returns {Array} Returns the new flattened array.
    -     * @example
    -     *
    -     * _.flattenDeep([1, [2, [3, [4]], 5]]);
    -     * // => [1, 2, 3, 4, 5]
    -     */
    -    function flattenDeep(array) {
    -      var length = array == null ? 0 : array.length;
    -      return length ? baseFlatten(array, INFINITY) : [];
    -    }
    -
    -    /**
    -     * Recursively flatten `array` up to `depth` times.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 4.4.0
    -     * @category Array
    -     * @param {Array} array The array to flatten.
    -     * @param {number} [depth=1] The maximum recursion depth.
    -     * @returns {Array} Returns the new flattened array.
    -     * @example
    -     *
    -     * var array = [1, [2, [3, [4]], 5]];
    -     *
    -     * _.flattenDepth(array, 1);
    -     * // => [1, 2, [3, [4]], 5]
    -     *
    -     * _.flattenDepth(array, 2);
    -     * // => [1, 2, 3, [4], 5]
    -     */
    -    function flattenDepth(array, depth) {
    -      var length = array == null ? 0 : array.length;
    -      if (!length) {
    -        return [];
    -      }
    -      depth = depth === undefined ? 1 : toInteger(depth);
    -      return baseFlatten(array, depth);
    -    }
    -
    -    /**
    -     * The inverse of `_.toPairs`; this method returns an object composed
    -     * from key-value `pairs`.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 4.0.0
    -     * @category Array
    -     * @param {Array} pairs The key-value pairs.
    -     * @returns {Object} Returns the new object.
    -     * @example
    -     *
    -     * _.fromPairs([['a', 1], ['b', 2]]);
    -     * // => { 'a': 1, 'b': 2 }
    -     */
    -    function fromPairs(pairs) {
    -      var index = -1,
    -        length = pairs == null ? 0 : pairs.length,
    -        result = {};
    -      while (++index < length) {
    -        var pair = pairs[index];
    -        result[pair[0]] = pair[1];
    -      }
    -      return result;
    -    }
    -
    -    /**
    -     * Gets the first element of `array`.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.1.0
    -     * @alias first
    -     * @category Array
    -     * @param {Array} array The array to query.
    -     * @returns {*} Returns the first element of `array`.
    -     * @example
    -     *
    -     * _.head([1, 2, 3]);
    -     * // => 1
    -     *
    -     * _.head([]);
    -     * // => undefined
    -     */
    -    function head(array) {
    -      return array && array.length ? array[0] : undefined;
    -    }
    -
    -    /**
    -     * Gets the index at which the first occurrence of `value` is found in `array`
    -     * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
    -     * for equality comparisons. If `fromIndex` is negative, it's used as the
    -     * offset from the end of `array`.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.1.0
    -     * @category Array
    -     * @param {Array} array The array to inspect.
    -     * @param {*} value The value to search for.
    -     * @param {number} [fromIndex=0] The index to search from.
    -     * @returns {number} Returns the index of the matched value, else `-1`.
    -     * @example
    -     *
    -     * _.indexOf([1, 2, 1, 2], 2);
    -     * // => 1
    -     *
    -     * // Search from the `fromIndex`.
    -     * _.indexOf([1, 2, 1, 2], 2, 2);
    -     * // => 3
    -     */
    -    function indexOf(array, value, fromIndex) {
    -      var length = array == null ? 0 : array.length;
    -      if (!length) {
    -        return -1;
    -      }
    -      var index = fromIndex == null ? 0 : toInteger(fromIndex);
    -      if (index < 0) {
    -        index = nativeMax(length + index, 0);
    -      }
    -      return baseIndexOf(array, value, index);
    -    }
    -
    -    /**
    -     * Gets all but the last element of `array`.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.1.0
    -     * @category Array
    -     * @param {Array} array The array to query.
    -     * @returns {Array} Returns the slice of `array`.
    -     * @example
    -     *
    -     * _.initial([1, 2, 3]);
    -     * // => [1, 2]
    -     */
    -    function initial(array) {
    -      var length = array == null ? 0 : array.length;
    -      return length ? baseSlice(array, 0, -1) : [];
    -    }
    -
    -    /**
    -     * Creates an array of unique values that are included in all given arrays
    -     * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
    -     * for equality comparisons. The order and references of result values are
    -     * determined by the first array.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.1.0
    -     * @category Array
    -     * @param {...Array} [arrays] The arrays to inspect.
    -     * @returns {Array} Returns the new array of intersecting values.
    -     * @example
    -     *
    -     * _.intersection([2, 1], [2, 3]);
    -     * // => [2]
    -     */
    -    var intersection = baseRest(function (arrays) {
    -      var mapped = arrayMap(arrays, castArrayLikeObject);
    -      return mapped.length && mapped[0] === arrays[0] ? baseIntersection(mapped) : [];
    -    });
    -
    -    /**
    -     * This method is like `_.intersection` except that it accepts `iteratee`
    -     * which is invoked for each element of each `arrays` to generate the criterion
    -     * by which they're compared. The order and references of result values are
    -     * determined by the first array. The iteratee is invoked with one argument:
    -     * (value).
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 4.0.0
    -     * @category Array
    -     * @param {...Array} [arrays] The arrays to inspect.
    -     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
    -     * @returns {Array} Returns the new array of intersecting values.
    -     * @example
    -     *
    -     * _.intersectionBy([2.1, 1.2], [2.3, 3.4], Math.floor);
    -     * // => [2.1]
    -     *
    -     * // The `_.property` iteratee shorthand.
    -     * _.intersectionBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');
    -     * // => [{ 'x': 1 }]
    -     */
    -    var intersectionBy = baseRest(function (arrays) {
    -      var iteratee = last(arrays),
    -        mapped = arrayMap(arrays, castArrayLikeObject);
    -      if (iteratee === last(mapped)) {
    -        iteratee = undefined;
    -      } else {
    -        mapped.pop();
    -      }
    -      return mapped.length && mapped[0] === arrays[0] ? baseIntersection(mapped, getIteratee(iteratee, 2)) : [];
    -    });
    -
    -    /**
    -     * This method is like `_.intersection` except that it accepts `comparator`
    -     * which is invoked to compare elements of `arrays`. The order and references
    -     * of result values are determined by the first array. The comparator is
    -     * invoked with two arguments: (arrVal, othVal).
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 4.0.0
    -     * @category Array
    -     * @param {...Array} [arrays] The arrays to inspect.
    -     * @param {Function} [comparator] The comparator invoked per element.
    -     * @returns {Array} Returns the new array of intersecting values.
    -     * @example
    -     *
    -     * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
    -     * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];
    -     *
    -     * _.intersectionWith(objects, others, _.isEqual);
    -     * // => [{ 'x': 1, 'y': 2 }]
    -     */
    -    var intersectionWith = baseRest(function (arrays) {
    -      var comparator = last(arrays),
    -        mapped = arrayMap(arrays, castArrayLikeObject);
    -      comparator = typeof comparator == 'function' ? comparator : undefined;
    -      if (comparator) {
    -        mapped.pop();
    -      }
    -      return mapped.length && mapped[0] === arrays[0] ? baseIntersection(mapped, undefined, comparator) : [];
    -    });
    -
    -    /**
    -     * Converts all elements in `array` into a string separated by `separator`.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 4.0.0
    -     * @category Array
    -     * @param {Array} array The array to convert.
    -     * @param {string} [separator=','] The element separator.
    -     * @returns {string} Returns the joined string.
    -     * @example
    -     *
    -     * _.join(['a', 'b', 'c'], '~');
    -     * // => 'a~b~c'
    -     */
    -    function join(array, separator) {
    -      return array == null ? '' : nativeJoin.call(array, separator);
    -    }
    -
    -    /**
    -     * Gets the last element of `array`.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.1.0
    -     * @category Array
    -     * @param {Array} array The array to query.
    -     * @returns {*} Returns the last element of `array`.
    -     * @example
    -     *
    -     * _.last([1, 2, 3]);
    -     * // => 3
    -     */
    -    function last(array) {
    -      var length = array == null ? 0 : array.length;
    -      return length ? array[length - 1] : undefined;
    -    }
    -
    -    /**
    -     * This method is like `_.indexOf` except that it iterates over elements of
    -     * `array` from right to left.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.1.0
    -     * @category Array
    -     * @param {Array} array The array to inspect.
    -     * @param {*} value The value to search for.
    -     * @param {number} [fromIndex=array.length-1] The index to search from.
    -     * @returns {number} Returns the index of the matched value, else `-1`.
    -     * @example
    -     *
    -     * _.lastIndexOf([1, 2, 1, 2], 2);
    -     * // => 3
    -     *
    -     * // Search from the `fromIndex`.
    -     * _.lastIndexOf([1, 2, 1, 2], 2, 2);
    -     * // => 1
    -     */
    -    function lastIndexOf(array, value, fromIndex) {
    -      var length = array == null ? 0 : array.length;
    -      if (!length) {
    -        return -1;
    -      }
    -      var index = length;
    -      if (fromIndex !== undefined) {
    -        index = toInteger(fromIndex);
    -        index = index < 0 ? nativeMax(length + index, 0) : nativeMin(index, length - 1);
    -      }
    -      return value === value ? strictLastIndexOf(array, value, index) : baseFindIndex(array, baseIsNaN, index, true);
    -    }
    -
    -    /**
    -     * Gets the element at index `n` of `array`. If `n` is negative, the nth
    -     * element from the end is returned.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 4.11.0
    -     * @category Array
    -     * @param {Array} array The array to query.
    -     * @param {number} [n=0] The index of the element to return.
    -     * @returns {*} Returns the nth element of `array`.
    -     * @example
    -     *
    -     * var array = ['a', 'b', 'c', 'd'];
    -     *
    -     * _.nth(array, 1);
    -     * // => 'b'
    -     *
    -     * _.nth(array, -2);
    -     * // => 'c';
    -     */
    -    function nth(array, n) {
    -      return array && array.length ? baseNth(array, toInteger(n)) : undefined;
    -    }
    -
    -    /**
    -     * Removes all given values from `array` using
    -     * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
    -     * for equality comparisons.
    -     *
    -     * **Note:** Unlike `_.without`, this method mutates `array`. Use `_.remove`
    -     * to remove elements from an array by predicate.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 2.0.0
    -     * @category Array
    -     * @param {Array} array The array to modify.
    -     * @param {...*} [values] The values to remove.
    -     * @returns {Array} Returns `array`.
    -     * @example
    -     *
    -     * var array = ['a', 'b', 'c', 'a', 'b', 'c'];
    -     *
    -     * _.pull(array, 'a', 'c');
    -     * console.log(array);
    -     * // => ['b', 'b']
    -     */
    -    var pull = baseRest(pullAll);
    -
    -    /**
    -     * This method is like `_.pull` except that it accepts an array of values to remove.
    -     *
    -     * **Note:** Unlike `_.difference`, this method mutates `array`.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 4.0.0
    -     * @category Array
    -     * @param {Array} array The array to modify.
    -     * @param {Array} values The values to remove.
    -     * @returns {Array} Returns `array`.
    -     * @example
    -     *
    -     * var array = ['a', 'b', 'c', 'a', 'b', 'c'];
    -     *
    -     * _.pullAll(array, ['a', 'c']);
    -     * console.log(array);
    -     * // => ['b', 'b']
    -     */
    -    function pullAll(array, values) {
    -      return array && array.length && values && values.length ? basePullAll(array, values) : array;
    -    }
    -
    -    /**
    -     * This method is like `_.pullAll` except that it accepts `iteratee` which is
    -     * invoked for each element of `array` and `values` to generate the criterion
    -     * by which they're compared. The iteratee is invoked with one argument: (value).
    -     *
    -     * **Note:** Unlike `_.differenceBy`, this method mutates `array`.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 4.0.0
    -     * @category Array
    -     * @param {Array} array The array to modify.
    -     * @param {Array} values The values to remove.
    -     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
    -     * @returns {Array} Returns `array`.
    -     * @example
    -     *
    -     * var array = [{ 'x': 1 }, { 'x': 2 }, { 'x': 3 }, { 'x': 1 }];
    -     *
    -     * _.pullAllBy(array, [{ 'x': 1 }, { 'x': 3 }], 'x');
    -     * console.log(array);
    -     * // => [{ 'x': 2 }]
    -     */
    -    function pullAllBy(array, values, iteratee) {
    -      return array && array.length && values && values.length ? basePullAll(array, values, getIteratee(iteratee, 2)) : array;
    -    }
    -
    -    /**
    -     * This method is like `_.pullAll` except that it accepts `comparator` which
    -     * is invoked to compare elements of `array` to `values`. The comparator is
    -     * invoked with two arguments: (arrVal, othVal).
    -     *
    -     * **Note:** Unlike `_.differenceWith`, this method mutates `array`.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 4.6.0
    -     * @category Array
    -     * @param {Array} array The array to modify.
    -     * @param {Array} values The values to remove.
    -     * @param {Function} [comparator] The comparator invoked per element.
    -     * @returns {Array} Returns `array`.
    -     * @example
    -     *
    -     * var array = [{ 'x': 1, 'y': 2 }, { 'x': 3, 'y': 4 }, { 'x': 5, 'y': 6 }];
    -     *
    -     * _.pullAllWith(array, [{ 'x': 3, 'y': 4 }], _.isEqual);
    -     * console.log(array);
    -     * // => [{ 'x': 1, 'y': 2 }, { 'x': 5, 'y': 6 }]
    -     */
    -    function pullAllWith(array, values, comparator) {
    -      return array && array.length && values && values.length ? basePullAll(array, values, undefined, comparator) : array;
    -    }
    -
    -    /**
    -     * Removes elements from `array` corresponding to `indexes` and returns an
    -     * array of removed elements.
    -     *
    -     * **Note:** Unlike `_.at`, this method mutates `array`.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 3.0.0
    -     * @category Array
    -     * @param {Array} array The array to modify.
    -     * @param {...(number|number[])} [indexes] The indexes of elements to remove.
    -     * @returns {Array} Returns the new array of removed elements.
    -     * @example
    -     *
    -     * var array = ['a', 'b', 'c', 'd'];
    -     * var pulled = _.pullAt(array, [1, 3]);
    -     *
    -     * console.log(array);
    -     * // => ['a', 'c']
    -     *
    -     * console.log(pulled);
    -     * // => ['b', 'd']
    -     */
    -    var pullAt = flatRest(function (array, indexes) {
    -      var length = array == null ? 0 : array.length,
    -        result = baseAt(array, indexes);
    -      basePullAt(array, arrayMap(indexes, function (index) {
    -        return isIndex(index, length) ? +index : index;
    -      }).sort(compareAscending));
    -      return result;
    -    });
    -
    -    /**
    -     * Removes all elements from `array` that `predicate` returns truthy for
    -     * and returns an array of the removed elements. The predicate is invoked
    -     * with three arguments: (value, index, array).
    -     *
    -     * **Note:** Unlike `_.filter`, this method mutates `array`. Use `_.pull`
    -     * to pull elements from an array by value.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 2.0.0
    -     * @category Array
    -     * @param {Array} array The array to modify.
    -     * @param {Function} [predicate=_.identity] The function invoked per iteration.
    -     * @returns {Array} Returns the new array of removed elements.
    -     * @example
    -     *
    -     * var array = [1, 2, 3, 4];
    -     * var evens = _.remove(array, function(n) {
    -     *   return n % 2 == 0;
    -     * });
    -     *
    -     * console.log(array);
    -     * // => [1, 3]
    -     *
    -     * console.log(evens);
    -     * // => [2, 4]
    -     */
    -    function remove(array, predicate) {
    -      var result = [];
    -      if (!(array && array.length)) {
    -        return result;
    -      }
    -      var index = -1,
    -        indexes = [],
    -        length = array.length;
    -      predicate = getIteratee(predicate, 3);
    -      while (++index < length) {
    -        var value = array[index];
    -        if (predicate(value, index, array)) {
    -          result.push(value);
    -          indexes.push(index);
    -        }
    -      }
    -      basePullAt(array, indexes);
    -      return result;
    -    }
    -
    -    /**
    -     * Reverses `array` so that the first element becomes the last, the second
    -     * element becomes the second to last, and so on.
    -     *
    -     * **Note:** This method mutates `array` and is based on
    -     * [`Array#reverse`](https://mdn.io/Array/reverse).
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 4.0.0
    -     * @category Array
    -     * @param {Array} array The array to modify.
    -     * @returns {Array} Returns `array`.
    -     * @example
    -     *
    -     * var array = [1, 2, 3];
    -     *
    -     * _.reverse(array);
    -     * // => [3, 2, 1]
    -     *
    -     * console.log(array);
    -     * // => [3, 2, 1]
    -     */
    -    function reverse(array) {
    -      return array == null ? array : nativeReverse.call(array);
    -    }
    -
    -    /**
    -     * Creates a slice of `array` from `start` up to, but not including, `end`.
    -     *
    -     * **Note:** This method is used instead of
    -     * [`Array#slice`](https://mdn.io/Array/slice) to ensure dense arrays are
    -     * returned.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 3.0.0
    -     * @category Array
    -     * @param {Array} array The array to slice.
    -     * @param {number} [start=0] The start position.
    -     * @param {number} [end=array.length] The end position.
    -     * @returns {Array} Returns the slice of `array`.
    -     */
    -    function slice(array, start, end) {
    -      var length = array == null ? 0 : array.length;
    -      if (!length) {
    -        return [];
    -      }
    -      if (end && typeof end != 'number' && isIterateeCall(array, start, end)) {
    -        start = 0;
    -        end = length;
    -      } else {
    -        start = start == null ? 0 : toInteger(start);
    -        end = end === undefined ? length : toInteger(end);
    -      }
    -      return baseSlice(array, start, end);
    -    }
    -
    -    /**
    -     * Uses a binary search to determine the lowest index at which `value`
    -     * should be inserted into `array` in order to maintain its sort order.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.1.0
    -     * @category Array
    -     * @param {Array} array The sorted array to inspect.
    -     * @param {*} value The value to evaluate.
    -     * @returns {number} Returns the index at which `value` should be inserted
    -     *  into `array`.
    -     * @example
    -     *
    -     * _.sortedIndex([30, 50], 40);
    -     * // => 1
    -     */
    -    function sortedIndex(array, value) {
    -      return baseSortedIndex(array, value);
    -    }
    -
    -    /**
    -     * This method is like `_.sortedIndex` except that it accepts `iteratee`
    -     * which is invoked for `value` and each element of `array` to compute their
    -     * sort ranking. The iteratee is invoked with one argument: (value).
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 4.0.0
    -     * @category Array
    -     * @param {Array} array The sorted array to inspect.
    -     * @param {*} value The value to evaluate.
    -     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
    -     * @returns {number} Returns the index at which `value` should be inserted
    -     *  into `array`.
    -     * @example
    -     *
    -     * var objects = [{ 'x': 4 }, { 'x': 5 }];
    -     *
    -     * _.sortedIndexBy(objects, { 'x': 4 }, function(o) { return o.x; });
    -     * // => 0
    -     *
    -     * // The `_.property` iteratee shorthand.
    -     * _.sortedIndexBy(objects, { 'x': 4 }, 'x');
    -     * // => 0
    -     */
    -    function sortedIndexBy(array, value, iteratee) {
    -      return baseSortedIndexBy(array, value, getIteratee(iteratee, 2));
    -    }
    -
    -    /**
    -     * This method is like `_.indexOf` except that it performs a binary
    -     * search on a sorted `array`.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 4.0.0
    -     * @category Array
    -     * @param {Array} array The array to inspect.
    -     * @param {*} value The value to search for.
    -     * @returns {number} Returns the index of the matched value, else `-1`.
    -     * @example
    -     *
    -     * _.sortedIndexOf([4, 5, 5, 5, 6], 5);
    -     * // => 1
    -     */
    -    function sortedIndexOf(array, value) {
    -      var length = array == null ? 0 : array.length;
    -      if (length) {
    -        var index = baseSortedIndex(array, value);
    -        if (index < length && eq(array[index], value)) {
    -          return index;
    -        }
    -      }
    -      return -1;
    -    }
    -
    -    /**
    -     * This method is like `_.sortedIndex` except that it returns the highest
    -     * index at which `value` should be inserted into `array` in order to
    -     * maintain its sort order.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 3.0.0
    -     * @category Array
    -     * @param {Array} array The sorted array to inspect.
    -     * @param {*} value The value to evaluate.
    -     * @returns {number} Returns the index at which `value` should be inserted
    -     *  into `array`.
    -     * @example
    -     *
    -     * _.sortedLastIndex([4, 5, 5, 5, 6], 5);
    -     * // => 4
    -     */
    -    function sortedLastIndex(array, value) {
    -      return baseSortedIndex(array, value, true);
    -    }
    -
    -    /**
    -     * This method is like `_.sortedLastIndex` except that it accepts `iteratee`
    -     * which is invoked for `value` and each element of `array` to compute their
    -     * sort ranking. The iteratee is invoked with one argument: (value).
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 4.0.0
    -     * @category Array
    -     * @param {Array} array The sorted array to inspect.
    -     * @param {*} value The value to evaluate.
    -     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
    -     * @returns {number} Returns the index at which `value` should be inserted
    -     *  into `array`.
    -     * @example
    -     *
    -     * var objects = [{ 'x': 4 }, { 'x': 5 }];
    -     *
    -     * _.sortedLastIndexBy(objects, { 'x': 4 }, function(o) { return o.x; });
    -     * // => 1
    -     *
    -     * // The `_.property` iteratee shorthand.
    -     * _.sortedLastIndexBy(objects, { 'x': 4 }, 'x');
    -     * // => 1
    -     */
    -    function sortedLastIndexBy(array, value, iteratee) {
    -      return baseSortedIndexBy(array, value, getIteratee(iteratee, 2), true);
    -    }
    -
    -    /**
    -     * This method is like `_.lastIndexOf` except that it performs a binary
    -     * search on a sorted `array`.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 4.0.0
    -     * @category Array
    -     * @param {Array} array The array to inspect.
    -     * @param {*} value The value to search for.
    -     * @returns {number} Returns the index of the matched value, else `-1`.
    -     * @example
    -     *
    -     * _.sortedLastIndexOf([4, 5, 5, 5, 6], 5);
    -     * // => 3
    -     */
    -    function sortedLastIndexOf(array, value) {
    -      var length = array == null ? 0 : array.length;
    -      if (length) {
    -        var index = baseSortedIndex(array, value, true) - 1;
    -        if (eq(array[index], value)) {
    -          return index;
    -        }
    -      }
    -      return -1;
    -    }
    -
    -    /**
    -     * This method is like `_.uniq` except that it's designed and optimized
    -     * for sorted arrays.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 4.0.0
    -     * @category Array
    -     * @param {Array} array The array to inspect.
    -     * @returns {Array} Returns the new duplicate free array.
    -     * @example
    -     *
    -     * _.sortedUniq([1, 1, 2]);
    -     * // => [1, 2]
    -     */
    -    function sortedUniq(array) {
    -      return array && array.length ? baseSortedUniq(array) : [];
    -    }
    -
    -    /**
    -     * This method is like `_.uniqBy` except that it's designed and optimized
    -     * for sorted arrays.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 4.0.0
    -     * @category Array
    -     * @param {Array} array The array to inspect.
    -     * @param {Function} [iteratee] The iteratee invoked per element.
    -     * @returns {Array} Returns the new duplicate free array.
    -     * @example
    -     *
    -     * _.sortedUniqBy([1.1, 1.2, 2.3, 2.4], Math.floor);
    -     * // => [1.1, 2.3]
    -     */
    -    function sortedUniqBy(array, iteratee) {
    -      return array && array.length ? baseSortedUniq(array, getIteratee(iteratee, 2)) : [];
    -    }
    -
    -    /**
    -     * Gets all but the first element of `array`.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 4.0.0
    -     * @category Array
    -     * @param {Array} array The array to query.
    -     * @returns {Array} Returns the slice of `array`.
    -     * @example
    -     *
    -     * _.tail([1, 2, 3]);
    -     * // => [2, 3]
    -     */
    -    function tail(array) {
    -      var length = array == null ? 0 : array.length;
    -      return length ? baseSlice(array, 1, length) : [];
    -    }
    -
    -    /**
    -     * Creates a slice of `array` with `n` elements taken from the beginning.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.1.0
    -     * @category Array
    -     * @param {Array} array The array to query.
    -     * @param {number} [n=1] The number of elements to take.
    -     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
    -     * @returns {Array} Returns the slice of `array`.
    -     * @example
    -     *
    -     * _.take([1, 2, 3]);
    -     * // => [1]
    -     *
    -     * _.take([1, 2, 3], 2);
    -     * // => [1, 2]
    -     *
    -     * _.take([1, 2, 3], 5);
    -     * // => [1, 2, 3]
    -     *
    -     * _.take([1, 2, 3], 0);
    -     * // => []
    -     */
    -    function take(array, n, guard) {
    -      if (!(array && array.length)) {
    -        return [];
    -      }
    -      n = guard || n === undefined ? 1 : toInteger(n);
    -      return baseSlice(array, 0, n < 0 ? 0 : n);
    -    }
    -
    -    /**
    -     * Creates a slice of `array` with `n` elements taken from the end.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 3.0.0
    -     * @category Array
    -     * @param {Array} array The array to query.
    -     * @param {number} [n=1] The number of elements to take.
    -     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
    -     * @returns {Array} Returns the slice of `array`.
    -     * @example
    -     *
    -     * _.takeRight([1, 2, 3]);
    -     * // => [3]
    -     *
    -     * _.takeRight([1, 2, 3], 2);
    -     * // => [2, 3]
    -     *
    -     * _.takeRight([1, 2, 3], 5);
    -     * // => [1, 2, 3]
    -     *
    -     * _.takeRight([1, 2, 3], 0);
    -     * // => []
    -     */
    -    function takeRight(array, n, guard) {
    -      var length = array == null ? 0 : array.length;
    -      if (!length) {
    -        return [];
    -      }
    -      n = guard || n === undefined ? 1 : toInteger(n);
    -      n = length - n;
    -      return baseSlice(array, n < 0 ? 0 : n, length);
    -    }
    -
    -    /**
    -     * Creates a slice of `array` with elements taken from the end. Elements are
    -     * taken until `predicate` returns falsey. The predicate is invoked with
    -     * three arguments: (value, index, array).
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 3.0.0
    -     * @category Array
    -     * @param {Array} array The array to query.
    -     * @param {Function} [predicate=_.identity] The function invoked per iteration.
    -     * @returns {Array} Returns the slice of `array`.
    -     * @example
    -     *
    -     * var users = [
    -     *   { 'user': 'barney',  'active': true },
    -     *   { 'user': 'fred',    'active': false },
    -     *   { 'user': 'pebbles', 'active': false }
    -     * ];
    -     *
    -     * _.takeRightWhile(users, function(o) { return !o.active; });
    -     * // => objects for ['fred', 'pebbles']
    -     *
    -     * // The `_.matches` iteratee shorthand.
    -     * _.takeRightWhile(users, { 'user': 'pebbles', 'active': false });
    -     * // => objects for ['pebbles']
    -     *
    -     * // The `_.matchesProperty` iteratee shorthand.
    -     * _.takeRightWhile(users, ['active', false]);
    -     * // => objects for ['fred', 'pebbles']
    -     *
    -     * // The `_.property` iteratee shorthand.
    -     * _.takeRightWhile(users, 'active');
    -     * // => []
    -     */
    -    function takeRightWhile(array, predicate) {
    -      return array && array.length ? baseWhile(array, getIteratee(predicate, 3), false, true) : [];
    -    }
    -
    -    /**
    -     * Creates a slice of `array` with elements taken from the beginning. Elements
    -     * are taken until `predicate` returns falsey. The predicate is invoked with
    -     * three arguments: (value, index, array).
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 3.0.0
    -     * @category Array
    -     * @param {Array} array The array to query.
    -     * @param {Function} [predicate=_.identity] The function invoked per iteration.
    -     * @returns {Array} Returns the slice of `array`.
    -     * @example
    -     *
    -     * var users = [
    -     *   { 'user': 'barney',  'active': false },
    -     *   { 'user': 'fred',    'active': false },
    -     *   { 'user': 'pebbles', 'active': true }
    -     * ];
    -     *
    -     * _.takeWhile(users, function(o) { return !o.active; });
    -     * // => objects for ['barney', 'fred']
    -     *
    -     * // The `_.matches` iteratee shorthand.
    -     * _.takeWhile(users, { 'user': 'barney', 'active': false });
    -     * // => objects for ['barney']
    -     *
    -     * // The `_.matchesProperty` iteratee shorthand.
    -     * _.takeWhile(users, ['active', false]);
    -     * // => objects for ['barney', 'fred']
    -     *
    -     * // The `_.property` iteratee shorthand.
    -     * _.takeWhile(users, 'active');
    -     * // => []
    -     */
    -    function takeWhile(array, predicate) {
    -      return array && array.length ? baseWhile(array, getIteratee(predicate, 3)) : [];
    -    }
    -
    -    /**
    -     * Creates an array of unique values, in order, from all given arrays using
    -     * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
    -     * for equality comparisons.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.1.0
    -     * @category Array
    -     * @param {...Array} [arrays] The arrays to inspect.
    -     * @returns {Array} Returns the new array of combined values.
    -     * @example
    -     *
    -     * _.union([2], [1, 2]);
    -     * // => [2, 1]
    -     */
    -    var union = baseRest(function (arrays) {
    -      return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true));
    -    });
    -
    -    /**
    -     * This method is like `_.union` except that it accepts `iteratee` which is
    -     * invoked for each element of each `arrays` to generate the criterion by
    -     * which uniqueness is computed. Result values are chosen from the first
    -     * array in which the value occurs. The iteratee is invoked with one argument:
    -     * (value).
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 4.0.0
    -     * @category Array
    -     * @param {...Array} [arrays] The arrays to inspect.
    -     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
    -     * @returns {Array} Returns the new array of combined values.
    -     * @example
    -     *
    -     * _.unionBy([2.1], [1.2, 2.3], Math.floor);
    -     * // => [2.1, 1.2]
    -     *
    -     * // The `_.property` iteratee shorthand.
    -     * _.unionBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');
    -     * // => [{ 'x': 1 }, { 'x': 2 }]
    -     */
    -    var unionBy = baseRest(function (arrays) {
    -      var iteratee = last(arrays);
    -      if (isArrayLikeObject(iteratee)) {
    -        iteratee = undefined;
    -      }
    -      return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true), getIteratee(iteratee, 2));
    -    });
    -
    -    /**
    -     * This method is like `_.union` except that it accepts `comparator` which
    -     * is invoked to compare elements of `arrays`. Result values are chosen from
    -     * the first array in which the value occurs. The comparator is invoked
    -     * with two arguments: (arrVal, othVal).
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 4.0.0
    -     * @category Array
    -     * @param {...Array} [arrays] The arrays to inspect.
    -     * @param {Function} [comparator] The comparator invoked per element.
    -     * @returns {Array} Returns the new array of combined values.
    -     * @example
    -     *
    -     * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
    -     * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];
    -     *
    -     * _.unionWith(objects, others, _.isEqual);
    -     * // => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }]
    -     */
    -    var unionWith = baseRest(function (arrays) {
    -      var comparator = last(arrays);
    -      comparator = typeof comparator == 'function' ? comparator : undefined;
    -      return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true), undefined, comparator);
    -    });
    -
    -    /**
    -     * Creates a duplicate-free version of an array, using
    -     * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
    -     * for equality comparisons, in which only the first occurrence of each element
    -     * is kept. The order of result values is determined by the order they occur
    -     * in the array.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.1.0
    -     * @category Array
    -     * @param {Array} array The array to inspect.
    -     * @returns {Array} Returns the new duplicate free array.
    -     * @example
    -     *
    -     * _.uniq([2, 1, 2]);
    -     * // => [2, 1]
    -     */
    -    function uniq(array) {
    -      return array && array.length ? baseUniq(array) : [];
    -    }
    -
    -    /**
    -     * This method is like `_.uniq` except that it accepts `iteratee` which is
    -     * invoked for each element in `array` to generate the criterion by which
    -     * uniqueness is computed. The order of result values is determined by the
    -     * order they occur in the array. The iteratee is invoked with one argument:
    -     * (value).
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 4.0.0
    -     * @category Array
    -     * @param {Array} array The array to inspect.
    -     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
    -     * @returns {Array} Returns the new duplicate free array.
    -     * @example
    -     *
    -     * _.uniqBy([2.1, 1.2, 2.3], Math.floor);
    -     * // => [2.1, 1.2]
    -     *
    -     * // The `_.property` iteratee shorthand.
    -     * _.uniqBy([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x');
    -     * // => [{ 'x': 1 }, { 'x': 2 }]
    -     */
    -    function uniqBy(array, iteratee) {
    -      return array && array.length ? baseUniq(array, getIteratee(iteratee, 2)) : [];
    -    }
    -
    -    /**
    -     * This method is like `_.uniq` except that it accepts `comparator` which
    -     * is invoked to compare elements of `array`. The order of result values is
    -     * determined by the order they occur in the array.The comparator is invoked
    -     * with two arguments: (arrVal, othVal).
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 4.0.0
    -     * @category Array
    -     * @param {Array} array The array to inspect.
    -     * @param {Function} [comparator] The comparator invoked per element.
    -     * @returns {Array} Returns the new duplicate free array.
    -     * @example
    -     *
    -     * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 2 }];
    -     *
    -     * _.uniqWith(objects, _.isEqual);
    -     * // => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]
    -     */
    -    function uniqWith(array, comparator) {
    -      comparator = typeof comparator == 'function' ? comparator : undefined;
    -      return array && array.length ? baseUniq(array, undefined, comparator) : [];
    -    }
    -
    -    /**
    -     * This method is like `_.zip` except that it accepts an array of grouped
    -     * elements and creates an array regrouping the elements to their pre-zip
    -     * configuration.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 1.2.0
    -     * @category Array
    -     * @param {Array} array The array of grouped elements to process.
    -     * @returns {Array} Returns the new array of regrouped elements.
    -     * @example
    -     *
    -     * var zipped = _.zip(['a', 'b'], [1, 2], [true, false]);
    -     * // => [['a', 1, true], ['b', 2, false]]
    -     *
    -     * _.unzip(zipped);
    -     * // => [['a', 'b'], [1, 2], [true, false]]
    -     */
    -    function unzip(array) {
    -      if (!(array && array.length)) {
    -        return [];
    -      }
    -      var length = 0;
    -      array = arrayFilter(array, function (group) {
    -        if (isArrayLikeObject(group)) {
    -          length = nativeMax(group.length, length);
    -          return true;
    -        }
    -      });
    -      return baseTimes(length, function (index) {
    -        return arrayMap(array, baseProperty(index));
    -      });
    -    }
    -
    -    /**
    -     * This method is like `_.unzip` except that it accepts `iteratee` to specify
    -     * how regrouped values should be combined. The iteratee is invoked with the
    -     * elements of each group: (...group).
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 3.8.0
    -     * @category Array
    -     * @param {Array} array The array of grouped elements to process.
    -     * @param {Function} [iteratee=_.identity] The function to combine
    -     *  regrouped values.
    -     * @returns {Array} Returns the new array of regrouped elements.
    -     * @example
    -     *
    -     * var zipped = _.zip([1, 2], [10, 20], [100, 200]);
    -     * // => [[1, 10, 100], [2, 20, 200]]
    -     *
    -     * _.unzipWith(zipped, _.add);
    -     * // => [3, 30, 300]
    -     */
    -    function unzipWith(array, iteratee) {
    -      if (!(array && array.length)) {
    -        return [];
    -      }
    -      var result = unzip(array);
    -      if (iteratee == null) {
    -        return result;
    -      }
    -      return arrayMap(result, function (group) {
    -        return apply(iteratee, undefined, group);
    -      });
    -    }
    -
    -    /**
    -     * Creates an array excluding all given values using
    -     * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
    -     * for equality comparisons.
    -     *
    -     * **Note:** Unlike `_.pull`, this method returns a new array.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.1.0
    -     * @category Array
    -     * @param {Array} array The array to inspect.
    -     * @param {...*} [values] The values to exclude.
    -     * @returns {Array} Returns the new array of filtered values.
    -     * @see _.difference, _.xor
    -     * @example
    -     *
    -     * _.without([2, 1, 2, 3], 1, 2);
    -     * // => [3]
    -     */
    -    var without = baseRest(function (array, values) {
    -      return isArrayLikeObject(array) ? baseDifference(array, values) : [];
    -    });
    -
    -    /**
    -     * Creates an array of unique values that is the
    -     * [symmetric difference](https://en.wikipedia.org/wiki/Symmetric_difference)
    -     * of the given arrays. The order of result values is determined by the order
    -     * they occur in the arrays.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 2.4.0
    -     * @category Array
    -     * @param {...Array} [arrays] The arrays to inspect.
    -     * @returns {Array} Returns the new array of filtered values.
    -     * @see _.difference, _.without
    -     * @example
    -     *
    -     * _.xor([2, 1], [2, 3]);
    -     * // => [1, 3]
    -     */
    -    var xor = baseRest(function (arrays) {
    -      return baseXor(arrayFilter(arrays, isArrayLikeObject));
    -    });
    -
    -    /**
    -     * This method is like `_.xor` except that it accepts `iteratee` which is
    -     * invoked for each element of each `arrays` to generate the criterion by
    -     * which by which they're compared. The order of result values is determined
    -     * by the order they occur in the arrays. The iteratee is invoked with one
    -     * argument: (value).
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 4.0.0
    -     * @category Array
    -     * @param {...Array} [arrays] The arrays to inspect.
    -     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
    -     * @returns {Array} Returns the new array of filtered values.
    -     * @example
    -     *
    -     * _.xorBy([2.1, 1.2], [2.3, 3.4], Math.floor);
    -     * // => [1.2, 3.4]
    -     *
    -     * // The `_.property` iteratee shorthand.
    -     * _.xorBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');
    -     * // => [{ 'x': 2 }]
    -     */
    -    var xorBy = baseRest(function (arrays) {
    -      var iteratee = last(arrays);
    -      if (isArrayLikeObject(iteratee)) {
    -        iteratee = undefined;
    -      }
    -      return baseXor(arrayFilter(arrays, isArrayLikeObject), getIteratee(iteratee, 2));
    -    });
    -
    -    /**
    -     * This method is like `_.xor` except that it accepts `comparator` which is
    -     * invoked to compare elements of `arrays`. The order of result values is
    -     * determined by the order they occur in the arrays. The comparator is invoked
    -     * with two arguments: (arrVal, othVal).
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 4.0.0
    -     * @category Array
    -     * @param {...Array} [arrays] The arrays to inspect.
    -     * @param {Function} [comparator] The comparator invoked per element.
    -     * @returns {Array} Returns the new array of filtered values.
    -     * @example
    -     *
    -     * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
    -     * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];
    -     *
    -     * _.xorWith(objects, others, _.isEqual);
    -     * // => [{ 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }]
    -     */
    -    var xorWith = baseRest(function (arrays) {
    -      var comparator = last(arrays);
    -      comparator = typeof comparator == 'function' ? comparator : undefined;
    -      return baseXor(arrayFilter(arrays, isArrayLikeObject), undefined, comparator);
    -    });
    -
    -    /**
    -     * Creates an array of grouped elements, the first of which contains the
    -     * first elements of the given arrays, the second of which contains the
    -     * second elements of the given arrays, and so on.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.1.0
    -     * @category Array
    -     * @param {...Array} [arrays] The arrays to process.
    -     * @returns {Array} Returns the new array of grouped elements.
    -     * @example
    -     *
    -     * _.zip(['a', 'b'], [1, 2], [true, false]);
    -     * // => [['a', 1, true], ['b', 2, false]]
    -     */
    -    var zip = baseRest(unzip);
    -
    -    /**
    -     * This method is like `_.fromPairs` except that it accepts two arrays,
    -     * one of property identifiers and one of corresponding values.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.4.0
    -     * @category Array
    -     * @param {Array} [props=[]] The property identifiers.
    -     * @param {Array} [values=[]] The property values.
    -     * @returns {Object} Returns the new object.
    -     * @example
    -     *
    -     * _.zipObject(['a', 'b'], [1, 2]);
    -     * // => { 'a': 1, 'b': 2 }
    -     */
    -    function zipObject(props, values) {
    -      return baseZipObject(props || [], values || [], assignValue);
    -    }
    -
    -    /**
    -     * This method is like `_.zipObject` except that it supports property paths.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 4.1.0
    -     * @category Array
    -     * @param {Array} [props=[]] The property identifiers.
    -     * @param {Array} [values=[]] The property values.
    -     * @returns {Object} Returns the new object.
    -     * @example
    -     *
    -     * _.zipObjectDeep(['a.b[0].c', 'a.b[1].d'], [1, 2]);
    -     * // => { 'a': { 'b': [{ 'c': 1 }, { 'd': 2 }] } }
    -     */
    -    function zipObjectDeep(props, values) {
    -      return baseZipObject(props || [], values || [], baseSet);
    -    }
    -
    -    /**
    -     * This method is like `_.zip` except that it accepts `iteratee` to specify
    -     * how grouped values should be combined. The iteratee is invoked with the
    -     * elements of each group: (...group).
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 3.8.0
    -     * @category Array
    -     * @param {...Array} [arrays] The arrays to process.
    -     * @param {Function} [iteratee=_.identity] The function to combine
    -     *  grouped values.
    -     * @returns {Array} Returns the new array of grouped elements.
    -     * @example
    -     *
    -     * _.zipWith([1, 2], [10, 20], [100, 200], function(a, b, c) {
    -     *   return a + b + c;
    -     * });
    -     * // => [111, 222]
    -     */
    -    var zipWith = baseRest(function (arrays) {
    -      var length = arrays.length,
    -        iteratee = length > 1 ? arrays[length - 1] : undefined;
    -      iteratee = typeof iteratee == 'function' ? (arrays.pop(), iteratee) : undefined;
    -      return unzipWith(arrays, iteratee);
    -    });
    -
    -    /*------------------------------------------------------------------------*/
    -
    -    /**
    -     * Creates a `lodash` wrapper instance that wraps `value` with explicit method
    -     * chain sequences enabled. The result of such sequences must be unwrapped
    -     * with `_#value`.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 1.3.0
    -     * @category Seq
    -     * @param {*} value The value to wrap.
    -     * @returns {Object} Returns the new `lodash` wrapper instance.
    -     * @example
    -     *
    -     * var users = [
    -     *   { 'user': 'barney',  'age': 36 },
    -     *   { 'user': 'fred',    'age': 40 },
    -     *   { 'user': 'pebbles', 'age': 1 }
    -     * ];
    -     *
    -     * var youngest = _
    -     *   .chain(users)
    -     *   .sortBy('age')
    -     *   .map(function(o) {
    -     *     return o.user + ' is ' + o.age;
    -     *   })
    -     *   .head()
    -     *   .value();
    -     * // => 'pebbles is 1'
    -     */
    -    function chain(value) {
    -      var result = lodash(value);
    -      result.__chain__ = true;
    -      return result;
    -    }
    -
    -    /**
    -     * This method invokes `interceptor` and returns `value`. The interceptor
    -     * is invoked with one argument; (value). The purpose of this method is to
    -     * "tap into" a method chain sequence in order to modify intermediate results.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.1.0
    -     * @category Seq
    -     * @param {*} value The value to provide to `interceptor`.
    -     * @param {Function} interceptor The function to invoke.
    -     * @returns {*} Returns `value`.
    -     * @example
    -     *
    -     * _([1, 2, 3])
    -     *  .tap(function(array) {
    -     *    // Mutate input array.
    -     *    array.pop();
    -     *  })
    -     *  .reverse()
    -     *  .value();
    -     * // => [2, 1]
    -     */
    -    function tap(value, interceptor) {
    -      interceptor(value);
    -      return value;
    -    }
    -
    -    /**
    -     * This method is like `_.tap` except that it returns the result of `interceptor`.
    -     * The purpose of this method is to "pass thru" values replacing intermediate
    -     * results in a method chain sequence.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 3.0.0
    -     * @category Seq
    -     * @param {*} value The value to provide to `interceptor`.
    -     * @param {Function} interceptor The function to invoke.
    -     * @returns {*} Returns the result of `interceptor`.
    -     * @example
    -     *
    -     * _('  abc  ')
    -     *  .chain()
    -     *  .trim()
    -     *  .thru(function(value) {
    -     *    return [value];
    -     *  })
    -     *  .value();
    -     * // => ['abc']
    -     */
    -    function thru(value, interceptor) {
    -      return interceptor(value);
    -    }
    -
    -    /**
    -     * This method is the wrapper version of `_.at`.
    -     *
    -     * @name at
    -     * @memberOf _
    -     * @since 1.0.0
    -     * @category Seq
    -     * @param {...(string|string[])} [paths] The property paths to pick.
    -     * @returns {Object} Returns the new `lodash` wrapper instance.
    -     * @example
    -     *
    -     * var object = { 'a': [{ 'b': { 'c': 3 } }, 4] };
    -     *
    -     * _(object).at(['a[0].b.c', 'a[1]']).value();
    -     * // => [3, 4]
    -     */
    -    var wrapperAt = flatRest(function (paths) {
    -      var length = paths.length,
    -        start = length ? paths[0] : 0,
    -        value = this.__wrapped__,
    -        interceptor = function interceptor(object) {
    -          return baseAt(object, paths);
    -        };
    -      if (length > 1 || this.__actions__.length || !(value instanceof LazyWrapper) || !isIndex(start)) {
    -        return this.thru(interceptor);
    -      }
    -      value = value.slice(start, +start + (length ? 1 : 0));
    -      value.__actions__.push({
    -        'func': thru,
    -        'args': [interceptor],
    -        'thisArg': undefined
    -      });
    -      return new LodashWrapper(value, this.__chain__).thru(function (array) {
    -        if (length && !array.length) {
    -          array.push(undefined);
    -        }
    -        return array;
    -      });
    -    });
    -
    -    /**
    -     * Creates a `lodash` wrapper instance with explicit method chain sequences enabled.
    -     *
    -     * @name chain
    -     * @memberOf _
    -     * @since 0.1.0
    -     * @category Seq
    -     * @returns {Object} Returns the new `lodash` wrapper instance.
    -     * @example
    -     *
    -     * var users = [
    -     *   { 'user': 'barney', 'age': 36 },
    -     *   { 'user': 'fred',   'age': 40 }
    -     * ];
    -     *
    -     * // A sequence without explicit chaining.
    -     * _(users).head();
    -     * // => { 'user': 'barney', 'age': 36 }
    -     *
    -     * // A sequence with explicit chaining.
    -     * _(users)
    -     *   .chain()
    -     *   .head()
    -     *   .pick('user')
    -     *   .value();
    -     * // => { 'user': 'barney' }
    -     */
    -    function wrapperChain() {
    -      return chain(this);
    -    }
    -
    -    /**
    -     * Executes the chain sequence and returns the wrapped result.
    -     *
    -     * @name commit
    -     * @memberOf _
    -     * @since 3.2.0
    -     * @category Seq
    -     * @returns {Object} Returns the new `lodash` wrapper instance.
    -     * @example
    -     *
    -     * var array = [1, 2];
    -     * var wrapped = _(array).push(3);
    -     *
    -     * console.log(array);
    -     * // => [1, 2]
    -     *
    -     * wrapped = wrapped.commit();
    -     * console.log(array);
    -     * // => [1, 2, 3]
    -     *
    -     * wrapped.last();
    -     * // => 3
    -     *
    -     * console.log(array);
    -     * // => [1, 2, 3]
    -     */
    -    function wrapperCommit() {
    -      return new LodashWrapper(this.value(), this.__chain__);
    -    }
    -
    -    /**
    -     * Gets the next value on a wrapped object following the
    -     * [iterator protocol](https://mdn.io/iteration_protocols#iterator).
    -     *
    -     * @name next
    -     * @memberOf _
    -     * @since 4.0.0
    -     * @category Seq
    -     * @returns {Object} Returns the next iterator value.
    -     * @example
    -     *
    -     * var wrapped = _([1, 2]);
    -     *
    -     * wrapped.next();
    -     * // => { 'done': false, 'value': 1 }
    -     *
    -     * wrapped.next();
    -     * // => { 'done': false, 'value': 2 }
    -     *
    -     * wrapped.next();
    -     * // => { 'done': true, 'value': undefined }
    -     */
    -    function wrapperNext() {
    -      if (this.__values__ === undefined) {
    -        this.__values__ = toArray(this.value());
    -      }
    -      var done = this.__index__ >= this.__values__.length,
    -        value = done ? undefined : this.__values__[this.__index__++];
    -      return {
    -        'done': done,
    -        'value': value
    -      };
    -    }
    -
    -    /**
    -     * Enables the wrapper to be iterable.
    -     *
    -     * @name Symbol.iterator
    -     * @memberOf _
    -     * @since 4.0.0
    -     * @category Seq
    -     * @returns {Object} Returns the wrapper object.
    -     * @example
    -     *
    -     * var wrapped = _([1, 2]);
    -     *
    -     * wrapped[Symbol.iterator]() === wrapped;
    -     * // => true
    -     *
    -     * Array.from(wrapped);
    -     * // => [1, 2]
    -     */
    -    function wrapperToIterator() {
    -      return this;
    -    }
    -
    -    /**
    -     * Creates a clone of the chain sequence planting `value` as the wrapped value.
    -     *
    -     * @name plant
    -     * @memberOf _
    -     * @since 3.2.0
    -     * @category Seq
    -     * @param {*} value The value to plant.
    -     * @returns {Object} Returns the new `lodash` wrapper instance.
    -     * @example
    -     *
    -     * function square(n) {
    -     *   return n * n;
    -     * }
    -     *
    -     * var wrapped = _([1, 2]).map(square);
    -     * var other = wrapped.plant([3, 4]);
    -     *
    -     * other.value();
    -     * // => [9, 16]
    -     *
    -     * wrapped.value();
    -     * // => [1, 4]
    -     */
    -    function wrapperPlant(value) {
    -      var result,
    -        parent = this;
    -      while (parent instanceof baseLodash) {
    -        var clone = wrapperClone(parent);
    -        clone.__index__ = 0;
    -        clone.__values__ = undefined;
    -        if (result) {
    -          previous.__wrapped__ = clone;
    -        } else {
    -          result = clone;
    -        }
    -        var previous = clone;
    -        parent = parent.__wrapped__;
    -      }
    -      previous.__wrapped__ = value;
    -      return result;
    -    }
    -
    -    /**
    -     * This method is the wrapper version of `_.reverse`.
    -     *
    -     * **Note:** This method mutates the wrapped array.
    -     *
    -     * @name reverse
    -     * @memberOf _
    -     * @since 0.1.0
    -     * @category Seq
    -     * @returns {Object} Returns the new `lodash` wrapper instance.
    -     * @example
    -     *
    -     * var array = [1, 2, 3];
    -     *
    -     * _(array).reverse().value()
    -     * // => [3, 2, 1]
    -     *
    -     * console.log(array);
    -     * // => [3, 2, 1]
    -     */
    -    function wrapperReverse() {
    -      var value = this.__wrapped__;
    -      if (value instanceof LazyWrapper) {
    -        var wrapped = value;
    -        if (this.__actions__.length) {
    -          wrapped = new LazyWrapper(this);
    -        }
    -        wrapped = wrapped.reverse();
    -        wrapped.__actions__.push({
    -          'func': thru,
    -          'args': [reverse],
    -          'thisArg': undefined
    -        });
    -        return new LodashWrapper(wrapped, this.__chain__);
    -      }
    -      return this.thru(reverse);
    -    }
    -
    -    /**
    -     * Executes the chain sequence to resolve the unwrapped value.
    -     *
    -     * @name value
    -     * @memberOf _
    -     * @since 0.1.0
    -     * @alias toJSON, valueOf
    -     * @category Seq
    -     * @returns {*} Returns the resolved unwrapped value.
    -     * @example
    -     *
    -     * _([1, 2, 3]).value();
    -     * // => [1, 2, 3]
    -     */
    -    function wrapperValue() {
    -      return baseWrapperValue(this.__wrapped__, this.__actions__);
    -    }
    -
    -    /*------------------------------------------------------------------------*/
    -
    -    /**
    -     * Creates an object composed of keys generated from the results of running
    -     * each element of `collection` thru `iteratee`. The corresponding value of
    -     * each key is the number of times the key was returned by `iteratee`. The
    -     * iteratee is invoked with one argument: (value).
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.5.0
    -     * @category Collection
    -     * @param {Array|Object} collection The collection to iterate over.
    -     * @param {Function} [iteratee=_.identity] The iteratee to transform keys.
    -     * @returns {Object} Returns the composed aggregate object.
    -     * @example
    -     *
    -     * _.countBy([6.1, 4.2, 6.3], Math.floor);
    -     * // => { '4': 1, '6': 2 }
    -     *
    -     * // The `_.property` iteratee shorthand.
    -     * _.countBy(['one', 'two', 'three'], 'length');
    -     * // => { '3': 2, '5': 1 }
    -     */
    -    var countBy = createAggregator(function (result, value, key) {
    -      if (hasOwnProperty.call(result, key)) {
    -        ++result[key];
    -      } else {
    -        baseAssignValue(result, key, 1);
    -      }
    -    });
    -
    -    /**
    -     * Checks if `predicate` returns truthy for **all** elements of `collection`.
    -     * Iteration is stopped once `predicate` returns falsey. The predicate is
    -     * invoked with three arguments: (value, index|key, collection).
    -     *
    -     * **Note:** This method returns `true` for
    -     * [empty collections](https://en.wikipedia.org/wiki/Empty_set) because
    -     * [everything is true](https://en.wikipedia.org/wiki/Vacuous_truth) of
    -     * elements of empty collections.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.1.0
    -     * @category Collection
    -     * @param {Array|Object} collection The collection to iterate over.
    -     * @param {Function} [predicate=_.identity] The function invoked per iteration.
    -     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
    -     * @returns {boolean} Returns `true` if all elements pass the predicate check,
    -     *  else `false`.
    -     * @example
    -     *
    -     * _.every([true, 1, null, 'yes'], Boolean);
    -     * // => false
    -     *
    -     * var users = [
    -     *   { 'user': 'barney', 'age': 36, 'active': false },
    -     *   { 'user': 'fred',   'age': 40, 'active': false }
    -     * ];
    -     *
    -     * // The `_.matches` iteratee shorthand.
    -     * _.every(users, { 'user': 'barney', 'active': false });
    -     * // => false
    -     *
    -     * // The `_.matchesProperty` iteratee shorthand.
    -     * _.every(users, ['active', false]);
    -     * // => true
    -     *
    -     * // The `_.property` iteratee shorthand.
    -     * _.every(users, 'active');
    -     * // => false
    -     */
    -    function every(collection, predicate, guard) {
    -      var func = isArray(collection) ? arrayEvery : baseEvery;
    -      if (guard && isIterateeCall(collection, predicate, guard)) {
    -        predicate = undefined;
    -      }
    -      return func(collection, getIteratee(predicate, 3));
    -    }
    -
    -    /**
    -     * Iterates over elements of `collection`, returning an array of all elements
    -     * `predicate` returns truthy for. The predicate is invoked with three
    -     * arguments: (value, index|key, collection).
    -     *
    -     * **Note:** Unlike `_.remove`, this method returns a new array.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.1.0
    -     * @category Collection
    -     * @param {Array|Object} collection The collection to iterate over.
    -     * @param {Function} [predicate=_.identity] The function invoked per iteration.
    -     * @returns {Array} Returns the new filtered array.
    -     * @see _.reject
    -     * @example
    -     *
    -     * var users = [
    -     *   { 'user': 'barney', 'age': 36, 'active': true },
    -     *   { 'user': 'fred',   'age': 40, 'active': false }
    -     * ];
    -     *
    -     * _.filter(users, function(o) { return !o.active; });
    -     * // => objects for ['fred']
    -     *
    -     * // The `_.matches` iteratee shorthand.
    -     * _.filter(users, { 'age': 36, 'active': true });
    -     * // => objects for ['barney']
    -     *
    -     * // The `_.matchesProperty` iteratee shorthand.
    -     * _.filter(users, ['active', false]);
    -     * // => objects for ['fred']
    -     *
    -     * // The `_.property` iteratee shorthand.
    -     * _.filter(users, 'active');
    -     * // => objects for ['barney']
    -     *
    -     * // Combining several predicates using `_.overEvery` or `_.overSome`.
    -     * _.filter(users, _.overSome([{ 'age': 36 }, ['age', 40]]));
    -     * // => objects for ['fred', 'barney']
    -     */
    -    function filter(collection, predicate) {
    -      var func = isArray(collection) ? arrayFilter : baseFilter;
    -      return func(collection, getIteratee(predicate, 3));
    -    }
    -
    -    /**
    -     * Iterates over elements of `collection`, returning the first element
    -     * `predicate` returns truthy for. The predicate is invoked with three
    -     * arguments: (value, index|key, collection).
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.1.0
    -     * @category Collection
    -     * @param {Array|Object} collection The collection to inspect.
    -     * @param {Function} [predicate=_.identity] The function invoked per iteration.
    -     * @param {number} [fromIndex=0] The index to search from.
    -     * @returns {*} Returns the matched element, else `undefined`.
    -     * @example
    -     *
    -     * var users = [
    -     *   { 'user': 'barney',  'age': 36, 'active': true },
    -     *   { 'user': 'fred',    'age': 40, 'active': false },
    -     *   { 'user': 'pebbles', 'age': 1,  'active': true }
    -     * ];
    -     *
    -     * _.find(users, function(o) { return o.age < 40; });
    -     * // => object for 'barney'
    -     *
    -     * // The `_.matches` iteratee shorthand.
    -     * _.find(users, { 'age': 1, 'active': true });
    -     * // => object for 'pebbles'
    -     *
    -     * // The `_.matchesProperty` iteratee shorthand.
    -     * _.find(users, ['active', false]);
    -     * // => object for 'fred'
    -     *
    -     * // The `_.property` iteratee shorthand.
    -     * _.find(users, 'active');
    -     * // => object for 'barney'
    -     */
    -    var find = createFind(findIndex);
    -
    -    /**
    -     * This method is like `_.find` except that it iterates over elements of
    -     * `collection` from right to left.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 2.0.0
    -     * @category Collection
    -     * @param {Array|Object} collection The collection to inspect.
    -     * @param {Function} [predicate=_.identity] The function invoked per iteration.
    -     * @param {number} [fromIndex=collection.length-1] The index to search from.
    -     * @returns {*} Returns the matched element, else `undefined`.
    -     * @example
    -     *
    -     * _.findLast([1, 2, 3, 4], function(n) {
    -     *   return n % 2 == 1;
    -     * });
    -     * // => 3
    -     */
    -    var findLast = createFind(findLastIndex);
    -
    -    /**
    -     * Creates a flattened array of values by running each element in `collection`
    -     * thru `iteratee` and flattening the mapped results. The iteratee is invoked
    -     * with three arguments: (value, index|key, collection).
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 4.0.0
    -     * @category Collection
    -     * @param {Array|Object} collection The collection to iterate over.
    -     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
    -     * @returns {Array} Returns the new flattened array.
    -     * @example
    -     *
    -     * function duplicate(n) {
    -     *   return [n, n];
    -     * }
    -     *
    -     * _.flatMap([1, 2], duplicate);
    -     * // => [1, 1, 2, 2]
    -     */
    -    function flatMap(collection, iteratee) {
    -      return baseFlatten(map(collection, iteratee), 1);
    -    }
    -
    -    /**
    -     * This method is like `_.flatMap` except that it recursively flattens the
    -     * mapped results.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 4.7.0
    -     * @category Collection
    -     * @param {Array|Object} collection The collection to iterate over.
    -     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
    -     * @returns {Array} Returns the new flattened array.
    -     * @example
    -     *
    -     * function duplicate(n) {
    -     *   return [[[n, n]]];
    -     * }
    -     *
    -     * _.flatMapDeep([1, 2], duplicate);
    -     * // => [1, 1, 2, 2]
    -     */
    -    function flatMapDeep(collection, iteratee) {
    -      return baseFlatten(map(collection, iteratee), INFINITY);
    -    }
    -
    -    /**
    -     * This method is like `_.flatMap` except that it recursively flattens the
    -     * mapped results up to `depth` times.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 4.7.0
    -     * @category Collection
    -     * @param {Array|Object} collection The collection to iterate over.
    -     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
    -     * @param {number} [depth=1] The maximum recursion depth.
    -     * @returns {Array} Returns the new flattened array.
    -     * @example
    -     *
    -     * function duplicate(n) {
    -     *   return [[[n, n]]];
    -     * }
    -     *
    -     * _.flatMapDepth([1, 2], duplicate, 2);
    -     * // => [[1, 1], [2, 2]]
    -     */
    -    function flatMapDepth(collection, iteratee, depth) {
    -      depth = depth === undefined ? 1 : toInteger(depth);
    -      return baseFlatten(map(collection, iteratee), depth);
    -    }
    -
    -    /**
    -     * Iterates over elements of `collection` and invokes `iteratee` for each element.
    -     * The iteratee is invoked with three arguments: (value, index|key, collection).
    -     * Iteratee functions may exit iteration early by explicitly returning `false`.
    -     *
    -     * **Note:** As with other "Collections" methods, objects with a "length"
    -     * property are iterated like arrays. To avoid this behavior use `_.forIn`
    -     * or `_.forOwn` for object iteration.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.1.0
    -     * @alias each
    -     * @category Collection
    -     * @param {Array|Object} collection The collection to iterate over.
    -     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
    -     * @returns {Array|Object} Returns `collection`.
    -     * @see _.forEachRight
    -     * @example
    -     *
    -     * _.forEach([1, 2], function(value) {
    -     *   console.log(value);
    -     * });
    -     * // => Logs `1` then `2`.
    -     *
    -     * _.forEach({ 'a': 1, 'b': 2 }, function(value, key) {
    -     *   console.log(key);
    -     * });
    -     * // => Logs 'a' then 'b' (iteration order is not guaranteed).
    -     */
    -    function forEach(collection, iteratee) {
    -      var func = isArray(collection) ? arrayEach : baseEach;
    -      return func(collection, getIteratee(iteratee, 3));
    -    }
    -
    -    /**
    -     * This method is like `_.forEach` except that it iterates over elements of
    -     * `collection` from right to left.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 2.0.0
    -     * @alias eachRight
    -     * @category Collection
    -     * @param {Array|Object} collection The collection to iterate over.
    -     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
    -     * @returns {Array|Object} Returns `collection`.
    -     * @see _.forEach
    -     * @example
    -     *
    -     * _.forEachRight([1, 2], function(value) {
    -     *   console.log(value);
    -     * });
    -     * // => Logs `2` then `1`.
    -     */
    -    function forEachRight(collection, iteratee) {
    -      var func = isArray(collection) ? arrayEachRight : baseEachRight;
    -      return func(collection, getIteratee(iteratee, 3));
    -    }
    -
    -    /**
    -     * Creates an object composed of keys generated from the results of running
    -     * each element of `collection` thru `iteratee`. The order of grouped values
    -     * is determined by the order they occur in `collection`. The corresponding
    -     * value of each key is an array of elements responsible for generating the
    -     * key. The iteratee is invoked with one argument: (value).
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.1.0
    -     * @category Collection
    -     * @param {Array|Object} collection The collection to iterate over.
    -     * @param {Function} [iteratee=_.identity] The iteratee to transform keys.
    -     * @returns {Object} Returns the composed aggregate object.
    -     * @example
    -     *
    -     * _.groupBy([6.1, 4.2, 6.3], Math.floor);
    -     * // => { '4': [4.2], '6': [6.1, 6.3] }
    -     *
    -     * // The `_.property` iteratee shorthand.
    -     * _.groupBy(['one', 'two', 'three'], 'length');
    -     * // => { '3': ['one', 'two'], '5': ['three'] }
    -     */
    -    var groupBy = createAggregator(function (result, value, key) {
    -      if (hasOwnProperty.call(result, key)) {
    -        result[key].push(value);
    -      } else {
    -        baseAssignValue(result, key, [value]);
    -      }
    -    });
    -
    -    /**
    -     * Checks if `value` is in `collection`. If `collection` is a string, it's
    -     * checked for a substring of `value`, otherwise
    -     * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
    -     * is used for equality comparisons. If `fromIndex` is negative, it's used as
    -     * the offset from the end of `collection`.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.1.0
    -     * @category Collection
    -     * @param {Array|Object|string} collection The collection to inspect.
    -     * @param {*} value The value to search for.
    -     * @param {number} [fromIndex=0] The index to search from.
    -     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.reduce`.
    -     * @returns {boolean} Returns `true` if `value` is found, else `false`.
    -     * @example
    -     *
    -     * _.includes([1, 2, 3], 1);
    -     * // => true
    -     *
    -     * _.includes([1, 2, 3], 1, 2);
    -     * // => false
    -     *
    -     * _.includes({ 'a': 1, 'b': 2 }, 1);
    -     * // => true
    -     *
    -     * _.includes('abcd', 'bc');
    -     * // => true
    -     */
    -    function includes(collection, value, fromIndex, guard) {
    -      collection = isArrayLike(collection) ? collection : values(collection);
    -      fromIndex = fromIndex && !guard ? toInteger(fromIndex) : 0;
    -      var length = collection.length;
    -      if (fromIndex < 0) {
    -        fromIndex = nativeMax(length + fromIndex, 0);
    -      }
    -      return isString(collection) ? fromIndex <= length && collection.indexOf(value, fromIndex) > -1 : !!length && baseIndexOf(collection, value, fromIndex) > -1;
    -    }
    -
    -    /**
    -     * Invokes the method at `path` of each element in `collection`, returning
    -     * an array of the results of each invoked method. Any additional arguments
    -     * are provided to each invoked method. If `path` is a function, it's invoked
    -     * for, and `this` bound to, each element in `collection`.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 4.0.0
    -     * @category Collection
    -     * @param {Array|Object} collection The collection to iterate over.
    -     * @param {Array|Function|string} path The path of the method to invoke or
    -     *  the function invoked per iteration.
    -     * @param {...*} [args] The arguments to invoke each method with.
    -     * @returns {Array} Returns the array of results.
    -     * @example
    -     *
    -     * _.invokeMap([[5, 1, 7], [3, 2, 1]], 'sort');
    -     * // => [[1, 5, 7], [1, 2, 3]]
    -     *
    -     * _.invokeMap([123, 456], String.prototype.split, '');
    -     * // => [['1', '2', '3'], ['4', '5', '6']]
    -     */
    -    var invokeMap = baseRest(function (collection, path, args) {
    -      var index = -1,
    -        isFunc = typeof path == 'function',
    -        result = isArrayLike(collection) ? Array(collection.length) : [];
    -      baseEach(collection, function (value) {
    -        result[++index] = isFunc ? apply(path, value, args) : baseInvoke(value, path, args);
    -      });
    -      return result;
    -    });
    -
    -    /**
    -     * Creates an object composed of keys generated from the results of running
    -     * each element of `collection` thru `iteratee`. The corresponding value of
    -     * each key is the last element responsible for generating the key. The
    -     * iteratee is invoked with one argument: (value).
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 4.0.0
    -     * @category Collection
    -     * @param {Array|Object} collection The collection to iterate over.
    -     * @param {Function} [iteratee=_.identity] The iteratee to transform keys.
    -     * @returns {Object} Returns the composed aggregate object.
    -     * @example
    -     *
    -     * var array = [
    -     *   { 'dir': 'left', 'code': 97 },
    -     *   { 'dir': 'right', 'code': 100 }
    -     * ];
    -     *
    -     * _.keyBy(array, function(o) {
    -     *   return String.fromCharCode(o.code);
    -     * });
    -     * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } }
    -     *
    -     * _.keyBy(array, 'dir');
    -     * // => { 'left': { 'dir': 'left', 'code': 97 }, 'right': { 'dir': 'right', 'code': 100 } }
    -     */
    -    var keyBy = createAggregator(function (result, value, key) {
    -      baseAssignValue(result, key, value);
    -    });
    -
    -    /**
    -     * Creates an array of values by running each element in `collection` thru
    -     * `iteratee`. The iteratee is invoked with three arguments:
    -     * (value, index|key, collection).
    -     *
    -     * Many lodash methods are guarded to work as iteratees for methods like
    -     * `_.every`, `_.filter`, `_.map`, `_.mapValues`, `_.reject`, and `_.some`.
    -     *
    -     * The guarded methods are:
    -     * `ary`, `chunk`, `curry`, `curryRight`, `drop`, `dropRight`, `every`,
    -     * `fill`, `invert`, `parseInt`, `random`, `range`, `rangeRight`, `repeat`,
    -     * `sampleSize`, `slice`, `some`, `sortBy`, `split`, `take`, `takeRight`,
    -     * `template`, `trim`, `trimEnd`, `trimStart`, and `words`
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.1.0
    -     * @category Collection
    -     * @param {Array|Object} collection The collection to iterate over.
    -     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
    -     * @returns {Array} Returns the new mapped array.
    -     * @example
    -     *
    -     * function square(n) {
    -     *   return n * n;
    -     * }
    -     *
    -     * _.map([4, 8], square);
    -     * // => [16, 64]
    -     *
    -     * _.map({ 'a': 4, 'b': 8 }, square);
    -     * // => [16, 64] (iteration order is not guaranteed)
    -     *
    -     * var users = [
    -     *   { 'user': 'barney' },
    -     *   { 'user': 'fred' }
    -     * ];
    -     *
    -     * // The `_.property` iteratee shorthand.
    -     * _.map(users, 'user');
    -     * // => ['barney', 'fred']
    -     */
    -    function map(collection, iteratee) {
    -      var func = isArray(collection) ? arrayMap : baseMap;
    -      return func(collection, getIteratee(iteratee, 3));
    -    }
    -
    -    /**
    -     * This method is like `_.sortBy` except that it allows specifying the sort
    -     * orders of the iteratees to sort by. If `orders` is unspecified, all values
    -     * are sorted in ascending order. Otherwise, specify an order of "desc" for
    -     * descending or "asc" for ascending sort order of corresponding values.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 4.0.0
    -     * @category Collection
    -     * @param {Array|Object} collection The collection to iterate over.
    -     * @param {Array[]|Function[]|Object[]|string[]} [iteratees=[_.identity]]
    -     *  The iteratees to sort by.
    -     * @param {string[]} [orders] The sort orders of `iteratees`.
    -     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.reduce`.
    -     * @returns {Array} Returns the new sorted array.
    -     * @example
    -     *
    -     * var users = [
    -     *   { 'user': 'fred',   'age': 48 },
    -     *   { 'user': 'barney', 'age': 34 },
    -     *   { 'user': 'fred',   'age': 40 },
    -     *   { 'user': 'barney', 'age': 36 }
    -     * ];
    -     *
    -     * // Sort by `user` in ascending order and by `age` in descending order.
    -     * _.orderBy(users, ['user', 'age'], ['asc', 'desc']);
    -     * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]
    -     */
    -    function orderBy(collection, iteratees, orders, guard) {
    -      if (collection == null) {
    -        return [];
    -      }
    -      if (!isArray(iteratees)) {
    -        iteratees = iteratees == null ? [] : [iteratees];
    -      }
    -      orders = guard ? undefined : orders;
    -      if (!isArray(orders)) {
    -        orders = orders == null ? [] : [orders];
    -      }
    -      return baseOrderBy(collection, iteratees, orders);
    -    }
    -
    -    /**
    -     * Creates an array of elements split into two groups, the first of which
    -     * contains elements `predicate` returns truthy for, the second of which
    -     * contains elements `predicate` returns falsey for. The predicate is
    -     * invoked with one argument: (value).
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 3.0.0
    -     * @category Collection
    -     * @param {Array|Object} collection The collection to iterate over.
    -     * @param {Function} [predicate=_.identity] The function invoked per iteration.
    -     * @returns {Array} Returns the array of grouped elements.
    -     * @example
    -     *
    -     * var users = [
    -     *   { 'user': 'barney',  'age': 36, 'active': false },
    -     *   { 'user': 'fred',    'age': 40, 'active': true },
    -     *   { 'user': 'pebbles', 'age': 1,  'active': false }
    -     * ];
    -     *
    -     * _.partition(users, function(o) { return o.active; });
    -     * // => objects for [['fred'], ['barney', 'pebbles']]
    -     *
    -     * // The `_.matches` iteratee shorthand.
    -     * _.partition(users, { 'age': 1, 'active': false });
    -     * // => objects for [['pebbles'], ['barney', 'fred']]
    -     *
    -     * // The `_.matchesProperty` iteratee shorthand.
    -     * _.partition(users, ['active', false]);
    -     * // => objects for [['barney', 'pebbles'], ['fred']]
    -     *
    -     * // The `_.property` iteratee shorthand.
    -     * _.partition(users, 'active');
    -     * // => objects for [['fred'], ['barney', 'pebbles']]
    -     */
    -    var partition = createAggregator(function (result, value, key) {
    -      result[key ? 0 : 1].push(value);
    -    }, function () {
    -      return [[], []];
    -    });
    -
    -    /**
    -     * Reduces `collection` to a value which is the accumulated result of running
    -     * each element in `collection` thru `iteratee`, where each successive
    -     * invocation is supplied the return value of the previous. If `accumulator`
    -     * is not given, the first element of `collection` is used as the initial
    -     * value. The iteratee is invoked with four arguments:
    -     * (accumulator, value, index|key, collection).
    -     *
    -     * Many lodash methods are guarded to work as iteratees for methods like
    -     * `_.reduce`, `_.reduceRight`, and `_.transform`.
    -     *
    -     * The guarded methods are:
    -     * `assign`, `defaults`, `defaultsDeep`, `includes`, `merge`, `orderBy`,
    -     * and `sortBy`
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.1.0
    -     * @category Collection
    -     * @param {Array|Object} collection The collection to iterate over.
    -     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
    -     * @param {*} [accumulator] The initial value.
    -     * @returns {*} Returns the accumulated value.
    -     * @see _.reduceRight
    -     * @example
    -     *
    -     * _.reduce([1, 2], function(sum, n) {
    -     *   return sum + n;
    -     * }, 0);
    -     * // => 3
    -     *
    -     * _.reduce({ 'a': 1, 'b': 2, 'c': 1 }, function(result, value, key) {
    -     *   (result[value] || (result[value] = [])).push(key);
    -     *   return result;
    -     * }, {});
    -     * // => { '1': ['a', 'c'], '2': ['b'] } (iteration order is not guaranteed)
    -     */
    -    function reduce(collection, iteratee, accumulator) {
    -      var func = isArray(collection) ? arrayReduce : baseReduce,
    -        initAccum = arguments.length < 3;
    -      return func(collection, getIteratee(iteratee, 4), accumulator, initAccum, baseEach);
    -    }
    -
    -    /**
    -     * This method is like `_.reduce` except that it iterates over elements of
    -     * `collection` from right to left.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.1.0
    -     * @category Collection
    -     * @param {Array|Object} collection The collection to iterate over.
    -     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
    -     * @param {*} [accumulator] The initial value.
    -     * @returns {*} Returns the accumulated value.
    -     * @see _.reduce
    -     * @example
    -     *
    -     * var array = [[0, 1], [2, 3], [4, 5]];
    -     *
    -     * _.reduceRight(array, function(flattened, other) {
    -     *   return flattened.concat(other);
    -     * }, []);
    -     * // => [4, 5, 2, 3, 0, 1]
    -     */
    -    function reduceRight(collection, iteratee, accumulator) {
    -      var func = isArray(collection) ? arrayReduceRight : baseReduce,
    -        initAccum = arguments.length < 3;
    -      return func(collection, getIteratee(iteratee, 4), accumulator, initAccum, baseEachRight);
    -    }
    -
    -    /**
    -     * The opposite of `_.filter`; this method returns the elements of `collection`
    -     * that `predicate` does **not** return truthy for.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.1.0
    -     * @category Collection
    -     * @param {Array|Object} collection The collection to iterate over.
    -     * @param {Function} [predicate=_.identity] The function invoked per iteration.
    -     * @returns {Array} Returns the new filtered array.
    -     * @see _.filter
    -     * @example
    -     *
    -     * var users = [
    -     *   { 'user': 'barney', 'age': 36, 'active': false },
    -     *   { 'user': 'fred',   'age': 40, 'active': true }
    -     * ];
    -     *
    -     * _.reject(users, function(o) { return !o.active; });
    -     * // => objects for ['fred']
    -     *
    -     * // The `_.matches` iteratee shorthand.
    -     * _.reject(users, { 'age': 40, 'active': true });
    -     * // => objects for ['barney']
    -     *
    -     * // The `_.matchesProperty` iteratee shorthand.
    -     * _.reject(users, ['active', false]);
    -     * // => objects for ['fred']
    -     *
    -     * // The `_.property` iteratee shorthand.
    -     * _.reject(users, 'active');
    -     * // => objects for ['barney']
    -     */
    -    function reject(collection, predicate) {
    -      var func = isArray(collection) ? arrayFilter : baseFilter;
    -      return func(collection, negate(getIteratee(predicate, 3)));
    -    }
    -
    -    /**
    -     * Gets a random element from `collection`.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 2.0.0
    -     * @category Collection
    -     * @param {Array|Object} collection The collection to sample.
    -     * @returns {*} Returns the random element.
    -     * @example
    -     *
    -     * _.sample([1, 2, 3, 4]);
    -     * // => 2
    -     */
    -    function sample(collection) {
    -      var func = isArray(collection) ? arraySample : baseSample;
    -      return func(collection);
    -    }
    -
    -    /**
    -     * Gets `n` random elements at unique keys from `collection` up to the
    -     * size of `collection`.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 4.0.0
    -     * @category Collection
    -     * @param {Array|Object} collection The collection to sample.
    -     * @param {number} [n=1] The number of elements to sample.
    -     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
    -     * @returns {Array} Returns the random elements.
    -     * @example
    -     *
    -     * _.sampleSize([1, 2, 3], 2);
    -     * // => [3, 1]
    -     *
    -     * _.sampleSize([1, 2, 3], 4);
    -     * // => [2, 3, 1]
    -     */
    -    function sampleSize(collection, n, guard) {
    -      if (guard ? isIterateeCall(collection, n, guard) : n === undefined) {
    -        n = 1;
    -      } else {
    -        n = toInteger(n);
    -      }
    -      var func = isArray(collection) ? arraySampleSize : baseSampleSize;
    -      return func(collection, n);
    -    }
    -
    -    /**
    -     * Creates an array of shuffled values, using a version of the
    -     * [Fisher-Yates shuffle](https://en.wikipedia.org/wiki/Fisher-Yates_shuffle).
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.1.0
    -     * @category Collection
    -     * @param {Array|Object} collection The collection to shuffle.
    -     * @returns {Array} Returns the new shuffled array.
    -     * @example
    -     *
    -     * _.shuffle([1, 2, 3, 4]);
    -     * // => [4, 1, 3, 2]
    -     */
    -    function shuffle(collection) {
    -      var func = isArray(collection) ? arrayShuffle : baseShuffle;
    -      return func(collection);
    -    }
    -
    -    /**
    -     * Gets the size of `collection` by returning its length for array-like
    -     * values or the number of own enumerable string keyed properties for objects.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.1.0
    -     * @category Collection
    -     * @param {Array|Object|string} collection The collection to inspect.
    -     * @returns {number} Returns the collection size.
    -     * @example
    -     *
    -     * _.size([1, 2, 3]);
    -     * // => 3
    -     *
    -     * _.size({ 'a': 1, 'b': 2 });
    -     * // => 2
    -     *
    -     * _.size('pebbles');
    -     * // => 7
    -     */
    -    function size(collection) {
    -      if (collection == null) {
    -        return 0;
    -      }
    -      if (isArrayLike(collection)) {
    -        return isString(collection) ? stringSize(collection) : collection.length;
    -      }
    -      var tag = getTag(collection);
    -      if (tag == mapTag || tag == setTag) {
    -        return collection.size;
    -      }
    -      return baseKeys(collection).length;
    -    }
    -
    -    /**
    -     * Checks if `predicate` returns truthy for **any** element of `collection`.
    -     * Iteration is stopped once `predicate` returns truthy. The predicate is
    -     * invoked with three arguments: (value, index|key, collection).
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.1.0
    -     * @category Collection
    -     * @param {Array|Object} collection The collection to iterate over.
    -     * @param {Function} [predicate=_.identity] The function invoked per iteration.
    -     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
    -     * @returns {boolean} Returns `true` if any element passes the predicate check,
    -     *  else `false`.
    -     * @example
    -     *
    -     * _.some([null, 0, 'yes', false], Boolean);
    -     * // => true
    -     *
    -     * var users = [
    -     *   { 'user': 'barney', 'active': true },
    -     *   { 'user': 'fred',   'active': false }
    -     * ];
    -     *
    -     * // The `_.matches` iteratee shorthand.
    -     * _.some(users, { 'user': 'barney', 'active': false });
    -     * // => false
    -     *
    -     * // The `_.matchesProperty` iteratee shorthand.
    -     * _.some(users, ['active', false]);
    -     * // => true
    -     *
    -     * // The `_.property` iteratee shorthand.
    -     * _.some(users, 'active');
    -     * // => true
    -     */
    -    function some(collection, predicate, guard) {
    -      var func = isArray(collection) ? arraySome : baseSome;
    -      if (guard && isIterateeCall(collection, predicate, guard)) {
    -        predicate = undefined;
    -      }
    -      return func(collection, getIteratee(predicate, 3));
    -    }
    -
    -    /**
    -     * Creates an array of elements, sorted in ascending order by the results of
    -     * running each element in a collection thru each iteratee. This method
    -     * performs a stable sort, that is, it preserves the original sort order of
    -     * equal elements. The iteratees are invoked with one argument: (value).
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.1.0
    -     * @category Collection
    -     * @param {Array|Object} collection The collection to iterate over.
    -     * @param {...(Function|Function[])} [iteratees=[_.identity]]
    -     *  The iteratees to sort by.
    -     * @returns {Array} Returns the new sorted array.
    -     * @example
    -     *
    -     * var users = [
    -     *   { 'user': 'fred',   'age': 48 },
    -     *   { 'user': 'barney', 'age': 36 },
    -     *   { 'user': 'fred',   'age': 30 },
    -     *   { 'user': 'barney', 'age': 34 }
    -     * ];
    -     *
    -     * _.sortBy(users, [function(o) { return o.user; }]);
    -     * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 30]]
    -     *
    -     * _.sortBy(users, ['user', 'age']);
    -     * // => objects for [['barney', 34], ['barney', 36], ['fred', 30], ['fred', 48]]
    -     */
    -    var sortBy = baseRest(function (collection, iteratees) {
    -      if (collection == null) {
    -        return [];
    -      }
    -      var length = iteratees.length;
    -      if (length > 1 && isIterateeCall(collection, iteratees[0], iteratees[1])) {
    -        iteratees = [];
    -      } else if (length > 2 && isIterateeCall(iteratees[0], iteratees[1], iteratees[2])) {
    -        iteratees = [iteratees[0]];
    -      }
    -      return baseOrderBy(collection, baseFlatten(iteratees, 1), []);
    -    });
    -
    -    /*------------------------------------------------------------------------*/
    -
    -    /**
    -     * Gets the timestamp of the number of milliseconds that have elapsed since
    -     * the Unix epoch (1 January 1970 00:00:00 UTC).
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 2.4.0
    -     * @category Date
    -     * @returns {number} Returns the timestamp.
    -     * @example
    -     *
    -     * _.defer(function(stamp) {
    -     *   console.log(_.now() - stamp);
    -     * }, _.now());
    -     * // => Logs the number of milliseconds it took for the deferred invocation.
    -     */
    -    var now = ctxNow || function () {
    -      return root.Date.now();
    -    };
    -
    -    /*------------------------------------------------------------------------*/
    -
    -    /**
    -     * The opposite of `_.before`; this method creates a function that invokes
    -     * `func` once it's called `n` or more times.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.1.0
    -     * @category Function
    -     * @param {number} n The number of calls before `func` is invoked.
    -     * @param {Function} func The function to restrict.
    -     * @returns {Function} Returns the new restricted function.
    -     * @example
    -     *
    -     * var saves = ['profile', 'settings'];
    -     *
    -     * var done = _.after(saves.length, function() {
    -     *   console.log('done saving!');
    -     * });
    -     *
    -     * _.forEach(saves, function(type) {
    -     *   asyncSave({ 'type': type, 'complete': done });
    -     * });
    -     * // => Logs 'done saving!' after the two async saves have completed.
    -     */
    -    function after(n, func) {
    -      if (typeof func != 'function') {
    -        throw new TypeError(FUNC_ERROR_TEXT);
    -      }
    -      n = toInteger(n);
    -      return function () {
    -        if (--n < 1) {
    -          return func.apply(this, arguments);
    -        }
    -      };
    -    }
    -
    -    /**
    -     * Creates a function that invokes `func`, with up to `n` arguments,
    -     * ignoring any additional arguments.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 3.0.0
    -     * @category Function
    -     * @param {Function} func The function to cap arguments for.
    -     * @param {number} [n=func.length] The arity cap.
    -     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
    -     * @returns {Function} Returns the new capped function.
    -     * @example
    -     *
    -     * _.map(['6', '8', '10'], _.ary(parseInt, 1));
    -     * // => [6, 8, 10]
    -     */
    -    function ary(func, n, guard) {
    -      n = guard ? undefined : n;
    -      n = func && n == null ? func.length : n;
    -      return createWrap(func, WRAP_ARY_FLAG, undefined, undefined, undefined, undefined, n);
    -    }
    -
    -    /**
    -     * Creates a function that invokes `func`, with the `this` binding and arguments
    -     * of the created function, while it's called less than `n` times. Subsequent
    -     * calls to the created function return the result of the last `func` invocation.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 3.0.0
    -     * @category Function
    -     * @param {number} n The number of calls at which `func` is no longer invoked.
    -     * @param {Function} func The function to restrict.
    -     * @returns {Function} Returns the new restricted function.
    -     * @example
    -     *
    -     * jQuery(element).on('click', _.before(5, addContactToList));
    -     * // => Allows adding up to 4 contacts to the list.
    -     */
    -    function before(n, func) {
    -      var result;
    -      if (typeof func != 'function') {
    -        throw new TypeError(FUNC_ERROR_TEXT);
    -      }
    -      n = toInteger(n);
    -      return function () {
    -        if (--n > 0) {
    -          result = func.apply(this, arguments);
    -        }
    -        if (n <= 1) {
    -          func = undefined;
    -        }
    -        return result;
    -      };
    -    }
    -
    -    /**
    -     * Creates a function that invokes `func` with the `this` binding of `thisArg`
    -     * and `partials` prepended to the arguments it receives.
    -     *
    -     * The `_.bind.placeholder` value, which defaults to `_` in monolithic builds,
    -     * may be used as a placeholder for partially applied arguments.
    -     *
    -     * **Note:** Unlike native `Function#bind`, this method doesn't set the "length"
    -     * property of bound functions.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.1.0
    -     * @category Function
    -     * @param {Function} func The function to bind.
    -     * @param {*} thisArg The `this` binding of `func`.
    -     * @param {...*} [partials] The arguments to be partially applied.
    -     * @returns {Function} Returns the new bound function.
    -     * @example
    -     *
    -     * function greet(greeting, punctuation) {
    -     *   return greeting + ' ' + this.user + punctuation;
    -     * }
    -     *
    -     * var object = { 'user': 'fred' };
    -     *
    -     * var bound = _.bind(greet, object, 'hi');
    -     * bound('!');
    -     * // => 'hi fred!'
    -     *
    -     * // Bound with placeholders.
    -     * var bound = _.bind(greet, object, _, '!');
    -     * bound('hi');
    -     * // => 'hi fred!'
    -     */
    -    var bind = baseRest(function (func, thisArg, partials) {
    -      var bitmask = WRAP_BIND_FLAG;
    -      if (partials.length) {
    -        var holders = replaceHolders(partials, getHolder(bind));
    -        bitmask |= WRAP_PARTIAL_FLAG;
    -      }
    -      return createWrap(func, bitmask, thisArg, partials, holders);
    -    });
    -
    -    /**
    -     * Creates a function that invokes the method at `object[key]` with `partials`
    -     * prepended to the arguments it receives.
    -     *
    -     * This method differs from `_.bind` by allowing bound functions to reference
    -     * methods that may be redefined or don't yet exist. See
    -     * [Peter Michaux's article](http://peter.michaux.ca/articles/lazy-function-definition-pattern)
    -     * for more details.
    -     *
    -     * The `_.bindKey.placeholder` value, which defaults to `_` in monolithic
    -     * builds, may be used as a placeholder for partially applied arguments.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.10.0
    -     * @category Function
    -     * @param {Object} object The object to invoke the method on.
    -     * @param {string} key The key of the method.
    -     * @param {...*} [partials] The arguments to be partially applied.
    -     * @returns {Function} Returns the new bound function.
    -     * @example
    -     *
    -     * var object = {
    -     *   'user': 'fred',
    -     *   'greet': function(greeting, punctuation) {
    -     *     return greeting + ' ' + this.user + punctuation;
    -     *   }
    -     * };
    -     *
    -     * var bound = _.bindKey(object, 'greet', 'hi');
    -     * bound('!');
    -     * // => 'hi fred!'
    -     *
    -     * object.greet = function(greeting, punctuation) {
    -     *   return greeting + 'ya ' + this.user + punctuation;
    -     * };
    -     *
    -     * bound('!');
    -     * // => 'hiya fred!'
    -     *
    -     * // Bound with placeholders.
    -     * var bound = _.bindKey(object, 'greet', _, '!');
    -     * bound('hi');
    -     * // => 'hiya fred!'
    -     */
    -    var bindKey = baseRest(function (object, key, partials) {
    -      var bitmask = WRAP_BIND_FLAG | WRAP_BIND_KEY_FLAG;
    -      if (partials.length) {
    -        var holders = replaceHolders(partials, getHolder(bindKey));
    -        bitmask |= WRAP_PARTIAL_FLAG;
    -      }
    -      return createWrap(key, bitmask, object, partials, holders);
    -    });
    -
    -    /**
    -     * Creates a function that accepts arguments of `func` and either invokes
    -     * `func` returning its result, if at least `arity` number of arguments have
    -     * been provided, or returns a function that accepts the remaining `func`
    -     * arguments, and so on. The arity of `func` may be specified if `func.length`
    -     * is not sufficient.
    -     *
    -     * The `_.curry.placeholder` value, which defaults to `_` in monolithic builds,
    -     * may be used as a placeholder for provided arguments.
    -     *
    -     * **Note:** This method doesn't set the "length" property of curried functions.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 2.0.0
    -     * @category Function
    -     * @param {Function} func The function to curry.
    -     * @param {number} [arity=func.length] The arity of `func`.
    -     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
    -     * @returns {Function} Returns the new curried function.
    -     * @example
    -     *
    -     * var abc = function(a, b, c) {
    -     *   return [a, b, c];
    -     * };
    -     *
    -     * var curried = _.curry(abc);
    -     *
    -     * curried(1)(2)(3);
    -     * // => [1, 2, 3]
    -     *
    -     * curried(1, 2)(3);
    -     * // => [1, 2, 3]
    -     *
    -     * curried(1, 2, 3);
    -     * // => [1, 2, 3]
    -     *
    -     * // Curried with placeholders.
    -     * curried(1)(_, 3)(2);
    -     * // => [1, 2, 3]
    -     */
    -    function curry(func, arity, guard) {
    -      arity = guard ? undefined : arity;
    -      var result = createWrap(func, WRAP_CURRY_FLAG, undefined, undefined, undefined, undefined, undefined, arity);
    -      result.placeholder = curry.placeholder;
    -      return result;
    -    }
    -
    -    /**
    -     * This method is like `_.curry` except that arguments are applied to `func`
    -     * in the manner of `_.partialRight` instead of `_.partial`.
    -     *
    -     * The `_.curryRight.placeholder` value, which defaults to `_` in monolithic
    -     * builds, may be used as a placeholder for provided arguments.
    -     *
    -     * **Note:** This method doesn't set the "length" property of curried functions.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 3.0.0
    -     * @category Function
    -     * @param {Function} func The function to curry.
    -     * @param {number} [arity=func.length] The arity of `func`.
    -     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
    -     * @returns {Function} Returns the new curried function.
    -     * @example
    -     *
    -     * var abc = function(a, b, c) {
    -     *   return [a, b, c];
    -     * };
    -     *
    -     * var curried = _.curryRight(abc);
    -     *
    -     * curried(3)(2)(1);
    -     * // => [1, 2, 3]
    -     *
    -     * curried(2, 3)(1);
    -     * // => [1, 2, 3]
    -     *
    -     * curried(1, 2, 3);
    -     * // => [1, 2, 3]
    -     *
    -     * // Curried with placeholders.
    -     * curried(3)(1, _)(2);
    -     * // => [1, 2, 3]
    -     */
    -    function curryRight(func, arity, guard) {
    -      arity = guard ? undefined : arity;
    -      var result = createWrap(func, WRAP_CURRY_RIGHT_FLAG, undefined, undefined, undefined, undefined, undefined, arity);
    -      result.placeholder = curryRight.placeholder;
    -      return result;
    -    }
    -
    -    /**
    -     * Creates a debounced function that delays invoking `func` until after `wait`
    -     * milliseconds have elapsed since the last time the debounced function was
    -     * invoked. The debounced function comes with a `cancel` method to cancel
    -     * delayed `func` invocations and a `flush` method to immediately invoke them.
    -     * Provide `options` to indicate whether `func` should be invoked on the
    -     * leading and/or trailing edge of the `wait` timeout. The `func` is invoked
    -     * with the last arguments provided to the debounced function. Subsequent
    -     * calls to the debounced function return the result of the last `func`
    -     * invocation.
    -     *
    -     * **Note:** If `leading` and `trailing` options are `true`, `func` is
    -     * invoked on the trailing edge of the timeout only if the debounced function
    -     * is invoked more than once during the `wait` timeout.
    -     *
    -     * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
    -     * until to the next tick, similar to `setTimeout` with a timeout of `0`.
    -     *
    -     * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
    -     * for details over the differences between `_.debounce` and `_.throttle`.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.1.0
    -     * @category Function
    -     * @param {Function} func The function to debounce.
    -     * @param {number} [wait=0] The number of milliseconds to delay.
    -     * @param {Object} [options={}] The options object.
    -     * @param {boolean} [options.leading=false]
    -     *  Specify invoking on the leading edge of the timeout.
    -     * @param {number} [options.maxWait]
    -     *  The maximum time `func` is allowed to be delayed before it's invoked.
    -     * @param {boolean} [options.trailing=true]
    -     *  Specify invoking on the trailing edge of the timeout.
    -     * @returns {Function} Returns the new debounced function.
    -     * @example
    -     *
    -     * // Avoid costly calculations while the window size is in flux.
    -     * jQuery(window).on('resize', _.debounce(calculateLayout, 150));
    -     *
    -     * // Invoke `sendMail` when clicked, debouncing subsequent calls.
    -     * jQuery(element).on('click', _.debounce(sendMail, 300, {
    -     *   'leading': true,
    -     *   'trailing': false
    -     * }));
    -     *
    -     * // Ensure `batchLog` is invoked once after 1 second of debounced calls.
    -     * var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 });
    -     * var source = new EventSource('/stream');
    -     * jQuery(source).on('message', debounced);
    -     *
    -     * // Cancel the trailing debounced invocation.
    -     * jQuery(window).on('popstate', debounced.cancel);
    -     */
    -    function debounce(func, wait, options) {
    -      var lastArgs,
    -        lastThis,
    -        maxWait,
    -        result,
    -        timerId,
    -        lastCallTime,
    -        lastInvokeTime = 0,
    -        leading = false,
    -        maxing = false,
    -        trailing = true;
    -      if (typeof func != 'function') {
    -        throw new TypeError(FUNC_ERROR_TEXT);
    -      }
    -      wait = toNumber(wait) || 0;
    -      if (isObject(options)) {
    -        leading = !!options.leading;
    -        maxing = 'maxWait' in options;
    -        maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait;
    -        trailing = 'trailing' in options ? !!options.trailing : trailing;
    -      }
    -      function invokeFunc(time) {
    -        var args = lastArgs,
    -          thisArg = lastThis;
    -        lastArgs = lastThis = undefined;
    -        lastInvokeTime = time;
    -        result = func.apply(thisArg, args);
    -        return result;
    -      }
    -      function leadingEdge(time) {
    -        // Reset any `maxWait` timer.
    -        lastInvokeTime = time;
    -        // Start the timer for the trailing edge.
    -        timerId = setTimeout(timerExpired, wait);
    -        // Invoke the leading edge.
    -        return leading ? invokeFunc(time) : result;
    -      }
    -      function remainingWait(time) {
    -        var timeSinceLastCall = time - lastCallTime,
    -          timeSinceLastInvoke = time - lastInvokeTime,
    -          timeWaiting = wait - timeSinceLastCall;
    -        return maxing ? nativeMin(timeWaiting, maxWait - timeSinceLastInvoke) : timeWaiting;
    -      }
    -      function shouldInvoke(time) {
    -        var timeSinceLastCall = time - lastCallTime,
    -          timeSinceLastInvoke = time - lastInvokeTime;
    -
    -        // Either this is the first call, activity has stopped and we're at the
    -        // trailing edge, the system time has gone backwards and we're treating
    -        // it as the trailing edge, or we've hit the `maxWait` limit.
    -        return lastCallTime === undefined || timeSinceLastCall >= wait || timeSinceLastCall < 0 || maxing && timeSinceLastInvoke >= maxWait;
    -      }
    -      function timerExpired() {
    -        var time = now();
    -        if (shouldInvoke(time)) {
    -          return trailingEdge(time);
    -        }
    -        // Restart the timer.
    -        timerId = setTimeout(timerExpired, remainingWait(time));
    -      }
    -      function trailingEdge(time) {
    -        timerId = undefined;
    -
    -        // Only invoke if we have `lastArgs` which means `func` has been
    -        // debounced at least once.
    -        if (trailing && lastArgs) {
    -          return invokeFunc(time);
    -        }
    -        lastArgs = lastThis = undefined;
    -        return result;
    -      }
    -      function cancel() {
    -        if (timerId !== undefined) {
    -          clearTimeout(timerId);
    -        }
    -        lastInvokeTime = 0;
    -        lastArgs = lastCallTime = lastThis = timerId = undefined;
    -      }
    -      function flush() {
    -        return timerId === undefined ? result : trailingEdge(now());
    -      }
    -      function debounced() {
    -        var time = now(),
    -          isInvoking = shouldInvoke(time);
    -        lastArgs = arguments;
    -        lastThis = this;
    -        lastCallTime = time;
    -        if (isInvoking) {
    -          if (timerId === undefined) {
    -            return leadingEdge(lastCallTime);
    -          }
    -          if (maxing) {
    -            // Handle invocations in a tight loop.
    -            clearTimeout(timerId);
    -            timerId = setTimeout(timerExpired, wait);
    -            return invokeFunc(lastCallTime);
    -          }
    -        }
    -        if (timerId === undefined) {
    -          timerId = setTimeout(timerExpired, wait);
    -        }
    -        return result;
    -      }
    -      debounced.cancel = cancel;
    -      debounced.flush = flush;
    -      return debounced;
    -    }
    -
    -    /**
    -     * Defers invoking the `func` until the current call stack has cleared. Any
    -     * additional arguments are provided to `func` when it's invoked.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.1.0
    -     * @category Function
    -     * @param {Function} func The function to defer.
    -     * @param {...*} [args] The arguments to invoke `func` with.
    -     * @returns {number} Returns the timer id.
    -     * @example
    -     *
    -     * _.defer(function(text) {
    -     *   console.log(text);
    -     * }, 'deferred');
    -     * // => Logs 'deferred' after one millisecond.
    -     */
    -    var defer = baseRest(function (func, args) {
    -      return baseDelay(func, 1, args);
    -    });
    -
    -    /**
    -     * Invokes `func` after `wait` milliseconds. Any additional arguments are
    -     * provided to `func` when it's invoked.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.1.0
    -     * @category Function
    -     * @param {Function} func The function to delay.
    -     * @param {number} wait The number of milliseconds to delay invocation.
    -     * @param {...*} [args] The arguments to invoke `func` with.
    -     * @returns {number} Returns the timer id.
    -     * @example
    -     *
    -     * _.delay(function(text) {
    -     *   console.log(text);
    -     * }, 1000, 'later');
    -     * // => Logs 'later' after one second.
    -     */
    -    var delay = baseRest(function (func, wait, args) {
    -      return baseDelay(func, toNumber(wait) || 0, args);
    -    });
    -
    -    /**
    -     * Creates a function that invokes `func` with arguments reversed.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 4.0.0
    -     * @category Function
    -     * @param {Function} func The function to flip arguments for.
    -     * @returns {Function} Returns the new flipped function.
    -     * @example
    -     *
    -     * var flipped = _.flip(function() {
    -     *   return _.toArray(arguments);
    -     * });
    -     *
    -     * flipped('a', 'b', 'c', 'd');
    -     * // => ['d', 'c', 'b', 'a']
    -     */
    -    function flip(func) {
    -      return createWrap(func, WRAP_FLIP_FLAG);
    -    }
    -
    -    /**
    -     * Creates a function that memoizes the result of `func`. If `resolver` is
    -     * provided, it determines the cache key for storing the result based on the
    -     * arguments provided to the memoized function. By default, the first argument
    -     * provided to the memoized function is used as the map cache key. The `func`
    -     * is invoked with the `this` binding of the memoized function.
    -     *
    -     * **Note:** The cache is exposed as the `cache` property on the memoized
    -     * function. Its creation may be customized by replacing the `_.memoize.Cache`
    -     * constructor with one whose instances implement the
    -     * [`Map`](http://ecma-international.org/ecma-262/7.0/#sec-properties-of-the-map-prototype-object)
    -     * method interface of `clear`, `delete`, `get`, `has`, and `set`.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.1.0
    -     * @category Function
    -     * @param {Function} func The function to have its output memoized.
    -     * @param {Function} [resolver] The function to resolve the cache key.
    -     * @returns {Function} Returns the new memoized function.
    -     * @example
    -     *
    -     * var object = { 'a': 1, 'b': 2 };
    -     * var other = { 'c': 3, 'd': 4 };
    -     *
    -     * var values = _.memoize(_.values);
    -     * values(object);
    -     * // => [1, 2]
    -     *
    -     * values(other);
    -     * // => [3, 4]
    -     *
    -     * object.a = 2;
    -     * values(object);
    -     * // => [1, 2]
    -     *
    -     * // Modify the result cache.
    -     * values.cache.set(object, ['a', 'b']);
    -     * values(object);
    -     * // => ['a', 'b']
    -     *
    -     * // Replace `_.memoize.Cache`.
    -     * _.memoize.Cache = WeakMap;
    -     */
    -    function memoize(func, resolver) {
    -      if (typeof func != 'function' || resolver != null && typeof resolver != 'function') {
    -        throw new TypeError(FUNC_ERROR_TEXT);
    -      }
    -      var memoized = function memoized() {
    -        var args = arguments,
    -          key = resolver ? resolver.apply(this, args) : args[0],
    -          cache = memoized.cache;
    -        if (cache.has(key)) {
    -          return cache.get(key);
    -        }
    -        var result = func.apply(this, args);
    -        memoized.cache = cache.set(key, result) || cache;
    -        return result;
    -      };
    -      memoized.cache = new (memoize.Cache || MapCache)();
    -      return memoized;
    -    }
    -
    -    // Expose `MapCache`.
    -    memoize.Cache = MapCache;
    -
    -    /**
    -     * Creates a function that negates the result of the predicate `func`. The
    -     * `func` predicate is invoked with the `this` binding and arguments of the
    -     * created function.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 3.0.0
    -     * @category Function
    -     * @param {Function} predicate The predicate to negate.
    -     * @returns {Function} Returns the new negated function.
    -     * @example
    -     *
    -     * function isEven(n) {
    -     *   return n % 2 == 0;
    -     * }
    -     *
    -     * _.filter([1, 2, 3, 4, 5, 6], _.negate(isEven));
    -     * // => [1, 3, 5]
    -     */
    -    function negate(predicate) {
    -      if (typeof predicate != 'function') {
    -        throw new TypeError(FUNC_ERROR_TEXT);
    -      }
    -      return function () {
    -        var args = arguments;
    -        switch (args.length) {
    -          case 0:
    -            return !predicate.call(this);
    -          case 1:
    -            return !predicate.call(this, args[0]);
    -          case 2:
    -            return !predicate.call(this, args[0], args[1]);
    -          case 3:
    -            return !predicate.call(this, args[0], args[1], args[2]);
    -        }
    -        return !predicate.apply(this, args);
    -      };
    -    }
    -
    -    /**
    -     * Creates a function that is restricted to invoking `func` once. Repeat calls
    -     * to the function return the value of the first invocation. The `func` is
    -     * invoked with the `this` binding and arguments of the created function.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.1.0
    -     * @category Function
    -     * @param {Function} func The function to restrict.
    -     * @returns {Function} Returns the new restricted function.
    -     * @example
    -     *
    -     * var initialize = _.once(createApplication);
    -     * initialize();
    -     * initialize();
    -     * // => `createApplication` is invoked once
    -     */
    -    function once(func) {
    -      return before(2, func);
    -    }
    -
    -    /**
    -     * Creates a function that invokes `func` with its arguments transformed.
    -     *
    -     * @static
    -     * @since 4.0.0
    -     * @memberOf _
    -     * @category Function
    -     * @param {Function} func The function to wrap.
    -     * @param {...(Function|Function[])} [transforms=[_.identity]]
    -     *  The argument transforms.
    -     * @returns {Function} Returns the new function.
    -     * @example
    -     *
    -     * function doubled(n) {
    -     *   return n * 2;
    -     * }
    -     *
    -     * function square(n) {
    -     *   return n * n;
    -     * }
    -     *
    -     * var func = _.overArgs(function(x, y) {
    -     *   return [x, y];
    -     * }, [square, doubled]);
    -     *
    -     * func(9, 3);
    -     * // => [81, 6]
    -     *
    -     * func(10, 5);
    -     * // => [100, 10]
    -     */
    -    var overArgs = castRest(function (func, transforms) {
    -      transforms = transforms.length == 1 && isArray(transforms[0]) ? arrayMap(transforms[0], baseUnary(getIteratee())) : arrayMap(baseFlatten(transforms, 1), baseUnary(getIteratee()));
    -      var funcsLength = transforms.length;
    -      return baseRest(function (args) {
    -        var index = -1,
    -          length = nativeMin(args.length, funcsLength);
    -        while (++index < length) {
    -          args[index] = transforms[index].call(this, args[index]);
    -        }
    -        return apply(func, this, args);
    -      });
    -    });
    -
    -    /**
    -     * Creates a function that invokes `func` with `partials` prepended to the
    -     * arguments it receives. This method is like `_.bind` except it does **not**
    -     * alter the `this` binding.
    -     *
    -     * The `_.partial.placeholder` value, which defaults to `_` in monolithic
    -     * builds, may be used as a placeholder for partially applied arguments.
    -     *
    -     * **Note:** This method doesn't set the "length" property of partially
    -     * applied functions.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.2.0
    -     * @category Function
    -     * @param {Function} func The function to partially apply arguments to.
    -     * @param {...*} [partials] The arguments to be partially applied.
    -     * @returns {Function} Returns the new partially applied function.
    -     * @example
    -     *
    -     * function greet(greeting, name) {
    -     *   return greeting + ' ' + name;
    -     * }
    -     *
    -     * var sayHelloTo = _.partial(greet, 'hello');
    -     * sayHelloTo('fred');
    -     * // => 'hello fred'
    -     *
    -     * // Partially applied with placeholders.
    -     * var greetFred = _.partial(greet, _, 'fred');
    -     * greetFred('hi');
    -     * // => 'hi fred'
    -     */
    -    var partial = baseRest(function (func, partials) {
    -      var holders = replaceHolders(partials, getHolder(partial));
    -      return createWrap(func, WRAP_PARTIAL_FLAG, undefined, partials, holders);
    -    });
    -
    -    /**
    -     * This method is like `_.partial` except that partially applied arguments
    -     * are appended to the arguments it receives.
    -     *
    -     * The `_.partialRight.placeholder` value, which defaults to `_` in monolithic
    -     * builds, may be used as a placeholder for partially applied arguments.
    -     *
    -     * **Note:** This method doesn't set the "length" property of partially
    -     * applied functions.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 1.0.0
    -     * @category Function
    -     * @param {Function} func The function to partially apply arguments to.
    -     * @param {...*} [partials] The arguments to be partially applied.
    -     * @returns {Function} Returns the new partially applied function.
    -     * @example
    -     *
    -     * function greet(greeting, name) {
    -     *   return greeting + ' ' + name;
    -     * }
    -     *
    -     * var greetFred = _.partialRight(greet, 'fred');
    -     * greetFred('hi');
    -     * // => 'hi fred'
    -     *
    -     * // Partially applied with placeholders.
    -     * var sayHelloTo = _.partialRight(greet, 'hello', _);
    -     * sayHelloTo('fred');
    -     * // => 'hello fred'
    -     */
    -    var partialRight = baseRest(function (func, partials) {
    -      var holders = replaceHolders(partials, getHolder(partialRight));
    -      return createWrap(func, WRAP_PARTIAL_RIGHT_FLAG, undefined, partials, holders);
    -    });
    -
    -    /**
    -     * Creates a function that invokes `func` with arguments arranged according
    -     * to the specified `indexes` where the argument value at the first index is
    -     * provided as the first argument, the argument value at the second index is
    -     * provided as the second argument, and so on.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 3.0.0
    -     * @category Function
    -     * @param {Function} func The function to rearrange arguments for.
    -     * @param {...(number|number[])} indexes The arranged argument indexes.
    -     * @returns {Function} Returns the new function.
    -     * @example
    -     *
    -     * var rearged = _.rearg(function(a, b, c) {
    -     *   return [a, b, c];
    -     * }, [2, 0, 1]);
    -     *
    -     * rearged('b', 'c', 'a')
    -     * // => ['a', 'b', 'c']
    -     */
    -    var rearg = flatRest(function (func, indexes) {
    -      return createWrap(func, WRAP_REARG_FLAG, undefined, undefined, undefined, indexes);
    -    });
    -
    -    /**
    -     * Creates a function that invokes `func` with the `this` binding of the
    -     * created function and arguments from `start` and beyond provided as
    -     * an array.
    -     *
    -     * **Note:** This method is based on the
    -     * [rest parameter](https://mdn.io/rest_parameters).
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 4.0.0
    -     * @category Function
    -     * @param {Function} func The function to apply a rest parameter to.
    -     * @param {number} [start=func.length-1] The start position of the rest parameter.
    -     * @returns {Function} Returns the new function.
    -     * @example
    -     *
    -     * var say = _.rest(function(what, names) {
    -     *   return what + ' ' + _.initial(names).join(', ') +
    -     *     (_.size(names) > 1 ? ', & ' : '') + _.last(names);
    -     * });
    -     *
    -     * say('hello', 'fred', 'barney', 'pebbles');
    -     * // => 'hello fred, barney, & pebbles'
    -     */
    -    function rest(func, start) {
    -      if (typeof func != 'function') {
    -        throw new TypeError(FUNC_ERROR_TEXT);
    -      }
    -      start = start === undefined ? start : toInteger(start);
    -      return baseRest(func, start);
    -    }
    -
    -    /**
    -     * Creates a function that invokes `func` with the `this` binding of the
    -     * create function and an array of arguments much like
    -     * [`Function#apply`](http://www.ecma-international.org/ecma-262/7.0/#sec-function.prototype.apply).
    -     *
    -     * **Note:** This method is based on the
    -     * [spread operator](https://mdn.io/spread_operator).
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 3.2.0
    -     * @category Function
    -     * @param {Function} func The function to spread arguments over.
    -     * @param {number} [start=0] The start position of the spread.
    -     * @returns {Function} Returns the new function.
    -     * @example
    -     *
    -     * var say = _.spread(function(who, what) {
    -     *   return who + ' says ' + what;
    -     * });
    -     *
    -     * say(['fred', 'hello']);
    -     * // => 'fred says hello'
    -     *
    -     * var numbers = Promise.all([
    -     *   Promise.resolve(40),
    -     *   Promise.resolve(36)
    -     * ]);
    -     *
    -     * numbers.then(_.spread(function(x, y) {
    -     *   return x + y;
    -     * }));
    -     * // => a Promise of 76
    -     */
    -    function spread(func, start) {
    -      if (typeof func != 'function') {
    -        throw new TypeError(FUNC_ERROR_TEXT);
    -      }
    -      start = start == null ? 0 : nativeMax(toInteger(start), 0);
    -      return baseRest(function (args) {
    -        var array = args[start],
    -          otherArgs = castSlice(args, 0, start);
    -        if (array) {
    -          arrayPush(otherArgs, array);
    -        }
    -        return apply(func, this, otherArgs);
    -      });
    -    }
    -
    -    /**
    -     * Creates a throttled function that only invokes `func` at most once per
    -     * every `wait` milliseconds. The throttled function comes with a `cancel`
    -     * method to cancel delayed `func` invocations and a `flush` method to
    -     * immediately invoke them. Provide `options` to indicate whether `func`
    -     * should be invoked on the leading and/or trailing edge of the `wait`
    -     * timeout. The `func` is invoked with the last arguments provided to the
    -     * throttled function. Subsequent calls to the throttled function return the
    -     * result of the last `func` invocation.
    -     *
    -     * **Note:** If `leading` and `trailing` options are `true`, `func` is
    -     * invoked on the trailing edge of the timeout only if the throttled function
    -     * is invoked more than once during the `wait` timeout.
    -     *
    -     * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
    -     * until to the next tick, similar to `setTimeout` with a timeout of `0`.
    -     *
    -     * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
    -     * for details over the differences between `_.throttle` and `_.debounce`.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.1.0
    -     * @category Function
    -     * @param {Function} func The function to throttle.
    -     * @param {number} [wait=0] The number of milliseconds to throttle invocations to.
    -     * @param {Object} [options={}] The options object.
    -     * @param {boolean} [options.leading=true]
    -     *  Specify invoking on the leading edge of the timeout.
    -     * @param {boolean} [options.trailing=true]
    -     *  Specify invoking on the trailing edge of the timeout.
    -     * @returns {Function} Returns the new throttled function.
    -     * @example
    -     *
    -     * // Avoid excessively updating the position while scrolling.
    -     * jQuery(window).on('scroll', _.throttle(updatePosition, 100));
    -     *
    -     * // Invoke `renewToken` when the click event is fired, but not more than once every 5 minutes.
    -     * var throttled = _.throttle(renewToken, 300000, { 'trailing': false });
    -     * jQuery(element).on('click', throttled);
    -     *
    -     * // Cancel the trailing throttled invocation.
    -     * jQuery(window).on('popstate', throttled.cancel);
    -     */
    -    function throttle(func, wait, options) {
    -      var leading = true,
    -        trailing = true;
    -      if (typeof func != 'function') {
    -        throw new TypeError(FUNC_ERROR_TEXT);
    -      }
    -      if (isObject(options)) {
    -        leading = 'leading' in options ? !!options.leading : leading;
    -        trailing = 'trailing' in options ? !!options.trailing : trailing;
    -      }
    -      return debounce(func, wait, {
    -        'leading': leading,
    -        'maxWait': wait,
    -        'trailing': trailing
    -      });
    -    }
    -
    -    /**
    -     * Creates a function that accepts up to one argument, ignoring any
    -     * additional arguments.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 4.0.0
    -     * @category Function
    -     * @param {Function} func The function to cap arguments for.
    -     * @returns {Function} Returns the new capped function.
    -     * @example
    -     *
    -     * _.map(['6', '8', '10'], _.unary(parseInt));
    -     * // => [6, 8, 10]
    -     */
    -    function unary(func) {
    -      return ary(func, 1);
    -    }
    -
    -    /**
    -     * Creates a function that provides `value` to `wrapper` as its first
    -     * argument. Any additional arguments provided to the function are appended
    -     * to those provided to the `wrapper`. The wrapper is invoked with the `this`
    -     * binding of the created function.
    -     *
    -     * @static
    -     * @memberOf _
    -     * @since 0.1.0
    -     * @category Function
    -     * @param {*} value The value to wrap.
    -     * @param {Function} [wrapper=identity] The wrapper function.
    -     * @returns {Function} Returns the new function.
    -     * @example
    -     *
    -     * var p = _.wrap(_.escape, function(func, text) {
    -     *   return '

    ' + func(text) + '

    '; - * }); - * - * p('fred, barney, & pebbles'); - * // => '

    fred, barney, & pebbles

    ' - */ - function wrap(value, wrapper) { - return partial(castFunction(wrapper), value); - } - - /*------------------------------------------------------------------------*/ - - /** - * Casts `value` as an array if it's not one. - * - * @static - * @memberOf _ - * @since 4.4.0 - * @category Lang - * @param {*} value The value to inspect. - * @returns {Array} Returns the cast array. - * @example - * - * _.castArray(1); - * // => [1] - * - * _.castArray({ 'a': 1 }); - * // => [{ 'a': 1 }] - * - * _.castArray('abc'); - * // => ['abc'] - * - * _.castArray(null); - * // => [null] - * - * _.castArray(undefined); - * // => [undefined] - * - * _.castArray(); - * // => [] - * - * var array = [1, 2, 3]; - * console.log(_.castArray(array) === array); - * // => true - */ - function castArray() { - if (!arguments.length) { - return []; - } - var value = arguments[0]; - return isArray(value) ? value : [value]; - } - - /** - * Creates a shallow clone of `value`. - * - * **Note:** This method is loosely based on the - * [structured clone algorithm](https://mdn.io/Structured_clone_algorithm) - * and supports cloning arrays, array buffers, booleans, date objects, maps, - * numbers, `Object` objects, regexes, sets, strings, symbols, and typed - * arrays. The own enumerable properties of `arguments` objects are cloned - * as plain objects. An empty object is returned for uncloneable values such - * as error objects, functions, DOM nodes, and WeakMaps. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to clone. - * @returns {*} Returns the cloned value. - * @see _.cloneDeep - * @example - * - * var objects = [{ 'a': 1 }, { 'b': 2 }]; - * - * var shallow = _.clone(objects); - * console.log(shallow[0] === objects[0]); - * // => true - */ - function clone(value) { - return baseClone(value, CLONE_SYMBOLS_FLAG); - } - - /** - * This method is like `_.clone` except that it accepts `customizer` which - * is invoked to produce the cloned value. If `customizer` returns `undefined`, - * cloning is handled by the method instead. The `customizer` is invoked with - * up to four arguments; (value [, index|key, object, stack]). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to clone. - * @param {Function} [customizer] The function to customize cloning. - * @returns {*} Returns the cloned value. - * @see _.cloneDeepWith - * @example - * - * function customizer(value) { - * if (_.isElement(value)) { - * return value.cloneNode(false); - * } - * } - * - * var el = _.cloneWith(document.body, customizer); - * - * console.log(el === document.body); - * // => false - * console.log(el.nodeName); - * // => 'BODY' - * console.log(el.childNodes.length); - * // => 0 - */ - function cloneWith(value, customizer) { - customizer = typeof customizer == 'function' ? customizer : undefined; - return baseClone(value, CLONE_SYMBOLS_FLAG, customizer); - } - - /** - * This method is like `_.clone` except that it recursively clones `value`. - * - * @static - * @memberOf _ - * @since 1.0.0 - * @category Lang - * @param {*} value The value to recursively clone. - * @returns {*} Returns the deep cloned value. - * @see _.clone - * @example - * - * var objects = [{ 'a': 1 }, { 'b': 2 }]; - * - * var deep = _.cloneDeep(objects); - * console.log(deep[0] === objects[0]); - * // => false - */ - function cloneDeep(value) { - return baseClone(value, CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG); - } - - /** - * This method is like `_.cloneWith` except that it recursively clones `value`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to recursively clone. - * @param {Function} [customizer] The function to customize cloning. - * @returns {*} Returns the deep cloned value. - * @see _.cloneWith - * @example - * - * function customizer(value) { - * if (_.isElement(value)) { - * return value.cloneNode(true); - * } - * } - * - * var el = _.cloneDeepWith(document.body, customizer); - * - * console.log(el === document.body); - * // => false - * console.log(el.nodeName); - * // => 'BODY' - * console.log(el.childNodes.length); - * // => 20 - */ - function cloneDeepWith(value, customizer) { - customizer = typeof customizer == 'function' ? customizer : undefined; - return baseClone(value, CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG, customizer); - } - - /** - * Checks if `object` conforms to `source` by invoking the predicate - * properties of `source` with the corresponding property values of `object`. - * - * **Note:** This method is equivalent to `_.conforms` when `source` is - * partially applied. - * - * @static - * @memberOf _ - * @since 4.14.0 - * @category Lang - * @param {Object} object The object to inspect. - * @param {Object} source The object of property predicates to conform to. - * @returns {boolean} Returns `true` if `object` conforms, else `false`. - * @example - * - * var object = { 'a': 1, 'b': 2 }; - * - * _.conformsTo(object, { 'b': function(n) { return n > 1; } }); - * // => true - * - * _.conformsTo(object, { 'b': function(n) { return n > 2; } }); - * // => false - */ - function conformsTo(object, source) { - return source == null || baseConformsTo(object, source, keys(source)); - } - - /** - * Performs a - * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) - * comparison between two values to determine if they are equivalent. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @returns {boolean} Returns `true` if the values are equivalent, else `false`. - * @example - * - * var object = { 'a': 1 }; - * var other = { 'a': 1 }; - * - * _.eq(object, object); - * // => true - * - * _.eq(object, other); - * // => false - * - * _.eq('a', 'a'); - * // => true - * - * _.eq('a', Object('a')); - * // => false - * - * _.eq(NaN, NaN); - * // => true - */ - function eq(value, other) { - return value === other || value !== value && other !== other; - } - - /** - * Checks if `value` is greater than `other`. - * - * @static - * @memberOf _ - * @since 3.9.0 - * @category Lang - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @returns {boolean} Returns `true` if `value` is greater than `other`, - * else `false`. - * @see _.lt - * @example - * - * _.gt(3, 1); - * // => true - * - * _.gt(3, 3); - * // => false - * - * _.gt(1, 3); - * // => false - */ - var gt = createRelationalOperation(baseGt); - - /** - * Checks if `value` is greater than or equal to `other`. - * - * @static - * @memberOf _ - * @since 3.9.0 - * @category Lang - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @returns {boolean} Returns `true` if `value` is greater than or equal to - * `other`, else `false`. - * @see _.lte - * @example - * - * _.gte(3, 1); - * // => true - * - * _.gte(3, 3); - * // => true - * - * _.gte(1, 3); - * // => false - */ - var gte = createRelationalOperation(function (value, other) { - return value >= other; - }); - - /** - * Checks if `value` is likely an `arguments` object. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an `arguments` object, - * else `false`. - * @example - * - * _.isArguments(function() { return arguments; }()); - * // => true - * - * _.isArguments([1, 2, 3]); - * // => false - */ - var isArguments = baseIsArguments(function () { - return arguments; - }()) ? baseIsArguments : function (value) { - return isObjectLike(value) && hasOwnProperty.call(value, 'callee') && !propertyIsEnumerable.call(value, 'callee'); - }; - - /** - * Checks if `value` is classified as an `Array` object. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an array, else `false`. - * @example - * - * _.isArray([1, 2, 3]); - * // => true - * - * _.isArray(document.body.children); - * // => false - * - * _.isArray('abc'); - * // => false - * - * _.isArray(_.noop); - * // => false - */ - var isArray = Array.isArray; - - /** - * Checks if `value` is classified as an `ArrayBuffer` object. - * - * @static - * @memberOf _ - * @since 4.3.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an array buffer, else `false`. - * @example - * - * _.isArrayBuffer(new ArrayBuffer(2)); - * // => true - * - * _.isArrayBuffer(new Array(2)); - * // => false - */ - var isArrayBuffer = nodeIsArrayBuffer ? baseUnary(nodeIsArrayBuffer) : baseIsArrayBuffer; - - /** - * Checks if `value` is array-like. A value is considered array-like if it's - * not a function and has a `value.length` that's an integer greater than or - * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is array-like, else `false`. - * @example - * - * _.isArrayLike([1, 2, 3]); - * // => true - * - * _.isArrayLike(document.body.children); - * // => true - * - * _.isArrayLike('abc'); - * // => true - * - * _.isArrayLike(_.noop); - * // => false - */ - function isArrayLike(value) { - return value != null && isLength(value.length) && !isFunction(value); - } - - /** - * This method is like `_.isArrayLike` except that it also checks if `value` - * is an object. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an array-like object, - * else `false`. - * @example - * - * _.isArrayLikeObject([1, 2, 3]); - * // => true - * - * _.isArrayLikeObject(document.body.children); - * // => true - * - * _.isArrayLikeObject('abc'); - * // => false - * - * _.isArrayLikeObject(_.noop); - * // => false - */ - function isArrayLikeObject(value) { - return isObjectLike(value) && isArrayLike(value); - } - - /** - * Checks if `value` is classified as a boolean primitive or object. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a boolean, else `false`. - * @example - * - * _.isBoolean(false); - * // => true - * - * _.isBoolean(null); - * // => false - */ - function isBoolean(value) { - return value === true || value === false || isObjectLike(value) && baseGetTag(value) == boolTag; - } - - /** - * Checks if `value` is a buffer. - * - * @static - * @memberOf _ - * @since 4.3.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a buffer, else `false`. - * @example - * - * _.isBuffer(new Buffer(2)); - * // => true - * - * _.isBuffer(new Uint8Array(2)); - * // => false - */ - var isBuffer = nativeIsBuffer || stubFalse; - - /** - * Checks if `value` is classified as a `Date` object. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a date object, else `false`. - * @example - * - * _.isDate(new Date); - * // => true - * - * _.isDate('Mon April 23 2012'); - * // => false - */ - var isDate = nodeIsDate ? baseUnary(nodeIsDate) : baseIsDate; - - /** - * Checks if `value` is likely a DOM element. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a DOM element, else `false`. - * @example - * - * _.isElement(document.body); - * // => true - * - * _.isElement(''); - * // => false - */ - function isElement(value) { - return isObjectLike(value) && value.nodeType === 1 && !isPlainObject(value); - } - - /** - * Checks if `value` is an empty object, collection, map, or set. - * - * Objects are considered empty if they have no own enumerable string keyed - * properties. - * - * Array-like values such as `arguments` objects, arrays, buffers, strings, or - * jQuery-like collections are considered empty if they have a `length` of `0`. - * Similarly, maps and sets are considered empty if they have a `size` of `0`. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is empty, else `false`. - * @example - * - * _.isEmpty(null); - * // => true - * - * _.isEmpty(true); - * // => true - * - * _.isEmpty(1); - * // => true - * - * _.isEmpty([1, 2, 3]); - * // => false - * - * _.isEmpty({ 'a': 1 }); - * // => false - */ - function isEmpty(value) { - if (value == null) { - return true; - } - if (isArrayLike(value) && (isArray(value) || typeof value == 'string' || typeof value.splice == 'function' || isBuffer(value) || isTypedArray(value) || isArguments(value))) { - return !value.length; - } - var tag = getTag(value); - if (tag == mapTag || tag == setTag) { - return !value.size; - } - if (isPrototype(value)) { - return !baseKeys(value).length; - } - for (var key in value) { - if (hasOwnProperty.call(value, key)) { - return false; - } - } - return true; - } - - /** - * Performs a deep comparison between two values to determine if they are - * equivalent. - * - * **Note:** This method supports comparing arrays, array buffers, booleans, - * date objects, error objects, maps, numbers, `Object` objects, regexes, - * sets, strings, symbols, and typed arrays. `Object` objects are compared - * by their own, not inherited, enumerable properties. Functions and DOM - * nodes are compared by strict equality, i.e. `===`. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @returns {boolean} Returns `true` if the values are equivalent, else `false`. - * @example - * - * var object = { 'a': 1 }; - * var other = { 'a': 1 }; - * - * _.isEqual(object, other); - * // => true - * - * object === other; - * // => false - */ - function isEqual(value, other) { - return baseIsEqual(value, other); - } - - /** - * This method is like `_.isEqual` except that it accepts `customizer` which - * is invoked to compare values. If `customizer` returns `undefined`, comparisons - * are handled by the method instead. The `customizer` is invoked with up to - * six arguments: (objValue, othValue [, index|key, object, other, stack]). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @param {Function} [customizer] The function to customize comparisons. - * @returns {boolean} Returns `true` if the values are equivalent, else `false`. - * @example - * - * function isGreeting(value) { - * return /^h(?:i|ello)$/.test(value); - * } - * - * function customizer(objValue, othValue) { - * if (isGreeting(objValue) && isGreeting(othValue)) { - * return true; - * } - * } - * - * var array = ['hello', 'goodbye']; - * var other = ['hi', 'goodbye']; - * - * _.isEqualWith(array, other, customizer); - * // => true - */ - function isEqualWith(value, other, customizer) { - customizer = typeof customizer == 'function' ? customizer : undefined; - var result = customizer ? customizer(value, other) : undefined; - return result === undefined ? baseIsEqual(value, other, undefined, customizer) : !!result; - } - - /** - * Checks if `value` is an `Error`, `EvalError`, `RangeError`, `ReferenceError`, - * `SyntaxError`, `TypeError`, or `URIError` object. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an error object, else `false`. - * @example - * - * _.isError(new Error); - * // => true - * - * _.isError(Error); - * // => false - */ - function isError(value) { - if (!isObjectLike(value)) { - return false; - } - var tag = baseGetTag(value); - return tag == errorTag || tag == domExcTag || typeof value.message == 'string' && typeof value.name == 'string' && !isPlainObject(value); - } - - /** - * Checks if `value` is a finite primitive number. - * - * **Note:** This method is based on - * [`Number.isFinite`](https://mdn.io/Number/isFinite). - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a finite number, else `false`. - * @example - * - * _.isFinite(3); - * // => true - * - * _.isFinite(Number.MIN_VALUE); - * // => true - * - * _.isFinite(Infinity); - * // => false - * - * _.isFinite('3'); - * // => false - */ - function isFinite(value) { - return typeof value == 'number' && nativeIsFinite(value); - } - - /** - * Checks if `value` is classified as a `Function` object. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a function, else `false`. - * @example - * - * _.isFunction(_); - * // => true - * - * _.isFunction(/abc/); - * // => false - */ - function isFunction(value) { - if (!isObject(value)) { - return false; - } - // The use of `Object#toString` avoids issues with the `typeof` operator - // in Safari 9 which returns 'object' for typed arrays and other constructors. - var tag = baseGetTag(value); - return tag == funcTag || tag == genTag || tag == asyncTag || tag == proxyTag; - } - - /** - * Checks if `value` is an integer. - * - * **Note:** This method is based on - * [`Number.isInteger`](https://mdn.io/Number/isInteger). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an integer, else `false`. - * @example - * - * _.isInteger(3); - * // => true - * - * _.isInteger(Number.MIN_VALUE); - * // => false - * - * _.isInteger(Infinity); - * // => false - * - * _.isInteger('3'); - * // => false - */ - function isInteger(value) { - return typeof value == 'number' && value == toInteger(value); - } - - /** - * Checks if `value` is a valid array-like length. - * - * **Note:** This method is loosely based on - * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. - * @example - * - * _.isLength(3); - * // => true - * - * _.isLength(Number.MIN_VALUE); - * // => false - * - * _.isLength(Infinity); - * // => false - * - * _.isLength('3'); - * // => false - */ - function isLength(value) { - return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; - } - - /** - * Checks if `value` is the - * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types) - * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an object, else `false`. - * @example - * - * _.isObject({}); - * // => true - * - * _.isObject([1, 2, 3]); - * // => true - * - * _.isObject(_.noop); - * // => true - * - * _.isObject(null); - * // => false - */ - function isObject(value) { - var type = typeof value; - return value != null && (type == 'object' || type == 'function'); - } - - /** - * Checks if `value` is object-like. A value is object-like if it's not `null` - * and has a `typeof` result of "object". - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is object-like, else `false`. - * @example - * - * _.isObjectLike({}); - * // => true - * - * _.isObjectLike([1, 2, 3]); - * // => true - * - * _.isObjectLike(_.noop); - * // => false - * - * _.isObjectLike(null); - * // => false - */ - function isObjectLike(value) { - return value != null && typeof value == 'object'; - } - - /** - * Checks if `value` is classified as a `Map` object. - * - * @static - * @memberOf _ - * @since 4.3.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a map, else `false`. - * @example - * - * _.isMap(new Map); - * // => true - * - * _.isMap(new WeakMap); - * // => false - */ - var isMap = nodeIsMap ? baseUnary(nodeIsMap) : baseIsMap; - - /** - * Performs a partial deep comparison between `object` and `source` to - * determine if `object` contains equivalent property values. - * - * **Note:** This method is equivalent to `_.matches` when `source` is - * partially applied. - * - * Partial comparisons will match empty array and empty object `source` - * values against any array or object value, respectively. See `_.isEqual` - * for a list of supported value comparisons. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Lang - * @param {Object} object The object to inspect. - * @param {Object} source The object of property values to match. - * @returns {boolean} Returns `true` if `object` is a match, else `false`. - * @example - * - * var object = { 'a': 1, 'b': 2 }; - * - * _.isMatch(object, { 'b': 2 }); - * // => true - * - * _.isMatch(object, { 'b': 1 }); - * // => false - */ - function isMatch(object, source) { - return object === source || baseIsMatch(object, source, getMatchData(source)); - } - - /** - * This method is like `_.isMatch` except that it accepts `customizer` which - * is invoked to compare values. If `customizer` returns `undefined`, comparisons - * are handled by the method instead. The `customizer` is invoked with five - * arguments: (objValue, srcValue, index|key, object, source). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {Object} object The object to inspect. - * @param {Object} source The object of property values to match. - * @param {Function} [customizer] The function to customize comparisons. - * @returns {boolean} Returns `true` if `object` is a match, else `false`. - * @example - * - * function isGreeting(value) { - * return /^h(?:i|ello)$/.test(value); - * } - * - * function customizer(objValue, srcValue) { - * if (isGreeting(objValue) && isGreeting(srcValue)) { - * return true; - * } - * } - * - * var object = { 'greeting': 'hello' }; - * var source = { 'greeting': 'hi' }; - * - * _.isMatchWith(object, source, customizer); - * // => true - */ - function isMatchWith(object, source, customizer) { - customizer = typeof customizer == 'function' ? customizer : undefined; - return baseIsMatch(object, source, getMatchData(source), customizer); - } - - /** - * Checks if `value` is `NaN`. - * - * **Note:** This method is based on - * [`Number.isNaN`](https://mdn.io/Number/isNaN) and is not the same as - * global [`isNaN`](https://mdn.io/isNaN) which returns `true` for - * `undefined` and other non-number values. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is `NaN`, else `false`. - * @example - * - * _.isNaN(NaN); - * // => true - * - * _.isNaN(new Number(NaN)); - * // => true - * - * isNaN(undefined); - * // => true - * - * _.isNaN(undefined); - * // => false - */ - function isNaN(value) { - // An `NaN` primitive is the only value that is not equal to itself. - // Perform the `toStringTag` check first to avoid errors with some - // ActiveX objects in IE. - return isNumber(value) && value != +value; - } - - /** - * Checks if `value` is a pristine native function. - * - * **Note:** This method can't reliably detect native functions in the presence - * of the core-js package because core-js circumvents this kind of detection. - * Despite multiple requests, the core-js maintainer has made it clear: any - * attempt to fix the detection will be obstructed. As a result, we're left - * with little choice but to throw an error. Unfortunately, this also affects - * packages, like [babel-polyfill](https://www.npmjs.com/package/babel-polyfill), - * which rely on core-js. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a native function, - * else `false`. - * @example - * - * _.isNative(Array.prototype.push); - * // => true - * - * _.isNative(_); - * // => false - */ - function isNative(value) { - if (isMaskable(value)) { - throw new Error(CORE_ERROR_TEXT); - } - return baseIsNative(value); - } - - /** - * Checks if `value` is `null`. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is `null`, else `false`. - * @example - * - * _.isNull(null); - * // => true - * - * _.isNull(void 0); - * // => false - */ - function isNull(value) { - return value === null; - } - - /** - * Checks if `value` is `null` or `undefined`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is nullish, else `false`. - * @example - * - * _.isNil(null); - * // => true - * - * _.isNil(void 0); - * // => true - * - * _.isNil(NaN); - * // => false - */ - function isNil(value) { - return value == null; - } - - /** - * Checks if `value` is classified as a `Number` primitive or object. - * - * **Note:** To exclude `Infinity`, `-Infinity`, and `NaN`, which are - * classified as numbers, use the `_.isFinite` method. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a number, else `false`. - * @example - * - * _.isNumber(3); - * // => true - * - * _.isNumber(Number.MIN_VALUE); - * // => true - * - * _.isNumber(Infinity); - * // => true - * - * _.isNumber('3'); - * // => false - */ - function isNumber(value) { - return typeof value == 'number' || isObjectLike(value) && baseGetTag(value) == numberTag; - } - - /** - * Checks if `value` is a plain object, that is, an object created by the - * `Object` constructor or one with a `[[Prototype]]` of `null`. - * - * @static - * @memberOf _ - * @since 0.8.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a plain object, else `false`. - * @example - * - * function Foo() { - * this.a = 1; - * } - * - * _.isPlainObject(new Foo); - * // => false - * - * _.isPlainObject([1, 2, 3]); - * // => false - * - * _.isPlainObject({ 'x': 0, 'y': 0 }); - * // => true - * - * _.isPlainObject(Object.create(null)); - * // => true - */ - function isPlainObject(value) { - if (!isObjectLike(value) || baseGetTag(value) != objectTag) { - return false; - } - var proto = getPrototype(value); - if (proto === null) { - return true; - } - var Ctor = hasOwnProperty.call(proto, 'constructor') && proto.constructor; - return typeof Ctor == 'function' && Ctor instanceof Ctor && funcToString.call(Ctor) == objectCtorString; - } - - /** - * Checks if `value` is classified as a `RegExp` object. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a regexp, else `false`. - * @example - * - * _.isRegExp(/abc/); - * // => true - * - * _.isRegExp('/abc/'); - * // => false - */ - var isRegExp = nodeIsRegExp ? baseUnary(nodeIsRegExp) : baseIsRegExp; - - /** - * Checks if `value` is a safe integer. An integer is safe if it's an IEEE-754 - * double precision number which isn't the result of a rounded unsafe integer. - * - * **Note:** This method is based on - * [`Number.isSafeInteger`](https://mdn.io/Number/isSafeInteger). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a safe integer, else `false`. - * @example - * - * _.isSafeInteger(3); - * // => true - * - * _.isSafeInteger(Number.MIN_VALUE); - * // => false - * - * _.isSafeInteger(Infinity); - * // => false - * - * _.isSafeInteger('3'); - * // => false - */ - function isSafeInteger(value) { - return isInteger(value) && value >= -MAX_SAFE_INTEGER && value <= MAX_SAFE_INTEGER; - } - - /** - * Checks if `value` is classified as a `Set` object. - * - * @static - * @memberOf _ - * @since 4.3.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a set, else `false`. - * @example - * - * _.isSet(new Set); - * // => true - * - * _.isSet(new WeakSet); - * // => false - */ - var isSet = nodeIsSet ? baseUnary(nodeIsSet) : baseIsSet; - - /** - * Checks if `value` is classified as a `String` primitive or object. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a string, else `false`. - * @example - * - * _.isString('abc'); - * // => true - * - * _.isString(1); - * // => false - */ - function isString(value) { - return typeof value == 'string' || !isArray(value) && isObjectLike(value) && baseGetTag(value) == stringTag; - } - - /** - * Checks if `value` is classified as a `Symbol` primitive or object. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a symbol, else `false`. - * @example - * - * _.isSymbol(Symbol.iterator); - * // => true - * - * _.isSymbol('abc'); - * // => false - */ - function isSymbol(value) { - return typeof value == 'symbol' || isObjectLike(value) && baseGetTag(value) == symbolTag; - } - - /** - * Checks if `value` is classified as a typed array. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a typed array, else `false`. - * @example - * - * _.isTypedArray(new Uint8Array); - * // => true - * - * _.isTypedArray([]); - * // => false - */ - var isTypedArray = nodeIsTypedArray ? baseUnary(nodeIsTypedArray) : baseIsTypedArray; - - /** - * Checks if `value` is `undefined`. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is `undefined`, else `false`. - * @example - * - * _.isUndefined(void 0); - * // => true - * - * _.isUndefined(null); - * // => false - */ - function isUndefined(value) { - return value === undefined; - } - - /** - * Checks if `value` is classified as a `WeakMap` object. - * - * @static - * @memberOf _ - * @since 4.3.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a weak map, else `false`. - * @example - * - * _.isWeakMap(new WeakMap); - * // => true - * - * _.isWeakMap(new Map); - * // => false - */ - function isWeakMap(value) { - return isObjectLike(value) && getTag(value) == weakMapTag; - } - - /** - * Checks if `value` is classified as a `WeakSet` object. - * - * @static - * @memberOf _ - * @since 4.3.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a weak set, else `false`. - * @example - * - * _.isWeakSet(new WeakSet); - * // => true - * - * _.isWeakSet(new Set); - * // => false - */ - function isWeakSet(value) { - return isObjectLike(value) && baseGetTag(value) == weakSetTag; - } - - /** - * Checks if `value` is less than `other`. - * - * @static - * @memberOf _ - * @since 3.9.0 - * @category Lang - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @returns {boolean} Returns `true` if `value` is less than `other`, - * else `false`. - * @see _.gt - * @example - * - * _.lt(1, 3); - * // => true - * - * _.lt(3, 3); - * // => false - * - * _.lt(3, 1); - * // => false - */ - var lt = createRelationalOperation(baseLt); - - /** - * Checks if `value` is less than or equal to `other`. - * - * @static - * @memberOf _ - * @since 3.9.0 - * @category Lang - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @returns {boolean} Returns `true` if `value` is less than or equal to - * `other`, else `false`. - * @see _.gte - * @example - * - * _.lte(1, 3); - * // => true - * - * _.lte(3, 3); - * // => true - * - * _.lte(3, 1); - * // => false - */ - var lte = createRelationalOperation(function (value, other) { - return value <= other; - }); - - /** - * Converts `value` to an array. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Lang - * @param {*} value The value to convert. - * @returns {Array} Returns the converted array. - * @example - * - * _.toArray({ 'a': 1, 'b': 2 }); - * // => [1, 2] - * - * _.toArray('abc'); - * // => ['a', 'b', 'c'] - * - * _.toArray(1); - * // => [] - * - * _.toArray(null); - * // => [] - */ - function toArray(value) { - if (!value) { - return []; - } - if (isArrayLike(value)) { - return isString(value) ? stringToArray(value) : copyArray(value); - } - if (symIterator && value[symIterator]) { - return iteratorToArray(value[symIterator]()); - } - var tag = getTag(value), - func = tag == mapTag ? mapToArray : tag == setTag ? setToArray : values; - return func(value); - } - - /** - * Converts `value` to a finite number. - * - * @static - * @memberOf _ - * @since 4.12.0 - * @category Lang - * @param {*} value The value to convert. - * @returns {number} Returns the converted number. - * @example - * - * _.toFinite(3.2); - * // => 3.2 - * - * _.toFinite(Number.MIN_VALUE); - * // => 5e-324 - * - * _.toFinite(Infinity); - * // => 1.7976931348623157e+308 - * - * _.toFinite('3.2'); - * // => 3.2 - */ - function toFinite(value) { - if (!value) { - return value === 0 ? value : 0; - } - value = toNumber(value); - if (value === INFINITY || value === -INFINITY) { - var sign = value < 0 ? -1 : 1; - return sign * MAX_INTEGER; - } - return value === value ? value : 0; - } - - /** - * Converts `value` to an integer. - * - * **Note:** This method is loosely based on - * [`ToInteger`](http://www.ecma-international.org/ecma-262/7.0/#sec-tointeger). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to convert. - * @returns {number} Returns the converted integer. - * @example - * - * _.toInteger(3.2); - * // => 3 - * - * _.toInteger(Number.MIN_VALUE); - * // => 0 - * - * _.toInteger(Infinity); - * // => 1.7976931348623157e+308 - * - * _.toInteger('3.2'); - * // => 3 - */ - function toInteger(value) { - var result = toFinite(value), - remainder = result % 1; - return result === result ? remainder ? result - remainder : result : 0; - } - - /** - * Converts `value` to an integer suitable for use as the length of an - * array-like object. - * - * **Note:** This method is based on - * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to convert. - * @returns {number} Returns the converted integer. - * @example - * - * _.toLength(3.2); - * // => 3 - * - * _.toLength(Number.MIN_VALUE); - * // => 0 - * - * _.toLength(Infinity); - * // => 4294967295 - * - * _.toLength('3.2'); - * // => 3 - */ - function toLength(value) { - return value ? baseClamp(toInteger(value), 0, MAX_ARRAY_LENGTH) : 0; - } - - /** - * Converts `value` to a number. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to process. - * @returns {number} Returns the number. - * @example - * - * _.toNumber(3.2); - * // => 3.2 - * - * _.toNumber(Number.MIN_VALUE); - * // => 5e-324 - * - * _.toNumber(Infinity); - * // => Infinity - * - * _.toNumber('3.2'); - * // => 3.2 - */ - function toNumber(value) { - if (typeof value == 'number') { - return value; - } - if (isSymbol(value)) { - return NAN; - } - if (isObject(value)) { - var other = typeof value.valueOf == 'function' ? value.valueOf() : value; - value = isObject(other) ? other + '' : other; - } - if (typeof value != 'string') { - return value === 0 ? value : +value; - } - value = baseTrim(value); - var isBinary = reIsBinary.test(value); - return isBinary || reIsOctal.test(value) ? freeParseInt(value.slice(2), isBinary ? 2 : 8) : reIsBadHex.test(value) ? NAN : +value; - } - - /** - * Converts `value` to a plain object flattening inherited enumerable string - * keyed properties of `value` to own properties of the plain object. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Lang - * @param {*} value The value to convert. - * @returns {Object} Returns the converted plain object. - * @example - * - * function Foo() { - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.assign({ 'a': 1 }, new Foo); - * // => { 'a': 1, 'b': 2 } - * - * _.assign({ 'a': 1 }, _.toPlainObject(new Foo)); - * // => { 'a': 1, 'b': 2, 'c': 3 } - */ - function toPlainObject(value) { - return copyObject(value, keysIn(value)); - } - - /** - * Converts `value` to a safe integer. A safe integer can be compared and - * represented correctly. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to convert. - * @returns {number} Returns the converted integer. - * @example - * - * _.toSafeInteger(3.2); - * // => 3 - * - * _.toSafeInteger(Number.MIN_VALUE); - * // => 0 - * - * _.toSafeInteger(Infinity); - * // => 9007199254740991 - * - * _.toSafeInteger('3.2'); - * // => 3 - */ - function toSafeInteger(value) { - return value ? baseClamp(toInteger(value), -MAX_SAFE_INTEGER, MAX_SAFE_INTEGER) : value === 0 ? value : 0; - } - - /** - * Converts `value` to a string. An empty string is returned for `null` - * and `undefined` values. The sign of `-0` is preserved. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to convert. - * @returns {string} Returns the converted string. - * @example - * - * _.toString(null); - * // => '' - * - * _.toString(-0); - * // => '-0' - * - * _.toString([1, 2, 3]); - * // => '1,2,3' - */ - function toString(value) { - return value == null ? '' : baseToString(value); - } - - /*------------------------------------------------------------------------*/ - - /** - * Assigns own enumerable string keyed properties of source objects to the - * destination object. Source objects are applied from left to right. - * Subsequent sources overwrite property assignments of previous sources. - * - * **Note:** This method mutates `object` and is loosely based on - * [`Object.assign`](https://mdn.io/Object/assign). - * - * @static - * @memberOf _ - * @since 0.10.0 - * @category Object - * @param {Object} object The destination object. - * @param {...Object} [sources] The source objects. - * @returns {Object} Returns `object`. - * @see _.assignIn - * @example - * - * function Foo() { - * this.a = 1; - * } - * - * function Bar() { - * this.c = 3; - * } - * - * Foo.prototype.b = 2; - * Bar.prototype.d = 4; - * - * _.assign({ 'a': 0 }, new Foo, new Bar); - * // => { 'a': 1, 'c': 3 } - */ - var assign = createAssigner(function (object, source) { - if (isPrototype(source) || isArrayLike(source)) { - copyObject(source, keys(source), object); - return; - } - for (var key in source) { - if (hasOwnProperty.call(source, key)) { - assignValue(object, key, source[key]); - } - } - }); - - /** - * This method is like `_.assign` except that it iterates over own and - * inherited source properties. - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @alias extend - * @category Object - * @param {Object} object The destination object. - * @param {...Object} [sources] The source objects. - * @returns {Object} Returns `object`. - * @see _.assign - * @example - * - * function Foo() { - * this.a = 1; - * } - * - * function Bar() { - * this.c = 3; - * } - * - * Foo.prototype.b = 2; - * Bar.prototype.d = 4; - * - * _.assignIn({ 'a': 0 }, new Foo, new Bar); - * // => { 'a': 1, 'b': 2, 'c': 3, 'd': 4 } - */ - var assignIn = createAssigner(function (object, source) { - copyObject(source, keysIn(source), object); - }); - - /** - * This method is like `_.assignIn` except that it accepts `customizer` - * which is invoked to produce the assigned values. If `customizer` returns - * `undefined`, assignment is handled by the method instead. The `customizer` - * is invoked with five arguments: (objValue, srcValue, key, object, source). - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @alias extendWith - * @category Object - * @param {Object} object The destination object. - * @param {...Object} sources The source objects. - * @param {Function} [customizer] The function to customize assigned values. - * @returns {Object} Returns `object`. - * @see _.assignWith - * @example - * - * function customizer(objValue, srcValue) { - * return _.isUndefined(objValue) ? srcValue : objValue; - * } - * - * var defaults = _.partialRight(_.assignInWith, customizer); - * - * defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 }); - * // => { 'a': 1, 'b': 2 } - */ - var assignInWith = createAssigner(function (object, source, srcIndex, customizer) { - copyObject(source, keysIn(source), object, customizer); - }); - - /** - * This method is like `_.assign` except that it accepts `customizer` - * which is invoked to produce the assigned values. If `customizer` returns - * `undefined`, assignment is handled by the method instead. The `customizer` - * is invoked with five arguments: (objValue, srcValue, key, object, source). - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Object - * @param {Object} object The destination object. - * @param {...Object} sources The source objects. - * @param {Function} [customizer] The function to customize assigned values. - * @returns {Object} Returns `object`. - * @see _.assignInWith - * @example - * - * function customizer(objValue, srcValue) { - * return _.isUndefined(objValue) ? srcValue : objValue; - * } - * - * var defaults = _.partialRight(_.assignWith, customizer); - * - * defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 }); - * // => { 'a': 1, 'b': 2 } - */ - var assignWith = createAssigner(function (object, source, srcIndex, customizer) { - copyObject(source, keys(source), object, customizer); - }); - - /** - * Creates an array of values corresponding to `paths` of `object`. - * - * @static - * @memberOf _ - * @since 1.0.0 - * @category Object - * @param {Object} object The object to iterate over. - * @param {...(string|string[])} [paths] The property paths to pick. - * @returns {Array} Returns the picked values. - * @example - * - * var object = { 'a': [{ 'b': { 'c': 3 } }, 4] }; - * - * _.at(object, ['a[0].b.c', 'a[1]']); - * // => [3, 4] - */ - var at = flatRest(baseAt); - - /** - * Creates an object that inherits from the `prototype` object. If a - * `properties` object is given, its own enumerable string keyed properties - * are assigned to the created object. - * - * @static - * @memberOf _ - * @since 2.3.0 - * @category Object - * @param {Object} prototype The object to inherit from. - * @param {Object} [properties] The properties to assign to the object. - * @returns {Object} Returns the new object. - * @example - * - * function Shape() { - * this.x = 0; - * this.y = 0; - * } - * - * function Circle() { - * Shape.call(this); - * } - * - * Circle.prototype = _.create(Shape.prototype, { - * 'constructor': Circle - * }); - * - * var circle = new Circle; - * circle instanceof Circle; - * // => true - * - * circle instanceof Shape; - * // => true - */ - function create(prototype, properties) { - var result = baseCreate(prototype); - return properties == null ? result : baseAssign(result, properties); - } - - /** - * Assigns own and inherited enumerable string keyed properties of source - * objects to the destination object for all destination properties that - * resolve to `undefined`. Source objects are applied from left to right. - * Once a property is set, additional values of the same property are ignored. - * - * **Note:** This method mutates `object`. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Object - * @param {Object} object The destination object. - * @param {...Object} [sources] The source objects. - * @returns {Object} Returns `object`. - * @see _.defaultsDeep - * @example - * - * _.defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 }); - * // => { 'a': 1, 'b': 2 } - */ - var defaults = baseRest(function (object, sources) { - object = Object(object); - var index = -1; - var length = sources.length; - var guard = length > 2 ? sources[2] : undefined; - if (guard && isIterateeCall(sources[0], sources[1], guard)) { - length = 1; - } - while (++index < length) { - var source = sources[index]; - var props = keysIn(source); - var propsIndex = -1; - var propsLength = props.length; - while (++propsIndex < propsLength) { - var key = props[propsIndex]; - var value = object[key]; - if (value === undefined || eq(value, objectProto[key]) && !hasOwnProperty.call(object, key)) { - object[key] = source[key]; - } - } - } - return object; - }); - - /** - * This method is like `_.defaults` except that it recursively assigns - * default properties. - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 3.10.0 - * @category Object - * @param {Object} object The destination object. - * @param {...Object} [sources] The source objects. - * @returns {Object} Returns `object`. - * @see _.defaults - * @example - * - * _.defaultsDeep({ 'a': { 'b': 2 } }, { 'a': { 'b': 1, 'c': 3 } }); - * // => { 'a': { 'b': 2, 'c': 3 } } - */ - var defaultsDeep = baseRest(function (args) { - args.push(undefined, customDefaultsMerge); - return apply(mergeWith, undefined, args); - }); - - /** - * This method is like `_.find` except that it returns the key of the first - * element `predicate` returns truthy for instead of the element itself. - * - * @static - * @memberOf _ - * @since 1.1.0 - * @category Object - * @param {Object} object The object to inspect. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @returns {string|undefined} Returns the key of the matched element, - * else `undefined`. - * @example - * - * var users = { - * 'barney': { 'age': 36, 'active': true }, - * 'fred': { 'age': 40, 'active': false }, - * 'pebbles': { 'age': 1, 'active': true } - * }; - * - * _.findKey(users, function(o) { return o.age < 40; }); - * // => 'barney' (iteration order is not guaranteed) - * - * // The `_.matches` iteratee shorthand. - * _.findKey(users, { 'age': 1, 'active': true }); - * // => 'pebbles' - * - * // The `_.matchesProperty` iteratee shorthand. - * _.findKey(users, ['active', false]); - * // => 'fred' - * - * // The `_.property` iteratee shorthand. - * _.findKey(users, 'active'); - * // => 'barney' - */ - function findKey(object, predicate) { - return baseFindKey(object, getIteratee(predicate, 3), baseForOwn); - } - - /** - * This method is like `_.findKey` except that it iterates over elements of - * a collection in the opposite order. - * - * @static - * @memberOf _ - * @since 2.0.0 - * @category Object - * @param {Object} object The object to inspect. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @returns {string|undefined} Returns the key of the matched element, - * else `undefined`. - * @example - * - * var users = { - * 'barney': { 'age': 36, 'active': true }, - * 'fred': { 'age': 40, 'active': false }, - * 'pebbles': { 'age': 1, 'active': true } - * }; - * - * _.findLastKey(users, function(o) { return o.age < 40; }); - * // => returns 'pebbles' assuming `_.findKey` returns 'barney' - * - * // The `_.matches` iteratee shorthand. - * _.findLastKey(users, { 'age': 36, 'active': true }); - * // => 'barney' - * - * // The `_.matchesProperty` iteratee shorthand. - * _.findLastKey(users, ['active', false]); - * // => 'fred' - * - * // The `_.property` iteratee shorthand. - * _.findLastKey(users, 'active'); - * // => 'pebbles' - */ - function findLastKey(object, predicate) { - return baseFindKey(object, getIteratee(predicate, 3), baseForOwnRight); - } - - /** - * Iterates over own and inherited enumerable string keyed properties of an - * object and invokes `iteratee` for each property. The iteratee is invoked - * with three arguments: (value, key, object). Iteratee functions may exit - * iteration early by explicitly returning `false`. - * - * @static - * @memberOf _ - * @since 0.3.0 - * @category Object - * @param {Object} object The object to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @returns {Object} Returns `object`. - * @see _.forInRight - * @example - * - * function Foo() { - * this.a = 1; - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.forIn(new Foo, function(value, key) { - * console.log(key); - * }); - * // => Logs 'a', 'b', then 'c' (iteration order is not guaranteed). - */ - function forIn(object, iteratee) { - return object == null ? object : baseFor(object, getIteratee(iteratee, 3), keysIn); - } - - /** - * This method is like `_.forIn` except that it iterates over properties of - * `object` in the opposite order. - * - * @static - * @memberOf _ - * @since 2.0.0 - * @category Object - * @param {Object} object The object to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @returns {Object} Returns `object`. - * @see _.forIn - * @example - * - * function Foo() { - * this.a = 1; - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.forInRight(new Foo, function(value, key) { - * console.log(key); - * }); - * // => Logs 'c', 'b', then 'a' assuming `_.forIn` logs 'a', 'b', then 'c'. - */ - function forInRight(object, iteratee) { - return object == null ? object : baseForRight(object, getIteratee(iteratee, 3), keysIn); - } - - /** - * Iterates over own enumerable string keyed properties of an object and - * invokes `iteratee` for each property. The iteratee is invoked with three - * arguments: (value, key, object). Iteratee functions may exit iteration - * early by explicitly returning `false`. - * - * @static - * @memberOf _ - * @since 0.3.0 - * @category Object - * @param {Object} object The object to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @returns {Object} Returns `object`. - * @see _.forOwnRight - * @example - * - * function Foo() { - * this.a = 1; - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.forOwn(new Foo, function(value, key) { - * console.log(key); - * }); - * // => Logs 'a' then 'b' (iteration order is not guaranteed). - */ - function forOwn(object, iteratee) { - return object && baseForOwn(object, getIteratee(iteratee, 3)); - } - - /** - * This method is like `_.forOwn` except that it iterates over properties of - * `object` in the opposite order. - * - * @static - * @memberOf _ - * @since 2.0.0 - * @category Object - * @param {Object} object The object to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @returns {Object} Returns `object`. - * @see _.forOwn - * @example - * - * function Foo() { - * this.a = 1; - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.forOwnRight(new Foo, function(value, key) { - * console.log(key); - * }); - * // => Logs 'b' then 'a' assuming `_.forOwn` logs 'a' then 'b'. - */ - function forOwnRight(object, iteratee) { - return object && baseForOwnRight(object, getIteratee(iteratee, 3)); - } - - /** - * Creates an array of function property names from own enumerable properties - * of `object`. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Object - * @param {Object} object The object to inspect. - * @returns {Array} Returns the function names. - * @see _.functionsIn - * @example - * - * function Foo() { - * this.a = _.constant('a'); - * this.b = _.constant('b'); - * } - * - * Foo.prototype.c = _.constant('c'); - * - * _.functions(new Foo); - * // => ['a', 'b'] - */ - function functions(object) { - return object == null ? [] : baseFunctions(object, keys(object)); - } - - /** - * Creates an array of function property names from own and inherited - * enumerable properties of `object`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Object - * @param {Object} object The object to inspect. - * @returns {Array} Returns the function names. - * @see _.functions - * @example - * - * function Foo() { - * this.a = _.constant('a'); - * this.b = _.constant('b'); - * } - * - * Foo.prototype.c = _.constant('c'); - * - * _.functionsIn(new Foo); - * // => ['a', 'b', 'c'] - */ - function functionsIn(object) { - return object == null ? [] : baseFunctions(object, keysIn(object)); - } - - /** - * Gets the value at `path` of `object`. If the resolved value is - * `undefined`, the `defaultValue` is returned in its place. - * - * @static - * @memberOf _ - * @since 3.7.0 - * @category Object - * @param {Object} object The object to query. - * @param {Array|string} path The path of the property to get. - * @param {*} [defaultValue] The value returned for `undefined` resolved values. - * @returns {*} Returns the resolved value. - * @example - * - * var object = { 'a': [{ 'b': { 'c': 3 } }] }; - * - * _.get(object, 'a[0].b.c'); - * // => 3 - * - * _.get(object, ['a', '0', 'b', 'c']); - * // => 3 - * - * _.get(object, 'a.b.c', 'default'); - * // => 'default' - */ - function get(object, path, defaultValue) { - var result = object == null ? undefined : baseGet(object, path); - return result === undefined ? defaultValue : result; - } - - /** - * Checks if `path` is a direct property of `object`. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Object - * @param {Object} object The object to query. - * @param {Array|string} path The path to check. - * @returns {boolean} Returns `true` if `path` exists, else `false`. - * @example - * - * var object = { 'a': { 'b': 2 } }; - * var other = _.create({ 'a': _.create({ 'b': 2 }) }); - * - * _.has(object, 'a'); - * // => true - * - * _.has(object, 'a.b'); - * // => true - * - * _.has(object, ['a', 'b']); - * // => true - * - * _.has(other, 'a'); - * // => false - */ - function has(object, path) { - return object != null && hasPath(object, path, baseHas); - } - - /** - * Checks if `path` is a direct or inherited property of `object`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Object - * @param {Object} object The object to query. - * @param {Array|string} path The path to check. - * @returns {boolean} Returns `true` if `path` exists, else `false`. - * @example - * - * var object = _.create({ 'a': _.create({ 'b': 2 }) }); - * - * _.hasIn(object, 'a'); - * // => true - * - * _.hasIn(object, 'a.b'); - * // => true - * - * _.hasIn(object, ['a', 'b']); - * // => true - * - * _.hasIn(object, 'b'); - * // => false - */ - function hasIn(object, path) { - return object != null && hasPath(object, path, baseHasIn); - } - - /** - * Creates an object composed of the inverted keys and values of `object`. - * If `object` contains duplicate values, subsequent values overwrite - * property assignments of previous values. - * - * @static - * @memberOf _ - * @since 0.7.0 - * @category Object - * @param {Object} object The object to invert. - * @returns {Object} Returns the new inverted object. - * @example - * - * var object = { 'a': 1, 'b': 2, 'c': 1 }; - * - * _.invert(object); - * // => { '1': 'c', '2': 'b' } - */ - var invert = createInverter(function (result, value, key) { - if (value != null && typeof value.toString != 'function') { - value = nativeObjectToString.call(value); - } - result[value] = key; - }, constant(identity)); - - /** - * This method is like `_.invert` except that the inverted object is generated - * from the results of running each element of `object` thru `iteratee`. The - * corresponding inverted value of each inverted key is an array of keys - * responsible for generating the inverted value. The iteratee is invoked - * with one argument: (value). - * - * @static - * @memberOf _ - * @since 4.1.0 - * @category Object - * @param {Object} object The object to invert. - * @param {Function} [iteratee=_.identity] The iteratee invoked per element. - * @returns {Object} Returns the new inverted object. - * @example - * - * var object = { 'a': 1, 'b': 2, 'c': 1 }; - * - * _.invertBy(object); - * // => { '1': ['a', 'c'], '2': ['b'] } - * - * _.invertBy(object, function(value) { - * return 'group' + value; - * }); - * // => { 'group1': ['a', 'c'], 'group2': ['b'] } - */ - var invertBy = createInverter(function (result, value, key) { - if (value != null && typeof value.toString != 'function') { - value = nativeObjectToString.call(value); - } - if (hasOwnProperty.call(result, value)) { - result[value].push(key); - } else { - result[value] = [key]; - } - }, getIteratee); - - /** - * Invokes the method at `path` of `object`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Object - * @param {Object} object The object to query. - * @param {Array|string} path The path of the method to invoke. - * @param {...*} [args] The arguments to invoke the method with. - * @returns {*} Returns the result of the invoked method. - * @example - * - * var object = { 'a': [{ 'b': { 'c': [1, 2, 3, 4] } }] }; - * - * _.invoke(object, 'a[0].b.c.slice', 1, 3); - * // => [2, 3] - */ - var invoke = baseRest(baseInvoke); - - /** - * Creates an array of the own enumerable property names of `object`. - * - * **Note:** Non-object values are coerced to objects. See the - * [ES spec](http://ecma-international.org/ecma-262/7.0/#sec-object.keys) - * for more details. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Object - * @param {Object} object The object to query. - * @returns {Array} Returns the array of property names. - * @example - * - * function Foo() { - * this.a = 1; - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.keys(new Foo); - * // => ['a', 'b'] (iteration order is not guaranteed) - * - * _.keys('hi'); - * // => ['0', '1'] - */ - function keys(object) { - return isArrayLike(object) ? arrayLikeKeys(object) : baseKeys(object); - } - - /** - * Creates an array of the own and inherited enumerable property names of `object`. - * - * **Note:** Non-object values are coerced to objects. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Object - * @param {Object} object The object to query. - * @returns {Array} Returns the array of property names. - * @example - * - * function Foo() { - * this.a = 1; - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.keysIn(new Foo); - * // => ['a', 'b', 'c'] (iteration order is not guaranteed) - */ - function keysIn(object) { - return isArrayLike(object) ? arrayLikeKeys(object, true) : baseKeysIn(object); - } - - /** - * The opposite of `_.mapValues`; this method creates an object with the - * same values as `object` and keys generated by running each own enumerable - * string keyed property of `object` thru `iteratee`. The iteratee is invoked - * with three arguments: (value, key, object). - * - * @static - * @memberOf _ - * @since 3.8.0 - * @category Object - * @param {Object} object The object to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @returns {Object} Returns the new mapped object. - * @see _.mapValues - * @example - * - * _.mapKeys({ 'a': 1, 'b': 2 }, function(value, key) { - * return key + value; - * }); - * // => { 'a1': 1, 'b2': 2 } - */ - function mapKeys(object, iteratee) { - var result = {}; - iteratee = getIteratee(iteratee, 3); - baseForOwn(object, function (value, key, object) { - baseAssignValue(result, iteratee(value, key, object), value); - }); - return result; - } - - /** - * Creates an object with the same keys as `object` and values generated - * by running each own enumerable string keyed property of `object` thru - * `iteratee`. The iteratee is invoked with three arguments: - * (value, key, object). - * - * @static - * @memberOf _ - * @since 2.4.0 - * @category Object - * @param {Object} object The object to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @returns {Object} Returns the new mapped object. - * @see _.mapKeys - * @example - * - * var users = { - * 'fred': { 'user': 'fred', 'age': 40 }, - * 'pebbles': { 'user': 'pebbles', 'age': 1 } - * }; - * - * _.mapValues(users, function(o) { return o.age; }); - * // => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed) - * - * // The `_.property` iteratee shorthand. - * _.mapValues(users, 'age'); - * // => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed) - */ - function mapValues(object, iteratee) { - var result = {}; - iteratee = getIteratee(iteratee, 3); - baseForOwn(object, function (value, key, object) { - baseAssignValue(result, key, iteratee(value, key, object)); - }); - return result; - } - - /** - * This method is like `_.assign` except that it recursively merges own and - * inherited enumerable string keyed properties of source objects into the - * destination object. Source properties that resolve to `undefined` are - * skipped if a destination value exists. Array and plain object properties - * are merged recursively. Other objects and value types are overridden by - * assignment. Source objects are applied from left to right. Subsequent - * sources overwrite property assignments of previous sources. - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 0.5.0 - * @category Object - * @param {Object} object The destination object. - * @param {...Object} [sources] The source objects. - * @returns {Object} Returns `object`. - * @example - * - * var object = { - * 'a': [{ 'b': 2 }, { 'd': 4 }] - * }; - * - * var other = { - * 'a': [{ 'c': 3 }, { 'e': 5 }] - * }; - * - * _.merge(object, other); - * // => { 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] } - */ - var merge = createAssigner(function (object, source, srcIndex) { - baseMerge(object, source, srcIndex); - }); - - /** - * This method is like `_.merge` except that it accepts `customizer` which - * is invoked to produce the merged values of the destination and source - * properties. If `customizer` returns `undefined`, merging is handled by the - * method instead. The `customizer` is invoked with six arguments: - * (objValue, srcValue, key, object, source, stack). - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Object - * @param {Object} object The destination object. - * @param {...Object} sources The source objects. - * @param {Function} customizer The function to customize assigned values. - * @returns {Object} Returns `object`. - * @example - * - * function customizer(objValue, srcValue) { - * if (_.isArray(objValue)) { - * return objValue.concat(srcValue); - * } - * } - * - * var object = { 'a': [1], 'b': [2] }; - * var other = { 'a': [3], 'b': [4] }; - * - * _.mergeWith(object, other, customizer); - * // => { 'a': [1, 3], 'b': [2, 4] } - */ - var mergeWith = createAssigner(function (object, source, srcIndex, customizer) { - baseMerge(object, source, srcIndex, customizer); - }); - - /** - * The opposite of `_.pick`; this method creates an object composed of the - * own and inherited enumerable property paths of `object` that are not omitted. - * - * **Note:** This method is considerably slower than `_.pick`. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Object - * @param {Object} object The source object. - * @param {...(string|string[])} [paths] The property paths to omit. - * @returns {Object} Returns the new object. - * @example - * - * var object = { 'a': 1, 'b': '2', 'c': 3 }; - * - * _.omit(object, ['a', 'c']); - * // => { 'b': '2' } - */ - var omit = flatRest(function (object, paths) { - var result = {}; - if (object == null) { - return result; - } - var isDeep = false; - paths = arrayMap(paths, function (path) { - path = castPath(path, object); - isDeep || (isDeep = path.length > 1); - return path; - }); - copyObject(object, getAllKeysIn(object), result); - if (isDeep) { - result = baseClone(result, CLONE_DEEP_FLAG | CLONE_FLAT_FLAG | CLONE_SYMBOLS_FLAG, customOmitClone); - } - var length = paths.length; - while (length--) { - baseUnset(result, paths[length]); - } - return result; - }); - - /** - * The opposite of `_.pickBy`; this method creates an object composed of - * the own and inherited enumerable string keyed properties of `object` that - * `predicate` doesn't return truthy for. The predicate is invoked with two - * arguments: (value, key). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Object - * @param {Object} object The source object. - * @param {Function} [predicate=_.identity] The function invoked per property. - * @returns {Object} Returns the new object. - * @example - * - * var object = { 'a': 1, 'b': '2', 'c': 3 }; - * - * _.omitBy(object, _.isNumber); - * // => { 'b': '2' } - */ - function omitBy(object, predicate) { - return pickBy(object, negate(getIteratee(predicate))); - } - - /** - * Creates an object composed of the picked `object` properties. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Object - * @param {Object} object The source object. - * @param {...(string|string[])} [paths] The property paths to pick. - * @returns {Object} Returns the new object. - * @example - * - * var object = { 'a': 1, 'b': '2', 'c': 3 }; - * - * _.pick(object, ['a', 'c']); - * // => { 'a': 1, 'c': 3 } - */ - var pick = flatRest(function (object, paths) { - return object == null ? {} : basePick(object, paths); - }); - - /** - * Creates an object composed of the `object` properties `predicate` returns - * truthy for. The predicate is invoked with two arguments: (value, key). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Object - * @param {Object} object The source object. - * @param {Function} [predicate=_.identity] The function invoked per property. - * @returns {Object} Returns the new object. - * @example - * - * var object = { 'a': 1, 'b': '2', 'c': 3 }; - * - * _.pickBy(object, _.isNumber); - * // => { 'a': 1, 'c': 3 } - */ - function pickBy(object, predicate) { - if (object == null) { - return {}; - } - var props = arrayMap(getAllKeysIn(object), function (prop) { - return [prop]; - }); - predicate = getIteratee(predicate); - return basePickBy(object, props, function (value, path) { - return predicate(value, path[0]); - }); - } - - /** - * This method is like `_.get` except that if the resolved value is a - * function it's invoked with the `this` binding of its parent object and - * its result is returned. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Object - * @param {Object} object The object to query. - * @param {Array|string} path The path of the property to resolve. - * @param {*} [defaultValue] The value returned for `undefined` resolved values. - * @returns {*} Returns the resolved value. - * @example - * - * var object = { 'a': [{ 'b': { 'c1': 3, 'c2': _.constant(4) } }] }; - * - * _.result(object, 'a[0].b.c1'); - * // => 3 - * - * _.result(object, 'a[0].b.c2'); - * // => 4 - * - * _.result(object, 'a[0].b.c3', 'default'); - * // => 'default' - * - * _.result(object, 'a[0].b.c3', _.constant('default')); - * // => 'default' - */ - function result(object, path, defaultValue) { - path = castPath(path, object); - var index = -1, - length = path.length; - - // Ensure the loop is entered when path is empty. - if (!length) { - length = 1; - object = undefined; - } - while (++index < length) { - var value = object == null ? undefined : object[toKey(path[index])]; - if (value === undefined) { - index = length; - value = defaultValue; - } - object = isFunction(value) ? value.call(object) : value; - } - return object; - } - - /** - * Sets the value at `path` of `object`. If a portion of `path` doesn't exist, - * it's created. Arrays are created for missing index properties while objects - * are created for all other missing properties. Use `_.setWith` to customize - * `path` creation. - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 3.7.0 - * @category Object - * @param {Object} object The object to modify. - * @param {Array|string} path The path of the property to set. - * @param {*} value The value to set. - * @returns {Object} Returns `object`. - * @example - * - * var object = { 'a': [{ 'b': { 'c': 3 } }] }; - * - * _.set(object, 'a[0].b.c', 4); - * console.log(object.a[0].b.c); - * // => 4 - * - * _.set(object, ['x', '0', 'y', 'z'], 5); - * console.log(object.x[0].y.z); - * // => 5 - */ - function set(object, path, value) { - return object == null ? object : baseSet(object, path, value); - } - - /** - * This method is like `_.set` except that it accepts `customizer` which is - * invoked to produce the objects of `path`. If `customizer` returns `undefined` - * path creation is handled by the method instead. The `customizer` is invoked - * with three arguments: (nsValue, key, nsObject). - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Object - * @param {Object} object The object to modify. - * @param {Array|string} path The path of the property to set. - * @param {*} value The value to set. - * @param {Function} [customizer] The function to customize assigned values. - * @returns {Object} Returns `object`. - * @example - * - * var object = {}; - * - * _.setWith(object, '[0][1]', 'a', Object); - * // => { '0': { '1': 'a' } } - */ - function setWith(object, path, value, customizer) { - customizer = typeof customizer == 'function' ? customizer : undefined; - return object == null ? object : baseSet(object, path, value, customizer); - } - - /** - * Creates an array of own enumerable string keyed-value pairs for `object` - * which can be consumed by `_.fromPairs`. If `object` is a map or set, its - * entries are returned. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @alias entries - * @category Object - * @param {Object} object The object to query. - * @returns {Array} Returns the key-value pairs. - * @example - * - * function Foo() { - * this.a = 1; - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.toPairs(new Foo); - * // => [['a', 1], ['b', 2]] (iteration order is not guaranteed) - */ - var toPairs = createToPairs(keys); - - /** - * Creates an array of own and inherited enumerable string keyed-value pairs - * for `object` which can be consumed by `_.fromPairs`. If `object` is a map - * or set, its entries are returned. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @alias entriesIn - * @category Object - * @param {Object} object The object to query. - * @returns {Array} Returns the key-value pairs. - * @example - * - * function Foo() { - * this.a = 1; - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.toPairsIn(new Foo); - * // => [['a', 1], ['b', 2], ['c', 3]] (iteration order is not guaranteed) - */ - var toPairsIn = createToPairs(keysIn); - - /** - * An alternative to `_.reduce`; this method transforms `object` to a new - * `accumulator` object which is the result of running each of its own - * enumerable string keyed properties thru `iteratee`, with each invocation - * potentially mutating the `accumulator` object. If `accumulator` is not - * provided, a new object with the same `[[Prototype]]` will be used. The - * iteratee is invoked with four arguments: (accumulator, value, key, object). - * Iteratee functions may exit iteration early by explicitly returning `false`. - * - * @static - * @memberOf _ - * @since 1.3.0 - * @category Object - * @param {Object} object The object to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @param {*} [accumulator] The custom accumulator value. - * @returns {*} Returns the accumulated value. - * @example - * - * _.transform([2, 3, 4], function(result, n) { - * result.push(n *= n); - * return n % 2 == 0; - * }, []); - * // => [4, 9] - * - * _.transform({ 'a': 1, 'b': 2, 'c': 1 }, function(result, value, key) { - * (result[value] || (result[value] = [])).push(key); - * }, {}); - * // => { '1': ['a', 'c'], '2': ['b'] } - */ - function transform(object, iteratee, accumulator) { - var isArr = isArray(object), - isArrLike = isArr || isBuffer(object) || isTypedArray(object); - iteratee = getIteratee(iteratee, 4); - if (accumulator == null) { - var Ctor = object && object.constructor; - if (isArrLike) { - accumulator = isArr ? new Ctor() : []; - } else if (isObject(object)) { - accumulator = isFunction(Ctor) ? baseCreate(getPrototype(object)) : {}; - } else { - accumulator = {}; - } - } - (isArrLike ? arrayEach : baseForOwn)(object, function (value, index, object) { - return iteratee(accumulator, value, index, object); - }); - return accumulator; - } - - /** - * Removes the property at `path` of `object`. - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Object - * @param {Object} object The object to modify. - * @param {Array|string} path The path of the property to unset. - * @returns {boolean} Returns `true` if the property is deleted, else `false`. - * @example - * - * var object = { 'a': [{ 'b': { 'c': 7 } }] }; - * _.unset(object, 'a[0].b.c'); - * // => true - * - * console.log(object); - * // => { 'a': [{ 'b': {} }] }; - * - * _.unset(object, ['a', '0', 'b', 'c']); - * // => true - * - * console.log(object); - * // => { 'a': [{ 'b': {} }] }; - */ - function unset(object, path) { - return object == null ? true : baseUnset(object, path); - } - - /** - * This method is like `_.set` except that accepts `updater` to produce the - * value to set. Use `_.updateWith` to customize `path` creation. The `updater` - * is invoked with one argument: (value). - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 4.6.0 - * @category Object - * @param {Object} object The object to modify. - * @param {Array|string} path The path of the property to set. - * @param {Function} updater The function to produce the updated value. - * @returns {Object} Returns `object`. - * @example - * - * var object = { 'a': [{ 'b': { 'c': 3 } }] }; - * - * _.update(object, 'a[0].b.c', function(n) { return n * n; }); - * console.log(object.a[0].b.c); - * // => 9 - * - * _.update(object, 'x[0].y.z', function(n) { return n ? n + 1 : 0; }); - * console.log(object.x[0].y.z); - * // => 0 - */ - function update(object, path, updater) { - return object == null ? object : baseUpdate(object, path, castFunction(updater)); - } - - /** - * This method is like `_.update` except that it accepts `customizer` which is - * invoked to produce the objects of `path`. If `customizer` returns `undefined` - * path creation is handled by the method instead. The `customizer` is invoked - * with three arguments: (nsValue, key, nsObject). - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 4.6.0 - * @category Object - * @param {Object} object The object to modify. - * @param {Array|string} path The path of the property to set. - * @param {Function} updater The function to produce the updated value. - * @param {Function} [customizer] The function to customize assigned values. - * @returns {Object} Returns `object`. - * @example - * - * var object = {}; - * - * _.updateWith(object, '[0][1]', _.constant('a'), Object); - * // => { '0': { '1': 'a' } } - */ - function updateWith(object, path, updater, customizer) { - customizer = typeof customizer == 'function' ? customizer : undefined; - return object == null ? object : baseUpdate(object, path, castFunction(updater), customizer); - } - - /** - * Creates an array of the own enumerable string keyed property values of `object`. - * - * **Note:** Non-object values are coerced to objects. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Object - * @param {Object} object The object to query. - * @returns {Array} Returns the array of property values. - * @example - * - * function Foo() { - * this.a = 1; - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.values(new Foo); - * // => [1, 2] (iteration order is not guaranteed) - * - * _.values('hi'); - * // => ['h', 'i'] - */ - function values(object) { - return object == null ? [] : baseValues(object, keys(object)); - } - - /** - * Creates an array of the own and inherited enumerable string keyed property - * values of `object`. - * - * **Note:** Non-object values are coerced to objects. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Object - * @param {Object} object The object to query. - * @returns {Array} Returns the array of property values. - * @example - * - * function Foo() { - * this.a = 1; - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.valuesIn(new Foo); - * // => [1, 2, 3] (iteration order is not guaranteed) - */ - function valuesIn(object) { - return object == null ? [] : baseValues(object, keysIn(object)); - } - - /*------------------------------------------------------------------------*/ - - /** - * Clamps `number` within the inclusive `lower` and `upper` bounds. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Number - * @param {number} number The number to clamp. - * @param {number} [lower] The lower bound. - * @param {number} upper The upper bound. - * @returns {number} Returns the clamped number. - * @example - * - * _.clamp(-10, -5, 5); - * // => -5 - * - * _.clamp(10, -5, 5); - * // => 5 - */ - function clamp(number, lower, upper) { - if (upper === undefined) { - upper = lower; - lower = undefined; - } - if (upper !== undefined) { - upper = toNumber(upper); - upper = upper === upper ? upper : 0; - } - if (lower !== undefined) { - lower = toNumber(lower); - lower = lower === lower ? lower : 0; - } - return baseClamp(toNumber(number), lower, upper); - } - - /** - * Checks if `n` is between `start` and up to, but not including, `end`. If - * `end` is not specified, it's set to `start` with `start` then set to `0`. - * If `start` is greater than `end` the params are swapped to support - * negative ranges. - * - * @static - * @memberOf _ - * @since 3.3.0 - * @category Number - * @param {number} number The number to check. - * @param {number} [start=0] The start of the range. - * @param {number} end The end of the range. - * @returns {boolean} Returns `true` if `number` is in the range, else `false`. - * @see _.range, _.rangeRight - * @example - * - * _.inRange(3, 2, 4); - * // => true - * - * _.inRange(4, 8); - * // => true - * - * _.inRange(4, 2); - * // => false - * - * _.inRange(2, 2); - * // => false - * - * _.inRange(1.2, 2); - * // => true - * - * _.inRange(5.2, 4); - * // => false - * - * _.inRange(-3, -2, -6); - * // => true - */ - function inRange(number, start, end) { - start = toFinite(start); - if (end === undefined) { - end = start; - start = 0; - } else { - end = toFinite(end); - } - number = toNumber(number); - return baseInRange(number, start, end); - } - - /** - * Produces a random number between the inclusive `lower` and `upper` bounds. - * If only one argument is provided a number between `0` and the given number - * is returned. If `floating` is `true`, or either `lower` or `upper` are - * floats, a floating-point number is returned instead of an integer. - * - * **Note:** JavaScript follows the IEEE-754 standard for resolving - * floating-point values which can produce unexpected results. - * - * @static - * @memberOf _ - * @since 0.7.0 - * @category Number - * @param {number} [lower=0] The lower bound. - * @param {number} [upper=1] The upper bound. - * @param {boolean} [floating] Specify returning a floating-point number. - * @returns {number} Returns the random number. - * @example - * - * _.random(0, 5); - * // => an integer between 0 and 5 - * - * _.random(5); - * // => also an integer between 0 and 5 - * - * _.random(5, true); - * // => a floating-point number between 0 and 5 - * - * _.random(1.2, 5.2); - * // => a floating-point number between 1.2 and 5.2 - */ - function random(lower, upper, floating) { - if (floating && typeof floating != 'boolean' && isIterateeCall(lower, upper, floating)) { - upper = floating = undefined; - } - if (floating === undefined) { - if (typeof upper == 'boolean') { - floating = upper; - upper = undefined; - } else if (typeof lower == 'boolean') { - floating = lower; - lower = undefined; - } - } - if (lower === undefined && upper === undefined) { - lower = 0; - upper = 1; - } else { - lower = toFinite(lower); - if (upper === undefined) { - upper = lower; - lower = 0; - } else { - upper = toFinite(upper); - } - } - if (lower > upper) { - var temp = lower; - lower = upper; - upper = temp; - } - if (floating || lower % 1 || upper % 1) { - var rand = nativeRandom(); - return nativeMin(lower + rand * (upper - lower + freeParseFloat('1e-' + ((rand + '').length - 1))), upper); - } - return baseRandom(lower, upper); - } - - /*------------------------------------------------------------------------*/ - - /** - * Converts `string` to [camel case](https://en.wikipedia.org/wiki/CamelCase). - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category String - * @param {string} [string=''] The string to convert. - * @returns {string} Returns the camel cased string. - * @example - * - * _.camelCase('Foo Bar'); - * // => 'fooBar' - * - * _.camelCase('--foo-bar--'); - * // => 'fooBar' - * - * _.camelCase('__FOO_BAR__'); - * // => 'fooBar' - */ - var camelCase = createCompounder(function (result, word, index) { - word = word.toLowerCase(); - return result + (index ? capitalize(word) : word); - }); - - /** - * Converts the first character of `string` to upper case and the remaining - * to lower case. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category String - * @param {string} [string=''] The string to capitalize. - * @returns {string} Returns the capitalized string. - * @example - * - * _.capitalize('FRED'); - * // => 'Fred' - */ - function capitalize(string) { - return upperFirst(toString(string).toLowerCase()); - } - - /** - * Deburrs `string` by converting - * [Latin-1 Supplement](https://en.wikipedia.org/wiki/Latin-1_Supplement_(Unicode_block)#Character_table) - * and [Latin Extended-A](https://en.wikipedia.org/wiki/Latin_Extended-A) - * letters to basic Latin letters and removing - * [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks). - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category String - * @param {string} [string=''] The string to deburr. - * @returns {string} Returns the deburred string. - * @example - * - * _.deburr('déjà vu'); - * // => 'deja vu' - */ - function deburr(string) { - string = toString(string); - return string && string.replace(reLatin, deburrLetter).replace(reComboMark, ''); - } - - /** - * Checks if `string` ends with the given target string. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category String - * @param {string} [string=''] The string to inspect. - * @param {string} [target] The string to search for. - * @param {number} [position=string.length] The position to search up to. - * @returns {boolean} Returns `true` if `string` ends with `target`, - * else `false`. - * @example - * - * _.endsWith('abc', 'c'); - * // => true - * - * _.endsWith('abc', 'b'); - * // => false - * - * _.endsWith('abc', 'b', 2); - * // => true - */ - function endsWith(string, target, position) { - string = toString(string); - target = baseToString(target); - var length = string.length; - position = position === undefined ? length : baseClamp(toInteger(position), 0, length); - var end = position; - position -= target.length; - return position >= 0 && string.slice(position, end) == target; - } - - /** - * Converts the characters "&", "<", ">", '"', and "'" in `string` to their - * corresponding HTML entities. - * - * **Note:** No other characters are escaped. To escape additional - * characters use a third-party library like [_he_](https://mths.be/he). - * - * Though the ">" character is escaped for symmetry, characters like - * ">" and "/" don't need escaping in HTML and have no special meaning - * unless they're part of a tag or unquoted attribute value. See - * [Mathias Bynens's article](https://mathiasbynens.be/notes/ambiguous-ampersands) - * (under "semi-related fun fact") for more details. - * - * When working with HTML you should always - * [quote attribute values](http://wonko.com/post/html-escaping) to reduce - * XSS vectors. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category String - * @param {string} [string=''] The string to escape. - * @returns {string} Returns the escaped string. - * @example - * - * _.escape('fred, barney, & pebbles'); - * // => 'fred, barney, & pebbles' - */ - function escape(string) { - string = toString(string); - return string && reHasUnescapedHtml.test(string) ? string.replace(reUnescapedHtml, escapeHtmlChar) : string; - } - - /** - * Escapes the `RegExp` special characters "^", "$", "\", ".", "*", "+", - * "?", "(", ")", "[", "]", "{", "}", and "|" in `string`. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category String - * @param {string} [string=''] The string to escape. - * @returns {string} Returns the escaped string. - * @example - * - * _.escapeRegExp('[lodash](https://lodash.com/)'); - * // => '\[lodash\]\(https://lodash\.com/\)' - */ - function escapeRegExp(string) { - string = toString(string); - return string && reHasRegExpChar.test(string) ? string.replace(reRegExpChar, '\\$&') : string; - } - - /** - * Converts `string` to - * [kebab case](https://en.wikipedia.org/wiki/Letter_case#Special_case_styles). - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category String - * @param {string} [string=''] The string to convert. - * @returns {string} Returns the kebab cased string. - * @example - * - * _.kebabCase('Foo Bar'); - * // => 'foo-bar' - * - * _.kebabCase('fooBar'); - * // => 'foo-bar' - * - * _.kebabCase('__FOO_BAR__'); - * // => 'foo-bar' - */ - var kebabCase = createCompounder(function (result, word, index) { - return result + (index ? '-' : '') + word.toLowerCase(); - }); - - /** - * Converts `string`, as space separated words, to lower case. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category String - * @param {string} [string=''] The string to convert. - * @returns {string} Returns the lower cased string. - * @example - * - * _.lowerCase('--Foo-Bar--'); - * // => 'foo bar' - * - * _.lowerCase('fooBar'); - * // => 'foo bar' - * - * _.lowerCase('__FOO_BAR__'); - * // => 'foo bar' - */ - var lowerCase = createCompounder(function (result, word, index) { - return result + (index ? ' ' : '') + word.toLowerCase(); - }); - - /** - * Converts the first character of `string` to lower case. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category String - * @param {string} [string=''] The string to convert. - * @returns {string} Returns the converted string. - * @example - * - * _.lowerFirst('Fred'); - * // => 'fred' - * - * _.lowerFirst('FRED'); - * // => 'fRED' - */ - var lowerFirst = createCaseFirst('toLowerCase'); - - /** - * Pads `string` on the left and right sides if it's shorter than `length`. - * Padding characters are truncated if they can't be evenly divided by `length`. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category String - * @param {string} [string=''] The string to pad. - * @param {number} [length=0] The padding length. - * @param {string} [chars=' '] The string used as padding. - * @returns {string} Returns the padded string. - * @example - * - * _.pad('abc', 8); - * // => ' abc ' - * - * _.pad('abc', 8, '_-'); - * // => '_-abc_-_' - * - * _.pad('abc', 3); - * // => 'abc' - */ - function pad(string, length, chars) { - string = toString(string); - length = toInteger(length); - var strLength = length ? stringSize(string) : 0; - if (!length || strLength >= length) { - return string; - } - var mid = (length - strLength) / 2; - return createPadding(nativeFloor(mid), chars) + string + createPadding(nativeCeil(mid), chars); - } - - /** - * Pads `string` on the right side if it's shorter than `length`. Padding - * characters are truncated if they exceed `length`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category String - * @param {string} [string=''] The string to pad. - * @param {number} [length=0] The padding length. - * @param {string} [chars=' '] The string used as padding. - * @returns {string} Returns the padded string. - * @example - * - * _.padEnd('abc', 6); - * // => 'abc ' - * - * _.padEnd('abc', 6, '_-'); - * // => 'abc_-_' - * - * _.padEnd('abc', 3); - * // => 'abc' - */ - function padEnd(string, length, chars) { - string = toString(string); - length = toInteger(length); - var strLength = length ? stringSize(string) : 0; - return length && strLength < length ? string + createPadding(length - strLength, chars) : string; - } - - /** - * Pads `string` on the left side if it's shorter than `length`. Padding - * characters are truncated if they exceed `length`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category String - * @param {string} [string=''] The string to pad. - * @param {number} [length=0] The padding length. - * @param {string} [chars=' '] The string used as padding. - * @returns {string} Returns the padded string. - * @example - * - * _.padStart('abc', 6); - * // => ' abc' - * - * _.padStart('abc', 6, '_-'); - * // => '_-_abc' - * - * _.padStart('abc', 3); - * // => 'abc' - */ - function padStart(string, length, chars) { - string = toString(string); - length = toInteger(length); - var strLength = length ? stringSize(string) : 0; - return length && strLength < length ? createPadding(length - strLength, chars) + string : string; - } - - /** - * Converts `string` to an integer of the specified radix. If `radix` is - * `undefined` or `0`, a `radix` of `10` is used unless `value` is a - * hexadecimal, in which case a `radix` of `16` is used. - * - * **Note:** This method aligns with the - * [ES5 implementation](https://es5.github.io/#x15.1.2.2) of `parseInt`. - * - * @static - * @memberOf _ - * @since 1.1.0 - * @category String - * @param {string} string The string to convert. - * @param {number} [radix=10] The radix to interpret `value` by. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {number} Returns the converted integer. - * @example - * - * _.parseInt('08'); - * // => 8 - * - * _.map(['6', '08', '10'], _.parseInt); - * // => [6, 8, 10] - */ - function parseInt(string, radix, guard) { - if (guard || radix == null) { - radix = 0; - } else if (radix) { - radix = +radix; - } - return nativeParseInt(toString(string).replace(reTrimStart, ''), radix || 0); - } - - /** - * Repeats the given string `n` times. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category String - * @param {string} [string=''] The string to repeat. - * @param {number} [n=1] The number of times to repeat the string. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {string} Returns the repeated string. - * @example - * - * _.repeat('*', 3); - * // => '***' - * - * _.repeat('abc', 2); - * // => 'abcabc' - * - * _.repeat('abc', 0); - * // => '' - */ - function repeat(string, n, guard) { - if (guard ? isIterateeCall(string, n, guard) : n === undefined) { - n = 1; - } else { - n = toInteger(n); - } - return baseRepeat(toString(string), n); - } - - /** - * Replaces matches for `pattern` in `string` with `replacement`. - * - * **Note:** This method is based on - * [`String#replace`](https://mdn.io/String/replace). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category String - * @param {string} [string=''] The string to modify. - * @param {RegExp|string} pattern The pattern to replace. - * @param {Function|string} replacement The match replacement. - * @returns {string} Returns the modified string. - * @example - * - * _.replace('Hi Fred', 'Fred', 'Barney'); - * // => 'Hi Barney' - */ - function replace() { - var args = arguments, - string = toString(args[0]); - return args.length < 3 ? string : string.replace(args[1], args[2]); - } - - /** - * Converts `string` to - * [snake case](https://en.wikipedia.org/wiki/Snake_case). - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category String - * @param {string} [string=''] The string to convert. - * @returns {string} Returns the snake cased string. - * @example - * - * _.snakeCase('Foo Bar'); - * // => 'foo_bar' - * - * _.snakeCase('fooBar'); - * // => 'foo_bar' - * - * _.snakeCase('--FOO-BAR--'); - * // => 'foo_bar' - */ - var snakeCase = createCompounder(function (result, word, index) { - return result + (index ? '_' : '') + word.toLowerCase(); - }); - - /** - * Splits `string` by `separator`. - * - * **Note:** This method is based on - * [`String#split`](https://mdn.io/String/split). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category String - * @param {string} [string=''] The string to split. - * @param {RegExp|string} separator The separator pattern to split by. - * @param {number} [limit] The length to truncate results to. - * @returns {Array} Returns the string segments. - * @example - * - * _.split('a-b-c', '-', 2); - * // => ['a', 'b'] - */ - function split(string, separator, limit) { - if (limit && typeof limit != 'number' && isIterateeCall(string, separator, limit)) { - separator = limit = undefined; - } - limit = limit === undefined ? MAX_ARRAY_LENGTH : limit >>> 0; - if (!limit) { - return []; - } - string = toString(string); - if (string && (typeof separator == 'string' || separator != null && !isRegExp(separator))) { - separator = baseToString(separator); - if (!separator && hasUnicode(string)) { - return castSlice(stringToArray(string), 0, limit); - } - } - return string.split(separator, limit); - } - - /** - * Converts `string` to - * [start case](https://en.wikipedia.org/wiki/Letter_case#Stylistic_or_specialised_usage). - * - * @static - * @memberOf _ - * @since 3.1.0 - * @category String - * @param {string} [string=''] The string to convert. - * @returns {string} Returns the start cased string. - * @example - * - * _.startCase('--foo-bar--'); - * // => 'Foo Bar' - * - * _.startCase('fooBar'); - * // => 'Foo Bar' - * - * _.startCase('__FOO_BAR__'); - * // => 'FOO BAR' - */ - var startCase = createCompounder(function (result, word, index) { - return result + (index ? ' ' : '') + upperFirst(word); - }); - - /** - * Checks if `string` starts with the given target string. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category String - * @param {string} [string=''] The string to inspect. - * @param {string} [target] The string to search for. - * @param {number} [position=0] The position to search from. - * @returns {boolean} Returns `true` if `string` starts with `target`, - * else `false`. - * @example - * - * _.startsWith('abc', 'a'); - * // => true - * - * _.startsWith('abc', 'b'); - * // => false - * - * _.startsWith('abc', 'b', 1); - * // => true - */ - function startsWith(string, target, position) { - string = toString(string); - position = position == null ? 0 : baseClamp(toInteger(position), 0, string.length); - target = baseToString(target); - return string.slice(position, position + target.length) == target; - } - - /** - * Creates a compiled template function that can interpolate data properties - * in "interpolate" delimiters, HTML-escape interpolated data properties in - * "escape" delimiters, and execute JavaScript in "evaluate" delimiters. Data - * properties may be accessed as free variables in the template. If a setting - * object is given, it takes precedence over `_.templateSettings` values. - * - * **Note:** In the development build `_.template` utilizes - * [sourceURLs](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl) - * for easier debugging. - * - * For more information on precompiling templates see - * [lodash's custom builds documentation](https://lodash.com/custom-builds). - * - * For more information on Chrome extension sandboxes see - * [Chrome's extensions documentation](https://developer.chrome.com/extensions/sandboxingEval). - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category String - * @param {string} [string=''] The template string. - * @param {Object} [options={}] The options object. - * @param {RegExp} [options.escape=_.templateSettings.escape] - * The HTML "escape" delimiter. - * @param {RegExp} [options.evaluate=_.templateSettings.evaluate] - * The "evaluate" delimiter. - * @param {Object} [options.imports=_.templateSettings.imports] - * An object to import into the template as free variables. - * @param {RegExp} [options.interpolate=_.templateSettings.interpolate] - * The "interpolate" delimiter. - * @param {string} [options.sourceURL='lodash.templateSources[n]'] - * The sourceURL of the compiled template. - * @param {string} [options.variable='obj'] - * The data object variable name. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {Function} Returns the compiled template function. - * @example - * - * // Use the "interpolate" delimiter to create a compiled template. - * var compiled = _.template('hello <%= user %>!'); - * compiled({ 'user': 'fred' }); - * // => 'hello fred!' - * - * // Use the HTML "escape" delimiter to escape data property values. - * var compiled = _.template('<%- value %>'); - * compiled({ 'value': '") + Markup('<script>alert(document.cookie);</script>') + + >>> # wrap in Markup to mark text "safe" and prevent escaping + >>> Markup("Hello") + Markup('hello') + + >>> escape(Markup("Hello")) + Markup('hello') + + >>> # Markup is a str subclass + >>> # methods and operators escape their arguments + >>> template = Markup("Hello {name}") + >>> template.format(name='"World"') + Markup('Hello "World"') + + +Donate +------ + +The Pallets organization develops and supports MarkupSafe and other +popular packages. In order to grow the community of contributors and +users, and allow the maintainers to devote more time to the projects, +`please donate today`_. + +.. _please donate today: https://palletsprojects.com/donate + + +Links +----- + +- Documentation: https://markupsafe.palletsprojects.com/ +- Changes: https://markupsafe.palletsprojects.com/changes/ +- PyPI Releases: https://pypi.org/project/MarkupSafe/ +- Source Code: https://github.com/pallets/markupsafe/ +- Issue Tracker: https://github.com/pallets/markupsafe/issues/ +- Chat: https://discord.gg/pallets diff --git a/lib/go-jinja2/internal/data/darwin-amd64/MarkupSafe-2.1.5.dist-info/RECORD b/lib/go-jinja2/internal/data/darwin-amd64/MarkupSafe-2.1.5.dist-info/RECORD new file mode 100644 index 000000000..3ef30fccc --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/MarkupSafe-2.1.5.dist-info/RECORD @@ -0,0 +1,15 @@ +MarkupSafe-2.1.5.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +MarkupSafe-2.1.5.dist-info/LICENSE.rst,sha256=SJqOEQhQntmKN7uYPhHg9-HTHwvY-Zp5yESOf_N9B-o,1475 +MarkupSafe-2.1.5.dist-info/METADATA,sha256=2dRDPam6OZLfpX0wg1JN5P3u9arqACxVSfdGmsJU7o8,3003 +MarkupSafe-2.1.5.dist-info/RECORD,, +MarkupSafe-2.1.5.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +MarkupSafe-2.1.5.dist-info/WHEEL,sha256=2rnFQKN1y4ODxAzmbuA5Z6aEng-PoJTYpqGpnJDGdxw,111 +MarkupSafe-2.1.5.dist-info/top_level.txt,sha256=qy0Plje5IJuvsCBjejJyhDCjEAdcDLK_2agVcex8Z6U,11 +markupsafe/__init__.py,sha256=r7VOTjUq7EMQ4v3p4R1LoVOGJg6ysfYRncLr34laRBs,10958 +markupsafe/__pycache__/__init__.cpython-311.pyc,, +markupsafe/__pycache__/_native.cpython-311.pyc,, +markupsafe/_native.py,sha256=GR86Qvo_GcgKmKreA1WmYN9ud17OFwkww8E-fiW-57s,1713 +markupsafe/_speedups.c,sha256=X2XvQVtIdcK4Usz70BvkzoOfjTCmQlDkkjYSn-swE0g,7083 +markupsafe/_speedups.cpython-311-darwin.so,sha256=dLtLNhRcybjKGMYMx94fHSTTAPgtYoW9L06TV79sZMs,35272 +markupsafe/_speedups.pyi,sha256=vfMCsOgbAXRNLUXkyuyonG8uEWKYU4PDqNuMaDELAYw,229 +markupsafe/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 diff --git a/lib/go-jinja2/internal/data/darwin-amd64/MarkupSafe-2.1.5.dist-info/REQUESTED b/lib/go-jinja2/internal/data/darwin-amd64/MarkupSafe-2.1.5.dist-info/REQUESTED new file mode 100644 index 000000000..e69de29bb diff --git a/lib/go-jinja2/internal/data/darwin-amd64/MarkupSafe-2.1.5.dist-info/WHEEL b/lib/go-jinja2/internal/data/darwin-amd64/MarkupSafe-2.1.5.dist-info/WHEEL new file mode 100644 index 000000000..a88b33b1b --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/MarkupSafe-2.1.5.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.42.0) +Root-Is-Purelib: false +Tag: cp311-cp311-macosx_10_9_x86_64 + diff --git a/lib/go-jinja2/internal/data/darwin-amd64/MarkupSafe-2.1.5.dist-info/top_level.txt b/lib/go-jinja2/internal/data/darwin-amd64/MarkupSafe-2.1.5.dist-info/top_level.txt new file mode 100644 index 000000000..75bf72925 --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/MarkupSafe-2.1.5.dist-info/top_level.txt @@ -0,0 +1 @@ +markupsafe diff --git a/lib/go-jinja2/internal/data/darwin-amd64/PyYAML-6.0.1.dist-info/INSTALLER b/lib/go-jinja2/internal/data/darwin-amd64/PyYAML-6.0.1.dist-info/INSTALLER new file mode 100644 index 000000000..a1b589e38 --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/PyYAML-6.0.1.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/lib/go-jinja2/internal/data/darwin-amd64/PyYAML-6.0.1.dist-info/LICENSE b/lib/go-jinja2/internal/data/darwin-amd64/PyYAML-6.0.1.dist-info/LICENSE new file mode 100644 index 000000000..2f1b8e15e --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/PyYAML-6.0.1.dist-info/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2017-2021 Ingy döt Net +Copyright (c) 2006-2016 Kirill Simonov + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lib/go-jinja2/internal/data/darwin-amd64/PyYAML-6.0.1.dist-info/METADATA b/lib/go-jinja2/internal/data/darwin-amd64/PyYAML-6.0.1.dist-info/METADATA new file mode 100644 index 000000000..c8905983e --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/PyYAML-6.0.1.dist-info/METADATA @@ -0,0 +1,46 @@ +Metadata-Version: 2.1 +Name: PyYAML +Version: 6.0.1 +Summary: YAML parser and emitter for Python +Home-page: https://pyyaml.org/ +Download-URL: https://pypi.org/project/PyYAML/ +Author: Kirill Simonov +Author-email: xi@resolvent.net +License: MIT +Project-URL: Bug Tracker, https://github.com/yaml/pyyaml/issues +Project-URL: CI, https://github.com/yaml/pyyaml/actions +Project-URL: Documentation, https://pyyaml.org/wiki/PyYAMLDocumentation +Project-URL: Mailing lists, http://lists.sourceforge.net/lists/listinfo/yaml-core +Project-URL: Source Code, https://github.com/yaml/pyyaml +Platform: Any +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Cython +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: Text Processing :: Markup +Requires-Python: >=3.6 +License-File: LICENSE + +YAML is a data serialization format designed for human readability +and interaction with scripting languages. PyYAML is a YAML parser +and emitter for Python. + +PyYAML features a complete YAML 1.1 parser, Unicode support, pickle +support, capable extension API, and sensible error messages. PyYAML +supports standard YAML tags and provides Python-specific tags that +allow to represent an arbitrary Python object. + +PyYAML is applicable for a broad range of tasks from complex +configuration files to object serialization and persistence. diff --git a/lib/go-jinja2/internal/data/darwin-amd64/PyYAML-6.0.1.dist-info/RECORD b/lib/go-jinja2/internal/data/darwin-amd64/PyYAML-6.0.1.dist-info/RECORD new file mode 100644 index 000000000..88258ef4b --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/PyYAML-6.0.1.dist-info/RECORD @@ -0,0 +1,44 @@ +PyYAML-6.0.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +PyYAML-6.0.1.dist-info/LICENSE,sha256=jTko-dxEkP1jVwfLiOsmvXZBAqcoKVQwfT5RZ6V36KQ,1101 +PyYAML-6.0.1.dist-info/METADATA,sha256=UNNF8-SzzwOKXVo-kV5lXUGH2_wDWMBmGxqISpp5HQk,2058 +PyYAML-6.0.1.dist-info/RECORD,, +PyYAML-6.0.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +PyYAML-6.0.1.dist-info/WHEEL,sha256=zATDCBMA0NrnUw5OGrN7taAN4ImJV7kn5PkKRuCgN5Q,111 +PyYAML-6.0.1.dist-info/top_level.txt,sha256=rpj0IVMTisAjh_1vG3Ccf9v5jpCQwAz6cD1IVU5ZdhQ,11 +_yaml/__init__.py,sha256=04Ae_5osxahpJHa3XBZUAf4wi6XX32gR8D6X6p64GEA,1402 +_yaml/__pycache__/__init__.cpython-311.pyc,, +yaml/__init__.py,sha256=bhl05qSeO-1ZxlSRjGrvl2m9nrXb1n9-GQatTN0Mrqc,12311 +yaml/__pycache__/__init__.cpython-311.pyc,, +yaml/__pycache__/composer.cpython-311.pyc,, +yaml/__pycache__/constructor.cpython-311.pyc,, +yaml/__pycache__/cyaml.cpython-311.pyc,, +yaml/__pycache__/dumper.cpython-311.pyc,, +yaml/__pycache__/emitter.cpython-311.pyc,, +yaml/__pycache__/error.cpython-311.pyc,, +yaml/__pycache__/events.cpython-311.pyc,, +yaml/__pycache__/loader.cpython-311.pyc,, +yaml/__pycache__/nodes.cpython-311.pyc,, +yaml/__pycache__/parser.cpython-311.pyc,, +yaml/__pycache__/reader.cpython-311.pyc,, +yaml/__pycache__/representer.cpython-311.pyc,, +yaml/__pycache__/resolver.cpython-311.pyc,, +yaml/__pycache__/scanner.cpython-311.pyc,, +yaml/__pycache__/serializer.cpython-311.pyc,, +yaml/__pycache__/tokens.cpython-311.pyc,, +yaml/_yaml.cpython-311-darwin.so,sha256=9rqdpMeZVNxWDZ56UEFXdbczGQdHw8YHXxJkbZSz9_M,429464 +yaml/composer.py,sha256=_Ko30Wr6eDWUeUpauUGT3Lcg9QPBnOPVlTnIMRGJ9FM,4883 +yaml/constructor.py,sha256=kNgkfaeLUkwQYY_Q6Ff1Tz2XVw_pG1xVE9Ak7z-viLA,28639 +yaml/cyaml.py,sha256=6ZrAG9fAYvdVe2FK_w0hmXoG7ZYsoYUwapG8CiC72H0,3851 +yaml/dumper.py,sha256=PLctZlYwZLp7XmeUdwRuv4nYOZ2UBnDIUy8-lKfLF-o,2837 +yaml/emitter.py,sha256=jghtaU7eFwg31bG0B7RZea_29Adi9CKmXq_QjgQpCkQ,43006 +yaml/error.py,sha256=Ah9z-toHJUbE9j-M8YpxgSRM5CgLCcwVzJgLLRF2Fxo,2533 +yaml/events.py,sha256=50_TksgQiE4up-lKo_V-nBy-tAIxkIPQxY5qDhKCeHw,2445 +yaml/loader.py,sha256=UVa-zIqmkFSCIYq_PgSGm4NSJttHY2Rf_zQ4_b1fHN0,2061 +yaml/nodes.py,sha256=gPKNj8pKCdh2d4gr3gIYINnPOaOxGhJAUiYhGRnPE84,1440 +yaml/parser.py,sha256=ilWp5vvgoHFGzvOZDItFoGjD6D42nhlZrZyjAwa0oJo,25495 +yaml/reader.py,sha256=0dmzirOiDG4Xo41RnuQS7K9rkY3xjHiVasfDMNTqCNw,6794 +yaml/representer.py,sha256=IuWP-cAW9sHKEnS0gCqSa894k1Bg4cgTxaDwIcbRQ-Y,14190 +yaml/resolver.py,sha256=9L-VYfm4mWHxUD1Vg4X7rjDRK_7VZd6b92wzq7Y2IKY,9004 +yaml/scanner.py,sha256=YEM3iLZSaQwXcQRg2l2R4MdT0zGP2F9eHkKGKnHyWQY,51279 +yaml/serializer.py,sha256=ChuFgmhU01hj4xgI8GaKv6vfM2Bujwa9i7d2FAHj7cA,4165 +yaml/tokens.py,sha256=lTQIzSVw8Mg9wv459-TjiOQe6wVziqaRlqX2_89rp54,2573 diff --git a/lib/go-jinja2/internal/data/darwin-amd64/PyYAML-6.0.1.dist-info/REQUESTED b/lib/go-jinja2/internal/data/darwin-amd64/PyYAML-6.0.1.dist-info/REQUESTED new file mode 100644 index 000000000..e69de29bb diff --git a/lib/go-jinja2/internal/data/darwin-amd64/PyYAML-6.0.1.dist-info/WHEEL b/lib/go-jinja2/internal/data/darwin-amd64/PyYAML-6.0.1.dist-info/WHEEL new file mode 100644 index 000000000..b77285b61 --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/PyYAML-6.0.1.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.40.0) +Root-Is-Purelib: false +Tag: cp311-cp311-macosx_10_9_x86_64 + diff --git a/lib/go-jinja2/internal/data/darwin-amd64/PyYAML-6.0.1.dist-info/top_level.txt b/lib/go-jinja2/internal/data/darwin-amd64/PyYAML-6.0.1.dist-info/top_level.txt new file mode 100644 index 000000000..e6475e911 --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/PyYAML-6.0.1.dist-info/top_level.txt @@ -0,0 +1,2 @@ +_yaml +yaml diff --git a/lib/go-jinja2/internal/data/darwin-amd64/_yaml/__init__.py b/lib/go-jinja2/internal/data/darwin-amd64/_yaml/__init__.py new file mode 100644 index 000000000..7baa8c4b6 --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/_yaml/__init__.py @@ -0,0 +1,33 @@ +# This is a stub package designed to roughly emulate the _yaml +# extension module, which previously existed as a standalone module +# and has been moved into the `yaml` package namespace. +# It does not perfectly mimic its old counterpart, but should get +# close enough for anyone who's relying on it even when they shouldn't. +import yaml + +# in some circumstances, the yaml module we imoprted may be from a different version, so we need +# to tread carefully when poking at it here (it may not have the attributes we expect) +if not getattr(yaml, '__with_libyaml__', False): + from sys import version_info + + exc = ModuleNotFoundError if version_info >= (3, 6) else ImportError + raise exc("No module named '_yaml'") +else: + from yaml._yaml import * + import warnings + warnings.warn( + 'The _yaml extension module is now located at yaml._yaml' + ' and its location is subject to change. To use the' + ' LibYAML-based parser and emitter, import from `yaml`:' + ' `from yaml import CLoader as Loader, CDumper as Dumper`.', + DeprecationWarning + ) + del warnings + # Don't `del yaml` here because yaml is actually an existing + # namespace member of _yaml. + +__name__ = '_yaml' +# If the module is top-level (i.e. not a part of any specific package) +# then the attribute should be set to ''. +# https://docs.python.org/3.8/library/types.html +__package__ = '' diff --git a/lib/go-jinja2/internal/data/darwin-amd64/bin/jsonpath_ng b/lib/go-jinja2/internal/data/darwin-amd64/bin/jsonpath_ng new file mode 100644 index 000000000..28a424903 --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/bin/jsonpath_ng @@ -0,0 +1,8 @@ +#!/tmp/python-pip-darwin-amd64/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from jsonpath_ng.bin.jsonpath import entry_point +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(entry_point()) diff --git a/lib/go-jinja2/internal/data/darwin-amd64/bin/slugify b/lib/go-jinja2/internal/data/darwin-amd64/bin/slugify new file mode 100644 index 000000000..bc16b756e --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/bin/slugify @@ -0,0 +1,8 @@ +#!/tmp/python-pip-darwin-amd64/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from slugify.__main__ import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/lib/go-jinja2/internal/data/darwin-amd64/click-8.1.7.dist-info/INSTALLER b/lib/go-jinja2/internal/data/darwin-amd64/click-8.1.7.dist-info/INSTALLER new file mode 100644 index 000000000..a1b589e38 --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/click-8.1.7.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/lib/go-jinja2/internal/data/darwin-amd64/click-8.1.7.dist-info/LICENSE.rst b/lib/go-jinja2/internal/data/darwin-amd64/click-8.1.7.dist-info/LICENSE.rst new file mode 100644 index 000000000..d12a84918 --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/click-8.1.7.dist-info/LICENSE.rst @@ -0,0 +1,28 @@ +Copyright 2014 Pallets + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/lib/go-jinja2/internal/data/darwin-amd64/click-8.1.7.dist-info/METADATA b/lib/go-jinja2/internal/data/darwin-amd64/click-8.1.7.dist-info/METADATA new file mode 100644 index 000000000..7a6bbb24b --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/click-8.1.7.dist-info/METADATA @@ -0,0 +1,103 @@ +Metadata-Version: 2.1 +Name: click +Version: 8.1.7 +Summary: Composable command line interface toolkit +Home-page: https://palletsprojects.com/p/click/ +Maintainer: Pallets +Maintainer-email: contact@palletsprojects.com +License: BSD-3-Clause +Project-URL: Donate, https://palletsprojects.com/donate +Project-URL: Documentation, https://click.palletsprojects.com/ +Project-URL: Changes, https://click.palletsprojects.com/changes/ +Project-URL: Source Code, https://github.com/pallets/click/ +Project-URL: Issue Tracker, https://github.com/pallets/click/issues/ +Project-URL: Chat, https://discord.gg/pallets +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Requires-Python: >=3.7 +Description-Content-Type: text/x-rst +License-File: LICENSE.rst +Requires-Dist: colorama ; platform_system == "Windows" +Requires-Dist: importlib-metadata ; python_version < "3.8" + +\$ click\_ +========== + +Click is a Python package for creating beautiful command line interfaces +in a composable way with as little code as necessary. It's the "Command +Line Interface Creation Kit". It's highly configurable but comes with +sensible defaults out of the box. + +It aims to make the process of writing command line tools quick and fun +while also preventing any frustration caused by the inability to +implement an intended CLI API. + +Click in three points: + +- Arbitrary nesting of commands +- Automatic help page generation +- Supports lazy loading of subcommands at runtime + + +Installing +---------- + +Install and update using `pip`_: + +.. code-block:: text + + $ pip install -U click + +.. _pip: https://pip.pypa.io/en/stable/getting-started/ + + +A Simple Example +---------------- + +.. code-block:: python + + import click + + @click.command() + @click.option("--count", default=1, help="Number of greetings.") + @click.option("--name", prompt="Your name", help="The person to greet.") + def hello(count, name): + """Simple program that greets NAME for a total of COUNT times.""" + for _ in range(count): + click.echo(f"Hello, {name}!") + + if __name__ == '__main__': + hello() + +.. code-block:: text + + $ python hello.py --count=3 + Your name: Click + Hello, Click! + Hello, Click! + Hello, Click! + + +Donate +------ + +The Pallets organization develops and supports Click and other popular +packages. In order to grow the community of contributors and users, and +allow the maintainers to devote more time to the projects, `please +donate today`_. + +.. _please donate today: https://palletsprojects.com/donate + + +Links +----- + +- Documentation: https://click.palletsprojects.com/ +- Changes: https://click.palletsprojects.com/changes/ +- PyPI Releases: https://pypi.org/project/click/ +- Source Code: https://github.com/pallets/click +- Issue Tracker: https://github.com/pallets/click/issues +- Chat: https://discord.gg/pallets diff --git a/lib/go-jinja2/internal/data/darwin-amd64/click-8.1.7.dist-info/RECORD b/lib/go-jinja2/internal/data/darwin-amd64/click-8.1.7.dist-info/RECORD new file mode 100644 index 000000000..dcffbf004 --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/click-8.1.7.dist-info/RECORD @@ -0,0 +1,40 @@ +click-8.1.7.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +click-8.1.7.dist-info/LICENSE.rst,sha256=morRBqOU6FO_4h9C9OctWSgZoigF2ZG18ydQKSkrZY0,1475 +click-8.1.7.dist-info/METADATA,sha256=qIMevCxGA9yEmJOM_4WHuUJCwWpsIEVbCPOhs45YPN4,3014 +click-8.1.7.dist-info/RECORD,, +click-8.1.7.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +click-8.1.7.dist-info/WHEEL,sha256=5sUXSg9e4bi7lTLOHcm6QEYwO5TIF1TNbTSVFVjcJcc,92 +click-8.1.7.dist-info/top_level.txt,sha256=J1ZQogalYS4pphY_lPECoNMfw0HzTSrZglC4Yfwo4xA,6 +click/__init__.py,sha256=YDDbjm406dTOA0V8bTtdGnhN7zj5j-_dFRewZF_pLvw,3138 +click/__pycache__/__init__.cpython-311.pyc,, +click/__pycache__/_compat.cpython-311.pyc,, +click/__pycache__/_termui_impl.cpython-311.pyc,, +click/__pycache__/_textwrap.cpython-311.pyc,, +click/__pycache__/_winconsole.cpython-311.pyc,, +click/__pycache__/core.cpython-311.pyc,, +click/__pycache__/decorators.cpython-311.pyc,, +click/__pycache__/exceptions.cpython-311.pyc,, +click/__pycache__/formatting.cpython-311.pyc,, +click/__pycache__/globals.cpython-311.pyc,, +click/__pycache__/parser.cpython-311.pyc,, +click/__pycache__/shell_completion.cpython-311.pyc,, +click/__pycache__/termui.cpython-311.pyc,, +click/__pycache__/testing.cpython-311.pyc,, +click/__pycache__/types.cpython-311.pyc,, +click/__pycache__/utils.cpython-311.pyc,, +click/_compat.py,sha256=5318agQpbt4kroKsbqDOYpTSWzL_YCZVUQiTT04yXmc,18744 +click/_termui_impl.py,sha256=3dFYv4445Nw-rFvZOTBMBPYwB1bxnmNk9Du6Dm_oBSU,24069 +click/_textwrap.py,sha256=10fQ64OcBUMuK7mFvh8363_uoOxPlRItZBmKzRJDgoY,1353 +click/_winconsole.py,sha256=5ju3jQkcZD0W27WEMGqmEP4y_crUVzPCqsX_FYb7BO0,7860 +click/core.py,sha256=j6oEWtGgGna8JarD6WxhXmNnxLnfRjwXglbBc-8jr7U,114086 +click/decorators.py,sha256=-ZlbGYgV-oI8jr_oH4RpuL1PFS-5QmeuEAsLDAYgxtw,18719 +click/exceptions.py,sha256=fyROO-47HWFDjt2qupo7A3J32VlpM-ovJnfowu92K3s,9273 +click/formatting.py,sha256=Frf0-5W33-loyY_i9qrwXR8-STnW3m5gvyxLVUdyxyk,9706 +click/globals.py,sha256=TP-qM88STzc7f127h35TD_v920FgfOD2EwzqA0oE8XU,1961 +click/parser.py,sha256=LKyYQE9ZLj5KgIDXkrcTHQRXIggfoivX14_UVIn56YA,19067 +click/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +click/shell_completion.py,sha256=Ty3VM_ts0sQhj6u7eFTiLwHPoTgcXTGEAUg2OpLqYKw,18460 +click/termui.py,sha256=H7Q8FpmPelhJ2ovOhfCRhjMtCpNyjFXryAMLZODqsdc,28324 +click/testing.py,sha256=1Qd4kS5bucn1hsNIRryd0WtTMuCpkA93grkWxT8POsU,16084 +click/types.py,sha256=TZvz3hKvBztf-Hpa2enOmP4eznSPLzijjig5b_0XMxE,36391 +click/utils.py,sha256=1476UduUNY6UePGU4m18uzVHLt1sKM2PP3yWsQhbItM,20298 diff --git a/lib/go-jinja2/internal/data/darwin-amd64/click-8.1.7.dist-info/REQUESTED b/lib/go-jinja2/internal/data/darwin-amd64/click-8.1.7.dist-info/REQUESTED new file mode 100644 index 000000000..e69de29bb diff --git a/lib/go-jinja2/internal/data/darwin-amd64/click-8.1.7.dist-info/WHEEL b/lib/go-jinja2/internal/data/darwin-amd64/click-8.1.7.dist-info/WHEEL new file mode 100644 index 000000000..2c08da084 --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/click-8.1.7.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.41.1) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/lib/go-jinja2/internal/data/darwin-amd64/click-8.1.7.dist-info/top_level.txt b/lib/go-jinja2/internal/data/darwin-amd64/click-8.1.7.dist-info/top_level.txt new file mode 100644 index 000000000..dca9a9096 --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/click-8.1.7.dist-info/top_level.txt @@ -0,0 +1 @@ +click diff --git a/lib/go-jinja2/internal/data/darwin-amd64/click/__init__.py b/lib/go-jinja2/internal/data/darwin-amd64/click/__init__.py new file mode 100644 index 000000000..9a1dab048 --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/click/__init__.py @@ -0,0 +1,73 @@ +""" +Click is a simple Python module inspired by the stdlib optparse to make +writing command line scripts fun. Unlike other modules, it's based +around a simple API that does not come with too much magic and is +composable. +""" +from .core import Argument as Argument +from .core import BaseCommand as BaseCommand +from .core import Command as Command +from .core import CommandCollection as CommandCollection +from .core import Context as Context +from .core import Group as Group +from .core import MultiCommand as MultiCommand +from .core import Option as Option +from .core import Parameter as Parameter +from .decorators import argument as argument +from .decorators import command as command +from .decorators import confirmation_option as confirmation_option +from .decorators import group as group +from .decorators import help_option as help_option +from .decorators import make_pass_decorator as make_pass_decorator +from .decorators import option as option +from .decorators import pass_context as pass_context +from .decorators import pass_obj as pass_obj +from .decorators import password_option as password_option +from .decorators import version_option as version_option +from .exceptions import Abort as Abort +from .exceptions import BadArgumentUsage as BadArgumentUsage +from .exceptions import BadOptionUsage as BadOptionUsage +from .exceptions import BadParameter as BadParameter +from .exceptions import ClickException as ClickException +from .exceptions import FileError as FileError +from .exceptions import MissingParameter as MissingParameter +from .exceptions import NoSuchOption as NoSuchOption +from .exceptions import UsageError as UsageError +from .formatting import HelpFormatter as HelpFormatter +from .formatting import wrap_text as wrap_text +from .globals import get_current_context as get_current_context +from .parser import OptionParser as OptionParser +from .termui import clear as clear +from .termui import confirm as confirm +from .termui import echo_via_pager as echo_via_pager +from .termui import edit as edit +from .termui import getchar as getchar +from .termui import launch as launch +from .termui import pause as pause +from .termui import progressbar as progressbar +from .termui import prompt as prompt +from .termui import secho as secho +from .termui import style as style +from .termui import unstyle as unstyle +from .types import BOOL as BOOL +from .types import Choice as Choice +from .types import DateTime as DateTime +from .types import File as File +from .types import FLOAT as FLOAT +from .types import FloatRange as FloatRange +from .types import INT as INT +from .types import IntRange as IntRange +from .types import ParamType as ParamType +from .types import Path as Path +from .types import STRING as STRING +from .types import Tuple as Tuple +from .types import UNPROCESSED as UNPROCESSED +from .types import UUID as UUID +from .utils import echo as echo +from .utils import format_filename as format_filename +from .utils import get_app_dir as get_app_dir +from .utils import get_binary_stream as get_binary_stream +from .utils import get_text_stream as get_text_stream +from .utils import open_file as open_file + +__version__ = "8.1.7" diff --git a/lib/go-jinja2/internal/data/darwin-amd64/click/_compat.py b/lib/go-jinja2/internal/data/darwin-amd64/click/_compat.py new file mode 100644 index 000000000..23f886659 --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/click/_compat.py @@ -0,0 +1,623 @@ +import codecs +import io +import os +import re +import sys +import typing as t +from weakref import WeakKeyDictionary + +CYGWIN = sys.platform.startswith("cygwin") +WIN = sys.platform.startswith("win") +auto_wrap_for_ansi: t.Optional[t.Callable[[t.TextIO], t.TextIO]] = None +_ansi_re = re.compile(r"\033\[[;?0-9]*[a-zA-Z]") + + +def _make_text_stream( + stream: t.BinaryIO, + encoding: t.Optional[str], + errors: t.Optional[str], + force_readable: bool = False, + force_writable: bool = False, +) -> t.TextIO: + if encoding is None: + encoding = get_best_encoding(stream) + if errors is None: + errors = "replace" + return _NonClosingTextIOWrapper( + stream, + encoding, + errors, + line_buffering=True, + force_readable=force_readable, + force_writable=force_writable, + ) + + +def is_ascii_encoding(encoding: str) -> bool: + """Checks if a given encoding is ascii.""" + try: + return codecs.lookup(encoding).name == "ascii" + except LookupError: + return False + + +def get_best_encoding(stream: t.IO[t.Any]) -> str: + """Returns the default stream encoding if not found.""" + rv = getattr(stream, "encoding", None) or sys.getdefaultencoding() + if is_ascii_encoding(rv): + return "utf-8" + return rv + + +class _NonClosingTextIOWrapper(io.TextIOWrapper): + def __init__( + self, + stream: t.BinaryIO, + encoding: t.Optional[str], + errors: t.Optional[str], + force_readable: bool = False, + force_writable: bool = False, + **extra: t.Any, + ) -> None: + self._stream = stream = t.cast( + t.BinaryIO, _FixupStream(stream, force_readable, force_writable) + ) + super().__init__(stream, encoding, errors, **extra) + + def __del__(self) -> None: + try: + self.detach() + except Exception: + pass + + def isatty(self) -> bool: + # https://bitbucket.org/pypy/pypy/issue/1803 + return self._stream.isatty() + + +class _FixupStream: + """The new io interface needs more from streams than streams + traditionally implement. As such, this fix-up code is necessary in + some circumstances. + + The forcing of readable and writable flags are there because some tools + put badly patched objects on sys (one such offender are certain version + of jupyter notebook). + """ + + def __init__( + self, + stream: t.BinaryIO, + force_readable: bool = False, + force_writable: bool = False, + ): + self._stream = stream + self._force_readable = force_readable + self._force_writable = force_writable + + def __getattr__(self, name: str) -> t.Any: + return getattr(self._stream, name) + + def read1(self, size: int) -> bytes: + f = getattr(self._stream, "read1", None) + + if f is not None: + return t.cast(bytes, f(size)) + + return self._stream.read(size) + + def readable(self) -> bool: + if self._force_readable: + return True + x = getattr(self._stream, "readable", None) + if x is not None: + return t.cast(bool, x()) + try: + self._stream.read(0) + except Exception: + return False + return True + + def writable(self) -> bool: + if self._force_writable: + return True + x = getattr(self._stream, "writable", None) + if x is not None: + return t.cast(bool, x()) + try: + self._stream.write("") # type: ignore + except Exception: + try: + self._stream.write(b"") + except Exception: + return False + return True + + def seekable(self) -> bool: + x = getattr(self._stream, "seekable", None) + if x is not None: + return t.cast(bool, x()) + try: + self._stream.seek(self._stream.tell()) + except Exception: + return False + return True + + +def _is_binary_reader(stream: t.IO[t.Any], default: bool = False) -> bool: + try: + return isinstance(stream.read(0), bytes) + except Exception: + return default + # This happens in some cases where the stream was already + # closed. In this case, we assume the default. + + +def _is_binary_writer(stream: t.IO[t.Any], default: bool = False) -> bool: + try: + stream.write(b"") + except Exception: + try: + stream.write("") + return False + except Exception: + pass + return default + return True + + +def _find_binary_reader(stream: t.IO[t.Any]) -> t.Optional[t.BinaryIO]: + # We need to figure out if the given stream is already binary. + # This can happen because the official docs recommend detaching + # the streams to get binary streams. Some code might do this, so + # we need to deal with this case explicitly. + if _is_binary_reader(stream, False): + return t.cast(t.BinaryIO, stream) + + buf = getattr(stream, "buffer", None) + + # Same situation here; this time we assume that the buffer is + # actually binary in case it's closed. + if buf is not None and _is_binary_reader(buf, True): + return t.cast(t.BinaryIO, buf) + + return None + + +def _find_binary_writer(stream: t.IO[t.Any]) -> t.Optional[t.BinaryIO]: + # We need to figure out if the given stream is already binary. + # This can happen because the official docs recommend detaching + # the streams to get binary streams. Some code might do this, so + # we need to deal with this case explicitly. + if _is_binary_writer(stream, False): + return t.cast(t.BinaryIO, stream) + + buf = getattr(stream, "buffer", None) + + # Same situation here; this time we assume that the buffer is + # actually binary in case it's closed. + if buf is not None and _is_binary_writer(buf, True): + return t.cast(t.BinaryIO, buf) + + return None + + +def _stream_is_misconfigured(stream: t.TextIO) -> bool: + """A stream is misconfigured if its encoding is ASCII.""" + # If the stream does not have an encoding set, we assume it's set + # to ASCII. This appears to happen in certain unittest + # environments. It's not quite clear what the correct behavior is + # but this at least will force Click to recover somehow. + return is_ascii_encoding(getattr(stream, "encoding", None) or "ascii") + + +def _is_compat_stream_attr(stream: t.TextIO, attr: str, value: t.Optional[str]) -> bool: + """A stream attribute is compatible if it is equal to the + desired value or the desired value is unset and the attribute + has a value. + """ + stream_value = getattr(stream, attr, None) + return stream_value == value or (value is None and stream_value is not None) + + +def _is_compatible_text_stream( + stream: t.TextIO, encoding: t.Optional[str], errors: t.Optional[str] +) -> bool: + """Check if a stream's encoding and errors attributes are + compatible with the desired values. + """ + return _is_compat_stream_attr( + stream, "encoding", encoding + ) and _is_compat_stream_attr(stream, "errors", errors) + + +def _force_correct_text_stream( + text_stream: t.IO[t.Any], + encoding: t.Optional[str], + errors: t.Optional[str], + is_binary: t.Callable[[t.IO[t.Any], bool], bool], + find_binary: t.Callable[[t.IO[t.Any]], t.Optional[t.BinaryIO]], + force_readable: bool = False, + force_writable: bool = False, +) -> t.TextIO: + if is_binary(text_stream, False): + binary_reader = t.cast(t.BinaryIO, text_stream) + else: + text_stream = t.cast(t.TextIO, text_stream) + # If the stream looks compatible, and won't default to a + # misconfigured ascii encoding, return it as-is. + if _is_compatible_text_stream(text_stream, encoding, errors) and not ( + encoding is None and _stream_is_misconfigured(text_stream) + ): + return text_stream + + # Otherwise, get the underlying binary reader. + possible_binary_reader = find_binary(text_stream) + + # If that's not possible, silently use the original reader + # and get mojibake instead of exceptions. + if possible_binary_reader is None: + return text_stream + + binary_reader = possible_binary_reader + + # Default errors to replace instead of strict in order to get + # something that works. + if errors is None: + errors = "replace" + + # Wrap the binary stream in a text stream with the correct + # encoding parameters. + return _make_text_stream( + binary_reader, + encoding, + errors, + force_readable=force_readable, + force_writable=force_writable, + ) + + +def _force_correct_text_reader( + text_reader: t.IO[t.Any], + encoding: t.Optional[str], + errors: t.Optional[str], + force_readable: bool = False, +) -> t.TextIO: + return _force_correct_text_stream( + text_reader, + encoding, + errors, + _is_binary_reader, + _find_binary_reader, + force_readable=force_readable, + ) + + +def _force_correct_text_writer( + text_writer: t.IO[t.Any], + encoding: t.Optional[str], + errors: t.Optional[str], + force_writable: bool = False, +) -> t.TextIO: + return _force_correct_text_stream( + text_writer, + encoding, + errors, + _is_binary_writer, + _find_binary_writer, + force_writable=force_writable, + ) + + +def get_binary_stdin() -> t.BinaryIO: + reader = _find_binary_reader(sys.stdin) + if reader is None: + raise RuntimeError("Was not able to determine binary stream for sys.stdin.") + return reader + + +def get_binary_stdout() -> t.BinaryIO: + writer = _find_binary_writer(sys.stdout) + if writer is None: + raise RuntimeError("Was not able to determine binary stream for sys.stdout.") + return writer + + +def get_binary_stderr() -> t.BinaryIO: + writer = _find_binary_writer(sys.stderr) + if writer is None: + raise RuntimeError("Was not able to determine binary stream for sys.stderr.") + return writer + + +def get_text_stdin( + encoding: t.Optional[str] = None, errors: t.Optional[str] = None +) -> t.TextIO: + rv = _get_windows_console_stream(sys.stdin, encoding, errors) + if rv is not None: + return rv + return _force_correct_text_reader(sys.stdin, encoding, errors, force_readable=True) + + +def get_text_stdout( + encoding: t.Optional[str] = None, errors: t.Optional[str] = None +) -> t.TextIO: + rv = _get_windows_console_stream(sys.stdout, encoding, errors) + if rv is not None: + return rv + return _force_correct_text_writer(sys.stdout, encoding, errors, force_writable=True) + + +def get_text_stderr( + encoding: t.Optional[str] = None, errors: t.Optional[str] = None +) -> t.TextIO: + rv = _get_windows_console_stream(sys.stderr, encoding, errors) + if rv is not None: + return rv + return _force_correct_text_writer(sys.stderr, encoding, errors, force_writable=True) + + +def _wrap_io_open( + file: t.Union[str, "os.PathLike[str]", int], + mode: str, + encoding: t.Optional[str], + errors: t.Optional[str], +) -> t.IO[t.Any]: + """Handles not passing ``encoding`` and ``errors`` in binary mode.""" + if "b" in mode: + return open(file, mode) + + return open(file, mode, encoding=encoding, errors=errors) + + +def open_stream( + filename: "t.Union[str, os.PathLike[str]]", + mode: str = "r", + encoding: t.Optional[str] = None, + errors: t.Optional[str] = "strict", + atomic: bool = False, +) -> t.Tuple[t.IO[t.Any], bool]: + binary = "b" in mode + filename = os.fspath(filename) + + # Standard streams first. These are simple because they ignore the + # atomic flag. Use fsdecode to handle Path("-"). + if os.fsdecode(filename) == "-": + if any(m in mode for m in ["w", "a", "x"]): + if binary: + return get_binary_stdout(), False + return get_text_stdout(encoding=encoding, errors=errors), False + if binary: + return get_binary_stdin(), False + return get_text_stdin(encoding=encoding, errors=errors), False + + # Non-atomic writes directly go out through the regular open functions. + if not atomic: + return _wrap_io_open(filename, mode, encoding, errors), True + + # Some usability stuff for atomic writes + if "a" in mode: + raise ValueError( + "Appending to an existing file is not supported, because that" + " would involve an expensive `copy`-operation to a temporary" + " file. Open the file in normal `w`-mode and copy explicitly" + " if that's what you're after." + ) + if "x" in mode: + raise ValueError("Use the `overwrite`-parameter instead.") + if "w" not in mode: + raise ValueError("Atomic writes only make sense with `w`-mode.") + + # Atomic writes are more complicated. They work by opening a file + # as a proxy in the same folder and then using the fdopen + # functionality to wrap it in a Python file. Then we wrap it in an + # atomic file that moves the file over on close. + import errno + import random + + try: + perm: t.Optional[int] = os.stat(filename).st_mode + except OSError: + perm = None + + flags = os.O_RDWR | os.O_CREAT | os.O_EXCL + + if binary: + flags |= getattr(os, "O_BINARY", 0) + + while True: + tmp_filename = os.path.join( + os.path.dirname(filename), + f".__atomic-write{random.randrange(1 << 32):08x}", + ) + try: + fd = os.open(tmp_filename, flags, 0o666 if perm is None else perm) + break + except OSError as e: + if e.errno == errno.EEXIST or ( + os.name == "nt" + and e.errno == errno.EACCES + and os.path.isdir(e.filename) + and os.access(e.filename, os.W_OK) + ): + continue + raise + + if perm is not None: + os.chmod(tmp_filename, perm) # in case perm includes bits in umask + + f = _wrap_io_open(fd, mode, encoding, errors) + af = _AtomicFile(f, tmp_filename, os.path.realpath(filename)) + return t.cast(t.IO[t.Any], af), True + + +class _AtomicFile: + def __init__(self, f: t.IO[t.Any], tmp_filename: str, real_filename: str) -> None: + self._f = f + self._tmp_filename = tmp_filename + self._real_filename = real_filename + self.closed = False + + @property + def name(self) -> str: + return self._real_filename + + def close(self, delete: bool = False) -> None: + if self.closed: + return + self._f.close() + os.replace(self._tmp_filename, self._real_filename) + self.closed = True + + def __getattr__(self, name: str) -> t.Any: + return getattr(self._f, name) + + def __enter__(self) -> "_AtomicFile": + return self + + def __exit__(self, exc_type: t.Optional[t.Type[BaseException]], *_: t.Any) -> None: + self.close(delete=exc_type is not None) + + def __repr__(self) -> str: + return repr(self._f) + + +def strip_ansi(value: str) -> str: + return _ansi_re.sub("", value) + + +def _is_jupyter_kernel_output(stream: t.IO[t.Any]) -> bool: + while isinstance(stream, (_FixupStream, _NonClosingTextIOWrapper)): + stream = stream._stream + + return stream.__class__.__module__.startswith("ipykernel.") + + +def should_strip_ansi( + stream: t.Optional[t.IO[t.Any]] = None, color: t.Optional[bool] = None +) -> bool: + if color is None: + if stream is None: + stream = sys.stdin + return not isatty(stream) and not _is_jupyter_kernel_output(stream) + return not color + + +# On Windows, wrap the output streams with colorama to support ANSI +# color codes. +# NOTE: double check is needed so mypy does not analyze this on Linux +if sys.platform.startswith("win") and WIN: + from ._winconsole import _get_windows_console_stream + + def _get_argv_encoding() -> str: + import locale + + return locale.getpreferredencoding() + + _ansi_stream_wrappers: t.MutableMapping[t.TextIO, t.TextIO] = WeakKeyDictionary() + + def auto_wrap_for_ansi( # noqa: F811 + stream: t.TextIO, color: t.Optional[bool] = None + ) -> t.TextIO: + """Support ANSI color and style codes on Windows by wrapping a + stream with colorama. + """ + try: + cached = _ansi_stream_wrappers.get(stream) + except Exception: + cached = None + + if cached is not None: + return cached + + import colorama + + strip = should_strip_ansi(stream, color) + ansi_wrapper = colorama.AnsiToWin32(stream, strip=strip) + rv = t.cast(t.TextIO, ansi_wrapper.stream) + _write = rv.write + + def _safe_write(s): + try: + return _write(s) + except BaseException: + ansi_wrapper.reset_all() + raise + + rv.write = _safe_write + + try: + _ansi_stream_wrappers[stream] = rv + except Exception: + pass + + return rv + +else: + + def _get_argv_encoding() -> str: + return getattr(sys.stdin, "encoding", None) or sys.getfilesystemencoding() + + def _get_windows_console_stream( + f: t.TextIO, encoding: t.Optional[str], errors: t.Optional[str] + ) -> t.Optional[t.TextIO]: + return None + + +def term_len(x: str) -> int: + return len(strip_ansi(x)) + + +def isatty(stream: t.IO[t.Any]) -> bool: + try: + return stream.isatty() + except Exception: + return False + + +def _make_cached_stream_func( + src_func: t.Callable[[], t.Optional[t.TextIO]], + wrapper_func: t.Callable[[], t.TextIO], +) -> t.Callable[[], t.Optional[t.TextIO]]: + cache: t.MutableMapping[t.TextIO, t.TextIO] = WeakKeyDictionary() + + def func() -> t.Optional[t.TextIO]: + stream = src_func() + + if stream is None: + return None + + try: + rv = cache.get(stream) + except Exception: + rv = None + if rv is not None: + return rv + rv = wrapper_func() + try: + cache[stream] = rv + except Exception: + pass + return rv + + return func + + +_default_text_stdin = _make_cached_stream_func(lambda: sys.stdin, get_text_stdin) +_default_text_stdout = _make_cached_stream_func(lambda: sys.stdout, get_text_stdout) +_default_text_stderr = _make_cached_stream_func(lambda: sys.stderr, get_text_stderr) + + +binary_streams: t.Mapping[str, t.Callable[[], t.BinaryIO]] = { + "stdin": get_binary_stdin, + "stdout": get_binary_stdout, + "stderr": get_binary_stderr, +} + +text_streams: t.Mapping[ + str, t.Callable[[t.Optional[str], t.Optional[str]], t.TextIO] +] = { + "stdin": get_text_stdin, + "stdout": get_text_stdout, + "stderr": get_text_stderr, +} diff --git a/lib/go-jinja2/internal/data/darwin-amd64/click/_termui_impl.py b/lib/go-jinja2/internal/data/darwin-amd64/click/_termui_impl.py new file mode 100644 index 000000000..f74465775 --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/click/_termui_impl.py @@ -0,0 +1,739 @@ +""" +This module contains implementations for the termui module. To keep the +import time of Click down, some infrequently used functionality is +placed in this module and only imported as needed. +""" +import contextlib +import math +import os +import sys +import time +import typing as t +from gettext import gettext as _ +from io import StringIO +from types import TracebackType + +from ._compat import _default_text_stdout +from ._compat import CYGWIN +from ._compat import get_best_encoding +from ._compat import isatty +from ._compat import open_stream +from ._compat import strip_ansi +from ._compat import term_len +from ._compat import WIN +from .exceptions import ClickException +from .utils import echo + +V = t.TypeVar("V") + +if os.name == "nt": + BEFORE_BAR = "\r" + AFTER_BAR = "\n" +else: + BEFORE_BAR = "\r\033[?25l" + AFTER_BAR = "\033[?25h\n" + + +class ProgressBar(t.Generic[V]): + def __init__( + self, + iterable: t.Optional[t.Iterable[V]], + length: t.Optional[int] = None, + fill_char: str = "#", + empty_char: str = " ", + bar_template: str = "%(bar)s", + info_sep: str = " ", + show_eta: bool = True, + show_percent: t.Optional[bool] = None, + show_pos: bool = False, + item_show_func: t.Optional[t.Callable[[t.Optional[V]], t.Optional[str]]] = None, + label: t.Optional[str] = None, + file: t.Optional[t.TextIO] = None, + color: t.Optional[bool] = None, + update_min_steps: int = 1, + width: int = 30, + ) -> None: + self.fill_char = fill_char + self.empty_char = empty_char + self.bar_template = bar_template + self.info_sep = info_sep + self.show_eta = show_eta + self.show_percent = show_percent + self.show_pos = show_pos + self.item_show_func = item_show_func + self.label: str = label or "" + + if file is None: + file = _default_text_stdout() + + # There are no standard streams attached to write to. For example, + # pythonw on Windows. + if file is None: + file = StringIO() + + self.file = file + self.color = color + self.update_min_steps = update_min_steps + self._completed_intervals = 0 + self.width: int = width + self.autowidth: bool = width == 0 + + if length is None: + from operator import length_hint + + length = length_hint(iterable, -1) + + if length == -1: + length = None + if iterable is None: + if length is None: + raise TypeError("iterable or length is required") + iterable = t.cast(t.Iterable[V], range(length)) + self.iter: t.Iterable[V] = iter(iterable) + self.length = length + self.pos = 0 + self.avg: t.List[float] = [] + self.last_eta: float + self.start: float + self.start = self.last_eta = time.time() + self.eta_known: bool = False + self.finished: bool = False + self.max_width: t.Optional[int] = None + self.entered: bool = False + self.current_item: t.Optional[V] = None + self.is_hidden: bool = not isatty(self.file) + self._last_line: t.Optional[str] = None + + def __enter__(self) -> "ProgressBar[V]": + self.entered = True + self.render_progress() + return self + + def __exit__( + self, + exc_type: t.Optional[t.Type[BaseException]], + exc_value: t.Optional[BaseException], + tb: t.Optional[TracebackType], + ) -> None: + self.render_finish() + + def __iter__(self) -> t.Iterator[V]: + if not self.entered: + raise RuntimeError("You need to use progress bars in a with block.") + self.render_progress() + return self.generator() + + def __next__(self) -> V: + # Iteration is defined in terms of a generator function, + # returned by iter(self); use that to define next(). This works + # because `self.iter` is an iterable consumed by that generator, + # so it is re-entry safe. Calling `next(self.generator())` + # twice works and does "what you want". + return next(iter(self)) + + def render_finish(self) -> None: + if self.is_hidden: + return + self.file.write(AFTER_BAR) + self.file.flush() + + @property + def pct(self) -> float: + if self.finished: + return 1.0 + return min(self.pos / (float(self.length or 1) or 1), 1.0) + + @property + def time_per_iteration(self) -> float: + if not self.avg: + return 0.0 + return sum(self.avg) / float(len(self.avg)) + + @property + def eta(self) -> float: + if self.length is not None and not self.finished: + return self.time_per_iteration * (self.length - self.pos) + return 0.0 + + def format_eta(self) -> str: + if self.eta_known: + t = int(self.eta) + seconds = t % 60 + t //= 60 + minutes = t % 60 + t //= 60 + hours = t % 24 + t //= 24 + if t > 0: + return f"{t}d {hours:02}:{minutes:02}:{seconds:02}" + else: + return f"{hours:02}:{minutes:02}:{seconds:02}" + return "" + + def format_pos(self) -> str: + pos = str(self.pos) + if self.length is not None: + pos += f"/{self.length}" + return pos + + def format_pct(self) -> str: + return f"{int(self.pct * 100): 4}%"[1:] + + def format_bar(self) -> str: + if self.length is not None: + bar_length = int(self.pct * self.width) + bar = self.fill_char * bar_length + bar += self.empty_char * (self.width - bar_length) + elif self.finished: + bar = self.fill_char * self.width + else: + chars = list(self.empty_char * (self.width or 1)) + if self.time_per_iteration != 0: + chars[ + int( + (math.cos(self.pos * self.time_per_iteration) / 2.0 + 0.5) + * self.width + ) + ] = self.fill_char + bar = "".join(chars) + return bar + + def format_progress_line(self) -> str: + show_percent = self.show_percent + + info_bits = [] + if self.length is not None and show_percent is None: + show_percent = not self.show_pos + + if self.show_pos: + info_bits.append(self.format_pos()) + if show_percent: + info_bits.append(self.format_pct()) + if self.show_eta and self.eta_known and not self.finished: + info_bits.append(self.format_eta()) + if self.item_show_func is not None: + item_info = self.item_show_func(self.current_item) + if item_info is not None: + info_bits.append(item_info) + + return ( + self.bar_template + % { + "label": self.label, + "bar": self.format_bar(), + "info": self.info_sep.join(info_bits), + } + ).rstrip() + + def render_progress(self) -> None: + import shutil + + if self.is_hidden: + # Only output the label as it changes if the output is not a + # TTY. Use file=stderr if you expect to be piping stdout. + if self._last_line != self.label: + self._last_line = self.label + echo(self.label, file=self.file, color=self.color) + + return + + buf = [] + # Update width in case the terminal has been resized + if self.autowidth: + old_width = self.width + self.width = 0 + clutter_length = term_len(self.format_progress_line()) + new_width = max(0, shutil.get_terminal_size().columns - clutter_length) + if new_width < old_width: + buf.append(BEFORE_BAR) + buf.append(" " * self.max_width) # type: ignore + self.max_width = new_width + self.width = new_width + + clear_width = self.width + if self.max_width is not None: + clear_width = self.max_width + + buf.append(BEFORE_BAR) + line = self.format_progress_line() + line_len = term_len(line) + if self.max_width is None or self.max_width < line_len: + self.max_width = line_len + + buf.append(line) + buf.append(" " * (clear_width - line_len)) + line = "".join(buf) + # Render the line only if it changed. + + if line != self._last_line: + self._last_line = line + echo(line, file=self.file, color=self.color, nl=False) + self.file.flush() + + def make_step(self, n_steps: int) -> None: + self.pos += n_steps + if self.length is not None and self.pos >= self.length: + self.finished = True + + if (time.time() - self.last_eta) < 1.0: + return + + self.last_eta = time.time() + + # self.avg is a rolling list of length <= 7 of steps where steps are + # defined as time elapsed divided by the total progress through + # self.length. + if self.pos: + step = (time.time() - self.start) / self.pos + else: + step = time.time() - self.start + + self.avg = self.avg[-6:] + [step] + + self.eta_known = self.length is not None + + def update(self, n_steps: int, current_item: t.Optional[V] = None) -> None: + """Update the progress bar by advancing a specified number of + steps, and optionally set the ``current_item`` for this new + position. + + :param n_steps: Number of steps to advance. + :param current_item: Optional item to set as ``current_item`` + for the updated position. + + .. versionchanged:: 8.0 + Added the ``current_item`` optional parameter. + + .. versionchanged:: 8.0 + Only render when the number of steps meets the + ``update_min_steps`` threshold. + """ + if current_item is not None: + self.current_item = current_item + + self._completed_intervals += n_steps + + if self._completed_intervals >= self.update_min_steps: + self.make_step(self._completed_intervals) + self.render_progress() + self._completed_intervals = 0 + + def finish(self) -> None: + self.eta_known = False + self.current_item = None + self.finished = True + + def generator(self) -> t.Iterator[V]: + """Return a generator which yields the items added to the bar + during construction, and updates the progress bar *after* the + yielded block returns. + """ + # WARNING: the iterator interface for `ProgressBar` relies on + # this and only works because this is a simple generator which + # doesn't create or manage additional state. If this function + # changes, the impact should be evaluated both against + # `iter(bar)` and `next(bar)`. `next()` in particular may call + # `self.generator()` repeatedly, and this must remain safe in + # order for that interface to work. + if not self.entered: + raise RuntimeError("You need to use progress bars in a with block.") + + if self.is_hidden: + yield from self.iter + else: + for rv in self.iter: + self.current_item = rv + + # This allows show_item_func to be updated before the + # item is processed. Only trigger at the beginning of + # the update interval. + if self._completed_intervals == 0: + self.render_progress() + + yield rv + self.update(1) + + self.finish() + self.render_progress() + + +def pager(generator: t.Iterable[str], color: t.Optional[bool] = None) -> None: + """Decide what method to use for paging through text.""" + stdout = _default_text_stdout() + + # There are no standard streams attached to write to. For example, + # pythonw on Windows. + if stdout is None: + stdout = StringIO() + + if not isatty(sys.stdin) or not isatty(stdout): + return _nullpager(stdout, generator, color) + pager_cmd = (os.environ.get("PAGER", None) or "").strip() + if pager_cmd: + if WIN: + return _tempfilepager(generator, pager_cmd, color) + return _pipepager(generator, pager_cmd, color) + if os.environ.get("TERM") in ("dumb", "emacs"): + return _nullpager(stdout, generator, color) + if WIN or sys.platform.startswith("os2"): + return _tempfilepager(generator, "more <", color) + if hasattr(os, "system") and os.system("(less) 2>/dev/null") == 0: + return _pipepager(generator, "less", color) + + import tempfile + + fd, filename = tempfile.mkstemp() + os.close(fd) + try: + if hasattr(os, "system") and os.system(f'more "{filename}"') == 0: + return _pipepager(generator, "more", color) + return _nullpager(stdout, generator, color) + finally: + os.unlink(filename) + + +def _pipepager(generator: t.Iterable[str], cmd: str, color: t.Optional[bool]) -> None: + """Page through text by feeding it to another program. Invoking a + pager through this might support colors. + """ + import subprocess + + env = dict(os.environ) + + # If we're piping to less we might support colors under the + # condition that + cmd_detail = cmd.rsplit("/", 1)[-1].split() + if color is None and cmd_detail[0] == "less": + less_flags = f"{os.environ.get('LESS', '')}{' '.join(cmd_detail[1:])}" + if not less_flags: + env["LESS"] = "-R" + color = True + elif "r" in less_flags or "R" in less_flags: + color = True + + c = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, env=env) + stdin = t.cast(t.BinaryIO, c.stdin) + encoding = get_best_encoding(stdin) + try: + for text in generator: + if not color: + text = strip_ansi(text) + + stdin.write(text.encode(encoding, "replace")) + except (OSError, KeyboardInterrupt): + pass + else: + stdin.close() + + # Less doesn't respect ^C, but catches it for its own UI purposes (aborting + # search or other commands inside less). + # + # That means when the user hits ^C, the parent process (click) terminates, + # but less is still alive, paging the output and messing up the terminal. + # + # If the user wants to make the pager exit on ^C, they should set + # `LESS='-K'`. It's not our decision to make. + while True: + try: + c.wait() + except KeyboardInterrupt: + pass + else: + break + + +def _tempfilepager( + generator: t.Iterable[str], cmd: str, color: t.Optional[bool] +) -> None: + """Page through text by invoking a program on a temporary file.""" + import tempfile + + fd, filename = tempfile.mkstemp() + # TODO: This never terminates if the passed generator never terminates. + text = "".join(generator) + if not color: + text = strip_ansi(text) + encoding = get_best_encoding(sys.stdout) + with open_stream(filename, "wb")[0] as f: + f.write(text.encode(encoding)) + try: + os.system(f'{cmd} "{filename}"') + finally: + os.close(fd) + os.unlink(filename) + + +def _nullpager( + stream: t.TextIO, generator: t.Iterable[str], color: t.Optional[bool] +) -> None: + """Simply print unformatted text. This is the ultimate fallback.""" + for text in generator: + if not color: + text = strip_ansi(text) + stream.write(text) + + +class Editor: + def __init__( + self, + editor: t.Optional[str] = None, + env: t.Optional[t.Mapping[str, str]] = None, + require_save: bool = True, + extension: str = ".txt", + ) -> None: + self.editor = editor + self.env = env + self.require_save = require_save + self.extension = extension + + def get_editor(self) -> str: + if self.editor is not None: + return self.editor + for key in "VISUAL", "EDITOR": + rv = os.environ.get(key) + if rv: + return rv + if WIN: + return "notepad" + for editor in "sensible-editor", "vim", "nano": + if os.system(f"which {editor} >/dev/null 2>&1") == 0: + return editor + return "vi" + + def edit_file(self, filename: str) -> None: + import subprocess + + editor = self.get_editor() + environ: t.Optional[t.Dict[str, str]] = None + + if self.env: + environ = os.environ.copy() + environ.update(self.env) + + try: + c = subprocess.Popen(f'{editor} "{filename}"', env=environ, shell=True) + exit_code = c.wait() + if exit_code != 0: + raise ClickException( + _("{editor}: Editing failed").format(editor=editor) + ) + except OSError as e: + raise ClickException( + _("{editor}: Editing failed: {e}").format(editor=editor, e=e) + ) from e + + def edit(self, text: t.Optional[t.AnyStr]) -> t.Optional[t.AnyStr]: + import tempfile + + if not text: + data = b"" + elif isinstance(text, (bytes, bytearray)): + data = text + else: + if text and not text.endswith("\n"): + text += "\n" + + if WIN: + data = text.replace("\n", "\r\n").encode("utf-8-sig") + else: + data = text.encode("utf-8") + + fd, name = tempfile.mkstemp(prefix="editor-", suffix=self.extension) + f: t.BinaryIO + + try: + with os.fdopen(fd, "wb") as f: + f.write(data) + + # If the filesystem resolution is 1 second, like Mac OS + # 10.12 Extended, or 2 seconds, like FAT32, and the editor + # closes very fast, require_save can fail. Set the modified + # time to be 2 seconds in the past to work around this. + os.utime(name, (os.path.getatime(name), os.path.getmtime(name) - 2)) + # Depending on the resolution, the exact value might not be + # recorded, so get the new recorded value. + timestamp = os.path.getmtime(name) + + self.edit_file(name) + + if self.require_save and os.path.getmtime(name) == timestamp: + return None + + with open(name, "rb") as f: + rv = f.read() + + if isinstance(text, (bytes, bytearray)): + return rv + + return rv.decode("utf-8-sig").replace("\r\n", "\n") # type: ignore + finally: + os.unlink(name) + + +def open_url(url: str, wait: bool = False, locate: bool = False) -> int: + import subprocess + + def _unquote_file(url: str) -> str: + from urllib.parse import unquote + + if url.startswith("file://"): + url = unquote(url[7:]) + + return url + + if sys.platform == "darwin": + args = ["open"] + if wait: + args.append("-W") + if locate: + args.append("-R") + args.append(_unquote_file(url)) + null = open("/dev/null", "w") + try: + return subprocess.Popen(args, stderr=null).wait() + finally: + null.close() + elif WIN: + if locate: + url = _unquote_file(url.replace('"', "")) + args = f'explorer /select,"{url}"' + else: + url = url.replace('"', "") + wait_str = "/WAIT" if wait else "" + args = f'start {wait_str} "" "{url}"' + return os.system(args) + elif CYGWIN: + if locate: + url = os.path.dirname(_unquote_file(url).replace('"', "")) + args = f'cygstart "{url}"' + else: + url = url.replace('"', "") + wait_str = "-w" if wait else "" + args = f'cygstart {wait_str} "{url}"' + return os.system(args) + + try: + if locate: + url = os.path.dirname(_unquote_file(url)) or "." + else: + url = _unquote_file(url) + c = subprocess.Popen(["xdg-open", url]) + if wait: + return c.wait() + return 0 + except OSError: + if url.startswith(("http://", "https://")) and not locate and not wait: + import webbrowser + + webbrowser.open(url) + return 0 + return 1 + + +def _translate_ch_to_exc(ch: str) -> t.Optional[BaseException]: + if ch == "\x03": + raise KeyboardInterrupt() + + if ch == "\x04" and not WIN: # Unix-like, Ctrl+D + raise EOFError() + + if ch == "\x1a" and WIN: # Windows, Ctrl+Z + raise EOFError() + + return None + + +if WIN: + import msvcrt + + @contextlib.contextmanager + def raw_terminal() -> t.Iterator[int]: + yield -1 + + def getchar(echo: bool) -> str: + # The function `getch` will return a bytes object corresponding to + # the pressed character. Since Windows 10 build 1803, it will also + # return \x00 when called a second time after pressing a regular key. + # + # `getwch` does not share this probably-bugged behavior. Moreover, it + # returns a Unicode object by default, which is what we want. + # + # Either of these functions will return \x00 or \xe0 to indicate + # a special key, and you need to call the same function again to get + # the "rest" of the code. The fun part is that \u00e0 is + # "latin small letter a with grave", so if you type that on a French + # keyboard, you _also_ get a \xe0. + # E.g., consider the Up arrow. This returns \xe0 and then \x48. The + # resulting Unicode string reads as "a with grave" + "capital H". + # This is indistinguishable from when the user actually types + # "a with grave" and then "capital H". + # + # When \xe0 is returned, we assume it's part of a special-key sequence + # and call `getwch` again, but that means that when the user types + # the \u00e0 character, `getchar` doesn't return until a second + # character is typed. + # The alternative is returning immediately, but that would mess up + # cross-platform handling of arrow keys and others that start with + # \xe0. Another option is using `getch`, but then we can't reliably + # read non-ASCII characters, because return values of `getch` are + # limited to the current 8-bit codepage. + # + # Anyway, Click doesn't claim to do this Right(tm), and using `getwch` + # is doing the right thing in more situations than with `getch`. + func: t.Callable[[], str] + + if echo: + func = msvcrt.getwche # type: ignore + else: + func = msvcrt.getwch # type: ignore + + rv = func() + + if rv in ("\x00", "\xe0"): + # \x00 and \xe0 are control characters that indicate special key, + # see above. + rv += func() + + _translate_ch_to_exc(rv) + return rv + +else: + import tty + import termios + + @contextlib.contextmanager + def raw_terminal() -> t.Iterator[int]: + f: t.Optional[t.TextIO] + fd: int + + if not isatty(sys.stdin): + f = open("/dev/tty") + fd = f.fileno() + else: + fd = sys.stdin.fileno() + f = None + + try: + old_settings = termios.tcgetattr(fd) + + try: + tty.setraw(fd) + yield fd + finally: + termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) + sys.stdout.flush() + + if f is not None: + f.close() + except termios.error: + pass + + def getchar(echo: bool) -> str: + with raw_terminal() as fd: + ch = os.read(fd, 32).decode(get_best_encoding(sys.stdin), "replace") + + if echo and isatty(sys.stdout): + sys.stdout.write(ch) + + _translate_ch_to_exc(ch) + return ch diff --git a/lib/go-jinja2/internal/data/darwin-amd64/click/_textwrap.py b/lib/go-jinja2/internal/data/darwin-amd64/click/_textwrap.py new file mode 100644 index 000000000..b47dcbd42 --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/click/_textwrap.py @@ -0,0 +1,49 @@ +import textwrap +import typing as t +from contextlib import contextmanager + + +class TextWrapper(textwrap.TextWrapper): + def _handle_long_word( + self, + reversed_chunks: t.List[str], + cur_line: t.List[str], + cur_len: int, + width: int, + ) -> None: + space_left = max(width - cur_len, 1) + + if self.break_long_words: + last = reversed_chunks[-1] + cut = last[:space_left] + res = last[space_left:] + cur_line.append(cut) + reversed_chunks[-1] = res + elif not cur_line: + cur_line.append(reversed_chunks.pop()) + + @contextmanager + def extra_indent(self, indent: str) -> t.Iterator[None]: + old_initial_indent = self.initial_indent + old_subsequent_indent = self.subsequent_indent + self.initial_indent += indent + self.subsequent_indent += indent + + try: + yield + finally: + self.initial_indent = old_initial_indent + self.subsequent_indent = old_subsequent_indent + + def indent_only(self, text: str) -> str: + rv = [] + + for idx, line in enumerate(text.splitlines()): + indent = self.initial_indent + + if idx > 0: + indent = self.subsequent_indent + + rv.append(f"{indent}{line}") + + return "\n".join(rv) diff --git a/lib/go-jinja2/internal/data/darwin-amd64/click/_winconsole.py b/lib/go-jinja2/internal/data/darwin-amd64/click/_winconsole.py new file mode 100644 index 000000000..6b20df315 --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/click/_winconsole.py @@ -0,0 +1,279 @@ +# This module is based on the excellent work by Adam Bartoš who +# provided a lot of what went into the implementation here in +# the discussion to issue1602 in the Python bug tracker. +# +# There are some general differences in regards to how this works +# compared to the original patches as we do not need to patch +# the entire interpreter but just work in our little world of +# echo and prompt. +import io +import sys +import time +import typing as t +from ctypes import byref +from ctypes import c_char +from ctypes import c_char_p +from ctypes import c_int +from ctypes import c_ssize_t +from ctypes import c_ulong +from ctypes import c_void_p +from ctypes import POINTER +from ctypes import py_object +from ctypes import Structure +from ctypes.wintypes import DWORD +from ctypes.wintypes import HANDLE +from ctypes.wintypes import LPCWSTR +from ctypes.wintypes import LPWSTR + +from ._compat import _NonClosingTextIOWrapper + +assert sys.platform == "win32" +import msvcrt # noqa: E402 +from ctypes import windll # noqa: E402 +from ctypes import WINFUNCTYPE # noqa: E402 + +c_ssize_p = POINTER(c_ssize_t) + +kernel32 = windll.kernel32 +GetStdHandle = kernel32.GetStdHandle +ReadConsoleW = kernel32.ReadConsoleW +WriteConsoleW = kernel32.WriteConsoleW +GetConsoleMode = kernel32.GetConsoleMode +GetLastError = kernel32.GetLastError +GetCommandLineW = WINFUNCTYPE(LPWSTR)(("GetCommandLineW", windll.kernel32)) +CommandLineToArgvW = WINFUNCTYPE(POINTER(LPWSTR), LPCWSTR, POINTER(c_int))( + ("CommandLineToArgvW", windll.shell32) +) +LocalFree = WINFUNCTYPE(c_void_p, c_void_p)(("LocalFree", windll.kernel32)) + +STDIN_HANDLE = GetStdHandle(-10) +STDOUT_HANDLE = GetStdHandle(-11) +STDERR_HANDLE = GetStdHandle(-12) + +PyBUF_SIMPLE = 0 +PyBUF_WRITABLE = 1 + +ERROR_SUCCESS = 0 +ERROR_NOT_ENOUGH_MEMORY = 8 +ERROR_OPERATION_ABORTED = 995 + +STDIN_FILENO = 0 +STDOUT_FILENO = 1 +STDERR_FILENO = 2 + +EOF = b"\x1a" +MAX_BYTES_WRITTEN = 32767 + +try: + from ctypes import pythonapi +except ImportError: + # On PyPy we cannot get buffers so our ability to operate here is + # severely limited. + get_buffer = None +else: + + class Py_buffer(Structure): + _fields_ = [ + ("buf", c_void_p), + ("obj", py_object), + ("len", c_ssize_t), + ("itemsize", c_ssize_t), + ("readonly", c_int), + ("ndim", c_int), + ("format", c_char_p), + ("shape", c_ssize_p), + ("strides", c_ssize_p), + ("suboffsets", c_ssize_p), + ("internal", c_void_p), + ] + + PyObject_GetBuffer = pythonapi.PyObject_GetBuffer + PyBuffer_Release = pythonapi.PyBuffer_Release + + def get_buffer(obj, writable=False): + buf = Py_buffer() + flags = PyBUF_WRITABLE if writable else PyBUF_SIMPLE + PyObject_GetBuffer(py_object(obj), byref(buf), flags) + + try: + buffer_type = c_char * buf.len + return buffer_type.from_address(buf.buf) + finally: + PyBuffer_Release(byref(buf)) + + +class _WindowsConsoleRawIOBase(io.RawIOBase): + def __init__(self, handle): + self.handle = handle + + def isatty(self): + super().isatty() + return True + + +class _WindowsConsoleReader(_WindowsConsoleRawIOBase): + def readable(self): + return True + + def readinto(self, b): + bytes_to_be_read = len(b) + if not bytes_to_be_read: + return 0 + elif bytes_to_be_read % 2: + raise ValueError( + "cannot read odd number of bytes from UTF-16-LE encoded console" + ) + + buffer = get_buffer(b, writable=True) + code_units_to_be_read = bytes_to_be_read // 2 + code_units_read = c_ulong() + + rv = ReadConsoleW( + HANDLE(self.handle), + buffer, + code_units_to_be_read, + byref(code_units_read), + None, + ) + if GetLastError() == ERROR_OPERATION_ABORTED: + # wait for KeyboardInterrupt + time.sleep(0.1) + if not rv: + raise OSError(f"Windows error: {GetLastError()}") + + if buffer[0] == EOF: + return 0 + return 2 * code_units_read.value + + +class _WindowsConsoleWriter(_WindowsConsoleRawIOBase): + def writable(self): + return True + + @staticmethod + def _get_error_message(errno): + if errno == ERROR_SUCCESS: + return "ERROR_SUCCESS" + elif errno == ERROR_NOT_ENOUGH_MEMORY: + return "ERROR_NOT_ENOUGH_MEMORY" + return f"Windows error {errno}" + + def write(self, b): + bytes_to_be_written = len(b) + buf = get_buffer(b) + code_units_to_be_written = min(bytes_to_be_written, MAX_BYTES_WRITTEN) // 2 + code_units_written = c_ulong() + + WriteConsoleW( + HANDLE(self.handle), + buf, + code_units_to_be_written, + byref(code_units_written), + None, + ) + bytes_written = 2 * code_units_written.value + + if bytes_written == 0 and bytes_to_be_written > 0: + raise OSError(self._get_error_message(GetLastError())) + return bytes_written + + +class ConsoleStream: + def __init__(self, text_stream: t.TextIO, byte_stream: t.BinaryIO) -> None: + self._text_stream = text_stream + self.buffer = byte_stream + + @property + def name(self) -> str: + return self.buffer.name + + def write(self, x: t.AnyStr) -> int: + if isinstance(x, str): + return self._text_stream.write(x) + try: + self.flush() + except Exception: + pass + return self.buffer.write(x) + + def writelines(self, lines: t.Iterable[t.AnyStr]) -> None: + for line in lines: + self.write(line) + + def __getattr__(self, name: str) -> t.Any: + return getattr(self._text_stream, name) + + def isatty(self) -> bool: + return self.buffer.isatty() + + def __repr__(self): + return f"" + + +def _get_text_stdin(buffer_stream: t.BinaryIO) -> t.TextIO: + text_stream = _NonClosingTextIOWrapper( + io.BufferedReader(_WindowsConsoleReader(STDIN_HANDLE)), + "utf-16-le", + "strict", + line_buffering=True, + ) + return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream)) + + +def _get_text_stdout(buffer_stream: t.BinaryIO) -> t.TextIO: + text_stream = _NonClosingTextIOWrapper( + io.BufferedWriter(_WindowsConsoleWriter(STDOUT_HANDLE)), + "utf-16-le", + "strict", + line_buffering=True, + ) + return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream)) + + +def _get_text_stderr(buffer_stream: t.BinaryIO) -> t.TextIO: + text_stream = _NonClosingTextIOWrapper( + io.BufferedWriter(_WindowsConsoleWriter(STDERR_HANDLE)), + "utf-16-le", + "strict", + line_buffering=True, + ) + return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream)) + + +_stream_factories: t.Mapping[int, t.Callable[[t.BinaryIO], t.TextIO]] = { + 0: _get_text_stdin, + 1: _get_text_stdout, + 2: _get_text_stderr, +} + + +def _is_console(f: t.TextIO) -> bool: + if not hasattr(f, "fileno"): + return False + + try: + fileno = f.fileno() + except (OSError, io.UnsupportedOperation): + return False + + handle = msvcrt.get_osfhandle(fileno) + return bool(GetConsoleMode(handle, byref(DWORD()))) + + +def _get_windows_console_stream( + f: t.TextIO, encoding: t.Optional[str], errors: t.Optional[str] +) -> t.Optional[t.TextIO]: + if ( + get_buffer is not None + and encoding in {"utf-16-le", None} + and errors in {"strict", None} + and _is_console(f) + ): + func = _stream_factories.get(f.fileno()) + if func is not None: + b = getattr(f, "buffer", None) + + if b is None: + return None + + return func(b) diff --git a/lib/go-jinja2/internal/data/darwin-amd64/click/core.py b/lib/go-jinja2/internal/data/darwin-amd64/click/core.py new file mode 100644 index 000000000..cc65e896b --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/click/core.py @@ -0,0 +1,3042 @@ +import enum +import errno +import inspect +import os +import sys +import typing as t +from collections import abc +from contextlib import contextmanager +from contextlib import ExitStack +from functools import update_wrapper +from gettext import gettext as _ +from gettext import ngettext +from itertools import repeat +from types import TracebackType + +from . import types +from .exceptions import Abort +from .exceptions import BadParameter +from .exceptions import ClickException +from .exceptions import Exit +from .exceptions import MissingParameter +from .exceptions import UsageError +from .formatting import HelpFormatter +from .formatting import join_options +from .globals import pop_context +from .globals import push_context +from .parser import _flag_needs_value +from .parser import OptionParser +from .parser import split_opt +from .termui import confirm +from .termui import prompt +from .termui import style +from .utils import _detect_program_name +from .utils import _expand_args +from .utils import echo +from .utils import make_default_short_help +from .utils import make_str +from .utils import PacifyFlushWrapper + +if t.TYPE_CHECKING: + import typing_extensions as te + from .shell_completion import CompletionItem + +F = t.TypeVar("F", bound=t.Callable[..., t.Any]) +V = t.TypeVar("V") + + +def _complete_visible_commands( + ctx: "Context", incomplete: str +) -> t.Iterator[t.Tuple[str, "Command"]]: + """List all the subcommands of a group that start with the + incomplete value and aren't hidden. + + :param ctx: Invocation context for the group. + :param incomplete: Value being completed. May be empty. + """ + multi = t.cast(MultiCommand, ctx.command) + + for name in multi.list_commands(ctx): + if name.startswith(incomplete): + command = multi.get_command(ctx, name) + + if command is not None and not command.hidden: + yield name, command + + +def _check_multicommand( + base_command: "MultiCommand", cmd_name: str, cmd: "Command", register: bool = False +) -> None: + if not base_command.chain or not isinstance(cmd, MultiCommand): + return + if register: + hint = ( + "It is not possible to add multi commands as children to" + " another multi command that is in chain mode." + ) + else: + hint = ( + "Found a multi command as subcommand to a multi command" + " that is in chain mode. This is not supported." + ) + raise RuntimeError( + f"{hint}. Command {base_command.name!r} is set to chain and" + f" {cmd_name!r} was added as a subcommand but it in itself is a" + f" multi command. ({cmd_name!r} is a {type(cmd).__name__}" + f" within a chained {type(base_command).__name__} named" + f" {base_command.name!r})." + ) + + +def batch(iterable: t.Iterable[V], batch_size: int) -> t.List[t.Tuple[V, ...]]: + return list(zip(*repeat(iter(iterable), batch_size))) + + +@contextmanager +def augment_usage_errors( + ctx: "Context", param: t.Optional["Parameter"] = None +) -> t.Iterator[None]: + """Context manager that attaches extra information to exceptions.""" + try: + yield + except BadParameter as e: + if e.ctx is None: + e.ctx = ctx + if param is not None and e.param is None: + e.param = param + raise + except UsageError as e: + if e.ctx is None: + e.ctx = ctx + raise + + +def iter_params_for_processing( + invocation_order: t.Sequence["Parameter"], + declaration_order: t.Sequence["Parameter"], +) -> t.List["Parameter"]: + """Given a sequence of parameters in the order as should be considered + for processing and an iterable of parameters that exist, this returns + a list in the correct order as they should be processed. + """ + + def sort_key(item: "Parameter") -> t.Tuple[bool, float]: + try: + idx: float = invocation_order.index(item) + except ValueError: + idx = float("inf") + + return not item.is_eager, idx + + return sorted(declaration_order, key=sort_key) + + +class ParameterSource(enum.Enum): + """This is an :class:`~enum.Enum` that indicates the source of a + parameter's value. + + Use :meth:`click.Context.get_parameter_source` to get the + source for a parameter by name. + + .. versionchanged:: 8.0 + Use :class:`~enum.Enum` and drop the ``validate`` method. + + .. versionchanged:: 8.0 + Added the ``PROMPT`` value. + """ + + COMMANDLINE = enum.auto() + """The value was provided by the command line args.""" + ENVIRONMENT = enum.auto() + """The value was provided with an environment variable.""" + DEFAULT = enum.auto() + """Used the default specified by the parameter.""" + DEFAULT_MAP = enum.auto() + """Used a default provided by :attr:`Context.default_map`.""" + PROMPT = enum.auto() + """Used a prompt to confirm a default or provide a value.""" + + +class Context: + """The context is a special internal object that holds state relevant + for the script execution at every single level. It's normally invisible + to commands unless they opt-in to getting access to it. + + The context is useful as it can pass internal objects around and can + control special execution features such as reading data from + environment variables. + + A context can be used as context manager in which case it will call + :meth:`close` on teardown. + + :param command: the command class for this context. + :param parent: the parent context. + :param info_name: the info name for this invocation. Generally this + is the most descriptive name for the script or + command. For the toplevel script it is usually + the name of the script, for commands below it it's + the name of the script. + :param obj: an arbitrary object of user data. + :param auto_envvar_prefix: the prefix to use for automatic environment + variables. If this is `None` then reading + from environment variables is disabled. This + does not affect manually set environment + variables which are always read. + :param default_map: a dictionary (like object) with default values + for parameters. + :param terminal_width: the width of the terminal. The default is + inherit from parent context. If no context + defines the terminal width then auto + detection will be applied. + :param max_content_width: the maximum width for content rendered by + Click (this currently only affects help + pages). This defaults to 80 characters if + not overridden. In other words: even if the + terminal is larger than that, Click will not + format things wider than 80 characters by + default. In addition to that, formatters might + add some safety mapping on the right. + :param resilient_parsing: if this flag is enabled then Click will + parse without any interactivity or callback + invocation. Default values will also be + ignored. This is useful for implementing + things such as completion support. + :param allow_extra_args: if this is set to `True` then extra arguments + at the end will not raise an error and will be + kept on the context. The default is to inherit + from the command. + :param allow_interspersed_args: if this is set to `False` then options + and arguments cannot be mixed. The + default is to inherit from the command. + :param ignore_unknown_options: instructs click to ignore options it does + not know and keeps them for later + processing. + :param help_option_names: optionally a list of strings that define how + the default help parameter is named. The + default is ``['--help']``. + :param token_normalize_func: an optional function that is used to + normalize tokens (options, choices, + etc.). This for instance can be used to + implement case insensitive behavior. + :param color: controls if the terminal supports ANSI colors or not. The + default is autodetection. This is only needed if ANSI + codes are used in texts that Click prints which is by + default not the case. This for instance would affect + help output. + :param show_default: Show the default value for commands. If this + value is not set, it defaults to the value from the parent + context. ``Command.show_default`` overrides this default for the + specific command. + + .. versionchanged:: 8.1 + The ``show_default`` parameter is overridden by + ``Command.show_default``, instead of the other way around. + + .. versionchanged:: 8.0 + The ``show_default`` parameter defaults to the value from the + parent context. + + .. versionchanged:: 7.1 + Added the ``show_default`` parameter. + + .. versionchanged:: 4.0 + Added the ``color``, ``ignore_unknown_options``, and + ``max_content_width`` parameters. + + .. versionchanged:: 3.0 + Added the ``allow_extra_args`` and ``allow_interspersed_args`` + parameters. + + .. versionchanged:: 2.0 + Added the ``resilient_parsing``, ``help_option_names``, and + ``token_normalize_func`` parameters. + """ + + #: The formatter class to create with :meth:`make_formatter`. + #: + #: .. versionadded:: 8.0 + formatter_class: t.Type["HelpFormatter"] = HelpFormatter + + def __init__( + self, + command: "Command", + parent: t.Optional["Context"] = None, + info_name: t.Optional[str] = None, + obj: t.Optional[t.Any] = None, + auto_envvar_prefix: t.Optional[str] = None, + default_map: t.Optional[t.MutableMapping[str, t.Any]] = None, + terminal_width: t.Optional[int] = None, + max_content_width: t.Optional[int] = None, + resilient_parsing: bool = False, + allow_extra_args: t.Optional[bool] = None, + allow_interspersed_args: t.Optional[bool] = None, + ignore_unknown_options: t.Optional[bool] = None, + help_option_names: t.Optional[t.List[str]] = None, + token_normalize_func: t.Optional[t.Callable[[str], str]] = None, + color: t.Optional[bool] = None, + show_default: t.Optional[bool] = None, + ) -> None: + #: the parent context or `None` if none exists. + self.parent = parent + #: the :class:`Command` for this context. + self.command = command + #: the descriptive information name + self.info_name = info_name + #: Map of parameter names to their parsed values. Parameters + #: with ``expose_value=False`` are not stored. + self.params: t.Dict[str, t.Any] = {} + #: the leftover arguments. + self.args: t.List[str] = [] + #: protected arguments. These are arguments that are prepended + #: to `args` when certain parsing scenarios are encountered but + #: must be never propagated to another arguments. This is used + #: to implement nested parsing. + self.protected_args: t.List[str] = [] + #: the collected prefixes of the command's options. + self._opt_prefixes: t.Set[str] = set(parent._opt_prefixes) if parent else set() + + if obj is None and parent is not None: + obj = parent.obj + + #: the user object stored. + self.obj: t.Any = obj + self._meta: t.Dict[str, t.Any] = getattr(parent, "meta", {}) + + #: A dictionary (-like object) with defaults for parameters. + if ( + default_map is None + and info_name is not None + and parent is not None + and parent.default_map is not None + ): + default_map = parent.default_map.get(info_name) + + self.default_map: t.Optional[t.MutableMapping[str, t.Any]] = default_map + + #: This flag indicates if a subcommand is going to be executed. A + #: group callback can use this information to figure out if it's + #: being executed directly or because the execution flow passes + #: onwards to a subcommand. By default it's None, but it can be + #: the name of the subcommand to execute. + #: + #: If chaining is enabled this will be set to ``'*'`` in case + #: any commands are executed. It is however not possible to + #: figure out which ones. If you require this knowledge you + #: should use a :func:`result_callback`. + self.invoked_subcommand: t.Optional[str] = None + + if terminal_width is None and parent is not None: + terminal_width = parent.terminal_width + + #: The width of the terminal (None is autodetection). + self.terminal_width: t.Optional[int] = terminal_width + + if max_content_width is None and parent is not None: + max_content_width = parent.max_content_width + + #: The maximum width of formatted content (None implies a sensible + #: default which is 80 for most things). + self.max_content_width: t.Optional[int] = max_content_width + + if allow_extra_args is None: + allow_extra_args = command.allow_extra_args + + #: Indicates if the context allows extra args or if it should + #: fail on parsing. + #: + #: .. versionadded:: 3.0 + self.allow_extra_args = allow_extra_args + + if allow_interspersed_args is None: + allow_interspersed_args = command.allow_interspersed_args + + #: Indicates if the context allows mixing of arguments and + #: options or not. + #: + #: .. versionadded:: 3.0 + self.allow_interspersed_args: bool = allow_interspersed_args + + if ignore_unknown_options is None: + ignore_unknown_options = command.ignore_unknown_options + + #: Instructs click to ignore options that a command does not + #: understand and will store it on the context for later + #: processing. This is primarily useful for situations where you + #: want to call into external programs. Generally this pattern is + #: strongly discouraged because it's not possibly to losslessly + #: forward all arguments. + #: + #: .. versionadded:: 4.0 + self.ignore_unknown_options: bool = ignore_unknown_options + + if help_option_names is None: + if parent is not None: + help_option_names = parent.help_option_names + else: + help_option_names = ["--help"] + + #: The names for the help options. + self.help_option_names: t.List[str] = help_option_names + + if token_normalize_func is None and parent is not None: + token_normalize_func = parent.token_normalize_func + + #: An optional normalization function for tokens. This is + #: options, choices, commands etc. + self.token_normalize_func: t.Optional[ + t.Callable[[str], str] + ] = token_normalize_func + + #: Indicates if resilient parsing is enabled. In that case Click + #: will do its best to not cause any failures and default values + #: will be ignored. Useful for completion. + self.resilient_parsing: bool = resilient_parsing + + # If there is no envvar prefix yet, but the parent has one and + # the command on this level has a name, we can expand the envvar + # prefix automatically. + if auto_envvar_prefix is None: + if ( + parent is not None + and parent.auto_envvar_prefix is not None + and self.info_name is not None + ): + auto_envvar_prefix = ( + f"{parent.auto_envvar_prefix}_{self.info_name.upper()}" + ) + else: + auto_envvar_prefix = auto_envvar_prefix.upper() + + if auto_envvar_prefix is not None: + auto_envvar_prefix = auto_envvar_prefix.replace("-", "_") + + self.auto_envvar_prefix: t.Optional[str] = auto_envvar_prefix + + if color is None and parent is not None: + color = parent.color + + #: Controls if styling output is wanted or not. + self.color: t.Optional[bool] = color + + if show_default is None and parent is not None: + show_default = parent.show_default + + #: Show option default values when formatting help text. + self.show_default: t.Optional[bool] = show_default + + self._close_callbacks: t.List[t.Callable[[], t.Any]] = [] + self._depth = 0 + self._parameter_source: t.Dict[str, ParameterSource] = {} + self._exit_stack = ExitStack() + + def to_info_dict(self) -> t.Dict[str, t.Any]: + """Gather information that could be useful for a tool generating + user-facing documentation. This traverses the entire CLI + structure. + + .. code-block:: python + + with Context(cli) as ctx: + info = ctx.to_info_dict() + + .. versionadded:: 8.0 + """ + return { + "command": self.command.to_info_dict(self), + "info_name": self.info_name, + "allow_extra_args": self.allow_extra_args, + "allow_interspersed_args": self.allow_interspersed_args, + "ignore_unknown_options": self.ignore_unknown_options, + "auto_envvar_prefix": self.auto_envvar_prefix, + } + + def __enter__(self) -> "Context": + self._depth += 1 + push_context(self) + return self + + def __exit__( + self, + exc_type: t.Optional[t.Type[BaseException]], + exc_value: t.Optional[BaseException], + tb: t.Optional[TracebackType], + ) -> None: + self._depth -= 1 + if self._depth == 0: + self.close() + pop_context() + + @contextmanager + def scope(self, cleanup: bool = True) -> t.Iterator["Context"]: + """This helper method can be used with the context object to promote + it to the current thread local (see :func:`get_current_context`). + The default behavior of this is to invoke the cleanup functions which + can be disabled by setting `cleanup` to `False`. The cleanup + functions are typically used for things such as closing file handles. + + If the cleanup is intended the context object can also be directly + used as a context manager. + + Example usage:: + + with ctx.scope(): + assert get_current_context() is ctx + + This is equivalent:: + + with ctx: + assert get_current_context() is ctx + + .. versionadded:: 5.0 + + :param cleanup: controls if the cleanup functions should be run or + not. The default is to run these functions. In + some situations the context only wants to be + temporarily pushed in which case this can be disabled. + Nested pushes automatically defer the cleanup. + """ + if not cleanup: + self._depth += 1 + try: + with self as rv: + yield rv + finally: + if not cleanup: + self._depth -= 1 + + @property + def meta(self) -> t.Dict[str, t.Any]: + """This is a dictionary which is shared with all the contexts + that are nested. It exists so that click utilities can store some + state here if they need to. It is however the responsibility of + that code to manage this dictionary well. + + The keys are supposed to be unique dotted strings. For instance + module paths are a good choice for it. What is stored in there is + irrelevant for the operation of click. However what is important is + that code that places data here adheres to the general semantics of + the system. + + Example usage:: + + LANG_KEY = f'{__name__}.lang' + + def set_language(value): + ctx = get_current_context() + ctx.meta[LANG_KEY] = value + + def get_language(): + return get_current_context().meta.get(LANG_KEY, 'en_US') + + .. versionadded:: 5.0 + """ + return self._meta + + def make_formatter(self) -> HelpFormatter: + """Creates the :class:`~click.HelpFormatter` for the help and + usage output. + + To quickly customize the formatter class used without overriding + this method, set the :attr:`formatter_class` attribute. + + .. versionchanged:: 8.0 + Added the :attr:`formatter_class` attribute. + """ + return self.formatter_class( + width=self.terminal_width, max_width=self.max_content_width + ) + + def with_resource(self, context_manager: t.ContextManager[V]) -> V: + """Register a resource as if it were used in a ``with`` + statement. The resource will be cleaned up when the context is + popped. + + Uses :meth:`contextlib.ExitStack.enter_context`. It calls the + resource's ``__enter__()`` method and returns the result. When + the context is popped, it closes the stack, which calls the + resource's ``__exit__()`` method. + + To register a cleanup function for something that isn't a + context manager, use :meth:`call_on_close`. Or use something + from :mod:`contextlib` to turn it into a context manager first. + + .. code-block:: python + + @click.group() + @click.option("--name") + @click.pass_context + def cli(ctx): + ctx.obj = ctx.with_resource(connect_db(name)) + + :param context_manager: The context manager to enter. + :return: Whatever ``context_manager.__enter__()`` returns. + + .. versionadded:: 8.0 + """ + return self._exit_stack.enter_context(context_manager) + + def call_on_close(self, f: t.Callable[..., t.Any]) -> t.Callable[..., t.Any]: + """Register a function to be called when the context tears down. + + This can be used to close resources opened during the script + execution. Resources that support Python's context manager + protocol which would be used in a ``with`` statement should be + registered with :meth:`with_resource` instead. + + :param f: The function to execute on teardown. + """ + return self._exit_stack.callback(f) + + def close(self) -> None: + """Invoke all close callbacks registered with + :meth:`call_on_close`, and exit all context managers entered + with :meth:`with_resource`. + """ + self._exit_stack.close() + # In case the context is reused, create a new exit stack. + self._exit_stack = ExitStack() + + @property + def command_path(self) -> str: + """The computed command path. This is used for the ``usage`` + information on the help page. It's automatically created by + combining the info names of the chain of contexts to the root. + """ + rv = "" + if self.info_name is not None: + rv = self.info_name + if self.parent is not None: + parent_command_path = [self.parent.command_path] + + if isinstance(self.parent.command, Command): + for param in self.parent.command.get_params(self): + parent_command_path.extend(param.get_usage_pieces(self)) + + rv = f"{' '.join(parent_command_path)} {rv}" + return rv.lstrip() + + def find_root(self) -> "Context": + """Finds the outermost context.""" + node = self + while node.parent is not None: + node = node.parent + return node + + def find_object(self, object_type: t.Type[V]) -> t.Optional[V]: + """Finds the closest object of a given type.""" + node: t.Optional["Context"] = self + + while node is not None: + if isinstance(node.obj, object_type): + return node.obj + + node = node.parent + + return None + + def ensure_object(self, object_type: t.Type[V]) -> V: + """Like :meth:`find_object` but sets the innermost object to a + new instance of `object_type` if it does not exist. + """ + rv = self.find_object(object_type) + if rv is None: + self.obj = rv = object_type() + return rv + + @t.overload + def lookup_default( + self, name: str, call: "te.Literal[True]" = True + ) -> t.Optional[t.Any]: + ... + + @t.overload + def lookup_default( + self, name: str, call: "te.Literal[False]" = ... + ) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]: + ... + + def lookup_default(self, name: str, call: bool = True) -> t.Optional[t.Any]: + """Get the default for a parameter from :attr:`default_map`. + + :param name: Name of the parameter. + :param call: If the default is a callable, call it. Disable to + return the callable instead. + + .. versionchanged:: 8.0 + Added the ``call`` parameter. + """ + if self.default_map is not None: + value = self.default_map.get(name) + + if call and callable(value): + return value() + + return value + + return None + + def fail(self, message: str) -> "te.NoReturn": + """Aborts the execution of the program with a specific error + message. + + :param message: the error message to fail with. + """ + raise UsageError(message, self) + + def abort(self) -> "te.NoReturn": + """Aborts the script.""" + raise Abort() + + def exit(self, code: int = 0) -> "te.NoReturn": + """Exits the application with a given exit code.""" + raise Exit(code) + + def get_usage(self) -> str: + """Helper method to get formatted usage string for the current + context and command. + """ + return self.command.get_usage(self) + + def get_help(self) -> str: + """Helper method to get formatted help page for the current + context and command. + """ + return self.command.get_help(self) + + def _make_sub_context(self, command: "Command") -> "Context": + """Create a new context of the same type as this context, but + for a new command. + + :meta private: + """ + return type(self)(command, info_name=command.name, parent=self) + + @t.overload + def invoke( + __self, # noqa: B902 + __callback: "t.Callable[..., V]", + *args: t.Any, + **kwargs: t.Any, + ) -> V: + ... + + @t.overload + def invoke( + __self, # noqa: B902 + __callback: "Command", + *args: t.Any, + **kwargs: t.Any, + ) -> t.Any: + ... + + def invoke( + __self, # noqa: B902 + __callback: t.Union["Command", "t.Callable[..., V]"], + *args: t.Any, + **kwargs: t.Any, + ) -> t.Union[t.Any, V]: + """Invokes a command callback in exactly the way it expects. There + are two ways to invoke this method: + + 1. the first argument can be a callback and all other arguments and + keyword arguments are forwarded directly to the function. + 2. the first argument is a click command object. In that case all + arguments are forwarded as well but proper click parameters + (options and click arguments) must be keyword arguments and Click + will fill in defaults. + + Note that before Click 3.2 keyword arguments were not properly filled + in against the intention of this code and no context was created. For + more information about this change and why it was done in a bugfix + release see :ref:`upgrade-to-3.2`. + + .. versionchanged:: 8.0 + All ``kwargs`` are tracked in :attr:`params` so they will be + passed if :meth:`forward` is called at multiple levels. + """ + if isinstance(__callback, Command): + other_cmd = __callback + + if other_cmd.callback is None: + raise TypeError( + "The given command does not have a callback that can be invoked." + ) + else: + __callback = t.cast("t.Callable[..., V]", other_cmd.callback) + + ctx = __self._make_sub_context(other_cmd) + + for param in other_cmd.params: + if param.name not in kwargs and param.expose_value: + kwargs[param.name] = param.type_cast_value( # type: ignore + ctx, param.get_default(ctx) + ) + + # Track all kwargs as params, so that forward() will pass + # them on in subsequent calls. + ctx.params.update(kwargs) + else: + ctx = __self + + with augment_usage_errors(__self): + with ctx: + return __callback(*args, **kwargs) + + def forward( + __self, __cmd: "Command", *args: t.Any, **kwargs: t.Any # noqa: B902 + ) -> t.Any: + """Similar to :meth:`invoke` but fills in default keyword + arguments from the current context if the other command expects + it. This cannot invoke callbacks directly, only other commands. + + .. versionchanged:: 8.0 + All ``kwargs`` are tracked in :attr:`params` so they will be + passed if ``forward`` is called at multiple levels. + """ + # Can only forward to other commands, not direct callbacks. + if not isinstance(__cmd, Command): + raise TypeError("Callback is not a command.") + + for param in __self.params: + if param not in kwargs: + kwargs[param] = __self.params[param] + + return __self.invoke(__cmd, *args, **kwargs) + + def set_parameter_source(self, name: str, source: ParameterSource) -> None: + """Set the source of a parameter. This indicates the location + from which the value of the parameter was obtained. + + :param name: The name of the parameter. + :param source: A member of :class:`~click.core.ParameterSource`. + """ + self._parameter_source[name] = source + + def get_parameter_source(self, name: str) -> t.Optional[ParameterSource]: + """Get the source of a parameter. This indicates the location + from which the value of the parameter was obtained. + + This can be useful for determining when a user specified a value + on the command line that is the same as the default value. It + will be :attr:`~click.core.ParameterSource.DEFAULT` only if the + value was actually taken from the default. + + :param name: The name of the parameter. + :rtype: ParameterSource + + .. versionchanged:: 8.0 + Returns ``None`` if the parameter was not provided from any + source. + """ + return self._parameter_source.get(name) + + +class BaseCommand: + """The base command implements the minimal API contract of commands. + Most code will never use this as it does not implement a lot of useful + functionality but it can act as the direct subclass of alternative + parsing methods that do not depend on the Click parser. + + For instance, this can be used to bridge Click and other systems like + argparse or docopt. + + Because base commands do not implement a lot of the API that other + parts of Click take for granted, they are not supported for all + operations. For instance, they cannot be used with the decorators + usually and they have no built-in callback system. + + .. versionchanged:: 2.0 + Added the `context_settings` parameter. + + :param name: the name of the command to use unless a group overrides it. + :param context_settings: an optional dictionary with defaults that are + passed to the context object. + """ + + #: The context class to create with :meth:`make_context`. + #: + #: .. versionadded:: 8.0 + context_class: t.Type[Context] = Context + #: the default for the :attr:`Context.allow_extra_args` flag. + allow_extra_args = False + #: the default for the :attr:`Context.allow_interspersed_args` flag. + allow_interspersed_args = True + #: the default for the :attr:`Context.ignore_unknown_options` flag. + ignore_unknown_options = False + + def __init__( + self, + name: t.Optional[str], + context_settings: t.Optional[t.MutableMapping[str, t.Any]] = None, + ) -> None: + #: the name the command thinks it has. Upon registering a command + #: on a :class:`Group` the group will default the command name + #: with this information. You should instead use the + #: :class:`Context`\'s :attr:`~Context.info_name` attribute. + self.name = name + + if context_settings is None: + context_settings = {} + + #: an optional dictionary with defaults passed to the context. + self.context_settings: t.MutableMapping[str, t.Any] = context_settings + + def to_info_dict(self, ctx: Context) -> t.Dict[str, t.Any]: + """Gather information that could be useful for a tool generating + user-facing documentation. This traverses the entire structure + below this command. + + Use :meth:`click.Context.to_info_dict` to traverse the entire + CLI structure. + + :param ctx: A :class:`Context` representing this command. + + .. versionadded:: 8.0 + """ + return {"name": self.name} + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} {self.name}>" + + def get_usage(self, ctx: Context) -> str: + raise NotImplementedError("Base commands cannot get usage") + + def get_help(self, ctx: Context) -> str: + raise NotImplementedError("Base commands cannot get help") + + def make_context( + self, + info_name: t.Optional[str], + args: t.List[str], + parent: t.Optional[Context] = None, + **extra: t.Any, + ) -> Context: + """This function when given an info name and arguments will kick + off the parsing and create a new :class:`Context`. It does not + invoke the actual command callback though. + + To quickly customize the context class used without overriding + this method, set the :attr:`context_class` attribute. + + :param info_name: the info name for this invocation. Generally this + is the most descriptive name for the script or + command. For the toplevel script it's usually + the name of the script, for commands below it's + the name of the command. + :param args: the arguments to parse as list of strings. + :param parent: the parent context if available. + :param extra: extra keyword arguments forwarded to the context + constructor. + + .. versionchanged:: 8.0 + Added the :attr:`context_class` attribute. + """ + for key, value in self.context_settings.items(): + if key not in extra: + extra[key] = value + + ctx = self.context_class( + self, info_name=info_name, parent=parent, **extra # type: ignore + ) + + with ctx.scope(cleanup=False): + self.parse_args(ctx, args) + return ctx + + def parse_args(self, ctx: Context, args: t.List[str]) -> t.List[str]: + """Given a context and a list of arguments this creates the parser + and parses the arguments, then modifies the context as necessary. + This is automatically invoked by :meth:`make_context`. + """ + raise NotImplementedError("Base commands do not know how to parse arguments.") + + def invoke(self, ctx: Context) -> t.Any: + """Given a context, this invokes the command. The default + implementation is raising a not implemented error. + """ + raise NotImplementedError("Base commands are not invocable by default") + + def shell_complete(self, ctx: Context, incomplete: str) -> t.List["CompletionItem"]: + """Return a list of completions for the incomplete value. Looks + at the names of chained multi-commands. + + Any command could be part of a chained multi-command, so sibling + commands are valid at any point during command completion. Other + command classes will return more completions. + + :param ctx: Invocation context for this command. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from click.shell_completion import CompletionItem + + results: t.List["CompletionItem"] = [] + + while ctx.parent is not None: + ctx = ctx.parent + + if isinstance(ctx.command, MultiCommand) and ctx.command.chain: + results.extend( + CompletionItem(name, help=command.get_short_help_str()) + for name, command in _complete_visible_commands(ctx, incomplete) + if name not in ctx.protected_args + ) + + return results + + @t.overload + def main( + self, + args: t.Optional[t.Sequence[str]] = None, + prog_name: t.Optional[str] = None, + complete_var: t.Optional[str] = None, + standalone_mode: "te.Literal[True]" = True, + **extra: t.Any, + ) -> "te.NoReturn": + ... + + @t.overload + def main( + self, + args: t.Optional[t.Sequence[str]] = None, + prog_name: t.Optional[str] = None, + complete_var: t.Optional[str] = None, + standalone_mode: bool = ..., + **extra: t.Any, + ) -> t.Any: + ... + + def main( + self, + args: t.Optional[t.Sequence[str]] = None, + prog_name: t.Optional[str] = None, + complete_var: t.Optional[str] = None, + standalone_mode: bool = True, + windows_expand_args: bool = True, + **extra: t.Any, + ) -> t.Any: + """This is the way to invoke a script with all the bells and + whistles as a command line application. This will always terminate + the application after a call. If this is not wanted, ``SystemExit`` + needs to be caught. + + This method is also available by directly calling the instance of + a :class:`Command`. + + :param args: the arguments that should be used for parsing. If not + provided, ``sys.argv[1:]`` is used. + :param prog_name: the program name that should be used. By default + the program name is constructed by taking the file + name from ``sys.argv[0]``. + :param complete_var: the environment variable that controls the + bash completion support. The default is + ``"__COMPLETE"`` with prog_name in + uppercase. + :param standalone_mode: the default behavior is to invoke the script + in standalone mode. Click will then + handle exceptions and convert them into + error messages and the function will never + return but shut down the interpreter. If + this is set to `False` they will be + propagated to the caller and the return + value of this function is the return value + of :meth:`invoke`. + :param windows_expand_args: Expand glob patterns, user dir, and + env vars in command line args on Windows. + :param extra: extra keyword arguments are forwarded to the context + constructor. See :class:`Context` for more information. + + .. versionchanged:: 8.0.1 + Added the ``windows_expand_args`` parameter to allow + disabling command line arg expansion on Windows. + + .. versionchanged:: 8.0 + When taking arguments from ``sys.argv`` on Windows, glob + patterns, user dir, and env vars are expanded. + + .. versionchanged:: 3.0 + Added the ``standalone_mode`` parameter. + """ + if args is None: + args = sys.argv[1:] + + if os.name == "nt" and windows_expand_args: + args = _expand_args(args) + else: + args = list(args) + + if prog_name is None: + prog_name = _detect_program_name() + + # Process shell completion requests and exit early. + self._main_shell_completion(extra, prog_name, complete_var) + + try: + try: + with self.make_context(prog_name, args, **extra) as ctx: + rv = self.invoke(ctx) + if not standalone_mode: + return rv + # it's not safe to `ctx.exit(rv)` here! + # note that `rv` may actually contain data like "1" which + # has obvious effects + # more subtle case: `rv=[None, None]` can come out of + # chained commands which all returned `None` -- so it's not + # even always obvious that `rv` indicates success/failure + # by its truthiness/falsiness + ctx.exit() + except (EOFError, KeyboardInterrupt) as e: + echo(file=sys.stderr) + raise Abort() from e + except ClickException as e: + if not standalone_mode: + raise + e.show() + sys.exit(e.exit_code) + except OSError as e: + if e.errno == errno.EPIPE: + sys.stdout = t.cast(t.TextIO, PacifyFlushWrapper(sys.stdout)) + sys.stderr = t.cast(t.TextIO, PacifyFlushWrapper(sys.stderr)) + sys.exit(1) + else: + raise + except Exit as e: + if standalone_mode: + sys.exit(e.exit_code) + else: + # in non-standalone mode, return the exit code + # note that this is only reached if `self.invoke` above raises + # an Exit explicitly -- thus bypassing the check there which + # would return its result + # the results of non-standalone execution may therefore be + # somewhat ambiguous: if there are codepaths which lead to + # `ctx.exit(1)` and to `return 1`, the caller won't be able to + # tell the difference between the two + return e.exit_code + except Abort: + if not standalone_mode: + raise + echo(_("Aborted!"), file=sys.stderr) + sys.exit(1) + + def _main_shell_completion( + self, + ctx_args: t.MutableMapping[str, t.Any], + prog_name: str, + complete_var: t.Optional[str] = None, + ) -> None: + """Check if the shell is asking for tab completion, process + that, then exit early. Called from :meth:`main` before the + program is invoked. + + :param prog_name: Name of the executable in the shell. + :param complete_var: Name of the environment variable that holds + the completion instruction. Defaults to + ``_{PROG_NAME}_COMPLETE``. + + .. versionchanged:: 8.2.0 + Dots (``.``) in ``prog_name`` are replaced with underscores (``_``). + """ + if complete_var is None: + complete_name = prog_name.replace("-", "_").replace(".", "_") + complete_var = f"_{complete_name}_COMPLETE".upper() + + instruction = os.environ.get(complete_var) + + if not instruction: + return + + from .shell_completion import shell_complete + + rv = shell_complete(self, ctx_args, prog_name, complete_var, instruction) + sys.exit(rv) + + def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Any: + """Alias for :meth:`main`.""" + return self.main(*args, **kwargs) + + +class Command(BaseCommand): + """Commands are the basic building block of command line interfaces in + Click. A basic command handles command line parsing and might dispatch + more parsing to commands nested below it. + + :param name: the name of the command to use unless a group overrides it. + :param context_settings: an optional dictionary with defaults that are + passed to the context object. + :param callback: the callback to invoke. This is optional. + :param params: the parameters to register with this command. This can + be either :class:`Option` or :class:`Argument` objects. + :param help: the help string to use for this command. + :param epilog: like the help string but it's printed at the end of the + help page after everything else. + :param short_help: the short help to use for this command. This is + shown on the command listing of the parent command. + :param add_help_option: by default each command registers a ``--help`` + option. This can be disabled by this parameter. + :param no_args_is_help: this controls what happens if no arguments are + provided. This option is disabled by default. + If enabled this will add ``--help`` as argument + if no arguments are passed + :param hidden: hide this command from help outputs. + + :param deprecated: issues a message indicating that + the command is deprecated. + + .. versionchanged:: 8.1 + ``help``, ``epilog``, and ``short_help`` are stored unprocessed, + all formatting is done when outputting help text, not at init, + and is done even if not using the ``@command`` decorator. + + .. versionchanged:: 8.0 + Added a ``repr`` showing the command name. + + .. versionchanged:: 7.1 + Added the ``no_args_is_help`` parameter. + + .. versionchanged:: 2.0 + Added the ``context_settings`` parameter. + """ + + def __init__( + self, + name: t.Optional[str], + context_settings: t.Optional[t.MutableMapping[str, t.Any]] = None, + callback: t.Optional[t.Callable[..., t.Any]] = None, + params: t.Optional[t.List["Parameter"]] = None, + help: t.Optional[str] = None, + epilog: t.Optional[str] = None, + short_help: t.Optional[str] = None, + options_metavar: t.Optional[str] = "[OPTIONS]", + add_help_option: bool = True, + no_args_is_help: bool = False, + hidden: bool = False, + deprecated: bool = False, + ) -> None: + super().__init__(name, context_settings) + #: the callback to execute when the command fires. This might be + #: `None` in which case nothing happens. + self.callback = callback + #: the list of parameters for this command in the order they + #: should show up in the help page and execute. Eager parameters + #: will automatically be handled before non eager ones. + self.params: t.List["Parameter"] = params or [] + self.help = help + self.epilog = epilog + self.options_metavar = options_metavar + self.short_help = short_help + self.add_help_option = add_help_option + self.no_args_is_help = no_args_is_help + self.hidden = hidden + self.deprecated = deprecated + + def to_info_dict(self, ctx: Context) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict(ctx) + info_dict.update( + params=[param.to_info_dict() for param in self.get_params(ctx)], + help=self.help, + epilog=self.epilog, + short_help=self.short_help, + hidden=self.hidden, + deprecated=self.deprecated, + ) + return info_dict + + def get_usage(self, ctx: Context) -> str: + """Formats the usage line into a string and returns it. + + Calls :meth:`format_usage` internally. + """ + formatter = ctx.make_formatter() + self.format_usage(ctx, formatter) + return formatter.getvalue().rstrip("\n") + + def get_params(self, ctx: Context) -> t.List["Parameter"]: + rv = self.params + help_option = self.get_help_option(ctx) + + if help_option is not None: + rv = [*rv, help_option] + + return rv + + def format_usage(self, ctx: Context, formatter: HelpFormatter) -> None: + """Writes the usage line into the formatter. + + This is a low-level method called by :meth:`get_usage`. + """ + pieces = self.collect_usage_pieces(ctx) + formatter.write_usage(ctx.command_path, " ".join(pieces)) + + def collect_usage_pieces(self, ctx: Context) -> t.List[str]: + """Returns all the pieces that go into the usage line and returns + it as a list of strings. + """ + rv = [self.options_metavar] if self.options_metavar else [] + + for param in self.get_params(ctx): + rv.extend(param.get_usage_pieces(ctx)) + + return rv + + def get_help_option_names(self, ctx: Context) -> t.List[str]: + """Returns the names for the help option.""" + all_names = set(ctx.help_option_names) + for param in self.params: + all_names.difference_update(param.opts) + all_names.difference_update(param.secondary_opts) + return list(all_names) + + def get_help_option(self, ctx: Context) -> t.Optional["Option"]: + """Returns the help option object.""" + help_options = self.get_help_option_names(ctx) + + if not help_options or not self.add_help_option: + return None + + def show_help(ctx: Context, param: "Parameter", value: str) -> None: + if value and not ctx.resilient_parsing: + echo(ctx.get_help(), color=ctx.color) + ctx.exit() + + return Option( + help_options, + is_flag=True, + is_eager=True, + expose_value=False, + callback=show_help, + help=_("Show this message and exit."), + ) + + def make_parser(self, ctx: Context) -> OptionParser: + """Creates the underlying option parser for this command.""" + parser = OptionParser(ctx) + for param in self.get_params(ctx): + param.add_to_parser(parser, ctx) + return parser + + def get_help(self, ctx: Context) -> str: + """Formats the help into a string and returns it. + + Calls :meth:`format_help` internally. + """ + formatter = ctx.make_formatter() + self.format_help(ctx, formatter) + return formatter.getvalue().rstrip("\n") + + def get_short_help_str(self, limit: int = 45) -> str: + """Gets short help for the command or makes it by shortening the + long help string. + """ + if self.short_help: + text = inspect.cleandoc(self.short_help) + elif self.help: + text = make_default_short_help(self.help, limit) + else: + text = "" + + if self.deprecated: + text = _("(Deprecated) {text}").format(text=text) + + return text.strip() + + def format_help(self, ctx: Context, formatter: HelpFormatter) -> None: + """Writes the help into the formatter if it exists. + + This is a low-level method called by :meth:`get_help`. + + This calls the following methods: + + - :meth:`format_usage` + - :meth:`format_help_text` + - :meth:`format_options` + - :meth:`format_epilog` + """ + self.format_usage(ctx, formatter) + self.format_help_text(ctx, formatter) + self.format_options(ctx, formatter) + self.format_epilog(ctx, formatter) + + def format_help_text(self, ctx: Context, formatter: HelpFormatter) -> None: + """Writes the help text to the formatter if it exists.""" + if self.help is not None: + # truncate the help text to the first form feed + text = inspect.cleandoc(self.help).partition("\f")[0] + else: + text = "" + + if self.deprecated: + text = _("(Deprecated) {text}").format(text=text) + + if text: + formatter.write_paragraph() + + with formatter.indentation(): + formatter.write_text(text) + + def format_options(self, ctx: Context, formatter: HelpFormatter) -> None: + """Writes all the options into the formatter if they exist.""" + opts = [] + for param in self.get_params(ctx): + rv = param.get_help_record(ctx) + if rv is not None: + opts.append(rv) + + if opts: + with formatter.section(_("Options")): + formatter.write_dl(opts) + + def format_epilog(self, ctx: Context, formatter: HelpFormatter) -> None: + """Writes the epilog into the formatter if it exists.""" + if self.epilog: + epilog = inspect.cleandoc(self.epilog) + formatter.write_paragraph() + + with formatter.indentation(): + formatter.write_text(epilog) + + def parse_args(self, ctx: Context, args: t.List[str]) -> t.List[str]: + if not args and self.no_args_is_help and not ctx.resilient_parsing: + echo(ctx.get_help(), color=ctx.color) + ctx.exit() + + parser = self.make_parser(ctx) + opts, args, param_order = parser.parse_args(args=args) + + for param in iter_params_for_processing(param_order, self.get_params(ctx)): + value, args = param.handle_parse_result(ctx, opts, args) + + if args and not ctx.allow_extra_args and not ctx.resilient_parsing: + ctx.fail( + ngettext( + "Got unexpected extra argument ({args})", + "Got unexpected extra arguments ({args})", + len(args), + ).format(args=" ".join(map(str, args))) + ) + + ctx.args = args + ctx._opt_prefixes.update(parser._opt_prefixes) + return args + + def invoke(self, ctx: Context) -> t.Any: + """Given a context, this invokes the attached callback (if it exists) + in the right way. + """ + if self.deprecated: + message = _( + "DeprecationWarning: The command {name!r} is deprecated." + ).format(name=self.name) + echo(style(message, fg="red"), err=True) + + if self.callback is not None: + return ctx.invoke(self.callback, **ctx.params) + + def shell_complete(self, ctx: Context, incomplete: str) -> t.List["CompletionItem"]: + """Return a list of completions for the incomplete value. Looks + at the names of options and chained multi-commands. + + :param ctx: Invocation context for this command. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from click.shell_completion import CompletionItem + + results: t.List["CompletionItem"] = [] + + if incomplete and not incomplete[0].isalnum(): + for param in self.get_params(ctx): + if ( + not isinstance(param, Option) + or param.hidden + or ( + not param.multiple + and ctx.get_parameter_source(param.name) # type: ignore + is ParameterSource.COMMANDLINE + ) + ): + continue + + results.extend( + CompletionItem(name, help=param.help) + for name in [*param.opts, *param.secondary_opts] + if name.startswith(incomplete) + ) + + results.extend(super().shell_complete(ctx, incomplete)) + return results + + +class MultiCommand(Command): + """A multi command is the basic implementation of a command that + dispatches to subcommands. The most common version is the + :class:`Group`. + + :param invoke_without_command: this controls how the multi command itself + is invoked. By default it's only invoked + if a subcommand is provided. + :param no_args_is_help: this controls what happens if no arguments are + provided. This option is enabled by default if + `invoke_without_command` is disabled or disabled + if it's enabled. If enabled this will add + ``--help`` as argument if no arguments are + passed. + :param subcommand_metavar: the string that is used in the documentation + to indicate the subcommand place. + :param chain: if this is set to `True` chaining of multiple subcommands + is enabled. This restricts the form of commands in that + they cannot have optional arguments but it allows + multiple commands to be chained together. + :param result_callback: The result callback to attach to this multi + command. This can be set or changed later with the + :meth:`result_callback` decorator. + :param attrs: Other command arguments described in :class:`Command`. + """ + + allow_extra_args = True + allow_interspersed_args = False + + def __init__( + self, + name: t.Optional[str] = None, + invoke_without_command: bool = False, + no_args_is_help: t.Optional[bool] = None, + subcommand_metavar: t.Optional[str] = None, + chain: bool = False, + result_callback: t.Optional[t.Callable[..., t.Any]] = None, + **attrs: t.Any, + ) -> None: + super().__init__(name, **attrs) + + if no_args_is_help is None: + no_args_is_help = not invoke_without_command + + self.no_args_is_help = no_args_is_help + self.invoke_without_command = invoke_without_command + + if subcommand_metavar is None: + if chain: + subcommand_metavar = "COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]..." + else: + subcommand_metavar = "COMMAND [ARGS]..." + + self.subcommand_metavar = subcommand_metavar + self.chain = chain + # The result callback that is stored. This can be set or + # overridden with the :func:`result_callback` decorator. + self._result_callback = result_callback + + if self.chain: + for param in self.params: + if isinstance(param, Argument) and not param.required: + raise RuntimeError( + "Multi commands in chain mode cannot have" + " optional arguments." + ) + + def to_info_dict(self, ctx: Context) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict(ctx) + commands = {} + + for name in self.list_commands(ctx): + command = self.get_command(ctx, name) + + if command is None: + continue + + sub_ctx = ctx._make_sub_context(command) + + with sub_ctx.scope(cleanup=False): + commands[name] = command.to_info_dict(sub_ctx) + + info_dict.update(commands=commands, chain=self.chain) + return info_dict + + def collect_usage_pieces(self, ctx: Context) -> t.List[str]: + rv = super().collect_usage_pieces(ctx) + rv.append(self.subcommand_metavar) + return rv + + def format_options(self, ctx: Context, formatter: HelpFormatter) -> None: + super().format_options(ctx, formatter) + self.format_commands(ctx, formatter) + + def result_callback(self, replace: bool = False) -> t.Callable[[F], F]: + """Adds a result callback to the command. By default if a + result callback is already registered this will chain them but + this can be disabled with the `replace` parameter. The result + callback is invoked with the return value of the subcommand + (or the list of return values from all subcommands if chaining + is enabled) as well as the parameters as they would be passed + to the main callback. + + Example:: + + @click.group() + @click.option('-i', '--input', default=23) + def cli(input): + return 42 + + @cli.result_callback() + def process_result(result, input): + return result + input + + :param replace: if set to `True` an already existing result + callback will be removed. + + .. versionchanged:: 8.0 + Renamed from ``resultcallback``. + + .. versionadded:: 3.0 + """ + + def decorator(f: F) -> F: + old_callback = self._result_callback + + if old_callback is None or replace: + self._result_callback = f + return f + + def function(__value, *args, **kwargs): # type: ignore + inner = old_callback(__value, *args, **kwargs) + return f(inner, *args, **kwargs) + + self._result_callback = rv = update_wrapper(t.cast(F, function), f) + return rv + + return decorator + + def format_commands(self, ctx: Context, formatter: HelpFormatter) -> None: + """Extra format methods for multi methods that adds all the commands + after the options. + """ + commands = [] + for subcommand in self.list_commands(ctx): + cmd = self.get_command(ctx, subcommand) + # What is this, the tool lied about a command. Ignore it + if cmd is None: + continue + if cmd.hidden: + continue + + commands.append((subcommand, cmd)) + + # allow for 3 times the default spacing + if len(commands): + limit = formatter.width - 6 - max(len(cmd[0]) for cmd in commands) + + rows = [] + for subcommand, cmd in commands: + help = cmd.get_short_help_str(limit) + rows.append((subcommand, help)) + + if rows: + with formatter.section(_("Commands")): + formatter.write_dl(rows) + + def parse_args(self, ctx: Context, args: t.List[str]) -> t.List[str]: + if not args and self.no_args_is_help and not ctx.resilient_parsing: + echo(ctx.get_help(), color=ctx.color) + ctx.exit() + + rest = super().parse_args(ctx, args) + + if self.chain: + ctx.protected_args = rest + ctx.args = [] + elif rest: + ctx.protected_args, ctx.args = rest[:1], rest[1:] + + return ctx.args + + def invoke(self, ctx: Context) -> t.Any: + def _process_result(value: t.Any) -> t.Any: + if self._result_callback is not None: + value = ctx.invoke(self._result_callback, value, **ctx.params) + return value + + if not ctx.protected_args: + if self.invoke_without_command: + # No subcommand was invoked, so the result callback is + # invoked with the group return value for regular + # groups, or an empty list for chained groups. + with ctx: + rv = super().invoke(ctx) + return _process_result([] if self.chain else rv) + ctx.fail(_("Missing command.")) + + # Fetch args back out + args = [*ctx.protected_args, *ctx.args] + ctx.args = [] + ctx.protected_args = [] + + # If we're not in chain mode, we only allow the invocation of a + # single command but we also inform the current context about the + # name of the command to invoke. + if not self.chain: + # Make sure the context is entered so we do not clean up + # resources until the result processor has worked. + with ctx: + cmd_name, cmd, args = self.resolve_command(ctx, args) + assert cmd is not None + ctx.invoked_subcommand = cmd_name + super().invoke(ctx) + sub_ctx = cmd.make_context(cmd_name, args, parent=ctx) + with sub_ctx: + return _process_result(sub_ctx.command.invoke(sub_ctx)) + + # In chain mode we create the contexts step by step, but after the + # base command has been invoked. Because at that point we do not + # know the subcommands yet, the invoked subcommand attribute is + # set to ``*`` to inform the command that subcommands are executed + # but nothing else. + with ctx: + ctx.invoked_subcommand = "*" if args else None + super().invoke(ctx) + + # Otherwise we make every single context and invoke them in a + # chain. In that case the return value to the result processor + # is the list of all invoked subcommand's results. + contexts = [] + while args: + cmd_name, cmd, args = self.resolve_command(ctx, args) + assert cmd is not None + sub_ctx = cmd.make_context( + cmd_name, + args, + parent=ctx, + allow_extra_args=True, + allow_interspersed_args=False, + ) + contexts.append(sub_ctx) + args, sub_ctx.args = sub_ctx.args, [] + + rv = [] + for sub_ctx in contexts: + with sub_ctx: + rv.append(sub_ctx.command.invoke(sub_ctx)) + return _process_result(rv) + + def resolve_command( + self, ctx: Context, args: t.List[str] + ) -> t.Tuple[t.Optional[str], t.Optional[Command], t.List[str]]: + cmd_name = make_str(args[0]) + original_cmd_name = cmd_name + + # Get the command + cmd = self.get_command(ctx, cmd_name) + + # If we can't find the command but there is a normalization + # function available, we try with that one. + if cmd is None and ctx.token_normalize_func is not None: + cmd_name = ctx.token_normalize_func(cmd_name) + cmd = self.get_command(ctx, cmd_name) + + # If we don't find the command we want to show an error message + # to the user that it was not provided. However, there is + # something else we should do: if the first argument looks like + # an option we want to kick off parsing again for arguments to + # resolve things like --help which now should go to the main + # place. + if cmd is None and not ctx.resilient_parsing: + if split_opt(cmd_name)[0]: + self.parse_args(ctx, ctx.args) + ctx.fail(_("No such command {name!r}.").format(name=original_cmd_name)) + return cmd_name if cmd else None, cmd, args[1:] + + def get_command(self, ctx: Context, cmd_name: str) -> t.Optional[Command]: + """Given a context and a command name, this returns a + :class:`Command` object if it exists or returns `None`. + """ + raise NotImplementedError + + def list_commands(self, ctx: Context) -> t.List[str]: + """Returns a list of subcommand names in the order they should + appear. + """ + return [] + + def shell_complete(self, ctx: Context, incomplete: str) -> t.List["CompletionItem"]: + """Return a list of completions for the incomplete value. Looks + at the names of options, subcommands, and chained + multi-commands. + + :param ctx: Invocation context for this command. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from click.shell_completion import CompletionItem + + results = [ + CompletionItem(name, help=command.get_short_help_str()) + for name, command in _complete_visible_commands(ctx, incomplete) + ] + results.extend(super().shell_complete(ctx, incomplete)) + return results + + +class Group(MultiCommand): + """A group allows a command to have subcommands attached. This is + the most common way to implement nesting in Click. + + :param name: The name of the group command. + :param commands: A dict mapping names to :class:`Command` objects. + Can also be a list of :class:`Command`, which will use + :attr:`Command.name` to create the dict. + :param attrs: Other command arguments described in + :class:`MultiCommand`, :class:`Command`, and + :class:`BaseCommand`. + + .. versionchanged:: 8.0 + The ``commands`` argument can be a list of command objects. + """ + + #: If set, this is used by the group's :meth:`command` decorator + #: as the default :class:`Command` class. This is useful to make all + #: subcommands use a custom command class. + #: + #: .. versionadded:: 8.0 + command_class: t.Optional[t.Type[Command]] = None + + #: If set, this is used by the group's :meth:`group` decorator + #: as the default :class:`Group` class. This is useful to make all + #: subgroups use a custom group class. + #: + #: If set to the special value :class:`type` (literally + #: ``group_class = type``), this group's class will be used as the + #: default class. This makes a custom group class continue to make + #: custom groups. + #: + #: .. versionadded:: 8.0 + group_class: t.Optional[t.Union[t.Type["Group"], t.Type[type]]] = None + # Literal[type] isn't valid, so use Type[type] + + def __init__( + self, + name: t.Optional[str] = None, + commands: t.Optional[ + t.Union[t.MutableMapping[str, Command], t.Sequence[Command]] + ] = None, + **attrs: t.Any, + ) -> None: + super().__init__(name, **attrs) + + if commands is None: + commands = {} + elif isinstance(commands, abc.Sequence): + commands = {c.name: c for c in commands if c.name is not None} + + #: The registered subcommands by their exported names. + self.commands: t.MutableMapping[str, Command] = commands + + def add_command(self, cmd: Command, name: t.Optional[str] = None) -> None: + """Registers another :class:`Command` with this group. If the name + is not provided, the name of the command is used. + """ + name = name or cmd.name + if name is None: + raise TypeError("Command has no name.") + _check_multicommand(self, name, cmd, register=True) + self.commands[name] = cmd + + @t.overload + def command(self, __func: t.Callable[..., t.Any]) -> Command: + ... + + @t.overload + def command( + self, *args: t.Any, **kwargs: t.Any + ) -> t.Callable[[t.Callable[..., t.Any]], Command]: + ... + + def command( + self, *args: t.Any, **kwargs: t.Any + ) -> t.Union[t.Callable[[t.Callable[..., t.Any]], Command], Command]: + """A shortcut decorator for declaring and attaching a command to + the group. This takes the same arguments as :func:`command` and + immediately registers the created command with this group by + calling :meth:`add_command`. + + To customize the command class used, set the + :attr:`command_class` attribute. + + .. versionchanged:: 8.1 + This decorator can be applied without parentheses. + + .. versionchanged:: 8.0 + Added the :attr:`command_class` attribute. + """ + from .decorators import command + + func: t.Optional[t.Callable[..., t.Any]] = None + + if args and callable(args[0]): + assert ( + len(args) == 1 and not kwargs + ), "Use 'command(**kwargs)(callable)' to provide arguments." + (func,) = args + args = () + + if self.command_class and kwargs.get("cls") is None: + kwargs["cls"] = self.command_class + + def decorator(f: t.Callable[..., t.Any]) -> Command: + cmd: Command = command(*args, **kwargs)(f) + self.add_command(cmd) + return cmd + + if func is not None: + return decorator(func) + + return decorator + + @t.overload + def group(self, __func: t.Callable[..., t.Any]) -> "Group": + ... + + @t.overload + def group( + self, *args: t.Any, **kwargs: t.Any + ) -> t.Callable[[t.Callable[..., t.Any]], "Group"]: + ... + + def group( + self, *args: t.Any, **kwargs: t.Any + ) -> t.Union[t.Callable[[t.Callable[..., t.Any]], "Group"], "Group"]: + """A shortcut decorator for declaring and attaching a group to + the group. This takes the same arguments as :func:`group` and + immediately registers the created group with this group by + calling :meth:`add_command`. + + To customize the group class used, set the :attr:`group_class` + attribute. + + .. versionchanged:: 8.1 + This decorator can be applied without parentheses. + + .. versionchanged:: 8.0 + Added the :attr:`group_class` attribute. + """ + from .decorators import group + + func: t.Optional[t.Callable[..., t.Any]] = None + + if args and callable(args[0]): + assert ( + len(args) == 1 and not kwargs + ), "Use 'group(**kwargs)(callable)' to provide arguments." + (func,) = args + args = () + + if self.group_class is not None and kwargs.get("cls") is None: + if self.group_class is type: + kwargs["cls"] = type(self) + else: + kwargs["cls"] = self.group_class + + def decorator(f: t.Callable[..., t.Any]) -> "Group": + cmd: Group = group(*args, **kwargs)(f) + self.add_command(cmd) + return cmd + + if func is not None: + return decorator(func) + + return decorator + + def get_command(self, ctx: Context, cmd_name: str) -> t.Optional[Command]: + return self.commands.get(cmd_name) + + def list_commands(self, ctx: Context) -> t.List[str]: + return sorted(self.commands) + + +class CommandCollection(MultiCommand): + """A command collection is a multi command that merges multiple multi + commands together into one. This is a straightforward implementation + that accepts a list of different multi commands as sources and + provides all the commands for each of them. + + See :class:`MultiCommand` and :class:`Command` for the description of + ``name`` and ``attrs``. + """ + + def __init__( + self, + name: t.Optional[str] = None, + sources: t.Optional[t.List[MultiCommand]] = None, + **attrs: t.Any, + ) -> None: + super().__init__(name, **attrs) + #: The list of registered multi commands. + self.sources: t.List[MultiCommand] = sources or [] + + def add_source(self, multi_cmd: MultiCommand) -> None: + """Adds a new multi command to the chain dispatcher.""" + self.sources.append(multi_cmd) + + def get_command(self, ctx: Context, cmd_name: str) -> t.Optional[Command]: + for source in self.sources: + rv = source.get_command(ctx, cmd_name) + + if rv is not None: + if self.chain: + _check_multicommand(self, cmd_name, rv) + + return rv + + return None + + def list_commands(self, ctx: Context) -> t.List[str]: + rv: t.Set[str] = set() + + for source in self.sources: + rv.update(source.list_commands(ctx)) + + return sorted(rv) + + +def _check_iter(value: t.Any) -> t.Iterator[t.Any]: + """Check if the value is iterable but not a string. Raises a type + error, or return an iterator over the value. + """ + if isinstance(value, str): + raise TypeError + + return iter(value) + + +class Parameter: + r"""A parameter to a command comes in two versions: they are either + :class:`Option`\s or :class:`Argument`\s. Other subclasses are currently + not supported by design as some of the internals for parsing are + intentionally not finalized. + + Some settings are supported by both options and arguments. + + :param param_decls: the parameter declarations for this option or + argument. This is a list of flags or argument + names. + :param type: the type that should be used. Either a :class:`ParamType` + or a Python type. The latter is converted into the former + automatically if supported. + :param required: controls if this is optional or not. + :param default: the default value if omitted. This can also be a callable, + in which case it's invoked when the default is needed + without any arguments. + :param callback: A function to further process or validate the value + after type conversion. It is called as ``f(ctx, param, value)`` + and must return the value. It is called for all sources, + including prompts. + :param nargs: the number of arguments to match. If not ``1`` the return + value is a tuple instead of single value. The default for + nargs is ``1`` (except if the type is a tuple, then it's + the arity of the tuple). If ``nargs=-1``, all remaining + parameters are collected. + :param metavar: how the value is represented in the help page. + :param expose_value: if this is `True` then the value is passed onwards + to the command callback and stored on the context, + otherwise it's skipped. + :param is_eager: eager values are processed before non eager ones. This + should not be set for arguments or it will inverse the + order of processing. + :param envvar: a string or list of strings that are environment variables + that should be checked. + :param shell_complete: A function that returns custom shell + completions. Used instead of the param's type completion if + given. Takes ``ctx, param, incomplete`` and must return a list + of :class:`~click.shell_completion.CompletionItem` or a list of + strings. + + .. versionchanged:: 8.0 + ``process_value`` validates required parameters and bounded + ``nargs``, and invokes the parameter callback before returning + the value. This allows the callback to validate prompts. + ``full_process_value`` is removed. + + .. versionchanged:: 8.0 + ``autocompletion`` is renamed to ``shell_complete`` and has new + semantics described above. The old name is deprecated and will + be removed in 8.1, until then it will be wrapped to match the + new requirements. + + .. versionchanged:: 8.0 + For ``multiple=True, nargs>1``, the default must be a list of + tuples. + + .. versionchanged:: 8.0 + Setting a default is no longer required for ``nargs>1``, it will + default to ``None``. ``multiple=True`` or ``nargs=-1`` will + default to ``()``. + + .. versionchanged:: 7.1 + Empty environment variables are ignored rather than taking the + empty string value. This makes it possible for scripts to clear + variables if they can't unset them. + + .. versionchanged:: 2.0 + Changed signature for parameter callback to also be passed the + parameter. The old callback format will still work, but it will + raise a warning to give you a chance to migrate the code easier. + """ + + param_type_name = "parameter" + + def __init__( + self, + param_decls: t.Optional[t.Sequence[str]] = None, + type: t.Optional[t.Union[types.ParamType, t.Any]] = None, + required: bool = False, + default: t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]] = None, + callback: t.Optional[t.Callable[[Context, "Parameter", t.Any], t.Any]] = None, + nargs: t.Optional[int] = None, + multiple: bool = False, + metavar: t.Optional[str] = None, + expose_value: bool = True, + is_eager: bool = False, + envvar: t.Optional[t.Union[str, t.Sequence[str]]] = None, + shell_complete: t.Optional[ + t.Callable[ + [Context, "Parameter", str], + t.Union[t.List["CompletionItem"], t.List[str]], + ] + ] = None, + ) -> None: + self.name: t.Optional[str] + self.opts: t.List[str] + self.secondary_opts: t.List[str] + self.name, self.opts, self.secondary_opts = self._parse_decls( + param_decls or (), expose_value + ) + self.type: types.ParamType = types.convert_type(type, default) + + # Default nargs to what the type tells us if we have that + # information available. + if nargs is None: + if self.type.is_composite: + nargs = self.type.arity + else: + nargs = 1 + + self.required = required + self.callback = callback + self.nargs = nargs + self.multiple = multiple + self.expose_value = expose_value + self.default = default + self.is_eager = is_eager + self.metavar = metavar + self.envvar = envvar + self._custom_shell_complete = shell_complete + + if __debug__: + if self.type.is_composite and nargs != self.type.arity: + raise ValueError( + f"'nargs' must be {self.type.arity} (or None) for" + f" type {self.type!r}, but it was {nargs}." + ) + + # Skip no default or callable default. + check_default = default if not callable(default) else None + + if check_default is not None: + if multiple: + try: + # Only check the first value against nargs. + check_default = next(_check_iter(check_default), None) + except TypeError: + raise ValueError( + "'default' must be a list when 'multiple' is true." + ) from None + + # Can be None for multiple with empty default. + if nargs != 1 and check_default is not None: + try: + _check_iter(check_default) + except TypeError: + if multiple: + message = ( + "'default' must be a list of lists when 'multiple' is" + " true and 'nargs' != 1." + ) + else: + message = "'default' must be a list when 'nargs' != 1." + + raise ValueError(message) from None + + if nargs > 1 and len(check_default) != nargs: + subject = "item length" if multiple else "length" + raise ValueError( + f"'default' {subject} must match nargs={nargs}." + ) + + def to_info_dict(self) -> t.Dict[str, t.Any]: + """Gather information that could be useful for a tool generating + user-facing documentation. + + Use :meth:`click.Context.to_info_dict` to traverse the entire + CLI structure. + + .. versionadded:: 8.0 + """ + return { + "name": self.name, + "param_type_name": self.param_type_name, + "opts": self.opts, + "secondary_opts": self.secondary_opts, + "type": self.type.to_info_dict(), + "required": self.required, + "nargs": self.nargs, + "multiple": self.multiple, + "default": self.default, + "envvar": self.envvar, + } + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} {self.name}>" + + def _parse_decls( + self, decls: t.Sequence[str], expose_value: bool + ) -> t.Tuple[t.Optional[str], t.List[str], t.List[str]]: + raise NotImplementedError() + + @property + def human_readable_name(self) -> str: + """Returns the human readable name of this parameter. This is the + same as the name for options, but the metavar for arguments. + """ + return self.name # type: ignore + + def make_metavar(self) -> str: + if self.metavar is not None: + return self.metavar + + metavar = self.type.get_metavar(self) + + if metavar is None: + metavar = self.type.name.upper() + + if self.nargs != 1: + metavar += "..." + + return metavar + + @t.overload + def get_default( + self, ctx: Context, call: "te.Literal[True]" = True + ) -> t.Optional[t.Any]: + ... + + @t.overload + def get_default( + self, ctx: Context, call: bool = ... + ) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]: + ... + + def get_default( + self, ctx: Context, call: bool = True + ) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]: + """Get the default for the parameter. Tries + :meth:`Context.lookup_default` first, then the local default. + + :param ctx: Current context. + :param call: If the default is a callable, call it. Disable to + return the callable instead. + + .. versionchanged:: 8.0.2 + Type casting is no longer performed when getting a default. + + .. versionchanged:: 8.0.1 + Type casting can fail in resilient parsing mode. Invalid + defaults will not prevent showing help text. + + .. versionchanged:: 8.0 + Looks at ``ctx.default_map`` first. + + .. versionchanged:: 8.0 + Added the ``call`` parameter. + """ + value = ctx.lookup_default(self.name, call=False) # type: ignore + + if value is None: + value = self.default + + if call and callable(value): + value = value() + + return value + + def add_to_parser(self, parser: OptionParser, ctx: Context) -> None: + raise NotImplementedError() + + def consume_value( + self, ctx: Context, opts: t.Mapping[str, t.Any] + ) -> t.Tuple[t.Any, ParameterSource]: + value = opts.get(self.name) # type: ignore + source = ParameterSource.COMMANDLINE + + if value is None: + value = self.value_from_envvar(ctx) + source = ParameterSource.ENVIRONMENT + + if value is None: + value = ctx.lookup_default(self.name) # type: ignore + source = ParameterSource.DEFAULT_MAP + + if value is None: + value = self.get_default(ctx) + source = ParameterSource.DEFAULT + + return value, source + + def type_cast_value(self, ctx: Context, value: t.Any) -> t.Any: + """Convert and validate a value against the option's + :attr:`type`, :attr:`multiple`, and :attr:`nargs`. + """ + if value is None: + return () if self.multiple or self.nargs == -1 else None + + def check_iter(value: t.Any) -> t.Iterator[t.Any]: + try: + return _check_iter(value) + except TypeError: + # This should only happen when passing in args manually, + # the parser should construct an iterable when parsing + # the command line. + raise BadParameter( + _("Value must be an iterable."), ctx=ctx, param=self + ) from None + + if self.nargs == 1 or self.type.is_composite: + + def convert(value: t.Any) -> t.Any: + return self.type(value, param=self, ctx=ctx) + + elif self.nargs == -1: + + def convert(value: t.Any) -> t.Any: # t.Tuple[t.Any, ...] + return tuple(self.type(x, self, ctx) for x in check_iter(value)) + + else: # nargs > 1 + + def convert(value: t.Any) -> t.Any: # t.Tuple[t.Any, ...] + value = tuple(check_iter(value)) + + if len(value) != self.nargs: + raise BadParameter( + ngettext( + "Takes {nargs} values but 1 was given.", + "Takes {nargs} values but {len} were given.", + len(value), + ).format(nargs=self.nargs, len=len(value)), + ctx=ctx, + param=self, + ) + + return tuple(self.type(x, self, ctx) for x in value) + + if self.multiple: + return tuple(convert(x) for x in check_iter(value)) + + return convert(value) + + def value_is_missing(self, value: t.Any) -> bool: + if value is None: + return True + + if (self.nargs != 1 or self.multiple) and value == (): + return True + + return False + + def process_value(self, ctx: Context, value: t.Any) -> t.Any: + value = self.type_cast_value(ctx, value) + + if self.required and self.value_is_missing(value): + raise MissingParameter(ctx=ctx, param=self) + + if self.callback is not None: + value = self.callback(ctx, self, value) + + return value + + def resolve_envvar_value(self, ctx: Context) -> t.Optional[str]: + if self.envvar is None: + return None + + if isinstance(self.envvar, str): + rv = os.environ.get(self.envvar) + + if rv: + return rv + else: + for envvar in self.envvar: + rv = os.environ.get(envvar) + + if rv: + return rv + + return None + + def value_from_envvar(self, ctx: Context) -> t.Optional[t.Any]: + rv: t.Optional[t.Any] = self.resolve_envvar_value(ctx) + + if rv is not None and self.nargs != 1: + rv = self.type.split_envvar_value(rv) + + return rv + + def handle_parse_result( + self, ctx: Context, opts: t.Mapping[str, t.Any], args: t.List[str] + ) -> t.Tuple[t.Any, t.List[str]]: + with augment_usage_errors(ctx, param=self): + value, source = self.consume_value(ctx, opts) + ctx.set_parameter_source(self.name, source) # type: ignore + + try: + value = self.process_value(ctx, value) + except Exception: + if not ctx.resilient_parsing: + raise + + value = None + + if self.expose_value: + ctx.params[self.name] = value # type: ignore + + return value, args + + def get_help_record(self, ctx: Context) -> t.Optional[t.Tuple[str, str]]: + pass + + def get_usage_pieces(self, ctx: Context) -> t.List[str]: + return [] + + def get_error_hint(self, ctx: Context) -> str: + """Get a stringified version of the param for use in error messages to + indicate which param caused the error. + """ + hint_list = self.opts or [self.human_readable_name] + return " / ".join(f"'{x}'" for x in hint_list) + + def shell_complete(self, ctx: Context, incomplete: str) -> t.List["CompletionItem"]: + """Return a list of completions for the incomplete value. If a + ``shell_complete`` function was given during init, it is used. + Otherwise, the :attr:`type` + :meth:`~click.types.ParamType.shell_complete` function is used. + + :param ctx: Invocation context for this command. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + if self._custom_shell_complete is not None: + results = self._custom_shell_complete(ctx, self, incomplete) + + if results and isinstance(results[0], str): + from click.shell_completion import CompletionItem + + results = [CompletionItem(c) for c in results] + + return t.cast(t.List["CompletionItem"], results) + + return self.type.shell_complete(ctx, self, incomplete) + + +class Option(Parameter): + """Options are usually optional values on the command line and + have some extra features that arguments don't have. + + All other parameters are passed onwards to the parameter constructor. + + :param show_default: Show the default value for this option in its + help text. Values are not shown by default, unless + :attr:`Context.show_default` is ``True``. If this value is a + string, it shows that string in parentheses instead of the + actual value. This is particularly useful for dynamic options. + For single option boolean flags, the default remains hidden if + its value is ``False``. + :param show_envvar: Controls if an environment variable should be + shown on the help page. Normally, environment variables are not + shown. + :param prompt: If set to ``True`` or a non empty string then the + user will be prompted for input. If set to ``True`` the prompt + will be the option name capitalized. + :param confirmation_prompt: Prompt a second time to confirm the + value if it was prompted for. Can be set to a string instead of + ``True`` to customize the message. + :param prompt_required: If set to ``False``, the user will be + prompted for input only when the option was specified as a flag + without a value. + :param hide_input: If this is ``True`` then the input on the prompt + will be hidden from the user. This is useful for password input. + :param is_flag: forces this option to act as a flag. The default is + auto detection. + :param flag_value: which value should be used for this flag if it's + enabled. This is set to a boolean automatically if + the option string contains a slash to mark two options. + :param multiple: if this is set to `True` then the argument is accepted + multiple times and recorded. This is similar to ``nargs`` + in how it works but supports arbitrary number of + arguments. + :param count: this flag makes an option increment an integer. + :param allow_from_autoenv: if this is enabled then the value of this + parameter will be pulled from an environment + variable in case a prefix is defined on the + context. + :param help: the help string. + :param hidden: hide this option from help outputs. + :param attrs: Other command arguments described in :class:`Parameter`. + + .. versionchanged:: 8.1.0 + Help text indentation is cleaned here instead of only in the + ``@option`` decorator. + + .. versionchanged:: 8.1.0 + The ``show_default`` parameter overrides + ``Context.show_default``. + + .. versionchanged:: 8.1.0 + The default of a single option boolean flag is not shown if the + default value is ``False``. + + .. versionchanged:: 8.0.1 + ``type`` is detected from ``flag_value`` if given. + """ + + param_type_name = "option" + + def __init__( + self, + param_decls: t.Optional[t.Sequence[str]] = None, + show_default: t.Union[bool, str, None] = None, + prompt: t.Union[bool, str] = False, + confirmation_prompt: t.Union[bool, str] = False, + prompt_required: bool = True, + hide_input: bool = False, + is_flag: t.Optional[bool] = None, + flag_value: t.Optional[t.Any] = None, + multiple: bool = False, + count: bool = False, + allow_from_autoenv: bool = True, + type: t.Optional[t.Union[types.ParamType, t.Any]] = None, + help: t.Optional[str] = None, + hidden: bool = False, + show_choices: bool = True, + show_envvar: bool = False, + **attrs: t.Any, + ) -> None: + if help: + help = inspect.cleandoc(help) + + default_is_missing = "default" not in attrs + super().__init__(param_decls, type=type, multiple=multiple, **attrs) + + if prompt is True: + if self.name is None: + raise TypeError("'name' is required with 'prompt=True'.") + + prompt_text: t.Optional[str] = self.name.replace("_", " ").capitalize() + elif prompt is False: + prompt_text = None + else: + prompt_text = prompt + + self.prompt = prompt_text + self.confirmation_prompt = confirmation_prompt + self.prompt_required = prompt_required + self.hide_input = hide_input + self.hidden = hidden + + # If prompt is enabled but not required, then the option can be + # used as a flag to indicate using prompt or flag_value. + self._flag_needs_value = self.prompt is not None and not self.prompt_required + + if is_flag is None: + if flag_value is not None: + # Implicitly a flag because flag_value was set. + is_flag = True + elif self._flag_needs_value: + # Not a flag, but when used as a flag it shows a prompt. + is_flag = False + else: + # Implicitly a flag because flag options were given. + is_flag = bool(self.secondary_opts) + elif is_flag is False and not self._flag_needs_value: + # Not a flag, and prompt is not enabled, can be used as a + # flag if flag_value is set. + self._flag_needs_value = flag_value is not None + + self.default: t.Union[t.Any, t.Callable[[], t.Any]] + + if is_flag and default_is_missing and not self.required: + if multiple: + self.default = () + else: + self.default = False + + if flag_value is None: + flag_value = not self.default + + self.type: types.ParamType + if is_flag and type is None: + # Re-guess the type from the flag value instead of the + # default. + self.type = types.convert_type(None, flag_value) + + self.is_flag: bool = is_flag + self.is_bool_flag: bool = is_flag and isinstance(self.type, types.BoolParamType) + self.flag_value: t.Any = flag_value + + # Counting + self.count = count + if count: + if type is None: + self.type = types.IntRange(min=0) + if default_is_missing: + self.default = 0 + + self.allow_from_autoenv = allow_from_autoenv + self.help = help + self.show_default = show_default + self.show_choices = show_choices + self.show_envvar = show_envvar + + if __debug__: + if self.nargs == -1: + raise TypeError("nargs=-1 is not supported for options.") + + if self.prompt and self.is_flag and not self.is_bool_flag: + raise TypeError("'prompt' is not valid for non-boolean flag.") + + if not self.is_bool_flag and self.secondary_opts: + raise TypeError("Secondary flag is not valid for non-boolean flag.") + + if self.is_bool_flag and self.hide_input and self.prompt is not None: + raise TypeError( + "'prompt' with 'hide_input' is not valid for boolean flag." + ) + + if self.count: + if self.multiple: + raise TypeError("'count' is not valid with 'multiple'.") + + if self.is_flag: + raise TypeError("'count' is not valid with 'is_flag'.") + + def to_info_dict(self) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict.update( + help=self.help, + prompt=self.prompt, + is_flag=self.is_flag, + flag_value=self.flag_value, + count=self.count, + hidden=self.hidden, + ) + return info_dict + + def _parse_decls( + self, decls: t.Sequence[str], expose_value: bool + ) -> t.Tuple[t.Optional[str], t.List[str], t.List[str]]: + opts = [] + secondary_opts = [] + name = None + possible_names = [] + + for decl in decls: + if decl.isidentifier(): + if name is not None: + raise TypeError(f"Name '{name}' defined twice") + name = decl + else: + split_char = ";" if decl[:1] == "/" else "/" + if split_char in decl: + first, second = decl.split(split_char, 1) + first = first.rstrip() + if first: + possible_names.append(split_opt(first)) + opts.append(first) + second = second.lstrip() + if second: + secondary_opts.append(second.lstrip()) + if first == second: + raise ValueError( + f"Boolean option {decl!r} cannot use the" + " same flag for true/false." + ) + else: + possible_names.append(split_opt(decl)) + opts.append(decl) + + if name is None and possible_names: + possible_names.sort(key=lambda x: -len(x[0])) # group long options first + name = possible_names[0][1].replace("-", "_").lower() + if not name.isidentifier(): + name = None + + if name is None: + if not expose_value: + return None, opts, secondary_opts + raise TypeError("Could not determine name for option") + + if not opts and not secondary_opts: + raise TypeError( + f"No options defined but a name was passed ({name})." + " Did you mean to declare an argument instead? Did" + f" you mean to pass '--{name}'?" + ) + + return name, opts, secondary_opts + + def add_to_parser(self, parser: OptionParser, ctx: Context) -> None: + if self.multiple: + action = "append" + elif self.count: + action = "count" + else: + action = "store" + + if self.is_flag: + action = f"{action}_const" + + if self.is_bool_flag and self.secondary_opts: + parser.add_option( + obj=self, opts=self.opts, dest=self.name, action=action, const=True + ) + parser.add_option( + obj=self, + opts=self.secondary_opts, + dest=self.name, + action=action, + const=False, + ) + else: + parser.add_option( + obj=self, + opts=self.opts, + dest=self.name, + action=action, + const=self.flag_value, + ) + else: + parser.add_option( + obj=self, + opts=self.opts, + dest=self.name, + action=action, + nargs=self.nargs, + ) + + def get_help_record(self, ctx: Context) -> t.Optional[t.Tuple[str, str]]: + if self.hidden: + return None + + any_prefix_is_slash = False + + def _write_opts(opts: t.Sequence[str]) -> str: + nonlocal any_prefix_is_slash + + rv, any_slashes = join_options(opts) + + if any_slashes: + any_prefix_is_slash = True + + if not self.is_flag and not self.count: + rv += f" {self.make_metavar()}" + + return rv + + rv = [_write_opts(self.opts)] + + if self.secondary_opts: + rv.append(_write_opts(self.secondary_opts)) + + help = self.help or "" + extra = [] + + if self.show_envvar: + envvar = self.envvar + + if envvar is None: + if ( + self.allow_from_autoenv + and ctx.auto_envvar_prefix is not None + and self.name is not None + ): + envvar = f"{ctx.auto_envvar_prefix}_{self.name.upper()}" + + if envvar is not None: + var_str = ( + envvar + if isinstance(envvar, str) + else ", ".join(str(d) for d in envvar) + ) + extra.append(_("env var: {var}").format(var=var_str)) + + # Temporarily enable resilient parsing to avoid type casting + # failing for the default. Might be possible to extend this to + # help formatting in general. + resilient = ctx.resilient_parsing + ctx.resilient_parsing = True + + try: + default_value = self.get_default(ctx, call=False) + finally: + ctx.resilient_parsing = resilient + + show_default = False + show_default_is_str = False + + if self.show_default is not None: + if isinstance(self.show_default, str): + show_default_is_str = show_default = True + else: + show_default = self.show_default + elif ctx.show_default is not None: + show_default = ctx.show_default + + if show_default_is_str or (show_default and (default_value is not None)): + if show_default_is_str: + default_string = f"({self.show_default})" + elif isinstance(default_value, (list, tuple)): + default_string = ", ".join(str(d) for d in default_value) + elif inspect.isfunction(default_value): + default_string = _("(dynamic)") + elif self.is_bool_flag and self.secondary_opts: + # For boolean flags that have distinct True/False opts, + # use the opt without prefix instead of the value. + default_string = split_opt( + (self.opts if self.default else self.secondary_opts)[0] + )[1] + elif self.is_bool_flag and not self.secondary_opts and not default_value: + default_string = "" + else: + default_string = str(default_value) + + if default_string: + extra.append(_("default: {default}").format(default=default_string)) + + if ( + isinstance(self.type, types._NumberRangeBase) + # skip count with default range type + and not (self.count and self.type.min == 0 and self.type.max is None) + ): + range_str = self.type._describe_range() + + if range_str: + extra.append(range_str) + + if self.required: + extra.append(_("required")) + + if extra: + extra_str = "; ".join(extra) + help = f"{help} [{extra_str}]" if help else f"[{extra_str}]" + + return ("; " if any_prefix_is_slash else " / ").join(rv), help + + @t.overload + def get_default( + self, ctx: Context, call: "te.Literal[True]" = True + ) -> t.Optional[t.Any]: + ... + + @t.overload + def get_default( + self, ctx: Context, call: bool = ... + ) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]: + ... + + def get_default( + self, ctx: Context, call: bool = True + ) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]: + # If we're a non boolean flag our default is more complex because + # we need to look at all flags in the same group to figure out + # if we're the default one in which case we return the flag + # value as default. + if self.is_flag and not self.is_bool_flag: + for param in ctx.command.params: + if param.name == self.name and param.default: + return t.cast(Option, param).flag_value + + return None + + return super().get_default(ctx, call=call) + + def prompt_for_value(self, ctx: Context) -> t.Any: + """This is an alternative flow that can be activated in the full + value processing if a value does not exist. It will prompt the + user until a valid value exists and then returns the processed + value as result. + """ + assert self.prompt is not None + + # Calculate the default before prompting anything to be stable. + default = self.get_default(ctx) + + # If this is a prompt for a flag we need to handle this + # differently. + if self.is_bool_flag: + return confirm(self.prompt, default) + + return prompt( + self.prompt, + default=default, + type=self.type, + hide_input=self.hide_input, + show_choices=self.show_choices, + confirmation_prompt=self.confirmation_prompt, + value_proc=lambda x: self.process_value(ctx, x), + ) + + def resolve_envvar_value(self, ctx: Context) -> t.Optional[str]: + rv = super().resolve_envvar_value(ctx) + + if rv is not None: + return rv + + if ( + self.allow_from_autoenv + and ctx.auto_envvar_prefix is not None + and self.name is not None + ): + envvar = f"{ctx.auto_envvar_prefix}_{self.name.upper()}" + rv = os.environ.get(envvar) + + if rv: + return rv + + return None + + def value_from_envvar(self, ctx: Context) -> t.Optional[t.Any]: + rv: t.Optional[t.Any] = self.resolve_envvar_value(ctx) + + if rv is None: + return None + + value_depth = (self.nargs != 1) + bool(self.multiple) + + if value_depth > 0: + rv = self.type.split_envvar_value(rv) + + if self.multiple and self.nargs != 1: + rv = batch(rv, self.nargs) + + return rv + + def consume_value( + self, ctx: Context, opts: t.Mapping[str, "Parameter"] + ) -> t.Tuple[t.Any, ParameterSource]: + value, source = super().consume_value(ctx, opts) + + # The parser will emit a sentinel value if the option can be + # given as a flag without a value. This is different from None + # to distinguish from the flag not being given at all. + if value is _flag_needs_value: + if self.prompt is not None and not ctx.resilient_parsing: + value = self.prompt_for_value(ctx) + source = ParameterSource.PROMPT + else: + value = self.flag_value + source = ParameterSource.COMMANDLINE + + elif ( + self.multiple + and value is not None + and any(v is _flag_needs_value for v in value) + ): + value = [self.flag_value if v is _flag_needs_value else v for v in value] + source = ParameterSource.COMMANDLINE + + # The value wasn't set, or used the param's default, prompt if + # prompting is enabled. + elif ( + source in {None, ParameterSource.DEFAULT} + and self.prompt is not None + and (self.required or self.prompt_required) + and not ctx.resilient_parsing + ): + value = self.prompt_for_value(ctx) + source = ParameterSource.PROMPT + + return value, source + + +class Argument(Parameter): + """Arguments are positional parameters to a command. They generally + provide fewer features than options but can have infinite ``nargs`` + and are required by default. + + All parameters are passed onwards to the constructor of :class:`Parameter`. + """ + + param_type_name = "argument" + + def __init__( + self, + param_decls: t.Sequence[str], + required: t.Optional[bool] = None, + **attrs: t.Any, + ) -> None: + if required is None: + if attrs.get("default") is not None: + required = False + else: + required = attrs.get("nargs", 1) > 0 + + if "multiple" in attrs: + raise TypeError("__init__() got an unexpected keyword argument 'multiple'.") + + super().__init__(param_decls, required=required, **attrs) + + if __debug__: + if self.default is not None and self.nargs == -1: + raise TypeError("'default' is not supported for nargs=-1.") + + @property + def human_readable_name(self) -> str: + if self.metavar is not None: + return self.metavar + return self.name.upper() # type: ignore + + def make_metavar(self) -> str: + if self.metavar is not None: + return self.metavar + var = self.type.get_metavar(self) + if not var: + var = self.name.upper() # type: ignore + if not self.required: + var = f"[{var}]" + if self.nargs != 1: + var += "..." + return var + + def _parse_decls( + self, decls: t.Sequence[str], expose_value: bool + ) -> t.Tuple[t.Optional[str], t.List[str], t.List[str]]: + if not decls: + if not expose_value: + return None, [], [] + raise TypeError("Could not determine name for argument") + if len(decls) == 1: + name = arg = decls[0] + name = name.replace("-", "_").lower() + else: + raise TypeError( + "Arguments take exactly one parameter declaration, got" + f" {len(decls)}." + ) + return name, [arg], [] + + def get_usage_pieces(self, ctx: Context) -> t.List[str]: + return [self.make_metavar()] + + def get_error_hint(self, ctx: Context) -> str: + return f"'{self.make_metavar()}'" + + def add_to_parser(self, parser: OptionParser, ctx: Context) -> None: + parser.add_argument(dest=self.name, nargs=self.nargs, obj=self) diff --git a/lib/go-jinja2/internal/data/darwin-amd64/click/decorators.py b/lib/go-jinja2/internal/data/darwin-amd64/click/decorators.py new file mode 100644 index 000000000..d9bba9502 --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/click/decorators.py @@ -0,0 +1,561 @@ +import inspect +import types +import typing as t +from functools import update_wrapper +from gettext import gettext as _ + +from .core import Argument +from .core import Command +from .core import Context +from .core import Group +from .core import Option +from .core import Parameter +from .globals import get_current_context +from .utils import echo + +if t.TYPE_CHECKING: + import typing_extensions as te + + P = te.ParamSpec("P") + +R = t.TypeVar("R") +T = t.TypeVar("T") +_AnyCallable = t.Callable[..., t.Any] +FC = t.TypeVar("FC", bound=t.Union[_AnyCallable, Command]) + + +def pass_context(f: "t.Callable[te.Concatenate[Context, P], R]") -> "t.Callable[P, R]": + """Marks a callback as wanting to receive the current context + object as first argument. + """ + + def new_func(*args: "P.args", **kwargs: "P.kwargs") -> "R": + return f(get_current_context(), *args, **kwargs) + + return update_wrapper(new_func, f) + + +def pass_obj(f: "t.Callable[te.Concatenate[t.Any, P], R]") -> "t.Callable[P, R]": + """Similar to :func:`pass_context`, but only pass the object on the + context onwards (:attr:`Context.obj`). This is useful if that object + represents the state of a nested system. + """ + + def new_func(*args: "P.args", **kwargs: "P.kwargs") -> "R": + return f(get_current_context().obj, *args, **kwargs) + + return update_wrapper(new_func, f) + + +def make_pass_decorator( + object_type: t.Type[T], ensure: bool = False +) -> t.Callable[["t.Callable[te.Concatenate[T, P], R]"], "t.Callable[P, R]"]: + """Given an object type this creates a decorator that will work + similar to :func:`pass_obj` but instead of passing the object of the + current context, it will find the innermost context of type + :func:`object_type`. + + This generates a decorator that works roughly like this:: + + from functools import update_wrapper + + def decorator(f): + @pass_context + def new_func(ctx, *args, **kwargs): + obj = ctx.find_object(object_type) + return ctx.invoke(f, obj, *args, **kwargs) + return update_wrapper(new_func, f) + return decorator + + :param object_type: the type of the object to pass. + :param ensure: if set to `True`, a new object will be created and + remembered on the context if it's not there yet. + """ + + def decorator(f: "t.Callable[te.Concatenate[T, P], R]") -> "t.Callable[P, R]": + def new_func(*args: "P.args", **kwargs: "P.kwargs") -> "R": + ctx = get_current_context() + + obj: t.Optional[T] + if ensure: + obj = ctx.ensure_object(object_type) + else: + obj = ctx.find_object(object_type) + + if obj is None: + raise RuntimeError( + "Managed to invoke callback without a context" + f" object of type {object_type.__name__!r}" + " existing." + ) + + return ctx.invoke(f, obj, *args, **kwargs) + + return update_wrapper(new_func, f) + + return decorator # type: ignore[return-value] + + +def pass_meta_key( + key: str, *, doc_description: t.Optional[str] = None +) -> "t.Callable[[t.Callable[te.Concatenate[t.Any, P], R]], t.Callable[P, R]]": + """Create a decorator that passes a key from + :attr:`click.Context.meta` as the first argument to the decorated + function. + + :param key: Key in ``Context.meta`` to pass. + :param doc_description: Description of the object being passed, + inserted into the decorator's docstring. Defaults to "the 'key' + key from Context.meta". + + .. versionadded:: 8.0 + """ + + def decorator(f: "t.Callable[te.Concatenate[t.Any, P], R]") -> "t.Callable[P, R]": + def new_func(*args: "P.args", **kwargs: "P.kwargs") -> R: + ctx = get_current_context() + obj = ctx.meta[key] + return ctx.invoke(f, obj, *args, **kwargs) + + return update_wrapper(new_func, f) + + if doc_description is None: + doc_description = f"the {key!r} key from :attr:`click.Context.meta`" + + decorator.__doc__ = ( + f"Decorator that passes {doc_description} as the first argument" + " to the decorated function." + ) + return decorator # type: ignore[return-value] + + +CmdType = t.TypeVar("CmdType", bound=Command) + + +# variant: no call, directly as decorator for a function. +@t.overload +def command(name: _AnyCallable) -> Command: + ... + + +# variant: with positional name and with positional or keyword cls argument: +# @command(namearg, CommandCls, ...) or @command(namearg, cls=CommandCls, ...) +@t.overload +def command( + name: t.Optional[str], + cls: t.Type[CmdType], + **attrs: t.Any, +) -> t.Callable[[_AnyCallable], CmdType]: + ... + + +# variant: name omitted, cls _must_ be a keyword argument, @command(cls=CommandCls, ...) +@t.overload +def command( + name: None = None, + *, + cls: t.Type[CmdType], + **attrs: t.Any, +) -> t.Callable[[_AnyCallable], CmdType]: + ... + + +# variant: with optional string name, no cls argument provided. +@t.overload +def command( + name: t.Optional[str] = ..., cls: None = None, **attrs: t.Any +) -> t.Callable[[_AnyCallable], Command]: + ... + + +def command( + name: t.Union[t.Optional[str], _AnyCallable] = None, + cls: t.Optional[t.Type[CmdType]] = None, + **attrs: t.Any, +) -> t.Union[Command, t.Callable[[_AnyCallable], t.Union[Command, CmdType]]]: + r"""Creates a new :class:`Command` and uses the decorated function as + callback. This will also automatically attach all decorated + :func:`option`\s and :func:`argument`\s as parameters to the command. + + The name of the command defaults to the name of the function with + underscores replaced by dashes. If you want to change that, you can + pass the intended name as the first argument. + + All keyword arguments are forwarded to the underlying command class. + For the ``params`` argument, any decorated params are appended to + the end of the list. + + Once decorated the function turns into a :class:`Command` instance + that can be invoked as a command line utility or be attached to a + command :class:`Group`. + + :param name: the name of the command. This defaults to the function + name with underscores replaced by dashes. + :param cls: the command class to instantiate. This defaults to + :class:`Command`. + + .. versionchanged:: 8.1 + This decorator can be applied without parentheses. + + .. versionchanged:: 8.1 + The ``params`` argument can be used. Decorated params are + appended to the end of the list. + """ + + func: t.Optional[t.Callable[[_AnyCallable], t.Any]] = None + + if callable(name): + func = name + name = None + assert cls is None, "Use 'command(cls=cls)(callable)' to specify a class." + assert not attrs, "Use 'command(**kwargs)(callable)' to provide arguments." + + if cls is None: + cls = t.cast(t.Type[CmdType], Command) + + def decorator(f: _AnyCallable) -> CmdType: + if isinstance(f, Command): + raise TypeError("Attempted to convert a callback into a command twice.") + + attr_params = attrs.pop("params", None) + params = attr_params if attr_params is not None else [] + + try: + decorator_params = f.__click_params__ # type: ignore + except AttributeError: + pass + else: + del f.__click_params__ # type: ignore + params.extend(reversed(decorator_params)) + + if attrs.get("help") is None: + attrs["help"] = f.__doc__ + + if t.TYPE_CHECKING: + assert cls is not None + assert not callable(name) + + cmd = cls( + name=name or f.__name__.lower().replace("_", "-"), + callback=f, + params=params, + **attrs, + ) + cmd.__doc__ = f.__doc__ + return cmd + + if func is not None: + return decorator(func) + + return decorator + + +GrpType = t.TypeVar("GrpType", bound=Group) + + +# variant: no call, directly as decorator for a function. +@t.overload +def group(name: _AnyCallable) -> Group: + ... + + +# variant: with positional name and with positional or keyword cls argument: +# @group(namearg, GroupCls, ...) or @group(namearg, cls=GroupCls, ...) +@t.overload +def group( + name: t.Optional[str], + cls: t.Type[GrpType], + **attrs: t.Any, +) -> t.Callable[[_AnyCallable], GrpType]: + ... + + +# variant: name omitted, cls _must_ be a keyword argument, @group(cmd=GroupCls, ...) +@t.overload +def group( + name: None = None, + *, + cls: t.Type[GrpType], + **attrs: t.Any, +) -> t.Callable[[_AnyCallable], GrpType]: + ... + + +# variant: with optional string name, no cls argument provided. +@t.overload +def group( + name: t.Optional[str] = ..., cls: None = None, **attrs: t.Any +) -> t.Callable[[_AnyCallable], Group]: + ... + + +def group( + name: t.Union[str, _AnyCallable, None] = None, + cls: t.Optional[t.Type[GrpType]] = None, + **attrs: t.Any, +) -> t.Union[Group, t.Callable[[_AnyCallable], t.Union[Group, GrpType]]]: + """Creates a new :class:`Group` with a function as callback. This + works otherwise the same as :func:`command` just that the `cls` + parameter is set to :class:`Group`. + + .. versionchanged:: 8.1 + This decorator can be applied without parentheses. + """ + if cls is None: + cls = t.cast(t.Type[GrpType], Group) + + if callable(name): + return command(cls=cls, **attrs)(name) + + return command(name, cls, **attrs) + + +def _param_memo(f: t.Callable[..., t.Any], param: Parameter) -> None: + if isinstance(f, Command): + f.params.append(param) + else: + if not hasattr(f, "__click_params__"): + f.__click_params__ = [] # type: ignore + + f.__click_params__.append(param) # type: ignore + + +def argument( + *param_decls: str, cls: t.Optional[t.Type[Argument]] = None, **attrs: t.Any +) -> t.Callable[[FC], FC]: + """Attaches an argument to the command. All positional arguments are + passed as parameter declarations to :class:`Argument`; all keyword + arguments are forwarded unchanged (except ``cls``). + This is equivalent to creating an :class:`Argument` instance manually + and attaching it to the :attr:`Command.params` list. + + For the default argument class, refer to :class:`Argument` and + :class:`Parameter` for descriptions of parameters. + + :param cls: the argument class to instantiate. This defaults to + :class:`Argument`. + :param param_decls: Passed as positional arguments to the constructor of + ``cls``. + :param attrs: Passed as keyword arguments to the constructor of ``cls``. + """ + if cls is None: + cls = Argument + + def decorator(f: FC) -> FC: + _param_memo(f, cls(param_decls, **attrs)) + return f + + return decorator + + +def option( + *param_decls: str, cls: t.Optional[t.Type[Option]] = None, **attrs: t.Any +) -> t.Callable[[FC], FC]: + """Attaches an option to the command. All positional arguments are + passed as parameter declarations to :class:`Option`; all keyword + arguments are forwarded unchanged (except ``cls``). + This is equivalent to creating an :class:`Option` instance manually + and attaching it to the :attr:`Command.params` list. + + For the default option class, refer to :class:`Option` and + :class:`Parameter` for descriptions of parameters. + + :param cls: the option class to instantiate. This defaults to + :class:`Option`. + :param param_decls: Passed as positional arguments to the constructor of + ``cls``. + :param attrs: Passed as keyword arguments to the constructor of ``cls``. + """ + if cls is None: + cls = Option + + def decorator(f: FC) -> FC: + _param_memo(f, cls(param_decls, **attrs)) + return f + + return decorator + + +def confirmation_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]: + """Add a ``--yes`` option which shows a prompt before continuing if + not passed. If the prompt is declined, the program will exit. + + :param param_decls: One or more option names. Defaults to the single + value ``"--yes"``. + :param kwargs: Extra arguments are passed to :func:`option`. + """ + + def callback(ctx: Context, param: Parameter, value: bool) -> None: + if not value: + ctx.abort() + + if not param_decls: + param_decls = ("--yes",) + + kwargs.setdefault("is_flag", True) + kwargs.setdefault("callback", callback) + kwargs.setdefault("expose_value", False) + kwargs.setdefault("prompt", "Do you want to continue?") + kwargs.setdefault("help", "Confirm the action without prompting.") + return option(*param_decls, **kwargs) + + +def password_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]: + """Add a ``--password`` option which prompts for a password, hiding + input and asking to enter the value again for confirmation. + + :param param_decls: One or more option names. Defaults to the single + value ``"--password"``. + :param kwargs: Extra arguments are passed to :func:`option`. + """ + if not param_decls: + param_decls = ("--password",) + + kwargs.setdefault("prompt", True) + kwargs.setdefault("confirmation_prompt", True) + kwargs.setdefault("hide_input", True) + return option(*param_decls, **kwargs) + + +def version_option( + version: t.Optional[str] = None, + *param_decls: str, + package_name: t.Optional[str] = None, + prog_name: t.Optional[str] = None, + message: t.Optional[str] = None, + **kwargs: t.Any, +) -> t.Callable[[FC], FC]: + """Add a ``--version`` option which immediately prints the version + number and exits the program. + + If ``version`` is not provided, Click will try to detect it using + :func:`importlib.metadata.version` to get the version for the + ``package_name``. On Python < 3.8, the ``importlib_metadata`` + backport must be installed. + + If ``package_name`` is not provided, Click will try to detect it by + inspecting the stack frames. This will be used to detect the + version, so it must match the name of the installed package. + + :param version: The version number to show. If not provided, Click + will try to detect it. + :param param_decls: One or more option names. Defaults to the single + value ``"--version"``. + :param package_name: The package name to detect the version from. If + not provided, Click will try to detect it. + :param prog_name: The name of the CLI to show in the message. If not + provided, it will be detected from the command. + :param message: The message to show. The values ``%(prog)s``, + ``%(package)s``, and ``%(version)s`` are available. Defaults to + ``"%(prog)s, version %(version)s"``. + :param kwargs: Extra arguments are passed to :func:`option`. + :raise RuntimeError: ``version`` could not be detected. + + .. versionchanged:: 8.0 + Add the ``package_name`` parameter, and the ``%(package)s`` + value for messages. + + .. versionchanged:: 8.0 + Use :mod:`importlib.metadata` instead of ``pkg_resources``. The + version is detected based on the package name, not the entry + point name. The Python package name must match the installed + package name, or be passed with ``package_name=``. + """ + if message is None: + message = _("%(prog)s, version %(version)s") + + if version is None and package_name is None: + frame = inspect.currentframe() + f_back = frame.f_back if frame is not None else None + f_globals = f_back.f_globals if f_back is not None else None + # break reference cycle + # https://docs.python.org/3/library/inspect.html#the-interpreter-stack + del frame + + if f_globals is not None: + package_name = f_globals.get("__name__") + + if package_name == "__main__": + package_name = f_globals.get("__package__") + + if package_name: + package_name = package_name.partition(".")[0] + + def callback(ctx: Context, param: Parameter, value: bool) -> None: + if not value or ctx.resilient_parsing: + return + + nonlocal prog_name + nonlocal version + + if prog_name is None: + prog_name = ctx.find_root().info_name + + if version is None and package_name is not None: + metadata: t.Optional[types.ModuleType] + + try: + from importlib import metadata # type: ignore + except ImportError: + # Python < 3.8 + import importlib_metadata as metadata # type: ignore + + try: + version = metadata.version(package_name) # type: ignore + except metadata.PackageNotFoundError: # type: ignore + raise RuntimeError( + f"{package_name!r} is not installed. Try passing" + " 'package_name' instead." + ) from None + + if version is None: + raise RuntimeError( + f"Could not determine the version for {package_name!r} automatically." + ) + + echo( + message % {"prog": prog_name, "package": package_name, "version": version}, + color=ctx.color, + ) + ctx.exit() + + if not param_decls: + param_decls = ("--version",) + + kwargs.setdefault("is_flag", True) + kwargs.setdefault("expose_value", False) + kwargs.setdefault("is_eager", True) + kwargs.setdefault("help", _("Show the version and exit.")) + kwargs["callback"] = callback + return option(*param_decls, **kwargs) + + +def help_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]: + """Add a ``--help`` option which immediately prints the help page + and exits the program. + + This is usually unnecessary, as the ``--help`` option is added to + each command automatically unless ``add_help_option=False`` is + passed. + + :param param_decls: One or more option names. Defaults to the single + value ``"--help"``. + :param kwargs: Extra arguments are passed to :func:`option`. + """ + + def callback(ctx: Context, param: Parameter, value: bool) -> None: + if not value or ctx.resilient_parsing: + return + + echo(ctx.get_help(), color=ctx.color) + ctx.exit() + + if not param_decls: + param_decls = ("--help",) + + kwargs.setdefault("is_flag", True) + kwargs.setdefault("expose_value", False) + kwargs.setdefault("is_eager", True) + kwargs.setdefault("help", _("Show this message and exit.")) + kwargs["callback"] = callback + return option(*param_decls, **kwargs) diff --git a/lib/go-jinja2/internal/data/darwin-amd64/click/exceptions.py b/lib/go-jinja2/internal/data/darwin-amd64/click/exceptions.py new file mode 100644 index 000000000..fe68a3613 --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/click/exceptions.py @@ -0,0 +1,288 @@ +import typing as t +from gettext import gettext as _ +from gettext import ngettext + +from ._compat import get_text_stderr +from .utils import echo +from .utils import format_filename + +if t.TYPE_CHECKING: + from .core import Command + from .core import Context + from .core import Parameter + + +def _join_param_hints( + param_hint: t.Optional[t.Union[t.Sequence[str], str]] +) -> t.Optional[str]: + if param_hint is not None and not isinstance(param_hint, str): + return " / ".join(repr(x) for x in param_hint) + + return param_hint + + +class ClickException(Exception): + """An exception that Click can handle and show to the user.""" + + #: The exit code for this exception. + exit_code = 1 + + def __init__(self, message: str) -> None: + super().__init__(message) + self.message = message + + def format_message(self) -> str: + return self.message + + def __str__(self) -> str: + return self.message + + def show(self, file: t.Optional[t.IO[t.Any]] = None) -> None: + if file is None: + file = get_text_stderr() + + echo(_("Error: {message}").format(message=self.format_message()), file=file) + + +class UsageError(ClickException): + """An internal exception that signals a usage error. This typically + aborts any further handling. + + :param message: the error message to display. + :param ctx: optionally the context that caused this error. Click will + fill in the context automatically in some situations. + """ + + exit_code = 2 + + def __init__(self, message: str, ctx: t.Optional["Context"] = None) -> None: + super().__init__(message) + self.ctx = ctx + self.cmd: t.Optional["Command"] = self.ctx.command if self.ctx else None + + def show(self, file: t.Optional[t.IO[t.Any]] = None) -> None: + if file is None: + file = get_text_stderr() + color = None + hint = "" + if ( + self.ctx is not None + and self.ctx.command.get_help_option(self.ctx) is not None + ): + hint = _("Try '{command} {option}' for help.").format( + command=self.ctx.command_path, option=self.ctx.help_option_names[0] + ) + hint = f"{hint}\n" + if self.ctx is not None: + color = self.ctx.color + echo(f"{self.ctx.get_usage()}\n{hint}", file=file, color=color) + echo( + _("Error: {message}").format(message=self.format_message()), + file=file, + color=color, + ) + + +class BadParameter(UsageError): + """An exception that formats out a standardized error message for a + bad parameter. This is useful when thrown from a callback or type as + Click will attach contextual information to it (for instance, which + parameter it is). + + .. versionadded:: 2.0 + + :param param: the parameter object that caused this error. This can + be left out, and Click will attach this info itself + if possible. + :param param_hint: a string that shows up as parameter name. This + can be used as alternative to `param` in cases + where custom validation should happen. If it is + a string it's used as such, if it's a list then + each item is quoted and separated. + """ + + def __init__( + self, + message: str, + ctx: t.Optional["Context"] = None, + param: t.Optional["Parameter"] = None, + param_hint: t.Optional[str] = None, + ) -> None: + super().__init__(message, ctx) + self.param = param + self.param_hint = param_hint + + def format_message(self) -> str: + if self.param_hint is not None: + param_hint = self.param_hint + elif self.param is not None: + param_hint = self.param.get_error_hint(self.ctx) # type: ignore + else: + return _("Invalid value: {message}").format(message=self.message) + + return _("Invalid value for {param_hint}: {message}").format( + param_hint=_join_param_hints(param_hint), message=self.message + ) + + +class MissingParameter(BadParameter): + """Raised if click required an option or argument but it was not + provided when invoking the script. + + .. versionadded:: 4.0 + + :param param_type: a string that indicates the type of the parameter. + The default is to inherit the parameter type from + the given `param`. Valid values are ``'parameter'``, + ``'option'`` or ``'argument'``. + """ + + def __init__( + self, + message: t.Optional[str] = None, + ctx: t.Optional["Context"] = None, + param: t.Optional["Parameter"] = None, + param_hint: t.Optional[str] = None, + param_type: t.Optional[str] = None, + ) -> None: + super().__init__(message or "", ctx, param, param_hint) + self.param_type = param_type + + def format_message(self) -> str: + if self.param_hint is not None: + param_hint: t.Optional[str] = self.param_hint + elif self.param is not None: + param_hint = self.param.get_error_hint(self.ctx) # type: ignore + else: + param_hint = None + + param_hint = _join_param_hints(param_hint) + param_hint = f" {param_hint}" if param_hint else "" + + param_type = self.param_type + if param_type is None and self.param is not None: + param_type = self.param.param_type_name + + msg = self.message + if self.param is not None: + msg_extra = self.param.type.get_missing_message(self.param) + if msg_extra: + if msg: + msg += f". {msg_extra}" + else: + msg = msg_extra + + msg = f" {msg}" if msg else "" + + # Translate param_type for known types. + if param_type == "argument": + missing = _("Missing argument") + elif param_type == "option": + missing = _("Missing option") + elif param_type == "parameter": + missing = _("Missing parameter") + else: + missing = _("Missing {param_type}").format(param_type=param_type) + + return f"{missing}{param_hint}.{msg}" + + def __str__(self) -> str: + if not self.message: + param_name = self.param.name if self.param else None + return _("Missing parameter: {param_name}").format(param_name=param_name) + else: + return self.message + + +class NoSuchOption(UsageError): + """Raised if click attempted to handle an option that does not + exist. + + .. versionadded:: 4.0 + """ + + def __init__( + self, + option_name: str, + message: t.Optional[str] = None, + possibilities: t.Optional[t.Sequence[str]] = None, + ctx: t.Optional["Context"] = None, + ) -> None: + if message is None: + message = _("No such option: {name}").format(name=option_name) + + super().__init__(message, ctx) + self.option_name = option_name + self.possibilities = possibilities + + def format_message(self) -> str: + if not self.possibilities: + return self.message + + possibility_str = ", ".join(sorted(self.possibilities)) + suggest = ngettext( + "Did you mean {possibility}?", + "(Possible options: {possibilities})", + len(self.possibilities), + ).format(possibility=possibility_str, possibilities=possibility_str) + return f"{self.message} {suggest}" + + +class BadOptionUsage(UsageError): + """Raised if an option is generally supplied but the use of the option + was incorrect. This is for instance raised if the number of arguments + for an option is not correct. + + .. versionadded:: 4.0 + + :param option_name: the name of the option being used incorrectly. + """ + + def __init__( + self, option_name: str, message: str, ctx: t.Optional["Context"] = None + ) -> None: + super().__init__(message, ctx) + self.option_name = option_name + + +class BadArgumentUsage(UsageError): + """Raised if an argument is generally supplied but the use of the argument + was incorrect. This is for instance raised if the number of values + for an argument is not correct. + + .. versionadded:: 6.0 + """ + + +class FileError(ClickException): + """Raised if a file cannot be opened.""" + + def __init__(self, filename: str, hint: t.Optional[str] = None) -> None: + if hint is None: + hint = _("unknown error") + + super().__init__(hint) + self.ui_filename: str = format_filename(filename) + self.filename = filename + + def format_message(self) -> str: + return _("Could not open file {filename!r}: {message}").format( + filename=self.ui_filename, message=self.message + ) + + +class Abort(RuntimeError): + """An internal signalling exception that signals Click to abort.""" + + +class Exit(RuntimeError): + """An exception that indicates that the application should exit with some + status code. + + :param code: the status code to exit with. + """ + + __slots__ = ("exit_code",) + + def __init__(self, code: int = 0) -> None: + self.exit_code: int = code diff --git a/lib/go-jinja2/internal/data/darwin-amd64/click/formatting.py b/lib/go-jinja2/internal/data/darwin-amd64/click/formatting.py new file mode 100644 index 000000000..ddd2a2f82 --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/click/formatting.py @@ -0,0 +1,301 @@ +import typing as t +from contextlib import contextmanager +from gettext import gettext as _ + +from ._compat import term_len +from .parser import split_opt + +# Can force a width. This is used by the test system +FORCED_WIDTH: t.Optional[int] = None + + +def measure_table(rows: t.Iterable[t.Tuple[str, str]]) -> t.Tuple[int, ...]: + widths: t.Dict[int, int] = {} + + for row in rows: + for idx, col in enumerate(row): + widths[idx] = max(widths.get(idx, 0), term_len(col)) + + return tuple(y for x, y in sorted(widths.items())) + + +def iter_rows( + rows: t.Iterable[t.Tuple[str, str]], col_count: int +) -> t.Iterator[t.Tuple[str, ...]]: + for row in rows: + yield row + ("",) * (col_count - len(row)) + + +def wrap_text( + text: str, + width: int = 78, + initial_indent: str = "", + subsequent_indent: str = "", + preserve_paragraphs: bool = False, +) -> str: + """A helper function that intelligently wraps text. By default, it + assumes that it operates on a single paragraph of text but if the + `preserve_paragraphs` parameter is provided it will intelligently + handle paragraphs (defined by two empty lines). + + If paragraphs are handled, a paragraph can be prefixed with an empty + line containing the ``\\b`` character (``\\x08``) to indicate that + no rewrapping should happen in that block. + + :param text: the text that should be rewrapped. + :param width: the maximum width for the text. + :param initial_indent: the initial indent that should be placed on the + first line as a string. + :param subsequent_indent: the indent string that should be placed on + each consecutive line. + :param preserve_paragraphs: if this flag is set then the wrapping will + intelligently handle paragraphs. + """ + from ._textwrap import TextWrapper + + text = text.expandtabs() + wrapper = TextWrapper( + width, + initial_indent=initial_indent, + subsequent_indent=subsequent_indent, + replace_whitespace=False, + ) + if not preserve_paragraphs: + return wrapper.fill(text) + + p: t.List[t.Tuple[int, bool, str]] = [] + buf: t.List[str] = [] + indent = None + + def _flush_par() -> None: + if not buf: + return + if buf[0].strip() == "\b": + p.append((indent or 0, True, "\n".join(buf[1:]))) + else: + p.append((indent or 0, False, " ".join(buf))) + del buf[:] + + for line in text.splitlines(): + if not line: + _flush_par() + indent = None + else: + if indent is None: + orig_len = term_len(line) + line = line.lstrip() + indent = orig_len - term_len(line) + buf.append(line) + _flush_par() + + rv = [] + for indent, raw, text in p: + with wrapper.extra_indent(" " * indent): + if raw: + rv.append(wrapper.indent_only(text)) + else: + rv.append(wrapper.fill(text)) + + return "\n\n".join(rv) + + +class HelpFormatter: + """This class helps with formatting text-based help pages. It's + usually just needed for very special internal cases, but it's also + exposed so that developers can write their own fancy outputs. + + At present, it always writes into memory. + + :param indent_increment: the additional increment for each level. + :param width: the width for the text. This defaults to the terminal + width clamped to a maximum of 78. + """ + + def __init__( + self, + indent_increment: int = 2, + width: t.Optional[int] = None, + max_width: t.Optional[int] = None, + ) -> None: + import shutil + + self.indent_increment = indent_increment + if max_width is None: + max_width = 80 + if width is None: + width = FORCED_WIDTH + if width is None: + width = max(min(shutil.get_terminal_size().columns, max_width) - 2, 50) + self.width = width + self.current_indent = 0 + self.buffer: t.List[str] = [] + + def write(self, string: str) -> None: + """Writes a unicode string into the internal buffer.""" + self.buffer.append(string) + + def indent(self) -> None: + """Increases the indentation.""" + self.current_indent += self.indent_increment + + def dedent(self) -> None: + """Decreases the indentation.""" + self.current_indent -= self.indent_increment + + def write_usage( + self, prog: str, args: str = "", prefix: t.Optional[str] = None + ) -> None: + """Writes a usage line into the buffer. + + :param prog: the program name. + :param args: whitespace separated list of arguments. + :param prefix: The prefix for the first line. Defaults to + ``"Usage: "``. + """ + if prefix is None: + prefix = f"{_('Usage:')} " + + usage_prefix = f"{prefix:>{self.current_indent}}{prog} " + text_width = self.width - self.current_indent + + if text_width >= (term_len(usage_prefix) + 20): + # The arguments will fit to the right of the prefix. + indent = " " * term_len(usage_prefix) + self.write( + wrap_text( + args, + text_width, + initial_indent=usage_prefix, + subsequent_indent=indent, + ) + ) + else: + # The prefix is too long, put the arguments on the next line. + self.write(usage_prefix) + self.write("\n") + indent = " " * (max(self.current_indent, term_len(prefix)) + 4) + self.write( + wrap_text( + args, text_width, initial_indent=indent, subsequent_indent=indent + ) + ) + + self.write("\n") + + def write_heading(self, heading: str) -> None: + """Writes a heading into the buffer.""" + self.write(f"{'':>{self.current_indent}}{heading}:\n") + + def write_paragraph(self) -> None: + """Writes a paragraph into the buffer.""" + if self.buffer: + self.write("\n") + + def write_text(self, text: str) -> None: + """Writes re-indented text into the buffer. This rewraps and + preserves paragraphs. + """ + indent = " " * self.current_indent + self.write( + wrap_text( + text, + self.width, + initial_indent=indent, + subsequent_indent=indent, + preserve_paragraphs=True, + ) + ) + self.write("\n") + + def write_dl( + self, + rows: t.Sequence[t.Tuple[str, str]], + col_max: int = 30, + col_spacing: int = 2, + ) -> None: + """Writes a definition list into the buffer. This is how options + and commands are usually formatted. + + :param rows: a list of two item tuples for the terms and values. + :param col_max: the maximum width of the first column. + :param col_spacing: the number of spaces between the first and + second column. + """ + rows = list(rows) + widths = measure_table(rows) + if len(widths) != 2: + raise TypeError("Expected two columns for definition list") + + first_col = min(widths[0], col_max) + col_spacing + + for first, second in iter_rows(rows, len(widths)): + self.write(f"{'':>{self.current_indent}}{first}") + if not second: + self.write("\n") + continue + if term_len(first) <= first_col - col_spacing: + self.write(" " * (first_col - term_len(first))) + else: + self.write("\n") + self.write(" " * (first_col + self.current_indent)) + + text_width = max(self.width - first_col - 2, 10) + wrapped_text = wrap_text(second, text_width, preserve_paragraphs=True) + lines = wrapped_text.splitlines() + + if lines: + self.write(f"{lines[0]}\n") + + for line in lines[1:]: + self.write(f"{'':>{first_col + self.current_indent}}{line}\n") + else: + self.write("\n") + + @contextmanager + def section(self, name: str) -> t.Iterator[None]: + """Helpful context manager that writes a paragraph, a heading, + and the indents. + + :param name: the section name that is written as heading. + """ + self.write_paragraph() + self.write_heading(name) + self.indent() + try: + yield + finally: + self.dedent() + + @contextmanager + def indentation(self) -> t.Iterator[None]: + """A context manager that increases the indentation.""" + self.indent() + try: + yield + finally: + self.dedent() + + def getvalue(self) -> str: + """Returns the buffer contents.""" + return "".join(self.buffer) + + +def join_options(options: t.Sequence[str]) -> t.Tuple[str, bool]: + """Given a list of option strings this joins them in the most appropriate + way and returns them in the form ``(formatted_string, + any_prefix_is_slash)`` where the second item in the tuple is a flag that + indicates if any of the option prefixes was a slash. + """ + rv = [] + any_prefix_is_slash = False + + for opt in options: + prefix = split_opt(opt)[0] + + if prefix == "/": + any_prefix_is_slash = True + + rv.append((len(prefix), opt)) + + rv.sort(key=lambda x: x[0]) + return ", ".join(x[1] for x in rv), any_prefix_is_slash diff --git a/lib/go-jinja2/internal/data/darwin-amd64/click/globals.py b/lib/go-jinja2/internal/data/darwin-amd64/click/globals.py new file mode 100644 index 000000000..480058f10 --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/click/globals.py @@ -0,0 +1,68 @@ +import typing as t +from threading import local + +if t.TYPE_CHECKING: + import typing_extensions as te + from .core import Context + +_local = local() + + +@t.overload +def get_current_context(silent: "te.Literal[False]" = False) -> "Context": + ... + + +@t.overload +def get_current_context(silent: bool = ...) -> t.Optional["Context"]: + ... + + +def get_current_context(silent: bool = False) -> t.Optional["Context"]: + """Returns the current click context. This can be used as a way to + access the current context object from anywhere. This is a more implicit + alternative to the :func:`pass_context` decorator. This function is + primarily useful for helpers such as :func:`echo` which might be + interested in changing its behavior based on the current context. + + To push the current context, :meth:`Context.scope` can be used. + + .. versionadded:: 5.0 + + :param silent: if set to `True` the return value is `None` if no context + is available. The default behavior is to raise a + :exc:`RuntimeError`. + """ + try: + return t.cast("Context", _local.stack[-1]) + except (AttributeError, IndexError) as e: + if not silent: + raise RuntimeError("There is no active click context.") from e + + return None + + +def push_context(ctx: "Context") -> None: + """Pushes a new context to the current stack.""" + _local.__dict__.setdefault("stack", []).append(ctx) + + +def pop_context() -> None: + """Removes the top level from the stack.""" + _local.stack.pop() + + +def resolve_color_default(color: t.Optional[bool] = None) -> t.Optional[bool]: + """Internal helper to get the default value of the color flag. If a + value is passed it's returned unchanged, otherwise it's looked up from + the current context. + """ + if color is not None: + return color + + ctx = get_current_context(silent=True) + + if ctx is not None: + return ctx.color + + return None diff --git a/lib/go-jinja2/internal/data/darwin-amd64/click/parser.py b/lib/go-jinja2/internal/data/darwin-amd64/click/parser.py new file mode 100644 index 000000000..5fa7adfac --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/click/parser.py @@ -0,0 +1,529 @@ +""" +This module started out as largely a copy paste from the stdlib's +optparse module with the features removed that we do not need from +optparse because we implement them in Click on a higher level (for +instance type handling, help formatting and a lot more). + +The plan is to remove more and more from here over time. + +The reason this is a different module and not optparse from the stdlib +is that there are differences in 2.x and 3.x about the error messages +generated and optparse in the stdlib uses gettext for no good reason +and might cause us issues. + +Click uses parts of optparse written by Gregory P. Ward and maintained +by the Python Software Foundation. This is limited to code in parser.py. + +Copyright 2001-2006 Gregory P. Ward. All rights reserved. +Copyright 2002-2006 Python Software Foundation. All rights reserved. +""" +# This code uses parts of optparse written by Gregory P. Ward and +# maintained by the Python Software Foundation. +# Copyright 2001-2006 Gregory P. Ward +# Copyright 2002-2006 Python Software Foundation +import typing as t +from collections import deque +from gettext import gettext as _ +from gettext import ngettext + +from .exceptions import BadArgumentUsage +from .exceptions import BadOptionUsage +from .exceptions import NoSuchOption +from .exceptions import UsageError + +if t.TYPE_CHECKING: + import typing_extensions as te + from .core import Argument as CoreArgument + from .core import Context + from .core import Option as CoreOption + from .core import Parameter as CoreParameter + +V = t.TypeVar("V") + +# Sentinel value that indicates an option was passed as a flag without a +# value but is not a flag option. Option.consume_value uses this to +# prompt or use the flag_value. +_flag_needs_value = object() + + +def _unpack_args( + args: t.Sequence[str], nargs_spec: t.Sequence[int] +) -> t.Tuple[t.Sequence[t.Union[str, t.Sequence[t.Optional[str]], None]], t.List[str]]: + """Given an iterable of arguments and an iterable of nargs specifications, + it returns a tuple with all the unpacked arguments at the first index + and all remaining arguments as the second. + + The nargs specification is the number of arguments that should be consumed + or `-1` to indicate that this position should eat up all the remainders. + + Missing items are filled with `None`. + """ + args = deque(args) + nargs_spec = deque(nargs_spec) + rv: t.List[t.Union[str, t.Tuple[t.Optional[str], ...], None]] = [] + spos: t.Optional[int] = None + + def _fetch(c: "te.Deque[V]") -> t.Optional[V]: + try: + if spos is None: + return c.popleft() + else: + return c.pop() + except IndexError: + return None + + while nargs_spec: + nargs = _fetch(nargs_spec) + + if nargs is None: + continue + + if nargs == 1: + rv.append(_fetch(args)) + elif nargs > 1: + x = [_fetch(args) for _ in range(nargs)] + + # If we're reversed, we're pulling in the arguments in reverse, + # so we need to turn them around. + if spos is not None: + x.reverse() + + rv.append(tuple(x)) + elif nargs < 0: + if spos is not None: + raise TypeError("Cannot have two nargs < 0") + + spos = len(rv) + rv.append(None) + + # spos is the position of the wildcard (star). If it's not `None`, + # we fill it with the remainder. + if spos is not None: + rv[spos] = tuple(args) + args = [] + rv[spos + 1 :] = reversed(rv[spos + 1 :]) + + return tuple(rv), list(args) + + +def split_opt(opt: str) -> t.Tuple[str, str]: + first = opt[:1] + if first.isalnum(): + return "", opt + if opt[1:2] == first: + return opt[:2], opt[2:] + return first, opt[1:] + + +def normalize_opt(opt: str, ctx: t.Optional["Context"]) -> str: + if ctx is None or ctx.token_normalize_func is None: + return opt + prefix, opt = split_opt(opt) + return f"{prefix}{ctx.token_normalize_func(opt)}" + + +def split_arg_string(string: str) -> t.List[str]: + """Split an argument string as with :func:`shlex.split`, but don't + fail if the string is incomplete. Ignores a missing closing quote or + incomplete escape sequence and uses the partial token as-is. + + .. code-block:: python + + split_arg_string("example 'my file") + ["example", "my file"] + + split_arg_string("example my\\") + ["example", "my"] + + :param string: String to split. + """ + import shlex + + lex = shlex.shlex(string, posix=True) + lex.whitespace_split = True + lex.commenters = "" + out = [] + + try: + for token in lex: + out.append(token) + except ValueError: + # Raised when end-of-string is reached in an invalid state. Use + # the partial token as-is. The quote or escape character is in + # lex.state, not lex.token. + out.append(lex.token) + + return out + + +class Option: + def __init__( + self, + obj: "CoreOption", + opts: t.Sequence[str], + dest: t.Optional[str], + action: t.Optional[str] = None, + nargs: int = 1, + const: t.Optional[t.Any] = None, + ): + self._short_opts = [] + self._long_opts = [] + self.prefixes: t.Set[str] = set() + + for opt in opts: + prefix, value = split_opt(opt) + if not prefix: + raise ValueError(f"Invalid start character for option ({opt})") + self.prefixes.add(prefix[0]) + if len(prefix) == 1 and len(value) == 1: + self._short_opts.append(opt) + else: + self._long_opts.append(opt) + self.prefixes.add(prefix) + + if action is None: + action = "store" + + self.dest = dest + self.action = action + self.nargs = nargs + self.const = const + self.obj = obj + + @property + def takes_value(self) -> bool: + return self.action in ("store", "append") + + def process(self, value: t.Any, state: "ParsingState") -> None: + if self.action == "store": + state.opts[self.dest] = value # type: ignore + elif self.action == "store_const": + state.opts[self.dest] = self.const # type: ignore + elif self.action == "append": + state.opts.setdefault(self.dest, []).append(value) # type: ignore + elif self.action == "append_const": + state.opts.setdefault(self.dest, []).append(self.const) # type: ignore + elif self.action == "count": + state.opts[self.dest] = state.opts.get(self.dest, 0) + 1 # type: ignore + else: + raise ValueError(f"unknown action '{self.action}'") + state.order.append(self.obj) + + +class Argument: + def __init__(self, obj: "CoreArgument", dest: t.Optional[str], nargs: int = 1): + self.dest = dest + self.nargs = nargs + self.obj = obj + + def process( + self, + value: t.Union[t.Optional[str], t.Sequence[t.Optional[str]]], + state: "ParsingState", + ) -> None: + if self.nargs > 1: + assert value is not None + holes = sum(1 for x in value if x is None) + if holes == len(value): + value = None + elif holes != 0: + raise BadArgumentUsage( + _("Argument {name!r} takes {nargs} values.").format( + name=self.dest, nargs=self.nargs + ) + ) + + if self.nargs == -1 and self.obj.envvar is not None and value == (): + # Replace empty tuple with None so that a value from the + # environment may be tried. + value = None + + state.opts[self.dest] = value # type: ignore + state.order.append(self.obj) + + +class ParsingState: + def __init__(self, rargs: t.List[str]) -> None: + self.opts: t.Dict[str, t.Any] = {} + self.largs: t.List[str] = [] + self.rargs = rargs + self.order: t.List["CoreParameter"] = [] + + +class OptionParser: + """The option parser is an internal class that is ultimately used to + parse options and arguments. It's modelled after optparse and brings + a similar but vastly simplified API. It should generally not be used + directly as the high level Click classes wrap it for you. + + It's not nearly as extensible as optparse or argparse as it does not + implement features that are implemented on a higher level (such as + types or defaults). + + :param ctx: optionally the :class:`~click.Context` where this parser + should go with. + """ + + def __init__(self, ctx: t.Optional["Context"] = None) -> None: + #: The :class:`~click.Context` for this parser. This might be + #: `None` for some advanced use cases. + self.ctx = ctx + #: This controls how the parser deals with interspersed arguments. + #: If this is set to `False`, the parser will stop on the first + #: non-option. Click uses this to implement nested subcommands + #: safely. + self.allow_interspersed_args: bool = True + #: This tells the parser how to deal with unknown options. By + #: default it will error out (which is sensible), but there is a + #: second mode where it will ignore it and continue processing + #: after shifting all the unknown options into the resulting args. + self.ignore_unknown_options: bool = False + + if ctx is not None: + self.allow_interspersed_args = ctx.allow_interspersed_args + self.ignore_unknown_options = ctx.ignore_unknown_options + + self._short_opt: t.Dict[str, Option] = {} + self._long_opt: t.Dict[str, Option] = {} + self._opt_prefixes = {"-", "--"} + self._args: t.List[Argument] = [] + + def add_option( + self, + obj: "CoreOption", + opts: t.Sequence[str], + dest: t.Optional[str], + action: t.Optional[str] = None, + nargs: int = 1, + const: t.Optional[t.Any] = None, + ) -> None: + """Adds a new option named `dest` to the parser. The destination + is not inferred (unlike with optparse) and needs to be explicitly + provided. Action can be any of ``store``, ``store_const``, + ``append``, ``append_const`` or ``count``. + + The `obj` can be used to identify the option in the order list + that is returned from the parser. + """ + opts = [normalize_opt(opt, self.ctx) for opt in opts] + option = Option(obj, opts, dest, action=action, nargs=nargs, const=const) + self._opt_prefixes.update(option.prefixes) + for opt in option._short_opts: + self._short_opt[opt] = option + for opt in option._long_opts: + self._long_opt[opt] = option + + def add_argument( + self, obj: "CoreArgument", dest: t.Optional[str], nargs: int = 1 + ) -> None: + """Adds a positional argument named `dest` to the parser. + + The `obj` can be used to identify the option in the order list + that is returned from the parser. + """ + self._args.append(Argument(obj, dest=dest, nargs=nargs)) + + def parse_args( + self, args: t.List[str] + ) -> t.Tuple[t.Dict[str, t.Any], t.List[str], t.List["CoreParameter"]]: + """Parses positional arguments and returns ``(values, args, order)`` + for the parsed options and arguments as well as the leftover + arguments if there are any. The order is a list of objects as they + appear on the command line. If arguments appear multiple times they + will be memorized multiple times as well. + """ + state = ParsingState(args) + try: + self._process_args_for_options(state) + self._process_args_for_args(state) + except UsageError: + if self.ctx is None or not self.ctx.resilient_parsing: + raise + return state.opts, state.largs, state.order + + def _process_args_for_args(self, state: ParsingState) -> None: + pargs, args = _unpack_args( + state.largs + state.rargs, [x.nargs for x in self._args] + ) + + for idx, arg in enumerate(self._args): + arg.process(pargs[idx], state) + + state.largs = args + state.rargs = [] + + def _process_args_for_options(self, state: ParsingState) -> None: + while state.rargs: + arg = state.rargs.pop(0) + arglen = len(arg) + # Double dashes always handled explicitly regardless of what + # prefixes are valid. + if arg == "--": + return + elif arg[:1] in self._opt_prefixes and arglen > 1: + self._process_opts(arg, state) + elif self.allow_interspersed_args: + state.largs.append(arg) + else: + state.rargs.insert(0, arg) + return + + # Say this is the original argument list: + # [arg0, arg1, ..., arg(i-1), arg(i), arg(i+1), ..., arg(N-1)] + # ^ + # (we are about to process arg(i)). + # + # Then rargs is [arg(i), ..., arg(N-1)] and largs is a *subset* of + # [arg0, ..., arg(i-1)] (any options and their arguments will have + # been removed from largs). + # + # The while loop will usually consume 1 or more arguments per pass. + # If it consumes 1 (eg. arg is an option that takes no arguments), + # then after _process_arg() is done the situation is: + # + # largs = subset of [arg0, ..., arg(i)] + # rargs = [arg(i+1), ..., arg(N-1)] + # + # If allow_interspersed_args is false, largs will always be + # *empty* -- still a subset of [arg0, ..., arg(i-1)], but + # not a very interesting subset! + + def _match_long_opt( + self, opt: str, explicit_value: t.Optional[str], state: ParsingState + ) -> None: + if opt not in self._long_opt: + from difflib import get_close_matches + + possibilities = get_close_matches(opt, self._long_opt) + raise NoSuchOption(opt, possibilities=possibilities, ctx=self.ctx) + + option = self._long_opt[opt] + if option.takes_value: + # At this point it's safe to modify rargs by injecting the + # explicit value, because no exception is raised in this + # branch. This means that the inserted value will be fully + # consumed. + if explicit_value is not None: + state.rargs.insert(0, explicit_value) + + value = self._get_value_from_state(opt, option, state) + + elif explicit_value is not None: + raise BadOptionUsage( + opt, _("Option {name!r} does not take a value.").format(name=opt) + ) + + else: + value = None + + option.process(value, state) + + def _match_short_opt(self, arg: str, state: ParsingState) -> None: + stop = False + i = 1 + prefix = arg[0] + unknown_options = [] + + for ch in arg[1:]: + opt = normalize_opt(f"{prefix}{ch}", self.ctx) + option = self._short_opt.get(opt) + i += 1 + + if not option: + if self.ignore_unknown_options: + unknown_options.append(ch) + continue + raise NoSuchOption(opt, ctx=self.ctx) + if option.takes_value: + # Any characters left in arg? Pretend they're the + # next arg, and stop consuming characters of arg. + if i < len(arg): + state.rargs.insert(0, arg[i:]) + stop = True + + value = self._get_value_from_state(opt, option, state) + + else: + value = None + + option.process(value, state) + + if stop: + break + + # If we got any unknown options we recombine the string of the + # remaining options and re-attach the prefix, then report that + # to the state as new larg. This way there is basic combinatorics + # that can be achieved while still ignoring unknown arguments. + if self.ignore_unknown_options and unknown_options: + state.largs.append(f"{prefix}{''.join(unknown_options)}") + + def _get_value_from_state( + self, option_name: str, option: Option, state: ParsingState + ) -> t.Any: + nargs = option.nargs + + if len(state.rargs) < nargs: + if option.obj._flag_needs_value: + # Option allows omitting the value. + value = _flag_needs_value + else: + raise BadOptionUsage( + option_name, + ngettext( + "Option {name!r} requires an argument.", + "Option {name!r} requires {nargs} arguments.", + nargs, + ).format(name=option_name, nargs=nargs), + ) + elif nargs == 1: + next_rarg = state.rargs[0] + + if ( + option.obj._flag_needs_value + and isinstance(next_rarg, str) + and next_rarg[:1] in self._opt_prefixes + and len(next_rarg) > 1 + ): + # The next arg looks like the start of an option, don't + # use it as the value if omitting the value is allowed. + value = _flag_needs_value + else: + value = state.rargs.pop(0) + else: + value = tuple(state.rargs[:nargs]) + del state.rargs[:nargs] + + return value + + def _process_opts(self, arg: str, state: ParsingState) -> None: + explicit_value = None + # Long option handling happens in two parts. The first part is + # supporting explicitly attached values. In any case, we will try + # to long match the option first. + if "=" in arg: + long_opt, explicit_value = arg.split("=", 1) + else: + long_opt = arg + norm_long_opt = normalize_opt(long_opt, self.ctx) + + # At this point we will match the (assumed) long option through + # the long option matching code. Note that this allows options + # like "-foo" to be matched as long options. + try: + self._match_long_opt(norm_long_opt, explicit_value, state) + except NoSuchOption: + # At this point the long option matching failed, and we need + # to try with short options. However there is a special rule + # which says, that if we have a two character options prefix + # (applies to "--foo" for instance), we do not dispatch to the + # short option code and will instead raise the no option + # error. + if arg[:2] not in self._opt_prefixes: + self._match_short_opt(arg, state) + return + + if not self.ignore_unknown_options: + raise + + state.largs.append(arg) diff --git a/lib/go-jinja2/internal/data/darwin-amd64/click/py.typed b/lib/go-jinja2/internal/data/darwin-amd64/click/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/lib/go-jinja2/internal/data/darwin-amd64/click/shell_completion.py b/lib/go-jinja2/internal/data/darwin-amd64/click/shell_completion.py new file mode 100644 index 000000000..dc9e00b9b --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/click/shell_completion.py @@ -0,0 +1,596 @@ +import os +import re +import typing as t +from gettext import gettext as _ + +from .core import Argument +from .core import BaseCommand +from .core import Context +from .core import MultiCommand +from .core import Option +from .core import Parameter +from .core import ParameterSource +from .parser import split_arg_string +from .utils import echo + + +def shell_complete( + cli: BaseCommand, + ctx_args: t.MutableMapping[str, t.Any], + prog_name: str, + complete_var: str, + instruction: str, +) -> int: + """Perform shell completion for the given CLI program. + + :param cli: Command being called. + :param ctx_args: Extra arguments to pass to + ``cli.make_context``. + :param prog_name: Name of the executable in the shell. + :param complete_var: Name of the environment variable that holds + the completion instruction. + :param instruction: Value of ``complete_var`` with the completion + instruction and shell, in the form ``instruction_shell``. + :return: Status code to exit with. + """ + shell, _, instruction = instruction.partition("_") + comp_cls = get_completion_class(shell) + + if comp_cls is None: + return 1 + + comp = comp_cls(cli, ctx_args, prog_name, complete_var) + + if instruction == "source": + echo(comp.source()) + return 0 + + if instruction == "complete": + echo(comp.complete()) + return 0 + + return 1 + + +class CompletionItem: + """Represents a completion value and metadata about the value. The + default metadata is ``type`` to indicate special shell handling, + and ``help`` if a shell supports showing a help string next to the + value. + + Arbitrary parameters can be passed when creating the object, and + accessed using ``item.attr``. If an attribute wasn't passed, + accessing it returns ``None``. + + :param value: The completion suggestion. + :param type: Tells the shell script to provide special completion + support for the type. Click uses ``"dir"`` and ``"file"``. + :param help: String shown next to the value if supported. + :param kwargs: Arbitrary metadata. The built-in implementations + don't use this, but custom type completions paired with custom + shell support could use it. + """ + + __slots__ = ("value", "type", "help", "_info") + + def __init__( + self, + value: t.Any, + type: str = "plain", + help: t.Optional[str] = None, + **kwargs: t.Any, + ) -> None: + self.value: t.Any = value + self.type: str = type + self.help: t.Optional[str] = help + self._info = kwargs + + def __getattr__(self, name: str) -> t.Any: + return self._info.get(name) + + +# Only Bash >= 4.4 has the nosort option. +_SOURCE_BASH = """\ +%(complete_func)s() { + local IFS=$'\\n' + local response + + response=$(env COMP_WORDS="${COMP_WORDS[*]}" COMP_CWORD=$COMP_CWORD \ +%(complete_var)s=bash_complete $1) + + for completion in $response; do + IFS=',' read type value <<< "$completion" + + if [[ $type == 'dir' ]]; then + COMPREPLY=() + compopt -o dirnames + elif [[ $type == 'file' ]]; then + COMPREPLY=() + compopt -o default + elif [[ $type == 'plain' ]]; then + COMPREPLY+=($value) + fi + done + + return 0 +} + +%(complete_func)s_setup() { + complete -o nosort -F %(complete_func)s %(prog_name)s +} + +%(complete_func)s_setup; +""" + +_SOURCE_ZSH = """\ +#compdef %(prog_name)s + +%(complete_func)s() { + local -a completions + local -a completions_with_descriptions + local -a response + (( ! $+commands[%(prog_name)s] )) && return 1 + + response=("${(@f)$(env COMP_WORDS="${words[*]}" COMP_CWORD=$((CURRENT-1)) \ +%(complete_var)s=zsh_complete %(prog_name)s)}") + + for type key descr in ${response}; do + if [[ "$type" == "plain" ]]; then + if [[ "$descr" == "_" ]]; then + completions+=("$key") + else + completions_with_descriptions+=("$key":"$descr") + fi + elif [[ "$type" == "dir" ]]; then + _path_files -/ + elif [[ "$type" == "file" ]]; then + _path_files -f + fi + done + + if [ -n "$completions_with_descriptions" ]; then + _describe -V unsorted completions_with_descriptions -U + fi + + if [ -n "$completions" ]; then + compadd -U -V unsorted -a completions + fi +} + +if [[ $zsh_eval_context[-1] == loadautofunc ]]; then + # autoload from fpath, call function directly + %(complete_func)s "$@" +else + # eval/source/. command, register function for later + compdef %(complete_func)s %(prog_name)s +fi +""" + +_SOURCE_FISH = """\ +function %(complete_func)s; + set -l response (env %(complete_var)s=fish_complete COMP_WORDS=(commandline -cp) \ +COMP_CWORD=(commandline -t) %(prog_name)s); + + for completion in $response; + set -l metadata (string split "," $completion); + + if test $metadata[1] = "dir"; + __fish_complete_directories $metadata[2]; + else if test $metadata[1] = "file"; + __fish_complete_path $metadata[2]; + else if test $metadata[1] = "plain"; + echo $metadata[2]; + end; + end; +end; + +complete --no-files --command %(prog_name)s --arguments \ +"(%(complete_func)s)"; +""" + + +class ShellComplete: + """Base class for providing shell completion support. A subclass for + a given shell will override attributes and methods to implement the + completion instructions (``source`` and ``complete``). + + :param cli: Command being called. + :param prog_name: Name of the executable in the shell. + :param complete_var: Name of the environment variable that holds + the completion instruction. + + .. versionadded:: 8.0 + """ + + name: t.ClassVar[str] + """Name to register the shell as with :func:`add_completion_class`. + This is used in completion instructions (``{name}_source`` and + ``{name}_complete``). + """ + + source_template: t.ClassVar[str] + """Completion script template formatted by :meth:`source`. This must + be provided by subclasses. + """ + + def __init__( + self, + cli: BaseCommand, + ctx_args: t.MutableMapping[str, t.Any], + prog_name: str, + complete_var: str, + ) -> None: + self.cli = cli + self.ctx_args = ctx_args + self.prog_name = prog_name + self.complete_var = complete_var + + @property + def func_name(self) -> str: + """The name of the shell function defined by the completion + script. + """ + safe_name = re.sub(r"\W*", "", self.prog_name.replace("-", "_"), flags=re.ASCII) + return f"_{safe_name}_completion" + + def source_vars(self) -> t.Dict[str, t.Any]: + """Vars for formatting :attr:`source_template`. + + By default this provides ``complete_func``, ``complete_var``, + and ``prog_name``. + """ + return { + "complete_func": self.func_name, + "complete_var": self.complete_var, + "prog_name": self.prog_name, + } + + def source(self) -> str: + """Produce the shell script that defines the completion + function. By default this ``%``-style formats + :attr:`source_template` with the dict returned by + :meth:`source_vars`. + """ + return self.source_template % self.source_vars() + + def get_completion_args(self) -> t.Tuple[t.List[str], str]: + """Use the env vars defined by the shell script to return a + tuple of ``args, incomplete``. This must be implemented by + subclasses. + """ + raise NotImplementedError + + def get_completions( + self, args: t.List[str], incomplete: str + ) -> t.List[CompletionItem]: + """Determine the context and last complete command or parameter + from the complete args. Call that object's ``shell_complete`` + method to get the completions for the incomplete value. + + :param args: List of complete args before the incomplete value. + :param incomplete: Value being completed. May be empty. + """ + ctx = _resolve_context(self.cli, self.ctx_args, self.prog_name, args) + obj, incomplete = _resolve_incomplete(ctx, args, incomplete) + return obj.shell_complete(ctx, incomplete) + + def format_completion(self, item: CompletionItem) -> str: + """Format a completion item into the form recognized by the + shell script. This must be implemented by subclasses. + + :param item: Completion item to format. + """ + raise NotImplementedError + + def complete(self) -> str: + """Produce the completion data to send back to the shell. + + By default this calls :meth:`get_completion_args`, gets the + completions, then calls :meth:`format_completion` for each + completion. + """ + args, incomplete = self.get_completion_args() + completions = self.get_completions(args, incomplete) + out = [self.format_completion(item) for item in completions] + return "\n".join(out) + + +class BashComplete(ShellComplete): + """Shell completion for Bash.""" + + name = "bash" + source_template = _SOURCE_BASH + + @staticmethod + def _check_version() -> None: + import subprocess + + output = subprocess.run( + ["bash", "-c", 'echo "${BASH_VERSION}"'], stdout=subprocess.PIPE + ) + match = re.search(r"^(\d+)\.(\d+)\.\d+", output.stdout.decode()) + + if match is not None: + major, minor = match.groups() + + if major < "4" or major == "4" and minor < "4": + echo( + _( + "Shell completion is not supported for Bash" + " versions older than 4.4." + ), + err=True, + ) + else: + echo( + _("Couldn't detect Bash version, shell completion is not supported."), + err=True, + ) + + def source(self) -> str: + self._check_version() + return super().source() + + def get_completion_args(self) -> t.Tuple[t.List[str], str]: + cwords = split_arg_string(os.environ["COMP_WORDS"]) + cword = int(os.environ["COMP_CWORD"]) + args = cwords[1:cword] + + try: + incomplete = cwords[cword] + except IndexError: + incomplete = "" + + return args, incomplete + + def format_completion(self, item: CompletionItem) -> str: + return f"{item.type},{item.value}" + + +class ZshComplete(ShellComplete): + """Shell completion for Zsh.""" + + name = "zsh" + source_template = _SOURCE_ZSH + + def get_completion_args(self) -> t.Tuple[t.List[str], str]: + cwords = split_arg_string(os.environ["COMP_WORDS"]) + cword = int(os.environ["COMP_CWORD"]) + args = cwords[1:cword] + + try: + incomplete = cwords[cword] + except IndexError: + incomplete = "" + + return args, incomplete + + def format_completion(self, item: CompletionItem) -> str: + return f"{item.type}\n{item.value}\n{item.help if item.help else '_'}" + + +class FishComplete(ShellComplete): + """Shell completion for Fish.""" + + name = "fish" + source_template = _SOURCE_FISH + + def get_completion_args(self) -> t.Tuple[t.List[str], str]: + cwords = split_arg_string(os.environ["COMP_WORDS"]) + incomplete = os.environ["COMP_CWORD"] + args = cwords[1:] + + # Fish stores the partial word in both COMP_WORDS and + # COMP_CWORD, remove it from complete args. + if incomplete and args and args[-1] == incomplete: + args.pop() + + return args, incomplete + + def format_completion(self, item: CompletionItem) -> str: + if item.help: + return f"{item.type},{item.value}\t{item.help}" + + return f"{item.type},{item.value}" + + +ShellCompleteType = t.TypeVar("ShellCompleteType", bound=t.Type[ShellComplete]) + + +_available_shells: t.Dict[str, t.Type[ShellComplete]] = { + "bash": BashComplete, + "fish": FishComplete, + "zsh": ZshComplete, +} + + +def add_completion_class( + cls: ShellCompleteType, name: t.Optional[str] = None +) -> ShellCompleteType: + """Register a :class:`ShellComplete` subclass under the given name. + The name will be provided by the completion instruction environment + variable during completion. + + :param cls: The completion class that will handle completion for the + shell. + :param name: Name to register the class under. Defaults to the + class's ``name`` attribute. + """ + if name is None: + name = cls.name + + _available_shells[name] = cls + + return cls + + +def get_completion_class(shell: str) -> t.Optional[t.Type[ShellComplete]]: + """Look up a registered :class:`ShellComplete` subclass by the name + provided by the completion instruction environment variable. If the + name isn't registered, returns ``None``. + + :param shell: Name the class is registered under. + """ + return _available_shells.get(shell) + + +def _is_incomplete_argument(ctx: Context, param: Parameter) -> bool: + """Determine if the given parameter is an argument that can still + accept values. + + :param ctx: Invocation context for the command represented by the + parsed complete args. + :param param: Argument object being checked. + """ + if not isinstance(param, Argument): + return False + + assert param.name is not None + # Will be None if expose_value is False. + value = ctx.params.get(param.name) + return ( + param.nargs == -1 + or ctx.get_parameter_source(param.name) is not ParameterSource.COMMANDLINE + or ( + param.nargs > 1 + and isinstance(value, (tuple, list)) + and len(value) < param.nargs + ) + ) + + +def _start_of_option(ctx: Context, value: str) -> bool: + """Check if the value looks like the start of an option.""" + if not value: + return False + + c = value[0] + return c in ctx._opt_prefixes + + +def _is_incomplete_option(ctx: Context, args: t.List[str], param: Parameter) -> bool: + """Determine if the given parameter is an option that needs a value. + + :param args: List of complete args before the incomplete value. + :param param: Option object being checked. + """ + if not isinstance(param, Option): + return False + + if param.is_flag or param.count: + return False + + last_option = None + + for index, arg in enumerate(reversed(args)): + if index + 1 > param.nargs: + break + + if _start_of_option(ctx, arg): + last_option = arg + + return last_option is not None and last_option in param.opts + + +def _resolve_context( + cli: BaseCommand, + ctx_args: t.MutableMapping[str, t.Any], + prog_name: str, + args: t.List[str], +) -> Context: + """Produce the context hierarchy starting with the command and + traversing the complete arguments. This only follows the commands, + it doesn't trigger input prompts or callbacks. + + :param cli: Command being called. + :param prog_name: Name of the executable in the shell. + :param args: List of complete args before the incomplete value. + """ + ctx_args["resilient_parsing"] = True + ctx = cli.make_context(prog_name, args.copy(), **ctx_args) + args = ctx.protected_args + ctx.args + + while args: + command = ctx.command + + if isinstance(command, MultiCommand): + if not command.chain: + name, cmd, args = command.resolve_command(ctx, args) + + if cmd is None: + return ctx + + ctx = cmd.make_context(name, args, parent=ctx, resilient_parsing=True) + args = ctx.protected_args + ctx.args + else: + sub_ctx = ctx + + while args: + name, cmd, args = command.resolve_command(ctx, args) + + if cmd is None: + return ctx + + sub_ctx = cmd.make_context( + name, + args, + parent=ctx, + allow_extra_args=True, + allow_interspersed_args=False, + resilient_parsing=True, + ) + args = sub_ctx.args + + ctx = sub_ctx + args = [*sub_ctx.protected_args, *sub_ctx.args] + else: + break + + return ctx + + +def _resolve_incomplete( + ctx: Context, args: t.List[str], incomplete: str +) -> t.Tuple[t.Union[BaseCommand, Parameter], str]: + """Find the Click object that will handle the completion of the + incomplete value. Return the object and the incomplete value. + + :param ctx: Invocation context for the command represented by + the parsed complete args. + :param args: List of complete args before the incomplete value. + :param incomplete: Value being completed. May be empty. + """ + # Different shells treat an "=" between a long option name and + # value differently. Might keep the value joined, return the "=" + # as a separate item, or return the split name and value. Always + # split and discard the "=" to make completion easier. + if incomplete == "=": + incomplete = "" + elif "=" in incomplete and _start_of_option(ctx, incomplete): + name, _, incomplete = incomplete.partition("=") + args.append(name) + + # The "--" marker tells Click to stop treating values as options + # even if they start with the option character. If it hasn't been + # given and the incomplete arg looks like an option, the current + # command will provide option name completions. + if "--" not in args and _start_of_option(ctx, incomplete): + return ctx.command, incomplete + + params = ctx.command.get_params(ctx) + + # If the last complete arg is an option name with an incomplete + # value, the option will provide value completions. + for param in params: + if _is_incomplete_option(ctx, args, param): + return param, incomplete + + # It's not an option name or value. The first argument without a + # parsed value will provide value completions. + for param in params: + if _is_incomplete_argument(ctx, param): + return param, incomplete + + # There were no unparsed arguments, the command may be a group that + # will provide command name completions. + return ctx.command, incomplete diff --git a/lib/go-jinja2/internal/data/darwin-amd64/click/termui.py b/lib/go-jinja2/internal/data/darwin-amd64/click/termui.py new file mode 100644 index 000000000..db7a4b286 --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/click/termui.py @@ -0,0 +1,784 @@ +import inspect +import io +import itertools +import sys +import typing as t +from gettext import gettext as _ + +from ._compat import isatty +from ._compat import strip_ansi +from .exceptions import Abort +from .exceptions import UsageError +from .globals import resolve_color_default +from .types import Choice +from .types import convert_type +from .types import ParamType +from .utils import echo +from .utils import LazyFile + +if t.TYPE_CHECKING: + from ._termui_impl import ProgressBar + +V = t.TypeVar("V") + +# The prompt functions to use. The doc tools currently override these +# functions to customize how they work. +visible_prompt_func: t.Callable[[str], str] = input + +_ansi_colors = { + "black": 30, + "red": 31, + "green": 32, + "yellow": 33, + "blue": 34, + "magenta": 35, + "cyan": 36, + "white": 37, + "reset": 39, + "bright_black": 90, + "bright_red": 91, + "bright_green": 92, + "bright_yellow": 93, + "bright_blue": 94, + "bright_magenta": 95, + "bright_cyan": 96, + "bright_white": 97, +} +_ansi_reset_all = "\033[0m" + + +def hidden_prompt_func(prompt: str) -> str: + import getpass + + return getpass.getpass(prompt) + + +def _build_prompt( + text: str, + suffix: str, + show_default: bool = False, + default: t.Optional[t.Any] = None, + show_choices: bool = True, + type: t.Optional[ParamType] = None, +) -> str: + prompt = text + if type is not None and show_choices and isinstance(type, Choice): + prompt += f" ({', '.join(map(str, type.choices))})" + if default is not None and show_default: + prompt = f"{prompt} [{_format_default(default)}]" + return f"{prompt}{suffix}" + + +def _format_default(default: t.Any) -> t.Any: + if isinstance(default, (io.IOBase, LazyFile)) and hasattr(default, "name"): + return default.name + + return default + + +def prompt( + text: str, + default: t.Optional[t.Any] = None, + hide_input: bool = False, + confirmation_prompt: t.Union[bool, str] = False, + type: t.Optional[t.Union[ParamType, t.Any]] = None, + value_proc: t.Optional[t.Callable[[str], t.Any]] = None, + prompt_suffix: str = ": ", + show_default: bool = True, + err: bool = False, + show_choices: bool = True, +) -> t.Any: + """Prompts a user for input. This is a convenience function that can + be used to prompt a user for input later. + + If the user aborts the input by sending an interrupt signal, this + function will catch it and raise a :exc:`Abort` exception. + + :param text: the text to show for the prompt. + :param default: the default value to use if no input happens. If this + is not given it will prompt until it's aborted. + :param hide_input: if this is set to true then the input value will + be hidden. + :param confirmation_prompt: Prompt a second time to confirm the + value. Can be set to a string instead of ``True`` to customize + the message. + :param type: the type to use to check the value against. + :param value_proc: if this parameter is provided it's a function that + is invoked instead of the type conversion to + convert a value. + :param prompt_suffix: a suffix that should be added to the prompt. + :param show_default: shows or hides the default value in the prompt. + :param err: if set to true the file defaults to ``stderr`` instead of + ``stdout``, the same as with echo. + :param show_choices: Show or hide choices if the passed type is a Choice. + For example if type is a Choice of either day or week, + show_choices is true and text is "Group by" then the + prompt will be "Group by (day, week): ". + + .. versionadded:: 8.0 + ``confirmation_prompt`` can be a custom string. + + .. versionadded:: 7.0 + Added the ``show_choices`` parameter. + + .. versionadded:: 6.0 + Added unicode support for cmd.exe on Windows. + + .. versionadded:: 4.0 + Added the `err` parameter. + + """ + + def prompt_func(text: str) -> str: + f = hidden_prompt_func if hide_input else visible_prompt_func + try: + # Write the prompt separately so that we get nice + # coloring through colorama on Windows + echo(text.rstrip(" "), nl=False, err=err) + # Echo a space to stdout to work around an issue where + # readline causes backspace to clear the whole line. + return f(" ") + except (KeyboardInterrupt, EOFError): + # getpass doesn't print a newline if the user aborts input with ^C. + # Allegedly this behavior is inherited from getpass(3). + # A doc bug has been filed at https://bugs.python.org/issue24711 + if hide_input: + echo(None, err=err) + raise Abort() from None + + if value_proc is None: + value_proc = convert_type(type, default) + + prompt = _build_prompt( + text, prompt_suffix, show_default, default, show_choices, type + ) + + if confirmation_prompt: + if confirmation_prompt is True: + confirmation_prompt = _("Repeat for confirmation") + + confirmation_prompt = _build_prompt(confirmation_prompt, prompt_suffix) + + while True: + while True: + value = prompt_func(prompt) + if value: + break + elif default is not None: + value = default + break + try: + result = value_proc(value) + except UsageError as e: + if hide_input: + echo(_("Error: The value you entered was invalid."), err=err) + else: + echo(_("Error: {e.message}").format(e=e), err=err) # noqa: B306 + continue + if not confirmation_prompt: + return result + while True: + value2 = prompt_func(confirmation_prompt) + is_empty = not value and not value2 + if value2 or is_empty: + break + if value == value2: + return result + echo(_("Error: The two entered values do not match."), err=err) + + +def confirm( + text: str, + default: t.Optional[bool] = False, + abort: bool = False, + prompt_suffix: str = ": ", + show_default: bool = True, + err: bool = False, +) -> bool: + """Prompts for confirmation (yes/no question). + + If the user aborts the input by sending a interrupt signal this + function will catch it and raise a :exc:`Abort` exception. + + :param text: the question to ask. + :param default: The default value to use when no input is given. If + ``None``, repeat until input is given. + :param abort: if this is set to `True` a negative answer aborts the + exception by raising :exc:`Abort`. + :param prompt_suffix: a suffix that should be added to the prompt. + :param show_default: shows or hides the default value in the prompt. + :param err: if set to true the file defaults to ``stderr`` instead of + ``stdout``, the same as with echo. + + .. versionchanged:: 8.0 + Repeat until input is given if ``default`` is ``None``. + + .. versionadded:: 4.0 + Added the ``err`` parameter. + """ + prompt = _build_prompt( + text, + prompt_suffix, + show_default, + "y/n" if default is None else ("Y/n" if default else "y/N"), + ) + + while True: + try: + # Write the prompt separately so that we get nice + # coloring through colorama on Windows + echo(prompt.rstrip(" "), nl=False, err=err) + # Echo a space to stdout to work around an issue where + # readline causes backspace to clear the whole line. + value = visible_prompt_func(" ").lower().strip() + except (KeyboardInterrupt, EOFError): + raise Abort() from None + if value in ("y", "yes"): + rv = True + elif value in ("n", "no"): + rv = False + elif default is not None and value == "": + rv = default + else: + echo(_("Error: invalid input"), err=err) + continue + break + if abort and not rv: + raise Abort() + return rv + + +def echo_via_pager( + text_or_generator: t.Union[t.Iterable[str], t.Callable[[], t.Iterable[str]], str], + color: t.Optional[bool] = None, +) -> None: + """This function takes a text and shows it via an environment specific + pager on stdout. + + .. versionchanged:: 3.0 + Added the `color` flag. + + :param text_or_generator: the text to page, or alternatively, a + generator emitting the text to page. + :param color: controls if the pager supports ANSI colors or not. The + default is autodetection. + """ + color = resolve_color_default(color) + + if inspect.isgeneratorfunction(text_or_generator): + i = t.cast(t.Callable[[], t.Iterable[str]], text_or_generator)() + elif isinstance(text_or_generator, str): + i = [text_or_generator] + else: + i = iter(t.cast(t.Iterable[str], text_or_generator)) + + # convert every element of i to a text type if necessary + text_generator = (el if isinstance(el, str) else str(el) for el in i) + + from ._termui_impl import pager + + return pager(itertools.chain(text_generator, "\n"), color) + + +def progressbar( + iterable: t.Optional[t.Iterable[V]] = None, + length: t.Optional[int] = None, + label: t.Optional[str] = None, + show_eta: bool = True, + show_percent: t.Optional[bool] = None, + show_pos: bool = False, + item_show_func: t.Optional[t.Callable[[t.Optional[V]], t.Optional[str]]] = None, + fill_char: str = "#", + empty_char: str = "-", + bar_template: str = "%(label)s [%(bar)s] %(info)s", + info_sep: str = " ", + width: int = 36, + file: t.Optional[t.TextIO] = None, + color: t.Optional[bool] = None, + update_min_steps: int = 1, +) -> "ProgressBar[V]": + """This function creates an iterable context manager that can be used + to iterate over something while showing a progress bar. It will + either iterate over the `iterable` or `length` items (that are counted + up). While iteration happens, this function will print a rendered + progress bar to the given `file` (defaults to stdout) and will attempt + to calculate remaining time and more. By default, this progress bar + will not be rendered if the file is not a terminal. + + The context manager creates the progress bar. When the context + manager is entered the progress bar is already created. With every + iteration over the progress bar, the iterable passed to the bar is + advanced and the bar is updated. When the context manager exits, + a newline is printed and the progress bar is finalized on screen. + + Note: The progress bar is currently designed for use cases where the + total progress can be expected to take at least several seconds. + Because of this, the ProgressBar class object won't display + progress that is considered too fast, and progress where the time + between steps is less than a second. + + No printing must happen or the progress bar will be unintentionally + destroyed. + + Example usage:: + + with progressbar(items) as bar: + for item in bar: + do_something_with(item) + + Alternatively, if no iterable is specified, one can manually update the + progress bar through the `update()` method instead of directly + iterating over the progress bar. The update method accepts the number + of steps to increment the bar with:: + + with progressbar(length=chunks.total_bytes) as bar: + for chunk in chunks: + process_chunk(chunk) + bar.update(chunks.bytes) + + The ``update()`` method also takes an optional value specifying the + ``current_item`` at the new position. This is useful when used + together with ``item_show_func`` to customize the output for each + manual step:: + + with click.progressbar( + length=total_size, + label='Unzipping archive', + item_show_func=lambda a: a.filename + ) as bar: + for archive in zip_file: + archive.extract() + bar.update(archive.size, archive) + + :param iterable: an iterable to iterate over. If not provided the length + is required. + :param length: the number of items to iterate over. By default the + progressbar will attempt to ask the iterator about its + length, which might or might not work. If an iterable is + also provided this parameter can be used to override the + length. If an iterable is not provided the progress bar + will iterate over a range of that length. + :param label: the label to show next to the progress bar. + :param show_eta: enables or disables the estimated time display. This is + automatically disabled if the length cannot be + determined. + :param show_percent: enables or disables the percentage display. The + default is `True` if the iterable has a length or + `False` if not. + :param show_pos: enables or disables the absolute position display. The + default is `False`. + :param item_show_func: A function called with the current item which + can return a string to show next to the progress bar. If the + function returns ``None`` nothing is shown. The current item can + be ``None``, such as when entering and exiting the bar. + :param fill_char: the character to use to show the filled part of the + progress bar. + :param empty_char: the character to use to show the non-filled part of + the progress bar. + :param bar_template: the format string to use as template for the bar. + The parameters in it are ``label`` for the label, + ``bar`` for the progress bar and ``info`` for the + info section. + :param info_sep: the separator between multiple info items (eta etc.) + :param width: the width of the progress bar in characters, 0 means full + terminal width + :param file: The file to write to. If this is not a terminal then + only the label is printed. + :param color: controls if the terminal supports ANSI colors or not. The + default is autodetection. This is only needed if ANSI + codes are included anywhere in the progress bar output + which is not the case by default. + :param update_min_steps: Render only when this many updates have + completed. This allows tuning for very fast iterators. + + .. versionchanged:: 8.0 + Output is shown even if execution time is less than 0.5 seconds. + + .. versionchanged:: 8.0 + ``item_show_func`` shows the current item, not the previous one. + + .. versionchanged:: 8.0 + Labels are echoed if the output is not a TTY. Reverts a change + in 7.0 that removed all output. + + .. versionadded:: 8.0 + Added the ``update_min_steps`` parameter. + + .. versionchanged:: 4.0 + Added the ``color`` parameter. Added the ``update`` method to + the object. + + .. versionadded:: 2.0 + """ + from ._termui_impl import ProgressBar + + color = resolve_color_default(color) + return ProgressBar( + iterable=iterable, + length=length, + show_eta=show_eta, + show_percent=show_percent, + show_pos=show_pos, + item_show_func=item_show_func, + fill_char=fill_char, + empty_char=empty_char, + bar_template=bar_template, + info_sep=info_sep, + file=file, + label=label, + width=width, + color=color, + update_min_steps=update_min_steps, + ) + + +def clear() -> None: + """Clears the terminal screen. This will have the effect of clearing + the whole visible space of the terminal and moving the cursor to the + top left. This does not do anything if not connected to a terminal. + + .. versionadded:: 2.0 + """ + if not isatty(sys.stdout): + return + + # ANSI escape \033[2J clears the screen, \033[1;1H moves the cursor + echo("\033[2J\033[1;1H", nl=False) + + +def _interpret_color( + color: t.Union[int, t.Tuple[int, int, int], str], offset: int = 0 +) -> str: + if isinstance(color, int): + return f"{38 + offset};5;{color:d}" + + if isinstance(color, (tuple, list)): + r, g, b = color + return f"{38 + offset};2;{r:d};{g:d};{b:d}" + + return str(_ansi_colors[color] + offset) + + +def style( + text: t.Any, + fg: t.Optional[t.Union[int, t.Tuple[int, int, int], str]] = None, + bg: t.Optional[t.Union[int, t.Tuple[int, int, int], str]] = None, + bold: t.Optional[bool] = None, + dim: t.Optional[bool] = None, + underline: t.Optional[bool] = None, + overline: t.Optional[bool] = None, + italic: t.Optional[bool] = None, + blink: t.Optional[bool] = None, + reverse: t.Optional[bool] = None, + strikethrough: t.Optional[bool] = None, + reset: bool = True, +) -> str: + """Styles a text with ANSI styles and returns the new string. By + default the styling is self contained which means that at the end + of the string a reset code is issued. This can be prevented by + passing ``reset=False``. + + Examples:: + + click.echo(click.style('Hello World!', fg='green')) + click.echo(click.style('ATTENTION!', blink=True)) + click.echo(click.style('Some things', reverse=True, fg='cyan')) + click.echo(click.style('More colors', fg=(255, 12, 128), bg=117)) + + Supported color names: + + * ``black`` (might be a gray) + * ``red`` + * ``green`` + * ``yellow`` (might be an orange) + * ``blue`` + * ``magenta`` + * ``cyan`` + * ``white`` (might be light gray) + * ``bright_black`` + * ``bright_red`` + * ``bright_green`` + * ``bright_yellow`` + * ``bright_blue`` + * ``bright_magenta`` + * ``bright_cyan`` + * ``bright_white`` + * ``reset`` (reset the color code only) + + If the terminal supports it, color may also be specified as: + + - An integer in the interval [0, 255]. The terminal must support + 8-bit/256-color mode. + - An RGB tuple of three integers in [0, 255]. The terminal must + support 24-bit/true-color mode. + + See https://en.wikipedia.org/wiki/ANSI_color and + https://gist.github.com/XVilka/8346728 for more information. + + :param text: the string to style with ansi codes. + :param fg: if provided this will become the foreground color. + :param bg: if provided this will become the background color. + :param bold: if provided this will enable or disable bold mode. + :param dim: if provided this will enable or disable dim mode. This is + badly supported. + :param underline: if provided this will enable or disable underline. + :param overline: if provided this will enable or disable overline. + :param italic: if provided this will enable or disable italic. + :param blink: if provided this will enable or disable blinking. + :param reverse: if provided this will enable or disable inverse + rendering (foreground becomes background and the + other way round). + :param strikethrough: if provided this will enable or disable + striking through text. + :param reset: by default a reset-all code is added at the end of the + string which means that styles do not carry over. This + can be disabled to compose styles. + + .. versionchanged:: 8.0 + A non-string ``message`` is converted to a string. + + .. versionchanged:: 8.0 + Added support for 256 and RGB color codes. + + .. versionchanged:: 8.0 + Added the ``strikethrough``, ``italic``, and ``overline`` + parameters. + + .. versionchanged:: 7.0 + Added support for bright colors. + + .. versionadded:: 2.0 + """ + if not isinstance(text, str): + text = str(text) + + bits = [] + + if fg: + try: + bits.append(f"\033[{_interpret_color(fg)}m") + except KeyError: + raise TypeError(f"Unknown color {fg!r}") from None + + if bg: + try: + bits.append(f"\033[{_interpret_color(bg, 10)}m") + except KeyError: + raise TypeError(f"Unknown color {bg!r}") from None + + if bold is not None: + bits.append(f"\033[{1 if bold else 22}m") + if dim is not None: + bits.append(f"\033[{2 if dim else 22}m") + if underline is not None: + bits.append(f"\033[{4 if underline else 24}m") + if overline is not None: + bits.append(f"\033[{53 if overline else 55}m") + if italic is not None: + bits.append(f"\033[{3 if italic else 23}m") + if blink is not None: + bits.append(f"\033[{5 if blink else 25}m") + if reverse is not None: + bits.append(f"\033[{7 if reverse else 27}m") + if strikethrough is not None: + bits.append(f"\033[{9 if strikethrough else 29}m") + bits.append(text) + if reset: + bits.append(_ansi_reset_all) + return "".join(bits) + + +def unstyle(text: str) -> str: + """Removes ANSI styling information from a string. Usually it's not + necessary to use this function as Click's echo function will + automatically remove styling if necessary. + + .. versionadded:: 2.0 + + :param text: the text to remove style information from. + """ + return strip_ansi(text) + + +def secho( + message: t.Optional[t.Any] = None, + file: t.Optional[t.IO[t.AnyStr]] = None, + nl: bool = True, + err: bool = False, + color: t.Optional[bool] = None, + **styles: t.Any, +) -> None: + """This function combines :func:`echo` and :func:`style` into one + call. As such the following two calls are the same:: + + click.secho('Hello World!', fg='green') + click.echo(click.style('Hello World!', fg='green')) + + All keyword arguments are forwarded to the underlying functions + depending on which one they go with. + + Non-string types will be converted to :class:`str`. However, + :class:`bytes` are passed directly to :meth:`echo` without applying + style. If you want to style bytes that represent text, call + :meth:`bytes.decode` first. + + .. versionchanged:: 8.0 + A non-string ``message`` is converted to a string. Bytes are + passed through without style applied. + + .. versionadded:: 2.0 + """ + if message is not None and not isinstance(message, (bytes, bytearray)): + message = style(message, **styles) + + return echo(message, file=file, nl=nl, err=err, color=color) + + +def edit( + text: t.Optional[t.AnyStr] = None, + editor: t.Optional[str] = None, + env: t.Optional[t.Mapping[str, str]] = None, + require_save: bool = True, + extension: str = ".txt", + filename: t.Optional[str] = None, +) -> t.Optional[t.AnyStr]: + r"""Edits the given text in the defined editor. If an editor is given + (should be the full path to the executable but the regular operating + system search path is used for finding the executable) it overrides + the detected editor. Optionally, some environment variables can be + used. If the editor is closed without changes, `None` is returned. In + case a file is edited directly the return value is always `None` and + `require_save` and `extension` are ignored. + + If the editor cannot be opened a :exc:`UsageError` is raised. + + Note for Windows: to simplify cross-platform usage, the newlines are + automatically converted from POSIX to Windows and vice versa. As such, + the message here will have ``\n`` as newline markers. + + :param text: the text to edit. + :param editor: optionally the editor to use. Defaults to automatic + detection. + :param env: environment variables to forward to the editor. + :param require_save: if this is true, then not saving in the editor + will make the return value become `None`. + :param extension: the extension to tell the editor about. This defaults + to `.txt` but changing this might change syntax + highlighting. + :param filename: if provided it will edit this file instead of the + provided text contents. It will not use a temporary + file as an indirection in that case. + """ + from ._termui_impl import Editor + + ed = Editor(editor=editor, env=env, require_save=require_save, extension=extension) + + if filename is None: + return ed.edit(text) + + ed.edit_file(filename) + return None + + +def launch(url: str, wait: bool = False, locate: bool = False) -> int: + """This function launches the given URL (or filename) in the default + viewer application for this file type. If this is an executable, it + might launch the executable in a new session. The return value is + the exit code of the launched application. Usually, ``0`` indicates + success. + + Examples:: + + click.launch('https://click.palletsprojects.com/') + click.launch('/my/downloaded/file', locate=True) + + .. versionadded:: 2.0 + + :param url: URL or filename of the thing to launch. + :param wait: Wait for the program to exit before returning. This + only works if the launched program blocks. In particular, + ``xdg-open`` on Linux does not block. + :param locate: if this is set to `True` then instead of launching the + application associated with the URL it will attempt to + launch a file manager with the file located. This + might have weird effects if the URL does not point to + the filesystem. + """ + from ._termui_impl import open_url + + return open_url(url, wait=wait, locate=locate) + + +# If this is provided, getchar() calls into this instead. This is used +# for unittesting purposes. +_getchar: t.Optional[t.Callable[[bool], str]] = None + + +def getchar(echo: bool = False) -> str: + """Fetches a single character from the terminal and returns it. This + will always return a unicode character and under certain rare + circumstances this might return more than one character. The + situations which more than one character is returned is when for + whatever reason multiple characters end up in the terminal buffer or + standard input was not actually a terminal. + + Note that this will always read from the terminal, even if something + is piped into the standard input. + + Note for Windows: in rare cases when typing non-ASCII characters, this + function might wait for a second character and then return both at once. + This is because certain Unicode characters look like special-key markers. + + .. versionadded:: 2.0 + + :param echo: if set to `True`, the character read will also show up on + the terminal. The default is to not show it. + """ + global _getchar + + if _getchar is None: + from ._termui_impl import getchar as f + + _getchar = f + + return _getchar(echo) + + +def raw_terminal() -> t.ContextManager[int]: + from ._termui_impl import raw_terminal as f + + return f() + + +def pause(info: t.Optional[str] = None, err: bool = False) -> None: + """This command stops execution and waits for the user to press any + key to continue. This is similar to the Windows batch "pause" + command. If the program is not run through a terminal, this command + will instead do nothing. + + .. versionadded:: 2.0 + + .. versionadded:: 4.0 + Added the `err` parameter. + + :param info: The message to print before pausing. Defaults to + ``"Press any key to continue..."``. + :param err: if set to message goes to ``stderr`` instead of + ``stdout``, the same as with echo. + """ + if not isatty(sys.stdin) or not isatty(sys.stdout): + return + + if info is None: + info = _("Press any key to continue...") + + try: + if info: + echo(info, nl=False, err=err) + try: + getchar() + except (KeyboardInterrupt, EOFError): + pass + finally: + if info: + echo(err=err) diff --git a/lib/go-jinja2/internal/data/darwin-amd64/click/testing.py b/lib/go-jinja2/internal/data/darwin-amd64/click/testing.py new file mode 100644 index 000000000..e0df0d2a6 --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/click/testing.py @@ -0,0 +1,479 @@ +import contextlib +import io +import os +import shlex +import shutil +import sys +import tempfile +import typing as t +from types import TracebackType + +from . import formatting +from . import termui +from . import utils +from ._compat import _find_binary_reader + +if t.TYPE_CHECKING: + from .core import BaseCommand + + +class EchoingStdin: + def __init__(self, input: t.BinaryIO, output: t.BinaryIO) -> None: + self._input = input + self._output = output + self._paused = False + + def __getattr__(self, x: str) -> t.Any: + return getattr(self._input, x) + + def _echo(self, rv: bytes) -> bytes: + if not self._paused: + self._output.write(rv) + + return rv + + def read(self, n: int = -1) -> bytes: + return self._echo(self._input.read(n)) + + def read1(self, n: int = -1) -> bytes: + return self._echo(self._input.read1(n)) # type: ignore + + def readline(self, n: int = -1) -> bytes: + return self._echo(self._input.readline(n)) + + def readlines(self) -> t.List[bytes]: + return [self._echo(x) for x in self._input.readlines()] + + def __iter__(self) -> t.Iterator[bytes]: + return iter(self._echo(x) for x in self._input) + + def __repr__(self) -> str: + return repr(self._input) + + +@contextlib.contextmanager +def _pause_echo(stream: t.Optional[EchoingStdin]) -> t.Iterator[None]: + if stream is None: + yield + else: + stream._paused = True + yield + stream._paused = False + + +class _NamedTextIOWrapper(io.TextIOWrapper): + def __init__( + self, buffer: t.BinaryIO, name: str, mode: str, **kwargs: t.Any + ) -> None: + super().__init__(buffer, **kwargs) + self._name = name + self._mode = mode + + @property + def name(self) -> str: + return self._name + + @property + def mode(self) -> str: + return self._mode + + +def make_input_stream( + input: t.Optional[t.Union[str, bytes, t.IO[t.Any]]], charset: str +) -> t.BinaryIO: + # Is already an input stream. + if hasattr(input, "read"): + rv = _find_binary_reader(t.cast(t.IO[t.Any], input)) + + if rv is not None: + return rv + + raise TypeError("Could not find binary reader for input stream.") + + if input is None: + input = b"" + elif isinstance(input, str): + input = input.encode(charset) + + return io.BytesIO(input) + + +class Result: + """Holds the captured result of an invoked CLI script.""" + + def __init__( + self, + runner: "CliRunner", + stdout_bytes: bytes, + stderr_bytes: t.Optional[bytes], + return_value: t.Any, + exit_code: int, + exception: t.Optional[BaseException], + exc_info: t.Optional[ + t.Tuple[t.Type[BaseException], BaseException, TracebackType] + ] = None, + ): + #: The runner that created the result + self.runner = runner + #: The standard output as bytes. + self.stdout_bytes = stdout_bytes + #: The standard error as bytes, or None if not available + self.stderr_bytes = stderr_bytes + #: The value returned from the invoked command. + #: + #: .. versionadded:: 8.0 + self.return_value = return_value + #: The exit code as integer. + self.exit_code = exit_code + #: The exception that happened if one did. + self.exception = exception + #: The traceback + self.exc_info = exc_info + + @property + def output(self) -> str: + """The (standard) output as unicode string.""" + return self.stdout + + @property + def stdout(self) -> str: + """The standard output as unicode string.""" + return self.stdout_bytes.decode(self.runner.charset, "replace").replace( + "\r\n", "\n" + ) + + @property + def stderr(self) -> str: + """The standard error as unicode string.""" + if self.stderr_bytes is None: + raise ValueError("stderr not separately captured") + return self.stderr_bytes.decode(self.runner.charset, "replace").replace( + "\r\n", "\n" + ) + + def __repr__(self) -> str: + exc_str = repr(self.exception) if self.exception else "okay" + return f"<{type(self).__name__} {exc_str}>" + + +class CliRunner: + """The CLI runner provides functionality to invoke a Click command line + script for unittesting purposes in a isolated environment. This only + works in single-threaded systems without any concurrency as it changes the + global interpreter state. + + :param charset: the character set for the input and output data. + :param env: a dictionary with environment variables for overriding. + :param echo_stdin: if this is set to `True`, then reading from stdin writes + to stdout. This is useful for showing examples in + some circumstances. Note that regular prompts + will automatically echo the input. + :param mix_stderr: if this is set to `False`, then stdout and stderr are + preserved as independent streams. This is useful for + Unix-philosophy apps that have predictable stdout and + noisy stderr, such that each may be measured + independently + """ + + def __init__( + self, + charset: str = "utf-8", + env: t.Optional[t.Mapping[str, t.Optional[str]]] = None, + echo_stdin: bool = False, + mix_stderr: bool = True, + ) -> None: + self.charset = charset + self.env: t.Mapping[str, t.Optional[str]] = env or {} + self.echo_stdin = echo_stdin + self.mix_stderr = mix_stderr + + def get_default_prog_name(self, cli: "BaseCommand") -> str: + """Given a command object it will return the default program name + for it. The default is the `name` attribute or ``"root"`` if not + set. + """ + return cli.name or "root" + + def make_env( + self, overrides: t.Optional[t.Mapping[str, t.Optional[str]]] = None + ) -> t.Mapping[str, t.Optional[str]]: + """Returns the environment overrides for invoking a script.""" + rv = dict(self.env) + if overrides: + rv.update(overrides) + return rv + + @contextlib.contextmanager + def isolation( + self, + input: t.Optional[t.Union[str, bytes, t.IO[t.Any]]] = None, + env: t.Optional[t.Mapping[str, t.Optional[str]]] = None, + color: bool = False, + ) -> t.Iterator[t.Tuple[io.BytesIO, t.Optional[io.BytesIO]]]: + """A context manager that sets up the isolation for invoking of a + command line tool. This sets up stdin with the given input data + and `os.environ` with the overrides from the given dictionary. + This also rebinds some internals in Click to be mocked (like the + prompt functionality). + + This is automatically done in the :meth:`invoke` method. + + :param input: the input stream to put into sys.stdin. + :param env: the environment overrides as dictionary. + :param color: whether the output should contain color codes. The + application can still override this explicitly. + + .. versionchanged:: 8.0 + ``stderr`` is opened with ``errors="backslashreplace"`` + instead of the default ``"strict"``. + + .. versionchanged:: 4.0 + Added the ``color`` parameter. + """ + bytes_input = make_input_stream(input, self.charset) + echo_input = None + + old_stdin = sys.stdin + old_stdout = sys.stdout + old_stderr = sys.stderr + old_forced_width = formatting.FORCED_WIDTH + formatting.FORCED_WIDTH = 80 + + env = self.make_env(env) + + bytes_output = io.BytesIO() + + if self.echo_stdin: + bytes_input = echo_input = t.cast( + t.BinaryIO, EchoingStdin(bytes_input, bytes_output) + ) + + sys.stdin = text_input = _NamedTextIOWrapper( + bytes_input, encoding=self.charset, name="", mode="r" + ) + + if self.echo_stdin: + # Force unbuffered reads, otherwise TextIOWrapper reads a + # large chunk which is echoed early. + text_input._CHUNK_SIZE = 1 # type: ignore + + sys.stdout = _NamedTextIOWrapper( + bytes_output, encoding=self.charset, name="", mode="w" + ) + + bytes_error = None + if self.mix_stderr: + sys.stderr = sys.stdout + else: + bytes_error = io.BytesIO() + sys.stderr = _NamedTextIOWrapper( + bytes_error, + encoding=self.charset, + name="", + mode="w", + errors="backslashreplace", + ) + + @_pause_echo(echo_input) # type: ignore + def visible_input(prompt: t.Optional[str] = None) -> str: + sys.stdout.write(prompt or "") + val = text_input.readline().rstrip("\r\n") + sys.stdout.write(f"{val}\n") + sys.stdout.flush() + return val + + @_pause_echo(echo_input) # type: ignore + def hidden_input(prompt: t.Optional[str] = None) -> str: + sys.stdout.write(f"{prompt or ''}\n") + sys.stdout.flush() + return text_input.readline().rstrip("\r\n") + + @_pause_echo(echo_input) # type: ignore + def _getchar(echo: bool) -> str: + char = sys.stdin.read(1) + + if echo: + sys.stdout.write(char) + + sys.stdout.flush() + return char + + default_color = color + + def should_strip_ansi( + stream: t.Optional[t.IO[t.Any]] = None, color: t.Optional[bool] = None + ) -> bool: + if color is None: + return not default_color + return not color + + old_visible_prompt_func = termui.visible_prompt_func + old_hidden_prompt_func = termui.hidden_prompt_func + old__getchar_func = termui._getchar + old_should_strip_ansi = utils.should_strip_ansi # type: ignore + termui.visible_prompt_func = visible_input + termui.hidden_prompt_func = hidden_input + termui._getchar = _getchar + utils.should_strip_ansi = should_strip_ansi # type: ignore + + old_env = {} + try: + for key, value in env.items(): + old_env[key] = os.environ.get(key) + if value is None: + try: + del os.environ[key] + except Exception: + pass + else: + os.environ[key] = value + yield (bytes_output, bytes_error) + finally: + for key, value in old_env.items(): + if value is None: + try: + del os.environ[key] + except Exception: + pass + else: + os.environ[key] = value + sys.stdout = old_stdout + sys.stderr = old_stderr + sys.stdin = old_stdin + termui.visible_prompt_func = old_visible_prompt_func + termui.hidden_prompt_func = old_hidden_prompt_func + termui._getchar = old__getchar_func + utils.should_strip_ansi = old_should_strip_ansi # type: ignore + formatting.FORCED_WIDTH = old_forced_width + + def invoke( + self, + cli: "BaseCommand", + args: t.Optional[t.Union[str, t.Sequence[str]]] = None, + input: t.Optional[t.Union[str, bytes, t.IO[t.Any]]] = None, + env: t.Optional[t.Mapping[str, t.Optional[str]]] = None, + catch_exceptions: bool = True, + color: bool = False, + **extra: t.Any, + ) -> Result: + """Invokes a command in an isolated environment. The arguments are + forwarded directly to the command line script, the `extra` keyword + arguments are passed to the :meth:`~clickpkg.Command.main` function of + the command. + + This returns a :class:`Result` object. + + :param cli: the command to invoke + :param args: the arguments to invoke. It may be given as an iterable + or a string. When given as string it will be interpreted + as a Unix shell command. More details at + :func:`shlex.split`. + :param input: the input data for `sys.stdin`. + :param env: the environment overrides. + :param catch_exceptions: Whether to catch any other exceptions than + ``SystemExit``. + :param extra: the keyword arguments to pass to :meth:`main`. + :param color: whether the output should contain color codes. The + application can still override this explicitly. + + .. versionchanged:: 8.0 + The result object has the ``return_value`` attribute with + the value returned from the invoked command. + + .. versionchanged:: 4.0 + Added the ``color`` parameter. + + .. versionchanged:: 3.0 + Added the ``catch_exceptions`` parameter. + + .. versionchanged:: 3.0 + The result object has the ``exc_info`` attribute with the + traceback if available. + """ + exc_info = None + with self.isolation(input=input, env=env, color=color) as outstreams: + return_value = None + exception: t.Optional[BaseException] = None + exit_code = 0 + + if isinstance(args, str): + args = shlex.split(args) + + try: + prog_name = extra.pop("prog_name") + except KeyError: + prog_name = self.get_default_prog_name(cli) + + try: + return_value = cli.main(args=args or (), prog_name=prog_name, **extra) + except SystemExit as e: + exc_info = sys.exc_info() + e_code = t.cast(t.Optional[t.Union[int, t.Any]], e.code) + + if e_code is None: + e_code = 0 + + if e_code != 0: + exception = e + + if not isinstance(e_code, int): + sys.stdout.write(str(e_code)) + sys.stdout.write("\n") + e_code = 1 + + exit_code = e_code + + except Exception as e: + if not catch_exceptions: + raise + exception = e + exit_code = 1 + exc_info = sys.exc_info() + finally: + sys.stdout.flush() + stdout = outstreams[0].getvalue() + if self.mix_stderr: + stderr = None + else: + stderr = outstreams[1].getvalue() # type: ignore + + return Result( + runner=self, + stdout_bytes=stdout, + stderr_bytes=stderr, + return_value=return_value, + exit_code=exit_code, + exception=exception, + exc_info=exc_info, # type: ignore + ) + + @contextlib.contextmanager + def isolated_filesystem( + self, temp_dir: t.Optional[t.Union[str, "os.PathLike[str]"]] = None + ) -> t.Iterator[str]: + """A context manager that creates a temporary directory and + changes the current working directory to it. This isolates tests + that affect the contents of the CWD to prevent them from + interfering with each other. + + :param temp_dir: Create the temporary directory under this + directory. If given, the created directory is not removed + when exiting. + + .. versionchanged:: 8.0 + Added the ``temp_dir`` parameter. + """ + cwd = os.getcwd() + dt = tempfile.mkdtemp(dir=temp_dir) + os.chdir(dt) + + try: + yield dt + finally: + os.chdir(cwd) + + if temp_dir is None: + try: + shutil.rmtree(dt) + except OSError: # noqa: B014 + pass diff --git a/lib/go-jinja2/internal/data/darwin-amd64/click/types.py b/lib/go-jinja2/internal/data/darwin-amd64/click/types.py new file mode 100644 index 000000000..2b1d1797f --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/click/types.py @@ -0,0 +1,1089 @@ +import os +import stat +import sys +import typing as t +from datetime import datetime +from gettext import gettext as _ +from gettext import ngettext + +from ._compat import _get_argv_encoding +from ._compat import open_stream +from .exceptions import BadParameter +from .utils import format_filename +from .utils import LazyFile +from .utils import safecall + +if t.TYPE_CHECKING: + import typing_extensions as te + from .core import Context + from .core import Parameter + from .shell_completion import CompletionItem + + +class ParamType: + """Represents the type of a parameter. Validates and converts values + from the command line or Python into the correct type. + + To implement a custom type, subclass and implement at least the + following: + + - The :attr:`name` class attribute must be set. + - Calling an instance of the type with ``None`` must return + ``None``. This is already implemented by default. + - :meth:`convert` must convert string values to the correct type. + - :meth:`convert` must accept values that are already the correct + type. + - It must be able to convert a value if the ``ctx`` and ``param`` + arguments are ``None``. This can occur when converting prompt + input. + """ + + is_composite: t.ClassVar[bool] = False + arity: t.ClassVar[int] = 1 + + #: the descriptive name of this type + name: str + + #: if a list of this type is expected and the value is pulled from a + #: string environment variable, this is what splits it up. `None` + #: means any whitespace. For all parameters the general rule is that + #: whitespace splits them up. The exception are paths and files which + #: are split by ``os.path.pathsep`` by default (":" on Unix and ";" on + #: Windows). + envvar_list_splitter: t.ClassVar[t.Optional[str]] = None + + def to_info_dict(self) -> t.Dict[str, t.Any]: + """Gather information that could be useful for a tool generating + user-facing documentation. + + Use :meth:`click.Context.to_info_dict` to traverse the entire + CLI structure. + + .. versionadded:: 8.0 + """ + # The class name without the "ParamType" suffix. + param_type = type(self).__name__.partition("ParamType")[0] + param_type = param_type.partition("ParameterType")[0] + + # Custom subclasses might not remember to set a name. + if hasattr(self, "name"): + name = self.name + else: + name = param_type + + return {"param_type": param_type, "name": name} + + def __call__( + self, + value: t.Any, + param: t.Optional["Parameter"] = None, + ctx: t.Optional["Context"] = None, + ) -> t.Any: + if value is not None: + return self.convert(value, param, ctx) + + def get_metavar(self, param: "Parameter") -> t.Optional[str]: + """Returns the metavar default for this param if it provides one.""" + + def get_missing_message(self, param: "Parameter") -> t.Optional[str]: + """Optionally might return extra information about a missing + parameter. + + .. versionadded:: 2.0 + """ + + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + """Convert the value to the correct type. This is not called if + the value is ``None`` (the missing value). + + This must accept string values from the command line, as well as + values that are already the correct type. It may also convert + other compatible types. + + The ``param`` and ``ctx`` arguments may be ``None`` in certain + situations, such as when converting prompt input. + + If the value cannot be converted, call :meth:`fail` with a + descriptive message. + + :param value: The value to convert. + :param param: The parameter that is using this type to convert + its value. May be ``None``. + :param ctx: The current context that arrived at this value. May + be ``None``. + """ + return value + + def split_envvar_value(self, rv: str) -> t.Sequence[str]: + """Given a value from an environment variable this splits it up + into small chunks depending on the defined envvar list splitter. + + If the splitter is set to `None`, which means that whitespace splits, + then leading and trailing whitespace is ignored. Otherwise, leading + and trailing splitters usually lead to empty items being included. + """ + return (rv or "").split(self.envvar_list_splitter) + + def fail( + self, + message: str, + param: t.Optional["Parameter"] = None, + ctx: t.Optional["Context"] = None, + ) -> "t.NoReturn": + """Helper method to fail with an invalid value message.""" + raise BadParameter(message, ctx=ctx, param=param) + + def shell_complete( + self, ctx: "Context", param: "Parameter", incomplete: str + ) -> t.List["CompletionItem"]: + """Return a list of + :class:`~click.shell_completion.CompletionItem` objects for the + incomplete value. Most types do not provide completions, but + some do, and this allows custom types to provide custom + completions as well. + + :param ctx: Invocation context for this command. + :param param: The parameter that is requesting completion. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + return [] + + +class CompositeParamType(ParamType): + is_composite = True + + @property + def arity(self) -> int: # type: ignore + raise NotImplementedError() + + +class FuncParamType(ParamType): + def __init__(self, func: t.Callable[[t.Any], t.Any]) -> None: + self.name: str = func.__name__ + self.func = func + + def to_info_dict(self) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict["func"] = self.func + return info_dict + + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + try: + return self.func(value) + except ValueError: + try: + value = str(value) + except UnicodeError: + value = value.decode("utf-8", "replace") + + self.fail(value, param, ctx) + + +class UnprocessedParamType(ParamType): + name = "text" + + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + return value + + def __repr__(self) -> str: + return "UNPROCESSED" + + +class StringParamType(ParamType): + name = "text" + + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + if isinstance(value, bytes): + enc = _get_argv_encoding() + try: + value = value.decode(enc) + except UnicodeError: + fs_enc = sys.getfilesystemencoding() + if fs_enc != enc: + try: + value = value.decode(fs_enc) + except UnicodeError: + value = value.decode("utf-8", "replace") + else: + value = value.decode("utf-8", "replace") + return value + return str(value) + + def __repr__(self) -> str: + return "STRING" + + +class Choice(ParamType): + """The choice type allows a value to be checked against a fixed set + of supported values. All of these values have to be strings. + + You should only pass a list or tuple of choices. Other iterables + (like generators) may lead to surprising results. + + The resulting value will always be one of the originally passed choices + regardless of ``case_sensitive`` or any ``ctx.token_normalize_func`` + being specified. + + See :ref:`choice-opts` for an example. + + :param case_sensitive: Set to false to make choices case + insensitive. Defaults to true. + """ + + name = "choice" + + def __init__(self, choices: t.Sequence[str], case_sensitive: bool = True) -> None: + self.choices = choices + self.case_sensitive = case_sensitive + + def to_info_dict(self) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict["choices"] = self.choices + info_dict["case_sensitive"] = self.case_sensitive + return info_dict + + def get_metavar(self, param: "Parameter") -> str: + choices_str = "|".join(self.choices) + + # Use curly braces to indicate a required argument. + if param.required and param.param_type_name == "argument": + return f"{{{choices_str}}}" + + # Use square braces to indicate an option or optional argument. + return f"[{choices_str}]" + + def get_missing_message(self, param: "Parameter") -> str: + return _("Choose from:\n\t{choices}").format(choices=",\n\t".join(self.choices)) + + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + # Match through normalization and case sensitivity + # first do token_normalize_func, then lowercase + # preserve original `value` to produce an accurate message in + # `self.fail` + normed_value = value + normed_choices = {choice: choice for choice in self.choices} + + if ctx is not None and ctx.token_normalize_func is not None: + normed_value = ctx.token_normalize_func(value) + normed_choices = { + ctx.token_normalize_func(normed_choice): original + for normed_choice, original in normed_choices.items() + } + + if not self.case_sensitive: + normed_value = normed_value.casefold() + normed_choices = { + normed_choice.casefold(): original + for normed_choice, original in normed_choices.items() + } + + if normed_value in normed_choices: + return normed_choices[normed_value] + + choices_str = ", ".join(map(repr, self.choices)) + self.fail( + ngettext( + "{value!r} is not {choice}.", + "{value!r} is not one of {choices}.", + len(self.choices), + ).format(value=value, choice=choices_str, choices=choices_str), + param, + ctx, + ) + + def __repr__(self) -> str: + return f"Choice({list(self.choices)})" + + def shell_complete( + self, ctx: "Context", param: "Parameter", incomplete: str + ) -> t.List["CompletionItem"]: + """Complete choices that start with the incomplete value. + + :param ctx: Invocation context for this command. + :param param: The parameter that is requesting completion. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from click.shell_completion import CompletionItem + + str_choices = map(str, self.choices) + + if self.case_sensitive: + matched = (c for c in str_choices if c.startswith(incomplete)) + else: + incomplete = incomplete.lower() + matched = (c for c in str_choices if c.lower().startswith(incomplete)) + + return [CompletionItem(c) for c in matched] + + +class DateTime(ParamType): + """The DateTime type converts date strings into `datetime` objects. + + The format strings which are checked are configurable, but default to some + common (non-timezone aware) ISO 8601 formats. + + When specifying *DateTime* formats, you should only pass a list or a tuple. + Other iterables, like generators, may lead to surprising results. + + The format strings are processed using ``datetime.strptime``, and this + consequently defines the format strings which are allowed. + + Parsing is tried using each format, in order, and the first format which + parses successfully is used. + + :param formats: A list or tuple of date format strings, in the order in + which they should be tried. Defaults to + ``'%Y-%m-%d'``, ``'%Y-%m-%dT%H:%M:%S'``, + ``'%Y-%m-%d %H:%M:%S'``. + """ + + name = "datetime" + + def __init__(self, formats: t.Optional[t.Sequence[str]] = None): + self.formats: t.Sequence[str] = formats or [ + "%Y-%m-%d", + "%Y-%m-%dT%H:%M:%S", + "%Y-%m-%d %H:%M:%S", + ] + + def to_info_dict(self) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict["formats"] = self.formats + return info_dict + + def get_metavar(self, param: "Parameter") -> str: + return f"[{'|'.join(self.formats)}]" + + def _try_to_convert_date(self, value: t.Any, format: str) -> t.Optional[datetime]: + try: + return datetime.strptime(value, format) + except ValueError: + return None + + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + if isinstance(value, datetime): + return value + + for format in self.formats: + converted = self._try_to_convert_date(value, format) + + if converted is not None: + return converted + + formats_str = ", ".join(map(repr, self.formats)) + self.fail( + ngettext( + "{value!r} does not match the format {format}.", + "{value!r} does not match the formats {formats}.", + len(self.formats), + ).format(value=value, format=formats_str, formats=formats_str), + param, + ctx, + ) + + def __repr__(self) -> str: + return "DateTime" + + +class _NumberParamTypeBase(ParamType): + _number_class: t.ClassVar[t.Type[t.Any]] + + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + try: + return self._number_class(value) + except ValueError: + self.fail( + _("{value!r} is not a valid {number_type}.").format( + value=value, number_type=self.name + ), + param, + ctx, + ) + + +class _NumberRangeBase(_NumberParamTypeBase): + def __init__( + self, + min: t.Optional[float] = None, + max: t.Optional[float] = None, + min_open: bool = False, + max_open: bool = False, + clamp: bool = False, + ) -> None: + self.min = min + self.max = max + self.min_open = min_open + self.max_open = max_open + self.clamp = clamp + + def to_info_dict(self) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict.update( + min=self.min, + max=self.max, + min_open=self.min_open, + max_open=self.max_open, + clamp=self.clamp, + ) + return info_dict + + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + import operator + + rv = super().convert(value, param, ctx) + lt_min: bool = self.min is not None and ( + operator.le if self.min_open else operator.lt + )(rv, self.min) + gt_max: bool = self.max is not None and ( + operator.ge if self.max_open else operator.gt + )(rv, self.max) + + if self.clamp: + if lt_min: + return self._clamp(self.min, 1, self.min_open) # type: ignore + + if gt_max: + return self._clamp(self.max, -1, self.max_open) # type: ignore + + if lt_min or gt_max: + self.fail( + _("{value} is not in the range {range}.").format( + value=rv, range=self._describe_range() + ), + param, + ctx, + ) + + return rv + + def _clamp(self, bound: float, dir: "te.Literal[1, -1]", open: bool) -> float: + """Find the valid value to clamp to bound in the given + direction. + + :param bound: The boundary value. + :param dir: 1 or -1 indicating the direction to move. + :param open: If true, the range does not include the bound. + """ + raise NotImplementedError + + def _describe_range(self) -> str: + """Describe the range for use in help text.""" + if self.min is None: + op = "<" if self.max_open else "<=" + return f"x{op}{self.max}" + + if self.max is None: + op = ">" if self.min_open else ">=" + return f"x{op}{self.min}" + + lop = "<" if self.min_open else "<=" + rop = "<" if self.max_open else "<=" + return f"{self.min}{lop}x{rop}{self.max}" + + def __repr__(self) -> str: + clamp = " clamped" if self.clamp else "" + return f"<{type(self).__name__} {self._describe_range()}{clamp}>" + + +class IntParamType(_NumberParamTypeBase): + name = "integer" + _number_class = int + + def __repr__(self) -> str: + return "INT" + + +class IntRange(_NumberRangeBase, IntParamType): + """Restrict an :data:`click.INT` value to a range of accepted + values. See :ref:`ranges`. + + If ``min`` or ``max`` are not passed, any value is accepted in that + direction. If ``min_open`` or ``max_open`` are enabled, the + corresponding boundary is not included in the range. + + If ``clamp`` is enabled, a value outside the range is clamped to the + boundary instead of failing. + + .. versionchanged:: 8.0 + Added the ``min_open`` and ``max_open`` parameters. + """ + + name = "integer range" + + def _clamp( # type: ignore + self, bound: int, dir: "te.Literal[1, -1]", open: bool + ) -> int: + if not open: + return bound + + return bound + dir + + +class FloatParamType(_NumberParamTypeBase): + name = "float" + _number_class = float + + def __repr__(self) -> str: + return "FLOAT" + + +class FloatRange(_NumberRangeBase, FloatParamType): + """Restrict a :data:`click.FLOAT` value to a range of accepted + values. See :ref:`ranges`. + + If ``min`` or ``max`` are not passed, any value is accepted in that + direction. If ``min_open`` or ``max_open`` are enabled, the + corresponding boundary is not included in the range. + + If ``clamp`` is enabled, a value outside the range is clamped to the + boundary instead of failing. This is not supported if either + boundary is marked ``open``. + + .. versionchanged:: 8.0 + Added the ``min_open`` and ``max_open`` parameters. + """ + + name = "float range" + + def __init__( + self, + min: t.Optional[float] = None, + max: t.Optional[float] = None, + min_open: bool = False, + max_open: bool = False, + clamp: bool = False, + ) -> None: + super().__init__( + min=min, max=max, min_open=min_open, max_open=max_open, clamp=clamp + ) + + if (min_open or max_open) and clamp: + raise TypeError("Clamping is not supported for open bounds.") + + def _clamp(self, bound: float, dir: "te.Literal[1, -1]", open: bool) -> float: + if not open: + return bound + + # Could use Python 3.9's math.nextafter here, but clamping an + # open float range doesn't seem to be particularly useful. It's + # left up to the user to write a callback to do it if needed. + raise RuntimeError("Clamping is not supported for open bounds.") + + +class BoolParamType(ParamType): + name = "boolean" + + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + if value in {False, True}: + return bool(value) + + norm = value.strip().lower() + + if norm in {"1", "true", "t", "yes", "y", "on"}: + return True + + if norm in {"0", "false", "f", "no", "n", "off"}: + return False + + self.fail( + _("{value!r} is not a valid boolean.").format(value=value), param, ctx + ) + + def __repr__(self) -> str: + return "BOOL" + + +class UUIDParameterType(ParamType): + name = "uuid" + + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + import uuid + + if isinstance(value, uuid.UUID): + return value + + value = value.strip() + + try: + return uuid.UUID(value) + except ValueError: + self.fail( + _("{value!r} is not a valid UUID.").format(value=value), param, ctx + ) + + def __repr__(self) -> str: + return "UUID" + + +class File(ParamType): + """Declares a parameter to be a file for reading or writing. The file + is automatically closed once the context tears down (after the command + finished working). + + Files can be opened for reading or writing. The special value ``-`` + indicates stdin or stdout depending on the mode. + + By default, the file is opened for reading text data, but it can also be + opened in binary mode or for writing. The encoding parameter can be used + to force a specific encoding. + + The `lazy` flag controls if the file should be opened immediately or upon + first IO. The default is to be non-lazy for standard input and output + streams as well as files opened for reading, `lazy` otherwise. When opening a + file lazily for reading, it is still opened temporarily for validation, but + will not be held open until first IO. lazy is mainly useful when opening + for writing to avoid creating the file until it is needed. + + Starting with Click 2.0, files can also be opened atomically in which + case all writes go into a separate file in the same folder and upon + completion the file will be moved over to the original location. This + is useful if a file regularly read by other users is modified. + + See :ref:`file-args` for more information. + """ + + name = "filename" + envvar_list_splitter: t.ClassVar[str] = os.path.pathsep + + def __init__( + self, + mode: str = "r", + encoding: t.Optional[str] = None, + errors: t.Optional[str] = "strict", + lazy: t.Optional[bool] = None, + atomic: bool = False, + ) -> None: + self.mode = mode + self.encoding = encoding + self.errors = errors + self.lazy = lazy + self.atomic = atomic + + def to_info_dict(self) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict.update(mode=self.mode, encoding=self.encoding) + return info_dict + + def resolve_lazy_flag(self, value: "t.Union[str, os.PathLike[str]]") -> bool: + if self.lazy is not None: + return self.lazy + if os.fspath(value) == "-": + return False + elif "w" in self.mode: + return True + return False + + def convert( + self, + value: t.Union[str, "os.PathLike[str]", t.IO[t.Any]], + param: t.Optional["Parameter"], + ctx: t.Optional["Context"], + ) -> t.IO[t.Any]: + if _is_file_like(value): + return value + + value = t.cast("t.Union[str, os.PathLike[str]]", value) + + try: + lazy = self.resolve_lazy_flag(value) + + if lazy: + lf = LazyFile( + value, self.mode, self.encoding, self.errors, atomic=self.atomic + ) + + if ctx is not None: + ctx.call_on_close(lf.close_intelligently) + + return t.cast(t.IO[t.Any], lf) + + f, should_close = open_stream( + value, self.mode, self.encoding, self.errors, atomic=self.atomic + ) + + # If a context is provided, we automatically close the file + # at the end of the context execution (or flush out). If a + # context does not exist, it's the caller's responsibility to + # properly close the file. This for instance happens when the + # type is used with prompts. + if ctx is not None: + if should_close: + ctx.call_on_close(safecall(f.close)) + else: + ctx.call_on_close(safecall(f.flush)) + + return f + except OSError as e: # noqa: B014 + self.fail(f"'{format_filename(value)}': {e.strerror}", param, ctx) + + def shell_complete( + self, ctx: "Context", param: "Parameter", incomplete: str + ) -> t.List["CompletionItem"]: + """Return a special completion marker that tells the completion + system to use the shell to provide file path completions. + + :param ctx: Invocation context for this command. + :param param: The parameter that is requesting completion. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from click.shell_completion import CompletionItem + + return [CompletionItem(incomplete, type="file")] + + +def _is_file_like(value: t.Any) -> "te.TypeGuard[t.IO[t.Any]]": + return hasattr(value, "read") or hasattr(value, "write") + + +class Path(ParamType): + """The ``Path`` type is similar to the :class:`File` type, but + returns the filename instead of an open file. Various checks can be + enabled to validate the type of file and permissions. + + :param exists: The file or directory needs to exist for the value to + be valid. If this is not set to ``True``, and the file does not + exist, then all further checks are silently skipped. + :param file_okay: Allow a file as a value. + :param dir_okay: Allow a directory as a value. + :param readable: if true, a readable check is performed. + :param writable: if true, a writable check is performed. + :param executable: if true, an executable check is performed. + :param resolve_path: Make the value absolute and resolve any + symlinks. A ``~`` is not expanded, as this is supposed to be + done by the shell only. + :param allow_dash: Allow a single dash as a value, which indicates + a standard stream (but does not open it). Use + :func:`~click.open_file` to handle opening this value. + :param path_type: Convert the incoming path value to this type. If + ``None``, keep Python's default, which is ``str``. Useful to + convert to :class:`pathlib.Path`. + + .. versionchanged:: 8.1 + Added the ``executable`` parameter. + + .. versionchanged:: 8.0 + Allow passing ``path_type=pathlib.Path``. + + .. versionchanged:: 6.0 + Added the ``allow_dash`` parameter. + """ + + envvar_list_splitter: t.ClassVar[str] = os.path.pathsep + + def __init__( + self, + exists: bool = False, + file_okay: bool = True, + dir_okay: bool = True, + writable: bool = False, + readable: bool = True, + resolve_path: bool = False, + allow_dash: bool = False, + path_type: t.Optional[t.Type[t.Any]] = None, + executable: bool = False, + ): + self.exists = exists + self.file_okay = file_okay + self.dir_okay = dir_okay + self.readable = readable + self.writable = writable + self.executable = executable + self.resolve_path = resolve_path + self.allow_dash = allow_dash + self.type = path_type + + if self.file_okay and not self.dir_okay: + self.name: str = _("file") + elif self.dir_okay and not self.file_okay: + self.name = _("directory") + else: + self.name = _("path") + + def to_info_dict(self) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict.update( + exists=self.exists, + file_okay=self.file_okay, + dir_okay=self.dir_okay, + writable=self.writable, + readable=self.readable, + allow_dash=self.allow_dash, + ) + return info_dict + + def coerce_path_result( + self, value: "t.Union[str, os.PathLike[str]]" + ) -> "t.Union[str, bytes, os.PathLike[str]]": + if self.type is not None and not isinstance(value, self.type): + if self.type is str: + return os.fsdecode(value) + elif self.type is bytes: + return os.fsencode(value) + else: + return t.cast("os.PathLike[str]", self.type(value)) + + return value + + def convert( + self, + value: "t.Union[str, os.PathLike[str]]", + param: t.Optional["Parameter"], + ctx: t.Optional["Context"], + ) -> "t.Union[str, bytes, os.PathLike[str]]": + rv = value + + is_dash = self.file_okay and self.allow_dash and rv in (b"-", "-") + + if not is_dash: + if self.resolve_path: + # os.path.realpath doesn't resolve symlinks on Windows + # until Python 3.8. Use pathlib for now. + import pathlib + + rv = os.fsdecode(pathlib.Path(rv).resolve()) + + try: + st = os.stat(rv) + except OSError: + if not self.exists: + return self.coerce_path_result(rv) + self.fail( + _("{name} {filename!r} does not exist.").format( + name=self.name.title(), filename=format_filename(value) + ), + param, + ctx, + ) + + if not self.file_okay and stat.S_ISREG(st.st_mode): + self.fail( + _("{name} {filename!r} is a file.").format( + name=self.name.title(), filename=format_filename(value) + ), + param, + ctx, + ) + if not self.dir_okay and stat.S_ISDIR(st.st_mode): + self.fail( + _("{name} '{filename}' is a directory.").format( + name=self.name.title(), filename=format_filename(value) + ), + param, + ctx, + ) + + if self.readable and not os.access(rv, os.R_OK): + self.fail( + _("{name} {filename!r} is not readable.").format( + name=self.name.title(), filename=format_filename(value) + ), + param, + ctx, + ) + + if self.writable and not os.access(rv, os.W_OK): + self.fail( + _("{name} {filename!r} is not writable.").format( + name=self.name.title(), filename=format_filename(value) + ), + param, + ctx, + ) + + if self.executable and not os.access(value, os.X_OK): + self.fail( + _("{name} {filename!r} is not executable.").format( + name=self.name.title(), filename=format_filename(value) + ), + param, + ctx, + ) + + return self.coerce_path_result(rv) + + def shell_complete( + self, ctx: "Context", param: "Parameter", incomplete: str + ) -> t.List["CompletionItem"]: + """Return a special completion marker that tells the completion + system to use the shell to provide path completions for only + directories or any paths. + + :param ctx: Invocation context for this command. + :param param: The parameter that is requesting completion. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from click.shell_completion import CompletionItem + + type = "dir" if self.dir_okay and not self.file_okay else "file" + return [CompletionItem(incomplete, type=type)] + + +class Tuple(CompositeParamType): + """The default behavior of Click is to apply a type on a value directly. + This works well in most cases, except for when `nargs` is set to a fixed + count and different types should be used for different items. In this + case the :class:`Tuple` type can be used. This type can only be used + if `nargs` is set to a fixed number. + + For more information see :ref:`tuple-type`. + + This can be selected by using a Python tuple literal as a type. + + :param types: a list of types that should be used for the tuple items. + """ + + def __init__(self, types: t.Sequence[t.Union[t.Type[t.Any], ParamType]]) -> None: + self.types: t.Sequence[ParamType] = [convert_type(ty) for ty in types] + + def to_info_dict(self) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict["types"] = [t.to_info_dict() for t in self.types] + return info_dict + + @property + def name(self) -> str: # type: ignore + return f"<{' '.join(ty.name for ty in self.types)}>" + + @property + def arity(self) -> int: # type: ignore + return len(self.types) + + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + len_type = len(self.types) + len_value = len(value) + + if len_value != len_type: + self.fail( + ngettext( + "{len_type} values are required, but {len_value} was given.", + "{len_type} values are required, but {len_value} were given.", + len_value, + ).format(len_type=len_type, len_value=len_value), + param=param, + ctx=ctx, + ) + + return tuple(ty(x, param, ctx) for ty, x in zip(self.types, value)) + + +def convert_type(ty: t.Optional[t.Any], default: t.Optional[t.Any] = None) -> ParamType: + """Find the most appropriate :class:`ParamType` for the given Python + type. If the type isn't provided, it can be inferred from a default + value. + """ + guessed_type = False + + if ty is None and default is not None: + if isinstance(default, (tuple, list)): + # If the default is empty, ty will remain None and will + # return STRING. + if default: + item = default[0] + + # A tuple of tuples needs to detect the inner types. + # Can't call convert recursively because that would + # incorrectly unwind the tuple to a single type. + if isinstance(item, (tuple, list)): + ty = tuple(map(type, item)) + else: + ty = type(item) + else: + ty = type(default) + + guessed_type = True + + if isinstance(ty, tuple): + return Tuple(ty) + + if isinstance(ty, ParamType): + return ty + + if ty is str or ty is None: + return STRING + + if ty is int: + return INT + + if ty is float: + return FLOAT + + if ty is bool: + return BOOL + + if guessed_type: + return STRING + + if __debug__: + try: + if issubclass(ty, ParamType): + raise AssertionError( + f"Attempted to use an uninstantiated parameter type ({ty})." + ) + except TypeError: + # ty is an instance (correct), so issubclass fails. + pass + + return FuncParamType(ty) + + +#: A dummy parameter type that just does nothing. From a user's +#: perspective this appears to just be the same as `STRING` but +#: internally no string conversion takes place if the input was bytes. +#: This is usually useful when working with file paths as they can +#: appear in bytes and unicode. +#: +#: For path related uses the :class:`Path` type is a better choice but +#: there are situations where an unprocessed type is useful which is why +#: it is is provided. +#: +#: .. versionadded:: 4.0 +UNPROCESSED = UnprocessedParamType() + +#: A unicode string parameter type which is the implicit default. This +#: can also be selected by using ``str`` as type. +STRING = StringParamType() + +#: An integer parameter. This can also be selected by using ``int`` as +#: type. +INT = IntParamType() + +#: A floating point value parameter. This can also be selected by using +#: ``float`` as type. +FLOAT = FloatParamType() + +#: A boolean parameter. This is the default for boolean flags. This can +#: also be selected by using ``bool`` as a type. +BOOL = BoolParamType() + +#: A UUID parameter. +UUID = UUIDParameterType() diff --git a/lib/go-jinja2/internal/data/darwin-amd64/click/utils.py b/lib/go-jinja2/internal/data/darwin-amd64/click/utils.py new file mode 100644 index 000000000..d536434f0 --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/click/utils.py @@ -0,0 +1,624 @@ +import os +import re +import sys +import typing as t +from functools import update_wrapper +from types import ModuleType +from types import TracebackType + +from ._compat import _default_text_stderr +from ._compat import _default_text_stdout +from ._compat import _find_binary_writer +from ._compat import auto_wrap_for_ansi +from ._compat import binary_streams +from ._compat import open_stream +from ._compat import should_strip_ansi +from ._compat import strip_ansi +from ._compat import text_streams +from ._compat import WIN +from .globals import resolve_color_default + +if t.TYPE_CHECKING: + import typing_extensions as te + + P = te.ParamSpec("P") + +R = t.TypeVar("R") + + +def _posixify(name: str) -> str: + return "-".join(name.split()).lower() + + +def safecall(func: "t.Callable[P, R]") -> "t.Callable[P, t.Optional[R]]": + """Wraps a function so that it swallows exceptions.""" + + def wrapper(*args: "P.args", **kwargs: "P.kwargs") -> t.Optional[R]: + try: + return func(*args, **kwargs) + except Exception: + pass + return None + + return update_wrapper(wrapper, func) + + +def make_str(value: t.Any) -> str: + """Converts a value into a valid string.""" + if isinstance(value, bytes): + try: + return value.decode(sys.getfilesystemencoding()) + except UnicodeError: + return value.decode("utf-8", "replace") + return str(value) + + +def make_default_short_help(help: str, max_length: int = 45) -> str: + """Returns a condensed version of help string.""" + # Consider only the first paragraph. + paragraph_end = help.find("\n\n") + + if paragraph_end != -1: + help = help[:paragraph_end] + + # Collapse newlines, tabs, and spaces. + words = help.split() + + if not words: + return "" + + # The first paragraph started with a "no rewrap" marker, ignore it. + if words[0] == "\b": + words = words[1:] + + total_length = 0 + last_index = len(words) - 1 + + for i, word in enumerate(words): + total_length += len(word) + (i > 0) + + if total_length > max_length: # too long, truncate + break + + if word[-1] == ".": # sentence end, truncate without "..." + return " ".join(words[: i + 1]) + + if total_length == max_length and i != last_index: + break # not at sentence end, truncate with "..." + else: + return " ".join(words) # no truncation needed + + # Account for the length of the suffix. + total_length += len("...") + + # remove words until the length is short enough + while i > 0: + total_length -= len(words[i]) + (i > 0) + + if total_length <= max_length: + break + + i -= 1 + + return " ".join(words[:i]) + "..." + + +class LazyFile: + """A lazy file works like a regular file but it does not fully open + the file but it does perform some basic checks early to see if the + filename parameter does make sense. This is useful for safely opening + files for writing. + """ + + def __init__( + self, + filename: t.Union[str, "os.PathLike[str]"], + mode: str = "r", + encoding: t.Optional[str] = None, + errors: t.Optional[str] = "strict", + atomic: bool = False, + ): + self.name: str = os.fspath(filename) + self.mode = mode + self.encoding = encoding + self.errors = errors + self.atomic = atomic + self._f: t.Optional[t.IO[t.Any]] + self.should_close: bool + + if self.name == "-": + self._f, self.should_close = open_stream(filename, mode, encoding, errors) + else: + if "r" in mode: + # Open and close the file in case we're opening it for + # reading so that we can catch at least some errors in + # some cases early. + open(filename, mode).close() + self._f = None + self.should_close = True + + def __getattr__(self, name: str) -> t.Any: + return getattr(self.open(), name) + + def __repr__(self) -> str: + if self._f is not None: + return repr(self._f) + return f"" + + def open(self) -> t.IO[t.Any]: + """Opens the file if it's not yet open. This call might fail with + a :exc:`FileError`. Not handling this error will produce an error + that Click shows. + """ + if self._f is not None: + return self._f + try: + rv, self.should_close = open_stream( + self.name, self.mode, self.encoding, self.errors, atomic=self.atomic + ) + except OSError as e: # noqa: E402 + from .exceptions import FileError + + raise FileError(self.name, hint=e.strerror) from e + self._f = rv + return rv + + def close(self) -> None: + """Closes the underlying file, no matter what.""" + if self._f is not None: + self._f.close() + + def close_intelligently(self) -> None: + """This function only closes the file if it was opened by the lazy + file wrapper. For instance this will never close stdin. + """ + if self.should_close: + self.close() + + def __enter__(self) -> "LazyFile": + return self + + def __exit__( + self, + exc_type: t.Optional[t.Type[BaseException]], + exc_value: t.Optional[BaseException], + tb: t.Optional[TracebackType], + ) -> None: + self.close_intelligently() + + def __iter__(self) -> t.Iterator[t.AnyStr]: + self.open() + return iter(self._f) # type: ignore + + +class KeepOpenFile: + def __init__(self, file: t.IO[t.Any]) -> None: + self._file: t.IO[t.Any] = file + + def __getattr__(self, name: str) -> t.Any: + return getattr(self._file, name) + + def __enter__(self) -> "KeepOpenFile": + return self + + def __exit__( + self, + exc_type: t.Optional[t.Type[BaseException]], + exc_value: t.Optional[BaseException], + tb: t.Optional[TracebackType], + ) -> None: + pass + + def __repr__(self) -> str: + return repr(self._file) + + def __iter__(self) -> t.Iterator[t.AnyStr]: + return iter(self._file) + + +def echo( + message: t.Optional[t.Any] = None, + file: t.Optional[t.IO[t.Any]] = None, + nl: bool = True, + err: bool = False, + color: t.Optional[bool] = None, +) -> None: + """Print a message and newline to stdout or a file. This should be + used instead of :func:`print` because it provides better support + for different data, files, and environments. + + Compared to :func:`print`, this does the following: + + - Ensures that the output encoding is not misconfigured on Linux. + - Supports Unicode in the Windows console. + - Supports writing to binary outputs, and supports writing bytes + to text outputs. + - Supports colors and styles on Windows. + - Removes ANSI color and style codes if the output does not look + like an interactive terminal. + - Always flushes the output. + + :param message: The string or bytes to output. Other objects are + converted to strings. + :param file: The file to write to. Defaults to ``stdout``. + :param err: Write to ``stderr`` instead of ``stdout``. + :param nl: Print a newline after the message. Enabled by default. + :param color: Force showing or hiding colors and other styles. By + default Click will remove color if the output does not look like + an interactive terminal. + + .. versionchanged:: 6.0 + Support Unicode output on the Windows console. Click does not + modify ``sys.stdout``, so ``sys.stdout.write()`` and ``print()`` + will still not support Unicode. + + .. versionchanged:: 4.0 + Added the ``color`` parameter. + + .. versionadded:: 3.0 + Added the ``err`` parameter. + + .. versionchanged:: 2.0 + Support colors on Windows if colorama is installed. + """ + if file is None: + if err: + file = _default_text_stderr() + else: + file = _default_text_stdout() + + # There are no standard streams attached to write to. For example, + # pythonw on Windows. + if file is None: + return + + # Convert non bytes/text into the native string type. + if message is not None and not isinstance(message, (str, bytes, bytearray)): + out: t.Optional[t.Union[str, bytes]] = str(message) + else: + out = message + + if nl: + out = out or "" + if isinstance(out, str): + out += "\n" + else: + out += b"\n" + + if not out: + file.flush() + return + + # If there is a message and the value looks like bytes, we manually + # need to find the binary stream and write the message in there. + # This is done separately so that most stream types will work as you + # would expect. Eg: you can write to StringIO for other cases. + if isinstance(out, (bytes, bytearray)): + binary_file = _find_binary_writer(file) + + if binary_file is not None: + file.flush() + binary_file.write(out) + binary_file.flush() + return + + # ANSI style code support. For no message or bytes, nothing happens. + # When outputting to a file instead of a terminal, strip codes. + else: + color = resolve_color_default(color) + + if should_strip_ansi(file, color): + out = strip_ansi(out) + elif WIN: + if auto_wrap_for_ansi is not None: + file = auto_wrap_for_ansi(file) # type: ignore + elif not color: + out = strip_ansi(out) + + file.write(out) # type: ignore + file.flush() + + +def get_binary_stream(name: "te.Literal['stdin', 'stdout', 'stderr']") -> t.BinaryIO: + """Returns a system stream for byte processing. + + :param name: the name of the stream to open. Valid names are ``'stdin'``, + ``'stdout'`` and ``'stderr'`` + """ + opener = binary_streams.get(name) + if opener is None: + raise TypeError(f"Unknown standard stream '{name}'") + return opener() + + +def get_text_stream( + name: "te.Literal['stdin', 'stdout', 'stderr']", + encoding: t.Optional[str] = None, + errors: t.Optional[str] = "strict", +) -> t.TextIO: + """Returns a system stream for text processing. This usually returns + a wrapped stream around a binary stream returned from + :func:`get_binary_stream` but it also can take shortcuts for already + correctly configured streams. + + :param name: the name of the stream to open. Valid names are ``'stdin'``, + ``'stdout'`` and ``'stderr'`` + :param encoding: overrides the detected default encoding. + :param errors: overrides the default error mode. + """ + opener = text_streams.get(name) + if opener is None: + raise TypeError(f"Unknown standard stream '{name}'") + return opener(encoding, errors) + + +def open_file( + filename: str, + mode: str = "r", + encoding: t.Optional[str] = None, + errors: t.Optional[str] = "strict", + lazy: bool = False, + atomic: bool = False, +) -> t.IO[t.Any]: + """Open a file, with extra behavior to handle ``'-'`` to indicate + a standard stream, lazy open on write, and atomic write. Similar to + the behavior of the :class:`~click.File` param type. + + If ``'-'`` is given to open ``stdout`` or ``stdin``, the stream is + wrapped so that using it in a context manager will not close it. + This makes it possible to use the function without accidentally + closing a standard stream: + + .. code-block:: python + + with open_file(filename) as f: + ... + + :param filename: The name of the file to open, or ``'-'`` for + ``stdin``/``stdout``. + :param mode: The mode in which to open the file. + :param encoding: The encoding to decode or encode a file opened in + text mode. + :param errors: The error handling mode. + :param lazy: Wait to open the file until it is accessed. For read + mode, the file is temporarily opened to raise access errors + early, then closed until it is read again. + :param atomic: Write to a temporary file and replace the given file + on close. + + .. versionadded:: 3.0 + """ + if lazy: + return t.cast( + t.IO[t.Any], LazyFile(filename, mode, encoding, errors, atomic=atomic) + ) + + f, should_close = open_stream(filename, mode, encoding, errors, atomic=atomic) + + if not should_close: + f = t.cast(t.IO[t.Any], KeepOpenFile(f)) + + return f + + +def format_filename( + filename: "t.Union[str, bytes, os.PathLike[str], os.PathLike[bytes]]", + shorten: bool = False, +) -> str: + """Format a filename as a string for display. Ensures the filename can be + displayed by replacing any invalid bytes or surrogate escapes in the name + with the replacement character ``�``. + + Invalid bytes or surrogate escapes will raise an error when written to a + stream with ``errors="strict". This will typically happen with ``stdout`` + when the locale is something like ``en_GB.UTF-8``. + + Many scenarios *are* safe to write surrogates though, due to PEP 538 and + PEP 540, including: + + - Writing to ``stderr``, which uses ``errors="backslashreplace"``. + - The system has ``LANG=C.UTF-8``, ``C``, or ``POSIX``. Python opens + stdout and stderr with ``errors="surrogateescape"``. + - None of ``LANG/LC_*`` are set. Python assumes ``LANG=C.UTF-8``. + - Python is started in UTF-8 mode with ``PYTHONUTF8=1`` or ``-X utf8``. + Python opens stdout and stderr with ``errors="surrogateescape"``. + + :param filename: formats a filename for UI display. This will also convert + the filename into unicode without failing. + :param shorten: this optionally shortens the filename to strip of the + path that leads up to it. + """ + if shorten: + filename = os.path.basename(filename) + else: + filename = os.fspath(filename) + + if isinstance(filename, bytes): + filename = filename.decode(sys.getfilesystemencoding(), "replace") + else: + filename = filename.encode("utf-8", "surrogateescape").decode( + "utf-8", "replace" + ) + + return filename + + +def get_app_dir(app_name: str, roaming: bool = True, force_posix: bool = False) -> str: + r"""Returns the config folder for the application. The default behavior + is to return whatever is most appropriate for the operating system. + + To give you an idea, for an app called ``"Foo Bar"``, something like + the following folders could be returned: + + Mac OS X: + ``~/Library/Application Support/Foo Bar`` + Mac OS X (POSIX): + ``~/.foo-bar`` + Unix: + ``~/.config/foo-bar`` + Unix (POSIX): + ``~/.foo-bar`` + Windows (roaming): + ``C:\Users\\AppData\Roaming\Foo Bar`` + Windows (not roaming): + ``C:\Users\\AppData\Local\Foo Bar`` + + .. versionadded:: 2.0 + + :param app_name: the application name. This should be properly capitalized + and can contain whitespace. + :param roaming: controls if the folder should be roaming or not on Windows. + Has no effect otherwise. + :param force_posix: if this is set to `True` then on any POSIX system the + folder will be stored in the home folder with a leading + dot instead of the XDG config home or darwin's + application support folder. + """ + if WIN: + key = "APPDATA" if roaming else "LOCALAPPDATA" + folder = os.environ.get(key) + if folder is None: + folder = os.path.expanduser("~") + return os.path.join(folder, app_name) + if force_posix: + return os.path.join(os.path.expanduser(f"~/.{_posixify(app_name)}")) + if sys.platform == "darwin": + return os.path.join( + os.path.expanduser("~/Library/Application Support"), app_name + ) + return os.path.join( + os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config")), + _posixify(app_name), + ) + + +class PacifyFlushWrapper: + """This wrapper is used to catch and suppress BrokenPipeErrors resulting + from ``.flush()`` being called on broken pipe during the shutdown/final-GC + of the Python interpreter. Notably ``.flush()`` is always called on + ``sys.stdout`` and ``sys.stderr``. So as to have minimal impact on any + other cleanup code, and the case where the underlying file is not a broken + pipe, all calls and attributes are proxied. + """ + + def __init__(self, wrapped: t.IO[t.Any]) -> None: + self.wrapped = wrapped + + def flush(self) -> None: + try: + self.wrapped.flush() + except OSError as e: + import errno + + if e.errno != errno.EPIPE: + raise + + def __getattr__(self, attr: str) -> t.Any: + return getattr(self.wrapped, attr) + + +def _detect_program_name( + path: t.Optional[str] = None, _main: t.Optional[ModuleType] = None +) -> str: + """Determine the command used to run the program, for use in help + text. If a file or entry point was executed, the file name is + returned. If ``python -m`` was used to execute a module or package, + ``python -m name`` is returned. + + This doesn't try to be too precise, the goal is to give a concise + name for help text. Files are only shown as their name without the + path. ``python`` is only shown for modules, and the full path to + ``sys.executable`` is not shown. + + :param path: The Python file being executed. Python puts this in + ``sys.argv[0]``, which is used by default. + :param _main: The ``__main__`` module. This should only be passed + during internal testing. + + .. versionadded:: 8.0 + Based on command args detection in the Werkzeug reloader. + + :meta private: + """ + if _main is None: + _main = sys.modules["__main__"] + + if not path: + path = sys.argv[0] + + # The value of __package__ indicates how Python was called. It may + # not exist if a setuptools script is installed as an egg. It may be + # set incorrectly for entry points created with pip on Windows. + # It is set to "" inside a Shiv or PEX zipapp. + if getattr(_main, "__package__", None) in {None, ""} or ( + os.name == "nt" + and _main.__package__ == "" + and not os.path.exists(path) + and os.path.exists(f"{path}.exe") + ): + # Executed a file, like "python app.py". + return os.path.basename(path) + + # Executed a module, like "python -m example". + # Rewritten by Python from "-m script" to "/path/to/script.py". + # Need to look at main module to determine how it was executed. + py_module = t.cast(str, _main.__package__) + name = os.path.splitext(os.path.basename(path))[0] + + # A submodule like "example.cli". + if name != "__main__": + py_module = f"{py_module}.{name}" + + return f"python -m {py_module.lstrip('.')}" + + +def _expand_args( + args: t.Iterable[str], + *, + user: bool = True, + env: bool = True, + glob_recursive: bool = True, +) -> t.List[str]: + """Simulate Unix shell expansion with Python functions. + + See :func:`glob.glob`, :func:`os.path.expanduser`, and + :func:`os.path.expandvars`. + + This is intended for use on Windows, where the shell does not do any + expansion. It may not exactly match what a Unix shell would do. + + :param args: List of command line arguments to expand. + :param user: Expand user home directory. + :param env: Expand environment variables. + :param glob_recursive: ``**`` matches directories recursively. + + .. versionchanged:: 8.1 + Invalid glob patterns are treated as empty expansions rather + than raising an error. + + .. versionadded:: 8.0 + + :meta private: + """ + from glob import glob + + out = [] + + for arg in args: + if user: + arg = os.path.expanduser(arg) + + if env: + arg = os.path.expandvars(arg) + + try: + matches = glob(arg, recursive=glob_recursive) + except re.error: + matches = [] + + if not matches: + out.append(arg) + else: + out.extend(matches) + + return out diff --git a/lib/go-jinja2/internal/data/darwin-amd64/files.json b/lib/go-jinja2/internal/data/darwin-amd64/files.json new file mode 100644 index 000000000..0b2e283b2 --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/files.json @@ -0,0 +1,888 @@ +{ + "contentHash": "d6fc8f26c0da5462509c87d32316bba4e8766aec5635673b2cd5cb73f0be68a2", + "files": [ + { + "name": "MarkupSafe-2.1.5.dist-info", + "size": 0, + "perm": 2147484141 + }, + { + "name": "MarkupSafe-2.1.5.dist-info/INSTALLER", + "size": 4, + "perm": 420 + }, + { + "name": "MarkupSafe-2.1.5.dist-info/LICENSE.rst", + "size": 1475, + "perm": 420 + }, + { + "name": "MarkupSafe-2.1.5.dist-info/METADATA", + "size": 3003, + "perm": 420 + }, + { + "name": "MarkupSafe-2.1.5.dist-info/RECORD", + "size": 1190, + "perm": 420 + }, + { + "name": "MarkupSafe-2.1.5.dist-info/REQUESTED", + "size": 0, + "perm": 420 + }, + { + "name": "MarkupSafe-2.1.5.dist-info/WHEEL", + "size": 111, + "perm": 420 + }, + { + "name": "MarkupSafe-2.1.5.dist-info/top_level.txt", + "size": 11, + "perm": 420 + }, + { + "name": "PyYAML-6.0.1.dist-info", + "size": 0, + "perm": 2147484141 + }, + { + "name": "PyYAML-6.0.1.dist-info/INSTALLER", + "size": 4, + "perm": 420 + }, + { + "name": "PyYAML-6.0.1.dist-info/LICENSE", + "size": 1101, + "perm": 420 + }, + { + "name": "PyYAML-6.0.1.dist-info/METADATA", + "size": 2058, + "perm": 420 + }, + { + "name": "PyYAML-6.0.1.dist-info/RECORD", + "size": 2773, + "perm": 420 + }, + { + "name": "PyYAML-6.0.1.dist-info/REQUESTED", + "size": 0, + "perm": 420 + }, + { + "name": "PyYAML-6.0.1.dist-info/WHEEL", + "size": 111, + "perm": 420 + }, + { + "name": "PyYAML-6.0.1.dist-info/top_level.txt", + "size": 11, + "perm": 420 + }, + { + "name": "_yaml", + "size": 0, + "perm": 2147484141 + }, + { + "name": "_yaml/__init__.py", + "size": 1402, + "perm": 420 + }, + { + "name": "bin", + "size": 0, + "perm": 2147484141 + }, + { + "name": "bin/jsonpath_ng", + "size": 261, + "perm": 493 + }, + { + "name": "bin/slugify", + "size": 239, + "perm": 493 + }, + { + "name": "click", + "size": 0, + "perm": 2147484141 + }, + { + "name": "click-8.1.7.dist-info", + "size": 0, + "perm": 2147484141 + }, + { + "name": "click-8.1.7.dist-info/INSTALLER", + "size": 4, + "perm": 420 + }, + { + "name": "click-8.1.7.dist-info/LICENSE.rst", + "size": 1475, + "perm": 420 + }, + { + "name": "click-8.1.7.dist-info/METADATA", + "size": 3014, + "perm": 420 + }, + { + "name": "click-8.1.7.dist-info/RECORD", + "size": 2582, + "perm": 420 + }, + { + "name": "click-8.1.7.dist-info/REQUESTED", + "size": 0, + "perm": 420 + }, + { + "name": "click-8.1.7.dist-info/WHEEL", + "size": 92, + "perm": 420 + }, + { + "name": "click-8.1.7.dist-info/top_level.txt", + "size": 6, + "perm": 420 + }, + { + "name": "click/__init__.py", + "size": 3138, + "perm": 420 + }, + { + "name": "click/_compat.py", + "size": 18744, + "perm": 420 + }, + { + "name": "click/_termui_impl.py", + "size": 24069, + "perm": 420 + }, + { + "name": "click/_textwrap.py", + "size": 1353, + "perm": 420 + }, + { + "name": "click/_winconsole.py", + "size": 7860, + "perm": 420 + }, + { + "name": "click/core.py", + "size": 114086, + "perm": 420 + }, + { + "name": "click/decorators.py", + "size": 18719, + "perm": 420 + }, + { + "name": "click/exceptions.py", + "size": 9273, + "perm": 420 + }, + { + "name": "click/formatting.py", + "size": 9706, + "perm": 420 + }, + { + "name": "click/globals.py", + "size": 1961, + "perm": 420 + }, + { + "name": "click/parser.py", + "size": 19067, + "perm": 420 + }, + { + "name": "click/py.typed", + "size": 0, + "perm": 420 + }, + { + "name": "click/shell_completion.py", + "size": 18460, + "perm": 420 + }, + { + "name": "click/termui.py", + "size": 28324, + "perm": 420 + }, + { + "name": "click/testing.py", + "size": 16084, + "perm": 420 + }, + { + "name": "click/types.py", + "size": 36391, + "perm": 420 + }, + { + "name": "click/utils.py", + "size": 20298, + "perm": 420 + }, + { + "name": "jinja2", + "size": 0, + "perm": 2147484141 + }, + { + "name": "jinja2-3.1.4.dist-info", + "size": 0, + "perm": 2147484141 + }, + { + "name": "jinja2-3.1.4.dist-info/INSTALLER", + "size": 4, + "perm": 420 + }, + { + "name": "jinja2-3.1.4.dist-info/LICENSE.txt", + "size": 1475, + "perm": 420 + }, + { + "name": "jinja2-3.1.4.dist-info/METADATA", + "size": 2640, + "perm": 420 + }, + { + "name": "jinja2-3.1.4.dist-info/RECORD", + "size": 3697, + "perm": 420 + }, + { + "name": "jinja2-3.1.4.dist-info/REQUESTED", + "size": 0, + "perm": 420 + }, + { + "name": "jinja2-3.1.4.dist-info/WHEEL", + "size": 81, + "perm": 420 + }, + { + "name": "jinja2-3.1.4.dist-info/entry_points.txt", + "size": 58, + "perm": 420 + }, + { + "name": "jinja2/__init__.py", + "size": 1928, + "perm": 420 + }, + { + "name": "jinja2/_identifier.py", + "size": 1958, + "perm": 420 + }, + { + "name": "jinja2/async_utils.py", + "size": 2477, + "perm": 420 + }, + { + "name": "jinja2/bccache.py", + "size": 14061, + "perm": 420 + }, + { + "name": "jinja2/compiler.py", + "size": 72271, + "perm": 420 + }, + { + "name": "jinja2/constants.py", + "size": 1433, + "perm": 420 + }, + { + "name": "jinja2/debug.py", + "size": 6299, + "perm": 420 + }, + { + "name": "jinja2/defaults.py", + "size": 1267, + "perm": 420 + }, + { + "name": "jinja2/environment.py", + "size": 61538, + "perm": 420 + }, + { + "name": "jinja2/exceptions.py", + "size": 5071, + "perm": 420 + }, + { + "name": "jinja2/ext.py", + "size": 31877, + "perm": 420 + }, + { + "name": "jinja2/filters.py", + "size": 54611, + "perm": 420 + }, + { + "name": "jinja2/idtracking.py", + "size": 10704, + "perm": 420 + }, + { + "name": "jinja2/lexer.py", + "size": 29754, + "perm": 420 + }, + { + "name": "jinja2/loaders.py", + "size": 23167, + "perm": 420 + }, + { + "name": "jinja2/meta.py", + "size": 4397, + "perm": 420 + }, + { + "name": "jinja2/nativetypes.py", + "size": 4210, + "perm": 420 + }, + { + "name": "jinja2/nodes.py", + "size": 34579, + "perm": 420 + }, + { + "name": "jinja2/optimizer.py", + "size": 1651, + "perm": 420 + }, + { + "name": "jinja2/parser.py", + "size": 39890, + "perm": 420 + }, + { + "name": "jinja2/py.typed", + "size": 0, + "perm": 420 + }, + { + "name": "jinja2/runtime.py", + "size": 33435, + "perm": 420 + }, + { + "name": "jinja2/sandbox.py", + "size": 14616, + "perm": 420 + }, + { + "name": "jinja2/tests.py", + "size": 5926, + "perm": 420 + }, + { + "name": "jinja2/utils.py", + "size": 23952, + "perm": 420 + }, + { + "name": "jinja2/visitor.py", + "size": 3557, + "perm": 420 + }, + { + "name": "jsonpath_ng", + "size": 0, + "perm": 2147484141 + }, + { + "name": "jsonpath_ng-1.6.1.dist-info", + "size": 0, + "perm": 2147484141 + }, + { + "name": "jsonpath_ng-1.6.1.dist-info/INSTALLER", + "size": 4, + "perm": 420 + }, + { + "name": "jsonpath_ng-1.6.1.dist-info/LICENSE", + "size": 11358, + "perm": 420 + }, + { + "name": "jsonpath_ng-1.6.1.dist-info/METADATA", + "size": 18072, + "perm": 420 + }, + { + "name": "jsonpath_ng-1.6.1.dist-info/RECORD", + "size": 2549, + "perm": 420 + }, + { + "name": "jsonpath_ng-1.6.1.dist-info/REQUESTED", + "size": 0, + "perm": 420 + }, + { + "name": "jsonpath_ng-1.6.1.dist-info/WHEEL", + "size": 92, + "perm": 420 + }, + { + "name": "jsonpath_ng-1.6.1.dist-info/entry_points.txt", + "size": 69, + "perm": 420 + }, + { + "name": "jsonpath_ng-1.6.1.dist-info/top_level.txt", + "size": 12, + "perm": 420 + }, + { + "name": "jsonpath_ng/__init__.py", + "size": 116, + "perm": 420 + }, + { + "name": "jsonpath_ng/bin", + "size": 0, + "perm": 2147484141 + }, + { + "name": "jsonpath_ng/bin/__init__.py", + "size": 0, + "perm": 420 + }, + { + "name": "jsonpath_ng/bin/jsonpath.py", + "size": 2057, + "perm": 420 + }, + { + "name": "jsonpath_ng/exceptions.py", + "size": 146, + "perm": 420 + }, + { + "name": "jsonpath_ng/ext", + "size": 0, + "perm": 2147484141 + }, + { + "name": "jsonpath_ng/ext/__init__.py", + "size": 605, + "perm": 420 + }, + { + "name": "jsonpath_ng/ext/arithmetic.py", + "size": 2381, + "perm": 420 + }, + { + "name": "jsonpath_ng/ext/filter.py", + "size": 4312, + "perm": 420 + }, + { + "name": "jsonpath_ng/ext/iterable.py", + "size": 3680, + "perm": 420 + }, + { + "name": "jsonpath_ng/ext/parser.py", + "size": 5351, + "perm": 420 + }, + { + "name": "jsonpath_ng/ext/string.py", + "size": 3261, + "perm": 420 + }, + { + "name": "jsonpath_ng/jsonpath.py", + "size": 26402, + "perm": 420 + }, + { + "name": "jsonpath_ng/lexer.py", + "size": 5231, + "perm": 420 + }, + { + "name": "jsonpath_ng/parser.py", + "size": 5883, + "perm": 420 + }, + { + "name": "markupsafe", + "size": 0, + "perm": 2147484141 + }, + { + "name": "markupsafe/__init__.py", + "size": 10958, + "perm": 420 + }, + { + "name": "markupsafe/_native.py", + "size": 1713, + "perm": 420 + }, + { + "name": "markupsafe/_speedups.c", + "size": 7083, + "perm": 420 + }, + { + "name": "markupsafe/_speedups.cpython-311-darwin.so", + "size": 35272, + "perm": 493, + "compressed": true + }, + { + "name": "markupsafe/_speedups.pyi", + "size": 229, + "perm": 420 + }, + { + "name": "markupsafe/py.typed", + "size": 0, + "perm": 420 + }, + { + "name": "ply", + "size": 0, + "perm": 2147484141 + }, + { + "name": "ply-3.11.dist-info", + "size": 0, + "perm": 2147484141 + }, + { + "name": "ply-3.11.dist-info/DESCRIPTION.rst", + "size": 519, + "perm": 420 + }, + { + "name": "ply-3.11.dist-info/INSTALLER", + "size": 4, + "perm": 420 + }, + { + "name": "ply-3.11.dist-info/METADATA", + "size": 844, + "perm": 420 + }, + { + "name": "ply-3.11.dist-info/RECORD", + "size": 1211, + "perm": 420 + }, + { + "name": "ply-3.11.dist-info/WHEEL", + "size": 110, + "perm": 420 + }, + { + "name": "ply-3.11.dist-info/metadata.json", + "size": 515, + "perm": 420 + }, + { + "name": "ply-3.11.dist-info/top_level.txt", + "size": 4, + "perm": 420 + }, + { + "name": "ply/__init__.py", + "size": 103, + "perm": 420 + }, + { + "name": "ply/cpp.py", + "size": 33639, + "perm": 420 + }, + { + "name": "ply/ctokens.py", + "size": 3155, + "perm": 420 + }, + { + "name": "ply/lex.py", + "size": 42905, + "perm": 420 + }, + { + "name": "ply/yacc.py", + "size": 137736, + "perm": 420 + }, + { + "name": "ply/ygen.py", + "size": 2246, + "perm": 420 + }, + { + "name": "python_slugify-8.0.4.dist-info", + "size": 0, + "perm": 2147484141 + }, + { + "name": "python_slugify-8.0.4.dist-info/INSTALLER", + "size": 4, + "perm": 420 + }, + { + "name": "python_slugify-8.0.4.dist-info/LICENSE", + "size": 1103, + "perm": 420 + }, + { + "name": "python_slugify-8.0.4.dist-info/METADATA", + "size": 8469, + "perm": 420 + }, + { + "name": "python_slugify-8.0.4.dist-info/RECORD", + "size": 1489, + "perm": 420 + }, + { + "name": "python_slugify-8.0.4.dist-info/REQUESTED", + "size": 0, + "perm": 420 + }, + { + "name": "python_slugify-8.0.4.dist-info/WHEEL", + "size": 110, + "perm": 420 + }, + { + "name": "python_slugify-8.0.4.dist-info/entry_points.txt", + "size": 50, + "perm": 420 + }, + { + "name": "python_slugify-8.0.4.dist-info/top_level.txt", + "size": 8, + "perm": 420 + }, + { + "name": "slugify", + "size": 0, + "perm": 2147484141 + }, + { + "name": "slugify/__init__.py", + "size": 346, + "perm": 420 + }, + { + "name": "slugify/__main__.py", + "size": 3961, + "perm": 420 + }, + { + "name": "slugify/__version__.py", + "size": 325, + "perm": 420 + }, + { + "name": "slugify/py.typed", + "size": 0, + "perm": 420 + }, + { + "name": "slugify/slugify.py", + "size": 6180, + "perm": 420 + }, + { + "name": "slugify/special.py", + "size": 1222, + "perm": 420 + }, + { + "name": "text_unidecode", + "size": 0, + "perm": 2147484141 + }, + { + "name": "text_unidecode-1.3.dist-info", + "size": 0, + "perm": 2147484141 + }, + { + "name": "text_unidecode-1.3.dist-info/DESCRIPTION.rst", + "size": 1199, + "perm": 420 + }, + { + "name": "text_unidecode-1.3.dist-info/INSTALLER", + "size": 4, + "perm": 420 + }, + { + "name": "text_unidecode-1.3.dist-info/LICENSE.txt", + "size": 6535, + "perm": 420 + }, + { + "name": "text_unidecode-1.3.dist-info/METADATA", + "size": 2422, + "perm": 420 + }, + { + "name": "text_unidecode-1.3.dist-info/RECORD", + "size": 937, + "perm": 420 + }, + { + "name": "text_unidecode-1.3.dist-info/WHEEL", + "size": 110, + "perm": 420 + }, + { + "name": "text_unidecode-1.3.dist-info/metadata.json", + "size": 1299, + "perm": 420 + }, + { + "name": "text_unidecode-1.3.dist-info/top_level.txt", + "size": 15, + "perm": 420 + }, + { + "name": "text_unidecode/__init__.py", + "size": 484, + "perm": 420 + }, + { + "name": "text_unidecode/data.bin", + "size": 311077, + "perm": 420, + "compressed": true + }, + { + "name": "yaml", + "size": 0, + "perm": 2147484141 + }, + { + "name": "yaml/__init__.py", + "size": 12311, + "perm": 420 + }, + { + "name": "yaml/_yaml.cpython-311-darwin.so", + "size": 429464, + "perm": 493, + "compressed": true + }, + { + "name": "yaml/composer.py", + "size": 4883, + "perm": 420 + }, + { + "name": "yaml/constructor.py", + "size": 28639, + "perm": 420 + }, + { + "name": "yaml/cyaml.py", + "size": 3851, + "perm": 420 + }, + { + "name": "yaml/dumper.py", + "size": 2837, + "perm": 420 + }, + { + "name": "yaml/emitter.py", + "size": 43006, + "perm": 420 + }, + { + "name": "yaml/error.py", + "size": 2533, + "perm": 420 + }, + { + "name": "yaml/events.py", + "size": 2445, + "perm": 420 + }, + { + "name": "yaml/loader.py", + "size": 2061, + "perm": 420 + }, + { + "name": "yaml/nodes.py", + "size": 1440, + "perm": 420 + }, + { + "name": "yaml/parser.py", + "size": 25495, + "perm": 420 + }, + { + "name": "yaml/reader.py", + "size": 6794, + "perm": 420 + }, + { + "name": "yaml/representer.py", + "size": 14190, + "perm": 420 + }, + { + "name": "yaml/resolver.py", + "size": 9004, + "perm": 420 + }, + { + "name": "yaml/scanner.py", + "size": 51279, + "perm": 420 + }, + { + "name": "yaml/serializer.py", + "size": 4165, + "perm": 420 + }, + { + "name": "yaml/tokens.py", + "size": 2573, + "perm": 420 + } + ] +} \ No newline at end of file diff --git a/lib/go-jinja2/internal/data/darwin-amd64/jinja2-3.1.4.dist-info/INSTALLER b/lib/go-jinja2/internal/data/darwin-amd64/jinja2-3.1.4.dist-info/INSTALLER new file mode 100644 index 000000000..a1b589e38 --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/jinja2-3.1.4.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/lib/go-jinja2/internal/data/darwin-amd64/jinja2-3.1.4.dist-info/LICENSE.txt b/lib/go-jinja2/internal/data/darwin-amd64/jinja2-3.1.4.dist-info/LICENSE.txt new file mode 100644 index 000000000..c37cae49e --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/jinja2-3.1.4.dist-info/LICENSE.txt @@ -0,0 +1,28 @@ +Copyright 2007 Pallets + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/lib/go-jinja2/internal/data/darwin-amd64/jinja2-3.1.4.dist-info/METADATA b/lib/go-jinja2/internal/data/darwin-amd64/jinja2-3.1.4.dist-info/METADATA new file mode 100644 index 000000000..265cc32e1 --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/jinja2-3.1.4.dist-info/METADATA @@ -0,0 +1,76 @@ +Metadata-Version: 2.1 +Name: Jinja2 +Version: 3.1.4 +Summary: A very fast and expressive template engine. +Maintainer-email: Pallets +Requires-Python: >=3.7 +Description-Content-Type: text/markdown +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Web Environment +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content +Classifier: Topic :: Text Processing :: Markup :: HTML +Classifier: Typing :: Typed +Requires-Dist: MarkupSafe>=2.0 +Requires-Dist: Babel>=2.7 ; extra == "i18n" +Project-URL: Changes, https://jinja.palletsprojects.com/changes/ +Project-URL: Chat, https://discord.gg/pallets +Project-URL: Documentation, https://jinja.palletsprojects.com/ +Project-URL: Donate, https://palletsprojects.com/donate +Project-URL: Source, https://github.com/pallets/jinja/ +Provides-Extra: i18n + +# Jinja + +Jinja is a fast, expressive, extensible templating engine. Special +placeholders in the template allow writing code similar to Python +syntax. Then the template is passed data to render the final document. + +It includes: + +- Template inheritance and inclusion. +- Define and import macros within templates. +- HTML templates can use autoescaping to prevent XSS from untrusted + user input. +- A sandboxed environment can safely render untrusted templates. +- AsyncIO support for generating templates and calling async + functions. +- I18N support with Babel. +- Templates are compiled to optimized Python code just-in-time and + cached, or can be compiled ahead-of-time. +- Exceptions point to the correct line in templates to make debugging + easier. +- Extensible filters, tests, functions, and even syntax. + +Jinja's philosophy is that while application logic belongs in Python if +possible, it shouldn't make the template designer's job difficult by +restricting functionality too much. + + +## In A Nutshell + +.. code-block:: jinja + + {% extends "base.html" %} + {% block title %}Members{% endblock %} + {% block content %} + + {% endblock %} + + +## Donate + +The Pallets organization develops and supports Jinja and other popular +packages. In order to grow the community of contributors and users, and +allow the maintainers to devote more time to the projects, [please +donate today][]. + +[please donate today]: https://palletsprojects.com/donate + diff --git a/lib/go-jinja2/internal/data/darwin-amd64/jinja2-3.1.4.dist-info/RECORD b/lib/go-jinja2/internal/data/darwin-amd64/jinja2-3.1.4.dist-info/RECORD new file mode 100644 index 000000000..e9bdca35c --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/jinja2-3.1.4.dist-info/RECORD @@ -0,0 +1,58 @@ +jinja2-3.1.4.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +jinja2-3.1.4.dist-info/LICENSE.txt,sha256=O0nc7kEF6ze6wQ-vG-JgQI_oXSUrjp3y4JefweCUQ3s,1475 +jinja2-3.1.4.dist-info/METADATA,sha256=R_brzpPQVBvpGcsm-WbrtgotO7suQ1D0F-qkhTzeEfY,2640 +jinja2-3.1.4.dist-info/RECORD,, +jinja2-3.1.4.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +jinja2-3.1.4.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81 +jinja2-3.1.4.dist-info/entry_points.txt,sha256=OL85gYU1eD8cuPlikifFngXpeBjaxl6rIJ8KkC_3r-I,58 +jinja2/__init__.py,sha256=wIl45IM20KGw-kfr7jJhaBxxX5g4-kihlBYjxopX7Pw,1928 +jinja2/__pycache__/__init__.cpython-311.pyc,, +jinja2/__pycache__/_identifier.cpython-311.pyc,, +jinja2/__pycache__/async_utils.cpython-311.pyc,, +jinja2/__pycache__/bccache.cpython-311.pyc,, +jinja2/__pycache__/compiler.cpython-311.pyc,, +jinja2/__pycache__/constants.cpython-311.pyc,, +jinja2/__pycache__/debug.cpython-311.pyc,, +jinja2/__pycache__/defaults.cpython-311.pyc,, +jinja2/__pycache__/environment.cpython-311.pyc,, +jinja2/__pycache__/exceptions.cpython-311.pyc,, +jinja2/__pycache__/ext.cpython-311.pyc,, +jinja2/__pycache__/filters.cpython-311.pyc,, +jinja2/__pycache__/idtracking.cpython-311.pyc,, +jinja2/__pycache__/lexer.cpython-311.pyc,, +jinja2/__pycache__/loaders.cpython-311.pyc,, +jinja2/__pycache__/meta.cpython-311.pyc,, +jinja2/__pycache__/nativetypes.cpython-311.pyc,, +jinja2/__pycache__/nodes.cpython-311.pyc,, +jinja2/__pycache__/optimizer.cpython-311.pyc,, +jinja2/__pycache__/parser.cpython-311.pyc,, +jinja2/__pycache__/runtime.cpython-311.pyc,, +jinja2/__pycache__/sandbox.cpython-311.pyc,, +jinja2/__pycache__/tests.cpython-311.pyc,, +jinja2/__pycache__/utils.cpython-311.pyc,, +jinja2/__pycache__/visitor.cpython-311.pyc,, +jinja2/_identifier.py,sha256=_zYctNKzRqlk_murTNlzrju1FFJL7Va_Ijqqd7ii2lU,1958 +jinja2/async_utils.py,sha256=JXKWCAXmTx0iZB4-hAsF50vgjxw_RJTjiLOlGGTBso0,2477 +jinja2/bccache.py,sha256=gh0qs9rulnXo0PhX5jTJy2UHzI8wFnQ63o_vw7nhzRg,14061 +jinja2/compiler.py,sha256=dpV-n6_iQUP4uSwlXwGUavJmwjvXdyxKzJ-AonFjPBk,72271 +jinja2/constants.py,sha256=GMoFydBF_kdpaRKPoM5cl5MviquVRLVyZtfp5-16jg0,1433 +jinja2/debug.py,sha256=iWJ432RadxJNnaMOPrjIDInz50UEgni3_HKuFXi2vuQ,6299 +jinja2/defaults.py,sha256=boBcSw78h-lp20YbaXSJsqkAI2uN_mD_TtCydpeq5wU,1267 +jinja2/environment.py,sha256=xhFkmxO0CESA76Ki5tz4XWq9yzGu-t0p93JCCVBVNps,61538 +jinja2/exceptions.py,sha256=ioHeHrWwCWNaXX1inHmHVblvc4haO7AXsjCp3GfWvx0,5071 +jinja2/ext.py,sha256=igsBH7c6C0byHaOtMbE-ugpt4GjLGgR-ywskyXtKgq8,31877 +jinja2/filters.py,sha256=bKeqjFjjz88TkHVLSyyMIEB75CzAN6b3Airgx0phJDg,54611 +jinja2/idtracking.py,sha256=GfNmadir4oDALVxzn3DL9YInhJDr69ebXeA2ygfuCGA,10704 +jinja2/lexer.py,sha256=xnWWXhPndHFsoqzpc5VTjheDE9JuKk9MUo9DZkrM8Os,29754 +jinja2/loaders.py,sha256=ru0GIWHo5KiHJi7_MoI_LvGDoBBvP6rd0hiC1ReaTwk,23167 +jinja2/meta.py,sha256=OTDPkaFvU2Hgvx-6akz7154F8BIWaRmvJcBFvwopHww,4397 +jinja2/nativetypes.py,sha256=7GIGALVJgdyL80oZJdQUaUfwSt5q2lSSZbXt0dNf_M4,4210 +jinja2/nodes.py,sha256=m1Duzcr6qhZI8JQ6VyJgUNinjAf5bQzijSmDnMsvUx8,34579 +jinja2/optimizer.py,sha256=rJnCRlQ7pZsEEmMhsQDgC_pKyDHxP5TPS6zVPGsgcu8,1651 +jinja2/parser.py,sha256=DV1iF1FR2Rsaj_5zl8rmx7j6Bj4S8iLHoYsvJ0bfEis,39890 +jinja2/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +jinja2/runtime.py,sha256=POXT3tKNKJRENx2CymwUsOOXH2JwGPjW702njB5__cQ,33435 +jinja2/sandbox.py,sha256=TJjBNS9qRJ2ZgBMWdAgRBpyDLOHea2kT-2mk4PrjYx0,14616 +jinja2/tests.py,sha256=VLsBhVFnWg-PxSBz1MhRnNWgP1ovXk3neO1FLQMeC9Q,5926 +jinja2/utils.py,sha256=nV7IpWLvRCMyHW1irBAK8CIPAnOFfkb2ukggDBjbBEY,23952 +jinja2/visitor.py,sha256=EcnL1PIwf_4RVCOMxsRNuR8AXHbS1qfAdMOE2ngKJz4,3557 diff --git a/lib/go-jinja2/internal/data/darwin-amd64/jinja2-3.1.4.dist-info/REQUESTED b/lib/go-jinja2/internal/data/darwin-amd64/jinja2-3.1.4.dist-info/REQUESTED new file mode 100644 index 000000000..e69de29bb diff --git a/lib/go-jinja2/internal/data/darwin-amd64/jinja2-3.1.4.dist-info/WHEEL b/lib/go-jinja2/internal/data/darwin-amd64/jinja2-3.1.4.dist-info/WHEEL new file mode 100644 index 000000000..3b5e64b5e --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/jinja2-3.1.4.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: flit 3.9.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/lib/go-jinja2/internal/data/darwin-amd64/jinja2-3.1.4.dist-info/entry_points.txt b/lib/go-jinja2/internal/data/darwin-amd64/jinja2-3.1.4.dist-info/entry_points.txt new file mode 100644 index 000000000..abc3eae3b --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/jinja2-3.1.4.dist-info/entry_points.txt @@ -0,0 +1,3 @@ +[babel.extractors] +jinja2=jinja2.ext:babel_extract[i18n] + diff --git a/lib/go-jinja2/internal/data/darwin-amd64/jinja2/__init__.py b/lib/go-jinja2/internal/data/darwin-amd64/jinja2/__init__.py new file mode 100644 index 000000000..2f0b5b286 --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/jinja2/__init__.py @@ -0,0 +1,38 @@ +"""Jinja is a template engine written in pure Python. It provides a +non-XML syntax that supports inline expressions and an optional +sandboxed environment. +""" + +from .bccache import BytecodeCache as BytecodeCache +from .bccache import FileSystemBytecodeCache as FileSystemBytecodeCache +from .bccache import MemcachedBytecodeCache as MemcachedBytecodeCache +from .environment import Environment as Environment +from .environment import Template as Template +from .exceptions import TemplateAssertionError as TemplateAssertionError +from .exceptions import TemplateError as TemplateError +from .exceptions import TemplateNotFound as TemplateNotFound +from .exceptions import TemplateRuntimeError as TemplateRuntimeError +from .exceptions import TemplatesNotFound as TemplatesNotFound +from .exceptions import TemplateSyntaxError as TemplateSyntaxError +from .exceptions import UndefinedError as UndefinedError +from .loaders import BaseLoader as BaseLoader +from .loaders import ChoiceLoader as ChoiceLoader +from .loaders import DictLoader as DictLoader +from .loaders import FileSystemLoader as FileSystemLoader +from .loaders import FunctionLoader as FunctionLoader +from .loaders import ModuleLoader as ModuleLoader +from .loaders import PackageLoader as PackageLoader +from .loaders import PrefixLoader as PrefixLoader +from .runtime import ChainableUndefined as ChainableUndefined +from .runtime import DebugUndefined as DebugUndefined +from .runtime import make_logging_undefined as make_logging_undefined +from .runtime import StrictUndefined as StrictUndefined +from .runtime import Undefined as Undefined +from .utils import clear_caches as clear_caches +from .utils import is_undefined as is_undefined +from .utils import pass_context as pass_context +from .utils import pass_environment as pass_environment +from .utils import pass_eval_context as pass_eval_context +from .utils import select_autoescape as select_autoescape + +__version__ = "3.1.4" diff --git a/lib/go-jinja2/internal/data/darwin-amd64/jinja2/_identifier.py b/lib/go-jinja2/internal/data/darwin-amd64/jinja2/_identifier.py new file mode 100644 index 000000000..928c1503c --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/jinja2/_identifier.py @@ -0,0 +1,6 @@ +import re + +# generated by scripts/generate_identifier_pattern.py +pattern = re.compile( + r"[\w·̀-ͯ·҃-֑҇-ׇֽֿׁׂׅׄؐ-ًؚ-ٰٟۖ-ۜ۟-۪ۤۧۨ-ܑۭܰ-݊ަ-ް߫-߽߳ࠖ-࠙ࠛ-ࠣࠥ-ࠧࠩ-࡙࠭-࡛࣓-ࣣ࣡-ःऺ-़ा-ॏ॑-ॗॢॣঁ-ঃ়া-ৄেৈো-্ৗৢৣ৾ਁ-ਃ਼ਾ-ੂੇੈੋ-੍ੑੰੱੵઁ-ઃ઼ા-ૅે-ૉો-્ૢૣૺ-૿ଁ-ଃ଼ା-ୄେୈୋ-୍ୖୗୢୣஂா-ூெ-ைொ-்ௗఀ-ఄా-ౄె-ైొ-్ౕౖౢౣಁ-ಃ಼ಾ-ೄೆ-ೈೊ-್ೕೖೢೣഀ-ഃ഻഼ാ-ൄെ-ൈൊ-്ൗൢൣංඃ්ා-ුූෘ-ෟෲෳัิ-ฺ็-๎ັິ-ູົຼ່-ໍ༹༘༙༵༷༾༿ཱ-྄྆྇ྍ-ྗྙ-ྼ࿆ါ-ှၖ-ၙၞ-ၠၢ-ၤၧ-ၭၱ-ၴႂ-ႍႏႚ-ႝ፝-፟ᜒ-᜔ᜲ-᜴ᝒᝓᝲᝳ឴-៓៝᠋-᠍ᢅᢆᢩᤠ-ᤫᤰ-᤻ᨗ-ᨛᩕ-ᩞ᩠-᩿᩼᪰-᪽ᬀ-ᬄ᬴-᭄᭫-᭳ᮀ-ᮂᮡ-ᮭ᯦-᯳ᰤ-᰷᳐-᳔᳒-᳨᳭ᳲ-᳴᳷-᳹᷀-᷹᷻-᷿‿⁀⁔⃐-⃥⃜⃡-⃰℘℮⳯-⵿⳱ⷠ-〪ⷿ-゙゚〯꙯ꙴ-꙽ꚞꚟ꛰꛱ꠂ꠆ꠋꠣ-ꠧꢀꢁꢴ-ꣅ꣠-꣱ꣿꤦ-꤭ꥇ-꥓ꦀ-ꦃ꦳-꧀ꧥꨩ-ꨶꩃꩌꩍꩻ-ꩽꪰꪲ-ꪴꪷꪸꪾ꪿꫁ꫫ-ꫯꫵ꫶ꯣ-ꯪ꯬꯭ﬞ︀-️︠-︯︳︴﹍-﹏_𐇽𐋠𐍶-𐍺𐨁-𐨃𐨅𐨆𐨌-𐨏𐨸-𐨿𐨺𐫦𐫥𐴤-𐽆𐴧-𐽐𑀀-𑀂𑀸-𑁆𑁿-𑂂𑂰-𑂺𑄀-𑄂𑄧-𑄴𑅅𑅆𑅳𑆀-𑆂𑆳-𑇀𑇉-𑇌𑈬-𑈷𑈾𑋟-𑋪𑌀-𑌃𑌻𑌼𑌾-𑍄𑍇𑍈𑍋-𑍍𑍗𑍢𑍣𑍦-𑍬𑍰-𑍴𑐵-𑑆𑑞𑒰-𑓃𑖯-𑖵𑖸-𑗀𑗜𑗝𑘰-𑙀𑚫-𑚷𑜝-𑜫𑠬-𑠺𑨁-𑨊𑨳-𑨹𑨻-𑨾𑩇𑩑-𑩛𑪊-𑪙𑰯-𑰶𑰸-𑰿𑲒-𑲧𑲩-𑲶𑴱-𑴶𑴺𑴼𑴽𑴿-𑵅𑵇𑶊-𑶎𑶐𑶑𑶓-𑶗𑻳-𑻶𖫰-𖫴𖬰-𖬶𖽑-𖽾𖾏-𖾒𛲝𛲞𝅥-𝅩𝅭-𝅲𝅻-𝆂𝆅-𝆋𝆪-𝆭𝉂-𝉄𝨀-𝨶𝨻-𝩬𝩵𝪄𝪛-𝪟𝪡-𝪯𞀀-𞀆𞀈-𞀘𞀛-𞀡𞀣𞀤𞀦-𞣐𞀪-𞣖𞥄-𞥊󠄀-󠇯]+" # noqa: B950 +) diff --git a/lib/go-jinja2/internal/data/darwin-amd64/jinja2/async_utils.py b/lib/go-jinja2/internal/data/darwin-amd64/jinja2/async_utils.py new file mode 100644 index 000000000..e65219e49 --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/jinja2/async_utils.py @@ -0,0 +1,84 @@ +import inspect +import typing as t +from functools import WRAPPER_ASSIGNMENTS +from functools import wraps + +from .utils import _PassArg +from .utils import pass_eval_context + +V = t.TypeVar("V") + + +def async_variant(normal_func): # type: ignore + def decorator(async_func): # type: ignore + pass_arg = _PassArg.from_obj(normal_func) + need_eval_context = pass_arg is None + + if pass_arg is _PassArg.environment: + + def is_async(args: t.Any) -> bool: + return t.cast(bool, args[0].is_async) + + else: + + def is_async(args: t.Any) -> bool: + return t.cast(bool, args[0].environment.is_async) + + # Take the doc and annotations from the sync function, but the + # name from the async function. Pallets-Sphinx-Themes + # build_function_directive expects __wrapped__ to point to the + # sync function. + async_func_attrs = ("__module__", "__name__", "__qualname__") + normal_func_attrs = tuple(set(WRAPPER_ASSIGNMENTS).difference(async_func_attrs)) + + @wraps(normal_func, assigned=normal_func_attrs) + @wraps(async_func, assigned=async_func_attrs, updated=()) + def wrapper(*args, **kwargs): # type: ignore + b = is_async(args) + + if need_eval_context: + args = args[1:] + + if b: + return async_func(*args, **kwargs) + + return normal_func(*args, **kwargs) + + if need_eval_context: + wrapper = pass_eval_context(wrapper) + + wrapper.jinja_async_variant = True # type: ignore[attr-defined] + return wrapper + + return decorator + + +_common_primitives = {int, float, bool, str, list, dict, tuple, type(None)} + + +async def auto_await(value: t.Union[t.Awaitable["V"], "V"]) -> "V": + # Avoid a costly call to isawaitable + if type(value) in _common_primitives: + return t.cast("V", value) + + if inspect.isawaitable(value): + return await t.cast("t.Awaitable[V]", value) + + return t.cast("V", value) + + +async def auto_aiter( + iterable: "t.Union[t.AsyncIterable[V], t.Iterable[V]]", +) -> "t.AsyncIterator[V]": + if hasattr(iterable, "__aiter__"): + async for item in t.cast("t.AsyncIterable[V]", iterable): + yield item + else: + for item in iterable: + yield item + + +async def auto_to_list( + value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]", +) -> t.List["V"]: + return [x async for x in auto_aiter(value)] diff --git a/lib/go-jinja2/internal/data/darwin-amd64/jinja2/bccache.py b/lib/go-jinja2/internal/data/darwin-amd64/jinja2/bccache.py new file mode 100644 index 000000000..ada8b099f --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/jinja2/bccache.py @@ -0,0 +1,408 @@ +"""The optional bytecode cache system. This is useful if you have very +complex template situations and the compilation of all those templates +slows down your application too much. + +Situations where this is useful are often forking web applications that +are initialized on the first request. +""" + +import errno +import fnmatch +import marshal +import os +import pickle +import stat +import sys +import tempfile +import typing as t +from hashlib import sha1 +from io import BytesIO +from types import CodeType + +if t.TYPE_CHECKING: + import typing_extensions as te + + from .environment import Environment + + class _MemcachedClient(te.Protocol): + def get(self, key: str) -> bytes: ... + + def set( + self, key: str, value: bytes, timeout: t.Optional[int] = None + ) -> None: ... + + +bc_version = 5 +# Magic bytes to identify Jinja bytecode cache files. Contains the +# Python major and minor version to avoid loading incompatible bytecode +# if a project upgrades its Python version. +bc_magic = ( + b"j2" + + pickle.dumps(bc_version, 2) + + pickle.dumps((sys.version_info[0] << 24) | sys.version_info[1], 2) +) + + +class Bucket: + """Buckets are used to store the bytecode for one template. It's created + and initialized by the bytecode cache and passed to the loading functions. + + The buckets get an internal checksum from the cache assigned and use this + to automatically reject outdated cache material. Individual bytecode + cache subclasses don't have to care about cache invalidation. + """ + + def __init__(self, environment: "Environment", key: str, checksum: str) -> None: + self.environment = environment + self.key = key + self.checksum = checksum + self.reset() + + def reset(self) -> None: + """Resets the bucket (unloads the bytecode).""" + self.code: t.Optional[CodeType] = None + + def load_bytecode(self, f: t.BinaryIO) -> None: + """Loads bytecode from a file or file like object.""" + # make sure the magic header is correct + magic = f.read(len(bc_magic)) + if magic != bc_magic: + self.reset() + return + # the source code of the file changed, we need to reload + checksum = pickle.load(f) + if self.checksum != checksum: + self.reset() + return + # if marshal_load fails then we need to reload + try: + self.code = marshal.load(f) + except (EOFError, ValueError, TypeError): + self.reset() + return + + def write_bytecode(self, f: t.IO[bytes]) -> None: + """Dump the bytecode into the file or file like object passed.""" + if self.code is None: + raise TypeError("can't write empty bucket") + f.write(bc_magic) + pickle.dump(self.checksum, f, 2) + marshal.dump(self.code, f) + + def bytecode_from_string(self, string: bytes) -> None: + """Load bytecode from bytes.""" + self.load_bytecode(BytesIO(string)) + + def bytecode_to_string(self) -> bytes: + """Return the bytecode as bytes.""" + out = BytesIO() + self.write_bytecode(out) + return out.getvalue() + + +class BytecodeCache: + """To implement your own bytecode cache you have to subclass this class + and override :meth:`load_bytecode` and :meth:`dump_bytecode`. Both of + these methods are passed a :class:`~jinja2.bccache.Bucket`. + + A very basic bytecode cache that saves the bytecode on the file system:: + + from os import path + + class MyCache(BytecodeCache): + + def __init__(self, directory): + self.directory = directory + + def load_bytecode(self, bucket): + filename = path.join(self.directory, bucket.key) + if path.exists(filename): + with open(filename, 'rb') as f: + bucket.load_bytecode(f) + + def dump_bytecode(self, bucket): + filename = path.join(self.directory, bucket.key) + with open(filename, 'wb') as f: + bucket.write_bytecode(f) + + A more advanced version of a filesystem based bytecode cache is part of + Jinja. + """ + + def load_bytecode(self, bucket: Bucket) -> None: + """Subclasses have to override this method to load bytecode into a + bucket. If they are not able to find code in the cache for the + bucket, it must not do anything. + """ + raise NotImplementedError() + + def dump_bytecode(self, bucket: Bucket) -> None: + """Subclasses have to override this method to write the bytecode + from a bucket back to the cache. If it unable to do so it must not + fail silently but raise an exception. + """ + raise NotImplementedError() + + def clear(self) -> None: + """Clears the cache. This method is not used by Jinja but should be + implemented to allow applications to clear the bytecode cache used + by a particular environment. + """ + + def get_cache_key( + self, name: str, filename: t.Optional[t.Union[str]] = None + ) -> str: + """Returns the unique hash key for this template name.""" + hash = sha1(name.encode("utf-8")) + + if filename is not None: + hash.update(f"|{filename}".encode()) + + return hash.hexdigest() + + def get_source_checksum(self, source: str) -> str: + """Returns a checksum for the source.""" + return sha1(source.encode("utf-8")).hexdigest() + + def get_bucket( + self, + environment: "Environment", + name: str, + filename: t.Optional[str], + source: str, + ) -> Bucket: + """Return a cache bucket for the given template. All arguments are + mandatory but filename may be `None`. + """ + key = self.get_cache_key(name, filename) + checksum = self.get_source_checksum(source) + bucket = Bucket(environment, key, checksum) + self.load_bytecode(bucket) + return bucket + + def set_bucket(self, bucket: Bucket) -> None: + """Put the bucket into the cache.""" + self.dump_bytecode(bucket) + + +class FileSystemBytecodeCache(BytecodeCache): + """A bytecode cache that stores bytecode on the filesystem. It accepts + two arguments: The directory where the cache items are stored and a + pattern string that is used to build the filename. + + If no directory is specified a default cache directory is selected. On + Windows the user's temp directory is used, on UNIX systems a directory + is created for the user in the system temp directory. + + The pattern can be used to have multiple separate caches operate on the + same directory. The default pattern is ``'__jinja2_%s.cache'``. ``%s`` + is replaced with the cache key. + + >>> bcc = FileSystemBytecodeCache('/tmp/jinja_cache', '%s.cache') + + This bytecode cache supports clearing of the cache using the clear method. + """ + + def __init__( + self, directory: t.Optional[str] = None, pattern: str = "__jinja2_%s.cache" + ) -> None: + if directory is None: + directory = self._get_default_cache_dir() + self.directory = directory + self.pattern = pattern + + def _get_default_cache_dir(self) -> str: + def _unsafe_dir() -> "te.NoReturn": + raise RuntimeError( + "Cannot determine safe temp directory. You " + "need to explicitly provide one." + ) + + tmpdir = tempfile.gettempdir() + + # On windows the temporary directory is used specific unless + # explicitly forced otherwise. We can just use that. + if os.name == "nt": + return tmpdir + if not hasattr(os, "getuid"): + _unsafe_dir() + + dirname = f"_jinja2-cache-{os.getuid()}" + actual_dir = os.path.join(tmpdir, dirname) + + try: + os.mkdir(actual_dir, stat.S_IRWXU) + except OSError as e: + if e.errno != errno.EEXIST: + raise + try: + os.chmod(actual_dir, stat.S_IRWXU) + actual_dir_stat = os.lstat(actual_dir) + if ( + actual_dir_stat.st_uid != os.getuid() + or not stat.S_ISDIR(actual_dir_stat.st_mode) + or stat.S_IMODE(actual_dir_stat.st_mode) != stat.S_IRWXU + ): + _unsafe_dir() + except OSError as e: + if e.errno != errno.EEXIST: + raise + + actual_dir_stat = os.lstat(actual_dir) + if ( + actual_dir_stat.st_uid != os.getuid() + or not stat.S_ISDIR(actual_dir_stat.st_mode) + or stat.S_IMODE(actual_dir_stat.st_mode) != stat.S_IRWXU + ): + _unsafe_dir() + + return actual_dir + + def _get_cache_filename(self, bucket: Bucket) -> str: + return os.path.join(self.directory, self.pattern % (bucket.key,)) + + def load_bytecode(self, bucket: Bucket) -> None: + filename = self._get_cache_filename(bucket) + + # Don't test for existence before opening the file, since the + # file could disappear after the test before the open. + try: + f = open(filename, "rb") + except (FileNotFoundError, IsADirectoryError, PermissionError): + # PermissionError can occur on Windows when an operation is + # in progress, such as calling clear(). + return + + with f: + bucket.load_bytecode(f) + + def dump_bytecode(self, bucket: Bucket) -> None: + # Write to a temporary file, then rename to the real name after + # writing. This avoids another process reading the file before + # it is fully written. + name = self._get_cache_filename(bucket) + f = tempfile.NamedTemporaryFile( + mode="wb", + dir=os.path.dirname(name), + prefix=os.path.basename(name), + suffix=".tmp", + delete=False, + ) + + def remove_silent() -> None: + try: + os.remove(f.name) + except OSError: + # Another process may have called clear(). On Windows, + # another program may be holding the file open. + pass + + try: + with f: + bucket.write_bytecode(f) + except BaseException: + remove_silent() + raise + + try: + os.replace(f.name, name) + except OSError: + # Another process may have called clear(). On Windows, + # another program may be holding the file open. + remove_silent() + except BaseException: + remove_silent() + raise + + def clear(self) -> None: + # imported lazily here because google app-engine doesn't support + # write access on the file system and the function does not exist + # normally. + from os import remove + + files = fnmatch.filter(os.listdir(self.directory), self.pattern % ("*",)) + for filename in files: + try: + remove(os.path.join(self.directory, filename)) + except OSError: + pass + + +class MemcachedBytecodeCache(BytecodeCache): + """This class implements a bytecode cache that uses a memcache cache for + storing the information. It does not enforce a specific memcache library + (tummy's memcache or cmemcache) but will accept any class that provides + the minimal interface required. + + Libraries compatible with this class: + + - `cachelib `_ + - `python-memcached `_ + + (Unfortunately the django cache interface is not compatible because it + does not support storing binary data, only text. You can however pass + the underlying cache client to the bytecode cache which is available + as `django.core.cache.cache._client`.) + + The minimal interface for the client passed to the constructor is this: + + .. class:: MinimalClientInterface + + .. method:: set(key, value[, timeout]) + + Stores the bytecode in the cache. `value` is a string and + `timeout` the timeout of the key. If timeout is not provided + a default timeout or no timeout should be assumed, if it's + provided it's an integer with the number of seconds the cache + item should exist. + + .. method:: get(key) + + Returns the value for the cache key. If the item does not + exist in the cache the return value must be `None`. + + The other arguments to the constructor are the prefix for all keys that + is added before the actual cache key and the timeout for the bytecode in + the cache system. We recommend a high (or no) timeout. + + This bytecode cache does not support clearing of used items in the cache. + The clear method is a no-operation function. + + .. versionadded:: 2.7 + Added support for ignoring memcache errors through the + `ignore_memcache_errors` parameter. + """ + + def __init__( + self, + client: "_MemcachedClient", + prefix: str = "jinja2/bytecode/", + timeout: t.Optional[int] = None, + ignore_memcache_errors: bool = True, + ): + self.client = client + self.prefix = prefix + self.timeout = timeout + self.ignore_memcache_errors = ignore_memcache_errors + + def load_bytecode(self, bucket: Bucket) -> None: + try: + code = self.client.get(self.prefix + bucket.key) + except Exception: + if not self.ignore_memcache_errors: + raise + else: + bucket.bytecode_from_string(code) + + def dump_bytecode(self, bucket: Bucket) -> None: + key = self.prefix + bucket.key + value = bucket.bytecode_to_string() + + try: + if self.timeout is not None: + self.client.set(key, value, self.timeout) + else: + self.client.set(key, value) + except Exception: + if not self.ignore_memcache_errors: + raise diff --git a/lib/go-jinja2/internal/data/darwin-amd64/jinja2/compiler.py b/lib/go-jinja2/internal/data/darwin-amd64/jinja2/compiler.py new file mode 100644 index 000000000..274071750 --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/jinja2/compiler.py @@ -0,0 +1,1960 @@ +"""Compiles nodes from the parser into Python code.""" + +import typing as t +from contextlib import contextmanager +from functools import update_wrapper +from io import StringIO +from itertools import chain +from keyword import iskeyword as is_python_keyword + +from markupsafe import escape +from markupsafe import Markup + +from . import nodes +from .exceptions import TemplateAssertionError +from .idtracking import Symbols +from .idtracking import VAR_LOAD_ALIAS +from .idtracking import VAR_LOAD_PARAMETER +from .idtracking import VAR_LOAD_RESOLVE +from .idtracking import VAR_LOAD_UNDEFINED +from .nodes import EvalContext +from .optimizer import Optimizer +from .utils import _PassArg +from .utils import concat +from .visitor import NodeVisitor + +if t.TYPE_CHECKING: + import typing_extensions as te + + from .environment import Environment + +F = t.TypeVar("F", bound=t.Callable[..., t.Any]) + +operators = { + "eq": "==", + "ne": "!=", + "gt": ">", + "gteq": ">=", + "lt": "<", + "lteq": "<=", + "in": "in", + "notin": "not in", +} + + +def optimizeconst(f: F) -> F: + def new_func( + self: "CodeGenerator", node: nodes.Expr, frame: "Frame", **kwargs: t.Any + ) -> t.Any: + # Only optimize if the frame is not volatile + if self.optimizer is not None and not frame.eval_ctx.volatile: + new_node = self.optimizer.visit(node, frame.eval_ctx) + + if new_node != node: + return self.visit(new_node, frame) + + return f(self, node, frame, **kwargs) + + return update_wrapper(t.cast(F, new_func), f) + + +def _make_binop(op: str) -> t.Callable[["CodeGenerator", nodes.BinExpr, "Frame"], None]: + @optimizeconst + def visitor(self: "CodeGenerator", node: nodes.BinExpr, frame: Frame) -> None: + if ( + self.environment.sandboxed and op in self.environment.intercepted_binops # type: ignore + ): + self.write(f"environment.call_binop(context, {op!r}, ") + self.visit(node.left, frame) + self.write(", ") + self.visit(node.right, frame) + else: + self.write("(") + self.visit(node.left, frame) + self.write(f" {op} ") + self.visit(node.right, frame) + + self.write(")") + + return visitor + + +def _make_unop( + op: str, +) -> t.Callable[["CodeGenerator", nodes.UnaryExpr, "Frame"], None]: + @optimizeconst + def visitor(self: "CodeGenerator", node: nodes.UnaryExpr, frame: Frame) -> None: + if ( + self.environment.sandboxed and op in self.environment.intercepted_unops # type: ignore + ): + self.write(f"environment.call_unop(context, {op!r}, ") + self.visit(node.node, frame) + else: + self.write("(" + op) + self.visit(node.node, frame) + + self.write(")") + + return visitor + + +def generate( + node: nodes.Template, + environment: "Environment", + name: t.Optional[str], + filename: t.Optional[str], + stream: t.Optional[t.TextIO] = None, + defer_init: bool = False, + optimized: bool = True, +) -> t.Optional[str]: + """Generate the python source for a node tree.""" + if not isinstance(node, nodes.Template): + raise TypeError("Can't compile non template nodes") + + generator = environment.code_generator_class( + environment, name, filename, stream, defer_init, optimized + ) + generator.visit(node) + + if stream is None: + return generator.stream.getvalue() # type: ignore + + return None + + +def has_safe_repr(value: t.Any) -> bool: + """Does the node have a safe representation?""" + if value is None or value is NotImplemented or value is Ellipsis: + return True + + if type(value) in {bool, int, float, complex, range, str, Markup}: + return True + + if type(value) in {tuple, list, set, frozenset}: + return all(has_safe_repr(v) for v in value) + + if type(value) is dict: # noqa E721 + return all(has_safe_repr(k) and has_safe_repr(v) for k, v in value.items()) + + return False + + +def find_undeclared( + nodes: t.Iterable[nodes.Node], names: t.Iterable[str] +) -> t.Set[str]: + """Check if the names passed are accessed undeclared. The return value + is a set of all the undeclared names from the sequence of names found. + """ + visitor = UndeclaredNameVisitor(names) + try: + for node in nodes: + visitor.visit(node) + except VisitorExit: + pass + return visitor.undeclared + + +class MacroRef: + def __init__(self, node: t.Union[nodes.Macro, nodes.CallBlock]) -> None: + self.node = node + self.accesses_caller = False + self.accesses_kwargs = False + self.accesses_varargs = False + + +class Frame: + """Holds compile time information for us.""" + + def __init__( + self, + eval_ctx: EvalContext, + parent: t.Optional["Frame"] = None, + level: t.Optional[int] = None, + ) -> None: + self.eval_ctx = eval_ctx + + # the parent of this frame + self.parent = parent + + if parent is None: + self.symbols = Symbols(level=level) + + # in some dynamic inheritance situations the compiler needs to add + # write tests around output statements. + self.require_output_check = False + + # inside some tags we are using a buffer rather than yield statements. + # this for example affects {% filter %} or {% macro %}. If a frame + # is buffered this variable points to the name of the list used as + # buffer. + self.buffer: t.Optional[str] = None + + # the name of the block we're in, otherwise None. + self.block: t.Optional[str] = None + + else: + self.symbols = Symbols(parent.symbols, level=level) + self.require_output_check = parent.require_output_check + self.buffer = parent.buffer + self.block = parent.block + + # a toplevel frame is the root + soft frames such as if conditions. + self.toplevel = False + + # the root frame is basically just the outermost frame, so no if + # conditions. This information is used to optimize inheritance + # situations. + self.rootlevel = False + + # variables set inside of loops and blocks should not affect outer frames, + # but they still needs to be kept track of as part of the active context. + self.loop_frame = False + self.block_frame = False + + # track whether the frame is being used in an if-statement or conditional + # expression as it determines which errors should be raised during runtime + # or compile time. + self.soft_frame = False + + def copy(self) -> "Frame": + """Create a copy of the current one.""" + rv = object.__new__(self.__class__) + rv.__dict__.update(self.__dict__) + rv.symbols = self.symbols.copy() + return rv + + def inner(self, isolated: bool = False) -> "Frame": + """Return an inner frame.""" + if isolated: + return Frame(self.eval_ctx, level=self.symbols.level + 1) + return Frame(self.eval_ctx, self) + + def soft(self) -> "Frame": + """Return a soft frame. A soft frame may not be modified as + standalone thing as it shares the resources with the frame it + was created of, but it's not a rootlevel frame any longer. + + This is only used to implement if-statements and conditional + expressions. + """ + rv = self.copy() + rv.rootlevel = False + rv.soft_frame = True + return rv + + __copy__ = copy + + +class VisitorExit(RuntimeError): + """Exception used by the `UndeclaredNameVisitor` to signal a stop.""" + + +class DependencyFinderVisitor(NodeVisitor): + """A visitor that collects filter and test calls.""" + + def __init__(self) -> None: + self.filters: t.Set[str] = set() + self.tests: t.Set[str] = set() + + def visit_Filter(self, node: nodes.Filter) -> None: + self.generic_visit(node) + self.filters.add(node.name) + + def visit_Test(self, node: nodes.Test) -> None: + self.generic_visit(node) + self.tests.add(node.name) + + def visit_Block(self, node: nodes.Block) -> None: + """Stop visiting at blocks.""" + + +class UndeclaredNameVisitor(NodeVisitor): + """A visitor that checks if a name is accessed without being + declared. This is different from the frame visitor as it will + not stop at closure frames. + """ + + def __init__(self, names: t.Iterable[str]) -> None: + self.names = set(names) + self.undeclared: t.Set[str] = set() + + def visit_Name(self, node: nodes.Name) -> None: + if node.ctx == "load" and node.name in self.names: + self.undeclared.add(node.name) + if self.undeclared == self.names: + raise VisitorExit() + else: + self.names.discard(node.name) + + def visit_Block(self, node: nodes.Block) -> None: + """Stop visiting a blocks.""" + + +class CompilerExit(Exception): + """Raised if the compiler encountered a situation where it just + doesn't make sense to further process the code. Any block that + raises such an exception is not further processed. + """ + + +class CodeGenerator(NodeVisitor): + def __init__( + self, + environment: "Environment", + name: t.Optional[str], + filename: t.Optional[str], + stream: t.Optional[t.TextIO] = None, + defer_init: bool = False, + optimized: bool = True, + ) -> None: + if stream is None: + stream = StringIO() + self.environment = environment + self.name = name + self.filename = filename + self.stream = stream + self.created_block_context = False + self.defer_init = defer_init + self.optimizer: t.Optional[Optimizer] = None + + if optimized: + self.optimizer = Optimizer(environment) + + # aliases for imports + self.import_aliases: t.Dict[str, str] = {} + + # a registry for all blocks. Because blocks are moved out + # into the global python scope they are registered here + self.blocks: t.Dict[str, nodes.Block] = {} + + # the number of extends statements so far + self.extends_so_far = 0 + + # some templates have a rootlevel extends. In this case we + # can safely assume that we're a child template and do some + # more optimizations. + self.has_known_extends = False + + # the current line number + self.code_lineno = 1 + + # registry of all filters and tests (global, not block local) + self.tests: t.Dict[str, str] = {} + self.filters: t.Dict[str, str] = {} + + # the debug information + self.debug_info: t.List[t.Tuple[int, int]] = [] + self._write_debug_info: t.Optional[int] = None + + # the number of new lines before the next write() + self._new_lines = 0 + + # the line number of the last written statement + self._last_line = 0 + + # true if nothing was written so far. + self._first_write = True + + # used by the `temporary_identifier` method to get new + # unique, temporary identifier + self._last_identifier = 0 + + # the current indentation + self._indentation = 0 + + # Tracks toplevel assignments + self._assign_stack: t.List[t.Set[str]] = [] + + # Tracks parameter definition blocks + self._param_def_block: t.List[t.Set[str]] = [] + + # Tracks the current context. + self._context_reference_stack = ["context"] + + @property + def optimized(self) -> bool: + return self.optimizer is not None + + # -- Various compilation helpers + + def fail(self, msg: str, lineno: int) -> "te.NoReturn": + """Fail with a :exc:`TemplateAssertionError`.""" + raise TemplateAssertionError(msg, lineno, self.name, self.filename) + + def temporary_identifier(self) -> str: + """Get a new unique identifier.""" + self._last_identifier += 1 + return f"t_{self._last_identifier}" + + def buffer(self, frame: Frame) -> None: + """Enable buffering for the frame from that point onwards.""" + frame.buffer = self.temporary_identifier() + self.writeline(f"{frame.buffer} = []") + + def return_buffer_contents( + self, frame: Frame, force_unescaped: bool = False + ) -> None: + """Return the buffer contents of the frame.""" + if not force_unescaped: + if frame.eval_ctx.volatile: + self.writeline("if context.eval_ctx.autoescape:") + self.indent() + self.writeline(f"return Markup(concat({frame.buffer}))") + self.outdent() + self.writeline("else:") + self.indent() + self.writeline(f"return concat({frame.buffer})") + self.outdent() + return + elif frame.eval_ctx.autoescape: + self.writeline(f"return Markup(concat({frame.buffer}))") + return + self.writeline(f"return concat({frame.buffer})") + + def indent(self) -> None: + """Indent by one.""" + self._indentation += 1 + + def outdent(self, step: int = 1) -> None: + """Outdent by step.""" + self._indentation -= step + + def start_write(self, frame: Frame, node: t.Optional[nodes.Node] = None) -> None: + """Yield or write into the frame buffer.""" + if frame.buffer is None: + self.writeline("yield ", node) + else: + self.writeline(f"{frame.buffer}.append(", node) + + def end_write(self, frame: Frame) -> None: + """End the writing process started by `start_write`.""" + if frame.buffer is not None: + self.write(")") + + def simple_write( + self, s: str, frame: Frame, node: t.Optional[nodes.Node] = None + ) -> None: + """Simple shortcut for start_write + write + end_write.""" + self.start_write(frame, node) + self.write(s) + self.end_write(frame) + + def blockvisit(self, nodes: t.Iterable[nodes.Node], frame: Frame) -> None: + """Visit a list of nodes as block in a frame. If the current frame + is no buffer a dummy ``if 0: yield None`` is written automatically. + """ + try: + self.writeline("pass") + for node in nodes: + self.visit(node, frame) + except CompilerExit: + pass + + def write(self, x: str) -> None: + """Write a string into the output stream.""" + if self._new_lines: + if not self._first_write: + self.stream.write("\n" * self._new_lines) + self.code_lineno += self._new_lines + if self._write_debug_info is not None: + self.debug_info.append((self._write_debug_info, self.code_lineno)) + self._write_debug_info = None + self._first_write = False + self.stream.write(" " * self._indentation) + self._new_lines = 0 + self.stream.write(x) + + def writeline( + self, x: str, node: t.Optional[nodes.Node] = None, extra: int = 0 + ) -> None: + """Combination of newline and write.""" + self.newline(node, extra) + self.write(x) + + def newline(self, node: t.Optional[nodes.Node] = None, extra: int = 0) -> None: + """Add one or more newlines before the next write.""" + self._new_lines = max(self._new_lines, 1 + extra) + if node is not None and node.lineno != self._last_line: + self._write_debug_info = node.lineno + self._last_line = node.lineno + + def signature( + self, + node: t.Union[nodes.Call, nodes.Filter, nodes.Test], + frame: Frame, + extra_kwargs: t.Optional[t.Mapping[str, t.Any]] = None, + ) -> None: + """Writes a function call to the stream for the current node. + A leading comma is added automatically. The extra keyword + arguments may not include python keywords otherwise a syntax + error could occur. The extra keyword arguments should be given + as python dict. + """ + # if any of the given keyword arguments is a python keyword + # we have to make sure that no invalid call is created. + kwarg_workaround = any( + is_python_keyword(t.cast(str, k)) + for k in chain((x.key for x in node.kwargs), extra_kwargs or ()) + ) + + for arg in node.args: + self.write(", ") + self.visit(arg, frame) + + if not kwarg_workaround: + for kwarg in node.kwargs: + self.write(", ") + self.visit(kwarg, frame) + if extra_kwargs is not None: + for key, value in extra_kwargs.items(): + self.write(f", {key}={value}") + if node.dyn_args: + self.write(", *") + self.visit(node.dyn_args, frame) + + if kwarg_workaround: + if node.dyn_kwargs is not None: + self.write(", **dict({") + else: + self.write(", **{") + for kwarg in node.kwargs: + self.write(f"{kwarg.key!r}: ") + self.visit(kwarg.value, frame) + self.write(", ") + if extra_kwargs is not None: + for key, value in extra_kwargs.items(): + self.write(f"{key!r}: {value}, ") + if node.dyn_kwargs is not None: + self.write("}, **") + self.visit(node.dyn_kwargs, frame) + self.write(")") + else: + self.write("}") + + elif node.dyn_kwargs is not None: + self.write(", **") + self.visit(node.dyn_kwargs, frame) + + def pull_dependencies(self, nodes: t.Iterable[nodes.Node]) -> None: + """Find all filter and test names used in the template and + assign them to variables in the compiled namespace. Checking + that the names are registered with the environment is done when + compiling the Filter and Test nodes. If the node is in an If or + CondExpr node, the check is done at runtime instead. + + .. versionchanged:: 3.0 + Filters and tests in If and CondExpr nodes are checked at + runtime instead of compile time. + """ + visitor = DependencyFinderVisitor() + + for node in nodes: + visitor.visit(node) + + for id_map, names, dependency in ( + (self.filters, visitor.filters, "filters"), + ( + self.tests, + visitor.tests, + "tests", + ), + ): + for name in sorted(names): + if name not in id_map: + id_map[name] = self.temporary_identifier() + + # add check during runtime that dependencies used inside of executed + # blocks are defined, as this step may be skipped during compile time + self.writeline("try:") + self.indent() + self.writeline(f"{id_map[name]} = environment.{dependency}[{name!r}]") + self.outdent() + self.writeline("except KeyError:") + self.indent() + self.writeline("@internalcode") + self.writeline(f"def {id_map[name]}(*unused):") + self.indent() + self.writeline( + f'raise TemplateRuntimeError("No {dependency[:-1]}' + f' named {name!r} found.")' + ) + self.outdent() + self.outdent() + + def enter_frame(self, frame: Frame) -> None: + undefs = [] + for target, (action, param) in frame.symbols.loads.items(): + if action == VAR_LOAD_PARAMETER: + pass + elif action == VAR_LOAD_RESOLVE: + self.writeline(f"{target} = {self.get_resolve_func()}({param!r})") + elif action == VAR_LOAD_ALIAS: + self.writeline(f"{target} = {param}") + elif action == VAR_LOAD_UNDEFINED: + undefs.append(target) + else: + raise NotImplementedError("unknown load instruction") + if undefs: + self.writeline(f"{' = '.join(undefs)} = missing") + + def leave_frame(self, frame: Frame, with_python_scope: bool = False) -> None: + if not with_python_scope: + undefs = [] + for target in frame.symbols.loads: + undefs.append(target) + if undefs: + self.writeline(f"{' = '.join(undefs)} = missing") + + def choose_async(self, async_value: str = "async ", sync_value: str = "") -> str: + return async_value if self.environment.is_async else sync_value + + def func(self, name: str) -> str: + return f"{self.choose_async()}def {name}" + + def macro_body( + self, node: t.Union[nodes.Macro, nodes.CallBlock], frame: Frame + ) -> t.Tuple[Frame, MacroRef]: + """Dump the function def of a macro or call block.""" + frame = frame.inner() + frame.symbols.analyze_node(node) + macro_ref = MacroRef(node) + + explicit_caller = None + skip_special_params = set() + args = [] + + for idx, arg in enumerate(node.args): + if arg.name == "caller": + explicit_caller = idx + if arg.name in ("kwargs", "varargs"): + skip_special_params.add(arg.name) + args.append(frame.symbols.ref(arg.name)) + + undeclared = find_undeclared(node.body, ("caller", "kwargs", "varargs")) + + if "caller" in undeclared: + # In older Jinja versions there was a bug that allowed caller + # to retain the special behavior even if it was mentioned in + # the argument list. However thankfully this was only really + # working if it was the last argument. So we are explicitly + # checking this now and error out if it is anywhere else in + # the argument list. + if explicit_caller is not None: + try: + node.defaults[explicit_caller - len(node.args)] + except IndexError: + self.fail( + "When defining macros or call blocks the " + 'special "caller" argument must be omitted ' + "or be given a default.", + node.lineno, + ) + else: + args.append(frame.symbols.declare_parameter("caller")) + macro_ref.accesses_caller = True + if "kwargs" in undeclared and "kwargs" not in skip_special_params: + args.append(frame.symbols.declare_parameter("kwargs")) + macro_ref.accesses_kwargs = True + if "varargs" in undeclared and "varargs" not in skip_special_params: + args.append(frame.symbols.declare_parameter("varargs")) + macro_ref.accesses_varargs = True + + # macros are delayed, they never require output checks + frame.require_output_check = False + frame.symbols.analyze_node(node) + self.writeline(f"{self.func('macro')}({', '.join(args)}):", node) + self.indent() + + self.buffer(frame) + self.enter_frame(frame) + + self.push_parameter_definitions(frame) + for idx, arg in enumerate(node.args): + ref = frame.symbols.ref(arg.name) + self.writeline(f"if {ref} is missing:") + self.indent() + try: + default = node.defaults[idx - len(node.args)] + except IndexError: + self.writeline( + f'{ref} = undefined("parameter {arg.name!r} was not provided",' + f" name={arg.name!r})" + ) + else: + self.writeline(f"{ref} = ") + self.visit(default, frame) + self.mark_parameter_stored(ref) + self.outdent() + self.pop_parameter_definitions() + + self.blockvisit(node.body, frame) + self.return_buffer_contents(frame, force_unescaped=True) + self.leave_frame(frame, with_python_scope=True) + self.outdent() + + return frame, macro_ref + + def macro_def(self, macro_ref: MacroRef, frame: Frame) -> None: + """Dump the macro definition for the def created by macro_body.""" + arg_tuple = ", ".join(repr(x.name) for x in macro_ref.node.args) + name = getattr(macro_ref.node, "name", None) + if len(macro_ref.node.args) == 1: + arg_tuple += "," + self.write( + f"Macro(environment, macro, {name!r}, ({arg_tuple})," + f" {macro_ref.accesses_kwargs!r}, {macro_ref.accesses_varargs!r}," + f" {macro_ref.accesses_caller!r}, context.eval_ctx.autoescape)" + ) + + def position(self, node: nodes.Node) -> str: + """Return a human readable position for the node.""" + rv = f"line {node.lineno}" + if self.name is not None: + rv = f"{rv} in {self.name!r}" + return rv + + def dump_local_context(self, frame: Frame) -> str: + items_kv = ", ".join( + f"{name!r}: {target}" + for name, target in frame.symbols.dump_stores().items() + ) + return f"{{{items_kv}}}" + + def write_commons(self) -> None: + """Writes a common preamble that is used by root and block functions. + Primarily this sets up common local helpers and enforces a generator + through a dead branch. + """ + self.writeline("resolve = context.resolve_or_missing") + self.writeline("undefined = environment.undefined") + self.writeline("concat = environment.concat") + # always use the standard Undefined class for the implicit else of + # conditional expressions + self.writeline("cond_expr_undefined = Undefined") + self.writeline("if 0: yield None") + + def push_parameter_definitions(self, frame: Frame) -> None: + """Pushes all parameter targets from the given frame into a local + stack that permits tracking of yet to be assigned parameters. In + particular this enables the optimization from `visit_Name` to skip + undefined expressions for parameters in macros as macros can reference + otherwise unbound parameters. + """ + self._param_def_block.append(frame.symbols.dump_param_targets()) + + def pop_parameter_definitions(self) -> None: + """Pops the current parameter definitions set.""" + self._param_def_block.pop() + + def mark_parameter_stored(self, target: str) -> None: + """Marks a parameter in the current parameter definitions as stored. + This will skip the enforced undefined checks. + """ + if self._param_def_block: + self._param_def_block[-1].discard(target) + + def push_context_reference(self, target: str) -> None: + self._context_reference_stack.append(target) + + def pop_context_reference(self) -> None: + self._context_reference_stack.pop() + + def get_context_ref(self) -> str: + return self._context_reference_stack[-1] + + def get_resolve_func(self) -> str: + target = self._context_reference_stack[-1] + if target == "context": + return "resolve" + return f"{target}.resolve" + + def derive_context(self, frame: Frame) -> str: + return f"{self.get_context_ref()}.derived({self.dump_local_context(frame)})" + + def parameter_is_undeclared(self, target: str) -> bool: + """Checks if a given target is an undeclared parameter.""" + if not self._param_def_block: + return False + return target in self._param_def_block[-1] + + def push_assign_tracking(self) -> None: + """Pushes a new layer for assignment tracking.""" + self._assign_stack.append(set()) + + def pop_assign_tracking(self, frame: Frame) -> None: + """Pops the topmost level for assignment tracking and updates the + context variables if necessary. + """ + vars = self._assign_stack.pop() + if ( + not frame.block_frame + and not frame.loop_frame + and not frame.toplevel + or not vars + ): + return + public_names = [x for x in vars if x[:1] != "_"] + if len(vars) == 1: + name = next(iter(vars)) + ref = frame.symbols.ref(name) + if frame.loop_frame: + self.writeline(f"_loop_vars[{name!r}] = {ref}") + return + if frame.block_frame: + self.writeline(f"_block_vars[{name!r}] = {ref}") + return + self.writeline(f"context.vars[{name!r}] = {ref}") + else: + if frame.loop_frame: + self.writeline("_loop_vars.update({") + elif frame.block_frame: + self.writeline("_block_vars.update({") + else: + self.writeline("context.vars.update({") + for idx, name in enumerate(vars): + if idx: + self.write(", ") + ref = frame.symbols.ref(name) + self.write(f"{name!r}: {ref}") + self.write("})") + if not frame.block_frame and not frame.loop_frame and public_names: + if len(public_names) == 1: + self.writeline(f"context.exported_vars.add({public_names[0]!r})") + else: + names_str = ", ".join(map(repr, public_names)) + self.writeline(f"context.exported_vars.update(({names_str}))") + + # -- Statement Visitors + + def visit_Template( + self, node: nodes.Template, frame: t.Optional[Frame] = None + ) -> None: + assert frame is None, "no root frame allowed" + eval_ctx = EvalContext(self.environment, self.name) + + from .runtime import async_exported + from .runtime import exported + + if self.environment.is_async: + exported_names = sorted(exported + async_exported) + else: + exported_names = sorted(exported) + + self.writeline("from jinja2.runtime import " + ", ".join(exported_names)) + + # if we want a deferred initialization we cannot move the + # environment into a local name + envenv = "" if self.defer_init else ", environment=environment" + + # do we have an extends tag at all? If not, we can save some + # overhead by just not processing any inheritance code. + have_extends = node.find(nodes.Extends) is not None + + # find all blocks + for block in node.find_all(nodes.Block): + if block.name in self.blocks: + self.fail(f"block {block.name!r} defined twice", block.lineno) + self.blocks[block.name] = block + + # find all imports and import them + for import_ in node.find_all(nodes.ImportedName): + if import_.importname not in self.import_aliases: + imp = import_.importname + self.import_aliases[imp] = alias = self.temporary_identifier() + if "." in imp: + module, obj = imp.rsplit(".", 1) + self.writeline(f"from {module} import {obj} as {alias}") + else: + self.writeline(f"import {imp} as {alias}") + + # add the load name + self.writeline(f"name = {self.name!r}") + + # generate the root render function. + self.writeline( + f"{self.func('root')}(context, missing=missing{envenv}):", extra=1 + ) + self.indent() + self.write_commons() + + # process the root + frame = Frame(eval_ctx) + if "self" in find_undeclared(node.body, ("self",)): + ref = frame.symbols.declare_parameter("self") + self.writeline(f"{ref} = TemplateReference(context)") + frame.symbols.analyze_node(node) + frame.toplevel = frame.rootlevel = True + frame.require_output_check = have_extends and not self.has_known_extends + if have_extends: + self.writeline("parent_template = None") + self.enter_frame(frame) + self.pull_dependencies(node.body) + self.blockvisit(node.body, frame) + self.leave_frame(frame, with_python_scope=True) + self.outdent() + + # make sure that the parent root is called. + if have_extends: + if not self.has_known_extends: + self.indent() + self.writeline("if parent_template is not None:") + self.indent() + if not self.environment.is_async: + self.writeline("yield from parent_template.root_render_func(context)") + else: + self.writeline( + "async for event in parent_template.root_render_func(context):" + ) + self.indent() + self.writeline("yield event") + self.outdent() + self.outdent(1 + (not self.has_known_extends)) + + # at this point we now have the blocks collected and can visit them too. + for name, block in self.blocks.items(): + self.writeline( + f"{self.func('block_' + name)}(context, missing=missing{envenv}):", + block, + 1, + ) + self.indent() + self.write_commons() + # It's important that we do not make this frame a child of the + # toplevel template. This would cause a variety of + # interesting issues with identifier tracking. + block_frame = Frame(eval_ctx) + block_frame.block_frame = True + undeclared = find_undeclared(block.body, ("self", "super")) + if "self" in undeclared: + ref = block_frame.symbols.declare_parameter("self") + self.writeline(f"{ref} = TemplateReference(context)") + if "super" in undeclared: + ref = block_frame.symbols.declare_parameter("super") + self.writeline(f"{ref} = context.super({name!r}, block_{name})") + block_frame.symbols.analyze_node(block) + block_frame.block = name + self.writeline("_block_vars = {}") + self.enter_frame(block_frame) + self.pull_dependencies(block.body) + self.blockvisit(block.body, block_frame) + self.leave_frame(block_frame, with_python_scope=True) + self.outdent() + + blocks_kv_str = ", ".join(f"{x!r}: block_{x}" for x in self.blocks) + self.writeline(f"blocks = {{{blocks_kv_str}}}", extra=1) + debug_kv_str = "&".join(f"{k}={v}" for k, v in self.debug_info) + self.writeline(f"debug_info = {debug_kv_str!r}") + + def visit_Block(self, node: nodes.Block, frame: Frame) -> None: + """Call a block and register it for the template.""" + level = 0 + if frame.toplevel: + # if we know that we are a child template, there is no need to + # check if we are one + if self.has_known_extends: + return + if self.extends_so_far > 0: + self.writeline("if parent_template is None:") + self.indent() + level += 1 + + if node.scoped: + context = self.derive_context(frame) + else: + context = self.get_context_ref() + + if node.required: + self.writeline(f"if len(context.blocks[{node.name!r}]) <= 1:", node) + self.indent() + self.writeline( + f'raise TemplateRuntimeError("Required block {node.name!r} not found")', + node, + ) + self.outdent() + + if not self.environment.is_async and frame.buffer is None: + self.writeline( + f"yield from context.blocks[{node.name!r}][0]({context})", node + ) + else: + self.writeline( + f"{self.choose_async()}for event in" + f" context.blocks[{node.name!r}][0]({context}):", + node, + ) + self.indent() + self.simple_write("event", frame) + self.outdent() + + self.outdent(level) + + def visit_Extends(self, node: nodes.Extends, frame: Frame) -> None: + """Calls the extender.""" + if not frame.toplevel: + self.fail("cannot use extend from a non top-level scope", node.lineno) + + # if the number of extends statements in general is zero so + # far, we don't have to add a check if something extended + # the template before this one. + if self.extends_so_far > 0: + # if we have a known extends we just add a template runtime + # error into the generated code. We could catch that at compile + # time too, but i welcome it not to confuse users by throwing the + # same error at different times just "because we can". + if not self.has_known_extends: + self.writeline("if parent_template is not None:") + self.indent() + self.writeline('raise TemplateRuntimeError("extended multiple times")') + + # if we have a known extends already we don't need that code here + # as we know that the template execution will end here. + if self.has_known_extends: + raise CompilerExit() + else: + self.outdent() + + self.writeline("parent_template = environment.get_template(", node) + self.visit(node.template, frame) + self.write(f", {self.name!r})") + self.writeline("for name, parent_block in parent_template.blocks.items():") + self.indent() + self.writeline("context.blocks.setdefault(name, []).append(parent_block)") + self.outdent() + + # if this extends statement was in the root level we can take + # advantage of that information and simplify the generated code + # in the top level from this point onwards + if frame.rootlevel: + self.has_known_extends = True + + # and now we have one more + self.extends_so_far += 1 + + def visit_Include(self, node: nodes.Include, frame: Frame) -> None: + """Handles includes.""" + if node.ignore_missing: + self.writeline("try:") + self.indent() + + func_name = "get_or_select_template" + if isinstance(node.template, nodes.Const): + if isinstance(node.template.value, str): + func_name = "get_template" + elif isinstance(node.template.value, (tuple, list)): + func_name = "select_template" + elif isinstance(node.template, (nodes.Tuple, nodes.List)): + func_name = "select_template" + + self.writeline(f"template = environment.{func_name}(", node) + self.visit(node.template, frame) + self.write(f", {self.name!r})") + if node.ignore_missing: + self.outdent() + self.writeline("except TemplateNotFound:") + self.indent() + self.writeline("pass") + self.outdent() + self.writeline("else:") + self.indent() + + skip_event_yield = False + if node.with_context: + self.writeline( + f"{self.choose_async()}for event in template.root_render_func(" + "template.new_context(context.get_all(), True," + f" {self.dump_local_context(frame)})):" + ) + elif self.environment.is_async: + self.writeline( + "for event in (await template._get_default_module_async())" + "._body_stream:" + ) + else: + self.writeline("yield from template._get_default_module()._body_stream") + skip_event_yield = True + + if not skip_event_yield: + self.indent() + self.simple_write("event", frame) + self.outdent() + + if node.ignore_missing: + self.outdent() + + def _import_common( + self, node: t.Union[nodes.Import, nodes.FromImport], frame: Frame + ) -> None: + self.write(f"{self.choose_async('await ')}environment.get_template(") + self.visit(node.template, frame) + self.write(f", {self.name!r}).") + + if node.with_context: + f_name = f"make_module{self.choose_async('_async')}" + self.write( + f"{f_name}(context.get_all(), True, {self.dump_local_context(frame)})" + ) + else: + self.write(f"_get_default_module{self.choose_async('_async')}(context)") + + def visit_Import(self, node: nodes.Import, frame: Frame) -> None: + """Visit regular imports.""" + self.writeline(f"{frame.symbols.ref(node.target)} = ", node) + if frame.toplevel: + self.write(f"context.vars[{node.target!r}] = ") + + self._import_common(node, frame) + + if frame.toplevel and not node.target.startswith("_"): + self.writeline(f"context.exported_vars.discard({node.target!r})") + + def visit_FromImport(self, node: nodes.FromImport, frame: Frame) -> None: + """Visit named imports.""" + self.newline(node) + self.write("included_template = ") + self._import_common(node, frame) + var_names = [] + discarded_names = [] + for name in node.names: + if isinstance(name, tuple): + name, alias = name + else: + alias = name + self.writeline( + f"{frame.symbols.ref(alias)} =" + f" getattr(included_template, {name!r}, missing)" + ) + self.writeline(f"if {frame.symbols.ref(alias)} is missing:") + self.indent() + message = ( + "the template {included_template.__name__!r}" + f" (imported on {self.position(node)})" + f" does not export the requested name {name!r}" + ) + self.writeline( + f"{frame.symbols.ref(alias)} = undefined(f{message!r}, name={name!r})" + ) + self.outdent() + if frame.toplevel: + var_names.append(alias) + if not alias.startswith("_"): + discarded_names.append(alias) + + if var_names: + if len(var_names) == 1: + name = var_names[0] + self.writeline(f"context.vars[{name!r}] = {frame.symbols.ref(name)}") + else: + names_kv = ", ".join( + f"{name!r}: {frame.symbols.ref(name)}" for name in var_names + ) + self.writeline(f"context.vars.update({{{names_kv}}})") + if discarded_names: + if len(discarded_names) == 1: + self.writeline(f"context.exported_vars.discard({discarded_names[0]!r})") + else: + names_str = ", ".join(map(repr, discarded_names)) + self.writeline( + f"context.exported_vars.difference_update(({names_str}))" + ) + + def visit_For(self, node: nodes.For, frame: Frame) -> None: + loop_frame = frame.inner() + loop_frame.loop_frame = True + test_frame = frame.inner() + else_frame = frame.inner() + + # try to figure out if we have an extended loop. An extended loop + # is necessary if the loop is in recursive mode if the special loop + # variable is accessed in the body if the body is a scoped block. + extended_loop = ( + node.recursive + or "loop" + in find_undeclared(node.iter_child_nodes(only=("body",)), ("loop",)) + or any(block.scoped for block in node.find_all(nodes.Block)) + ) + + loop_ref = None + if extended_loop: + loop_ref = loop_frame.symbols.declare_parameter("loop") + + loop_frame.symbols.analyze_node(node, for_branch="body") + if node.else_: + else_frame.symbols.analyze_node(node, for_branch="else") + + if node.test: + loop_filter_func = self.temporary_identifier() + test_frame.symbols.analyze_node(node, for_branch="test") + self.writeline(f"{self.func(loop_filter_func)}(fiter):", node.test) + self.indent() + self.enter_frame(test_frame) + self.writeline(self.choose_async("async for ", "for ")) + self.visit(node.target, loop_frame) + self.write(" in ") + self.write(self.choose_async("auto_aiter(fiter)", "fiter")) + self.write(":") + self.indent() + self.writeline("if ", node.test) + self.visit(node.test, test_frame) + self.write(":") + self.indent() + self.writeline("yield ") + self.visit(node.target, loop_frame) + self.outdent(3) + self.leave_frame(test_frame, with_python_scope=True) + + # if we don't have an recursive loop we have to find the shadowed + # variables at that point. Because loops can be nested but the loop + # variable is a special one we have to enforce aliasing for it. + if node.recursive: + self.writeline( + f"{self.func('loop')}(reciter, loop_render_func, depth=0):", node + ) + self.indent() + self.buffer(loop_frame) + + # Use the same buffer for the else frame + else_frame.buffer = loop_frame.buffer + + # make sure the loop variable is a special one and raise a template + # assertion error if a loop tries to write to loop + if extended_loop: + self.writeline(f"{loop_ref} = missing") + + for name in node.find_all(nodes.Name): + if name.ctx == "store" and name.name == "loop": + self.fail( + "Can't assign to special loop variable in for-loop target", + name.lineno, + ) + + if node.else_: + iteration_indicator = self.temporary_identifier() + self.writeline(f"{iteration_indicator} = 1") + + self.writeline(self.choose_async("async for ", "for "), node) + self.visit(node.target, loop_frame) + if extended_loop: + self.write(f", {loop_ref} in {self.choose_async('Async')}LoopContext(") + else: + self.write(" in ") + + if node.test: + self.write(f"{loop_filter_func}(") + if node.recursive: + self.write("reciter") + else: + if self.environment.is_async and not extended_loop: + self.write("auto_aiter(") + self.visit(node.iter, frame) + if self.environment.is_async and not extended_loop: + self.write(")") + if node.test: + self.write(")") + + if node.recursive: + self.write(", undefined, loop_render_func, depth):") + else: + self.write(", undefined):" if extended_loop else ":") + + self.indent() + self.enter_frame(loop_frame) + + self.writeline("_loop_vars = {}") + self.blockvisit(node.body, loop_frame) + if node.else_: + self.writeline(f"{iteration_indicator} = 0") + self.outdent() + self.leave_frame( + loop_frame, with_python_scope=node.recursive and not node.else_ + ) + + if node.else_: + self.writeline(f"if {iteration_indicator}:") + self.indent() + self.enter_frame(else_frame) + self.blockvisit(node.else_, else_frame) + self.leave_frame(else_frame) + self.outdent() + + # if the node was recursive we have to return the buffer contents + # and start the iteration code + if node.recursive: + self.return_buffer_contents(loop_frame) + self.outdent() + self.start_write(frame, node) + self.write(f"{self.choose_async('await ')}loop(") + if self.environment.is_async: + self.write("auto_aiter(") + self.visit(node.iter, frame) + if self.environment.is_async: + self.write(")") + self.write(", loop)") + self.end_write(frame) + + # at the end of the iteration, clear any assignments made in the + # loop from the top level + if self._assign_stack: + self._assign_stack[-1].difference_update(loop_frame.symbols.stores) + + def visit_If(self, node: nodes.If, frame: Frame) -> None: + if_frame = frame.soft() + self.writeline("if ", node) + self.visit(node.test, if_frame) + self.write(":") + self.indent() + self.blockvisit(node.body, if_frame) + self.outdent() + for elif_ in node.elif_: + self.writeline("elif ", elif_) + self.visit(elif_.test, if_frame) + self.write(":") + self.indent() + self.blockvisit(elif_.body, if_frame) + self.outdent() + if node.else_: + self.writeline("else:") + self.indent() + self.blockvisit(node.else_, if_frame) + self.outdent() + + def visit_Macro(self, node: nodes.Macro, frame: Frame) -> None: + macro_frame, macro_ref = self.macro_body(node, frame) + self.newline() + if frame.toplevel: + if not node.name.startswith("_"): + self.write(f"context.exported_vars.add({node.name!r})") + self.writeline(f"context.vars[{node.name!r}] = ") + self.write(f"{frame.symbols.ref(node.name)} = ") + self.macro_def(macro_ref, macro_frame) + + def visit_CallBlock(self, node: nodes.CallBlock, frame: Frame) -> None: + call_frame, macro_ref = self.macro_body(node, frame) + self.writeline("caller = ") + self.macro_def(macro_ref, call_frame) + self.start_write(frame, node) + self.visit_Call(node.call, frame, forward_caller=True) + self.end_write(frame) + + def visit_FilterBlock(self, node: nodes.FilterBlock, frame: Frame) -> None: + filter_frame = frame.inner() + filter_frame.symbols.analyze_node(node) + self.enter_frame(filter_frame) + self.buffer(filter_frame) + self.blockvisit(node.body, filter_frame) + self.start_write(frame, node) + self.visit_Filter(node.filter, filter_frame) + self.end_write(frame) + self.leave_frame(filter_frame) + + def visit_With(self, node: nodes.With, frame: Frame) -> None: + with_frame = frame.inner() + with_frame.symbols.analyze_node(node) + self.enter_frame(with_frame) + for target, expr in zip(node.targets, node.values): + self.newline() + self.visit(target, with_frame) + self.write(" = ") + self.visit(expr, frame) + self.blockvisit(node.body, with_frame) + self.leave_frame(with_frame) + + def visit_ExprStmt(self, node: nodes.ExprStmt, frame: Frame) -> None: + self.newline(node) + self.visit(node.node, frame) + + class _FinalizeInfo(t.NamedTuple): + const: t.Optional[t.Callable[..., str]] + src: t.Optional[str] + + @staticmethod + def _default_finalize(value: t.Any) -> t.Any: + """The default finalize function if the environment isn't + configured with one. Or, if the environment has one, this is + called on that function's output for constants. + """ + return str(value) + + _finalize: t.Optional[_FinalizeInfo] = None + + def _make_finalize(self) -> _FinalizeInfo: + """Build the finalize function to be used on constants and at + runtime. Cached so it's only created once for all output nodes. + + Returns a ``namedtuple`` with the following attributes: + + ``const`` + A function to finalize constant data at compile time. + + ``src`` + Source code to output around nodes to be evaluated at + runtime. + """ + if self._finalize is not None: + return self._finalize + + finalize: t.Optional[t.Callable[..., t.Any]] + finalize = default = self._default_finalize + src = None + + if self.environment.finalize: + src = "environment.finalize(" + env_finalize = self.environment.finalize + pass_arg = { + _PassArg.context: "context", + _PassArg.eval_context: "context.eval_ctx", + _PassArg.environment: "environment", + }.get( + _PassArg.from_obj(env_finalize) # type: ignore + ) + finalize = None + + if pass_arg is None: + + def finalize(value: t.Any) -> t.Any: # noqa: F811 + return default(env_finalize(value)) + + else: + src = f"{src}{pass_arg}, " + + if pass_arg == "environment": + + def finalize(value: t.Any) -> t.Any: # noqa: F811 + return default(env_finalize(self.environment, value)) + + self._finalize = self._FinalizeInfo(finalize, src) + return self._finalize + + def _output_const_repr(self, group: t.Iterable[t.Any]) -> str: + """Given a group of constant values converted from ``Output`` + child nodes, produce a string to write to the template module + source. + """ + return repr(concat(group)) + + def _output_child_to_const( + self, node: nodes.Expr, frame: Frame, finalize: _FinalizeInfo + ) -> str: + """Try to optimize a child of an ``Output`` node by trying to + convert it to constant, finalized data at compile time. + + If :exc:`Impossible` is raised, the node is not constant and + will be evaluated at runtime. Any other exception will also be + evaluated at runtime for easier debugging. + """ + const = node.as_const(frame.eval_ctx) + + if frame.eval_ctx.autoescape: + const = escape(const) + + # Template data doesn't go through finalize. + if isinstance(node, nodes.TemplateData): + return str(const) + + return finalize.const(const) # type: ignore + + def _output_child_pre( + self, node: nodes.Expr, frame: Frame, finalize: _FinalizeInfo + ) -> None: + """Output extra source code before visiting a child of an + ``Output`` node. + """ + if frame.eval_ctx.volatile: + self.write("(escape if context.eval_ctx.autoescape else str)(") + elif frame.eval_ctx.autoescape: + self.write("escape(") + else: + self.write("str(") + + if finalize.src is not None: + self.write(finalize.src) + + def _output_child_post( + self, node: nodes.Expr, frame: Frame, finalize: _FinalizeInfo + ) -> None: + """Output extra source code after visiting a child of an + ``Output`` node. + """ + self.write(")") + + if finalize.src is not None: + self.write(")") + + def visit_Output(self, node: nodes.Output, frame: Frame) -> None: + # If an extends is active, don't render outside a block. + if frame.require_output_check: + # A top-level extends is known to exist at compile time. + if self.has_known_extends: + return + + self.writeline("if parent_template is None:") + self.indent() + + finalize = self._make_finalize() + body: t.List[t.Union[t.List[t.Any], nodes.Expr]] = [] + + # Evaluate constants at compile time if possible. Each item in + # body will be either a list of static data or a node to be + # evaluated at runtime. + for child in node.nodes: + try: + if not ( + # If the finalize function requires runtime context, + # constants can't be evaluated at compile time. + finalize.const + # Unless it's basic template data that won't be + # finalized anyway. + or isinstance(child, nodes.TemplateData) + ): + raise nodes.Impossible() + + const = self._output_child_to_const(child, frame, finalize) + except (nodes.Impossible, Exception): + # The node was not constant and needs to be evaluated at + # runtime. Or another error was raised, which is easier + # to debug at runtime. + body.append(child) + continue + + if body and isinstance(body[-1], list): + body[-1].append(const) + else: + body.append([const]) + + if frame.buffer is not None: + if len(body) == 1: + self.writeline(f"{frame.buffer}.append(") + else: + self.writeline(f"{frame.buffer}.extend((") + + self.indent() + + for item in body: + if isinstance(item, list): + # A group of constant data to join and output. + val = self._output_const_repr(item) + + if frame.buffer is None: + self.writeline("yield " + val) + else: + self.writeline(val + ",") + else: + if frame.buffer is None: + self.writeline("yield ", item) + else: + self.newline(item) + + # A node to be evaluated at runtime. + self._output_child_pre(item, frame, finalize) + self.visit(item, frame) + self._output_child_post(item, frame, finalize) + + if frame.buffer is not None: + self.write(",") + + if frame.buffer is not None: + self.outdent() + self.writeline(")" if len(body) == 1 else "))") + + if frame.require_output_check: + self.outdent() + + def visit_Assign(self, node: nodes.Assign, frame: Frame) -> None: + self.push_assign_tracking() + self.newline(node) + self.visit(node.target, frame) + self.write(" = ") + self.visit(node.node, frame) + self.pop_assign_tracking(frame) + + def visit_AssignBlock(self, node: nodes.AssignBlock, frame: Frame) -> None: + self.push_assign_tracking() + block_frame = frame.inner() + # This is a special case. Since a set block always captures we + # will disable output checks. This way one can use set blocks + # toplevel even in extended templates. + block_frame.require_output_check = False + block_frame.symbols.analyze_node(node) + self.enter_frame(block_frame) + self.buffer(block_frame) + self.blockvisit(node.body, block_frame) + self.newline(node) + self.visit(node.target, frame) + self.write(" = (Markup if context.eval_ctx.autoescape else identity)(") + if node.filter is not None: + self.visit_Filter(node.filter, block_frame) + else: + self.write(f"concat({block_frame.buffer})") + self.write(")") + self.pop_assign_tracking(frame) + self.leave_frame(block_frame) + + # -- Expression Visitors + + def visit_Name(self, node: nodes.Name, frame: Frame) -> None: + if node.ctx == "store" and ( + frame.toplevel or frame.loop_frame or frame.block_frame + ): + if self._assign_stack: + self._assign_stack[-1].add(node.name) + ref = frame.symbols.ref(node.name) + + # If we are looking up a variable we might have to deal with the + # case where it's undefined. We can skip that case if the load + # instruction indicates a parameter which are always defined. + if node.ctx == "load": + load = frame.symbols.find_load(ref) + if not ( + load is not None + and load[0] == VAR_LOAD_PARAMETER + and not self.parameter_is_undeclared(ref) + ): + self.write( + f"(undefined(name={node.name!r}) if {ref} is missing else {ref})" + ) + return + + self.write(ref) + + def visit_NSRef(self, node: nodes.NSRef, frame: Frame) -> None: + # NSRefs can only be used to store values; since they use the normal + # `foo.bar` notation they will be parsed as a normal attribute access + # when used anywhere but in a `set` context + ref = frame.symbols.ref(node.name) + self.writeline(f"if not isinstance({ref}, Namespace):") + self.indent() + self.writeline( + "raise TemplateRuntimeError" + '("cannot assign attribute on non-namespace object")' + ) + self.outdent() + self.writeline(f"{ref}[{node.attr!r}]") + + def visit_Const(self, node: nodes.Const, frame: Frame) -> None: + val = node.as_const(frame.eval_ctx) + if isinstance(val, float): + self.write(str(val)) + else: + self.write(repr(val)) + + def visit_TemplateData(self, node: nodes.TemplateData, frame: Frame) -> None: + try: + self.write(repr(node.as_const(frame.eval_ctx))) + except nodes.Impossible: + self.write( + f"(Markup if context.eval_ctx.autoescape else identity)({node.data!r})" + ) + + def visit_Tuple(self, node: nodes.Tuple, frame: Frame) -> None: + self.write("(") + idx = -1 + for idx, item in enumerate(node.items): + if idx: + self.write(", ") + self.visit(item, frame) + self.write(",)" if idx == 0 else ")") + + def visit_List(self, node: nodes.List, frame: Frame) -> None: + self.write("[") + for idx, item in enumerate(node.items): + if idx: + self.write(", ") + self.visit(item, frame) + self.write("]") + + def visit_Dict(self, node: nodes.Dict, frame: Frame) -> None: + self.write("{") + for idx, item in enumerate(node.items): + if idx: + self.write(", ") + self.visit(item.key, frame) + self.write(": ") + self.visit(item.value, frame) + self.write("}") + + visit_Add = _make_binop("+") + visit_Sub = _make_binop("-") + visit_Mul = _make_binop("*") + visit_Div = _make_binop("/") + visit_FloorDiv = _make_binop("//") + visit_Pow = _make_binop("**") + visit_Mod = _make_binop("%") + visit_And = _make_binop("and") + visit_Or = _make_binop("or") + visit_Pos = _make_unop("+") + visit_Neg = _make_unop("-") + visit_Not = _make_unop("not ") + + @optimizeconst + def visit_Concat(self, node: nodes.Concat, frame: Frame) -> None: + if frame.eval_ctx.volatile: + func_name = "(markup_join if context.eval_ctx.volatile else str_join)" + elif frame.eval_ctx.autoescape: + func_name = "markup_join" + else: + func_name = "str_join" + self.write(f"{func_name}((") + for arg in node.nodes: + self.visit(arg, frame) + self.write(", ") + self.write("))") + + @optimizeconst + def visit_Compare(self, node: nodes.Compare, frame: Frame) -> None: + self.write("(") + self.visit(node.expr, frame) + for op in node.ops: + self.visit(op, frame) + self.write(")") + + def visit_Operand(self, node: nodes.Operand, frame: Frame) -> None: + self.write(f" {operators[node.op]} ") + self.visit(node.expr, frame) + + @optimizeconst + def visit_Getattr(self, node: nodes.Getattr, frame: Frame) -> None: + if self.environment.is_async: + self.write("(await auto_await(") + + self.write("environment.getattr(") + self.visit(node.node, frame) + self.write(f", {node.attr!r})") + + if self.environment.is_async: + self.write("))") + + @optimizeconst + def visit_Getitem(self, node: nodes.Getitem, frame: Frame) -> None: + # slices bypass the environment getitem method. + if isinstance(node.arg, nodes.Slice): + self.visit(node.node, frame) + self.write("[") + self.visit(node.arg, frame) + self.write("]") + else: + if self.environment.is_async: + self.write("(await auto_await(") + + self.write("environment.getitem(") + self.visit(node.node, frame) + self.write(", ") + self.visit(node.arg, frame) + self.write(")") + + if self.environment.is_async: + self.write("))") + + def visit_Slice(self, node: nodes.Slice, frame: Frame) -> None: + if node.start is not None: + self.visit(node.start, frame) + self.write(":") + if node.stop is not None: + self.visit(node.stop, frame) + if node.step is not None: + self.write(":") + self.visit(node.step, frame) + + @contextmanager + def _filter_test_common( + self, node: t.Union[nodes.Filter, nodes.Test], frame: Frame, is_filter: bool + ) -> t.Iterator[None]: + if self.environment.is_async: + self.write("(await auto_await(") + + if is_filter: + self.write(f"{self.filters[node.name]}(") + func = self.environment.filters.get(node.name) + else: + self.write(f"{self.tests[node.name]}(") + func = self.environment.tests.get(node.name) + + # When inside an If or CondExpr frame, allow the filter to be + # undefined at compile time and only raise an error if it's + # actually called at runtime. See pull_dependencies. + if func is None and not frame.soft_frame: + type_name = "filter" if is_filter else "test" + self.fail(f"No {type_name} named {node.name!r}.", node.lineno) + + pass_arg = { + _PassArg.context: "context", + _PassArg.eval_context: "context.eval_ctx", + _PassArg.environment: "environment", + }.get( + _PassArg.from_obj(func) # type: ignore + ) + + if pass_arg is not None: + self.write(f"{pass_arg}, ") + + # Back to the visitor function to handle visiting the target of + # the filter or test. + yield + + self.signature(node, frame) + self.write(")") + + if self.environment.is_async: + self.write("))") + + @optimizeconst + def visit_Filter(self, node: nodes.Filter, frame: Frame) -> None: + with self._filter_test_common(node, frame, True): + # if the filter node is None we are inside a filter block + # and want to write to the current buffer + if node.node is not None: + self.visit(node.node, frame) + elif frame.eval_ctx.volatile: + self.write( + f"(Markup(concat({frame.buffer}))" + f" if context.eval_ctx.autoescape else concat({frame.buffer}))" + ) + elif frame.eval_ctx.autoescape: + self.write(f"Markup(concat({frame.buffer}))") + else: + self.write(f"concat({frame.buffer})") + + @optimizeconst + def visit_Test(self, node: nodes.Test, frame: Frame) -> None: + with self._filter_test_common(node, frame, False): + self.visit(node.node, frame) + + @optimizeconst + def visit_CondExpr(self, node: nodes.CondExpr, frame: Frame) -> None: + frame = frame.soft() + + def write_expr2() -> None: + if node.expr2 is not None: + self.visit(node.expr2, frame) + return + + self.write( + f'cond_expr_undefined("the inline if-expression on' + f" {self.position(node)} evaluated to false and no else" + f' section was defined.")' + ) + + self.write("(") + self.visit(node.expr1, frame) + self.write(" if ") + self.visit(node.test, frame) + self.write(" else ") + write_expr2() + self.write(")") + + @optimizeconst + def visit_Call( + self, node: nodes.Call, frame: Frame, forward_caller: bool = False + ) -> None: + if self.environment.is_async: + self.write("(await auto_await(") + if self.environment.sandboxed: + self.write("environment.call(context, ") + else: + self.write("context.call(") + self.visit(node.node, frame) + extra_kwargs = {"caller": "caller"} if forward_caller else None + loop_kwargs = {"_loop_vars": "_loop_vars"} if frame.loop_frame else {} + block_kwargs = {"_block_vars": "_block_vars"} if frame.block_frame else {} + if extra_kwargs: + extra_kwargs.update(loop_kwargs, **block_kwargs) + elif loop_kwargs or block_kwargs: + extra_kwargs = dict(loop_kwargs, **block_kwargs) + self.signature(node, frame, extra_kwargs) + self.write(")") + if self.environment.is_async: + self.write("))") + + def visit_Keyword(self, node: nodes.Keyword, frame: Frame) -> None: + self.write(node.key + "=") + self.visit(node.value, frame) + + # -- Unused nodes for extensions + + def visit_MarkSafe(self, node: nodes.MarkSafe, frame: Frame) -> None: + self.write("Markup(") + self.visit(node.expr, frame) + self.write(")") + + def visit_MarkSafeIfAutoescape( + self, node: nodes.MarkSafeIfAutoescape, frame: Frame + ) -> None: + self.write("(Markup if context.eval_ctx.autoescape else identity)(") + self.visit(node.expr, frame) + self.write(")") + + def visit_EnvironmentAttribute( + self, node: nodes.EnvironmentAttribute, frame: Frame + ) -> None: + self.write("environment." + node.name) + + def visit_ExtensionAttribute( + self, node: nodes.ExtensionAttribute, frame: Frame + ) -> None: + self.write(f"environment.extensions[{node.identifier!r}].{node.name}") + + def visit_ImportedName(self, node: nodes.ImportedName, frame: Frame) -> None: + self.write(self.import_aliases[node.importname]) + + def visit_InternalName(self, node: nodes.InternalName, frame: Frame) -> None: + self.write(node.name) + + def visit_ContextReference( + self, node: nodes.ContextReference, frame: Frame + ) -> None: + self.write("context") + + def visit_DerivedContextReference( + self, node: nodes.DerivedContextReference, frame: Frame + ) -> None: + self.write(self.derive_context(frame)) + + def visit_Continue(self, node: nodes.Continue, frame: Frame) -> None: + self.writeline("continue", node) + + def visit_Break(self, node: nodes.Break, frame: Frame) -> None: + self.writeline("break", node) + + def visit_Scope(self, node: nodes.Scope, frame: Frame) -> None: + scope_frame = frame.inner() + scope_frame.symbols.analyze_node(node) + self.enter_frame(scope_frame) + self.blockvisit(node.body, scope_frame) + self.leave_frame(scope_frame) + + def visit_OverlayScope(self, node: nodes.OverlayScope, frame: Frame) -> None: + ctx = self.temporary_identifier() + self.writeline(f"{ctx} = {self.derive_context(frame)}") + self.writeline(f"{ctx}.vars = ") + self.visit(node.context, frame) + self.push_context_reference(ctx) + + scope_frame = frame.inner(isolated=True) + scope_frame.symbols.analyze_node(node) + self.enter_frame(scope_frame) + self.blockvisit(node.body, scope_frame) + self.leave_frame(scope_frame) + self.pop_context_reference() + + def visit_EvalContextModifier( + self, node: nodes.EvalContextModifier, frame: Frame + ) -> None: + for keyword in node.options: + self.writeline(f"context.eval_ctx.{keyword.key} = ") + self.visit(keyword.value, frame) + try: + val = keyword.value.as_const(frame.eval_ctx) + except nodes.Impossible: + frame.eval_ctx.volatile = True + else: + setattr(frame.eval_ctx, keyword.key, val) + + def visit_ScopedEvalContextModifier( + self, node: nodes.ScopedEvalContextModifier, frame: Frame + ) -> None: + old_ctx_name = self.temporary_identifier() + saved_ctx = frame.eval_ctx.save() + self.writeline(f"{old_ctx_name} = context.eval_ctx.save()") + self.visit_EvalContextModifier(node, frame) + for child in node.body: + self.visit(child, frame) + frame.eval_ctx.revert(saved_ctx) + self.writeline(f"context.eval_ctx.revert({old_ctx_name})") diff --git a/lib/go-jinja2/internal/data/darwin-amd64/jinja2/constants.py b/lib/go-jinja2/internal/data/darwin-amd64/jinja2/constants.py new file mode 100644 index 000000000..41a1c23b0 --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/jinja2/constants.py @@ -0,0 +1,20 @@ +#: list of lorem ipsum words used by the lipsum() helper function +LOREM_IPSUM_WORDS = """\ +a ac accumsan ad adipiscing aenean aliquam aliquet amet ante aptent arcu at +auctor augue bibendum blandit class commodo condimentum congue consectetuer +consequat conubia convallis cras cubilia cum curabitur curae cursus dapibus +diam dictum dictumst dignissim dis dolor donec dui duis egestas eget eleifend +elementum elit enim erat eros est et etiam eu euismod facilisi facilisis fames +faucibus felis fermentum feugiat fringilla fusce gravida habitant habitasse hac +hendrerit hymenaeos iaculis id imperdiet in inceptos integer interdum ipsum +justo lacinia lacus laoreet lectus leo libero ligula litora lobortis lorem +luctus maecenas magna magnis malesuada massa mattis mauris metus mi molestie +mollis montes morbi mus nam nascetur natoque nec neque netus nibh nisi nisl non +nonummy nostra nulla nullam nunc odio orci ornare parturient pede pellentesque +penatibus per pharetra phasellus placerat platea porta porttitor posuere +potenti praesent pretium primis proin pulvinar purus quam quis quisque rhoncus +ridiculus risus rutrum sagittis sapien scelerisque sed sem semper senectus sit +sociis sociosqu sodales sollicitudin suscipit suspendisse taciti tellus tempor +tempus tincidunt torquent tortor tristique turpis ullamcorper ultrices +ultricies urna ut varius vehicula vel velit venenatis vestibulum vitae vivamus +viverra volutpat vulputate""" diff --git a/lib/go-jinja2/internal/data/darwin-amd64/jinja2/debug.py b/lib/go-jinja2/internal/data/darwin-amd64/jinja2/debug.py new file mode 100644 index 000000000..7ed7e9297 --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/jinja2/debug.py @@ -0,0 +1,191 @@ +import sys +import typing as t +from types import CodeType +from types import TracebackType + +from .exceptions import TemplateSyntaxError +from .utils import internal_code +from .utils import missing + +if t.TYPE_CHECKING: + from .runtime import Context + + +def rewrite_traceback_stack(source: t.Optional[str] = None) -> BaseException: + """Rewrite the current exception to replace any tracebacks from + within compiled template code with tracebacks that look like they + came from the template source. + + This must be called within an ``except`` block. + + :param source: For ``TemplateSyntaxError``, the original source if + known. + :return: The original exception with the rewritten traceback. + """ + _, exc_value, tb = sys.exc_info() + exc_value = t.cast(BaseException, exc_value) + tb = t.cast(TracebackType, tb) + + if isinstance(exc_value, TemplateSyntaxError) and not exc_value.translated: + exc_value.translated = True + exc_value.source = source + # Remove the old traceback, otherwise the frames from the + # compiler still show up. + exc_value.with_traceback(None) + # Outside of runtime, so the frame isn't executing template + # code, but it still needs to point at the template. + tb = fake_traceback( + exc_value, None, exc_value.filename or "", exc_value.lineno + ) + else: + # Skip the frame for the render function. + tb = tb.tb_next + + stack = [] + + # Build the stack of traceback object, replacing any in template + # code with the source file and line information. + while tb is not None: + # Skip frames decorated with @internalcode. These are internal + # calls that aren't useful in template debugging output. + if tb.tb_frame.f_code in internal_code: + tb = tb.tb_next + continue + + template = tb.tb_frame.f_globals.get("__jinja_template__") + + if template is not None: + lineno = template.get_corresponding_lineno(tb.tb_lineno) + fake_tb = fake_traceback(exc_value, tb, template.filename, lineno) + stack.append(fake_tb) + else: + stack.append(tb) + + tb = tb.tb_next + + tb_next = None + + # Assign tb_next in reverse to avoid circular references. + for tb in reversed(stack): + tb.tb_next = tb_next + tb_next = tb + + return exc_value.with_traceback(tb_next) + + +def fake_traceback( # type: ignore + exc_value: BaseException, tb: t.Optional[TracebackType], filename: str, lineno: int +) -> TracebackType: + """Produce a new traceback object that looks like it came from the + template source instead of the compiled code. The filename, line + number, and location name will point to the template, and the local + variables will be the current template context. + + :param exc_value: The original exception to be re-raised to create + the new traceback. + :param tb: The original traceback to get the local variables and + code info from. + :param filename: The template filename. + :param lineno: The line number in the template source. + """ + if tb is not None: + # Replace the real locals with the context that would be + # available at that point in the template. + locals = get_template_locals(tb.tb_frame.f_locals) + locals.pop("__jinja_exception__", None) + else: + locals = {} + + globals = { + "__name__": filename, + "__file__": filename, + "__jinja_exception__": exc_value, + } + # Raise an exception at the correct line number. + code: CodeType = compile( + "\n" * (lineno - 1) + "raise __jinja_exception__", filename, "exec" + ) + + # Build a new code object that points to the template file and + # replaces the location with a block name. + location = "template" + + if tb is not None: + function = tb.tb_frame.f_code.co_name + + if function == "root": + location = "top-level template code" + elif function.startswith("block_"): + location = f"block {function[6:]!r}" + + if sys.version_info >= (3, 8): + code = code.replace(co_name=location) + else: + code = CodeType( + code.co_argcount, + code.co_kwonlyargcount, + code.co_nlocals, + code.co_stacksize, + code.co_flags, + code.co_code, + code.co_consts, + code.co_names, + code.co_varnames, + code.co_filename, + location, + code.co_firstlineno, + code.co_lnotab, + code.co_freevars, + code.co_cellvars, + ) + + # Execute the new code, which is guaranteed to raise, and return + # the new traceback without this frame. + try: + exec(code, globals, locals) + except BaseException: + return sys.exc_info()[2].tb_next # type: ignore + + +def get_template_locals(real_locals: t.Mapping[str, t.Any]) -> t.Dict[str, t.Any]: + """Based on the runtime locals, get the context that would be + available at that point in the template. + """ + # Start with the current template context. + ctx: "t.Optional[Context]" = real_locals.get("context") + + if ctx is not None: + data: t.Dict[str, t.Any] = ctx.get_all().copy() + else: + data = {} + + # Might be in a derived context that only sets local variables + # rather than pushing a context. Local variables follow the scheme + # l_depth_name. Find the highest-depth local that has a value for + # each name. + local_overrides: t.Dict[str, t.Tuple[int, t.Any]] = {} + + for name, value in real_locals.items(): + if not name.startswith("l_") or value is missing: + # Not a template variable, or no longer relevant. + continue + + try: + _, depth_str, name = name.split("_", 2) + depth = int(depth_str) + except ValueError: + continue + + cur_depth = local_overrides.get(name, (-1,))[0] + + if cur_depth < depth: + local_overrides[name] = (depth, value) + + # Modify the context with any derived context. + for name, (_, value) in local_overrides.items(): + if value is missing: + data.pop(name, None) + else: + data[name] = value + + return data diff --git a/lib/go-jinja2/internal/data/darwin-amd64/jinja2/defaults.py b/lib/go-jinja2/internal/data/darwin-amd64/jinja2/defaults.py new file mode 100644 index 000000000..638cad3d2 --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/jinja2/defaults.py @@ -0,0 +1,48 @@ +import typing as t + +from .filters import FILTERS as DEFAULT_FILTERS # noqa: F401 +from .tests import TESTS as DEFAULT_TESTS # noqa: F401 +from .utils import Cycler +from .utils import generate_lorem_ipsum +from .utils import Joiner +from .utils import Namespace + +if t.TYPE_CHECKING: + import typing_extensions as te + +# defaults for the parser / lexer +BLOCK_START_STRING = "{%" +BLOCK_END_STRING = "%}" +VARIABLE_START_STRING = "{{" +VARIABLE_END_STRING = "}}" +COMMENT_START_STRING = "{#" +COMMENT_END_STRING = "#}" +LINE_STATEMENT_PREFIX: t.Optional[str] = None +LINE_COMMENT_PREFIX: t.Optional[str] = None +TRIM_BLOCKS = False +LSTRIP_BLOCKS = False +NEWLINE_SEQUENCE: "te.Literal['\\n', '\\r\\n', '\\r']" = "\n" +KEEP_TRAILING_NEWLINE = False + +# default filters, tests and namespace + +DEFAULT_NAMESPACE = { + "range": range, + "dict": dict, + "lipsum": generate_lorem_ipsum, + "cycler": Cycler, + "joiner": Joiner, + "namespace": Namespace, +} + +# default policies +DEFAULT_POLICIES: t.Dict[str, t.Any] = { + "compiler.ascii_str": True, + "urlize.rel": "noopener", + "urlize.target": None, + "urlize.extra_schemes": None, + "truncate.leeway": 5, + "json.dumps_function": None, + "json.dumps_kwargs": {"sort_keys": True}, + "ext.i18n.trimmed": False, +} diff --git a/lib/go-jinja2/internal/data/darwin-amd64/jinja2/environment.py b/lib/go-jinja2/internal/data/darwin-amd64/jinja2/environment.py new file mode 100644 index 000000000..1d3be0bed --- /dev/null +++ b/lib/go-jinja2/internal/data/darwin-amd64/jinja2/environment.py @@ -0,0 +1,1675 @@ +"""Classes for managing templates and their runtime and compile time +options. +""" + +import os +import typing +import typing as t +import weakref +from collections import ChainMap +from functools import lru_cache +from functools import partial +from functools import reduce +from types import CodeType + +from markupsafe import Markup + +from . import nodes +from .compiler import CodeGenerator +from .compiler import generate +from .defaults import BLOCK_END_STRING +from .defaults import BLOCK_START_STRING +from .defaults import COMMENT_END_STRING +from .defaults import COMMENT_START_STRING +from .defaults import DEFAULT_FILTERS # type: ignore[attr-defined] +from .defaults import DEFAULT_NAMESPACE +from .defaults import DEFAULT_POLICIES +from .defaults import DEFAULT_TESTS # type: ignore[attr-defined] +from .defaults import KEEP_TRAILING_NEWLINE +from .defaults import LINE_COMMENT_PREFIX +from .defaults import LINE_STATEMENT_PREFIX +from .defaults import LSTRIP_BLOCKS +from .defaults import NEWLINE_SEQUENCE +from .defaults import TRIM_BLOCKS +from .defaults import VARIABLE_END_STRING +from .defaults import VARIABLE_START_STRING +from .exceptions import TemplateNotFound +from .exceptions import TemplateRuntimeError +from .exceptions import TemplatesNotFound +from .exceptions import TemplateSyntaxError +from .exceptions import UndefinedError +from .lexer import get_lexer +from .lexer import Lexer +from .lexer import TokenStream +from .nodes import EvalContext +from .parser import Parser +from .runtime import Context +from .runtime import new_context +from .runtime import Undefined +from .utils import _PassArg +from .utils import concat +from .utils import consume +from .utils import import_string +from .utils import internalcode +from .utils import LRUCache +from .utils import missing + +if t.TYPE_CHECKING: + import typing_extensions as te + + from .bccache import BytecodeCache + from .ext import Extension + from .loaders import BaseLoader + +_env_bound = t.TypeVar("_env_bound", bound="Environment") + + +# for direct template usage we have up to ten living environments +@lru_cache(maxsize=10) +def get_spontaneous_environment(cls: t.Type[_env_bound], *args: t.Any) -> _env_bound: + """Return a new spontaneous environment. A spontaneous environment + is used for templates created directly rather than through an + existing environment. + + :param cls: Environment class to create. + :param args: Positional arguments passed to environment. + """ + env = cls(*args) + env.shared = True + return env + + +def create_cache( + size: int, +) -> t.Optional[t.MutableMapping[t.Tuple["weakref.ref[t.Any]", str], "Template"]]: + """Return the cache class for the given size.""" + if size == 0: + return None + + if size < 0: + return {} + + return LRUCache(size) # type: ignore + + +def copy_cache( + cache: t.Optional[t.MutableMapping[t.Any, t.Any]], +) -> t.Optional[t.MutableMapping[t.Tuple["weakref.ref[t.Any]", str], "Template"]]: + """Create an empty copy of the given cache.""" + if cache is None: + return None + + if type(cache) is dict: # noqa E721 + return {} + + return LRUCache(cache.capacity) # type: ignore + + +def load_extensions( + environment: "Environment", + extensions: t.Sequence[t.Union[str, t.Type["Extension"]]], +) -> t.Dict[str, "Extension"]: + """Load the extensions from the list and bind it to the environment. + Returns a dict of instantiated extensions. + """ + result = {} + + for extension in extensions: + if isinstance(extension, str): + extension = t.cast(t.Type["Extension"], import_string(extension)) + + result[extension.identifier] = extension(environment) + + return result + + +def _environment_config_check(environment: "Environment") -> "Environment": + """Perform a sanity check on the environment.""" + assert issubclass( + environment.undefined, Undefined + ), "'undefined' must be a subclass of 'jinja2.Undefined'." + assert ( + environment.block_start_string + != environment.variable_start_string + != environment.comment_start_string + ), "block, variable and comment start strings must be different." + assert environment.newline_sequence in { + "\r", + "\r\n", + "\n", + }, "'newline_sequence' must be one of '\\n', '\\r\\n', or '\\r'." + return environment + + +class Environment: + r"""The core component of Jinja is the `Environment`. It contains + important shared variables like configuration, filters, tests, + globals and others. Instances of this class may be modified if + they are not shared and if no template was loaded so far. + Modifications on environments after the first template was loaded + will lead to surprising effects and undefined behavior. + + Here are the possible initialization parameters: + + `block_start_string` + The string marking the beginning of a block. Defaults to ``'{%'``. + + `block_end_string` + The string marking the end of a block. Defaults to ``'%}'``. + + `variable_start_string` + The string marking the beginning of a print statement. + Defaults to ``'{{'``. + + `variable_end_string` + The string marking the end of a print statement. Defaults to + ``'}}'``. + + `comment_start_string` + The string marking the beginning of a comment. Defaults to ``'{#'``. + + `comment_end_string` + The string marking the end of a comment. Defaults to ``'#}'``. + + `line_statement_prefix` + If given and a string, this will be used as prefix for line based + statements. See also :ref:`line-statements`. + + `line_comment_prefix` + If given and a string, this will be used as prefix for line based + comments. See also :ref:`line-statements`. + + .. versionadded:: 2.2 + + `trim_blocks` + If this is set to ``True`` the first newline after a block is + removed (block, not variable tag!). Defaults to `False`. + + `lstrip_blocks` + If this is set to ``True`` leading spaces and tabs are stripped + from the start of a line to a block. Defaults to `False`. + + `newline_sequence` + The sequence that starts a newline. Must be one of ``'\r'``, + ``'\n'`` or ``'\r\n'``. The default is ``'\n'`` which is a + useful default for Linux and OS X systems as well as web + applications. + + `keep_trailing_newline` + Preserve the trailing newline when rendering templates. + The default is ``False``, which causes a single newline, + if present, to be stripped from the end of the template. + + .. versionadded:: 2.7 + + `extensions` + List of Jinja extensions to use. This can either be import paths + as strings or extension classes. For more information have a + look at :ref:`the extensions documentation `. + + `optimized` + should the optimizer be enabled? Default is ``True``. + + `undefined` + :class:`Undefined` or a subclass of it that is used to represent + undefined values in the template. + + `finalize` + A callable that can be used to process the result of a variable + expression before it is output. For example one can convert + ``None`` implicitly into an empty string here. + + `autoescape` + If set to ``True`` the XML/HTML autoescaping feature is enabled by + default. For more details about autoescaping see + :class:`~markupsafe.Markup`. As of Jinja 2.4 this can also + be a callable that is passed the template name and has to + return ``True`` or ``False`` depending on autoescape should be + enabled by default. + + .. versionchanged:: 2.4 + `autoescape` can now be a function + + `loader` + The template loader for this environment. + + `cache_size` + The size of the cache. Per default this is ``400`` which means + that if more than 400 templates are loaded the loader will clean + out the least recently used template. If the cache size is set to + ``0`` templates are recompiled all the time, if the cache size is + ``-1`` the cache will not be cleaned. + + .. versionchanged:: 2.8 + The cache size was increased to 400 from a low 50. + + `auto_reload` + Some loaders load templates from locations where the template + sources may change (ie: file system or database). If + ``auto_reload`` is set to ``True`` (default) every time a template is + requested the loader checks if the source changed and if yes, it + will reload the template. For higher performance it's possible to + disable that. + + `bytecode_cache` + If set to a bytecode cache object, this object will provide a + cache for the internal Jinja bytecode so that templates don't + have to be parsed if they were not changed. + + See :ref:`bytecode-cache` for more information. + + `enable_async` + If set to true this enables async template execution which + allows using async functions and generators. + """ + + #: if this environment is sandboxed. Modifying this variable won't make + #: the environment sandboxed though. For a real sandboxed environment + #: have a look at jinja2.sandbox. This flag alone controls the code + #: generation by the compiler. + sandboxed = False + + #: True if the environment is just an overlay + overlayed = False + + #: the environment this environment is linked to if it is an overlay + linked_to: t.Optional["Environment"] = None + + #: shared environments have this set to `True`. A shared environment + #: must not be modified + shared = False + + #: the class that is used for code generation. See + #: :class:`~jinja2.compiler.CodeGenerator` for more information. + code_generator_class: t.Type["CodeGenerator"] = CodeGenerator + + concat = "".join + + #: the context class that is used for templates. See + #: :class:`~jinja2.runtime.Context` for more information. + context_class: t.Type[Context] = Context + + template_class: t.Type["Template"] + + def __init__( + self, + block_start_string: str = BLOCK_START_STRING, + block_end_string: str = BLOCK_END_STRING, + variable_start_string: str = VARIABLE_START_STRING, + variable_end_string: str = VARIABLE_END_STRING, + comment_start_string: str = COMMENT_START_STRING, + comment_end_string: str = COMMENT_END_STRING, + line_statement_prefix: t.Optional[str] = LINE_STATEMENT_PREFIX, + line_comment_prefix: t.Optional[str] = LINE_COMMENT_PREFIX, + trim_blocks: bool = TRIM_BLOCKS, + lstrip_blocks: bool = LSTRIP_BLOCKS, + newline_sequence: "te.Literal['\\n', '\\r\\n', '\\r']" = NEWLINE_SEQUENCE, + keep_trailing_newline: bool = KEEP_TRAILING_NEWLINE, + extensions: t.Sequence[t.Union[str, t.Type["Extension"]]] = (), + optimized: bool = True, + undefined: t.Type[Undefined] = Undefined, + finalize: t.Optional[t.Callable[..., t.Any]] = None, + autoescape: t.Union[bool, t.Callable[[t.Optional[str]], bool]] = False, + loader: t.Optional["BaseLoader"] = None, + cache_size: int = 400, + auto_reload: bool = True, + bytecode_cache: t.Optional["BytecodeCache"] = None, + enable_async: bool = False, + ): + # !!Important notice!! + # The constructor accepts quite a few arguments that should be + # passed by keyword rather than position. However it's important to + # not change the order of arguments because it's used at least + # internally in those cases: + # - spontaneous environments (i18n extension and Template) + # - unittests + # If parameter changes are required only add parameters at the end + # and don't change the arguments (or the defaults!) of the arguments + # existing already. + + # lexer / parser information + self.block_start_string = block_start_string + self.block_end_string = block_end_string + self.variable_start_string = variable_start_string + self.variable_end_string = variable_end_string + self.comment_start_string = comment_start_string + self.comment_end_string = comment_end_string + self.line_statement_prefix = line_statement_prefix + self.line_comment_prefix = line_comment_prefix + self.trim_blocks = trim_blocks + self.lstrip_blocks = lstrip_blocks + self.newline_sequence = newline_sequence + self.keep_trailing_newline = keep_trailing_newline + + # runtime information + self.undefined: t.Type[Undefined] = undefined + self.optimized = optimized + self.finalize = finalize + self.autoescape = autoescape + + # defaults + self.filters = DEFAULT_FILTERS.copy() + self.tests = DEFAULT_TESTS.copy() + self.globals = DEFAULT_NAMESPACE.copy() + + # set the loader provided + self.loader = loader + self.cache = create_cache(cache_size) + self.bytecode_cache = bytecode_cache + self.auto_reload = auto_reload + + # configurable policies + self.policies = DEFAULT_POLICIES.copy() + + # load extensions + self.extensions = load_extensions(self, extensions) + + self.is_async = enable_async + _environment_config_check(self) + + def add_extension(self, extension: t.Union[str, t.Type["Extension"]]) -> None: + """Adds an extension after the environment was created. + + .. versionadded:: 2.5 + """ + self.extensions.update(load_extensions(self, [extension])) + + def extend(self, **attributes: t.Any) -> None: + """Add the items to the instance of the environment if they do not exist + yet. This is used by :ref:`extensions ` to register + callbacks and configuration values without breaking inheritance. + """ + for key, value in attributes.items(): + if not hasattr(self, key): + setattr(self, key, value) + + def overlay( + self, + block_start_string: str = missing, + block_end_string: str = missing, + variable_start_string: str = missing, + variable_end_string: str = missing, + comment_start_string: str = missing, + comment_end_string: str = missing, + line_statement_prefix: t.Optional[str] = missing, + line_comment_prefix: t.Optional[str] = missing, + trim_blocks: bool = missing, + lstrip_blocks: bool = missing, + newline_sequence: "te.Literal['\\n', '\\r\\n', '\\r']" = missing, + keep_trailing_newline: bool = missing, + extensions: t.Sequence[t.Union[str, t.Type["Extension"]]] = missing, + optimized: bool = missing, + undefined: t.Type[Undefined] = missing, + finalize: t.Optional[t.Callable[..., t.Any]] = missing, + autoescape: t.Union[bool, t.Callable[[t.Optional[str]], bool]] = missing, + loader: t.Optional["BaseLoader"] = missing, + cache_size: int = missing, + auto_reload: bool = missing, + bytecode_cache: t.Optional["BytecodeCache"] = missing, + enable_async: bool = False, + ) -> "Environment": + """Create a new overlay environment that shares all the data with the + current environment except for cache and the overridden attributes. + Extensions cannot be removed for an overlayed environment. An overlayed + environment automatically gets all the extensions of the environment it + is linked to plus optional extra extensions. + + Creating overlays should happen after the initial environment was set + up completely. Not all attributes are truly linked, some are just + copied over so modifications on the original environment may not shine + through. + + .. versionchanged:: 3.1.2 + Added the ``newline_sequence``,, ``keep_trailing_newline``, + and ``enable_async`` parameters to match ``__init__``. + """ + args = dict(locals()) + del args["self"], args["cache_size"], args["extensions"], args["enable_async"] + + rv = object.__new__(self.__class__) + rv.__dict__.update(self.__dict__) + rv.overlayed = True + rv.linked_to = self + + for key, value in args.items(): + if value is not missing: + setattr(rv, key, value) + + if cache_size is not missing: + rv.cache = create_cache(cache_size) + else: + rv.cache = copy_cache(self.cache) + + rv.extensions = {} + for key, value in self.extensions.items(): + rv.extensions[key] = value.bind(rv) + if extensions is not missing: + rv.extensions.update(load_extensions(rv, extensions)) + + if enable_async is not missing: + rv.is_async = enable_async + + return _environment_config_check(rv) + + @property + def lexer(self) -> Lexer: + """The lexer for this environment.""" + return get_lexer(self) + + def iter_extensions(self) -> t.Iterator["Extension"]: + """Iterates over the extensions by priority.""" + return iter(sorted(self.extensions.values(), key=lambda x: x.priority)) + + def getitem( + self, obj: t.Any, argument: t.Union[str, t.Any] + ) -> t.Union[t.Any, Undefined]: + """Get an item or attribute of an object but prefer the item.""" + try: + return obj[argument] + except (AttributeError, TypeError, LookupError): + if isinstance(argument, str): + try: + attr = str(argument) + except Exception: + pass + else: + try: + return getattr(obj, attr) + except AttributeError: + pass + return self.undefined(obj=obj, name=argument) + + def getattr(self, obj: t.Any, attribute: str) -> t.Any: + """Get an item or attribute of an object but prefer the attribute. + Unlike :meth:`getitem` the attribute *must* be a string. + """ + try: + return getattr(obj, attribute) + except AttributeError: + pass + try: + return obj[attribute] + except (TypeError, LookupError, AttributeError): + return self.undefined(obj=obj, name=attribute) + + def _filter_test_common( + self, + name: t.Union[str, Undefined], + value: t.Any, + args: t.Optional[t.Sequence[t.Any]], + kwargs: t.Optional[t.Mapping[str, t.Any]], + context: t.Optional[Context], + eval_ctx: t.Optional[EvalContext], + is_filter: bool, + ) -> t.Any: + if is_filter: + env_map = self.filters + type_name = "filter" + else: + env_map = self.tests + type_name = "test" + + func = env_map.get(name) # type: ignore + + if func is None: + msg = f"No {type_name} named {name!r}." + + if isinstance(name, Undefined): + try: + name._fail_with_undefined_error() + except Exception as e: + msg = f"{msg} ({e}; did you forget to quote the callable name?)" + + raise TemplateRuntimeError(msg) + + args = [value, *(args if args is not None else ())] + kwargs = kwargs if kwargs is not None else {} + pass_arg = _PassArg.from_obj(func) + + if pass_arg is _PassArg.context: + if context is None: + raise TemplateRuntimeError( + f"Attempted to invoke a context {type_name} without context." + ) + + args.insert(0, context) + elif pass_arg is _PassArg.eval_context: + if eval_ctx is None: + if context is not None: + eval_ctx = context.eval_ctx + else: + eval_ctx = EvalContext(self) + + args.insert(0, eval_ctx) + elif pass_arg is _PassArg.environment: + args.insert(0, self) + + return func(*args, **kwargs) + + def call_filter( + self, + name: str, + value: t.Any, + args: t.Optional[t.Sequence[t.Any]] = None, + kwargs: t.Optional[t.Mapping[str, t.Any]] = None, + context: t.Optional[Context] = None, + eval_ctx: t.Optional[EvalContext] = None, + ) -> t.Any: + """Invoke a filter on a value the same way the compiler does. + + This might return a coroutine if the filter is running from an + environment in async mode and the filter supports async + execution. It's your responsibility to await this if needed. + + .. versionadded:: 2.7 + """ + return self._filter_test_common( + name, value, args, kwargs, context, eval_ctx, True + ) + + def call_test( + self, + name: str, + value: t.Any, + args: t.Optional[t.Sequence[t.Any]] = None, + kwargs: t.Optional[t.Mapping[str, t.Any]] = None, + context: t.Optional[Context] = None, + eval_ctx: t.Optional[EvalContext] = None, + ) -> t.Any: + """Invoke a test on a value the same way the compiler does. + + This might return a coroutine if the test is running from an + environment in async mode and the test supports async execution. + It's your responsibility to await this if needed. + + .. versionchanged:: 3.0 + Tests support ``@pass_context``, etc. decorators. Added + the ``context`` and ``eval_ctx`` parameters. + + .. versionadded:: 2.7 + """ + return self._filter_test_common( + name, value, args, kwargs, context, eval_ctx, False + ) + + @internalcode + def parse( + self, + source: str, + name: t.Optional[str] = None, + filename: t.Optional[str] = None, + ) -> nodes.Template: + """Parse the sourcecode and return the abstract syntax tree. This + tree of nodes is used by the compiler to convert the template into + executable source- or bytecode. This is useful for debugging or to + extract information from templates. + + If you are :ref:`developing Jinja extensions ` + this gives you a good overview of the node tree generated. + """ + try: + return self._parse(source, name, filename) + except TemplateSyntaxError: + self.handle_exception(source=source) + + def _parse( + self, source: str, name: t.Optional[str], filename: t.Optional[str] + ) -> nodes.Template: + """Internal parsing function used by `parse` and `compile`.""" + return Parser(self, source, name, filename).parse() + + def lex( + self, + source: str, + name: t.Optional[str] = None, + filename: t.Optional[str] = None, + ) -> t.Iterator[t.Tuple[int, str, str]]: + """Lex the given sourcecode and return a generator that yields + tokens as tuples in the form ``(lineno, token_type, value)``. + This can be useful for :ref:`extension development ` + and debugging templates. + + This does not perform preprocessing. If you want the preprocessing + of the extensions to be applied you have to filter source through + the :meth:`preprocess` method. + """ + source = str(source) + try: + return self.lexer.tokeniter(source, name, filename) + except TemplateSyntaxError: + self.handle_exception(source=source) + + def preprocess( + self, + source: str, + name: t.Optional[str] = None, + filename: t.Optional[str] = None, + ) -> str: + """Preprocesses the source with all extensions. This is automatically + called for all parsing and compiling methods but *not* for :meth:`lex` + because there you usually only want the actual source tokenized. + """ + return reduce( + lambda s, e: e.preprocess(s, name, filename), + self.iter_extensions(), + str(source), + ) + + def _tokenize( + self, + source: str, + name: t.Optional[str], + filename: t.Optional[str] = None, + state: t.Optional[str] = None, + ) -> TokenStream: + """Called by the parser to do the preprocessing and filtering + for all the extensions. Returns a :class:`~jinja2.lexer.TokenStream`. + """ + source = self.preprocess(source, name, filename) + stream = self.lexer.tokenize(source, name, filename, state) + + for ext in self.iter_extensions(): + stream = ext.filter_stream(stream) # type: ignore + + if not isinstance(stream, TokenStream): + stream = TokenStream(stream, name, filename) + + return stream + + def _generate( + self, + source: nodes.Template, + name: t.Optional[str], + filename: t.Optional[str], + defer_init: bool = False, + ) -> str: + """Internal hook that can be overridden to hook a different generate + method in. + + .. versionadded:: 2.5 + """ + return generate( # type: ignore + source, + self, + name, + filename, + defer_init=defer_init, + optimized=self.optimized, + ) + + def _compile(self, source: str, filename: str) -> CodeType: + """Internal hook that can be overridden to hook a different compile + method in. + + .. versionadded:: 2.5 + """ + return compile(source, filename, "exec") + + @typing.overload + def compile( # type: ignore + self, + source: t.Union[str, nodes.Template], + name: t.Optional[str] = None, + filename: t.Optional[str] = None, + raw: "te.Literal[False]" = False, + defer_init: bool = False, + ) -> CodeType: ... + + @typing.overload + def compile( + self, + source: t.Union[str, nodes.Template], + name: t.Optional[str] = None, + filename: t.Optional[str] = None, + raw: "te.Literal[True]" = ..., + defer_init: bool = False, + ) -> str: ... + + @internalcode + def compile( + self, + source: t.Union[str, nodes.Template], + name: t.Optional[str] = None, + filename: t.Optional[str] = None, + raw: bool = False, + defer_init: bool = False, + ) -> t.Union[str, CodeType]: + """Compile a node or template source code. The `name` parameter is + the load name of the template after it was joined using + :meth:`join_path` if necessary, not the filename on the file system. + the `filename` parameter is the estimated filename of the template on + the file system. If the template came from a database or memory this + can be omitted. + + The return value of this method is a python code object. If the `raw` + parameter is `True` the return value will be a string with python + code equivalent to the bytecode returned otherwise. This method is + mainly used internally. + + `defer_init` is use internally to aid the module code generator. This + causes the generated code to be able to import without the global + environment variable to be set. + + .. versionadded:: 2.4 + `defer_init` parameter added. + """ + source_hint = None + try: + if isinstance(source, str): + source_hint = source + source = self._parse(source, name, filename) + source = self._generate(source, name, filename, defer_init=defer_init) + if raw: + return source + if filename is None: + filename = "